Merge package:glob into the tools monorepo
diff --git a/.github/ISSUE_TEMPLATE/bazel_worker.md b/.github/ISSUE_TEMPLATE/bazel_worker.md
new file mode 100644
index 0000000..db70c0d
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bazel_worker.md
@@ -0,0 +1,5 @@
+---
+name: "package:bazel_worker"
+about: "Create a bug or file a feature request against package:bazel_worker."
+labels: "package:bazel_worker"
+---
diff --git a/.github/ISSUE_TEMPLATE/benchmark_harness.md b/.github/ISSUE_TEMPLATE/benchmark_harness.md
new file mode 100644
index 0000000..2f788b0
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/benchmark_harness.md
@@ -0,0 +1,5 @@
+---
+name: "package:benchmark_harness"
+about: "Create a bug or file a feature request against package:benchmark_harness."
+labels: "package:benchmark_harness"
+---
diff --git a/.github/ISSUE_TEMPLATE/boolean_selector.md b/.github/ISSUE_TEMPLATE/boolean_selector.md
new file mode 100644
index 0000000..0b7737a
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/boolean_selector.md
@@ -0,0 +1,5 @@
+---
+name: "package:boolean_selector"
+about: "Create a bug or file a feature request against package:boolean_selector."
+labels: "package:boolean_selector"
+---
diff --git a/.github/ISSUE_TEMPLATE/browser_launcher.md b/.github/ISSUE_TEMPLATE/browser_launcher.md
new file mode 100644
index 0000000..f893b1f
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/browser_launcher.md
@@ -0,0 +1,5 @@
+---
+name: "package:browser_launcher"
+about: "Create a bug or file a feature request against package:browser_launcher."
+labels: "package:browser_launcher"
+---
diff --git a/.github/ISSUE_TEMPLATE/cli_config.md b/.github/ISSUE_TEMPLATE/cli_config.md
new file mode 100644
index 0000000..c47fbcf
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/cli_config.md
@@ -0,0 +1,5 @@
+---
+name: "package:cli_config"
+about: "Create a bug or file a feature request against package:cli_config."
+labels: "package:cli_config"
+---
diff --git a/.github/ISSUE_TEMPLATE/cli_util.md b/.github/ISSUE_TEMPLATE/cli_util.md
new file mode 100644
index 0000000..ec090f1
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/cli_util.md
@@ -0,0 +1,5 @@
+---
+name: "package:cli_util"
+about: "Create a bug or file a feature request against package:cli_util."
+labels: "package:cli_util"
+---
diff --git a/.github/ISSUE_TEMPLATE/clock.md b/.github/ISSUE_TEMPLATE/clock.md
new file mode 100644
index 0000000..73dc6c7
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/clock.md
@@ -0,0 +1,5 @@
+---
+name: "package:clock"
+about: "Create a bug or file a feature request against package:clock."
+labels: "package:clock"
+---
diff --git a/.github/ISSUE_TEMPLATE/code_builder.md b/.github/ISSUE_TEMPLATE/code_builder.md
new file mode 100644
index 0000000..62f89f9
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/code_builder.md
@@ -0,0 +1,5 @@
+---
+name: "package:code_builder"
+about: "Create a bug or file a feature request against package:code_builder."
+labels: "package:code_builder"
+---
diff --git a/.github/ISSUE_TEMPLATE/coverage.md b/.github/ISSUE_TEMPLATE/coverage.md
new file mode 100644
index 0000000..d0b0fb7
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/coverage.md
@@ -0,0 +1,5 @@
+---
+name: "package:coverage"
+about: "Create a bug or file a feature request against package:coverage."
+labels: "package:coverage"
+---
diff --git a/.github/ISSUE_TEMPLATE/csslib.md b/.github/ISSUE_TEMPLATE/csslib.md
new file mode 100644
index 0000000..670d354
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/csslib.md
@@ -0,0 +1,5 @@
+---
+name: "package:csslib"
+about: "Create a bug or file a feature request against package:csslib."
+labels: "package:csslib"
+---
diff --git a/.github/ISSUE_TEMPLATE/extension_discovery.md b/.github/ISSUE_TEMPLATE/extension_discovery.md
new file mode 100644
index 0000000..165b7cb
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/extension_discovery.md
@@ -0,0 +1,5 @@
+---
+name: "package:extension_discovery"
+about: "Create a bug or file a feature request against package:extension_discovery."
+labels: "package:extension_discovery"
+---
diff --git a/.github/ISSUE_TEMPLATE/file.md b/.github/ISSUE_TEMPLATE/file.md
new file mode 100644
index 0000000..3430d7e
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/file.md
@@ -0,0 +1,5 @@
+---
+name: "package:file"
+about: "Create a bug or file a feature request against package:file."
+labels: "package:file"
+---
diff --git a/.github/ISSUE_TEMPLATE/graphs.md b/.github/ISSUE_TEMPLATE/graphs.md
new file mode 100644
index 0000000..b4cb60f
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/graphs.md
@@ -0,0 +1,5 @@
+---
+name: "package:graphs"
+about: "Create a bug or file a feature request against package:graphs."
+labels: "package:graphs"
+---
diff --git a/.github/ISSUE_TEMPLATE/html.md b/.github/ISSUE_TEMPLATE/html.md
new file mode 100644
index 0000000..b128343
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/html.md
@@ -0,0 +1,5 @@
+---
+name: "package:html"
+about: "Create a bug or file a feature request against package:html."
+labels: "package:html"
+---
diff --git a/.github/ISSUE_TEMPLATE/io.md b/.github/ISSUE_TEMPLATE/io.md
new file mode 100644
index 0000000..5646f0f
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/io.md
@@ -0,0 +1,5 @@
+---
+name: "package:io"
+about: "Create a bug or file a feature request against package:io."
+labels: "package:io"
+---
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/json_rpc_2.md b/.github/ISSUE_TEMPLATE/json_rpc_2.md
new file mode 100644
index 0000000..b4dec80
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/json_rpc_2.md
@@ -0,0 +1,5 @@
+---
+name: "package:json_rpc_2"
+about: "Create a bug or file a feature request against package:json_rpc_2."
+labels: "package:json_rpc_2"
+---
diff --git a/.github/ISSUE_TEMPLATE/markdown.md b/.github/ISSUE_TEMPLATE/markdown.md
new file mode 100644
index 0000000..24e9f16
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/markdown.md
@@ -0,0 +1,5 @@
+---
+name: "package:markdown"
+about: "Create a bug or file a feature request against package:markdown."
+labels: "package:markdown"
+---
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/mime.md b/.github/ISSUE_TEMPLATE/mime.md
new file mode 100644
index 0000000..9a3c57e
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/mime.md
@@ -0,0 +1,5 @@
+---
+name: "package:mime"
+about: "Create a bug or file a feature request against package:mime."
+labels: "package:mime"
+---
diff --git a/.github/ISSUE_TEMPLATE/oauth2.md b/.github/ISSUE_TEMPLATE/oauth2.md
new file mode 100644
index 0000000..396f2e2
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/oauth2.md
@@ -0,0 +1,5 @@
+---
+name: "package:oauth2"
+about: "Create a bug or file a feature request against package:oauth2."
+labels: "package:oauth2"
+---
diff --git a/.github/ISSUE_TEMPLATE/package_config.md b/.github/ISSUE_TEMPLATE/package_config.md
new file mode 100644
index 0000000..f6322d0
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/package_config.md
@@ -0,0 +1,5 @@
+---
+name: "package:package_config"
+about: "Create a bug or file a feature request against package:package_config."
+labels: "package:package_config"
+---
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/pool.md b/.github/ISSUE_TEMPLATE/pool.md
new file mode 100644
index 0000000..7af32c4
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/pool.md
@@ -0,0 +1,5 @@
+---
+name: "package:pool"
+about: "Create a bug or file a feature request against package:pool."
+labels: "package:pool"
+---
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/pub_semver.md b/.github/ISSUE_TEMPLATE/pub_semver.md
new file mode 100644
index 0000000..c7db9b5
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/pub_semver.md
@@ -0,0 +1,5 @@
+---
+name: "package:pub_semver"
+about: "Create a bug or file a feature request against package:pub_semver."
+labels: "package:pub_semver"
+---
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/pubspec_parse.md b/.github/ISSUE_TEMPLATE/pubspec_parse.md
new file mode 100644
index 0000000..2d65881
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/pubspec_parse.md
@@ -0,0 +1,5 @@
+---
+name: "package:pubspec_parse"
+about: "Create a bug or file a feature request against package:pubspec_parse."
+labels: "package:pubspec_parse"
+---
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/source_map_stack_trace.md b/.github/ISSUE_TEMPLATE/source_map_stack_trace.md
new file mode 100644
index 0000000..66d4e32
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/source_map_stack_trace.md
@@ -0,0 +1,5 @@
+---
+name: "package:source_map_stack_trace"
+about: "Create a bug or file a feature request against package:source_map_stack_trace."
+labels: "package:source_map_stack_trace"
+---
diff --git a/.github/ISSUE_TEMPLATE/source_maps.md b/.github/ISSUE_TEMPLATE/source_maps.md
new file mode 100644
index 0000000..a1e390a
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/source_maps.md
@@ -0,0 +1,5 @@
+---
+name: "package:source_maps"
+about: "Create a bug or file a feature request against package:source_maps."
+labels: "package:source_maps"
+---
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/source_span.md b/.github/ISSUE_TEMPLATE/source_span.md
new file mode 100644
index 0000000..7dbb3c4
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/source_span.md
@@ -0,0 +1,5 @@
+---
+name: "package:source_span"
+about: "Create a bug or file a feature request against package:source_span."
+labels: "package:source_span"
+---
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/sse.md b/.github/ISSUE_TEMPLATE/sse.md
new file mode 100644
index 0000000..17cc488
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/sse.md
@@ -0,0 +1,5 @@
+---
+name: "package:sse"
+about: "Create a bug or file a feature request against package:sse."
+labels: "package:sse"
+---
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/stack_trace.md b/.github/ISSUE_TEMPLATE/stack_trace.md
new file mode 100644
index 0000000..417362b
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/stack_trace.md
@@ -0,0 +1,5 @@
+---
+name: "package:stack_trace"
+about: "Create a bug or file a feature request against package:stack_trace."
+labels: "package:stack_trace"
+---
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/stream_channel.md b/.github/ISSUE_TEMPLATE/stream_channel.md
new file mode 100644
index 0000000..76b5994
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/stream_channel.md
@@ -0,0 +1,5 @@
+---
+name: "package:stream_channel"
+about: "Create a bug or file a feature request against package:stream_channel."
+labels: "package:stream_channel"
+---
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/stream_transform.md b/.github/ISSUE_TEMPLATE/stream_transform.md
new file mode 100644
index 0000000..475bd83
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/stream_transform.md
@@ -0,0 +1,5 @@
+---
+name: "package:stream_transform"
+about: "Create a bug or file a feature request against package:stream_transform."
+labels: "package:stream_transform"
+---
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/string_scanner.md b/.github/ISSUE_TEMPLATE/string_scanner.md
new file mode 100644
index 0000000..ad89f1b
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/string_scanner.md
@@ -0,0 +1,5 @@
+---
+name: "package:string_scanner"
+about: "Create a bug or file a feature request against package:string_scanner."
+labels: "package:string_scanner"
+---
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/term_glyph.md b/.github/ISSUE_TEMPLATE/term_glyph.md
new file mode 100644
index 0000000..b6a4766
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/term_glyph.md
@@ -0,0 +1,5 @@
+---
+name: "package:term_glyph"
+about: "Create a bug or file a feature request against package:term_glyph."
+labels: "package:term_glyph"
+---
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/test_reflective_loader.md b/.github/ISSUE_TEMPLATE/test_reflective_loader.md
new file mode 100644
index 0000000..bde03fe
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/test_reflective_loader.md
@@ -0,0 +1,5 @@
+---
+name: "package:test_reflective_loader"
+about: "Create a bug or file a feature request against package:test_reflective_loader."
+labels: "package:test_reflective_loader"
+---
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/timing.md b/.github/ISSUE_TEMPLATE/timing.md
new file mode 100644
index 0000000..38a0015
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/timing.md
@@ -0,0 +1,5 @@
+---
+name: "package:timing"
+about: "Create a bug or file a feature request against package:timing."
+labels: "package:timing"
+---
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/unified_analytics.md b/.github/ISSUE_TEMPLATE/unified_analytics.md
new file mode 100644
index 0000000..163951e
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/unified_analytics.md
@@ -0,0 +1,5 @@
+---
+name: "package:unified_analytics - bug or enhancement"
+about: "Create a bug or file a feature request against package:unified_analytics."
+labels: "package:unified_analytics"
+---
diff --git a/.github/ISSUE_TEMPLATE/unified_analytics_event.yml b/.github/ISSUE_TEMPLATE/unified_analytics_event.yml
new file mode 100644
index 0000000..52c8d83
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/unified_analytics_event.yml
@@ -0,0 +1,47 @@
+name: "package:unified_analytics - request a new event"
+description: "Create a request for collecting a new event or new event data."
+labels: "package:unified_analytics"
+body:
+ - type: markdown
+ attributes:
+ value: |
+ An event is an action that a user, or the tool, performs.
+ You can see the events that are being collected [here](../../pkgs/unified_analytics/lib/src/enums.dart)
+ and the data associated with those events [here](../../pkgs/unified_analytics/lib/src/event.dart).
+ - type: checkboxes
+ attributes:
+ label: Has this already been approved in the privacy design documentation?
+ options:
+ - label: This field is already covered in the PDD
+ description: |
+ You can request that a new event be added to the package
+ regardless of whether it's included in the privacy design documentation.
+ However, the privacy team will need to approve before it is added.
+
+ - type: textarea
+ attributes:
+ label: Event name
+ description: |
+ If the event already exists, please tell us the name of the event
+ you would like to add data to.
+
+ If this is a new event, tell us the name, description, and tool owner.
+ Possible tool owners can be found in the `DashTool` enum [here](../../pkgs/unified_analytics/lib/src/enums.dart).
+
+ For example, pub_get represents pub package resolution details. The owner is the Dart tool.
+ validations:
+ required: true
+
+ - type: textarea
+ attributes:
+ label: Event data
+ description: |
+ Event data are key-value pairs that are associated with an event.
+ Please list the different event data associated with this event.
+ You should also include a description of the event data values.
+
+ For example, if the event is pub_get, one event data may be
+ the packageName. The values would be the name of the package
+ as a string.
+ validations:
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/unified_analytics_user_property.yml b/.github/ISSUE_TEMPLATE/unified_analytics_user_property.yml
new file mode 100644
index 0000000..3fc960e
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/unified_analytics_user_property.yml
@@ -0,0 +1,37 @@
+name: "package:unified_analytics - request a new user property"
+description: "Create a request for collecting a new user property."
+labels: "package:unified_analytics"
+body:
+ - type: markdown
+ attributes:
+ value: |
+ A user property is a key-value pair that is used to segment users.
+ For example, the Flutter channel that the user is on. You can see
+ which user properties are being collected
+ [here](../../pkgs/unified_analytics/lib/src/user_property.dart).
+
+ - type: checkboxes
+ attributes:
+ label: Has this already been approved in the privacy design documentation?
+ options:
+ - label: This field is already covered in the PDD
+ description: |
+ You can request that a new user property be added to the package
+ regardless of whether it's included in the privacy design documentation.
+ However, the privacy team will need to approve before it is added.
+
+ - type: textarea
+ attributes:
+ label: User property name
+ description: |
+ Please tell us the name of the user property you would like to add (e.g. flutter_channel).
+ validations:
+ required: true
+
+ - type: textarea
+ attributes:
+ label: User property values
+ description: |
+ Please list or describe the unique values for this property (e.g. master, beta, stable).
+ validations:
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/watcher.md b/.github/ISSUE_TEMPLATE/watcher.md
new file mode 100644
index 0000000..2578819
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/watcher.md
@@ -0,0 +1,5 @@
+---
+name: "package:watcher"
+about: "Create a bug or file a feature request against package:watcher."
+labels: "package:watcher"
+---
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/yaml.md b/.github/ISSUE_TEMPLATE/yaml.md
new file mode 100644
index 0000000..d6a7c7f
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/yaml.md
@@ -0,0 +1,5 @@
+---
+name: "package:yaml"
+about: "Create a bug or file a feature request against package:yaml."
+labels: "package:yaml"
+---
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/yaml_edit.md b/.github/ISSUE_TEMPLATE/yaml_edit.md
new file mode 100644
index 0000000..d1122a9
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/yaml_edit.md
@@ -0,0 +1,5 @@
+---
+name: "package:yaml_edit"
+about: "Create a bug or file a feature request against package:yaml_edit."
+labels: "package:yaml_edit"
+---
\ No newline at end of file
diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml
new file mode 100644
index 0000000..bf6b38a
--- /dev/null
+++ b/.github/dependabot.yaml
@@ -0,0 +1,14 @@
+# Dependabot configuration file.
+version: 2
+
+updates:
+ - package-ecosystem: github-actions
+ directory: /
+ schedule:
+ interval: monthly
+ labels:
+ - autosubmit
+ groups:
+ github-actions:
+ patterns:
+ - "*"
diff --git a/.github/labeler.yml b/.github/labeler.yml
new file mode 100644
index 0000000..0bb7feb
--- /dev/null
+++ b/.github/labeler.yml
@@ -0,0 +1,157 @@
+# Configuration for .github/workflows/pull_request_label.yml.
+
+'type-infra':
+ - changed-files:
+ - any-glob-to-any-file: '.github/**'
+
+'package:bazel_worker':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/bazel_worker/**'
+
+'package:benchmark_harness':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/benchmark_harness/**'
+
+'package:boolean_selector':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/boolean_selector/**'
+
+'package:browser_launcher':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/browser_launcher/**'
+
+'package:cli_config':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/cli_config/**'
+
+'package:cli_util':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/cli_util/**'
+
+'package:clock':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/clock/**'
+
+'package:code_builder':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/code_builder/**'
+
+'package:coverage':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/coverage/**'
+
+'package:csslib':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/csslib/**'
+
+'package:extension_discovery':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/extension_discovery/**'
+
+'package:file':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/file/**'
+
+'package:file_testing':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/file_testing/**'
+
+'package:graphs':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/graphs/**'
+
+'package:html':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/html/**'
+
+'package:io':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/io/**'
+
+'package:json_rpc_2':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/json_rpc_2/**'
+
+'package:markdown':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/markdown/**'
+
+'package:mime':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/mime/**'
+
+'package:oauth2':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/oauth2/**'
+
+'package:package_config':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/package_config/**'
+
+'package:pool':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/pool/**'
+
+'package:pub_semver':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/pub_semver/**'
+
+'package:pubspec_parse':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/pubspec_parse/**'
+
+'package:source_map_stack_trace':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/source_map_stack_trace/**'
+
+'package:source_maps':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/source_maps/**'
+
+'package:source_span':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/source_span/**'
+
+'package:sse':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/sse/**'
+
+'package:stack_trace':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/stack_trace/**'
+
+'package:stream_channel':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/stream_channel/**'
+
+'package:stream_transform':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/stream_transform/**'
+
+'package:term_glyph':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/term_glyph/**'
+
+'package:test_reflective_loader':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/test_reflective_loader/**'
+
+'package:timing':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/timing/**'
+
+'package:unified_analytics':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/unified_analytics/**'
+
+'package:watcher':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/watcher/**'
+
+'package:yaml':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/yaml/**'
+
+'package:yaml_edit':
+ - changed-files:
+ - any-glob-to-any-file: 'pkgs/yaml_edit/**'
diff --git a/.github/workflows/bazel_worker.yaml b/.github/workflows/bazel_worker.yaml
new file mode 100644
index 0000000..4446793
--- /dev/null
+++ b/.github/workflows/bazel_worker.yaml
@@ -0,0 +1,43 @@
+name: package:bazel_worker
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/bazel_worker.yaml'
+ - 'pkgs/bazel_worker/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/bazel_worker.yaml'
+ - 'pkgs/bazel_worker/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+
+defaults:
+ run:
+ working-directory: pkgs/bazel_worker/
+
+jobs:
+ # Run the test script against the latest dev build.
+ test:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [3.4, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - run: dart pub get
+ - run: "dart format --output=none --set-exit-if-changed ."
+ if: ${{ matrix.sdk == 'dev' }}
+ - name: Test
+ run: ./tool/travis.sh
diff --git a/.github/workflows/benchmark_harness.yaml b/.github/workflows/benchmark_harness.yaml
new file mode 100644
index 0000000..04a5829
--- /dev/null
+++ b/.github/workflows/benchmark_harness.yaml
@@ -0,0 +1,74 @@
+name: package:benchmark_harness
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/benchmark_harness.yaml'
+ - 'pkgs/benchmark_harness/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/benchmark_harness.yaml'
+ - 'pkgs/benchmark_harness/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+defaults:
+ run:
+ working-directory: pkgs/benchmark_harness/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest]
+ sdk: [3.2, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
+ - name: Run Chrome tests
+ run: dart test --platform chrome
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/boolean_selector.yaml b/.github/workflows/boolean_selector.yaml
new file mode 100644
index 0000000..4a74aed
--- /dev/null
+++ b/.github/workflows/boolean_selector.yaml
@@ -0,0 +1,72 @@
+name: package:boolean_selector
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/boolean_selector.yaml'
+ - 'pkgs/boolean_selector/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/boolean_selector.yaml'
+ - 'pkgs/boolean_selector/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+
+defaults:
+ run:
+ working-directory: pkgs/boolean_selector/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest]
+ sdk: [3.1, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
+ - name: Run Chrome tests
+ run: dart test --platform chrome
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/browser_launcher.yaml b/.github/workflows/browser_launcher.yaml
new file mode 100644
index 0000000..699cac6
--- /dev/null
+++ b/.github/workflows/browser_launcher.yaml
@@ -0,0 +1,46 @@
+name: package:browser_launcher
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/browser_launcher.yaml'
+ - 'pkgs/browser_launcher/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/browser_launcher.yaml'
+ - 'pkgs/browser_launcher/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+ DISPLAY: ':99'
+
+defaults:
+ run:
+ working-directory: pkgs/browser_launcher/
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ sdk: [3.4, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+
+ - run: dart pub get
+
+ - run: dart format --output=none --set-exit-if-changed .
+ - run: dart analyze --fatal-infos
+
+ - name: Run Xvfb
+ run: Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
+
+ - run: dart test
diff --git a/.github/workflows/cli_config.yaml b/.github/workflows/cli_config.yaml
new file mode 100644
index 0000000..980df5c
--- /dev/null
+++ b/.github/workflows/cli_config.yaml
@@ -0,0 +1,77 @@
+name: package:cli_config
+permissions: read-all
+
+on:
+ pull_request:
+ paths:
+ - ".github/workflows/cli_config.yml"
+ - "pkgs/cli_config/**"
+ push:
+ branches: [main]
+ paths:
+ - ".github/workflows/cli_config.yml"
+ - "pkgs/cli_config/**"
+ schedule:
+ - cron: "0 0 * * 0" # weekly
+
+defaults:
+ run:
+ working-directory: pkgs/cli_config/
+
+jobs:
+ build_linux:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ sdk: [stable, dev] # {pkgs.versions}
+ include:
+ - sdk: stable
+ run-tests: true
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{matrix.sdk}}
+
+ - run: dart pub get
+
+ - run: dart analyze --fatal-infos
+
+ - run: dart format --output=none --set-exit-if-changed .
+ if: ${{matrix.run-tests}}
+
+ - run: dart test
+ if: ${{matrix.run-tests}}
+
+ - name: Install coverage
+ run: dart pub global activate coverage
+ if: ${{ matrix.sdk == 'stable' }}
+ - name: Collect coverage
+ run: dart pub global run coverage:test_with_coverage
+ if: ${{ matrix.sdk == 'stable' }}
+ - name: Upload coverage
+ uses: coverallsapp/github-action@cfd0633edbd2411b532b808ba7a8b5e04f76d2c8
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ flag-name: cli_config_linux
+ path-to-lcov: ./pkgs/cli_config/coverage/lcov.info
+ if: ${{ matrix.sdk == 'stable' }}
+
+ build_windows:
+ runs-on: windows-latest
+ strategy:
+ matrix:
+ sdk: [stable, dev] # {pkgs.versions}
+ include:
+ - sdk: stable
+ run-tests: true
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{matrix.sdk}}
+
+ - run: dart pub get
+
+ - run: dart test
+ if: ${{matrix.run-tests}}
diff --git a/.github/workflows/cli_util.yaml b/.github/workflows/cli_util.yaml
new file mode 100644
index 0000000..69aae5e
--- /dev/null
+++ b/.github/workflows/cli_util.yaml
@@ -0,0 +1,66 @@
+name: package:cli_util
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/cli_util.yaml'
+ - 'pkgs/cli_util/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/cli_util.yaml'
+ - 'pkgs/cli_util/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+
+defaults:
+ run:
+ working-directory: pkgs/cli_util/
+
+jobs:
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest]
+ sdk: ['3.4', dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run tests
+ run: dart test --test-randomize-ordering-seed=random
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/clock.yaml b/.github/workflows/clock.yaml
new file mode 100644
index 0000000..a09a601
--- /dev/null
+++ b/.github/workflows/clock.yaml
@@ -0,0 +1,75 @@
+name: package:clock
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/clock.yaml'
+ - 'pkgs/clock/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/clock.yaml'
+ - 'pkgs/clock/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+
+defaults:
+ run:
+ working-directory: pkgs/clock/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest]
+ sdk: [3.4, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
+ - name: Run Chrome tests
+ run: dart test --platform chrome
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/code_builder.yaml b/.github/workflows/code_builder.yaml
new file mode 100644
index 0000000..a0d3ec2
--- /dev/null
+++ b/.github/workflows/code_builder.yaml
@@ -0,0 +1,68 @@
+name: package:code_builder
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/code_builder.yaml'
+ - 'pkgs/code_builder/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/code_builder.yaml'
+ - 'pkgs/code_builder/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+
+defaults:
+ run:
+ working-directory: pkgs/code_builder/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev and an earlier stable version.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos .
+ if: always() && steps.install.outcome == 'success'
+
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest]
+ sdk: [3.5.0, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml
new file mode 100644
index 0000000..2252b94
--- /dev/null
+++ b/.github/workflows/coverage.yaml
@@ -0,0 +1,99 @@
+name: package:coverage
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/coverage.yaml'
+ - 'pkgs/coverage/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/coverage.yaml'
+ - 'pkgs/coverage/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: pkgs/coverage/
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, macos-latest, windows-latest
+ # 2. release channel: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ defaults:
+ run:
+ working-directory: pkgs/coverage/
+ strategy:
+ 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
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
+
+ coverage:
+ needs: test
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: pkgs/coverage/
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: dev
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Collect and report coverage
+ run: dart run bin/test_with_coverage.dart --port=9292
+ - name: Upload coverage
+ uses: coverallsapp/github-action@cfd0633edbd2411b532b808ba7a8b5e04f76d2c8
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ path-to-lcov: pkgs/coverage/coverage/lcov.info
diff --git a/.github/workflows/csslib.yaml b/.github/workflows/csslib.yaml
new file mode 100644
index 0000000..3ce2cd9
--- /dev/null
+++ b/.github/workflows/csslib.yaml
@@ -0,0 +1,72 @@
+name: package:csslib
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/csslib.yaml'
+ - 'pkgs/csslib/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/csslib.yaml'
+ - 'pkgs/csslib/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+
+defaults:
+ run:
+ working-directory: pkgs/csslib/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest, windows-latest]
+ sdk: [3.1, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/deploy_pages.yaml b/.github/workflows/deploy_pages.yaml
new file mode 100644
index 0000000..a7908f3
--- /dev/null
+++ b/.github/workflows/deploy_pages.yaml
@@ -0,0 +1,43 @@
+# Publish the GitHub Pages site for this repo.
+
+name: "Deploy Pages"
+
+on:
+ # Run on pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/deploy_pages.yaml'
+ - 'pkgs/markdown/**'
+
+jobs:
+ deploy:
+ permissions:
+ contents: read
+ pages: write
+ id-token: write
+ runs-on: ubuntu-latest
+ environment:
+ name: github-pages
+ url: ${{steps.deployment.outputs.page_url}}
+
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+
+ # Build the markdown playground.
+ - run: dart pub get
+ working-directory: pkgs/markdown
+ - run: dart run build_runner build -o example:build --release --delete-conflicting-outputs --verbose
+ working-directory: pkgs/markdown
+
+ # Create the _site directory.
+ - run: mkdir _site
+ - run: cp -r pkgs/markdown/build _site/markdown
+
+ # Deploy to GitHub Pages.
+ - uses: actions/configure-pages@v5
+ - uses: actions/upload-pages-artifact@v3
+ with:
+ path: _site
+ - uses: actions/deploy-pages@v4
diff --git a/.github/workflows/extension_discovery.yaml b/.github/workflows/extension_discovery.yaml
new file mode 100644
index 0000000..6a6d5d7
--- /dev/null
+++ b/.github/workflows/extension_discovery.yaml
@@ -0,0 +1,47 @@
+name: package:extension_discovery
+permissions: read-all
+
+on:
+ pull_request:
+ paths:
+ - '.github/workflows/extension_discovery.yaml'
+ - 'pkgs/extension_discovery/**'
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/extension_discovery.yaml'
+ - 'pkgs/extension_discovery/**'
+ schedule:
+ - cron: '0 0 * * 0' # weekly
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: pkgs/extension_discovery
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [stable, dev] # {pkgs.versions}
+ include:
+ - sdk: stable
+ check-formatting: true
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{matrix.sdk}}
+
+ - run: dart pub get
+ - run: |
+ (cd example/hello_world && dart pub get)
+ (cd example/hello_world_app && dart pub get)
+ (cd example/hello_world_german && dart pub get)
+
+ - run: dart analyze --fatal-infos
+
+ - run: dart format --output=none --set-exit-if-changed .
+ if: ${{matrix.check-formatting}}
+
+ - run: dart test
diff --git a/.github/workflows/file.yaml b/.github/workflows/file.yaml
new file mode 100644
index 0000000..3254f2e
--- /dev/null
+++ b/.github/workflows/file.yaml
@@ -0,0 +1,54 @@
+name: package:file
+permissions: read-all
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/file.yaml'
+ - 'pkgs/file/**'
+ - 'pkgs/file_testing/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/file.yaml'
+ - 'pkgs/file/**'
+ - 'pkgs/file_testing/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+defaults:
+ run:
+ working-directory: pkgs/file/
+
+jobs:
+ correctness:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: dev
+
+ - run: dart pub get
+ - run: dart format --output=none --set-exit-if-changed .
+ - run: dart analyze --fatal-infos
+
+ test:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ package: [file]
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ sdk: [stable, dev]
+
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+
+ - run: dart pub get
+ - run: dart pub run test -j1
diff --git a/.github/workflows/file_testing.yaml b/.github/workflows/file_testing.yaml
new file mode 100644
index 0000000..f5e5a0e
--- /dev/null
+++ b/.github/workflows/file_testing.yaml
@@ -0,0 +1,36 @@
+name: package:file_testing
+permissions: read-all
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/file_testing.yaml'
+ - 'pkgs/file/**'
+ - 'pkgs/file_testing/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/file_testing.yaml'
+ - 'pkgs/file/**'
+ - 'pkgs/file_testing/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+defaults:
+ run:
+ working-directory: pkgs/file_testing/
+
+jobs:
+ correctness:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: dev
+
+ - run: dart pub get
+ - run: dart format --output=none --set-exit-if-changed .
+ - run: dart analyze --fatal-infos
diff --git a/.github/workflows/graphs.yaml b/.github/workflows/graphs.yaml
new file mode 100644
index 0000000..a3b3733
--- /dev/null
+++ b/.github/workflows/graphs.yaml
@@ -0,0 +1,43 @@
+name: package:graphs
+
+permissions: read-all
+
+on:
+ # Run CI on all PRs (against any branch) and on pushes to the main branch.
+ pull_request:
+ paths:
+ - '.github/workflows/graphs.yaml'
+ - 'pkgs/graphs/**'
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/graphs.yaml'
+ - 'pkgs/graphs/**'
+ schedule:
+ - cron: '0 0 * * 0' # weekly
+
+defaults:
+ run:
+ working-directory: pkgs/graphs
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [stable, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+
+ - run: dart pub get
+
+ - run: dart analyze --fatal-infos
+
+ - run: dart format --output=none --set-exit-if-changed .
+ if: ${{ matrix.sdk == 'stable' }}
+
+ - run: dart test
diff --git a/.github/workflows/health.yaml b/.github/workflows/health.yaml
new file mode 100644
index 0000000..3ac7e89
--- /dev/null
+++ b/.github/workflows/health.yaml
@@ -0,0 +1,14 @@
+name: Health
+on:
+ pull_request:
+ branches: [ main ]
+ types: [opened, synchronize, reopened, labeled, unlabeled]
+
+jobs:
+ health:
+ uses: dart-lang/ecosystem/.github/workflows/health.yaml@main
+ with:
+ ignore_coverage: "**.mock.dart,**.g.dart"
+ ignore_license: "**.mock.dart,**.g.dart,**.mocks.dart,pkgs/markdown/**"
+ permissions:
+ pull-requests: write
diff --git a/.github/workflows/html.yaml b/.github/workflows/html.yaml
new file mode 100644
index 0000000..e777b79
--- /dev/null
+++ b/.github/workflows/html.yaml
@@ -0,0 +1,61 @@
+name: package:html
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/html.yaml'
+ - 'pkgs/html/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/html.yaml'
+ - 'pkgs/html/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+
+defaults:
+ run:
+ working-directory: pkgs/html/
+
+jobs:
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: dev
+ - id: install
+ run: dart pub get
+ - run: dart format --output=none --set-exit-if-changed .
+ if: steps.install.outcome == 'success'
+ - run: dart analyze --fatal-infos
+ if: steps.install.outcome == 'success'
+
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest]
+ sdk: [3.2, stable, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ run: dart pub get
+ - run: dart test --platform vm
+ if: steps.install.outcome == 'success'
+ - run: dart test --platform chrome
+ if: steps.install.outcome == 'success'
diff --git a/.github/workflows/io.yaml b/.github/workflows/io.yaml
new file mode 100644
index 0000000..7733cd6
--- /dev/null
+++ b/.github/workflows/io.yaml
@@ -0,0 +1,72 @@
+name: package:io
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/io.yaml'
+ - 'pkgs/io/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/io.yaml'
+ - 'pkgs/io/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+
+defaults:
+ run:
+ working-directory: pkgs/io/
+
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev and stable.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev, 3.4]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev, stable
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest]
+ sdk: [dev, 3.4]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - run: dart test
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/json_rpc_2.yaml b/.github/workflows/json_rpc_2.yaml
new file mode 100644
index 0000000..276a4d7
--- /dev/null
+++ b/.github/workflows/json_rpc_2.yaml
@@ -0,0 +1,75 @@
+name: package:json_rpc_2
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/json_rpc_2.yaml'
+ - 'pkgs/json_rpc_2/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/json_rpc_2.yaml'
+ - 'pkgs/json_rpc_2/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+
+defaults:
+ run:
+ working-directory: pkgs/json_rpc_2/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest]
+ sdk: [3.4, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
+ - name: Run browser tests
+ run: dart test --platform chrome --compiler dart2wasm,dart2js
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/markdown.yaml b/.github/workflows/markdown.yaml
new file mode 100644
index 0000000..5b4e569
--- /dev/null
+++ b/.github/workflows/markdown.yaml
@@ -0,0 +1,106 @@
+name: package:markdown
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/markdown.yaml'
+ - 'pkgs/markdown/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/markdown.yaml'
+ - 'pkgs/markdown/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+defaults:
+ run:
+ working-directory: pkgs/markdown/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Ensure the markdown playground builds.
+ build-playground:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [stable]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - run: dart pub get
+ - run: dart compile js -o example/app.dart.js example/app.dart
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest]
+ sdk: [3.2, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
+
+ coverage:
+ needs: test
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: dev
+ - name: Install dependencies
+ run: dart pub get
+ - name: Install coverage
+ run: dart pub global activate coverage
+ - name: Collect and report coverage
+ run: dart pub global run coverage:test_with_coverage
+ - name: Upload coverage
+ uses: coverallsapp/github-action@master
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ path-to-lcov: pkgs/markdown/coverage/lcov.info
diff --git a/.github/workflows/markdown_crash_test.yaml b/.github/workflows/markdown_crash_test.yaml
new file mode 100644
index 0000000..6e7aad2
--- /dev/null
+++ b/.github/workflows/markdown_crash_test.yaml
@@ -0,0 +1,37 @@
+# Run against all markdown files in the latest version of packages on pub.dev
+# to see if any can provoke a crash.
+
+name: "package:markdown: crash tests"
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/markdown_crash_test.yaml'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/markdown_crash_test.yaml'
+ schedule:
+ # “At 00:00 (UTC) on Sunday.”
+ - cron: '0 0 * * 0'
+ workflow_dispatch:
+
+defaults:
+ run:
+ working-directory: pkgs/markdown/
+
+jobs:
+ crash-test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+
+ - name: Install dependencies
+ run: dart pub get
+
+ - name: Run crash_test.dart
+ run: dart test -P crash_test test/crash_test.dart
diff --git a/.github/workflows/markdown_flutter.yaml b/.github/workflows/markdown_flutter.yaml
new file mode 100644
index 0000000..16f48d7
--- /dev/null
+++ b/.github/workflows/markdown_flutter.yaml
@@ -0,0 +1,66 @@
+# 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"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+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@74af56c5ed2697ba4621264652728e8d217e53d3
+ 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
diff --git a/.github/workflows/mime.yaml b/.github/workflows/mime.yaml
new file mode 100644
index 0000000..9e49f0e
--- /dev/null
+++ b/.github/workflows/mime.yaml
@@ -0,0 +1,45 @@
+name: package:mime
+
+on:
+ schedule:
+ # “At 00:00 (UTC) on Sunday.”
+ - cron: '0 0 * * 0'
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/mime.yaml'
+ - 'pkgs/mime/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/mime.yaml'
+ - 'pkgs/mime/**'
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: pkgs/mime/
+
+ strategy:
+ matrix:
+ os: [ubuntu-latest]
+ sdk: [3.2, dev]
+ 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: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+
+ - name: Install dependencies
+ run: dart pub get
+
+ - name: Verify formatting
+ run: dart format --output=none --set-exit-if-changed .
+
+ - name: Analyze project source
+ run: dart analyze --fatal-infos
+
+ - name: Run tests
+ run: dart test
diff --git a/.github/workflows/no-response.yml b/.github/workflows/no-response.yml
new file mode 100644
index 0000000..ab1ac49
--- /dev/null
+++ b/.github/workflows/no-response.yml
@@ -0,0 +1,37 @@
+# A workflow to close issues where the author hasn't responded to a request for
+# more information; see https://github.com/actions/stale.
+
+name: No Response
+
+# Run as a daily cron.
+on:
+ schedule:
+ # Every day at 8am
+ - cron: '0 8 * * *'
+
+# All permissions not specified are set to 'none'.
+permissions:
+ issues: write
+ pull-requests: write
+
+jobs:
+ no-response:
+ runs-on: ubuntu-latest
+ if: ${{ github.repository_owner == 'dart-lang' }}
+ steps:
+ - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e
+ with:
+ # Don't automatically mark inactive issues+PRs as stale.
+ days-before-stale: -1
+ # Close needs-info issues and PRs after 14 days of inactivity.
+ days-before-close: 14
+ stale-issue-label: "needs-info"
+ close-issue-message: >
+ Without additional information we're not able to resolve this issue.
+ Feel free to add more info or respond to any questions above and we
+ can reopen the case. Thanks for your contribution!
+ stale-pr-label: "needs-info"
+ close-pr-message: >
+ Without additional information we're not able to resolve this PR.
+ Feel free to add more info or respond to any questions above.
+ Thanks for your contribution!
diff --git a/.github/workflows/oauth2.yaml b/.github/workflows/oauth2.yaml
new file mode 100644
index 0000000..6de86b5
--- /dev/null
+++ b/.github/workflows/oauth2.yaml
@@ -0,0 +1,79 @@
+name: package:oauth2
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/oauth2.yaml'
+ - 'pkgs/oauth2/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/oauth2.yaml'
+ - 'pkgs/oauth2/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+jobs:
+ # Check code formatting and lints against Dart dev, check analyzer warnings
+ # against the oldest supported SDK.
+ analyze:
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: pkgs/oauth2/
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: matrix.sdk == 'dev' && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: matrix.sdk == 'dev' && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze
+ if: matrix.sdk != 'dev' && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ defaults:
+ run:
+ working-directory: pkgs/oauth2/
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest]
+ sdk: [3.4, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
+ - name: Run Chrome tests
+ run: dart test --platform chrome
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/package_config.yaml b/.github/workflows/package_config.yaml
new file mode 100644
index 0000000..2d028b9
--- /dev/null
+++ b/.github/workflows/package_config.yaml
@@ -0,0 +1,71 @@
+name: package:package_config
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/package_config.yaml'
+ - 'pkgs/package_config/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/package_config.yaml'
+ - 'pkgs/package_config/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+
+defaults:
+ run:
+ working-directory: pkgs/package_config/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, windows-latest]
+ sdk: [3.4, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run tests
+ run: dart test -p chrome,vm
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/pool.yaml b/.github/workflows/pool.yaml
new file mode 100644
index 0000000..6d64062
--- /dev/null
+++ b/.github/workflows/pool.yaml
@@ -0,0 +1,78 @@
+name: package:pool
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/pool.yaml'
+ - 'pkgs/pool/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/pool.yaml'
+ - 'pkgs/pool/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+
+defaults:
+ run:
+ working-directory: pkgs/pool/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest]
+ sdk: [3.4, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
+ - name: Run Chrome tests
+ run: dart test --platform chrome
+ if: always() && steps.install.outcome == 'success'
+ - name: Run Chrome tests - wasm
+ run: dart test --platform chrome -c dart2wasm
+ if: always() && steps.install.outcome == 'success' && matrix.sdk == 'dev'
diff --git a/.github/workflows/post_summaries.yaml b/.github/workflows/post_summaries.yaml
new file mode 100644
index 0000000..f5c8be8
--- /dev/null
+++ b/.github/workflows/post_summaries.yaml
@@ -0,0 +1,17 @@
+name: Comment on the pull request
+
+on:
+ # Trigger this workflow after the Health workflow completes. This workflow will have permissions to
+ # do things like create comments on the PR, even if the original workflow couldn't.
+ workflow_run:
+ workflows:
+ - Publish
+ - Health
+ types:
+ - completed
+
+jobs:
+ upload:
+ uses: dart-lang/ecosystem/.github/workflows/post_summaries.yaml@main
+ permissions:
+ pull-requests: write
diff --git a/.github/workflows/pub_semver.yaml b/.github/workflows/pub_semver.yaml
new file mode 100644
index 0000000..ba0db18
--- /dev/null
+++ b/.github/workflows/pub_semver.yaml
@@ -0,0 +1,75 @@
+name: package:pub_semver
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/pub_semver.yaml'
+ - 'pkgs/pub_semver/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/pub_semver.yaml'
+ - 'pkgs/pub_semver/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+
+defaults:
+ run:
+ working-directory: pkgs/pub_semver/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest]
+ sdk: [3.4, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
+ - name: Run Chrome tests
+ run: dart test --platform chrome --compiler dart2js,dart2wasm
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml
new file mode 100644
index 0000000..2ec45aa
--- /dev/null
+++ b/.github/workflows/publish.yaml
@@ -0,0 +1,16 @@
+# A CI configuration to auto-publish pub packages.
+
+name: Publish
+
+on:
+ pull_request:
+ branches: [ main ]
+ push:
+ tags: [ '[A-z]+-v[0-9]+.[0-9]+.[0-9]+' ]
+
+jobs:
+ publish:
+ if: ${{ github.repository_owner == 'dart-lang' }}
+ uses: dart-lang/ecosystem/.github/workflows/publish.yaml@main
+ with:
+ write-comments: false
diff --git a/.github/workflows/pubspec_parse.yaml b/.github/workflows/pubspec_parse.yaml
new file mode 100644
index 0000000..9cf6257
--- /dev/null
+++ b/.github/workflows/pubspec_parse.yaml
@@ -0,0 +1,71 @@
+name: package:pubspec_parse
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/pubspec_parse.yaml'
+ - 'pkgs/pubspec_parse/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/pubspec_parse.yaml'
+ - 'pkgs/pubspec_parse/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+
+defaults:
+ run:
+ working-directory: pkgs/pubspec_parse/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest]
+ sdk: [3.6, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm --run-skipped
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/pull_request_label.yml b/.github/workflows/pull_request_label.yml
new file mode 100644
index 0000000..54e3df5
--- /dev/null
+++ b/.github/workflows/pull_request_label.yml
@@ -0,0 +1,22 @@
+# This workflow applies labels to pull requests based on the paths that are
+# modified in the pull request.
+#
+# Edit `.github/labeler.yml` to configure labels. For more information, see
+# https://github.com/actions/labeler.
+
+name: Pull Request Labeler
+permissions: read-all
+
+on:
+ pull_request_target
+
+jobs:
+ label:
+ permissions:
+ pull-requests: write
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9
+ 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
new file mode 100644
index 0000000..59cfab1
--- /dev/null
+++ b/.github/workflows/source_map_stack_trace.yaml
@@ -0,0 +1,76 @@
+name: package:source_map_stack_trace
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/source_map_stack_trace.yaml'
+ - 'pkgs/source_map_stack_trace/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/source_map_stack_trace.yaml'
+ - 'pkgs/source_map_stack_trace/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: pkgs/source_map_stack_trace/
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ defaults:
+ run:
+ working-directory: pkgs/source_map_stack_trace/
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest]
+ sdk: [3.3, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
+ - name: Run Chrome tests
+ run: dart test --platform chrome
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/source_maps.yaml b/.github/workflows/source_maps.yaml
new file mode 100644
index 0000000..2ae0f20
--- /dev/null
+++ b/.github/workflows/source_maps.yaml
@@ -0,0 +1,72 @@
+name: package:source_maps
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/source_maps.yaml'
+ - 'pkgs/source_maps/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/source_maps.yaml'
+ - 'pkgs/source_maps/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+
+defaults:
+ run:
+ working-directory: pkgs/source_maps/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest]
+ sdk: [3.3.0, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/source_span.yaml b/.github/workflows/source_span.yaml
new file mode 100644
index 0000000..422d55e
--- /dev/null
+++ b/.github/workflows/source_span.yaml
@@ -0,0 +1,75 @@
+name: package:source_span
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/source_span.yaml'
+ - 'pkgs/source_span/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/source_span.yaml'
+ - 'pkgs/source_span/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+
+defaults:
+ run:
+ working-directory: pkgs/source_span/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest]
+ sdk: [3.1.0, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
+ - name: Run Chrome tests
+ run: dart test --platform chrome
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/sse.yaml b/.github/workflows/sse.yaml
new file mode 100644
index 0000000..01d7d4f
--- /dev/null
+++ b/.github/workflows/sse.yaml
@@ -0,0 +1,73 @@
+name: package:sse
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/sse.yaml'
+ - 'pkgs/sse/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/sse.yaml'
+ - 'pkgs/sse/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+
+defaults:
+ run:
+ working-directory: pkgs/sse/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest]
+ sdk: [3.3, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - uses: nanasess/setup-chromedriver@e93e57b843c0c92788f22483f1a31af8ee48db25
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm --test-randomize-ordering-seed=random -j 1
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/stack_trace.yaml b/.github/workflows/stack_trace.yaml
new file mode 100644
index 0000000..7435967
--- /dev/null
+++ b/.github/workflows/stack_trace.yaml
@@ -0,0 +1,75 @@
+name: package:stack_trace
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/stack_trace.yaml'
+ - 'pkgs/stack_trace/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/stack_trace.yaml'
+ - 'pkgs/stack_trace/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+
+defaults:
+ run:
+ working-directory: pkgs/stack_trace/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest]
+ sdk: [3.4, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
+ - name: Run browser tests
+ run: dart test --platform chrome
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/stream_channel.yaml b/.github/workflows/stream_channel.yaml
new file mode 100644
index 0000000..c39424d
--- /dev/null
+++ b/.github/workflows/stream_channel.yaml
@@ -0,0 +1,74 @@
+name: package:stream_channel
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/stream_channel.yaml'
+ - 'pkgs/stream_channel/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/stream_channel.yaml'
+ - 'pkgs/stream_channel/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+defaults:
+ run:
+ working-directory: pkgs/stream_channel/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest]
+ sdk: [3.3, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
+ - name: Run Chrome tests
+ run: dart test --platform chrome
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/stream_transform.yaml b/.github/workflows/stream_transform.yaml
new file mode 100644
index 0000000..38be5cc
--- /dev/null
+++ b/.github/workflows/stream_transform.yaml
@@ -0,0 +1,73 @@
+name: package:stream_transform
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/stream_transform.yaml'
+ - 'pkgs/stream_transform/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/stream_transform.yaml'
+ - 'pkgs/stream_transform/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+
+defaults:
+ run:
+ working-directory: pkgs/stream_transform/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest]
+ # Bump SDK for Legacy tests when changing min SDK.
+ sdk: [3.4, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run tests
+ run: dart test -p chrome,vm --test-randomize-ordering-seed=random
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/string_scanner.yaml b/.github/workflows/string_scanner.yaml
new file mode 100644
index 0000000..665883a
--- /dev/null
+++ b/.github/workflows/string_scanner.yaml
@@ -0,0 +1,75 @@
+name: package:string_scanner
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/string_scanner.yaml'
+ - 'pkgs/string_scanner/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/string_scanner.yaml'
+ - 'pkgs/string_scanner/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+
+defaults:
+ run:
+ working-directory: pkgs/string_scanner/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest]
+ sdk: [3.1, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
+ - name: Run Chrome tests
+ run: dart test --platform chrome
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/term_glyph.yaml b/.github/workflows/term_glyph.yaml
new file mode 100644
index 0000000..5b3b320
--- /dev/null
+++ b/.github/workflows/term_glyph.yaml
@@ -0,0 +1,72 @@
+name: package:term_glyph
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/term_glyph.yaml'
+ - 'pkgs/term_glyph/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/term_glyph.yaml'
+ - 'pkgs/term_glyph/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+
+defaults:
+ run:
+ working-directory: pkgs/term_glyph/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest]
+ sdk: [3.1, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/test_reflective_loader.yaml b/.github/workflows/test_reflective_loader.yaml
new file mode 100644
index 0000000..975c970
--- /dev/null
+++ b/.github/workflows/test_reflective_loader.yaml
@@ -0,0 +1,43 @@
+name: package:test_reflective_loader
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/test_reflective_loader.yaml'
+ - 'pkgs/test_reflective_loader/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/test_reflective_loader.yaml'
+ - 'pkgs/test_reflective_loader/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+defaults:
+ run:
+ working-directory: pkgs/test_reflective_loader/
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev, 3.1]
+
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+
+ - run: dart pub get
+ - name: dart format
+ run: dart format --output=none --set-exit-if-changed .
+ - run: dart analyze --fatal-infos
+ - run: dart test
diff --git a/.github/workflows/timing.yaml b/.github/workflows/timing.yaml
new file mode 100644
index 0000000..df77b13
--- /dev/null
+++ b/.github/workflows/timing.yaml
@@ -0,0 +1,67 @@
+name: package:timing
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/timing.yaml'
+ - 'pkgs/timing/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/timing.yaml'
+ - 'pkgs/timing/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+
+defaults:
+ run:
+ working-directory: pkgs/timing/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [3.4, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ run: dart pub get
+ - run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev, 2.2.0
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest]
+ sdk: [3.4, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ run: dart pub get
+ - run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/unified_analytics.yaml b/.github/workflows/unified_analytics.yaml
new file mode 100644
index 0000000..aa30852
--- /dev/null
+++ b/.github/workflows/unified_analytics.yaml
@@ -0,0 +1,43 @@
+name: package:unified_analytics
+permissions: read-all
+
+on:
+ pull_request:
+ paths:
+ - '.github/workflows/unified_analytics.yaml'
+ - 'pkgs/unified_analytics/**'
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/unified_analytics.yaml'
+ - 'pkgs/unified_analytics/**'
+ schedule:
+ - cron: '0 0 * * 0' # weekly
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: pkgs/unified_analytics
+ strategy:
+ matrix:
+ sdk: [stable, dev] # {pkgs.versions}
+ include:
+ - sdk: stable
+ run-tests: true
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{matrix.sdk}}
+
+ - run: dart pub get
+
+ - run: dart analyze --fatal-infos
+
+ - run: dart format --output=none --set-exit-if-changed .
+ if: ${{matrix.run-tests}}
+
+ - run: dart test
+ if: ${{matrix.run-tests}}
diff --git a/.github/workflows/watcher.yaml b/.github/workflows/watcher.yaml
new file mode 100644
index 0000000..a04483c
--- /dev/null
+++ b/.github/workflows/watcher.yaml
@@ -0,0 +1,71 @@
+name: package:watcher
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/watcher.yaml'
+ - 'pkgs/watcher/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/watcher.yaml'
+ - 'pkgs/watcher/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+
+defaults:
+ run:
+ working-directory: pkgs/watcher/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, macos-latest, windows-latest
+ # 2. release channel: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ sdk: [3.1, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/yaml.yaml b/.github/workflows/yaml.yaml
new file mode 100644
index 0000000..735461e
--- /dev/null
+++ b/.github/workflows/yaml.yaml
@@ -0,0 +1,75 @@
+name: package:yaml
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/yaml.yaml'
+ - 'pkgs/yaml/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/yaml.yaml'
+ - 'pkgs/yaml/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+
+defaults:
+ run:
+ working-directory: pkgs/yaml/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest]
+ sdk: [3.4, dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
+ - name: Run Chrome tests
+ run: dart test --platform chrome
+ if: always() && steps.install.outcome == 'success'
diff --git a/.github/workflows/yaml_edit.yaml b/.github/workflows/yaml_edit.yaml
new file mode 100644
index 0000000..ffea62c
--- /dev/null
+++ b/.github/workflows/yaml_edit.yaml
@@ -0,0 +1,91 @@
+name: package:yaml_edit
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/yaml_edit.yaml'
+ - 'pkgs/yaml_edit/**'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.github/workflows/yaml_edit.yaml'
+ - 'pkgs/yaml_edit/**'
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+
+defaults:
+ run:
+ working-directory: pkgs/yaml_edit/
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest]
+ sdk: ['3.1', stable, dev]
+ platform: [vm, chrome]
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run tests on ${{ matrix.platform }}
+ run: dart test --platform ${{ matrix.platform }} --coverage=./coverage
+ if: always() && steps.install.outcome == 'success'
+ # We don't collect code coverage from 2.12.0, because it doesn't work
+ - name: Convert coverage to lcov
+ run: dart run coverage:format_coverage -i ./coverage -o ./coverage/lcov.info --lcov --report-on lib/
+ if: always() && steps.install.outcome == 'success' && matrix.sdk != '2.12.0'
+ - uses: coverallsapp/github-action@cfd0633edbd2411b532b808ba7a8b5e04f76d2c8
+ if: always() && steps.install.outcome == 'success' && matrix.sdk != '2.12.0'
+ with:
+ flag-name: os:${{ matrix.os }}/dart:${{ matrix.sdk }}/platform:${{ matrix.platform }}
+ parallel: true
+
+ report-coverage:
+ needs: test
+ if: ${{ always() }}
+ runs-on: ubuntu-latest
+ steps:
+ - uses: coverallsapp/github-action@cfd0633edbd2411b532b808ba7a8b5e04f76d2c8
+ with:
+ parallel-finished: true
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d92c880
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+# https://dart.dev/tools/pub/private-files
+.dart_tool
+pubspec.lock
+_site/
diff --git a/CODEOWNERS b/CODEOWNERS
new file mode 100644
index 0000000..7694822
--- /dev/null
+++ b/CODEOWNERS
@@ -0,0 +1,5 @@
+# The CODEOWNERS file helps to define individuals or teams that responsible
+# for code within the repository
+# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
+
+/pkgs/unified_analytics/ @andrewkolos
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..3c28563
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,51 @@
+# How to Contribute
+
+We'd love to accept your patches and contributions to this project. There are
+just a few small guidelines you need to follow.
+
+## Contributor License Agreement
+
+Contributions to this project must be accompanied by a Contributor License
+Agreement (CLA). You (or your employer) retain the copyright to your
+contribution; this simply gives us permission to use and redistribute your
+contributions as part of the project. Head over to
+<https://cla.developers.google.com/> to see your current agreements on file or
+to sign a new one.
+
+You generally only need to submit a CLA once, so if you've already submitted one
+(even if it was for a different project), you probably don't need to do it
+again.
+
+## Code Reviews
+
+All submissions, including submissions by project members, require review. We
+use GitHub pull requests for this purpose. Consult
+[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
+information on using pull requests.
+
+## Coding style
+
+The Dart source code in this repo follows the:
+
+ * [Dart style guide](https://dart.dev/guides/language/effective-dart/style)
+
+You should familiarize yourself with those guidelines.
+
+## File headers
+
+All files in the Dart project must start with the following header; if you add a
+new file please also add this. The year should be a single number stating the
+year the file was created (don't use a range like "2011-2012"). Additionally, if
+you edit an existing file, you shouldn't update the year.
+
+ // Copyright (c) 2023, 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.
+
+## Community Guidelines
+
+This project follows
+[Google's Open Source Community Guidelines](https://opensource.google/conduct/).
+
+We pledge to maintain an open and welcoming environment. For details, see our
+[code of conduct](https://dart.dev/code-of-conduct).
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..4fd5739
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2023, 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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ac242eb
--- /dev/null
+++ b/README.md
@@ -0,0 +1,103 @@
+<!-- [](https://github.com/dart-lang/tools/actions/workflows/dart.yml) -->
+
+## Overview
+
+This repository is home to tooling related Dart packages. Generally, this means
+packages published through the
+[tools.dart.dev](https://pub.dev/publishers/tools.dart.dev) publisher that
+don't naturally belong to other topic monorepos (like
+[dart-lang/build](https://github.com/dart-lang/build),
+[dart-lang/test](https://github.com/dart-lang/test), or
+[dart-lang/shelf](https://github.com/dart-lang/shelf)).
+
+## Packages
+
+| Package | Description | Issues | Version |
+| --- | --- | --- | --- |
+| [bazel_worker](pkgs/bazel_worker/) | Protocol and utilities to implement or invoke persistent bazel workers. | [][bazel_worker_issues] | [](https://pub.dev/packages/bazel_worker) |
+| [benchmark_harness](pkgs/benchmark_harness/) | The official Dart project benchmark harness. | [][benchmark_harness_issues] | [](https://pub.dev/packages/benchmark_harness) |
+| [boolean_selector](pkgs/boolean_selector/) | A flexible syntax for boolean expressions, based on a simplified version of Dart's expression syntax. | [][boolean_selector_issues] | [](https://pub.dev/packages/boolean_selector) |
+| [browser_launcher](pkgs/browser_launcher/) | Provides a standardized way to launch web browsers for testing and tools. | [][browser_launcher_issues] | [](https://pub.dev/packages/browser_launcher) |
+| [cli_config](pkgs/cli_config/) | A library to take config values from configuration files, CLI arguments, and environment variables. | [][cli_config_issues] | [](https://pub.dev/packages/cli_config) |
+| [cli_util](pkgs/cli_util/) | A library to help in building Dart command-line apps. | [][cli_util_issues] | [](https://pub.dev/packages/cli_util) |
+| [clock](pkgs/clock/) | A fakeable wrapper for dart:core clock APIs. | [][clock_issues] | [](https://pub.dev/packages/clock) |
+| [code_builder](pkgs/code_builder/) | A fluent, builder-based library for generating valid Dart code. | [][code_builder_issues] | [](https://pub.dev/packages/code_builder) |
+| [coverage](pkgs/coverage/) | Coverage data manipulation and formatting | [][coverage_issues] | [](https://pub.dev/packages/coverage) |
+| [csslib](pkgs/csslib/) | A library for parsing and analyzing CSS (Cascading Style Sheets). | [][csslib_issues] | [](https://pub.dev/packages/csslib) |
+| [extension_discovery](pkgs/extension_discovery/) | A convention and utilities for package extension discovery. | [][extension_discovery_issues] | [](https://pub.dev/packages/extension_discovery) |
+| [file](pkgs/file/) | A pluggable, mockable file system abstraction for Dart. | [][file_issues] | [](https://pub.dev/packages/file) |
+| [file_testing](pkgs/file_testing/) | Testing utilities for package:file. | [][file_testing_issues] | [](https://pub.dev/packages/file_testing) |
+| [graphs](pkgs/graphs/) | Graph algorithms that operate on graphs in any representation. | [][graphs_issues] | [](https://pub.dev/packages/graphs) |
+| [html](pkgs/html/) | APIs for parsing and manipulating HTML content outside the browser. | [][html_issues] | [](https://pub.dev/packages/html) |
+| [io](pkgs/io/) | Utilities for the Dart VM Runtime including support for ANSI colors, file copying, and standard exit code values. | [][io_issues] | [](https://pub.dev/packages/io) |
+| [json_rpc_2](pkgs/json_rpc_2/) | Utilities to write a client or server using the JSON-RPC 2.0 spec. | [][json_rpc_2_issues] | [](https://pub.dev/packages/json_rpc_2) |
+| [markdown](pkgs/markdown/) | A portable Markdown library written in Dart that can parse Markdown into HTML. | [][markdown_issues] | [](https://pub.dev/packages/markdown) |
+| [mime](pkgs/mime/) | Utilities for handling media (MIME) types, including determining a type from a file extension and file contents. | [][mime_issues] | [](https://pub.dev/packages/mime) |
+| [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. | [][oauth2_issues] | [](https://pub.dev/packages/oauth2) |
+| [package_config](pkgs/package_config/) | Support for reading and writing Dart Package Configuration files. | [][package_config_issues] | [](https://pub.dev/packages/package_config) |
+| [pool](pkgs/pool/) | Manage a finite pool of resources. Useful for controlling concurrent file system or network requests. | [][pool_issues] | [](https://pub.dev/packages/pool) |
+| [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. | [][pub_semver_issues] | [](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. | [][pubspec_parse_issues] | [](https://pub.dev/packages/pubspec_parse) |
+| [source_map_stack_trace](pkgs/source_map_stack_trace/) | A package for applying source maps to stack traces. | [][source_map_stack_trace_issues] | [](https://pub.dev/packages/source_map_stack_trace) |
+| [source_maps](pkgs/source_maps/) | A library to programmatically manipulate source map files. | [][source_maps_issues] | [](https://pub.dev/packages/source_maps) |
+| [source_span](pkgs/source_span/) | Provides a standard representation for source code locations and spans. | [][source_span_issues] | [](https://pub.dev/packages/source_span) |
+| [sse](pkgs/sse/) | Provides client and server functionality for setting up bi-directional communication through Server Sent Events (SSE) and corresponding POST requests. | [][sse_issues] | [](https://pub.dev/packages/sse) |
+| [stack_trace](pkgs/stack_trace/) | A package for manipulating stack traces and printing them readably. | [][stack_trace_issues] | [](https://pub.dev/packages/stack_trace) |
+| [stream_channel](pkgs/stream_channel/) | An abstraction for two-way communication channels based on the Dart Stream class. | [][stream_channel_issues] | [](https://pub.dev/packages/stream_channel) |
+| [stream_transform](pkgs/stream_transform/) | A collection of utilities to transform and manipulate streams. | [][stream_transform_issues] | [](https://pub.dev/packages/stream_transform) |
+| [string_scanner](pkgs/string_scanner/) | A class for parsing strings using a sequence of patterns. | [][string_scanner_issues] | [](https://pub.dev/packages/string_scanner) |
+| [term_glyph](pkgs/term_glyph/) | Useful Unicode glyphs and ASCII substitutes. | [][term_glyph_issues] | [](https://pub.dev/packages/term_glyph) |
+| [test_reflective_loader](pkgs/test_reflective_loader/) | Support for discovering tests and test suites using reflection. | [][test_reflective_loader_issues] | [](https://pub.dev/packages/test_reflective_loader) |
+| [timing](pkgs/timing/) | A simple package for tracking the performance of synchronous and asynchronous actions. | [][timing_issues] | [](https://pub.dev/packages/timing) |
+| [unified_analytics](pkgs/unified_analytics/) | A package for logging analytics for all Dart and Flutter related tooling to Google Analytics. | [][unified_analytics_issues] | [](https://pub.dev/packages/unified_analytics) |
+| [watcher](pkgs/watcher/) | A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified. | [][watcher_issues] | [](https://pub.dev/packages/watcher) |
+| [yaml](pkgs/yaml/) | A parser for YAML, a human-friendly data serialization standard | [][yaml_issues] | [](https://pub.dev/packages/yaml) |
+| [yaml_edit](pkgs/yaml_edit/) | A library for YAML manipulation with comment and whitespace preservation. | [][yaml_edit_issues] | [](https://pub.dev/packages/yaml_edit) |
+
+[bazel_worker_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Abazel_worker
+[benchmark_harness_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Abenchmark_harness
+[boolean_selector_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aboolean_selector
+[browser_launcher_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Abrowser_launcher
+[cli_config_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Acli_config
+[cli_util_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Acli_util
+[clock_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aclock
+[code_builder_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Acode_builder
+[coverage_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Acoverage
+[csslib_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Acsslib
+[extension_discovery_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aextension_discovery
+[file_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Afile
+[file_testing_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Afile_testing
+[graphs_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Agraphs
+[html_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Ahtml
+[io_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aio
+[json_rpc_2_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Ajson_rpc_2
+[markdown_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Amarkdown
+[mime_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Amime
+[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
+[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
+[source_maps_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Asource_maps
+[source_span_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Asource_span
+[sse_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Asse
+[stack_trace_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Astack_trace
+[stream_channel_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Astream_channel
+[stream_transform_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Astream_transform
+[string_scanner_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Astring_scanner
+[term_glyph_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aterm_glyph
+[test_reflective_loader_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Atest_reflective_loader
+[timing_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Atiming
+[unified_analytics_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aunified_analytics
+[watcher_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Awatcher
+[yaml_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Ayaml
+[yaml_edit_issues]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Ayaml_edit
+
+## Publishing automation
+
+For information about our publishing automation and release process, see
+https://github.com/dart-lang/ecosystem/wiki/Publishing-automation.
+
+For additional information about contributing, see our
+[contributing](CONTRIBUTING.md) page.
diff --git a/pkgs/bazel_worker/.gitignore b/pkgs/bazel_worker/.gitignore
new file mode 100644
index 0000000..00035d7
--- /dev/null
+++ b/pkgs/bazel_worker/.gitignore
@@ -0,0 +1,10 @@
+.buildlog
+.DS_Store
+.idea
+.pub/
+.settings/
+build/
+packages
+.packages
+pubspec.lock
+.dart_tool
diff --git a/pkgs/bazel_worker/AUTHORS b/pkgs/bazel_worker/AUTHORS
new file mode 100644
index 0000000..e8063a8
--- /dev/null
+++ b/pkgs/bazel_worker/AUTHORS
@@ -0,0 +1,6 @@
+# Below is a list of people and organizations that have contributed
+# to the project. Names should be added to the list like so:
+#
+# Name/Organization <email address>
+
+Google Inc.
diff --git a/pkgs/bazel_worker/CHANGELOG.md b/pkgs/bazel_worker/CHANGELOG.md
new file mode 100644
index 0000000..d74d967
--- /dev/null
+++ b/pkgs/bazel_worker/CHANGELOG.md
@@ -0,0 +1,193 @@
+## 1.1.3-wip
+
+* Require Dart SDK `^3.4.0`.
+
+## 1.1.2
+
+* Require Dart SDK `^3.1.0`.
+* Move to `dart-lang/tools` monorepo.
+
+## 1.1.1
+
+* Fix a bug where if spawnWorker threw an error, work requests would hang
+ forever instead of failing.
+
+## 1.1.0
+
+* Add constructors with named parameters to
+ the generated worker protocol messages.
+* Include comments on the generated worker protocol API.
+
+## 1.0.3
+
+* Require `package:protobuf` >= 3.0.0.
+* Require Dart SDK >= 2.19.0
+
+## 1.0.2
+
+* Expand pub documentation to improve pub score.
+
+## 1.0.1
+
+* Require Dart SDK >=2.14.0
+* Drop dependency on `package:pedantic`.
+
+## 1.0.0
+
+* Improve `AsyncMessageGrouper` performance.
+* Add `benchmark/benchmark.dart` measuring `AsyncMessageGrouper` performance.
+
+## 1.0.0-nullsafety.0
+
+* Migrate to null safety.
+* Use `WorkResponse` with `exitCode` set to `EXIT_CODE_BROKEN_PIPE` instead of
+ `null` responses.
+
+## 0.1.25+1-dev
+
+* Regenerate proto code and fix some new analysis hints.
+
+## 0.1.25
+
+* Add `isBroadcast` implementation to `TestStdin` classes.
+
+## 0.1.24
+
+* Check for closed port when trying to read a response in
+ `IsolateDriverConnection` and return `null` if there is nothing to be read.
+
+## 0.1.23+1
+
+* Don't rely on `exitCode` to know when a worker terminates, instead wait for
+ the input stream to close.
+ * The SDK may also start throwing instead of returning a `null` here, so this
+ pre-emptively guards against that.
+
+## 0.1.23
+
+* Support protobuf `1.x`.
+* Added a tool for updating generated proto files and updated them
+ using the latest version of the protoc_plugin package.
+ * This required a lower bound bump of the `protobuf` package to `0.14.4`.
+
+## 0.1.22
+
+* Require protobuf 0.14.0.
+
+## 0.1.21+1
+
+* Don't rely on `exitCode` to know when a worker terminates, instead wait for
+ the input stream to close. Backport of fix in `0.1.23+1` in a version that
+ does not require a newer protobuf.
+
+## 0.1.21
+
+* Make `TestStdinAsync` behave like a `Stream<Uint8List>`
+
+## 0.1.20
+
+* Close worker `outputStream` on `cancel`.
+
+## 0.1.19
+
+* Work around https://github.com/dart-lang/sdk/issues/35874.
+
+## 0.1.18
+
+* Add a `trackWork` optional named argument to `BazelDriver.doWork`. This allows
+ the caller to know when a work request is actually sent to a worker.
+
+## 0.1.17
+
+* Allow protobuf 0.13.0.
+
+## 0.1.16
+
+* Update the worker_protocol.pb.dart file with the latest proto generator.
+* Require protobuf 0.11.0.
+
+## 0.1.15
+
+* Update the worker_protocol.pb.dart file with the latest proto generator.
+* Require protobuf 0.10.4.
+
+## 0.1.14
+
+* Allow workers to support running in isolates. To support running in isolates,
+ workers must modify their `main` method to accept a `SendPort` then use it
+ when creating the `AsyncWorkerConnection`. See `async_worker` in `e2e_test`.
+
+## 0.1.13
+
+* Support protobuf 0.10.0.
+
+## 0.1.12
+
+* Set max SDK version to `<3.0.0`.
+
+## 0.1.11
+
+* Added support for protobuf 0.9.0.
+
+## 0.1.10
+
+* Update the SDK dependency to 2.0.0-dev.17.0.
+* Update to protobuf version 0.8.0
+* Remove usages of deprecated upper-case constants from the SDK.
+
+## 0.1.9
+
+* Update the worker_protocol.pb.dart file with the latest proto generator.
+
+## 0.1.8
+
+* Add `Future cancel()` method to `DriverConnection`, which in the case of a
+ `StdDriverConnection` closes the input stream.
+ * The `terminateWorkers` method on `BazelWorkerDriver` now calls `cancel` on
+ all worker connections to ensure the vm can exit correctly.
+
+## 0.1.7
+
+* Update the `BazelWorkerDriver` class to handle worker crashes, and retry work
+ requests. The number of retries is configurable with the new `int maxRetries`
+ optional arg to the `BazelWorkerDriver` constructor.
+
+## 0.1.6
+
+* Update the worker_protocol.pb.dart file with the latest proto generator.
+* Add support for package:async 2.x and package:protobuf 6.x.
+
+## 0.1.5
+
+* Change TestStdinAsync.controller to StreamController<List<int>> (instead of
+ using dynamic as the type argument).
+
+## 0.1.4
+
+* Added `BazelWorkerDriver` class, which can be used to implement the bazel side
+ of the protocol. This allows you to speak to any process which knows the bazel
+ protocol from your own process.
+* Changed `WorkerConnection#readRequest` to return a `FutureOr<WorkRequest>`
+ instead of dynamic.
+
+## 0.1.3
+
+* Add automatic intercepting of print calls and append them to
+ `response.output`. This makes more libraries work out of the box, as printing
+ would previously cause an error due to communication over stdin/stdout.
+ * Note that using stdin/stdout directly will still cause an error, but that is
+ less common.
+
+## 0.1.2
+
+* Add better handling for the case where stdin gives an error instead of an EOF.
+
+## 0.1.1
+
+* Export `AsyncMessageGrouper` and `SyncMessageGrouper` as part of the testing
+ library. These can assist when writing e2e tests and communicating with a
+ worker process.
+
+## 0.1.0
+
+* Initial version.
diff --git a/pkgs/bazel_worker/LICENSE b/pkgs/bazel_worker/LICENSE
new file mode 100644
index 0000000..2372431
--- /dev/null
+++ b/pkgs/bazel_worker/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2016, 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.
diff --git a/pkgs/bazel_worker/README.md b/pkgs/bazel_worker/README.md
new file mode 100644
index 0000000..22a86dc
--- /dev/null
+++ b/pkgs/bazel_worker/README.md
@@ -0,0 +1,66 @@
+Tools for creating a persistent worker loop for [bazel](https://bazel.build/).
+
+## Usage
+
+There are two abstract classes provided by this package, `AsyncWorkerLoop` and
+`SyncWorkerLoop`. These each have a `performRequest` method which you must
+implement.
+
+Lets look at a simple example of a `SyncWorkerLoop` implementation:
+
+```dart
+import 'dart:io';
+import 'package:bazel_worker/bazel_worker.dart';
+
+void main() {
+ // Blocks until it gets an EOF from stdin.
+ SyncSimpleWorker().run();
+}
+
+class SyncSimpleWorker extends SyncWorkerLoop {
+ /// Must synchronously return a [WorkResponse], since this is a
+ /// [SyncWorkerLoop].
+ WorkResponse performRequest(WorkRequest request) {
+ File('hello.txt').writeAsStringSync('hello world!');
+ return WorkResponse()..exitCode = EXIT_CODE_OK;
+ }
+}
+```
+
+And now the same thing, implemented as an `AsyncWorkerLoop`:
+
+```dart
+import 'dart:io';
+import 'package:bazel_worker/bazel_worker.dart';
+
+void main() {
+ // Doesn't block, runs tasks async as they are received on stdin.
+ AsyncSimpleWorker().run();
+}
+
+class AsyncSimpleWorker extends AsyncWorkerLoop {
+ /// Must return a [Future<WorkResponse>], since this is an
+ /// [AsyncWorkerLoop].
+ Future<WorkResponse> performRequest(WorkRequest request) async {
+ await File('hello.txt').writeAsString('hello world!');
+ return WorkResponse()..exitCode = EXIT_CODE_OK;
+ }
+}
+```
+
+As you can see, these are nearly identical, it mostly comes down to the
+constraints on your package and personal preference which one you choose to
+implement.
+
+## Testing
+
+A `package:bazel_worker/testing.dart` file is also provided, which can greatly
+assist with writing unit tests for your worker. See the
+`test/worker_loop_test.dart` test included in this package for an example of how
+the helpers can be used.
+
+## Features and bugs
+
+Please file feature requests and bugs at the [issue tracker][tracker].
+
+[tracker]: https://github.com/dart-lang/tools/issues
diff --git a/pkgs/bazel_worker/analysis_options.yaml b/pkgs/bazel_worker/analysis_options.yaml
new file mode 100644
index 0000000..2648764
--- /dev/null
+++ b/pkgs/bazel_worker/analysis_options.yaml
@@ -0,0 +1,9 @@
+# https://dart.dev/guides/language/analysis-options
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-casts: true
+ errors:
+ # For the generated file
+ lines_longer_than_80_chars: ignore
diff --git a/pkgs/bazel_worker/benchmark/benchmark.dart b/pkgs/bazel_worker/benchmark/benchmark.dart
new file mode 100644
index 0000000..0a03122
--- /dev/null
+++ b/pkgs/bazel_worker/benchmark/benchmark.dart
@@ -0,0 +1,60 @@
+import 'dart:math';
+import 'dart:typed_data';
+
+import 'package:bazel_worker/bazel_worker.dart';
+import 'package:bazel_worker/src/async_message_grouper.dart';
+
+/// Benchmark for `AsyncMessageGrouper`.
+Future<void> main() async {
+ // Create a large work request with 10,000 inputs.
+ var workRequest = WorkRequest();
+ for (var i = 0; i != 10000; ++i) {
+ var path = 'blaze-bin/some/path/to/a/file/that/is/an/input/$i';
+ workRequest
+ ..arguments.add('--input=$path')
+ ..inputs.add(Input(path: '', digest: List.filled(70, 0x11)));
+ }
+
+ // Serialize it.
+ var requestBytes = workRequest.writeToBuffer();
+ var length = requestBytes.length;
+ print('Request has $length requestBytes.');
+
+ // Add the length in front base 128 encoded as in the worker protocol.
+ requestBytes = Uint8List.fromList(
+ requestBytes.toList()..insertAll(0, _varInt(length)),
+ );
+
+ // Split into 10000 byte chunks.
+ var lists = <Uint8List>[];
+ for (var i = 0; i < requestBytes.length; i += 10000) {
+ lists.add(
+ Uint8List.sublistView(
+ requestBytes,
+ i,
+ min(i + 10000, requestBytes.length),
+ ),
+ );
+ }
+
+ // Time `AsyncMessageGrouper` and deserialization.
+ for (var i = 0; i != 30; ++i) {
+ var stopwatch = Stopwatch()..start();
+ var asyncGrouper = AsyncMessageGrouper(Stream.fromIterable(lists));
+ var message = (await asyncGrouper.next)!;
+ print('Grouped in ${stopwatch.elapsedMilliseconds}ms');
+ stopwatch.reset();
+ WorkRequest.fromBuffer(message);
+ print('Deserialized in ${stopwatch.elapsedMilliseconds}ms');
+ }
+}
+
+Uint8List _varInt(int value) {
+ var result = <int>[];
+ while (value >= 0x80) {
+ result.add(0x80 | (value & 0x7f));
+ value >>= 7;
+ }
+ result.add(value);
+ return Uint8List.fromList(result);
+}
diff --git a/pkgs/bazel_worker/e2e_test/analysis_options.yaml b/pkgs/bazel_worker/e2e_test/analysis_options.yaml
new file mode 120000
index 0000000..c9e0d9f
--- /dev/null
+++ b/pkgs/bazel_worker/e2e_test/analysis_options.yaml
@@ -0,0 +1 @@
+../analysis_options.yaml
\ No newline at end of file
diff --git a/pkgs/bazel_worker/e2e_test/bin/async_worker.dart b/pkgs/bazel_worker/e2e_test/bin/async_worker.dart
new file mode 100644
index 0000000..80d624f
--- /dev/null
+++ b/pkgs/bazel_worker/e2e_test/bin/async_worker.dart
@@ -0,0 +1,14 @@
+// Copyright (c) 2017, 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:isolate';
+
+import 'package:e2e_test/async_worker.dart';
+
+/// This worker can run in one of two ways: normally, using stdin/stdout, or
+/// in an isolate, communicating over a [SendPort].
+Future main(List<String> args, [SendPort? sendPort]) async {
+ await ExampleAsyncWorker(sendPort).run();
+}
diff --git a/pkgs/bazel_worker/e2e_test/bin/async_worker_in_isolate.dart b/pkgs/bazel_worker/e2e_test/bin/async_worker_in_isolate.dart
new file mode 100644
index 0000000..285b03d
--- /dev/null
+++ b/pkgs/bazel_worker/e2e_test/bin/async_worker_in_isolate.dart
@@ -0,0 +1,27 @@
+// 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.
+
+import 'dart:async';
+import 'dart:isolate';
+
+import 'package:e2e_test/forwards_to_isolate_async_worker.dart';
+
+/// Wraps the worker provided by `async_worker.dart`, launching it in an
+/// isolate. Requests are forwarded to the isolate and responses are returned
+/// directly from the isolate.
+///
+/// Anyone actually using the facility to wrap a worker in an isolate will want
+/// to use this code to do additional work, for example post processing one of
+/// the output files.
+Future main(List<String> args, [SendPort? message]) async {
+ var receivePort = ReceivePort();
+ await Isolate.spawnUri(
+ Uri.file('async_worker.dart'),
+ [],
+ receivePort.sendPort,
+ );
+
+ var worker = await ForwardsToIsolateAsyncWorker.create(receivePort);
+ await worker.run();
+}
diff --git a/pkgs/bazel_worker/e2e_test/bin/sync_worker.dart b/pkgs/bazel_worker/e2e_test/bin/sync_worker.dart
new file mode 100644
index 0000000..9bdcc77
--- /dev/null
+++ b/pkgs/bazel_worker/e2e_test/bin/sync_worker.dart
@@ -0,0 +1,9 @@
+// Copyright (c) 2017, 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:e2e_test/sync_worker.dart';
+
+void main() {
+ ExampleSyncWorker().run();
+}
diff --git a/pkgs/bazel_worker/e2e_test/lib/async_worker.dart b/pkgs/bazel_worker/e2e_test/lib/async_worker.dart
new file mode 100644
index 0000000..55f5171
--- /dev/null
+++ b/pkgs/bazel_worker/e2e_test/lib/async_worker.dart
@@ -0,0 +1,21 @@
+// Copyright (c) 2017, 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:isolate';
+
+import 'package:bazel_worker/bazel_worker.dart';
+
+/// Example worker that just returns in its response all the arguments passed
+/// separated by newlines.
+class ExampleAsyncWorker extends AsyncWorkerLoop {
+ /// Set [sendPort] to run in an isolate.
+ ExampleAsyncWorker([SendPort? sendPort])
+ : super(connection: AsyncWorkerConnection(sendPort: sendPort));
+
+ @override
+ Future<WorkResponse> performRequest(WorkRequest request) async {
+ return WorkResponse(exitCode: 0, output: request.arguments.join('\n'));
+ }
+}
diff --git a/pkgs/bazel_worker/e2e_test/lib/forwards_to_isolate_async_worker.dart b/pkgs/bazel_worker/e2e_test/lib/forwards_to_isolate_async_worker.dart
new file mode 100644
index 0000000..a4845cf
--- /dev/null
+++ b/pkgs/bazel_worker/e2e_test/lib/forwards_to_isolate_async_worker.dart
@@ -0,0 +1,30 @@
+// Copyright (c) 2017, 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:isolate';
+
+import 'package:bazel_worker/bazel_worker.dart';
+import 'package:bazel_worker/driver.dart';
+
+/// Example worker that just forwards requests to an isolate.
+class ForwardsToIsolateAsyncWorker extends AsyncWorkerLoop {
+ final IsolateDriverConnection _isolateDriverConnection;
+
+ static Future<ForwardsToIsolateAsyncWorker> create(
+ ReceivePort receivePort,
+ ) async {
+ return ForwardsToIsolateAsyncWorker(
+ await IsolateDriverConnection.create(receivePort),
+ );
+ }
+
+ ForwardsToIsolateAsyncWorker(this._isolateDriverConnection);
+
+ @override
+ Future<WorkResponse> performRequest(WorkRequest request) {
+ _isolateDriverConnection.writeRequest(request);
+ return _isolateDriverConnection.readResponse();
+ }
+}
diff --git a/pkgs/bazel_worker/e2e_test/lib/sync_worker.dart b/pkgs/bazel_worker/e2e_test/lib/sync_worker.dart
new file mode 100644
index 0000000..687ecdd
--- /dev/null
+++ b/pkgs/bazel_worker/e2e_test/lib/sync_worker.dart
@@ -0,0 +1,14 @@
+// Copyright (c) 2017, 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:bazel_worker/bazel_worker.dart';
+
+/// Example worker that just returns in its response all the arguments passed
+/// separated by newlines.
+class ExampleSyncWorker extends SyncWorkerLoop {
+ @override
+ WorkResponse performRequest(WorkRequest request) {
+ return WorkResponse(exitCode: 0, output: request.arguments.join('\n'));
+ }
+}
diff --git a/pkgs/bazel_worker/e2e_test/pubspec.yaml b/pkgs/bazel_worker/e2e_test/pubspec.yaml
new file mode 100644
index 0000000..7eaa89a
--- /dev/null
+++ b/pkgs/bazel_worker/e2e_test/pubspec.yaml
@@ -0,0 +1,15 @@
+name: e2e_test
+publish_to: none
+
+environment:
+ sdk: ^3.4.0
+
+dependencies:
+ bazel_worker:
+ path: ../
+
+dev_dependencies:
+ cli_util: ^0.4.2
+ dart_flutter_team_lints: ^3.0.0
+ path: ^1.8.0
+ test: ^1.16.0
diff --git a/pkgs/bazel_worker/e2e_test/test/e2e_test.dart b/pkgs/bazel_worker/e2e_test/test/e2e_test.dart
new file mode 100644
index 0000000..6b79b5e
--- /dev/null
+++ b/pkgs/bazel_worker/e2e_test/test/e2e_test.dart
@@ -0,0 +1,67 @@
+// Copyright (c) 2017, 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';
+
+import 'package:bazel_worker/driver.dart';
+import 'package:cli_util/cli_util.dart';
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+
+void main() {
+ var dart = p.join(sdkPath, 'bin', 'dart');
+ runE2eTestForWorker(
+ 'sync worker',
+ () => Process.start(dart, [p.join('bin', 'sync_worker.dart')]),
+ );
+ runE2eTestForWorker(
+ 'async worker',
+ () => Process.start(dart, [p.join('bin', 'async_worker.dart')]),
+ );
+ runE2eTestForWorker(
+ 'async worker in isolate',
+ () => Process.start(dart, [p.join('bin', 'async_worker_in_isolate.dart')]),
+ );
+}
+
+void runE2eTestForWorker(String groupName, SpawnWorker spawnWorker) {
+ late BazelWorkerDriver driver;
+ group(groupName, () {
+ setUp(() {
+ driver = BazelWorkerDriver(spawnWorker);
+ });
+
+ tearDown(() async {
+ await driver.terminateWorkers();
+ });
+
+ test('single work request', () async {
+ await _doRequests(driver, count: 1);
+ });
+
+ test('lots of requests', () async {
+ await _doRequests(driver, count: 1000);
+ });
+ });
+}
+
+/// Runs [count] work requests through [driver], and asserts that they all
+/// completed with the correct response.
+Future _doRequests(BazelWorkerDriver driver, {int? count}) async {
+ count ??= 100;
+ var requests = List.generate(count, (requestNum) {
+ var request = WorkRequest(
+ arguments: List.generate(requestNum, (argNum) => '$argNum'),
+ );
+ return request;
+ });
+ var responses = await Future.wait(requests.map(driver.doWork));
+ for (var i = 0; i < responses.length; i++) {
+ var request = requests[i];
+ var response = responses[i];
+ expect(response.exitCode, EXIT_CODE_OK);
+ expect(response.output, request.arguments.join('\n'));
+ }
+}
diff --git a/pkgs/bazel_worker/example/README.md b/pkgs/bazel_worker/example/README.md
new file mode 100644
index 0000000..dad3b79
--- /dev/null
+++ b/pkgs/bazel_worker/example/README.md
@@ -0,0 +1,3 @@
+Run `dart example/client.dart`. The client will start up a worker process, send
+a single work request, read a file written by the worker, then terminate the
+worker.
diff --git a/pkgs/bazel_worker/example/client.dart b/pkgs/bazel_worker/example/client.dart
new file mode 100644
index 0000000..326bb18
--- /dev/null
+++ b/pkgs/bazel_worker/example/client.dart
@@ -0,0 +1,26 @@
+import 'dart:io';
+
+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),
+ maxWorkers: 4,
+ );
+ var response = await driver.doWork(WorkRequest(arguments: ['foo']));
+ if (response.exitCode != EXIT_CODE_OK) {
+ print('Worker request failed');
+ } else {
+ print('Worker request succeeded, file content:');
+ var outputFile = File.fromUri(scratchSpace.uri.resolve('hello.txt'));
+ print(await outputFile.readAsString());
+ }
+ await scratchSpace.delete(recursive: true);
+ await driver.terminateWorkers();
+}
diff --git a/pkgs/bazel_worker/example/worker.dart b/pkgs/bazel_worker/example/worker.dart
new file mode 100644
index 0000000..ba3f48c
--- /dev/null
+++ b/pkgs/bazel_worker/example/worker.dart
@@ -0,0 +1,15 @@
+import 'dart:io';
+import 'package:bazel_worker/bazel_worker.dart';
+
+void main() {
+ // Blocks until it gets an EOF from stdin.
+ SyncSimpleWorker().run();
+}
+
+class SyncSimpleWorker extends SyncWorkerLoop {
+ @override
+ WorkResponse performRequest(WorkRequest request) {
+ File('hello.txt').writeAsStringSync(request.arguments.first);
+ return WorkResponse(exitCode: EXIT_CODE_OK);
+ }
+}
diff --git a/pkgs/bazel_worker/lib/bazel_worker.dart b/pkgs/bazel_worker/lib/bazel_worker.dart
new file mode 100644
index 0000000..d77b53f
--- /dev/null
+++ b/pkgs/bazel_worker/lib/bazel_worker.dart
@@ -0,0 +1,11 @@
+// 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.
+
+export 'src/constants.dart';
+export 'src/message_grouper.dart';
+export 'src/worker/async_worker_loop.dart';
+export 'src/worker/sync_worker_loop.dart';
+export 'src/worker/worker_connection.dart';
+export 'src/worker/worker_loop.dart';
+export 'src/worker_protocol.pb.dart';
diff --git a/pkgs/bazel_worker/lib/driver.dart b/pkgs/bazel_worker/lib/driver.dart
new file mode 100644
index 0000000..362841c
--- /dev/null
+++ b/pkgs/bazel_worker/lib/driver.dart
@@ -0,0 +1,9 @@
+// Copyright (c) 2017, 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/constants.dart';
+export 'src/driver/driver.dart';
+export 'src/driver/driver_connection.dart';
+export 'src/message_grouper.dart';
+export 'src/worker_protocol.pb.dart';
diff --git a/pkgs/bazel_worker/lib/src/async_message_grouper.dart b/pkgs/bazel_worker/lib/src/async_message_grouper.dart
new file mode 100644
index 0000000..8fc4778
--- /dev/null
+++ b/pkgs/bazel_worker/lib/src/async_message_grouper.dart
@@ -0,0 +1,136 @@
+// 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.
+
+import 'dart:async';
+import 'dart:math';
+import 'dart:typed_data';
+
+import 'package:async/async.dart';
+import 'package:protobuf/protobuf.dart';
+
+import 'message_grouper.dart';
+
+/// Collects stream data into messages by interpreting it as
+/// base-128 encoded lengths interleaved with raw data.
+class AsyncMessageGrouper implements MessageGrouper {
+ /// The input stream.
+ final StreamQueue<List<int>> _inputQueue;
+
+ /// The current input buffer.
+ List<int> _inputBuffer = [];
+
+ /// Position in the current input buffer.
+ int _inputBufferPos = 0;
+
+ /// Completes after [cancel] is called or `inputStream` is closed.
+ Future<void> get done => _done.future;
+ final _done = Completer<void>();
+
+ /// Whether currently reading length or raw data.
+ bool _readingLength = true;
+
+ /// If reading length, buffer to build up length one byte at a time until
+ /// done.
+ List<int> _lengthBuffer = [];
+
+ /// If reading raw data, buffer for the data.
+ Uint8List _message = Uint8List(0);
+
+ /// If reading raw data, position in the buffer.
+ int _messagePos = 0;
+
+ AsyncMessageGrouper(Stream<List<int>> inputStream)
+ : _inputQueue = StreamQueue(inputStream);
+
+ /// Returns the next full message that is received, or null if none are left.
+ @override
+ Future<List<int>?> get next async {
+ try {
+ // Loop while there is data in the input buffer or the input stream.
+ 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;
+ _inputBufferPos = 0;
+ }
+
+ // Loop over the input buffer. Might return without reading the full
+ // buffer if a message completes. Then, this is tracked in
+ // `_inputBufferPos`.
+ while (_inputBufferPos != _inputBuffer.length) {
+ if (_readingLength) {
+ // Reading message length byte by byte.
+ var byte = _inputBuffer[_inputBufferPos++];
+ _lengthBuffer.add(byte);
+ // Check for the last byte in the length, and then read it.
+ if ((byte & 0x80) == 0) {
+ var reader = CodedBufferReader(_lengthBuffer);
+ var length = reader.readInt32();
+ _lengthBuffer = [];
+
+ // Special case: don't keep reading an empty message, return it
+ // and `_readingLength` stays true.
+ if (length == 0) {
+ return Uint8List(0);
+ }
+
+ // Switch to reading raw data. Allocate message buffer and reset
+ // `_messagePos`.
+ _readingLength = false;
+ _message = Uint8List(length);
+ _messagePos = 0;
+ }
+ } else {
+ // Copy as much as possible from the input buffer. Limit is the
+ // smaller of the remaining length to fill in the message and the
+ // remaining length in the buffer.
+ var lengthToCopy = min(
+ _message.length - _messagePos,
+ _inputBuffer.length - _inputBufferPos,
+ );
+ _message.setRange(
+ _messagePos,
+ _messagePos + lengthToCopy,
+ _inputBuffer.sublist(
+ _inputBufferPos,
+ _inputBufferPos + lengthToCopy,
+ ),
+ );
+ _messagePos += lengthToCopy;
+ _inputBufferPos += lengthToCopy;
+
+ // If there is a complete message to return, return it and switch
+ // back to reading length.
+ if (_messagePos == _message.length) {
+ var result = _message;
+ // Don't keep a reference to the message.
+ _message = Uint8List(0);
+ _readingLength = true;
+ return result;
+ }
+ }
+ }
+ }
+
+ // If there is nothing left in the queue then cancel the subscription.
+ unawaited(cancel());
+ } catch (e) {
+ // It appears we sometimes get an exception instead of -1 as expected when
+ // stdin closes, this handles that in the same way (returning a null
+ // message)
+ return null;
+ }
+ return null;
+ }
+
+ /// Stop listening to the stream for further updates.
+ Future cancel() {
+ if (!_done.isCompleted) {
+ _done.complete(null);
+ return _inputQueue.cancel()!;
+ }
+ return done;
+ }
+}
diff --git a/pkgs/bazel_worker/lib/src/constants.dart b/pkgs/bazel_worker/lib/src/constants.dart
new file mode 100644
index 0000000..a6d044a
--- /dev/null
+++ b/pkgs/bazel_worker/lib/src/constants.dart
@@ -0,0 +1,9 @@
+// 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.
+
+// ignore_for_file: constant_identifier_names
+
+const int EXIT_CODE_OK = 0;
+const int EXIT_CODE_ERROR = 15;
+const int EXIT_CODE_BROKEN_PIPE = 32;
diff --git a/pkgs/bazel_worker/lib/src/driver/driver.dart b/pkgs/bazel_worker/lib/src/driver/driver.dart
new file mode 100644
index 0000000..06cf0fe
--- /dev/null
+++ b/pkgs/bazel_worker/lib/src/driver/driver.dart
@@ -0,0 +1,251 @@
+// Copyright (c) 2017, 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:collection';
+import 'dart:io';
+
+import '../constants.dart';
+import '../worker_protocol.pb.dart';
+import 'driver_connection.dart';
+
+typedef SpawnWorker = Future<Process> Function();
+
+/// A driver for talking to a bazel worker.
+///
+/// This allows you to use any binary that supports the bazel worker protocol in
+/// the same way that bazel would, but from another dart process instead.
+class BazelWorkerDriver {
+ /// Idle worker processes.
+ final _idleWorkers = <Process>[];
+
+ /// The maximum number of idle workers at any given time.
+ final int _maxIdleWorkers;
+
+ /// The maximum number of times to retry a [_WorkAttempt] if there is an error.
+ final int _maxRetries;
+
+ /// The maximum number of concurrent workers to run at any given time.
+ final int _maxWorkers;
+
+ /// The number of currently active workers.
+ int get _numWorkers => _readyWorkers.length + _spawningWorkers.length;
+
+ /// All workers that are fully spawned and ready to handle work.
+ final _readyWorkers = <Process>[];
+
+ /// All workers that are in the process of being spawned.
+ final _spawningWorkers = <Future<Process>>[];
+
+ /// Work requests that haven't been started yet.
+ final _workQueue = Queue<_WorkAttempt>();
+
+ /// Factory method that spawns a worker process.
+ final SpawnWorker _spawnWorker;
+
+ BazelWorkerDriver(
+ this._spawnWorker, {
+ int? maxIdleWorkers,
+ int? maxWorkers,
+ int? maxRetries,
+ }) : _maxIdleWorkers = maxIdleWorkers ?? 4,
+ _maxWorkers = maxWorkers ?? 4,
+ _maxRetries = maxRetries ?? 4;
+
+ /// Waits for an available worker, and then sends [WorkRequest] to it.
+ ///
+ /// If [trackWork] is provided it will be invoked with a [Future] once the
+ /// [request] has been actually sent to the worker. This allows the caller
+ /// to determine when actual work is being done versus just waiting for an
+ /// available worker.
+ Future<WorkResponse> doWork(
+ WorkRequest request, {
+ void Function(Future<WorkResponse?>)? trackWork,
+ }) {
+ var attempt = _WorkAttempt(request, trackWork: trackWork);
+ _workQueue.add(attempt);
+ _runWorkQueue();
+ return attempt.response;
+ }
+
+ /// Calls `kill` on all worker processes.
+ Future terminateWorkers() async {
+ for (var worker in _readyWorkers.toList()) {
+ _killWorker(worker);
+ }
+ await Future.wait(
+ _spawningWorkers.map((worker) async {
+ _killWorker(await worker);
+ }),
+ );
+ }
+
+ /// Runs as many items in [_workQueue] as possible given the number of
+ /// available workers.
+ ///
+ /// Will spawn additional workers until [_maxWorkers] has been reached.
+ ///
+ /// This method synchronously drains the [_workQueue] and [_idleWorkers], but
+ /// some tasks may not actually start right away if they need to wait for a
+ /// worker to spin up.
+ void _runWorkQueue() {
+ // Bail out conditions, we will continue to call ourselves indefinitely
+ // until one of these is met.
+ if (_workQueue.isEmpty) return;
+ if (_numWorkers == _maxWorkers && _idleWorkers.isEmpty) return;
+ if (_numWorkers > _maxWorkers) {
+ throw StateError(
+ 'Internal error, created to many workers. Please '
+ 'file a bug at https://github.com/dart-lang/bazel_worker/issues/new',
+ );
+ }
+
+ // At this point we definitely want to run a task, we just need to decide
+ // whether or not we need to start up a new worker.
+ var attempt = _workQueue.removeFirst();
+ if (_idleWorkers.isNotEmpty) {
+ _runWorker(_idleWorkers.removeLast(), attempt);
+ } else {
+ // No need to block here, we want to continue to synchronously drain the
+ // 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<Object>((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();
+ }
+
+ /// Sends [attempt] to [worker].
+ ///
+ /// Once the worker responds then it will be added back to the pool of idle
+ /// workers.
+ void _runWorker(Process worker, _WorkAttempt attempt) {
+ var rescheduled = false;
+
+ runZonedGuarded(
+ () async {
+ var connection = _workerConnections[worker]!;
+
+ connection.writeRequest(attempt.request);
+ var responseFuture = connection.readResponse();
+ if (attempt.trackWork != null) {
+ attempt.trackWork!(responseFuture);
+ }
+ var response = await responseFuture;
+
+ // It is possible for us to complete with an error response due to an
+ // unhandled async error before we get here.
+ if (!attempt.responseCompleter.isCompleted) {
+ if (response.exitCode == EXIT_CODE_BROKEN_PIPE) {
+ rescheduled = _tryReschedule(attempt);
+ if (rescheduled) return;
+ stderr.writeln('Failed to run request ${attempt.request}');
+ response = WorkResponse(
+ exitCode: EXIT_CODE_ERROR,
+ output:
+ 'Invalid response from worker, this probably means it wrote '
+ 'invalid output or died.',
+ );
+ }
+ attempt.responseCompleter.complete(response);
+ _cleanUp(worker);
+ }
+ },
+ (e, s) {
+ // Note that we don't need to do additional cleanup here on failures. If
+ // the worker dies that is already handled in a generic fashion, we just
+ // need to make sure we complete with a valid response.
+ if (!attempt.responseCompleter.isCompleted) {
+ rescheduled = _tryReschedule(attempt);
+ if (rescheduled) return;
+ var response = WorkResponse(
+ exitCode: EXIT_CODE_ERROR,
+ output: 'Error running worker:\n$e\n$s',
+ );
+ attempt.responseCompleter.complete(response);
+ _cleanUp(worker);
+ }
+ },
+ );
+ }
+
+ /// Performs post-work cleanup for [worker].
+ void _cleanUp(Process worker) {
+ // If the worker crashes, it won't be in `_readyWorkers` any more, and
+ // we don't want to add it to _idleWorkers.
+ if (_readyWorkers.contains(worker)) {
+ _idleWorkers.add(worker);
+ }
+
+ // Do additional work if available.
+ _runWorkQueue();
+
+ // If the worker wasn't immediately used we might have to many idle
+ // workers now, kill one if necessary.
+ if (_idleWorkers.length > _maxIdleWorkers) {
+ // Note that whenever we spawn a worker we listen for its exit code
+ // and clean it up so we don't need to do that here.
+ var worker = _idleWorkers.removeLast();
+ _killWorker(worker);
+ }
+ }
+
+ /// Attempts to reschedule a failed [attempt].
+ ///
+ /// Returns whether or not the job was successfully rescheduled.
+ bool _tryReschedule(_WorkAttempt attempt) {
+ if (attempt.timesRetried >= _maxRetries) return false;
+ stderr.writeln('Rescheduling failed request...');
+ attempt.timesRetried++;
+ _workQueue.add(attempt);
+ _runWorkQueue();
+ return true;
+ }
+
+ void _killWorker(Process worker) {
+ _workerConnections[worker]!.cancel();
+ _readyWorkers.remove(worker);
+ _idleWorkers.remove(worker);
+ worker.kill();
+ }
+}
+
+/// Encapsulates an attempt to fulfill a [WorkRequest], a completer for the
+/// [WorkResponse], and the number of times it has been retried.
+class _WorkAttempt {
+ final WorkRequest request;
+ final responseCompleter = Completer<WorkResponse>();
+ final void Function(Future<WorkResponse?>)? trackWork;
+
+ Future<WorkResponse> get response => responseCompleter.future;
+
+ int timesRetried = 0;
+
+ _WorkAttempt(this.request, {this.trackWork});
+}
+
+final _workerConnections = Expando<DriverConnection>('connection');
diff --git a/pkgs/bazel_worker/lib/src/driver/driver_connection.dart b/pkgs/bazel_worker/lib/src/driver/driver_connection.dart
new file mode 100644
index 0000000..80d5c98
--- /dev/null
+++ b/pkgs/bazel_worker/lib/src/driver/driver_connection.dart
@@ -0,0 +1,132 @@
+// Copyright (c) 2017, 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';
+import 'dart:isolate';
+
+import '../async_message_grouper.dart';
+import '../constants.dart';
+import '../message_grouper.dart';
+import '../utils.dart';
+import '../worker_protocol.pb.dart';
+
+/// A connection from a `BazelWorkerDriver` to a worker.
+///
+/// Unlike `WorkerConnection` there is no synchronous version of this class.
+/// This is because drivers talk to multiple workers, so they should never block
+/// when waiting for the response of any individual worker.
+abstract class DriverConnection {
+ Future<WorkResponse> readResponse();
+
+ void writeRequest(WorkRequest request);
+
+ Future cancel();
+}
+
+/// Default implementation of [DriverConnection] that works with [Stdin]
+/// and [Stdout].
+class StdDriverConnection implements DriverConnection {
+ final AsyncMessageGrouper _messageGrouper;
+ final StreamSink<List<int>> _outputStream;
+
+ Future<void> get done => _messageGrouper.done;
+
+ StdDriverConnection({
+ Stream<List<int>>? inputStream,
+ StreamSink<List<int>>? outputStream,
+ }) : _messageGrouper = AsyncMessageGrouper(inputStream ?? stdin),
+ _outputStream = outputStream ?? stdout;
+
+ factory StdDriverConnection.forWorker(Process worker) => StdDriverConnection(
+ 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
+ /// message to stdout on accident). This isn't perfect however as it only
+ /// happens if the parsing throws, you can still hang indefinitely if the
+ /// [MessageGrouper] doesn't find what it thinks is the end of a proto
+ /// message.
+ @override
+ Future<WorkResponse> readResponse() async {
+ var buffer = await _messageGrouper.next;
+ if (buffer == null) {
+ return WorkResponse(
+ exitCode: EXIT_CODE_BROKEN_PIPE,
+ output: 'Connection to worker closed',
+ );
+ }
+
+ WorkResponse response;
+ try {
+ response = WorkResponse.fromBuffer(buffer);
+ } catch (_) {
+ try {
+ // Try parsing the message as a string and set that as the output.
+ var output = utf8.decode(buffer);
+ var response = WorkResponse(
+ exitCode: EXIT_CODE_ERROR,
+ output: 'Worker sent an invalid response:\n$output',
+ );
+ return response;
+ } catch (_) {
+ // Fall back to original exception and rethrow if we fail to parse as
+ // a string.
+ }
+ rethrow;
+ }
+ return response;
+ }
+
+ @override
+ void writeRequest(WorkRequest request) {
+ _outputStream.add(protoToDelimitedBuffer(request));
+ }
+
+ @override
+ Future cancel() async {
+ await _outputStream.close();
+ await _messageGrouper.cancel();
+ }
+}
+
+/// [DriverConnection] that works with an isolate via a [SendPort].
+class IsolateDriverConnection implements DriverConnection {
+ final StreamIterator _receivePortIterator;
+ final SendPort _sendPort;
+
+ IsolateDriverConnection._(this._receivePortIterator, this._sendPort);
+
+ /// Creates a driver connection for a worker in an isolate. Provide the
+ /// [receivePort] attached to the [SendPort] that the isolate was created
+ /// with.
+ static Future<IsolateDriverConnection> create(ReceivePort receivePort) async {
+ var receivePortIterator = StreamIterator(receivePort);
+ await receivePortIterator.moveNext();
+ var sendPort = receivePortIterator.current as SendPort;
+ return IsolateDriverConnection._(receivePortIterator, sendPort);
+ }
+
+ @override
+ Future<WorkResponse> readResponse() async {
+ if (!await _receivePortIterator.moveNext()) {
+ return WorkResponse(
+ exitCode: EXIT_CODE_BROKEN_PIPE,
+ output: 'Connection to worker closed.',
+ );
+ }
+ return WorkResponse.fromBuffer(_receivePortIterator.current as List<int>);
+ }
+
+ @override
+ void writeRequest(WorkRequest request) {
+ _sendPort.send(request.writeToBuffer());
+ }
+
+ @override
+ Future cancel() async {}
+}
diff --git a/pkgs/bazel_worker/lib/src/message_grouper.dart b/pkgs/bazel_worker/lib/src/message_grouper.dart
new file mode 100644
index 0000000..31aa204
--- /dev/null
+++ b/pkgs/bazel_worker/lib/src/message_grouper.dart
@@ -0,0 +1,16 @@
+// 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.
+
+import 'async_message_grouper.dart';
+import 'sync_message_grouper.dart';
+
+/// Interface for a [MessageGrouper], which groups bytes in delimited proto
+/// format into the bytes for each message.
+///
+/// This interface should not generally be implemented directly, instead use
+/// the [SyncMessageGrouper] or [AsyncMessageGrouper] implementations.
+abstract class MessageGrouper {
+ /// Returns either a [List<int>] or a [Future<List<int>>].
+ dynamic get next;
+}
diff --git a/pkgs/bazel_worker/lib/src/message_grouper_state.dart b/pkgs/bazel_worker/lib/src/message_grouper_state.dart
new file mode 100644
index 0000000..2656835
--- /dev/null
+++ b/pkgs/bazel_worker/lib/src/message_grouper_state.dart
@@ -0,0 +1,123 @@
+// 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.
+
+import 'dart:typed_data';
+
+import 'package:protobuf/protobuf.dart';
+
+import 'message_grouper.dart';
+
+/// State held by the [MessageGrouper] while waiting for additional data to
+/// arrive.
+class MessageGrouperState {
+ /// Reads the initial length.
+ var _lengthReader = _LengthReader();
+
+ /// Reads messages from a stream of bytes.
+ _MessageReader? _messageReader;
+
+ /// Handle one byte at a time.
+ ///
+ /// Returns a [List<int>] of message bytes if [byte] was the last byte in a
+ /// message, otherwise returns `null`.
+ List<int>? handleInput(int byte) {
+ if (!_lengthReader.done) {
+ _lengthReader.readByte(byte);
+ if (_lengthReader.done) {
+ _messageReader = _MessageReader(_lengthReader.length);
+ }
+ } else {
+ assert(_messageReader != null);
+ _messageReader!.readByte(byte);
+ }
+
+ if (_lengthReader.done && _messageReader!.done) {
+ var message = _messageReader!.message;
+ reset();
+ return message;
+ }
+
+ return null;
+ }
+
+ /// Reset the state so that we are ready to receive the next message.
+ void reset() {
+ _lengthReader = _LengthReader();
+ _messageReader = null;
+ }
+}
+
+/// Reads a length one byte at a time.
+///
+/// The base-128 encoding is in little-endian order, with the high bit set on
+/// all bytes but the last. This was chosen since it's the same as the
+/// base-128 encoding used by protobufs, so it allows a modest amount of code
+/// reuse at the other end of the protocol.
+class _LengthReader {
+ /// Whether or not we are done reading the length.
+ bool get done => _done;
+ bool _done = false;
+
+ /// If [_done] is `true`, the decoded value of the length bytes received so
+ /// far (if any), otherwise unitialized.
+ late int _length;
+
+ /// The length read in. You are only allowed to read this if [_done] is
+ /// `true`.
+ int get length {
+ assert(_done);
+ return _length;
+ }
+
+ final List<int> _buffer = <int>[];
+
+ /// Read a single byte into [_length].
+ void readByte(int byte) {
+ assert(!_done);
+ _buffer.add(byte);
+
+ // Check for the last byte in the length, and then read it.
+ if ((byte & 0x80) == 0) {
+ _done = true;
+ var reader = CodedBufferReader(_buffer);
+ _length = reader.readInt32();
+ }
+ }
+}
+
+/// Reads some number of bytes from a stream, one byte at a time.
+class _MessageReader {
+ /// Whether or not we are done reading bytes from the stream.
+ bool get done => _done;
+ bool _done;
+
+ /// The total length of the message to be read.
+ final int _length;
+
+ /// A [Uint8List] which holds the message data. You are only allowed to read
+ /// this if [_done] is `true`.
+ Uint8List get message {
+ assert(_done);
+ return _message;
+ }
+
+ final Uint8List _message;
+
+ /// If [_done] is `false`, the number of message bytes that have been received
+ /// so far. Otherwise zero.
+ int _numMessageBytesReceived = 0;
+
+ _MessageReader(int length)
+ : _message = Uint8List(length),
+ _length = length,
+ _done = length == 0;
+
+ /// Reads [byte] into [_message].
+ void readByte(int byte) {
+ assert(!done);
+
+ _message[_numMessageBytesReceived++] = byte;
+ if (_numMessageBytesReceived == _length) _done = true;
+ }
+}
diff --git a/pkgs/bazel_worker/lib/src/sync_message_grouper.dart b/pkgs/bazel_worker/lib/src/sync_message_grouper.dart
new file mode 100644
index 0000000..b8f7b36
--- /dev/null
+++ b/pkgs/bazel_worker/lib/src/sync_message_grouper.dart
@@ -0,0 +1,37 @@
+// 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.
+
+import 'dart:io';
+
+import 'message_grouper.dart';
+import 'message_grouper_state.dart';
+
+/// Groups bytes in delimited proto format into the bytes for each message.
+class SyncMessageGrouper implements MessageGrouper {
+ final _state = MessageGrouperState();
+ final Stdin _stdin;
+
+ SyncMessageGrouper(this._stdin);
+
+ /// Blocks until the next full message is received, and then returns it.
+ ///
+ /// Returns null at end of file.
+ @override
+ List<int>? get next {
+ try {
+ List<int>? message;
+ while (message == null) {
+ var nextByte = _stdin.readByteSync();
+ if (nextByte == -1) return null;
+ message = _state.handleInput(nextByte);
+ }
+ return message;
+ } catch (e) {
+ // It appears we sometimes get an exception instead of -1 as expected when
+ // stdin closes, this handles that in the same way (returning a null
+ // message)
+ return null;
+ }
+ }
+}
diff --git a/pkgs/bazel_worker/lib/src/utils.dart b/pkgs/bazel_worker/lib/src/utils.dart
new file mode 100644
index 0000000..f67bbac
--- /dev/null
+++ b/pkgs/bazel_worker/lib/src/utils.dart
@@ -0,0 +1,24 @@
+// 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.
+
+import 'dart:typed_data';
+
+import 'package:protobuf/protobuf.dart';
+
+List<int> protoToDelimitedBuffer(GeneratedMessage message) {
+ var messageBuffer = CodedBufferWriter();
+ message.writeToCodedBufferWriter(messageBuffer);
+
+ var delimiterBuffer = CodedBufferWriter();
+ delimiterBuffer.writeInt32NoTag(messageBuffer.lengthInBytes);
+
+ var result = Uint8List(
+ messageBuffer.lengthInBytes + delimiterBuffer.lengthInBytes,
+ );
+
+ delimiterBuffer.writeTo(result);
+ messageBuffer.writeTo(result, delimiterBuffer.lengthInBytes);
+
+ return result;
+}
diff --git a/pkgs/bazel_worker/lib/src/worker/async_worker_loop.dart b/pkgs/bazel_worker/lib/src/worker/async_worker_loop.dart
new file mode 100644
index 0000000..a95d09a
--- /dev/null
+++ b/pkgs/bazel_worker/lib/src/worker/async_worker_loop.dart
@@ -0,0 +1,54 @@
+// 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.
+
+import 'dart:async';
+
+import '../constants.dart';
+import '../worker_protocol.pb.dart';
+import 'worker_connection.dart';
+import 'worker_loop.dart';
+
+/// Persistent Bazel worker loop.
+///
+/// Extend this class and implement the `performRequest` method.
+abstract class AsyncWorkerLoop implements WorkerLoop {
+ final AsyncWorkerConnection connection;
+
+ AsyncWorkerLoop({AsyncWorkerConnection? connection})
+ : connection = connection ?? StdAsyncWorkerConnection();
+
+ /// Perform a single [WorkRequest], and return a [WorkResponse].
+ @override
+ Future<WorkResponse> performRequest(WorkRequest request);
+
+ /// Run the worker loop. The returned [Future] doesn't complete until
+ /// [connection#readRequest] returns `null`.
+ @override
+ Future run() async {
+ while (true) {
+ late WorkResponse response;
+ try {
+ var request = await connection.readRequest();
+ if (request == null) break;
+ var printMessages = StringBuffer();
+ response = await runZoned(
+ () => performRequest(request),
+ zoneSpecification: ZoneSpecification(
+ print: (self, parent, zone, message) {
+ printMessages.writeln();
+ printMessages.write(message);
+ },
+ ),
+ );
+ if (printMessages.isNotEmpty) {
+ response.output = '${response.output}$printMessages';
+ }
+ } catch (e, s) {
+ response = WorkResponse(exitCode: EXIT_CODE_ERROR, output: '$e\n$s');
+ }
+
+ connection.writeResponse(response);
+ }
+ }
+}
diff --git a/pkgs/bazel_worker/lib/src/worker/sync_worker_loop.dart b/pkgs/bazel_worker/lib/src/worker/sync_worker_loop.dart
new file mode 100644
index 0000000..51da684
--- /dev/null
+++ b/pkgs/bazel_worker/lib/src/worker/sync_worker_loop.dart
@@ -0,0 +1,52 @@
+// 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.
+import 'dart:async';
+
+import '../constants.dart';
+import '../worker_protocol.pb.dart';
+import 'worker_connection.dart';
+import 'worker_loop.dart';
+
+/// Persistent Bazel worker loop.
+///
+/// Extend this class and implement the `performRequest` method.
+abstract class SyncWorkerLoop implements WorkerLoop {
+ final SyncWorkerConnection connection;
+
+ SyncWorkerLoop({SyncWorkerConnection? connection})
+ : connection = connection ?? StdSyncWorkerConnection();
+
+ /// Perform a single [WorkRequest], and return a [WorkResponse].
+ @override
+ WorkResponse performRequest(WorkRequest request);
+
+ /// Run the worker loop. Blocks until [connection#readRequest] returns `null`.
+ @override
+ void run() {
+ while (true) {
+ late WorkResponse response;
+ try {
+ var request = connection.readRequest();
+ if (request == null) break;
+ var printMessages = StringBuffer();
+ response = runZoned(
+ () => performRequest(request),
+ zoneSpecification: ZoneSpecification(
+ print: (self, parent, zone, message) {
+ printMessages.writeln();
+ printMessages.write(message);
+ },
+ ),
+ );
+ if (printMessages.isNotEmpty) {
+ response.output = '${response.output}$printMessages';
+ }
+ } catch (e, s) {
+ response = WorkResponse(exitCode: EXIT_CODE_ERROR, output: '$e\n$s');
+ }
+
+ connection.writeResponse(response);
+ }
+ }
+}
diff --git a/pkgs/bazel_worker/lib/src/worker/worker_connection.dart b/pkgs/bazel_worker/lib/src/worker/worker_connection.dart
new file mode 100644
index 0000000..fd5508e
--- /dev/null
+++ b/pkgs/bazel_worker/lib/src/worker/worker_connection.dart
@@ -0,0 +1,128 @@
+// 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.
+
+import 'dart:async';
+import 'dart:io';
+import 'dart:isolate';
+import 'dart:typed_data';
+
+import '../async_message_grouper.dart';
+import '../sync_message_grouper.dart';
+import '../utils.dart';
+import '../worker_protocol.pb.dart';
+
+/// A connection from a worker to a driver (driver could be bazel, a dart
+/// program using `BazelWorkerDriver`, or any other process that speaks the
+/// protocol).
+abstract class WorkerConnection {
+ /// Reads a [WorkRequest] or returns `null` if there are none left.
+ ///
+ /// See [AsyncWorkerConnection] and [SyncWorkerConnection] for more narrow
+ /// interfaces.
+ FutureOr<WorkRequest?> readRequest();
+
+ void writeResponse(WorkResponse response);
+}
+
+abstract class AsyncWorkerConnection implements WorkerConnection {
+ /// Creates a [StdAsyncWorkerConnection] with the specified [inputStream]
+ /// and [outputStream], unless [sendPort] is specified, in which case
+ /// creates a [SendPortAsyncWorkerConnection].
+ factory AsyncWorkerConnection({
+ Stream<List<int>>? inputStream,
+ StreamSink<List<int>>? outputStream,
+ SendPort? sendPort,
+ }) =>
+ sendPort == null
+ ? StdAsyncWorkerConnection(
+ inputStream: inputStream,
+ outputStream: outputStream,
+ )
+ : SendPortAsyncWorkerConnection(sendPort);
+
+ @override
+ Future<WorkRequest?> readRequest();
+}
+
+abstract class SyncWorkerConnection implements WorkerConnection {
+ @override
+ WorkRequest? readRequest();
+}
+
+/// Default implementation of [AsyncWorkerConnection] that works with [Stdin]
+/// and [Stdout].
+class StdAsyncWorkerConnection implements AsyncWorkerConnection {
+ final AsyncMessageGrouper _messageGrouper;
+ final StreamSink<List<int>> _outputStream;
+
+ StdAsyncWorkerConnection({
+ Stream<List<int>>? inputStream,
+ StreamSink<List<int>>? outputStream,
+ }) : _messageGrouper = AsyncMessageGrouper(inputStream ?? stdin),
+ _outputStream = outputStream ?? stdout;
+
+ @override
+ Future<WorkRequest?> readRequest() async {
+ var buffer = await _messageGrouper.next;
+ if (buffer == null) return null;
+
+ return WorkRequest.fromBuffer(buffer);
+ }
+
+ @override
+ void writeResponse(WorkResponse response) {
+ _outputStream.add(protoToDelimitedBuffer(response));
+ }
+}
+
+/// Implementation of [AsyncWorkerConnection] for running in an isolate.
+class SendPortAsyncWorkerConnection implements AsyncWorkerConnection {
+ final ReceivePort receivePort;
+ final StreamIterator<Uint8List> receivePortIterator;
+ final SendPort sendPort;
+
+ factory SendPortAsyncWorkerConnection(SendPort sendPort) {
+ var receivePort = ReceivePort();
+ sendPort.send(receivePort.sendPort);
+ return SendPortAsyncWorkerConnection._(receivePort, sendPort);
+ }
+
+ SendPortAsyncWorkerConnection._(this.receivePort, this.sendPort)
+ : receivePortIterator = StreamIterator(receivePort.cast());
+
+ @override
+ Future<WorkRequest?> readRequest() async {
+ if (!await receivePortIterator.moveNext()) return null;
+ return WorkRequest.fromBuffer(receivePortIterator.current);
+ }
+
+ @override
+ void writeResponse(WorkResponse response) {
+ sendPort.send(response.writeToBuffer());
+ }
+}
+
+/// Default implementation of [SyncWorkerConnection] that works with [Stdin] and
+/// [Stdout].
+class StdSyncWorkerConnection implements SyncWorkerConnection {
+ final SyncMessageGrouper _messageGrouper;
+ final Stdout _stdoutStream;
+
+ StdSyncWorkerConnection({Stdin? stdinStream, Stdout? stdoutStream})
+ : _messageGrouper = SyncMessageGrouper(stdinStream ?? stdin),
+ _stdoutStream = stdoutStream ?? stdout;
+
+ @override
+ WorkRequest? readRequest() {
+ var buffer = _messageGrouper.next;
+ if (buffer == null) return null;
+
+ return WorkRequest.fromBuffer(buffer);
+ }
+
+ @override
+ void writeResponse(WorkResponse response) {
+ _stdoutStream.add(protoToDelimitedBuffer(response));
+ }
+}
diff --git a/pkgs/bazel_worker/lib/src/worker/worker_loop.dart b/pkgs/bazel_worker/lib/src/worker/worker_loop.dart
new file mode 100644
index 0000000..5723fd1
--- /dev/null
+++ b/pkgs/bazel_worker/lib/src/worker/worker_loop.dart
@@ -0,0 +1,20 @@
+// 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.
+
+import '../worker_protocol.pb.dart';
+import 'async_worker_loop.dart';
+import 'sync_worker_loop.dart';
+
+/// Interface for a [WorkerLoop].
+///
+/// This interface should not generally be implemented directly, instead use
+/// the [SyncWorkerLoop] or [AsyncWorkerLoop] implementations.
+abstract class WorkerLoop {
+ /// Perform a single [WorkRequest], and return either a [WorkResponse] or
+ /// a [Future<WorkResponse>].
+ dynamic performRequest(WorkRequest request);
+
+ /// Run the worker loop. Should return either a [Future] or `null`.
+ dynamic run();
+}
diff --git a/pkgs/bazel_worker/lib/src/worker_protocol.pb.dart b/pkgs/bazel_worker/lib/src/worker_protocol.pb.dart
new file mode 100644
index 0000000..8b7a7a9
--- /dev/null
+++ b/pkgs/bazel_worker/lib/src/worker_protocol.pb.dart
@@ -0,0 +1,384 @@
+//
+// Generated code. Do not modify.
+// source: worker_protocol.proto
+//
+// @dart = 2.12
+
+// ignore_for_file: annotate_overrides, camel_case_types, comment_references
+// ignore_for_file: constant_identifier_names, library_prefixes
+// ignore_for_file: non_constant_identifier_names, prefer_final_fields
+// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
+
+import 'dart:core' as $core;
+
+import 'package:protobuf/protobuf.dart' as $pb;
+
+/// An input file.
+class Input extends $pb.GeneratedMessage {
+ factory Input({
+ $core.String? path,
+ $core.List<$core.int>? digest,
+ }) {
+ final $result = create();
+ if (path != null) {
+ $result.path = path;
+ }
+ if (digest != null) {
+ $result.digest = digest;
+ }
+ return $result;
+ }
+ Input._() : super();
+ factory Input.fromBuffer($core.List<$core.int> i,
+ [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+ create()..mergeFromBuffer(i, r);
+ factory Input.fromJson($core.String i,
+ [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+ create()..mergeFromJson(i, r);
+
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(
+ _omitMessageNames ? '' : 'Input',
+ package: const $pb.PackageName(_omitMessageNames ? '' : 'blaze.worker'),
+ createEmptyInstance: create)
+ ..aOS(1, _omitFieldNames ? '' : 'path')
+ ..a<$core.List<$core.int>>(
+ 2, _omitFieldNames ? '' : 'digest', $pb.PbFieldType.OY)
+ ..hasRequiredFields = false;
+
+ @$core.Deprecated('Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+ 'Will be removed in next major version')
+ Input clone() => Input()..mergeFromMessage(this);
+ @$core.Deprecated('Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+ 'Will be removed in next major version')
+ Input copyWith(void Function(Input) updates) =>
+ super.copyWith((message) => updates(message as Input)) as Input;
+
+ $pb.BuilderInfo get info_ => _i;
+
+ @$core.pragma('dart2js:noInline')
+ static Input create() => Input._();
+ Input createEmptyInstance() => create();
+ static $pb.PbList<Input> createRepeated() => $pb.PbList<Input>();
+ @$core.pragma('dart2js:noInline')
+ static Input getDefault() =>
+ _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Input>(create);
+ static Input? _defaultInstance;
+
+ /// The path in the file system where to read this input artifact from. This is
+ /// either a path relative to the execution root (the worker process is
+ /// launched with the working directory set to the execution root), or an
+ /// absolute path.
+ @$pb.TagNumber(1)
+ $core.String get path => $_getSZ(0);
+ @$pb.TagNumber(1)
+ set path($core.String v) {
+ $_setString(0, v);
+ }
+
+ @$pb.TagNumber(1)
+ $core.bool hasPath() => $_has(0);
+ @$pb.TagNumber(1)
+ void clearPath() => clearField(1);
+
+ /// A hash-value of the contents. The format of the contents is unspecified and
+ /// the digest should be treated as an opaque token. This can be empty in some
+ /// cases.
+ @$pb.TagNumber(2)
+ $core.List<$core.int> get digest => $_getN(1);
+ @$pb.TagNumber(2)
+ set digest($core.List<$core.int> v) {
+ $_setBytes(1, v);
+ }
+
+ @$pb.TagNumber(2)
+ $core.bool hasDigest() => $_has(1);
+ @$pb.TagNumber(2)
+ void clearDigest() => clearField(2);
+}
+
+/// This represents a single work unit that Blaze sends to the worker.
+class WorkRequest extends $pb.GeneratedMessage {
+ factory WorkRequest({
+ $core.Iterable<$core.String>? arguments,
+ $core.Iterable<Input>? inputs,
+ $core.int? requestId,
+ $core.bool? cancel,
+ $core.int? verbosity,
+ $core.String? sandboxDir,
+ }) {
+ final $result = create();
+ if (arguments != null) {
+ $result.arguments.addAll(arguments);
+ }
+ if (inputs != null) {
+ $result.inputs.addAll(inputs);
+ }
+ if (requestId != null) {
+ $result.requestId = requestId;
+ }
+ if (cancel != null) {
+ $result.cancel = cancel;
+ }
+ if (verbosity != null) {
+ $result.verbosity = verbosity;
+ }
+ if (sandboxDir != null) {
+ $result.sandboxDir = sandboxDir;
+ }
+ return $result;
+ }
+ WorkRequest._() : super();
+ factory WorkRequest.fromBuffer($core.List<$core.int> i,
+ [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+ create()..mergeFromBuffer(i, r);
+ factory WorkRequest.fromJson($core.String i,
+ [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+ create()..mergeFromJson(i, r);
+
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(
+ _omitMessageNames ? '' : 'WorkRequest',
+ package: const $pb.PackageName(_omitMessageNames ? '' : 'blaze.worker'),
+ createEmptyInstance: create)
+ ..pPS(1, _omitFieldNames ? '' : 'arguments')
+ ..pc<Input>(2, _omitFieldNames ? '' : 'inputs', $pb.PbFieldType.PM,
+ subBuilder: Input.create)
+ ..a<$core.int>(3, _omitFieldNames ? '' : 'requestId', $pb.PbFieldType.O3)
+ ..aOB(4, _omitFieldNames ? '' : 'cancel')
+ ..a<$core.int>(5, _omitFieldNames ? '' : 'verbosity', $pb.PbFieldType.O3)
+ ..aOS(6, _omitFieldNames ? '' : 'sandboxDir')
+ ..hasRequiredFields = false;
+
+ @$core.Deprecated('Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+ 'Will be removed in next major version')
+ WorkRequest clone() => WorkRequest()..mergeFromMessage(this);
+ @$core.Deprecated('Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+ 'Will be removed in next major version')
+ WorkRequest copyWith(void Function(WorkRequest) updates) =>
+ super.copyWith((message) => updates(message as WorkRequest))
+ as WorkRequest;
+
+ $pb.BuilderInfo get info_ => _i;
+
+ @$core.pragma('dart2js:noInline')
+ static WorkRequest create() => WorkRequest._();
+ WorkRequest createEmptyInstance() => create();
+ static $pb.PbList<WorkRequest> createRepeated() => $pb.PbList<WorkRequest>();
+ @$core.pragma('dart2js:noInline')
+ static WorkRequest getDefault() => _defaultInstance ??=
+ $pb.GeneratedMessage.$_defaultFor<WorkRequest>(create);
+ static WorkRequest? _defaultInstance;
+
+ @$pb.TagNumber(1)
+ $core.List<$core.String> get arguments => $_getList(0);
+
+ /// The inputs that the worker is allowed to read during execution of this
+ /// request.
+ @$pb.TagNumber(2)
+ $core.List<Input> get inputs => $_getList(1);
+
+ /// Each WorkRequest must have either a unique
+ /// request_id or request_id = 0. If request_id is 0, this WorkRequest must be
+ /// processed alone (singleplex), otherwise the worker may process multiple
+ /// WorkRequests in parallel (multiplexing). As an exception to the above, if
+ /// the cancel field is true, the request_id must be the same as a previously
+ /// sent WorkRequest. The request_id must be attached unchanged to the
+ /// corresponding WorkResponse. Only one singleplex request may be sent to a
+ /// worker at a time.
+ @$pb.TagNumber(3)
+ $core.int get requestId => $_getIZ(2);
+ @$pb.TagNumber(3)
+ set requestId($core.int v) {
+ $_setSignedInt32(2, v);
+ }
+
+ @$pb.TagNumber(3)
+ $core.bool hasRequestId() => $_has(2);
+ @$pb.TagNumber(3)
+ void clearRequestId() => clearField(3);
+
+ /// EXPERIMENTAL: When true, this is a cancel request, indicating that a
+ /// previously sent WorkRequest with the same request_id should be cancelled.
+ /// The arguments and inputs fields must be empty and should be ignored.
+ @$pb.TagNumber(4)
+ $core.bool get cancel => $_getBF(3);
+ @$pb.TagNumber(4)
+ set cancel($core.bool v) {
+ $_setBool(3, v);
+ }
+
+ @$pb.TagNumber(4)
+ $core.bool hasCancel() => $_has(3);
+ @$pb.TagNumber(4)
+ void clearCancel() => clearField(4);
+
+ /// Values greater than 0 indicate that the worker may output extra debug
+ /// information to stderr (which will go into the worker log). Setting the
+ /// --worker_verbose flag for Bazel makes this flag default to 10.
+ @$pb.TagNumber(5)
+ $core.int get verbosity => $_getIZ(4);
+ @$pb.TagNumber(5)
+ set verbosity($core.int v) {
+ $_setSignedInt32(4, v);
+ }
+
+ @$pb.TagNumber(5)
+ $core.bool hasVerbosity() => $_has(4);
+ @$pb.TagNumber(5)
+ void clearVerbosity() => clearField(5);
+
+ /// The relative directory inside the workers working directory where the
+ /// inputs and outputs are placed, for sandboxing purposes. For singleplex
+ /// workers, this is unset, as they can use their working directory as sandbox.
+ /// For multiplex workers, this will be set when the
+ /// --experimental_worker_multiplex_sandbox flag is set _and_ the execution
+ /// requirements for the worker includes 'supports-multiplex-sandbox'.
+ /// The paths in `inputs` will not contain this prefix, but the actual files
+ /// will be placed/must be written relative to this directory. The worker
+ /// implementation is responsible for resolving the file paths.
+ @$pb.TagNumber(6)
+ $core.String get sandboxDir => $_getSZ(5);
+ @$pb.TagNumber(6)
+ set sandboxDir($core.String v) {
+ $_setString(5, v);
+ }
+
+ @$pb.TagNumber(6)
+ $core.bool hasSandboxDir() => $_has(5);
+ @$pb.TagNumber(6)
+ void clearSandboxDir() => clearField(6);
+}
+
+/// The worker sends this message to Blaze when it finished its work on the
+/// WorkRequest message.
+class WorkResponse extends $pb.GeneratedMessage {
+ factory WorkResponse({
+ $core.int? exitCode,
+ $core.String? output,
+ $core.int? requestId,
+ $core.bool? wasCancelled,
+ }) {
+ final $result = create();
+ if (exitCode != null) {
+ $result.exitCode = exitCode;
+ }
+ if (output != null) {
+ $result.output = output;
+ }
+ if (requestId != null) {
+ $result.requestId = requestId;
+ }
+ if (wasCancelled != null) {
+ $result.wasCancelled = wasCancelled;
+ }
+ return $result;
+ }
+ WorkResponse._() : super();
+ factory WorkResponse.fromBuffer($core.List<$core.int> i,
+ [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+ create()..mergeFromBuffer(i, r);
+ factory WorkResponse.fromJson($core.String i,
+ [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+ create()..mergeFromJson(i, r);
+
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(
+ _omitMessageNames ? '' : 'WorkResponse',
+ package: const $pb.PackageName(_omitMessageNames ? '' : 'blaze.worker'),
+ createEmptyInstance: create)
+ ..a<$core.int>(1, _omitFieldNames ? '' : 'exitCode', $pb.PbFieldType.O3)
+ ..aOS(2, _omitFieldNames ? '' : 'output')
+ ..a<$core.int>(3, _omitFieldNames ? '' : 'requestId', $pb.PbFieldType.O3)
+ ..aOB(4, _omitFieldNames ? '' : 'wasCancelled')
+ ..hasRequiredFields = false;
+
+ @$core.Deprecated('Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+ 'Will be removed in next major version')
+ WorkResponse clone() => WorkResponse()..mergeFromMessage(this);
+ @$core.Deprecated('Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+ 'Will be removed in next major version')
+ WorkResponse copyWith(void Function(WorkResponse) updates) =>
+ super.copyWith((message) => updates(message as WorkResponse))
+ as WorkResponse;
+
+ $pb.BuilderInfo get info_ => _i;
+
+ @$core.pragma('dart2js:noInline')
+ static WorkResponse create() => WorkResponse._();
+ WorkResponse createEmptyInstance() => create();
+ static $pb.PbList<WorkResponse> createRepeated() =>
+ $pb.PbList<WorkResponse>();
+ @$core.pragma('dart2js:noInline')
+ static WorkResponse getDefault() => _defaultInstance ??=
+ $pb.GeneratedMessage.$_defaultFor<WorkResponse>(create);
+ static WorkResponse? _defaultInstance;
+
+ @$pb.TagNumber(1)
+ $core.int get exitCode => $_getIZ(0);
+ @$pb.TagNumber(1)
+ set exitCode($core.int v) {
+ $_setSignedInt32(0, v);
+ }
+
+ @$pb.TagNumber(1)
+ $core.bool hasExitCode() => $_has(0);
+ @$pb.TagNumber(1)
+ void clearExitCode() => clearField(1);
+
+ /// This is printed to the user after the WorkResponse has been received and is
+ /// supposed to contain compiler warnings / errors etc. - thus we'll use a
+ /// string type here, which gives us UTF-8 encoding.
+ @$pb.TagNumber(2)
+ $core.String get output => $_getSZ(1);
+ @$pb.TagNumber(2)
+ set output($core.String v) {
+ $_setString(1, v);
+ }
+
+ @$pb.TagNumber(2)
+ $core.bool hasOutput() => $_has(1);
+ @$pb.TagNumber(2)
+ void clearOutput() => clearField(2);
+
+ /// This field must be set to the same request_id as the WorkRequest it is a
+ /// response to. Since worker processes which support multiplex worker will
+ /// handle multiple WorkRequests in parallel, this ID will be used to
+ /// determined which WorkerProxy does this WorkResponse belong to.
+ @$pb.TagNumber(3)
+ $core.int get requestId => $_getIZ(2);
+ @$pb.TagNumber(3)
+ set requestId($core.int v) {
+ $_setSignedInt32(2, v);
+ }
+
+ @$pb.TagNumber(3)
+ $core.bool hasRequestId() => $_has(2);
+ @$pb.TagNumber(3)
+ void clearRequestId() => clearField(3);
+
+ /// EXPERIMENTAL When true, indicates that this response was sent due to
+ /// receiving a cancel request. The exit_code and output fields should be empty
+ /// and will be ignored. Exactly one WorkResponse must be sent for each
+ /// non-cancelling WorkRequest received by the worker, but if the worker
+ /// received a cancel request, it doesn't matter if it replies with a regular
+ /// WorkResponse or with one where was_cancelled = true.
+ @$pb.TagNumber(4)
+ $core.bool get wasCancelled => $_getBF(3);
+ @$pb.TagNumber(4)
+ set wasCancelled($core.bool v) {
+ $_setBool(3, v);
+ }
+
+ @$pb.TagNumber(4)
+ $core.bool hasWasCancelled() => $_has(3);
+ @$pb.TagNumber(4)
+ void clearWasCancelled() => clearField(4);
+}
+
+const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
+const _omitMessageNames =
+ $core.bool.fromEnvironment('protobuf.omit_message_names');
diff --git a/pkgs/bazel_worker/lib/testing.dart b/pkgs/bazel_worker/lib/testing.dart
new file mode 100644
index 0000000..7aefabb
--- /dev/null
+++ b/pkgs/bazel_worker/lib/testing.dart
@@ -0,0 +1,212 @@
+// 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.
+
+import 'dart:async';
+import 'dart:collection';
+import 'dart:io';
+import 'dart:typed_data';
+
+import 'bazel_worker.dart';
+
+export 'src/async_message_grouper.dart';
+export 'src/sync_message_grouper.dart';
+export 'src/utils.dart' show protoToDelimitedBuffer;
+
+/// Interface for a mock [Stdin] object that allows you to add bytes manually.
+abstract class TestStdin implements Stdin {
+ void addInputBytes(List<int> bytes);
+
+ void close();
+}
+
+/// A [Stdin] mock object which only implements `readByteSync`.
+class TestStdinSync implements TestStdin {
+ /// Pending bytes to be delivered synchronously.
+ final Queue<int> pendingBytes = Queue<int>();
+
+ /// Adds all the [bytes] to this stream.
+ @override
+ void addInputBytes(List<int> bytes) {
+ pendingBytes.addAll(bytes);
+ }
+
+ /// Add a -1 to signal EOF.
+ @override
+ void close() {
+ pendingBytes.add(-1);
+ }
+
+ @override
+ int readByteSync() {
+ return pendingBytes.removeFirst();
+ }
+
+ @override
+ bool get isBroadcast => false;
+
+ @override
+ void noSuchMethod(Invocation invocation) {
+ throw StateError('Unexpected invocation ${invocation.memberName}.');
+ }
+}
+
+/// A mock [Stdin] object which only implements `listen`.
+///
+/// Note: You must call [close] in order for the loop to exit properly.
+class TestStdinAsync implements TestStdin {
+ /// Controls the stream for async delivery of bytes.
+ final StreamController<Uint8List> _controller = StreamController();
+ StreamController<Uint8List> get controller => _controller;
+
+ /// Adds all the [bytes] to this stream.
+ @override
+ void addInputBytes(List<int> bytes) {
+ _controller.add(Uint8List.fromList(bytes));
+ }
+
+ /// Closes this stream. This is necessary for the [AsyncWorkerLoop] to exit.
+ @override
+ void close() {
+ _controller.close();
+ }
+
+ @override
+ StreamSubscription<Uint8List> listen(
+ void Function(Uint8List bytes)? onData, {
+ Function? onError,
+ void Function()? onDone,
+ bool? cancelOnError,
+ }) {
+ return _controller.stream.listen(
+ onData,
+ onError: onError,
+ onDone: onDone,
+ cancelOnError: cancelOnError,
+ );
+ }
+
+ @override
+ bool get isBroadcast => false;
+
+ @override
+ void noSuchMethod(Invocation invocation) {
+ throw StateError('Unexpected invocation ${invocation.memberName}.');
+ }
+}
+
+/// A [Stdout] mock object.
+class TestStdoutStream implements Stdout {
+ final List<List<int>> writes = <List<int>>[];
+
+ @override
+ void add(List<int> bytes) {
+ writes.add(bytes);
+ }
+
+ @override
+ void noSuchMethod(Invocation invocation) {
+ throw StateError('Unexpected invocation ${invocation.memberName}.');
+ }
+}
+
+/// Interface for a [TestWorkerConnection] which records its responses
+abstract class TestWorkerConnection implements WorkerConnection {
+ List<WorkResponse> get responses;
+}
+
+/// Interface for a [TestWorkerLoop] which allows you to enqueue responses.
+abstract class TestWorkerLoop implements WorkerLoop {
+ void enqueueResponse(WorkResponse response);
+
+ /// If set, this message will be printed during the call to `performRequest`.
+ String? get printMessage;
+}
+
+/// A [StdSyncWorkerConnection] which records its responses.
+class TestSyncWorkerConnection extends StdSyncWorkerConnection
+ implements TestWorkerConnection {
+ @override
+ final List<WorkResponse> responses = <WorkResponse>[];
+
+ TestSyncWorkerConnection(Stdin stdinStream, Stdout stdoutStream)
+ : super(stdinStream: stdinStream, stdoutStream: stdoutStream);
+
+ @override
+ void writeResponse(WorkResponse response) {
+ super.writeResponse(response);
+ responses.add(response);
+ }
+}
+
+/// A [SyncWorkerLoop] for testing.
+class TestSyncWorkerLoop extends SyncWorkerLoop implements TestWorkerLoop {
+ final List<WorkRequest> requests = <WorkRequest>[];
+ final Queue<WorkResponse> _responses = Queue<WorkResponse>();
+
+ @override
+ final String? printMessage;
+
+ TestSyncWorkerLoop(SyncWorkerConnection connection, {this.printMessage})
+ : super(connection: connection);
+
+ @override
+ WorkResponse performRequest(WorkRequest request) {
+ requests.add(request);
+ if (printMessage != null) print(printMessage);
+ return _responses.removeFirst();
+ }
+
+ /// Adds [response] to the queue. These will be returned from
+ /// [performRequest] in the order they are added, otherwise it will throw
+ /// if the queue is empty.
+ @override
+ void enqueueResponse(WorkResponse response) {
+ _responses.addLast(response);
+ }
+}
+
+/// A [StdAsyncWorkerConnection] which records its responses.
+class TestAsyncWorkerConnection extends StdAsyncWorkerConnection
+ implements TestWorkerConnection {
+ @override
+ final List<WorkResponse> responses = <WorkResponse>[];
+
+ TestAsyncWorkerConnection(
+ Stream<List<int>> inputStream,
+ StreamSink<List<int>> outputStream,
+ ) : super(inputStream: inputStream, outputStream: outputStream);
+
+ @override
+ void writeResponse(WorkResponse response) {
+ super.writeResponse(response);
+ responses.add(response);
+ }
+}
+
+/// A [AsyncWorkerLoop] for testing.
+class TestAsyncWorkerLoop extends AsyncWorkerLoop implements TestWorkerLoop {
+ final List<WorkRequest> requests = <WorkRequest>[];
+ final Queue<WorkResponse> _responses = Queue<WorkResponse>();
+
+ @override
+ final String? printMessage;
+
+ TestAsyncWorkerLoop(AsyncWorkerConnection connection, {this.printMessage})
+ : super(connection: connection);
+
+ @override
+ Future<WorkResponse> performRequest(WorkRequest request) async {
+ requests.add(request);
+ if (printMessage != null) print(printMessage);
+ return _responses.removeFirst();
+ }
+
+ /// Adds [response] to the queue. These will be returned from
+ /// [performRequest] in the order they are added, otherwise it will throw
+ /// if the queue is empty.
+ @override
+ void enqueueResponse(WorkResponse response) {
+ _responses.addLast(response);
+ }
+}
diff --git a/pkgs/bazel_worker/pubspec.yaml b/pkgs/bazel_worker/pubspec.yaml
new file mode 100644
index 0000000..561a791
--- /dev/null
+++ b/pkgs/bazel_worker/pubspec.yaml
@@ -0,0 +1,17 @@
+name: bazel_worker
+version: 1.1.3-wip
+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
+
+environment:
+ sdk: ^3.4.0
+
+dependencies:
+ async: ^2.5.0
+ protobuf: ^3.0.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ test: ^1.16.0
diff --git a/pkgs/bazel_worker/test/driver_connection_test.dart b/pkgs/bazel_worker/test/driver_connection_test.dart
new file mode 100644
index 0000000..3bbb56d
--- /dev/null
+++ b/pkgs/bazel_worker/test/driver_connection_test.dart
@@ -0,0 +1,24 @@
+// Copyright (c) 2020, 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:isolate';
+
+import 'package:bazel_worker/src/constants.dart';
+import 'package:bazel_worker/src/driver/driver_connection.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('IsolateDriverConnection', () {
+ test('handles closed port', () async {
+ var isolatePort = ReceivePort();
+ var outsidePort = ReceivePort();
+ isolatePort.sendPort.send(outsidePort.sendPort);
+ var connection = await IsolateDriverConnection.create(isolatePort);
+
+ isolatePort.close();
+
+ expect((await connection.readResponse()).exitCode, EXIT_CODE_BROKEN_PIPE);
+ });
+ });
+}
diff --git a/pkgs/bazel_worker/test/driver_test.dart b/pkgs/bazel_worker/test/driver_test.dart
new file mode 100644
index 0000000..c3db55c
--- /dev/null
+++ b/pkgs/bazel_worker/test/driver_test.dart
@@ -0,0 +1,321 @@
+// Copyright (c) 2017, 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:collection';
+import 'dart:io';
+
+import 'package:bazel_worker/bazel_worker.dart';
+import 'package:bazel_worker/driver.dart';
+import 'package:test/test.dart';
+
+void main() {
+ BazelWorkerDriver? driver;
+ final disconnectedResponse = WorkResponse(
+ exitCode: EXIT_CODE_BROKEN_PIPE,
+ output: 'Connection closed',
+ )..freeze();
+
+ group('basic driver', () {
+ test('can run a single request', () async {
+ await _doRequests(count: 1);
+ await _doRequests(count: 1);
+ });
+
+ test(
+ 'can run multiple batches of requests through multiple workers',
+ () async {
+ var maxWorkers = 4;
+ var maxIdleWorkers = 2;
+ driver = BazelWorkerDriver(
+ MockWorker.spawn,
+ maxWorkers: maxWorkers,
+ maxIdleWorkers: maxIdleWorkers,
+ );
+ for (var i = 0; i < 10; i++) {
+ await _doRequests(driver: driver);
+ expect(MockWorker.liveWorkers.length, maxIdleWorkers);
+ // No workers should be killed while there is ongoing work, but they
+ // should be cleaned up once there isn't any more work to do.
+ expect(
+ MockWorker.deadWorkers.length,
+ (maxWorkers - maxIdleWorkers) * (i + 1),
+ );
+ }
+ },
+ );
+
+ test('can run multiple requests through one worker', () async {
+ var maxWorkers = 1;
+ var maxIdleWorkers = 1;
+ driver = BazelWorkerDriver(
+ MockWorker.spawn,
+ maxWorkers: maxWorkers,
+ maxIdleWorkers: maxIdleWorkers,
+ );
+ for (var i = 0; i < 10; i++) {
+ await _doRequests(driver: driver);
+ expect(MockWorker.liveWorkers.length, 1);
+ expect(MockWorker.deadWorkers.length, 0);
+ }
+ });
+
+ test('can run one request through multiple workers', () async {
+ driver = BazelWorkerDriver(
+ MockWorker.spawn,
+ maxWorkers: 4,
+ maxIdleWorkers: 4,
+ );
+ for (var i = 0; i < 10; i++) {
+ await _doRequests(driver: driver, count: 1);
+ expect(MockWorker.liveWorkers.length, 1);
+ expect(MockWorker.deadWorkers.length, 0);
+ }
+ });
+
+ test('can run with maxIdleWorkers == 0', () async {
+ var maxWorkers = 4;
+ driver = BazelWorkerDriver(
+ MockWorker.spawn,
+ maxWorkers: maxWorkers,
+ maxIdleWorkers: 0,
+ );
+ for (var i = 0; i < 10; i++) {
+ await _doRequests(driver: driver);
+ expect(MockWorker.liveWorkers.length, 0);
+ expect(MockWorker.deadWorkers.length, maxWorkers * (i + 1));
+ }
+ });
+
+ test('trackWork gets invoked when a worker is actually ready', () async {
+ var maxWorkers = 2;
+ driver = BazelWorkerDriver(MockWorker.spawn, maxWorkers: maxWorkers);
+ var tracking = <Future>[];
+ await _doRequests(
+ driver: driver,
+ count: 10,
+ trackWork: (Future response) {
+ // We should never be tracking more than `maxWorkers` jobs at a time.
+ expect(tracking.length, lessThan(maxWorkers));
+ tracking.add(response);
+ response.then((_) => tracking.remove(response));
+ },
+ );
+ });
+
+ group('failing workers', () {
+ /// A driver which spawns [numBadWorkers] failing workers and then good
+ /// ones after that, and which will retry [maxRetries] times.
+ void createDriver({int maxRetries = 2, int numBadWorkers = 2}) {
+ var numSpawned = 0;
+ driver = BazelWorkerDriver(
+ () async => MockWorker(
+ workerLoopFactory: (MockWorker worker) {
+ var connection = StdAsyncWorkerConnection(
+ inputStream: worker._stdinController.stream,
+ outputStream: worker._stdoutController.sink,
+ );
+ if (numSpawned < numBadWorkers) {
+ numSpawned++;
+ return ThrowingMockWorkerLoop(
+ worker,
+ MockWorker.responseQueue,
+ connection,
+ );
+ } else {
+ return MockWorkerLoop(
+ MockWorker.responseQueue,
+ connection: connection,
+ );
+ }
+ },
+ ),
+ maxRetries: maxRetries,
+ );
+ }
+
+ test('should retry up to maxRetries times', () async {
+ createDriver();
+ var expectedResponse = WorkResponse();
+ MockWorker.responseQueue.addAll([
+ disconnectedResponse,
+ disconnectedResponse,
+ expectedResponse,
+ ]);
+ var actualResponse = await driver!.doWork(WorkRequest());
+ // The first 2 null responses are thrown away, and we should get the
+ // third one.
+ expect(actualResponse, expectedResponse);
+
+ expect(MockWorker.deadWorkers.length, 2);
+ expect(MockWorker.liveWorkers.length, 1);
+ });
+
+ test('should fail if it exceeds maxRetries failures', () async {
+ createDriver(maxRetries: 2, numBadWorkers: 3);
+ MockWorker.responseQueue.addAll([
+ disconnectedResponse,
+ disconnectedResponse,
+ WorkResponse(),
+ ]);
+ var actualResponse = await driver!.doWork(WorkRequest());
+ // Should actually get a bad response.
+ expect(actualResponse.exitCode, 15);
+ expect(
+ actualResponse.output,
+ 'Invalid response from worker, this probably means it wrote '
+ 'invalid output or died.',
+ );
+
+ expect(MockWorker.deadWorkers.length, 3);
+ });
+ });
+
+ test('handles spawnWorker failures', () async {
+ driver = BazelWorkerDriver(
+ () async => throw StateError('oh no!'),
+ maxRetries: 0,
+ );
+ expect(driver!.doWork(WorkRequest()), throwsA(isA<StateError>()));
+ });
+
+ tearDown(() async {
+ await driver?.terminateWorkers();
+ expect(MockWorker.liveWorkers, isEmpty);
+ MockWorker.deadWorkers.clear();
+ MockWorker.responseQueue.clear();
+ });
+ });
+}
+
+/// Runs [count] of fake work requests through [driver], and asserts that they
+/// all completed.
+Future _doRequests({
+ BazelWorkerDriver? driver,
+ int count = 100,
+ void Function(Future<WorkResponse?>)? trackWork,
+}) async {
+ // If we create a driver, we need to make sure and terminate it.
+ var terminateDriver = driver == null;
+ driver ??= BazelWorkerDriver(MockWorker.spawn);
+ var requests = List.generate(count, (_) => WorkRequest());
+ var responses = List.generate(count, (_) => WorkResponse());
+ MockWorker.responseQueue.addAll(responses);
+ var actualResponses = await Future.wait(
+ requests.map((request) => driver!.doWork(request, trackWork: trackWork)),
+ );
+ expect(actualResponses, unorderedEquals(responses));
+ if (terminateDriver) await driver.terminateWorkers();
+}
+
+/// A mock worker loop that returns work responses from the provided list.
+///
+/// Throws if it runs out of responses.
+class MockWorkerLoop extends AsyncWorkerLoop {
+ final Queue<WorkResponse> _responseQueue;
+
+ MockWorkerLoop(this._responseQueue, {super.connection});
+
+ @override
+ Future<WorkResponse> performRequest(WorkRequest request) async {
+ print('Performing request $request');
+ return _responseQueue.removeFirst();
+ }
+}
+
+/// A mock worker loop with a custom `run` function that throws.
+class ThrowingMockWorkerLoop extends MockWorkerLoop {
+ final MockWorker _mockWorker;
+
+ ThrowingMockWorkerLoop(
+ this._mockWorker,
+ Queue<WorkResponse> responseQueue,
+ AsyncWorkerConnection connection,
+ ) : super(responseQueue, connection: connection);
+
+ /// Run the worker loop. The returned [Future] doesn't complete until
+ /// [connection#readRequest] returns `null`.
+ @override
+ Future run() async {
+ while (true) {
+ var request = await connection.readRequest();
+ if (request == null) break;
+ await performRequest(request);
+ _mockWorker.kill();
+ }
+ }
+}
+
+/// A mock worker process.
+///
+/// Items in [responseQueue] will be returned in order based on requests.
+///
+/// If there are no items left in [responseQueue] then it will throw.
+class MockWorker implements Process {
+ /// Spawns a new [MockWorker].
+ static Future<MockWorker> spawn() async => MockWorker();
+
+ /// Static queue of pending responses, these are shared by all workers.
+ ///
+ /// If this is empty and a request is received then it will throw.
+ static final responseQueue = Queue<WorkResponse>();
+
+ /// Static list of all live workers.
+ static final liveWorkers = <MockWorker>[];
+
+ /// Static list of all the dead workers.
+ static final deadWorkers = <MockWorker>[];
+
+ /// Standard constructor, creates a [WorkerLoop] from [workerLoopFactory] or
+ /// a [MockWorkerLoop] if no factory is provided.
+ MockWorker({WorkerLoop Function(MockWorker)? workerLoopFactory}) {
+ liveWorkers.add(this);
+ var workerLoop = workerLoopFactory != null
+ ? workerLoopFactory(this)
+ : MockWorkerLoop(
+ responseQueue,
+ connection: StdAsyncWorkerConnection(
+ inputStream: _stdinController.stream,
+ outputStream: _stdoutController.sink,
+ ),
+ );
+ workerLoop.run();
+ }
+
+ @override
+ Future<int> get exitCode => throw UnsupportedError('Not needed.');
+
+ @override
+ Stream<List<int>> get stdout => _stdoutController.stream;
+ final _stdoutController = StreamController<List<int>>();
+
+ @override
+ Stream<List<int>> get stderr => _stderrController.stream;
+ final _stderrController = StreamController<List<int>>();
+
+ @override
+ late final IOSink stdin = IOSink(_stdinController.sink);
+ final _stdinController = StreamController<List<int>>();
+
+ @override
+ int get pid => throw UnsupportedError('Not needed.');
+
+ @override
+ bool kill([
+ ProcessSignal processSignal = ProcessSignal.sigterm,
+ int exitCode = 0,
+ ]) {
+ if (_killed) return false;
+ () async {
+ await _stdoutController.close();
+ await _stderrController.close();
+ await _stdinController.close();
+ }();
+ deadWorkers.add(this);
+ liveWorkers.remove(this);
+ return true;
+ }
+
+ final _killed = false;
+}
diff --git a/pkgs/bazel_worker/test/message_grouper_test.dart b/pkgs/bazel_worker/test/message_grouper_test.dart
new file mode 100644
index 0000000..fd99911
--- /dev/null
+++ b/pkgs/bazel_worker/test/message_grouper_test.dart
@@ -0,0 +1,130 @@
+// 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.
+
+import 'dart:io';
+
+import 'package:bazel_worker/bazel_worker.dart';
+import 'package:bazel_worker/testing.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('AsyncMessageGrouper', () {
+ runTests(TestStdinAsync.new, AsyncMessageGrouper.new);
+ });
+
+ group('SyncMessageGrouper', () {
+ runTests(TestStdinSync.new, SyncMessageGrouper.new);
+ });
+}
+
+void runTests(
+ TestStdin Function() stdinFactory,
+ MessageGrouper Function(Stdin) messageGrouperFactory,
+) {
+ late MessageGrouper messageGrouper;
+
+ late TestStdin stdinStream;
+
+ setUp(() {
+ stdinStream = stdinFactory();
+ messageGrouper = messageGrouperFactory(stdinStream);
+ });
+
+ /// Check that if the message grouper produces the [expectedOutput] in
+ /// response to the corresponding [input].
+ Future check(List<int> input, List<List<int>> expectedOutput) async {
+ stdinStream.addInputBytes(input);
+ for (var chunk in expectedOutput) {
+ expect(await messageGrouper.next, equals(chunk));
+ }
+ }
+
+ /// Make a simple message having the given [length]
+ List<int> makeMessage(int length) {
+ var result = <int>[];
+ for (var i = 0; i < length; i++) {
+ result.add(i & 0xff);
+ }
+ return result;
+ }
+
+ test('Empty message', () async {
+ await check([0], [[]]);
+ });
+
+ test('Short message', () async {
+ await check(
+ [5, 10, 20, 30, 40, 50],
+ [
+ [10, 20, 30, 40, 50],
+ ],
+ );
+ });
+
+ test('Message with 2-byte length', () async {
+ var len = 0x155;
+ var msg = makeMessage(len);
+ var encodedLen = [0xd5, 0x02];
+ await check([...encodedLen, ...msg], [msg]);
+ });
+
+ test('Message with 3-byte length', () async {
+ var len = 0x4103;
+ var msg = makeMessage(len);
+ var encodedLen = [0x83, 0x82, 0x01];
+ await check([...encodedLen, ...msg], [msg]);
+ });
+
+ test('Multiple messages', () async {
+ await check(
+ [2, 10, 20, 2, 30, 40],
+ [
+ [10, 20],
+ [30, 40],
+ ],
+ );
+ });
+
+ test('Empty message at start', () async {
+ await check(
+ [0, 2, 10, 20],
+ [
+ [],
+ [10, 20],
+ ],
+ );
+ });
+
+ test('Empty message at end', () async {
+ await check(
+ [2, 10, 20, 0],
+ [
+ [10, 20],
+ [],
+ ],
+ );
+ });
+
+ test('Empty message in the middle', () async {
+ await check(
+ [2, 10, 20, 0, 2, 30, 40],
+ [
+ [10, 20],
+ [],
+ [30, 40],
+ ],
+ );
+ });
+
+ test('Handles the case when stdin gives an error instead of EOF', () async {
+ if (stdinStream is TestStdinSync) {
+ // Reading will now cause an error as pendingBytes is empty.
+ (stdinStream as TestStdinSync).pendingBytes.clear();
+ expect(messageGrouper.next, isNull);
+ } else if (stdinStream is TestStdinAsync) {
+ (stdinStream as TestStdinAsync).controller.addError('Error!');
+ expect(await messageGrouper.next, isNull);
+ }
+ });
+}
diff --git a/pkgs/bazel_worker/test/test_all.dart b/pkgs/bazel_worker/test/test_all.dart
new file mode 100644
index 0000000..7eb6b11
--- /dev/null
+++ b/pkgs/bazel_worker/test/test_all.dart
@@ -0,0 +1,15 @@
+// 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.
+
+import 'driver_connection_test.dart' as driver_connection;
+import 'driver_test.dart' as driver;
+import 'message_grouper_test.dart' as message_grouper;
+import 'worker_loop_test.dart' as worker_loop;
+
+void main() {
+ driver.main();
+ message_grouper.main();
+ worker_loop.main();
+ driver_connection.main();
+}
diff --git a/pkgs/bazel_worker/test/worker_loop_test.dart b/pkgs/bazel_worker/test/worker_loop_test.dart
new file mode 100644
index 0000000..24068b1
--- /dev/null
+++ b/pkgs/bazel_worker/test/worker_loop_test.dart
@@ -0,0 +1,141 @@
+// 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.
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:bazel_worker/bazel_worker.dart';
+import 'package:bazel_worker/testing.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('SyncWorkerLoop', () {
+ runTests(
+ TestStdinSync.new,
+ TestSyncWorkerConnection.new,
+ TestSyncWorkerLoop.new,
+ );
+ });
+
+ group('AsyncWorkerLoop', () {
+ runTests(
+ TestStdinAsync.new,
+ TestAsyncWorkerConnection.new,
+ TestAsyncWorkerLoop.new,
+ );
+ });
+
+ group('SyncWorkerLoopWithPrint', () {
+ runTests(
+ TestStdinSync.new,
+ TestSyncWorkerConnection.new,
+ (TestSyncWorkerConnection connection) =>
+ TestSyncWorkerLoop(connection, printMessage: 'Goodbye!'),
+ );
+ });
+
+ group('AsyncWorkerLoopWithPrint', () {
+ runTests(
+ TestStdinAsync.new,
+ TestAsyncWorkerConnection.new,
+ (TestAsyncWorkerConnection connection) =>
+ TestAsyncWorkerLoop(connection, printMessage: 'Goodbye!'),
+ );
+ });
+}
+
+void runTests<T extends TestWorkerConnection>(
+ TestStdin Function() stdinFactory,
+ T Function(Stdin, Stdout) workerConnectionFactory,
+ TestWorkerLoop Function(T) workerLoopFactory,
+) {
+ late TestStdin stdinStream;
+ late TestStdoutStream stdoutStream;
+ late T connection;
+ late TestWorkerLoop workerLoop;
+
+ setUp(() {
+ stdinStream = stdinFactory();
+ stdoutStream = TestStdoutStream();
+ connection = workerConnectionFactory(stdinStream, stdoutStream);
+ workerLoop = workerLoopFactory(connection);
+ });
+
+ test('basic', () async {
+ var request = WorkRequest(arguments: ['--foo=bar']);
+ stdinStream.addInputBytes(protoToDelimitedBuffer(request));
+ stdinStream.close();
+
+ var response = WorkResponse(output: 'Hello World');
+ workerLoop.enqueueResponse(response);
+
+ // Make sure `print` never gets called in the parent zone.
+ var printMessages = <String>[];
+ await runZoned(
+ () => workerLoop.run(),
+ zoneSpecification: ZoneSpecification(
+ print: (self, parent, zone, message) {
+ printMessages.add(message);
+ },
+ ),
+ );
+ expect(
+ printMessages,
+ isEmpty,
+ reason: 'The worker loop should hide all print calls from the parent '
+ 'zone.',
+ );
+
+ expect(connection.responses, hasLength(1));
+ expect(connection.responses[0], response);
+ if (workerLoop.printMessage != null) {
+ expect(
+ response.output,
+ endsWith(workerLoop.printMessage!),
+ reason: 'Print messages should get appended to the response output.',
+ );
+ }
+
+ // Check that a serialized version was written to std out.
+ expect(stdoutStream.writes, hasLength(1));
+ expect(stdoutStream.writes[0], protoToDelimitedBuffer(response));
+ });
+
+ test('Exception in the worker.', () async {
+ var request = WorkRequest(arguments: ['--foo=bar']);
+ stdinStream.addInputBytes(protoToDelimitedBuffer(request));
+ stdinStream.close();
+
+ // Didn't enqueue a response, so this will throw inside of `performRequest`.
+ await workerLoop.run();
+
+ expect(connection.responses, hasLength(1));
+ var response = connection.responses[0];
+ expect(response.exitCode, EXIT_CODE_ERROR);
+
+ // Check that a serialized version was written to std out.
+ expect(stdoutStream.writes, hasLength(1));
+ expect(stdoutStream.writes[0], protoToDelimitedBuffer(response));
+ });
+
+ test('Stops at EOF', () async {
+ stdinStream.addInputBytes([-1]);
+ stdinStream.close();
+ await workerLoop.run();
+ });
+
+ test('Stops if stdin gives an error instead of EOF', () async {
+ if (stdinStream is TestStdinSync) {
+ // Reading will now cause an error as pendingBytes is empty.
+ (stdinStream as TestStdinSync).pendingBytes.clear();
+ await workerLoop.run();
+ } else if (stdinStream is TestStdinAsync) {
+ var done = Completer<void>();
+ // ignore: avoid_dynamic_calls
+ workerLoop.run().then((_) => done.complete(null));
+ (stdinStream as TestStdinAsync).controller.addError('Error!!');
+ await done.future;
+ }
+ });
+}
diff --git a/pkgs/bazel_worker/tool/travis.sh b/pkgs/bazel_worker/tool/travis.sh
new file mode 100755
index 0000000..05adb02
--- /dev/null
+++ b/pkgs/bazel_worker/tool/travis.sh
@@ -0,0 +1,26 @@
+#!/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
diff --git a/pkgs/bazel_worker/tool/update_proto.sh b/pkgs/bazel_worker/tool/update_proto.sh
new file mode 100755
index 0000000..3f77a06
--- /dev/null
+++ b/pkgs/bazel_worker/tool/update_proto.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+set -e
+
+if [ -z "$1" ]; then
+ echo "Expected exactly one argument which is the protoc_plugin version to use"
+else
+ echo "Using protoc_plugin version $1"
+ dart pub global activate protoc_plugin "$1"
+fi
+
+BAZEL_REPO=.dart_tool/bazel_worker/bazel.git/
+# Bash away old versions if they exist
+rm -rf "$BAZEL_REPO"
+git clone --depth 1 https://github.com/bazelbuild/bazel.git "$BAZEL_REPO"
+
+protoc --proto_path="${BAZEL_REPO}/src/main/protobuf" --dart_out="lib/src" worker_protocol.proto
+dart format lib/src/worker_protocol.pb.dart
+
+# We only care about the *.pb.dart file, not the extra files
+rm lib/src/worker_protocol.pbenum.dart
+rm lib/src/worker_protocol.pbjson.dart
+rm lib/src/worker_protocol.pbserver.dart
+
+rm -rf "$BAZEL_REPO"
diff --git a/pkgs/benchmark_harness/.gitignore b/pkgs/benchmark_harness/.gitignore
new file mode 100644
index 0000000..2afa93d
--- /dev/null
+++ b/pkgs/benchmark_harness/.gitignore
@@ -0,0 +1,4 @@
+.dart_tool
+.packages
+.pub
+pubspec.lock
diff --git a/pkgs/benchmark_harness/CHANGELOG.md b/pkgs/benchmark_harness/CHANGELOG.md
new file mode 100644
index 0000000..fceecb8
--- /dev/null
+++ b/pkgs/benchmark_harness/CHANGELOG.md
@@ -0,0 +1,50 @@
+## 2.3.1
+
+- Move to `dart-lang/tools` monorepo.
+
+## 2.3.0
+
+- Require Dart 3.2.
+- Add ScoreEmitterV2 interface, documented with the intention to change
+ScoreEmitter interface to match it in the next major release,
+ a breaking change.
+- Add `PerfBenchmarkBase` class which runs the 'perf stat' command from
+linux-tools on a benchmark and reports metrics from the hardware
+performance counters and the iteration count, as well as the run time
+measurement reported by `BenchmarkBase`.
+
+## 2.2.2
+
+- Added package topics to the pubspec file.
+- Require Dart 2.19.
+
+## 2.2.1
+
+- Improve convergence speed of `BenchmarkBase` measuring algorithm by allowing
+some degree of measuring jitter.
+
+## 2.2.0
+
+- Change measuring algorithm in `BenchmarkBase` to avoid calling stopwatch
+methods repeatedly in the measuring loop. This makes measurement work better
+for `run` methods which are small themselves.
+
+## 2.1.0
+
+- Add AsyncBenchmarkBase.
+
+## 2.0.0
+
+- Stable null safety release.
+
+## 2.0.0-nullsafety.0
+
+- Opt in to null safety.
+
+## 1.0.6
+
+- Require at least Dart 2.1.
+
+## 1.0.5
+
+- Updates to support Dart 2.
diff --git a/pkgs/benchmark_harness/LICENSE b/pkgs/benchmark_harness/LICENSE
new file mode 100644
index 0000000..db5bf46
--- /dev/null
+++ b/pkgs/benchmark_harness/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2021, 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/benchmark_harness/README.md b/pkgs/benchmark_harness/README.md
new file mode 100644
index 0000000..f156f23
--- /dev/null
+++ b/pkgs/benchmark_harness/README.md
@@ -0,0 +1,116 @@
+[](https://github.com/dart-lang/tools/actions/workflows/benchmark_harness.yaml)
+[](https://pub.dev/packages/benchmark_harness)
+[](https://pub.dev/packages/benchmark_harness/publisher)
+
+The Dart project benchmark harness is the recommended starting point when
+building a benchmark for Dart.
+
+## Interpreting Results
+
+By default, the reported runtime in `BenchmarkBase` is not for a single call to
+`run()`, but for the average time it takes to call `run()` __10 times__ for
+legacy reasons. The benchmark harness executes a 10-call timing loop repeatedly
+until 2 seconds have elapsed; the reported result is the average of the runtimes
+for each loop. This behavior will change in a future major version.
+
+Benchmarks extending `BenchmarkBase` can opt into the reporting the average time
+to call `run()` once by overriding the `exercise` method:
+
+```dart
+ @override
+ void exercise() => run();
+```
+
+`AsyncBenchmarkBase` already reports the average time to call `run()` __once__.
+
+## Comparing Results
+
+If you are running the same benchmark, on the same machine, running the same OS,
+the reported run times can be carefully compared across runs.
+Carefully because there are a variety of factors which
+could cause error in the run time, for example, the load from
+other applications running on your machine could alter the result.
+
+Comparing the run time of different benchmarks is not recommended.
+In other words, don't compare apples with oranges.
+
+## Features
+
+* `BenchmarkBase` class that all new benchmarks should `extend`.
+* `AsyncBenchmarkBase` for asynchronous benchmarks.
+* Template benchmark that you can copy and paste when building new benchmarks.
+
+## Getting Started
+
+1\. Add the following to your project's **pubspec.yaml**
+
+```yaml
+dependencies:
+ benchmark_harness: any
+```
+
+2\. Install pub packages
+
+```sh
+dart pub install
+```
+
+3\. Add the following import:
+
+```dart
+import 'package:benchmark_harness/benchmark_harness.dart';
+```
+
+4\. Create a benchmark class which inherits from `BenchmarkBase` or
+ `AsyncBenchmarkBase`.
+
+## Example
+
+Create a dart file in the
+[`benchmark/`](https://dart.dev/tools/pub/package-layout#tests-and-benchmarks)
+folder of your package.
+
+```dart
+// Import BenchmarkBase class.
+import 'package:benchmark_harness/benchmark_harness.dart';
+
+// Create a new benchmark by extending BenchmarkBase
+class TemplateBenchmark extends BenchmarkBase {
+ const TemplateBenchmark() : super('Template');
+
+ static void main() {
+ const TemplateBenchmark().report();
+ }
+
+ // The benchmark code.
+ @override
+ void run() {}
+
+ // Not measured setup code executed prior to the benchmark runs.
+ @override
+ void setup() {}
+
+ // Not measured teardown code executed after the benchmark runs.
+ @override
+ void teardown() {}
+
+ // To opt into the reporting the time per run() instead of per 10 run() calls.
+ //@override
+ //void exercise() => run();
+}
+
+void main() {
+ // Run TemplateBenchmark
+ TemplateBenchmark.main();
+}
+```
+
+### Output
+
+```console
+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.
diff --git a/pkgs/benchmark_harness/analysis_options.yaml b/pkgs/benchmark_harness/analysis_options.yaml
new file mode 100644
index 0000000..9a77232
--- /dev/null
+++ b/pkgs/benchmark_harness/analysis_options.yaml
@@ -0,0 +1,14 @@
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-inference: true
+ strict-casts: true
+
+linter:
+ rules:
+ - avoid_unused_constructor_parameters
+ - cancel_subscriptions
+ - literal_only_boolean_expressions
+ - no_adjacent_strings_in_list
+ - unnecessary_await_in_return
diff --git a/pkgs/benchmark_harness/example/template.dart b/pkgs/benchmark_harness/example/template.dart
new file mode 100644
index 0000000..d13a98b
--- /dev/null
+++ b/pkgs/benchmark_harness/example/template.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2021, 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 BenchmarkBase class.
+import 'package:benchmark_harness/benchmark_harness.dart';
+
+// Create a new benchmark by extending BenchmarkBase
+class TemplateBenchmark extends BenchmarkBase {
+ const TemplateBenchmark() : super('Template');
+
+ static void main() {
+ const TemplateBenchmark().report();
+ }
+
+ // The benchmark code.
+ @override
+ void run() {}
+
+ // Not measured setup code executed prior to the benchmark runs.
+ @override
+ void setup() {}
+
+ // Not measures teardown code executed after the benchmark runs.
+ @override
+ void teardown() {}
+
+ // To opt into the reporting the time per run() instead of per 10 run() calls.
+ //@override
+ //void exercise() => run();
+}
+
+void main() {
+ // Run TemplateBenchmark
+ TemplateBenchmark.main();
+}
diff --git a/pkgs/benchmark_harness/integration_test/perf_benchmark_test.dart b/pkgs/benchmark_harness/integration_test/perf_benchmark_test.dart
new file mode 100644
index 0000000..339777f
--- /dev/null
+++ b/pkgs/benchmark_harness/integration_test/perf_benchmark_test.dart
@@ -0,0 +1,26 @@
+// Copyright 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:benchmark_harness/perf_benchmark_harness.dart';
+import 'package:test/test.dart';
+
+class PerfBenchmark extends PerfBenchmarkBase {
+ PerfBenchmark(super.name);
+ int runCount = 0;
+
+ @override
+ void run() {
+ runCount++;
+ for (final i in List.filled(1000, 7)) {
+ runCount += i - i;
+ }
+ }
+}
+
+void main() {
+ test('run is called', () async {
+ final benchmark = PerfBenchmark('ForLoop');
+ await benchmark.reportPerf();
+ });
+}
diff --git a/pkgs/benchmark_harness/lib/benchmark_harness.dart b/pkgs/benchmark_harness/lib/benchmark_harness.dart
new file mode 100644
index 0000000..b46a36f
--- /dev/null
+++ b/pkgs/benchmark_harness/lib/benchmark_harness.dart
@@ -0,0 +1,7 @@
+// Copyright (c) 2021, 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/async_benchmark_base.dart';
+export 'src/benchmark_base.dart' show BenchmarkBase;
+export 'src/score_emitter.dart';
diff --git a/pkgs/benchmark_harness/lib/perf_benchmark_harness.dart b/pkgs/benchmark_harness/lib/perf_benchmark_harness.dart
new file mode 100644
index 0000000..3de8329
--- /dev/null
+++ b/pkgs/benchmark_harness/lib/perf_benchmark_harness.dart
@@ -0,0 +1,7 @@
+// 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.
+
+export 'src/perf_benchmark_base_stub.dart'
+ if (dart.library.io) 'src/perf_benchmark_base.dart';
+export 'src/score_emitter.dart';
diff --git a/pkgs/benchmark_harness/lib/src/async_benchmark_base.dart b/pkgs/benchmark_harness/lib/src/async_benchmark_base.dart
new file mode 100644
index 0000000..1472ee7
--- /dev/null
+++ b/pkgs/benchmark_harness/lib/src/async_benchmark_base.dart
@@ -0,0 +1,69 @@
+// Copyright (c) 2014, 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 'score_emitter.dart';
+
+class AsyncBenchmarkBase {
+ final String name;
+ final ScoreEmitter emitter;
+
+ /// Empty constructor.
+ const AsyncBenchmarkBase(this.name, {this.emitter = const PrintEmitter()});
+
+ /// The benchmark code.
+ ///
+ /// This function is not used, if both [warmup] and [exercise] are
+ /// overwritten.
+ Future<void> run() async {}
+
+ /// Runs a short version of the benchmark. By default invokes [run] once.
+ Future<void> warmup() async {
+ await run();
+ }
+
+ /// Exercises the benchmark. By default invokes [run] once.
+ Future<void> exercise() async {
+ await run();
+ }
+
+ /// Not measured setup code executed prior to the benchmark runs.
+ Future<void> setup() async {}
+
+ /// Not measures teardown code executed after the benchmark runs.
+ Future<void> teardown() async {}
+
+ /// Measures the score for this benchmark by executing it repeatedly until
+ /// time minimum has been reached.
+ static Future<double> measureFor(
+ Future<void> Function() f, int minimumMillis) async {
+ final minimumMicros = minimumMillis * 1000;
+ final watch = Stopwatch()..start();
+ var iter = 0;
+ var elapsed = 0;
+ while (elapsed < minimumMicros) {
+ await f();
+ elapsed = watch.elapsedMicroseconds;
+ iter++;
+ }
+ return elapsed / iter;
+ }
+
+ /// Measures the score for the benchmark and returns it.
+ Future<double> measure() async {
+ await setup();
+ try {
+ // Warmup for at least 100ms. Discard result.
+ await measureFor(warmup, 100);
+ // Run the benchmark for at least 2000ms.
+ return await measureFor(exercise, 2000);
+ } finally {
+ await teardown();
+ }
+ }
+
+ /// Run the benchmark and report results on the [emitter].
+ Future<void> report() async {
+ emitter.emit(name, await measure());
+ }
+}
diff --git a/pkgs/benchmark_harness/lib/src/benchmark_base.dart b/pkgs/benchmark_harness/lib/src/benchmark_base.dart
new file mode 100644
index 0000000..bc874f5
--- /dev/null
+++ b/pkgs/benchmark_harness/lib/src/benchmark_base.dart
@@ -0,0 +1,113 @@
+// 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:math' as math;
+
+import 'score_emitter.dart';
+
+const int minimumMeasureDurationMillis = 2000;
+
+class BenchmarkBase {
+ final String name;
+ final ScoreEmitter emitter;
+
+ const BenchmarkBase(this.name, {this.emitter = const PrintEmitter()});
+
+ /// The benchmark code.
+ ///
+ /// This function is not used, if both [warmup] and [exercise] are
+ /// overwritten.
+ void run() {}
+
+ /// Runs a short version of the benchmark. By default invokes [run] once.
+ void warmup() {
+ run();
+ }
+
+ /// Exercises the benchmark. By default invokes [run] 10 times.
+ void exercise() {
+ for (var i = 0; i < 10; i++) {
+ run();
+ }
+ }
+
+ /// Not measured setup code executed prior to the benchmark runs.
+ void setup() {}
+
+ /// Not measured teardown code executed after the benchmark runs.
+ void teardown() {}
+
+ /// Measures the score for this benchmark by executing it enough times
+ /// to reach [minimumMillis].
+
+ /// Measures the score for this benchmark by executing it repeatedly until
+ /// time minimum has been reached.
+ static double measureFor(void Function() f, int minimumMillis) =>
+ measureForImpl(f, minimumMillis).score;
+
+ /// Measures the score for the benchmark and returns it.
+ double measure() {
+ setup();
+ // Warmup for at least 100ms. Discard result.
+ measureForImpl(warmup, 100);
+ // Run the benchmark for at least 2000ms.
+ var result = measureForImpl(exercise, minimumMeasureDurationMillis);
+ teardown();
+ return result.score;
+ }
+
+ void report() {
+ 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/perf_benchmark_base.dart b/pkgs/benchmark_harness/lib/src/perf_benchmark_base.dart
new file mode 100644
index 0000000..1b7fb92
--- /dev/null
+++ b/pkgs/benchmark_harness/lib/src/perf_benchmark_base.dart
@@ -0,0 +1,135 @@
+// 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 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'benchmark_base.dart';
+import 'score_emitter.dart';
+
+class PerfBenchmarkBase extends BenchmarkBase {
+ late final Directory fifoDir;
+ late final String perfControlFifo;
+ late final RandomAccessFile openedFifo;
+ late final String perfControlAck;
+ late final RandomAccessFile openedAck;
+ late final Process perfProcess;
+ late final List<String> perfProcessArgs;
+
+ PerfBenchmarkBase(super.name,
+ {ScoreEmitterV2 super.emitter = const PrintEmitterV2()});
+
+ ScoreEmitterV2 get _emitterV2 => emitter as ScoreEmitterV2;
+
+ Future<void> _createFifos() async {
+ perfControlFifo = '${fifoDir.path}/perf_control_fifo';
+ perfControlAck = '${fifoDir.path}/perf_control_ack';
+ for (final path in [perfControlFifo, perfControlAck]) {
+ final fifoResult = await Process.run('mkfifo', [path]);
+ if (fifoResult.exitCode != 0) {
+ throw ProcessException('mkfifo', [path],
+ 'Cannot create fifo: ${fifoResult.stderr}', fifoResult.exitCode);
+ }
+ }
+ }
+
+ Future<void> _startPerfStat() async {
+ await _createFifos();
+ perfProcessArgs = [
+ 'stat',
+ '--delay=-1',
+ '--control=fifo:$perfControlFifo,$perfControlAck',
+ '-x\\t',
+ '--pid=$pid',
+ ];
+ perfProcess = await Process.start('perf', perfProcessArgs);
+ }
+
+ void _enablePerf() {
+ openedFifo = File(perfControlFifo).openSync(mode: FileMode.writeOnly);
+ openedAck = File(perfControlAck).openSync();
+ openedFifo.writeStringSync('enable\n');
+ _waitForAck();
+ }
+
+ Future<void> _stopPerfStat(int totalIterations) async {
+ openedFifo.writeStringSync('disable\n');
+ openedFifo.closeSync();
+ _waitForAck();
+ openedAck.closeSync();
+ perfProcess.kill(ProcessSignal.sigint);
+ unawaited(perfProcess.stdout.drain());
+ final lines = await perfProcess.stderr
+ .transform(utf8.decoder)
+ .transform(const LineSplitter())
+ .toList();
+ final exitCode = await perfProcess.exitCode;
+ // Exit code from perf is -SIGINT when terminated with SIGINT.
+ if (exitCode != 0 && exitCode != -ProcessSignal.sigint.signalNumber) {
+ throw ProcessException(
+ 'perf', perfProcessArgs, lines.join('\n'), exitCode);
+ }
+
+ const metrics = {
+ 'cycles': 'CpuCycles',
+ 'page-faults': 'MajorPageFaults',
+ };
+ for (final line in lines) {
+ if (line.split('\t')
+ case [
+ String counter,
+ _,
+ String event && ('cycles' || 'page-faults'),
+ ...
+ ]) {
+ _emitterV2.emit(name, double.parse(counter) / totalIterations,
+ metric: metrics[event]!);
+ }
+ }
+ _emitterV2.emit('$name.totalIterations', totalIterations.toDouble(),
+ metric: 'Count');
+ }
+
+ /// Measures the score for the benchmark and returns it.
+ Future<double> measurePerf() async {
+ Measurement result;
+ setup();
+ try {
+ fifoDir = await Directory.systemTemp.createTemp('fifo');
+ try {
+ // Warmup for at least 100ms. Discard result.
+ measureForImpl(warmup, 100);
+ await _startPerfStat();
+ try {
+ _enablePerf();
+ // Run the benchmark for at least 2000ms.
+ result = measureForImpl(exercise, minimumMeasureDurationMillis);
+ await _stopPerfStat(result.totalIterations);
+ } catch (_) {
+ perfProcess.kill(ProcessSignal.sigkill);
+ rethrow;
+ }
+ } finally {
+ await fifoDir.delete(recursive: true);
+ }
+ } finally {
+ teardown();
+ }
+ return result.score;
+ }
+
+ Future<void> reportPerf() async {
+ _emitterV2.emit(name, await measurePerf(), unit: 'us.');
+ }
+
+ void _waitForAck() {
+ // Perf writes 'ack\n\x00' to the acknowledgement fifo.
+ const ackLength = 'ack\n\x00'.length;
+ var ack = <int>[...openedAck.readSync(ackLength)];
+ while (ack.length < ackLength) {
+ ack.addAll(openedAck.readSync(ackLength - ack.length));
+ }
+ }
+}
diff --git a/pkgs/benchmark_harness/lib/src/perf_benchmark_base_stub.dart b/pkgs/benchmark_harness/lib/src/perf_benchmark_base_stub.dart
new file mode 100644
index 0000000..81aa0ea
--- /dev/null
+++ b/pkgs/benchmark_harness/lib/src/perf_benchmark_base_stub.dart
@@ -0,0 +1,18 @@
+// 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 'benchmark_base.dart';
+import 'score_emitter.dart';
+
+class PerfBenchmarkBase extends BenchmarkBase {
+ PerfBenchmarkBase(super.name, {super.emitter = const PrintEmitter()});
+
+ Future<double> measurePerf() async {
+ return super.measure();
+ }
+
+ Future<void> reportPerf() async {
+ super.report();
+ }
+}
diff --git a/pkgs/benchmark_harness/lib/src/score_emitter.dart b/pkgs/benchmark_harness/lib/src/score_emitter.dart
new file mode 100644
index 0000000..4407118
--- /dev/null
+++ b/pkgs/benchmark_harness/lib/src/score_emitter.dart
@@ -0,0 +1,38 @@
+// 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.
+
+abstract class ScoreEmitter {
+ void emit(String testName, double value);
+}
+
+class PrintEmitter implements ScoreEmitter {
+ const PrintEmitter();
+
+ @override
+ void emit(String testName, double value) {
+ print('$testName(RunTime): $value us.');
+ }
+}
+
+/// New interface for [ScoreEmitter]. [ScoreEmitter] will be changed to
+/// this interface in the next major version release, and this class will
+/// be deprecated and removed. That release will be a breaking change.
+abstract class ScoreEmitterV2 implements ScoreEmitter {
+ @override
+ void emit(String testName, double value,
+ {String metric = 'RunTime', String unit});
+}
+
+/// New implementation of [PrintEmitter] implementing the [ScoreEmitterV2]
+/// interface. [PrintEmitter] will be changed to this implementation in the
+/// next major version release.
+class PrintEmitterV2 implements ScoreEmitterV2 {
+ const PrintEmitterV2();
+
+ @override
+ void emit(String testName, double value,
+ {String metric = 'RunTime', String unit = ''}) {
+ print(['$testName($metric):', value, if (unit.isNotEmpty) unit].join(' '));
+ }
+}
diff --git a/pkgs/benchmark_harness/pubspec.yaml b/pkgs/benchmark_harness/pubspec.yaml
new file mode 100644
index 0000000..7d5a9d1
--- /dev/null
+++ b/pkgs/benchmark_harness/pubspec.yaml
@@ -0,0 +1,18 @@
+name: benchmark_harness
+version: 2.3.1
+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
+
+topics:
+ - benchmarking
+
+environment:
+ sdk: ^3.2.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
diff --git a/pkgs/benchmark_harness/test/benchmark_harness_test.dart b/pkgs/benchmark_harness/test/benchmark_harness_test.dart
new file mode 100644
index 0000000..7f88584
--- /dev/null
+++ b/pkgs/benchmark_harness/test/benchmark_harness_test.dart
@@ -0,0 +1,47 @@
+// Copyright (c) 2021, 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 'package:benchmark_harness/benchmark_harness.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('benchmark_harness', () {
+ test('run is called', () {
+ final benchmark = MockBenchmark();
+ final micros = benchmark.measure();
+ expect(micros, isPositive);
+ expect(benchmark.runCount, isPositive);
+ });
+ test('async run is awaited', () async {
+ final benchmark = MockAsyncBenchmark();
+ final micros = await benchmark.measure();
+ expect(micros, isPositive);
+ expect(benchmark.runCount, isPositive);
+ });
+ });
+}
+
+class MockBenchmark extends BenchmarkBase {
+ int runCount = 0;
+
+ MockBenchmark() : super('mock benchmark');
+
+ @override
+ void run() {
+ runCount++;
+ }
+}
+
+class MockAsyncBenchmark extends AsyncBenchmarkBase {
+ int runCount = 0;
+ MockAsyncBenchmark() : super('mock benchmark');
+
+ @override
+ Future<void> run() async {
+ await Future<void>.delayed(Duration.zero);
+ runCount++;
+ }
+}
diff --git a/pkgs/benchmark_harness/test/result_emitter_test.dart b/pkgs/benchmark_harness/test/result_emitter_test.dart
new file mode 100644
index 0000000..e2cd1ea
--- /dev/null
+++ b/pkgs/benchmark_harness/test/result_emitter_test.dart
@@ -0,0 +1,51 @@
+// Copyright (c) 2021, 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:benchmark_harness/benchmark_harness.dart';
+import 'package:test/test.dart';
+
+void main() {
+ benchmarkHarnessTest();
+}
+
+class MockResultEmitter extends ScoreEmitter {
+ int emitCount = 0;
+
+ @override
+ void emit(String name, double value) {
+ emitCount++;
+ }
+}
+
+// Create a new benchmark which has an emitter.
+class BenchmarkWithResultEmitter extends BenchmarkBase {
+ const BenchmarkWithResultEmitter(ScoreEmitter emitter)
+ : super('Template', emitter: emitter);
+
+ @override
+ void run() {}
+
+ @override
+ void setup() {}
+
+ @override
+ void teardown() {}
+}
+
+void benchmarkHarnessTest() {
+ MockResultEmitter createMockEmitter() {
+ var emitter = MockResultEmitter();
+ return emitter;
+ }
+
+ group('ResultEmitter', () {
+ test('should be called when emitter is provided', () {
+ var emitter = createMockEmitter();
+ var testBenchmark = BenchmarkWithResultEmitter(emitter);
+ testBenchmark.report();
+
+ expect(emitter.emitCount, equals(1));
+ });
+ });
+}
diff --git a/pkgs/boolean_selector/.gitignore b/pkgs/boolean_selector/.gitignore
new file mode 100644
index 0000000..49ce72d
--- /dev/null
+++ b/pkgs/boolean_selector/.gitignore
@@ -0,0 +1,3 @@
+.dart_tool/
+.packages
+pubspec.lock
diff --git a/pkgs/boolean_selector/AUTHORS b/pkgs/boolean_selector/AUTHORS
new file mode 100644
index 0000000..e8063a8
--- /dev/null
+++ b/pkgs/boolean_selector/AUTHORS
@@ -0,0 +1,6 @@
+# Below is a list of people and organizations that have contributed
+# to the project. Names should be added to the list like so:
+#
+# Name/Organization <email address>
+
+Google Inc.
diff --git a/pkgs/boolean_selector/CHANGELOG.md b/pkgs/boolean_selector/CHANGELOG.md
new file mode 100644
index 0000000..f870787
--- /dev/null
+++ b/pkgs/boolean_selector/CHANGELOG.md
@@ -0,0 +1,44 @@
+## 2.1.2
+
+* Increase the SDK minimum to `3.1.0`.
+* Move to `dart-lang/tools` monorepo.
+
+## 2.1.1
+
+* Increase the SDK minimum to `2.17.0`.
+* Populate the pubspec `repository` field.
+
+## 2.1.0
+
+* Stable release for null safety.
+
+## 2.0.0
+
+* Breaking: `BooleanSelector.evaluate` always takes a `bool Function(String)`.
+ For use cases previously passing a `Set<String>`, tear off the `.contains`
+ method. For use cases passing an `Iterable<String>` it may be worthwhile to
+ first use `.toSet()` before tearing off `.contains`.
+
+## 1.0.5
+
+* Update package metadata & add `example/` folder
+
+## 1.0.4
+
+* Now requires Dart 2.
+
+## 1.0.3
+
+* Work around an inference bug in the new common front-end.
+
+## 1.0.2
+
+* Declare compatibility with `string_scanner` 1.0.0.
+
+## 1.0.1
+
+* Fix all strong mode warnings.
+
+## 1.0.0
+
+* Initial release.
diff --git a/pkgs/boolean_selector/LICENSE b/pkgs/boolean_selector/LICENSE
new file mode 100644
index 0000000..ac7d7af
--- /dev/null
+++ b/pkgs/boolean_selector/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2016, 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.
diff --git a/pkgs/boolean_selector/README.md b/pkgs/boolean_selector/README.md
new file mode 100644
index 0000000..8d58dbb
--- /dev/null
+++ b/pkgs/boolean_selector/README.md
@@ -0,0 +1,42 @@
+[](https://github.com/dart-lang/tools/actions/workflows/boolean_selector.yaml)
+[](https://pub.dev/packages/boolean_selector)
+[](https://pub.dev/packages/boolean_selector/publisher)
+
+The `boolean_selector` package defines a simple and flexible syntax for boolean
+expressions. It can be used for filtering based on user-defined expressions. For
+example, the [`test`][test] package uses boolean selectors to allow users to
+define what platforms their tests support.
+
+[test]: https://github.com/dart-lang/test
+
+The boolean selector syntax is based on a simplified version of Dart's
+expression syntax. Selectors can contain identifiers, parentheses, and boolean
+operators, including `||`, `&&`, `!`, and `? :`. Any valid Dart identifier is
+allowed, and identifiers may also contain hyphens. For example, `chrome`,
+`chrome || content-shell`, and `js || (vm && linux)` are all valid boolean
+selectors.
+
+A boolean selector is parsed from a string using
+[`BooleanSelector.parse()`][parse], and evaluated against a set of variables
+using [`BooleanSelector.evaluate()`][evaluate]. The variables are supplied as
+a function that takes a variable name and returns its value. For example:
+
+[parse]: https://pub.dev/documentation/boolean_selector/latest/boolean_selector/BooleanSelector/BooleanSelector.parse.html
+
+[evaluate]: https://pub.dev/documentation/boolean_selector/latest/boolean_selector/BooleanSelector/evaluate.html
+
+```dart
+import 'package:boolean_selector/boolean_selector.dart';
+
+void main(List<String> args) {
+ var selector = BooleanSelector.parse("(x && y) || z");
+ print(selector.evaluate((variable) => args.contains(variable)));
+}
+```
+
+## Versioning
+
+If this package adds new features to the boolean selector syntax, it will
+increment its major version number. This ensures that packages that expose the
+syntax to their users will be able to update their own minor versions, so their
+users can indicate that they rely on the new syntax.
diff --git a/pkgs/boolean_selector/analysis_options.yaml b/pkgs/boolean_selector/analysis_options.yaml
new file mode 100644
index 0000000..af2619a
--- /dev/null
+++ b/pkgs/boolean_selector/analysis_options.yaml
@@ -0,0 +1,6 @@
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+linter:
+ rules:
+ - avoid_unused_constructor_parameters
+ - cancel_subscriptions
diff --git a/pkgs/boolean_selector/example/example.dart b/pkgs/boolean_selector/example/example.dart
new file mode 100644
index 0000000..a4f7879
--- /dev/null
+++ b/pkgs/boolean_selector/example/example.dart
@@ -0,0 +1,6 @@
+import 'package:boolean_selector/boolean_selector.dart';
+
+void main(List<String> args) {
+ var selector = BooleanSelector.parse('(x && y) || z');
+ print(selector.evaluate((variable) => args.contains(variable)));
+}
diff --git a/pkgs/boolean_selector/lib/boolean_selector.dart b/pkgs/boolean_selector/lib/boolean_selector.dart
new file mode 100644
index 0000000..1c42641
--- /dev/null
+++ b/pkgs/boolean_selector/lib/boolean_selector.dart
@@ -0,0 +1,56 @@
+// 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.
+
+import 'package:source_span/source_span.dart';
+
+import 'src/all.dart';
+import 'src/impl.dart';
+import 'src/none.dart';
+
+/// A boolean expression that evaluates to `true` or `false` based on certain
+/// inputs.
+///
+/// The syntax is mostly Dart's expression syntax restricted to boolean
+/// operations. See [the README][] for full details.
+///
+/// [the README]: https://github.com/dart-lang/boolean_selector/blob/master/README.md
+///
+/// Boolean selectors support structural equality. Two selectors that have the
+/// same parsed structure are considered equal.
+abstract class BooleanSelector {
+ /// A selector that accepts all inputs.
+ static const all = All();
+
+ /// A selector that accepts no inputs.
+ static const none = None();
+
+ /// All the variables in this selector, in the order they appear.
+ Iterable<String> get variables;
+
+ /// Parses [selector].
+ ///
+ /// This will throw a [SourceSpanFormatException] if the selector is
+ /// malformed or if it uses an undefined variable.
+ factory BooleanSelector.parse(String selector) = BooleanSelectorImpl.parse;
+
+ /// Returns whether the selector matches the given [semantics].
+ ///
+ /// The [semantics] define which variables evaluate to `true` or `false`. When
+ /// passed a variable name it should return the value of that variable.
+ bool evaluate(bool Function(String variable) semantics);
+
+ /// Returns a new [BooleanSelector] that matches only inputs matched by both
+ /// `this` and [other].
+ BooleanSelector intersection(BooleanSelector other);
+
+ /// Returns a new [BooleanSelector] that matches all inputs matched by either
+ /// `this` or [other].
+ BooleanSelector union(BooleanSelector other);
+
+ /// Throws a [FormatException] if any variables are undefined.
+ ///
+ /// The [isDefined] function should return `true` for any variables that are
+ /// considered valid, and `false` for any invalid or undefined variables.
+ void validate(bool Function(String variable) isDefined);
+}
diff --git a/pkgs/boolean_selector/lib/src/all.dart b/pkgs/boolean_selector/lib/src/all.dart
new file mode 100644
index 0000000..3424319
--- /dev/null
+++ b/pkgs/boolean_selector/lib/src/all.dart
@@ -0,0 +1,30 @@
+// 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.
+
+import '../boolean_selector.dart';
+
+/// A selector that matches all inputs.
+class All implements BooleanSelector {
+ // TODO(nweiz): Stop explicitly providing a type argument when sdk#32412 is
+ // fixed.
+ @override
+ final Iterable<String> variables = const <String>[];
+
+ const All();
+
+ @override
+ bool evaluate(bool Function(String variable) semantics) => true;
+
+ @override
+ BooleanSelector intersection(BooleanSelector other) => other;
+
+ @override
+ BooleanSelector union(BooleanSelector other) => this;
+
+ @override
+ void validate(bool Function(String variable) isDefined) {}
+
+ @override
+ String toString() => '<all>';
+}
diff --git a/pkgs/boolean_selector/lib/src/ast.dart b/pkgs/boolean_selector/lib/src/ast.dart
new file mode 100644
index 0000000..d0d8583
--- /dev/null
+++ b/pkgs/boolean_selector/lib/src/ast.dart
@@ -0,0 +1,210 @@
+// 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.
+
+import 'package:source_span/source_span.dart';
+
+import 'visitor.dart';
+
+/// The superclass of nodes in the boolean selector abstract syntax tree.
+abstract class Node {
+ /// The span indicating where this node came from.
+ ///
+ /// This is a [FileSpan] because the nodes are parsed from a single continuous
+ /// string, but the string itself isn't actually a file. It might come from a
+ /// statically-parsed annotation or from a parameter.
+ ///
+ /// This may be `null` for nodes without source information.
+ FileSpan? get span;
+
+ /// All the variables in this node, in the order they appear.
+ Iterable<String> get variables;
+
+ /// Calls the appropriate [Visitor] method on `this` and returns the result.
+ T accept<T>(Visitor<T> visitor);
+}
+
+/// A single variable.
+class VariableNode implements Node {
+ @override
+ final FileSpan? span;
+
+ /// The variable name.
+ final String name;
+
+ @override
+ Iterable<String> get variables => [name];
+
+ VariableNode(this.name, [this.span]);
+
+ @override
+ T accept<T>(Visitor<T> visitor) => visitor.visitVariable(this);
+
+ @override
+ String toString() => name;
+
+ @override
+ bool operator ==(Object other) => other is VariableNode && name == other.name;
+
+ @override
+ int get hashCode => name.hashCode;
+}
+
+/// A negation expression.
+class NotNode implements Node {
+ @override
+ final FileSpan? span;
+
+ /// The expression being negated.
+ final Node child;
+
+ @override
+ Iterable<String> get variables => child.variables;
+
+ NotNode(this.child, [this.span]);
+
+ @override
+ T accept<T>(Visitor<T> visitor) => visitor.visitNot(this);
+
+ @override
+ String toString() =>
+ child is VariableNode || child is NotNode ? '!$child' : '!($child)';
+
+ @override
+ bool operator ==(Object other) => other is NotNode && child == other.child;
+
+ @override
+ int get hashCode => ~child.hashCode;
+}
+
+/// An or expression.
+class OrNode implements Node {
+ @override
+ FileSpan? get span => _expandSafe(left.span, right.span);
+
+ /// The left-hand branch of the expression.
+ final Node left;
+
+ /// The right-hand branch of the expression.
+ final Node right;
+
+ @override
+ Iterable<String> get variables sync* {
+ yield* left.variables;
+ yield* right.variables;
+ }
+
+ OrNode(this.left, this.right);
+
+ @override
+ T accept<T>(Visitor<T> visitor) => visitor.visitOr(this);
+
+ @override
+ String toString() {
+ var string1 = left is AndNode || left is ConditionalNode ? '($left)' : left;
+ var string2 =
+ right is AndNode || right is ConditionalNode ? '($right)' : right;
+
+ return '$string1 || $string2';
+ }
+
+ @override
+ bool operator ==(Object other) =>
+ other is OrNode && left == other.left && right == other.right;
+
+ @override
+ int get hashCode => left.hashCode ^ right.hashCode;
+}
+
+/// An and expression.
+class AndNode implements Node {
+ @override
+ FileSpan? get span => _expandSafe(left.span, right.span);
+
+ /// The left-hand branch of the expression.
+ final Node left;
+
+ /// The right-hand branch of the expression.
+ final Node right;
+
+ @override
+ Iterable<String> get variables sync* {
+ yield* left.variables;
+ yield* right.variables;
+ }
+
+ AndNode(this.left, this.right);
+
+ @override
+ T accept<T>(Visitor<T> visitor) => visitor.visitAnd(this);
+
+ @override
+ String toString() {
+ var string1 = left is OrNode || left is ConditionalNode ? '($left)' : left;
+ var string2 =
+ right is OrNode || right is ConditionalNode ? '($right)' : right;
+
+ return '$string1 && $string2';
+ }
+
+ @override
+ bool operator ==(Object other) =>
+ other is AndNode && left == other.left && right == other.right;
+
+ @override
+ int get hashCode => left.hashCode ^ right.hashCode;
+}
+
+/// A ternary conditional expression.
+class ConditionalNode implements Node {
+ @override
+ FileSpan? get span => _expandSafe(condition.span, whenFalse.span);
+
+ /// The condition expression to check.
+ final Node condition;
+
+ /// The branch to run if the condition is true.
+ final Node whenTrue;
+
+ /// The branch to run if the condition is false.
+ final Node whenFalse;
+
+ @override
+ Iterable<String> get variables sync* {
+ yield* condition.variables;
+ yield* whenTrue.variables;
+ yield* whenFalse.variables;
+ }
+
+ ConditionalNode(this.condition, this.whenTrue, this.whenFalse);
+
+ @override
+ T accept<T>(Visitor<T> visitor) => visitor.visitConditional(this);
+
+ @override
+ String toString() {
+ var conditionString =
+ condition is ConditionalNode ? '($condition)' : condition;
+ var trueString = whenTrue is ConditionalNode ? '($whenTrue)' : whenTrue;
+ return '$conditionString ? $trueString : $whenFalse';
+ }
+
+ @override
+ bool operator ==(Object other) =>
+ other is ConditionalNode &&
+ condition == other.condition &&
+ whenTrue == other.whenTrue &&
+ whenFalse == other.whenFalse;
+
+ @override
+ int get hashCode =>
+ condition.hashCode ^ whenTrue.hashCode ^ whenFalse.hashCode;
+}
+
+/// Like [FileSpan.expand], except if [start] and [end] are `null` or from
+/// different files it returns `null` rather than throwing an error.
+FileSpan? _expandSafe(FileSpan? start, FileSpan? end) {
+ if (start == null || end == null) return null;
+ if (start.file != end.file) return null;
+ return start.expand(end);
+}
diff --git a/pkgs/boolean_selector/lib/src/evaluator.dart b/pkgs/boolean_selector/lib/src/evaluator.dart
new file mode 100644
index 0000000..ee57149
--- /dev/null
+++ b/pkgs/boolean_selector/lib/src/evaluator.dart
@@ -0,0 +1,33 @@
+// 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.
+
+import 'ast.dart';
+import 'visitor.dart';
+
+/// A visitor for evaluating boolean selectors against a specific set of
+/// semantics.
+class Evaluator implements Visitor<bool> {
+ final bool Function(String variable) _semantics;
+
+ Evaluator(this._semantics);
+
+ @override
+ bool visitVariable(VariableNode node) => _semantics(node.name);
+
+ @override
+ bool visitNot(NotNode node) => !node.child.accept(this);
+
+ @override
+ bool visitOr(OrNode node) =>
+ node.left.accept(this) || node.right.accept(this);
+
+ @override
+ bool visitAnd(AndNode node) =>
+ node.left.accept(this) && node.right.accept(this);
+
+ @override
+ bool visitConditional(ConditionalNode node) => node.condition.accept(this)
+ ? node.whenTrue.accept(this)
+ : node.whenFalse.accept(this);
+}
diff --git a/pkgs/boolean_selector/lib/src/impl.dart b/pkgs/boolean_selector/lib/src/impl.dart
new file mode 100644
index 0000000..cafb1a4
--- /dev/null
+++ b/pkgs/boolean_selector/lib/src/impl.dart
@@ -0,0 +1,72 @@
+// 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.
+
+import 'package:source_span/source_span.dart';
+
+import '../boolean_selector.dart';
+import 'ast.dart';
+import 'evaluator.dart';
+import 'intersection_selector.dart';
+import 'parser.dart';
+import 'union_selector.dart';
+import 'validator.dart';
+
+/// The concrete implementation of a [BooleanSelector] parsed from a string.
+///
+/// This is separate from [BooleanSelector] so that [intersection] and [union]
+/// can check to see whether they're passed a [BooleanSelectorImpl] or a
+/// different class that implements [BooleanSelector].
+class BooleanSelectorImpl implements BooleanSelector {
+ /// The parsed AST.
+ final Node _selector;
+
+ /// Parses [selector].
+ ///
+ /// This will throw a [SourceSpanFormatException] if the selector is
+ /// malformed or if it uses an undefined variable.
+ BooleanSelectorImpl.parse(String selector)
+ : _selector = Parser(selector).parse();
+
+ BooleanSelectorImpl._(this._selector);
+
+ @override
+ Iterable<String> get variables => _selector.variables;
+
+ @override
+ bool evaluate(bool Function(String variable) semantics) =>
+ _selector.accept(Evaluator(semantics));
+
+ @override
+ BooleanSelector intersection(BooleanSelector other) {
+ if (other == BooleanSelector.all) return this;
+ if (other == BooleanSelector.none) return other;
+ return other is BooleanSelectorImpl
+ ? BooleanSelectorImpl._(AndNode(_selector, other._selector))
+ : IntersectionSelector(this, other);
+ }
+
+ @override
+ BooleanSelector union(BooleanSelector other) {
+ if (other == BooleanSelector.all) return other;
+ if (other == BooleanSelector.none) return this;
+ return other is BooleanSelectorImpl
+ ? BooleanSelectorImpl._(OrNode(_selector, other._selector))
+ : UnionSelector(this, other);
+ }
+
+ @override
+ void validate(bool Function(String variable) isDefined) {
+ _selector.accept(Validator(isDefined));
+ }
+
+ @override
+ String toString() => _selector.toString();
+
+ @override
+ bool operator ==(Object other) =>
+ other is BooleanSelectorImpl && _selector == other._selector;
+
+ @override
+ int get hashCode => _selector.hashCode;
+}
diff --git a/pkgs/boolean_selector/lib/src/intersection_selector.dart b/pkgs/boolean_selector/lib/src/intersection_selector.dart
new file mode 100644
index 0000000..3bf468c
--- /dev/null
+++ b/pkgs/boolean_selector/lib/src/intersection_selector.dart
@@ -0,0 +1,49 @@
+// 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.
+
+import '../boolean_selector.dart';
+import 'union_selector.dart';
+
+/// A selector that matches inputs that both of its sub-selectors match.
+class IntersectionSelector implements BooleanSelector {
+ final BooleanSelector _selector1;
+ final BooleanSelector _selector2;
+
+ @override
+ Iterable<String> get variables sync* {
+ yield* _selector1.variables;
+ yield* _selector2.variables;
+ }
+
+ IntersectionSelector(this._selector1, this._selector2);
+
+ @override
+ bool evaluate(bool Function(String variable) semantics) =>
+ _selector1.evaluate(semantics) && _selector2.evaluate(semantics);
+
+ @override
+ BooleanSelector intersection(BooleanSelector other) =>
+ IntersectionSelector(this, other);
+
+ @override
+ BooleanSelector union(BooleanSelector other) => UnionSelector(this, other);
+
+ @override
+ void validate(bool Function(String variable) isDefined) {
+ _selector1.validate(isDefined);
+ _selector2.validate(isDefined);
+ }
+
+ @override
+ String toString() => '($_selector1) && ($_selector2)';
+
+ @override
+ bool operator ==(Object other) =>
+ other is IntersectionSelector &&
+ _selector1 == other._selector1 &&
+ _selector2 == other._selector2;
+
+ @override
+ int get hashCode => _selector1.hashCode ^ _selector2.hashCode;
+}
diff --git a/pkgs/boolean_selector/lib/src/none.dart b/pkgs/boolean_selector/lib/src/none.dart
new file mode 100644
index 0000000..08ac08a
--- /dev/null
+++ b/pkgs/boolean_selector/lib/src/none.dart
@@ -0,0 +1,28 @@
+// 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.
+
+import '../boolean_selector.dart';
+
+/// A selector that matches no inputs.
+class None implements BooleanSelector {
+ @override
+ final Iterable<String> variables = const [];
+
+ const None();
+
+ @override
+ bool evaluate(bool Function(String variable) semantics) => false;
+
+ @override
+ BooleanSelector intersection(BooleanSelector other) => this;
+
+ @override
+ BooleanSelector union(BooleanSelector other) => other;
+
+ @override
+ void validate(bool Function(String) isDefined) {}
+
+ @override
+ String toString() => '<none>';
+}
diff --git a/pkgs/boolean_selector/lib/src/parser.dart b/pkgs/boolean_selector/lib/src/parser.dart
new file mode 100644
index 0000000..368ce5e
--- /dev/null
+++ b/pkgs/boolean_selector/lib/src/parser.dart
@@ -0,0 +1,103 @@
+// 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.
+
+import 'package:source_span/source_span.dart';
+
+import 'ast.dart';
+import 'scanner.dart';
+import 'token.dart';
+
+/// A class for parsing a boolean selector.
+///
+/// Boolean selectors use a stripped-down version of the Dart expression syntax
+/// that only contains variables, parentheses, and boolean operators. Variables
+/// may also contain dashes, contrary to Dart's syntax; this allows consistency
+/// with command-line arguments.
+class Parser {
+ /// The scanner that tokenizes the selector.
+ final Scanner _scanner;
+
+ Parser(String selector) : _scanner = Scanner(selector);
+
+ /// Parses the selector.
+ ///
+ /// This must only be called once per parser.
+ Node parse() {
+ var selector = _conditional();
+
+ if (_scanner.peek().type != TokenType.endOfFile) {
+ throw SourceSpanFormatException(
+ 'Expected end of input.', _scanner.peek().span);
+ }
+
+ return selector;
+ }
+
+ /// Parses a conditional:
+ ///
+ /// conditionalExpression:
+ /// logicalOrExpression ("?" conditionalExpression ":"
+ /// conditionalExpression)?
+ Node _conditional() {
+ var condition = _or();
+ if (!_scanner.scan(TokenType.questionMark)) return condition;
+
+ var whenTrue = _conditional();
+ if (!_scanner.scan(TokenType.colon)) {
+ throw SourceSpanFormatException('Expected ":".', _scanner.peek().span);
+ }
+
+ var whenFalse = _conditional();
+ return ConditionalNode(condition, whenTrue, whenFalse);
+ }
+
+ /// Parses a logical or:
+ ///
+ /// logicalOrExpression:
+ /// logicalAndExpression ("||" logicalOrExpression)?
+ Node _or() {
+ var left = _and();
+ if (!_scanner.scan(TokenType.or)) return left;
+ return OrNode(left, _or());
+ }
+
+ /// Parses a logical and:
+ ///
+ /// logicalAndExpression:
+ /// simpleExpression ("&&" logicalAndExpression)?
+ Node _and() {
+ var left = _simpleExpression();
+ if (!_scanner.scan(TokenType.and)) return left;
+ return AndNode(left, _and());
+ }
+
+ /// Parses a simple expression:
+ ///
+ /// simpleExpression:
+ /// "!" simpleExpression |
+ /// "(" conditionalExpression ")" |
+ /// IDENTIFIER
+ Node _simpleExpression() {
+ var token = _scanner.next();
+ switch (token.type) {
+ case TokenType.not:
+ var child = _simpleExpression();
+ return NotNode(child, token.span.expand(child.span!));
+
+ case TokenType.leftParen:
+ var child = _conditional();
+ if (!_scanner.scan(TokenType.rightParen)) {
+ throw SourceSpanFormatException(
+ 'Expected ")".', _scanner.peek().span);
+ }
+ return child;
+
+ case TokenType.identifier:
+ return VariableNode((token as IdentifierToken).name, token.span);
+
+ default:
+ throw SourceSpanFormatException('Expected expression.', token.span);
+ }
+ }
+}
diff --git a/pkgs/boolean_selector/lib/src/scanner.dart b/pkgs/boolean_selector/lib/src/scanner.dart
new file mode 100644
index 0000000..25c4a11
--- /dev/null
+++ b/pkgs/boolean_selector/lib/src/scanner.dart
@@ -0,0 +1,144 @@
+// 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.
+
+import 'package:string_scanner/string_scanner.dart';
+
+import 'token.dart';
+
+/// A regular expression matching both whitespace and single-line comments.
+///
+/// This will only match if consumes at least one character.
+final _whitespaceAndSingleLineComments = RegExp(r'([ \t\n]+|//[^\n]*(\n|$))+');
+
+/// A regular expression matching the body of a multi-line comment, after `/*`
+/// but before `*/` or a nested `/*`.
+///
+/// This will only match if it consumes at least one character.
+final _multiLineCommentBody = RegExp(r'([^/*]|/[^*]|\*[^/])+');
+
+/// A regular expression matching a hyphenated identifier.
+///
+/// This is like a standard Dart identifier, except that it can also contain
+/// hyphens.
+final _hyphenatedIdentifier = RegExp(r'[a-zA-Z_-][a-zA-Z0-9_-]*');
+
+/// A scanner that converts a boolean selector string into a stream of tokens.
+class Scanner {
+ /// The underlying string scanner.
+ final SpanScanner _scanner;
+
+ /// The next token to emit.
+ Token? _next;
+
+ /// Whether the scanner has emitted a [TokenType.endOfFile] token.
+ bool _endOfFileEmitted = false;
+
+ Scanner(String selector) : _scanner = SpanScanner(selector);
+
+ /// Returns the next token that will be returned by [next].
+ ///
+ /// Throws a [StateError] if a [TokenType.endOfFile] token has already been
+ /// consumed.
+ Token peek() => _next ??= _readNext();
+
+ /// Consumes and returns the next token in the stream.
+ ///
+ /// Throws a [StateError] if a [TokenType.endOfFile] token has already been
+ /// consumed.
+ Token next() {
+ var token = _next ?? _readNext();
+ _endOfFileEmitted = token.type == TokenType.endOfFile;
+ _next = null;
+ return token;
+ }
+
+ /// If the next token matches [type], consumes it and returns `true`;
+ /// otherwise, returns `false`.
+ ///
+ /// Throws a [StateError] if a [TokenType.endOfFile] token has already been
+ /// consumed.
+ bool scan(TokenType type) {
+ if (peek().type != type) return false;
+ next();
+ return true;
+ }
+
+ /// Scan and return the next token in the stream.
+ Token _readNext() {
+ if (_endOfFileEmitted) throw StateError('No more tokens.');
+
+ _consumeWhitespace();
+ if (_scanner.isDone) {
+ return Token(TokenType.endOfFile, _scanner.spanFrom(_scanner.state));
+ }
+
+ return switch (_scanner.peekChar()) {
+ 0x28 /* ( */ => _scanOperator(TokenType.leftParen),
+ 0x29 /* ) */ => _scanOperator(TokenType.rightParen),
+ 0x3F /* ? */ => _scanOperator(TokenType.questionMark),
+ 0x3A /* : */ => _scanOperator(TokenType.colon),
+ 0x21 /* ! */ => _scanOperator(TokenType.not),
+ 0x7C /* | */ => _scanOr(),
+ 0x26 /* & */ => _scanAnd(),
+ _ => _scanIdentifier()
+ };
+ }
+
+ /// Scans a single-character operator and returns a token of type [type].
+ ///
+ /// This assumes that the caller has already verified that the next character
+ /// is correct for the given operator.
+ Token _scanOperator(TokenType type) {
+ var start = _scanner.state;
+ _scanner.readChar();
+ return Token(type, _scanner.spanFrom(start));
+ }
+
+ /// Scans a `||` operator and returns the appropriate token.
+ ///
+ /// This validates that the next two characters are `||`.
+ Token _scanOr() {
+ var start = _scanner.state;
+ _scanner.expect('||');
+ return Token(TokenType.or, _scanner.spanFrom(start));
+ }
+
+ /// Scans a `&&` operator and returns the appropriate token.
+ ///
+ /// This validates that the next two characters are `&&`.
+ Token _scanAnd() {
+ var start = _scanner.state;
+ _scanner.expect('&&');
+ return Token(TokenType.and, _scanner.spanFrom(start));
+ }
+
+ /// Scans and returns an identifier token.
+ Token _scanIdentifier() {
+ _scanner.expect(_hyphenatedIdentifier, name: 'expression');
+ return IdentifierToken(_scanner.lastMatch![0]!, _scanner.lastSpan!);
+ }
+
+ /// Consumes all whitespace and comments immediately following the cursor's
+ /// current position.
+ void _consumeWhitespace() {
+ while (_scanner.scan(_whitespaceAndSingleLineComments) ||
+ _multiLineComment()) {
+ // Do nothing.
+ }
+ }
+
+ /// Consumes a single multi-line comment.
+ ///
+ /// Returns whether or not a comment was consumed.
+ bool _multiLineComment() {
+ if (!_scanner.scan('/*')) return false;
+
+ while (_scanner.scan(_multiLineCommentBody) || _multiLineComment()) {
+ // Do nothing.
+ }
+ _scanner.expect('*/');
+
+ return true;
+ }
+}
diff --git a/pkgs/boolean_selector/lib/src/token.dart b/pkgs/boolean_selector/lib/src/token.dart
new file mode 100644
index 0000000..19e6864
--- /dev/null
+++ b/pkgs/boolean_selector/lib/src/token.dart
@@ -0,0 +1,74 @@
+// 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.
+
+import 'package:source_span/source_span.dart';
+
+/// A token in a boolean selector.
+class Token {
+ /// The type of the token.
+ final TokenType type;
+
+ /// The span indicating where this token came from.
+ ///
+ /// This is a [FileSpan] because the tokens are parsed from a single
+ /// continuous string, but the string itself isn't actually a file. It might
+ /// come from a statically-parsed annotation or from a parameter.
+ final FileSpan span;
+
+ Token(this.type, this.span);
+}
+
+/// A token representing an identifier.
+class IdentifierToken implements Token {
+ @override
+ final type = TokenType.identifier;
+ @override
+ final FileSpan span;
+
+ /// The name of the identifier.
+ final String name;
+
+ IdentifierToken(this.name, this.span);
+
+ @override
+ String toString() => 'identifier "$name"';
+}
+
+/// An enumeration of types of tokens.
+class TokenType {
+ /// A `(` character.
+ static const leftParen = TokenType._('left paren');
+
+ /// A `)` character.
+ static const rightParen = TokenType._('right paren');
+
+ /// A `||` sequence.
+ static const or = TokenType._('or');
+
+ /// A `&&` sequence.
+ static const and = TokenType._('and');
+
+ /// A `!` character.
+ static const not = TokenType._('not');
+
+ /// A `?` character.
+ static const questionMark = TokenType._('question mark');
+
+ /// A `:` character.
+ static const colon = TokenType._('colon');
+
+ /// A named identifier.
+ static const identifier = TokenType._('identifier');
+
+ /// The end of the selector.
+ static const endOfFile = TokenType._('end of file');
+
+ /// The name of the token type.
+ final String name;
+
+ const TokenType._(this.name);
+
+ @override
+ String toString() => name;
+}
diff --git a/pkgs/boolean_selector/lib/src/union_selector.dart b/pkgs/boolean_selector/lib/src/union_selector.dart
new file mode 100644
index 0000000..e355eb2
--- /dev/null
+++ b/pkgs/boolean_selector/lib/src/union_selector.dart
@@ -0,0 +1,47 @@
+// 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.
+
+import '../boolean_selector.dart';
+import 'intersection_selector.dart';
+
+/// A selector that matches inputs that either of its sub-selectors match.
+class UnionSelector implements BooleanSelector {
+ final BooleanSelector _selector1;
+ final BooleanSelector _selector2;
+
+ UnionSelector(this._selector1, this._selector2);
+
+ @override
+ List<String> get variables =>
+ _selector1.variables.toList()..addAll(_selector2.variables);
+
+ @override
+ bool evaluate(bool Function(String variable) semantics) =>
+ _selector1.evaluate(semantics) || _selector2.evaluate(semantics);
+
+ @override
+ BooleanSelector intersection(BooleanSelector other) =>
+ IntersectionSelector(this, other);
+
+ @override
+ BooleanSelector union(BooleanSelector other) => UnionSelector(this, other);
+
+ @override
+ void validate(bool Function(String variable) isDefined) {
+ _selector1.validate(isDefined);
+ _selector2.validate(isDefined);
+ }
+
+ @override
+ String toString() => '($_selector1) && ($_selector2)';
+
+ @override
+ bool operator ==(Object other) =>
+ other is UnionSelector &&
+ _selector1 == other._selector1 &&
+ _selector2 == other._selector2;
+
+ @override
+ int get hashCode => _selector1.hashCode ^ _selector2.hashCode;
+}
diff --git a/pkgs/boolean_selector/lib/src/validator.dart b/pkgs/boolean_selector/lib/src/validator.dart
new file mode 100644
index 0000000..a7f49f2
--- /dev/null
+++ b/pkgs/boolean_selector/lib/src/validator.dart
@@ -0,0 +1,23 @@
+// 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.
+
+import 'package:source_span/source_span.dart';
+
+import 'ast.dart';
+import 'visitor.dart';
+
+typedef _IsDefined = bool Function(String variable);
+
+/// An AST visitor that ensures that all variables are valid.
+class Validator extends RecursiveVisitor {
+ final _IsDefined _isDefined;
+
+ Validator(this._isDefined);
+
+ @override
+ void visitVariable(VariableNode node) {
+ if (_isDefined(node.name)) return;
+ throw SourceSpanFormatException('Undefined variable.', node.span);
+ }
+}
diff --git a/pkgs/boolean_selector/lib/src/visitor.dart b/pkgs/boolean_selector/lib/src/visitor.dart
new file mode 100644
index 0000000..c6808c2
--- /dev/null
+++ b/pkgs/boolean_selector/lib/src/visitor.dart
@@ -0,0 +1,49 @@
+// 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.
+
+import 'ast.dart';
+
+/// The interface for visitors of the boolean selector AST.
+abstract class Visitor<T> {
+ T visitVariable(VariableNode node);
+ T visitNot(NotNode node);
+ T visitOr(OrNode node);
+ T visitAnd(AndNode node);
+ T visitConditional(ConditionalNode node);
+}
+
+/// An abstract superclass for side-effect-based visitors.
+///
+/// The default implementations of this visitor's methods just traverse the AST
+/// and do nothing with it.
+abstract class RecursiveVisitor implements Visitor<void> {
+ const RecursiveVisitor();
+
+ @override
+ void visitVariable(VariableNode node) {}
+
+ @override
+ void visitNot(NotNode node) {
+ node.child.accept(this);
+ }
+
+ @override
+ void visitOr(OrNode node) {
+ node.left.accept(this);
+ node.right.accept(this);
+ }
+
+ @override
+ void visitAnd(AndNode node) {
+ node.left.accept(this);
+ node.right.accept(this);
+ }
+
+ @override
+ void visitConditional(ConditionalNode node) {
+ node.condition.accept(this);
+ node.whenTrue.accept(this);
+ node.whenFalse.accept(this);
+ }
+}
diff --git a/pkgs/boolean_selector/pubspec.yaml b/pkgs/boolean_selector/pubspec.yaml
new file mode 100644
index 0000000..89e49cc
--- /dev/null
+++ b/pkgs/boolean_selector/pubspec.yaml
@@ -0,0 +1,18 @@
+name: boolean_selector
+version: 2.1.2
+description: >-
+ A flexible syntax for boolean expressions, based on a simplified version of
+ Dart's expression syntax.
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/boolean_selector
+issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aboolean_selector
+
+environment:
+ sdk: ^3.1.0
+
+dependencies:
+ source_span: ^1.8.0
+ string_scanner: ^1.1.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ test: ^1.16.0
diff --git a/pkgs/boolean_selector/test/equality_test.dart b/pkgs/boolean_selector/test/equality_test.dart
new file mode 100644
index 0000000..c5ccb64
--- /dev/null
+++ b/pkgs/boolean_selector/test/equality_test.dart
@@ -0,0 +1,51 @@
+// 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.
+
+import 'package:boolean_selector/boolean_selector.dart';
+import 'package:test/test.dart';
+
+void main() {
+ test('variable', () {
+ _expectEqualsSelf('foo');
+ });
+
+ test('not', () {
+ _expectEqualsSelf('!foo');
+ });
+
+ test('or', () {
+ _expectEqualsSelf('foo || bar');
+ });
+
+ test('and', () {
+ _expectEqualsSelf('foo && bar');
+ });
+
+ test('conditional', () {
+ _expectEqualsSelf('foo ? bar : baz');
+ });
+
+ test('all', () {
+ expect(BooleanSelector.all, equals(BooleanSelector.all));
+ });
+
+ test('none', () {
+ expect(BooleanSelector.none, equals(BooleanSelector.none));
+ });
+
+ test("redundant parens don't matter", () {
+ expect(BooleanSelector.parse('foo && (bar && baz)'),
+ equals(BooleanSelector.parse('foo && (bar && baz)')));
+ });
+
+ test('meaningful parens do matter', () {
+ expect(BooleanSelector.parse('(foo && bar) || baz'),
+ equals(BooleanSelector.parse('foo && bar || baz')));
+ });
+}
+
+void _expectEqualsSelf(String selector) {
+ expect(
+ BooleanSelector.parse(selector), equals(BooleanSelector.parse(selector)));
+}
diff --git a/pkgs/boolean_selector/test/evaluate_test.dart b/pkgs/boolean_selector/test/evaluate_test.dart
new file mode 100644
index 0000000..2d94906
--- /dev/null
+++ b/pkgs/boolean_selector/test/evaluate_test.dart
@@ -0,0 +1,59 @@
+// 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.
+
+import 'package:boolean_selector/boolean_selector.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('operator:', () {
+ test('conditional', () {
+ _expectEval('true ? true : false', true);
+ _expectEval('true ? false : true', false);
+ _expectEval('false ? true : false', false);
+ _expectEval('false ? false : true', true);
+ });
+
+ test('or', () {
+ _expectEval('true || true', true);
+ _expectEval('true || false', true);
+ _expectEval('false || true', true);
+ _expectEval('false || false', false);
+ });
+
+ test('and', () {
+ _expectEval('true && true', true);
+ _expectEval('true && false', false);
+ _expectEval('false && true', false);
+ _expectEval('false && false', false);
+ });
+
+ test('not', () {
+ _expectEval('!true', false);
+ _expectEval('!false', true);
+ });
+ });
+
+ test('with a semantics function', () {
+ _expectEval('foo', false, semantics: (variable) => variable.contains('a'));
+ _expectEval('bar', true, semantics: (variable) => variable.contains('a'));
+ _expectEval('baz', true, semantics: (variable) => variable.contains('a'));
+ });
+}
+
+/// Asserts that [expression] evaluates to [result] against [semantics].
+///
+/// By default, "true" is true and all other variables are "false".
+void _expectEval(String expression, bool result,
+ {bool Function(String variable)? semantics}) {
+ expect(_eval(expression, semantics: semantics), equals(result),
+ reason: 'Expected "$expression" to evaluate to $result.');
+}
+
+/// Returns the result of evaluating [expression] on [semantics].
+///
+/// By default, "true" is true and all other variables are "false".
+bool _eval(String expression, {bool Function(String variable)? semantics}) {
+ var selector = BooleanSelector.parse(expression);
+ return selector.evaluate(semantics ?? (v) => v == 'true');
+}
diff --git a/pkgs/boolean_selector/test/parser_test.dart b/pkgs/boolean_selector/test/parser_test.dart
new file mode 100644
index 0000000..ba55e70
--- /dev/null
+++ b/pkgs/boolean_selector/test/parser_test.dart
@@ -0,0 +1,298 @@
+// 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.
+
+import 'package:boolean_selector/src/ast.dart';
+import 'package:boolean_selector/src/parser.dart';
+import 'package:test/test.dart';
+
+/// A matcher that asserts that a value is a [ConditionalNode].
+const _isConditionalNode = TypeMatcher<ConditionalNode>();
+
+/// A matcher that asserts that a value is an [OrNode].
+const _isOrNode = TypeMatcher<OrNode>();
+
+/// A matcher that asserts that a value is an [AndNode].
+const _isAndNode = TypeMatcher<AndNode>();
+
+/// A matcher that asserts that a value is a [NotNode].
+const _isNotNode = TypeMatcher<NotNode>();
+
+void main() {
+ group('parses a conditional expression', () {
+ test('with identifiers', () {
+ var node = _parse(' a ? b : c ');
+ expect(node.toString(), equals('a ? b : c'));
+
+ expect(node.span, isNotNull);
+ expect(node.span!.text, equals('a ? b : c'));
+ expect(node.span!.start.offset, equals(2));
+ expect(node.span!.end.offset, equals(11));
+ });
+
+ test('with nested ors', () {
+ // Should parse as "(a || b) ? (c || d) : (e || f)".
+ // Should not parse as "a || (b ? (c || d) : (e || f))".
+ // Should not parse as "((a || b) ? (c || d) : e) || f".
+ // Should not parse as "a || (b ? (c || d) : e) || f".
+ _expectToString('a || b ? c || d : e || f', 'a || b ? c || d : e || f');
+ });
+
+ test('with a conditional expression as branch 1', () {
+ // Should parse as "a ? (b ? c : d) : e".
+ var node = _parse('a ? b ? c : d : e');
+ expect(node, _isConditionalNode);
+ node as ConditionalNode; // promote node
+
+ expect(node.condition, _isVar('a'));
+ expect(node.whenFalse, _isVar('e'));
+
+ expect(node.whenTrue, _isConditionalNode);
+ var whenTrue = node.whenTrue as ConditionalNode;
+ expect(whenTrue.condition, _isVar('b'));
+ expect(whenTrue.whenTrue, _isVar('c'));
+ expect(whenTrue.whenFalse, _isVar('d'));
+ });
+
+ test('with a conditional expression as branch 2', () {
+ // Should parse as "a ? b : (c ? d : e)".
+ // Should not parse as "(a ? b : c) ? d : e".
+ var node = _parse('a ? b : c ? d : e');
+ expect(node, _isConditionalNode);
+ node as ConditionalNode; //promote node
+
+ expect(node.condition, _isVar('a'));
+ expect(node.whenTrue, _isVar('b'));
+
+ expect(node.whenFalse, _isConditionalNode);
+ var whenFalse = node.whenFalse as ConditionalNode;
+ expect(whenFalse.condition, _isVar('c'));
+ expect(whenFalse.whenTrue, _isVar('d'));
+ expect(whenFalse.whenFalse, _isVar('e'));
+ });
+
+ group('which must have', () {
+ test('an expression after the ?', () {
+ expect(() => _parse('a ?'), throwsFormatException);
+ expect(() => _parse('a ? && b'), throwsFormatException);
+ });
+
+ test('a :', () {
+ expect(() => _parse('a ? b'), throwsFormatException);
+ expect(() => _parse('a ? b && c'), throwsFormatException);
+ });
+
+ test('an expression after the :', () {
+ expect(() => _parse('a ? b :'), throwsFormatException);
+ expect(() => _parse('a ? b : && c'), throwsFormatException);
+ });
+ });
+ });
+
+ group('parses an or expression', () {
+ test('with identifiers', () {
+ var node = _parse(' a || b ');
+ expect(node, _isOrNode);
+ node as OrNode; //promote node
+
+ expect(node.left, _isVar('a'));
+ expect(node.right, _isVar('b'));
+
+ expect(node.span, isNotNull);
+ expect(node.span!.text, equals('a || b'));
+ expect(node.span!.start.offset, equals(2));
+ expect(node.span!.end.offset, equals(8));
+ });
+
+ test('with nested ands', () {
+ // Should parse as "(a && b) || (c && d)".
+ // Should not parse as "a && (b || c) && d".
+ var node = _parse('a && b || c && d');
+ expect(node, _isOrNode);
+ node as OrNode; //promote node
+
+ expect(node.left, _isAndNode);
+ var left = node.left as AndNode;
+ expect(left.left, _isVar('a'));
+ expect(left.right, _isVar('b'));
+
+ expect(node.right, _isAndNode);
+ var right = node.right as AndNode;
+ expect(right.left, _isVar('c'));
+ expect(right.right, _isVar('d'));
+ });
+
+ test('with trailing ors', () {
+ // Should parse as "a || (b || (c || d))", although it doesn't affect the
+ // semantics.
+ var node = _parse('a || b || c || d');
+
+ for (var variable in ['a', 'b', 'c']) {
+ expect(node, _isOrNode);
+ node as OrNode; //promote node
+
+ expect(node.left, _isVar(variable));
+ node = node.right;
+ }
+ expect(node, _isVar('d'));
+ });
+
+ test('which must have an expression after the ||', () {
+ expect(() => _parse('a ||'), throwsFormatException);
+ expect(() => _parse('a || && b'), throwsFormatException);
+ });
+ });
+
+ group('parses an and expression', () {
+ test('with identifiers', () {
+ var node = _parse(' a && b ');
+ expect(node, _isAndNode);
+ node as AndNode; //promote node
+
+ expect(node.left, _isVar('a'));
+ expect(node.right, _isVar('b'));
+
+ expect(node.span, isNotNull);
+ expect(node.span!.text, equals('a && b'));
+ expect(node.span!.start.offset, equals(2));
+ expect(node.span!.end.offset, equals(8));
+ });
+
+ test('with nested nots', () {
+ // Should parse as "(!a) && (!b)", obviously.
+ // Should not parse as "!(a && (!b))".
+ var node = _parse('!a && !b');
+ expect(node, _isAndNode);
+ node as AndNode; //promote node
+
+ expect(node.left, _isNotNode);
+ var left = node.left as NotNode;
+ expect(left.child, _isVar('a'));
+
+ expect(node.right, _isNotNode);
+ var right = node.right as NotNode;
+ expect(right.child, _isVar('b'));
+ });
+
+ test('with trailing ands', () {
+ // Should parse as "a && (b && (c && d))", although it doesn't affect the
+ // semantics since .
+ var node = _parse('a && b && c && d');
+
+ for (var variable in ['a', 'b', 'c']) {
+ expect(node, _isAndNode);
+ node as AndNode; //promote node
+
+ expect(node.left, _isVar(variable));
+ node = node.right;
+ }
+ expect(node, _isVar('d'));
+ });
+
+ test('which must have an expression after the &&', () {
+ expect(() => _parse('a &&'), throwsFormatException);
+ expect(() => _parse('a && && b'), throwsFormatException);
+ });
+ });
+
+ group('parses a not expression', () {
+ test('with an identifier', () {
+ var node = _parse(' ! a ');
+ expect(node, _isNotNode);
+ node as NotNode; //promote node
+ expect(node.child, _isVar('a'));
+
+ expect(node.span, isNotNull);
+ expect(node.span!.text, equals('! a'));
+ expect(node.span!.start.offset, equals(2));
+ expect(node.span!.end.offset, equals(5));
+ });
+
+ test('with a parenthesized expression', () {
+ var node = _parse('!(a || b)');
+ expect(node, _isNotNode);
+ node as NotNode; //promote node
+
+ expect(node.child, _isOrNode);
+ var child = node.child as OrNode;
+ expect(child.left, _isVar('a'));
+ expect(child.right, _isVar('b'));
+ });
+
+ test('with a nested not', () {
+ var node = _parse('!!a');
+ expect(node, _isNotNode);
+ node as NotNode; //promote node
+
+ expect(node.child, _isNotNode);
+ var child = node.child as NotNode;
+ expect(child.child, _isVar('a'));
+ });
+
+ test('which must have an expression after the !', () {
+ expect(() => _parse('!'), throwsFormatException);
+ expect(() => _parse('! && a'), throwsFormatException);
+ });
+ });
+
+ group('parses a parenthesized expression', () {
+ test('with an identifier', () {
+ var node = _parse('(a)');
+ expect(node, _isVar('a'));
+ });
+
+ test('controls precedence', () {
+ // Without parentheses, this would parse as "(a || b) ? c : d".
+ var node = _parse('a || (b ? c : d)');
+
+ expect(node, _isOrNode);
+ node as OrNode; //promote node
+
+ expect(node.left, _isVar('a'));
+
+ expect(node.right, _isConditionalNode);
+ var right = node.right as ConditionalNode;
+ expect(right.condition, _isVar('b'));
+ expect(right.whenTrue, _isVar('c'));
+ expect(right.whenFalse, _isVar('d'));
+ });
+
+ group('which must have', () {
+ test('an expression within the ()', () {
+ expect(() => _parse('()'), throwsFormatException);
+ expect(() => _parse('( && a )'), throwsFormatException);
+ });
+
+ test('a matching )', () {
+ expect(() => _parse('( a'), throwsFormatException);
+ });
+ });
+ });
+
+ group('disallows', () {
+ test('an empty selector', () {
+ expect(() => _parse(''), throwsFormatException);
+ });
+
+ test('too many expressions', () {
+ expect(() => _parse('a b'), throwsFormatException);
+ });
+ });
+}
+
+/// Parses [selector] and returns its root node.
+Node _parse(String selector) => Parser(selector).parse();
+
+/// A matcher that asserts that a value is a [VariableNode] with the given
+/// [name].
+Matcher _isVar(String name) => predicate(
+ (dynamic value) => value is VariableNode && value.name == name,
+ 'is a variable named "$name"');
+
+void _expectToString(String selector, [String? result]) {
+ result ??= selector;
+ expect(_toString(selector), equals(result),
+ reason: 'Expected toString of "$selector" to be "$result".');
+}
+
+String _toString(String selector) => Parser(selector).parse().toString();
diff --git a/pkgs/boolean_selector/test/scanner_test.dart b/pkgs/boolean_selector/test/scanner_test.dart
new file mode 100644
index 0000000..7ad78e2
--- /dev/null
+++ b/pkgs/boolean_selector/test/scanner_test.dart
@@ -0,0 +1,279 @@
+// 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.
+
+import 'package:boolean_selector/src/scanner.dart';
+import 'package:boolean_selector/src/token.dart';
+import 'package:test/test.dart';
+
+/// A matcher that asserts that a value is a [IdentifierToken].
+const _isIdentifierToken = TypeMatcher<IdentifierToken>();
+
+void main() {
+ group('peek()', () {
+ test('returns the next token without consuming it', () {
+ var scanner = Scanner('( )');
+ expect(scanner.peek().type, equals(TokenType.leftParen));
+ expect(scanner.peek().type, equals(TokenType.leftParen));
+ expect(scanner.peek().type, equals(TokenType.leftParen));
+ });
+
+ test('returns an end-of-file token at the end of a file', () {
+ var scanner = Scanner('( )');
+ scanner.next();
+ scanner.next();
+
+ var token = scanner.peek();
+ expect(token.type, equals(TokenType.endOfFile));
+ expect(token.span.start.offset, equals(3));
+ expect(token.span.end.offset, equals(3));
+ });
+
+ test('throws a StateError if called after end-of-file was consumed', () {
+ var scanner = Scanner('( )');
+ scanner.next();
+ scanner.next();
+ scanner.next();
+ expect(() => scanner.peek(), throwsStateError);
+ });
+ });
+
+ group('next()', () {
+ test('consumes and returns the next token', () {
+ var scanner = Scanner('( )');
+ expect(scanner.next().type, equals(TokenType.leftParen));
+ expect(scanner.peek().type, equals(TokenType.rightParen));
+ expect(scanner.next().type, equals(TokenType.rightParen));
+ });
+
+ test('returns an end-of-file token at the end of a file', () {
+ var scanner = Scanner('( )');
+ scanner.next();
+ scanner.next();
+
+ var token = scanner.next();
+ expect(token.type, equals(TokenType.endOfFile));
+ expect(token.span.start.offset, equals(3));
+ expect(token.span.end.offset, equals(3));
+ });
+
+ test('throws a StateError if called after end-of-file was consumed', () {
+ var scanner = Scanner('( )');
+ scanner.next();
+ scanner.next();
+ scanner.next();
+ expect(() => scanner.next(), throwsStateError);
+ });
+ });
+
+ group('scan()', () {
+ test('consumes a matching token and returns true', () {
+ var scanner = Scanner('( )');
+ expect(scanner.scan(TokenType.leftParen), isTrue);
+ expect(scanner.peek().type, equals(TokenType.rightParen));
+ });
+
+ test("doesn't consume a matching token and returns false", () {
+ var scanner = Scanner('( )');
+ expect(scanner.scan(TokenType.questionMark), isFalse);
+ expect(scanner.peek().type, equals(TokenType.leftParen));
+ });
+
+ test('throws a StateError called after end-of-file was consumed', () {
+ var scanner = Scanner('( )');
+ scanner.next();
+ scanner.next();
+ scanner.next();
+ expect(() => scanner.scan(TokenType.endOfFile), throwsStateError);
+ });
+ });
+
+ group('scans a simple token:', () {
+ test('left paren', () => _expectSimpleScan('(', TokenType.leftParen));
+ test('right paren', () => _expectSimpleScan(')', TokenType.rightParen));
+ test('or', () => _expectSimpleScan('||', TokenType.or));
+ test('and', () => _expectSimpleScan('&&', TokenType.and));
+ test('not', () => _expectSimpleScan('!', TokenType.not));
+ test('question mark', () => _expectSimpleScan('?', TokenType.questionMark));
+ test('colon', () => _expectSimpleScan(':', TokenType.colon));
+ });
+
+ group('scans an identifier that', () {
+ test('is simple', () {
+ var token = _scan(' foo ');
+ expect(token, _isIdentifierToken);
+ token as IdentifierToken; // promote token
+
+ expect(token.name, equals('foo'));
+ expect(token.span.text, equals('foo'));
+ expect(token.span.start.offset, equals(3));
+ expect(token.span.end.offset, equals(6));
+ });
+
+ test('is a single character', () {
+ var token = _scan('f');
+ expect(token, _isIdentifierToken);
+ expect((token as IdentifierToken).name, equals('f'));
+ });
+
+ test('has a leading underscore', () {
+ var token = _scan('_foo');
+ expect(token, _isIdentifierToken);
+ expect((token as IdentifierToken).name, equals('_foo'));
+ });
+
+ test('has a leading dash', () {
+ var token = _scan('-foo');
+ expect(token, _isIdentifierToken);
+ expect((token as IdentifierToken).name, equals('-foo'));
+ });
+
+ test('contains an underscore', () {
+ var token = _scan('foo_bar');
+ expect(token, _isIdentifierToken);
+ expect((token as IdentifierToken).name, equals('foo_bar'));
+ });
+
+ test('contains a dash', () {
+ var token = _scan('foo-bar');
+ expect(token, _isIdentifierToken);
+ expect((token as IdentifierToken).name, equals('foo-bar'));
+ });
+
+ test('is capitalized', () {
+ var token = _scan('FOO');
+ expect(token, _isIdentifierToken);
+ expect((token as IdentifierToken).name, equals('FOO'));
+ });
+
+ test('contains numbers', () {
+ var token = _scan('foo123');
+ expect(token, _isIdentifierToken);
+ expect((token as IdentifierToken).name, equals('foo123'));
+ });
+ });
+
+ test('scans an empty selector', () {
+ expect(_scan('').type, equals(TokenType.endOfFile));
+ });
+
+ test('scans multiple tokens', () {
+ var scanner = Scanner('(foo && bar)');
+
+ var token = scanner.next();
+ expect(token.type, equals(TokenType.leftParen));
+ expect(token.span.start.offset, equals(0));
+ expect(token.span.end.offset, equals(1));
+
+ token = scanner.next();
+ expect(token.type, equals(TokenType.identifier));
+ expect((token as IdentifierToken).name, equals('foo'));
+ expect(token.span.start.offset, equals(1));
+ expect(token.span.end.offset, equals(4));
+
+ token = scanner.next();
+ expect(token.type, equals(TokenType.and));
+ expect(token.span.start.offset, equals(5));
+ expect(token.span.end.offset, equals(7));
+
+ token = scanner.next();
+ expect(token.type, equals(TokenType.identifier));
+ expect((token as IdentifierToken).name, equals('bar'));
+ expect(token.span.start.offset, equals(8));
+ expect(token.span.end.offset, equals(11));
+
+ token = scanner.next();
+ expect(token.type, equals(TokenType.rightParen));
+ expect(token.span.start.offset, equals(11));
+ expect(token.span.end.offset, equals(12));
+
+ token = scanner.next();
+ expect(token.type, equals(TokenType.endOfFile));
+ expect(token.span.start.offset, equals(12));
+ expect(token.span.end.offset, equals(12));
+ });
+
+ group('ignores', () {
+ test('a single-line comment', () {
+ var scanner = Scanner('( // &&\n// ||\n)');
+ expect(scanner.next().type, equals(TokenType.leftParen));
+ expect(scanner.next().type, equals(TokenType.rightParen));
+ expect(scanner.next().type, equals(TokenType.endOfFile));
+ });
+
+ test('a single-line comment without a trailing newline', () {
+ var scanner = Scanner('( // &&');
+ expect(scanner.next().type, equals(TokenType.leftParen));
+ expect(scanner.next().type, equals(TokenType.endOfFile));
+ });
+
+ test('a multi-line comment', () {
+ var scanner = Scanner('( /* && * /\n|| */\n)');
+ expect(scanner.next().type, equals(TokenType.leftParen));
+ expect(scanner.next().type, equals(TokenType.rightParen));
+ expect(scanner.next().type, equals(TokenType.endOfFile));
+ });
+
+ test('a multi-line nested comment', () {
+ var scanner = Scanner('(/* && /* ? /* || */ : */ ! */)');
+ expect(scanner.next().type, equals(TokenType.leftParen));
+ expect(scanner.next().type, equals(TokenType.rightParen));
+ expect(scanner.next().type, equals(TokenType.endOfFile));
+ });
+
+ test("Dart's notion of whitespace", () {
+ var scanner = Scanner('( \t \n)');
+ expect(scanner.next().type, equals(TokenType.leftParen));
+ expect(scanner.next().type, equals(TokenType.rightParen));
+ expect(scanner.next().type, equals(TokenType.endOfFile));
+ });
+ });
+
+ group('disallows', () {
+ test('a single |', () {
+ expect(() => _scan('|'), throwsFormatException);
+ });
+
+ test('"| |"', () {
+ expect(() => _scan('| |'), throwsFormatException);
+ });
+
+ test('a single &', () {
+ expect(() => _scan('&'), throwsFormatException);
+ });
+
+ test('"& &"', () {
+ expect(() => _scan('& &'), throwsFormatException);
+ });
+
+ test('an unknown operator', () {
+ expect(() => _scan('=='), throwsFormatException);
+ });
+
+ test('unicode', () {
+ expect(() => _scan('öh'), throwsFormatException);
+ });
+
+ test('an unclosed multi-line comment', () {
+ expect(() => _scan('/*'), throwsFormatException);
+ });
+
+ test('an unopened multi-line comment', () {
+ expect(() => _scan('*/'), throwsFormatException);
+ });
+ });
+}
+
+/// Asserts that the first token scanned from [selector] has type [type],
+/// and that that token's span is exactly [selector].
+void _expectSimpleScan(String selector, TokenType type) {
+ // Complicate the selector to test that the span covers it correctly.
+ var token = _scan(' $selector ');
+ expect(token.type, equals(type));
+ expect(token.span.text, equals(selector));
+ expect(token.span.start.offset, equals(3));
+ expect(token.span.end.offset, equals(3 + selector.length));
+}
+
+/// Scans a single token from [selector].
+Token _scan(String selector) => Scanner(selector).next();
diff --git a/pkgs/boolean_selector/test/to_string_test.dart b/pkgs/boolean_selector/test/to_string_test.dart
new file mode 100644
index 0000000..971e5b9
--- /dev/null
+++ b/pkgs/boolean_selector/test/to_string_test.dart
@@ -0,0 +1,84 @@
+// 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.
+
+import 'package:boolean_selector/boolean_selector.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('toString() for', () {
+ test('a variable is its name', () {
+ _expectToString('foo');
+ _expectToString('a-b');
+ });
+
+ group('not', () {
+ test("doesn't parenthesize a variable", () => _expectToString('!a'));
+ test("doesn't parenthesize a nested not", () => _expectToString('!!a'));
+ test('parenthesizes an or', () => _expectToString('!(a || b)'));
+ test('parenthesizes an and', () => _expectToString('!(a && b)'));
+ test('parenthesizes a condition', () => _expectToString('!(a ? b : c)'));
+ });
+
+ group('or', () {
+ test("doesn't parenthesize variables", () => _expectToString('a || b'));
+ test("doesn't parenthesize nots", () => _expectToString('!a || !b'));
+
+ test("doesn't parenthesize ors", () {
+ _expectToString('a || b || c || d');
+ _expectToString('((a || b) || c) || d', 'a || b || c || d');
+ });
+
+ test('parenthesizes ands',
+ () => _expectToString('a && b || c && d', '(a && b) || (c && d)'));
+
+ test('parenthesizes conditions',
+ () => _expectToString('(a ? b : c) || (e ? f : g)'));
+ });
+
+ group('and', () {
+ test("doesn't parenthesize variables", () => _expectToString('a && b'));
+ test("doesn't parenthesize nots", () => _expectToString('!a && !b'));
+
+ test(
+ 'parenthesizes ors',
+ () =>
+ _expectToString('(a || b) && (c || d)', '(a || b) && (c || d)'));
+
+ test("doesn't parenthesize ands", () {
+ _expectToString('a && b && c && d');
+ _expectToString('((a && b) && c) && d', 'a && b && c && d');
+ });
+
+ test('parenthesizes conditions',
+ () => _expectToString('(a ? b : c) && (e ? f : g)'));
+ });
+
+ group('conditional', () {
+ test(
+ "doesn't parenthesize variables", () => _expectToString('a ? b : c'));
+
+ test("doesn't parenthesize nots", () => _expectToString('!a ? !b : !c'));
+
+ test("doesn't parenthesize ors",
+ () => _expectToString('a || b ? c || d : e || f'));
+
+ test("doesn't parenthesize ands",
+ () => _expectToString('a && b ? c && d : e && f'));
+
+ test('parenthesizes non-trailing conditions', () {
+ _expectToString('(a ? b : c) ? (e ? f : g) : h ? i : j');
+ _expectToString('(a ? b : c) ? (e ? f : g) : (h ? i : j)',
+ '(a ? b : c) ? (e ? f : g) : h ? i : j');
+ });
+ });
+ });
+}
+
+void _expectToString(String selector, [String? result]) {
+ result ??= selector;
+ expect(_toString(selector), equals(result),
+ reason: 'Expected toString of "$selector" to be "$result".');
+}
+
+String _toString(String selector) => BooleanSelector.parse(selector).toString();
diff --git a/pkgs/boolean_selector/test/validate_test.dart b/pkgs/boolean_selector/test/validate_test.dart
new file mode 100644
index 0000000..ec5b7eb
--- /dev/null
+++ b/pkgs/boolean_selector/test/validate_test.dart
@@ -0,0 +1,20 @@
+// 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.
+
+import 'package:boolean_selector/boolean_selector.dart';
+import 'package:test/test.dart';
+
+var _selector = BooleanSelector.parse('foo && bar && baz');
+
+void main() {
+ test('throws if any variables are undefined', () {
+ expect(() => _selector.validate((variable) => variable == 'bar'),
+ throwsFormatException);
+ });
+
+ test("doesn't throw if all variables are defined", () {
+ // Should not throw.
+ _selector.validate((variable) => true);
+ });
+}
diff --git a/pkgs/boolean_selector/test/variables_test.dart b/pkgs/boolean_selector/test/variables_test.dart
new file mode 100644
index 0000000..5331409
--- /dev/null
+++ b/pkgs/boolean_selector/test/variables_test.dart
@@ -0,0 +1,43 @@
+// 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.
+
+import 'package:boolean_selector/boolean_selector.dart';
+import 'package:test/test.dart';
+
+void main() {
+ test('a variable reports itself', () {
+ expect(BooleanSelector.parse('foo').variables, equals(['foo']));
+ });
+
+ test('a negation reports its contents', () {
+ expect(BooleanSelector.parse('!foo').variables, equals(['foo']));
+ });
+
+ test('a parenthesized expression reports its contents', () {
+ expect(BooleanSelector.parse('(foo)').variables, equals(['foo']));
+ });
+
+ test('an or reports its contents', () {
+ expect(
+ BooleanSelector.parse('foo || bar').variables, equals(['foo', 'bar']));
+ });
+
+ test('an and reports its contents', () {
+ expect(
+ BooleanSelector.parse('foo && bar').variables, equals(['foo', 'bar']));
+ });
+
+ test('a conditional reports its contents', () {
+ expect(BooleanSelector.parse('foo ? bar : baz').variables,
+ equals(['foo', 'bar', 'baz']));
+ });
+
+ test('BooleanSelector.all reports no variables', () {
+ expect(BooleanSelector.all.variables, isEmpty);
+ });
+
+ test('BooleanSelector.none reports no variables', () {
+ expect(BooleanSelector.none.variables, isEmpty);
+ });
+}
diff --git a/pkgs/browser_launcher/.gitignore b/pkgs/browser_launcher/.gitignore
new file mode 100644
index 0000000..ec8eae3
--- /dev/null
+++ b/pkgs/browser_launcher/.gitignore
@@ -0,0 +1,4 @@
+# Don’t commit the following directories created by pub.
+.dart_tool/
+.packages
+pubspec.lock
diff --git a/pkgs/browser_launcher/AUTHORS b/pkgs/browser_launcher/AUTHORS
new file mode 100644
index 0000000..e8063a8
--- /dev/null
+++ b/pkgs/browser_launcher/AUTHORS
@@ -0,0 +1,6 @@
+# Below is a list of people and organizations that have contributed
+# to the project. Names should be added to the list like so:
+#
+# Name/Organization <email address>
+
+Google Inc.
diff --git a/pkgs/browser_launcher/CHANGELOG.md b/pkgs/browser_launcher/CHANGELOG.md
new file mode 100644
index 0000000..f375056
--- /dev/null
+++ b/pkgs/browser_launcher/CHANGELOG.md
@@ -0,0 +1,71 @@
+## 1.1.3
+
+- Move to `dart-lang/tools` monorepo.
+
+## 1.1.2
+
+- Require Dart 3.4
+- Log errors from chrome
+- Allow tests to detect headless-only environment (for CI).
+- Add extra flags that may help disable additional throttling in background tabs
+- Add `--use-mock-keychain` flag to avoid blocking dialog on MacOS.
+
+## 1.1.1
+
+- Populate the pubspec `repository` field.
+
+## 1.1.0
+
+- Add optional `signIn` argument to `startWithDebugPort`.
+ To be used together with `user-data-dir` to start a Chrome
+ window signed in to the default profile with extensions enabled.
+- Enable the `avoid_dynamic_calls` lint.
+
+## 1.0.0
+
+- Migrate to null-safety.
+
+## 0.1.10
+
+- Support `webkit_inspection_protocol` version `^1.0.0`.
+
+## 0.1.9
+
+- Add support for Chrome executables in `CHROME_PATH`.
+
+## 0.1.8
+
+- Log `STDERR` on Chrome launch failure.
+
+## 0.1.7
+
+- Widen the dependency range on `package:webkit_inspection_protocol`.
+
+## 0.1.6
+
+- Update lower Dart SDK requirement to `2.2.0`.
+- Update the dependency range on `package:webkit_inspection_protocol`.
+
+## 0.1.5
+
+- Add a parameter to use a specified user-data-dir instead of a system temp.
+
+## 0.1.4
+
+- Start Chrome maximized.
+
+## 0.1.3
+
+- widen the version constraint on `package:webkit_inspection_protocol`
+
+## 0.1.2
+
+- lower min sdk version to match Flutter stable
+
+## 0.1.1
+
+- added example
+
+## 0.1.0
+
+- initial release
diff --git a/pkgs/browser_launcher/LICENSE b/pkgs/browser_launcher/LICENSE
new file mode 100644
index 0000000..7670007
--- /dev/null
+++ b/pkgs/browser_launcher/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2019, 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.
diff --git a/pkgs/browser_launcher/README.md b/pkgs/browser_launcher/README.md
new file mode 100644
index 0000000..f6398b6
--- /dev/null
+++ b/pkgs/browser_launcher/README.md
@@ -0,0 +1,8 @@
+[](https://github.com/dart-lang/tools/actions/workflows/browser_launcher.yaml)
+[](https://pub.dev/packages/browser_launcher)
+[](https://pub.dev/packages/browser_launcher/publisher)
+
+Provides a standardized way to launch web browsers.
+
+Currently, Chrome is the only supported browser; support for other browsers may
+be added in the future.
diff --git a/pkgs/browser_launcher/analysis_options.yaml b/pkgs/browser_launcher/analysis_options.yaml
new file mode 100644
index 0000000..9125368
--- /dev/null
+++ b/pkgs/browser_launcher/analysis_options.yaml
@@ -0,0 +1,32 @@
+# https://dart.dev/guides/language/analysis-options
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-casts: true
+ strict-inference: true
+ strict-raw-types: true
+
+linter:
+ rules:
+ - avoid_bool_literals_in_conditional_expressions
+ - avoid_classes_with_only_static_members
+ - avoid_private_typedef_functions
+ - avoid_redundant_argument_values
+ - avoid_returning_this
+ - avoid_unused_constructor_parameters
+ - avoid_void_async
+ - cancel_subscriptions
+ - join_return_with_assignment
+ - literal_only_boolean_expressions
+ - missing_whitespace_between_adjacent_strings
+ - no_adjacent_strings_in_list
+ - no_runtimeType_toString
+ - prefer_const_declarations
+ - prefer_expression_function_bodies
+ - prefer_final_locals
+ - require_trailing_commas
+ - unnecessary_raw_strings
+ - use_if_null_to_convert_nulls_to_bools
+ - use_raw_strings
+ - use_string_buffers
diff --git a/pkgs/browser_launcher/example/main.dart b/pkgs/browser_launcher/example/main.dart
new file mode 100644
index 0000000..6814bce
--- /dev/null
+++ b/pkgs/browser_launcher/example/main.dart
@@ -0,0 +1,31 @@
+// 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:browser_launcher/browser_launcher.dart';
+
+const _googleUrl = 'https://www.google.com/';
+const _googleImagesUrl = 'https://www.google.com/imghp?hl=en';
+
+Future<void> main() async {
+ // Launches a chrome browser with two tabs open to [_googleUrl] and
+ // [_googleImagesUrl].
+ await Chrome.start([_googleUrl, _googleImagesUrl]);
+ print('launched Chrome');
+
+ // Pause briefly before opening Chrome with a debug port.
+ await Future<void>.delayed(const Duration(seconds: 3));
+
+ // Launches a chrome browser open to [_googleUrl]. Since we are launching with
+ // a debug port, we will use a variety of different launch configurations,
+ // such as launching in a new browser.
+ final chrome = await Chrome.startWithDebugPort([_googleUrl], debugPort: 8888);
+ print('launched Chrome with a debug port');
+
+ // When running this dart code, observe that the browser stays open for 3
+ // seconds before we close it.
+ await Future<void>.delayed(const Duration(seconds: 3));
+
+ await chrome.close();
+ print('closed Chrome');
+}
diff --git a/pkgs/browser_launcher/lib/browser_launcher.dart b/pkgs/browser_launcher/lib/browser_launcher.dart
new file mode 100644
index 0000000..7d85dad
--- /dev/null
+++ b/pkgs/browser_launcher/lib/browser_launcher.dart
@@ -0,0 +1,5 @@
+// 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.
+
+export 'src/chrome.dart';
diff --git a/pkgs/browser_launcher/lib/src/chrome.dart b/pkgs/browser_launcher/lib/src/chrome.dart
new file mode 100644
index 0000000..8ee14f0
--- /dev/null
+++ b/pkgs/browser_launcher/lib/src/chrome.dart
@@ -0,0 +1,237 @@
+// 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:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:logging/logging.dart';
+import 'package:path/path.dart' as p;
+import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
+
+const _chromeEnvironments = ['CHROME_EXECUTABLE', 'CHROME_PATH'];
+const _linuxExecutable = 'google-chrome';
+const _macOSExecutable =
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
+const _windowsExecutable = r'Google\Chrome\Application\chrome.exe';
+
+String get _executable {
+ for (var chromeEnv in _chromeEnvironments) {
+ if (Platform.environment.containsKey(chromeEnv)) {
+ return Platform.environment[chromeEnv]!;
+ }
+ }
+ if (Platform.isLinux) return _linuxExecutable;
+ if (Platform.isMacOS) return _macOSExecutable;
+ if (Platform.isWindows) {
+ final windowsPrefixes = [
+ Platform.environment['LOCALAPPDATA'],
+ Platform.environment['PROGRAMFILES'],
+ Platform.environment['PROGRAMFILES(X86)'],
+ ];
+ return p.join(
+ windowsPrefixes.firstWhere(
+ (prefix) {
+ if (prefix == null) return false;
+ final path = p.join(prefix, _windowsExecutable);
+ return File(path).existsSync();
+ },
+ orElse: () => '.',
+ )!,
+ _windowsExecutable,
+ );
+ }
+ throw StateError('Unexpected platform type.');
+}
+
+/// Manager for an instance of Chrome.
+class Chrome {
+ static final _logger = Logger('BROWSER_LAUNCHER.CHROME');
+
+ Chrome._(
+ this.debugPort,
+ this.chromeConnection, {
+ Process? process,
+ Directory? dataDir,
+ this.deleteDataDir = false,
+ }) : _process = process,
+ _dataDir = dataDir;
+
+ final int debugPort;
+ final ChromeConnection chromeConnection;
+ final Process? _process;
+ final Directory? _dataDir;
+ final bool deleteDataDir;
+
+ /// Connects to an instance of Chrome with an open debug port.
+ static Future<Chrome> fromExisting(int port) async =>
+ _connect(Chrome._(port, ChromeConnection('localhost', port)));
+
+ /// Starts Chrome with the given arguments and a specific port.
+ ///
+ /// Each url in [urls] will be loaded in a separate tab.
+ ///
+ /// If [userDataDir] is `null`, a new temp directory will be
+ /// passed to chrome as a user data directory. Chrome will
+ /// start without sign in and with extensions disabled.
+ ///
+ /// If [userDataDir] is not `null`, it will be passed to chrome
+ /// as a user data directory. Chrome will start signed into
+ /// the default profile with extensions enabled if [signIn]
+ /// is also true.
+ static Future<Chrome> startWithDebugPort(
+ List<String> urls, {
+ int debugPort = 0,
+ bool headless = false,
+ String? userDataDir,
+ bool signIn = false,
+ }) async {
+ Directory dataDir;
+ if (userDataDir == null) {
+ signIn = false;
+ dataDir = Directory.systemTemp.createTempSync();
+ } else {
+ dataDir = Directory(userDataDir);
+ }
+ final port = debugPort == 0 ? await findUnusedPort() : debugPort;
+ final args = [
+ // Using a tmp directory ensures that a new instance of chrome launches
+ // allowing for the remote debug port to be enabled.
+ '--user-data-dir=${dataDir.path}',
+ '--remote-debugging-port=$port',
+ // When the DevTools has focus we don't want to slow down the application.
+ '--disable-background-timer-throttling',
+ '--disable-blink-features=TimerThrottlingForBackgroundTabs',
+ '--disable-features=IntensiveWakeUpThrottling',
+ // Since we are using a temp profile, disable features that slow the
+ // Chrome launch.
+ if (!signIn) '--disable-extensions',
+ '--disable-popup-blocking',
+ if (!signIn) '--bwsi',
+ '--no-first-run',
+ '--no-default-browser-check',
+ '--disable-default-apps',
+ '--disable-translate',
+ '--start-maximized',
+ // When running on MacOS, Chrome may open system dialogs requesting
+ // credentials. This uses a mock keychain to avoid that dialog from
+ // blocking.
+ '--use-mock-keychain',
+ ];
+ if (headless) {
+ args.add('--headless');
+ }
+
+ final process = await _startProcess(urls, args: args);
+
+ // Wait until the DevTools are listening before trying to connect.
+ final errorLines = <String>[];
+ try {
+ final stderr = process.stderr.asBroadcastStream();
+ stderr
+ .transform(utf8.decoder)
+ .transform(const LineSplitter())
+ .listen(_logger.fine);
+
+ await stderr
+ .transform(utf8.decoder)
+ .transform(const LineSplitter())
+ .firstWhere((line) {
+ errorLines.add(line);
+ return line.startsWith('DevTools listening');
+ }).timeout(const Duration(seconds: 60));
+ } on TimeoutException catch (e, s) {
+ _logger.severe('Unable to connect to Chrome DevTools', e, s);
+ throw Exception(
+ 'Unable to connect to Chrome DevTools: $e.\n\n'
+ 'Chrome STDERR:\n${errorLines.join('\n')}',
+ );
+ }
+
+ return _connect(
+ Chrome._(
+ port,
+ ChromeConnection('localhost', port),
+ process: process,
+ dataDir: dataDir,
+ deleteDataDir: userDataDir == null,
+ ),
+ );
+ }
+
+ /// Starts Chrome with the given arguments.
+ ///
+ /// Each url in [urls] will be loaded in a separate tab.
+ static Future<Process> start(
+ List<String> urls, {
+ List<String> args = const [],
+ }) async =>
+ await _startProcess(urls, args: args);
+
+ static Future<Process> _startProcess(
+ List<String> urls, {
+ List<String> args = const [],
+ }) async {
+ final processArgs = args.toList()..addAll(urls);
+ return await Process.start(_executable, processArgs);
+ }
+
+ static Future<Chrome> _connect(Chrome chrome) async {
+ // The connection is lazy. Try a simple call to make sure the provided
+ // connection is valid.
+ try {
+ await chrome.chromeConnection.getTabs();
+ } catch (e) {
+ await chrome.close();
+ throw ChromeError(
+ 'Unable to connect to Chrome debug port: ${chrome.debugPort}\n $e',
+ );
+ }
+ return chrome;
+ }
+
+ Future<void> close() async {
+ chromeConnection.close();
+ _process?.kill(ProcessSignal.sigkill);
+ await _process?.exitCode;
+ try {
+ // Chrome starts another process as soon as it dies that modifies the
+ // profile information. Give it some time before attempting to delete
+ // the directory.
+ if (deleteDataDir) {
+ await Future<void>.delayed(const Duration(milliseconds: 500));
+ await _dataDir?.delete(recursive: true);
+ }
+ } catch (_) {
+ // Silently fail if we can't clean up the profile information.
+ // It is a system tmp directory so it should get cleaned up eventually.
+ }
+ }
+}
+
+class ChromeError extends Error {
+ final String details;
+ ChromeError(this.details);
+
+ @override
+ String toString() => 'ChromeError: $details';
+}
+
+/// Returns a port that is probably, but not definitely, not in use.
+///
+/// This has a built-in race condition: another process may bind this port at
+/// any time after this call has returned.
+Future<int> findUnusedPort() async {
+ int port;
+ ServerSocket socket;
+ try {
+ socket =
+ await ServerSocket.bind(InternetAddress.loopbackIPv6, 0, v6Only: true);
+ } on SocketException {
+ socket = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0);
+ }
+ port = socket.port;
+ await socket.close();
+ return port;
+}
diff --git a/pkgs/browser_launcher/pubspec.yaml b/pkgs/browser_launcher/pubspec.yaml
new file mode 100644
index 0000000..e046097
--- /dev/null
+++ b/pkgs/browser_launcher/pubspec.yaml
@@ -0,0 +1,17 @@
+name: browser_launcher
+version: 1.1.3
+description: Provides a standardized way to launch web browsers for testing and tools.
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/browser_launcher
+issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Abrowser_launcher
+
+environment:
+ sdk: ^3.4.0
+
+dependencies:
+ logging: ^1.0.0
+ path: ^1.8.0
+ webkit_inspection_protocol: ^1.0.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ test: ^1.17.3
diff --git a/pkgs/browser_launcher/test/chrome_test.dart b/pkgs/browser_launcher/test/chrome_test.dart
new file mode 100644
index 0000000..9243768
--- /dev/null
+++ b/pkgs/browser_launcher/test/chrome_test.dart
@@ -0,0 +1,226 @@
+// 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.
+
+@OnPlatform({'windows': Skip('appveyor is not setup to install Chrome')})
+library;
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:browser_launcher/src/chrome.dart';
+import 'package:logging/logging.dart';
+import 'package:test/test.dart';
+import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
+
+const _headlessOnlyEnvironment = 'HEADLESS_ONLY';
+
+bool get headlessOnlyEnvironment =>
+ Platform.environment[_headlessOnlyEnvironment] == 'true';
+
+void _configureLogging(bool verbose) {
+ Logger.root.level = verbose ? Level.ALL : Level.INFO;
+ Logger.root.onRecord.listen((record) {
+ print('${record.level.name}: ${record.time}: ${record.message}');
+ });
+}
+
+void main() {
+ Chrome? chrome;
+
+ // Pass 'true' for debugging.
+ _configureLogging(false);
+
+ Future<ChromeTab?> getTab(String url) => chrome!.chromeConnection.getTab(
+ (t) => t.url.contains(url),
+ retryFor: const Duration(seconds: 5),
+ );
+
+ Future<List<ChromeTab>?> getTabs() => chrome!.chromeConnection.getTabs(
+ retryFor: const Duration(seconds: 5),
+ );
+
+ Future<WipConnection> connectToTab(String url) async {
+ final tab = await getTab(url);
+ expect(tab, isNotNull);
+ return tab!.connect();
+ }
+
+ Future<HttpClientResponse> openTab(String url) =>
+ chrome!.chromeConnection.getUrl(_openTabUrl(url));
+
+ Future<void> launchChromeWithDebugPort({
+ int port = 0,
+ String? userDataDir,
+ bool signIn = false,
+ bool headless = false,
+ }) async {
+ chrome = await Chrome.startWithDebugPort(
+ [_googleUrl],
+ debugPort: port,
+ userDataDir: userDataDir,
+ signIn: signIn,
+ headless: headless,
+ );
+ }
+
+ Future<void> launchChrome({bool headless = false}) async {
+ await Chrome.start([_googleUrl], args: [if (headless) '--headless']);
+ }
+
+ final headlessModes = [
+ true,
+ if (!headlessOnlyEnvironment) false,
+ ];
+
+ for (var headless in headlessModes) {
+ group('(headless: $headless)', () {
+ group('chrome with temp data dir', () {
+ tearDown(() async {
+ await chrome?.close();
+ chrome = null;
+ });
+
+ test('can launch chrome', () async {
+ await launchChrome(headless: headless);
+ expect(chrome, isNull);
+ });
+
+ test('can launch chrome with debug port', () async {
+ await launchChromeWithDebugPort(headless: headless);
+ expect(chrome, isNotNull);
+ });
+
+ test('has a working debugger', () async {
+ await launchChromeWithDebugPort(headless: headless);
+ final tabs = await getTabs();
+ expect(
+ tabs,
+ contains(
+ const TypeMatcher<ChromeTab>()
+ .having((t) => t.url, 'url', _googleUrl),
+ ),
+ );
+ });
+
+ test('uses open debug port if provided port is 0', () async {
+ await launchChromeWithDebugPort(headless: headless);
+ expect(chrome!.debugPort, isNot(equals(0)));
+ });
+
+ test('can provide a specific debug port', () async {
+ final port = await findUnusedPort();
+ await launchChromeWithDebugPort(port: port, headless: headless);
+ expect(chrome!.debugPort, port);
+ });
+ });
+
+ group('chrome with user data dir', () {
+ late Directory dataDir;
+ const waitMilliseconds = Duration(milliseconds: 100);
+
+ for (var signIn in [false, true]) {
+ group('and signIn = $signIn', () {
+ setUp(() {
+ dataDir = Directory.systemTemp.createTempSync(_userDataDirName);
+ });
+
+ tearDown(() async {
+ await chrome?.close();
+ chrome = null;
+
+ var attempts = 0;
+ while (true) {
+ try {
+ attempts++;
+ await Future<dynamic>.delayed(waitMilliseconds);
+ dataDir.deleteSync(recursive: true);
+ break;
+ } catch (_) {
+ if (attempts > 3) rethrow;
+ }
+ }
+ });
+
+ test('can launch with debug port', () async {
+ await launchChromeWithDebugPort(
+ userDataDir: dataDir.path,
+ signIn: signIn,
+ headless: headless,
+ );
+ expect(chrome, isNotNull);
+ });
+
+ test('has a working debugger', () async {
+ await launchChromeWithDebugPort(
+ userDataDir: dataDir.path,
+ signIn: signIn,
+ headless: headless,
+ );
+ final tabs = await getTabs();
+ expect(
+ tabs,
+ contains(
+ const TypeMatcher<ChromeTab>()
+ .having((t) => t.url, 'url', _googleUrl),
+ ),
+ );
+ });
+
+ test(
+ 'has correct profile path',
+ () async {
+ await launchChromeWithDebugPort(
+ userDataDir: dataDir.path,
+ signIn: signIn,
+ headless: headless,
+ );
+ await openTab(_chromeVersionUrl);
+ final wipConnection = await connectToTab(_chromeVersionUrl);
+ await wipConnection.debugger.enable();
+ await wipConnection.runtime.enable();
+ final result = await _evaluate(
+ wipConnection.page,
+ "document.getElementById('profile_path').textContent",
+ );
+ expect(result, contains(_userDataDirName));
+ },
+ // Note: When re-enabling, skip for headless mode because headless
+ // mode does not allow chrome: urls.
+ skip: 'https://github.com/dart-lang/sdk/issues/52357',
+ );
+ });
+ }
+ });
+ });
+ }
+}
+
+String _openTabUrl(String url) => '/json/new?$url';
+
+Future<String?> _evaluate(WipPage page, String expression) async {
+ String? result;
+ const stopInSeconds = Duration(seconds: 5);
+ const waitMilliseconds = Duration(milliseconds: 100);
+ final stopTime = DateTime.now().add(stopInSeconds);
+
+ while (result == null && DateTime.now().isBefore(stopTime)) {
+ await Future<dynamic>.delayed(waitMilliseconds);
+ try {
+ final wipResponse = await page.sendCommand(
+ 'Runtime.evaluate',
+ params: {'expression': expression},
+ );
+ final response = wipResponse.json['result'] as Map<String, dynamic>;
+ final value = (response['result'] as Map<String, dynamic>)['value'];
+ result = value?.toString();
+ } catch (_) {
+ return null;
+ }
+ }
+ return result;
+}
+
+const _googleUrl = 'https://www.google.com/';
+const _chromeVersionUrl = 'chrome://version/';
+const _userDataDirName = 'data dir';
diff --git a/pkgs/cli_config/.gitignore b/pkgs/cli_config/.gitignore
new file mode 100644
index 0000000..58e48f3
--- /dev/null
+++ b/pkgs/cli_config/.gitignore
@@ -0,0 +1,9 @@
+# https://dart.dev/guides/libraries/private-files
+# Created by `dart pub`
+.dart_tool/
+
+# Avoid committing pubspec.lock for library packages; see
+# https://dart.dev/guides/libraries/private-files#pubspeclock.
+pubspec.lock
+
+coverage/
diff --git a/pkgs/cli_config/CHANGELOG.md b/pkgs/cli_config/CHANGELOG.md
new file mode 100644
index 0000000..55fcd2d
--- /dev/null
+++ b/pkgs/cli_config/CHANGELOG.md
@@ -0,0 +1,22 @@
+## 0.2.1-wip
+
+- Require Dart 3.4.
+
+## 0.2.0
+
+- **Breaking Change** Rename `Config.fromArgs` to `Config.fromArguments`.
+- Add `Config.fromArgumentsSync`.
+
+## 0.1.2
+
+- Add usage docs to the readme.
+- Require Dart 3.0.
+
+## 0.1.1
+
+- Support non-String map keys in YAML configuration files.
+- Support null values in YAML configuration files.
+
+## 0.1.0
+
+- Initial version.
diff --git a/pkgs/cli_config/LICENSE b/pkgs/cli_config/LICENSE
new file mode 100644
index 0000000..ac90031
--- /dev/null
+++ b/pkgs/cli_config/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2023, 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/cli_config/README.md b/pkgs/cli_config/README.md
new file mode 100644
index 0000000..54c08e4
--- /dev/null
+++ b/pkgs/cli_config/README.md
@@ -0,0 +1,71 @@
+[](https://github.com/dart-lang/tools/actions/workflows/cli_config.yml)
+[](https://coveralls.io/github/dart-lang/tools?branch=main)
+[](https://pub.dev/packages/cli_config)
+[](https://pub.dev/packages/cli_config/publisher)
+
+A library to take config values from configuration files, CLI arguments, and
+environment variables.
+
+## Usage
+
+Configuration can be provided from commandline arguments, environment variables,
+and configuration files. This library makes these accessible via a uniform API.
+
+Configuration can be provided via the three sources as follows:
+1. commandline argument defines as `-Dsome_key=some_value`,
+2. environment variables as `SOME_KEY=some_value`, and
+3. config files as JSON or YAML as `{'some_key': 'some_value'}`.
+
+The default lookup behavior is that commandline argument defines take precedence
+over environment variables, which take precedence over the configuration file.
+
+If a single value is requested from this configuration, the first source that
+can provide the value will provide it. For example
+`config.string('some_key')` with `{'some_key': 'file_value'}` in the config file
+and `-Dsome_key=cli_value` as commandline argument returns
+`'cli_value'`. The implication is that you can not remove keys from the
+configuration file, only overwrite or append them.
+
+If a list value is requested from this configuration, the values provided by the
+various sources can be combined or not. For example
+`config.optionalStringList('some_key', combineAllConfigs: true)` returns
+`['cli_value', 'file_value']`.
+
+The config is hierarchical in nature, using `.` as the hierarchy separator for
+lookup and commandline defines. The hierarchy should be materialized in the JSON
+or YAML configuration file. For environment variables `__` is used as hierarchy
+separator.
+
+Hierarchical configuration can be provided via the three sources as follows:
+1. commandline argument defines as `-Dsome_key.some_nested_key=some_value`,
+2. environment variables as `SOME_KEY__SOME_NESTED_KEY=some_value`, and
+3. config files as JSON or YAML as
+ ```yaml
+ some_key:
+ some_nested_key:
+ some_value
+ ```
+
+The config is opinionated on the format of the keys in the sources.
+* Command-line argument keys should be lower-cased alphanumeric
+ characters or underscores, with `.` for hierarchy.
+* Environment variables keys should be upper-cased alphanumeric
+ characters or underscores, with `__` for hierarchy.
+* Config files keys should be lower-cased alphanumeric
+ characters or underscores.
+
+In the API they are made available lower-cased and with underscores, and
+`.` as hierarchy separator.
+
+## Example usage
+
+This example creates a configuration which first looks for command-line defines
+in the `arguments` list then looks in `Platform.environment`, then looks in any
+local configuration file.
+
+```dart
+final config = await Config.fromArguments(arguments: arguments);
+final pathValue =
+ config.optionalPath('my_path', resolveUri: true, mustExist: false);
+print(pathValue?.toFilePath());
+```
diff --git a/pkgs/cli_config/analysis_options.yaml b/pkgs/cli_config/analysis_options.yaml
new file mode 100644
index 0000000..dd3dcda
--- /dev/null
+++ b/pkgs/cli_config/analysis_options.yaml
@@ -0,0 +1,12 @@
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-raw-types: true
+
+linter:
+ rules:
+ - prefer_const_declarations
+ - prefer_expression_function_bodies
+ - prefer_final_in_for_each
+ - prefer_final_locals
diff --git a/pkgs/cli_config/example/bin/cli_config_example.dart b/pkgs/cli_config/example/bin/cli_config_example.dart
new file mode 100644
index 0000000..1256bd7
--- /dev/null
+++ b/pkgs/cli_config/example/bin/cli_config_example.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2023, 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:cli_config/cli_config.dart';
+
+Future<void> main(List<String> args) async {
+ final config = await Config.fromArguments(arguments: args);
+ final myPath =
+ config.optionalPath('my_path', resolveUri: true, mustExist: false);
+ print(myPath?.toFilePath());
+}
diff --git a/pkgs/cli_config/example/pubspec.yaml b/pkgs/cli_config/example/pubspec.yaml
new file mode 100644
index 0000000..cec900a
--- /dev/null
+++ b/pkgs/cli_config/example/pubspec.yaml
@@ -0,0 +1,11 @@
+name: cli_config_example
+description: An example for cli_config.
+
+publish_to: none
+
+environment:
+ sdk: ">=2.19.3 <4.0.0"
+
+dependencies:
+ cli_config:
+ path: ../
diff --git a/pkgs/cli_config/lib/cli_config.dart b/pkgs/cli_config/lib/cli_config.dart
new file mode 100644
index 0000000..51df312
--- /dev/null
+++ b/pkgs/cli_config/lib/cli_config.dart
@@ -0,0 +1,9 @@
+// Copyright (c) 2023, 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.
+
+/// A library to take config values from configuration files, CLI arguments,
+/// and environment variables.
+library;
+
+export 'src/config.dart';
diff --git a/pkgs/cli_config/lib/src/cli_parser.dart b/pkgs/cli_config/lib/src/cli_parser.dart
new file mode 100644
index 0000000..9396a7a
--- /dev/null
+++ b/pkgs/cli_config/lib/src/cli_parser.dart
@@ -0,0 +1,53 @@
+// Copyright (c) 2023, 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';
+
+class CliParser {
+ final ArgParser parser = () {
+ final parser = ArgParser();
+ parser.addFlag(
+ 'help',
+ abbr: 'h',
+ help: 'Show this help.',
+ );
+ parser.addMultiOption(
+ 'define',
+ abbr: 'D',
+ help: '''Define or override a config property from command line.
+The same option can be passed multiple times.
+Keys should only contain lower-case alphanumeric characters, underscores,
+and '.'s''',
+ );
+ parser.addOption(
+ 'config',
+ abbr: 'c',
+ help: '''Path to JSON or YAML config file.
+Keys should only contain lower-case alphanumeric characters, and underscores.
+Hierarchies should be maps.''',
+ );
+ return parser;
+ }();
+
+ ArgResults parse(List<String> args) => parser.parse(args);
+}
+
+class DefinesParser {
+ static final _defineRegex = RegExp('([a-z_.]+)=(.+)');
+
+ Map<String, List<String>> parse(List<String> args) {
+ final defines = <String, List<String>>{};
+ for (final arg in args) {
+ final match = _defineRegex.matchAsPrefix(arg);
+ if (match == null || match.group(0) != arg) {
+ throw FormatException("Define '$arg' does not match expected pattern "
+ "'${_defineRegex.pattern}'.");
+ }
+ final key = match.group(1)!;
+ final value = match.group(2)!;
+ defines[key] = (defines[key] ?? [])..add(value);
+ }
+ return defines;
+ }
+}
diff --git a/pkgs/cli_config/lib/src/cli_source.dart b/pkgs/cli_config/lib/src/cli_source.dart
new file mode 100644
index 0000000..20e662c
--- /dev/null
+++ b/pkgs/cli_config/lib/src/cli_source.dart
@@ -0,0 +1,95 @@
+// Copyright (c) 2023, 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 'config.dart';
+import 'source.dart';
+
+class CliSource extends Source {
+ /// Configuration options passed in via CLI arguments.
+ ///
+ /// Options can be passed multiple times, so the values here are a list.
+ ///
+ /// Stored as a flat non-hierarchical structure, keys contain `.`.
+ final Map<String, List<String>> _cli;
+
+ /// If provided, used to resolve paths within [_cli].
+ ///
+ /// Typically the current working directory at application start.
+ @override
+ final Uri? baseUri;
+
+ CliSource(this._cli, this.baseUri);
+
+ @override
+ String? optionalString(String key) {
+ final value = _cli[key];
+ if (value == null) {
+ return null;
+ }
+ if (value.length > 1) {
+ throw FormatException(
+ "More than one value was passed for '$key' in the CLI defines."
+ ' Values passed: $value');
+ }
+ return value.single;
+ }
+
+ @override
+ List<String>? optionalStringList(
+ String key, {
+ String? splitPattern,
+ }) {
+ final cliValue = _cli[key];
+ if (cliValue == null) {
+ return null;
+ }
+ if (splitPattern != null) {
+ return [for (final value in cliValue) ...value.split(splitPattern)];
+ }
+ return cliValue;
+ }
+
+ @override
+ bool? optionalBool(String key) {
+ final stringValue = optionalString(key);
+ if (stringValue != null) {
+ Source.throwIfUnexpectedValue(key, stringValue, Config.boolStrings.keys);
+ return Config.boolStrings[stringValue]!;
+ }
+ return null;
+ }
+
+ @override
+ int? optionalInt(String key) {
+ final stringValue = optionalString(key);
+ if (stringValue != null) {
+ try {
+ return int.parse(stringValue);
+ } on FormatException catch (e) {
+ throw FormatException(
+ "Unexpected value '$stringValue' for key '$key'. Expected an int."
+ ' ${e.message}');
+ }
+ }
+ return null;
+ }
+
+ @override
+ double? optionalDouble(String key) {
+ final stringValue = optionalString(key);
+ if (stringValue != null) {
+ try {
+ return double.parse(stringValue);
+ } on FormatException catch (e) {
+ throw FormatException(
+ "Unexpected value '$stringValue' for key '$key'. Expected a double."
+ ' ${e.message}');
+ }
+ }
+ return null;
+ }
+
+ @override
+ String toString() => 'CliSource($_cli)';
+}
diff --git a/pkgs/cli_config/lib/src/config.dart b/pkgs/cli_config/lib/src/config.dart
new file mode 100644
index 0000000..85e2c0c
--- /dev/null
+++ b/pkgs/cli_config/lib/src/config.dart
@@ -0,0 +1,650 @@
+// Copyright (c) 2023, 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:core' as core show bool, double, int;
+import 'dart:core' hide bool, double, int;
+import 'dart:io';
+
+import 'cli_parser.dart';
+import 'cli_source.dart';
+import 'environment_parser.dart';
+import 'environment_source.dart';
+import 'file_parser.dart';
+import 'file_source.dart';
+import 'source.dart';
+
+/// A hierarchical configuration.
+///
+/// Configuration can be provided from three sources: commandline arguments,
+/// environment variables and configuration files. This configuration makes
+/// these accessible via a uniform API.
+///
+/// Configuration can be provided via the three sources as follows:
+/// 1. commandline argument defines as `-Dsome_key=some_value`,
+/// 2. environment variables as `SOME_KEY=some_value`, and
+/// 3. config files as JSON or YAML as `{'some_key': 'some_value'}`.
+///
+/// The default lookup behavior is that commandline argument defines take
+/// precedence over environment variables, which take precedence over the
+/// configuration file.
+///
+/// If a single value is requested from this configuration, the first source
+/// that can provide the value will provide it. For example
+/// `config.string('some_key')` with `{'some_key': 'file_value'}` in the
+/// config file and `-Dsome_key=cli_value` as commandline argument returns
+/// `'cli_value'`. The implication is that you can not remove keys from the
+/// configuration file, only overwrite or append them.
+///
+/// If a list value is requested from this configuration, the values provided
+/// by the various sources can be combined or not. For example
+/// `config.optionalStringList('some_key', combineAllConfigs: true)` returns
+/// `['cli_value', 'file_value']`.
+///
+/// The config is hierarchical in nature, using `.` as the hierarchy separator
+/// for lookup and commandline defines. The hierarchy should be materialized in
+/// the JSON or YAML configuration file. For environment variables `__` is used
+/// as hierarchy separator.
+///
+/// Hierarchical configuration can be provided via the three sources as follows:
+/// 1. commandline argument defines as `-Dsome_key.some_nested_key=some_value`,
+/// 2. environment variables as `SOME_KEY__SOME_NESTED_KEY=some_value`, and
+/// 3. config files as JSON or YAML as
+/// ```yaml
+/// some_key:
+/// some_nested_key:
+/// some_value
+/// ```
+///
+/// The config is opinionated on the format of the keys in the sources.
+/// * Command-line argument keys should be lower-cased alphanumeric
+/// characters or underscores, with `.` for hierarchy.
+/// * Environment variables keys should be upper-cased alphanumeric
+/// characters or underscores, with `__` for hierarchy.
+/// * Config files keys should be lower-cased alphanumeric
+/// characters or underscores.
+///
+/// In the API they are made available lower-cased and with underscores, and
+/// `.` as hierarchy separator.
+class Config {
+ final CliSource _cliSource;
+ final EnvironmentSource _environmentSource;
+ final FileSource _fileSource;
+
+ /// Config sources, ordered by precedence.
+ late final _sources = [_cliSource, _environmentSource, _fileSource];
+
+ Config._(
+ this._cliSource,
+ this._environmentSource,
+ this._fileSource,
+ );
+
+ /// Constructs a config by parsing the three sources.
+ ///
+ /// If provided, [commandLineDefines] must be a list of `<key>=<value>`.
+ ///
+ /// If provided, [workingDirectory] is used to resolves paths inside
+ /// [commandLineDefines].
+ ///
+ /// If provided, [environment] must be a map containing environment variables.
+ ///
+ /// If provided, [fileParsed] must be valid parsed YSON or YAML (maps, lists,
+ /// strings, integers, and booleans).
+ ///
+ /// If provided [fileSourceUri] is used to resolve paths inside
+ /// [fileParsed] and to provide better error messages on parsing the
+ /// configuration file.
+ factory Config({
+ List<String> commandLineDefines = const [],
+ Uri? workingDirectory,
+ Map<String, String> environment = const {},
+ Map<String, dynamic> fileParsed = const {},
+ Uri? fileSourceUri,
+ }) {
+ // Parse config file.
+ final fileConfig = FileParser().parseToplevelMap(fileParsed);
+
+ // Parse CLI argument defines.
+ final cliConfig = DefinesParser().parse(commandLineDefines);
+
+ // Parse environment.
+ final environmentConfig = EnvironmentParser().parse(environment);
+
+ return Config._(
+ CliSource(cliConfig, workingDirectory?.normalizePath()),
+ EnvironmentSource(environmentConfig),
+ FileSource(fileConfig, fileSourceUri?.normalizePath()),
+ );
+ }
+
+ /// Constructs a config by parsing the three sources.
+ ///
+ /// If provided, [commandLineDefines] must be a list of `<key>=<value>`.
+ ///
+ /// If provided, [workingDirectory] is used to resolves paths inside
+ /// [commandLineDefines].
+ ///
+ /// If provided, [environment] must be a map containing environment variables.
+ ///
+ /// If provided, [fileContents] must be valid JSON or YAML.
+ ///
+ /// If provided [fileSourceUri] is used to resolve paths inside
+ /// [fileContents] and to provide better error messages on parsing the
+ /// configuration file.
+ factory Config.fromConfigFileContents({
+ List<String> commandLineDefines = const [],
+ Uri? workingDirectory,
+ Map<String, String> environment = const {},
+ String? fileContents,
+ Uri? fileSourceUri,
+ }) {
+ // Parse config file.
+ final Map<String, dynamic> fileConfig;
+ if (fileContents != null) {
+ fileConfig = FileParser().parse(
+ fileContents,
+ sourceUrl: fileSourceUri,
+ );
+ } else {
+ fileConfig = {};
+ }
+
+ // Parse CLI argument defines.
+ final cliConfig = DefinesParser().parse(commandLineDefines);
+
+ // Parse environment.
+ final environmentConfig = EnvironmentParser().parse(environment);
+
+ return Config._(
+ CliSource(cliConfig, workingDirectory),
+ EnvironmentSource(environmentConfig),
+ FileSource(fileConfig, fileSourceUri),
+ );
+ }
+
+ /// Constructs a config by parsing CLI arguments and loading the config file.
+ ///
+ /// The [arguments] must be commandline arguments.
+ ///
+ /// If provided, [environment] must be a map containing environment variables.
+ /// If not provided, [environment] defaults to [Platform.environment].
+ ///
+ /// If provided, [workingDirectory] is used to resolves paths inside
+ /// [environment].
+ /// If not provided, [workingDirectory] defaults to [Directory.current].
+ ///
+ /// This async constructor is intended to be used directly in CLI files.
+ static Future<Config> fromArguments({
+ required List<String> arguments,
+ Map<String, String>? environment,
+ Uri? workingDirectory,
+ }) async {
+ final results = CliParser().parse(arguments);
+
+ // Load config file.
+ final configFile = results['config'] as String?;
+ String? fileContents;
+ Uri? fileSourceUri;
+ if (configFile != null) {
+ fileContents = await File(configFile).readAsString();
+ fileSourceUri = Uri.file(configFile);
+ }
+
+ return Config.fromConfigFileContents(
+ commandLineDefines: results['define'] as List<String>,
+ workingDirectory: workingDirectory ?? Directory.current.uri,
+ environment: environment ?? Platform.environment,
+ fileContents: fileContents,
+ fileSourceUri: fileSourceUri,
+ );
+ }
+
+ /// Constructs a config by parsing CLI arguments and loading the config file.
+ ///
+ /// The [arguments] must be commandline arguments.
+ ///
+ /// If provided, [environment] must be a map containing environment variables.
+ /// If not provided, [environment] defaults to [Platform.environment].
+ ///
+ /// If provided, [workingDirectory] is used to resolves paths inside
+ /// [environment].
+ /// If not provided, [workingDirectory] defaults to [Directory.current].
+ ///
+ /// This synchronous constructor is intended to be used directly in CLI files.
+ static Config fromArgumentsSync({
+ required List<String> arguments,
+ Map<String, String>? environment,
+ Uri? workingDirectory,
+ }) {
+ final results = CliParser().parse(arguments);
+
+ // Load config file.
+ final configFile = results['config'] as String?;
+ String? fileContents;
+ Uri? fileSourceUri;
+ if (configFile != null) {
+ fileContents = File(configFile).readAsStringSync();
+ fileSourceUri = Uri.file(configFile);
+ }
+
+ return Config.fromConfigFileContents(
+ commandLineDefines: results['define'] as List<String>,
+ workingDirectory: workingDirectory ?? Directory.current.uri,
+ environment: environment ?? Platform.environment,
+ fileContents: fileContents,
+ fileSourceUri: fileSourceUri,
+ );
+ }
+
+ /// Lookup a string value in this config.
+ ///
+ /// First tries CLI argument defines, then environment variables, and
+ /// finally the config file.
+ ///
+ /// Throws if one of the configs does not contain the expected value type.
+ ///
+ /// If [validValues] is provided, throws if an unxpected value is provided.
+ String string(String key, {Iterable<String>? validValues}) {
+ final value = optionalString(key, validValues: validValues);
+ _throwIfNull(key, value);
+ return value!;
+ }
+
+ /// Lookup an optional string value in this config.
+ ///
+ /// First tries CLI argument defines, then environment variables, and
+ /// finally the config file.
+ ///
+ /// If [validValues] is provided, throws if an unxpected value is provided.
+ String? optionalString(String key, {Iterable<String>? validValues}) {
+ String? value;
+ for (final source in _sources) {
+ value ??= source.optionalString(key);
+ }
+ if (value != null && validValues != null) {
+ Source.throwIfUnexpectedValue(key, value, validValues);
+ }
+ return value;
+ }
+
+ /// Lookup an optional string list in this config.
+ ///
+ /// If none of the sources provide a list, lookup will fail.
+ /// If an empty list is provided by one of the sources, lookup wil succeed.
+ ///
+ /// First tries CLI argument defines, then environment variables, and
+ /// finally the config file.
+ ///
+ /// If [combineAllConfigs] combines results from cli, environment, and
+ /// config file. Otherwise, precedence rules apply.
+ ///
+ /// If provided, [splitCliPattern] splits cli defines.
+ /// For example: `-Dfoo=bar;baz` can be split on `;`.
+ /// If not provided, a list can still be provided with multiple cli defines.
+ /// For example: `-Dfoo=bar -Dfoo=baz`.
+ ///
+ /// If provided, [splitEnvironmentPattern] splits environment values.
+ List<String> stringList(
+ String key, {
+ core.bool combineAllConfigs = true,
+ String? splitCliPattern,
+ String? splitEnvironmentPattern,
+ }) {
+ final value = optionalStringList(
+ key,
+ combineAllConfigs: combineAllConfigs,
+ splitCliPattern: splitCliPattern,
+ splitEnvironmentPattern: splitEnvironmentPattern,
+ );
+ _throwIfNull(key, value);
+ return value!;
+ }
+
+ /// Lookup an optional string list in this config.
+ ///
+ /// First tries CLI argument defines, then environment variables, and
+ /// finally the config file.
+ ///
+ /// If [combineAllConfigs] combines results from cli, environment, and
+ /// config file. Otherwise, precedence rules apply.
+ ///
+ /// If provided, [splitCliPattern] splits cli defines.
+ /// For example: `-Dfoo=bar;baz` can be split on `;`.
+ /// If not provided, a list can still be provided with multiple cli defines.
+ /// For example: `-Dfoo=bar -Dfoo=baz`.
+ ///
+ /// If provided, [splitEnvironmentPattern] splits environment values.
+ List<String>? optionalStringList(
+ String key, {
+ core.bool combineAllConfigs = true,
+ String? splitCliPattern,
+ String? splitEnvironmentPattern,
+ }) {
+ List<String>? result;
+ for (final entry in {
+ _cliSource: splitCliPattern,
+ _environmentSource: splitEnvironmentPattern,
+ _fileSource: null
+ }.entries) {
+ final source = entry.key;
+ final splitPattern = entry.value;
+ final value = source.optionalStringList(key, splitPattern: splitPattern);
+ if (value != null) {
+ if (combineAllConfigs) {
+ (result ??= []).addAll(value);
+ } else {
+ return value;
+ }
+ }
+ }
+ return result;
+ }
+
+ static const boolStrings = {
+ '0': false,
+ '1': true,
+ 'false': false,
+ 'FALSE': false,
+ 'no': false,
+ 'NO': false,
+ 'true': true,
+ 'TRUE': true,
+ 'yes': true,
+ 'YES': true,
+ };
+
+ /// Lookup a boolean value in this config.
+ ///
+ /// First tries CLI argument defines, then environment variables, and
+ /// finally the config file.
+ ///
+ /// For cli defines and environment variables, the value must be one of
+ /// [boolStrings].
+ /// For the config file, it must be a boolean.
+ ///
+ /// Throws if one of the configs does not contain the expected value type.
+ core.bool bool(String key) {
+ final value = optionalBool(key);
+ _throwIfNull(key, value);
+ return value!;
+ }
+
+ /// Lookup an optional boolean value in this config.
+ ///
+ /// First tries CLI argument defines, then environment variables, and
+ /// finally the config file.
+ ///
+ /// For cli defines and environment variables, the value must be one of
+ /// [boolStrings].
+ /// For the config file, it must be a boolean or null.
+ core.bool? optionalBool(String key) {
+ core.bool? value;
+ for (final source in _sources) {
+ value ??= source.optionalBool(key);
+ }
+ return value;
+ }
+
+ /// Lookup an integer value in this config.
+ ///
+ /// First tries CLI argument defines, then environment variables, and
+ /// finally the config file.
+ ///
+ /// For cli defines and environment variables, the value must be parseble
+ /// by [core.int.parse].
+ /// For the config file, it must be an integer.
+ core.int int(String key) {
+ final value = optionalInt(key);
+ _throwIfNull(key, value);
+ return value!;
+ }
+
+ /// Lookup an optional integer value in this config.
+ ///
+ /// First tries CLI argument defines, then environment variables, and
+ /// finally the config file.
+ ///
+ /// For cli defines and environment variables, the value must be parseble
+ /// by [core.int.parse].
+ /// For the config file, it must be an integer or null.
+ core.int? optionalInt(String key) {
+ core.int? value;
+ for (final source in _sources) {
+ value ??= source.optionalInt(key);
+ }
+ return value;
+ }
+
+ /// Lookup an double value in this config.
+ ///
+ /// First tries CLI argument defines, then environment variables, and
+ /// finally the config file.
+ ///
+ /// For cli defines and environment variables, the value must be parseble
+ /// by [core.double.parse].
+ /// For the config file, it must be an double.
+ core.double double(String key) {
+ final value = optionalDouble(key);
+ _throwIfNull(key, value);
+ return value!;
+ }
+
+ /// Lookup an optional double value in this config.
+ ///
+ /// First tries CLI argument defines, then environment variables, and
+ /// finally the config file.
+ ///
+ /// For cli defines and environment variables, the value must be parseble
+ /// by [core.double.parse].
+ /// For the config file, it must be an double or null.
+ core.double? optionalDouble(String key) {
+ core.double? value;
+ for (final source in _sources) {
+ value ??= source.optionalDouble(key);
+ }
+ return value;
+ }
+
+ /// Lookup a path in this config.
+ ///
+ /// First tries CLI argument defines, then environment variables, and
+ /// finally the config file.
+ ///
+ /// Throws if one of the configs does not contain the expected value type.
+ ///
+ /// If [resolveUri], resolves the paths in a source relative to the base
+ /// uri of that source. The base uri for the config file is the path of the
+ /// file. The base uri for environment values is the current working
+ /// directory.
+ ///
+ /// If [mustExist], throws if the path doesn't resolve to a file or directory
+ /// on the file system.
+ ///
+ /// Throws if one of the configs does not contain the expected value type.
+ Uri path(
+ String key, {
+ core.bool resolveUri = true,
+ core.bool mustExist = false,
+ }) {
+ final value =
+ optionalPath(key, resolveUri: resolveUri, mustExist: mustExist);
+ _throwIfNull(key, value);
+ return value!;
+ }
+
+ /// Lookup an optional path in this config.
+ ///
+ /// First tries CLI argument defines, then environment variables, and
+ /// finally the config file.
+ ///
+ /// Throws if one of the configs does not contain the expected value type.
+ ///
+ /// If [resolveUri], resolves the paths in a source relative to the base
+ /// uri of that source. The base uri for the config file is the path of the
+ /// file. The base uri for environment values is the current working
+ /// directory.
+ ///
+ /// If [mustExist], throws if the path doesn't resolve to a file or directory
+ /// on the file system.
+ Uri? optionalPath(
+ String key, {
+ core.bool resolveUri = true,
+ core.bool mustExist = false,
+ }) {
+ for (final source in _sources) {
+ final path = source.optionalString(key);
+ if (path != null) {
+ final value = _pathToUri(
+ path,
+ resolveUri: resolveUri,
+ baseUri: source.baseUri,
+ );
+ if (mustExist) {
+ _throwIfNotExists(key, value);
+ }
+ return value;
+ }
+ }
+ return null;
+ }
+
+ Uri _pathToUri(
+ String path, {
+ required core.bool resolveUri,
+ required Uri? baseUri,
+ }) {
+ final uri = Source.fileSystemPathToUri(path);
+ if (resolveUri && baseUri != null) {
+ return baseUri.resolveUri(uri);
+ }
+ return uri;
+ }
+
+ /// Lookup a list of paths in this config.
+ ///
+ /// If none of the sources provide a path, lookup will fail.
+ /// If an empty list is provided by one of the sources, lookup wil succeed.
+ ///
+ /// If [combineAllConfigs] combines results from cli, environment, and
+ /// config file. Otherwise, precedence rules apply.
+ ///
+ /// If provided, [splitCliPattern] splits cli defines.
+ ///
+ /// If provided, [splitEnvironmentPattern] splits environment values.
+ ///
+ /// If [resolveUri], resolves the paths in a source relative to the base
+ /// uri of that source. The base uri for the config file is the path of the
+ /// file. The base uri for environment values is the current working
+ /// directory.
+ List<Uri> pathList(
+ String key, {
+ core.bool combineAllConfigs = true,
+ String? splitCliPattern,
+ String? splitEnvironmentPattern,
+ core.bool resolveUri = true,
+ }) {
+ final value = optionalPathList(
+ key,
+ combineAllConfigs: combineAllConfigs,
+ splitCliPattern: splitCliPattern,
+ splitEnvironmentPattern: splitEnvironmentPattern,
+ resolveUri: resolveUri,
+ );
+ _throwIfNull(key, value);
+ return value!;
+ }
+
+ /// Lookup an optional list of paths in this config.
+ ///
+ /// If [combineAllConfigs] combines results from cli, environment, and
+ /// config file. Otherwise, precedence rules apply.
+ ///
+ /// If provided, [splitCliPattern] splits cli defines.
+ ///
+ /// If provided, [splitEnvironmentPattern] splits environment values.
+ ///
+ /// If [resolveUri], resolves the paths in a source relative to the base
+ /// uri of that source. The base uri for the config file is the path of the
+ /// file. The base uri for environment values is the current working
+ /// directory.
+ List<Uri>? optionalPathList(
+ String key, {
+ core.bool combineAllConfigs = true,
+ String? splitCliPattern,
+ String? splitEnvironmentPattern,
+ core.bool resolveUri = true,
+ }) {
+ List<Uri>? result;
+ for (final entry in {
+ _cliSource: splitCliPattern,
+ _environmentSource: splitEnvironmentPattern,
+ _fileSource: null
+ }.entries) {
+ final source = entry.key;
+ final splitPattern = entry.value;
+ final paths = source.optionalStringList(
+ key,
+ splitPattern: splitPattern,
+ );
+ if (paths != null) {
+ final value = [
+ for (final path in paths)
+ _pathToUri(
+ path,
+ resolveUri: resolveUri,
+ baseUri: source.baseUri,
+ )
+ ];
+ if (combineAllConfigs) {
+ (result ??= []).addAll(value);
+ } else {
+ return value;
+ }
+ }
+ }
+ return result;
+ }
+
+ /// Lookup a value of type [T] in this configuration.
+ ///
+ /// Does not support specialized options such as `splitPattern`. One must
+ /// use the specialized methods such as [optionalStringList] for that.
+ ///
+ /// If sources cannot lookup type [T], they return null.
+ T valueOf<T>(String key) {
+ T? value;
+ for (final source in _sources) {
+ value ??= source.optionalValueOf<T>(key);
+ }
+ if (null is! T) {
+ _throwIfNull(key, value);
+ }
+ return value as T;
+ }
+
+ void _throwIfNull(String key, Object? value) {
+ if (value == null) {
+ throw FormatException('No value was provided for required key: $key');
+ }
+ }
+
+ void _throwIfNotExists(String key, Uri value) {
+ final fileSystemEntity = value.fileSystemEntity;
+ if (!fileSystemEntity.existsSync()) {
+ throw FormatException("Path '$value' for key '$key' doesn't exist.");
+ }
+ }
+
+ @override
+ String toString() => 'Config($_sources)';
+}
+
+extension on Uri {
+ FileSystemEntity get fileSystemEntity {
+ if (path.endsWith(Platform.pathSeparator) || path.endsWith('/')) {
+ return Directory.fromUri(this);
+ }
+ return File.fromUri(this);
+ }
+}
diff --git a/pkgs/cli_config/lib/src/environment_parser.dart b/pkgs/cli_config/lib/src/environment_parser.dart
new file mode 100644
index 0000000..d91b8fa
--- /dev/null
+++ b/pkgs/cli_config/lib/src/environment_parser.dart
@@ -0,0 +1,19 @@
+// Copyright (c) 2023, 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.
+
+class EnvironmentParser {
+ /// Parses an environment key into an config key.
+ ///
+ /// Environment keys can only contain alphanumeric characters and underscores.
+ /// Treats `__` as hierarchy separator, and replaces it with `.`.
+ ///
+ /// Often, environment variables are uppercased.
+ /// Replaces all uppercase characters with lowercase characters.
+ String parseKey(String key) => key.replaceAll('__', '.').toLowerCase();
+
+ Map<String, String> parse(Map<String, String> environment) => {
+ for (final entry in environment.entries)
+ parseKey(entry.key): entry.value,
+ };
+}
diff --git a/pkgs/cli_config/lib/src/environment_source.dart b/pkgs/cli_config/lib/src/environment_source.dart
new file mode 100644
index 0000000..9cff18f
--- /dev/null
+++ b/pkgs/cli_config/lib/src/environment_source.dart
@@ -0,0 +1,86 @@
+// Copyright (c) 2023, 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 'config.dart';
+import 'source.dart';
+
+class EnvironmentSource extends Source {
+ /// Configuration options passed in via the [Platform.environment].
+ ///
+ /// The keys have been transformed by `EnvironmentParser.parseKey`.
+ ///
+ /// Environment values are left intact.
+ ///
+ /// Stored as a flat non-hierarchical structure, keys contain `.`.
+ final Map<String, String> _environment;
+
+ EnvironmentSource(this._environment);
+
+ @override
+ String? optionalString(String key) => _environment[key];
+
+ @override
+ List<String>? optionalStringList(
+ String key, {
+ String? splitPattern,
+ }) {
+ final envValue = _environment[key];
+ if (envValue == null) {
+ return null;
+ }
+ if (splitPattern != null) {
+ return envValue.split(splitPattern);
+ }
+ return [envValue];
+ }
+
+ @override
+ bool? optionalBool(String key) {
+ final stringValue = optionalString(key);
+ if (stringValue != null) {
+ Source.throwIfUnexpectedValue(key, stringValue, Config.boolStrings.keys);
+ return Config.boolStrings[stringValue]!;
+ }
+ return null;
+ }
+
+ @override
+ int? optionalInt(String key) {
+ final stringValue = optionalString(key);
+ if (stringValue != null) {
+ try {
+ return int.parse(stringValue);
+ } on FormatException catch (e) {
+ throw FormatException(
+ "Unexpected value '$stringValue' for key '$key'. Expected an int."
+ ' ${e.message}');
+ }
+ }
+ return null;
+ }
+
+ @override
+ double? optionalDouble(String key) {
+ final stringValue = optionalString(key);
+ if (stringValue != null) {
+ try {
+ return double.parse(stringValue);
+ } on FormatException catch (e) {
+ throw FormatException(
+ "Unexpected value '$stringValue' for key '$key'. Expected a double."
+ ' ${e.message}');
+ }
+ }
+ return null;
+ }
+
+ @override
+ String toString() => 'EnvironmentSource($_environment)';
+
+ /// Environment path are not resolved.
+ @override
+ Uri? get baseUri => null;
+}
diff --git a/pkgs/cli_config/lib/src/file_parser.dart b/pkgs/cli_config/lib/src/file_parser.dart
new file mode 100644
index 0000000..2a647e5
--- /dev/null
+++ b/pkgs/cli_config/lib/src/file_parser.dart
@@ -0,0 +1,46 @@
+// Copyright (c) 2023, 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:yaml/yaml.dart';
+
+class FileParser {
+ Map<String, Object?> parse(
+ String fileContents, {
+ Uri? sourceUrl,
+ }) {
+ final parsedYaml = loadYaml(
+ fileContents,
+ sourceUrl: sourceUrl,
+ );
+ if (parsedYaml is! Map) {
+ throw const FormatException(
+ 'YAML config must be set of key value pairs.');
+ }
+ return parseToplevelMap(parsedYaml);
+ }
+
+ Map<String, Object?> parseToplevelMap(Map<dynamic, dynamic> input) {
+ final result = <String, Object?>{};
+ for (final entry in input.entries) {
+ final key = parseToplevelKey(entry.key);
+ final value = entry.value as Object?;
+ result[key] = value;
+ }
+ return result;
+ }
+
+ static final _keyRegex = RegExp('([a-z-_]+)');
+
+ String parseToplevelKey(Object? key) {
+ if (key is! String) {
+ throw FormatException("Key '$key' is not a String.");
+ }
+ final match = _keyRegex.matchAsPrefix(key);
+ if (match == null || match.group(0) != key) {
+ throw FormatException("Define '$key' does not match expected pattern "
+ "'${_keyRegex.pattern}'.");
+ }
+ return key.replaceAll('-', '_');
+ }
+}
diff --git a/pkgs/cli_config/lib/src/file_source.dart b/pkgs/cli_config/lib/src/file_source.dart
new file mode 100644
index 0000000..d0594c6
--- /dev/null
+++ b/pkgs/cli_config/lib/src/file_source.dart
@@ -0,0 +1,68 @@
+// Copyright (c) 2023, 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 'source.dart';
+
+class FileSource extends Source {
+ /// Configuration options passed in via a JSON or YAML configuration file.
+ ///
+ /// Stored as a partial hierarchical data structure. The values can be maps
+ /// in which subsequent parts of a key after a `.` can be resolved.
+ final Map<String, dynamic> _file;
+
+ /// If provided, used to resolve paths within [_file].
+ @override
+ final Uri? baseUri;
+
+ FileSource(this._file, this.baseUri);
+
+ @override
+ String? optionalString(String key) => optionalValueOf<String>(key);
+
+ @override
+ List<String>? optionalStringList(
+ String key, {
+ String? splitPattern,
+ }) {
+ assert(splitPattern == null);
+ return optionalValueOf<List<dynamic>>(key)?.cast<String>();
+ }
+
+ @override
+ bool? optionalBool(String key) => optionalValueOf<bool>(key);
+
+ @override
+ int? optionalInt(String key) => optionalValueOf<int>(key);
+
+ @override
+ double? optionalDouble(String key) => optionalValueOf<double>(key);
+
+ @override
+ T? optionalValueOf<T>(String key) {
+ Object? cursor = _file;
+ var current = '';
+ for (final keyPart in key.split('.')) {
+ if (cursor == null) {
+ return null;
+ }
+ if (cursor is! Map) {
+ throw FormatException(
+ "Unexpected value '$cursor' for key '$current' in config file. "
+ 'Expected a Map.');
+ } else {
+ cursor = cursor[keyPart];
+ }
+ current += '.$keyPart';
+ }
+ if (cursor is! T?) {
+ throw FormatException(
+ "Unexpected value '$cursor' for key '$current' in config file. "
+ 'Expected a $T.');
+ }
+ return cursor;
+ }
+
+ @override
+ String toString() => 'FileSource(file: $_file, fileUri: $baseUri)';
+}
diff --git a/pkgs/cli_config/lib/src/source.dart b/pkgs/cli_config/lib/src/source.dart
new file mode 100644
index 0000000..14c7965
--- /dev/null
+++ b/pkgs/cli_config/lib/src/source.dart
@@ -0,0 +1,65 @@
+// Copyright (c) 2023, 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';
+
+abstract class Source {
+ /// If provided, the uri used for resolving paths.
+ Uri? get baseUri;
+
+ /// Lookup a nullable string value.
+ String? optionalString(String key);
+
+ /// Lookup a nullable string list.
+ ///
+ /// If provided, [splitPattern] splits config.
+ List<String>? optionalStringList(
+ String key, {
+ String? splitPattern,
+ });
+
+ /// Lookup an optional boolean value.
+ bool? optionalBool(String key);
+
+ /// Lookup an optional int value.
+ int? optionalInt(String key);
+
+ /// Lookup an optional int value.
+ double? optionalDouble(String key);
+
+ /// Lookup an optional value of type [T].
+ ///
+ /// Does not support specialized options such as `splitPattern`. One must
+ /// use the specialized methods such as [optionalStringList] for that.
+ ///
+ /// Returns `null` if the source cannot provide a value of type [T].
+ T? optionalValueOf<T>(String key) {
+ if (T == bool) {
+ return optionalBool(key) as T?;
+ }
+ if (T == String) {
+ return optionalString(key) as T?;
+ }
+ if (T == List<String>) {
+ return optionalStringList(key) as T?;
+ }
+ return null;
+ }
+
+ static void throwIfUnexpectedValue<T>(
+ String key, T value, Iterable<T> validValues) {
+ if (!validValues.contains(value)) {
+ throw FormatException(
+ "Unexpected value '$value' for key '$key'. Expected one of: "
+ "${validValues.map((e) => "'$e'").join(', ')}.");
+ }
+ }
+
+ static Uri fileSystemPathToUri(String path) {
+ if (path.endsWith(Platform.pathSeparator)) {
+ return Uri.directory(path);
+ }
+ return Uri.file(path);
+ }
+}
diff --git a/pkgs/cli_config/pubspec.yaml b/pkgs/cli_config/pubspec.yaml
new file mode 100644
index 0000000..54a5363
--- /dev/null
+++ b/pkgs/cli_config/pubspec.yaml
@@ -0,0 +1,18 @@
+name: cli_config
+description: >-
+ A library to take config values from configuration files, CLI arguments, and
+ environment variables.
+version: 0.2.1-wip
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/cli_config
+issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Acli_config
+
+environment:
+ sdk: ^3.4.0
+
+dependencies:
+ args: ^2.4.0
+ yaml: ^3.1.1
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ test: ^1.21.0
diff --git a/pkgs/cli_config/test/cli_config_example_test.dart b/pkgs/cli_config/test/cli_config_example_test.dart
new file mode 100644
index 0000000..43ed7b9
--- /dev/null
+++ b/pkgs/cli_config/test/cli_config_example_test.dart
@@ -0,0 +1,106 @@
+// Copyright (c) 2023, 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 'helpers.dart';
+
+void main() {
+ test('resolve command line paths relative to working directory', () async {
+ await inTempDir((tempUri) async {
+ final rootUri = Directory.current.uri.normalizePath();
+ final examplePackageUri =
+ rootUri.resolve('example${Platform.pathSeparator}');
+ final entryPoint = 'bin${Platform.pathSeparator}cli_config_example.dart';
+ const pubSpec = 'pubspec.yaml';
+ for (final filename in [entryPoint, pubSpec]) {
+ final targetUri = tempUri.resolve(filename);
+ await File.fromUri(targetUri).create(recursive: true);
+ await File.fromUri(examplePackageUri.resolve(filename))
+ .copy(targetUri.toFilePath());
+ }
+ final pubspecFile = File.fromUri(tempUri.resolve(pubSpec));
+ await pubspecFile.writeAsString(
+ (await pubspecFile.readAsString())
+ .replaceAll('path: ../', 'path: ${rootUri.toFilePath()}'),
+ );
+
+ final pubGetResult = await runProcess(
+ executable: Uri.file(Platform.resolvedExecutable),
+ arguments: ['pub', 'get'],
+ workingDirectory: tempUri,
+ );
+ expect(pubGetResult.exitCode, 0);
+
+ {
+ final commandLinePath = Uri.file(
+ 'a${Platform.pathSeparator}b${Platform.pathSeparator}d.ext');
+ final result = await runProcess(
+ executable: Uri.file(Platform.resolvedExecutable),
+ arguments: [
+ tempUri.resolve(entryPoint).toFilePath(),
+ '-Dmy_path=${commandLinePath.toFilePath()}'
+ ],
+ workingDirectory: rootUri,
+ );
+ final stdout = (result.stdout as String).trim();
+ final resolvedPath = Uri.file(stdout);
+ expect(resolvedPath, rootUri.resolveUri(commandLinePath));
+ }
+
+ {
+ final commandLinePath = Uri.file(
+ 'a${Platform.pathSeparator}b${Platform.pathSeparator}d.ext');
+ final result = await runProcess(
+ executable: Uri.file(Platform.resolvedExecutable),
+ arguments: [
+ tempUri.resolve(entryPoint).toFilePath(),
+ '-Dmy_path=${commandLinePath.toFilePath()}'
+ ],
+ workingDirectory: tempUri,
+ );
+ final stdout = (result.stdout as String).trim();
+ final resolvedPath = Uri.file(stdout);
+ expect(resolvedPath, tempUri.resolveUri(commandLinePath));
+ }
+
+ final pathInFile =
+ Uri.file('a${Platform.pathSeparator}b${Platform.pathSeparator}d.ext');
+ final configUri = tempUri.resolve('config.yaml');
+ await File.fromUri(configUri).writeAsString('''
+my_path: ${pathInFile.toFilePath()}
+''');
+
+ {
+ final result = await runProcess(
+ executable: Uri.file(Platform.resolvedExecutable),
+ arguments: [
+ tempUri.resolve(entryPoint).toFilePath(),
+ '--config=${configUri.toFilePath()}'
+ ],
+ workingDirectory: tempUri,
+ );
+ final stdout = (result.stdout as String).trim();
+ final resolvedPath = Uri.file(stdout);
+ expect(resolvedPath, tempUri.resolveUri(pathInFile));
+ }
+
+ {
+ final result = await runProcess(
+ executable: Uri.file(Platform.resolvedExecutable),
+ arguments: [
+ tempUri.resolve(entryPoint).toFilePath(),
+ '--config=${configUri.toFilePath()}'
+ ],
+ workingDirectory: rootUri,
+ );
+ final stdout = (result.stdout as String).trim();
+ final resolvedPath = Uri.file(stdout);
+ expect(resolvedPath, tempUri.resolveUri(pathInFile));
+ }
+ });
+ });
+}
diff --git a/pkgs/cli_config/test/cli_config_test.dart b/pkgs/cli_config/test/cli_config_test.dart
new file mode 100644
index 0000000..073002c
--- /dev/null
+++ b/pkgs/cli_config/test/cli_config_test.dart
@@ -0,0 +1,567 @@
+// Copyright (c) 2023, 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:cli_config/cli_config.dart';
+import 'package:test/test.dart';
+
+import 'helpers.dart';
+
+void main() {
+ test('optionalStringList', () {
+ const path1 = 'path/in/cli_arguments/';
+ const path2 = 'path/in/cli_arguments_2/';
+ const path3 = 'path/in/environment/';
+ const path4 = 'path/in/environment_2/';
+ const path5 = 'path/in/config_file/';
+ const path6 = 'path/in/config_file_2/';
+ final config = Config.fromConfigFileContents(
+ commandLineDefines: [
+ 'build.out_dir=$path1',
+ 'build.out_dir=$path2',
+ ],
+ environment: {
+ 'BUILD__OUT_DIR': '$path3:$path4',
+ },
+ fileContents: jsonEncode(
+ {
+ 'build': {
+ 'out_dir': [
+ path5,
+ path6,
+ ],
+ }
+ },
+ ),
+ );
+
+ {
+ final result = config.optionalStringList(
+ 'build.out_dir',
+ combineAllConfigs: true,
+ splitEnvironmentPattern: ':',
+ );
+ expect(result, [path1, path2, path3, path4, path5, path6]);
+ }
+
+ {
+ final result = config.optionalStringList(
+ 'build.out_dir',
+ combineAllConfigs: false,
+ splitEnvironmentPattern: ':',
+ );
+ expect(result, [path1, path2]);
+ }
+ });
+
+ test('optionalString cli precedence', () {
+ const path1 = 'path/in/cli_arguments/';
+ const path2 = 'path/in/environment/';
+ const path3 = 'path/in/config_file/';
+ final config = Config.fromConfigFileContents(
+ commandLineDefines: [
+ 'build.out_dir=$path1',
+ ],
+ environment: {
+ 'BUILD__OUT_DIR': path2,
+ },
+ fileContents: jsonEncode(
+ {
+ 'build': {
+ 'out_dir': path3,
+ }
+ },
+ ),
+ );
+
+ final result = config.optionalString(
+ 'build.out_dir',
+ );
+ expect(result, path1);
+ });
+
+ test('optionalString environment precedence', () {
+ const path2 = 'path/in/environment/';
+ const path3 = 'path/in/config_file/';
+ final config = Config.fromConfigFileContents(
+ commandLineDefines: [],
+ environment: {
+ 'BUILD__OUT_DIR': path2,
+ },
+ fileContents: jsonEncode(
+ {
+ 'build': {
+ 'out_dir': path3,
+ }
+ },
+ ),
+ );
+
+ final result = config.optionalString(
+ 'build.out_dir',
+ );
+ expect(result, path2);
+ });
+
+ test('optionalString config file', () {
+ const path3 = 'path/in/config_file/';
+ final config = Config.fromConfigFileContents(
+ commandLineDefines: [],
+ environment: {},
+ fileContents: jsonEncode(
+ {
+ 'build': {
+ 'out_dir': path3,
+ }
+ },
+ ),
+ );
+
+ final result = config.optionalString(
+ 'build.out_dir',
+ );
+ expect(result, path3);
+ });
+
+ test('optionalBool define', () {
+ final config = Config(
+ commandLineDefines: ['my_bool=true'],
+ );
+
+ expect(config.optionalBool('my_bool'), true);
+ });
+
+ test('optionalBool environment', () {
+ final config = Config(
+ environment: {
+ 'MY_BOOL': 'true',
+ },
+ );
+
+ expect(config.optionalBool('my_bool'), true);
+ });
+
+ test('optionalBool file', () {
+ final config = Config.fromConfigFileContents(
+ fileContents: jsonEncode(
+ {'my_bool': true},
+ ),
+ );
+
+ expect(config.optionalBool('my_bool'), true);
+ });
+
+ test('Read file and parse CLI args', () async {
+ final temp = await Directory.systemTemp.createTemp();
+ final configFile = File.fromUri(temp.uri.resolve('config.yaml'));
+ await configFile.writeAsString(jsonEncode(
+ {
+ 'build': {
+ 'out_dir': 'path/in/config_file/',
+ }
+ },
+ ));
+ final config = await Config.fromArguments(
+ arguments: [
+ '--config',
+ configFile.path,
+ '-Dbuild.out_dir=path/in/cli_arguments/',
+ ],
+ environment: {
+ 'BUILD__OUT_DIR': 'path/in/environment',
+ },
+ );
+
+ final result = config.optionalString('build.out_dir');
+ expect(result, 'path/in/cli_arguments/');
+ });
+
+ test('Resolve config file path relative to config file', () async {
+ final temp = await Directory.systemTemp.createTemp();
+ final tempUri = temp.uri;
+ final configUri = tempUri.resolve('config.yaml');
+ final configFile = File.fromUri(configUri);
+ const relativePath = 'path/in/config_file/';
+ final resolvedPath = configUri.resolve(relativePath);
+
+ await configFile.writeAsString(jsonEncode(
+ {
+ 'build': {
+ 'out_dir': relativePath,
+ }
+ },
+ ));
+
+ final config = await Config.fromArguments(
+ arguments: [
+ '--config',
+ configFile.path,
+ ],
+ );
+ final result = config.optionalPath('build.out_dir');
+ expect(result!.path, resolvedPath.path);
+
+ final configSync = Config.fromArgumentsSync(
+ arguments: [
+ '--config',
+ configFile.path,
+ ],
+ );
+ final resultSync = configSync.optionalPath('build.out_dir');
+ expect(resultSync!.path, resolvedPath.path);
+ });
+
+ test('provide pre-parsed config', () {
+ const path3 = 'path/in/config_file/';
+ final config = Config(
+ commandLineDefines: [],
+ environment: {},
+ fileParsed: {
+ 'build': {
+ 'out_dir': path3,
+ }
+ },
+ );
+
+ final result = config.optionalString('build.out_dir');
+ expect(result, path3);
+ });
+
+ test('path exists', () async {
+ await inTempDir((tempUri) async {
+ final tempFileUri = tempUri.resolve('file.ext');
+ await File.fromUri(tempFileUri).create();
+ final nonExistUri = tempUri.resolve('foo.ext');
+ final config = Config(
+ commandLineDefines: [],
+ environment: {},
+ fileParsed: {
+ 'build': {
+ 'out_dir': tempUri.toFilePath(),
+ 'file': tempFileUri.toFilePath(),
+ 'non_exist': nonExistUri.toFilePath(),
+ }
+ },
+ );
+
+ final result = config.optionalPath('build.out_dir', mustExist: true);
+ expect(result, tempUri);
+ final result2 = config.optionalPath('build.file', mustExist: true);
+ expect(result2, tempFileUri);
+ expect(
+ () => config.optionalPath('build.non_exist', mustExist: true),
+ throwsFormatException,
+ );
+ });
+ });
+
+ test('wrong CLI key format', () {
+ expect(
+ () => Config(commandLineDefines: ['CAPITALIZED=value']),
+ throwsFormatException,
+ );
+ });
+
+ test('CLI two values when expecting one', () {
+ final config = Config(commandLineDefines: ['key=value', 'key=value2']);
+ expect(
+ () => config.string('key'),
+ throwsFormatException,
+ );
+ });
+
+ test('CLI split optionalStringList', () {
+ final config = Config(commandLineDefines: ['key=value;value2']);
+ final value = config.optionalStringList('key', splitCliPattern: ';');
+ expect(value, ['value', 'value2']);
+ });
+
+ test('CLI path', () {
+ final uri = Uri.file('some/path.ext');
+ final config = Config(commandLineDefines: ['key=${uri.path}']);
+ final value = config.optionalPath('key');
+ expect(value, uri);
+ });
+
+ test('CLI path list', () {
+ final uri = Uri.file('some/path.ext');
+ final uri2 = Uri.file('some/directory/');
+ final config = Config(commandLineDefines: ['key=${uri.path}:${uri2.path}']);
+ final value = config.optionalPathList('key', splitCliPattern: ':');
+ expect(value, [uri, uri2]);
+ });
+
+ test('toString', () {
+ final config = Config(
+ commandLineDefines: ['key=foo'],
+ environment: {'key': 'bar'},
+ fileParsed: {'key': 'baz'},
+ );
+ config.toString();
+ });
+
+ test('Missing nonullable throws FormatException', () {
+ final config = Config.fromConfigFileContents();
+ expect(() => config.bool('key'), throwsFormatException);
+ expect(() => config.string('key'), throwsFormatException);
+ expect(() => config.path('key'), throwsFormatException);
+ });
+
+ test('string not validValue throws FormatException', () {
+ final config = Config(environment: {'foo': 'bar'});
+ expect(
+ () => config.string('foo', validValues: ['not_bar']),
+ throwsFormatException,
+ );
+ });
+
+ test('optionalString validValues', () {
+ final config = Config();
+ expect(config.optionalString('foo', validValues: ['bar']), isNull);
+ });
+
+ test('valueOf file source', () {
+ final config = Config(fileParsed: {
+ 'key': {'some': 'map'}
+ });
+ final value = config.valueOf<Map<dynamic, dynamic>>('key');
+ expect(value, {'some': 'map'});
+ });
+
+ test('valueOf command line source', () {
+ final config = Config(commandLineDefines: [
+ 'string_key=value',
+ 'bool_key=true',
+ 'string_list_key=value1',
+ 'string_list_key=value2',
+ ]);
+ expect(config.valueOf<String>('string_key'), 'value');
+ expect(config.valueOf<bool>('bool_key'), true);
+ expect(
+ config.valueOf<List<String>>('string_list_key'),
+ ['value1', 'value2'],
+ );
+ });
+
+ test('environment split optionalStringList', () {
+ final config = Config(environment: {'key': 'value;value2'});
+ final value =
+ config.optionalStringList('key', splitEnvironmentPattern: ';');
+ expect(value, ['value', 'value2']);
+ });
+
+ test('environment non split optionalStringList', () {
+ final config = Config(environment: {'key': 'value'});
+ final value = config.optionalStringList('key');
+ expect(value, ['value']);
+ });
+
+ test('environment path', () {
+ final uri = Uri.file('some/path.ext');
+ final config = Config(environment: {'key': uri.path});
+ final value = config.optionalPath('key');
+ expect(value, uri);
+ });
+
+ test('environment path list', () {
+ final uri = Uri.file('some/path.ext');
+ final uri2 = Uri.file('some/directory/');
+ final config = Config(environment: {'key': '${uri.path}:${uri2.path}'});
+ final value = config.optionalPathList('key', splitEnvironmentPattern: ':');
+ expect(value, [uri, uri2]);
+ });
+
+ test('Unexpected config file contents', () {
+ expect(() => Config.fromConfigFileContents(fileContents: 'asdf'),
+ throwsFormatException);
+ expect(() => Config.fromConfigFileContents(fileContents: "['asdf']"),
+ throwsFormatException);
+ expect(
+ () => Config.fromConfigFileContents(
+ fileContents: '''
+WRONGKEY:
+ 1
+'''
+ .trim()),
+ throwsFormatException,
+ );
+ expect(
+ () => Config.fromConfigFileContents(
+ fileContents: '''
+1: 'asdf'
+'''
+ .trim()),
+ throwsFormatException,
+ );
+ });
+
+ test('file config try to access object as wrong type', () {
+ final config = Config.fromConfigFileContents(fileContents: '''foo:
+ bar:
+ true
+''');
+ expect(config.bool('foo.bar'), true);
+ expect(() => config.bool('foo.bar.baz'), throwsFormatException);
+ expect(() => config.string('foo.bar'), throwsFormatException);
+ });
+
+ test('file config path list unresolved', () {
+ final uri = Uri.file('some/path.ext');
+ final uri2 = Uri.file('some/directory/');
+ final config = Config(fileParsed: {
+ 'key': [uri.path, uri2.path]
+ });
+ final value = config.optionalPathList('key', resolveUri: false);
+ expect(value, [uri, uri2]);
+ });
+
+ test('file config path list resolved', () {
+ final configUri = Uri.file('path/to/config.json');
+ final uri = Uri.file('some/path.ext');
+ final uri2 = Uri.file('some/directory/');
+ final config = Config(
+ fileSourceUri: configUri,
+ fileParsed: {
+ 'key': [uri.path, uri2.path]
+ },
+ );
+ final value = config.optionalPathList('key', resolveUri: true);
+ expect(value, [configUri.resolveUri(uri), configUri.resolveUri(uri2)]);
+ });
+
+ test('resolveUri in working directory', () {
+ final systemTemp = Directory.systemTemp.uri;
+ final tempUri = systemTemp.resolve('x/y/z/');
+
+ final relativePath = Uri.file('a/b/c/d.ext');
+ final absolutePath = tempUri.resolveUri(relativePath);
+ final config = Config(
+ commandLineDefines: ['path=${relativePath.path}'],
+ workingDirectory: tempUri,
+ );
+
+ expect(config.optionalPath('path', mustExist: false, resolveUri: true),
+ absolutePath);
+ });
+
+ test('ints', () {
+ final config = Config(
+ commandLineDefines: ['cl=1', 'not_parsable=asdf'],
+ environment: {
+ 'env': '2',
+ 'not_parsable2': 'asfd',
+ },
+ fileParsed: {'file': 3},
+ );
+
+ expect(config.int('cl'), 1);
+ expect(config.optionalInt('env'), 2);
+ expect(config.optionalInt('file'), 3);
+ expect(config.optionalInt('nothing'), null);
+ expect(() => config.optionalInt('not_parsable'), throwsFormatException);
+ expect(() => config.optionalInt('not_parsable2'), throwsFormatException);
+ });
+
+ test('doubles', () {
+ final config = Config(
+ commandLineDefines: ['cl=1.1', 'not_parsable=asdf'],
+ environment: {
+ 'env': '2.2',
+ 'not_parsable2': 'asfd',
+ },
+ fileParsed: {'file': 3.3},
+ );
+
+ expect(config.double('cl'), 1.1);
+ expect(config.optionalDouble('env'), 2.2);
+ expect(config.optionalDouble('file'), 3.3);
+ expect(config.optionalDouble('nothing'), null);
+ expect(() => config.optionalDouble('not_parsable'), throwsFormatException);
+ expect(() => config.optionalDouble('not_parsable2'), throwsFormatException);
+ });
+
+ test('stringList and optionalStringList', () {
+ {
+ final config = Config(
+ fileParsed: {},
+ );
+
+ expect(config.optionalStringList('my_list'), null);
+ expect(() => config.stringList('my_list'), throwsFormatException);
+ }
+
+ {
+ final config = Config(
+ fileParsed: {'my_list': <String>[]},
+ );
+
+ expect(config.optionalStringList('my_list'), <String>[]);
+ expect(config.stringList('my_list'), <String>[]);
+ }
+ });
+
+ test('pathList and optionalPathList', () {
+ {
+ final config = Config(
+ fileParsed: {},
+ );
+
+ expect(config.optionalPathList('my_list'), null);
+ expect(() => config.pathList('my_list'), throwsFormatException);
+ }
+
+ {
+ final config = Config(
+ fileParsed: {'my_list': <String>[]},
+ );
+
+ expect(config.optionalPathList('my_list'), <String>[]);
+ expect(config.pathList('my_list'), <String>[]);
+ }
+ });
+
+ test('non-string key maps', () {
+ // This is valid in YAML (not in JSON).
+ //
+ // Such values cannot be accessed with our hierarchical keys, but they can
+ // be accessed with [Config.valueOf].
+ final config = Config(
+ fileParsed: {
+ 'my_non_string_key_map': {
+ 1: 'asdf',
+ 2: 'foo',
+ },
+ },
+ );
+
+ expect(
+ config.valueOf<Map<Object, Object?>>('my_non_string_key_map'),
+ {
+ 1: 'asdf',
+ 2: 'foo',
+ },
+ );
+ });
+
+ test('null values in maps', () {
+ final config = Config(
+ fileParsed: {
+ 'my_non_string_key_map': {
+ 'x': null,
+ 'y': 42,
+ },
+ },
+ );
+
+ expect(
+ config.valueOf<Map<Object, Object?>>('my_non_string_key_map'),
+ {
+ 'x': null,
+ 'y': 42,
+ },
+ );
+ });
+}
diff --git a/pkgs/cli_config/test/cli_config_windows_test.dart b/pkgs/cli_config/test/cli_config_windows_test.dart
new file mode 100644
index 0000000..2c401f2
--- /dev/null
+++ b/pkgs/cli_config/test/cli_config_windows_test.dart
@@ -0,0 +1,23 @@
+// Copyright (c) 2023, 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('windows')
+library;
+
+import 'package:cli_config/cli_config.dart';
+import 'package:test/test.dart';
+
+void main() {
+ test('path resolving windows', () {
+ const path = 'C:\\foo\\bar\\';
+ final workingDirectory = Uri.parse('file:///C:/baz/baf/');
+
+ final config = Config(
+ commandLineDefines: ['key=$path'],
+ workingDirectory: workingDirectory,
+ );
+ final value = config.path('key', resolveUri: true);
+ expect(value.toFilePath(), path);
+ });
+}
diff --git a/pkgs/cli_config/test/helpers.dart b/pkgs/cli_config/test/helpers.dart
new file mode 100644
index 0000000..d9fb40e
--- /dev/null
+++ b/pkgs/cli_config/test/helpers.dart
@@ -0,0 +1,43 @@
+// Copyright (c) 2023, 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';
+
+const keepTempKey = 'KEEP_TEMPORARY_DIRECTORIES';
+
+Future<void> inTempDir(
+ Future<void> Function(Uri tempUri) fun, {
+ String? prefix,
+}) async {
+ final tempDir = await Directory.systemTemp.createTemp(prefix);
+ // Deal with Windows temp folder aliases.
+ final tempUri =
+ Directory(await tempDir.resolveSymbolicLinks()).uri.normalizePath();
+ try {
+ await fun(tempUri);
+ } finally {
+ if (!Platform.environment.containsKey(keepTempKey) ||
+ Platform.environment[keepTempKey]!.isEmpty) {
+ await tempDir.delete(recursive: true);
+ }
+ }
+}
+
+Future<ProcessResult> runProcess({
+ required Uri executable,
+ List<String> arguments = const [],
+ required Uri workingDirectory,
+}) async {
+ final result = await Process.run(
+ executable.toFilePath(),
+ arguments,
+ workingDirectory: workingDirectory.toFilePath(),
+ );
+ if (result.exitCode != 0) {
+ print(result.stdout);
+ print(result.stderr);
+ print(result.exitCode);
+ }
+ return result;
+}
diff --git a/pkgs/cli_util/.gitignore b/pkgs/cli_util/.gitignore
new file mode 100644
index 0000000..49ce72d
--- /dev/null
+++ b/pkgs/cli_util/.gitignore
@@ -0,0 +1,3 @@
+.dart_tool/
+.packages
+pubspec.lock
diff --git a/pkgs/cli_util/AUTHORS b/pkgs/cli_util/AUTHORS
new file mode 100644
index 0000000..7a6d1d9
--- /dev/null
+++ b/pkgs/cli_util/AUTHORS
@@ -0,0 +1,6 @@
+# Below is a list of people and organizations that have contributed
+# to the cli_util project. Names should be added to the list like so:
+#
+# Name/Organization <email address>
+
+Google Inc.
diff --git a/pkgs/cli_util/CHANGELOG.md b/pkgs/cli_util/CHANGELOG.md
new file mode 100644
index 0000000..9e77b8f
--- /dev/null
+++ b/pkgs/cli_util/CHANGELOG.md
@@ -0,0 +1,105 @@
+## 0.4.2
+
+- Add `sdkPath` getter, deprecate `getSdkPath` function.
+* Move to `dart-lang/tools` monorepo.
+
+## 0.4.1
+
+- Fix a broken link in the readme.
+- Require Dart 3.0.
+
+## 0.4.0
+
+- Remove the deprecated method `getSdkDir()` (instead, use `getSdkPath()`).
+- Require Dart 2.19.
+
+## 0.3.5
+- Make `applicationConfigHome` throw an `Exception` when it fails to find a
+ configuration folder.
+
+## 0.3.4
+
+- Introduce `applicationConfigHome` for making it easy to consistently find the
+ user-specific application configuration folder.
+
+## 0.3.3
+
+- Reverted `meta` constraint to `^1.3.0`.
+
+## 0.3.2
+
+- Update `meta` constraint to `>=1.3.0 <3.0.0`.
+
+## 0.3.1
+
+- Fix a bug in `AnsiProgress` where the spinning character doesn't every update.
+
+## 0.3.0
+
+- Stable null safety release.
+
+## 0.3.0-nullsafety.0
+
+- Updated to support 2.12.0 and null safety.
+
+## 0.2.1
+
+## 0.2.0
+
+- Add `Logger.write` and `Logger.writeCharCode` methods which write without
+ printing a trailing newline.
+
+## 0.1.4
+
+- Add `Ansi.reversed` getter.
+
+## 0.1.3+2
+
+- Update Dart SDK constraint to < 3.0.0.
+
+## 0.1.3+1
+
+- Update Dart SDK to 2.0.0-dev.
+
+## 0.1.3
+
+- In verbose mode, instead of printing the diff from the last log message,
+ print the total time since the tool started
+- Change to not buffer the last log message sent in verbose logging mode
+- Expose more classes from the logging library
+
+## 0.1.2+1
+
+- Remove unneeded change to Dart SDK constraint.
+
+## 0.1.2
+
+- Fix a bug in `getSdkDir` (#21)
+
+## 0.1.1
+
+- Updated to the output for indeterminate progress
+- Exposed a `Logger.isVerbose` getter
+
+## 0.1.0
+
+- Added a new `getSdkPath()` method to get the location of the SDK (this uses the new
+ `Platform.resolvedExecutable` API to locate the SDK)
+- Deprecated `getSdkDir()` in favor of `getSdkPath()`
+- Add the `cli_logging.dart` library - utilities to display output and progress
+
+## 0.0.1+3
+
+- Find SDK properly when invoked from inside SDK tests.
+
+## 0.0.1+2
+
+- Support an executable in a symlinked directory.
+
+## 0.0.1+1
+
+- Fix for when the dart executable can't be found by `which`.
+
+## 0.0.1
+
+- Initial version
diff --git a/pkgs/cli_util/LICENSE b/pkgs/cli_util/LICENSE
new file mode 100644
index 0000000..dbd2843
--- /dev/null
+++ b/pkgs/cli_util/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2015, 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.
diff --git a/pkgs/cli_util/README.md b/pkgs/cli_util/README.md
new file mode 100644
index 0000000..b86e8c0
--- /dev/null
+++ b/pkgs/cli_util/README.md
@@ -0,0 +1,68 @@
+[](https://github.com/dart-lang/tools/actions/workflows/cli_util.yaml)
+[](https://pub.dev/packages/cli_util)
+[](https://pub.dev/packages/cli_util/publisher)
+
+A package to help in building Dart command-line apps.
+
+## What's this?
+
+`package:cli_util` provides:
+- utilities to find the Dart SDK directory (`sdkPath`)
+- utilities to find the settings directory for a tool (`applicationConfigHome()`)
+- utilities to aid in showing rich CLI output and progress information (`cli_logging.dart`)
+
+## Locating the Dart SDK
+
+```dart
+import 'dart:io';
+
+import 'package:cli_util/cli_util.dart';
+import 'package:path/path.dart' as path;
+
+main(args) {
+ // Get SDK directory from cli_util.
+ var sdkDir = sdkPath;
+
+ // Do stuff... For example, print version string
+ var versionFile = File(path.join(sdkDir, 'version'));
+ print(versionFile.readAsStringSync());
+}
+```
+
+## Displaying output and progress
+
+`package:cli_util` can also be used to help CLI tools display output and progress.
+It has a logging mechanism which can help differentiate between regular tool
+output and error messages, and can facilitate having a more verbose (`-v`) mode for
+output.
+
+In addition, it can display an indeterminate progress spinner for longer running
+tasks, and optionally display the elapsed time when finished:
+
+```dart
+import 'package:cli_util/cli_logging.dart';
+
+void main(List<String> args) async {
+ var verbose = args.contains('-v');
+ var logger = verbose ? Logger.verbose() : Logger.standard();
+
+ logger.stdout('Hello world!');
+ logger.trace('message 1');
+ await Future.delayed(Duration(milliseconds: 200));
+ logger.trace('message 2');
+ logger.trace('message 3');
+
+ var progress = logger.progress('doing some work');
+ await Future.delayed(Duration(seconds: 2));
+ progress.finish(showTiming: true);
+
+ logger.stdout('All ${logger.ansi.emphasized('done')}.');
+ logger.flush();
+}
+```
+
+## Features and bugs
+
+Please file feature requests and bugs at the [issue tracker][tracker].
+
+[tracker]: https://github.com/dart-lang/tools/issues
diff --git a/pkgs/cli_util/analysis_options.yaml b/pkgs/cli_util/analysis_options.yaml
new file mode 100644
index 0000000..a30462e
--- /dev/null
+++ b/pkgs/cli_util/analysis_options.yaml
@@ -0,0 +1,32 @@
+# https://dart.dev/guides/language/analysis-options
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-casts: true
+ strict-inference: true
+ strict-raw-types: true
+
+linter:
+ rules:
+ - avoid_bool_literals_in_conditional_expressions
+ - avoid_classes_with_only_static_members
+ - avoid_private_typedef_functions
+ - avoid_redundant_argument_values
+ - avoid_returning_this
+ - avoid_unused_constructor_parameters
+ - avoid_void_async
+ - cancel_subscriptions
+ - join_return_with_assignment
+ - literal_only_boolean_expressions
+ - missing_whitespace_between_adjacent_strings
+ - no_adjacent_strings_in_list
+ - no_runtimeType_toString
+ - prefer_const_declarations
+ - prefer_expression_function_bodies
+ - prefer_final_locals
+ - unnecessary_await_in_return
+ - unnecessary_raw_strings
+ - use_if_null_to_convert_nulls_to_bools
+ - use_raw_strings
+ - use_string_buffers
diff --git a/pkgs/cli_util/example/main.dart b/pkgs/cli_util/example/main.dart
new file mode 100644
index 0000000..3bae827
--- /dev/null
+++ b/pkgs/cli_util/example/main.dart
@@ -0,0 +1,24 @@
+// Copyright (c) 2017, 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 'package:cli_util/cli_logging.dart';
+
+Future<void> main(List<String> args) async {
+ final verbose = args.contains('-v');
+ final logger = verbose ? Logger.verbose() : Logger.standard();
+
+ logger.stdout('Hello world!');
+ logger.trace('message 1');
+ await Future<void>.delayed(const Duration(milliseconds: 200));
+ logger.trace('message 2');
+ logger.trace('message 3');
+
+ final progress = logger.progress('doing some work');
+ await Future<void>.delayed(const Duration(seconds: 2));
+ progress.finish(showTiming: true);
+
+ logger.stdout('All ${logger.ansi.emphasized('done')}.');
+}
diff --git a/pkgs/cli_util/lib/cli_logging.dart b/pkgs/cli_util/lib/cli_logging.dart
new file mode 100644
index 0000000..dda287b
--- /dev/null
+++ b/pkgs/cli_util/lib/cli_logging.dart
@@ -0,0 +1,316 @@
+// Copyright (c) 2017, 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.
+
+/// This library contains functionality to help command-line utilities to easily
+/// create aesthetic output.
+library;
+
+import 'dart:async';
+import 'dart:io' as io;
+
+/// A small utility class to make it easier to work with common ANSI escape
+/// sequences.
+class Ansi {
+ /// Return whether the current stdout terminal supports ANSI escape sequences.
+ static bool get terminalSupportsAnsi =>
+ io.stdout.supportsAnsiEscapes &&
+ io.stdioType(io.stdout) == io.StdioType.terminal;
+
+ final bool useAnsi;
+
+ Ansi(this.useAnsi);
+
+ String get cyan => _code('\u001b[36m');
+
+ String get green => _code('\u001b[32m');
+
+ String get magenta => _code('\u001b[35m');
+
+ String get red => _code('\u001b[31m');
+
+ String get yellow => _code('\u001b[33m');
+
+ String get blue => _code('\u001b[34m');
+
+ String get gray => _code('\u001b[1;30m');
+
+ String get noColor => _code('\u001b[39m');
+
+ String get none => _code('\u001b[0m');
+
+ String get bold => _code('\u001b[1m');
+
+ String get reversed => _code('\u001b[7m');
+
+ String get backspace => '\b';
+
+ String get bullet => io.stdout.supportsAnsiEscapes ? '•' : '-';
+
+ /// Display [message] in an emphasized format.
+ String emphasized(String message) => '$bold$message$none';
+
+ /// Display [message] in an subtle (gray) format.
+ String subtle(String message) => '$gray$message$none';
+
+ /// Display [message] in an error (red) format.
+ String error(String message) => '$red$message$none';
+
+ String _code(String ansiCode) => useAnsi ? ansiCode : '';
+}
+
+/// An abstract representation of a [Logger] - used to pretty print errors,
+/// standard status messages, trace level output, and indeterminate progress.
+abstract class Logger {
+ /// Create a normal [Logger]; this logger will not display trace level output.
+ factory Logger.standard({Ansi? ansi}) => StandardLogger(ansi: ansi);
+
+ /// Create a [Logger] that will display trace level output.
+ ///
+ /// If [logTime] is `true`, this logger will display the time of the message.
+ factory Logger.verbose({Ansi? ansi, bool logTime = true}) =>
+ VerboseLogger(ansi: ansi, logTime: logTime);
+
+ Ansi get ansi;
+
+ bool get isVerbose;
+
+ /// Print an error message.
+ void stderr(String message);
+
+ /// Print a standard status message.
+ void stdout(String message);
+
+ /// Print trace output.
+ void trace(String message);
+
+ /// Print text to stdout, without a trailing newline.
+ void write(String message);
+
+ /// Print a character code to stdout, without a trailing newline.
+ void writeCharCode(int charCode);
+
+ /// Start an indeterminate progress display.
+ Progress progress(String message);
+
+ /// Flush any un-written output.
+ @Deprecated('This method will be removed in the future')
+ void flush();
+}
+
+/// A handle to an indeterminate progress display.
+abstract class Progress {
+ final String message;
+ final Stopwatch _stopwatch;
+
+ Progress(this.message) : _stopwatch = Stopwatch()..start();
+
+ Duration get elapsed => _stopwatch.elapsed;
+
+ /// Finish the indeterminate progress display.
+ void finish({String? message, bool showTiming = false});
+
+ /// Cancel the indeterminate progress display.
+ void cancel();
+}
+
+class StandardLogger implements Logger {
+ @override
+ Ansi ansi;
+
+ StandardLogger({Ansi? ansi}) : ansi = ansi ?? Ansi(Ansi.terminalSupportsAnsi);
+
+ @override
+ bool get isVerbose => false;
+
+ Progress? _currentProgress;
+
+ @override
+ void stderr(String message) {
+ _cancelProgress();
+
+ io.stderr.writeln(message);
+ }
+
+ @override
+ void stdout(String message) {
+ _cancelProgress();
+
+ print(message);
+ }
+
+ @override
+ void trace(String message) {}
+
+ @override
+ void write(String message) {
+ _cancelProgress();
+
+ io.stdout.write(message);
+ }
+
+ @override
+ void writeCharCode(int charCode) {
+ _cancelProgress();
+
+ io.stdout.writeCharCode(charCode);
+ }
+
+ void _cancelProgress() {
+ final progress = _currentProgress;
+ if (progress != null) {
+ _currentProgress = null;
+ progress.cancel();
+ }
+ }
+
+ @override
+ Progress progress(String message) {
+ _cancelProgress();
+
+ final progress = ansi.useAnsi
+ ? AnsiProgress(ansi, message)
+ : SimpleProgress(this, message);
+ _currentProgress = progress;
+ return progress;
+ }
+
+ @override
+ @Deprecated('This method will be removed in the future')
+ void flush() {}
+}
+
+class SimpleProgress extends Progress {
+ final Logger logger;
+
+ SimpleProgress(this.logger, String message) : super(message) {
+ logger.stdout('$message...');
+ }
+
+ @override
+ void cancel() {}
+
+ @override
+ void finish({String? message, bool showTiming = false}) {}
+}
+
+class AnsiProgress extends Progress {
+ static const List<String> kAnimationItems = ['/', '-', r'\', '|'];
+
+ final Ansi ansi;
+
+ late final Timer _timer;
+
+ AnsiProgress(this.ansi, String message) : super(message) {
+ _timer = Timer.periodic(const Duration(milliseconds: 80), (t) {
+ _updateDisplay();
+ });
+ io.stdout.write('$message... '.padRight(40));
+ _updateDisplay();
+ }
+
+ @override
+ void cancel() {
+ if (_timer.isActive) {
+ _timer.cancel();
+ _updateDisplay(cancelled: true);
+ }
+ }
+
+ @override
+ void finish({String? message, bool showTiming = false}) {
+ if (_timer.isActive) {
+ _timer.cancel();
+ _updateDisplay(isFinal: true, message: message, showTiming: showTiming);
+ }
+ }
+
+ void _updateDisplay(
+ {bool isFinal = false,
+ bool cancelled = false,
+ String? message,
+ bool showTiming = false}) {
+ var char = kAnimationItems[_timer.tick % kAnimationItems.length];
+ if (isFinal || cancelled) {
+ char = '';
+ }
+ io.stdout.write('${ansi.backspace}$char');
+ if (isFinal || cancelled) {
+ if (message != null) {
+ io.stdout.write(message.isEmpty ? ' ' : message);
+ } else if (showTiming) {
+ final time = (elapsed.inMilliseconds / 1000.0).toStringAsFixed(1);
+ io.stdout.write('${time}s');
+ } else {
+ io.stdout.write(' ');
+ }
+ io.stdout.writeln();
+ }
+ }
+}
+
+class VerboseLogger implements Logger {
+ @override
+ Ansi ansi;
+ bool logTime;
+ final _timer = Stopwatch()..start();
+
+ VerboseLogger({Ansi? ansi, this.logTime = false})
+ : ansi = ansi ?? Ansi(Ansi.terminalSupportsAnsi);
+
+ @override
+ bool get isVerbose => true;
+
+ @override
+ void stdout(String message) {
+ io.stdout.writeln('${_createPrefix()}$message');
+ }
+
+ @override
+ void stderr(String message) {
+ io.stderr.writeln('${_createPrefix()}${ansi.red}$message${ansi.none}');
+ }
+
+ @override
+ void trace(String message) {
+ io.stdout.writeln('${_createPrefix()}${ansi.gray}$message${ansi.none}');
+ }
+
+ @override
+ void write(String message) {
+ io.stdout.write(message);
+ }
+
+ @override
+ void writeCharCode(int charCode) {
+ io.stdout.writeCharCode(charCode);
+ }
+
+ @override
+ Progress progress(String message) => SimpleProgress(this, message);
+
+ @override
+ @Deprecated('This method will be removed in the future')
+ void flush() {}
+
+ String _createPrefix() {
+ if (!logTime) {
+ return '';
+ }
+
+ var seconds = _timer.elapsedMilliseconds / 1000.0;
+ final minutes = seconds ~/ 60;
+ seconds -= minutes * 60.0;
+
+ final buf = StringBuffer();
+ if (minutes > 0) {
+ buf.write(minutes % 60);
+ buf.write('m ');
+ }
+
+ buf.write(seconds.toStringAsFixed(3).padLeft(minutes > 0 ? 6 : 1, '0'));
+ buf.write('s');
+
+ return '[${buf.toString().padLeft(11)}] ';
+ }
+}
diff --git a/pkgs/cli_util/lib/cli_util.dart b/pkgs/cli_util/lib/cli_util.dart
new file mode 100644
index 0000000..e497d68
--- /dev/null
+++ b/pkgs/cli_util/lib/cli_util.dart
@@ -0,0 +1,90 @@
+// Copyright (c) 2015, 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.
+
+/// Utilities to locate the Dart SDK.
+library;
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:path/path.dart' as path;
+
+/// The path to the current Dart SDK.
+String get sdkPath => path.dirname(path.dirname(Platform.resolvedExecutable));
+
+/// Returns the path to the current Dart SDK.
+@Deprecated("Use 'sdkPath' instead")
+String getSdkPath() => sdkPath;
+
+/// The user-specific application configuration folder for the current platform.
+///
+/// This is a location appropriate for storing application specific
+/// configuration for the current user. The [productName] should be unique to
+/// avoid clashes with other applications on the same machine. This method won't
+/// actually create the folder, merely return the recommended location for
+/// storing user-specific application configuration.
+///
+/// The folder location depends on the platform:
+/// * `%APPDATA%\<productName>` on **Windows**,
+/// * `$HOME/Library/Application Support/<productName>` on **Mac OS**,
+/// * `$XDG_CONFIG_HOME/<productName>` on **Linux**
+/// (if `$XDG_CONFIG_HOME` is defined), and,
+/// * `$HOME/.config/<productName>` otherwise.
+///
+/// The chosen location aims to follow best practices for each platform,
+/// honoring the [XDG Base Directory Specification][1] on Linux and
+/// [File System Basics][2] on Mac OS.
+///
+/// Throws an [EnvironmentNotFoundException] if an environment entry,
+/// `%APPDATA%` or `$HOME`, is needed and not available.
+///
+/// [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
+String applicationConfigHome(String productName) =>
+ path.join(_configHome, productName);
+
+String get _configHome {
+ if (Platform.isWindows) {
+ return _requireEnv('APPDATA');
+ }
+
+ if (Platform.isMacOS) {
+ return path.join(_requireEnv('HOME'), 'Library', 'Application Support');
+ }
+
+ if (Platform.isLinux) {
+ final xdgConfigHome = _env['XDG_CONFIG_HOME'];
+ if (xdgConfigHome != null) {
+ return xdgConfigHome;
+ }
+ // XDG Base Directory Specification says to use $HOME/.config/ when
+ // $XDG_CONFIG_HOME isn't defined.
+ return path.join(_requireEnv('HOME'), '.config');
+ }
+
+ // We have no guidelines, perhaps we should just do: $HOME/.config/
+ // same as XDG specification would specify as fallback.
+ return path.join(_requireEnv('HOME'), '.config');
+}
+
+String _requireEnv(String name) =>
+ _env[name] ?? (throw EnvironmentNotFoundException(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.
+class EnvironmentNotFoundException implements Exception {
+ /// Name of environment entry which was needed, but not found.
+ final String entryName;
+ String get message => 'Environment variable \'$entryName\' is not defined!';
+ EnvironmentNotFoundException(this.entryName);
+ @override
+ String toString() => message;
+}
+
+// This zone override exists solely for testing (see lib/cli_util_test.dart).
+Map<String, String> get _env =>
+ (Zone.current[#environmentOverrides] as Map<String, String>?) ??
+ Platform.environment;
diff --git a/pkgs/cli_util/pubspec.yaml b/pkgs/cli_util/pubspec.yaml
new file mode 100644
index 0000000..ab879db
--- /dev/null
+++ b/pkgs/cli_util/pubspec.yaml
@@ -0,0 +1,16 @@
+name: cli_util
+version: 0.4.2
+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
+
+environment:
+ sdk: ^3.4.0
+
+dependencies:
+ meta: ^1.7.0
+ path: ^1.8.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ test: ^1.20.0
diff --git a/pkgs/cli_util/test/cli_util_test.dart b/pkgs/cli_util/test/cli_util_test.dart
new file mode 100644
index 0000000..e16bc59
--- /dev/null
+++ b/pkgs/cli_util/test/cli_util_test.dart
@@ -0,0 +1,39 @@
+// Copyright (c) 2015, 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';
+
+import 'package:cli_util/cli_util.dart';
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+
+void main() {
+ group('sdkPath', () {
+ test('sdkPath', () {
+ expect(sdkPath, isNotNull);
+ });
+ });
+
+ group('applicationConfigHome', () {
+ test('returns a non-empty string', () {
+ expect(applicationConfigHome('dart'), isNotEmpty);
+ });
+
+ test('has an ancestor folder that exists', () {
+ final path = p.split(applicationConfigHome('dart'));
+ // We expect that first two segments of the path exist. This is really
+ // just a dummy check that some part of the path exists.
+ expect(Directory(p.joinAll(path.take(2))).existsSync(), isTrue);
+ });
+
+ test('empty environment throws exception', () async {
+ expect(() {
+ runZoned(() => applicationConfigHome('dart'), zoneValues: {
+ #environmentOverrides: <String, String>{},
+ });
+ }, throwsA(isA<EnvironmentNotFoundException>()));
+ });
+ });
+}
diff --git a/pkgs/clock/.gitignore b/pkgs/clock/.gitignore
new file mode 100644
index 0000000..63fe85d
--- /dev/null
+++ b/pkgs/clock/.gitignore
@@ -0,0 +1,5 @@
+.packages
+.pub/
+.dart_tool/
+build/
+pubspec.lock
diff --git a/pkgs/clock/AUTHORS b/pkgs/clock/AUTHORS
new file mode 100644
index 0000000..e8063a8
--- /dev/null
+++ b/pkgs/clock/AUTHORS
@@ -0,0 +1,6 @@
+# Below is a list of people and organizations that have contributed
+# to the project. Names should be added to the list like so:
+#
+# Name/Organization <email address>
+
+Google Inc.
diff --git a/pkgs/clock/CHANGELOG.md b/pkgs/clock/CHANGELOG.md
new file mode 100644
index 0000000..f372ada
--- /dev/null
+++ b/pkgs/clock/CHANGELOG.md
@@ -0,0 +1,52 @@
+## 1.1.2
+
+* Require Dart 3.4
+* Move to `dart-lang/tools` monorepo.
+
+## 1.1.1
+
+* Switch to using `package:lints`.
+* Populate the pubspec `repository` field.
+
+## 1.1.0
+
+* Update SDK constraints to `>=2.12.0 <3.0.0`.
+* Update to null safety.
+
+## 1.0.1
+
+* Update to lowercase Dart core library constants.
+
+## 1.0.0
+
+This release contains the `Clock` class that was defined in [`quiver`][]. It's
+backwards-compatible with the `quiver` version, and *mostly*
+backwards-compatible with the old version of the `clock` package.
+
+[`quiver`]: https://pub.dartlang.org/packages/quiver
+
+### New Features
+
+* A top-level `clock` field has been added that provides a default `Clock`
+ implementation. It can be controlled by the `withClock()` function. It should
+ generally be used in preference to manual dependency-injection, since it will
+ work with the [`fake_async`][] package.
+
+* A `Clock.stopwatch()` method has been added that creates a `Stopwatch` that
+ uses the clock as its source of time.
+
+[`fake_async`]: https://pub.dartlang.org/packages/fake_async
+
+### Changes Relative to `clock` 0.1
+
+* The top-level `new` getter and `getStopwatch()` methods are deprecated.
+ `clock.new()` and `clock.stopwatch()` should be used instead.
+
+* `Clock.getStopwatch()` is deprecated. `Clock.stopwatch()` should be used instead.
+
+* The `isFinal` argument to `withClock()` is deprecated.
+
+* `new Clock()` now takes an optional positional argument that returns the
+ current time as a `DateTime` instead of its old arguments.
+
+* `Clock.now()` is now a method rather than a getter.
diff --git a/pkgs/clock/LICENSE b/pkgs/clock/LICENSE
new file mode 100644
index 0000000..7a4a3ea
--- /dev/null
+++ b/pkgs/clock/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/pkgs/clock/README.md b/pkgs/clock/README.md
new file mode 100644
index 0000000..dccdc07
--- /dev/null
+++ b/pkgs/clock/README.md
@@ -0,0 +1,52 @@
+[](https://github.com/dart-lang/tools/actions/workflows/clock.yaml)
+[](https://pub.dev/packages/clock)
+[](https://pub.dev/packages/clock/publisher)
+
+This package provides a [`Clock`][] class which encapsulates the notion of the
+"current time" and provides easy access to points relative to the current time.
+Different `Clock`s can have a different notion of the current time, and the
+default top-level [`clock`][]'s notion can be swapped out to reliably test
+timing-dependent code.
+
+[`Clock`]: https://pub.dev/documentation/clock/latest/clock/Clock-class.html
+[`clock`]: https://pub.dev/documentation/clock/latest/clock/clock.html
+
+For example, you can use `clock` in your libraries like this:
+
+```dart
+// run_with_timing.dart
+import 'package:clock/clock.dart';
+
+/// Runs [callback] and prints how long it took.
+T runWithTiming<T>(T Function() callback) {
+ var stopwatch = clock.stopwatch()..start();
+ var result = callback();
+ print('It took ${stopwatch.elapsed}!');
+ return result;
+}
+```
+
+...and then test your code using the [`fake_async`][] package, which
+automatically overrides the current clock:
+
+[`fake_async`]: https://pub.dartlang.org/packages/fake_async
+
+```dart
+// run_with_timing_test.dart
+import 'run_with_timing.dart';
+
+import 'package:fake_async/fake_async.dart';
+import 'package:test/test.dart';
+
+void main() {
+ test('runWithTiming() prints the elapsed time', () {
+ FakeAsync().run((async) {
+ expect(() {
+ runWithTiming(() {
+ async.elapse(Duration(seconds: 10));
+ });
+ }, prints('It took 0:00:10.000000!'));
+ });
+ });
+}
+```
diff --git a/pkgs/clock/analysis_options.yaml b/pkgs/clock/analysis_options.yaml
new file mode 100644
index 0000000..db6072d
--- /dev/null
+++ b/pkgs/clock/analysis_options.yaml
@@ -0,0 +1,13 @@
+# https://dart.dev/guides/language/analysis-options
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-casts: true
+ strict-inference: true
+ strict-raw-types: true
+
+linter:
+ rules:
+ - avoid_private_typedef_functions
+ - avoid_redundant_argument_values
diff --git a/pkgs/clock/lib/clock.dart b/pkgs/clock/lib/clock.dart
new file mode 100644
index 0000000..755789e
--- /dev/null
+++ b/pkgs/clock/lib/clock.dart
@@ -0,0 +1,34 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'src/default.dart';
+
+export 'src/clock.dart';
+export 'src/default.dart';
+
+/// Returns current time.
+@Deprecated('Pass around an instance of Clock instead.')
+typedef TimeFunction = DateTime Function();
+
+/// Returns the current system time.
+@Deprecated('Use new DateTime.now() instead.')
+DateTime systemTime() => DateTime.now();
+
+/// Returns the current time as reported by [clock].
+@Deprecated('Use clock.now() instead.')
+DateTime get now => clock.now();
+
+/// Returns a stopwatch that uses the current time as reported by [clock].
+@Deprecated('Use clock.stopwatch() instead.')
+Stopwatch getStopwatch() => clock.stopwatch();
diff --git a/pkgs/clock/lib/src/clock.dart b/pkgs/clock/lib/src/clock.dart
new file mode 100644
index 0000000..f6f47de
--- /dev/null
+++ b/pkgs/clock/lib/src/clock.dart
@@ -0,0 +1,182 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import '../clock.dart';
+import 'stopwatch.dart';
+import 'utils.dart';
+
+/// A provider for the "current time" and points relative to the current time.
+///
+/// This class is designed with testability in mind. The current point in time
+/// (or [now()]) is defined by a function that returns a [DateTime]. By
+/// supplying your own time function or using [Clock.fixed], you can control
+/// exactly what time a [Clock] returns and base your test expectations on that.
+///
+/// Most users should use the top-level [clock] field, which provides access to
+/// a default implementation of [Clock] which can be overridden using
+/// [withClock].
+class Clock {
+ /// The function that's called to determine this clock's notion of the current
+ /// time.
+ final DateTime Function() _time;
+
+ /// Creates a clock based on the given [currentTime], or on the system clock
+ /// by default.
+ // ignore: deprecated_member_use_from_same_package
+ const Clock([DateTime Function() currentTime = systemTime])
+ : _time = currentTime;
+
+ /// Creates [Clock] that always considers the current time to be [time].
+ Clock.fixed(DateTime time) : _time = (() => time);
+
+ /// Returns current time.
+ DateTime now() => _time();
+
+ /// Returns the point in time [Duration] amount of time ago.
+ DateTime agoBy(Duration duration) => now().subtract(duration);
+
+ /// Returns the point in time [Duration] amount of time from now.
+ DateTime fromNowBy(Duration duration) => now().add(duration);
+
+ /// Returns the point in time that's given amount of time ago.
+ ///
+ /// The amount of time is the sum of the individual parts.
+ DateTime ago(
+ {int days = 0,
+ int hours = 0,
+ int minutes = 0,
+ int seconds = 0,
+ int milliseconds = 0,
+ int microseconds = 0}) =>
+ agoBy(Duration(
+ days: days,
+ hours: hours,
+ minutes: minutes,
+ seconds: seconds,
+ milliseconds: milliseconds,
+ microseconds: microseconds));
+
+ /// Returns the point in time that's given amount of time from now.
+ ///
+ /// The amount of time is the sum of the individual parts.
+ DateTime fromNow(
+ {int days = 0,
+ int hours = 0,
+ int minutes = 0,
+ int seconds = 0,
+ int milliseconds = 0,
+ int microseconds = 0}) =>
+ fromNowBy(Duration(
+ days: days,
+ hours: hours,
+ minutes: minutes,
+ seconds: seconds,
+ milliseconds: milliseconds,
+ microseconds: microseconds));
+
+ /// Return the point in time [microseconds] ago.
+ DateTime microsAgo(int microseconds) => ago(microseconds: microseconds);
+
+ /// Return the point in time [microseconds] from now.
+ DateTime microsFromNow(int microseconds) =>
+ fromNow(microseconds: microseconds);
+
+ /// Return the point in time [milliseconds] ago.
+ DateTime millisAgo(int milliseconds) => ago(milliseconds: milliseconds);
+
+ /// Return the point in time [milliseconds] from now.
+ DateTime millisFromNow(int milliseconds) =>
+ fromNow(milliseconds: milliseconds);
+
+ /// Return the point in time [seconds] ago.
+ DateTime secondsAgo(int seconds) => ago(seconds: seconds);
+
+ /// Return the point in time [seconds] from now.
+ DateTime secondsFromNow(int seconds) => fromNow(seconds: seconds);
+
+ /// Return the point in time [minutes] ago.
+ DateTime minutesAgo(int minutes) => ago(minutes: minutes);
+
+ /// Return the point in time [minutes] from now.
+ DateTime minutesFromNow(int minutes) => fromNow(minutes: minutes);
+
+ /// Return the point in time [hours] ago.
+ DateTime hoursAgo(int hours) => ago(hours: hours);
+
+ /// Return the point in time [hours] from now.
+ DateTime hoursFromNow(int hours) => fromNow(hours: hours);
+
+ /// Return the point in time [days] ago.
+ DateTime daysAgo(int days) => ago(days: days);
+
+ /// Return the point in time [days] from now.
+ DateTime daysFromNow(int days) => fromNow(days: days);
+
+ /// Return the point in time [weeks] ago.
+ DateTime weeksAgo(int weeks) => ago(days: 7 * weeks);
+
+ /// Return the point in time [weeks] from now.
+ DateTime weeksFromNow(int weeks) => fromNow(days: 7 * weeks);
+
+ /// Return the point in time [months] ago on the same date.
+ ///
+ /// If the current day of the month isn't valid in the new month, the nearest
+ /// valid day in the new month will be used.
+ DateTime monthsAgo(int months) {
+ var time = now();
+ var month = (time.month - months - 1) % 12 + 1;
+ 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);
+ }
+
+ /// Return the point in time [months] from now on the same date.
+ ///
+ /// If the current day of the month isn't valid in the new month, the nearest
+ /// valid day in the new month will be used.
+ DateTime monthsFromNow(int months) {
+ var time = now();
+ var month = (time.month + months - 1) % 12 + 1;
+ 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);
+ }
+
+ /// Return the point in time [years] ago on the same date.
+ ///
+ /// If the current day of the month isn't valid in the new year, the nearest
+ /// valid day in the original month will be used.
+ DateTime yearsAgo(int years) {
+ var time = now();
+ 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);
+ }
+
+ /// Return the point in time [years] from now on the same date.
+ ///
+ /// If the current day of the month isn't valid in the new year, the nearest
+ /// valid day in the original month will be used.
+ DateTime yearsFromNow(int years) => yearsAgo(-years);
+
+ /// Returns a new stopwatch that uses the current time as reported by `this`.
+ Stopwatch stopwatch() => ClockStopwatch(this);
+
+ /// Returns a new stopwatch that uses the current time as reported by `this`.
+ @Deprecated('Use stopwatch() instead.')
+ Stopwatch getStopwatch() => stopwatch();
+}
diff --git a/pkgs/clock/lib/src/default.dart b/pkgs/clock/lib/src/default.dart
new file mode 100644
index 0000000..2a46b9f
--- /dev/null
+++ b/pkgs/clock/lib/src/default.dart
@@ -0,0 +1,54 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'dart:async';
+
+import 'clock.dart';
+
+/// The key for the [Zone] value that controls the current implementation of
+/// [clock].
+final _clockKey = Object();
+
+/// The key for the [Zone] value that controls whether nested zones can override
+/// [clock].
+final _isFinalKey = Object();
+
+/// The default implementation of [clock] for the current [Zone].
+///
+/// This defaults to the system clock. It can be set within a zone using
+/// [withClock].
+Clock get clock => Zone.current[_clockKey] as Clock? ?? const Clock();
+
+/// Runs [callback] with the given value for the top-level [clock] field.
+///
+/// This is [Zone]-scoped, so asynchronous callbacks spawned within [callback]
+/// will also use the new value for [clock].
+///
+// ignore: deprecated_member_use_from_same_package
+/// If [isFinal] is `true`, calls to [withClock] within [callback] will throw a
+/// [StateError]. However, this parameter is deprecated and should be avoided.
+T withClock<T>(
+ Clock clock,
+ T Function() callback, {
+ @Deprecated('This parameter is deprecated and should be avoided')
+ bool isFinal = false,
+}) {
+ if ((Zone.current[_isFinalKey] ?? false) == true) {
+ throw StateError(
+ 'Cannot call withClock() within a call to withClock(isFinal = true).');
+ }
+
+ return runZoned(callback,
+ zoneValues: {_clockKey: clock, _isFinalKey: isFinal});
+}
diff --git a/pkgs/clock/lib/src/stopwatch.dart b/pkgs/clock/lib/src/stopwatch.dart
new file mode 100644
index 0000000..93fe1ab
--- /dev/null
+++ b/pkgs/clock/lib/src/stopwatch.dart
@@ -0,0 +1,73 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'clock.dart';
+
+/// The system's timer frequency in Hz.
+///
+/// We can't really know how frequently the clock is updated, and that may not
+/// even make sense for some implementations, so we just pretend we follow the
+/// system's frequency.
+final _frequency = Stopwatch().frequency;
+
+/// A stopwatch that gets its notion of the current time from a [Clock].
+class ClockStopwatch implements Stopwatch {
+ /// The provider for this stopwatch's notion of the current time.
+ final Clock _clock;
+
+ /// The number of elapsed microseconds that have been recorded from previous
+ /// runs of this stopwatch.
+ ///
+ /// This doesn't include the time between [_start] and the current time.
+ var _elapsed = 0;
+
+ /// The point at which [start] was called most recently, or `null` if this
+ /// isn't active.
+ DateTime? _start;
+
+ ClockStopwatch(this._clock);
+
+ @override
+ int get frequency => _frequency;
+ @override
+ int get elapsedTicks => (elapsedMicroseconds * frequency) ~/ 1000000;
+ @override
+ Duration get elapsed => Duration(microseconds: elapsedMicroseconds);
+ @override
+ int get elapsedMilliseconds => elapsedMicroseconds ~/ 1000;
+ @override
+ bool get isRunning => _start != null;
+
+ @override
+ int get elapsedMicroseconds =>
+ _elapsed +
+ (_start == null ? 0 : _clock.now().difference(_start!).inMicroseconds);
+
+ @override
+ void start() {
+ _start ??= _clock.now();
+ }
+
+ @override
+ void stop() {
+ _elapsed = elapsedMicroseconds;
+ _start = null;
+ }
+
+ @override
+ void reset() {
+ _elapsed = 0;
+ if (_start != null) _start = _clock.now();
+ }
+}
diff --git a/pkgs/clock/lib/src/utils.dart b/pkgs/clock/lib/src/utils.dart
new file mode 100644
index 0000000..4301b09
--- /dev/null
+++ b/pkgs/clock/lib/src/utils.dart
@@ -0,0 +1,59 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// This code is copied from quiver. We don't take on an explicit dependency
+// because quiver is very large and the amount of code we use from it is very
+// small.
+
+/// The number of days in each month.
+///
+/// This array uses 1-based month numbers, i.e. January is the 1-st element in
+/// the array, not the 0-th.
+const _daysInMonth = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
+
+/// Returns the number of days in the specified month.
+///
+/// This function assumes the use of the Gregorian calendar or the proleptic
+/// Gregorian calendar.
+int daysInMonth(int year, int month) =>
+ (month == DateTime.february && isLeapYear(year)) ? 29 : _daysInMonth[month];
+
+/// Returns true if [year] is a leap year.
+///
+/// This implements the Gregorian calendar leap year rules wherein a year is
+/// considered to be a leap year if it is divisible by 4, excepting years
+/// divisible by 100, but including years divisible by 400.
+///
+/// This function assumes the use of the Gregorian calendar or the proleptic
+/// Gregorian calendar.
+bool isLeapYear(int year) =>
+ year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
+
+/// Takes a `date` that may be outside the allowed range of dates for a given
+/// [month] in a given [year] and returns the closest date that is within the
+/// allowed range.
+///
+/// For example:
+///
+/// February 31, 2013 => February 28, 2013
+///
+/// When jumping from month to month or from leap year to common year we may
+/// end up in a month that has fewer days than the month we are jumping from.
+/// In that case it is impossible to preserve the exact date. So we "clamp" the
+/// date value to fit within the month. For example, jumping from March 31 one
+/// month back takes us to February 28 (or 29 during a leap year), as February
+/// doesn't have 31-st date.
+int clampDayOfMonth(
+ {required int year, required int month, required int day}) =>
+ day.clamp(1, daysInMonth(year, month));
diff --git a/pkgs/clock/pubspec.yaml b/pkgs/clock/pubspec.yaml
new file mode 100644
index 0000000..605aa09
--- /dev/null
+++ b/pkgs/clock/pubspec.yaml
@@ -0,0 +1,12 @@
+name: clock
+version: 1.1.2
+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
+
+environment:
+ sdk: ^3.4.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ test: ^1.16.6
diff --git a/pkgs/clock/test/clock_test.dart b/pkgs/clock/test/clock_test.dart
new file mode 100644
index 0000000..c457153
--- /dev/null
+++ b/pkgs/clock/test/clock_test.dart
@@ -0,0 +1,210 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the 'License');
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an 'AS IS' BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'package:clock/clock.dart';
+
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ late Clock clock;
+ setUp(() {
+ clock = Clock.fixed(date(2013));
+ });
+
+ test('should return a non-null value from system clock', () {
+ expect(const Clock().now(), isNotNull);
+ });
+
+ // This test may be flaky on certain systems. I ran it over 10 million
+ // cycles on my machine without any failures, but that's no guarantee.
+ test('should be close enough to system clock', () {
+ // At 10ms the test doesn't seem to be flaky.
+ var epsilon = 10;
+ expect(DateTime.now().difference(const Clock().now()).inMilliseconds.abs(),
+ lessThan(epsilon));
+ expect(DateTime.now().difference(const Clock().now()).inMilliseconds.abs(),
+ lessThan(epsilon));
+ });
+
+ test('should return time provided by a custom function', () {
+ var time = date(2013);
+ var fixedClock = Clock(() => time);
+ expect(fixedClock.now(), date(2013));
+
+ time = date(2014);
+ expect(fixedClock.now(), date(2014));
+ });
+
+ test('should return fixed time', () {
+ expect(Clock.fixed(date(2013)).now(), date(2013));
+ });
+
+ test('should return time Duration ago', () {
+ expect(clock.agoBy(const Duration(days: 366)), date(2012));
+ });
+
+ test('should return time Duration from now', () {
+ expect(clock.fromNowBy(const Duration(days: 365)), date(2014));
+ });
+
+ test('should return time parts ago', () {
+ expect(
+ clock.ago(
+ days: 1,
+ hours: 1,
+ minutes: 1,
+ seconds: 1,
+ milliseconds: 1,
+ microseconds: 1000),
+ DateTime(2012, 12, 30, 22, 58, 58, 998));
+ });
+
+ test('should return time parts from now', () {
+ expect(
+ clock.fromNow(
+ days: 1,
+ hours: 1,
+ minutes: 1,
+ seconds: 1,
+ milliseconds: 1,
+ microseconds: 1000),
+ DateTime(2013, 1, 2, 1, 1, 1, 2));
+ });
+
+ test('should return time micros ago', () {
+ expect(clock.microsAgo(1000), DateTime(2012, 12, 31, 23, 59, 59, 999));
+ });
+
+ test('should return time micros from now', () {
+ expect(clock.microsFromNow(1000), DateTime(2013, 1, 1, 0, 0, 0, 1));
+ });
+
+ test('should return time millis ago', () {
+ expect(clock.millisAgo(1000), DateTime(2012, 12, 31, 23, 59, 59));
+ });
+
+ test('should return time millis from now', () {
+ expect(clock.millisFromNow(3), DateTime(2013, 1, 1, 0, 0, 0, 3));
+ });
+
+ test('should return time seconds ago', () {
+ expect(clock.secondsAgo(10), DateTime(2012, 12, 31, 23, 59, 50));
+ });
+
+ test('should return time seconds from now', () {
+ expect(clock.secondsFromNow(3), DateTime(2013, 1, 1, 0, 0, 3));
+ });
+
+ test('should return time minutes ago', () {
+ expect(clock.minutesAgo(10), DateTime(2012, 12, 31, 23, 50));
+ });
+
+ test('should return time minutes from now', () {
+ expect(clock.minutesFromNow(3), DateTime(2013, 1, 1, 0, 3));
+ });
+
+ test('should return time hours ago', () {
+ expect(clock.hoursAgo(10), DateTime(2012, 12, 31, 14));
+ });
+
+ test('should return time hours from now', () {
+ expect(clock.hoursFromNow(3), DateTime(2013, 1, 1, 3));
+ });
+
+ test('should return time days ago', () {
+ expect(clock.daysAgo(10), date(2012, 12, 22));
+ });
+
+ test('should return time days from now', () {
+ expect(clock.daysFromNow(3), date(2013, 1, 4));
+ });
+
+ test('should return time months ago on the same date', () {
+ expect(clock.monthsAgo(1), date(2012, 12, 1));
+ expect(clock.monthsAgo(2), date(2012, 11, 1));
+ expect(clock.monthsAgo(3), date(2012, 10, 1));
+ expect(clock.monthsAgo(4), date(2012, 9, 1));
+ });
+
+ test('should return time months from now on the same date', () {
+ expect(clock.monthsFromNow(1), date(2013, 2, 1));
+ expect(clock.monthsFromNow(2), date(2013, 3, 1));
+ expect(clock.monthsFromNow(3), date(2013, 4, 1));
+ expect(clock.monthsFromNow(4), date(2013, 5, 1));
+ });
+
+ test('should go from 2013-05-31 to 2012-11-30', () {
+ expect(fixed(2013, 5, 31).monthsAgo(6), date(2012, 11, 30));
+ });
+
+ test('should go from 2013-03-31 to 2013-02-28 (common year)', () {
+ expect(fixed(2013, 3, 31).monthsAgo(1), date(2013, 2, 28));
+ });
+
+ test('should go from 2013-05-31 to 2013-02-28 (common year)', () {
+ expect(fixed(2013, 5, 31).monthsAgo(3), date(2013, 2, 28));
+ });
+
+ test('should go from 2004-03-31 to 2004-02-29 (leap year)', () {
+ expect(fixed(2004, 3, 31).monthsAgo(1), date(2004, 2, 29));
+ });
+
+ test('should go from 2013-03-31 to 2013-06-30', () {
+ expect(fixed(2013, 3, 31).monthsFromNow(3), date(2013, 6, 30));
+ });
+
+ test('should go from 2003-12-31 to 2004-02-29 (common to leap)', () {
+ expect(fixed(2003, 12, 31).monthsFromNow(2), date(2004, 2, 29));
+ });
+
+ test('should go from 2004-02-29 to 2003-02-28 by year', () {
+ expect(fixed(2004, 2, 29).yearsAgo(1), date(2003, 2, 28));
+ });
+
+ test('should go from 2004-02-29 to 2003-02-28 by month', () {
+ expect(fixed(2004, 2, 29).monthsAgo(12), date(2003, 2, 28));
+ });
+
+ test('should go from 2004-02-29 to 2005-02-28 by year', () {
+ expect(fixed(2004, 2, 29).yearsFromNow(1), date(2005, 2, 28));
+ });
+
+ test('should go from 2004-02-29 to 2005-02-28 by month', () {
+ expect(fixed(2004, 2, 29).monthsFromNow(12), date(2005, 2, 28));
+ });
+
+ test('should return time years ago on the same date', () {
+ expect(clock.yearsAgo(1), date(2012, 1, 1)); // leap year
+ expect(clock.yearsAgo(2), date(2011, 1, 1));
+ expect(clock.yearsAgo(3), date(2010, 1, 1));
+ expect(clock.yearsAgo(4), date(2009, 1, 1));
+ expect(clock.yearsAgo(5), date(2008, 1, 1)); // leap year
+ expect(clock.yearsAgo(6), date(2007, 1, 1));
+ expect(clock.yearsAgo(30), date(1983, 1, 1));
+ expect(clock.yearsAgo(2013), date(0, 1, 1));
+ });
+
+ test('should return time years from now on the same date', () {
+ expect(clock.yearsFromNow(1), date(2014, 1, 1));
+ expect(clock.yearsFromNow(2), date(2015, 1, 1));
+ expect(clock.yearsFromNow(3), date(2016, 1, 1));
+ expect(clock.yearsFromNow(4), date(2017, 1, 1));
+ expect(clock.yearsFromNow(5), date(2018, 1, 1));
+ expect(clock.yearsFromNow(6), date(2019, 1, 1));
+ expect(clock.yearsFromNow(30), date(2043, 1, 1));
+ expect(clock.yearsFromNow(1000), date(3013, 1, 1));
+ });
+}
diff --git a/pkgs/clock/test/default_test.dart b/pkgs/clock/test/default_test.dart
new file mode 100644
index 0000000..80acd15
--- /dev/null
+++ b/pkgs/clock/test/default_test.dart
@@ -0,0 +1,84 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the 'License');
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an 'AS IS' BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'package:clock/clock.dart';
+
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ test('the default clock returns the system time', () {
+ expect(DateTime.now().difference(clock.now()).inMilliseconds.abs(),
+ lessThan(100));
+ });
+
+ group('withClock()', () {
+ group('overrides the clock', () {
+ test('synchronously', () {
+ var time = date(1990, 11, 8);
+ withClock(Clock(() => time), () {
+ expect(clock.now(), equals(time));
+ time = date(2016, 6, 26);
+ expect(clock.now(), equals(time));
+ });
+ });
+
+ test('asynchronously', () {
+ var time = date(1990, 11, 8);
+ withClock(Clock.fixed(time), () {
+ expect(Future(() async {
+ expect(clock.now(), equals(time));
+ }), completes);
+ });
+ });
+
+ test('within another withClock() call', () {
+ var outerTime = date(1990, 11, 8);
+ withClock(Clock.fixed(outerTime), () {
+ expect(clock.now(), equals(outerTime));
+
+ var innerTime = date(2016, 11, 8);
+ withClock(Clock.fixed(innerTime), () {
+ expect(clock.now(), equals(innerTime));
+ expect(Future(() async {
+ expect(clock.now(), equals(innerTime));
+ }), completes);
+ });
+
+ expect(clock.now(), equals(outerTime));
+ });
+ });
+ });
+
+ test("with isFinal: true doesn't allow nested calls", () {
+ var outerTime = date(1990, 11, 8);
+ withClock(Clock.fixed(outerTime), () {
+ expect(clock.now(), equals(outerTime));
+
+ expect(() => withClock(fixed(2016, 11, 8), neverCalledVoid),
+ throwsStateError);
+
+ expect(clock.now(), equals(outerTime));
+ // ignore: deprecated_member_use_from_same_package
+ }, isFinal: true);
+ });
+ });
+}
+
+/// A wrapper for [neverCalled] that works around sdk#33015.
+void Function() get neverCalledVoid {
+ var function = neverCalled;
+ return () => function();
+}
diff --git a/pkgs/clock/test/stopwatch_test.dart b/pkgs/clock/test/stopwatch_test.dart
new file mode 100644
index 0000000..e96c0f7
--- /dev/null
+++ b/pkgs/clock/test/stopwatch_test.dart
@@ -0,0 +1,171 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the 'License');
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an 'AS IS' BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'package:clock/clock.dart';
+
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ test('returns the system frequency', () {
+ expect(fixed(1990, 11, 8).stopwatch().frequency,
+ equals(Stopwatch().frequency));
+ });
+
+ group('before it starts', () {
+ late Stopwatch stopwatch;
+ setUp(() {
+ stopwatch = clock.stopwatch();
+ });
+
+ test('is not running', () => expect(stopwatch.isRunning, isFalse));
+
+ test('stop() does nothing', () {
+ stopwatch.stop();
+ expect(stopwatch.isRunning, isFalse);
+ expect(stopwatch.elapsed, equals(Duration.zero));
+ });
+
+ group('reports no elapsed', () {
+ test('duration', () => expect(stopwatch.elapsed, equals(Duration.zero)));
+ test('ticks', () => expect(stopwatch.elapsedTicks, isZero));
+ test('microseconds', () => expect(stopwatch.elapsedMicroseconds, isZero));
+ test('milliseconds', () => expect(stopwatch.elapsedMilliseconds, isZero));
+ });
+ });
+
+ group('when 12345μs have elapsed', () {
+ late DateTime time;
+ late Clock clock;
+ late Stopwatch stopwatch;
+ setUp(() {
+ time = date(1990, 11, 8);
+ clock = Clock(() => time);
+ stopwatch = clock.stopwatch()..start();
+ time = clock.microsFromNow(12345);
+ });
+
+ group('and the stopwatch is active', () {
+ test('is running', () {
+ expect(stopwatch.isRunning, isTrue);
+ });
+
+ test('reports more elapsed time', () {
+ time = clock.microsFromNow(54321);
+ expect(stopwatch.elapsedMicroseconds, equals(66666));
+ });
+
+ test('start does nothing', () {
+ stopwatch.start();
+ expect(stopwatch.isRunning, isTrue);
+ expect(stopwatch.elapsedMicroseconds, equals(12345));
+ });
+
+ group('reset()', () {
+ setUp(() {
+ stopwatch.reset();
+ });
+
+ test('sets the elapsed time to zero', () {
+ expect(stopwatch.elapsed, equals(Duration.zero));
+ });
+
+ test('reports more elapsed time', () {
+ time = clock.microsFromNow(54321);
+ expect(stopwatch.elapsedMicroseconds, equals(54321));
+ });
+ });
+
+ group('reports elapsed', () {
+ test('duration', () {
+ expect(
+ stopwatch.elapsed, equals(const Duration(microseconds: 12345)));
+ });
+
+ test('ticks', () {
+ expect(stopwatch.elapsedTicks,
+ equals((Stopwatch().frequency * 12345) ~/ 1000000));
+ });
+
+ test('microseconds', () {
+ expect(stopwatch.elapsedMicroseconds, equals(12345));
+ });
+
+ test('milliseconds', () {
+ expect(stopwatch.elapsedMilliseconds, equals(12));
+ });
+ });
+ });
+
+ group('and the stopwatch is inactive, reports that as', () {
+ setUp(() {
+ stopwatch.stop();
+ });
+
+ test('is not running', () {
+ expect(stopwatch.isRunning, isFalse);
+ });
+
+ test("doesn't report more elapsed time", () {
+ time = clock.microsFromNow(54321);
+ expect(stopwatch.elapsedMicroseconds, equals(12345));
+ });
+
+ test('start starts reporting more elapsed time', () {
+ stopwatch.start();
+ expect(stopwatch.isRunning, isTrue);
+ time = clock.microsFromNow(54321);
+ expect(stopwatch.elapsedMicroseconds, equals(66666));
+ });
+
+ group('reset()', () {
+ setUp(() {
+ stopwatch.reset();
+ });
+
+ test('sets the elapsed time to zero', () {
+ expect(stopwatch.elapsed, equals(Duration.zero));
+ });
+
+ test("doesn't report more elapsed time", () {
+ time = clock.microsFromNow(54321);
+ expect(stopwatch.elapsed, equals(Duration.zero));
+ });
+ });
+
+ group('reports elapsed', () {
+ test('duration', () {
+ expect(
+ stopwatch.elapsed, equals(const Duration(microseconds: 12345)));
+ });
+
+ test('ticks', () {
+ expect(stopwatch.elapsedTicks,
+ equals((Stopwatch().frequency * 12345) ~/ 1000000));
+ });
+
+ test('microseconds', () {
+ expect(stopwatch.elapsedMicroseconds, equals(12345));
+ });
+
+ test('milliseconds', () {
+ expect(stopwatch.elapsedMilliseconds, equals(12));
+ });
+ });
+ });
+ }, onPlatform: {
+ 'js': const Skip('Web does not have enough precision'),
+ });
+}
diff --git a/pkgs/clock/test/utils.dart b/pkgs/clock/test/utils.dart
new file mode 100644
index 0000000..ea4b1df
--- /dev/null
+++ b/pkgs/clock/test/utils.dart
@@ -0,0 +1,25 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'package:clock/clock.dart';
+
+/// A utility function for tersely constructing a [DateTime] with no time
+/// component.
+DateTime date(int year, [int? month, int? day]) =>
+ DateTime(year, month ?? 1, day ?? 1);
+
+/// Returns a clock that always returns a date with the given [year], [month],
+/// and [day].
+Clock fixed(int year, [int? month, int? day]) =>
+ Clock.fixed(date(year, month, day));
diff --git a/pkgs/code_builder/.gitignore b/pkgs/code_builder/.gitignore
new file mode 100644
index 0000000..feb089d
--- /dev/null
+++ b/pkgs/code_builder/.gitignore
@@ -0,0 +1,5 @@
+# Files and directories created by pub
+.dart_tool
+.packages
+.pub
+pubspec.lock
diff --git a/pkgs/code_builder/AUTHORS b/pkgs/code_builder/AUTHORS
new file mode 100644
index 0000000..a904f0c
--- /dev/null
+++ b/pkgs/code_builder/AUTHORS
@@ -0,0 +1,7 @@
+# Below is a list of people and organizations that have contributed
+# to the project. Names should be added to the list like so:
+#
+# Name/Organization <email address>
+
+Nikolas Rimikis <rimikis.nikolas@gmail.com>
+Google Inc.
diff --git a/pkgs/code_builder/CHANGELOG.md b/pkgs/code_builder/CHANGELOG.md
new file mode 100644
index 0000000..5e5521c
--- /dev/null
+++ b/pkgs/code_builder/CHANGELOG.md
@@ -0,0 +1,857 @@
+## 4.10.1
+
+* Require Dart `^3.5.0`
+* Upgrade to `dart_style` 2.3.7.
+* Move to `dart-lang/tools` monorepo.
+
+## 4.10.0
+
+* Add `Library.docs` to support emitting doc comments on libraries.
+* Export `RepresentationDeclaration` and `RepresentationDeclarationBuilder` so
+ they can be used when generating extension types.
+* Upgrade to `dart_style` 2.3.4.
+
+## 4.9.0
+
+* Add `Library.generatedByComment` to support emitting 'generated by' comments.
+* Support emitting an unnamed library with annotations.
+
+## 4.8.0
+
+* Add `Expression.operatorSubtract`
+* Deprecate `Expression.operatorSubstract`
+* Add `Expression.operatorIntDivide`
+* Add `Expression.operatorUnaryPrefixIncrement`
+* Add `Expression.operatorUnaryPostfixIncrement`
+* Add `Expression.operatorUnaryMinus`
+* Add `Expression.operatorUnaryPrefixDecrement`
+* Add `Expression.operatorUnaryPostfixDecrement`
+* Add `Expression.operatorBitwiseAnd`
+* Add `Expression.operatorBitwiseOr`
+* Add `Expression.operatorBitwiseXor`
+* Add `Expression.operatorUnaryBitwiseComplement`
+* Add `Expression.operatorShiftLeft`
+* Add `Expression.operatorShiftRight`
+* Add `Expression.operatorShiftRightUnsigned`
+* Add `Expression.addAssign`
+* Add `Expression.subtractAssign`
+* Add `Expression.multiplyAssign`
+* Add `Expression.divideAssign`
+* Add `Expression.intDivideAssign`
+* Add `Expression.euclideanModuloAssign`
+* Add `Expression.shiftLeftAssign`
+* Add `Expression.shiftRightAssign`
+* Add `Expression.shiftRightUnsignedAssign`
+* Add `Expression.bitwiseAndAssign`
+* Add `Expression.bitwiseXorAssign`
+* Add `Expression.bitwiseOrAssign`
+* Allow passing an `Expression` through `literal` without an exception.
+* Add support for extension types.
+* Update SDK version constraints to `>=3.0.0`.
+
+## 4.7.0
+
+* Add a newline after lambdas.
+
+## 4.6.0
+
+* Add support for named arguments in `enum` classes
+* Add support for external keyword on fields.
+* Add `Expression.parenthesized` to manually wrap an expression in parenthesis.
+
+## 4.5.0
+
+* Require Dart 2.19
+* Add support for emitting type parameters for typedefs.
+* Add support for class modifiers.
+* Add support for records (both types and record literals).
+* Add `literalSpread` and `literalNullSafeSpread` to support adding spreads to
+ `literalMap`.
+
+```dart
+void main() {
+ // Creates a map
+ // {
+ // ...one,
+ // 2: two,
+ // ...?three,
+ // }
+ final map = literalMap({
+ literalSpread(): refer('one'),
+ 2: refer('two'),
+ literalNullSafeSpread(): refer('three'),
+ });
+}
+```
+
+## 4.4.0
+
+* Mention how the `allocator` argument relates to imports in the `DartEmitter`
+ constructor doc.
+* Add support for emitting typedefs.
+* Add support for emitting leading line comments for libraries.
+* Add support for emitting `ignore_for_file` analyzer directive comments.
+
+## 4.3.0
+
+* Add support for adding more implementation in `enum` classes.
+* Only emit `late` keyword when using null safety syntax.
+* Use implicit `const` when assigning to a `declareConst` variable.
+* Deprecate `assignVar`, `assignConst`, and `assignFinal`.
+* Add trailing commas to any parameter list, argument list, or collection
+ literal which has more than one element.
+
+## 4.2.0
+
+* Add an ignore for a lint from the `package:lints` recommended set. The lint,
+ `no_leading_underscores_for_library_prefixes` is most useful for hand edited
+ code where the appearance of a private name that is already not visible
+ outside the library is confusing.
+* Fix the docs for `Expression.assign`, `ifNullThen`, and `assignNullAware`
+ which had the argument and receiver flipped.
+* Add `declareConst`, `declareFinal`, and `declareVar` to replace
+ `Expression.assignConst`, `assignFinal`, and `assignVar`. Add support for late
+ variables with the `declare*` utilities.
+* Add `ParameterBuilder.toSuper` so support super formal parameters language
+ feature.
+
+## 4.1.0
+
+* Add `Expression.spread` for the spread operator `...`.
+* Add support 'late' field modifier.
+* Add support for `Expression.nullChecked` to add a null assertion operator.
+* Add support for creating `mixin`s.
+* Add `Expression.nullSafeSpread` for the null aware spread operator `...?`.
+* A `Library` can now be annotated.
+
+## 4.0.0
+
+* Migrate to null safety.
+* Changed the `DartEmitter` constructor to use named optional parameters.
+* Add `ParenthesizedExpression` and
+ `ExpressionVisitor.visitParenthesizedExpression.`
+
+## 3.7.0
+
+* Add support for converting a Method to a generic closure, with
+ `Method.genericClosure`.
+
+## 3.6.0
+
+* Add support for creating `extension` methods.
+* Expand constraint on `built_value` to allow null safe migrated version.
+
+## 3.5.0
+
+* Add support for defining enums.
+* Fix keyword ordering for `const factory` constructors.
+
+## 3.4.1
+
+* Fix confusing mismatch description from `equalsDart`.
+ https://github.com/dart-lang/code_builder/issues/293
+
+## 3.4.0
+
+* Introduce `Expression.thrown` for throwing an expression.
+* Introduce `FunctionType.isNullable`.
+* Update SDK requirement to `>=2.7.0 <3.0.0`.
+
+## 3.3.0
+
+* Add `??` null-aware operator.
+* Add `..` cascade assignment operator.
+* Add `part` directive.
+* Introduce `TypeReference.isNullable`.
+* Add an option in `DartEmitter` to emit nullable types with trailing `?`
+ characters.
+
+## 3.2.2
+
+* Require minimum Dart SDK of `2.6.0`.
+
+## 3.2.1
+
+* Escape newlines in String literals.
+* Introduce `Expression.or` for boolean OR.
+* Introduce `Expression.negate` for boolean NOT.
+* No longer emits redundant `,`s in `FunctionType`s.
+* Added support for `literalSet` and `literalConstSet`.
+* Depend on the latest `package:built_value`.
+
+## 3.2.0
+
+* Emit `=` instead of `:` for named parameter default values.
+* The `new` keyword will not be used in generated code.
+* The `const` keyword will be omitted when it can be inferred.
+* Add an option in `DartEmitter` to order directives.
+* `DartEmitter` added a `startConstCode` function to track the creation of
+ constant expression trees.
+* `BinaryExpression` added the `final bool isConst` field.
+
+## 3.1.3
+
+* Bump dependency on built_collection to include v4.0.0.
+
+## 3.1.2
+
+* Set max SDK version to `<3.0.0`.
+
+## 3.1.1
+
+* `Expression.asA` is now wrapped with parenthesis so that further calls may be
+ made on it as an expression.
+
+
+## 3.1.0
+
+* Added `Expression.asA` for creating explicit casts:
+
+```dart
+void main() {
+ test('should emit an explicit cast', () {
+ expect(
+ refer('foo').asA(refer('String')),
+ equalsDart('foo as String'),
+ );
+ });
+}
+```
+
+## 3.0.3
+
+* Fix a bug that caused all downstream users of `code_builder` to crash due to
+ `build_runner` trying to import our private builder (in `tool/`). Sorry for
+ the inconvenience.
+
+## 3.0.2
+
+* Require `source_gen: ^0.7.5`.
+
+## 3.0.1
+
+* Upgrade to `built_value` 5.1.0.
+* Export the `literalNum` function.
+* **BUG FIX**: `literal` supports a `Map`.
+
+## 3.0.0
+
+* Also infer `Constructor.lambda` for `factory` constructors.
+
+## 3.0.0-alpha
+
+* Using `equalsDart` no longer formats automatically with `dartfmt`.
+
+* Removed deprecated `Annotation` and `File` classes.
+
+* `Method.lambda` is inferred based on `Method.body` where possible and now
+ defaults to `null`.
+
+## 2.4.0
+
+* Add `equalTo`, `notEqualTo`, `greaterThan`, `lessThan`, `greateOrEqualTo`, and
+ `lessOrEqualTo` to `Expression`.
+
+## 2.3.0
+
+* Using `equalsDart` and expecting `dartfmt` by default is *deprecated*. This
+ requires this package to have a direct dependency on specific versions of
+ `dart_style` (and transitively `analyzer`), which is problematic just for
+ testing infrastructure. To future proof, we've exposed the `EqualsDart` class
+ with a `format` override:
+
+```dart
+// Copyright (c) 2017, 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:code_builder/code_builder.dart';
+import 'package:dart_style/dart_style.dart';
+
+final DartFormatter _dartfmt = new DartFormatter();
+String _format(String source) {
+ try {
+ return _dartfmt.format(source);
+ } on FormatException catch (_) {
+ return _dartfmt.formatStatement(source);
+ }
+}
+
+/// Should be invoked in `main()` of every test in `test/**_test.dart`.
+void useDartfmt() => EqualsDart.format = _format;
+```
+
+* Added `Expression.isA` and `Expression.isNotA`:
+
+```dart
+void main() {
+ test('should emit an is check', () {
+ expect(
+ refer('foo').isA(refer('String')),
+ equalsDart('foo is String'),
+ );
+ });
+}
+```
+
+* Deprecated `Annotation`. It is now legal to simply pass any `Expression` as
+ a metadata annotation to `Class`, `Method`, `Field,` and `Parameter`. In
+ `3.0.0`, the `Annotation` class will be completely removed:
+
+```dart
+void main() {
+ test('should create a class with a annotated constructor', () {
+ expect(
+ new Class((b) => b
+ ..name = 'Foo'
+ ..constructors.add(
+ new Constructor((b) => b..annotations.add(refer('deprecated'))))),
+ equalsDart(r'''
+ class Foo {
+ @deprecated
+ Foo();
+ }
+ '''),
+ );
+ });
+}
+```
+
+* Added inference support for `Method.lambda` and `Constructor.lambda`. If not
+ explicitly provided and the body of the function originated from an
+ `Expression` then `lambda` is inferred to be true. This is not a breaking
+ change yet, as it requires an explicit `null` value. In `3.0.0` this will be
+ the default:
+
+```dart
+void main() {
+ final animal = new Class((b) => b
+ ..name = 'Animal'
+ ..extend = refer('Organism')
+ ..methods.add(new Method.returnsVoid((b) => b
+ ..name = 'eat'
+ // In 3.0.0, this may be omitted and still is inferred.
+ ..lambda = null
+ ..body = refer('print').call([literalString('Yum!')]).code)));
+ final emitter = new DartEmitter();
+ print(new DartFormatter().format('${animal.accept(emitter)}'));
+}
+```
+
+* Added `nullSafeProperty` to `Expression` to access properties with `?.`
+* Added `conditional` to `Expression` to use the ternary operator `? : `
+* Methods taking `positionalArguments` accept `Iterable<Expression>`
+* **BUG FIX**: Parameters can take a `FunctionType` as a `type`.
+ `Reference.type` now returns a `Reference`. Note that this change is
+ technically breaking but should not impacts most clients.
+
+## 2.2.0
+
+* Imports are prefixed with `_i1` rather than `_1` which satisfies the lint
+ `lowercase_with_underscores`. While not a strictly breaking change you may
+ have to fix/regenerate golden file-like tests. We added documentation that
+ the specific prefix is not considered stable.
+
+* Added `Expression.index` for accessing the `[]` operator:
+
+```dart
+void main() {
+ test('should emit an index operator', () {
+ expect(
+ refer('bar').index(literalTrue).assignVar('foo').statement,
+ equalsDart('var foo = bar[true];'),
+ );
+ });
+
+ test('should emit an index operator set', () {
+ expect(
+ refer('bar')
+ .index(literalTrue)
+ .assign(literalFalse)
+ .assignVar('foo')
+ .statement,
+ equalsDart('var foo = bar[true] = false;'),
+ );
+ });
+}
+```
+
+* `literalList` accepts an `Iterable` argument.
+
+* Fixed an NPE when a method had a return type of a `FunctionType`:
+
+```dart
+void main() {
+ test('should create a method with a function type return type', () {
+ expect(
+ new Method((b) => b
+ ..name = 'foo'
+ ..returns = new FunctionType((b) => b
+ ..returnType = refer('String')
+ ..requiredParameters.addAll([
+ refer('int'),
+ ]))),
+ equalsDart(r'''
+ String Function(int) foo();
+ '''),
+ );
+ });
+}
+```
+
+## 2.1.0
+
+We now require the Dart 2.0-dev branch SDK (`>= 2.0.0-dev`).
+
+* Added support for raw `String` literals.
+* Automatically escapes single quotes in now-raw `String` literals.
+* Deprecated `File`, which is now a redirect to the preferred class, `Library`.
+
+This helps avoid symbol clashes when used with `dart:io`, a popular library. It
+is now safe to do the following and get full access to the `code_builder` API:
+
+```dart
+import 'dart:io';
+
+import 'package:code_builder/code_builder.dart' hide File;
+```
+
+We will remove `File` in `3.0.0`, so use `Library` instead.
+
+## 2.0.0
+
+Re-released without a direct dependency on `package:analyzer`!
+
+For users of the `1.x` branch of `code_builder`, this is a pretty big breaking
+change but ultimately is for the better - it's easier to evolve this library
+now and even add your own builders on top of the library.
+
+```dart
+// Copyright (c) 2017, 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:code_builder/code_builder.dart';
+import 'package:dart_style/dart_style.dart';
+
+void main() {
+ final animal = new Class((b) => b
+ ..name = 'Animal'
+ ..extend = refer('Organism')
+ ..methods.add(new Method.returnsVoid((b) => b
+ ..name = 'eat'
+ ..lambda = true
+ ..body = const Code('print(\'Yum\')'))));
+ final emitter = new DartEmitter();
+ print(new DartFormatter().format('${animal.accept(emitter)}'));
+}
+```
+
+...outputs...
+
+```dart
+class Animal extends Organism {
+ void eat() => print('Yum!');
+}
+```
+
+**Major changes**:
+
+* Builders now use `built_value`, and have a more consistent, friendly API.
+* Builders are now consistent - they don't perform work work until code is
+ emitted.
+* It's possible to overwrite the built-in code emitting, formatting, etc by
+ providing your own visitors. See `DartEmitter` as an example of the built-in
+ visitor/emitter.
+* Most of the expression and statement-level helpers were removed; in practice,
+ they were difficult to write and maintain, and many users commonly asked for
+ opt-out type APIs. See the `Code` example below:
+
+```dart
+void main() {
+ var code = new Code('x + y = z');
+ code.expression;
+ code.statement;
+}
+```
+
+See the commit log, examples, and tests for full details. While we want to try
+and avoid breaking changes, suggestions, new features, and incremental updates
+are welcome!
+
+## 2.0.0-beta
+
+* Added `lazySpec` and `lazyCode` to lazily create code on visit [#145](https://github.com/dart-lang/code_builder/issues/145).
+
+* **BUG FIX**: `equalsDart` emits the failing source code [#147](https://github.com/dart-lang/code_builder/issues/147).
+* **BUG FIX**: Top-level `lambda` `Method`s no longer emit invalid code [#146](https://github.com/dart-lang/code_builder/issues/146).
+
+## 2.0.0-alpha+3
+
+* Added `Expression.annotation` and `Expression.annotationNamed`.
+* Added `Method.closure` to create an `Expression`.
+* Added `FunctionType`.
+* Added `{new|const}InstanceNamed` to `Expression` [#135](https://github.com/dart-lang/code_builder/issues/135).
+ * Also added a `typeArguments` option to all invocations.
+* Added `assign{...}` variants to `Expression` [#137](https://github.com/dart-lang/code_builder/issues/137).
+* Added `.awaited` and `.returned` to `Expression` [#138](https://github.com/dart-lang/code_builder/issues/138).
+
+* **BUG FIX**: `Block` now implements `Code` [#136](https://github.com/dart-lang/code_builder/issues/136).
+* **BUG FIX**: `new DartEmitter.scoped()` applies prefixing [#139](https://github.com/dart-lang/code_builder/issues/139).
+
+* Renamed many of the `.asFoo(...)` and `.toFoo(...)` methods to single getter:
+ * `asCode()` to `code`
+ * `asStatement()` to `statement`
+ * `toExpression()` to `expression`
+
+* Moved `{new|const}Instance{[Named]}` from `Expression` to `Reference`.
+
+## 2.0.0-alpha+2
+
+* Upgraded `build_runner` from `^0.3.0` to `>=0.4.0 <0.6.0`.
+* Upgraded `build_value{_generator}` from `^1.0.0` to `>=2.0.0 <5.0.0`.
+* Upgraded `source_gen` from `>=0.5.0 <0.7.0` to `^0.7.0`.
+
+* Added `MethodModifier` to allow emit a `Method` with `async|async*|sync*`.
+* Added `show|hide` to `Directive`.
+* Added `Directive.importDeferredAs`.
+* Added a new line character after emitting some types (class, method, etc).
+* Added `refer` as a short-hand for `new Reference(...)`.
+ * `Reference` now implements `Expression`.
+
+* Added many classes/methods for writing bodies of `Code` fluently:
+ * `Expression`
+ * `LiteralExpression`
+ * `literal`
+ * `literalNull`
+ * `literalBool`
+ * `literalTrue`
+ * `literalFalse`
+ * `literalNum`
+ * `literalString`
+ * `literalList` and `literalConstList`
+ * `literalMap` and `literalConstMap`
+ * `const Code(staticString)`
+ * `const Code.scope((allocate) => '')`
+
+* Removed `SimpleSpecVisitor` (it was unused).
+* Removed `implements Reference` from `Method` and `Field`; not a lot of value.
+
+* `SpecVisitor<T>`'s methods all have an optional `[T context]` parameter now.
+ * This makes it much easier to avoid allocating extra `StringBuffer`s.
+* `equalsDart` removes insignificant white space before comparing results.
+
+## 2.0.0-alpha+1
+
+* Removed `Reference.localScope`. Just use `Reference(symbol)` now.
+* Allow `Reference` instead of an explicit `TypeReference` in most APIs.
+ * `toType()` is performed for you as part the emitter process
+
+```dart
+final animal = new Class((b) => b
+ ..name = 'Animal'
+ // Used to need a suffix of .toType().
+ ..extend = const Reference('Organism')
+ ..methods.add(new Method.returnsVoid((b) => b
+ ..name = 'eat'
+ ..lambda = true
+ ..body = new Code((b) => b..code = 'print(\'Yum\')'))));
+```
+
+* We now support the Dart 2.0 pre-release SDKs (`<2.0.0-dev.infinity`)
+* Removed the ability to treat `Class` as a `TypeReference`.
+ * Was required for compilation to `dart2js`, which is now tested on travis.
+
+## 2.0.0-alpha
+
+* Complete re-write to not use `package:analyzer`.
+* Code generation now properly uses the _builder_ pattern (via `built_value`).
+* See examples and tests for details.
+
+## 1.0.4
+
+* Added `isInstanceOf` to `ExpressionBuilder`, which performs an `is` check:
+
+```dart
+expect(
+ reference('foo').isInstanceOf(_barType),
+ equalsSource('foo is Bar'),
+);
+```
+
+## 1.0.3
+
+* Support latest `pkg/analyzer` and `pkg/func`.
+
+## 1.0.2
+
+* Update internals to use newer analyzer API
+
+## 1.0.1
+
+* Support the latest version of `package:dart_style`.
+
+## 1.0.0
+
+First full release. At this point, all changes until `2.0.0` will be backward
+compatible (new features) or bug fixes that are not breaking. This doesn't mean
+that the entire Dart language is buildable with our API, though.
+
+**Contributions are welcome.**
+
+- Exposed `uri` in `ImportBuilder`, `ExportBuilder`, and `Part[Of]Builder`.
+
+## 1.0.0-beta+7
+
+- Added `ExpressionBuilder#ternary`.
+
+## 1.0.0-beta+6
+
+- Added `TypeDefBuilder`.
+- Added `FunctionParameterBuilder`.
+- Added `asAbstract` to various `MethodBuilder` constructors.
+
+## 1.0.0-beta+5
+
+- Re-published the package without merge conflicts.
+
+## 1.0.0-beta+4
+
+- Renamed `PartBuilder` to `PartOfBuilder`.
+- Added a new class, `PartBuilder`, to represent `part '...dart'` directives.
+- Added the `HasAnnotations` interface to all library/part/directive builders.
+- Added `asFactory` and `asConst` to `ConstructorBuilder`.
+- Added `ConstructorBuilder.redirectTo` for a redirecting factory constructor.
+- Added a `name` getter to `ReferenceBuilder`.
+- Supplying an empty constructor name (`''`) is equivalent to `null` (default).
+- Automatically encodes string literals with multiple lines as `'''`.
+- Added `asThrow` to `ExpressionBuilder`.
+- Fixed a bug that prevented `FieldBuilder` from being used at the top-level.
+
+## 1.0.0-beta+3
+
+- Added support for `genericTypes` parameter for `ExpressionBuilder#invoke`:
+
+```dart
+expect(
+ explicitThis.invoke('doThing', [literal(true)], genericTypes: [
+ lib$core.bool,
+ ]),
+ equalsSource(r'''
+ this.doThing<bool>(true)
+ '''),
+);
+```
+
+- Added a `castAs` method to `ExpressionBuilder`:
+
+```dart
+expect(
+ literal(1.0).castAs(lib$core.num),
+ equalsSource(r'''
+ 1.0 as num
+ '''),
+);
+```
+
+### BREAKING CHANGES
+
+- Removed `namedNewInstance` and `namedConstInstance`, replaced with `constructor: `:
+
+```dart
+expect(
+ reference('Foo').newInstance([], constructor: 'other'),
+ equalsSource(r'''
+ new Foo.other()
+ '''),
+);
+```
+
+- Renamed `named` parameter to `namedArguments`:
+
+```dart
+expect(
+ reference('doThing').call(
+ [literal(true)],
+ namedArguments: {
+ 'otherFlag': literal(false),
+ },
+ ),
+ equalsSource(r'''
+ doThing(true, otherFlag: false)
+ '''),
+);
+```
+
+## 1.0.0-beta+2
+
+### BREAKING CHANGES
+
+Avoid creating symbols that can collide with the Dart language:
+
+- `MethodModifier.async` -> `MethodModifier.asAsync`
+- `MethodModifier.asyncStar` -> `MethodModifier.asAsyncStar`
+- `MethodModifier.syncStar` -> `MethodModifier.asSyncStar`
+
+## 1.0.0-beta+1
+
+- Add support for `switch` statements
+- Add support for a raw expression and statement
+ - `new ExpressionBuilder.raw(...)`
+ - `new StatemnetBuilder.raw(...)`
+
+This should help cover any cases not covered with builders today.
+
+- Allow referring to a `ClassBuilder` and `TypeBuilder` as an expression
+- Add support for accessing the index `[]` operator on an expression
+
+### BREAKING CHANGES
+
+- Changed `ExpressionBuilder.asAssign` to always take an `ExpressionBuilder` as
+ target and removed the `value` property. Most changes are pretty simple, and
+ involve just using `reference(...)`. For example:
+
+```dart
+literal(true).asAssign(reference('flag'))
+```
+
+... emits `flag = true`.
+
+## 1.0.0-beta
+
+- Add support for `async`, `sync`, `sync*` functions
+- Add support for expression `asAwait`, `asYield`, `asYieldStar`
+- Add `toExportBuilder` and `toImportBuilder` to types and references
+- Fix an import scoping bug in `return` statements and named constructor invocations.
+- Added constructor initializer support
+- Add `while` and `do {} while` loop support
+- Add `for` and `for-in` support
+- Added a `name` getter for `ParameterBuilder`
+
+## 1.0.0-alpha+7
+
+- Make use of the new analyzer APIs in preparation for analyzer version 0.30.
+
+## 1.0.0-alpha+6
+
+- `MethodBuilder.closure` emits properly as a top-level function
+
+## 1.0.0-alpha+5
+
+- MethodBuilder with no statements will create an empty block instead of
+ a semicolon.
+
+```dart
+// main() {}
+method('main')
+```
+
+- Fix lambdas and closures to not include a trailing semicolon when used
+ as an expression.
+
+```dart
+ // () => false
+ new MethodBuilder.closure(returns: literal(false));
+```
+
+## 1.0.0-alpha+4
+
+- Add support for the latest `pkg/analyzer`.
+
+## 1.0.0-alpha+3
+
+- BREAKING CHANGE: Added generics support to `TypeBuilder`:
+
+`importFrom` becomes a _named_, not a positional argument, and the named
+argument `genericTypes` is added (`Iterable<TypeBuilder>`).
+
+```dart
+// List<String>
+new TypeBuilder('List', genericTypes: [reference('String')])
+```
+
+- Added generic support to `ReferenceBuilder`:
+
+```dart
+// List<String>
+reference('List').toTyped([reference('String')])
+```
+
+- Fixed a bug where `ReferenceBuilder.buildAst` was not implemented
+- Added `and` and `or` methods to `ExpressionBuilder`:
+
+```dart
+// true || false
+literal(true).or(literal(false));
+
+// true && false
+literal(true).and(literal(false));
+```
+
+- Added support for creating closures - `MethodBuilder.closure`:
+
+```dart
+// () => true
+new MethodBuilder.closure(
+ returns: literal(true),
+ returnType: lib$core.bool,
+)
+```
+
+## 1.0.0-alpha+2
+
+- Added `returnVoid` to well, `return;`
+- Added support for top-level field assignments:
+
+```dart
+new LibraryBuilder()..addMember(literal(false).asConst('foo'))
+```
+
+- Added support for specifying a `target` when using `asAssign`:
+
+```dart
+// Outputs bank.bar = goldBar
+reference('goldBar').asAssign('bar', target: reference('bank'))
+```
+
+- Added support for the cascade operator:
+
+```dart
+// Outputs foo..doThis()..doThat()
+reference('foo').cascade((c) => <ExpressionBuilder> [
+ c.invoke('doThis', []),
+ c.invoke('doThat', []),
+]);
+```
+
+- Added support for accessing a property
+
+```dart
+// foo.bar
+reference('foo').property('bar');
+```
+
+## 1.0.0-alpha+1
+
+- Slight updates to confusing documentation.
+- Added support for null-aware assignments.
+- Added `show` and `hide` support to `ImportBuilder`
+- Added `deferred` support to `ImportBuilder`
+- Added `ExportBuilder`
+- Added `list` and `map` literals that support generic types
+
+## 1.0.0-alpha
+
+- Large refactor that makes the library more feature complete.
+
+## 0.1.1
+
+- Add the concept of `Scope` and change `toAst` to support it
+
+Now your entire AST tree can be scoped and import directives
+automatically added to a `LibraryBuilder` for you if you use
+`LibraryBuilder.scope`.
+
+## 0.1.0
+
+- Initial version
diff --git a/pkgs/code_builder/LICENSE b/pkgs/code_builder/LICENSE
new file mode 100644
index 0000000..2372431
--- /dev/null
+++ b/pkgs/code_builder/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2016, 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.
diff --git a/pkgs/code_builder/README.md b/pkgs/code_builder/README.md
new file mode 100644
index 0000000..86decb5
--- /dev/null
+++ b/pkgs/code_builder/README.md
@@ -0,0 +1,114 @@
+[](https://github.com/dart-lang/tools/actions/workflows/code_builder.yaml)
+[](https://pub.dev/packages/code_builder)
+[](https://pub.dev/packages/code_builder/publisher)
+[](https://gitter.im/dart-lang/build)
+
+A fluent, builder-based library for generating valid Dart code.
+
+## Usage
+
+`code_builder` has a narrow and user-friendly API.
+
+See the `example` and `test` folders for additional examples.
+
+For example creating a class with a method:
+
+```dart
+import 'package:code_builder/code_builder.dart';
+import 'package:dart_style/dart_style.dart';
+
+void main() {
+ final animal = Class((b) => b
+ ..name = 'Animal'
+ ..extend = refer('Organism')
+ ..methods.add(Method.returnsVoid((b) => b
+ ..name = 'eat'
+ ..body = const Code("print('Yum!');"))));
+ final emitter = DartEmitter();
+ print(
+ DartFormatter(languageVersion: DartFormatter.latestLanguageVersion)
+ .format('${animal.accept(emitter)}'),
+ );
+}
+```
+
+Outputs:
+
+```dart
+class Animal extends Organism {
+ void eat() => print('Yum!');
+}
+```
+
+Have a complicated set of dependencies for your generated code? `code_builder`
+supports automatic scoping of your ASTs to automatically use prefixes to avoid
+symbol conflicts:
+
+```dart
+import 'package:code_builder/code_builder.dart';
+import 'package:dart_style/dart_style.dart';
+
+void main() {
+ final library = Library((b) => b.body.addAll([
+ 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 emitter = DartEmitter.scoped();
+ print(
+ DartFormatter(languageVersion: DartFormatter.latestLanguageVersion)
+ .format('${library.accept(emitter)}'),
+ );
+}
+```
+
+Outputs:
+
+```dart
+import 'package:a/a.dart' as _i1;
+import 'package:b/b.dart' as _i2;
+
+_i1.Thing doThing() {}
+_i2.Other doOther() {}
+```
+
+## Contributing
+
+- Read and help us document common patterns over [in the docs][docs].
+- Is there a _bug_ in the code? [File an issue][issue].
+
+If a feature is missing (the Dart language is always evolving) or you'd like an
+easier or better way to do something, consider [opening a pull request][pull].
+You can always [file an issue][issue], but generally speaking, feature requests
+will be on a best-effort basis.
+
+> **NOTE**: Due to the evolving Dart SDK the local `dartfmt` must be used to
+> format this repository. You can run it simply from the command-line:
+>
+> ```sh
+> $ dart run dart_style:format -w .
+> ```
+
+[issue]: https://github.com/dart-lang/tools/issues
+[pull]: https://github.com/dart-lang/tools/pulls
+
+### Updating generated (`.g.dart`) files
+
+> **NOTE**: There is currently a limitation in `build_runner` that requires a
+> workaround for developing this package since it is a dependency of the build
+> system.
+
+Make a snapshot of the generated [`build_runner`][build_runner] build script and
+run from the snapshot instead of from source to avoid problems with deleted
+files. These steps must be run without deleting the source files.
+
+```bash
+./tool/regenerate.sh
+```
+
+[build_runner]: https://pub.dev/packages/build_runner
diff --git a/pkgs/code_builder/analysis_options.yaml b/pkgs/code_builder/analysis_options.yaml
new file mode 100644
index 0000000..d052c68
--- /dev/null
+++ b/pkgs/code_builder/analysis_options.yaml
@@ -0,0 +1,25 @@
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-casts: true
+ strict-inference: true
+ strict-raw-types: true
+
+linter:
+ rules:
+ - avoid_private_typedef_functions
+ - avoid_redundant_argument_values
+ - avoid_unused_constructor_parameters
+ - cancel_subscriptions
+ - cascade_invocations
+ - join_return_with_assignment
+ - literal_only_boolean_expressions
+ - missing_whitespace_between_adjacent_strings
+ - no_adjacent_strings_in_list
+ - no_runtimeType_toString
+ - prefer_const_declarations
+ - prefer_expression_function_bodies
+ - prefer_final_locals
+ - unnecessary_await_in_return
+ - use_string_buffers
diff --git a/pkgs/code_builder/doc/Getting-Started.md b/pkgs/code_builder/doc/Getting-Started.md
new file mode 100644
index 0000000..b82da6c
--- /dev/null
+++ b/pkgs/code_builder/doc/Getting-Started.md
@@ -0,0 +1,35 @@
+# Common patterns
+
+## Classes
+
+### Creating a simple class
+
+> ```dart
+> class Animal {}
+> ```
+
+```dart
+new ClassBuilder(
+ 'Animal',
+)
+```
+
+### Creating an abstract class with a method
+
+> ```dart
+> abstract class Animal {
+> void eat();
+> }
+> ```
+
+```dart
+new ClassBuilder(
+ 'Animal',
+ asAbstract: true,
+)..addMethod(
+ new MethodBuilder.returnVoid(
+ 'eat',
+ asAbstract: true,
+ ),
+)
+```
\ No newline at end of file
diff --git a/pkgs/code_builder/doc/Home.md b/pkgs/code_builder/doc/Home.md
new file mode 100644
index 0000000..72e87a8
--- /dev/null
+++ b/pkgs/code_builder/doc/Home.md
@@ -0,0 +1,6 @@
+The [`code_builder`][code_builder] package helps generate valid [Dart][] source code programmatically, usually as part of a build process or generation step. Most of the API currently takes a little exploring to get used to, it's recommended to read the [API documentation][docs], or read the [getting started][] guide.
+
+[code_builder]: pub.dartlang.org/packages/code_builder
+[Dart]: https://www.dartlang.org/
+[docs]: https://www.dartdocs.org/documentation/code_builder/latest
+[getting started]: Getting-Started
diff --git a/pkgs/code_builder/example/example.dart b/pkgs/code_builder/example/example.dart
new file mode 100644
index 0000000..6f73598
--- /dev/null
+++ b/pkgs/code_builder/example/example.dart
@@ -0,0 +1,86 @@
+// Copyright (c) 2017, 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:code_builder/code_builder.dart';
+import 'package:dart_style/dart_style.dart';
+
+final _dartfmt = DartFormatter(
+ languageVersion: DartFormatter.latestLanguageVersion,
+);
+
+void main() {
+ print('animalClass():\n${'=' * 40}\n${animalClass()}');
+ print('scopedLibrary():\n${'=' * 40}\n${scopedLibrary()}');
+ print('jsonEnum():\n${'=' * 40}\n${jsonEnum()}');
+}
+
+/// Outputs:
+///
+/// ```dart
+/// class Animal extends Organism {
+/// void eat() => print('Yum!');
+/// }
+/// ```
+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)));
+ return _dartfmt.format('${animal.accept(DartEmitter())}');
+}
+
+/// Outputs:
+///
+/// ```dart
+/// import 'package:a/a.dart' as _i1;
+/// import 'package:b/b.dart' as _i2;
+///
+/// _i1.Thing doThing() {}
+/// _i2.Other doOther() {}
+/// ```
+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')),
+ ];
+ final library = Library((b) => b.body.addAll(methods));
+ return _dartfmt.format('${library.accept(DartEmitter.scoped())}');
+}
+
+/// Outputs:
+///
+/// ```dart
+/// enum Unit {
+/// @JsonKey('m')
+/// metric,
+/// @JsonKey('i')
+/// imperial
+/// }
+/// ```
+String jsonEnum() {
+ final values = <EnumValue>[
+ 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));
+ return _dartfmt.format('${e.accept(DartEmitter())}');
+}
diff --git a/pkgs/code_builder/lib/code_builder.dart b/pkgs/code_builder/lib/code_builder.dart
new file mode 100644
index 0000000..9cd15b9
--- /dev/null
+++ b/pkgs/code_builder/lib/code_builder.dart
@@ -0,0 +1,72 @@
+// Copyright (c) 2017, 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/allocator.dart' show Allocator;
+export 'src/base.dart' show Spec, lazySpec;
+export 'src/emitter.dart' show DartEmitter;
+export 'src/matchers.dart' show EqualsDart, equalsDart;
+export 'src/specs/class.dart' show Class, ClassBuilder, ClassModifier;
+export 'src/specs/code.dart'
+ show Block, BlockBuilder, Code, ScopedCode, StaticCode, lazyCode;
+export 'src/specs/constructor.dart' show Constructor, ConstructorBuilder;
+export 'src/specs/directive.dart'
+ show Directive, DirectiveBuilder, DirectiveType;
+export 'src/specs/enum.dart'
+ show Enum, EnumBuilder, EnumValue, EnumValueBuilder;
+export 'src/specs/expression.dart'
+ show
+ BinaryExpression,
+ CodeExpression,
+ Expression,
+ ExpressionEmitter,
+ ExpressionVisitor,
+ InvokeExpression,
+ InvokeExpressionType,
+ LiteralExpression,
+ LiteralListExpression,
+ ParenthesizedExpression,
+ ToCodeExpression,
+ declareConst,
+ declareFinal,
+ declareVar,
+ literal,
+ literalBool,
+ literalConstList,
+ literalConstMap,
+ literalConstRecord,
+ literalConstSet,
+ literalFalse,
+ literalList,
+ literalMap,
+ literalNull,
+ literalNullSafeSpread,
+ literalNum,
+ literalRecord,
+ literalSet,
+ literalSpread,
+ literalString,
+ literalTrue;
+export 'src/specs/extension.dart' show Extension, ExtensionBuilder;
+export 'src/specs/extension_type.dart'
+ show
+ ExtensionType,
+ ExtensionTypeBuilder,
+ RepresentationDeclaration,
+ RepresentationDeclarationBuilder;
+export 'src/specs/field.dart' show Field, FieldBuilder, FieldModifier;
+export 'src/specs/library.dart' show Library, LibraryBuilder;
+export 'src/specs/method.dart'
+ show
+ Method,
+ MethodBuilder,
+ MethodModifier,
+ MethodType,
+ Parameter,
+ ParameterBuilder;
+export 'src/specs/mixin.dart' show Mixin, MixinBuilder;
+export 'src/specs/reference.dart' show Reference, refer;
+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;
diff --git a/pkgs/code_builder/lib/src/allocator.dart b/pkgs/code_builder/lib/src/allocator.dart
new file mode 100644
index 0000000..18d2d8c
--- /dev/null
+++ b/pkgs/code_builder/lib/src/allocator.dart
@@ -0,0 +1,96 @@
+// Copyright (c) 2017, 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 'specs/directive.dart';
+import 'specs/reference.dart';
+
+/// Collects references and automatically allocates prefixes and imports.
+///
+/// `Allocator` takes out the manual work of deciding whether a symbol will
+/// clash with other imports in your generated code, or what imports are needed
+/// to resolve all symbols in your generated code.
+abstract class Allocator {
+ /// An allocator that does not prefix symbols nor collects imports.
+ static const Allocator none = _NullAllocator();
+
+ /// Creates a new default allocator that applies no prefixing.
+ factory Allocator() = _Allocator;
+
+ /// Creates a new allocator that applies naive prefixing to avoid conflicts.
+ ///
+ /// This implementation is not optimized for any particular code generation
+ /// style and instead takes a conservative approach of prefixing _every_
+ /// import except references to `dart:core` (which are considered always
+ /// imported).
+ ///
+ /// The prefixes are not guaranteed to be stable and cannot be expected to
+ /// have any particular value.
+ factory Allocator.simplePrefixing() = _PrefixedAllocator;
+
+ /// Returns a reference string given a [reference] object.
+ ///
+ /// For example, a no-op implementation:
+ /// ```dart
+ /// allocate(const Reference('List', 'dart:core')); // Returns 'List'.
+ /// ```
+ ///
+ /// Where-as an implementation that prefixes imports might output:
+ /// ```dart
+ /// allocate(const Reference('Foo', 'package:foo')); // Returns '_i1.Foo'.
+ /// ```
+ String allocate(Reference reference);
+
+ /// All imports that have so far been added implicitly via [allocate].
+ Iterable<Directive> get imports;
+}
+
+class _Allocator implements Allocator {
+ final _imports = <String>{};
+
+ @override
+ String allocate(Reference reference) {
+ final url = reference.url;
+ if (url != null) {
+ _imports.add(url);
+ }
+ return reference.symbol!;
+ }
+
+ @override
+ Iterable<Directive> get imports => _imports.map(Directive.import);
+}
+
+class _NullAllocator implements Allocator {
+ const _NullAllocator();
+
+ @override
+ String allocate(Reference reference) => reference.symbol!;
+
+ @override
+ Iterable<Directive> get imports => const [];
+}
+
+class _PrefixedAllocator implements Allocator {
+ static const _doNotPrefix = ['dart:core'];
+
+ final _imports = <String, int>{};
+ var _keys = 1;
+
+ @override
+ String allocate(Reference reference) {
+ final symbol = reference.symbol;
+ final url = reference.url;
+ if (url == null || _doNotPrefix.contains(url)) {
+ return symbol!;
+ }
+ return '_i${_imports.putIfAbsent(url, _nextKey)}.$symbol';
+ }
+
+ int _nextKey() => _keys++;
+
+ @override
+ Iterable<Directive> get imports => _imports.keys.map(
+ (u) => Directive.import(u, as: '_i${_imports[u]}'),
+ );
+}
diff --git a/pkgs/code_builder/lib/src/base.dart b/pkgs/code_builder/lib/src/base.dart
new file mode 100644
index 0000000..216b9a9
--- /dev/null
+++ b/pkgs/code_builder/lib/src/base.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2017, 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 'visitors.dart';
+
+abstract class Spec {
+ R accept<R>(SpecVisitor<R> visitor, [R? context]);
+}
+
+/// Returns a generic [Spec] that is lazily generated when visited.
+Spec lazySpec(Spec Function() generate) => _LazySpec(generate);
+
+class _LazySpec implements Spec {
+ final Spec Function() generate;
+
+ const _LazySpec(this.generate);
+
+ @override
+ R accept<R>(SpecVisitor<R> visitor, [R? context]) =>
+ generate().accept(visitor, context);
+}
diff --git a/pkgs/code_builder/lib/src/emitter.dart b/pkgs/code_builder/lib/src/emitter.dart
new file mode 100644
index 0000000..12109cc
--- /dev/null
+++ b/pkgs/code_builder/lib/src/emitter.dart
@@ -0,0 +1,944 @@
+// Copyright (c) 2017, 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 'allocator.dart';
+import 'base.dart';
+import 'specs/class.dart';
+import 'specs/code.dart';
+import 'specs/constructor.dart';
+import 'specs/directive.dart';
+import 'specs/enum.dart';
+import 'specs/expression.dart';
+import 'specs/extension.dart';
+import 'specs/extension_type.dart';
+import 'specs/field.dart';
+import 'specs/library.dart';
+import 'specs/method.dart';
+import 'specs/mixin.dart';
+import 'specs/reference.dart';
+import 'specs/type_function.dart';
+import 'specs/type_record.dart';
+import 'specs/type_reference.dart';
+import 'specs/typedef.dart';
+import 'visitors.dart';
+
+/// Helper method improving on [StringSink.writeAll].
+///
+/// For every `Spec` in [elements], executing [visit].
+///
+/// If [elements] is at least 2 elements, inserts [separator] delimiting them.
+StringSink visitAll<T>(
+ Iterable<T> elements,
+ StringSink output,
+ void Function(T) visit, [
+ String separator = ', ',
+]) {
+ // Basically, this whole method is an improvement on
+ // output.writeAll(specs.map((s) => s.accept(visitor));
+ //
+ // ... which would allocate more StringBuffer(s) for a one-time use.
+ if (elements.isEmpty) {
+ return output;
+ }
+ final iterator = elements.iterator..moveNext();
+ visit(iterator.current);
+ while (iterator.moveNext()) {
+ output.write(separator);
+ visit(iterator.current);
+ }
+ return output;
+}
+
+class DartEmitter extends Object
+ with CodeEmitter, ExpressionEmitter
+ implements SpecVisitor<StringSink> {
+ @override
+ final Allocator allocator;
+
+ /// If directives should be ordered while emitting.
+ ///
+ /// Ordering rules follow the guidance in
+ /// [Effective Dart](https://dart.dev/guides/language/effective-dart/style#ordering)
+ /// and the
+ /// [directives_ordering](https://dart-lang.github.io/linter/lints/directives_ordering.html)
+ /// lint.
+ final bool orderDirectives;
+
+ /// If nullable types should be emitted with the nullable suffix ("?").
+ ///
+ /// Null safety syntax should only be enabled if the output will be used with
+ /// a Dart language version which supports it.
+ final bool _useNullSafetySyntax;
+
+ /// Creates a new instance of [DartEmitter].
+ ///
+ /// 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;
+
+ /// 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);
+
+ static bool _isLambdaBody(Code? code) =>
+ code is ToCodeExpression && !code.isStatement;
+
+ /// Whether the provided [method] is considered a lambda method.
+ static bool _isLambdaMethod(Method method) =>
+ method.lambda ?? _isLambdaBody(method.body);
+
+ /// Whether the provided [constructor] is considered a lambda method.
+ static bool _isLambdaConstructor(Constructor constructor) =>
+ constructor.lambda ??
+ constructor.factory && _isLambdaBody(constructor.body);
+
+ @override
+ StringSink visitAnnotation(Expression spec, [StringSink? output]) {
+ (output ??= StringBuffer()).write('@');
+ spec.accept(this, output);
+ output.write(' ');
+ return output;
+ }
+
+ @override
+ StringSink visitClass(Class spec, [StringSink? output]) {
+ final out = output ??= StringBuffer();
+ spec.docs.forEach(out.writeln);
+ for (var a in spec.annotations) {
+ visitAnnotation(a, out);
+ }
+
+ void writeModifier() {
+ if (spec.modifier != null) {
+ out.write('${spec.modifier!.name} ');
+ }
+ }
+
+ if (spec.sealed) {
+ out.write('sealed ');
+ } else {
+ if (spec.abstract) {
+ out.write('abstract ');
+ }
+ writeModifier();
+ if (spec.mixin) {
+ out.write('mixin ');
+ }
+ }
+ out.write('class ${spec.name}');
+ visitTypeParameters(spec.types.map((r) => r.type), out);
+ if (spec.extend != null) {
+ out.write(' extends ');
+ spec.extend!.type.accept(this, out);
+ }
+ if (spec.mixins.isNotEmpty) {
+ out
+ ..write(' with ')
+ ..writeAll(
+ spec.mixins.map<StringSink>((m) => m.type.accept(this)), ',');
+ }
+ if (spec.implements.isNotEmpty) {
+ out
+ ..write(' implements ')
+ ..writeAll(
+ spec.implements.map<StringSink>((m) => m.type.accept(this)), ',');
+ }
+ out.write(' {');
+ for (var c in spec.constructors) {
+ visitConstructor(c, spec.name, out);
+ out.writeln();
+ }
+ for (var f in spec.fields) {
+ visitField(f, out);
+ out.writeln();
+ }
+ for (var m in spec.methods) {
+ visitMethod(m, out);
+ if (_isLambdaMethod(m)) {
+ out.writeln(';');
+ }
+ out.writeln();
+ }
+ out.writeln(' }');
+ return out;
+ }
+
+ @override
+ StringSink visitMixin(Mixin spec, [StringSink? output]) {
+ final out = output ??= StringBuffer();
+ spec.docs.forEach(out.writeln);
+ for (var a in spec.annotations) {
+ visitAnnotation(a, out);
+ }
+
+ if (spec.base) {
+ out.write('base ');
+ }
+ out.write('mixin ${spec.name}');
+ visitTypeParameters(spec.types.map((r) => r.type), out);
+ if (spec.on != null) {
+ out.write(' on ');
+ spec.on!.type.accept(this, out);
+ }
+ if (spec.implements.isNotEmpty) {
+ out
+ ..write(' implements ')
+ ..writeAll(
+ spec.implements.map<StringSink>((m) => m.type.accept(this)), ',');
+ }
+ out.write(' {');
+ for (var f in spec.fields) {
+ visitField(f, out);
+ out.writeln();
+ }
+ for (var m in spec.methods) {
+ visitMethod(m, out);
+ if (_isLambdaMethod(m)) {
+ out.write(';');
+ }
+ out.writeln();
+ }
+ out.write(' }');
+ return out;
+ }
+
+ @override
+ StringSink visitConstructor(Constructor spec, String clazz,
+ [StringSink? output]) {
+ output ??= StringBuffer();
+ spec.docs.forEach(output.writeln);
+ for (var a in spec.annotations) {
+ visitAnnotation(a, output);
+ }
+ if (spec.external) {
+ output.write('external ');
+ }
+ if (spec.constant) {
+ output.write('const ');
+ }
+ if (spec.factory) {
+ output.write('factory ');
+ }
+ output.write(clazz);
+ if (spec.name != null) {
+ output
+ ..write('.')
+ ..write(spec.name);
+ }
+ output.write('(');
+ final hasMultipleParameters =
+ spec.requiredParameters.length + spec.optionalParameters.length > 1;
+ if (spec.requiredParameters.isNotEmpty) {
+ var count = 0;
+ for (final p in spec.requiredParameters) {
+ count++;
+ _visitParameter(p, output);
+ if (hasMultipleParameters ||
+ spec.requiredParameters.length != count ||
+ spec.optionalParameters.isNotEmpty) {
+ output.write(', ');
+ }
+ }
+ }
+ if (spec.optionalParameters.isNotEmpty) {
+ final named = spec.optionalParameters.any((p) => p.named);
+ if (named) {
+ output.write('{');
+ } else {
+ output.write('[');
+ }
+ var count = 0;
+ for (final p in spec.optionalParameters) {
+ count++;
+ _visitParameter(p, output, optional: true, named: named);
+ if (hasMultipleParameters || spec.optionalParameters.length != count) {
+ output.write(', ');
+ }
+ }
+ if (named) {
+ output.write('}');
+ } else {
+ output.write(']');
+ }
+ }
+ output.write(')');
+ if (spec.initializers.isNotEmpty) {
+ output.write(' : ');
+ var count = 0;
+ for (final initializer in spec.initializers) {
+ count++;
+ initializer.accept(this, output);
+ if (count != spec.initializers.length) {
+ output.write(', ');
+ }
+ }
+ }
+ if (spec.redirect != null) {
+ output.write(' = ');
+ spec.redirect!.type.accept(this, output);
+ output.write(';');
+ } else if (spec.body != null) {
+ if (_isLambdaConstructor(spec)) {
+ output.write(' => ');
+ spec.body!.accept(this, output);
+ output.write(';');
+ } else {
+ output.write(' { ');
+ spec.body!.accept(this, output);
+ output.write(' }');
+ }
+ } else {
+ output.write(';');
+ }
+ output.writeln();
+ return output;
+ }
+
+ @override
+ StringSink visitExtension(Extension spec, [StringSink? output]) {
+ final out = output ??= StringBuffer();
+ spec.docs.forEach(out.writeln);
+ for (var a in spec.annotations) {
+ visitAnnotation(a, out);
+ }
+
+ out.write('extension');
+ if (spec.name != null) {
+ out.write(' ${spec.name}');
+ }
+ visitTypeParameters(spec.types.map((r) => r.type), out);
+ if (spec.on != null) {
+ out.write(' on ');
+ spec.on!.type.accept(this, out);
+ }
+ out.write(' {');
+ for (var f in spec.fields) {
+ visitField(f, out);
+ out.writeln();
+ }
+ for (var m in spec.methods) {
+ visitMethod(m, out);
+ if (_isLambdaMethod(m)) {
+ out.write(';');
+ }
+ out.writeln();
+ }
+ out.writeln(' }');
+ return out;
+ }
+
+ @override
+ StringSink visitExtensionType(ExtensionType spec, [StringSink? output]) {
+ final out = output ??= StringBuffer();
+ spec.docs.forEach(out.writeln);
+ for (var a in spec.annotations) {
+ visitAnnotation(a, out);
+ }
+
+ out.write('extension type ');
+ if (spec.constant) out.write('const ');
+ out.write(spec.name);
+ visitTypeParameters(spec.types.map((r) => r.type), out);
+ if (spec.primaryConstructorName.isNotEmpty) {
+ out.write('.${spec.primaryConstructorName}');
+ }
+ out.write('(');
+ _visitRepresentationDeclaration(spec.representationDeclaration, out);
+ out.write(')');
+
+ if (spec.implements.isNotEmpty) {
+ out
+ ..write(' implements ')
+ ..writeAll(
+ spec.implements.map<StringSink>((m) => m.type.accept(this)), ',');
+ }
+
+ out.writeln(' {');
+ for (var c in spec.constructors) {
+ visitConstructor(c, spec.name, out);
+ out.writeln();
+ }
+ for (var f in spec.fields) {
+ visitField(f, out);
+ out.writeln();
+ }
+ for (var m in spec.methods) {
+ visitMethod(m, out);
+ if (_isLambdaMethod(m)) {
+ out.writeln(';');
+ }
+ out.writeln();
+ }
+ out.writeln('}');
+ return out;
+ }
+
+ void _visitRepresentationDeclaration(
+ RepresentationDeclaration spec, StringSink out) {
+ spec.docs.forEach(out.writeln);
+ for (var a in spec.annotations) {
+ visitAnnotation(a, out);
+ }
+ spec.declaredRepresentationType.accept(this, out);
+ out.write(' ${spec.name}');
+ }
+
+ @override
+ StringSink visitDirective(Directive spec, [StringSink? output]) {
+ output ??= StringBuffer();
+ switch (spec.type) {
+ case DirectiveType.import:
+ output.write('import ');
+ break;
+ case DirectiveType.export:
+ output.write('export ');
+ break;
+ case DirectiveType.part:
+ output.write('part ');
+ break;
+ case DirectiveType.partOf:
+ output.write('part of ');
+ break;
+ }
+ output.write("'${spec.url}'");
+ if (spec.as != null) {
+ if (spec.deferred) {
+ output.write(' deferred ');
+ }
+ output.write(' as ${spec.as}');
+ }
+ if (spec.show.isNotEmpty) {
+ output
+ ..write(' show ')
+ ..writeAll(spec.show, ', ');
+ } else if (spec.hide.isNotEmpty) {
+ output
+ ..write(' hide ')
+ ..writeAll(spec.hide, ', ');
+ }
+ output.write(';');
+ return output;
+ }
+
+ @override
+ StringSink visitField(Field spec, [StringSink? output]) {
+ output ??= StringBuffer();
+ spec.docs.forEach(output.writeln);
+ for (var a in spec.annotations) {
+ visitAnnotation(a, output);
+ }
+ 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) {
+ output.write('var ');
+ }
+ break;
+ case FieldModifier.final$:
+ output.write('final ');
+ break;
+ case FieldModifier.constant:
+ output.write('const ');
+ break;
+ }
+ if (spec.type != null) {
+ spec.type!.type.accept(this, output);
+ output.write(' ');
+ }
+ output.write(spec.name);
+ if (spec.assignment != null) {
+ output.write(' = ');
+ startConstCode(spec.modifier == FieldModifier.constant, () {
+ spec.assignment!.accept(this, output);
+ });
+ }
+ output.writeln(';');
+ return output;
+ }
+
+ @override
+ StringSink visitLibrary(Library spec, [StringSink? output]) {
+ output ??= StringBuffer();
+
+ if (spec.comments.isNotEmpty) {
+ spec.comments.map((line) => '// $line').forEach(output.writeln);
+ output.writeln();
+ }
+
+ if (spec.generatedByComment != null) {
+ output
+ ..writeln('// ${spec.generatedByComment}')
+ ..writeln();
+ }
+
+ if (spec.ignoreForFile.isNotEmpty) {
+ final ignores = spec.ignoreForFile.toList()..sort();
+ final lines = ['// ignore_for_file: ${ignores.first}'];
+ for (var ignore in ignores.skip(1)) {
+ if (lines.last.length + 2 + ignore.length > 80) {
+ lines.add('// ignore_for_file: $ignore');
+ } else {
+ lines[lines.length - 1] = '${lines.last}, $ignore';
+ }
+ }
+ lines.forEach(output.writeln);
+ output.writeln();
+ }
+
+ // Process the body first in order to prime the allocators.
+ final body = StringBuffer();
+ for (final spec in spec.body) {
+ spec.accept(this, body);
+ if (spec is Method && _isLambdaMethod(spec)) {
+ body.write(';');
+ }
+ }
+
+ spec.docs.forEach(output.writeln);
+ for (var a in spec.annotations) {
+ visitAnnotation(a, output);
+ }
+ if (spec.name != null) {
+ output.write('library ${spec.name!};');
+ } else if (spec.annotations.isNotEmpty || spec.docs.isNotEmpty) {
+ // An explicit _unnamed_ library directive is only required if there are
+ // annotations or doc comments on the library.
+ output.write('library;');
+ }
+
+ final directives = <Directive>[...allocator.imports, ...spec.directives];
+
+ if (orderDirectives) {
+ directives.sort();
+ }
+
+ Directive? previous;
+ if (directives.any((d) => d.as?.startsWith('_') ?? false)) {
+ output.writeln(
+ '// ignore_for_file: no_leading_underscores_for_library_prefixes');
+ }
+ for (final directive in directives) {
+ if (_newLineBetween(orderDirectives, previous, directive)) {
+ // Note: dartfmt handles creating new lines between directives.
+ // 2 lines are written here. The first one comes after the previous
+ // directive `;`, the second is the empty line.
+ output
+ ..writeln()
+ ..writeln();
+ }
+ directive.accept(this, output);
+ previous = directive;
+ }
+ output.write(body);
+ return output;
+ }
+
+ @override
+ StringSink visitFunctionType(FunctionType spec, [StringSink? output]) {
+ final out = output ??= StringBuffer();
+ if (spec.returnType != null) {
+ spec.returnType!.accept(this, out);
+ out.write(' ');
+ }
+ out.write('Function');
+ if (spec.types.isNotEmpty) {
+ out.write('<');
+ visitAll<Reference>(spec.types, out, (spec) {
+ spec.accept(this, out);
+ });
+ out.write('>');
+ }
+ out.write('(');
+ final needsTrailingComma = spec.requiredParameters.length +
+ spec.optionalParameters.length +
+ spec.namedRequiredParameters.length +
+ spec.namedParameters.length >
+ 1;
+ visitAll<Reference>(spec.requiredParameters, out, (spec) {
+ spec.accept(this, out);
+ });
+ final hasNamedParameters = spec.namedRequiredParameters.isNotEmpty ||
+ spec.namedParameters.isNotEmpty;
+ if (spec.requiredParameters.isNotEmpty &&
+ (needsTrailingComma ||
+ spec.optionalParameters.isNotEmpty ||
+ hasNamedParameters)) {
+ out.write(', ');
+ }
+ if (spec.optionalParameters.isNotEmpty) {
+ out.write('[');
+ visitAll<Reference>(spec.optionalParameters, out, (spec) {
+ spec.accept(this, out);
+ });
+ if (needsTrailingComma) {
+ out.write(', ');
+ }
+ out.write(']');
+ } else if (hasNamedParameters) {
+ out.write('{');
+ visitAll<String>(spec.namedRequiredParameters.keys, out, (name) {
+ out.write('required ');
+ spec.namedRequiredParameters[name]!.accept(this, out);
+ out
+ ..write(' ')
+ ..write(name);
+ });
+ if (spec.namedRequiredParameters.isNotEmpty &&
+ spec.namedParameters.isNotEmpty) {
+ out.write(', ');
+ }
+ visitAll<String>(spec.namedParameters.keys, out, (name) {
+ spec.namedParameters[name]!.accept(this, out);
+ out
+ ..write(' ')
+ ..write(name);
+ });
+ if (needsTrailingComma) {
+ out.write(', ');
+ }
+ out.write('}');
+ }
+ out.write(')');
+ if (_useNullSafetySyntax && (spec.isNullable ?? false)) {
+ out.write('?');
+ }
+ return out;
+ }
+
+ @override
+ StringSink visitRecordType(RecordType spec, [StringSink? output]) {
+ final out = (output ??= StringBuffer())..write('(');
+ visitAll<Reference>(spec.positionalFieldTypes, out, (spec) {
+ spec.accept(this, out);
+ });
+ if (spec.namedFieldTypes.isNotEmpty) {
+ if (spec.positionalFieldTypes.isNotEmpty) {
+ out.write(', ');
+ }
+ out.write('{');
+ visitAll<MapEntry<String, Reference>>(spec.namedFieldTypes.entries, out,
+ (entry) {
+ entry.value.accept(this, out);
+ out.write(' ${entry.key}');
+ });
+ out.write('}');
+ } else if (spec.positionalFieldTypes.length == 1) {
+ out.write(',');
+ }
+ out.write(')');
+ // It doesn't really make sense to use records without
+ // `_useNullSafetySyntax`, but since code_builder is generally very
+ // permissive, follow it here too.
+ if (_useNullSafetySyntax && (spec.isNullable ?? false)) {
+ out.write('?');
+ }
+ return out;
+ }
+
+ @override
+ StringSink visitTypeDef(TypeDef spec, [StringSink? output]) {
+ final out = output ??= StringBuffer();
+ spec.docs.forEach(out.writeln);
+ for (var a in spec.annotations) {
+ visitAnnotation(a, out);
+ }
+ out.write('typedef ${spec.name}');
+ visitTypeParameters(spec.types.map((r) => r.type), out);
+ out.write(' = ');
+ spec.definition.accept(this, out);
+ out.writeln(';');
+ return out;
+ }
+
+ @override
+ StringSink visitMethod(Method spec, [StringSink? output]) {
+ output ??= StringBuffer();
+ spec.docs.forEach(output.writeln);
+ for (var a in spec.annotations) {
+ visitAnnotation(a, output);
+ }
+ if (spec.external) {
+ output.write('external ');
+ }
+ if (spec.static) {
+ output.write('static ');
+ }
+ if (spec.returns != null) {
+ spec.returns!.accept(this, output);
+ output.write(' ');
+ }
+ if (spec.type == MethodType.getter) {
+ output
+ ..write('get ')
+ ..write(spec.name);
+ } else {
+ if (spec.type == MethodType.setter) {
+ output.write('set ');
+ }
+ if (spec.name != null) {
+ output.write(spec.name);
+ }
+ visitTypeParameters(spec.types.map((r) => r.type), output);
+ output.write('(');
+ final hasMultipleParameters =
+ spec.requiredParameters.length + spec.optionalParameters.length > 1;
+ if (spec.requiredParameters.isNotEmpty) {
+ var count = 0;
+ for (final p in spec.requiredParameters) {
+ count++;
+ _visitParameter(p, output);
+ if (hasMultipleParameters ||
+ spec.requiredParameters.length != count ||
+ spec.optionalParameters.isNotEmpty) {
+ output.write(', ');
+ }
+ }
+ }
+ if (spec.optionalParameters.isNotEmpty) {
+ final named = spec.optionalParameters.any((p) => p.named);
+ if (named) {
+ output.write('{');
+ } else {
+ output.write('[');
+ }
+ var count = 0;
+ for (final p in spec.optionalParameters) {
+ count++;
+ _visitParameter(p, output, optional: true, named: named);
+ if (hasMultipleParameters ||
+ spec.optionalParameters.length != count) {
+ output.write(', ');
+ }
+ }
+ if (named) {
+ output.write('}');
+ } else {
+ output.write(']');
+ }
+ }
+ output.write(')');
+ }
+ if (spec.body != null) {
+ if (spec.modifier != null) {
+ switch (spec.modifier!) {
+ case MethodModifier.async:
+ output.write(' async ');
+ break;
+ case MethodModifier.asyncStar:
+ output.write(' async* ');
+ break;
+ case MethodModifier.syncStar:
+ output.write(' sync* ');
+ break;
+ }
+ }
+ if (_isLambdaMethod(spec)) {
+ output.write(' => ');
+ } else {
+ output.write(' { ');
+ }
+ spec.body!.accept(this, output);
+ if (!_isLambdaMethod(spec)) {
+ output.write(' } ');
+ }
+ } else {
+ output.write(';');
+ }
+ return output;
+ }
+
+ // Expose as a first-class visit function only if needed.
+ void _visitParameter(
+ Parameter spec,
+ StringSink output, {
+ bool optional = false,
+ bool named = false,
+ }) {
+ spec.docs.forEach(output.writeln);
+ for (var a in spec.annotations) {
+ visitAnnotation(a, output);
+ }
+ // The `required` keyword must precede the `covariant` keyword.
+ if (spec.required) {
+ output.write('required ');
+ }
+ if (spec.covariant) {
+ output.write('covariant ');
+ }
+ if (spec.type != null) {
+ spec.type!.type.accept(this, output);
+ output.write(' ');
+ }
+ if (spec.toThis) {
+ output.write('this.');
+ }
+ if (spec.toSuper) {
+ output.write('super.');
+ }
+ output.write(spec.name);
+ if (optional && spec.defaultTo != null) {
+ output.write(' = ');
+ spec.defaultTo!.accept(this, output);
+ }
+ }
+
+ @override
+ StringSink visitReference(Reference spec, [StringSink? output]) =>
+ (output ??= StringBuffer())..write(allocator.allocate(spec));
+
+ @override
+ StringSink visitSpec(Spec spec, [StringSink? output]) =>
+ spec.accept(this, output);
+
+ @override
+ StringSink visitType(TypeReference spec, [StringSink? output]) {
+ output ??= StringBuffer();
+ // Intentionally not .accept to avoid stack overflow.
+ visitReference(spec, output);
+ if (spec.bound != null) {
+ output.write(' extends ');
+ spec.bound!.type.accept(this, output);
+ }
+ visitTypeParameters(spec.types.map((r) => r.type), output);
+ if (_useNullSafetySyntax && (spec.isNullable ?? false)) {
+ output.write('?');
+ }
+ return output;
+ }
+
+ @override
+ StringSink visitTypeParameters(Iterable<Reference> specs,
+ [StringSink? output]) {
+ output ??= StringBuffer();
+ if (specs.isNotEmpty) {
+ output
+ ..write('<')
+ ..writeAll(specs.map<StringSink>((s) => s.accept(this)), ',')
+ ..write('>');
+ }
+ return output;
+ }
+
+ @override
+ StringSink visitEnum(Enum spec, [StringSink? output]) {
+ final out = output ??= StringBuffer();
+ spec.docs.forEach(out.writeln);
+ for (var a in spec.annotations) {
+ visitAnnotation(a, out);
+ }
+ out.write('enum ${spec.name}');
+ visitTypeParameters(spec.types.map((r) => r.type), out);
+ if (spec.mixins.isNotEmpty) {
+ out
+ ..write(' with ')
+ ..writeAll(
+ spec.mixins.map<StringSink>((m) => m.type.accept(this)), ', ');
+ }
+ if (spec.implements.isNotEmpty) {
+ out
+ ..write(' implements ')
+ ..writeAll(
+ spec.implements.map<StringSink>((m) => m.type.accept(this)), ', ');
+ }
+ out.write(' { ');
+ for (var v in spec.values) {
+ v.docs.forEach(out.writeln);
+ for (var a in v.annotations) {
+ visitAnnotation(a, out);
+ }
+ out.write(v.name);
+ if (v.constructorName != null) {
+ out.write('.${v.constructorName}');
+ }
+ visitTypeParameters(v.types.map((r) => r.type), out);
+ final takesArguments = v.constructorName != null ||
+ v.arguments.isNotEmpty ||
+ v.namedArguments.isNotEmpty;
+ if (takesArguments) {
+ out.write('(');
+ }
+ if (v.arguments.isNotEmpty) {
+ out.writeAll(
+ v.arguments.map<StringSink>((arg) => arg.accept(this)), ', ');
+ }
+ if (v.arguments.isNotEmpty && v.namedArguments.isNotEmpty) {
+ out.write(', ');
+ }
+ visitAll<String>(v.namedArguments.keys, out, (name) {
+ out
+ ..write(name)
+ ..write(': ');
+ v.namedArguments[name]!.accept(this, out);
+ });
+ if (takesArguments) {
+ out.write(')');
+ }
+ if (v != spec.values.last) {
+ out.writeln(',');
+ } else if (spec.constructors.isNotEmpty ||
+ spec.fields.isNotEmpty ||
+ spec.methods.isNotEmpty) {
+ out.writeln(';');
+ }
+ }
+ for (var c in spec.constructors) {
+ visitConstructor(c, spec.name, out);
+ out.writeln();
+ }
+ for (var f in spec.fields) {
+ visitField(f, out);
+ out.writeln();
+ }
+ for (var m in spec.methods) {
+ visitMethod(m, out);
+ if (_isLambdaMethod(m)) {
+ out.write(';');
+ }
+ out.writeln();
+ }
+ out.writeln(' }');
+ return out;
+ }
+}
+
+/// Returns `true` if:
+///
+/// * [ordered] is `true`
+/// * [a] is non-`null`
+/// * If there should be an empty line before [b] if it's emitted after [a].
+bool _newLineBetween(bool ordered, Directive? a, Directive? b) {
+ if (!ordered) return false;
+ if (a == null) return false;
+
+ assert(b != null);
+
+ // Put a line between imports and exports
+ if (a.type != b!.type) return true;
+
+ // Within exports, don't put in extra blank lines
+ if (a.type == DirectiveType.export) {
+ assert(b.type == DirectiveType.export);
+ return false;
+ }
+
+ // Return `true` if the schemes for [a] and [b] are different
+ return !Uri.parse(a.url).isScheme(Uri.parse(b.url).scheme);
+}
diff --git a/pkgs/code_builder/lib/src/matchers.dart b/pkgs/code_builder/lib/src/matchers.dart
new file mode 100644
index 0000000..0cdf739
--- /dev/null
+++ b/pkgs/code_builder/lib/src/matchers.dart
@@ -0,0 +1,73 @@
+// Copyright (c) 2017, 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:matcher/matcher.dart';
+
+import 'base.dart';
+import 'emitter.dart';
+
+/// Encodes [spec] as Dart source code.
+String _dart(Spec spec, DartEmitter emitter) =>
+ EqualsDart._format(spec.accept<StringSink>(emitter).toString());
+
+/// Returns a matcher for [Spec] objects that emit code matching [source].
+///
+/// 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,
+]) =>
+ EqualsDart._(EqualsDart._format(source), emitter ?? DartEmitter());
+
+/// Implementation detail of using the [equalsDart] matcher.
+///
+/// See [EqualsDart.format] to specify the default source code formatter.
+class EqualsDart extends Matcher {
+ /// May override to provide a function to format Dart on [equalsDart].
+ ///
+ /// By default, uses [collapseWhitespace], but it is recommended to instead
+ /// use `dart_style` (dartfmt) where possible. See `test/common.dart` for an
+ /// example.
+ static String Function(String) format = collapseWhitespace;
+
+ static String _format(String source) {
+ try {
+ return format(source).trim();
+ } catch (_) {
+ // Ignored on purpose, probably not exactly valid Dart code.
+ return collapseWhitespace(source).trim();
+ }
+ }
+
+ final DartEmitter _emitter;
+ final String _expectedSource;
+
+ const EqualsDart._(this._expectedSource, this._emitter);
+
+ @override
+ Description describe(Description description) =>
+ description.add(_expectedSource);
+
+ @override
+ Description describeMismatch(
+ covariant Spec item,
+ Description mismatchDescription,
+ Map<dynamic, dynamic> matchState,
+ bool verbose,
+ ) {
+ final actualSource = _dart(item, _emitter);
+ return equals(_expectedSource).describeMismatch(
+ actualSource,
+ mismatchDescription,
+ matchState,
+ verbose,
+ );
+ }
+
+ @override
+ bool matches(covariant Spec item, Object? matchState) =>
+ _dart(item, _emitter) == _expectedSource;
+}
diff --git a/pkgs/code_builder/lib/src/mixins/annotations.dart b/pkgs/code_builder/lib/src/mixins/annotations.dart
new file mode 100644
index 0000000..7ce6a20
--- /dev/null
+++ b/pkgs/code_builder/lib/src/mixins/annotations.dart
@@ -0,0 +1,19 @@
+// Copyright (c) 2017, 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:built_collection/built_collection.dart';
+
+import '../specs/expression.dart';
+
+/// A type of AST node that can have metadata [annotations].
+abstract mixin class HasAnnotations {
+ /// Annotations as metadata on the node.
+ BuiltList<Expression> get annotations;
+}
+
+/// Compliment to the [HasAnnotations] mixin for metadata [annotations].
+abstract mixin class HasAnnotationsBuilder {
+ /// Annotations as metadata on the node.
+ abstract ListBuilder<Expression> annotations;
+}
diff --git a/pkgs/code_builder/lib/src/mixins/dartdoc.dart b/pkgs/code_builder/lib/src/mixins/dartdoc.dart
new file mode 100644
index 0000000..78144f6
--- /dev/null
+++ b/pkgs/code_builder/lib/src/mixins/dartdoc.dart
@@ -0,0 +1,15 @@
+// Copyright (c) 2017, 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:built_collection/built_collection.dart';
+
+abstract mixin class HasDartDocs {
+ /// Dart docs.
+ BuiltList<String> get docs;
+}
+
+abstract mixin class HasDartDocsBuilder {
+ /// Dart docs.
+ abstract ListBuilder<String> docs;
+}
diff --git a/pkgs/code_builder/lib/src/mixins/generics.dart b/pkgs/code_builder/lib/src/mixins/generics.dart
new file mode 100644
index 0000000..b18a858
--- /dev/null
+++ b/pkgs/code_builder/lib/src/mixins/generics.dart
@@ -0,0 +1,17 @@
+// Copyright (c) 2017, 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:built_collection/built_collection.dart';
+
+import '../specs/reference.dart';
+
+abstract mixin class HasGenerics {
+ /// Generic type parameters.
+ BuiltList<Reference> get types;
+}
+
+abstract mixin class HasGenericsBuilder {
+ /// Generic type parameters.
+ abstract ListBuilder<Reference> types;
+}
diff --git a/pkgs/code_builder/lib/src/specs/class.dart b/pkgs/code_builder/lib/src/specs/class.dart
new file mode 100644
index 0000000..4c1915e
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/class.dart
@@ -0,0 +1,128 @@
+// Copyright (c) 2017, 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:built_collection/built_collection.dart';
+import 'package:built_value/built_value.dart';
+import 'package:meta/meta.dart';
+
+import '../base.dart';
+import '../mixins/annotations.dart';
+import '../mixins/dartdoc.dart';
+import '../mixins/generics.dart';
+import '../visitors.dart';
+import 'constructor.dart';
+import 'expression.dart';
+import 'field.dart';
+import 'method.dart';
+import 'reference.dart';
+
+part 'class.g.dart';
+
+@immutable
+abstract class Class extends Object
+ with HasAnnotations, HasDartDocs, HasGenerics
+ implements Built<Class, ClassBuilder>, Spec {
+ factory Class([void Function(ClassBuilder) updates]) = _$Class;
+
+ Class._();
+
+ /// Whether the class is `abstract`.
+ bool get abstract;
+
+ /// Whether the class is `sealed`.
+ bool get sealed;
+
+ /// Whether the class is a `mixin class`.
+ bool get mixin;
+
+ /// The class modifier, i.e. `base`, `final`, `interface`.
+ ClassModifier? get modifier;
+
+ @override
+ BuiltList<Expression> get annotations;
+
+ @override
+ BuiltList<String> get docs;
+
+ Reference? get extend;
+
+ BuiltList<Reference> get implements;
+
+ BuiltList<Reference> get mixins;
+
+ @override
+ BuiltList<Reference> get types;
+
+ BuiltList<Constructor> get constructors;
+ BuiltList<Method> get methods;
+ BuiltList<Field> get fields;
+
+ /// Name of the class.
+ String get name;
+
+ @override
+ R accept<R>(
+ SpecVisitor<R> visitor, [
+ R? context,
+ ]) =>
+ visitor.visitClass(this, context);
+}
+
+enum ClassModifier {
+ base,
+ final$,
+ interface;
+
+ String get name => switch (this) {
+ ClassModifier.base => 'base',
+ ClassModifier.final$ => 'final',
+ ClassModifier.interface => 'interface'
+ };
+}
+
+abstract class ClassBuilder extends Object
+ with HasAnnotationsBuilder, HasDartDocsBuilder, HasGenericsBuilder
+ implements Builder<Class, ClassBuilder> {
+ factory ClassBuilder() = _$ClassBuilder;
+
+ ClassBuilder._();
+
+ @override
+ void update(void Function(ClassBuilder)? updates) {
+ updates?.call(this);
+ }
+
+ /// Whether the class is `abstract`.
+ bool abstract = false;
+
+ /// Whether the class is `sealed`.
+ bool sealed = false;
+
+ /// Whether the class is a `mixin class`.
+ bool mixin = false;
+
+ /// The class modifier, i.e. `base`, `final`, `interface`.
+ ClassModifier? modifier;
+
+ @override
+ ListBuilder<Expression> annotations = ListBuilder<Expression>();
+
+ @override
+ ListBuilder<String> docs = ListBuilder<String>();
+
+ Reference? extend;
+
+ ListBuilder<Reference> implements = ListBuilder<Reference>();
+ ListBuilder<Reference> mixins = ListBuilder<Reference>();
+
+ @override
+ ListBuilder<Reference> types = ListBuilder<Reference>();
+
+ ListBuilder<Constructor> constructors = ListBuilder<Constructor>();
+ ListBuilder<Method> methods = ListBuilder<Method>();
+ ListBuilder<Field> fields = ListBuilder<Field>();
+
+ /// Name of the class.
+ String? name;
+}
diff --git a/pkgs/code_builder/lib/src/specs/class.g.dart b/pkgs/code_builder/lib/src/specs/class.g.dart
new file mode 100644
index 0000000..423f576
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/class.g.dart
@@ -0,0 +1,405 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'class.dart';
+
+// **************************************************************************
+// BuiltValueGenerator
+// **************************************************************************
+
+class _$Class extends Class {
+ @override
+ final bool abstract;
+ @override
+ final bool sealed;
+ @override
+ final bool mixin;
+ @override
+ final ClassModifier? modifier;
+ @override
+ final BuiltList<Expression> annotations;
+ @override
+ final BuiltList<String> docs;
+ @override
+ final Reference? extend;
+ @override
+ final BuiltList<Reference> implements;
+ @override
+ final BuiltList<Reference> mixins;
+ @override
+ final BuiltList<Reference> types;
+ @override
+ final BuiltList<Constructor> constructors;
+ @override
+ final BuiltList<Method> methods;
+ @override
+ final BuiltList<Field> fields;
+ @override
+ final String name;
+
+ 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._() {
+ BuiltValueNullFieldError.checkNotNull(abstract, r'Class', 'abstract');
+ BuiltValueNullFieldError.checkNotNull(sealed, r'Class', 'sealed');
+ BuiltValueNullFieldError.checkNotNull(mixin, r'Class', 'mixin');
+ BuiltValueNullFieldError.checkNotNull(annotations, r'Class', 'annotations');
+ BuiltValueNullFieldError.checkNotNull(docs, r'Class', 'docs');
+ BuiltValueNullFieldError.checkNotNull(implements, r'Class', 'implements');
+ BuiltValueNullFieldError.checkNotNull(mixins, r'Class', 'mixins');
+ BuiltValueNullFieldError.checkNotNull(types, r'Class', 'types');
+ BuiltValueNullFieldError.checkNotNull(
+ constructors, r'Class', 'constructors');
+ BuiltValueNullFieldError.checkNotNull(methods, r'Class', 'methods');
+ BuiltValueNullFieldError.checkNotNull(fields, r'Class', 'fields');
+ BuiltValueNullFieldError.checkNotNull(name, r'Class', 'name');
+ }
+
+ @override
+ Class rebuild(void Function(ClassBuilder) updates) =>
+ (toBuilder()..update(updates)).build();
+
+ @override
+ _$ClassBuilder toBuilder() => new _$ClassBuilder()..replace(this);
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(other, this)) return true;
+ return other is Class &&
+ abstract == other.abstract &&
+ sealed == other.sealed &&
+ mixin == other.mixin &&
+ modifier == other.modifier &&
+ annotations == other.annotations &&
+ docs == other.docs &&
+ extend == other.extend &&
+ implements == other.implements &&
+ mixins == other.mixins &&
+ types == other.types &&
+ constructors == other.constructors &&
+ methods == other.methods &&
+ fields == other.fields &&
+ name == other.name;
+ }
+
+ @override
+ int get hashCode {
+ var _$hash = 0;
+ _$hash = $jc(_$hash, abstract.hashCode);
+ _$hash = $jc(_$hash, sealed.hashCode);
+ _$hash = $jc(_$hash, mixin.hashCode);
+ _$hash = $jc(_$hash, modifier.hashCode);
+ _$hash = $jc(_$hash, annotations.hashCode);
+ _$hash = $jc(_$hash, docs.hashCode);
+ _$hash = $jc(_$hash, extend.hashCode);
+ _$hash = $jc(_$hash, implements.hashCode);
+ _$hash = $jc(_$hash, mixins.hashCode);
+ _$hash = $jc(_$hash, types.hashCode);
+ _$hash = $jc(_$hash, constructors.hashCode);
+ _$hash = $jc(_$hash, methods.hashCode);
+ _$hash = $jc(_$hash, fields.hashCode);
+ _$hash = $jc(_$hash, name.hashCode);
+ _$hash = $jf(_$hash);
+ return _$hash;
+ }
+
+ @override
+ String toString() {
+ return (newBuiltValueToStringHelper(r'Class')
+ ..add('abstract', abstract)
+ ..add('sealed', sealed)
+ ..add('mixin', mixin)
+ ..add('modifier', modifier)
+ ..add('annotations', annotations)
+ ..add('docs', docs)
+ ..add('extend', extend)
+ ..add('implements', implements)
+ ..add('mixins', mixins)
+ ..add('types', types)
+ ..add('constructors', constructors)
+ ..add('methods', methods)
+ ..add('fields', fields)
+ ..add('name', name))
+ .toString();
+ }
+}
+
+class _$ClassBuilder extends ClassBuilder {
+ _$Class? _$v;
+
+ @override
+ bool get abstract {
+ _$this;
+ return super.abstract;
+ }
+
+ @override
+ set abstract(bool abstract) {
+ _$this;
+ super.abstract = abstract;
+ }
+
+ @override
+ bool get sealed {
+ _$this;
+ return super.sealed;
+ }
+
+ @override
+ set sealed(bool sealed) {
+ _$this;
+ super.sealed = sealed;
+ }
+
+ @override
+ bool get mixin {
+ _$this;
+ return super.mixin;
+ }
+
+ @override
+ set mixin(bool mixin) {
+ _$this;
+ super.mixin = mixin;
+ }
+
+ @override
+ ClassModifier? get modifier {
+ _$this;
+ return super.modifier;
+ }
+
+ @override
+ set modifier(ClassModifier? modifier) {
+ _$this;
+ super.modifier = modifier;
+ }
+
+ @override
+ ListBuilder<Expression> get annotations {
+ _$this;
+ return super.annotations;
+ }
+
+ @override
+ set annotations(ListBuilder<Expression> annotations) {
+ _$this;
+ super.annotations = annotations;
+ }
+
+ @override
+ ListBuilder<String> get docs {
+ _$this;
+ return super.docs;
+ }
+
+ @override
+ set docs(ListBuilder<String> docs) {
+ _$this;
+ super.docs = docs;
+ }
+
+ @override
+ Reference? get extend {
+ _$this;
+ return super.extend;
+ }
+
+ @override
+ set extend(Reference? extend) {
+ _$this;
+ super.extend = extend;
+ }
+
+ @override
+ ListBuilder<Reference> get implements {
+ _$this;
+ return super.implements;
+ }
+
+ @override
+ set implements(ListBuilder<Reference> implements) {
+ _$this;
+ super.implements = implements;
+ }
+
+ @override
+ ListBuilder<Reference> get mixins {
+ _$this;
+ return super.mixins;
+ }
+
+ @override
+ set mixins(ListBuilder<Reference> mixins) {
+ _$this;
+ super.mixins = mixins;
+ }
+
+ @override
+ ListBuilder<Reference> get types {
+ _$this;
+ return super.types;
+ }
+
+ @override
+ set types(ListBuilder<Reference> types) {
+ _$this;
+ super.types = types;
+ }
+
+ @override
+ ListBuilder<Constructor> get constructors {
+ _$this;
+ return super.constructors;
+ }
+
+ @override
+ set constructors(ListBuilder<Constructor> constructors) {
+ _$this;
+ super.constructors = constructors;
+ }
+
+ @override
+ ListBuilder<Method> get methods {
+ _$this;
+ return super.methods;
+ }
+
+ @override
+ set methods(ListBuilder<Method> methods) {
+ _$this;
+ super.methods = methods;
+ }
+
+ @override
+ ListBuilder<Field> get fields {
+ _$this;
+ return super.fields;
+ }
+
+ @override
+ set fields(ListBuilder<Field> fields) {
+ _$this;
+ super.fields = fields;
+ }
+
+ @override
+ String? get name {
+ _$this;
+ return super.name;
+ }
+
+ @override
+ set name(String? name) {
+ _$this;
+ super.name = name;
+ }
+
+ _$ClassBuilder() : super._();
+
+ ClassBuilder get _$this {
+ final $v = _$v;
+ if ($v != null) {
+ super.abstract = $v.abstract;
+ super.sealed = $v.sealed;
+ super.mixin = $v.mixin;
+ super.modifier = $v.modifier;
+ super.annotations = $v.annotations.toBuilder();
+ super.docs = $v.docs.toBuilder();
+ super.extend = $v.extend;
+ super.implements = $v.implements.toBuilder();
+ super.mixins = $v.mixins.toBuilder();
+ super.types = $v.types.toBuilder();
+ super.constructors = $v.constructors.toBuilder();
+ super.methods = $v.methods.toBuilder();
+ super.fields = $v.fields.toBuilder();
+ super.name = $v.name;
+ _$v = null;
+ }
+ return this;
+ }
+
+ @override
+ void replace(Class other) {
+ ArgumentError.checkNotNull(other, 'other');
+ _$v = other as _$Class;
+ }
+
+ @override
+ void update(void Function(ClassBuilder)? updates) {
+ if (updates != null) updates(this);
+ }
+
+ @override
+ Class build() => _build();
+
+ _$Class _build() {
+ _$Class _$result;
+ try {
+ _$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'));
+ } catch (_) {
+ late String _$failedField;
+ try {
+ _$failedField = 'annotations';
+ annotations.build();
+ _$failedField = 'docs';
+ docs.build();
+
+ _$failedField = 'implements';
+ implements.build();
+ _$failedField = 'mixins';
+ mixins.build();
+ _$failedField = 'types';
+ types.build();
+ _$failedField = 'constructors';
+ constructors.build();
+ _$failedField = 'methods';
+ methods.build();
+ _$failedField = 'fields';
+ fields.build();
+ } catch (e) {
+ throw new BuiltValueNestedFieldError(
+ r'Class', _$failedField, e.toString());
+ }
+ rethrow;
+ }
+ replace(_$result);
+ return _$result;
+ }
+}
+
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/pkgs/code_builder/lib/src/specs/code.dart b/pkgs/code_builder/lib/src/specs/code.dart
new file mode 100644
index 0000000..521eb1f
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/code.dart
@@ -0,0 +1,165 @@
+// Copyright (c) 2017, 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:built_collection/built_collection.dart';
+import 'package:built_value/built_value.dart';
+import 'package:meta/meta.dart';
+
+import '../allocator.dart';
+import '../base.dart';
+import '../emitter.dart';
+import '../visitors.dart';
+import 'expression.dart';
+import 'reference.dart';
+
+part 'code.g.dart';
+
+/// Returns a scoped symbol to [Reference], with an import prefix if needed.
+///
+/// This is short-hand for [Allocator.allocate] in most implementations.
+typedef Allocate = String Function(Reference);
+
+/// Represents arbitrary Dart code (either expressions or statements).
+///
+/// See the various constructors for details.
+abstract class Code implements Spec {
+ /// Create a simple code body based on a static string.
+ const factory Code(String code) = StaticCode._;
+
+ /// Create a code based that may use a provided [Allocator] for scoping:
+ ///
+ /// ```dart
+ /// // Emits `_i123.FooType()`, where `_i123` is the import prefix.
+ ///
+ /// Code.scope((a) {
+ /// return '${a.allocate(fooType)}()'
+ /// });
+ /// ```
+ const factory Code.scope(
+ String Function(Allocate) scope,
+ ) = ScopedCode._;
+
+ @override
+ R accept<R>(covariant CodeVisitor<R> visitor, [R? context]);
+}
+
+/// Represents blocks of statements of Dart code.
+abstract class Block implements Built<Block, BlockBuilder>, Code, Spec {
+ factory Block([void Function(BlockBuilder) updates]) = _$Block;
+
+ factory Block.of(Iterable<Code> statements) =>
+ Block((b) => b..statements.addAll(statements));
+
+ Block._();
+
+ @override
+ R accept<R>(covariant CodeVisitor<R> visitor, [R? context]) =>
+ visitor.visitBlock(this, context);
+
+ BuiltList<Code> get statements;
+}
+
+abstract class BlockBuilder implements Builder<Block, BlockBuilder> {
+ factory BlockBuilder() = _$BlockBuilder;
+
+ BlockBuilder._();
+
+ /// Adds an [expression] to [statements].
+ ///
+ /// **NOTE**: Not all expressions are _useful_ statements.
+ void addExpression(Expression expression) {
+ statements.add(expression.statement);
+ }
+
+ ListBuilder<Code> statements = ListBuilder<Code>();
+}
+
+/// Knowledge of different types of blocks of code in Dart.
+///
+/// **INTERNAL ONLY**.
+abstract class CodeVisitor<T> implements SpecVisitor<T> {
+ T visitBlock(Block code, [T? context]);
+
+ T visitStaticCode(StaticCode code, [T? context]);
+
+ T visitScopedCode(ScopedCode code, [T? context]);
+}
+
+/// Knowledge of how to write valid Dart code from [CodeVisitor].
+abstract mixin class CodeEmitter implements CodeVisitor<StringSink> {
+ @protected
+ Allocator get allocator;
+
+ @override
+ StringSink visitBlock(Block block, [StringSink? output]) {
+ output ??= StringBuffer();
+ return visitAll<Code>(block.statements, output, (statement) {
+ statement.accept(this, output);
+ }, '\n');
+ }
+
+ @override
+ StringSink visitStaticCode(StaticCode code, [StringSink? output]) {
+ output ??= StringBuffer();
+ return output..write(code.code);
+ }
+
+ @override
+ StringSink visitScopedCode(ScopedCode code, [StringSink? output]) {
+ output ??= StringBuffer();
+ return output..write(code.code(allocator.allocate));
+ }
+}
+
+/// Represents a code block that requires lazy visiting.
+class LazyCode implements Code {
+ final Spec Function(SpecVisitor) generate;
+
+ const LazyCode._(this.generate);
+
+ @override
+ R accept<R>(CodeVisitor<R> visitor, [R? context]) =>
+ generate(visitor).accept(visitor, context);
+}
+
+/// Returns a generic [Code] that is lazily generated when visited.
+Code lazyCode(Code Function() generate) => _LazyCode(generate);
+
+class _LazyCode implements Code {
+ final Code Function() generate;
+
+ const _LazyCode(this.generate);
+
+ @override
+ R accept<R>(CodeVisitor<R> visitor, [R? context]) =>
+ generate().accept(visitor, context);
+}
+
+/// Represents a simple, literal code block to be inserted as-is.
+class StaticCode implements Code {
+ final String code;
+
+ const StaticCode._(this.code);
+
+ @override
+ R accept<R>(CodeVisitor<R> visitor, [R? context]) =>
+ visitor.visitStaticCode(this, context);
+
+ @override
+ String toString() => code;
+}
+
+/// Represents a code block that may require scoping.
+class ScopedCode implements Code {
+ final String Function(Allocate) code;
+
+ const ScopedCode._(this.code);
+
+ @override
+ R accept<R>(CodeVisitor<R> visitor, [R? context]) =>
+ visitor.visitScopedCode(this, context);
+
+ @override
+ String toString() => code((ref) => ref.symbol!);
+}
diff --git a/pkgs/code_builder/lib/src/specs/code.g.dart b/pkgs/code_builder/lib/src/specs/code.g.dart
new file mode 100644
index 0000000..7b5ba78
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/code.g.dart
@@ -0,0 +1,109 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'code.dart';
+
+// **************************************************************************
+// BuiltValueGenerator
+// **************************************************************************
+
+class _$Block extends Block {
+ @override
+ final BuiltList<Code> statements;
+
+ factory _$Block([void Function(BlockBuilder)? updates]) =>
+ (new BlockBuilder()..update(updates)).build() as _$Block;
+
+ _$Block._({required this.statements}) : super._() {
+ BuiltValueNullFieldError.checkNotNull(statements, r'Block', 'statements');
+ }
+
+ @override
+ Block rebuild(void Function(BlockBuilder) updates) =>
+ (toBuilder()..update(updates)).build();
+
+ @override
+ _$BlockBuilder toBuilder() => new _$BlockBuilder()..replace(this);
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(other, this)) return true;
+ return other is Block && statements == other.statements;
+ }
+
+ @override
+ int get hashCode {
+ var _$hash = 0;
+ _$hash = $jc(_$hash, statements.hashCode);
+ _$hash = $jf(_$hash);
+ return _$hash;
+ }
+
+ @override
+ String toString() {
+ return (newBuiltValueToStringHelper(r'Block')
+ ..add('statements', statements))
+ .toString();
+ }
+}
+
+class _$BlockBuilder extends BlockBuilder {
+ _$Block? _$v;
+
+ @override
+ ListBuilder<Code> get statements {
+ _$this;
+ return super.statements;
+ }
+
+ @override
+ set statements(ListBuilder<Code> statements) {
+ _$this;
+ super.statements = statements;
+ }
+
+ _$BlockBuilder() : super._();
+
+ BlockBuilder get _$this {
+ final $v = _$v;
+ if ($v != null) {
+ super.statements = $v.statements.toBuilder();
+ _$v = null;
+ }
+ return this;
+ }
+
+ @override
+ void replace(Block other) {
+ ArgumentError.checkNotNull(other, 'other');
+ _$v = other as _$Block;
+ }
+
+ @override
+ void update(void Function(BlockBuilder)? updates) {
+ if (updates != null) updates(this);
+ }
+
+ @override
+ Block build() => _build();
+
+ _$Block _build() {
+ _$Block _$result;
+ try {
+ _$result = _$v ?? new _$Block._(statements: statements.build());
+ } catch (_) {
+ late String _$failedField;
+ try {
+ _$failedField = 'statements';
+ statements.build();
+ } catch (e) {
+ throw new BuiltValueNestedFieldError(
+ r'Block', _$failedField, e.toString());
+ }
+ rethrow;
+ }
+ replace(_$result);
+ return _$result;
+ }
+}
+
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/pkgs/code_builder/lib/src/specs/constructor.dart b/pkgs/code_builder/lib/src/specs/constructor.dart
new file mode 100644
index 0000000..1c98eb5
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/constructor.dart
@@ -0,0 +1,106 @@
+// Copyright (c) 2017, 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:built_collection/built_collection.dart';
+import 'package:built_value/built_value.dart';
+import 'package:meta/meta.dart';
+
+import '../mixins/annotations.dart';
+import '../mixins/dartdoc.dart';
+import 'code.dart';
+import 'expression.dart';
+import 'method.dart';
+import 'reference.dart';
+
+part 'constructor.g.dart';
+
+@immutable
+abstract class Constructor extends Object
+ with HasAnnotations, HasDartDocs
+ implements Built<Constructor, ConstructorBuilder> {
+ factory Constructor([void Function(ConstructorBuilder) updates]) =
+ _$Constructor;
+
+ Constructor._();
+
+ @override
+ BuiltList<Expression> get annotations;
+
+ @override
+ BuiltList<String> get docs;
+
+ /// Optional parameters.
+ BuiltList<Parameter> get optionalParameters;
+
+ /// Required parameters.
+ BuiltList<Parameter> get requiredParameters;
+
+ /// Constructor initializer statements.
+ BuiltList<Code> get initializers;
+
+ /// Body of the method.
+ Code? get body;
+
+ /// Whether the constructor should be prefixed with `external`.
+ bool get external;
+
+ /// Whether the constructor should be prefixed with `const`.
+ bool get constant;
+
+ /// Whether this constructor should be prefixed with `factory`.
+ bool get factory;
+
+ /// Whether this constructor is a simple lambda expression.
+ bool? get lambda;
+
+ /// Name of the constructor - optional.
+ String? get name;
+
+ /// If non-null, redirect to this constructor.
+ Reference? get redirect;
+}
+
+abstract class ConstructorBuilder extends Object
+ with HasAnnotationsBuilder, HasDartDocsBuilder
+ implements Builder<Constructor, ConstructorBuilder> {
+ factory ConstructorBuilder() = _$ConstructorBuilder;
+
+ ConstructorBuilder._();
+
+ @override
+ ListBuilder<Expression> annotations = ListBuilder<Expression>();
+
+ @override
+ ListBuilder<String> docs = ListBuilder<String>();
+
+ /// Optional parameters.
+ ListBuilder<Parameter> optionalParameters = ListBuilder<Parameter>();
+
+ /// Required parameters.
+ ListBuilder<Parameter> requiredParameters = ListBuilder<Parameter>();
+
+ /// Constructor initializer statements.
+ ListBuilder<Code> initializers = ListBuilder<Code>();
+
+ /// Body of the constructor.
+ Code? body;
+
+ /// Whether the constructor should be prefixed with `const`.
+ bool constant = false;
+
+ /// Whether the constructor should be prefixed with `external`.
+ bool external = false;
+
+ /// Whether this constructor should be prefixed with `factory`.
+ bool factory = false;
+
+ /// Whether this constructor is a simple lambda expression.
+ bool? lambda;
+
+ /// Name of the constructor - optional.
+ String? name;
+
+ /// If non-null, redirect to this constructor.
+ Reference? redirect;
+}
diff --git a/pkgs/code_builder/lib/src/specs/constructor.g.dart b/pkgs/code_builder/lib/src/specs/constructor.g.dart
new file mode 100644
index 0000000..3f06932
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/constructor.g.dart
@@ -0,0 +1,356 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'constructor.dart';
+
+// **************************************************************************
+// BuiltValueGenerator
+// **************************************************************************
+
+class _$Constructor extends Constructor {
+ @override
+ final BuiltList<Expression> annotations;
+ @override
+ final BuiltList<String> docs;
+ @override
+ final BuiltList<Parameter> optionalParameters;
+ @override
+ final BuiltList<Parameter> requiredParameters;
+ @override
+ final BuiltList<Code> initializers;
+ @override
+ final Code? body;
+ @override
+ final bool external;
+ @override
+ final bool constant;
+ @override
+ final bool factory;
+ @override
+ final bool? lambda;
+ @override
+ final String? name;
+ @override
+ final Reference? redirect;
+
+ 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._() {
+ BuiltValueNullFieldError.checkNotNull(
+ annotations, r'Constructor', 'annotations');
+ BuiltValueNullFieldError.checkNotNull(docs, r'Constructor', 'docs');
+ BuiltValueNullFieldError.checkNotNull(
+ optionalParameters, r'Constructor', 'optionalParameters');
+ BuiltValueNullFieldError.checkNotNull(
+ requiredParameters, r'Constructor', 'requiredParameters');
+ BuiltValueNullFieldError.checkNotNull(
+ initializers, r'Constructor', 'initializers');
+ BuiltValueNullFieldError.checkNotNull(external, r'Constructor', 'external');
+ BuiltValueNullFieldError.checkNotNull(constant, r'Constructor', 'constant');
+ BuiltValueNullFieldError.checkNotNull(factory, r'Constructor', 'factory');
+ }
+
+ @override
+ Constructor rebuild(void Function(ConstructorBuilder) updates) =>
+ (toBuilder()..update(updates)).build();
+
+ @override
+ _$ConstructorBuilder toBuilder() => new _$ConstructorBuilder()..replace(this);
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(other, this)) return true;
+ return other is Constructor &&
+ annotations == other.annotations &&
+ docs == other.docs &&
+ optionalParameters == other.optionalParameters &&
+ requiredParameters == other.requiredParameters &&
+ initializers == other.initializers &&
+ body == other.body &&
+ external == other.external &&
+ constant == other.constant &&
+ factory == other.factory &&
+ lambda == other.lambda &&
+ name == other.name &&
+ redirect == other.redirect;
+ }
+
+ @override
+ int get hashCode {
+ var _$hash = 0;
+ _$hash = $jc(_$hash, annotations.hashCode);
+ _$hash = $jc(_$hash, docs.hashCode);
+ _$hash = $jc(_$hash, optionalParameters.hashCode);
+ _$hash = $jc(_$hash, requiredParameters.hashCode);
+ _$hash = $jc(_$hash, initializers.hashCode);
+ _$hash = $jc(_$hash, body.hashCode);
+ _$hash = $jc(_$hash, external.hashCode);
+ _$hash = $jc(_$hash, constant.hashCode);
+ _$hash = $jc(_$hash, factory.hashCode);
+ _$hash = $jc(_$hash, lambda.hashCode);
+ _$hash = $jc(_$hash, name.hashCode);
+ _$hash = $jc(_$hash, redirect.hashCode);
+ _$hash = $jf(_$hash);
+ return _$hash;
+ }
+
+ @override
+ String toString() {
+ return (newBuiltValueToStringHelper(r'Constructor')
+ ..add('annotations', annotations)
+ ..add('docs', docs)
+ ..add('optionalParameters', optionalParameters)
+ ..add('requiredParameters', requiredParameters)
+ ..add('initializers', initializers)
+ ..add('body', body)
+ ..add('external', external)
+ ..add('constant', constant)
+ ..add('factory', factory)
+ ..add('lambda', lambda)
+ ..add('name', name)
+ ..add('redirect', redirect))
+ .toString();
+ }
+}
+
+class _$ConstructorBuilder extends ConstructorBuilder {
+ _$Constructor? _$v;
+
+ @override
+ ListBuilder<Expression> get annotations {
+ _$this;
+ return super.annotations;
+ }
+
+ @override
+ set annotations(ListBuilder<Expression> annotations) {
+ _$this;
+ super.annotations = annotations;
+ }
+
+ @override
+ ListBuilder<String> get docs {
+ _$this;
+ return super.docs;
+ }
+
+ @override
+ set docs(ListBuilder<String> docs) {
+ _$this;
+ super.docs = docs;
+ }
+
+ @override
+ ListBuilder<Parameter> get optionalParameters {
+ _$this;
+ return super.optionalParameters;
+ }
+
+ @override
+ set optionalParameters(ListBuilder<Parameter> optionalParameters) {
+ _$this;
+ super.optionalParameters = optionalParameters;
+ }
+
+ @override
+ ListBuilder<Parameter> get requiredParameters {
+ _$this;
+ return super.requiredParameters;
+ }
+
+ @override
+ set requiredParameters(ListBuilder<Parameter> requiredParameters) {
+ _$this;
+ super.requiredParameters = requiredParameters;
+ }
+
+ @override
+ ListBuilder<Code> get initializers {
+ _$this;
+ return super.initializers;
+ }
+
+ @override
+ set initializers(ListBuilder<Code> initializers) {
+ _$this;
+ super.initializers = initializers;
+ }
+
+ @override
+ Code? get body {
+ _$this;
+ return super.body;
+ }
+
+ @override
+ set body(Code? body) {
+ _$this;
+ super.body = body;
+ }
+
+ @override
+ bool get external {
+ _$this;
+ return super.external;
+ }
+
+ @override
+ set external(bool external) {
+ _$this;
+ super.external = external;
+ }
+
+ @override
+ bool get constant {
+ _$this;
+ return super.constant;
+ }
+
+ @override
+ set constant(bool constant) {
+ _$this;
+ super.constant = constant;
+ }
+
+ @override
+ bool get factory {
+ _$this;
+ return super.factory;
+ }
+
+ @override
+ set factory(bool factory) {
+ _$this;
+ super.factory = factory;
+ }
+
+ @override
+ bool? get lambda {
+ _$this;
+ return super.lambda;
+ }
+
+ @override
+ set lambda(bool? lambda) {
+ _$this;
+ super.lambda = lambda;
+ }
+
+ @override
+ String? get name {
+ _$this;
+ return super.name;
+ }
+
+ @override
+ set name(String? name) {
+ _$this;
+ super.name = name;
+ }
+
+ @override
+ Reference? get redirect {
+ _$this;
+ return super.redirect;
+ }
+
+ @override
+ set redirect(Reference? redirect) {
+ _$this;
+ super.redirect = redirect;
+ }
+
+ _$ConstructorBuilder() : super._();
+
+ ConstructorBuilder get _$this {
+ final $v = _$v;
+ if ($v != null) {
+ super.annotations = $v.annotations.toBuilder();
+ super.docs = $v.docs.toBuilder();
+ super.optionalParameters = $v.optionalParameters.toBuilder();
+ super.requiredParameters = $v.requiredParameters.toBuilder();
+ super.initializers = $v.initializers.toBuilder();
+ super.body = $v.body;
+ super.external = $v.external;
+ super.constant = $v.constant;
+ super.factory = $v.factory;
+ super.lambda = $v.lambda;
+ super.name = $v.name;
+ super.redirect = $v.redirect;
+ _$v = null;
+ }
+ return this;
+ }
+
+ @override
+ void replace(Constructor other) {
+ ArgumentError.checkNotNull(other, 'other');
+ _$v = other as _$Constructor;
+ }
+
+ @override
+ void update(void Function(ConstructorBuilder)? updates) {
+ if (updates != null) updates(this);
+ }
+
+ @override
+ Constructor build() => _build();
+
+ _$Constructor _build() {
+ _$Constructor _$result;
+ try {
+ _$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);
+ } catch (_) {
+ late String _$failedField;
+ try {
+ _$failedField = 'annotations';
+ annotations.build();
+ _$failedField = 'docs';
+ docs.build();
+ _$failedField = 'optionalParameters';
+ optionalParameters.build();
+ _$failedField = 'requiredParameters';
+ requiredParameters.build();
+ _$failedField = 'initializers';
+ initializers.build();
+ } catch (e) {
+ throw new BuiltValueNestedFieldError(
+ r'Constructor', _$failedField, e.toString());
+ }
+ rethrow;
+ }
+ replace(_$result);
+ return _$result;
+ }
+}
+
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/pkgs/code_builder/lib/src/specs/directive.dart b/pkgs/code_builder/lib/src/specs/directive.dart
new file mode 100644
index 0000000..e10c0ea
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/directive.dart
@@ -0,0 +1,158 @@
+// Copyright (c) 2017, 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:built_value/built_value.dart';
+import 'package:collection/collection.dart';
+import 'package:meta/meta.dart';
+
+import '../base.dart';
+import '../visitors.dart';
+
+part 'directive.g.dart';
+
+@immutable
+abstract class Directive
+ implements Built<Directive, DirectiveBuilder>, Spec, Comparable<Directive> {
+ factory Directive([void Function(DirectiveBuilder) updates]) = _$Directive;
+
+ factory Directive.import(
+ String url, {
+ String? as,
+ List<String> show = const [],
+ List<String> hide = const [],
+ }) =>
+ Directive((builder) => builder
+ ..as = as
+ ..type = DirectiveType.import
+ ..url = url
+ ..show.addAll(show)
+ ..hide.addAll(hide));
+
+ factory Directive.importDeferredAs(
+ String url,
+ String as, {
+ List<String> show = const [],
+ List<String> hide = const [],
+ }) =>
+ Directive((builder) => builder
+ ..as = as
+ ..type = DirectiveType.import
+ ..url = url
+ ..deferred = true
+ ..show.addAll(show)
+ ..hide.addAll(hide));
+
+ factory Directive.export(
+ String url, {
+ List<String> show = const [],
+ List<String> 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._();
+
+ String? get as;
+
+ String get url;
+
+ DirectiveType get type;
+
+ List<String> get show;
+
+ List<String> get hide;
+
+ bool get deferred;
+
+ @override
+ R accept<R>(
+ SpecVisitor<R> visitor, [
+ R? context,
+ ]) =>
+ visitor.visitDirective(this, context);
+
+ @override
+ int compareTo(Directive other) => _compareDirectives(this, other);
+}
+
+abstract class DirectiveBuilder
+ implements Builder<Directive, DirectiveBuilder> {
+ factory DirectiveBuilder() = _$DirectiveBuilder;
+
+ DirectiveBuilder._();
+
+ bool deferred = false;
+
+ String? as;
+
+ String? url;
+
+ List<String> show = <String>[];
+
+ List<String> hide = <String>[];
+
+ DirectiveType? type;
+}
+
+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
+/// `directives_ordering` lint.
+///
+/// 1. `import`s before `export`s
+/// 2. `dart:`
+/// 3. `package:`
+/// 4. relative
+/// 5. `part`s
+int _compareDirectives(Directive a, Directive b) {
+ // NOTE: using the fact that `import` is before `export` in the
+ // `DirectiveType` enum – which allows us to compare using `indexOf`.
+ var value = DirectiveType.values
+ .indexOf(a.type)
+ .compareTo(DirectiveType.values.indexOf(b.type));
+
+ if (value == 0) {
+ final uriA = Uri.parse(a.url);
+ final uriB = Uri.parse(b.url);
+
+ if (uriA.hasScheme) {
+ if (uriB.hasScheme) {
+ // If both import URIs have schemes, compare them based on scheme
+ // `dart` will sort before `package` which is what we want
+ // schemes are case-insensitive, so compare accordingly
+ value = compareAsciiLowerCase(uriA.scheme, uriB.scheme);
+ } else {
+ value = -1;
+ }
+ } else if (uriB.hasScheme) {
+ value = 1;
+ }
+
+ // If both schemes are the same, compare based on path
+ if (value == 0) {
+ value = compareAsciiLowerCase(uriA.path, uriB.path);
+ }
+
+ assert((value == 0) == (a.url == b.url));
+ }
+
+ return value;
+}
diff --git a/pkgs/code_builder/lib/src/specs/directive.g.dart b/pkgs/code_builder/lib/src/specs/directive.g.dart
new file mode 100644
index 0000000..b28158e
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/directive.g.dart
@@ -0,0 +1,210 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'directive.dart';
+
+// **************************************************************************
+// BuiltValueGenerator
+// **************************************************************************
+
+class _$Directive extends Directive {
+ @override
+ final String? as;
+ @override
+ final String url;
+ @override
+ final DirectiveType type;
+ @override
+ final List<String> show;
+ @override
+ final List<String> hide;
+ @override
+ final bool deferred;
+
+ 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._() {
+ BuiltValueNullFieldError.checkNotNull(url, r'Directive', 'url');
+ BuiltValueNullFieldError.checkNotNull(type, r'Directive', 'type');
+ BuiltValueNullFieldError.checkNotNull(show, r'Directive', 'show');
+ BuiltValueNullFieldError.checkNotNull(hide, r'Directive', 'hide');
+ BuiltValueNullFieldError.checkNotNull(deferred, r'Directive', 'deferred');
+ }
+
+ @override
+ Directive rebuild(void Function(DirectiveBuilder) updates) =>
+ (toBuilder()..update(updates)).build();
+
+ @override
+ _$DirectiveBuilder toBuilder() => new _$DirectiveBuilder()..replace(this);
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(other, this)) return true;
+ return other is Directive &&
+ as == other.as &&
+ url == other.url &&
+ type == other.type &&
+ show == other.show &&
+ hide == other.hide &&
+ deferred == other.deferred;
+ }
+
+ @override
+ int get hashCode {
+ var _$hash = 0;
+ _$hash = $jc(_$hash, as.hashCode);
+ _$hash = $jc(_$hash, url.hashCode);
+ _$hash = $jc(_$hash, type.hashCode);
+ _$hash = $jc(_$hash, show.hashCode);
+ _$hash = $jc(_$hash, hide.hashCode);
+ _$hash = $jc(_$hash, deferred.hashCode);
+ _$hash = $jf(_$hash);
+ return _$hash;
+ }
+
+ @override
+ String toString() {
+ return (newBuiltValueToStringHelper(r'Directive')
+ ..add('as', as)
+ ..add('url', url)
+ ..add('type', type)
+ ..add('show', show)
+ ..add('hide', hide)
+ ..add('deferred', deferred))
+ .toString();
+ }
+}
+
+class _$DirectiveBuilder extends DirectiveBuilder {
+ _$Directive? _$v;
+
+ @override
+ String? get as {
+ _$this;
+ return super.as;
+ }
+
+ @override
+ set as(String? as) {
+ _$this;
+ super.as = as;
+ }
+
+ @override
+ String? get url {
+ _$this;
+ return super.url;
+ }
+
+ @override
+ set url(String? url) {
+ _$this;
+ super.url = url;
+ }
+
+ @override
+ DirectiveType? get type {
+ _$this;
+ return super.type;
+ }
+
+ @override
+ set type(DirectiveType? type) {
+ _$this;
+ super.type = type;
+ }
+
+ @override
+ List<String> get show {
+ _$this;
+ return super.show;
+ }
+
+ @override
+ set show(List<String> show) {
+ _$this;
+ super.show = show;
+ }
+
+ @override
+ List<String> get hide {
+ _$this;
+ return super.hide;
+ }
+
+ @override
+ set hide(List<String> hide) {
+ _$this;
+ super.hide = hide;
+ }
+
+ @override
+ bool get deferred {
+ _$this;
+ return super.deferred;
+ }
+
+ @override
+ set deferred(bool deferred) {
+ _$this;
+ super.deferred = deferred;
+ }
+
+ _$DirectiveBuilder() : super._();
+
+ DirectiveBuilder get _$this {
+ final $v = _$v;
+ if ($v != null) {
+ super.as = $v.as;
+ super.url = $v.url;
+ super.type = $v.type;
+ super.show = $v.show;
+ super.hide = $v.hide;
+ super.deferred = $v.deferred;
+ _$v = null;
+ }
+ return this;
+ }
+
+ @override
+ void replace(Directive other) {
+ ArgumentError.checkNotNull(other, 'other');
+ _$v = other as _$Directive;
+ }
+
+ @override
+ void update(void Function(DirectiveBuilder)? updates) {
+ if (updates != null) updates(this);
+ }
+
+ @override
+ Directive build() => _build();
+
+ _$Directive _build() {
+ 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'));
+ replace(_$result);
+ return _$result;
+ }
+}
+
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/pkgs/code_builder/lib/src/specs/enum.dart b/pkgs/code_builder/lib/src/specs/enum.dart
new file mode 100644
index 0000000..c58f92c
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/enum.dart
@@ -0,0 +1,139 @@
+// Copyright (c) 2020, 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:built_collection/built_collection.dart';
+import 'package:built_value/built_value.dart';
+import 'package:meta/meta.dart';
+
+import '../../code_builder.dart';
+import '../mixins/annotations.dart';
+import '../mixins/dartdoc.dart';
+import '../mixins/generics.dart';
+import '../visitors.dart';
+
+part 'enum.g.dart';
+
+@immutable
+abstract class Enum extends Object
+ with HasAnnotations, HasDartDocs, HasGenerics
+ implements Built<Enum, EnumBuilder>, Spec {
+ factory Enum([void Function(EnumBuilder) updates]) = _$Enum;
+
+ Enum._();
+
+ String get name;
+
+ BuiltList<EnumValue> get values;
+
+ @override
+ BuiltList<Expression> get annotations;
+
+ @override
+ BuiltList<String> get docs;
+
+ BuiltList<Reference> get implements;
+
+ BuiltList<Reference> get mixins;
+
+ @override
+ BuiltList<Reference> get types;
+
+ BuiltList<Constructor> get constructors;
+ BuiltList<Method> get methods;
+ BuiltList<Field> get fields;
+
+ @override
+ R accept<R>(
+ SpecVisitor<R> visitor, [
+ R? context,
+ ]) =>
+ visitor.visitEnum(this, context);
+}
+
+abstract class EnumBuilder extends Object
+ with HasAnnotationsBuilder, HasDartDocsBuilder, HasGenericsBuilder
+ implements Builder<Enum, EnumBuilder> {
+ factory EnumBuilder() = _$EnumBuilder;
+
+ EnumBuilder._();
+
+ String? name;
+
+ ListBuilder<EnumValue> values = ListBuilder<EnumValue>();
+
+ @override
+ ListBuilder<Expression> annotations = ListBuilder<Expression>();
+
+ @override
+ ListBuilder<String> docs = ListBuilder<String>();
+
+ ListBuilder<Reference> implements = ListBuilder<Reference>();
+ ListBuilder<Reference> mixins = ListBuilder<Reference>();
+
+ @override
+ ListBuilder<Reference> types = ListBuilder<Reference>();
+
+ ListBuilder<Constructor> constructors = ListBuilder<Constructor>();
+ ListBuilder<Method> methods = ListBuilder<Method>();
+ ListBuilder<Field> fields = ListBuilder<Field>();
+}
+
+@immutable
+abstract class EnumValue extends Object
+ with HasAnnotations, HasDartDocs, HasGenerics
+ implements Built<EnumValue, EnumValueBuilder> {
+ factory EnumValue([void Function(EnumValueBuilder) updates]) = _$EnumValue;
+
+ EnumValue._();
+
+ String get name;
+
+ @override
+ BuiltList<Expression> get annotations;
+
+ @override
+ BuiltList<String> get docs;
+
+ /// The name of the constructor to target.
+ ///
+ /// If `null` uses the unnamed constructor.
+ String? get constructorName;
+
+ @override
+ BuiltList<Reference> get types;
+
+ /// Arguments to the constructor.
+ BuiltList<Expression> get arguments;
+
+ /// Named arguments to the constructor.
+ BuiltMap<String, Expression> get namedArguments;
+}
+
+abstract class EnumValueBuilder extends Object
+ with HasAnnotationsBuilder, HasDartDocsBuilder, HasGenericsBuilder
+ implements Builder<EnumValue, EnumValueBuilder> {
+ factory EnumValueBuilder() = _$EnumValueBuilder;
+
+ EnumValueBuilder._();
+
+ String? name;
+
+ @override
+ ListBuilder<Expression> annotations = ListBuilder<Expression>();
+
+ @override
+ ListBuilder<String> docs = ListBuilder<String>();
+
+ /// The name of the constructor to target.
+ String? constructorName;
+
+ @override
+ ListBuilder<Reference> types = ListBuilder<Reference>();
+
+ /// Arguments to the constructor.
+ ListBuilder<Expression> arguments = ListBuilder();
+
+ /// Named arguments to the constructor.
+ MapBuilder<String, Expression> namedArguments = MapBuilder();
+}
diff --git a/pkgs/code_builder/lib/src/specs/enum.g.dart b/pkgs/code_builder/lib/src/specs/enum.g.dart
new file mode 100644
index 0000000..651cc61
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/enum.g.dart
@@ -0,0 +1,563 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'enum.dart';
+
+// **************************************************************************
+// BuiltValueGenerator
+// **************************************************************************
+
+class _$Enum extends Enum {
+ @override
+ final String name;
+ @override
+ final BuiltList<EnumValue> values;
+ @override
+ final BuiltList<Expression> annotations;
+ @override
+ final BuiltList<String> docs;
+ @override
+ final BuiltList<Reference> implements;
+ @override
+ final BuiltList<Reference> mixins;
+ @override
+ final BuiltList<Reference> types;
+ @override
+ final BuiltList<Constructor> constructors;
+ @override
+ final BuiltList<Method> methods;
+ @override
+ final BuiltList<Field> fields;
+
+ 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._() {
+ BuiltValueNullFieldError.checkNotNull(name, r'Enum', 'name');
+ BuiltValueNullFieldError.checkNotNull(values, r'Enum', 'values');
+ BuiltValueNullFieldError.checkNotNull(annotations, r'Enum', 'annotations');
+ BuiltValueNullFieldError.checkNotNull(docs, r'Enum', 'docs');
+ BuiltValueNullFieldError.checkNotNull(implements, r'Enum', 'implements');
+ BuiltValueNullFieldError.checkNotNull(mixins, r'Enum', 'mixins');
+ BuiltValueNullFieldError.checkNotNull(types, r'Enum', 'types');
+ BuiltValueNullFieldError.checkNotNull(
+ constructors, r'Enum', 'constructors');
+ BuiltValueNullFieldError.checkNotNull(methods, r'Enum', 'methods');
+ BuiltValueNullFieldError.checkNotNull(fields, r'Enum', 'fields');
+ }
+
+ @override
+ Enum rebuild(void Function(EnumBuilder) updates) =>
+ (toBuilder()..update(updates)).build();
+
+ @override
+ _$EnumBuilder toBuilder() => new _$EnumBuilder()..replace(this);
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(other, this)) return true;
+ return other is Enum &&
+ name == other.name &&
+ values == other.values &&
+ annotations == other.annotations &&
+ docs == other.docs &&
+ implements == other.implements &&
+ mixins == other.mixins &&
+ types == other.types &&
+ constructors == other.constructors &&
+ methods == other.methods &&
+ fields == other.fields;
+ }
+
+ @override
+ int get hashCode {
+ var _$hash = 0;
+ _$hash = $jc(_$hash, name.hashCode);
+ _$hash = $jc(_$hash, values.hashCode);
+ _$hash = $jc(_$hash, annotations.hashCode);
+ _$hash = $jc(_$hash, docs.hashCode);
+ _$hash = $jc(_$hash, implements.hashCode);
+ _$hash = $jc(_$hash, mixins.hashCode);
+ _$hash = $jc(_$hash, types.hashCode);
+ _$hash = $jc(_$hash, constructors.hashCode);
+ _$hash = $jc(_$hash, methods.hashCode);
+ _$hash = $jc(_$hash, fields.hashCode);
+ _$hash = $jf(_$hash);
+ return _$hash;
+ }
+
+ @override
+ String toString() {
+ return (newBuiltValueToStringHelper(r'Enum')
+ ..add('name', name)
+ ..add('values', values)
+ ..add('annotations', annotations)
+ ..add('docs', docs)
+ ..add('implements', implements)
+ ..add('mixins', mixins)
+ ..add('types', types)
+ ..add('constructors', constructors)
+ ..add('methods', methods)
+ ..add('fields', fields))
+ .toString();
+ }
+}
+
+class _$EnumBuilder extends EnumBuilder {
+ _$Enum? _$v;
+
+ @override
+ String? get name {
+ _$this;
+ return super.name;
+ }
+
+ @override
+ set name(String? name) {
+ _$this;
+ super.name = name;
+ }
+
+ @override
+ ListBuilder<EnumValue> get values {
+ _$this;
+ return super.values;
+ }
+
+ @override
+ set values(ListBuilder<EnumValue> values) {
+ _$this;
+ super.values = values;
+ }
+
+ @override
+ ListBuilder<Expression> get annotations {
+ _$this;
+ return super.annotations;
+ }
+
+ @override
+ set annotations(ListBuilder<Expression> annotations) {
+ _$this;
+ super.annotations = annotations;
+ }
+
+ @override
+ ListBuilder<String> get docs {
+ _$this;
+ return super.docs;
+ }
+
+ @override
+ set docs(ListBuilder<String> docs) {
+ _$this;
+ super.docs = docs;
+ }
+
+ @override
+ ListBuilder<Reference> get implements {
+ _$this;
+ return super.implements;
+ }
+
+ @override
+ set implements(ListBuilder<Reference> implements) {
+ _$this;
+ super.implements = implements;
+ }
+
+ @override
+ ListBuilder<Reference> get mixins {
+ _$this;
+ return super.mixins;
+ }
+
+ @override
+ set mixins(ListBuilder<Reference> mixins) {
+ _$this;
+ super.mixins = mixins;
+ }
+
+ @override
+ ListBuilder<Reference> get types {
+ _$this;
+ return super.types;
+ }
+
+ @override
+ set types(ListBuilder<Reference> types) {
+ _$this;
+ super.types = types;
+ }
+
+ @override
+ ListBuilder<Constructor> get constructors {
+ _$this;
+ return super.constructors;
+ }
+
+ @override
+ set constructors(ListBuilder<Constructor> constructors) {
+ _$this;
+ super.constructors = constructors;
+ }
+
+ @override
+ ListBuilder<Method> get methods {
+ _$this;
+ return super.methods;
+ }
+
+ @override
+ set methods(ListBuilder<Method> methods) {
+ _$this;
+ super.methods = methods;
+ }
+
+ @override
+ ListBuilder<Field> get fields {
+ _$this;
+ return super.fields;
+ }
+
+ @override
+ set fields(ListBuilder<Field> fields) {
+ _$this;
+ super.fields = fields;
+ }
+
+ _$EnumBuilder() : super._();
+
+ EnumBuilder get _$this {
+ final $v = _$v;
+ if ($v != null) {
+ super.name = $v.name;
+ super.values = $v.values.toBuilder();
+ super.annotations = $v.annotations.toBuilder();
+ super.docs = $v.docs.toBuilder();
+ super.implements = $v.implements.toBuilder();
+ super.mixins = $v.mixins.toBuilder();
+ super.types = $v.types.toBuilder();
+ super.constructors = $v.constructors.toBuilder();
+ super.methods = $v.methods.toBuilder();
+ super.fields = $v.fields.toBuilder();
+ _$v = null;
+ }
+ return this;
+ }
+
+ @override
+ void replace(Enum other) {
+ ArgumentError.checkNotNull(other, 'other');
+ _$v = other as _$Enum;
+ }
+
+ @override
+ void update(void Function(EnumBuilder)? updates) {
+ if (updates != null) updates(this);
+ }
+
+ @override
+ Enum build() => _build();
+
+ _$Enum _build() {
+ _$Enum _$result;
+ try {
+ _$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());
+ } catch (_) {
+ late String _$failedField;
+ try {
+ _$failedField = 'values';
+ values.build();
+ _$failedField = 'annotations';
+ annotations.build();
+ _$failedField = 'docs';
+ docs.build();
+ _$failedField = 'implements';
+ implements.build();
+ _$failedField = 'mixins';
+ mixins.build();
+ _$failedField = 'types';
+ types.build();
+ _$failedField = 'constructors';
+ constructors.build();
+ _$failedField = 'methods';
+ methods.build();
+ _$failedField = 'fields';
+ fields.build();
+ } catch (e) {
+ throw new BuiltValueNestedFieldError(
+ r'Enum', _$failedField, e.toString());
+ }
+ rethrow;
+ }
+ replace(_$result);
+ return _$result;
+ }
+}
+
+class _$EnumValue extends EnumValue {
+ @override
+ final String name;
+ @override
+ final BuiltList<Expression> annotations;
+ @override
+ final BuiltList<String> docs;
+ @override
+ final String? constructorName;
+ @override
+ final BuiltList<Reference> types;
+ @override
+ final BuiltList<Expression> arguments;
+ @override
+ final BuiltMap<String, Expression> namedArguments;
+
+ 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._() {
+ BuiltValueNullFieldError.checkNotNull(name, r'EnumValue', 'name');
+ BuiltValueNullFieldError.checkNotNull(
+ 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');
+ }
+
+ @override
+ EnumValue rebuild(void Function(EnumValueBuilder) updates) =>
+ (toBuilder()..update(updates)).build();
+
+ @override
+ _$EnumValueBuilder toBuilder() => new _$EnumValueBuilder()..replace(this);
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(other, this)) return true;
+ return other is EnumValue &&
+ name == other.name &&
+ annotations == other.annotations &&
+ docs == other.docs &&
+ constructorName == other.constructorName &&
+ types == other.types &&
+ arguments == other.arguments &&
+ namedArguments == other.namedArguments;
+ }
+
+ @override
+ int get hashCode {
+ var _$hash = 0;
+ _$hash = $jc(_$hash, name.hashCode);
+ _$hash = $jc(_$hash, annotations.hashCode);
+ _$hash = $jc(_$hash, docs.hashCode);
+ _$hash = $jc(_$hash, constructorName.hashCode);
+ _$hash = $jc(_$hash, types.hashCode);
+ _$hash = $jc(_$hash, arguments.hashCode);
+ _$hash = $jc(_$hash, namedArguments.hashCode);
+ _$hash = $jf(_$hash);
+ return _$hash;
+ }
+
+ @override
+ String toString() {
+ return (newBuiltValueToStringHelper(r'EnumValue')
+ ..add('name', name)
+ ..add('annotations', annotations)
+ ..add('docs', docs)
+ ..add('constructorName', constructorName)
+ ..add('types', types)
+ ..add('arguments', arguments)
+ ..add('namedArguments', namedArguments))
+ .toString();
+ }
+}
+
+class _$EnumValueBuilder extends EnumValueBuilder {
+ _$EnumValue? _$v;
+
+ @override
+ String? get name {
+ _$this;
+ return super.name;
+ }
+
+ @override
+ set name(String? name) {
+ _$this;
+ super.name = name;
+ }
+
+ @override
+ ListBuilder<Expression> get annotations {
+ _$this;
+ return super.annotations;
+ }
+
+ @override
+ set annotations(ListBuilder<Expression> annotations) {
+ _$this;
+ super.annotations = annotations;
+ }
+
+ @override
+ ListBuilder<String> get docs {
+ _$this;
+ return super.docs;
+ }
+
+ @override
+ set docs(ListBuilder<String> docs) {
+ _$this;
+ super.docs = docs;
+ }
+
+ @override
+ String? get constructorName {
+ _$this;
+ return super.constructorName;
+ }
+
+ @override
+ set constructorName(String? constructorName) {
+ _$this;
+ super.constructorName = constructorName;
+ }
+
+ @override
+ ListBuilder<Reference> get types {
+ _$this;
+ return super.types;
+ }
+
+ @override
+ set types(ListBuilder<Reference> types) {
+ _$this;
+ super.types = types;
+ }
+
+ @override
+ ListBuilder<Expression> get arguments {
+ _$this;
+ return super.arguments;
+ }
+
+ @override
+ set arguments(ListBuilder<Expression> arguments) {
+ _$this;
+ super.arguments = arguments;
+ }
+
+ @override
+ MapBuilder<String, Expression> get namedArguments {
+ _$this;
+ return super.namedArguments;
+ }
+
+ @override
+ set namedArguments(MapBuilder<String, Expression> namedArguments) {
+ _$this;
+ super.namedArguments = namedArguments;
+ }
+
+ _$EnumValueBuilder() : super._();
+
+ EnumValueBuilder get _$this {
+ final $v = _$v;
+ if ($v != null) {
+ super.name = $v.name;
+ super.annotations = $v.annotations.toBuilder();
+ super.docs = $v.docs.toBuilder();
+ super.constructorName = $v.constructorName;
+ super.types = $v.types.toBuilder();
+ super.arguments = $v.arguments.toBuilder();
+ super.namedArguments = $v.namedArguments.toBuilder();
+ _$v = null;
+ }
+ return this;
+ }
+
+ @override
+ void replace(EnumValue other) {
+ ArgumentError.checkNotNull(other, 'other');
+ _$v = other as _$EnumValue;
+ }
+
+ @override
+ void update(void Function(EnumValueBuilder)? updates) {
+ if (updates != null) updates(this);
+ }
+
+ @override
+ EnumValue build() => _build();
+
+ _$EnumValue _build() {
+ _$EnumValue _$result;
+ try {
+ _$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());
+ } catch (_) {
+ late String _$failedField;
+ try {
+ _$failedField = 'annotations';
+ annotations.build();
+ _$failedField = 'docs';
+ docs.build();
+
+ _$failedField = 'types';
+ types.build();
+ _$failedField = 'arguments';
+ arguments.build();
+ _$failedField = 'namedArguments';
+ namedArguments.build();
+ } catch (e) {
+ throw new BuiltValueNestedFieldError(
+ r'EnumValue', _$failedField, e.toString());
+ }
+ rethrow;
+ }
+ replace(_$result);
+ return _$result;
+ }
+}
+
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/pkgs/code_builder/lib/src/specs/expression.dart b/pkgs/code_builder/lib/src/specs/expression.dart
new file mode 100644
index 0000000..b9193e6
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/expression.dart
@@ -0,0 +1,793 @@
+// Copyright (c) 2017, 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:meta/meta.dart';
+
+import '../base.dart';
+import '../emitter.dart';
+import '../visitors.dart';
+import 'code.dart';
+import 'method.dart';
+import 'reference.dart';
+import 'type_function.dart';
+
+part 'expression/binary.dart';
+part 'expression/closure.dart';
+part 'expression/code.dart';
+part 'expression/invoke.dart';
+part 'expression/literal.dart';
+part 'expression/parenthesized.dart';
+
+/// Represents a [code] block that wraps an [Expression].
+
+/// Represents a Dart expression.
+///
+/// See various concrete implementations for details.
+abstract class Expression implements Spec {
+ const Expression();
+
+ /// An empty expression.
+ static const _empty = CodeExpression(Code(''));
+
+ /// Whether this expression implies a const context for sub expressions.
+ ///
+ /// Collection literals that are const imply const for all values.
+ /// Assignment to a const variable implies a const value.
+ /// Invoking a const constructor implies const for all arguments.
+ ///
+ /// The implied const context is used to omit redundant `const` keywords.
+ /// A value of `false` does not imply that the expression cannot be used in a
+ /// const context.
+ bool get isConst => false;
+
+ @override
+ R accept<R>(covariant ExpressionVisitor<R> visitor, [R? context]);
+
+ /// The expression as a valid [Code] block.
+ ///
+ /// Also see [statement].
+ Code get code => ToCodeExpression(this);
+
+ /// The expression as a valid [Code] block with a trailing `;`.
+ Code get statement => ToCodeExpression(this, true);
+
+ /// Returns the result of `this` `&&` [other].
+ Expression and(Expression other) =>
+ BinaryExpression._(expression, other, '&&');
+
+ /// Returns the result of `this` `||` [other].
+ Expression or(Expression other) =>
+ BinaryExpression._(expression, other, '||');
+
+ /// Returns the result of `!this`.
+ Expression negate() =>
+ BinaryExpression._(_empty, expression, '!', addSpace: false);
+
+ /// Returns the result of `this` `as` [other].
+ Expression asA(Expression other) =>
+ 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(']'),
+ ])),
+ '',
+ );
+
+ /// Returns the result of `this` `is` [other].
+ Expression isA(Expression other) => BinaryExpression._(
+ expression,
+ other,
+ 'is',
+ );
+
+ /// Returns the result of `this` `is!` [other].
+ Expression isNotA(Expression other) => BinaryExpression._(
+ expression,
+ other,
+ 'is!',
+ );
+
+ /// Returns the result of `this` `==` [other].
+ Expression equalTo(Expression other) => BinaryExpression._(
+ expression,
+ other,
+ '==',
+ );
+
+ /// Returns the result of `this` `!=` [other].
+ Expression notEqualTo(Expression other) => BinaryExpression._(
+ expression,
+ other,
+ '!=',
+ );
+
+ /// Returns the result of `this` `>` [other].
+ Expression greaterThan(Expression other) => BinaryExpression._(
+ expression,
+ other,
+ '>',
+ );
+
+ /// Returns the result of `this` `<` [other].
+ Expression lessThan(Expression other) => BinaryExpression._(
+ expression,
+ other,
+ '<',
+ );
+
+ /// Returns the result of `this` `>=` [other].
+ Expression greaterOrEqualTo(Expression other) => BinaryExpression._(
+ expression,
+ other,
+ '>=',
+ );
+
+ /// Returns the result of `this` `<=` [other].
+ Expression lessOrEqualTo(Expression other) => BinaryExpression._(
+ expression,
+ other,
+ '<=',
+ );
+
+ /// Returns the result of `this` `+` [other].
+ Expression operatorAdd(Expression other) => BinaryExpression._(
+ expression,
+ other,
+ '+',
+ );
+
+ /// Returns the result of `this` `-` [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,
+ '/',
+ );
+
+ /// Returns the result of `this` `*` [other].
+ Expression operatorMultiply(Expression other) => BinaryExpression._(
+ expression,
+ other,
+ '*',
+ );
+
+ /// Returns the result of `this` `%` [other].
+ Expression operatorEuclideanModulo(Expression other) => BinaryExpression._(
+ expression,
+ other,
+ '%',
+ );
+
+ /// Returns the result of `this` `~/` [other].
+ Expression operatorIntDivide(Expression other) =>
+ BinaryExpression._(expression, other, '~/');
+
+ Expression conditional(Expression whenTrue, Expression whenFalse) =>
+ BinaryExpression._(
+ expression,
+ BinaryExpression._(whenTrue, whenFalse, ':'),
+ '?',
+ );
+
+ /// This expression preceded by `await`.
+ Expression get awaited => BinaryExpression._(
+ _empty,
+ this,
+ 'await',
+ );
+
+ /// Returns the result of `++this`.
+ Expression operatorUnaryPrefixIncrement() =>
+ BinaryExpression._(_empty, expression, '++', addSpace: false);
+
+ /// Return the result of `this++`.
+ Expression operatorUnaryPostfixIncrement() =>
+ BinaryExpression._(expression, _empty, '++', addSpace: false);
+
+ /// Returns the result of `-this`.
+ Expression operatorUnaryMinus() =>
+ BinaryExpression._(_empty, expression, '-', addSpace: false);
+
+ /// Returns the result of `--this`.
+ Expression operatorUnaryPrefixDecrement() =>
+ BinaryExpression._(_empty, expression, '--', addSpace: false);
+
+ /// Return the result of `this--`.
+ Expression operatorUnaryPostfixDecrement() =>
+ BinaryExpression._(expression, _empty, '--', addSpace: false);
+
+ /// Returns the result of `this` `&` [other].
+ Expression operatorBitwiseAnd(Expression other) =>
+ BinaryExpression._(expression, other, '&');
+
+ /// Returns the result of `this` `|` [other].
+ Expression operatorBitwiseOr(Expression other) =>
+ BinaryExpression._(expression, other, '|');
+
+ /// Returns the result of `this` `^` [other].
+ Expression operatorBitwiseXor(Expression other) =>
+ BinaryExpression._(expression, other, '^');
+
+ /// Returns the result of `~this`.
+ Expression operatorUnaryBitwiseComplement() =>
+ BinaryExpression._(_empty, expression, '~', addSpace: false);
+
+ /// Returns the result of `this` `<<` [other].
+ Expression operatorShiftLeft(Expression other) =>
+ BinaryExpression._(expression, other, '<<');
+
+ /// Returns the result of `this` `>>` [other].
+ Expression operatorShiftRight(Expression other) =>
+ BinaryExpression._(expression, other, '>>');
+
+ /// Returns the result of `this` `>>>` [other].
+ Expression operatorShiftRightUnsigned(Expression other) =>
+ BinaryExpression._(expression, other, '>>>');
+
+ /// Return `{this} = {other}`.
+ Expression assign(Expression other) =>
+ BinaryExpression._(this, other, '=', isConst: isConst);
+
+ /// Return `this` += [other].
+ Expression addAssign(Expression other) =>
+ BinaryExpression._(this, other, '+=');
+
+ /// Return `this` -= [other].
+ Expression subtractAssign(Expression other) =>
+ BinaryExpression._(this, other, '-=');
+
+ /// Return `this` *= [other].
+ Expression multiplyAssign(Expression other) =>
+ BinaryExpression._(this, other, '*=');
+
+ /// Return `this` /= [other].
+ Expression divideAssign(Expression other) =>
+ BinaryExpression._(this, other, '/=');
+
+ /// Return `this` ~/= [other].
+ Expression intDivideAssign(Expression other) =>
+ BinaryExpression._(this, other, '~/=');
+
+ /// Return `this` %= [other].
+ Expression euclideanModuloAssign(Expression other) =>
+ BinaryExpression._(this, other, '%=');
+
+ /// Return `this` <<= [other].
+ Expression shiftLeftAssign(Expression other) =>
+ BinaryExpression._(this, other, '<<=');
+
+ /// Return `this` >>= [other].
+ Expression shiftRightAssign(Expression other) =>
+ BinaryExpression._(this, other, '>>=');
+
+ /// Return `this` >>>= [other].
+ Expression shiftRightUnsignedAssign(Expression other) =>
+ BinaryExpression._(this, other, '>>>=');
+
+ /// Return `this` &= [other].
+ Expression bitwiseAndAssign(Expression other) =>
+ BinaryExpression._(this, other, '&=');
+
+ /// Return `this` ^= [other].
+ Expression bitwiseXorAssign(Expression other) =>
+ BinaryExpression._(this, other, '^=');
+
+ /// Return `this` |= [other].
+ Expression bitwiseOrAssign(Expression other) =>
+ BinaryExpression._(this, other, '|=');
+
+ /// Return `{this} ?? {other}`.
+ Expression ifNullThen(Expression other) => BinaryExpression._(
+ this,
+ other,
+ '??',
+ );
+
+ /// Return `{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,
+ '=',
+ );
+
+ /// 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 =',
+ );
+
+ /// 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,
+ );
+
+ /// Call this expression as a method.
+ Expression call(
+ Iterable<Expression> positionalArguments, [
+ Map<String, Expression> namedArguments = const {},
+ List<Reference> typeArguments = const [],
+ ]) =>
+ InvokeExpression._(
+ this,
+ positionalArguments.toList(),
+ namedArguments,
+ typeArguments,
+ );
+
+ /// Returns an expression accessing `.<name>` on this expression.
+ Expression property(String name) => BinaryExpression._(
+ this,
+ LiteralExpression._(name),
+ '.',
+ addSpace: false,
+ );
+
+ /// Returns an expression accessing `..<name>` on this expression.
+ Expression cascade(String name) => BinaryExpression._(
+ this,
+ LiteralExpression._(name),
+ '..',
+ addSpace: false,
+ );
+
+ /// Returns an expression accessing `?.<name>` on this expression.
+ Expression nullSafeProperty(String name) => BinaryExpression._(
+ 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 expression preceded by `return`.
+ Expression get returned => BinaryExpression._(
+ const LiteralExpression._('return'),
+ this,
+ '',
+ );
+
+ /// This expression preceded by the spread operator `...`.
+ Expression get spread => BinaryExpression._(
+ const LiteralExpression._('...'),
+ this,
+ '',
+ addSpace: false,
+ );
+
+ /// This expression preceded by the null safe spread operator `?...`.
+ Expression get nullSafeSpread => BinaryExpression._(
+ const LiteralExpression._('...?'),
+ this,
+ '',
+ addSpace: false,
+ );
+
+ /// This expression preceded by `throw`.
+ Expression get thrown => BinaryExpression._(
+ const LiteralExpression._('throw'),
+ this,
+ '',
+ );
+
+ /// May be overridden to support other types implementing [Expression].
+ @visibleForOverriding
+ Expression get expression => this;
+
+ /// Returns this expression wrapped in parenthesis.
+ ParenthesizedExpression get parenthesized => ParenthesizedExpression._(this);
+}
+
+/// Declare a const variable named [variableName].
+///
+/// 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);
+
+/// 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), ''));
+
+/// 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 _typedVar(String variableName, Reference type) =>
+ BinaryExpression._(type.expression, LiteralExpression._(variableName), '');
+
+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;
+
+class ToCodeExpression implements Code {
+ final Expression code;
+
+ /// Whether this code should be considered a _statement_.
+ final bool isStatement;
+
+ @visibleForTesting
+ const ToCodeExpression(this.code, [this.isStatement = false]);
+
+ @override
+ R accept<R>(CodeVisitor<R> visitor, [R? context]) =>
+ (visitor as ExpressionVisitor<R>).visitToCodeExpression(this, context);
+
+ @override
+ String toString() => code.toString();
+}
+
+/// Knowledge of different types of expressions in Dart.
+///
+/// **INTERNAL ONLY**.
+abstract class ExpressionVisitor<T> implements SpecVisitor<T> {
+ T visitToCodeExpression(ToCodeExpression code, [T? context]);
+ T visitBinaryExpression(BinaryExpression expression, [T? context]);
+ T visitClosureExpression(ClosureExpression expression, [T? context]);
+ T visitCodeExpression(CodeExpression expression, [T? context]);
+ T visitInvokeExpression(InvokeExpression expression, [T? context]);
+ T visitLiteralExpression(LiteralExpression expression, [T? context]);
+ 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]);
+}
+
+/// Knowledge of how to write valid Dart code from [ExpressionVisitor].
+///
+/// **INTERNAL ONLY**.
+abstract mixin class ExpressionEmitter
+ implements ExpressionVisitor<StringSink> {
+ @override
+ StringSink visitToCodeExpression(ToCodeExpression expression,
+ [StringSink? output]) {
+ output ??= StringBuffer();
+ expression.code.accept(this, output);
+ if (expression.isStatement) {
+ output.write(';');
+ }
+ return output;
+ }
+
+ @override
+ StringSink visitBinaryExpression(BinaryExpression expression,
+ [StringSink? output]) {
+ output ??= StringBuffer();
+ expression.left.accept(this, output);
+ if (expression.addSpace) {
+ output.write(' ');
+ }
+ output.write(expression.operator);
+ if (expression.addSpace) {
+ output.write(' ');
+ }
+ startConstCode(expression.isConst, () {
+ expression.right.accept(this, output);
+ });
+ return output;
+ }
+
+ @override
+ StringSink visitClosureExpression(ClosureExpression expression,
+ [StringSink? output]) {
+ output ??= StringBuffer();
+ return expression.method.accept(this, output);
+ }
+
+ @override
+ StringSink visitCodeExpression(CodeExpression expression,
+ [StringSink? output]) {
+ output ??= StringBuffer();
+ final visitor = this as CodeVisitor<StringSink>;
+ return expression.code.accept(visitor, output);
+ }
+
+ @override
+ StringSink visitInvokeExpression(InvokeExpression expression,
+ [StringSink? output]) {
+ final out = output ??= StringBuffer();
+ return _writeConstExpression(out, expression.isConst, () {
+ expression.target.accept(this, out);
+ if (expression.name != null) {
+ out
+ ..write('.')
+ ..write(expression.name);
+ }
+ if (expression.typeArguments.isNotEmpty) {
+ out.write('<');
+ visitAll<Reference>(expression.typeArguments, out, (type) {
+ type.accept(this, out);
+ });
+ out.write('>');
+ }
+ out.write('(');
+ visitAll<Spec>(expression.positionalArguments, out, (spec) {
+ spec.accept(this, out);
+ });
+ if (expression.positionalArguments.isNotEmpty &&
+ expression.namedArguments.isNotEmpty) {
+ out.write(', ');
+ }
+ visitAll<String>(expression.namedArguments.keys, out, (name) {
+ out
+ ..write(name)
+ ..write(': ');
+ expression.namedArguments[name]!.accept(this, out);
+ });
+ final argumentCount = expression.positionalArguments.length +
+ expression.namedArguments.length;
+ if (argumentCount > 1) {
+ out.write(', ');
+ }
+ return out..write(')');
+ });
+ }
+
+ @override
+ StringSink visitLiteralExpression(LiteralExpression expression,
+ [StringSink? output]) {
+ output ??= StringBuffer();
+ return output..write(expression.literal);
+ }
+
+ void _acceptLiteral(Object? literalOrSpec, StringSink output) {
+ if (literalOrSpec is Spec) {
+ literalOrSpec.accept(this, output);
+ return;
+ }
+ literal(literalOrSpec).accept(this, output);
+ }
+
+ bool _withInConstExpression = false;
+
+ @override
+ StringSink visitLiteralListExpression(
+ LiteralListExpression expression, [
+ StringSink? output,
+ ]) {
+ final out = output ??= StringBuffer();
+
+ return _writeConstExpression(output, expression.isConst, () {
+ if (expression.type != null) {
+ out.write('<');
+ expression.type!.accept(this, output);
+ out.write('>');
+ }
+ out.write('[');
+ visitAll<Object?>(expression.values, out, (value) {
+ _acceptLiteral(value, out);
+ });
+ if (expression.values.length > 1) {
+ out.write(', ');
+ }
+ return out..write(']');
+ });
+ }
+
+ @override
+ StringSink visitLiteralSetExpression(
+ LiteralSetExpression expression, [
+ StringSink? output,
+ ]) {
+ final out = output ??= StringBuffer();
+
+ return _writeConstExpression(output, expression.isConst, () {
+ if (expression.type != null) {
+ out.write('<');
+ expression.type!.accept(this, output);
+ out.write('>');
+ }
+ out.write('{');
+ visitAll<Object?>(expression.values, out, (value) {
+ _acceptLiteral(value, out);
+ });
+ if (expression.values.length > 1) {
+ out.write(', ');
+ }
+ return out..write('}');
+ });
+ }
+
+ @override
+ StringSink visitLiteralMapExpression(
+ LiteralMapExpression expression, [
+ StringSink? output,
+ ]) {
+ final out = output ??= StringBuffer();
+ return _writeConstExpression(out, expression.isConst, () {
+ if (expression.keyType != null) {
+ out.write('<');
+ expression.keyType!.accept(this, out);
+ out.write(', ');
+ if (expression.valueType == null) {
+ const Reference('dynamic', 'dart:core').accept(this, out);
+ } else {
+ expression.valueType!.accept(this, out);
+ }
+ out.write('>');
+ }
+ out.write('{');
+ visitAll<Object?>(expression.values.keys, out, (key) {
+ final value = expression.values[key];
+ _acceptLiteral(key, out);
+ if (key is! LiteralSpreadExpression) {
+ out.write(': ');
+ }
+ _acceptLiteral(value, out);
+ });
+ if (expression.values.length > 1) {
+ out.write(', ');
+ }
+ return out..write('}');
+ });
+ }
+
+ @override
+ StringSink visitLiteralRecordExpression(
+ LiteralRecordExpression expression, [
+ StringSink? output,
+ ]) {
+ final out = output ??= StringBuffer();
+ return _writeConstExpression(out, expression.isConst, () {
+ out.write('(');
+ visitAll<Object?>(expression.positionalFieldValues, out, (value) {
+ _acceptLiteral(value, out);
+ });
+ if (expression.namedFieldValues.isNotEmpty) {
+ if (expression.positionalFieldValues.isNotEmpty) {
+ out.write(', ');
+ }
+ } else if (expression.positionalFieldValues.length == 1) {
+ out.write(',');
+ }
+ visitAll<MapEntry<String, Object?>>(
+ expression.namedFieldValues.entries, out, (entry) {
+ out.write('${entry.key}: ');
+ _acceptLiteral(entry.value, out);
+ });
+ return out..write(')');
+ });
+ }
+
+ @override
+ StringSink visitParenthesizedExpression(
+ ParenthesizedExpression expression, [
+ StringSink? output,
+ ]) {
+ output ??= StringBuffer();
+ output.write('(');
+ expression.inner.accept(this, output);
+ output.write(')');
+ return output;
+ }
+
+ /// Executes [visit] within a context which may alter the output if [isConst]
+ /// is `true`.
+ ///
+ /// This allows constant expressions to omit the `const` keyword if they
+ /// are already within a constant expression.
+ void startConstCode(
+ bool isConst,
+ Null Function() visit,
+ ) {
+ final previousConstContext = _withInConstExpression;
+ if (isConst) {
+ _withInConstExpression = true;
+ }
+
+ visit();
+ _withInConstExpression = previousConstContext;
+ }
+
+ /// Similar to [startConstCode], but handles writing `"const "` if [isConst]
+ /// is `true` and the invocation is not nested under other invocations where
+ /// [isConst] is true.
+ StringSink _writeConstExpression(
+ StringSink sink,
+ bool isConst,
+ StringSink Function() visitExpression,
+ ) {
+ final previousConstContext = _withInConstExpression;
+ if (isConst) {
+ if (!_withInConstExpression) {
+ sink.write('const ');
+ }
+ _withInConstExpression = true;
+ }
+
+ final returnedSink = visitExpression();
+ assert(identical(returnedSink, sink));
+ _withInConstExpression = previousConstContext;
+ return sink;
+ }
+}
diff --git a/pkgs/code_builder/lib/src/specs/expression/binary.dart b/pkgs/code_builder/lib/src/specs/expression/binary.dart
new file mode 100644
index 0000000..d02a2a6
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/expression/binary.dart
@@ -0,0 +1,27 @@
+// Copyright (c) 2017, 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.
+
+part of '../expression.dart';
+
+/// Represents two expressions ([left] and [right]) and an [operator].
+class BinaryExpression extends Expression {
+ final Expression left;
+ final Expression right;
+ final String operator;
+ final bool addSpace;
+ @override
+ final bool isConst;
+
+ const BinaryExpression._(
+ this.left,
+ this.right,
+ this.operator, {
+ this.addSpace = true,
+ this.isConst = false,
+ });
+
+ @override
+ R accept<R>(ExpressionVisitor<R> visitor, [R? context]) =>
+ visitor.visitBinaryExpression(this, context);
+}
diff --git a/pkgs/code_builder/lib/src/specs/expression/closure.dart b/pkgs/code_builder/lib/src/specs/expression/closure.dart
new file mode 100644
index 0000000..706600c
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/expression/closure.dart
@@ -0,0 +1,32 @@
+// Copyright (c) 2017, 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.
+
+part of '../expression.dart';
+
+/// Returns [method] as closure, removing its return type and type parameters.
+Expression toClosure(Method method) {
+ final withoutTypes = method.rebuild((b) {
+ b.returns = null;
+ b.types.clear();
+ });
+ return ClosureExpression._(withoutTypes);
+}
+
+/// Returns [method] as a (possibly) generic closure, removing its return type.
+Expression toGenericClosure(Method method) {
+ final withoutReturnType = method.rebuild((b) {
+ b.returns = null;
+ });
+ return ClosureExpression._(withoutReturnType);
+}
+
+class ClosureExpression extends Expression {
+ final Method method;
+
+ const ClosureExpression._(this.method);
+
+ @override
+ R accept<R>(ExpressionVisitor<R> visitor, [R? context]) =>
+ visitor.visitClosureExpression(this, context);
+}
diff --git a/pkgs/code_builder/lib/src/specs/expression/code.dart b/pkgs/code_builder/lib/src/specs/expression/code.dart
new file mode 100644
index 0000000..1529edb
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/expression/code.dart
@@ -0,0 +1,18 @@
+// Copyright (c) 2017, 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.
+
+part of '../expression.dart';
+
+/// Represents a [Code] block as an [Expression].
+class CodeExpression extends Expression {
+ @override
+ final Code code;
+
+ /// **INTERNAL ONLY**: Used to wrap [Code] as an [Expression].
+ const CodeExpression(this.code);
+
+ @override
+ R accept<R>(ExpressionVisitor<R> visitor, [R? context]) =>
+ visitor.visitCodeExpression(this, context);
+}
diff --git a/pkgs/code_builder/lib/src/specs/expression/invoke.dart b/pkgs/code_builder/lib/src/specs/expression/invoke.dart
new file mode 100644
index 0000000..f54dc0e
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/expression/invoke.dart
@@ -0,0 +1,65 @@
+// Copyright (c) 2017, 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
+
+part of '../expression.dart';
+
+/// Represents invoking [target] as a method with arguments.
+class InvokeExpression extends Expression {
+ /// Target of the method invocation.
+ final Expression target;
+
+ /// Optional; type of invocation.
+ @Deprecated('Use isConst instead')
+ final InvokeExpressionType? type;
+
+ @override
+ final bool isConst;
+
+ final List<Expression> positionalArguments;
+ final Map<String, Expression> namedArguments;
+ final List<Reference> typeArguments;
+ final String? name;
+
+ const InvokeExpression._(
+ this.target,
+ this.positionalArguments,
+ this.namedArguments,
+ this.typeArguments,
+ ) : name = null,
+ type = null,
+ isConst = false;
+
+ const InvokeExpression.newOf(
+ this.target,
+ this.positionalArguments, [
+ this.namedArguments = const {},
+ this.typeArguments = const [],
+ this.name,
+ ]) : type = InvokeExpressionType.newInstance,
+ isConst = false;
+
+ const InvokeExpression.constOf(
+ this.target,
+ this.positionalArguments, [
+ this.namedArguments = const {},
+ this.typeArguments = const [],
+ this.name,
+ ]) : type = InvokeExpressionType.constInstance,
+ isConst = true;
+
+ @override
+ R accept<R>(ExpressionVisitor<R> visitor, [R? context]) =>
+ visitor.visitInvokeExpression(this, context);
+
+ @override
+ String toString() =>
+ '${type ?? ''} $target($positionalArguments, $namedArguments)';
+}
+
+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
new file mode 100644
index 0000000..ced6cf9
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/expression/literal.dart
@@ -0,0 +1,218 @@
+// Copyright (c) 2017, 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.
+
+part of '../expression.dart';
+
+/// Converts a runtime Dart [literal] value into an [Expression].
+///
+/// Supported Dart types are translated into literal expressions.
+/// If the [literal] is already an [Expression] it is returned without change to
+/// allow operating on a collection of mixed simple literals and more complex
+/// expressions.
+/// Unsupported inputs invoke the [onError] callback.
+Expression literal(Object? literal, {Expression Function(Object)? onError}) {
+ if (literal is Expression) return literal;
+ if (literal is bool) return literalBool(literal);
+ if (literal is num) return literalNum(literal);
+ if (literal is String) return literalString(literal);
+ if (literal is List) return literalList(literal);
+ if (literal is Set) return literalSet(literal);
+ if (literal is Map) return literalMap(literal);
+ if (literal == null) return literalNull;
+ if (onError != null) return onError(literal);
+ throw UnsupportedError('Not a supported literal type: $literal.');
+}
+
+/// Represents the literal value `true`.
+const Expression literalTrue = LiteralExpression._('true');
+
+/// Represents the literal value `false`.
+const Expression literalFalse = LiteralExpression._('false');
+
+/// Create a literal expression from a boolean [value].
+Expression literalBool(bool value) => value ? literalTrue : literalFalse;
+
+/// Represents the literal value `null`.
+const Expression literalNull = LiteralExpression._('null');
+
+/// Create a literal expression from a number [value].
+Expression literalNum(num value) => LiteralExpression._('$value');
+
+/// Create a literal expression from a string [value].
+///
+/// **NOTE**: The string is always formatted `'<value>'`.
+///
+/// If [raw] is `true`, creates a raw String formatted `r'<value>'` and the
+/// value may not contain a single quote.
+/// Escapes single quotes and newlines in the value.
+Expression literalString(String value, {bool raw = false}) {
+ if (raw && value.contains('\'')) {
+ throw ArgumentError('Cannot include a single quote in a raw string');
+ }
+ final escaped = value.replaceAll('\'', '\\\'').replaceAll('\n', '\\n');
+ return LiteralExpression._("${raw ? 'r' : ''}'$escaped'");
+}
+
+/// Create a literal `...` operator for use when creating a Map literal.
+///
+/// *NOTE* This is used as a sentinel when constructing a `literalMap` or a
+/// or `literalConstMap` to signify that the value should be spread. Do NOT
+/// reuse the value when creating a Map with multiple spreads.
+Expression literalSpread() => LiteralSpreadExpression._(false);
+
+/// Create a literal `...?` operator for use when creating a Map literal.
+///
+/// *NOTE* This is used as a sentinel when constructing a `literalMap` or a
+/// or `literalConstMap` to signify that the value should be spread. Do NOT
+/// reuse the value when creating a Map with multiple spreads.
+Expression literalNullSafeSpread() => LiteralSpreadExpression._(true);
+
+/// Creates a literal list expression from [values].
+LiteralListExpression literalList(Iterable<Object?> values,
+ [Reference? type]) =>
+ LiteralListExpression._(false, values.toList(), type);
+
+/// Creates a literal `const` list expression from [values].
+LiteralListExpression literalConstList(List<Object?> values,
+ [Reference? type]) =>
+ LiteralListExpression._(true, values, type);
+
+/// Creates a literal set expression from [values].
+LiteralSetExpression literalSet(Iterable<Object?> values, [Reference? type]) =>
+ LiteralSetExpression._(false, values.toSet(), type);
+
+/// Creates a literal `const` set expression from [values].
+LiteralSetExpression literalConstSet(Set<Object?> values, [Reference? type]) =>
+ LiteralSetExpression._(true, values, type);
+
+/// Create a literal map expression from [values].
+LiteralMapExpression literalMap(
+ Map<Object?, Object?> values, [
+ Reference? keyType,
+ Reference? valueType,
+]) =>
+ LiteralMapExpression._(false, values, keyType, valueType);
+
+/// Create a literal `const` map expression from [values].
+LiteralMapExpression literalConstMap(
+ Map<Object?, Object?> values, [
+ Reference? keyType,
+ Reference? valueType,
+]) =>
+ LiteralMapExpression._(true, values, keyType, valueType);
+
+/// Create a literal record expression from [positionalFieldValues] and
+/// [namedFieldValues].
+LiteralRecordExpression literalRecord(List<Object?> positionalFieldValues,
+ Map<String, Object?> namedFieldValues) =>
+ LiteralRecordExpression._(false, positionalFieldValues, namedFieldValues);
+
+/// Create a literal `const` record expression from [positionalFieldValues] and
+/// [namedFieldValues].
+LiteralRecordExpression literalConstRecord(List<Object?> positionalFieldValues,
+ Map<String, Object?> namedFieldValues) =>
+ LiteralRecordExpression._(true, positionalFieldValues, namedFieldValues);
+
+/// Represents a literal value in Dart source code.
+///
+/// For example, `LiteralExpression('null')` should emit `null`.
+///
+/// Some common literals and helpers are available as methods/fields:
+/// * [literal]
+/// * [literalBool] and [literalTrue], [literalFalse]
+/// * [literalNull]
+/// * [literalList] and [literalConstList]
+/// * [literalSet] and [literalConstSet]
+class LiteralExpression extends Expression {
+ final String literal;
+
+ const LiteralExpression._(this.literal);
+
+ @override
+ R accept<R>(ExpressionVisitor<R> visitor, [R? context]) =>
+ visitor.visitLiteralExpression(this, context);
+
+ @override
+ String toString() => literal;
+}
+
+class LiteralSpreadExpression extends LiteralExpression {
+ LiteralSpreadExpression._(bool nullAware)
+ : super._('...${nullAware ? '?' : ''}');
+}
+
+class LiteralListExpression extends Expression {
+ @override
+ final bool isConst;
+ final List<Object?> values;
+ final Reference? type;
+
+ const LiteralListExpression._(this.isConst, this.values, this.type);
+
+ @override
+ R accept<R>(ExpressionVisitor<R> visitor, [R? context]) =>
+ visitor.visitLiteralListExpression(this, context);
+
+ @override
+ String toString() => '[${values.map(literal).join(', ')}]';
+}
+
+class LiteralSetExpression extends Expression {
+ @override
+ final bool isConst;
+ final Set<Object?> values;
+ final Reference? type;
+
+ const LiteralSetExpression._(this.isConst, this.values, this.type);
+
+ @override
+ R accept<R>(ExpressionVisitor<R> visitor, [R? context]) =>
+ visitor.visitLiteralSetExpression(this, context);
+
+ @override
+ String toString() => '{${values.map(literal).join(', ')}}';
+}
+
+class LiteralMapExpression extends Expression {
+ @override
+ final bool isConst;
+ final Map<Object?, Object?> values;
+ final Reference? keyType;
+ final Reference? valueType;
+
+ const LiteralMapExpression._(
+ this.isConst,
+ this.values,
+ this.keyType,
+ this.valueType,
+ );
+
+ @override
+ R accept<R>(ExpressionVisitor<R> visitor, [R? context]) =>
+ visitor.visitLiteralMapExpression(this, context);
+
+ @override
+ String toString() => '{$values}';
+}
+
+class LiteralRecordExpression extends Expression {
+ @override
+ final bool isConst;
+ final List<Object?> positionalFieldValues;
+ final Map<String, Object?> namedFieldValues;
+
+ const LiteralRecordExpression._(
+ this.isConst, this.positionalFieldValues, this.namedFieldValues);
+
+ @override
+ R accept<R>(ExpressionVisitor<R> visitor, [R? context]) =>
+ visitor.visitLiteralRecordExpression(this, context);
+
+ @override
+ String toString() {
+ 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/expression/parenthesized.dart b/pkgs/code_builder/lib/src/specs/expression/parenthesized.dart
new file mode 100644
index 0000000..ea34ae8
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/expression/parenthesized.dart
@@ -0,0 +1,16 @@
+// Copyright (c) 2021, 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.
+
+part of '../expression.dart';
+
+/// An [Expression] wrapped with parenthesis.
+class ParenthesizedExpression extends Expression {
+ final Expression inner;
+
+ const ParenthesizedExpression._(this.inner);
+
+ @override
+ R accept<R>(ExpressionVisitor<R> visitor, [R? context]) =>
+ visitor.visitParenthesizedExpression(this, context);
+}
diff --git a/pkgs/code_builder/lib/src/specs/extension.dart b/pkgs/code_builder/lib/src/specs/extension.dart
new file mode 100644
index 0000000..395994f
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/extension.dart
@@ -0,0 +1,78 @@
+// Copyright (c) 2020, 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:built_collection/built_collection.dart';
+import 'package:built_value/built_value.dart';
+import 'package:meta/meta.dart';
+
+import '../base.dart';
+import '../mixins/annotations.dart';
+import '../mixins/dartdoc.dart';
+import '../mixins/generics.dart';
+import '../visitors.dart';
+import 'expression.dart';
+import 'field.dart';
+import 'method.dart';
+import 'reference.dart';
+
+part 'extension.g.dart';
+
+@immutable
+abstract class Extension extends Object
+ with HasAnnotations, HasDartDocs, HasGenerics
+ implements Built<Extension, ExtensionBuilder>, Spec {
+ factory Extension([void Function(ExtensionBuilder b) updates]) = _$Extension;
+
+ Extension._();
+
+ @override
+ BuiltList<Expression> get annotations;
+
+ @override
+ BuiltList<String> get docs;
+
+ Reference? get on;
+
+ @override
+ BuiltList<Reference> get types;
+
+ BuiltList<Method> get methods;
+
+ BuiltList<Field> get fields;
+
+ /// Name of the extension - optional.
+ String? get name;
+
+ @override
+ R accept<R>(
+ SpecVisitor<R> visitor, [
+ R? context,
+ ]) =>
+ visitor.visitExtension(this, context);
+}
+
+abstract class ExtensionBuilder extends Object
+ with HasAnnotationsBuilder, HasDartDocsBuilder, HasGenericsBuilder
+ implements Builder<Extension, ExtensionBuilder> {
+ factory ExtensionBuilder() = _$ExtensionBuilder;
+
+ ExtensionBuilder._();
+
+ @override
+ ListBuilder<Expression> annotations = ListBuilder<Expression>();
+
+ @override
+ ListBuilder<String> docs = ListBuilder<String>();
+
+ Reference? on;
+
+ @override
+ ListBuilder<Reference> types = ListBuilder<Reference>();
+
+ ListBuilder<Method> methods = ListBuilder<Method>();
+ ListBuilder<Field> fields = ListBuilder<Field>();
+
+ /// Name of the extension - optional.
+ String? name;
+}
diff --git a/pkgs/code_builder/lib/src/specs/extension.g.dart b/pkgs/code_builder/lib/src/specs/extension.g.dart
new file mode 100644
index 0000000..83de176
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/extension.g.dart
@@ -0,0 +1,248 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'extension.dart';
+
+// **************************************************************************
+// BuiltValueGenerator
+// **************************************************************************
+
+class _$Extension extends Extension {
+ @override
+ final BuiltList<Expression> annotations;
+ @override
+ final BuiltList<String> docs;
+ @override
+ final Reference? on;
+ @override
+ final BuiltList<Reference> types;
+ @override
+ final BuiltList<Method> methods;
+ @override
+ final BuiltList<Field> fields;
+ @override
+ final String? name;
+
+ 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._() {
+ BuiltValueNullFieldError.checkNotNull(
+ annotations, r'Extension', 'annotations');
+ BuiltValueNullFieldError.checkNotNull(docs, r'Extension', 'docs');
+ BuiltValueNullFieldError.checkNotNull(types, r'Extension', 'types');
+ BuiltValueNullFieldError.checkNotNull(methods, r'Extension', 'methods');
+ BuiltValueNullFieldError.checkNotNull(fields, r'Extension', 'fields');
+ }
+
+ @override
+ Extension rebuild(void Function(ExtensionBuilder) updates) =>
+ (toBuilder()..update(updates)).build();
+
+ @override
+ _$ExtensionBuilder toBuilder() => new _$ExtensionBuilder()..replace(this);
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(other, this)) return true;
+ return other is Extension &&
+ annotations == other.annotations &&
+ docs == other.docs &&
+ on == other.on &&
+ types == other.types &&
+ methods == other.methods &&
+ fields == other.fields &&
+ name == other.name;
+ }
+
+ @override
+ int get hashCode {
+ var _$hash = 0;
+ _$hash = $jc(_$hash, annotations.hashCode);
+ _$hash = $jc(_$hash, docs.hashCode);
+ _$hash = $jc(_$hash, on.hashCode);
+ _$hash = $jc(_$hash, types.hashCode);
+ _$hash = $jc(_$hash, methods.hashCode);
+ _$hash = $jc(_$hash, fields.hashCode);
+ _$hash = $jc(_$hash, name.hashCode);
+ _$hash = $jf(_$hash);
+ return _$hash;
+ }
+
+ @override
+ String toString() {
+ return (newBuiltValueToStringHelper(r'Extension')
+ ..add('annotations', annotations)
+ ..add('docs', docs)
+ ..add('on', on)
+ ..add('types', types)
+ ..add('methods', methods)
+ ..add('fields', fields)
+ ..add('name', name))
+ .toString();
+ }
+}
+
+class _$ExtensionBuilder extends ExtensionBuilder {
+ _$Extension? _$v;
+
+ @override
+ ListBuilder<Expression> get annotations {
+ _$this;
+ return super.annotations;
+ }
+
+ @override
+ set annotations(ListBuilder<Expression> annotations) {
+ _$this;
+ super.annotations = annotations;
+ }
+
+ @override
+ ListBuilder<String> get docs {
+ _$this;
+ return super.docs;
+ }
+
+ @override
+ set docs(ListBuilder<String> docs) {
+ _$this;
+ super.docs = docs;
+ }
+
+ @override
+ Reference? get on {
+ _$this;
+ return super.on;
+ }
+
+ @override
+ set on(Reference? on) {
+ _$this;
+ super.on = on;
+ }
+
+ @override
+ ListBuilder<Reference> get types {
+ _$this;
+ return super.types;
+ }
+
+ @override
+ set types(ListBuilder<Reference> types) {
+ _$this;
+ super.types = types;
+ }
+
+ @override
+ ListBuilder<Method> get methods {
+ _$this;
+ return super.methods;
+ }
+
+ @override
+ set methods(ListBuilder<Method> methods) {
+ _$this;
+ super.methods = methods;
+ }
+
+ @override
+ ListBuilder<Field> get fields {
+ _$this;
+ return super.fields;
+ }
+
+ @override
+ set fields(ListBuilder<Field> fields) {
+ _$this;
+ super.fields = fields;
+ }
+
+ @override
+ String? get name {
+ _$this;
+ return super.name;
+ }
+
+ @override
+ set name(String? name) {
+ _$this;
+ super.name = name;
+ }
+
+ _$ExtensionBuilder() : super._();
+
+ ExtensionBuilder get _$this {
+ final $v = _$v;
+ if ($v != null) {
+ super.annotations = $v.annotations.toBuilder();
+ super.docs = $v.docs.toBuilder();
+ super.on = $v.on;
+ super.types = $v.types.toBuilder();
+ super.methods = $v.methods.toBuilder();
+ super.fields = $v.fields.toBuilder();
+ super.name = $v.name;
+ _$v = null;
+ }
+ return this;
+ }
+
+ @override
+ void replace(Extension other) {
+ ArgumentError.checkNotNull(other, 'other');
+ _$v = other as _$Extension;
+ }
+
+ @override
+ void update(void Function(ExtensionBuilder)? updates) {
+ if (updates != null) updates(this);
+ }
+
+ @override
+ Extension build() => _build();
+
+ _$Extension _build() {
+ _$Extension _$result;
+ try {
+ _$result = _$v ??
+ new _$Extension._(
+ annotations: annotations.build(),
+ docs: docs.build(),
+ on: on,
+ types: types.build(),
+ methods: methods.build(),
+ fields: fields.build(),
+ name: name);
+ } catch (_) {
+ late String _$failedField;
+ try {
+ _$failedField = 'annotations';
+ annotations.build();
+ _$failedField = 'docs';
+ docs.build();
+
+ _$failedField = 'types';
+ types.build();
+ _$failedField = 'methods';
+ methods.build();
+ _$failedField = 'fields';
+ fields.build();
+ } catch (e) {
+ throw new BuiltValueNestedFieldError(
+ r'Extension', _$failedField, e.toString());
+ }
+ rethrow;
+ }
+ replace(_$result);
+ return _$result;
+ }
+}
+
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/pkgs/code_builder/lib/src/specs/extension_type.dart b/pkgs/code_builder/lib/src/specs/extension_type.dart
new file mode 100644
index 0000000..ec3bd33
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/extension_type.dart
@@ -0,0 +1,152 @@
+// Copyright (c) 2023, 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:built_collection/built_collection.dart';
+import 'package:built_value/built_value.dart';
+import 'package:meta/meta.dart';
+
+import '../base.dart';
+import '../mixins/annotations.dart';
+import '../mixins/dartdoc.dart';
+import '../mixins/generics.dart';
+import '../visitors.dart';
+import 'constructor.dart';
+import 'expression.dart';
+import 'field.dart';
+import 'method.dart';
+import 'reference.dart';
+
+part 'extension_type.g.dart';
+
+@immutable
+abstract class ExtensionType extends Object
+ with HasAnnotations, HasDartDocs, HasGenerics
+ implements Built<ExtensionType, ExtensionTypeBuilder>, Spec {
+ factory ExtensionType([void Function(ExtensionTypeBuilder)? updates]) =
+ _$ExtensionType;
+
+ ExtensionType._();
+
+ @override
+ BuiltList<Expression> get annotations;
+
+ @override
+ BuiltList<String> get docs;
+
+ /// Whether this extension type is declared as `const`.
+ bool get constant;
+
+ String get name;
+
+ @override
+ BuiltList<Reference> get types;
+
+ /// Name of the extension type's primary constructor. An empty string
+ /// will make it unnamed.
+ String get primaryConstructorName;
+
+ RepresentationDeclaration get representationDeclaration;
+
+ BuiltList<Reference> get implements;
+
+ BuiltList<Constructor> get constructors;
+
+ BuiltList<Field> get fields;
+
+ BuiltList<Method> get methods;
+
+ @override
+ R accept<R>(
+ SpecVisitor<R> visitor, [
+ R? context,
+ ]) =>
+ visitor.visitExtensionType(this, context);
+}
+
+abstract class ExtensionTypeBuilder extends Object
+ with HasAnnotationsBuilder, HasDartDocsBuilder, HasGenericsBuilder
+ implements Builder<ExtensionType, ExtensionTypeBuilder> {
+ factory ExtensionTypeBuilder() = _$ExtensionTypeBuilder;
+
+ ExtensionTypeBuilder._();
+
+ @override
+ void update(void Function(ExtensionTypeBuilder)? updates) {
+ updates?.call(this);
+ }
+
+ @override
+ ListBuilder<Expression> annotations = ListBuilder<Expression>();
+
+ @override
+ ListBuilder<String> docs = ListBuilder<String>();
+
+ /// Whether this extension type is declared as `const`.
+ bool constant = false;
+
+ String? name;
+
+ @override
+ ListBuilder<Reference> types = ListBuilder<Reference>();
+
+ /// Name of the extension type's primary constructor. An empty string
+ /// will make it unnamed.
+ String primaryConstructorName = '';
+
+ RepresentationDeclaration? representationDeclaration;
+
+ ListBuilder<Reference> implements = ListBuilder<Reference>();
+
+ ListBuilder<Constructor> constructors = ListBuilder<Constructor>();
+
+ ListBuilder<Field> fields = ListBuilder<Field>();
+
+ ListBuilder<Method> methods = ListBuilder<Method>();
+}
+
+abstract class RepresentationDeclaration extends Object
+ with HasAnnotations, HasDartDocs
+ implements
+ Built<RepresentationDeclaration, RepresentationDeclarationBuilder> {
+ factory RepresentationDeclaration(
+ [void Function(RepresentationDeclarationBuilder)? updates]) =
+ _$RepresentationDeclaration;
+
+ RepresentationDeclaration._();
+
+ @override
+ BuiltList<Expression> get annotations;
+
+ @override
+ BuiltList<String> get docs;
+
+ Reference get declaredRepresentationType;
+
+ String get name;
+}
+
+abstract class RepresentationDeclarationBuilder extends Object
+ with HasAnnotationsBuilder, HasDartDocsBuilder
+ implements
+ Builder<RepresentationDeclaration, RepresentationDeclarationBuilder> {
+ factory RepresentationDeclarationBuilder() =
+ _$RepresentationDeclarationBuilder;
+
+ RepresentationDeclarationBuilder._();
+
+ @override
+ void update(void Function(RepresentationDeclarationBuilder)? updates) {
+ updates?.call(this);
+ }
+
+ @override
+ ListBuilder<Expression> annotations = ListBuilder<Expression>();
+
+ @override
+ ListBuilder<String> docs = ListBuilder<String>();
+
+ Reference? declaredRepresentationType;
+
+ String? name;
+}
diff --git a/pkgs/code_builder/lib/src/specs/extension_type.g.dart b/pkgs/code_builder/lib/src/specs/extension_type.g.dart
new file mode 100644
index 0000000..5769840
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/extension_type.g.dart
@@ -0,0 +1,537 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'extension_type.dart';
+
+// **************************************************************************
+// BuiltValueGenerator
+// **************************************************************************
+
+class _$ExtensionType extends ExtensionType {
+ @override
+ final BuiltList<Expression> annotations;
+ @override
+ final BuiltList<String> docs;
+ @override
+ final bool constant;
+ @override
+ final String name;
+ @override
+ final BuiltList<Reference> types;
+ @override
+ final String primaryConstructorName;
+ @override
+ final RepresentationDeclaration representationDeclaration;
+ @override
+ final BuiltList<Reference> implements;
+ @override
+ final BuiltList<Constructor> constructors;
+ @override
+ final BuiltList<Field> fields;
+ @override
+ final BuiltList<Method> methods;
+
+ 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._() {
+ BuiltValueNullFieldError.checkNotNull(
+ annotations, r'ExtensionType', 'annotations');
+ BuiltValueNullFieldError.checkNotNull(docs, r'ExtensionType', 'docs');
+ BuiltValueNullFieldError.checkNotNull(
+ 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');
+ BuiltValueNullFieldError.checkNotNull(
+ implements, r'ExtensionType', 'implements');
+ BuiltValueNullFieldError.checkNotNull(
+ constructors, r'ExtensionType', 'constructors');
+ BuiltValueNullFieldError.checkNotNull(fields, r'ExtensionType', 'fields');
+ BuiltValueNullFieldError.checkNotNull(methods, r'ExtensionType', 'methods');
+ }
+
+ @override
+ ExtensionType rebuild(void Function(ExtensionTypeBuilder) updates) =>
+ (toBuilder()..update(updates)).build();
+
+ @override
+ _$ExtensionTypeBuilder toBuilder() =>
+ new _$ExtensionTypeBuilder()..replace(this);
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(other, this)) return true;
+ return other is ExtensionType &&
+ annotations == other.annotations &&
+ docs == other.docs &&
+ constant == other.constant &&
+ name == other.name &&
+ types == other.types &&
+ primaryConstructorName == other.primaryConstructorName &&
+ representationDeclaration == other.representationDeclaration &&
+ implements == other.implements &&
+ constructors == other.constructors &&
+ fields == other.fields &&
+ methods == other.methods;
+ }
+
+ @override
+ int get hashCode {
+ var _$hash = 0;
+ _$hash = $jc(_$hash, annotations.hashCode);
+ _$hash = $jc(_$hash, docs.hashCode);
+ _$hash = $jc(_$hash, constant.hashCode);
+ _$hash = $jc(_$hash, name.hashCode);
+ _$hash = $jc(_$hash, types.hashCode);
+ _$hash = $jc(_$hash, primaryConstructorName.hashCode);
+ _$hash = $jc(_$hash, representationDeclaration.hashCode);
+ _$hash = $jc(_$hash, implements.hashCode);
+ _$hash = $jc(_$hash, constructors.hashCode);
+ _$hash = $jc(_$hash, fields.hashCode);
+ _$hash = $jc(_$hash, methods.hashCode);
+ _$hash = $jf(_$hash);
+ return _$hash;
+ }
+
+ @override
+ String toString() {
+ return (newBuiltValueToStringHelper(r'ExtensionType')
+ ..add('annotations', annotations)
+ ..add('docs', docs)
+ ..add('constant', constant)
+ ..add('name', name)
+ ..add('types', types)
+ ..add('primaryConstructorName', primaryConstructorName)
+ ..add('representationDeclaration', representationDeclaration)
+ ..add('implements', implements)
+ ..add('constructors', constructors)
+ ..add('fields', fields)
+ ..add('methods', methods))
+ .toString();
+ }
+}
+
+class _$ExtensionTypeBuilder extends ExtensionTypeBuilder {
+ _$ExtensionType? _$v;
+
+ @override
+ ListBuilder<Expression> get annotations {
+ _$this;
+ return super.annotations;
+ }
+
+ @override
+ set annotations(ListBuilder<Expression> annotations) {
+ _$this;
+ super.annotations = annotations;
+ }
+
+ @override
+ ListBuilder<String> get docs {
+ _$this;
+ return super.docs;
+ }
+
+ @override
+ set docs(ListBuilder<String> docs) {
+ _$this;
+ super.docs = docs;
+ }
+
+ @override
+ bool get constant {
+ _$this;
+ return super.constant;
+ }
+
+ @override
+ set constant(bool constant) {
+ _$this;
+ super.constant = constant;
+ }
+
+ @override
+ String? get name {
+ _$this;
+ return super.name;
+ }
+
+ @override
+ set name(String? name) {
+ _$this;
+ super.name = name;
+ }
+
+ @override
+ ListBuilder<Reference> get types {
+ _$this;
+ return super.types;
+ }
+
+ @override
+ set types(ListBuilder<Reference> types) {
+ _$this;
+ super.types = types;
+ }
+
+ @override
+ String get primaryConstructorName {
+ _$this;
+ return super.primaryConstructorName;
+ }
+
+ @override
+ set primaryConstructorName(String primaryConstructorName) {
+ _$this;
+ super.primaryConstructorName = primaryConstructorName;
+ }
+
+ @override
+ RepresentationDeclaration? get representationDeclaration {
+ _$this;
+ return super.representationDeclaration;
+ }
+
+ @override
+ set representationDeclaration(
+ RepresentationDeclaration? representationDeclaration) {
+ _$this;
+ super.representationDeclaration = representationDeclaration;
+ }
+
+ @override
+ ListBuilder<Reference> get implements {
+ _$this;
+ return super.implements;
+ }
+
+ @override
+ set implements(ListBuilder<Reference> implements) {
+ _$this;
+ super.implements = implements;
+ }
+
+ @override
+ ListBuilder<Constructor> get constructors {
+ _$this;
+ return super.constructors;
+ }
+
+ @override
+ set constructors(ListBuilder<Constructor> constructors) {
+ _$this;
+ super.constructors = constructors;
+ }
+
+ @override
+ ListBuilder<Field> get fields {
+ _$this;
+ return super.fields;
+ }
+
+ @override
+ set fields(ListBuilder<Field> fields) {
+ _$this;
+ super.fields = fields;
+ }
+
+ @override
+ ListBuilder<Method> get methods {
+ _$this;
+ return super.methods;
+ }
+
+ @override
+ set methods(ListBuilder<Method> methods) {
+ _$this;
+ super.methods = methods;
+ }
+
+ _$ExtensionTypeBuilder() : super._();
+
+ ExtensionTypeBuilder get _$this {
+ final $v = _$v;
+ if ($v != null) {
+ super.annotations = $v.annotations.toBuilder();
+ super.docs = $v.docs.toBuilder();
+ super.constant = $v.constant;
+ super.name = $v.name;
+ super.types = $v.types.toBuilder();
+ super.primaryConstructorName = $v.primaryConstructorName;
+ super.representationDeclaration = $v.representationDeclaration;
+ super.implements = $v.implements.toBuilder();
+ super.constructors = $v.constructors.toBuilder();
+ super.fields = $v.fields.toBuilder();
+ super.methods = $v.methods.toBuilder();
+ _$v = null;
+ }
+ return this;
+ }
+
+ @override
+ void replace(ExtensionType other) {
+ ArgumentError.checkNotNull(other, 'other');
+ _$v = other as _$ExtensionType;
+ }
+
+ @override
+ void update(void Function(ExtensionTypeBuilder)? updates) {
+ if (updates != null) updates(this);
+ }
+
+ @override
+ ExtensionType build() => _build();
+
+ _$ExtensionType _build() {
+ _$ExtensionType _$result;
+ try {
+ _$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());
+ } catch (_) {
+ late String _$failedField;
+ try {
+ _$failedField = 'annotations';
+ annotations.build();
+ _$failedField = 'docs';
+ docs.build();
+
+ _$failedField = 'types';
+ types.build();
+
+ _$failedField = 'implements';
+ implements.build();
+ _$failedField = 'constructors';
+ constructors.build();
+ _$failedField = 'fields';
+ fields.build();
+ _$failedField = 'methods';
+ methods.build();
+ } catch (e) {
+ throw new BuiltValueNestedFieldError(
+ r'ExtensionType', _$failedField, e.toString());
+ }
+ rethrow;
+ }
+ replace(_$result);
+ return _$result;
+ }
+}
+
+class _$RepresentationDeclaration extends RepresentationDeclaration {
+ @override
+ final BuiltList<Expression> annotations;
+ @override
+ final BuiltList<String> docs;
+ @override
+ final Reference declaredRepresentationType;
+ @override
+ final String name;
+
+ 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._() {
+ BuiltValueNullFieldError.checkNotNull(
+ annotations, r'RepresentationDeclaration', 'annotations');
+ BuiltValueNullFieldError.checkNotNull(
+ docs, r'RepresentationDeclaration', 'docs');
+ BuiltValueNullFieldError.checkNotNull(declaredRepresentationType,
+ r'RepresentationDeclaration', 'declaredRepresentationType');
+ BuiltValueNullFieldError.checkNotNull(
+ name, r'RepresentationDeclaration', 'name');
+ }
+
+ @override
+ RepresentationDeclaration rebuild(
+ void Function(RepresentationDeclarationBuilder) updates) =>
+ (toBuilder()..update(updates)).build();
+
+ @override
+ _$RepresentationDeclarationBuilder toBuilder() =>
+ new _$RepresentationDeclarationBuilder()..replace(this);
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(other, this)) return true;
+ return other is RepresentationDeclaration &&
+ annotations == other.annotations &&
+ docs == other.docs &&
+ declaredRepresentationType == other.declaredRepresentationType &&
+ name == other.name;
+ }
+
+ @override
+ int get hashCode {
+ var _$hash = 0;
+ _$hash = $jc(_$hash, annotations.hashCode);
+ _$hash = $jc(_$hash, docs.hashCode);
+ _$hash = $jc(_$hash, declaredRepresentationType.hashCode);
+ _$hash = $jc(_$hash, name.hashCode);
+ _$hash = $jf(_$hash);
+ return _$hash;
+ }
+
+ @override
+ String toString() {
+ return (newBuiltValueToStringHelper(r'RepresentationDeclaration')
+ ..add('annotations', annotations)
+ ..add('docs', docs)
+ ..add('declaredRepresentationType', declaredRepresentationType)
+ ..add('name', name))
+ .toString();
+ }
+}
+
+class _$RepresentationDeclarationBuilder
+ extends RepresentationDeclarationBuilder {
+ _$RepresentationDeclaration? _$v;
+
+ @override
+ ListBuilder<Expression> get annotations {
+ _$this;
+ return super.annotations;
+ }
+
+ @override
+ set annotations(ListBuilder<Expression> annotations) {
+ _$this;
+ super.annotations = annotations;
+ }
+
+ @override
+ ListBuilder<String> get docs {
+ _$this;
+ return super.docs;
+ }
+
+ @override
+ set docs(ListBuilder<String> docs) {
+ _$this;
+ super.docs = docs;
+ }
+
+ @override
+ Reference? get declaredRepresentationType {
+ _$this;
+ return super.declaredRepresentationType;
+ }
+
+ @override
+ set declaredRepresentationType(Reference? declaredRepresentationType) {
+ _$this;
+ super.declaredRepresentationType = declaredRepresentationType;
+ }
+
+ @override
+ String? get name {
+ _$this;
+ return super.name;
+ }
+
+ @override
+ set name(String? name) {
+ _$this;
+ super.name = name;
+ }
+
+ _$RepresentationDeclarationBuilder() : super._();
+
+ RepresentationDeclarationBuilder get _$this {
+ final $v = _$v;
+ if ($v != null) {
+ super.annotations = $v.annotations.toBuilder();
+ super.docs = $v.docs.toBuilder();
+ super.declaredRepresentationType = $v.declaredRepresentationType;
+ super.name = $v.name;
+ _$v = null;
+ }
+ return this;
+ }
+
+ @override
+ void replace(RepresentationDeclaration other) {
+ ArgumentError.checkNotNull(other, 'other');
+ _$v = other as _$RepresentationDeclaration;
+ }
+
+ @override
+ void update(void Function(RepresentationDeclarationBuilder)? updates) {
+ if (updates != null) updates(this);
+ }
+
+ @override
+ RepresentationDeclaration build() => _build();
+
+ _$RepresentationDeclaration _build() {
+ _$RepresentationDeclaration _$result;
+ try {
+ _$result = _$v ??
+ new _$RepresentationDeclaration._(
+ 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 {
+ _$failedField = 'annotations';
+ annotations.build();
+ _$failedField = 'docs';
+ docs.build();
+ } catch (e) {
+ throw new BuiltValueNestedFieldError(
+ r'RepresentationDeclaration', _$failedField, e.toString());
+ }
+ rethrow;
+ }
+ replace(_$result);
+ return _$result;
+ }
+}
+
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/pkgs/code_builder/lib/src/specs/field.dart b/pkgs/code_builder/lib/src/specs/field.dart
new file mode 100644
index 0000000..7930bf6
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/field.dart
@@ -0,0 +1,101 @@
+// Copyright (c) 2017, 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:built_collection/built_collection.dart';
+import 'package:built_value/built_value.dart';
+import 'package:meta/meta.dart';
+
+import '../base.dart';
+import '../mixins/annotations.dart';
+import '../mixins/dartdoc.dart';
+import '../visitors.dart';
+import 'code.dart';
+import 'expression.dart';
+import 'reference.dart';
+
+part 'field.g.dart';
+
+@immutable
+abstract class Field extends Object
+ with HasAnnotations, HasDartDocs
+ implements Built<Field, FieldBuilder>, Spec {
+ factory Field([void Function(FieldBuilder) updates]) = _$Field;
+
+ Field._();
+
+ @override
+ BuiltList<Expression> get annotations;
+
+ @override
+ BuiltList<String> get docs;
+
+ /// Field assignment, if any.
+ Code? get assignment;
+
+ /// Whether this field should be prefixed with `static`.
+ ///
+ /// This is only valid within classes.
+ bool get static;
+
+ /// Whether this field should be prefixed with `late`.
+ bool get late;
+
+ /// Whether the field should be prefixed with `external`.
+ bool get external;
+
+ /// Name of the field.
+ String get name;
+
+ Reference? get type;
+
+ FieldModifier get modifier;
+
+ @override
+ R accept<R>(
+ SpecVisitor<R> visitor, [
+ R? context,
+ ]) =>
+ visitor.visitField(this, context);
+}
+
+enum FieldModifier {
+ var$,
+ final$,
+ constant,
+}
+
+abstract class FieldBuilder extends Object
+ with HasAnnotationsBuilder, HasDartDocsBuilder
+ implements Builder<Field, FieldBuilder> {
+ factory FieldBuilder() = _$FieldBuilder;
+
+ FieldBuilder._();
+
+ @override
+ ListBuilder<Expression> annotations = ListBuilder<Expression>();
+
+ @override
+ ListBuilder<String> docs = ListBuilder<String>();
+
+ /// Field assignment, if any.
+ Code? assignment;
+
+ /// Whether this field should be prefixed with `static`.
+ ///
+ /// This is only valid within classes.
+ bool static = false;
+
+ /// Whether this field should be prefixed with `late`.
+ bool late = false;
+
+ /// Whether the field should be prefixed with `external`.
+ bool external = false;
+
+ /// Name of the field.
+ String? name;
+
+ Reference? type;
+
+ FieldModifier modifier = FieldModifier.var$;
+}
diff --git a/pkgs/code_builder/lib/src/specs/field.g.dart b/pkgs/code_builder/lib/src/specs/field.g.dart
new file mode 100644
index 0000000..d15f1c7
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/field.g.dart
@@ -0,0 +1,287 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'field.dart';
+
+// **************************************************************************
+// BuiltValueGenerator
+// **************************************************************************
+
+class _$Field extends Field {
+ @override
+ final BuiltList<Expression> annotations;
+ @override
+ final BuiltList<String> docs;
+ @override
+ final Code? assignment;
+ @override
+ final bool static;
+ @override
+ final bool late;
+ @override
+ final bool external;
+ @override
+ final String name;
+ @override
+ final Reference? type;
+ @override
+ final FieldModifier modifier;
+
+ 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._() {
+ BuiltValueNullFieldError.checkNotNull(annotations, r'Field', 'annotations');
+ BuiltValueNullFieldError.checkNotNull(docs, r'Field', 'docs');
+ BuiltValueNullFieldError.checkNotNull(static, r'Field', 'static');
+ BuiltValueNullFieldError.checkNotNull(late, r'Field', 'late');
+ BuiltValueNullFieldError.checkNotNull(external, r'Field', 'external');
+ BuiltValueNullFieldError.checkNotNull(name, r'Field', 'name');
+ BuiltValueNullFieldError.checkNotNull(modifier, r'Field', 'modifier');
+ }
+
+ @override
+ Field rebuild(void Function(FieldBuilder) updates) =>
+ (toBuilder()..update(updates)).build();
+
+ @override
+ _$FieldBuilder toBuilder() => new _$FieldBuilder()..replace(this);
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(other, this)) return true;
+ return other is Field &&
+ annotations == other.annotations &&
+ docs == other.docs &&
+ assignment == other.assignment &&
+ static == other.static &&
+ late == other.late &&
+ external == other.external &&
+ name == other.name &&
+ type == other.type &&
+ modifier == other.modifier;
+ }
+
+ @override
+ int get hashCode {
+ var _$hash = 0;
+ _$hash = $jc(_$hash, annotations.hashCode);
+ _$hash = $jc(_$hash, docs.hashCode);
+ _$hash = $jc(_$hash, assignment.hashCode);
+ _$hash = $jc(_$hash, static.hashCode);
+ _$hash = $jc(_$hash, late.hashCode);
+ _$hash = $jc(_$hash, external.hashCode);
+ _$hash = $jc(_$hash, name.hashCode);
+ _$hash = $jc(_$hash, type.hashCode);
+ _$hash = $jc(_$hash, modifier.hashCode);
+ _$hash = $jf(_$hash);
+ return _$hash;
+ }
+
+ @override
+ String toString() {
+ return (newBuiltValueToStringHelper(r'Field')
+ ..add('annotations', annotations)
+ ..add('docs', docs)
+ ..add('assignment', assignment)
+ ..add('static', static)
+ ..add('late', late)
+ ..add('external', external)
+ ..add('name', name)
+ ..add('type', type)
+ ..add('modifier', modifier))
+ .toString();
+ }
+}
+
+class _$FieldBuilder extends FieldBuilder {
+ _$Field? _$v;
+
+ @override
+ ListBuilder<Expression> get annotations {
+ _$this;
+ return super.annotations;
+ }
+
+ @override
+ set annotations(ListBuilder<Expression> annotations) {
+ _$this;
+ super.annotations = annotations;
+ }
+
+ @override
+ ListBuilder<String> get docs {
+ _$this;
+ return super.docs;
+ }
+
+ @override
+ set docs(ListBuilder<String> docs) {
+ _$this;
+ super.docs = docs;
+ }
+
+ @override
+ Code? get assignment {
+ _$this;
+ return super.assignment;
+ }
+
+ @override
+ set assignment(Code? assignment) {
+ _$this;
+ super.assignment = assignment;
+ }
+
+ @override
+ bool get static {
+ _$this;
+ return super.static;
+ }
+
+ @override
+ set static(bool static) {
+ _$this;
+ super.static = static;
+ }
+
+ @override
+ bool get late {
+ _$this;
+ return super.late;
+ }
+
+ @override
+ set late(bool late) {
+ _$this;
+ super.late = late;
+ }
+
+ @override
+ bool get external {
+ _$this;
+ return super.external;
+ }
+
+ @override
+ set external(bool external) {
+ _$this;
+ super.external = external;
+ }
+
+ @override
+ String? get name {
+ _$this;
+ return super.name;
+ }
+
+ @override
+ set name(String? name) {
+ _$this;
+ super.name = name;
+ }
+
+ @override
+ Reference? get type {
+ _$this;
+ return super.type;
+ }
+
+ @override
+ set type(Reference? type) {
+ _$this;
+ super.type = type;
+ }
+
+ @override
+ FieldModifier get modifier {
+ _$this;
+ return super.modifier;
+ }
+
+ @override
+ set modifier(FieldModifier modifier) {
+ _$this;
+ super.modifier = modifier;
+ }
+
+ _$FieldBuilder() : super._();
+
+ FieldBuilder get _$this {
+ final $v = _$v;
+ if ($v != null) {
+ super.annotations = $v.annotations.toBuilder();
+ super.docs = $v.docs.toBuilder();
+ super.assignment = $v.assignment;
+ super.static = $v.static;
+ super.late = $v.late;
+ super.external = $v.external;
+ super.name = $v.name;
+ super.type = $v.type;
+ super.modifier = $v.modifier;
+ _$v = null;
+ }
+ return this;
+ }
+
+ @override
+ void replace(Field other) {
+ ArgumentError.checkNotNull(other, 'other');
+ _$v = other as _$Field;
+ }
+
+ @override
+ void update(void Function(FieldBuilder)? updates) {
+ if (updates != null) updates(this);
+ }
+
+ @override
+ Field build() => _build();
+
+ _$Field _build() {
+ _$Field _$result;
+ try {
+ _$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'));
+ } catch (_) {
+ late String _$failedField;
+ try {
+ _$failedField = 'annotations';
+ annotations.build();
+ _$failedField = 'docs';
+ docs.build();
+ } catch (e) {
+ throw new BuiltValueNestedFieldError(
+ r'Field', _$failedField, e.toString());
+ }
+ rethrow;
+ }
+ replace(_$result);
+ return _$result;
+ }
+}
+
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/pkgs/code_builder/lib/src/specs/library.dart b/pkgs/code_builder/lib/src/specs/library.dart
new file mode 100644
index 0000000..bbfbf3f
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/library.dart
@@ -0,0 +1,79 @@
+// Copyright (c) 2017, 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:built_collection/built_collection.dart';
+import 'package:built_value/built_value.dart';
+import 'package:meta/meta.dart';
+
+import '../base.dart';
+import '../mixins/annotations.dart';
+import '../mixins/dartdoc.dart';
+import '../visitors.dart';
+import 'directive.dart';
+import 'expression.dart';
+
+part 'library.g.dart';
+
+@immutable
+abstract class Library
+ with HasAnnotations, HasDartDocs
+ implements Built<Library, LibraryBuilder>, Spec {
+ factory Library([void Function(LibraryBuilder) updates]) = _$Library;
+ Library._();
+
+ @override
+ BuiltList<Expression> get annotations;
+
+ @override
+ BuiltList<String> get docs;
+
+ BuiltList<Directive> get directives;
+ BuiltList<Spec> get body;
+
+ /// Line comments to place at the start of the library.
+ BuiltList<String> get comments;
+
+ /// A comment indicating the tool this library was generated by.
+ ///
+ /// This is typically of the form `Generated by xxx.`; it should exclude
+ /// leading line comment characters.
+ String? get generatedByComment;
+
+ /// A list of analysis issues to ignore (`ignore_for_file: ...`).
+ BuiltList<String> get ignoreForFile;
+
+ /// Name of the library.
+ ///
+ /// May be `null` when no [annotations] are specified.
+ String? get name;
+
+ @override
+ R accept<R>(
+ SpecVisitor<R> visitor, [
+ R? context,
+ ]) =>
+ visitor.visitLibrary(this, context);
+}
+
+abstract class LibraryBuilder
+ with HasAnnotationsBuilder, HasDartDocsBuilder
+ implements Builder<Library, LibraryBuilder> {
+ factory LibraryBuilder() = _$LibraryBuilder;
+ LibraryBuilder._();
+
+ @override
+ ListBuilder<Expression> annotations = ListBuilder<Expression>();
+
+ @override
+ ListBuilder<String> docs = ListBuilder<String>();
+
+ ListBuilder<Spec> body = ListBuilder<Spec>();
+ ListBuilder<Directive> directives = ListBuilder<Directive>();
+
+ ListBuilder<String> comments = ListBuilder<String>();
+ String? generatedByComment;
+ ListBuilder<String> ignoreForFile = ListBuilder<String>();
+
+ String? name;
+}
diff --git a/pkgs/code_builder/lib/src/specs/library.g.dart b/pkgs/code_builder/lib/src/specs/library.g.dart
new file mode 100644
index 0000000..63cfc20
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/library.g.dart
@@ -0,0 +1,272 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'library.dart';
+
+// **************************************************************************
+// BuiltValueGenerator
+// **************************************************************************
+
+class _$Library extends Library {
+ @override
+ final BuiltList<Expression> annotations;
+ @override
+ final BuiltList<String> docs;
+ @override
+ final BuiltList<Directive> directives;
+ @override
+ final BuiltList<Spec> body;
+ @override
+ final BuiltList<String> comments;
+ @override
+ final String? generatedByComment;
+ @override
+ final BuiltList<String> ignoreForFile;
+ @override
+ final String? name;
+
+ 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._() {
+ BuiltValueNullFieldError.checkNotNull(
+ 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');
+ }
+
+ @override
+ Library rebuild(void Function(LibraryBuilder) updates) =>
+ (toBuilder()..update(updates)).build();
+
+ @override
+ _$LibraryBuilder toBuilder() => new _$LibraryBuilder()..replace(this);
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(other, this)) return true;
+ return other is Library &&
+ annotations == other.annotations &&
+ docs == other.docs &&
+ directives == other.directives &&
+ body == other.body &&
+ comments == other.comments &&
+ generatedByComment == other.generatedByComment &&
+ ignoreForFile == other.ignoreForFile &&
+ name == other.name;
+ }
+
+ @override
+ int get hashCode {
+ var _$hash = 0;
+ _$hash = $jc(_$hash, annotations.hashCode);
+ _$hash = $jc(_$hash, docs.hashCode);
+ _$hash = $jc(_$hash, directives.hashCode);
+ _$hash = $jc(_$hash, body.hashCode);
+ _$hash = $jc(_$hash, comments.hashCode);
+ _$hash = $jc(_$hash, generatedByComment.hashCode);
+ _$hash = $jc(_$hash, ignoreForFile.hashCode);
+ _$hash = $jc(_$hash, name.hashCode);
+ _$hash = $jf(_$hash);
+ return _$hash;
+ }
+
+ @override
+ String toString() {
+ return (newBuiltValueToStringHelper(r'Library')
+ ..add('annotations', annotations)
+ ..add('docs', docs)
+ ..add('directives', directives)
+ ..add('body', body)
+ ..add('comments', comments)
+ ..add('generatedByComment', generatedByComment)
+ ..add('ignoreForFile', ignoreForFile)
+ ..add('name', name))
+ .toString();
+ }
+}
+
+class _$LibraryBuilder extends LibraryBuilder {
+ _$Library? _$v;
+
+ @override
+ ListBuilder<Expression> get annotations {
+ _$this;
+ return super.annotations;
+ }
+
+ @override
+ set annotations(ListBuilder<Expression> annotations) {
+ _$this;
+ super.annotations = annotations;
+ }
+
+ @override
+ ListBuilder<String> get docs {
+ _$this;
+ return super.docs;
+ }
+
+ @override
+ set docs(ListBuilder<String> docs) {
+ _$this;
+ super.docs = docs;
+ }
+
+ @override
+ ListBuilder<Directive> get directives {
+ _$this;
+ return super.directives;
+ }
+
+ @override
+ set directives(ListBuilder<Directive> directives) {
+ _$this;
+ super.directives = directives;
+ }
+
+ @override
+ ListBuilder<Spec> get body {
+ _$this;
+ return super.body;
+ }
+
+ @override
+ set body(ListBuilder<Spec> body) {
+ _$this;
+ super.body = body;
+ }
+
+ @override
+ ListBuilder<String> get comments {
+ _$this;
+ return super.comments;
+ }
+
+ @override
+ set comments(ListBuilder<String> comments) {
+ _$this;
+ super.comments = comments;
+ }
+
+ @override
+ String? get generatedByComment {
+ _$this;
+ return super.generatedByComment;
+ }
+
+ @override
+ set generatedByComment(String? generatedByComment) {
+ _$this;
+ super.generatedByComment = generatedByComment;
+ }
+
+ @override
+ ListBuilder<String> get ignoreForFile {
+ _$this;
+ return super.ignoreForFile;
+ }
+
+ @override
+ set ignoreForFile(ListBuilder<String> ignoreForFile) {
+ _$this;
+ super.ignoreForFile = ignoreForFile;
+ }
+
+ @override
+ String? get name {
+ _$this;
+ return super.name;
+ }
+
+ @override
+ set name(String? name) {
+ _$this;
+ super.name = name;
+ }
+
+ _$LibraryBuilder() : super._();
+
+ LibraryBuilder get _$this {
+ final $v = _$v;
+ if ($v != null) {
+ super.annotations = $v.annotations.toBuilder();
+ super.docs = $v.docs.toBuilder();
+ super.directives = $v.directives.toBuilder();
+ super.body = $v.body.toBuilder();
+ super.comments = $v.comments.toBuilder();
+ super.generatedByComment = $v.generatedByComment;
+ super.ignoreForFile = $v.ignoreForFile.toBuilder();
+ super.name = $v.name;
+ _$v = null;
+ }
+ return this;
+ }
+
+ @override
+ void replace(Library other) {
+ ArgumentError.checkNotNull(other, 'other');
+ _$v = other as _$Library;
+ }
+
+ @override
+ void update(void Function(LibraryBuilder)? updates) {
+ if (updates != null) updates(this);
+ }
+
+ @override
+ Library build() => _build();
+
+ _$Library _build() {
+ _$Library _$result;
+ try {
+ _$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);
+ } catch (_) {
+ late String _$failedField;
+ try {
+ _$failedField = 'annotations';
+ annotations.build();
+ _$failedField = 'docs';
+ docs.build();
+ _$failedField = 'directives';
+ directives.build();
+ _$failedField = 'body';
+ body.build();
+ _$failedField = 'comments';
+ comments.build();
+
+ _$failedField = 'ignoreForFile';
+ ignoreForFile.build();
+ } catch (e) {
+ throw new BuiltValueNestedFieldError(
+ r'Library', _$failedField, e.toString());
+ }
+ rethrow;
+ }
+ replace(_$result);
+ return _$result;
+ }
+}
+
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/pkgs/code_builder/lib/src/specs/method.dart b/pkgs/code_builder/lib/src/specs/method.dart
new file mode 100644
index 0000000..2c0a645
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/method.dart
@@ -0,0 +1,271 @@
+// Copyright (c) 2017, 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:built_collection/built_collection.dart';
+import 'package:built_value/built_value.dart';
+import 'package:meta/meta.dart';
+
+import '../base.dart';
+import '../mixins/annotations.dart';
+import '../mixins/dartdoc.dart';
+import '../mixins/generics.dart';
+import '../visitors.dart';
+import 'code.dart';
+import 'expression.dart';
+import 'reference.dart';
+
+part 'method.g.dart';
+
+const _$void = Reference('void');
+
+@immutable
+abstract class Method extends Object
+ with HasAnnotations, HasGenerics, HasDartDocs
+ implements Built<Method, MethodBuilder>, Spec {
+ factory Method([void Function(MethodBuilder) updates]) = _$Method;
+
+ factory Method.returnsVoid([void Function(MethodBuilder)? updates]) =>
+ Method((b) {
+ if (updates != null) {
+ updates(b);
+ }
+ b.returns = _$void;
+ });
+
+ Method._();
+
+ @override
+ BuiltList<Expression> get annotations;
+
+ @override
+ BuiltList<String> get docs;
+
+ @override
+ BuiltList<Reference> get types;
+
+ /// Optional parameters.
+ BuiltList<Parameter> get optionalParameters;
+
+ /// Required parameters.
+ BuiltList<Parameter> get requiredParameters;
+
+ /// Body of the method.
+ Code? get body;
+
+ /// Whether the method should be prefixed with `external`.
+ bool get external;
+
+ /// Whether this method is a simple lambda expression.
+ ///
+ /// May be `null` to be inferred based on the value of [body].
+ bool? get lambda;
+
+ /// Whether this method should be prefixed with `static`.
+ ///
+ /// This is only valid within classes.
+ bool get static;
+
+ /// Name of the method or function.
+ ///
+ /// May be `null` when being used as a [closure].
+ String? get name;
+
+ /// Whether this is a getter or setter.
+ MethodType? get type;
+
+ /// Whether this method is `async`, `async*`, or `sync*`.
+ MethodModifier? get modifier;
+
+ Reference? get returns;
+
+ @override
+ R accept<R>(
+ SpecVisitor<R> visitor, [
+ R? context,
+ ]) =>
+ visitor.visitMethod(this, context);
+
+ /// This method as a closure.
+ Expression get closure => toClosure(this);
+
+ /// This method as a (possibly) generic closure.
+ Expression get genericClosure => toGenericClosure(this);
+}
+
+abstract class MethodBuilder extends Object
+ with HasAnnotationsBuilder, HasGenericsBuilder, HasDartDocsBuilder
+ implements Builder<Method, MethodBuilder> {
+ factory MethodBuilder() = _$MethodBuilder;
+
+ MethodBuilder._();
+
+ @override
+ void update(void Function(MethodBuilder)? updates) {
+ updates?.call(this);
+ }
+
+ @override
+ ListBuilder<Expression> annotations = ListBuilder<Expression>();
+
+ @override
+ ListBuilder<String> docs = ListBuilder<String>();
+
+ @override
+ ListBuilder<Reference> types = ListBuilder<Reference>();
+
+ /// Optional parameters.
+ ListBuilder<Parameter> optionalParameters = ListBuilder<Parameter>();
+
+ /// Required parameters.
+ ListBuilder<Parameter> requiredParameters = ListBuilder<Parameter>();
+
+ /// Body of the method.
+ Code? body;
+
+ /// Whether the method should be prefixed with `external`.
+ bool external = false;
+
+ /// Whether this method is a simple lambda expression.
+ ///
+ /// If not specified this is inferred from the [body].
+ bool? lambda;
+
+ /// Whether this method should be prefixed with `static`.
+ ///
+ /// This is only valid within classes.
+ bool static = false;
+
+ /// Name of the method or function.
+ String? name;
+
+ /// Whether this is a getter or setter.
+ MethodType? type;
+
+ /// Whether this method is `async`, `async*`, or `sync*`.
+ MethodModifier? modifier;
+
+ Reference? returns;
+}
+
+enum MethodType {
+ getter,
+ setter,
+}
+
+enum MethodModifier {
+ async,
+ asyncStar,
+ syncStar,
+}
+
+abstract class Parameter extends Object
+ with HasAnnotations, HasGenerics, HasDartDocs
+ implements Built<Parameter, ParameterBuilder> {
+ factory Parameter([void Function(ParameterBuilder) updates]) = _$Parameter;
+
+ Parameter._();
+
+ /// If not `null`, a default assignment if the parameter is optional.
+ Code? get defaultTo;
+
+ /// Name of the parameter.
+ String get name;
+
+ /// Whether this parameter should be named, if optional.
+ bool get named;
+
+ /// Whether this parameter should be field formal (i.e. `this.`).
+ ///
+ /// This is only valid on constructors;
+ bool get toThis;
+
+ /// Whether this parameter should be passed to super\
+ /// constructor (i.e. `super.`).
+ ///
+ /// This is only valid on constructors;
+ bool get toSuper;
+
+ @override
+ BuiltList<Expression> get annotations;
+
+ @override
+ BuiltList<String> get docs;
+
+ @override
+ BuiltList<Reference> get types;
+
+ /// Type of the parameter;
+ Reference? get type;
+
+ /// Whether this parameter should be annotated with the `required` keyword.
+ ///
+ /// This is only valid on named parameters.
+ ///
+ /// This is only valid when the output is targeting a Dart language version
+ /// that supports null safety.
+ bool get required;
+
+ /// Whether this parameter should be annotated with the `covariant` keyword.
+ ///
+ /// This is only valid on instance methods.
+ bool get covariant;
+}
+
+abstract class ParameterBuilder extends Object
+ with HasAnnotationsBuilder, HasGenericsBuilder, HasDartDocsBuilder
+ implements Builder<Parameter, ParameterBuilder> {
+ factory ParameterBuilder() = _$ParameterBuilder;
+
+ ParameterBuilder._();
+
+ @override
+ void update(void Function(ParameterBuilder)? updates) {
+ updates?.call(this);
+ }
+
+ /// If not `null`, a default assignment if the parameter is optional.
+ Code? defaultTo;
+
+ /// Name of the parameter.
+ late final String name;
+
+ /// Whether this parameter should be named, if optional.
+ bool named = false;
+
+ /// Whether this parameter should be field formal (i.e. `this.`).
+ ///
+ /// This is only valid on constructors;
+ bool toThis = false;
+
+ /// Whether this parameter should be passed to super\
+ /// constructor (i.e. `super.`).
+ ///
+ /// This is only valid on constructors;
+ bool toSuper = false;
+
+ @override
+ ListBuilder<Expression> annotations = ListBuilder<Expression>();
+
+ @override
+ ListBuilder<String> docs = ListBuilder<String>();
+
+ @override
+ ListBuilder<Reference> types = ListBuilder<Reference>();
+
+ /// Type of the parameter;
+ Reference? type;
+
+ /// Whether this parameter should be annotated with the `required` keyword.
+ ///
+ /// This is only valid on named parameters.
+ ///
+ /// This is only valid when the output is targeting a Dart language version
+ /// that supports null safety.
+ bool required = false;
+
+ /// Whether this parameter should be annotated with the `covariant` keyword.
+ ///
+ /// This is only valid on instance methods.
+ bool covariant = false;
+}
diff --git a/pkgs/code_builder/lib/src/specs/method.g.dart b/pkgs/code_builder/lib/src/specs/method.g.dart
new file mode 100644
index 0000000..214f6b2
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/method.g.dart
@@ -0,0 +1,697 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'method.dart';
+
+// **************************************************************************
+// BuiltValueGenerator
+// **************************************************************************
+
+class _$Method extends Method {
+ @override
+ final BuiltList<Expression> annotations;
+ @override
+ final BuiltList<String> docs;
+ @override
+ final BuiltList<Reference> types;
+ @override
+ final BuiltList<Parameter> optionalParameters;
+ @override
+ final BuiltList<Parameter> requiredParameters;
+ @override
+ final Code? body;
+ @override
+ final bool external;
+ @override
+ final bool? lambda;
+ @override
+ final bool static;
+ @override
+ final String? name;
+ @override
+ final MethodType? type;
+ @override
+ final MethodModifier? modifier;
+ @override
+ final Reference? returns;
+
+ 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._() {
+ BuiltValueNullFieldError.checkNotNull(
+ annotations, r'Method', 'annotations');
+ BuiltValueNullFieldError.checkNotNull(docs, r'Method', 'docs');
+ BuiltValueNullFieldError.checkNotNull(types, r'Method', 'types');
+ BuiltValueNullFieldError.checkNotNull(
+ optionalParameters, r'Method', 'optionalParameters');
+ BuiltValueNullFieldError.checkNotNull(
+ requiredParameters, r'Method', 'requiredParameters');
+ BuiltValueNullFieldError.checkNotNull(external, r'Method', 'external');
+ BuiltValueNullFieldError.checkNotNull(static, r'Method', 'static');
+ }
+
+ @override
+ Method rebuild(void Function(MethodBuilder) updates) =>
+ (toBuilder()..update(updates)).build();
+
+ @override
+ _$MethodBuilder toBuilder() => new _$MethodBuilder()..replace(this);
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(other, this)) return true;
+ return other is Method &&
+ annotations == other.annotations &&
+ docs == other.docs &&
+ types == other.types &&
+ optionalParameters == other.optionalParameters &&
+ requiredParameters == other.requiredParameters &&
+ body == other.body &&
+ external == other.external &&
+ lambda == other.lambda &&
+ static == other.static &&
+ name == other.name &&
+ type == other.type &&
+ modifier == other.modifier &&
+ returns == other.returns;
+ }
+
+ @override
+ int get hashCode {
+ var _$hash = 0;
+ _$hash = $jc(_$hash, annotations.hashCode);
+ _$hash = $jc(_$hash, docs.hashCode);
+ _$hash = $jc(_$hash, types.hashCode);
+ _$hash = $jc(_$hash, optionalParameters.hashCode);
+ _$hash = $jc(_$hash, requiredParameters.hashCode);
+ _$hash = $jc(_$hash, body.hashCode);
+ _$hash = $jc(_$hash, external.hashCode);
+ _$hash = $jc(_$hash, lambda.hashCode);
+ _$hash = $jc(_$hash, static.hashCode);
+ _$hash = $jc(_$hash, name.hashCode);
+ _$hash = $jc(_$hash, type.hashCode);
+ _$hash = $jc(_$hash, modifier.hashCode);
+ _$hash = $jc(_$hash, returns.hashCode);
+ _$hash = $jf(_$hash);
+ return _$hash;
+ }
+
+ @override
+ String toString() {
+ return (newBuiltValueToStringHelper(r'Method')
+ ..add('annotations', annotations)
+ ..add('docs', docs)
+ ..add('types', types)
+ ..add('optionalParameters', optionalParameters)
+ ..add('requiredParameters', requiredParameters)
+ ..add('body', body)
+ ..add('external', external)
+ ..add('lambda', lambda)
+ ..add('static', static)
+ ..add('name', name)
+ ..add('type', type)
+ ..add('modifier', modifier)
+ ..add('returns', returns))
+ .toString();
+ }
+}
+
+class _$MethodBuilder extends MethodBuilder {
+ _$Method? _$v;
+
+ @override
+ ListBuilder<Expression> get annotations {
+ _$this;
+ return super.annotations;
+ }
+
+ @override
+ set annotations(ListBuilder<Expression> annotations) {
+ _$this;
+ super.annotations = annotations;
+ }
+
+ @override
+ ListBuilder<String> get docs {
+ _$this;
+ return super.docs;
+ }
+
+ @override
+ set docs(ListBuilder<String> docs) {
+ _$this;
+ super.docs = docs;
+ }
+
+ @override
+ ListBuilder<Reference> get types {
+ _$this;
+ return super.types;
+ }
+
+ @override
+ set types(ListBuilder<Reference> types) {
+ _$this;
+ super.types = types;
+ }
+
+ @override
+ ListBuilder<Parameter> get optionalParameters {
+ _$this;
+ return super.optionalParameters;
+ }
+
+ @override
+ set optionalParameters(ListBuilder<Parameter> optionalParameters) {
+ _$this;
+ super.optionalParameters = optionalParameters;
+ }
+
+ @override
+ ListBuilder<Parameter> get requiredParameters {
+ _$this;
+ return super.requiredParameters;
+ }
+
+ @override
+ set requiredParameters(ListBuilder<Parameter> requiredParameters) {
+ _$this;
+ super.requiredParameters = requiredParameters;
+ }
+
+ @override
+ Code? get body {
+ _$this;
+ return super.body;
+ }
+
+ @override
+ set body(Code? body) {
+ _$this;
+ super.body = body;
+ }
+
+ @override
+ bool get external {
+ _$this;
+ return super.external;
+ }
+
+ @override
+ set external(bool external) {
+ _$this;
+ super.external = external;
+ }
+
+ @override
+ bool? get lambda {
+ _$this;
+ return super.lambda;
+ }
+
+ @override
+ set lambda(bool? lambda) {
+ _$this;
+ super.lambda = lambda;
+ }
+
+ @override
+ bool get static {
+ _$this;
+ return super.static;
+ }
+
+ @override
+ set static(bool static) {
+ _$this;
+ super.static = static;
+ }
+
+ @override
+ String? get name {
+ _$this;
+ return super.name;
+ }
+
+ @override
+ set name(String? name) {
+ _$this;
+ super.name = name;
+ }
+
+ @override
+ MethodType? get type {
+ _$this;
+ return super.type;
+ }
+
+ @override
+ set type(MethodType? type) {
+ _$this;
+ super.type = type;
+ }
+
+ @override
+ MethodModifier? get modifier {
+ _$this;
+ return super.modifier;
+ }
+
+ @override
+ set modifier(MethodModifier? modifier) {
+ _$this;
+ super.modifier = modifier;
+ }
+
+ @override
+ Reference? get returns {
+ _$this;
+ return super.returns;
+ }
+
+ @override
+ set returns(Reference? returns) {
+ _$this;
+ super.returns = returns;
+ }
+
+ _$MethodBuilder() : super._();
+
+ MethodBuilder get _$this {
+ final $v = _$v;
+ if ($v != null) {
+ super.annotations = $v.annotations.toBuilder();
+ super.docs = $v.docs.toBuilder();
+ super.types = $v.types.toBuilder();
+ super.optionalParameters = $v.optionalParameters.toBuilder();
+ super.requiredParameters = $v.requiredParameters.toBuilder();
+ super.body = $v.body;
+ super.external = $v.external;
+ super.lambda = $v.lambda;
+ super.static = $v.static;
+ super.name = $v.name;
+ super.type = $v.type;
+ super.modifier = $v.modifier;
+ super.returns = $v.returns;
+ _$v = null;
+ }
+ return this;
+ }
+
+ @override
+ void replace(Method other) {
+ ArgumentError.checkNotNull(other, 'other');
+ _$v = other as _$Method;
+ }
+
+ @override
+ void update(void Function(MethodBuilder)? updates) {
+ if (updates != null) updates(this);
+ }
+
+ @override
+ Method build() => _build();
+
+ _$Method _build() {
+ _$Method _$result;
+ try {
+ _$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);
+ } catch (_) {
+ late String _$failedField;
+ try {
+ _$failedField = 'annotations';
+ annotations.build();
+ _$failedField = 'docs';
+ docs.build();
+ _$failedField = 'types';
+ types.build();
+ _$failedField = 'optionalParameters';
+ optionalParameters.build();
+ _$failedField = 'requiredParameters';
+ requiredParameters.build();
+ } catch (e) {
+ throw new BuiltValueNestedFieldError(
+ r'Method', _$failedField, e.toString());
+ }
+ rethrow;
+ }
+ replace(_$result);
+ return _$result;
+ }
+}
+
+class _$Parameter extends Parameter {
+ @override
+ final Code? defaultTo;
+ @override
+ final String name;
+ @override
+ final bool named;
+ @override
+ final bool toThis;
+ @override
+ final bool toSuper;
+ @override
+ final BuiltList<Expression> annotations;
+ @override
+ final BuiltList<String> docs;
+ @override
+ final BuiltList<Reference> types;
+ @override
+ final Reference? type;
+ @override
+ final bool required;
+ @override
+ final bool covariant;
+
+ 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._() {
+ 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');
+ BuiltValueNullFieldError.checkNotNull(docs, r'Parameter', 'docs');
+ BuiltValueNullFieldError.checkNotNull(types, r'Parameter', 'types');
+ BuiltValueNullFieldError.checkNotNull(required, r'Parameter', 'required');
+ BuiltValueNullFieldError.checkNotNull(covariant, r'Parameter', 'covariant');
+ }
+
+ @override
+ Parameter rebuild(void Function(ParameterBuilder) updates) =>
+ (toBuilder()..update(updates)).build();
+
+ @override
+ _$ParameterBuilder toBuilder() => new _$ParameterBuilder()..replace(this);
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(other, this)) return true;
+ return other is Parameter &&
+ defaultTo == other.defaultTo &&
+ name == other.name &&
+ named == other.named &&
+ toThis == other.toThis &&
+ toSuper == other.toSuper &&
+ annotations == other.annotations &&
+ docs == other.docs &&
+ types == other.types &&
+ type == other.type &&
+ required == other.required &&
+ covariant == other.covariant;
+ }
+
+ @override
+ int get hashCode {
+ var _$hash = 0;
+ _$hash = $jc(_$hash, defaultTo.hashCode);
+ _$hash = $jc(_$hash, name.hashCode);
+ _$hash = $jc(_$hash, named.hashCode);
+ _$hash = $jc(_$hash, toThis.hashCode);
+ _$hash = $jc(_$hash, toSuper.hashCode);
+ _$hash = $jc(_$hash, annotations.hashCode);
+ _$hash = $jc(_$hash, docs.hashCode);
+ _$hash = $jc(_$hash, types.hashCode);
+ _$hash = $jc(_$hash, type.hashCode);
+ _$hash = $jc(_$hash, required.hashCode);
+ _$hash = $jc(_$hash, covariant.hashCode);
+ _$hash = $jf(_$hash);
+ return _$hash;
+ }
+
+ @override
+ String toString() {
+ return (newBuiltValueToStringHelper(r'Parameter')
+ ..add('defaultTo', defaultTo)
+ ..add('name', name)
+ ..add('named', named)
+ ..add('toThis', toThis)
+ ..add('toSuper', toSuper)
+ ..add('annotations', annotations)
+ ..add('docs', docs)
+ ..add('types', types)
+ ..add('type', type)
+ ..add('required', required)
+ ..add('covariant', covariant))
+ .toString();
+ }
+}
+
+class _$ParameterBuilder extends ParameterBuilder {
+ _$Parameter? _$v;
+
+ @override
+ Code? get defaultTo {
+ _$this;
+ return super.defaultTo;
+ }
+
+ @override
+ set defaultTo(Code? defaultTo) {
+ _$this;
+ super.defaultTo = defaultTo;
+ }
+
+ @override
+ String get name {
+ _$this;
+ return super.name;
+ }
+
+ @override
+ set name(String name) {
+ _$this;
+ super.name = name;
+ }
+
+ @override
+ bool get named {
+ _$this;
+ return super.named;
+ }
+
+ @override
+ set named(bool named) {
+ _$this;
+ super.named = named;
+ }
+
+ @override
+ bool get toThis {
+ _$this;
+ return super.toThis;
+ }
+
+ @override
+ set toThis(bool toThis) {
+ _$this;
+ super.toThis = toThis;
+ }
+
+ @override
+ bool get toSuper {
+ _$this;
+ return super.toSuper;
+ }
+
+ @override
+ set toSuper(bool toSuper) {
+ _$this;
+ super.toSuper = toSuper;
+ }
+
+ @override
+ ListBuilder<Expression> get annotations {
+ _$this;
+ return super.annotations;
+ }
+
+ @override
+ set annotations(ListBuilder<Expression> annotations) {
+ _$this;
+ super.annotations = annotations;
+ }
+
+ @override
+ ListBuilder<String> get docs {
+ _$this;
+ return super.docs;
+ }
+
+ @override
+ set docs(ListBuilder<String> docs) {
+ _$this;
+ super.docs = docs;
+ }
+
+ @override
+ ListBuilder<Reference> get types {
+ _$this;
+ return super.types;
+ }
+
+ @override
+ set types(ListBuilder<Reference> types) {
+ _$this;
+ super.types = types;
+ }
+
+ @override
+ Reference? get type {
+ _$this;
+ return super.type;
+ }
+
+ @override
+ set type(Reference? type) {
+ _$this;
+ super.type = type;
+ }
+
+ @override
+ bool get required {
+ _$this;
+ return super.required;
+ }
+
+ @override
+ set required(bool required) {
+ _$this;
+ super.required = required;
+ }
+
+ @override
+ bool get covariant {
+ _$this;
+ return super.covariant;
+ }
+
+ @override
+ set covariant(bool covariant) {
+ _$this;
+ super.covariant = covariant;
+ }
+
+ _$ParameterBuilder() : super._();
+
+ ParameterBuilder get _$this {
+ final $v = _$v;
+ if ($v != null) {
+ super.defaultTo = $v.defaultTo;
+ super.name = $v.name;
+ super.named = $v.named;
+ super.toThis = $v.toThis;
+ super.toSuper = $v.toSuper;
+ super.annotations = $v.annotations.toBuilder();
+ super.docs = $v.docs.toBuilder();
+ super.types = $v.types.toBuilder();
+ super.type = $v.type;
+ super.required = $v.required;
+ super.covariant = $v.covariant;
+ _$v = null;
+ }
+ return this;
+ }
+
+ @override
+ void replace(Parameter other) {
+ ArgumentError.checkNotNull(other, 'other');
+ _$v = other as _$Parameter;
+ }
+
+ @override
+ void update(void Function(ParameterBuilder)? updates) {
+ if (updates != null) updates(this);
+ }
+
+ @override
+ Parameter build() => _build();
+
+ _$Parameter _build() {
+ _$Parameter _$result;
+ try {
+ _$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'));
+ } catch (_) {
+ late String _$failedField;
+ try {
+ _$failedField = 'annotations';
+ annotations.build();
+ _$failedField = 'docs';
+ docs.build();
+ _$failedField = 'types';
+ types.build();
+ } catch (e) {
+ throw new BuiltValueNestedFieldError(
+ r'Parameter', _$failedField, e.toString());
+ }
+ rethrow;
+ }
+ replace(_$result);
+ return _$result;
+ }
+}
+
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/pkgs/code_builder/lib/src/specs/mixin.dart b/pkgs/code_builder/lib/src/specs/mixin.dart
new file mode 100644
index 0000000..e666bdd
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/mixin.dart
@@ -0,0 +1,88 @@
+// Copyright (c) 2021, 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:built_collection/built_collection.dart';
+import 'package:built_value/built_value.dart';
+import 'package:meta/meta.dart';
+
+import '../base.dart';
+import '../mixins/annotations.dart';
+import '../mixins/dartdoc.dart';
+import '../mixins/generics.dart';
+import '../visitors.dart';
+import 'expression.dart';
+import 'field.dart';
+import 'method.dart';
+import 'reference.dart';
+
+part 'mixin.g.dart';
+
+@immutable
+abstract class Mixin extends Object
+ with HasAnnotations, HasDartDocs, HasGenerics
+ implements Built<Mixin, MixinBuilder>, Spec {
+ factory Mixin([void Function(MixinBuilder b) updates]) = _$Mixin;
+
+ Mixin._();
+
+ /// Whether the mixin is a `base mixin`.
+ bool get base;
+
+ @override
+ BuiltList<Expression> get annotations;
+
+ @override
+ BuiltList<String> get docs;
+
+ Reference? get on;
+
+ BuiltList<Reference> get implements;
+
+ @override
+ BuiltList<Reference> get types;
+
+ BuiltList<Method> get methods;
+ BuiltList<Field> get fields;
+
+ /// Name of the mixin.
+ String get name;
+
+ @override
+ R accept<R>(
+ SpecVisitor<R> visitor, [
+ R? context,
+ ]) =>
+ visitor.visitMixin(this, context);
+}
+
+abstract class MixinBuilder extends Object
+ with HasAnnotationsBuilder, HasDartDocsBuilder, HasGenericsBuilder
+ implements Builder<Mixin, MixinBuilder> {
+ factory MixinBuilder() = _$MixinBuilder;
+
+ MixinBuilder._();
+
+ /// Whether the mixin is a `base mixin`.
+ bool base = false;
+
+ @override
+ ListBuilder<Expression> annotations = ListBuilder<Expression>();
+
+ @override
+ ListBuilder<String> docs = ListBuilder<String>();
+
+ Reference? on;
+
+ ListBuilder<Reference> implements = ListBuilder<Reference>();
+
+ @override
+ ListBuilder<Reference> types = ListBuilder<Reference>();
+
+ ListBuilder<Method> methods = ListBuilder<Method>();
+
+ ListBuilder<Field> fields = ListBuilder<Field>();
+
+ /// Name of the mixin.
+ String? name;
+}
diff --git a/pkgs/code_builder/lib/src/specs/mixin.g.dart b/pkgs/code_builder/lib/src/specs/mixin.g.dart
new file mode 100644
index 0000000..28c7356
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/mixin.g.dart
@@ -0,0 +1,294 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'mixin.dart';
+
+// **************************************************************************
+// BuiltValueGenerator
+// **************************************************************************
+
+class _$Mixin extends Mixin {
+ @override
+ final bool base;
+ @override
+ final BuiltList<Expression> annotations;
+ @override
+ final BuiltList<String> docs;
+ @override
+ final Reference? on;
+ @override
+ final BuiltList<Reference> implements;
+ @override
+ final BuiltList<Reference> types;
+ @override
+ final BuiltList<Method> methods;
+ @override
+ final BuiltList<Field> fields;
+ @override
+ final String name;
+
+ 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._() {
+ BuiltValueNullFieldError.checkNotNull(base, r'Mixin', 'base');
+ BuiltValueNullFieldError.checkNotNull(annotations, r'Mixin', 'annotations');
+ BuiltValueNullFieldError.checkNotNull(docs, r'Mixin', 'docs');
+ BuiltValueNullFieldError.checkNotNull(implements, r'Mixin', 'implements');
+ BuiltValueNullFieldError.checkNotNull(types, r'Mixin', 'types');
+ BuiltValueNullFieldError.checkNotNull(methods, r'Mixin', 'methods');
+ BuiltValueNullFieldError.checkNotNull(fields, r'Mixin', 'fields');
+ BuiltValueNullFieldError.checkNotNull(name, r'Mixin', 'name');
+ }
+
+ @override
+ Mixin rebuild(void Function(MixinBuilder) updates) =>
+ (toBuilder()..update(updates)).build();
+
+ @override
+ _$MixinBuilder toBuilder() => new _$MixinBuilder()..replace(this);
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(other, this)) return true;
+ return other is Mixin &&
+ base == other.base &&
+ annotations == other.annotations &&
+ docs == other.docs &&
+ on == other.on &&
+ implements == other.implements &&
+ types == other.types &&
+ methods == other.methods &&
+ fields == other.fields &&
+ name == other.name;
+ }
+
+ @override
+ int get hashCode {
+ var _$hash = 0;
+ _$hash = $jc(_$hash, base.hashCode);
+ _$hash = $jc(_$hash, annotations.hashCode);
+ _$hash = $jc(_$hash, docs.hashCode);
+ _$hash = $jc(_$hash, on.hashCode);
+ _$hash = $jc(_$hash, implements.hashCode);
+ _$hash = $jc(_$hash, types.hashCode);
+ _$hash = $jc(_$hash, methods.hashCode);
+ _$hash = $jc(_$hash, fields.hashCode);
+ _$hash = $jc(_$hash, name.hashCode);
+ _$hash = $jf(_$hash);
+ return _$hash;
+ }
+
+ @override
+ String toString() {
+ return (newBuiltValueToStringHelper(r'Mixin')
+ ..add('base', base)
+ ..add('annotations', annotations)
+ ..add('docs', docs)
+ ..add('on', on)
+ ..add('implements', implements)
+ ..add('types', types)
+ ..add('methods', methods)
+ ..add('fields', fields)
+ ..add('name', name))
+ .toString();
+ }
+}
+
+class _$MixinBuilder extends MixinBuilder {
+ _$Mixin? _$v;
+
+ @override
+ bool get base {
+ _$this;
+ return super.base;
+ }
+
+ @override
+ set base(bool base) {
+ _$this;
+ super.base = base;
+ }
+
+ @override
+ ListBuilder<Expression> get annotations {
+ _$this;
+ return super.annotations;
+ }
+
+ @override
+ set annotations(ListBuilder<Expression> annotations) {
+ _$this;
+ super.annotations = annotations;
+ }
+
+ @override
+ ListBuilder<String> get docs {
+ _$this;
+ return super.docs;
+ }
+
+ @override
+ set docs(ListBuilder<String> docs) {
+ _$this;
+ super.docs = docs;
+ }
+
+ @override
+ Reference? get on {
+ _$this;
+ return super.on;
+ }
+
+ @override
+ set on(Reference? on) {
+ _$this;
+ super.on = on;
+ }
+
+ @override
+ ListBuilder<Reference> get implements {
+ _$this;
+ return super.implements;
+ }
+
+ @override
+ set implements(ListBuilder<Reference> implements) {
+ _$this;
+ super.implements = implements;
+ }
+
+ @override
+ ListBuilder<Reference> get types {
+ _$this;
+ return super.types;
+ }
+
+ @override
+ set types(ListBuilder<Reference> types) {
+ _$this;
+ super.types = types;
+ }
+
+ @override
+ ListBuilder<Method> get methods {
+ _$this;
+ return super.methods;
+ }
+
+ @override
+ set methods(ListBuilder<Method> methods) {
+ _$this;
+ super.methods = methods;
+ }
+
+ @override
+ ListBuilder<Field> get fields {
+ _$this;
+ return super.fields;
+ }
+
+ @override
+ set fields(ListBuilder<Field> fields) {
+ _$this;
+ super.fields = fields;
+ }
+
+ @override
+ String? get name {
+ _$this;
+ return super.name;
+ }
+
+ @override
+ set name(String? name) {
+ _$this;
+ super.name = name;
+ }
+
+ _$MixinBuilder() : super._();
+
+ MixinBuilder get _$this {
+ final $v = _$v;
+ if ($v != null) {
+ super.base = $v.base;
+ super.annotations = $v.annotations.toBuilder();
+ super.docs = $v.docs.toBuilder();
+ super.on = $v.on;
+ super.implements = $v.implements.toBuilder();
+ super.types = $v.types.toBuilder();
+ super.methods = $v.methods.toBuilder();
+ super.fields = $v.fields.toBuilder();
+ super.name = $v.name;
+ _$v = null;
+ }
+ return this;
+ }
+
+ @override
+ void replace(Mixin other) {
+ ArgumentError.checkNotNull(other, 'other');
+ _$v = other as _$Mixin;
+ }
+
+ @override
+ void update(void Function(MixinBuilder)? updates) {
+ if (updates != null) updates(this);
+ }
+
+ @override
+ Mixin build() => _build();
+
+ _$Mixin _build() {
+ _$Mixin _$result;
+ try {
+ _$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'));
+ } catch (_) {
+ late String _$failedField;
+ try {
+ _$failedField = 'annotations';
+ annotations.build();
+ _$failedField = 'docs';
+ docs.build();
+
+ _$failedField = 'implements';
+ implements.build();
+ _$failedField = 'types';
+ types.build();
+ _$failedField = 'methods';
+ methods.build();
+ _$failedField = 'fields';
+ fields.build();
+ } catch (e) {
+ throw new BuiltValueNestedFieldError(
+ r'Mixin', _$failedField, e.toString());
+ }
+ rethrow;
+ }
+ replace(_$result);
+ return _$result;
+ }
+}
+
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/pkgs/code_builder/lib/src/specs/reference.dart b/pkgs/code_builder/lib/src/specs/reference.dart
new file mode 100644
index 0000000..06032ad
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/reference.dart
@@ -0,0 +1,120 @@
+// Copyright (c) 2017, 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:built_value/built_value.dart';
+import 'package:meta/meta.dart';
+
+import '../base.dart';
+import '../visitors.dart';
+import 'code.dart';
+import 'expression.dart';
+import 'type_reference.dart';
+
+/// Short-hand for `Reference(symbol, url)`.
+Reference refer(String symbol, [String? url]) => Reference(symbol, url);
+
+/// A reference to [symbol], such as a class, or top-level method or field.
+///
+/// References can be collected and collated in order to automatically generate
+/// `import` statements for all used symbols.
+@immutable
+class Reference extends Expression implements Spec {
+ /// Relative, `package:` or `dart:` URL of the library.
+ ///
+ /// May be omitted (`null`) in order to express "same library".
+ final String? url;
+
+ /// Name of the class, method, or field.
+ ///
+ /// May be `null` for references without symbols, for instance a function type
+ /// has no symbol.
+ final String? symbol;
+
+ /// Create a reference to [symbol] in [url].
+ const Reference(this.symbol, [this.url]);
+
+ @override
+ R accept<R>(
+ SpecVisitor<R> visitor, [
+ R? context,
+ ]) =>
+ visitor.visitReference(this, context);
+
+ @override
+ int get hashCode => '$url#$symbol'.hashCode;
+
+ @override
+ bool operator ==(Object other) =>
+ other is Reference && other.url == url && other.symbol == symbol;
+
+ /// Returns a new instance of this expression.
+ Expression newInstance(
+ Iterable<Expression> positionalArguments, [
+ Map<String, Expression> namedArguments = const {},
+ List<Reference> typeArguments = const [],
+ ]) =>
+ InvokeExpression.newOf(
+ this,
+ positionalArguments.toList(),
+ namedArguments,
+ typeArguments,
+ );
+
+ /// Returns a new instance of this expression with a named constructor.
+ Expression newInstanceNamed(
+ String name,
+ Iterable<Expression> positionalArguments, [
+ Map<String, Expression> namedArguments = const {},
+ List<Reference> typeArguments = const [],
+ ]) =>
+ InvokeExpression.newOf(
+ this,
+ positionalArguments.toList(),
+ namedArguments,
+ typeArguments,
+ name,
+ );
+
+ /// Returns a const instance of this expression.
+ Expression constInstance(
+ Iterable<Expression> positionalArguments, [
+ Map<String, Expression> namedArguments = const {},
+ List<Reference> typeArguments = const [],
+ ]) =>
+ InvokeExpression.constOf(
+ this,
+ positionalArguments.toList(),
+ namedArguments,
+ typeArguments,
+ );
+
+ /// Returns a const instance of this expression with a named constructor.
+ Expression constInstanceNamed(
+ String name,
+ Iterable<Expression> positionalArguments, [
+ Map<String, Expression> namedArguments = const {},
+ List<Reference> typeArguments = const [],
+ ]) =>
+ 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();
+
+ /// Returns as a [TypeReference], which allows adding generic type parameters.
+ 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
new file mode 100644
index 0000000..be7c3ea
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/type_function.dart
@@ -0,0 +1,133 @@
+// Copyright (c) 2017, 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:built_collection/built_collection.dart';
+import 'package:built_value/built_value.dart';
+import 'package:meta/meta.dart';
+
+import '../base.dart';
+import '../mixins/generics.dart';
+import '../visitors.dart';
+import 'code.dart';
+import 'expression.dart';
+import 'reference.dart';
+
+part 'type_function.g.dart';
+
+@immutable
+abstract class FunctionType extends Expression
+ with HasGenerics
+ implements Built<FunctionType, FunctionTypeBuilder>, Reference, Spec {
+ factory FunctionType([
+ void Function(FunctionTypeBuilder) updates,
+ ]) = _$FunctionType;
+
+ FunctionType._();
+
+ @override
+ R accept<R>(
+ SpecVisitor<R> visitor, [
+ R? context,
+ ]) =>
+ visitor.visitFunctionType(this, context);
+
+ /// Return type.
+ Reference? get returnType;
+
+ @override
+ BuiltList<Reference> get types;
+
+ /// Required positional parameters of this function type.
+ BuiltList<Reference> get requiredParameters;
+
+ /// Optional positional parameters of this function type.
+ BuiltList<Reference> get optionalParameters;
+
+ /// Named optional parameters of this function type.
+ BuiltMap<String, Reference> get namedParameters;
+
+ /// Named required parameters of this function type.
+ BuiltMap<String, Reference> get namedRequiredParameters;
+
+ @override
+ String? get url => null;
+
+ @override
+ String? get symbol => null;
+
+ @override
+ Reference get type => this;
+
+ /// Optional nullability.
+ ///
+ /// An emitter may ignore this if the output is not targeting a Dart language
+ /// version that supports null safety.
+ bool? get isNullable;
+
+ @override
+ Expression newInstance(
+ Iterable<Expression> positionalArguments, [
+ Map<String, Expression> namedArguments = const {},
+ List<Reference> typeArguments = const [],
+ ]) =>
+ throw UnsupportedError('Cannot instantiate a function type.');
+
+ @override
+ Expression newInstanceNamed(
+ String name,
+ Iterable<Expression> positionalArguments, [
+ Map<String, Expression> namedArguments = const {},
+ List<Reference> typeArguments = const [],
+ ]) =>
+ throw UnsupportedError('Cannot instantiate a function type.');
+
+ @override
+ Expression constInstance(
+ Iterable<Expression> positionalArguments, [
+ Map<String, Expression> namedArguments = const {},
+ List<Reference> typeArguments = const [],
+ ]) =>
+ throw UnsupportedError('Cannot "const" a function type.');
+
+ @override
+ Expression constInstanceNamed(
+ String name,
+ Iterable<Expression> positionalArguments, [
+ Map<String, Expression> namedArguments = const {},
+ List<Reference> typeArguments = const [],
+ ]) =>
+ throw UnsupportedError('Cannot "const" a function type.');
+
+ /// A typedef assignment to this type.
+ Code toTypeDef(String name) => createTypeDef(name, this);
+}
+
+abstract class FunctionTypeBuilder extends Object
+ with HasGenericsBuilder
+ implements Builder<FunctionType, FunctionTypeBuilder> {
+ factory FunctionTypeBuilder() = _$FunctionTypeBuilder;
+
+ FunctionTypeBuilder._();
+
+ Reference? returnType;
+
+ @override
+ ListBuilder<Reference> types = ListBuilder<Reference>();
+
+ ListBuilder<Reference> requiredParameters = ListBuilder<Reference>();
+
+ ListBuilder<Reference> optionalParameters = ListBuilder<Reference>();
+
+ MapBuilder<String, Reference> namedParameters =
+ MapBuilder<String, Reference>();
+
+ MapBuilder<String, Reference> namedRequiredParameters =
+ MapBuilder<String, Reference>();
+
+ bool? isNullable;
+
+ String? url;
+
+ String? symbol;
+}
diff --git a/pkgs/code_builder/lib/src/specs/type_function.g.dart b/pkgs/code_builder/lib/src/specs/type_function.g.dart
new file mode 100644
index 0000000..d09f59b
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/type_function.g.dart
@@ -0,0 +1,252 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'type_function.dart';
+
+// **************************************************************************
+// BuiltValueGenerator
+// **************************************************************************
+
+class _$FunctionType extends FunctionType {
+ @override
+ final Reference? returnType;
+ @override
+ final BuiltList<Reference> types;
+ @override
+ final BuiltList<Reference> requiredParameters;
+ @override
+ final BuiltList<Reference> optionalParameters;
+ @override
+ final BuiltMap<String, Reference> namedParameters;
+ @override
+ final BuiltMap<String, Reference> namedRequiredParameters;
+ @override
+ final bool? isNullable;
+
+ 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._() {
+ BuiltValueNullFieldError.checkNotNull(types, r'FunctionType', 'types');
+ BuiltValueNullFieldError.checkNotNull(
+ requiredParameters, r'FunctionType', 'requiredParameters');
+ BuiltValueNullFieldError.checkNotNull(
+ optionalParameters, r'FunctionType', 'optionalParameters');
+ BuiltValueNullFieldError.checkNotNull(
+ namedParameters, r'FunctionType', 'namedParameters');
+ BuiltValueNullFieldError.checkNotNull(
+ namedRequiredParameters, r'FunctionType', 'namedRequiredParameters');
+ }
+
+ @override
+ FunctionType rebuild(void Function(FunctionTypeBuilder) updates) =>
+ (toBuilder()..update(updates)).build();
+
+ @override
+ _$FunctionTypeBuilder toBuilder() =>
+ new _$FunctionTypeBuilder()..replace(this);
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(other, this)) return true;
+ return other is FunctionType &&
+ returnType == other.returnType &&
+ types == other.types &&
+ requiredParameters == other.requiredParameters &&
+ optionalParameters == other.optionalParameters &&
+ namedParameters == other.namedParameters &&
+ namedRequiredParameters == other.namedRequiredParameters &&
+ isNullable == other.isNullable;
+ }
+
+ @override
+ int get hashCode {
+ var _$hash = 0;
+ _$hash = $jc(_$hash, returnType.hashCode);
+ _$hash = $jc(_$hash, types.hashCode);
+ _$hash = $jc(_$hash, requiredParameters.hashCode);
+ _$hash = $jc(_$hash, optionalParameters.hashCode);
+ _$hash = $jc(_$hash, namedParameters.hashCode);
+ _$hash = $jc(_$hash, namedRequiredParameters.hashCode);
+ _$hash = $jc(_$hash, isNullable.hashCode);
+ _$hash = $jf(_$hash);
+ return _$hash;
+ }
+
+ @override
+ String toString() {
+ return (newBuiltValueToStringHelper(r'FunctionType')
+ ..add('returnType', returnType)
+ ..add('types', types)
+ ..add('requiredParameters', requiredParameters)
+ ..add('optionalParameters', optionalParameters)
+ ..add('namedParameters', namedParameters)
+ ..add('namedRequiredParameters', namedRequiredParameters)
+ ..add('isNullable', isNullable))
+ .toString();
+ }
+}
+
+class _$FunctionTypeBuilder extends FunctionTypeBuilder {
+ _$FunctionType? _$v;
+
+ @override
+ Reference? get returnType {
+ _$this;
+ return super.returnType;
+ }
+
+ @override
+ set returnType(Reference? returnType) {
+ _$this;
+ super.returnType = returnType;
+ }
+
+ @override
+ ListBuilder<Reference> get types {
+ _$this;
+ return super.types;
+ }
+
+ @override
+ set types(ListBuilder<Reference> types) {
+ _$this;
+ super.types = types;
+ }
+
+ @override
+ ListBuilder<Reference> get requiredParameters {
+ _$this;
+ return super.requiredParameters;
+ }
+
+ @override
+ set requiredParameters(ListBuilder<Reference> requiredParameters) {
+ _$this;
+ super.requiredParameters = requiredParameters;
+ }
+
+ @override
+ ListBuilder<Reference> get optionalParameters {
+ _$this;
+ return super.optionalParameters;
+ }
+
+ @override
+ set optionalParameters(ListBuilder<Reference> optionalParameters) {
+ _$this;
+ super.optionalParameters = optionalParameters;
+ }
+
+ @override
+ MapBuilder<String, Reference> get namedParameters {
+ _$this;
+ return super.namedParameters;
+ }
+
+ @override
+ set namedParameters(MapBuilder<String, Reference> namedParameters) {
+ _$this;
+ super.namedParameters = namedParameters;
+ }
+
+ @override
+ MapBuilder<String, Reference> get namedRequiredParameters {
+ _$this;
+ return super.namedRequiredParameters;
+ }
+
+ @override
+ set namedRequiredParameters(
+ MapBuilder<String, Reference> namedRequiredParameters) {
+ _$this;
+ super.namedRequiredParameters = namedRequiredParameters;
+ }
+
+ @override
+ bool? get isNullable {
+ _$this;
+ return super.isNullable;
+ }
+
+ @override
+ set isNullable(bool? isNullable) {
+ _$this;
+ super.isNullable = isNullable;
+ }
+
+ _$FunctionTypeBuilder() : super._();
+
+ FunctionTypeBuilder get _$this {
+ final $v = _$v;
+ if ($v != null) {
+ super.returnType = $v.returnType;
+ super.types = $v.types.toBuilder();
+ super.requiredParameters = $v.requiredParameters.toBuilder();
+ super.optionalParameters = $v.optionalParameters.toBuilder();
+ super.namedParameters = $v.namedParameters.toBuilder();
+ super.namedRequiredParameters = $v.namedRequiredParameters.toBuilder();
+ super.isNullable = $v.isNullable;
+ _$v = null;
+ }
+ return this;
+ }
+
+ @override
+ void replace(FunctionType other) {
+ ArgumentError.checkNotNull(other, 'other');
+ _$v = other as _$FunctionType;
+ }
+
+ @override
+ void update(void Function(FunctionTypeBuilder)? updates) {
+ if (updates != null) updates(this);
+ }
+
+ @override
+ FunctionType build() => _build();
+
+ _$FunctionType _build() {
+ _$FunctionType _$result;
+ try {
+ _$result = _$v ??
+ new _$FunctionType._(
+ returnType: returnType,
+ types: types.build(),
+ requiredParameters: requiredParameters.build(),
+ optionalParameters: optionalParameters.build(),
+ namedParameters: namedParameters.build(),
+ namedRequiredParameters: namedRequiredParameters.build(),
+ isNullable: isNullable);
+ } catch (_) {
+ late String _$failedField;
+ try {
+ _$failedField = 'types';
+ types.build();
+ _$failedField = 'requiredParameters';
+ requiredParameters.build();
+ _$failedField = 'optionalParameters';
+ optionalParameters.build();
+ _$failedField = 'namedParameters';
+ namedParameters.build();
+ _$failedField = 'namedRequiredParameters';
+ namedRequiredParameters.build();
+ } catch (e) {
+ throw new BuiltValueNestedFieldError(
+ r'FunctionType', _$failedField, e.toString());
+ }
+ rethrow;
+ }
+ replace(_$result);
+ return _$result;
+ }
+}
+
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/pkgs/code_builder/lib/src/specs/type_record.dart b/pkgs/code_builder/lib/src/specs/type_record.dart
new file mode 100644
index 0000000..2f0594c
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/type_record.dart
@@ -0,0 +1,99 @@
+// Copyright (c) 2023, 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:built_collection/built_collection.dart';
+import 'package:built_value/built_value.dart';
+import 'package:meta/meta.dart';
+
+import '../base.dart';
+import '../visitors.dart';
+import 'expression.dart';
+import 'reference.dart';
+
+part 'type_record.g.dart';
+
+@immutable
+abstract class RecordType extends Expression
+ implements Built<RecordType, RecordTypeBuilder>, Reference, Spec {
+ factory RecordType([
+ void Function(RecordTypeBuilder) updates,
+ ]) = _$RecordType;
+
+ RecordType._();
+
+ @override
+ R accept<R>(
+ SpecVisitor<R> visitor, [
+ R? context,
+ ]) =>
+ visitor.visitRecordType(this, context);
+
+ BuiltList<Reference> get positionalFieldTypes;
+
+ BuiltMap<String, Reference> get namedFieldTypes;
+
+ @override
+ String? get url => null;
+
+ @override
+ String? get symbol => null;
+
+ @override
+ Reference get type => this;
+
+ /// Optional nullability.
+ bool? get isNullable;
+
+ @override
+ Expression newInstance(
+ Iterable<Expression> positionalArguments, [
+ Map<String, Expression> namedArguments = const {},
+ List<Reference> typeArguments = const [],
+ ]) =>
+ throw UnsupportedError('Cannot instantiate a record type.');
+
+ @override
+ Expression newInstanceNamed(
+ String name,
+ Iterable<Expression> positionalArguments, [
+ Map<String, Expression> namedArguments = const {},
+ List<Reference> typeArguments = const [],
+ ]) =>
+ throw UnsupportedError('Cannot instantiate a record type.');
+
+ @override
+ Expression constInstance(
+ Iterable<Expression> positionalArguments, [
+ Map<String, Expression> namedArguments = const {},
+ List<Reference> typeArguments = const [],
+ ]) =>
+ throw UnsupportedError('Cannot "const" a record type.');
+
+ @override
+ Expression constInstanceNamed(
+ String name,
+ Iterable<Expression> positionalArguments, [
+ Map<String, Expression> namedArguments = const {},
+ List<Reference> typeArguments = const [],
+ ]) =>
+ throw UnsupportedError('Cannot "const" a record type.');
+}
+
+abstract class RecordTypeBuilder extends Object
+ implements Builder<RecordType, RecordTypeBuilder> {
+ factory RecordTypeBuilder() = _$RecordTypeBuilder;
+
+ RecordTypeBuilder._();
+
+ ListBuilder<Reference> positionalFieldTypes = ListBuilder<Reference>();
+
+ MapBuilder<String, Reference> namedFieldTypes =
+ MapBuilder<String, Reference>();
+
+ bool? isNullable;
+
+ String? url;
+
+ String? symbol;
+}
diff --git a/pkgs/code_builder/lib/src/specs/type_record.g.dart b/pkgs/code_builder/lib/src/specs/type_record.g.dart
new file mode 100644
index 0000000..b1d47df
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/type_record.g.dart
@@ -0,0 +1,159 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'type_record.dart';
+
+// **************************************************************************
+// BuiltValueGenerator
+// **************************************************************************
+
+class _$RecordType extends RecordType {
+ @override
+ final BuiltList<Reference> positionalFieldTypes;
+ @override
+ final BuiltMap<String, Reference> namedFieldTypes;
+ @override
+ final bool? isNullable;
+
+ factory _$RecordType([void Function(RecordTypeBuilder)? updates]) =>
+ (new RecordTypeBuilder()..update(updates)).build() as _$RecordType;
+
+ _$RecordType._(
+ {required this.positionalFieldTypes,
+ required this.namedFieldTypes,
+ this.isNullable})
+ : super._() {
+ BuiltValueNullFieldError.checkNotNull(
+ positionalFieldTypes, r'RecordType', 'positionalFieldTypes');
+ BuiltValueNullFieldError.checkNotNull(
+ namedFieldTypes, r'RecordType', 'namedFieldTypes');
+ }
+
+ @override
+ RecordType rebuild(void Function(RecordTypeBuilder) updates) =>
+ (toBuilder()..update(updates)).build();
+
+ @override
+ _$RecordTypeBuilder toBuilder() => new _$RecordTypeBuilder()..replace(this);
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(other, this)) return true;
+ return other is RecordType &&
+ positionalFieldTypes == other.positionalFieldTypes &&
+ namedFieldTypes == other.namedFieldTypes &&
+ isNullable == other.isNullable;
+ }
+
+ @override
+ int get hashCode {
+ var _$hash = 0;
+ _$hash = $jc(_$hash, positionalFieldTypes.hashCode);
+ _$hash = $jc(_$hash, namedFieldTypes.hashCode);
+ _$hash = $jc(_$hash, isNullable.hashCode);
+ _$hash = $jf(_$hash);
+ return _$hash;
+ }
+
+ @override
+ String toString() {
+ return (newBuiltValueToStringHelper(r'RecordType')
+ ..add('positionalFieldTypes', positionalFieldTypes)
+ ..add('namedFieldTypes', namedFieldTypes)
+ ..add('isNullable', isNullable))
+ .toString();
+ }
+}
+
+class _$RecordTypeBuilder extends RecordTypeBuilder {
+ _$RecordType? _$v;
+
+ @override
+ ListBuilder<Reference> get positionalFieldTypes {
+ _$this;
+ return super.positionalFieldTypes;
+ }
+
+ @override
+ set positionalFieldTypes(ListBuilder<Reference> positionalFieldTypes) {
+ _$this;
+ super.positionalFieldTypes = positionalFieldTypes;
+ }
+
+ @override
+ MapBuilder<String, Reference> get namedFieldTypes {
+ _$this;
+ return super.namedFieldTypes;
+ }
+
+ @override
+ set namedFieldTypes(MapBuilder<String, Reference> namedFieldTypes) {
+ _$this;
+ super.namedFieldTypes = namedFieldTypes;
+ }
+
+ @override
+ bool? get isNullable {
+ _$this;
+ return super.isNullable;
+ }
+
+ @override
+ set isNullable(bool? isNullable) {
+ _$this;
+ super.isNullable = isNullable;
+ }
+
+ _$RecordTypeBuilder() : super._();
+
+ RecordTypeBuilder get _$this {
+ final $v = _$v;
+ if ($v != null) {
+ super.positionalFieldTypes = $v.positionalFieldTypes.toBuilder();
+ super.namedFieldTypes = $v.namedFieldTypes.toBuilder();
+ super.isNullable = $v.isNullable;
+ _$v = null;
+ }
+ return this;
+ }
+
+ @override
+ void replace(RecordType other) {
+ ArgumentError.checkNotNull(other, 'other');
+ _$v = other as _$RecordType;
+ }
+
+ @override
+ void update(void Function(RecordTypeBuilder)? updates) {
+ if (updates != null) updates(this);
+ }
+
+ @override
+ RecordType build() => _build();
+
+ _$RecordType _build() {
+ _$RecordType _$result;
+ try {
+ _$result = _$v ??
+ new _$RecordType._(
+ positionalFieldTypes: positionalFieldTypes.build(),
+ namedFieldTypes: namedFieldTypes.build(),
+ isNullable: isNullable);
+ } catch (_) {
+ late String _$failedField;
+ try {
+ _$failedField = 'positionalFieldTypes';
+ positionalFieldTypes.build();
+ _$failedField = 'namedFieldTypes';
+ namedFieldTypes.build();
+ } catch (e) {
+ throw new BuiltValueNestedFieldError(
+ r'RecordType', _$failedField, e.toString());
+ }
+ rethrow;
+ }
+ replace(_$result);
+ return _$result;
+ }
+}
+
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/pkgs/code_builder/lib/src/specs/type_reference.dart b/pkgs/code_builder/lib/src/specs/type_reference.dart
new file mode 100644
index 0000000..74c7840
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/type_reference.dart
@@ -0,0 +1,138 @@
+// Copyright (c) 2017, 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:built_collection/built_collection.dart';
+import 'package:built_value/built_value.dart';
+import 'package:meta/meta.dart';
+
+import '../base.dart';
+import '../mixins/generics.dart';
+import '../visitors.dart';
+import 'code.dart';
+import 'expression.dart';
+import 'reference.dart';
+
+part 'type_reference.g.dart';
+
+@immutable
+abstract class TypeReference extends Expression
+ with HasGenerics
+ implements Built<TypeReference, TypeReferenceBuilder>, Reference, Spec {
+ factory TypeReference([
+ void Function(TypeReferenceBuilder) updates,
+ ]) = _$TypeReference;
+
+ TypeReference._();
+
+ @override
+ String get symbol;
+
+ @override
+ String? get url;
+
+ /// Optional bound generic.
+ Reference? get bound;
+
+ @override
+ BuiltList<Reference> get types;
+
+ /// Optional nullability.
+ ///
+ /// An emitter may ignore this if the output is not targeting a Dart language
+ /// version that supports null safety.
+ bool? get isNullable;
+
+ @override
+ R accept<R>(
+ SpecVisitor<R> visitor, [
+ R? context,
+ ]) =>
+ visitor.visitType(this, context);
+
+ @override
+ Expression get expression => CodeExpression(Code.scope((a) => a(this)));
+
+ @override
+ TypeReference get type => this;
+
+ @override
+ Expression newInstance(
+ Iterable<Expression> positionalArguments, [
+ Map<String, Expression> namedArguments = const {},
+ List<Reference> typeArguments = const [],
+ ]) =>
+ InvokeExpression.newOf(
+ this,
+ positionalArguments.toList(),
+ namedArguments,
+ typeArguments,
+ );
+
+ @override
+ Expression newInstanceNamed(
+ String name,
+ Iterable<Expression> positionalArguments, [
+ Map<String, Expression> namedArguments = const {},
+ List<Reference> typeArguments = const [],
+ ]) =>
+ InvokeExpression.newOf(
+ this,
+ positionalArguments.toList(),
+ namedArguments,
+ typeArguments,
+ name,
+ );
+
+ @override
+ Expression constInstance(
+ Iterable<Expression> positionalArguments, [
+ Map<String, Expression> namedArguments = const {},
+ List<Reference> typeArguments = const [],
+ ]) =>
+ InvokeExpression.constOf(
+ this,
+ positionalArguments.toList(),
+ namedArguments,
+ typeArguments,
+ );
+
+ @override
+ Expression constInstanceNamed(
+ String name,
+ Iterable<Expression> positionalArguments, [
+ Map<String, Expression> namedArguments = const {},
+ List<Reference> typeArguments = const [],
+ ]) =>
+ InvokeExpression.constOf(
+ this,
+ positionalArguments.toList(),
+ namedArguments,
+ typeArguments,
+ name,
+ );
+}
+
+abstract class TypeReferenceBuilder extends Object
+ with HasGenericsBuilder
+ implements Builder<TypeReference, TypeReferenceBuilder> {
+ factory TypeReferenceBuilder() = _$TypeReferenceBuilder;
+
+ TypeReferenceBuilder._();
+
+ String? symbol;
+
+ String? url;
+
+ /// Optional bound generic.
+ Reference? bound;
+
+ @override
+ ListBuilder<Reference> types = ListBuilder<Reference>();
+
+ /// Optional nullability.
+ ///
+ /// An emitter may ignore this if the output is not targeting a Dart language
+ /// version that supports null safety.
+ bool? isNullable;
+}
diff --git a/pkgs/code_builder/lib/src/specs/type_reference.g.dart b/pkgs/code_builder/lib/src/specs/type_reference.g.dart
new file mode 100644
index 0000000..124e8b4
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/type_reference.g.dart
@@ -0,0 +1,197 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'type_reference.dart';
+
+// **************************************************************************
+// BuiltValueGenerator
+// **************************************************************************
+
+class _$TypeReference extends TypeReference {
+ @override
+ final String symbol;
+ @override
+ final String? url;
+ @override
+ final Reference? bound;
+ @override
+ final BuiltList<Reference> types;
+ @override
+ final bool? isNullable;
+
+ 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._() {
+ BuiltValueNullFieldError.checkNotNull(symbol, r'TypeReference', 'symbol');
+ BuiltValueNullFieldError.checkNotNull(types, r'TypeReference', 'types');
+ }
+
+ @override
+ TypeReference rebuild(void Function(TypeReferenceBuilder) updates) =>
+ (toBuilder()..update(updates)).build();
+
+ @override
+ _$TypeReferenceBuilder toBuilder() =>
+ new _$TypeReferenceBuilder()..replace(this);
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(other, this)) return true;
+ return other is TypeReference &&
+ symbol == other.symbol &&
+ url == other.url &&
+ bound == other.bound &&
+ types == other.types &&
+ isNullable == other.isNullable;
+ }
+
+ @override
+ int get hashCode {
+ var _$hash = 0;
+ _$hash = $jc(_$hash, symbol.hashCode);
+ _$hash = $jc(_$hash, url.hashCode);
+ _$hash = $jc(_$hash, bound.hashCode);
+ _$hash = $jc(_$hash, types.hashCode);
+ _$hash = $jc(_$hash, isNullable.hashCode);
+ _$hash = $jf(_$hash);
+ return _$hash;
+ }
+
+ @override
+ String toString() {
+ return (newBuiltValueToStringHelper(r'TypeReference')
+ ..add('symbol', symbol)
+ ..add('url', url)
+ ..add('bound', bound)
+ ..add('types', types)
+ ..add('isNullable', isNullable))
+ .toString();
+ }
+}
+
+class _$TypeReferenceBuilder extends TypeReferenceBuilder {
+ _$TypeReference? _$v;
+
+ @override
+ String? get symbol {
+ _$this;
+ return super.symbol;
+ }
+
+ @override
+ set symbol(String? symbol) {
+ _$this;
+ super.symbol = symbol;
+ }
+
+ @override
+ String? get url {
+ _$this;
+ return super.url;
+ }
+
+ @override
+ set url(String? url) {
+ _$this;
+ super.url = url;
+ }
+
+ @override
+ Reference? get bound {
+ _$this;
+ return super.bound;
+ }
+
+ @override
+ set bound(Reference? bound) {
+ _$this;
+ super.bound = bound;
+ }
+
+ @override
+ ListBuilder<Reference> get types {
+ _$this;
+ return super.types;
+ }
+
+ @override
+ set types(ListBuilder<Reference> types) {
+ _$this;
+ super.types = types;
+ }
+
+ @override
+ bool? get isNullable {
+ _$this;
+ return super.isNullable;
+ }
+
+ @override
+ set isNullable(bool? isNullable) {
+ _$this;
+ super.isNullable = isNullable;
+ }
+
+ _$TypeReferenceBuilder() : super._();
+
+ TypeReferenceBuilder get _$this {
+ final $v = _$v;
+ if ($v != null) {
+ super.symbol = $v.symbol;
+ super.url = $v.url;
+ super.bound = $v.bound;
+ super.types = $v.types.toBuilder();
+ super.isNullable = $v.isNullable;
+ _$v = null;
+ }
+ return this;
+ }
+
+ @override
+ void replace(TypeReference other) {
+ ArgumentError.checkNotNull(other, 'other');
+ _$v = other as _$TypeReference;
+ }
+
+ @override
+ void update(void Function(TypeReferenceBuilder)? updates) {
+ if (updates != null) updates(this);
+ }
+
+ @override
+ TypeReference build() => _build();
+
+ _$TypeReference _build() {
+ _$TypeReference _$result;
+ try {
+ _$result = _$v ??
+ new _$TypeReference._(
+ symbol: BuiltValueNullFieldError.checkNotNull(
+ symbol, r'TypeReference', 'symbol'),
+ url: url,
+ bound: bound,
+ types: types.build(),
+ isNullable: isNullable);
+ } catch (_) {
+ late String _$failedField;
+ try {
+ _$failedField = 'types';
+ types.build();
+ } catch (e) {
+ throw new BuiltValueNestedFieldError(
+ r'TypeReference', _$failedField, e.toString());
+ }
+ rethrow;
+ }
+ replace(_$result);
+ return _$result;
+ }
+}
+
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/pkgs/code_builder/lib/src/specs/typedef.dart b/pkgs/code_builder/lib/src/specs/typedef.dart
new file mode 100644
index 0000000..fe5d8f1
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/typedef.dart
@@ -0,0 +1,62 @@
+// 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:built_collection/built_collection.dart';
+import 'package:built_value/built_value.dart';
+import 'package:meta/meta.dart';
+
+import '../base.dart';
+import '../mixins/annotations.dart';
+import '../mixins/dartdoc.dart';
+import '../mixins/generics.dart';
+import '../visitors.dart';
+import 'expression.dart';
+import 'reference.dart';
+
+part 'typedef.g.dart';
+
+@immutable
+abstract class TypeDef extends Object
+ with HasAnnotations, HasDartDocs, HasGenerics
+ implements Built<TypeDef, TypeDefBuilder>, Spec {
+ factory TypeDef([void Function(TypeDefBuilder)? updates]) = _$TypeDef;
+
+ TypeDef._();
+
+ /// Name of the typedef.
+ String get name;
+
+ /// The right hand side of the typedef.
+ ///
+ /// Typically a reference to a type, or a Function type.
+ Expression get definition;
+
+ @override
+ R accept<R>(
+ SpecVisitor<R> visitor, [
+ R? context,
+ ]) =>
+ visitor.visitTypeDef(this, context);
+}
+
+abstract class TypeDefBuilder extends Object
+ with HasAnnotationsBuilder, HasDartDocsBuilder, HasGenericsBuilder
+ implements Builder<TypeDef, TypeDefBuilder> {
+ factory TypeDefBuilder() = _$TypeDefBuilder;
+
+ TypeDefBuilder._();
+
+ @override
+ ListBuilder<Expression> annotations = ListBuilder<Expression>();
+
+ @override
+ ListBuilder<String> docs = ListBuilder<String>();
+
+ @override
+ ListBuilder<Reference> types = ListBuilder<Reference>();
+
+ String? name;
+
+ Expression? definition;
+}
diff --git a/pkgs/code_builder/lib/src/specs/typedef.g.dart b/pkgs/code_builder/lib/src/specs/typedef.g.dart
new file mode 100644
index 0000000..8c2a16c
--- /dev/null
+++ b/pkgs/code_builder/lib/src/specs/typedef.g.dart
@@ -0,0 +1,205 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'typedef.dart';
+
+// **************************************************************************
+// BuiltValueGenerator
+// **************************************************************************
+
+class _$TypeDef extends TypeDef {
+ @override
+ final String name;
+ @override
+ final Expression definition;
+ @override
+ final BuiltList<Expression> annotations;
+ @override
+ final BuiltList<String> docs;
+ @override
+ final BuiltList<Reference> types;
+
+ 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._() {
+ BuiltValueNullFieldError.checkNotNull(name, r'TypeDef', 'name');
+ BuiltValueNullFieldError.checkNotNull(definition, r'TypeDef', 'definition');
+ BuiltValueNullFieldError.checkNotNull(
+ annotations, r'TypeDef', 'annotations');
+ BuiltValueNullFieldError.checkNotNull(docs, r'TypeDef', 'docs');
+ BuiltValueNullFieldError.checkNotNull(types, r'TypeDef', 'types');
+ }
+
+ @override
+ TypeDef rebuild(void Function(TypeDefBuilder) updates) =>
+ (toBuilder()..update(updates)).build();
+
+ @override
+ _$TypeDefBuilder toBuilder() => new _$TypeDefBuilder()..replace(this);
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(other, this)) return true;
+ return other is TypeDef &&
+ name == other.name &&
+ definition == other.definition &&
+ annotations == other.annotations &&
+ docs == other.docs &&
+ types == other.types;
+ }
+
+ @override
+ int get hashCode {
+ var _$hash = 0;
+ _$hash = $jc(_$hash, name.hashCode);
+ _$hash = $jc(_$hash, definition.hashCode);
+ _$hash = $jc(_$hash, annotations.hashCode);
+ _$hash = $jc(_$hash, docs.hashCode);
+ _$hash = $jc(_$hash, types.hashCode);
+ _$hash = $jf(_$hash);
+ return _$hash;
+ }
+
+ @override
+ String toString() {
+ return (newBuiltValueToStringHelper(r'TypeDef')
+ ..add('name', name)
+ ..add('definition', definition)
+ ..add('annotations', annotations)
+ ..add('docs', docs)
+ ..add('types', types))
+ .toString();
+ }
+}
+
+class _$TypeDefBuilder extends TypeDefBuilder {
+ _$TypeDef? _$v;
+
+ @override
+ String? get name {
+ _$this;
+ return super.name;
+ }
+
+ @override
+ set name(String? name) {
+ _$this;
+ super.name = name;
+ }
+
+ @override
+ Expression? get definition {
+ _$this;
+ return super.definition;
+ }
+
+ @override
+ set definition(Expression? definition) {
+ _$this;
+ super.definition = definition;
+ }
+
+ @override
+ ListBuilder<Expression> get annotations {
+ _$this;
+ return super.annotations;
+ }
+
+ @override
+ set annotations(ListBuilder<Expression> annotations) {
+ _$this;
+ super.annotations = annotations;
+ }
+
+ @override
+ ListBuilder<String> get docs {
+ _$this;
+ return super.docs;
+ }
+
+ @override
+ set docs(ListBuilder<String> docs) {
+ _$this;
+ super.docs = docs;
+ }
+
+ @override
+ ListBuilder<Reference> get types {
+ _$this;
+ return super.types;
+ }
+
+ @override
+ set types(ListBuilder<Reference> types) {
+ _$this;
+ super.types = types;
+ }
+
+ _$TypeDefBuilder() : super._();
+
+ TypeDefBuilder get _$this {
+ final $v = _$v;
+ if ($v != null) {
+ super.name = $v.name;
+ super.definition = $v.definition;
+ super.annotations = $v.annotations.toBuilder();
+ super.docs = $v.docs.toBuilder();
+ super.types = $v.types.toBuilder();
+ _$v = null;
+ }
+ return this;
+ }
+
+ @override
+ void replace(TypeDef other) {
+ ArgumentError.checkNotNull(other, 'other');
+ _$v = other as _$TypeDef;
+ }
+
+ @override
+ void update(void Function(TypeDefBuilder)? updates) {
+ if (updates != null) updates(this);
+ }
+
+ @override
+ TypeDef build() => _build();
+
+ _$TypeDef _build() {
+ _$TypeDef _$result;
+ try {
+ _$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());
+ } catch (_) {
+ late String _$failedField;
+ try {
+ _$failedField = 'annotations';
+ annotations.build();
+ _$failedField = 'docs';
+ docs.build();
+ _$failedField = 'types';
+ types.build();
+ } catch (e) {
+ throw new BuiltValueNestedFieldError(
+ r'TypeDef', _$failedField, e.toString());
+ }
+ rethrow;
+ }
+ replace(_$result);
+ return _$result;
+ }
+}
+
+// ignore_for_file: deprecated_member_use_from_same_package,type=lint
diff --git a/pkgs/code_builder/lib/src/visitors.dart b/pkgs/code_builder/lib/src/visitors.dart
new file mode 100644
index 0000000..10fe0d8
--- /dev/null
+++ b/pkgs/code_builder/lib/src/visitors.dart
@@ -0,0 +1,64 @@
+// Copyright (c) 2017, 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:meta/meta.dart';
+
+import 'base.dart';
+import 'specs/class.dart';
+import 'specs/constructor.dart';
+import 'specs/directive.dart';
+import 'specs/enum.dart';
+import 'specs/expression.dart';
+import 'specs/extension.dart';
+import 'specs/extension_type.dart';
+import 'specs/field.dart';
+import 'specs/library.dart';
+import 'specs/method.dart';
+import 'specs/mixin.dart';
+import 'specs/reference.dart';
+import 'specs/type_function.dart';
+import 'specs/type_record.dart';
+import 'specs/type_reference.dart';
+import 'specs/typedef.dart';
+
+@optionalTypeArgs
+abstract class SpecVisitor<T> {
+ const SpecVisitor._();
+
+ T visitAnnotation(Expression spec, [T? context]);
+
+ T visitClass(Class spec, [T? context]);
+
+ T visitMixin(Mixin spec, [T? context]);
+
+ T visitExtension(Extension spec, [T? context]);
+
+ T visitExtensionType(ExtensionType spec, [T? context]);
+
+ T visitEnum(Enum spec, [T? context]);
+
+ T visitConstructor(Constructor spec, String clazz, [T? context]);
+
+ T visitDirective(Directive spec, [T? context]);
+
+ T visitField(Field spec, [T? context]);
+
+ T visitLibrary(Library spec, [T? context]);
+
+ T visitFunctionType(FunctionType spec, [T? context]);
+
+ T visitTypeDef(TypeDef spec, [T? context]);
+
+ T visitMethod(Method spec, [T? context]);
+
+ T visitRecordType(RecordType spec, [T? context]);
+
+ T visitReference(Reference spec, [T? context]);
+
+ T visitSpec(Spec spec, [T? context]);
+
+ T visitType(TypeReference spec, [T? context]);
+
+ T visitTypeParameters(Iterable<Reference> specs, [T? context]);
+}
diff --git a/pkgs/code_builder/pubspec.yaml b/pkgs/code_builder/pubspec.yaml
new file mode 100644
index 0000000..9fb58cc
--- /dev/null
+++ b/pkgs/code_builder/pubspec.yaml
@@ -0,0 +1,24 @@
+name: code_builder
+version: 4.10.1
+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.5.0
+
+dependencies:
+ built_collection: ^5.0.0
+ built_value: ^8.0.0
+ collection: ^1.15.0
+ matcher: ^0.12.10
+ meta: ^1.3.0
+
+dev_dependencies:
+ build: ^2.0.0
+ build_runner: ^2.0.3
+ built_value_generator: ^8.0.0
+ dart_flutter_team_lints: ^3.0.0
+ dart_style: ^2.3.7
+ source_gen: ^1.0.0
+ test: ^1.16.0
diff --git a/pkgs/code_builder/test/allocator_test.dart b/pkgs/code_builder/test/allocator_test.dart
new file mode 100644
index 0000000..09f135c
--- /dev/null
+++ b/pkgs/code_builder/test/allocator_test.dart
@@ -0,0 +1,53 @@
+// Copyright (c) 2017, 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:code_builder/code_builder.dart';
+import 'package:test/test.dart';
+
+import 'common.dart';
+
+void main() {
+ useDartfmt();
+
+ group('Allocator', () {
+ Allocator allocator;
+
+ test('should return the exact (non-prefixed) symbol', () {
+ allocator = Allocator();
+ expect(allocator.allocate(refer('Foo', 'package:foo')), 'Foo');
+ });
+
+ test('should collect import URLs', () {
+ 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',
+ ]);
+ });
+
+ test('.none should do nothing', () {
+ allocator = Allocator.none;
+ expect(allocator.allocate(refer('Foo', 'package:foo')), 'Foo');
+ expect(allocator.imports, isEmpty);
+ });
+
+ test('.simplePrefixing should add import prefixes', () {
+ allocator = Allocator.simplePrefixing();
+ expect(
+ allocator.allocate(refer('List', 'dart:core')),
+ 'List',
+ );
+ expect(
+ allocator.allocate(refer('LinkedHashMap', 'dart:collection')),
+ '_i1.LinkedHashMap',
+ );
+ expect(allocator.imports.map((d) => '${d.url} as ${d.as}'), [
+ 'dart:collection as _i1',
+ ]);
+ });
+ });
+}
diff --git a/pkgs/code_builder/test/common.dart b/pkgs/code_builder/test/common.dart
new file mode 100644
index 0000000..b1db8d0
--- /dev/null
+++ b/pkgs/code_builder/test/common.dart
@@ -0,0 +1,21 @@
+// Copyright (c) 2017, 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:code_builder/code_builder.dart';
+import 'package:dart_style/dart_style.dart';
+
+String _format(String source) {
+ final formatter = DartFormatter(
+ languageVersion: DartFormatter.latestLanguageVersion,
+ );
+
+ try {
+ return formatter.format(source);
+ } on FormatterException catch (_) {
+ return formatter.formatStatement(source);
+ }
+}
+
+/// Should be invoked in `main()` of every test in `test/**_test.dart`.
+void useDartfmt() => EqualsDart.format = _format;
diff --git a/pkgs/code_builder/test/const_test.dart b/pkgs/code_builder/test/const_test.dart
new file mode 100644
index 0000000..c4d69c6
--- /dev/null
+++ b/pkgs/code_builder/test/const_test.dart
@@ -0,0 +1,69 @@
+// Copyright (c) 2017, 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:code_builder/code_builder.dart';
+import 'package:test/test.dart';
+
+import 'common.dart';
+
+void main() {
+ useDartfmt();
+
+ final constMap = literalConstMap({
+ 'list': literalConstList([]),
+ 'duration': refer('Duration').constInstance([]),
+ });
+
+ test('expression', () {
+ expect(constMap, equalsDart(r'''
+ const {'list': [], 'duration': Duration(), }'''));
+ });
+
+ test('assignConst', () {
+ expect(
+ // ignore: deprecated_member_use_from_same_package
+ constMap.assignConst('constField'),
+ equalsDart(r'''
+ const constField = {'list': [], 'duration': Duration(), }''',
+ DartEmitter.scoped()),
+ );
+ });
+
+ test('assign to declared constant', () {
+ expect(
+ declareConst('constField').assign(constMap),
+ equalsDart(r'''
+ const constField = {'list': [], 'duration': Duration(), }''',
+ DartEmitter.scoped()),
+ );
+ });
+
+ test('assign to declared non-constant', () {
+ expect(
+ declareVar('varField').assign(constMap),
+ equalsDart(r'''
+ var varField = const {'list': [], 'duration': Duration(), }''',
+ 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)));
+
+ test('should emit a source file with imports in defined order', () {
+ expect(
+ library,
+ equalsDart(r'''
+ const val1 = ConstClass();
+ const val2 = ConstClass.other();'''),
+ );
+ });
+}
diff --git a/pkgs/code_builder/test/directive_test.dart b/pkgs/code_builder/test/directive_test.dart
new file mode 100644
index 0000000..0aee6ae
--- /dev/null
+++ b/pkgs/code_builder/test/directive_test.dart
@@ -0,0 +1,81 @@
+// Copyright (c) 2017, 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:code_builder/code_builder.dart';
+import 'package:test/test.dart';
+
+import 'common.dart';
+
+void main() {
+ useDartfmt();
+
+ 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)));
+
+ test('should emit a source file with imports in defined order', () {
+ expect(
+ library,
+ equalsDart(r'''
+ // ignore_for_file: no_leading_underscores_for_library_prefixes
+ import '../relative.dart' as _i1;
+ import 'package:foo/foo.dart' as _i2;
+ import 'package:foo/bar.dart' as _i3;
+ import 'dart:collection' as _i4;
+ export '../relative.dart';
+ export 'package:foo/foo.dart';
+ part 'lib.g.dart';
+
+ final relativeRef = _i1.Relative();
+ final pkgRefFoo = _i2.Foo();
+ final pkgRefBar = _i3.Bar();
+ final collectionRef = _i4.LinkedHashMap();''', DartEmitter.scoped()),
+ );
+ });
+
+ test('should emit a source file with ordered', () {
+ expect(
+ library,
+ equalsDart(r'''
+ // ignore_for_file: no_leading_underscores_for_library_prefixes
+ import 'dart:collection' as _i4;
+
+ import 'package:foo/bar.dart' as _i3;
+ import 'package:foo/foo.dart' as _i2;
+
+ import '../relative.dart' as _i1;
+
+ export 'package:foo/foo.dart';
+ export '../relative.dart';
+
+ part 'lib.g.dart';
+
+ final relativeRef = _i1.Relative();
+ final pkgRefFoo = _i2.Foo();
+ final pkgRefBar = _i3.Bar();
+ final collectionRef = _i4.LinkedHashMap();''',
+ DartEmitter.scoped(orderDirectives: true)),
+ );
+ });
+}
diff --git a/pkgs/code_builder/test/e2e/injection_test.dart b/pkgs/code_builder/test/e2e/injection_test.dart
new file mode 100644
index 0000000..129f02c
--- /dev/null
+++ b/pkgs/code_builder/test/e2e/injection_test.dart
@@ -0,0 +1,67 @@
+// Copyright (c) 2017, 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:code_builder/code_builder.dart';
+import 'package:test/test.dart';
+
+import '../common.dart';
+
+void main() {
+ useDartfmt();
+
+ test('should generate a complex generated file', () {
+ // Imports from an existing Dart library.
+ final $App = refer('App', 'package:app/app.dart');
+ 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'))));
+
+ expect(
+ clazz.build(),
+ equalsDart(r'''
+ class Injector implements App {
+ Injector(this._module);
+
+ final Module _module;
+
+ @override
+ Thing getThing() => Thing(_module.get1(), _module.get2(), );
+ }
+ '''),
+ );
+
+ expect(
+ clazz.build(),
+ equalsDart(r'''
+ class Injector implements _i1.App {
+ Injector(this._module);
+
+ final _i2.Module _module;
+
+ @override
+ _i3.Thing getThing() => _i3.Thing(_module.get1(), _module.get2(), );
+ }
+ ''', DartEmitter(allocator: Allocator.simplePrefixing())),
+ );
+ });
+}
diff --git a/pkgs/code_builder/test/matcher_test.dart b/pkgs/code_builder/test/matcher_test.dart
new file mode 100644
index 0000000..c9475a8
--- /dev/null
+++ b/pkgs/code_builder/test/matcher_test.dart
@@ -0,0 +1,30 @@
+// Copyright (c) 2020, 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:code_builder/code_builder.dart';
+import 'package:test/test.dart';
+
+void main() {
+ test('describes mismatches', () {
+ const actual = Code('final x=1;');
+ equalsDart('final y=2;').expectMismatch(actual, '''
+ Expected: final y=2;
+ Actual: StaticCode:<final x=1;>
+ Which: is different.
+ Expected: final y=2;
+ Actual: final x=1;
+ ^
+ Differ at offset 6
+''');
+ });
+}
+
+extension on Matcher {
+ void expectMismatch(dynamic actual, String mismatch) {
+ expect(
+ () => expect(actual, this),
+ throwsA(isA<TestFailure>().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
new file mode 100644
index 0000000..562bca4
--- /dev/null
+++ b/pkgs/code_builder/test/specs/class_test.dart
@@ -0,0 +1,484 @@
+// Copyright (c) 2017, 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:code_builder/code_builder.dart';
+import 'package:test/test.dart';
+
+import '../common.dart';
+
+void main() {
+ useDartfmt();
+
+ test('should create a class', () {
+ expect(
+ Class((b) => b..name = 'Foo'),
+ equalsDart(r'''
+ class Foo {}
+ '''),
+ );
+ });
+
+ test('should create an abstract class', () {
+ expect(
+ Class((b) => b
+ ..name = 'Foo'
+ ..abstract = true),
+ equalsDart(r'''
+ abstract class Foo {}
+ '''),
+ );
+ });
+
+ test('should create an abstract base class', () {
+ expect(
+ Class((b) => b
+ ..name = 'Foo'
+ ..abstract = true
+ ..modifier = ClassModifier.base),
+ equalsDart(r'''
+ abstract base class Foo {}
+ '''),
+ );
+ });
+
+ test('should create a final class', () {
+ expect(
+ Class((b) => b
+ ..name = 'Foo'
+ ..modifier = ClassModifier.final$),
+ equalsDart(r'''
+ final class Foo {}
+ '''),
+ );
+ });
+
+ test('should create a sealed class', () {
+ expect(
+ Class((b) => b
+ ..name = 'Foo'
+ ..sealed = true),
+ equalsDart(r'''
+ sealed class Foo {}
+ '''),
+ );
+ });
+
+ test('should create an abstract interface class', () {
+ expect(
+ Class((b) => b
+ ..name = 'Foo'
+ ..abstract = true
+ ..modifier = ClassModifier.interface),
+ equalsDart(r'''
+ abstract interface class Foo {}
+ '''),
+ );
+ });
+
+ test('should create a mixin class', () {
+ expect(
+ Class((b) => b
+ ..name = 'Foo'
+ ..mixin = true),
+ equalsDart(r'''
+ mixin class Foo {}
+ '''),
+ );
+ });
+
+ test('should create an abstract mixin class', () {
+ expect(
+ Class((b) => b
+ ..name = 'Foo'
+ ..abstract = true
+ ..mixin = true),
+ equalsDart(r'''
+ abstract mixin class Foo {}
+ '''),
+ );
+ });
+
+ test('should create a base mixin class', () {
+ expect(
+ Class((b) => b
+ ..name = 'Foo'
+ ..mixin = true
+ ..modifier = ClassModifier.base),
+ equalsDart(r'''
+ base mixin class Foo {}
+ '''),
+ );
+ });
+
+ test('should create an abstract base mixin class', () {
+ expect(
+ Class((b) => b
+ ..name = 'Foo'
+ ..abstract = true
+ ..mixin = true
+ ..modifier = ClassModifier.base),
+ equalsDart(r'''
+ abstract base mixin class Foo {}
+ '''),
+ );
+ });
+
+ test('should create a class with documentations', () {
+ expect(
+ Class(
+ (b) => b
+ ..name = 'Foo'
+ ..docs.addAll(
+ const [
+ '/// My favorite class.',
+ ],
+ ),
+ ),
+ equalsDart(r'''
+ /// My favorite class.
+ class Foo {}
+ '''),
+ );
+ });
+
+ 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')])
+ ]),
+ ),
+ equalsDart(r'''
+ @deprecated
+ @Deprecated('This is an old class')
+ class Foo {}
+ '''),
+ );
+ });
+
+ test('should create a class with a generic type', () {
+ expect(
+ Class((b) => b
+ ..name = 'List'
+ ..types.add(refer('T'))),
+ equalsDart(r'''
+ class List<T> {}
+ '''),
+ );
+ });
+
+ test('should create a class with multiple generic types', () {
+ expect(
+ Class(
+ (b) => b
+ ..name = 'Map'
+ ..types.addAll([
+ refer('K'),
+ refer('V'),
+ ]),
+ ),
+ equalsDart(r'''
+ class Map<K, V> {}
+ '''),
+ );
+ });
+
+ 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))))),
+ equalsDart(r'''
+ class Comparable<T extends Comparable<T>> {}
+ '''),
+ );
+ });
+
+ test('should create a class extending another class', () {
+ expect(
+ Class((b) => b
+ ..name = 'Foo'
+ ..extend = TypeReference((b) => b.symbol = 'Bar')),
+ equalsDart(r'''
+ class Foo extends Bar {}
+ '''),
+ );
+ });
+
+ 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'))),
+ equalsDart(r'''
+ class Foo extends Bar with Foo {}
+ '''),
+ );
+ });
+
+ 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'))),
+ equalsDart(r'''
+ class Foo extends Bar implements Foo {}
+ '''),
+ );
+ });
+
+ test('should create a class with a constructor', () {
+ expect(
+ Class((b) => b
+ ..name = 'Foo'
+ ..constructors.add(Constructor())),
+ equalsDart(r'''
+ class Foo {
+ Foo();
+ }
+ '''),
+ );
+ });
+
+ 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()'),
+ ]),
+ ),
+ ),
+ ),
+ equalsDart(r'''
+ class Foo {
+ Foo() : a = 5, super();
+ }
+ '''),
+ );
+ });
+
+ test('should create a class with a annotated constructor', () {
+ expect(
+ Class((b) => b
+ ..name = 'Foo'
+ ..constructors
+ .add(Constructor((b) => b..annotations.add(refer('deprecated'))))),
+ equalsDart(r'''
+ class Foo {
+ @deprecated
+ Foo();
+ }
+ '''),
+ );
+ });
+
+ test('should create a class with a named constructor', () {
+ expect(
+ Class((b) => b
+ ..name = 'Foo'
+ ..constructors.add(Constructor((b) => b..name = 'named'))),
+ equalsDart(r'''
+ class Foo {
+ Foo.named();
+ }
+ '''),
+ );
+ });
+
+ test('should create a class with a const constructor', () {
+ expect(
+ Class((b) => b
+ ..name = 'Foo'
+ ..constructors.add(Constructor((b) => b..constant = true))),
+ equalsDart(r'''
+ class Foo {
+ const Foo();
+ }
+ '''),
+ );
+ });
+
+ test('should create a class with an external constructor', () {
+ expect(
+ Class((b) => b
+ ..name = 'Foo'
+ ..constructors.add(Constructor((b) => b..external = true))),
+ equalsDart(r'''
+ class Foo {
+ external Foo();
+ }
+ '''),
+ );
+ });
+
+ 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')))),
+ equalsDart(r'''
+ class Foo {
+ factory Foo() = _Foo;
+ }
+ '''),
+ );
+ });
+
+ 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')))),
+ equalsDart(r'''
+ class Foo {
+ const factory Foo() = _Foo;
+ }
+ '''),
+ );
+ });
+
+ 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()')))),
+ equalsDart(r'''
+ class Foo {
+ factory Foo() => _Foo();
+ }
+ '''),
+ );
+ });
+
+ 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))),
+ equalsDart(r'''
+ class Foo {
+ factory Foo() => _Foo();
+ }
+ '''),
+ );
+ });
+
+ 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();')))),
+ equalsDart(r'''
+ class Foo {
+ factory Foo() {
+ return _Foo();
+ }
+ }
+ '''),
+ );
+ });
+
+ 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),
+ ])))),
+ equalsDart(r'''
+ class Foo {
+ Foo(a, b, {c, });
+ }
+ '''),
+ );
+ });
+
+ 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),
+ ])))),
+ equalsDart(r'''
+ class Foo {
+ Foo(this.a, this.b, {this.c, });
+ }
+ '''),
+ );
+ });
+
+ 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),
+ ])))),
+ 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
new file mode 100644
index 0000000..4ce9eba
--- /dev/null
+++ b/pkgs/code_builder/test/specs/code/expression_test.dart
@@ -0,0 +1,955 @@
+// Copyright (c) 2017, 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:code_builder/code_builder.dart';
+import 'package:test/test.dart';
+
+import '../../common.dart';
+
+void main() {
+ useDartfmt();
+
+ test('should emit a simple expression', () {
+ expect(literalNull, equalsDart('null'));
+ });
+
+ group('literal', () {
+ test('forwards values that are already expressions', () {
+ expect(literal(refer('foo')), equalsDart('foo'));
+ expect(literal([refer('foo')]), equalsDart('[foo]'));
+ });
+ group('wraps', () {
+ test('bool values', () {
+ expect(literal(true), equalsDart('true'));
+ });
+ test('numeric values', () {
+ expect(literal(1), equalsDart('1'));
+ expect(literal(1.0), equalsDart('1.0'));
+ });
+ test('string values', () {
+ expect(literal('foo'), equalsDart("'foo'"));
+ });
+ test('list values', () {
+ expect(literal([1]), equalsDart('[1]'));
+ });
+ test('set values', () {
+ expect(literal({1}), equalsDart('{1}'));
+ });
+ test('map values', () {
+ expect(literal({'foo': 1}), equalsDart("{'foo': 1}"));
+ });
+ test('null', () {
+ expect(literal(null), equalsDart('null'));
+ });
+ });
+ test('uses `onError` for unhandled types', () {
+ expect(
+ literal(Uri.https('google.com'), onError: (value) {
+ if (value is Uri) {
+ return refer('Uri')
+ .newInstanceNamed('parse', [literalString(value.toString())]);
+ }
+ throw UnsupportedError('Not supported: $value');
+ }),
+ equalsDart("Uri.parse('https://google.com')"));
+ });
+ });
+
+ test('should emit a String', () {
+ expect(literalString(r'$monkey'), equalsDart(r"'$monkey'"));
+ });
+
+ test('should emit a raw String', () {
+ expect(literalString(r'$monkey', raw: true), equalsDart(r"r'$monkey'"));
+ });
+
+ test('should escape single quotes in a String', () {
+ expect(literalString(r"don't"), equalsDart(r"'don\'t'"));
+ });
+
+ test('does not allow single quote in raw string', () {
+ expect(() => literalString(r"don't", raw: true), throwsArgumentError);
+ });
+
+ test('should escape a newline in a string', () {
+ expect(literalString('some\nthing'), equalsDart(r"'some\nthing'"));
+ });
+
+ test('should emit a && expression', () {
+ expect(literalTrue.and(literalFalse), equalsDart('true && false'));
+ });
+
+ test('should emit a || expression', () {
+ expect(literalTrue.or(literalFalse), equalsDart('true || false'));
+ });
+
+ test('should emit a ! expression', () {
+ expect(literalTrue.negate(), equalsDart('!true'));
+ });
+
+ test('should emit a list', () {
+ expect(literalList([]), equalsDart('[]'));
+ });
+
+ test('should emit a const list', () {
+ expect(literalConstList([]), equalsDart('const []'));
+ });
+
+ test('should emit an explicitly typed list', () {
+ expect(literalList([], refer('int')), equalsDart('<int>[]'));
+ });
+
+ test('should emit a set', () {
+ // ignore: prefer_collection_literals
+ expect(literalSet(Set()), equalsDart('{}'));
+ });
+
+ test('should emit a const set', () {
+ // ignore: prefer_collection_literals
+ expect(literalConstSet(Set()), equalsDart('const {}'));
+ });
+
+ test('should emit an explicitly typed set', () {
+ // ignore: prefer_collection_literals
+ expect(literalSet(Set(), refer('int')), equalsDart('<int>{}'));
+ });
+
+ test('should emit a map', () {
+ expect(literalMap({}), equalsDart('{}'));
+ });
+
+ test('should emit a const map', () {
+ expect(literalConstMap({}), equalsDart('const {}'));
+ });
+
+ test('should emit an explicitly typed map', () {
+ expect(
+ literalMap({}, refer('int'), refer('bool')),
+ equalsDart('<int, bool>{}'),
+ );
+ });
+
+ test('should emit a map of other literals and expressions', () {
+ expect(
+ literalMap({
+ 1: 'one',
+ 2: refer('two'),
+ refer('three'): 3,
+ refer('Map').newInstance([]): null,
+ }),
+ equalsDart(r"{1: 'one', 2: two, three: 3, Map(): null, }"),
+ );
+ });
+
+ test('should emit a map with spreads', () {
+ expect(
+ literalMap({
+ literalSpread(): refer('one'),
+ 2: refer('two'),
+ literalNullSafeSpread(): refer('three'),
+ refer('Map').newInstance([]): null,
+ }),
+ equalsDart('{...one, 2: two, ...?three, Map(): null, }'),
+ );
+ });
+
+ test('should emit a list of other literals and expressions', () {
+ expect(
+ literalList([
+ <dynamic>[],
+ // ignore: prefer_collection_literals
+ Set<dynamic>(),
+ true,
+ null,
+ refer('Map').newInstance([])
+ ]),
+ equalsDart('[[], {}, true, null, Map(), ]'),
+ );
+ });
+
+ test('can toString a list literal with an expression as a value', () {
+ expect(literalList([refer('foo')]).toString, isNot(throwsA(anything)));
+ });
+
+ test('should emit a set of other literals and expressions', () {
+ expect(
+ // ignore: prefer_collection_literals
+ literalSet([
+ <dynamic>[],
+ // ignore: prefer_collection_literals
+ Set<dynamic>(),
+ true,
+ null,
+ refer('Map').newInstance([])
+ ]),
+ equalsDart('{[], {}, true, null, Map(), }'),
+ );
+ });
+
+ test('should emit an empty record', () {
+ expect(literalRecord([], {}), equalsDart('()'));
+ });
+
+ test('should emit a const empty record', () {
+ expect(literalConstRecord([], {}), equalsDart('const ()'));
+ });
+
+ test('should emit a record with only positional fields', () {
+ expect(literalRecord([1, ''], {}), equalsDart("(1, '')"));
+ });
+
+ test('should correctly emit a record with a single positional field', () {
+ expect(literalRecord([1], {}), equalsDart('(1,)'));
+ });
+
+ test('should emit a record with only named fields', () {
+ 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)'));
+ });
+
+ 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))"));
+ });
+
+ test('should emit a type as an expression', () {
+ expect(refer('Map'), equalsDart('Map'));
+ });
+
+ test('should emit a scoped type as an expression', () {
+ expect(
+ refer('Foo', 'package:foo/foo.dart'),
+ equalsDart(
+ '_i1.Foo', DartEmitter(allocator: Allocator.simplePrefixing())),
+ );
+ });
+
+ test('should emit invoking Type()', () {
+ expect(
+ refer('Map').newInstance([]),
+ equalsDart('Map()'),
+ );
+ });
+
+ test('should emit invoking named constructor', () {
+ expect(
+ refer('Foo').newInstanceNamed('bar', []),
+ equalsDart('Foo.bar()'),
+ );
+ });
+
+ test('should emit invoking const Type()', () {
+ expect(
+ refer('Object').constInstance([]),
+ equalsDart('const Object()'),
+ );
+ });
+
+ test('should emit invoking a property accessor', () {
+ expect(refer('foo').property('bar'), equalsDart('foo.bar'));
+ });
+
+ test('should emit invoking a cascade property accessor', () {
+ expect(refer('foo').cascade('bar'), equalsDart('foo..bar'));
+ });
+
+ test('should emit invoking a null safe property accessor', () {
+ expect(refer('foo').nullSafeProperty('bar'), equalsDart('foo?.bar'));
+ });
+
+ test('should emit invoking a method with a single positional argument', () {
+ 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),
+ ]),
+ equalsDart('foo(1, 2, 3, )'),
+ );
+ });
+
+ test('should emit invoking a method with a single named argument', () {
+ expect(
+ 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),
+ }),
+ 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),
+ }),
+ equalsDart('foo(1, bar: 2, baz: 3, )'),
+ );
+ });
+
+ test('should emit invoking a method with a single type argument', () {
+ expect(
+ refer('foo').call(
+ [],
+ {},
+ [
+ refer('String'),
+ ],
+ ),
+ equalsDart('foo<String>()'),
+ );
+ });
+
+ test('should emit invoking a method with type arguments', () {
+ expect(
+ refer('foo').call(
+ [],
+ {},
+ [
+ refer('String'),
+ refer('int'),
+ ],
+ ),
+ equalsDart('foo<String, int>()'),
+ );
+ });
+
+ test('should emit a function type', () {
+ expect(
+ FunctionType((b) => b.returnType = refer('void')),
+ equalsDart('void Function()'),
+ );
+ });
+
+ test('should emit a typedef statement', () {
+ expect(
+ FunctionType((b) => b.returnType = refer('void')).toTypeDef('Void0'),
+ equalsDart('typedef Void0 = void Function();'),
+ );
+ });
+
+ test('should emit a function type with type parameters', () {
+ expect(
+ FunctionType((b) => b
+ ..returnType = refer('T')
+ ..types.add(refer('T'))),
+ equalsDart('T Function<T>()'),
+ );
+ });
+
+ test('should emit a function type a single parameter', () {
+ expect(
+ FunctionType((b) => b..requiredParameters.add(refer('String'))),
+ equalsDart('Function(String)'),
+ );
+ });
+
+ test('should emit a function type with parameters', () {
+ expect(
+ 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'),
+ })),
+ 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, })'),
+ );
+ });
+
+ test('should emit a function type with named required parameters', () {
+ expect(
+ FunctionType((b) => b
+ ..namedRequiredParameters.addAll({
+ 'x': refer('int'),
+ 'y': refer('int'),
+ })),
+ equalsDart('Function({required int x, required int y, })'),
+ );
+ });
+
+ 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),
+ 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),
+ equalsDart('Function(String)'),
+ );
+ });
+
+ 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),
+ 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 closure', () {
+ expect(
+ refer('map').property('putIfAbsent').call([
+ literalString('foo'),
+ Method((b) => b..body = literalTrue.code).closure,
+ ]),
+ equalsDart("map.putIfAbsent('foo', () => true, )"),
+ );
+ });
+
+ test('should emit a generic closure', () {
+ expect(
+ refer('map').property('putIfAbsent').call([
+ literalString('foo'),
+ Method((b) => b
+ ..types.add(refer('T'))
+ ..body = literalTrue.code).genericClosure,
+ ]),
+ equalsDart("map.putIfAbsent('foo', <T>() => true, )"),
+ );
+ });
+
+ test('should emit an assignment', () {
+ expect(
+ refer('foo').assign(literalTrue),
+ equalsDart('foo = true'),
+ );
+ });
+
+ test('should emit an if null assignment', () {
+ expect(
+ refer('foo').ifNullThen(literalTrue),
+ equalsDart('foo ?? true'),
+ );
+ });
+
+ test('should emit a null check', () {
+ expect(refer('foo').nullChecked, equalsDart('foo!'));
+ });
+
+ test('should emit an if null index operator set', () {
+ expect(
+ refer('bar')
+ .index(literalTrue)
+ .ifNullThen(literalFalse)
+ // ignore: deprecated_member_use_from_same_package
+ .assignVar('foo')
+ .statement,
+ equalsDart('var foo = bar[true] ?? false;'),
+ );
+ });
+
+ test('should emit a null-aware assignment', () {
+ expect(
+ refer('foo').assignNullAware(literalTrue),
+ equalsDart('foo ??= true'),
+ );
+ });
+
+ test('should emit an index operator', () {
+ expect(
+ // ignore: deprecated_member_use_from_same_package
+ refer('bar').index(literalString('key')).assignVar('foo').statement,
+ equalsDart("var foo = bar['key'];"),
+ );
+ });
+
+ test('should emit an index operator set', () {
+ expect(
+ refer('bar')
+ .index(literalString('key'))
+ .assign(literalFalse)
+ // ignore: deprecated_member_use_from_same_package
+ .assignVar('foo')
+ .statement,
+ equalsDart("var foo = bar['key'] = false;"),
+ );
+ });
+
+ test('should emit a null-aware index operator set', () {
+ expect(
+ refer('bar')
+ .index(literalTrue)
+ .assignNullAware(literalFalse)
+ // ignore: deprecated_member_use_from_same_package
+ .assignVar('foo')
+ .statement,
+ equalsDart('var foo = bar[true] ??= false;'),
+ );
+ });
+
+ test('should emit assigning to a var', () {
+ expect(
+ // ignore: deprecated_member_use_from_same_package
+ literalTrue.assignVar('foo'),
+ equalsDart('var foo = true'),
+ );
+ });
+
+ test('should emit assigning to a type', () {
+ expect(
+ // ignore: deprecated_member_use_from_same_package
+ literalTrue.assignVar('foo', refer('bool')),
+ equalsDart('bool foo = true'),
+ );
+ });
+
+ test('should emit assigning to a final', () {
+ expect(
+ // ignore: deprecated_member_use_from_same_package
+ literalTrue.assignFinal('foo'),
+ equalsDart('final foo = true'),
+ );
+ });
+
+ test('should emit assigning to a const', () {
+ expect(
+ // ignore: deprecated_member_use_from_same_package
+ literalTrue.assignConst('foo'),
+ equalsDart('const foo = true'),
+ );
+ });
+
+ test('should emit await', () {
+ expect(
+ refer('future').awaited,
+ equalsDart('await future'),
+ );
+ });
+
+ test('should emit return', () {
+ expect(
+ literalNull.returned,
+ equalsDart('return null'),
+ );
+ });
+
+ test('should emit spread', () {
+ expect(
+ refer('foo').spread,
+ equalsDart('...foo'),
+ );
+ });
+
+ test('should emit null safe spread', () {
+ expect(
+ refer('foo').nullSafeSpread,
+ equalsDart('...?foo'),
+ );
+ });
+
+ test('should emit throw', () {
+ expect(
+ literalNull.thrown,
+ equalsDart('throw null'),
+ );
+ });
+
+ test('should emit an explicit cast', () {
+ expect(
+ refer('foo').asA(refer('String')).property('length'),
+ equalsDart('(foo as String).length'),
+ );
+ });
+
+ test('should emit an is check', () {
+ 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'),
+ );
+ });
+
+ test('should emit an equality check', () {
+ expect(
+ refer('foo').equalTo(literalString('bar')),
+ equalsDart("foo == 'bar'"),
+ );
+ });
+
+ test('should emit an inequality check', () {
+ expect(
+ refer('foo').notEqualTo(literalString('bar')),
+ equalsDart("foo != 'bar'"),
+ );
+ });
+
+ test('should emit an greater than check', () {
+ expect(
+ refer('foo').greaterThan(literalString('bar')),
+ equalsDart("foo > 'bar'"),
+ );
+ });
+
+ test('should emit an less than check', () {
+ expect(
+ refer('foo').lessThan(literalString('bar')),
+ equalsDart("foo < 'bar'"),
+ );
+ });
+
+ test('should emit an greater or equals check', () {
+ expect(
+ refer('foo').greaterOrEqualTo(literalString('bar')),
+ equalsDart("foo >= 'bar'"),
+ );
+ });
+
+ test('should emit an less or equals check', () {
+ expect(
+ refer('foo').lessOrEqualTo(literalString('bar')),
+ equalsDart("foo <= 'bar'"),
+ );
+ });
+
+ test('should emit a conditional', () {
+ expect(
+ refer('foo').conditional(literal(1), literal(2)),
+ equalsDart('foo ? 1 : 2'),
+ );
+ });
+
+ test('should emit an operator add call', () {
+ expect(refer('foo').operatorAdd(refer('foo2')), equalsDart('foo + foo2'));
+ });
+
+ test('should emit an operator subtract call', () {
+ // ignore: deprecated_member_use_from_same_package
+ expect(refer('foo').operatorSubstract(refer('foo2')),
+ equalsDart('foo - foo2'));
+
+ expect(
+ refer('foo').operatorSubtract(refer('foo2')),
+ equalsDart('foo - foo2'),
+ );
+ });
+
+ test('should emit an operator divide call', () {
+ expect(
+ refer('foo').operatorDivide(refer('foo2')), equalsDart('foo / foo2'));
+ });
+
+ test('should emit an operator multiply call', () {
+ expect(
+ 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'));
+ });
+
+ test('should emit an operator int divide call', () {
+ expect(
+ refer('foo').operatorIntDivide(refer('foo2')),
+ equalsDart('foo ~/ foo2'),
+ );
+ });
+
+ test('should emit a unary prefix increment operator call', () {
+ expect(refer('foo').operatorUnaryPrefixIncrement(), equalsDart('++foo'));
+ });
+
+ test('should emit a unary postfix increment operator call', () {
+ expect(refer('foo').operatorUnaryPostfixIncrement(), equalsDart('foo++'));
+ });
+
+ test('should emit a unary prefix minus operator call', () {
+ expect(refer('foo').operatorUnaryMinus(), equalsDart('-foo'));
+ });
+
+ test('should emit a unary prefix decrement operator call', () {
+ expect(refer('foo').operatorUnaryPrefixDecrement(), equalsDart('--foo'));
+ });
+
+ test('should emit a unary postfix decrement operator call', () {
+ expect(refer('foo').operatorUnaryPostfixDecrement(), equalsDart('foo--'));
+ });
+
+ test('should emit a bitwise AND operator call', () {
+ expect(
+ refer('foo').operatorBitwiseAnd(refer('foo2')),
+ equalsDart('foo & foo2'),
+ );
+ });
+
+ test('should emit a bitwise OR operator call', () {
+ expect(
+ refer('foo').operatorBitwiseOr(refer('foo2')),
+ equalsDart('foo | foo2'),
+ );
+ });
+
+ test('should emit a bitwise XOR operator call', () {
+ expect(
+ refer('foo').operatorBitwiseXor(refer('foo2')),
+ equalsDart('foo ^ foo2'),
+ );
+ });
+
+ test('should emit a unary bitwise complement operator call', () {
+ expect(
+ refer('foo').operatorUnaryBitwiseComplement(),
+ equalsDart('~foo'),
+ );
+ });
+
+ test('should emit a shift left operator call', () {
+ expect(
+ refer('foo').operatorShiftLeft(refer('foo2')),
+ equalsDart('foo << foo2'),
+ );
+ });
+
+ test('should emit a shift right operator call', () {
+ expect(
+ refer('foo').operatorShiftRight(refer('foo2')),
+ equalsDart('foo >> foo2'),
+ );
+ });
+
+ test('should emit a shift right unsigned operator call', () {
+ expect(
+ refer('foo').operatorShiftRightUnsigned(refer('foo2')),
+ equalsDart('foo >>> foo2'),
+ );
+ });
+
+ test('should emit a const variable declaration', () {
+ 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'));
+ });
+
+ test('should emit a final variable declaration', () {
+ 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'));
+ });
+
+ 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'));
+ });
+
+ 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'));
+ });
+
+ test('should emit a variable declaration', () {
+ expect(declareVar('foo').assign(refer('bar')), equalsDart('var foo = bar'));
+ });
+
+ test('should emit a typed variable declaration', () {
+ 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'));
+ });
+
+ test('should emit a late typed variable declaration', () {
+ expect(
+ 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\'))'));
+ });
+
+ test('should emit an addition assigment expression', () {
+ 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'),
+ );
+ });
+
+ test('should emit a multiplication assigment expression', () {
+ 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'),
+ );
+ });
+
+ test('should emit an int division assigment expression', () {
+ expect(
+ refer('foo').intDivideAssign(refer('bar')),
+ equalsDart('foo ~/= bar'),
+ );
+ });
+
+ test('should emit a euclidean modulo assigment expression', () {
+ expect(
+ refer('foo').euclideanModuloAssign(refer('bar')),
+ equalsDart('foo %= bar'),
+ );
+ });
+
+ test('should emit a shift left assigment expression', () {
+ expect(
+ refer('foo').shiftLeftAssign(refer('bar')),
+ equalsDart('foo <<= bar'),
+ );
+ });
+
+ test('should emit a shift right assigment expression', () {
+ expect(
+ refer('foo').shiftRightAssign(refer('bar')),
+ equalsDart('foo >>= bar'),
+ );
+ });
+
+ test('should emit a shift right unsigned assigment expression', () {
+ expect(
+ refer('foo').shiftRightUnsignedAssign(refer('bar')),
+ equalsDart('foo >>>= bar'),
+ );
+ });
+
+ test('should emit a bitwise AND assigment expression', () {
+ expect(
+ refer('foo').bitwiseAndAssign(refer('bar')),
+ equalsDart('foo &= bar'),
+ );
+ });
+
+ test('should emit a bitwise XOR assigment expression', () {
+ expect(
+ refer('foo').bitwiseXorAssign(refer('bar')),
+ equalsDart('foo ^= bar'),
+ );
+ });
+
+ test('should emit a bitwise OR assigment expression', () {
+ expect(
+ refer('foo').bitwiseOrAssign(refer('bar')),
+ equalsDart('foo |= bar'),
+ );
+ });
+}
diff --git a/pkgs/code_builder/test/specs/code/statement_test.dart b/pkgs/code_builder/test/specs/code/statement_test.dart
new file mode 100644
index 0000000..cd3d53a
--- /dev/null
+++ b/pkgs/code_builder/test/specs/code/statement_test.dart
@@ -0,0 +1,63 @@
+// Copyright (c) 2017, 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:code_builder/code_builder.dart';
+import 'package:test/test.dart';
+
+import '../../common.dart';
+
+void main() {
+ useDartfmt();
+
+ test('should emit a block of code', () {
+ expect(
+ Block.of([
+ const Code('if (foo) {'),
+ const Code(' print(true);'),
+ const Code('}'),
+ ]),
+ equalsDart(r'''
+ if (foo) {
+ print(true);
+ }
+ '''),
+ );
+ });
+
+ test('should emit a block of code including expressions', () {
+ expect(
+ Block.of([
+ const Code('if (foo) {'),
+ refer('print')([literalTrue]).statement,
+ const Code('}'),
+ ]),
+ equalsDart(r'''
+ if (foo) {
+ print(true);
+ }
+ '''),
+ );
+ });
+
+ 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('}'),
+ ])),
+ equalsDart(r'''
+ main() {
+ if (foo) {
+ print(true);
+ }
+ }
+ '''),
+ );
+ });
+}
diff --git a/pkgs/code_builder/test/specs/enum_test.dart b/pkgs/code_builder/test/specs/enum_test.dart
new file mode 100644
index 0000000..5a0b19a
--- /dev/null
+++ b/pkgs/code_builder/test/specs/enum_test.dart
@@ -0,0 +1,442 @@
+// Copyright (c) 2020, 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:code_builder/code_builder.dart';
+import 'package:test/test.dart';
+
+import '../common.dart';
+
+void main() {
+ useDartfmt();
+
+ 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 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'''
+ @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 Status {
+ @deprecated
+ @Deprecated('use Good instead')
+ okay,
+ @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('''
+ 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('''
+ enum MyEnum {
+ a,
+ b.named(),
+ c;
+
+ const MyEnum();
+
+ 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('''
+ enum MyEnum {
+ a,
+ b.redirect(),
+ c;
+
+ const MyEnum();
+
+ 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('''
+ enum MyEnum {
+ a,
+ b.redirect(1, 'abc'),
+ c.redirect();
+
+ const MyEnum();
+
+ const factory MyEnum.redirect([
+ int? myInt,
+ 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('''
+ enum MyEnum {
+ a,
+ b(1, 'abc'),
+ c;
+
+ const MyEnum([
+ this.myInt,
+ this.myString,
+ ]);
+
+ final int? myInt;
+
+ 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('''
+ enum MyEnum<T> {
+ a<int>(123),
+ b<String>('abc'),
+ c<MyEnum>(MyEnum.a);
+
+ const MyEnum(this.value);
+
+ 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('''
+ enum MyEnum {
+ a,
+ b,
+ c;
+
+ const MyEnum([this.myInt]);
+
+ final int? myInt;
+
+ 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<String>')
+ ..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,
+ c;
+
+ int get myInt => 123;
+ Iterable<String> myStrings() sync* {
+ yield 'a';
+ yield 'b';
+ 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('''
+ enum MyEnum {
+ a,
+ b(1, myString: 'abc'),
+ c;
+
+ const MyEnum(
+ this.myInt,
+ {required this.myString, }
+ );
+
+ final int? myInt;
+
+ final String? myString;
+ }
+ '''));
+ });
+}
diff --git a/pkgs/code_builder/test/specs/extension_test.dart b/pkgs/code_builder/test/specs/extension_test.dart
new file mode 100644
index 0000000..b45fa65
--- /dev/null
+++ b/pkgs/code_builder/test/specs/extension_test.dart
@@ -0,0 +1,137 @@
+// Copyright (c) 2017, 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:code_builder/code_builder.dart';
+import 'package:test/test.dart';
+
+import '../common.dart';
+
+void main() {
+ useDartfmt();
+
+ test('should create an extension', () {
+ expect(
+ Extension((b) => b
+ ..name = 'Foo'
+ ..on = TypeReference((b) => b.symbol = 'Bar')),
+ equalsDart(r'''
+ extension Foo on Bar {}
+ '''),
+ );
+ });
+
+ test('should create an extension without an identifier', () {
+ expect(
+ Extension((b) => b..on = TypeReference((b) => b.symbol = 'Bar')),
+ equalsDart(r'''
+ extension on Bar {}
+ '''),
+ );
+ });
+
+ 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.',
+ ],
+ ),
+ ),
+ equalsDart(r'''
+ /// My favorite extension.
+ extension Foo on Bar {}
+ '''),
+ );
+ });
+
+ 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')])
+ ]),
+ ),
+ equalsDart(r'''
+ @deprecated
+ @Deprecated('This is an old extension')
+ extension Foo on Bar {}
+ '''),
+ );
+ });
+
+ 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'))),
+ equalsDart(r'''
+ extension Foo<T> on Bar {}
+ '''),
+ );
+ });
+
+ 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'),
+ ]),
+ ),
+ equalsDart(r'''
+ extension Map<K, V> on Bar {}
+ '''),
+ );
+ });
+
+ 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))))),
+ equalsDart(r'''
+ extension Foo<T extends Comparable<T>> on Bar {}
+ '''),
+ );
+ });
+
+ 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);',
+ )))),
+ equalsDart(r'''
+ extension Foo on Bar {
+ int parseInt() {
+ return int.parse(this);
+ }
+ }
+ '''),
+ );
+ });
+}
diff --git a/pkgs/code_builder/test/specs/extension_type_test.dart b/pkgs/code_builder/test/specs/extension_type_test.dart
new file mode 100644
index 0000000..cc51046
--- /dev/null
+++ b/pkgs/code_builder/test/specs/extension_type_test.dart
@@ -0,0 +1,244 @@
+// Copyright (c) 2023, 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:code_builder/code_builder.dart';
+import 'package:test/test.dart';
+
+import '../common.dart';
+
+void main() {
+ useDartfmt();
+
+ test('minimum extension type', () {
+ expect(
+ ExtensionType((b) => b
+ ..name = 'Foo'
+ ..representationDeclaration = RepresentationDeclaration((b) => b
+ ..declaredRepresentationType = TypeReference((b) => b.symbol = 'int')
+ ..name = 'bar')),
+ equalsDart(r'''
+ extension type Foo(int bar) { }
+ '''),
+ );
+ });
+
+ test('const extension type', () {
+ expect(
+ 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) { }
+ '''),
+ );
+ });
+
+ 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')])
+ ])),
+ equalsDart(r'''
+ /// My favorite extension type.
+ @deprecated
+ @Deprecated('This is an old extension type')
+ extension type Foo(int bar) { }
+ '''),
+ );
+ });
+
+ 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')),
+ equalsDart(r'''
+ extension type Foo<T,U>(T bar) { }
+ '''),
+ );
+ });
+
+ 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')),
+ equalsDart(r'''
+ extension type Foo<T extends num>(T bar) { }
+ '''),
+ );
+ });
+
+ 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')),
+ equalsDart(r'''
+ extension type Foo.named(int bar) { }
+ '''),
+ );
+ });
+
+ 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')])
+ ]))),
+ equalsDart(r'''
+ extension type Foo(/// My favorite representation declaration.
+ @deprecated
+ @Deprecated('This is an old representation declaration')
+ int bar) { }
+ '''),
+ );
+ });
+
+ 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')),
+ equalsDart(r'''
+ extension type Foo(int bar) implements num { }
+ '''),
+ );
+ });
+
+ 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')),
+ equalsDart(r'''
+ extension type Foo(int bar) implements num,Object { }
+ '''),
+ );
+ });
+
+ 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);'))
+ ])),
+ equalsDart(r'''
+ extension type Foo._(int bar) {
+ Foo(this.bar);
+
+ factory Foo.named(int baz) {
+ return Foo(baz);
+ }
+ }
+ '''),
+ );
+ });
+
+ 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'))),
+ equalsDart(r'''
+ extension type Foo(int bar) {
+ external int property;
+ }
+ '''),
+ );
+ });
+
+ 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'))
+ ])),
+ equalsDart(r'''
+ extension type Foo(int bar) {
+ int get value { return this.bar; }
+ int getValue() => this.bar;
+ }
+ '''),
+ );
+ });
+}
diff --git a/pkgs/code_builder/test/specs/field_test.dart b/pkgs/code_builder/test/specs/field_test.dart
new file mode 100644
index 0000000..3d53687
--- /dev/null
+++ b/pkgs/code_builder/test/specs/field_test.dart
@@ -0,0 +1,113 @@
+// Copyright (c) 2017, 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:code_builder/code_builder.dart';
+import 'package:test/test.dart';
+
+import '../common.dart';
+
+void main() {
+ useDartfmt();
+
+ test('should create a field', () {
+ expect(
+ Field((b) => b..name = 'foo'),
+ equalsDart(r'''
+ var foo;
+ '''),
+ );
+ });
+
+ test('should create a typed field', () {
+ expect(
+ Field((b) => b
+ ..name = 'foo'
+ ..type = refer('String')),
+ equalsDart(r'''
+ String foo;
+ '''),
+ );
+ });
+
+ test('should create a final field', () {
+ expect(
+ Field((b) => b
+ ..name = 'foo'
+ ..modifier = FieldModifier.final$),
+ equalsDart(r'''
+ final foo;
+ '''),
+ );
+ });
+
+ test('should create a constant field', () {
+ expect(
+ Field((b) => b
+ ..name = 'foo'
+ ..modifier = FieldModifier.constant),
+ equalsDart(r'''
+ const foo;
+ '''),
+ );
+ });
+
+ test('should create a late field if using null-safety', () {
+ expect(
+ Field((b) => b
+ ..late = true
+ ..name = 'foo'),
+ equalsDart(r'''
+ late var foo;
+ ''', DartEmitter(useNullSafetySyntax: true)),
+ );
+ });
+
+ test('should not create a late field if not using null-safety', () {
+ expect(
+ Field((b) => b
+ ..late = true
+ ..name = 'foo'),
+ equalsDart(r'''
+ var foo;
+ '''),
+ );
+ });
+
+ test('should create a static late field', () {
+ expect(
+ Field((b) => b
+ ..static = true
+ ..late = true
+ ..name = 'foo'),
+ equalsDart(r'''
+ static late var foo;
+ ''', DartEmitter(useNullSafetySyntax: true)),
+ );
+ });
+
+ test('should create a field with an assignment', () {
+ expect(
+ Field((b) => b
+ ..name = 'foo'
+ ..assignment = const Code('1')),
+ equalsDart(r'''
+ var foo = 1;
+ '''),
+ );
+ });
+
+ test('should create a external field', () {
+ expect(
+ Field((b) => b
+ ..name = 'value'
+ ..external = true
+ ..type = refer('double')
+ ..annotations.addAll([refer('Float').call([])])),
+ equalsDart(r'''
+ @Float()
+ external double value;
+ '''),
+ );
+ });
+}
diff --git a/pkgs/code_builder/test/specs/library_test.dart b/pkgs/code_builder/test/specs/library_test.dart
new file mode 100644
index 0000000..8ea4c58
--- /dev/null
+++ b/pkgs/code_builder/test/specs/library_test.dart
@@ -0,0 +1,313 @@
+// Copyright (c) 2017, 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:code_builder/code_builder.dart';
+import 'package:test/test.dart';
+
+import '../common.dart';
+
+void main() {
+ useDartfmt();
+
+ group('File', () {
+ final $LinkedHashMap = refer('LinkedHashMap', 'dart:collection');
+
+ 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'),
+ ),
+ ),
+ equalsDart(r'''
+ // Generated by foo.
+
+ class Foo { }
+ ''', DartEmitter(allocator: Allocator())),
+ );
+ });
+
+ 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'),
+ ),
+ ),
+ equalsDart(r'''
+ // Generated by foo!
+ //
+ // Avoid editing by hand.
+
+ class Foo { }
+ ''', DartEmitter(allocator: Allocator())),
+ );
+ });
+
+ 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'),
+ ),
+ ),
+ equalsDart(r'''
+ // Generated by fooBar.
+
+ class Foo { }
+ ''', DartEmitter(allocator: Allocator())),
+ );
+ });
+
+ 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'),
+ ),
+ ),
+ equalsDart(r'''
+ // ignore_for_file: sort_constructors_first
+
+ class Foo { }
+ ''', DartEmitter(allocator: Allocator())),
+ );
+ });
+
+ 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'),
+ ),
+ ),
+ equalsDart(r'''
+ // ignore_for_file: file_names, implementation_imports, sort_constructors_first
+ // ignore_for_file: type=lint
+
+ class Foo { }
+ ''', DartEmitter(allocator: Allocator())),
+ );
+ });
+
+ 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.
+
+ // ignore_for_file: sort_constructors_first
+
+ 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))),
+ equalsDart(r'''
+ import 'dart:collection';
+
+ final test = LinkedHashMap();
+ ''', DartEmitter()),
+ );
+ });
+
+ test('should emit a source file with a deferred import', () {
+ expect(
+ Library(
+ (b) => b
+ ..directives.add(
+ Directive.importDeferredAs(
+ 'package:foo/foo.dart',
+ 'foo',
+ ),
+ ),
+ ),
+ equalsDart(r'''
+ import 'package:foo/foo.dart' deferred as foo;
+ '''),
+ );
+ });
+
+ 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'],
+ ),
+ ),
+ ),
+ equalsDart(r'''
+ import 'package:foo/foo.dart' show Foo, Bar;
+ '''),
+ );
+ });
+
+ 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'],
+ ),
+ ),
+ ),
+ equalsDart(r'''
+ import 'package:foo/foo.dart' hide Foo, Bar;
+ '''),
+ );
+ });
+
+ 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)}()')))),
+ equalsDart(r'''
+ import 'dart:collection';
+
+ final test = LinkedHashMap();
+ ''', DartEmitter(allocator: Allocator())),
+ );
+ });
+
+ 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)}()')))),
+ equalsDart(r'''
+ // ignore_for_file: no_leading_underscores_for_library_prefixes
+ import 'dart:collection' as _i1;
+
+ final test = _i1.LinkedHashMap();
+ ''', DartEmitter(allocator: Allocator.simplePrefixing())),
+ );
+ });
+
+ test('should emit a source file with part directives', () {
+ expect(
+ Library(
+ (b) => b
+ ..directives.add(
+ Directive.part('test.g.dart'),
+ ),
+ ),
+ equalsDart(r'''
+ part 'test.g.dart';
+ ''', DartEmitter()),
+ );
+ });
+
+ test('should emit a source file with part of directives', () {
+ expect(
+ Library(
+ (b) => b
+ ..directives.add(
+ Directive.partOf('test.dart'),
+ ),
+ ),
+ equalsDart(r'''
+ part of 'test.dart';
+ ''', DartEmitter()),
+ );
+ });
+
+ test('should emit a source file with annotations', () {
+ expect(
+ Library(
+ (b) => b
+ ..name = 'js_interop'
+ ..annotations.add(
+ refer('JS', 'package:js/js.dart').call([]),
+ ),
+ ),
+ equalsDart(r'''
+ @JS()
+ library js_interop;
+ import 'package:js/js.dart';
+ ''', DartEmitter(allocator: Allocator())),
+ );
+ });
+
+ test('should emit an unnamed library source file with annotations', () {
+ expect(
+ Library(
+ (b) => b
+ ..annotations.add(
+ refer('JS', 'package:js/js.dart').call([]),
+ ),
+ ),
+ equalsDart(r'''
+ @JS()
+ library;
+ import 'package:js/js.dart';
+ ''', DartEmitter(allocator: Allocator())),
+ );
+ });
+
+ test('should emit an unnamed library source file with documentation', () {
+ expect(
+ 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
new file mode 100644
index 0000000..5621bc5
--- /dev/null
+++ b/pkgs/code_builder/test/specs/method_test.dart
@@ -0,0 +1,632 @@
+// Copyright (c) 2017, 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:code_builder/code_builder.dart';
+import 'package:test/test.dart';
+
+import '../common.dart';
+
+void main() {
+ useDartfmt();
+
+ test('should create a method', () {
+ expect(
+ Method((b) => b..name = 'foo'),
+ equalsDart(r'''
+ foo();
+ '''),
+ );
+ });
+
+ test('should create an async method', () {
+ expect(
+ Method((b) => b
+ ..name = 'foo'
+ ..modifier = MethodModifier.async
+ ..body = literalNull.code),
+ equalsDart(r'''
+ foo() async => null
+ '''),
+ );
+ });
+
+ test('should create an async* method', () {
+ expect(
+ Method((b) => b
+ ..name = 'foo'
+ ..modifier = MethodModifier.asyncStar
+ ..body = literalNull.code),
+ equalsDart(r'''
+ foo() async* => null
+ '''),
+ );
+ });
+
+ test('should create an sync* method', () {
+ expect(
+ Method((b) => b
+ ..name = 'foo'
+ ..modifier = MethodModifier.syncStar
+ ..body = literalNull.code),
+ equalsDart(r'''
+ foo() sync* => null
+ '''),
+ );
+ });
+
+ test('should create a lambda method implicitly', () {
+ expect(
+ Method((b) => b
+ ..name = 'returnsTrue'
+ ..returns = refer('bool')
+ ..body = literalTrue.code),
+ equalsDart(r'''
+ bool returnsTrue() => true
+ '''),
+ );
+ });
+
+ 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),
+ equalsDart(r'''
+ Foo returnsCastedValue() => (bar as Foo)
+ '''),
+ );
+ });
+
+ test('should create a normal method implicitly', () {
+ expect(
+ Method.returnsVoid((b) => b
+ ..name = 'assignTrue'
+ ..body = refer('topLevelFoo').assign(literalTrue).statement),
+ equalsDart(r'''
+ void assignTrue() {
+ topLevelFoo = true;
+ }
+ '''),
+ );
+ });
+
+ test('should create a getter', () {
+ expect(
+ Method((b) => b
+ ..name = 'foo'
+ ..external = true
+ ..type = MethodType.getter),
+ equalsDart(r'''
+ external get foo;
+ '''),
+ );
+ });
+
+ test('should create a setter', () {
+ expect(
+ Method((b) => b
+ ..name = 'foo'
+ ..external = true
+ ..requiredParameters.add(Parameter((b) => b..name = 'foo'))
+ ..type = MethodType.setter),
+ equalsDart(r'''
+ external set foo(foo);
+ '''),
+ );
+ });
+
+ test('should create a method with a return type', () {
+ expect(
+ Method((b) => b
+ ..name = 'foo'
+ ..returns = refer('String')),
+ equalsDart(r'''
+ String foo();
+ '''),
+ );
+ });
+
+ test('should create a method with a void return type', () {
+ expect(
+ Method.returnsVoid((b) => b..name = 'foo'),
+ equalsDart(r'''
+ void foo();
+ '''),
+ );
+ });
+
+ 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'),
+ ]))),
+ 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'''
+ String Function([int])
+ '''),
+ );
+ });
+
+ 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'))),
+ equalsDart(r'''
+ String Function(int, [int, ])
+ '''),
+ );
+ });
+
+ test('should create a function type without parameters', () {
+ expect(
+ FunctionType((b) => b..returnType = refer('String')),
+ equalsDart(r'''
+ String Function()
+ '''),
+ );
+ });
+
+ test('should create a function type with an optional named parameter', () {
+ expect(
+ 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 '
+ 'and an optional named parameter', () {
+ expect(
+ FunctionType((b) => b
+ ..returnType = refer('String')
+ ..requiredParameters.add(refer('int'))
+ ..namedParameters['named'] = refer('int')),
+ equalsDart(r'''
+ String Function(int, {int named, })
+ '''),
+ );
+ });
+
+ test('should create a function type with a required named parameter', () {
+ expect(
+ 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 '
+ 'named parameter', () {
+ expect(
+ FunctionType((b) => b
+ ..returnType = refer('String')
+ ..namedRequiredParameters['named'] = refer('int')
+ ..namedParameters['optional'] = refer('int')),
+ equalsDart(r'''
+ String Function({required int named, int optional, })
+ '''),
+ );
+ });
+
+ test('should create a typedef to a reference', () {
+ expect(
+ TypeDef((b) => b
+ ..name = 'i32'
+ ..definition = const Reference('int')),
+ equalsDart(r'''
+ typedef i32 = int;
+ '''),
+ );
+ });
+
+ 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')))),
+ equalsDart(r'''
+ typedef MyMapper = String Function([int]);
+ '''),
+ );
+ });
+
+ 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'),
+ ]))),
+ equalsDart(r'''
+ String Function(String) Function(int) foo();
+ '''),
+ );
+ });
+
+ 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'''
+ 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'''
+ 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'))),
+ equalsDart(r'''
+ foo<T>();
+ '''),
+ );
+ });
+
+ test('should create an external method', () {
+ expect(
+ Method((b) => b
+ ..name = 'foo'
+ ..external = true),
+ equalsDart(r'''
+ external foo();
+ '''),
+ );
+ });
+
+ test('should create a method with a body', () {
+ expect(
+ Method((b) => b
+ ..name = 'foo'
+ ..body = const Code('return 1+ 2;')),
+ equalsDart(r'''
+ foo() {
+ return 1 + 2;
+ }
+ '''),
+ );
+ });
+
+ test('should create a lambda method (explicitly)', () {
+ expect(
+ Method((b) => b
+ ..name = 'foo'
+ ..lambda = true
+ ..body = const Code('1 + 2')),
+ equalsDart(r'''
+ foo() => 1 + 2
+ '''),
+ );
+ });
+
+ 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)}();',
+ )),
+ equalsDart(r'''
+ foo() {
+ return LinkedHashMap();
+ }
+ '''),
+ );
+ });
+
+ test('should create a method with a parameter', () {
+ expect(
+ Method(
+ (b) => b
+ ..name = 'fib'
+ ..requiredParameters.add(
+ Parameter((b) => b.name = 'i'),
+ ),
+ ),
+ equalsDart(r'''
+ fib(i);
+ '''),
+ );
+ });
+
+ 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'))),
+ ),
+ ),
+ equalsDart(r'''
+ fib(@deprecated i);
+ '''),
+ );
+ });
+
+ 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,
+ ),
+ ),
+ ),
+ equalsDart(r'''
+ fib(int i);
+ '''),
+ );
+ });
+
+ 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,
+ ),
+ ),
+ ),
+ equalsDart(r'''
+ fib(covariant int i);
+ '''),
+ );
+ });
+
+ 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')))),
+ ]),
+ ),
+ equalsDart(r'''
+ foo<T extends Iterable>(T t, X<T> x, );
+ '''),
+ );
+ });
+
+ test('should create a method with an optional parameter', () {
+ expect(
+ Method(
+ (b) => b
+ ..name = 'fib'
+ ..optionalParameters.add(
+ Parameter((b) => b.name = 'i'),
+ ),
+ ),
+ equalsDart(r'''
+ fib([i]);
+ '''),
+ );
+ });
+
+ 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'),
+ ]),
+ ),
+ equalsDart(r'''
+ foo([a, b, ]);
+ '''),
+ );
+ });
+
+ 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')),
+ ),
+ ),
+ equalsDart(r'''
+ fib([i = 0]);
+ '''),
+ );
+ });
+
+ 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,
+ ),
+ ),
+ ),
+ equalsDart(r'''
+ fib({required int i});
+ '''),
+ );
+ });
+
+ 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,
+ ),
+ ),
+ ),
+ equalsDart(r'''
+ fib({required covariant int i});
+ '''),
+ );
+ });
+
+ 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'),
+ ),
+ ),
+ equalsDart(r'''
+ fib({i});
+ '''),
+ );
+ });
+
+ 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')),
+ ),
+ ),
+ equalsDart(r'''
+ fib({i = 0});
+ '''),
+ );
+ });
+
+ 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'),
+ ),
+ ),
+ equalsDart(r'''
+ foo(a, {b, });
+ '''),
+ );
+ });
+
+ test('should create a method as a closure', () {
+ expect(
+ Method(
+ (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
new file mode 100644
index 0000000..e167d02
--- /dev/null
+++ b/pkgs/code_builder/test/specs/mixin_test.dart
@@ -0,0 +1,150 @@
+// Copyright (c) 2021, 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:code_builder/code_builder.dart';
+import 'package:test/test.dart';
+
+import '../common.dart';
+
+void main() {
+ useDartfmt();
+
+ test('should create a mixin', () {
+ expect(
+ Mixin((b) => b..name = 'Foo'),
+ equalsDart(r'''
+ mixin Foo {}
+ '''),
+ );
+ });
+
+ test('should create a base mixin', () {
+ expect(
+ Mixin((b) => b
+ ..name = 'Foo'
+ ..base = true),
+ equalsDart(r'''
+ base mixin Foo {}
+ '''),
+ );
+ });
+
+ test('should create a mixin with documentations', () {
+ expect(
+ Mixin(
+ (b) => b
+ ..name = 'Foo'
+ ..docs.addAll(
+ const [
+ '/// My favorite mixin.',
+ ],
+ ),
+ ),
+ equalsDart(r'''
+ /// My favorite mixin.
+ mixin Foo {}
+ '''),
+ );
+ });
+
+ 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')])
+ ]),
+ ),
+ equalsDart(r'''
+ @deprecated
+ @Deprecated('This is an old mixin')
+ mixin Foo {}
+ '''),
+ );
+ });
+
+ test('should create a mixin with a generic type', () {
+ expect(
+ Mixin((b) => b
+ ..name = 'List'
+ ..types.add(refer('T'))),
+ equalsDart(r'''
+ mixin List<T> {}
+ '''),
+ );
+ });
+
+ test('should create a mixin with multiple generic types', () {
+ expect(
+ Mixin(
+ (b) => b
+ ..name = 'Map'
+ ..types.addAll([
+ refer('K'),
+ refer('V'),
+ ]),
+ ),
+ equalsDart(r'''
+ mixin Map<K, V> {}
+ '''),
+ );
+ });
+
+ 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))))),
+ equalsDart(r'''
+ mixin Comparable<T extends Comparable<T>> {}
+ '''),
+ );
+ });
+
+ test('should create a mixin on another mixin', () {
+ expect(
+ Mixin((b) => b
+ ..name = 'Foo'
+ ..on = TypeReference((b) => b.symbol = 'Bar')),
+ equalsDart(r'''
+ mixin Foo on Bar {}
+ '''),
+ );
+ });
+
+ 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'))),
+ equalsDart(r'''
+ mixin Foo on Bar implements Foo {}
+ '''),
+ );
+ });
+
+ 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;')))),
+ equalsDart(r'''
+ mixin Foo {
+ foo() {
+ return 1 + 2;
+ }
+ }
+ '''),
+ );
+ });
+}
diff --git a/pkgs/code_builder/test/specs/record_type_test.dart b/pkgs/code_builder/test/specs/record_type_test.dart
new file mode 100644
index 0000000..0084c50
--- /dev/null
+++ b/pkgs/code_builder/test/specs/record_type_test.dart
@@ -0,0 +1,68 @@
+// Copyright (c) 2023, 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:code_builder/code_builder.dart';
+import 'package:test/test.dart';
+
+import '../common.dart';
+
+void main() {
+ late DartEmitter emitter;
+
+ useDartfmt();
+
+ 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));
+
+ test('should create an empty record type', () {
+ expect(RecordType(), equalsDart('()', emitter));
+ });
+
+ 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),
+ equalsDart('(int, List<int>?)?', emitter),
+ );
+ });
+
+ test('should create a record type with one positional field', () {
+ 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<int> 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>, {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));
+ });
+}
diff --git a/pkgs/code_builder/test/specs/type_reference_test.dart b/pkgs/code_builder/test/specs/type_reference_test.dart
new file mode 100644
index 0000000..35365cb
--- /dev/null
+++ b/pkgs/code_builder/test/specs/type_reference_test.dart
@@ -0,0 +1,56 @@
+// Copyright (c) 2020, 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:code_builder/code_builder.dart';
+import 'package:test/test.dart';
+
+import '../common.dart';
+
+void main() {
+ useDartfmt();
+
+ test('should create a nullable type in a pre-Null Safety library', () {
+ expect(
+ TypeReference((b) => b
+ ..symbol = 'Foo'
+ ..isNullable = true),
+ equalsDart(r'''
+ Foo
+ '''),
+ );
+ });
+
+ group('in a Null Safety library', () {
+ late DartEmitter emitter;
+
+ setUp(() => emitter = DartEmitter.scoped(useNullSafetySyntax: true));
+
+ test('should create a nullable type', () {
+ expect(
+ TypeReference((b) => b
+ ..symbol = 'Foo'
+ ..isNullable = true),
+ equalsDart(r'Foo?', emitter),
+ );
+ });
+
+ test('should create a non-nullable type', () {
+ expect(
+ TypeReference((b) => b.symbol = 'Foo'),
+ equalsDart(r'Foo', emitter),
+ );
+ });
+
+ test('should create a type with nullable type arguments', () {
+ expect(
+ TypeReference((b) => b
+ ..symbol = 'List'
+ ..types.add(TypeReference((b) => b
+ ..symbol = 'int'
+ ..isNullable = true))),
+ equalsDart(r'List<int?>', emitter),
+ );
+ });
+ });
+}
diff --git a/pkgs/code_builder/tool/regenerate.sh b/pkgs/code_builder/tool/regenerate.sh
new file mode 100755
index 0000000..722e98a
--- /dev/null
+++ b/pkgs/code_builder/tool/regenerate.sh
@@ -0,0 +1,7 @@
+# Copyright (c) 2023, 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.
+
+dart run build_runner generate-build-script
+dart compile kernel .dart_tool/build/entrypoint/build.dart
+dart .dart_tool/build/entrypoint/build.dill build --delete-conflicting-outputs
diff --git a/pkgs/coverage/.gitignore b/pkgs/coverage/.gitignore
new file mode 100644
index 0000000..1a25097
--- /dev/null
+++ b/pkgs/coverage/.gitignore
@@ -0,0 +1,18 @@
+# Pub
+packages
+pubspec.lock
+build
+.dart_tool/
+.pub
+.packages
+
+# IDEs
+.project
+.settings
+.idea
+*.iml
+
+# Temp files
+*~
+coverage/
+var/
diff --git a/pkgs/coverage/AUTHORS b/pkgs/coverage/AUTHORS
new file mode 100644
index 0000000..7513829
--- /dev/null
+++ b/pkgs/coverage/AUTHORS
@@ -0,0 +1,12 @@
+# Below is a list of people and organizations that have contributed
+# to the coverage project. Names should be added to the list like so:
+#
+# Name/Organization <email address>
+
+Google Inc.
+Achilleas Anagnostopoulos <achilleas.imap.mbox@gmail.com>
+Adam Singer <adammichaelsinger@gmail.com>
+Cédric Belin <cedxbelin@gmail.com>
+Evan Weible <ekweible@gmail.com>
+Günter Zöchbauer <guenter@gzoechbauer.com>
+Will Drach <will.drach@gmail.com>
diff --git a/pkgs/coverage/CHANGELOG.md b/pkgs/coverage/CHANGELOG.md
new file mode 100644
index 0000000..685c1c0
--- /dev/null
+++ b/pkgs/coverage/CHANGELOG.md
@@ -0,0 +1,507 @@
+## 1.11.1
+
+- Update `package:vm_service` constraints to '>=12.0.0 <16.0.0'.
+
+## 1.11.0
+
+- Fix a [bug](https://github.com/dart-lang/tools/issues/685) where the tool
+ would occasionally try to resume an isolate after the VM service had been
+ disposed.
+
+## 1.10.0
+
+- Fix a [bug](https://github.com/dart-lang/tools/issues/520) where tests
+ involving multiple isolates never finish.
+
+## 1.9.2
+
+- Fix repository link in pubspec.
+
+## 1.9.1
+
+- Remove outdated VM service version checks.
+- Move to `dart-lang/tools`.
+
+## 1.9.0
+
+- Require Dart ^3.4
+- Fix bug where some ranges were able to bypass the `--scope-output` filters.
+- Add --ignore-files option allowing to exclude files from coverage reports using glob patterns
+
+## 1.8.0
+
+- Copy collect_coverage's `--scope-output` flag to test_with_coverage.
+
+## 1.7.2
+
+- Update `package:vm_service` constraints to '>=12.0.0 <15.0.0'.
+
+## 1.7.1
+
+- Update `package:vm_service` constraints to '>=12.0.0 <14.0.0'.
+
+## 1.7.0
+
+- Require Dart 3.0.0
+- Update `package:vm_service` constraints to '^12.0.0'.
+- Add `coverableLineCache` parameter to `collect`. This allows the set of
+ coverable lines to be cached between calls to `collect`, avoiding the need to
+ force compile the same libraries repeatedly. This is only useful when running
+ multiple coverage collections over the same libraries.
+
+## 1.6.4
+
+- allow omitting space between `//` and `coverage` in coverage ignore comments
+- allow text after coverage ignore comments
+- throw FormatException when encountering unbalanced ignore comments instead of silently erroring
+- Update `package:vm_service` constraints to '>= 9.4.0 <12.0.0'.
+
+## 1.6.3
+
+- Require Dart 2.18
+- Update `package:vm_service` constraints to '>=9.4.0 <12.0.0'.
+
+## 1.6.2
+
+- Update `package:vm_service` constraints to '>=9.4.0 <11.0.0'.
+
+## 1.6.1
+
+- Handle SentinelExceptions thrown by vm_service.
+
+## 1.6.0
+
+- Update to vm_service 9.4.0.
+- Use IsolateRef.isolateGroupId to speed up coverage collection.
+- Ignore uncoverable abstract methods.
+- Fix bug where 'ingore-line' comments etc are applied even if they're inside
+ string literals.
+- Change the LICENSE file to the standard Dart BSD license.
+
+## 1.5.0
+
+- Support passing extra arguments to `test_with_coverage` which are then passed
+ to `package:test`.
+
+ Example: `dart run coverage:test_with_coverage -- --preset CI`
+
+## 1.4.0
+
+- Added `HitMap.parseJsonSync` which takes a cache of ignored lines which can
+ speedup calls when `checkIgnoredLines` is true and the function is called
+ several times with overlapping files in the input json.
+- Bump the version of vm_service to 9.0.0.
+
+## 1.3.2
+
+- Fix test_with_coverage listening to an unsupported signal on windows.
+- Fix `--reportOn` on windows using incorrect path separators.
+
+## 1.3.1
+
+- Fix running `dart pub global run coverage:test_with_coverage` or
+ `dart run coverage:test_with_coverage`
+
+## 1.3.0
+
+- Bump the minimum Dart SDK version to 2.15.0
+- Add a `--package` flag, which takes the package's root directory, instead of
+ the .package file. Deprecate the `--packages` flag.
+- Deprecate the packagesPath parameter and add packagePath instead, in
+ `HitMap.parseJson`, `HitMap.parseFiles`, `createHitmap`, and `parseCoverage`.
+- Add a new executable to the package, `test_with_coverage`. This simplifies the
+ most common use case of coverage, running all the tests for a package, and
+ generating an lcov.info file.
+- Use the `libraryFilters` option in `getSourceReport` to speed up coverage runs
+ that use `scopedOutput`.
+
+## 1.2.0
+
+- Support branch level coverage information, when running tests in the Dart VM.
+ This is not supported for web tests yet.
+- Add flag `--branch-coverage` (abbr `-b`) to collect_coverage that collects
+ branch coverage information. The VM must also be run with the
+ `--branch-coverage` flag.
+- Add flag `--pretty-print-branch` to format_coverage that works similarly to
+ pretty print, but outputs branch level coverage, rather than line level.
+- Update `--lcov` (abbr `-l`) in format_coverage to output branch level
+ coverage, in addition to line level.
+- Add an optional bool flag to `collect` that controls whether branch coverage
+ is collected.
+- Add a `branchHits` field to `HitMap`.
+- Add support for scraping the service URI from the new Dart VM service message.
+- Correctly parse package_config files on Windows when the root URI is relative.
+
+## 1.1.0
+
+- Support function level coverage information, when running tests in the Dart
+ VM. This is not supported for web tests yet.
+- Add flag `--function-coverage` (abbr `-f`) to collect_coverage that collects
+ function coverage information.
+- Add flag `--pretty-print-func` (abbr `-f`) to format_coverage that works
+ similarly to pretty print, but outputs function level coverage, rather than
+ line level.
+- Update `--lcov` (abbr `-l`) in format_coverage to output function level
+ coverage, in addition to line level.
+- Add an optional bool flag to `collect` that controls whether function coverage
+ is collected.
+- Added `HitMap.parseJson`, `FileHitMaps.merge`, `HitMap.parseFiles`,
+ `HitMap.toJson`, `FileHitMapsFormatter.formatLcov`, and
+ `FileHitMapsFormatter.prettyPrint` that switch from using `Map<int, int>` to
+ represent line coverage to using `HitMap` (which contains both line and
+ function coverage). Document the old versions of these functions as
+ deprecated. We will delete the old functions when we update to coverage
+ version 2.0.0.
+- Ensure `createHitmap` returns a sorted hitmap. This fixes a potential issue
+ with ignore line annotations.
+- Use the `reportLines` flag in `vm_service`'s `getSourceReport` RPC. This
+ typically halves the number of RPCs that the coverage collector needs to run.
+- Require Dart `>=2.14.0`
+
+## 1.0.4
+
+- Updated dependency on `vm_service` package from `>=6.1.0 <8.0.0`to
+ `>=8.1.0 <9.0.0`.
+
+## 1.0.3
+
+- Updated dependency on `vm_service` package from `^6.1.0` to `>=6.1.0 <8.0.0`.
+
+## 1.0.2
+
+- Fix an issue where the `--packages` argument wasn't passed to
+ `format_coverage`.
+
+## 1.0.1
+
+- Allow the chrome `sourceUriProvider` to return `null`.
+
+## 1.0.0
+
+- Migrate to null safety.
+- Removed support for SDK `1.x.x`.
+
+## 0.15.2
+
+- Update `args`, `logging`, and `package_config` deps to allow the latest stable
+ releases.
+
+## 0.15.1
+
+- Updated dependency on `vm_service` package from `>=1.0.0 < 5.0.0` to
+ `>=1.0.0 <7.0.0`.
+
+## 0.15.0
+
+- BREAKING CHANGE: Eliminate the `--package-root` option from
+ `bin/run_and_collect.dart` and `bin/format_coverage.dart` as well as from
+ `runAndCollect` and the `Resolver` constructor.
+
+## 0.14.2
+
+- Fix an issue where `--wait-paused` with `collect` would attempt to collect
+ coverage if no isolates have started.
+
+## 0.14.1
+
+- Updated dependency on `vm_service` package from `>=1.0.0 < 5.0.0` to
+ `>=1.0.0 <6.0.0`.
+
+## 0.14.0
+
+- Add flag `--check-ignore` that is used to ignore lines from coverage depending
+ on the comments.
+
+ Use // coverage:ignore-line to ignore one line. Use // coverage:ignore-start
+ and // coverage:ignore-end to ignore range of lines inclusive. Use //
+ coverage:ignore-file to ignore the whole file.
+
+## 0.13.11
+
+- Revert breaking change in 13.10
+
+## 0.13.10
+
+- Add flag `--check-ignore` that is used to ignore lines from coverage depending
+ on the comments.
+
+ Use // coverage:ignore-line to ignore one line. Use // coverage:ignore-start
+ and // coverage:ignore-end to ignore range of lines inclusive. Use //
+ coverage:ignore-file to ignore the whole file.
+
+## 0.13.9
+
+- Don't crash on empty JSON input files.
+- Loosen the dependency on the `vm_service` package from `>=1.0.0 <4.0.0` to
+ `>=1.0.0 <5.0.0`.
+
+## 0.13.8
+
+- Update to package_config `1.9.0` which supports package_config.json files and
+ should be forwards compatible with `2.0.0`.
+- Deprecate the `packageRoot` argument on `Resolver`.
+
+## 0.13.7
+
+- Loosen the dependency on the `vm_service` package from `>=1.0.0 <3.0.0` to
+ `>=1.0.0 <4.0.0`.
+
+## 0.13.6
+
+- Now consider all `.json` files for the `format_coverage` command.
+
+## 0.13.5
+
+- Update `parseChromeCoverage` to merge coverage information for a given line.
+- Handle source map parse errors in `parseChromeCoverage`. Coverage will not be
+ considered for Dart files that have corresponding invalid source maps.
+
+## 0.13.4
+
+- Add `parseChromeCoverage` for creating a Dart based coverage report from a
+ Chrome coverage report.
+
+## 0.13.3+3 - 2019-12-03
+
+- Re-loosen the dependency on the `vm_service` package from `>=1.0.0 < 2.1.2` to
+ `>=1.0.0 <3.0.0` now that breakage introduced in version `2.1.2` has been
+ resolved. Fixed in:
+ https://github.com/dart-lang/sdk/commit/7a911ce3f1e945f2cbd1967c6109127e3acbab5a.
+
+## 0.13.3+2 - 2019-12-02
+
+- Tighten the dependency on the `vm_service` package from `>=1.0.0 <3.0.0` down
+ to `>=1.0.0 <2.1.2` in order to exclude version `2.1.2` which is broken on the
+ current stable Dart VM due to a missing SDK constraint in its pubspec.yaml.
+ The breakage was introduced in:
+ https://github.com/dart-lang/sdk/commit/9e636b5ab4de850fb19bc262e0686fdf14bfbfc0.
+
+## 0.13.3+1 - 2019-10-10
+
+- Loosen the dependency on the `vm_service` package from `^1.0.0` to
+ `>=1.0.0 <3.0.0`. Ensures dependency version range compatibility with the
+ latest versions of package `test`.
+
+## 0.13.3
+
+- Adds a new named argument to `collect` to filter coverage results by a set of
+ VM isolate IDs.
+- Migrates implementation of VM service protocol library from
+ `package:vm_service_lib`, which is no longer maintained, to
+ `package:vm_service`, which is.
+
+## 0.13.2
+
+- Add new multi-flag option `--scope-output` which restricts coverage output so
+ that only scripts that start with the provided path are considered.
+
+## 0.13.1
+
+- Handle scenario where the VM returns empty coverage information for a range.
+
+## 0.13.0
+
+- BREAKING CHANGE: Skips collecting coverage for `dart:` libraries by default,
+ which provides a significant performance boost. To restore the previous
+ behaviour and collect coverage for these libraries, use the `--include-dart`
+ flag.
+- Disables WebSocket compression for coverage collection. Since almost all
+ coverage collection runs happen over the loopback interface to localhost, this
+ improves performance and reduces CPU usage.
+- Migrates implementation of VM service protocol library from
+ `package:vm_service_client`, which is no longer maintained, to
+ `package:vm_service_lib`, which is.
+
+## 0.12.4
+
+- `collect()` now immediately throws `ArgumentError` if a null URI is passed in
+ the `serviceUri` parameter to avoid a less-easily debuggable null dereference
+ later. See dart-lang/coverage#240 for details.
+
+## 0.12.3
+
+- Fixed dart-lang/coverage#194. During collection, we now track each script by
+ its (unique) VMScriptRef. This ensures we look up the correct script when
+ computing the affected line for each hit token. The hitmap remains URI based,
+ since in the end, we want a single, unified set of line to hitCount mappings
+ per script.
+
+## 0.12.2
+
+- Dart SDK upper bound raised to <3.0.0.
+
+## 0.12.1
+
+- Minor type, dartfmt fixes.
+- Require package:args >= 1.4.0.
+
+## 0.12.0
+
+- BREAKING CHANGE: This version requires Dart SDK 2.0.0-dev.64.1 or later.
+- Strong mode fixes as of Dart SDK 2.0.0-dev.64.1.
+
+## 0.11.0
+
+- BREAKING CHANGE: This version requires Dart SDK 2.0.0-dev.30 or later.
+- Updated to Dart 2.0 constants from dart:convert.
+
+## 0.10.0
+
+- BREAKING CHANGE: `createHitmap` and `mergeHitmaps` now specify generic types
+ (`Map<String, Map<int, int>>`) on their hit map parameter/return value.
+- Updated package:args dependency to 1.0.0.
+
+## 0.9.3
+
+- Strong mode fixes as of Dart SDK 1.24.0.
+- Restrict the SDK lower version constraint to `>=1.21.0`. Required for method
+ generics.
+- Eliminate dependency on package:async.
+
+## 0.9.2
+
+- Strong mode fixes as of Dart SDK 1.22.0.
+
+## 0.9.1
+
+- Temporarily add back support for the `--host` and `--port` options to
+ `collect_coverage`. This is a temporary measure for backwards-compatibility
+ that may stop working on Dart SDKs >= 1.22. See the related
+ [breaking change note](https://groups.google.com/a/dartlang.org/forum/#!msg/announce/VxSw-V5tx8k/wPV0GfX7BwAJ)
+ for the Dart VM service protocol.
+
+## 0.9.0
+
+- BREAKING CHANGE: `collect` no longer supports the `host` and `port`
+ parameters. These are replaced with a `serviceUri` parameter. As of Dart SDK
+ 1.22, the Dart VM will emit Observatory URIs that include an authentication
+ token for security reasons. Automated tools will need to scrape stdout for
+ this URI and pass it to `collect_coverage`.
+- BREAKING CHANGE: `collect_coverage`: the `--host` and `--port` options have
+ been replaced with a `--uri` option. See the above change for details.
+- BREAKING CHANGE: `runAndCollect` now defaults to running in checked mode.
+- Added `extractObservatoryUri`: scrapes an input string for an Observatory URI.
+ Potentially useful for automated tooling after Dart SDK 1.22.
+
+## 0.8.1
+
+- Added optional `checked` parameter to `runAndCollect` to run in checked mode.
+
+## 0.8.0+2
+
+- Strong mode fixes as of Dart SDK 1.20.1.
+
+## 0.8.0+1
+
+- Make strong mode clean.
+
+## 0.8.0
+
+- Moved `Formatter.format` parameters `reportOn` and `basePath` to constructor.
+ Eliminated `pathFilter` parameter.
+
+## 0.7.9
+
+- `format_coverage`: add `--base-directory` option. Source paths in
+ LCOV/pretty-print output are relative to this directory, or absolute if
+ unspecified.
+
+## 0.7.8
+
+- `format_coverage`: support `--packages` option for package specs.
+
+## 0.7.7
+
+- Add fallback URI resolution for Bazel http(s) URIs that don't contain a
+ `packages` path component.
+
+## 0.7.6
+
+- Add [Bazel](http://bazel.io) support to `format_coverage`.
+
+## 0.7.5
+
+- Bugfix in `collect_coverage`: prevent hang if initial VM service connection is
+ slow.
+- Workaround for VM behaviour in which `evaluate:source` ranges may appear in
+ the returned source report manifesting in a crash in `collect_coverage`. These
+ generally correspond to source evaluations in the debugger and add little
+ value to line coverage.
+- `format_coverage`: may be slower for large sets of coverage JSON input files.
+ Unlikely to be an issue due to elimination of `--coverage-dir` VM flag.
+
+## 0.7.4
+
+- Require at least Dart SDK 1.16.0.
+
+- Bugfix in format_coverage: if `--report-on` is not specified, emit all
+ coverage, rather than none.
+
+## 0.7.3
+
+- Added support for the latest Dart SDK.
+
+## 0.7.2
+
+- `Formatter.format` added two optional arguments: `reportOn` and `pathFilter`.
+ They can be used independently to limit the files which are included in the
+ output.
+
+- Added `runAndCollect` API to library.
+
+## 0.7.1
+
+- Added `collect` top-level method.
+
+- Updated support for latest `0.11.0` dev build.
+
+- Replaced `ServiceEvent.eventType` with `ServiceEvent.kind`.
+ - `ServiceEvent.eventType` is deprecated and will be removed in `0.8`.
+
+## 0.7.0
+
+- `format_coverage` no longer emits SDK coverage unless --sdk-root is set
+ explicitly.
+
+- Removed support for collecting coverage from old (<1.9.0) Dart SDKs.
+
+- Removed deprecated `Resolver.pkgRoot`.
+
+## 0.6.5
+
+- Fixed early collection bug when --wait-paused is set.
+
+## 0.6.4
+
+- Optimized formatters and fixed return value of `format` methods.
+
+- Added `Resolver.packageRoot` – deprecated `Resolver.pkgRoot`.
+
+## 0.6.3
+
+- Support the latest release of `args` package.
+
+- Support the latest release of `logging` package.
+
+- Fixed error when trying to access invalid paths.
+
+- Require at least Dart SDK v1.9.0.
+
+## 0.6.2
+
+- Support observatory protocol changes for VM >= 1.11.0.
+
+## 0.6.1
+
+- Support observatory protocol changes for VM >= 1.10.0.
+
+## 0.6.0+1
+
+- Add support for `pub global run`.
+
+## 0.6.0
+
+- Add support for SDK versions >= 1.9.0. For Dartium/content-shell versions past
+ 1.9.0, coverage collection is no longer done over the remote debugging port,
+ but via the observatory port emitted on stdout. Backward compatibility with
+ SDKs back to 1.5.x is provided.
diff --git a/pkgs/coverage/LICENSE b/pkgs/coverage/LICENSE
new file mode 100644
index 0000000..000cd7b
--- /dev/null
+++ b/pkgs/coverage/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2014, 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.
diff --git a/pkgs/coverage/PATENTS b/pkgs/coverage/PATENTS
new file mode 100644
index 0000000..6954196
--- /dev/null
+++ b/pkgs/coverage/PATENTS
@@ -0,0 +1,23 @@
+Additional IP Rights Grant (Patents)
+
+"This implementation" means the copyrightable works distributed by
+Google as part of the Dart Project.
+
+Google hereby grants to you a perpetual, worldwide, non-exclusive,
+no-charge, royalty-free, irrevocable (except as stated in this
+section) patent license to make, have made, use, offer to sell, sell,
+import, transfer, and otherwise run, modify and propagate the contents
+of this implementation of Dart, where such license applies only to
+those patent claims, both currently owned by Google and acquired in
+the future, licensable by Google that are necessarily infringed by
+this implementation of Dart. This grant does not include claims that
+would be infringed only as a consequence of further modification of
+this implementation. If you or your agent or exclusive licensee
+institute or order or agree to the institution of patent litigation
+against any entity (including a cross-claim or counterclaim in a
+lawsuit) alleging that this implementation of Dart or any code
+incorporated within this implementation of Dart constitutes direct or
+contributory patent infringement, or inducement of patent
+infringement, then any patent rights granted to you under this License
+for this implementation of Dart shall terminate as of the date such
+litigation is filed.
diff --git a/pkgs/coverage/README.md b/pkgs/coverage/README.md
new file mode 100644
index 0000000..81736a4
--- /dev/null
+++ b/pkgs/coverage/README.md
@@ -0,0 +1,123 @@
+Coverage provides coverage data collection, manipulation, and formatting for
+Dart.
+
+[](https://github.com/dart-lang/tools/actions/workflows/coverage.yml)
+[](https://coveralls.io/github/dart-lang/tools?branch=main)
+[](https://pub.dev/packages/coverage)
+
+## Tools
+
+`collect_coverage` collects coverage JSON from the Dart VM Service.
+`format_coverage` formats JSON coverage data into either
+[LCOV](https://github.com/linux-test-project/lcov) or pretty-printed format.
+
+#### Install coverage
+
+ dart pub global activate coverage
+
+Consider adding the `dart pub global run` executables directory to your path.
+See
+[Running a script from your PATH](https://dart.dev/tools/pub/cmd/pub-global#running-a-script-from-your-path)
+for more details.
+
+#### Running tests with coverage
+
+For the common use case where you just want to run all your tests, and generate
+an lcov.info file, you can use the test_with_coverage script:
+
+```
+dart pub global run coverage:test_with_coverage
+```
+
+By default, this script assumes it's being run from the root directory of a
+package, and outputs a coverage.json and lcov.info file to ./coverage/
+
+This script is essentially the same as running:
+
+```
+dart run --pause-isolates-on-exit --disable-service-auth-codes --enable-vm-service=8181 test &
+dart pub global run coverage:collect_coverage --wait-paused --uri=http://127.0.0.1:8181/ -o coverage/coverage.json --resume-isolates --scope-output=foo
+dart pub global run coverage:format_coverage --packages=.dart_tool/package_config.json --lcov -i coverage/coverage.json -o coverage/lcov.info
+```
+
+For more complicated use cases, where you want to control each of these stages,
+see the sections below.
+
+#### Collecting coverage from the VM
+
+```
+dart --pause-isolates-on-exit --disable-service-auth-codes --enable-vm-service=NNNN script.dart
+dart pub global run coverage:collect_coverage --uri=http://... -o coverage.json --resume-isolates
+```
+
+or if the `dart pub global run` executables are on your PATH,
+
+```
+collect_coverage --uri=http://... -o coverage.json --resume-isolates
+```
+
+where `--uri` specifies the Dart VM Service URI emitted by the VM.
+
+If `collect_coverage` is invoked before the script from which coverage is to be
+collected, it will wait until it detects a VM observatory to which it can
+connect. An optional `--connect-timeout` may be specified (in seconds). The
+`--wait-paused` flag may be enabled, causing `collect_coverage` to wait until
+all isolates are paused before collecting coverage.
+
+#### Formatting coverage data
+
+```
+dart pub global run coverage:format_coverage --package=app_package -i coverage.json
+```
+
+or if the `dart pub global run` exectuables are on your PATH,
+
+```
+format_coverage --package=app_package -i coverage.json
+```
+
+where `app_package` is the path to the package whose coverage is being collected
+(defaults to the current working directory). If `--sdk-root` is set, Dart SDK
+coverage will also be output.
+
+#### Ignore lines from coverage
+
+- `// coverage:ignore-line` to ignore one line.
+- `// coverage:ignore-start` and `// coverage:ignore-end` to ignore range of
+ lines inclusive.
+- `// coverage:ignore-file` to ignore the whole file.
+
+Then pass `--check-ignore` to `format_coverage`.
+
+#### Function and branch coverage
+
+To gather function level coverage information, pass `--function-coverage` to
+collect_coverage:
+
+```
+dart --pause-isolates-on-exit --disable-service-auth-codes --enable-vm-service=NNNN script.dart
+dart pub global run coverage:collect_coverage --uri=http://... -o coverage.json --resume-isolates --function-coverage
+```
+
+To gather branch level coverage information, pass `--branch-coverage` to _both_
+collect_coverage and the Dart command you're gathering coverage from:
+
+```
+dart --pause-isolates-on-exit --disable-service-auth-codes --enable-vm-service=NNNN --branch-coverage script.dart
+dart pub global run coverage:collect_coverage --uri=http://... -o coverage.json --resume-isolates --branch-coverage
+```
+
+Branch coverage requires Dart VM 2.17.0, with service API v3.56. Function,
+branch, and line coverage can all be gathered at the same time, by combining
+those flags:
+
+```
+dart --pause-isolates-on-exit --disable-service-auth-codes --enable-vm-service=NNNN --branch-coverage script.dart
+dart pub global run coverage:collect_coverage --uri=http://... -o coverage.json --resume-isolates --function-coverage --branch-coverage
+```
+
+These flags can also be passed to test_with_coverage:
+
+```
+pub global run coverage:test_with_coverage --branch-coverage --function-coverage
+```
diff --git a/pkgs/coverage/analysis_options.yaml b/pkgs/coverage/analysis_options.yaml
new file mode 100644
index 0000000..bb1afe0
--- /dev/null
+++ b/pkgs/coverage/analysis_options.yaml
@@ -0,0 +1,17 @@
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-casts: true
+
+ exclude:
+ - var/**
+
+linter:
+ rules:
+ - avoid_slow_async_io
+ - cancel_subscriptions
+ - literal_only_boolean_expressions
+ - prefer_final_locals
+ - sort_constructors_first
+ - sort_unnamed_constructors_first
diff --git a/pkgs/coverage/benchmark/.gitignore b/pkgs/coverage/benchmark/.gitignore
new file mode 100644
index 0000000..1269488
--- /dev/null
+++ b/pkgs/coverage/benchmark/.gitignore
@@ -0,0 +1 @@
+data
diff --git a/pkgs/coverage/benchmark/many_isolates.dart b/pkgs/coverage/benchmark/many_isolates.dart
new file mode 100644
index 0000000..185576a
--- /dev/null
+++ b/pkgs/coverage/benchmark/many_isolates.dart
@@ -0,0 +1,27 @@
+// 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 'dart:isolate';
+import 'dart:math';
+
+Future<void> main(List<String> args, dynamic message) async {
+ if (message == null) {
+ // If there is no message, it means this instance was created by
+ // run_benchmarks.dart. In that case, this is the parent instance that
+ // spawns all the others.
+ var sum = 0;
+ for (var i = 0; i < 10; ++i) {
+ final port = ReceivePort();
+ final isolate =
+ Isolate.spawnUri(Uri.file('many_isolates.dart'), [], port.sendPort);
+ sum += await port.first as int;
+ await isolate;
+ }
+ print('sum = $sum');
+ } else {
+ // If there is a message, it means this instance is one of the child
+ // instances. The message is the port that this instance replies on.
+ (message as SendPort).send(Random().nextInt(1000));
+ }
+}
diff --git a/pkgs/coverage/benchmark/run_benchmarks.dart b/pkgs/coverage/benchmark/run_benchmarks.dart
new file mode 100644
index 0000000..1bd2990
--- /dev/null
+++ b/pkgs/coverage/benchmark/run_benchmarks.dart
@@ -0,0 +1,143 @@
+// 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 'dart:async';
+import 'dart:io';
+
+import 'package:benchmark_harness/benchmark_harness.dart';
+
+import '../bin/collect_coverage.dart' as collect_coverage;
+import '../bin/format_coverage.dart' as format_coverage;
+
+// Runs a test script with various different coverage configurations.
+class CoverageBenchmark extends AsyncBenchmarkBase {
+ CoverageBenchmark(
+ ScoreEmitter emitter,
+ super.name,
+ this.script, {
+ this.gatherCoverage = false,
+ this.functionCoverage = false,
+ this.branchCoverage = false,
+ }) : super(emitter: emitter);
+
+ final String script;
+ final bool gatherCoverage;
+ final bool functionCoverage;
+ final bool branchCoverage;
+ int iteration = 0;
+
+ @override
+ Future<void> run() async {
+ print('Running $name...');
+ final covFile = 'data/$name $iteration coverage.json';
+ final lcovFile = 'data/$name $iteration lcov.info';
+ ++iteration;
+
+ await Process.start(
+ Platform.executable,
+ [
+ if (branchCoverage) '--branch-coverage',
+ 'run',
+ if (gatherCoverage) ...[
+ '--pause-isolates-on-exit',
+ '--disable-service-auth-codes',
+ '--enable-vm-service=1234',
+ ],
+ script,
+ ],
+ mode: ProcessStartMode.detached,
+ );
+ if (gatherCoverage) {
+ await collect_coverage.main([
+ '--wait-paused',
+ '--resume-isolates',
+ '--uri=http://127.0.0.1:1234/',
+ if (branchCoverage) '--branch-coverage',
+ if (functionCoverage) '--function-coverage',
+ '-o',
+ covFile,
+ ]);
+
+ await format_coverage.main([
+ '--lcov',
+ '--check-ignore',
+ '-i',
+ covFile,
+ '-o',
+ lcovFile,
+ ]);
+ }
+ }
+}
+
+// Emitter that just captures the value.
+class CaptureEmitter implements ScoreEmitter {
+ late double capturedValue;
+
+ @override
+ void emit(String testName, double value) {
+ capturedValue = value;
+ }
+}
+
+// Prints a JSON representation of the benchmark results, in a format compatible
+// with the github benchmark action.
+class JsonEmitter implements ScoreEmitter {
+ JsonEmitter(this._baseline);
+
+ final double _baseline;
+ final _results = <String, double>{};
+
+ @override
+ void emit(String testName, double value) {
+ _results[testName] = value;
+ }
+
+ String write() => '[${_results.entries.map((entry) => """{
+ "name": "${entry.key}",
+ "unit": "times slower",
+ "value": ${(entry.value / _baseline).toStringAsFixed(2)}
+}""").join(',\n')}]';
+}
+
+Future<void> runBenchmark(CoverageBenchmark benchmark) async {
+ for (var i = 0; i < 3; ++i) {
+ try {
+ await benchmark.report().timeout(const Duration(minutes: 2));
+ return;
+ } on TimeoutException {
+ print('Timed out');
+ }
+ }
+ print('Timed out too many times. Giving up.');
+ exit(127);
+}
+
+Future<String> runBenchmarkSet(String name, String script) async {
+ final captureEmitter = CaptureEmitter();
+ await runBenchmark(
+ CoverageBenchmark(captureEmitter, '$name - no coverage', script));
+ final benchmarkBaseline = captureEmitter.capturedValue;
+
+ final emitter = JsonEmitter(benchmarkBaseline);
+ await runBenchmark(CoverageBenchmark(
+ emitter, '$name - basic coverage', script,
+ gatherCoverage: true));
+ await runBenchmark(CoverageBenchmark(
+ emitter, '$name - function coverage', script,
+ gatherCoverage: true, functionCoverage: true));
+ await runBenchmark(CoverageBenchmark(
+ emitter, '$name - branch coverage', script,
+ gatherCoverage: true, branchCoverage: true));
+ return emitter.write();
+}
+
+Future<void> main() async {
+ // Assume this script was started from the root coverage directory. Change to
+ // the benchmark directory.
+ Directory.current = 'benchmark';
+ final result = await runBenchmarkSet('Many isolates', 'many_isolates.dart');
+ await File('data/benchmark_result.json').writeAsString(result);
+ exit(0);
+}
diff --git a/pkgs/coverage/bin/collect_coverage.dart b/pkgs/coverage/bin/collect_coverage.dart
new file mode 100644
index 0000000..b02ac21
--- /dev/null
+++ b/pkgs/coverage/bin/collect_coverage.dart
@@ -0,0 +1,149 @@
+// Copyright (c) 2014, 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' show json;
+import 'dart:io';
+
+import 'package:args/args.dart';
+import 'package:coverage/src/collect.dart';
+import 'package:logging/logging.dart';
+import 'package:stack_trace/stack_trace.dart';
+
+Future<void> main(List<String> arguments) async {
+ Logger.root.level = Level.WARNING;
+ Logger.root.onRecord.listen((LogRecord rec) {
+ print('${rec.level.name}: ${rec.time}: ${rec.message}');
+ });
+
+ final options = _parseArgs(arguments);
+ await Chain.capture(() async {
+ final coverage = await collect(options.serviceUri, options.resume,
+ options.waitPaused, options.includeDart, options.scopedOutput,
+ timeout: options.timeout,
+ functionCoverage: options.functionCoverage,
+ branchCoverage: options.branchCoverage);
+ options.out.write(json.encode(coverage));
+ await options.out.close();
+ }, onError: (dynamic error, Chain chain) {
+ stderr.writeln(error);
+ stderr.writeln(chain.terse);
+ // See http://www.retro11.de/ouxr/211bsd/usr/include/sysexits.h.html
+ // EX_SOFTWARE
+ exit(70);
+ });
+}
+
+class Options {
+ Options(
+ this.serviceUri,
+ this.out,
+ this.timeout,
+ this.waitPaused,
+ this.resume,
+ this.includeDart,
+ this.functionCoverage,
+ this.branchCoverage,
+ this.scopedOutput);
+
+ final Uri serviceUri;
+ final IOSink out;
+ final Duration? timeout;
+ final bool waitPaused;
+ final bool resume;
+ final bool includeDart;
+ final bool functionCoverage;
+ final bool branchCoverage;
+ final Set<String> scopedOutput;
+}
+
+Options _parseArgs(List<String> arguments) {
+ final parser = ArgParser()
+ ..addOption('host',
+ abbr: 'H',
+ help: 'remote VM host. DEPRECATED: use --uri',
+ defaultsTo: '127.0.0.1')
+ ..addOption('port',
+ abbr: 'p',
+ help: 'remote VM port. DEPRECATED: use --uri',
+ defaultsTo: '8181')
+ ..addOption('uri', abbr: 'u', help: 'VM observatory service URI')
+ ..addOption('out',
+ abbr: 'o', defaultsTo: 'stdout', help: 'output: may be file or stdout')
+ ..addOption('connect-timeout',
+ abbr: 't', help: 'connect timeout in seconds')
+ ..addMultiOption('scope-output',
+ help: 'restrict coverage results so that only scripts that start with '
+ 'the provided package path are considered')
+ ..addFlag('wait-paused',
+ abbr: 'w',
+ defaultsTo: false,
+ help: 'wait for all isolates to be paused before collecting coverage')
+ ..addFlag('resume-isolates',
+ abbr: 'r', defaultsTo: false, help: 'resume all isolates on exit')
+ ..addFlag('include-dart',
+ abbr: 'd', defaultsTo: false, help: 'include "dart:" libraries')
+ ..addFlag('function-coverage',
+ abbr: 'f', defaultsTo: false, help: 'Collect function coverage info')
+ ..addFlag('branch-coverage',
+ abbr: 'b',
+ defaultsTo: false,
+ help: 'Collect branch coverage info (Dart VM must also be run with '
+ '--branch-coverage for this to work)')
+ ..addFlag('help', abbr: 'h', negatable: false, help: 'show this help');
+
+ final args = parser.parse(arguments);
+
+ void printUsage() {
+ print('Usage: dart collect_coverage.dart --uri=http://... [OPTION...]\n');
+ print(parser.usage);
+ }
+
+ Never fail(String message) {
+ print('Error: $message\n');
+ printUsage();
+ exit(1);
+ }
+
+ if (args['help'] as bool) {
+ printUsage();
+ exit(0);
+ }
+
+ Uri serviceUri;
+ if (args['uri'] == null) {
+ // TODO(cbracken) eliminate --host and --port support when VM defaults to
+ // requiring an auth token. Estimated for Dart SDK 1.22.
+ serviceUri = Uri.parse('http://${args['host']}:${args['port']}/');
+ } else {
+ try {
+ serviceUri = Uri.parse(args['uri'] as String);
+ } on FormatException {
+ fail('Invalid service URI specified: ${args['uri']}');
+ }
+ }
+
+ final scopedOutput = args['scope-output'] as List<String>;
+ IOSink out;
+ if (args['out'] == 'stdout') {
+ out = stdout;
+ } else {
+ final outfile = File(args['out'] as String)..createSync(recursive: true);
+ out = outfile.openWrite();
+ }
+ final timeout = (args['connect-timeout'] == null)
+ ? null
+ : Duration(seconds: int.parse(args['connect-timeout'] as String));
+ return Options(
+ serviceUri,
+ out,
+ timeout,
+ args['wait-paused'] as bool,
+ args['resume-isolates'] as bool,
+ args['include-dart'] as bool,
+ args['function-coverage'] as bool,
+ args['branch-coverage'] as bool,
+ scopedOutput.toSet(),
+ );
+}
diff --git a/pkgs/coverage/bin/format_coverage.dart b/pkgs/coverage/bin/format_coverage.dart
new file mode 100644
index 0000000..d24f60d
--- /dev/null
+++ b/pkgs/coverage/bin/format_coverage.dart
@@ -0,0 +1,310 @@
+// 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';
+
+import 'package:args/args.dart';
+import 'package:coverage/coverage.dart';
+import 'package:glob/glob.dart';
+import 'package:path/path.dart' as p;
+
+/// [Environment] stores gathered arguments information.
+class Environment {
+ Environment({
+ required this.baseDirectory,
+ required this.bazel,
+ required this.bazelWorkspace,
+ required this.checkIgnore,
+ required this.input,
+ required this.lcov,
+ required this.output,
+ required this.packagesPath,
+ required this.packagePath,
+ required this.prettyPrint,
+ required this.prettyPrintFunc,
+ required this.prettyPrintBranch,
+ required this.reportOn,
+ required this.ignoreFiles,
+ required this.sdkRoot,
+ required this.verbose,
+ required this.workers,
+ });
+
+ String? baseDirectory;
+ bool bazel;
+ String bazelWorkspace;
+ bool checkIgnore;
+ String input;
+ bool lcov;
+ IOSink output;
+ String? packagesPath;
+ String packagePath;
+ bool prettyPrint;
+ bool prettyPrintFunc;
+ bool prettyPrintBranch;
+ List<String>? reportOn;
+ List<String>? ignoreFiles;
+ String? sdkRoot;
+ bool verbose;
+ int workers;
+}
+
+Future<void> main(List<String> arguments) async {
+ final env = parseArgs(arguments);
+
+ final files = filesToProcess(env.input);
+ if (env.verbose) {
+ print('Environment:');
+ print(' # files: ${files.length}');
+ print(' # workers: ${env.workers}');
+ print(' sdk-root: ${env.sdkRoot}');
+ print(' package-path: ${env.packagePath}');
+ print(' packages-path: ${env.packagesPath}');
+ print(' report-on: ${env.reportOn}');
+ print(' check-ignore: ${env.checkIgnore}');
+ }
+
+ final clock = Stopwatch()..start();
+ final hitmap = await HitMap.parseFiles(
+ files,
+ checkIgnoredLines: env.checkIgnore,
+ // ignore: deprecated_member_use_from_same_package
+ packagesPath: env.packagesPath,
+ packagePath: env.packagePath,
+ );
+
+ // All workers are done. Process the data.
+ if (env.verbose) {
+ print('Done creating global hitmap. Took ${clock.elapsedMilliseconds} ms.');
+ }
+
+ final ignoreGlobs = env.ignoreFiles?.map(Glob.new).toSet();
+
+ String output;
+ final resolver = env.bazel
+ ? BazelResolver(workspacePath: env.bazelWorkspace)
+ : await Resolver.create(
+ packagesPath: env.packagesPath,
+ packagePath: env.packagePath,
+ sdkRoot: env.sdkRoot,
+ );
+ final loader = Loader();
+ if (env.prettyPrint) {
+ output = await hitmap.prettyPrint(resolver, loader,
+ reportOn: env.reportOn,
+ ignoreGlobs: ignoreGlobs,
+ reportFuncs: env.prettyPrintFunc,
+ reportBranches: env.prettyPrintBranch);
+ } else {
+ assert(env.lcov);
+ output = hitmap.formatLcov(resolver,
+ reportOn: env.reportOn,
+ ignoreGlobs: ignoreGlobs,
+ basePath: env.baseDirectory);
+ }
+
+ env.output.write(output);
+ await env.output.flush();
+ if (env.verbose) {
+ print('Done flushing output. Took ${clock.elapsedMilliseconds} ms.');
+ }
+
+ if (env.verbose) {
+ if (resolver.failed.isNotEmpty) {
+ print('Failed to resolve:');
+ for (var error in resolver.failed.toSet()) {
+ print(' $error');
+ }
+ }
+ if (loader.failed.isNotEmpty) {
+ print('Failed to load:');
+ for (var error in loader.failed.toSet()) {
+ print(' $error');
+ }
+ }
+ }
+ await env.output.close();
+}
+
+/// Checks the validity of the provided arguments. Does not initialize actual
+/// processing.
+Environment parseArgs(List<String> arguments) {
+ final parser = ArgParser();
+
+ parser
+ ..addOption('sdk-root', abbr: 's', help: 'path to the SDK root')
+ ..addOption('packages', help: '[DEPRECATED] path to the package spec file')
+ ..addOption('package',
+ help: 'root directory of the package', defaultsTo: '.')
+ ..addOption('in', abbr: 'i', help: 'input(s): may be file or directory')
+ ..addOption('out',
+ abbr: 'o', defaultsTo: 'stdout', help: 'output: may be file or stdout')
+ ..addMultiOption('report-on',
+ help: 'which directories or files to report coverage on')
+ ..addOption('workers',
+ abbr: 'j', defaultsTo: '1', help: 'number of workers')
+ ..addOption('bazel-workspace',
+ defaultsTo: '', help: 'Bazel workspace directory')
+ ..addOption('base-directory',
+ abbr: 'b',
+ help: 'the base directory relative to which source paths are output')
+ ..addFlag('bazel',
+ defaultsTo: false, help: 'use Bazel-style path resolution')
+ ..addFlag('pretty-print',
+ abbr: 'r',
+ negatable: false,
+ help: 'convert line coverage data to pretty print format')
+ ..addFlag('pretty-print-func',
+ abbr: 'f',
+ negatable: false,
+ help: 'convert function coverage data to pretty print format')
+ ..addFlag('pretty-print-branch',
+ negatable: false,
+ help: 'convert branch coverage data to pretty print format')
+ ..addFlag('lcov',
+ abbr: 'l',
+ negatable: false,
+ help: 'convert coverage data to lcov format')
+ ..addFlag('verbose', abbr: 'v', negatable: false, help: 'verbose output')
+ ..addFlag(
+ 'check-ignore',
+ abbr: 'c',
+ negatable: false,
+ help: 'check for coverage ignore comments.'
+ ' Not supported in web coverage.',
+ )
+ ..addMultiOption(
+ 'ignore-files',
+ defaultsTo: [],
+ help: 'Ignore files by glob patterns',
+ )
+ ..addFlag('help', abbr: 'h', negatable: false, help: 'show this help');
+
+ final args = parser.parse(arguments);
+
+ void printUsage() {
+ print('Usage: dart format_coverage.dart [OPTION...]\n');
+ print(parser.usage);
+ }
+
+ Never fail(String msg) {
+ print('\n$msg\n');
+ printUsage();
+ exit(1);
+ }
+
+ if (args['help'] as bool) {
+ printUsage();
+ exit(0);
+ }
+
+ var sdkRoot = args['sdk-root'] as String?;
+ if (sdkRoot != null) {
+ sdkRoot = p.normalize(p.join(p.absolute(sdkRoot), 'lib'));
+ if (!FileSystemEntity.isDirectorySync(sdkRoot)) {
+ fail('Provided SDK root "${args["sdk-root"]}" is not a valid SDK '
+ 'top-level directory');
+ }
+ }
+
+ final packagesPath = args['packages'] as String?;
+ if (packagesPath != null) {
+ if (!FileSystemEntity.isFileSync(packagesPath)) {
+ fail('Package spec "${args["packages"]}" not found, or not a file.');
+ }
+ }
+
+ final packagePath = args['package'] as String;
+ if (!FileSystemEntity.isDirectorySync(packagePath)) {
+ fail('Package spec "${args["package"]}" not found, or not a directory.');
+ }
+
+ if (args['in'] == null) fail('No input files given.');
+ final input = p.absolute(p.normalize(args['in'] as String));
+ if (!FileSystemEntity.isDirectorySync(input) &&
+ !FileSystemEntity.isFileSync(input)) {
+ fail('Provided input "${args["in"]}" is neither a directory nor a file.');
+ }
+
+ IOSink output;
+ if (args['out'] == 'stdout') {
+ output = stdout;
+ } else {
+ final outpath = p.absolute(p.normalize(args['out'] as String));
+ final outfile = File(outpath)..createSync(recursive: true);
+ output = outfile.openWrite();
+ }
+
+ final reportOnRaw = args['report-on'] as List<String>;
+ final reportOn = reportOnRaw.isNotEmpty ? reportOnRaw : null;
+
+ final bazel = args['bazel'] as bool;
+ final bazelWorkspace = args['bazel-workspace'] as String;
+ if (bazelWorkspace.isNotEmpty && !bazel) {
+ stderr.writeln('warning: ignoring --bazel-workspace: --bazel not set');
+ }
+
+ String? baseDirectory;
+ if (args['base-directory'] != null) {
+ baseDirectory = p.absolute(args['base-directory'] as String);
+ }
+
+ final lcov = args['lcov'] as bool;
+ var prettyPrint = args['pretty-print'] as bool;
+ final prettyPrintFunc = args['pretty-print-func'] as bool;
+ final prettyPrintBranch = args['pretty-print-branch'] as bool;
+ final numModesChosen = (prettyPrint ? 1 : 0) +
+ (prettyPrintFunc ? 1 : 0) +
+ (prettyPrintBranch ? 1 : 0) +
+ (lcov ? 1 : 0);
+ if (numModesChosen > 1) {
+ fail('Choose one of the pretty-print modes or lcov output');
+ }
+
+ // The pretty printer is used by all modes other than lcov.
+ if (!lcov) prettyPrint = true;
+
+ int workers;
+ try {
+ workers = int.parse('${args["workers"]}');
+ } catch (e) {
+ fail('Invalid worker count: $e');
+ }
+
+ final checkIgnore = args['check-ignore'] as bool;
+ final ignoredGlobs = args['ignore-files'] as List<String>;
+ final verbose = args['verbose'] as bool;
+ 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);
+}
+
+/// Given an absolute path absPath, this function returns a [List] of files
+/// are contained by it if it is a directory, or a [List] containing the file if
+/// it is a file.
+List<File> filesToProcess(String absPath) {
+ if (FileSystemEntity.isDirectorySync(absPath)) {
+ return Directory(absPath)
+ .listSync(recursive: true)
+ .whereType<File>()
+ .where((e) => e.path.endsWith('.json'))
+ .toList();
+ }
+ return <File>[File(absPath)];
+}
diff --git a/pkgs/coverage/bin/run_and_collect.dart b/pkgs/coverage/bin/run_and_collect.dart
new file mode 100644
index 0000000..c255d01
--- /dev/null
+++ b/pkgs/coverage/bin/run_and_collect.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2017, 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 'package:coverage/src/run_and_collect.dart';
+
+Future<void> main(List<String> args) async {
+ final Map results = await runAndCollect(args[0]);
+ print(results);
+}
diff --git a/pkgs/coverage/bin/test_with_coverage.dart b/pkgs/coverage/bin/test_with_coverage.dart
new file mode 100644
index 0000000..010e608
--- /dev/null
+++ b/pkgs/coverage/bin/test_with_coverage.dart
@@ -0,0 +1,229 @@
+// 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 'dart:async';
+import 'dart:io';
+
+import 'package:args/args.dart';
+import 'package:coverage/src/util.dart'
+ show StandardOutExtension, extractVMServiceUri;
+import 'package:package_config/package_config.dart';
+import 'package:path/path.dart' as path;
+
+import 'collect_coverage.dart' as collect_coverage;
+import 'format_coverage.dart' as format_coverage;
+
+final _allProcesses = <Process>[];
+
+Future<void> _dartRun(List<String> args,
+ {void Function(String)? onStdout, String? workingDir}) async {
+ final process = await Process.start(
+ Platform.executable,
+ args,
+ workingDirectory: workingDir,
+ );
+ _allProcesses.add(process);
+ final broadStdout = process.stdout.asBroadcastStream();
+ broadStdout.listen(stdout.add);
+ if (onStdout != null) {
+ broadStdout.lines().listen(onStdout);
+ }
+ process.stderr.listen(stderr.add);
+ final result = await process.exitCode;
+ if (result != 0) {
+ throw ProcessException(Platform.executable, args, '', result);
+ }
+}
+
+Future<String?> _packageNameFromConfig(String packageDir) async {
+ final config = await findPackageConfig(Directory(packageDir));
+ return config?.packageOf(Uri.directory(packageDir))?.name;
+}
+
+void _watchExitSignal(ProcessSignal signal) {
+ signal.watch().listen((sig) {
+ for (final process in _allProcesses) {
+ process.kill(sig);
+ }
+ exit(1);
+ });
+}
+
+ArgParser _createArgParser() => ArgParser()
+ ..addOption(
+ 'package',
+ help: 'Root directory of the package to test.',
+ defaultsTo: '.',
+ )
+ ..addOption(
+ 'package-name',
+ help: 'Name of the package to test. '
+ 'Deduced from --package if not provided.',
+ )
+ ..addOption('port', help: 'VM service port.', defaultsTo: '8181')
+ ..addOption(
+ 'out',
+ abbr: 'o',
+ help: 'Output directory. Defaults to <package-dir>/coverage.',
+ )
+ ..addOption('test', help: 'Test script to run.', defaultsTo: 'test')
+ ..addFlag(
+ 'function-coverage',
+ abbr: 'f',
+ defaultsTo: false,
+ help: 'Collect function coverage info.',
+ )
+ ..addFlag(
+ 'branch-coverage',
+ abbr: 'b',
+ defaultsTo: false,
+ help: 'Collect branch coverage info.',
+ )
+ ..addMultiOption('scope-output',
+ 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.')
+ ..addFlag('help', abbr: 'h', negatable: false, help: 'Show this help.');
+
+class Flags {
+ Flags(
+ this.packageDir,
+ this.packageName,
+ this.outDir,
+ this.port,
+ this.testScript,
+ this.functionCoverage,
+ this.branchCoverage,
+ this.scopeOutput, {
+ required this.rest,
+ });
+
+ final String packageDir;
+ final String packageName;
+ final String outDir;
+ final String port;
+ final String testScript;
+ final bool functionCoverage;
+ final bool branchCoverage;
+ final List<String> scopeOutput;
+ final List<String> rest;
+}
+
+Future<Flags> _parseArgs(List<String> arguments) async {
+ final parser = _createArgParser();
+ final args = parser.parse(arguments);
+
+ void printUsage() {
+ print('''
+Runs tests and collects coverage for a package.
+
+By default this script assumes it's being run from the root directory of a
+package, and outputs a coverage.json and lcov.info to ./coverage/
+
+Usage: test_with_coverage [OPTIONS...] [-- <test script OPTIONS>]
+
+${parser.usage}
+''');
+ }
+
+ Never fail(String msg) {
+ print('\n$msg\n');
+ printUsage();
+ exit(1);
+ }
+
+ if (args['help'] as bool) {
+ printUsage();
+ exit(0);
+ }
+
+ final packageDir = path.canonicalize(args['package'] as String);
+ if (!FileSystemEntity.isDirectorySync(packageDir)) {
+ fail('--package is not a valid directory.');
+ }
+
+ final packageName = (args['package-name'] as String?) ??
+ await _packageNameFromConfig(packageDir);
+ if (packageName == null) {
+ fail(
+ "Couldn't figure out package name from --package. Make sure this is a "
+ 'package directory, or try passing --package-name explicitly.',
+ );
+ }
+
+ 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<String>,
+ rest: args.rest,
+ );
+}
+
+Future<void> main(List<String> arguments) async {
+ final flags = await _parseArgs(arguments);
+ final outJson = path.join(flags.outDir, 'coverage.json');
+ final outLcov = path.join(flags.outDir, 'lcov.info');
+
+ if (!FileSystemEntity.isDirectorySync(flags.outDir)) {
+ await Directory(flags.outDir).create(recursive: true);
+ }
+
+ _watchExitSignal(ProcessSignal.sighup);
+ _watchExitSignal(ProcessSignal.sigint);
+ if (!Platform.isWindows) {
+ _watchExitSignal(ProcessSignal.sigterm);
+ }
+
+ final serviceUriCompleter = Completer<Uri>();
+ final testProcess = _dartRun(
+ [
+ if (flags.branchCoverage) '--branch-coverage',
+ 'run',
+ '--pause-isolates-on-exit',
+ '--disable-service-auth-codes',
+ '--enable-vm-service=${flags.port}',
+ flags.testScript,
+ ...flags.rest,
+ ],
+ onStdout: (line) {
+ if (!serviceUriCompleter.isCompleted) {
+ final uri = extractVMServiceUri(line);
+ if (uri != null) {
+ serviceUriCompleter.complete(uri);
+ }
+ }
+ },
+ );
+ final serviceUri = await serviceUriCompleter.future;
+
+ final scopes =
+ flags.scopeOutput.isEmpty ? [flags.packageName] : flags.scopeOutput;
+ await collect_coverage.main([
+ '--wait-paused',
+ '--resume-isolates',
+ '--uri=$serviceUri',
+ for (final scope in scopes) '--scope-output=$scope',
+ if (flags.branchCoverage) '--branch-coverage',
+ if (flags.functionCoverage) '--function-coverage',
+ '-o',
+ outJson,
+ ]);
+ await testProcess;
+
+ await format_coverage.main([
+ '--lcov',
+ '--check-ignore',
+ '--package=${flags.packageDir}',
+ '-i',
+ outJson,
+ '-o',
+ outLcov,
+ ]);
+ exit(0);
+}
diff --git a/pkgs/coverage/lib/coverage.dart b/pkgs/coverage/lib/coverage.dart
new file mode 100644
index 0000000..9cc81ab
--- /dev/null
+++ b/pkgs/coverage/lib/coverage.dart
@@ -0,0 +1,10 @@
+// Copyright (c) 2014, 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/chrome.dart';
+export 'src/collect.dart';
+export 'src/formatter.dart';
+export 'src/hitmap.dart' hide hitmapToJson;
+export 'src/resolver.dart';
+export 'src/run_and_collect.dart';
diff --git a/pkgs/coverage/lib/src/chrome.dart b/pkgs/coverage/lib/src/chrome.dart
new file mode 100644
index 0000000..2a97696
--- /dev/null
+++ b/pkgs/coverage/lib/src/chrome.dart
@@ -0,0 +1,171 @@
+// Copyright (c) 2020, 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/parser.dart';
+
+import 'hitmap.dart';
+
+/// Returns a Dart based hit-map containing coverage report for the provided
+/// Chrome [preciseCoverage].
+///
+/// [sourceProvider] returns the source content for the Chrome scriptId, or null
+/// if not available.
+///
+/// [sourceMapProvider] returns the associated source map content for the Chrome
+/// scriptId, or null if not available.
+///
+/// [sourceUriProvider] returns the uri for the provided sourceUrl and
+/// associated scriptId, or null if not available.
+///
+/// Chrome coverage information for which the corresponding source map or source
+/// content is null will be ignored.
+Future<Map<String, dynamic>> parseChromeCoverage(
+ List<Map<String, dynamic>> preciseCoverage,
+ Future<String?> Function(String scriptId) sourceProvider,
+ Future<String?> Function(String scriptId) sourceMapProvider,
+ Future<Uri?> Function(String sourceUrl, String scriptId) sourceUriProvider,
+) async {
+ final coverageReport = <Uri, Map<int, bool>>{};
+ for (var entry in preciseCoverage) {
+ final scriptId = entry['scriptId'] as String;
+
+ final mapResponse = await sourceMapProvider(scriptId);
+ if (mapResponse == null) continue;
+
+ SingleMapping mapping;
+ try {
+ mapping = parse(mapResponse) as SingleMapping;
+ } on FormatException {
+ continue;
+ // ignore: avoid_catching_errors
+ } on ArgumentError {
+ continue;
+ }
+
+ final compiledSource = await sourceProvider(scriptId);
+ if (compiledSource == null) continue;
+
+ final coverageInfo = _coverageInfoFor(entry);
+ final offsetCoverage = _offsetCoverage(coverageInfo, compiledSource.length);
+ final coveredPositions = _coveredPositions(compiledSource, offsetCoverage);
+
+ for (var lineEntry in mapping.lines) {
+ for (var columnEntry in lineEntry.entries) {
+ final sourceUrlId = columnEntry.sourceUrlId;
+ if (sourceUrlId == null) continue;
+ final sourceUrl = mapping.urls[sourceUrlId];
+
+ // Ignore coverage information for the SDK.
+ if (sourceUrl.startsWith('org-dartlang-sdk:')) continue;
+
+ final uri = await sourceUriProvider(sourceUrl, scriptId);
+ if (uri == null) continue;
+ final coverage = coverageReport.putIfAbsent(uri, () => <int, bool>{});
+
+ final sourceLine = columnEntry.sourceLine!;
+ final current = coverage[sourceLine + 1] ?? false;
+ coverage[sourceLine + 1] = current ||
+ coveredPositions.contains(
+ _Position(lineEntry.line + 1, columnEntry.column + 1));
+ }
+ }
+ }
+
+ final coverageHitMaps = <Uri, HitMap>{};
+ coverageReport.forEach((uri, coverage) {
+ final hitMap = HitMap();
+ for (var line in coverage.keys.toList()..sort()) {
+ hitMap.lineHits[line] = coverage[line]! ? 1 : 0;
+ }
+ coverageHitMaps[uri] = hitMap;
+ });
+
+ final allCoverage = <Map<String, dynamic>>[];
+ coverageHitMaps.forEach((uri, hitMap) {
+ allCoverage.add(hitmapToJson(hitMap, uri));
+ });
+ return <String, dynamic>{'type': 'CodeCoverage', 'coverage': allCoverage};
+}
+
+/// Returns all covered positions in a provided source.
+Set<_Position> _coveredPositions(
+ String compiledSource, List<bool> offsetCoverage) {
+ final positions = <_Position>{};
+ // Line is 1 based.
+ var line = 1;
+ // Column is 1 based.
+ var column = 0;
+ for (var offset = 0; offset < compiledSource.length; offset++) {
+ if (compiledSource[offset] == '\n') {
+ line++;
+ column = 0;
+ } else {
+ column++;
+ }
+ if (offsetCoverage[offset]) positions.add(_Position(line, column));
+ }
+ return positions;
+}
+
+/// Returns coverage information for a Chrome entry.
+List<_CoverageInfo> _coverageInfoFor(Map<String, dynamic> entry) {
+ final result = <_CoverageInfo>[];
+ for (var functions
+ in (entry['functions'] as List).cast<Map<String, dynamic>>()) {
+ for (var range
+ in (functions['ranges'] as List).cast<Map<String, dynamic>>()) {
+ result.add(_CoverageInfo(
+ range['startOffset'] as int,
+ range['endOffset'] as int,
+ (range['count'] as int) > 0,
+ ));
+ }
+ }
+ return result;
+}
+
+/// Returns the coverage information for each offset.
+List<bool> _offsetCoverage(List<_CoverageInfo> coverageInfo, int sourceLength) {
+ final offsetCoverage = List.filled(sourceLength, false);
+
+ // Sort coverage information by their size.
+ // Coverage information takes granularity as precedence.
+ coverageInfo.sort((a, b) =>
+ (b.endOffset - b.startOffset).compareTo(a.endOffset - a.startOffset));
+
+ for (var range in coverageInfo) {
+ for (var i = range.startOffset; i < range.endOffset; i++) {
+ offsetCoverage[i] = range.isCovered;
+ }
+ }
+
+ return offsetCoverage;
+}
+
+class _CoverageInfo {
+ _CoverageInfo(this.startOffset, this.endOffset, this.isCovered);
+
+ /// 0 based byte offset.
+ final int startOffset;
+
+ /// 0 based byte offset.
+ final int endOffset;
+
+ final bool isCovered;
+}
+
+/// A covered position in a source file where [line] and [column] are 1 based.
+class _Position {
+ _Position(this.line, this.column);
+
+ final int line;
+ final int column;
+
+ @override
+ int get hashCode => Object.hash(line, column);
+
+ @override
+ bool operator ==(Object o) =>
+ o is _Position && o.line == line && o.column == column;
+}
diff --git a/pkgs/coverage/lib/src/collect.dart b/pkgs/coverage/lib/src/collect.dart
new file mode 100644
index 0000000..76227ba
--- /dev/null
+++ b/pkgs/coverage/lib/src/collect.dart
@@ -0,0 +1,447 @@
+// Copyright (c) 2014, 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';
+
+import 'package:vm_service/vm_service.dart';
+
+import 'hitmap.dart';
+import 'isolate_paused_listener.dart';
+import 'util.dart';
+
+const _retryInterval = Duration(milliseconds: 200);
+const _debugTokenPositions = bool.fromEnvironment('DEBUG_COVERAGE');
+
+/// Collects coverage for all isolates in the running VM.
+///
+/// Collects a hit-map containing merged coverage for all isolates in the Dart
+/// VM associated with the specified [serviceUri]. Returns a map suitable for
+/// input to the coverage formatters that ship with this package.
+///
+/// [serviceUri] must specify the http/https URI of the service port of a
+/// running Dart VM and must not be null.
+///
+/// If [resume] is true, all isolates will be resumed once coverage collection
+/// is complete.
+///
+/// If [waitPaused] is true, collection will not begin for an isolate until it
+/// is in the paused state.
+///
+/// If [includeDart] is true, code coverage for core `dart:*` libraries will be
+/// collected.
+///
+/// If [functionCoverage] is true, function coverage information will be
+/// collected.
+///
+/// If [branchCoverage] is true, branch coverage information will be collected.
+/// This will only work correctly if the target VM was run with the
+/// --branch-coverage flag.
+///
+/// If [scopedOutput] is non-empty, coverage will be restricted so that only
+/// scripts that start with any of the provided paths are considered.
+///
+/// If [isolateIds] is set, the coverage gathering will be restricted to only
+/// those VM isolates.
+///
+/// If [coverableLineCache] is set, the collector will avoid recompiling
+/// libraries it has already seen (see VmService.getSourceReport's
+/// librariesAlreadyCompiled parameter). This is only useful when doing more
+/// than one [collect] call over the same libraries. Pass an empty map to the
+/// first call, and then pass the same map to all subsequent calls.
+///
+/// [serviceOverrideForTesting] is for internal testing only, and should not be
+/// set by users.
+Future<Map<String, dynamic>> collect(Uri serviceUri, bool resume,
+ bool waitPaused, bool includeDart, Set<String>? scopedOutput,
+ {Set<String>? isolateIds,
+ Duration? timeout,
+ bool functionCoverage = false,
+ bool branchCoverage = false,
+ Map<String, Set<int>>? coverableLineCache,
+ VmService? serviceOverrideForTesting}) async {
+ scopedOutput ??= <String>{};
+
+ late VmService service;
+ if (serviceOverrideForTesting != null) {
+ service = serviceOverrideForTesting;
+ } else {
+ // Create websocket URI. Handle any trailing slashes.
+ final pathSegments =
+ serviceUri.pathSegments.where((c) => c.isNotEmpty).toList()..add('ws');
+ final uri = serviceUri.replace(scheme: 'ws', pathSegments: pathSegments);
+
+ await retry(() async {
+ try {
+ final options = const CompressionOptions(enabled: false);
+ final socket = await WebSocket.connect('$uri', compression: options);
+ final controller = StreamController<String>();
+ socket.listen((data) => controller.add(data as String), onDone: () {
+ controller.close();
+ service.dispose();
+ });
+ service = VmService(controller.stream, socket.add,
+ log: StdoutLog(), disposeHandler: socket.close);
+ await service.getVM().timeout(_retryInterval);
+ } on TimeoutException {
+ // The signature changed in vm_service version 6.0.0.
+ // ignore: await_only_futures
+ await service.dispose();
+ rethrow;
+ }
+ }, _retryInterval, timeout: timeout);
+ }
+
+ try {
+ return await _getAllCoverage(
+ service,
+ includeDart,
+ functionCoverage,
+ branchCoverage,
+ scopedOutput,
+ isolateIds,
+ coverableLineCache,
+ waitPaused);
+ } finally {
+ if (resume && !waitPaused) {
+ await _resumeIsolates(service);
+ }
+ // The signature changed in vm_service version 6.0.0.
+ // ignore: await_only_futures
+ await service.dispose();
+ }
+}
+
+Future<Map<String, dynamic>> _getAllCoverage(
+ VmService service,
+ bool includeDart,
+ bool functionCoverage,
+ bool branchCoverage,
+ Set<String> scopedOutput,
+ Set<String>? isolateIds,
+ Map<String, Set<int>>? coverableLineCache,
+ bool waitPaused) async {
+ final allCoverage = <Map<String, dynamic>>[];
+
+ final sourceReportKinds = [
+ SourceReportKind.kCoverage,
+ if (branchCoverage) SourceReportKind.kBranchCoverage,
+ ];
+
+ final librariesAlreadyCompiled = coverableLineCache?.keys.toList();
+
+ // Program counters are shared between isolates in the same group. So we need
+ // to make sure we're only gathering coverage data for one isolate in each
+ // group, otherwise we'll double count the hits.
+ final coveredIsolateGroups = <String>{};
+
+ Future<void> collectIsolate(IsolateRef isolateRef) async {
+ if (!(isolateIds?.contains(isolateRef.id) ?? true)) return;
+
+ // coveredIsolateGroups is only relevant for the !waitPaused flow. The
+ // waitPaused flow achieves the same once-per-group behavior using the
+ // isLastIsolateInGroup flag.
+ final isolateGroupId = isolateRef.isolateGroupId;
+ if (isolateGroupId != null) {
+ if (coveredIsolateGroups.contains(isolateGroupId)) return;
+ coveredIsolateGroups.add(isolateGroupId);
+ }
+
+ late final SourceReport isolateReport;
+ try {
+ isolateReport = await service.getSourceReport(
+ isolateRef.id!,
+ sourceReportKinds,
+ forceCompile: true,
+ reportLines: true,
+ libraryFilters: scopedOutput.isNotEmpty
+ ? List.from(scopedOutput.map((filter) => 'package:$filter/'))
+ : null,
+ librariesAlreadyCompiled: librariesAlreadyCompiled,
+ );
+ } on SentinelException {
+ return;
+ }
+
+ final coverage = await _processSourceReport(
+ service,
+ isolateRef,
+ isolateReport,
+ includeDart,
+ functionCoverage,
+ coverableLineCache,
+ scopedOutput);
+ allCoverage.addAll(coverage);
+ }
+
+ if (waitPaused) {
+ await IsolatePausedListener(service,
+ (IsolateRef isolateRef, bool isLastIsolateInGroup) async {
+ if (isLastIsolateInGroup) {
+ await collectIsolate(isolateRef);
+ }
+ }, stderr.writeln)
+ .waitUntilAllExited();
+ } else {
+ for (final isolateRef in await getAllIsolates(service)) {
+ await collectIsolate(isolateRef);
+ }
+ }
+
+ return <String, dynamic>{'type': 'CodeCoverage', 'coverage': allCoverage};
+}
+
+Future _resumeIsolates(VmService service) async {
+ final vm = await service.getVM();
+ final futures = <Future>[];
+ for (var isolateRef in vm.isolates!) {
+ // Guard against sync as well as async errors: sync - when we are writing
+ // message to the socket, the socket might be closed; async - when we are
+ // waiting for the response, the socket again closes.
+ futures.add(Future.sync(() async {
+ final isolate = await service.getIsolate(isolateRef.id!);
+ if (isolate.pauseEvent!.kind != EventKind.kResume) {
+ await service.resume(isolateRef.id!);
+ }
+ }));
+ }
+ try {
+ await Future.wait(futures);
+ } catch (_) {
+ // Ignore resume isolate failures
+ }
+}
+
+/// Returns the line number to which the specified token position maps.
+///
+/// Performs a binary search within the script's token position table to locate
+/// the line in question.
+int? _getLineFromTokenPos(Script script, int tokenPos) {
+ // TODO(cbracken): investigate whether caching this lookup results in
+ // significant performance gains.
+ var min = 0;
+ var max = script.tokenPosTable!.length;
+ while (min < max) {
+ final mid = min + ((max - min) >> 1);
+ final row = script.tokenPosTable![mid];
+ if (row[1] > tokenPos) {
+ max = mid;
+ } else {
+ for (var i = 1; i < row.length; i += 2) {
+ if (row[i] == tokenPos) return row.first;
+ }
+ min = mid + 1;
+ }
+ }
+ return null;
+}
+
+/// Returns a JSON coverage list backward-compatible with pre-1.16.0 SDKs.
+Future<List<Map<String, dynamic>>> _processSourceReport(
+ VmService service,
+ IsolateRef isolateRef,
+ SourceReport report,
+ bool includeDart,
+ bool functionCoverage,
+ Map<String, Set<int>>? coverableLineCache,
+ Set<String> scopedOutput) async {
+ final hitMaps = <Uri, HitMap>{};
+ final scripts = <ScriptRef, Script>{};
+ final libraries = <LibraryRef>{};
+ final needScripts = functionCoverage;
+
+ Future<Script?> getScript(ScriptRef? scriptRef) async {
+ if (scriptRef == null) {
+ return null;
+ }
+ if (!scripts.containsKey(scriptRef)) {
+ scripts[scriptRef] =
+ await service.getObject(isolateRef.id!, scriptRef.id!) as Script;
+ }
+ return scripts[scriptRef];
+ }
+
+ HitMap getHitMap(Uri scriptUri) => hitMaps.putIfAbsent(scriptUri, HitMap.new);
+
+ Future<void> processFunction(FuncRef funcRef) async {
+ final func = await service.getObject(isolateRef.id!, funcRef.id!) as Func;
+ if ((func.implicit ?? false) || (func.isAbstract ?? false)) {
+ return;
+ }
+ final location = func.location;
+ if (location == null) {
+ return;
+ }
+ final script = await getScript(location.script);
+ if (script == null) {
+ return;
+ }
+ final funcName = await _getFuncName(service, isolateRef, func);
+ // TODO(liama): Is this still necessary, or is location.line valid?
+ final tokenPos = location.tokenPos!;
+ final line = _getLineFromTokenPos(script, tokenPos);
+ if (line == null) {
+ if (_debugTokenPositions) {
+ stderr.writeln(
+ 'tokenPos $tokenPos in function ${funcRef.name} has no line '
+ 'mapping for script ${script.uri!}');
+ }
+ return;
+ }
+ final hits = getHitMap(Uri.parse(script.uri!));
+ hits.funcHits ??= <int, int>{};
+ (hits.funcNames ??= <int, String>{})[line] = funcName;
+ }
+
+ for (var range in report.ranges!) {
+ final scriptRef = report.scripts![range.scriptIndex!];
+ final scriptUriString = scriptRef.uri;
+ if (!scopedOutput.includesScript(scriptUriString)) {
+ // Sometimes a range's script can be different to the function's script
+ // (eg mixins), so we have to re-check the scope filter.
+ // See https://github.com/dart-lang/tools/issues/530
+ continue;
+ }
+ final scriptUri = Uri.parse(scriptUriString!);
+
+ // If we have a coverableLineCache, use it in the same way we use
+ // SourceReportCoverage.misses: to add zeros to the coverage result for all
+ // the lines that don't have a hit. Afterwards, add all the lines that were
+ // hit or missed to the cache, so that the next coverage collection won't
+ // need to compile this library.
+ final coverableLines =
+ coverableLineCache?.putIfAbsent(scriptUriString, () => <int>{});
+
+ // Not returned in scripts section of source report.
+ if (scriptUri.scheme == 'evaluate') continue;
+
+ // Skip scripts from dart:.
+ if (!includeDart && scriptUri.scheme == 'dart') continue;
+
+ // Look up the hit maps for this script (shared across isolates).
+ final hits = getHitMap(scriptUri);
+
+ Script? script;
+ if (needScripts) {
+ script = await getScript(scriptRef);
+ if (script == null) continue;
+ }
+
+ // If the script's library isn't loaded, load it then look up all its funcs.
+ final libRef = script?.library;
+ if (functionCoverage && libRef != null && !libraries.contains(libRef)) {
+ libraries.add(libRef);
+ final library =
+ await service.getObject(isolateRef.id!, libRef.id!) as Library;
+ if (library.functions != null) {
+ for (var funcRef in library.functions!) {
+ await processFunction(funcRef);
+ }
+ }
+ if (library.classes != null) {
+ for (var classRef in library.classes!) {
+ final clazz =
+ await service.getObject(isolateRef.id!, classRef.id!) as Class;
+ if (clazz.functions != null) {
+ for (var funcRef in clazz.functions!) {
+ await processFunction(funcRef);
+ }
+ }
+ }
+ }
+ }
+
+ // Collect hits and misses.
+ final coverage = range.coverage;
+
+ if (coverage == null) continue;
+
+ void forEachLine(List<int>? tokenPositions, void Function(int line) body) {
+ if (tokenPositions == null) return;
+ for (final line in tokenPositions) {
+ body(line);
+ }
+ }
+
+ if (coverableLines != null) {
+ for (final line in coverableLines) {
+ hits.lineHits.putIfAbsent(line, () => 0);
+ }
+ }
+
+ forEachLine(coverage.hits, (line) {
+ hits.lineHits.increment(line);
+ coverableLines?.add(line);
+ if (hits.funcNames != null && hits.funcNames!.containsKey(line)) {
+ hits.funcHits!.increment(line);
+ }
+ });
+ forEachLine(coverage.misses, (line) {
+ hits.lineHits.putIfAbsent(line, () => 0);
+ coverableLines?.add(line);
+ });
+ hits.funcNames?.forEach((line, funcName) {
+ hits.funcHits?.putIfAbsent(line, () => 0);
+ });
+
+ final branchCoverage = range.branchCoverage;
+ if (branchCoverage != null) {
+ hits.branchHits ??= <int, int>{};
+ forEachLine(branchCoverage.hits, (line) {
+ hits.branchHits!.increment(line);
+ });
+ forEachLine(branchCoverage.misses, (line) {
+ hits.branchHits!.putIfAbsent(line, () => 0);
+ });
+ }
+ }
+
+ // Output JSON
+ final coverage = <Map<String, dynamic>>[];
+ hitMaps.forEach((uri, hits) {
+ coverage.add(hitmapToJson(hits, uri));
+ });
+ return coverage;
+}
+
+extension _MapExtension<T> on Map<T, int> {
+ void increment(T key) => this[key] = (this[key] ?? 0) + 1;
+}
+
+Future<String> _getFuncName(
+ VmService service, IsolateRef isolateRef, Func func) async {
+ if (func.name == null) {
+ return '${func.type}:${func.location!.tokenPos}';
+ }
+ final owner = func.owner;
+ if (owner is ClassRef) {
+ final cls = await service.getObject(isolateRef.id!, owner.id!) as Class;
+ if (cls.name != null) return '${cls.name}.${func.name}';
+ }
+ return func.name!;
+}
+
+class StdoutLog extends Log {
+ @override
+ void warning(String message) => print(message);
+
+ @override
+ void severe(String message) => print(message);
+}
+
+extension _ScopedOutput on Set<String> {
+ bool includesScript(String? scriptUriString) {
+ if (scriptUriString == null) return false;
+
+ // If the set is empty, it means the user didn't specify a --scope-output
+ // flag, so allow everything.
+ if (isEmpty) return true;
+
+ final scriptUri = Uri.parse(scriptUriString);
+ if (scriptUri.scheme != 'package') return false;
+
+ final scope = scriptUri.pathSegments.first;
+ return contains(scope);
+ }
+}
diff --git a/pkgs/coverage/lib/src/formatter.dart b/pkgs/coverage/lib/src/formatter.dart
new file mode 100644
index 0000000..a37df73
--- /dev/null
+++ b/pkgs/coverage/lib/src/formatter.dart
@@ -0,0 +1,223 @@
+// Copyright (c) 2014, 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:glob/glob.dart';
+import 'package:path/path.dart' as p;
+
+import 'hitmap.dart';
+import 'resolver.dart';
+
+@Deprecated('Migrate to FileHitMapsFormatter')
+abstract class Formatter {
+ /// Returns the formatted coverage data.
+ Future<String> format(Map<String, Map<int, int>> hitmap);
+}
+
+/// Converts the given hitmap to lcov format and appends the result to
+/// env.output.
+///
+/// Returns a [Future] that completes as soon as all map entries have been
+/// emitted.
+@Deprecated('Migrate to FileHitMapsFormatter.formatLcov')
+class LcovFormatter implements Formatter {
+ /// Creates a LCOV formatter.
+ ///
+ /// If [reportOn] is provided, coverage report output is limited to files
+ /// prefixed with one of the paths included. If [basePath] is provided, paths
+ /// are reported relative to that path.
+ LcovFormatter(this.resolver, {this.reportOn, this.basePath});
+
+ final Resolver resolver;
+ final String? basePath;
+ final List<String>? reportOn;
+
+ @override
+ Future<String> format(Map<String, Map<int, int>> hitmap) {
+ return Future.value(hitmap
+ .map((key, value) => MapEntry(key, HitMap(value)))
+ .formatLcov(resolver, basePath: basePath, reportOn: reportOn));
+ }
+}
+
+/// Converts the given hitmap to a pretty-print format and appends the result
+/// to env.output.
+///
+/// Returns a [Future] that completes as soon as all map entries have been
+/// emitted.
+@Deprecated('Migrate to FileHitMapsFormatter.prettyPrint')
+class PrettyPrintFormatter implements Formatter {
+ /// Creates a pretty-print formatter.
+ ///
+ /// If [reportOn] is provided, coverage report output is limited to files
+ /// prefixed with one of the paths included.
+ PrettyPrintFormatter(this.resolver, this.loader,
+ {this.reportOn, this.reportFuncs = false});
+
+ final Resolver resolver;
+ final Loader loader;
+ final List<String>? reportOn;
+ final bool reportFuncs;
+
+ @override
+ Future<String> format(Map<String, Map<int, int>> hitmap) {
+ return hitmap.map((key, value) => MapEntry(key, HitMap(value))).prettyPrint(
+ resolver, loader,
+ reportOn: reportOn, reportFuncs: reportFuncs);
+ }
+}
+
+extension FileHitMapsFormatter on Map<String, HitMap> {
+ /// Converts the given hitmap to lcov format.
+ ///
+ /// If [reportOn] is provided, coverage report output is limited to files
+ /// prefixed with one of the paths included. If [basePath] is provided, paths
+ /// are reported relative to that path.
+ String formatLcov(
+ Resolver resolver, {
+ String? basePath,
+ List<String>? reportOn,
+ Set<Glob>? ignoreGlobs,
+ }) {
+ final pathFilter = _getPathFilter(
+ reportOn: reportOn,
+ ignoreGlobs: ignoreGlobs,
+ );
+ final buf = StringBuffer();
+ for (final entry in entries) {
+ final v = entry.value;
+ final lineHits = v.lineHits;
+ final funcHits = v.funcHits;
+ final funcNames = v.funcNames;
+ final branchHits = v.branchHits;
+ var source = resolver.resolve(entry.key);
+ if (source == null) {
+ continue;
+ }
+
+ if (!pathFilter(source)) {
+ continue;
+ }
+
+ if (basePath != null) {
+ source = p.relative(source, from: basePath);
+ }
+
+ buf.write('SF:$source\n');
+ if (funcHits != null && funcNames != null) {
+ for (final k in funcNames.keys.toList()..sort()) {
+ buf.write('FN:$k,${funcNames[k]}\n');
+ }
+ for (final k in funcHits.keys.toList()..sort()) {
+ if (funcHits[k]! != 0) {
+ buf.write('FNDA:${funcHits[k]},${funcNames[k]}\n');
+ }
+ }
+ buf.write('FNF:${funcNames.length}\n');
+ buf.write('FNH:${funcHits.values.where((v) => v > 0).length}\n');
+ }
+ for (final k in lineHits.keys.toList()..sort()) {
+ buf.write('DA:$k,${lineHits[k]}\n');
+ }
+ buf.write('LF:${lineHits.length}\n');
+ buf.write('LH:${lineHits.values.where((v) => v > 0).length}\n');
+ if (branchHits != null) {
+ for (final k in branchHits.keys.toList()..sort()) {
+ buf.write('BRDA:$k,0,0,${branchHits[k]}\n');
+ }
+ }
+ buf.write('end_of_record\n');
+ }
+
+ return buf.toString();
+ }
+
+ /// Converts the given hitmap to a pretty-print format.
+ ///
+ /// If [reportOn] is provided, coverage report output is limited to files
+ /// prefixed with one of the paths included. If [reportFuncs] is provided,
+ /// only function coverage information will be shown.
+ Future<String> prettyPrint(
+ Resolver resolver,
+ Loader loader, {
+ List<String>? reportOn,
+ Set<Glob>? ignoreGlobs,
+ bool reportFuncs = false,
+ bool reportBranches = false,
+ }) async {
+ final pathFilter = _getPathFilter(
+ reportOn: reportOn,
+ ignoreGlobs: ignoreGlobs,
+ );
+ final buf = StringBuffer();
+ for (final entry in entries) {
+ final v = entry.value;
+ if (reportFuncs && v.funcHits == null) {
+ throw StateError(
+ 'Function coverage formatting was requested, but the hit map is '
+ 'missing function coverage information. Did you run '
+ 'collect_coverage with the --function-coverage flag?',
+ );
+ }
+ if (reportBranches && v.branchHits == null) {
+ throw StateError(
+ 'Branch coverage formatting was requested, but the hit map is '
+ '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;
+ if (hits.containsKey(line)) {
+ prefix = hits[line].toString().padLeft(_prefix.length);
+ }
+ buf.writeln('$prefix|${lines[line - 1]}');
+ }
+ }
+
+ return buf.toString();
+ }
+}
+
+const _prefix = ' ';
+
+typedef _PathFilter = bool Function(String path);
+
+_PathFilter _getPathFilter({List<String>? reportOn, Set<Glob>? ignoreGlobs}) {
+ if (reportOn == null && ignoreGlobs == null) return (String path) => true;
+
+ final absolutePaths = reportOn?.map(p.canonicalize).toList();
+
+ return (String path) {
+ final canonicalizedPath = p.canonicalize(path);
+
+ if (absolutePaths != null &&
+ !absolutePaths.any(canonicalizedPath.startsWith)) {
+ return false;
+ }
+ if (ignoreGlobs != null &&
+ ignoreGlobs.any((glob) => glob.matches(canonicalizedPath))) {
+ return false;
+ }
+
+ return true;
+ };
+}
diff --git a/pkgs/coverage/lib/src/hitmap.dart b/pkgs/coverage/lib/src/hitmap.dart
new file mode 100644
index 0000000..4c3b468
--- /dev/null
+++ b/pkgs/coverage/lib/src/hitmap.dart
@@ -0,0 +1,386 @@
+// Copyright (c) 2014, 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' show json;
+import 'dart:io';
+
+import 'resolver.dart';
+import 'util.dart';
+
+/// Contains line and function hit information for a single script.
+class HitMap {
+ /// Constructs a HitMap.
+ HitMap([
+ Map<int, int>? lineHits,
+ this.funcHits,
+ this.funcNames,
+ this.branchHits,
+ ]) : lineHits = lineHits ?? {};
+
+ /// Map from line to hit count for that line.
+ final Map<int, int> lineHits;
+
+ /// Map from the first line of each function, to the hit count for that
+ /// function. Null if function coverage info was not gathered.
+ Map<int, int>? funcHits;
+
+ /// Map from the first line of each function, to the function name. Null if
+ /// function coverage info was not gathered.
+ Map<int, String>? funcNames;
+
+ /// Map from branch line, to the hit count for that branch. Null if branch
+ /// coverage info was not gathered.
+ Map<int, int>? branchHits;
+
+ /// Creates a single hitmap from a raw json object.
+ ///
+ /// Note that when [checkIgnoredLines] is `true` all files will be
+ /// read to get ignore comments. This will add some overhead.
+ /// To combat this when calling this function multiple times from the
+ /// same source (e.g. test runs of different files) a cache is taken
+ /// via [ignoredLinesInFilesCache]. If this cache contains the parsed
+ /// data for the specific file already, the file will not be read and
+ /// parsed again.
+ ///
+ /// Throws away all entries that are not resolvable.
+ static Map<String, HitMap> parseJsonSync(
+ List<Map<String, dynamic>> jsonResult, {
+ required bool checkIgnoredLines,
+ required Map<String, List<List<int>>?> ignoredLinesInFilesCache,
+ required Resolver resolver,
+ }) {
+ final loader = Loader();
+
+ // Map of source file to map of line to hit count for that line.
+ final globalHitMap = <String, HitMap>{};
+
+ for (var e in jsonResult) {
+ final source = e['source'] as String?;
+ if (source == null) {
+ // Couldn't resolve import, so skip this entry.
+ continue;
+ }
+
+ var ignoredLinesList = <List<int>>[];
+
+ 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<List<int>> 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<int, int> map, int line, int count) {
+ final oldCount = map.putIfAbsent(line, () => 0);
+ map[line] = count + oldCount;
+ }
+
+ void fillHitMap(List hits, Map<int, int> hitMap) {
+ // Ignore line annotations require hits to be sorted.
+ hits = _sortHits(hits);
+ // hits is a flat array of the following format:
+ // [ <line|linerange>, <hitcount>,...]
+ // line: number.
+ // linerange: '<line>-<line>'.
+ for (var i = 0; i < hits.length; i += 2) {
+ 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.
+ final splitPos = k.indexOf('-');
+ 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 {
+ throw StateError('Expected value of type int or String');
+ }
+ }
+ }
+
+ final sourceHitMap = globalHitMap.putIfAbsent(source, HitMap.new);
+ fillHitMap(e['hits'] as List, sourceHitMap.lineHits);
+ if (e.containsKey('funcHits')) {
+ sourceHitMap.funcHits ??= <int, int>{};
+ fillHitMap(e['funcHits'] as List, sourceHitMap.funcHits!);
+ }
+ if (e.containsKey('funcNames')) {
+ sourceHitMap.funcNames ??= <int, String>{};
+ final funcNames = e['funcNames'] as List;
+ for (var i = 0; i < funcNames.length; i += 2) {
+ sourceHitMap.funcNames![funcNames[i] as int] =
+ funcNames[i + 1] as String;
+ }
+ }
+ if (e.containsKey('branchHits')) {
+ sourceHitMap.branchHits ??= <int, int>{};
+ fillHitMap(e['branchHits'] as List, sourceHitMap.branchHits!);
+ }
+ }
+ return globalHitMap;
+ }
+
+ /// Creates a single hitmap from a raw json object.
+ ///
+ /// Throws away all entries that are not resolvable.
+ static Future<Map<String, HitMap>> parseJson(
+ List<Map<String, dynamic>> jsonResult, {
+ bool checkIgnoredLines = false,
+ @Deprecated('Use packagePath') String? packagesPath,
+ String? packagePath,
+ }) async {
+ final resolver = await Resolver.create(
+ packagesPath: packagesPath, packagePath: packagePath);
+ return parseJsonSync(jsonResult,
+ checkIgnoredLines: checkIgnoredLines,
+ ignoredLinesInFilesCache: {},
+ resolver: resolver);
+ }
+
+ /// Generates a merged hitmap from a set of coverage JSON files.
+ static Future<Map<String, HitMap>> parseFiles(
+ Iterable<File> files, {
+ bool checkIgnoredLines = false,
+ @Deprecated('Use packagePath') String? packagesPath,
+ String? packagePath,
+ }) async {
+ final globalHitmap = <String, HitMap>{};
+ for (var file in files) {
+ final contents = file.readAsStringSync();
+ final jsonMap = json.decode(contents) as Map<String, dynamic>;
+ if (jsonMap.containsKey('coverage')) {
+ final jsonResult = jsonMap['coverage'] as List;
+ globalHitmap.merge(await HitMap.parseJson(
+ jsonResult.cast<Map<String, dynamic>>(),
+ checkIgnoredLines: checkIgnoredLines,
+ // ignore: deprecated_member_use_from_same_package
+ packagesPath: packagesPath,
+ packagePath: packagePath,
+ ));
+ }
+ }
+ return globalHitmap;
+ }
+}
+
+extension FileHitMaps on Map<String, HitMap> {
+ /// Merges [newMap] into this one.
+ void merge(Map<String, HitMap> newMap) {
+ newMap.forEach((file, v) {
+ final fileResult = this[file];
+ if (fileResult != null) {
+ _mergeHitCounts(v.lineHits, fileResult.lineHits);
+ if (v.funcHits != null) {
+ fileResult.funcHits ??= <int, int>{};
+ _mergeHitCounts(v.funcHits!, fileResult.funcHits!);
+ }
+ if (v.funcNames != null) {
+ fileResult.funcNames ??= <int, String>{};
+ v.funcNames?.forEach((line, name) {
+ fileResult.funcNames![line] = name;
+ });
+ }
+ if (v.branchHits != null) {
+ fileResult.branchHits ??= <int, int>{};
+ _mergeHitCounts(v.branchHits!, fileResult.branchHits!);
+ }
+ } else {
+ this[file] = v;
+ }
+ });
+ }
+
+ static void _mergeHitCounts(Map<int, int> src, Map<int, int> dest) {
+ src.forEach((line, count) {
+ final lineFileResult = dest[line];
+ if (lineFileResult == null) {
+ dest[line] = count;
+ } else {
+ dest[line] = lineFileResult + count;
+ }
+ });
+ }
+}
+
+/// Class containing information about a coverage hit.
+class _HitInfo {
+ _HitInfo(this.firstLine, this.hitRange, this.hitCount);
+
+ /// The line number of the first line of this hit range.
+ final int firstLine;
+
+ /// A hit range is either a number (1 line) or a String of the form
+ /// "start-end" (multi-line range).
+ final dynamic hitRange;
+
+ /// How many times this hit range was executed.
+ final int hitCount;
+}
+
+/// Creates a single hitmap from a raw json object.
+///
+/// Throws away all entries that are not resolvable.
+@Deprecated('Migrate to HitMap.parseJson')
+Future<Map<String, Map<int, int>>> createHitmap(
+ List<Map<String, dynamic>> jsonResult, {
+ bool checkIgnoredLines = false,
+ @Deprecated('Use packagePath') String? packagesPath,
+ String? packagePath,
+}) async {
+ final result = await HitMap.parseJson(
+ jsonResult,
+ checkIgnoredLines: checkIgnoredLines,
+ packagesPath: packagesPath,
+ packagePath: packagePath,
+ );
+ return result.map((key, value) => MapEntry(key, value.lineHits));
+}
+
+/// Merges [newMap] into [result].
+@Deprecated('Migrate to FileHitMaps.merge')
+void mergeHitmaps(
+ Map<String, Map<int, int>> newMap, Map<String, Map<int, int>> result) {
+ newMap.forEach((file, v) {
+ final fileResult = result[file];
+ if (fileResult != null) {
+ v.forEach((line, count) {
+ final lineFileResult = fileResult[line];
+ if (lineFileResult == null) {
+ fileResult[line] = count;
+ } else {
+ fileResult[line] = lineFileResult + count;
+ }
+ });
+ } else {
+ result[file] = v;
+ }
+ });
+}
+
+/// Generates a merged hitmap from a set of coverage JSON files.
+@Deprecated('Migrate to HitMap.parseFiles')
+Future<Map<String, Map<int, int>>> parseCoverage(
+ Iterable<File> files,
+ int _, {
+ bool checkIgnoredLines = false,
+ @Deprecated('Use packagePath') String? packagesPath,
+ String? packagePath,
+}) async {
+ final result = await HitMap.parseFiles(files,
+ checkIgnoredLines: checkIgnoredLines,
+ packagesPath: packagesPath,
+ packagePath: packagePath);
+ return result.map((key, value) => MapEntry(key, value.lineHits));
+}
+
+/// Returns a JSON hit map backward-compatible with pre-1.16.0 SDKs.
+@Deprecated('Will be removed in 2.0.0')
+Map<String, dynamic> toScriptCoverageJson(Uri scriptUri, Map<int, int> hitMap) {
+ return hitmapToJson(HitMap(hitMap), scriptUri);
+}
+
+List<T> _flattenMap<T>(Map map) {
+ final kvs = <T>[];
+ map.forEach((k, v) {
+ kvs.add(k as T);
+ kvs.add(v as T);
+ });
+ return kvs;
+}
+
+/// Returns a JSON hit map backward-compatible with pre-1.16.0 SDKs.
+Map<String, dynamic> hitmapToJson(HitMap hitmap, Uri scriptUri) =>
+ <String, dynamic>{
+ 'source': '$scriptUri',
+ 'script': {
+ 'type': '@Script',
+ 'fixedId': true,
+ 'id':
+ 'libraries/1/scripts/${Uri.encodeComponent(scriptUri.toString())}',
+ 'uri': '$scriptUri',
+ '_kind': 'library',
+ },
+ 'hits': _flattenMap<int>(hitmap.lineHits),
+ if (hitmap.funcHits != null)
+ 'funcHits': _flattenMap<int>(hitmap.funcHits!),
+ if (hitmap.funcNames != null)
+ 'funcNames': _flattenMap<dynamic>(hitmap.funcNames!),
+ if (hitmap.branchHits != null)
+ 'branchHits': _flattenMap<int>(hitmap.branchHits!),
+ };
+
+/// Sorts the hits array based on the line numbers.
+List _sortHits(List hits) {
+ final structuredHits = <_HitInfo>[];
+ for (var i = 0; i < hits.length - 1; i += 2) {
+ final lineOrLineRange = hits[i];
+ final firstLineInRange = lineOrLineRange is int
+ ? lineOrLineRange
+ : int.parse((lineOrLineRange as String).split('-')[0]);
+ structuredHits.add(_HitInfo(firstLineInRange, hits[i], hits[i + 1] as int));
+ }
+ structuredHits.sort((a, b) => a.firstLine.compareTo(b.firstLine));
+ return structuredHits
+ .map((item) => [item.hitRange, item.hitCount])
+ .expand((item) => item)
+ .toList();
+}
diff --git a/pkgs/coverage/lib/src/isolate_paused_listener.dart b/pkgs/coverage/lib/src/isolate_paused_listener.dart
new file mode 100644
index 0000000..6f538a4
--- /dev/null
+++ b/pkgs/coverage/lib/src/isolate_paused_listener.dart
@@ -0,0 +1,281 @@
+// 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 'dart:async';
+import 'dart:collection';
+
+import 'package:meta/meta.dart';
+import 'package:vm_service/vm_service.dart';
+
+import 'util.dart';
+
+typedef SyncIsolateCallback = void Function(IsolateRef isolate);
+typedef AsyncIsolateCallback = Future<void> Function(IsolateRef isolate);
+typedef AsyncIsolatePausedCallback = Future<void> Function(
+ IsolateRef isolate, bool isLastIsolateInGroup);
+typedef AsyncVmServiceEventCallback = Future<void> Function(Event event);
+typedef SyncErrorLogger = void Function(String message);
+
+/// Calls onIsolatePaused whenever an isolate reaches the pause-on-exit state,
+/// and passes a flag stating whether that isolate is the last one in the group.
+class IsolatePausedListener {
+ IsolatePausedListener(this._service, this._onIsolatePaused, this._log);
+
+ final VmService _service;
+ final AsyncIsolatePausedCallback _onIsolatePaused;
+ final SyncErrorLogger _log;
+
+ final _isolateGroups = <String, IsolateGroupState>{};
+
+ int _numNonMainIsolates = 0;
+ final _allNonMainIsolatesExited = Completer<void>();
+ bool _finished = false;
+
+ IsolateRef? _mainIsolate;
+ final _mainIsolatePaused = Completer<bool>();
+
+ /// Starts listening and returns a future that completes when all isolates
+ /// have exited.
+ Future<void> waitUntilAllExited() async {
+ await listenToIsolateLifecycleEvents(_service, _onStart, _onPause, _onExit);
+
+ await _allNonMainIsolatesExited.future;
+
+ // Resume the main isolate.
+ try {
+ if (_mainIsolate != null) {
+ if (await _mainIsolatePaused.future) {
+ await _runCallbackAndResume(
+ _mainIsolate!, !_getGroup(_mainIsolate!).collected);
+ }
+ }
+ } finally {
+ _finished = true;
+ }
+ }
+
+ IsolateGroupState _getGroup(IsolateRef isolateRef) =>
+ _isolateGroups[isolateRef.isolateGroupId!] ??= IsolateGroupState();
+
+ void _onStart(IsolateRef isolateRef) {
+ if (_finished) return;
+ final group = _getGroup(isolateRef);
+ group.start(isolateRef.id!);
+ if (_mainIsolate == null && _isMainIsolate(isolateRef)) {
+ _mainIsolate = isolateRef;
+ } else {
+ ++_numNonMainIsolates;
+ }
+ }
+
+ Future<void> _onPause(IsolateRef isolateRef) async {
+ if (_finished) return;
+ final group = _getGroup(isolateRef);
+ group.pause(isolateRef.id!);
+ if (isolateRef.id! == _mainIsolate?.id) {
+ _mainIsolatePaused.complete(true);
+
+ // If the main isolate is the only isolate, then _allNonMainIsolatesExited
+ // will never be completed. So check that case here.
+ _checkCompleted();
+ } else {
+ await _runCallbackAndResume(isolateRef, group.noRunningIsolates);
+ }
+ }
+
+ Future<void> _runCallbackAndResume(
+ IsolateRef isolateRef, bool isLastIsolateInGroup) async {
+ if (isLastIsolateInGroup) {
+ _getGroup(isolateRef).collected = true;
+ }
+ try {
+ await _onIsolatePaused(isolateRef, isLastIsolateInGroup);
+ } finally {
+ await _service.resume(isolateRef.id!);
+ }
+ }
+
+ void _onExit(IsolateRef isolateRef) {
+ if (_finished) return;
+ final group = _getGroup(isolateRef);
+ group.exit(isolateRef.id!);
+ if (group.noLiveIsolates && !group.collected) {
+ _log('ERROR: An isolate exited without pausing, causing '
+ 'coverage data to be lost for group ${isolateRef.isolateGroupId!}.');
+ }
+ if (isolateRef.id! == _mainIsolate?.id) {
+ if (!_mainIsolatePaused.isCompleted) {
+ // Main isolate exited without pausing.
+ _mainIsolatePaused.complete(false);
+ }
+ } else {
+ --_numNonMainIsolates;
+ _checkCompleted();
+ }
+ }
+
+ bool get _mainRunning =>
+ _mainIsolate != null && !_mainIsolatePaused.isCompleted;
+
+ void _checkCompleted() {
+ if (_numNonMainIsolates == 0 &&
+ !_mainRunning &&
+ !_allNonMainIsolatesExited.isCompleted) {
+ _allNonMainIsolatesExited.complete();
+ }
+ }
+
+ static bool _isMainIsolate(IsolateRef isolateRef) {
+ // HACK: This should pretty reliably detect the main isolate, but it's not
+ // foolproof and relies on unstable features. The Dart standalone embedder
+ // and Flutter both call the main isolate "main", and they both also list
+ // this isolate first when querying isolates from the VM service. So
+ // selecting the first isolate named "main" combines these conditions and
+ // should be reliable enough for now, while we wait for a better test.
+ // TODO(https://github.com/dart-lang/sdk/issues/56732): Switch to more
+ // reliable test when it's available.
+ return isolateRef.name == 'main';
+ }
+}
+
+/// Listens to isolate start and pause events, and backfills events for isolates
+/// that existed before listening started.
+///
+/// Ensures that:
+/// - Every [onIsolatePaused] and [onIsolateExited] call will be preceeded by
+/// an [onIsolateStarted] call for the same isolate.
+/// - Not every [onIsolateExited] call will be preceeded by a [onIsolatePaused]
+/// call, but a [onIsolatePaused] will never follow a [onIsolateExited].
+/// - [onIsolateExited] will always run after [onIsolatePaused] completes, even
+/// if an exit event arrives while [onIsolatePaused] is being awaited.
+/// - Each callback will only be called once per isolate.
+Future<void> listenToIsolateLifecycleEvents(
+ VmService service,
+ SyncIsolateCallback onIsolateStarted,
+ AsyncIsolateCallback onIsolatePaused,
+ SyncIsolateCallback onIsolateExited) async {
+ final started = <String>{};
+ void onStart(IsolateRef isolateRef) {
+ if (started.add(isolateRef.id!)) onIsolateStarted(isolateRef);
+ }
+
+ final paused = <String, Future<void>>{};
+ Future<void> onPause(IsolateRef isolateRef) async {
+ try {
+ onStart(isolateRef);
+ } finally {
+ await (paused[isolateRef.id!] ??= onIsolatePaused(isolateRef));
+ }
+ }
+
+ final exited = <String>{};
+ Future<void> onExit(IsolateRef isolateRef) async {
+ onStart(isolateRef);
+ if (exited.add(isolateRef.id!)) {
+ try {
+ // Wait for in-progress pause callbacks, and prevent future pause
+ // callbacks from running.
+ await (paused[isolateRef.id!] ??= Future<void>.value());
+ } finally {
+ onIsolateExited(isolateRef);
+ }
+ }
+ }
+
+ final eventBuffer = IsolateEventBuffer((Event event) async {
+ switch (event.kind) {
+ case EventKind.kIsolateStart:
+ return onStart(event.isolate!);
+ case EventKind.kPauseExit:
+ return await onPause(event.isolate!);
+ case EventKind.kIsolateExit:
+ return await onExit(event.isolate!);
+ }
+ });
+
+ // Listen for isolate start/exit events.
+ service.onIsolateEvent.listen(eventBuffer.add);
+ await service.streamListen(EventStreams.kIsolate);
+
+ // Listen for isolate paused events.
+ service.onDebugEvent.listen(eventBuffer.add);
+ await service.streamListen(EventStreams.kDebug);
+
+ // Backfill. Add/pause isolates that existed before we subscribed.
+ for (final isolateRef in await getAllIsolates(service)) {
+ onStart(isolateRef);
+ final isolate = await service.getIsolate(isolateRef.id!);
+ if (isolate.pauseEvent?.kind == EventKind.kPauseExit) {
+ await onPause(isolateRef);
+ }
+ }
+
+ // Flush the buffered stream events, and the start processing them as they
+ // arrive.
+ await eventBuffer.flush();
+}
+
+/// Keeps track of isolates in an isolate group.
+class IsolateGroupState {
+ // IDs of the isolates running in this group.
+ @visibleForTesting
+ final running = <String>{};
+
+ // IDs of the isolates paused just before exiting in this group.
+ @visibleForTesting
+ final paused = <String>{};
+
+ bool collected = false;
+
+ bool get noRunningIsolates => running.isEmpty;
+ bool get noLiveIsolates => running.isEmpty && paused.isEmpty;
+
+ void start(String id) {
+ paused.remove(id);
+ running.add(id);
+ }
+
+ void pause(String id) {
+ running.remove(id);
+ paused.add(id);
+ }
+
+ void exit(String id) {
+ running.remove(id);
+ paused.remove(id);
+ }
+
+ @override
+ String toString() => '{running: $running, paused: $paused}';
+}
+
+/// Buffers VM service isolate [Event]s until [flush] is called.
+///
+/// [flush] passes each buffered event to the handler function. After that, any
+/// further events are immediately passed to the handler. [flush] returns a
+/// future that completes when all the events in the queue have been handled (as
+/// well as any events that arrive while flush is in progress).
+class IsolateEventBuffer {
+ IsolateEventBuffer(this._handler);
+
+ final AsyncVmServiceEventCallback _handler;
+ final _buffer = Queue<Event>();
+ var _flushed = false;
+
+ Future<void> add(Event event) async {
+ if (_flushed) {
+ await _handler(event);
+ } else {
+ _buffer.add(event);
+ }
+ }
+
+ Future<void> flush() async {
+ while (_buffer.isNotEmpty) {
+ final event = _buffer.removeFirst();
+ await _handler(event);
+ }
+ _flushed = true;
+ }
+}
diff --git a/pkgs/coverage/lib/src/resolver.dart b/pkgs/coverage/lib/src/resolver.dart
new file mode 100644
index 0000000..cb5b728
--- /dev/null
+++ b/pkgs/coverage/lib/src/resolver.dart
@@ -0,0 +1,216 @@
+// Copyright (c) 2014, 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:package_config/package_config.dart';
+import 'package:path/path.dart' as p;
+
+/// [Resolver] resolves imports with respect to a given environment.
+class Resolver {
+ @Deprecated('Use Resolver.create')
+ Resolver({this.packagesPath, this.sdkRoot})
+ : _packages = packagesPath != null ? _parsePackages(packagesPath) : null,
+ packagePath = null;
+
+ Resolver._(
+ {this.packagesPath,
+ this.packagePath,
+ this.sdkRoot,
+ Map<String, Uri>? packages})
+ : _packages = packages;
+
+ static Future<Resolver> create({
+ String? packagesPath,
+ String? packagePath,
+ String? sdkRoot,
+ }) async {
+ return Resolver._(
+ packagesPath: packagesPath,
+ packagePath: packagePath,
+ sdkRoot: sdkRoot,
+ packages: packagesPath != null
+ ? _parsePackages(packagesPath)
+ : (packagePath != null ? await _parsePackage(packagePath) : null),
+ );
+ }
+
+ final String? packagesPath;
+ final String? packagePath;
+ final String? sdkRoot;
+ final List<String> failed = [];
+ final Map<String, Uri>? _packages;
+
+ /// Returns the absolute path wrt. to the given environment or null, if the
+ /// import could not be resolved.
+ String? resolve(String scriptUri) {
+ final uri = Uri.parse(scriptUri);
+ if (uri.scheme == 'dart') {
+ final sdkRoot = this.sdkRoot;
+ if (sdkRoot == null) {
+ // No sdk-root given, do not resolve dart: URIs.
+ return null;
+ }
+ String filePath;
+ if (uri.pathSegments.length > 1) {
+ var path = uri.pathSegments[0];
+ // Drop patch files, since we don't have their source in the compiled
+ // SDK.
+ if (path.endsWith('-patch')) {
+ failed.add('$uri');
+ return null;
+ }
+ // Canonicalize path. For instance: _collection-dev => _collection_dev.
+ path = path.replaceAll('-', '_');
+ final pathSegments = [
+ sdkRoot,
+ path,
+ ...uri.pathSegments.sublist(1),
+ ];
+ filePath = p.joinAll(pathSegments);
+ } else {
+ // Resolve 'dart:something' to be something/something.dart in the SDK.
+ final lib = uri.path;
+ filePath = p.join(sdkRoot, lib, '$lib.dart');
+ }
+ return resolveSymbolicLinks(filePath);
+ }
+ if (uri.scheme == 'package') {
+ final packages = _packages;
+ if (packages == null) {
+ return null;
+ }
+
+ final packageName = uri.pathSegments[0];
+ final packageUri = packages[packageName];
+ if (packageUri == null) {
+ failed.add('$uri');
+ return null;
+ }
+ final packagePath = p.fromUri(packageUri);
+ final pathInPackage = p.joinAll(uri.pathSegments.sublist(1));
+ return resolveSymbolicLinks(p.join(packagePath, pathInPackage));
+ }
+ if (uri.scheme == 'file') {
+ return resolveSymbolicLinks(p.fromUri(uri));
+ }
+ // We cannot deal with anything else.
+ failed.add('$uri');
+ return null;
+ }
+
+ /// Returns a canonicalized path, or `null` if the path cannot be resolved.
+ String? resolveSymbolicLinks(String path) {
+ final normalizedPath = p.normalize(path);
+ final type = FileSystemEntity.typeSync(normalizedPath, followLinks: true);
+ if (type == FileSystemEntityType.notFound) return null;
+ return File(normalizedPath).resolveSymbolicLinksSync();
+ }
+
+ static Map<String, Uri> _parsePackages(String packagesPath) {
+ final content = File(packagesPath).readAsStringSync();
+ final packagesUri = p.toUri(packagesPath);
+ final parsed =
+ PackageConfig.parseString(content, Uri.base.resolveUri(packagesUri));
+ return {
+ for (var package in parsed.packages) package.name: package.packageUriRoot
+ };
+ }
+
+ static Future<Map<String, Uri>?> _parsePackage(String packagePath) async {
+ final parsed = await findPackageConfig(Directory(packagePath));
+ if (parsed == null) return null;
+ return {
+ for (var package in parsed.packages) package.name: package.packageUriRoot
+ };
+ }
+}
+
+/// Bazel URI resolver.
+class BazelResolver extends Resolver {
+ /// Creates a Bazel resolver with the specified workspace path, if any.
+ BazelResolver({this.workspacePath = ''});
+
+ final String workspacePath;
+
+ /// Returns the absolute path wrt. to the given environment or null, if the
+ /// import could not be resolved.
+ @override
+ String? resolve(String scriptUri) {
+ final uri = Uri.parse(scriptUri);
+ if (uri.scheme == 'dart') {
+ // Ignore the SDK
+ return null;
+ }
+ if (uri.scheme == 'package') {
+ // TODO(cbracken) belongs in a Bazel package
+ return _resolveBazelPackage(uri.pathSegments);
+ }
+ if (uri.scheme == 'file') {
+ final runfilesPathSegment =
+ '.runfiles/$workspacePath'.replaceAll(RegExp(r'/*$'), '/');
+ final runfilesPos = uri.path.indexOf(runfilesPathSegment);
+ if (runfilesPos >= 0) {
+ final pathStart = runfilesPos + runfilesPathSegment.length;
+ return uri.path.substring(pathStart);
+ }
+ return null;
+ }
+ if (uri.scheme == 'https' || uri.scheme == 'http') {
+ return _extractHttpPath(uri);
+ }
+ // We cannot deal with anything else.
+ failed.add('$uri');
+ return null;
+ }
+
+ String _extractHttpPath(Uri uri) {
+ final packagesPos = uri.pathSegments.indexOf('packages');
+ if (packagesPos >= 0) {
+ final workspacePath = uri.pathSegments.sublist(packagesPos + 1);
+ return _resolveBazelPackage(workspacePath);
+ }
+ return uri.pathSegments.join('/');
+ }
+
+ String _resolveBazelPackage(List<String> pathSegments) {
+ // TODO(cbracken) belongs in a Bazel package
+ final packageName = pathSegments[0];
+ final pathInPackage = pathSegments.sublist(1).join('/');
+ final packagePath = packageName.contains('.')
+ ? packageName.replaceAll('.', '/')
+ : 'third_party/dart/$packageName';
+ return '$packagePath/lib/$pathInPackage';
+ }
+}
+
+/// Loads the lines of imported resources.
+class Loader {
+ final List<String> failed = [];
+
+ /// Loads an imported resource and returns a [Future] with a [List] of lines.
+ /// Returns `null` if the resource could not be loaded.
+ Future<List<String>?> load(String path) async {
+ try {
+ // Ensure `readAsLines` runs within the try block so errors are caught.
+ return await File(path).readAsLines();
+ } catch (_) {
+ failed.add(path);
+ return null;
+ }
+ }
+
+ /// Loads an imported resource and returns a [List] of lines.
+ /// Returns `null` if the resource could not be loaded.
+ List<String>? loadSync(String path) {
+ try {
+ // Ensure `readAsLinesSync` runs within the try block so errors are
+ // caught.
+ return File(path).readAsLinesSync();
+ } catch (_) {
+ failed.add(path);
+ return null;
+ }
+ }
+}
diff --git a/pkgs/coverage/lib/src/run_and_collect.dart b/pkgs/coverage/lib/src/run_and_collect.dart
new file mode 100644
index 0000000..f2a9cbc
--- /dev/null
+++ b/pkgs/coverage/lib/src/run_and_collect.dart
@@ -0,0 +1,50 @@
+// Copyright (c) 2015, 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';
+
+import 'collect.dart';
+import 'util.dart';
+
+Future<Map<String, dynamic>> runAndCollect(String scriptPath,
+ {List<String>? scriptArgs,
+ bool checked = false,
+ bool includeDart = false,
+ Duration? timeout}) async {
+ final dartArgs = [
+ '--enable-vm-service',
+ '--pause_isolates_on_exit',
+ if (checked) '--checked',
+ scriptPath,
+ ...?scriptArgs,
+ ];
+
+ final process = await Process.start(Platform.executable, dartArgs);
+
+ final serviceUri = await serviceUriFromProcess(process.stdout.lines());
+ Map<String, dynamic> coverage;
+ try {
+ coverage = await collect(
+ serviceUri,
+ true,
+ true,
+ includeDart,
+ <String>{},
+ timeout: timeout,
+ );
+ } finally {
+ await process.stderr.drain<void>();
+ }
+ final exitStatus = await process.exitCode;
+ if (exitStatus != 0) {
+ throw ProcessException(
+ Platform.executable,
+ dartArgs,
+ 'Process failed.',
+ exitStatus,
+ );
+ }
+ return coverage;
+}
diff --git a/pkgs/coverage/lib/src/util.dart b/pkgs/coverage/lib/src/util.dart
new file mode 100644
index 0000000..cc7f584
--- /dev/null
+++ b/pkgs/coverage/lib/src/util.dart
@@ -0,0 +1,186 @@
+// Copyright (c) 2014, 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';
+
+import 'package:vm_service/vm_service.dart';
+
+// TODO(cbracken) make generic
+/// Retries the specified function with the specified interval and returns
+/// the result on successful completion.
+Future<dynamic> retry(Future Function() f, Duration interval,
+ {Duration? timeout}) async {
+ var keepGoing = true;
+
+ Future<dynamic> withTimeout(Future Function() f, {Duration? duration}) {
+ if (duration == null) {
+ return f();
+ }
+
+ return f().timeout(duration, onTimeout: () {
+ keepGoing = false;
+ final msg = duration.inSeconds == 0
+ ? '${duration.inMilliseconds}ms'
+ : '${duration.inSeconds}s';
+ throw StateError('Failed to complete within $msg');
+ });
+ }
+
+ return withTimeout(() async {
+ while (keepGoing) {
+ try {
+ return await f();
+ } catch (_) {
+ if (keepGoing) {
+ await Future<dynamic>.delayed(interval);
+ }
+ }
+ }
+ }, duration: timeout);
+}
+
+/// Scrapes and returns the Dart VM service URI from a string, or null if not
+/// found.
+///
+/// Potentially useful as a means to extract it from log statements.
+Uri? extractVMServiceUri(String str) {
+ final listeningMessageRegExp = RegExp(
+ r'(?:Observatory|The Dart VM service is) listening on ((http|//)[a-zA-Z0-9:/=_\-\.\[\]]+)',
+ );
+ final match = listeningMessageRegExp.firstMatch(str);
+ if (match != null) {
+ return Uri.parse(match[1]!);
+ }
+ return null;
+}
+
+/// Returns an open port by creating a temporary Socket
+Future<int> 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]*$');
+final ignoreFile = RegExp(r'//\s*coverage:ignore-file[\w\d\s]*$');
+
+/// Return list containing inclusive range 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.
+///
+/// ```
+/// 1. final str = ''; // coverage:ignore-line
+/// 2. final str = '';
+/// 3. final str = ''; // coverage:ignore-start
+/// 4. final str = '';
+/// 5. final str = ''; // coverage:ignore-end
+/// ```
+///
+/// Returns
+/// ```
+/// [
+/// [1,1],
+/// [3,5],
+/// ]
+/// ```
+///
+List<List<int>> getIgnoredLines(String filePath, List<String>? lines) {
+ final ignoredLines = <List<int>>[];
+ 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(muliLineIgnoreEnd)) {
+ err ??= FormatException(
+ 'unmatched coverage:ignore-end found at $filePath:${i + 1}',
+ );
+ }
+
+ if (lines[i].contains(singleLineIgnore)) ignoredLines.add([i + 1, i + 1]);
+
+ if (lines[i].contains(muliLineIgnoreStart)) {
+ final start = i;
+ var isUnmatched = true;
+ ++i;
+ while (i < lines.length) {
+ if (lines[i].contains(ignoreFile)) return allLines;
+ if (lines[i].contains(muliLineIgnoreStart)) {
+ err ??= FormatException(
+ 'coverage:ignore-start found at $filePath:${i + 1}'
+ ' before previous coverage:ignore-start ended',
+ );
+ break;
+ }
+
+ if (lines[i].contains(muliLineIgnoreEnd)) {
+ ignoredLines.add([start + 1, i + 1]);
+ isUnmatched = false;
+ break;
+ }
+ ++i;
+ }
+
+ if (isUnmatched) {
+ err ??= FormatException(
+ 'coverage:ignore-start found at $filePath:${start + 1}'
+ ' has no matching coverage:ignore-end',
+ );
+ }
+ }
+ ++i;
+ }
+
+ if (err == null) {
+ return ignoredLines;
+ }
+
+ throw err;
+}
+
+extension StandardOutExtension on Stream<List<int>> {
+ Stream<String> lines() =>
+ transform(const SystemEncoding().decoder).transform(const LineSplitter());
+}
+
+Future<Uri> serviceUriFromProcess(Stream<String> procStdout) {
+ // Capture the VM service URI.
+ final serviceUriCompleter = Completer<Uri>();
+ procStdout.listen((line) {
+ if (!serviceUriCompleter.isCompleted) {
+ final serviceUri = extractVMServiceUri(line);
+ if (serviceUri != null) {
+ serviceUriCompleter.complete(serviceUri);
+ }
+ }
+ });
+ return serviceUriCompleter.future;
+}
+
+Future<List<IsolateRef>> getAllIsolates(VmService service) async =>
+ (await service.getVM()).isolates ?? [];
diff --git a/pkgs/coverage/pubspec.yaml b/pkgs/coverage/pubspec.yaml
new file mode 100644
index 0000000..2721626
--- /dev/null
+++ b/pkgs/coverage/pubspec.yaml
@@ -0,0 +1,33 @@
+name: coverage
+version: 1.11.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
+
+environment:
+ sdk: ^3.4.0
+
+dependencies:
+ args: ^2.0.0
+ glob: ^2.1.2
+ logging: ^1.0.0
+ meta: ^1.0.2
+ package_config: ^2.0.0
+ path: ^1.8.0
+ source_maps: ^0.10.10
+ stack_trace: ^1.10.0
+ vm_service: '>=12.0.0 <16.0.0'
+
+dev_dependencies:
+ benchmark_harness: ^2.2.0
+ build_runner: ^2.3.1
+ dart_flutter_team_lints: ^3.0.0
+ mockito: ^5.4.4
+ test: ^1.24.7
+ test_descriptor: ^2.0.0
+ test_process: ^2.0.0
+
+executables:
+ collect_coverage:
+ format_coverage:
+ test_with_coverage:
diff --git a/pkgs/coverage/test/README.md b/pkgs/coverage/test/README.md
new file mode 100644
index 0000000..8fa00df
--- /dev/null
+++ b/pkgs/coverage/test/README.md
@@ -0,0 +1,9 @@
+## Regenerating mocks
+
+Some of the tests use a mock VmService that is automatically generated by
+Mockito. If the VmService changes, run this command in the root directory of
+this repo to regenerate that mock:
+
+```bash
+dart run build_runner build
+```
diff --git a/pkgs/coverage/test/chrome_test.dart b/pkgs/coverage/test/chrome_test.dart
new file mode 100644
index 0000000..50e7600
--- /dev/null
+++ b/pkgs/coverage/test/chrome_test.dart
@@ -0,0 +1,78 @@
+// Copyright (c) 2020, 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.
+
+// TODO(https://github.com/dart-lang/tools/issues/494): Fix and re-enable this.
+@TestOn('!windows')
+library;
+
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:coverage/coverage.dart';
+import 'package:test/test.dart';
+
+// The scriptId for the main_test.js in the sample report.
+const String mainScriptId = '31';
+
+Future<String> sourceMapProvider(String scriptId) async {
+ if (scriptId != mainScriptId) {
+ return 'something invalid!';
+ }
+ return File('test/test_files/main_test.js.map').readAsString();
+}
+
+Future<String?> sourceProvider(String scriptId) async {
+ if (scriptId != mainScriptId) return null;
+ return File('test/test_files/main_test.js').readAsString();
+}
+
+Future<Uri> sourceUriProvider(String sourceUrl, String scriptId) async =>
+ Uri.parse(sourceUrl);
+
+void main() {
+ test('reports correctly', () async {
+ final preciseCoverage = json.decode(
+ await File('test/test_files/chrome_precise_report.txt')
+ .readAsString()) as List;
+
+ final report = await parseChromeCoverage(
+ preciseCoverage.cast(),
+ sourceProvider,
+ sourceMapProvider,
+ sourceUriProvider,
+ );
+
+ final sourceReport =
+ (report['coverage'] as List<Map<String, dynamic>>).firstWhere(
+ (Map<String, dynamic> report) =>
+ report['source'].toString().contains('main_test.dart'),
+ );
+
+ final expectedHits = {
+ 7: 1,
+ 11: 1,
+ 13: 1,
+ 14: 1,
+ 17: 0,
+ 19: 0,
+ 20: 0,
+ 22: 1,
+ 23: 1,
+ 24: 1,
+ 25: 1,
+ 28: 1,
+ 30: 0,
+ 32: 1,
+ 34: 1,
+ 35: 1,
+ 36: 1,
+ };
+
+ final hitMap = sourceReport['hits'] as List<int>;
+ expect(hitMap.length, equals(expectedHits.keys.length * 2));
+ for (var i = 0; i < hitMap.length; i += 2) {
+ expect(expectedHits[hitMap[i]], equals(hitMap[i + 1]));
+ }
+ });
+}
diff --git a/pkgs/coverage/test/collect_coverage_api_test.dart b/pkgs/coverage/test/collect_coverage_api_test.dart
new file mode 100644
index 0000000..fd3de43
--- /dev/null
+++ b/pkgs/coverage/test/collect_coverage_api_test.dart
@@ -0,0 +1,157 @@
+// Copyright (c) 2015, 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 'package:coverage/coverage.dart';
+import 'package:coverage/src/util.dart';
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+
+import 'test_util.dart';
+
+final _isolateLibPath = p.join('test', 'test_files', 'test_app_isolate.dart');
+
+final _sampleAppFileUri = p.toUri(p.absolute(testAppPath)).toString();
+final _isolateLibFileUri = p.toUri(p.absolute(_isolateLibPath)).toString();
+
+void main() {
+ test('collect_coverage_api', () async {
+ final coverage = coverageDataFromJson(await _collectCoverage());
+ expect(coverage, isNotEmpty);
+
+ final sources = coverage.sources();
+
+ for (var sampleCoverageData in sources[_sampleAppFileUri]!) {
+ expect(sampleCoverageData['hits'], isNotEmpty);
+ }
+
+ for (var sampleCoverageData in sources[_isolateLibFileUri]!) {
+ expect(sampleCoverageData['hits'], isNotEmpty);
+ }
+ });
+
+ test('collect_coverage_api with scoped output', () async {
+ final coverage = coverageDataFromJson(
+ await _collectCoverage(scopedOutput: <String>{}..add('coverage')),
+ );
+ expect(coverage, isNotEmpty);
+
+ final sources = coverage.sources();
+
+ for (var key in sources.keys) {
+ final uri = Uri.parse(key);
+ expect(uri.path.startsWith('coverage'), isTrue);
+ }
+ });
+
+ test('collect_coverage_api with isolateIds', () async {
+ final coverage =
+ coverageDataFromJson(await _collectCoverage(isolateIds: true));
+ expect(coverage, isEmpty);
+ });
+
+ test('collect_coverage_api with function coverage', () async {
+ final coverage =
+ coverageDataFromJson(await _collectCoverage(functionCoverage: true));
+ expect(coverage, isNotEmpty);
+
+ final sources = coverage.sources();
+
+ final functionInfo = functionInfoFromSources(sources);
+
+ expect(
+ functionInfo[_sampleAppFileUri]!,
+ {
+ 'main': 1,
+ 'usedMethod': 1,
+ 'unusedMethod': 0,
+ },
+ );
+
+ expect(
+ functionInfo[_isolateLibFileUri]!,
+ {
+ 'BarClass.BarClass': 1,
+ 'fooAsync': 1,
+ 'fooSync': 1,
+ 'isolateTask': 1,
+ 'BarClass.baz': 1
+ },
+ );
+ });
+
+ test('collect_coverage_api with branch coverage', () async {
+ final coverage =
+ coverageDataFromJson(await _collectCoverage(branchCoverage: true));
+ expect(coverage, isNotEmpty);
+
+ final sources = coverage.sources();
+
+ // Dart VM versions before 2.17 don't support branch coverage.
+ expect(sources[_sampleAppFileUri],
+ everyElement(containsPair('branchHits', isNotEmpty)));
+ expect(sources[_isolateLibFileUri],
+ everyElement(containsPair('branchHits', isNotEmpty)));
+ });
+
+ test('collect_coverage_api with coverableLineCache', () async {
+ final coverableLineCache = <String, Set<int>>{};
+ final coverage =
+ await _collectCoverage(coverableLineCache: coverableLineCache);
+ final result = await HitMap.parseJson(
+ coverage['coverage'] as List<Map<String, dynamic>>);
+
+ expect(coverableLineCache, contains(_sampleAppFileUri));
+ expect(coverableLineCache, contains(_isolateLibFileUri));
+
+ // Expect that we have some missed lines.
+ expect(result[_sampleAppFileUri]!.lineHits.containsValue(0), isTrue);
+ expect(result[_isolateLibFileUri]!.lineHits.containsValue(0), isTrue);
+
+ // Clear _sampleAppFileUri's cache entry, then gather coverage again. We're
+ // doing this to verify that force compilation is disabled for these
+ // libraries. The result should be that _isolateLibFileUri should be the
+ // same, but _sampleAppFileUri should be missing all its missed lines.
+ coverableLineCache[_sampleAppFileUri] = {};
+ final coverage2 =
+ await _collectCoverage(coverableLineCache: coverableLineCache);
+ final result2 = await HitMap.parseJson(
+ coverage2['coverage'] as List<Map<String, dynamic>>);
+
+ // _isolateLibFileUri still has missed lines, but _sampleAppFileUri doesn't.
+ expect(result2[_sampleAppFileUri]!.lineHits.containsValue(0), isFalse);
+ expect(result2[_isolateLibFileUri]!.lineHits.containsValue(0), isTrue);
+
+ // _isolateLibFileUri is the same. _sampleAppFileUri is the same, but
+ // without all its missed lines.
+ expect(result2[_isolateLibFileUri]!.lineHits,
+ result[_isolateLibFileUri]!.lineHits);
+ result[_sampleAppFileUri]!.lineHits.removeWhere((line, hits) => hits == 0);
+ expect(result2[_sampleAppFileUri]!.lineHits,
+ result[_sampleAppFileUri]!.lineHits);
+ }, skip: !platformVersionCheck(3, 2));
+}
+
+Future<Map<String, dynamic>> _collectCoverage(
+ {Set<String> scopedOutput = const {},
+ bool isolateIds = false,
+ bool functionCoverage = false,
+ bool branchCoverage = false,
+ Map<String, Set<int>>? coverableLineCache}) async {
+ final openPort = await getOpenPort();
+
+ // run the sample app, with the right flags
+ final sampleProcess = await runTestApp(openPort);
+
+ final serviceUri = await serviceUriFromProcess(sampleProcess.stdoutStream());
+ final isolateIdSet = isolateIds ? <String>{} : null;
+
+ return collect(serviceUri, true, true, false, scopedOutput,
+ timeout: timeout,
+ isolateIds: isolateIdSet,
+ functionCoverage: functionCoverage,
+ branchCoverage: branchCoverage,
+ coverableLineCache: coverableLineCache);
+}
diff --git a/pkgs/coverage/test/collect_coverage_mock_test.dart b/pkgs/coverage/test/collect_coverage_mock_test.dart
new file mode 100644
index 0000000..372bd48
--- /dev/null
+++ b/pkgs/coverage/test/collect_coverage_mock_test.dart
@@ -0,0 +1,416 @@
+// 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:coverage/coverage.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+import 'package:test/test.dart';
+import 'package:vm_service/vm_service.dart';
+
+import 'collect_coverage_mock_test.mocks.dart';
+
+@GenerateMocks([VmService])
+SourceReportRange _range(int scriptIndex, SourceReportCoverage coverage) =>
+ SourceReportRange(scriptIndex: scriptIndex, coverage: coverage);
+
+IsolateRef _isoRef(String id, String isoGroupId) =>
+ IsolateRef(id: id, isolateGroupId: isoGroupId);
+
+IsolateGroupRef _isoGroupRef(String id) => IsolateGroupRef(id: id);
+
+IsolateGroup _isoGroup(String id, List<IsolateRef> isolates) =>
+ IsolateGroup(id: id, isolates: isolates);
+
+class FakeSentinelException implements SentinelException {
+ @override
+ dynamic noSuchMethod(Invocation invocation) {}
+}
+
+MockVmService _mockService(
+ int majorVersion,
+ int minorVersion, {
+ Map<String, List<String>> isolateGroups = const {
+ 'isolateGroup': ['isolate'],
+ },
+}) {
+ final service = MockVmService();
+ final isoRefs = <IsolateRef>[];
+ final isoGroupRefs = <IsolateGroupRef>[];
+ final isoGroups = <IsolateGroup>[];
+ for (final group in isolateGroups.entries) {
+ isoGroupRefs.add(_isoGroupRef(group.key));
+ final isosOfGroup = <IsolateRef>[];
+ for (final isoId in group.value) {
+ isosOfGroup.add(_isoRef(isoId, group.key));
+ }
+ isoGroups.add(_isoGroup(group.key, isosOfGroup));
+ isoRefs.addAll(isosOfGroup);
+ }
+ when(service.getVM()).thenAnswer(
+ (_) async => VM(isolates: isoRefs, isolateGroups: isoGroupRefs));
+ for (final group in isoGroups) {
+ when(service.getIsolateGroup(group.id)).thenAnswer((_) async => group);
+ }
+ when(service.getVersion()).thenAnswer(
+ (_) async => Version(major: majorVersion, minor: minorVersion));
+ return service;
+}
+
+void main() {
+ group('Mock VM Service', () {
+ test('Collect coverage', () async {
+ final service = _mockService(4, 13);
+ when(service.getSourceReport(
+ 'isolate',
+ ['Coverage'],
+ forceCompile: true,
+ reportLines: true,
+ )).thenAnswer((_) async => SourceReport(
+ ranges: [
+ _range(
+ 0,
+ SourceReportCoverage(
+ hits: [12],
+ misses: [47],
+ ),
+ ),
+ _range(
+ 1,
+ SourceReportCoverage(
+ hits: [95],
+ misses: [52],
+ ),
+ ),
+ ],
+ scripts: [
+ ScriptRef(
+ uri: 'package:foo/foo.dart',
+ id: 'foo',
+ ),
+ ScriptRef(
+ uri: 'package:bar/bar.dart',
+ id: 'bar',
+ ),
+ ],
+ ));
+
+ final jsonResult = await collect(Uri(), false, false, false, null,
+ serviceOverrideForTesting: service);
+ final result = await HitMap.parseJson(
+ jsonResult['coverage'] as List<Map<String, dynamic>>);
+
+ expect(result.length, 2);
+ expect(result['package:foo/foo.dart']?.lineHits, {12: 1, 47: 0});
+ expect(result['package:bar/bar.dart']?.lineHits, {95: 1, 52: 0});
+ });
+
+ test('Collect coverage, scoped output', () async {
+ final service = _mockService(4, 13);
+ when(service.getSourceReport(
+ 'isolate',
+ ['Coverage'],
+ forceCompile: true,
+ reportLines: true,
+ libraryFilters: ['package:foo/'],
+ )).thenAnswer((_) async => SourceReport(
+ ranges: [
+ _range(
+ 0,
+ SourceReportCoverage(
+ hits: [12],
+ misses: [47],
+ ),
+ ),
+ ],
+ scripts: [
+ ScriptRef(
+ uri: 'package:foo/foo.dart',
+ id: 'foo',
+ ),
+ ],
+ ));
+
+ final jsonResult = await collect(Uri(), false, false, false, {'foo'},
+ serviceOverrideForTesting: service);
+ final result = await HitMap.parseJson(
+ jsonResult['coverage'] as List<Map<String, dynamic>>);
+
+ expect(result.length, 1);
+ expect(result['package:foo/foo.dart']?.lineHits, {12: 1, 47: 0});
+ });
+
+ test('Collect coverage, fast isolate group deduping', () async {
+ final service = _mockService(4, 13, isolateGroups: {
+ 'isolateGroupA': ['isolate1', 'isolate2'],
+ 'isolateGroupB': ['isolate3'],
+ });
+ when(service.getSourceReport(
+ 'isolate1',
+ ['Coverage'],
+ forceCompile: true,
+ reportLines: true,
+ )).thenAnswer((_) async => SourceReport(
+ ranges: [
+ _range(
+ 0,
+ SourceReportCoverage(
+ hits: [12],
+ misses: [47],
+ ),
+ ),
+ _range(
+ 1,
+ SourceReportCoverage(
+ hits: [95],
+ misses: [52],
+ ),
+ ),
+ ],
+ scripts: [
+ ScriptRef(
+ uri: 'package:foo/foo.dart',
+ id: 'foo',
+ ),
+ ScriptRef(
+ uri: 'package:bar/bar.dart',
+ id: 'bar',
+ ),
+ ],
+ ));
+ when(service.getSourceReport(
+ 'isolate3',
+ ['Coverage'],
+ forceCompile: true,
+ reportLines: true,
+ )).thenAnswer((_) async => SourceReport(
+ ranges: [
+ _range(
+ 0,
+ SourceReportCoverage(
+ hits: [34],
+ misses: [61],
+ ),
+ ),
+ ],
+ scripts: [
+ ScriptRef(
+ uri: 'package:baz/baz.dart',
+ id: 'baz',
+ ),
+ ],
+ ));
+
+ final jsonResult = await collect(Uri(), false, false, false, null,
+ serviceOverrideForTesting: service);
+ final result = await HitMap.parseJson(
+ jsonResult['coverage'] as List<Map<String, dynamic>>);
+
+ expect(result.length, 3);
+ expect(result['package:foo/foo.dart']?.lineHits, {12: 1, 47: 0});
+ expect(result['package:bar/bar.dart']?.lineHits, {95: 1, 52: 0});
+ expect(result['package:baz/baz.dart']?.lineHits, {34: 1, 61: 0});
+ verifyNever(service.getSourceReport('isolate2', ['Coverage'],
+ forceCompile: true, reportLines: true));
+ verifyNever(service.getIsolateGroup('isolateGroupA'));
+ verifyNever(service.getIsolateGroup('isolateGroupB'));
+ });
+
+ test(
+ 'Collect coverage, no scoped output, '
+ 'handles SentinelException from getSourceReport', () async {
+ final service = _mockService(4, 13);
+ when(service.getSourceReport(
+ 'isolate',
+ ['Coverage'],
+ forceCompile: true,
+ reportLines: true,
+ )).thenThrow(FakeSentinelException());
+
+ final jsonResult = await collect(Uri(), false, false, false, null,
+ serviceOverrideForTesting: service);
+ final result = await HitMap.parseJson(
+ jsonResult['coverage'] as List<Map<String, dynamic>>);
+
+ expect(result.length, 0);
+ });
+
+ test('Collect coverage, coverableLineCache', () async {
+ // Expect that on the first getSourceReport call, librariesAlreadyCompiled
+ // is empty.
+ final service = _mockService(4, 13);
+ when(service.getSourceReport(
+ 'isolate',
+ ['Coverage'],
+ forceCompile: true,
+ reportLines: true,
+ librariesAlreadyCompiled: [],
+ )).thenAnswer((_) async => SourceReport(
+ ranges: [
+ _range(
+ 0,
+ SourceReportCoverage(
+ hits: [12],
+ misses: [47],
+ ),
+ ),
+ _range(
+ 1,
+ SourceReportCoverage(
+ hits: [95],
+ misses: [52],
+ ),
+ ),
+ ],
+ scripts: [
+ ScriptRef(
+ uri: 'package:foo/foo.dart',
+ id: 'foo',
+ ),
+ ScriptRef(
+ uri: 'package:bar/bar.dart',
+ id: 'bar',
+ ),
+ ],
+ ));
+
+ final coverableLineCache = <String, Set<int>>{};
+ final jsonResult = await collect(Uri(), false, false, false, null,
+ coverableLineCache: coverableLineCache,
+ serviceOverrideForTesting: service);
+ final result = await HitMap.parseJson(
+ jsonResult['coverage'] as List<Map<String, dynamic>>);
+
+ expect(result.length, 2);
+ expect(result['package:foo/foo.dart']?.lineHits, {12: 1, 47: 0});
+ expect(result['package:bar/bar.dart']?.lineHits, {95: 1, 52: 0});
+
+ // The coverableLineCache should now be filled with all the lines that
+ // were hit or missed.
+ expect(coverableLineCache, {
+ 'package:foo/foo.dart': {12, 47},
+ 'package:bar/bar.dart': {95, 52},
+ });
+
+ // The second getSourceReport call should now list all the libraries we've
+ // seen. The response won't contain any misses for these libraries,
+ // because they won't be force compiled. We'll also return a 3rd library,
+ // which will contain misses, as it hasn't been compiled yet.
+ when(service.getSourceReport(
+ 'isolate',
+ ['Coverage'],
+ forceCompile: true,
+ reportLines: true,
+ librariesAlreadyCompiled: [
+ 'package:foo/foo.dart',
+ 'package:bar/bar.dart'
+ ],
+ )).thenAnswer((_) async => SourceReport(
+ ranges: [
+ _range(
+ 0,
+ SourceReportCoverage(
+ hits: [47],
+ ),
+ ),
+ _range(
+ 1,
+ SourceReportCoverage(
+ hits: [95],
+ ),
+ ),
+ _range(
+ 2,
+ SourceReportCoverage(
+ hits: [36],
+ misses: [81],
+ ),
+ ),
+ ],
+ scripts: [
+ ScriptRef(
+ uri: 'package:foo/foo.dart',
+ id: 'foo',
+ ),
+ ScriptRef(
+ uri: 'package:bar/bar.dart',
+ id: 'bar',
+ ),
+ ScriptRef(
+ uri: 'package:baz/baz.dart',
+ id: 'baz',
+ ),
+ ],
+ ));
+
+ final jsonResult2 = await collect(Uri(), false, false, false, null,
+ coverableLineCache: coverableLineCache,
+ serviceOverrideForTesting: service);
+ final result2 = await HitMap.parseJson(
+ jsonResult2['coverage'] as List<Map<String, dynamic>>);
+
+ // The missed lines still appear in foo and bar, even though they weren't
+ // returned in the response. They were read from the cache.
+ expect(result2.length, 3);
+ expect(result2['package:foo/foo.dart']?.lineHits, {12: 0, 47: 1});
+ expect(result2['package:bar/bar.dart']?.lineHits, {95: 1, 52: 0});
+ expect(result2['package:baz/baz.dart']?.lineHits, {36: 1, 81: 0});
+
+ // The coverableLineCache should now also contain the baz library.
+ expect(coverableLineCache, {
+ 'package:foo/foo.dart': {12, 47},
+ 'package:bar/bar.dart': {95, 52},
+ 'package:baz/baz.dart': {36, 81},
+ });
+ });
+
+ test(
+ 'Collect coverage, scoped output, '
+ 'handles SourceReports that contain unfiltered ranges', () async {
+ // Regression test for https://github.com/dart-lang/tools/issues/530
+ final service = _mockService(4, 13);
+ when(service.getSourceReport(
+ 'isolate',
+ ['Coverage'],
+ forceCompile: true,
+ reportLines: true,
+ libraryFilters: ['package:foo/'],
+ )).thenAnswer((_) async => SourceReport(
+ ranges: [
+ _range(
+ 0,
+ SourceReportCoverage(
+ hits: [12],
+ misses: [47],
+ ),
+ ),
+ _range(
+ 1,
+ SourceReportCoverage(
+ hits: [86],
+ misses: [91],
+ ),
+ ),
+ ],
+ scripts: [
+ ScriptRef(
+ uri: 'package:foo/foo.dart',
+ id: 'foo',
+ ),
+ ScriptRef(
+ uri: 'package:bar/bar.dart',
+ id: 'bar',
+ ),
+ ],
+ ));
+
+ final jsonResult = await collect(Uri(), false, false, false, {'foo'},
+ serviceOverrideForTesting: service);
+ final result = await HitMap.parseJson(
+ jsonResult['coverage'] as List<Map<String, dynamic>>);
+
+ expect(result.length, 1);
+ expect(result['package:foo/foo.dart']?.lineHits, {12: 1, 47: 0});
+ });
+ });
+}
diff --git a/pkgs/coverage/test/collect_coverage_mock_test.mocks.dart b/pkgs/coverage/test/collect_coverage_mock_test.mocks.dart
new file mode 100644
index 0000000..86f596a
--- /dev/null
+++ b/pkgs/coverage/test/collect_coverage_mock_test.mocks.dart
@@ -0,0 +1,2047 @@
+// Mocks generated by Mockito 5.4.4 from annotations
+// in coverage/test/collect_coverage_mock_test.dart.
+// Do not manually edit this file.
+
+// ignore_for_file: no_leading_underscores_for_library_prefixes
+import 'dart:async' as _i3;
+
+import 'package:mockito/mockito.dart' as _i1;
+import 'package:mockito/src/dummies.dart' as _i4;
+import 'package:vm_service/src/vm_service.dart' as _i2;
+
+// ignore_for_file: type=lint
+// ignore_for_file: avoid_redundant_argument_values
+// ignore_for_file: avoid_setters_without_getters
+// ignore_for_file: comment_references
+// ignore_for_file: deprecated_member_use
+// ignore_for_file: deprecated_member_use_from_same_package
+// ignore_for_file: implementation_imports
+// ignore_for_file: invalid_use_of_visible_for_testing_member
+// ignore_for_file: prefer_const_constructors
+// ignore_for_file: unnecessary_parenthesis
+// ignore_for_file: camel_case_types
+// ignore_for_file: subtype_of_sealed_class
+
+class _FakeBreakpoint_0 extends _i1.SmartFake implements _i2.Breakpoint {
+ _FakeBreakpoint_0(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeSuccess_1 extends _i1.SmartFake implements _i2.Success {
+ _FakeSuccess_1(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeIdZone_2 extends _i1.SmartFake implements _i2.IdZone {
+ _FakeIdZone_2(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeResponse_3 extends _i1.SmartFake implements _i2.Response {
+ _FakeResponse_3(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeAllocationProfile_4 extends _i1.SmartFake
+ implements _i2.AllocationProfile {
+ _FakeAllocationProfile_4(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeCpuSamples_5 extends _i1.SmartFake implements _i2.CpuSamples {
+ _FakeCpuSamples_5(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeClassList_6 extends _i1.SmartFake implements _i2.ClassList {
+ _FakeClassList_6(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeFlagList_7 extends _i1.SmartFake implements _i2.FlagList {
+ _FakeFlagList_7(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeInboundReferences_8 extends _i1.SmartFake
+ implements _i2.InboundReferences {
+ _FakeInboundReferences_8(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeInstanceSet_9 extends _i1.SmartFake implements _i2.InstanceSet {
+ _FakeInstanceSet_9(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeInstanceRef_10 extends _i1.SmartFake implements _i2.InstanceRef {
+ _FakeInstanceRef_10(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeIsolate_11 extends _i1.SmartFake implements _i2.Isolate {
+ _FakeIsolate_11(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeIsolateGroup_12 extends _i1.SmartFake implements _i2.IsolateGroup {
+ _FakeIsolateGroup_12(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeEvent_13 extends _i1.SmartFake implements _i2.Event {
+ _FakeEvent_13(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeMemoryUsage_14 extends _i1.SmartFake implements _i2.MemoryUsage {
+ _FakeMemoryUsage_14(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeScriptList_15 extends _i1.SmartFake implements _i2.ScriptList {
+ _FakeScriptList_15(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeObj_16 extends _i1.SmartFake implements _i2.Obj {
+ _FakeObj_16(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakePerfettoCpuSamples_17 extends _i1.SmartFake
+ implements _i2.PerfettoCpuSamples {
+ _FakePerfettoCpuSamples_17(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakePerfettoTimeline_18 extends _i1.SmartFake
+ implements _i2.PerfettoTimeline {
+ _FakePerfettoTimeline_18(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakePortList_19 extends _i1.SmartFake implements _i2.PortList {
+ _FakePortList_19(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeRetainingPath_20 extends _i1.SmartFake implements _i2.RetainingPath {
+ _FakeRetainingPath_20(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeProcessMemoryUsage_21 extends _i1.SmartFake
+ implements _i2.ProcessMemoryUsage {
+ _FakeProcessMemoryUsage_21(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeStack_22 extends _i1.SmartFake implements _i2.Stack {
+ _FakeStack_22(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeProtocolList_23 extends _i1.SmartFake implements _i2.ProtocolList {
+ _FakeProtocolList_23(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeSourceReport_24 extends _i1.SmartFake implements _i2.SourceReport {
+ _FakeSourceReport_24(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeVersion_25 extends _i1.SmartFake implements _i2.Version {
+ _FakeVersion_25(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeVM_26 extends _i1.SmartFake implements _i2.VM {
+ _FakeVM_26(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeTimeline_27 extends _i1.SmartFake implements _i2.Timeline {
+ _FakeTimeline_27(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeTimelineFlags_28 extends _i1.SmartFake implements _i2.TimelineFlags {
+ _FakeTimelineFlags_28(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeTimestamp_29 extends _i1.SmartFake implements _i2.Timestamp {
+ _FakeTimestamp_29(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeUriList_30 extends _i1.SmartFake implements _i2.UriList {
+ _FakeUriList_30(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeReloadReport_31 extends _i1.SmartFake implements _i2.ReloadReport {
+ _FakeReloadReport_31(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeFuture_32<T1> extends _i1.SmartFake implements _i3.Future<T1> {
+ _FakeFuture_32(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+/// A class which mocks [VmService].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockVmService extends _i1.Mock implements _i2.VmService {
+ MockVmService() {
+ _i1.throwOnMissingStub(this);
+ }
+
+ @override
+ _i3.Stream<String> get onSend => (super.noSuchMethod(
+ Invocation.getter(#onSend),
+ returnValue: _i3.Stream<String>.empty(),
+ ) as _i3.Stream<String>);
+
+ @override
+ _i3.Stream<String> get onReceive => (super.noSuchMethod(
+ Invocation.getter(#onReceive),
+ returnValue: _i3.Stream<String>.empty(),
+ ) as _i3.Stream<String>);
+
+ @override
+ _i3.Future<void> get onDone => (super.noSuchMethod(
+ Invocation.getter(#onDone),
+ returnValue: _i3.Future<void>.value(),
+ ) as _i3.Future<void>);
+
+ @override
+ _i3.Stream<_i2.Event> get onVMEvent => (super.noSuchMethod(
+ Invocation.getter(#onVMEvent),
+ returnValue: _i3.Stream<_i2.Event>.empty(),
+ ) as _i3.Stream<_i2.Event>);
+
+ @override
+ _i3.Stream<_i2.Event> get onIsolateEvent => (super.noSuchMethod(
+ Invocation.getter(#onIsolateEvent),
+ returnValue: _i3.Stream<_i2.Event>.empty(),
+ ) as _i3.Stream<_i2.Event>);
+
+ @override
+ _i3.Stream<_i2.Event> get onDebugEvent => (super.noSuchMethod(
+ Invocation.getter(#onDebugEvent),
+ returnValue: _i3.Stream<_i2.Event>.empty(),
+ ) as _i3.Stream<_i2.Event>);
+
+ @override
+ _i3.Stream<_i2.Event> get onProfilerEvent => (super.noSuchMethod(
+ Invocation.getter(#onProfilerEvent),
+ returnValue: _i3.Stream<_i2.Event>.empty(),
+ ) as _i3.Stream<_i2.Event>);
+
+ @override
+ _i3.Stream<_i2.Event> get onGCEvent => (super.noSuchMethod(
+ Invocation.getter(#onGCEvent),
+ returnValue: _i3.Stream<_i2.Event>.empty(),
+ ) as _i3.Stream<_i2.Event>);
+
+ @override
+ _i3.Stream<_i2.Event> get onExtensionEvent => (super.noSuchMethod(
+ Invocation.getter(#onExtensionEvent),
+ returnValue: _i3.Stream<_i2.Event>.empty(),
+ ) as _i3.Stream<_i2.Event>);
+
+ @override
+ _i3.Stream<_i2.Event> get onTimelineEvent => (super.noSuchMethod(
+ Invocation.getter(#onTimelineEvent),
+ returnValue: _i3.Stream<_i2.Event>.empty(),
+ ) as _i3.Stream<_i2.Event>);
+
+ @override
+ _i3.Stream<_i2.Event> get onLoggingEvent => (super.noSuchMethod(
+ Invocation.getter(#onLoggingEvent),
+ returnValue: _i3.Stream<_i2.Event>.empty(),
+ ) as _i3.Stream<_i2.Event>);
+
+ @override
+ _i3.Stream<_i2.Event> get onServiceEvent => (super.noSuchMethod(
+ Invocation.getter(#onServiceEvent),
+ returnValue: _i3.Stream<_i2.Event>.empty(),
+ ) as _i3.Stream<_i2.Event>);
+
+ @override
+ _i3.Stream<_i2.Event> get onHeapSnapshotEvent => (super.noSuchMethod(
+ Invocation.getter(#onHeapSnapshotEvent),
+ returnValue: _i3.Stream<_i2.Event>.empty(),
+ ) as _i3.Stream<_i2.Event>);
+
+ @override
+ _i3.Stream<_i2.Event> get onStdoutEvent => (super.noSuchMethod(
+ Invocation.getter(#onStdoutEvent),
+ returnValue: _i3.Stream<_i2.Event>.empty(),
+ ) as _i3.Stream<_i2.Event>);
+
+ @override
+ _i3.Stream<_i2.Event> get onStderrEvent => (super.noSuchMethod(
+ Invocation.getter(#onStderrEvent),
+ returnValue: _i3.Stream<_i2.Event>.empty(),
+ ) as _i3.Stream<_i2.Event>);
+
+ @override
+ _i3.Stream<_i2.Event> onEvent(String? streamId) => (super.noSuchMethod(
+ Invocation.method(
+ #onEvent,
+ [streamId],
+ ),
+ returnValue: _i3.Stream<_i2.Event>.empty(),
+ ) as _i3.Stream<_i2.Event>);
+
+ @override
+ _i3.Future<_i2.Breakpoint> addBreakpoint(
+ String? isolateId,
+ String? scriptId,
+ int? line, {
+ int? column,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #addBreakpoint,
+ [
+ isolateId,
+ scriptId,
+ line,
+ ],
+ {#column: column},
+ ),
+ returnValue: _i3.Future<_i2.Breakpoint>.value(_FakeBreakpoint_0(
+ this,
+ Invocation.method(
+ #addBreakpoint,
+ [
+ isolateId,
+ scriptId,
+ line,
+ ],
+ {#column: column},
+ ),
+ )),
+ ) as _i3.Future<_i2.Breakpoint>);
+
+ @override
+ _i3.Future<_i2.Breakpoint> addBreakpointWithScriptUri(
+ String? isolateId,
+ String? scriptUri,
+ int? line, {
+ int? column,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #addBreakpointWithScriptUri,
+ [
+ isolateId,
+ scriptUri,
+ line,
+ ],
+ {#column: column},
+ ),
+ returnValue: _i3.Future<_i2.Breakpoint>.value(_FakeBreakpoint_0(
+ this,
+ Invocation.method(
+ #addBreakpointWithScriptUri,
+ [
+ isolateId,
+ scriptUri,
+ line,
+ ],
+ {#column: column},
+ ),
+ )),
+ ) as _i3.Future<_i2.Breakpoint>);
+
+ @override
+ _i3.Future<_i2.Breakpoint> addBreakpointAtEntry(
+ String? isolateId,
+ String? functionId,
+ ) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #addBreakpointAtEntry,
+ [
+ isolateId,
+ functionId,
+ ],
+ ),
+ returnValue: _i3.Future<_i2.Breakpoint>.value(_FakeBreakpoint_0(
+ this,
+ Invocation.method(
+ #addBreakpointAtEntry,
+ [
+ isolateId,
+ functionId,
+ ],
+ ),
+ )),
+ ) as _i3.Future<_i2.Breakpoint>);
+
+ @override
+ _i3.Future<_i2.Success> clearCpuSamples(String? isolateId) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #clearCpuSamples,
+ [isolateId],
+ ),
+ returnValue: _i3.Future<_i2.Success>.value(_FakeSuccess_1(
+ this,
+ Invocation.method(
+ #clearCpuSamples,
+ [isolateId],
+ ),
+ )),
+ ) as _i3.Future<_i2.Success>);
+
+ @override
+ _i3.Future<_i2.Success> clearVMTimeline() => (super.noSuchMethod(
+ Invocation.method(
+ #clearVMTimeline,
+ [],
+ ),
+ returnValue: _i3.Future<_i2.Success>.value(_FakeSuccess_1(
+ this,
+ Invocation.method(
+ #clearVMTimeline,
+ [],
+ ),
+ )),
+ ) as _i3.Future<_i2.Success>);
+
+ @override
+ _i3.Future<_i2.IdZone> createIdZone(
+ String? isolateId,
+ String? backingBufferKind,
+ String? idAssignmentPolicy, {
+ int? capacity,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #createIdZone,
+ [
+ isolateId,
+ backingBufferKind,
+ idAssignmentPolicy,
+ ],
+ {#capacity: capacity},
+ ),
+ returnValue: _i3.Future<_i2.IdZone>.value(_FakeIdZone_2(
+ this,
+ Invocation.method(
+ #createIdZone,
+ [
+ isolateId,
+ backingBufferKind,
+ idAssignmentPolicy,
+ ],
+ {#capacity: capacity},
+ ),
+ )),
+ ) as _i3.Future<_i2.IdZone>);
+
+ @override
+ _i3.Future<_i2.Success> deleteIdZone(
+ String? isolateId,
+ String? idZoneId,
+ ) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #deleteIdZone,
+ [
+ isolateId,
+ idZoneId,
+ ],
+ ),
+ returnValue: _i3.Future<_i2.Success>.value(_FakeSuccess_1(
+ this,
+ Invocation.method(
+ #deleteIdZone,
+ [
+ isolateId,
+ idZoneId,
+ ],
+ ),
+ )),
+ ) as _i3.Future<_i2.Success>);
+
+ @override
+ _i3.Future<_i2.Success> invalidateIdZone(
+ String? isolateId,
+ String? idZoneId,
+ ) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #invalidateIdZone,
+ [
+ isolateId,
+ idZoneId,
+ ],
+ ),
+ returnValue: _i3.Future<_i2.Success>.value(_FakeSuccess_1(
+ this,
+ Invocation.method(
+ #invalidateIdZone,
+ [
+ isolateId,
+ idZoneId,
+ ],
+ ),
+ )),
+ ) as _i3.Future<_i2.Success>);
+
+ @override
+ _i3.Future<_i2.Response> invoke(
+ String? isolateId,
+ String? targetId,
+ String? selector,
+ List<String>? argumentIds, {
+ bool? disableBreakpoints,
+ String? idZoneId,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #invoke,
+ [
+ isolateId,
+ targetId,
+ selector,
+ argumentIds,
+ ],
+ {
+ #disableBreakpoints: disableBreakpoints,
+ #idZoneId: idZoneId,
+ },
+ ),
+ returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_3(
+ this,
+ Invocation.method(
+ #invoke,
+ [
+ isolateId,
+ targetId,
+ selector,
+ argumentIds,
+ ],
+ {
+ #disableBreakpoints: disableBreakpoints,
+ #idZoneId: idZoneId,
+ },
+ ),
+ )),
+ ) as _i3.Future<_i2.Response>);
+
+ @override
+ _i3.Future<_i2.Response> evaluate(
+ String? isolateId,
+ String? targetId,
+ String? expression, {
+ Map<String, String>? scope,
+ bool? disableBreakpoints,
+ String? idZoneId,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #evaluate,
+ [
+ isolateId,
+ targetId,
+ expression,
+ ],
+ {
+ #scope: scope,
+ #disableBreakpoints: disableBreakpoints,
+ #idZoneId: idZoneId,
+ },
+ ),
+ returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_3(
+ this,
+ Invocation.method(
+ #evaluate,
+ [
+ isolateId,
+ targetId,
+ expression,
+ ],
+ {
+ #scope: scope,
+ #disableBreakpoints: disableBreakpoints,
+ #idZoneId: idZoneId,
+ },
+ ),
+ )),
+ ) as _i3.Future<_i2.Response>);
+
+ @override
+ _i3.Future<_i2.Response> evaluateInFrame(
+ String? isolateId,
+ int? frameIndex,
+ String? expression, {
+ Map<String, String>? scope,
+ bool? disableBreakpoints,
+ String? idZoneId,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #evaluateInFrame,
+ [
+ isolateId,
+ frameIndex,
+ expression,
+ ],
+ {
+ #scope: scope,
+ #disableBreakpoints: disableBreakpoints,
+ #idZoneId: idZoneId,
+ },
+ ),
+ returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_3(
+ this,
+ Invocation.method(
+ #evaluateInFrame,
+ [
+ isolateId,
+ frameIndex,
+ expression,
+ ],
+ {
+ #scope: scope,
+ #disableBreakpoints: disableBreakpoints,
+ #idZoneId: idZoneId,
+ },
+ ),
+ )),
+ ) as _i3.Future<_i2.Response>);
+
+ @override
+ _i3.Future<_i2.AllocationProfile> getAllocationProfile(
+ String? isolateId, {
+ bool? reset,
+ bool? gc,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getAllocationProfile,
+ [isolateId],
+ {
+ #reset: reset,
+ #gc: gc,
+ },
+ ),
+ returnValue:
+ _i3.Future<_i2.AllocationProfile>.value(_FakeAllocationProfile_4(
+ this,
+ Invocation.method(
+ #getAllocationProfile,
+ [isolateId],
+ {
+ #reset: reset,
+ #gc: gc,
+ },
+ ),
+ )),
+ ) as _i3.Future<_i2.AllocationProfile>);
+
+ @override
+ _i3.Future<_i2.CpuSamples> getAllocationTraces(
+ String? isolateId, {
+ int? timeOriginMicros,
+ int? timeExtentMicros,
+ String? classId,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getAllocationTraces,
+ [isolateId],
+ {
+ #timeOriginMicros: timeOriginMicros,
+ #timeExtentMicros: timeExtentMicros,
+ #classId: classId,
+ },
+ ),
+ returnValue: _i3.Future<_i2.CpuSamples>.value(_FakeCpuSamples_5(
+ this,
+ Invocation.method(
+ #getAllocationTraces,
+ [isolateId],
+ {
+ #timeOriginMicros: timeOriginMicros,
+ #timeExtentMicros: timeExtentMicros,
+ #classId: classId,
+ },
+ ),
+ )),
+ ) as _i3.Future<_i2.CpuSamples>);
+
+ @override
+ _i3.Future<_i2.ClassList> getClassList(String? isolateId) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getClassList,
+ [isolateId],
+ ),
+ returnValue: _i3.Future<_i2.ClassList>.value(_FakeClassList_6(
+ this,
+ Invocation.method(
+ #getClassList,
+ [isolateId],
+ ),
+ )),
+ ) as _i3.Future<_i2.ClassList>);
+
+ @override
+ _i3.Future<_i2.CpuSamples> getCpuSamples(
+ String? isolateId,
+ int? timeOriginMicros,
+ int? timeExtentMicros,
+ ) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getCpuSamples,
+ [
+ isolateId,
+ timeOriginMicros,
+ timeExtentMicros,
+ ],
+ ),
+ returnValue: _i3.Future<_i2.CpuSamples>.value(_FakeCpuSamples_5(
+ this,
+ Invocation.method(
+ #getCpuSamples,
+ [
+ isolateId,
+ timeOriginMicros,
+ timeExtentMicros,
+ ],
+ ),
+ )),
+ ) as _i3.Future<_i2.CpuSamples>);
+
+ @override
+ _i3.Future<_i2.FlagList> getFlagList() => (super.noSuchMethod(
+ Invocation.method(
+ #getFlagList,
+ [],
+ ),
+ returnValue: _i3.Future<_i2.FlagList>.value(_FakeFlagList_7(
+ this,
+ Invocation.method(
+ #getFlagList,
+ [],
+ ),
+ )),
+ ) as _i3.Future<_i2.FlagList>);
+
+ @override
+ _i3.Future<_i2.InboundReferences> getInboundReferences(
+ String? isolateId,
+ String? targetId,
+ int? limit, {
+ String? idZoneId,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getInboundReferences,
+ [
+ isolateId,
+ targetId,
+ limit,
+ ],
+ {#idZoneId: idZoneId},
+ ),
+ returnValue:
+ _i3.Future<_i2.InboundReferences>.value(_FakeInboundReferences_8(
+ this,
+ Invocation.method(
+ #getInboundReferences,
+ [
+ isolateId,
+ targetId,
+ limit,
+ ],
+ {#idZoneId: idZoneId},
+ ),
+ )),
+ ) as _i3.Future<_i2.InboundReferences>);
+
+ @override
+ _i3.Future<_i2.InstanceSet> getInstances(
+ String? isolateId,
+ String? objectId,
+ int? limit, {
+ bool? includeSubclasses,
+ bool? includeImplementers,
+ String? idZoneId,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getInstances,
+ [
+ isolateId,
+ objectId,
+ limit,
+ ],
+ {
+ #includeSubclasses: includeSubclasses,
+ #includeImplementers: includeImplementers,
+ #idZoneId: idZoneId,
+ },
+ ),
+ returnValue: _i3.Future<_i2.InstanceSet>.value(_FakeInstanceSet_9(
+ this,
+ Invocation.method(
+ #getInstances,
+ [
+ isolateId,
+ objectId,
+ limit,
+ ],
+ {
+ #includeSubclasses: includeSubclasses,
+ #includeImplementers: includeImplementers,
+ #idZoneId: idZoneId,
+ },
+ ),
+ )),
+ ) as _i3.Future<_i2.InstanceSet>);
+
+ @override
+ _i3.Future<_i2.InstanceRef> getInstancesAsList(
+ String? isolateId,
+ String? objectId, {
+ bool? includeSubclasses,
+ bool? includeImplementers,
+ String? idZoneId,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getInstancesAsList,
+ [
+ isolateId,
+ objectId,
+ ],
+ {
+ #includeSubclasses: includeSubclasses,
+ #includeImplementers: includeImplementers,
+ #idZoneId: idZoneId,
+ },
+ ),
+ returnValue: _i3.Future<_i2.InstanceRef>.value(_FakeInstanceRef_10(
+ this,
+ Invocation.method(
+ #getInstancesAsList,
+ [
+ isolateId,
+ objectId,
+ ],
+ {
+ #includeSubclasses: includeSubclasses,
+ #includeImplementers: includeImplementers,
+ #idZoneId: idZoneId,
+ },
+ ),
+ )),
+ ) as _i3.Future<_i2.InstanceRef>);
+
+ @override
+ _i3.Future<_i2.Isolate> getIsolate(String? isolateId) => (super.noSuchMethod(
+ Invocation.method(
+ #getIsolate,
+ [isolateId],
+ ),
+ returnValue: _i3.Future<_i2.Isolate>.value(_FakeIsolate_11(
+ this,
+ Invocation.method(
+ #getIsolate,
+ [isolateId],
+ ),
+ )),
+ ) as _i3.Future<_i2.Isolate>);
+
+ @override
+ _i3.Future<_i2.IsolateGroup> getIsolateGroup(String? isolateGroupId) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getIsolateGroup,
+ [isolateGroupId],
+ ),
+ returnValue: _i3.Future<_i2.IsolateGroup>.value(_FakeIsolateGroup_12(
+ this,
+ Invocation.method(
+ #getIsolateGroup,
+ [isolateGroupId],
+ ),
+ )),
+ ) as _i3.Future<_i2.IsolateGroup>);
+
+ @override
+ _i3.Future<_i2.Event> getIsolatePauseEvent(String? isolateId) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getIsolatePauseEvent,
+ [isolateId],
+ ),
+ returnValue: _i3.Future<_i2.Event>.value(_FakeEvent_13(
+ this,
+ Invocation.method(
+ #getIsolatePauseEvent,
+ [isolateId],
+ ),
+ )),
+ ) as _i3.Future<_i2.Event>);
+
+ @override
+ _i3.Future<_i2.MemoryUsage> getMemoryUsage(String? isolateId) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getMemoryUsage,
+ [isolateId],
+ ),
+ returnValue: _i3.Future<_i2.MemoryUsage>.value(_FakeMemoryUsage_14(
+ this,
+ Invocation.method(
+ #getMemoryUsage,
+ [isolateId],
+ ),
+ )),
+ ) as _i3.Future<_i2.MemoryUsage>);
+
+ @override
+ _i3.Future<_i2.MemoryUsage> getIsolateGroupMemoryUsage(
+ String? isolateGroupId) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getIsolateGroupMemoryUsage,
+ [isolateGroupId],
+ ),
+ returnValue: _i3.Future<_i2.MemoryUsage>.value(_FakeMemoryUsage_14(
+ this,
+ Invocation.method(
+ #getIsolateGroupMemoryUsage,
+ [isolateGroupId],
+ ),
+ )),
+ ) as _i3.Future<_i2.MemoryUsage>);
+
+ @override
+ _i3.Future<_i2.ScriptList> getScripts(String? isolateId) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getScripts,
+ [isolateId],
+ ),
+ returnValue: _i3.Future<_i2.ScriptList>.value(_FakeScriptList_15(
+ this,
+ Invocation.method(
+ #getScripts,
+ [isolateId],
+ ),
+ )),
+ ) as _i3.Future<_i2.ScriptList>);
+
+ @override
+ _i3.Future<_i2.Obj> getObject(
+ String? isolateId,
+ String? objectId, {
+ int? offset,
+ int? count,
+ String? idZoneId,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getObject,
+ [
+ isolateId,
+ objectId,
+ ],
+ {
+ #offset: offset,
+ #count: count,
+ #idZoneId: idZoneId,
+ },
+ ),
+ returnValue: _i3.Future<_i2.Obj>.value(_FakeObj_16(
+ this,
+ Invocation.method(
+ #getObject,
+ [
+ isolateId,
+ objectId,
+ ],
+ {
+ #offset: offset,
+ #count: count,
+ #idZoneId: idZoneId,
+ },
+ ),
+ )),
+ ) as _i3.Future<_i2.Obj>);
+
+ @override
+ _i3.Future<_i2.PerfettoCpuSamples> getPerfettoCpuSamples(
+ String? isolateId, {
+ int? timeOriginMicros,
+ int? timeExtentMicros,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getPerfettoCpuSamples,
+ [isolateId],
+ {
+ #timeOriginMicros: timeOriginMicros,
+ #timeExtentMicros: timeExtentMicros,
+ },
+ ),
+ returnValue:
+ _i3.Future<_i2.PerfettoCpuSamples>.value(_FakePerfettoCpuSamples_17(
+ this,
+ Invocation.method(
+ #getPerfettoCpuSamples,
+ [isolateId],
+ {
+ #timeOriginMicros: timeOriginMicros,
+ #timeExtentMicros: timeExtentMicros,
+ },
+ ),
+ )),
+ ) as _i3.Future<_i2.PerfettoCpuSamples>);
+
+ @override
+ _i3.Future<_i2.PerfettoTimeline> getPerfettoVMTimeline({
+ int? timeOriginMicros,
+ int? timeExtentMicros,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getPerfettoVMTimeline,
+ [],
+ {
+ #timeOriginMicros: timeOriginMicros,
+ #timeExtentMicros: timeExtentMicros,
+ },
+ ),
+ returnValue:
+ _i3.Future<_i2.PerfettoTimeline>.value(_FakePerfettoTimeline_18(
+ this,
+ Invocation.method(
+ #getPerfettoVMTimeline,
+ [],
+ {
+ #timeOriginMicros: timeOriginMicros,
+ #timeExtentMicros: timeExtentMicros,
+ },
+ ),
+ )),
+ ) as _i3.Future<_i2.PerfettoTimeline>);
+
+ @override
+ _i3.Future<_i2.PortList> getPorts(String? isolateId) => (super.noSuchMethod(
+ Invocation.method(
+ #getPorts,
+ [isolateId],
+ ),
+ returnValue: _i3.Future<_i2.PortList>.value(_FakePortList_19(
+ this,
+ Invocation.method(
+ #getPorts,
+ [isolateId],
+ ),
+ )),
+ ) as _i3.Future<_i2.PortList>);
+
+ @override
+ _i3.Future<_i2.RetainingPath> getRetainingPath(
+ String? isolateId,
+ String? targetId,
+ int? limit, {
+ String? idZoneId,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getRetainingPath,
+ [
+ isolateId,
+ targetId,
+ limit,
+ ],
+ {#idZoneId: idZoneId},
+ ),
+ returnValue: _i3.Future<_i2.RetainingPath>.value(_FakeRetainingPath_20(
+ this,
+ Invocation.method(
+ #getRetainingPath,
+ [
+ isolateId,
+ targetId,
+ limit,
+ ],
+ {#idZoneId: idZoneId},
+ ),
+ )),
+ ) as _i3.Future<_i2.RetainingPath>);
+
+ @override
+ _i3.Future<_i2.ProcessMemoryUsage> getProcessMemoryUsage() =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getProcessMemoryUsage,
+ [],
+ ),
+ returnValue:
+ _i3.Future<_i2.ProcessMemoryUsage>.value(_FakeProcessMemoryUsage_21(
+ this,
+ Invocation.method(
+ #getProcessMemoryUsage,
+ [],
+ ),
+ )),
+ ) as _i3.Future<_i2.ProcessMemoryUsage>);
+
+ @override
+ _i3.Future<_i2.Stack> getStack(
+ String? isolateId, {
+ int? limit,
+ String? idZoneId,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getStack,
+ [isolateId],
+ {
+ #limit: limit,
+ #idZoneId: idZoneId,
+ },
+ ),
+ returnValue: _i3.Future<_i2.Stack>.value(_FakeStack_22(
+ this,
+ Invocation.method(
+ #getStack,
+ [isolateId],
+ {
+ #limit: limit,
+ #idZoneId: idZoneId,
+ },
+ ),
+ )),
+ ) as _i3.Future<_i2.Stack>);
+
+ @override
+ _i3.Future<_i2.ProtocolList> getSupportedProtocols() => (super.noSuchMethod(
+ Invocation.method(
+ #getSupportedProtocols,
+ [],
+ ),
+ returnValue: _i3.Future<_i2.ProtocolList>.value(_FakeProtocolList_23(
+ this,
+ Invocation.method(
+ #getSupportedProtocols,
+ [],
+ ),
+ )),
+ ) as _i3.Future<_i2.ProtocolList>);
+
+ @override
+ _i3.Future<_i2.SourceReport> getSourceReport(
+ String? isolateId,
+ List<String>? reports, {
+ String? scriptId,
+ int? tokenPos,
+ int? endTokenPos,
+ bool? forceCompile,
+ bool? reportLines,
+ List<String>? libraryFilters,
+ List<String>? librariesAlreadyCompiled,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getSourceReport,
+ [
+ isolateId,
+ reports,
+ ],
+ {
+ #scriptId: scriptId,
+ #tokenPos: tokenPos,
+ #endTokenPos: endTokenPos,
+ #forceCompile: forceCompile,
+ #reportLines: reportLines,
+ #libraryFilters: libraryFilters,
+ #librariesAlreadyCompiled: librariesAlreadyCompiled,
+ },
+ ),
+ returnValue: _i3.Future<_i2.SourceReport>.value(_FakeSourceReport_24(
+ this,
+ Invocation.method(
+ #getSourceReport,
+ [
+ isolateId,
+ reports,
+ ],
+ {
+ #scriptId: scriptId,
+ #tokenPos: tokenPos,
+ #endTokenPos: endTokenPos,
+ #forceCompile: forceCompile,
+ #reportLines: reportLines,
+ #libraryFilters: libraryFilters,
+ #librariesAlreadyCompiled: librariesAlreadyCompiled,
+ },
+ ),
+ )),
+ ) as _i3.Future<_i2.SourceReport>);
+
+ @override
+ _i3.Future<_i2.Version> getVersion() => (super.noSuchMethod(
+ Invocation.method(
+ #getVersion,
+ [],
+ ),
+ returnValue: _i3.Future<_i2.Version>.value(_FakeVersion_25(
+ this,
+ Invocation.method(
+ #getVersion,
+ [],
+ ),
+ )),
+ ) as _i3.Future<_i2.Version>);
+
+ @override
+ _i3.Future<_i2.VM> getVM() => (super.noSuchMethod(
+ Invocation.method(
+ #getVM,
+ [],
+ ),
+ returnValue: _i3.Future<_i2.VM>.value(_FakeVM_26(
+ this,
+ Invocation.method(
+ #getVM,
+ [],
+ ),
+ )),
+ ) as _i3.Future<_i2.VM>);
+
+ @override
+ _i3.Future<_i2.Timeline> getVMTimeline({
+ int? timeOriginMicros,
+ int? timeExtentMicros,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #getVMTimeline,
+ [],
+ {
+ #timeOriginMicros: timeOriginMicros,
+ #timeExtentMicros: timeExtentMicros,
+ },
+ ),
+ returnValue: _i3.Future<_i2.Timeline>.value(_FakeTimeline_27(
+ this,
+ Invocation.method(
+ #getVMTimeline,
+ [],
+ {
+ #timeOriginMicros: timeOriginMicros,
+ #timeExtentMicros: timeExtentMicros,
+ },
+ ),
+ )),
+ ) as _i3.Future<_i2.Timeline>);
+
+ @override
+ _i3.Future<_i2.TimelineFlags> getVMTimelineFlags() => (super.noSuchMethod(
+ Invocation.method(
+ #getVMTimelineFlags,
+ [],
+ ),
+ returnValue: _i3.Future<_i2.TimelineFlags>.value(_FakeTimelineFlags_28(
+ this,
+ Invocation.method(
+ #getVMTimelineFlags,
+ [],
+ ),
+ )),
+ ) as _i3.Future<_i2.TimelineFlags>);
+
+ @override
+ _i3.Future<_i2.Timestamp> getVMTimelineMicros() => (super.noSuchMethod(
+ Invocation.method(
+ #getVMTimelineMicros,
+ [],
+ ),
+ returnValue: _i3.Future<_i2.Timestamp>.value(_FakeTimestamp_29(
+ this,
+ Invocation.method(
+ #getVMTimelineMicros,
+ [],
+ ),
+ )),
+ ) as _i3.Future<_i2.Timestamp>);
+
+ @override
+ _i3.Future<_i2.Success> pause(String? isolateId) => (super.noSuchMethod(
+ Invocation.method(
+ #pause,
+ [isolateId],
+ ),
+ returnValue: _i3.Future<_i2.Success>.value(_FakeSuccess_1(
+ this,
+ Invocation.method(
+ #pause,
+ [isolateId],
+ ),
+ )),
+ ) as _i3.Future<_i2.Success>);
+
+ @override
+ _i3.Future<_i2.Success> kill(String? isolateId) => (super.noSuchMethod(
+ Invocation.method(
+ #kill,
+ [isolateId],
+ ),
+ returnValue: _i3.Future<_i2.Success>.value(_FakeSuccess_1(
+ this,
+ Invocation.method(
+ #kill,
+ [isolateId],
+ ),
+ )),
+ ) as _i3.Future<_i2.Success>);
+
+ @override
+ _i3.Future<_i2.UriList> lookupResolvedPackageUris(
+ String? isolateId,
+ List<String>? uris, {
+ bool? local,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #lookupResolvedPackageUris,
+ [
+ isolateId,
+ uris,
+ ],
+ {#local: local},
+ ),
+ returnValue: _i3.Future<_i2.UriList>.value(_FakeUriList_30(
+ this,
+ Invocation.method(
+ #lookupResolvedPackageUris,
+ [
+ isolateId,
+ uris,
+ ],
+ {#local: local},
+ ),
+ )),
+ ) as _i3.Future<_i2.UriList>);
+
+ @override
+ _i3.Future<_i2.UriList> lookupPackageUris(
+ String? isolateId,
+ List<String>? uris,
+ ) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #lookupPackageUris,
+ [
+ isolateId,
+ uris,
+ ],
+ ),
+ returnValue: _i3.Future<_i2.UriList>.value(_FakeUriList_30(
+ this,
+ Invocation.method(
+ #lookupPackageUris,
+ [
+ isolateId,
+ uris,
+ ],
+ ),
+ )),
+ ) as _i3.Future<_i2.UriList>);
+
+ @override
+ _i3.Future<_i2.Success> registerService(
+ String? service,
+ String? alias,
+ ) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #registerService,
+ [
+ service,
+ alias,
+ ],
+ ),
+ returnValue: _i3.Future<_i2.Success>.value(_FakeSuccess_1(
+ this,
+ Invocation.method(
+ #registerService,
+ [
+ service,
+ alias,
+ ],
+ ),
+ )),
+ ) as _i3.Future<_i2.Success>);
+
+ @override
+ _i3.Future<_i2.ReloadReport> reloadSources(
+ String? isolateId, {
+ bool? force,
+ bool? pause,
+ String? rootLibUri,
+ String? packagesUri,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #reloadSources,
+ [isolateId],
+ {
+ #force: force,
+ #pause: pause,
+ #rootLibUri: rootLibUri,
+ #packagesUri: packagesUri,
+ },
+ ),
+ returnValue: _i3.Future<_i2.ReloadReport>.value(_FakeReloadReport_31(
+ this,
+ Invocation.method(
+ #reloadSources,
+ [isolateId],
+ {
+ #force: force,
+ #pause: pause,
+ #rootLibUri: rootLibUri,
+ #packagesUri: packagesUri,
+ },
+ ),
+ )),
+ ) as _i3.Future<_i2.ReloadReport>);
+
+ @override
+ _i3.Future<_i2.Success> removeBreakpoint(
+ String? isolateId,
+ String? breakpointId,
+ ) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #removeBreakpoint,
+ [
+ isolateId,
+ breakpointId,
+ ],
+ ),
+ returnValue: _i3.Future<_i2.Success>.value(_FakeSuccess_1(
+ this,
+ Invocation.method(
+ #removeBreakpoint,
+ [
+ isolateId,
+ breakpointId,
+ ],
+ ),
+ )),
+ ) as _i3.Future<_i2.Success>);
+
+ @override
+ _i3.Future<_i2.Success> requestHeapSnapshot(String? isolateId) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #requestHeapSnapshot,
+ [isolateId],
+ ),
+ returnValue: _i3.Future<_i2.Success>.value(_FakeSuccess_1(
+ this,
+ Invocation.method(
+ #requestHeapSnapshot,
+ [isolateId],
+ ),
+ )),
+ ) as _i3.Future<_i2.Success>);
+
+ @override
+ _i3.Future<_i2.Success> resume(
+ String? isolateId, {
+ String? step,
+ int? frameIndex,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #resume,
+ [isolateId],
+ {
+ #step: step,
+ #frameIndex: frameIndex,
+ },
+ ),
+ returnValue: _i3.Future<_i2.Success>.value(_FakeSuccess_1(
+ this,
+ Invocation.method(
+ #resume,
+ [isolateId],
+ {
+ #step: step,
+ #frameIndex: frameIndex,
+ },
+ ),
+ )),
+ ) as _i3.Future<_i2.Success>);
+
+ @override
+ _i3.Future<_i2.Breakpoint> setBreakpointState(
+ String? isolateId,
+ String? breakpointId,
+ bool? enable,
+ ) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #setBreakpointState,
+ [
+ isolateId,
+ breakpointId,
+ enable,
+ ],
+ ),
+ returnValue: _i3.Future<_i2.Breakpoint>.value(_FakeBreakpoint_0(
+ this,
+ Invocation.method(
+ #setBreakpointState,
+ [
+ isolateId,
+ breakpointId,
+ enable,
+ ],
+ ),
+ )),
+ ) as _i3.Future<_i2.Breakpoint>);
+
+ @override
+ _i3.Future<_i2.Success> setExceptionPauseMode(
+ String? isolateId,
+ String? mode,
+ ) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #setExceptionPauseMode,
+ [
+ isolateId,
+ mode,
+ ],
+ ),
+ returnValue: _i3.Future<_i2.Success>.value(_FakeSuccess_1(
+ this,
+ Invocation.method(
+ #setExceptionPauseMode,
+ [
+ isolateId,
+ mode,
+ ],
+ ),
+ )),
+ ) as _i3.Future<_i2.Success>);
+
+ @override
+ _i3.Future<_i2.Success> setIsolatePauseMode(
+ String? isolateId, {
+ String? exceptionPauseMode,
+ bool? shouldPauseOnExit,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #setIsolatePauseMode,
+ [isolateId],
+ {
+ #exceptionPauseMode: exceptionPauseMode,
+ #shouldPauseOnExit: shouldPauseOnExit,
+ },
+ ),
+ returnValue: _i3.Future<_i2.Success>.value(_FakeSuccess_1(
+ this,
+ Invocation.method(
+ #setIsolatePauseMode,
+ [isolateId],
+ {
+ #exceptionPauseMode: exceptionPauseMode,
+ #shouldPauseOnExit: shouldPauseOnExit,
+ },
+ ),
+ )),
+ ) as _i3.Future<_i2.Success>);
+
+ @override
+ _i3.Future<_i2.Response> setFlag(
+ String? name,
+ String? value,
+ ) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #setFlag,
+ [
+ name,
+ value,
+ ],
+ ),
+ returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_3(
+ this,
+ Invocation.method(
+ #setFlag,
+ [
+ name,
+ value,
+ ],
+ ),
+ )),
+ ) as _i3.Future<_i2.Response>);
+
+ @override
+ _i3.Future<_i2.Success> setLibraryDebuggable(
+ String? isolateId,
+ String? libraryId,
+ bool? isDebuggable,
+ ) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #setLibraryDebuggable,
+ [
+ isolateId,
+ libraryId,
+ isDebuggable,
+ ],
+ ),
+ returnValue: _i3.Future<_i2.Success>.value(_FakeSuccess_1(
+ this,
+ Invocation.method(
+ #setLibraryDebuggable,
+ [
+ isolateId,
+ libraryId,
+ isDebuggable,
+ ],
+ ),
+ )),
+ ) as _i3.Future<_i2.Success>);
+
+ @override
+ _i3.Future<_i2.Success> setName(
+ String? isolateId,
+ String? name,
+ ) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #setName,
+ [
+ isolateId,
+ name,
+ ],
+ ),
+ returnValue: _i3.Future<_i2.Success>.value(_FakeSuccess_1(
+ this,
+ Invocation.method(
+ #setName,
+ [
+ isolateId,
+ name,
+ ],
+ ),
+ )),
+ ) as _i3.Future<_i2.Success>);
+
+ @override
+ _i3.Future<_i2.Success> setTraceClassAllocation(
+ String? isolateId,
+ String? classId,
+ bool? enable,
+ ) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #setTraceClassAllocation,
+ [
+ isolateId,
+ classId,
+ enable,
+ ],
+ ),
+ returnValue: _i3.Future<_i2.Success>.value(_FakeSuccess_1(
+ this,
+ Invocation.method(
+ #setTraceClassAllocation,
+ [
+ isolateId,
+ classId,
+ enable,
+ ],
+ ),
+ )),
+ ) as _i3.Future<_i2.Success>);
+
+ @override
+ _i3.Future<_i2.Success> setVMName(String? name) => (super.noSuchMethod(
+ Invocation.method(
+ #setVMName,
+ [name],
+ ),
+ returnValue: _i3.Future<_i2.Success>.value(_FakeSuccess_1(
+ this,
+ Invocation.method(
+ #setVMName,
+ [name],
+ ),
+ )),
+ ) as _i3.Future<_i2.Success>);
+
+ @override
+ _i3.Future<_i2.Success> setVMTimelineFlags(List<String>? recordedStreams) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #setVMTimelineFlags,
+ [recordedStreams],
+ ),
+ returnValue: _i3.Future<_i2.Success>.value(_FakeSuccess_1(
+ this,
+ Invocation.method(
+ #setVMTimelineFlags,
+ [recordedStreams],
+ ),
+ )),
+ ) as _i3.Future<_i2.Success>);
+
+ @override
+ _i3.Future<_i2.Success> streamCancel(String? streamId) => (super.noSuchMethod(
+ Invocation.method(
+ #streamCancel,
+ [streamId],
+ ),
+ returnValue: _i3.Future<_i2.Success>.value(_FakeSuccess_1(
+ this,
+ Invocation.method(
+ #streamCancel,
+ [streamId],
+ ),
+ )),
+ ) as _i3.Future<_i2.Success>);
+
+ @override
+ _i3.Future<_i2.Success> streamCpuSamplesWithUserTag(List<String>? userTags) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #streamCpuSamplesWithUserTag,
+ [userTags],
+ ),
+ returnValue: _i3.Future<_i2.Success>.value(_FakeSuccess_1(
+ this,
+ Invocation.method(
+ #streamCpuSamplesWithUserTag,
+ [userTags],
+ ),
+ )),
+ ) as _i3.Future<_i2.Success>);
+
+ @override
+ _i3.Future<_i2.Success> streamListen(String? streamId) => (super.noSuchMethod(
+ Invocation.method(
+ #streamListen,
+ [streamId],
+ ),
+ returnValue: _i3.Future<_i2.Success>.value(_FakeSuccess_1(
+ this,
+ Invocation.method(
+ #streamListen,
+ [streamId],
+ ),
+ )),
+ ) as _i3.Future<_i2.Success>);
+
+ @override
+ _i3.Future<_i2.Response> callMethod(
+ String? method, {
+ String? isolateId,
+ Map<String, dynamic>? args,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #callMethod,
+ [method],
+ {
+ #isolateId: isolateId,
+ #args: args,
+ },
+ ),
+ returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_3(
+ this,
+ Invocation.method(
+ #callMethod,
+ [method],
+ {
+ #isolateId: isolateId,
+ #args: args,
+ },
+ ),
+ )),
+ ) as _i3.Future<_i2.Response>);
+
+ @override
+ _i3.Future<_i2.Response> callServiceExtension(
+ String? method, {
+ String? isolateId,
+ Map<String, dynamic>? args,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #callServiceExtension,
+ [method],
+ {
+ #isolateId: isolateId,
+ #args: args,
+ },
+ ),
+ returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_3(
+ this,
+ Invocation.method(
+ #callServiceExtension,
+ [method],
+ {
+ #isolateId: isolateId,
+ #args: args,
+ },
+ ),
+ )),
+ ) as _i3.Future<_i2.Response>);
+
+ @override
+ _i3.Future<void> dispose() => (super.noSuchMethod(
+ Invocation.method(
+ #dispose,
+ [],
+ ),
+ returnValue: _i3.Future<void>.value(),
+ returnValueForMissingStub: _i3.Future<void>.value(),
+ ) as _i3.Future<void>);
+
+ @override
+ _i3.Future<T> wrapFuture<T>(
+ String? name,
+ _i3.Future<T>? future,
+ ) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #wrapFuture,
+ [
+ name,
+ future,
+ ],
+ ),
+ returnValue: _i4.ifNotNull(
+ _i4.dummyValueOrNull<T>(
+ this,
+ Invocation.method(
+ #wrapFuture,
+ [
+ name,
+ future,
+ ],
+ ),
+ ),
+ (T v) => _i3.Future<T>.value(v),
+ ) ??
+ _FakeFuture_32<T>(
+ this,
+ Invocation.method(
+ #wrapFuture,
+ [
+ name,
+ future,
+ ],
+ ),
+ ),
+ ) as _i3.Future<T>);
+
+ @override
+ void registerServiceCallback(
+ String? service,
+ _i2.ServiceCallback? cb,
+ ) =>
+ super.noSuchMethod(
+ Invocation.method(
+ #registerServiceCallback,
+ [
+ service,
+ cb,
+ ],
+ ),
+ returnValueForMissingStub: null,
+ );
+}
diff --git a/pkgs/coverage/test/collect_coverage_test.dart b/pkgs/coverage/test/collect_coverage_test.dart
new file mode 100644
index 0000000..7262757
--- /dev/null
+++ b/pkgs/coverage/test/collect_coverage_test.dart
@@ -0,0 +1,325 @@
+// Copyright (c) 2015, 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.
+
+@Retry(3)
+library;
+
+import 'dart:async';
+import 'dart:convert' show json;
+import 'dart:io';
+
+import 'package:coverage/coverage.dart';
+import 'package:coverage/src/util.dart';
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+import 'package:test_process/test_process.dart';
+
+import 'test_util.dart';
+
+final _isolateLibPath = p.join('test', 'test_files', 'test_app_isolate.dart');
+final _collectAppPath = p.join('bin', 'collect_coverage.dart');
+
+final _sampleAppFileUri = p.toUri(p.absolute(testAppPath)).toString();
+final _isolateLibFileUri = p.toUri(p.absolute(_isolateLibPath)).toString();
+
+void main() {
+ test('collect_coverage', () async {
+ final resultString = await _getCoverageResult();
+
+ // analyze the output json
+ final coverage =
+ coverageDataFromJson(json.decode(resultString) as Map<String, dynamic>);
+
+ expect(coverage, isNotEmpty);
+
+ final sources = coverage.sources();
+
+ for (var sampleCoverageData in sources[_sampleAppFileUri]!) {
+ expect(sampleCoverageData['hits'], isNotNull);
+ }
+
+ for (var sampleCoverageData in sources[_isolateLibFileUri]!) {
+ expect(sampleCoverageData['hits'], isNotEmpty);
+ }
+ });
+
+ test('createHitmap returns a sorted hitmap', () async {
+ final coverage = [
+ {
+ 'source': 'foo',
+ 'script': '{type: @Script, fixedId: true, '
+ 'id: bar.dart, uri: bar.dart, _kind: library}',
+ 'hits': [
+ 45,
+ 1,
+ 46,
+ 1,
+ 49,
+ 0,
+ 50,
+ 0,
+ 15,
+ 1,
+ 16,
+ 2,
+ 17,
+ 2,
+ ]
+ }
+ ];
+ // ignore: deprecated_member_use_from_same_package
+ final hitMap = await createHitmap(
+ coverage.cast<Map<String, dynamic>>(),
+ );
+ final expectedHits = {15: 1, 16: 2, 17: 2, 45: 1, 46: 1, 49: 0, 50: 0};
+ expect(hitMap['foo'], expectedHits);
+ });
+
+ test('HitMap.parseJson returns a sorted hitmap', () async {
+ final coverage = [
+ {
+ 'source': 'foo',
+ 'script': '{type: @Script, fixedId: true, '
+ 'id: bar.dart, uri: bar.dart, _kind: library}',
+ 'hits': [
+ 45,
+ 1,
+ 46,
+ 1,
+ 49,
+ 0,
+ 50,
+ 0,
+ 15,
+ 1,
+ 16,
+ 2,
+ 17,
+ 2,
+ ]
+ }
+ ];
+ final hitMap = await HitMap.parseJson(
+ coverage.cast<Map<String, dynamic>>(),
+ );
+ final expectedHits = {15: 1, 16: 2, 17: 2, 45: 1, 46: 1, 49: 0, 50: 0};
+ expect(hitMap['foo']?.lineHits, expectedHits);
+ });
+
+ test('HitMap.parseJson', () async {
+ final resultString = await _collectCoverage(true, true);
+ final jsonResult = json.decode(resultString) as Map<String, dynamic>;
+ final coverage = jsonResult['coverage'] as List;
+ final hitMap = await HitMap.parseJson(
+ coverage.cast<Map<String, dynamic>>(),
+ );
+ expect(hitMap, contains(_sampleAppFileUri));
+
+ final isolateFile = hitMap[_isolateLibFileUri];
+ final expectedHits = {
+ 11: 1,
+ 12: 1,
+ 13: 1,
+ 15: 0,
+ 19: 1,
+ 23: 1,
+ 24: 2,
+ 28: 1,
+ 29: 1,
+ 30: 1,
+ 32: 0,
+ 38: 1,
+ 39: 1,
+ 41: 1,
+ 42: 4,
+ 43: 1,
+ 44: 3,
+ 45: 1,
+ 48: 1,
+ 49: 1,
+ 51: 1,
+ 54: 1,
+ 55: 1,
+ 56: 1,
+ 59: 1,
+ 60: 1,
+ 62: 1,
+ 63: 1,
+ 64: 1,
+ 66: 1,
+ 67: 1,
+ 68: 1,
+ 71: 3,
+ };
+ expect(isolateFile?.lineHits, expectedHits);
+ expect(isolateFile?.funcHits, {11: 1, 19: 1, 23: 1, 28: 1, 38: 1});
+ expect(isolateFile?.funcNames, {
+ 11: 'fooSync',
+ 19: 'BarClass.BarClass',
+ 23: 'BarClass.baz',
+ 28: 'fooAsync',
+ 38: 'isolateTask'
+ });
+ expect(
+ isolateFile?.branchHits,
+ {
+ 11: 1,
+ 12: 1,
+ 15: 0,
+ 19: 1,
+ 23: 1,
+ 28: 1,
+ 29: 1,
+ 32: 0,
+ 38: 1,
+ 42: 1,
+ 71: 1,
+ },
+ );
+ });
+
+ test('parseCoverage', () async {
+ final tempDir = await Directory.systemTemp.createTemp('coverage.test.');
+
+ try {
+ final outputFile = File(p.join(tempDir.path, 'coverage.json'));
+
+ final coverageResults = await _getCoverageResult();
+ await outputFile.writeAsString(coverageResults, flush: true);
+
+ // ignore: deprecated_member_use_from_same_package
+ final parsedResult = await parseCoverage([outputFile], 1);
+
+ expect(parsedResult, contains(_sampleAppFileUri));
+ expect(parsedResult, contains(_isolateLibFileUri));
+ } finally {
+ await tempDir.delete(recursive: true);
+ }
+ });
+
+ test('HitMap.parseFiles', () async {
+ final tempDir = await Directory.systemTemp.createTemp('coverage.test.');
+
+ try {
+ final outputFile = File(p.join(tempDir.path, 'coverage.json'));
+
+ final coverageResults = await _getCoverageResult();
+ await outputFile.writeAsString(coverageResults, flush: true);
+
+ final parsedResult = await HitMap.parseFiles([outputFile]);
+
+ expect(parsedResult, contains(_sampleAppFileUri));
+ expect(parsedResult, contains(_isolateLibFileUri));
+ } finally {
+ await tempDir.delete(recursive: true);
+ }
+ });
+
+ test('HitMap.parseFiles with packagesPath and checkIgnoredLines', () async {
+ final tempDir = await Directory.systemTemp.createTemp('coverage.test.');
+
+ try {
+ final outputFile = File(p.join(tempDir.path, 'coverage.json'));
+
+ final coverageResults = await _getCoverageResult();
+ await outputFile.writeAsString(coverageResults, flush: true);
+
+ final parsedResult = await HitMap.parseFiles([outputFile],
+ packagePath: '.', checkIgnoredLines: true);
+
+ // This file has ignore:coverage-file.
+ expect(parsedResult, isNot(contains(_sampleAppFileUri)));
+ expect(parsedResult, contains(_isolateLibFileUri));
+ } finally {
+ await tempDir.delete(recursive: true);
+ }
+ });
+
+ test('mergeHitmaps', () {
+ final resultMap = <String, Map<int, int>>{
+ 'foo.dart': {10: 2, 20: 0},
+ 'bar.dart': {10: 3, 20: 1, 30: 0},
+ };
+ final newMap = <String, Map<int, int>>{
+ 'bar.dart': {10: 2, 20: 0, 40: 3},
+ 'baz.dart': {10: 1, 20: 0, 30: 1},
+ };
+ // ignore: deprecated_member_use_from_same_package
+ mergeHitmaps(newMap, resultMap);
+ expect(resultMap, <String, Map<int, int>>{
+ 'foo.dart': {10: 2, 20: 0},
+ 'bar.dart': {10: 5, 20: 1, 30: 0, 40: 3},
+ 'baz.dart': {10: 1, 20: 0, 30: 1},
+ });
+ });
+
+ test('FileHitMaps.merge', () {
+ final resultMap = <String, HitMap>{
+ 'foo.dart':
+ HitMap({10: 2, 20: 0}, {15: 0, 25: 1}, {15: 'bobble', 25: 'cobble'}),
+ 'bar.dart': HitMap(
+ {10: 3, 20: 1, 30: 0}, {15: 5, 25: 0}, {15: 'gobble', 25: 'wobble'}),
+ };
+ final newMap = <String, HitMap>{
+ 'bar.dart': HitMap(
+ {10: 2, 20: 0, 40: 3}, {15: 1, 35: 4}, {15: 'gobble', 35: 'dobble'}),
+ 'baz.dart': HitMap(
+ {10: 1, 20: 0, 30: 1}, {15: 0, 25: 2}, {15: 'lobble', 25: 'zobble'}),
+ };
+ resultMap.merge(newMap);
+ expect(resultMap['foo.dart']?.lineHits, <int, int>{10: 2, 20: 0});
+ expect(resultMap['foo.dart']?.funcHits, <int, int>{15: 0, 25: 1});
+ expect(resultMap['foo.dart']?.funcNames,
+ <int, String>{15: 'bobble', 25: 'cobble'});
+ expect(resultMap['bar.dart']?.lineHits,
+ <int, int>{10: 5, 20: 1, 30: 0, 40: 3});
+ expect(resultMap['bar.dart']?.funcHits, <int, int>{15: 6, 25: 0, 35: 4});
+ expect(resultMap['bar.dart']?.funcNames,
+ <int, String>{15: 'gobble', 25: 'wobble', 35: 'dobble'});
+ expect(resultMap['baz.dart']?.lineHits, <int, int>{10: 1, 20: 0, 30: 1});
+ expect(resultMap['baz.dart']?.funcHits, <int, int>{15: 0, 25: 2});
+ expect(resultMap['baz.dart']?.funcNames,
+ <int, String>{15: 'lobble', 25: 'zobble'});
+ });
+}
+
+String? _coverageData;
+
+Future<String> _getCoverageResult() async =>
+ _coverageData ??= await _collectCoverage(false, false);
+
+Future<String> _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);
+
+ // Capture the VM service URI.
+ final serviceUri = await serviceUriFromProcess(sampleProcess.stdoutStream());
+
+ // Run the collection tool.
+ // TODO: need to get all of this functionality in the lib
+ final toolResult = await TestProcess.start(Platform.resolvedExecutable, [
+ _collectAppPath,
+ if (functionCoverage) '--function-coverage',
+ if (branchCoverage) '--branch-coverage',
+ '--uri',
+ '$serviceUri',
+ '--resume-isolates',
+ '--wait-paused'
+ ]);
+
+ await toolResult.shouldExit(0).timeout(
+ timeout,
+ onTimeout: () =>
+ throw StateError('We timed out waiting for the tool to finish.'),
+ );
+
+ await sampleProcess.shouldExit();
+
+ return toolResult.stdoutStream().join('\n');
+}
diff --git a/pkgs/coverage/test/format_coverage_test.dart b/pkgs/coverage/test/format_coverage_test.dart
new file mode 100644
index 0000000..85493f7
--- /dev/null
+++ b/pkgs/coverage/test/format_coverage_test.dart
@@ -0,0 +1,37 @@
+// 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 'dart:io';
+
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+
+import '../bin/format_coverage.dart';
+
+void main() {
+ late Directory testDir;
+ setUp(() {
+ testDir = Directory.systemTemp.createTempSync('coverage_test_temp');
+ });
+
+ tearDown(() async {
+ if (testDir.existsSync()) testDir.deleteSync(recursive: true);
+ });
+
+ test('considers all json files', () async {
+ final fileA = File(p.join(testDir.path, 'coverage_a.json'));
+ fileA.createSync();
+ final fileB = File(p.join(testDir.path, 'coverage_b.json'));
+ fileB.createSync();
+ final fileC = File(p.join(testDir.path, 'not_coverage.foo'));
+ fileC.createSync();
+
+ final files = filesToProcess(testDir.path);
+ expect(files.length, equals(2));
+ expect(
+ files.map((f) => f.path),
+ containsAll(
+ [endsWith('coverage_a.json'), endsWith('coverage_b.json')]));
+ });
+}
diff --git a/pkgs/coverage/test/function_coverage_test.dart b/pkgs/coverage/test/function_coverage_test.dart
new file mode 100644
index 0000000..965d0d0
--- /dev/null
+++ b/pkgs/coverage/test/function_coverage_test.dart
@@ -0,0 +1,107 @@
+// 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 'dart:async';
+import 'dart:convert' show json;
+import 'dart:io';
+
+import 'package:coverage/coverage.dart';
+import 'package:coverage/src/util.dart';
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+import 'package:test_process/test_process.dart';
+
+import 'test_util.dart';
+
+final _collectAppPath = p.join('bin', 'collect_coverage.dart');
+final _funcCovApp = p.join('test', 'test_files', 'function_coverage_app.dart');
+final _sampleAppFileUri = p.toUri(p.absolute(_funcCovApp)).toString();
+
+void main() {
+ test('Function coverage', () async {
+ final resultString = await _collectCoverage();
+ final jsonResult = json.decode(resultString) as Map<String, dynamic>;
+ final coverage = jsonResult['coverage'] as List;
+ final hitMap = await HitMap.parseJson(
+ coverage.cast<Map<String, dynamic>>(),
+ );
+
+ // function_coverage_app.dart.
+ expect(hitMap, contains(_sampleAppFileUri));
+ final isolateFile = hitMap[_sampleAppFileUri]!;
+ expect(isolateFile.funcHits, {
+ 7: 1,
+ 16: 1,
+ 21: 1,
+ 25: 1,
+ 29: 1,
+ 36: 1,
+ 42: 1,
+ 47: 1,
+ });
+ expect(isolateFile.funcNames, {
+ 7: 'normalFunction',
+ 16: 'SomeClass.SomeClass',
+ 21: 'SomeClass.normalMethod',
+ 25: 'SomeClass.staticMethod',
+ 29: 'SomeClass.abstractMethod',
+ 36: 'SomeExtension.extensionMethod',
+ 42: 'OtherClass.otherMethod',
+ 47: 'main',
+ });
+
+ // test_library.dart.
+ final testLibraryPath =
+ p.absolute(p.join('test', 'test_files', 'test_library.dart'));
+ final testLibraryUri = p.toUri(testLibraryPath).toString();
+ expect(hitMap, contains(testLibraryUri));
+ final libraryfile = hitMap[testLibraryUri]!;
+ expect(libraryfile.funcHits, {7: 1});
+ expect(libraryfile.funcNames, {7: 'libraryFunction'});
+
+ // test_library_part.dart.
+ final testLibraryPartPath =
+ p.absolute(p.join('test', 'test_files', 'test_library_part.dart'));
+ final testLibraryPartUri = p.toUri(testLibraryPartPath).toString();
+ expect(hitMap, contains(testLibraryPartUri));
+ final libraryPartFile = hitMap[testLibraryPartUri]!;
+ expect(libraryPartFile.funcHits, {7: 1});
+ expect(libraryPartFile.funcNames, {7: 'otherLibraryFunction'});
+ });
+}
+
+Future<String> _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 serviceUri = await serviceUriFromProcess(sampleProcess.stdoutStream());
+
+ // Run the collection tool.
+ final toolResult = await TestProcess.start(Platform.resolvedExecutable, [
+ _collectAppPath,
+ '--function-coverage',
+ '--uri',
+ '$serviceUri',
+ '--resume-isolates',
+ '--wait-paused'
+ ]);
+
+ await toolResult.shouldExit(0).timeout(
+ timeout,
+ onTimeout: () =>
+ throw StateError('We timed out waiting for the tool to finish.'),
+ );
+
+ await sampleProcess.shouldExit();
+
+ return toolResult.stdoutStream().join('\n');
+}
diff --git a/pkgs/coverage/test/isolate_paused_listener_test.dart b/pkgs/coverage/test/isolate_paused_listener_test.dart
new file mode 100644
index 0000000..b6016ce
--- /dev/null
+++ b/pkgs/coverage/test/isolate_paused_listener_test.dart
@@ -0,0 +1,815 @@
+// 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 'dart:async';
+
+import 'package:coverage/src/isolate_paused_listener.dart';
+import 'package:mockito/mockito.dart';
+import 'package:test/test.dart';
+import 'package:vm_service/vm_service.dart';
+
+import 'collect_coverage_mock_test.mocks.dart';
+
+Event event(
+ String id, {
+ String? kind,
+ String? groupId,
+ String? name,
+}) =>
+ Event(
+ kind: kind,
+ isolate: IsolateRef(
+ isolateGroupId: groupId,
+ id: id,
+ name: name,
+ ));
+
+Isolate isolate(
+ String id, {
+ String? groupId,
+ String? name,
+ String? pauseKind,
+}) =>
+ Isolate(
+ isolateGroupId: groupId,
+ id: id,
+ name: name,
+ pauseEvent: pauseKind == null ? null : Event(kind: pauseKind),
+ );
+
+(MockVmService, StreamController<Event>) createServiceAndEventStreams() {
+ final service = MockVmService();
+ when(service.streamListen(any)).thenAnswer((_) async => Success());
+
+ // The VM service events we care about come in on 2 different streams,
+ // onIsolateEvent and onDebugEvent. We want to write tests that send sequences
+ // of events like [I1, D1, I2, D2, I3, D3], but since I and D go to separate
+ // streams, the listener may see them arrive like [I1, I2, I3, D1, D2, D3] or
+ // [D1, D2, D3, I1, I2, I3] or any other interleaving. So instead we send all
+ // the events through a single stream that gets split up. This emulates how
+ // the events work in reality, since they all come from a single web socket.
+ final allEvents = StreamController<Event>();
+ final isolateEvents = StreamController<Event>();
+ final debugEvents = StreamController<Event>();
+ allEvents.stream.listen((Event e) {
+ if (e.kind == EventKind.kIsolateStart ||
+ e.kind == EventKind.kIsolateStart) {
+ isolateEvents.add(e);
+ } else {
+ debugEvents.add(e);
+ }
+ });
+ when(service.onIsolateEvent).thenAnswer((_) => isolateEvents.stream);
+ when(service.onDebugEvent).thenAnswer((_) => debugEvents.stream);
+
+ return (service, allEvents);
+}
+
+void main() {
+ group('IsolateEventBuffer', () {
+ test('buffers events', () async {
+ final received = <String>[];
+ final eventBuffer = IsolateEventBuffer((Event event) async {
+ await Future<void>.delayed(Duration.zero);
+ received.add(event.isolate!.id!);
+ });
+
+ await eventBuffer.add(event('a'));
+ await eventBuffer.add(event('b'));
+ await eventBuffer.add(event('c'));
+ expect(received, <String>[]);
+
+ await eventBuffer.flush();
+ expect(received, ['a', 'b', 'c']);
+
+ await eventBuffer.flush();
+ expect(received, ['a', 'b', 'c']);
+
+ await eventBuffer.add(event('d'));
+ await eventBuffer.add(event('e'));
+ await eventBuffer.add(event('f'));
+ expect(received, ['a', 'b', 'c', 'd', 'e', 'f']);
+
+ await eventBuffer.flush();
+ expect(received, ['a', 'b', 'c', 'd', 'e', 'f']);
+ });
+
+ test('buffers events during flush', () async {
+ final received = <String>[];
+ final pause = Completer<void>();
+ final eventBuffer = IsolateEventBuffer((Event event) async {
+ await pause.future;
+ received.add(event.isolate!.id!);
+ });
+
+ await eventBuffer.add(event('a'));
+ await eventBuffer.add(event('b'));
+ await eventBuffer.add(event('c'));
+ expect(received, <String>[]);
+
+ final flushing = eventBuffer.flush();
+ expect(received, <String>[]);
+
+ await eventBuffer.add(event('d'));
+ await eventBuffer.add(event('e'));
+ await eventBuffer.add(event('f'));
+ expect(received, <String>[]);
+
+ pause.complete();
+ await flushing;
+ expect(received, ['a', 'b', 'c', 'd', 'e', 'f']);
+ });
+ });
+
+ test('IsolateEventBuffer', () {
+ final group = IsolateGroupState();
+ expect(group.running, isEmpty);
+ expect(group.paused, isEmpty);
+ expect(group.noRunningIsolates, isTrue);
+ expect(group.noLiveIsolates, isTrue);
+
+ group.start('a');
+ expect(group.running, unorderedEquals(['a']));
+ expect(group.paused, isEmpty);
+ expect(group.noRunningIsolates, isFalse);
+ expect(group.noLiveIsolates, isFalse);
+
+ group.start('a');
+ expect(group.running, unorderedEquals(['a']));
+ expect(group.paused, isEmpty);
+ expect(group.noRunningIsolates, isFalse);
+ expect(group.noLiveIsolates, isFalse);
+
+ group.start('b');
+ expect(group.running, unorderedEquals(['a', 'b']));
+ expect(group.paused, isEmpty);
+ expect(group.noRunningIsolates, isFalse);
+ expect(group.noLiveIsolates, isFalse);
+
+ group.pause('a');
+ expect(group.running, unorderedEquals(['b']));
+ expect(group.paused, unorderedEquals(['a']));
+ expect(group.noRunningIsolates, isFalse);
+ expect(group.noLiveIsolates, isFalse);
+
+ group.pause('a');
+ expect(group.running, unorderedEquals(['b']));
+ expect(group.paused, unorderedEquals(['a']));
+ expect(group.noRunningIsolates, isFalse);
+ expect(group.noLiveIsolates, isFalse);
+
+ group.pause('c');
+ expect(group.running, unorderedEquals(['b']));
+ expect(group.paused, unorderedEquals(['a', 'c']));
+ expect(group.noRunningIsolates, isFalse);
+ expect(group.noLiveIsolates, isFalse);
+
+ group.start('c');
+ expect(group.running, unorderedEquals(['b', 'c']));
+ expect(group.paused, unorderedEquals(['a']));
+ expect(group.noRunningIsolates, isFalse);
+ expect(group.noLiveIsolates, isFalse);
+
+ group.pause('c');
+ expect(group.running, unorderedEquals(['b']));
+ expect(group.paused, unorderedEquals(['a', 'c']));
+ expect(group.noRunningIsolates, isFalse);
+ expect(group.noLiveIsolates, isFalse);
+
+ group.exit('a');
+ expect(group.running, unorderedEquals(['b']));
+ expect(group.paused, unorderedEquals(['c']));
+ expect(group.noRunningIsolates, isFalse);
+ expect(group.noLiveIsolates, isFalse);
+
+ group.pause('b');
+ expect(group.running, isEmpty);
+ expect(group.paused, unorderedEquals(['b', 'c']));
+ expect(group.noRunningIsolates, isTrue);
+ expect(group.noLiveIsolates, isFalse);
+
+ group.exit('b');
+ expect(group.running, isEmpty);
+ expect(group.paused, unorderedEquals(['c']));
+ expect(group.noRunningIsolates, isTrue);
+ expect(group.noLiveIsolates, isFalse);
+
+ group.exit('c');
+ expect(group.running, isEmpty);
+ expect(group.paused, isEmpty);
+ expect(group.noRunningIsolates, isTrue);
+ expect(group.noLiveIsolates, isTrue);
+ });
+
+ group('listenToIsolateLifecycleEvents', () {
+ late MockVmService service;
+ late StreamController<Event> allEvents;
+ late Completer<List<Isolate>> isolates;
+ late Future<void> backfilled;
+ late Future<void> testEnded;
+
+ late List<String> received;
+ Future<void>? delayTheOnPauseCallback;
+
+ void startEvent(String id) =>
+ allEvents.add(event(id, kind: EventKind.kIsolateStart));
+ void exitEvent(String id) =>
+ allEvents.add(event(id, kind: EventKind.kIsolateExit));
+ void pauseEvent(String id) =>
+ allEvents.add(event(id, kind: EventKind.kPauseExit));
+ void otherEvent(String id, String kind) =>
+ allEvents.add(event(id, kind: kind));
+
+ Future<void> backfill(List<Isolate> isos) async {
+ isolates.complete(isos);
+ await backfilled;
+ }
+
+ // We end the test by sending an exit event with a specific ID.
+ const endTestEventId = 'END';
+ Future<void> endTest() {
+ exitEvent(endTestEventId);
+ return testEnded;
+ }
+
+ setUp(() {
+ (service, allEvents) = createServiceAndEventStreams();
+
+ isolates = Completer<List<Isolate>>();
+ when(service.getVM())
+ .thenAnswer((_) async => VM(isolates: await isolates.future));
+ when(service.getIsolate(any)).thenAnswer((invocation) async {
+ final id = invocation.positionalArguments[0];
+ return (await isolates.future).firstWhere((iso) => iso.id == id);
+ });
+
+ received = <String>[];
+ delayTheOnPauseCallback = null;
+ final testEnder = Completer<void>();
+ testEnded = testEnder.future;
+ backfilled = listenToIsolateLifecycleEvents(
+ service,
+ (iso) {
+ if (iso.id == endTestEventId) return;
+ received.add('Start ${iso.id}');
+ },
+ (iso) async {
+ received.add('Pause ${iso.id}');
+ if (delayTheOnPauseCallback != null) {
+ await delayTheOnPauseCallback;
+ received.add('Pause done ${iso.id}');
+ }
+ },
+ (iso) {
+ if (iso.id == endTestEventId) {
+ testEnder.complete();
+ } else {
+ received.add('Exit ${iso.id}');
+ }
+ },
+ );
+ });
+
+ test('ordinary flows', () async {
+ // Events sent before backfill.
+ startEvent('A');
+ startEvent('C');
+ startEvent('B');
+ pauseEvent('C');
+ pauseEvent('A');
+ startEvent('D');
+ pauseEvent('D');
+ exitEvent('A');
+
+ // Run backfill.
+ await backfill([
+ isolate('B'),
+ isolate('C', pauseKind: EventKind.kPauseExit),
+ isolate('D'),
+ isolate('E'),
+ isolate('F', pauseKind: EventKind.kPauseExit),
+ ]);
+
+ // All the backfill events happen before any of the real events.
+ expect(received, [
+ // Backfill events.
+ 'Start B',
+ 'Start C',
+ 'Pause C',
+ 'Start D',
+ 'Start E',
+ 'Start F',
+ 'Pause F',
+
+ // Real events from before backfill.
+ 'Start A',
+ 'Pause A',
+ 'Pause D',
+ 'Exit A',
+ ]);
+
+ // Events sent after backfill.
+ received.clear();
+ startEvent('G');
+ exitEvent('C');
+ exitEvent('B');
+ exitEvent('G');
+ exitEvent('D');
+ exitEvent('E');
+ exitEvent('F');
+
+ await endTest();
+ expect(received, [
+ 'Start G',
+ 'Exit C',
+ 'Exit B',
+ 'Exit G',
+ 'Exit D',
+ 'Exit E',
+ 'Exit F',
+ ]);
+
+ verify(service.streamListen(EventStreams.kIsolate)).called(1);
+ verify(service.streamListen(EventStreams.kDebug)).called(1);
+ });
+
+ test('pause and exit events without start', () async {
+ await backfill([]);
+
+ pauseEvent('A');
+ exitEvent('B');
+
+ await endTest();
+ expect(received, [
+ 'Start A',
+ 'Pause A',
+ 'Start B',
+ 'Exit B',
+ ]);
+ });
+
+ test('pause event after exit is ignored', () async {
+ await backfill([]);
+
+ exitEvent('A');
+ pauseEvent('A');
+
+ await endTest();
+ expect(received, [
+ 'Start A',
+ 'Exit A',
+ ]);
+ });
+
+ test('event deduping', () async {
+ startEvent('A');
+ startEvent('A');
+ pauseEvent('A');
+ pauseEvent('A');
+ exitEvent('A');
+ exitEvent('A');
+
+ pauseEvent('B');
+ startEvent('B');
+
+ exitEvent('C');
+ startEvent('C');
+
+ await backfill([]);
+ await endTest();
+ expect(received, [
+ 'Start A',
+ 'Pause A',
+ 'Exit A',
+ 'Start B',
+ 'Pause B',
+ 'Start C',
+ 'Exit C',
+ ]);
+ });
+
+ test('ignore other events', () async {
+ await backfill([]);
+
+ startEvent('A');
+ pauseEvent('A');
+ otherEvent('A', EventKind.kResume);
+ exitEvent('A');
+
+ startEvent('B');
+ otherEvent('B', EventKind.kPauseBreakpoint);
+ exitEvent('B');
+
+ otherEvent('C', EventKind.kInspect);
+
+ await endTest();
+ expect(received, [
+ 'Start A',
+ 'Pause A',
+ 'Exit A',
+ 'Start B',
+ 'Exit B',
+ ]);
+ });
+
+ test('exit event during pause callback', () async {
+ final delayingTheOnPauseCallback = Completer<void>();
+ delayTheOnPauseCallback = delayingTheOnPauseCallback.future;
+ await backfill([]);
+
+ startEvent('A');
+ pauseEvent('A');
+ exitEvent('A');
+
+ while (received.length < 2) {
+ await Future<void>.delayed(Duration.zero);
+ }
+
+ expect(received, [
+ 'Start A',
+ 'Pause A',
+ ]);
+
+ delayingTheOnPauseCallback.complete();
+ await endTest();
+ expect(received, [
+ 'Start A',
+ 'Pause A',
+ 'Pause done A',
+ 'Exit A',
+ ]);
+ });
+
+ test('exit event during pause callback, event deduping', () async {
+ final delayingTheOnPauseCallback = Completer<void>();
+ delayTheOnPauseCallback = delayingTheOnPauseCallback.future;
+ await backfill([]);
+
+ startEvent('A');
+ pauseEvent('A');
+ exitEvent('A');
+ pauseEvent('A');
+ pauseEvent('A');
+ exitEvent('A');
+ exitEvent('A');
+
+ while (received.length < 2) {
+ await Future<void>.delayed(Duration.zero);
+ }
+
+ expect(received, [
+ 'Start A',
+ 'Pause A',
+ ]);
+
+ delayingTheOnPauseCallback.complete();
+ await endTest();
+ expect(received, [
+ 'Start A',
+ 'Pause A',
+ 'Pause done A',
+ 'Exit A',
+ ]);
+ });
+ });
+
+ group('IsolatePausedListener', () {
+ late MockVmService service;
+ late StreamController<Event> allEvents;
+ late Future<void> allIsolatesExited;
+
+ late List<String> received;
+ late bool stopped;
+
+ void startEvent(String id, String groupId, [String? name]) =>
+ allEvents.add(event(
+ id,
+ kind: EventKind.kIsolateStart,
+ groupId: groupId,
+ name: name ?? id,
+ ));
+ void exitEvent(String id, String groupId, [String? name]) =>
+ allEvents.add(event(
+ id,
+ kind: EventKind.kIsolateExit,
+ groupId: groupId,
+ name: name ?? id,
+ ));
+ void pauseEvent(String id, String groupId, [String? name]) =>
+ allEvents.add(event(
+ id,
+ kind: EventKind.kPauseExit,
+ groupId: groupId,
+ name: name ?? id,
+ ));
+
+ Future<void> endTest() async {
+ await allIsolatesExited;
+ stopped = true;
+ }
+
+ 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 = <String>[];
+ 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}. Last in group ${iso.isolateGroupId}? '
+ '${isLastIsolateInGroup ? 'Yes' : 'No'}');
+ },
+ (message) => received.add(message),
+ ).waitUntilAllExited();
+ });
+
+ test('ordinary flows', () async {
+ startEvent('A', '1');
+ startEvent('B', '1');
+ pauseEvent('A', '1');
+ startEvent('C', '1');
+ pauseEvent('B', '1');
+ exitEvent('A', '1');
+ startEvent('D', '2');
+ startEvent('E', '2');
+ startEvent('F', '2');
+ pauseEvent('C', '1');
+ pauseEvent('F', '2');
+ pauseEvent('E', '2');
+ exitEvent('C', '1');
+ exitEvent('E', '2');
+ startEvent('G', '3');
+ exitEvent('F', '2');
+ startEvent('H', '3');
+ startEvent('I', '3');
+ pauseEvent('I', '3');
+ exitEvent('I', '3');
+ pauseEvent('H', '3');
+ exitEvent('H', '3');
+ pauseEvent('D', '2');
+ pauseEvent('G', '3');
+ exitEvent('D', '2');
+ exitEvent('G', '3');
+ exitEvent('B', '1');
+
+ await endTest();
+
+ // Events sent after waitUntilAllExited is finished do nothing.
+ startEvent('Z', '9');
+ pauseEvent('Z', '9');
+ exitEvent('Z', '9');
+
+ expect(received, [
+ 'Pause A. Last in group 1? No',
+ 'Resume A',
+ 'Pause B. Last in group 1? No',
+ 'Resume B',
+ 'Pause C. Last in group 1? Yes',
+ 'Resume C',
+ 'Pause F. Last in group 2? No',
+ 'Resume F',
+ 'Pause E. Last in group 2? No',
+ 'Resume E',
+ 'Pause I. Last in group 3? No',
+ 'Resume I',
+ 'Pause H. Last in group 3? No',
+ 'Resume H',
+ 'Pause D. Last in group 2? Yes',
+ 'Resume D',
+ 'Pause G. Last in group 3? Yes',
+ 'Resume G',
+ ]);
+ });
+
+ test('exit without pausing', () async {
+ // If an isolate exits without pausing, this may mess up coverage
+ // collection (if it happens to be the last isolate in the group, that
+ // group won't be collected). The best we can do is log an error, and make
+ // sure not to wait forever for pause events that aren't coming.
+ startEvent('A', '1');
+ startEvent('B', '1');
+ exitEvent('A', '1');
+ pauseEvent('B', '1');
+ startEvent('C', '2');
+ startEvent('D', '2');
+ pauseEvent('D', '2');
+ exitEvent('D', '2');
+ exitEvent('C', '2');
+ exitEvent('B', '1');
+
+ await endTest();
+
+ // B was paused correctly and was the last to exit isolate 1, so isolate 1
+ // was collected ok.
+ expect(received, [
+ 'Pause B. Last in group 1? Yes',
+ 'Resume B',
+ 'Pause D. Last in group 2? No',
+ 'Resume D',
+ 'ERROR: An isolate exited without pausing, causing coverage data to '
+ 'be lost for group 2.',
+ ]);
+ });
+
+ test('main isolate resumed last', () async {
+ startEvent('A', '1', 'main');
+ startEvent('B', '1', 'main'); // Second isolate named main, ignored.
+ pauseEvent('B', '1', 'main');
+ startEvent('C', '2', 'main'); // Third isolate named main, ignored.
+ pauseEvent('A', '1', 'main');
+ startEvent('D', '2');
+ pauseEvent('C', '2');
+ exitEvent('C', '2');
+ pauseEvent('D', '2');
+ exitEvent('D', '2');
+ exitEvent('B', '1');
+
+ await endTest();
+
+ expect(received, [
+ 'Pause B. Last in group 1? No',
+ 'Resume B',
+ 'Pause C. Last in group 2? No',
+ 'Resume C',
+ 'Pause D. Last in group 2? Yes',
+ 'Resume D',
+ 'Pause A. Last in group 1? Yes',
+ 'Resume A',
+ ]);
+ });
+
+ test('main isolate exits without pausing', () async {
+ startEvent('A', '1', 'main');
+ startEvent('B', '1');
+ pauseEvent('B', '1');
+ exitEvent('A', '1', 'main');
+ exitEvent('B', '1');
+
+ await endTest();
+
+ expect(received, [
+ 'Pause B. Last in group 1? No',
+ 'Resume B',
+ 'ERROR: An isolate exited without pausing, causing coverage data to '
+ 'be lost for group 1.',
+ ]);
+ });
+
+ test('main isolate is the only isolate', () async {
+ startEvent('A', '1', 'main');
+ pauseEvent('A', '1', 'main');
+
+ await endTest();
+
+ expect(received, [
+ 'Pause A. Last in group 1? Yes',
+ 'Resume A',
+ ]);
+ });
+
+ test('all other isolates exit before main isolate pauses', () async {
+ startEvent('A', '1', 'main');
+ startEvent('B', '1');
+ pauseEvent('B', '1');
+ exitEvent('B', '1');
+
+ await Future<void>.delayed(Duration.zero);
+
+ pauseEvent('A', '1', 'main');
+ exitEvent('A', '1', 'main');
+
+ await endTest();
+
+ expect(received, [
+ 'Pause B. Last in group 1? No',
+ 'Resume B',
+ 'Pause A. Last in group 1? Yes',
+ 'Resume A',
+ ]);
+ });
+
+ test(
+ 'all other isolates exit before main isolate pauses, then main '
+ 'starts another isolate, then pauses before they exit', () async {
+ startEvent('A', '1', 'main');
+ startEvent('B', '1');
+ pauseEvent('B', '1');
+ exitEvent('B', '1');
+
+ await Future<void>.delayed(Duration.zero);
+
+ startEvent('C', '1');
+ pauseEvent('C', '1');
+ pauseEvent('A', '1', 'main');
+ exitEvent('C', '1');
+
+ await Future<void>.delayed(Duration.zero);
+
+ exitEvent('A', '1', 'main');
+
+ await endTest();
+
+ expect(received, [
+ 'Pause B. Last in group 1? No',
+ 'Resume B',
+ 'Pause C. Last in group 1? No',
+ 'Resume C',
+ 'Pause A. Last in group 1? Yes',
+ 'Resume A',
+ ]);
+ });
+
+ test(
+ 'all other isolates exit before main isolate pauses, then main '
+ 'starts another isolate, then pauses before they pause', () async {
+ startEvent('A', '1', 'main');
+ startEvent('B', '1');
+ pauseEvent('B', '1');
+ exitEvent('B', '1');
+
+ await Future<void>.delayed(Duration.zero);
+
+ startEvent('C', '1');
+ pauseEvent('A', '1', 'main');
+ pauseEvent('C', '1');
+ exitEvent('C', '1');
+
+ await Future<void>.delayed(Duration.zero);
+
+ exitEvent('A', '1', 'main');
+
+ await endTest();
+
+ expect(received, [
+ 'Pause B. Last in group 1? No',
+ 'Resume B',
+ 'Pause C. Last in group 1? Yes',
+ 'Resume C',
+ 'Pause A. Last in group 1? No',
+ 'Resume A',
+ ]);
+ });
+
+ test('group reopened', () async {
+ // If an isolate is reported in a group after the group as believed to be
+ // closed, reopen the group. This double counts some coverage, but at
+ // least won't miss any.
+
+ startEvent('Z', '9'); // Separate isolate to keep the system alive until
+ pauseEvent('Z', '9'); // the test is complete.
+
+ startEvent('A', '1');
+ startEvent('B', '1');
+ pauseEvent('A', '1');
+ pauseEvent('B', '1');
+ exitEvent('B', '1');
+ exitEvent('A', '1');
+
+ startEvent('D', '2');
+ startEvent('E', '2');
+ pauseEvent('E', '2');
+ pauseEvent('D', '2');
+ exitEvent('E', '2');
+ exitEvent('D', '2');
+
+ startEvent('C', '1');
+ pauseEvent('F', '2');
+ pauseEvent('C', '1');
+ exitEvent('C', '1');
+ exitEvent('F', '2');
+
+ exitEvent('Z', '9');
+
+ await endTest();
+
+ expect(received, [
+ 'Pause Z. Last in group 9? Yes',
+ 'Resume Z',
+ 'Pause A. Last in group 1? No',
+ 'Resume A',
+ 'Pause B. Last in group 1? Yes',
+ 'Resume B',
+ 'Pause E. Last in group 2? No',
+ 'Resume E',
+ 'Pause D. Last in group 2? Yes',
+ 'Resume D',
+ 'Pause F. Last in group 2? Yes',
+ 'Resume F',
+ 'Pause C. Last in group 1? Yes',
+ 'Resume C',
+ ]);
+ });
+ });
+}
diff --git a/pkgs/coverage/test/lcov_test.dart b/pkgs/coverage/test/lcov_test.dart
new file mode 100644
index 0000000..ca62117
--- /dev/null
+++ b/pkgs/coverage/test/lcov_test.dart
@@ -0,0 +1,364 @@
+// Copyright (c) 2015, 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';
+
+import 'package:coverage/coverage.dart';
+import 'package:coverage/src/util.dart';
+import 'package:glob/glob.dart';
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+import 'package:test_process/test_process.dart';
+
+final _sampleAppPath = p.join('test', 'test_files', 'test_app.dart');
+final _sampleGeneratedPath = p.join('test', 'test_files', 'test_app.g.dart');
+final _isolateLibPath = p.join('test', 'test_files', 'test_app_isolate.dart');
+
+final _sampleAppFileUri = p.toUri(p.absolute(_sampleAppPath)).toString();
+final _sampleGeneratedFileUri =
+ p.toUri(p.absolute(_sampleGeneratedPath)).toString();
+final _isolateLibFileUri = p.toUri(p.absolute(_isolateLibPath)).toString();
+
+void main() {
+ test('validate hitMap', () async {
+ final hitmap = await _getHitMap();
+
+ expect(hitmap, contains(_sampleAppFileUri));
+ expect(hitmap, contains(_sampleGeneratedFileUri));
+ expect(hitmap, contains(_isolateLibFileUri));
+ expect(hitmap, contains('package:coverage/src/util.dart'));
+
+ final sampleAppHitMap = hitmap[_sampleAppFileUri];
+ final sampleAppHitLines = sampleAppHitMap?.lineHits;
+ final sampleAppHitFuncs = sampleAppHitMap?.funcHits;
+ final sampleAppFuncNames = sampleAppHitMap?.funcNames;
+ final sampleAppBranchHits = sampleAppHitMap?.branchHits;
+
+ expect(sampleAppHitLines, containsPair(53, greaterThanOrEqualTo(1)),
+ reason: 'be careful if you modify the test file');
+ expect(sampleAppHitLines, containsPair(57, 0),
+ reason: 'be careful if you modify the test file');
+ expect(sampleAppHitLines, isNot(contains(39)),
+ reason: 'be careful if you modify the test file');
+ expect(sampleAppHitFuncs, containsPair(52, 1),
+ reason: 'be careful if you modify the test file');
+ expect(sampleAppHitFuncs, containsPair(56, 0),
+ reason: 'be careful if you modify the test file');
+ expect(sampleAppFuncNames, containsPair(52, 'usedMethod'),
+ reason: 'be careful if you modify the test file');
+ expect(sampleAppBranchHits, containsPair(48, 1),
+ reason: 'be careful if you modify the test file');
+ });
+
+ test('validate hitMap, old VM without branch coverage', () async {
+ final hitmap = await _getHitMap();
+
+ expect(hitmap, contains(_sampleAppFileUri));
+ expect(hitmap, contains(_sampleGeneratedFileUri));
+ expect(hitmap, contains(_isolateLibFileUri));
+ expect(hitmap, contains('package:coverage/src/util.dart'));
+
+ final sampleAppHitMap = hitmap[_sampleAppFileUri];
+ final sampleAppHitLines = sampleAppHitMap?.lineHits;
+ final sampleAppHitFuncs = sampleAppHitMap?.funcHits;
+ final sampleAppFuncNames = sampleAppHitMap?.funcNames;
+
+ expect(sampleAppHitLines, containsPair(53, greaterThanOrEqualTo(1)),
+ reason: 'be careful if you modify the test file');
+ expect(sampleAppHitLines, containsPair(57, 0),
+ reason: 'be careful if you modify the test file');
+ expect(sampleAppHitLines, isNot(contains(39)),
+ reason: 'be careful if you modify the test file');
+ expect(sampleAppHitFuncs, containsPair(52, 1),
+ reason: 'be careful if you modify the test file');
+ expect(sampleAppHitFuncs, containsPair(56, 0),
+ reason: 'be careful if you modify the test file');
+ expect(sampleAppFuncNames, containsPair(52, 'usedMethod'),
+ reason: 'be careful if you modify the test file');
+ });
+
+ group('LcovFormatter', () {
+ test('format()', () async {
+ final hitmap = await _getHitMap();
+
+ final resolver = await Resolver.create(packagePath: '.');
+ // ignore: deprecated_member_use_from_same_package
+ final formatter = LcovFormatter(resolver);
+
+ final res = await formatter
+ .format(hitmap.map((key, value) => MapEntry(key, value.lineHits)));
+
+ expect(res, contains(p.absolute(_sampleAppPath)));
+ expect(res, contains(p.absolute(_sampleGeneratedPath)));
+ expect(res, contains(p.absolute(_isolateLibPath)));
+ expect(res, contains(p.absolute(p.join('lib', 'src', 'util.dart'))));
+ });
+
+ test('formatLcov()', () async {
+ final hitmap = await _getHitMap();
+
+ final resolver = await Resolver.create(packagePath: '.');
+ final res = hitmap.formatLcov(resolver);
+
+ expect(res, contains(p.absolute(_sampleAppPath)));
+ expect(res, contains(p.absolute(_sampleGeneratedPath)));
+ expect(res, contains(p.absolute(_isolateLibPath)));
+ expect(res, contains(p.absolute(p.join('lib', 'src', 'util.dart'))));
+ });
+
+ test('formatLcov() includes files in reportOn list', () async {
+ final hitmap = await _getHitMap();
+
+ final resolver = await Resolver.create(packagePath: '.');
+ final res = hitmap.formatLcov(resolver, reportOn: ['lib/', 'test/']);
+
+ expect(res, contains(p.absolute(_sampleAppPath)));
+ expect(res, contains(p.absolute(_sampleGeneratedPath)));
+ expect(res, contains(p.absolute(_isolateLibPath)));
+ expect(res, contains(p.absolute(p.join('lib', 'src', 'util.dart'))));
+ });
+
+ test('formatLcov() excludes files not in reportOn list', () async {
+ final hitmap = await _getHitMap();
+
+ final resolver = await Resolver.create(packagePath: '.');
+ final res = hitmap.formatLcov(resolver, reportOn: ['lib/']);
+
+ expect(res, isNot(contains(p.absolute(_sampleAppPath))));
+ expect(res, isNot(contains(p.absolute(_sampleGeneratedPath))));
+ expect(res, isNot(contains(p.absolute(_isolateLibPath))));
+ expect(res, contains(p.absolute(p.join('lib', 'src', 'util.dart'))));
+ });
+
+ test('formatLcov() excludes files matching glob patterns', () async {
+ final hitmap = await _getHitMap();
+
+ final resolver = await Resolver.create(packagePath: '.');
+ final res = hitmap.formatLcov(
+ resolver,
+ ignoreGlobs: {Glob('**/*.g.dart'), Glob('**/util.dart')},
+ );
+
+ expect(res, contains(p.absolute(_sampleAppPath)));
+ expect(res, isNot(contains(p.absolute(_sampleGeneratedPath))));
+ expect(res, contains(p.absolute(_isolateLibPath)));
+ expect(
+ res,
+ isNot(contains(p.absolute(p.join('lib', 'src', 'util.dart')))),
+ );
+ });
+
+ test(
+ 'formatLcov() excludes files matching glob patterns regardless of their'
+ 'presence on reportOn list', () async {
+ final hitmap = await _getHitMap();
+
+ final resolver = await Resolver.create(packagePath: '.');
+ final res = hitmap.formatLcov(
+ resolver,
+ reportOn: ['test/'],
+ ignoreGlobs: {Glob('**/*.g.dart')},
+ );
+
+ expect(res, contains(p.absolute(_sampleAppPath)));
+ expect(res, isNot(contains(p.absolute(_sampleGeneratedPath))));
+ expect(res, contains(p.absolute(_isolateLibPath)));
+ });
+
+ test('formatLcov() uses paths relative to basePath', () async {
+ final hitmap = await _getHitMap();
+
+ final resolver = await Resolver.create(packagePath: '.');
+ final res = hitmap.formatLcov(resolver, basePath: p.absolute('lib'));
+
+ expect(
+ res, isNot(contains(p.absolute(p.join('lib', 'src', 'util.dart')))));
+ expect(res, contains(p.join('src', 'util.dart')));
+ });
+ });
+
+ group('PrettyPrintFormatter', () {
+ test('format()', () async {
+ final hitmap = await _getHitMap();
+
+ final resolver = await Resolver.create(packagePath: '.');
+ // ignore: deprecated_member_use_from_same_package
+ final formatter = PrettyPrintFormatter(resolver, Loader());
+
+ final res = await formatter
+ .format(hitmap.map((key, value) => MapEntry(key, value.lineHits)));
+
+ expect(res, contains(p.absolute(_sampleAppPath)));
+ expect(res, contains(p.absolute(_sampleGeneratedPath)));
+ expect(res, contains(p.absolute(_isolateLibPath)));
+ expect(res, contains(p.absolute(p.join('lib', 'src', 'util.dart'))));
+
+ // be very careful if you change the test file
+ expect(res, contains(' 0| return a - b;'));
+
+ expect(res, contains('| return withTimeout(() async {'),
+ reason: 'be careful if you change lib/src/util.dart');
+
+ final hitLineRegexp = RegExp(r'\s+(\d+)\| return a \+ b;');
+ final match = hitLineRegexp.allMatches(res).single;
+
+ final hitCount = int.parse(match[1]!);
+ expect(hitCount, greaterThanOrEqualTo(1));
+ });
+
+ test('prettyPrint()', () async {
+ final hitmap = await _getHitMap();
+
+ final resolver = await Resolver.create(packagePath: '.');
+ final res = await hitmap.prettyPrint(resolver, Loader());
+
+ expect(res, contains(p.absolute(_sampleAppPath)));
+ expect(res, contains(p.absolute(_sampleGeneratedPath)));
+ expect(res, contains(p.absolute(_isolateLibPath)));
+ expect(res, contains(p.absolute(p.join('lib', 'src', 'util.dart'))));
+
+ // be very careful if you change the test file
+ expect(res, contains(' 0| return a - b;'));
+
+ expect(res, contains('| return withTimeout(() async {'),
+ reason: 'be careful if you change lib/src/util.dart');
+
+ final hitLineRegexp = RegExp(r'\s+(\d+)\| return a \+ b;');
+ final match = hitLineRegexp.allMatches(res).single;
+
+ final hitCount = int.parse(match[1]!);
+ expect(hitCount, greaterThanOrEqualTo(1));
+ });
+
+ test('prettyPrint() includes files in reportOn list', () async {
+ final hitmap = await _getHitMap();
+
+ final resolver = await Resolver.create(packagePath: '.');
+ final res = await hitmap
+ .prettyPrint(resolver, Loader(), reportOn: ['lib/', 'test/']);
+
+ expect(res, contains(p.absolute(_sampleAppPath)));
+ expect(res, contains(p.absolute(_sampleGeneratedPath)));
+ expect(res, contains(p.absolute(_isolateLibPath)));
+ expect(res, contains(p.absolute(p.join('lib', 'src', 'util.dart'))));
+ });
+
+ test('prettyPrint() excludes files not in reportOn list', () async {
+ final hitmap = await _getHitMap();
+
+ final resolver = await Resolver.create(packagePath: '.');
+ final res =
+ await hitmap.prettyPrint(resolver, Loader(), reportOn: ['lib/']);
+
+ expect(res, isNot(contains(p.absolute(_sampleAppPath))));
+ expect(res, isNot(contains(p.absolute(_sampleGeneratedPath))));
+ expect(res, isNot(contains(p.absolute(_isolateLibPath))));
+ expect(res, contains(p.absolute(p.join('lib', 'src', 'util.dart'))));
+ });
+
+ test('prettyPrint() excludes files matching glob patterns', () async {
+ final hitmap = await _getHitMap();
+
+ final resolver = await Resolver.create(packagePath: '.');
+ final res = await hitmap.prettyPrint(
+ resolver,
+ Loader(),
+ ignoreGlobs: {Glob('**/*.g.dart'), Glob('**/util.dart')},
+ );
+
+ expect(res, contains(p.absolute(_sampleAppPath)));
+ expect(res, isNot(contains(p.absolute(_sampleGeneratedPath))));
+ expect(res, contains(p.absolute(_isolateLibPath)));
+ expect(
+ res,
+ isNot(contains(p.absolute(p.join('lib', 'src', 'util.dart')))),
+ );
+ });
+
+ test(
+ 'prettyPrint() excludes files matching glob patterns regardless of'
+ 'their presence on reportOn list', () async {
+ final hitmap = await _getHitMap();
+
+ final resolver = await Resolver.create(packagePath: '.');
+ final res = await hitmap.prettyPrint(
+ resolver,
+ Loader(),
+ reportOn: ['test/'],
+ ignoreGlobs: {Glob('**/*.g.dart')},
+ );
+
+ expect(res, contains(p.absolute(_sampleAppPath)));
+ expect(res, isNot(contains(p.absolute(_sampleGeneratedPath))));
+ expect(res, contains(p.absolute(_isolateLibPath)));
+ });
+
+ test('prettyPrint() functions', () async {
+ final hitmap = await _getHitMap();
+
+ final resolver = await Resolver.create(packagePath: '.');
+ final res =
+ await hitmap.prettyPrint(resolver, Loader(), reportFuncs: true);
+
+ expect(res, contains(p.absolute(_sampleAppPath)));
+ expect(res, contains(p.absolute(_sampleGeneratedPath)));
+ expect(res, contains(p.absolute(_isolateLibPath)));
+ expect(res, contains(p.absolute(p.join('lib', 'src', 'util.dart'))));
+
+ // be very careful if you change the test file
+ expect(res, contains(' 1|Future<void> main() async {'));
+ expect(res, contains(' 1|int usedMethod(int a, int b) {'));
+ expect(res, contains(' 0|int unusedMethod(int a, int b) {'));
+ expect(res, contains(' | return a + b;'));
+ });
+
+ test('prettyPrint() branches', () async {
+ final hitmap = await _getHitMap();
+
+ final resolver = await Resolver.create(packagePath: '.');
+ final res =
+ await hitmap.prettyPrint(resolver, Loader(), reportBranches: true);
+
+ expect(res, contains(p.absolute(_sampleAppPath)));
+ expect(res, contains(p.absolute(_sampleGeneratedPath)));
+ expect(res, contains(p.absolute(_isolateLibPath)));
+ expect(res, contains(p.absolute(p.join('lib', 'src', 'util.dart'))));
+
+ // be very careful if you change the test file
+ expect(res, contains(' 1| if (x == answer) {'));
+ expect(res, contains(' 0| while (i < lines.length) {'));
+ expect(res, contains(' | bar.baz();'));
+ });
+ });
+}
+
+Future<Map<String, HitMap>> _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',
+ '--branch-coverage',
+ _sampleAppPath
+ ];
+ final sampleProcess =
+ await TestProcess.start(Platform.resolvedExecutable, sampleAppArgs);
+
+ final serviceUri = await serviceUriFromProcess(sampleProcess.stdoutStream());
+
+ // collect hit map.
+ final coverageJson = (await collect(serviceUri, true, true, false, <String>{},
+ functionCoverage: true,
+ branchCoverage: true))['coverage'] as List<Map<String, dynamic>>;
+ final hitMap = HitMap.parseJson(coverageJson);
+
+ await sampleProcess.shouldExit(0);
+
+ return hitMap;
+}
diff --git a/pkgs/coverage/test/resolver_test.dart b/pkgs/coverage/test/resolver_test.dart
new file mode 100644
index 0000000..1161aee
--- /dev/null
+++ b/pkgs/coverage/test/resolver_test.dart
@@ -0,0 +1,211 @@
+// 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.
+
+import 'package:coverage/src/resolver.dart';
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+void main() {
+ group('Default Resolver', () {
+ setUp(() async {
+ final sandboxUriPath = p.toUri(d.sandbox).toString();
+ await d.dir('bar', [
+ d.dir('lib', [
+ d.file('bar.dart', 'final fizz = "bar";'),
+ ])
+ ]).create();
+
+ await d.dir('foo', [
+ d.dir('.dart_tool', [
+ d.file('bad_package_config.json', 'thisIsntAPackageConfigFile!'),
+ d.file('package_config.json', '''
+{
+ "configVersion": 2,
+ "packages": [
+ {
+ "name": "foo",
+ "rootUri": "../",
+ "packageUri": "lib/"
+ },
+ {
+ "name": "bar",
+ "rootUri": "$sandboxUriPath/bar",
+ "packageUri": "lib/"
+ }
+ ]
+}
+'''),
+ ]),
+ d.dir('lib', [
+ d.file('foo.dart', 'final foo = "bar";'),
+ ]),
+ ]).create();
+
+ await d.dir('sdk', [
+ d.dir('io', [
+ d.file('io.dart', 'final io = "hello";'),
+ ]),
+ d.dir('io_patch', [
+ d.file('io.dart', 'final patch = true;'),
+ ]),
+ d.dir('io_dev', [
+ d.file('io.dart', 'final dev = true;'),
+ ]),
+ ]).create();
+ });
+
+ test('can be created from a package_config.json', () async {
+ final resolver = await Resolver.create(
+ packagesPath:
+ p.join(d.sandbox, 'foo', '.dart_tool', 'package_config.json'));
+ expect(resolver.resolve('package:foo/foo.dart'),
+ p.join(d.sandbox, 'foo', 'lib', 'foo.dart'));
+ expect(resolver.resolve('package:bar/bar.dart'),
+ p.join(d.sandbox, 'bar', 'lib', 'bar.dart'));
+ });
+
+ test('can be created from a package directory', () async {
+ final resolver =
+ await Resolver.create(packagePath: p.join(d.sandbox, 'foo'));
+ expect(resolver.resolve('package:foo/foo.dart'),
+ p.join(d.sandbox, 'foo', 'lib', 'foo.dart'));
+ });
+
+ test('errors if the packagesFile is an unknown format', () async {
+ expect(
+ () async => await Resolver.create(
+ packagesPath: p.join(
+ d.sandbox, 'foo', '.dart_tool', 'bad_package_config.json')),
+ throwsA(isA<FormatException>()));
+ });
+
+ test('resolves dart: URIs', () async {
+ final resolver = await Resolver.create(
+ packagePath: p.join(d.sandbox, 'foo'),
+ sdkRoot: p.join(d.sandbox, 'sdk'));
+ expect(resolver.resolve('dart:io'),
+ p.join(d.sandbox, 'sdk', 'io', 'io.dart'));
+ expect(resolver.resolve('dart:io-patch/io.dart'), null);
+ expect(resolver.resolve('dart:io-dev/io.dart'),
+ p.join(d.sandbox, 'sdk', 'io_dev', 'io.dart'));
+ });
+
+ test('cannot resolve SDK URIs if sdkRoot is null', () async {
+ final resolver =
+ await Resolver.create(packagePath: p.join(d.sandbox, 'foo'));
+ expect(resolver.resolve('dart:convert'), null);
+ });
+
+ test('cannot resolve package URIs if packagePath is null', () async {
+ // ignore: deprecated_member_use_from_same_package
+ final resolver = Resolver();
+ expect(resolver.resolve('package:foo/foo.dart'), null);
+ });
+
+ test('cannot resolve package URIs if packagePath is not found', () async {
+ final resolver =
+ await Resolver.create(packagePath: p.join(d.sandbox, 'foo'));
+ expect(resolver.resolve('package:baz/baz.dart'), null);
+ });
+
+ test('cannot resolve unexpected URI schemes', () async {
+ final resolver =
+ await Resolver.create(packagePath: p.join(d.sandbox, 'foo'));
+ expect(resolver.resolve('thing:foo/foo.dart'), null);
+ });
+ });
+
+ group('Bazel resolver', () {
+ const workspace = 'foo';
+ final resolver = BazelResolver(workspacePath: workspace);
+
+ test('does not resolve SDK URIs', () {
+ expect(resolver.resolve('dart:convert'), null);
+ });
+
+ test('resolves third-party package URIs', () {
+ expect(resolver.resolve('package:foo/bar.dart'),
+ 'third_party/dart/foo/lib/bar.dart');
+ expect(resolver.resolve('package:foo/src/bar.dart'),
+ 'third_party/dart/foo/lib/src/bar.dart');
+ });
+
+ test('resolves non-third-party package URIs', () {
+ expect(
+ resolver.resolve('package:foo.bar/baz.dart'), 'foo/bar/lib/baz.dart');
+ expect(resolver.resolve('package:foo.bar/src/baz.dart'),
+ 'foo/bar/lib/src/baz.dart');
+ });
+
+ test('resolves file URIs', () {
+ expect(
+ resolver
+ .resolve('file://x/y/z.runfiles/$workspace/foo/bar/lib/baz.dart'),
+ 'foo/bar/lib/baz.dart');
+ expect(
+ resolver.resolve(
+ 'file://x/y/z.runfiles/$workspace/foo/bar/lib/src/baz.dart'),
+ 'foo/bar/lib/src/baz.dart');
+ });
+
+ test('resolves HTTPS URIs containing /packages/', () {
+ expect(resolver.resolve('https://host:8080/a/b/packages/foo/bar.dart'),
+ 'third_party/dart/foo/lib/bar.dart');
+ expect(
+ resolver.resolve('https://host:8080/a/b/packages/foo/src/bar.dart'),
+ 'third_party/dart/foo/lib/src/bar.dart');
+ expect(
+ resolver.resolve('https://host:8080/a/b/packages/foo.bar/baz.dart'),
+ 'foo/bar/lib/baz.dart');
+ expect(
+ resolver
+ .resolve('https://host:8080/a/b/packages/foo.bar/src/baz.dart'),
+ 'foo/bar/lib/src/baz.dart');
+ });
+
+ test('resolves HTTP URIs containing /packages/', () {
+ expect(resolver.resolve('http://host:8080/a/b/packages/foo/bar.dart'),
+ 'third_party/dart/foo/lib/bar.dart');
+ expect(resolver.resolve('http://host:8080/a/b/packages/foo/src/bar.dart'),
+ 'third_party/dart/foo/lib/src/bar.dart');
+ expect(resolver.resolve('http://host:8080/a/b/packages/foo.bar/baz.dart'),
+ 'foo/bar/lib/baz.dart');
+ expect(
+ resolver
+ .resolve('http://host:8080/a/b/packages/foo.bar/src/baz.dart'),
+ 'foo/bar/lib/src/baz.dart');
+ });
+
+ test('resolves HTTPS URIs without /packages/', () {
+ expect(
+ resolver
+ .resolve('https://host:8080/third_party/dart/foo/lib/bar.dart'),
+ 'third_party/dart/foo/lib/bar.dart');
+ expect(
+ resolver.resolve(
+ 'https://host:8080/third_party/dart/foo/lib/src/bar.dart'),
+ 'third_party/dart/foo/lib/src/bar.dart');
+ expect(resolver.resolve('https://host:8080/foo/lib/bar.dart'),
+ 'foo/lib/bar.dart');
+ expect(resolver.resolve('https://host:8080/foo/lib/src/bar.dart'),
+ 'foo/lib/src/bar.dart');
+ });
+
+ test('resolves HTTP URIs without /packages/', () {
+ expect(
+ resolver
+ .resolve('http://host:8080/third_party/dart/foo/lib/bar.dart'),
+ 'third_party/dart/foo/lib/bar.dart');
+ expect(
+ resolver.resolve(
+ 'http://host:8080/third_party/dart/foo/lib/src/bar.dart'),
+ 'third_party/dart/foo/lib/src/bar.dart');
+ expect(resolver.resolve('http://host:8080/foo/lib/bar.dart'),
+ 'foo/lib/bar.dart');
+ expect(resolver.resolve('http://host:8080/foo/lib/src/bar.dart'),
+ 'foo/lib/src/bar.dart');
+ });
+ });
+}
diff --git a/pkgs/coverage/test/run_and_collect_test.dart b/pkgs/coverage/test/run_and_collect_test.dart
new file mode 100644
index 0000000..e371f90
--- /dev/null
+++ b/pkgs/coverage/test/run_and_collect_test.dart
@@ -0,0 +1,128 @@
+// Copyright (c) 2015, 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/coverage.dart';
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+
+import 'test_util.dart';
+
+final _isolateLibPath = p.join('test', 'test_files', 'test_app_isolate.dart');
+
+final _sampleAppFileUri = p.toUri(p.absolute(testAppPath)).toString();
+final _isolateLibFileUri = p.toUri(p.absolute(_isolateLibPath)).toString();
+
+void main() {
+ test('runAndCollect', () async {
+ // use runAndCollect and verify that the results match w/ running manually
+ final coverage = coverageDataFromJson(await runAndCollect(testAppPath));
+ expect(coverage, isNotEmpty);
+
+ final sources = coverage.sources();
+
+ for (var sampleCoverageData in sources[_sampleAppFileUri]!) {
+ expect(sampleCoverageData['hits'], isNotNull);
+ }
+
+ for (var sampleCoverageData in sources[_isolateLibFileUri]!) {
+ expect(sampleCoverageData['hits'], isNotEmpty);
+ }
+
+ final hitMap = await HitMap.parseJson(coverage, checkIgnoredLines: true);
+ checkHitmap(hitMap);
+ final resolver = await Resolver.create();
+ final ignoredLinesInFilesCache = <String, List<List<int>>?>{};
+ final hitMap2 = HitMap.parseJsonSync(coverage,
+ checkIgnoredLines: true,
+ ignoredLinesInFilesCache: ignoredLinesInFilesCache,
+ resolver: resolver);
+ checkHitmap(hitMap2);
+ checkIgnoredLinesInFilesCache(ignoredLinesInFilesCache);
+
+ // Asking again the cache should answer questions about ignored lines,
+ // so providing a resolver that throws when asked for files should be ok.
+ final hitMap3 = HitMap.parseJsonSync(coverage,
+ checkIgnoredLines: true,
+ ignoredLinesInFilesCache: ignoredLinesInFilesCache,
+ resolver: ThrowingResolver());
+ checkHitmap(hitMap3);
+ checkIgnoredLinesInFilesCache(ignoredLinesInFilesCache);
+ });
+}
+
+class ThrowingResolver implements Resolver {
+ @override
+ List<String> get failed => throw UnimplementedError();
+
+ @override
+ String? get packagePath => throw UnimplementedError();
+
+ @override
+ String? get packagesPath => throw UnimplementedError();
+
+ @override
+ String? resolve(String scriptUri) => throw UnimplementedError();
+
+ @override
+ String? resolveSymbolicLinks(String path) => throw UnimplementedError();
+
+ @override
+ String? get sdkRoot => throw UnimplementedError();
+}
+
+void checkIgnoredLinesInFilesCache(
+ Map<String, List<List<int>>?> ignoredLinesInFilesCache) {
+ final keys = ignoredLinesInFilesCache.keys.toList();
+ final testAppKey =
+ keys.where((element) => element.endsWith('test_app.dart')).single;
+ final testAppIsolateKey =
+ keys.where((element) => element.endsWith('test_app_isolate.dart')).single;
+ final packageUtilKey = keys
+ .where((element) => element.endsWith('package:coverage/src/util.dart'))
+ .single;
+ expect(ignoredLinesInFilesCache[packageUtilKey], isEmpty);
+ expect(ignoredLinesInFilesCache[testAppKey], null /* means whole file */);
+ expect(ignoredLinesInFilesCache[testAppIsolateKey], [
+ [51, 51],
+ [53, 57],
+ [62, 65],
+ [66, 72]
+ ]);
+}
+
+void checkHitmap(Map<String, HitMap> hitMap) {
+ expect(hitMap, isNot(contains(_sampleAppFileUri)));
+
+ final actualHitMap = hitMap[_isolateLibFileUri];
+ final actualLineHits = actualHitMap?.lineHits;
+ final expectedLineHits = {
+ 11: 1,
+ 12: 1,
+ 13: 1,
+ 15: 0,
+ 19: 1,
+ 23: 1,
+ 24: 2,
+ 28: 1,
+ 29: 1,
+ 30: 1,
+ 32: 0,
+ 38: 1,
+ 39: 1,
+ 41: 1,
+ 42: 4,
+ 43: 1,
+ 44: 3,
+ 45: 1,
+ 48: 1,
+ 49: 1,
+ 59: 1,
+ 60: 1
+ };
+
+ expect(actualLineHits, expectedLineHits);
+ expect(actualHitMap?.funcHits, isNull);
+ expect(actualHitMap?.funcNames, isNull);
+ expect(actualHitMap?.branchHits, isNull);
+}
diff --git a/pkgs/coverage/test/test_files/chrome_precise_report.txt b/pkgs/coverage/test/test_files/chrome_precise_report.txt
new file mode 100644
index 0000000..41a3e64
--- /dev/null
+++ b/pkgs/coverage/test/test_files/chrome_precise_report.txt
@@ -0,0 +1 @@
+[{"scriptId":"5","url":"http://localhost:33587/JvrQonmMf3ATS3rRCO76ZTlBfGvy%2Fj69/packages/test/src/runner/browser/static/host.dart.js","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":598832,"count":1}],"isBlockCoverage":true},{"functionName":"dartProgram","ranges":[{"startOffset":1228,"endOffset":598788,"count":1}],"isBlockCoverage":true},{"functionName":"copyProperties","ranges":[{"startOffset":1255,"endOffset":1430,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1465,"endOffset":2035,"count":1},{"startOffset":1646,"endOffset":1659,"count":0},{"startOffset":1822,"endOffset":1884,"count":0},{"startOffset":1886,"endOffset":1989,"count":0},{"startOffset":1995,"endOffset":2034,"count":0}],"isBlockCoverage":true},{"functionName":"cls","ranges":[{"startOffset":1492,"endOffset":1510,"count":1}],"isBlockCoverage":true},{"functionName":"setFunctionNamesIfNecessary","ranges":[{"startOffset":2041,"endOffset":2464,"count":1},{"startOffset":2168,"endOffset":2460,"count":0}],"isBlockCoverage":true},{"functionName":"t","ranges":[{"startOffset":2093,"endOffset":2113,"count":0}],"isBlockCoverage":false},{"functionName":"inherit","ranges":[{"startOffset":2467,"endOffset":2862,"count":1},{"startOffset":2710,"endOffset":2858,"count":0}],"isBlockCoverage":true},{"functionName":"inheritMany","ranges":[{"startOffset":2865,"endOffset":2982,"count":1}],"isBlockCoverage":true},{"functionName":"mixin","ranges":[{"startOffset":2985,"endOffset":3106,"count":1}],"isBlockCoverage":true},{"functionName":"lazy","ranges":[{"startOffset":3109,"endOffset":3858,"count":1}],"isBlockCoverage":true},{"functionName":"holder.<computed>","ranges":[{"startOffset":3271,"endOffset":3853,"count":1},{"startOffset":3606,"endOffset":3644,"count":0},{"startOffset":3716,"endOffset":3736,"count":0}],"isBlockCoverage":true},{"functionName":"holder.<computed>","ranges":[{"startOffset":3311,"endOffset":3364,"count":0}],"isBlockCoverage":false},{"functionName":"holder.<computed>","ranges":[{"startOffset":3766,"endOffset":3817,"count":1}],"isBlockCoverage":true},{"functionName":"makeConstList","ranges":[{"startOffset":3861,"endOffset":3976,"count":1}],"isBlockCoverage":true},{"functionName":"convertToFastObject","ranges":[{"startOffset":3979,"endOffset":4116,"count":1}],"isBlockCoverage":true},{"functionName":"t","ranges":[{"startOffset":4026,"endOffset":4046,"count":1}],"isBlockCoverage":true},{"functionName":"convertAllToFastObject","ranges":[{"startOffset":4119,"endOffset":4270,"count":1}],"isBlockCoverage":true},{"functionName":"tearOffGetter","ranges":[{"startOffset":4300,"endOffset":5215,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff","ranges":[{"startOffset":5218,"endOffset":5626,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":5357,"endOffset":5539,"count":1}],"isBlockCoverage":true},{"functionName":"installTearOff","ranges":[{"startOffset":5652,"endOffset":6582,"count":1}],"isBlockCoverage":true},{"functionName":"installStaticTearOff","ranges":[{"startOffset":6585,"endOffset":6908,"count":1}],"isBlockCoverage":true},{"functionName":"installInstanceTearOff","ranges":[{"startOffset":6911,"endOffset":7260,"count":1}],"isBlockCoverage":true},{"functionName":"setOrUpdateInterceptorsByTag","ranges":[{"startOffset":7263,"endOffset":7466,"count":1},{"startOffset":7427,"endOffset":7465,"count":0}],"isBlockCoverage":true},{"functionName":"setOrUpdateLeafTags","ranges":[{"startOffset":7469,"endOffset":7645,"count":1},{"startOffset":7606,"endOffset":7644,"count":0}],"isBlockCoverage":true},{"functionName":"updateTypes","ranges":[{"startOffset":7648,"endOffset":7801,"count":0}],"isBlockCoverage":false},{"functionName":"updateHolder","ranges":[{"startOffset":7804,"endOffset":7908,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":7929,"endOffset":9593,"count":1}],"isBlockCoverage":true},{"functionName":"mkInstance","ranges":[{"startOffset":7963,"endOffset":8325,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":8083,"endOffset":8316,"count":1}],"isBlockCoverage":true},{"functionName":"mkStatic","ranges":[{"startOffset":8344,"endOffset":8674,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":8449,"endOffset":8665,"count":1}],"isBlockCoverage":true},{"functionName":"initializeDeferredHunk","ranges":[{"startOffset":9599,"endOffset":9720,"count":0}],"isBlockCoverage":false},{"functionName":"getGlobalFromName","ranges":[{"startOffset":9723,"endOffset":9920,"count":0}],"isBlockCoverage":false},{"functionName":"JS_CONST","ranges":[{"startOffset":9952,"endOffset":9979,"count":0}],"isBlockCoverage":false},{"functionName":"hexDigitValue","ranges":[{"startOffset":10000,"endOffset":10228,"count":1},{"startOffset":10205,"endOffset":10227,"count":0}],"isBlockCoverage":true},{"functionName":"SubListIterable$","ranges":[{"startOffset":10252,"endOffset":10660,"count":0}],"isBlockCoverage":false},{"functionName":"MappedIterable_MappedIterable","ranges":[{"startOffset":10697,"endOffset":11098,"count":0}],"isBlockCoverage":false},{"functionName":"IterableElementError_noElement","ranges":[{"startOffset":11136,"endOffset":11199,"count":0}],"isBlockCoverage":false},{"functionName":"IterableElementError_tooFew","ranges":[{"startOffset":11234,"endOffset":11303,"count":0}],"isBlockCoverage":false},{"functionName":"CodeUnits","ranges":[{"startOffset":11320,"endOffset":11375,"count":0}],"isBlockCoverage":false},{"functionName":"EfficientLengthIterable","ranges":[{"startOffset":11406,"endOffset":11448,"count":0}],"isBlockCoverage":false},{"functionName":"ListIterable","ranges":[{"startOffset":11468,"endOffset":11499,"count":0}],"isBlockCoverage":false},{"functionName":"SubListIterable","ranges":[{"startOffset":11522,"endOffset":11702,"count":0}],"isBlockCoverage":false},{"functionName":"ListIterator","ranges":[{"startOffset":11722,"endOffset":11941,"count":0}],"isBlockCoverage":false},{"functionName":"MappedIterable","ranges":[{"startOffset":11963,"endOffset":12085,"count":0}],"isBlockCoverage":false},{"functionName":"EfficientLengthMappedIterable","ranges":[{"startOffset":12122,"endOffset":12259,"count":0}],"isBlockCoverage":false},{"functionName":"MappedIterator","ranges":[{"startOffset":12281,"endOffset":12439,"count":0}],"isBlockCoverage":false},{"functionName":"MappedListIterable","ranges":[{"startOffset":12465,"endOffset":12578,"count":0}],"isBlockCoverage":false},{"functionName":"WhereIterable","ranges":[{"startOffset":12599,"endOffset":12720,"count":0}],"isBlockCoverage":false},{"functionName":"WhereIterator","ranges":[{"startOffset":12741,"endOffset":12851,"count":0}],"isBlockCoverage":false},{"functionName":"ExpandIterable","ranges":[{"startOffset":12873,"endOffset":12995,"count":0}],"isBlockCoverage":false},{"functionName":"ExpandIterator","ranges":[{"startOffset":13017,"endOffset":13211,"count":0}],"isBlockCoverage":false},{"functionName":"SkipWhileIterable","ranges":[{"startOffset":13236,"endOffset":13361,"count":0}],"isBlockCoverage":false},{"functionName":"SkipWhileIterator","ranges":[{"startOffset":13386,"endOffset":13540,"count":0}],"isBlockCoverage":false},{"functionName":"EmptyIterator","ranges":[{"startOffset":13561,"endOffset":13616,"count":1}],"isBlockCoverage":true},{"functionName":"FixedLengthListMixin","ranges":[{"startOffset":13644,"endOffset":13683,"count":0}],"isBlockCoverage":false},{"functionName":"UnmodifiableListMixin","ranges":[{"startOffset":13712,"endOffset":13752,"count":0}],"isBlockCoverage":false},{"functionName":"UnmodifiableListBase","ranges":[{"startOffset":13780,"endOffset":13819,"count":0}],"isBlockCoverage":false},{"functionName":"ReversedListIterable","ranges":[{"startOffset":13847,"endOffset":13938,"count":0}],"isBlockCoverage":false},{"functionName":"Symbol","ranges":[{"startOffset":13952,"endOffset":14013,"count":1}],"isBlockCoverage":true},{"functionName":"ConstantMap__throwUnmodifiable","ranges":[{"startOffset":14051,"endOffset":14153,"count":0}],"isBlockCoverage":false},{"functionName":"instantiate1","ranges":[{"startOffset":14173,"endOffset":14345,"count":0}],"isBlockCoverage":false},{"functionName":"unminifyOrTag","ranges":[{"startOffset":14366,"endOffset":14570,"count":0}],"isBlockCoverage":false},{"functionName":"getType","ranges":[{"startOffset":14585,"endOffset":14656,"count":1}],"isBlockCoverage":true},{"functionName":"isJsIndexable","ranges":[{"startOffset":14677,"endOffset":14915,"count":0}],"isBlockCoverage":false},{"functionName":"S","ranges":[{"startOffset":14924,"endOffset":15426,"count":1},{"startOffset":15015,"endOffset":15425,"count":0}],"isBlockCoverage":true},{"functionName":"Primitives_objectHashCode","ranges":[{"startOffset":15459,"endOffset":15659,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_parseInt","ranges":[{"startOffset":15686,"endOffset":16847,"count":1},{"startOffset":15821,"endOffset":15869,"count":0},{"startOffset":15981,"endOffset":15988,"count":0},{"startOffset":16026,"endOffset":16051,"count":0},{"startOffset":16201,"endOffset":16328,"count":0},{"startOffset":16338,"endOffset":16456,"count":0},{"startOffset":16466,"endOffset":16539,"count":0},{"startOffset":16541,"endOffset":16846,"count":0}],"isBlockCoverage":true},{"functionName":"Primitives_objectTypeName","ranges":[{"startOffset":16880,"endOffset":17014,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives__objectClassName","ranges":[{"startOffset":17049,"endOffset":18437,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_currentUri","ranges":[{"startOffset":18466,"endOffset":18560,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives__fromCharCodeApply","ranges":[{"startOffset":18597,"endOffset":19022,"count":1},{"startOffset":18798,"endOffset":19021,"count":0}],"isBlockCoverage":true},{"functionName":"Primitives_stringFromCodePoints","ranges":[{"startOffset":19061,"endOffset":19851,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_stringFromCharCodes","ranges":[{"startOffset":19889,"endOffset":20367,"count":1},{"startOffset":20086,"endOffset":20133,"count":0},{"startOffset":20163,"endOffset":20210,"count":0},{"startOffset":20244,"endOffset":20296,"count":0}],"isBlockCoverage":true},{"functionName":"Primitives_stringFromNativeUint8List","ranges":[{"startOffset":20411,"endOffset":20838,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_stringFromCharCode","ranges":[{"startOffset":20875,"endOffset":21388,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_lazyAsJsDate","ranges":[{"startOffset":21419,"endOffset":21566,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_getYear","ranges":[{"startOffset":21592,"endOffset":21708,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_getMonth","ranges":[{"startOffset":21735,"endOffset":21848,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_getDay","ranges":[{"startOffset":21873,"endOffset":21985,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_getHours","ranges":[{"startOffset":22012,"endOffset":22125,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_getMinutes","ranges":[{"startOffset":22154,"endOffset":22269,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_getSeconds","ranges":[{"startOffset":22298,"endOffset":22413,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_getMilliseconds","ranges":[{"startOffset":22447,"endOffset":22567,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_functionNoSuchMethod","ranges":[{"startOffset":22606,"endOffset":23376,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_applyFunction","ranges":[{"startOffset":23408,"endOffset":25001,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives__genericApplyFunction2","ranges":[{"startOffset":25042,"endOffset":28197,"count":0}],"isBlockCoverage":false},{"functionName":"iae","ranges":[{"startOffset":28208,"endOffset":28295,"count":0}],"isBlockCoverage":false},{"functionName":"ioore","ranges":[{"startOffset":28308,"endOffset":28473,"count":0}],"isBlockCoverage":false},{"functionName":"diagnoseIndexError","ranges":[{"startOffset":28499,"endOffset":29047,"count":0}],"isBlockCoverage":false},{"functionName":"diagnoseRangeError","ranges":[{"startOffset":29073,"endOffset":29430,"count":0}],"isBlockCoverage":false},{"functionName":"argumentErrorValue","ranges":[{"startOffset":29456,"endOffset":29540,"count":0}],"isBlockCoverage":false},{"functionName":"checkNum","ranges":[{"startOffset":29556,"endOffset":29696,"count":0}],"isBlockCoverage":false},{"functionName":"wrapException","ranges":[{"startOffset":29717,"endOffset":30104,"count":0}],"isBlockCoverage":false},{"functionName":"toStringWrapper","ranges":[{"startOffset":30127,"endOffset":30193,"count":0}],"isBlockCoverage":false},{"functionName":"throwExpression","ranges":[{"startOffset":30216,"endOffset":30269,"count":0}],"isBlockCoverage":false},{"functionName":"throwConcurrentModificationError","ranges":[{"startOffset":30309,"endOffset":30410,"count":0}],"isBlockCoverage":false},{"functionName":"TypeErrorDecoder_extractPattern","ranges":[{"startOffset":30449,"endOffset":31458,"count":0}],"isBlockCoverage":false},{"functionName":"TypeErrorDecoder_provokeCallErrorOn","ranges":[{"startOffset":31501,"endOffset":31745,"count":0}],"isBlockCoverage":false},{"functionName":"TypeErrorDecoder_provokePropertyErrorOn","ranges":[{"startOffset":31792,"endOffset":31974,"count":0}],"isBlockCoverage":false},{"functionName":"NullError$","ranges":[{"startOffset":31992,"endOffset":32102,"count":0}],"isBlockCoverage":false},{"functionName":"JsNoSuchMethodError$","ranges":[{"startOffset":32130,"endOffset":32326,"count":0}],"isBlockCoverage":false},{"functionName":"unwrapException","ranges":[{"startOffset":32349,"endOffset":36809,"count":0}],"isBlockCoverage":false},{"functionName":"getTraceFromException","ranges":[{"startOffset":36838,"endOffset":37203,"count":0}],"isBlockCoverage":false},{"functionName":"fillLiteralMap","ranges":[{"startOffset":37225,"endOffset":37561,"count":0}],"isBlockCoverage":false},{"functionName":"invokeClosure","ranges":[{"startOffset":37582,"endOffset":38172,"count":1},{"startOffset":37758,"endOffset":37800,"count":0},{"startOffset":37864,"endOffset":37916,"count":0},{"startOffset":37925,"endOffset":37983,"count":0},{"startOffset":37992,"endOffset":38056,"count":0},{"startOffset":38064,"endOffset":38171,"count":0}],"isBlockCoverage":true},{"functionName":"convertDartClosureToJS","ranges":[{"startOffset":38202,"endOffset":38679,"count":1},{"startOffset":38314,"endOffset":38321,"count":0},{"startOffset":38390,"endOffset":38407,"count":0}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":38426,"endOffset":38578,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":38476,"endOffset":38569,"count":1}],"isBlockCoverage":true},{"functionName":"Closure_fromTearOff","ranges":[{"startOffset":38706,"endOffset":41471,"count":1},{"startOffset":39469,"endOffset":39486,"count":0},{"startOffset":40204,"endOffset":40709,"count":0}],"isBlockCoverage":true},{"functionName":"static_tear_off","ranges":[{"startOffset":39300,"endOffset":39368,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":40070,"endOffset":40176,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":40110,"endOffset":40165,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":40461,"endOffset":40599,"count":0}],"isBlockCoverage":false},{"functionName":"Closure_cspForwardCall","ranges":[{"startOffset":41501,"endOffset":42886,"count":1},{"startOffset":41627,"endOffset":41634,"count":0},{"startOffset":41646,"endOffset":41801,"count":0},{"startOffset":41810,"endOffset":41967,"count":0},{"startOffset":41976,"endOffset":42139,"count":0},{"startOffset":42148,"endOffset":42317,"count":0},{"startOffset":42326,"endOffset":42501,"count":0},{"startOffset":42510,"endOffset":42691,"count":0}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":41671,"endOffset":41781,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":41835,"endOffset":41947,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":42001,"endOffset":42119,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":42173,"endOffset":42297,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":42351,"endOffset":42481,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":42535,"endOffset":42671,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":42726,"endOffset":42851,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":42762,"endOffset":42838,"count":0}],"isBlockCoverage":false},{"functionName":"Closure_forwardCallTo","ranges":[{"startOffset":42915,"endOffset":44616,"count":1},{"startOffset":43278,"endOffset":43304,"count":0},{"startOffset":43569,"endOffset":43586,"count":0},{"startOffset":43798,"endOffset":43916,"count":0},{"startOffset":44191,"endOffset":44208,"count":0},{"startOffset":44406,"endOffset":44518,"count":0}],"isBlockCoverage":true},{"functionName":"Closure_cspForwardInterceptedCall","ranges":[{"startOffset":44657,"endOffset":46449,"count":1},{"startOffset":44855,"endOffset":44862,"count":0},{"startOffset":44874,"endOffset":44974,"count":0},{"startOffset":44983,"endOffset":45158,"count":0},{"startOffset":45167,"endOffset":45346,"count":0},{"startOffset":45355,"endOffset":45540,"count":0},{"startOffset":45549,"endOffset":45740,"count":0},{"startOffset":45749,"endOffset":45946,"count":0},{"startOffset":45955,"endOffset":46158,"count":0}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":45008,"endOffset":45128,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":45192,"endOffset":45316,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":45380,"endOffset":45510,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":45574,"endOffset":45710,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":45774,"endOffset":45916,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":45980,"endOffset":46128,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":46193,"endOffset":46401,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":46235,"endOffset":46388,"count":0}],"isBlockCoverage":false},{"functionName":"Closure_forwardInterceptedCallTo","ranges":[{"startOffset":46489,"endOffset":48059,"count":1},{"startOffset":47112,"endOffset":47138,"count":0},{"startOffset":47326,"endOffset":47636,"count":0},{"startOffset":47950,"endOffset":47967,"count":0}],"isBlockCoverage":true},{"functionName":"closureFromTearOff","ranges":[{"startOffset":48085,"endOffset":48339,"count":1}],"isBlockCoverage":true},{"functionName":"BoundClosure_selfOf","ranges":[{"startOffset":48366,"endOffset":48419,"count":0}],"isBlockCoverage":false},{"functionName":"BoundClosure_receiverOf","ranges":[{"startOffset":48450,"endOffset":48507,"count":0}],"isBlockCoverage":false},{"functionName":"BoundClosure_computeFieldNamed","ranges":[{"startOffset":48545,"endOffset":48918,"count":1}],"isBlockCoverage":true},{"functionName":"stringTypeCheck","ranges":[{"startOffset":48941,"endOffset":49144,"count":1},{"startOffset":48992,"endOffset":49005,"count":0},{"startOffset":49064,"endOffset":49143,"count":0}],"isBlockCoverage":true},{"functionName":"stringTypeCast","ranges":[{"startOffset":49166,"endOffset":49339,"count":1},{"startOffset":49220,"endOffset":49236,"count":0},{"startOffset":49259,"endOffset":49338,"count":0}],"isBlockCoverage":true},{"functionName":"doubleTypeCheck","ranges":[{"startOffset":49362,"endOffset":49565,"count":0}],"isBlockCoverage":false},{"functionName":"numTypeCheck","ranges":[{"startOffset":49585,"endOffset":49785,"count":0}],"isBlockCoverage":false},{"functionName":"boolTypeCheck","ranges":[{"startOffset":49806,"endOffset":50008,"count":0}],"isBlockCoverage":false},{"functionName":"intTypeCheck","ranges":[{"startOffset":50028,"endOffset":50259,"count":1},{"startOffset":50079,"endOffset":50092,"count":0},{"startOffset":50182,"endOffset":50258,"count":0}],"isBlockCoverage":true},{"functionName":"intTypeCast","ranges":[{"startOffset":50278,"endOffset":50479,"count":1},{"startOffset":50363,"endOffset":50379,"count":0},{"startOffset":50402,"endOffset":50478,"count":0}],"isBlockCoverage":true},{"functionName":"propertyTypeError","ranges":[{"startOffset":50504,"endOffset":50660,"count":0}],"isBlockCoverage":false},{"functionName":"interceptedTypeCheck","ranges":[{"startOffset":50688,"endOffset":50942,"count":1},{"startOffset":50800,"endOffset":50830,"count":0},{"startOffset":50892,"endOffset":50941,"count":0}],"isBlockCoverage":true},{"functionName":"stringSuperNativeTypeCheck","ranges":[{"startOffset":50976,"endOffset":51227,"count":0}],"isBlockCoverage":false},{"functionName":"listTypeCheck","ranges":[{"startOffset":51248,"endOffset":51467,"count":1},{"startOffset":51380,"endOffset":51466,"count":0}],"isBlockCoverage":true},{"functionName":"listSuperNativeTypeCheck","ranges":[{"startOffset":51499,"endOffset":51766,"count":0}],"isBlockCoverage":false},{"functionName":"extractFunctionTypeObjectFromInternal","ranges":[{"startOffset":51811,"endOffset":52082,"count":1},{"startOffset":52062,"endOffset":52081,"count":0}],"isBlockCoverage":true},{"functionName":"functionTypeTest","ranges":[{"startOffset":52106,"endOffset":52520,"count":1},{"startOffset":52204,"endOffset":52217,"count":0},{"startOffset":52417,"endOffset":52430,"count":0}],"isBlockCoverage":true},{"functionName":"functionTypeCheck","ranges":[{"startOffset":52545,"endOffset":53038,"count":1},{"startOffset":52686,"endOffset":52699,"count":0},{"startOffset":52824,"endOffset":52979,"count":0}],"isBlockCoverage":true},{"functionName":"futureOrCheck","ranges":[{"startOffset":53059,"endOffset":53270,"count":1},{"startOffset":53106,"endOffset":53153,"count":0},{"startOffset":53163,"endOffset":53248,"count":0}],"isBlockCoverage":true},{"functionName":"TypeErrorImplementation$","ranges":[{"startOffset":53302,"endOffset":53507,"count":0}],"isBlockCoverage":false},{"functionName":"CastErrorImplementation$","ranges":[{"startOffset":53539,"endOffset":53744,"count":0}],"isBlockCoverage":false},{"functionName":"_typeDescription","ranges":[{"startOffset":53768,"endOffset":54148,"count":0}],"isBlockCoverage":false},{"functionName":"throwCyclicInit","ranges":[{"startOffset":54171,"endOffset":54292,"count":0}],"isBlockCoverage":false},{"functionName":"RuntimeError$","ranges":[{"startOffset":54313,"endOffset":54380,"count":0}],"isBlockCoverage":false},{"functionName":"getIsolateAffinityTag","ranges":[{"startOffset":54409,"endOffset":54472,"count":1}],"isBlockCoverage":true},{"functionName":"setRuntimeTypeInfo","ranges":[{"startOffset":54498,"endOffset":54572,"count":1}],"isBlockCoverage":true},{"functionName":"getRuntimeTypeInfo","ranges":[{"startOffset":54598,"endOffset":54689,"count":1},{"startOffset":54651,"endOffset":54658,"count":0}],"isBlockCoverage":true},{"functionName":"getRuntimeTypeArguments","ranges":[{"startOffset":54720,"endOffset":54876,"count":0}],"isBlockCoverage":false},{"functionName":"getRuntimeTypeArgumentIntercepted","ranges":[{"startOffset":54917,"endOffset":55240,"count":1},{"startOffset":55207,"endOffset":55213,"count":0}],"isBlockCoverage":true},{"functionName":"getRuntimeTypeArgument","ranges":[{"startOffset":55270,"endOffset":55575,"count":1},{"startOffset":55542,"endOffset":55548,"count":0}],"isBlockCoverage":true},{"functionName":"getTypeArgumentByIndex","ranges":[{"startOffset":55605,"endOffset":55768,"count":1}],"isBlockCoverage":true},{"functionName":"runtimeTypeToString","ranges":[{"startOffset":55795,"endOffset":55864,"count":0}],"isBlockCoverage":false},{"functionName":"_runtimeTypeToString","ranges":[{"startOffset":55892,"endOffset":57053,"count":0}],"isBlockCoverage":false},{"functionName":"_functionRtiToString","ranges":[{"startOffset":57081,"endOffset":59945,"count":0}],"isBlockCoverage":false},{"functionName":"_joinArguments","ranges":[{"startOffset":59967,"endOffset":60636,"count":0}],"isBlockCoverage":false},{"functionName":"substitute","ranges":[{"startOffset":60654,"endOffset":61121,"count":1},{"startOffset":60849,"endOffset":60856,"count":0},{"startOffset":60992,"endOffset":61120,"count":0}],"isBlockCoverage":true},{"functionName":"checkSubtype","ranges":[{"startOffset":61141,"endOffset":61627,"count":1},{"startOffset":61512,"endOffset":61525,"count":0}],"isBlockCoverage":true},{"functionName":"assertSubtype","ranges":[{"startOffset":61648,"endOffset":62224,"count":1},{"startOffset":61923,"endOffset":62223,"count":0}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":61987,"endOffset":62115,"count":0}],"isBlockCoverage":false},{"functionName":"assertIsSubtype","ranges":[{"startOffset":62247,"endOffset":62611,"count":1},{"startOffset":62464,"endOffset":62605,"count":0}],"isBlockCoverage":true},{"functionName":"throwTypeError","ranges":[{"startOffset":62633,"endOffset":62746,"count":0}],"isBlockCoverage":false},{"functionName":"areSubtypes","ranges":[{"startOffset":62765,"endOffset":63196,"count":1},{"startOffset":62841,"endOffset":62853,"count":0},{"startOffset":62875,"endOffset":63042,"count":0},{"startOffset":63158,"endOffset":63171,"count":0}],"isBlockCoverage":true},{"functionName":"computeSignature","ranges":[{"startOffset":63220,"endOffset":63407,"count":0}],"isBlockCoverage":false},{"functionName":"isSupertypeOfNullRecursive","ranges":[{"startOffset":63441,"endOffset":63818,"count":0}],"isBlockCoverage":false},{"functionName":"checkSubtypeOfRuntimeType","ranges":[{"startOffset":63851,"endOffset":64616,"count":1},{"startOffset":63918,"endOffset":64038,"count":0},{"startOffset":64201,"endOffset":64290,"count":0}],"isBlockCoverage":true},{"functionName":"assertSubtypeOfRuntimeType","ranges":[{"startOffset":64650,"endOffset":64869,"count":1},{"startOffset":64755,"endOffset":64842,"count":0}],"isBlockCoverage":true},{"functionName":"_isSubtype","ranges":[{"startOffset":64887,"endOffset":67156,"count":1},{"startOffset":65190,"endOffset":65202,"count":0},{"startOffset":65318,"endOffset":65331,"count":0},{"startOffset":65421,"endOffset":65427,"count":0},{"startOffset":65435,"endOffset":65465,"count":0},{"startOffset":65507,"endOffset":65520,"count":0},{"startOffset":65562,"endOffset":65575,"count":0},{"startOffset":65945,"endOffset":65951,"count":0},{"startOffset":65992,"endOffset":66068,"count":0},{"startOffset":66152,"endOffset":66605,"count":0},{"startOffset":66860,"endOffset":66873,"count":0},{"startOffset":67044,"endOffset":67050,"count":0}],"isBlockCoverage":true},{"functionName":"_isFunctionSubtype","ranges":[{"startOffset":67182,"endOffset":69335,"count":1},{"startOffset":67490,"endOffset":67503,"count":0},{"startOffset":67571,"endOffset":67584,"count":0},{"startOffset":67698,"endOffset":67711,"count":0},{"startOffset":67752,"endOffset":67765,"count":0},{"startOffset":67825,"endOffset":67838,"count":0},{"startOffset":68299,"endOffset":68331,"count":0},{"startOffset":68388,"endOffset":68401,"count":0},{"startOffset":68503,"endOffset":68516,"count":0},{"startOffset":68659,"endOffset":68672,"count":0},{"startOffset":68848,"endOffset":68861,"count":0},{"startOffset":68938,"endOffset":69054,"count":0},{"startOffset":69179,"endOffset":69334,"count":0}],"isBlockCoverage":true},{"functionName":"namedParametersSubtypeCheck","ranges":[{"startOffset":69370,"endOffset":69743,"count":0}],"isBlockCoverage":false},{"functionName":"instantiatedGenericFunctionType","ranges":[{"startOffset":69782,"endOffset":69981,"count":0}],"isBlockCoverage":false},{"functionName":"finishBindInstantiatedFunctionType","ranges":[{"startOffset":70023,"endOffset":70893,"count":0}],"isBlockCoverage":false},{"functionName":"bindInstantiatedType","ranges":[{"startOffset":70921,"endOffset":71830,"count":0}],"isBlockCoverage":false},{"functionName":"bindInstantiatedTypes","ranges":[{"startOffset":71859,"endOffset":72120,"count":0}],"isBlockCoverage":false},{"functionName":"JsLinkedHashMap_JsLinkedHashMap$es6","ranges":[{"startOffset":72163,"endOffset":72233,"count":1}],"isBlockCoverage":true},{"functionName":"defineProperty","ranges":[{"startOffset":72255,"endOffset":72427,"count":0}],"isBlockCoverage":false},{"functionName":"lookupAndCacheInterceptor","ranges":[{"startOffset":72460,"endOffset":74952,"count":0}],"isBlockCoverage":false},{"functionName":"patchInteriorProto","ranges":[{"startOffset":74978,"endOffset":75265,"count":0}],"isBlockCoverage":false},{"functionName":"makeLeafDispatchRecord","ranges":[{"startOffset":75295,"endOffset":75430,"count":1}],"isBlockCoverage":true},{"functionName":"makeDefaultDispatchRecord","ranges":[{"startOffset":75463,"endOffset":75734,"count":1}],"isBlockCoverage":true},{"functionName":"initNativeDispatch","ranges":[{"startOffset":75760,"endOffset":75916,"count":1},{"startOffset":75826,"endOffset":75833,"count":0}],"isBlockCoverage":true},{"functionName":"initNativeDispatchContinue","ranges":[{"startOffset":75950,"endOffset":77230,"count":1}],"isBlockCoverage":true},{"functionName":"fun","ranges":[{"startOffset":76329,"endOffset":76351,"count":0}],"isBlockCoverage":false},{"functionName":"initHooks","ranges":[{"startOffset":77247,"endOffset":78506,"count":1},{"startOffset":77761,"endOffset":78174,"count":0}],"isBlockCoverage":true},{"functionName":"applyHooksTransformer","ranges":[{"startOffset":78535,"endOffset":78613,"count":1}],"isBlockCoverage":true},{"functionName":"JSSyntaxRegExp_makeNative","ranges":[{"startOffset":78646,"endOffset":79188,"count":0}],"isBlockCoverage":false},{"functionName":"stringContainsUnchecked","ranges":[{"startOffset":79219,"endOffset":79767,"count":0}],"isBlockCoverage":false},{"functionName":"stringReplaceFirstRE","ranges":[{"startOffset":79795,"endOffset":80070,"count":0}],"isBlockCoverage":false},{"functionName":"stringReplaceAllUnchecked","ranges":[{"startOffset":80103,"endOffset":81120,"count":0}],"isBlockCoverage":false},{"functionName":"stringReplaceFirstUnchecked","ranges":[{"startOffset":81155,"endOffset":82205,"count":0}],"isBlockCoverage":false},{"functionName":"stringReplaceRangeUnchecked","ranges":[{"startOffset":82240,"endOffset":82446,"count":0}],"isBlockCoverage":false},{"functionName":"ConstantMapView","ranges":[{"startOffset":82469,"endOffset":82552,"count":0}],"isBlockCoverage":false},{"functionName":"ConstantMap","ranges":[{"startOffset":82571,"endOffset":82601,"count":0}],"isBlockCoverage":false},{"functionName":"ConstantStringMap","ranges":[{"startOffset":82626,"endOffset":82792,"count":1}],"isBlockCoverage":true},{"functionName":"Instantiation","ranges":[{"startOffset":82813,"endOffset":82845,"count":0}],"isBlockCoverage":false},{"functionName":"Instantiation1","ranges":[{"startOffset":82867,"endOffset":82960,"count":0}],"isBlockCoverage":false},{"functionName":"JSInvocationMirror","ranges":[{"startOffset":82986,"endOffset":83199,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_functionNoSuchMethod_closure","ranges":[{"startOffset":83246,"endOffset":83401,"count":0}],"isBlockCoverage":false},{"functionName":"TypeErrorDecoder","ranges":[{"startOffset":83425,"endOffset":83645,"count":0}],"isBlockCoverage":false},{"functionName":"NullError","ranges":[{"startOffset":83662,"endOffset":83747,"count":0}],"isBlockCoverage":false},{"functionName":"JsNoSuchMethodError","ranges":[{"startOffset":83774,"endOffset":83900,"count":0}],"isBlockCoverage":false},{"functionName":"UnknownJsTypeError","ranges":[{"startOffset":83926,"endOffset":83991,"count":0}],"isBlockCoverage":false},{"functionName":"ExceptionAndStackTrace","ranges":[{"startOffset":84021,"endOffset":84127,"count":0}],"isBlockCoverage":false},{"functionName":"unwrapException_saveStackTrace","ranges":[{"startOffset":84165,"endOffset":84236,"count":0}],"isBlockCoverage":false},{"functionName":"_StackTrace","ranges":[{"startOffset":84255,"endOffset":84353,"count":0}],"isBlockCoverage":false},{"functionName":"Closure","ranges":[{"startOffset":84368,"endOffset":84394,"count":0}],"isBlockCoverage":false},{"functionName":"TearOffClosure","ranges":[{"startOffset":84416,"endOffset":84449,"count":0}],"isBlockCoverage":false},{"functionName":"StaticClosure","ranges":[{"startOffset":84470,"endOffset":84502,"count":1}],"isBlockCoverage":true},{"functionName":"BoundClosure","ranges":[{"startOffset":84522,"endOffset":84673,"count":1}],"isBlockCoverage":true},{"functionName":"TypeErrorImplementation","ranges":[{"startOffset":84704,"endOffset":84773,"count":0}],"isBlockCoverage":false},{"functionName":"CastErrorImplementation","ranges":[{"startOffset":84804,"endOffset":84873,"count":0}],"isBlockCoverage":false},{"functionName":"RuntimeError","ranges":[{"startOffset":84893,"endOffset":84951,"count":0}],"isBlockCoverage":false},{"functionName":"TypeImpl","ranges":[{"startOffset":84967,"endOffset":85065,"count":0}],"isBlockCoverage":false},{"functionName":"JsLinkedHashMap","ranges":[{"startOffset":85088,"endOffset":85289,"count":1}],"isBlockCoverage":true},{"functionName":"JsLinkedHashMap_values_closure","ranges":[{"startOffset":85327,"endOffset":85401,"count":0}],"isBlockCoverage":false},{"functionName":"LinkedHashMapCell","ranges":[{"startOffset":85426,"endOffset":85584,"count":1}],"isBlockCoverage":true},{"functionName":"LinkedHashMapKeyIterable","ranges":[{"startOffset":85616,"endOffset":85720,"count":0}],"isBlockCoverage":false},{"functionName":"LinkedHashMapKeyIterator","ranges":[{"startOffset":85752,"endOffset":85950,"count":0}],"isBlockCoverage":false},{"functionName":"initHooks_closure","ranges":[{"startOffset":85975,"endOffset":86037,"count":1}],"isBlockCoverage":true},{"functionName":"initHooks_closure0","ranges":[{"startOffset":86063,"endOffset":86133,"count":1}],"isBlockCoverage":true},{"functionName":"initHooks_closure1","ranges":[{"startOffset":86159,"endOffset":86231,"count":1}],"isBlockCoverage":true},{"functionName":"JSSyntaxRegExp","ranges":[{"startOffset":86253,"endOffset":86424,"count":0}],"isBlockCoverage":false},{"functionName":"_MatchImplementation","ranges":[{"startOffset":86452,"endOffset":86517,"count":0}],"isBlockCoverage":false},{"functionName":"_AllMatchesIterable","ranges":[{"startOffset":86544,"endOffset":86674,"count":0}],"isBlockCoverage":false},{"functionName":"_AllMatchesIterator","ranges":[{"startOffset":86701,"endOffset":86887,"count":0}],"isBlockCoverage":false},{"functionName":"StringMatch","ranges":[{"startOffset":86906,"endOffset":86990,"count":0}],"isBlockCoverage":false},{"functionName":"_StringAllMatchesIterable","ranges":[{"startOffset":87023,"endOffset":87163,"count":0}],"isBlockCoverage":false},{"functionName":"_StringAllMatchesIterator","ranges":[{"startOffset":87196,"endOffset":87384,"count":0}],"isBlockCoverage":false},{"functionName":"_ensureNativeList","ranges":[{"startOffset":87409,"endOffset":87450,"count":0}],"isBlockCoverage":false},{"functionName":"NativeInt8List__create1","ranges":[{"startOffset":87481,"endOffset":87535,"count":0}],"isBlockCoverage":false},{"functionName":"_checkValidIndex","ranges":[{"startOffset":87559,"endOffset":87716,"count":1},{"startOffset":87653,"endOffset":87710,"count":0}],"isBlockCoverage":true},{"functionName":"_checkValidRange","ranges":[{"startOffset":87740,"endOffset":88027,"count":0}],"isBlockCoverage":false},{"functionName":"NativeByteBuffer","ranges":[{"startOffset":88051,"endOffset":88086,"count":0}],"isBlockCoverage":false},{"functionName":"NativeTypedData","ranges":[{"startOffset":88109,"endOffset":88143,"count":0}],"isBlockCoverage":false},{"functionName":"NativeTypedArray","ranges":[{"startOffset":88167,"endOffset":88202,"count":0}],"isBlockCoverage":false},{"functionName":"NativeTypedArrayOfDouble","ranges":[{"startOffset":88234,"endOffset":88277,"count":0}],"isBlockCoverage":false},{"functionName":"NativeTypedArrayOfInt","ranges":[{"startOffset":88306,"endOffset":88346,"count":0}],"isBlockCoverage":false},{"functionName":"NativeInt16List","ranges":[{"startOffset":88369,"endOffset":88403,"count":0}],"isBlockCoverage":false},{"functionName":"NativeInt32List","ranges":[{"startOffset":88426,"endOffset":88460,"count":0}],"isBlockCoverage":false},{"functionName":"NativeInt8List","ranges":[{"startOffset":88482,"endOffset":88515,"count":0}],"isBlockCoverage":false},{"functionName":"NativeUint16List","ranges":[{"startOffset":88539,"endOffset":88574,"count":0}],"isBlockCoverage":false},{"functionName":"NativeUint32List","ranges":[{"startOffset":88598,"endOffset":88633,"count":0}],"isBlockCoverage":false},{"functionName":"NativeUint8ClampedList","ranges":[{"startOffset":88663,"endOffset":88704,"count":0}],"isBlockCoverage":false},{"functionName":"NativeUint8List","ranges":[{"startOffset":88727,"endOffset":88761,"count":0}],"isBlockCoverage":false},{"functionName":"_NativeTypedArrayOfDouble_NativeTypedArray_ListMixin","ranges":[{"startOffset":88821,"endOffset":88892,"count":0}],"isBlockCoverage":false},{"functionName":"_NativeTypedArrayOfDouble_NativeTypedArray_ListMixin_FixedLengthListMixin","ranges":[{"startOffset":88973,"endOffset":89065,"count":0}],"isBlockCoverage":false},{"functionName":"_NativeTypedArrayOfInt_NativeTypedArray_ListMixin","ranges":[{"startOffset":89122,"endOffset":89190,"count":0}],"isBlockCoverage":false},{"functionName":"_NativeTypedArrayOfInt_NativeTypedArray_ListMixin_FixedLengthListMixin","ranges":[{"startOffset":89268,"endOffset":89357,"count":0}],"isBlockCoverage":false},{"functionName":"extractKeys","ranges":[{"startOffset":89376,"endOffset":89483,"count":0}],"isBlockCoverage":false},{"functionName":"printString","ranges":[{"startOffset":89502,"endOffset":89949,"count":0}],"isBlockCoverage":false},{"functionName":"makeDispatchRecord","ranges":[{"startOffset":89987,"endOffset":90120,"count":1}],"isBlockCoverage":true},{"functionName":"getNativeInterceptor","ranges":[{"startOffset":90148,"endOffset":91834,"count":1},{"startOffset":90602,"endOffset":90616,"count":0},{"startOffset":90731,"endOffset":90881,"count":0},{"startOffset":90889,"endOffset":90978,"count":0},{"startOffset":90979,"endOffset":91030,"count":0},{"startOffset":91071,"endOffset":91833,"count":0}],"isBlockCoverage":true},{"functionName":"JSArray_JSArray$fixed","ranges":[{"startOffset":91863,"endOffset":92096,"count":0}],"isBlockCoverage":false},{"functionName":"JSArray_JSArray$markFixed","ranges":[{"startOffset":92129,"endOffset":92239,"count":0}],"isBlockCoverage":false},{"functionName":"JSArray_markFixedList","ranges":[{"startOffset":92268,"endOffset":92371,"count":1}],"isBlockCoverage":true},{"functionName":"JSArray_markUnmodifiableList","ranges":[{"startOffset":92407,"endOffset":92516,"count":0}],"isBlockCoverage":false},{"functionName":"JSString__isWhitespace","ranges":[{"startOffset":92546,"endOffset":93301,"count":0}],"isBlockCoverage":false},{"functionName":"JSString__skipLeadingWhitespace","ranges":[{"startOffset":93340,"endOffset":93658,"count":0}],"isBlockCoverage":false},{"functionName":"JSString__skipTrailingWhitespace","ranges":[{"startOffset":93698,"endOffset":94027,"count":0}],"isBlockCoverage":false},{"functionName":"getInterceptor$","ranges":[{"startOffset":94050,"endOffset":94813,"count":1},{"startOffset":94193,"endOffset":94238,"count":0},{"startOffset":94426,"endOffset":94452,"count":0},{"startOffset":94569,"endOffset":94695,"count":0}],"isBlockCoverage":true},{"functionName":"getInterceptor$ansx","ranges":[{"startOffset":94840,"endOffset":95426,"count":0}],"isBlockCoverage":false},{"functionName":"getInterceptor$asx","ranges":[{"startOffset":95452,"endOffset":95962,"count":1},{"startOffset":95520,"endOffset":95548,"count":0},{"startOffset":95585,"endOffset":95601,"count":0},{"startOffset":95718,"endOffset":95844,"count":0},{"startOffset":95909,"endOffset":95961,"count":0}],"isBlockCoverage":true},{"functionName":"getInterceptor$ax","ranges":[{"startOffset":95987,"endOffset":96421,"count":1},{"startOffset":96044,"endOffset":96060,"count":0},{"startOffset":96177,"endOffset":96303,"count":0},{"startOffset":96368,"endOffset":96420,"count":0}],"isBlockCoverage":true},{"functionName":"getInterceptor$n","ranges":[{"startOffset":96445,"endOffset":96718,"count":0}],"isBlockCoverage":false},{"functionName":"getInterceptor$s","ranges":[{"startOffset":96742,"endOffset":97015,"count":1},{"startOffset":96838,"endOffset":97014,"count":0}],"isBlockCoverage":true},{"functionName":"getInterceptor$x","ranges":[{"startOffset":97039,"endOffset":97396,"count":1},{"startOffset":97096,"endOffset":97112,"count":0},{"startOffset":97152,"endOffset":97278,"count":0}],"isBlockCoverage":true},{"functionName":"get$hashCode$","ranges":[{"startOffset":97417,"endOffset":97508,"count":0}],"isBlockCoverage":false},{"functionName":"get$iterator$ax","ranges":[{"startOffset":97531,"endOffset":97624,"count":0}],"isBlockCoverage":false},{"functionName":"get$length$asx","ranges":[{"startOffset":97646,"endOffset":97738,"count":1}],"isBlockCoverage":true},{"functionName":"get$onClick$x","ranges":[{"startOffset":97759,"endOffset":97850,"count":1}],"isBlockCoverage":true},{"functionName":"$add$ansx","ranges":[{"startOffset":97867,"endOffset":98056,"count":0}],"isBlockCoverage":false},{"functionName":"$eq$","ranges":[{"startOffset":98068,"endOffset":98298,"count":1},{"startOffset":98129,"endOffset":98147,"count":0},{"startOffset":98232,"endOffset":98297,"count":0}],"isBlockCoverage":true},{"functionName":"$gt$n","ranges":[{"startOffset":98311,"endOffset":98496,"count":1},{"startOffset":98429,"endOffset":98495,"count":0}],"isBlockCoverage":true},{"functionName":"$index$asx","ranges":[{"startOffset":98514,"endOffset":98872,"count":1},{"startOffset":98581,"endOffset":98800,"count":0}],"isBlockCoverage":true},{"functionName":"$indexSet$ax","ranges":[{"startOffset":98892,"endOffset":98998,"count":1}],"isBlockCoverage":true},{"functionName":"_codeUnitAt$1$s","ranges":[{"startOffset":99021,"endOffset":99122,"count":1}],"isBlockCoverage":true},{"functionName":"_removeChild$1$x","ranges":[{"startOffset":99146,"endOffset":99248,"count":0}],"isBlockCoverage":false},{"functionName":"_removeEventListener$3$x","ranges":[{"startOffset":99280,"endOffset":99406,"count":0}],"isBlockCoverage":false},{"functionName":"addEventListener$3$x","ranges":[{"startOffset":99434,"endOffset":99556,"count":1}],"isBlockCoverage":true},{"functionName":"codeUnitAt$1$s","ranges":[{"startOffset":99578,"endOffset":99678,"count":0}],"isBlockCoverage":false},{"functionName":"contains$1$asx","ranges":[{"startOffset":99700,"endOffset":99800,"count":0}],"isBlockCoverage":false},{"functionName":"elementAt$1$ax","ranges":[{"startOffset":99822,"endOffset":99922,"count":0}],"isBlockCoverage":false},{"functionName":"endsWith$1$s","ranges":[{"startOffset":99942,"endOffset":100040,"count":0}],"isBlockCoverage":false},{"functionName":"fillRange$3$x","ranges":[{"startOffset":100061,"endOffset":100176,"count":1}],"isBlockCoverage":true},{"functionName":"indexOf$2$s","ranges":[{"startOffset":100195,"endOffset":100300,"count":0}],"isBlockCoverage":false},{"functionName":"matchAsPrefix$2$s","ranges":[{"startOffset":100325,"endOffset":100436,"count":0}],"isBlockCoverage":false},{"functionName":"noSuchMethod$1$","ranges":[{"startOffset":100459,"endOffset":100560,"count":0}],"isBlockCoverage":false},{"functionName":"padRight$1$s","ranges":[{"startOffset":100580,"endOffset":100678,"count":0}],"isBlockCoverage":false},{"functionName":"postMessage$3$x","ranges":[{"startOffset":100701,"endOffset":100818,"count":1}],"isBlockCoverage":true},{"functionName":"replaceFirst$2$s","ranges":[{"startOffset":100842,"endOffset":100952,"count":0}],"isBlockCoverage":false},{"functionName":"replaceRange$3$asx","ranges":[{"startOffset":100978,"endOffset":101098,"count":0}],"isBlockCoverage":false},{"functionName":"startsWith$1$s","ranges":[{"startOffset":101120,"endOffset":101220,"count":0}],"isBlockCoverage":false},{"functionName":"startsWith$2$s","ranges":[{"startOffset":101242,"endOffset":101350,"count":0}],"isBlockCoverage":false},{"functionName":"substring$1$s","ranges":[{"startOffset":101371,"endOffset":101470,"count":0}],"isBlockCoverage":false},{"functionName":"substring$2$s","ranges":[{"startOffset":101491,"endOffset":101598,"count":1}],"isBlockCoverage":true},{"functionName":"toString$0$","ranges":[{"startOffset":101617,"endOffset":101706,"count":0}],"isBlockCoverage":false},{"functionName":"trim$0$s","ranges":[{"startOffset":101722,"endOffset":101808,"count":0}],"isBlockCoverage":false},{"functionName":"waitUntilDone$0$x","ranges":[{"startOffset":101833,"endOffset":101928,"count":0}],"isBlockCoverage":false},{"functionName":"Interceptor","ranges":[{"startOffset":101947,"endOffset":101977,"count":0}],"isBlockCoverage":false},{"functionName":"JSBool","ranges":[{"startOffset":101991,"endOffset":102016,"count":0}],"isBlockCoverage":false},{"functionName":"JSNull","ranges":[{"startOffset":102030,"endOffset":102055,"count":0}],"isBlockCoverage":false},{"functionName":"JavaScriptObject","ranges":[{"startOffset":102079,"endOffset":102114,"count":0}],"isBlockCoverage":false},{"functionName":"PlainJavaScriptObject","ranges":[{"startOffset":102143,"endOffset":102183,"count":0}],"isBlockCoverage":false},{"functionName":"UnknownJavaScriptObject","ranges":[{"startOffset":102214,"endOffset":102256,"count":0}],"isBlockCoverage":false},{"functionName":"JavaScriptFunction","ranges":[{"startOffset":102282,"endOffset":102319,"count":0}],"isBlockCoverage":false},{"functionName":"JSArray","ranges":[{"startOffset":102334,"endOffset":102383,"count":0}],"isBlockCoverage":true},{"functionName":"JSUnmodifiableArray","ranges":[{"startOffset":102410,"endOffset":102471,"count":0}],"isBlockCoverage":false},{"functionName":"ArrayIterator","ranges":[{"startOffset":102492,"endOffset":102698,"count":0}],"isBlockCoverage":false},{"functionName":"JSNumber","ranges":[{"startOffset":102714,"endOffset":102741,"count":0}],"isBlockCoverage":false},{"functionName":"JSInt","ranges":[{"startOffset":102754,"endOffset":102778,"count":0}],"isBlockCoverage":true},{"functionName":"JSDouble","ranges":[{"startOffset":102794,"endOffset":102821,"count":0}],"isBlockCoverage":false},{"functionName":"JSString","ranges":[{"startOffset":102837,"endOffset":102864,"count":0}],"isBlockCoverage":true},{"functionName":"_AsyncRun__initializeScheduleImmediate","ranges":[{"startOffset":102922,"endOffset":103739,"count":1},{"startOffset":103025,"endOffset":103089,"count":0},{"startOffset":103548,"endOffset":103738,"count":0}],"isBlockCoverage":true},{"functionName":"_AsyncRun__scheduleImmediateJsOverride","ranges":[{"startOffset":103785,"endOffset":103984,"count":0}],"isBlockCoverage":false},{"functionName":"_AsyncRun__scheduleImmediateWithSetImmediate","ranges":[{"startOffset":104036,"endOffset":104236,"count":0}],"isBlockCoverage":false},{"functionName":"_AsyncRun__scheduleImmediateWithTimer","ranges":[{"startOffset":104281,"endOffset":104400,"count":0}],"isBlockCoverage":false},{"functionName":"Timer__createTimer","ranges":[{"startOffset":104426,"endOffset":104694,"count":0}],"isBlockCoverage":false},{"functionName":"Timer__createPeriodicTimer","ranges":[{"startOffset":104728,"endOffset":105021,"count":1},{"startOffset":104985,"endOffset":104988,"count":0}],"isBlockCoverage":true},{"functionName":"_TimerImpl$","ranges":[{"startOffset":105040,"endOffset":105179,"count":0}],"isBlockCoverage":false},{"functionName":"_TimerImpl$periodic","ranges":[{"startOffset":105206,"endOffset":105354,"count":1}],"isBlockCoverage":true},{"functionName":"_makeAsyncAwaitCompleter","ranges":[{"startOffset":105386,"endOffset":105529,"count":1}],"isBlockCoverage":true},{"functionName":"_asyncStartSync","ranges":[{"startOffset":105552,"endOffset":105848,"count":1}],"isBlockCoverage":true},{"functionName":"_asyncAwait","ranges":[{"startOffset":105867,"endOffset":106009,"count":1}],"isBlockCoverage":true},{"functionName":"_asyncReturn","ranges":[{"startOffset":106029,"endOffset":106143,"count":1}],"isBlockCoverage":true},{"functionName":"_asyncRethrow","ranges":[{"startOffset":106164,"endOffset":106332,"count":0}],"isBlockCoverage":false},{"functionName":"_awaitOnObject","ranges":[{"startOffset":106354,"endOffset":107170,"count":1},{"startOffset":106791,"endOffset":107164,"count":0}],"isBlockCoverage":true},{"functionName":"_wrapJsFunctionForAsync","ranges":[{"startOffset":107201,"endOffset":107687,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":107246,"endOffset":107537,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":107283,"endOffset":107528,"count":1},{"startOffset":107425,"endOffset":107518,"count":0}],"isBlockCoverage":true},{"functionName":"_Future$zoneValue","ranges":[{"startOffset":107712,"endOffset":107914,"count":0}],"isBlockCoverage":false},{"functionName":"_Future__chainForeignFuture","ranges":[{"startOffset":107949,"endOffset":108400,"count":0}],"isBlockCoverage":false},{"functionName":"_Future__chainCoreFuture","ranges":[{"startOffset":108432,"endOffset":109081,"count":0}],"isBlockCoverage":false},{"functionName":"_Future__propagateToListeners","ranges":[{"startOffset":109118,"endOffset":112741,"count":1},{"startOffset":109462,"endOffset":109649,"count":0},{"startOffset":110172,"endOffset":110183,"count":0},{"startOffset":110194,"endOffset":110220,"count":0},{"startOffset":110323,"endOffset":110467,"count":0},{"startOffset":110515,"endOffset":110754,"count":0},{"startOffset":110835,"endOffset":110858,"count":0},{"startOffset":110970,"endOffset":111079,"count":0},{"startOffset":111261,"endOffset":111383,"count":0},{"startOffset":111427,"endOffset":111453,"count":0},{"startOffset":111547,"endOffset":112035,"count":0},{"startOffset":112531,"endOffset":112674,"count":0}],"isBlockCoverage":true},{"functionName":"_registerErrorHandler","ranges":[{"startOffset":112770,"endOffset":113325,"count":0}],"isBlockCoverage":false},{"functionName":"_microtaskLoop","ranges":[{"startOffset":113347,"endOffset":113621,"count":1}],"isBlockCoverage":true},{"functionName":"_startMicrotaskLoop","ranges":[{"startOffset":113648,"endOffset":113983,"count":1},{"startOffset":113877,"endOffset":113969,"count":0}],"isBlockCoverage":true},{"functionName":"_scheduleAsyncCallback","ranges":[{"startOffset":114013,"endOffset":114484,"count":1},{"startOffset":114386,"endOffset":114478,"count":0}],"isBlockCoverage":true},{"functionName":"_schedulePriorityAsyncCallback","ranges":[{"startOffset":114522,"endOffset":115197,"count":0}],"isBlockCoverage":false},{"functionName":"scheduleMicrotask","ranges":[{"startOffset":115222,"endOffset":115926,"count":0}],"isBlockCoverage":false},{"functionName":"StreamIterator_StreamIterator","ranges":[{"startOffset":115963,"endOffset":116090,"count":0}],"isBlockCoverage":false},{"functionName":"StreamController_StreamController","ranges":[{"startOffset":116131,"endOffset":116365,"count":1}],"isBlockCoverage":true},{"functionName":"_runGuarded","ranges":[{"startOffset":116384,"endOffset":116791,"count":1},{"startOffset":116615,"endOffset":116785,"count":0}],"isBlockCoverage":true},{"functionName":"_nullDataHandler","ranges":[{"startOffset":116815,"endOffset":116838,"count":0}],"isBlockCoverage":false},{"functionName":"_nullErrorHandler","ranges":[{"startOffset":116863,"endOffset":117021,"count":0}],"isBlockCoverage":false},{"functionName":"_nullDoneHandler","ranges":[{"startOffset":117045,"endOffset":117063,"count":0}],"isBlockCoverage":false},{"functionName":"Timer_Timer$periodic","ranges":[{"startOffset":117091,"endOffset":117502,"count":1},{"startOffset":117293,"endOffset":117345,"count":0}],"isBlockCoverage":true},{"functionName":"_ZoneSpecification$","ranges":[{"startOffset":117529,"endOffset":117972,"count":1}],"isBlockCoverage":true},{"functionName":"_parentDelegate","ranges":[{"startOffset":117995,"endOffset":118126,"count":1},{"startOffset":118068,"endOffset":118125,"count":0}],"isBlockCoverage":true},{"functionName":"_rootHandleUncaughtError","ranges":[{"startOffset":118158,"endOffset":118395,"count":0}],"isBlockCoverage":false},{"functionName":"_rootRun","ranges":[{"startOffset":118411,"endOffset":118914,"count":1},{"startOffset":118731,"endOffset":118749,"count":0},{"startOffset":118859,"endOffset":118860,"count":0}],"isBlockCoverage":true},{"functionName":"_rootRunUnary","ranges":[{"startOffset":118935,"endOffset":119510,"count":1},{"startOffset":119455,"endOffset":119456,"count":0}],"isBlockCoverage":true},{"functionName":"_rootRunBinary","ranges":[{"startOffset":119532,"endOffset":120183,"count":0}],"isBlockCoverage":false},{"functionName":"_rootRegisterCallback","ranges":[{"startOffset":120212,"endOffset":120314,"count":1}],"isBlockCoverage":true},{"functionName":"_rootRegisterUnaryCallback","ranges":[{"startOffset":120348,"endOffset":120466,"count":1}],"isBlockCoverage":true},{"functionName":"_rootRegisterBinaryCallback","ranges":[{"startOffset":120501,"endOffset":120627,"count":1}],"isBlockCoverage":true},{"functionName":"_rootErrorCallback","ranges":[{"startOffset":120653,"endOffset":120783,"count":0}],"isBlockCoverage":false},{"functionName":"_rootScheduleMicrotask","ranges":[{"startOffset":120813,"endOffset":121143,"count":1},{"startOffset":121071,"endOffset":121101,"count":0}],"isBlockCoverage":true},{"functionName":"_rootCreateTimer","ranges":[{"startOffset":121167,"endOffset":121430,"count":0}],"isBlockCoverage":false},{"functionName":"_rootCreatePeriodicTimer","ranges":[{"startOffset":121462,"endOffset":121766,"count":1}],"isBlockCoverage":true},{"functionName":"_rootPrint","ranges":[{"startOffset":121784,"endOffset":121874,"count":0}],"isBlockCoverage":false},{"functionName":"_printToZone","ranges":[{"startOffset":121894,"endOffset":121956,"count":0}],"isBlockCoverage":false},{"functionName":"_rootFork","ranges":[{"startOffset":121973,"endOffset":123508,"count":1},{"startOffset":122278,"endOffset":122319,"count":0},{"startOffset":123450,"endOffset":123483,"count":0}],"isBlockCoverage":true},{"functionName":"runZoned","ranges":[{"startOffset":123524,"endOffset":125443,"count":1},{"startOffset":124003,"endOffset":124288,"count":0},{"startOffset":124511,"endOffset":125049,"count":0},{"startOffset":125155,"endOffset":125442,"count":0}],"isBlockCoverage":true},{"functionName":"_runZoned","ranges":[{"startOffset":125460,"endOffset":125673,"count":1}],"isBlockCoverage":true},{"functionName":"_AsyncRun__initializeScheduleImmediate_internalCallback","ranges":[{"startOffset":125736,"endOffset":125836,"count":1}],"isBlockCoverage":true},{"functionName":"_AsyncRun__initializeScheduleImmediate_closure","ranges":[{"startOffset":125890,"endOffset":126032,"count":1}],"isBlockCoverage":true},{"functionName":"_AsyncRun__scheduleImmediateJsOverride_internalCallback","ranges":[{"startOffset":126095,"endOffset":126197,"count":0}],"isBlockCoverage":false},{"functionName":"_AsyncRun__scheduleImmediateWithSetImmediate_internalCallback","ranges":[{"startOffset":126266,"endOffset":126374,"count":0}],"isBlockCoverage":false},{"functionName":"_TimerImpl","ranges":[{"startOffset":126392,"endOffset":126443,"count":1}],"isBlockCoverage":true},{"functionName":"_TimerImpl_internalCallback","ranges":[{"startOffset":126478,"endOffset":126579,"count":0}],"isBlockCoverage":false},{"functionName":"_TimerImpl$periodic_closure","ranges":[{"startOffset":126614,"endOffset":126784,"count":1}],"isBlockCoverage":true},{"functionName":"_AsyncAwaitCompleter","ranges":[{"startOffset":126812,"endOffset":126934,"count":1}],"isBlockCoverage":true},{"functionName":"_AsyncAwaitCompleter_complete_closure","ranges":[{"startOffset":126979,"endOffset":127087,"count":0}],"isBlockCoverage":false},{"functionName":"_AsyncAwaitCompleter_completeError_closure","ranges":[{"startOffset":127137,"endOffset":127270,"count":0}],"isBlockCoverage":false},{"functionName":"_awaitOnObject_closure","ranges":[{"startOffset":127300,"endOffset":127373,"count":1}],"isBlockCoverage":true},{"functionName":"_awaitOnObject_closure0","ranges":[{"startOffset":127404,"endOffset":127478,"count":1}],"isBlockCoverage":true},{"functionName":"_wrapJsFunctionForAsync_closure","ranges":[{"startOffset":127517,"endOffset":127597,"count":1}],"isBlockCoverage":true},{"functionName":"Future","ranges":[{"startOffset":127611,"endOffset":127636,"count":0}],"isBlockCoverage":false},{"functionName":"_Completer","ranges":[{"startOffset":127654,"endOffset":127683,"count":0}],"isBlockCoverage":false},{"functionName":"_AsyncCompleter","ranges":[{"startOffset":127706,"endOffset":127791,"count":1}],"isBlockCoverage":true},{"functionName":"_SyncCompleter","ranges":[{"startOffset":127813,"endOffset":127897,"count":1}],"isBlockCoverage":true},{"functionName":"_FutureListener","ranges":[{"startOffset":127920,"endOffset":128132,"count":1}],"isBlockCoverage":true},{"functionName":"_Future","ranges":[{"startOffset":128147,"endOffset":128297,"count":1}],"isBlockCoverage":true},{"functionName":"_Future__addListener_closure","ranges":[{"startOffset":128333,"endOffset":128435,"count":1}],"isBlockCoverage":true},{"functionName":"_Future__prependListeners_closure","ranges":[{"startOffset":128476,"endOffset":128581,"count":0}],"isBlockCoverage":false},{"functionName":"_Future__chainForeignFuture_closure","ranges":[{"startOffset":128624,"endOffset":128704,"count":0}],"isBlockCoverage":false},{"functionName":"_Future__chainForeignFuture_closure0","ranges":[{"startOffset":128748,"endOffset":128829,"count":0}],"isBlockCoverage":false},{"functionName":"_Future__chainForeignFuture_closure1","ranges":[{"startOffset":128873,"endOffset":129000,"count":0}],"isBlockCoverage":false},{"functionName":"_Future__asyncComplete_closure","ranges":[{"startOffset":129038,"endOffset":129139,"count":1}],"isBlockCoverage":true},{"functionName":"_Future__chainFuture_closure","ranges":[{"startOffset":129175,"endOffset":129274,"count":0}],"isBlockCoverage":false},{"functionName":"_Future__asyncCompleteError_closure","ranges":[{"startOffset":129317,"endOffset":129455,"count":0}],"isBlockCoverage":false},{"functionName":"_Future__propagateToListeners_handleWhenCompleteCallback","ranges":[{"startOffset":129519,"endOffset":129716,"count":0}],"isBlockCoverage":false},{"functionName":"_Future__propagateToListeners_handleWhenCompleteCallback_closure","ranges":[{"startOffset":129788,"endOffset":129905,"count":0}],"isBlockCoverage":false},{"functionName":"_Future__propagateToListeners_handleValueCallback","ranges":[{"startOffset":129962,"endOffset":130120,"count":1}],"isBlockCoverage":true},{"functionName":"_Future__propagateToListeners_handleError","ranges":[{"startOffset":130169,"endOffset":130313,"count":0}],"isBlockCoverage":false},{"functionName":"_AsyncCallbackEntry","ranges":[{"startOffset":130340,"endOffset":130430,"count":1}],"isBlockCoverage":true},{"functionName":"Stream","ranges":[{"startOffset":130444,"endOffset":130469,"count":0}],"isBlockCoverage":false},{"functionName":"Stream_pipe_closure","ranges":[{"startOffset":130496,"endOffset":130568,"count":1}],"isBlockCoverage":true},{"functionName":"Stream_length_closure","ranges":[{"startOffset":130597,"endOffset":130690,"count":0}],"isBlockCoverage":false},{"functionName":"Stream_length_closure0","ranges":[{"startOffset":130720,"endOffset":130815,"count":0}],"isBlockCoverage":false},{"functionName":"StreamSubscription","ranges":[{"startOffset":130841,"endOffset":130878,"count":0}],"isBlockCoverage":false},{"functionName":"StreamTransformerBase","ranges":[{"startOffset":130907,"endOffset":130947,"count":0}],"isBlockCoverage":false},{"functionName":"_StreamController","ranges":[{"startOffset":130972,"endOffset":131008,"count":0}],"isBlockCoverage":false},{"functionName":"_StreamController__subscribe_closure","ranges":[{"startOffset":131052,"endOffset":131132,"count":1}],"isBlockCoverage":true},{"functionName":"_StreamController__recordCancel_complete","ranges":[{"startOffset":131180,"endOffset":131264,"count":0}],"isBlockCoverage":false},{"functionName":"_SyncStreamControllerDispatch","ranges":[{"startOffset":131301,"endOffset":131349,"count":0}],"isBlockCoverage":false},{"functionName":"_SyncStreamController","ranges":[{"startOffset":131378,"endOffset":131643,"count":1}],"isBlockCoverage":true},{"functionName":"_ControllerStream","ranges":[{"startOffset":131668,"endOffset":131760,"count":1}],"isBlockCoverage":true},{"functionName":"_ControllerSubscription","ranges":[{"startOffset":131791,"endOffset":132051,"count":1}],"isBlockCoverage":true},{"functionName":"_StreamSinkWrapper","ranges":[{"startOffset":132077,"endOffset":132173,"count":1}],"isBlockCoverage":true},{"functionName":"_AddStreamState_cancel_closure","ranges":[{"startOffset":132211,"endOffset":132285,"count":0}],"isBlockCoverage":false},{"functionName":"_BufferingStreamSubscription","ranges":[{"startOffset":132321,"endOffset":132368,"count":0}],"isBlockCoverage":false},{"functionName":"_BufferingStreamSubscription__sendError_sendError","ranges":[{"startOffset":132425,"endOffset":132577,"count":0}],"isBlockCoverage":false},{"functionName":"_BufferingStreamSubscription__sendDone_sendDone","ranges":[{"startOffset":132632,"endOffset":132723,"count":0}],"isBlockCoverage":false},{"functionName":"_StreamImpl","ranges":[{"startOffset":132742,"endOffset":132772,"count":0}],"isBlockCoverage":false},{"functionName":"_DelayedEvent","ranges":[{"startOffset":132793,"endOffset":132825,"count":0}],"isBlockCoverage":false},{"functionName":"_DelayedData","ranges":[{"startOffset":132845,"endOffset":132950,"count":0}],"isBlockCoverage":false},{"functionName":"_DelayedError","ranges":[{"startOffset":132971,"endOffset":133084,"count":0}],"isBlockCoverage":false},{"functionName":"_DelayedDone","ranges":[{"startOffset":133104,"endOffset":133135,"count":1}],"isBlockCoverage":true},{"functionName":"_PendingEvents","ranges":[{"startOffset":133157,"endOffset":133190,"count":0}],"isBlockCoverage":false},{"functionName":"_PendingEvents_schedule_closure","ranges":[{"startOffset":133229,"endOffset":133334,"count":0}],"isBlockCoverage":false},{"functionName":"_StreamImplEvents","ranges":[{"startOffset":133359,"endOffset":133515,"count":0}],"isBlockCoverage":false},{"functionName":"_DoneStreamSubscription","ranges":[{"startOffset":133546,"endOffset":133698,"count":0}],"isBlockCoverage":false},{"functionName":"_StreamIterator","ranges":[{"startOffset":133721,"endOffset":133881,"count":0}],"isBlockCoverage":false},{"functionName":"_EmptyStream","ranges":[{"startOffset":133901,"endOffset":133955,"count":0}],"isBlockCoverage":false},{"functionName":"Timer","ranges":[{"startOffset":133968,"endOffset":133992,"count":0}],"isBlockCoverage":false},{"functionName":"AsyncError","ranges":[{"startOffset":134010,"endOffset":134096,"count":0}],"isBlockCoverage":false},{"functionName":"_ZoneFunction","ranges":[{"startOffset":134117,"endOffset":134229,"count":1}],"isBlockCoverage":true},{"functionName":"ZoneSpecification","ranges":[{"startOffset":134254,"endOffset":134290,"count":0}],"isBlockCoverage":false},{"functionName":"_ZoneSpecification","ranges":[{"startOffset":134316,"endOffset":134791,"count":1}],"isBlockCoverage":true},{"functionName":"ZoneDelegate","ranges":[{"startOffset":134811,"endOffset":134842,"count":0}],"isBlockCoverage":false},{"functionName":"Zone","ranges":[{"startOffset":134854,"endOffset":134877,"count":0}],"isBlockCoverage":false},{"functionName":"_ZoneDelegate","ranges":[{"startOffset":134898,"endOffset":134967,"count":0}],"isBlockCoverage":false},{"functionName":"_Zone","ranges":[{"startOffset":134980,"endOffset":135004,"count":0}],"isBlockCoverage":false},{"functionName":"_CustomZone","ranges":[{"startOffset":135023,"endOffset":135402,"count":1}],"isBlockCoverage":true},{"functionName":"_CustomZone_bindCallback_closure","ranges":[{"startOffset":135442,"endOffset":135573,"count":0}],"isBlockCoverage":false},{"functionName":"_CustomZone_bindUnaryCallback_closure","ranges":[{"startOffset":135618,"endOffset":135785,"count":1}],"isBlockCoverage":true},{"functionName":"_CustomZone_bindCallbackGuarded_closure","ranges":[{"startOffset":135832,"endOffset":135947,"count":1}],"isBlockCoverage":true},{"functionName":"_CustomZone_bindUnaryCallbackGuarded_closure","ranges":[{"startOffset":135999,"endOffset":136142,"count":1}],"isBlockCoverage":true},{"functionName":"_rootHandleUncaughtError_closure","ranges":[{"startOffset":136182,"endOffset":136291,"count":0}],"isBlockCoverage":false},{"functionName":"_RootZone","ranges":[{"startOffset":136308,"endOffset":136336,"count":1}],"isBlockCoverage":true},{"functionName":"_RootZone_bindCallback_closure","ranges":[{"startOffset":136374,"endOffset":136494,"count":0}],"isBlockCoverage":false},{"functionName":"_RootZone_bindCallbackGuarded_closure","ranges":[{"startOffset":136539,"endOffset":136643,"count":0}],"isBlockCoverage":false},{"functionName":"_RootZone_bindUnaryCallbackGuarded_closure","ranges":[{"startOffset":136693,"endOffset":136825,"count":0}],"isBlockCoverage":false},{"functionName":"runZoned_closure","ranges":[{"startOffset":136849,"endOffset":136910,"count":1}],"isBlockCoverage":true},{"functionName":"HashMap_HashMap","ranges":[{"startOffset":136933,"endOffset":136996,"count":1}],"isBlockCoverage":true},{"functionName":"_HashMap__getTableEntry","ranges":[{"startOffset":137027,"endOffset":137130,"count":0}],"isBlockCoverage":false},{"functionName":"_HashMap__setTableEntry","ranges":[{"startOffset":137161,"endOffset":137288,"count":0}],"isBlockCoverage":false},{"functionName":"_HashMap__newHashTable","ranges":[{"startOffset":137318,"endOffset":137510,"count":0}],"isBlockCoverage":false},{"functionName":"LinkedHashMap_LinkedHashMap$_literal","ranges":[{"startOffset":137554,"endOffset":137777,"count":0}],"isBlockCoverage":false},{"functionName":"LinkedHashMap_LinkedHashMap$_empty","ranges":[{"startOffset":137819,"endOffset":137889,"count":1}],"isBlockCoverage":true},{"functionName":"LinkedHashMap__makeEmpty","ranges":[{"startOffset":137921,"endOffset":137989,"count":1}],"isBlockCoverage":true},{"functionName":"LinkedHashSet_LinkedHashSet","ranges":[{"startOffset":138024,"endOffset":138085,"count":1}],"isBlockCoverage":true},{"functionName":"_LinkedHashSet__newHashTable","ranges":[{"startOffset":138121,"endOffset":138287,"count":0}],"isBlockCoverage":false},{"functionName":"IterableBase_iterableToShortString","ranges":[{"startOffset":138329,"endOffset":139079,"count":0}],"isBlockCoverage":false},{"functionName":"IterableBase_iterableToFullString","ranges":[{"startOffset":139120,"endOffset":139770,"count":0}],"isBlockCoverage":false},{"functionName":"_isToStringVisiting","ranges":[{"startOffset":139797,"endOffset":139972,"count":0}],"isBlockCoverage":false},{"functionName":"_iterablePartsToStrings","ranges":[{"startOffset":140003,"endOffset":142710,"count":0}],"isBlockCoverage":false},{"functionName":"MapBase_mapToString","ranges":[{"startOffset":142737,"endOffset":143352,"count":0}],"isBlockCoverage":false},{"functionName":"_HashMap","ranges":[{"startOffset":143368,"endOffset":143571,"count":1}],"isBlockCoverage":true},{"functionName":"_HashMapKeyIterable","ranges":[{"startOffset":143598,"endOffset":143685,"count":0}],"isBlockCoverage":false},{"functionName":"_HashMapKeyIterator","ranges":[{"startOffset":143712,"endOffset":143907,"count":0}],"isBlockCoverage":false},{"functionName":"_LinkedHashSet","ranges":[{"startOffset":143929,"endOffset":144201,"count":1}],"isBlockCoverage":true},{"functionName":"_LinkedHashSetCell","ranges":[{"startOffset":144227,"endOffset":144358,"count":0}],"isBlockCoverage":false},{"functionName":"_LinkedHashSetIterator","ranges":[{"startOffset":144388,"endOffset":144596,"count":0}],"isBlockCoverage":false},{"functionName":"IterableBase","ranges":[{"startOffset":144616,"endOffset":144647,"count":0}],"isBlockCoverage":false},{"functionName":"ListBase","ranges":[{"startOffset":144663,"endOffset":144690,"count":0}],"isBlockCoverage":false},{"functionName":"ListMixin","ranges":[{"startOffset":144707,"endOffset":144735,"count":0}],"isBlockCoverage":false},{"functionName":"MapBase","ranges":[{"startOffset":144750,"endOffset":144776,"count":0}],"isBlockCoverage":false},{"functionName":"MapBase_mapToString_closure","ranges":[{"startOffset":144811,"endOffset":144911,"count":0}],"isBlockCoverage":false},{"functionName":"MapMixin","ranges":[{"startOffset":144927,"endOffset":144954,"count":0}],"isBlockCoverage":false},{"functionName":"_UnmodifiableMapMixin","ranges":[{"startOffset":144983,"endOffset":145023,"count":0}],"isBlockCoverage":false},{"functionName":"MapView","ranges":[{"startOffset":145038,"endOffset":145064,"count":0}],"isBlockCoverage":false},{"functionName":"UnmodifiableMapView","ranges":[{"startOffset":145091,"endOffset":145178,"count":1}],"isBlockCoverage":true},{"functionName":"_SetBase","ranges":[{"startOffset":145194,"endOffset":145221,"count":0}],"isBlockCoverage":false},{"functionName":"_ListBase_Object_ListMixin","ranges":[{"startOffset":145255,"endOffset":145300,"count":0}],"isBlockCoverage":false},{"functionName":"_UnmodifiableMapView_MapView__UnmodifiableMapMixin","ranges":[{"startOffset":145358,"endOffset":145427,"count":0}],"isBlockCoverage":false},{"functionName":"_parseJson","ranges":[{"startOffset":145445,"endOffset":145914,"count":1},{"startOffset":145555,"endOffset":145607,"count":0},{"startOffset":145686,"endOffset":145846,"count":0}],"isBlockCoverage":true},{"functionName":"_convertJsonToDartLazy","ranges":[{"startOffset":145944,"endOffset":146324,"count":1}],"isBlockCoverage":true},{"functionName":"Utf8Decoder__convertIntercepted","ranges":[{"startOffset":146363,"endOffset":146629,"count":1},{"startOffset":146529,"endOffset":146609,"count":0}],"isBlockCoverage":true},{"functionName":"Utf8Decoder__convertInterceptedUint8List","ranges":[{"startOffset":146677,"endOffset":147268,"count":0}],"isBlockCoverage":false},{"functionName":"Utf8Decoder__useTextDecoderChecked","ranges":[{"startOffset":147310,"endOffset":147479,"count":0}],"isBlockCoverage":false},{"functionName":"Utf8Decoder__useTextDecoderUnchecked","ranges":[{"startOffset":147523,"endOffset":147743,"count":0}],"isBlockCoverage":false},{"functionName":"Utf8Decoder__unsafe","ranges":[{"startOffset":147770,"endOffset":148014,"count":0}],"isBlockCoverage":false},{"functionName":"Utf8Decoder__makeDecoder","ranges":[{"startOffset":148046,"endOffset":148262,"count":0}],"isBlockCoverage":false},{"functionName":"_scanOneByteCharacters","ranges":[{"startOffset":148292,"endOffset":148695,"count":1},{"startOffset":148570,"endOffset":148589,"count":0},{"startOffset":148635,"endOffset":148651,"count":0}],"isBlockCoverage":true},{"functionName":"Base64Codec__checkPadding","ranges":[{"startOffset":148728,"endOffset":149339,"count":0}],"isBlockCoverage":false},{"functionName":"JsonUnsupportedObjectError$","ranges":[{"startOffset":149374,"endOffset":149504,"count":0}],"isBlockCoverage":false},{"functionName":"_defaultToEncodable","ranges":[{"startOffset":149531,"endOffset":149587,"count":0}],"isBlockCoverage":false},{"functionName":"_JsonStringStringifier_stringify","ranges":[{"startOffset":149627,"endOffset":149885,"count":1},{"startOffset":149869,"endOffset":149873,"count":0}],"isBlockCoverage":true},{"functionName":"_JsonStringStringifier_printOn","ranges":[{"startOffset":149923,"endOffset":150125,"count":1}],"isBlockCoverage":true},{"functionName":"_JsonMap","ranges":[{"startOffset":150141,"endOffset":150254,"count":1}],"isBlockCoverage":true},{"functionName":"_JsonMapKeyIterable","ranges":[{"startOffset":150281,"endOffset":150355,"count":0}],"isBlockCoverage":false},{"functionName":"AsciiCodec","ranges":[{"startOffset":150373,"endOffset":150435,"count":1}],"isBlockCoverage":true},{"functionName":"_UnicodeSubsetEncoder","ranges":[{"startOffset":150464,"endOffset":150504,"count":0}],"isBlockCoverage":false},{"functionName":"AsciiEncoder","ranges":[{"startOffset":150524,"endOffset":150586,"count":1}],"isBlockCoverage":true},{"functionName":"Base64Codec","ranges":[{"startOffset":150605,"endOffset":150663,"count":1}],"isBlockCoverage":true},{"functionName":"Base64Encoder","ranges":[{"startOffset":150684,"endOffset":150744,"count":1}],"isBlockCoverage":true},{"functionName":"Codec","ranges":[{"startOffset":150757,"endOffset":150781,"count":0}],"isBlockCoverage":false},{"functionName":"_FusedCodec","ranges":[{"startOffset":150800,"endOffset":150919,"count":0}],"isBlockCoverage":false},{"functionName":"Converter","ranges":[{"startOffset":150936,"endOffset":150964,"count":0}],"isBlockCoverage":false},{"functionName":"Encoding","ranges":[{"startOffset":150980,"endOffset":151007,"count":0}],"isBlockCoverage":false},{"functionName":"JsonUnsupportedObjectError","ranges":[{"startOffset":151041,"endOffset":151150,"count":0}],"isBlockCoverage":false},{"functionName":"JsonCyclicError","ranges":[{"startOffset":151173,"endOffset":151271,"count":0}],"isBlockCoverage":false},{"functionName":"JsonCodec","ranges":[{"startOffset":151288,"endOffset":151378,"count":1}],"isBlockCoverage":true},{"functionName":"JsonEncoder","ranges":[{"startOffset":151397,"endOffset":151487,"count":1}],"isBlockCoverage":true},{"functionName":"JsonDecoder","ranges":[{"startOffset":151506,"endOffset":151564,"count":1}],"isBlockCoverage":true},{"functionName":"_JsonStringifier","ranges":[{"startOffset":151588,"endOffset":151623,"count":0}],"isBlockCoverage":false},{"functionName":"_JsonStringifier_writeMap_closure","ranges":[{"startOffset":151664,"endOffset":151776,"count":1}],"isBlockCoverage":true},{"functionName":"_JsonStringStringifier","ranges":[{"startOffset":151806,"endOffset":151942,"count":1}],"isBlockCoverage":true},{"functionName":"Utf8Codec","ranges":[{"startOffset":151959,"endOffset":152022,"count":1}],"isBlockCoverage":true},{"functionName":"Utf8Encoder","ranges":[{"startOffset":152041,"endOffset":152071,"count":1}],"isBlockCoverage":true},{"functionName":"_Utf8Encoder","ranges":[{"startOffset":152091,"endOffset":152192,"count":0}],"isBlockCoverage":false},{"functionName":"Utf8Decoder","ranges":[{"startOffset":152211,"endOffset":152276,"count":1}],"isBlockCoverage":true},{"functionName":"_Utf8Decoder","ranges":[{"startOffset":152296,"endOffset":152498,"count":1}],"isBlockCoverage":true},{"functionName":"_Utf8Decoder_convert_addSingleBytes","ranges":[{"startOffset":152541,"endOffset":152721,"count":1}],"isBlockCoverage":true},{"functionName":"int_parse","ranges":[{"startOffset":152738,"endOffset":153106,"count":1},{"startOffset":152965,"endOffset":153105,"count":0}],"isBlockCoverage":true},{"functionName":"Error__objectToString","ranges":[{"startOffset":153135,"endOffset":153309,"count":0}],"isBlockCoverage":false},{"functionName":"List_List$filled","ranges":[{"startOffset":153333,"endOffset":153687,"count":0}],"isBlockCoverage":false},{"functionName":"List_List$from","ranges":[{"startOffset":153709,"endOffset":154115,"count":0}],"isBlockCoverage":false},{"functionName":"List_List$unmodifiable","ranges":[{"startOffset":154145,"endOffset":154366,"count":0}],"isBlockCoverage":false},{"functionName":"String_String$fromCharCodes","ranges":[{"startOffset":154401,"endOffset":155216,"count":1},{"startOffset":154876,"endOffset":154928,"count":0},{"startOffset":154950,"endOffset":155215,"count":0}],"isBlockCoverage":true},{"functionName":"String_String$fromCharCode","ranges":[{"startOffset":155250,"endOffset":155332,"count":0}],"isBlockCoverage":false},{"functionName":"String__stringFromIterable","ranges":[{"startOffset":155366,"endOffset":156339,"count":0}],"isBlockCoverage":false},{"functionName":"RegExp_RegExp","ranges":[{"startOffset":156360,"endOffset":156499,"count":0}],"isBlockCoverage":false},{"functionName":"StringBuffer__writeAll","ranges":[{"startOffset":156529,"endOffset":157013,"count":0}],"isBlockCoverage":false},{"functionName":"NoSuchMethodError$","ranges":[{"startOffset":157039,"endOffset":157217,"count":0}],"isBlockCoverage":false},{"functionName":"Uri_base","ranges":[{"startOffset":157233,"endOffset":157431,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__uriEncode","ranges":[{"startOffset":157454,"endOffset":158643,"count":0}],"isBlockCoverage":false},{"functionName":"DateTime__fourDigits","ranges":[{"startOffset":158671,"endOffset":158964,"count":0}],"isBlockCoverage":false},{"functionName":"DateTime__threeDigits","ranges":[{"startOffset":158993,"endOffset":159121,"count":0}],"isBlockCoverage":false},{"functionName":"DateTime__twoDigits","ranges":[{"startOffset":159148,"endOffset":159231,"count":0}],"isBlockCoverage":false},{"functionName":"Duration$","ranges":[{"startOffset":159248,"endOffset":159321,"count":1}],"isBlockCoverage":true},{"functionName":"Error_safeToString","ranges":[{"startOffset":159347,"endOffset":159619,"count":0}],"isBlockCoverage":false},{"functionName":"ArgumentError$","ranges":[{"startOffset":159641,"endOffset":159728,"count":0}],"isBlockCoverage":false},{"functionName":"ArgumentError$value","ranges":[{"startOffset":159755,"endOffset":159857,"count":0}],"isBlockCoverage":false},{"functionName":"RangeError$value","ranges":[{"startOffset":159881,"endOffset":159996,"count":0}],"isBlockCoverage":false},{"functionName":"RangeError$range","ranges":[{"startOffset":160020,"endOffset":160181,"count":0}],"isBlockCoverage":false},{"functionName":"RangeError_checkValueInInterval","ranges":[{"startOffset":160220,"endOffset":160409,"count":0}],"isBlockCoverage":false},{"functionName":"RangeError_checkValidRange","ranges":[{"startOffset":160443,"endOffset":160875,"count":1},{"startOffset":160520,"endOffset":160540,"count":0},{"startOffset":160589,"endOffset":160665,"count":0},{"startOffset":160743,"endOffset":160819,"count":0}],"isBlockCoverage":true},{"functionName":"RangeError_checkNotNegative","ranges":[{"startOffset":160910,"endOffset":161106,"count":0}],"isBlockCoverage":false},{"functionName":"IndexError$","ranges":[{"startOffset":161125,"endOffset":161363,"count":0}],"isBlockCoverage":false},{"functionName":"UnsupportedError$","ranges":[{"startOffset":161388,"endOffset":161459,"count":0}],"isBlockCoverage":false},{"functionName":"UnimplementedError$","ranges":[{"startOffset":161486,"endOffset":161559,"count":0}],"isBlockCoverage":false},{"functionName":"StateError$","ranges":[{"startOffset":161578,"endOffset":161643,"count":0}],"isBlockCoverage":false},{"functionName":"ConcurrentModificationError$","ranges":[{"startOffset":161679,"endOffset":161775,"count":0}],"isBlockCoverage":false},{"functionName":"FormatException$","ranges":[{"startOffset":161799,"endOffset":161901,"count":0}],"isBlockCoverage":false},{"functionName":"List_List$generate","ranges":[{"startOffset":161927,"endOffset":162297,"count":1}],"isBlockCoverage":true},{"functionName":"Uri_parse","ranges":[{"startOffset":162314,"endOffset":168453,"count":1},{"startOffset":162832,"endOffset":162938,"count":0},{"startOffset":162980,"endOffset":163068,"count":0},{"startOffset":163649,"endOffset":163694,"count":0},{"startOffset":163774,"endOffset":163797,"count":0},{"startOffset":163894,"endOffset":163917,"count":0},{"startOffset":163983,"endOffset":164000,"count":0},{"startOffset":164205,"endOffset":164232,"count":0},{"startOffset":164283,"endOffset":164308,"count":0},{"startOffset":164355,"endOffset":164382,"count":0},{"startOffset":164432,"endOffset":164455,"count":0},{"startOffset":164497,"endOffset":164520,"count":0},{"startOffset":164568,"endOffset":164594,"count":0},{"startOffset":164644,"endOffset":164667,"count":0},{"startOffset":164709,"endOffset":164731,"count":0},{"startOffset":164797,"endOffset":164813,"count":0},{"startOffset":164867,"endOffset":167872,"count":0},{"startOffset":167927,"endOffset":168319,"count":0}],"isBlockCoverage":true},{"functionName":"Uri_decodeComponent","ranges":[{"startOffset":168480,"endOffset":168661,"count":0}],"isBlockCoverage":false},{"functionName":"Uri_splitQueryString","ranges":[{"startOffset":168689,"endOffset":168954,"count":1}],"isBlockCoverage":true},{"functionName":"Uri__parseIPv4Address","ranges":[{"startOffset":168983,"endOffset":170538,"count":0}],"isBlockCoverage":false},{"functionName":"Uri_parseIPv6Address","ranges":[{"startOffset":170566,"endOffset":173975,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__Uri$notSimple","ranges":[{"startOffset":174002,"endOffset":175800,"count":1},{"startOffset":174254,"endOffset":174277,"count":0},{"startOffset":174370,"endOffset":174515,"count":0},{"startOffset":174608,"endOffset":174632,"count":0},{"startOffset":174717,"endOffset":174774,"count":0},{"startOffset":174901,"endOffset":174925,"count":0},{"startOffset":175007,"endOffset":175031,"count":0},{"startOffset":175196,"endOffset":175202,"count":0},{"startOffset":175211,"endOffset":175291,"count":0},{"startOffset":175429,"endOffset":175453,"count":0},{"startOffset":175507,"endOffset":175535,"count":0},{"startOffset":175639,"endOffset":175645,"count":0},{"startOffset":175734,"endOffset":175785,"count":0}],"isBlockCoverage":true},{"functionName":"_Uri__Uri","ranges":[{"startOffset":175817,"endOffset":177057,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__defaultPort","ranges":[{"startOffset":177082,"endOffset":177220,"count":1},{"startOffset":177148,"endOffset":177219,"count":0}],"isBlockCoverage":true},{"functionName":"_Uri__fail","ranges":[{"startOffset":177238,"endOffset":177345,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__Uri$file","ranges":[{"startOffset":177367,"endOffset":177497,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__checkNonWindowsPathReservedCharacters","ranges":[{"startOffset":177548,"endOffset":177751,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__checkWindowsPathReservedCharacters","ranges":[{"startOffset":177799,"endOffset":178489,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__checkWindowsDriveLetter","ranges":[{"startOffset":178526,"endOffset":179005,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__makeFileUri","ranges":[{"startOffset":179030,"endOffset":179320,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__makeWindowsFileUrl","ranges":[{"startOffset":179352,"endOffset":181689,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__makePort","ranges":[{"startOffset":181711,"endOffset":181840,"count":1},{"startOffset":181808,"endOffset":181815,"count":0}],"isBlockCoverage":true},{"functionName":"_Uri__makeHost","ranges":[{"startOffset":181862,"endOffset":182764,"count":1},{"startOffset":181952,"endOffset":181959,"count":0},{"startOffset":181993,"endOffset":182003,"count":0},{"startOffset":182067,"endOffset":182435,"count":0},{"startOffset":182479,"endOffset":182497,"count":0},{"startOffset":182603,"endOffset":182701,"count":0}],"isBlockCoverage":true},{"functionName":"_Uri__normalizeRegName","ranges":[{"startOffset":182794,"endOffset":186280,"count":1},{"startOffset":182973,"endOffset":182991,"count":0},{"startOffset":183204,"endOffset":184074,"count":0},{"startOffset":184178,"endOffset":184209,"count":0},{"startOffset":184281,"endOffset":184310,"count":0},{"startOffset":184389,"endOffset":184714,"count":0},{"startOffset":184747,"endOffset":185904,"count":0},{"startOffset":186013,"endOffset":186268,"count":0},{"startOffset":186269,"endOffset":186273,"count":0}],"isBlockCoverage":true},{"functionName":"_Uri__makeScheme","ranges":[{"startOffset":186304,"endOffset":187376,"count":1},{"startOffset":186414,"endOffset":186424,"count":0},{"startOffset":186532,"endOffset":186609,"count":0},{"startOffset":186653,"endOffset":186671,"count":0},{"startOffset":186911,"endOffset":186942,"count":0},{"startOffset":187014,"endOffset":187041,"count":0},{"startOffset":187069,"endOffset":187121,"count":0},{"startOffset":187178,"endOffset":187203,"count":0},{"startOffset":187337,"endOffset":187359,"count":0}],"isBlockCoverage":true},{"functionName":"_Uri__canonicalizeScheme","ranges":[{"startOffset":187408,"endOffset":187669,"count":1},{"startOffset":187478,"endOffset":187668,"count":0}],"isBlockCoverage":true},{"functionName":"_Uri__makeUserInfo","ranges":[{"startOffset":187695,"endOffset":187864,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__makePath","ranges":[{"startOffset":187886,"endOffset":189059,"count":1},{"startOffset":188222,"endOffset":188245,"count":0},{"startOffset":188255,"endOffset":188280,"count":0},{"startOffset":188343,"endOffset":188423,"count":0},{"startOffset":188520,"endOffset":188795,"count":0},{"startOffset":188827,"endOffset":188878,"count":0},{"startOffset":188965,"endOffset":188987,"count":0}],"isBlockCoverage":true},{"functionName":"_Uri__normalizePath","ranges":[{"startOffset":189086,"endOffset":189363,"count":1},{"startOffset":189174,"endOffset":189190,"count":0},{"startOffset":189191,"endOffset":189237,"count":0},{"startOffset":189247,"endOffset":189311,"count":0}],"isBlockCoverage":true},{"functionName":"_Uri__makeQuery","ranges":[{"startOffset":189386,"endOffset":189559,"count":1},{"startOffset":189539,"endOffset":189558,"count":0}],"isBlockCoverage":true},{"functionName":"_Uri__makeFragment","ranges":[{"startOffset":189585,"endOffset":189750,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__normalizeEscape","ranges":[{"startOffset":189779,"endOffset":190943,"count":1},{"startOffset":189942,"endOffset":189962,"count":0},{"startOffset":190024,"endOffset":190035,"count":0},{"startOffset":190351,"endOffset":190362,"count":0},{"startOffset":190534,"endOffset":190565,"count":0},{"startOffset":190630,"endOffset":190655,"count":0},{"startOffset":190678,"endOffset":190787,"count":0},{"startOffset":190845,"endOffset":190923,"count":0}],"isBlockCoverage":true},{"functionName":"_Uri__escapeChar","ranges":[{"startOffset":190967,"endOffset":192472,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__normalizeOrSubstring","ranges":[{"startOffset":192506,"endOffset":192779,"count":1},{"startOffset":192768,"endOffset":192772,"count":0}],"isBlockCoverage":true},{"functionName":"_Uri__normalize","ranges":[{"startOffset":192802,"endOffset":196002,"count":1},{"startOffset":193232,"endOffset":193251,"count":0},{"startOffset":193299,"endOffset":193317,"count":0},{"startOffset":193526,"endOffset":193556,"count":0},{"startOffset":193627,"endOffset":193656,"count":0},{"startOffset":193933,"endOffset":194100,"count":0},{"startOffset":194114,"endOffset":195678,"count":0},{"startOffset":195738,"endOffset":195990,"count":0},{"startOffset":195991,"endOffset":195995,"count":0}],"isBlockCoverage":true},{"functionName":"_Uri__mayContainDotSegments","ranges":[{"startOffset":196037,"endOffset":196202,"count":1},{"startOffset":196122,"endOffset":196134,"count":0}],"isBlockCoverage":true},{"functionName":"_Uri__removeDotSegments","ranges":[{"startOffset":196233,"endOffset":197172,"count":1},{"startOffset":196374,"endOffset":197171,"count":0}],"isBlockCoverage":true},{"functionName":"_Uri__normalizeRelativePath","ranges":[{"startOffset":197207,"endOffset":198750,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__escapeScheme","ranges":[{"startOffset":198776,"endOffset":199463,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__toWindowsFilePath","ranges":[{"startOffset":199494,"endOffset":200490,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__hexCharPairToByte","ranges":[{"startOffset":200521,"endOffset":201052,"count":1},{"startOffset":200917,"endOffset":201009,"count":0}],"isBlockCoverage":true},{"functionName":"_Uri__uriDecode","ranges":[{"startOffset":201075,"endOffset":202742,"count":1},{"startOffset":201518,"endOffset":201552,"count":0},{"startOffset":201719,"endOffset":201730,"count":0},{"startOffset":201831,"endOffset":201913,"count":0},{"startOffset":202110,"endOffset":202185,"count":0},{"startOffset":202270,"endOffset":202327,"count":0},{"startOffset":202497,"endOffset":202532,"count":0}],"isBlockCoverage":true},{"functionName":"_Uri__isAlphabeticCharacter","ranges":[{"startOffset":202777,"endOffset":202890,"count":1}],"isBlockCoverage":true},{"functionName":"UriData__writeUri","ranges":[{"startOffset":202915,"endOffset":203591,"count":0}],"isBlockCoverage":false},{"functionName":"UriData__validateMimeType","ranges":[{"startOffset":203624,"endOffset":203975,"count":0}],"isBlockCoverage":false},{"functionName":"UriData__parse","ranges":[{"startOffset":203997,"endOffset":205905,"count":0}],"isBlockCoverage":false},{"functionName":"UriData__uriEncodeBytes","ranges":[{"startOffset":205936,"endOffset":207460,"count":0}],"isBlockCoverage":false},{"functionName":"_createTables","ranges":[{"startOffset":207481,"endOffset":213521,"count":1}],"isBlockCoverage":true},{"functionName":"_scan","ranges":[{"startOffset":213534,"endOffset":214326,"count":1},{"startOffset":213774,"endOffset":213792,"count":0},{"startOffset":213934,"endOffset":213964,"count":0},{"startOffset":214075,"endOffset":214085,"count":0},{"startOffset":214130,"endOffset":214158,"count":0}],"isBlockCoverage":true},{"functionName":"NoSuchMethodError_toString_closure","ranges":[{"startOffset":214368,"endOffset":214471,"count":0}],"isBlockCoverage":false},{"functionName":"bool","ranges":[{"startOffset":214483,"endOffset":214506,"count":0}],"isBlockCoverage":false},{"functionName":"DateTime","ranges":[{"startOffset":214522,"endOffset":214608,"count":0}],"isBlockCoverage":false},{"functionName":"double","ranges":[{"startOffset":214622,"endOffset":214647,"count":0}],"isBlockCoverage":false},{"functionName":"Duration","ranges":[{"startOffset":214663,"endOffset":214719,"count":1}],"isBlockCoverage":true},{"functionName":"Duration_toString_sixDigits","ranges":[{"startOffset":214754,"endOffset":214800,"count":0}],"isBlockCoverage":false},{"functionName":"Duration_toString_twoDigits","ranges":[{"startOffset":214835,"endOffset":214881,"count":0}],"isBlockCoverage":false},{"functionName":"Error","ranges":[{"startOffset":214894,"endOffset":214918,"count":0}],"isBlockCoverage":false},{"functionName":"NullThrownError","ranges":[{"startOffset":214941,"endOffset":214975,"count":0}],"isBlockCoverage":false},{"functionName":"ArgumentError","ranges":[{"startOffset":214996,"endOffset":215154,"count":0}],"isBlockCoverage":false},{"functionName":"RangeError","ranges":[{"startOffset":215172,"endOffset":215373,"count":0}],"isBlockCoverage":false},{"functionName":"IndexError","ranges":[{"startOffset":215391,"endOffset":215571,"count":0}],"isBlockCoverage":false},{"functionName":"NoSuchMethodError","ranges":[{"startOffset":215596,"endOffset":215830,"count":0}],"isBlockCoverage":false},{"functionName":"UnsupportedError","ranges":[{"startOffset":215854,"endOffset":215916,"count":0}],"isBlockCoverage":false},{"functionName":"UnimplementedError","ranges":[{"startOffset":215942,"endOffset":216006,"count":0}],"isBlockCoverage":false},{"functionName":"StateError","ranges":[{"startOffset":216024,"endOffset":216080,"count":0}],"isBlockCoverage":false},{"functionName":"ConcurrentModificationError","ranges":[{"startOffset":216115,"endOffset":216195,"count":0}],"isBlockCoverage":false},{"functionName":"OutOfMemoryError","ranges":[{"startOffset":216219,"endOffset":216254,"count":1}],"isBlockCoverage":true},{"functionName":"StackOverflowError","ranges":[{"startOffset":216280,"endOffset":216317,"count":0}],"isBlockCoverage":false},{"functionName":"CyclicInitializationError","ranges":[{"startOffset":216350,"endOffset":216426,"count":0}],"isBlockCoverage":false},{"functionName":"_Exception","ranges":[{"startOffset":216444,"endOffset":216500,"count":0}],"isBlockCoverage":false},{"functionName":"FormatException","ranges":[{"startOffset":216523,"endOffset":216640,"count":0}],"isBlockCoverage":false},{"functionName":"Function","ranges":[{"startOffset":216656,"endOffset":216683,"count":0}],"isBlockCoverage":false},{"functionName":"int","ranges":[{"startOffset":216694,"endOffset":216716,"count":0}],"isBlockCoverage":false},{"functionName":"Iterable","ranges":[{"startOffset":216732,"endOffset":216759,"count":0}],"isBlockCoverage":false},{"functionName":"Iterator","ranges":[{"startOffset":216775,"endOffset":216802,"count":0}],"isBlockCoverage":false},{"functionName":"List","ranges":[{"startOffset":216814,"endOffset":216837,"count":0}],"isBlockCoverage":false},{"functionName":"Map","ranges":[{"startOffset":216848,"endOffset":216870,"count":0}],"isBlockCoverage":false},{"functionName":"Null","ranges":[{"startOffset":216882,"endOffset":216905,"count":0}],"isBlockCoverage":false},{"functionName":"num","ranges":[{"startOffset":216916,"endOffset":216938,"count":0}],"isBlockCoverage":false},{"functionName":"Object","ranges":[{"startOffset":216952,"endOffset":216977,"count":0}],"isBlockCoverage":true},{"functionName":"Match","ranges":[{"startOffset":216990,"endOffset":217014,"count":0}],"isBlockCoverage":false},{"functionName":"StackTrace","ranges":[{"startOffset":217032,"endOffset":217061,"count":0}],"isBlockCoverage":false},{"functionName":"_StringStackTrace","ranges":[{"startOffset":217086,"endOffset":217153,"count":0}],"isBlockCoverage":false},{"functionName":"String","ranges":[{"startOffset":217167,"endOffset":217192,"count":0}],"isBlockCoverage":false},{"functionName":"StringBuffer","ranges":[{"startOffset":217212,"endOffset":217272,"count":1}],"isBlockCoverage":true},{"functionName":"Symbol0","ranges":[{"startOffset":217287,"endOffset":217313,"count":0}],"isBlockCoverage":false},{"functionName":"Uri_splitQueryString_closure","ranges":[{"startOffset":217349,"endOffset":217424,"count":1}],"isBlockCoverage":true},{"functionName":"Uri__parseIPv4Address_error","ranges":[{"startOffset":217459,"endOffset":217529,"count":0}],"isBlockCoverage":false},{"functionName":"Uri_parseIPv6Address_error","ranges":[{"startOffset":217563,"endOffset":217632,"count":0}],"isBlockCoverage":false},{"functionName":"Uri_parseIPv6Address_parseHex","ranges":[{"startOffset":217669,"endOffset":217768,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri","ranges":[{"startOffset":217780,"endOffset":218078,"count":1}],"isBlockCoverage":true},{"functionName":"_Uri__Uri$notSimple_closure","ranges":[{"startOffset":218113,"endOffset":218213,"count":1}],"isBlockCoverage":true},{"functionName":"_Uri__checkNonWindowsPathReservedCharacters_closure","ranges":[{"startOffset":218272,"endOffset":218375,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__makePath_closure","ranges":[{"startOffset":218405,"endOffset":218446,"count":0}],"isBlockCoverage":false},{"functionName":"UriData","ranges":[{"startOffset":218461,"endOffset":218582,"count":0}],"isBlockCoverage":false},{"functionName":"_createTables_closure","ranges":[{"startOffset":218611,"endOffset":218651,"count":1}],"isBlockCoverage":true},{"functionName":"_createTables_build","ranges":[{"startOffset":218678,"endOffset":218742,"count":1}],"isBlockCoverage":true},{"functionName":"_createTables_setChars","ranges":[{"startOffset":218772,"endOffset":218813,"count":1}],"isBlockCoverage":true},{"functionName":"_createTables_setRange","ranges":[{"startOffset":218843,"endOffset":218884,"count":1}],"isBlockCoverage":true},{"functionName":"_SimpleUri","ranges":[{"startOffset":218902,"endOffset":219213,"count":0}],"isBlockCoverage":false},{"functionName":"_DataUri","ranges":[{"startOffset":219229,"endOffset":219531,"count":0}],"isBlockCoverage":false},{"functionName":"convertNativePromiseToDartFuture","ranges":[{"startOffset":219571,"endOffset":219951,"count":0}],"isBlockCoverage":false},{"functionName":"_StructuredClone","ranges":[{"startOffset":219975,"endOffset":220010,"count":0}],"isBlockCoverage":false},{"functionName":"_StructuredClone_walk_closure","ranges":[{"startOffset":220047,"endOffset":220148,"count":1}],"isBlockCoverage":true},{"functionName":"_AcceptStructuredClone","ranges":[{"startOffset":220178,"endOffset":220219,"count":0}],"isBlockCoverage":false},{"functionName":"_AcceptStructuredClone_walk_closure","ranges":[{"startOffset":220262,"endOffset":220369,"count":1}],"isBlockCoverage":true},{"functionName":"_StructuredCloneDart2Js","ranges":[{"startOffset":220400,"endOffset":220496,"count":1}],"isBlockCoverage":true},{"functionName":"_AcceptStructuredCloneDart2Js","ranges":[{"startOffset":220533,"endOffset":220664,"count":1}],"isBlockCoverage":true},{"functionName":"convertNativePromiseToDartFuture_closure","ranges":[{"startOffset":220712,"endOffset":220800,"count":0}],"isBlockCoverage":false},{"functionName":"convertNativePromiseToDartFuture_closure0","ranges":[{"startOffset":220849,"endOffset":220938,"count":0}],"isBlockCoverage":false},{"functionName":"SvgElement","ranges":[{"startOffset":220956,"endOffset":220985,"count":0}],"isBlockCoverage":false},{"functionName":"Uint8List","ranges":[{"startOffset":221002,"endOffset":221030,"count":0}],"isBlockCoverage":false},{"functionName":"_convertDartFunctionFast","ranges":[{"startOffset":221062,"endOffset":221474,"count":1},{"startOffset":221174,"endOffset":221190,"count":0}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":221203,"endOffset":221337,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":221239,"endOffset":221328,"count":0}],"isBlockCoverage":false},{"functionName":"_callDartFunctionFast","ranges":[{"startOffset":221503,"endOffset":221700,"count":0}],"isBlockCoverage":false},{"functionName":"allowInterop","ranges":[{"startOffset":221720,"endOffset":222089,"count":1},{"startOffset":221983,"endOffset":221992,"count":0}],"isBlockCoverage":true},{"functionName":"max","ranges":[{"startOffset":222100,"endOffset":222411,"count":0}],"isBlockCoverage":false},{"functionName":"window","ranges":[{"startOffset":222437,"endOffset":222476,"count":1}],"isBlockCoverage":true},{"functionName":"WebSocket_WebSocket","ranges":[{"startOffset":222503,"endOffset":222557,"count":1}],"isBlockCoverage":true},{"functionName":"_EventStreamSubscription$","ranges":[{"startOffset":222590,"endOffset":222884,"count":1},{"startOffset":222677,"endOffset":222683,"count":0}],"isBlockCoverage":true},{"functionName":"_convertNativeToDart_Window","ranges":[{"startOffset":222919,"endOffset":223033,"count":1},{"startOffset":222966,"endOffset":222973,"count":0}],"isBlockCoverage":true},{"functionName":"_DOMWindowCrossFrame__createSafe","ranges":[{"startOffset":223073,"endOffset":223232,"count":1},{"startOffset":223119,"endOffset":223169,"count":0}],"isBlockCoverage":true},{"functionName":"_wrapZone","ranges":[{"startOffset":223249,"endOffset":223507,"count":1},{"startOffset":223425,"endOffset":223441,"count":0}],"isBlockCoverage":true},{"functionName":"HtmlElement","ranges":[{"startOffset":223526,"endOffset":223556,"count":0}],"isBlockCoverage":false},{"functionName":"AnchorElement","ranges":[{"startOffset":223577,"endOffset":223609,"count":0}],"isBlockCoverage":false},{"functionName":"AreaElement","ranges":[{"startOffset":223628,"endOffset":223658,"count":0}],"isBlockCoverage":false},{"functionName":"Blob","ranges":[{"startOffset":223670,"endOffset":223693,"count":0}],"isBlockCoverage":false},{"functionName":"CharacterData","ranges":[{"startOffset":223714,"endOffset":223746,"count":0}],"isBlockCoverage":false},{"functionName":"DomException","ranges":[{"startOffset":223766,"endOffset":223797,"count":0}],"isBlockCoverage":false},{"functionName":"DomTokenList","ranges":[{"startOffset":223817,"endOffset":223848,"count":0}],"isBlockCoverage":false},{"functionName":"Element","ranges":[{"startOffset":223863,"endOffset":223889,"count":0}],"isBlockCoverage":false},{"functionName":"Event","ranges":[{"startOffset":223902,"endOffset":223926,"count":0}],"isBlockCoverage":false},{"functionName":"EventTarget","ranges":[{"startOffset":223945,"endOffset":223975,"count":0}],"isBlockCoverage":false},{"functionName":"File","ranges":[{"startOffset":223987,"endOffset":224010,"count":0}],"isBlockCoverage":false},{"functionName":"FormElement","ranges":[{"startOffset":224029,"endOffset":224059,"count":0}],"isBlockCoverage":false},{"functionName":"HtmlCollection","ranges":[{"startOffset":224081,"endOffset":224114,"count":0}],"isBlockCoverage":false},{"functionName":"IFrameElement","ranges":[{"startOffset":224135,"endOffset":224167,"count":0}],"isBlockCoverage":false},{"functionName":"Location","ranges":[{"startOffset":224183,"endOffset":224210,"count":0}],"isBlockCoverage":false},{"functionName":"MessageEvent","ranges":[{"startOffset":224230,"endOffset":224261,"count":0}],"isBlockCoverage":false},{"functionName":"MessagePort","ranges":[{"startOffset":224280,"endOffset":224310,"count":0}],"isBlockCoverage":false},{"functionName":"MouseEvent","ranges":[{"startOffset":224328,"endOffset":224357,"count":0}],"isBlockCoverage":false},{"functionName":"Node","ranges":[{"startOffset":224369,"endOffset":224392,"count":0}],"isBlockCoverage":false},{"functionName":"SelectElement","ranges":[{"startOffset":224413,"endOffset":224445,"count":0}],"isBlockCoverage":false},{"functionName":"UIEvent","ranges":[{"startOffset":224460,"endOffset":224486,"count":0}],"isBlockCoverage":false},{"functionName":"Window","ranges":[{"startOffset":224500,"endOffset":224525,"count":0}],"isBlockCoverage":false},{"functionName":"_EventStream","ranges":[{"startOffset":224545,"endOffset":224707,"count":0}],"isBlockCoverage":false},{"functionName":"_ElementEventStreamImpl","ranges":[{"startOffset":224738,"endOffset":224911,"count":1}],"isBlockCoverage":true},{"functionName":"_EventStreamSubscription","ranges":[{"startOffset":224943,"endOffset":225168,"count":1}],"isBlockCoverage":true},{"functionName":"_EventStreamSubscription_closure","ranges":[{"startOffset":225208,"endOffset":225285,"count":1}],"isBlockCoverage":true},{"functionName":"ImmutableListMixin","ranges":[{"startOffset":225311,"endOffset":225348,"count":0}],"isBlockCoverage":false},{"functionName":"FixedSizeListIterator","ranges":[{"startOffset":225377,"endOffset":225561,"count":0}],"isBlockCoverage":false},{"functionName":"_DOMWindowCrossFrame","ranges":[{"startOffset":225589,"endOffset":225655,"count":1}],"isBlockCoverage":true},{"functionName":"_HtmlCollection_Interceptor_ListMixin","ranges":[{"startOffset":225700,"endOffset":225756,"count":0}],"isBlockCoverage":false},{"functionName":"_HtmlCollection_Interceptor_ListMixin_ImmutableListMixin","ranges":[{"startOffset":225820,"endOffset":225895,"count":0}],"isBlockCoverage":false},{"functionName":"NullStreamSink","ranges":[{"startOffset":225924,"endOffset":226081,"count":0}],"isBlockCoverage":false},{"functionName":"NullStreamSink_addStream_closure","ranges":[{"startOffset":226117,"endOffset":226193,"count":0}],"isBlockCoverage":false},{"functionName":"Context_Context","ranges":[{"startOffset":226225,"endOffset":226413,"count":0}],"isBlockCoverage":false},{"functionName":"_parseUri","ranges":[{"startOffset":226430,"endOffset":226613,"count":0}],"isBlockCoverage":false},{"functionName":"_validateArgList","ranges":[{"startOffset":226637,"endOffset":227693,"count":0}],"isBlockCoverage":false},{"functionName":"Context","ranges":[{"startOffset":227708,"endOffset":227798,"count":0}],"isBlockCoverage":false},{"functionName":"Context_join_closure","ranges":[{"startOffset":227826,"endOffset":227865,"count":0}],"isBlockCoverage":false},{"functionName":"Context_joinAll_closure","ranges":[{"startOffset":227896,"endOffset":227938,"count":0}],"isBlockCoverage":false},{"functionName":"Context_split_closure","ranges":[{"startOffset":227967,"endOffset":228007,"count":0}],"isBlockCoverage":false},{"functionName":"_validateArgList_closure","ranges":[{"startOffset":228039,"endOffset":228082,"count":0}],"isBlockCoverage":false},{"functionName":"InternalStyle","ranges":[{"startOffset":228110,"endOffset":228142,"count":0}],"isBlockCoverage":false},{"functionName":"StreamChannelController$","ranges":[{"startOffset":228174,"endOffset":228991,"count":1}],"isBlockCoverage":true},{"functionName":"StreamChannelController","ranges":[{"startOffset":229022,"endOffset":229129,"count":1}],"isBlockCoverage":true},{"functionName":"isAlphabetic","ranges":[{"startOffset":229149,"endOffset":229311,"count":0}],"isBlockCoverage":false},{"functionName":"isDriveLetter","ranges":[{"startOffset":229332,"endOffset":229761,"count":0}],"isBlockCoverage":false},{"functionName":"ParsedPath_ParsedPath$parse","ranges":[{"startOffset":229808,"endOffset":231004,"count":0}],"isBlockCoverage":false},{"functionName":"ParsedPath","ranges":[{"startOffset":231022,"endOffset":231169,"count":0}],"isBlockCoverage":false},{"functionName":"ParsedPath_normalize_closure","ranges":[{"startOffset":231205,"endOffset":231277,"count":0}],"isBlockCoverage":false},{"functionName":"PathException$","ranges":[{"startOffset":231299,"endOffset":231367,"count":0}],"isBlockCoverage":false},{"functionName":"PathException","ranges":[{"startOffset":231388,"endOffset":231447,"count":0}],"isBlockCoverage":false},{"functionName":"Style__getPlatformStyle","ranges":[{"startOffset":231490,"endOffset":231852,"count":0}],"isBlockCoverage":false},{"functionName":"Style","ranges":[{"startOffset":231865,"endOffset":231889,"count":0}],"isBlockCoverage":false},{"functionName":"PosixStyle","ranges":[{"startOffset":231914,"endOffset":231998,"count":0}],"isBlockCoverage":false},{"functionName":"UrlStyle","ranges":[{"startOffset":232018,"endOffset":232098,"count":0}],"isBlockCoverage":false},{"functionName":"WindowsStyle","ranges":[{"startOffset":232122,"endOffset":232211,"count":0}],"isBlockCoverage":false},{"functionName":"WindowsStyle_absolutePathToUri_closure","ranges":[{"startOffset":232253,"endOffset":232310,"count":0}],"isBlockCoverage":false},{"functionName":"Chain_Chain$parse","ranges":[{"startOffset":232344,"endOffset":233600,"count":0}],"isBlockCoverage":false},{"functionName":"Chain","ranges":[{"startOffset":233613,"endOffset":233663,"count":0}],"isBlockCoverage":false},{"functionName":"Chain_Chain$parse_closure","ranges":[{"startOffset":233696,"endOffset":233740,"count":0}],"isBlockCoverage":false},{"functionName":"Chain_Chain$parse_closure0","ranges":[{"startOffset":233774,"endOffset":233819,"count":0}],"isBlockCoverage":false},{"functionName":"Chain_toTrace_closure","ranges":[{"startOffset":233848,"endOffset":233888,"count":0}],"isBlockCoverage":false},{"functionName":"Chain_toString_closure0","ranges":[{"startOffset":233919,"endOffset":233961,"count":0}],"isBlockCoverage":false},{"functionName":"Chain_toString__closure0","ranges":[{"startOffset":233993,"endOffset":234036,"count":0}],"isBlockCoverage":false},{"functionName":"Chain_toString_closure","ranges":[{"startOffset":234066,"endOffset":234134,"count":0}],"isBlockCoverage":false},{"functionName":"Chain_toString__closure","ranges":[{"startOffset":234165,"endOffset":234234,"count":0}],"isBlockCoverage":false},{"functionName":"Frame_Frame$parseVM","ranges":[{"startOffset":234273,"endOffset":234389,"count":0}],"isBlockCoverage":false},{"functionName":"Frame_Frame$parseV8","ranges":[{"startOffset":234416,"endOffset":234532,"count":0}],"isBlockCoverage":false},{"functionName":"Frame_Frame$parseFirefox","ranges":[{"startOffset":234564,"endOffset":234685,"count":0}],"isBlockCoverage":false},{"functionName":"Frame_Frame$parseFriendly","ranges":[{"startOffset":234718,"endOffset":234840,"count":0}],"isBlockCoverage":false},{"functionName":"Frame__uriOrPathToUri","ranges":[{"startOffset":234869,"endOffset":235427,"count":0}],"isBlockCoverage":false},{"functionName":"Frame__catchFormatException","ranges":[{"startOffset":235462,"endOffset":235866,"count":0}],"isBlockCoverage":false},{"functionName":"Frame","ranges":[{"startOffset":235879,"endOffset":236016,"count":0}],"isBlockCoverage":false},{"functionName":"Frame_Frame$parseVM_closure","ranges":[{"startOffset":236051,"endOffset":236122,"count":0}],"isBlockCoverage":false},{"functionName":"Frame_Frame$parseV8_closure","ranges":[{"startOffset":236157,"endOffset":236228,"count":0}],"isBlockCoverage":false},{"functionName":"Frame_Frame$parseV8_closure_parseLocation","ranges":[{"startOffset":236277,"endOffset":236362,"count":0}],"isBlockCoverage":false},{"functionName":"Frame_Frame$parseFirefox_closure","ranges":[{"startOffset":236402,"endOffset":236478,"count":0}],"isBlockCoverage":false},{"functionName":"Frame_Frame$parseFriendly_closure","ranges":[{"startOffset":236519,"endOffset":236596,"count":0}],"isBlockCoverage":false},{"functionName":"LazyTrace","ranges":[{"startOffset":236620,"endOffset":236712,"count":0}],"isBlockCoverage":false},{"functionName":"LazyTrace_terse_closure","ranges":[{"startOffset":236739,"endOffset":236806,"count":0}],"isBlockCoverage":false},{"functionName":"Trace_Trace$from","ranges":[{"startOffset":236839,"endOffset":237153,"count":0}],"isBlockCoverage":false},{"functionName":"Trace_Trace$parse","ranges":[{"startOffset":237178,"endOffset":238690,"count":0}],"isBlockCoverage":false},{"functionName":"Trace__parseVM","ranges":[{"startOffset":238712,"endOffset":239434,"count":0}],"isBlockCoverage":false},{"functionName":"Trace$parseV8","ranges":[{"startOffset":239455,"endOffset":240083,"count":0}],"isBlockCoverage":false},{"functionName":"Trace$parseJSCore","ranges":[{"startOffset":240108,"endOffset":240613,"count":0}],"isBlockCoverage":false},{"functionName":"Trace$parseFirefox","ranges":[{"startOffset":240639,"endOffset":241158,"count":0}],"isBlockCoverage":false},{"functionName":"Trace$parseFriendly","ranges":[{"startOffset":241185,"endOffset":241840,"count":0}],"isBlockCoverage":false},{"functionName":"Trace","ranges":[{"startOffset":241853,"endOffset":241933,"count":0}],"isBlockCoverage":false},{"functionName":"Trace_Trace$from_closure","ranges":[{"startOffset":241965,"endOffset":242033,"count":0}],"isBlockCoverage":false},{"functionName":"Trace__parseVM_closure","ranges":[{"startOffset":242063,"endOffset":242104,"count":0}],"isBlockCoverage":false},{"functionName":"Trace$parseV8_closure","ranges":[{"startOffset":242133,"endOffset":242173,"count":0}],"isBlockCoverage":false},{"functionName":"Trace$parseV8_closure0","ranges":[{"startOffset":242203,"endOffset":242244,"count":0}],"isBlockCoverage":false},{"functionName":"Trace$parseJSCore_closure","ranges":[{"startOffset":242277,"endOffset":242321,"count":0}],"isBlockCoverage":false},{"functionName":"Trace$parseJSCore_closure0","ranges":[{"startOffset":242355,"endOffset":242400,"count":0}],"isBlockCoverage":false},{"functionName":"Trace$parseFirefox_closure","ranges":[{"startOffset":242434,"endOffset":242479,"count":0}],"isBlockCoverage":false},{"functionName":"Trace$parseFirefox_closure0","ranges":[{"startOffset":242514,"endOffset":242560,"count":0}],"isBlockCoverage":false},{"functionName":"Trace$parseFriendly_closure","ranges":[{"startOffset":242595,"endOffset":242641,"count":0}],"isBlockCoverage":false},{"functionName":"Trace$parseFriendly_closure0","ranges":[{"startOffset":242677,"endOffset":242724,"count":0}],"isBlockCoverage":false},{"functionName":"Trace_terse_closure","ranges":[{"startOffset":242751,"endOffset":242789,"count":0}],"isBlockCoverage":false},{"functionName":"Trace_foldFrames_closure","ranges":[{"startOffset":242821,"endOffset":242896,"count":0}],"isBlockCoverage":false},{"functionName":"Trace_foldFrames_closure0","ranges":[{"startOffset":242929,"endOffset":242999,"count":0}],"isBlockCoverage":false},{"functionName":"Trace_toString_closure0","ranges":[{"startOffset":243030,"endOffset":243072,"count":0}],"isBlockCoverage":false},{"functionName":"Trace_toString_closure","ranges":[{"startOffset":243102,"endOffset":243170,"count":0}],"isBlockCoverage":false},{"functionName":"UnparsedFrame","ranges":[{"startOffset":243198,"endOffset":243437,"count":0}],"isBlockCoverage":false},{"functionName":"GuaranteeChannel$","ranges":[{"startOffset":243471,"endOffset":243729,"count":1}],"isBlockCoverage":true},{"functionName":"GuaranteeChannel","ranges":[{"startOffset":243753,"endOffset":243940,"count":1}],"isBlockCoverage":true},{"functionName":"GuaranteeChannel_closure","ranges":[{"startOffset":243972,"endOffset":244068,"count":1}],"isBlockCoverage":true},{"functionName":"GuaranteeChannel__closure","ranges":[{"startOffset":244101,"endOffset":244170,"count":1}],"isBlockCoverage":true},{"functionName":"_GuaranteeSink","ranges":[{"startOffset":244192,"endOffset":244487,"count":1}],"isBlockCoverage":true},{"functionName":"_GuaranteeSink_addStream_closure","ranges":[{"startOffset":244527,"endOffset":244603,"count":1}],"isBlockCoverage":true},{"functionName":"main","ranges":[{"startOffset":244615,"endOffset":244926,"count":1},{"startOffset":244690,"endOffset":244714,"count":0}],"isBlockCoverage":true},{"functionName":"_connectToServer","ranges":[{"startOffset":244950,"endOffset":245613,"count":1}],"isBlockCoverage":true},{"functionName":"_connectToIframe","ranges":[{"startOffset":245637,"endOffset":247021,"count":1}],"isBlockCoverage":true},{"functionName":"_TestRunner","ranges":[{"startOffset":247040,"endOffset":247070,"count":0}],"isBlockCoverage":false},{"functionName":"_JSApi","ranges":[{"startOffset":247084,"endOffset":247109,"count":0}],"isBlockCoverage":false},{"functionName":"main_closure","ranges":[{"startOffset":247129,"endOffset":247160,"count":1}],"isBlockCoverage":true},{"functionName":"main__closure","ranges":[{"startOffset":247181,"endOffset":247246,"count":1}],"isBlockCoverage":true},{"functionName":"main__closure0","ranges":[{"startOffset":247268,"endOffset":247334,"count":1}],"isBlockCoverage":true},{"functionName":"main__closure1","ranges":[{"startOffset":247356,"endOffset":247422,"count":1}],"isBlockCoverage":true},{"functionName":"main__closure2","ranges":[{"startOffset":247444,"endOffset":247510,"count":1}],"isBlockCoverage":true},{"functionName":"main__closure3","ranges":[{"startOffset":247532,"endOffset":247598,"count":1}],"isBlockCoverage":true},{"functionName":"main_closure0","ranges":[{"startOffset":247619,"endOffset":247651,"count":1}],"isBlockCoverage":true},{"functionName":"_connectToServer_closure","ranges":[{"startOffset":247683,"endOffset":247756,"count":1}],"isBlockCoverage":true},{"functionName":"_connectToServer_closure0","ranges":[{"startOffset":247789,"endOffset":247862,"count":1}],"isBlockCoverage":true},{"functionName":"_connectToIframe_closure","ranges":[{"startOffset":247894,"endOffset":248068,"count":1}],"isBlockCoverage":true},{"functionName":"_connectToIframe_closure0","ranges":[{"startOffset":248101,"endOffset":248175,"count":1}],"isBlockCoverage":true},{"functionName":"_connectToIframe_closure1","ranges":[{"startOffset":248208,"endOffset":248315,"count":1}],"isBlockCoverage":true},{"functionName":"_MultiChannel$","ranges":[{"startOffset":248349,"endOffset":248683,"count":1}],"isBlockCoverage":true},{"functionName":"_MultiChannel","ranges":[{"startOffset":248704,"endOffset":249002,"count":1}],"isBlockCoverage":true},{"functionName":"_MultiChannel_closure","ranges":[{"startOffset":249031,"endOffset":249119,"count":1}],"isBlockCoverage":true},{"functionName":"_MultiChannel_closure0","ranges":[{"startOffset":249149,"endOffset":249215,"count":1}],"isBlockCoverage":true},{"functionName":"_MultiChannel_closure1","ranges":[{"startOffset":249245,"endOffset":249334,"count":1}],"isBlockCoverage":true},{"functionName":"_MultiChannel__closure","ranges":[{"startOffset":249364,"endOffset":249477,"count":1}],"isBlockCoverage":true},{"functionName":"_MultiChannel_virtualChannel_closure","ranges":[{"startOffset":249521,"endOffset":249629,"count":1}],"isBlockCoverage":true},{"functionName":"_MultiChannel_virtualChannel_closure0","ranges":[{"startOffset":249674,"endOffset":249783,"count":1}],"isBlockCoverage":true},{"functionName":"VirtualChannel","ranges":[{"startOffset":249805,"endOffset":249952,"count":1}],"isBlockCoverage":true},{"functionName":"current","ranges":[{"startOffset":249967,"endOffset":250526,"count":0}],"isBlockCoverage":false},{"functionName":"StreamChannelMixin","ranges":[{"startOffset":250559,"endOffset":250596,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":250814,"endOffset":250880,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":250900,"endOffset":250978,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":250996,"endOffset":251098,"count":0}],"isBlockCoverage":false},{"functionName":"noSuchMethod$1","ranges":[{"startOffset":251120,"endOffset":251378,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":251425,"endOffset":251482,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":251502,"endOffset":251569,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":251625,"endOffset":251686,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":251704,"endOffset":251751,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":251771,"endOffset":251813,"count":0}],"isBlockCoverage":false},{"functionName":"noSuchMethod$1","ranges":[{"startOffset":251835,"endOffset":251986,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":252061,"endOffset":252103,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":252121,"endOffset":252178,"count":0}],"isBlockCoverage":false},{"functionName":"waitUntilDone$0","ranges":[{"startOffset":252201,"endOffset":252266,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":252411,"endOffset":252677,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":252695,"endOffset":252762,"count":0}],"isBlockCoverage":false},{"functionName":"add$1","ranges":[{"startOffset":252825,"endOffset":253058,"count":1},{"startOffset":252978,"endOffset":253024,"count":0}],"isBlockCoverage":true},{"functionName":"removeAt$1","ranges":[{"startOffset":253076,"endOffset":253376,"count":0}],"isBlockCoverage":false},{"functionName":"insert$2","ranges":[{"startOffset":253392,"endOffset":253775,"count":0}],"isBlockCoverage":false},{"functionName":"insertAll$2","ranges":[{"startOffset":253794,"endOffset":254419,"count":0}],"isBlockCoverage":false},{"functionName":"removeLast$0","ranges":[{"startOffset":254439,"endOffset":254691,"count":0}],"isBlockCoverage":false},{"functionName":"addAll$1","ranges":[{"startOffset":254707,"endOffset":255155,"count":0}],"isBlockCoverage":false},{"functionName":"forEach$1","ranges":[{"startOffset":255172,"endOffset":255530,"count":0}],"isBlockCoverage":false},{"functionName":"join$1","ranges":[{"startOffset":255544,"endOffset":255804,"count":0}],"isBlockCoverage":false},{"functionName":"join$0","ranges":[{"startOffset":255818,"endOffset":255886,"count":0}],"isBlockCoverage":false},{"functionName":"fold$1$2","ranges":[{"startOffset":255902,"endOffset":256434,"count":1},{"startOffset":256336,"endOffset":256400,"count":0}],"isBlockCoverage":true},{"functionName":"elementAt$1","ranges":[{"startOffset":256453,"endOffset":256606,"count":0}],"isBlockCoverage":false},{"functionName":"sublist$2","ranges":[{"startOffset":256623,"endOffset":257154,"count":0}],"isBlockCoverage":false},{"functionName":"get$first","ranges":[{"startOffset":257171,"endOffset":257321,"count":0}],"isBlockCoverage":false},{"functionName":"get$last","ranges":[{"startOffset":257337,"endOffset":257511,"count":0}],"isBlockCoverage":false},{"functionName":"setRange$4","ranges":[{"startOffset":257529,"endOffset":258508,"count":0}],"isBlockCoverage":false},{"functionName":"setRange$3","ranges":[{"startOffset":258526,"endOffset":258641,"count":0}],"isBlockCoverage":false},{"functionName":"get$isNotEmpty","ranges":[{"startOffset":258663,"endOffset":258725,"count":1}],"isBlockCoverage":true},{"functionName":"toString$0","ranges":[{"startOffset":258743,"endOffset":258839,"count":0}],"isBlockCoverage":false},{"functionName":"get$iterator","ranges":[{"startOffset":258859,"endOffset":258990,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":259010,"endOffset":259088,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":259106,"endOffset":259162,"count":1}],"isBlockCoverage":true},{"functionName":"set$length","ranges":[{"startOffset":259180,"endOffset":259464,"count":1},{"startOffset":259255,"endOffset":259308,"count":0},{"startOffset":259342,"endOffset":259423,"count":0}],"isBlockCoverage":true},{"functionName":"$index","ranges":[{"startOffset":259478,"endOffset":259689,"count":1},{"startOffset":259592,"endOffset":259653,"count":0}],"isBlockCoverage":true},{"functionName":"$indexSet","ranges":[{"startOffset":259706,"endOffset":260245,"count":1},{"startOffset":259897,"endOffset":259951,"count":0},{"startOffset":260028,"endOffset":260089,"count":0},{"startOffset":260147,"endOffset":260208,"count":0}],"isBlockCoverage":true},{"functionName":"get$current","ranges":[{"startOffset":260411,"endOffset":260472,"count":0}],"isBlockCoverage":false},{"functionName":"moveNext$0","ranges":[{"startOffset":260490,"endOffset":260928,"count":0}],"isBlockCoverage":false},{"functionName":"set$__interceptors$_current","ranges":[{"startOffset":260963,"endOffset":261101,"count":0}],"isBlockCoverage":false},{"functionName":"toRadixString$1","ranges":[{"startOffset":261175,"endOffset":262041,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":262059,"endOffset":262195,"count":1},{"startOffset":262134,"endOffset":262148,"count":0}],"isBlockCoverage":true},{"functionName":"get$hashCode","ranges":[{"startOffset":262215,"endOffset":262729,"count":0}],"isBlockCoverage":false},{"functionName":"$mod","ranges":[{"startOffset":262741,"endOffset":262992,"count":0}],"isBlockCoverage":false},{"functionName":"$tdiv","ranges":[{"startOffset":263005,"endOffset":263202,"count":0}],"isBlockCoverage":false},{"functionName":"_tdivFast$1","ranges":[{"startOffset":263221,"endOffset":263355,"count":1},{"startOffset":263313,"endOffset":263348,"count":0}],"isBlockCoverage":true},{"functionName":"_tdivSlow$1","ranges":[{"startOffset":263374,"endOffset":263848,"count":0}],"isBlockCoverage":false},{"functionName":"_shrOtherPositive$1","ranges":[{"startOffset":263875,"endOffset":264112,"count":1},{"startOffset":263995,"endOffset":264089,"count":0}],"isBlockCoverage":true},{"functionName":"_shrReceiverPositive$1","ranges":[{"startOffset":264142,"endOffset":264311,"count":0}],"isBlockCoverage":false},{"functionName":"_shrBothPositive$1","ranges":[{"startOffset":264337,"endOffset":264420,"count":1},{"startOffset":264389,"endOffset":264392,"count":0}],"isBlockCoverage":true},{"functionName":"$gt","ranges":[{"startOffset":264431,"endOffset":264592,"count":0}],"isBlockCoverage":false},{"functionName":"codeUnitAt$1","ranges":[{"startOffset":264740,"endOffset":265145,"count":1},{"startOffset":264844,"endOffset":264905,"count":0},{"startOffset":264935,"endOffset":264996,"count":0},{"startOffset":265041,"endOffset":265098,"count":0}],"isBlockCoverage":true},{"functionName":"_codeUnitAt$1","ranges":[{"startOffset":265166,"endOffset":265346,"count":1},{"startOffset":265238,"endOffset":265299,"count":0}],"isBlockCoverage":true},{"functionName":"allMatches$2","ranges":[{"startOffset":265366,"endOffset":265723,"count":0}],"isBlockCoverage":false},{"functionName":"allMatches$1","ranges":[{"startOffset":265743,"endOffset":265832,"count":0}],"isBlockCoverage":false},{"functionName":"matchAsPrefix$2","ranges":[{"startOffset":265855,"endOffset":266378,"count":0}],"isBlockCoverage":false},{"functionName":"$add","ranges":[{"startOffset":266390,"endOffset":266564,"count":0}],"isBlockCoverage":false},{"functionName":"endsWith$1","ranges":[{"startOffset":266582,"endOffset":266823,"count":0}],"isBlockCoverage":false},{"functionName":"replaceFirst$2","ranges":[{"startOffset":266845,"endOffset":267026,"count":0}],"isBlockCoverage":false},{"functionName":"replaceRange$3","ranges":[{"startOffset":267048,"endOffset":267373,"count":0}],"isBlockCoverage":false},{"functionName":"startsWith$2","ranges":[{"startOffset":267393,"endOffset":268070,"count":1},{"startOffset":267526,"endOffset":267573,"count":0},{"startOffset":267619,"endOffset":267638,"count":0},{"startOffset":267695,"endOffset":267776,"count":0},{"startOffset":267911,"endOffset":267924,"count":0},{"startOffset":267996,"endOffset":268069,"count":0}],"isBlockCoverage":true},{"functionName":"startsWith$1","ranges":[{"startOffset":268090,"endOffset":268181,"count":1}],"isBlockCoverage":true},{"functionName":"substring$2","ranges":[{"startOffset":268200,"endOffset":268888,"count":1},{"startOffset":268334,"endOffset":268386,"count":0},{"startOffset":268501,"endOffset":268525,"count":0},{"startOffset":268560,"endOffset":268620,"count":0},{"startOffset":268662,"endOffset":268722,"count":0},{"startOffset":268769,"endOffset":268827,"count":0}],"isBlockCoverage":true},{"functionName":"substring$1","ranges":[{"startOffset":268907,"endOffset":269006,"count":1}],"isBlockCoverage":true},{"functionName":"trim$0","ranges":[{"startOffset":269020,"endOffset":269700,"count":0}],"isBlockCoverage":false},{"functionName":"$mul","ranges":[{"startOffset":269712,"endOffset":270191,"count":0}],"isBlockCoverage":false},{"functionName":"padRight$1","ranges":[{"startOffset":270209,"endOffset":270458,"count":0}],"isBlockCoverage":false},{"functionName":"indexOf$2","ranges":[{"startOffset":270475,"endOffset":270731,"count":1},{"startOffset":270582,"endOffset":270663,"count":0}],"isBlockCoverage":true},{"functionName":"indexOf$1","ranges":[{"startOffset":270748,"endOffset":270836,"count":1}],"isBlockCoverage":true},{"functionName":"lastIndexOf$2","ranges":[{"startOffset":270857,"endOffset":271276,"count":0}],"isBlockCoverage":false},{"functionName":"lastIndexOf$1","ranges":[{"startOffset":271297,"endOffset":271392,"count":0}],"isBlockCoverage":false},{"functionName":"contains$1","ranges":[{"startOffset":271410,"endOffset":271584,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":271602,"endOffset":271651,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":271671,"endOffset":272077,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":272095,"endOffset":272151,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":272165,"endOffset":272372,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":272459,"endOffset":272512,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":272526,"endOffset":272627,"count":0}],"isBlockCoverage":false},{"functionName":"$asEfficientLengthIterable","ranges":[{"startOffset":272661,"endOffset":272701,"count":0}],"isBlockCoverage":false},{"functionName":"$asUnmodifiableListMixin","ranges":[{"startOffset":272733,"endOffset":272773,"count":0}],"isBlockCoverage":false},{"functionName":"$asListMixin","ranges":[{"startOffset":272793,"endOffset":272833,"count":0}],"isBlockCoverage":false},{"functionName":"$asIterable","ranges":[{"startOffset":272852,"endOffset":272892,"count":0}],"isBlockCoverage":false},{"functionName":"$asList","ranges":[{"startOffset":272907,"endOffset":272947,"count":0}],"isBlockCoverage":false},{"functionName":"get$iterator","ranges":[{"startOffset":273046,"endOffset":273183,"count":0}],"isBlockCoverage":false},{"functionName":"get$isEmpty","ranges":[{"startOffset":273202,"endOffset":273263,"count":0}],"isBlockCoverage":false},{"functionName":"join$1","ranges":[{"startOffset":273277,"endOffset":274216,"count":0}],"isBlockCoverage":false},{"functionName":"join$0","ranges":[{"startOffset":274230,"endOffset":274298,"count":0}],"isBlockCoverage":false},{"functionName":"fold$1$2","ranges":[{"startOffset":274314,"endOffset":274870,"count":0}],"isBlockCoverage":false},{"functionName":"toList$1$growable","ranges":[{"startOffset":274895,"endOffset":275249,"count":0}],"isBlockCoverage":false},{"functionName":"toList$0","ranges":[{"startOffset":275265,"endOffset":275346,"count":0}],"isBlockCoverage":false},{"functionName":"get$_endIndex","ranges":[{"startOffset":275405,"endOffset":275616,"count":0}],"isBlockCoverage":false},{"functionName":"get$_startIndex","ranges":[{"startOffset":275639,"endOffset":275841,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":275859,"endOffset":276224,"count":0}],"isBlockCoverage":false},{"functionName":"elementAt$1","ranges":[{"startOffset":276243,"endOffset":276692,"count":0}],"isBlockCoverage":false},{"functionName":"get$current","ranges":[{"startOffset":276746,"endOffset":276803,"count":0}],"isBlockCoverage":false},{"functionName":"moveNext$0","ranges":[{"startOffset":276821,"endOffset":277341,"count":0}],"isBlockCoverage":false},{"functionName":"set$__internal$_current","ranges":[{"startOffset":277372,"endOffset":277506,"count":0}],"isBlockCoverage":false},{"functionName":"get$iterator","ranges":[{"startOffset":277583,"endOffset":277702,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":277720,"endOffset":277797,"count":0}],"isBlockCoverage":false},{"functionName":"$asIterable","ranges":[{"startOffset":277816,"endOffset":277859,"count":0}],"isBlockCoverage":false},{"functionName":"$asEfficientLengthIterable","ranges":[{"startOffset":277975,"endOffset":278018,"count":0}],"isBlockCoverage":false},{"functionName":"moveNext$0","ranges":[{"startOffset":278073,"endOffset":278314,"count":0}],"isBlockCoverage":false},{"functionName":"get$current","ranges":[{"startOffset":278333,"endOffset":278390,"count":0}],"isBlockCoverage":false},{"functionName":"set$__internal$_current","ranges":[{"startOffset":278421,"endOffset":278555,"count":0}],"isBlockCoverage":false},{"functionName":"$asIterator","ranges":[{"startOffset":278574,"endOffset":278617,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":278676,"endOffset":278740,"count":0}],"isBlockCoverage":false},{"functionName":"elementAt$1","ranges":[{"startOffset":278759,"endOffset":278853,"count":0}],"isBlockCoverage":false},{"functionName":"$asEfficientLengthIterable","ranges":[{"startOffset":278887,"endOffset":278930,"count":0}],"isBlockCoverage":false},{"functionName":"$asListIterable","ranges":[{"startOffset":278953,"endOffset":278996,"count":0}],"isBlockCoverage":false},{"functionName":"$asIterable","ranges":[{"startOffset":279015,"endOffset":279058,"count":0}],"isBlockCoverage":false},{"functionName":"get$iterator","ranges":[{"startOffset":279114,"endOffset":279232,"count":0}],"isBlockCoverage":false},{"functionName":"moveNext$0","ranges":[{"startOffset":279286,"endOffset":279470,"count":0}],"isBlockCoverage":false},{"functionName":"get$current","ranges":[{"startOffset":279489,"endOffset":279550,"count":0}],"isBlockCoverage":false},{"functionName":"get$iterator","ranges":[{"startOffset":279607,"endOffset":279745,"count":0}],"isBlockCoverage":false},{"functionName":"$asIterable","ranges":[{"startOffset":279764,"endOffset":279807,"count":0}],"isBlockCoverage":false},{"functionName":"get$current","ranges":[{"startOffset":279863,"endOffset":279920,"count":0}],"isBlockCoverage":false},{"functionName":"moveNext$0","ranges":[{"startOffset":279938,"endOffset":280470,"count":0}],"isBlockCoverage":false},{"functionName":"set$_currentExpansion","ranges":[{"startOffset":280499,"endOffset":280668,"count":0}],"isBlockCoverage":false},{"functionName":"set$__internal$_current","ranges":[{"startOffset":280699,"endOffset":280833,"count":0}],"isBlockCoverage":false},{"functionName":"$asIterator","ranges":[{"startOffset":280872,"endOffset":280915,"count":0}],"isBlockCoverage":false},{"functionName":"get$iterator","ranges":[{"startOffset":280975,"endOffset":281097,"count":0}],"isBlockCoverage":false},{"functionName":"moveNext$0","ranges":[{"startOffset":281155,"endOffset":281440,"count":0}],"isBlockCoverage":false},{"functionName":"get$current","ranges":[{"startOffset":281459,"endOffset":281520,"count":0}],"isBlockCoverage":false},{"functionName":"moveNext$0","ranges":[{"startOffset":281574,"endOffset":281612,"count":0}],"isBlockCoverage":false},{"functionName":"get$current","ranges":[{"startOffset":281631,"endOffset":281663,"count":0}],"isBlockCoverage":false},{"functionName":"$indexSet","ranges":[{"startOffset":281785,"endOffset":282038,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":282140,"endOffset":282204,"count":0}],"isBlockCoverage":false},{"functionName":"elementAt$1","ranges":[{"startOffset":282223,"endOffset":282393,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":282442,"endOffset":282661,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":282679,"endOffset":282759,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":282770,"endOffset":282934,"count":0}],"isBlockCoverage":false},{"functionName":"get$isEmpty","ranges":[{"startOffset":283042,"endOffset":283103,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":283121,"endOffset":283182,"count":0}],"isBlockCoverage":false},{"functionName":"$indexSet","ranges":[{"startOffset":283199,"endOffset":283429,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":283502,"endOffset":283560,"count":0}],"isBlockCoverage":false},{"functionName":"containsKey$1","ranges":[{"startOffset":283581,"endOffset":283761,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":283775,"endOffset":283884,"count":0}],"isBlockCoverage":false},{"functionName":"_fetch$1","ranges":[{"startOffset":283900,"endOffset":283974,"count":0}],"isBlockCoverage":false},{"functionName":"forEach$1","ranges":[{"startOffset":283991,"endOffset":284372,"count":0}],"isBlockCoverage":false},{"functionName":"Instantiation$1","ranges":[{"startOffset":284431,"endOffset":284530,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":284548,"endOffset":284741,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":284792,"endOffset":284881,"count":0}],"isBlockCoverage":false},{"functionName":"call$4","ranges":[{"startOffset":284895,"endOffset":285000,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":285018,"endOffset":285157,"count":0}],"isBlockCoverage":false},{"functionName":"get$memberName","ranges":[{"startOffset":285220,"endOffset":285288,"count":0}],"isBlockCoverage":false},{"functionName":"get$positionalArguments","ranges":[{"startOffset":285319,"endOffset":285850,"count":0}],"isBlockCoverage":false},{"functionName":"get$namedArguments","ranges":[{"startOffset":285876,"endOffset":286722,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":286820,"endOffset":287115,"count":0}],"isBlockCoverage":false},{"functionName":"matchTypeError$1","ranges":[{"startOffset":287198,"endOffset":287850,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":287900,"endOffset":288103,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":288163,"endOffset":288563,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":288622,"endOffset":288728,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":288838,"endOffset":289012,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":289083,"endOffset":289391,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":289461,"endOffset":289555,"count":0}],"isBlockCoverage":false},{"functionName":"get$$call","ranges":[{"startOffset":289592,"endOffset":289629,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":289793,"endOffset":289982,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":290028,"endOffset":290329,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":290349,"endOffset":290718,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":290736,"endOffset":290971,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":291035,"endOffset":291081,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":291145,"endOffset":291191,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":291244,"endOffset":291314,"count":0}],"isBlockCoverage":false},{"functionName":"get$_typeName","ranges":[{"startOffset":291366,"endOffset":291542,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":291560,"endOffset":291614,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":291634,"endOffset":291830,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":291841,"endOffset":292006,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":292062,"endOffset":292120,"count":1}],"isBlockCoverage":true},{"functionName":"get$isEmpty","ranges":[{"startOffset":292139,"endOffset":292203,"count":1}],"isBlockCoverage":true},{"functionName":"get$keys","ranges":[{"startOffset":292219,"endOffset":292325,"count":0}],"isBlockCoverage":false},{"functionName":"get$values","ranges":[{"startOffset":292343,"endOffset":292585,"count":0}],"isBlockCoverage":false},{"functionName":"containsKey$1","ranges":[{"startOffset":292606,"endOffset":293097,"count":1},{"startOffset":292682,"endOffset":292834,"count":0},{"startOffset":292963,"endOffset":292976,"count":0},{"startOffset":293038,"endOffset":293091,"count":0}],"isBlockCoverage":true},{"functionName":"internalContainsKey$1","ranges":[{"startOffset":293126,"endOffset":293341,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":293355,"endOffset":293981,"count":1},{"startOffset":293518,"endOffset":293525,"count":0},{"startOffset":293603,"endOffset":293609,"count":0},{"startOffset":293790,"endOffset":293797,"count":0},{"startOffset":293872,"endOffset":293878,"count":0},{"startOffset":293930,"endOffset":293975,"count":0}],"isBlockCoverage":true},{"functionName":"internalGet$1","ranges":[{"startOffset":294002,"endOffset":294339,"count":0}],"isBlockCoverage":false},{"functionName":"$indexSet","ranges":[{"startOffset":294356,"endOffset":295711,"count":1},{"startOffset":295102,"endOffset":295705,"count":0}],"isBlockCoverage":true},{"functionName":"putIfAbsent$2","ranges":[{"startOffset":295732,"endOffset":296105,"count":1},{"startOffset":296009,"endOffset":296104,"count":0}],"isBlockCoverage":true},{"functionName":"remove$1","ranges":[{"startOffset":296121,"endOffset":296430,"count":0}],"isBlockCoverage":false},{"functionName":"internalRemove$1","ranges":[{"startOffset":296454,"endOffset":296861,"count":0}],"isBlockCoverage":false},{"functionName":"clear$0","ranges":[{"startOffset":296876,"endOffset":297150,"count":0}],"isBlockCoverage":false},{"functionName":"forEach$1","ranges":[{"startOffset":297167,"endOffset":297681,"count":1},{"startOffset":297580,"endOffset":297640,"count":0}],"isBlockCoverage":true},{"functionName":"_addHashTableEntry$3","ranges":[{"startOffset":297709,"endOffset":298113,"count":1},{"startOffset":298057,"endOffset":298107,"count":0}],"isBlockCoverage":true},{"functionName":"_removeHashTableEntry$2","ranges":[{"startOffset":298144,"endOffset":298428,"count":0}],"isBlockCoverage":false},{"functionName":"_modified$0","ranges":[{"startOffset":298447,"endOffset":298529,"count":1}],"isBlockCoverage":true},{"functionName":"_newLinkedCell$2","ranges":[{"startOffset":298553,"endOffset":299085,"count":1}],"isBlockCoverage":true},{"functionName":"_unlinkCell$1","ranges":[{"startOffset":299106,"endOffset":299471,"count":0}],"isBlockCoverage":false},{"functionName":"internalFindBucketIndex$2","ranges":[{"startOffset":299504,"endOffset":299755,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":299773,"endOffset":299834,"count":0}],"isBlockCoverage":false},{"functionName":"_getTableCell$2","ranges":[{"startOffset":299857,"endOffset":299910,"count":1}],"isBlockCoverage":true},{"functionName":"_getTableBucket$2","ranges":[{"startOffset":299935,"endOffset":299988,"count":0}],"isBlockCoverage":false},{"functionName":"_setTableEntry$3","ranges":[{"startOffset":300012,"endOffset":300073,"count":1}],"isBlockCoverage":true},{"functionName":"_deleteTableEntry$2","ranges":[{"startOffset":300100,"endOffset":300153,"count":1}],"isBlockCoverage":true},{"functionName":"_containsTableEntry$2","ranges":[{"startOffset":300182,"endOffset":300265,"count":1}],"isBlockCoverage":true},{"functionName":"_newHashTable$0","ranges":[{"startOffset":300288,"endOffset":300495,"count":1}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":300587,"endOffset":300732,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":300750,"endOffset":300898,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":301001,"endOffset":301076,"count":0}],"isBlockCoverage":false},{"functionName":"get$isEmpty","ranges":[{"startOffset":301095,"endOffset":301176,"count":0}],"isBlockCoverage":false},{"functionName":"get$iterator","ranges":[{"startOffset":301196,"endOffset":301388,"count":0}],"isBlockCoverage":false},{"functionName":"get$current","ranges":[{"startOffset":301454,"endOffset":301512,"count":0}],"isBlockCoverage":false},{"functionName":"moveNext$0","ranges":[{"startOffset":301530,"endOffset":302000,"count":0}],"isBlockCoverage":false},{"functionName":"set$__js_helper$_current","ranges":[{"startOffset":302032,"endOffset":302167,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":302241,"endOffset":302289,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":302363,"endOffset":302428,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":302503,"endOffset":302583,"count":1}],"isBlockCoverage":true},{"functionName":"toString$0","ranges":[{"startOffset":302658,"endOffset":302722,"count":0}],"isBlockCoverage":false},{"functionName":"get$_nativeGlobalVersion","ranges":[{"startOffset":302754,"endOffset":303029,"count":0}],"isBlockCoverage":false},{"functionName":"get$_nativeAnchoredVersion","ranges":[{"startOffset":303063,"endOffset":303350,"count":0}],"isBlockCoverage":false},{"functionName":"firstMatch$1","ranges":[{"startOffset":303370,"endOffset":303626,"count":0}],"isBlockCoverage":false},{"functionName":"allMatches$2","ranges":[{"startOffset":303646,"endOffset":303862,"count":0}],"isBlockCoverage":false},{"functionName":"allMatches$1","ranges":[{"startOffset":303882,"endOffset":303971,"count":0}],"isBlockCoverage":false},{"functionName":"_execGlobal$2","ranges":[{"startOffset":303992,"endOffset":304252,"count":0}],"isBlockCoverage":false},{"functionName":"_execAnchored$2","ranges":[{"startOffset":304275,"endOffset":304648,"count":0}],"isBlockCoverage":false},{"functionName":"matchAsPrefix$2","ranges":[{"startOffset":304671,"endOffset":304954,"count":0}],"isBlockCoverage":false},{"functionName":"get$start","ranges":[{"startOffset":305051,"endOffset":305102,"count":0}],"isBlockCoverage":false},{"functionName":"get$end","ranges":[{"startOffset":305117,"endOffset":305201,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":305215,"endOffset":305397,"count":0}],"isBlockCoverage":false},{"functionName":"get$iterator","ranges":[{"startOffset":305476,"endOffset":305584,"count":0}],"isBlockCoverage":false},{"functionName":"$asIterable","ranges":[{"startOffset":305603,"endOffset":305645,"count":0}],"isBlockCoverage":false},{"functionName":"get$current","ranges":[{"startOffset":305706,"endOffset":305764,"count":0}],"isBlockCoverage":false},{"functionName":"moveNext$0","ranges":[{"startOffset":305782,"endOffset":306369,"count":0}],"isBlockCoverage":false},{"functionName":"$asIterator","ranges":[{"startOffset":306408,"endOffset":306450,"count":0}],"isBlockCoverage":false},{"functionName":"get$end","ranges":[{"startOffset":306499,"endOffset":306643,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":306657,"endOffset":306806,"count":0}],"isBlockCoverage":false},{"functionName":"get$start","ranges":[{"startOffset":306840,"endOffset":306891,"count":0}],"isBlockCoverage":false},{"functionName":"get$iterator","ranges":[{"startOffset":306959,"endOffset":307077,"count":0}],"isBlockCoverage":false},{"functionName":"$asIterable","ranges":[{"startOffset":307096,"endOffset":307138,"count":0}],"isBlockCoverage":false},{"functionName":"moveNext$0","ranges":[{"startOffset":307204,"endOffset":307851,"count":0}],"isBlockCoverage":false},{"functionName":"get$current","ranges":[{"startOffset":307870,"endOffset":307928,"count":0}],"isBlockCoverage":false},{"functionName":"$asIterator","ranges":[{"startOffset":307967,"endOffset":308009,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":308182,"endOffset":308238,"count":0}],"isBlockCoverage":false},{"functionName":"$asJavaScriptIndexingBehavior","ranges":[{"startOffset":308313,"endOffset":308331,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":308392,"endOffset":308544,"count":0}],"isBlockCoverage":false},{"functionName":"$indexSet","ranges":[{"startOffset":308561,"endOffset":308753,"count":0}],"isBlockCoverage":false},{"functionName":"$asEfficientLengthIterable","ranges":[{"startOffset":308822,"endOffset":308865,"count":0}],"isBlockCoverage":false},{"functionName":"$asFixedLengthListMixin","ranges":[{"startOffset":308896,"endOffset":308939,"count":0}],"isBlockCoverage":false},{"functionName":"$asListMixin","ranges":[{"startOffset":308959,"endOffset":309002,"count":0}],"isBlockCoverage":false},{"functionName":"$asIterable","ranges":[{"startOffset":309041,"endOffset":309084,"count":0}],"isBlockCoverage":false},{"functionName":"$asList","ranges":[{"startOffset":309115,"endOffset":309158,"count":0}],"isBlockCoverage":false},{"functionName":"$indexSet","ranges":[{"startOffset":309219,"endOffset":309408,"count":1}],"isBlockCoverage":true},{"functionName":"$asEfficientLengthIterable","ranges":[{"startOffset":309477,"endOffset":309517,"count":0}],"isBlockCoverage":false},{"functionName":"$asFixedLengthListMixin","ranges":[{"startOffset":309548,"endOffset":309588,"count":0}],"isBlockCoverage":false},{"functionName":"$asListMixin","ranges":[{"startOffset":309608,"endOffset":309648,"count":1}],"isBlockCoverage":true},{"functionName":"$asIterable","ranges":[{"startOffset":309687,"endOffset":309727,"count":0}],"isBlockCoverage":false},{"functionName":"$asList","ranges":[{"startOffset":309758,"endOffset":309798,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":309850,"endOffset":310002,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":310054,"endOffset":310206,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":310257,"endOffset":310409,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":310462,"endOffset":310614,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":310667,"endOffset":310819,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":310882,"endOffset":310938,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":310952,"endOffset":311104,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":311160,"endOffset":311216,"count":1}],"isBlockCoverage":true},{"functionName":"$index","ranges":[{"startOffset":311230,"endOffset":311382,"count":0}],"isBlockCoverage":false},{"functionName":"sublist$2","ranges":[{"startOffset":311399,"endOffset":311541,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":312009,"endOffset":312148,"count":1}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":312250,"endOffset":312486,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":312598,"endOffset":312646,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":312812,"endOffset":312860,"count":0}],"isBlockCoverage":false},{"functionName":"_TimerImpl$2","ranges":[{"startOffset":312981,"endOffset":313267,"count":0}],"isBlockCoverage":false},{"functionName":"_TimerImpl$periodic$2","ranges":[{"startOffset":313296,"endOffset":313599,"count":1},{"startOffset":313511,"endOffset":313593,"count":0}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":313680,"endOffset":313756,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":313888,"endOffset":314241,"count":0}],"isBlockCoverage":false},{"functionName":"complete$1","ranges":[{"startOffset":314370,"endOffset":314852,"count":1},{"startOffset":314559,"endOffset":314846,"count":0}],"isBlockCoverage":true},{"functionName":"completeError$2","ranges":[{"startOffset":314875,"endOffset":315072,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":315167,"endOffset":315240,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":315387,"endOffset":315467,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":315594,"endOffset":315668,"count":1}],"isBlockCoverage":true},{"functionName":"call$2","ranges":[{"startOffset":315747,"endOffset":315907,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":316044,"endOffset":316137,"count":1}],"isBlockCoverage":true},{"functionName":"completeError$2","ranges":[{"startOffset":316289,"endOffset":316893,"count":0}],"isBlockCoverage":false},{"functionName":"completeError$1","ranges":[{"startOffset":316916,"endOffset":316987,"count":0}],"isBlockCoverage":false},{"functionName":"complete$1","ranges":[{"startOffset":317064,"endOffset":317349,"count":1},{"startOffset":317244,"endOffset":317309,"count":0}],"isBlockCoverage":true},{"functionName":"complete$0","ranges":[{"startOffset":317367,"endOffset":317441,"count":1}],"isBlockCoverage":true},{"functionName":"_completeError$2","ranges":[{"startOffset":317465,"endOffset":317560,"count":0}],"isBlockCoverage":false},{"functionName":"complete$1","ranges":[{"startOffset":317615,"endOffset":317895,"count":1},{"startOffset":317795,"endOffset":317860,"count":0}],"isBlockCoverage":true},{"functionName":"complete$0","ranges":[{"startOffset":317913,"endOffset":317987,"count":0}],"isBlockCoverage":false},{"functionName":"_completeError$2","ranges":[{"startOffset":318011,"endOffset":318101,"count":0}],"isBlockCoverage":false},{"functionName":"matchesErrorTest$1","ranges":[{"startOffset":318165,"endOffset":318401,"count":0}],"isBlockCoverage":false},{"functionName":"handleError$1","ranges":[{"startOffset":318422,"endOffset":319026,"count":0}],"isBlockCoverage":false},{"functionName":"then$1$2$onError","ranges":[{"startOffset":319080,"endOffset":319598,"count":1},{"startOffset":319465,"endOffset":319521,"count":0}],"isBlockCoverage":true},{"functionName":"then$1$1","ranges":[{"startOffset":319614,"endOffset":319686,"count":1}],"isBlockCoverage":true},{"functionName":"_thenNoZoneRegistration$1$2","ranges":[{"startOffset":319721,"endOffset":320105,"count":1}],"isBlockCoverage":true},{"functionName":"whenComplete$1","ranges":[{"startOffset":320127,"endOffset":320534,"count":0}],"isBlockCoverage":false},{"functionName":"_addListener$1","ranges":[{"startOffset":320556,"endOffset":321250,"count":1},{"startOffset":320829,"endOffset":321144,"count":0}],"isBlockCoverage":true},{"functionName":"_prependListeners$1","ranges":[{"startOffset":321277,"endOffset":322404,"count":0}],"isBlockCoverage":false},{"functionName":"_removeListeners$0","ranges":[{"startOffset":322430,"endOffset":322625,"count":1}],"isBlockCoverage":true},{"functionName":"_reverseListeners$1","ranges":[{"startOffset":322652,"endOffset":322909,"count":1}],"isBlockCoverage":true},{"functionName":"_complete$1","ranges":[{"startOffset":322928,"endOffset":323583,"count":1},{"startOffset":323168,"endOffset":323336,"count":0}],"isBlockCoverage":true},{"functionName":"_completeError$2","ranges":[{"startOffset":323607,"endOffset":323915,"count":0}],"isBlockCoverage":false},{"functionName":"_completeError$1","ranges":[{"startOffset":323939,"endOffset":324011,"count":0}],"isBlockCoverage":false},{"functionName":"_asyncComplete$1","ranges":[{"startOffset":324035,"endOffset":324387,"count":1},{"startOffset":324208,"endOffset":324269,"count":0}],"isBlockCoverage":true},{"functionName":"_chainFuture$1","ranges":[{"startOffset":324409,"endOffset":324868,"count":0}],"isBlockCoverage":false},{"functionName":"_asyncCompleteError$2","ranges":[{"startOffset":324897,"endOffset":325120,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":325203,"endOffset":325287,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":325425,"endOffset":325517,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":325657,"endOffset":325758,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":325850,"endOffset":325999,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":326013,"endOffset":326075,"count":0}],"isBlockCoverage":false},{"functionName":"$defaultValues","ranges":[{"startOffset":326120,"endOffset":326159,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":326252,"endOffset":326322,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":326457,"endOffset":326765,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":326898,"endOffset":326974,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":327114,"endOffset":327196,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":327357,"endOffset":328955,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":329075,"endOffset":329128,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":329234,"endOffset":329953,"count":1},{"startOffset":329710,"endOffset":329947,"count":0}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":330050,"endOffset":330966,"count":0}],"isBlockCoverage":false},{"functionName":"pipe$1","ranges":[{"startOffset":331068,"endOffset":331333,"count":1}],"isBlockCoverage":true},{"functionName":"get$length","ranges":[{"startOffset":331351,"endOffset":331673,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":331729,"endOffset":331793,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":331871,"endOffset":332007,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":332025,"endOffset":332139,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":332198,"endOffset":332266,"count":0}],"isBlockCoverage":false},{"functionName":"get$_pendingEvents","ranges":[{"startOffset":332504,"endOffset":332877,"count":1},{"startOffset":332650,"endOffset":332876,"count":0}],"isBlockCoverage":true},{"functionName":"_ensurePendingEvents$0","ranges":[{"startOffset":332907,"endOffset":333600,"count":0}],"isBlockCoverage":false},{"functionName":"get$_subscription","ranges":[{"startOffset":333625,"endOffset":334046,"count":1},{"startOffset":333673,"endOffset":333925,"count":0}],"isBlockCoverage":true},{"functionName":"_badEventState$0","ranges":[{"startOffset":334070,"endOffset":334263,"count":0}],"isBlockCoverage":false},{"functionName":"_ensureDoneFuture$0","ranges":[{"startOffset":334290,"endOffset":334533,"count":0}],"isBlockCoverage":false},{"functionName":"add$1","ranges":[{"startOffset":334546,"endOffset":334760,"count":1},{"startOffset":334681,"endOffset":334728,"count":0}],"isBlockCoverage":true},{"functionName":"addError$2","ranges":[{"startOffset":334778,"endOffset":335358,"count":0}],"isBlockCoverage":false},{"functionName":"addError$1","ranges":[{"startOffset":335376,"endOffset":335442,"count":0}],"isBlockCoverage":false},{"functionName":"close$0","ranges":[{"startOffset":335457,"endOffset":335879,"count":0}],"isBlockCoverage":false},{"functionName":"_add$1","ranges":[{"startOffset":335893,"endOffset":336207,"count":1},{"startOffset":336085,"endOffset":336201,"count":0}],"isBlockCoverage":true},{"functionName":"_async$_addError$2","ranges":[{"startOffset":336233,"endOffset":336487,"count":0}],"isBlockCoverage":false},{"functionName":"_subscribe$4","ranges":[{"startOffset":336507,"endOffset":337709,"count":1},{"startOffset":336834,"endOffset":336911,"count":0},{"startOffset":336965,"endOffset":336968,"count":0},{"startOffset":337264,"endOffset":337491,"count":0}],"isBlockCoverage":true},{"functionName":"_recordCancel$1","ranges":[{"startOffset":337732,"endOffset":338892,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":339105,"endOffset":339165,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":339261,"endOffset":339394,"count":0}],"isBlockCoverage":false},{"functionName":"_sendData$1","ranges":[{"startOffset":339533,"endOffset":339677,"count":1}],"isBlockCoverage":true},{"functionName":"_sendError$2","ranges":[{"startOffset":339697,"endOffset":339802,"count":0}],"isBlockCoverage":false},{"functionName":"_sendDone$0","ranges":[{"startOffset":339821,"endOffset":339882,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":339984,"endOffset":340083,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":340094,"endOffset":340307,"count":0}],"isBlockCoverage":false},{"functionName":"_onCancel$0","ranges":[{"startOffset":340372,"endOffset":340443,"count":0}],"isBlockCoverage":false},{"functionName":"_onPause$0","ranges":[{"startOffset":340461,"endOffset":340868,"count":0}],"isBlockCoverage":false},{"functionName":"_onResume$0","ranges":[{"startOffset":340887,"endOffset":341296,"count":0}],"isBlockCoverage":false},{"functionName":"add$1","ranges":[{"startOffset":341350,"endOffset":341482,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":341597,"endOffset":341672,"count":0}],"isBlockCoverage":false},{"functionName":"_BufferingStreamSubscription$4","ranges":[{"startOffset":341780,"endOffset":343046,"count":1},{"startOffset":342069,"endOffset":342106,"count":0},{"startOffset":342501,"endOffset":342838,"count":0}],"isBlockCoverage":true},{"functionName":"_setPendingEvents$1","ranges":[{"startOffset":343073,"endOffset":343490,"count":1},{"startOffset":343296,"endOffset":343484,"count":0}],"isBlockCoverage":true},{"functionName":"cancel$0","ranges":[{"startOffset":343506,"endOffset":343740,"count":0}],"isBlockCoverage":false},{"functionName":"_cancel$0","ranges":[{"startOffset":343757,"endOffset":344079,"count":0}],"isBlockCoverage":false},{"functionName":"_add$1","ranges":[{"startOffset":344093,"endOffset":344445,"count":1},{"startOffset":344310,"endOffset":344317,"count":0},{"startOffset":344368,"endOffset":344439,"count":0}],"isBlockCoverage":true},{"functionName":"_async$_addError$2","ranges":[{"startOffset":344471,"endOffset":344720,"count":0}],"isBlockCoverage":false},{"functionName":"_close$0","ranges":[{"startOffset":344736,"endOffset":344979,"count":0}],"isBlockCoverage":false},{"functionName":"_onPause$0","ranges":[{"startOffset":344997,"endOffset":345015,"count":0}],"isBlockCoverage":false},{"functionName":"_onResume$0","ranges":[{"startOffset":345034,"endOffset":345052,"count":0}],"isBlockCoverage":false},{"functionName":"_onCancel$0","ranges":[{"startOffset":345071,"endOffset":345103,"count":0}],"isBlockCoverage":false},{"functionName":"_addPending$1","ranges":[{"startOffset":345124,"endOffset":345687,"count":0}],"isBlockCoverage":false},{"functionName":"_sendData$1","ranges":[{"startOffset":345706,"endOffset":346096,"count":1}],"isBlockCoverage":true},{"functionName":"_sendError$2","ranges":[{"startOffset":346116,"endOffset":346631,"count":0}],"isBlockCoverage":false},{"functionName":"_sendDone$0","ranges":[{"startOffset":346650,"endOffset":346982,"count":0}],"isBlockCoverage":false},{"functionName":"_guardCallback$1","ranges":[{"startOffset":347006,"endOffset":347285,"count":1}],"isBlockCoverage":true},{"functionName":"_checkState$1","ranges":[{"startOffset":347306,"endOffset":348403,"count":1},{"startOffset":347416,"endOffset":347457,"count":0},{"startOffset":347459,"endOffset":347850,"count":0},{"startOffset":347932,"endOffset":347996,"count":0},{"startOffset":348099,"endOffset":348318,"count":0},{"startOffset":348345,"endOffset":348356,"count":0},{"startOffset":348366,"endOffset":348397,"count":0}],"isBlockCoverage":true},{"functionName":"set$_async$_onData","ranges":[{"startOffset":348429,"endOffset":348607,"count":1}],"isBlockCoverage":true},{"functionName":"set$_onDone","ranges":[{"startOffset":348626,"endOffset":348722,"count":1}],"isBlockCoverage":true},{"functionName":"set$_pending","ranges":[{"startOffset":348742,"endOffset":348928,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":349070,"endOffset":349703,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":349855,"endOffset":350105,"count":0}],"isBlockCoverage":false},{"functionName":"listen$4$cancelOnError$onDone$onError","ranges":[{"startOffset":350252,"endOffset":350638,"count":1}],"isBlockCoverage":true},{"functionName":"listen$1","ranges":[{"startOffset":350654,"endOffset":350761,"count":1}],"isBlockCoverage":true},{"functionName":"listen$3$onDone$onError","ranges":[{"startOffset":350792,"endOffset":350921,"count":1}],"isBlockCoverage":true},{"functionName":"listen$2$onDone","ranges":[{"startOffset":350944,"endOffset":351061,"count":1}],"isBlockCoverage":true},{"functionName":"set$next","ranges":[{"startOffset":351113,"endOffset":351203,"count":0}],"isBlockCoverage":false},{"functionName":"get$next","ranges":[{"startOffset":351219,"endOffset":351261,"count":0}],"isBlockCoverage":false},{"functionName":"perform$1","ranges":[{"startOffset":351313,"endOffset":351448,"count":0}],"isBlockCoverage":false},{"functionName":"perform$1","ranges":[{"startOffset":351501,"endOffset":351585,"count":0}],"isBlockCoverage":false},{"functionName":"$as_DelayedEvent","ranges":[{"startOffset":351609,"endOffset":351627,"count":0}],"isBlockCoverage":false},{"functionName":"perform$1","ranges":[{"startOffset":351679,"endOffset":351735,"count":0}],"isBlockCoverage":false},{"functionName":"get$next","ranges":[{"startOffset":351751,"endOffset":351783,"count":0}],"isBlockCoverage":false},{"functionName":"set$next","ranges":[{"startOffset":351799,"endOffset":351889,"count":0}],"isBlockCoverage":false},{"functionName":"$as_DelayedEvent","ranges":[{"startOffset":351938,"endOffset":351956,"count":0}],"isBlockCoverage":false},{"functionName":"schedule$1","ranges":[{"startOffset":352011,"endOffset":352371,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":352439,"endOffset":352917,"count":0}],"isBlockCoverage":false},{"functionName":"add$1","ranges":[{"startOffset":353038,"endOffset":353300,"count":0}],"isBlockCoverage":false},{"functionName":"_schedule$0","ranges":[{"startOffset":353365,"endOffset":353539,"count":0}],"isBlockCoverage":false},{"functionName":"cancel$0","ranges":[{"startOffset":353555,"endOffset":353615,"count":0}],"isBlockCoverage":false},{"functionName":"_sendDone$0","ranges":[{"startOffset":353634,"endOffset":353880,"count":0}],"isBlockCoverage":false},{"functionName":"listen$4$cancelOnError$onDone$onError","ranges":[{"startOffset":354026,"endOffset":354367,"count":0}],"isBlockCoverage":false},{"functionName":"listen$1","ranges":[{"startOffset":354383,"endOffset":354490,"count":0}],"isBlockCoverage":false},{"functionName":"listen$3$onDone$onError","ranges":[{"startOffset":354521,"endOffset":354650,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":354727,"endOffset":354776,"count":0}],"isBlockCoverage":false},{"functionName":"handleUncaughtError$3","ranges":[{"startOffset":355050,"endOffset":355408,"count":0}],"isBlockCoverage":false},{"functionName":"get$_delegate","ranges":[{"startOffset":355523,"endOffset":355705,"count":0}],"isBlockCoverage":false},{"functionName":"get$errorZone","ranges":[{"startOffset":355726,"endOffset":355789,"count":1}],"isBlockCoverage":true},{"functionName":"runGuarded$1","ranges":[{"startOffset":355809,"endOffset":356114,"count":1},{"startOffset":355949,"endOffset":356108,"count":0}],"isBlockCoverage":true},{"functionName":"runUnaryGuarded$1$2","ranges":[{"startOffset":356141,"endOffset":356526,"count":1},{"startOffset":356361,"endOffset":356520,"count":0}],"isBlockCoverage":true},{"functionName":"runBinaryGuarded$2$3","ranges":[{"startOffset":356554,"endOffset":357013,"count":0}],"isBlockCoverage":false},{"functionName":"bindCallback$1$1","ranges":[{"startOffset":357037,"endOffset":357198,"count":0}],"isBlockCoverage":false},{"functionName":"bindUnaryCallback$2$1","ranges":[{"startOffset":357227,"endOffset":357422,"count":1}],"isBlockCoverage":true},{"functionName":"bindCallbackGuarded$1","ranges":[{"startOffset":357451,"endOffset":357611,"count":1}],"isBlockCoverage":true},{"functionName":"bindUnaryCallbackGuarded$1$1","ranges":[{"startOffset":357647,"endOffset":357841,"count":1}],"isBlockCoverage":true},{"functionName":"$index","ranges":[{"startOffset":357855,"endOffset":358169,"count":0}],"isBlockCoverage":false},{"functionName":"handleUncaughtError$2","ranges":[{"startOffset":358198,"endOffset":358557,"count":0}],"isBlockCoverage":false},{"functionName":"fork$2$specification$zoneValues","ranges":[{"startOffset":358596,"endOffset":358897,"count":0}],"isBlockCoverage":false},{"functionName":"run$1$1","ranges":[{"startOffset":358912,"endOffset":359343,"count":1}],"isBlockCoverage":true},{"functionName":"runUnary$2$2","ranges":[{"startOffset":359363,"endOffset":359898,"count":1}],"isBlockCoverage":true},{"functionName":"runBinary$3$3","ranges":[{"startOffset":359919,"endOffset":360544,"count":0}],"isBlockCoverage":false},{"functionName":"registerCallback$1$1","ranges":[{"startOffset":360572,"endOffset":361053,"count":1}],"isBlockCoverage":true},{"functionName":"registerUnaryCallback$2$1","ranges":[{"startOffset":361086,"endOffset":361624,"count":1}],"isBlockCoverage":true},{"functionName":"registerBinaryCallback$3$1","ranges":[{"startOffset":361658,"endOffset":362225,"count":1}],"isBlockCoverage":true},{"functionName":"errorCallback$2","ranges":[{"startOffset":362248,"endOffset":362670,"count":0}],"isBlockCoverage":false},{"functionName":"scheduleMicrotask$1","ranges":[{"startOffset":362697,"endOffset":363013,"count":1}],"isBlockCoverage":true},{"functionName":"createPeriodicTimer$2","ranges":[{"startOffset":363042,"endOffset":363397,"count":1}],"isBlockCoverage":true},{"functionName":"print$1","ranges":[{"startOffset":363412,"endOffset":363675,"count":0}],"isBlockCoverage":false},{"functionName":"set$_run","ranges":[{"startOffset":363691,"endOffset":363808,"count":1}],"isBlockCoverage":true},{"functionName":"set$_runUnary","ranges":[{"startOffset":363829,"endOffset":363961,"count":1}],"isBlockCoverage":true},{"functionName":"set$_runBinary","ranges":[{"startOffset":363983,"endOffset":364118,"count":1}],"isBlockCoverage":true},{"functionName":"set$_registerCallback","ranges":[{"startOffset":364147,"endOffset":364303,"count":1}],"isBlockCoverage":true},{"functionName":"set$_registerUnaryCallback","ranges":[{"startOffset":364337,"endOffset":364508,"count":1}],"isBlockCoverage":true},{"functionName":"set$_registerBinaryCallback","ranges":[{"startOffset":364543,"endOffset":364717,"count":1}],"isBlockCoverage":true},{"functionName":"set$_errorCallback","ranges":[{"startOffset":364743,"endOffset":364972,"count":1}],"isBlockCoverage":true},{"functionName":"set$_scheduleMicrotask","ranges":[{"startOffset":365002,"endOffset":365229,"count":1}],"isBlockCoverage":true},{"functionName":"set$_createTimer","ranges":[{"startOffset":365253,"endOffset":365479,"count":1}],"isBlockCoverage":true},{"functionName":"set$_createPeriodicTimer","ranges":[{"startOffset":365511,"endOffset":365778,"count":1}],"isBlockCoverage":true},{"functionName":"set$_print","ranges":[{"startOffset":365796,"endOffset":365977,"count":1}],"isBlockCoverage":true},{"functionName":"set$_fork","ranges":[{"startOffset":365994,"endOffset":366199,"count":1}],"isBlockCoverage":true},{"functionName":"set$_handleUncaughtError","ranges":[{"startOffset":366231,"endOffset":366468,"count":1}],"isBlockCoverage":true},{"functionName":"get$_run","ranges":[{"startOffset":366484,"endOffset":366526,"count":0}],"isBlockCoverage":false},{"functionName":"get$_runUnary","ranges":[{"startOffset":366547,"endOffset":366594,"count":0}],"isBlockCoverage":false},{"functionName":"get$_runBinary","ranges":[{"startOffset":366616,"endOffset":366664,"count":0}],"isBlockCoverage":false},{"functionName":"get$_registerCallback","ranges":[{"startOffset":366693,"endOffset":366748,"count":0}],"isBlockCoverage":false},{"functionName":"get$_registerUnaryCallback","ranges":[{"startOffset":366782,"endOffset":366842,"count":0}],"isBlockCoverage":false},{"functionName":"get$_registerBinaryCallback","ranges":[{"startOffset":366877,"endOffset":366938,"count":0}],"isBlockCoverage":false},{"functionName":"get$_errorCallback","ranges":[{"startOffset":366964,"endOffset":367016,"count":0}],"isBlockCoverage":false},{"functionName":"get$_scheduleMicrotask","ranges":[{"startOffset":367046,"endOffset":367102,"count":0}],"isBlockCoverage":false},{"functionName":"get$_createTimer","ranges":[{"startOffset":367126,"endOffset":367176,"count":0}],"isBlockCoverage":false},{"functionName":"get$_createPeriodicTimer","ranges":[{"startOffset":367208,"endOffset":367266,"count":0}],"isBlockCoverage":false},{"functionName":"get$_print","ranges":[{"startOffset":367284,"endOffset":367328,"count":0}],"isBlockCoverage":false},{"functionName":"get$_fork","ranges":[{"startOffset":367345,"endOffset":367388,"count":0}],"isBlockCoverage":false},{"functionName":"get$_handleUncaughtError","ranges":[{"startOffset":367420,"endOffset":367478,"count":0}],"isBlockCoverage":false},{"functionName":"get$parent","ranges":[{"startOffset":367496,"endOffset":367548,"count":0}],"isBlockCoverage":false},{"functionName":"get$_async$_map","ranges":[{"startOffset":367571,"endOffset":367620,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":367689,"endOffset":367765,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":367783,"endOffset":367838,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":367912,"endOffset":368062,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":368080,"endOffset":368151,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":368227,"endOffset":368300,"count":1}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":368449,"endOffset":368598,"count":1}],"isBlockCoverage":true},{"functionName":"$signature","ranges":[{"startOffset":368616,"endOffset":368683,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":368752,"endOffset":369158,"count":0}],"isBlockCoverage":false},{"functionName":"get$_run","ranges":[{"startOffset":369225,"endOffset":369292,"count":1}],"isBlockCoverage":true},{"functionName":"get$_runUnary","ranges":[{"startOffset":369313,"endOffset":369385,"count":1}],"isBlockCoverage":true},{"functionName":"get$_runBinary","ranges":[{"startOffset":369407,"endOffset":369480,"count":1}],"isBlockCoverage":true},{"functionName":"get$_registerCallback","ranges":[{"startOffset":369509,"endOffset":369589,"count":1}],"isBlockCoverage":true},{"functionName":"get$_registerUnaryCallback","ranges":[{"startOffset":369623,"endOffset":369675,"count":1}],"isBlockCoverage":true},{"functionName":"get$_registerBinaryCallback","ranges":[{"startOffset":369710,"endOffset":369762,"count":1}],"isBlockCoverage":true},{"functionName":"get$_errorCallback","ranges":[{"startOffset":369788,"endOffset":369865,"count":1}],"isBlockCoverage":true},{"functionName":"get$_scheduleMicrotask","ranges":[{"startOffset":369895,"endOffset":369976,"count":1}],"isBlockCoverage":true},{"functionName":"get$_createTimer","ranges":[{"startOffset":370000,"endOffset":370075,"count":1}],"isBlockCoverage":true},{"functionName":"get$_createPeriodicTimer","ranges":[{"startOffset":370107,"endOffset":370159,"count":1}],"isBlockCoverage":true},{"functionName":"get$_print","ranges":[{"startOffset":370177,"endOffset":370246,"count":1}],"isBlockCoverage":true},{"functionName":"get$_fork","ranges":[{"startOffset":370263,"endOffset":370331,"count":1}],"isBlockCoverage":true},{"functionName":"get$_handleUncaughtError","ranges":[{"startOffset":370363,"endOffset":370415,"count":0}],"isBlockCoverage":false},{"functionName":"get$parent","ranges":[{"startOffset":370433,"endOffset":370466,"count":1}],"isBlockCoverage":true},{"functionName":"get$_async$_map","ranges":[{"startOffset":370489,"endOffset":370549,"count":1}],"isBlockCoverage":true},{"functionName":"get$_delegate","ranges":[{"startOffset":370570,"endOffset":370764,"count":0}],"isBlockCoverage":false},{"functionName":"get$errorZone","ranges":[{"startOffset":370785,"endOffset":370822,"count":1}],"isBlockCoverage":true},{"functionName":"runGuarded$1","ranges":[{"startOffset":370842,"endOffset":371321,"count":0}],"isBlockCoverage":false},{"functionName":"runUnaryGuarded$1$2","ranges":[{"startOffset":371348,"endOffset":371910,"count":0}],"isBlockCoverage":false},{"functionName":"runBinaryGuarded$2$3","ranges":[{"startOffset":371938,"endOffset":372581,"count":0}],"isBlockCoverage":false},{"functionName":"bindCallback$1$1","ranges":[{"startOffset":372605,"endOffset":372733,"count":0}],"isBlockCoverage":false},{"functionName":"bindCallbackGuarded$1","ranges":[{"startOffset":372762,"endOffset":372889,"count":0}],"isBlockCoverage":false},{"functionName":"bindUnaryCallbackGuarded$1$1","ranges":[{"startOffset":372925,"endOffset":373077,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":373091,"endOffset":373129,"count":0}],"isBlockCoverage":false},{"functionName":"handleUncaughtError$2","ranges":[{"startOffset":373158,"endOffset":373305,"count":0}],"isBlockCoverage":false},{"functionName":"fork$2$specification$zoneValues","ranges":[{"startOffset":373344,"endOffset":373458,"count":1}],"isBlockCoverage":true},{"functionName":"run$1$1","ranges":[{"startOffset":373473,"endOffset":373668,"count":0}],"isBlockCoverage":false},{"functionName":"runUnary$2$2","ranges":[{"startOffset":373688,"endOffset":373966,"count":0}],"isBlockCoverage":false},{"functionName":"runBinary$3$3","ranges":[{"startOffset":373987,"endOffset":374346,"count":0}],"isBlockCoverage":false},{"functionName":"registerCallback$1$1","ranges":[{"startOffset":374374,"endOffset":374454,"count":0}],"isBlockCoverage":false},{"functionName":"registerUnaryCallback$2$1","ranges":[{"startOffset":374487,"endOffset":374583,"count":0}],"isBlockCoverage":false},{"functionName":"registerBinaryCallback$3$1","ranges":[{"startOffset":374617,"endOffset":374721,"count":0}],"isBlockCoverage":false},{"functionName":"errorCallback$2","ranges":[{"startOffset":374744,"endOffset":374793,"count":0}],"isBlockCoverage":false},{"functionName":"scheduleMicrotask$1","ranges":[{"startOffset":374820,"endOffset":374933,"count":0}],"isBlockCoverage":false},{"functionName":"createPeriodicTimer$2","ranges":[{"startOffset":374962,"endOffset":375105,"count":0}],"isBlockCoverage":false},{"functionName":"print$1","ranges":[{"startOffset":375120,"endOffset":375172,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":375239,"endOffset":375306,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":375324,"endOffset":375379,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":375453,"endOffset":375517,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":375664,"endOffset":375804,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":375822,"endOffset":375889,"count":0}],"isBlockCoverage":false},{"functionName":"call$5","ranges":[{"startOffset":375942,"endOffset":376743,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":376812,"endOffset":376870,"count":0}],"isBlockCoverage":false},{"functionName":"get$isEmpty","ranges":[{"startOffset":376889,"endOffset":376953,"count":0}],"isBlockCoverage":false},{"functionName":"get$keys","ranges":[{"startOffset":376969,"endOffset":377070,"count":0}],"isBlockCoverage":false},{"functionName":"containsKey$1","ranges":[{"startOffset":377091,"endOffset":377529,"count":0}],"isBlockCoverage":false},{"functionName":"_containsKey$1","ranges":[{"startOffset":377551,"endOffset":377737,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":377751,"endOffset":378258,"count":0}],"isBlockCoverage":false},{"functionName":"_get$1","ranges":[{"startOffset":378272,"endOffset":378549,"count":0}],"isBlockCoverage":false},{"functionName":"$indexSet","ranges":[{"startOffset":378566,"endOffset":379433,"count":0}],"isBlockCoverage":false},{"functionName":"_set$2","ranges":[{"startOffset":379447,"endOffset":380330,"count":0}],"isBlockCoverage":false},{"functionName":"forEach$1","ranges":[{"startOffset":380347,"endOffset":380898,"count":0}],"isBlockCoverage":false},{"functionName":"_collection$_computeKeys$0","ranges":[{"startOffset":380932,"endOffset":382221,"count":0}],"isBlockCoverage":false},{"functionName":"_collection$_addHashTableEntry$3","ranges":[{"startOffset":382261,"endOffset":382617,"count":0}],"isBlockCoverage":false},{"functionName":"_computeHashCode$1","ranges":[{"startOffset":382643,"endOffset":382712,"count":0}],"isBlockCoverage":false},{"functionName":"_getBucket$2","ranges":[{"startOffset":382732,"endOffset":382810,"count":0}],"isBlockCoverage":false},{"functionName":"_findBucketIndex$2","ranges":[{"startOffset":382836,"endOffset":383075,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":383135,"endOffset":383198,"count":0}],"isBlockCoverage":false},{"functionName":"get$isEmpty","ranges":[{"startOffset":383217,"endOffset":383286,"count":0}],"isBlockCoverage":false},{"functionName":"get$iterator","ranges":[{"startOffset":383306,"endOffset":383438,"count":0}],"isBlockCoverage":false},{"functionName":"get$current","ranges":[{"startOffset":383499,"endOffset":383557,"count":0}],"isBlockCoverage":false},{"functionName":"moveNext$0","ranges":[{"startOffset":383575,"endOffset":384056,"count":0}],"isBlockCoverage":false},{"functionName":"set$_collection$_current","ranges":[{"startOffset":384088,"endOffset":384223,"count":0}],"isBlockCoverage":false},{"functionName":"get$iterator","ranges":[{"startOffset":384300,"endOffset":384484,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":384502,"endOffset":384560,"count":0}],"isBlockCoverage":false},{"functionName":"contains$1","ranges":[{"startOffset":384578,"endOffset":385195,"count":1},{"startOffset":384662,"endOffset":384687,"count":0},{"startOffset":384689,"endOffset":384885,"count":0},{"startOffset":385049,"endOffset":385189,"count":0}],"isBlockCoverage":true},{"functionName":"_contains$1","ranges":[{"startOffset":385214,"endOffset":385409,"count":0}],"isBlockCoverage":false},{"functionName":"add$1","ranges":[{"startOffset":385422,"endOffset":386268,"count":0}],"isBlockCoverage":false},{"functionName":"_collection$_add$1","ranges":[{"startOffset":386294,"endOffset":386943,"count":0}],"isBlockCoverage":false},{"functionName":"remove$1","ranges":[{"startOffset":386959,"endOffset":387360,"count":1},{"startOffset":387018,"endOffset":387043,"count":0},{"startOffset":387053,"endOffset":387136,"count":0},{"startOffset":387304,"endOffset":387354,"count":0}],"isBlockCoverage":true},{"functionName":"_remove$1","ranges":[{"startOffset":387377,"endOffset":387746,"count":0}],"isBlockCoverage":false},{"functionName":"_collection$_addHashTableEntry$2","ranges":[{"startOffset":387786,"endOffset":388089,"count":0}],"isBlockCoverage":false},{"functionName":"_collection$_removeHashTableEntry$2","ranges":[{"startOffset":388132,"endOffset":388443,"count":1},{"startOffset":388221,"endOffset":388442,"count":0}],"isBlockCoverage":true},{"functionName":"_collection$_modified$0","ranges":[{"startOffset":388474,"endOffset":388582,"count":0}],"isBlockCoverage":false},{"functionName":"_collection$_newLinkedCell$1","ranges":[{"startOffset":388618,"endOffset":389176,"count":0}],"isBlockCoverage":false},{"functionName":"_collection$_unlinkCell$1","ranges":[{"startOffset":389209,"endOffset":389658,"count":0}],"isBlockCoverage":false},{"functionName":"_computeHashCode$1","ranges":[{"startOffset":389684,"endOffset":389761,"count":0}],"isBlockCoverage":false},{"functionName":"_getBucket$2","ranges":[{"startOffset":389781,"endOffset":389867,"count":0}],"isBlockCoverage":false},{"functionName":"_findBucketIndex$2","ranges":[{"startOffset":389893,"endOffset":390203,"count":0}],"isBlockCoverage":false},{"functionName":"get$current","ranges":[{"startOffset":390306,"endOffset":390364,"count":0}],"isBlockCoverage":false},{"functionName":"moveNext$0","ranges":[{"startOffset":390382,"endOffset":390971,"count":0}],"isBlockCoverage":false},{"functionName":"set$_collection$_current","ranges":[{"startOffset":391003,"endOffset":391138,"count":0}],"isBlockCoverage":false},{"functionName":"get$iterator","ranges":[{"startOffset":391329,"endOffset":391499,"count":0}],"isBlockCoverage":false},{"functionName":"elementAt$1","ranges":[{"startOffset":391518,"endOffset":391594,"count":0}],"isBlockCoverage":false},{"functionName":"get$isEmpty","ranges":[{"startOffset":391613,"endOffset":391685,"count":0}],"isBlockCoverage":false},{"functionName":"get$isNotEmpty","ranges":[{"startOffset":391707,"endOffset":391775,"count":0}],"isBlockCoverage":false},{"functionName":"fillRange$3","ranges":[{"startOffset":391794,"endOffset":392116,"count":1}],"isBlockCoverage":true},{"functionName":"toString$0","ranges":[{"startOffset":392134,"endOffset":392230,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":392322,"endOffset":392591,"count":0}],"isBlockCoverage":false},{"functionName":"forEach$1","ranges":[{"startOffset":392659,"endOffset":393030,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":393048,"endOffset":393131,"count":0}],"isBlockCoverage":false},{"functionName":"get$isEmpty","ranges":[{"startOffset":393150,"endOffset":393234,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":393252,"endOffset":393313,"count":0}],"isBlockCoverage":false},{"functionName":"$indexSet","ranges":[{"startOffset":393389,"endOffset":393708,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":393752,"endOffset":393815,"count":1}],"isBlockCoverage":true},{"functionName":"$indexSet","ranges":[{"startOffset":393832,"endOffset":394035,"count":0}],"isBlockCoverage":false},{"functionName":"forEach$1","ranges":[{"startOffset":394052,"endOffset":394236,"count":0}],"isBlockCoverage":false},{"functionName":"get$isEmpty","ranges":[{"startOffset":394255,"endOffset":394333,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":394351,"endOffset":394428,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":394446,"endOffset":394504,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":394608,"endOffset":394693,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":394926,"endOffset":395244,"count":1},{"startOffset":395025,"endOffset":395058,"count":0},{"startOffset":395107,"endOffset":395114,"count":0},{"startOffset":395221,"endOffset":395229,"count":0}],"isBlockCoverage":true},{"functionName":"get$length","ranges":[{"startOffset":395262,"endOffset":395383,"count":0}],"isBlockCoverage":false},{"functionName":"get$isEmpty","ranges":[{"startOffset":395402,"endOffset":395463,"count":0}],"isBlockCoverage":false},{"functionName":"get$keys","ranges":[{"startOffset":395479,"endOffset":395703,"count":0}],"isBlockCoverage":false},{"functionName":"$indexSet","ranges":[{"startOffset":395720,"endOffset":396180,"count":0}],"isBlockCoverage":false},{"functionName":"containsKey$1","ranges":[{"startOffset":396201,"endOffset":396375,"count":0}],"isBlockCoverage":false},{"functionName":"forEach$1","ranges":[{"startOffset":396392,"endOffset":397032,"count":1},{"startOffset":396553,"endOffset":396587,"count":0},{"startOffset":396958,"endOffset":397018,"count":0}],"isBlockCoverage":true},{"functionName":"_computeKeys$0","ranges":[{"startOffset":397054,"endOffset":397276,"count":1}],"isBlockCoverage":true},{"functionName":"_upgrade$0","ranges":[{"startOffset":397294,"endOffset":397882,"count":0}],"isBlockCoverage":false},{"functionName":"_process$1","ranges":[{"startOffset":397900,"endOffset":398131,"count":1},{"startOffset":398012,"endOffset":398019,"count":0}],"isBlockCoverage":true},{"functionName":"$asMapMixin","ranges":[{"startOffset":398150,"endOffset":398199,"count":0}],"isBlockCoverage":false},{"functionName":"$asMap","ranges":[{"startOffset":398213,"endOffset":398262,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":398322,"endOffset":398411,"count":0}],"isBlockCoverage":false},{"functionName":"elementAt$1","ranges":[{"startOffset":398430,"endOffset":398755,"count":0}],"isBlockCoverage":false},{"functionName":"get$iterator","ranges":[{"startOffset":398775,"endOffset":399090,"count":0}],"isBlockCoverage":false},{"functionName":"$asEfficientLengthIterable","ranges":[{"startOffset":399124,"endOffset":399167,"count":0}],"isBlockCoverage":false},{"functionName":"$asListIterable","ranges":[{"startOffset":399190,"endOffset":399233,"count":0}],"isBlockCoverage":false},{"functionName":"$asIterable","ranges":[{"startOffset":399252,"endOffset":399295,"count":0}],"isBlockCoverage":false},{"functionName":"encode$1","ranges":[{"startOffset":399344,"endOffset":399419,"count":0}],"isBlockCoverage":false},{"functionName":"convert$1","ranges":[{"startOffset":399480,"endOffset":400124,"count":0}],"isBlockCoverage":false},{"functionName":"$asStreamTransformer","ranges":[{"startOffset":400152,"endOffset":400212,"count":0}],"isBlockCoverage":false},{"functionName":"$asConverter","ranges":[{"startOffset":400232,"endOffset":400292,"count":0}],"isBlockCoverage":false},{"functionName":"normalize$3","ranges":[{"startOffset":400378,"endOffset":404030,"count":0}],"isBlockCoverage":false},{"functionName":"$asCodec","ranges":[{"startOffset":404046,"endOffset":404106,"count":0}],"isBlockCoverage":false},{"functionName":"$asStreamTransformer","ranges":[{"startOffset":404170,"endOffset":404230,"count":0}],"isBlockCoverage":false},{"functionName":"$asConverter","ranges":[{"startOffset":404250,"endOffset":404310,"count":0}],"isBlockCoverage":false},{"functionName":"$asCodec","ranges":[{"startOffset":404386,"endOffset":404437,"count":0}],"isBlockCoverage":false},{"functionName":"$asCodec","ranges":[{"startOffset":404514,"endOffset":404574,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":404641,"endOffset":404894,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":404950,"endOffset":405016,"count":0}],"isBlockCoverage":false},{"functionName":"decode$2$reviver","ranges":[{"startOffset":405072,"endOffset":405191,"count":1}],"isBlockCoverage":true},{"functionName":"encode$2$toEncodable","ranges":[{"startOffset":405219,"endOffset":405389,"count":1}],"isBlockCoverage":true},{"functionName":"get$encoder","ranges":[{"startOffset":405408,"endOffset":405464,"count":1}],"isBlockCoverage":true},{"functionName":"get$decoder","ranges":[{"startOffset":405483,"endOffset":405534,"count":1}],"isBlockCoverage":true},{"functionName":"$asCodec","ranges":[{"startOffset":405550,"endOffset":405603,"count":0}],"isBlockCoverage":false},{"functionName":"$asStreamTransformer","ranges":[{"startOffset":405665,"endOffset":405718,"count":0}],"isBlockCoverage":false},{"functionName":"$asConverter","ranges":[{"startOffset":405738,"endOffset":405791,"count":0}],"isBlockCoverage":false},{"functionName":"$asStreamTransformer","ranges":[{"startOffset":405853,"endOffset":405906,"count":0}],"isBlockCoverage":false},{"functionName":"$asConverter","ranges":[{"startOffset":405926,"endOffset":405979,"count":0}],"isBlockCoverage":false},{"functionName":"writeStringContent$1","ranges":[{"startOffset":406046,"endOffset":407693,"count":1},{"startOffset":406329,"endOffset":407300,"count":0},{"startOffset":407346,"endOffset":407538,"count":0},{"startOffset":407601,"endOffset":407687,"count":0}],"isBlockCoverage":true},{"functionName":"_checkCycle$1","ranges":[{"startOffset":407714,"endOffset":408027,"count":1},{"startOffset":407871,"endOffset":407883,"count":0},{"startOffset":407911,"endOffset":407970,"count":0}],"isBlockCoverage":true},{"functionName":"writeObject$1","ranges":[{"startOffset":408048,"endOffset":408754,"count":1},{"startOffset":408163,"endOffset":408748,"count":0}],"isBlockCoverage":true},{"functionName":"writeJsonValue$1","ranges":[{"startOffset":408778,"endOffset":410030,"count":1},{"startOffset":408901,"endOffset":408914,"count":0},{"startOffset":409006,"endOffset":409072,"count":0},{"startOffset":409100,"endOffset":409167,"count":0},{"startOffset":409655,"endOffset":409678,"count":0},{"startOffset":409908,"endOffset":409931,"count":0},{"startOffset":409987,"endOffset":410016,"count":0}],"isBlockCoverage":true},{"functionName":"writeList$1","ranges":[{"startOffset":410049,"endOffset":410429,"count":1}],"isBlockCoverage":true},{"functionName":"writeMap$1","ranges":[{"startOffset":410447,"endOffset":411384,"count":1},{"startOffset":410929,"endOffset":410942,"count":0},{"startOffset":411241,"endOffset":411274,"count":0}],"isBlockCoverage":true},{"functionName":"call$2","ranges":[{"startOffset":411454,"endOffset":411738,"count":1},{"startOffset":411538,"endOffset":411572,"count":0}],"isBlockCoverage":true},{"functionName":"get$_partialResult","ranges":[{"startOffset":411829,"endOffset":411944,"count":0}],"isBlockCoverage":false},{"functionName":"writeNumber$1","ranges":[{"startOffset":411965,"endOffset":412063,"count":1}],"isBlockCoverage":true},{"functionName":"writeString$1","ranges":[{"startOffset":412084,"endOffset":412151,"count":1}],"isBlockCoverage":true},{"functionName":"writeStringSlice$3","ranges":[{"startOffset":412177,"endOffset":412285,"count":0}],"isBlockCoverage":false},{"functionName":"writeCharCode$1","ranges":[{"startOffset":412308,"endOffset":412387,"count":0}],"isBlockCoverage":false},{"functionName":"get$encoder","ranges":[{"startOffset":412438,"endOffset":412486,"count":0}],"isBlockCoverage":false},{"functionName":"convert$1","ranges":[{"startOffset":412537,"endOffset":413070,"count":0}],"isBlockCoverage":false},{"functionName":"$asStreamTransformer","ranges":[{"startOffset":413098,"endOffset":413158,"count":0}],"isBlockCoverage":false},{"functionName":"$asConverter","ranges":[{"startOffset":413178,"endOffset":413238,"count":0}],"isBlockCoverage":false},{"functionName":"_writeSurrogate$2","ranges":[{"startOffset":413298,"endOffset":414650,"count":0}],"isBlockCoverage":false},{"functionName":"_fillBuffer$3","ranges":[{"startOffset":414671,"endOffset":416478,"count":0}],"isBlockCoverage":false},{"functionName":"convert$1","ranges":[{"startOffset":416529,"endOffset":417119,"count":1},{"startOffset":416771,"endOffset":416785,"count":0},{"startOffset":417103,"endOffset":417107,"count":0}],"isBlockCoverage":true},{"functionName":"$asStreamTransformer","ranges":[{"startOffset":417147,"endOffset":417207,"count":0}],"isBlockCoverage":false},{"functionName":"$asConverter","ranges":[{"startOffset":417227,"endOffset":417287,"count":0}],"isBlockCoverage":false},{"functionName":"flush$2","ranges":[{"startOffset":417337,"endOffset":417609,"count":1},{"startOffset":417475,"endOffset":417603,"count":0}],"isBlockCoverage":true},{"functionName":"convert$3","ranges":[{"startOffset":417626,"endOffset":421538,"count":1},{"startOffset":418311,"endOffset":419809,"count":0},{"startOffset":420150,"endOffset":421331,"count":0},{"startOffset":421410,"endOffset":421532,"count":0}],"isBlockCoverage":true},{"functionName":"call$2","ranges":[{"startOffset":421610,"endOffset":421735,"count":1}],"isBlockCoverage":true},{"functionName":"call$2","ranges":[{"startOffset":421826,"endOffset":422161,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":422248,"endOffset":422415,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":422435,"endOffset":422566,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":422584,"endOffset":423195,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":423264,"endOffset":423417,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":423437,"endOffset":423515,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":423533,"endOffset":424155,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":424219,"endOffset":424494,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":424578,"endOffset":424661,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":424763,"endOffset":424813,"count":0}],"isBlockCoverage":false},{"functionName":"get$_errorName","ranges":[{"startOffset":424871,"endOffset":424955,"count":0}],"isBlockCoverage":false},{"functionName":"get$_errorExplanation","ranges":[{"startOffset":424984,"endOffset":425019,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":425037,"endOffset":425551,"count":0}],"isBlockCoverage":false},{"functionName":"get$_errorName","ranges":[{"startOffset":425606,"endOffset":425651,"count":0}],"isBlockCoverage":false},{"functionName":"get$_errorExplanation","ranges":[{"startOffset":425680,"endOffset":426274,"count":0}],"isBlockCoverage":false},{"functionName":"get$_errorName","ranges":[{"startOffset":426329,"endOffset":426374,"count":0}],"isBlockCoverage":false},{"functionName":"get$_errorExplanation","ranges":[{"startOffset":426403,"endOffset":426799,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":426817,"endOffset":426869,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":426927,"endOffset":427772,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":427829,"endOffset":427903,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":427962,"endOffset":428088,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":428139,"endOffset":428201,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":428269,"endOffset":428498,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":428555,"endOffset":428604,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":428680,"endOffset":428730,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":428813,"endOffset":429012,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":429063,"endOffset":429125,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":429181,"endOffset":431814,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":431916,"endOffset":432069,"count":0}],"isBlockCoverage":false},{"functionName":"get$isEmpty","ranges":[{"startOffset":432088,"endOffset":432159,"count":0}],"isBlockCoverage":false},{"functionName":"skipWhile$1","ranges":[{"startOffset":432178,"endOffset":432378,"count":0}],"isBlockCoverage":false},{"functionName":"get$first","ranges":[{"startOffset":432395,"endOffset":432580,"count":0}],"isBlockCoverage":false},{"functionName":"get$last","ranges":[{"startOffset":432596,"endOffset":432864,"count":0}],"isBlockCoverage":false},{"functionName":"elementAt$1","ranges":[{"startOffset":432883,"endOffset":433294,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":433312,"endOffset":433398,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":433568,"endOffset":433650,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":433668,"endOffset":433708,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":433808,"endOffset":433863,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":433883,"endOffset":433950,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":433968,"endOffset":434059,"count":0}],"isBlockCoverage":false},{"functionName":"noSuchMethod$1","ranges":[{"startOffset":434081,"endOffset":434328,"count":0}],"isBlockCoverage":false},{"functionName":"toString","ranges":[{"startOffset":434344,"endOffset":434398,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":434513,"endOffset":434563,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":434678,"endOffset":434733,"count":0}],"isBlockCoverage":false},{"functionName":"write$1","ranges":[{"startOffset":434748,"endOffset":434803,"count":1}],"isBlockCoverage":true},{"functionName":"writeCharCode$1","ranges":[{"startOffset":434826,"endOffset":434919,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":434937,"endOffset":435033,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":435148,"endOffset":435892,"count":1},{"startOffset":435410,"endOffset":435547,"count":0}],"isBlockCoverage":true},{"functionName":"call$2","ranges":[{"startOffset":435976,"endOffset":436109,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":436192,"endOffset":436325,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":436339,"endOffset":436397,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":436483,"endOffset":436948,"count":0}],"isBlockCoverage":false},{"functionName":"get$userInfo","ranges":[{"startOffset":437015,"endOffset":437062,"count":0}],"isBlockCoverage":false},{"functionName":"get$host","ranges":[{"startOffset":437078,"endOffset":437303,"count":0}],"isBlockCoverage":false},{"functionName":"get$port","ranges":[{"startOffset":437319,"endOffset":437453,"count":0}],"isBlockCoverage":false},{"functionName":"get$query","ranges":[{"startOffset":437470,"endOffset":437551,"count":0}],"isBlockCoverage":false},{"functionName":"get$fragment","ranges":[{"startOffset":437571,"endOffset":437655,"count":0}],"isBlockCoverage":false},{"functionName":"get$pathSegments","ranges":[{"startOffset":437679,"endOffset":438447,"count":0}],"isBlockCoverage":false},{"functionName":"get$queryParameters","ranges":[{"startOffset":438474,"endOffset":438764,"count":1},{"startOffset":438691,"endOffset":438695,"count":0}],"isBlockCoverage":true},{"functionName":"_mergePaths$2","ranges":[{"startOffset":438785,"endOffset":439869,"count":0}],"isBlockCoverage":false},{"functionName":"resolve$1","ranges":[{"startOffset":439886,"endOffset":439969,"count":0}],"isBlockCoverage":false},{"functionName":"resolveUri$1","ranges":[{"startOffset":439989,"endOffset":442823,"count":0}],"isBlockCoverage":false},{"functionName":"get$hasAuthority","ranges":[{"startOffset":442847,"endOffset":442898,"count":0}],"isBlockCoverage":false},{"functionName":"get$hasPort","ranges":[{"startOffset":442917,"endOffset":442968,"count":0}],"isBlockCoverage":false},{"functionName":"get$hasQuery","ranges":[{"startOffset":442988,"endOffset":443040,"count":0}],"isBlockCoverage":false},{"functionName":"get$hasFragment","ranges":[{"startOffset":443063,"endOffset":443118,"count":0}],"isBlockCoverage":false},{"functionName":"get$hasAbsolutePath","ranges":[{"startOffset":443145,"endOffset":443210,"count":0}],"isBlockCoverage":false},{"functionName":"toFilePath$0","ranges":[{"startOffset":443230,"endOffset":444436,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":444454,"endOffset":445288,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":445299,"endOffset":446881,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":446901,"endOffset":447105,"count":0}],"isBlockCoverage":false},{"functionName":"set$_pathSegments","ranges":[{"startOffset":447130,"endOffset":447254,"count":0}],"isBlockCoverage":false},{"functionName":"set$_queryParameters","ranges":[{"startOffset":447282,"endOffset":447436,"count":1}],"isBlockCoverage":true},{"functionName":"get$scheme","ranges":[{"startOffset":447469,"endOffset":447513,"count":0}],"isBlockCoverage":false},{"functionName":"get$path","ranges":[{"startOffset":447529,"endOffset":447579,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":447643,"endOffset":447836,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":447944,"endOffset":448269,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":448348,"endOffset":448460,"count":0}],"isBlockCoverage":false},{"functionName":"get$uri","ranges":[{"startOffset":448525,"endOffset":449212,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":449230,"endOffset":449434,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":449492,"endOffset":449544,"count":1}],"isBlockCoverage":true},{"functionName":"call$2","ranges":[{"startOffset":449620,"endOffset":449847,"count":1},{"startOffset":449723,"endOffset":449749,"count":0}],"isBlockCoverage":true},{"functionName":"call$3","ranges":[{"startOffset":449926,"endOffset":450216,"count":1},{"startOffset":450142,"endOffset":450169,"count":0}],"isBlockCoverage":true},{"functionName":"call$3","ranges":[{"startOffset":450275,"endOffset":450601,"count":1},{"startOffset":450527,"endOffset":450554,"count":0}],"isBlockCoverage":true},{"functionName":"get$hasAuthority","ranges":[{"startOffset":450658,"endOffset":450710,"count":0}],"isBlockCoverage":false},{"functionName":"get$hasPort","ranges":[{"startOffset":450729,"endOffset":451079,"count":0}],"isBlockCoverage":false},{"functionName":"get$hasQuery","ranges":[{"startOffset":451099,"endOffset":451337,"count":0}],"isBlockCoverage":false},{"functionName":"get$hasFragment","ranges":[{"startOffset":451360,"endOffset":451538,"count":0}],"isBlockCoverage":false},{"functionName":"get$_isFile","ranges":[{"startOffset":451557,"endOffset":451650,"count":0}],"isBlockCoverage":false},{"functionName":"get$_isHttp","ranges":[{"startOffset":451669,"endOffset":451762,"count":0}],"isBlockCoverage":false},{"functionName":"get$_isHttps","ranges":[{"startOffset":451782,"endOffset":451876,"count":0}],"isBlockCoverage":false},{"functionName":"get$hasAbsolutePath","ranges":[{"startOffset":451903,"endOffset":451985,"count":0}],"isBlockCoverage":false},{"functionName":"get$scheme","ranges":[{"startOffset":452003,"endOffset":452773,"count":0}],"isBlockCoverage":false},{"functionName":"get$userInfo","ranges":[{"startOffset":452793,"endOffset":453028,"count":0}],"isBlockCoverage":false},{"functionName":"get$host","ranges":[{"startOffset":453044,"endOffset":453171,"count":0}],"isBlockCoverage":false},{"functionName":"get$port","ranges":[{"startOffset":453187,"endOffset":453563,"count":0}],"isBlockCoverage":false},{"functionName":"get$path","ranges":[{"startOffset":453579,"endOffset":453674,"count":0}],"isBlockCoverage":false},{"functionName":"get$query","ranges":[{"startOffset":453691,"endOffset":453975,"count":0}],"isBlockCoverage":false},{"functionName":"get$fragment","ranges":[{"startOffset":453995,"endOffset":454227,"count":0}],"isBlockCoverage":false},{"functionName":"get$pathSegments","ranges":[{"startOffset":454251,"endOffset":455219,"count":0}],"isBlockCoverage":false},{"functionName":"get$queryParameters","ranges":[{"startOffset":455246,"endOffset":455623,"count":0}],"isBlockCoverage":false},{"functionName":"_isPort$1","ranges":[{"startOffset":455640,"endOffset":455927,"count":0}],"isBlockCoverage":false},{"functionName":"removeFragment$0","ranges":[{"startOffset":455951,"endOffset":456335,"count":0}],"isBlockCoverage":false},{"functionName":"resolve$1","ranges":[{"startOffset":456352,"endOffset":456435,"count":0}],"isBlockCoverage":false},{"functionName":"resolveUri$1","ranges":[{"startOffset":456455,"endOffset":456640,"count":0}],"isBlockCoverage":false},{"functionName":"_simpleMerge$2","ranges":[{"startOffset":456662,"endOffset":462688,"count":0}],"isBlockCoverage":false},{"functionName":"toFilePath$0","ranges":[{"startOffset":462708,"endOffset":463987,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":464007,"endOffset":464186,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":464197,"endOffset":464401,"count":0}],"isBlockCoverage":false},{"functionName":"_toNonSimple$0","ranges":[{"startOffset":464423,"endOffset":465061,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":465079,"endOffset":465122,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":465252,"endOffset":465309,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":465361,"endOffset":465418,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":465507,"endOffset":465563,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":465616,"endOffset":465673,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":465726,"endOffset":465782,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":465830,"endOffset":465889,"count":0}],"isBlockCoverage":false},{"functionName":"get$onClick","ranges":[{"startOffset":465908,"endOffset":466020,"count":0}],"isBlockCoverage":false},{"functionName":"addEventListener$3","ranges":[{"startOffset":466136,"endOffset":466350,"count":1}],"isBlockCoverage":true},{"functionName":"_addEventListener$3","ranges":[{"startOffset":466377,"endOffset":466569,"count":1}],"isBlockCoverage":true},{"functionName":"_removeEventListener$3","ranges":[{"startOffset":466599,"endOffset":466794,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":466904,"endOffset":466960,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":467015,"endOffset":467071,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":467085,"endOffset":467319,"count":0}],"isBlockCoverage":false},{"functionName":"$indexSet","ranges":[{"startOffset":467336,"endOffset":467547,"count":0}],"isBlockCoverage":false},{"functionName":"elementAt$1","ranges":[{"startOffset":467566,"endOffset":467719,"count":0}],"isBlockCoverage":false},{"functionName":"$asEfficientLengthIterable","ranges":[{"startOffset":467788,"endOffset":467829,"count":0}],"isBlockCoverage":false},{"functionName":"$asJavaScriptIndexingBehavior","ranges":[{"startOffset":467904,"endOffset":467945,"count":0}],"isBlockCoverage":false},{"functionName":"$asListMixin","ranges":[{"startOffset":467965,"endOffset":468006,"count":0}],"isBlockCoverage":false},{"functionName":"$asIterable","ranges":[{"startOffset":468045,"endOffset":468086,"count":0}],"isBlockCoverage":false},{"functionName":"$asList","ranges":[{"startOffset":468117,"endOffset":468158,"count":0}],"isBlockCoverage":false},{"functionName":"$asImmutableListMixin","ranges":[{"startOffset":468187,"endOffset":468228,"count":0}],"isBlockCoverage":false},{"functionName":"get$origin","ranges":[{"startOffset":468330,"endOffset":468485,"count":1},{"startOffset":468414,"endOffset":468484,"count":0}],"isBlockCoverage":true},{"functionName":"toString$0","ranges":[{"startOffset":468503,"endOffset":468560,"count":0}],"isBlockCoverage":false},{"functionName":"addEventListener$3","ranges":[{"startOffset":468671,"endOffset":468926,"count":1}],"isBlockCoverage":true},{"functionName":"postMessage$1","ranges":[{"startOffset":468947,"endOffset":469079,"count":1}],"isBlockCoverage":true},{"functionName":"toString$0","ranges":[{"startOffset":469194,"endOffset":469338,"count":0}],"isBlockCoverage":false},{"functionName":"_removeChild$1","ranges":[{"startOffset":469360,"endOffset":469435,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":469505,"endOffset":469561,"count":0}],"isBlockCoverage":false},{"functionName":"get$location","ranges":[{"startOffset":469638,"endOffset":469696,"count":1}],"isBlockCoverage":true},{"functionName":"postMessage$3","ranges":[{"startOffset":469717,"endOffset":469975,"count":0}],"isBlockCoverage":false},{"functionName":"_postMessage_1$3","ranges":[{"startOffset":469999,"endOffset":470177,"count":0}],"isBlockCoverage":false},{"functionName":"listen$4$cancelOnError$onDone$onError","ranges":[{"startOffset":470279,"endOffset":470605,"count":0}],"isBlockCoverage":false},{"functionName":"listen$1","ranges":[{"startOffset":470621,"endOffset":470728,"count":0}],"isBlockCoverage":false},{"functionName":"listen$3$onDone$onError","ranges":[{"startOffset":470759,"endOffset":470888,"count":0}],"isBlockCoverage":false},{"functionName":"cancel$0","ranges":[{"startOffset":470995,"endOffset":471170,"count":0}],"isBlockCoverage":false},{"functionName":"_tryResume$0","ranges":[{"startOffset":471190,"endOffset":471364,"count":1}],"isBlockCoverage":true},{"functionName":"_unlisten$0","ranges":[{"startOffset":471383,"endOffset":471696,"count":0}],"isBlockCoverage":false},{"functionName":"set$_onData","ranges":[{"startOffset":471715,"endOffset":471819,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":471888,"endOffset":471979,"count":1}],"isBlockCoverage":true},{"functionName":"get$iterator","ranges":[{"startOffset":472060,"endOffset":472239,"count":0}],"isBlockCoverage":false},{"functionName":"moveNext$0","ranges":[{"startOffset":472301,"endOffset":472764,"count":0}],"isBlockCoverage":false},{"functionName":"get$current","ranges":[{"startOffset":472783,"endOffset":472829,"count":0}],"isBlockCoverage":false},{"functionName":"set$_current","ranges":[{"startOffset":472849,"endOffset":472972,"count":0}],"isBlockCoverage":false},{"functionName":"postMessage$3","ranges":[{"startOffset":473056,"endOffset":473227,"count":1}],"isBlockCoverage":true},{"functionName":"findSlot$1","ranges":[{"startOffset":473464,"endOffset":473763,"count":1},{"startOffset":473634,"endOffset":473643,"count":0}],"isBlockCoverage":true},{"functionName":"walk$1","ranges":[{"startOffset":473777,"endOffset":475247,"count":1},{"startOffset":474098,"endOffset":474130,"count":0},{"startOffset":474165,"endOffset":474240,"count":0},{"startOffset":474273,"endOffset":474282,"count":0},{"startOffset":474315,"endOffset":474324,"count":0},{"startOffset":474419,"endOffset":474428,"count":0},{"startOffset":474556,"endOffset":474581,"count":0},{"startOffset":474667,"endOffset":474679,"count":0},{"startOffset":475007,"endOffset":475032,"count":0},{"startOffset":475094,"endOffset":475106,"count":0},{"startOffset":475155,"endOffset":475246,"count":0}],"isBlockCoverage":true},{"functionName":"copyList$2","ranges":[{"startOffset":475265,"endOffset":475616,"count":1}],"isBlockCoverage":true},{"functionName":"call$2","ranges":[{"startOffset":475682,"endOffset":475766,"count":1}],"isBlockCoverage":true},{"functionName":"findSlot$1","ranges":[{"startOffset":475849,"endOffset":476208,"count":1},{"startOffset":476029,"endOffset":476044,"count":0},{"startOffset":476071,"endOffset":476080,"count":0}],"isBlockCoverage":true},{"functionName":"walk$1","ranges":[{"startOffset":476222,"endOffset":478314,"count":1},{"startOffset":476555,"endOffset":476884,"count":0},{"startOffset":476924,"endOffset":476999,"count":0},{"startOffset":477073,"endOffset":477118,"count":0},{"startOffset":477319,"endOffset":477344,"count":0},{"startOffset":477434,"endOffset":477446,"count":0},{"startOffset":477849,"endOffset":477874,"count":0},{"startOffset":477936,"endOffset":477948,"count":0},{"startOffset":478073,"endOffset":478076,"count":0},{"startOffset":478292,"endOffset":478313,"count":0}],"isBlockCoverage":true},{"functionName":"convertNativeToDart_AcceptStructuredClone$2$mustCopy","ranges":[{"startOffset":478374,"endOffset":478470,"count":1}],"isBlockCoverage":true},{"functionName":"call$2","ranges":[{"startOffset":478542,"endOffset":478706,"count":1}],"isBlockCoverage":true},{"functionName":"forEachJsField$2","ranges":[{"startOffset":478846,"endOffset":479184,"count":1},{"startOffset":479052,"endOffset":479098,"count":0}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":479261,"endOffset":479336,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":479433,"endOffset":479510,"count":0}],"isBlockCoverage":false},{"functionName":"get$onClick","ranges":[{"startOffset":479581,"endOffset":479693,"count":1}],"isBlockCoverage":true},{"functionName":"$asEfficientLengthIterable","ranges":[{"startOffset":479789,"endOffset":479829,"count":0}],"isBlockCoverage":false},{"functionName":"$asIterable","ranges":[{"startOffset":479868,"endOffset":479908,"count":0}],"isBlockCoverage":false},{"functionName":"$asList","ranges":[{"startOffset":479939,"endOffset":479979,"count":0}],"isBlockCoverage":false},{"functionName":"addStream$1","ranges":[{"startOffset":480035,"endOffset":480478,"count":0}],"isBlockCoverage":false},{"functionName":"_checkEventAllowed$0","ranges":[{"startOffset":480506,"endOffset":480767,"count":0}],"isBlockCoverage":false},{"functionName":"close$0","ranges":[{"startOffset":480782,"endOffset":480870,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":480987,"endOffset":481045,"count":0}],"isBlockCoverage":false},{"functionName":"absolute$7","ranges":[{"startOffset":481161,"endOffset":481639,"count":0}],"isBlockCoverage":false},{"functionName":"absolute$1","ranges":[{"startOffset":481657,"endOffset":481775,"count":0}],"isBlockCoverage":false},{"functionName":"join$8","ranges":[{"startOffset":481789,"endOffset":482229,"count":0}],"isBlockCoverage":false},{"functionName":"join$2","ranges":[{"startOffset":482243,"endOffset":482371,"count":0}],"isBlockCoverage":false},{"functionName":"joinAll$1","ranges":[{"startOffset":482388,"endOffset":483822,"count":0}],"isBlockCoverage":false},{"functionName":"split$1","ranges":[{"startOffset":483837,"endOffset":484322,"count":0}],"isBlockCoverage":false},{"functionName":"normalize$1","ranges":[{"startOffset":484341,"endOffset":484574,"count":0}],"isBlockCoverage":false},{"functionName":"_needsNormalization$1","ranges":[{"startOffset":484603,"endOffset":486114,"count":0}],"isBlockCoverage":false},{"functionName":"relative$1","ranges":[{"startOffset":486132,"endOffset":488870,"count":0}],"isBlockCoverage":false},{"functionName":"toUri$1","ranges":[{"startOffset":488885,"endOffset":489181,"count":0}],"isBlockCoverage":false},{"functionName":"prettyUri$1","ranges":[{"startOffset":489200,"endOffset":489763,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":489820,"endOffset":489888,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":489967,"endOffset":490034,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":490111,"endOffset":490184,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":490264,"endOffset":490368,"count":0}],"isBlockCoverage":false},{"functionName":"getRoot$1","ranges":[{"startOffset":490441,"endOffset":490777,"count":0}],"isBlockCoverage":false},{"functionName":"relativePathToUri$1","ranges":[{"startOffset":490804,"endOffset":491061,"count":0}],"isBlockCoverage":false},{"functionName":"pathsEqual$2","ranges":[{"startOffset":491081,"endOffset":491140,"count":0}],"isBlockCoverage":false},{"functionName":"get$hasTrailingSeparator","ranges":[{"startOffset":491205,"endOffset":491442,"count":0}],"isBlockCoverage":false},{"functionName":"removeTrailingSeparators$0","ranges":[{"startOffset":491476,"endOffset":491891,"count":0}],"isBlockCoverage":false},{"functionName":"normalize$0","ranges":[{"startOffset":491910,"endOffset":493391,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":493409,"endOffset":493905,"count":0}],"isBlockCoverage":false},{"functionName":"set$parts","ranges":[{"startOffset":493922,"endOffset":494022,"count":0}],"isBlockCoverage":false},{"functionName":"set$separators","ranges":[{"startOffset":494044,"endOffset":494159,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":494224,"endOffset":494290,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":494364,"endOffset":494430,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":494476,"endOffset":494529,"count":0}],"isBlockCoverage":false},{"functionName":"containsSeparator$1","ranges":[{"startOffset":494589,"endOffset":494666,"count":0}],"isBlockCoverage":false},{"functionName":"isSeparator$1","ranges":[{"startOffset":494687,"endOffset":494743,"count":0}],"isBlockCoverage":false},{"functionName":"needsSeparator$1","ranges":[{"startOffset":494767,"endOffset":494881,"count":0}],"isBlockCoverage":false},{"functionName":"rootLength$2$withDrive","ranges":[{"startOffset":494911,"endOffset":495044,"count":0}],"isBlockCoverage":false},{"functionName":"rootLength$1","ranges":[{"startOffset":495064,"endOffset":495141,"count":0}],"isBlockCoverage":false},{"functionName":"isRootRelative$1","ranges":[{"startOffset":495165,"endOffset":495207,"count":0}],"isBlockCoverage":false},{"functionName":"pathFromUri$1","ranges":[{"startOffset":495228,"endOffset":495555,"count":0}],"isBlockCoverage":false},{"functionName":"absolutePathToUri$1","ranges":[{"startOffset":495582,"endOffset":495981,"count":0}],"isBlockCoverage":false},{"functionName":"get$name","ranges":[{"startOffset":495997,"endOffset":496047,"count":0}],"isBlockCoverage":false},{"functionName":"get$separator","ranges":[{"startOffset":496068,"endOffset":496115,"count":0}],"isBlockCoverage":false},{"functionName":"containsSeparator$1","ranges":[{"startOffset":496173,"endOffset":496250,"count":0}],"isBlockCoverage":false},{"functionName":"isSeparator$1","ranges":[{"startOffset":496271,"endOffset":496327,"count":0}],"isBlockCoverage":false},{"functionName":"needsSeparator$1","ranges":[{"startOffset":496351,"endOffset":496625,"count":0}],"isBlockCoverage":false},{"functionName":"rootLength$2$withDrive","ranges":[{"startOffset":496655,"endOffset":497605,"count":0}],"isBlockCoverage":false},{"functionName":"rootLength$1","ranges":[{"startOffset":497625,"endOffset":497702,"count":0}],"isBlockCoverage":false},{"functionName":"isRootRelative$1","ranges":[{"startOffset":497726,"endOffset":497817,"count":0}],"isBlockCoverage":false},{"functionName":"pathFromUri$1","ranges":[{"startOffset":497838,"endOffset":497892,"count":0}],"isBlockCoverage":false},{"functionName":"relativePathToUri$1","ranges":[{"startOffset":497919,"endOffset":497973,"count":0}],"isBlockCoverage":false},{"functionName":"absolutePathToUri$1","ranges":[{"startOffset":498000,"endOffset":498054,"count":0}],"isBlockCoverage":false},{"functionName":"get$name","ranges":[{"startOffset":498070,"endOffset":498120,"count":0}],"isBlockCoverage":false},{"functionName":"get$separator","ranges":[{"startOffset":498141,"endOffset":498188,"count":0}],"isBlockCoverage":false},{"functionName":"containsSeparator$1","ranges":[{"startOffset":498250,"endOffset":498327,"count":0}],"isBlockCoverage":false},{"functionName":"isSeparator$1","ranges":[{"startOffset":498348,"endOffset":498423,"count":0}],"isBlockCoverage":false},{"functionName":"needsSeparator$1","ranges":[{"startOffset":498447,"endOffset":498622,"count":0}],"isBlockCoverage":false},{"functionName":"rootLength$2$withDrive","ranges":[{"startOffset":498652,"endOffset":499519,"count":0}],"isBlockCoverage":false},{"functionName":"rootLength$1","ranges":[{"startOffset":499539,"endOffset":499616,"count":0}],"isBlockCoverage":false},{"functionName":"isRootRelative$1","ranges":[{"startOffset":499640,"endOffset":499706,"count":0}],"isBlockCoverage":false},{"functionName":"pathFromUri$1","ranges":[{"startOffset":499727,"endOffset":500380,"count":0}],"isBlockCoverage":false},{"functionName":"absolutePathToUri$1","ranges":[{"startOffset":500407,"endOffset":501556,"count":0}],"isBlockCoverage":false},{"functionName":"codeUnitsEqual$2","ranges":[{"startOffset":501580,"endOffset":501969,"count":0}],"isBlockCoverage":false},{"functionName":"pathsEqual$2","ranges":[{"startOffset":501989,"endOffset":502379,"count":0}],"isBlockCoverage":false},{"functionName":"get$name","ranges":[{"startOffset":502395,"endOffset":502445,"count":0}],"isBlockCoverage":false},{"functionName":"get$separator","ranges":[{"startOffset":502466,"endOffset":502513,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":502588,"endOffset":502655,"count":0}],"isBlockCoverage":false},{"functionName":"toTrace$0","ranges":[{"startOffset":502719,"endOffset":503068,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":503086,"endOffset":503628,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":503712,"endOffset":503885,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":503968,"endOffset":504053,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":504131,"endOffset":504223,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":504303,"endOffset":504679,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":504760,"endOffset":504879,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":504958,"endOffset":505296,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":505376,"endOffset":505558,"count":0}],"isBlockCoverage":false},{"functionName":"get$isCore","ranges":[{"startOffset":505624,"endOffset":505689,"count":0}],"isBlockCoverage":false},{"functionName":"get$library","ranges":[{"startOffset":505708,"endOffset":505863,"count":0}],"isBlockCoverage":false},{"functionName":"get$$package","ranges":[{"startOffset":505883,"endOffset":506053,"count":0}],"isBlockCoverage":false},{"functionName":"get$location","ranges":[{"startOffset":506073,"endOffset":506361,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":506379,"endOffset":506469,"count":0}],"isBlockCoverage":false},{"functionName":"get$uri","ranges":[{"startOffset":506484,"endOffset":506525,"count":0}],"isBlockCoverage":false},{"functionName":"get$line","ranges":[{"startOffset":506541,"endOffset":506583,"count":0}],"isBlockCoverage":false},{"functionName":"get$column","ranges":[{"startOffset":506601,"endOffset":506645,"count":0}],"isBlockCoverage":false},{"functionName":"get$member","ranges":[{"startOffset":506663,"endOffset":506707,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":506771,"endOffset":507825,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":507908,"endOffset":508742,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":508839,"endOffset":509859,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":509948,"endOffset":511172,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":511261,"endOffset":512805,"count":0}],"isBlockCoverage":false},{"functionName":"get$_trace","ranges":[{"startOffset":512874,"endOffset":513090,"count":0}],"isBlockCoverage":false},{"functionName":"get$frames","ranges":[{"startOffset":513108,"endOffset":513171,"count":0}],"isBlockCoverage":false},{"functionName":"get$terse","ranges":[{"startOffset":513188,"endOffset":513273,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":513291,"endOffset":513357,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":513456,"endOffset":513524,"count":0}],"isBlockCoverage":false},{"functionName":"get$terse","ranges":[{"startOffset":513589,"endOffset":513680,"count":0}],"isBlockCoverage":false},{"functionName":"foldFrames$2$terse","ranges":[{"startOffset":513706,"endOffset":515185,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":515203,"endOffset":515689,"count":0}],"isBlockCoverage":false},{"functionName":"get$frames","ranges":[{"startOffset":515729,"endOffset":515773,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":515834,"endOffset":515912,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":515991,"endOffset":516074,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":516151,"endOffset":516253,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":516331,"endOffset":516414,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":516495,"endOffset":516567,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":516649,"endOffset":516732,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":516814,"endOffset":516927,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":517010,"endOffset":517098,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":517181,"endOffset":517269,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":517353,"endOffset":517442,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":517517,"endOffset":517556,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":517637,"endOffset":517967,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":518049,"endOffset":518442,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":518522,"endOffset":518641,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":518720,"endOffset":518989,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":519063,"endOffset":519108,"count":0}],"isBlockCoverage":false},{"functionName":"get$uri","ranges":[{"startOffset":519140,"endOffset":519181,"count":0}],"isBlockCoverage":false},{"functionName":"get$line","ranges":[{"startOffset":519197,"endOffset":519239,"count":0}],"isBlockCoverage":false},{"functionName":"get$column","ranges":[{"startOffset":519257,"endOffset":519301,"count":0}],"isBlockCoverage":false},{"functionName":"get$isCore","ranges":[{"startOffset":519319,"endOffset":519363,"count":0}],"isBlockCoverage":false},{"functionName":"get$library","ranges":[{"startOffset":519382,"endOffset":519427,"count":0}],"isBlockCoverage":false},{"functionName":"get$$package","ranges":[{"startOffset":519447,"endOffset":519493,"count":0}],"isBlockCoverage":false},{"functionName":"get$location","ranges":[{"startOffset":519513,"endOffset":519567,"count":0}],"isBlockCoverage":false},{"functionName":"get$member","ranges":[{"startOffset":519585,"endOffset":519629,"count":0}],"isBlockCoverage":false},{"functionName":"GuaranteeChannel$3$allowSinkErrors","ranges":[{"startOffset":519710,"endOffset":520046,"count":1}],"isBlockCoverage":true},{"functionName":"_onSinkDisconnected$0","ranges":[{"startOffset":520075,"endOffset":520266,"count":0}],"isBlockCoverage":false},{"functionName":"set$_sink","ranges":[{"startOffset":520283,"endOffset":520401,"count":1}],"isBlockCoverage":true},{"functionName":"set$_streamController","ranges":[{"startOffset":520430,"endOffset":520588,"count":1}],"isBlockCoverage":true},{"functionName":"set$_guarantee_channel$_subscription","ranges":[{"startOffset":520632,"endOffset":520801,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":520862,"endOffset":521185,"count":1},{"startOffset":520956,"endOffset":520963,"count":0}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":521266,"endOffset":521392,"count":0}],"isBlockCoverage":false},{"functionName":"add$1","ranges":[{"startOffset":521510,"endOffset":522038,"count":1},{"startOffset":521653,"endOffset":521725,"count":0},{"startOffset":521781,"endOffset":521859,"count":0},{"startOffset":521898,"endOffset":521905,"count":0}],"isBlockCoverage":true},{"functionName":"addError$2","ranges":[{"startOffset":522056,"endOffset":522478,"count":0}],"isBlockCoverage":false},{"functionName":"addError$1","ranges":[{"startOffset":522496,"endOffset":522562,"count":0}],"isBlockCoverage":false},{"functionName":"_addError$2","ranges":[{"startOffset":522581,"endOffset":522735,"count":0}],"isBlockCoverage":false},{"functionName":"_addError$1","ranges":[{"startOffset":522754,"endOffset":522821,"count":0}],"isBlockCoverage":false},{"functionName":"addStream$1","ranges":[{"startOffset":522840,"endOffset":523722,"count":1},{"startOffset":522976,"endOffset":523049,"count":0},{"startOffset":523105,"endOffset":523184,"count":0},{"startOffset":523215,"endOffset":523334,"count":0}],"isBlockCoverage":true},{"functionName":"close$0","ranges":[{"startOffset":523737,"endOffset":524196,"count":0}],"isBlockCoverage":false},{"functionName":"_onStreamDisconnected$0","ranges":[{"startOffset":524227,"endOffset":524597,"count":0}],"isBlockCoverage":false},{"functionName":"set$_addStreamSubscription","ranges":[{"startOffset":524631,"endOffset":524808,"count":1}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":524925,"endOffset":525051,"count":0}],"isBlockCoverage":false},{"functionName":"_MultiChannel$1","ranges":[{"startOffset":525129,"endOffset":525778,"count":1}],"isBlockCoverage":true},{"functionName":"virtualChannel$1","ranges":[{"startOffset":525802,"endOffset":527448,"count":1},{"startOffset":526029,"endOffset":526206,"count":0},{"startOffset":526253,"endOffset":526477,"count":0},{"startOffset":526530,"endOffset":526575,"count":0},{"startOffset":526702,"endOffset":526803,"count":0}],"isBlockCoverage":true},{"functionName":"_closeChannel$2","ranges":[{"startOffset":527471,"endOffset":527872,"count":0}],"isBlockCoverage":false},{"functionName":"_closeInnerChannel$0","ranges":[{"startOffset":527900,"endOffset":528350,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":528432,"endOffset":528620,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":528638,"endOffset":528705,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":528764,"endOffset":528829,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":528956,"endOffset":529518,"count":1},{"startOffset":529164,"endOffset":529171,"count":0},{"startOffset":529457,"endOffset":529512,"count":0}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":529596,"endOffset":529733,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":529751,"endOffset":529835,"count":1}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":529908,"endOffset":530159,"count":1}],"isBlockCoverage":true},{"functionName":"$signature","ranges":[{"startOffset":530177,"endOffset":530277,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":530351,"endOffset":530463,"count":0}],"isBlockCoverage":false},{"functionName":"get$local","ranges":[{"startOffset":530647,"endOffset":530691,"count":0}],"isBlockCoverage":false},{"functionName":"set$_local","ranges":[{"startOffset":530709,"endOffset":530828,"count":1}],"isBlockCoverage":true},{"functionName":"set$_foreign","ranges":[{"startOffset":530848,"endOffset":530973,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":531139,"endOffset":532002,"count":1}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":532120,"endOffset":533385,"count":1},{"startOffset":532816,"endOffset":533379,"count":0}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":533455,"endOffset":533696,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":533767,"endOffset":534167,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":534238,"endOffset":534587,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":534706,"endOffset":534887,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":535005,"endOffset":535323,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":535453,"endOffset":535766,"count":1}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":535848,"endOffset":535966,"count":1}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":536046,"endOffset":537326,"count":1},{"startOffset":536265,"endOffset":536272,"count":0},{"startOffset":536472,"endOffset":536479,"count":0},{"startOffset":536965,"endOffset":537320,"count":0}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":537408,"endOffset":537676,"count":1}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":537758,"endOffset":538685,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":537952,"endOffset":538612,"count":1},{"startOffset":538042,"endOffset":538098,"count":0}],"isBlockCoverage":true},{"functionName":"aliases","ranges":[{"startOffset":538714,"endOffset":539149,"count":1}],"isBlockCoverage":true},{"functionName":"installTearOffs","ranges":[{"startOffset":539157,"endOffset":544148,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":540050,"endOffset":540089,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":540135,"endOffset":540205,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":540496,"endOffset":540593,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":540702,"endOffset":540820,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":540931,"endOffset":541070,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":541195,"endOffset":541305,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":541440,"endOffset":541561,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":541698,"endOffset":541826,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":542583,"endOffset":542622,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":542757,"endOffset":542796,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":542921,"endOffset":542960,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":543161,"endOffset":543200,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":543660,"endOffset":543699,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":543800,"endOffset":543839,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":544079,"endOffset":544134,"count":0}],"isBlockCoverage":false},{"functionName":"inheritance","ranges":[{"startOffset":544156,"endOffset":556574,"count":1}],"isBlockCoverage":true},{"functionName":"constants","ranges":[{"startOffset":556582,"endOffset":566012,"count":1}],"isBlockCoverage":true},{"functionName":"getTagFallback","ranges":[{"startOffset":557622,"endOffset":557736,"count":0}],"isBlockCoverage":false},{"functionName":"C.C_JS_CONST0","ranges":[{"startOffset":557758,"endOffset":558873,"count":1},{"startOffset":558781,"endOffset":558796,"count":0}],"isBlockCoverage":true},{"functionName":"getTag","ranges":[{"startOffset":557825,"endOffset":557928,"count":0}],"isBlockCoverage":false},{"functionName":"getUnknownTag","ranges":[{"startOffset":557931,"endOffset":558148,"count":0}],"isBlockCoverage":false},{"functionName":"getUnknownTagGenericBrowser","ranges":[{"startOffset":558151,"endOffset":558326,"count":0}],"isBlockCoverage":false},{"functionName":"prototypeForTag","ranges":[{"startOffset":558329,"endOffset":558595,"count":1},{"startOffset":558399,"endOffset":558411,"count":0},{"startOffset":558545,"endOffset":558557,"count":0}],"isBlockCoverage":true},{"functionName":"discriminator","ranges":[{"startOffset":558598,"endOffset":558642,"count":0}],"isBlockCoverage":false},{"functionName":"C.C_JS_CONST6","ranges":[{"startOffset":558895,"endOffset":559359,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":558931,"endOffset":559356,"count":1},{"startOffset":558987,"endOffset":559000,"count":0},{"startOffset":559078,"endOffset":559091,"count":0},{"startOffset":559317,"endOffset":559355,"count":0}],"isBlockCoverage":true},{"functionName":"confirm","ranges":[{"startOffset":559135,"endOffset":559242,"count":1}],"isBlockCoverage":true},{"functionName":"C.C_JS_CONST1","ranges":[{"startOffset":559381,"endOffset":559530,"count":1},{"startOffset":559468,"endOffset":559529,"count":0}],"isBlockCoverage":true},{"functionName":"C.C_JS_CONST2","ranges":[{"startOffset":559552,"endOffset":560025,"count":1}],"isBlockCoverage":true},{"functionName":"getTagFixed","ranges":[{"startOffset":559648,"endOffset":559829,"count":0}],"isBlockCoverage":false},{"functionName":"prototypeForTagFixed","ranges":[{"startOffset":559832,"endOffset":559945,"count":1}],"isBlockCoverage":true},{"functionName":"C.C_JS_CONST5","ranges":[{"startOffset":560047,"endOffset":560577,"count":1},{"startOffset":560134,"endOffset":560138,"count":0},{"startOffset":560195,"endOffset":560576,"count":0}],"isBlockCoverage":true},{"functionName":"getTagFirefox","ranges":[{"startOffset":560454,"endOffset":560543,"count":0}],"isBlockCoverage":false},{"functionName":"C.C_JS_CONST4","ranges":[{"startOffset":560599,"endOffset":561481,"count":1},{"startOffset":560686,"endOffset":560690,"count":0},{"startOffset":560748,"endOffset":561480,"count":0}],"isBlockCoverage":true},{"functionName":"getTagIE","ranges":[{"startOffset":561013,"endOffset":561256,"count":0}],"isBlockCoverage":false},{"functionName":"prototypeForTagIE","ranges":[{"startOffset":561259,"endOffset":561407,"count":0}],"isBlockCoverage":false},{"functionName":"C.C_JS_CONST3","ranges":[{"startOffset":561503,"endOffset":561536,"count":1}],"isBlockCoverage":true},{"functionName":"staticFields","ranges":[{"startOffset":566020,"endOffset":566720,"count":1}],"isBlockCoverage":true},{"functionName":"lazyInitializers","ranges":[{"startOffset":566728,"endOffset":575683,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":566870,"endOffset":566948,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":567029,"endOffset":567098,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":567199,"endOffset":567387,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":567484,"endOffset":567687,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":567780,"endOffset":567891,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":567998,"endOffset":568249,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":568352,"endOffset":568465,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":568582,"endOffset":568837,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":568938,"endOffset":569053,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":569168,"endOffset":569357,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":569468,"endOffset":569585,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":569710,"endOffset":569903,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":570002,"endOffset":570077,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":570142,"endOffset":570223,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":570288,"endOffset":570350,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":570413,"endOffset":570448,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":570517,"endOffset":570578,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":570669,"endOffset":571283,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":571354,"endOffset":571512,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":571583,"endOffset":571664,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":571721,"endOffset":571771,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":571814,"endOffset":571888,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":571931,"endOffset":572008,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":572059,"endOffset":572220,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":572275,"endOffset":572548,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":572595,"endOffset":572849,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":572906,"endOffset":572966,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":573011,"endOffset":573116,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":573161,"endOffset":573279,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":573336,"endOffset":573422,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":573481,"endOffset":573591,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":573658,"endOffset":573797,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":573854,"endOffset":573959,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":574008,"endOffset":574107,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":574158,"endOffset":574221,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":574282,"endOffset":574368,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":574437,"endOffset":574524,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":574577,"endOffset":574658,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":574703,"endOffset":574773,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":574826,"endOffset":574893,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":574960,"endOffset":575064,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":575121,"endOffset":575225,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":575270,"endOffset":575364,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":575421,"endOffset":575534,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":575585,"endOffset":575677,"count":1}],"isBlockCoverage":true},{"functionName":"nativeSupport","ranges":[{"startOffset":579591,"endOffset":596530,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":579623,"endOffset":580408,"count":1},{"startOffset":580311,"endOffset":580407,"count":0}],"isBlockCoverage":true},{"functionName":"intern","ranges":[{"startOffset":579655,"endOffset":579781,"count":1}],"isBlockCoverage":true},{"functionName":"init.getIsolateTag","ranges":[{"startOffset":579810,"endOffset":579894,"count":1}],"isBlockCoverage":true},{"functionName":"Function.call$0","ranges":[{"startOffset":596565,"endOffset":596600,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$1","ranges":[{"startOffset":596632,"endOffset":596669,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$2","ranges":[{"startOffset":596701,"endOffset":596744,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$3","ranges":[{"startOffset":596776,"endOffset":596825,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$4","ranges":[{"startOffset":596857,"endOffset":596912,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$1$1","ranges":[{"startOffset":596946,"endOffset":596983,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$3$1","ranges":[{"startOffset":597017,"endOffset":597054,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$3$3","ranges":[{"startOffset":597088,"endOffset":597137,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$2$2","ranges":[{"startOffset":597171,"endOffset":597214,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$2$1","ranges":[{"startOffset":597248,"endOffset":597285,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$2$3","ranges":[{"startOffset":597319,"endOffset":597368,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$1$2","ranges":[{"startOffset":597402,"endOffset":597445,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$5","ranges":[{"startOffset":597477,"endOffset":597538,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$3$4","ranges":[{"startOffset":597572,"endOffset":597627,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$2$4","ranges":[{"startOffset":597661,"endOffset":597716,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$1$4","ranges":[{"startOffset":597750,"endOffset":597805,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$3$6","ranges":[{"startOffset":597839,"endOffset":597906,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$2$5","ranges":[{"startOffset":597940,"endOffset":598001,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":598067,"endOffset":598606,"count":1},{"startOffset":598129,"endOffset":598227,"count":0},{"startOffset":598282,"endOffset":598605,"count":0}],"isBlockCoverage":true},{"functionName":"onLoad","ranges":[{"startOffset":598329,"endOffset":598499,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":598608,"endOffset":598784,"count":1},{"startOffset":598726,"endOffset":598753,"count":0}],"isBlockCoverage":true}]},{"scriptId":"6","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":276,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":275,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff_completeError$20","ranges":[{"startOffset":82,"endOffset":273,"count":0}],"isBlockCoverage":false}]},{"scriptId":"7","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":282,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":281,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff_complete$11","ranges":[{"startOffset":82,"endOffset":279,"count":1}],"isBlockCoverage":true}]},{"scriptId":"8","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":277,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":276,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff__completeError$22","ranges":[{"startOffset":82,"endOffset":274,"count":0}],"isBlockCoverage":false}]},{"scriptId":"9","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":277,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":276,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff_add$13","ranges":[{"startOffset":82,"endOffset":274,"count":1}],"isBlockCoverage":true}]},{"scriptId":"10","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":271,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":270,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff_addError$24","ranges":[{"startOffset":82,"endOffset":268,"count":1}],"isBlockCoverage":true}]},{"scriptId":"11","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":277,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":276,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff_add$15","ranges":[{"startOffset":82,"endOffset":274,"count":1}],"isBlockCoverage":true}]},{"scriptId":"12","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":272,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":271,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff__sendDone$06","ranges":[{"startOffset":82,"endOffset":269,"count":0}],"isBlockCoverage":false}]},{"scriptId":"13","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":271,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":270,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff_addError$27","ranges":[{"startOffset":82,"endOffset":268,"count":1}],"isBlockCoverage":true}]},{"scriptId":"14","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":272,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":271,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff__addError$28","ranges":[{"startOffset":82,"endOffset":269,"count":1}],"isBlockCoverage":true}]},{"scriptId":"15","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":281,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":280,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff__closeInnerChannel$09","ranges":[{"startOffset":82,"endOffset":278,"count":1}],"isBlockCoverage":true}]},{"scriptId":"16","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":62,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":61,"count":1}],"isBlockCoverage":true}]},{"scriptId":"17","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":92,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":91,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":32,"endOffset":89,"count":1},{"startOffset":87,"endOffset":88,"count":0}],"isBlockCoverage":true}]},{"scriptId":"18","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":62,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":61,"count":1}],"isBlockCoverage":true}]},{"scriptId":"19","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":86,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":85,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":32,"endOffset":83,"count":0}],"isBlockCoverage":false}]},{"scriptId":"20","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":62,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":61,"count":1}],"isBlockCoverage":true}]},{"scriptId":"21","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":106,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":105,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":32,"endOffset":103,"count":0}],"isBlockCoverage":false}]},{"scriptId":"22","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":62,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":61,"count":1}],"isBlockCoverage":true}]},{"scriptId":"23","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":86,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":85,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":32,"endOffset":83,"count":0}],"isBlockCoverage":false}]},{"scriptId":"24","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":62,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":61,"count":1}],"isBlockCoverage":true}]},{"scriptId":"25","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":92,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":91,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":32,"endOffset":89,"count":1},{"startOffset":87,"endOffset":88,"count":0}],"isBlockCoverage":true}]},{"scriptId":"26","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":64,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":63,"count":1}],"isBlockCoverage":true}]},{"scriptId":"27","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":98,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":97,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":32,"endOffset":95,"count":0}],"isBlockCoverage":false}]},{"scriptId":"28","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":64,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":63,"count":1}],"isBlockCoverage":true}]},{"scriptId":"29","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":89,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":88,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":32,"endOffset":86,"count":0}],"isBlockCoverage":false}]},{"scriptId":"30","url":"http://localhost:33587/JvrQonmMf3ATS3rRCO76ZTlBfGvy%2Fj69/test/packages/test/dart.js","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":2011,"count":1}],"isBlockCoverage":true},{"functionName":"window.onload","ranges":[{"startOffset":384,"endOffset":2009,"count":1},{"startOffset":1295,"endOffset":1442,"count":0},{"startOffset":1491,"endOffset":1629,"count":0}],"isBlockCoverage":true},{"functionName":"sendLoadException","ranges":[{"startOffset":560,"endOffset":762,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":875,"endOffset":921,"count":0}],"isBlockCoverage":false},{"functionName":"script.onerror","ranges":[{"startOffset":1747,"endOffset":1907,"count":0}],"isBlockCoverage":false}]},{"scriptId":"31","url":"http://localhost:33587/JvrQonmMf3ATS3rRCO76ZTlBfGvy%2Fj69/test/main_test.dart.browser_test.dart.js","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":1081547,"count":1}],"isBlockCoverage":true},{"functionName":"dartProgram","ranges":[{"startOffset":1051,"endOffset":1081480,"count":1}],"isBlockCoverage":true},{"functionName":"copyProperties","ranges":[{"startOffset":1078,"endOffset":1253,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1288,"endOffset":1858,"count":1},{"startOffset":1469,"endOffset":1482,"count":0},{"startOffset":1645,"endOffset":1707,"count":0},{"startOffset":1709,"endOffset":1812,"count":0},{"startOffset":1818,"endOffset":1857,"count":0}],"isBlockCoverage":true},{"functionName":"cls","ranges":[{"startOffset":1315,"endOffset":1333,"count":1}],"isBlockCoverage":true},{"functionName":"setFunctionNamesIfNecessary","ranges":[{"startOffset":1864,"endOffset":2287,"count":1},{"startOffset":1991,"endOffset":2283,"count":0}],"isBlockCoverage":true},{"functionName":"t","ranges":[{"startOffset":1916,"endOffset":1936,"count":0}],"isBlockCoverage":false},{"functionName":"inherit","ranges":[{"startOffset":2290,"endOffset":2685,"count":1},{"startOffset":2533,"endOffset":2681,"count":0}],"isBlockCoverage":true},{"functionName":"inheritMany","ranges":[{"startOffset":2688,"endOffset":2805,"count":1}],"isBlockCoverage":true},{"functionName":"mixin","ranges":[{"startOffset":2808,"endOffset":2929,"count":1}],"isBlockCoverage":true},{"functionName":"lazy","ranges":[{"startOffset":2932,"endOffset":3681,"count":1}],"isBlockCoverage":true},{"functionName":"holder.<computed>","ranges":[{"startOffset":3094,"endOffset":3676,"count":1},{"startOffset":3429,"endOffset":3467,"count":0},{"startOffset":3539,"endOffset":3559,"count":0}],"isBlockCoverage":true},{"functionName":"holder.<computed>","ranges":[{"startOffset":3134,"endOffset":3187,"count":0}],"isBlockCoverage":false},{"functionName":"holder.<computed>","ranges":[{"startOffset":3589,"endOffset":3640,"count":1}],"isBlockCoverage":true},{"functionName":"makeConstList","ranges":[{"startOffset":3684,"endOffset":3799,"count":1}],"isBlockCoverage":true},{"functionName":"convertToFastObject","ranges":[{"startOffset":3802,"endOffset":3939,"count":1}],"isBlockCoverage":true},{"functionName":"t","ranges":[{"startOffset":3849,"endOffset":3869,"count":1}],"isBlockCoverage":true},{"functionName":"convertAllToFastObject","ranges":[{"startOffset":3942,"endOffset":4093,"count":1}],"isBlockCoverage":true},{"functionName":"tearOffGetter","ranges":[{"startOffset":4123,"endOffset":5038,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff","ranges":[{"startOffset":5041,"endOffset":5449,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":5180,"endOffset":5362,"count":1}],"isBlockCoverage":true},{"functionName":"installTearOff","ranges":[{"startOffset":5475,"endOffset":6389,"count":1}],"isBlockCoverage":true},{"functionName":"installStaticTearOff","ranges":[{"startOffset":6392,"endOffset":6715,"count":1}],"isBlockCoverage":true},{"functionName":"installInstanceTearOff","ranges":[{"startOffset":6718,"endOffset":7067,"count":1}],"isBlockCoverage":true},{"functionName":"setOrUpdateInterceptorsByTag","ranges":[{"startOffset":7070,"endOffset":7273,"count":1},{"startOffset":7234,"endOffset":7272,"count":0}],"isBlockCoverage":true},{"functionName":"setOrUpdateLeafTags","ranges":[{"startOffset":7276,"endOffset":7452,"count":1},{"startOffset":7413,"endOffset":7451,"count":0}],"isBlockCoverage":true},{"functionName":"updateTypes","ranges":[{"startOffset":7455,"endOffset":7608,"count":0}],"isBlockCoverage":false},{"functionName":"updateHolder","ranges":[{"startOffset":7611,"endOffset":7715,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":7736,"endOffset":9400,"count":1}],"isBlockCoverage":true},{"functionName":"mkInstance","ranges":[{"startOffset":7770,"endOffset":8132,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":7890,"endOffset":8123,"count":1}],"isBlockCoverage":true},{"functionName":"mkStatic","ranges":[{"startOffset":8151,"endOffset":8481,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":8256,"endOffset":8472,"count":1}],"isBlockCoverage":true},{"functionName":"initializeDeferredHunk","ranges":[{"startOffset":9406,"endOffset":9527,"count":0}],"isBlockCoverage":false},{"functionName":"getGlobalFromName","ranges":[{"startOffset":9530,"endOffset":9727,"count":0}],"isBlockCoverage":false},{"functionName":"JS_CONST","ranges":[{"startOffset":9759,"endOffset":9786,"count":0}],"isBlockCoverage":false},{"functionName":"CastIterable_CastIterable","ranges":[{"startOffset":9819,"endOffset":10153,"count":1},{"startOffset":10045,"endOffset":10152,"count":0}],"isBlockCoverage":true},{"functionName":"hexDigitValue","ranges":[{"startOffset":10174,"endOffset":10439,"count":0}],"isBlockCoverage":false},{"functionName":"SubListIterable$","ranges":[{"startOffset":10463,"endOffset":10899,"count":0}],"isBlockCoverage":false},{"functionName":"MappedIterable_MappedIterable","ranges":[{"startOffset":10936,"endOffset":11314,"count":0}],"isBlockCoverage":false},{"functionName":"IterableElementError_noElement","ranges":[{"startOffset":11352,"endOffset":11415,"count":0}],"isBlockCoverage":false},{"functionName":"IterableElementError_tooMany","ranges":[{"startOffset":11451,"endOffset":11521,"count":0}],"isBlockCoverage":false},{"functionName":"IterableElementError_tooFew","ranges":[{"startOffset":11556,"endOffset":11625,"count":0}],"isBlockCoverage":false},{"functionName":"_CastIterableBase","ranges":[{"startOffset":11650,"endOffset":11686,"count":0}],"isBlockCoverage":false},{"functionName":"CastIterator","ranges":[{"startOffset":11706,"endOffset":11789,"count":1}],"isBlockCoverage":true},{"functionName":"CastIterable","ranges":[{"startOffset":11809,"endOffset":11892,"count":0}],"isBlockCoverage":false},{"functionName":"_EfficientLengthCastIterable","ranges":[{"startOffset":11928,"endOffset":12027,"count":1}],"isBlockCoverage":true},{"functionName":"CastMap","ranges":[{"startOffset":12042,"endOffset":12120,"count":1}],"isBlockCoverage":true},{"functionName":"CastMap_forEach_closure","ranges":[{"startOffset":12151,"endOffset":12241,"count":0}],"isBlockCoverage":false},{"functionName":"CodeUnits","ranges":[{"startOffset":12258,"endOffset":12313,"count":0}],"isBlockCoverage":false},{"functionName":"EfficientLengthIterable","ranges":[{"startOffset":12344,"endOffset":12386,"count":0}],"isBlockCoverage":false},{"functionName":"ListIterable","ranges":[{"startOffset":12406,"endOffset":12437,"count":0}],"isBlockCoverage":false},{"functionName":"SubListIterable","ranges":[{"startOffset":12460,"endOffset":12640,"count":0}],"isBlockCoverage":false},{"functionName":"ListIterator","ranges":[{"startOffset":12660,"endOffset":12874,"count":1}],"isBlockCoverage":true},{"functionName":"MappedIterable","ranges":[{"startOffset":12896,"endOffset":13018,"count":1}],"isBlockCoverage":true},{"functionName":"EfficientLengthMappedIterable","ranges":[{"startOffset":13055,"endOffset":13192,"count":0}],"isBlockCoverage":false},{"functionName":"MappedIterator","ranges":[{"startOffset":13214,"endOffset":13372,"count":1}],"isBlockCoverage":true},{"functionName":"MappedListIterable","ranges":[{"startOffset":13398,"endOffset":13511,"count":1}],"isBlockCoverage":true},{"functionName":"WhereIterable","ranges":[{"startOffset":13532,"endOffset":13653,"count":1}],"isBlockCoverage":true},{"functionName":"WhereIterator","ranges":[{"startOffset":13674,"endOffset":13784,"count":1}],"isBlockCoverage":true},{"functionName":"ExpandIterable","ranges":[{"startOffset":13806,"endOffset":13928,"count":0}],"isBlockCoverage":false},{"functionName":"ExpandIterator","ranges":[{"startOffset":13950,"endOffset":14144,"count":0}],"isBlockCoverage":false},{"functionName":"SkipWhileIterable","ranges":[{"startOffset":14169,"endOffset":14294,"count":0}],"isBlockCoverage":false},{"functionName":"SkipWhileIterator","ranges":[{"startOffset":14319,"endOffset":14473,"count":0}],"isBlockCoverage":false},{"functionName":"EmptyIterator","ranges":[{"startOffset":14494,"endOffset":14549,"count":1}],"isBlockCoverage":true},{"functionName":"FixedLengthListMixin","ranges":[{"startOffset":14577,"endOffset":14616,"count":0}],"isBlockCoverage":false},{"functionName":"UnmodifiableListMixin","ranges":[{"startOffset":14645,"endOffset":14685,"count":0}],"isBlockCoverage":false},{"functionName":"UnmodifiableListBase","ranges":[{"startOffset":14713,"endOffset":14752,"count":0}],"isBlockCoverage":false},{"functionName":"ReversedListIterable","ranges":[{"startOffset":14780,"endOffset":14871,"count":1}],"isBlockCoverage":true},{"functionName":"Symbol","ranges":[{"startOffset":14885,"endOffset":14946,"count":1}],"isBlockCoverage":true},{"functionName":"ConstantMap__throwUnmodifiable","ranges":[{"startOffset":14984,"endOffset":15086,"count":0}],"isBlockCoverage":false},{"functionName":"instantiate1","ranges":[{"startOffset":15106,"endOffset":15248,"count":0}],"isBlockCoverage":false},{"functionName":"unminifyOrTag","ranges":[{"startOffset":15269,"endOffset":15468,"count":0}],"isBlockCoverage":false},{"functionName":"isJsIndexable","ranges":[{"startOffset":15489,"endOffset":15722,"count":0}],"isBlockCoverage":false},{"functionName":"S","ranges":[{"startOffset":15731,"endOffset":16230,"count":1},{"startOffset":15821,"endOffset":16229,"count":0}],"isBlockCoverage":true},{"functionName":"Primitives_objectHashCode","ranges":[{"startOffset":16263,"endOffset":16463,"count":1}],"isBlockCoverage":true},{"functionName":"Primitives_parseInt","ranges":[{"startOffset":16490,"endOffset":17741,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_objectTypeName","ranges":[{"startOffset":17774,"endOffset":17874,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives__objectTypeNameNewRti","ranges":[{"startOffset":17914,"endOffset":18695,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives__saneNativeClassName","ranges":[{"startOffset":18734,"endOffset":18825,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_dateNow","ranges":[{"startOffset":18851,"endOffset":18894,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_initTicker","ranges":[{"startOffset":18923,"endOffset":19555,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_currentUri","ranges":[{"startOffset":19584,"endOffset":19683,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives__fromCharCodeApply","ranges":[{"startOffset":19720,"endOffset":20112,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_stringFromCodePoints","ranges":[{"startOffset":20151,"endOffset":20858,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_stringFromCharCodes","ranges":[{"startOffset":20896,"endOffset":21417,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_stringFromNativeUint8List","ranges":[{"startOffset":21461,"endOffset":21888,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_stringFromCharCode","ranges":[{"startOffset":21925,"endOffset":22438,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_lazyAsJsDate","ranges":[{"startOffset":22469,"endOffset":22610,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_getYear","ranges":[{"startOffset":22636,"endOffset":22752,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_getMonth","ranges":[{"startOffset":22779,"endOffset":22892,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_getDay","ranges":[{"startOffset":22917,"endOffset":23029,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_getHours","ranges":[{"startOffset":23056,"endOffset":23169,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_getMinutes","ranges":[{"startOffset":23198,"endOffset":23313,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_getSeconds","ranges":[{"startOffset":23342,"endOffset":23457,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_getMilliseconds","ranges":[{"startOffset":23491,"endOffset":23611,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_getProperty","ranges":[{"startOffset":23641,"endOffset":23862,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_setProperty","ranges":[{"startOffset":23892,"endOffset":24121,"count":0}],"isBlockCoverage":false},{"functionName":"iae","ranges":[{"startOffset":24132,"endOffset":24219,"count":0}],"isBlockCoverage":false},{"functionName":"ioore","ranges":[{"startOffset":24232,"endOffset":24397,"count":0}],"isBlockCoverage":false},{"functionName":"diagnoseIndexError","ranges":[{"startOffset":24423,"endOffset":24943,"count":0}],"isBlockCoverage":false},{"functionName":"diagnoseRangeError","ranges":[{"startOffset":24969,"endOffset":25354,"count":0}],"isBlockCoverage":false},{"functionName":"argumentErrorValue","ranges":[{"startOffset":25380,"endOffset":25464,"count":0}],"isBlockCoverage":false},{"functionName":"checkNum","ranges":[{"startOffset":25480,"endOffset":25619,"count":0}],"isBlockCoverage":false},{"functionName":"wrapException","ranges":[{"startOffset":25640,"endOffset":26027,"count":0}],"isBlockCoverage":false},{"functionName":"toStringWrapper","ranges":[{"startOffset":26050,"endOffset":26116,"count":0}],"isBlockCoverage":false},{"functionName":"throwExpression","ranges":[{"startOffset":26139,"endOffset":26192,"count":0}],"isBlockCoverage":false},{"functionName":"throwConcurrentModificationError","ranges":[{"startOffset":26232,"endOffset":26333,"count":0}],"isBlockCoverage":false},{"functionName":"TypeErrorDecoder_extractPattern","ranges":[{"startOffset":26372,"endOffset":27376,"count":0}],"isBlockCoverage":false},{"functionName":"TypeErrorDecoder_provokeCallErrorOn","ranges":[{"startOffset":27419,"endOffset":27663,"count":0}],"isBlockCoverage":false},{"functionName":"TypeErrorDecoder_provokePropertyErrorOn","ranges":[{"startOffset":27710,"endOffset":27892,"count":0}],"isBlockCoverage":false},{"functionName":"NullError$","ranges":[{"startOffset":27910,"endOffset":28020,"count":0}],"isBlockCoverage":false},{"functionName":"JsNoSuchMethodError$","ranges":[{"startOffset":28048,"endOffset":28232,"count":0}],"isBlockCoverage":false},{"functionName":"unwrapException","ranges":[{"startOffset":28255,"endOffset":32748,"count":0}],"isBlockCoverage":false},{"functionName":"getTraceFromException","ranges":[{"startOffset":32777,"endOffset":33142,"count":1},{"startOffset":32881,"endOffset":32909,"count":0},{"startOffset":32947,"endOffset":32983,"count":0},{"startOffset":33055,"endOffset":33068,"count":0}],"isBlockCoverage":true},{"functionName":"objectHashCode","ranges":[{"startOffset":33164,"endOffset":33346,"count":1}],"isBlockCoverage":true},{"functionName":"fillLiteralMap","ranges":[{"startOffset":33368,"endOffset":33829,"count":1}],"isBlockCoverage":true},{"functionName":"fillLiteralSet","ranges":[{"startOffset":33851,"endOffset":34149,"count":1}],"isBlockCoverage":true},{"functionName":"invokeClosure","ranges":[{"startOffset":34170,"endOffset":34749,"count":1},{"startOffset":34441,"endOffset":34493,"count":0},{"startOffset":34502,"endOffset":34560,"count":0},{"startOffset":34569,"endOffset":34633,"count":0},{"startOffset":34641,"endOffset":34748,"count":0}],"isBlockCoverage":true},{"functionName":"convertDartClosureToJS","ranges":[{"startOffset":34779,"endOffset":35232,"count":1},{"startOffset":34862,"endOffset":34874,"count":0}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":34979,"endOffset":35131,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":35029,"endOffset":35122,"count":1}],"isBlockCoverage":true},{"functionName":"Closure_fromTearOff","ranges":[{"startOffset":35259,"endOffset":37399,"count":1},{"startOffset":36000,"endOffset":36017,"count":0},{"startOffset":37093,"endOffset":37187,"count":0}],"isBlockCoverage":true},{"functionName":"static_tear_off","ranges":[{"startOffset":35831,"endOffset":35899,"count":0}],"isBlockCoverage":false},{"functionName":"Closure__computeSignatureFunctionNewRti","ranges":[{"startOffset":37446,"endOffset":38243,"count":1},{"startOffset":37726,"endOffset":38242,"count":0}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":37580,"endOffset":37686,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":37620,"endOffset":37675,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":37997,"endOffset":38133,"count":0}],"isBlockCoverage":false},{"functionName":"Closure_cspForwardCall","ranges":[{"startOffset":38273,"endOffset":39658,"count":1},{"startOffset":38399,"endOffset":38406,"count":0},{"startOffset":38418,"endOffset":38573,"count":0},{"startOffset":38582,"endOffset":38739,"count":0},{"startOffset":38748,"endOffset":38911,"count":0},{"startOffset":38920,"endOffset":39089,"count":0},{"startOffset":39098,"endOffset":39273,"count":0},{"startOffset":39282,"endOffset":39463,"count":0}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":38443,"endOffset":38553,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":38607,"endOffset":38719,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":38773,"endOffset":38891,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":38945,"endOffset":39069,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":39123,"endOffset":39253,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":39307,"endOffset":39443,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":39498,"endOffset":39623,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":39534,"endOffset":39610,"count":0}],"isBlockCoverage":false},{"functionName":"Closure_forwardCallTo","ranges":[{"startOffset":39687,"endOffset":41342,"count":1},{"startOffset":40050,"endOffset":40076,"count":0},{"startOffset":40341,"endOffset":40358,"count":0},{"startOffset":40594,"endOffset":40672,"count":0},{"startOffset":40960,"endOffset":40977,"count":0},{"startOffset":41199,"endOffset":41277,"count":0}],"isBlockCoverage":true},{"functionName":"Closure_cspForwardInterceptedCall","ranges":[{"startOffset":41383,"endOffset":43149,"count":0}],"isBlockCoverage":false},{"functionName":"Closure_forwardInterceptedCallTo","ranges":[{"startOffset":43189,"endOffset":44760,"count":1},{"startOffset":43766,"endOffset":43792,"count":0},{"startOffset":43880,"endOffset":43956,"count":0},{"startOffset":44175,"endOffset":44192,"count":0},{"startOffset":44651,"endOffset":44668,"count":0}],"isBlockCoverage":true},{"functionName":"closureFromTearOff","ranges":[{"startOffset":44786,"endOffset":45024,"count":1}],"isBlockCoverage":true},{"functionName":"BoundClosure_evalRecipe","ranges":[{"startOffset":45055,"endOffset":45190,"count":0}],"isBlockCoverage":false},{"functionName":"BoundClosure_evalRecipeIntercepted","ranges":[{"startOffset":45232,"endOffset":45371,"count":0}],"isBlockCoverage":false},{"functionName":"BoundClosure_selfOf","ranges":[{"startOffset":45398,"endOffset":45451,"count":0}],"isBlockCoverage":false},{"functionName":"BoundClosure_receiverOf","ranges":[{"startOffset":45482,"endOffset":45539,"count":0}],"isBlockCoverage":false},{"functionName":"BoundClosure_computeFieldNamed","ranges":[{"startOffset":45577,"endOffset":45937,"count":1}],"isBlockCoverage":true},{"functionName":"boolConversionCheck","ranges":[{"startOffset":45964,"endOffset":46094,"count":1},{"startOffset":46015,"endOffset":46068,"count":0}],"isBlockCoverage":true},{"functionName":"extractFunctionTypeObjectFromInternal","ranges":[{"startOffset":46139,"endOffset":46420,"count":0}],"isBlockCoverage":false},{"functionName":"TypeErrorImplementation$","ranges":[{"startOffset":46452,"endOffset":46662,"count":0}],"isBlockCoverage":false},{"functionName":"_typeDescription","ranges":[{"startOffset":46686,"endOffset":47058,"count":0}],"isBlockCoverage":false},{"functionName":"assertTest","ranges":[{"startOffset":47076,"endOffset":47290,"count":1},{"startOffset":47149,"endOffset":47289,"count":0}],"isBlockCoverage":true},{"functionName":"assertThrow","ranges":[{"startOffset":47309,"endOffset":47395,"count":0}],"isBlockCoverage":false},{"functionName":"assertHelper","ranges":[{"startOffset":47415,"endOffset":47533,"count":1},{"startOffset":47480,"endOffset":47527,"count":0}],"isBlockCoverage":true},{"functionName":"throwCyclicInit","ranges":[{"startOffset":47556,"endOffset":47658,"count":0}],"isBlockCoverage":false},{"functionName":"RuntimeError$","ranges":[{"startOffset":47679,"endOffset":47746,"count":0}],"isBlockCoverage":false},{"functionName":"getIsolateAffinityTag","ranges":[{"startOffset":47775,"endOffset":47838,"count":0}],"isBlockCoverage":false},{"functionName":"setRuntimeTypeInfo","ranges":[{"startOffset":47864,"endOffset":47973,"count":1}],"isBlockCoverage":true},{"functionName":"getRuntimeTypeInfo","ranges":[{"startOffset":47999,"endOffset":48095,"count":0}],"isBlockCoverage":false},{"functionName":"getRuntimeTypeArguments","ranges":[{"startOffset":48126,"endOffset":48282,"count":0}],"isBlockCoverage":false},{"functionName":"runtimeTypeToString","ranges":[{"startOffset":48309,"endOffset":48378,"count":0}],"isBlockCoverage":false},{"functionName":"_runtimeTypeToString","ranges":[{"startOffset":48406,"endOffset":49897,"count":0}],"isBlockCoverage":false},{"functionName":"_functionRtiToString0","ranges":[{"startOffset":49926,"endOffset":52750,"count":0}],"isBlockCoverage":false},{"functionName":"_joinArguments","ranges":[{"startOffset":52772,"endOffset":53457,"count":0}],"isBlockCoverage":false},{"functionName":"getRuntimeType","ranges":[{"startOffset":53479,"endOffset":53665,"count":0}],"isBlockCoverage":false},{"functionName":"substitute","ranges":[{"startOffset":53683,"endOffset":54227,"count":0}],"isBlockCoverage":false},{"functionName":"computeSignature","ranges":[{"startOffset":54251,"endOffset":54444,"count":0}],"isBlockCoverage":false},{"functionName":"invokeOn","ranges":[{"startOffset":54460,"endOffset":54686,"count":0}],"isBlockCoverage":false},{"functionName":"defineProperty","ranges":[{"startOffset":54708,"endOffset":54861,"count":0}],"isBlockCoverage":false},{"functionName":"lookupAndCacheInterceptor","ranges":[{"startOffset":54894,"endOffset":57451,"count":0}],"isBlockCoverage":false},{"functionName":"patchInteriorProto","ranges":[{"startOffset":57477,"endOffset":57764,"count":0}],"isBlockCoverage":false},{"functionName":"makeLeafDispatchRecord","ranges":[{"startOffset":57794,"endOffset":57929,"count":1}],"isBlockCoverage":true},{"functionName":"makeDefaultDispatchRecord","ranges":[{"startOffset":57962,"endOffset":58233,"count":1}],"isBlockCoverage":true},{"functionName":"initNativeDispatch","ranges":[{"startOffset":58259,"endOffset":58415,"count":1},{"startOffset":58325,"endOffset":58332,"count":0}],"isBlockCoverage":true},{"functionName":"initNativeDispatchContinue","ranges":[{"startOffset":58449,"endOffset":59729,"count":1}],"isBlockCoverage":true},{"functionName":"fun","ranges":[{"startOffset":58828,"endOffset":58850,"count":0}],"isBlockCoverage":false},{"functionName":"initHooks","ranges":[{"startOffset":59746,"endOffset":61000,"count":1},{"startOffset":60255,"endOffset":60668,"count":0}],"isBlockCoverage":true},{"functionName":"applyHooksTransformer","ranges":[{"startOffset":61029,"endOffset":61107,"count":1}],"isBlockCoverage":true},{"functionName":"JSSyntaxRegExp_makeNative","ranges":[{"startOffset":61140,"endOffset":61765,"count":0}],"isBlockCoverage":false},{"functionName":"_MatchImplementation$","ranges":[{"startOffset":61794,"endOffset":61977,"count":0}],"isBlockCoverage":false},{"functionName":"stringContainsUnchecked","ranges":[{"startOffset":62008,"endOffset":62496,"count":0}],"isBlockCoverage":false},{"functionName":"escapeReplacement","ranges":[{"startOffset":62521,"endOffset":62671,"count":0}],"isBlockCoverage":false},{"functionName":"stringReplaceFirstRE","ranges":[{"startOffset":62699,"endOffset":62974,"count":0}],"isBlockCoverage":false},{"functionName":"quoteStringForRegExp","ranges":[{"startOffset":63002,"endOffset":63154,"count":0}],"isBlockCoverage":false},{"functionName":"stringReplaceAllUnchecked","ranges":[{"startOffset":63187,"endOffset":63770,"count":0}],"isBlockCoverage":false},{"functionName":"stringReplaceAllUncheckedString","ranges":[{"startOffset":63809,"endOffset":64503,"count":0}],"isBlockCoverage":false},{"functionName":"_stringIdentity","ranges":[{"startOffset":64526,"endOffset":64571,"count":0}],"isBlockCoverage":false},{"functionName":"stringReplaceAllFuncUnchecked","ranges":[{"startOffset":64608,"endOffset":65417,"count":0}],"isBlockCoverage":false},{"functionName":"stringReplaceFirstUnchecked","ranges":[{"startOffset":65452,"endOffset":66440,"count":0}],"isBlockCoverage":false},{"functionName":"stringReplaceRangeUnchecked","ranges":[{"startOffset":66475,"endOffset":66661,"count":0}],"isBlockCoverage":false},{"functionName":"ConstantMap","ranges":[{"startOffset":66680,"endOffset":66710,"count":0}],"isBlockCoverage":false},{"functionName":"ConstantMap_map_closure","ranges":[{"startOffset":66741,"endOffset":66867,"count":0}],"isBlockCoverage":false},{"functionName":"ConstantStringMap","ranges":[{"startOffset":66892,"endOffset":67070,"count":1}],"isBlockCoverage":true},{"functionName":"_ConstantMapKeyIterable","ranges":[{"startOffset":67101,"endOffset":67204,"count":0}],"isBlockCoverage":false},{"functionName":"Instantiation","ranges":[{"startOffset":67225,"endOffset":67257,"count":0}],"isBlockCoverage":false},{"functionName":"Instantiation1","ranges":[{"startOffset":67279,"endOffset":67372,"count":0}],"isBlockCoverage":false},{"functionName":"Primitives_initTicker_closure","ranges":[{"startOffset":67409,"endOffset":67488,"count":0}],"isBlockCoverage":false},{"functionName":"TypeErrorDecoder","ranges":[{"startOffset":67512,"endOffset":67732,"count":0}],"isBlockCoverage":false},{"functionName":"NullError","ranges":[{"startOffset":67749,"endOffset":67846,"count":0}],"isBlockCoverage":false},{"functionName":"JsNoSuchMethodError","ranges":[{"startOffset":67873,"endOffset":68011,"count":0}],"isBlockCoverage":false},{"functionName":"UnknownJsTypeError","ranges":[{"startOffset":68037,"endOffset":68114,"count":0}],"isBlockCoverage":false},{"functionName":"ExceptionAndStackTrace","ranges":[{"startOffset":68144,"endOffset":68250,"count":0}],"isBlockCoverage":true},{"functionName":"unwrapException_saveStackTrace","ranges":[{"startOffset":68288,"endOffset":68359,"count":0}],"isBlockCoverage":false},{"functionName":"_StackTrace","ranges":[{"startOffset":68378,"endOffset":68464,"count":1}],"isBlockCoverage":true},{"functionName":"Closure","ranges":[{"startOffset":68479,"endOffset":68505,"count":0}],"isBlockCoverage":true},{"functionName":"TearOffClosure","ranges":[{"startOffset":68527,"endOffset":68560,"count":0}],"isBlockCoverage":false},{"functionName":"StaticClosure","ranges":[{"startOffset":68581,"endOffset":68613,"count":1}],"isBlockCoverage":true},{"functionName":"BoundClosure","ranges":[{"startOffset":68633,"endOffset":68784,"count":1}],"isBlockCoverage":true},{"functionName":"TypeErrorImplementation","ranges":[{"startOffset":68815,"endOffset":68884,"count":0}],"isBlockCoverage":false},{"functionName":"RuntimeError","ranges":[{"startOffset":68904,"endOffset":68962,"count":0}],"isBlockCoverage":false},{"functionName":"_AssertionError","ranges":[{"startOffset":68985,"endOffset":69046,"count":0}],"isBlockCoverage":false},{"functionName":"JsLinkedHashMap","ranges":[{"startOffset":69069,"endOffset":69306,"count":1}],"isBlockCoverage":true},{"functionName":"JsLinkedHashMap_values_closure","ranges":[{"startOffset":69344,"endOffset":69418,"count":0}],"isBlockCoverage":false},{"functionName":"JsLinkedHashMap_addAll_closure","ranges":[{"startOffset":69456,"endOffset":69530,"count":0}],"isBlockCoverage":false},{"functionName":"LinkedHashMapCell","ranges":[{"startOffset":69555,"endOffset":69713,"count":1}],"isBlockCoverage":true},{"functionName":"LinkedHashMapKeyIterable","ranges":[{"startOffset":69745,"endOffset":69849,"count":1}],"isBlockCoverage":true},{"functionName":"LinkedHashMapKeyIterator","ranges":[{"startOffset":69881,"endOffset":70079,"count":1}],"isBlockCoverage":true},{"functionName":"initHooks_closure","ranges":[{"startOffset":70104,"endOffset":70166,"count":1}],"isBlockCoverage":true},{"functionName":"initHooks_closure0","ranges":[{"startOffset":70192,"endOffset":70262,"count":1}],"isBlockCoverage":true},{"functionName":"initHooks_closure1","ranges":[{"startOffset":70288,"endOffset":70360,"count":1}],"isBlockCoverage":true},{"functionName":"JSSyntaxRegExp","ranges":[{"startOffset":70382,"endOffset":70553,"count":0}],"isBlockCoverage":false},{"functionName":"_MatchImplementation","ranges":[{"startOffset":70581,"endOffset":70646,"count":0}],"isBlockCoverage":false},{"functionName":"_AllMatchesIterable","ranges":[{"startOffset":70673,"endOffset":70803,"count":0}],"isBlockCoverage":false},{"functionName":"_AllMatchesIterator","ranges":[{"startOffset":70830,"endOffset":71016,"count":0}],"isBlockCoverage":false},{"functionName":"StringMatch","ranges":[{"startOffset":71035,"endOffset":71119,"count":0}],"isBlockCoverage":false},{"functionName":"_StringAllMatchesIterable","ranges":[{"startOffset":71152,"endOffset":71292,"count":0}],"isBlockCoverage":false},{"functionName":"_StringAllMatchesIterator","ranges":[{"startOffset":71325,"endOffset":71513,"count":0}],"isBlockCoverage":false},{"functionName":"_ensureNativeList","ranges":[{"startOffset":71538,"endOffset":71579,"count":0}],"isBlockCoverage":false},{"functionName":"NativeInt8List__create1","ranges":[{"startOffset":71610,"endOffset":71664,"count":0}],"isBlockCoverage":false},{"functionName":"_checkValidIndex","ranges":[{"startOffset":71688,"endOffset":71845,"count":1},{"startOffset":71782,"endOffset":71839,"count":0}],"isBlockCoverage":true},{"functionName":"_checkValidRange","ranges":[{"startOffset":71869,"endOffset":72275,"count":0}],"isBlockCoverage":false},{"functionName":"NativeByteBuffer","ranges":[{"startOffset":72299,"endOffset":72334,"count":0}],"isBlockCoverage":false},{"functionName":"NativeTypedData","ranges":[{"startOffset":72357,"endOffset":72391,"count":0}],"isBlockCoverage":false},{"functionName":"NativeByteData","ranges":[{"startOffset":72413,"endOffset":72446,"count":0}],"isBlockCoverage":false},{"functionName":"NativeTypedArray","ranges":[{"startOffset":72470,"endOffset":72505,"count":0}],"isBlockCoverage":false},{"functionName":"NativeTypedArrayOfDouble","ranges":[{"startOffset":72537,"endOffset":72580,"count":0}],"isBlockCoverage":false},{"functionName":"NativeTypedArrayOfInt","ranges":[{"startOffset":72609,"endOffset":72649,"count":0}],"isBlockCoverage":false},{"functionName":"NativeFloat32List","ranges":[{"startOffset":72674,"endOffset":72710,"count":0}],"isBlockCoverage":false},{"functionName":"NativeFloat64List","ranges":[{"startOffset":72735,"endOffset":72771,"count":0}],"isBlockCoverage":false},{"functionName":"NativeInt16List","ranges":[{"startOffset":72794,"endOffset":72828,"count":0}],"isBlockCoverage":false},{"functionName":"NativeInt32List","ranges":[{"startOffset":72851,"endOffset":72885,"count":0}],"isBlockCoverage":false},{"functionName":"NativeInt8List","ranges":[{"startOffset":72907,"endOffset":72940,"count":0}],"isBlockCoverage":false},{"functionName":"NativeUint16List","ranges":[{"startOffset":72964,"endOffset":72999,"count":0}],"isBlockCoverage":false},{"functionName":"NativeUint32List","ranges":[{"startOffset":73023,"endOffset":73058,"count":0}],"isBlockCoverage":false},{"functionName":"NativeUint8ClampedList","ranges":[{"startOffset":73088,"endOffset":73129,"count":0}],"isBlockCoverage":false},{"functionName":"NativeUint8List","ranges":[{"startOffset":73152,"endOffset":73186,"count":0}],"isBlockCoverage":false},{"functionName":"_NativeTypedArrayOfDouble_NativeTypedArray_ListMixin","ranges":[{"startOffset":73246,"endOffset":73317,"count":0}],"isBlockCoverage":false},{"functionName":"_NativeTypedArrayOfDouble_NativeTypedArray_ListMixin_FixedLengthListMixin","ranges":[{"startOffset":73398,"endOffset":73490,"count":0}],"isBlockCoverage":false},{"functionName":"_NativeTypedArrayOfInt_NativeTypedArray_ListMixin","ranges":[{"startOffset":73547,"endOffset":73615,"count":0}],"isBlockCoverage":false},{"functionName":"_NativeTypedArrayOfInt_NativeTypedArray_ListMixin_FixedLengthListMixin","ranges":[{"startOffset":73693,"endOffset":73782,"count":0}],"isBlockCoverage":false},{"functionName":"Rti__getFutureFromFutureOr","ranges":[{"startOffset":73816,"endOffset":74142,"count":0}],"isBlockCoverage":false},{"functionName":"Rti__isUnionOfFunctionType","ranges":[{"startOffset":74176,"endOffset":74375,"count":1}],"isBlockCoverage":true},{"functionName":"Rti__getInterfaceTypeArguments","ranges":[{"startOffset":74413,"endOffset":74497,"count":1}],"isBlockCoverage":true},{"functionName":"Rti__getBindingArguments","ranges":[{"startOffset":74529,"endOffset":74614,"count":1}],"isBlockCoverage":true},{"functionName":"Rti__getFunctionParameters","ranges":[{"startOffset":74648,"endOffset":74733,"count":1}],"isBlockCoverage":true},{"functionName":"Rti__getGenericFunctionBounds","ranges":[{"startOffset":74770,"endOffset":74855,"count":1}],"isBlockCoverage":true},{"functionName":"Rti__getCanonicalRecipe","ranges":[{"startOffset":74886,"endOffset":75056,"count":0}],"isBlockCoverage":false},{"functionName":"findType","ranges":[{"startOffset":75072,"endOffset":75154,"count":1}],"isBlockCoverage":true},{"functionName":"instantiatedGenericFunctionType","ranges":[{"startOffset":75193,"endOffset":76127,"count":0}],"isBlockCoverage":false},{"functionName":"_instantiate","ranges":[{"startOffset":76147,"endOffset":80529,"count":0}],"isBlockCoverage":false},{"functionName":"_instantiateArray","ranges":[{"startOffset":80554,"endOffset":81024,"count":0}],"isBlockCoverage":false},{"functionName":"_instantiateNamed","ranges":[{"startOffset":81049,"endOffset":81640,"count":0}],"isBlockCoverage":false},{"functionName":"_instantiateFunctionParameters","ranges":[{"startOffset":81678,"endOffset":82753,"count":0}],"isBlockCoverage":false},{"functionName":"closureFunctionType","ranges":[{"startOffset":82780,"endOffset":83037,"count":1},{"startOffset":83012,"endOffset":83036,"count":0}],"isBlockCoverage":true},{"functionName":"instanceOrFunctionType","ranges":[{"startOffset":83067,"endOffset":83352,"count":1}],"isBlockCoverage":true},{"functionName":"instanceType","ranges":[{"startOffset":83372,"endOffset":83710,"count":1},{"startOffset":83505,"endOffset":83545,"count":0}],"isBlockCoverage":true},{"functionName":"_arrayInstanceType","ranges":[{"startOffset":83736,"endOffset":83981,"count":1},{"startOffset":83939,"endOffset":83957,"count":0}],"isBlockCoverage":true},{"functionName":"_instanceType","ranges":[{"startOffset":84002,"endOffset":84127,"count":1},{"startOffset":84080,"endOffset":84120,"count":0}],"isBlockCoverage":true},{"functionName":"_instanceTypeFromConstructor","ranges":[{"startOffset":84163,"endOffset":84394,"count":1}],"isBlockCoverage":true},{"functionName":"_instanceTypeFromConstructorMiss","ranges":[{"startOffset":84434,"endOffset":84736,"count":1},{"startOffset":84532,"endOffset":84574,"count":0}],"isBlockCoverage":true},{"functionName":"getTypeFromTypesTable","ranges":[{"startOffset":84765,"endOffset":85054,"count":1}],"isBlockCoverage":true},{"functionName":"createRuntimeType","ranges":[{"startOffset":85079,"endOffset":85242,"count":1},{"startOffset":85168,"endOffset":85180,"count":0}],"isBlockCoverage":true},{"functionName":"typeLiteral","ranges":[{"startOffset":85261,"endOffset":85364,"count":1}],"isBlockCoverage":true},{"functionName":"_installSpecializedIsTest","ranges":[{"startOffset":85397,"endOffset":86499,"count":1},{"startOffset":85764,"endOffset":85806,"count":0},{"startOffset":85923,"endOffset":85939,"count":0}],"isBlockCoverage":true},{"functionName":"_generalIsTestImplementation","ranges":[{"startOffset":86535,"endOffset":86701,"count":1}],"isBlockCoverage":true},{"functionName":"_isTestViaProperty","ranges":[{"startOffset":86727,"endOffset":86913,"count":1}],"isBlockCoverage":true},{"functionName":"_generalAsCheckImplementation","ranges":[{"startOffset":86950,"endOffset":87273,"count":1},{"startOffset":87022,"endOffset":87036,"count":0},{"startOffset":87112,"endOffset":87272,"count":0}],"isBlockCoverage":true},{"functionName":"_generalTypeCheckImplementation","ranges":[{"startOffset":87312,"endOffset":87635,"count":1},{"startOffset":87474,"endOffset":87634,"count":0}],"isBlockCoverage":true},{"functionName":"checkTypeBound","ranges":[{"startOffset":87657,"endOffset":88102,"count":0}],"isBlockCoverage":false},{"functionName":"_Error_compose","ranges":[{"startOffset":88124,"endOffset":88496,"count":0}],"isBlockCoverage":false},{"functionName":"_CastError$fromMessage","ranges":[{"startOffset":88526,"endOffset":88607,"count":0}],"isBlockCoverage":false},{"functionName":"_CastError__CastError$forType","ranges":[{"startOffset":88644,"endOffset":88759,"count":0}],"isBlockCoverage":false},{"functionName":"_TypeError$fromMessage","ranges":[{"startOffset":88789,"endOffset":88870,"count":0}],"isBlockCoverage":false},{"functionName":"_TypeError__TypeError$forType","ranges":[{"startOffset":88907,"endOffset":89022,"count":0}],"isBlockCoverage":false},{"functionName":"_isTop","ranges":[{"startOffset":89036,"endOffset":89079,"count":1}],"isBlockCoverage":true},{"functionName":"_asTop","ranges":[{"startOffset":89093,"endOffset":89138,"count":1}],"isBlockCoverage":true},{"functionName":"_isBool","ranges":[{"startOffset":89153,"endOffset":89227,"count":1}],"isBlockCoverage":true},{"functionName":"_asBoolNullable","ranges":[{"startOffset":89250,"endOffset":89471,"count":1},{"startOffset":89387,"endOffset":89470,"count":0}],"isBlockCoverage":true},{"functionName":"_checkBoolNullable","ranges":[{"startOffset":89497,"endOffset":89718,"count":1},{"startOffset":89634,"endOffset":89717,"count":0}],"isBlockCoverage":true},{"functionName":"_asDoubleNullable","ranges":[{"startOffset":89743,"endOffset":89956,"count":0}],"isBlockCoverage":false},{"functionName":"_checkDoubleNullable","ranges":[{"startOffset":89984,"endOffset":90197,"count":0}],"isBlockCoverage":false},{"functionName":"_isInt","ranges":[{"startOffset":90211,"endOffset":90308,"count":1}],"isBlockCoverage":true},{"functionName":"_asIntNullable","ranges":[{"startOffset":90330,"endOffset":90573,"count":1},{"startOffset":90490,"endOffset":90572,"count":0}],"isBlockCoverage":true},{"functionName":"_checkIntNullable","ranges":[{"startOffset":90598,"endOffset":90841,"count":1},{"startOffset":90709,"endOffset":90840,"count":0}],"isBlockCoverage":true},{"functionName":"_isNum","ranges":[{"startOffset":90855,"endOffset":90919,"count":1}],"isBlockCoverage":true},{"functionName":"_asNumNullable","ranges":[{"startOffset":90941,"endOffset":91151,"count":1},{"startOffset":91019,"endOffset":91150,"count":0}],"isBlockCoverage":true},{"functionName":"_checkNumNullable","ranges":[{"startOffset":91176,"endOffset":91386,"count":0}],"isBlockCoverage":false},{"functionName":"_isString","ranges":[{"startOffset":91403,"endOffset":91467,"count":1}],"isBlockCoverage":true},{"functionName":"_asStringNullable","ranges":[{"startOffset":91492,"endOffset":91705,"count":1},{"startOffset":91619,"endOffset":91704,"count":0}],"isBlockCoverage":true},{"functionName":"_checkStringNullable","ranges":[{"startOffset":91733,"endOffset":91946,"count":1},{"startOffset":91811,"endOffset":91945,"count":0}],"isBlockCoverage":true},{"functionName":"_rtiArrayToString","ranges":[{"startOffset":91971,"endOffset":92203,"count":0}],"isBlockCoverage":false},{"functionName":"_functionRtiToString","ranges":[{"startOffset":92231,"endOffset":95348,"count":0}],"isBlockCoverage":false},{"functionName":"_rtiToString","ranges":[{"startOffset":95368,"endOffset":96985,"count":0}],"isBlockCoverage":false},{"functionName":"_unminifyOrTag","ranges":[{"startOffset":97007,"endOffset":97195,"count":0}],"isBlockCoverage":false},{"functionName":"_Universe_findRule","ranges":[{"startOffset":97221,"endOffset":97393,"count":1},{"startOffset":97343,"endOffset":97368,"count":0}],"isBlockCoverage":true},{"functionName":"_Universe_findErasedType","ranges":[{"startOffset":97425,"endOffset":98058,"count":1},{"startOffset":97640,"endOffset":98052,"count":0}],"isBlockCoverage":true},{"functionName":"_Universe_addRules","ranges":[{"startOffset":98084,"endOffset":98173,"count":1}],"isBlockCoverage":true},{"functionName":"_Universe_addErasedTypes","ranges":[{"startOffset":98205,"endOffset":98294,"count":1}],"isBlockCoverage":true},{"functionName":"_Universe_eval","ranges":[{"startOffset":98316,"endOffset":98586,"count":1}],"isBlockCoverage":true},{"functionName":"_Universe_evalInEnvironment","ranges":[{"startOffset":98621,"endOffset":99004,"count":1}],"isBlockCoverage":true},{"functionName":"_Universe_bind","ranges":[{"startOffset":99026,"endOffset":99702,"count":1},{"startOffset":99328,"endOffset":99370,"count":0},{"startOffset":99578,"endOffset":99620,"count":0}],"isBlockCoverage":true},{"functionName":"_Universe__parseRecipe","ranges":[{"startOffset":99732,"endOffset":99879,"count":1}],"isBlockCoverage":true},{"functionName":"_Universe__finishRti","ranges":[{"startOffset":99907,"endOffset":100268,"count":1},{"startOffset":100023,"endOffset":100065,"count":0}],"isBlockCoverage":true},{"functionName":"_Universe__lookupTerminalRti","ranges":[{"startOffset":100304,"endOffset":100628,"count":1}],"isBlockCoverage":true},{"functionName":"_Universe__lookupUnaryRti","ranges":[{"startOffset":100661,"endOffset":101026,"count":1}],"isBlockCoverage":true},{"functionName":"_Universe__lookupGenericFunctionParameterRti","ranges":[{"startOffset":101078,"endOffset":101456,"count":1}],"isBlockCoverage":true},{"functionName":"_Universe__canonicalRecipeJoin","ranges":[{"startOffset":101494,"endOffset":101845,"count":1},{"startOffset":101750,"endOffset":101792,"count":0}],"isBlockCoverage":true},{"functionName":"_Universe__canonicalRecipeJoinNamed","ranges":[{"startOffset":101888,"endOffset":102332,"count":0}],"isBlockCoverage":false},{"functionName":"_Universe__lookupInterfaceRti","ranges":[{"startOffset":102369,"endOffset":102971,"count":1}],"isBlockCoverage":true},{"functionName":"_Universe__lookupBindingRti","ranges":[{"startOffset":103006,"endOffset":103877,"count":1},{"startOffset":103457,"endOffset":103499,"count":0}],"isBlockCoverage":true},{"functionName":"_Universe__lookupFunctionRti","ranges":[{"startOffset":103913,"endOffset":105417,"count":1},{"startOffset":104216,"endOffset":104258,"count":0},{"startOffset":104965,"endOffset":105084,"count":0}],"isBlockCoverage":true},{"functionName":"_Universe__lookupGenericFunctionRti","ranges":[{"startOffset":105460,"endOffset":106050,"count":1},{"startOffset":105635,"endOffset":105677,"count":0},{"startOffset":105817,"endOffset":105830,"count":0}],"isBlockCoverage":true},{"functionName":"_Parser_create","ranges":[{"startOffset":106072,"endOffset":106187,"count":1}],"isBlockCoverage":true},{"functionName":"_Parser_parse","ranges":[{"startOffset":106208,"endOffset":111999,"count":1},{"startOffset":106817,"endOffset":106880,"count":0},{"startOffset":106989,"endOffset":107018,"count":0},{"startOffset":107031,"endOffset":107137,"count":0},{"startOffset":107388,"endOffset":107493,"count":0},{"startOffset":108690,"endOffset":108784,"count":0},{"startOffset":108797,"endOffset":109122,"count":0},{"startOffset":109135,"endOffset":109460,"count":0},{"startOffset":109682,"endOffset":109703,"count":0},{"startOffset":110356,"endOffset":110440,"count":0},{"startOffset":111456,"endOffset":111561,"count":0},{"startOffset":111574,"endOffset":111816,"count":0},{"startOffset":111829,"endOffset":111880,"count":0}],"isBlockCoverage":true},{"functionName":"_Parser_handleDigit","ranges":[{"startOffset":112026,"endOffset":112337,"count":1},{"startOffset":112242,"endOffset":112290,"count":0}],"isBlockCoverage":true},{"functionName":"_Parser_handleIdentifier","ranges":[{"startOffset":112369,"endOffset":113633,"count":1},{"startOffset":112644,"endOffset":112650,"count":0},{"startOffset":113401,"endOffset":113494,"count":0}],"isBlockCoverage":true},{"functionName":"_Parser_handleExtendedOperations","ranges":[{"startOffset":113673,"endOffset":114066,"count":0}],"isBlockCoverage":false},{"functionName":"_Parser_toType","ranges":[{"startOffset":114088,"endOffset":114385,"count":1}],"isBlockCoverage":true},{"functionName":"_Parser_toTypes","ranges":[{"startOffset":114408,"endOffset":114605,"count":1}],"isBlockCoverage":true},{"functionName":"_Parser_toTypesNamed","ranges":[{"startOffset":114633,"endOffset":114876,"count":0}],"isBlockCoverage":false},{"functionName":"_Parser_indexToType","ranges":[{"startOffset":114903,"endOffset":115904,"count":1},{"startOffset":115075,"endOffset":115162,"count":0},{"startOffset":115556,"endOffset":115639,"count":0},{"startOffset":115790,"endOffset":115903,"count":0}],"isBlockCoverage":true},{"functionName":"_isSubtype","ranges":[{"startOffset":115922,"endOffset":118887,"count":1},{"startOffset":116182,"endOffset":116194,"count":0},{"startOffset":116235,"endOffset":116248,"count":0},{"startOffset":116365,"endOffset":116512,"count":0},{"startOffset":116574,"endOffset":116694,"count":0},{"startOffset":116718,"endOffset":116850,"count":0},{"startOffset":116874,"endOffset":117119,"count":0},{"startOffset":117143,"endOffset":117280,"count":0},{"startOffset":117431,"endOffset":117542,"count":0},{"startOffset":117566,"endOffset":117698,"count":0},{"startOffset":117735,"endOffset":117748,"count":0},{"startOffset":117932,"endOffset":117944,"count":0},{"startOffset":117981,"endOffset":117994,"count":0},{"startOffset":118164,"endOffset":118177,"count":0},{"startOffset":118216,"endOffset":118238,"count":0},{"startOffset":118278,"endOffset":118300,"count":0},{"startOffset":118576,"endOffset":118588,"count":0},{"startOffset":118615,"endOffset":118628,"count":0},{"startOffset":118768,"endOffset":118781,"count":0},{"startOffset":118861,"endOffset":118886,"count":0}],"isBlockCoverage":true},{"functionName":"_isFunctionSubtype","ranges":[{"startOffset":118913,"endOffset":121867,"count":1},{"startOffset":119567,"endOffset":119580,"count":0},{"startOffset":120006,"endOffset":120019,"count":0},{"startOffset":120482,"endOffset":120495,"count":0},{"startOffset":120682,"endOffset":120695,"count":0},{"startOffset":120916,"endOffset":120929,"count":0},{"startOffset":120992,"endOffset":121171,"count":0},{"startOffset":121437,"endOffset":121842,"count":0}],"isBlockCoverage":true},{"functionName":"_isInterfaceSubtype","ranges":[{"startOffset":121894,"endOffset":123121,"count":1},{"startOffset":122175,"endOffset":122584,"count":0},{"startOffset":122669,"endOffset":122682,"count":0},{"startOffset":123083,"endOffset":123096,"count":0}],"isBlockCoverage":true},{"functionName":"typeEqual","ranges":[{"startOffset":123138,"endOffset":124947,"count":1},{"startOffset":123229,"endOffset":123426,"count":0},{"startOffset":123435,"endOffset":123442,"count":0},{"startOffset":123451,"endOffset":123518,"count":0},{"startOffset":123527,"endOffset":123808,"count":0},{"startOffset":123817,"endOffset":124067,"count":0},{"startOffset":124076,"endOffset":124623,"count":0},{"startOffset":124632,"endOffset":124892,"count":0},{"startOffset":124901,"endOffset":124933,"count":0}],"isBlockCoverage":true},{"functionName":"typesEqual","ranges":[{"startOffset":124965,"endOffset":125245,"count":1},{"startOffset":125093,"endOffset":125106,"count":0},{"startOffset":125207,"endOffset":125220,"count":0}],"isBlockCoverage":true},{"functionName":"namedTypesEqual","ranges":[{"startOffset":125268,"endOffset":125761,"count":0}],"isBlockCoverage":false},{"functionName":"isLegacyTopType","ranges":[{"startOffset":125784,"endOffset":125838,"count":1}],"isBlockCoverage":true},{"functionName":"isTopType","ranges":[{"startOffset":125855,"endOffset":126428,"count":1},{"startOffset":126261,"endOffset":126341,"count":0}],"isBlockCoverage":true},{"functionName":"_Utils_objectAssign","ranges":[{"startOffset":126455,"endOffset":126663,"count":1}],"isBlockCoverage":true},{"functionName":"Rti","ranges":[{"startOffset":126674,"endOffset":126970,"count":1}],"isBlockCoverage":true},{"functionName":"_FunctionParameters","ranges":[{"startOffset":126997,"endOffset":127123,"count":1}],"isBlockCoverage":true},{"functionName":"_Type","ranges":[{"startOffset":127136,"endOffset":127213,"count":1}],"isBlockCoverage":true},{"functionName":"_Error","ranges":[{"startOffset":127227,"endOffset":127252,"count":0}],"isBlockCoverage":false},{"functionName":"_CastError","ranges":[{"startOffset":127270,"endOffset":127327,"count":0}],"isBlockCoverage":false},{"functionName":"_TypeError","ranges":[{"startOffset":127345,"endOffset":127402,"count":0}],"isBlockCoverage":false},{"functionName":"extractKeys","ranges":[{"startOffset":127421,"endOffset":127537,"count":0}],"isBlockCoverage":false},{"functionName":"unmangleGlobalNameIfPreservedAnyways","ranges":[{"startOffset":127581,"endOffset":127649,"count":0}],"isBlockCoverage":false},{"functionName":"printString","ranges":[{"startOffset":127668,"endOffset":128115,"count":0}],"isBlockCoverage":false},{"functionName":"makeDispatchRecord","ranges":[{"startOffset":128153,"endOffset":128286,"count":1}],"isBlockCoverage":true},{"functionName":"getNativeInterceptor","ranges":[{"startOffset":128314,"endOffset":129994,"count":1},{"startOffset":128762,"endOffset":128776,"count":0},{"startOffset":128891,"endOffset":129041,"count":0},{"startOffset":129049,"endOffset":129138,"count":0},{"startOffset":129139,"endOffset":129190,"count":0},{"startOffset":129231,"endOffset":129993,"count":0}],"isBlockCoverage":true},{"functionName":"JSArray_JSArray$fixed","ranges":[{"startOffset":130023,"endOffset":130380,"count":0}],"isBlockCoverage":false},{"functionName":"JSArray_JSArray$markFixed","ranges":[{"startOffset":130413,"endOffset":130543,"count":0}],"isBlockCoverage":false},{"functionName":"JSArray_markFixedList","ranges":[{"startOffset":130572,"endOffset":130646,"count":1}],"isBlockCoverage":true},{"functionName":"JSString__isWhitespace","ranges":[{"startOffset":130676,"endOffset":131431,"count":0}],"isBlockCoverage":false},{"functionName":"JSString__skipLeadingWhitespace","ranges":[{"startOffset":131470,"endOffset":131788,"count":0}],"isBlockCoverage":false},{"functionName":"JSString__skipTrailingWhitespace","ranges":[{"startOffset":131828,"endOffset":132157,"count":0}],"isBlockCoverage":false},{"functionName":"getInterceptor$","ranges":[{"startOffset":132180,"endOffset":132943,"count":1},{"startOffset":132240,"endOffset":132368,"count":0},{"startOffset":132699,"endOffset":132825,"count":0}],"isBlockCoverage":true},{"functionName":"getInterceptor$ansx","ranges":[{"startOffset":132970,"endOffset":133556,"count":0}],"isBlockCoverage":false},{"functionName":"getInterceptor$asx","ranges":[{"startOffset":133582,"endOffset":134092,"count":1},{"startOffset":133715,"endOffset":133731,"count":0},{"startOffset":133848,"endOffset":133974,"count":0},{"startOffset":134039,"endOffset":134091,"count":0}],"isBlockCoverage":true},{"functionName":"getInterceptor$ax","ranges":[{"startOffset":134117,"endOffset":134551,"count":1},{"startOffset":134174,"endOffset":134190,"count":0},{"startOffset":134307,"endOffset":134433,"count":0}],"isBlockCoverage":true},{"functionName":"getInterceptor$n","ranges":[{"startOffset":134575,"endOffset":134848,"count":0}],"isBlockCoverage":false},{"functionName":"getInterceptor$s","ranges":[{"startOffset":134872,"endOffset":135145,"count":1},{"startOffset":134968,"endOffset":135144,"count":0}],"isBlockCoverage":true},{"functionName":"getInterceptor$x","ranges":[{"startOffset":135169,"endOffset":135526,"count":1},{"startOffset":135226,"endOffset":135242,"count":0},{"startOffset":135282,"endOffset":135408,"count":0},{"startOffset":135457,"endOffset":135473,"count":0}],"isBlockCoverage":true},{"functionName":"get$first$ax","ranges":[{"startOffset":135546,"endOffset":135636,"count":1}],"isBlockCoverage":true},{"functionName":"get$hashCode$","ranges":[{"startOffset":135657,"endOffset":135748,"count":1}],"isBlockCoverage":true},{"functionName":"get$isEmpty$asx","ranges":[{"startOffset":135771,"endOffset":135864,"count":1}],"isBlockCoverage":true},{"functionName":"get$iterator$ax","ranges":[{"startOffset":135887,"endOffset":135980,"count":1}],"isBlockCoverage":true},{"functionName":"get$last$ax","ranges":[{"startOffset":135999,"endOffset":136088,"count":0}],"isBlockCoverage":false},{"functionName":"get$length$asx","ranges":[{"startOffset":136110,"endOffset":136202,"count":1}],"isBlockCoverage":true},{"functionName":"get$message$x","ranges":[{"startOffset":136223,"endOffset":136314,"count":0}],"isBlockCoverage":false},{"functionName":"get$runtimeType$","ranges":[{"startOffset":136338,"endOffset":136432,"count":0}],"isBlockCoverage":false},{"functionName":"get$startsWith$s","ranges":[{"startOffset":136456,"endOffset":136550,"count":0}],"isBlockCoverage":false},{"functionName":"$add$ansx","ranges":[{"startOffset":136567,"endOffset":136756,"count":0}],"isBlockCoverage":false},{"functionName":"$eq$","ranges":[{"startOffset":136768,"endOffset":136998,"count":1}],"isBlockCoverage":true},{"functionName":"$index$asx","ranges":[{"startOffset":137016,"endOffset":137374,"count":0}],"isBlockCoverage":false},{"functionName":"$indexSet$ax","ranges":[{"startOffset":137394,"endOffset":137500,"count":1}],"isBlockCoverage":true},{"functionName":"_codeUnitAt$1$s","ranges":[{"startOffset":137523,"endOffset":137624,"count":1}],"isBlockCoverage":true},{"functionName":"_removeEventListener$3$x","ranges":[{"startOffset":137656,"endOffset":137782,"count":1}],"isBlockCoverage":true},{"functionName":"addEventListener$3$x","ranges":[{"startOffset":137810,"endOffset":137932,"count":1}],"isBlockCoverage":true},{"functionName":"allMatches$1$s","ranges":[{"startOffset":137954,"endOffset":138054,"count":0}],"isBlockCoverage":false},{"functionName":"allMatches$2$s","ranges":[{"startOffset":138076,"endOffset":138184,"count":0}],"isBlockCoverage":false},{"functionName":"codeUnitAt$1$s","ranges":[{"startOffset":138206,"endOffset":138306,"count":0}],"isBlockCoverage":false},{"functionName":"contains$1$asx","ranges":[{"startOffset":138328,"endOffset":138428,"count":0}],"isBlockCoverage":false},{"functionName":"elementAt$1$ax","ranges":[{"startOffset":138450,"endOffset":138550,"count":1}],"isBlockCoverage":true},{"functionName":"endsWith$1$s","ranges":[{"startOffset":138570,"endOffset":138668,"count":0}],"isBlockCoverage":false},{"functionName":"fillRange$3$ax","ranges":[{"startOffset":138690,"endOffset":138806,"count":1}],"isBlockCoverage":true},{"functionName":"map$1$1$ax","ranges":[{"startOffset":138824,"endOffset":138930,"count":0}],"isBlockCoverage":false},{"functionName":"matchAsPrefix$2$s","ranges":[{"startOffset":138955,"endOffset":139066,"count":0}],"isBlockCoverage":false},{"functionName":"padRight$1$s","ranges":[{"startOffset":139086,"endOffset":139184,"count":0}],"isBlockCoverage":false},{"functionName":"replaceRange$3$asx","ranges":[{"startOffset":139210,"endOffset":139330,"count":0}],"isBlockCoverage":false},{"functionName":"startsWith$1$s","ranges":[{"startOffset":139352,"endOffset":139452,"count":0}],"isBlockCoverage":false},{"functionName":"startsWith$2$s","ranges":[{"startOffset":139474,"endOffset":139582,"count":1}],"isBlockCoverage":true},{"functionName":"substring$1$s","ranges":[{"startOffset":139603,"endOffset":139702,"count":0}],"isBlockCoverage":false},{"functionName":"substring$2$s","ranges":[{"startOffset":139723,"endOffset":139830,"count":0}],"isBlockCoverage":false},{"functionName":"toRadixString$1$n","ranges":[{"startOffset":139855,"endOffset":139958,"count":0}],"isBlockCoverage":false},{"functionName":"toSet$0$ax","ranges":[{"startOffset":139976,"endOffset":140064,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0$","ranges":[{"startOffset":140083,"endOffset":140172,"count":0}],"isBlockCoverage":false},{"functionName":"trim$0$s","ranges":[{"startOffset":140188,"endOffset":140274,"count":0}],"isBlockCoverage":false},{"functionName":"Interceptor","ranges":[{"startOffset":140293,"endOffset":140323,"count":0}],"isBlockCoverage":false},{"functionName":"JSBool","ranges":[{"startOffset":140337,"endOffset":140362,"count":0}],"isBlockCoverage":false},{"functionName":"JSNull","ranges":[{"startOffset":140376,"endOffset":140401,"count":0}],"isBlockCoverage":false},{"functionName":"JSObject","ranges":[{"startOffset":140417,"endOffset":140444,"count":0}],"isBlockCoverage":false},{"functionName":"JavaScriptObject","ranges":[{"startOffset":140468,"endOffset":140503,"count":0}],"isBlockCoverage":false},{"functionName":"PlainJavaScriptObject","ranges":[{"startOffset":140532,"endOffset":140572,"count":0}],"isBlockCoverage":false},{"functionName":"UnknownJavaScriptObject","ranges":[{"startOffset":140603,"endOffset":140645,"count":0}],"isBlockCoverage":false},{"functionName":"JavaScriptFunction","ranges":[{"startOffset":140671,"endOffset":140708,"count":0}],"isBlockCoverage":false},{"functionName":"JSArray","ranges":[{"startOffset":140723,"endOffset":140772,"count":0}],"isBlockCoverage":true},{"functionName":"JSUnmodifiableArray","ranges":[{"startOffset":140799,"endOffset":140860,"count":0}],"isBlockCoverage":false},{"functionName":"ArrayIterator","ranges":[{"startOffset":140881,"endOffset":141052,"count":1}],"isBlockCoverage":true},{"functionName":"JSNumber","ranges":[{"startOffset":141068,"endOffset":141095,"count":0}],"isBlockCoverage":false},{"functionName":"JSInt","ranges":[{"startOffset":141108,"endOffset":141132,"count":0}],"isBlockCoverage":false},{"functionName":"JSDouble","ranges":[{"startOffset":141148,"endOffset":141175,"count":0}],"isBlockCoverage":false},{"functionName":"JSString","ranges":[{"startOffset":141191,"endOffset":141218,"count":0}],"isBlockCoverage":true},{"functionName":"_AsyncRun__initializeScheduleImmediate","ranges":[{"startOffset":141276,"endOffset":142083,"count":1},{"startOffset":141369,"endOffset":141433,"count":0},{"startOffset":141892,"endOffset":142082,"count":0}],"isBlockCoverage":true},{"functionName":"_AsyncRun__scheduleImmediateJsOverride","ranges":[{"startOffset":142129,"endOffset":142315,"count":0}],"isBlockCoverage":false},{"functionName":"_AsyncRun__scheduleImmediateWithSetImmediate","ranges":[{"startOffset":142367,"endOffset":142554,"count":0}],"isBlockCoverage":false},{"functionName":"_AsyncRun__scheduleImmediateWithTimer","ranges":[{"startOffset":142599,"endOffset":142705,"count":0}],"isBlockCoverage":false},{"functionName":"Timer__createTimer","ranges":[{"startOffset":142731,"endOffset":142922,"count":1},{"startOffset":142886,"endOffset":142889,"count":0}],"isBlockCoverage":true},{"functionName":"_TimerImpl$","ranges":[{"startOffset":142941,"endOffset":143084,"count":1}],"isBlockCoverage":true},{"functionName":"_TimerImpl$periodic","ranges":[{"startOffset":143111,"endOffset":143264,"count":0}],"isBlockCoverage":false},{"functionName":"_makeAsyncAwaitCompleter","ranges":[{"startOffset":143296,"endOffset":143454,"count":1}],"isBlockCoverage":true},{"functionName":"_asyncStartSync","ranges":[{"startOffset":143477,"endOffset":143617,"count":1}],"isBlockCoverage":true},{"functionName":"_asyncAwait","ranges":[{"startOffset":143636,"endOffset":143720,"count":1}],"isBlockCoverage":true},{"functionName":"_asyncReturn","ranges":[{"startOffset":143740,"endOffset":143811,"count":1}],"isBlockCoverage":true},{"functionName":"_asyncRethrow","ranges":[{"startOffset":143832,"endOffset":143960,"count":0}],"isBlockCoverage":false},{"functionName":"_awaitOnObject","ranges":[{"startOffset":143982,"endOffset":144686,"count":1},{"startOffset":144384,"endOffset":144441,"count":0}],"isBlockCoverage":true},{"functionName":"_wrapJsFunctionForAsync","ranges":[{"startOffset":144717,"endOffset":145220,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":144762,"endOffset":145053,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":144799,"endOffset":145044,"count":1},{"startOffset":144941,"endOffset":145034,"count":0}],"isBlockCoverage":true},{"functionName":"Future_Future","ranges":[{"startOffset":145241,"endOffset":145457,"count":1}],"isBlockCoverage":true},{"functionName":"Future_Future$microtask","ranges":[{"startOffset":145488,"endOffset":145706,"count":0}],"isBlockCoverage":false},{"functionName":"Future_Future$sync","ranges":[{"startOffset":145732,"endOffset":146777,"count":1},{"startOffset":145947,"endOffset":145961,"count":0},{"startOffset":146185,"endOffset":146771,"count":0}],"isBlockCoverage":true},{"functionName":"Future_Future$error","ranges":[{"startOffset":146804,"endOffset":147339,"count":0}],"isBlockCoverage":false},{"functionName":"Future_wait","ranges":[{"startOffset":147358,"endOffset":148702,"count":0}],"isBlockCoverage":false},{"functionName":"Future_forEach","ranges":[{"startOffset":148724,"endOffset":148925,"count":1}],"isBlockCoverage":true},{"functionName":"Future__kTrue","ranges":[{"startOffset":148946,"endOffset":148984,"count":0}],"isBlockCoverage":false},{"functionName":"Future_doWhile","ranges":[{"startOffset":149006,"endOffset":149408,"count":1}],"isBlockCoverage":true},{"functionName":"_completeWithErrorCallback","ranges":[{"startOffset":149442,"endOffset":149802,"count":0}],"isBlockCoverage":false},{"functionName":"_Future$zoneValue","ranges":[{"startOffset":149827,"endOffset":150023,"count":1}],"isBlockCoverage":true},{"functionName":"_Future__chainForeignFuture","ranges":[{"startOffset":150058,"endOffset":150653,"count":0}],"isBlockCoverage":false},{"functionName":"_Future__chainCoreFuture","ranges":[{"startOffset":150685,"endOffset":151411,"count":1},{"startOffset":150866,"endOffset":150958,"count":0},{"startOffset":150978,"endOffset":151135,"count":0}],"isBlockCoverage":true},{"functionName":"_Future__propagateToListeners","ranges":[{"startOffset":151448,"endOffset":155442,"count":1},{"startOffset":151952,"endOffset":152109,"count":0},{"startOffset":152235,"endOffset":152357,"count":0},{"startOffset":152661,"endOffset":152687,"count":0},{"startOffset":152790,"endOffset":152910,"count":0},{"startOffset":152958,"endOffset":153223,"count":0},{"startOffset":153845,"endOffset":153967,"count":0},{"startOffset":154108,"endOffset":154697,"count":0},{"startOffset":155231,"endOffset":155375,"count":0}],"isBlockCoverage":true},{"functionName":"_registerErrorHandler","ranges":[{"startOffset":155471,"endOffset":156025,"count":0}],"isBlockCoverage":false},{"functionName":"_microtaskLoop","ranges":[{"startOffset":156047,"endOffset":156321,"count":1}],"isBlockCoverage":true},{"functionName":"_startMicrotaskLoop","ranges":[{"startOffset":156348,"endOffset":156683,"count":1},{"startOffset":156577,"endOffset":156669,"count":0}],"isBlockCoverage":true},{"functionName":"_scheduleAsyncCallback","ranges":[{"startOffset":156713,"endOffset":157097,"count":1}],"isBlockCoverage":true},{"functionName":"_schedulePriorityAsyncCallback","ranges":[{"startOffset":157135,"endOffset":157721,"count":0}],"isBlockCoverage":false},{"functionName":"scheduleMicrotask","ranges":[{"startOffset":157746,"endOffset":158408,"count":1},{"startOffset":157973,"endOffset":158407,"count":0}],"isBlockCoverage":true},{"functionName":"Stream_Stream$fromFuture","ranges":[{"startOffset":158440,"endOffset":158865,"count":0}],"isBlockCoverage":false},{"functionName":"Stream_Stream$fromIterable","ranges":[{"startOffset":158899,"endOffset":159065,"count":1}],"isBlockCoverage":true},{"functionName":"StreamIterator_StreamIterator","ranges":[{"startOffset":159102,"endOffset":159288,"count":0}],"isBlockCoverage":false},{"functionName":"StreamController_StreamController","ranges":[{"startOffset":159329,"endOffset":159628,"count":1},{"startOffset":159516,"endOffset":159621,"count":0}],"isBlockCoverage":true},{"functionName":"_runGuarded","ranges":[{"startOffset":159647,"endOffset":159995,"count":1},{"startOffset":159819,"endOffset":159989,"count":0}],"isBlockCoverage":true},{"functionName":"_AddStreamState_makeErrorHandler","ranges":[{"startOffset":160035,"endOffset":160136,"count":0}],"isBlockCoverage":false},{"functionName":"_BufferingStreamSubscription$","ranges":[{"startOffset":160173,"endOffset":160505,"count":1},{"startOffset":160287,"endOffset":160290,"count":0}],"isBlockCoverage":true},{"functionName":"_nullDataHandler","ranges":[{"startOffset":160529,"endOffset":160552,"count":0}],"isBlockCoverage":false},{"functionName":"_nullErrorHandler","ranges":[{"startOffset":160577,"endOffset":160719,"count":0}],"isBlockCoverage":false},{"functionName":"_nullDoneHandler","ranges":[{"startOffset":160743,"endOffset":160761,"count":0}],"isBlockCoverage":false},{"functionName":"_runUserCode","ranges":[{"startOffset":160781,"endOffset":161431,"count":1},{"startOffset":160958,"endOffset":161425,"count":0}],"isBlockCoverage":true},{"functionName":"_cancelAndError","ranges":[{"startOffset":161454,"endOffset":161799,"count":0}],"isBlockCoverage":false},{"functionName":"_cancelAndErrorClosure","ranges":[{"startOffset":161829,"endOffset":161940,"count":1}],"isBlockCoverage":true},{"functionName":"_cancelAndValue","ranges":[{"startOffset":161963,"endOffset":162267,"count":1}],"isBlockCoverage":true},{"functionName":"Timer_Timer","ranges":[{"startOffset":162286,"endOffset":162516,"count":1},{"startOffset":162389,"endOffset":162433,"count":0}],"isBlockCoverage":true},{"functionName":"_ZoneSpecification$","ranges":[{"startOffset":162543,"endOffset":162986,"count":1}],"isBlockCoverage":true},{"functionName":"Zone__enter","ranges":[{"startOffset":163005,"endOffset":163218,"count":1}],"isBlockCoverage":true},{"functionName":"_parentDelegate","ranges":[{"startOffset":163241,"endOffset":163377,"count":1}],"isBlockCoverage":true},{"functionName":"_rootHandleUncaughtError","ranges":[{"startOffset":163409,"endOffset":163630,"count":0}],"isBlockCoverage":false},{"functionName":"_rootRun","ranges":[{"startOffset":163646,"endOffset":164117,"count":1},{"startOffset":164009,"endOffset":164010,"count":0}],"isBlockCoverage":true},{"functionName":"_rootRunUnary","ranges":[{"startOffset":164138,"endOffset":164675,"count":1},{"startOffset":164567,"endOffset":164568,"count":0}],"isBlockCoverage":true},{"functionName":"_rootRunBinary","ranges":[{"startOffset":164697,"endOffset":165297,"count":0}],"isBlockCoverage":false},{"functionName":"_rootRegisterCallback","ranges":[{"startOffset":165326,"endOffset":165413,"count":1}],"isBlockCoverage":true},{"functionName":"_rootRegisterUnaryCallback","ranges":[{"startOffset":165447,"endOffset":165567,"count":1}],"isBlockCoverage":true},{"functionName":"_rootRegisterBinaryCallback","ranges":[{"startOffset":165602,"endOffset":165740,"count":1}],"isBlockCoverage":true},{"functionName":"_rootErrorCallback","ranges":[{"startOffset":165766,"endOffset":165885,"count":0}],"isBlockCoverage":false},{"functionName":"_rootScheduleMicrotask","ranges":[{"startOffset":165915,"endOffset":166240,"count":1},{"startOffset":166160,"endOffset":166198,"count":0}],"isBlockCoverage":true},{"functionName":"_rootCreateTimer","ranges":[{"startOffset":166264,"endOffset":166506,"count":1}],"isBlockCoverage":true},{"functionName":"_rootCreatePeriodicTimer","ranges":[{"startOffset":166538,"endOffset":166935,"count":0}],"isBlockCoverage":false},{"functionName":"_rootPrint","ranges":[{"startOffset":166953,"endOffset":167048,"count":0}],"isBlockCoverage":false},{"functionName":"_printToZone","ranges":[{"startOffset":167068,"endOffset":167130,"count":0}],"isBlockCoverage":false},{"functionName":"_rootFork","ranges":[{"startOffset":167147,"endOffset":169398,"count":1}],"isBlockCoverage":true},{"functionName":"runZoned","ranges":[{"startOffset":169414,"endOffset":171320,"count":1},{"startOffset":169842,"endOffset":170101,"count":0},{"startOffset":170196,"endOffset":170336,"count":0},{"startOffset":170963,"endOffset":171319,"count":0}],"isBlockCoverage":true},{"functionName":"_runZoned","ranges":[{"startOffset":171337,"endOffset":171497,"count":1}],"isBlockCoverage":true},{"functionName":"_AsyncRun__initializeScheduleImmediate_internalCallback","ranges":[{"startOffset":171560,"endOffset":171660,"count":1}],"isBlockCoverage":true},{"functionName":"_AsyncRun__initializeScheduleImmediate_closure","ranges":[{"startOffset":171714,"endOffset":171856,"count":1}],"isBlockCoverage":true},{"functionName":"_AsyncRun__scheduleImmediateJsOverride_internalCallback","ranges":[{"startOffset":171919,"endOffset":172021,"count":0}],"isBlockCoverage":false},{"functionName":"_AsyncRun__scheduleImmediateWithSetImmediate_internalCallback","ranges":[{"startOffset":172090,"endOffset":172198,"count":0}],"isBlockCoverage":false},{"functionName":"_TimerImpl","ranges":[{"startOffset":172216,"endOffset":172319,"count":1}],"isBlockCoverage":true},{"functionName":"_TimerImpl_internalCallback","ranges":[{"startOffset":172354,"endOffset":172455,"count":1}],"isBlockCoverage":true},{"functionName":"_TimerImpl$periodic_closure","ranges":[{"startOffset":172490,"endOffset":172660,"count":0}],"isBlockCoverage":false},{"functionName":"_AsyncAwaitCompleter","ranges":[{"startOffset":172688,"endOffset":172806,"count":1}],"isBlockCoverage":true},{"functionName":"_awaitOnObject_closure","ranges":[{"startOffset":172836,"endOffset":172909,"count":1}],"isBlockCoverage":true},{"functionName":"_awaitOnObject_closure0","ranges":[{"startOffset":172940,"endOffset":173014,"count":1}],"isBlockCoverage":true},{"functionName":"_wrapJsFunctionForAsync_closure","ranges":[{"startOffset":173053,"endOffset":173133,"count":1}],"isBlockCoverage":true},{"functionName":"_BroadcastStream","ranges":[{"startOffset":173157,"endOffset":173248,"count":1}],"isBlockCoverage":true},{"functionName":"_BroadcastSubscription","ranges":[{"startOffset":173278,"endOffset":173605,"count":1}],"isBlockCoverage":true},{"functionName":"_BroadcastStreamController","ranges":[{"startOffset":173639,"endOffset":173684,"count":0}],"isBlockCoverage":false},{"functionName":"_SyncBroadcastStreamController","ranges":[{"startOffset":173722,"endOffset":173978,"count":1}],"isBlockCoverage":true},{"functionName":"_SyncBroadcastStreamController__sendData_closure","ranges":[{"startOffset":174034,"endOffset":174152,"count":0}],"isBlockCoverage":false},{"functionName":"_SyncBroadcastStreamController__sendError_closure","ranges":[{"startOffset":174209,"endOffset":174361,"count":0}],"isBlockCoverage":false},{"functionName":"_SyncBroadcastStreamController__sendDone_closure","ranges":[{"startOffset":174417,"endOffset":174509,"count":0}],"isBlockCoverage":false},{"functionName":"_AsyncBroadcastStreamController","ranges":[{"startOffset":174548,"endOffset":174805,"count":0}],"isBlockCoverage":false},{"functionName":"Future","ranges":[{"startOffset":174819,"endOffset":174844,"count":0}],"isBlockCoverage":false},{"functionName":"Future_Future_closure","ranges":[{"startOffset":174873,"endOffset":174972,"count":1}],"isBlockCoverage":true},{"functionName":"Future_Future$microtask_closure","ranges":[{"startOffset":175011,"endOffset":175120,"count":0}],"isBlockCoverage":false},{"functionName":"Future_wait_handleError","ranges":[{"startOffset":175151,"endOffset":175316,"count":0}],"isBlockCoverage":false},{"functionName":"Future_wait_closure","ranges":[{"startOffset":175343,"endOffset":175546,"count":0}],"isBlockCoverage":false},{"functionName":"Future_forEach_closure","ranges":[{"startOffset":175576,"endOffset":175673,"count":1}],"isBlockCoverage":true},{"functionName":"Future_doWhile_closure","ranges":[{"startOffset":175703,"endOffset":175830,"count":1}],"isBlockCoverage":true},{"functionName":"TimeoutException","ranges":[{"startOffset":175854,"endOffset":175946,"count":0}],"isBlockCoverage":false},{"functionName":"Completer","ranges":[{"startOffset":175963,"endOffset":175991,"count":0}],"isBlockCoverage":false},{"functionName":"_Completer","ranges":[{"startOffset":176009,"endOffset":176038,"count":0}],"isBlockCoverage":false},{"functionName":"_AsyncCompleter","ranges":[{"startOffset":176061,"endOffset":176146,"count":1}],"isBlockCoverage":true},{"functionName":"_SyncCompleter","ranges":[{"startOffset":176168,"endOffset":176252,"count":1}],"isBlockCoverage":true},{"functionName":"_FutureListener","ranges":[{"startOffset":176275,"endOffset":176487,"count":1}],"isBlockCoverage":true},{"functionName":"_Future","ranges":[{"startOffset":176502,"endOffset":176647,"count":1}],"isBlockCoverage":true},{"functionName":"_Future__addListener_closure","ranges":[{"startOffset":176683,"endOffset":176785,"count":1}],"isBlockCoverage":true},{"functionName":"_Future__prependListeners_closure","ranges":[{"startOffset":176826,"endOffset":176931,"count":0}],"isBlockCoverage":false},{"functionName":"_Future__chainForeignFuture_closure","ranges":[{"startOffset":176974,"endOffset":177054,"count":0}],"isBlockCoverage":false},{"functionName":"_Future__chainForeignFuture_closure0","ranges":[{"startOffset":177098,"endOffset":177179,"count":0}],"isBlockCoverage":false},{"functionName":"_Future__chainForeignFuture_closure1","ranges":[{"startOffset":177223,"endOffset":177350,"count":0}],"isBlockCoverage":false},{"functionName":"_Future__asyncComplete_closure","ranges":[{"startOffset":177388,"endOffset":177489,"count":1}],"isBlockCoverage":true},{"functionName":"_Future__chainFuture_closure","ranges":[{"startOffset":177525,"endOffset":177624,"count":0}],"isBlockCoverage":false},{"functionName":"_Future__asyncCompleteError_closure","ranges":[{"startOffset":177667,"endOffset":177805,"count":0}],"isBlockCoverage":false},{"functionName":"_Future__propagateToListeners_handleWhenCompleteCallback","ranges":[{"startOffset":177869,"endOffset":178066,"count":1}],"isBlockCoverage":true},{"functionName":"_Future__propagateToListeners_handleWhenCompleteCallback_closure","ranges":[{"startOffset":178138,"endOffset":178255,"count":0}],"isBlockCoverage":false},{"functionName":"_Future__propagateToListeners_handleValueCallback","ranges":[{"startOffset":178312,"endOffset":178470,"count":1}],"isBlockCoverage":true},{"functionName":"_Future__propagateToListeners_handleError","ranges":[{"startOffset":178519,"endOffset":178663,"count":0}],"isBlockCoverage":false},{"functionName":"_AsyncCallbackEntry","ranges":[{"startOffset":178690,"endOffset":178780,"count":1}],"isBlockCoverage":true},{"functionName":"Stream","ranges":[{"startOffset":178794,"endOffset":178819,"count":0}],"isBlockCoverage":false},{"functionName":"Stream_Stream$fromFuture_closure","ranges":[{"startOffset":178859,"endOffset":178963,"count":0}],"isBlockCoverage":false},{"functionName":"Stream_Stream$fromFuture_closure0","ranges":[{"startOffset":179004,"endOffset":179086,"count":0}],"isBlockCoverage":false},{"functionName":"Stream_Stream$fromIterable_closure","ranges":[{"startOffset":179128,"endOffset":179232,"count":1}],"isBlockCoverage":true},{"functionName":"Stream_pipe_closure","ranges":[{"startOffset":179259,"endOffset":179331,"count":1}],"isBlockCoverage":true},{"functionName":"Stream_length_closure","ranges":[{"startOffset":179360,"endOffset":179453,"count":0}],"isBlockCoverage":false},{"functionName":"Stream_length_closure0","ranges":[{"startOffset":179483,"endOffset":179578,"count":0}],"isBlockCoverage":false},{"functionName":"Stream_first_closure","ranges":[{"startOffset":179606,"endOffset":179726,"count":1}],"isBlockCoverage":true},{"functionName":"Stream_first_closure0","ranges":[{"startOffset":179755,"endOffset":179821,"count":1}],"isBlockCoverage":true},{"functionName":"Stream_last_closure","ranges":[{"startOffset":179848,"endOffset":179939,"count":0}],"isBlockCoverage":false},{"functionName":"Stream_last_closure0","ranges":[{"startOffset":179967,"endOffset":180060,"count":0}],"isBlockCoverage":false},{"functionName":"Stream_firstWhere_closure","ranges":[{"startOffset":180093,"endOffset":180252,"count":1}],"isBlockCoverage":true},{"functionName":"Stream_firstWhere__closure","ranges":[{"startOffset":180286,"endOffset":180382,"count":1}],"isBlockCoverage":true},{"functionName":"Stream_firstWhere__closure0","ranges":[{"startOffset":180417,"endOffset":180544,"count":1}],"isBlockCoverage":true},{"functionName":"Stream_firstWhere_closure0","ranges":[{"startOffset":180578,"endOffset":180704,"count":1}],"isBlockCoverage":true},{"functionName":"StreamSubscription","ranges":[{"startOffset":180730,"endOffset":180767,"count":0}],"isBlockCoverage":false},{"functionName":"StreamTransformerBase","ranges":[{"startOffset":180796,"endOffset":180836,"count":0}],"isBlockCoverage":false},{"functionName":"_StreamController","ranges":[{"startOffset":180861,"endOffset":180897,"count":0}],"isBlockCoverage":false},{"functionName":"_StreamController__subscribe_closure","ranges":[{"startOffset":180941,"endOffset":181021,"count":1}],"isBlockCoverage":true},{"functionName":"_StreamController__recordCancel_complete","ranges":[{"startOffset":181069,"endOffset":181153,"count":1}],"isBlockCoverage":true},{"functionName":"_SyncStreamControllerDispatch","ranges":[{"startOffset":181190,"endOffset":181238,"count":0}],"isBlockCoverage":false},{"functionName":"_AsyncStreamControllerDispatch","ranges":[{"startOffset":181276,"endOffset":181325,"count":0}],"isBlockCoverage":false},{"functionName":"_AsyncStreamController","ranges":[{"startOffset":181355,"endOffset":181616,"count":0}],"isBlockCoverage":false},{"functionName":"_SyncStreamController","ranges":[{"startOffset":181645,"endOffset":181905,"count":1}],"isBlockCoverage":true},{"functionName":"_ControllerStream","ranges":[{"startOffset":181930,"endOffset":182022,"count":1}],"isBlockCoverage":true},{"functionName":"_ControllerSubscription","ranges":[{"startOffset":182053,"endOffset":182306,"count":1}],"isBlockCoverage":true},{"functionName":"_StreamSinkWrapper","ranges":[{"startOffset":182332,"endOffset":182428,"count":1}],"isBlockCoverage":true},{"functionName":"_AddStreamState","ranges":[{"startOffset":182451,"endOffset":182485,"count":0}],"isBlockCoverage":false},{"functionName":"_AddStreamState_makeErrorHandler_closure","ranges":[{"startOffset":182533,"endOffset":182622,"count":0}],"isBlockCoverage":false},{"functionName":"_AddStreamState_cancel_closure","ranges":[{"startOffset":182660,"endOffset":182734,"count":1}],"isBlockCoverage":true},{"functionName":"_StreamControllerAddStreamState","ranges":[{"startOffset":182773,"endOffset":182957,"count":1}],"isBlockCoverage":true},{"functionName":"_BufferingStreamSubscription","ranges":[{"startOffset":182993,"endOffset":183221,"count":1}],"isBlockCoverage":true},{"functionName":"_BufferingStreamSubscription__sendError_sendError","ranges":[{"startOffset":183278,"endOffset":183430,"count":0}],"isBlockCoverage":false},{"functionName":"_BufferingStreamSubscription__sendDone_sendDone","ranges":[{"startOffset":183485,"endOffset":183576,"count":0}],"isBlockCoverage":false},{"functionName":"_StreamImpl","ranges":[{"startOffset":183595,"endOffset":183625,"count":0}],"isBlockCoverage":false},{"functionName":"_GeneratedStreamImpl","ranges":[{"startOffset":183653,"endOffset":183773,"count":1}],"isBlockCoverage":true},{"functionName":"_IterablePendingEvents","ranges":[{"startOffset":183803,"endOffset":183928,"count":1}],"isBlockCoverage":true},{"functionName":"_DelayedEvent","ranges":[{"startOffset":183949,"endOffset":183981,"count":0}],"isBlockCoverage":false},{"functionName":"_DelayedData","ranges":[{"startOffset":184001,"endOffset":184106,"count":0}],"isBlockCoverage":false},{"functionName":"_DelayedError","ranges":[{"startOffset":184127,"endOffset":184240,"count":0}],"isBlockCoverage":false},{"functionName":"_DelayedDone","ranges":[{"startOffset":184260,"endOffset":184291,"count":1}],"isBlockCoverage":true},{"functionName":"_PendingEvents","ranges":[{"startOffset":184313,"endOffset":184346,"count":0}],"isBlockCoverage":false},{"functionName":"_PendingEvents_schedule_closure","ranges":[{"startOffset":184385,"endOffset":184490,"count":1}],"isBlockCoverage":true},{"functionName":"_StreamImplEvents","ranges":[{"startOffset":184515,"endOffset":184666,"count":0}],"isBlockCoverage":false},{"functionName":"_DoneStreamSubscription","ranges":[{"startOffset":184697,"endOffset":184849,"count":0}],"isBlockCoverage":false},{"functionName":"_StreamIterator","ranges":[{"startOffset":184872,"endOffset":184929,"count":0}],"isBlockCoverage":false},{"functionName":"_EmptyStream","ranges":[{"startOffset":184949,"endOffset":185003,"count":0}],"isBlockCoverage":false},{"functionName":"_cancelAndError_closure","ranges":[{"startOffset":185034,"endOffset":185161,"count":0}],"isBlockCoverage":false},{"functionName":"_cancelAndErrorClosure_closure","ranges":[{"startOffset":185199,"endOffset":185308,"count":1}],"isBlockCoverage":true},{"functionName":"_cancelAndValue_closure","ranges":[{"startOffset":185339,"endOffset":185434,"count":1}],"isBlockCoverage":true},{"functionName":"Timer","ranges":[{"startOffset":185447,"endOffset":185471,"count":0}],"isBlockCoverage":false},{"functionName":"AsyncError","ranges":[{"startOffset":185489,"endOffset":185575,"count":0}],"isBlockCoverage":false},{"functionName":"_ZoneFunction","ranges":[{"startOffset":185596,"endOffset":185708,"count":1}],"isBlockCoverage":true},{"functionName":"ZoneSpecification","ranges":[{"startOffset":185733,"endOffset":185769,"count":0}],"isBlockCoverage":false},{"functionName":"_ZoneSpecification","ranges":[{"startOffset":185795,"endOffset":186270,"count":1}],"isBlockCoverage":true},{"functionName":"ZoneDelegate","ranges":[{"startOffset":186290,"endOffset":186321,"count":0}],"isBlockCoverage":false},{"functionName":"Zone","ranges":[{"startOffset":186333,"endOffset":186356,"count":0}],"isBlockCoverage":false},{"functionName":"_ZoneDelegate","ranges":[{"startOffset":186377,"endOffset":186446,"count":1}],"isBlockCoverage":true},{"functionName":"_Zone","ranges":[{"startOffset":186459,"endOffset":186483,"count":0}],"isBlockCoverage":false},{"functionName":"_CustomZone","ranges":[{"startOffset":186502,"endOffset":186881,"count":1}],"isBlockCoverage":true},{"functionName":"_CustomZone_bindCallback_closure","ranges":[{"startOffset":186921,"endOffset":187052,"count":1}],"isBlockCoverage":true},{"functionName":"_CustomZone_bindUnaryCallback_closure","ranges":[{"startOffset":187097,"endOffset":187264,"count":0}],"isBlockCoverage":false},{"functionName":"_CustomZone_bindCallbackGuarded_closure","ranges":[{"startOffset":187311,"endOffset":187426,"count":1}],"isBlockCoverage":true},{"functionName":"_CustomZone_bindUnaryCallbackGuarded_closure","ranges":[{"startOffset":187478,"endOffset":187621,"count":1}],"isBlockCoverage":true},{"functionName":"_rootHandleUncaughtError_closure","ranges":[{"startOffset":187661,"endOffset":187770,"count":0}],"isBlockCoverage":false},{"functionName":"_RootZone","ranges":[{"startOffset":187787,"endOffset":187815,"count":1}],"isBlockCoverage":true},{"functionName":"_RootZone_bindCallback_closure","ranges":[{"startOffset":187853,"endOffset":187973,"count":0}],"isBlockCoverage":false},{"functionName":"_RootZone_bindCallbackGuarded_closure","ranges":[{"startOffset":188018,"endOffset":188122,"count":0}],"isBlockCoverage":false},{"functionName":"_RootZone_bindUnaryCallbackGuarded_closure","ranges":[{"startOffset":188172,"endOffset":188304,"count":0}],"isBlockCoverage":false},{"functionName":"runZoned_closure","ranges":[{"startOffset":188328,"endOffset":188389,"count":1}],"isBlockCoverage":true},{"functionName":"HashMap_HashMap","ranges":[{"startOffset":188412,"endOffset":188522,"count":1}],"isBlockCoverage":true},{"functionName":"_HashMap__getTableEntry","ranges":[{"startOffset":188553,"endOffset":188656,"count":0}],"isBlockCoverage":false},{"functionName":"_HashMap__setTableEntry","ranges":[{"startOffset":188687,"endOffset":188814,"count":1},{"startOffset":188750,"endOffset":188769,"count":0}],"isBlockCoverage":true},{"functionName":"_HashMap__newHashTable","ranges":[{"startOffset":188844,"endOffset":189036,"count":1}],"isBlockCoverage":true},{"functionName":"LinkedHashMap_LinkedHashMap","ranges":[{"startOffset":189071,"endOffset":189195,"count":1}],"isBlockCoverage":true},{"functionName":"LinkedHashMap_LinkedHashMap$_literal","ranges":[{"startOffset":189239,"endOffset":189480,"count":1}],"isBlockCoverage":true},{"functionName":"LinkedHashMap_LinkedHashMap$_empty","ranges":[{"startOffset":189522,"endOffset":189646,"count":1}],"isBlockCoverage":true},{"functionName":"LinkedHashSet_LinkedHashSet","ranges":[{"startOffset":189681,"endOffset":189769,"count":1}],"isBlockCoverage":true},{"functionName":"LinkedHashSet_LinkedHashSet$_empty","ranges":[{"startOffset":189811,"endOffset":189899,"count":1}],"isBlockCoverage":true},{"functionName":"LinkedHashSet_LinkedHashSet$_literal","ranges":[{"startOffset":189943,"endOffset":190104,"count":1}],"isBlockCoverage":true},{"functionName":"_LinkedHashSet__newHashTable","ranges":[{"startOffset":190140,"endOffset":190343,"count":1}],"isBlockCoverage":true},{"functionName":"_LinkedHashSetIterator$","ranges":[{"startOffset":190374,"endOffset":190595,"count":1}],"isBlockCoverage":true},{"functionName":"HashMap_HashMap$from","ranges":[{"startOffset":190623,"endOffset":190799,"count":1}],"isBlockCoverage":true},{"functionName":"IterableBase_iterableToShortString","ranges":[{"startOffset":190841,"endOffset":191704,"count":0}],"isBlockCoverage":false},{"functionName":"IterableBase_iterableToFullString","ranges":[{"startOffset":191745,"endOffset":192506,"count":0}],"isBlockCoverage":false},{"functionName":"_isToStringVisiting","ranges":[{"startOffset":192533,"endOffset":192718,"count":0}],"isBlockCoverage":false},{"functionName":"_iterablePartsToStrings","ranges":[{"startOffset":192749,"endOffset":195409,"count":0}],"isBlockCoverage":false},{"functionName":"LinkedHashMap_LinkedHashMap$from","ranges":[{"startOffset":195449,"endOffset":195649,"count":1}],"isBlockCoverage":true},{"functionName":"LinkedHashSet_LinkedHashSet$from","ranges":[{"startOffset":195689,"endOffset":195923,"count":1}],"isBlockCoverage":true},{"functionName":"MapBase_mapToString","ranges":[{"startOffset":195950,"endOffset":196637,"count":0}],"isBlockCoverage":false},{"functionName":"ListQueue$","ranges":[{"startOffset":196655,"endOffset":196883,"count":1}],"isBlockCoverage":true},{"functionName":"_ListQueueIterator$","ranges":[{"startOffset":196910,"endOffset":197072,"count":0}],"isBlockCoverage":false},{"functionName":"_HashMap","ranges":[{"startOffset":197088,"endOffset":197255,"count":1}],"isBlockCoverage":true},{"functionName":"_IdentityHashMap","ranges":[{"startOffset":197279,"endOffset":197454,"count":1}],"isBlockCoverage":true},{"functionName":"_HashMapKeyIterable","ranges":[{"startOffset":197481,"endOffset":197568,"count":0}],"isBlockCoverage":false},{"functionName":"_HashMapKeyIterator","ranges":[{"startOffset":197595,"endOffset":197778,"count":0}],"isBlockCoverage":false},{"functionName":"_LinkedHashSet","ranges":[{"startOffset":197800,"endOffset":198048,"count":1}],"isBlockCoverage":true},{"functionName":"_LinkedHashSetCell","ranges":[{"startOffset":198074,"endOffset":198205,"count":1}],"isBlockCoverage":true},{"functionName":"_LinkedHashSetIterator","ranges":[{"startOffset":198235,"endOffset":198443,"count":1}],"isBlockCoverage":true},{"functionName":"UnmodifiableListView","ranges":[{"startOffset":198471,"endOffset":198574,"count":0}],"isBlockCoverage":false},{"functionName":"HashMap_HashMap$from_closure","ranges":[{"startOffset":198610,"endOffset":198729,"count":1}],"isBlockCoverage":true},{"functionName":"IterableBase","ranges":[{"startOffset":198749,"endOffset":198780,"count":0}],"isBlockCoverage":false},{"functionName":"LinkedHashMap_LinkedHashMap$from_closure","ranges":[{"startOffset":198828,"endOffset":198959,"count":1}],"isBlockCoverage":true},{"functionName":"ListBase","ranges":[{"startOffset":198975,"endOffset":199002,"count":0}],"isBlockCoverage":false},{"functionName":"ListMixin","ranges":[{"startOffset":199019,"endOffset":199047,"count":0}],"isBlockCoverage":false},{"functionName":"MapBase","ranges":[{"startOffset":199062,"endOffset":199088,"count":0}],"isBlockCoverage":false},{"functionName":"MapBase_mapToString_closure","ranges":[{"startOffset":199123,"endOffset":199223,"count":0}],"isBlockCoverage":false},{"functionName":"MapMixin","ranges":[{"startOffset":199239,"endOffset":199266,"count":0}],"isBlockCoverage":false},{"functionName":"_UnmodifiableMapMixin","ranges":[{"startOffset":199295,"endOffset":199335,"count":0}],"isBlockCoverage":false},{"functionName":"MapView","ranges":[{"startOffset":199350,"endOffset":199376,"count":0}],"isBlockCoverage":false},{"functionName":"UnmodifiableMapView","ranges":[{"startOffset":199403,"endOffset":199490,"count":1}],"isBlockCoverage":true},{"functionName":"ListQueue","ranges":[{"startOffset":199507,"endOffset":199650,"count":1}],"isBlockCoverage":true},{"functionName":"_ListQueueIterator","ranges":[{"startOffset":199676,"endOffset":199903,"count":0}],"isBlockCoverage":false},{"functionName":"SetMixin","ranges":[{"startOffset":199919,"endOffset":199946,"count":0}],"isBlockCoverage":false},{"functionName":"SetBase","ranges":[{"startOffset":199961,"endOffset":199987,"count":0}],"isBlockCoverage":false},{"functionName":"_SetBase","ranges":[{"startOffset":200003,"endOffset":200030,"count":0}],"isBlockCoverage":false},{"functionName":"_ListBase_Object_ListMixin","ranges":[{"startOffset":200064,"endOffset":200109,"count":0}],"isBlockCoverage":false},{"functionName":"_SetBase_Object_SetMixin","ranges":[{"startOffset":200141,"endOffset":200184,"count":0}],"isBlockCoverage":false},{"functionName":"_UnmodifiableMapView_MapView__UnmodifiableMapMixin","ranges":[{"startOffset":200242,"endOffset":200311,"count":0}],"isBlockCoverage":false},{"functionName":"_parseJson","ranges":[{"startOffset":200329,"endOffset":200797,"count":0}],"isBlockCoverage":false},{"functionName":"_convertJsonToDartLazy","ranges":[{"startOffset":200827,"endOffset":201212,"count":0}],"isBlockCoverage":false},{"functionName":"Utf8Decoder__convertIntercepted","ranges":[{"startOffset":201251,"endOffset":201457,"count":0}],"isBlockCoverage":false},{"functionName":"Utf8Decoder__convertInterceptedUint8List","ranges":[{"startOffset":201505,"endOffset":202094,"count":0}],"isBlockCoverage":false},{"functionName":"Utf8Decoder__useTextDecoderChecked","ranges":[{"startOffset":202136,"endOffset":202310,"count":0}],"isBlockCoverage":false},{"functionName":"Utf8Decoder__useTextDecoderUnchecked","ranges":[{"startOffset":202354,"endOffset":202579,"count":0}],"isBlockCoverage":false},{"functionName":"Utf8Decoder__unsafe","ranges":[{"startOffset":202606,"endOffset":202845,"count":0}],"isBlockCoverage":false},{"functionName":"Utf8Decoder__makeDecoder","ranges":[{"startOffset":202877,"endOffset":203098,"count":0}],"isBlockCoverage":false},{"functionName":"_scanOneByteCharacters","ranges":[{"startOffset":203128,"endOffset":203470,"count":0}],"isBlockCoverage":false},{"functionName":"Base64Codec__checkPadding","ranges":[{"startOffset":203503,"endOffset":204114,"count":0}],"isBlockCoverage":false},{"functionName":"_JsonMap","ranges":[{"startOffset":204130,"endOffset":204243,"count":0}],"isBlockCoverage":false},{"functionName":"_JsonMapKeyIterable","ranges":[{"startOffset":204270,"endOffset":204335,"count":0}],"isBlockCoverage":false},{"functionName":"AsciiCodec","ranges":[{"startOffset":204353,"endOffset":204382,"count":1}],"isBlockCoverage":true},{"functionName":"_UnicodeSubsetEncoder","ranges":[{"startOffset":204411,"endOffset":204451,"count":0}],"isBlockCoverage":false},{"functionName":"AsciiEncoder","ranges":[{"startOffset":204471,"endOffset":204533,"count":1}],"isBlockCoverage":true},{"functionName":"Base64Codec","ranges":[{"startOffset":204552,"endOffset":204582,"count":1}],"isBlockCoverage":true},{"functionName":"Base64Encoder","ranges":[{"startOffset":204603,"endOffset":204635,"count":1}],"isBlockCoverage":true},{"functionName":"Codec","ranges":[{"startOffset":204648,"endOffset":204672,"count":0}],"isBlockCoverage":false},{"functionName":"_FusedCodec","ranges":[{"startOffset":204691,"endOffset":204810,"count":0}],"isBlockCoverage":false},{"functionName":"Converter","ranges":[{"startOffset":204827,"endOffset":204855,"count":0}],"isBlockCoverage":false},{"functionName":"Encoding","ranges":[{"startOffset":204871,"endOffset":204898,"count":0}],"isBlockCoverage":false},{"functionName":"JsonCodec","ranges":[{"startOffset":204915,"endOffset":204943,"count":1}],"isBlockCoverage":true},{"functionName":"JsonDecoder","ranges":[{"startOffset":204962,"endOffset":205020,"count":1}],"isBlockCoverage":true},{"functionName":"Utf8Codec","ranges":[{"startOffset":205037,"endOffset":205065,"count":1}],"isBlockCoverage":true},{"functionName":"Utf8Encoder","ranges":[{"startOffset":205084,"endOffset":205114,"count":1}],"isBlockCoverage":true},{"functionName":"_Utf8Encoder","ranges":[{"startOffset":205134,"endOffset":205235,"count":0}],"isBlockCoverage":false},{"functionName":"Utf8Decoder","ranges":[{"startOffset":205254,"endOffset":205319,"count":0}],"isBlockCoverage":false},{"functionName":"_Utf8Decoder","ranges":[{"startOffset":205339,"endOffset":205550,"count":0}],"isBlockCoverage":false},{"functionName":"int_parse","ranges":[{"startOffset":205567,"endOffset":205845,"count":0}],"isBlockCoverage":false},{"functionName":"Error__objectToString","ranges":[{"startOffset":205874,"endOffset":206053,"count":0}],"isBlockCoverage":false},{"functionName":"List_List$filled","ranges":[{"startOffset":206077,"endOffset":206334,"count":0}],"isBlockCoverage":false},{"functionName":"List_List$from","ranges":[{"startOffset":206356,"endOffset":206723,"count":1}],"isBlockCoverage":true},{"functionName":"List_List$unmodifiable","ranges":[{"startOffset":206753,"endOffset":206964,"count":1}],"isBlockCoverage":true},{"functionName":"String_String$fromCharCodes","ranges":[{"startOffset":206999,"endOffset":207622,"count":0}],"isBlockCoverage":false},{"functionName":"String_String$fromCharCode","ranges":[{"startOffset":207656,"endOffset":207738,"count":0}],"isBlockCoverage":false},{"functionName":"String__stringFromIterable","ranges":[{"startOffset":207772,"endOffset":208694,"count":0}],"isBlockCoverage":false},{"functionName":"RegExp_RegExp","ranges":[{"startOffset":208715,"endOffset":208868,"count":0}],"isBlockCoverage":false},{"functionName":"StringBuffer__writeAll","ranges":[{"startOffset":208898,"endOffset":209382,"count":0}],"isBlockCoverage":false},{"functionName":"Uri_base","ranges":[{"startOffset":209398,"endOffset":209596,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__uriEncode","ranges":[{"startOffset":209619,"endOffset":210718,"count":0}],"isBlockCoverage":false},{"functionName":"StackTrace_current","ranges":[{"startOffset":210744,"endOffset":211119,"count":1},{"startOffset":210908,"endOffset":211113,"count":0}],"isBlockCoverage":true},{"functionName":"DateTime__fourDigits","ranges":[{"startOffset":211147,"endOffset":211424,"count":0}],"isBlockCoverage":false},{"functionName":"DateTime__threeDigits","ranges":[{"startOffset":211453,"endOffset":211581,"count":0}],"isBlockCoverage":false},{"functionName":"DateTime__twoDigits","ranges":[{"startOffset":211608,"endOffset":211691,"count":0}],"isBlockCoverage":false},{"functionName":"Duration$","ranges":[{"startOffset":211708,"endOffset":211891,"count":1},{"startOffset":211794,"endOffset":211821,"count":0}],"isBlockCoverage":true},{"functionName":"Error_safeToString","ranges":[{"startOffset":211917,"endOffset":212177,"count":0}],"isBlockCoverage":false},{"functionName":"AssertionError$","ranges":[{"startOffset":212200,"endOffset":212269,"count":0}],"isBlockCoverage":false},{"functionName":"ArgumentError$","ranges":[{"startOffset":212291,"endOffset":212378,"count":0}],"isBlockCoverage":false},{"functionName":"ArgumentError$value","ranges":[{"startOffset":212405,"endOffset":212507,"count":0}],"isBlockCoverage":false},{"functionName":"ArgumentError$notNull","ranges":[{"startOffset":212536,"endOffset":212633,"count":0}],"isBlockCoverage":false},{"functionName":"RangeError$","ranges":[{"startOffset":212652,"endOffset":212776,"count":0}],"isBlockCoverage":false},{"functionName":"RangeError$value","ranges":[{"startOffset":212800,"endOffset":212915,"count":0}],"isBlockCoverage":false},{"functionName":"RangeError$range","ranges":[{"startOffset":212939,"endOffset":213100,"count":0}],"isBlockCoverage":false},{"functionName":"RangeError_checkValueInInterval","ranges":[{"startOffset":213139,"endOffset":213328,"count":0}],"isBlockCoverage":false},{"functionName":"RangeError_checkValidRange","ranges":[{"startOffset":213362,"endOffset":213728,"count":1},{"startOffset":213442,"endOffset":213518,"count":0},{"startOffset":213596,"endOffset":213672,"count":0},{"startOffset":213700,"endOffset":213727,"count":0}],"isBlockCoverage":true},{"functionName":"RangeError_checkNotNegative","ranges":[{"startOffset":213763,"endOffset":213959,"count":0}],"isBlockCoverage":false},{"functionName":"IndexError$","ranges":[{"startOffset":213978,"endOffset":214221,"count":0}],"isBlockCoverage":false},{"functionName":"UnsupportedError$","ranges":[{"startOffset":214246,"endOffset":214317,"count":0}],"isBlockCoverage":false},{"functionName":"UnimplementedError$","ranges":[{"startOffset":214344,"endOffset":214417,"count":0}],"isBlockCoverage":false},{"functionName":"StateError$","ranges":[{"startOffset":214436,"endOffset":214501,"count":0}],"isBlockCoverage":false},{"functionName":"ConcurrentModificationError$","ranges":[{"startOffset":214537,"endOffset":214633,"count":0}],"isBlockCoverage":false},{"functionName":"FormatException$","ranges":[{"startOffset":214657,"endOffset":214759,"count":0}],"isBlockCoverage":false},{"functionName":"List_List$generate","ranges":[{"startOffset":214785,"endOffset":215096,"count":1}],"isBlockCoverage":true},{"functionName":"Map_castFrom","ranges":[{"startOffset":215116,"endOffset":215276,"count":1}],"isBlockCoverage":true},{"functionName":"print","ranges":[{"startOffset":215289,"endOffset":215458,"count":0}],"isBlockCoverage":false},{"functionName":"_combineSurrogatePair","ranges":[{"startOffset":215487,"endOffset":215575,"count":0}],"isBlockCoverage":false},{"functionName":"Uri_parse","ranges":[{"startOffset":215592,"endOffset":222061,"count":1},{"startOffset":216121,"endOffset":216228,"count":0},{"startOffset":216270,"endOffset":216359,"count":0},{"startOffset":216950,"endOffset":216995,"count":0},{"startOffset":217075,"endOffset":217098,"count":0},{"startOffset":217195,"endOffset":217218,"count":0},{"startOffset":217284,"endOffset":217301,"count":0},{"startOffset":217506,"endOffset":217533,"count":0},{"startOffset":217584,"endOffset":217609,"count":0},{"startOffset":217656,"endOffset":217683,"count":0},{"startOffset":217733,"endOffset":217756,"count":0},{"startOffset":217798,"endOffset":217821,"count":0},{"startOffset":217869,"endOffset":217895,"count":0},{"startOffset":217945,"endOffset":217968,"count":0},{"startOffset":218399,"endOffset":218415,"count":0},{"startOffset":218500,"endOffset":218565,"count":0},{"startOffset":218652,"endOffset":218723,"count":0},{"startOffset":218766,"endOffset":218797,"count":0},{"startOffset":218798,"endOffset":218839,"count":0},{"startOffset":218936,"endOffset":218978,"count":0},{"startOffset":218999,"endOffset":219076,"count":0},{"startOffset":219213,"endOffset":219911,"count":0},{"startOffset":219947,"endOffset":220222,"count":0},{"startOffset":220275,"endOffset":221412,"count":0},{"startOffset":221479,"endOffset":221514,"count":0},{"startOffset":221584,"endOffset":221802,"count":0},{"startOffset":221927,"endOffset":222060,"count":0}],"isBlockCoverage":true},{"functionName":"Uri_decodeComponent","ranges":[{"startOffset":222088,"endOffset":222270,"count":0}],"isBlockCoverage":false},{"functionName":"Uri__parseIPv4Address","ranges":[{"startOffset":222299,"endOffset":223833,"count":0}],"isBlockCoverage":false},{"functionName":"Uri_parseIPv6Address","ranges":[{"startOffset":223861,"endOffset":226795,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__Uri$notSimple","ranges":[{"startOffset":226822,"endOffset":228163,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__Uri","ranges":[{"startOffset":228180,"endOffset":229390,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__defaultPort","ranges":[{"startOffset":229415,"endOffset":229553,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__fail","ranges":[{"startOffset":229571,"endOffset":229678,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__Uri$file","ranges":[{"startOffset":229700,"endOffset":229830,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__checkNonWindowsPathReservedCharacters","ranges":[{"startOffset":229881,"endOffset":230033,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__checkWindowsPathReservedCharacters","ranges":[{"startOffset":230081,"endOffset":230721,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__checkWindowsDriveLetter","ranges":[{"startOffset":230758,"endOffset":231242,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__makeFileUri","ranges":[{"startOffset":231267,"endOffset":231594,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__makeWindowsFileUrl","ranges":[{"startOffset":231626,"endOffset":234062,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__makePort","ranges":[{"startOffset":234084,"endOffset":234218,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__makeHost","ranges":[{"startOffset":234240,"endOffset":235817,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__checkZoneID","ranges":[{"startOffset":235842,"endOffset":236000,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__normalizeZoneID","ranges":[{"startOffset":236029,"endOffset":238805,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__normalizeRegName","ranges":[{"startOffset":238835,"endOffset":242233,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__makeScheme","ranges":[{"startOffset":242257,"endOffset":243253,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__canonicalizeScheme","ranges":[{"startOffset":243285,"endOffset":243546,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__makeUserInfo","ranges":[{"startOffset":243572,"endOffset":243741,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__makePath","ranges":[{"startOffset":243763,"endOffset":244818,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__normalizePath","ranges":[{"startOffset":244845,"endOffset":245122,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__makeQuery","ranges":[{"startOffset":245145,"endOffset":245323,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__makeFragment","ranges":[{"startOffset":245349,"endOffset":245519,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__normalizeEscape","ranges":[{"startOffset":245548,"endOffset":246720,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__escapeChar","ranges":[{"startOffset":246744,"endOffset":248292,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__normalizeOrSubstring","ranges":[{"startOffset":248326,"endOffset":248566,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__normalize","ranges":[{"startOffset":248589,"endOffset":251311,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__mayContainDotSegments","ranges":[{"startOffset":251346,"endOffset":251505,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__removeDotSegments","ranges":[{"startOffset":251536,"endOffset":252526,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__normalizeRelativePath","ranges":[{"startOffset":252561,"endOffset":254222,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__escapeScheme","ranges":[{"startOffset":254248,"endOffset":254933,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__toWindowsFilePath","ranges":[{"startOffset":254964,"endOffset":255945,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__hexCharPairToByte","ranges":[{"startOffset":255976,"endOffset":256491,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__uriDecode","ranges":[{"startOffset":256514,"endOffset":258126,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__isAlphabeticCharacter","ranges":[{"startOffset":258161,"endOffset":258274,"count":0}],"isBlockCoverage":false},{"functionName":"UriData__writeUri","ranges":[{"startOffset":258299,"endOffset":258967,"count":0}],"isBlockCoverage":false},{"functionName":"UriData__validateMimeType","ranges":[{"startOffset":259000,"endOffset":259351,"count":0}],"isBlockCoverage":false},{"functionName":"UriData__parse","ranges":[{"startOffset":259373,"endOffset":261418,"count":0}],"isBlockCoverage":false},{"functionName":"UriData__uriEncodeBytes","ranges":[{"startOffset":261449,"endOffset":262927,"count":0}],"isBlockCoverage":false},{"functionName":"_createTables","ranges":[{"startOffset":262948,"endOffset":267507,"count":1}],"isBlockCoverage":true},{"functionName":"_scan","ranges":[{"startOffset":267520,"endOffset":268208,"count":1},{"startOffset":267816,"endOffset":267846,"count":0},{"startOffset":267957,"endOffset":267967,"count":0},{"startOffset":268012,"endOffset":268040,"count":0}],"isBlockCoverage":true},{"functionName":"bool","ranges":[{"startOffset":268220,"endOffset":268243,"count":0}],"isBlockCoverage":false},{"functionName":"DateTime","ranges":[{"startOffset":268259,"endOffset":268339,"count":0}],"isBlockCoverage":false},{"functionName":"double","ranges":[{"startOffset":268353,"endOffset":268378,"count":0}],"isBlockCoverage":false},{"functionName":"Duration","ranges":[{"startOffset":268394,"endOffset":268450,"count":1}],"isBlockCoverage":true},{"functionName":"Duration_toString_sixDigits","ranges":[{"startOffset":268485,"endOffset":268531,"count":0}],"isBlockCoverage":false},{"functionName":"Duration_toString_twoDigits","ranges":[{"startOffset":268566,"endOffset":268612,"count":0}],"isBlockCoverage":false},{"functionName":"Error","ranges":[{"startOffset":268625,"endOffset":268649,"count":0}],"isBlockCoverage":false},{"functionName":"AssertionError","ranges":[{"startOffset":268671,"endOffset":268731,"count":0}],"isBlockCoverage":false},{"functionName":"NullThrownError","ranges":[{"startOffset":268754,"endOffset":268788,"count":0}],"isBlockCoverage":false},{"functionName":"ArgumentError","ranges":[{"startOffset":268809,"endOffset":268967,"count":0}],"isBlockCoverage":false},{"functionName":"RangeError","ranges":[{"startOffset":268985,"endOffset":269186,"count":0}],"isBlockCoverage":false},{"functionName":"IndexError","ranges":[{"startOffset":269204,"endOffset":269384,"count":0}],"isBlockCoverage":false},{"functionName":"UnsupportedError","ranges":[{"startOffset":269408,"endOffset":269470,"count":0}],"isBlockCoverage":false},{"functionName":"UnimplementedError","ranges":[{"startOffset":269496,"endOffset":269560,"count":0}],"isBlockCoverage":false},{"functionName":"StateError","ranges":[{"startOffset":269578,"endOffset":269634,"count":0}],"isBlockCoverage":false},{"functionName":"ConcurrentModificationError","ranges":[{"startOffset":269669,"endOffset":269749,"count":0}],"isBlockCoverage":false},{"functionName":"OutOfMemoryError","ranges":[{"startOffset":269773,"endOffset":269808,"count":1}],"isBlockCoverage":true},{"functionName":"StackOverflowError","ranges":[{"startOffset":269834,"endOffset":269871,"count":0}],"isBlockCoverage":false},{"functionName":"CyclicInitializationError","ranges":[{"startOffset":269904,"endOffset":269980,"count":0}],"isBlockCoverage":false},{"functionName":"_Exception","ranges":[{"startOffset":269998,"endOffset":270054,"count":0}],"isBlockCoverage":false},{"functionName":"FormatException","ranges":[{"startOffset":270077,"endOffset":270194,"count":0}],"isBlockCoverage":false},{"functionName":"Expando","ranges":[{"startOffset":270209,"endOffset":270321,"count":1}],"isBlockCoverage":true},{"functionName":"Function","ranges":[{"startOffset":270337,"endOffset":270364,"count":0}],"isBlockCoverage":false},{"functionName":"int","ranges":[{"startOffset":270375,"endOffset":270397,"count":0}],"isBlockCoverage":false},{"functionName":"Iterable","ranges":[{"startOffset":270413,"endOffset":270440,"count":0}],"isBlockCoverage":false},{"functionName":"Iterator","ranges":[{"startOffset":270456,"endOffset":270483,"count":0}],"isBlockCoverage":false},{"functionName":"List","ranges":[{"startOffset":270495,"endOffset":270518,"count":0}],"isBlockCoverage":false},{"functionName":"Map","ranges":[{"startOffset":270529,"endOffset":270551,"count":0}],"isBlockCoverage":false},{"functionName":"MapEntry","ranges":[{"startOffset":270567,"endOffset":270669,"count":1}],"isBlockCoverage":true},{"functionName":"Null","ranges":[{"startOffset":270681,"endOffset":270704,"count":0}],"isBlockCoverage":false},{"functionName":"num","ranges":[{"startOffset":270715,"endOffset":270737,"count":0}],"isBlockCoverage":false},{"functionName":"Object","ranges":[{"startOffset":270751,"endOffset":270776,"count":1}],"isBlockCoverage":true},{"functionName":"Pattern","ranges":[{"startOffset":270791,"endOffset":270817,"count":0}],"isBlockCoverage":false},{"functionName":"Match","ranges":[{"startOffset":270830,"endOffset":270854,"count":0}],"isBlockCoverage":false},{"functionName":"RegExpMatch","ranges":[{"startOffset":270873,"endOffset":270903,"count":0}],"isBlockCoverage":false},{"functionName":"Set","ranges":[{"startOffset":270914,"endOffset":270936,"count":0}],"isBlockCoverage":false},{"functionName":"StackTrace","ranges":[{"startOffset":270954,"endOffset":270983,"count":0}],"isBlockCoverage":false},{"functionName":"_StringStackTrace","ranges":[{"startOffset":271008,"endOffset":271075,"count":0}],"isBlockCoverage":false},{"functionName":"Stopwatch","ranges":[{"startOffset":271092,"endOffset":271162,"count":0}],"isBlockCoverage":false},{"functionName":"String","ranges":[{"startOffset":271176,"endOffset":271201,"count":0}],"isBlockCoverage":false},{"functionName":"Runes","ranges":[{"startOffset":271214,"endOffset":271264,"count":0}],"isBlockCoverage":false},{"functionName":"RuneIterator","ranges":[{"startOffset":271284,"endOffset":271439,"count":0}],"isBlockCoverage":false},{"functionName":"StringBuffer","ranges":[{"startOffset":271459,"endOffset":271519,"count":0}],"isBlockCoverage":false},{"functionName":"Uri","ranges":[{"startOffset":271530,"endOffset":271552,"count":0}],"isBlockCoverage":false},{"functionName":"Uri__parseIPv4Address_error","ranges":[{"startOffset":271587,"endOffset":271657,"count":0}],"isBlockCoverage":false},{"functionName":"Uri_parseIPv6Address_error","ranges":[{"startOffset":271691,"endOffset":271760,"count":0}],"isBlockCoverage":false},{"functionName":"Uri_parseIPv6Address_parseHex","ranges":[{"startOffset":271797,"endOffset":271896,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri","ranges":[{"startOffset":271908,"endOffset":272185,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__Uri$notSimple_closure","ranges":[{"startOffset":272220,"endOffset":272320,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__checkNonWindowsPathReservedCharacters_closure","ranges":[{"startOffset":272379,"endOffset":272482,"count":0}],"isBlockCoverage":false},{"functionName":"_Uri__makePath_closure","ranges":[{"startOffset":272512,"endOffset":272553,"count":0}],"isBlockCoverage":false},{"functionName":"UriData","ranges":[{"startOffset":272568,"endOffset":272689,"count":0}],"isBlockCoverage":false},{"functionName":"_createTables_closure","ranges":[{"startOffset":272718,"endOffset":272758,"count":1}],"isBlockCoverage":true},{"functionName":"_createTables_build","ranges":[{"startOffset":272785,"endOffset":272849,"count":1}],"isBlockCoverage":true},{"functionName":"_createTables_setChars","ranges":[{"startOffset":272879,"endOffset":272920,"count":1}],"isBlockCoverage":true},{"functionName":"_createTables_setRange","ranges":[{"startOffset":272950,"endOffset":272991,"count":1}],"isBlockCoverage":true},{"functionName":"_SimpleUri","ranges":[{"startOffset":273009,"endOffset":273320,"count":1}],"isBlockCoverage":true},{"functionName":"_DataUri","ranges":[{"startOffset":273336,"endOffset":273617,"count":0}],"isBlockCoverage":false},{"functionName":"_StructuredClone","ranges":[{"startOffset":273641,"endOffset":273676,"count":0}],"isBlockCoverage":false},{"functionName":"_StructuredClone_walk_closure","ranges":[{"startOffset":273713,"endOffset":273814,"count":1}],"isBlockCoverage":true},{"functionName":"_StructuredClone_walk_closure0","ranges":[{"startOffset":273852,"endOffset":273954,"count":0}],"isBlockCoverage":false},{"functionName":"_AcceptStructuredClone","ranges":[{"startOffset":273984,"endOffset":274025,"count":0}],"isBlockCoverage":false},{"functionName":"_AcceptStructuredClone_walk_closure","ranges":[{"startOffset":274068,"endOffset":274175,"count":1}],"isBlockCoverage":true},{"functionName":"_StructuredCloneDart2Js","ranges":[{"startOffset":274206,"endOffset":274302,"count":1}],"isBlockCoverage":true},{"functionName":"_AcceptStructuredCloneDart2Js","ranges":[{"startOffset":274339,"endOffset":274470,"count":1}],"isBlockCoverage":true},{"functionName":"_convertDataTree","ranges":[{"startOffset":274494,"endOffset":274639,"count":1}],"isBlockCoverage":true},{"functionName":"promiseToFuture","ranges":[{"startOffset":274662,"endOffset":275033,"count":0}],"isBlockCoverage":false},{"functionName":"_convertDataTree__convert","ranges":[{"startOffset":275066,"endOffset":275147,"count":1}],"isBlockCoverage":true},{"functionName":"promiseToFuture_closure","ranges":[{"startOffset":275178,"endOffset":275272,"count":0}],"isBlockCoverage":false},{"functionName":"promiseToFuture_closure0","ranges":[{"startOffset":275304,"endOffset":275376,"count":0}],"isBlockCoverage":false},{"functionName":"ByteBuffer","ranges":[{"startOffset":275394,"endOffset":275423,"count":0}],"isBlockCoverage":false},{"functionName":"ByteData","ranges":[{"startOffset":275439,"endOffset":275466,"count":0}],"isBlockCoverage":false},{"functionName":"Int8List","ranges":[{"startOffset":275482,"endOffset":275509,"count":0}],"isBlockCoverage":false},{"functionName":"Uint8List","ranges":[{"startOffset":275526,"endOffset":275554,"count":0}],"isBlockCoverage":false},{"functionName":"Uint8ClampedList","ranges":[{"startOffset":275578,"endOffset":275613,"count":0}],"isBlockCoverage":false},{"functionName":"Int16List","ranges":[{"startOffset":275630,"endOffset":275658,"count":0}],"isBlockCoverage":false},{"functionName":"Uint16List","ranges":[{"startOffset":275676,"endOffset":275705,"count":0}],"isBlockCoverage":false},{"functionName":"Int32List","ranges":[{"startOffset":275722,"endOffset":275750,"count":0}],"isBlockCoverage":false},{"functionName":"Uint32List","ranges":[{"startOffset":275768,"endOffset":275797,"count":0}],"isBlockCoverage":false},{"functionName":"Float32List","ranges":[{"startOffset":275816,"endOffset":275846,"count":0}],"isBlockCoverage":false},{"functionName":"Float64List","ranges":[{"startOffset":275865,"endOffset":275895,"count":0}],"isBlockCoverage":false},{"functionName":"SqlError","ranges":[{"startOffset":275911,"endOffset":275938,"count":0}],"isBlockCoverage":false},{"functionName":"max","ranges":[{"startOffset":275949,"endOffset":276119,"count":0}],"isBlockCoverage":false},{"functionName":"pow","ranges":[{"startOffset":276130,"endOffset":276195,"count":0}],"isBlockCoverage":false},{"functionName":"_EventStreamSubscription$","ranges":[{"startOffset":276240,"endOffset":276575,"count":1},{"startOffset":276327,"endOffset":276333,"count":0}],"isBlockCoverage":true},{"functionName":"_wrapZone","ranges":[{"startOffset":276592,"endOffset":276771,"count":1},{"startOffset":276705,"endOffset":276770,"count":0}],"isBlockCoverage":true},{"functionName":"ApplicationCacheErrorEvent","ranges":[{"startOffset":276805,"endOffset":276850,"count":0}],"isBlockCoverage":false},{"functionName":"Blob","ranges":[{"startOffset":276862,"endOffset":276885,"count":0}],"isBlockCoverage":false},{"functionName":"DomError","ranges":[{"startOffset":276901,"endOffset":276928,"count":0}],"isBlockCoverage":false},{"functionName":"DomException","ranges":[{"startOffset":276948,"endOffset":276979,"count":0}],"isBlockCoverage":false},{"functionName":"ErrorEvent","ranges":[{"startOffset":276997,"endOffset":277026,"count":0}],"isBlockCoverage":false},{"functionName":"Event","ranges":[{"startOffset":277039,"endOffset":277063,"count":0}],"isBlockCoverage":false},{"functionName":"EventTarget","ranges":[{"startOffset":277082,"endOffset":277112,"count":0}],"isBlockCoverage":false},{"functionName":"File","ranges":[{"startOffset":277124,"endOffset":277147,"count":0}],"isBlockCoverage":false},{"functionName":"Location","ranges":[{"startOffset":277163,"endOffset":277190,"count":0}],"isBlockCoverage":false},{"functionName":"MediaError","ranges":[{"startOffset":277208,"endOffset":277237,"count":0}],"isBlockCoverage":false},{"functionName":"MediaKeyMessageEvent","ranges":[{"startOffset":277265,"endOffset":277304,"count":0}],"isBlockCoverage":false},{"functionName":"MessageEvent","ranges":[{"startOffset":277324,"endOffset":277355,"count":0}],"isBlockCoverage":false},{"functionName":"MessagePort","ranges":[{"startOffset":277374,"endOffset":277404,"count":0}],"isBlockCoverage":false},{"functionName":"NavigatorUserMediaError","ranges":[{"startOffset":277435,"endOffset":277477,"count":0}],"isBlockCoverage":false},{"functionName":"OverconstrainedError","ranges":[{"startOffset":277505,"endOffset":277544,"count":0}],"isBlockCoverage":false},{"functionName":"PositionError","ranges":[{"startOffset":277565,"endOffset":277597,"count":0}],"isBlockCoverage":false},{"functionName":"PresentationConnectionCloseEvent","ranges":[{"startOffset":277637,"endOffset":277688,"count":0}],"isBlockCoverage":false},{"functionName":"SpeechRecognitionError","ranges":[{"startOffset":277718,"endOffset":277759,"count":0}],"isBlockCoverage":false},{"functionName":"EventStreamProvider","ranges":[{"startOffset":277786,"endOffset":277847,"count":0}],"isBlockCoverage":false},{"functionName":"_EventStream","ranges":[{"startOffset":277867,"endOffset":278029,"count":1}],"isBlockCoverage":true},{"functionName":"_EventStreamSubscription","ranges":[{"startOffset":278061,"endOffset":278292,"count":1}],"isBlockCoverage":true},{"functionName":"_EventStreamSubscription_closure","ranges":[{"startOffset":278332,"endOffset":278409,"count":1}],"isBlockCoverage":true},{"functionName":"AsyncMemoizer","ranges":[{"startOffset":278437,"endOffset":278540,"count":0}],"isBlockCoverage":false},{"functionName":"NullStreamSink","ranges":[{"startOffset":278558,"endOffset":278715,"count":0}],"isBlockCoverage":false},{"functionName":"NullStreamSink_addStream_closure","ranges":[{"startOffset":278751,"endOffset":278827,"count":0}],"isBlockCoverage":false},{"functionName":"Validator","ranges":[{"startOffset":278840,"endOffset":278898,"count":0}],"isBlockCoverage":false},{"functionName":"SpanScanner$","ranges":[{"startOffset":278918,"endOffset":279265,"count":0}],"isBlockCoverage":false},{"functionName":"SpanScanner","ranges":[{"startOffset":279284,"endOffset":279530,"count":0}],"isBlockCoverage":false},{"functionName":"_SpanScannerState","ranges":[{"startOffset":279555,"endOffset":279663,"count":0}],"isBlockCoverage":false},{"functionName":"RemoteListener_start","ranges":[{"startOffset":279691,"endOffset":280824,"count":1}],"isBlockCoverage":true},{"functionName":"RemoteListener__deserializeSet","ranges":[{"startOffset":280862,"endOffset":281054,"count":1},{"startOffset":280911,"endOffset":280923,"count":0},{"startOffset":280979,"endOffset":281053,"count":0}],"isBlockCoverage":true},{"functionName":"RemoteListener__sendLoadException","ranges":[{"startOffset":281095,"endOffset":281308,"count":0}],"isBlockCoverage":false},{"functionName":"RemoteListener__sendError","ranges":[{"startOffset":281341,"endOffset":281720,"count":0}],"isBlockCoverage":false},{"functionName":"RemoteListener","ranges":[{"startOffset":281742,"endOffset":281833,"count":1}],"isBlockCoverage":true},{"functionName":"RemoteListener_start_closure","ranges":[{"startOffset":281869,"endOffset":281974,"count":1}],"isBlockCoverage":true},{"functionName":"RemoteListener_start_closure0","ranges":[{"startOffset":282011,"endOffset":282059,"count":1}],"isBlockCoverage":true},{"functionName":"RemoteListener_start_closure1","ranges":[{"startOffset":282096,"endOffset":282348,"count":1}],"isBlockCoverage":true},{"functionName":"RemoteListener_start__closure","ranges":[{"startOffset":282385,"endOffset":282637,"count":1}],"isBlockCoverage":true},{"functionName":"RemoteListener_start___closure","ranges":[{"startOffset":282675,"endOffset":282905,"count":1}],"isBlockCoverage":true},{"functionName":"RemoteListener_start____closure","ranges":[{"startOffset":282944,"endOffset":283053,"count":1}],"isBlockCoverage":true},{"functionName":"RemoteListener_start____closure0","ranges":[{"startOffset":283093,"endOffset":283229,"count":1}],"isBlockCoverage":true},{"functionName":"RemoteListener_start_____closure","ranges":[{"startOffset":283269,"endOffset":283405,"count":1}],"isBlockCoverage":true},{"functionName":"RemoteListener_start___closure0","ranges":[{"startOffset":283444,"endOffset":283549,"count":1}],"isBlockCoverage":true},{"functionName":"RemoteListener__serializeGroup_closure","ranges":[{"startOffset":283595,"endOffset":283734,"count":1}],"isBlockCoverage":true},{"functionName":"RemoteListener__serializeTest_closure","ranges":[{"startOffset":283779,"endOffset":283951,"count":1}],"isBlockCoverage":true},{"functionName":"RemoteListener__runLiveTest_closure","ranges":[{"startOffset":283994,"endOffset":284076,"count":1}],"isBlockCoverage":true},{"functionName":"RemoteListener__runLiveTest_closure0","ranges":[{"startOffset":284120,"endOffset":284202,"count":1}],"isBlockCoverage":true},{"functionName":"RemoteListener__runLiveTest_closure1","ranges":[{"startOffset":284246,"endOffset":284358,"count":1}],"isBlockCoverage":true},{"functionName":"RemoteListener__runLiveTest_closure2","ranges":[{"startOffset":284402,"endOffset":284511,"count":1}],"isBlockCoverage":true},{"functionName":"RemoteListener__runLiveTest_closure3","ranges":[{"startOffset":284555,"endOffset":284667,"count":1}],"isBlockCoverage":true},{"functionName":"RemoteListener__runLiveTest__closure","ranges":[{"startOffset":284711,"endOffset":284793,"count":1}],"isBlockCoverage":true},{"functionName":"DelegatingSink","ranges":[{"startOffset":284822,"endOffset":284911,"count":0}],"isBlockCoverage":false},{"functionName":"None","ranges":[{"startOffset":284919,"endOffset":284942,"count":1}],"isBlockCoverage":true},{"functionName":"Scanner","ranges":[{"startOffset":284953,"endOffset":285079,"count":0}],"isBlockCoverage":false},{"functionName":"EmptyUnmodifiableSet","ranges":[{"startOffset":285103,"endOffset":285165,"count":1}],"isBlockCoverage":true},{"functionName":"Style__getPlatformStyle","ranges":[{"startOffset":285196,"endOffset":285573,"count":0}],"isBlockCoverage":false},{"functionName":"Style","ranges":[{"startOffset":285586,"endOffset":285610,"count":0}],"isBlockCoverage":false},{"functionName":"Pool$","ranges":[{"startOffset":285623,"endOffset":286231,"count":0}],"isBlockCoverage":false},{"functionName":"Pool","ranges":[{"startOffset":286243,"endOffset":286547,"count":0}],"isBlockCoverage":false},{"functionName":"Pool_close_closure","ranges":[{"startOffset":286573,"endOffset":286635,"count":0}],"isBlockCoverage":false},{"functionName":"Pool__onResourceReleaseAllowed_closure","ranges":[{"startOffset":286681,"endOffset":286794,"count":0}],"isBlockCoverage":false},{"functionName":"Pool__runOnRelease_closure","ranges":[{"startOffset":286828,"endOffset":286898,"count":0}],"isBlockCoverage":false},{"functionName":"Pool__runOnRelease_closure0","ranges":[{"startOffset":286933,"endOffset":287004,"count":0}],"isBlockCoverage":false},{"functionName":"PoolResource","ranges":[{"startOffset":287024,"endOffset":287110,"count":0}],"isBlockCoverage":false},{"functionName":"mapStackTrace","ranges":[{"startOffset":287131,"endOffset":288220,"count":0}],"isBlockCoverage":false},{"functionName":"_prettifyMember","ranges":[{"startOffset":288243,"endOffset":289263,"count":0}],"isBlockCoverage":false},{"functionName":"mapStackTrace_closure","ranges":[{"startOffset":289292,"endOffset":289456,"count":0}],"isBlockCoverage":false},{"functionName":"mapStackTrace_closure0","ranges":[{"startOffset":289486,"endOffset":289676,"count":0}],"isBlockCoverage":false},{"functionName":"mapStackTrace_closure1","ranges":[{"startOffset":289706,"endOffset":289747,"count":0}],"isBlockCoverage":false},{"functionName":"_prettifyMember_closure","ranges":[{"startOffset":289778,"endOffset":289820,"count":0}],"isBlockCoverage":false},{"functionName":"_prettifyMember_closure0","ranges":[{"startOffset":289852,"endOffset":289895,"count":0}],"isBlockCoverage":false},{"functionName":"StackZoneSpecification","ranges":[{"startOffset":289925,"endOffset":290121,"count":1}],"isBlockCoverage":true},{"functionName":"StackZoneSpecification_chainFor_closure","ranges":[{"startOffset":290168,"endOffset":290252,"count":0}],"isBlockCoverage":false},{"functionName":"StackZoneSpecification_chainFor_closure0","ranges":[{"startOffset":290300,"endOffset":290414,"count":0}],"isBlockCoverage":false},{"functionName":"StackZoneSpecification__registerCallback_closure","ranges":[{"startOffset":290470,"endOffset":290642,"count":1}],"isBlockCoverage":true},{"functionName":"StackZoneSpecification__registerUnaryCallback_closure","ranges":[{"startOffset":290703,"endOffset":290900,"count":1}],"isBlockCoverage":true},{"functionName":"StackZoneSpecification__registerUnaryCallback__closure","ranges":[{"startOffset":290962,"endOffset":291104,"count":1}],"isBlockCoverage":true},{"functionName":"StackZoneSpecification__registerBinaryCallback_closure","ranges":[{"startOffset":291166,"endOffset":291386,"count":1}],"isBlockCoverage":true},{"functionName":"StackZoneSpecification__registerBinaryCallback__closure","ranges":[{"startOffset":291449,"endOffset":291627,"count":1}],"isBlockCoverage":true},{"functionName":"StackZoneSpecification__currentTrace_closure","ranges":[{"startOffset":291679,"endOffset":291827,"count":1}],"isBlockCoverage":true},{"functionName":"_Node","ranges":[{"startOffset":291840,"endOffset":291919,"count":1}],"isBlockCoverage":true},{"functionName":"Group$","ranges":[{"startOffset":291933,"endOffset":292150,"count":1}],"isBlockCoverage":true},{"functionName":"Group","ranges":[{"startOffset":292163,"endOffset":292362,"count":1}],"isBlockCoverage":true},{"functionName":"Group_forPlatform_closure","ranges":[{"startOffset":292395,"endOffset":292467,"count":1}],"isBlockCoverage":true},{"functionName":"Group__map_closure","ranges":[{"startOffset":292493,"endOffset":292558,"count":1}],"isBlockCoverage":true},{"functionName":"Group__map_closure0","ranges":[{"startOffset":292585,"endOffset":292623,"count":1}],"isBlockCoverage":true},{"functionName":"Metadata__parseOnPlatform","ranges":[{"startOffset":292656,"endOffset":292775,"count":1}],"isBlockCoverage":true},{"functionName":"Metadata__parseTags","ranges":[{"startOffset":292802,"endOffset":292889,"count":1}],"isBlockCoverage":true},{"functionName":"Metadata_Metadata","ranges":[{"startOffset":292914,"endOffset":293928,"count":1},{"startOffset":293321,"endOffset":293340,"count":0},{"startOffset":293880,"endOffset":293927,"count":0}],"isBlockCoverage":true},{"functionName":"Metadata$_","ranges":[{"startOffset":293946,"endOffset":294846,"count":1},{"startOffset":294749,"endOffset":294795,"count":0}],"isBlockCoverage":true},{"functionName":"Metadata$parse","ranges":[{"startOffset":294868,"endOffset":295398,"count":1},{"startOffset":294999,"endOffset":295008,"count":0},{"startOffset":295061,"endOffset":295067,"count":0},{"startOffset":295301,"endOffset":295347,"count":0}],"isBlockCoverage":true},{"functionName":"Metadata$deserialize","ranges":[{"startOffset":295426,"endOffset":296886,"count":1},{"startOffset":295595,"endOffset":295675,"count":0},{"startOffset":296451,"endOffset":296672,"count":0}],"isBlockCoverage":true},{"functionName":"Metadata__deserializeTimeout","ranges":[{"startOffset":296922,"endOffset":297350,"count":1},{"startOffset":297058,"endOffset":297085,"count":0},{"startOffset":297241,"endOffset":297349,"count":0}],"isBlockCoverage":true},{"functionName":"Metadata","ranges":[{"startOffset":297366,"endOffset":297685,"count":1}],"isBlockCoverage":true},{"functionName":"Metadata_Metadata__unresolved","ranges":[{"startOffset":297722,"endOffset":298035,"count":1}],"isBlockCoverage":true},{"functionName":"Metadata_Metadata_closure","ranges":[{"startOffset":298068,"endOffset":298138,"count":1}],"isBlockCoverage":true},{"functionName":"Metadata$deserialize_closure","ranges":[{"startOffset":298174,"endOffset":298221,"count":1}],"isBlockCoverage":true},{"functionName":"Metadata__validateTags_closure","ranges":[{"startOffset":298259,"endOffset":298308,"count":1}],"isBlockCoverage":true},{"functionName":"Metadata__validateTags_closure0","ranges":[{"startOffset":298347,"endOffset":298397,"count":1}],"isBlockCoverage":true},{"functionName":"Metadata_validatePlatformSelectors_closure","ranges":[{"startOffset":298447,"endOffset":298542,"count":1}],"isBlockCoverage":true},{"functionName":"Metadata_merge_closure","ranges":[{"startOffset":298572,"endOffset":298613,"count":1}],"isBlockCoverage":true},{"functionName":"Metadata_merge_closure0","ranges":[{"startOffset":298644,"endOffset":298686,"count":1}],"isBlockCoverage":true},{"functionName":"Metadata_forPlatform_closure","ranges":[{"startOffset":298722,"endOffset":298825,"count":0}],"isBlockCoverage":false},{"functionName":"Metadata_serialize_closure","ranges":[{"startOffset":298859,"endOffset":298944,"count":1}],"isBlockCoverage":true},{"functionName":"Metadata_serialize_closure0","ranges":[{"startOffset":298979,"endOffset":299025,"count":1}],"isBlockCoverage":true},{"functionName":"IterableSet","ranges":[{"startOffset":299044,"endOffset":299138,"count":0}],"isBlockCoverage":false},{"functionName":"_IterableSet_SetMixin_UnmodifiableSetMixin","ranges":[{"startOffset":299188,"endOffset":299249,"count":0}],"isBlockCoverage":false},{"functionName":"Engine$","ranges":[{"startOffset":299264,"endOffset":300874,"count":0}],"isBlockCoverage":false},{"functionName":"Engine","ranges":[{"startOffset":300888,"endOffset":301558,"count":0}],"isBlockCoverage":false},{"functionName":"Engine_success_closure","ranges":[{"startOffset":301588,"endOffset":301629,"count":0}],"isBlockCoverage":false},{"functionName":"Engine_closure","ranges":[{"startOffset":301651,"endOffset":301709,"count":0}],"isBlockCoverage":false},{"functionName":"Engine_closure0","ranges":[{"startOffset":301732,"endOffset":301766,"count":0}],"isBlockCoverage":false},{"functionName":"Engine_run_closure","ranges":[{"startOffset":301792,"endOffset":301854,"count":0}],"isBlockCoverage":false},{"functionName":"Engine_run__closure","ranges":[{"startOffset":301881,"endOffset":301971,"count":0}],"isBlockCoverage":false},{"functionName":"Engine_run___closure","ranges":[{"startOffset":301999,"endOffset":302125,"count":0}],"isBlockCoverage":false},{"functionName":"Engine_run____closure","ranges":[{"startOffset":302154,"endOffset":302220,"count":0}],"isBlockCoverage":false},{"functionName":"Engine_run_closure0","ranges":[{"startOffset":302247,"endOffset":302338,"count":0}],"isBlockCoverage":false},{"functionName":"Engine__runLiveTest_closure","ranges":[{"startOffset":302373,"endOffset":302474,"count":0}],"isBlockCoverage":false},{"functionName":"Engine__runLiveTest_closure0","ranges":[{"startOffset":302510,"endOffset":302610,"count":0}],"isBlockCoverage":false},{"functionName":"Engine__runLiveTest_closure1","ranges":[{"startOffset":302646,"endOffset":302693,"count":0}],"isBlockCoverage":false},{"functionName":"Engine__runSkippedTest_closure","ranges":[{"startOffset":302731,"endOffset":302780,"count":0}],"isBlockCoverage":false},{"functionName":"Engine__runSkippedTest_closure0","ranges":[{"startOffset":302819,"endOffset":302924,"count":0}],"isBlockCoverage":false},{"functionName":"Engine__runSkippedTest_closure1","ranges":[{"startOffset":302963,"endOffset":303013,"count":0}],"isBlockCoverage":false},{"functionName":"binarySearch","ranges":[{"startOffset":303033,"endOffset":303712,"count":0}],"isBlockCoverage":false},{"functionName":"DelegatingStreamSubscription","ranges":[{"startOffset":303755,"endOffset":303802,"count":0}],"isBlockCoverage":false},{"functionName":"StreamCompleter","ranges":[{"startOffset":303821,"endOffset":303907,"count":1}],"isBlockCoverage":true},{"functionName":"_CompleterStream","ranges":[{"startOffset":303927,"endOffset":304055,"count":1}],"isBlockCoverage":true},{"functionName":"BooleanSelectorImpl","ranges":[{"startOffset":304078,"endOffset":304145,"count":0}],"isBlockCoverage":false},{"functionName":"mapMap","ranges":[{"startOffset":304159,"endOffset":304498,"count":1}],"isBlockCoverage":true},{"functionName":"mergeMaps","ranges":[{"startOffset":304515,"endOffset":304721,"count":1}],"isBlockCoverage":true},{"functionName":"mapMap_closure","ranges":[{"startOffset":304743,"endOffset":304846,"count":1}],"isBlockCoverage":true},{"functionName":"mapMap_closure0","ranges":[{"startOffset":304869,"endOffset":305013,"count":1}],"isBlockCoverage":true},{"functionName":"mergeMaps_closure","ranges":[{"startOffset":305038,"endOffset":305181,"count":1}],"isBlockCoverage":true},{"functionName":"UnionSetController$","ranges":[{"startOffset":305208,"endOffset":305497,"count":0}],"isBlockCoverage":false},{"functionName":"UnionSetController","ranges":[{"startOffset":305523,"endOffset":305678,"count":0}],"isBlockCoverage":false},{"functionName":"_IsTrue","ranges":[{"startOffset":305693,"endOffset":305719,"count":1}],"isBlockCoverage":true},{"functionName":"_Predicate","ranges":[{"startOffset":305737,"endOffset":305868,"count":0}],"isBlockCoverage":false},{"functionName":"FileLocation$_","ranges":[{"startOffset":305890,"endOffset":306294,"count":0}],"isBlockCoverage":false},{"functionName":"_FileSpan$","ranges":[{"startOffset":306312,"endOffset":306850,"count":0}],"isBlockCoverage":false},{"functionName":"SourceFile","ranges":[{"startOffset":306868,"endOffset":307027,"count":0}],"isBlockCoverage":false},{"functionName":"FileLocation","ranges":[{"startOffset":307047,"endOffset":307130,"count":0}],"isBlockCoverage":false},{"functionName":"_FileSpan","ranges":[{"startOffset":307147,"endOffset":307265,"count":0}],"isBlockCoverage":false},{"functionName":"SourceSpanMixin","ranges":[{"startOffset":307288,"endOffset":307322,"count":0}],"isBlockCoverage":false},{"functionName":"Trace_Trace$current","ranges":[{"startOffset":307349,"endOffset":307488,"count":0}],"isBlockCoverage":false},{"functionName":"Trace_Trace$from","ranges":[{"startOffset":307512,"endOffset":307838,"count":1},{"startOffset":307563,"endOffset":307639,"count":0},{"startOffset":307695,"endOffset":307837,"count":0}],"isBlockCoverage":true},{"functionName":"Trace_Trace$parse","ranges":[{"startOffset":307863,"endOffset":309377,"count":0}],"isBlockCoverage":false},{"functionName":"Trace__parseVM","ranges":[{"startOffset":309399,"endOffset":310090,"count":0}],"isBlockCoverage":false},{"functionName":"Trace$parseV8","ranges":[{"startOffset":310111,"endOffset":310662,"count":0}],"isBlockCoverage":false},{"functionName":"Trace$parseJSCore","ranges":[{"startOffset":310687,"endOffset":311118,"count":0}],"isBlockCoverage":false},{"functionName":"Trace$parseFirefox","ranges":[{"startOffset":311144,"endOffset":311604,"count":0}],"isBlockCoverage":false},{"functionName":"Trace$parseFriendly","ranges":[{"startOffset":311631,"endOffset":312181,"count":0}],"isBlockCoverage":false},{"functionName":"Trace","ranges":[{"startOffset":312194,"endOffset":312274,"count":0}],"isBlockCoverage":false},{"functionName":"Trace_Trace$current_closure","ranges":[{"startOffset":312309,"endOffset":312407,"count":0}],"isBlockCoverage":false},{"functionName":"Trace_Trace$from_closure","ranges":[{"startOffset":312439,"endOffset":312507,"count":0}],"isBlockCoverage":false},{"functionName":"Trace__parseVM_closure","ranges":[{"startOffset":312537,"endOffset":312578,"count":0}],"isBlockCoverage":false},{"functionName":"Trace$parseV8_closure","ranges":[{"startOffset":312607,"endOffset":312647,"count":0}],"isBlockCoverage":false},{"functionName":"Trace$parseV8_closure0","ranges":[{"startOffset":312677,"endOffset":312718,"count":0}],"isBlockCoverage":false},{"functionName":"Trace$parseJSCore_closure","ranges":[{"startOffset":312751,"endOffset":312795,"count":0}],"isBlockCoverage":false},{"functionName":"Trace$parseJSCore_closure0","ranges":[{"startOffset":312829,"endOffset":312874,"count":0}],"isBlockCoverage":false},{"functionName":"Trace$parseFirefox_closure","ranges":[{"startOffset":312908,"endOffset":312953,"count":0}],"isBlockCoverage":false},{"functionName":"Trace$parseFirefox_closure0","ranges":[{"startOffset":312988,"endOffset":313034,"count":0}],"isBlockCoverage":false},{"functionName":"Trace$parseFriendly_closure","ranges":[{"startOffset":313069,"endOffset":313115,"count":0}],"isBlockCoverage":false},{"functionName":"Trace$parseFriendly_closure0","ranges":[{"startOffset":313151,"endOffset":313198,"count":0}],"isBlockCoverage":false},{"functionName":"Trace_foldFrames_closure","ranges":[{"startOffset":313230,"endOffset":313305,"count":0}],"isBlockCoverage":false},{"functionName":"Trace_foldFrames_closure0","ranges":[{"startOffset":313338,"endOffset":313408,"count":0}],"isBlockCoverage":false},{"functionName":"Trace_toString_closure0","ranges":[{"startOffset":313439,"endOffset":313481,"count":0}],"isBlockCoverage":false},{"functionName":"Trace_toString_closure","ranges":[{"startOffset":313511,"endOffset":313579,"count":0}],"isBlockCoverage":false},{"functionName":"RunnerSuite","ranges":[{"startOffset":313598,"endOffset":313764,"count":0}],"isBlockCoverage":false},{"functionName":"RunnerSuiteController","ranges":[{"startOffset":313793,"endOffset":314020,"count":0}],"isBlockCoverage":false},{"functionName":"RunnerSuiteController__close_closure","ranges":[{"startOffset":314064,"endOffset":314144,"count":0}],"isBlockCoverage":false},{"functionName":"FutureGroup","ranges":[{"startOffset":314170,"endOffset":314384,"count":0}],"isBlockCoverage":false},{"functionName":"FutureGroup_add_closure","ranges":[{"startOffset":314411,"endOffset":314505,"count":0}],"isBlockCoverage":false},{"functionName":"FutureGroup_add_closure0","ranges":[{"startOffset":314533,"endOffset":314601,"count":0}],"isBlockCoverage":false},{"functionName":"ValueResult","ranges":[{"startOffset":314616,"endOffset":314696,"count":1}],"isBlockCoverage":true},{"functionName":"PackageConfigResolver__normalizeMap","ranges":[{"startOffset":314739,"endOffset":314975,"count":1}],"isBlockCoverage":true},{"functionName":"PackageConfigResolver","ranges":[{"startOffset":315004,"endOffset":315080,"count":1}],"isBlockCoverage":true},{"functionName":"PackageConfigResolver__normalizeMap_closure","ranges":[{"startOffset":315131,"endOffset":315193,"count":1}],"isBlockCoverage":true},{"functionName":"UrlStyle","ranges":[{"startOffset":315209,"endOffset":315397,"count":0}],"isBlockCoverage":false},{"functionName":"ErrorResult","ranges":[{"startOffset":315423,"endOffset":315510,"count":0}],"isBlockCoverage":false},{"functionName":"SourceLocation$","ranges":[{"startOffset":315533,"endOffset":316255,"count":0}],"isBlockCoverage":false},{"functionName":"SourceLocation","ranges":[{"startOffset":316277,"endOffset":316429,"count":0}],"isBlockCoverage":false},{"functionName":"SourceSpan","ranges":[{"startOffset":316447,"endOffset":316476,"count":0}],"isBlockCoverage":false},{"functionName":"SourceSpanBase","ranges":[{"startOffset":316498,"endOffset":316531,"count":0}],"isBlockCoverage":false},{"functionName":"GroupEntry","ranges":[{"startOffset":316549,"endOffset":316578,"count":0}],"isBlockCoverage":false},{"functionName":"LiveTestController$","ranges":[{"startOffset":316605,"endOffset":317396,"count":1}],"isBlockCoverage":true},{"functionName":"_LiveTest","ranges":[{"startOffset":317413,"endOffset":317494,"count":1}],"isBlockCoverage":true},{"functionName":"LiveTestController","ranges":[{"startOffset":317520,"endOffset":318000,"count":1}],"isBlockCoverage":true},{"functionName":"Test","ranges":[{"startOffset":318012,"endOffset":318035,"count":0}],"isBlockCoverage":false},{"functionName":"errorsDontStopTest","ranges":[{"startOffset":318061,"endOffset":318525,"count":0}],"isBlockCoverage":false},{"functionName":"errorsDontStopTest_closure","ranges":[{"startOffset":318559,"endOffset":318659,"count":0}],"isBlockCoverage":false},{"functionName":"errorsDontStopTest_closure0","ranges":[{"startOffset":318694,"endOffset":318740,"count":0}],"isBlockCoverage":false},{"functionName":"Result","ranges":[{"startOffset":318761,"endOffset":318786,"count":0}],"isBlockCoverage":false},{"functionName":"StringDescription","ranges":[{"startOffset":318807,"endOffset":318867,"count":0}],"isBlockCoverage":false},{"functionName":"FeatureMatcher","ranges":[{"startOffset":318885,"endOffset":318918,"count":0}],"isBlockCoverage":false},{"functionName":"PosixStyle","ranges":[{"startOffset":318932,"endOffset":319073,"count":0}],"isBlockCoverage":false},{"functionName":"StringScannerException$","ranges":[{"startOffset":319104,"endOffset":319201,"count":0}],"isBlockCoverage":false},{"functionName":"StringScannerException","ranges":[{"startOffset":319231,"endOffset":319359,"count":0}],"isBlockCoverage":false},{"functionName":"PlatformSelector$parse","ranges":[{"startOffset":319389,"endOffset":319570,"count":0}],"isBlockCoverage":false},{"functionName":"PlatformSelector__wrapFormatException","ranges":[{"startOffset":319615,"endOffset":319694,"count":0}],"isBlockCoverage":false},{"functionName":"PlatformSelector","ranges":[{"startOffset":319718,"endOffset":319798,"count":1}],"isBlockCoverage":true},{"functionName":"PlatformSelector$parse_closure","ranges":[{"startOffset":319836,"endOffset":319913,"count":0}],"isBlockCoverage":false},{"functionName":"PlatformSelector_validate_closure","ranges":[{"startOffset":319954,"endOffset":320067,"count":0}],"isBlockCoverage":false},{"functionName":"PlatformSelector_validate__closure","ranges":[{"startOffset":320109,"endOffset":320196,"count":0}],"isBlockCoverage":false},{"functionName":"PlatformSelector_evaluate_closure","ranges":[{"startOffset":320237,"endOffset":320317,"count":1}],"isBlockCoverage":true},{"functionName":"SuitePlatform$","ranges":[{"startOffset":320339,"endOffset":320703,"count":1},{"startOffset":320429,"endOffset":320433,"count":0},{"startOffset":320533,"endOffset":320640,"count":0}],"isBlockCoverage":true},{"functionName":"SuitePlatform","ranges":[{"startOffset":320724,"endOffset":320837,"count":1}],"isBlockCoverage":true},{"functionName":"StackTraceMapper","ranges":[{"startOffset":320861,"endOffset":320896,"count":0}],"isBlockCoverage":false},{"functionName":"LiveSuite","ranges":[{"startOffset":320913,"endOffset":320941,"count":0}],"isBlockCoverage":false},{"functionName":"JSStackTraceMapper_deserialize","ranges":[{"startOffset":320979,"endOffset":321921,"count":1},{"startOffset":321375,"endOffset":321508,"count":0}],"isBlockCoverage":true},{"functionName":"JSStackTraceMapper__deserializePackageConfigMap","ranges":[{"startOffset":321976,"endOffset":322130,"count":1}],"isBlockCoverage":true},{"functionName":"JSStackTraceMapper","ranges":[{"startOffset":322156,"endOffset":322355,"count":1}],"isBlockCoverage":true},{"functionName":"JSStackTraceMapper__deserializePackageConfigMap_closure","ranges":[{"startOffset":322418,"endOffset":322492,"count":1}],"isBlockCoverage":true},{"functionName":"StreamGroup","ranges":[{"startOffset":322518,"endOffset":322755,"count":0}],"isBlockCoverage":false},{"functionName":"StreamGroup_add_closure","ranges":[{"startOffset":322782,"endOffset":322824,"count":0}],"isBlockCoverage":false},{"functionName":"StreamGroup_add_closure0","ranges":[{"startOffset":322852,"endOffset":322948,"count":0}],"isBlockCoverage":false},{"functionName":"StreamGroup__onListen_closure","ranges":[{"startOffset":322981,"endOffset":323054,"count":0}],"isBlockCoverage":false},{"functionName":"StreamGroup__onCancelBroadcast_closure","ranges":[{"startOffset":323096,"endOffset":323178,"count":0}],"isBlockCoverage":false},{"functionName":"StreamGroup__listenToStream_closure","ranges":[{"startOffset":323217,"endOffset":323324,"count":0}],"isBlockCoverage":false},{"functionName":"_StreamGroupState","ranges":[{"startOffset":323345,"endOffset":323405,"count":1}],"isBlockCoverage":true},{"functionName":"Token","ranges":[{"startOffset":323414,"endOffset":323488,"count":0}],"isBlockCoverage":false},{"functionName":"IdentifierToken","ranges":[{"startOffset":323507,"endOffset":323591,"count":0}],"isBlockCoverage":false},{"functionName":"TokenType","ranges":[{"startOffset":323604,"endOffset":323656,"count":1}],"isBlockCoverage":true},{"functionName":"UnmodifiableSetView$","ranges":[{"startOffset":323684,"endOffset":323800,"count":0}],"isBlockCoverage":false},{"functionName":"UnmodifiableSetView","ranges":[{"startOffset":323827,"endOffset":323915,"count":1}],"isBlockCoverage":true},{"functionName":"UnmodifiableSetMixin","ranges":[{"startOffset":323943,"endOffset":323982,"count":0}],"isBlockCoverage":false},{"functionName":"_UnmodifiableSetView_DelegatingSet_UnmodifiableSetMixin","ranges":[{"startOffset":324045,"endOffset":324119,"count":0}],"isBlockCoverage":false},{"functionName":"WindowsStyle","ranges":[{"startOffset":324139,"endOffset":324331,"count":0}],"isBlockCoverage":false},{"functionName":"WindowsStyle_absolutePathToUri_closure","ranges":[{"startOffset":324377,"endOffset":324434,"count":0}],"isBlockCoverage":false},{"functionName":"decodeVlq","ranges":[{"startOffset":324451,"endOffset":325682,"count":0}],"isBlockCoverage":false},{"functionName":"closure","ranges":[{"startOffset":325697,"endOffset":325723,"count":0}],"isBlockCoverage":false},{"functionName":"internalBootstrapBrowserTest","ranges":[{"startOffset":325759,"endOffset":326211,"count":1}],"isBlockCoverage":true},{"functionName":"internalBootstrapBrowserTest_closure","ranges":[{"startOffset":326255,"endOffset":326310,"count":1}],"isBlockCoverage":true},{"functionName":"StreamQueue","ranges":[{"startOffset":326336,"endOffset":326620,"count":1}],"isBlockCoverage":true},{"functionName":"StreamQueue__ensureListening_closure","ranges":[{"startOffset":326660,"endOffset":326740,"count":1}],"isBlockCoverage":true},{"functionName":"StreamQueue__ensureListening_closure1","ranges":[{"startOffset":326781,"endOffset":326862,"count":1}],"isBlockCoverage":true},{"functionName":"StreamQueue__ensureListening_closure0","ranges":[{"startOffset":326903,"endOffset":326984,"count":1}],"isBlockCoverage":true},{"functionName":"_EventRequest","ranges":[{"startOffset":327001,"endOffset":327033,"count":0}],"isBlockCoverage":false},{"functionName":"_NextRequest","ranges":[{"startOffset":327049,"endOffset":327135,"count":1}],"isBlockCoverage":true},{"functionName":"_RestRequest","ranges":[{"startOffset":327151,"endOffset":327271,"count":1}],"isBlockCoverage":true},{"functionName":"Parser","ranges":[{"startOffset":327281,"endOffset":327343,"count":0}],"isBlockCoverage":false},{"functionName":"Matcher","ranges":[{"startOffset":327354,"endOffset":327380,"count":0}],"isBlockCoverage":false},{"functionName":"SourceMapSpan","ranges":[{"startOffset":327397,"endOffset":327547,"count":0}],"isBlockCoverage":false},{"functionName":"SourceSpanFormatException$","ranges":[{"startOffset":327581,"endOffset":327682,"count":0}],"isBlockCoverage":false},{"functionName":"SourceSpanException","ranges":[{"startOffset":327709,"endOffset":327747,"count":0}],"isBlockCoverage":false},{"functionName":"SourceSpanFormatException","ranges":[{"startOffset":327780,"endOffset":327911,"count":0}],"isBlockCoverage":false},{"functionName":"State","ranges":[{"startOffset":327924,"endOffset":328002,"count":1}],"isBlockCoverage":true},{"functionName":"Status","ranges":[{"startOffset":328016,"endOffset":328065,"count":1}],"isBlockCoverage":true},{"functionName":"Result0","ranges":[{"startOffset":328080,"endOffset":328130,"count":1}],"isBlockCoverage":true},{"functionName":"expect","ranges":[{"startOffset":328144,"endOffset":328244,"count":1}],"isBlockCoverage":true},{"functionName":"_expect","ranges":[{"startOffset":328259,"endOffset":329408,"count":1},{"startOffset":328467,"endOffset":328550,"count":0},{"startOffset":328759,"endOffset":328803,"count":0},{"startOffset":329097,"endOffset":329407,"count":0}],"isBlockCoverage":true},{"functionName":"fail","ranges":[{"startOffset":329420,"endOffset":329505,"count":0}],"isBlockCoverage":false},{"functionName":"formatFailure","ranges":[{"startOffset":329526,"endOffset":330135,"count":0}],"isBlockCoverage":false},{"functionName":"TestFailure","ranges":[{"startOffset":330154,"endOffset":330211,"count":0}],"isBlockCoverage":false},{"functionName":"_expect_closure0","ranges":[{"startOffset":330235,"endOffset":330270,"count":0}],"isBlockCoverage":false},{"functionName":"_expect_closure","ranges":[{"startOffset":330293,"endOffset":330327,"count":1}],"isBlockCoverage":true},{"functionName":"method1","ranges":[{"startOffset":330342,"endOffset":330388,"count":1}],"isBlockCoverage":true},{"functionName":"method2","ranges":[{"startOffset":330403,"endOffset":330449,"count":0}],"isBlockCoverage":false},{"functionName":"main0","ranges":[{"startOffset":330462,"endOffset":330529,"count":1}],"isBlockCoverage":true},{"functionName":"main_closure0","ranges":[{"startOffset":330550,"endOffset":330582,"count":1}],"isBlockCoverage":true},{"functionName":"main__closure","ranges":[{"startOffset":330603,"endOffset":330635,"count":1}],"isBlockCoverage":true},{"functionName":"StreamSinkCompleter","ranges":[{"startOffset":330669,"endOffset":330756,"count":1}],"isBlockCoverage":true},{"functionName":"_CompleterSink","ranges":[{"startOffset":330774,"endOffset":330961,"count":1}],"isBlockCoverage":true},{"functionName":"_CompleterSink__setDestinationSink_closure","ranges":[{"startOffset":331007,"endOffset":331068,"count":0}],"isBlockCoverage":false},{"functionName":"SubscriptionStream","ranges":[{"startOffset":331090,"endOffset":331200,"count":1}],"isBlockCoverage":true},{"functionName":"_CancelOnErrorSubscriptionWrapper","ranges":[{"startOffset":331237,"endOffset":331362,"count":0}],"isBlockCoverage":false},{"functionName":"_CancelOnErrorSubscriptionWrapper_onError_closure","ranges":[{"startOffset":331415,"endOffset":331541,"count":0}],"isBlockCoverage":false},{"functionName":"_CancelOnErrorSubscriptionWrapper_onError__closure","ranges":[{"startOffset":331595,"endOffset":331754,"count":0}],"isBlockCoverage":false},{"functionName":"Evaluator","ranges":[{"startOffset":331767,"endOffset":331825,"count":0}],"isBlockCoverage":false},{"functionName":"parseJsonExtended","ranges":[{"startOffset":331850,"endOffset":332085,"count":0}],"isBlockCoverage":false},{"functionName":"parseJson","ranges":[{"startOffset":332102,"endOffset":332830,"count":0}],"isBlockCoverage":false},{"functionName":"MultiSectionMapping$fromJson","ranges":[{"startOffset":332866,"endOffset":333186,"count":0}],"isBlockCoverage":false},{"functionName":"MappingBundle$fromJson","ranges":[{"startOffset":333216,"endOffset":333428,"count":0}],"isBlockCoverage":false},{"functionName":"SingleMapping$fromJson","ranges":[{"startOffset":333458,"endOffset":334453,"count":0}],"isBlockCoverage":false},{"functionName":"Mapping","ranges":[{"startOffset":334468,"endOffset":334494,"count":0}],"isBlockCoverage":false},{"functionName":"MultiSectionMapping","ranges":[{"startOffset":334521,"endOffset":334650,"count":0}],"isBlockCoverage":false},{"functionName":"MappingBundle","ranges":[{"startOffset":334671,"endOffset":334732,"count":0}],"isBlockCoverage":false},{"functionName":"SingleMapping","ranges":[{"startOffset":334753,"endOffset":335018,"count":0}],"isBlockCoverage":false},{"functionName":"SingleMapping$fromJson_closure","ranges":[{"startOffset":335056,"endOffset":335130,"count":0}],"isBlockCoverage":false},{"functionName":"SingleMapping__findLine_closure","ranges":[{"startOffset":335169,"endOffset":335243,"count":0}],"isBlockCoverage":false},{"functionName":"SingleMapping__findColumn_closure","ranges":[{"startOffset":335284,"endOffset":335362,"count":0}],"isBlockCoverage":false},{"functionName":"TargetLineEntry","ranges":[{"startOffset":335385,"endOffset":335472,"count":0}],"isBlockCoverage":false},{"functionName":"TargetEntry","ranges":[{"startOffset":335491,"endOffset":335685,"count":0}],"isBlockCoverage":false},{"functionName":"_MappingTokenizer","ranges":[{"startOffset":335710,"endOffset":335835,"count":0}],"isBlockCoverage":false},{"functionName":"_TokenKind","ranges":[{"startOffset":335853,"endOffset":335972,"count":1}],"isBlockCoverage":true},{"functionName":"LazyTrace","ranges":[{"startOffset":335989,"endOffset":336093,"count":1}],"isBlockCoverage":true},{"functionName":"LazyTrace_foldFrames_closure","ranges":[{"startOffset":336129,"endOffset":336259,"count":0}],"isBlockCoverage":false},{"functionName":"RuntimeSelection","ranges":[{"startOffset":336283,"endOffset":336318,"count":0}],"isBlockCoverage":false},{"functionName":"BooleanSelector","ranges":[{"startOffset":336348,"endOffset":336382,"count":0}],"isBlockCoverage":false},{"functionName":"All","ranges":[{"startOffset":336389,"endOffset":336411,"count":1}],"isBlockCoverage":true},{"functionName":"ParsedPath_ParsedPath$parse","ranges":[{"startOffset":336446,"endOffset":337648,"count":0}],"isBlockCoverage":false},{"functionName":"ParsedPath","ranges":[{"startOffset":337666,"endOffset":337813,"count":0}],"isBlockCoverage":false},{"functionName":"ParsedPath_normalize_closure","ranges":[{"startOffset":337849,"endOffset":337921,"count":0}],"isBlockCoverage":false},{"functionName":"PathException$","ranges":[{"startOffset":337943,"endOffset":338011,"count":0}],"isBlockCoverage":false},{"functionName":"PathException","ranges":[{"startOffset":338032,"endOffset":338091,"count":0}],"isBlockCoverage":false},{"functionName":"SourceSpanWithContext$","ranges":[{"startOffset":338121,"endOffset":338709,"count":0}],"isBlockCoverage":false},{"functionName":"SourceSpanWithContext","ranges":[{"startOffset":338738,"endOffset":338892,"count":0}],"isBlockCoverage":false},{"functionName":"LazyChain","ranges":[{"startOffset":338909,"endOffset":339001,"count":0}],"isBlockCoverage":false},{"functionName":"LazyChain_foldFrames_closure","ranges":[{"startOffset":339037,"endOffset":339167,"count":0}],"isBlockCoverage":false},{"functionName":"LazyChain_toTrace_closure","ranges":[{"startOffset":339200,"endOffset":339269,"count":0}],"isBlockCoverage":false},{"functionName":"StringScanner","ranges":[{"startOffset":339290,"endOffset":339322,"count":0}],"isBlockCoverage":false},{"functionName":"Declarer$","ranges":[{"startOffset":339339,"endOffset":340044,"count":1},{"startOffset":339457,"endOffset":339548,"count":0},{"startOffset":339600,"endOffset":339626,"count":0}],"isBlockCoverage":true},{"functionName":"Declarer","ranges":[{"startOffset":340060,"endOffset":340568,"count":1}],"isBlockCoverage":true},{"functionName":"Declarer_test_closure","ranges":[{"startOffset":340597,"endOffset":340688,"count":1}],"isBlockCoverage":true},{"functionName":"Declarer_test__closure","ranges":[{"startOffset":340718,"endOffset":340810,"count":1}],"isBlockCoverage":true},{"functionName":"Declarer_test___closure","ranges":[{"startOffset":340841,"endOffset":340934,"count":1}],"isBlockCoverage":true},{"functionName":"Declarer_group_closure","ranges":[{"startOffset":340964,"endOffset":341029,"count":1}],"isBlockCoverage":true},{"functionName":"Declarer_build_closure","ranges":[{"startOffset":341059,"endOffset":341125,"count":1}],"isBlockCoverage":true},{"functionName":"Declarer__runSetUps_closure","ranges":[{"startOffset":341160,"endOffset":341206,"count":1}],"isBlockCoverage":true},{"functionName":"Declarer__tearDownAll_closure","ranges":[{"startOffset":341243,"endOffset":341316,"count":0}],"isBlockCoverage":false},{"functionName":"Declarer__tearDownAll__closure","ranges":[{"startOffset":341354,"endOffset":341428,"count":0}],"isBlockCoverage":false},{"functionName":"Declarer__tearDownAll___closure","ranges":[{"startOffset":341467,"endOffset":341542,"count":0}],"isBlockCoverage":false},{"functionName":"PrintSink","ranges":[{"startOffset":341559,"endOffset":341626,"count":0}],"isBlockCoverage":false},{"functionName":"_expandSafe","ranges":[{"startOffset":341657,"endOffset":341839,"count":0}],"isBlockCoverage":false},{"functionName":"VariableNode","ranges":[{"startOffset":341859,"endOffset":341940,"count":0}],"isBlockCoverage":false},{"functionName":"NotNode","ranges":[{"startOffset":341955,"endOffset":342032,"count":0}],"isBlockCoverage":false},{"functionName":"OrNode","ranges":[{"startOffset":342046,"endOffset":342122,"count":0}],"isBlockCoverage":false},{"functionName":"AndNode","ranges":[{"startOffset":342137,"endOffset":342214,"count":0}],"isBlockCoverage":false},{"functionName":"ConditionalNode","ranges":[{"startOffset":342237,"endOffset":342361,"count":0}],"isBlockCoverage":false},{"functionName":"Highlighter__normalizeNewlines","ranges":[{"startOffset":342399,"endOffset":343195,"count":0}],"isBlockCoverage":false},{"functionName":"Highlighter__normalizeTrailingNewline","ranges":[{"startOffset":343240,"endOffset":344711,"count":0}],"isBlockCoverage":false},{"functionName":"Highlighter__normalizeEndOfLine","ranges":[{"startOffset":344750,"endOffset":345414,"count":0}],"isBlockCoverage":false},{"functionName":"Highlighter__lastLineLength","ranges":[{"startOffset":345449,"endOffset":345775,"count":0}],"isBlockCoverage":false},{"functionName":"Highlighter","ranges":[{"startOffset":345794,"endOffset":345999,"count":0}],"isBlockCoverage":false},{"functionName":"Highlighter__writeFirstLine_closure","ranges":[{"startOffset":346042,"endOffset":346147,"count":0}],"isBlockCoverage":false},{"functionName":"Highlighter__writeFirstLine_closure0","ranges":[{"startOffset":346191,"endOffset":346303,"count":0}],"isBlockCoverage":false},{"functionName":"Highlighter__writeFirstLine_closure1","ranges":[{"startOffset":346347,"endOffset":346455,"count":0}],"isBlockCoverage":false},{"functionName":"Highlighter__writeFirstLine_closure2","ranges":[{"startOffset":346499,"endOffset":346607,"count":0}],"isBlockCoverage":false},{"functionName":"Highlighter__writeIntermediateLines_closure","ranges":[{"startOffset":346658,"endOffset":346771,"count":0}],"isBlockCoverage":false},{"functionName":"Highlighter__writeLastLine_closure","ranges":[{"startOffset":346813,"endOffset":346917,"count":0}],"isBlockCoverage":false},{"functionName":"Highlighter__writeLastLine_closure0","ranges":[{"startOffset":346960,"endOffset":347071,"count":0}],"isBlockCoverage":false},{"functionName":"Highlighter__writeLastLine_closure1","ranges":[{"startOffset":347114,"endOffset":347221,"count":0}],"isBlockCoverage":false},{"functionName":"Highlighter__writeSidebar_closure","ranges":[{"startOffset":347262,"endOffset":347390,"count":0}],"isBlockCoverage":false},{"functionName":"Chain_capture","ranges":[{"startOffset":347411,"endOffset":348428,"count":1},{"startOffset":347512,"endOffset":347565,"count":0},{"startOffset":347633,"endOffset":347760,"count":0}],"isBlockCoverage":true},{"functionName":"Chain_Chain$current","ranges":[{"startOffset":348455,"endOffset":348942,"count":0}],"isBlockCoverage":false},{"functionName":"Chain_Chain$forTrace","ranges":[{"startOffset":348970,"endOffset":349524,"count":0}],"isBlockCoverage":false},{"functionName":"Chain_Chain$parse","ranges":[{"startOffset":349549,"endOffset":350621,"count":0}],"isBlockCoverage":false},{"functionName":"Chain","ranges":[{"startOffset":350634,"endOffset":350684,"count":0}],"isBlockCoverage":false},{"functionName":"Chain_capture_closure","ranges":[{"startOffset":350713,"endOffset":350804,"count":1}],"isBlockCoverage":true},{"functionName":"Chain_Chain$current_closure","ranges":[{"startOffset":350839,"endOffset":350937,"count":0}],"isBlockCoverage":false},{"functionName":"Chain_Chain$forTrace_closure","ranges":[{"startOffset":350973,"endOffset":351045,"count":0}],"isBlockCoverage":false},{"functionName":"Chain_Chain$parse_closure","ranges":[{"startOffset":351078,"endOffset":351122,"count":0}],"isBlockCoverage":false},{"functionName":"Chain_Chain$parse_closure0","ranges":[{"startOffset":351156,"endOffset":351201,"count":0}],"isBlockCoverage":false},{"functionName":"Chain_foldFrames_closure","ranges":[{"startOffset":351233,"endOffset":351332,"count":0}],"isBlockCoverage":false},{"functionName":"Chain_foldFrames_closure0","ranges":[{"startOffset":351365,"endOffset":351434,"count":0}],"isBlockCoverage":false},{"functionName":"Chain_toTrace_closure","ranges":[{"startOffset":351463,"endOffset":351503,"count":0}],"isBlockCoverage":false},{"functionName":"Chain_toString_closure0","ranges":[{"startOffset":351534,"endOffset":351576,"count":0}],"isBlockCoverage":false},{"functionName":"Chain_toString__closure0","ranges":[{"startOffset":351608,"endOffset":351651,"count":0}],"isBlockCoverage":false},{"functionName":"Chain_toString_closure","ranges":[{"startOffset":351681,"endOffset":351749,"count":0}],"isBlockCoverage":false},{"functionName":"Chain_toString__closure","ranges":[{"startOffset":351780,"endOffset":351849,"count":0}],"isBlockCoverage":false},{"functionName":"Invoker_guard","ranges":[{"startOffset":351870,"endOffset":352115,"count":1}],"isBlockCoverage":true},{"functionName":"LocalTest","ranges":[{"startOffset":352132,"endOffset":352335,"count":1}],"isBlockCoverage":true},{"functionName":"Invoker","ranges":[{"startOffset":352350,"endOffset":352742,"count":1}],"isBlockCoverage":true},{"functionName":"Invoker_guard_closure","ranges":[{"startOffset":352771,"endOffset":352811,"count":1}],"isBlockCoverage":true},{"functionName":"Invoker_guard__closure","ranges":[{"startOffset":352841,"endOffset":353002,"count":0}],"isBlockCoverage":false},{"functionName":"Invoker_waitForOutstandingCallbacks_closure","ranges":[{"startOffset":353053,"endOffset":353229,"count":1}],"isBlockCoverage":true},{"functionName":"Invoker_waitForOutstandingCallbacks_closure0","ranges":[{"startOffset":353281,"endOffset":353397,"count":1}],"isBlockCoverage":true},{"functionName":"Invoker_heartbeat_message","ranges":[{"startOffset":353430,"endOffset":353501,"count":1}],"isBlockCoverage":true},{"functionName":"Invoker_heartbeat_closure","ranges":[{"startOffset":353534,"endOffset":353661,"count":1}],"isBlockCoverage":true},{"functionName":"Invoker_heartbeat__closure","ranges":[{"startOffset":353695,"endOffset":353823,"count":0}],"isBlockCoverage":false},{"functionName":"Invoker__handleError_closure","ranges":[{"startOffset":353859,"endOffset":353932,"count":0}],"isBlockCoverage":false},{"functionName":"Invoker__handleError_closure0","ranges":[{"startOffset":353969,"endOffset":354042,"count":0}],"isBlockCoverage":false},{"functionName":"Invoker__onRun_closure","ranges":[{"startOffset":354072,"endOffset":354187,"count":1}],"isBlockCoverage":true},{"functionName":"Invoker__onRun__closure","ranges":[{"startOffset":354218,"endOffset":354334,"count":1}],"isBlockCoverage":true},{"functionName":"Invoker__onRun___closure","ranges":[{"startOffset":354366,"endOffset":354434,"count":1}],"isBlockCoverage":true},{"functionName":"Invoker__onRun____closure","ranges":[{"startOffset":354467,"endOffset":354536,"count":1}],"isBlockCoverage":true},{"functionName":"Invoker__onRun___closure0","ranges":[{"startOffset":354569,"endOffset":354638,"count":1}],"isBlockCoverage":true},{"functionName":"_AsyncCounter","ranges":[{"startOffset":354659,"endOffset":354753,"count":1}],"isBlockCoverage":true},{"functionName":"StackTraceFormatter","ranges":[{"startOffset":354780,"endOffset":354899,"count":1}],"isBlockCoverage":true},{"functionName":"StackTraceFormatter_formatStackTrace_closure","ranges":[{"startOffset":354951,"endOffset":355039,"count":0}],"isBlockCoverage":false},{"functionName":"Suite__filterGroup","ranges":[{"startOffset":355065,"endOffset":355410,"count":1},{"startOffset":355223,"endOffset":355409,"count":0}],"isBlockCoverage":true},{"functionName":"Suite","ranges":[{"startOffset":355423,"endOffset":355528,"count":1}],"isBlockCoverage":true},{"functionName":"SuiteConfiguration__list","ranges":[{"startOffset":355560,"endOffset":355606,"count":0}],"isBlockCoverage":false},{"functionName":"SuiteConfiguration__map","ranges":[{"startOffset":355637,"endOffset":355695,"count":0}],"isBlockCoverage":false},{"functionName":"SuiteConfiguration","ranges":[{"startOffset":355721,"endOffset":355758,"count":0}],"isBlockCoverage":false},{"functionName":"RemoteException_serialize","ranges":[{"startOffset":355791,"endOffset":356552,"count":0}],"isBlockCoverage":false},{"functionName":"IntersectionSelector","ranges":[{"startOffset":356587,"endOffset":356688,"count":0}],"isBlockCoverage":false},{"functionName":"StreamChannel","ranges":[{"startOffset":356705,"endOffset":356737,"count":0}],"isBlockCoverage":false},{"functionName":"_StreamChannel","ranges":[{"startOffset":356755,"endOffset":356865,"count":1}],"isBlockCoverage":true},{"functionName":"StreamChannelMixin","ranges":[{"startOffset":356887,"endOffset":356924,"count":0}],"isBlockCoverage":false},{"functionName":"Timeout","ranges":[{"startOffset":356935,"endOffset":357022,"count":1}],"isBlockCoverage":true},{"functionName":"ExpandedReporter","ranges":[{"startOffset":357042,"endOffset":357603,"count":0}],"isBlockCoverage":false},{"functionName":"ExpandedReporter__onTestStarted_closure","ranges":[{"startOffset":357646,"endOffset":357759,"count":0}],"isBlockCoverage":false},{"functionName":"ExpandedReporter__onTestStarted_closure0","ranges":[{"startOffset":357803,"endOffset":357917,"count":0}],"isBlockCoverage":false},{"functionName":"ExpandedReporter__onTestStarted_closure1","ranges":[{"startOffset":357961,"endOffset":358075,"count":0}],"isBlockCoverage":false},{"functionName":"_declarer","ranges":[{"startOffset":358092,"endOffset":358479,"count":1},{"startOffset":358251,"endOffset":358478,"count":0}],"isBlockCoverage":true},{"functionName":"test","ranges":[{"startOffset":358491,"endOffset":358707,"count":1}],"isBlockCoverage":true},{"functionName":"group","ranges":[{"startOffset":358720,"endOffset":358937,"count":1}],"isBlockCoverage":true},{"functionName":"_declarer_closure","ranges":[{"startOffset":358962,"endOffset":358998,"count":0}],"isBlockCoverage":false},{"functionName":"_declarer__closure","ranges":[{"startOffset":359024,"endOffset":359087,"count":0}],"isBlockCoverage":false},{"functionName":"main","ranges":[{"startOffset":359099,"endOffset":359177,"count":1}],"isBlockCoverage":true},{"functionName":"main_closure","ranges":[{"startOffset":359197,"endOffset":359228,"count":1}],"isBlockCoverage":true},{"functionName":"RecursiveVisitor","ranges":[{"startOffset":359259,"endOffset":359294,"count":0}],"isBlockCoverage":false},{"functionName":"InternalStyle","ranges":[{"startOffset":359311,"endOffset":359343,"count":0}],"isBlockCoverage":false},{"functionName":"StreamChannelController$","ranges":[{"startOffset":359375,"endOffset":360324,"count":1}],"isBlockCoverage":true},{"functionName":"StreamChannelController","ranges":[{"startOffset":360355,"endOffset":360462,"count":1}],"isBlockCoverage":true},{"functionName":"Runtime_Runtime$deserialize","ranges":[{"startOffset":360497,"endOffset":361558,"count":1},{"startOffset":360735,"endOffset":361557,"count":0}],"isBlockCoverage":true},{"functionName":"Runtime","ranges":[{"startOffset":361573,"endOffset":361827,"count":1}],"isBlockCoverage":true},{"functionName":"Runtime_Runtime$deserialize_closure","ranges":[{"startOffset":361870,"endOffset":361954,"count":1}],"isBlockCoverage":true},{"functionName":"indent","ranges":[{"startOffset":361968,"endOffset":362129,"count":0}],"isBlockCoverage":false},{"functionName":"toSentence","ranges":[{"startOffset":362147,"endOffset":362529,"count":0}],"isBlockCoverage":false},{"functionName":"pluralize","ranges":[{"startOffset":362546,"endOffset":362649,"count":0}],"isBlockCoverage":false},{"functionName":"prefixLines","ranges":[{"startOffset":362668,"endOffset":363386,"count":0}],"isBlockCoverage":false},{"functionName":"closure0","ranges":[{"startOffset":363402,"endOffset":363429,"count":0}],"isBlockCoverage":false},{"functionName":"LiveSuiteController$","ranges":[{"startOffset":363457,"endOffset":364346,"count":0}],"isBlockCoverage":false},{"functionName":"_LiveSuite","ranges":[{"startOffset":364364,"endOffset":364447,"count":0}],"isBlockCoverage":false},{"functionName":"LiveSuiteController","ranges":[{"startOffset":364474,"endOffset":364920,"count":0}],"isBlockCoverage":false},{"functionName":"LiveSuiteController_closure","ranges":[{"startOffset":364955,"endOffset":365026,"count":0}],"isBlockCoverage":false},{"functionName":"LiveSuiteController_closure0","ranges":[{"startOffset":365062,"endOffset":365109,"count":0}],"isBlockCoverage":false},{"functionName":"LiveSuiteController_reportLiveTest_closure","ranges":[{"startOffset":365159,"endOffset":365309,"count":0}],"isBlockCoverage":false},{"functionName":"LiveSuiteController_close_closure","ranges":[{"startOffset":365350,"endOffset":365427,"count":0}],"isBlockCoverage":false},{"functionName":"asUri","ranges":[{"startOffset":365440,"endOffset":365612,"count":0}],"isBlockCoverage":false},{"functionName":"ensureTrailingSlash","ranges":[{"startOffset":365639,"endOffset":366120,"count":1},{"startOffset":365724,"endOffset":365758,"count":0},{"startOffset":365898,"endOffset":366119,"count":0}],"isBlockCoverage":true},{"functionName":"isAlphabetic","ranges":[{"startOffset":366140,"endOffset":366302,"count":0}],"isBlockCoverage":false},{"functionName":"isDriveLetter","ranges":[{"startOffset":366323,"endOffset":366734,"count":0}],"isBlockCoverage":false},{"functionName":"countCodeUnits","ranges":[{"startOffset":366756,"endOffset":367071,"count":0}],"isBlockCoverage":false},{"functionName":"findLineStart","ranges":[{"startOffset":367092,"endOffset":367938,"count":0}],"isBlockCoverage":false},{"functionName":"validateErrorArgs","ranges":[{"startOffset":367963,"endOffset":368466,"count":0}],"isBlockCoverage":false},{"functionName":"QueueList$","ranges":[{"startOffset":368496,"endOffset":368736,"count":1}],"isBlockCoverage":true},{"functionName":"QueueList__nextPowerOf2","ranges":[{"startOffset":368767,"endOffset":369042,"count":0}],"isBlockCoverage":false},{"functionName":"QueueList","ranges":[{"startOffset":369059,"endOffset":369215,"count":1}],"isBlockCoverage":true},{"functionName":"_QueueList_Object_ListMixin","ranges":[{"startOffset":369250,"endOffset":369296,"count":0}],"isBlockCoverage":false},{"functionName":"UnionSet","ranges":[{"startOffset":369319,"endOffset":369427,"count":0}],"isBlockCoverage":false},{"functionName":"UnionSet_length_closure","ranges":[{"startOffset":369454,"endOffset":369521,"count":0}],"isBlockCoverage":false},{"functionName":"UnionSet__iterable_closure","ranges":[{"startOffset":369551,"endOffset":369621,"count":0}],"isBlockCoverage":false},{"functionName":"_UnionSet_SetBase_UnmodifiableSetMixin","ranges":[{"startOffset":369663,"endOffset":369720,"count":0}],"isBlockCoverage":false},{"functionName":"_DelegatingIterableBase","ranges":[{"startOffset":369747,"endOffset":369789,"count":0}],"isBlockCoverage":false},{"functionName":"DelegatingIterable","ranges":[{"startOffset":369811,"endOffset":369848,"count":0}],"isBlockCoverage":false},{"functionName":"DelegatingSet","ranges":[{"startOffset":369865,"endOffset":369947,"count":0}],"isBlockCoverage":false},{"functionName":"TypeMatcher","ranges":[{"startOffset":369962,"endOffset":369992,"count":0}],"isBlockCoverage":false},{"functionName":"wrapMatcher","ranges":[{"startOffset":370011,"endOffset":370493,"count":1},{"startOffset":370116,"endOffset":370487,"count":0}],"isBlockCoverage":true},{"functionName":"escape","ranges":[{"startOffset":370507,"endOffset":370736,"count":0}],"isBlockCoverage":false},{"functionName":"_getHexLiteral","ranges":[{"startOffset":370758,"endOffset":371247,"count":0}],"isBlockCoverage":false},{"functionName":"wrapMatcher_closure","ranges":[{"startOffset":371274,"endOffset":371333,"count":0}],"isBlockCoverage":false},{"functionName":"escape_closure","ranges":[{"startOffset":371355,"endOffset":371388,"count":0}],"isBlockCoverage":false},{"functionName":"Context_Context","ranges":[{"startOffset":371411,"endOffset":371599,"count":0}],"isBlockCoverage":false},{"functionName":"_parseUri","ranges":[{"startOffset":371616,"endOffset":371786,"count":0}],"isBlockCoverage":false},{"functionName":"_validateArgList","ranges":[{"startOffset":371810,"endOffset":372805,"count":0}],"isBlockCoverage":false},{"functionName":"Context","ranges":[{"startOffset":372820,"endOffset":372910,"count":0}],"isBlockCoverage":false},{"functionName":"Context_join_closure","ranges":[{"startOffset":372938,"endOffset":372977,"count":0}],"isBlockCoverage":false},{"functionName":"Context_joinAll_closure","ranges":[{"startOffset":373008,"endOffset":373050,"count":0}],"isBlockCoverage":false},{"functionName":"Context_split_closure","ranges":[{"startOffset":373079,"endOffset":373119,"count":0}],"isBlockCoverage":false},{"functionName":"_validateArgList_closure","ranges":[{"startOffset":373151,"endOffset":373194,"count":0}],"isBlockCoverage":false},{"functionName":"_PathDirection","ranges":[{"startOffset":373216,"endOffset":373273,"count":1}],"isBlockCoverage":true},{"functionName":"_PathRelation","ranges":[{"startOffset":373294,"endOffset":373350,"count":1}],"isBlockCoverage":true},{"functionName":"_StringEqualsMatcher__writeLeading","ranges":[{"startOffset":373404,"endOffset":373714,"count":0}],"isBlockCoverage":false},{"functionName":"_StringEqualsMatcher__writeTrailing","ranges":[{"startOffset":373757,"endOffset":374094,"count":0}],"isBlockCoverage":false},{"functionName":"_StringEqualsMatcher","ranges":[{"startOffset":374122,"endOffset":374203,"count":0}],"isBlockCoverage":false},{"functionName":"_DeepMatcher","ranges":[{"startOffset":374223,"endOffset":374311,"count":0}],"isBlockCoverage":false},{"functionName":"_DeepMatcher__compareSets_closure","ranges":[{"startOffset":374352,"endOffset":374533,"count":0}],"isBlockCoverage":false},{"functionName":"PackageRootResolver","ranges":[{"startOffset":374560,"endOffset":374629,"count":0}],"isBlockCoverage":false},{"functionName":"SourceLocationMixin","ranges":[{"startOffset":374656,"endOffset":374694,"count":0}],"isBlockCoverage":false},{"functionName":"_MultiChannel$","ranges":[{"startOffset":374716,"endOffset":375095,"count":1}],"isBlockCoverage":true},{"functionName":"_MultiChannel","ranges":[{"startOffset":375116,"endOffset":375414,"count":1}],"isBlockCoverage":true},{"functionName":"_MultiChannel_closure","ranges":[{"startOffset":375443,"endOffset":375531,"count":1}],"isBlockCoverage":true},{"functionName":"_MultiChannel_closure0","ranges":[{"startOffset":375561,"endOffset":375627,"count":1}],"isBlockCoverage":true},{"functionName":"_MultiChannel_closure1","ranges":[{"startOffset":375657,"endOffset":375746,"count":1}],"isBlockCoverage":true},{"functionName":"_MultiChannel__closure","ranges":[{"startOffset":375776,"endOffset":375889,"count":1}],"isBlockCoverage":true},{"functionName":"_MultiChannel_virtualChannel_closure","ranges":[{"startOffset":375933,"endOffset":376041,"count":1}],"isBlockCoverage":true},{"functionName":"_MultiChannel_virtualChannel_closure0","ranges":[{"startOffset":376086,"endOffset":376195,"count":1}],"isBlockCoverage":true},{"functionName":"VirtualChannel","ranges":[{"startOffset":376217,"endOffset":376400,"count":1}],"isBlockCoverage":true},{"functionName":"Message","ranges":[{"startOffset":376415,"endOffset":376491,"count":0}],"isBlockCoverage":false},{"functionName":"MessageType","ranges":[{"startOffset":376510,"endOffset":376564,"count":1}],"isBlockCoverage":true},{"functionName":"current","ranges":[{"startOffset":376579,"endOffset":377522,"count":0}],"isBlockCoverage":false},{"functionName":"prettyPrint","ranges":[{"startOffset":377553,"endOffset":377748,"count":0}],"isBlockCoverage":false},{"functionName":"_typeName","ranges":[{"startOffset":377765,"endOffset":377994,"count":0}],"isBlockCoverage":false},{"functionName":"_escapeString","ranges":[{"startOffset":378015,"endOffset":378154,"count":0}],"isBlockCoverage":false},{"functionName":"prettyPrint__prettyPrint","ranges":[{"startOffset":378186,"endOffset":378292,"count":0}],"isBlockCoverage":false},{"functionName":"prettyPrint__prettyPrint_pp","ranges":[{"startOffset":378327,"endOffset":378461,"count":0}],"isBlockCoverage":false},{"functionName":"prettyPrint__prettyPrint_closure","ranges":[{"startOffset":378501,"endOffset":378578,"count":0}],"isBlockCoverage":false},{"functionName":"prettyPrint__prettyPrint_closure0","ranges":[{"startOffset":378619,"endOffset":378721,"count":0}],"isBlockCoverage":false},{"functionName":"prettyPrint__prettyPrint_closure1","ranges":[{"startOffset":378762,"endOffset":378840,"count":0}],"isBlockCoverage":false},{"functionName":"LiveTest","ranges":[{"startOffset":378856,"endOffset":378883,"count":0}],"isBlockCoverage":false},{"functionName":"Frame_Frame$parseVM","ranges":[{"startOffset":378922,"endOffset":379038,"count":0}],"isBlockCoverage":false},{"functionName":"Frame_Frame$parseV8","ranges":[{"startOffset":379065,"endOffset":379181,"count":0}],"isBlockCoverage":false},{"functionName":"Frame_Frame$parseFirefox","ranges":[{"startOffset":379213,"endOffset":379334,"count":0}],"isBlockCoverage":false},{"functionName":"Frame_Frame$parseFriendly","ranges":[{"startOffset":379367,"endOffset":379489,"count":0}],"isBlockCoverage":false},{"functionName":"Frame__uriOrPathToUri","ranges":[{"startOffset":379518,"endOffset":380076,"count":0}],"isBlockCoverage":false},{"functionName":"Frame__catchFormatException","ranges":[{"startOffset":380111,"endOffset":380455,"count":0}],"isBlockCoverage":false},{"functionName":"Frame","ranges":[{"startOffset":380468,"endOffset":380605,"count":0}],"isBlockCoverage":false},{"functionName":"Frame_Frame$parseVM_closure","ranges":[{"startOffset":380640,"endOffset":380711,"count":0}],"isBlockCoverage":false},{"functionName":"Frame_Frame$parseV8_closure","ranges":[{"startOffset":380746,"endOffset":380817,"count":0}],"isBlockCoverage":false},{"functionName":"Frame_Frame$parseV8_closure_parseLocation","ranges":[{"startOffset":380866,"endOffset":380951,"count":0}],"isBlockCoverage":false},{"functionName":"Frame_Frame$parseFirefox_closure","ranges":[{"startOffset":380991,"endOffset":381067,"count":0}],"isBlockCoverage":false},{"functionName":"Frame_Frame$parseFriendly_closure","ranges":[{"startOffset":381108,"endOffset":381185,"count":0}],"isBlockCoverage":false},{"functionName":"AsciiGlyphSet","ranges":[{"startOffset":381206,"endOffset":381238,"count":1}],"isBlockCoverage":true},{"functionName":"UnparsedFrame","ranges":[{"startOffset":381266,"endOffset":381349,"count":0}],"isBlockCoverage":false},{"functionName":"StreamChannelCompleter","ranges":[{"startOffset":381375,"endOffset":381623,"count":1}],"isBlockCoverage":true},{"functionName":"postMessageChannel","ranges":[{"startOffset":381649,"endOffset":382274,"count":1}],"isBlockCoverage":true},{"functionName":"postMessageChannel_closure","ranges":[{"startOffset":382308,"endOffset":382353,"count":1}],"isBlockCoverage":true},{"functionName":"postMessageChannel_closure0","ranges":[{"startOffset":382388,"endOffset":382464,"count":1}],"isBlockCoverage":true},{"functionName":"postMessageChannel__closure","ranges":[{"startOffset":382499,"endOffset":382575,"count":1}],"isBlockCoverage":true},{"functionName":"postMessageChannel__closure0","ranges":[{"startOffset":382611,"endOffset":382682,"count":1}],"isBlockCoverage":true},{"functionName":"postMessageChannel__closure1","ranges":[{"startOffset":382718,"endOffset":382827,"count":1}],"isBlockCoverage":true},{"functionName":"OperatingSystem_find","ranges":[{"startOffset":382855,"endOffset":383034,"count":1}],"isBlockCoverage":true},{"functionName":"OperatingSystem","ranges":[{"startOffset":383057,"endOffset":383147,"count":1}],"isBlockCoverage":true},{"functionName":"OperatingSystem_find_closure","ranges":[{"startOffset":383183,"endOffset":383260,"count":1}],"isBlockCoverage":true},{"functionName":"OperatingSystem_find_closure0","ranges":[{"startOffset":383297,"endOffset":383345,"count":1}],"isBlockCoverage":true},{"functionName":"SuiteChannelManager","ranges":[{"startOffset":383372,"endOffset":383520,"count":1}],"isBlockCoverage":true},{"functionName":"GuaranteeChannel$","ranges":[{"startOffset":383557,"endOffset":383845,"count":1}],"isBlockCoverage":true},{"functionName":"GuaranteeChannel","ranges":[{"startOffset":383869,"endOffset":384037,"count":1}],"isBlockCoverage":true},{"functionName":"GuaranteeChannel_closure","ranges":[{"startOffset":384069,"endOffset":384165,"count":1}],"isBlockCoverage":true},{"functionName":"GuaranteeChannel__closure","ranges":[{"startOffset":384198,"endOffset":384267,"count":1}],"isBlockCoverage":true},{"functionName":"_GuaranteeSink","ranges":[{"startOffset":384289,"endOffset":384584,"count":1}],"isBlockCoverage":true},{"functionName":"_GuaranteeSink__addError_closure","ranges":[{"startOffset":384624,"endOffset":384675,"count":0}],"isBlockCoverage":false},{"functionName":"_GuaranteeSink_addStream_closure","ranges":[{"startOffset":384715,"endOffset":384791,"count":1}],"isBlockCoverage":true},{"functionName":"UnicodeGlyphSet","ranges":[{"startOffset":384814,"endOffset":384848,"count":1}],"isBlockCoverage":true},{"functionName":"ClosedException$","ranges":[{"startOffset":384872,"endOffset":384928,"count":0}],"isBlockCoverage":false},{"functionName":"ClosedException","ranges":[{"startOffset":384951,"endOffset":384985,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":385218,"endOffset":385284,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":385304,"endOffset":385382,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":385400,"endOffset":385507,"count":0}],"isBlockCoverage":false},{"functionName":"get$runtimeType","ranges":[{"startOffset":385530,"endOffset":385597,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":385644,"endOffset":385701,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":385721,"endOffset":385788,"count":1},{"startOffset":385773,"endOffset":385781,"count":0}],"isBlockCoverage":true},{"functionName":"get$runtimeType","ranges":[{"startOffset":385811,"endOffset":385867,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":385923,"endOffset":385984,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":386002,"endOffset":386049,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":386069,"endOffset":386111,"count":0}],"isBlockCoverage":false},{"functionName":"get$runtimeType","ranges":[{"startOffset":386134,"endOffset":386190,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":386294,"endOffset":386336,"count":0}],"isBlockCoverage":false},{"functionName":"get$runtimeType","ranges":[{"startOffset":386359,"endOffset":386419,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":386437,"endOffset":386494,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":386659,"endOffset":386925,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":386943,"endOffset":387010,"count":0}],"isBlockCoverage":false},{"functionName":"add$1","ranges":[{"startOffset":387073,"endOffset":387290,"count":1},{"startOffset":387210,"endOffset":387256,"count":0}],"isBlockCoverage":true},{"functionName":"removeAt$1","ranges":[{"startOffset":387308,"endOffset":387608,"count":0}],"isBlockCoverage":false},{"functionName":"insert$2","ranges":[{"startOffset":387624,"endOffset":387991,"count":0}],"isBlockCoverage":false},{"functionName":"insertAll$2","ranges":[{"startOffset":388010,"endOffset":388615,"count":0}],"isBlockCoverage":false},{"functionName":"removeLast$0","ranges":[{"startOffset":388635,"endOffset":388887,"count":0}],"isBlockCoverage":false},{"functionName":"remove$1","ranges":[{"startOffset":388903,"endOffset":389218,"count":1},{"startOffset":388989,"endOffset":389038,"count":0},{"startOffset":389192,"endOffset":389217,"count":0}],"isBlockCoverage":true},{"functionName":"addAll$1","ranges":[{"startOffset":389234,"endOffset":389764,"count":0}],"isBlockCoverage":false},{"functionName":"forEach$1","ranges":[{"startOffset":389781,"endOffset":390106,"count":0}],"isBlockCoverage":false},{"functionName":"map$1$1","ranges":[{"startOffset":390121,"endOffset":390359,"count":0}],"isBlockCoverage":false},{"functionName":"join$1","ranges":[{"startOffset":390373,"endOffset":390629,"count":0}],"isBlockCoverage":false},{"functionName":"join$0","ranges":[{"startOffset":390643,"endOffset":390711,"count":0}],"isBlockCoverage":false},{"functionName":"fold$1$2","ranges":[{"startOffset":390727,"endOffset":391213,"count":1},{"startOffset":391010,"endOffset":391187,"count":0}],"isBlockCoverage":true},{"functionName":"firstWhere$2$orElse","ranges":[{"startOffset":391240,"endOffset":391854,"count":1},{"startOffset":391653,"endOffset":391717,"count":0},{"startOffset":391783,"endOffset":391853,"count":0}],"isBlockCoverage":true},{"functionName":"firstWhere$1","ranges":[{"startOffset":391874,"endOffset":391969,"count":1}],"isBlockCoverage":true},{"functionName":"elementAt$1","ranges":[{"startOffset":391988,"endOffset":392141,"count":1},{"startOffset":392073,"endOffset":392105,"count":0}],"isBlockCoverage":true},{"functionName":"sublist$2","ranges":[{"startOffset":392158,"endOffset":392671,"count":0}],"isBlockCoverage":false},{"functionName":"get$first","ranges":[{"startOffset":392688,"endOffset":392838,"count":1},{"startOffset":392767,"endOffset":392837,"count":0}],"isBlockCoverage":true},{"functionName":"get$last","ranges":[{"startOffset":392854,"endOffset":393028,"count":1},{"startOffset":392957,"endOffset":393027,"count":0}],"isBlockCoverage":true},{"functionName":"get$single","ranges":[{"startOffset":393046,"endOffset":393372,"count":0}],"isBlockCoverage":false},{"functionName":"setRange$4","ranges":[{"startOffset":393390,"endOffset":394326,"count":0}],"isBlockCoverage":false},{"functionName":"setRange$3","ranges":[{"startOffset":394344,"endOffset":394459,"count":0}],"isBlockCoverage":false},{"functionName":"fillRange$3","ranges":[{"startOffset":394478,"endOffset":394843,"count":0}],"isBlockCoverage":false},{"functionName":"replaceRange$3","ranges":[{"startOffset":394865,"endOffset":395890,"count":0}],"isBlockCoverage":false},{"functionName":"contains$1","ranges":[{"startOffset":395908,"endOffset":396081,"count":0}],"isBlockCoverage":false},{"functionName":"get$isEmpty","ranges":[{"startOffset":396100,"endOffset":396162,"count":1}],"isBlockCoverage":true},{"functionName":"toString$0","ranges":[{"startOffset":396180,"endOffset":396276,"count":0}],"isBlockCoverage":false},{"functionName":"toSet$0","ranges":[{"startOffset":396291,"endOffset":396422,"count":0}],"isBlockCoverage":false},{"functionName":"get$iterator","ranges":[{"startOffset":396442,"endOffset":396589,"count":1}],"isBlockCoverage":true},{"functionName":"get$hashCode","ranges":[{"startOffset":396609,"endOffset":396687,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":396705,"endOffset":396761,"count":1}],"isBlockCoverage":true},{"functionName":"set$length","ranges":[{"startOffset":396779,"endOffset":397195,"count":1},{"startOffset":396884,"endOffset":396937,"count":0},{"startOffset":396978,"endOffset":397046,"count":0},{"startOffset":397080,"endOffset":397154,"count":0}],"isBlockCoverage":true},{"functionName":"$index","ranges":[{"startOffset":397209,"endOffset":397523,"count":1},{"startOffset":397307,"endOffset":397368,"count":0},{"startOffset":397426,"endOffset":397487,"count":0}],"isBlockCoverage":true},{"functionName":"$indexSet","ranges":[{"startOffset":397540,"endOffset":398028,"count":1},{"startOffset":397720,"endOffset":397774,"count":0},{"startOffset":397811,"endOffset":397872,"count":0},{"startOffset":397930,"endOffset":397991,"count":0}],"isBlockCoverage":true},{"functionName":"get$current","ranges":[{"startOffset":398194,"endOffset":398240,"count":1}],"isBlockCoverage":true},{"functionName":"moveNext$0","ranges":[{"startOffset":398258,"endOffset":398662,"count":1},{"startOffset":398403,"endOffset":398465,"count":0}],"isBlockCoverage":true},{"functionName":"set$_current","ranges":[{"startOffset":398682,"endOffset":398771,"count":1}],"isBlockCoverage":true},{"functionName":"floor$0","ranges":[{"startOffset":398837,"endOffset":399285,"count":0}],"isBlockCoverage":false},{"functionName":"round$0","ranges":[{"startOffset":399300,"endOffset":399582,"count":1},{"startOffset":399425,"endOffset":399581,"count":0}],"isBlockCoverage":true},{"functionName":"toRadixString$1","ranges":[{"startOffset":399605,"endOffset":400471,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":400489,"endOffset":400625,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":400645,"endOffset":401151,"count":0}],"isBlockCoverage":false},{"functionName":"$mod","ranges":[{"startOffset":401163,"endOffset":401414,"count":0}],"isBlockCoverage":false},{"functionName":"$tdiv","ranges":[{"startOffset":401427,"endOffset":401624,"count":0}],"isBlockCoverage":false},{"functionName":"_tdivFast$1","ranges":[{"startOffset":401643,"endOffset":401777,"count":1},{"startOffset":401735,"endOffset":401770,"count":0}],"isBlockCoverage":true},{"functionName":"_tdivSlow$1","ranges":[{"startOffset":401796,"endOffset":402270,"count":0}],"isBlockCoverage":false},{"functionName":"_shlPositive$1","ranges":[{"startOffset":402292,"endOffset":402380,"count":0}],"isBlockCoverage":false},{"functionName":"_shrOtherPositive$1","ranges":[{"startOffset":402407,"endOffset":402644,"count":0}],"isBlockCoverage":false},{"functionName":"_shrReceiverPositive$1","ranges":[{"startOffset":402674,"endOffset":402843,"count":0}],"isBlockCoverage":false},{"functionName":"_shrBothPositive$1","ranges":[{"startOffset":402869,"endOffset":402952,"count":0}],"isBlockCoverage":false},{"functionName":"get$runtimeType","ranges":[{"startOffset":402975,"endOffset":403030,"count":0}],"isBlockCoverage":false},{"functionName":"get$runtimeType","ranges":[{"startOffset":403114,"endOffset":403169,"count":0}],"isBlockCoverage":false},{"functionName":"get$runtimeType","ranges":[{"startOffset":403238,"endOffset":403296,"count":0}],"isBlockCoverage":false},{"functionName":"codeUnitAt$1","ranges":[{"startOffset":403347,"endOffset":403614,"count":1},{"startOffset":403404,"endOffset":403465,"count":0},{"startOffset":403510,"endOffset":403567,"count":0}],"isBlockCoverage":true},{"functionName":"_codeUnitAt$1","ranges":[{"startOffset":403635,"endOffset":403815,"count":1},{"startOffset":403707,"endOffset":403768,"count":0}],"isBlockCoverage":true},{"functionName":"allMatches$2","ranges":[{"startOffset":403835,"endOffset":404180,"count":0}],"isBlockCoverage":false},{"functionName":"allMatches$1","ranges":[{"startOffset":404200,"endOffset":404289,"count":0}],"isBlockCoverage":false},{"functionName":"matchAsPrefix$2","ranges":[{"startOffset":404312,"endOffset":404833,"count":0}],"isBlockCoverage":false},{"functionName":"$add","ranges":[{"startOffset":404845,"endOffset":405018,"count":0}],"isBlockCoverage":false},{"functionName":"endsWith$1","ranges":[{"startOffset":405036,"endOffset":405256,"count":0}],"isBlockCoverage":false},{"functionName":"splitMapJoin$2$onMatch","ranges":[{"startOffset":405286,"endOffset":405477,"count":0}],"isBlockCoverage":false},{"functionName":"replaceFirst$2","ranges":[{"startOffset":405499,"endOffset":405680,"count":0}],"isBlockCoverage":false},{"functionName":"replaceRange$3","ranges":[{"startOffset":405702,"endOffset":405903,"count":0}],"isBlockCoverage":false},{"functionName":"startsWith$2","ranges":[{"startOffset":405923,"endOffset":406447,"count":1},{"startOffset":406073,"endOffset":406154,"count":0},{"startOffset":406288,"endOffset":406301,"count":0},{"startOffset":406373,"endOffset":406446,"count":0}],"isBlockCoverage":true},{"functionName":"startsWith$1","ranges":[{"startOffset":406467,"endOffset":406558,"count":0}],"isBlockCoverage":false},{"functionName":"substring$2","ranges":[{"startOffset":406577,"endOffset":407046,"count":1},{"startOffset":406656,"endOffset":406683,"count":0},{"startOffset":406718,"endOffset":406778,"count":0},{"startOffset":406820,"endOffset":406880,"count":0},{"startOffset":406927,"endOffset":406985,"count":0}],"isBlockCoverage":true},{"functionName":"substring$1","ranges":[{"startOffset":407065,"endOffset":407164,"count":0}],"isBlockCoverage":false},{"functionName":"trim$0","ranges":[{"startOffset":407178,"endOffset":407844,"count":0}],"isBlockCoverage":false},{"functionName":"$mul","ranges":[{"startOffset":407856,"endOffset":408335,"count":0}],"isBlockCoverage":false},{"functionName":"padLeft$2","ranges":[{"startOffset":408352,"endOffset":408535,"count":0}],"isBlockCoverage":false},{"functionName":"padRight$1","ranges":[{"startOffset":408553,"endOffset":408802,"count":0}],"isBlockCoverage":false},{"functionName":"indexOf$2","ranges":[{"startOffset":408819,"endOffset":409075,"count":0}],"isBlockCoverage":false},{"functionName":"indexOf$1","ranges":[{"startOffset":409092,"endOffset":409180,"count":0}],"isBlockCoverage":false},{"functionName":"lastIndexOf$2","ranges":[{"startOffset":409201,"endOffset":409620,"count":0}],"isBlockCoverage":false},{"functionName":"lastIndexOf$1","ranges":[{"startOffset":409641,"endOffset":409736,"count":0}],"isBlockCoverage":false},{"functionName":"contains$1","ranges":[{"startOffset":409754,"endOffset":409928,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":409946,"endOffset":409995,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":410015,"endOffset":410421,"count":1}],"isBlockCoverage":true},{"functionName":"get$runtimeType","ranges":[{"startOffset":410444,"endOffset":410502,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":410520,"endOffset":410576,"count":1}],"isBlockCoverage":true},{"functionName":"$index","ranges":[{"startOffset":410590,"endOffset":410806,"count":0}],"isBlockCoverage":false},{"functionName":"get$iterator","ranges":[{"startOffset":410903,"endOffset":411111,"count":1}],"isBlockCoverage":true},{"functionName":"get$length","ranges":[{"startOffset":411129,"endOffset":411209,"count":0}],"isBlockCoverage":false},{"functionName":"get$isEmpty","ranges":[{"startOffset":411228,"endOffset":411309,"count":0}],"isBlockCoverage":false},{"functionName":"get$first","ranges":[{"startOffset":411326,"endOffset":411441,"count":0}],"isBlockCoverage":false},{"functionName":"get$last","ranges":[{"startOffset":411457,"endOffset":411571,"count":0}],"isBlockCoverage":false},{"functionName":"contains$1","ranges":[{"startOffset":411589,"endOffset":411663,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":411681,"endOffset":411741,"count":0}],"isBlockCoverage":false},{"functionName":"moveNext$0","ranges":[{"startOffset":411794,"endOffset":411852,"count":1}],"isBlockCoverage":true},{"functionName":"get$current","ranges":[{"startOffset":411871,"endOffset":411953,"count":1}],"isBlockCoverage":true},{"functionName":"cast$2$0","ranges":[{"startOffset":412130,"endOffset":412317,"count":0}],"isBlockCoverage":false},{"functionName":"containsKey$1","ranges":[{"startOffset":412338,"endOffset":412405,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":412419,"endOffset":412508,"count":1}],"isBlockCoverage":true},{"functionName":"remove$1","ranges":[{"startOffset":412524,"endOffset":412615,"count":0}],"isBlockCoverage":false},{"functionName":"forEach$1","ranges":[{"startOffset":412632,"endOffset":412762,"count":0}],"isBlockCoverage":false},{"functionName":"get$keys","ranges":[{"startOffset":412778,"endOffset":412919,"count":1}],"isBlockCoverage":true},{"functionName":"get$length","ranges":[{"startOffset":412937,"endOffset":413017,"count":0}],"isBlockCoverage":false},{"functionName":"get$isEmpty","ranges":[{"startOffset":413036,"endOffset":413117,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":413177,"endOffset":413372,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":413390,"endOffset":413458,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":413508,"endOffset":413561,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":413575,"endOffset":413681,"count":0}],"isBlockCoverage":false},{"functionName":"get$iterator","ranges":[{"startOffset":413780,"endOffset":413952,"count":1}],"isBlockCoverage":true},{"functionName":"get$isEmpty","ranges":[{"startOffset":413971,"endOffset":414032,"count":0}],"isBlockCoverage":false},{"functionName":"get$first","ranges":[{"startOffset":414049,"endOffset":414211,"count":0}],"isBlockCoverage":false},{"functionName":"get$last","ranges":[{"startOffset":414227,"endOffset":414442,"count":0}],"isBlockCoverage":false},{"functionName":"contains$1","ranges":[{"startOffset":414460,"endOffset":414822,"count":0}],"isBlockCoverage":false},{"functionName":"join$1","ranges":[{"startOffset":414836,"endOffset":415796,"count":0}],"isBlockCoverage":false},{"functionName":"join$0","ranges":[{"startOffset":415810,"endOffset":415878,"count":0}],"isBlockCoverage":false},{"functionName":"map$1$1","ranges":[{"startOffset":415893,"endOffset":416137,"count":0}],"isBlockCoverage":false},{"functionName":"fold$1$2","ranges":[{"startOffset":416153,"endOffset":416676,"count":0}],"isBlockCoverage":false},{"functionName":"toList$1$growable","ranges":[{"startOffset":416701,"endOffset":417074,"count":1}],"isBlockCoverage":true},{"functionName":"toList$0","ranges":[{"startOffset":417090,"endOffset":417171,"count":1}],"isBlockCoverage":true},{"functionName":"toSet$0","ranges":[{"startOffset":417186,"endOffset":417453,"count":0}],"isBlockCoverage":false},{"functionName":"get$_endIndex","ranges":[{"startOffset":417512,"endOffset":417706,"count":0}],"isBlockCoverage":false},{"functionName":"get$_startIndex","ranges":[{"startOffset":417729,"endOffset":417914,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":417932,"endOffset":418288,"count":0}],"isBlockCoverage":false},{"functionName":"elementAt$1","ranges":[{"startOffset":418307,"endOffset":418765,"count":0}],"isBlockCoverage":false},{"functionName":"take$1","ranges":[{"startOffset":418779,"endOffset":419284,"count":0}],"isBlockCoverage":false},{"functionName":"get$current","ranges":[{"startOffset":419338,"endOffset":419395,"count":1}],"isBlockCoverage":true},{"functionName":"moveNext$0","ranges":[{"startOffset":419413,"endOffset":419942,"count":1},{"startOffset":419627,"endOffset":419685,"count":0}],"isBlockCoverage":true},{"functionName":"set$__internal$_current","ranges":[{"startOffset":419973,"endOffset":420073,"count":1}],"isBlockCoverage":true},{"functionName":"get$iterator","ranges":[{"startOffset":420150,"endOffset":420369,"count":1}],"isBlockCoverage":true},{"functionName":"get$length","ranges":[{"startOffset":420387,"endOffset":420464,"count":0}],"isBlockCoverage":false},{"functionName":"get$isEmpty","ranges":[{"startOffset":420483,"endOffset":420561,"count":0}],"isBlockCoverage":false},{"functionName":"get$first","ranges":[{"startOffset":420578,"endOffset":420669,"count":0}],"isBlockCoverage":false},{"functionName":"get$last","ranges":[{"startOffset":420685,"endOffset":420775,"count":0}],"isBlockCoverage":false},{"functionName":"moveNext$0","ranges":[{"startOffset":420909,"endOffset":421176,"count":1},{"startOffset":421003,"endOffset":421107,"count":0}],"isBlockCoverage":true},{"functionName":"get$current","ranges":[{"startOffset":421195,"endOffset":421252,"count":0}],"isBlockCoverage":false},{"functionName":"set$__internal$_current","ranges":[{"startOffset":421283,"endOffset":421378,"count":1}],"isBlockCoverage":true},{"functionName":"get$length","ranges":[{"startOffset":421437,"endOffset":421501,"count":1}],"isBlockCoverage":true},{"functionName":"elementAt$1","ranges":[{"startOffset":421520,"endOffset":421614,"count":1}],"isBlockCoverage":true},{"functionName":"get$iterator","ranges":[{"startOffset":421670,"endOffset":421816,"count":1}],"isBlockCoverage":true},{"functionName":"map$1$1","ranges":[{"startOffset":421831,"endOffset":422028,"count":0}],"isBlockCoverage":false},{"functionName":"moveNext$0","ranges":[{"startOffset":422082,"endOffset":422289,"count":1}],"isBlockCoverage":true},{"functionName":"get$current","ranges":[{"startOffset":422308,"endOffset":422369,"count":1}],"isBlockCoverage":true},{"functionName":"get$iterator","ranges":[{"startOffset":422426,"endOffset":422651,"count":0}],"isBlockCoverage":false},{"functionName":"get$current","ranges":[{"startOffset":422707,"endOffset":422764,"count":0}],"isBlockCoverage":false},{"functionName":"moveNext$0","ranges":[{"startOffset":422782,"endOffset":423337,"count":0}],"isBlockCoverage":false},{"functionName":"set$_currentExpansion","ranges":[{"startOffset":423366,"endOffset":423491,"count":0}],"isBlockCoverage":false},{"functionName":"set$__internal$_current","ranges":[{"startOffset":423522,"endOffset":423617,"count":0}],"isBlockCoverage":false},{"functionName":"get$iterator","ranges":[{"startOffset":423697,"endOffset":423851,"count":0}],"isBlockCoverage":false},{"functionName":"moveNext$0","ranges":[{"startOffset":423909,"endOffset":424236,"count":0}],"isBlockCoverage":false},{"functionName":"get$current","ranges":[{"startOffset":424255,"endOffset":424316,"count":0}],"isBlockCoverage":false},{"functionName":"moveNext$0","ranges":[{"startOffset":424370,"endOffset":424408,"count":0}],"isBlockCoverage":false},{"functionName":"get$current","ranges":[{"startOffset":424427,"endOffset":424464,"count":0}],"isBlockCoverage":false},{"functionName":"set$length","ranges":[{"startOffset":424545,"endOffset":424683,"count":0}],"isBlockCoverage":false},{"functionName":"$indexSet","ranges":[{"startOffset":424744,"endOffset":424977,"count":0}],"isBlockCoverage":false},{"functionName":"set$length","ranges":[{"startOffset":424995,"endOffset":425127,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":425229,"endOffset":425293,"count":1}],"isBlockCoverage":true},{"functionName":"elementAt$1","ranges":[{"startOffset":425312,"endOffset":425470,"count":1}],"isBlockCoverage":true},{"functionName":"get$hashCode","ranges":[{"startOffset":425519,"endOffset":425738,"count":1}],"isBlockCoverage":true},{"functionName":"toString$0","ranges":[{"startOffset":425756,"endOffset":425836,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":425847,"endOffset":426011,"count":1},{"startOffset":425901,"endOffset":425914,"count":0}],"isBlockCoverage":true},{"functionName":"cast$2$0","ranges":[{"startOffset":426061,"endOffset":426200,"count":0}],"isBlockCoverage":false},{"functionName":"get$isEmpty","ranges":[{"startOffset":426219,"endOffset":426280,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":426298,"endOffset":426359,"count":0}],"isBlockCoverage":false},{"functionName":"remove$1","ranges":[{"startOffset":426375,"endOffset":426448,"count":0}],"isBlockCoverage":false},{"functionName":"map$2$1","ranges":[{"startOffset":426463,"endOffset":426752,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":426827,"endOffset":427051,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":427069,"endOffset":427150,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":427208,"endOffset":427266,"count":0}],"isBlockCoverage":false},{"functionName":"containsKey$1","ranges":[{"startOffset":427287,"endOffset":427466,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":427480,"endOffset":427594,"count":0}],"isBlockCoverage":false},{"functionName":"_fetch$1","ranges":[{"startOffset":427610,"endOffset":427689,"count":0}],"isBlockCoverage":false},{"functionName":"forEach$1","ranges":[{"startOffset":427706,"endOffset":428021,"count":1},{"startOffset":427929,"endOffset":428015,"count":0}],"isBlockCoverage":true},{"functionName":"get$keys","ranges":[{"startOffset":428037,"endOffset":428166,"count":0}],"isBlockCoverage":false},{"functionName":"get$iterator","ranges":[{"startOffset":428232,"endOffset":428410,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":428428,"endOffset":428508,"count":0}],"isBlockCoverage":false},{"functionName":"Instantiation$1","ranges":[{"startOffset":428567,"endOffset":428666,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":428684,"endOffset":428871,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":428922,"endOffset":429017,"count":0}],"isBlockCoverage":false},{"functionName":"call$4","ranges":[{"startOffset":429031,"endOffset":429142,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":429160,"endOffset":429281,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":429347,"endOffset":429437,"count":0}],"isBlockCoverage":false},{"functionName":"matchTypeError$1","ranges":[{"startOffset":429520,"endOffset":430192,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":430242,"endOffset":430457,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":430546,"endOffset":430987,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":431075,"endOffset":431193,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":431303,"endOffset":431464,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":431536,"endOffset":431793,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":431863,"endOffset":432075,"count":0}],"isBlockCoverage":false},{"functionName":"get$$call","ranges":[{"startOffset":432112,"endOffset":432149,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":432313,"endOffset":432502,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":432548,"endOffset":432877,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":432897,"endOffset":433264,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":433282,"endOffset":433522,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":433586,"endOffset":433632,"count":0}],"isBlockCoverage":false},{"functionName":"get$message","ranges":[{"startOffset":433651,"endOffset":433704,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":433757,"endOffset":433827,"count":0}],"isBlockCoverage":false},{"functionName":"get$message","ranges":[{"startOffset":433846,"endOffset":433899,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":433955,"endOffset":434046,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":434102,"endOffset":434160,"count":0}],"isBlockCoverage":false},{"functionName":"get$isEmpty","ranges":[{"startOffset":434179,"endOffset":434243,"count":1}],"isBlockCoverage":true},{"functionName":"get$keys","ranges":[{"startOffset":434259,"endOffset":434390,"count":1}],"isBlockCoverage":true},{"functionName":"get$values","ranges":[{"startOffset":434408,"endOffset":434602,"count":0}],"isBlockCoverage":false},{"functionName":"containsKey$1","ranges":[{"startOffset":434623,"endOffset":435155,"count":1},{"startOffset":435019,"endOffset":435032,"count":0},{"startOffset":435095,"endOffset":435149,"count":0}],"isBlockCoverage":true},{"functionName":"internalContainsKey$1","ranges":[{"startOffset":435184,"endOffset":435440,"count":0}],"isBlockCoverage":false},{"functionName":"addAll$1","ranges":[{"startOffset":435456,"endOffset":435602,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":435616,"endOffset":436311,"count":1},{"startOffset":435819,"endOffset":435832,"count":0},{"startOffset":435911,"endOffset":435918,"count":0},{"startOffset":436111,"endOffset":436124,"count":0},{"startOffset":436200,"endOffset":436207,"count":0},{"startOffset":436259,"endOffset":436305,"count":0}],"isBlockCoverage":true},{"functionName":"internalGet$1","ranges":[{"startOffset":436332,"endOffset":436708,"count":0}],"isBlockCoverage":false},{"functionName":"$indexSet","ranges":[{"startOffset":436725,"endOffset":437424,"count":1}],"isBlockCoverage":true},{"functionName":"internalSet$2","ranges":[{"startOffset":437445,"endOffset":438198,"count":1},{"startOffset":437963,"endOffset":438192,"count":0}],"isBlockCoverage":true},{"functionName":"putIfAbsent$2","ranges":[{"startOffset":438219,"endOffset":438560,"count":1},{"startOffset":438463,"endOffset":438559,"count":0}],"isBlockCoverage":true},{"functionName":"remove$1","ranges":[{"startOffset":438576,"endOffset":438960,"count":1},{"startOffset":438743,"endOffset":438954,"count":0}],"isBlockCoverage":true},{"functionName":"internalRemove$1","ranges":[{"startOffset":438984,"endOffset":439546,"count":0}],"isBlockCoverage":false},{"functionName":"clear$0","ranges":[{"startOffset":439561,"endOffset":439847,"count":0}],"isBlockCoverage":false},{"functionName":"forEach$1","ranges":[{"startOffset":439864,"endOffset":440326,"count":1},{"startOffset":440224,"endOffset":440285,"count":0}],"isBlockCoverage":true},{"functionName":"__js_helper$_addHashTableEntry$3","ranges":[{"startOffset":440366,"endOffset":440739,"count":1},{"startOffset":440683,"endOffset":440733,"count":0}],"isBlockCoverage":true},{"functionName":"__js_helper$_removeHashTableEntry$2","ranges":[{"startOffset":440782,"endOffset":441088,"count":1},{"startOffset":440854,"endOffset":440866,"count":0},{"startOffset":440946,"endOffset":440958,"count":0}],"isBlockCoverage":true},{"functionName":"_modified$0","ranges":[{"startOffset":441107,"endOffset":441189,"count":1}],"isBlockCoverage":true},{"functionName":"_newLinkedCell$2","ranges":[{"startOffset":441213,"endOffset":441682,"count":1}],"isBlockCoverage":true},{"functionName":"__js_helper$_unlinkCell$1","ranges":[{"startOffset":441715,"endOffset":442185,"count":1},{"startOffset":441930,"endOffset":441966,"count":0},{"startOffset":442077,"endOffset":442117,"count":0}],"isBlockCoverage":true},{"functionName":"internalComputeHashCode$1","ranges":[{"startOffset":442218,"endOffset":442286,"count":1}],"isBlockCoverage":true},{"functionName":"internalFindBucketIndex$2","ranges":[{"startOffset":442319,"endOffset":442570,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":442588,"endOffset":442649,"count":0}],"isBlockCoverage":false},{"functionName":"_getTableCell$2","ranges":[{"startOffset":442672,"endOffset":442725,"count":1}],"isBlockCoverage":true},{"functionName":"_getTableBucket$2","ranges":[{"startOffset":442750,"endOffset":442803,"count":1}],"isBlockCoverage":true},{"functionName":"_setTableEntry$3","ranges":[{"startOffset":442827,"endOffset":442925,"count":1}],"isBlockCoverage":true},{"functionName":"_deleteTableEntry$2","ranges":[{"startOffset":442952,"endOffset":443005,"count":1}],"isBlockCoverage":true},{"functionName":"_containsTableEntry$2","ranges":[{"startOffset":443034,"endOffset":443117,"count":1}],"isBlockCoverage":true},{"functionName":"_newHashTable$0","ranges":[{"startOffset":443140,"endOffset":443353,"count":1}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":443445,"endOffset":443569,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":443587,"endOffset":443663,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":443730,"endOffset":443899,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":443917,"endOffset":443998,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":444101,"endOffset":444176,"count":0}],"isBlockCoverage":false},{"functionName":"get$isEmpty","ranges":[{"startOffset":444195,"endOffset":444276,"count":0}],"isBlockCoverage":false},{"functionName":"get$iterator","ranges":[{"startOffset":444296,"endOffset":444515,"count":1}],"isBlockCoverage":true},{"functionName":"contains$1","ranges":[{"startOffset":444533,"endOffset":444620,"count":0}],"isBlockCoverage":false},{"functionName":"get$current","ranges":[{"startOffset":444686,"endOffset":444744,"count":1}],"isBlockCoverage":true},{"functionName":"moveNext$0","ranges":[{"startOffset":444762,"endOffset":445261,"count":1},{"startOffset":444898,"endOffset":444956,"count":0}],"isBlockCoverage":true},{"functionName":"set$__js_helper$_current","ranges":[{"startOffset":445293,"endOffset":445394,"count":1}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":445468,"endOffset":445516,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":445591,"endOffset":445656,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":445732,"endOffset":445817,"count":1}],"isBlockCoverage":true},{"functionName":"toString$0","ranges":[{"startOffset":445892,"endOffset":445983,"count":0}],"isBlockCoverage":false},{"functionName":"get$_nativeGlobalVersion","ranges":[{"startOffset":446015,"endOffset":446314,"count":0}],"isBlockCoverage":false},{"functionName":"get$_nativeAnchoredVersion","ranges":[{"startOffset":446348,"endOffset":446659,"count":0}],"isBlockCoverage":false},{"functionName":"firstMatch$1","ranges":[{"startOffset":446679,"endOffset":446942,"count":0}],"isBlockCoverage":false},{"functionName":"allMatches$2","ranges":[{"startOffset":446962,"endOffset":447186,"count":0}],"isBlockCoverage":false},{"functionName":"allMatches$1","ranges":[{"startOffset":447206,"endOffset":447295,"count":0}],"isBlockCoverage":false},{"functionName":"_execGlobal$2","ranges":[{"startOffset":447316,"endOffset":447578,"count":0}],"isBlockCoverage":false},{"functionName":"_execAnchored$2","ranges":[{"startOffset":447601,"endOffset":447981,"count":0}],"isBlockCoverage":false},{"functionName":"matchAsPrefix$2","ranges":[{"startOffset":448004,"endOffset":448222,"count":0}],"isBlockCoverage":false},{"functionName":"get$start","ranges":[{"startOffset":448319,"endOffset":448369,"count":0}],"isBlockCoverage":false},{"functionName":"get$end","ranges":[{"startOffset":448384,"endOffset":448468,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":448482,"endOffset":448669,"count":0}],"isBlockCoverage":false},{"functionName":"get$iterator","ranges":[{"startOffset":448771,"endOffset":448879,"count":0}],"isBlockCoverage":false},{"functionName":"get$current","ranges":[{"startOffset":448940,"endOffset":448998,"count":0}],"isBlockCoverage":false},{"functionName":"moveNext$0","ranges":[{"startOffset":449016,"endOffset":450272,"count":0}],"isBlockCoverage":false},{"functionName":"get$end","ranges":[{"startOffset":450341,"endOffset":450406,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":450420,"endOffset":450574,"count":0}],"isBlockCoverage":false},{"functionName":"get$start","ranges":[{"startOffset":450608,"endOffset":450651,"count":0}],"isBlockCoverage":false},{"functionName":"get$iterator","ranges":[{"startOffset":450719,"endOffset":450837,"count":0}],"isBlockCoverage":false},{"functionName":"get$first","ranges":[{"startOffset":450854,"endOffset":451101,"count":0}],"isBlockCoverage":false},{"functionName":"moveNext$0","ranges":[{"startOffset":451167,"endOffset":451827,"count":0}],"isBlockCoverage":false},{"functionName":"get$current","ranges":[{"startOffset":451846,"endOffset":451904,"count":0}],"isBlockCoverage":false},{"functionName":"get$runtimeType","ranges":[{"startOffset":451986,"endOffset":452048,"count":0}],"isBlockCoverage":false},{"functionName":"get$runtimeType","ranges":[{"startOffset":452215,"endOffset":452275,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":452332,"endOffset":452388,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":452487,"endOffset":452644,"count":0}],"isBlockCoverage":false},{"functionName":"$indexSet","ranges":[{"startOffset":452661,"endOffset":452863,"count":0}],"isBlockCoverage":false},{"functionName":"$indexSet","ranges":[{"startOffset":452995,"endOffset":453194,"count":1}],"isBlockCoverage":true},{"functionName":"get$runtimeType","ranges":[{"startOffset":453328,"endOffset":453391,"count":0}],"isBlockCoverage":false},{"functionName":"get$runtimeType","ranges":[{"startOffset":453454,"endOffset":453517,"count":0}],"isBlockCoverage":false},{"functionName":"get$runtimeType","ranges":[{"startOffset":453578,"endOffset":453639,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":453653,"endOffset":453810,"count":0}],"isBlockCoverage":false},{"functionName":"get$runtimeType","ranges":[{"startOffset":453871,"endOffset":453932,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":453946,"endOffset":454103,"count":0}],"isBlockCoverage":false},{"functionName":"get$runtimeType","ranges":[{"startOffset":454163,"endOffset":454223,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":454237,"endOffset":454394,"count":0}],"isBlockCoverage":false},{"functionName":"get$runtimeType","ranges":[{"startOffset":454456,"endOffset":454518,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":454532,"endOffset":454689,"count":0}],"isBlockCoverage":false},{"functionName":"get$runtimeType","ranges":[{"startOffset":454751,"endOffset":454813,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":454827,"endOffset":454984,"count":0}],"isBlockCoverage":false},{"functionName":"sublist$2","ranges":[{"startOffset":455001,"endOffset":455144,"count":0}],"isBlockCoverage":false},{"functionName":"get$runtimeType","ranges":[{"startOffset":455234,"endOffset":455302,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":455320,"endOffset":455376,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":455390,"endOffset":455547,"count":0}],"isBlockCoverage":false},{"functionName":"get$runtimeType","ranges":[{"startOffset":455608,"endOffset":455669,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":455687,"endOffset":455743,"count":1}],"isBlockCoverage":true},{"functionName":"$index","ranges":[{"startOffset":455757,"endOffset":455914,"count":0}],"isBlockCoverage":false},{"functionName":"sublist$2","ranges":[{"startOffset":455931,"endOffset":456073,"count":0}],"isBlockCoverage":false},{"functionName":"_eval$1","ranges":[{"startOffset":456490,"endOffset":456591,"count":1}],"isBlockCoverage":true},{"functionName":"_bind$1","ranges":[{"startOffset":456606,"endOffset":456704,"count":1}],"isBlockCoverage":true},{"functionName":"get$hashCode","ranges":[{"startOffset":456792,"endOffset":457109,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":457120,"endOffset":457259,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":457277,"endOffset":457342,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":457405,"endOffset":457452,"count":0}],"isBlockCoverage":false},{"functionName":"get$message","ranges":[{"startOffset":457535,"endOffset":457582,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":457674,"endOffset":457802,"count":1}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":457904,"endOffset":458207,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":458319,"endOffset":458367,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":458484,"endOffset":458532,"count":0}],"isBlockCoverage":false},{"functionName":"_TimerImpl$2","ranges":[{"startOffset":458604,"endOffset":458905,"count":1},{"startOffset":458807,"endOffset":458899,"count":0}],"isBlockCoverage":true},{"functionName":"_TimerImpl$periodic$2","ranges":[{"startOffset":458934,"endOffset":459252,"count":0}],"isBlockCoverage":false},{"functionName":"cancel$0","ranges":[{"startOffset":459268,"endOffset":459616,"count":1},{"startOffset":459383,"endOffset":459390,"count":0},{"startOffset":459447,"endOffset":459494,"count":0},{"startOffset":459531,"endOffset":459610,"count":0}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":459697,"endOffset":459817,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":459900,"endOffset":460263,"count":0}],"isBlockCoverage":false},{"functionName":"complete$1","ranges":[{"startOffset":460343,"endOffset":460659,"count":1}],"isBlockCoverage":true},{"functionName":"completeError$2","ranges":[{"startOffset":460682,"endOffset":460845,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":460925,"endOffset":460999,"count":1}],"isBlockCoverage":true},{"functionName":"call$2","ranges":[{"startOffset":461079,"endOffset":461223,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":461310,"endOffset":461408,"count":1}],"isBlockCoverage":true},{"functionName":"get$isBroadcast","ranges":[{"startOffset":461490,"endOffset":461527,"count":0}],"isBlockCoverage":false},{"functionName":"_onPause$0","ranges":[{"startOffset":461590,"endOffset":461608,"count":0}],"isBlockCoverage":false},{"functionName":"_onResume$0","ranges":[{"startOffset":461627,"endOffset":461645,"count":0}],"isBlockCoverage":false},{"functionName":"set$_async$_next","ranges":[{"startOffset":461669,"endOffset":461742,"count":1}],"isBlockCoverage":true},{"functionName":"set$_async$_previous","ranges":[{"startOffset":461770,"endOffset":461855,"count":1}],"isBlockCoverage":true},{"functionName":"get$_mayAddEvent","ranges":[{"startOffset":461928,"endOffset":461976,"count":1}],"isBlockCoverage":true},{"functionName":"_ensureDoneFuture$0","ranges":[{"startOffset":462003,"endOffset":462182,"count":0}],"isBlockCoverage":false},{"functionName":"_removeListener$1","ranges":[{"startOffset":462207,"endOffset":462934,"count":0}],"isBlockCoverage":false},{"functionName":"_subscribe$4","ranges":[{"startOffset":462954,"endOffset":464472,"count":1},{"startOffset":463220,"endOffset":463469,"count":0},{"startOffset":463523,"endOffset":463526,"count":0},{"startOffset":464278,"endOffset":464337,"count":0}],"isBlockCoverage":true},{"functionName":"_recordCancel$1","ranges":[{"startOffset":464495,"endOffset":465046,"count":0}],"isBlockCoverage":false},{"functionName":"_recordPause$1","ranges":[{"startOffset":465068,"endOffset":465181,"count":0}],"isBlockCoverage":false},{"functionName":"_recordResume$1","ranges":[{"startOffset":465204,"endOffset":465317,"count":0}],"isBlockCoverage":false},{"functionName":"_addEventError$0","ranges":[{"startOffset":465341,"endOffset":465610,"count":0}],"isBlockCoverage":false},{"functionName":"add$1","ranges":[{"startOffset":465623,"endOffset":465854,"count":1},{"startOffset":465769,"endOffset":465817,"count":0}],"isBlockCoverage":true},{"functionName":"addError$2","ranges":[{"startOffset":465872,"endOffset":466438,"count":0}],"isBlockCoverage":false},{"functionName":"addError$1","ranges":[{"startOffset":466456,"endOffset":466522,"count":0}],"isBlockCoverage":false},{"functionName":"close$0","ranges":[{"startOffset":466537,"endOffset":466942,"count":0}],"isBlockCoverage":false},{"functionName":"_async$_addError$2","ranges":[{"startOffset":466968,"endOffset":467072,"count":0}],"isBlockCoverage":false},{"functionName":"_close$0","ranges":[{"startOffset":467088,"endOffset":467356,"count":0}],"isBlockCoverage":false},{"functionName":"_forEachListener$1","ranges":[{"startOffset":467382,"endOffset":468483,"count":0}],"isBlockCoverage":false},{"functionName":"_callOnCancel$0","ranges":[{"startOffset":468506,"endOffset":468761,"count":0}],"isBlockCoverage":false},{"functionName":"set$_firstSubscription","ranges":[{"startOffset":468791,"endOffset":468946,"count":1}],"isBlockCoverage":true},{"functionName":"set$_lastSubscription","ranges":[{"startOffset":468975,"endOffset":469127,"count":1}],"isBlockCoverage":true},{"functionName":"set$_addStreamState","ranges":[{"startOffset":469154,"endOffset":469293,"count":0}],"isBlockCoverage":false},{"functionName":"get$_mayAddEvent","ranges":[{"startOffset":469547,"endOffset":469673,"count":1}],"isBlockCoverage":true},{"functionName":"_addEventError$0","ranges":[{"startOffset":469697,"endOffset":469916,"count":0}],"isBlockCoverage":false},{"functionName":"_sendData$1","ranges":[{"startOffset":469935,"endOffset":470463,"count":1},{"startOffset":470076,"endOffset":470083,"count":0},{"startOffset":470308,"endOffset":470332,"count":0},{"startOffset":470356,"endOffset":470462,"count":0}],"isBlockCoverage":true},{"functionName":"_sendError$2","ranges":[{"startOffset":470483,"endOffset":470690,"count":0}],"isBlockCoverage":false},{"functionName":"_sendDone$0","ranges":[{"startOffset":470709,"endOffset":471070,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":471155,"endOffset":471289,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":471307,"endOffset":471403,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":471489,"endOffset":471653,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":471671,"endOffset":471767,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":471852,"endOffset":471979,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":471997,"endOffset":472093,"count":0}],"isBlockCoverage":false},{"functionName":"_sendData$1","ranges":[{"startOffset":472166,"endOffset":472489,"count":0}],"isBlockCoverage":false},{"functionName":"_sendError$2","ranges":[{"startOffset":472509,"endOffset":472759,"count":0}],"isBlockCoverage":false},{"functionName":"_sendDone$0","ranges":[{"startOffset":472778,"endOffset":473214,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":473299,"endOffset":473599,"count":1},{"startOffset":473419,"endOffset":473593,"count":0}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":473686,"endOffset":473986,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":474065,"endOffset":474620,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":474695,"endOffset":475141,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":475159,"endOffset":475217,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":475276,"endOffset":475578,"count":1},{"startOffset":475384,"endOffset":475577,"count":0}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":475657,"endOffset":476933,"count":1},{"startOffset":475992,"endOffset":476655,"count":0},{"startOffset":476684,"endOffset":476827,"count":0}],"isBlockCoverage":true},{"functionName":"toString$0","ranges":[{"startOffset":477010,"endOffset":477201,"count":0}],"isBlockCoverage":false},{"functionName":"get$message","ranges":[{"startOffset":477241,"endOffset":477294,"count":0}],"isBlockCoverage":false},{"functionName":"completeError$2","ranges":[{"startOffset":477380,"endOffset":477925,"count":0}],"isBlockCoverage":false},{"functionName":"completeError$1","ranges":[{"startOffset":477948,"endOffset":478019,"count":0}],"isBlockCoverage":false},{"functionName":"complete$1","ranges":[{"startOffset":478096,"endOffset":478336,"count":1},{"startOffset":478231,"endOffset":478296,"count":0}],"isBlockCoverage":true},{"functionName":"complete$0","ranges":[{"startOffset":478354,"endOffset":478408,"count":1}],"isBlockCoverage":true},{"functionName":"_completeError$2","ranges":[{"startOffset":478432,"endOffset":478527,"count":0}],"isBlockCoverage":false},{"functionName":"complete$1","ranges":[{"startOffset":478582,"endOffset":478817,"count":0}],"isBlockCoverage":false},{"functionName":"complete$0","ranges":[{"startOffset":478835,"endOffset":478889,"count":0}],"isBlockCoverage":false},{"functionName":"_completeError$2","ranges":[{"startOffset":478913,"endOffset":479003,"count":0}],"isBlockCoverage":false},{"functionName":"matchesErrorTest$1","ranges":[{"startOffset":479067,"endOffset":479290,"count":0}],"isBlockCoverage":false},{"functionName":"handleError$1","ranges":[{"startOffset":479311,"endOffset":480110,"count":0}],"isBlockCoverage":false},{"functionName":"then$1$2$onError","ranges":[{"startOffset":480164,"endOffset":480845,"count":1},{"startOffset":480507,"endOffset":480563,"count":0},{"startOffset":480676,"endOffset":480679,"count":0}],"isBlockCoverage":true},{"functionName":"then$1$1","ranges":[{"startOffset":480861,"endOffset":480933,"count":1}],"isBlockCoverage":true},{"functionName":"_thenAwait$1$2","ranges":[{"startOffset":480955,"endOffset":481337,"count":1}],"isBlockCoverage":true},{"functionName":"catchError$1","ranges":[{"startOffset":481357,"endOffset":481806,"count":0}],"isBlockCoverage":false},{"functionName":"whenComplete$1","ranges":[{"startOffset":481828,"endOffset":482281,"count":1}],"isBlockCoverage":true},{"functionName":"_cloneResult$1","ranges":[{"startOffset":482303,"endOffset":482502,"count":0}],"isBlockCoverage":false},{"functionName":"_addListener$1","ranges":[{"startOffset":482524,"endOffset":483257,"count":1},{"startOffset":482868,"endOffset":483106,"count":0}],"isBlockCoverage":true},{"functionName":"_prependListeners$1","ranges":[{"startOffset":483284,"endOffset":484374,"count":1},{"startOffset":483471,"endOffset":484368,"count":0}],"isBlockCoverage":true},{"functionName":"_removeListeners$0","ranges":[{"startOffset":484400,"endOffset":484667,"count":1}],"isBlockCoverage":true},{"functionName":"_reverseListeners$1","ranges":[{"startOffset":484694,"endOffset":484951,"count":1}],"isBlockCoverage":true},{"functionName":"_complete$1","ranges":[{"startOffset":484970,"endOffset":485601,"count":1},{"startOffset":485249,"endOffset":485317,"count":0}],"isBlockCoverage":true},{"functionName":"_completeWithValue$1","ranges":[{"startOffset":485629,"endOffset":486055,"count":1}],"isBlockCoverage":true},{"functionName":"_completeError$2","ranges":[{"startOffset":486079,"endOffset":486469,"count":0}],"isBlockCoverage":false},{"functionName":"_completeError$1","ranges":[{"startOffset":486493,"endOffset":486565,"count":0}],"isBlockCoverage":false},{"functionName":"_asyncComplete$1","ranges":[{"startOffset":486589,"endOffset":487004,"count":1},{"startOffset":486779,"endOffset":486841,"count":0}],"isBlockCoverage":true},{"functionName":"_chainFuture$1","ranges":[{"startOffset":487026,"endOffset":487512,"count":0}],"isBlockCoverage":false},{"functionName":"_asyncCompleteError$2","ranges":[{"startOffset":487541,"endOffset":487857,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":487940,"endOffset":488024,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":488113,"endOffset":488205,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":488296,"endOffset":488475,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":488567,"endOffset":488768,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":488782,"endOffset":488844,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":488937,"endOffset":489007,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":489093,"endOffset":489210,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":489294,"endOffset":489370,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":489461,"endOffset":489543,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":489655,"endOffset":491612,"count":1},{"startOffset":490115,"endOffset":490871,"count":0},{"startOffset":491050,"endOffset":491298,"count":0},{"startOffset":491326,"endOffset":491606,"count":0}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":491732,"endOffset":491785,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":491891,"endOffset":492537,"count":1},{"startOffset":492293,"endOffset":492531,"count":0}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":492634,"endOffset":493916,"count":0}],"isBlockCoverage":false},{"functionName":"get$isBroadcast","ranges":[{"startOffset":494027,"endOffset":494065,"count":1}],"isBlockCoverage":true},{"functionName":"pipe$1","ranges":[{"startOffset":494079,"endOffset":494313,"count":1}],"isBlockCoverage":true},{"functionName":"get$length","ranges":[{"startOffset":494331,"endOffset":494644,"count":0}],"isBlockCoverage":false},{"functionName":"get$first","ranges":[{"startOffset":494661,"endOffset":495037,"count":1}],"isBlockCoverage":true},{"functionName":"get$last","ranges":[{"startOffset":495053,"endOffset":495429,"count":0}],"isBlockCoverage":false},{"functionName":"firstWhere$1","ranges":[{"startOffset":495449,"endOffset":495994,"count":1}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":496063,"endOffset":496187,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":496205,"endOffset":496263,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":496333,"endOffset":496503,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":496593,"endOffset":496811,"count":1}],"isBlockCoverage":true},{"functionName":"$signature","ranges":[{"startOffset":496829,"endOffset":496907,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":496963,"endOffset":497027,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":497105,"endOffset":497216,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":497234,"endOffset":497320,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":497379,"endOffset":497447,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":497523,"endOffset":497686,"count":1}],"isBlockCoverage":true},{"functionName":"$signature","ranges":[{"startOffset":497704,"endOffset":497790,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":497848,"endOffset":498176,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":498251,"endOffset":498435,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":498453,"endOffset":498539,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":498596,"endOffset":499042,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":499123,"endOffset":499482,"count":1}],"isBlockCoverage":true},{"functionName":"$signature","ranges":[{"startOffset":499500,"endOffset":499586,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":499649,"endOffset":499710,"count":1}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":499794,"endOffset":499961,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":500044,"endOffset":500372,"count":0}],"isBlockCoverage":false},{"functionName":"get$_pendingEvents","ranges":[{"startOffset":500561,"endOffset":500951,"count":1},{"startOffset":500776,"endOffset":500950,"count":0}],"isBlockCoverage":true},{"functionName":"_ensurePendingEvents$0","ranges":[{"startOffset":500981,"endOffset":501700,"count":0}],"isBlockCoverage":false},{"functionName":"get$_async$_subscription","ranges":[{"startOffset":501732,"endOffset":502152,"count":1}],"isBlockCoverage":true},{"functionName":"_badEventState$0","ranges":[{"startOffset":502176,"endOffset":502426,"count":0}],"isBlockCoverage":false},{"functionName":"addStream$2$cancelOnError","ranges":[{"startOffset":502459,"endOffset":503511,"count":1},{"startOffset":502668,"endOffset":502716,"count":0},{"startOffset":502743,"endOffset":502874,"count":0},{"startOffset":503017,"endOffset":503060,"count":0},{"startOffset":503300,"endOffset":503316,"count":0},{"startOffset":503326,"endOffset":503339,"count":0}],"isBlockCoverage":true},{"functionName":"_ensureDoneFuture$0","ranges":[{"startOffset":503538,"endOffset":503771,"count":1},{"startOffset":503692,"endOffset":503747,"count":0}],"isBlockCoverage":true},{"functionName":"add$1","ranges":[{"startOffset":503784,"endOffset":504005,"count":1},{"startOffset":503924,"endOffset":503972,"count":0}],"isBlockCoverage":true},{"functionName":"addError$2","ranges":[{"startOffset":504023,"endOffset":504587,"count":0}],"isBlockCoverage":false},{"functionName":"addError$1","ranges":[{"startOffset":504605,"endOffset":504671,"count":0}],"isBlockCoverage":false},{"functionName":"close$0","ranges":[{"startOffset":504686,"endOffset":504977,"count":1},{"startOffset":504785,"endOffset":504820,"count":0},{"startOffset":504848,"endOffset":504896,"count":0}],"isBlockCoverage":true},{"functionName":"_closeUnchecked$0","ranges":[{"startOffset":505002,"endOffset":505204,"count":1},{"startOffset":505082,"endOffset":505101,"count":0},{"startOffset":505141,"endOffset":505198,"count":0}],"isBlockCoverage":true},{"functionName":"_add$1","ranges":[{"startOffset":505218,"endOffset":505567,"count":1},{"startOffset":505423,"endOffset":505561,"count":0}],"isBlockCoverage":true},{"functionName":"_async$_addError$2","ranges":[{"startOffset":505593,"endOffset":505900,"count":0}],"isBlockCoverage":false},{"functionName":"_close$0","ranges":[{"startOffset":505916,"endOffset":506256,"count":0}],"isBlockCoverage":false},{"functionName":"_subscribe$4","ranges":[{"startOffset":506276,"endOffset":507433,"count":1},{"startOffset":506562,"endOffset":506639,"count":0},{"startOffset":507026,"endOffset":507213,"count":0}],"isBlockCoverage":true},{"functionName":"_recordCancel$1","ranges":[{"startOffset":507456,"endOffset":508586,"count":1},{"startOffset":507936,"endOffset":508391,"count":0}],"isBlockCoverage":true},{"functionName":"_recordPause$1","ranges":[{"startOffset":508608,"endOffset":508942,"count":1},{"startOffset":508802,"endOffset":508900,"count":0}],"isBlockCoverage":true},{"functionName":"_recordResume$1","ranges":[{"startOffset":508965,"endOffset":509301,"count":1},{"startOffset":509159,"endOffset":509258,"count":0}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":509551,"endOffset":509611,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":509707,"endOffset":509840,"count":1},{"startOffset":509780,"endOffset":509798,"count":0},{"startOffset":509808,"endOffset":509834,"count":0}],"isBlockCoverage":true},{"functionName":"_sendData$1","ranges":[{"startOffset":509930,"endOffset":510047,"count":1}],"isBlockCoverage":true},{"functionName":"_sendError$2","ranges":[{"startOffset":510067,"endOffset":510179,"count":0}],"isBlockCoverage":false},{"functionName":"_sendDone$0","ranges":[{"startOffset":510198,"endOffset":510266,"count":0}],"isBlockCoverage":false},{"functionName":"_sendData$1","ranges":[{"startOffset":510338,"endOffset":510532,"count":0}],"isBlockCoverage":false},{"functionName":"_sendError$2","ranges":[{"startOffset":510552,"endOffset":510680,"count":0}],"isBlockCoverage":false},{"functionName":"_sendDone$0","ranges":[{"startOffset":510699,"endOffset":510788,"count":0}],"isBlockCoverage":false},{"functionName":"_createSubscription$4","ranges":[{"startOffset":510942,"endOffset":511156,"count":1}],"isBlockCoverage":true},{"functionName":"get$hashCode","ranges":[{"startOffset":511176,"endOffset":511275,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":511286,"endOffset":511499,"count":0}],"isBlockCoverage":false},{"functionName":"_onCancel$0","ranges":[{"startOffset":511564,"endOffset":511635,"count":1}],"isBlockCoverage":true},{"functionName":"_onPause$0","ranges":[{"startOffset":511653,"endOffset":511716,"count":1}],"isBlockCoverage":true},{"functionName":"_onResume$0","ranges":[{"startOffset":511735,"endOffset":511799,"count":1}],"isBlockCoverage":true},{"functionName":"add$1","ranges":[{"startOffset":511853,"endOffset":511951,"count":1}],"isBlockCoverage":true},{"functionName":"cancel$0","ranges":[{"startOffset":512090,"endOffset":512350,"count":1},{"startOffset":512181,"endOffset":512264,"count":0}],"isBlockCoverage":true},{"functionName":"call$2","ranges":[{"startOffset":512427,"endOffset":512562,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":512648,"endOffset":512723,"count":1}],"isBlockCoverage":true},{"functionName":"_BufferingStreamSubscription$4","ranges":[{"startOffset":512883,"endOffset":513035,"count":1}],"isBlockCoverage":true},{"functionName":"_setPendingEvents$1","ranges":[{"startOffset":513062,"endOffset":513518,"count":1}],"isBlockCoverage":true},{"functionName":"onData$1","ranges":[{"startOffset":513534,"endOffset":513897,"count":1}],"isBlockCoverage":true},{"functionName":"onError$1","ranges":[{"startOffset":513914,"endOffset":514590,"count":1},{"startOffset":514246,"endOffset":514584,"count":0}],"isBlockCoverage":true},{"functionName":"onDone$1","ranges":[{"startOffset":514606,"endOffset":514849,"count":1}],"isBlockCoverage":true},{"functionName":"pause$1","ranges":[{"startOffset":514864,"endOffset":515291,"count":1},{"startOffset":514982,"endOffset":514989,"count":0},{"startOffset":515094,"endOffset":515186,"count":0},{"startOffset":515240,"endOffset":515285,"count":0}],"isBlockCoverage":true},{"functionName":"pause$0","ranges":[{"startOffset":515306,"endOffset":515357,"count":1}],"isBlockCoverage":true},{"functionName":"resume$0","ranges":[{"startOffset":515373,"endOffset":516055,"count":1},{"startOffset":515471,"endOffset":515478,"count":0},{"startOffset":515591,"endOffset":515675,"count":0},{"startOffset":515735,"endOffset":515768,"count":0}],"isBlockCoverage":true},{"functionName":"cancel$0","ranges":[{"startOffset":516071,"endOffset":516331,"count":1}],"isBlockCoverage":true},{"functionName":"get$_mayResumeInput","ranges":[{"startOffset":516358,"endOffset":516536,"count":1},{"startOffset":516458,"endOffset":516479,"count":0},{"startOffset":516488,"endOffset":516513,"count":0}],"isBlockCoverage":true},{"functionName":"get$isPaused","ranges":[{"startOffset":516556,"endOffset":516607,"count":1}],"isBlockCoverage":true},{"functionName":"_cancel$0","ranges":[{"startOffset":516624,"endOffset":516954,"count":1}],"isBlockCoverage":true},{"functionName":"_add$1","ranges":[{"startOffset":516968,"endOffset":517413,"count":1},{"startOffset":517222,"endOffset":517229,"count":0},{"startOffset":517281,"endOffset":517407,"count":0}],"isBlockCoverage":true},{"functionName":"_async$_addError$2","ranges":[{"startOffset":517439,"endOffset":517688,"count":0}],"isBlockCoverage":false},{"functionName":"_close$0","ranges":[{"startOffset":517704,"endOffset":518023,"count":0}],"isBlockCoverage":false},{"functionName":"_onPause$0","ranges":[{"startOffset":518041,"endOffset":518106,"count":0}],"isBlockCoverage":false},{"functionName":"_onResume$0","ranges":[{"startOffset":518125,"endOffset":518190,"count":0}],"isBlockCoverage":false},{"functionName":"_onCancel$0","ranges":[{"startOffset":518209,"endOffset":518293,"count":1}],"isBlockCoverage":true},{"functionName":"_addPending$1","ranges":[{"startOffset":518314,"endOffset":518843,"count":0}],"isBlockCoverage":false},{"functionName":"_sendData$1","ranges":[{"startOffset":518862,"endOffset":519379,"count":1}],"isBlockCoverage":true},{"functionName":"_sendError$2","ranges":[{"startOffset":519399,"endOffset":520116,"count":0}],"isBlockCoverage":false},{"functionName":"_sendDone$0","ranges":[{"startOffset":520135,"endOffset":520625,"count":0}],"isBlockCoverage":false},{"functionName":"_guardCallback$1","ranges":[{"startOffset":520649,"endOffset":520983,"count":1}],"isBlockCoverage":true},{"functionName":"_checkState$1","ranges":[{"startOffset":521004,"endOffset":522070,"count":1},{"startOffset":521160,"endOffset":521231,"count":0},{"startOffset":521271,"endOffset":521474,"count":0},{"startOffset":521848,"endOffset":521892,"count":0},{"startOffset":522010,"endOffset":522021,"count":0},{"startOffset":522031,"endOffset":522064,"count":0}],"isBlockCoverage":true},{"functionName":"set$_onData","ranges":[{"startOffset":522089,"endOffset":522219,"count":1}],"isBlockCoverage":true},{"functionName":"set$_onDone","ranges":[{"startOffset":522238,"endOffset":522321,"count":1}],"isBlockCoverage":true},{"functionName":"set$_pending","ranges":[{"startOffset":522341,"endOffset":522487,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":522629,"endOffset":523285,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":523388,"endOffset":523626,"count":0}],"isBlockCoverage":false},{"functionName":"listen$4$cancelOnError$onDone$onError","ranges":[{"startOffset":523724,"endOffset":523966,"count":1}],"isBlockCoverage":true},{"functionName":"listen$2$onDone","ranges":[{"startOffset":523989,"endOffset":524106,"count":1}],"isBlockCoverage":true},{"functionName":"listen$3$onDone$onError","ranges":[{"startOffset":524137,"endOffset":524266,"count":1}],"isBlockCoverage":true},{"functionName":"listen$1","ranges":[{"startOffset":524282,"endOffset":524389,"count":1}],"isBlockCoverage":true},{"functionName":"_createSubscription$4","ranges":[{"startOffset":524418,"endOffset":524671,"count":0}],"isBlockCoverage":false},{"functionName":"_createSubscription$4","ranges":[{"startOffset":524743,"endOffset":525243,"count":1},{"startOffset":524958,"endOffset":525035,"count":0}],"isBlockCoverage":true},{"functionName":"get$isEmpty","ranges":[{"startOffset":525307,"endOffset":525370,"count":1}],"isBlockCoverage":true},{"functionName":"handleNext$1","ranges":[{"startOffset":525390,"endOffset":526251,"count":0}],"isBlockCoverage":false},{"functionName":"set$_async$_iterator","ranges":[{"startOffset":526279,"endOffset":526387,"count":0}],"isBlockCoverage":false},{"functionName":"set$next","ranges":[{"startOffset":526439,"endOffset":526521,"count":0}],"isBlockCoverage":false},{"functionName":"get$next","ranges":[{"startOffset":526537,"endOffset":526579,"count":0}],"isBlockCoverage":false},{"functionName":"perform$1","ranges":[{"startOffset":526631,"endOffset":526743,"count":0}],"isBlockCoverage":false},{"functionName":"perform$1","ranges":[{"startOffset":526796,"endOffset":526880,"count":0}],"isBlockCoverage":false},{"functionName":"perform$1","ranges":[{"startOffset":526932,"endOffset":526988,"count":0}],"isBlockCoverage":false},{"functionName":"get$next","ranges":[{"startOffset":527004,"endOffset":527041,"count":0}],"isBlockCoverage":false},{"functionName":"set$next","ranges":[{"startOffset":527057,"endOffset":527147,"count":0}],"isBlockCoverage":false},{"functionName":"schedule$1","ranges":[{"startOffset":527227,"endOffset":527689,"count":1},{"startOffset":527390,"endOffset":527397,"count":0},{"startOffset":527491,"endOffset":527576,"count":0}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":527757,"endOffset":527933,"count":1},{"startOffset":527889,"endOffset":527932,"count":0}],"isBlockCoverage":true},{"functionName":"get$isEmpty","ranges":[{"startOffset":528011,"endOffset":528074,"count":0}],"isBlockCoverage":false},{"functionName":"add$1","ranges":[{"startOffset":528087,"endOffset":528355,"count":0}],"isBlockCoverage":false},{"functionName":"handleNext$1","ranges":[{"startOffset":528375,"endOffset":528743,"count":0}],"isBlockCoverage":false},{"functionName":"get$isPaused","ranges":[{"startOffset":528809,"endOffset":528858,"count":0}],"isBlockCoverage":false},{"functionName":"_schedule$0","ranges":[{"startOffset":528877,"endOffset":529080,"count":0}],"isBlockCoverage":false},{"functionName":"onData$1","ranges":[{"startOffset":529096,"endOffset":529175,"count":0}],"isBlockCoverage":false},{"functionName":"onError$1","ranges":[{"startOffset":529192,"endOffset":529224,"count":0}],"isBlockCoverage":false},{"functionName":"onDone$1","ranges":[{"startOffset":529240,"endOffset":529332,"count":0}],"isBlockCoverage":false},{"functionName":"pause$1","ranges":[{"startOffset":529347,"endOffset":529401,"count":0}],"isBlockCoverage":false},{"functionName":"pause$0","ranges":[{"startOffset":529416,"endOffset":529467,"count":0}],"isBlockCoverage":false},{"functionName":"resume$0","ranges":[{"startOffset":529483,"endOffset":529661,"count":0}],"isBlockCoverage":false},{"functionName":"cancel$0","ranges":[{"startOffset":529677,"endOffset":529737,"count":0}],"isBlockCoverage":false},{"functionName":"_sendDone$0","ranges":[{"startOffset":529756,"endOffset":530019,"count":0}],"isBlockCoverage":false},{"functionName":"set$_onDone","ranges":[{"startOffset":530038,"endOffset":530121,"count":0}],"isBlockCoverage":false},{"functionName":"get$isBroadcast","ranges":[{"startOffset":530245,"endOffset":530282,"count":0}],"isBlockCoverage":false},{"functionName":"listen$4$cancelOnError$onDone$onError","ranges":[{"startOffset":530327,"endOffset":530684,"count":0}],"isBlockCoverage":false},{"functionName":"listen$3$onDone$onError","ranges":[{"startOffset":530715,"endOffset":530844,"count":0}],"isBlockCoverage":false},{"functionName":"listen$1","ranges":[{"startOffset":530860,"endOffset":530967,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":531027,"endOffset":531117,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":531203,"endOffset":531339,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":531418,"endOffset":531486,"count":1}],"isBlockCoverage":true},{"functionName":"toString$0","ranges":[{"startOffset":531582,"endOffset":531631,"count":0}],"isBlockCoverage":false},{"functionName":"handleUncaughtError$3","ranges":[{"startOffset":531905,"endOffset":532247,"count":0}],"isBlockCoverage":false},{"functionName":"registerCallback$1$2","ranges":[{"startOffset":532275,"endOffset":532668,"count":1}],"isBlockCoverage":true},{"functionName":"registerUnaryCallback$2$2","ranges":[{"startOffset":532701,"endOffset":533146,"count":1}],"isBlockCoverage":true},{"functionName":"registerBinaryCallback$3$2","ranges":[{"startOffset":533180,"endOffset":533670,"count":1}],"isBlockCoverage":true},{"functionName":"errorCallback$3","ranges":[{"startOffset":533693,"endOffset":534015,"count":0}],"isBlockCoverage":false},{"functionName":"get$_delegate","ranges":[{"startOffset":534130,"endOffset":534287,"count":1}],"isBlockCoverage":true},{"functionName":"get$errorZone","ranges":[{"startOffset":534308,"endOffset":534371,"count":1}],"isBlockCoverage":true},{"functionName":"runGuarded$1","ranges":[{"startOffset":534391,"endOffset":534691,"count":1},{"startOffset":534526,"endOffset":534685,"count":0}],"isBlockCoverage":true},{"functionName":"runUnaryGuarded$1$2","ranges":[{"startOffset":534718,"endOffset":535062,"count":1},{"startOffset":534897,"endOffset":535056,"count":0}],"isBlockCoverage":true},{"functionName":"runBinaryGuarded$2$3","ranges":[{"startOffset":535090,"endOffset":535511,"count":0}],"isBlockCoverage":false},{"functionName":"bindCallback$1$1","ranges":[{"startOffset":535535,"endOffset":535681,"count":1}],"isBlockCoverage":true},{"functionName":"bindUnaryCallback$2$1","ranges":[{"startOffset":535710,"endOffset":535907,"count":0}],"isBlockCoverage":false},{"functionName":"bindCallbackGuarded$1","ranges":[{"startOffset":535936,"endOffset":536091,"count":1}],"isBlockCoverage":true},{"functionName":"bindUnaryCallbackGuarded$1$1","ranges":[{"startOffset":536127,"endOffset":536303,"count":1}],"isBlockCoverage":true},{"functionName":"$index","ranges":[{"startOffset":536317,"endOffset":536623,"count":1}],"isBlockCoverage":true},{"functionName":"handleUncaughtError$2","ranges":[{"startOffset":536652,"endOffset":537041,"count":0}],"isBlockCoverage":false},{"functionName":"fork$2$specification$zoneValues","ranges":[{"startOffset":537080,"endOffset":537413,"count":1}],"isBlockCoverage":true},{"functionName":"run$1$1","ranges":[{"startOffset":537428,"endOffset":537846,"count":1}],"isBlockCoverage":true},{"functionName":"runUnary$2$2","ranges":[{"startOffset":537866,"endOffset":538372,"count":1}],"isBlockCoverage":true},{"functionName":"runBinary$3$3","ranges":[{"startOffset":538393,"endOffset":538980,"count":0}],"isBlockCoverage":false},{"functionName":"registerCallback$1$1","ranges":[{"startOffset":539008,"endOffset":539469,"count":1}],"isBlockCoverage":true},{"functionName":"registerUnaryCallback$2$1","ranges":[{"startOffset":539502,"endOffset":540015,"count":1}],"isBlockCoverage":true},{"functionName":"registerBinaryCallback$3$1","ranges":[{"startOffset":540049,"endOffset":540607,"count":1}],"isBlockCoverage":true},{"functionName":"errorCallback$2","ranges":[{"startOffset":540630,"endOffset":541146,"count":0}],"isBlockCoverage":false},{"functionName":"scheduleMicrotask$1","ranges":[{"startOffset":541173,"endOffset":541522,"count":1}],"isBlockCoverage":true},{"functionName":"createTimer$2","ranges":[{"startOffset":541543,"endOffset":541906,"count":1}],"isBlockCoverage":true},{"functionName":"print$1","ranges":[{"startOffset":541921,"endOffset":542216,"count":0}],"isBlockCoverage":false},{"functionName":"set$_run","ranges":[{"startOffset":542232,"endOffset":542315,"count":1}],"isBlockCoverage":true},{"functionName":"set$_runUnary","ranges":[{"startOffset":542336,"endOffset":542434,"count":1}],"isBlockCoverage":true},{"functionName":"set$_runBinary","ranges":[{"startOffset":542456,"endOffset":542557,"count":1}],"isBlockCoverage":true},{"functionName":"set$_registerCallback","ranges":[{"startOffset":542586,"endOffset":542708,"count":1}],"isBlockCoverage":true},{"functionName":"set$_registerUnaryCallback","ranges":[{"startOffset":542742,"endOffset":542879,"count":1}],"isBlockCoverage":true},{"functionName":"set$_registerBinaryCallback","ranges":[{"startOffset":542914,"endOffset":543054,"count":1}],"isBlockCoverage":true},{"functionName":"set$_errorCallback","ranges":[{"startOffset":543080,"endOffset":543248,"count":1}],"isBlockCoverage":true},{"functionName":"set$_scheduleMicrotask","ranges":[{"startOffset":543278,"endOffset":543462,"count":1}],"isBlockCoverage":true},{"functionName":"set$_createTimer","ranges":[{"startOffset":543486,"endOffset":543666,"count":1}],"isBlockCoverage":true},{"functionName":"set$_createPeriodicTimer","ranges":[{"startOffset":543698,"endOffset":543908,"count":1}],"isBlockCoverage":true},{"functionName":"set$_print","ranges":[{"startOffset":543926,"endOffset":544053,"count":1}],"isBlockCoverage":true},{"functionName":"set$_fork","ranges":[{"startOffset":544070,"endOffset":544243,"count":1}],"isBlockCoverage":true},{"functionName":"set$_handleUncaughtError","ranges":[{"startOffset":544275,"endOffset":544455,"count":1}],"isBlockCoverage":true},{"functionName":"get$_run","ranges":[{"startOffset":544471,"endOffset":544513,"count":1}],"isBlockCoverage":true},{"functionName":"get$_runUnary","ranges":[{"startOffset":544534,"endOffset":544581,"count":1}],"isBlockCoverage":true},{"functionName":"get$_runBinary","ranges":[{"startOffset":544603,"endOffset":544651,"count":1}],"isBlockCoverage":true},{"functionName":"get$_registerCallback","ranges":[{"startOffset":544680,"endOffset":544735,"count":1}],"isBlockCoverage":true},{"functionName":"get$_registerUnaryCallback","ranges":[{"startOffset":544769,"endOffset":544829,"count":1}],"isBlockCoverage":true},{"functionName":"get$_registerBinaryCallback","ranges":[{"startOffset":544864,"endOffset":544925,"count":1}],"isBlockCoverage":true},{"functionName":"get$_errorCallback","ranges":[{"startOffset":544951,"endOffset":545003,"count":1}],"isBlockCoverage":true},{"functionName":"get$_scheduleMicrotask","ranges":[{"startOffset":545033,"endOffset":545089,"count":1}],"isBlockCoverage":true},{"functionName":"get$_createTimer","ranges":[{"startOffset":545113,"endOffset":545163,"count":1}],"isBlockCoverage":true},{"functionName":"get$_createPeriodicTimer","ranges":[{"startOffset":545195,"endOffset":545253,"count":1}],"isBlockCoverage":true},{"functionName":"get$_print","ranges":[{"startOffset":545271,"endOffset":545315,"count":1}],"isBlockCoverage":true},{"functionName":"get$_fork","ranges":[{"startOffset":545332,"endOffset":545375,"count":1}],"isBlockCoverage":true},{"functionName":"get$_handleUncaughtError","ranges":[{"startOffset":545407,"endOffset":545465,"count":1}],"isBlockCoverage":true},{"functionName":"get$parent","ranges":[{"startOffset":545483,"endOffset":545535,"count":1}],"isBlockCoverage":true},{"functionName":"get$_async$_map","ranges":[{"startOffset":545558,"endOffset":545607,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":545676,"endOffset":545752,"count":1}],"isBlockCoverage":true},{"functionName":"$signature","ranges":[{"startOffset":545770,"endOffset":545824,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":545898,"endOffset":546051,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":546069,"endOffset":546156,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":546232,"endOffset":546305,"count":1}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":546405,"endOffset":546531,"count":1}],"isBlockCoverage":true},{"functionName":"$signature","ranges":[{"startOffset":546549,"endOffset":546604,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":546673,"endOffset":546999,"count":0}],"isBlockCoverage":false},{"functionName":"get$_run","ranges":[{"startOffset":547066,"endOffset":547133,"count":1}],"isBlockCoverage":true},{"functionName":"get$_runUnary","ranges":[{"startOffset":547154,"endOffset":547226,"count":1}],"isBlockCoverage":true},{"functionName":"get$_runBinary","ranges":[{"startOffset":547248,"endOffset":547321,"count":1}],"isBlockCoverage":true},{"functionName":"get$_registerCallback","ranges":[{"startOffset":547350,"endOffset":547430,"count":1}],"isBlockCoverage":true},{"functionName":"get$_registerUnaryCallback","ranges":[{"startOffset":547464,"endOffset":547516,"count":1}],"isBlockCoverage":true},{"functionName":"get$_registerBinaryCallback","ranges":[{"startOffset":547551,"endOffset":547603,"count":1}],"isBlockCoverage":true},{"functionName":"get$_errorCallback","ranges":[{"startOffset":547629,"endOffset":547706,"count":1}],"isBlockCoverage":true},{"functionName":"get$_scheduleMicrotask","ranges":[{"startOffset":547736,"endOffset":547817,"count":1}],"isBlockCoverage":true},{"functionName":"get$_createTimer","ranges":[{"startOffset":547841,"endOffset":547916,"count":1}],"isBlockCoverage":true},{"functionName":"get$_createPeriodicTimer","ranges":[{"startOffset":547948,"endOffset":548000,"count":1}],"isBlockCoverage":true},{"functionName":"get$_print","ranges":[{"startOffset":548018,"endOffset":548087,"count":1}],"isBlockCoverage":true},{"functionName":"get$_fork","ranges":[{"startOffset":548104,"endOffset":548172,"count":1}],"isBlockCoverage":true},{"functionName":"get$_handleUncaughtError","ranges":[{"startOffset":548204,"endOffset":548256,"count":1}],"isBlockCoverage":true},{"functionName":"get$parent","ranges":[{"startOffset":548274,"endOffset":548312,"count":1}],"isBlockCoverage":true},{"functionName":"get$_async$_map","ranges":[{"startOffset":548335,"endOffset":548395,"count":0}],"isBlockCoverage":false},{"functionName":"get$_delegate","ranges":[{"startOffset":548416,"endOffset":548585,"count":0}],"isBlockCoverage":false},{"functionName":"get$errorZone","ranges":[{"startOffset":548606,"endOffset":548643,"count":1}],"isBlockCoverage":true},{"functionName":"runGuarded$1","ranges":[{"startOffset":548663,"endOffset":549139,"count":0}],"isBlockCoverage":false},{"functionName":"runUnaryGuarded$1$2","ranges":[{"startOffset":549166,"endOffset":549689,"count":1},{"startOffset":549479,"endOffset":549683,"count":0}],"isBlockCoverage":true},{"functionName":"runBinaryGuarded$2$3","ranges":[{"startOffset":549717,"endOffset":550324,"count":0}],"isBlockCoverage":false},{"functionName":"bindCallback$1$1","ranges":[{"startOffset":550348,"endOffset":550461,"count":0}],"isBlockCoverage":false},{"functionName":"bindCallbackGuarded$1","ranges":[{"startOffset":550490,"endOffset":550604,"count":0}],"isBlockCoverage":false},{"functionName":"bindUnaryCallbackGuarded$1$1","ranges":[{"startOffset":550640,"endOffset":550766,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":550780,"endOffset":550823,"count":0}],"isBlockCoverage":false},{"functionName":"handleUncaughtError$2","ranges":[{"startOffset":550852,"endOffset":550983,"count":0}],"isBlockCoverage":false},{"functionName":"fork$2$specification$zoneValues","ranges":[{"startOffset":551022,"endOffset":551136,"count":1}],"isBlockCoverage":true},{"functionName":"run$1$1","ranges":[{"startOffset":551151,"endOffset":551331,"count":0}],"isBlockCoverage":false},{"functionName":"runUnary$2$2","ranges":[{"startOffset":551351,"endOffset":551608,"count":1},{"startOffset":551538,"endOffset":551607,"count":0}],"isBlockCoverage":true},{"functionName":"runBinary$3$3","ranges":[{"startOffset":551629,"endOffset":551954,"count":0}],"isBlockCoverage":false},{"functionName":"registerCallback$1$1","ranges":[{"startOffset":551982,"endOffset":552047,"count":1}],"isBlockCoverage":true},{"functionName":"registerUnaryCallback$2$1","ranges":[{"startOffset":552080,"endOffset":552178,"count":1}],"isBlockCoverage":true},{"functionName":"registerBinaryCallback$3$1","ranges":[{"startOffset":552212,"endOffset":552328,"count":1}],"isBlockCoverage":true},{"functionName":"errorCallback$2","ranges":[{"startOffset":552351,"endOffset":552448,"count":0}],"isBlockCoverage":false},{"functionName":"scheduleMicrotask$1","ranges":[{"startOffset":552475,"endOffset":552575,"count":1}],"isBlockCoverage":true},{"functionName":"createTimer$2","ranges":[{"startOffset":552596,"endOffset":552701,"count":0}],"isBlockCoverage":false},{"functionName":"print$1","ranges":[{"startOffset":552716,"endOffset":552768,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":552835,"endOffset":552902,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":552920,"endOffset":552974,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":553048,"endOffset":553112,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":553210,"endOffset":553327,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":553345,"endOffset":553400,"count":0}],"isBlockCoverage":false},{"functionName":"call$5","ranges":[{"startOffset":553453,"endOffset":554320,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":554389,"endOffset":554447,"count":0}],"isBlockCoverage":false},{"functionName":"get$isEmpty","ranges":[{"startOffset":554466,"endOffset":554530,"count":0}],"isBlockCoverage":false},{"functionName":"get$keys","ranges":[{"startOffset":554546,"endOffset":554667,"count":0}],"isBlockCoverage":false},{"functionName":"containsKey$1","ranges":[{"startOffset":554688,"endOffset":555100,"count":1},{"startOffset":554860,"endOffset":554882,"count":0},{"startOffset":554924,"endOffset":554953,"count":0},{"startOffset":554955,"endOffset":555048,"count":0}],"isBlockCoverage":true},{"functionName":"_containsKey$1","ranges":[{"startOffset":555122,"endOffset":555308,"count":1}],"isBlockCoverage":true},{"functionName":"$index","ranges":[{"startOffset":555322,"endOffset":555803,"count":1},{"startOffset":555403,"endOffset":555425,"count":0},{"startOffset":555427,"endOffset":555567,"count":0},{"startOffset":555600,"endOffset":555629,"count":0},{"startOffset":555631,"endOffset":555759,"count":0}],"isBlockCoverage":true},{"functionName":"_get$1","ranges":[{"startOffset":555817,"endOffset":556095,"count":1},{"startOffset":555929,"endOffset":555941,"count":0}],"isBlockCoverage":true},{"functionName":"$indexSet","ranges":[{"startOffset":556112,"endOffset":556762,"count":1},{"startOffset":556316,"endOffset":556338,"count":0},{"startOffset":556340,"endOffset":556504,"count":0},{"startOffset":556537,"endOffset":556566,"count":0},{"startOffset":556568,"endOffset":556717,"count":0}],"isBlockCoverage":true},{"functionName":"_set$2","ranges":[{"startOffset":556776,"endOffset":557585,"count":1},{"startOffset":557442,"endOffset":557571,"count":0}],"isBlockCoverage":true},{"functionName":"remove$1","ranges":[{"startOffset":557601,"endOffset":557678,"count":0}],"isBlockCoverage":false},{"functionName":"_remove$1","ranges":[{"startOffset":557695,"endOffset":558203,"count":0}],"isBlockCoverage":false},{"functionName":"forEach$1","ranges":[{"startOffset":558220,"endOffset":558693,"count":0}],"isBlockCoverage":false},{"functionName":"_computeKeys$0","ranges":[{"startOffset":558715,"endOffset":560019,"count":0}],"isBlockCoverage":false},{"functionName":"_addHashTableEntry$3","ranges":[{"startOffset":560047,"endOffset":560344,"count":0}],"isBlockCoverage":false},{"functionName":"_computeHashCode$1","ranges":[{"startOffset":560370,"endOffset":560439,"count":1}],"isBlockCoverage":true},{"functionName":"_getBucket$2","ranges":[{"startOffset":560459,"endOffset":560537,"count":1}],"isBlockCoverage":true},{"functionName":"_findBucketIndex$2","ranges":[{"startOffset":560563,"endOffset":560802,"count":1},{"startOffset":560779,"endOffset":560801,"count":0}],"isBlockCoverage":true},{"functionName":"_computeHashCode$1","ranges":[{"startOffset":560867,"endOffset":560937,"count":1}],"isBlockCoverage":true},{"functionName":"_findBucketIndex$2","ranges":[{"startOffset":560963,"endOffset":561255,"count":1},{"startOffset":561057,"endOffset":561254,"count":0}],"isBlockCoverage":true},{"functionName":"get$length","ranges":[{"startOffset":561315,"endOffset":561378,"count":0}],"isBlockCoverage":false},{"functionName":"get$isEmpty","ranges":[{"startOffset":561397,"endOffset":561466,"count":0}],"isBlockCoverage":false},{"functionName":"get$iterator","ranges":[{"startOffset":561486,"endOffset":561640,"count":0}],"isBlockCoverage":false},{"functionName":"contains$1","ranges":[{"startOffset":561658,"endOffset":561733,"count":0}],"isBlockCoverage":false},{"functionName":"get$current","ranges":[{"startOffset":561794,"endOffset":561852,"count":0}],"isBlockCoverage":false},{"functionName":"moveNext$0","ranges":[{"startOffset":561870,"endOffset":562335,"count":0}],"isBlockCoverage":false},{"functionName":"set$_collection$_current","ranges":[{"startOffset":562367,"endOffset":562468,"count":0}],"isBlockCoverage":false},{"functionName":"_newSet$0","ranges":[{"startOffset":562542,"endOffset":562647,"count":1}],"isBlockCoverage":true},{"functionName":"get$iterator","ranges":[{"startOffset":562667,"endOffset":562927,"count":1}],"isBlockCoverage":true},{"functionName":"get$length","ranges":[{"startOffset":562945,"endOffset":563003,"count":0}],"isBlockCoverage":false},{"functionName":"get$isEmpty","ranges":[{"startOffset":563022,"endOffset":563086,"count":0}],"isBlockCoverage":false},{"functionName":"contains$1","ranges":[{"startOffset":563104,"endOffset":563663,"count":1},{"startOffset":563301,"endOffset":563382,"count":0},{"startOffset":563533,"endOffset":563657,"count":0}],"isBlockCoverage":true},{"functionName":"_contains$1","ranges":[{"startOffset":563682,"endOffset":563883,"count":0}],"isBlockCoverage":false},{"functionName":"get$first","ranges":[{"startOffset":563900,"endOffset":564112,"count":0}],"isBlockCoverage":false},{"functionName":"get$last","ranges":[{"startOffset":564128,"endOffset":564339,"count":0}],"isBlockCoverage":false},{"functionName":"add$1","ranges":[{"startOffset":564352,"endOffset":565009,"count":1},{"startOffset":564713,"endOffset":565003,"count":0}],"isBlockCoverage":true},{"functionName":"_collection$_add$1","ranges":[{"startOffset":565035,"endOffset":565721,"count":0}],"isBlockCoverage":false},{"functionName":"remove$1","ranges":[{"startOffset":565737,"endOffset":566117,"count":1},{"startOffset":565819,"endOffset":565844,"count":0},{"startOffset":565854,"endOffset":565915,"count":0},{"startOffset":566060,"endOffset":566111,"count":0}],"isBlockCoverage":true},{"functionName":"_remove$1","ranges":[{"startOffset":566134,"endOffset":566619,"count":0}],"isBlockCoverage":false},{"functionName":"_addHashTableEntry$2","ranges":[{"startOffset":566647,"endOffset":566913,"count":1},{"startOffset":566808,"endOffset":566821,"count":0}],"isBlockCoverage":true},{"functionName":"_removeHashTableEntry$2","ranges":[{"startOffset":566944,"endOffset":567227,"count":1},{"startOffset":567033,"endOffset":567226,"count":0}],"isBlockCoverage":true},{"functionName":"_collection$_modified$0","ranges":[{"startOffset":567258,"endOffset":567366,"count":1}],"isBlockCoverage":true},{"functionName":"_collection$_newLinkedCell$1","ranges":[{"startOffset":567402,"endOffset":567925,"count":1}],"isBlockCoverage":true},{"functionName":"_unlinkCell$1","ranges":[{"startOffset":567946,"endOffset":568524,"count":0}],"isBlockCoverage":false},{"functionName":"_computeHashCode$1","ranges":[{"startOffset":568550,"endOffset":568627,"count":0}],"isBlockCoverage":false},{"functionName":"_findBucketIndex$2","ranges":[{"startOffset":568653,"endOffset":568906,"count":0}],"isBlockCoverage":false},{"functionName":"get$current","ranges":[{"startOffset":569034,"endOffset":569092,"count":0}],"isBlockCoverage":false},{"functionName":"moveNext$0","ranges":[{"startOffset":569110,"endOffset":569695,"count":1},{"startOffset":569258,"endOffset":569316,"count":0},{"startOffset":569475,"endOffset":569681,"count":0}],"isBlockCoverage":true},{"functionName":"set$_collection$_current","ranges":[{"startOffset":569727,"endOffset":569828,"count":1}],"isBlockCoverage":true},{"functionName":"get$length","ranges":[{"startOffset":569909,"endOffset":570001,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":570015,"endOffset":570118,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":570183,"endOffset":570273,"count":1}],"isBlockCoverage":true},{"functionName":"call$2","ranges":[{"startOffset":570402,"endOffset":570492,"count":0}],"isBlockCoverage":false},{"functionName":"get$iterator","ranges":[{"startOffset":570649,"endOffset":570808,"count":0}],"isBlockCoverage":false},{"functionName":"elementAt$1","ranges":[{"startOffset":570827,"endOffset":570903,"count":0}],"isBlockCoverage":false},{"functionName":"get$isEmpty","ranges":[{"startOffset":570922,"endOffset":570994,"count":0}],"isBlockCoverage":false},{"functionName":"get$first","ranges":[{"startOffset":571011,"endOffset":571186,"count":0}],"isBlockCoverage":false},{"functionName":"get$last","ranges":[{"startOffset":571202,"endOffset":571405,"count":0}],"isBlockCoverage":false},{"functionName":"map$1$1","ranges":[{"startOffset":571420,"endOffset":571672,"count":0}],"isBlockCoverage":false},{"functionName":"toList$1$growable","ranges":[{"startOffset":571697,"endOffset":572067,"count":0}],"isBlockCoverage":false},{"functionName":"toList$0","ranges":[{"startOffset":572083,"endOffset":572164,"count":0}],"isBlockCoverage":false},{"functionName":"toSet$0","ranges":[{"startOffset":572179,"endOffset":572441,"count":0}],"isBlockCoverage":false},{"functionName":"remove$1","ranges":[{"startOffset":572457,"endOffset":572717,"count":0}],"isBlockCoverage":false},{"functionName":"_closeGap$2","ranges":[{"startOffset":572736,"endOffset":573112,"count":0}],"isBlockCoverage":false},{"functionName":"fillRange$3","ranges":[{"startOffset":573131,"endOffset":573410,"count":1}],"isBlockCoverage":true},{"functionName":"toString$0","ranges":[{"startOffset":573428,"endOffset":573524,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":573616,"endOffset":573883,"count":0}],"isBlockCoverage":false},{"functionName":"cast$2$0","ranges":[{"startOffset":573949,"endOffset":574109,"count":1}],"isBlockCoverage":true},{"functionName":"forEach$1","ranges":[{"startOffset":574126,"endOffset":574420,"count":0}],"isBlockCoverage":false},{"functionName":"map$2$1","ranges":[{"startOffset":574435,"endOffset":574940,"count":1}],"isBlockCoverage":true},{"functionName":"containsKey$1","ranges":[{"startOffset":574961,"endOffset":575031,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":575049,"endOffset":575132,"count":0}],"isBlockCoverage":false},{"functionName":"get$isEmpty","ranges":[{"startOffset":575151,"endOffset":575235,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":575253,"endOffset":575314,"count":0}],"isBlockCoverage":false},{"functionName":"remove$1","ranges":[{"startOffset":575389,"endOffset":575497,"count":0}],"isBlockCoverage":false},{"functionName":"cast$2$0","ranges":[{"startOffset":575543,"endOffset":575614,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":575628,"endOffset":575691,"count":0}],"isBlockCoverage":false},{"functionName":"containsKey$1","ranges":[{"startOffset":575712,"endOffset":575776,"count":0}],"isBlockCoverage":false},{"functionName":"forEach$1","ranges":[{"startOffset":575793,"endOffset":575906,"count":1}],"isBlockCoverage":true},{"functionName":"get$isEmpty","ranges":[{"startOffset":575925,"endOffset":576003,"count":1}],"isBlockCoverage":true},{"functionName":"get$length","ranges":[{"startOffset":576021,"endOffset":576098,"count":0}],"isBlockCoverage":false},{"functionName":"get$keys","ranges":[{"startOffset":576114,"endOffset":576167,"count":0}],"isBlockCoverage":false},{"functionName":"remove$1","ranges":[{"startOffset":576183,"endOffset":576248,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":576266,"endOffset":576323,"count":0}],"isBlockCoverage":false},{"functionName":"map$2$1","ranges":[{"startOffset":576338,"endOffset":576514,"count":1}],"isBlockCoverage":true},{"functionName":"cast$2$0","ranges":[{"startOffset":576587,"endOffset":576753,"count":0}],"isBlockCoverage":false},{"functionName":"get$iterator","ranges":[{"startOffset":576805,"endOffset":576990,"count":0}],"isBlockCoverage":false},{"functionName":"get$isEmpty","ranges":[{"startOffset":577009,"endOffset":577068,"count":1}],"isBlockCoverage":true},{"functionName":"get$length","ranges":[{"startOffset":577086,"endOffset":577176,"count":0}],"isBlockCoverage":false},{"functionName":"get$first","ranges":[{"startOffset":577193,"endOffset":577451,"count":0}],"isBlockCoverage":false},{"functionName":"get$last","ranges":[{"startOffset":577467,"endOffset":577803,"count":0}],"isBlockCoverage":false},{"functionName":"elementAt$1","ranges":[{"startOffset":577822,"endOffset":578227,"count":0}],"isBlockCoverage":false},{"functionName":"clear$0","ranges":[{"startOffset":578242,"endOffset":578565,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":578583,"endOffset":578668,"count":0}],"isBlockCoverage":false},{"functionName":"removeFirst$0","ranges":[{"startOffset":578689,"endOffset":579148,"count":1},{"startOffset":578802,"endOffset":578860,"count":0},{"startOffset":578955,"endOffset":578978,"count":0}],"isBlockCoverage":true},{"functionName":"_collection$_add$1","ranges":[{"startOffset":579174,"endOffset":580057,"count":1},{"startOffset":579524,"endOffset":580017,"count":0}],"isBlockCoverage":true},{"functionName":"set$_table","ranges":[{"startOffset":580075,"endOffset":580163,"count":1}],"isBlockCoverage":true},{"functionName":"get$current","ranges":[{"startOffset":580240,"endOffset":580298,"count":0}],"isBlockCoverage":false},{"functionName":"moveNext$0","ranges":[{"startOffset":580316,"endOffset":580875,"count":0}],"isBlockCoverage":false},{"functionName":"set$_collection$_current","ranges":[{"startOffset":580907,"endOffset":581008,"count":0}],"isBlockCoverage":false},{"functionName":"get$isEmpty","ranges":[{"startOffset":581078,"endOffset":581139,"count":0}],"isBlockCoverage":false},{"functionName":"map$1$1","ranges":[{"startOffset":581154,"endOffset":581394,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":581412,"endOffset":581497,"count":0}],"isBlockCoverage":false},{"functionName":"every$1","ranges":[{"startOffset":581512,"endOffset":581772,"count":0}],"isBlockCoverage":false},{"functionName":"get$first","ranges":[{"startOffset":581789,"endOffset":581974,"count":0}],"isBlockCoverage":false},{"functionName":"get$last","ranges":[{"startOffset":581990,"endOffset":582256,"count":0}],"isBlockCoverage":false},{"functionName":"toSet$0","ranges":[{"startOffset":582456,"endOffset":582553,"count":1}],"isBlockCoverage":true},{"functionName":"get$isEmpty","ranges":[{"startOffset":582572,"endOffset":582636,"count":0}],"isBlockCoverage":false},{"functionName":"addAll$1","ranges":[{"startOffset":582652,"endOffset":582872,"count":1},{"startOffset":582834,"endOffset":582866,"count":0}],"isBlockCoverage":true},{"functionName":"union$1","ranges":[{"startOffset":582887,"endOffset":583059,"count":1}],"isBlockCoverage":true},{"functionName":"toList$1$growable","ranges":[{"startOffset":583084,"endOffset":583579,"count":1},{"startOffset":583450,"endOffset":583552,"count":0}],"isBlockCoverage":true},{"functionName":"toList$0","ranges":[{"startOffset":583595,"endOffset":583676,"count":1}],"isBlockCoverage":true},{"functionName":"map$1$1","ranges":[{"startOffset":583691,"endOffset":583931,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":583949,"endOffset":584034,"count":0}],"isBlockCoverage":false},{"functionName":"where$1","ranges":[{"startOffset":584049,"endOffset":584214,"count":1}],"isBlockCoverage":true},{"functionName":"fold$1$2","ranges":[{"startOffset":584230,"endOffset":584642,"count":0}],"isBlockCoverage":false},{"functionName":"every$1","ranges":[{"startOffset":584657,"endOffset":584988,"count":0}],"isBlockCoverage":false},{"functionName":"any$1","ranges":[{"startOffset":585001,"endOffset":585340,"count":0}],"isBlockCoverage":false},{"functionName":"get$first","ranges":[{"startOffset":585357,"endOffset":585627,"count":0}],"isBlockCoverage":false},{"functionName":"get$last","ranges":[{"startOffset":585643,"endOffset":585994,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":586272,"endOffset":586605,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":586623,"endOffset":586845,"count":0}],"isBlockCoverage":false},{"functionName":"get$isEmpty","ranges":[{"startOffset":586864,"endOffset":586925,"count":0}],"isBlockCoverage":false},{"functionName":"get$keys","ranges":[{"startOffset":586941,"endOffset":587091,"count":0}],"isBlockCoverage":false},{"functionName":"containsKey$1","ranges":[{"startOffset":587112,"endOffset":587355,"count":0}],"isBlockCoverage":false},{"functionName":"remove$1","ranges":[{"startOffset":587371,"endOffset":587528,"count":0}],"isBlockCoverage":false},{"functionName":"forEach$1","ranges":[{"startOffset":587545,"endOffset":588211,"count":0}],"isBlockCoverage":false},{"functionName":"get$_upgradedMap","ranges":[{"startOffset":588235,"endOffset":588325,"count":0}],"isBlockCoverage":false},{"functionName":"_convert$_computeKeys$0","ranges":[{"startOffset":588356,"endOffset":588651,"count":0}],"isBlockCoverage":false},{"functionName":"_upgrade$0","ranges":[{"startOffset":588669,"endOffset":589287,"count":0}],"isBlockCoverage":false},{"functionName":"_process$1","ranges":[{"startOffset":589305,"endOffset":589541,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":589601,"endOffset":589681,"count":0}],"isBlockCoverage":false},{"functionName":"elementAt$1","ranges":[{"startOffset":589700,"endOffset":590025,"count":0}],"isBlockCoverage":false},{"functionName":"get$iterator","ranges":[{"startOffset":590045,"endOffset":590376,"count":0}],"isBlockCoverage":false},{"functionName":"contains$1","ranges":[{"startOffset":590394,"endOffset":590464,"count":0}],"isBlockCoverage":false},{"functionName":"encode$1","ranges":[{"startOffset":590513,"endOffset":590588,"count":0}],"isBlockCoverage":false},{"functionName":"convert$1","ranges":[{"startOffset":590649,"endOffset":591298,"count":0}],"isBlockCoverage":false},{"functionName":"normalize$3","ranges":[{"startOffset":591384,"endOffset":595104,"count":0}],"isBlockCoverage":false},{"functionName":"decode$2$reviver","ranges":[{"startOffset":595311,"endOffset":595497,"count":0}],"isBlockCoverage":false},{"functionName":"get$decoder","ranges":[{"startOffset":595516,"endOffset":595567,"count":0}],"isBlockCoverage":false},{"functionName":"get$encoder","ranges":[{"startOffset":595650,"endOffset":595698,"count":0}],"isBlockCoverage":false},{"functionName":"convert$1","ranges":[{"startOffset":595749,"endOffset":596529,"count":0}],"isBlockCoverage":false},{"functionName":"_writeSurrogate$2","ranges":[{"startOffset":596589,"endOffset":598037,"count":0}],"isBlockCoverage":false},{"functionName":"_fillBuffer$3","ranges":[{"startOffset":598058,"endOffset":599908,"count":0}],"isBlockCoverage":false},{"functionName":"convert$1","ranges":[{"startOffset":599959,"endOffset":601081,"count":0}],"isBlockCoverage":false},{"functionName":"flush$2","ranges":[{"startOffset":601131,"endOffset":601378,"count":0}],"isBlockCoverage":false},{"functionName":"convert$3","ranges":[{"startOffset":601395,"endOffset":605325,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":605392,"endOffset":605547,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":605567,"endOffset":605692,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":605710,"endOffset":606328,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":606397,"endOffset":606550,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":606570,"endOffset":606648,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":606666,"endOffset":607284,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":607348,"endOffset":607623,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":607707,"endOffset":607790,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":607891,"endOffset":608058,"count":0}],"isBlockCoverage":false},{"functionName":"get$message","ranges":[{"startOffset":608077,"endOffset":608130,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":608186,"endOffset":608236,"count":0}],"isBlockCoverage":false},{"functionName":"get$_errorName","ranges":[{"startOffset":608294,"endOffset":608378,"count":0}],"isBlockCoverage":false},{"functionName":"get$_errorExplanation","ranges":[{"startOffset":608407,"endOffset":608442,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":608460,"endOffset":608982,"count":0}],"isBlockCoverage":false},{"functionName":"get$message","ranges":[{"startOffset":609001,"endOffset":609054,"count":0}],"isBlockCoverage":false},{"functionName":"get$_errorName","ranges":[{"startOffset":609109,"endOffset":609154,"count":0}],"isBlockCoverage":false},{"functionName":"get$_errorExplanation","ranges":[{"startOffset":609183,"endOffset":609833,"count":0}],"isBlockCoverage":false},{"functionName":"get$_errorName","ranges":[{"startOffset":609888,"endOffset":609933,"count":0}],"isBlockCoverage":false},{"functionName":"get$_errorExplanation","ranges":[{"startOffset":609962,"endOffset":610401,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":610419,"endOffset":610471,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":610528,"endOffset":610602,"count":0}],"isBlockCoverage":false},{"functionName":"get$message","ranges":[{"startOffset":610621,"endOffset":610674,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":610733,"endOffset":610859,"count":0}],"isBlockCoverage":false},{"functionName":"get$message","ranges":[{"startOffset":610878,"endOffset":610931,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":610982,"endOffset":611044,"count":0}],"isBlockCoverage":false},{"functionName":"get$message","ranges":[{"startOffset":611063,"endOffset":611116,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":611184,"endOffset":611413,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":611470,"endOffset":611519,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":611595,"endOffset":611645,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":611728,"endOffset":611927,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":611978,"endOffset":612040,"count":0}],"isBlockCoverage":false},{"functionName":"get$message","ranges":[{"startOffset":612080,"endOffset":612133,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":612189,"endOffset":614795,"count":0}],"isBlockCoverage":false},{"functionName":"get$message","ranges":[{"startOffset":614835,"endOffset":614888,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":614932,"endOffset":615559,"count":0}],"isBlockCoverage":false},{"functionName":"$indexSet","ranges":[{"startOffset":615576,"endOffset":616073,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":616091,"endOffset":616147,"count":0}],"isBlockCoverage":false},{"functionName":"map$1$1","ranges":[{"startOffset":616246,"endOffset":616443,"count":0}],"isBlockCoverage":false},{"functionName":"where$1","ranges":[{"startOffset":616458,"endOffset":616647,"count":1}],"isBlockCoverage":true},{"functionName":"contains$1","ranges":[{"startOffset":616665,"endOffset":616856,"count":0}],"isBlockCoverage":false},{"functionName":"join$1","ranges":[{"startOffset":616870,"endOffset":617364,"count":0}],"isBlockCoverage":false},{"functionName":"join$0","ranges":[{"startOffset":617378,"endOffset":617446,"count":0}],"isBlockCoverage":false},{"functionName":"toList$1$growable","ranges":[{"startOffset":617471,"endOffset":617588,"count":0}],"isBlockCoverage":false},{"functionName":"toList$0","ranges":[{"startOffset":617604,"endOffset":617685,"count":0}],"isBlockCoverage":false},{"functionName":"toSet$0","ranges":[{"startOffset":617700,"endOffset":617819,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":617837,"endOffset":618062,"count":0}],"isBlockCoverage":false},{"functionName":"get$isEmpty","ranges":[{"startOffset":618081,"endOffset":618152,"count":0}],"isBlockCoverage":false},{"functionName":"skipWhile$1","ranges":[{"startOffset":618171,"endOffset":618368,"count":0}],"isBlockCoverage":false},{"functionName":"get$first","ranges":[{"startOffset":618385,"endOffset":618570,"count":0}],"isBlockCoverage":false},{"functionName":"get$last","ranges":[{"startOffset":618586,"endOffset":618852,"count":0}],"isBlockCoverage":false},{"functionName":"elementAt$1","ranges":[{"startOffset":618871,"endOffset":619282,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":619300,"endOffset":619386,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":619558,"endOffset":619650,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":619697,"endOffset":619779,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":619797,"endOffset":619837,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":619937,"endOffset":619992,"count":1}],"isBlockCoverage":true},{"functionName":"get$hashCode","ranges":[{"startOffset":620012,"endOffset":620079,"count":1}],"isBlockCoverage":true},{"functionName":"toString$0","ranges":[{"startOffset":620097,"endOffset":620193,"count":0}],"isBlockCoverage":false},{"functionName":"get$runtimeType","ranges":[{"startOffset":620216,"endOffset":620272,"count":0}],"isBlockCoverage":false},{"functionName":"toString","ranges":[{"startOffset":620288,"endOffset":620342,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":620552,"endOffset":620602,"count":0}],"isBlockCoverage":false},{"functionName":"get$elapsedMicroseconds","ranges":[{"startOffset":620687,"endOffset":621099,"count":0}],"isBlockCoverage":false},{"functionName":"get$iterator","ranges":[{"startOffset":621187,"endOffset":621252,"count":0}],"isBlockCoverage":false},{"functionName":"get$last","ranges":[{"startOffset":621268,"endOffset":621758,"count":0}],"isBlockCoverage":false},{"functionName":"get$current","ranges":[{"startOffset":621812,"endOffset":621867,"count":0}],"isBlockCoverage":false},{"functionName":"moveNext$0","ranges":[{"startOffset":621885,"endOffset":622720,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":622793,"endOffset":622848,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":622866,"endOffset":622962,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":623072,"endOffset":623205,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":623288,"endOffset":623421,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":623435,"endOffset":623493,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":623579,"endOffset":624044,"count":0}],"isBlockCoverage":false},{"functionName":"get$userInfo","ranges":[{"startOffset":624111,"endOffset":624158,"count":0}],"isBlockCoverage":false},{"functionName":"get$host","ranges":[{"startOffset":624174,"endOffset":624399,"count":0}],"isBlockCoverage":false},{"functionName":"get$port","ranges":[{"startOffset":624415,"endOffset":624549,"count":0}],"isBlockCoverage":false},{"functionName":"get$query","ranges":[{"startOffset":624566,"endOffset":624647,"count":0}],"isBlockCoverage":false},{"functionName":"get$fragment","ranges":[{"startOffset":624667,"endOffset":624751,"count":0}],"isBlockCoverage":false},{"functionName":"replace$2$path$pathSegments","ranges":[{"startOffset":624786,"endOffset":625788,"count":0}],"isBlockCoverage":false},{"functionName":"replace$1$path","ranges":[{"startOffset":625810,"endOffset":625913,"count":0}],"isBlockCoverage":false},{"functionName":"replace$1$pathSegments","ranges":[{"startOffset":625943,"endOffset":626062,"count":0}],"isBlockCoverage":false},{"functionName":"get$pathSegments","ranges":[{"startOffset":626086,"endOffset":626770,"count":0}],"isBlockCoverage":false},{"functionName":"_mergePaths$2","ranges":[{"startOffset":626791,"endOffset":627849,"count":0}],"isBlockCoverage":false},{"functionName":"resolve$1","ranges":[{"startOffset":627866,"endOffset":627949,"count":0}],"isBlockCoverage":false},{"functionName":"resolveUri$1","ranges":[{"startOffset":627969,"endOffset":630843,"count":0}],"isBlockCoverage":false},{"functionName":"get$hasAuthority","ranges":[{"startOffset":630867,"endOffset":630918,"count":0}],"isBlockCoverage":false},{"functionName":"get$hasPort","ranges":[{"startOffset":630937,"endOffset":630988,"count":0}],"isBlockCoverage":false},{"functionName":"get$hasQuery","ranges":[{"startOffset":631008,"endOffset":631060,"count":0}],"isBlockCoverage":false},{"functionName":"get$hasFragment","ranges":[{"startOffset":631083,"endOffset":631138,"count":0}],"isBlockCoverage":false},{"functionName":"get$hasAbsolutePath","ranges":[{"startOffset":631165,"endOffset":631245,"count":0}],"isBlockCoverage":false},{"functionName":"toFilePath$0","ranges":[{"startOffset":631265,"endOffset":632530,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":632548,"endOffset":633376,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":633387,"endOffset":634982,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":635002,"endOffset":635163,"count":0}],"isBlockCoverage":false},{"functionName":"set$_pathSegments","ranges":[{"startOffset":635188,"endOffset":635287,"count":0}],"isBlockCoverage":false},{"functionName":"get$scheme","ranges":[{"startOffset":635320,"endOffset":635364,"count":0}],"isBlockCoverage":false},{"functionName":"get$path","ranges":[{"startOffset":635380,"endOffset":635430,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":635494,"endOffset":635608,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":635716,"endOffset":636051,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":636130,"endOffset":636243,"count":0}],"isBlockCoverage":false},{"functionName":"get$uri","ranges":[{"startOffset":636307,"endOffset":637019,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":637037,"endOffset":637234,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":637292,"endOffset":637344,"count":1}],"isBlockCoverage":true},{"functionName":"call$2","ranges":[{"startOffset":637420,"endOffset":637648,"count":1},{"startOffset":637523,"endOffset":637549,"count":0}],"isBlockCoverage":true},{"functionName":"call$3","ranges":[{"startOffset":637727,"endOffset":638030,"count":1},{"startOffset":637956,"endOffset":637983,"count":0}],"isBlockCoverage":true},{"functionName":"call$3","ranges":[{"startOffset":638109,"endOffset":638448,"count":1},{"startOffset":638374,"endOffset":638401,"count":0}],"isBlockCoverage":true},{"functionName":"get$hasAuthority","ranges":[{"startOffset":638525,"endOffset":638577,"count":0}],"isBlockCoverage":false},{"functionName":"get$hasPort","ranges":[{"startOffset":638596,"endOffset":638689,"count":0}],"isBlockCoverage":false},{"functionName":"get$hasQuery","ranges":[{"startOffset":638709,"endOffset":638780,"count":0}],"isBlockCoverage":false},{"functionName":"get$hasFragment","ranges":[{"startOffset":638803,"endOffset":638874,"count":0}],"isBlockCoverage":false},{"functionName":"get$_isFile","ranges":[{"startOffset":638893,"endOffset":639001,"count":0}],"isBlockCoverage":false},{"functionName":"get$_isHttp","ranges":[{"startOffset":639020,"endOffset":639128,"count":0}],"isBlockCoverage":false},{"functionName":"get$_isHttps","ranges":[{"startOffset":639148,"endOffset":639257,"count":0}],"isBlockCoverage":false},{"functionName":"get$hasAbsolutePath","ranges":[{"startOffset":639284,"endOffset":639381,"count":0}],"isBlockCoverage":false},{"functionName":"get$scheme","ranges":[{"startOffset":639399,"endOffset":640155,"count":0}],"isBlockCoverage":false},{"functionName":"get$userInfo","ranges":[{"startOffset":640175,"endOffset":640342,"count":0}],"isBlockCoverage":false},{"functionName":"get$host","ranges":[{"startOffset":640358,"endOffset":640500,"count":0}],"isBlockCoverage":false},{"functionName":"get$port","ranges":[{"startOffset":640516,"endOffset":640832,"count":0}],"isBlockCoverage":false},{"functionName":"get$path","ranges":[{"startOffset":640848,"endOffset":640958,"count":0}],"isBlockCoverage":false},{"functionName":"get$query","ranges":[{"startOffset":640975,"endOffset":641143,"count":0}],"isBlockCoverage":false},{"functionName":"get$fragment","ranges":[{"startOffset":641163,"endOffset":641320,"count":0}],"isBlockCoverage":false},{"functionName":"get$pathSegments","ranges":[{"startOffset":641344,"endOffset":642029,"count":1},{"startOffset":641575,"endOffset":641595,"count":0}],"isBlockCoverage":true},{"functionName":"_isPort$1","ranges":[{"startOffset":642046,"endOffset":642247,"count":0}],"isBlockCoverage":false},{"functionName":"removeFragment$0","ranges":[{"startOffset":642271,"endOffset":642611,"count":0}],"isBlockCoverage":false},{"functionName":"replace$2$path$pathSegments","ranges":[{"startOffset":642646,"endOffset":644260,"count":0}],"isBlockCoverage":false},{"functionName":"replace$1$path","ranges":[{"startOffset":644282,"endOffset":644385,"count":0}],"isBlockCoverage":false},{"functionName":"replace$1$pathSegments","ranges":[{"startOffset":644415,"endOffset":644534,"count":0}],"isBlockCoverage":false},{"functionName":"resolve$1","ranges":[{"startOffset":644551,"endOffset":644634,"count":0}],"isBlockCoverage":false},{"functionName":"resolveUri$1","ranges":[{"startOffset":644654,"endOffset":644839,"count":0}],"isBlockCoverage":false},{"functionName":"_simpleMerge$2","ranges":[{"startOffset":644861,"endOffset":648814,"count":0}],"isBlockCoverage":false},{"functionName":"toFilePath$0","ranges":[{"startOffset":648834,"endOffset":649861,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":649881,"endOffset":650033,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":650044,"endOffset":650236,"count":0}],"isBlockCoverage":false},{"functionName":"_toNonSimple$0","ranges":[{"startOffset":650258,"endOffset":650822,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":650840,"endOffset":650883,"count":0}],"isBlockCoverage":false},{"functionName":"get$message","ranges":[{"startOffset":650995,"endOffset":651052,"count":0}],"isBlockCoverage":false},{"functionName":"get$message","ranges":[{"startOffset":651137,"endOffset":651194,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":651247,"endOffset":651304,"count":0}],"isBlockCoverage":false},{"functionName":"get$message","ranges":[{"startOffset":651323,"endOffset":651380,"count":0}],"isBlockCoverage":false},{"functionName":"get$message","ranges":[{"startOffset":651432,"endOffset":651489,"count":0}],"isBlockCoverage":false},{"functionName":"addEventListener$3","ranges":[{"startOffset":651586,"endOffset":651788,"count":1}],"isBlockCoverage":true},{"functionName":"_addEventListener$3","ranges":[{"startOffset":651815,"endOffset":651995,"count":1}],"isBlockCoverage":true},{"functionName":"_removeEventListener$3","ranges":[{"startOffset":652025,"endOffset":652208,"count":1}],"isBlockCoverage":true},{"functionName":"get$origin","ranges":[{"startOffset":652315,"endOffset":652470,"count":1},{"startOffset":652399,"endOffset":652469,"count":0}],"isBlockCoverage":true},{"functionName":"toString$0","ranges":[{"startOffset":652488,"endOffset":652545,"count":0}],"isBlockCoverage":false},{"functionName":"get$message","ranges":[{"startOffset":652597,"endOffset":652654,"count":0}],"isBlockCoverage":false},{"functionName":"get$message","ranges":[{"startOffset":652716,"endOffset":652773,"count":0}],"isBlockCoverage":false},{"functionName":"addEventListener$3","ranges":[{"startOffset":652884,"endOffset":653127,"count":1}],"isBlockCoverage":true},{"functionName":"postMessage$1","ranges":[{"startOffset":653148,"endOffset":653318,"count":1}],"isBlockCoverage":true},{"functionName":"get$message","ranges":[{"startOffset":653406,"endOffset":653463,"count":0}],"isBlockCoverage":false},{"functionName":"get$message","ranges":[{"startOffset":653525,"endOffset":653582,"count":0}],"isBlockCoverage":false},{"functionName":"get$message","ranges":[{"startOffset":653637,"endOffset":653694,"count":0}],"isBlockCoverage":false},{"functionName":"get$message","ranges":[{"startOffset":653768,"endOffset":653825,"count":0}],"isBlockCoverage":false},{"functionName":"get$message","ranges":[{"startOffset":653889,"endOffset":653946,"count":0}],"isBlockCoverage":false},{"functionName":"get$isBroadcast","ranges":[{"startOffset":654044,"endOffset":654081,"count":0}],"isBlockCoverage":false},{"functionName":"listen$4$cancelOnError$onDone$onError","ranges":[{"startOffset":654126,"endOffset":654445,"count":1}],"isBlockCoverage":true},{"functionName":"listen$3$onDone$onError","ranges":[{"startOffset":654476,"endOffset":654605,"count":0}],"isBlockCoverage":false},{"functionName":"listen$1","ranges":[{"startOffset":654621,"endOffset":654728,"count":0}],"isBlockCoverage":false},{"functionName":"cancel$0","ranges":[{"startOffset":654791,"endOffset":655010,"count":1},{"startOffset":654875,"endOffset":654887,"count":0}],"isBlockCoverage":true},{"functionName":"pause$1","ranges":[{"startOffset":655025,"endOffset":655161,"count":0}],"isBlockCoverage":false},{"functionName":"pause$0","ranges":[{"startOffset":655176,"endOffset":655227,"count":0}],"isBlockCoverage":false},{"functionName":"resume$0","ranges":[{"startOffset":655243,"endOffset":655421,"count":0}],"isBlockCoverage":false},{"functionName":"_tryResume$0","ranges":[{"startOffset":655441,"endOffset":655647,"count":1}],"isBlockCoverage":true},{"functionName":"_unlisten$0","ranges":[{"startOffset":655666,"endOffset":655969,"count":1}],"isBlockCoverage":true},{"functionName":"set$_html$_onData","ranges":[{"startOffset":655994,"endOffset":656092,"count":1}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":656161,"endOffset":656236,"count":1}],"isBlockCoverage":true},{"functionName":"findSlot$1","ranges":[{"startOffset":656313,"endOffset":656603,"count":1},{"startOffset":656474,"endOffset":656483,"count":0}],"isBlockCoverage":true},{"functionName":"walk$1","ranges":[{"startOffset":656617,"endOffset":658566,"count":1},{"startOffset":656751,"endOffset":656760,"count":0},{"startOffset":656904,"endOffset":656930,"count":0},{"startOffset":656970,"endOffset":657045,"count":0},{"startOffset":657083,"endOffset":657092,"count":0},{"startOffset":657130,"endOffset":657139,"count":0},{"startOffset":657249,"endOffset":657258,"count":0},{"startOffset":657409,"endOffset":657434,"count":0},{"startOffset":657506,"endOffset":657518,"count":0},{"startOffset":657862,"endOffset":657887,"count":0},{"startOffset":657949,"endOffset":657961,"count":0},{"startOffset":658011,"endOffset":658565,"count":0}],"isBlockCoverage":true},{"functionName":"copyList$2","ranges":[{"startOffset":658584,"endOffset":658922,"count":1}],"isBlockCoverage":true},{"functionName":"call$2","ranges":[{"startOffset":658988,"endOffset":659072,"count":1}],"isBlockCoverage":true},{"functionName":"call$2","ranges":[{"startOffset":659158,"endOffset":659242,"count":0}],"isBlockCoverage":false},{"functionName":"findSlot$1","ranges":[{"startOffset":659324,"endOffset":659614,"count":1},{"startOffset":659485,"endOffset":659494,"count":0}],"isBlockCoverage":true},{"functionName":"walk$1","ranges":[{"startOffset":659628,"endOffset":661744,"count":1},{"startOffset":659949,"endOffset":660278,"count":0},{"startOffset":660318,"endOffset":660393,"count":0},{"startOffset":660467,"endOffset":660510,"count":0},{"startOffset":660713,"endOffset":660738,"count":0},{"startOffset":660814,"endOffset":660826,"count":0},{"startOffset":661277,"endOffset":661302,"count":0},{"startOffset":661364,"endOffset":661376,"count":0},{"startOffset":661502,"endOffset":661505,"count":0},{"startOffset":661722,"endOffset":661743,"count":0}],"isBlockCoverage":true},{"functionName":"convertNativeToDart_AcceptStructuredClone$2$mustCopy","ranges":[{"startOffset":661804,"endOffset":661900,"count":1}],"isBlockCoverage":true},{"functionName":"call$2","ranges":[{"startOffset":661972,"endOffset":662124,"count":1}],"isBlockCoverage":true},{"functionName":"forEachObjectKey$2","ranges":[{"startOffset":662216,"endOffset":662485,"count":0}],"isBlockCoverage":false},{"functionName":"forEachJsField$2","ranges":[{"startOffset":662561,"endOffset":662902,"count":1},{"startOffset":662770,"endOffset":662816,"count":0}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":662964,"endOffset":663725,"count":1},{"startOffset":663098,"endOffset":663121,"count":0},{"startOffset":663497,"endOffset":663696,"count":0}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":663805,"endOffset":663896,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":663977,"endOffset":664044,"count":0}],"isBlockCoverage":false},{"functionName":"get$message","ranges":[{"startOffset":664969,"endOffset":665026,"count":0}],"isBlockCoverage":false},{"functionName":"runOnce$1","ranges":[{"startOffset":665079,"endOffset":665381,"count":0}],"isBlockCoverage":false},{"functionName":"onData$1","ranges":[{"startOffset":665493,"endOffset":665616,"count":0}],"isBlockCoverage":false},{"functionName":"onError$1","ranges":[{"startOffset":665633,"endOffset":665732,"count":0}],"isBlockCoverage":false},{"functionName":"onDone$1","ranges":[{"startOffset":665748,"endOffset":665866,"count":0}],"isBlockCoverage":false},{"functionName":"pause$1","ranges":[{"startOffset":665881,"endOffset":665974,"count":0}],"isBlockCoverage":false},{"functionName":"pause$0","ranges":[{"startOffset":665989,"endOffset":666040,"count":0}],"isBlockCoverage":false},{"functionName":"resume$0","ranges":[{"startOffset":666056,"endOffset":666126,"count":0}],"isBlockCoverage":false},{"functionName":"cancel$0","ranges":[{"startOffset":666142,"endOffset":666219,"count":0}],"isBlockCoverage":false},{"functionName":"add$1","ranges":[{"startOffset":666296,"endOffset":666782,"count":0}],"isBlockCoverage":false},{"functionName":"close$0","ranges":[{"startOffset":666797,"endOffset":667077,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":667153,"endOffset":667619,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":667637,"endOffset":667703,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":667764,"endOffset":667976,"count":0}],"isBlockCoverage":false},{"functionName":"add$1","ranges":[{"startOffset":668045,"endOffset":668148,"count":0}],"isBlockCoverage":false},{"functionName":"addStream$1","ranges":[{"startOffset":668167,"endOffset":668625,"count":0}],"isBlockCoverage":false},{"functionName":"_checkEventAllowed$0","ranges":[{"startOffset":668653,"endOffset":668914,"count":0}],"isBlockCoverage":false},{"functionName":"close$0","ranges":[{"startOffset":668929,"endOffset":669017,"count":0}],"isBlockCoverage":false},{"functionName":"get$done","ranges":[{"startOffset":669118,"endOffset":669160,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":669229,"endOffset":669287,"count":0}],"isBlockCoverage":false},{"functionName":"complete$1","ranges":[{"startOffset":669358,"endOffset":669447,"count":0}],"isBlockCoverage":false},{"functionName":"addTo$1","ranges":[{"startOffset":669462,"endOffset":669536,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":669556,"endOffset":669672,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":669683,"endOffset":669875,"count":0}],"isBlockCoverage":false},{"functionName":"complete$1","ranges":[{"startOffset":669972,"endOffset":670080,"count":1}],"isBlockCoverage":true},{"functionName":"addTo$1","ranges":[{"startOffset":670095,"endOffset":670191,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":670211,"endOffset":670292,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":670303,"endOffset":670456,"count":0}],"isBlockCoverage":false},{"functionName":"setSourceStream$1","ranges":[{"startOffset":670537,"endOffset":670945,"count":1},{"startOffset":670703,"endOffset":670769,"count":0}],"isBlockCoverage":true},{"functionName":"listen$4$cancelOnError$onDone$onError","ranges":[{"startOffset":671029,"endOffset":671873,"count":1},{"startOffset":671585,"endOffset":671619,"count":0}],"isBlockCoverage":true},{"functionName":"listen$3$onDone$onError","ranges":[{"startOffset":671904,"endOffset":672033,"count":0}],"isBlockCoverage":false},{"functionName":"listen$1","ranges":[{"startOffset":672049,"endOffset":672156,"count":1}],"isBlockCoverage":true},{"functionName":"_linkStreamToController$0","ranges":[{"startOffset":672189,"endOffset":672550,"count":1}],"isBlockCoverage":true},{"functionName":"_createController$0","ranges":[{"startOffset":672577,"endOffset":672786,"count":1}],"isBlockCoverage":true},{"functionName":"set$_stream_completer$_controller","ranges":[{"startOffset":672827,"endOffset":672960,"count":1}],"isBlockCoverage":true},{"functionName":"set$_sourceStream","ranges":[{"startOffset":672985,"endOffset":673096,"count":1}],"isBlockCoverage":true},{"functionName":"add$1","ranges":[{"startOffset":673143,"endOffset":673811,"count":0}],"isBlockCoverage":false},{"functionName":"_onListen$0","ranges":[{"startOffset":673830,"endOffset":674010,"count":0}],"isBlockCoverage":false},{"functionName":"_onCancelBroadcast$0","ranges":[{"startOffset":674038,"endOffset":674225,"count":0}],"isBlockCoverage":false},{"functionName":"_listenToStream$1","ranges":[{"startOffset":674250,"endOffset":674687,"count":0}],"isBlockCoverage":false},{"functionName":"close$0","ranges":[{"startOffset":674702,"endOffset":675097,"count":0}],"isBlockCoverage":false},{"functionName":"set$_stream_group$_controller","ranges":[{"startOffset":675134,"endOffset":675263,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":675339,"endOffset":675376,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":675456,"endOffset":675530,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":675548,"endOffset":675630,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":675696,"endOffset":676010,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":676028,"endOffset":676124,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":676199,"endOffset":676545,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":676563,"endOffset":676659,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":676731,"endOffset":677110,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":677188,"endOffset":677231,"count":0}],"isBlockCoverage":false},{"functionName":"get$next","ranges":[{"startOffset":677281,"endOffset":677676,"count":1},{"startOffset":677618,"endOffset":677675,"count":0}],"isBlockCoverage":true},{"functionName":"_updateRequests$0","ranges":[{"startOffset":677701,"endOffset":678244,"count":1},{"startOffset":677901,"endOffset":677955,"count":0},{"startOffset":678019,"endOffset":678042,"count":0},{"startOffset":678120,"endOffset":678151,"count":0}],"isBlockCoverage":true},{"functionName":"_extractStream$0","ranges":[{"startOffset":678268,"endOffset":678938,"count":1},{"startOffset":678406,"endOffset":678470,"count":0},{"startOffset":678575,"endOffset":678610,"count":0}],"isBlockCoverage":true},{"functionName":"_ensureListening$0","ranges":[{"startOffset":678964,"endOffset":679408,"count":1},{"startOffset":679038,"endOffset":679045,"count":0},{"startOffset":679368,"endOffset":679402,"count":0}],"isBlockCoverage":true},{"functionName":"_addResult$1","ranges":[{"startOffset":679428,"endOffset":679693,"count":1}],"isBlockCoverage":true},{"functionName":"_failClosed$0","ranges":[{"startOffset":679714,"endOffset":679784,"count":0}],"isBlockCoverage":false},{"functionName":"_addRequest$1","ranges":[{"startOffset":679805,"endOffset":680179,"count":1}],"isBlockCoverage":true},{"functionName":"set$_stream_queue$_subscription","ranges":[{"startOffset":680218,"endOffset":680355,"count":1}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":680428,"endOffset":680601,"count":1}],"isBlockCoverage":true},{"functionName":"$signature","ranges":[{"startOffset":680619,"endOffset":680685,"count":1}],"isBlockCoverage":true},{"functionName":"call$2","ranges":[{"startOffset":680759,"endOffset":680888,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":680981,"endOffset":681129,"count":0}],"isBlockCoverage":false},{"functionName":"update$2","ranges":[{"startOffset":681233,"endOffset":681629,"count":1},{"startOffset":681475,"endOffset":681603,"count":0}],"isBlockCoverage":true},{"functionName":"update$2","ranges":[{"startOffset":681705,"endOffset":683204,"count":1},{"startOffset":681986,"endOffset":682524,"count":0},{"startOffset":682592,"endOffset":683179,"count":0}],"isBlockCoverage":true},{"functionName":"get$_canSendDirectly","ranges":[{"startOffset":683334,"endOffset":683447,"count":0}],"isBlockCoverage":false},{"functionName":"get$done","ranges":[{"startOffset":683463,"endOffset":683878,"count":0}],"isBlockCoverage":false},{"functionName":"addStream$1","ranges":[{"startOffset":683897,"endOffset":684228,"count":0}],"isBlockCoverage":false},{"functionName":"close$0","ranges":[{"startOffset":684243,"endOffset":684519,"count":0}],"isBlockCoverage":false},{"functionName":"_ensureController$0","ranges":[{"startOffset":684546,"endOffset":684755,"count":0}],"isBlockCoverage":false},{"functionName":"_setDestinationSink$1","ranges":[{"startOffset":684784,"endOffset":685386,"count":1},{"startOffset":685061,"endOffset":685262,"count":0},{"startOffset":685349,"endOffset":685380,"count":0}],"isBlockCoverage":true},{"functionName":"set$_stream_sink_completer$_controller","ranges":[{"startOffset":685432,"endOffset":685570,"count":0}],"isBlockCoverage":false},{"functionName":"set$_destinationSink","ranges":[{"startOffset":685598,"endOffset":685722,"count":1}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":685886,"endOffset":685905,"count":0}],"isBlockCoverage":false},{"functionName":"listen$4$cancelOnError$onDone$onError","ranges":[{"startOffset":686010,"endOffset":686754,"count":1},{"startOffset":686321,"endOffset":686398,"count":0},{"startOffset":686489,"endOffset":686590,"count":0}],"isBlockCoverage":true},{"functionName":"listen$3$onDone$onError","ranges":[{"startOffset":686785,"endOffset":686914,"count":0}],"isBlockCoverage":false},{"functionName":"listen$1","ranges":[{"startOffset":686930,"endOffset":687037,"count":0}],"isBlockCoverage":false},{"functionName":"set$_subscription_stream$_source","ranges":[{"startOffset":687077,"endOffset":687203,"count":1}],"isBlockCoverage":true},{"functionName":"onError$1","ranges":[{"startOffset":687276,"endOffset":687442,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":687528,"endOffset":688068,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":688174,"endOffset":688382,"count":0}],"isBlockCoverage":false},{"functionName":"evaluate$1","ranges":[{"startOffset":688481,"endOffset":688527,"count":1}],"isBlockCoverage":true},{"functionName":"intersection$1","ranges":[{"startOffset":688549,"endOffset":688592,"count":0}],"isBlockCoverage":false},{"functionName":"validate$1","ranges":[{"startOffset":688610,"endOffset":688689,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":688707,"endOffset":688748,"count":0}],"isBlockCoverage":false},{"functionName":"accept$1","ranges":[{"startOffset":688826,"endOffset":688895,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":688913,"endOffset":688956,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":688967,"endOffset":689113,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":689133,"endOffset":689193,"count":0}],"isBlockCoverage":false},{"functionName":"get$span","ranges":[{"startOffset":689225,"endOffset":689267,"count":0}],"isBlockCoverage":false},{"functionName":"accept$1","ranges":[{"startOffset":689313,"endOffset":689377,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":689395,"endOffset":689568,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":689579,"endOffset":689727,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":689747,"endOffset":689834,"count":0}],"isBlockCoverage":false},{"functionName":"get$span","ranges":[{"startOffset":689866,"endOffset":689908,"count":0}],"isBlockCoverage":false},{"functionName":"get$span","ranges":[{"startOffset":689953,"endOffset":690044,"count":0}],"isBlockCoverage":false},{"functionName":"accept$1","ranges":[{"startOffset":690060,"endOffset":690123,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":690141,"endOffset":690553,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":690564,"endOffset":690743,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":690763,"endOffset":690897,"count":0}],"isBlockCoverage":false},{"functionName":"get$span","ranges":[{"startOffset":690959,"endOffset":691050,"count":0}],"isBlockCoverage":false},{"functionName":"accept$1","ranges":[{"startOffset":691066,"endOffset":691130,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":691148,"endOffset":691558,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":691569,"endOffset":691749,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":691769,"endOffset":691903,"count":0}],"isBlockCoverage":false},{"functionName":"get$span","ranges":[{"startOffset":691973,"endOffset":692073,"count":0}],"isBlockCoverage":false},{"functionName":"accept$1","ranges":[{"startOffset":692089,"endOffset":692161,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":692179,"endOffset":692631,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":692642,"endOffset":692888,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":692908,"endOffset":693101,"count":0}],"isBlockCoverage":false},{"functionName":"visitVariable$1","ranges":[{"startOffset":693172,"endOffset":693242,"count":0}],"isBlockCoverage":false},{"functionName":"visitNot$1","ranges":[{"startOffset":693260,"endOffset":693346,"count":0}],"isBlockCoverage":false},{"functionName":"visitOr$1","ranges":[{"startOffset":693363,"endOffset":693499,"count":0}],"isBlockCoverage":false},{"functionName":"visitAnd$1","ranges":[{"startOffset":693517,"endOffset":693653,"count":0}],"isBlockCoverage":false},{"functionName":"visitConditional$1","ranges":[{"startOffset":693679,"endOffset":693831,"count":0}],"isBlockCoverage":false},{"functionName":"evaluate$1","ranges":[{"startOffset":693910,"endOffset":694228,"count":0}],"isBlockCoverage":false},{"functionName":"intersection$1","ranges":[{"startOffset":694250,"endOffset":694597,"count":0}],"isBlockCoverage":false},{"functionName":"validate$1","ranges":[{"startOffset":694615,"endOffset":694736,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":694754,"endOffset":694816,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":694827,"endOffset":694995,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":695015,"endOffset":695099,"count":0}],"isBlockCoverage":false},{"functionName":"evaluate$1","ranges":[{"startOffset":695187,"endOffset":695353,"count":0}],"isBlockCoverage":false},{"functionName":"intersection$1","ranges":[{"startOffset":695375,"endOffset":695452,"count":0}],"isBlockCoverage":false},{"functionName":"validate$1","ranges":[{"startOffset":695470,"endOffset":695639,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":695657,"endOffset":695766,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":695777,"endOffset":695993,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":696013,"endOffset":696141,"count":0}],"isBlockCoverage":false},{"functionName":"evaluate$1","ranges":[{"startOffset":696213,"endOffset":696260,"count":0}],"isBlockCoverage":false},{"functionName":"intersection$1","ranges":[{"startOffset":696282,"endOffset":696324,"count":0}],"isBlockCoverage":false},{"functionName":"validate$1","ranges":[{"startOffset":696342,"endOffset":696421,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":696439,"endOffset":696481,"count":0}],"isBlockCoverage":false},{"functionName":"parse$0","ranges":[{"startOffset":696552,"endOffset":696864,"count":0}],"isBlockCoverage":false},{"functionName":"_conditional$0","ranges":[{"startOffset":696886,"endOffset":697347,"count":0}],"isBlockCoverage":false},{"functionName":"_or$0","ranges":[{"startOffset":697360,"endOffset":697536,"count":0}],"isBlockCoverage":false},{"functionName":"_and$0","ranges":[{"startOffset":697550,"endOffset":697742,"count":0}],"isBlockCoverage":false},{"functionName":"_simpleExpression$0","ranges":[{"startOffset":697769,"endOffset":698600,"count":0}],"isBlockCoverage":false},{"functionName":"peek$0","ranges":[{"startOffset":698644,"endOffset":698770,"count":0}],"isBlockCoverage":false},{"functionName":"next$0","ranges":[{"startOffset":698784,"endOffset":699055,"count":0}],"isBlockCoverage":false},{"functionName":"scan$1","ranges":[{"startOffset":699069,"endOffset":699219,"count":0}],"isBlockCoverage":false},{"functionName":"_getNext$0","ranges":[{"startOffset":699237,"endOffset":700677,"count":0}],"isBlockCoverage":false},{"functionName":"_scanOperator$1","ranges":[{"startOffset":700700,"endOffset":701057,"count":0}],"isBlockCoverage":false},{"functionName":"_consumeWhitespace$0","ranges":[{"startOffset":701085,"endOffset":701439,"count":0}],"isBlockCoverage":false},{"functionName":"_multiLineComment$0","ranges":[{"startOffset":701466,"endOffset":701903,"count":0}],"isBlockCoverage":false},{"functionName":"get$type","ranges":[{"startOffset":701947,"endOffset":701997,"count":0}],"isBlockCoverage":false},{"functionName":"get$span","ranges":[{"startOffset":702013,"endOffset":702055,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":702111,"endOffset":702182,"count":0}],"isBlockCoverage":false},{"functionName":"get$type","ranges":[{"startOffset":702215,"endOffset":702270,"count":0}],"isBlockCoverage":false},{"functionName":"get$span","ranges":[{"startOffset":702286,"endOffset":702328,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":702378,"endOffset":702421,"count":0}],"isBlockCoverage":false},{"functionName":"visitVariable$1","ranges":[{"startOffset":702476,"endOffset":702681,"count":0}],"isBlockCoverage":false},{"functionName":"visitNot$1","ranges":[{"startOffset":702738,"endOffset":702793,"count":0}],"isBlockCoverage":false},{"functionName":"visitOr$1","ranges":[{"startOffset":702810,"endOffset":702897,"count":0}],"isBlockCoverage":false},{"functionName":"visitAnd$1","ranges":[{"startOffset":702915,"endOffset":703002,"count":0}],"isBlockCoverage":false},{"functionName":"visitConditional$1","ranges":[{"startOffset":703028,"endOffset":703160,"count":0}],"isBlockCoverage":false},{"functionName":"get$iterator","ranges":[{"startOffset":703242,"endOffset":703293,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":703311,"endOffset":703346,"count":0}],"isBlockCoverage":false},{"functionName":"contains$1","ranges":[{"startOffset":703364,"endOffset":703412,"count":0}],"isBlockCoverage":false},{"functionName":"toSet$0","ranges":[{"startOffset":703427,"endOffset":703514,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":703615,"endOffset":703731,"count":1}],"isBlockCoverage":true},{"functionName":"$signature","ranges":[{"startOffset":703749,"endOffset":703857,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":703909,"endOffset":704161,"count":1}],"isBlockCoverage":true},{"functionName":"$signature","ranges":[{"startOffset":704179,"endOffset":704273,"count":1}],"isBlockCoverage":true},{"functionName":"call$2","ranges":[{"startOffset":704327,"endOffset":704582,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":704600,"endOffset":704692,"count":1}],"isBlockCoverage":true},{"functionName":"toString$0","ranges":[{"startOffset":704742,"endOffset":704827,"count":0}],"isBlockCoverage":false},{"functionName":"removeFirst$0","ranges":[{"startOffset":704848,"endOffset":705338,"count":1},{"startOffset":704985,"endOffset":705036,"count":0},{"startOffset":705109,"endOffset":705132,"count":0}],"isBlockCoverage":true},{"functionName":"get$length","ranges":[{"startOffset":705356,"endOffset":705482,"count":1}],"isBlockCoverage":true},{"functionName":"set$length","ranges":[{"startOffset":705500,"endOffset":706426,"count":0}],"isBlockCoverage":false},{"functionName":"$index","ranges":[{"startOffset":706440,"endOffset":706924,"count":0}],"isBlockCoverage":false},{"functionName":"$indexSet","ranges":[{"startOffset":706941,"endOffset":707471,"count":0}],"isBlockCoverage":false},{"functionName":"_queue_list$_add$1","ranges":[{"startOffset":707497,"endOffset":708514,"count":1},{"startOffset":707919,"endOffset":708508,"count":0}],"isBlockCoverage":true},{"functionName":"_writeToList$1","ranges":[{"startOffset":708536,"endOffset":709305,"count":0}],"isBlockCoverage":false},{"functionName":"_preGrow$1","ranges":[{"startOffset":709323,"endOffset":709960,"count":0}],"isBlockCoverage":false},{"functionName":"set$_queue_list$_table","ranges":[{"startOffset":709990,"endOffset":710090,"count":1}],"isBlockCoverage":true},{"functionName":"get$length","ranges":[{"startOffset":710275,"endOffset":710401,"count":0}],"isBlockCoverage":false},{"functionName":"get$iterator","ranges":[{"startOffset":710421,"endOffset":710522,"count":0}],"isBlockCoverage":false},{"functionName":"get$_union_set$_iterable","ranges":[{"startOffset":710554,"endOffset":710878,"count":0}],"isBlockCoverage":false},{"functionName":"toSet$0","ranges":[{"startOffset":710893,"endOffset":711220,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":711280,"endOffset":711601,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":711619,"endOffset":711693,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":711756,"endOffset":711836,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":711854,"endOffset":711927,"count":0}],"isBlockCoverage":false},{"functionName":"set$_union_set_controller$_set","ranges":[{"startOffset":712065,"endOffset":712173,"count":0}],"isBlockCoverage":false},{"functionName":"contains$1","ranges":[{"startOffset":712394,"endOffset":712470,"count":0}],"isBlockCoverage":false},{"functionName":"every$1","ranges":[{"startOffset":712485,"endOffset":712601,"count":0}],"isBlockCoverage":false},{"functionName":"get$first","ranges":[{"startOffset":712618,"endOffset":712695,"count":0}],"isBlockCoverage":false},{"functionName":"get$isEmpty","ranges":[{"startOffset":712714,"endOffset":712793,"count":0}],"isBlockCoverage":false},{"functionName":"get$iterator","ranges":[{"startOffset":712813,"endOffset":712893,"count":0}],"isBlockCoverage":false},{"functionName":"get$last","ranges":[{"startOffset":712909,"endOffset":712985,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":713003,"endOffset":713081,"count":0}],"isBlockCoverage":false},{"functionName":"map$1$1","ranges":[{"startOffset":713096,"endOffset":713223,"count":0}],"isBlockCoverage":false},{"functionName":"toList$1$growable","ranges":[{"startOffset":713248,"endOffset":713329,"count":1}],"isBlockCoverage":true},{"functionName":"toList$0","ranges":[{"startOffset":713345,"endOffset":713426,"count":1}],"isBlockCoverage":true},{"functionName":"toSet$0","ranges":[{"startOffset":713441,"endOffset":713496,"count":0}],"isBlockCoverage":false},{"functionName":"where$1","ranges":[{"startOffset":713511,"endOffset":713627,"count":1}],"isBlockCoverage":true},{"functionName":"toString$0","ranges":[{"startOffset":713645,"endOffset":713703,"count":0}],"isBlockCoverage":false},{"functionName":"union$1","ranges":[{"startOffset":713813,"endOffset":713967,"count":1}],"isBlockCoverage":true},{"functionName":"toSet$0","ranges":[{"startOffset":713982,"endOffset":714157,"count":0}],"isBlockCoverage":false},{"functionName":"matches$2","ranges":[{"startOffset":714254,"endOffset":714324,"count":1}],"isBlockCoverage":true},{"functionName":"describe$1","ranges":[{"startOffset":714342,"endOffset":714441,"count":0}],"isBlockCoverage":false},{"functionName":"typedMatches$2","ranges":[{"startOffset":714496,"endOffset":714602,"count":0}],"isBlockCoverage":false},{"functionName":"describe$1","ranges":[{"startOffset":714620,"endOffset":714745,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":714803,"endOffset":714863,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":714881,"endOffset":714982,"count":0}],"isBlockCoverage":false},{"functionName":"addDescriptionOf$1","ranges":[{"startOffset":715008,"endOffset":715192,"count":0}],"isBlockCoverage":false},{"functionName":"typedMatches$2","ranges":[{"startOffset":715280,"endOffset":715389,"count":0}],"isBlockCoverage":false},{"functionName":"describe$1","ranges":[{"startOffset":715407,"endOffset":715510,"count":0}],"isBlockCoverage":false},{"functionName":"describeTypedMismatch$4","ranges":[{"startOffset":715541,"endOffset":717567,"count":0}],"isBlockCoverage":false},{"functionName":"_compareIterables$5","ranges":[{"startOffset":717629,"endOffset":718839,"count":0}],"isBlockCoverage":false},{"functionName":"_compareSets$5","ranges":[{"startOffset":718861,"endOffset":720203,"count":0}],"isBlockCoverage":false},{"functionName":"_recursiveMatch$4","ranges":[{"startOffset":720228,"endOffset":723384,"count":0}],"isBlockCoverage":false},{"functionName":"_equals_matcher$_match$3","ranges":[{"startOffset":723416,"endOffset":724262,"count":0}],"isBlockCoverage":false},{"functionName":"matches$2","ranges":[{"startOffset":724279,"endOffset":724402,"count":0}],"isBlockCoverage":false},{"functionName":"describe$1","ranges":[{"startOffset":724420,"endOffset":724510,"count":0}],"isBlockCoverage":false},{"functionName":"describeMismatch$4","ranges":[{"startOffset":724536,"endOffset":725070,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":725140,"endOffset":725305,"count":0}],"isBlockCoverage":false},{"functionName":"matches$2","ranges":[{"startOffset":725378,"endOffset":725603,"count":0}],"isBlockCoverage":false},{"functionName":"describeMismatch$4","ranges":[{"startOffset":725629,"endOffset":725977,"count":0}],"isBlockCoverage":false},{"functionName":"describeTypedMismatch$4","ranges":[{"startOffset":726008,"endOffset":726176,"count":0}],"isBlockCoverage":false},{"functionName":"describeMismatch$4","ranges":[{"startOffset":726232,"endOffset":726330,"count":0}],"isBlockCoverage":false},{"functionName":"call$4","ranges":[{"startOffset":726391,"endOffset":730032,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":730116,"endOffset":730227,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":730316,"endOffset":730510,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":730599,"endOffset":730730,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":730820,"endOffset":731014,"count":0}],"isBlockCoverage":false},{"functionName":"describe$1","ranges":[{"startOffset":731085,"endOffset":731450,"count":0}],"isBlockCoverage":false},{"functionName":"matches$2","ranges":[{"startOffset":731467,"endOffset":731575,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":731631,"endOffset":731703,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":731773,"endOffset":731958,"count":0}],"isBlockCoverage":false},{"functionName":"get$packageRoot","ranges":[{"startOffset":732071,"endOffset":732108,"count":0}],"isBlockCoverage":false},{"functionName":"get$packageConfigMap","ranges":[{"startOffset":732136,"endOffset":732190,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":732270,"endOffset":732353,"count":1}],"isBlockCoverage":true},{"functionName":"get$packageConfigMap","ranges":[{"startOffset":732469,"endOffset":732506,"count":0}],"isBlockCoverage":false},{"functionName":"get$packageRoot","ranges":[{"startOffset":732529,"endOffset":732578,"count":0}],"isBlockCoverage":false},{"functionName":"absolute$7","ranges":[{"startOffset":732626,"endOffset":733114,"count":0}],"isBlockCoverage":false},{"functionName":"absolute$1","ranges":[{"startOffset":733132,"endOffset":733250,"count":0}],"isBlockCoverage":false},{"functionName":"join$8","ranges":[{"startOffset":733264,"endOffset":733650,"count":0}],"isBlockCoverage":false},{"functionName":"join$2","ranges":[{"startOffset":733664,"endOffset":733792,"count":0}],"isBlockCoverage":false},{"functionName":"joinAll$1","ranges":[{"startOffset":733809,"endOffset":735362,"count":0}],"isBlockCoverage":false},{"functionName":"split$1","ranges":[{"startOffset":735377,"endOffset":735875,"count":0}],"isBlockCoverage":false},{"functionName":"normalize$1","ranges":[{"startOffset":735894,"endOffset":736127,"count":0}],"isBlockCoverage":false},{"functionName":"_needsNormalization$1","ranges":[{"startOffset":736156,"endOffset":737652,"count":0}],"isBlockCoverage":false},{"functionName":"relative$2$from","ranges":[{"startOffset":737675,"endOffset":741067,"count":0}],"isBlockCoverage":false},{"functionName":"relative$1","ranges":[{"startOffset":741085,"endOffset":741154,"count":0}],"isBlockCoverage":false},{"functionName":"_isWithinOrEquals$2","ranges":[{"startOffset":741181,"endOffset":743129,"count":0}],"isBlockCoverage":false},{"functionName":"_isWithinOrEqualsFast$2","ranges":[{"startOffset":743160,"endOffset":747690,"count":0}],"isBlockCoverage":false},{"functionName":"_pathDirection$2","ranges":[{"startOffset":747714,"endOffset":748939,"count":0}],"isBlockCoverage":false},{"functionName":"toUri$1","ranges":[{"startOffset":748954,"endOffset":749248,"count":0}],"isBlockCoverage":false},{"functionName":"prettyUri$1","ranges":[{"startOffset":749267,"endOffset":749843,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":749900,"endOffset":749973,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":750052,"endOffset":750124,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":750201,"endOffset":750279,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":750359,"endOffset":750468,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":750542,"endOffset":750585,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":750639,"endOffset":750682,"count":0}],"isBlockCoverage":false},{"functionName":"getRoot$1","ranges":[{"startOffset":750735,"endOffset":751064,"count":0}],"isBlockCoverage":false},{"functionName":"relativePathToUri$1","ranges":[{"startOffset":751091,"endOffset":751348,"count":0}],"isBlockCoverage":false},{"functionName":"codeUnitsEqual$2","ranges":[{"startOffset":751372,"endOffset":751448,"count":0}],"isBlockCoverage":false},{"functionName":"pathsEqual$2","ranges":[{"startOffset":751468,"endOffset":751527,"count":0}],"isBlockCoverage":false},{"functionName":"get$hasTrailingSeparator","ranges":[{"startOffset":751592,"endOffset":751829,"count":0}],"isBlockCoverage":false},{"functionName":"removeTrailingSeparators$0","ranges":[{"startOffset":751863,"endOffset":752298,"count":0}],"isBlockCoverage":false},{"functionName":"normalize$0","ranges":[{"startOffset":752317,"endOffset":753935,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":753953,"endOffset":754466,"count":0}],"isBlockCoverage":false},{"functionName":"set$parts","ranges":[{"startOffset":754483,"endOffset":754558,"count":0}],"isBlockCoverage":false},{"functionName":"set$separators","ranges":[{"startOffset":754580,"endOffset":754670,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":754735,"endOffset":754801,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":754875,"endOffset":754941,"count":0}],"isBlockCoverage":false},{"functionName":"get$message","ranges":[{"startOffset":754981,"endOffset":755034,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":755080,"endOffset":755133,"count":0}],"isBlockCoverage":false},{"functionName":"containsSeparator$1","ranges":[{"startOffset":755193,"endOffset":755270,"count":0}],"isBlockCoverage":false},{"functionName":"isSeparator$1","ranges":[{"startOffset":755291,"endOffset":755347,"count":0}],"isBlockCoverage":false},{"functionName":"needsSeparator$1","ranges":[{"startOffset":755371,"endOffset":755500,"count":0}],"isBlockCoverage":false},{"functionName":"rootLength$2$withDrive","ranges":[{"startOffset":755530,"endOffset":755678,"count":0}],"isBlockCoverage":false},{"functionName":"rootLength$1","ranges":[{"startOffset":755698,"endOffset":755775,"count":0}],"isBlockCoverage":false},{"functionName":"isRootRelative$1","ranges":[{"startOffset":755799,"endOffset":755841,"count":0}],"isBlockCoverage":false},{"functionName":"pathFromUri$1","ranges":[{"startOffset":755862,"endOffset":756185,"count":0}],"isBlockCoverage":false},{"functionName":"absolutePathToUri$1","ranges":[{"startOffset":756212,"endOffset":756605,"count":0}],"isBlockCoverage":false},{"functionName":"get$name","ranges":[{"startOffset":756621,"endOffset":756661,"count":0}],"isBlockCoverage":false},{"functionName":"get$separator","ranges":[{"startOffset":756682,"endOffset":756718,"count":0}],"isBlockCoverage":false},{"functionName":"containsSeparator$1","ranges":[{"startOffset":756776,"endOffset":756853,"count":0}],"isBlockCoverage":false},{"functionName":"isSeparator$1","ranges":[{"startOffset":756874,"endOffset":756930,"count":0}],"isBlockCoverage":false},{"functionName":"needsSeparator$1","ranges":[{"startOffset":756954,"endOffset":757222,"count":0}],"isBlockCoverage":false},{"functionName":"rootLength$2$withDrive","ranges":[{"startOffset":757252,"endOffset":758194,"count":0}],"isBlockCoverage":false},{"functionName":"rootLength$1","ranges":[{"startOffset":758214,"endOffset":758291,"count":0}],"isBlockCoverage":false},{"functionName":"isRootRelative$1","ranges":[{"startOffset":758315,"endOffset":758421,"count":0}],"isBlockCoverage":false},{"functionName":"pathFromUri$1","ranges":[{"startOffset":758442,"endOffset":758496,"count":0}],"isBlockCoverage":false},{"functionName":"relativePathToUri$1","ranges":[{"startOffset":758523,"endOffset":758577,"count":0}],"isBlockCoverage":false},{"functionName":"absolutePathToUri$1","ranges":[{"startOffset":758604,"endOffset":758658,"count":0}],"isBlockCoverage":false},{"functionName":"get$name","ranges":[{"startOffset":758674,"endOffset":758712,"count":0}],"isBlockCoverage":false},{"functionName":"get$separator","ranges":[{"startOffset":758733,"endOffset":758769,"count":0}],"isBlockCoverage":false},{"functionName":"containsSeparator$1","ranges":[{"startOffset":758831,"endOffset":758908,"count":0}],"isBlockCoverage":false},{"functionName":"isSeparator$1","ranges":[{"startOffset":758929,"endOffset":759004,"count":0}],"isBlockCoverage":false},{"functionName":"needsSeparator$1","ranges":[{"startOffset":759028,"endOffset":759218,"count":0}],"isBlockCoverage":false},{"functionName":"rootLength$2$withDrive","ranges":[{"startOffset":759248,"endOffset":760107,"count":0}],"isBlockCoverage":false},{"functionName":"rootLength$1","ranges":[{"startOffset":760127,"endOffset":760204,"count":0}],"isBlockCoverage":false},{"functionName":"isRootRelative$1","ranges":[{"startOffset":760228,"endOffset":760294,"count":0}],"isBlockCoverage":false},{"functionName":"pathFromUri$1","ranges":[{"startOffset":760315,"endOffset":760968,"count":0}],"isBlockCoverage":false},{"functionName":"absolutePathToUri$1","ranges":[{"startOffset":760995,"endOffset":762083,"count":0}],"isBlockCoverage":false},{"functionName":"codeUnitsEqual$2","ranges":[{"startOffset":762107,"endOffset":762496,"count":0}],"isBlockCoverage":false},{"functionName":"pathsEqual$2","ranges":[{"startOffset":762516,"endOffset":762906,"count":0}],"isBlockCoverage":false},{"functionName":"get$name","ranges":[{"startOffset":762922,"endOffset":762964,"count":0}],"isBlockCoverage":false},{"functionName":"get$separator","ranges":[{"startOffset":762985,"endOffset":763022,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":763097,"endOffset":763169,"count":0}],"isBlockCoverage":false},{"functionName":"request$0","ranges":[{"startOffset":763232,"endOffset":764213,"count":0}],"isBlockCoverage":false},{"functionName":"withResource$1$1","ranges":[{"startOffset":764237,"endOffset":764370,"count":0}],"isBlockCoverage":false},{"functionName":"withResource$body$Pool","ranges":[{"startOffset":764400,"endOffset":766633,"count":0}],"isBlockCoverage":false},{"functionName":"close$0","ranges":[{"startOffset":766648,"endOffset":766745,"count":0}],"isBlockCoverage":false},{"functionName":"_onResourceReleaseAllowed$1","ranges":[{"startOffset":766780,"endOffset":767623,"count":0}],"isBlockCoverage":false},{"functionName":"_runOnRelease$1","ranges":[{"startOffset":767646,"endOffset":768136,"count":0}],"isBlockCoverage":false},{"functionName":"_resetTimer$0","ranges":[{"startOffset":768157,"endOffset":768543,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":768598,"endOffset":769521,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":769616,"endOffset":769695,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":769777,"endOffset":769909,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":769992,"endOffset":770160,"count":0}],"isBlockCoverage":false},{"functionName":"release$0","ranges":[{"startOffset":770231,"endOffset":770780,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":770838,"endOffset":771040,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":771119,"endOffset":773479,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":773558,"endOffset":773629,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":773709,"endOffset":773806,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":773887,"endOffset":773961,"count":0}],"isBlockCoverage":false},{"functionName":"MultiSectionMapping$fromJson$3$mapUrl","ranges":[{"startOffset":774096,"endOffset":775955,"count":0}],"isBlockCoverage":false},{"functionName":"_indexFor$2","ranges":[{"startOffset":775974,"endOffset":776437,"count":0}],"isBlockCoverage":false},{"functionName":"spanFor$4$files$uri","ranges":[{"startOffset":776464,"endOffset":777052,"count":0}],"isBlockCoverage":false},{"functionName":"spanFor$3$uri","ranges":[{"startOffset":777073,"endOffset":777172,"count":0}],"isBlockCoverage":false},{"functionName":"spanFor$3$files","ranges":[{"startOffset":777195,"endOffset":777298,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":777316,"endOffset":777847,"count":0}],"isBlockCoverage":false},{"functionName":"MappingBundle$fromJson$2$mapUrl","ranges":[{"startOffset":777922,"endOffset":778250,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":778268,"endOffset":778496,"count":0}],"isBlockCoverage":false},{"functionName":"spanFor$4$files$uri","ranges":[{"startOffset":778523,"endOffset":779530,"count":0}],"isBlockCoverage":false},{"functionName":"spanFor$3$uri","ranges":[{"startOffset":779551,"endOffset":779650,"count":0}],"isBlockCoverage":false},{"functionName":"SingleMapping$fromJson$2$mapUrl","ranges":[{"startOffset":779725,"endOffset":783823,"count":0}],"isBlockCoverage":false},{"functionName":"_segmentError$2","ranges":[{"startOffset":783846,"endOffset":784041,"count":0}],"isBlockCoverage":false},{"functionName":"_findLine$1","ranges":[{"startOffset":784060,"endOffset":784406,"count":0}],"isBlockCoverage":false},{"functionName":"_findColumn$3","ranges":[{"startOffset":784427,"endOffset":785004,"count":0}],"isBlockCoverage":false},{"functionName":"spanFor$4$files$uri","ranges":[{"startOffset":785031,"endOffset":786224,"count":0}],"isBlockCoverage":false},{"functionName":"spanFor$3$uri","ranges":[{"startOffset":786245,"endOffset":786344,"count":0}],"isBlockCoverage":false},{"functionName":"spanFor$3$files","ranges":[{"startOffset":786367,"endOffset":786470,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":786488,"endOffset":786852,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":786919,"endOffset":787072,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":787159,"endOffset":787217,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":787306,"endOffset":787368,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":787443,"endOffset":787558,"count":0}],"isBlockCoverage":false},{"functionName":"get$line","ranges":[{"startOffset":787574,"endOffset":787616,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":787668,"endOffset":787921,"count":0}],"isBlockCoverage":false},{"functionName":"get$column","ranges":[{"startOffset":787939,"endOffset":787983,"count":0}],"isBlockCoverage":false},{"functionName":"moveNext$0","ranges":[{"startOffset":788041,"endOffset":788109,"count":0}],"isBlockCoverage":false},{"functionName":"get$current","ranges":[{"startOffset":788128,"endOffset":788407,"count":0}],"isBlockCoverage":false},{"functionName":"get$hasTokens","ranges":[{"startOffset":788428,"endOffset":788527,"count":0}],"isBlockCoverage":false},{"functionName":"get$nextKind","ranges":[{"startOffset":788547,"endOffset":788997,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":789015,"endOffset":789583,"count":0}],"isBlockCoverage":false},{"functionName":"get$isIdentifier","ranges":[{"startOffset":789694,"endOffset":789744,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":789788,"endOffset":790045,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":790116,"endOffset":790175,"count":0}],"isBlockCoverage":false},{"functionName":"get$lines","ranges":[{"startOffset":790192,"endOffset":790248,"count":0}],"isBlockCoverage":false},{"functionName":"SourceFile$decoded$2$url","ranges":[{"startOffset":790280,"endOffset":790792,"count":0}],"isBlockCoverage":false},{"functionName":"span$2","ranges":[{"startOffset":790806,"endOffset":790879,"count":0}],"isBlockCoverage":false},{"functionName":"getLine$1","ranges":[{"startOffset":790896,"endOffset":791616,"count":0}],"isBlockCoverage":false},{"functionName":"_isNearCachedLine$1","ranges":[{"startOffset":791643,"endOffset":792548,"count":0}],"isBlockCoverage":false},{"functionName":"_binarySearch$1","ranges":[{"startOffset":792571,"endOffset":792981,"count":0}],"isBlockCoverage":false},{"functionName":"getColumn$1","ranges":[{"startOffset":793000,"endOffset":793683,"count":0}],"isBlockCoverage":false},{"functionName":"getOffset$2","ranges":[{"startOffset":793702,"endOffset":794481,"count":0}],"isBlockCoverage":false},{"functionName":"getOffset$1","ranges":[{"startOffset":794500,"endOffset":794565,"count":0}],"isBlockCoverage":false},{"functionName":"get$sourceUrl","ranges":[{"startOffset":794621,"endOffset":794667,"count":0}],"isBlockCoverage":false},{"functionName":"get$line","ranges":[{"startOffset":794683,"endOffset":794748,"count":0}],"isBlockCoverage":false},{"functionName":"get$column","ranges":[{"startOffset":794766,"endOffset":794833,"count":0}],"isBlockCoverage":false},{"functionName":"get$offset","ranges":[{"startOffset":794851,"endOffset":794903,"count":0}],"isBlockCoverage":false},{"functionName":"get$sourceUrl","ranges":[{"startOffset":794956,"endOffset":795002,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":795020,"endOffset":795089,"count":0}],"isBlockCoverage":false},{"functionName":"get$start","ranges":[{"startOffset":795106,"endOffset":795185,"count":0}],"isBlockCoverage":false},{"functionName":"get$end","ranges":[{"startOffset":795200,"endOffset":795277,"count":0}],"isBlockCoverage":false},{"functionName":"get$text","ranges":[{"startOffset":795293,"endOffset":795463,"count":0}],"isBlockCoverage":false},{"functionName":"get$context","ranges":[{"startOffset":795482,"endOffset":796567,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":796578,"endOffset":797060,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":797080,"endOffset":797171,"count":0}],"isBlockCoverage":false},{"functionName":"expand$1","ranges":[{"startOffset":797187,"endOffset":797617,"count":0}],"isBlockCoverage":false},{"functionName":"highlight$0","ranges":[{"startOffset":797723,"endOffset":800062,"count":0}],"isBlockCoverage":false},{"functionName":"_writeFirstLine$1","ranges":[{"startOffset":800087,"endOffset":801960,"count":0}],"isBlockCoverage":false},{"functionName":"_writeIntermediateLines$1","ranges":[{"startOffset":801993,"endOffset":802716,"count":0}],"isBlockCoverage":false},{"functionName":"_writeLastLine$1","ranges":[{"startOffset":802740,"endOffset":803867,"count":0}],"isBlockCoverage":false},{"functionName":"_writeTrailingLines$1","ranges":[{"startOffset":803896,"endOffset":804584,"count":0}],"isBlockCoverage":false},{"functionName":"_writeText$1","ranges":[{"startOffset":804604,"endOffset":805084,"count":0}],"isBlockCoverage":false},{"functionName":"_writeSidebar$2$end$line","ranges":[{"startOffset":805116,"endOffset":805243,"count":0}],"isBlockCoverage":false},{"functionName":"_writeSidebar$1$end","ranges":[{"startOffset":805270,"endOffset":805346,"count":0}],"isBlockCoverage":false},{"functionName":"_writeSidebar$1$line","ranges":[{"startOffset":805374,"endOffset":805452,"count":0}],"isBlockCoverage":false},{"functionName":"_writeSidebar$0","ranges":[{"startOffset":805475,"endOffset":805549,"count":0}],"isBlockCoverage":false},{"functionName":"_countTabs$1","ranges":[{"startOffset":805569,"endOffset":805863,"count":0}],"isBlockCoverage":false},{"functionName":"_isOnlyWhitespace$1","ranges":[{"startOffset":805890,"endOffset":806214,"count":0}],"isBlockCoverage":false},{"functionName":"_colorize$2$color","ranges":[{"startOffset":806239,"endOffset":806594,"count":0}],"isBlockCoverage":false},{"functionName":"_colorize$1","ranges":[{"startOffset":806613,"endOffset":806692,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":806764,"endOffset":806982,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":807074,"endOffset":807147,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":807239,"endOffset":807514,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":807606,"endOffset":807796,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":807895,"endOffset":808102,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":808192,"endOffset":808411,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":808502,"endOffset":808715,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":808806,"endOffset":809078,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":809167,"endOffset":809588,"count":0}],"isBlockCoverage":false},{"functionName":"distance$1","ranges":[{"startOffset":809662,"endOffset":809961,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":809972,"endOffset":810183,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":810203,"endOffset":810282,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":810300,"endOffset":810590,"count":0}],"isBlockCoverage":false},{"functionName":"get$sourceUrl","ranges":[{"startOffset":810611,"endOffset":810658,"count":0}],"isBlockCoverage":false},{"functionName":"get$offset","ranges":[{"startOffset":810676,"endOffset":810728,"count":0}],"isBlockCoverage":false},{"functionName":"get$line","ranges":[{"startOffset":810744,"endOffset":810786,"count":0}],"isBlockCoverage":false},{"functionName":"get$column","ranges":[{"startOffset":810804,"endOffset":810848,"count":0}],"isBlockCoverage":false},{"functionName":"distance$1","ranges":[{"startOffset":810908,"endOffset":811205,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":811216,"endOffset":811426,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":811446,"endOffset":811524,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":811542,"endOffset":811946,"count":0}],"isBlockCoverage":false},{"functionName":"SourceSpanBase$3","ranges":[{"startOffset":812064,"endOffset":812766,"count":0}],"isBlockCoverage":false},{"functionName":"get$start","ranges":[{"startOffset":812783,"endOffset":812826,"count":0}],"isBlockCoverage":false},{"functionName":"get$end","ranges":[{"startOffset":812841,"endOffset":812882,"count":0}],"isBlockCoverage":false},{"functionName":"get$text","ranges":[{"startOffset":812898,"endOffset":812940,"count":0}],"isBlockCoverage":false},{"functionName":"get$message","ranges":[{"startOffset":813001,"endOffset":813064,"count":0}],"isBlockCoverage":false},{"functionName":"toString$1$color","ranges":[{"startOffset":813088,"endOffset":813313,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":813331,"endOffset":813411,"count":0}],"isBlockCoverage":false},{"functionName":"get$sourceUrl","ranges":[{"startOffset":813558,"endOffset":813623,"count":0}],"isBlockCoverage":false},{"functionName":"get$length","ranges":[{"startOffset":813641,"endOffset":813799,"count":0}],"isBlockCoverage":false},{"functionName":"message$2$color","ranges":[{"startOffset":813822,"endOffset":814454,"count":0}],"isBlockCoverage":false},{"functionName":"message$1","ranges":[{"startOffset":814471,"endOffset":814568,"count":0}],"isBlockCoverage":false},{"functionName":"highlight$1$color","ranges":[{"startOffset":814593,"endOffset":815709,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":815720,"endOffset":815923,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":815943,"endOffset":816110,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":816128,"endOffset":816349,"count":0}],"isBlockCoverage":false},{"functionName":"get$context","ranges":[{"startOffset":816434,"endOffset":816480,"count":0}],"isBlockCoverage":false},{"functionName":"foldFrames$2$terse","ranges":[{"startOffset":816534,"endOffset":817349,"count":0}],"isBlockCoverage":false},{"functionName":"toTrace$0","ranges":[{"startOffset":817366,"endOffset":817684,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":817702,"endOffset":818222,"count":0}],"isBlockCoverage":false},{"functionName":"get$traces","ranges":[{"startOffset":818262,"endOffset":818306,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":818364,"endOffset":818720,"count":1},{"startOffset":818496,"endOffset":818714,"count":0}],"isBlockCoverage":true},{"functionName":"$signature","ranges":[{"startOffset":818738,"endOffset":818792,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":818856,"endOffset":819515,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":819600,"endOffset":819679,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":819761,"endOffset":819943,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":820026,"endOffset":820116,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":820197,"endOffset":820307,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":820389,"endOffset":820699,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":820777,"endOffset":820853,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":820933,"endOffset":821283,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":821364,"endOffset":821467,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":821546,"endOffset":821837,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":821917,"endOffset":822083,"count":0}],"isBlockCoverage":false},{"functionName":"get$isCore","ranges":[{"startOffset":822149,"endOffset":822214,"count":0}],"isBlockCoverage":false},{"functionName":"get$library","ranges":[{"startOffset":822233,"endOffset":822388,"count":0}],"isBlockCoverage":false},{"functionName":"get$$package","ranges":[{"startOffset":822408,"endOffset":822583,"count":0}],"isBlockCoverage":false},{"functionName":"get$location","ranges":[{"startOffset":822603,"endOffset":822908,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":822926,"endOffset":823016,"count":0}],"isBlockCoverage":false},{"functionName":"get$uri","ranges":[{"startOffset":823031,"endOffset":823072,"count":0}],"isBlockCoverage":false},{"functionName":"get$line","ranges":[{"startOffset":823088,"endOffset":823130,"count":0}],"isBlockCoverage":false},{"functionName":"get$column","ranges":[{"startOffset":823148,"endOffset":823192,"count":0}],"isBlockCoverage":false},{"functionName":"get$member","ranges":[{"startOffset":823210,"endOffset":823254,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":823318,"endOffset":824399,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":824483,"endOffset":825317,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":825415,"endOffset":826447,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":826536,"endOffset":827780,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":827870,"endOffset":829445,"count":0}],"isBlockCoverage":false},{"functionName":"get$_chain","ranges":[{"startOffset":829515,"endOffset":829701,"count":0}],"isBlockCoverage":false},{"functionName":"get$traces","ranges":[{"startOffset":829719,"endOffset":829782,"count":0}],"isBlockCoverage":false},{"functionName":"foldFrames$2$terse","ranges":[{"startOffset":829808,"endOffset":829965,"count":0}],"isBlockCoverage":false},{"functionName":"toTrace$0","ranges":[{"startOffset":829982,"endOffset":830069,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":830087,"endOffset":830153,"count":0}],"isBlockCoverage":false},{"functionName":"set$_lazy_chain$_inner","ranges":[{"startOffset":830183,"endOffset":830267,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":830371,"endOffset":830474,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":830556,"endOffset":830624,"count":0}],"isBlockCoverage":false},{"functionName":"get$_lazy_trace$_trace","ranges":[{"startOffset":830705,"endOffset":830903,"count":0}],"isBlockCoverage":false},{"functionName":"get$frames","ranges":[{"startOffset":830921,"endOffset":830996,"count":0}],"isBlockCoverage":false},{"functionName":"get$original","ranges":[{"startOffset":831016,"endOffset":831093,"count":0}],"isBlockCoverage":false},{"functionName":"foldFrames$2$terse","ranges":[{"startOffset":831119,"endOffset":831276,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":831294,"endOffset":831372,"count":0}],"isBlockCoverage":false},{"functionName":"set$_lazy_trace$_inner","ranges":[{"startOffset":831402,"endOffset":831486,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":831590,"endOffset":831705,"count":0}],"isBlockCoverage":false},{"functionName":"chainFor$1","ranges":[{"startOffset":831787,"endOffset":832653,"count":0}],"isBlockCoverage":false},{"functionName":"_stack_zone_specification$_registerCallback$1$4","ranges":[{"startOffset":832708,"endOffset":833201,"count":1},{"startOffset":832917,"endOffset":832966,"count":0}],"isBlockCoverage":true},{"functionName":"_stack_zone_specification$_registerCallback$4","ranges":[{"startOffset":833254,"endOffset":833402,"count":0}],"isBlockCoverage":false},{"functionName":"_stack_zone_specification$_registerUnaryCallback$2$4","ranges":[{"startOffset":833462,"endOffset":834015,"count":1},{"startOffset":833704,"endOffset":833762,"count":0}],"isBlockCoverage":true},{"functionName":"_stack_zone_specification$_registerUnaryCallback$4","ranges":[{"startOffset":834073,"endOffset":834241,"count":0}],"isBlockCoverage":false},{"functionName":"_stack_zone_specification$_registerBinaryCallback$3$4","ranges":[{"startOffset":834302,"endOffset":834911,"count":1},{"startOffset":834516,"endOffset":834648,"count":0}],"isBlockCoverage":true},{"functionName":"_stack_zone_specification$_registerBinaryCallback$4","ranges":[{"startOffset":834970,"endOffset":835154,"count":0}],"isBlockCoverage":false},{"functionName":"_stack_zone_specification$_errorCallback$5","ranges":[{"startOffset":835204,"endOffset":836108,"count":0}],"isBlockCoverage":false},{"functionName":"_stack_zone_specification$_run$1$2","ranges":[{"startOffset":836150,"endOffset":836761,"count":1},{"startOffset":836415,"endOffset":836692,"count":0}],"isBlockCoverage":true},{"functionName":"_currentTrace$1","ranges":[{"startOffset":836784,"endOffset":836966,"count":1}],"isBlockCoverage":true},{"functionName":"_trimVMChain$1","ranges":[{"startOffset":836988,"endOffset":837222,"count":0}],"isBlockCoverage":false},{"functionName":"set$_currentNode","ranges":[{"startOffset":837246,"endOffset":837336,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":837412,"endOffset":837498,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":837595,"endOffset":837689,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":837793,"endOffset":837926,"count":1}],"isBlockCoverage":true},{"functionName":"$signature","ranges":[{"startOffset":837944,"endOffset":837998,"count":1}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":838088,"endOffset":838328,"count":1}],"isBlockCoverage":true},{"functionName":"$signature","ranges":[{"startOffset":838346,"endOffset":838433,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":838524,"endOffset":838580,"count":1}],"isBlockCoverage":true},{"functionName":"$signature","ranges":[{"startOffset":838598,"endOffset":838652,"count":1}],"isBlockCoverage":true},{"functionName":"call$2","ranges":[{"startOffset":838743,"endOffset":839016,"count":1}],"isBlockCoverage":true},{"functionName":"$signature","ranges":[{"startOffset":839034,"endOffset":839141,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":839233,"endOffset":839344,"count":1}],"isBlockCoverage":true},{"functionName":"$signature","ranges":[{"startOffset":839362,"endOffset":839416,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":839497,"endOffset":839889,"count":0}],"isBlockCoverage":false},{"functionName":"toChain$0","ranges":[{"startOffset":839953,"endOffset":840253,"count":0}],"isBlockCoverage":false},{"functionName":"foldFrames$2$terse","ranges":[{"startOffset":840307,"endOffset":841859,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":841877,"endOffset":842341,"count":0}],"isBlockCoverage":false},{"functionName":"get$frames","ranges":[{"startOffset":842381,"endOffset":842425,"count":0}],"isBlockCoverage":false},{"functionName":"get$original","ranges":[{"startOffset":842445,"endOffset":842491,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":842555,"endOffset":842867,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":842947,"endOffset":843025,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":843103,"endOffset":843191,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":843269,"endOffset":843376,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":843454,"endOffset":843542,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":843624,"endOffset":843701,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":843783,"endOffset":843871,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":843954,"endOffset":844072,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":844155,"endOffset":844248,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":844332,"endOffset":844425,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":844509,"endOffset":844603,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":844684,"endOffset":845037,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":845119,"endOffset":845519,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":845599,"endOffset":845702,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":845781,"endOffset":846034,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":846108,"endOffset":846153,"count":0}],"isBlockCoverage":false},{"functionName":"get$uri","ranges":[{"startOffset":846185,"endOffset":846226,"count":0}],"isBlockCoverage":false},{"functionName":"get$line","ranges":[{"startOffset":846242,"endOffset":846279,"count":0}],"isBlockCoverage":false},{"functionName":"get$column","ranges":[{"startOffset":846297,"endOffset":846334,"count":0}],"isBlockCoverage":false},{"functionName":"get$isCore","ranges":[{"startOffset":846352,"endOffset":846390,"count":0}],"isBlockCoverage":false},{"functionName":"get$library","ranges":[{"startOffset":846409,"endOffset":846452,"count":0}],"isBlockCoverage":false},{"functionName":"get$$package","ranges":[{"startOffset":846472,"endOffset":846509,"count":0}],"isBlockCoverage":false},{"functionName":"get$location","ranges":[{"startOffset":846529,"endOffset":846572,"count":0}],"isBlockCoverage":false},{"functionName":"get$member","ranges":[{"startOffset":846590,"endOffset":846634,"count":0}],"isBlockCoverage":false},{"functionName":"get$stream","ranges":[{"startOffset":846691,"endOffset":846863,"count":1}],"isBlockCoverage":true},{"functionName":"get$sink","ranges":[{"startOffset":846879,"endOffset":846922,"count":1}],"isBlockCoverage":true},{"functionName":"GuaranteeChannel$3$allowSinkErrors","ranges":[{"startOffset":846964,"endOffset":847401,"count":1}],"isBlockCoverage":true},{"functionName":"_onSinkDisconnected$0","ranges":[{"startOffset":847430,"endOffset":847602,"count":0}],"isBlockCoverage":false},{"functionName":"set$_sink","ranges":[{"startOffset":847619,"endOffset":847714,"count":1}],"isBlockCoverage":true},{"functionName":"set$_streamController","ranges":[{"startOffset":847743,"endOffset":847876,"count":1}],"isBlockCoverage":true},{"functionName":"set$_subscription","ranges":[{"startOffset":847901,"endOffset":848024,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":848085,"endOffset":848387,"count":1},{"startOffset":848177,"endOffset":848184,"count":0}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":848468,"endOffset":848594,"count":0}],"isBlockCoverage":false},{"functionName":"get$done","ranges":[{"startOffset":848666,"endOffset":848725,"count":0}],"isBlockCoverage":false},{"functionName":"add$1","ranges":[{"startOffset":848738,"endOffset":849217,"count":1},{"startOffset":848863,"endOffset":848935,"count":0},{"startOffset":848992,"endOffset":849070,"count":0},{"startOffset":849110,"endOffset":849117,"count":0}],"isBlockCoverage":true},{"functionName":"addError$2","ranges":[{"startOffset":849235,"endOffset":849669,"count":0}],"isBlockCoverage":false},{"functionName":"addError$1","ranges":[{"startOffset":849687,"endOffset":849753,"count":0}],"isBlockCoverage":false},{"functionName":"_addError$2","ranges":[{"startOffset":849772,"endOffset":850246,"count":0}],"isBlockCoverage":false},{"functionName":"_addError$1","ranges":[{"startOffset":850265,"endOffset":850332,"count":0}],"isBlockCoverage":false},{"functionName":"addStream$1","ranges":[{"startOffset":850351,"endOffset":851289,"count":1},{"startOffset":850488,"endOffset":850561,"count":0},{"startOffset":850618,"endOffset":850697,"count":0},{"startOffset":850729,"endOffset":850857,"count":0}],"isBlockCoverage":true},{"functionName":"close$0","ranges":[{"startOffset":851304,"endOffset":851793,"count":0}],"isBlockCoverage":false},{"functionName":"_onStreamDisconnected$0","ranges":[{"startOffset":851824,"endOffset":852220,"count":0}],"isBlockCoverage":false},{"functionName":"set$_addStreamSubscription","ranges":[{"startOffset":852254,"endOffset":852404,"count":1}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":852558,"endOffset":852577,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":852665,"endOffset":852791,"count":0}],"isBlockCoverage":false},{"functionName":"_MultiChannel$1","ranges":[{"startOffset":852869,"endOffset":853574,"count":1}],"isBlockCoverage":true},{"functionName":"virtualChannel$1","ranges":[{"startOffset":853598,"endOffset":855403,"count":1},{"startOffset":854008,"endOffset":854335,"count":0},{"startOffset":854389,"endOffset":854435,"count":0},{"startOffset":854564,"endOffset":854665,"count":0}],"isBlockCoverage":true},{"functionName":"virtualChannel$0","ranges":[{"startOffset":855427,"endOffset":855487,"count":1}],"isBlockCoverage":true},{"functionName":"_closeChannel$2","ranges":[{"startOffset":855510,"endOffset":855929,"count":0}],"isBlockCoverage":false},{"functionName":"_closeInnerChannel$0","ranges":[{"startOffset":855957,"endOffset":856432,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":856514,"endOffset":856645,"count":1}],"isBlockCoverage":true},{"functionName":"$signature","ranges":[{"startOffset":856663,"endOffset":856718,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":856777,"endOffset":856842,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":856920,"endOffset":857524,"count":1},{"startOffset":857122,"endOffset":857129,"count":0},{"startOffset":857353,"endOffset":857369,"count":0},{"startOffset":857463,"endOffset":857518,"count":0}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":857602,"endOffset":857750,"count":0}],"isBlockCoverage":false},{"functionName":"$signature","ranges":[{"startOffset":857768,"endOffset":857847,"count":1}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":857920,"endOffset":858103,"count":1}],"isBlockCoverage":true},{"functionName":"$signature","ranges":[{"startOffset":858121,"endOffset":858184,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":858258,"endOffset":858370,"count":0}],"isBlockCoverage":false},{"functionName":"get$stream","ranges":[{"startOffset":858463,"endOffset":858515,"count":0}],"isBlockCoverage":false},{"functionName":"set$_stream_channel_completer$_channel","ranges":[{"startOffset":858606,"endOffset":858735,"count":1}],"isBlockCoverage":true},{"functionName":"get$local","ranges":[{"startOffset":858798,"endOffset":858842,"count":0}],"isBlockCoverage":false},{"functionName":"set$_local","ranges":[{"startOffset":858860,"endOffset":858957,"count":1}],"isBlockCoverage":true},{"functionName":"set$_foreign","ranges":[{"startOffset":858977,"endOffset":859080,"count":1}],"isBlockCoverage":true},{"functionName":"get$stream","ranges":[{"startOffset":859169,"endOffset":859221,"count":1}],"isBlockCoverage":true},{"functionName":"spanFrom$1","ranges":[{"startOffset":859374,"endOffset":859530,"count":0}],"isBlockCoverage":false},{"functionName":"matches$1","ranges":[{"startOffset":859547,"endOffset":859856,"count":0}],"isBlockCoverage":false},{"functionName":"error$3$length$position","ranges":[{"startOffset":859887,"endOffset":860142,"count":0}],"isBlockCoverage":false},{"functionName":"get$lastMatch","ranges":[{"startOffset":860259,"endOffset":860437,"count":0}],"isBlockCoverage":false},{"functionName":"peekChar$0","ranges":[{"startOffset":860455,"endOffset":860647,"count":0}],"isBlockCoverage":false},{"functionName":"scan$1","ranges":[{"startOffset":860661,"endOffset":860895,"count":0}],"isBlockCoverage":false},{"functionName":"expect$2$name","ranges":[{"startOffset":860916,"endOffset":861423,"count":0}],"isBlockCoverage":false},{"functionName":"expect$1","ranges":[{"startOffset":861439,"endOffset":861512,"count":0}],"isBlockCoverage":false},{"functionName":"matches$1","ranges":[{"startOffset":861529,"endOffset":861791,"count":0}],"isBlockCoverage":false},{"functionName":"glyphOrAscii$2","ranges":[{"startOffset":861849,"endOffset":861911,"count":0}],"isBlockCoverage":false},{"functionName":"get$horizontalLine","ranges":[{"startOffset":861937,"endOffset":861973,"count":0}],"isBlockCoverage":false},{"functionName":"get$verticalLine","ranges":[{"startOffset":861997,"endOffset":862033,"count":0}],"isBlockCoverage":false},{"functionName":"get$topLeftCorner","ranges":[{"startOffset":862058,"endOffset":862094,"count":0}],"isBlockCoverage":false},{"functionName":"get$bottomLeftCorner","ranges":[{"startOffset":862122,"endOffset":862158,"count":0}],"isBlockCoverage":false},{"functionName":"get$upEnd","ranges":[{"startOffset":862175,"endOffset":862211,"count":0}],"isBlockCoverage":false},{"functionName":"get$downEnd","ranges":[{"startOffset":862230,"endOffset":862266,"count":0}],"isBlockCoverage":false},{"functionName":"glyphOrAscii$2","ranges":[{"startOffset":862326,"endOffset":862382,"count":0}],"isBlockCoverage":false},{"functionName":"get$horizontalLine","ranges":[{"startOffset":862408,"endOffset":862449,"count":0}],"isBlockCoverage":false},{"functionName":"get$verticalLine","ranges":[{"startOffset":862473,"endOffset":862514,"count":0}],"isBlockCoverage":false},{"functionName":"get$topLeftCorner","ranges":[{"startOffset":862539,"endOffset":862580,"count":0}],"isBlockCoverage":false},{"functionName":"get$bottomLeftCorner","ranges":[{"startOffset":862608,"endOffset":862649,"count":0}],"isBlockCoverage":false},{"functionName":"get$upEnd","ranges":[{"startOffset":862666,"endOffset":862707,"count":0}],"isBlockCoverage":false},{"functionName":"get$downEnd","ranges":[{"startOffset":862726,"endOffset":862767,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":862840,"endOffset":864682,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":863077,"endOffset":864609,"count":1},{"startOffset":863167,"endOffset":863223,"count":0},{"startOffset":863485,"endOffset":863577,"count":0},{"startOffset":863993,"endOffset":864097,"count":0},{"startOffset":864323,"endOffset":864422,"count":0}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":864764,"endOffset":865103,"count":1}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":865187,"endOffset":865869,"count":1}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":865953,"endOffset":866183,"count":1}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":866268,"endOffset":866429,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":866513,"endOffset":866721,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":866796,"endOffset":866858,"count":0}],"isBlockCoverage":false},{"functionName":"test$9$onPlatform$retry$skip$solo$tags$testOn$timeout","ranges":[{"startOffset":866971,"endOffset":867811,"count":1},{"startOffset":867323,"endOffset":867326,"count":0},{"startOffset":867550,"endOffset":867557,"count":0},{"startOffset":867633,"endOffset":867659,"count":0}],"isBlockCoverage":true},{"functionName":"group$9$onPlatform$retry$skip$solo$tags$testOn$timeout","ranges":[{"startOffset":867873,"endOffset":869608,"count":1},{"startOffset":868325,"endOffset":868328,"count":0},{"startOffset":868584,"endOffset":868610,"count":0},{"startOffset":868684,"endOffset":868702,"count":0},{"startOffset":869525,"endOffset":869602,"count":0}],"isBlockCoverage":true},{"functionName":"build$0","ranges":[{"startOffset":869623,"endOffset":870102,"count":1}],"isBlockCoverage":true},{"functionName":"_checkNotBuilt$1","ranges":[{"startOffset":870126,"endOffset":870294,"count":1},{"startOffset":870183,"endOffset":870293,"count":0}],"isBlockCoverage":true},{"functionName":"_runSetUps$0","ranges":[{"startOffset":870314,"endOffset":871575,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":870514,"endOffset":871496,"count":1},{"startOffset":870604,"endOffset":870660,"count":0}],"isBlockCoverage":true},{"functionName":"get$_setUpAll","ranges":[{"startOffset":871596,"endOffset":871633,"count":1}],"isBlockCoverage":true},{"functionName":"get$_tearDownAll","ranges":[{"startOffset":871657,"endOffset":872027,"count":1},{"startOffset":871775,"endOffset":871849,"count":0},{"startOffset":871850,"endOffset":871873,"count":0}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":872085,"endOffset":874867,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":872360,"endOffset":874794,"count":1},{"startOffset":872450,"endOffset":872506,"count":0},{"startOffset":873288,"endOffset":874287,"count":0}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":874945,"endOffset":875120,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":875200,"endOffset":876169,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":875387,"endOffset":876096,"count":1},{"startOffset":875477,"endOffset":875533,"count":0}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":876247,"endOffset":876413,"count":1},{"startOffset":876332,"endOffset":876412,"count":0}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":876491,"endOffset":876827,"count":1},{"startOffset":876626,"endOffset":876669,"count":0},{"startOffset":876670,"endOffset":876812,"count":0}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":876911,"endOffset":876963,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":877049,"endOffset":877288,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":877374,"endOffset":877550,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":877637,"endOffset":879068,"count":0}],"isBlockCoverage":false},{"functionName":"forPlatform$1","ranges":[{"startOffset":879136,"endOffset":879644,"count":1},{"startOffset":879307,"endOffset":879319,"count":0},{"startOffset":879481,"endOffset":879510,"count":0},{"startOffset":879520,"endOffset":879532,"count":0}],"isBlockCoverage":true},{"functionName":"_group$_map$1","ranges":[{"startOffset":879665,"endOffset":880149,"count":1}],"isBlockCoverage":true},{"functionName":"get$name","ranges":[{"startOffset":880187,"endOffset":880237,"count":0}],"isBlockCoverage":false},{"functionName":"get$metadata","ranges":[{"startOffset":880257,"endOffset":880303,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":880365,"endOffset":880437,"count":1}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":880512,"endOffset":880602,"count":1}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":880678,"endOffset":880754,"count":1}],"isBlockCoverage":true},{"functionName":"load$2$groups","ranges":[{"startOffset":880858,"endOffset":881479,"count":1}],"isBlockCoverage":true},{"functionName":"forPlatform$1","ranges":[{"startOffset":881500,"endOffset":881799,"count":1},{"startOffset":881648,"endOffset":881660,"count":0}],"isBlockCoverage":true},{"functionName":"get$name","ranges":[{"startOffset":881815,"endOffset":881865,"count":0}],"isBlockCoverage":false},{"functionName":"get$metadata","ranges":[{"startOffset":881885,"endOffset":881931,"count":0}],"isBlockCoverage":false},{"functionName":"get$_outstandingCallbacks","ranges":[{"startOffset":881994,"endOffset":882266,"count":1},{"startOffset":882147,"endOffset":882265,"count":0}],"isBlockCoverage":true},{"functionName":"addOutstandingCallback$0","ranges":[{"startOffset":882298,"endOffset":882562,"count":0}],"isBlockCoverage":false},{"functionName":"waitForOutstandingCallbacks$1","ranges":[{"startOffset":882599,"endOffset":883243,"count":1}],"isBlockCoverage":true},{"functionName":"unclosable$1$1","ranges":[{"startOffset":883265,"endOffset":883512,"count":1}],"isBlockCoverage":true},{"functionName":"heartbeat$0","ranges":[{"startOffset":883531,"endOffset":884164,"count":1},{"startOffset":883729,"endOffset":883736,"count":0},{"startOffset":883989,"endOffset":883996,"count":0}],"isBlockCoverage":true},{"functionName":"_handleError$3","ranges":[{"startOffset":884186,"endOffset":885992,"count":0}],"isBlockCoverage":false},{"functionName":"_handleError$2","ranges":[{"startOffset":886014,"endOffset":886096,"count":0}],"isBlockCoverage":false},{"functionName":"_invoker$_onRun$0","ranges":[{"startOffset":886121,"endOffset":886628,"count":1}],"isBlockCoverage":true},{"functionName":"_runTearDowns$0","ranges":[{"startOffset":886651,"endOffset":888109,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":886875,"endOffset":888027,"count":1},{"startOffset":886965,"endOffset":887021,"count":0},{"startOffset":887385,"endOffset":887694,"count":0},{"startOffset":887707,"endOffset":887841,"count":0}],"isBlockCoverage":true},{"functionName":"call$5","ranges":[{"startOffset":888167,"endOffset":888558,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":888637,"endOffset":888765,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":888865,"endOffset":889886,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":889058,"endOffset":889813,"count":1},{"startOffset":889148,"endOffset":889204,"count":0}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":889986,"endOffset":890094,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":890175,"endOffset":890978,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":891060,"endOffset":891255,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":891337,"endOffset":891638,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":891722,"endOffset":891930,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":892015,"endOffset":892186,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":892264,"endOffset":892515,"count":1},{"startOffset":892445,"endOffset":892477,"count":0}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":892594,"endOffset":893102,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":893182,"endOffset":895232,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":893413,"endOffset":895159,"count":1},{"startOffset":893503,"endOffset":893559,"count":0},{"startOffset":894440,"endOffset":894592,"count":0},{"startOffset":894648,"endOffset":894893,"count":0}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":895313,"endOffset":896507,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":895504,"endOffset":896434,"count":1},{"startOffset":895594,"endOffset":895650,"count":0}],"isBlockCoverage":true},{"functionName":"call$4","ranges":[{"startOffset":896588,"endOffset":896759,"count":0}],"isBlockCoverage":false},{"functionName":"decrement$0","ranges":[{"startOffset":896834,"endOffset":897013,"count":1},{"startOffset":896886,"endOffset":896893,"count":0},{"startOffset":896977,"endOffset":896984,"count":0}],"isBlockCoverage":true},{"functionName":"run$0","ranges":[{"startOffset":897087,"endOffset":897565,"count":0}],"isBlockCoverage":false},{"functionName":"addError$2","ranges":[{"startOffset":897624,"endOffset":897936,"count":0}],"isBlockCoverage":false},{"functionName":"setState$1","ranges":[{"startOffset":897954,"endOffset":898264,"count":1},{"startOffset":898062,"endOffset":898069,"count":0},{"startOffset":898141,"endOffset":898148,"count":0}],"isBlockCoverage":true},{"functionName":"message$1","ranges":[{"startOffset":898281,"endOffset":898470,"count":0}],"isBlockCoverage":false},{"functionName":"_live_test_controller$_run$0","ranges":[{"startOffset":898506,"endOffset":898976,"count":1},{"startOffset":898579,"endOffset":898668,"count":0},{"startOffset":898737,"endOffset":898829,"count":0}],"isBlockCoverage":true},{"functionName":"_live_test_controller$_close$0","ranges":[{"startOffset":899014,"endOffset":899384,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":899464,"endOffset":899507,"count":0}],"isBlockCoverage":false},{"functionName":"_validateTags$0","ranges":[{"startOffset":899561,"endOffset":900156,"count":1},{"startOffset":899966,"endOffset":900155,"count":0}],"isBlockCoverage":true},{"functionName":"validatePlatformSelectors$1","ranges":[{"startOffset":900191,"endOffset":900418,"count":1}],"isBlockCoverage":true},{"functionName":"merge$1","ranges":[{"startOffset":900433,"endOffset":901393,"count":1}],"isBlockCoverage":true},{"functionName":"change$4$onPlatform$skip$skipReason$timeout","ranges":[{"startOffset":901444,"endOffset":902100,"count":0}],"isBlockCoverage":false},{"functionName":"change$1$onPlatform","ranges":[{"startOffset":902127,"endOffset":902248,"count":0}],"isBlockCoverage":false},{"functionName":"change$1$timeout","ranges":[{"startOffset":902272,"endOffset":902387,"count":0}],"isBlockCoverage":false},{"functionName":"change$2$skip$skipReason","ranges":[{"startOffset":902419,"endOffset":902546,"count":0}],"isBlockCoverage":false},{"functionName":"forPlatform$1","ranges":[{"startOffset":902567,"endOffset":902916,"count":1},{"startOffset":902687,"endOffset":902915,"count":0}],"isBlockCoverage":true},{"functionName":"serialize$0","ranges":[{"startOffset":902935,"endOffset":903762,"count":1},{"startOffset":903242,"endOffset":903261,"count":0}],"isBlockCoverage":true},{"functionName":"_serializeTimeout$1","ranges":[{"startOffset":903789,"endOffset":904104,"count":1},{"startOffset":903878,"endOffset":903892,"count":0},{"startOffset":903951,"endOffset":903965,"count":0}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":904170,"endOffset":904437,"count":1}],"isBlockCoverage":true},{"functionName":"call$2","ranges":[{"startOffset":904519,"endOffset":904859,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":904944,"endOffset":905183,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":905270,"endOffset":905391,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":905478,"endOffset":905558,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":905656,"endOffset":905903,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":905982,"endOffset":906116,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":906196,"endOffset":906330,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":906415,"endOffset":906760,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":906843,"endOffset":907050,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":907134,"endOffset":907382,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":907458,"endOffset":907501,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":907566,"endOffset":907676,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":907762,"endOffset":907799,"count":1}],"isBlockCoverage":true},{"functionName":"validate$1","ranges":[{"startOffset":907875,"endOffset":908141,"count":1},{"startOffset":908007,"endOffset":908140,"count":0}],"isBlockCoverage":true},{"functionName":"evaluate$1","ranges":[{"startOffset":908159,"endOffset":908292,"count":1}],"isBlockCoverage":true},{"functionName":"intersection$1","ranges":[{"startOffset":908314,"endOffset":908542,"count":1},{"startOffset":908448,"endOffset":908541,"count":0}],"isBlockCoverage":true},{"functionName":"toString$0","ranges":[{"startOffset":908560,"endOffset":908639,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":908650,"endOffset":908848,"count":0}],"isBlockCoverage":false},{"functionName":"get$hashCode","ranges":[{"startOffset":908868,"endOffset":908949,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":909016,"endOffset":909144,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":909234,"endOffset":909377,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":909467,"endOffset":909603,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":909692,"endOffset":910518,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":910585,"endOffset":910628,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":910700,"endOffset":910802,"count":1}],"isBlockCoverage":true},{"functionName":"configure$3$except$mapper$only","ranges":[{"startOffset":910902,"endOffset":911192,"count":1},{"startOffset":911107,"endOffset":911132,"count":0},{"startOffset":911165,"endOffset":911186,"count":0}],"isBlockCoverage":true},{"functionName":"configure$2$except$only","ranges":[{"startOffset":911223,"endOffset":911323,"count":1}],"isBlockCoverage":true},{"functionName":"configure$1$mapper","ranges":[{"startOffset":911349,"endOffset":911443,"count":1}],"isBlockCoverage":true},{"functionName":"formatStackTrace$2$verbose","ranges":[{"startOffset":911477,"endOffset":912169,"count":0}],"isBlockCoverage":false},{"functionName":"set$_except","ranges":[{"startOffset":912188,"endOffset":912268,"count":0}],"isBlockCoverage":false},{"functionName":"set$_only","ranges":[{"startOffset":912285,"endOffset":912359,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":912440,"endOffset":912670,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":912729,"endOffset":912905,"count":1},{"startOffset":912783,"endOffset":912796,"count":0},{"startOffset":912867,"endOffset":912898,"count":0}],"isBlockCoverage":true},{"functionName":"get$hashCode","ranges":[{"startOffset":912925,"endOffset":913054,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":913072,"endOffset":913383,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":913430,"endOffset":913473,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":913521,"endOffset":913564,"count":0}],"isBlockCoverage":false},{"functionName":"toString$0","ranges":[{"startOffset":913717,"endOffset":913763,"count":0}],"isBlockCoverage":false},{"functionName":"get$message","ranges":[{"startOffset":913782,"endOffset":913835,"count":0}],"isBlockCoverage":false},{"functionName":"call$5","ranges":[{"startOffset":913888,"endOffset":914188,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":914260,"endOffset":914278,"count":1}],"isBlockCoverage":true},{"functionName":"merge$1","ranges":[{"startOffset":914342,"endOffset":915081,"count":1},{"startOffset":914467,"endOffset":914494,"count":0},{"startOffset":914552,"endOffset":914583,"count":0},{"startOffset":914632,"endOffset":914853,"count":0},{"startOffset":914955,"endOffset":914972,"count":0},{"startOffset":915015,"endOffset":915032,"count":0}],"isBlockCoverage":true},{"functionName":"apply$1","ranges":[{"startOffset":915096,"endOffset":915445,"count":1},{"startOffset":915179,"endOffset":915191,"count":0},{"startOffset":915319,"endOffset":915336,"count":0}],"isBlockCoverage":true},{"functionName":"get$hashCode","ranges":[{"startOffset":915465,"endOffset":915577,"count":0}],"isBlockCoverage":false},{"functionName":"$eq","ranges":[{"startOffset":915588,"endOffset":915784,"count":1},{"startOffset":915642,"endOffset":915655,"count":0}],"isBlockCoverage":true},{"functionName":"toString$0","ranges":[{"startOffset":915802,"endOffset":916008,"count":0}],"isBlockCoverage":false},{"functionName":"_serializeGroup$3","ranges":[{"startOffset":916070,"endOffset":917084,"count":1},{"startOffset":916473,"endOffset":916517,"count":0}],"isBlockCoverage":true},{"functionName":"_serializeTest$3","ranges":[{"startOffset":917108,"endOffset":917764,"count":1},{"startOffset":917540,"endOffset":917584,"count":0}],"isBlockCoverage":true},{"functionName":"_runLiveTest$2","ranges":[{"startOffset":917786,"endOffset":918752,"count":1}],"isBlockCoverage":true},{"functionName":"call$4","ranges":[{"startOffset":918817,"endOffset":919145,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":919231,"endOffset":919250,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":919335,"endOffset":919891,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":919976,"endOffset":920289,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":920375,"endOffset":925739,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":920701,"endOffset":925666,"count":1},{"startOffset":920791,"endOffset":920847,"count":0},{"startOffset":921070,"endOffset":921774,"count":0},{"startOffset":921820,"endOffset":922042,"count":0},{"startOffset":922087,"endOffset":922311,"count":0},{"startOffset":923050,"endOffset":923091,"count":0},{"startOffset":923567,"endOffset":923597,"count":0}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":925826,"endOffset":927365,"count":1},{"startOffset":925967,"endOffset":926041,"count":0},{"startOffset":926601,"endOffset":926671,"count":0},{"startOffset":927004,"endOffset":927069,"count":0},{"startOffset":927115,"endOffset":927359,"count":0}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":927453,"endOffset":927588,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":927676,"endOffset":928035,"count":1}],"isBlockCoverage":true},{"functionName":"call$2","ranges":[{"startOffset":928122,"endOffset":928276,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":928370,"endOffset":928663,"count":1}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":928757,"endOffset":929103,"count":1}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":929194,"endOffset":929380,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":929472,"endOffset":929733,"count":1}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":929826,"endOffset":930378,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":930471,"endOffset":930825,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":930918,"endOffset":931101,"count":1}],"isBlockCoverage":true},{"functionName":"call$1","ranges":[{"startOffset":931193,"endOffset":931352,"count":1}],"isBlockCoverage":true},{"functionName":"connectOut$1","ranges":[{"startOffset":931434,"endOffset":932439,"count":1},{"startOffset":931560,"endOffset":931587,"count":0},{"startOffset":931674,"endOffset":931767,"count":0}],"isBlockCoverage":true},{"functionName":"get$length","ranges":[{"startOffset":932491,"endOffset":932603,"count":0}],"isBlockCoverage":false},{"functionName":"get$iterator","ranges":[{"startOffset":932623,"endOffset":932784,"count":0}],"isBlockCoverage":false},{"functionName":"toSet$0","ranges":[{"startOffset":932799,"endOffset":932888,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":933051,"endOffset":933169,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":933252,"endOffset":933441,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":933506,"endOffset":933888,"count":0}],"isBlockCoverage":false},{"functionName":"get$_onUnpaused","ranges":[{"startOffset":933961,"endOffset":934099,"count":0}],"isBlockCoverage":false},{"functionName":"get$success","ranges":[{"startOffset":934118,"endOffset":935569,"count":0}],"isBlockCoverage":false},{"functionName":"get$liveTests","ranges":[{"startOffset":935590,"endOffset":936069,"count":0}],"isBlockCoverage":false},{"functionName":"Engine$3$concurrency$coverage$maxSuites","ranges":[{"startOffset":936116,"endOffset":936304,"count":0}],"isBlockCoverage":false},{"functionName":"run$0","ranges":[{"startOffset":936317,"endOffset":936943,"count":0}],"isBlockCoverage":false},{"functionName":"_runGroup$3","ranges":[{"startOffset":936962,"endOffset":937125,"count":0}],"isBlockCoverage":false},{"functionName":"_runGroup$body$Engine","ranges":[{"startOffset":937154,"endOffset":945098,"count":0}],"isBlockCoverage":false},{"functionName":"_engine$_runLiveTest$3$countSuccess","ranges":[{"startOffset":945141,"endOffset":945283,"count":0}],"isBlockCoverage":false},{"functionName":"_engine$_runLiveTest$2","ranges":[{"startOffset":945313,"endOffset":945444,"count":0}],"isBlockCoverage":false},{"functionName":"_runLiveTest$body$Engine","ranges":[{"startOffset":945476,"endOffset":948266,"count":0}],"isBlockCoverage":false},{"functionName":"_runSkippedTest$3","ranges":[{"startOffset":948291,"endOffset":948443,"count":0}],"isBlockCoverage":false},{"functionName":"_runSkippedTest$body$Engine","ranges":[{"startOffset":948478,"endOffset":950257,"count":0}],"isBlockCoverage":false},{"functionName":"_addLiveSuite$1","ranges":[{"startOffset":950280,"endOffset":951177,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":951236,"endOffset":951499,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":951570,"endOffset":951828,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":951900,"endOffset":951919,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":951993,"endOffset":952254,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":952330,"endOffset":952400,"count":0}],"isBlockCoverage":false},{"functionName":"$call$body$Engine_run__closure","ranges":[{"startOffset":952438,"endOffset":953809,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":953885,"endOffset":955623,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":955700,"endOffset":955766,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":955842,"endOffset":956062,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":956145,"endOffset":956558,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":956643,"endOffset":956740,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":956824,"endOffset":956842,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":956928,"endOffset":956946,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":957033,"endOffset":957502,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":957589,"endOffset":957607,"count":0}],"isBlockCoverage":false},{"functionName":"LiveSuiteController$1","ranges":[{"startOffset":957758,"endOffset":958027,"count":0}],"isBlockCoverage":false},{"functionName":"reportLiveTest$2$countSuccess","ranges":[{"startOffset":958064,"endOffset":958889,"count":0}],"isBlockCoverage":false},{"functionName":"close$0","ranges":[{"startOffset":958904,"endOffset":959033,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":959097,"endOffset":959152,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":959237,"endOffset":959256,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":959354,"endOffset":959967,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":960057,"endOffset":961699,"count":0}],"isBlockCoverage":false},{"functionName":"_onTestStarted$1","ranges":[{"startOffset":961781,"endOffset":963251,"count":0}],"isBlockCoverage":false},{"functionName":"_onStateChange$2","ranges":[{"startOffset":963275,"endOffset":963683,"count":0}],"isBlockCoverage":false},{"functionName":"_expanded$_onError$3","ranges":[{"startOffset":963711,"endOffset":964186,"count":0}],"isBlockCoverage":false},{"functionName":"_expanded$_onDone$1","ranges":[{"startOffset":964213,"endOffset":965237,"count":0}],"isBlockCoverage":false},{"functionName":"_progressLine$3$color$suffix","ranges":[{"startOffset":965273,"endOffset":967686,"count":0}],"isBlockCoverage":false},{"functionName":"_progressLine$2$suffix","ranges":[{"startOffset":967716,"endOffset":967820,"count":0}],"isBlockCoverage":false},{"functionName":"_progressLine$2$color","ranges":[{"startOffset":967849,"endOffset":967951,"count":0}],"isBlockCoverage":false},{"functionName":"_progressLine$1","ranges":[{"startOffset":967974,"endOffset":968068,"count":0}],"isBlockCoverage":false},{"functionName":"_description$1","ranges":[{"startOffset":968090,"endOffset":968203,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":968279,"endOffset":968386,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":968484,"endOffset":968637,"count":0}],"isBlockCoverage":false},{"functionName":"call$1","ranges":[{"startOffset":968735,"endOffset":969071,"count":0}],"isBlockCoverage":false},{"functionName":"_runner_suite$_close$0","ranges":[{"startOffset":969197,"endOffset":969305,"count":0}],"isBlockCoverage":false},{"functionName":"set$_runner_suite$_suite","ranges":[{"startOffset":969337,"endOffset":969408,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":969481,"endOffset":970301,"count":0}],"isBlockCoverage":false},{"functionName":"writeln$1","ranges":[{"startOffset":970445,"endOffset":970545,"count":0}],"isBlockCoverage":false},{"functionName":"_flush$0","ranges":[{"startOffset":970561,"endOffset":970743,"count":0}],"isBlockCoverage":false},{"functionName":"call$2","ranges":[{"startOffset":970896,"endOffset":971053,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":971128,"endOffset":974564,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":974638,"endOffset":974728,"count":0}],"isBlockCoverage":false},{"functionName":"call$0","ranges":[{"startOffset":974798,"endOffset":974868,"count":1}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":974937,"endOffset":975688,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":975109,"endOffset":975615,"count":1},{"startOffset":975199,"endOffset":975255,"count":0},{"startOffset":975440,"endOffset":975452,"count":0}],"isBlockCoverage":true},{"functionName":"call$0","ranges":[{"startOffset":975756,"endOffset":975816,"count":1}],"isBlockCoverage":true},{"functionName":"aliases","ranges":[{"startOffset":975846,"endOffset":976773,"count":1}],"isBlockCoverage":true},{"functionName":"installTearOffs","ranges":[{"startOffset":976781,"endOffset":984996,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":978155,"endOffset":978225,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":978518,"endOffset":978624,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":978734,"endOffset":978870,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":979096,"endOffset":979215,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":979350,"endOffset":979489,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":979626,"endOffset":979781,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":984923,"endOffset":984982,"count":0}],"isBlockCoverage":false},{"functionName":"inheritance","ranges":[{"startOffset":985004,"endOffset":1007745,"count":1}],"isBlockCoverage":true},{"functionName":"rtii","ranges":[{"startOffset":1029633,"endOffset":1042848,"count":1},{"startOffset":1042844,"endOffset":1042847,"count":0}],"isBlockCoverage":true},{"functionName":"constants","ranges":[{"startOffset":1042856,"endOffset":1058765,"count":1}],"isBlockCoverage":true},{"functionName":"getTagFallback","ranges":[{"startOffset":1044143,"endOffset":1044257,"count":0}],"isBlockCoverage":false},{"functionName":"C.C_JS_CONST0","ranges":[{"startOffset":1044279,"endOffset":1045394,"count":1},{"startOffset":1045302,"endOffset":1045317,"count":0}],"isBlockCoverage":true},{"functionName":"getTag","ranges":[{"startOffset":1044346,"endOffset":1044449,"count":0}],"isBlockCoverage":false},{"functionName":"getUnknownTag","ranges":[{"startOffset":1044452,"endOffset":1044669,"count":0}],"isBlockCoverage":false},{"functionName":"getUnknownTagGenericBrowser","ranges":[{"startOffset":1044672,"endOffset":1044847,"count":0}],"isBlockCoverage":false},{"functionName":"prototypeForTag","ranges":[{"startOffset":1044850,"endOffset":1045116,"count":1},{"startOffset":1044920,"endOffset":1044932,"count":0},{"startOffset":1045066,"endOffset":1045078,"count":0}],"isBlockCoverage":true},{"functionName":"discriminator","ranges":[{"startOffset":1045119,"endOffset":1045163,"count":0}],"isBlockCoverage":false},{"functionName":"C.C_JS_CONST6","ranges":[{"startOffset":1045416,"endOffset":1045880,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1045452,"endOffset":1045877,"count":1},{"startOffset":1045508,"endOffset":1045521,"count":0},{"startOffset":1045599,"endOffset":1045612,"count":0},{"startOffset":1045838,"endOffset":1045876,"count":0}],"isBlockCoverage":true},{"functionName":"confirm","ranges":[{"startOffset":1045656,"endOffset":1045763,"count":1}],"isBlockCoverage":true},{"functionName":"C.C_JS_CONST1","ranges":[{"startOffset":1045902,"endOffset":1046051,"count":1},{"startOffset":1045989,"endOffset":1046050,"count":0}],"isBlockCoverage":true},{"functionName":"C.C_JS_CONST2","ranges":[{"startOffset":1046073,"endOffset":1046546,"count":1}],"isBlockCoverage":true},{"functionName":"getTagFixed","ranges":[{"startOffset":1046169,"endOffset":1046350,"count":0}],"isBlockCoverage":false},{"functionName":"prototypeForTagFixed","ranges":[{"startOffset":1046353,"endOffset":1046466,"count":1},{"startOffset":1046417,"endOffset":1046429,"count":0}],"isBlockCoverage":true},{"functionName":"C.C_JS_CONST5","ranges":[{"startOffset":1046568,"endOffset":1047098,"count":1},{"startOffset":1046655,"endOffset":1046659,"count":0},{"startOffset":1046716,"endOffset":1047097,"count":0}],"isBlockCoverage":true},{"functionName":"getTagFirefox","ranges":[{"startOffset":1046975,"endOffset":1047064,"count":0}],"isBlockCoverage":false},{"functionName":"C.C_JS_CONST4","ranges":[{"startOffset":1047120,"endOffset":1048002,"count":1},{"startOffset":1047207,"endOffset":1047211,"count":0},{"startOffset":1047269,"endOffset":1048001,"count":0}],"isBlockCoverage":true},{"functionName":"getTagIE","ranges":[{"startOffset":1047534,"endOffset":1047777,"count":0}],"isBlockCoverage":false},{"functionName":"prototypeForTagIE","ranges":[{"startOffset":1047780,"endOffset":1047928,"count":0}],"isBlockCoverage":false},{"functionName":"C.C_JS_CONST3","ranges":[{"startOffset":1048024,"endOffset":1048057,"count":1}],"isBlockCoverage":true},{"functionName":"staticFields","ranges":[{"startOffset":1058773,"endOffset":1059835,"count":1}],"isBlockCoverage":true},{"functionName":"lazyInitializers","ranges":[{"startOffset":1059843,"endOffset":1071865,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1059985,"endOffset":1060063,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1060144,"endOffset":1060213,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1060314,"endOffset":1060502,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1060599,"endOffset":1060802,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1060895,"endOffset":1061006,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1061113,"endOffset":1061364,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1061467,"endOffset":1061580,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1061697,"endOffset":1061952,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1062053,"endOffset":1062168,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1062283,"endOffset":1062472,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1062583,"endOffset":1062700,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1062825,"endOffset":1063018,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1063117,"endOffset":1063192,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1063257,"endOffset":1063342,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1063407,"endOffset":1063495,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1063564,"endOffset":1063625,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1063716,"endOffset":1064340,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1064411,"endOffset":1064569,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1064640,"endOffset":1064721,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1064794,"endOffset":1064854,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1064911,"endOffset":1064961,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1065054,"endOffset":1065143,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1065214,"endOffset":1065295,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1065367,"endOffset":1065450,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1065513,"endOffset":1065593,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1065648,"endOffset":1065830,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1065873,"endOffset":1065947,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1065982,"endOffset":1066052,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1066095,"endOffset":1066172,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1066223,"endOffset":1066364,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1066419,"endOffset":1066666,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1066713,"endOffset":1066941,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1066998,"endOffset":1067058,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1067101,"endOffset":1067158,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1067205,"endOffset":1067275,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1067322,"endOffset":1067389,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1067434,"endOffset":1067481,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1067526,"endOffset":1067631,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1067676,"endOffset":1067794,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1067851,"endOffset":1067937,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1067996,"endOffset":1068106,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1068173,"endOffset":1068312,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1068369,"endOffset":1068474,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1068523,"endOffset":1068622,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1068673,"endOffset":1068736,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1068797,"endOffset":1068883,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1068952,"endOffset":1069039,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1069134,"endOffset":1069181,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1069234,"endOffset":1069315,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1069360,"endOffset":1069430,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1069483,"endOffset":1069550,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1069617,"endOffset":1069721,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1069778,"endOffset":1069882,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1069939,"endOffset":1070078,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1070155,"endOffset":1070594,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1070646,"endOffset":1070693,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1070745,"endOffset":1070792,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1070849,"endOffset":1070907,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1070979,"endOffset":1071062,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1071147,"endOffset":1071254,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1071331,"endOffset":1071859,"count":0}],"isBlockCoverage":false},{"functionName":"nativeSupport","ranges":[{"startOffset":1071873,"endOffset":1079153,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1071905,"endOffset":1072690,"count":1},{"startOffset":1072593,"endOffset":1072689,"count":0}],"isBlockCoverage":true},{"functionName":"intern","ranges":[{"startOffset":1071937,"endOffset":1072063,"count":1}],"isBlockCoverage":true},{"functionName":"init.getIsolateTag","ranges":[{"startOffset":1072092,"endOffset":1072176,"count":1}],"isBlockCoverage":true},{"functionName":"Function.call$0","ranges":[{"startOffset":1079188,"endOffset":1079223,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$1","ranges":[{"startOffset":1079255,"endOffset":1079292,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$2","ranges":[{"startOffset":1079324,"endOffset":1079367,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$3$3","ranges":[{"startOffset":1079401,"endOffset":1079450,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$2$2","ranges":[{"startOffset":1079484,"endOffset":1079527,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$1$1","ranges":[{"startOffset":1079561,"endOffset":1079598,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$2$1","ranges":[{"startOffset":1079632,"endOffset":1079669,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$3","ranges":[{"startOffset":1079701,"endOffset":1079750,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$4","ranges":[{"startOffset":1079782,"endOffset":1079837,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$3$1","ranges":[{"startOffset":1079871,"endOffset":1079908,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$2$3","ranges":[{"startOffset":1079942,"endOffset":1079991,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$1$2","ranges":[{"startOffset":1080025,"endOffset":1080068,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$5","ranges":[{"startOffset":1080100,"endOffset":1080161,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$3$4","ranges":[{"startOffset":1080195,"endOffset":1080250,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$2$4","ranges":[{"startOffset":1080284,"endOffset":1080339,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$1$4","ranges":[{"startOffset":1080373,"endOffset":1080428,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$3$6","ranges":[{"startOffset":1080462,"endOffset":1080529,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$2$5","ranges":[{"startOffset":1080563,"endOffset":1080624,"count":0}],"isBlockCoverage":false},{"functionName":"Function.call$2$0","ranges":[{"startOffset":1080658,"endOffset":1080693,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1080759,"endOffset":1081298,"count":1},{"startOffset":1080821,"endOffset":1080919,"count":0},{"startOffset":1080974,"endOffset":1081297,"count":0}],"isBlockCoverage":true},{"functionName":"onLoad","ranges":[{"startOffset":1081021,"endOffset":1081191,"count":0}],"isBlockCoverage":false},{"functionName":"","ranges":[{"startOffset":1081300,"endOffset":1081476,"count":1},{"startOffset":1081418,"endOffset":1081445,"count":0}],"isBlockCoverage":true}]},{"scriptId":"32","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":284,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":283,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff_startsWith$20","ranges":[{"startOffset":82,"endOffset":281,"count":0}],"isBlockCoverage":false}]},{"scriptId":"33","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":271,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":270,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff__onPause$01","ranges":[{"startOffset":82,"endOffset":268,"count":0}],"isBlockCoverage":false}]},{"scriptId":"34","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":272,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":271,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff__onResume$02","ranges":[{"startOffset":82,"endOffset":269,"count":0}],"isBlockCoverage":false}]},{"scriptId":"35","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":277,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":276,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff_add$13","ranges":[{"startOffset":82,"endOffset":274,"count":0}],"isBlockCoverage":false}]},{"scriptId":"36","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":271,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":270,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff_addError$24","ranges":[{"startOffset":82,"endOffset":268,"count":0}],"isBlockCoverage":false}]},{"scriptId":"37","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":271,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":270,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff_complete$15","ranges":[{"startOffset":82,"endOffset":268,"count":1}],"isBlockCoverage":true}]},{"scriptId":"38","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":271,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":270,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff_complete$16","ranges":[{"startOffset":82,"endOffset":268,"count":1}],"isBlockCoverage":true}]},{"scriptId":"39","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":277,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":276,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff__completeError$27","ranges":[{"startOffset":82,"endOffset":274,"count":1}],"isBlockCoverage":true}]},{"scriptId":"40","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":277,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":276,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff_add$18","ranges":[{"startOffset":82,"endOffset":274,"count":1}],"isBlockCoverage":true}]},{"scriptId":"41","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":271,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":270,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff_addError$29","ranges":[{"startOffset":82,"endOffset":268,"count":1}],"isBlockCoverage":true}]},{"scriptId":"42","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":280,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":279,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff_close$010","ranges":[{"startOffset":82,"endOffset":277,"count":1}],"isBlockCoverage":true}]},{"scriptId":"43","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":268,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":267,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff__add$111","ranges":[{"startOffset":82,"endOffset":265,"count":1}],"isBlockCoverage":true}]},{"scriptId":"44","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":280,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":279,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff__async$_addError$212","ranges":[{"startOffset":82,"endOffset":277,"count":1}],"isBlockCoverage":true}]},{"scriptId":"45","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":270,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":269,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff__close$013","ranges":[{"startOffset":82,"endOffset":267,"count":1}],"isBlockCoverage":true}]},{"scriptId":"46","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":272,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":271,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff__onPause$014","ranges":[{"startOffset":82,"endOffset":269,"count":0}],"isBlockCoverage":false}]},{"scriptId":"47","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":273,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":272,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff__onResume$015","ranges":[{"startOffset":82,"endOffset":270,"count":1}],"isBlockCoverage":true}]},{"scriptId":"48","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":278,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":277,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff_add$116","ranges":[{"startOffset":82,"endOffset":275,"count":1}],"isBlockCoverage":true}]},{"scriptId":"49","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":272,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":271,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff__onPause$017","ranges":[{"startOffset":82,"endOffset":269,"count":0}],"isBlockCoverage":false}]},{"scriptId":"50","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":273,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":272,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff__onResume$018","ranges":[{"startOffset":82,"endOffset":270,"count":0}],"isBlockCoverage":false}]},{"scriptId":"51","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":273,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":272,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff__sendDone$019","ranges":[{"startOffset":82,"endOffset":270,"count":0}],"isBlockCoverage":false}]},{"scriptId":"52","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":283,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":282,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff_contains$120","ranges":[{"startOffset":82,"endOffset":280,"count":0}],"isBlockCoverage":false}]},{"scriptId":"53","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":280,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":279,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff_close$021","ranges":[{"startOffset":82,"endOffset":277,"count":0}],"isBlockCoverage":false}]},{"scriptId":"54","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":273,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":272,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff__onListen$022","ranges":[{"startOffset":82,"endOffset":270,"count":0}],"isBlockCoverage":false}]},{"scriptId":"55","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":282,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":281,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff__onCancelBroadcast$023","ranges":[{"startOffset":82,"endOffset":279,"count":0}],"isBlockCoverage":false}]},{"scriptId":"56","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":283,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":282,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff_contains$124","ranges":[{"startOffset":82,"endOffset":280,"count":0}],"isBlockCoverage":false}]},{"scriptId":"57","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":279,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":278,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff__recursiveMatch$425","ranges":[{"startOffset":82,"endOffset":276,"count":0}],"isBlockCoverage":false}]},{"scriptId":"58","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":288,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":287,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff_message$2$color26","ranges":[{"startOffset":82,"endOffset":285,"count":0}],"isBlockCoverage":false}]},{"scriptId":"59","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":309,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":308,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff__stack_zone_specification$_registerCallback$1$427","ranges":[{"startOffset":82,"endOffset":306,"count":1}],"isBlockCoverage":true}]},{"scriptId":"60","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":314,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":313,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff__stack_zone_specification$_registerUnaryCallback$2$428","ranges":[{"startOffset":82,"endOffset":311,"count":1}],"isBlockCoverage":true}]},{"scriptId":"61","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":315,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":314,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff__stack_zone_specification$_registerBinaryCallback$3$429","ranges":[{"startOffset":82,"endOffset":312,"count":1}],"isBlockCoverage":true}]},{"scriptId":"62","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":304,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":303,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff__stack_zone_specification$_errorCallback$530","ranges":[{"startOffset":82,"endOffset":301,"count":1}],"isBlockCoverage":true}]},{"scriptId":"63","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":272,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":271,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff_addError$231","ranges":[{"startOffset":82,"endOffset":269,"count":1}],"isBlockCoverage":true}]},{"scriptId":"64","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":273,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":272,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff__addError$232","ranges":[{"startOffset":82,"endOffset":270,"count":1}],"isBlockCoverage":true}]},{"scriptId":"65","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":280,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":279,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff_close$033","ranges":[{"startOffset":82,"endOffset":277,"count":0}],"isBlockCoverage":false}]},{"scriptId":"66","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":282,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":281,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff__closeInnerChannel$034","ranges":[{"startOffset":82,"endOffset":279,"count":1}],"isBlockCoverage":true}]},{"scriptId":"67","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":279,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":278,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff__invoker$_onRun$035","ranges":[{"startOffset":82,"endOffset":276,"count":1}],"isBlockCoverage":true}]},{"scriptId":"68","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":277,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":276,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff__runTearDowns$036","ranges":[{"startOffset":82,"endOffset":274,"count":1}],"isBlockCoverage":true}]},{"scriptId":"69","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":267,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":266,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff_run$037","ranges":[{"startOffset":82,"endOffset":264,"count":0}],"isBlockCoverage":false}]},{"scriptId":"70","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":282,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":281,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff_message$138","ranges":[{"startOffset":82,"endOffset":279,"count":0}],"isBlockCoverage":false}]},{"scriptId":"71","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":267,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":266,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff_run$039","ranges":[{"startOffset":82,"endOffset":264,"count":0}],"isBlockCoverage":false}]},{"scriptId":"72","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":278,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":277,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff__onTestStarted$140","ranges":[{"startOffset":82,"endOffset":275,"count":0}],"isBlockCoverage":false}]},{"scriptId":"73","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":281,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":280,"count":1}],"isBlockCoverage":true},{"functionName":"tearOff__expanded$_onDone$141","ranges":[{"startOffset":82,"endOffset":278,"count":0}],"isBlockCoverage":false}]},{"scriptId":"74","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":62,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":61,"count":1}],"isBlockCoverage":true}]},{"scriptId":"75","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":92,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":91,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":32,"endOffset":89,"count":1},{"startOffset":87,"endOffset":88,"count":0}],"isBlockCoverage":true}]},{"scriptId":"76","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":62,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":61,"count":1}],"isBlockCoverage":true}]},{"scriptId":"77","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":86,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":85,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":32,"endOffset":83,"count":0}],"isBlockCoverage":false}]},{"scriptId":"78","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":62,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":61,"count":1}],"isBlockCoverage":true}]},{"scriptId":"79","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":106,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":105,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":32,"endOffset":103,"count":0}],"isBlockCoverage":false}]},{"scriptId":"80","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":62,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":61,"count":1}],"isBlockCoverage":true}]},{"scriptId":"81","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":86,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":85,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":32,"endOffset":83,"count":0}],"isBlockCoverage":false}]},{"scriptId":"82","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":62,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":61,"count":1}],"isBlockCoverage":true}]},{"scriptId":"83","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":92,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":91,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":32,"endOffset":89,"count":0}],"isBlockCoverage":false}]},{"scriptId":"84","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":64,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":63,"count":1}],"isBlockCoverage":true}]},{"scriptId":"85","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":93,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":92,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":32,"endOffset":90,"count":1},{"startOffset":87,"endOffset":89,"count":0}],"isBlockCoverage":true}]},{"scriptId":"86","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":64,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":63,"count":1}],"isBlockCoverage":true}]},{"scriptId":"87","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":84,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":83,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":32,"endOffset":81,"count":0}],"isBlockCoverage":false}]},{"scriptId":"88","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":64,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":63,"count":1}],"isBlockCoverage":true}]},{"scriptId":"89","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":89,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":88,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":32,"endOffset":86,"count":0}],"isBlockCoverage":false}]},{"scriptId":"90","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":64,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":63,"count":1}],"isBlockCoverage":true}]},{"scriptId":"91","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":99,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":98,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":32,"endOffset":96,"count":1}],"isBlockCoverage":true}]},{"scriptId":"92","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":64,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":63,"count":1}],"isBlockCoverage":true}]},{"scriptId":"93","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":96,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":95,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":32,"endOffset":93,"count":0}],"isBlockCoverage":false}]},{"scriptId":"94","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":64,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":63,"count":1}],"isBlockCoverage":true}]},{"scriptId":"95","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":80,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":79,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":32,"endOffset":77,"count":1}],"isBlockCoverage":true}]},{"scriptId":"96","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":64,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":63,"count":1}],"isBlockCoverage":true}]},{"scriptId":"97","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":96,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":95,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":32,"endOffset":93,"count":0}],"isBlockCoverage":false}]},{"scriptId":"98","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":64,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":63,"count":1}],"isBlockCoverage":true}]},{"scriptId":"99","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":91,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":90,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":32,"endOffset":88,"count":1},{"startOffset":85,"endOffset":87,"count":0}],"isBlockCoverage":true}]},{"scriptId":"100","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":64,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":63,"count":1}],"isBlockCoverage":true}]},{"scriptId":"101","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":105,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":104,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":32,"endOffset":102,"count":1}],"isBlockCoverage":true}]},{"scriptId":"102","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":64,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":63,"count":1}],"isBlockCoverage":true}]},{"scriptId":"103","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":84,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":83,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":32,"endOffset":81,"count":0}],"isBlockCoverage":false}]},{"scriptId":"104","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":64,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":63,"count":1}],"isBlockCoverage":true}]},{"scriptId":"105","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":132,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":131,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":32,"endOffset":129,"count":0}],"isBlockCoverage":false}]},{"scriptId":"106","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":64,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":63,"count":1}],"isBlockCoverage":true}]},{"scriptId":"107","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":151,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":150,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":32,"endOffset":148,"count":1}],"isBlockCoverage":true}]},{"scriptId":"108","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":64,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":63,"count":1}],"isBlockCoverage":true}]},{"scriptId":"109","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":137,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":136,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":32,"endOffset":134,"count":1}],"isBlockCoverage":true}]},{"scriptId":"110","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":64,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":63,"count":1}],"isBlockCoverage":true}]},{"scriptId":"111","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":146,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":145,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":32,"endOffset":143,"count":1}],"isBlockCoverage":true}]},{"scriptId":"112","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":64,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":63,"count":1}],"isBlockCoverage":true}]},{"scriptId":"113","url":"","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":103,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1,"endOffset":102,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":32,"endOffset":100,"count":1}],"isBlockCoverage":true}]}]
\ No newline at end of file
diff --git a/pkgs/coverage/test/test_files/function_coverage_app.dart b/pkgs/coverage/test/test_files/function_coverage_app.dart
new file mode 100644
index 0000000..c4868f9
--- /dev/null
+++ b/pkgs/coverage/test/test_files/function_coverage_app.dart
@@ -0,0 +1,58 @@
+// 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 'test_library.dart';
+
+int normalFunction() {
+ return 123;
+}
+
+abstract class BaseClass {
+ int abstractMethod();
+}
+
+class SomeClass extends BaseClass {
+ SomeClass() : x = 123;
+
+ // Creates an implicit getter and setter that should be ignored.
+ int x;
+
+ int normalMethod() {
+ return 123;
+ }
+
+ static int staticMethod() {
+ return 123;
+ }
+
+ @override
+ int abstractMethod() {
+ return 123;
+ }
+}
+
+extension SomeExtension on SomeClass {
+ int extensionMethod() {
+ return 123;
+ }
+}
+
+class OtherClass {
+ int otherMethod() {
+ return 123;
+ }
+}
+
+void main() {
+ print(normalFunction());
+ print(SomeClass().normalMethod());
+ print(SomeClass.staticMethod());
+ print(SomeClass().extensionMethod());
+ print(SomeClass().abstractMethod());
+ print(OtherClass().otherMethod());
+ print(libraryFunction());
+ print(otherLibraryFunction());
+}
+
+// ignore_for_file: unreachable_from_main
diff --git a/pkgs/coverage/test/test_files/main_test.js b/pkgs/coverage/test/test_files/main_test.js
new file mode 100644
index 0000000..4c4fb3e
--- /dev/null
+++ b/pkgs/coverage/test/test_files/main_test.js
@@ -0,0 +1,26570 @@
+// Generated by dart2js (fast startup emitter, strong), the Dart to JavaScript compiler version: 2.8.0-292505767.
+// The code supports the following hooks:
+// dartPrint(message):
+// if this function is defined it is called instead of the Dart [print]
+// method.
+//
+// dartMainRunner(main, args):
+// if this function is defined, the Dart [main] method will not be invoked
+// directly. Instead, a closure that will invoke [main], and its arguments
+// [args] is passed to [dartMainRunner].
+//
+// dartDeferredLibraryLoader(uri, successCallback, errorCallback):
+// if this function is defined, it will be called when a deferred library
+// is loaded. It should load and eval the javascript of `uri`, and call
+// successCallback. If it fails to do so, it should call errorCallback with
+// an error.
+//
+// dartCallInstrumentation(id, qualifiedName):
+// if this function is defined, it will be called at each entry of a
+// method or constructor. Used only when compiling programs with
+// --experiment-call-instrumentation.
+(function dartProgram() {
+ function copyProperties(from, to) {
+ var keys = Object.keys(from);
+ for (var i = 0; i < keys.length; i++) {
+ var key = keys[i];
+ to[key] = from[key];
+ }
+ }
+ var supportsDirectProtoAccess = function() {
+ var cls = function() {
+ };
+ cls.prototype = {p: {}};
+ var object = new cls();
+ if (!(object.__proto__ && object.__proto__.p === cls.prototype.p))
+ return false;
+ try {
+ if (typeof navigator != "undefined" && typeof navigator.userAgent == "string" && navigator.userAgent.indexOf("Chrome/") >= 0)
+ return true;
+ if (typeof version == "function" && version.length == 0) {
+ var v = version();
+ if (/^\d+\.\d+\.\d+\.\d+$/.test(v))
+ return true;
+ }
+ } catch (_) {
+ }
+ return false;
+ }();
+ function setFunctionNamesIfNecessary(holders) {
+ function t() {
+ }
+ ;
+ if (typeof t.name == "string")
+ return;
+ for (var i = 0; i < holders.length; i++) {
+ var holder = holders[i];
+ var keys = Object.keys(holder);
+ for (var j = 0; j < keys.length; j++) {
+ var key = keys[j];
+ var f = holder[key];
+ if (typeof f == 'function')
+ f.name = key;
+ }
+ }
+ }
+ function inherit(cls, sup) {
+ cls.prototype.constructor = cls;
+ cls.prototype["$is" + cls.name] = cls;
+ if (sup != null) {
+ if (supportsDirectProtoAccess) {
+ cls.prototype.__proto__ = sup.prototype;
+ return;
+ }
+ var clsPrototype = Object.create(sup.prototype);
+ copyProperties(cls.prototype, clsPrototype);
+ cls.prototype = clsPrototype;
+ }
+ }
+ function inheritMany(sup, classes) {
+ for (var i = 0; i < classes.length; i++)
+ inherit(classes[i], sup);
+ }
+ function mixin(cls, mixin) {
+ copyProperties(mixin.prototype, cls.prototype);
+ cls.prototype.constructor = cls;
+ }
+ function lazy(holder, name, getterName, initializer) {
+ var uninitializedSentinel = holder;
+ holder[name] = uninitializedSentinel;
+ holder[getterName] = function() {
+ holder[getterName] = function() {
+ H.throwCyclicInit(name);
+ };
+ var result;
+ var sentinelInProgress = initializer;
+ try {
+ if (holder[name] === uninitializedSentinel) {
+ result = holder[name] = sentinelInProgress;
+ result = holder[name] = initializer();
+ } else
+ result = holder[name];
+ } finally {
+ if (result === sentinelInProgress)
+ holder[name] = null;
+ holder[getterName] = function() {
+ return this[name];
+ };
+ }
+ return result;
+ };
+ }
+ function makeConstList(list) {
+ list.immutable$list = Array;
+ list.fixed$length = Array;
+ return list;
+ }
+ function convertToFastObject(properties) {
+ function t() {
+ }
+ t.prototype = properties;
+ new t();
+ return properties;
+ }
+ function convertAllToFastObject(arrayOfObjects) {
+ for (var i = 0; i < arrayOfObjects.length; ++i)
+ convertToFastObject(arrayOfObjects[i]);
+ }
+ var functionCounter = 0;
+ function tearOffGetter(funcs, applyTrampolineIndex, reflectionInfo, name, isIntercepted) {
+ return isIntercepted ? new Function("funcs", "applyTrampolineIndex", "reflectionInfo", "name", "H", "c", "return function tearOff_" + name + functionCounter++ + "(receiver) {" + "if (c === null) c = " + "H.closureFromTearOff" + "(" + "this, funcs, applyTrampolineIndex, reflectionInfo, false, true, name);" + "return new c(this, funcs[0], receiver, name);" + "}")(funcs, applyTrampolineIndex, reflectionInfo, name, H, null) : new Function("funcs", "applyTrampolineIndex", "reflectionInfo", "name", "H", "c", "return function tearOff_" + name + functionCounter++ + "() {" + "if (c === null) c = " + "H.closureFromTearOff" + "(" + "this, funcs, applyTrampolineIndex, reflectionInfo, false, false, name);" + "return new c(this, funcs[0], null, name);" + "}")(funcs, applyTrampolineIndex, reflectionInfo, name, H, null);
+ }
+ function tearOff(funcs, applyTrampolineIndex, reflectionInfo, isStatic, name, isIntercepted) {
+ var cache = null;
+ return isStatic ? function() {
+ if (cache === null)
+ cache = H.closureFromTearOff(this, funcs, applyTrampolineIndex, reflectionInfo, true, false, name).prototype;
+ return cache;
+ } : tearOffGetter(funcs, applyTrampolineIndex, reflectionInfo, name, isIntercepted);
+ }
+ var typesOffset = 0;
+ function installTearOff(container, getterName, isStatic, isIntercepted, requiredParameterCount, optionalParameterDefaultValues, callNames, funsOrNames, funType, applyIndex) {
+ var funs = [];
+ for (var i = 0; i < funsOrNames.length; i++) {
+ var fun = funsOrNames[i];
+ if (typeof fun == 'string')
+ fun = container[fun];
+ fun.$callName = callNames[i];
+ funs.push(fun);
+ }
+ var fun = funs[0];
+ fun.$requiredArgCount = requiredParameterCount;
+ fun.$defaultValues = optionalParameterDefaultValues;
+ var reflectionInfo = funType;
+ if (typeof reflectionInfo == "number")
+ reflectionInfo += typesOffset;
+ var name = funsOrNames[0];
+ fun.$stubName = name;
+ var getterFunction = tearOff(funs, applyIndex || 0, reflectionInfo, isStatic, name, isIntercepted);
+ container[getterName] = getterFunction;
+ if (isStatic)
+ fun.$tearOff = getterFunction;
+ }
+ function installStaticTearOff(container, getterName, requiredParameterCount, optionalParameterDefaultValues, callNames, funsOrNames, funType, applyIndex) {
+ return installTearOff(container, getterName, true, false, requiredParameterCount, optionalParameterDefaultValues, callNames, funsOrNames, funType, applyIndex);
+ }
+ function installInstanceTearOff(container, getterName, isIntercepted, requiredParameterCount, optionalParameterDefaultValues, callNames, funsOrNames, funType, applyIndex) {
+ return installTearOff(container, getterName, false, isIntercepted, requiredParameterCount, optionalParameterDefaultValues, callNames, funsOrNames, funType, applyIndex);
+ }
+ function setOrUpdateInterceptorsByTag(newTags) {
+ var tags = init.interceptorsByTag;
+ if (!tags) {
+ init.interceptorsByTag = newTags;
+ return;
+ }
+ copyProperties(newTags, tags);
+ }
+ function setOrUpdateLeafTags(newTags) {
+ var tags = init.leafTags;
+ if (!tags) {
+ init.leafTags = newTags;
+ return;
+ }
+ copyProperties(newTags, tags);
+ }
+ function updateTypes(newTypes) {
+ var types = init.types;
+ var length = types.length;
+ types.push.apply(types, newTypes);
+ return length;
+ }
+ function updateHolder(holder, newHolder) {
+ copyProperties(newHolder, holder);
+ return holder;
+ }
+ var hunkHelpers = function() {
+ var mkInstance = function(isIntercepted, requiredParameterCount, optionalParameterDefaultValues, callNames, applyIndex) {
+ return function(container, getterName, name, funType) {
+ return installInstanceTearOff(container, getterName, isIntercepted, requiredParameterCount, optionalParameterDefaultValues, callNames, [name], funType, applyIndex);
+ };
+ },
+ mkStatic = function(requiredParameterCount, optionalParameterDefaultValues, callNames, applyIndex) {
+ return function(container, getterName, name, funType) {
+ return installStaticTearOff(container, getterName, requiredParameterCount, optionalParameterDefaultValues, callNames, [name], funType, applyIndex);
+ };
+ };
+ return {inherit: inherit, inheritMany: inheritMany, mixin: mixin, installStaticTearOff: installStaticTearOff, installInstanceTearOff: installInstanceTearOff, _instance_0u: mkInstance(0, 0, null, ["call$0"], 0), _instance_1u: mkInstance(0, 1, null, ["call$1"], 0), _instance_2u: mkInstance(0, 2, null, ["call$2"], 0), _instance_0i: mkInstance(1, 0, null, ["call$0"], 0), _instance_1i: mkInstance(1, 1, null, ["call$1"], 0), _instance_2i: mkInstance(1, 2, null, ["call$2"], 0), _static_0: mkStatic(0, null, ["call$0"], 0), _static_1: mkStatic(1, null, ["call$1"], 0), _static_2: mkStatic(2, null, ["call$2"], 0), makeConstList: makeConstList, lazy: lazy, updateHolder: updateHolder, convertToFastObject: convertToFastObject, setFunctionNamesIfNecessary: setFunctionNamesIfNecessary, updateTypes: updateTypes, setOrUpdateInterceptorsByTag: setOrUpdateInterceptorsByTag, setOrUpdateLeafTags: setOrUpdateLeafTags};
+ }();
+ function initializeDeferredHunk(hunk) {
+ typesOffset = init.types.length;
+ hunk(hunkHelpers, init, holders, $);
+ }
+ function getGlobalFromName(name) {
+ for (var i = 0; i < holders.length; i++) {
+ if (holders[i] == C)
+ continue;
+ if (holders[i][name])
+ return holders[i][name];
+ }
+ }
+ var C = {},
+ H = {JS_CONST: function JS_CONST() {
+ },
+ CastIterable_CastIterable: function(source, $S, $T) {
+ if ($S._eval$1("EfficientLengthIterable<0>")._is(source))
+ return new H._EfficientLengthCastIterable(source, $S._eval$1("@<0>")._bind$1($T)._eval$1("_EfficientLengthCastIterable<1,2>"));
+ return new H.CastIterable(source, $S._eval$1("@<0>")._bind$1($T)._eval$1("CastIterable<1,2>"));
+ },
+ hexDigitValue: function(char) {
+ var digit, letter;
+ H.assertHelper(char <= 65535);
+ digit = char ^ 48;
+ if (digit <= 9)
+ return digit;
+ letter = char | 32;
+ if (97 <= letter && letter <= 102)
+ return letter - 87;
+ return -1;
+ },
+ SubListIterable$: function(_iterable, _start, _endOrLength, $E) {
+ P.RangeError_checkNotNegative(_start, "start");
+ if (_endOrLength != null) {
+ P.RangeError_checkNotNegative(_endOrLength, "end");
+ if (_start > _endOrLength)
+ H.throwExpression(P.RangeError$range(_start, 0, _endOrLength, "start", null));
+ }
+ return new H.SubListIterable(_iterable, _start, _endOrLength, $E._eval$1("SubListIterable<0>"));
+ },
+ MappedIterable_MappedIterable: function(iterable, $function, $S, $T) {
+ if (type$.EfficientLengthIterable_dynamic._is(iterable))
+ return new H.EfficientLengthMappedIterable(iterable, $function, $S._eval$1("@<0>")._bind$1($T)._eval$1("EfficientLengthMappedIterable<1,2>"));
+ return new H.MappedIterable(iterable, $function, $S._eval$1("@<0>")._bind$1($T)._eval$1("MappedIterable<1,2>"));
+ },
+ IterableElementError_noElement: function() {
+ return new P.StateError("No element");
+ },
+ IterableElementError_tooMany: function() {
+ return new P.StateError("Too many elements");
+ },
+ IterableElementError_tooFew: function() {
+ return new P.StateError("Too few elements");
+ },
+ _CastIterableBase: function _CastIterableBase() {
+ },
+ CastIterator: function CastIterator(t0, t1) {
+ this._source = t0;
+ this.$ti = t1;
+ },
+ CastIterable: function CastIterable(t0, t1) {
+ this._source = t0;
+ this.$ti = t1;
+ },
+ _EfficientLengthCastIterable: function _EfficientLengthCastIterable(t0, t1) {
+ this._source = t0;
+ this.$ti = t1;
+ },
+ CastMap: function CastMap(t0, t1) {
+ this._source = t0;
+ this.$ti = t1;
+ },
+ CastMap_forEach_closure: function CastMap_forEach_closure(t0, t1) {
+ this.$this = t0;
+ this.f = t1;
+ },
+ CodeUnits: function CodeUnits(t0) {
+ this._string = t0;
+ },
+ EfficientLengthIterable: function EfficientLengthIterable() {
+ },
+ ListIterable: function ListIterable() {
+ },
+ SubListIterable: function SubListIterable(t0, t1, t2, t3) {
+ var _ = this;
+ _.__internal$_iterable = t0;
+ _.__internal$_start = t1;
+ _._endOrLength = t2;
+ _.$ti = t3;
+ },
+ ListIterator: function ListIterator(t0, t1, t2) {
+ var _ = this;
+ _.__internal$_iterable = t0;
+ _.__internal$_length = t1;
+ _.__internal$_index = 0;
+ _.__internal$_current = null;
+ _.$ti = t2;
+ },
+ MappedIterable: function MappedIterable(t0, t1, t2) {
+ this.__internal$_iterable = t0;
+ this._f = t1;
+ this.$ti = t2;
+ },
+ EfficientLengthMappedIterable: function EfficientLengthMappedIterable(t0, t1, t2) {
+ this.__internal$_iterable = t0;
+ this._f = t1;
+ this.$ti = t2;
+ },
+ MappedIterator: function MappedIterator(t0, t1, t2) {
+ var _ = this;
+ _.__internal$_current = null;
+ _._iterator = t0;
+ _._f = t1;
+ _.$ti = t2;
+ },
+ MappedListIterable: function MappedListIterable(t0, t1, t2) {
+ this._source = t0;
+ this._f = t1;
+ this.$ti = t2;
+ },
+ WhereIterable: function WhereIterable(t0, t1, t2) {
+ this.__internal$_iterable = t0;
+ this._f = t1;
+ this.$ti = t2;
+ },
+ WhereIterator: function WhereIterator(t0, t1, t2) {
+ this._iterator = t0;
+ this._f = t1;
+ this.$ti = t2;
+ },
+ ExpandIterable: function ExpandIterable(t0, t1, t2) {
+ this.__internal$_iterable = t0;
+ this._f = t1;
+ this.$ti = t2;
+ },
+ ExpandIterator: function ExpandIterator(t0, t1, t2, t3) {
+ var _ = this;
+ _._iterator = t0;
+ _._f = t1;
+ _._currentExpansion = t2;
+ _.__internal$_current = null;
+ _.$ti = t3;
+ },
+ SkipWhileIterable: function SkipWhileIterable(t0, t1, t2) {
+ this.__internal$_iterable = t0;
+ this._f = t1;
+ this.$ti = t2;
+ },
+ SkipWhileIterator: function SkipWhileIterator(t0, t1, t2) {
+ var _ = this;
+ _._iterator = t0;
+ _._f = t1;
+ _._hasSkipped = false;
+ _.$ti = t2;
+ },
+ EmptyIterator: function EmptyIterator(t0) {
+ this.$ti = t0;
+ },
+ FixedLengthListMixin: function FixedLengthListMixin() {
+ },
+ UnmodifiableListMixin: function UnmodifiableListMixin() {
+ },
+ UnmodifiableListBase: function UnmodifiableListBase() {
+ },
+ ReversedListIterable: function ReversedListIterable(t0, t1) {
+ this._source = t0;
+ this.$ti = t1;
+ },
+ Symbol: function Symbol(t0) {
+ this.__internal$_name = t0;
+ },
+ ConstantMap__throwUnmodifiable: function() {
+ throw H.wrapException(P.UnsupportedError$("Cannot modify unmodifiable Map"));
+ },
+ instantiate1: function(f, T1) {
+ var t1 = new H.Instantiation1(f, T1._eval$1("Instantiation1<0>"));
+ t1.Instantiation$1(f);
+ return t1;
+ },
+ unminifyOrTag: function(rawClassName) {
+ var preserved = H.unmangleGlobalNameIfPreservedAnyways(rawClassName);
+ if (typeof preserved == "string")
+ return preserved;
+ return rawClassName;
+ },
+ isJsIndexable: function(object, record) {
+ var result;
+ if (record != null) {
+ result = record.x;
+ if (result != null)
+ return result;
+ }
+ return type$.JavaScriptIndexingBehavior_dynamic._is(object);
+ },
+ S: function(value) {
+ var res;
+ if (typeof value == "string")
+ return value;
+ if (typeof value == "number") {
+ if (value !== 0)
+ return "" + value;
+ } else if (true === value)
+ return "true";
+ else if (false === value)
+ return "false";
+ else if (value == null)
+ return "null";
+ res = J.toString$0$(value);
+ if (typeof res != "string")
+ throw H.wrapException(H.argumentErrorValue(value));
+ return res;
+ },
+ Primitives_objectHashCode: function(object) {
+ var hash = object.$identityHash;
+ if (hash == null) {
+ hash = Math.random() * 0x3fffffff | 0;
+ object.$identityHash = hash;
+ }
+ return hash;
+ },
+ Primitives_parseInt: function(source, radix) {
+ var match, decimalMatch, maxCharCode, digitsPart, t1, i, _null = null;
+ if (typeof source != "string")
+ H.throwExpression(H.argumentErrorValue(source));
+ match = /^\s*[+-]?((0x[a-f0-9]+)|(\d+)|([a-z0-9]+))\s*$/i.exec(source);
+ if (match == null)
+ return _null;
+ if (3 >= match.length)
+ return H.ioore(match, 3);
+ decimalMatch = H._checkStringNullable(match[3]);
+ if (radix == null) {
+ if (decimalMatch != null)
+ return parseInt(source, 10);
+ if (match[2] != null)
+ return parseInt(source, 16);
+ return _null;
+ }
+ if (radix < 2 || radix > 36)
+ throw H.wrapException(P.RangeError$range(radix, 2, 36, "radix", _null));
+ if (radix === 10 && decimalMatch != null)
+ return parseInt(source, 10);
+ if (radix < 10 || decimalMatch == null) {
+ maxCharCode = radix <= 10 ? 47 + radix : 86 + radix;
+ H.assertHelper(typeof match[1] == "string");
+ digitsPart = match[1];
+ for (t1 = digitsPart.length, i = 0; i < t1; ++i)
+ if ((C.JSString_methods._codeUnitAt$1(digitsPart, i) | 32) > maxCharCode)
+ return _null;
+ }
+ return parseInt(source, radix);
+ },
+ Primitives_objectTypeName: function(object) {
+ var t1 = H.Primitives__objectTypeNameNewRti(object);
+ return t1;
+ },
+ Primitives__objectTypeNameNewRti: function(object) {
+ var dispatchName, $constructor, constructorName;
+ if (object instanceof P.Object)
+ return H._rtiToString(H.instanceType(object), null);
+ if (J.getInterceptor$(object) === C.Interceptor_methods || type$.UnknownJavaScriptObject._is(object)) {
+ dispatchName = C.C_JS_CONST(object);
+ if (H.Primitives__saneNativeClassName(dispatchName))
+ return dispatchName;
+ $constructor = object.constructor;
+ if (typeof $constructor == "function") {
+ constructorName = $constructor.name;
+ if (typeof constructorName == "string" && H.Primitives__saneNativeClassName(constructorName))
+ return constructorName;
+ }
+ }
+ return H._rtiToString(H.instanceType(object), null);
+ },
+ Primitives__saneNativeClassName: function($name) {
+ var t1 = $name !== "Object" && $name !== "";
+ return t1;
+ },
+ Primitives_dateNow: function() {
+ return Date.now();
+ },
+ Primitives_initTicker: function() {
+ var $window, performance;
+ if ($.Primitives_timerFrequency != null)
+ return;
+ $.Primitives_timerFrequency = 1000;
+ $.Primitives_timerTicks = H._js_helper_Primitives_dateNow$closure();
+ if (typeof window == "undefined")
+ return;
+ $window = window;
+ if ($window == null)
+ return;
+ performance = $window.performance;
+ if (performance == null)
+ return;
+ if (typeof performance.now != "function")
+ return;
+ $.Primitives_timerFrequency = 1000000;
+ $.Primitives_timerTicks = new H.Primitives_initTicker_closure(performance);
+ },
+ Primitives_currentUri: function() {
+ if (!!self.location)
+ return self.location.href;
+ return null;
+ },
+ Primitives__fromCharCodeApply: function(array) {
+ var result, i, i0, chunkEnd,
+ end = J.get$length$asx(array);
+ if (end <= 500)
+ return String.fromCharCode.apply(null, array);
+ for (result = "", i = 0; i < end; i = i0) {
+ i0 = i + 500;
+ chunkEnd = i0 < end ? i0 : end;
+ result += String.fromCharCode.apply(null, array.slice(i, chunkEnd));
+ }
+ return result;
+ },
+ Primitives_stringFromCodePoints: function(codePoints) {
+ var t1, i,
+ a = H.setRuntimeTypeInfo([], type$.JSArray_int);
+ for (t1 = J.get$iterator$ax(type$.Iterable_dynamic._check(codePoints)); t1.moveNext$0();) {
+ i = t1.get$current();
+ if (!H._isInt(i))
+ throw H.wrapException(H.argumentErrorValue(i));
+ if (i <= 65535)
+ C.JSArray_methods.add$1(a, i);
+ else if (i <= 1114111) {
+ C.JSArray_methods.add$1(a, 55296 + (C.JSInt_methods._shrOtherPositive$1(i - 65536, 10) & 1023));
+ C.JSArray_methods.add$1(a, 56320 + (i & 1023));
+ } else
+ throw H.wrapException(H.argumentErrorValue(i));
+ }
+ return H.Primitives__fromCharCodeApply(a);
+ },
+ Primitives_stringFromCharCodes: function(charCodes) {
+ var t1, i;
+ for (type$.Iterable_dynamic._check(charCodes), t1 = J.get$iterator$ax(charCodes); t1.moveNext$0();) {
+ i = t1.get$current();
+ if (!H._isInt(i))
+ throw H.wrapException(H.argumentErrorValue(i));
+ if (i < 0)
+ throw H.wrapException(H.argumentErrorValue(i));
+ if (i > 65535)
+ return H.Primitives_stringFromCodePoints(charCodes);
+ }
+ return H.Primitives__fromCharCodeApply(type$.List_dynamic._check(charCodes));
+ },
+ Primitives_stringFromNativeUint8List: function(charCodes, start, end) {
+ var i, result, i0, chunkEnd;
+ if (end <= 500 && start === 0 && end === charCodes.length)
+ return String.fromCharCode.apply(null, charCodes);
+ for (i = start, result = ""; i < end; i = i0) {
+ i0 = i + 500;
+ chunkEnd = i0 < end ? i0 : end;
+ result += String.fromCharCode.apply(null, charCodes.subarray(i, chunkEnd));
+ }
+ return result;
+ },
+ Primitives_stringFromCharCode: function(charCode) {
+ var bits;
+ if (typeof charCode !== "number")
+ return H.iae(charCode);
+ if (0 <= charCode) {
+ if (charCode <= 65535)
+ return String.fromCharCode(charCode);
+ if (charCode <= 1114111) {
+ bits = charCode - 65536;
+ return String.fromCharCode((55296 | C.JSInt_methods._shrOtherPositive$1(bits, 10)) >>> 0, 56320 | bits & 1023);
+ }
+ }
+ throw H.wrapException(P.RangeError$range(charCode, 0, 1114111, null, null));
+ },
+ Primitives_lazyAsJsDate: function(receiver) {
+ if (receiver.date === void 0)
+ receiver.date = new Date(receiver._value);
+ return receiver.date;
+ },
+ Primitives_getYear: function(receiver) {
+ var t1 = H.Primitives_lazyAsJsDate(receiver).getUTCFullYear() + 0;
+ return t1;
+ },
+ Primitives_getMonth: function(receiver) {
+ var t1 = H.Primitives_lazyAsJsDate(receiver).getUTCMonth() + 1;
+ return t1;
+ },
+ Primitives_getDay: function(receiver) {
+ var t1 = H.Primitives_lazyAsJsDate(receiver).getUTCDate() + 0;
+ return t1;
+ },
+ Primitives_getHours: function(receiver) {
+ var t1 = H.Primitives_lazyAsJsDate(receiver).getUTCHours() + 0;
+ return t1;
+ },
+ Primitives_getMinutes: function(receiver) {
+ var t1 = H.Primitives_lazyAsJsDate(receiver).getUTCMinutes() + 0;
+ return t1;
+ },
+ Primitives_getSeconds: function(receiver) {
+ var t1 = H.Primitives_lazyAsJsDate(receiver).getUTCSeconds() + 0;
+ return t1;
+ },
+ Primitives_getMilliseconds: function(receiver) {
+ var t1 = H.Primitives_lazyAsJsDate(receiver).getUTCMilliseconds() + 0;
+ return t1;
+ },
+ Primitives_getProperty: function(object, key) {
+ if (object == null || H._isBool(object) || typeof object == "number" || typeof object == "string")
+ throw H.wrapException(H.argumentErrorValue(object));
+ return object[key];
+ },
+ Primitives_setProperty: function(object, key, value) {
+ if (object == null || H._isBool(object) || typeof object == "number" || typeof object == "string")
+ throw H.wrapException(H.argumentErrorValue(object));
+ object[key] = value;
+ },
+ iae: function(argument) {
+ throw H.wrapException(H.argumentErrorValue(argument));
+ },
+ ioore: function(receiver, index) {
+ if (receiver == null)
+ J.get$length$asx(receiver);
+ throw H.wrapException(H.diagnoseIndexError(receiver, index));
+ },
+ diagnoseIndexError: function(indexable, index) {
+ var $length, t1, _s5_ = "index";
+ if (!H._isInt(index))
+ return new P.ArgumentError(true, index, _s5_, null);
+ $length = H._checkIntNullable(J.get$length$asx(indexable));
+ if (!(index < 0)) {
+ if (typeof $length !== "number")
+ return H.iae($length);
+ t1 = index >= $length;
+ } else
+ t1 = true;
+ if (t1)
+ return P.IndexError$(index, indexable, _s5_, null, $length);
+ return P.RangeError$value(index, _s5_);
+ },
+ diagnoseRangeError: function(start, end, $length) {
+ var _s13_ = "Invalid value";
+ if (start < 0 || start > $length)
+ return new P.RangeError(0, $length, true, start, "start", _s13_);
+ if (end != null)
+ if (end < start || end > $length)
+ return new P.RangeError(start, $length, true, end, "end", _s13_);
+ return new P.ArgumentError(true, end, "end", null);
+ },
+ argumentErrorValue: function(object) {
+ return new P.ArgumentError(true, object, null, null);
+ },
+ checkNum: function(value) {
+ if (typeof value != "number")
+ throw H.wrapException(H.argumentErrorValue(value));
+ return value;
+ },
+ wrapException: function(ex) {
+ var wrapper;
+ if (ex == null)
+ ex = new P.NullThrownError();
+ wrapper = new Error();
+ wrapper.dartException = ex;
+ if ("defineProperty" in Object) {
+ Object.defineProperty(wrapper, "message", {get: H.toStringWrapper});
+ wrapper.name = "";
+ } else
+ wrapper.toString = H.toStringWrapper;
+ return wrapper;
+ },
+ toStringWrapper: function() {
+ return J.toString$0$(this.dartException);
+ },
+ throwExpression: function(ex) {
+ throw H.wrapException(ex);
+ },
+ throwConcurrentModificationError: function(collection) {
+ throw H.wrapException(P.ConcurrentModificationError$(collection));
+ },
+ TypeErrorDecoder_extractPattern: function(message) {
+ var match, $arguments, argumentsExpr, expr, method, receiver;
+ message = H.quoteStringForRegExp(message.replace(String({}), '$receiver$'));
+ match = message.match(/\\\$[a-zA-Z]+\\\$/g);
+ if (match == null)
+ match = H.setRuntimeTypeInfo([], type$.JSArray_String);
+ $arguments = match.indexOf("\\$arguments\\$");
+ argumentsExpr = match.indexOf("\\$argumentsExpr\\$");
+ expr = match.indexOf("\\$expr\\$");
+ method = match.indexOf("\\$method\\$");
+ receiver = match.indexOf("\\$receiver\\$");
+ return new H.TypeErrorDecoder(message.replace(new RegExp('\\\\\\$arguments\\\\\\$', 'g'), '((?:x|[^x])*)').replace(new RegExp('\\\\\\$argumentsExpr\\\\\\$', 'g'), '((?:x|[^x])*)').replace(new RegExp('\\\\\\$expr\\\\\\$', 'g'), '((?:x|[^x])*)').replace(new RegExp('\\\\\\$method\\\\\\$', 'g'), '((?:x|[^x])*)').replace(new RegExp('\\\\\\$receiver\\\\\\$', 'g'), '((?:x|[^x])*)'), $arguments, argumentsExpr, expr, method, receiver);
+ },
+ TypeErrorDecoder_provokeCallErrorOn: function(expression) {
+ return function($expr$) {
+ var $argumentsExpr$ = '$arguments$';
+ try {
+ $expr$.$method$($argumentsExpr$);
+ } catch (e) {
+ return e.message;
+ }
+ }(expression);
+ },
+ TypeErrorDecoder_provokePropertyErrorOn: function(expression) {
+ return function($expr$) {
+ try {
+ $expr$.$method$;
+ } catch (e) {
+ return e.message;
+ }
+ }(expression);
+ },
+ NullError$: function(_message, match) {
+ return new H.NullError(_message, match == null ? null : match.method);
+ },
+ JsNoSuchMethodError$: function(_message, match) {
+ var t1 = match == null,
+ t2 = t1 ? null : match.method;
+ return new H.JsNoSuchMethodError(_message, t2, t1 ? null : match.receiver);
+ },
+ unwrapException: function(ex) {
+ var message, number, ieErrorCode, nsme, notClosure, nullCall, nullLiteralCall, undefCall, undefLiteralCall, nullProperty, undefProperty, undefLiteralProperty, match, t2, _null = null,
+ t1 = new H.unwrapException_saveStackTrace(ex);
+ if (ex == null)
+ return _null;
+ if (ex instanceof H.ExceptionAndStackTrace)
+ return t1.call$1(ex.dartException);
+ if (typeof ex !== "object")
+ return ex;
+ if ("dartException" in ex)
+ return t1.call$1(ex.dartException);
+ else if (!("message" in ex))
+ return ex;
+ message = ex.message;
+ if ("number" in ex && typeof ex.number == "number") {
+ number = ex.number;
+ ieErrorCode = number & 65535;
+ if ((C.JSInt_methods._shrOtherPositive$1(number, 16) & 8191) === 10)
+ switch (ieErrorCode) {
+ case 438:
+ return t1.call$1(H.JsNoSuchMethodError$(H.S(message) + " (Error " + ieErrorCode + ")", _null));
+ case 445:
+ case 5007:
+ return t1.call$1(H.NullError$(H.S(message) + " (Error " + ieErrorCode + ")", _null));
+ }
+ }
+ if (ex instanceof TypeError) {
+ nsme = $.$get$TypeErrorDecoder_noSuchMethodPattern();
+ notClosure = $.$get$TypeErrorDecoder_notClosurePattern();
+ nullCall = $.$get$TypeErrorDecoder_nullCallPattern();
+ nullLiteralCall = $.$get$TypeErrorDecoder_nullLiteralCallPattern();
+ undefCall = $.$get$TypeErrorDecoder_undefinedCallPattern();
+ undefLiteralCall = $.$get$TypeErrorDecoder_undefinedLiteralCallPattern();
+ nullProperty = $.$get$TypeErrorDecoder_nullPropertyPattern();
+ $.$get$TypeErrorDecoder_nullLiteralPropertyPattern();
+ undefProperty = $.$get$TypeErrorDecoder_undefinedPropertyPattern();
+ undefLiteralProperty = $.$get$TypeErrorDecoder_undefinedLiteralPropertyPattern();
+ match = nsme.matchTypeError$1(message);
+ if (match != null)
+ return t1.call$1(H.JsNoSuchMethodError$(H._checkStringNullable(message), match));
+ else {
+ match = notClosure.matchTypeError$1(message);
+ if (match != null) {
+ match.method = "call";
+ return t1.call$1(H.JsNoSuchMethodError$(H._checkStringNullable(message), match));
+ } else {
+ match = nullCall.matchTypeError$1(message);
+ if (match == null) {
+ match = nullLiteralCall.matchTypeError$1(message);
+ if (match == null) {
+ match = undefCall.matchTypeError$1(message);
+ if (match == null) {
+ match = undefLiteralCall.matchTypeError$1(message);
+ if (match == null) {
+ match = nullProperty.matchTypeError$1(message);
+ if (match == null) {
+ match = nullLiteralCall.matchTypeError$1(message);
+ if (match == null) {
+ match = undefProperty.matchTypeError$1(message);
+ if (match == null) {
+ match = undefLiteralProperty.matchTypeError$1(message);
+ t2 = match != null;
+ } else
+ t2 = true;
+ } else
+ t2 = true;
+ } else
+ t2 = true;
+ } else
+ t2 = true;
+ } else
+ t2 = true;
+ } else
+ t2 = true;
+ } else
+ t2 = true;
+ if (t2)
+ return t1.call$1(H.NullError$(H._checkStringNullable(message), match));
+ }
+ }
+ return t1.call$1(new H.UnknownJsTypeError(typeof message == "string" ? message : ""));
+ }
+ if (ex instanceof RangeError) {
+ if (typeof message == "string" && message.indexOf("call stack") !== -1)
+ return new P.StackOverflowError();
+ message = function(ex) {
+ try {
+ return String(ex);
+ } catch (e) {
+ }
+ return null;
+ }(ex);
+ return t1.call$1(new P.ArgumentError(false, _null, _null, typeof message == "string" ? message.replace(/^RangeError:\s*/, "") : message));
+ }
+ if (typeof InternalError == "function" && ex instanceof InternalError)
+ if (typeof message == "string" && message === "too much recursion")
+ return new P.StackOverflowError();
+ return ex;
+ },
+ getTraceFromException: function(exception) {
+ var trace;
+ if (exception instanceof H.ExceptionAndStackTrace)
+ return exception.stackTrace;
+ if (exception == null)
+ return new H._StackTrace(exception);
+ trace = exception.$cachedTrace;
+ if (trace != null)
+ return trace;
+ return exception.$cachedTrace = new H._StackTrace(exception);
+ },
+ objectHashCode: function(object) {
+ if (object == null || typeof object != 'object')
+ return J.get$hashCode$(object);
+ else
+ return H.Primitives_objectHashCode(object);
+ },
+ fillLiteralMap: function(keyValuePairs, result) {
+ var $length, index, index0, key,
+ t1 = Array.isArray(keyValuePairs);
+ H.assertHelper(t1);
+ $length = keyValuePairs.length;
+ for (index = 0; index < $length;) {
+ index0 = index + 1;
+ H.assertHelper(t1);
+ key = keyValuePairs[index];
+ index = index0 + 1;
+ H.assertHelper(t1);
+ result.$indexSet(0, key, keyValuePairs[index0]);
+ }
+ return result;
+ },
+ fillLiteralSet: function(values, result) {
+ var $length, index,
+ t1 = Array.isArray(values);
+ H.assertHelper(t1);
+ $length = values.length;
+ for (index = 0; index < $length; ++index) {
+ H.assertHelper(t1);
+ result.add$1(0, values[index]);
+ }
+ return result;
+ },
+ invokeClosure: function(closure, numberOfArguments, arg1, arg2, arg3, arg4) {
+ type$.Function._check(closure);
+ switch (H._checkIntNullable(numberOfArguments)) {
+ case 0:
+ return closure.call$0();
+ case 1:
+ return closure.call$1(arg1);
+ case 2:
+ return closure.call$2(arg1, arg2);
+ case 3:
+ return closure.call$3(arg1, arg2, arg3);
+ case 4:
+ return closure.call$4(arg1, arg2, arg3, arg4);
+ }
+ throw H.wrapException(new P._Exception("Unsupported number of arguments for wrapped closure"));
+ },
+ convertDartClosureToJS: function(closure, arity) {
+ var $function;
+ if (closure == null)
+ return null;
+ $function = closure.$identity;
+ if (!!$function)
+ return $function;
+ $function = function(closure, arity, invoke) {
+ return function(a1, a2, a3, a4) {
+ return invoke(closure, arity, a1, a2, a3, a4);
+ };
+ }(closure, arity, H.invokeClosure);
+ closure.$identity = $function;
+ return $function;
+ },
+ Closure_fromTearOff: function(receiver, functions, applyTrampolineIndex, reflectionInfo, isStatic, isIntercepted, propertyName) {
+ var $constructor, t1, trampoline, signatureFunction, applyTrampoline, i, stub, stubCallName, _null = null,
+ $function = functions[0],
+ callName = $function.$callName,
+ $prototype = isStatic ? Object.create(new H.StaticClosure().constructor.prototype) : Object.create(new H.BoundClosure(_null, _null, _null, _null).constructor.prototype);
+ $prototype.$initialize = $prototype.constructor;
+ if (isStatic)
+ $constructor = function static_tear_off() {
+ this.$initialize();
+ };
+ else {
+ t1 = $.Closure_functionCounter;
+ if (typeof t1 !== "number")
+ return t1.$add();
+ $.Closure_functionCounter = t1 + 1;
+ t1 = new Function("a,b,c,d" + t1, "this.$initialize(a,b,c,d" + t1 + ")");
+ $constructor = t1;
+ }
+ $prototype.constructor = $constructor;
+ $constructor.prototype = $prototype;
+ if (!isStatic) {
+ trampoline = H.Closure_forwardCallTo(receiver, $function, isIntercepted);
+ trampoline.$reflectionInfo = reflectionInfo;
+ } else {
+ $prototype.$static_name = propertyName;
+ trampoline = $function;
+ }
+ signatureFunction = H.Closure__computeSignatureFunctionNewRti(reflectionInfo, isStatic, isIntercepted);
+ $prototype.$signature = signatureFunction;
+ $prototype[callName] = trampoline;
+ for (applyTrampoline = trampoline, i = 1; i < functions.length; ++i) {
+ stub = functions[i];
+ stubCallName = stub.$callName;
+ if (stubCallName != null) {
+ stub = isStatic ? stub : H.Closure_forwardCallTo(receiver, stub, isIntercepted);
+ $prototype[stubCallName] = stub;
+ }
+ if (i === applyTrampolineIndex) {
+ stub.$reflectionInfo = reflectionInfo;
+ applyTrampoline = stub;
+ }
+ }
+ $prototype["call*"] = applyTrampoline;
+ $prototype.$requiredArgCount = $function.$requiredArgCount;
+ $prototype.$defaultValues = $function.$defaultValues;
+ return $constructor;
+ },
+ Closure__computeSignatureFunctionNewRti: function(functionType, isStatic, isIntercepted) {
+ var typeEvalMethod;
+ if (typeof functionType == "number")
+ return function(getType, t) {
+ return function() {
+ return getType(t);
+ };
+ }(H.getTypeFromTypesTable, functionType);
+ if (typeof functionType == "string") {
+ if (isStatic)
+ throw H.wrapException("Cannot compute signature for static tearoff.");
+ typeEvalMethod = isIntercepted ? H.BoundClosure_evalRecipeIntercepted : H.BoundClosure_evalRecipe;
+ return function(recipe, evalOnReceiver) {
+ return function() {
+ return evalOnReceiver(this, recipe);
+ };
+ }(functionType, typeEvalMethod);
+ }
+ throw H.wrapException("Error in functionType of tearoff");
+ },
+ Closure_cspForwardCall: function(arity, isSuperCall, stubName, $function) {
+ var getSelf = H.BoundClosure_selfOf;
+ switch (isSuperCall ? -1 : arity) {
+ case 0:
+ return function(n, S) {
+ return function() {
+ return S(this)[n]();
+ };
+ }(stubName, getSelf);
+ case 1:
+ return function(n, S) {
+ return function(a) {
+ return S(this)[n](a);
+ };
+ }(stubName, getSelf);
+ case 2:
+ return function(n, S) {
+ return function(a, b) {
+ return S(this)[n](a, b);
+ };
+ }(stubName, getSelf);
+ case 3:
+ return function(n, S) {
+ return function(a, b, c) {
+ return S(this)[n](a, b, c);
+ };
+ }(stubName, getSelf);
+ case 4:
+ return function(n, S) {
+ return function(a, b, c, d) {
+ return S(this)[n](a, b, c, d);
+ };
+ }(stubName, getSelf);
+ case 5:
+ return function(n, S) {
+ return function(a, b, c, d, e) {
+ return S(this)[n](a, b, c, d, e);
+ };
+ }(stubName, getSelf);
+ default:
+ return function(f, s) {
+ return function() {
+ return f.apply(s(this), arguments);
+ };
+ }($function, getSelf);
+ }
+ },
+ Closure_forwardCallTo: function(receiver, $function, isIntercepted) {
+ var stubName, arity, lookedUpFunction, t1, t2, selfName, $arguments;
+ if (isIntercepted)
+ return H.Closure_forwardInterceptedCallTo(receiver, $function);
+ stubName = $function.$stubName;
+ arity = $function.length;
+ lookedUpFunction = receiver[stubName];
+ t1 = $function == null ? lookedUpFunction == null : $function === lookedUpFunction;
+ t2 = !t1 || arity >= 27;
+ if (t2)
+ return H.Closure_cspForwardCall(arity, !t1, stubName, $function);
+ if (arity === 0) {
+ t1 = $.Closure_functionCounter;
+ if (typeof t1 !== "number")
+ return t1.$add();
+ $.Closure_functionCounter = t1 + 1;
+ selfName = "self" + t1;
+ t1 = "return function(){var " + selfName + " = this.";
+ t2 = $.BoundClosure_selfFieldNameCache;
+ return new Function(t1 + H.S(t2 == null ? $.BoundClosure_selfFieldNameCache = H.BoundClosure_computeFieldNamed("self") : t2) + ";return " + selfName + "." + H.S(stubName) + "();}")();
+ }
+ H.assertHelper(1 <= arity && arity < 27);
+ $arguments = "abcdefghijklmnopqrstuvwxyz".split("").splice(0, arity).join(",");
+ t1 = $.Closure_functionCounter;
+ if (typeof t1 !== "number")
+ return t1.$add();
+ $.Closure_functionCounter = t1 + 1;
+ $arguments += t1;
+ t1 = "return function(" + $arguments + "){return this.";
+ t2 = $.BoundClosure_selfFieldNameCache;
+ return new Function(t1 + H.S(t2 == null ? $.BoundClosure_selfFieldNameCache = H.BoundClosure_computeFieldNamed("self") : t2) + "." + H.S(stubName) + "(" + $arguments + ");}")();
+ },
+ Closure_cspForwardInterceptedCall: function(arity, isSuperCall, $name, $function) {
+ var getSelf = H.BoundClosure_selfOf,
+ getReceiver = H.BoundClosure_receiverOf;
+ switch (isSuperCall ? -1 : arity) {
+ case 0:
+ throw H.wrapException(H.RuntimeError$("Intercepted function with no arguments."));
+ case 1:
+ return function(n, s, r) {
+ return function() {
+ return s(this)[n](r(this));
+ };
+ }($name, getSelf, getReceiver);
+ case 2:
+ return function(n, s, r) {
+ return function(a) {
+ return s(this)[n](r(this), a);
+ };
+ }($name, getSelf, getReceiver);
+ case 3:
+ return function(n, s, r) {
+ return function(a, b) {
+ return s(this)[n](r(this), a, b);
+ };
+ }($name, getSelf, getReceiver);
+ case 4:
+ return function(n, s, r) {
+ return function(a, b, c) {
+ return s(this)[n](r(this), a, b, c);
+ };
+ }($name, getSelf, getReceiver);
+ case 5:
+ return function(n, s, r) {
+ return function(a, b, c, d) {
+ return s(this)[n](r(this), a, b, c, d);
+ };
+ }($name, getSelf, getReceiver);
+ case 6:
+ return function(n, s, r) {
+ return function(a, b, c, d, e) {
+ return s(this)[n](r(this), a, b, c, d, e);
+ };
+ }($name, getSelf, getReceiver);
+ default:
+ return function(f, s, r, a) {
+ return function() {
+ a = [r(this)];
+ Array.prototype.push.apply(a, arguments);
+ return f.apply(s(this), a);
+ };
+ }($function, getSelf, getReceiver);
+ }
+ },
+ Closure_forwardInterceptedCallTo: function(receiver, $function) {
+ var t2, stubName, arity, lookedUpFunction, t3, t4, $arguments,
+ t1 = $.BoundClosure_selfFieldNameCache;
+ if (t1 == null)
+ t1 = $.BoundClosure_selfFieldNameCache = H.BoundClosure_computeFieldNamed("self");
+ t2 = $.BoundClosure_receiverFieldNameCache;
+ if (t2 == null)
+ t2 = $.BoundClosure_receiverFieldNameCache = H.BoundClosure_computeFieldNamed("receiver");
+ stubName = $function.$stubName;
+ arity = $function.length;
+ lookedUpFunction = receiver[stubName];
+ t3 = $function == null ? lookedUpFunction == null : $function === lookedUpFunction;
+ t4 = !t3 || arity >= 28;
+ if (t4)
+ return H.Closure_cspForwardInterceptedCall(arity, !t3, stubName, $function);
+ if (arity === 1) {
+ t1 = "return function(){return this." + H.S(t1) + "." + H.S(stubName) + "(this." + H.S(t2) + ");";
+ t2 = $.Closure_functionCounter;
+ if (typeof t2 !== "number")
+ return t2.$add();
+ $.Closure_functionCounter = t2 + 1;
+ return new Function(t1 + t2 + "}")();
+ }
+ H.assertHelper(1 < arity && arity < 28);
+ $arguments = "abcdefghijklmnopqrstuvwxyz".split("").splice(0, arity - 1).join(",");
+ t1 = "return function(" + $arguments + "){return this." + H.S(t1) + "." + H.S(stubName) + "(this." + H.S(t2) + ", " + $arguments + ");";
+ t2 = $.Closure_functionCounter;
+ if (typeof t2 !== "number")
+ return t2.$add();
+ $.Closure_functionCounter = t2 + 1;
+ return new Function(t1 + t2 + "}")();
+ },
+ closureFromTearOff: function(receiver, functions, applyTrampolineIndex, reflectionInfo, isStatic, isIntercepted, $name) {
+ return H.Closure_fromTearOff(receiver, functions, applyTrampolineIndex, reflectionInfo, !!isStatic, !!isIntercepted, $name);
+ },
+ BoundClosure_evalRecipe: function(closure, recipe) {
+ return H._Universe_evalInEnvironment(init.typeUniverse, H.instanceType(closure._self), recipe);
+ },
+ BoundClosure_evalRecipeIntercepted: function(closure, recipe) {
+ return H._Universe_evalInEnvironment(init.typeUniverse, H.instanceType(closure._receiver), recipe);
+ },
+ BoundClosure_selfOf: function(closure) {
+ return closure._self;
+ },
+ BoundClosure_receiverOf: function(closure) {
+ return closure._receiver;
+ },
+ BoundClosure_computeFieldNamed: function(fieldName) {
+ var t1, i, $name,
+ template = new H.BoundClosure("self", "target", "receiver", "name"),
+ names = J.JSArray_markFixedList(Object.getOwnPropertyNames(template));
+ for (t1 = names.length, i = 0; i < t1; ++i) {
+ $name = names[i];
+ if (template[$name] === fieldName)
+ return $name;
+ }
+ },
+ boolConversionCheck: function(value) {
+ if (value == null)
+ H.assertThrow("boolean expression must not be null");
+ return value;
+ },
+ extractFunctionTypeObjectFromInternal: function(o) {
+ var signature;
+ if ("$signature" in o) {
+ signature = o.$signature;
+ if (typeof signature == "number")
+ return init.types[H._checkIntNullable(signature)];
+ else
+ return o.$signature();
+ }
+ return null;
+ },
+ TypeErrorImplementation$: function(value, type) {
+ return new H.TypeErrorImplementation("TypeError: " + P.Error_safeToString(value) + ": type '" + H.S(H._typeDescription(value)) + "' is not a subtype of type '" + type + "'");
+ },
+ _typeDescription: function(value) {
+ var functionTypeObject;
+ if (value instanceof H.Closure) {
+ functionTypeObject = H.extractFunctionTypeObjectFromInternal(J.getInterceptor$(value));
+ if (functionTypeObject != null)
+ return H.runtimeTypeToString(functionTypeObject);
+ return "Closure";
+ }
+ return H.Primitives_objectTypeName(value);
+ },
+ assertTest: function(condition) {
+ if (true === condition)
+ return false;
+ if (H._isBool(condition))
+ return !condition;
+ throw H.wrapException(H.TypeErrorImplementation$(condition, "bool"));
+ },
+ assertThrow: function(message) {
+ throw H.wrapException(new H._AssertionError(message));
+ },
+ assertHelper: function(condition) {
+ if (H.assertTest(condition))
+ throw H.wrapException(P.AssertionError$(null));
+ },
+ throwCyclicInit: function(staticName) {
+ throw H.wrapException(new P.CyclicInitializationError(staticName));
+ },
+ RuntimeError$: function(message) {
+ return new H.RuntimeError(message);
+ },
+ getIsolateAffinityTag: function($name) {
+ return init.getIsolateTag($name);
+ },
+ setRuntimeTypeInfo: function(target, rti) {
+ H.assertHelper(rti != null);
+ target.$ti = rti;
+ return target;
+ },
+ getRuntimeTypeInfo: function(target) {
+ if (target == null)
+ return null;
+ return target.$ti;
+ },
+ getRuntimeTypeArguments: function(interceptor, object, substitutionName) {
+ return H.substitute(interceptor["$as" + H.S(substitutionName)], H.getRuntimeTypeInfo(object));
+ },
+ runtimeTypeToString: function(rti) {
+ return H._runtimeTypeToString(rti, null);
+ },
+ _runtimeTypeToString: function(rti, genericContext) {
+ var t1, t2;
+ if (rti == null)
+ return "dynamic";
+ if (rti === -1)
+ return "void";
+ if (Array.isArray(rti))
+ return H.unminifyOrTag(rti[0].name) + H._joinArguments(rti, 1, genericContext);
+ if (typeof rti == "function")
+ return H.unminifyOrTag(rti.name);
+ if (rti === -2)
+ return "dynamic";
+ H.assertHelper(!(rti === -1));
+ if (typeof rti == "number") {
+ H._checkIntNullable(rti);
+ if (genericContext == null || rti < 0 || rti >= genericContext.length)
+ return "unexpected-generic-index:" + rti;
+ t1 = genericContext.length;
+ t2 = t1 - rti - 1;
+ if (t2 < 0 || t2 >= t1)
+ return H.ioore(genericContext, t2);
+ return H.S(genericContext[t2]);
+ }
+ t1 = rti != null;
+ if (t1)
+ t2 = typeof rti == "string";
+ else
+ t2 = true;
+ H.assertHelper(!t2);
+ if ('func' in rti)
+ return H._functionRtiToString0(rti, genericContext);
+ if (t1)
+ t2 = typeof rti == "string";
+ else
+ t2 = true;
+ H.assertHelper(!t2);
+ if ('futureOr' in rti) {
+ if (t1)
+ t1 = typeof rti == "string";
+ else
+ t1 = true;
+ H.assertHelper(!t1);
+ H.assertHelper('futureOr' in rti);
+ return "FutureOr<" + H._runtimeTypeToString("type" in rti ? rti.type : null, genericContext) + ">";
+ }
+ return "unknown-reified-type";
+ },
+ _functionRtiToString0: function(rti, genericContext) {
+ var boundsRti, outerContextLength, offset, i, i0, typeParameters, typeSep, t1, t2, boundRti, returnTypeText, $arguments, argumentsText, sep, _i, argument, optionalArguments, namedArguments, $name, _s2_ = ", ";
+ if ("bounds" in rti) {
+ boundsRti = rti.bounds;
+ if (genericContext == null) {
+ genericContext = H.setRuntimeTypeInfo([], type$.JSArray_String);
+ outerContextLength = null;
+ } else
+ outerContextLength = genericContext.length;
+ offset = genericContext.length;
+ for (i = boundsRti.length, i0 = i; i0 > 0; --i0)
+ C.JSArray_methods.add$1(genericContext, "T" + (offset + i0));
+ for (typeParameters = "<", typeSep = "", i0 = 0; i0 < i; ++i0, typeSep = _s2_) {
+ typeParameters += typeSep;
+ t1 = genericContext.length;
+ t2 = t1 - i0 - 1;
+ if (t2 < 0)
+ return H.ioore(genericContext, t2);
+ typeParameters = C.JSString_methods.$add(typeParameters, genericContext[t2]);
+ boundRti = boundsRti[i0];
+ if (boundRti != null && boundRti !== P.Object)
+ typeParameters += " extends " + H._runtimeTypeToString(boundRti, genericContext);
+ }
+ typeParameters += ">";
+ } else {
+ typeParameters = "";
+ outerContextLength = null;
+ }
+ returnTypeText = !!rti.v ? "void" : H._runtimeTypeToString(rti.ret, genericContext);
+ if ("args" in rti) {
+ $arguments = rti.args;
+ for (t1 = $arguments.length, argumentsText = "", sep = "", _i = 0; _i < t1; ++_i, sep = _s2_) {
+ argument = $arguments[_i];
+ argumentsText = argumentsText + sep + H._runtimeTypeToString(argument, genericContext);
+ }
+ } else {
+ argumentsText = "";
+ sep = "";
+ }
+ if ("opt" in rti) {
+ optionalArguments = rti.opt;
+ argumentsText += sep + "[";
+ for (t1 = optionalArguments.length, sep = "", _i = 0; _i < t1; ++_i, sep = _s2_) {
+ argument = optionalArguments[_i];
+ argumentsText = argumentsText + sep + H._runtimeTypeToString(argument, genericContext);
+ }
+ argumentsText += "]";
+ }
+ if ("named" in rti) {
+ namedArguments = rti.named;
+ argumentsText += sep + "{";
+ for (t1 = H.extractKeys(namedArguments), t2 = t1.length, sep = "", _i = 0; _i < t2; ++_i, sep = _s2_) {
+ $name = H._checkStringNullable(t1[_i]);
+ argumentsText = argumentsText + sep + H._runtimeTypeToString(namedArguments[$name], genericContext) + (" " + H.S($name));
+ }
+ argumentsText += "}";
+ }
+ if (outerContextLength != null)
+ genericContext.length = outerContextLength;
+ return typeParameters + "(" + argumentsText + ") => " + returnTypeText;
+ },
+ _joinArguments: function(types, startIndex, genericContext) {
+ var t1, buffer, index, separator, allDynamic, argument;
+ if (types == null)
+ return "";
+ t1 = Array.isArray(types);
+ H.assertHelper(t1);
+ buffer = new P.StringBuffer("");
+ for (index = startIndex, separator = "", allDynamic = true; H.assertHelper(t1), index < types.length; ++index, separator = ", ") {
+ buffer._contents += separator;
+ H.assertHelper(t1);
+ argument = types[index];
+ if (argument != null)
+ allDynamic = false;
+ buffer._contents += H._runtimeTypeToString(argument, genericContext);
+ }
+ return "<" + buffer.toString$0(0) + ">";
+ },
+ getRuntimeType: function(object) {
+ var rti = object instanceof H.Closure ? H.closureFunctionType(object) : null;
+ return H.createRuntimeType(rti == null ? H.instanceType(object) : rti);
+ },
+ substitute: function(substitution, $arguments) {
+ if (substitution == null)
+ return $arguments;
+ H.assertHelper(typeof substitution == "function");
+ H.assertHelper($arguments == null || Array.isArray($arguments));
+ substitution = H.invokeOn(substitution, null, $arguments);
+ if (substitution == null)
+ return null;
+ if (Array.isArray(substitution))
+ return substitution;
+ if (typeof substitution == "function")
+ return H.invokeOn(substitution, null, $arguments);
+ return $arguments;
+ },
+ computeSignature: function(signature, context, contextName) {
+ return H.invokeOn(signature, context, H.substitute(J.getInterceptor$(context)["$as" + H.S(contextName)], H.getRuntimeTypeInfo(context)));
+ },
+ invokeOn: function($function, receiver, $arguments) {
+ H.assertHelper(typeof $function == "function");
+ H.assertHelper($arguments == null || Array.isArray($arguments));
+ return $function.apply(receiver, $arguments);
+ },
+ defineProperty: function(obj, property, value) {
+ Object.defineProperty(obj, property, {value: value, enumerable: false, writable: true, configurable: true});
+ },
+ lookupAndCacheInterceptor: function(obj) {
+ var tag, record, interceptor, interceptorClass, mark, t1;
+ H.assertHelper(!(obj instanceof P.Object));
+ tag = H._checkStringNullable($.getTagFunction.call$1(obj));
+ record = $.dispatchRecordsForInstanceTags[tag];
+ if (record != null) {
+ Object.defineProperty(obj, init.dispatchPropertyName, {value: record, enumerable: false, writable: true, configurable: true});
+ return record.i;
+ }
+ interceptor = $.interceptorsForUncacheableTags[tag];
+ if (interceptor != null)
+ return interceptor;
+ interceptorClass = init.interceptorsByTag[tag];
+ if (interceptorClass == null) {
+ tag = H._checkStringNullable($.alternateTagFunction.call$2(obj, tag));
+ if (tag != null) {
+ record = $.dispatchRecordsForInstanceTags[tag];
+ if (record != null) {
+ Object.defineProperty(obj, init.dispatchPropertyName, {value: record, enumerable: false, writable: true, configurable: true});
+ return record.i;
+ }
+ interceptor = $.interceptorsForUncacheableTags[tag];
+ if (interceptor != null)
+ return interceptor;
+ interceptorClass = init.interceptorsByTag[tag];
+ }
+ }
+ if (interceptorClass == null)
+ return null;
+ interceptor = interceptorClass.prototype;
+ mark = tag[0];
+ if (mark === "!") {
+ record = H.makeLeafDispatchRecord(interceptor);
+ $.dispatchRecordsForInstanceTags[tag] = record;
+ Object.defineProperty(obj, init.dispatchPropertyName, {value: record, enumerable: false, writable: true, configurable: true});
+ return record.i;
+ }
+ if (mark === "~") {
+ $.interceptorsForUncacheableTags[tag] = interceptor;
+ return interceptor;
+ }
+ if (mark === "-") {
+ t1 = H.makeLeafDispatchRecord(interceptor);
+ Object.defineProperty(Object.getPrototypeOf(obj), init.dispatchPropertyName, {value: t1, enumerable: false, writable: true, configurable: true});
+ return t1.i;
+ }
+ if (mark === "+")
+ return H.patchInteriorProto(obj, interceptor);
+ if (mark === "*")
+ throw H.wrapException(P.UnimplementedError$(tag));
+ if (init.leafTags[tag] === true) {
+ t1 = H.makeLeafDispatchRecord(interceptor);
+ Object.defineProperty(Object.getPrototypeOf(obj), init.dispatchPropertyName, {value: t1, enumerable: false, writable: true, configurable: true});
+ return t1.i;
+ } else
+ return H.patchInteriorProto(obj, interceptor);
+ },
+ patchInteriorProto: function(obj, interceptor) {
+ var proto = Object.getPrototypeOf(obj);
+ Object.defineProperty(proto, init.dispatchPropertyName, {value: J.makeDispatchRecord(interceptor, proto, null, null), enumerable: false, writable: true, configurable: true});
+ return interceptor;
+ },
+ makeLeafDispatchRecord: function(interceptor) {
+ return J.makeDispatchRecord(interceptor, false, null, !!interceptor.$isJavaScriptIndexingBehavior);
+ },
+ makeDefaultDispatchRecord: function(tag, interceptorClass, proto) {
+ var interceptor = interceptorClass.prototype;
+ if (init.leafTags[tag] === true)
+ return H.makeLeafDispatchRecord(interceptor);
+ else
+ return J.makeDispatchRecord(interceptor, proto, null, null);
+ },
+ initNativeDispatch: function() {
+ if (true === $.initNativeDispatchFlag)
+ return;
+ $.initNativeDispatchFlag = true;
+ H.initNativeDispatchContinue();
+ },
+ initNativeDispatchContinue: function() {
+ var map, tags, fun, i, tag, proto, record, interceptorClass;
+ $.dispatchRecordsForInstanceTags = Object.create(null);
+ $.interceptorsForUncacheableTags = Object.create(null);
+ H.initHooks();
+ map = init.interceptorsByTag;
+ tags = Object.getOwnPropertyNames(map);
+ if (typeof window != "undefined") {
+ window;
+ fun = function() {
+ };
+ for (i = 0; i < tags.length; ++i) {
+ tag = tags[i];
+ proto = $.prototypeForTagFunction.call$1(tag);
+ if (proto != null) {
+ record = H.makeDefaultDispatchRecord(tag, map[tag], proto);
+ if (record != null) {
+ Object.defineProperty(proto, init.dispatchPropertyName, {value: record, enumerable: false, writable: true, configurable: true});
+ fun.prototype = proto;
+ }
+ }
+ }
+ }
+ for (i = 0; i < tags.length; ++i) {
+ tag = tags[i];
+ if (/^[A-Za-z_]/.test(tag)) {
+ interceptorClass = map[tag];
+ map["!" + tag] = interceptorClass;
+ map["~" + tag] = interceptorClass;
+ map["-" + tag] = interceptorClass;
+ map["+" + tag] = interceptorClass;
+ map["*" + tag] = interceptorClass;
+ }
+ }
+ },
+ initHooks: function() {
+ var transformers, i, transformer, getTag, getUnknownTag, prototypeForTag,
+ hooks = C.C_JS_CONST0();
+ hooks = H.applyHooksTransformer(C.C_JS_CONST1, H.applyHooksTransformer(C.C_JS_CONST2, H.applyHooksTransformer(C.C_JS_CONST3, H.applyHooksTransformer(C.C_JS_CONST3, H.applyHooksTransformer(C.C_JS_CONST4, H.applyHooksTransformer(C.C_JS_CONST5, H.applyHooksTransformer(C.C_JS_CONST6(C.C_JS_CONST), hooks)))))));
+ if (typeof dartNativeDispatchHooksTransformer != "undefined") {
+ transformers = dartNativeDispatchHooksTransformer;
+ if (typeof transformers == "function")
+ transformers = [transformers];
+ if (transformers.constructor == Array)
+ for (i = 0; i < transformers.length; ++i) {
+ transformer = transformers[i];
+ if (typeof transformer == "function")
+ hooks = transformer(hooks) || hooks;
+ }
+ }
+ getTag = hooks.getTag;
+ getUnknownTag = hooks.getUnknownTag;
+ prototypeForTag = hooks.prototypeForTag;
+ $.getTagFunction = new H.initHooks_closure(getTag);
+ $.alternateTagFunction = new H.initHooks_closure0(getUnknownTag);
+ $.prototypeForTagFunction = new H.initHooks_closure1(prototypeForTag);
+ },
+ applyHooksTransformer: function(transformer, hooks) {
+ return transformer(hooks) || hooks;
+ },
+ JSSyntaxRegExp_makeNative: function(source, multiLine, caseSensitive, unicode, dotAll, global) {
+ var m = multiLine ? "m" : "",
+ i = caseSensitive ? "" : "i",
+ u = unicode ? "u" : "",
+ s = dotAll ? "s" : "",
+ g = global ? "g" : "",
+ regexp = function(source, modifiers) {
+ try {
+ return new RegExp(source, modifiers);
+ } catch (e) {
+ return e;
+ }
+ }(source, m + i + u + s + g);
+ if (regexp instanceof RegExp)
+ return regexp;
+ throw H.wrapException(P.FormatException$("Illegal RegExp pattern (" + String(regexp) + ")", source, null));
+ },
+ _MatchImplementation$: function(pattern, _match) {
+ H.assertHelper(typeof _match.input == "string");
+ H.assertHelper(H._isInt(_match.index));
+ return new H._MatchImplementation(_match);
+ },
+ stringContainsUnchecked: function(receiver, other, startIndex) {
+ var t1, t2;
+ if (typeof other == "string")
+ return receiver.indexOf(other, startIndex) >= 0;
+ else if (other instanceof H.JSSyntaxRegExp) {
+ t1 = C.JSString_methods.substring$1(receiver, startIndex);
+ t2 = other._nativeRegExp;
+ return t2.test(t1);
+ } else {
+ t1 = J.allMatches$1$s(other, C.JSString_methods.substring$1(receiver, startIndex));
+ return !t1.get$isEmpty(t1);
+ }
+ },
+ escapeReplacement: function(replacement) {
+ if (replacement.indexOf("$", 0) >= 0)
+ return replacement.replace(/\$/g, "$$$$");
+ return replacement;
+ },
+ stringReplaceFirstRE: function(receiver, regexp, replacement, startIndex) {
+ var match = regexp._execGlobal$2(receiver, startIndex);
+ if (match == null)
+ return receiver;
+ return H.stringReplaceRangeUnchecked(receiver, match._match.index, match.get$end(), replacement);
+ },
+ quoteStringForRegExp: function(string) {
+ if (/[[\]{}()*+?.\\^$|]/.test(string))
+ return string.replace(/[[\]{}()*+?.\\^$|]/g, "\\$&");
+ return string;
+ },
+ stringReplaceAllUnchecked: function(receiver, pattern, replacement) {
+ var nativeRegexp;
+ if (typeof pattern == "string")
+ return H.stringReplaceAllUncheckedString(receiver, pattern, replacement);
+ if (pattern instanceof H.JSSyntaxRegExp) {
+ nativeRegexp = pattern.get$_nativeGlobalVersion();
+ nativeRegexp.lastIndex = 0;
+ return receiver.replace(nativeRegexp, H.escapeReplacement(replacement));
+ }
+ if (pattern == null)
+ H.throwExpression(H.argumentErrorValue(pattern));
+ throw H.wrapException("String.replaceAll(Pattern) UNIMPLEMENTED");
+ },
+ stringReplaceAllUncheckedString: function(receiver, pattern, replacement) {
+ var $length, t1, i, index;
+ if (pattern === "") {
+ if (receiver === "")
+ return replacement;
+ $length = receiver.length;
+ for (t1 = replacement, i = 0; i < $length; ++i)
+ t1 = t1 + receiver[i] + replacement;
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ }
+ index = receiver.indexOf(pattern, 0);
+ if (index < 0)
+ return receiver;
+ if (receiver.length < 500 || replacement.indexOf("$", 0) >= 0)
+ return receiver.split(pattern).join(replacement);
+ return receiver.replace(new RegExp(H.quoteStringForRegExp(pattern), 'g'), H.escapeReplacement(replacement));
+ },
+ _stringIdentity: function(string) {
+ return string;
+ },
+ stringReplaceAllFuncUnchecked: function(receiver, pattern, onMatch, onNonMatch) {
+ var t1, startIndex, t2, t3, t4, t5;
+ if (!type$.Pattern._is(pattern))
+ throw H.wrapException(P.ArgumentError$value(pattern, "pattern", "is not a Pattern"));
+ for (t1 = pattern.allMatches$1(0, receiver), t1 = new H._AllMatchesIterator(t1._re, t1.__js_helper$_string, t1._start), startIndex = 0, t2 = ""; t1.moveNext$0(); t2 = t3) {
+ t3 = t1.__js_helper$_current;
+ t4 = t3._match;
+ t5 = t4.index;
+ t3 = t2 + H.S(H._stringIdentity(C.JSString_methods.substring$2(receiver, startIndex, t5))) + H.S(onMatch.call$1(t3));
+ startIndex = t5 + t4[0].length;
+ }
+ t1 = t2 + H.S(H._stringIdentity(C.JSString_methods.substring$1(receiver, startIndex)));
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ stringReplaceFirstUnchecked: function(receiver, pattern, replacement, startIndex) {
+ var index, t1, matches, match;
+ if (typeof pattern == "string") {
+ index = receiver.indexOf(pattern, startIndex);
+ if (index < 0)
+ return receiver;
+ return H.stringReplaceRangeUnchecked(receiver, index, index + pattern.length, replacement);
+ }
+ if (pattern instanceof H.JSSyntaxRegExp)
+ return startIndex === 0 ? receiver.replace(pattern._nativeRegExp, H.escapeReplacement(replacement)) : H.stringReplaceFirstRE(receiver, pattern, replacement, startIndex);
+ if (pattern == null)
+ H.throwExpression(H.argumentErrorValue(pattern));
+ t1 = J.allMatches$2$s(pattern, receiver, startIndex);
+ matches = type$.Iterator_Match._check(t1.get$iterator(t1));
+ if (!matches.moveNext$0())
+ return receiver;
+ match = matches.get$current();
+ return C.JSString_methods.replaceRange$3(receiver, match.get$start(), match.get$end(), replacement);
+ },
+ stringReplaceRangeUnchecked: function(receiver, start, end, replacement) {
+ var prefix = receiver.substring(0, start),
+ suffix = receiver.substring(end);
+ return prefix + replacement + suffix;
+ },
+ ConstantMap: function ConstantMap() {
+ },
+ ConstantMap_map_closure: function ConstantMap_map_closure(t0, t1, t2) {
+ this.$this = t0;
+ this.transform = t1;
+ this.result = t2;
+ },
+ ConstantStringMap: function ConstantStringMap(t0, t1, t2, t3) {
+ var _ = this;
+ _.__js_helper$_length = t0;
+ _._jsObject = t1;
+ _.__js_helper$_keys = t2;
+ _.$ti = t3;
+ },
+ _ConstantMapKeyIterable: function _ConstantMapKeyIterable(t0, t1) {
+ this.__js_helper$_map = t0;
+ this.$ti = t1;
+ },
+ Instantiation: function Instantiation() {
+ },
+ Instantiation1: function Instantiation1(t0, t1) {
+ this._genericClosure = t0;
+ this.$ti = t1;
+ },
+ Primitives_initTicker_closure: function Primitives_initTicker_closure(t0) {
+ this.performance = t0;
+ },
+ TypeErrorDecoder: function TypeErrorDecoder(t0, t1, t2, t3, t4, t5) {
+ var _ = this;
+ _._pattern = t0;
+ _._arguments = t1;
+ _._argumentsExpr = t2;
+ _._expr = t3;
+ _._method = t4;
+ _._receiver = t5;
+ },
+ NullError: function NullError(t0, t1) {
+ this.__js_helper$_message = t0;
+ this._method = t1;
+ },
+ JsNoSuchMethodError: function JsNoSuchMethodError(t0, t1, t2) {
+ this.__js_helper$_message = t0;
+ this._method = t1;
+ this._receiver = t2;
+ },
+ UnknownJsTypeError: function UnknownJsTypeError(t0) {
+ this.__js_helper$_message = t0;
+ },
+ ExceptionAndStackTrace: function ExceptionAndStackTrace(t0, t1) {
+ this.dartException = t0;
+ this.stackTrace = t1;
+ },
+ unwrapException_saveStackTrace: function unwrapException_saveStackTrace(t0) {
+ this.ex = t0;
+ },
+ _StackTrace: function _StackTrace(t0) {
+ this._exception = t0;
+ this._trace = null;
+ },
+ Closure: function Closure() {
+ },
+ TearOffClosure: function TearOffClosure() {
+ },
+ StaticClosure: function StaticClosure() {
+ },
+ BoundClosure: function BoundClosure(t0, t1, t2, t3) {
+ var _ = this;
+ _._self = t0;
+ _._target = t1;
+ _._receiver = t2;
+ _._name = t3;
+ },
+ TypeErrorImplementation: function TypeErrorImplementation(t0) {
+ this.message = t0;
+ },
+ RuntimeError: function RuntimeError(t0) {
+ this.message = t0;
+ },
+ _AssertionError: function _AssertionError(t0) {
+ this.message = t0;
+ },
+ JsLinkedHashMap: function JsLinkedHashMap(t0) {
+ var _ = this;
+ _.__js_helper$_length = 0;
+ _._last = _._first = _.__js_helper$_rest = _.__js_helper$_nums = _.__js_helper$_strings = null;
+ _._modifications = 0;
+ _.$ti = t0;
+ },
+ JsLinkedHashMap_values_closure: function JsLinkedHashMap_values_closure(t0) {
+ this.$this = t0;
+ },
+ JsLinkedHashMap_addAll_closure: function JsLinkedHashMap_addAll_closure(t0) {
+ this.$this = t0;
+ },
+ LinkedHashMapCell: function LinkedHashMapCell(t0, t1) {
+ var _ = this;
+ _.hashMapCellKey = t0;
+ _.hashMapCellValue = t1;
+ _._previous = _._next = null;
+ },
+ LinkedHashMapKeyIterable: function LinkedHashMapKeyIterable(t0, t1) {
+ this.__js_helper$_map = t0;
+ this.$ti = t1;
+ },
+ LinkedHashMapKeyIterator: function LinkedHashMapKeyIterator(t0, t1, t2) {
+ var _ = this;
+ _.__js_helper$_map = t0;
+ _._modifications = t1;
+ _.__js_helper$_current = _._cell = null;
+ _.$ti = t2;
+ },
+ initHooks_closure: function initHooks_closure(t0) {
+ this.getTag = t0;
+ },
+ initHooks_closure0: function initHooks_closure0(t0) {
+ this.getUnknownTag = t0;
+ },
+ initHooks_closure1: function initHooks_closure1(t0) {
+ this.prototypeForTag = t0;
+ },
+ JSSyntaxRegExp: function JSSyntaxRegExp(t0, t1) {
+ var _ = this;
+ _.pattern = t0;
+ _._nativeRegExp = t1;
+ _._nativeAnchoredRegExp = _._nativeGlobalRegExp = null;
+ },
+ _MatchImplementation: function _MatchImplementation(t0) {
+ this._match = t0;
+ },
+ _AllMatchesIterable: function _AllMatchesIterable(t0, t1, t2) {
+ this._re = t0;
+ this.__js_helper$_string = t1;
+ this._start = t2;
+ },
+ _AllMatchesIterator: function _AllMatchesIterator(t0, t1, t2) {
+ var _ = this;
+ _._regExp = t0;
+ _.__js_helper$_string = t1;
+ _._nextIndex = t2;
+ _.__js_helper$_current = null;
+ },
+ StringMatch: function StringMatch(t0, t1) {
+ this.start = t0;
+ this.pattern = t1;
+ },
+ _StringAllMatchesIterable: function _StringAllMatchesIterable(t0, t1, t2) {
+ this._input = t0;
+ this._pattern = t1;
+ this.__js_helper$_index = t2;
+ },
+ _StringAllMatchesIterator: function _StringAllMatchesIterator(t0, t1, t2) {
+ var _ = this;
+ _._input = t0;
+ _._pattern = t1;
+ _.__js_helper$_index = t2;
+ _.__js_helper$_current = null;
+ },
+ _ensureNativeList: function(list) {
+ return list;
+ },
+ NativeInt8List__create1: function(arg) {
+ return new Int8Array(arg);
+ },
+ _checkValidIndex: function(index, list, $length) {
+ if (index >>> 0 !== index || index >= $length)
+ throw H.wrapException(H.diagnoseIndexError(list, index));
+ },
+ _checkValidRange: function(start, end, $length) {
+ var t1;
+ if (!(start >>> 0 !== start))
+ if (end == null)
+ t1 = start > $length;
+ else
+ t1 = end >>> 0 !== end || start > end || end > $length;
+ else
+ t1 = true;
+ if (t1)
+ throw H.wrapException(H.diagnoseRangeError(start, end, $length));
+ if (end == null)
+ return $length;
+ return end;
+ },
+ NativeByteBuffer: function NativeByteBuffer() {
+ },
+ NativeTypedData: function NativeTypedData() {
+ },
+ NativeByteData: function NativeByteData() {
+ },
+ NativeTypedArray: function NativeTypedArray() {
+ },
+ NativeTypedArrayOfDouble: function NativeTypedArrayOfDouble() {
+ },
+ NativeTypedArrayOfInt: function NativeTypedArrayOfInt() {
+ },
+ NativeFloat32List: function NativeFloat32List() {
+ },
+ NativeFloat64List: function NativeFloat64List() {
+ },
+ NativeInt16List: function NativeInt16List() {
+ },
+ NativeInt32List: function NativeInt32List() {
+ },
+ NativeInt8List: function NativeInt8List() {
+ },
+ NativeUint16List: function NativeUint16List() {
+ },
+ NativeUint32List: function NativeUint32List() {
+ },
+ NativeUint8ClampedList: function NativeUint8ClampedList() {
+ },
+ NativeUint8List: function NativeUint8List() {
+ },
+ _NativeTypedArrayOfDouble_NativeTypedArray_ListMixin: function _NativeTypedArrayOfDouble_NativeTypedArray_ListMixin() {
+ },
+ _NativeTypedArrayOfDouble_NativeTypedArray_ListMixin_FixedLengthListMixin: function _NativeTypedArrayOfDouble_NativeTypedArray_ListMixin_FixedLengthListMixin() {
+ },
+ _NativeTypedArrayOfInt_NativeTypedArray_ListMixin: function _NativeTypedArrayOfInt_NativeTypedArray_ListMixin() {
+ },
+ _NativeTypedArrayOfInt_NativeTypedArray_ListMixin_FixedLengthListMixin: function _NativeTypedArrayOfInt_NativeTypedArray_ListMixin_FixedLengthListMixin() {
+ },
+ Rti__getFutureFromFutureOr: function(universe, rti) {
+ var future;
+ H.assertHelper(rti._kind === 8);
+ future = rti._precomputed1;
+ if (future == null) {
+ H.assertHelper(rti._kind === 8);
+ future = rti._precomputed1 = H._Universe__lookupInterfaceRti(universe, "Future", [rti._primary]);
+ }
+ return future;
+ },
+ Rti__isUnionOfFunctionType: function(rti) {
+ var kind = rti._kind;
+ if (kind === 6 || kind === 7 || kind === 8)
+ return H.Rti__isUnionOfFunctionType(rti._primary);
+ return kind === 11 || kind === 12;
+ },
+ Rti__getInterfaceTypeArguments: function(rti) {
+ H.assertHelper(rti._kind === 9);
+ return rti._rest;
+ },
+ Rti__getBindingArguments: function(rti) {
+ H.assertHelper(rti._kind === 10);
+ return rti._rest;
+ },
+ Rti__getFunctionParameters: function(rti) {
+ H.assertHelper(rti._kind === 11);
+ return rti._rest;
+ },
+ Rti__getGenericFunctionBounds: function(rti) {
+ H.assertHelper(rti._kind === 12);
+ return rti._rest;
+ },
+ Rti__getCanonicalRecipe: function(rti) {
+ var s = rti._canonicalRecipe;
+ if (H.assertTest(typeof s == "string"))
+ H.assertThrow("Missing canonical recipe");
+ return s;
+ },
+ findType: function(recipe) {
+ return H._Universe_eval(init.typeUniverse, recipe);
+ },
+ instantiatedGenericFunctionType: function(genericFunctionRti, instantiationRti) {
+ var bounds, typeArguments, cache, s, key, probe, rti;
+ if (genericFunctionRti == null)
+ return null;
+ bounds = H.Rti__getGenericFunctionBounds(genericFunctionRti);
+ typeArguments = H.Rti__getInterfaceTypeArguments(instantiationRti);
+ H.assertHelper(bounds.length === typeArguments.length);
+ cache = genericFunctionRti._bindCache;
+ if (cache == null)
+ cache = genericFunctionRti._bindCache = new Map();
+ s = instantiationRti._canonicalRecipe;
+ if (H.assertTest(typeof s == "string"))
+ H.assertThrow("Missing canonical recipe");
+ key = s;
+ probe = cache.get(key);
+ if (probe != null)
+ return probe;
+ H.assertHelper(genericFunctionRti._kind === 12);
+ rti = H._instantiate(init.typeUniverse, genericFunctionRti._primary, typeArguments, 0);
+ cache.set(key, rti);
+ return rti;
+ },
+ _instantiate: function(universe, rti, typeArguments, depth) {
+ var baseType, instantiatedBaseType, s, interfaceTypeArguments, instantiatedInterfaceTypeArguments, base, instantiatedBase, $arguments, instantiatedArguments, returnType, instantiatedReturnType, functionParameters, instantiatedFunctionParameters, bounds, instantiatedBounds, index,
+ _s24_ = "Missing canonical recipe",
+ t1 = rti._kind,
+ kind = t1;
+ switch (kind) {
+ case 5:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ return rti;
+ case 6:
+ baseType = rti._primary;
+ instantiatedBaseType = H._instantiate(universe, baseType, typeArguments, depth);
+ if (instantiatedBaseType === baseType)
+ return rti;
+ s = instantiatedBaseType._canonicalRecipe;
+ if (H.assertTest(typeof s == "string"))
+ H.assertThrow(_s24_);
+ return H._Universe__lookupUnaryRti(universe, 6, instantiatedBaseType, s + "*");
+ case 7:
+ baseType = rti._primary;
+ instantiatedBaseType = H._instantiate(universe, baseType, typeArguments, depth);
+ if (instantiatedBaseType === baseType)
+ return rti;
+ s = instantiatedBaseType._canonicalRecipe;
+ if (H.assertTest(typeof s == "string"))
+ H.assertThrow(_s24_);
+ return H._Universe__lookupUnaryRti(universe, 7, instantiatedBaseType, s + "?");
+ case 8:
+ baseType = rti._primary;
+ instantiatedBaseType = H._instantiate(universe, baseType, typeArguments, depth);
+ if (instantiatedBaseType === baseType)
+ return rti;
+ s = instantiatedBaseType._canonicalRecipe;
+ if (H.assertTest(typeof s == "string"))
+ H.assertThrow(_s24_);
+ return H._Universe__lookupUnaryRti(universe, 8, instantiatedBaseType, s + "/");
+ case 9:
+ interfaceTypeArguments = H.Rti__getInterfaceTypeArguments(rti);
+ instantiatedInterfaceTypeArguments = H._instantiateArray(universe, interfaceTypeArguments, typeArguments, depth);
+ if (instantiatedInterfaceTypeArguments === interfaceTypeArguments)
+ return rti;
+ H.assertHelper(rti._kind === 9);
+ return H._Universe__lookupInterfaceRti(universe, rti._primary, instantiatedInterfaceTypeArguments);
+ case 10:
+ H.assertHelper(t1 === 10);
+ base = rti._primary;
+ instantiatedBase = H._instantiate(universe, base, typeArguments, depth);
+ $arguments = H.Rti__getBindingArguments(rti);
+ instantiatedArguments = H._instantiateArray(universe, $arguments, typeArguments, depth);
+ if (instantiatedBase === base && instantiatedArguments === $arguments)
+ return rti;
+ return H._Universe__lookupBindingRti(universe, instantiatedBase, instantiatedArguments);
+ case 11:
+ H.assertHelper(t1 === 11);
+ returnType = rti._primary;
+ instantiatedReturnType = H._instantiate(universe, returnType, typeArguments, depth);
+ functionParameters = H.Rti__getFunctionParameters(rti);
+ instantiatedFunctionParameters = H._instantiateFunctionParameters(universe, functionParameters, typeArguments, depth);
+ if (instantiatedReturnType === returnType && instantiatedFunctionParameters === functionParameters)
+ return rti;
+ return H._Universe__lookupFunctionRti(universe, instantiatedReturnType, instantiatedFunctionParameters);
+ case 12:
+ bounds = H.Rti__getGenericFunctionBounds(rti);
+ depth += bounds.length;
+ instantiatedBounds = H._instantiateArray(universe, bounds, typeArguments, depth);
+ H.assertHelper(rti._kind === 12);
+ base = rti._primary;
+ instantiatedBase = H._instantiate(universe, base, typeArguments, depth);
+ if (instantiatedBounds === bounds && instantiatedBase === base)
+ return rti;
+ return H._Universe__lookupGenericFunctionRti(universe, instantiatedBase, instantiatedBounds);
+ case 13:
+ H.assertHelper(t1 === 13);
+ index = rti._primary;
+ if (index < depth)
+ return null;
+ return typeArguments[index - depth];
+ default:
+ throw H.wrapException(P.AssertionError$("Attempted to instantiate unexpected RTI kind " + kind));
+ }
+ },
+ _instantiateArray: function(universe, rtiArray, typeArguments, depth) {
+ var changed, i, rti, instantiatedRti,
+ $length = rtiArray.length,
+ result = [];
+ for (changed = false, i = 0; i < $length; ++i) {
+ rti = rtiArray[i];
+ instantiatedRti = H._instantiate(universe, rti, typeArguments, depth);
+ if (instantiatedRti !== rti)
+ changed = true;
+ result.push(instantiatedRti);
+ }
+ return changed ? result : rtiArray;
+ },
+ _instantiateNamed: function(universe, namedArray, typeArguments, depth) {
+ var result, changed, i, t1, rti, instantiatedRti,
+ $length = namedArray.length;
+ H.assertHelper(($length & 1) === 0);
+ result = [];
+ for (changed = false, i = 0; i < $length; i += 2) {
+ t1 = namedArray[i];
+ rti = namedArray[i + 1];
+ instantiatedRti = H._instantiate(universe, rti, typeArguments, depth);
+ if (instantiatedRti !== rti)
+ changed = true;
+ result.push(t1);
+ result.push(instantiatedRti);
+ }
+ return changed ? result : namedArray;
+ },
+ _instantiateFunctionParameters: function(universe, functionParameters, typeArguments, depth) {
+ var result,
+ requiredPositional = functionParameters._requiredPositional,
+ instantiatedRequiredPositional = H._instantiateArray(universe, requiredPositional, typeArguments, depth),
+ optionalPositional = functionParameters._optionalPositional,
+ instantiatedOptionalPositional = H._instantiateArray(universe, optionalPositional, typeArguments, depth),
+ optionalNamed = functionParameters._optionalNamed,
+ instantiatedOptionalNamed = H._instantiateNamed(universe, optionalNamed, typeArguments, depth);
+ if (instantiatedRequiredPositional === requiredPositional && instantiatedOptionalPositional === optionalPositional && instantiatedOptionalNamed === optionalNamed)
+ return functionParameters;
+ result = new H._FunctionParameters();
+ result._requiredPositional = instantiatedRequiredPositional;
+ result._optionalPositional = instantiatedOptionalPositional;
+ result._optionalNamed = instantiatedOptionalNamed;
+ return result;
+ },
+ closureFunctionType: function(closure) {
+ var signature = closure.$signature;
+ if (signature != null) {
+ if (typeof signature == "number")
+ return H.getTypeFromTypesTable(signature);
+ return closure.$signature();
+ }
+ return null;
+ },
+ instanceOrFunctionType: function(object, testRti) {
+ var rti;
+ if (H.Rti__isUnionOfFunctionType(testRti))
+ if (object instanceof H.Closure) {
+ rti = H.closureFunctionType(object);
+ if (rti != null)
+ return rti;
+ }
+ return H.instanceType(object);
+ },
+ instanceType: function(object) {
+ var rti;
+ if (object instanceof P.Object) {
+ rti = object.$ti;
+ return rti != null ? rti : H._instanceTypeFromConstructor(object);
+ }
+ if (Array.isArray(object))
+ return H._arrayInstanceType(object);
+ return H._instanceTypeFromConstructor(J.getInterceptor$(object));
+ },
+ _arrayInstanceType: function(object) {
+ var rti = object.$ti,
+ defaultRti = type$.JSArray_dynamic;
+ if (rti == null)
+ return defaultRti;
+ if (rti.constructor !== defaultRti.constructor)
+ return defaultRti;
+ return rti;
+ },
+ _instanceType: function(object) {
+ var rti = object.$ti;
+ return rti != null ? rti : H._instanceTypeFromConstructor(object);
+ },
+ _instanceTypeFromConstructor: function(instance) {
+ var $constructor = instance.constructor,
+ probe = $constructor.$ccache;
+ if (probe != null)
+ return probe;
+ return H._instanceTypeFromConstructorMiss(instance, $constructor);
+ },
+ _instanceTypeFromConstructorMiss: function(instance, $constructor) {
+ var effectiveConstructor = instance instanceof H.Closure ? instance.__proto__.__proto__.constructor : $constructor,
+ rti = H._Universe_findErasedType(init.typeUniverse, effectiveConstructor.name);
+ $constructor.$ccache = rti;
+ return rti;
+ },
+ getTypeFromTypesTable: function(_index) {
+ var rti,
+ index = _index,
+ table = init.types,
+ type = table[index];
+ if (typeof type == "string") {
+ rti = H._Universe_eval(init.typeUniverse, type);
+ table[index] = rti;
+ return rti;
+ }
+ return type;
+ },
+ createRuntimeType: function(rti) {
+ var type = rti._cachedRuntimeType;
+ if (type != null)
+ return type;
+ return rti._cachedRuntimeType = new H._Type(rti);
+ },
+ typeLiteral: function(recipe) {
+ return H.createRuntimeType(H._Universe_eval(init.typeUniverse, recipe));
+ },
+ _installSpecializedIsTest: function(object) {
+ var s, key, testRti = this,
+ t1 = testRti._kind,
+ isFn = H._generalIsTestImplementation;
+ if (H.isTopType(testRti, true)) {
+ isFn = H._isTop;
+ testRti._check = testRti._as = H._asTop;
+ } else if (t1 === 9) {
+ s = testRti._canonicalRecipe;
+ if (H.assertTest(typeof s == "string"))
+ H.assertThrow("Missing canonical recipe");
+ key = s;
+ if ("int" === key)
+ isFn = H._isInt;
+ else if ("double" === key)
+ isFn = H._isNum;
+ else if ("num" === key)
+ isFn = H._isNum;
+ else if ("String" === key)
+ isFn = H._isString;
+ else if ("bool" === key)
+ isFn = H._isBool;
+ else {
+ H.assertHelper(testRti._kind === 9);
+ t1 = testRti._primary;
+ if (H.Rti__getInterfaceTypeArguments(testRti).every(H.isLegacyTopType)) {
+ testRti._specializedTestResource = "$is" + t1;
+ isFn = H._isTestViaProperty;
+ }
+ }
+ }
+ testRti._is = isFn;
+ return testRti._is(object);
+ },
+ _generalIsTestImplementation: function(object) {
+ var testRti = this;
+ return H._isSubtype(init.typeUniverse, H.instanceOrFunctionType(object, testRti), null, testRti, null, true);
+ },
+ _isTestViaProperty: function(object) {
+ var tag = this._specializedTestResource;
+ if (object instanceof P.Object)
+ return !!object[tag];
+ return !!J.getInterceptor$(object)[tag];
+ },
+ _generalAsCheckImplementation: function(object) {
+ var testRti;
+ if (object == null)
+ return object;
+ testRti = this;
+ if (testRti._is(object))
+ return object;
+ throw H.wrapException(H._CastError$fromMessage(H._Error_compose(object, H.instanceOrFunctionType(object, testRti), H._rtiToString(testRti, null))));
+ },
+ _generalTypeCheckImplementation: function(object) {
+ var testRti;
+ if (object == null)
+ return object;
+ testRti = this;
+ if (testRti._is(object))
+ return object;
+ throw H.wrapException(H._TypeError$fromMessage(H._Error_compose(object, H.instanceOrFunctionType(object, testRti), H._rtiToString(testRti, null))));
+ },
+ checkTypeBound: function(type, bound, variable, methodName) {
+ var _null = null;
+ if (H._isSubtype(init.typeUniverse, type, _null, bound, _null, true))
+ return type;
+ throw H.wrapException(H._TypeError$fromMessage("The type argument '" + H.S(H._rtiToString(type, _null)) + "' is not a subtype of the type variable bound '" + H.S(H._rtiToString(bound, _null)) + "' of type variable '" + variable + "' in '" + H.S(methodName) + "'."));
+ },
+ _Error_compose: function(object, objectRti, checkedTypeDescription) {
+ var objectDescription = P.Error_safeToString(object),
+ objectTypeDescription = H._rtiToString(objectRti == null ? H.instanceType(object) : objectRti, null);
+ return objectDescription + ": type '" + H.S(objectTypeDescription) + "' is not a subtype of type '" + H.S(checkedTypeDescription) + "'";
+ },
+ _CastError$fromMessage: function(message) {
+ return new H._CastError("CastError: " + message);
+ },
+ _CastError__CastError$forType: function(object, type) {
+ return new H._CastError("CastError: " + H._Error_compose(object, null, type));
+ },
+ _TypeError$fromMessage: function(message) {
+ return new H._TypeError("TypeError: " + message);
+ },
+ _TypeError__TypeError$forType: function(object, type) {
+ return new H._TypeError("TypeError: " + H._Error_compose(object, null, type));
+ },
+ _isTop: function(object) {
+ return true;
+ },
+ _asTop: function(object) {
+ return object;
+ },
+ _isBool: function(object) {
+ return true === object || false === object;
+ },
+ _asBoolNullable: function(object) {
+ if (true === object || false === object)
+ return object;
+ if (object == null)
+ return object;
+ throw H.wrapException(H._CastError__CastError$forType(object, "bool"));
+ },
+ _checkBoolNullable: function(object) {
+ if (true === object || false === object)
+ return object;
+ if (object == null)
+ return object;
+ throw H.wrapException(H._TypeError__TypeError$forType(object, "bool"));
+ },
+ _asDoubleNullable: function(object) {
+ if (typeof object == "number")
+ return object;
+ if (object == null)
+ return object;
+ throw H.wrapException(H._CastError__CastError$forType(object, "double"));
+ },
+ _checkDoubleNullable: function(object) {
+ if (typeof object == "number")
+ return object;
+ if (object == null)
+ return object;
+ throw H.wrapException(H._TypeError__TypeError$forType(object, "double"));
+ },
+ _isInt: function(object) {
+ return typeof object == "number" && Math.floor(object) === object;
+ },
+ _asIntNullable: function(object) {
+ if (typeof object == "number" && Math.floor(object) === object)
+ return object;
+ if (object == null)
+ return object;
+ throw H.wrapException(H._CastError__CastError$forType(object, "int"));
+ },
+ _checkIntNullable: function(object) {
+ if (typeof object == "number" && Math.floor(object) === object)
+ return object;
+ if (object == null)
+ return object;
+ throw H.wrapException(H._TypeError__TypeError$forType(object, "int"));
+ },
+ _isNum: function(object) {
+ return typeof object == "number";
+ },
+ _asNumNullable: function(object) {
+ if (typeof object == "number")
+ return object;
+ if (object == null)
+ return object;
+ throw H.wrapException(H._CastError__CastError$forType(object, "num"));
+ },
+ _checkNumNullable: function(object) {
+ if (typeof object == "number")
+ return object;
+ if (object == null)
+ return object;
+ throw H.wrapException(H._TypeError__TypeError$forType(object, "num"));
+ },
+ _isString: function(object) {
+ return typeof object == "string";
+ },
+ _asStringNullable: function(object) {
+ if (typeof object == "string")
+ return object;
+ if (object == null)
+ return object;
+ throw H.wrapException(H._CastError__CastError$forType(object, "String"));
+ },
+ _checkStringNullable: function(object) {
+ if (typeof object == "string")
+ return object;
+ if (object == null)
+ return object;
+ throw H.wrapException(H._TypeError__TypeError$forType(object, "String"));
+ },
+ _rtiArrayToString: function(array, genericContext) {
+ var s, sep, i;
+ for (s = "", sep = "", i = 0; i < array.length; ++i, sep = ", ")
+ s += C.JSString_methods.$add(sep, H._rtiToString(array[i], genericContext));
+ return s;
+ },
+ _functionRtiToString: function(functionType, genericContext, bounds) {
+ var boundsLength, outerContextLength, offset, i, typeParametersText, typeSep, t1, t2, boundRti, parameters, requiredPositional, requiredPositionalLength, optionalPositional, optionalPositionalLength, optionalNamed, optionalNamedLength, returnTypeText, argumentsText, sep, _s2_ = ", ";
+ if (bounds != null) {
+ boundsLength = bounds.length;
+ if (genericContext == null) {
+ genericContext = H.setRuntimeTypeInfo([], type$.JSArray_String);
+ outerContextLength = null;
+ } else
+ outerContextLength = genericContext.length;
+ offset = genericContext.length;
+ for (i = boundsLength; i > 0; --i)
+ C.JSArray_methods.add$1(genericContext, "T" + (offset + i));
+ for (typeParametersText = "<", typeSep = "", i = 0; i < boundsLength; ++i, typeSep = _s2_) {
+ typeParametersText += typeSep;
+ t1 = genericContext.length;
+ t2 = t1 - 1 - i;
+ if (t2 < 0)
+ return H.ioore(genericContext, t2);
+ typeParametersText = C.JSString_methods.$add(typeParametersText, genericContext[t2]);
+ boundRti = bounds[i];
+ if (!H.isTopType(boundRti, true))
+ typeParametersText += C.JSString_methods.$add(" extends ", H._rtiToString(boundRti, genericContext));
+ }
+ typeParametersText += ">";
+ } else {
+ typeParametersText = "";
+ outerContextLength = null;
+ }
+ H.assertHelper(functionType._kind === 11);
+ t1 = functionType._primary;
+ parameters = H.Rti__getFunctionParameters(functionType);
+ requiredPositional = parameters._requiredPositional;
+ requiredPositionalLength = requiredPositional.length;
+ optionalPositional = parameters._optionalPositional;
+ optionalPositionalLength = optionalPositional.length;
+ optionalNamed = parameters._optionalNamed;
+ optionalNamedLength = optionalNamed.length;
+ H.assertHelper(optionalPositionalLength === 0 || optionalNamedLength === 0);
+ returnTypeText = H._rtiToString(t1, genericContext);
+ for (argumentsText = "", sep = "", i = 0; i < requiredPositionalLength; ++i, sep = _s2_)
+ argumentsText += C.JSString_methods.$add(sep, H._rtiToString(requiredPositional[i], genericContext));
+ if (optionalPositionalLength > 0) {
+ argumentsText += sep + "[";
+ for (sep = "", i = 0; i < optionalPositionalLength; ++i, sep = _s2_)
+ argumentsText += C.JSString_methods.$add(sep, H._rtiToString(optionalPositional[i], genericContext));
+ argumentsText += "]";
+ }
+ if (optionalNamedLength > 0) {
+ argumentsText += sep + "{";
+ for (sep = "", i = 0; i < optionalNamedLength; i += 2, sep = _s2_)
+ argumentsText += C.JSString_methods.$add(sep, H._rtiToString(optionalNamed[i + 1], genericContext)) + " " + optionalNamed[i];
+ argumentsText += "}";
+ }
+ if (outerContextLength != null)
+ genericContext.length = outerContextLength;
+ return typeParametersText + "(" + argumentsText + ") => " + H.S(returnTypeText);
+ },
+ _rtiToString: function(rti, genericContext) {
+ var $name, $arguments, t2,
+ t1 = rti._kind,
+ kind = t1;
+ if (kind === 5)
+ return "erased";
+ if (kind === 2)
+ return "dynamic";
+ if (kind === 3)
+ return "void";
+ if (kind === 1)
+ return "Never";
+ if (kind === 4)
+ return "any";
+ if (kind === 6) {
+ H.assertHelper(t1 === 6);
+ return H.S(H._rtiToString(rti._primary, genericContext)) + "*";
+ }
+ if (kind === 7) {
+ H.assertHelper(t1 === 7);
+ return H.S(H._rtiToString(rti._primary, genericContext)) + "?";
+ }
+ if (kind === 8) {
+ H.assertHelper(t1 === 8);
+ return "FutureOr<" + H.S(H._rtiToString(rti._primary, genericContext)) + ">";
+ }
+ if (kind === 9) {
+ H.assertHelper(t1 === 9);
+ $name = H._unminifyOrTag(rti._primary);
+ $arguments = H.Rti__getInterfaceTypeArguments(rti);
+ return $arguments.length !== 0 ? $name + ("<" + H._rtiArrayToString($arguments, genericContext) + ">") : $name;
+ }
+ if (kind === 11)
+ return H._functionRtiToString(rti, genericContext, null);
+ if (kind === 12) {
+ H.assertHelper(t1 === 12);
+ return H._functionRtiToString(rti._primary, genericContext, H.Rti__getGenericFunctionBounds(rti));
+ }
+ if (kind === 13) {
+ H.assertHelper(t1 === 13);
+ t1 = rti._primary;
+ t2 = genericContext.length;
+ t1 = t2 - 1 - t1;
+ if (t1 < 0 || t1 >= t2)
+ return H.ioore(genericContext, t1);
+ return genericContext[t1];
+ }
+ return "?";
+ },
+ _unminifyOrTag: function(rawClassName) {
+ var preserved = H.unmangleGlobalNameIfPreservedAnyways(rawClassName);
+ if (preserved != null)
+ return preserved;
+ return rawClassName;
+ },
+ _Universe_findRule: function(universe, targetType) {
+ var rule = universe.tR[targetType];
+ for (; typeof rule == "string";)
+ rule = universe.tR[rule];
+ return rule;
+ },
+ _Universe_findErasedType: function(universe, cls) {
+ var $length, erased, $arguments, i, $interface,
+ metadata = universe.eT,
+ probe = metadata[cls];
+ if (probe == null)
+ return H._Universe_eval(universe, cls);
+ else if (typeof probe == "number") {
+ $length = probe;
+ erased = H._Universe__lookupTerminalRti(universe, 5, "#");
+ $arguments = [];
+ for (i = 0; i < $length; ++i)
+ $arguments.push(erased);
+ $interface = H._Universe__lookupInterfaceRti(universe, cls, $arguments);
+ metadata[cls] = $interface;
+ return $interface;
+ } else
+ return probe;
+ },
+ _Universe_addRules: function(universe, rules) {
+ return H._Utils_objectAssign(universe.tR, rules);
+ },
+ _Universe_addErasedTypes: function(universe, types) {
+ return H._Utils_objectAssign(universe.eT, types);
+ },
+ _Universe_eval: function(universe, recipe) {
+ var rti,
+ cache = universe.eC,
+ probe = cache.get(recipe);
+ if (probe != null)
+ return probe;
+ rti = H._Universe__parseRecipe(universe, null, recipe);
+ cache.set(recipe, rti);
+ return rti;
+ },
+ _Universe_evalInEnvironment: function(universe, environment, recipe) {
+ var probe, rti,
+ cache = environment._evalCache;
+ if (cache == null)
+ cache = environment._evalCache = new Map();
+ probe = cache.get(recipe);
+ if (probe != null)
+ return probe;
+ rti = H._Universe__parseRecipe(universe, environment, recipe);
+ cache.set(recipe, rti);
+ return rti;
+ },
+ _Universe_bind: function(universe, environment, argumentsRti) {
+ var s, argumentsRecipe, probe, rti,
+ cache = environment._bindCache;
+ if (cache == null)
+ cache = environment._bindCache = new Map();
+ s = argumentsRti._canonicalRecipe;
+ if (H.assertTest(typeof s == "string"))
+ H.assertThrow("Missing canonical recipe");
+ argumentsRecipe = s;
+ probe = cache.get(argumentsRecipe);
+ if (probe != null)
+ return probe;
+ rti = H._Universe__lookupBindingRti(universe, environment, argumentsRti._kind === 10 ? H.Rti__getBindingArguments(argumentsRti) : [argumentsRti]);
+ cache.set(argumentsRecipe, rti);
+ return rti;
+ },
+ _Universe__parseRecipe: function(universe, environment, recipe) {
+ var rti = H._Parser_parse(H._Parser_create(universe, environment, recipe));
+ return rti;
+ },
+ _Universe__finishRti: function(universe, rti) {
+ var s = rti._canonicalRecipe;
+ if (H.assertTest(typeof s == "string"))
+ H.assertThrow("Missing canonical recipe");
+ universe.eC.set(s, rti);
+ rti._as = H._generalAsCheckImplementation;
+ rti._check = H._generalTypeCheckImplementation;
+ rti._is = H._installSpecializedIsTest;
+ return rti;
+ },
+ _Universe__lookupTerminalRti: function(universe, kind, canonicalRecipe) {
+ var rti,
+ probe = universe.eC.get(canonicalRecipe);
+ if (probe != null)
+ return probe;
+ rti = new H.Rti(null, null, null);
+ rti._kind = kind;
+ rti._canonicalRecipe = canonicalRecipe;
+ return H._Universe__finishRti(universe, rti);
+ },
+ _Universe__lookupUnaryRti: function(universe, kind, baseType, canonicalRecipe) {
+ var rti,
+ probe = universe.eC.get(canonicalRecipe);
+ if (probe != null)
+ return probe;
+ rti = new H.Rti(null, null, null);
+ rti._kind = kind;
+ rti._primary = baseType;
+ rti._canonicalRecipe = canonicalRecipe;
+ return H._Universe__finishRti(universe, rti);
+ },
+ _Universe__lookupGenericFunctionParameterRti: function(universe, index) {
+ var rti,
+ canonicalRecipe = "" + index + "^",
+ probe = universe.eC.get(canonicalRecipe);
+ if (probe != null)
+ return probe;
+ rti = new H.Rti(null, null, null);
+ rti._kind = 13;
+ rti._primary = index;
+ rti._canonicalRecipe = canonicalRecipe;
+ return H._Universe__finishRti(universe, rti);
+ },
+ _Universe__canonicalRecipeJoin: function($arguments) {
+ var s, sep, i, s0,
+ $length = $arguments.length;
+ for (s = "", sep = "", i = 0; i < $length; ++i, sep = ",") {
+ s0 = $arguments[i]._canonicalRecipe;
+ if (H.assertTest(typeof s0 == "string"))
+ H.assertThrow("Missing canonical recipe");
+ s += sep + s0;
+ }
+ return s;
+ },
+ _Universe__canonicalRecipeJoinNamed: function($arguments) {
+ var s, sep, i, t1, s0,
+ $length = $arguments.length;
+ H.assertHelper(($length & 1) === 0);
+ for (s = "", sep = "", i = 0; i < $length; i += 2, sep = ",") {
+ t1 = $arguments[i];
+ s0 = $arguments[i + 1]._canonicalRecipe;
+ if (H.assertTest(typeof s0 == "string"))
+ H.assertThrow("Missing canonical recipe");
+ s += sep + t1 + ":" + s0;
+ }
+ return s;
+ },
+ _Universe__lookupInterfaceRti: function(universe, $name, $arguments) {
+ var s, probe, rti;
+ H.assertHelper(typeof $name == "string");
+ s = $name;
+ if ($arguments.length !== 0)
+ s += "<" + H._Universe__canonicalRecipeJoin($arguments) + ">";
+ probe = universe.eC.get(s);
+ if (probe != null)
+ return probe;
+ rti = new H.Rti(null, null, null);
+ rti._kind = 9;
+ rti._primary = $name;
+ rti._rest = $arguments;
+ if ($arguments.length > 0)
+ rti._precomputed1 = $arguments[0];
+ rti._canonicalRecipe = s;
+ return H._Universe__finishRti(universe, rti);
+ },
+ _Universe__lookupBindingRti: function(universe, base, $arguments) {
+ var newBase, newArguments, s, probe, rti,
+ t1 = base._kind;
+ if (t1 === 10) {
+ H.assertHelper(t1 === 10);
+ newBase = base._primary;
+ newArguments = H.Rti__getBindingArguments(base).concat($arguments);
+ } else {
+ newArguments = $arguments;
+ newBase = base;
+ }
+ s = newBase._canonicalRecipe;
+ if (H.assertTest(typeof s == "string"))
+ H.assertThrow("Missing canonical recipe");
+ s = s + ";" + ("<" + H._Universe__canonicalRecipeJoin(newArguments) + ">");
+ probe = universe.eC.get(s);
+ if (probe != null)
+ return probe;
+ rti = new H.Rti(null, null, null);
+ rti._kind = 10;
+ rti._primary = newBase;
+ rti._rest = newArguments;
+ rti._canonicalRecipe = s;
+ return H._Universe__finishRti(universe, rti);
+ },
+ _Universe__lookupFunctionRti: function(universe, returnType, parameters) {
+ var requiredPositional, requiredPositionalLength, optionalPositional, optionalPositionalLength, optionalNamed, optionalNamedLength, recipe, key, probe, rti,
+ s = returnType._canonicalRecipe;
+ if (H.assertTest(typeof s == "string"))
+ H.assertThrow("Missing canonical recipe");
+ requiredPositional = parameters._requiredPositional;
+ requiredPositionalLength = requiredPositional.length;
+ optionalPositional = parameters._optionalPositional;
+ optionalPositionalLength = optionalPositional.length;
+ optionalNamed = parameters._optionalNamed;
+ optionalNamedLength = optionalNamed.length;
+ H.assertHelper(optionalPositionalLength === 0 || optionalNamedLength === 0);
+ recipe = "(" + H._Universe__canonicalRecipeJoin(requiredPositional);
+ if (optionalPositionalLength > 0)
+ recipe += (requiredPositionalLength > 0 ? "," : "") + "[" + H._Universe__canonicalRecipeJoin(optionalPositional) + "]";
+ if (optionalNamedLength > 0)
+ recipe += (requiredPositionalLength > 0 ? "," : "") + "{" + H._Universe__canonicalRecipeJoinNamed(optionalNamed) + "}";
+ key = s + (recipe + ")");
+ probe = universe.eC.get(key);
+ if (probe != null)
+ return probe;
+ rti = new H.Rti(null, null, null);
+ rti._kind = 11;
+ rti._primary = returnType;
+ rti._rest = parameters;
+ rti._canonicalRecipe = key;
+ return H._Universe__finishRti(universe, rti);
+ },
+ _Universe__lookupGenericFunctionRti: function(universe, baseFunctionType, bounds) {
+ var key, probe, rti,
+ s = baseFunctionType._canonicalRecipe;
+ if (H.assertTest(typeof s == "string"))
+ H.assertThrow("Missing canonical recipe");
+ key = s + "<" + H._Universe__canonicalRecipeJoin(bounds) + ">";
+ probe = universe.eC.get(key);
+ if (probe != null)
+ return probe;
+ rti = new H.Rti(null, null, null);
+ rti._kind = 12;
+ rti._primary = baseFunctionType;
+ rti._rest = bounds;
+ rti._canonicalRecipe = key;
+ return H._Universe__finishRti(universe, rti);
+ },
+ _Parser_create: function(universe, environment, recipe) {
+ return {u: universe, e: environment, r: recipe, s: [], p: 0};
+ },
+ _Parser_parse: function(parser) {
+ var t1, i, ch, t2, t3, universe, array, head, base, u, s, parameters, optionalPositional, optionalNamed, item,
+ _s24_ = "Missing canonical recipe",
+ source = parser.r,
+ stack = parser.s;
+ for (t1 = source.length, i = 0; i < t1;) {
+ ch = source.charCodeAt(i);
+ if (ch >= 48 && ch <= 57)
+ i = H._Parser_handleDigit(i + 1, ch, source, stack);
+ else if ((((ch | 32) >>> 0) - 97 & 65535) < 26 || ch === 95 || ch === 36)
+ i = H._Parser_handleIdentifier(parser, i, source, stack, false);
+ else if (ch === 46)
+ i = H._Parser_handleIdentifier(parser, i, source, stack, true);
+ else {
+ ++i;
+ switch (ch) {
+ case 44:
+ break;
+ case 58:
+ break;
+ case 59:
+ stack.push(H._Parser_toType(parser.u, parser.e, stack.pop()));
+ break;
+ case 94:
+ t2 = parser.u;
+ t3 = stack.pop();
+ H.assertHelper(typeof t3 == "number");
+ stack.push(H._Universe__lookupGenericFunctionParameterRti(t2, t3));
+ break;
+ case 35:
+ stack.push(H._Universe__lookupTerminalRti(parser.u, 5, "#"));
+ break;
+ case 64:
+ stack.push(H._Universe__lookupTerminalRti(parser.u, 2, "@"));
+ break;
+ case 126:
+ stack.push(H._Universe__lookupTerminalRti(parser.u, 3, "~"));
+ break;
+ case 60:
+ stack.push(parser.p);
+ parser.p = stack.length;
+ break;
+ case 62:
+ universe = parser.u;
+ array = stack.splice(parser.p);
+ H._Parser_toTypes(parser.u, parser.e, array);
+ parser.p = stack.pop();
+ head = stack.pop();
+ if (typeof head == "string")
+ stack.push(H._Universe__lookupInterfaceRti(universe, head, array));
+ else {
+ base = H._Parser_toType(universe, parser.e, head);
+ switch (base._kind) {
+ case 11:
+ stack.push(H._Universe__lookupGenericFunctionRti(universe, base, array));
+ break;
+ default:
+ stack.push(H._Universe__lookupBindingRti(universe, base, array));
+ break;
+ }
+ }
+ break;
+ case 38:
+ H._Parser_handleExtendedOperations(parser, stack);
+ break;
+ case 42:
+ u = parser.u;
+ t2 = H._Parser_toType(u, parser.e, stack.pop());
+ s = t2._canonicalRecipe;
+ if (H.assertTest(typeof s == "string"))
+ H.assertThrow(_s24_);
+ stack.push(H._Universe__lookupUnaryRti(u, 6, t2, s + "*"));
+ break;
+ case 63:
+ u = parser.u;
+ t2 = H._Parser_toType(u, parser.e, stack.pop());
+ s = t2._canonicalRecipe;
+ if (H.assertTest(typeof s == "string"))
+ H.assertThrow(_s24_);
+ stack.push(H._Universe__lookupUnaryRti(u, 7, t2, s + "?"));
+ break;
+ case 47:
+ u = parser.u;
+ t2 = H._Parser_toType(u, parser.e, stack.pop());
+ s = t2._canonicalRecipe;
+ if (H.assertTest(typeof s == "string"))
+ H.assertThrow(_s24_);
+ stack.push(H._Universe__lookupUnaryRti(u, 8, t2, s + "/"));
+ break;
+ case 40:
+ stack.push(parser.p);
+ parser.p = stack.length;
+ break;
+ case 41:
+ universe = parser.u;
+ parameters = new H._FunctionParameters();
+ optionalPositional = universe.sEA;
+ optionalNamed = universe.sEA;
+ head = stack.pop();
+ if (typeof head == "number")
+ switch (head) {
+ case -1:
+ optionalPositional = stack.pop();
+ break;
+ case -2:
+ optionalNamed = stack.pop();
+ break;
+ default:
+ stack.push(head);
+ break;
+ }
+ else
+ stack.push(head);
+ array = stack.splice(parser.p);
+ H._Parser_toTypes(parser.u, parser.e, array);
+ parser.p = stack.pop();
+ parameters._requiredPositional = array;
+ parameters._optionalPositional = optionalPositional;
+ parameters._optionalNamed = optionalNamed;
+ stack.push(H._Universe__lookupFunctionRti(universe, H._Parser_toType(universe, parser.e, stack.pop()), parameters));
+ break;
+ case 91:
+ stack.push(parser.p);
+ parser.p = stack.length;
+ break;
+ case 93:
+ array = stack.splice(parser.p);
+ H._Parser_toTypes(parser.u, parser.e, array);
+ parser.p = stack.pop();
+ stack.push(array);
+ stack.push(-1);
+ break;
+ case 123:
+ stack.push(parser.p);
+ parser.p = stack.length;
+ break;
+ case 125:
+ array = stack.splice(parser.p);
+ H._Parser_toTypesNamed(parser.u, parser.e, array);
+ parser.p = stack.pop();
+ stack.push(array);
+ stack.push(-2);
+ break;
+ default:
+ throw "Bad character " + ch;
+ }
+ }
+ }
+ item = stack.pop();
+ return H._Parser_toType(parser.u, parser.e, item);
+ },
+ _Parser_handleDigit: function(i, digit, source, stack) {
+ var t1, ch,
+ value = digit - 48;
+ for (t1 = source.length; i < t1; ++i) {
+ ch = source.charCodeAt(i);
+ if (!(ch >= 48 && ch <= 57))
+ break;
+ value = value * 10 + (ch - 48);
+ }
+ stack.push(value);
+ return i;
+ },
+ _Parser_handleIdentifier: function(parser, start, source, stack, hasPeriod) {
+ var t1, ch, t2, string, environment, rule, recipe,
+ i = start + 1;
+ for (t1 = source.length; i < t1; ++i) {
+ ch = source.charCodeAt(i);
+ if (ch === 46) {
+ if (hasPeriod)
+ break;
+ hasPeriod = true;
+ } else {
+ if (!((((ch | 32) >>> 0) - 97 & 65535) < 26 || ch === 95 || ch === 36))
+ t2 = ch >= 48 && ch <= 57;
+ else
+ t2 = true;
+ if (!t2)
+ break;
+ }
+ }
+ string = source.substring(start, i);
+ if (hasPeriod) {
+ t1 = parser.u;
+ environment = parser.e;
+ t2 = environment._kind;
+ if (t2 === 10) {
+ H.assertHelper(t2 === 10);
+ environment = environment._primary;
+ }
+ H.assertHelper(environment._kind === 9);
+ rule = H._Universe_findRule(t1, environment._primary);
+ H.assertHelper(rule != null);
+ recipe = rule[string];
+ if (recipe == null)
+ H.throwExpression('No "' + string + '" in "' + H.Rti__getCanonicalRecipe(environment) + '"');
+ stack.push(H._Universe_evalInEnvironment(t1, environment, recipe));
+ } else
+ stack.push(string);
+ return i;
+ },
+ _Parser_handleExtendedOperations: function(parser, stack) {
+ var $top = stack.pop();
+ if (0 === $top) {
+ stack.push(H._Universe__lookupTerminalRti(parser.u, 1, "0&"));
+ return;
+ }
+ if (1 === $top) {
+ stack.push(H._Universe__lookupTerminalRti(parser.u, 4, "1&"));
+ return;
+ }
+ throw H.wrapException(P.AssertionError$("Unexpected extended operation " + H.S($top)));
+ },
+ _Parser_toType: function(universe, environment, item) {
+ if (typeof item == "string")
+ return H._Universe__lookupInterfaceRti(universe, item, universe.sEA);
+ else if (typeof item == "number")
+ return H._Parser_indexToType(universe, environment, item);
+ else
+ return item;
+ },
+ _Parser_toTypes: function(universe, environment, items) {
+ var i,
+ $length = items.length;
+ for (i = 0; i < $length; ++i)
+ items[i] = H._Parser_toType(universe, environment, items[i]);
+ },
+ _Parser_toTypesNamed: function(universe, environment, items) {
+ var i,
+ $length = items.length;
+ H.assertHelper(($length & 1) === 0);
+ for (i = 1; i < $length; i += 2)
+ items[i] = H._Parser_toType(universe, environment, items[i]);
+ },
+ _Parser_indexToType: function(universe, environment, index) {
+ var typeArguments, len,
+ t1 = environment._kind,
+ kind = t1;
+ if (kind === 10) {
+ if (index === 0) {
+ H.assertHelper(t1 === 10);
+ return environment._primary;
+ }
+ typeArguments = H.Rti__getBindingArguments(environment);
+ len = typeArguments.length;
+ if (index <= len)
+ return typeArguments[index - 1];
+ index -= len;
+ H.assertHelper(t1 === 10);
+ environment = environment._primary;
+ kind = environment._kind;
+ } else if (index === 0)
+ return environment;
+ if (kind !== 9)
+ throw H.wrapException(P.AssertionError$("Indexed base must be an interface type"));
+ typeArguments = H.Rti__getInterfaceTypeArguments(environment);
+ if (index <= typeArguments.length)
+ return typeArguments[index - 1];
+ throw H.wrapException(P.AssertionError$("Bad index " + index + " for " + environment.toString$0(0)));
+ },
+ _isSubtype: function(universe, s, sEnv, t, tEnv, isLegacy) {
+ var sKind, leftTypeVariable, t1, tKind, sBounds, tBounds;
+ if (s === t)
+ return true;
+ if (H.isTopType(t, true))
+ return true;
+ sKind = s._kind;
+ if (sKind === 4)
+ return true;
+ if (H.isTopType(s, true))
+ return false;
+ if (s === type$.Null)
+ return true;
+ leftTypeVariable = sKind === 13;
+ if (leftTypeVariable) {
+ H.assertHelper(s._kind === 13);
+ if (H._isSubtype(universe, sEnv[s._primary], sEnv, t, tEnv, true))
+ return true;
+ }
+ t1 = t._kind;
+ tKind = t1;
+ if (sKind === 6) {
+ H.assertHelper(s._kind === 6);
+ return H._isSubtype(universe, s._primary, sEnv, t, tEnv, true);
+ }
+ if (tKind === 6) {
+ H.assertHelper(t1 === 6);
+ t1 = t._primary;
+ return H._isSubtype(universe, s, sEnv, t1, tEnv, true);
+ }
+ if (sKind === 8) {
+ H.assertHelper(s._kind === 8);
+ if (!H._isSubtype(universe, s._primary, sEnv, t, tEnv, true))
+ return false;
+ return H._isSubtype(universe, H.Rti__getFutureFromFutureOr(universe, s), sEnv, t, tEnv, true);
+ }
+ if (sKind === 7) {
+ H.assertHelper(s._kind === 7);
+ t1 = H._isSubtype(universe, s._primary, sEnv, t, tEnv, true);
+ return t1;
+ }
+ if (tKind === 8) {
+ H.assertHelper(t1 === 8);
+ if (H._isSubtype(universe, s, sEnv, t._primary, tEnv, true))
+ return true;
+ return H._isSubtype(universe, s, sEnv, H.Rti__getFutureFromFutureOr(universe, t), tEnv, true);
+ }
+ if (tKind === 7) {
+ H.assertHelper(t1 === 7);
+ t1 = H._isSubtype(universe, s, sEnv, t._primary, tEnv, true);
+ return t1;
+ }
+ if (leftTypeVariable)
+ return false;
+ t1 = sKind !== 11;
+ if ((!t1 || sKind === 12) && t === type$.Function)
+ return true;
+ if (tKind === 12) {
+ if (s === type$.JavaScriptFunction)
+ return true;
+ if (sKind !== 12)
+ return false;
+ sBounds = H.Rti__getGenericFunctionBounds(s);
+ tBounds = H.Rti__getGenericFunctionBounds(t);
+ if (!H.typesEqual(sBounds, tBounds, true))
+ return false;
+ sEnv = sEnv == null ? sBounds : sBounds.concat(sEnv);
+ tEnv = tEnv == null ? tBounds : tBounds.concat(tEnv);
+ H.assertHelper(s._kind === 12);
+ t1 = s._primary;
+ H.assertHelper(t._kind === 12);
+ return H._isFunctionSubtype(universe, t1, sEnv, t._primary, tEnv, true);
+ }
+ if (tKind === 11) {
+ if (s === type$.JavaScriptFunction)
+ return true;
+ if (t1)
+ return false;
+ return H._isFunctionSubtype(universe, s, sEnv, t, tEnv, true);
+ }
+ if (sKind === 9) {
+ if (tKind !== 9)
+ return false;
+ return H._isInterfaceSubtype(universe, s, sEnv, t, tEnv, true);
+ }
+ return false;
+ },
+ _isFunctionSubtype: function(universe, s, sEnv, t, tEnv, isLegacy) {
+ var t1, sParameters, tParameters, sRequiredPositional, tRequiredPositional, sRequiredPositionalLength, tRequiredPositionalLength, requiredPositionalDelta, sOptionalPositional, tOptionalPositional, sOptionalPositionalLength, tOptionalPositionalLength, i, sOptionalNamed, tOptionalNamed, sOptionalNamedLength, tOptionalNamedLength, j, tName, sName;
+ H.assertHelper(s._kind === 11);
+ H.assertHelper(t._kind === 11);
+ H.assertHelper(s._kind === 11);
+ t1 = s._primary;
+ H.assertHelper(t._kind === 11);
+ if (!H._isSubtype(universe, t1, sEnv, t._primary, tEnv, true))
+ return false;
+ sParameters = H.Rti__getFunctionParameters(s);
+ tParameters = H.Rti__getFunctionParameters(t);
+ sRequiredPositional = sParameters._requiredPositional;
+ tRequiredPositional = tParameters._requiredPositional;
+ sRequiredPositionalLength = sRequiredPositional.length;
+ tRequiredPositionalLength = tRequiredPositional.length;
+ if (sRequiredPositionalLength > tRequiredPositionalLength)
+ return false;
+ requiredPositionalDelta = tRequiredPositionalLength - sRequiredPositionalLength;
+ sOptionalPositional = sParameters._optionalPositional;
+ tOptionalPositional = tParameters._optionalPositional;
+ sOptionalPositionalLength = sOptionalPositional.length;
+ tOptionalPositionalLength = tOptionalPositional.length;
+ if (sRequiredPositionalLength + sOptionalPositionalLength < tRequiredPositionalLength + tOptionalPositionalLength)
+ return false;
+ for (i = 0; i < sRequiredPositionalLength; ++i) {
+ t1 = sRequiredPositional[i];
+ if (!H._isSubtype(universe, tRequiredPositional[i], tEnv, t1, sEnv, true))
+ return false;
+ }
+ for (i = 0; i < requiredPositionalDelta; ++i) {
+ t1 = sOptionalPositional[i];
+ if (!H._isSubtype(universe, tRequiredPositional[sRequiredPositionalLength + i], tEnv, t1, sEnv, true))
+ return false;
+ }
+ for (i = 0; i < tOptionalPositionalLength; ++i) {
+ t1 = sOptionalPositional[requiredPositionalDelta + i];
+ if (!H._isSubtype(universe, tOptionalPositional[i], tEnv, t1, sEnv, true))
+ return false;
+ }
+ sOptionalNamed = sParameters._optionalNamed;
+ tOptionalNamed = tParameters._optionalNamed;
+ sOptionalNamedLength = sOptionalNamed.length;
+ tOptionalNamedLength = tOptionalNamed.length;
+ for (i = 0, j = 0; j < tOptionalNamedLength; j += 2) {
+ tName = tOptionalNamed[j];
+ do {
+ if (i >= sOptionalNamedLength)
+ return false;
+ sName = sOptionalNamed[i];
+ i += 2;
+ } while (sName < tName);
+ if (tName < sName)
+ return false;
+ t1 = sOptionalNamed[i - 1];
+ if (!H._isSubtype(universe, tOptionalNamed[j + 1], tEnv, t1, sEnv, true))
+ return false;
+ }
+ return true;
+ },
+ _isInterfaceSubtype: function(universe, s, sEnv, t, tEnv, isLegacy) {
+ var sName, tName, sArgs, tArgs, $length, i, t1, t2, rule, supertypeArgs;
+ H.assertHelper(s._kind === 9);
+ sName = s._primary;
+ H.assertHelper(t._kind === 9);
+ tName = t._primary;
+ if (sName === tName) {
+ sArgs = H.Rti__getInterfaceTypeArguments(s);
+ tArgs = H.Rti__getInterfaceTypeArguments(t);
+ $length = sArgs.length;
+ H.assertHelper($length === tArgs.length);
+ for (i = 0; i < $length; ++i) {
+ t1 = sArgs[i];
+ t2 = tArgs[i];
+ if (!H._isSubtype(universe, t1, sEnv, t2, tEnv, true))
+ return false;
+ }
+ return true;
+ }
+ rule = H._Universe_findRule(universe, sName);
+ if (rule == null)
+ return false;
+ supertypeArgs = rule[tName];
+ if (supertypeArgs == null)
+ return false;
+ $length = supertypeArgs.length;
+ tArgs = H.Rti__getInterfaceTypeArguments(t);
+ H.assertHelper($length === tArgs.length);
+ for (i = 0; i < $length; ++i)
+ if (!H._isSubtype(universe, H._Universe_evalInEnvironment(universe, s, supertypeArgs[i]), sEnv, tArgs[i], tEnv, true))
+ return false;
+ return true;
+ },
+ typeEqual: function(s, t, isLegacy) {
+ var t1, sKind, t2;
+ if (s === t)
+ return true;
+ if (H.isTopType(s, true))
+ return H.isTopType(t, true);
+ t1 = s._kind;
+ sKind = t1;
+ if (sKind !== t._kind)
+ return false;
+ switch (sKind) {
+ case 6:
+ case 7:
+ case 8:
+ return H.typeEqual(s._primary, t._primary, true);
+ case 9:
+ H.assertHelper(t1 === 9);
+ t1 = s._primary;
+ H.assertHelper(t._kind === 9);
+ if (t1 !== t._primary)
+ return false;
+ return H.typesEqual(H.Rti__getInterfaceTypeArguments(s), H.Rti__getInterfaceTypeArguments(t), true);
+ case 10:
+ H.assertHelper(t1 === 10);
+ t1 = s._primary;
+ H.assertHelper(t._kind === 10);
+ return H.typeEqual(t1, t._primary, true) && H.typesEqual(H.Rti__getBindingArguments(s), H.Rti__getBindingArguments(t), true);
+ case 11:
+ H.assertHelper(t1 === 11);
+ t1 = s._primary;
+ H.assertHelper(t._kind === 11);
+ if (H.typeEqual(t1, t._primary, true)) {
+ t1 = H.Rti__getFunctionParameters(s);
+ t2 = H.Rti__getFunctionParameters(t);
+ t1 = H.typesEqual(t1._requiredPositional, t2._requiredPositional, true) && H.typesEqual(t1._optionalPositional, t2._optionalPositional, true) && H.namedTypesEqual(t1._optionalNamed, t2._optionalNamed, true);
+ } else
+ t1 = false;
+ return t1;
+ case 12:
+ H.assertHelper(t1 === 12);
+ t1 = s._primary;
+ H.assertHelper(t._kind === 12);
+ return H.typeEqual(t1, t._primary, true) && H.typesEqual(H.Rti__getGenericFunctionBounds(s), H.Rti__getGenericFunctionBounds(t), true);
+ default:
+ return false;
+ }
+ },
+ typesEqual: function(sArray, tArray, isLegacy) {
+ var i,
+ sLength = sArray.length;
+ if (sLength !== tArray.length)
+ return false;
+ for (i = 0; i < sLength; ++i)
+ if (!H.typeEqual(sArray[i], tArray[i], true))
+ return false;
+ return true;
+ },
+ namedTypesEqual: function(sArray, tArray, isLegacy) {
+ var i, t1,
+ sLength = sArray.length,
+ tLength = tArray.length;
+ H.assertHelper((sLength & 1) === 0);
+ H.assertHelper((tLength & 1) === 0);
+ if (sLength !== tLength)
+ return false;
+ for (i = 0; i < sLength; i += 2) {
+ if (sArray[i] !== tArray[i])
+ return false;
+ t1 = i + 1;
+ if (!H.typeEqual(sArray[t1], tArray[t1], true))
+ return false;
+ }
+ return true;
+ },
+ isLegacyTopType: function(t) {
+ return H.isTopType(t, true);
+ },
+ isTopType: function(t, isLegacy) {
+ var t1, kind;
+ if (t === type$.Object)
+ return true;
+ t1 = t._kind;
+ kind = t1;
+ if (kind !== 2)
+ if (kind !== 3)
+ if (kind !== 4)
+ if (kind !== 5)
+ if (kind === 8) {
+ H.assertHelper(t1 === 8);
+ t1 = H.isTopType(t._primary, true);
+ } else
+ t1 = false;
+ else
+ t1 = true;
+ else
+ t1 = true;
+ else
+ t1 = true;
+ else
+ t1 = true;
+ return t1;
+ },
+ _Utils_objectAssign: function(o, other) {
+ var i, key,
+ keys = Object.keys(other),
+ $length = keys.length;
+ for (i = 0; i < $length; ++i) {
+ key = keys[i];
+ o[key] = other[key];
+ }
+ },
+ Rti: function Rti(t0, t1, t2) {
+ var _ = this;
+ _._as = t0;
+ _._check = t1;
+ _._is = t2;
+ _._cachedRuntimeType = _._specializedTestResource = _._precomputed1 = null;
+ _._kind = 0;
+ _._canonicalRecipe = _._bindCache = _._evalCache = _._rest = _._primary = null;
+ },
+ _FunctionParameters: function _FunctionParameters() {
+ this._optionalNamed = this._optionalPositional = this._requiredPositional = null;
+ },
+ _Type: function _Type(t0) {
+ this._rti = t0;
+ this._hashCode = null;
+ },
+ _Error: function _Error() {
+ },
+ _CastError: function _CastError(t0) {
+ this._message = t0;
+ },
+ _TypeError: function _TypeError(t0) {
+ this._message = t0;
+ },
+ extractKeys: function(victim) {
+ return J.JSArray_JSArray$markFixed(victim ? Object.keys(victim) : [], type$.dynamic);
+ },
+ unmangleGlobalNameIfPreservedAnyways: function($name) {
+ return init.mangledGlobalNames[$name];
+ },
+ printString: function(string) {
+ if (typeof dartPrint == "function") {
+ dartPrint(string);
+ return;
+ }
+ if (typeof console == "object" && typeof console.log != "undefined") {
+ console.log(string);
+ return;
+ }
+ if (typeof window == "object")
+ return;
+ if (typeof print == "function") {
+ print(string);
+ return;
+ }
+ throw "Unable to print message: " + String(string);
+ }
+ },
+ J = {
+ makeDispatchRecord: function(interceptor, proto, extension, indexability) {
+ return {i: interceptor, p: proto, e: extension, x: indexability};
+ },
+ getNativeInterceptor: function(object) {
+ var proto, objectProto, $constructor, interceptor,
+ record = object[init.dispatchPropertyName];
+ if (record == null)
+ if ($.initNativeDispatchFlag == null) {
+ H.initNativeDispatch();
+ record = object[init.dispatchPropertyName];
+ }
+ if (record != null) {
+ proto = record.p;
+ if (false === proto)
+ return record.i;
+ if (true === proto)
+ return object;
+ objectProto = Object.getPrototypeOf(object);
+ if (proto === objectProto)
+ return record.i;
+ if (record.e === objectProto)
+ throw H.wrapException(P.UnimplementedError$("Return interceptor for " + H.S(proto(object, record))));
+ }
+ $constructor = object.constructor;
+ interceptor = $constructor == null ? null : $constructor[$.$get$JS_INTEROP_INTERCEPTOR_TAG()];
+ if (interceptor != null)
+ return interceptor;
+ interceptor = H.lookupAndCacheInterceptor(object);
+ if (interceptor != null)
+ return interceptor;
+ if (typeof object == "function")
+ return C.JavaScriptFunction_methods;
+ proto = Object.getPrototypeOf(object);
+ if (proto == null)
+ return C.PlainJavaScriptObject_methods;
+ if (proto === Object.prototype)
+ return C.PlainJavaScriptObject_methods;
+ if (typeof $constructor == "function") {
+ Object.defineProperty($constructor, $.$get$JS_INTEROP_INTERCEPTOR_TAG(), {value: C.UnknownJavaScriptObject_methods, enumerable: false, writable: true, configurable: true});
+ return C.UnknownJavaScriptObject_methods;
+ }
+ return C.UnknownJavaScriptObject_methods;
+ },
+ JSArray_JSArray$fixed: function($length, $E) {
+ if (!H._isInt($length))
+ throw H.wrapException(P.ArgumentError$value($length, "length", "is not an integer"));
+ if ($length < 0 || $length > 4294967295)
+ throw H.wrapException(P.RangeError$range($length, 0, 4294967295, "length", null));
+ return J.JSArray_JSArray$markFixed(new Array($length), $E);
+ },
+ JSArray_JSArray$markFixed: function(allocation, $E) {
+ return J.JSArray_markFixedList(H.setRuntimeTypeInfo(allocation, $E._eval$1("JSArray<0>")));
+ },
+ JSArray_markFixedList: function(list) {
+ list.fixed$length = Array;
+ return list;
+ },
+ JSString__isWhitespace: function(codeUnit) {
+ if (codeUnit < 256)
+ switch (codeUnit) {
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 32:
+ case 133:
+ case 160:
+ return true;
+ default:
+ return false;
+ }
+ switch (codeUnit) {
+ case 5760:
+ case 8192:
+ case 8193:
+ case 8194:
+ case 8195:
+ case 8196:
+ case 8197:
+ case 8198:
+ case 8199:
+ case 8200:
+ case 8201:
+ case 8202:
+ case 8232:
+ case 8233:
+ case 8239:
+ case 8287:
+ case 12288:
+ case 65279:
+ return true;
+ default:
+ return false;
+ }
+ },
+ JSString__skipLeadingWhitespace: function(string, index) {
+ var t1, codeUnit;
+ for (t1 = string.length; index < t1;) {
+ codeUnit = C.JSString_methods._codeUnitAt$1(string, index);
+ if (codeUnit !== 32 && codeUnit !== 13 && !J.JSString__isWhitespace(codeUnit))
+ break;
+ ++index;
+ }
+ return index;
+ },
+ JSString__skipTrailingWhitespace: function(string, index) {
+ var index0, codeUnit;
+ for (; index > 0; index = index0) {
+ index0 = index - 1;
+ codeUnit = C.JSString_methods.codeUnitAt$1(string, index0);
+ if (codeUnit !== 32 && codeUnit !== 13 && !J.JSString__isWhitespace(codeUnit))
+ break;
+ }
+ return index;
+ },
+ getInterceptor$: function(receiver) {
+ if (typeof receiver == "number") {
+ if (Math.floor(receiver) == receiver)
+ return J.JSInt.prototype;
+ return J.JSDouble.prototype;
+ }
+ if (typeof receiver == "string")
+ return J.JSString.prototype;
+ if (receiver == null)
+ return J.JSNull.prototype;
+ if (typeof receiver == "boolean")
+ return J.JSBool.prototype;
+ if (receiver.constructor == Array)
+ return J.JSArray.prototype;
+ if (typeof receiver != "object") {
+ if (typeof receiver == "function")
+ return J.JavaScriptFunction.prototype;
+ return receiver;
+ }
+ if (receiver instanceof P.Object)
+ return receiver;
+ return J.getNativeInterceptor(receiver);
+ },
+ getInterceptor$ansx: function(receiver) {
+ if (typeof receiver == "number")
+ return J.JSNumber.prototype;
+ if (typeof receiver == "string")
+ return J.JSString.prototype;
+ if (receiver == null)
+ return receiver;
+ if (receiver.constructor == Array)
+ return J.JSArray.prototype;
+ if (typeof receiver != "object") {
+ if (typeof receiver == "function")
+ return J.JavaScriptFunction.prototype;
+ return receiver;
+ }
+ if (receiver instanceof P.Object)
+ return receiver;
+ return J.getNativeInterceptor(receiver);
+ },
+ getInterceptor$asx: function(receiver) {
+ if (typeof receiver == "string")
+ return J.JSString.prototype;
+ if (receiver == null)
+ return receiver;
+ if (receiver.constructor == Array)
+ return J.JSArray.prototype;
+ if (typeof receiver != "object") {
+ if (typeof receiver == "function")
+ return J.JavaScriptFunction.prototype;
+ return receiver;
+ }
+ if (receiver instanceof P.Object)
+ return receiver;
+ return J.getNativeInterceptor(receiver);
+ },
+ getInterceptor$ax: function(receiver) {
+ if (receiver == null)
+ return receiver;
+ if (receiver.constructor == Array)
+ return J.JSArray.prototype;
+ if (typeof receiver != "object") {
+ if (typeof receiver == "function")
+ return J.JavaScriptFunction.prototype;
+ return receiver;
+ }
+ if (receiver instanceof P.Object)
+ return receiver;
+ return J.getNativeInterceptor(receiver);
+ },
+ getInterceptor$n: function(receiver) {
+ if (typeof receiver == "number")
+ return J.JSNumber.prototype;
+ if (receiver == null)
+ return receiver;
+ if (!(receiver instanceof P.Object))
+ return J.UnknownJavaScriptObject.prototype;
+ return receiver;
+ },
+ getInterceptor$s: function(receiver) {
+ if (typeof receiver == "string")
+ return J.JSString.prototype;
+ if (receiver == null)
+ return receiver;
+ if (!(receiver instanceof P.Object))
+ return J.UnknownJavaScriptObject.prototype;
+ return receiver;
+ },
+ getInterceptor$x: function(receiver) {
+ if (receiver == null)
+ return receiver;
+ if (typeof receiver != "object") {
+ if (typeof receiver == "function")
+ return J.JavaScriptFunction.prototype;
+ return receiver;
+ }
+ if (receiver instanceof P.Object)
+ return receiver;
+ return J.getNativeInterceptor(receiver);
+ },
+ get$first$ax: function(receiver) {
+ return J.getInterceptor$ax(receiver).get$first(receiver);
+ },
+ get$hashCode$: function(receiver) {
+ return J.getInterceptor$(receiver).get$hashCode(receiver);
+ },
+ get$isEmpty$asx: function(receiver) {
+ return J.getInterceptor$asx(receiver).get$isEmpty(receiver);
+ },
+ get$iterator$ax: function(receiver) {
+ return J.getInterceptor$ax(receiver).get$iterator(receiver);
+ },
+ get$last$ax: function(receiver) {
+ return J.getInterceptor$ax(receiver).get$last(receiver);
+ },
+ get$length$asx: function(receiver) {
+ return J.getInterceptor$asx(receiver).get$length(receiver);
+ },
+ get$message$x: function(receiver) {
+ return J.getInterceptor$x(receiver).get$message(receiver);
+ },
+ get$runtimeType$: function(receiver) {
+ return J.getInterceptor$(receiver).get$runtimeType(receiver);
+ },
+ get$startsWith$s: function(receiver) {
+ return J.getInterceptor$s(receiver).get$startsWith(receiver);
+ },
+ $add$ansx: function(receiver, a0) {
+ if (typeof receiver == "number" && typeof a0 == "number")
+ return receiver + a0;
+ return J.getInterceptor$ansx(receiver).$add(receiver, a0);
+ },
+ $eq$: function(receiver, a0) {
+ if (receiver == null)
+ return a0 == null;
+ if (typeof receiver != "object")
+ return a0 != null && receiver === a0;
+ return J.getInterceptor$(receiver).$eq(receiver, a0);
+ },
+ $index$asx: function(receiver, a0) {
+ if (typeof a0 === "number")
+ if (receiver.constructor == Array || typeof receiver == "string" || H.isJsIndexable(receiver, receiver[init.dispatchPropertyName]))
+ if (a0 >>> 0 === a0 && a0 < receiver.length)
+ return receiver[a0];
+ return J.getInterceptor$asx(receiver).$index(receiver, a0);
+ },
+ $indexSet$ax: function(receiver, a0, a1) {
+ return J.getInterceptor$ax(receiver).$indexSet(receiver, a0, a1);
+ },
+ _codeUnitAt$1$s: function(receiver, a0) {
+ return J.getInterceptor$s(receiver)._codeUnitAt$1(receiver, a0);
+ },
+ _removeEventListener$3$x: function(receiver, a0, a1, a2) {
+ return J.getInterceptor$x(receiver)._removeEventListener$3(receiver, a0, a1, a2);
+ },
+ addEventListener$3$x: function(receiver, a0, a1, a2) {
+ return J.getInterceptor$x(receiver).addEventListener$3(receiver, a0, a1, a2);
+ },
+ allMatches$1$s: function(receiver, a0) {
+ return J.getInterceptor$s(receiver).allMatches$1(receiver, a0);
+ },
+ allMatches$2$s: function(receiver, a0, a1) {
+ return J.getInterceptor$s(receiver).allMatches$2(receiver, a0, a1);
+ },
+ codeUnitAt$1$s: function(receiver, a0) {
+ return J.getInterceptor$s(receiver).codeUnitAt$1(receiver, a0);
+ },
+ contains$1$asx: function(receiver, a0) {
+ return J.getInterceptor$asx(receiver).contains$1(receiver, a0);
+ },
+ elementAt$1$ax: function(receiver, a0) {
+ return J.getInterceptor$ax(receiver).elementAt$1(receiver, a0);
+ },
+ endsWith$1$s: function(receiver, a0) {
+ return J.getInterceptor$s(receiver).endsWith$1(receiver, a0);
+ },
+ fillRange$3$ax: function(receiver, a0, a1, a2) {
+ return J.getInterceptor$ax(receiver).fillRange$3(receiver, a0, a1, a2);
+ },
+ map$1$1$ax: function(receiver, a0, $T1) {
+ return J.getInterceptor$ax(receiver).map$1$1(receiver, a0, $T1);
+ },
+ matchAsPrefix$2$s: function(receiver, a0, a1) {
+ return J.getInterceptor$s(receiver).matchAsPrefix$2(receiver, a0, a1);
+ },
+ padRight$1$s: function(receiver, a0) {
+ return J.getInterceptor$s(receiver).padRight$1(receiver, a0);
+ },
+ replaceRange$3$asx: function(receiver, a0, a1, a2) {
+ return J.getInterceptor$asx(receiver).replaceRange$3(receiver, a0, a1, a2);
+ },
+ startsWith$1$s: function(receiver, a0) {
+ return J.getInterceptor$s(receiver).startsWith$1(receiver, a0);
+ },
+ startsWith$2$s: function(receiver, a0, a1) {
+ return J.getInterceptor$s(receiver).startsWith$2(receiver, a0, a1);
+ },
+ substring$1$s: function(receiver, a0) {
+ return J.getInterceptor$s(receiver).substring$1(receiver, a0);
+ },
+ substring$2$s: function(receiver, a0, a1) {
+ return J.getInterceptor$s(receiver).substring$2(receiver, a0, a1);
+ },
+ toRadixString$1$n: function(receiver, a0) {
+ return J.getInterceptor$n(receiver).toRadixString$1(receiver, a0);
+ },
+ toSet$0$ax: function(receiver) {
+ return J.getInterceptor$ax(receiver).toSet$0(receiver);
+ },
+ toString$0$: function(receiver) {
+ return J.getInterceptor$(receiver).toString$0(receiver);
+ },
+ trim$0$s: function(receiver) {
+ return J.getInterceptor$s(receiver).trim$0(receiver);
+ },
+ Interceptor: function Interceptor() {
+ },
+ JSBool: function JSBool() {
+ },
+ JSNull: function JSNull() {
+ },
+ JSObject: function JSObject() {
+ },
+ JavaScriptObject: function JavaScriptObject() {
+ },
+ PlainJavaScriptObject: function PlainJavaScriptObject() {
+ },
+ UnknownJavaScriptObject: function UnknownJavaScriptObject() {
+ },
+ JavaScriptFunction: function JavaScriptFunction() {
+ },
+ JSArray: function JSArray(t0) {
+ this.$ti = t0;
+ },
+ JSUnmodifiableArray: function JSUnmodifiableArray(t0) {
+ this.$ti = t0;
+ },
+ ArrayIterator: function ArrayIterator(t0, t1, t2) {
+ var _ = this;
+ _._iterable = t0;
+ _._length = t1;
+ _._index = 0;
+ _._current = null;
+ _.$ti = t2;
+ },
+ JSNumber: function JSNumber() {
+ },
+ JSInt: function JSInt() {
+ },
+ JSDouble: function JSDouble() {
+ },
+ JSString: function JSString() {
+ }
+ },
+ P = {
+ _AsyncRun__initializeScheduleImmediate: function() {
+ var div, span, t1 = {};
+ if (self.scheduleImmediate != null)
+ return P.async__AsyncRun__scheduleImmediateJsOverride$closure();
+ if (self.MutationObserver != null && self.document != null) {
+ div = self.document.createElement("div");
+ span = self.document.createElement("span");
+ t1.storedCallback = null;
+ new self.MutationObserver(H.convertDartClosureToJS(new P._AsyncRun__initializeScheduleImmediate_internalCallback(t1), 1)).observe(div, {childList: true});
+ return new P._AsyncRun__initializeScheduleImmediate_closure(t1, div, span);
+ } else if (self.setImmediate != null)
+ return P.async__AsyncRun__scheduleImmediateWithSetImmediate$closure();
+ return P.async__AsyncRun__scheduleImmediateWithTimer$closure();
+ },
+ _AsyncRun__scheduleImmediateJsOverride: function(callback) {
+ self.scheduleImmediate(H.convertDartClosureToJS(new P._AsyncRun__scheduleImmediateJsOverride_internalCallback(type$.void_Function._check(callback)), 0));
+ },
+ _AsyncRun__scheduleImmediateWithSetImmediate: function(callback) {
+ self.setImmediate(H.convertDartClosureToJS(new P._AsyncRun__scheduleImmediateWithSetImmediate_internalCallback(type$.void_Function._check(callback)), 0));
+ },
+ _AsyncRun__scheduleImmediateWithTimer: function(callback) {
+ P.Timer__createTimer(C.Duration_0, type$.void_Function._check(callback));
+ },
+ Timer__createTimer: function(duration, callback) {
+ var milliseconds = C.JSInt_methods._tdivFast$1(duration._duration, 1000);
+ return P._TimerImpl$(milliseconds < 0 ? 0 : milliseconds, callback);
+ },
+ _TimerImpl$: function(milliseconds, callback) {
+ var t1 = new P._TimerImpl(true);
+ t1._TimerImpl$2(milliseconds, callback);
+ return t1;
+ },
+ _TimerImpl$periodic: function(milliseconds, callback) {
+ var t1 = new P._TimerImpl(false);
+ t1._TimerImpl$periodic$2(milliseconds, callback);
+ return t1;
+ },
+ _makeAsyncAwaitCompleter: function($T) {
+ return new P._AsyncAwaitCompleter(new P._Future($.Zone__current, $T._eval$1("_Future<0>")), $T._eval$1("_AsyncAwaitCompleter<0>"));
+ },
+ _asyncStartSync: function(bodyFunction, completer) {
+ bodyFunction.call$2(0, null);
+ completer.isSync = true;
+ return completer._future;
+ },
+ _asyncAwait: function(object, bodyFunction) {
+ P._awaitOnObject(object, bodyFunction);
+ },
+ _asyncReturn: function(object, completer) {
+ completer.complete$1(object);
+ },
+ _asyncRethrow: function(object, completer) {
+ completer.completeError$2(H.unwrapException(object), H.getTraceFromException(object));
+ },
+ _awaitOnObject: function(object, bodyFunction) {
+ var t1, future,
+ thenCallback = new P._awaitOnObject_closure(bodyFunction),
+ errorCallback = new P._awaitOnObject_closure0(bodyFunction);
+ if (object instanceof P._Future)
+ object._thenAwait$1$2(thenCallback, errorCallback, type$.dynamic);
+ else {
+ t1 = type$.dynamic;
+ if (type$.Future_dynamic._is(object))
+ object.then$1$2$onError(thenCallback, errorCallback, t1);
+ else {
+ future = new P._Future($.Zone__current, type$._Future_dynamic);
+ future._state = 4;
+ future._resultOrListeners = object;
+ future._thenAwait$1$2(thenCallback, null, t1);
+ }
+ }
+ },
+ _wrapJsFunctionForAsync: function($function) {
+ var $protected = function(fn, ERROR) {
+ return function(errorCode, result) {
+ while (true)
+ try {
+ fn(errorCode, result);
+ break;
+ } catch (error) {
+ result = error;
+ errorCode = ERROR;
+ }
+ };
+ }($function, 1);
+ return $.Zone__current.registerBinaryCallback$3$1(new P._wrapJsFunctionForAsync_closure($protected), type$.Null, type$.int, type$.dynamic);
+ },
+ Future_Future: function(computation, $T) {
+ var result = new P._Future($.Zone__current, $T._eval$1("_Future<0>"));
+ P.Timer_Timer(C.Duration_0, new P.Future_Future_closure(result, computation));
+ return result;
+ },
+ Future_Future$microtask: function(computation, $T) {
+ var result = new P._Future($.Zone__current, $T._eval$1("_Future<0>"));
+ P.scheduleMicrotask(new P.Future_Future$microtask_closure(result, computation));
+ return result;
+ },
+ Future_Future$sync: function(computation, $T) {
+ var result, error, stackTrace, future, replacement, t1, t2, exception;
+ try {
+ result = computation.call$0();
+ if ($T._eval$1("Future<0>")._is(result))
+ return result;
+ else {
+ t1 = $T._check(result);
+ t2 = new P._Future($.Zone__current, $T._eval$1("_Future<0>"));
+ t2._state = 4;
+ t2._resultOrListeners = t1;
+ return t2;
+ }
+ } catch (exception) {
+ error = H.unwrapException(exception);
+ stackTrace = H.getTraceFromException(exception);
+ t1 = $.Zone__current;
+ future = new P._Future(t1, $T._eval$1("_Future<0>"));
+ replacement = t1.errorCallback$2(error, stackTrace);
+ if (replacement != null) {
+ t1 = replacement.error;
+ if (t1 == null)
+ t1 = new P.NullThrownError();
+ future._asyncCompleteError$2(t1, replacement.stackTrace);
+ } else
+ future._asyncCompleteError$2(error, stackTrace);
+ return future;
+ }
+ },
+ Future_Future$error: function(error, stackTrace, $T) {
+ var replacement,
+ t1 = $.Zone__current;
+ if (t1 !== C.C__RootZone) {
+ replacement = t1.errorCallback$2(error, stackTrace);
+ if (replacement != null) {
+ error = replacement.error;
+ if (error == null)
+ error = new P.NullThrownError();
+ stackTrace = replacement.stackTrace;
+ }
+ }
+ t1 = new P._Future($.Zone__current, $T._eval$1("_Future<0>"));
+ t1._asyncCompleteError$2(error, stackTrace);
+ return t1;
+ },
+ Future_wait: function(futures, eagerError, $T) {
+ var handleError, future, pos, e, st, t2, t3, _i, t4, exception, _box_0 = {}, cleanUp = null,
+ t1 = $T._eval$1("_Future<List<0>>"),
+ result = new P._Future($.Zone__current, t1);
+ _box_0.values = null;
+ _box_0.remaining = 0;
+ _box_0.stackTrace = _box_0.error = null;
+ handleError = new P.Future_wait_handleError(_box_0, cleanUp, true, result);
+ try {
+ for (t2 = futures.length, t3 = type$.Null, _i = 0, t4 = 0; _i < futures.length; futures.length === t2 || (0, H.throwConcurrentModificationError)(futures), ++_i) {
+ future = futures[_i];
+ pos = t4;
+ future.then$1$2$onError(new P.Future_wait_closure(_box_0, pos, result, cleanUp, true, $T), handleError, t3);
+ t4 = ++_box_0.remaining;
+ }
+ if (t4 === 0) {
+ t1 = new P._Future($.Zone__current, t1);
+ t1._asyncComplete$1(C.List_empty1);
+ return t1;
+ }
+ t1 = new Array(t4);
+ t1.fixed$length = Array;
+ _box_0.values = H.setRuntimeTypeInfo(t1, $T._eval$1("JSArray<0>"));
+ } catch (exception) {
+ e = H.unwrapException(exception);
+ st = H.getTraceFromException(exception);
+ t1 = P.Future_Future$error(e, st, $T._eval$1("List<0>"));
+ return t1;
+ }
+ return result;
+ },
+ Future_forEach: function(elements, action, $T) {
+ return P.Future_doWhile(new P.Future_forEach_closure(new J.ArrayIterator(elements, 0, H._arrayInstanceType(elements)._eval$1("ArrayIterator<1>")), action));
+ },
+ Future__kTrue: function(_) {
+ return true;
+ },
+ Future_doWhile: function(action) {
+ var nextIteration, t1 = {},
+ t2 = $.Zone__current,
+ doneSignal = new P._Future(t2, type$._Future_dynamic);
+ t1.nextIteration = null;
+ nextIteration = t2.bindUnaryCallbackGuarded$1$1(new P.Future_doWhile_closure(t1, action, doneSignal), type$.bool);
+ t1.nextIteration = nextIteration;
+ nextIteration.call$1(true);
+ return doneSignal;
+ },
+ _completeWithErrorCallback: function(result, error, stackTrace) {
+ var replacement = $.Zone__current.errorCallback$2(error, stackTrace);
+ if (replacement != null) {
+ error = replacement.error;
+ if (error == null)
+ error = new P.NullThrownError();
+ stackTrace = replacement.stackTrace;
+ }
+ result._completeError$2(error, stackTrace);
+ },
+ _Future$zoneValue: function(value, _zone, $T) {
+ var t1 = new P._Future(_zone, $T._eval$1("_Future<0>"));
+ $T._check(value);
+ t1._state = 4;
+ t1._resultOrListeners = value;
+ return t1;
+ },
+ _Future__chainForeignFuture: function(source, target) {
+ var e, s, exception;
+ H.assertHelper(target._state < 4);
+ H.assertHelper(!(source instanceof P._Future));
+ H.assertHelper(target._state === 0);
+ target._state = 1;
+ try {
+ source.then$1$2$onError(new P._Future__chainForeignFuture_closure(target), new P._Future__chainForeignFuture_closure0(target), type$.Null);
+ } catch (exception) {
+ e = H.unwrapException(exception);
+ s = H.getTraceFromException(exception);
+ P.scheduleMicrotask(new P._Future__chainForeignFuture_closure1(target, e, s));
+ }
+ },
+ _Future__chainCoreFuture: function(source, target) {
+ var t1, t2, t3, listeners;
+ H.assertHelper(target._state <= 1);
+ for (t1 = type$._Future_dynamic; t2 = source._state, t3 = t2 === 2, t3;) {
+ H.assertHelper(t3);
+ source = t1._check(source._resultOrListeners);
+ }
+ if (t2 >= 4) {
+ listeners = target._removeListeners$0();
+ target._cloneResult$1(source);
+ P._Future__propagateToListeners(target, listeners);
+ } else {
+ listeners = type$._FutureListener_dynamic_dynamic._check(target._resultOrListeners);
+ H.assertHelper(target._state <= 1);
+ target._state = 2;
+ target._resultOrListeners = source;
+ source._prependListeners$1(listeners);
+ }
+ },
+ _Future__propagateToListeners: function(source, listeners) {
+ var t2, t3, t4, _box_0, hasError, asyncError, listeners0, sourceResult, t5, t6, zone, previous, oldZone, current, result, _box_1 = {},
+ t1 = _box_1.source = source;
+ for (t2 = type$.AsyncError, t3 = type$._FutureListener_dynamic_dynamic, t4 = type$.Future_dynamic; true;) {
+ _box_0 = {};
+ H.assertHelper(t1._state >= 4);
+ t1 = _box_1.source;
+ hasError = t1._state === 8;
+ if (listeners == null) {
+ if (hasError) {
+ asyncError = t2._check(t1._resultOrListeners);
+ t1._zone.handleUncaughtError$2(asyncError.error, asyncError.stackTrace);
+ }
+ return;
+ }
+ for (; listeners0 = listeners._nextListener, listeners0 != null; listeners = listeners0) {
+ listeners._nextListener = null;
+ P._Future__propagateToListeners(_box_1.source, listeners);
+ }
+ t1 = _box_1.source;
+ sourceResult = t1._resultOrListeners;
+ _box_0.listenerHasError = hasError;
+ _box_0.listenerValueOrError = sourceResult;
+ t5 = !hasError;
+ if (t5) {
+ t6 = listeners.state;
+ t6 = (t6 & 1) !== 0 || (t6 & 15) === 8;
+ } else
+ t6 = true;
+ if (t6) {
+ t6 = listeners.result;
+ zone = t6._zone;
+ if (hasError) {
+ t1 = t1._zone;
+ t1 = !(t1 === zone || t1.get$errorZone() === zone.get$errorZone());
+ } else
+ t1 = false;
+ if (t1) {
+ t1 = _box_1.source;
+ H.assertHelper(t1._state === 8);
+ asyncError = t2._check(t1._resultOrListeners);
+ _box_1.source._zone.handleUncaughtError$2(asyncError.error, asyncError.stackTrace);
+ return;
+ }
+ t1 = $.Zone__current;
+ if (t1 !== zone) {
+ H.assertHelper(zone !== t1);
+ previous = $.Zone__current;
+ $.Zone__current = zone;
+ oldZone = previous;
+ } else
+ oldZone = null;
+ t1 = listeners.state;
+ if ((t1 & 15) === 8)
+ new P._Future__propagateToListeners_handleWhenCompleteCallback(_box_1, _box_0, listeners, hasError).call$0();
+ else if (t5) {
+ if ((t1 & 1) !== 0)
+ new P._Future__propagateToListeners_handleValueCallback(_box_0, listeners, sourceResult).call$0();
+ } else if ((t1 & 2) !== 0)
+ new P._Future__propagateToListeners_handleError(_box_1, _box_0, listeners).call$0();
+ if (oldZone != null)
+ $.Zone__current = oldZone;
+ t1 = _box_0.listenerValueOrError;
+ if (t4._is(t1)) {
+ if (t1._state >= 4) {
+ H.assertHelper(t6._state < 4);
+ current = t3._check(t6._resultOrListeners);
+ t6._resultOrListeners = null;
+ listeners = t6._reverseListeners$1(current);
+ H.assertHelper(t6._state < 4);
+ H.assertHelper(t1._state >= 4);
+ t6._state = t1._state;
+ t6._resultOrListeners = t1._resultOrListeners;
+ _box_1.source = t1;
+ continue;
+ } else
+ P._Future__chainCoreFuture(t1, t6);
+ return;
+ }
+ }
+ result = listeners.result;
+ H.assertHelper(result._state < 4);
+ current = t3._check(result._resultOrListeners);
+ result._resultOrListeners = null;
+ listeners = result._reverseListeners$1(current);
+ t1 = _box_0.listenerHasError;
+ t5 = _box_0.listenerValueOrError;
+ t6 = result._state >= 4;
+ if (!t1) {
+ result.$ti._precomputed1._check(t5);
+ H.assertHelper(!t6);
+ result._state = 4;
+ result._resultOrListeners = t5;
+ } else {
+ t2._check(t5);
+ H.assertHelper(!t6);
+ result._state = 8;
+ result._resultOrListeners = t5;
+ }
+ _box_1.source = result;
+ t1 = result;
+ }
+ },
+ _registerErrorHandler: function(errorHandler, zone) {
+ if (type$.dynamic_Function_Object_StackTrace._is(errorHandler))
+ return zone.registerBinaryCallback$3$1(errorHandler, type$.dynamic, type$.Object, type$.StackTrace);
+ if (type$.dynamic_Function_Object._is(errorHandler))
+ return zone.registerUnaryCallback$2$1(errorHandler, type$.dynamic, type$.Object);
+ throw H.wrapException(P.ArgumentError$value(errorHandler, "onError", "Error handler must accept one Object or one Object and a StackTrace as arguments, and return a a valid result"));
+ },
+ _microtaskLoop: function() {
+ var t1, t2;
+ for (; t1 = $._nextCallback, t1 != null;) {
+ $._lastPriorityCallback = null;
+ t2 = t1.next;
+ $._nextCallback = t2;
+ if (t2 == null)
+ $._lastCallback = null;
+ t1.callback.call$0();
+ }
+ },
+ _startMicrotaskLoop: function() {
+ $._isInCallbackLoop = true;
+ try {
+ P._microtaskLoop();
+ } finally {
+ $._lastPriorityCallback = null;
+ $._isInCallbackLoop = false;
+ if ($._nextCallback != null)
+ $.$get$_AsyncRun__scheduleImmediateClosure().call$1(P.async___startMicrotaskLoop$closure());
+ }
+ },
+ _scheduleAsyncCallback: function(callback) {
+ var newEntry = new P._AsyncCallbackEntry(callback);
+ if ($._nextCallback == null) {
+ $._nextCallback = $._lastCallback = newEntry;
+ if (!$._isInCallbackLoop)
+ $.$get$_AsyncRun__scheduleImmediateClosure().call$1(P.async___startMicrotaskLoop$closure());
+ } else
+ $._lastCallback = $._lastCallback.next = newEntry;
+ },
+ _schedulePriorityAsyncCallback: function(callback) {
+ var entry, t2,
+ t1 = $._nextCallback;
+ if (t1 == null) {
+ P._scheduleAsyncCallback(callback);
+ $._lastPriorityCallback = $._lastCallback;
+ return;
+ }
+ entry = new P._AsyncCallbackEntry(callback);
+ t2 = $._lastPriorityCallback;
+ if (t2 == null) {
+ entry.next = t1;
+ $._nextCallback = $._lastPriorityCallback = entry;
+ } else {
+ entry.next = t2.next;
+ $._lastPriorityCallback = t2.next = entry;
+ if (entry.next == null)
+ $._lastCallback = entry;
+ }
+ },
+ scheduleMicrotask: function(callback) {
+ var t1, _null = null,
+ currentZone = $.Zone__current;
+ if (C.C__RootZone === currentZone) {
+ P._rootScheduleMicrotask(_null, _null, C.C__RootZone, callback);
+ return;
+ }
+ if (C.C__RootZone === currentZone.get$_scheduleMicrotask().zone)
+ t1 = C.C__RootZone.get$errorZone() === currentZone.get$errorZone();
+ else
+ t1 = false;
+ if (t1) {
+ P._rootScheduleMicrotask(_null, _null, currentZone, currentZone.registerCallback$1$1(callback, type$.void));
+ return;
+ }
+ t1 = $.Zone__current;
+ t1.scheduleMicrotask$1(t1.bindCallbackGuarded$1(callback));
+ },
+ Stream_Stream$fromFuture: function(future, $T) {
+ var _null = null,
+ t1 = $T._eval$1("_SyncStreamController<0>"),
+ controller = new P._SyncStreamController(_null, _null, _null, _null, t1);
+ future.then$1$2$onError(new P.Stream_Stream$fromFuture_closure(controller, $T), new P.Stream_Stream$fromFuture_closure0(controller), type$.Null);
+ return new P._ControllerStream(controller, t1._eval$1("_ControllerStream<1>"));
+ },
+ Stream_Stream$fromIterable: function(elements, $T) {
+ return new P._GeneratedStreamImpl(new P.Stream_Stream$fromIterable_closure(elements, $T), $T._eval$1("_GeneratedStreamImpl<0>"));
+ },
+ StreamIterator_StreamIterator: function(stream, $T) {
+ if (stream == null)
+ H.throwExpression(P.ArgumentError$notNull("stream"));
+ return new P._StreamIterator($T._eval$1("_StreamIterator<0>"));
+ },
+ StreamController_StreamController: function(onCancel, onListen, sync, $T) {
+ var _null = null;
+ return sync ? new P._SyncStreamController(onListen, _null, _null, onCancel, $T._eval$1("_SyncStreamController<0>")) : new P._AsyncStreamController(onListen, _null, _null, onCancel, $T._eval$1("_AsyncStreamController<0>"));
+ },
+ _runGuarded: function(notificationHandler) {
+ var e, s, exception;
+ if (notificationHandler == null)
+ return;
+ try {
+ notificationHandler.call$0();
+ } catch (exception) {
+ e = H.unwrapException(exception);
+ s = H.getTraceFromException(exception);
+ $.Zone__current.handleUncaughtError$2(e, s);
+ }
+ },
+ _AddStreamState_makeErrorHandler: function(controller) {
+ return new P._AddStreamState_makeErrorHandler_closure(controller);
+ },
+ _BufferingStreamSubscription$: function(onData, onError, onDone, cancelOnError, $T) {
+ var t1 = $.Zone__current,
+ t2 = cancelOnError ? 1 : 0;
+ t2 = new P._BufferingStreamSubscription(t1, t2, $T._eval$1("_BufferingStreamSubscription<0>"));
+ t2._BufferingStreamSubscription$4(onData, onError, onDone, cancelOnError, $T);
+ return t2;
+ },
+ _nullDataHandler: function(value) {
+ },
+ _nullErrorHandler: function(error, stackTrace) {
+ type$.StackTrace._check(stackTrace);
+ $.Zone__current.handleUncaughtError$2(error, stackTrace);
+ },
+ _nullDoneHandler: function() {
+ },
+ _runUserCode: function(userCode, onSuccess, onError, $T) {
+ var e, s, replacement, error, stackTrace, exception, error0;
+ try {
+ onSuccess.call$1(userCode.call$0());
+ } catch (exception) {
+ e = H.unwrapException(exception);
+ s = H.getTraceFromException(exception);
+ replacement = $.Zone__current.errorCallback$2(e, s);
+ if (replacement == null)
+ onError.call$2(e, s);
+ else {
+ error0 = replacement.error;
+ error = error0 == null ? new P.NullThrownError() : error0;
+ stackTrace = replacement.stackTrace;
+ onError.call$2(error, stackTrace);
+ }
+ }
+ },
+ _cancelAndError: function(subscription, future, error, stackTrace) {
+ var cancelFuture = subscription.cancel$0();
+ if (cancelFuture != null && cancelFuture !== $.$get$Future__nullFuture())
+ cancelFuture.whenComplete$1(new P._cancelAndError_closure(future, error, stackTrace));
+ else
+ future._completeError$2(error, stackTrace);
+ },
+ _cancelAndErrorClosure: function(subscription, future) {
+ return new P._cancelAndErrorClosure_closure(subscription, future);
+ },
+ _cancelAndValue: function(subscription, future, value) {
+ var cancelFuture = subscription.cancel$0();
+ if (cancelFuture != null && cancelFuture !== $.$get$Future__nullFuture())
+ cancelFuture.whenComplete$1(new P._cancelAndValue_closure(future, value));
+ else
+ future._complete$1(value);
+ },
+ Timer_Timer: function(duration, callback) {
+ var t1 = $.Zone__current;
+ if (t1 === C.C__RootZone)
+ return t1.createTimer$2(duration, callback);
+ return t1.createTimer$2(duration, t1.bindCallbackGuarded$1(callback));
+ },
+ _ZoneSpecification$: function(createPeriodicTimer, createTimer, errorCallback, fork, handleUncaughtError, $print, registerBinaryCallback, registerCallback, registerUnaryCallback, run, runBinary, runUnary, scheduleMicrotask) {
+ return new P._ZoneSpecification(handleUncaughtError, run, runUnary, runBinary, registerCallback, registerUnaryCallback, registerBinaryCallback, errorCallback, scheduleMicrotask, createTimer, createPeriodicTimer, $print, fork);
+ },
+ Zone__enter: function(zone) {
+ var previous;
+ H.assertHelper(zone != null);
+ H.assertHelper(zone !== $.Zone__current);
+ previous = $.Zone__current;
+ $.Zone__current = zone;
+ return previous;
+ },
+ _parentDelegate: function(zone) {
+ if (zone.get$parent(zone) == null)
+ return null;
+ return zone.get$parent(zone).get$_delegate();
+ },
+ _rootHandleUncaughtError: function($self, $parent, zone, error, stackTrace) {
+ var t1 = {};
+ t1.error = error;
+ P._schedulePriorityAsyncCallback(new P._rootHandleUncaughtError_closure(t1, type$.StackTrace._check(stackTrace)));
+ },
+ _rootRun: function($self, $parent, zone, f, $R) {
+ var old,
+ t1 = type$.Zone;
+ t1._check($self);
+ type$.ZoneDelegate._check($parent);
+ t1._check(zone);
+ $R._eval$1("0()")._check(f);
+ if ($.Zone__current === zone)
+ return f.call$0();
+ old = P.Zone__enter(zone);
+ try {
+ t1 = f.call$0();
+ return t1;
+ } finally {
+ t1 = old;
+ H.assertHelper(t1 != null);
+ $.Zone__current = t1;
+ }
+ },
+ _rootRunUnary: function($self, $parent, zone, f, arg, $R, $T) {
+ var old,
+ t1 = type$.Zone;
+ t1._check($self);
+ type$.ZoneDelegate._check($parent);
+ t1._check(zone);
+ $R._eval$1("@<0>")._bind$1($T)._eval$1("1(2)")._check(f);
+ $T._check(arg);
+ if ($.Zone__current === zone)
+ return f.call$1(arg);
+ old = P.Zone__enter(zone);
+ try {
+ t1 = f.call$1(arg);
+ return t1;
+ } finally {
+ t1 = old;
+ H.assertHelper(t1 != null);
+ $.Zone__current = t1;
+ }
+ },
+ _rootRunBinary: function($self, $parent, zone, f, arg1, arg2, $R, T1, T2) {
+ var old,
+ t1 = type$.Zone;
+ t1._check($self);
+ type$.ZoneDelegate._check($parent);
+ t1._check(zone);
+ $R._eval$1("@<0>")._bind$1(T1)._bind$1(T2)._eval$1("1(2,3)")._check(f);
+ T1._check(arg1);
+ T2._check(arg2);
+ if ($.Zone__current === zone)
+ return f.call$2(arg1, arg2);
+ old = P.Zone__enter(zone);
+ try {
+ t1 = f.call$2(arg1, arg2);
+ return t1;
+ } finally {
+ t1 = old;
+ H.assertHelper(t1 != null);
+ $.Zone__current = t1;
+ }
+ },
+ _rootRegisterCallback: function($self, $parent, zone, f, $R) {
+ return $R._eval$1("0()")._check(f);
+ },
+ _rootRegisterUnaryCallback: function($self, $parent, zone, f, $R, $T) {
+ return $R._eval$1("@<0>")._bind$1($T)._eval$1("1(2)")._check(f);
+ },
+ _rootRegisterBinaryCallback: function($self, $parent, zone, f, $R, T1, T2) {
+ return $R._eval$1("@<0>")._bind$1(T1)._bind$1(T2)._eval$1("1(2,3)")._check(f);
+ },
+ _rootErrorCallback: function($self, $parent, zone, error, stackTrace) {
+ type$.StackTrace._check(stackTrace);
+ return null;
+ },
+ _rootScheduleMicrotask: function($self, $parent, zone, f) {
+ var t1;
+ type$.void_Function._check(f);
+ t1 = C.C__RootZone !== zone;
+ if (t1)
+ f = !(!t1 || C.C__RootZone.get$errorZone() === zone.get$errorZone()) ? zone.bindCallbackGuarded$1(f) : zone.bindCallback$1$1(f, type$.void);
+ P._scheduleAsyncCallback(f);
+ },
+ _rootCreateTimer: function($self, $parent, zone, duration, callback) {
+ type$.Duration._check(duration);
+ callback = zone.bindCallback$1$1(type$.void_Function._check(callback), type$.void);
+ return P.Timer__createTimer(duration, callback);
+ },
+ _rootCreatePeriodicTimer: function($self, $parent, zone, duration, callback) {
+ var milliseconds;
+ type$.Duration._check(duration);
+ callback = zone.bindUnaryCallback$2$1(type$.void_Function_Timer._check(callback), type$.dynamic, type$.Timer);
+ milliseconds = C.JSInt_methods._tdivFast$1(duration._duration, 1000);
+ return P._TimerImpl$periodic(milliseconds < 0 ? 0 : milliseconds, callback);
+ },
+ _rootPrint: function($self, $parent, zone, line) {
+ H.printString(H._checkStringNullable(line));
+ },
+ _printToZone: function(line) {
+ $.Zone__current.print$1(0, line);
+ },
+ _rootFork: function($self, $parent, zone, specification, zoneValues) {
+ var valueMap, t1, t2;
+ type$.ZoneSpecification._check(specification);
+ type$.Map_dynamic_dynamic._check(zoneValues);
+ $.printToZone = P.async___printToZone$closure();
+ if (specification == null)
+ specification = C._ZoneSpecification_ALf;
+ if (zoneValues == null)
+ valueMap = zone.get$_async$_map();
+ else {
+ t1 = type$.dynamic;
+ valueMap = P.HashMap_HashMap$from(zoneValues, t1, t1);
+ }
+ t1 = new P._CustomZone(zone, valueMap);
+ t2 = zone.get$_run();
+ t1.set$_run(t2);
+ t2 = zone.get$_runUnary();
+ t1.set$_runUnary(t2);
+ t2 = zone.get$_runBinary();
+ t1.set$_runBinary(t2);
+ t2 = specification.registerCallback;
+ t1.set$_registerCallback(t2 != null ? new P._ZoneFunction(t1, t2, type$._ZoneFunction_Function) : zone.get$_registerCallback());
+ t2 = specification.registerUnaryCallback;
+ t1.set$_registerUnaryCallback(t2 != null ? new P._ZoneFunction(t1, t2, type$._ZoneFunction_Function) : zone.get$_registerUnaryCallback());
+ t2 = specification.registerBinaryCallback;
+ t1.set$_registerBinaryCallback(t2 != null ? new P._ZoneFunction(t1, t2, type$._ZoneFunction_Function) : zone.get$_registerBinaryCallback());
+ t2 = specification.errorCallback;
+ t1.set$_errorCallback(t2 != null ? new P._ZoneFunction(t1, t2, type$._ZoneFunction_of_AsyncError_Function_Zone_ZoneDelegate_Zone_Object_StackTrace) : zone.get$_errorCallback());
+ t2 = zone.get$_scheduleMicrotask();
+ t1.set$_scheduleMicrotask(t2);
+ t2 = zone.get$_createTimer();
+ t1.set$_createTimer(t2);
+ t2 = zone.get$_createPeriodicTimer();
+ t1.set$_createPeriodicTimer(t2);
+ t2 = specification.print;
+ t1.set$_print(t2 != null ? new P._ZoneFunction(t1, t2, type$._ZoneFunction_of_void_Function_Zone_ZoneDelegate_Zone_String) : zone.get$_print());
+ t2 = zone.get$_fork();
+ t1.set$_fork(t2);
+ t2 = specification.handleUncaughtError;
+ t1.set$_handleUncaughtError(t2 != null ? new P._ZoneFunction(t1, t2, type$._ZoneFunction_of_void_Function_Zone_ZoneDelegate_Zone_Object_StackTrace) : zone.get$_handleUncaughtError());
+ return t1;
+ },
+ runZoned: function(body, onError, zoneSpecification, zoneValues, $R) {
+ var e, stackTrace, errorHandler, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, exception, _null = null, t1 = {};
+ if (onError == null)
+ return P._runZoned(body, zoneValues, zoneSpecification, $R);
+ t1.binaryOnError = t1.unaryOnError = null;
+ if (type$.void_Function_Object_StackTrace._is(onError))
+ t1.binaryOnError = onError;
+ else if (type$.void_Function_Object._is(onError))
+ t1.unaryOnError = onError;
+ else
+ throw H.wrapException(P.ArgumentError$("onError callback must take either an Object (the error), or both an Object (the error) and a StackTrace."));
+ errorHandler = new P.runZoned_closure(t1);
+ if (zoneSpecification == null)
+ zoneSpecification = P._ZoneSpecification$(_null, _null, _null, _null, errorHandler, _null, _null, _null, _null, _null, _null, _null, _null);
+ else {
+ t2 = zoneSpecification;
+ t3 = t2.run;
+ t4 = t2.runUnary;
+ t5 = t2.runBinary;
+ t6 = t2.registerCallback;
+ t7 = t2.registerUnaryCallback;
+ t8 = t2.registerBinaryCallback;
+ t9 = t2.errorCallback;
+ t10 = t2.scheduleMicrotask;
+ t11 = t2.createTimer;
+ t12 = t2.createPeriodicTimer;
+ t13 = t2.print;
+ zoneSpecification = P._ZoneSpecification$(t12, t11, t9, t2.fork, errorHandler, t13, t8, t6, t7, t3, t5, t4, t10);
+ }
+ try {
+ t2 = P._runZoned(body, zoneValues, zoneSpecification, $R);
+ return t2;
+ } catch (exception) {
+ e = H.unwrapException(exception);
+ stackTrace = H.getTraceFromException(exception);
+ t2 = t1.binaryOnError;
+ if (t2 != null)
+ t2.call$2(e, stackTrace);
+ else {
+ H.assertHelper(t1.unaryOnError != null);
+ t1.unaryOnError.call$1(e);
+ }
+ }
+ return _null;
+ },
+ _runZoned: function(body, zoneValues, specification, $R) {
+ return $.Zone__current.fork$2$specification$zoneValues(specification, zoneValues).run$1$1(body, $R);
+ },
+ _AsyncRun__initializeScheduleImmediate_internalCallback: function _AsyncRun__initializeScheduleImmediate_internalCallback(t0) {
+ this._box_0 = t0;
+ },
+ _AsyncRun__initializeScheduleImmediate_closure: function _AsyncRun__initializeScheduleImmediate_closure(t0, t1, t2) {
+ this._box_0 = t0;
+ this.div = t1;
+ this.span = t2;
+ },
+ _AsyncRun__scheduleImmediateJsOverride_internalCallback: function _AsyncRun__scheduleImmediateJsOverride_internalCallback(t0) {
+ this.callback = t0;
+ },
+ _AsyncRun__scheduleImmediateWithSetImmediate_internalCallback: function _AsyncRun__scheduleImmediateWithSetImmediate_internalCallback(t0) {
+ this.callback = t0;
+ },
+ _TimerImpl: function _TimerImpl(t0) {
+ this._once = t0;
+ this._handle = null;
+ this._tick = 0;
+ },
+ _TimerImpl_internalCallback: function _TimerImpl_internalCallback(t0, t1) {
+ this.$this = t0;
+ this.callback = t1;
+ },
+ _TimerImpl$periodic_closure: function _TimerImpl$periodic_closure(t0, t1, t2, t3) {
+ var _ = this;
+ _.$this = t0;
+ _.milliseconds = t1;
+ _.start = t2;
+ _.callback = t3;
+ },
+ _AsyncAwaitCompleter: function _AsyncAwaitCompleter(t0, t1) {
+ this._future = t0;
+ this.isSync = false;
+ this.$ti = t1;
+ },
+ _awaitOnObject_closure: function _awaitOnObject_closure(t0) {
+ this.bodyFunction = t0;
+ },
+ _awaitOnObject_closure0: function _awaitOnObject_closure0(t0) {
+ this.bodyFunction = t0;
+ },
+ _wrapJsFunctionForAsync_closure: function _wrapJsFunctionForAsync_closure(t0) {
+ this.$protected = t0;
+ },
+ _BroadcastStream: function _BroadcastStream(t0, t1) {
+ this._controller = t0;
+ this.$ti = t1;
+ },
+ _BroadcastSubscription: function _BroadcastSubscription(t0, t1, t2, t3) {
+ var _ = this;
+ _._eventState = 0;
+ _._async$_previous = _._async$_next = null;
+ _._controller = t0;
+ _._onDone = _._onError = _._onData = null;
+ _._zone = t1;
+ _._state = t2;
+ _._pending = _._cancelFuture = null;
+ _.$ti = t3;
+ },
+ _BroadcastStreamController: function _BroadcastStreamController() {
+ },
+ _SyncBroadcastStreamController: function _SyncBroadcastStreamController(t0, t1, t2) {
+ var _ = this;
+ _.onListen = t0;
+ _.onCancel = t1;
+ _._state = 0;
+ _._doneFuture = _._addStreamState = _._lastSubscription = _._firstSubscription = null;
+ _.$ti = t2;
+ },
+ _SyncBroadcastStreamController__sendData_closure: function _SyncBroadcastStreamController__sendData_closure(t0, t1) {
+ this.$this = t0;
+ this.data = t1;
+ },
+ _SyncBroadcastStreamController__sendError_closure: function _SyncBroadcastStreamController__sendError_closure(t0, t1, t2) {
+ this.$this = t0;
+ this.error = t1;
+ this.stackTrace = t2;
+ },
+ _SyncBroadcastStreamController__sendDone_closure: function _SyncBroadcastStreamController__sendDone_closure(t0) {
+ this.$this = t0;
+ },
+ _AsyncBroadcastStreamController: function _AsyncBroadcastStreamController(t0, t1, t2) {
+ var _ = this;
+ _.onListen = t0;
+ _.onCancel = t1;
+ _._state = 0;
+ _._doneFuture = _._addStreamState = _._lastSubscription = _._firstSubscription = null;
+ _.$ti = t2;
+ },
+ Future: function Future() {
+ },
+ Future_Future_closure: function Future_Future_closure(t0, t1) {
+ this.result = t0;
+ this.computation = t1;
+ },
+ Future_Future$microtask_closure: function Future_Future$microtask_closure(t0, t1) {
+ this.result = t0;
+ this.computation = t1;
+ },
+ Future_wait_handleError: function Future_wait_handleError(t0, t1, t2, t3) {
+ var _ = this;
+ _._box_0 = t0;
+ _.cleanUp = t1;
+ _.eagerError = t2;
+ _.result = t3;
+ },
+ Future_wait_closure: function Future_wait_closure(t0, t1, t2, t3, t4, t5) {
+ var _ = this;
+ _._box_0 = t0;
+ _.pos = t1;
+ _.result = t2;
+ _.cleanUp = t3;
+ _.eagerError = t4;
+ _.T = t5;
+ },
+ Future_forEach_closure: function Future_forEach_closure(t0, t1) {
+ this.iterator = t0;
+ this.action = t1;
+ },
+ Future_doWhile_closure: function Future_doWhile_closure(t0, t1, t2) {
+ this._box_0 = t0;
+ this.action = t1;
+ this.doneSignal = t2;
+ },
+ TimeoutException: function TimeoutException(t0, t1) {
+ this.message = t0;
+ this.duration = t1;
+ },
+ Completer: function Completer() {
+ },
+ _Completer: function _Completer() {
+ },
+ _AsyncCompleter: function _AsyncCompleter(t0, t1) {
+ this.future = t0;
+ this.$ti = t1;
+ },
+ _SyncCompleter: function _SyncCompleter(t0, t1) {
+ this.future = t0;
+ this.$ti = t1;
+ },
+ _FutureListener: function _FutureListener(t0, t1, t2, t3, t4) {
+ var _ = this;
+ _._nextListener = null;
+ _.result = t0;
+ _.state = t1;
+ _.callback = t2;
+ _.errorCallback = t3;
+ _.$ti = t4;
+ },
+ _Future: function _Future(t0, t1) {
+ var _ = this;
+ _._state = 0;
+ _._zone = t0;
+ _._resultOrListeners = null;
+ _.$ti = t1;
+ },
+ _Future__addListener_closure: function _Future__addListener_closure(t0, t1) {
+ this.$this = t0;
+ this.listener = t1;
+ },
+ _Future__prependListeners_closure: function _Future__prependListeners_closure(t0, t1) {
+ this._box_0 = t0;
+ this.$this = t1;
+ },
+ _Future__chainForeignFuture_closure: function _Future__chainForeignFuture_closure(t0) {
+ this.target = t0;
+ },
+ _Future__chainForeignFuture_closure0: function _Future__chainForeignFuture_closure0(t0) {
+ this.target = t0;
+ },
+ _Future__chainForeignFuture_closure1: function _Future__chainForeignFuture_closure1(t0, t1, t2) {
+ this.target = t0;
+ this.e = t1;
+ this.s = t2;
+ },
+ _Future__asyncComplete_closure: function _Future__asyncComplete_closure(t0, t1) {
+ this.$this = t0;
+ this.value = t1;
+ },
+ _Future__chainFuture_closure: function _Future__chainFuture_closure(t0, t1) {
+ this.$this = t0;
+ this.value = t1;
+ },
+ _Future__asyncCompleteError_closure: function _Future__asyncCompleteError_closure(t0, t1, t2) {
+ this.$this = t0;
+ this.error = t1;
+ this.stackTrace = t2;
+ },
+ _Future__propagateToListeners_handleWhenCompleteCallback: function _Future__propagateToListeners_handleWhenCompleteCallback(t0, t1, t2, t3) {
+ var _ = this;
+ _._box_1 = t0;
+ _._box_0 = t1;
+ _.listener = t2;
+ _.hasError = t3;
+ },
+ _Future__propagateToListeners_handleWhenCompleteCallback_closure: function _Future__propagateToListeners_handleWhenCompleteCallback_closure(t0) {
+ this.originalSource = t0;
+ },
+ _Future__propagateToListeners_handleValueCallback: function _Future__propagateToListeners_handleValueCallback(t0, t1, t2) {
+ this._box_0 = t0;
+ this.listener = t1;
+ this.sourceResult = t2;
+ },
+ _Future__propagateToListeners_handleError: function _Future__propagateToListeners_handleError(t0, t1, t2) {
+ this._box_1 = t0;
+ this._box_0 = t1;
+ this.listener = t2;
+ },
+ _AsyncCallbackEntry: function _AsyncCallbackEntry(t0) {
+ this.callback = t0;
+ this.next = null;
+ },
+ Stream: function Stream() {
+ },
+ Stream_Stream$fromFuture_closure: function Stream_Stream$fromFuture_closure(t0, t1) {
+ this.controller = t0;
+ this.T = t1;
+ },
+ Stream_Stream$fromFuture_closure0: function Stream_Stream$fromFuture_closure0(t0) {
+ this.controller = t0;
+ },
+ Stream_Stream$fromIterable_closure: function Stream_Stream$fromIterable_closure(t0, t1) {
+ this.elements = t0;
+ this.T = t1;
+ },
+ Stream_pipe_closure: function Stream_pipe_closure(t0) {
+ this.streamConsumer = t0;
+ },
+ Stream_length_closure: function Stream_length_closure(t0, t1) {
+ this._box_0 = t0;
+ this.$this = t1;
+ },
+ Stream_length_closure0: function Stream_length_closure0(t0, t1) {
+ this._box_0 = t0;
+ this.future = t1;
+ },
+ Stream_first_closure: function Stream_first_closure(t0, t1, t2) {
+ this._box_0 = t0;
+ this.$this = t1;
+ this.future = t2;
+ },
+ Stream_first_closure0: function Stream_first_closure0(t0) {
+ this.future = t0;
+ },
+ Stream_last_closure: function Stream_last_closure(t0, t1) {
+ this._box_0 = t0;
+ this.$this = t1;
+ },
+ Stream_last_closure0: function Stream_last_closure0(t0, t1) {
+ this._box_0 = t0;
+ this.future = t1;
+ },
+ Stream_firstWhere_closure: function Stream_firstWhere_closure(t0, t1, t2, t3) {
+ var _ = this;
+ _._box_0 = t0;
+ _.$this = t1;
+ _.test = t2;
+ _.future = t3;
+ },
+ Stream_firstWhere__closure: function Stream_firstWhere__closure(t0, t1) {
+ this.test = t0;
+ this.value = t1;
+ },
+ Stream_firstWhere__closure0: function Stream_firstWhere__closure0(t0, t1, t2) {
+ this._box_0 = t0;
+ this.future = t1;
+ this.value = t2;
+ },
+ Stream_firstWhere_closure0: function Stream_firstWhere_closure0(t0, t1, t2) {
+ this.$this = t0;
+ this.orElse = t1;
+ this.future = t2;
+ },
+ StreamSubscription: function StreamSubscription() {
+ },
+ StreamTransformerBase: function StreamTransformerBase() {
+ },
+ _StreamController: function _StreamController() {
+ },
+ _StreamController__subscribe_closure: function _StreamController__subscribe_closure(t0) {
+ this.$this = t0;
+ },
+ _StreamController__recordCancel_complete: function _StreamController__recordCancel_complete(t0) {
+ this.$this = t0;
+ },
+ _SyncStreamControllerDispatch: function _SyncStreamControllerDispatch() {
+ },
+ _AsyncStreamControllerDispatch: function _AsyncStreamControllerDispatch() {
+ },
+ _AsyncStreamController: function _AsyncStreamController(t0, t1, t2, t3, t4) {
+ var _ = this;
+ _._varData = null;
+ _._state = 0;
+ _._doneFuture = null;
+ _.onListen = t0;
+ _.onPause = t1;
+ _.onResume = t2;
+ _.onCancel = t3;
+ _.$ti = t4;
+ },
+ _SyncStreamController: function _SyncStreamController(t0, t1, t2, t3, t4) {
+ var _ = this;
+ _._varData = null;
+ _._state = 0;
+ _._doneFuture = null;
+ _.onListen = t0;
+ _.onPause = t1;
+ _.onResume = t2;
+ _.onCancel = t3;
+ _.$ti = t4;
+ },
+ _ControllerStream: function _ControllerStream(t0, t1) {
+ this._controller = t0;
+ this.$ti = t1;
+ },
+ _ControllerSubscription: function _ControllerSubscription(t0, t1, t2, t3) {
+ var _ = this;
+ _._controller = t0;
+ _._onDone = _._onError = _._onData = null;
+ _._zone = t1;
+ _._state = t2;
+ _._pending = _._cancelFuture = null;
+ _.$ti = t3;
+ },
+ _StreamSinkWrapper: function _StreamSinkWrapper(t0, t1) {
+ this._async$_target = t0;
+ this.$ti = t1;
+ },
+ _AddStreamState: function _AddStreamState() {
+ },
+ _AddStreamState_makeErrorHandler_closure: function _AddStreamState_makeErrorHandler_closure(t0) {
+ this.controller = t0;
+ },
+ _AddStreamState_cancel_closure: function _AddStreamState_cancel_closure(t0) {
+ this.$this = t0;
+ },
+ _StreamControllerAddStreamState: function _StreamControllerAddStreamState(t0, t1, t2, t3) {
+ var _ = this;
+ _.varData = t0;
+ _.addStreamFuture = t1;
+ _.addSubscription = t2;
+ _.$ti = t3;
+ },
+ _BufferingStreamSubscription: function _BufferingStreamSubscription(t0, t1, t2) {
+ var _ = this;
+ _._onDone = _._onError = _._onData = null;
+ _._zone = t0;
+ _._state = t1;
+ _._pending = _._cancelFuture = null;
+ _.$ti = t2;
+ },
+ _BufferingStreamSubscription__sendError_sendError: function _BufferingStreamSubscription__sendError_sendError(t0, t1, t2) {
+ this.$this = t0;
+ this.error = t1;
+ this.stackTrace = t2;
+ },
+ _BufferingStreamSubscription__sendDone_sendDone: function _BufferingStreamSubscription__sendDone_sendDone(t0) {
+ this.$this = t0;
+ },
+ _StreamImpl: function _StreamImpl() {
+ },
+ _GeneratedStreamImpl: function _GeneratedStreamImpl(t0, t1) {
+ this._pending = t0;
+ this._isUsed = false;
+ this.$ti = t1;
+ },
+ _IterablePendingEvents: function _IterablePendingEvents(t0, t1) {
+ this._async$_iterator = t0;
+ this._state = 0;
+ this.$ti = t1;
+ },
+ _DelayedEvent: function _DelayedEvent() {
+ },
+ _DelayedData: function _DelayedData(t0, t1) {
+ this.value = t0;
+ this.next = null;
+ this.$ti = t1;
+ },
+ _DelayedError: function _DelayedError(t0, t1) {
+ this.error = t0;
+ this.stackTrace = t1;
+ this.next = null;
+ },
+ _DelayedDone: function _DelayedDone() {
+ },
+ _PendingEvents: function _PendingEvents() {
+ },
+ _PendingEvents_schedule_closure: function _PendingEvents_schedule_closure(t0, t1) {
+ this.$this = t0;
+ this.dispatch = t1;
+ },
+ _StreamImplEvents: function _StreamImplEvents(t0) {
+ var _ = this;
+ _.lastPendingEvent = _.firstPendingEvent = null;
+ _._state = 0;
+ _.$ti = t0;
+ },
+ _DoneStreamSubscription: function _DoneStreamSubscription(t0, t1, t2) {
+ var _ = this;
+ _._zone = t0;
+ _._state = 0;
+ _._onDone = t1;
+ _.$ti = t2;
+ },
+ _StreamIterator: function _StreamIterator(t0) {
+ this.$ti = t0;
+ },
+ _EmptyStream: function _EmptyStream(t0) {
+ this.$ti = t0;
+ },
+ _cancelAndError_closure: function _cancelAndError_closure(t0, t1, t2) {
+ this.future = t0;
+ this.error = t1;
+ this.stackTrace = t2;
+ },
+ _cancelAndErrorClosure_closure: function _cancelAndErrorClosure_closure(t0, t1) {
+ this.subscription = t0;
+ this.future = t1;
+ },
+ _cancelAndValue_closure: function _cancelAndValue_closure(t0, t1) {
+ this.future = t0;
+ this.value = t1;
+ },
+ Timer: function Timer() {
+ },
+ AsyncError: function AsyncError(t0, t1) {
+ this.error = t0;
+ this.stackTrace = t1;
+ },
+ _ZoneFunction: function _ZoneFunction(t0, t1, t2) {
+ this.zone = t0;
+ this.$function = t1;
+ this.$ti = t2;
+ },
+ ZoneSpecification: function ZoneSpecification() {
+ },
+ _ZoneSpecification: function _ZoneSpecification(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12) {
+ var _ = this;
+ _.handleUncaughtError = t0;
+ _.run = t1;
+ _.runUnary = t2;
+ _.runBinary = t3;
+ _.registerCallback = t4;
+ _.registerUnaryCallback = t5;
+ _.registerBinaryCallback = t6;
+ _.errorCallback = t7;
+ _.scheduleMicrotask = t8;
+ _.createTimer = t9;
+ _.createPeriodicTimer = t10;
+ _.print = t11;
+ _.fork = t12;
+ },
+ ZoneDelegate: function ZoneDelegate() {
+ },
+ Zone: function Zone() {
+ },
+ _ZoneDelegate: function _ZoneDelegate(t0) {
+ this._delegationTarget = t0;
+ },
+ _Zone: function _Zone() {
+ },
+ _CustomZone: function _CustomZone(t0, t1) {
+ var _ = this;
+ _._delegateCache = _._handleUncaughtError = _._fork = _._print = _._createPeriodicTimer = _._createTimer = _._scheduleMicrotask = _._errorCallback = _._registerBinaryCallback = _._registerUnaryCallback = _._registerCallback = _._runBinary = _._runUnary = _._run = null;
+ _.parent = t0;
+ _._async$_map = t1;
+ },
+ _CustomZone_bindCallback_closure: function _CustomZone_bindCallback_closure(t0, t1, t2) {
+ this.$this = t0;
+ this.registered = t1;
+ this.R = t2;
+ },
+ _CustomZone_bindUnaryCallback_closure: function _CustomZone_bindUnaryCallback_closure(t0, t1, t2, t3) {
+ var _ = this;
+ _.$this = t0;
+ _.registered = t1;
+ _.T = t2;
+ _.R = t3;
+ },
+ _CustomZone_bindCallbackGuarded_closure: function _CustomZone_bindCallbackGuarded_closure(t0, t1) {
+ this.$this = t0;
+ this.registered = t1;
+ },
+ _CustomZone_bindUnaryCallbackGuarded_closure: function _CustomZone_bindUnaryCallbackGuarded_closure(t0, t1, t2) {
+ this.$this = t0;
+ this.registered = t1;
+ this.T = t2;
+ },
+ _rootHandleUncaughtError_closure: function _rootHandleUncaughtError_closure(t0, t1) {
+ this._box_0 = t0;
+ this.stackTrace = t1;
+ },
+ _RootZone: function _RootZone() {
+ },
+ _RootZone_bindCallback_closure: function _RootZone_bindCallback_closure(t0, t1, t2) {
+ this.$this = t0;
+ this.f = t1;
+ this.R = t2;
+ },
+ _RootZone_bindCallbackGuarded_closure: function _RootZone_bindCallbackGuarded_closure(t0, t1) {
+ this.$this = t0;
+ this.f = t1;
+ },
+ _RootZone_bindUnaryCallbackGuarded_closure: function _RootZone_bindUnaryCallbackGuarded_closure(t0, t1, t2) {
+ this.$this = t0;
+ this.f = t1;
+ this.T = t2;
+ },
+ runZoned_closure: function runZoned_closure(t0) {
+ this._box_0 = t0;
+ },
+ HashMap_HashMap: function($K, $V) {
+ return new P._HashMap($K._eval$1("@<0>")._bind$1($V)._eval$1("_HashMap<1,2>"));
+ },
+ _HashMap__getTableEntry: function(table, key) {
+ var entry = table[key];
+ return entry === table ? null : entry;
+ },
+ _HashMap__setTableEntry: function(table, key, value) {
+ if (value == null)
+ table[key] = table;
+ else
+ table[key] = value;
+ },
+ _HashMap__newHashTable: function() {
+ var table = Object.create(null);
+ P._HashMap__setTableEntry(table, "<non-identifier-key>", table);
+ delete table["<non-identifier-key>"];
+ return table;
+ },
+ LinkedHashMap_LinkedHashMap: function($K, $V) {
+ return new H.JsLinkedHashMap($K._eval$1("@<0>")._bind$1($V)._eval$1("JsLinkedHashMap<1,2>"));
+ },
+ LinkedHashMap_LinkedHashMap$_literal: function(keyValuePairs, $K, $V) {
+ return $K._eval$1("@<0>")._bind$1($V)._eval$1("LinkedHashMap<1,2>")._check(H.fillLiteralMap(keyValuePairs, new H.JsLinkedHashMap($K._eval$1("@<0>")._bind$1($V)._eval$1("JsLinkedHashMap<1,2>"))));
+ },
+ LinkedHashMap_LinkedHashMap$_empty: function($K, $V) {
+ return new H.JsLinkedHashMap($K._eval$1("@<0>")._bind$1($V)._eval$1("JsLinkedHashMap<1,2>"));
+ },
+ LinkedHashSet_LinkedHashSet: function($E) {
+ return new P._LinkedHashSet($E._eval$1("_LinkedHashSet<0>"));
+ },
+ LinkedHashSet_LinkedHashSet$_empty: function($E) {
+ return new P._LinkedHashSet($E._eval$1("_LinkedHashSet<0>"));
+ },
+ LinkedHashSet_LinkedHashSet$_literal: function(values, $E) {
+ return $E._eval$1("LinkedHashSet<0>")._check(H.fillLiteralSet(values, new P._LinkedHashSet($E._eval$1("_LinkedHashSet<0>"))));
+ },
+ _LinkedHashSet__newHashTable: function() {
+ var table = Object.create(null);
+ H.assertHelper(table != null);
+ table["<non-identifier-key>"] = table;
+ delete table["<non-identifier-key>"];
+ return table;
+ },
+ _LinkedHashSetIterator$: function(_set, _modifications, $E) {
+ var t1 = new P._LinkedHashSetIterator(_set, _modifications, $E._eval$1("_LinkedHashSetIterator<0>"));
+ t1._collection$_cell = _set._collection$_first;
+ return t1;
+ },
+ HashMap_HashMap$from: function(other, $K, $V) {
+ var result = P.HashMap_HashMap($K, $V);
+ other.forEach$1(0, new P.HashMap_HashMap$from_closure(result, $K, $V));
+ return result;
+ },
+ IterableBase_iterableToShortString: function(iterable, leftDelimiter, rightDelimiter) {
+ var parts, t1;
+ if (P._isToStringVisiting(iterable)) {
+ if (leftDelimiter === "(" && rightDelimiter === ")")
+ return "(...)";
+ return leftDelimiter + "..." + rightDelimiter;
+ }
+ parts = H.setRuntimeTypeInfo([], type$.JSArray_String);
+ C.JSArray_methods.add$1($._toStringVisiting, iterable);
+ try {
+ P._iterablePartsToStrings(iterable, parts);
+ } finally {
+ H.assertHelper(C.JSArray_methods.get$last($._toStringVisiting) === iterable);
+ if (0 >= $._toStringVisiting.length)
+ return H.ioore($._toStringVisiting, -1);
+ $._toStringVisiting.pop();
+ }
+ t1 = P.StringBuffer__writeAll(leftDelimiter, type$.Iterable_dynamic._check(parts), ", ") + rightDelimiter;
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ IterableBase_iterableToFullString: function(iterable, leftDelimiter, rightDelimiter) {
+ var buffer, t1;
+ if (P._isToStringVisiting(iterable))
+ return leftDelimiter + "..." + rightDelimiter;
+ buffer = new P.StringBuffer(leftDelimiter);
+ C.JSArray_methods.add$1($._toStringVisiting, iterable);
+ try {
+ t1 = buffer;
+ t1._contents = P.StringBuffer__writeAll(t1._contents, iterable, ", ");
+ } finally {
+ H.assertHelper(C.JSArray_methods.get$last($._toStringVisiting) === iterable);
+ if (0 >= $._toStringVisiting.length)
+ return H.ioore($._toStringVisiting, -1);
+ $._toStringVisiting.pop();
+ }
+ buffer._contents += rightDelimiter;
+ t1 = buffer._contents;
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ _isToStringVisiting: function(o) {
+ var t1, i;
+ for (t1 = $._toStringVisiting.length, i = 0; i < t1; ++i)
+ if (o === $._toStringVisiting[i])
+ return true;
+ return false;
+ },
+ _iterablePartsToStrings: function(iterable, parts) {
+ var next, ultimateString, penultimateString, penultimate, ultimate, ultimate0, elision,
+ it = iterable.get$iterator(iterable),
+ $length = 0, count = 0;
+ while (true) {
+ if (!($length < 80 || count < 3))
+ break;
+ if (!it.moveNext$0())
+ return;
+ next = H.S(it.get$current());
+ C.JSArray_methods.add$1(parts, next);
+ $length += next.length + 2;
+ ++count;
+ }
+ if (!it.moveNext$0()) {
+ if (count <= 5)
+ return;
+ if (0 >= parts.length)
+ return H.ioore(parts, -1);
+ ultimateString = parts.pop();
+ if (0 >= parts.length)
+ return H.ioore(parts, -1);
+ penultimateString = parts.pop();
+ } else {
+ penultimate = it.get$current();
+ ++count;
+ if (!it.moveNext$0()) {
+ if (count <= 4) {
+ C.JSArray_methods.add$1(parts, H.S(penultimate));
+ return;
+ }
+ ultimateString = H.S(penultimate);
+ if (0 >= parts.length)
+ return H.ioore(parts, -1);
+ penultimateString = parts.pop();
+ $length += ultimateString.length + 2;
+ } else {
+ ultimate = it.get$current();
+ ++count;
+ H.assertHelper(count < 100);
+ for (; it.moveNext$0(); penultimate = ultimate, ultimate = ultimate0) {
+ ultimate0 = it.get$current();
+ ++count;
+ if (count > 100) {
+ while (true) {
+ if (!($length > 75 && count > 3))
+ break;
+ if (0 >= parts.length)
+ return H.ioore(parts, -1);
+ $length -= parts.pop().length + 2;
+ --count;
+ }
+ C.JSArray_methods.add$1(parts, "...");
+ return;
+ }
+ }
+ penultimateString = H.S(penultimate);
+ ultimateString = H.S(ultimate);
+ $length += ultimateString.length + penultimateString.length + 4;
+ }
+ }
+ if (count > parts.length + 2) {
+ $length += 5;
+ elision = "...";
+ } else
+ elision = null;
+ while (true) {
+ if (!($length > 80 && parts.length > 3))
+ break;
+ if (0 >= parts.length)
+ return H.ioore(parts, -1);
+ $length -= parts.pop().length + 2;
+ if (elision == null) {
+ $length += 5;
+ elision = "...";
+ }
+ }
+ if (elision != null)
+ C.JSArray_methods.add$1(parts, elision);
+ C.JSArray_methods.add$1(parts, penultimateString);
+ C.JSArray_methods.add$1(parts, ultimateString);
+ },
+ LinkedHashMap_LinkedHashMap$from: function(other, $K, $V) {
+ var result = P.LinkedHashMap_LinkedHashMap($K, $V);
+ other.forEach$1(0, new P.LinkedHashMap_LinkedHashMap$from_closure(result, $K, $V));
+ return result;
+ },
+ LinkedHashSet_LinkedHashSet$from: function(elements, $E) {
+ var t1,
+ result = P.LinkedHashSet_LinkedHashSet($E);
+ for (t1 = J.get$iterator$ax(elements); t1.moveNext$0();)
+ result.add$1(0, $E._check(t1.get$current()));
+ return result;
+ },
+ MapBase_mapToString: function(m) {
+ var result, t1 = {};
+ if (P._isToStringVisiting(m))
+ return "{...}";
+ result = new P.StringBuffer("");
+ try {
+ C.JSArray_methods.add$1($._toStringVisiting, m);
+ result._contents += "{";
+ t1.first = true;
+ m.forEach$1(0, new P.MapBase_mapToString_closure(t1, result));
+ result._contents += "}";
+ } finally {
+ H.assertHelper(C.JSArray_methods.get$last($._toStringVisiting) === m);
+ if (0 >= $._toStringVisiting.length)
+ return H.ioore($._toStringVisiting, -1);
+ $._toStringVisiting.pop();
+ }
+ t1 = result._contents;
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ ListQueue$: function($E) {
+ var t1 = new P.ListQueue($E._eval$1("ListQueue<0>")),
+ t2 = new Array(8);
+ t2.fixed$length = Array;
+ t1.set$_table(H.setRuntimeTypeInfo(t2, $E._eval$1("JSArray<0>")));
+ return t1;
+ },
+ _ListQueueIterator$: function(queue, $E) {
+ return new P._ListQueueIterator(queue, queue._tail, queue._modificationCount, queue._head, $E._eval$1("_ListQueueIterator<0>"));
+ },
+ _HashMap: function _HashMap(t0) {
+ var _ = this;
+ _._collection$_length = 0;
+ _._keys = _._collection$_rest = _._nums = _._strings = null;
+ _.$ti = t0;
+ },
+ _IdentityHashMap: function _IdentityHashMap(t0) {
+ var _ = this;
+ _._collection$_length = 0;
+ _._keys = _._collection$_rest = _._nums = _._strings = null;
+ _.$ti = t0;
+ },
+ _HashMapKeyIterable: function _HashMapKeyIterable(t0, t1) {
+ this._map = t0;
+ this.$ti = t1;
+ },
+ _HashMapKeyIterator: function _HashMapKeyIterator(t0, t1, t2) {
+ var _ = this;
+ _._map = t0;
+ _._keys = t1;
+ _._offset = 0;
+ _._collection$_current = null;
+ _.$ti = t2;
+ },
+ _LinkedHashSet: function _LinkedHashSet(t0) {
+ var _ = this;
+ _._collection$_length = 0;
+ _._collection$_last = _._collection$_first = _._collection$_rest = _._nums = _._strings = null;
+ _._collection$_modifications = 0;
+ _.$ti = t0;
+ },
+ _LinkedHashSetCell: function _LinkedHashSetCell(t0) {
+ this._element = t0;
+ this._collection$_previous = this._collection$_next = null;
+ },
+ _LinkedHashSetIterator: function _LinkedHashSetIterator(t0, t1, t2) {
+ var _ = this;
+ _._set = t0;
+ _._collection$_modifications = t1;
+ _._collection$_current = _._collection$_cell = null;
+ _.$ti = t2;
+ },
+ UnmodifiableListView: function UnmodifiableListView(t0, t1) {
+ this._collection$_source = t0;
+ this.$ti = t1;
+ },
+ HashMap_HashMap$from_closure: function HashMap_HashMap$from_closure(t0, t1, t2) {
+ this.result = t0;
+ this.K = t1;
+ this.V = t2;
+ },
+ IterableBase: function IterableBase() {
+ },
+ LinkedHashMap_LinkedHashMap$from_closure: function LinkedHashMap_LinkedHashMap$from_closure(t0, t1, t2) {
+ this.result = t0;
+ this.K = t1;
+ this.V = t2;
+ },
+ ListBase: function ListBase() {
+ },
+ ListMixin: function ListMixin() {
+ },
+ MapBase: function MapBase() {
+ },
+ MapBase_mapToString_closure: function MapBase_mapToString_closure(t0, t1) {
+ this._box_0 = t0;
+ this.result = t1;
+ },
+ MapMixin: function MapMixin() {
+ },
+ _UnmodifiableMapMixin: function _UnmodifiableMapMixin() {
+ },
+ MapView: function MapView() {
+ },
+ UnmodifiableMapView: function UnmodifiableMapView(t0, t1) {
+ this._map = t0;
+ this.$ti = t1;
+ },
+ ListQueue: function ListQueue(t0) {
+ var _ = this;
+ _._table = null;
+ _._modificationCount = _._tail = _._head = 0;
+ _.$ti = t0;
+ },
+ _ListQueueIterator: function _ListQueueIterator(t0, t1, t2, t3, t4) {
+ var _ = this;
+ _._queue = t0;
+ _._end = t1;
+ _._modificationCount = t2;
+ _._position = t3;
+ _._collection$_current = null;
+ _.$ti = t4;
+ },
+ SetMixin: function SetMixin() {
+ },
+ SetBase: function SetBase() {
+ },
+ _SetBase: function _SetBase() {
+ },
+ _ListBase_Object_ListMixin: function _ListBase_Object_ListMixin() {
+ },
+ _SetBase_Object_SetMixin: function _SetBase_Object_SetMixin() {
+ },
+ _UnmodifiableMapView_MapView__UnmodifiableMapMixin: function _UnmodifiableMapView_MapView__UnmodifiableMapMixin() {
+ },
+ _parseJson: function(source, reviver) {
+ var parsed, e, exception, t1;
+ if (typeof source != "string")
+ throw H.wrapException(H.argumentErrorValue(source));
+ parsed = null;
+ try {
+ parsed = JSON.parse(source);
+ } catch (exception) {
+ e = H.unwrapException(exception);
+ t1 = P.FormatException$(String(e), null, null);
+ throw H.wrapException(t1);
+ }
+ t1 = P._convertJsonToDartLazy(parsed);
+ return t1;
+ },
+ _convertJsonToDartLazy: function(object) {
+ var i;
+ if (object == null)
+ return null;
+ if (typeof object != "object")
+ return object;
+ if (Object.getPrototypeOf(object) !== Array.prototype)
+ return new P._JsonMap(object, Object.create(null));
+ for (i = 0; i < object.length; ++i)
+ object[i] = P._convertJsonToDartLazy(object[i]);
+ return object;
+ },
+ Utf8Decoder__convertIntercepted: function(allowMalformed, codeUnits, start, end) {
+ if (codeUnits instanceof Uint8Array)
+ return P.Utf8Decoder__convertInterceptedUint8List(false, codeUnits, start, end);
+ return null;
+ },
+ Utf8Decoder__convertInterceptedUint8List: function(allowMalformed, codeUnits, start, end) {
+ var t1, $length,
+ decoder = $.$get$Utf8Decoder__decoder();
+ if (decoder == null)
+ return null;
+ t1 = 0 === start;
+ if (t1 && true)
+ return P.Utf8Decoder__useTextDecoderChecked(decoder, codeUnits);
+ $length = codeUnits.length;
+ end = P.RangeError_checkValidRange(start, end, $length);
+ if (t1 && end === $length)
+ return P.Utf8Decoder__useTextDecoderChecked(decoder, codeUnits);
+ return P.Utf8Decoder__useTextDecoderChecked(decoder, codeUnits.subarray(start, end));
+ },
+ Utf8Decoder__useTextDecoderChecked: function(decoder, codeUnits) {
+ if (P.Utf8Decoder__unsafe(codeUnits))
+ return null;
+ return P.Utf8Decoder__useTextDecoderUnchecked(decoder, codeUnits);
+ },
+ Utf8Decoder__useTextDecoderUnchecked: function(decoder, codeUnits) {
+ var t1, exception;
+ try {
+ t1 = decoder.decode(codeUnits);
+ return t1;
+ } catch (exception) {
+ H.unwrapException(exception);
+ }
+ return null;
+ },
+ Utf8Decoder__unsafe: function(codeUnits) {
+ var i,
+ limit = codeUnits.length - 2;
+ for (i = 0; i < limit; ++i)
+ if (codeUnits[i] === 237)
+ if ((codeUnits[i + 1] & 224) === 160)
+ return true;
+ return false;
+ },
+ Utf8Decoder__makeDecoder: function() {
+ var t1, exception;
+ try {
+ t1 = new TextDecoder("utf-8", {fatal: true});
+ return t1;
+ } catch (exception) {
+ H.unwrapException(exception);
+ }
+ return null;
+ },
+ _scanOneByteCharacters: function(units, from, endIndex) {
+ var t1, i, unit;
+ for (t1 = J.getInterceptor$asx(units), i = from; i < endIndex; ++i) {
+ unit = t1.$index(units, i);
+ if (typeof unit !== "number")
+ return unit.$and();
+ if ((unit & 127) !== unit)
+ return i - from;
+ }
+ return endIndex - from;
+ },
+ Base64Codec__checkPadding: function(source, sourceIndex, sourceEnd, firstPadding, paddingCount, $length) {
+ if (C.JSInt_methods.$mod($length, 4) !== 0)
+ throw H.wrapException(P.FormatException$("Invalid base64 padding, padded length must be multiple of four, is " + $length, source, sourceEnd));
+ if (firstPadding + paddingCount !== $length)
+ throw H.wrapException(P.FormatException$("Invalid base64 padding, '=' not at the end", source, sourceIndex));
+ if (paddingCount > 2)
+ throw H.wrapException(P.FormatException$("Invalid base64 padding, more than two '=' characters", source, sourceIndex));
+ },
+ _JsonMap: function _JsonMap(t0, t1) {
+ this._original = t0;
+ this._processed = t1;
+ this._data = null;
+ },
+ _JsonMapKeyIterable: function _JsonMapKeyIterable(t0) {
+ this._parent = t0;
+ },
+ AsciiCodec: function AsciiCodec() {
+ },
+ _UnicodeSubsetEncoder: function _UnicodeSubsetEncoder() {
+ },
+ AsciiEncoder: function AsciiEncoder(t0) {
+ this._subsetMask = t0;
+ },
+ Base64Codec: function Base64Codec() {
+ },
+ Base64Encoder: function Base64Encoder() {
+ },
+ Codec: function Codec() {
+ },
+ _FusedCodec: function _FusedCodec(t0, t1, t2) {
+ this._convert$_first = t0;
+ this._second = t1;
+ this.$ti = t2;
+ },
+ Converter: function Converter() {
+ },
+ Encoding: function Encoding() {
+ },
+ JsonCodec: function JsonCodec() {
+ },
+ JsonDecoder: function JsonDecoder(t0) {
+ this._reviver = t0;
+ },
+ Utf8Codec: function Utf8Codec() {
+ },
+ Utf8Encoder: function Utf8Encoder() {
+ },
+ _Utf8Encoder: function _Utf8Encoder(t0) {
+ this._bufferIndex = this._carry = 0;
+ this._buffer = t0;
+ },
+ Utf8Decoder: function Utf8Decoder(t0) {
+ this._allowMalformed = t0;
+ },
+ _Utf8Decoder: function _Utf8Decoder(t0, t1) {
+ var _ = this;
+ _._allowMalformed = t0;
+ _._stringSink = t1;
+ _._isFirstCharacter = true;
+ _._extraUnits = _._expectedUnits = _._convert$_value = 0;
+ },
+ int_parse: function(source, onError, radix) {
+ var value = H.Primitives_parseInt(source, radix);
+ if (value != null)
+ return value;
+ if (onError != null)
+ return onError.call$1(source);
+ throw H.wrapException(P.FormatException$(source, null, null));
+ },
+ Error__objectToString: function(object) {
+ if (object instanceof H.Closure)
+ return object.toString$0(0);
+ return "Instance of '" + H.S(H.Primitives_objectTypeName(object)) + "'";
+ },
+ List_List$filled: function($length, fill, $E) {
+ var i,
+ result = J.JSArray_JSArray$fixed($length, $E);
+ if ($length !== 0 && true)
+ for (i = 0; i < result.length; ++i)
+ C.JSArray_methods.$indexSet(result, i, fill);
+ return result;
+ },
+ List_List$from: function(elements, growable, $E) {
+ var t1,
+ list = H.setRuntimeTypeInfo([], $E._eval$1("JSArray<0>"));
+ for (t1 = J.get$iterator$ax(elements); t1.moveNext$0();)
+ C.JSArray_methods.add$1(list, $E._check(t1.get$current()));
+ if (growable)
+ return list;
+ return $E._eval$1("List<0>")._check(J.JSArray_markFixedList(list));
+ },
+ List_List$unmodifiable: function(elements, $E) {
+ var result = P.List_List$from(elements, false, $E);
+ result.fixed$length = Array;
+ result.immutable$list = Array;
+ return $E._eval$1("List<0>")._check(result);
+ },
+ String_String$fromCharCodes: function(charCodes, start, end) {
+ var len;
+ if (Array.isArray(charCodes)) {
+ type$.JSArray_int._check(charCodes);
+ len = charCodes.length;
+ end = P.RangeError_checkValidRange(start, end, len);
+ return H.Primitives_stringFromCharCodes(start > 0 || end < len ? C.JSArray_methods.sublist$2(charCodes, start, end) : charCodes);
+ }
+ if (type$.NativeUint8List._is(charCodes))
+ return H.Primitives_stringFromNativeUint8List(charCodes, start, P.RangeError_checkValidRange(start, end, charCodes.length));
+ return P.String__stringFromIterable(charCodes, start, end);
+ },
+ String_String$fromCharCode: function(charCode) {
+ return H.Primitives_stringFromCharCode(charCode);
+ },
+ String__stringFromIterable: function(charCodes, start, end) {
+ var t1, it, i, list, _null = null;
+ if (start < 0)
+ throw H.wrapException(P.RangeError$range(start, 0, J.get$length$asx(charCodes), _null, _null));
+ t1 = end == null;
+ if (!t1 && end < start)
+ throw H.wrapException(P.RangeError$range(end, start, J.get$length$asx(charCodes), _null, _null));
+ it = J.get$iterator$ax(charCodes);
+ for (i = 0; i < start; ++i)
+ if (!it.moveNext$0())
+ throw H.wrapException(P.RangeError$range(start, 0, i, _null, _null));
+ list = [];
+ if (t1)
+ for (; it.moveNext$0();)
+ list.push(it.get$current());
+ else
+ for (i = start; i < end; ++i) {
+ if (!it.moveNext$0())
+ throw H.wrapException(P.RangeError$range(end, start, i, _null, _null));
+ list.push(it.get$current());
+ }
+ return H.Primitives_stringFromCharCodes(list);
+ },
+ RegExp_RegExp: function(source, multiLine) {
+ return new H.JSSyntaxRegExp(source, H.JSSyntaxRegExp_makeNative(source, multiLine, true, false, false, false));
+ },
+ StringBuffer__writeAll: function(string, objects, separator) {
+ var iterator = J.get$iterator$ax(objects);
+ if (!iterator.moveNext$0())
+ return string;
+ if (separator.length === 0) {
+ do
+ string += H.S(iterator.get$current());
+ while (iterator.moveNext$0());
+ } else {
+ string += H.S(iterator.get$current());
+ for (; iterator.moveNext$0();)
+ string = string + separator + H.S(iterator.get$current());
+ }
+ return string;
+ },
+ Uri_base: function() {
+ var uri = H.Primitives_currentUri();
+ if (uri != null)
+ return P.Uri_parse(uri);
+ throw H.wrapException(P.UnsupportedError$("'Uri.base' is not supported"));
+ },
+ _Uri__uriEncode: function(canonicalTable, text, encoding, spaceToPlus) {
+ var t1, bytes, i, t2, byte, t3,
+ _s16_ = "0123456789ABCDEF";
+ if (encoding === C.C_Utf8Codec) {
+ t1 = $.$get$_Uri__needsNoEncoding()._nativeRegExp;
+ if (typeof text != "string")
+ H.throwExpression(H.argumentErrorValue(text));
+ t1 = t1.test(text);
+ } else
+ t1 = false;
+ if (t1)
+ return text;
+ H._instanceType(encoding)._eval$1("Codec.S")._check(text);
+ bytes = encoding.get$encoder().convert$1(text);
+ for (t1 = bytes.length, i = 0, t2 = ""; i < t1; ++i) {
+ byte = bytes[i];
+ if (byte < 128) {
+ t3 = byte >>> 4;
+ if (t3 >= 8)
+ return H.ioore(canonicalTable, t3);
+ t3 = (canonicalTable[t3] & 1 << (byte & 15)) !== 0;
+ } else
+ t3 = false;
+ if (t3)
+ t2 += H.Primitives_stringFromCharCode(byte);
+ else
+ t2 = spaceToPlus && byte === 32 ? t2 + "+" : t2 + "%" + _s16_[byte >>> 4 & 15] + _s16_[byte & 15];
+ }
+ return t2.charCodeAt(0) == 0 ? t2 : t2;
+ },
+ StackTrace_current: function() {
+ var stackTrace, exception;
+ if (H.boolConversionCheck($.$get$_hasErrorStackProperty()))
+ return H.getTraceFromException(new Error());
+ try {
+ throw H.wrapException("");
+ } catch (exception) {
+ H.unwrapException(exception);
+ stackTrace = H.getTraceFromException(exception);
+ return stackTrace;
+ }
+ },
+ DateTime__fourDigits: function(n) {
+ var absN = Math.abs(n),
+ sign = n < 0 ? "-" : "";
+ if (absN >= 1000)
+ return "" + n;
+ if (absN >= 100)
+ return sign + "0" + absN;
+ if (absN >= 10)
+ return sign + "00" + absN;
+ return sign + "000" + absN;
+ },
+ DateTime__threeDigits: function(n) {
+ if (n >= 100)
+ return "" + n;
+ if (n >= 10)
+ return "0" + n;
+ return "00" + n;
+ },
+ DateTime__twoDigits: function(n) {
+ if (n >= 10)
+ return "" + n;
+ return "0" + n;
+ },
+ Duration$: function(microseconds, minutes) {
+ if (typeof microseconds !== "number")
+ return H.iae(microseconds);
+ return new P.Duration(60000000 * minutes + microseconds);
+ },
+ Error_safeToString: function(object) {
+ if (typeof object == "number" || H._isBool(object) || null == object)
+ return J.toString$0$(object);
+ if (typeof object == "string")
+ return JSON.stringify(object);
+ return P.Error__objectToString(object);
+ },
+ AssertionError$: function(message) {
+ return new P.AssertionError(message);
+ },
+ ArgumentError$: function(message) {
+ return new P.ArgumentError(false, null, null, message);
+ },
+ ArgumentError$value: function(value, $name, message) {
+ return new P.ArgumentError(true, value, $name, message);
+ },
+ ArgumentError$notNull: function($name) {
+ return new P.ArgumentError(false, null, $name, "Must not be null");
+ },
+ RangeError$: function(message) {
+ var _null = null;
+ return new P.RangeError(_null, _null, false, _null, _null, message);
+ },
+ RangeError$value: function(value, $name) {
+ return new P.RangeError(null, null, true, value, $name, "Value not in range");
+ },
+ RangeError$range: function(invalidValue, minValue, maxValue, $name, message) {
+ return new P.RangeError(minValue, maxValue, true, invalidValue, $name, "Invalid value");
+ },
+ RangeError_checkValueInInterval: function(value, minValue, maxValue, $name) {
+ if (value < minValue || value > maxValue)
+ throw H.wrapException(P.RangeError$range(value, minValue, maxValue, $name, null));
+ },
+ RangeError_checkValidRange: function(start, end, $length) {
+ if (0 > start || start > $length)
+ throw H.wrapException(P.RangeError$range(start, 0, $length, "start", null));
+ if (end != null) {
+ if (start > end || end > $length)
+ throw H.wrapException(P.RangeError$range(end, start, $length, "end", null));
+ return end;
+ }
+ return $length;
+ },
+ RangeError_checkNotNegative: function(value, $name) {
+ if (typeof value !== "number")
+ return value.$lt();
+ if (value < 0)
+ throw H.wrapException(P.RangeError$range(value, 0, null, $name, null));
+ },
+ IndexError$: function(invalidValue, indexable, $name, message, $length) {
+ var t1 = H._checkIntNullable($length == null ? J.get$length$asx(indexable) : $length);
+ return new P.IndexError(t1, true, invalidValue, $name, "Index out of range");
+ },
+ UnsupportedError$: function(message) {
+ return new P.UnsupportedError(message);
+ },
+ UnimplementedError$: function(message) {
+ return new P.UnimplementedError(message);
+ },
+ StateError$: function(message) {
+ return new P.StateError(message);
+ },
+ ConcurrentModificationError$: function(modifiedObject) {
+ return new P.ConcurrentModificationError(modifiedObject);
+ },
+ FormatException$: function(message, source, offset) {
+ return new P.FormatException(message, source, offset);
+ },
+ List_List$generate: function($length, generator, growable, $E) {
+ var i,
+ result = H.setRuntimeTypeInfo([], $E._eval$1("JSArray<0>"));
+ C.JSArray_methods.set$length(result, $length);
+ for (i = 0; i < $length; ++i)
+ C.JSArray_methods.$indexSet(result, i, generator.call$1(i));
+ return result;
+ },
+ Map_castFrom: function(source, $K, $V, K2, V2) {
+ return new H.CastMap(source, $K._eval$1("@<0>")._bind$1($V)._bind$1(K2)._bind$1(V2)._eval$1("CastMap<1,2,3,4>"));
+ },
+ print: function(object) {
+ var line = H.S(object),
+ t1 = $.printToZone;
+ if (t1 == null)
+ H.printString(line);
+ else
+ t1.call$1(line);
+ },
+ _combineSurrogatePair: function(start, end) {
+ return 65536 + ((start & 1023) << 10) + (end & 1023);
+ },
+ Uri_parse: function(uri) {
+ var delta, t1, indices, schemeEnd, hostStart, portStart, pathStart, queryStart, fragmentStart, isSimple, scheme, t2, schemeAuth, queryStart0, pathStart0, _null = null,
+ end = uri.length;
+ if (end >= 5) {
+ delta = ((J._codeUnitAt$1$s(uri, 4) ^ 58) * 3 | C.JSString_methods._codeUnitAt$1(uri, 0) ^ 100 | C.JSString_methods._codeUnitAt$1(uri, 1) ^ 97 | C.JSString_methods._codeUnitAt$1(uri, 2) ^ 116 | C.JSString_methods._codeUnitAt$1(uri, 3) ^ 97) >>> 0;
+ if (delta === 0)
+ return P.UriData__parse(end < end ? C.JSString_methods.substring$2(uri, 0, end) : uri, 5, _null).get$uri();
+ else if (delta === 32)
+ return P.UriData__parse(C.JSString_methods.substring$2(uri, 5, end), 0, _null).get$uri();
+ }
+ t1 = new Array(8);
+ t1.fixed$length = Array;
+ indices = H.setRuntimeTypeInfo(t1, type$.JSArray_int);
+ C.JSArray_methods.$indexSet(indices, 0, 0);
+ C.JSArray_methods.$indexSet(indices, 1, -1);
+ C.JSArray_methods.$indexSet(indices, 2, -1);
+ C.JSArray_methods.$indexSet(indices, 7, -1);
+ C.JSArray_methods.$indexSet(indices, 3, 0);
+ C.JSArray_methods.$indexSet(indices, 4, 0);
+ C.JSArray_methods.$indexSet(indices, 5, end);
+ C.JSArray_methods.$indexSet(indices, 6, end);
+ if (P._scan(uri, 0, end, 0, indices) >= 14)
+ C.JSArray_methods.$indexSet(indices, 7, end);
+ schemeEnd = indices[1];
+ if (typeof schemeEnd !== "number")
+ return schemeEnd.$ge();
+ if (schemeEnd >= 0)
+ if (P._scan(uri, 0, schemeEnd, 20, indices) === 20)
+ indices[7] = schemeEnd;
+ t1 = indices[2];
+ if (typeof t1 !== "number")
+ return t1.$add();
+ hostStart = t1 + 1;
+ portStart = indices[3];
+ pathStart = indices[4];
+ queryStart = indices[5];
+ fragmentStart = indices[6];
+ if (typeof fragmentStart !== "number")
+ return fragmentStart.$lt();
+ if (typeof queryStart !== "number")
+ return H.iae(queryStart);
+ if (fragmentStart < queryStart)
+ queryStart = fragmentStart;
+ if (typeof pathStart !== "number")
+ return pathStart.$lt();
+ if (pathStart < hostStart)
+ pathStart = queryStart;
+ else if (pathStart <= schemeEnd)
+ pathStart = schemeEnd + 1;
+ if (typeof portStart !== "number")
+ return portStart.$lt();
+ if (portStart < hostStart)
+ portStart = pathStart;
+ H.assertHelper(hostStart === 0 || schemeEnd <= hostStart);
+ H.assertHelper(hostStart <= portStart);
+ H.assertHelper(schemeEnd <= pathStart);
+ H.assertHelper(portStart <= pathStart);
+ H.assertHelper(pathStart <= queryStart);
+ H.assertHelper(queryStart <= fragmentStart);
+ t1 = indices[7];
+ if (typeof t1 !== "number")
+ return t1.$lt();
+ isSimple = t1 < 0;
+ if (isSimple)
+ if (hostStart > schemeEnd + 3) {
+ scheme = _null;
+ isSimple = false;
+ } else {
+ t1 = portStart > 0;
+ if (t1 && portStart + 1 === pathStart) {
+ scheme = _null;
+ isSimple = false;
+ } else {
+ if (!(queryStart < end && queryStart === pathStart + 2 && J.startsWith$2$s(uri, "..", pathStart)))
+ t2 = queryStart > pathStart + 2 && J.startsWith$2$s(uri, "/..", queryStart - 3);
+ else
+ t2 = true;
+ if (t2) {
+ scheme = _null;
+ isSimple = false;
+ } else {
+ if (schemeEnd === 4)
+ if (J.startsWith$2$s(uri, "file", 0)) {
+ if (hostStart <= 0) {
+ if (!C.JSString_methods.startsWith$2(uri, "/", pathStart)) {
+ schemeAuth = "file:///";
+ delta = 3;
+ } else {
+ schemeAuth = "file://";
+ delta = 2;
+ }
+ uri = schemeAuth + C.JSString_methods.substring$2(uri, pathStart, end);
+ schemeEnd -= 0;
+ t1 = delta - 0;
+ queryStart += t1;
+ fragmentStart += t1;
+ end = uri.length;
+ hostStart = 7;
+ portStart = 7;
+ pathStart = 7;
+ } else if (pathStart === queryStart) {
+ queryStart0 = queryStart + 1;
+ ++fragmentStart;
+ uri = C.JSString_methods.replaceRange$3(uri, pathStart, queryStart, "/");
+ ++end;
+ queryStart = queryStart0;
+ }
+ scheme = "file";
+ } else if (C.JSString_methods.startsWith$2(uri, "http", 0)) {
+ if (t1 && portStart + 3 === pathStart && C.JSString_methods.startsWith$2(uri, "80", portStart + 1)) {
+ pathStart0 = pathStart - 3;
+ queryStart -= 3;
+ fragmentStart -= 3;
+ uri = C.JSString_methods.replaceRange$3(uri, portStart, pathStart, "");
+ end -= 3;
+ pathStart = pathStart0;
+ }
+ scheme = "http";
+ } else
+ scheme = _null;
+ else if (schemeEnd === 5 && J.startsWith$2$s(uri, "https", 0)) {
+ if (t1 && portStart + 4 === pathStart && J.startsWith$2$s(uri, "443", portStart + 1)) {
+ pathStart0 = pathStart - 4;
+ queryStart -= 4;
+ fragmentStart -= 4;
+ uri = J.replaceRange$3$asx(uri, portStart, pathStart, "");
+ end -= 3;
+ pathStart = pathStart0;
+ }
+ scheme = "https";
+ } else
+ scheme = _null;
+ isSimple = true;
+ }
+ }
+ }
+ else
+ scheme = _null;
+ if (isSimple) {
+ t1 = uri.length;
+ if (end < t1) {
+ uri = J.substring$2$s(uri, 0, end);
+ schemeEnd -= 0;
+ hostStart -= 0;
+ portStart -= 0;
+ pathStart -= 0;
+ queryStart -= 0;
+ fragmentStart -= 0;
+ }
+ return new P._SimpleUri(uri, schemeEnd, hostStart, portStart, pathStart, queryStart, fragmentStart, scheme);
+ }
+ return P._Uri__Uri$notSimple(uri, 0, end, schemeEnd, hostStart, portStart, pathStart, queryStart, fragmentStart, scheme);
+ },
+ Uri_decodeComponent: function(encodedComponent) {
+ H._checkStringNullable(encodedComponent);
+ return P._Uri__uriDecode(encodedComponent, 0, encodedComponent.length, C.C_Utf8Codec, false);
+ },
+ Uri__parseIPv4Address: function(host, start, end) {
+ var t1, i, partStart, partIndex, char, part, partIndex0, _null = null,
+ _s43_ = "IPv4 address should contain exactly 4 parts",
+ _s37_ = "each part must be in the range 0..255",
+ error = new P.Uri__parseIPv4Address_error(host),
+ result = new Uint8Array(4);
+ for (t1 = result.length, i = start, partStart = i, partIndex = 0; i < end; ++i) {
+ char = C.JSString_methods.codeUnitAt$1(host, i);
+ if (char !== 46) {
+ if ((char ^ 48) > 9)
+ error.call$2("invalid character", i);
+ } else {
+ if (partIndex === 3)
+ error.call$2(_s43_, i);
+ part = P.int_parse(C.JSString_methods.substring$2(host, partStart, i), _null, _null);
+ if (typeof part !== "number")
+ return part.$gt();
+ if (part > 255)
+ error.call$2(_s37_, partStart);
+ partIndex0 = partIndex + 1;
+ if (partIndex >= t1)
+ return H.ioore(result, partIndex);
+ result[partIndex] = part;
+ partStart = i + 1;
+ partIndex = partIndex0;
+ }
+ }
+ if (partIndex !== 3)
+ error.call$2(_s43_, end);
+ part = P.int_parse(C.JSString_methods.substring$2(host, partStart, end), _null, _null);
+ if (typeof part !== "number")
+ return part.$gt();
+ if (part > 255)
+ error.call$2(_s37_, partStart);
+ if (partIndex >= t1)
+ return H.ioore(result, partIndex);
+ result[partIndex] = part;
+ return result;
+ },
+ Uri_parseIPv6Address: function(host, start, end) {
+ var parts, i, partStart, wildcardSeen, seenDot, char, atEnd, t1, last, bytes, t2, wildCardLength, index, value, j, t3,
+ error = new P.Uri_parseIPv6Address_error(host),
+ parseHex = new P.Uri_parseIPv6Address_parseHex(error, host);
+ if (host.length < 2)
+ error.call$1("address is too short");
+ parts = H.setRuntimeTypeInfo([], type$.JSArray_int);
+ for (i = start, partStart = i, wildcardSeen = false, seenDot = false; i < end; ++i) {
+ char = C.JSString_methods.codeUnitAt$1(host, i);
+ if (char === 58) {
+ if (i === start) {
+ ++i;
+ if (C.JSString_methods.codeUnitAt$1(host, i) !== 58)
+ error.call$2("invalid start colon.", i);
+ partStart = i;
+ }
+ if (i === partStart) {
+ if (wildcardSeen)
+ error.call$2("only one wildcard `::` is allowed", i);
+ C.JSArray_methods.add$1(parts, -1);
+ wildcardSeen = true;
+ } else
+ C.JSArray_methods.add$1(parts, parseHex.call$2(partStart, i));
+ partStart = i + 1;
+ } else if (char === 46)
+ seenDot = true;
+ }
+ if (parts.length === 0)
+ error.call$1("too few parts");
+ atEnd = partStart === end;
+ t1 = C.JSArray_methods.get$last(parts);
+ if (atEnd && t1 !== -1)
+ error.call$2("expected a part after last `:`", end);
+ if (!atEnd)
+ if (!seenDot)
+ C.JSArray_methods.add$1(parts, parseHex.call$2(partStart, end));
+ else {
+ last = P.Uri__parseIPv4Address(host, partStart, end);
+ C.JSArray_methods.add$1(parts, (last[0] << 8 | last[1]) >>> 0);
+ C.JSArray_methods.add$1(parts, (last[2] << 8 | last[3]) >>> 0);
+ }
+ if (wildcardSeen) {
+ if (parts.length > 7)
+ error.call$1("an address with a wildcard must have less than 7 parts");
+ } else if (parts.length !== 8)
+ error.call$1("an address without a wildcard must contain exactly 8 parts");
+ bytes = new Uint8Array(16);
+ for (t1 = parts.length, t2 = bytes.length, wildCardLength = 9 - t1, i = 0, index = 0; i < t1; ++i) {
+ value = parts[i];
+ if (value === -1)
+ for (j = 0; j < wildCardLength; ++j) {
+ if (index < 0 || index >= t2)
+ return H.ioore(bytes, index);
+ bytes[index] = 0;
+ t3 = index + 1;
+ if (t3 >= t2)
+ return H.ioore(bytes, t3);
+ bytes[t3] = 0;
+ index += 2;
+ }
+ else {
+ t3 = C.JSInt_methods._shrOtherPositive$1(value, 8);
+ if (index < 0 || index >= t2)
+ return H.ioore(bytes, index);
+ bytes[index] = t3;
+ t3 = index + 1;
+ if (t3 >= t2)
+ return H.ioore(bytes, t3);
+ bytes[t3] = value & 255;
+ index += 2;
+ }
+ }
+ return bytes;
+ },
+ _Uri__Uri$notSimple: function(uri, start, end, schemeEnd, hostStart, portStart, pathStart, queryStart, fragmentStart, scheme) {
+ var userInfoStart, userInfo, host, t1, port, path, query, _null = null;
+ if (scheme == null)
+ if (schemeEnd > start)
+ scheme = P._Uri__makeScheme(uri, start, schemeEnd);
+ else {
+ if (schemeEnd === start)
+ P._Uri__fail(uri, start, "Invalid empty scheme");
+ scheme = "";
+ }
+ if (hostStart > start) {
+ userInfoStart = schemeEnd + 3;
+ userInfo = userInfoStart < hostStart ? P._Uri__makeUserInfo(uri, userInfoStart, hostStart - 1) : "";
+ host = P._Uri__makeHost(uri, hostStart, portStart, false);
+ t1 = portStart + 1;
+ port = t1 < pathStart ? P._Uri__makePort(P.int_parse(J.substring$2$s(uri, t1, pathStart), new P._Uri__Uri$notSimple_closure(uri, portStart), _null), scheme) : _null;
+ } else {
+ port = _null;
+ host = port;
+ userInfo = "";
+ }
+ path = P._Uri__makePath(uri, pathStart, queryStart, _null, scheme, host != null);
+ query = queryStart < fragmentStart ? P._Uri__makeQuery(uri, queryStart + 1, fragmentStart, _null) : _null;
+ return new P._Uri(scheme, userInfo, host, port, path, query, fragmentStart < end ? P._Uri__makeFragment(uri, fragmentStart + 1, end) : _null);
+ },
+ _Uri__Uri: function(host, path, pathSegments, scheme) {
+ var userInfo, query, fragment, port, isFile, t1, hasAuthority, t2, _null = null;
+ scheme = P._Uri__makeScheme(scheme, 0, scheme == null ? 0 : scheme.length);
+ userInfo = P._Uri__makeUserInfo(_null, 0, 0);
+ host = P._Uri__makeHost(host, 0, host == null ? 0 : host.length, false);
+ query = P._Uri__makeQuery(_null, 0, 0, _null);
+ fragment = P._Uri__makeFragment(_null, 0, 0);
+ port = P._Uri__makePort(_null, scheme);
+ isFile = scheme === "file";
+ if (host == null)
+ t1 = userInfo.length !== 0 || port != null || isFile;
+ else
+ t1 = false;
+ if (t1)
+ host = "";
+ t1 = host == null;
+ hasAuthority = !t1;
+ path = P._Uri__makePath(path, 0, path == null ? 0 : path.length, pathSegments, scheme, hasAuthority);
+ t2 = scheme.length === 0;
+ if (t2 && t1 && !C.JSString_methods.startsWith$1(path, "/"))
+ path = P._Uri__normalizeRelativePath(path, !t2 || hasAuthority);
+ else
+ path = P._Uri__removeDotSegments(path);
+ return new P._Uri(scheme, userInfo, t1 && C.JSString_methods.startsWith$1(path, "//") ? "" : host, port, path, query, fragment);
+ },
+ _Uri__defaultPort: function(scheme) {
+ if (scheme === "http")
+ return 80;
+ if (scheme === "https")
+ return 443;
+ return 0;
+ },
+ _Uri__fail: function(uri, index, message) {
+ throw H.wrapException(P.FormatException$(message, uri, index));
+ },
+ _Uri__Uri$file: function(path, windows) {
+ return windows ? P._Uri__makeWindowsFileUrl(path, false) : P._Uri__makeFileUri(path, false);
+ },
+ _Uri__checkNonWindowsPathReservedCharacters: function(segments, argumentError) {
+ C.JSArray_methods.forEach$1(segments, new P._Uri__checkNonWindowsPathReservedCharacters_closure(false));
+ },
+ _Uri__checkWindowsPathReservedCharacters: function(segments, argumentError, firstSegment) {
+ var t1, t2;
+ for (t1 = H.SubListIterable$(segments, firstSegment, null, H._arrayInstanceType(segments)._precomputed1), t1 = new H.ListIterator(t1, t1.get$length(t1), t1.$ti._eval$1("ListIterator<ListIterable.E>")); t1.moveNext$0();) {
+ t2 = t1.__internal$_current;
+ if (J.contains$1$asx(t2, P.RegExp_RegExp('["*/:<>?\\\\|]', false)))
+ if (argumentError)
+ throw H.wrapException(P.ArgumentError$("Illegal character in path"));
+ else
+ throw H.wrapException(P.UnsupportedError$("Illegal character in path: " + t2));
+ }
+ },
+ _Uri__checkWindowsDriveLetter: function(charCode, argumentError) {
+ var t1,
+ _s21_ = "Illegal drive letter ";
+ if (!(65 <= charCode && charCode <= 90))
+ t1 = 97 <= charCode && charCode <= 122;
+ else
+ t1 = true;
+ if (t1)
+ return;
+ if (argumentError)
+ throw H.wrapException(P.ArgumentError$(_s21_ + P.String_String$fromCharCode(charCode)));
+ else
+ throw H.wrapException(P.UnsupportedError$(_s21_ + P.String_String$fromCharCode(charCode)));
+ },
+ _Uri__makeFileUri: function(path, slashTerminated) {
+ var _null = null,
+ segments = H.setRuntimeTypeInfo(path.split("/"), type$.JSArray_String);
+ if (C.JSString_methods.startsWith$1(path, "/"))
+ return P._Uri__Uri(_null, _null, segments, "file");
+ else
+ return P._Uri__Uri(_null, _null, segments, _null);
+ },
+ _Uri__makeWindowsFileUrl: function(path, slashTerminated) {
+ var t1, pathSegments, pathStart, hostPart, _s1_ = "\\", _null = null, _s4_ = "file";
+ if (C.JSString_methods.startsWith$1(path, "\\\\?\\"))
+ if (C.JSString_methods.startsWith$2(path, "UNC\\", 4))
+ path = C.JSString_methods.replaceRange$3(path, 0, 7, _s1_);
+ else {
+ path = C.JSString_methods.substring$1(path, 4);
+ if (path.length < 3 || C.JSString_methods._codeUnitAt$1(path, 1) !== 58 || C.JSString_methods._codeUnitAt$1(path, 2) !== 92)
+ throw H.wrapException(P.ArgumentError$("Windows paths with \\\\?\\ prefix must be absolute"));
+ }
+ else
+ path = H.stringReplaceAllUnchecked(path, "/", _s1_);
+ t1 = path.length;
+ if (t1 > 1 && C.JSString_methods._codeUnitAt$1(path, 1) === 58) {
+ P._Uri__checkWindowsDriveLetter(C.JSString_methods._codeUnitAt$1(path, 0), true);
+ if (t1 === 2 || C.JSString_methods._codeUnitAt$1(path, 2) !== 92)
+ throw H.wrapException(P.ArgumentError$("Windows paths with drive letter must be absolute"));
+ pathSegments = H.setRuntimeTypeInfo(path.split(_s1_), type$.JSArray_String);
+ P._Uri__checkWindowsPathReservedCharacters(pathSegments, true, 1);
+ return P._Uri__Uri(_null, _null, pathSegments, _s4_);
+ }
+ if (C.JSString_methods.startsWith$1(path, _s1_))
+ if (C.JSString_methods.startsWith$2(path, _s1_, 1)) {
+ pathStart = C.JSString_methods.indexOf$2(path, _s1_, 2);
+ t1 = pathStart < 0;
+ hostPart = t1 ? C.JSString_methods.substring$1(path, 2) : C.JSString_methods.substring$2(path, 2, pathStart);
+ pathSegments = H.setRuntimeTypeInfo((t1 ? "" : C.JSString_methods.substring$1(path, pathStart + 1)).split(_s1_), type$.JSArray_String);
+ P._Uri__checkWindowsPathReservedCharacters(pathSegments, true, 0);
+ return P._Uri__Uri(hostPart, _null, pathSegments, _s4_);
+ } else {
+ pathSegments = H.setRuntimeTypeInfo(path.split(_s1_), type$.JSArray_String);
+ P._Uri__checkWindowsPathReservedCharacters(pathSegments, true, 0);
+ return P._Uri__Uri(_null, _null, pathSegments, _s4_);
+ }
+ else {
+ pathSegments = H.setRuntimeTypeInfo(path.split(_s1_), type$.JSArray_String);
+ P._Uri__checkWindowsPathReservedCharacters(pathSegments, true, 0);
+ return P._Uri__Uri(_null, _null, pathSegments, _null);
+ }
+ },
+ _Uri__makePort: function(port, scheme) {
+ if (port != null && port === P._Uri__defaultPort(scheme))
+ return null;
+ return port;
+ },
+ _Uri__makeHost: function(host, start, end, strictIPv6) {
+ var t1, t2, index, zoneIDstart, zoneID, i;
+ if (host == null)
+ return null;
+ if (start === end)
+ return "";
+ if (C.JSString_methods.codeUnitAt$1(host, start) === 91) {
+ t1 = end - 1;
+ if (C.JSString_methods.codeUnitAt$1(host, t1) !== 93)
+ P._Uri__fail(host, start, "Missing end `]` to match `[` in host");
+ t2 = start + 1;
+ index = P._Uri__checkZoneID(host, t2, t1);
+ if (index < t1) {
+ zoneIDstart = index + 1;
+ zoneID = P._Uri__normalizeZoneID(host, C.JSString_methods.startsWith$2(host, "25", zoneIDstart) ? index + 3 : zoneIDstart, t1, "%25");
+ } else
+ zoneID = "";
+ P.Uri_parseIPv6Address(host, t2, index);
+ return C.JSString_methods.substring$2(host, start, index).toLowerCase() + zoneID + "]";
+ }
+ for (i = start; i < end; ++i)
+ if (C.JSString_methods.codeUnitAt$1(host, i) === 58) {
+ index = C.JSString_methods.indexOf$2(host, "%", start);
+ index = index >= start && index < end ? index : end;
+ if (index < end) {
+ zoneIDstart = index + 1;
+ zoneID = P._Uri__normalizeZoneID(host, C.JSString_methods.startsWith$2(host, "25", zoneIDstart) ? index + 3 : zoneIDstart, end, "%25");
+ } else
+ zoneID = "";
+ P.Uri_parseIPv6Address(host, start, index);
+ return "[" + C.JSString_methods.substring$2(host, start, index) + zoneID + "]";
+ }
+ return P._Uri__normalizeRegName(host, start, end);
+ },
+ _Uri__checkZoneID: function(host, start, end) {
+ var index = C.JSString_methods.indexOf$2(host, "%", start);
+ return index >= start && index < end ? index : end;
+ },
+ _Uri__normalizeZoneID: function(host, start, end, prefix) {
+ var index, sectionStart, isNormalized, char, replacement, t1, t2, tail, sourceLength,
+ buffer = prefix !== "" ? new P.StringBuffer(prefix) : null;
+ for (index = start, sectionStart = index, isNormalized = true; index < end;) {
+ char = C.JSString_methods.codeUnitAt$1(host, index);
+ if (char === 37) {
+ replacement = P._Uri__normalizeEscape(host, index, true);
+ t1 = replacement == null;
+ if (t1 && isNormalized) {
+ index += 3;
+ continue;
+ }
+ if (buffer == null)
+ buffer = new P.StringBuffer("");
+ t2 = buffer._contents += C.JSString_methods.substring$2(host, sectionStart, index);
+ if (t1)
+ replacement = C.JSString_methods.substring$2(host, index, index + 3);
+ else if (replacement === "%")
+ P._Uri__fail(host, index, "ZoneID should not contain % anymore");
+ buffer._contents = t2 + replacement;
+ index += 3;
+ sectionStart = index;
+ isNormalized = true;
+ } else {
+ if (char < 127) {
+ t1 = char >>> 4;
+ if (t1 >= 8)
+ return H.ioore(C.List_nxB, t1);
+ t1 = (C.List_nxB[t1] & 1 << (char & 15)) !== 0;
+ } else
+ t1 = false;
+ if (t1) {
+ if (isNormalized && 65 <= char && 90 >= char) {
+ if (buffer == null)
+ buffer = new P.StringBuffer("");
+ if (sectionStart < index) {
+ buffer._contents += C.JSString_methods.substring$2(host, sectionStart, index);
+ sectionStart = index;
+ }
+ isNormalized = false;
+ }
+ ++index;
+ } else {
+ if ((char & 64512) === 55296 && index + 1 < end) {
+ tail = C.JSString_methods.codeUnitAt$1(host, index + 1);
+ if ((tail & 64512) === 56320) {
+ char = 65536 | (char & 1023) << 10 | tail & 1023;
+ sourceLength = 2;
+ } else
+ sourceLength = 1;
+ } else
+ sourceLength = 1;
+ if (buffer == null)
+ buffer = new P.StringBuffer("");
+ buffer._contents += C.JSString_methods.substring$2(host, sectionStart, index);
+ buffer._contents += P._Uri__escapeChar(char);
+ index += sourceLength;
+ sectionStart = index;
+ }
+ }
+ }
+ if (buffer == null)
+ return C.JSString_methods.substring$2(host, start, end);
+ if (sectionStart < end)
+ buffer._contents += C.JSString_methods.substring$2(host, sectionStart, end);
+ t1 = buffer._contents;
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ _Uri__normalizeRegName: function(host, start, end) {
+ var index, sectionStart, buffer, isNormalized, char, replacement, t1, slice, t2, sourceLength, tail;
+ for (index = start, sectionStart = index, buffer = null, isNormalized = true; index < end;) {
+ char = C.JSString_methods.codeUnitAt$1(host, index);
+ if (char === 37) {
+ replacement = P._Uri__normalizeEscape(host, index, true);
+ t1 = replacement == null;
+ if (t1 && isNormalized) {
+ index += 3;
+ continue;
+ }
+ if (buffer == null)
+ buffer = new P.StringBuffer("");
+ slice = C.JSString_methods.substring$2(host, sectionStart, index);
+ t2 = buffer._contents += !isNormalized ? slice.toLowerCase() : slice;
+ if (t1) {
+ replacement = C.JSString_methods.substring$2(host, index, index + 3);
+ sourceLength = 3;
+ } else if (replacement === "%") {
+ replacement = "%25";
+ sourceLength = 1;
+ } else
+ sourceLength = 3;
+ buffer._contents = t2 + replacement;
+ index += sourceLength;
+ sectionStart = index;
+ isNormalized = true;
+ } else {
+ if (char < 127) {
+ t1 = char >>> 4;
+ if (t1 >= 8)
+ return H.ioore(C.List_qNA, t1);
+ t1 = (C.List_qNA[t1] & 1 << (char & 15)) !== 0;
+ } else
+ t1 = false;
+ if (t1) {
+ if (isNormalized && 65 <= char && 90 >= char) {
+ if (buffer == null)
+ buffer = new P.StringBuffer("");
+ if (sectionStart < index) {
+ buffer._contents += C.JSString_methods.substring$2(host, sectionStart, index);
+ sectionStart = index;
+ }
+ isNormalized = false;
+ }
+ ++index;
+ } else {
+ if (char <= 93) {
+ t1 = char >>> 4;
+ if (t1 >= 8)
+ return H.ioore(C.List_2Vk, t1);
+ t1 = (C.List_2Vk[t1] & 1 << (char & 15)) !== 0;
+ } else
+ t1 = false;
+ if (t1)
+ P._Uri__fail(host, index, "Invalid character");
+ else {
+ if ((char & 64512) === 55296 && index + 1 < end) {
+ tail = C.JSString_methods.codeUnitAt$1(host, index + 1);
+ if ((tail & 64512) === 56320) {
+ char = 65536 | (char & 1023) << 10 | tail & 1023;
+ sourceLength = 2;
+ } else
+ sourceLength = 1;
+ } else
+ sourceLength = 1;
+ if (buffer == null)
+ buffer = new P.StringBuffer("");
+ slice = C.JSString_methods.substring$2(host, sectionStart, index);
+ buffer._contents += !isNormalized ? slice.toLowerCase() : slice;
+ buffer._contents += P._Uri__escapeChar(char);
+ index += sourceLength;
+ sectionStart = index;
+ }
+ }
+ }
+ }
+ if (buffer == null)
+ return C.JSString_methods.substring$2(host, start, end);
+ if (sectionStart < end) {
+ slice = C.JSString_methods.substring$2(host, sectionStart, end);
+ buffer._contents += !isNormalized ? slice.toLowerCase() : slice;
+ }
+ t1 = buffer._contents;
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ _Uri__makeScheme: function(scheme, start, end) {
+ var i, containsUpperCase, codeUnit, t1;
+ if (start === end)
+ return "";
+ if (!P._Uri__isAlphabeticCharacter(J.getInterceptor$s(scheme)._codeUnitAt$1(scheme, start)))
+ P._Uri__fail(scheme, start, "Scheme not starting with alphabetic character");
+ for (i = start, containsUpperCase = false; i < end; ++i) {
+ codeUnit = C.JSString_methods._codeUnitAt$1(scheme, i);
+ if (codeUnit < 128) {
+ t1 = codeUnit >>> 4;
+ if (t1 >= 8)
+ return H.ioore(C.List_JYB, t1);
+ t1 = (C.List_JYB[t1] & 1 << (codeUnit & 15)) !== 0;
+ } else
+ t1 = false;
+ if (!t1)
+ P._Uri__fail(scheme, i, "Illegal scheme character");
+ if (65 <= codeUnit && codeUnit <= 90)
+ containsUpperCase = true;
+ }
+ scheme = C.JSString_methods.substring$2(scheme, start, end);
+ return P._Uri__canonicalizeScheme(containsUpperCase ? scheme.toLowerCase() : scheme);
+ },
+ _Uri__canonicalizeScheme: function(scheme) {
+ if (scheme === "http")
+ return "http";
+ if (scheme === "file")
+ return "file";
+ if (scheme === "https")
+ return "https";
+ if (scheme === "package")
+ return "package";
+ return scheme;
+ },
+ _Uri__makeUserInfo: function(userInfo, start, end) {
+ if (userInfo == null)
+ return "";
+ return P._Uri__normalizeOrSubstring(userInfo, start, end, C.List_gRj, false);
+ },
+ _Uri__makePath: function(path, start, end, pathSegments, scheme, hasAuthority) {
+ var result,
+ isFile = scheme === "file",
+ ensureLeadingSlash = isFile || hasAuthority,
+ t1 = path == null;
+ if (t1 && pathSegments == null)
+ return isFile ? "/" : "";
+ t1 = !t1;
+ if (t1 && pathSegments != null)
+ throw H.wrapException(P.ArgumentError$("Both path and pathSegments specified"));
+ if (t1)
+ result = P._Uri__normalizeOrSubstring(path, start, end, C.List_qg4, true);
+ else {
+ pathSegments.toString;
+ t1 = H._arrayInstanceType(pathSegments);
+ result = new H.MappedListIterable(pathSegments, t1._eval$1("String(1)")._check(new P._Uri__makePath_closure()), t1._eval$1("MappedListIterable<1,String>")).join$1(0, "/");
+ }
+ if (result.length === 0) {
+ if (isFile)
+ return "/";
+ } else if (ensureLeadingSlash && !C.JSString_methods.startsWith$1(result, "/"))
+ result = "/" + result;
+ return P._Uri__normalizePath(result, scheme, hasAuthority);
+ },
+ _Uri__normalizePath: function(path, scheme, hasAuthority) {
+ var t1 = scheme.length === 0;
+ if (t1 && !hasAuthority && !C.JSString_methods.startsWith$1(path, "/"))
+ return P._Uri__normalizeRelativePath(path, !t1 || hasAuthority);
+ return P._Uri__removeDotSegments(path);
+ },
+ _Uri__makeQuery: function(query, start, end, queryParameters) {
+ if (query != null)
+ return P._Uri__normalizeOrSubstring(query, start, end, C.List_CVk, true);
+ return null;
+ },
+ _Uri__makeFragment: function(fragment, start, end) {
+ if (fragment == null)
+ return null;
+ return P._Uri__normalizeOrSubstring(fragment, start, end, C.List_CVk, true);
+ },
+ _Uri__normalizeEscape: function(source, index, lowerCase) {
+ var t1, firstDigit, secondDigit, firstDigitValue, secondDigitValue, value;
+ H.assertHelper(C.JSString_methods.codeUnitAt$1(source, index) === 37);
+ t1 = index + 2;
+ if (t1 >= source.length)
+ return "%";
+ firstDigit = C.JSString_methods.codeUnitAt$1(source, index + 1);
+ secondDigit = C.JSString_methods.codeUnitAt$1(source, t1);
+ firstDigitValue = H.hexDigitValue(firstDigit);
+ secondDigitValue = H.hexDigitValue(secondDigit);
+ if (firstDigitValue < 0 || secondDigitValue < 0)
+ return "%";
+ value = firstDigitValue * 16 + secondDigitValue;
+ if (value < 127) {
+ t1 = C.JSInt_methods._shrOtherPositive$1(value, 4);
+ if (t1 >= 8)
+ return H.ioore(C.List_nxB, t1);
+ t1 = (C.List_nxB[t1] & 1 << (value & 15)) !== 0;
+ } else
+ t1 = false;
+ if (t1)
+ return H.Primitives_stringFromCharCode(lowerCase && 65 <= value && 90 >= value ? (value | 32) >>> 0 : value);
+ if (firstDigit >= 97 || secondDigit >= 97)
+ return C.JSString_methods.substring$2(source, index, index + 3).toUpperCase();
+ return null;
+ },
+ _Uri__escapeChar: function(char) {
+ var t1, codeUnits, flag, encodedBytes, index, byte,
+ _s16_ = "0123456789ABCDEF";
+ H.assertHelper(char <= 1114111);
+ if (char < 128) {
+ t1 = new Array(3);
+ t1.fixed$length = Array;
+ codeUnits = H.setRuntimeTypeInfo(t1, type$.JSArray_int);
+ C.JSArray_methods.$indexSet(codeUnits, 0, 37);
+ C.JSArray_methods.$indexSet(codeUnits, 1, C.JSString_methods._codeUnitAt$1(_s16_, char >>> 4));
+ C.JSArray_methods.$indexSet(codeUnits, 2, C.JSString_methods._codeUnitAt$1(_s16_, char & 15));
+ } else {
+ if (char > 2047)
+ if (char > 65535) {
+ flag = 240;
+ encodedBytes = 4;
+ } else {
+ flag = 224;
+ encodedBytes = 3;
+ }
+ else {
+ flag = 192;
+ encodedBytes = 2;
+ }
+ t1 = new Array(3 * encodedBytes);
+ t1.fixed$length = Array;
+ codeUnits = H.setRuntimeTypeInfo(t1, type$.JSArray_int);
+ for (index = 0; --encodedBytes, encodedBytes >= 0; flag = 128) {
+ byte = C.JSInt_methods._shrReceiverPositive$1(char, 6 * encodedBytes) & 63 | flag;
+ C.JSArray_methods.$indexSet(codeUnits, index, 37);
+ C.JSArray_methods.$indexSet(codeUnits, index + 1, C.JSString_methods._codeUnitAt$1(_s16_, byte >>> 4));
+ C.JSArray_methods.$indexSet(codeUnits, index + 2, C.JSString_methods._codeUnitAt$1(_s16_, byte & 15));
+ index += 3;
+ }
+ }
+ return P.String_String$fromCharCodes(codeUnits, 0, null);
+ },
+ _Uri__normalizeOrSubstring: function(component, start, end, charTable, escapeDelimiters) {
+ var t1 = P._Uri__normalize(component, start, end, charTable, escapeDelimiters);
+ return t1 == null ? C.JSString_methods.substring$2(component, start, end) : t1;
+ },
+ _Uri__normalize: function(component, start, end, charTable, escapeDelimiters) {
+ var t1, index, sectionStart, buffer, char, t2, replacement, sourceLength, tail, _null = null;
+ for (t1 = !escapeDelimiters, index = start, sectionStart = index, buffer = _null; index < end;) {
+ char = C.JSString_methods.codeUnitAt$1(component, index);
+ if (char < 127) {
+ t2 = char >>> 4;
+ if (t2 >= 8)
+ return H.ioore(charTable, t2);
+ t2 = (charTable[t2] & 1 << (char & 15)) !== 0;
+ } else
+ t2 = false;
+ if (t2)
+ ++index;
+ else {
+ if (char === 37) {
+ replacement = P._Uri__normalizeEscape(component, index, false);
+ if (replacement == null) {
+ index += 3;
+ continue;
+ }
+ if ("%" === replacement) {
+ replacement = "%25";
+ sourceLength = 1;
+ } else
+ sourceLength = 3;
+ } else {
+ if (t1)
+ if (char <= 93) {
+ t2 = char >>> 4;
+ if (t2 >= 8)
+ return H.ioore(C.List_2Vk, t2);
+ t2 = (C.List_2Vk[t2] & 1 << (char & 15)) !== 0;
+ } else
+ t2 = false;
+ else
+ t2 = false;
+ if (t2) {
+ P._Uri__fail(component, index, "Invalid character");
+ sourceLength = _null;
+ replacement = sourceLength;
+ } else {
+ if ((char & 64512) === 55296) {
+ t2 = index + 1;
+ if (t2 < end) {
+ tail = C.JSString_methods.codeUnitAt$1(component, t2);
+ if ((tail & 64512) === 56320) {
+ char = 65536 | (char & 1023) << 10 | tail & 1023;
+ sourceLength = 2;
+ } else
+ sourceLength = 1;
+ } else
+ sourceLength = 1;
+ } else
+ sourceLength = 1;
+ replacement = P._Uri__escapeChar(char);
+ }
+ }
+ if (buffer == null)
+ buffer = new P.StringBuffer("");
+ buffer._contents += C.JSString_methods.substring$2(component, sectionStart, index);
+ buffer._contents += H.S(replacement);
+ if (typeof sourceLength !== "number")
+ return H.iae(sourceLength);
+ index += sourceLength;
+ sectionStart = index;
+ }
+ }
+ if (buffer == null)
+ return _null;
+ if (sectionStart < end)
+ buffer._contents += C.JSString_methods.substring$2(component, sectionStart, end);
+ t1 = buffer._contents;
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ _Uri__mayContainDotSegments: function(path) {
+ if (C.JSString_methods.startsWith$1(path, "."))
+ return true;
+ return C.JSString_methods.indexOf$1(path, "/.") !== -1;
+ },
+ _Uri__removeDotSegments: function(path) {
+ var output, t1, t2, appendSlash, _i, segment, t3;
+ if (!P._Uri__mayContainDotSegments(path))
+ return path;
+ H.assertHelper(path.length !== 0);
+ output = H.setRuntimeTypeInfo([], type$.JSArray_String);
+ for (t1 = path.split("/"), t2 = t1.length, appendSlash = false, _i = 0; _i < t2; ++_i) {
+ segment = t1[_i];
+ if (J.$eq$(segment, "..")) {
+ t3 = output.length;
+ if (t3 !== 0) {
+ if (0 >= t3)
+ return H.ioore(output, -1);
+ output.pop();
+ if (output.length === 0)
+ C.JSArray_methods.add$1(output, "");
+ }
+ appendSlash = true;
+ } else if ("." === segment)
+ appendSlash = true;
+ else {
+ C.JSArray_methods.add$1(output, segment);
+ appendSlash = false;
+ }
+ }
+ if (appendSlash)
+ C.JSArray_methods.add$1(output, "");
+ return C.JSArray_methods.join$1(output, "/");
+ },
+ _Uri__normalizeRelativePath: function(path, allowScheme) {
+ var output, t1, t2, appendSlash, _i, segment;
+ H.assertHelper(!C.JSString_methods.startsWith$1(path, "/"));
+ if (!P._Uri__mayContainDotSegments(path))
+ return !allowScheme ? P._Uri__escapeScheme(path) : path;
+ H.assertHelper(path.length !== 0);
+ output = H.setRuntimeTypeInfo([], type$.JSArray_String);
+ for (t1 = path.split("/"), t2 = t1.length, appendSlash = false, _i = 0; _i < t2; ++_i) {
+ segment = t1[_i];
+ if (".." === segment)
+ if (output.length !== 0 && C.JSArray_methods.get$last(output) !== "..") {
+ if (0 >= output.length)
+ return H.ioore(output, -1);
+ output.pop();
+ appendSlash = true;
+ } else {
+ C.JSArray_methods.add$1(output, "..");
+ appendSlash = false;
+ }
+ else if ("." === segment)
+ appendSlash = true;
+ else {
+ C.JSArray_methods.add$1(output, segment);
+ appendSlash = false;
+ }
+ }
+ t1 = output.length;
+ if (t1 !== 0)
+ if (t1 === 1) {
+ if (0 >= t1)
+ return H.ioore(output, 0);
+ t1 = output[0].length === 0;
+ } else
+ t1 = false;
+ else
+ t1 = true;
+ if (t1)
+ return "./";
+ if (appendSlash || C.JSArray_methods.get$last(output) === "..")
+ C.JSArray_methods.add$1(output, "");
+ if (!allowScheme) {
+ if (0 >= output.length)
+ return H.ioore(output, 0);
+ C.JSArray_methods.$indexSet(output, 0, P._Uri__escapeScheme(output[0]));
+ }
+ return C.JSArray_methods.join$1(output, "/");
+ },
+ _Uri__escapeScheme: function(path) {
+ var i, char, t2,
+ t1 = path.length;
+ if (t1 >= 2 && P._Uri__isAlphabeticCharacter(J._codeUnitAt$1$s(path, 0)))
+ for (i = 1; i < t1; ++i) {
+ char = C.JSString_methods._codeUnitAt$1(path, i);
+ if (char === 58)
+ return C.JSString_methods.substring$2(path, 0, i) + "%3A" + C.JSString_methods.substring$1(path, i + 1);
+ if (char <= 127) {
+ t2 = char >>> 4;
+ if (t2 >= 8)
+ return H.ioore(C.List_JYB, t2);
+ t2 = (C.List_JYB[t2] & 1 << (char & 15)) === 0;
+ } else
+ t2 = true;
+ if (t2)
+ break;
+ }
+ return path;
+ },
+ _Uri__toWindowsFilePath: function(uri) {
+ var hasDriveLetter, t2, host,
+ segments = uri.get$pathSegments(),
+ t1 = segments.length;
+ if (t1 > 0 && J.get$length$asx(segments[0]) === 2 && J.codeUnitAt$1$s(segments[0], 1) === 58) {
+ if (0 >= t1)
+ return H.ioore(segments, 0);
+ P._Uri__checkWindowsDriveLetter(J.codeUnitAt$1$s(segments[0], 0), false);
+ P._Uri__checkWindowsPathReservedCharacters(segments, false, 1);
+ hasDriveLetter = true;
+ } else {
+ P._Uri__checkWindowsPathReservedCharacters(segments, false, 0);
+ hasDriveLetter = false;
+ }
+ t2 = uri.get$hasAbsolutePath() && !hasDriveLetter ? "\\" : "";
+ if (uri.get$hasAuthority()) {
+ host = uri.get$host(uri);
+ if (host.length !== 0)
+ t2 = t2 + "\\" + host + "\\";
+ }
+ t2 = P.StringBuffer__writeAll(t2, segments, "\\");
+ t1 = hasDriveLetter && t1 === 1 ? t2 + "\\" : t2;
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ _Uri__hexCharPairToByte: function(s, pos) {
+ var byte, i, charCode;
+ for (byte = 0, i = 0; i < 2; ++i) {
+ charCode = C.JSString_methods._codeUnitAt$1(s, pos + i);
+ if (48 <= charCode && charCode <= 57)
+ byte = byte * 16 + charCode - 48;
+ else {
+ charCode |= 32;
+ if (97 <= charCode && charCode <= 102)
+ byte = byte * 16 + charCode - 87;
+ else
+ throw H.wrapException(P.ArgumentError$("Invalid URL encoding"));
+ }
+ }
+ return byte;
+ },
+ _Uri__uriDecode: function(text, start, end, encoding, plusToSpace) {
+ var t1, simple, t2, i, codeUnit, t3, bytes;
+ H.assertHelper(start <= end);
+ t1 = text.length;
+ H.assertHelper(end <= t1);
+ t2 = J.getInterceptor$s(text);
+ i = start;
+ while (true) {
+ if (!(i < end)) {
+ simple = true;
+ break;
+ }
+ codeUnit = t2._codeUnitAt$1(text, i);
+ if (codeUnit <= 127)
+ if (codeUnit !== 37)
+ t3 = false;
+ else
+ t3 = true;
+ else
+ t3 = true;
+ if (t3) {
+ simple = false;
+ break;
+ }
+ ++i;
+ }
+ if (simple) {
+ if (C.C_Utf8Codec !== encoding)
+ t1 = false;
+ else
+ t1 = true;
+ if (t1)
+ return t2.substring$2(text, start, end);
+ else
+ bytes = new H.CodeUnits(t2.substring$2(text, start, end));
+ } else {
+ bytes = H.setRuntimeTypeInfo([], type$.JSArray_int);
+ for (i = start; i < end; ++i) {
+ codeUnit = t2._codeUnitAt$1(text, i);
+ if (codeUnit > 127)
+ throw H.wrapException(P.ArgumentError$("Illegal percent encoding in URI"));
+ if (codeUnit === 37) {
+ if (i + 3 > t1)
+ throw H.wrapException(P.ArgumentError$("Truncated URI"));
+ C.JSArray_methods.add$1(bytes, P._Uri__hexCharPairToByte(text, i + 1));
+ i += 2;
+ } else
+ C.JSArray_methods.add$1(bytes, codeUnit);
+ }
+ }
+ type$.List_int._check(bytes);
+ return new P.Utf8Decoder(false).convert$1(bytes);
+ },
+ _Uri__isAlphabeticCharacter: function(codeUnit) {
+ var lowerCase = codeUnit | 32;
+ return 97 <= lowerCase && lowerCase <= 122;
+ },
+ UriData__writeUri: function(mimeType, charsetName, parameters, buffer, indices) {
+ var slashIndex, t1;
+ if (true)
+ buffer._contents = buffer._contents;
+ else {
+ slashIndex = P.UriData__validateMimeType("");
+ if (slashIndex < 0)
+ throw H.wrapException(P.ArgumentError$value("", "mimeType", "Invalid MIME type"));
+ t1 = buffer._contents += H.S(P._Uri__uriEncode(C.List_qFt, C.JSString_methods.substring$2("", 0, slashIndex), C.C_Utf8Codec, false));
+ buffer._contents = t1 + "/";
+ buffer._contents += H.S(P._Uri__uriEncode(C.List_qFt, C.JSString_methods.substring$1("", slashIndex + 1), C.C_Utf8Codec, false));
+ }
+ },
+ UriData__validateMimeType: function(mimeType) {
+ var t1, slashIndex, i;
+ for (t1 = mimeType.length, slashIndex = -1, i = 0; i < t1; ++i) {
+ if (C.JSString_methods._codeUnitAt$1(mimeType, i) !== 47)
+ continue;
+ if (slashIndex < 0) {
+ slashIndex = i;
+ continue;
+ }
+ return -1;
+ }
+ return slashIndex;
+ },
+ UriData__parse: function(text, start, sourceUri) {
+ var indices, t1, i, slashIndex, char, equalsIndex, lastSeparator, t2, data,
+ _s17_ = "Invalid MIME type";
+ H.assertHelper(start === 0 || start === 5);
+ H.assertHelper(start === 5 === C.JSString_methods.startsWith$1(text, "data:"));
+ indices = H.setRuntimeTypeInfo([start - 1], type$.JSArray_int);
+ for (t1 = text.length, i = start, slashIndex = -1, char = null; i < t1; ++i) {
+ char = C.JSString_methods._codeUnitAt$1(text, i);
+ if (char === 44 || char === 59)
+ break;
+ if (char === 47) {
+ if (slashIndex < 0) {
+ slashIndex = i;
+ continue;
+ }
+ throw H.wrapException(P.FormatException$(_s17_, text, i));
+ }
+ }
+ if (slashIndex < 0 && i > start)
+ throw H.wrapException(P.FormatException$(_s17_, text, i));
+ for (; char !== 44;) {
+ C.JSArray_methods.add$1(indices, i);
+ ++i;
+ for (equalsIndex = -1; i < t1; ++i) {
+ char = C.JSString_methods._codeUnitAt$1(text, i);
+ if (char === 61) {
+ if (equalsIndex < 0)
+ equalsIndex = i;
+ } else if (char === 59 || char === 44)
+ break;
+ }
+ if (equalsIndex >= 0)
+ C.JSArray_methods.add$1(indices, equalsIndex);
+ else {
+ lastSeparator = C.JSArray_methods.get$last(indices);
+ if (char !== 44 || i !== lastSeparator + 7 || !C.JSString_methods.startsWith$2(text, "base64", lastSeparator + 1))
+ throw H.wrapException(P.FormatException$("Expecting '='", text, i));
+ break;
+ }
+ }
+ C.JSArray_methods.add$1(indices, i);
+ t2 = i + 1;
+ if ((indices.length & 1) === 1)
+ text = C.C_Base64Codec.normalize$3(text, t2, t1);
+ else {
+ data = P._Uri__normalize(text, t2, t1, C.List_CVk, true);
+ if (data != null)
+ text = C.JSString_methods.replaceRange$3(text, t2, t1, data);
+ }
+ return new P.UriData(text, indices, sourceUri);
+ },
+ UriData__uriEncodeBytes: function(canonicalTable, bytes, buffer) {
+ var t1, byteOr, i, byte, t2, t3,
+ _s16_ = "0123456789ABCDEF";
+ for (t1 = J.getInterceptor$asx(bytes), byteOr = 0, i = 0; i < t1.get$length(bytes); ++i) {
+ byte = t1.$index(bytes, i);
+ if (typeof byte !== "number")
+ return H.iae(byte);
+ byteOr |= byte;
+ if (byte < 128) {
+ t2 = C.JSInt_methods._shrOtherPositive$1(byte, 4);
+ if (t2 >= 8)
+ return H.ioore(canonicalTable, t2);
+ t2 = (canonicalTable[t2] & 1 << (byte & 15)) !== 0;
+ } else
+ t2 = false;
+ t3 = buffer._contents;
+ if (t2)
+ buffer._contents = t3 + H.Primitives_stringFromCharCode(byte);
+ else {
+ t2 = t3 + H.Primitives_stringFromCharCode(37);
+ buffer._contents = t2;
+ t2 += H.Primitives_stringFromCharCode(C.JSString_methods._codeUnitAt$1(_s16_, C.JSInt_methods._shrOtherPositive$1(byte, 4)));
+ buffer._contents = t2;
+ buffer._contents = t2 + H.Primitives_stringFromCharCode(C.JSString_methods._codeUnitAt$1(_s16_, byte & 15));
+ }
+ }
+ if ((byteOr & 4294967040) >>> 0 !== 0)
+ for (i = 0; i < t1.get$length(bytes); ++i) {
+ byte = t1.$index(bytes, i);
+ if (typeof byte !== "number")
+ return byte.$lt();
+ if (byte < 0 || byte > 255)
+ throw H.wrapException(P.ArgumentError$value(byte, "non-byte value", null));
+ }
+ },
+ _createTables: function() {
+ var _s77_ = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-._~!$&'()*+,;=",
+ _s1_ = ".", _s1_0 = ":", _s1_1 = "/", _s1_2 = "?", _s1_3 = "#",
+ t1 = type$.Uint8List,
+ tables = P.List_List$generate(22, new P._createTables_closure(), true, t1),
+ t2 = new P._createTables_build(tables),
+ t3 = new P._createTables_setChars(),
+ t4 = new P._createTables_setRange(),
+ t5 = t1._check(t2.call$2(0, 225));
+ t3.call$3(t5, _s77_, 1);
+ t3.call$3(t5, _s1_, 14);
+ t3.call$3(t5, _s1_0, 34);
+ t3.call$3(t5, _s1_1, 3);
+ t3.call$3(t5, _s1_2, 172);
+ t3.call$3(t5, _s1_3, 205);
+ t5 = t1._check(t2.call$2(14, 225));
+ t3.call$3(t5, _s77_, 1);
+ t3.call$3(t5, _s1_, 15);
+ t3.call$3(t5, _s1_0, 34);
+ t3.call$3(t5, _s1_1, 234);
+ t3.call$3(t5, _s1_2, 172);
+ t3.call$3(t5, _s1_3, 205);
+ t5 = t1._check(t2.call$2(15, 225));
+ t3.call$3(t5, _s77_, 1);
+ t3.call$3(t5, "%", 225);
+ t3.call$3(t5, _s1_0, 34);
+ t3.call$3(t5, _s1_1, 9);
+ t3.call$3(t5, _s1_2, 172);
+ t3.call$3(t5, _s1_3, 205);
+ t5 = t1._check(t2.call$2(1, 225));
+ t3.call$3(t5, _s77_, 1);
+ t3.call$3(t5, _s1_0, 34);
+ t3.call$3(t5, _s1_1, 10);
+ t3.call$3(t5, _s1_2, 172);
+ t3.call$3(t5, _s1_3, 205);
+ t5 = t1._check(t2.call$2(2, 235));
+ t3.call$3(t5, _s77_, 139);
+ t3.call$3(t5, _s1_1, 131);
+ t3.call$3(t5, _s1_, 146);
+ t3.call$3(t5, _s1_2, 172);
+ t3.call$3(t5, _s1_3, 205);
+ t5 = t1._check(t2.call$2(3, 235));
+ t3.call$3(t5, _s77_, 11);
+ t3.call$3(t5, _s1_1, 68);
+ t3.call$3(t5, _s1_, 18);
+ t3.call$3(t5, _s1_2, 172);
+ t3.call$3(t5, _s1_3, 205);
+ t5 = t1._check(t2.call$2(4, 229));
+ t3.call$3(t5, _s77_, 5);
+ t4.call$3(t5, "AZ", 229);
+ t3.call$3(t5, _s1_0, 102);
+ t3.call$3(t5, "@", 68);
+ t3.call$3(t5, "[", 232);
+ t3.call$3(t5, _s1_1, 138);
+ t3.call$3(t5, _s1_2, 172);
+ t3.call$3(t5, _s1_3, 205);
+ t5 = t1._check(t2.call$2(5, 229));
+ t3.call$3(t5, _s77_, 5);
+ t4.call$3(t5, "AZ", 229);
+ t3.call$3(t5, _s1_0, 102);
+ t3.call$3(t5, "@", 68);
+ t3.call$3(t5, _s1_1, 138);
+ t3.call$3(t5, _s1_2, 172);
+ t3.call$3(t5, _s1_3, 205);
+ t5 = t1._check(t2.call$2(6, 231));
+ t4.call$3(t5, "19", 7);
+ t3.call$3(t5, "@", 68);
+ t3.call$3(t5, _s1_1, 138);
+ t3.call$3(t5, _s1_2, 172);
+ t3.call$3(t5, _s1_3, 205);
+ t5 = t1._check(t2.call$2(7, 231));
+ t4.call$3(t5, "09", 7);
+ t3.call$3(t5, "@", 68);
+ t3.call$3(t5, _s1_1, 138);
+ t3.call$3(t5, _s1_2, 172);
+ t3.call$3(t5, _s1_3, 205);
+ t3.call$3(t1._check(t2.call$2(8, 8)), "]", 5);
+ t5 = t1._check(t2.call$2(9, 235));
+ t3.call$3(t5, _s77_, 11);
+ t3.call$3(t5, _s1_, 16);
+ t3.call$3(t5, _s1_1, 234);
+ t3.call$3(t5, _s1_2, 172);
+ t3.call$3(t5, _s1_3, 205);
+ t5 = t1._check(t2.call$2(16, 235));
+ t3.call$3(t5, _s77_, 11);
+ t3.call$3(t5, _s1_, 17);
+ t3.call$3(t5, _s1_1, 234);
+ t3.call$3(t5, _s1_2, 172);
+ t3.call$3(t5, _s1_3, 205);
+ t5 = t1._check(t2.call$2(17, 235));
+ t3.call$3(t5, _s77_, 11);
+ t3.call$3(t5, _s1_1, 9);
+ t3.call$3(t5, _s1_2, 172);
+ t3.call$3(t5, _s1_3, 205);
+ t5 = t1._check(t2.call$2(10, 235));
+ t3.call$3(t5, _s77_, 11);
+ t3.call$3(t5, _s1_, 18);
+ t3.call$3(t5, _s1_1, 234);
+ t3.call$3(t5, _s1_2, 172);
+ t3.call$3(t5, _s1_3, 205);
+ t5 = t1._check(t2.call$2(18, 235));
+ t3.call$3(t5, _s77_, 11);
+ t3.call$3(t5, _s1_, 19);
+ t3.call$3(t5, _s1_1, 234);
+ t3.call$3(t5, _s1_2, 172);
+ t3.call$3(t5, _s1_3, 205);
+ t5 = t1._check(t2.call$2(19, 235));
+ t3.call$3(t5, _s77_, 11);
+ t3.call$3(t5, _s1_1, 234);
+ t3.call$3(t5, _s1_2, 172);
+ t3.call$3(t5, _s1_3, 205);
+ t5 = t1._check(t2.call$2(11, 235));
+ t3.call$3(t5, _s77_, 11);
+ t3.call$3(t5, _s1_1, 10);
+ t3.call$3(t5, _s1_2, 172);
+ t3.call$3(t5, _s1_3, 205);
+ t5 = t1._check(t2.call$2(12, 236));
+ t3.call$3(t5, _s77_, 12);
+ t3.call$3(t5, _s1_2, 12);
+ t3.call$3(t5, _s1_3, 205);
+ t5 = t1._check(t2.call$2(13, 237));
+ t3.call$3(t5, _s77_, 13);
+ t3.call$3(t5, _s1_2, 13);
+ t4.call$3(t1._check(t2.call$2(20, 245)), "az", 21);
+ t2 = t1._check(t2.call$2(21, 245));
+ t4.call$3(t2, "az", 21);
+ t4.call$3(t2, "09", 21);
+ t3.call$3(t2, "+-.", 21);
+ return tables;
+ },
+ _scan: function(uri, start, end, state, indices) {
+ var t1, i, table, char, transition,
+ tables = $.$get$_scannerTables();
+ H.assertHelper(end <= uri.length);
+ for (t1 = J.getInterceptor$s(uri), i = start; i < end; ++i) {
+ if (state < 0 || state >= tables.length)
+ return H.ioore(tables, state);
+ table = tables[state];
+ char = t1._codeUnitAt$1(uri, i) ^ 96;
+ if (char > 95)
+ char = 31;
+ if (char >= table.length)
+ return H.ioore(table, char);
+ transition = table[char];
+ state = transition & 31;
+ C.JSArray_methods.$indexSet(indices, transition >>> 5, i);
+ }
+ return state;
+ },
+ bool: function bool() {
+ },
+ DateTime: function DateTime(t0, t1) {
+ this._value = t0;
+ this.isUtc = t1;
+ },
+ double: function double() {
+ },
+ Duration: function Duration(t0) {
+ this._duration = t0;
+ },
+ Duration_toString_sixDigits: function Duration_toString_sixDigits() {
+ },
+ Duration_toString_twoDigits: function Duration_toString_twoDigits() {
+ },
+ Error: function Error() {
+ },
+ AssertionError: function AssertionError(t0) {
+ this.message = t0;
+ },
+ NullThrownError: function NullThrownError() {
+ },
+ ArgumentError: function ArgumentError(t0, t1, t2, t3) {
+ var _ = this;
+ _._hasValue = t0;
+ _.invalidValue = t1;
+ _.name = t2;
+ _.message = t3;
+ },
+ RangeError: function RangeError(t0, t1, t2, t3, t4, t5) {
+ var _ = this;
+ _.start = t0;
+ _.end = t1;
+ _._hasValue = t2;
+ _.invalidValue = t3;
+ _.name = t4;
+ _.message = t5;
+ },
+ IndexError: function IndexError(t0, t1, t2, t3, t4) {
+ var _ = this;
+ _.length = t0;
+ _._hasValue = t1;
+ _.invalidValue = t2;
+ _.name = t3;
+ _.message = t4;
+ },
+ UnsupportedError: function UnsupportedError(t0) {
+ this.message = t0;
+ },
+ UnimplementedError: function UnimplementedError(t0) {
+ this.message = t0;
+ },
+ StateError: function StateError(t0) {
+ this.message = t0;
+ },
+ ConcurrentModificationError: function ConcurrentModificationError(t0) {
+ this.modifiedObject = t0;
+ },
+ OutOfMemoryError: function OutOfMemoryError() {
+ },
+ StackOverflowError: function StackOverflowError() {
+ },
+ CyclicInitializationError: function CyclicInitializationError(t0) {
+ this.variableName = t0;
+ },
+ _Exception: function _Exception(t0) {
+ this.message = t0;
+ },
+ FormatException: function FormatException(t0, t1, t2) {
+ this.message = t0;
+ this.source = t1;
+ this.offset = t2;
+ },
+ Expando: function Expando(t0, t1, t2) {
+ this._jsWeakMapOrKey = t0;
+ this.name = t1;
+ this.$ti = t2;
+ },
+ Function: function Function() {
+ },
+ int: function int() {
+ },
+ Iterable: function Iterable() {
+ },
+ Iterator: function Iterator() {
+ },
+ List: function List() {
+ },
+ Map: function Map() {
+ },
+ MapEntry: function MapEntry(t0, t1, t2) {
+ this.key = t0;
+ this.value = t1;
+ this.$ti = t2;
+ },
+ Null: function Null() {
+ },
+ num: function num() {
+ },
+ Object: function Object() {
+ },
+ Pattern: function Pattern() {
+ },
+ Match: function Match() {
+ },
+ RegExpMatch: function RegExpMatch() {
+ },
+ Set: function Set() {
+ },
+ StackTrace: function StackTrace() {
+ },
+ _StringStackTrace: function _StringStackTrace(t0) {
+ this._stackTrace = t0;
+ },
+ Stopwatch: function Stopwatch() {
+ this._stop = this._core$_start = 0;
+ },
+ String: function String() {
+ },
+ Runes: function Runes(t0) {
+ this.string = t0;
+ },
+ RuneIterator: function RuneIterator(t0) {
+ var _ = this;
+ _.string = t0;
+ _._nextPosition = _._core$_position = 0;
+ _._currentCodePoint = null;
+ },
+ StringBuffer: function StringBuffer(t0) {
+ this._contents = t0;
+ },
+ Uri: function Uri() {
+ },
+ Uri__parseIPv4Address_error: function Uri__parseIPv4Address_error(t0) {
+ this.host = t0;
+ },
+ Uri_parseIPv6Address_error: function Uri_parseIPv6Address_error(t0) {
+ this.host = t0;
+ },
+ Uri_parseIPv6Address_parseHex: function Uri_parseIPv6Address_parseHex(t0, t1) {
+ this.error = t0;
+ this.host = t1;
+ },
+ _Uri: function _Uri(t0, t1, t2, t3, t4, t5, t6) {
+ var _ = this;
+ _.scheme = t0;
+ _._userInfo = t1;
+ _._host = t2;
+ _._port = t3;
+ _.path = t4;
+ _._query = t5;
+ _._fragment = t6;
+ _._hashCodeCache = _._text = _._pathSegments = null;
+ },
+ _Uri__Uri$notSimple_closure: function _Uri__Uri$notSimple_closure(t0, t1) {
+ this.uri = t0;
+ this.portStart = t1;
+ },
+ _Uri__checkNonWindowsPathReservedCharacters_closure: function _Uri__checkNonWindowsPathReservedCharacters_closure(t0) {
+ this.argumentError = t0;
+ },
+ _Uri__makePath_closure: function _Uri__makePath_closure() {
+ },
+ UriData: function UriData(t0, t1, t2) {
+ this._text = t0;
+ this._separatorIndices = t1;
+ this._uriCache = t2;
+ },
+ _createTables_closure: function _createTables_closure() {
+ },
+ _createTables_build: function _createTables_build(t0) {
+ this.tables = t0;
+ },
+ _createTables_setChars: function _createTables_setChars() {
+ },
+ _createTables_setRange: function _createTables_setRange() {
+ },
+ _SimpleUri: function _SimpleUri(t0, t1, t2, t3, t4, t5, t6, t7) {
+ var _ = this;
+ _._uri = t0;
+ _._schemeEnd = t1;
+ _._hostStart = t2;
+ _._portStart = t3;
+ _._pathStart = t4;
+ _._queryStart = t5;
+ _._fragmentStart = t6;
+ _._schemeCache = t7;
+ _._hashCodeCache = null;
+ },
+ _DataUri: function _DataUri(t0, t1, t2, t3, t4, t5, t6) {
+ var _ = this;
+ _.scheme = t0;
+ _._userInfo = t1;
+ _._host = t2;
+ _._port = t3;
+ _.path = t4;
+ _._query = t5;
+ _._fragment = t6;
+ _._hashCodeCache = _._text = _._pathSegments = null;
+ },
+ _StructuredClone: function _StructuredClone() {
+ },
+ _StructuredClone_walk_closure: function _StructuredClone_walk_closure(t0, t1) {
+ this._box_0 = t0;
+ this.$this = t1;
+ },
+ _StructuredClone_walk_closure0: function _StructuredClone_walk_closure0(t0, t1) {
+ this._box_0 = t0;
+ this.$this = t1;
+ },
+ _AcceptStructuredClone: function _AcceptStructuredClone() {
+ },
+ _AcceptStructuredClone_walk_closure: function _AcceptStructuredClone_walk_closure(t0, t1) {
+ this._box_0 = t0;
+ this.$this = t1;
+ },
+ _StructuredCloneDart2Js: function _StructuredCloneDart2Js(t0, t1) {
+ this.values = t0;
+ this.copies = t1;
+ },
+ _AcceptStructuredCloneDart2Js: function _AcceptStructuredCloneDart2Js(t0, t1) {
+ this.values = t0;
+ this.copies = t1;
+ this.mustCopy = false;
+ },
+ _convertDataTree: function(data) {
+ return new P._convertDataTree__convert(new P._IdentityHashMap(type$._IdentityHashMap_dynamic_dynamic)).call$1(data);
+ },
+ promiseToFuture: function(jsPromise, $T) {
+ var t1 = new P._Future($.Zone__current, $T._eval$1("_Future<0>")),
+ completer = new P._AsyncCompleter(t1, $T._eval$1("_AsyncCompleter<0>"));
+ jsPromise.then(H.convertDartClosureToJS(new P.promiseToFuture_closure(completer, $T), 1), H.convertDartClosureToJS(new P.promiseToFuture_closure0(completer), 1));
+ return t1;
+ },
+ _convertDataTree__convert: function _convertDataTree__convert(t0) {
+ this._convertedObjects = t0;
+ },
+ promiseToFuture_closure: function promiseToFuture_closure(t0, t1) {
+ this.completer = t0;
+ this.T = t1;
+ },
+ promiseToFuture_closure0: function promiseToFuture_closure0(t0) {
+ this.completer = t0;
+ },
+ ByteBuffer: function ByteBuffer() {
+ },
+ ByteData: function ByteData() {
+ },
+ Int8List: function Int8List() {
+ },
+ Uint8List: function Uint8List() {
+ },
+ Uint8ClampedList: function Uint8ClampedList() {
+ },
+ Int16List: function Int16List() {
+ },
+ Uint16List: function Uint16List() {
+ },
+ Int32List: function Int32List() {
+ },
+ Uint32List: function Uint32List() {
+ },
+ Float32List: function Float32List() {
+ },
+ Float64List: function Float64List() {
+ },
+ SqlError: function SqlError() {
+ },
+ max: function(a, b, $T) {
+ H.checkTypeBound($T, type$.num, "T", "max");
+ $T._check(a);
+ $T._check(b);
+ return Math.max(H.checkNum(a), H.checkNum(b));
+ },
+ pow: function(x, exponent) {
+ return Math.pow(x, exponent);
+ }
+ },
+ W = {
+ _EventStreamSubscription$: function(_target, _eventType, onData, _useCapture, $T) {
+ var t1 = onData == null ? null : W._wrapZone(new W._EventStreamSubscription_closure(onData), type$.Event);
+ t1 = new W._EventStreamSubscription(_target, _eventType, t1, false, $T._eval$1("_EventStreamSubscription<0>"));
+ t1._tryResume$0();
+ return t1;
+ },
+ _wrapZone: function(callback, $T) {
+ var t1 = $.Zone__current;
+ if (t1 === C.C__RootZone)
+ return callback;
+ return t1.bindUnaryCallbackGuarded$1$1(callback, $T);
+ },
+ ApplicationCacheErrorEvent: function ApplicationCacheErrorEvent() {
+ },
+ Blob: function Blob() {
+ },
+ DomError: function DomError() {
+ },
+ DomException: function DomException() {
+ },
+ ErrorEvent: function ErrorEvent() {
+ },
+ Event: function Event() {
+ },
+ EventTarget: function EventTarget() {
+ },
+ File: function File() {
+ },
+ Location: function Location() {
+ },
+ MediaError: function MediaError() {
+ },
+ MediaKeyMessageEvent: function MediaKeyMessageEvent() {
+ },
+ MessageEvent: function MessageEvent() {
+ },
+ MessagePort: function MessagePort() {
+ },
+ NavigatorUserMediaError: function NavigatorUserMediaError() {
+ },
+ OverconstrainedError: function OverconstrainedError() {
+ },
+ PositionError: function PositionError() {
+ },
+ PresentationConnectionCloseEvent: function PresentationConnectionCloseEvent() {
+ },
+ SpeechRecognitionError: function SpeechRecognitionError() {
+ },
+ EventStreamProvider: function EventStreamProvider(t0) {
+ this.$ti = t0;
+ },
+ _EventStream: function _EventStream(t0, t1, t2, t3) {
+ var _ = this;
+ _._html$_target = t0;
+ _._eventType = t1;
+ _._useCapture = t2;
+ _.$ti = t3;
+ },
+ _EventStreamSubscription: function _EventStreamSubscription(t0, t1, t2, t3, t4) {
+ var _ = this;
+ _._pauseCount = 0;
+ _._html$_target = t0;
+ _._eventType = t1;
+ _._html$_onData = t2;
+ _._useCapture = t3;
+ _.$ti = t4;
+ },
+ _EventStreamSubscription_closure: function _EventStreamSubscription_closure(t0) {
+ this.onData = t0;
+ }
+ },
+ S = {AsyncMemoizer: function AsyncMemoizer(t0, t1) {
+ this._async_memoizer$_completer = t0;
+ this.$ti = t1;
+ }, NullStreamSink: function NullStreamSink(t0, t1) {
+ var _ = this;
+ _.done = t0;
+ _._addingStream = _._null_stream_sink$_closed = false;
+ _.$ti = t1;
+ }, NullStreamSink_addStream_closure: function NullStreamSink_addStream_closure(t0) {
+ this.$this = t0;
+ }, Validator: function Validator(t0) {
+ this._isDefined = t0;
+ },
+ SpanScanner$: function(string) {
+ var t1, t2;
+ string.toString;
+ t1 = new H.CodeUnits(string);
+ t2 = H.setRuntimeTypeInfo([0], type$.JSArray_int);
+ t2 = new Y.SourceFile(null, t2, new Uint32Array(H._ensureNativeList(t1.toList$0(t1))));
+ t2.SourceFile$decoded$2$url(t1, null);
+ return new S.SpanScanner(t2, null, string);
+ },
+ SpanScanner: function SpanScanner(t0, t1, t2) {
+ var _ = this;
+ _._sourceFile = t0;
+ _._lastSpan = null;
+ _.sourceUrl = t1;
+ _.string = t2;
+ _._string_scanner$_position = 0;
+ _._lastMatchPosition = _._lastMatch = null;
+ },
+ _SpanScannerState: function _SpanScannerState(t0, t1) {
+ this._span_scanner$_scanner = t0;
+ this.position = t1;
+ },
+ RemoteListener_start: function(getMain, beforeLoad, hidePrints) {
+ var printZone, spec, t3, _null = null, t1 = {},
+ controller = B.StreamChannelController$(false, true, type$.Object),
+ t2 = type$.dynamic,
+ channel = D._MultiChannel$(controller._local, t2);
+ t1.verboseChain = true;
+ printZone = $.Zone__current;
+ spec = P._ZoneSpecification$(_null, _null, _null, _null, _null, new S.RemoteListener_start_closure(printZone, channel), _null, _null, _null, _null, _null, _null, _null);
+ P.Stream_Stream$fromIterable([], t2).listen$1(new S.RemoteListener_start_closure0()).cancel$0();
+ t3 = type$.String;
+ P.runZoned(type$.Null_Function._check(new S.RemoteListener_start_closure1(t1, getMain, channel, controller, beforeLoad, printZone, spec)), _null, _null, P.LinkedHashMap_LinkedHashMap$_literal([$.$get$_currentKey(), new N.SuiteChannelManager(P.LinkedHashMap_LinkedHashMap$_empty(t3, type$.StreamChannel_dynamic), P.LinkedHashMap_LinkedHashMap$_empty(t3, type$.StreamChannelCompleter_dynamic), P.LinkedHashSet_LinkedHashSet$_empty(t3))], t2, t2), type$.Null);
+ return controller._foreign;
+ },
+ RemoteListener__deserializeSet: function(list) {
+ if (list == null)
+ return null;
+ if (J.get$isEmpty$asx(list))
+ return null;
+ return P.LinkedHashSet_LinkedHashSet$from(list, type$.String);
+ },
+ RemoteListener__sendLoadException: function(channel, message) {
+ var t1 = type$.String;
+ channel._mainController._foreign._sink.add$1(0, P.LinkedHashMap_LinkedHashMap$_literal(["type", "loadException", "message", message], t1, t1));
+ },
+ RemoteListener__sendError: function(channel, error, stackTrace, verboseChain) {
+ channel._mainController._foreign._sink.add$1(0, P.LinkedHashMap_LinkedHashMap$_literal(["type", "error", "error", U.RemoteException_serialize(error, type$.StackTraceFormatter._as($.Zone__current.$index(0, $.$get$_currentKey0())).formatStackTrace$2$verbose(stackTrace, verboseChain))], type$.String, type$.Object));
+ },
+ RemoteListener: function RemoteListener(t0, t1) {
+ this._suite = t0;
+ this._printZone = t1;
+ },
+ RemoteListener_start_closure: function RemoteListener_start_closure(t0, t1) {
+ this.printZone = t0;
+ this.channel = t1;
+ },
+ RemoteListener_start_closure0: function RemoteListener_start_closure0() {
+ },
+ RemoteListener_start_closure1: function RemoteListener_start_closure1(t0, t1, t2, t3, t4, t5, t6) {
+ var _ = this;
+ _._box_0 = t0;
+ _.getMain = t1;
+ _.channel = t2;
+ _.controller = t3;
+ _.beforeLoad = t4;
+ _.printZone = t5;
+ _.spec = t6;
+ },
+ RemoteListener_start__closure: function RemoteListener_start__closure(t0, t1, t2, t3, t4, t5, t6) {
+ var _ = this;
+ _._box_0 = t0;
+ _.getMain = t1;
+ _.channel = t2;
+ _.controller = t3;
+ _.beforeLoad = t4;
+ _.printZone = t5;
+ _.spec = t6;
+ },
+ RemoteListener_start___closure: function RemoteListener_start___closure(t0, t1, t2, t3, t4, t5) {
+ var _ = this;
+ _._box_0 = t0;
+ _.getMain = t1;
+ _.channel = t2;
+ _.controller = t3;
+ _.beforeLoad = t4;
+ _.printZone = t5;
+ },
+ RemoteListener_start____closure: function RemoteListener_start____closure(t0, t1) {
+ this.controller = t0;
+ this.channel = t1;
+ },
+ RemoteListener_start____closure0: function RemoteListener_start____closure0(t0, t1, t2) {
+ this.suite = t0;
+ this.printZone = t1;
+ this.channel = t2;
+ },
+ RemoteListener_start_____closure: function RemoteListener_start_____closure(t0, t1, t2) {
+ this.suite = t0;
+ this.printZone = t1;
+ this.channel = t2;
+ },
+ RemoteListener_start___closure0: function RemoteListener_start___closure0(t0, t1) {
+ this._box_0 = t0;
+ this.channel = t1;
+ },
+ RemoteListener__serializeGroup_closure: function RemoteListener__serializeGroup_closure(t0, t1, t2) {
+ this._box_0 = t0;
+ this.$this = t1;
+ this.channel = t2;
+ },
+ RemoteListener__serializeTest_closure: function RemoteListener__serializeTest_closure(t0, t1, t2, t3) {
+ var _ = this;
+ _.$this = t0;
+ _.test = t1;
+ _.groups = t2;
+ _.channel = t3;
+ },
+ RemoteListener__runLiveTest_closure: function RemoteListener__runLiveTest_closure(t0) {
+ this.liveTest = t0;
+ },
+ RemoteListener__runLiveTest_closure0: function RemoteListener__runLiveTest_closure0(t0) {
+ this.channel = t0;
+ },
+ RemoteListener__runLiveTest_closure1: function RemoteListener__runLiveTest_closure1(t0, t1) {
+ this.channel = t0;
+ this.liveTest = t1;
+ },
+ RemoteListener__runLiveTest_closure2: function RemoteListener__runLiveTest_closure2(t0, t1) {
+ this.$this = t0;
+ this.channel = t1;
+ },
+ RemoteListener__runLiveTest_closure3: function RemoteListener__runLiveTest_closure3(t0, t1) {
+ this.liveTest = t0;
+ this.channel = t1;
+ },
+ RemoteListener__runLiveTest__closure: function RemoteListener__runLiveTest__closure(t0) {
+ this.channel = t0;
+ }
+ },
+ O = {DelegatingSink: function DelegatingSink(t0, t1) {
+ this._sink$_sink = t0;
+ this.$ti = t1;
+ }, None: function None() {
+ }, Scanner: function Scanner(t0) {
+ this._scanner = t0;
+ this._scanner$_next = null;
+ this._endOfFileEmitted = false;
+ }, EmptyUnmodifiableSet: function EmptyUnmodifiableSet(t0) {
+ this.$ti = t0;
+ },
+ Style__getPlatformStyle: function() {
+ if (P.Uri_base().get$scheme() !== "file")
+ return $.$get$Style_url();
+ var t1 = P.Uri_base();
+ if (!C.JSString_methods.endsWith$1(t1.get$path(t1), "/"))
+ return $.$get$Style_url();
+ if (P._Uri__Uri(null, "a/b", null, null).toFilePath$0() === "a\\b")
+ return $.$get$Style_windows();
+ return $.$get$Style_posix();
+ },
+ Style: function Style() {
+ },
+ Pool$: function(_maxAllocatedResources) {
+ var t4,
+ t1 = type$.Completer_PoolResource,
+ t2 = P.ListQueue$(t1),
+ t3 = P.ListQueue$(type$.void_Function);
+ t1 = P.ListQueue$(t1);
+ t4 = $.Zone__current;
+ if (_maxAllocatedResources <= 0)
+ H.throwExpression(P.ArgumentError$value(_maxAllocatedResources, "maxAllocatedResources", "Must be greater than zero."));
+ return new O.Pool(t2, t3, t1, _maxAllocatedResources, new S.AsyncMemoizer(new P._AsyncCompleter(new P._Future(t4, type$._Future_dynamic), type$._AsyncCompleter_dynamic), type$.AsyncMemoizer_dynamic));
+ },
+ Pool: function Pool(t0, t1, t2, t3, t4) {
+ var _ = this;
+ _._requestedResources = t0;
+ _._onReleaseCallbacks = t1;
+ _._onReleaseCompleters = t2;
+ _._maxAllocatedResources = t3;
+ _._allocatedResources = 0;
+ _._closeGroup = _._timer = null;
+ _._pool$_closeMemo = t4;
+ },
+ Pool_close_closure: function Pool_close_closure(t0) {
+ this.$this = t0;
+ },
+ Pool__onResourceReleaseAllowed_closure: function Pool__onResourceReleaseAllowed_closure(t0, t1) {
+ this.zone = t0;
+ this.registered = t1;
+ },
+ Pool__runOnRelease_closure: function Pool__runOnRelease_closure(t0) {
+ this.$this = t0;
+ },
+ Pool__runOnRelease_closure0: function Pool__runOnRelease_closure0(t0) {
+ this.$this = t0;
+ },
+ PoolResource: function PoolResource(t0) {
+ this._pool = t0;
+ this._released = false;
+ },
+ mapStackTrace: function(sourceMap, stackTrace, minified, packageResolver, sdkRoot) {
+ var t2, t3, sdkLib, t4, t1 = {};
+ t1.packageResolver = packageResolver;
+ if (type$.Chain._is(stackTrace)) {
+ t2 = stackTrace.get$traces();
+ t3 = H._arrayInstanceType(t2);
+ return new U.Chain(P.List_List$unmodifiable(new H.MappedListIterable(t2, t3._eval$1("Trace(1)")._check(new O.mapStackTrace_closure(t1, sourceMap, false, sdkRoot)), t3._eval$1("MappedListIterable<1,Trace>")), type$.Trace));
+ }
+ t2 = sdkRoot == null;
+ !t2;
+ sdkLib = t2 ? null : sdkRoot.toString$0(0) + "/lib";
+ t2 = Y.Trace_Trace$from(stackTrace).get$frames();
+ t3 = H._arrayInstanceType(t2);
+ t4 = t3._eval$1("MappedListIterable<1,Frame>");
+ return new Y.Trace(P.List_List$unmodifiable(new H.MappedListIterable(t2, t3._eval$1("Frame(1)")._check(new O.mapStackTrace_closure0(t1, sourceMap, sdkRoot, sdkLib, false)), t4).super$Iterable$where(0, t4._eval$1("bool(ListIterable.E)")._check(new O.mapStackTrace_closure1())), type$.Frame), new P._StringStackTrace(null));
+ },
+ _prettifyMember: function(member) {
+ var t2, t3,
+ t1 = P.RegExp_RegExp("/?<$", false);
+ member.toString;
+ t1 = H.stringReplaceAllUnchecked(member, t1, "");
+ t2 = P.RegExp_RegExp("\\$\\d+(\\$[a-zA-Z_0-9]+)*$", false);
+ t3 = type$.String_Function_Match;
+ t2 = C.JSString_methods.splitMapJoin$2$onMatch(H.stringReplaceAllUnchecked(t1, t2, ""), P.RegExp_RegExp("(_+)closure\\d*\\.call$", false), t3._check(new O._prettifyMember_closure()));
+ t1 = P.RegExp_RegExp("\\.call$", false);
+ t1 = H.stringReplaceAllUnchecked(t2, t1, "");
+ t2 = P.RegExp_RegExp("^dart\\.", false);
+ t1 = H.stringReplaceAllUnchecked(t1, t2, "");
+ t2 = P.RegExp_RegExp("[a-zA-Z_0-9]+\\$", false);
+ t1 = H.stringReplaceAllUnchecked(t1, t2, "");
+ t2 = P.RegExp_RegExp("^[a-zA-Z_0-9]+.(static|dart).", false);
+ return C.JSString_methods.splitMapJoin$2$onMatch(H.stringReplaceAllUnchecked(t1, t2, ""), P.RegExp_RegExp("([a-zA-Z0-9]+)_", false), t3._check(new O._prettifyMember_closure0()));
+ },
+ mapStackTrace_closure: function mapStackTrace_closure(t0, t1, t2, t3) {
+ var _ = this;
+ _._box_0 = t0;
+ _.sourceMap = t1;
+ _.minified = t2;
+ _.sdkRoot = t3;
+ },
+ mapStackTrace_closure0: function mapStackTrace_closure0(t0, t1, t2, t3, t4) {
+ var _ = this;
+ _._box_0 = t0;
+ _.sourceMap = t1;
+ _.sdkRoot = t2;
+ _.sdkLib = t3;
+ _.minified = t4;
+ },
+ mapStackTrace_closure1: function mapStackTrace_closure1() {
+ },
+ _prettifyMember_closure: function _prettifyMember_closure() {
+ },
+ _prettifyMember_closure0: function _prettifyMember_closure0() {
+ },
+ StackZoneSpecification: function StackZoneSpecification(t0, t1, t2) {
+ var _ = this;
+ _._chains = t0;
+ _._stack_zone_specification$_onError = t1;
+ _._currentNode = null;
+ _._errorZone = t2;
+ },
+ StackZoneSpecification_chainFor_closure: function StackZoneSpecification_chainFor_closure(t0) {
+ this._box_0 = t0;
+ },
+ StackZoneSpecification_chainFor_closure0: function StackZoneSpecification_chainFor_closure0(t0, t1) {
+ this.$this = t0;
+ this.original = t1;
+ },
+ StackZoneSpecification__registerCallback_closure: function StackZoneSpecification__registerCallback_closure(t0, t1, t2, t3) {
+ var _ = this;
+ _.$this = t0;
+ _.f = t1;
+ _.node = t2;
+ _.R = t3;
+ },
+ StackZoneSpecification__registerUnaryCallback_closure: function StackZoneSpecification__registerUnaryCallback_closure(t0, t1, t2, t3, t4) {
+ var _ = this;
+ _.$this = t0;
+ _.f = t1;
+ _.node = t2;
+ _.T = t3;
+ _.R = t4;
+ },
+ StackZoneSpecification__registerUnaryCallback__closure: function StackZoneSpecification__registerUnaryCallback__closure(t0, t1, t2) {
+ this.f = t0;
+ this.arg = t1;
+ this.R = t2;
+ },
+ StackZoneSpecification__registerBinaryCallback_closure: function StackZoneSpecification__registerBinaryCallback_closure(t0, t1, t2, t3, t4, t5) {
+ var _ = this;
+ _.$this = t0;
+ _.f = t1;
+ _.node = t2;
+ _.T1 = t3;
+ _.T2 = t4;
+ _.R = t5;
+ },
+ StackZoneSpecification__registerBinaryCallback__closure: function StackZoneSpecification__registerBinaryCallback__closure(t0, t1, t2, t3) {
+ var _ = this;
+ _.f = t0;
+ _.arg1 = t1;
+ _.arg2 = t2;
+ _.R = t3;
+ },
+ StackZoneSpecification__currentTrace_closure: function StackZoneSpecification__currentTrace_closure(t0, t1, t2) {
+ this._box_0 = t0;
+ this.$this = t1;
+ this.stackTrace = t2;
+ },
+ _Node: function _Node(t0, t1) {
+ this.trace = t0;
+ this.previous = t1;
+ },
+ Group$: function($name, entries, metadata, setUpAll, tearDownAll, trace) {
+ var t1 = P.List_List$unmodifiable(entries, type$.GroupEntry);
+ return new O.Group($name, metadata, trace, t1, setUpAll, tearDownAll);
+ },
+ Group: function Group(t0, t1, t2, t3, t4, t5) {
+ var _ = this;
+ _.name = t0;
+ _.metadata = t1;
+ _.trace = t2;
+ _.entries = t3;
+ _.setUpAll = t4;
+ _.tearDownAll = t5;
+ },
+ Group_forPlatform_closure: function Group_forPlatform_closure(t0) {
+ this.platform = t0;
+ },
+ Group__map_closure: function Group__map_closure(t0) {
+ this.callback = t0;
+ },
+ Group__map_closure0: function Group__map_closure0() {
+ },
+ Metadata__parseOnPlatform: function(onPlatform) {
+ return P.LinkedHashMap_LinkedHashMap$_empty(type$.PlatformSelector, type$.Metadata);
+ },
+ Metadata__parseTags: function(tags) {
+ return P.LinkedHashSet_LinkedHashSet$_empty(type$.String);
+ },
+ Metadata_Metadata: function(chainStackTraces, forTag, onPlatform, retry, skip, skipReason, tags, testOn, timeout, verboseTrace) {
+ var t2, t3, empty, t4, merged, _null = null, t1 = {};
+ t1.tags = tags;
+ t1.forTag = forTag;
+ t2 = new O.Metadata_Metadata__unresolved(t1, testOn, timeout, skip, verboseTrace, chainStackTraces, retry, skipReason, onPlatform);
+ if (forTag == null || tags == null)
+ return t2.call$0();
+ t1.tags = P.LinkedHashSet_LinkedHashSet$from(tags, type$.String);
+ t3 = type$.Metadata;
+ t1.forTag = P.LinkedHashMap_LinkedHashMap$from(t1.forTag, type$.BooleanSelector, t3);
+ empty = O.Metadata$_(_null, _null, _null, _null, _null, _null, _null, _null, _null, _null);
+ t4 = t1.forTag.get$keys();
+ merged = C.JSArray_methods.fold$1$2(P.List_List$from(t4, true, H._instanceType(t4)._eval$1("Iterable.E")), empty, new O.Metadata_Metadata_closure(t1), t3);
+ if (merged === empty)
+ return t2.call$0();
+ return merged.merge$1(t2.call$0());
+ },
+ Metadata$_: function(chainStackTraces, forTag, onPlatform, retry, skip, skipReason, tags, testOn, timeout, verboseTrace) {
+ var t1 = testOn == null ? C.PlatformSelector_All : testOn,
+ t2 = timeout == null ? C.Timeout_null_1 : timeout,
+ t3 = tags == null ? P.LinkedHashSet_LinkedHashSet$_empty(type$.String) : tags.toSet$0(0),
+ t4 = onPlatform == null ? C.Map_empty : new P.UnmodifiableMapView(onPlatform, type$.UnmodifiableMapView_PlatformSelector_Metadata),
+ t5 = forTag == null ? C.Map_empty0 : new P.UnmodifiableMapView(forTag, type$.UnmodifiableMapView_BooleanSelector_Metadata);
+ t5 = new O.Metadata(t1, t2, skip, skipReason, verboseTrace, chainStackTraces, new L.UnmodifiableSetView(t3, type$.UnmodifiableSetView_String), retry, t4, t5);
+ if (retry != null)
+ P.RangeError_checkNotNegative(retry, "retry");
+ t5._validateTags$0();
+ return t5;
+ },
+ Metadata$parse: function(onPlatform, retry, skip, tags, testOn, timeout) {
+ var _null = null,
+ t1 = timeout == null ? C.Timeout_null_1 : timeout,
+ t2 = skip == null,
+ t3 = t2 ? _null : skip,
+ t4 = O.Metadata__parseOnPlatform(onPlatform);
+ t4 = new O.Metadata(C.PlatformSelector_All, t1, t3, _null, _null, _null, O.Metadata__parseTags(tags), retry, t4, C.Map_empty0);
+ !t2;
+ if (retry != null)
+ P.RangeError_checkNotNegative(retry, "retry");
+ t4._validateTags$0();
+ return t4;
+ },
+ Metadata$deserialize: function(serialized) {
+ var pair, t13,
+ t1 = J.getInterceptor$asx(serialized),
+ t2 = t1.$index(serialized, "testOn") == null ? C.PlatformSelector_All : E.PlatformSelector$parse(H._asStringNullable(t1.$index(serialized, "testOn"))),
+ t3 = O.Metadata__deserializeTimeout(t1.$index(serialized, "timeout")),
+ t4 = H._asBoolNullable(t1.$index(serialized, "skip")),
+ t5 = H._asStringNullable(t1.$index(serialized, "skipReason")),
+ t6 = H._asBoolNullable(t1.$index(serialized, "verboseTrace")),
+ t7 = H._asBoolNullable(t1.$index(serialized, "chainStackTraces")),
+ t8 = H._asIntNullable(t1.$index(serialized, "retry")),
+ t9 = type$.Iterable_dynamic,
+ t10 = P.LinkedHashSet_LinkedHashSet$from(t9._as(t1.$index(serialized, "tags")), type$.String),
+ t11 = type$.Metadata,
+ t12 = P.LinkedHashMap_LinkedHashMap$_empty(type$.PlatformSelector, t11);
+ for (t9 = J.get$iterator$ax(t9._check(t1.$index(serialized, "onPlatform"))); t9.moveNext$0();) {
+ pair = t9.get$current();
+ t13 = J.getInterceptor$ax(pair);
+ t12.$indexSet(0, E.PlatformSelector$parse(H._asStringNullable(t13.get$first(pair))), O.Metadata$deserialize(t13.get$last(pair)));
+ }
+ return new O.Metadata(t2, t3, t4, t5, t6, t7, t10, t8, t12, type$.Map_dynamic_dynamic._as(t1.$index(serialized, "forTag")).map$2$1(0, new O.Metadata$deserialize_closure(), type$.BooleanSelector, t11));
+ },
+ Metadata__deserializeTimeout: function(serialized) {
+ var scaleFactor,
+ t1 = J.getInterceptor$(serialized);
+ if (t1.$eq(serialized, "none"))
+ return C.Timeout_null_null;
+ scaleFactor = t1.$index(serialized, "scaleFactor");
+ if (scaleFactor != null)
+ return new R.Timeout(null, H._asNumNullable(scaleFactor));
+ return new R.Timeout(P.Duration$(H._asIntNullable(t1.$index(serialized, "duration")), 0), null);
+ },
+ Metadata: function Metadata(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9) {
+ var _ = this;
+ _.testOn = t0;
+ _.timeout = t1;
+ _._skip = t2;
+ _.skipReason = t3;
+ _._verboseTrace = t4;
+ _._chainStackTraces = t5;
+ _.tags = t6;
+ _._retry = t7;
+ _.onPlatform = t8;
+ _.forTag = t9;
+ },
+ Metadata_Metadata__unresolved: function Metadata_Metadata__unresolved(t0, t1, t2, t3, t4, t5, t6, t7, t8) {
+ var _ = this;
+ _._box_0 = t0;
+ _.testOn = t1;
+ _.timeout = t2;
+ _.skip = t3;
+ _.verboseTrace = t4;
+ _.chainStackTraces = t5;
+ _.retry = t6;
+ _.skipReason = t7;
+ _.onPlatform = t8;
+ },
+ Metadata_Metadata_closure: function Metadata_Metadata_closure(t0) {
+ this._box_0 = t0;
+ },
+ Metadata$deserialize_closure: function Metadata$deserialize_closure() {
+ },
+ Metadata__validateTags_closure: function Metadata__validateTags_closure() {
+ },
+ Metadata__validateTags_closure0: function Metadata__validateTags_closure0() {
+ },
+ Metadata_validatePlatformSelectors_closure: function Metadata_validatePlatformSelectors_closure(t0) {
+ this.validVariables = t0;
+ },
+ Metadata_merge_closure: function Metadata_merge_closure() {
+ },
+ Metadata_merge_closure0: function Metadata_merge_closure0() {
+ },
+ Metadata_forPlatform_closure: function Metadata_forPlatform_closure(t0, t1) {
+ this._box_0 = t0;
+ this.platform = t1;
+ },
+ Metadata_serialize_closure: function Metadata_serialize_closure(t0) {
+ this.serializedOnPlatform = t0;
+ },
+ Metadata_serialize_closure0: function Metadata_serialize_closure0() {
+ },
+ IterableSet: function IterableSet(t0, t1) {
+ this._iterable_set$_base = t0;
+ this.$ti = t1;
+ },
+ _IterableSet_SetMixin_UnmodifiableSetMixin: function _IterableSet_SetMixin_UnmodifiableSetMixin() {
+ },
+ Engine$: function() {
+ var t6, t7, t8, t9, t10, t11, _null = null,
+ t1 = $.Zone__current,
+ t2 = H.setRuntimeTypeInfo([], type$.JSArray_dynamic),
+ t3 = type$.RunnerSuite,
+ t4 = P.StreamController_StreamController(_null, _null, false, t3),
+ t5 = new L.StreamGroup(C._StreamGroupState_dormant, new H.JsLinkedHashMap(type$.JsLinkedHashMap_of_Stream_LiveTest_and_StreamSubscription_LiveTest), type$.StreamGroup_LiveTest);
+ t5.set$_stream_group$_controller(new P._SyncBroadcastStreamController(t5.get$_onListen(), t5.get$_onCancelBroadcast(), type$._SyncBroadcastStreamController_LiveTest));
+ t6 = type$.LiveTest;
+ t7 = Y.UnionSetController$(true, t6);
+ t8 = Y.UnionSetController$(true, t6);
+ t9 = Y.UnionSetController$(true, t6);
+ t10 = Q.QueueList$(t6);
+ t11 = O.Pool$(1);
+ t1 = new O.Engine(t11, O.Pool$(2), new F.FutureGroup(new P._AsyncCompleter(new P._Future(t1, type$._Future_List_dynamic), type$._AsyncCompleter_List_dynamic), t2, type$.FutureGroup_dynamic), P.LinkedHashSet_LinkedHashSet$_empty(type$.StreamSubscription_dynamic), t4, P.LinkedHashSet_LinkedHashSet$_empty(t3), new P._AsyncBroadcastStreamController(_null, _null, type$._AsyncBroadcastStreamController_RunnerSuite), P.LinkedHashSet_LinkedHashSet$_empty(type$.LiveSuite), new P._AsyncBroadcastStreamController(_null, _null, type$._AsyncBroadcastStreamController_LiveSuite), t5, t7, t8, t9, t10, P.LinkedHashSet_LinkedHashSet$_empty(t6), P.LinkedHashSet_LinkedHashSet$_empty(t6));
+ t1.Engine$3$concurrency$coverage$maxSuites(_null, _null, _null);
+ return t1;
+ },
+ Engine: function Engine(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15) {
+ var _ = this;
+ _._engine$_closed = _._engine$_runCalled = false;
+ _._closedBeforeDone = null;
+ _._runPool = t0;
+ _._loadPool = t1;
+ _._group = t2;
+ _._engine$_subscriptions = t3;
+ _._suiteController = t4;
+ _._addedSuites = t5;
+ _._onSuiteAddedController = t6;
+ _._liveSuites = t7;
+ _._onSuiteStartedController = t8;
+ _._onTestStartedGroup = t9;
+ _._passedGroup = t10;
+ _._skippedGroup = t11;
+ _._failedGroup = t12;
+ _._active = t13;
+ _._restarted = t14;
+ _._activeLoadTests = t15;
+ },
+ Engine_success_closure: function Engine_success_closure() {
+ },
+ Engine_closure: function Engine_closure(t0) {
+ this.$this = t0;
+ },
+ Engine_closure0: function Engine_closure0() {
+ },
+ Engine_run_closure: function Engine_run_closure(t0) {
+ this.$this = t0;
+ },
+ Engine_run__closure: function Engine_run__closure(t0, t1) {
+ this.$this = t0;
+ this.suite = t1;
+ },
+ Engine_run___closure: function Engine_run___closure(t0, t1, t2) {
+ this._box_0 = t0;
+ this.$this = t1;
+ this.loadResource = t2;
+ },
+ Engine_run____closure: function Engine_run____closure(t0) {
+ this._box_0 = t0;
+ },
+ Engine_run_closure0: function Engine_run_closure0(t0, t1) {
+ this._box_1 = t0;
+ this.$this = t1;
+ },
+ Engine__runLiveTest_closure: function Engine__runLiveTest_closure(t0, t1) {
+ this.$this = t0;
+ this.liveTest = t1;
+ },
+ Engine__runLiveTest_closure0: function Engine__runLiveTest_closure0(t0, t1) {
+ this._box_0 = t0;
+ this.$this = t1;
+ },
+ Engine__runLiveTest_closure1: function Engine__runLiveTest_closure1() {
+ },
+ Engine__runSkippedTest_closure: function Engine__runSkippedTest_closure() {
+ },
+ Engine__runSkippedTest_closure0: function Engine__runSkippedTest_closure0(t0, t1) {
+ this._box_0 = t0;
+ this.skipped = t1;
+ },
+ Engine__runSkippedTest_closure1: function Engine__runSkippedTest_closure1() {
+ },
+ binarySearch: function(list, matches) {
+ var max, min, half;
+ if (list.length === 0)
+ return -1;
+ if (H.boolConversionCheck(matches.call$1(C.JSArray_methods.get$first(list))))
+ return 0;
+ if (!H.boolConversionCheck(matches.call$1(C.JSArray_methods.get$last(list))))
+ return list.length;
+ max = list.length - 1;
+ for (min = 0; min < max;) {
+ half = min + C.JSInt_methods._tdivFast$1(max - min, 2);
+ if (half < 0 || half >= list.length)
+ return H.ioore(list, half);
+ if (H.boolConversionCheck(matches.call$1(list[half])))
+ max = half;
+ else
+ min = half + 1;
+ }
+ return max;
+ }
+ },
+ Y = {DelegatingStreamSubscription: function DelegatingStreamSubscription() {
+ }, StreamCompleter: function StreamCompleter(t0, t1) {
+ this._stream = t0;
+ this.$ti = t1;
+ }, _CompleterStream: function _CompleterStream(t0) {
+ this._sourceStream = this._stream_completer$_controller = null;
+ this.$ti = t0;
+ }, BooleanSelectorImpl: function BooleanSelectorImpl(t0) {
+ this._selector = t0;
+ },
+ mapMap: function(map, value, K1, V1, K2, V2) {
+ var key, result, t1 = {};
+ t1.key = key;
+ t1.value = value;
+ t1.key = null;
+ t1.key = new Y.mapMap_closure(K2, K1, V1);
+ result = P.LinkedHashMap_LinkedHashMap$_empty(K2, V2);
+ map.forEach$1(0, new Y.mapMap_closure0(t1, result, K1, V1));
+ return result;
+ },
+ mergeMaps: function(map1, map2, value, $K, $V) {
+ var result = P.LinkedHashMap_LinkedHashMap$from(map1, $K, $V);
+ map2.forEach$1(0, new Y.mergeMaps_closure(result, value, $K, $V));
+ return result;
+ },
+ mapMap_closure: function mapMap_closure(t0, t1, t2) {
+ this.K2 = t0;
+ this.K1 = t1;
+ this.V1 = t2;
+ },
+ mapMap_closure0: function mapMap_closure0(t0, t1, t2, t3) {
+ var _ = this;
+ _._box_0 = t0;
+ _.result = t1;
+ _.K1 = t2;
+ _.V1 = t3;
+ },
+ mergeMaps_closure: function mergeMaps_closure(t0, t1, t2, t3) {
+ var _ = this;
+ _.result = t0;
+ _.value = t1;
+ _.K = t2;
+ _.V = t3;
+ },
+ UnionSetController$: function(disjoint, $E) {
+ var t1 = P.LinkedHashSet_LinkedHashSet($E._eval$1("Set<0>")),
+ t2 = new Y.UnionSetController(t1, $E._eval$1("UnionSetController<0>"));
+ t2.set$_union_set_controller$_set(new M.UnionSet(t1, true, $E._eval$1("UnionSet<0>")));
+ return t2;
+ },
+ UnionSetController: function UnionSetController(t0, t1) {
+ this._union_set_controller$_set = null;
+ this._union_set_controller$_sets = t0;
+ this.$ti = t1;
+ },
+ _IsTrue: function _IsTrue() {
+ },
+ _Predicate: function _Predicate(t0, t1, t2) {
+ this._matcher = t0;
+ this._core_matchers$_description = t1;
+ this.$ti = t2;
+ },
+ FileLocation$_: function(file, offset) {
+ if (offset < 0)
+ H.throwExpression(P.RangeError$("Offset may not be negative, was " + offset + "."));
+ else if (offset > file._decodedChars.length)
+ H.throwExpression(P.RangeError$("Offset " + offset + " must not be greater than the number of characters in the file, " + file.get$length(file) + "."));
+ return new Y.FileLocation(file, offset);
+ },
+ _FileSpan$: function(file, _start, _end) {
+ if (_end < _start)
+ H.throwExpression(P.ArgumentError$("End " + _end + " must come after start " + _start + "."));
+ else if (_end > file._decodedChars.length)
+ H.throwExpression(P.RangeError$("End " + _end + " must not be greater than the number of characters in the file, " + file.get$length(file) + "."));
+ else if (_start < 0)
+ H.throwExpression(P.RangeError$("Start may not be negative, was " + _start + "."));
+ return new Y._FileSpan(file, _start, _end);
+ },
+ SourceFile: function SourceFile(t0, t1, t2) {
+ var _ = this;
+ _.url = t0;
+ _._lineStarts = t1;
+ _._decodedChars = t2;
+ _._cachedLine = null;
+ },
+ FileLocation: function FileLocation(t0, t1) {
+ this.file = t0;
+ this.offset = t1;
+ },
+ _FileSpan: function _FileSpan(t0, t1, t2) {
+ this.file = t0;
+ this._file$_start = t1;
+ this._file$_end = t2;
+ },
+ SourceSpanMixin: function SourceSpanMixin() {
+ },
+ Trace_Trace$current: function(level) {
+ return new T.LazyTrace(new Y.Trace_Trace$current_closure(Y.Trace_Trace$from(P.StackTrace_current()), level));
+ },
+ Trace_Trace$from: function(trace) {
+ if (trace == null)
+ throw H.wrapException(P.ArgumentError$("Cannot create a Trace from null."));
+ if (type$.Trace._is(trace))
+ return trace;
+ if (type$.Chain._is(trace))
+ return trace.toTrace$0();
+ return new T.LazyTrace(new Y.Trace_Trace$from_closure(trace));
+ },
+ Trace_Trace$parse: function(trace) {
+ var error, t1, exception;
+ try {
+ if (trace.length === 0) {
+ t1 = P.List_List$unmodifiable(H.setRuntimeTypeInfo([], type$.JSArray_Frame), type$.Frame);
+ return new Y.Trace(t1, new P._StringStackTrace(null));
+ }
+ if (C.JSString_methods.contains$1(trace, $.$get$_v8Trace())) {
+ t1 = Y.Trace$parseV8(trace);
+ return t1;
+ }
+ if (C.JSString_methods.contains$1(trace, "\tat ")) {
+ t1 = Y.Trace$parseJSCore(trace);
+ return t1;
+ }
+ if (C.JSString_methods.contains$1(trace, $.$get$_firefoxSafariTrace())) {
+ t1 = Y.Trace$parseFirefox(trace);
+ return t1;
+ }
+ if (C.JSString_methods.contains$1(trace, "===== asynchronous gap ===========================\n")) {
+ t1 = U.Chain_Chain$parse(trace).toTrace$0();
+ return t1;
+ }
+ if (C.JSString_methods.contains$1(trace, $.$get$_friendlyTrace())) {
+ t1 = Y.Trace$parseFriendly(trace);
+ return t1;
+ }
+ t1 = P.List_List$unmodifiable(Y.Trace__parseVM(trace), type$.Frame);
+ return new Y.Trace(t1, new P._StringStackTrace(trace));
+ } catch (exception) {
+ t1 = H.unwrapException(exception);
+ if (type$.FormatException._is(t1)) {
+ error = t1;
+ throw H.wrapException(P.FormatException$(H.S(J.get$message$x(error)) + "\nStack trace:\n" + H.S(trace), null, null));
+ } else
+ throw exception;
+ }
+ },
+ Trace__parseVM: function(trace) {
+ var t2, $frames,
+ t1 = J.trim$0$s(trace),
+ lines = H.setRuntimeTypeInfo(H.stringReplaceAllUnchecked(t1, "<asynchronous suspension>\n", "").split("\n"), type$.JSArray_String);
+ t1 = H.SubListIterable$(lines, 0, lines.length - 1, type$.String);
+ t2 = t1.$ti;
+ $frames = new H.MappedListIterable(t1, t2._eval$1("Frame(ListIterable.E)")._check(new Y.Trace__parseVM_closure()), t2._eval$1("MappedListIterable<ListIterable.E,Frame>")).toList$0(0);
+ if (!J.endsWith$1$s(C.JSArray_methods.get$last(lines), ".da"))
+ C.JSArray_methods.add$1($frames, A.Frame_Frame$parseVM(C.JSArray_methods.get$last(lines)));
+ return $frames;
+ },
+ Trace$parseV8: function(trace) {
+ var t2, t3,
+ t1 = H.SubListIterable$(H.setRuntimeTypeInfo(trace.split("\n"), type$.JSArray_String), 1, null, type$.String);
+ t1 = t1.super$Iterable$skipWhile(0, t1.$ti._eval$1("bool(ListIterable.E)")._check(new Y.Trace$parseV8_closure()));
+ t2 = type$.Frame;
+ t3 = t1.$ti;
+ return new Y.Trace(P.List_List$unmodifiable(H.MappedIterable_MappedIterable(t1, t3._eval$1("Frame(Iterable.E)")._check(new Y.Trace$parseV8_closure0()), t3._eval$1("Iterable.E"), t2), t2), new P._StringStackTrace(trace));
+ },
+ Trace$parseJSCore: function(trace) {
+ return new Y.Trace(P.List_List$unmodifiable(new H.MappedIterable(new H.WhereIterable(H.setRuntimeTypeInfo(trace.split("\n"), type$.JSArray_String), type$.bool_Function_String._check(new Y.Trace$parseJSCore_closure()), type$.WhereIterable_String), type$.Frame_Function_String._check(new Y.Trace$parseJSCore_closure0()), type$.MappedIterable_String_Frame), type$.Frame), new P._StringStackTrace(trace));
+ },
+ Trace$parseFirefox: function(trace) {
+ return new Y.Trace(P.List_List$unmodifiable(new H.MappedIterable(new H.WhereIterable(H.setRuntimeTypeInfo(C.JSString_methods.trim$0(trace).split("\n"), type$.JSArray_String), type$.bool_Function_String._check(new Y.Trace$parseFirefox_closure()), type$.WhereIterable_String), type$.Frame_Function_String._check(new Y.Trace$parseFirefox_closure0()), type$.MappedIterable_String_Frame), type$.Frame), new P._StringStackTrace(trace));
+ },
+ Trace$parseFriendly: function(trace) {
+ var t1 = trace.length === 0 ? H.setRuntimeTypeInfo([], type$.JSArray_Frame) : new H.MappedIterable(new H.WhereIterable(H.setRuntimeTypeInfo(C.JSString_methods.trim$0(trace).split("\n"), type$.JSArray_String), type$.bool_Function_String._check(new Y.Trace$parseFriendly_closure()), type$.WhereIterable_String), type$.Frame_Function_String._check(new Y.Trace$parseFriendly_closure0()), type$.MappedIterable_String_Frame);
+ return new Y.Trace(P.List_List$unmodifiable(t1, type$.Frame), new P._StringStackTrace(trace));
+ },
+ Trace: function Trace(t0, t1) {
+ this.frames = t0;
+ this.original = t1;
+ },
+ Trace_Trace$current_closure: function Trace_Trace$current_closure(t0, t1) {
+ this.trace = t0;
+ this.level = t1;
+ },
+ Trace_Trace$from_closure: function Trace_Trace$from_closure(t0) {
+ this.trace = t0;
+ },
+ Trace__parseVM_closure: function Trace__parseVM_closure() {
+ },
+ Trace$parseV8_closure: function Trace$parseV8_closure() {
+ },
+ Trace$parseV8_closure0: function Trace$parseV8_closure0() {
+ },
+ Trace$parseJSCore_closure: function Trace$parseJSCore_closure() {
+ },
+ Trace$parseJSCore_closure0: function Trace$parseJSCore_closure0() {
+ },
+ Trace$parseFirefox_closure: function Trace$parseFirefox_closure() {
+ },
+ Trace$parseFirefox_closure0: function Trace$parseFirefox_closure0() {
+ },
+ Trace$parseFriendly_closure: function Trace$parseFriendly_closure() {
+ },
+ Trace$parseFriendly_closure0: function Trace$parseFriendly_closure0() {
+ },
+ Trace_foldFrames_closure: function Trace_foldFrames_closure(t0) {
+ this.oldPredicate = t0;
+ },
+ Trace_foldFrames_closure0: function Trace_foldFrames_closure0(t0) {
+ this._box_0 = t0;
+ },
+ Trace_toString_closure0: function Trace_toString_closure0() {
+ },
+ Trace_toString_closure: function Trace_toString_closure(t0) {
+ this.longest = t0;
+ },
+ RunnerSuite: function RunnerSuite(t0, t1, t2, t3) {
+ var _ = this;
+ _._runner_suite$_controller = t0;
+ _.platform = t1;
+ _.path = t2;
+ _.group = t3;
+ },
+ RunnerSuiteController: function RunnerSuiteController(t0, t1, t2, t3, t4) {
+ var _ = this;
+ _._config = t0;
+ _._runner_suite$_onClose = t1;
+ _._onDebuggingController = t2;
+ _._channelNames = t3;
+ _._closeMemo = t4;
+ },
+ RunnerSuiteController__close_closure: function RunnerSuiteController__close_closure(t0) {
+ this.$this = t0;
+ }
+ },
+ F = {FutureGroup: function FutureGroup(t0, t1, t2) {
+ var _ = this;
+ _._future_group$_pending = 0;
+ _._future_group$_closed = false;
+ _._future_group$_completer = t0;
+ _._values = t1;
+ _.$ti = t2;
+ }, FutureGroup_add_closure: function FutureGroup_add_closure(t0, t1) {
+ this.$this = t0;
+ this.index = t1;
+ }, FutureGroup_add_closure0: function FutureGroup_add_closure0(t0) {
+ this.$this = t0;
+ }, ValueResult: function ValueResult(t0, t1) {
+ this.value = t0;
+ this.$ti = t1;
+ },
+ PackageConfigResolver__normalizeMap: function(map) {
+ var t1 = type$.String,
+ t2 = type$.Uri;
+ return new P.UnmodifiableMapView(Y.mapMap(map, new F.PackageConfigResolver__normalizeMap_closure(), t1, t2, t1, t2), type$.UnmodifiableMapView_String_Uri);
+ },
+ PackageConfigResolver: function PackageConfigResolver(t0) {
+ this.packageConfigMap = t0;
+ },
+ PackageConfigResolver__normalizeMap_closure: function PackageConfigResolver__normalizeMap_closure() {
+ },
+ UrlStyle: function UrlStyle(t0, t1, t2, t3) {
+ var _ = this;
+ _.separatorPattern = t0;
+ _.needsSeparatorPattern = t1;
+ _.rootPattern = t2;
+ _.relativeRootPattern = t3;
+ }
+ },
+ V = {ErrorResult: function ErrorResult(t0, t1) {
+ this.error = t0;
+ this.stackTrace = t1;
+ },
+ SourceLocation$: function(offset, column, line, sourceUrl) {
+ var t2, t3, t4, t5,
+ t1 = typeof sourceUrl == "string" ? P.Uri_parse(sourceUrl) : sourceUrl;
+ type$.Uri._check(t1);
+ t2 = line == null;
+ t3 = t2 ? 0 : line;
+ t4 = column == null;
+ t5 = t4 ? offset : column;
+ if (offset < 0)
+ H.throwExpression(P.RangeError$("Offset may not be negative, was " + offset + "."));
+ else if (!t2 && line < 0)
+ H.throwExpression(P.RangeError$("Line may not be negative, was " + H.S(line) + "."));
+ else if (!t4 && column < 0)
+ H.throwExpression(P.RangeError$("Column may not be negative, was " + H.S(column) + "."));
+ return new V.SourceLocation(t1, offset, t3, t5);
+ },
+ SourceLocation: function SourceLocation(t0, t1, t2, t3) {
+ var _ = this;
+ _.sourceUrl = t0;
+ _.offset = t1;
+ _.line = t2;
+ _.column = t3;
+ },
+ SourceSpan: function SourceSpan() {
+ },
+ SourceSpanBase: function SourceSpanBase() {
+ },
+ GroupEntry: function GroupEntry() {
+ },
+ LiveTestController$: function(suite, _test, onRun, onClose, groups) {
+ var _null = null,
+ t1 = H.setRuntimeTypeInfo([], type$.JSArray_AsyncError),
+ t2 = $.Zone__current,
+ t3 = P.List_List$unmodifiable(groups, type$.Group);
+ t1 = new V.LiveTestController(suite, t3, _test, onRun, onClose, t1, C.State_Status_pending_Result_success, new P._SyncBroadcastStreamController(_null, _null, type$._SyncBroadcastStreamController_State), new P._SyncBroadcastStreamController(_null, _null, type$._SyncBroadcastStreamController_AsyncError), new P._SyncBroadcastStreamController(_null, _null, type$._SyncBroadcastStreamController_Message), new P._AsyncCompleter(new P._Future(t2, type$._Future_void), type$._AsyncCompleter_void));
+ t1._liveTest = new V._LiveTest(t1);
+ return t1;
+ },
+ _LiveTest: function _LiveTest(t0) {
+ this._live_test_controller$_controller = t0;
+ },
+ LiveTestController: function LiveTestController(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10) {
+ var _ = this;
+ _._liveTest = null;
+ _._live_test_controller$_suite = t0;
+ _._groups = t1;
+ _._test = t2;
+ _._onRun = t3;
+ _._onClose = t4;
+ _._errors = t5;
+ _._live_test_controller$_state = t6;
+ _._onStateChangeController = t7;
+ _._onErrorController = t8;
+ _._onMessageController = t9;
+ _.completer = t10;
+ _._runCalled = false;
+ },
+ Test: function Test() {
+ },
+ errorsDontStopTest: function(body) {
+ var t1 = $.Zone__current,
+ t2 = new P._Future(t1, type$._Future_dynamic),
+ t3 = type$.Invoker;
+ t3._as(t1.$index(0, C.Symbol_cQL)).addOutstandingCallback$0();
+ t3._as($.Zone__current.$index(0, C.Symbol_cQL)).waitForOutstandingCallbacks$1(new V.errorsDontStopTest_closure(body, new P._AsyncCompleter(t2, type$._AsyncCompleter_dynamic))).then$1$1(new V.errorsDontStopTest_closure0(), type$.void);
+ return t2;
+ },
+ errorsDontStopTest_closure: function errorsDontStopTest_closure(t0, t1) {
+ this.body = t0;
+ this.completer = t1;
+ },
+ errorsDontStopTest_closure0: function errorsDontStopTest_closure0() {
+ }
+ },
+ E = {Result: function Result() {
+ }, StringDescription: function StringDescription(t0) {
+ this._out = t0;
+ }, FeatureMatcher: function FeatureMatcher() {
+ }, PosixStyle: function PosixStyle(t0, t1, t2) {
+ this.separatorPattern = t0;
+ this.needsSeparatorPattern = t1;
+ this.rootPattern = t2;
+ },
+ StringScannerException$: function(message, span, source) {
+ return new E.StringScannerException(message, span);
+ },
+ StringScannerException: function StringScannerException(t0, t1) {
+ this._span_exception$_message = t0;
+ this._span_exception$_span = t1;
+ },
+ PlatformSelector$parse: function(selector) {
+ return new E.PlatformSelector(E.PlatformSelector__wrapFormatException(new E.PlatformSelector$parse_closure(selector), null, type$.BooleanSelector));
+ },
+ PlatformSelector__wrapFormatException: function(body, span, $T) {
+ var t1 = body.call$0();
+ return t1;
+ },
+ PlatformSelector: function PlatformSelector(t0) {
+ this._platform_selector$_inner = t0;
+ },
+ PlatformSelector$parse_closure: function PlatformSelector$parse_closure(t0) {
+ this.selector = t0;
+ },
+ PlatformSelector_validate_closure: function PlatformSelector_validate_closure(t0, t1) {
+ this.$this = t0;
+ this.validVariables = t1;
+ },
+ PlatformSelector_validate__closure: function PlatformSelector_validate__closure(t0) {
+ this.validVariables = t0;
+ },
+ PlatformSelector_evaluate_closure: function PlatformSelector_evaluate_closure(t0) {
+ this.platform = t0;
+ },
+ SuitePlatform$: function(runtime, inGoogle, os) {
+ var t1 = os == null ? C.OperatingSystem_none_none : os;
+ if (H.boolConversionCheck(runtime.isBrowser) && t1 !== C.OperatingSystem_none_none)
+ H.throwExpression(P.ArgumentError$('No OS should be passed for runtime "' + runtime.toString$0(0) + '".'));
+ return new E.SuitePlatform(runtime, t1, inGoogle);
+ },
+ SuitePlatform: function SuitePlatform(t0, t1, t2) {
+ this.runtime = t0;
+ this.os = t1;
+ this.inGoogle = t2;
+ },
+ StackTraceMapper: function StackTraceMapper() {
+ },
+ LiveSuite: function LiveSuite() {
+ },
+ JSStackTraceMapper_deserialize: function(serialized) {
+ var t1, t2, t3,
+ _s11_ = "packageRoot",
+ packageRoot = H._asStringNullable(serialized.$index(0, _s11_));
+ if (packageRoot == null)
+ packageRoot = "";
+ t1 = H._asStringNullable(serialized.$index(0, "mapContents"));
+ t2 = P.Uri_parse(H._asStringNullable(serialized.$index(0, "sdkRoot")));
+ if (packageRoot.length !== 0)
+ t3 = new D.PackageRootResolver(B.ensureTrailingSlash(B.asUri(P.Uri_parse(H._asStringNullable(serialized.$index(0, _s11_))), _s11_)));
+ else {
+ t3 = type$.String;
+ t3 = F.PackageConfigResolver__normalizeMap(E.JSStackTraceMapper__deserializePackageConfigMap(type$.Map_dynamic_dynamic._as(serialized.$index(0, "packageConfigMap")).cast$2$0(0, t3, t3)));
+ t3 = new F.PackageConfigResolver(t3);
+ }
+ return new E.JSStackTraceMapper(t3, t2, t1, P.Uri_parse(H._asStringNullable(serialized.$index(0, "mapUrl"))));
+ },
+ JSStackTraceMapper__deserializePackageConfigMap: function(serialized) {
+ return serialized.map$2$1(0, new E.JSStackTraceMapper__deserializePackageConfigMap_closure(), type$.String, type$.Uri);
+ },
+ JSStackTraceMapper: function JSStackTraceMapper(t0, t1, t2, t3) {
+ var _ = this;
+ _._mapping = null;
+ _._packageResolver = t0;
+ _._sdkRoot = t1;
+ _._mapContents = t2;
+ _._mapUrl = t3;
+ },
+ JSStackTraceMapper__deserializePackageConfigMap_closure: function JSStackTraceMapper__deserializePackageConfigMap_closure() {
+ }
+ },
+ L = {StreamGroup: function StreamGroup(t0, t1, t2) {
+ var _ = this;
+ _._stream_group$_controller = null;
+ _._stream_group$_closed = false;
+ _._stream_group$_state = t0;
+ _._stream_group$_subscriptions = t1;
+ _.$ti = t2;
+ }, StreamGroup_add_closure: function StreamGroup_add_closure() {
+ }, StreamGroup_add_closure0: function StreamGroup_add_closure0(t0, t1) {
+ this.$this = t0;
+ this.stream = t1;
+ }, StreamGroup__onListen_closure: function StreamGroup__onListen_closure(t0) {
+ this.$this = t0;
+ }, StreamGroup__onCancelBroadcast_closure: function StreamGroup__onCancelBroadcast_closure(t0) {
+ this.$this = t0;
+ }, StreamGroup__listenToStream_closure: function StreamGroup__listenToStream_closure(t0, t1) {
+ this.$this = t0;
+ this.stream = t1;
+ }, _StreamGroupState: function _StreamGroupState(t0) {
+ this.name = t0;
+ }, Token: function Token(t0, t1) {
+ this.type = t0;
+ this.span = t1;
+ }, IdentifierToken: function IdentifierToken(t0, t1) {
+ this.span = t0;
+ this.name = t1;
+ }, TokenType: function TokenType(t0) {
+ this.name = t0;
+ },
+ UnmodifiableSetView$: function(setBase, $E) {
+ return new L.UnmodifiableSetView(setBase, $E._eval$1("UnmodifiableSetView<0>"));
+ },
+ UnmodifiableSetView: function UnmodifiableSetView(t0, t1) {
+ this._base = t0;
+ this.$ti = t1;
+ },
+ UnmodifiableSetMixin: function UnmodifiableSetMixin() {
+ },
+ _UnmodifiableSetView_DelegatingSet_UnmodifiableSetMixin: function _UnmodifiableSetView_DelegatingSet_UnmodifiableSetMixin() {
+ },
+ WindowsStyle: function WindowsStyle(t0, t1, t2, t3) {
+ var _ = this;
+ _.separatorPattern = t0;
+ _.needsSeparatorPattern = t1;
+ _.rootPattern = t2;
+ _.relativeRootPattern = t3;
+ },
+ WindowsStyle_absolutePathToUri_closure: function WindowsStyle_absolutePathToUri_closure() {
+ },
+ decodeVlq: function(chars) {
+ var t1, t2, result, $stop, shift, t3, char, digit, result0, _null = null;
+ for (t1 = chars._parser$_length, t2 = chars._internal, result = 0, $stop = false, shift = 0; !$stop;) {
+ t3 = ++chars.index;
+ if (t3 >= t1)
+ throw H.wrapException(P.StateError$("incomplete VLQ value"));
+ if (t3 >= 0 && true) {
+ if (t3 < 0 || t3 >= t2.length)
+ return H.ioore(t2, t3);
+ char = t2[t3];
+ } else
+ char = _null;
+ t3 = $.$get$_digits();
+ if (!t3.containsKey$1(char))
+ throw H.wrapException(P.FormatException$("invalid character in VLQ encoding: " + H.S(char), _null, _null));
+ digit = t3.$index(0, char);
+ if (typeof digit !== "number")
+ return digit.$and();
+ $stop = (digit & 32) === 0;
+ result += C.JSInt_methods._shlPositive$1(digit & 31, shift);
+ shift += 5;
+ }
+ result0 = result >>> 1;
+ result = (result & 1) === 1 ? -result0 : result0;
+ if (result < $.$get$MIN_INT32() || result > $.$get$MAX_INT32())
+ throw H.wrapException(P.FormatException$("expected an encoded 32 bit int, but we got: " + result, _null, _null));
+ return result;
+ },
+ closure: function closure() {
+ },
+ internalBootstrapBrowserTest: function(getMain) {
+ var t2,
+ channel = S.RemoteListener_start(getMain, new L.internalBootstrapBrowserTest_closure(), false),
+ t1 = N.postMessageChannel();
+ t1.$ti._eval$1("StreamChannel<1>")._check(channel);
+ t1.get$stream(t1).pipe$1(channel._sink);
+ t2 = channel._streamController;
+ t2.toString;
+ new P._ControllerStream(t2, H._instanceType(t2)._eval$1("_ControllerStream<1>")).pipe$1(t1.get$sink());
+ },
+ internalBootstrapBrowserTest_closure: function internalBootstrapBrowserTest_closure() {
+ }
+ },
+ G = {StreamQueue: function StreamQueue(t0, t1, t2, t3) {
+ var _ = this;
+ _._stream_queue$_source = t0;
+ _._stream_queue$_subscription = null;
+ _._isClosed = _._isDone = false;
+ _._eventsReceived = 0;
+ _._eventQueue = t1;
+ _._requestQueue = t2;
+ _.$ti = t3;
+ }, StreamQueue__ensureListening_closure: function StreamQueue__ensureListening_closure(t0) {
+ this.$this = t0;
+ }, StreamQueue__ensureListening_closure1: function StreamQueue__ensureListening_closure1(t0) {
+ this.$this = t0;
+ }, StreamQueue__ensureListening_closure0: function StreamQueue__ensureListening_closure0(t0) {
+ this.$this = t0;
+ }, _EventRequest: function _EventRequest() {
+ }, _NextRequest: function _NextRequest(t0, t1) {
+ this._completer = t0;
+ this.$ti = t1;
+ }, _RestRequest: function _RestRequest(t0, t1, t2) {
+ this._completer = t0;
+ this._streamQueue = t1;
+ this.$ti = t2;
+ }, Parser: function Parser(t0) {
+ this._parser0$_scanner = t0;
+ }, Matcher: function Matcher() {
+ }, SourceMapSpan: function SourceMapSpan(t0, t1, t2, t3) {
+ var _ = this;
+ _.isIdentifier = t0;
+ _.start = t1;
+ _.end = t2;
+ _.text = t3;
+ },
+ SourceSpanFormatException$: function(message, span, _source) {
+ return new G.SourceSpanFormatException(message, span);
+ },
+ SourceSpanException: function SourceSpanException() {
+ },
+ SourceSpanFormatException: function SourceSpanFormatException(t0, t1) {
+ this._span_exception$_message = t0;
+ this._span_exception$_span = t1;
+ },
+ State: function State(t0, t1) {
+ this.status = t0;
+ this.result = t1;
+ },
+ Status: function Status(t0) {
+ this.name = t0;
+ },
+ Result0: function Result0(t0) {
+ this.name = t0;
+ },
+ expect: function(actual, matcher, reason) {
+ G._expect(true, matcher, null, reason, null, false);
+ },
+ _expect: function(actual, matcher, formatter, reason, skip, verbose) {
+ var matchState, e, trace, exception,
+ t1 = type$.Invoker;
+ if (t1._as($.Zone__current.$index(0, C.Symbol_cQL)) == null)
+ throw H.wrapException(P.StateError$("expect() may only be called within a test."));
+ t1 = t1._as($.Zone__current.$index(0, C.Symbol_cQL));
+ if (H.boolConversionCheck(H._asBoolNullable($.Zone__current.$index(0, t1._closableKey))) && t1._onCloseCompleter.future._state !== 0)
+ throw H.wrapException(K.ClosedException$());
+ matcher = M.wrapMatcher(matcher);
+ t1 = type$.dynamic;
+ matchState = P.LinkedHashMap_LinkedHashMap$_empty(t1, t1);
+ try {
+ if (matcher.matches$2(0, true, matchState)) {
+ t1 = P.Future_Future$sync(new G._expect_closure(), t1);
+ return t1;
+ }
+ t1 = reason;
+ } catch (exception) {
+ e = H.unwrapException(exception);
+ trace = H.getTraceFromException(exception);
+ t1 = reason == null ? H.S(e) + " at " + H.S(trace) : reason;
+ }
+ G.fail(new G._expect_closure0().call$5(true, matcher, t1, matchState, false));
+ },
+ fail: function(message) {
+ return H.throwExpression(new G.TestFailure(message));
+ },
+ formatFailure: function(expected, actual, which, reason) {
+ var t2,
+ t1 = new E.StringDescription(new P.StringBuffer("")).addDescriptionOf$1(expected)._out._contents;
+ t1 = B.indent(t1.charCodeAt(0) == 0 ? t1 : t1, "Expected: ") + "\n";
+ t2 = new E.StringDescription(new P.StringBuffer("")).addDescriptionOf$1(true)._out._contents;
+ t2 = t1 + (B.indent(t2.charCodeAt(0) == 0 ? t2 : t2, " Actual: ") + "\n");
+ t1 = which.length !== 0 ? t2 + (B.indent(which, " Which: ") + "\n") : t2;
+ if (reason != null)
+ t1 += reason + "\n";
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ TestFailure: function TestFailure(t0) {
+ this.message = t0;
+ },
+ _expect_closure0: function _expect_closure0() {
+ },
+ _expect_closure: function _expect_closure() {
+ },
+ method1: function() {
+ return $.x = $.x + 1;
+ },
+ method2: function() {
+ return $.x = $.x + 1;
+ },
+ main0: function() {
+ R.group("a group", new G.main_closure0());
+ },
+ main_closure0: function main_closure0() {
+ },
+ main__closure: function main__closure() {
+ }
+ },
+ T = {StreamSinkCompleter: function StreamSinkCompleter(t0, t1) {
+ this.sink = t0;
+ this.$ti = t1;
+ }, _CompleterSink: function _CompleterSink(t0) {
+ var _ = this;
+ _._destinationSink = _._stream_sink_completer$_doneCompleter = _._stream_sink_completer$_controller = null;
+ _.$ti = t0;
+ }, _CompleterSink__setDestinationSink_closure: function _CompleterSink__setDestinationSink_closure() {
+ }, SubscriptionStream: function SubscriptionStream(t0, t1) {
+ this._subscription_stream$_source = t0;
+ this.$ti = t1;
+ }, _CancelOnErrorSubscriptionWrapper: function _CancelOnErrorSubscriptionWrapper(t0, t1) {
+ this._stream_subscription$_source = t0;
+ this.$ti = t1;
+ }, _CancelOnErrorSubscriptionWrapper_onError_closure: function _CancelOnErrorSubscriptionWrapper_onError_closure(t0, t1) {
+ this.$this = t0;
+ this.handleError = t1;
+ }, _CancelOnErrorSubscriptionWrapper_onError__closure: function _CancelOnErrorSubscriptionWrapper_onError__closure(t0, t1, t2) {
+ this.handleError = t0;
+ this.error = t1;
+ this.stackTrace = t2;
+ }, Evaluator: function Evaluator(t0) {
+ this._semantics = t0;
+ },
+ parseJsonExtended: function(json, mapUrl, otherMaps) {
+ if (type$.List_dynamic._is(json))
+ return T.MappingBundle$fromJson(json, H._checkStringNullable(mapUrl));
+ return T.parseJson(type$.Map_dynamic_dynamic._as(json), null, null);
+ },
+ parseJson: function(map, mapUrl, otherMaps) {
+ var _s8_ = "sections";
+ if (!J.$eq$(map.$index(0, "version"), 3))
+ throw H.wrapException(P.ArgumentError$("unexpected source map version: " + H.S(map.$index(0, "version")) + ". Only version 3 is supported."));
+ if (map.containsKey$1(_s8_)) {
+ if (map.containsKey$1("mappings") || map.containsKey$1("sources") || map.containsKey$1("names"))
+ throw H.wrapException(P.FormatException$('map containing "sections" cannot contain "mappings", "sources", or "names".', null, null));
+ return T.MultiSectionMapping$fromJson(type$.List_dynamic._check(map.$index(0, _s8_)), otherMaps, mapUrl);
+ }
+ return T.SingleMapping$fromJson(map, mapUrl);
+ },
+ MultiSectionMapping$fromJson: function(sections, otherMaps, mapUrl) {
+ var t1 = type$.JSArray_int;
+ t1 = new T.MultiSectionMapping(H.setRuntimeTypeInfo([], t1), H.setRuntimeTypeInfo([], t1), H.setRuntimeTypeInfo([], type$.JSArray_Mapping));
+ t1.MultiSectionMapping$fromJson$3$mapUrl(sections, otherMaps, mapUrl);
+ return t1;
+ },
+ MappingBundle$fromJson: function(json, mapUrl) {
+ var t1 = new T.MappingBundle(P.LinkedHashMap_LinkedHashMap$_empty(type$.String, type$.SingleMapping));
+ t1.MappingBundle$fromJson$2$mapUrl(json, mapUrl);
+ return t1;
+ },
+ SingleMapping$fromJson: function(map, mapUrl) {
+ var t5, t6, t7, t8,
+ t1 = H._checkStringNullable(map.$index(0, "file")),
+ t2 = type$.Iterable_dynamic,
+ t3 = type$.String,
+ t4 = P.List_List$from(t2._check(map.$index(0, "sources")), true, t3);
+ t2 = P.List_List$from(t2._check(map.$index(0, "names")), true, t3);
+ t5 = H._checkIntNullable(J.get$length$asx(map.$index(0, "sources")));
+ if (typeof t5 !== "number")
+ return H.iae(t5);
+ t5 = new Array(t5);
+ t5.fixed$length = Array;
+ t5 = H.setRuntimeTypeInfo(t5, type$.JSArray_SourceFile);
+ t6 = H._checkStringNullable(map.$index(0, "sourceRoot"));
+ t7 = H.setRuntimeTypeInfo([], type$.JSArray_TargetLineEntry);
+ t8 = typeof mapUrl == "string" ? P.Uri_parse(mapUrl) : mapUrl;
+ t3 = new T.SingleMapping(t4, t2, t5, t7, t1, t6, type$.Uri._check(t8), P.LinkedHashMap_LinkedHashMap$_empty(t3, type$.dynamic));
+ t3.SingleMapping$fromJson$2$mapUrl(map, mapUrl);
+ return t3;
+ },
+ Mapping: function Mapping() {
+ },
+ MultiSectionMapping: function MultiSectionMapping(t0, t1, t2) {
+ this._lineStart = t0;
+ this._columnStart = t1;
+ this._maps = t2;
+ },
+ MappingBundle: function MappingBundle(t0) {
+ this._mappings = t0;
+ },
+ SingleMapping: function SingleMapping(t0, t1, t2, t3, t4, t5, t6, t7) {
+ var _ = this;
+ _.urls = t0;
+ _.names = t1;
+ _.files = t2;
+ _.lines = t3;
+ _.targetUrl = t4;
+ _.sourceRoot = t5;
+ _._parser$_mapUrl = t6;
+ _.extensions = t7;
+ },
+ SingleMapping$fromJson_closure: function SingleMapping$fromJson_closure(t0) {
+ this.$this = t0;
+ },
+ SingleMapping__findLine_closure: function SingleMapping__findLine_closure(t0) {
+ this.line = t0;
+ },
+ SingleMapping__findColumn_closure: function SingleMapping__findColumn_closure(t0) {
+ this.column = t0;
+ },
+ TargetLineEntry: function TargetLineEntry(t0, t1) {
+ this.line = t0;
+ this.entries = t1;
+ },
+ TargetEntry: function TargetEntry(t0, t1, t2, t3, t4) {
+ var _ = this;
+ _.column = t0;
+ _.sourceUrlId = t1;
+ _.sourceLine = t2;
+ _.sourceColumn = t3;
+ _.sourceNameId = t4;
+ },
+ _MappingTokenizer: function _MappingTokenizer(t0, t1) {
+ this._internal = t0;
+ this._parser$_length = t1;
+ this.index = -1;
+ },
+ _TokenKind: function _TokenKind(t0, t1, t2) {
+ this.isNewLine = t0;
+ this.isNewSegment = t1;
+ this.isEof = t2;
+ },
+ LazyTrace: function LazyTrace(t0) {
+ this._lazy_trace$_thunk = t0;
+ this._lazy_trace$_inner = null;
+ },
+ LazyTrace_foldFrames_closure: function LazyTrace_foldFrames_closure(t0, t1, t2) {
+ this.$this = t0;
+ this.predicate = t1;
+ this.terse = t2;
+ },
+ RuntimeSelection: function RuntimeSelection() {
+ }
+ },
+ X = {BooleanSelector: function BooleanSelector() {
+ }, All: function All() {
+ },
+ ParsedPath_ParsedPath$parse: function(path, style) {
+ var t1, parts, separators, start, i,
+ root = style.getRoot$1(path);
+ style.isRootRelative$1(path);
+ if (root != null)
+ path = J.substring$1$s(path, root.length);
+ t1 = type$.JSArray_String;
+ parts = H.setRuntimeTypeInfo([], t1);
+ separators = H.setRuntimeTypeInfo([], t1);
+ t1 = path.length;
+ if (t1 !== 0 && style.isSeparator$1(C.JSString_methods._codeUnitAt$1(path, 0))) {
+ if (0 >= t1)
+ return H.ioore(path, 0);
+ C.JSArray_methods.add$1(separators, path[0]);
+ start = 1;
+ } else {
+ C.JSArray_methods.add$1(separators, "");
+ start = 0;
+ }
+ for (i = start; i < t1; ++i)
+ if (style.isSeparator$1(C.JSString_methods._codeUnitAt$1(path, i))) {
+ C.JSArray_methods.add$1(parts, C.JSString_methods.substring$2(path, start, i));
+ C.JSArray_methods.add$1(separators, path[i]);
+ start = i + 1;
+ }
+ if (start < t1) {
+ C.JSArray_methods.add$1(parts, C.JSString_methods.substring$1(path, start));
+ C.JSArray_methods.add$1(separators, "");
+ }
+ return new X.ParsedPath(style, root, parts, separators);
+ },
+ ParsedPath: function ParsedPath(t0, t1, t2, t3) {
+ var _ = this;
+ _.style = t0;
+ _.root = t1;
+ _.parts = t2;
+ _.separators = t3;
+ },
+ ParsedPath_normalize_closure: function ParsedPath_normalize_closure(t0) {
+ this.$this = t0;
+ },
+ PathException$: function(message) {
+ return new X.PathException(message);
+ },
+ PathException: function PathException(t0) {
+ this.message = t0;
+ },
+ SourceSpanWithContext$: function(start, end, text, _context) {
+ var t1 = new X.SourceSpanWithContext(_context, start, end, text);
+ t1.SourceSpanBase$3(start, end, text);
+ if (!C.JSString_methods.contains$1(_context, text))
+ H.throwExpression(P.ArgumentError$('The context line "' + _context + '" must contain "' + text + '".'));
+ if (B.findLineStart(_context, text, start.get$column()) == null)
+ H.throwExpression(P.ArgumentError$('The span text "' + text + '" must start at column ' + (start.get$column() + 1) + ' in a line within "' + _context + '".'));
+ return t1;
+ },
+ SourceSpanWithContext: function SourceSpanWithContext(t0, t1, t2, t3) {
+ var _ = this;
+ _._context = t0;
+ _.start = t1;
+ _.end = t2;
+ _.text = t3;
+ },
+ LazyChain: function LazyChain(t0) {
+ this._thunk = t0;
+ this._lazy_chain$_inner = null;
+ },
+ LazyChain_foldFrames_closure: function LazyChain_foldFrames_closure(t0, t1, t2) {
+ this.$this = t0;
+ this.predicate = t1;
+ this.terse = t2;
+ },
+ LazyChain_toTrace_closure: function LazyChain_toTrace_closure(t0) {
+ this.$this = t0;
+ },
+ StringScanner: function StringScanner() {
+ },
+ Declarer$: function(collectTraces, metadata, noRetry, platformVariables) {
+ var _null = null,
+ t1 = metadata == null ? O.Metadata_Metadata(_null, _null, _null, _null, _null, _null, _null, _null, _null, _null) : metadata,
+ t2 = platformVariables == null ? C.C_EmptyUnmodifiableSet : platformVariables,
+ t3 = type$.JSArray_of_dynamic_Function,
+ t4 = type$.JSArray_GroupEntry;
+ return new X.Declarer(_null, _null, t1, t2, _null, collectTraces, noRetry, H.setRuntimeTypeInfo([], t3), H.setRuntimeTypeInfo([], t3), H.setRuntimeTypeInfo([], t3), new R.Timeout(P.Duration$(0, 12), _null), H.setRuntimeTypeInfo([], t3), H.setRuntimeTypeInfo([], t4), H.setRuntimeTypeInfo([], t4));
+ },
+ Declarer: function Declarer(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13) {
+ var _ = this;
+ _._declarer$_parent = t0;
+ _._declarer$_name = t1;
+ _._metadata = t2;
+ _._platformVariables = t3;
+ _._declarer$_trace = t4;
+ _._collectTraces = t5;
+ _._noRetry = t6;
+ _._setUps = t7;
+ _._tearDowns = t8;
+ _._setUpAlls = t9;
+ _._timeout = t10;
+ _._tearDownAlls = t11;
+ _._entries = t12;
+ _._built = false;
+ _._soloEntries = t13;
+ },
+ Declarer_test_closure: function Declarer_test_closure(t0, t1) {
+ this.$this = t0;
+ this.body = t1;
+ },
+ Declarer_test__closure: function Declarer_test__closure(t0, t1) {
+ this.$this = t0;
+ this.body = t1;
+ },
+ Declarer_test___closure: function Declarer_test___closure(t0, t1) {
+ this.$this = t0;
+ this.body = t1;
+ },
+ Declarer_group_closure: function Declarer_group_closure(t0) {
+ this.body = t0;
+ },
+ Declarer_build_closure: function Declarer_build_closure(t0) {
+ this.$this = t0;
+ },
+ Declarer__runSetUps_closure: function Declarer__runSetUps_closure() {
+ },
+ Declarer__tearDownAll_closure: function Declarer__tearDownAll_closure(t0) {
+ this.$this = t0;
+ },
+ Declarer__tearDownAll__closure: function Declarer__tearDownAll__closure(t0) {
+ this.$this = t0;
+ },
+ Declarer__tearDownAll___closure: function Declarer__tearDownAll___closure(t0) {
+ this.$this = t0;
+ },
+ PrintSink: function PrintSink(t0) {
+ this._print_sink$_buffer = t0;
+ }
+ },
+ U = {
+ _expandSafe: function(start, end) {
+ if (start == null || end == null)
+ return null;
+ if (start.file !== end.file)
+ return null;
+ return start.expand$1(0, end);
+ },
+ VariableNode: function VariableNode(t0, t1) {
+ this.span = t0;
+ this.name = t1;
+ },
+ NotNode: function NotNode(t0, t1) {
+ this.span = t0;
+ this.child = t1;
+ },
+ OrNode: function OrNode(t0, t1) {
+ this.left = t0;
+ this.right = t1;
+ },
+ AndNode: function AndNode(t0, t1) {
+ this.left = t0;
+ this.right = t1;
+ },
+ ConditionalNode: function ConditionalNode(t0, t1, t2) {
+ this.condition = t0;
+ this.whenTrue = t1;
+ this.whenFalse = t2;
+ },
+ Highlighter__normalizeNewlines: function(span) {
+ var t1, endOffset, i, t2, t3, t4,
+ text = span.get$text();
+ if (!C.JSString_methods.contains$1(text, "\r\n"))
+ return span;
+ t1 = span.get$end();
+ endOffset = t1.get$offset(t1);
+ for (t1 = text.length - 1, i = 0; i < t1; ++i)
+ if (C.JSString_methods._codeUnitAt$1(text, i) === 13 && C.JSString_methods._codeUnitAt$1(text, i + 1) === 10)
+ --endOffset;
+ t1 = span.get$start();
+ t2 = span.get$sourceUrl();
+ t3 = span.get$end().get$line();
+ t2 = V.SourceLocation$(endOffset, span.get$end().get$column(), t3, t2);
+ t3 = H.stringReplaceAllUnchecked(text, "\r\n", "\n");
+ t4 = span.get$context();
+ return X.SourceSpanWithContext$(t1, t2, t3, H.stringReplaceAllUnchecked(t4, "\r\n", "\n"));
+ },
+ Highlighter__normalizeTrailingNewline: function(span) {
+ var context, text, start, end, t1, t2, t3;
+ if (!C.JSString_methods.endsWith$1(span.get$context(), "\n"))
+ return span;
+ if (C.JSString_methods.endsWith$1(span.get$text(), "\n\n"))
+ return span;
+ context = C.JSString_methods.substring$2(span.get$context(), 0, span.get$context().length - 1);
+ text = span.get$text();
+ start = span.get$start();
+ end = span.get$end();
+ if (C.JSString_methods.endsWith$1(span.get$text(), "\n")) {
+ t1 = B.findLineStart(span.get$context(), span.get$text(), span.get$start().get$column());
+ t2 = span.get$start().get$column();
+ if (typeof t1 !== "number")
+ return t1.$add();
+ t2 = t1 + t2 + span.get$length(span) === span.get$context().length;
+ t1 = t2;
+ } else
+ t1 = false;
+ if (t1) {
+ text = C.JSString_methods.substring$2(span.get$text(), 0, span.get$text().length - 1);
+ t1 = span.get$end();
+ t1 = t1.get$offset(t1);
+ t2 = span.get$sourceUrl();
+ t3 = span.get$end().get$line();
+ if (typeof t3 !== "number")
+ return t3.$sub();
+ end = V.SourceLocation$(t1 - 1, U.Highlighter__lastLineLength(text), t3 - 1, t2);
+ t1 = span.get$start();
+ t1 = t1.get$offset(t1);
+ t2 = span.get$end();
+ start = t1 === t2.get$offset(t2) ? end : span.get$start();
+ }
+ return X.SourceSpanWithContext$(start, end, text, context);
+ },
+ Highlighter__normalizeEndOfLine: function(span) {
+ var text, t1, t2, t3, t4;
+ if (span.get$end().get$column() !== 0)
+ return span;
+ if (span.get$end().get$line() == span.get$start().get$line())
+ return span;
+ text = C.JSString_methods.substring$2(span.get$text(), 0, span.get$text().length - 1);
+ t1 = span.get$start();
+ t2 = span.get$end();
+ t2 = t2.get$offset(t2);
+ t3 = span.get$sourceUrl();
+ t4 = span.get$end().get$line();
+ if (typeof t4 !== "number")
+ return t4.$sub();
+ return X.SourceSpanWithContext$(t1, V.SourceLocation$(t2 - 1, U.Highlighter__lastLineLength(text), t4 - 1, t3), text, span.get$context());
+ },
+ Highlighter__lastLineLength: function(text) {
+ var t1 = text.length;
+ if (t1 === 0)
+ return 0;
+ if (C.JSString_methods.codeUnitAt$1(text, t1 - 1) === 10)
+ return t1 === 1 ? 0 : t1 - C.JSString_methods.lastIndexOf$2(text, "\n", t1 - 2) - 1;
+ else
+ return t1 - C.JSString_methods.lastIndexOf$1(text, "\n") - 1;
+ },
+ Highlighter: function Highlighter(t0, t1, t2, t3, t4) {
+ var _ = this;
+ _._span = t0;
+ _._color = t1;
+ _._multiline = t2;
+ _._paddingBeforeSidebar = t3;
+ _._highlighter$_buffer = t4;
+ },
+ Highlighter__writeFirstLine_closure: function Highlighter__writeFirstLine_closure(t0, t1) {
+ this.$this = t0;
+ this.line = t1;
+ },
+ Highlighter__writeFirstLine_closure0: function Highlighter__writeFirstLine_closure0(t0, t1) {
+ this.$this = t0;
+ this.textInside = t1;
+ },
+ Highlighter__writeFirstLine_closure1: function Highlighter__writeFirstLine_closure1(t0, t1) {
+ this._box_0 = t0;
+ this.$this = t1;
+ },
+ Highlighter__writeFirstLine_closure2: function Highlighter__writeFirstLine_closure2(t0, t1) {
+ this._box_0 = t0;
+ this.$this = t1;
+ },
+ Highlighter__writeIntermediateLines_closure: function Highlighter__writeIntermediateLines_closure(t0, t1) {
+ this.$this = t0;
+ this.line = t1;
+ },
+ Highlighter__writeLastLine_closure: function Highlighter__writeLastLine_closure(t0, t1) {
+ this.$this = t0;
+ this.line = t1;
+ },
+ Highlighter__writeLastLine_closure0: function Highlighter__writeLastLine_closure0(t0, t1) {
+ this.$this = t0;
+ this.textInside = t1;
+ },
+ Highlighter__writeLastLine_closure1: function Highlighter__writeLastLine_closure1(t0, t1) {
+ this._box_0 = t0;
+ this.$this = t1;
+ },
+ Highlighter__writeSidebar_closure: function Highlighter__writeSidebar_closure(t0, t1, t2) {
+ this.$this = t0;
+ this.line = t1;
+ this.end = t2;
+ },
+ Chain_capture: function(callback, errorZone, when, $T) {
+ var t1, spec, _null = null;
+ if (!when)
+ return P.runZoned(callback, _null, _null, _null, $T);
+ if (typeof WeakMap == "function")
+ t1 = new WeakMap();
+ else {
+ t1 = $.Expando__keyCount;
+ $.Expando__keyCount = t1 + 1;
+ t1 = "expando$key$" + t1;
+ }
+ spec = new O.StackZoneSpecification(new P.Expando(t1, "stack chains", type$.Expando__Node), _null, false);
+ t1 = type$.dynamic;
+ return P.runZoned(new U.Chain_capture_closure(callback, $T), _null, P._ZoneSpecification$(_null, _null, spec.get$_stack_zone_specification$_errorCallback(), _null, _null, _null, spec.get$_stack_zone_specification$_registerBinaryCallback(), spec.get$_stack_zone_specification$_registerCallback(), spec.get$_stack_zone_specification$_registerUnaryCallback(), _null, _null, _null, _null), P.LinkedHashMap_LinkedHashMap$_literal([$.$get$_specKey(), spec, $.$get$StackZoneSpecification_disableKey(), false], t1, t1), $T);
+ },
+ Chain_Chain$current: function() {
+ var t1 = $.Zone__current,
+ t2 = $.$get$_specKey(),
+ t3 = type$.StackZoneSpecification;
+ if (t3._check(t1.$index(0, t2)) != null) {
+ t1 = t3._check($.Zone__current.$index(0, t2));
+ t2 = t1._currentTrace$1(3);
+ t1 = t1._currentNode;
+ return new O._Node(Y.Trace_Trace$from(t2), t1).toChain$0();
+ }
+ return new X.LazyChain(new U.Chain_Chain$current_closure(U.Chain_Chain$forTrace(P.StackTrace_current()), 0));
+ },
+ Chain_Chain$forTrace: function(trace) {
+ var t1, t2, t3;
+ if (type$.Chain._is(trace))
+ return trace;
+ t1 = $.Zone__current;
+ t2 = $.$get$_specKey();
+ t3 = type$.StackZoneSpecification;
+ if (t3._check(t1.$index(0, t2)) != null)
+ return t3._check($.Zone__current.$index(0, t2)).chainFor$1(trace);
+ t1 = type$.Trace;
+ if (t1._is(trace))
+ return new U.Chain(P.List_List$unmodifiable(H.setRuntimeTypeInfo([trace], type$.JSArray_Trace), t1));
+ return new X.LazyChain(new U.Chain_Chain$forTrace_closure(trace));
+ },
+ Chain_Chain$parse: function(chain) {
+ var _s26_ = "<asynchronous suspension>\n",
+ _s51_ = "===== asynchronous gap ===========================\n";
+ if (chain.length === 0)
+ return new U.Chain(P.List_List$unmodifiable(H.setRuntimeTypeInfo([], type$.JSArray_Trace), type$.Trace));
+ if (C.JSString_methods.contains$1(chain, _s26_))
+ return new U.Chain(P.List_List$unmodifiable(new H.MappedListIterable(H.setRuntimeTypeInfo(chain.split(_s26_), type$.JSArray_String), type$.Trace_Function_String._check(new U.Chain_Chain$parse_closure()), type$.MappedListIterable_String_Trace), type$.Trace));
+ if (!C.JSString_methods.contains$1(chain, _s51_))
+ return new U.Chain(P.List_List$unmodifiable(H.setRuntimeTypeInfo([Y.Trace_Trace$parse(chain)], type$.JSArray_Trace), type$.Trace));
+ return new U.Chain(P.List_List$unmodifiable(new H.MappedListIterable(H.setRuntimeTypeInfo(chain.split(_s51_), type$.JSArray_String), type$.Trace_Function_String._check(new U.Chain_Chain$parse_closure0()), type$.MappedListIterable_String_Trace), type$.Trace));
+ },
+ Chain: function Chain(t0) {
+ this.traces = t0;
+ },
+ Chain_capture_closure: function Chain_capture_closure(t0, t1) {
+ this.callback = t0;
+ this.T = t1;
+ },
+ Chain_Chain$current_closure: function Chain_Chain$current_closure(t0, t1) {
+ this.chain = t0;
+ this.level = t1;
+ },
+ Chain_Chain$forTrace_closure: function Chain_Chain$forTrace_closure(t0) {
+ this.trace = t0;
+ },
+ Chain_Chain$parse_closure: function Chain_Chain$parse_closure() {
+ },
+ Chain_Chain$parse_closure0: function Chain_Chain$parse_closure0() {
+ },
+ Chain_foldFrames_closure: function Chain_foldFrames_closure(t0, t1) {
+ this.predicate = t0;
+ this.terse = t1;
+ },
+ Chain_foldFrames_closure0: function Chain_foldFrames_closure0(t0) {
+ this.terse = t0;
+ },
+ Chain_toTrace_closure: function Chain_toTrace_closure() {
+ },
+ Chain_toString_closure0: function Chain_toString_closure0() {
+ },
+ Chain_toString__closure0: function Chain_toString__closure0() {
+ },
+ Chain_toString_closure: function Chain_toString_closure(t0) {
+ this.longest = t0;
+ },
+ Chain_toString__closure: function Chain_toString__closure(t0) {
+ this.longest = t0;
+ },
+ Invoker_guard: function(callback, $T) {
+ var _null = null;
+ return P.runZoned(callback, _null, P._ZoneSpecification$(_null, _null, _null, _null, new U.Invoker_guard_closure(), _null, _null, _null, _null, _null, _null, _null, _null), _null, $T);
+ },
+ LocalTest: function LocalTest(t0, t1, t2, t3, t4, t5) {
+ var _ = this;
+ _.name = t0;
+ _.metadata = t1;
+ _.trace = t2;
+ _.isScaffoldAll = t3;
+ _._body = t4;
+ _._guarded = t5;
+ },
+ Invoker: function Invoker(t0, t1, t2, t3, t4, t5, t6) {
+ var _ = this;
+ _._invoker$_controller = null;
+ _._guarded = t0;
+ _._closableKey = t1;
+ _._onCloseCompleter = t2;
+ _._outstandingCallbackZones = t3;
+ _._counterKey = t4;
+ _._runCount = 0;
+ _._timeoutTimer = _._invokerZone = null;
+ _._invoker$_tearDowns = t5;
+ _._printsOnFailure = t6;
+ },
+ Invoker_guard_closure: function Invoker_guard_closure() {
+ },
+ Invoker_guard__closure: function Invoker_guard__closure(t0, t1, t2, t3) {
+ var _ = this;
+ _.invoker = t0;
+ _.zone = t1;
+ _.error = t2;
+ _.stackTrace = t3;
+ },
+ Invoker_waitForOutstandingCallbacks_closure: function Invoker_waitForOutstandingCallbacks_closure(t0, t1, t2, t3) {
+ var _ = this;
+ _._box_0 = t0;
+ _.$this = t1;
+ _.fn = t2;
+ _.counter = t3;
+ },
+ Invoker_waitForOutstandingCallbacks_closure0: function Invoker_waitForOutstandingCallbacks_closure0(t0, t1) {
+ this._box_0 = t0;
+ this.$this = t1;
+ },
+ Invoker_heartbeat_message: function Invoker_heartbeat_message(t0) {
+ this.timeout = t0;
+ },
+ Invoker_heartbeat_closure: function Invoker_heartbeat_closure(t0, t1, t2) {
+ this.$this = t0;
+ this.message = t1;
+ this.timeout = t2;
+ },
+ Invoker_heartbeat__closure: function Invoker_heartbeat__closure(t0, t1, t2) {
+ this.$this = t0;
+ this.message = t1;
+ this.timeout = t2;
+ },
+ Invoker__handleError_closure: function Invoker__handleError_closure(t0) {
+ this._box_0 = t0;
+ },
+ Invoker__handleError_closure0: function Invoker__handleError_closure0(t0) {
+ this.$this = t0;
+ },
+ Invoker__onRun_closure: function Invoker__onRun_closure(t0, t1) {
+ this.$this = t0;
+ this.outstandingCallbacksForBody = t1;
+ },
+ Invoker__onRun__closure: function Invoker__onRun__closure(t0, t1) {
+ this.$this = t0;
+ this.outstandingCallbacksForBody = t1;
+ },
+ Invoker__onRun___closure: function Invoker__onRun___closure(t0) {
+ this.$this = t0;
+ },
+ Invoker__onRun____closure: function Invoker__onRun____closure(t0) {
+ this.$this = t0;
+ },
+ Invoker__onRun___closure0: function Invoker__onRun___closure0(t0) {
+ this.$this = t0;
+ },
+ _AsyncCounter: function _AsyncCounter(t0) {
+ this._count = 1;
+ this._invoker$_completer = t0;
+ },
+ StackTraceFormatter: function StackTraceFormatter(t0, t1) {
+ this._mapper = null;
+ this._except = t0;
+ this._only = t1;
+ },
+ StackTraceFormatter_formatStackTrace_closure: function StackTraceFormatter_formatStackTrace_closure(t0) {
+ this.$this = t0;
+ },
+ Suite__filterGroup: function(group, platform) {
+ var t1, _null = null,
+ filtered = group.forPlatform$1(platform);
+ if (filtered != null)
+ return filtered;
+ t1 = P.List_List$unmodifiable(H.setRuntimeTypeInfo([], type$.JSArray_GroupEntry), type$.GroupEntry);
+ return new O.Group(_null, group.metadata, _null, t1, _null, _null);
+ },
+ Suite: function Suite(t0, t1, t2) {
+ this.platform = t0;
+ this.path = t1;
+ this.group = t2;
+ },
+ SuiteConfiguration__list: function(input, $T) {
+ return null;
+ },
+ SuiteConfiguration__map: function(input, $K, $V) {
+ return C.Map_empty1;
+ },
+ SuiteConfiguration: function SuiteConfiguration() {
+ },
+ RemoteException_serialize: function(error, stackTrace) {
+ var exception, supertype, t1, message = null;
+ if (typeof error == "string")
+ message = error;
+ else
+ try {
+ message = J.toString$0$(J.get$message$x(error));
+ } catch (exception) {
+ if (!type$.NoSuchMethodError._is(H.unwrapException(exception)))
+ throw exception;
+ }
+ supertype = error instanceof G.TestFailure ? "TestFailure" : null;
+ t1 = J.getInterceptor$(error);
+ return P.LinkedHashMap_LinkedHashMap$_literal(["message", message, "type", t1.get$runtimeType(error).toString$0(0), "supertype", supertype, "toString", t1.toString$0(error), "stackChain", J.toString$0$(U.Chain_Chain$forTrace(stackTrace))], type$.String, type$.dynamic);
+ }
+ },
+ R = {IntersectionSelector: function IntersectionSelector(t0, t1) {
+ this._selector1 = t0;
+ this._selector2 = t1;
+ }, StreamChannel: function StreamChannel() {
+ }, _StreamChannel: function _StreamChannel(t0, t1, t2) {
+ this.stream = t0;
+ this.sink = t1;
+ this.$ti = t2;
+ }, StreamChannelMixin: function StreamChannelMixin() {
+ }, Timeout: function Timeout(t0, t1) {
+ this.duration = t0;
+ this.scaleFactor = t1;
+ }, ExpandedReporter: function ExpandedReporter(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12) {
+ var _ = this;
+ _._expanded$_color = t0;
+ _._green = t1;
+ _._red = t2;
+ _._yellow = t3;
+ _._gray = t4;
+ _._bold = t5;
+ _._noColor = t6;
+ _._engine = t7;
+ _._printPath = t8;
+ _._printPlatform = t9;
+ _._stopwatch = t10;
+ _._lastProgressSuffix = _._lastProgressMessage = _._lastProgressFailed = _._lastProgressSkipped = _._lastProgressPassed = null;
+ _._subscriptions = t11;
+ _._expanded$_sink = t12;
+ }, ExpandedReporter__onTestStarted_closure: function ExpandedReporter__onTestStarted_closure(t0, t1) {
+ this.$this = t0;
+ this.liveTest = t1;
+ }, ExpandedReporter__onTestStarted_closure0: function ExpandedReporter__onTestStarted_closure0(t0, t1) {
+ this.$this = t0;
+ this.liveTest = t1;
+ }, ExpandedReporter__onTestStarted_closure1: function ExpandedReporter__onTestStarted_closure1(t0, t1) {
+ this.$this = t0;
+ this.liveTest = t1;
+ },
+ _declarer: function() {
+ var t1,
+ declarer = type$.Declarer._as($.Zone__current.$index(0, C.Symbol_Drw));
+ if (declarer != null)
+ return declarer;
+ t1 = $._globalDeclarer;
+ if (t1 != null)
+ return t1;
+ $._globalDeclarer = X.Declarer$(false, null, false, null);
+ P.scheduleMicrotask(new R._declarer_closure());
+ return $._globalDeclarer;
+ },
+ test: function(description, body) {
+ var _null = null;
+ R._declarer().test$9$onPlatform$retry$skip$solo$tags$testOn$timeout(description, body, _null, _null, _null, false, _null, _null, _null);
+ return;
+ },
+ group: function(description, body) {
+ var _null = null;
+ R._declarer().group$9$onPlatform$retry$skip$solo$tags$testOn$timeout(description, body, _null, _null, _null, false, _null, _null, _null);
+ return;
+ },
+ _declarer_closure: function _declarer_closure() {
+ },
+ _declarer__closure: function _declarer__closure(t0) {
+ this.engine = t0;
+ },
+ main: function() {
+ L.internalBootstrapBrowserTest(new R.main_closure());
+ },
+ main_closure: function main_closure() {
+ }
+ },
+ B = {RecursiveVisitor: function RecursiveVisitor() {
+ }, InternalStyle: function InternalStyle() {
+ },
+ StreamChannelController$: function(allowForeignErrors, sync, $T) {
+ var _null = null,
+ t1 = new B.StreamChannelController($T._eval$1("StreamChannelController<0>")),
+ localToForeignController = P.StreamController_StreamController(_null, _null, true, $T),
+ foreignToLocalController = P.StreamController_StreamController(_null, _null, true, $T),
+ t2 = H._instanceType(foreignToLocalController),
+ t3 = H._instanceType(localToForeignController);
+ t1.set$_local(K.GuaranteeChannel$(new P._ControllerStream(foreignToLocalController, t2._eval$1("_ControllerStream<1>")), new P._StreamSinkWrapper(localToForeignController, t3._eval$1("_StreamSinkWrapper<1>")), true, $T));
+ t1.set$_foreign(K.GuaranteeChannel$(new P._ControllerStream(localToForeignController, t3._eval$1("_ControllerStream<1>")), new P._StreamSinkWrapper(foreignToLocalController, t2._eval$1("_StreamSinkWrapper<1>")), allowForeignErrors, $T));
+ return t1;
+ },
+ StreamChannelController: function StreamChannelController(t0) {
+ this._foreign = this._local = null;
+ this.$ti = t0;
+ },
+ Runtime_Runtime$deserialize: function(serialized) {
+ var $parent, t1, t2, t3,
+ _s10_ = "identifier";
+ if (typeof serialized == "string")
+ return C.JSArray_methods.firstWhere$1(C.List_m7e, new B.Runtime_Runtime$deserialize_closure(serialized));
+ type$.Map_dynamic_dynamic._as(serialized);
+ $parent = serialized.$index(0, "parent");
+ if ($parent != null) {
+ t1 = H._asStringNullable(serialized.$index(0, "name"));
+ t2 = H._asStringNullable(serialized.$index(0, _s10_));
+ t3 = B.Runtime_Runtime$deserialize($parent);
+ return new B.Runtime(t1, t2, t3, t3.isDartVM, t3.isBrowser, t3.isJS, t3.isBlink, t3.isHeadless);
+ }
+ return new B.Runtime(H._asStringNullable(serialized.$index(0, "name")), H._asStringNullable(serialized.$index(0, _s10_)), null, H._asBoolNullable(serialized.$index(0, "isDartVM")), H._asBoolNullable(serialized.$index(0, "isBrowser")), H._asBoolNullable(serialized.$index(0, "isJS")), H._asBoolNullable(serialized.$index(0, "isBlink")), H._asBoolNullable(serialized.$index(0, "isHeadless")));
+ },
+ Runtime: function Runtime(t0, t1, t2, t3, t4, t5, t6, t7) {
+ var _ = this;
+ _.name = t0;
+ _.identifier = t1;
+ _.parent = t2;
+ _.isDartVM = t3;
+ _.isBrowser = t4;
+ _.isJS = t5;
+ _.isBlink = t6;
+ _.isHeadless = t7;
+ },
+ Runtime_Runtime$deserialize_closure: function Runtime_Runtime$deserialize_closure(t0) {
+ this.serialized = t0;
+ },
+ indent: function(string, first) {
+ var size = first == null ? 2 : first.length;
+ return B.prefixLines(string, C.JSString_methods.$mul(" ", size), first);
+ },
+ toSentence: function(iter) {
+ var result,
+ t1 = iter.length;
+ if (t1 === 1)
+ return J.toString$0$(C.JSArray_methods.get$first(iter));
+ result = H.SubListIterable$(iter, 0, t1 - 1, H._arrayInstanceType(iter)._precomputed1).join$1(0, ", ");
+ if (iter.length > 2)
+ result += ",";
+ return result + " and " + H.S(C.JSArray_methods.get$last(iter));
+ },
+ pluralize: function($name, number) {
+ if (number === 1)
+ return $name;
+ return $name + "s";
+ },
+ prefixLines: function(text, prefix, first) {
+ var single, lines, t1, t2;
+ if (first == null)
+ first = prefix;
+ single = first;
+ lines = H.setRuntimeTypeInfo(text.split("\n"), type$.JSArray_String);
+ if (lines.length === 1)
+ return single + text;
+ t1 = first + H.S(C.JSArray_methods.get$first(lines)) + "\n";
+ for (t2 = H.SubListIterable$(lines, 1, null, type$.String).take$1(0, lines.length - 2), t2 = new H.ListIterator(t2, t2.get$length(t2), t2.$ti._eval$1("ListIterator<ListIterable.E>")); t2.moveNext$0();)
+ t1 += prefix + H.S(t2.__internal$_current) + "\n";
+ t1 += prefix + H.S(C.JSArray_methods.get$last(lines));
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ closure0: function closure0() {
+ },
+ LiveSuiteController$: function(_suite) {
+ var t1 = $.Zone__current,
+ t2 = H.setRuntimeTypeInfo([], type$.JSArray_dynamic),
+ t3 = $.Zone__current,
+ t4 = type$._Future_dynamic,
+ t5 = type$._AsyncCompleter_dynamic,
+ t6 = type$.LiveTest;
+ t5 = new B.LiveSuiteController(_suite, new F.FutureGroup(new P._AsyncCompleter(new P._Future(t1, type$._Future_List_dynamic), type$._AsyncCompleter_List_dynamic), t2, type$.FutureGroup_dynamic), new P._AsyncCompleter(new P._Future(t3, t4), t5), new P._SyncBroadcastStreamController(null, null, type$._SyncBroadcastStreamController_LiveTest), P.LinkedHashSet_LinkedHashSet$_empty(t6), P.LinkedHashSet_LinkedHashSet$_empty(t6), P.LinkedHashSet_LinkedHashSet$_empty(t6), new S.AsyncMemoizer(new P._AsyncCompleter(new P._Future(t3, t4), t5), type$.AsyncMemoizer_dynamic));
+ t5.LiveSuiteController$1(_suite);
+ return t5;
+ },
+ _LiveSuite: function _LiveSuite(t0) {
+ this._live_suite_controller$_controller = t0;
+ },
+ LiveSuiteController: function LiveSuiteController(t0, t1, t2, t3, t4, t5, t6, t7) {
+ var _ = this;
+ _._liveSuite = null;
+ _._live_suite_controller$_suite = t0;
+ _._onCompleteGroup = t1;
+ _._live_suite_controller$_onCloseCompleter = t2;
+ _._onTestStartedController = t3;
+ _._passed = t4;
+ _._skipped = t5;
+ _._failed = t6;
+ _._live_suite_controller$_active = null;
+ _._live_suite_controller$_closeMemo = t7;
+ },
+ LiveSuiteController_closure: function LiveSuiteController_closure(t0) {
+ this.$this = t0;
+ },
+ LiveSuiteController_closure0: function LiveSuiteController_closure0() {
+ },
+ LiveSuiteController_reportLiveTest_closure: function LiveSuiteController_reportLiveTest_closure(t0, t1, t2) {
+ this.$this = t0;
+ this.liveTest = t1;
+ this.countSuccess = t2;
+ },
+ LiveSuiteController_close_closure: function LiveSuiteController_close_closure(t0) {
+ this.$this = t0;
+ },
+ asUri: function(uri, $name) {
+ if (type$.Uri._is(uri))
+ return uri;
+ throw H.wrapException(P.ArgumentError$value(uri, $name, "Must be a String or a Uri."));
+ },
+ ensureTrailingSlash: function(uri) {
+ var t1;
+ if (uri.get$pathSegments().length === 0)
+ return uri.replace$1$path(0, "/");
+ t1 = C.JSArray_methods.get$last(uri.get$pathSegments());
+ t1.toString;
+ if (J.get$length$asx(t1) === 0)
+ return uri;
+ t1 = uri.get$pathSegments();
+ t1 = H.setRuntimeTypeInfo(t1.slice(0), H._arrayInstanceType(t1)._eval$1("JSArray<1>"));
+ C.JSArray_methods.add$1(t1, "");
+ return uri.replace$1$pathSegments(0, t1);
+ },
+ isAlphabetic: function(char) {
+ var t1;
+ if (!(char >= 65 && char <= 90))
+ t1 = char >= 97 && char <= 122;
+ else
+ t1 = true;
+ return t1;
+ },
+ isDriveLetter: function(path, index) {
+ var t1 = path.length,
+ t2 = index + 2;
+ if (t1 < t2)
+ return false;
+ if (!B.isAlphabetic(C.JSString_methods.codeUnitAt$1(path, index)))
+ return false;
+ if (C.JSString_methods.codeUnitAt$1(path, index + 1) !== 58)
+ return false;
+ if (t1 === t2)
+ return true;
+ return C.JSString_methods.codeUnitAt$1(path, t2) === 47;
+ },
+ countCodeUnits: function(string, codeUnit) {
+ var t1, count;
+ for (t1 = new H.CodeUnits(string), t1 = new H.ListIterator(t1, t1.get$length(t1), type$.CodeUnits._eval$1("ListIterator<ListMixin.E>")), count = 0; t1.moveNext$0();)
+ if (t1.__internal$_current === codeUnit)
+ ++count;
+ return count;
+ },
+ findLineStart: function(context, text, column) {
+ var beginningOfLine, index, lineStart;
+ if (text.length === 0)
+ for (beginningOfLine = 0; true;) {
+ index = C.JSString_methods.indexOf$2(context, "\n", beginningOfLine);
+ if (index === -1)
+ return context.length - beginningOfLine >= column ? beginningOfLine : null;
+ if (index - beginningOfLine >= column)
+ return beginningOfLine;
+ beginningOfLine = index + 1;
+ }
+ index = C.JSString_methods.indexOf$1(context, text);
+ for (; index !== -1;) {
+ lineStart = index === 0 ? 0 : C.JSString_methods.lastIndexOf$2(context, "\n", index - 1) + 1;
+ if (column === index - lineStart)
+ return lineStart;
+ index = C.JSString_methods.indexOf$2(context, text, index + 1);
+ }
+ return null;
+ },
+ validateErrorArgs: function(string, match, position, $length) {
+ var t1;
+ if (position < 0)
+ throw H.wrapException(P.RangeError$("position must be greater than or equal to 0."));
+ else if (position > string.length)
+ throw H.wrapException(P.RangeError$("position must be less than or equal to the string length."));
+ t1 = position + $length > string.length;
+ if (t1)
+ throw H.wrapException(P.RangeError$("position plus length must not go beyond the end of the string."));
+ }
+ },
+ Q = {
+ QueueList$: function($E) {
+ var t1 = new Q.QueueList($E._eval$1("QueueList<0>")),
+ t2 = new Array(8);
+ t2.fixed$length = Array;
+ t1.set$_queue_list$_table(H.setRuntimeTypeInfo(t2, $E._eval$1("JSArray<0>")));
+ return t1;
+ },
+ QueueList__nextPowerOf2: function(number) {
+ var nextNumber;
+ H.assertHelper(number > 0);
+ number = (number << 1 >>> 0) - 1;
+ for (; true; number = nextNumber) {
+ nextNumber = (number & number - 1) >>> 0;
+ if (nextNumber === 0)
+ return number;
+ }
+ },
+ QueueList: function QueueList(t0) {
+ var _ = this;
+ _._queue_list$_table = null;
+ _._queue_list$_tail = _._queue_list$_head = 0;
+ _.$ti = t0;
+ },
+ _QueueList_Object_ListMixin: function _QueueList_Object_ListMixin() {
+ }
+ },
+ M = {UnionSet: function UnionSet(t0, t1, t2) {
+ this._sets = t0;
+ this._disjoint = t1;
+ this.$ti = t2;
+ }, UnionSet_length_closure: function UnionSet_length_closure(t0) {
+ this.$this = t0;
+ }, UnionSet__iterable_closure: function UnionSet__iterable_closure(t0) {
+ this.$this = t0;
+ }, _UnionSet_SetBase_UnmodifiableSetMixin: function _UnionSet_SetBase_UnmodifiableSetMixin() {
+ }, _DelegatingIterableBase: function _DelegatingIterableBase() {
+ }, DelegatingIterable: function DelegatingIterable() {
+ }, DelegatingSet: function DelegatingSet(t0, t1) {
+ this._base = t0;
+ this.$ti = t1;
+ }, TypeMatcher: function TypeMatcher() {
+ },
+ wrapMatcher: function(x) {
+ var _s18_ = "satisfies function";
+ if (x instanceof G.Matcher)
+ return x;
+ else if (type$.bool_Function_Object._is(x))
+ return new Y._Predicate(x, _s18_, type$._Predicate_Object);
+ else if (type$.bool_Function_Null._is(x))
+ return new Y._Predicate(new M.wrapMatcher_closure(x), _s18_, type$._Predicate_dynamic);
+ else
+ return typeof x == "string" ? new D._StringEqualsMatcher(x) : new D._DeepMatcher(x, 100);
+ },
+ escape: function(str) {
+ str.toString;
+ return C.JSString_methods.splitMapJoin$2$onMatch(H.stringReplaceAllUnchecked(str, "\\", "\\\\"), $.$get$_escapeRegExp(), type$.String_Function_Match._check(new M.escape_closure()));
+ },
+ _getHexLiteral: function(input) {
+ var t1, it, result;
+ H._checkStringNullable(input);
+ input.toString;
+ t1 = new P.Runes(input);
+ it = t1.get$iterator(t1);
+ if (!it.moveNext$0())
+ H.throwExpression(H.IterableElementError_noElement());
+ result = it.get$current();
+ if (it.moveNext$0())
+ H.throwExpression(H.IterableElementError_tooMany());
+ return "\\x" + C.JSString_methods.padLeft$2(J.toRadixString$1$n(result, 16).toUpperCase(), 2, "0");
+ },
+ wrapMatcher_closure: function wrapMatcher_closure(t0) {
+ this.x = t0;
+ },
+ escape_closure: function escape_closure() {
+ },
+ Context_Context: function(style) {
+ var current = style == null ? D.current() : ".";
+ if (style == null)
+ style = $.$get$Style_platform();
+ return new M.Context(style, current);
+ },
+ _parseUri: function(uri) {
+ if (type$.Uri._is(uri))
+ return uri;
+ throw H.wrapException(P.ArgumentError$value(uri, "uri", "Value must be a String or a Uri"));
+ },
+ _validateArgList: function(method, args) {
+ var numArgs, i, numArgs0, message, t1, t2, t3;
+ for (numArgs = args.length, i = 1; i < numArgs; ++i) {
+ if (args[i] == null || args[i - 1] != null)
+ continue;
+ for (; numArgs >= 1; numArgs = numArgs0) {
+ numArgs0 = numArgs - 1;
+ if (args[numArgs0] != null)
+ break;
+ }
+ message = new P.StringBuffer("");
+ t1 = method + "(";
+ message._contents = t1;
+ t2 = H.SubListIterable$(args, 0, numArgs, H._arrayInstanceType(args)._precomputed1);
+ t3 = t2.$ti;
+ t3 = t1 + new H.MappedListIterable(t2, t3._eval$1("String(ListIterable.E)")._check(new M._validateArgList_closure()), t3._eval$1("MappedListIterable<ListIterable.E,String>")).join$1(0, ", ");
+ message._contents = t3;
+ message._contents = t3 + ("): part " + (i - 1) + " was null, but part " + i + " was not.");
+ throw H.wrapException(P.ArgumentError$(message.toString$0(0)));
+ }
+ },
+ Context: function Context(t0, t1) {
+ this.style = t0;
+ this._context$_current = t1;
+ },
+ Context_join_closure: function Context_join_closure() {
+ },
+ Context_joinAll_closure: function Context_joinAll_closure() {
+ },
+ Context_split_closure: function Context_split_closure() {
+ },
+ _validateArgList_closure: function _validateArgList_closure() {
+ },
+ _PathDirection: function _PathDirection(t0) {
+ this.name = t0;
+ },
+ _PathRelation: function _PathRelation(t0) {
+ this.name = t0;
+ }
+ },
+ D = {
+ _StringEqualsMatcher__writeLeading: function(buff, s, start) {
+ var t1 = buff._contents;
+ if (start > 10) {
+ t1 += "... ";
+ buff._contents = t1;
+ buff._contents = t1 + C.JSString_methods.substring$2(s, start - 10, start);
+ } else
+ buff._contents = t1 + C.JSString_methods.substring$2(s, 0, start);
+ },
+ _StringEqualsMatcher__writeTrailing: function(buff, s, start) {
+ var t1 = start + 10,
+ t2 = buff._contents;
+ if (t1 > s.length)
+ buff._contents = t2 + C.JSString_methods.substring$1(s, start);
+ else {
+ t1 = t2 + C.JSString_methods.substring$2(s, start, t1);
+ buff._contents = t1;
+ buff._contents = t1 + " ...";
+ }
+ },
+ _StringEqualsMatcher: function _StringEqualsMatcher(t0) {
+ this._equals_matcher$_value = t0;
+ },
+ _DeepMatcher: function _DeepMatcher(t0, t1) {
+ this._expected = t0;
+ this._limit = t1;
+ },
+ _DeepMatcher__compareSets_closure: function _DeepMatcher__compareSets_closure(t0, t1, t2, t3) {
+ var _ = this;
+ _.matcher = t0;
+ _.expectedElement = t1;
+ _.location = t2;
+ _.depth = t3;
+ },
+ PackageRootResolver: function PackageRootResolver(t0) {
+ this.packageRoot = t0;
+ },
+ SourceLocationMixin: function SourceLocationMixin() {
+ },
+ _MultiChannel$: function(_inner, $T) {
+ var t1 = type$.int;
+ t1 = new D._MultiChannel(_inner, B.StreamChannelController$(true, true, $T), P.LinkedHashMap_LinkedHashMap$_empty(t1, $T._eval$1("StreamChannelController<0>")), P.LinkedHashSet_LinkedHashSet(t1), P.LinkedHashSet_LinkedHashSet(t1), $T._eval$1("_MultiChannel<0>"));
+ t1._MultiChannel$1(_inner, $T);
+ return t1;
+ },
+ _MultiChannel: function _MultiChannel(t0, t1, t2, t3, t4, t5) {
+ var _ = this;
+ _._multi_channel$_inner = t0;
+ _._innerStreamSubscription = null;
+ _._mainController = t1;
+ _._controllers = t2;
+ _._pendingIds = t3;
+ _._closedIds = t4;
+ _._nextId = 1;
+ _.$ti = t5;
+ },
+ _MultiChannel_closure: function _MultiChannel_closure(t0, t1) {
+ this.$this = t0;
+ this.T = t1;
+ },
+ _MultiChannel_closure0: function _MultiChannel_closure0(t0) {
+ this.$this = t0;
+ },
+ _MultiChannel_closure1: function _MultiChannel_closure1(t0, t1) {
+ this.$this = t0;
+ this.T = t1;
+ },
+ _MultiChannel__closure: function _MultiChannel__closure(t0, t1, t2) {
+ this.$this = t0;
+ this.id = t1;
+ this.T = t2;
+ },
+ _MultiChannel_virtualChannel_closure: function _MultiChannel_virtualChannel_closure(t0, t1) {
+ this._box_0 = t0;
+ this.$this = t1;
+ },
+ _MultiChannel_virtualChannel_closure0: function _MultiChannel_virtualChannel_closure0(t0, t1) {
+ this._box_0 = t0;
+ this.$this = t1;
+ },
+ VirtualChannel: function VirtualChannel(t0, t1, t2, t3, t4) {
+ var _ = this;
+ _._multi_channel$_parent = t0;
+ _.id = t1;
+ _.stream = t2;
+ _.sink = t3;
+ _.$ti = t4;
+ },
+ Message: function Message(t0, t1) {
+ this.type = t0;
+ this.text = t1;
+ },
+ MessageType: function MessageType(t0) {
+ this.name = t0;
+ },
+ current: function() {
+ var exception, t1, path, lastIndex, uri = null;
+ try {
+ uri = P.Uri_base();
+ } catch (exception) {
+ if (type$.Exception._is(H.unwrapException(exception))) {
+ t1 = $._current;
+ if (t1 != null)
+ return t1;
+ throw exception;
+ } else
+ throw exception;
+ }
+ if (J.$eq$(uri, $._currentUriBase))
+ return $._current;
+ $._currentUriBase = uri;
+ if ($.$get$Style_platform() == $.$get$Style_url())
+ return $._current = uri.resolve$1(".").toString$0(0);
+ else {
+ path = uri.toFilePath$0();
+ t1 = path.length;
+ lastIndex = t1 - 1;
+ if (lastIndex < 0)
+ return H.ioore(path, lastIndex);
+ t1 = path[lastIndex];
+ H.assertHelper(t1 === "/" || t1 === "\\");
+ return $._current = lastIndex === 0 ? path : C.JSString_methods.substring$2(path, 0, lastIndex);
+ }
+ }
+ },
+ Z = {
+ prettyPrint: function(object, maxItems, maxLineLength) {
+ return new Z.prettyPrint__prettyPrint(maxItems, maxLineLength).call$4(object, 0, P.LinkedHashSet_LinkedHashSet$_empty(type$.Object), true);
+ },
+ _typeName: function(x) {
+ if (type$.Type._is(x))
+ return "Type";
+ if (type$.Uri._is(x))
+ return "Uri";
+ if (type$.Set_dynamic._is(x))
+ return "Set";
+ return J.get$runtimeType$(x).toString$0(0);
+ },
+ _escapeString: function(source) {
+ var t1 = M.escape(H._checkStringNullable(source));
+ return H.stringReplaceAllUnchecked(t1, "'", "\\'");
+ },
+ prettyPrint__prettyPrint: function prettyPrint__prettyPrint(t0, t1) {
+ this.maxItems = t0;
+ this.maxLineLength = t1;
+ },
+ prettyPrint__prettyPrint_pp: function prettyPrint__prettyPrint_pp(t0, t1, t2) {
+ this._box_0 = t0;
+ this._prettyPrint = t1;
+ this.indent = t2;
+ },
+ prettyPrint__prettyPrint_closure: function prettyPrint__prettyPrint_closure(t0) {
+ this.indent = t0;
+ },
+ prettyPrint__prettyPrint_closure0: function prettyPrint__prettyPrint_closure0(t0, t1) {
+ this.pp = t0;
+ this.object = t1;
+ },
+ prettyPrint__prettyPrint_closure1: function prettyPrint__prettyPrint_closure1(t0) {
+ this.indent = t0;
+ },
+ LiveTest: function LiveTest() {
+ }
+ },
+ A = {
+ Frame_Frame$parseVM: function(frame) {
+ return A.Frame__catchFormatException(frame, new A.Frame_Frame$parseVM_closure(frame));
+ },
+ Frame_Frame$parseV8: function(frame) {
+ return A.Frame__catchFormatException(frame, new A.Frame_Frame$parseV8_closure(frame));
+ },
+ Frame_Frame$parseFirefox: function(frame) {
+ return A.Frame__catchFormatException(frame, new A.Frame_Frame$parseFirefox_closure(frame));
+ },
+ Frame_Frame$parseFriendly: function(frame) {
+ return A.Frame__catchFormatException(frame, new A.Frame_Frame$parseFriendly_closure(frame));
+ },
+ Frame__uriOrPathToUri: function(uriOrPath) {
+ if (J.getInterceptor$asx(uriOrPath).contains$1(uriOrPath, $.$get$Frame__uriRegExp()))
+ return P.Uri_parse(uriOrPath);
+ else if (C.JSString_methods.contains$1(uriOrPath, $.$get$Frame__windowsRegExp()))
+ return P._Uri__Uri$file(uriOrPath, true);
+ else if (C.JSString_methods.startsWith$1(uriOrPath, "/"))
+ return P._Uri__Uri$file(uriOrPath, false);
+ if (C.JSString_methods.contains$1(uriOrPath, "\\"))
+ return $.$get$windows().toUri$1(uriOrPath);
+ return P.Uri_parse(uriOrPath);
+ },
+ Frame__catchFormatException: function(text, body) {
+ var t1, exception;
+ try {
+ t1 = body.call$0();
+ return t1;
+ } catch (exception) {
+ if (type$.FormatException._is(H.unwrapException(exception)))
+ return new N.UnparsedFrame(P._Uri__Uri(null, "unparsed", null, null), text);
+ else
+ throw exception;
+ }
+ },
+ Frame: function Frame(t0, t1, t2, t3) {
+ var _ = this;
+ _.uri = t0;
+ _.line = t1;
+ _.column = t2;
+ _.member = t3;
+ },
+ Frame_Frame$parseVM_closure: function Frame_Frame$parseVM_closure(t0) {
+ this.frame = t0;
+ },
+ Frame_Frame$parseV8_closure: function Frame_Frame$parseV8_closure(t0) {
+ this.frame = t0;
+ },
+ Frame_Frame$parseV8_closure_parseLocation: function Frame_Frame$parseV8_closure_parseLocation(t0) {
+ this.frame = t0;
+ },
+ Frame_Frame$parseFirefox_closure: function Frame_Frame$parseFirefox_closure(t0) {
+ this.frame = t0;
+ },
+ Frame_Frame$parseFriendly_closure: function Frame_Frame$parseFriendly_closure(t0) {
+ this.frame = t0;
+ },
+ AsciiGlyphSet: function AsciiGlyphSet() {
+ }
+ },
+ N = {UnparsedFrame: function UnparsedFrame(t0, t1) {
+ this.uri = t0;
+ this.member = t1;
+ }, StreamChannelCompleter: function StreamChannelCompleter(t0, t1, t2) {
+ var _ = this;
+ _._streamCompleter = t0;
+ _._sinkCompleter = t1;
+ _._stream_channel_completer$_channel = null;
+ _._stream_channel_completer$_set = false;
+ _.$ti = t2;
+ },
+ postMessageChannel: function() {
+ var t1, t2,
+ controller = B.StreamChannelController$(true, true, type$.dynamic);
+ new W._EventStream(window, "message", false, type$._EventStream_MessageEvent).firstWhere$1(0, new N.postMessageChannel_closure()).then$1$1(new N.postMessageChannel_closure0(controller), type$.Null);
+ t1 = P.LinkedHashMap_LinkedHashMap$_literal(["href", window.location.href, "ready", true], type$.String, type$.Object);
+ t1 = P._convertDataTree(t1);
+ t2 = window.location;
+ self.window.parent.postMessage(t1, (t2 && C.Location_methods).get$origin(t2));
+ return controller._foreign;
+ },
+ postMessageChannel_closure: function postMessageChannel_closure() {
+ },
+ postMessageChannel_closure0: function postMessageChannel_closure0(t0) {
+ this.controller = t0;
+ },
+ postMessageChannel__closure: function postMessageChannel__closure(t0) {
+ this.controller = t0;
+ },
+ postMessageChannel__closure0: function postMessageChannel__closure0(t0) {
+ this.port = t0;
+ },
+ postMessageChannel__closure1: function postMessageChannel__closure1(t0, t1) {
+ this.port = t0;
+ this.portSubscription = t1;
+ },
+ OperatingSystem_find: function(identifier) {
+ return C.JSArray_methods.firstWhere$2$orElse(C.List_opx, new N.OperatingSystem_find_closure(identifier), new N.OperatingSystem_find_closure0());
+ },
+ OperatingSystem: function OperatingSystem(t0, t1) {
+ this.name = t0;
+ this.identifier = t1;
+ },
+ OperatingSystem_find_closure: function OperatingSystem_find_closure(t0) {
+ this.identifier = t0;
+ },
+ OperatingSystem_find_closure0: function OperatingSystem_find_closure0() {
+ },
+ SuiteChannelManager: function SuiteChannelManager(t0, t1, t2) {
+ this._incomingConnections = t0;
+ this._outgoingConnections = t1;
+ this._names = t2;
+ }
+ },
+ K = {
+ GuaranteeChannel$: function(innerStream, innerSink, allowSinkErrors, $T) {
+ var t2, t1 = {};
+ t1.innerStream = innerStream;
+ t2 = new K.GuaranteeChannel($T._eval$1("GuaranteeChannel<0>"));
+ t2.GuaranteeChannel$3$allowSinkErrors(innerSink, allowSinkErrors, t1, $T);
+ return t2;
+ },
+ GuaranteeChannel: function GuaranteeChannel(t0) {
+ var _ = this;
+ _._subscription = _._streamController = _._sink = null;
+ _._disconnected = false;
+ _.$ti = t0;
+ },
+ GuaranteeChannel_closure: function GuaranteeChannel_closure(t0, t1) {
+ this._box_0 = t0;
+ this.$this = t1;
+ },
+ GuaranteeChannel__closure: function GuaranteeChannel__closure(t0) {
+ this.$this = t0;
+ },
+ _GuaranteeSink: function _GuaranteeSink(t0, t1, t2, t3, t4) {
+ var _ = this;
+ _._inner = t0;
+ _._channel = t1;
+ _._doneCompleter = t2;
+ _._closed = _._disconnected = false;
+ _._addStreamCompleter = _._addStreamSubscription = null;
+ _._allowErrors = t3;
+ _.$ti = t4;
+ },
+ _GuaranteeSink__addError_closure: function _GuaranteeSink__addError_closure() {
+ },
+ _GuaranteeSink_addStream_closure: function _GuaranteeSink_addStream_closure(t0) {
+ this.$this = t0;
+ },
+ UnicodeGlyphSet: function UnicodeGlyphSet() {
+ },
+ ClosedException$: function() {
+ return new K.ClosedException();
+ },
+ ClosedException: function ClosedException() {
+ }
+ };
+ var holders = [C, H, J, P, W, S, O, Y, F, V, E, L, G, T, X, U, R, B, Q, M, D, Z, A, N, K];
+ hunkHelpers.setFunctionNamesIfNecessary(holders);
+ var $ = {};
+ H.JS_CONST.prototype = {};
+ J.Interceptor.prototype = {
+ $eq: function(receiver, other) {
+ return receiver === other;
+ },
+ get$hashCode: function(receiver) {
+ return H.Primitives_objectHashCode(receiver);
+ },
+ toString$0: function(receiver) {
+ return "Instance of '" + H.S(H.Primitives_objectTypeName(receiver)) + "'";
+ },
+ get$runtimeType: function(receiver) {
+ return H.getRuntimeType(receiver);
+ }
+ };
+ J.JSBool.prototype = {
+ toString$0: function(receiver) {
+ return String(receiver);
+ },
+ get$hashCode: function(receiver) {
+ return receiver ? 519018 : 218159;
+ },
+ get$runtimeType: function(receiver) {
+ return C.Type_bool_lhE;
+ },
+ $isbool: 1
+ };
+ J.JSNull.prototype = {
+ $eq: function(receiver, other) {
+ return null == other;
+ },
+ toString$0: function(receiver) {
+ return "null";
+ },
+ get$hashCode: function(receiver) {
+ return 0;
+ },
+ get$runtimeType: function(receiver) {
+ return C.Type_Null_Yyn;
+ },
+ $isNull: 1
+ };
+ J.JSObject.prototype = {};
+ J.JavaScriptObject.prototype = {
+ get$hashCode: function(receiver) {
+ return 0;
+ },
+ get$runtimeType: function(receiver) {
+ return C.Type_JSObject_8k0;
+ },
+ toString$0: function(receiver) {
+ return String(receiver);
+ },
+ $isJSObject: 1
+ };
+ J.PlainJavaScriptObject.prototype = {};
+ J.UnknownJavaScriptObject.prototype = {};
+ J.JavaScriptFunction.prototype = {
+ toString$0: function(receiver) {
+ var dartClosure = receiver[$.$get$DART_CLOSURE_PROPERTY_NAME()];
+ if (dartClosure == null)
+ return this.super$JavaScriptObject$toString(receiver);
+ return "JavaScript function for " + H.S(J.toString$0$(dartClosure));
+ },
+ $signature: function() {
+ return {func: 1, opt: [,,,,,,,,,,,,,,,,]};
+ },
+ $isFunction: 1
+ };
+ J.JSArray.prototype = {
+ add$1: function(receiver, value) {
+ H._arrayInstanceType(receiver)._precomputed1._check(value);
+ if (!!receiver.fixed$length)
+ H.throwExpression(P.UnsupportedError$("add"));
+ receiver.push(value);
+ },
+ removeAt$1: function(receiver, index) {
+ var t1;
+ if (!!receiver.fixed$length)
+ H.throwExpression(P.UnsupportedError$("removeAt"));
+ t1 = receiver.length;
+ if (index >= t1)
+ throw H.wrapException(P.RangeError$value(index, null));
+ return receiver.splice(index, 1)[0];
+ },
+ insert$2: function(receiver, index, value) {
+ var t1;
+ H._arrayInstanceType(receiver)._precomputed1._check(value);
+ if (!!receiver.fixed$length)
+ H.throwExpression(P.UnsupportedError$("insert"));
+ t1 = receiver.length;
+ if (index > t1)
+ throw H.wrapException(P.RangeError$value(index, null));
+ receiver.splice(index, 0, value);
+ },
+ insertAll$2: function(receiver, index, iterable) {
+ var t1, insertionLength, end;
+ H._arrayInstanceType(receiver)._eval$1("Iterable<1>")._check(iterable);
+ if (!!receiver.fixed$length)
+ H.throwExpression(P.UnsupportedError$("insertAll"));
+ t1 = receiver.length;
+ P.RangeError_checkValueInInterval(index, 0, t1, "index");
+ insertionLength = iterable.length;
+ this.set$length(receiver, t1 + insertionLength);
+ end = index + insertionLength;
+ this.setRange$4(receiver, end, receiver.length, receiver, index);
+ this.setRange$3(receiver, index, end, iterable);
+ },
+ removeLast$0: function(receiver) {
+ if (!!receiver.fixed$length)
+ H.throwExpression(P.UnsupportedError$("removeLast"));
+ if (receiver.length === 0)
+ throw H.wrapException(H.diagnoseIndexError(receiver, -1));
+ return receiver.pop();
+ },
+ remove$1: function(receiver, element) {
+ var i;
+ if (!!receiver.fixed$length)
+ H.throwExpression(P.UnsupportedError$("remove"));
+ for (i = 0; i < receiver.length; ++i)
+ if (J.$eq$(receiver[i], element)) {
+ receiver.splice(i, 1);
+ return true;
+ }
+ return false;
+ },
+ addAll$1: function(receiver, collection) {
+ var i, t1, e, i0;
+ H._arrayInstanceType(receiver)._eval$1("Iterable<1>")._check(collection);
+ i = receiver.length;
+ if (!!receiver.fixed$length)
+ H.throwExpression(P.UnsupportedError$("addAll"));
+ for (t1 = J.get$iterator$ax(collection); t1.moveNext$0(); i = i0) {
+ e = t1.get$current();
+ i0 = i + 1;
+ H.assertHelper(i === receiver.length || H.throwExpression(P.ConcurrentModificationError$(receiver)));
+ receiver.push(e);
+ }
+ },
+ forEach$1: function(receiver, f) {
+ var end, i;
+ H._arrayInstanceType(receiver)._eval$1("~(1)")._check(f);
+ end = receiver.length;
+ for (i = 0; i < end; ++i) {
+ f.call$1(receiver[i]);
+ if (receiver.length !== end)
+ throw H.wrapException(P.ConcurrentModificationError$(receiver));
+ }
+ },
+ map$1$1: function(receiver, f, $T) {
+ var t1 = H._arrayInstanceType(receiver);
+ return new H.MappedListIterable(receiver, t1._bind$1($T)._eval$1("1(2)")._check(f), t1._eval$1("@<1>")._bind$1($T)._eval$1("MappedListIterable<1,2>"));
+ },
+ join$1: function(receiver, separator) {
+ var i,
+ list = new Array(receiver.length);
+ list.fixed$length = Array;
+ for (i = 0; i < receiver.length; ++i)
+ this.$indexSet(list, i, H.S(receiver[i]));
+ return list.join(separator);
+ },
+ join$0: function($receiver) {
+ return this.join$1($receiver, "");
+ },
+ fold$1$2: function(receiver, initialValue, combine, $T) {
+ var $length, value, i;
+ $T._check(initialValue);
+ H._arrayInstanceType(receiver)._bind$1($T)._eval$1("1(1,2)")._check(combine);
+ $length = receiver.length;
+ for (value = initialValue, i = 0; i < $length; ++i) {
+ value = combine.call$2(value, receiver[i]);
+ if (receiver.length !== $length)
+ throw H.wrapException(P.ConcurrentModificationError$(receiver));
+ }
+ return value;
+ },
+ firstWhere$2$orElse: function(receiver, test, orElse) {
+ var end, i, element,
+ t1 = H._arrayInstanceType(receiver);
+ t1._eval$1("bool(1)")._check(test);
+ t1._eval$1("1()")._check(orElse);
+ end = receiver.length;
+ for (i = 0; i < end; ++i) {
+ element = receiver[i];
+ if (H.boolConversionCheck(test.call$1(element)))
+ return element;
+ if (receiver.length !== end)
+ throw H.wrapException(P.ConcurrentModificationError$(receiver));
+ }
+ if (orElse != null)
+ return orElse.call$0();
+ throw H.wrapException(H.IterableElementError_noElement());
+ },
+ firstWhere$1: function($receiver, test) {
+ return this.firstWhere$2$orElse($receiver, test, null);
+ },
+ elementAt$1: function(receiver, index) {
+ if (index < 0 || index >= receiver.length)
+ return H.ioore(receiver, index);
+ return receiver[index];
+ },
+ sublist$2: function(receiver, start, end) {
+ if (start < 0 || start > receiver.length)
+ throw H.wrapException(P.RangeError$range(start, 0, receiver.length, "start", null));
+ if (end < start || end > receiver.length)
+ throw H.wrapException(P.RangeError$range(end, start, receiver.length, "end", null));
+ if (start === end)
+ return H.setRuntimeTypeInfo([], H._arrayInstanceType(receiver));
+ return H.setRuntimeTypeInfo(receiver.slice(start, end), H._arrayInstanceType(receiver));
+ },
+ get$first: function(receiver) {
+ if (receiver.length > 0)
+ return receiver[0];
+ throw H.wrapException(H.IterableElementError_noElement());
+ },
+ get$last: function(receiver) {
+ var t1 = receiver.length;
+ if (t1 > 0)
+ return receiver[t1 - 1];
+ throw H.wrapException(H.IterableElementError_noElement());
+ },
+ get$single: function(receiver) {
+ var t1 = receiver.length;
+ if (t1 === 1) {
+ if (0 >= t1)
+ return H.ioore(receiver, 0);
+ return receiver[0];
+ }
+ if (t1 === 0)
+ throw H.wrapException(H.IterableElementError_noElement());
+ throw H.wrapException(H.IterableElementError_tooMany());
+ },
+ setRange$4: function(receiver, start, end, iterable, skipCount) {
+ var $length, i,
+ t1 = H._arrayInstanceType(receiver);
+ t1._eval$1("Iterable<1>")._check(iterable);
+ if (!!receiver.immutable$list)
+ H.throwExpression(P.UnsupportedError$("setRange"));
+ P.RangeError_checkValidRange(start, end, receiver.length);
+ $length = end - start;
+ if ($length === 0)
+ return;
+ P.RangeError_checkNotNegative(skipCount, "skipCount");
+ t1._eval$1("List<1>")._check(iterable);
+ t1 = J.getInterceptor$asx(iterable);
+ if (skipCount + $length > t1.get$length(iterable))
+ throw H.wrapException(H.IterableElementError_tooFew());
+ if (skipCount < start)
+ for (i = $length - 1; i >= 0; --i)
+ receiver[start + i] = t1.$index(iterable, skipCount + i);
+ else
+ for (i = 0; i < $length; ++i)
+ receiver[start + i] = t1.$index(iterable, skipCount + i);
+ },
+ setRange$3: function($receiver, start, end, iterable) {
+ return this.setRange$4($receiver, start, end, iterable, 0);
+ },
+ fillRange$3: function(receiver, start, end, fillValue) {
+ var i;
+ H._arrayInstanceType(receiver)._precomputed1._check(fillValue);
+ if (!!receiver.immutable$list)
+ H.throwExpression(P.UnsupportedError$("fill range"));
+ P.RangeError_checkValidRange(start, end, receiver.length);
+ for (i = start; i < end; ++i)
+ receiver[i] = fillValue;
+ },
+ replaceRange$3: function(receiver, start, end, replacement) {
+ var t1, removeLength, insertEnd, delta, newLength, _this = this;
+ H._arrayInstanceType(receiver)._eval$1("Iterable<1>")._check(replacement);
+ if (!!receiver.fixed$length)
+ H.throwExpression(P.UnsupportedError$("replaceRange"));
+ t1 = receiver.length;
+ P.RangeError_checkValidRange(start, end, t1);
+ removeLength = end - start;
+ insertEnd = start + 1;
+ if (removeLength >= 1) {
+ delta = removeLength - 1;
+ newLength = t1 - delta;
+ _this.setRange$3(receiver, start, insertEnd, replacement);
+ if (delta !== 0) {
+ _this.setRange$4(receiver, insertEnd, newLength, receiver, end);
+ _this.set$length(receiver, newLength);
+ }
+ } else {
+ newLength = t1 + (1 - removeLength);
+ _this.set$length(receiver, newLength);
+ _this.setRange$4(receiver, insertEnd, newLength, receiver, end);
+ _this.setRange$3(receiver, start, insertEnd, replacement);
+ }
+ },
+ contains$1: function(receiver, other) {
+ var i;
+ for (i = 0; i < receiver.length; ++i)
+ if (J.$eq$(receiver[i], other))
+ return true;
+ return false;
+ },
+ get$isEmpty: function(receiver) {
+ return receiver.length === 0;
+ },
+ toString$0: function(receiver) {
+ return P.IterableBase_iterableToFullString(receiver, "[", "]");
+ },
+ toSet$0: function(receiver) {
+ return P.LinkedHashSet_LinkedHashSet$from(receiver, H._arrayInstanceType(receiver)._precomputed1);
+ },
+ get$iterator: function(receiver) {
+ return new J.ArrayIterator(receiver, receiver.length, H._arrayInstanceType(receiver)._eval$1("ArrayIterator<1>"));
+ },
+ get$hashCode: function(receiver) {
+ return H.Primitives_objectHashCode(receiver);
+ },
+ get$length: function(receiver) {
+ return receiver.length;
+ },
+ set$length: function(receiver, newLength) {
+ var _s9_ = "newLength";
+ if (!!receiver.fixed$length)
+ H.throwExpression(P.UnsupportedError$("set length"));
+ if (!H._isInt(newLength))
+ throw H.wrapException(P.ArgumentError$value(newLength, _s9_, null));
+ if (newLength < 0)
+ throw H.wrapException(P.RangeError$range(newLength, 0, null, _s9_, null));
+ receiver.length = newLength;
+ },
+ $index: function(receiver, index) {
+ H._checkIntNullable(index);
+ if (!H._isInt(index))
+ throw H.wrapException(H.diagnoseIndexError(receiver, index));
+ if (index >= receiver.length || index < 0)
+ throw H.wrapException(H.diagnoseIndexError(receiver, index));
+ return receiver[index];
+ },
+ $indexSet: function(receiver, index, value) {
+ H._checkIntNullable(index);
+ H._arrayInstanceType(receiver)._precomputed1._check(value);
+ if (!!receiver.immutable$list)
+ H.throwExpression(P.UnsupportedError$("indexed set"));
+ if (!H._isInt(index))
+ throw H.wrapException(H.diagnoseIndexError(receiver, index));
+ if (index >= receiver.length || index < 0)
+ throw H.wrapException(H.diagnoseIndexError(receiver, index));
+ receiver[index] = value;
+ },
+ $isEfficientLengthIterable: 1,
+ $isIterable: 1,
+ $isList: 1
+ };
+ J.JSUnmodifiableArray.prototype = {};
+ J.ArrayIterator.prototype = {
+ get$current: function() {
+ return this._current;
+ },
+ moveNext$0: function() {
+ var t2, _this = this,
+ t1 = _this._iterable,
+ $length = t1.length;
+ if (_this._length !== $length)
+ throw H.wrapException(H.throwConcurrentModificationError(t1));
+ t2 = _this._index;
+ if (t2 >= $length) {
+ _this.set$_current(null);
+ return false;
+ }
+ _this.set$_current(t1[t2]);
+ ++_this._index;
+ return true;
+ },
+ set$_current: function(_current) {
+ this._current = this.$ti._precomputed1._check(_current);
+ },
+ $isIterator: 1
+ };
+ J.JSNumber.prototype = {
+ floor$0: function(receiver) {
+ var truncated, d;
+ if (receiver >= 0) {
+ if (receiver <= 2147483647)
+ return receiver | 0;
+ } else if (receiver >= -2147483648) {
+ truncated = receiver | 0;
+ return receiver === truncated ? truncated : truncated - 1;
+ }
+ d = Math.floor(receiver);
+ if (isFinite(d))
+ return d;
+ throw H.wrapException(P.UnsupportedError$("" + receiver + ".floor()"));
+ },
+ round$0: function(receiver) {
+ if (receiver > 0) {
+ if (receiver !== 1 / 0)
+ return Math.round(receiver);
+ } else if (receiver > -1 / 0)
+ return 0 - Math.round(0 - receiver);
+ throw H.wrapException(P.UnsupportedError$("" + receiver + ".round()"));
+ },
+ toRadixString$1: function(receiver, radix) {
+ var result, match, t1, exponent;
+ if (radix < 2 || radix > 36)
+ throw H.wrapException(P.RangeError$range(radix, 2, 36, "radix", null));
+ result = receiver.toString(radix);
+ if (C.JSString_methods.codeUnitAt$1(result, result.length - 1) !== 41)
+ return result;
+ match = /^([\da-z]+)(?:\.([\da-z]+))?\(e\+(\d+)\)$/.exec(result);
+ if (match == null)
+ H.throwExpression(P.UnsupportedError$("Unexpected toString result: " + result));
+ t1 = match.length;
+ if (1 >= t1)
+ return H.ioore(match, 1);
+ result = match[1];
+ if (3 >= t1)
+ return H.ioore(match, 3);
+ exponent = +match[3];
+ t1 = match[2];
+ if (t1 != null) {
+ result += t1;
+ exponent -= t1.length;
+ }
+ return result + C.JSString_methods.$mul("0", exponent);
+ },
+ toString$0: function(receiver) {
+ if (receiver === 0 && 1 / receiver < 0)
+ return "-0.0";
+ else
+ return "" + receiver;
+ },
+ get$hashCode: function(receiver) {
+ var absolute, floorLog2, factor, scaled,
+ intValue = receiver | 0;
+ if (receiver === intValue)
+ return 536870911 & intValue;
+ absolute = Math.abs(receiver);
+ floorLog2 = Math.log(absolute) / 0.6931471805599453 | 0;
+ factor = Math.pow(2, floorLog2);
+ scaled = absolute < 1 ? absolute / factor : factor / absolute;
+ return 536870911 & ((scaled * 9007199254740992 | 0) + (scaled * 3542243181176521 | 0)) * 599197 + floorLog2 * 1259;
+ },
+ $mod: function(receiver, other) {
+ var result = receiver % other;
+ if (result === 0)
+ return 0;
+ if (result > 0)
+ return result;
+ if (other < 0)
+ return result - other;
+ else
+ return result + other;
+ },
+ $tdiv: function(receiver, other) {
+ if ((receiver | 0) === receiver)
+ if (other >= 1 || other < -1)
+ return receiver / other | 0;
+ return this._tdivSlow$1(receiver, other);
+ },
+ _tdivFast$1: function(receiver, other) {
+ return (receiver | 0) === receiver ? receiver / other | 0 : this._tdivSlow$1(receiver, other);
+ },
+ _tdivSlow$1: function(receiver, other) {
+ var quotient = receiver / other;
+ if (quotient >= -2147483648 && quotient <= 2147483647)
+ return quotient | 0;
+ if (quotient > 0) {
+ if (quotient !== 1 / 0)
+ return Math.floor(quotient);
+ } else if (quotient > -1 / 0)
+ return Math.ceil(quotient);
+ throw H.wrapException(P.UnsupportedError$("Result of truncating division is " + H.S(quotient) + ": " + H.S(receiver) + " ~/ " + other));
+ },
+ _shlPositive$1: function(receiver, other) {
+ return other > 31 ? 0 : receiver << other >>> 0;
+ },
+ _shrOtherPositive$1: function(receiver, other) {
+ var t1;
+ if (receiver > 0)
+ t1 = this._shrBothPositive$1(receiver, other);
+ else {
+ t1 = other > 31 ? 31 : other;
+ t1 = receiver >> t1 >>> 0;
+ }
+ return t1;
+ },
+ _shrReceiverPositive$1: function(receiver, other) {
+ if (other < 0)
+ throw H.wrapException(H.argumentErrorValue(other));
+ return this._shrBothPositive$1(receiver, other);
+ },
+ _shrBothPositive$1: function(receiver, other) {
+ return other > 31 ? 0 : receiver >>> other;
+ },
+ get$runtimeType: function(receiver) {
+ return C.Type_num_cv7;
+ },
+ $isdouble: 1,
+ $isnum: 1
+ };
+ J.JSInt.prototype = {
+ get$runtimeType: function(receiver) {
+ return C.Type_int_tHn;
+ },
+ $isint: 1
+ };
+ J.JSDouble.prototype = {
+ get$runtimeType: function(receiver) {
+ return C.Type_double_K1J;
+ }
+ };
+ J.JSString.prototype = {
+ codeUnitAt$1: function(receiver, index) {
+ if (index < 0)
+ throw H.wrapException(H.diagnoseIndexError(receiver, index));
+ if (index >= receiver.length)
+ H.throwExpression(H.diagnoseIndexError(receiver, index));
+ return receiver.charCodeAt(index);
+ },
+ _codeUnitAt$1: function(receiver, index) {
+ if (index >= receiver.length)
+ throw H.wrapException(H.diagnoseIndexError(receiver, index));
+ return receiver.charCodeAt(index);
+ },
+ allMatches$2: function(receiver, string, start) {
+ var t1;
+ if (typeof string != "string")
+ H.throwExpression(H.argumentErrorValue(string));
+ t1 = string.length;
+ if (start > t1)
+ throw H.wrapException(P.RangeError$range(start, 0, t1, null, null));
+ return new H._StringAllMatchesIterable(string, receiver, start);
+ },
+ allMatches$1: function($receiver, string) {
+ return this.allMatches$2($receiver, string, 0);
+ },
+ matchAsPrefix$2: function(receiver, string, start) {
+ var t1, t2, i, _null = null;
+ if (start < 0 || start > string.length)
+ throw H.wrapException(P.RangeError$range(start, 0, string.length, _null, _null));
+ t1 = receiver.length;
+ if (start + t1 > string.length)
+ return _null;
+ for (t2 = J.getInterceptor$s(string), i = 0; i < t1; ++i)
+ if (t2.codeUnitAt$1(string, start + i) !== this._codeUnitAt$1(receiver, i))
+ return _null;
+ return new H.StringMatch(start, receiver);
+ },
+ $add: function(receiver, other) {
+ if (typeof other != "string")
+ throw H.wrapException(P.ArgumentError$value(other, null, null));
+ return receiver + other;
+ },
+ endsWith$1: function(receiver, other) {
+ var otherLength = other.length,
+ t1 = receiver.length;
+ if (otherLength > t1)
+ return false;
+ return other === this.substring$1(receiver, t1 - otherLength);
+ },
+ splitMapJoin$2$onMatch: function(receiver, from, onMatch) {
+ return H.stringReplaceAllFuncUnchecked(receiver, from, type$.String_Function_Match._check(onMatch), type$.String_Function_String._check(null));
+ },
+ replaceFirst$2: function(receiver, from, to) {
+ P.RangeError_checkValueInInterval(0, 0, receiver.length, "startIndex");
+ return H.stringReplaceFirstUnchecked(receiver, from, to, 0);
+ },
+ replaceRange$3: function(receiver, start, end, replacement) {
+ end = P.RangeError_checkValidRange(start, end, receiver.length);
+ return H.stringReplaceRangeUnchecked(receiver, start, end, replacement);
+ },
+ startsWith$2: function(receiver, pattern, index) {
+ var endIndex;
+ type$.Pattern._check(pattern);
+ if (index < 0 || index > receiver.length)
+ throw H.wrapException(P.RangeError$range(index, 0, receiver.length, null, null));
+ if (typeof pattern == "string") {
+ endIndex = index + pattern.length;
+ if (endIndex > receiver.length)
+ return false;
+ return pattern === receiver.substring(index, endIndex);
+ }
+ return J.matchAsPrefix$2$s(pattern, receiver, index) != null;
+ },
+ startsWith$1: function($receiver, pattern) {
+ return this.startsWith$2($receiver, pattern, 0);
+ },
+ substring$2: function(receiver, startIndex, endIndex) {
+ if (endIndex == null)
+ endIndex = receiver.length;
+ if (startIndex < 0)
+ throw H.wrapException(P.RangeError$value(startIndex, null));
+ if (startIndex > endIndex)
+ throw H.wrapException(P.RangeError$value(startIndex, null));
+ if (endIndex > receiver.length)
+ throw H.wrapException(P.RangeError$value(endIndex, null));
+ return receiver.substring(startIndex, endIndex);
+ },
+ substring$1: function($receiver, startIndex) {
+ return this.substring$2($receiver, startIndex, null);
+ },
+ trim$0: function(receiver) {
+ var startIndex, t1, endIndex0,
+ result = receiver.trim(),
+ endIndex = result.length;
+ if (endIndex === 0)
+ return result;
+ if (this._codeUnitAt$1(result, 0) === 133) {
+ startIndex = J.JSString__skipLeadingWhitespace(result, 1);
+ if (startIndex === endIndex)
+ return "";
+ } else
+ startIndex = 0;
+ t1 = endIndex - 1;
+ endIndex0 = this.codeUnitAt$1(result, t1) === 133 ? J.JSString__skipTrailingWhitespace(result, t1) : endIndex;
+ if (startIndex === 0 && endIndex0 === endIndex)
+ return result;
+ return result.substring(startIndex, endIndex0);
+ },
+ $mul: function(receiver, times) {
+ var s, result;
+ if (0 >= times)
+ return "";
+ if (times === 1 || receiver.length === 0)
+ return receiver;
+ if (times !== times >>> 0)
+ throw H.wrapException(C.C_OutOfMemoryError);
+ for (s = receiver, result = ""; true;) {
+ if ((times & 1) === 1)
+ result = s + result;
+ times = times >>> 1;
+ if (times === 0)
+ break;
+ s += s;
+ }
+ return result;
+ },
+ padLeft$2: function(receiver, width, padding) {
+ var delta = width - receiver.length;
+ if (delta <= 0)
+ return receiver;
+ return this.$mul(padding, delta) + receiver;
+ },
+ padRight$1: function(receiver, width) {
+ var delta;
+ if (typeof width !== "number")
+ return width.$sub();
+ delta = width - receiver.length;
+ if (delta <= 0)
+ return receiver;
+ return receiver + this.$mul(" ", delta);
+ },
+ indexOf$2: function(receiver, pattern, start) {
+ var t1;
+ if (start < 0 || start > receiver.length)
+ throw H.wrapException(P.RangeError$range(start, 0, receiver.length, null, null));
+ t1 = receiver.indexOf(pattern, start);
+ return t1;
+ },
+ indexOf$1: function($receiver, pattern) {
+ return this.indexOf$2($receiver, pattern, 0);
+ },
+ lastIndexOf$2: function(receiver, pattern, start) {
+ var t1, t2;
+ if (start == null)
+ start = receiver.length;
+ else if (start < 0 || start > receiver.length)
+ throw H.wrapException(P.RangeError$range(start, 0, receiver.length, null, null));
+ t1 = pattern.length;
+ t2 = receiver.length;
+ if (start + t1 > t2)
+ start = t2 - t1;
+ return receiver.lastIndexOf(pattern, start);
+ },
+ lastIndexOf$1: function($receiver, pattern) {
+ return this.lastIndexOf$2($receiver, pattern, null);
+ },
+ contains$1: function(receiver, other) {
+ if (other == null)
+ H.throwExpression(H.argumentErrorValue(other));
+ return H.stringContainsUnchecked(receiver, other, 0);
+ },
+ toString$0: function(receiver) {
+ return receiver;
+ },
+ get$hashCode: function(receiver) {
+ var t1, hash, i;
+ for (t1 = receiver.length, hash = 0, i = 0; i < t1; ++i) {
+ hash = 536870911 & hash + receiver.charCodeAt(i);
+ hash = 536870911 & hash + ((524287 & hash) << 10);
+ hash ^= hash >> 6;
+ }
+ hash = 536870911 & hash + ((67108863 & hash) << 3);
+ hash ^= hash >> 11;
+ return 536870911 & hash + ((16383 & hash) << 15);
+ },
+ get$runtimeType: function(receiver) {
+ return C.Type_String_k8F;
+ },
+ get$length: function(receiver) {
+ return receiver.length;
+ },
+ $index: function(receiver, index) {
+ H._checkIntNullable(index);
+ if (index >= receiver.length || index < 0)
+ throw H.wrapException(H.diagnoseIndexError(receiver, index));
+ return receiver[index];
+ },
+ $isPattern: 1,
+ $isString: 1
+ };
+ H._CastIterableBase.prototype = {
+ get$iterator: function(_) {
+ var t1 = this._source,
+ t2 = H._instanceType(this);
+ return new H.CastIterator(t1.get$iterator(t1), t2._eval$1("@<1>")._bind$1(t2._rest[1])._eval$1("CastIterator<1,2>"));
+ },
+ get$length: function(_) {
+ var t1 = this._source;
+ return t1.get$length(t1);
+ },
+ get$isEmpty: function(_) {
+ var t1 = this._source;
+ return t1.get$isEmpty(t1);
+ },
+ get$first: function(_) {
+ var t1 = this._source;
+ return H._instanceType(this)._rest[1]._as(t1.get$first(t1));
+ },
+ get$last: function(_) {
+ var t1 = this._source;
+ return H._instanceType(this)._rest[1]._as(t1.get$last(t1));
+ },
+ contains$1: function(_, other) {
+ return this._source.contains$1(0, other);
+ },
+ toString$0: function(_) {
+ return this._source.toString$0(0);
+ }
+ };
+ H.CastIterator.prototype = {
+ moveNext$0: function() {
+ return this._source.moveNext$0();
+ },
+ get$current: function() {
+ return this.$ti._rest[1]._as(this._source.get$current());
+ },
+ $isIterator: 1
+ };
+ H.CastIterable.prototype = {};
+ H._EfficientLengthCastIterable.prototype = {$isEfficientLengthIterable: 1};
+ H.CastMap.prototype = {
+ cast$2$0: function(_, RK, RV) {
+ var t1 = this.$ti;
+ return new H.CastMap(this._source, t1._eval$1("@<1>")._bind$1(t1._rest[1])._bind$1(RK)._bind$1(RV)._eval$1("CastMap<1,2,3,4>"));
+ },
+ containsKey$1: function(key) {
+ return this._source.containsKey$1(key);
+ },
+ $index: function(_, key) {
+ return this.$ti._rest[3]._as(this._source.$index(0, key));
+ },
+ remove$1: function(_, key) {
+ return this.$ti._rest[3]._as(this._source.remove$1(0, key));
+ },
+ forEach$1: function(_, f) {
+ this._source.forEach$1(0, new H.CastMap_forEach_closure(this, this.$ti._eval$1("~(3,4)")._check(f)));
+ },
+ get$keys: function() {
+ var t1 = this.$ti;
+ return H.CastIterable_CastIterable(this._source.get$keys(), t1._precomputed1, t1._rest[2]);
+ },
+ get$length: function(_) {
+ var t1 = this._source;
+ return t1.get$length(t1);
+ },
+ get$isEmpty: function(_) {
+ var t1 = this._source;
+ return t1.get$isEmpty(t1);
+ }
+ };
+ H.CastMap_forEach_closure.prototype = {
+ call$2: function(key, value) {
+ var t1 = this.$this.$ti;
+ t1._precomputed1._check(key);
+ t1._rest[1]._check(value);
+ this.f.call$2(t1._rest[2]._as(key), t1._rest[3]._as(value));
+ },
+ $signature: function() {
+ return this.$this.$ti._eval$1("Null(1,2)");
+ }
+ };
+ H.CodeUnits.prototype = {
+ get$length: function(_) {
+ return this._string.length;
+ },
+ $index: function(_, i) {
+ return C.JSString_methods.codeUnitAt$1(this._string, H._checkIntNullable(i));
+ }
+ };
+ H.EfficientLengthIterable.prototype = {};
+ H.ListIterable.prototype = {
+ get$iterator: function(_) {
+ var _this = this;
+ return new H.ListIterator(_this, _this.get$length(_this), H._instanceType(_this)._eval$1("ListIterator<ListIterable.E>"));
+ },
+ get$isEmpty: function(_) {
+ return this.get$length(this) === 0;
+ },
+ get$first: function(_) {
+ if (this.get$length(this) === 0)
+ throw H.wrapException(H.IterableElementError_noElement());
+ return this.elementAt$1(0, 0);
+ },
+ get$last: function(_) {
+ var _this = this;
+ if (_this.get$length(_this) === 0)
+ throw H.wrapException(H.IterableElementError_noElement());
+ return _this.elementAt$1(0, _this.get$length(_this) - 1);
+ },
+ contains$1: function(_, element) {
+ var i, _this = this,
+ $length = _this.get$length(_this);
+ for (i = 0; i < $length; ++i) {
+ if (J.$eq$(_this.elementAt$1(0, i), element))
+ return true;
+ if ($length !== _this.get$length(_this))
+ throw H.wrapException(P.ConcurrentModificationError$(_this));
+ }
+ return false;
+ },
+ join$1: function(_, separator) {
+ var first, t1, i, _this = this,
+ $length = _this.get$length(_this);
+ if (separator.length !== 0) {
+ if ($length === 0)
+ return "";
+ first = H.S(_this.elementAt$1(0, 0));
+ if ($length !== _this.get$length(_this))
+ throw H.wrapException(P.ConcurrentModificationError$(_this));
+ for (t1 = first, i = 1; i < $length; ++i) {
+ t1 = t1 + separator + H.S(_this.elementAt$1(0, i));
+ if ($length !== _this.get$length(_this))
+ throw H.wrapException(P.ConcurrentModificationError$(_this));
+ }
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ } else {
+ for (i = 0, t1 = ""; i < $length; ++i) {
+ t1 += H.S(_this.elementAt$1(0, i));
+ if ($length !== _this.get$length(_this))
+ throw H.wrapException(P.ConcurrentModificationError$(_this));
+ }
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ }
+ },
+ join$0: function($receiver) {
+ return this.join$1($receiver, "");
+ },
+ map$1$1: function(_, f, $T) {
+ var t1 = H._instanceType(this);
+ return new H.MappedListIterable(this, t1._bind$1($T)._eval$1("1(ListIterable.E)")._check(f), t1._eval$1("@<ListIterable.E>")._bind$1($T)._eval$1("MappedListIterable<1,2>"));
+ },
+ fold$1$2: function(_, initialValue, combine, $T) {
+ var $length, value, i, _this = this;
+ $T._check(initialValue);
+ H._instanceType(_this)._bind$1($T)._eval$1("1(1,ListIterable.E)")._check(combine);
+ $length = _this.get$length(_this);
+ for (value = initialValue, i = 0; i < $length; ++i) {
+ value = combine.call$2(value, _this.elementAt$1(0, i));
+ if ($length !== _this.get$length(_this))
+ throw H.wrapException(P.ConcurrentModificationError$(_this));
+ }
+ return value;
+ },
+ toList$1$growable: function(_, growable) {
+ var i, _this = this,
+ result = H.setRuntimeTypeInfo([], H._instanceType(_this)._eval$1("JSArray<ListIterable.E>"));
+ C.JSArray_methods.set$length(result, _this.get$length(_this));
+ for (i = 0; i < _this.get$length(_this); ++i)
+ C.JSArray_methods.$indexSet(result, i, _this.elementAt$1(0, i));
+ return result;
+ },
+ toList$0: function($receiver) {
+ return this.toList$1$growable($receiver, true);
+ },
+ toSet$0: function(_) {
+ var i, _this = this,
+ result = P.LinkedHashSet_LinkedHashSet(H._instanceType(_this)._eval$1("ListIterable.E"));
+ for (i = 0; i < _this.get$length(_this); ++i)
+ result.add$1(0, _this.elementAt$1(0, i));
+ return result;
+ }
+ };
+ H.SubListIterable.prototype = {
+ get$_endIndex: function() {
+ var $length = J.get$length$asx(this.__internal$_iterable),
+ t1 = this._endOrLength;
+ if (t1 == null || t1 > $length)
+ return $length;
+ return t1;
+ },
+ get$_startIndex: function() {
+ var $length = J.get$length$asx(this.__internal$_iterable),
+ t1 = this.__internal$_start;
+ if (t1 > $length)
+ return $length;
+ return t1;
+ },
+ get$length: function(_) {
+ var t2,
+ $length = J.get$length$asx(this.__internal$_iterable),
+ t1 = this.__internal$_start;
+ if (t1 >= $length)
+ return 0;
+ t2 = this._endOrLength;
+ if (t2 == null || t2 >= $length)
+ return $length - t1;
+ if (typeof t2 !== "number")
+ return t2.$sub();
+ return t2 - t1;
+ },
+ elementAt$1: function(_, index) {
+ var t1, _this = this,
+ realIndex = _this.get$_startIndex() + index;
+ if (index >= 0) {
+ t1 = _this.get$_endIndex();
+ if (typeof t1 !== "number")
+ return H.iae(t1);
+ t1 = realIndex >= t1;
+ } else
+ t1 = true;
+ if (t1)
+ throw H.wrapException(P.IndexError$(index, _this, "index", null, null));
+ return J.elementAt$1$ax(_this.__internal$_iterable, realIndex);
+ },
+ take$1: function(_, count) {
+ var t1, t2, newEnd, _this = this;
+ P.RangeError_checkNotNegative(count, "count");
+ t1 = _this._endOrLength;
+ t2 = _this.__internal$_start;
+ newEnd = t2 + count;
+ if (t1 == null)
+ return H.SubListIterable$(_this.__internal$_iterable, t2, newEnd, _this.$ti._precomputed1);
+ else {
+ if (t1 < newEnd)
+ return _this;
+ return H.SubListIterable$(_this.__internal$_iterable, t2, newEnd, _this.$ti._precomputed1);
+ }
+ }
+ };
+ H.ListIterator.prototype = {
+ get$current: function() {
+ return this.__internal$_current;
+ },
+ moveNext$0: function() {
+ var t3, _this = this,
+ t1 = _this.__internal$_iterable,
+ t2 = J.getInterceptor$asx(t1),
+ $length = t2.get$length(t1);
+ if (_this.__internal$_length !== $length)
+ throw H.wrapException(P.ConcurrentModificationError$(t1));
+ t3 = _this.__internal$_index;
+ if (t3 >= $length) {
+ _this.set$__internal$_current(null);
+ return false;
+ }
+ _this.set$__internal$_current(t2.elementAt$1(t1, t3));
+ ++_this.__internal$_index;
+ return true;
+ },
+ set$__internal$_current: function(_current) {
+ this.__internal$_current = this.$ti._precomputed1._check(_current);
+ },
+ $isIterator: 1
+ };
+ H.MappedIterable.prototype = {
+ get$iterator: function(_) {
+ var t1 = H._instanceType(this);
+ return new H.MappedIterator(J.get$iterator$ax(this.__internal$_iterable), this._f, t1._eval$1("@<1>")._bind$1(t1._rest[1])._eval$1("MappedIterator<1,2>"));
+ },
+ get$length: function(_) {
+ return J.get$length$asx(this.__internal$_iterable);
+ },
+ get$isEmpty: function(_) {
+ return J.get$isEmpty$asx(this.__internal$_iterable);
+ },
+ get$first: function(_) {
+ return this._f.call$1(J.get$first$ax(this.__internal$_iterable));
+ },
+ get$last: function(_) {
+ return this._f.call$1(J.get$last$ax(this.__internal$_iterable));
+ }
+ };
+ H.EfficientLengthMappedIterable.prototype = {$isEfficientLengthIterable: 1};
+ H.MappedIterator.prototype = {
+ moveNext$0: function() {
+ var _this = this,
+ t1 = _this._iterator;
+ if (t1.moveNext$0()) {
+ _this.set$__internal$_current(_this._f.call$1(t1.get$current()));
+ return true;
+ }
+ _this.set$__internal$_current(null);
+ return false;
+ },
+ get$current: function() {
+ return this.__internal$_current;
+ },
+ set$__internal$_current: function(_current) {
+ this.__internal$_current = this.$ti._rest[1]._check(_current);
+ }
+ };
+ H.MappedListIterable.prototype = {
+ get$length: function(_) {
+ return J.get$length$asx(this._source);
+ },
+ elementAt$1: function(_, index) {
+ return this._f.call$1(J.elementAt$1$ax(this._source, index));
+ }
+ };
+ H.WhereIterable.prototype = {
+ get$iterator: function(_) {
+ return new H.WhereIterator(J.get$iterator$ax(this.__internal$_iterable), this._f, this.$ti._eval$1("WhereIterator<1>"));
+ },
+ map$1$1: function(_, f, $T) {
+ var t1 = this.$ti;
+ return new H.MappedIterable(this, t1._bind$1($T)._eval$1("1(2)")._check(f), t1._eval$1("@<1>")._bind$1($T)._eval$1("MappedIterable<1,2>"));
+ }
+ };
+ H.WhereIterator.prototype = {
+ moveNext$0: function() {
+ var t1, t2;
+ for (t1 = this._iterator, t2 = this._f; t1.moveNext$0();)
+ if (H.boolConversionCheck(t2.call$1(t1.get$current())))
+ return true;
+ return false;
+ },
+ get$current: function() {
+ return this._iterator.get$current();
+ }
+ };
+ H.ExpandIterable.prototype = {
+ get$iterator: function(_) {
+ var t1 = this.$ti;
+ return new H.ExpandIterator(J.get$iterator$ax(this.__internal$_iterable), this._f, C.C_EmptyIterator, t1._eval$1("@<1>")._bind$1(t1._rest[1])._eval$1("ExpandIterator<1,2>"));
+ }
+ };
+ H.ExpandIterator.prototype = {
+ get$current: function() {
+ return this.__internal$_current;
+ },
+ moveNext$0: function() {
+ var t1, t2, _this = this;
+ if (_this._currentExpansion == null)
+ return false;
+ for (t1 = _this._iterator, t2 = _this._f; !_this._currentExpansion.moveNext$0();) {
+ _this.set$__internal$_current(null);
+ if (t1.moveNext$0()) {
+ _this.set$_currentExpansion(null);
+ _this.set$_currentExpansion(J.get$iterator$ax(t2.call$1(t1.get$current())));
+ } else
+ return false;
+ }
+ _this.set$__internal$_current(_this._currentExpansion.get$current());
+ return true;
+ },
+ set$_currentExpansion: function(_currentExpansion) {
+ this._currentExpansion = this.$ti._eval$1("Iterator<2>")._check(_currentExpansion);
+ },
+ set$__internal$_current: function(_current) {
+ this.__internal$_current = this.$ti._rest[1]._check(_current);
+ },
+ $isIterator: 1
+ };
+ H.SkipWhileIterable.prototype = {
+ get$iterator: function(_) {
+ return new H.SkipWhileIterator(J.get$iterator$ax(this.__internal$_iterable), this._f, this.$ti._eval$1("SkipWhileIterator<1>"));
+ }
+ };
+ H.SkipWhileIterator.prototype = {
+ moveNext$0: function() {
+ var t1, t2, _this = this;
+ if (!_this._hasSkipped) {
+ _this._hasSkipped = true;
+ for (t1 = _this._iterator, t2 = _this._f; t1.moveNext$0();)
+ if (!H.boolConversionCheck(t2.call$1(t1.get$current())))
+ return true;
+ }
+ return _this._iterator.moveNext$0();
+ },
+ get$current: function() {
+ return this._iterator.get$current();
+ }
+ };
+ H.EmptyIterator.prototype = {
+ moveNext$0: function() {
+ return false;
+ },
+ get$current: function() {
+ return null;
+ },
+ $isIterator: 1
+ };
+ H.FixedLengthListMixin.prototype = {
+ set$length: function(receiver, newLength) {
+ throw H.wrapException(P.UnsupportedError$("Cannot change the length of a fixed-length list"));
+ }
+ };
+ H.UnmodifiableListMixin.prototype = {
+ $indexSet: function(_, index, value) {
+ H._checkIntNullable(index);
+ H._instanceType(this)._eval$1("UnmodifiableListMixin.E")._check(value);
+ throw H.wrapException(P.UnsupportedError$("Cannot modify an unmodifiable list"));
+ },
+ set$length: function(_, newLength) {
+ throw H.wrapException(P.UnsupportedError$("Cannot change the length of an unmodifiable list"));
+ }
+ };
+ H.UnmodifiableListBase.prototype = {};
+ H.ReversedListIterable.prototype = {
+ get$length: function(_) {
+ return J.get$length$asx(this._source);
+ },
+ elementAt$1: function(_, index) {
+ var t1 = this._source,
+ t2 = J.getInterceptor$asx(t1);
+ return t2.elementAt$1(t1, t2.get$length(t1) - 1 - index);
+ }
+ };
+ H.Symbol.prototype = {
+ get$hashCode: function(_) {
+ var hash = this._hashCode;
+ if (hash != null)
+ return hash;
+ hash = 536870911 & 664597 * J.get$hashCode$(this.__internal$_name);
+ this._hashCode = hash;
+ return hash;
+ },
+ toString$0: function(_) {
+ return 'Symbol("' + H.S(this.__internal$_name) + '")';
+ },
+ $eq: function(_, other) {
+ if (other == null)
+ return false;
+ return other instanceof H.Symbol && this.__internal$_name == other.__internal$_name;
+ }
+ };
+ H.ConstantMap.prototype = {
+ cast$2$0: function(_, RK, RV) {
+ var t1 = H._instanceType(this);
+ return P.Map_castFrom(this, t1._precomputed1, t1._rest[1], RK, RV);
+ },
+ get$isEmpty: function(_) {
+ return this.get$length(this) === 0;
+ },
+ toString$0: function(_) {
+ return P.MapBase_mapToString(this);
+ },
+ remove$1: function(_, key) {
+ return H.ConstantMap__throwUnmodifiable();
+ },
+ map$2$1: function(_, transform, K2, V2) {
+ var result = P.LinkedHashMap_LinkedHashMap$_empty(K2, V2);
+ this.forEach$1(0, new H.ConstantMap_map_closure(this, H._instanceType(this)._bind$1(K2)._bind$1(V2)._eval$1("MapEntry<1,2>(3,4)")._check(transform), result));
+ return result;
+ },
+ $isMap: 1
+ };
+ H.ConstantMap_map_closure.prototype = {
+ call$2: function(key, value) {
+ var t1 = H._instanceType(this.$this),
+ entry = this.transform.call$2(t1._precomputed1._check(key), t1._rest[1]._check(value));
+ this.result.$indexSet(0, entry.key, entry.value);
+ },
+ $signature: function() {
+ return H._instanceType(this.$this)._eval$1("Null(1,2)");
+ }
+ };
+ H.ConstantStringMap.prototype = {
+ get$length: function(_) {
+ return this.__js_helper$_length;
+ },
+ containsKey$1: function(key) {
+ if (typeof key != "string")
+ return false;
+ if ("__proto__" === key)
+ return false;
+ return this._jsObject.hasOwnProperty(key);
+ },
+ $index: function(_, key) {
+ if (!this.containsKey$1(key))
+ return null;
+ return this._fetch$1(key);
+ },
+ _fetch$1: function(key) {
+ return this._jsObject[H._checkStringNullable(key)];
+ },
+ forEach$1: function(_, f) {
+ var keys, t2, i, key,
+ t1 = H._instanceType(this);
+ t1._eval$1("~(1,2)")._check(f);
+ keys = this.__js_helper$_keys;
+ for (t2 = keys.length, t1 = t1._rest[1], i = 0; i < t2; ++i) {
+ key = keys[i];
+ f.call$2(key, t1._check(this._fetch$1(key)));
+ }
+ },
+ get$keys: function() {
+ return new H._ConstantMapKeyIterable(this, H._instanceType(this)._eval$1("_ConstantMapKeyIterable<1>"));
+ }
+ };
+ H._ConstantMapKeyIterable.prototype = {
+ get$iterator: function(_) {
+ var t1 = this.__js_helper$_map.__js_helper$_keys;
+ return new J.ArrayIterator(t1, t1.length, H._arrayInstanceType(t1)._eval$1("ArrayIterator<1>"));
+ },
+ get$length: function(_) {
+ return this.__js_helper$_map.__js_helper$_keys.length;
+ }
+ };
+ H.Instantiation.prototype = {
+ Instantiation$1: function(_genericClosure) {
+ if (false)
+ H.instantiatedGenericFunctionType(0, 0);
+ },
+ toString$0: function(_) {
+ var types = "<" + C.JSArray_methods.join$1([H.createRuntimeType(this.$ti._precomputed1)], ", ") + ">";
+ return H.S(this._genericClosure) + " with " + types;
+ }
+ };
+ H.Instantiation1.prototype = {
+ call$2: function(a0, a1) {
+ return this._genericClosure.call$1$2(a0, a1, this.$ti._rest[0]);
+ },
+ call$4: function(a0, a1, a2, a3) {
+ return this._genericClosure.call$1$4(a0, a1, a2, a3, this.$ti._rest[0]);
+ },
+ $signature: function() {
+ return H.instantiatedGenericFunctionType(H.closureFunctionType(this._genericClosure), this.$ti);
+ }
+ };
+ H.Primitives_initTicker_closure.prototype = {
+ call$0: function() {
+ return C.JSNumber_methods.floor$0(1000 * this.performance.now());
+ },
+ $signature: 36
+ };
+ H.TypeErrorDecoder.prototype = {
+ matchTypeError$1: function(message) {
+ var result, t1, _this = this,
+ match = new RegExp(_this._pattern).exec(message);
+ if (match == null)
+ return null;
+ result = Object.create(null);
+ t1 = _this._arguments;
+ if (t1 !== -1)
+ result.arguments = match[t1 + 1];
+ t1 = _this._argumentsExpr;
+ if (t1 !== -1)
+ result.argumentsExpr = match[t1 + 1];
+ t1 = _this._expr;
+ if (t1 !== -1)
+ result.expr = match[t1 + 1];
+ t1 = _this._method;
+ if (t1 !== -1)
+ result.method = match[t1 + 1];
+ t1 = _this._receiver;
+ if (t1 !== -1)
+ result.receiver = match[t1 + 1];
+ return result;
+ }
+ };
+ H.NullError.prototype = {
+ toString$0: function(_) {
+ var t1 = this._method;
+ if (t1 == null)
+ return "NoSuchMethodError: " + H.S(this.__js_helper$_message);
+ return "NoSuchMethodError: method not found: '" + t1 + "' on null";
+ },
+ $isNoSuchMethodError: 1
+ };
+ H.JsNoSuchMethodError.prototype = {
+ toString$0: function(_) {
+ var t2, _this = this,
+ _s38_ = "NoSuchMethodError: method not found: '",
+ t1 = _this._method;
+ if (t1 == null)
+ return "NoSuchMethodError: " + H.S(_this.__js_helper$_message);
+ t2 = _this._receiver;
+ if (t2 == null)
+ return _s38_ + t1 + "' (" + H.S(_this.__js_helper$_message) + ")";
+ return _s38_ + t1 + "' on '" + t2 + "' (" + H.S(_this.__js_helper$_message) + ")";
+ },
+ $isNoSuchMethodError: 1
+ };
+ H.UnknownJsTypeError.prototype = {
+ toString$0: function(_) {
+ var t1 = this.__js_helper$_message;
+ return t1.length === 0 ? "Error" : "Error: " + t1;
+ }
+ };
+ H.ExceptionAndStackTrace.prototype = {};
+ H.unwrapException_saveStackTrace.prototype = {
+ call$1: function(error) {
+ if (type$.Error._is(error))
+ if (error.$thrownJsError == null)
+ error.$thrownJsError = this.ex;
+ return error;
+ },
+ $signature: 18
+ };
+ H._StackTrace.prototype = {
+ toString$0: function(_) {
+ var trace,
+ t1 = this._trace;
+ if (t1 != null)
+ return t1;
+ t1 = this._exception;
+ trace = t1 !== null && typeof t1 === "object" ? t1.stack : null;
+ return this._trace = trace == null ? "" : trace;
+ },
+ $isStackTrace: 1
+ };
+ H.Closure.prototype = {
+ toString$0: function(_) {
+ var $constructor = this.constructor,
+ $name = $constructor == null ? null : $constructor.name;
+ return "Closure '" + H.unminifyOrTag($name == null ? "unknown" : $name) + "'";
+ },
+ $isFunction: 1,
+ get$$call: function() {
+ return this;
+ },
+ "call*": "call$1",
+ $requiredArgCount: 1,
+ $defaultValues: null
+ };
+ H.TearOffClosure.prototype = {};
+ H.StaticClosure.prototype = {
+ toString$0: function(_) {
+ var $name = this.$static_name;
+ if ($name == null)
+ return "Closure of unknown static method";
+ return "Closure '" + H.unminifyOrTag($name) + "'";
+ }
+ };
+ H.BoundClosure.prototype = {
+ $eq: function(_, other) {
+ var _this = this;
+ if (other == null)
+ return false;
+ if (_this === other)
+ return true;
+ if (!(other instanceof H.BoundClosure))
+ return false;
+ return _this._self === other._self && _this._target === other._target && _this._receiver === other._receiver;
+ },
+ get$hashCode: function(_) {
+ var receiverHashCode,
+ t1 = this._receiver;
+ if (t1 == null)
+ receiverHashCode = H.Primitives_objectHashCode(this._self);
+ else
+ receiverHashCode = typeof t1 !== "object" ? J.get$hashCode$(t1) : H.Primitives_objectHashCode(t1);
+ return (receiverHashCode ^ H.Primitives_objectHashCode(this._target)) >>> 0;
+ },
+ toString$0: function(_) {
+ var receiver = this._receiver;
+ if (receiver == null)
+ receiver = this._self;
+ return "Closure '" + H.S(this._name) + "' of " + ("Instance of '" + H.S(H.Primitives_objectTypeName(receiver)) + "'");
+ }
+ };
+ H.TypeErrorImplementation.prototype = {
+ toString$0: function(_) {
+ return this.message;
+ },
+ get$message: function(receiver) {
+ return this.message;
+ }
+ };
+ H.RuntimeError.prototype = {
+ toString$0: function(_) {
+ return "RuntimeError: " + H.S(this.message);
+ },
+ get$message: function(receiver) {
+ return this.message;
+ }
+ };
+ H._AssertionError.prototype = {
+ toString$0: function(_) {
+ return "Assertion failed: " + P.Error_safeToString(this.message);
+ }
+ };
+ H.JsLinkedHashMap.prototype = {
+ get$length: function(_) {
+ return this.__js_helper$_length;
+ },
+ get$isEmpty: function(_) {
+ return this.__js_helper$_length === 0;
+ },
+ get$keys: function() {
+ return new H.LinkedHashMapKeyIterable(this, H._instanceType(this)._eval$1("LinkedHashMapKeyIterable<1>"));
+ },
+ get$values: function() {
+ var t1 = H._instanceType(this);
+ return H.MappedIterable_MappedIterable(this.get$keys(), new H.JsLinkedHashMap_values_closure(this), t1._precomputed1, t1._rest[1]);
+ },
+ containsKey$1: function(key) {
+ var strings, nums, _this = this;
+ if (typeof key == "string") {
+ strings = _this.__js_helper$_strings;
+ if (strings == null)
+ return false;
+ return _this._containsTableEntry$2(strings, key);
+ } else if (typeof key == "number" && (key & 0x3ffffff) === key) {
+ nums = _this.__js_helper$_nums;
+ if (nums == null)
+ return false;
+ return _this._containsTableEntry$2(nums, key);
+ } else
+ return _this.internalContainsKey$1(key);
+ },
+ internalContainsKey$1: function(key) {
+ var _this = this,
+ rest = _this.__js_helper$_rest;
+ if (rest == null)
+ return false;
+ return _this.internalFindBucketIndex$2(_this._getTableBucket$2(rest, _this.internalComputeHashCode$1(key)), key) >= 0;
+ },
+ addAll$1: function(_, other) {
+ H._instanceType(this)._eval$1("Map<1,2>")._check(other).forEach$1(0, new H.JsLinkedHashMap_addAll_closure(this));
+ },
+ $index: function(_, key) {
+ var strings, cell, t1, nums, _this = this, _null = null;
+ if (typeof key == "string") {
+ strings = _this.__js_helper$_strings;
+ if (strings == null)
+ return _null;
+ cell = _this._getTableCell$2(strings, key);
+ t1 = cell == null ? _null : cell.hashMapCellValue;
+ return t1;
+ } else if (typeof key == "number" && (key & 0x3ffffff) === key) {
+ nums = _this.__js_helper$_nums;
+ if (nums == null)
+ return _null;
+ cell = _this._getTableCell$2(nums, key);
+ t1 = cell == null ? _null : cell.hashMapCellValue;
+ return t1;
+ } else
+ return _this.internalGet$1(key);
+ },
+ internalGet$1: function(key) {
+ var bucket, index, _this = this,
+ rest = _this.__js_helper$_rest;
+ if (rest == null)
+ return null;
+ bucket = _this._getTableBucket$2(rest, _this.internalComputeHashCode$1(key));
+ index = _this.internalFindBucketIndex$2(bucket, key);
+ if (index < 0)
+ return null;
+ return bucket[index].hashMapCellValue;
+ },
+ $indexSet: function(_, key, value) {
+ var strings, nums, _this = this,
+ t1 = H._instanceType(_this);
+ t1._precomputed1._check(key);
+ t1._rest[1]._check(value);
+ if (typeof key == "string") {
+ strings = _this.__js_helper$_strings;
+ _this.__js_helper$_addHashTableEntry$3(strings == null ? _this.__js_helper$_strings = _this._newHashTable$0() : strings, key, value);
+ } else if (typeof key == "number" && (key & 0x3ffffff) === key) {
+ nums = _this.__js_helper$_nums;
+ _this.__js_helper$_addHashTableEntry$3(nums == null ? _this.__js_helper$_nums = _this._newHashTable$0() : nums, key, value);
+ } else
+ _this.internalSet$2(key, value);
+ },
+ internalSet$2: function(key, value) {
+ var rest, hash, bucket, index, _this = this,
+ t1 = H._instanceType(_this);
+ t1._precomputed1._check(key);
+ t1._rest[1]._check(value);
+ rest = _this.__js_helper$_rest;
+ if (rest == null)
+ rest = _this.__js_helper$_rest = _this._newHashTable$0();
+ hash = _this.internalComputeHashCode$1(key);
+ bucket = _this._getTableBucket$2(rest, hash);
+ if (bucket == null)
+ _this._setTableEntry$3(rest, hash, [_this._newLinkedCell$2(key, value)]);
+ else {
+ index = _this.internalFindBucketIndex$2(bucket, key);
+ if (index >= 0)
+ bucket[index].hashMapCellValue = value;
+ else
+ bucket.push(_this._newLinkedCell$2(key, value));
+ }
+ },
+ putIfAbsent$2: function(key, ifAbsent) {
+ var value, _this = this,
+ t1 = H._instanceType(_this);
+ t1._precomputed1._check(key);
+ t1._eval$1("2()")._check(ifAbsent);
+ if (_this.containsKey$1(key))
+ return _this.$index(0, key);
+ value = ifAbsent.call$0();
+ _this.$indexSet(0, key, value);
+ return value;
+ },
+ remove$1: function(_, key) {
+ var _this = this;
+ if (typeof key == "string")
+ return _this.__js_helper$_removeHashTableEntry$2(_this.__js_helper$_strings, key);
+ else if (typeof key == "number" && (key & 0x3ffffff) === key)
+ return _this.__js_helper$_removeHashTableEntry$2(_this.__js_helper$_nums, key);
+ else
+ return _this.internalRemove$1(key);
+ },
+ internalRemove$1: function(key) {
+ var hash, bucket, index, cell, _this = this,
+ rest = _this.__js_helper$_rest;
+ if (rest == null)
+ return null;
+ hash = _this.internalComputeHashCode$1(key);
+ bucket = _this._getTableBucket$2(rest, hash);
+ index = _this.internalFindBucketIndex$2(bucket, key);
+ if (index < 0)
+ return null;
+ cell = bucket.splice(index, 1)[0];
+ _this.__js_helper$_unlinkCell$1(cell);
+ if (bucket.length === 0)
+ _this._deleteTableEntry$2(rest, hash);
+ return cell.hashMapCellValue;
+ },
+ clear$0: function(_) {
+ var _this = this;
+ if (_this.__js_helper$_length > 0) {
+ _this.__js_helper$_strings = _this.__js_helper$_nums = _this.__js_helper$_rest = _this._first = _this._last = null;
+ _this.__js_helper$_length = 0;
+ _this._modified$0();
+ }
+ },
+ forEach$1: function(_, action) {
+ var cell, modifications, _this = this;
+ H._instanceType(_this)._eval$1("~(1,2)")._check(action);
+ cell = _this._first;
+ modifications = _this._modifications;
+ for (; cell != null;) {
+ action.call$2(cell.hashMapCellKey, cell.hashMapCellValue);
+ if (modifications !== _this._modifications)
+ throw H.wrapException(P.ConcurrentModificationError$(_this));
+ cell = cell._next;
+ }
+ },
+ __js_helper$_addHashTableEntry$3: function(table, key, value) {
+ var cell, _this = this,
+ t1 = H._instanceType(_this);
+ t1._precomputed1._check(key);
+ t1._rest[1]._check(value);
+ cell = _this._getTableCell$2(table, key);
+ if (cell == null)
+ _this._setTableEntry$3(table, key, _this._newLinkedCell$2(key, value));
+ else
+ cell.hashMapCellValue = value;
+ },
+ __js_helper$_removeHashTableEntry$2: function(table, key) {
+ var cell;
+ if (table == null)
+ return null;
+ cell = this._getTableCell$2(table, key);
+ if (cell == null)
+ return null;
+ this.__js_helper$_unlinkCell$1(cell);
+ this._deleteTableEntry$2(table, key);
+ return cell.hashMapCellValue;
+ },
+ _modified$0: function() {
+ this._modifications = this._modifications + 1 & 67108863;
+ },
+ _newLinkedCell$2: function(key, value) {
+ var last, _this = this,
+ t1 = H._instanceType(_this),
+ cell = new H.LinkedHashMapCell(t1._precomputed1._check(key), t1._rest[1]._check(value));
+ if (_this._first == null)
+ _this._first = _this._last = cell;
+ else {
+ last = _this._last;
+ cell._previous = last;
+ _this._last = last._next = cell;
+ }
+ ++_this.__js_helper$_length;
+ _this._modified$0();
+ return cell;
+ },
+ __js_helper$_unlinkCell$1: function(cell) {
+ var _this = this,
+ previous = cell._previous,
+ next = cell._next;
+ if (previous == null) {
+ H.assertHelper(cell == _this._first);
+ _this._first = next;
+ } else
+ previous._next = next;
+ if (next == null) {
+ H.assertHelper(cell == _this._last);
+ _this._last = previous;
+ } else
+ next._previous = previous;
+ --_this.__js_helper$_length;
+ _this._modified$0();
+ },
+ internalComputeHashCode$1: function(key) {
+ return J.get$hashCode$(key) & 0x3ffffff;
+ },
+ internalFindBucketIndex$2: function(bucket, key) {
+ var $length, i;
+ if (bucket == null)
+ return -1;
+ $length = bucket.length;
+ for (i = 0; i < $length; ++i)
+ if (J.$eq$(bucket[i].hashMapCellKey, key))
+ return i;
+ return -1;
+ },
+ toString$0: function(_) {
+ return P.MapBase_mapToString(this);
+ },
+ _getTableCell$2: function(table, key) {
+ return table[key];
+ },
+ _getTableBucket$2: function(table, key) {
+ return table[key];
+ },
+ _setTableEntry$3: function(table, key, value) {
+ H.assertHelper(value != null);
+ table[key] = value;
+ },
+ _deleteTableEntry$2: function(table, key) {
+ delete table[key];
+ },
+ _containsTableEntry$2: function(table, key) {
+ return this._getTableCell$2(table, key) != null;
+ },
+ _newHashTable$0: function() {
+ var _s20_ = "<non-identifier-key>",
+ table = Object.create(null);
+ this._setTableEntry$3(table, _s20_, table);
+ this._deleteTableEntry$2(table, _s20_);
+ return table;
+ },
+ $isLinkedHashMap: 1
+ };
+ H.JsLinkedHashMap_values_closure.prototype = {
+ call$1: function(each) {
+ var t1 = this.$this;
+ return t1.$index(0, H._instanceType(t1)._precomputed1._check(each));
+ },
+ $signature: function() {
+ return H._instanceType(this.$this)._eval$1("2(1)");
+ }
+ };
+ H.JsLinkedHashMap_addAll_closure.prototype = {
+ call$2: function(key, value) {
+ var t1 = this.$this,
+ t2 = H._instanceType(t1);
+ t1.$indexSet(0, t2._precomputed1._check(key), t2._rest[1]._check(value));
+ },
+ $signature: function() {
+ return H._instanceType(this.$this)._eval$1("Null(1,2)");
+ }
+ };
+ H.LinkedHashMapCell.prototype = {};
+ H.LinkedHashMapKeyIterable.prototype = {
+ get$length: function(_) {
+ return this.__js_helper$_map.__js_helper$_length;
+ },
+ get$isEmpty: function(_) {
+ return this.__js_helper$_map.__js_helper$_length === 0;
+ },
+ get$iterator: function(_) {
+ var t1 = this.__js_helper$_map,
+ t2 = new H.LinkedHashMapKeyIterator(t1, t1._modifications, this.$ti._eval$1("LinkedHashMapKeyIterator<1>"));
+ t2._cell = t1._first;
+ return t2;
+ },
+ contains$1: function(_, element) {
+ return this.__js_helper$_map.containsKey$1(element);
+ }
+ };
+ H.LinkedHashMapKeyIterator.prototype = {
+ get$current: function() {
+ return this.__js_helper$_current;
+ },
+ moveNext$0: function() {
+ var _this = this,
+ t1 = _this.__js_helper$_map;
+ if (_this._modifications !== t1._modifications)
+ throw H.wrapException(P.ConcurrentModificationError$(t1));
+ else {
+ t1 = _this._cell;
+ if (t1 == null) {
+ _this.set$__js_helper$_current(null);
+ return false;
+ } else {
+ _this.set$__js_helper$_current(t1.hashMapCellKey);
+ _this._cell = _this._cell._next;
+ return true;
+ }
+ }
+ },
+ set$__js_helper$_current: function(_current) {
+ this.__js_helper$_current = this.$ti._precomputed1._check(_current);
+ },
+ $isIterator: 1
+ };
+ H.initHooks_closure.prototype = {
+ call$1: function(o) {
+ return this.getTag(o);
+ },
+ $signature: 18
+ };
+ H.initHooks_closure0.prototype = {
+ call$2: function(o, tag) {
+ return this.getUnknownTag(o, tag);
+ },
+ $signature: 104
+ };
+ H.initHooks_closure1.prototype = {
+ call$1: function(tag) {
+ return this.prototypeForTag(H._checkStringNullable(tag));
+ },
+ $signature: 94
+ };
+ H.JSSyntaxRegExp.prototype = {
+ toString$0: function(_) {
+ return "RegExp/" + this.pattern + "/" + this._nativeRegExp.flags;
+ },
+ get$_nativeGlobalVersion: function() {
+ var _this = this,
+ t1 = _this._nativeGlobalRegExp;
+ if (t1 != null)
+ return t1;
+ t1 = _this._nativeRegExp;
+ return _this._nativeGlobalRegExp = H.JSSyntaxRegExp_makeNative(_this.pattern, t1.multiline, !t1.ignoreCase, t1.unicode, t1.dotAll, true);
+ },
+ get$_nativeAnchoredVersion: function() {
+ var _this = this,
+ t1 = _this._nativeAnchoredRegExp;
+ if (t1 != null)
+ return t1;
+ t1 = _this._nativeRegExp;
+ return _this._nativeAnchoredRegExp = H.JSSyntaxRegExp_makeNative(_this.pattern + "|()", t1.multiline, !t1.ignoreCase, t1.unicode, t1.dotAll, true);
+ },
+ firstMatch$1: function(string) {
+ var m;
+ if (typeof string != "string")
+ H.throwExpression(H.argumentErrorValue(string));
+ m = this._nativeRegExp.exec(string);
+ if (m == null)
+ return null;
+ return H._MatchImplementation$(this, m);
+ },
+ allMatches$2: function(_, string, start) {
+ var t1 = string.length;
+ if (start > t1)
+ throw H.wrapException(P.RangeError$range(start, 0, t1, null, null));
+ return new H._AllMatchesIterable(this, string, start);
+ },
+ allMatches$1: function($receiver, string) {
+ return this.allMatches$2($receiver, string, 0);
+ },
+ _execGlobal$2: function(string, start) {
+ var match,
+ regexp = this.get$_nativeGlobalVersion();
+ regexp.lastIndex = start;
+ match = regexp.exec(string);
+ if (match == null)
+ return null;
+ return H._MatchImplementation$(this, match);
+ },
+ _execAnchored$2: function(string, start) {
+ var match,
+ regexp = this.get$_nativeAnchoredVersion();
+ regexp.lastIndex = start;
+ match = regexp.exec(string);
+ if (match == null)
+ return null;
+ if (0 >= match.length)
+ return H.ioore(match, -1);
+ if (match.pop() != null)
+ return null;
+ return H._MatchImplementation$(this, match);
+ },
+ matchAsPrefix$2: function(_, string, start) {
+ if (start < 0 || start > string.length)
+ throw H.wrapException(P.RangeError$range(start, 0, string.length, null, null));
+ return this._execAnchored$2(string, start);
+ },
+ $isPattern: 1,
+ $isRegExp: 1
+ };
+ H._MatchImplementation.prototype = {
+ get$start: function() {
+ return this._match.index;
+ },
+ get$end: function() {
+ var t1 = this._match;
+ return t1.index + t1[0].length;
+ },
+ $index: function(_, index) {
+ var t1;
+ H._checkIntNullable(index);
+ t1 = this._match;
+ if (index >= t1.length)
+ return H.ioore(t1, index);
+ return t1[index];
+ },
+ $isMatch: 1,
+ $isRegExpMatch: 1
+ };
+ H._AllMatchesIterable.prototype = {
+ get$iterator: function(_) {
+ return new H._AllMatchesIterator(this._re, this.__js_helper$_string, this._start);
+ }
+ };
+ H._AllMatchesIterator.prototype = {
+ get$current: function() {
+ return this.__js_helper$_current;
+ },
+ moveNext$0: function() {
+ var t2, t3, match, nextIndex, _this = this,
+ t1 = _this.__js_helper$_string;
+ if (t1 == null)
+ return false;
+ t2 = _this._nextIndex;
+ if (t2 <= t1.length) {
+ t3 = _this._regExp;
+ match = t3._execGlobal$2(t1, t2);
+ if (match != null) {
+ _this.__js_helper$_current = match;
+ nextIndex = match.get$end();
+ if (match._match.index === nextIndex) {
+ if (t3._nativeRegExp.unicode) {
+ t1 = _this._nextIndex;
+ t2 = t1 + 1;
+ t3 = _this.__js_helper$_string;
+ if (t2 < t3.length) {
+ t1 = J.getInterceptor$s(t3).codeUnitAt$1(t3, t1);
+ if (t1 >= 55296 && t1 <= 56319) {
+ t1 = C.JSString_methods.codeUnitAt$1(t3, t2);
+ t1 = t1 >= 56320 && t1 <= 57343;
+ } else
+ t1 = false;
+ } else
+ t1 = false;
+ } else
+ t1 = false;
+ nextIndex = (t1 ? nextIndex + 1 : nextIndex) + 1;
+ }
+ _this._nextIndex = nextIndex;
+ return true;
+ }
+ }
+ _this.__js_helper$_string = _this.__js_helper$_current = null;
+ return false;
+ },
+ $isIterator: 1
+ };
+ H.StringMatch.prototype = {
+ get$end: function() {
+ return this.start + this.pattern.length;
+ },
+ $index: function(_, g) {
+ H._checkIntNullable(g);
+ if (g !== 0)
+ H.throwExpression(P.RangeError$value(g, null));
+ return this.pattern;
+ },
+ $isMatch: 1,
+ get$start: function() {
+ return this.start;
+ }
+ };
+ H._StringAllMatchesIterable.prototype = {
+ get$iterator: function(_) {
+ return new H._StringAllMatchesIterator(this._input, this._pattern, this.__js_helper$_index);
+ },
+ get$first: function(_) {
+ var t1 = this._pattern,
+ index = this._input.indexOf(t1, this.__js_helper$_index);
+ if (index >= 0)
+ return new H.StringMatch(index, t1);
+ throw H.wrapException(H.IterableElementError_noElement());
+ }
+ };
+ H._StringAllMatchesIterator.prototype = {
+ moveNext$0: function() {
+ var index, end, _this = this,
+ t1 = _this.__js_helper$_index,
+ t2 = _this._pattern,
+ t3 = t2.length,
+ t4 = _this._input,
+ t5 = t4.length;
+ if (t1 + t3 > t5) {
+ _this.__js_helper$_current = null;
+ return false;
+ }
+ index = t4.indexOf(t2, t1);
+ if (index < 0) {
+ _this.__js_helper$_index = t5 + 1;
+ _this.__js_helper$_current = null;
+ return false;
+ }
+ end = index + t3;
+ _this.__js_helper$_current = new H.StringMatch(index, t2);
+ _this.__js_helper$_index = end === _this.__js_helper$_index ? end + 1 : end;
+ return true;
+ },
+ get$current: function() {
+ return this.__js_helper$_current;
+ },
+ $isIterator: 1
+ };
+ H.NativeByteBuffer.prototype = {
+ get$runtimeType: function(receiver) {
+ return C.Type_ByteBuffer_RkP;
+ },
+ $isNativeByteBuffer: 1,
+ $isByteBuffer: 1
+ };
+ H.NativeTypedData.prototype = {$isNativeTypedData: 1};
+ H.NativeByteData.prototype = {
+ get$runtimeType: function(receiver) {
+ return C.Type_ByteData_zNC;
+ }
+ };
+ H.NativeTypedArray.prototype = {
+ get$length: function(receiver) {
+ return receiver.length;
+ },
+ $isJavaScriptIndexingBehavior: 1
+ };
+ H.NativeTypedArrayOfDouble.prototype = {
+ $index: function(receiver, index) {
+ H._checkIntNullable(index);
+ H._checkValidIndex(index, receiver, receiver.length);
+ return receiver[index];
+ },
+ $indexSet: function(receiver, index, value) {
+ H._checkIntNullable(index);
+ H._checkDoubleNullable(value);
+ H._checkValidIndex(index, receiver, receiver.length);
+ receiver[index] = value;
+ },
+ $isEfficientLengthIterable: 1,
+ $isIterable: 1,
+ $isList: 1
+ };
+ H.NativeTypedArrayOfInt.prototype = {
+ $indexSet: function(receiver, index, value) {
+ H._checkIntNullable(index);
+ H._checkIntNullable(value);
+ H._checkValidIndex(index, receiver, receiver.length);
+ receiver[index] = value;
+ },
+ $isEfficientLengthIterable: 1,
+ $isIterable: 1,
+ $isList: 1
+ };
+ H.NativeFloat32List.prototype = {
+ get$runtimeType: function(receiver) {
+ return C.Type_Float32List_LB7;
+ }
+ };
+ H.NativeFloat64List.prototype = {
+ get$runtimeType: function(receiver) {
+ return C.Type_Float64List_LB7;
+ }
+ };
+ H.NativeInt16List.prototype = {
+ get$runtimeType: function(receiver) {
+ return C.Type_Int16List_uXf;
+ },
+ $index: function(receiver, index) {
+ H._checkIntNullable(index);
+ H._checkValidIndex(index, receiver, receiver.length);
+ return receiver[index];
+ }
+ };
+ H.NativeInt32List.prototype = {
+ get$runtimeType: function(receiver) {
+ return C.Type_Int32List_O50;
+ },
+ $index: function(receiver, index) {
+ H._checkIntNullable(index);
+ H._checkValidIndex(index, receiver, receiver.length);
+ return receiver[index];
+ }
+ };
+ H.NativeInt8List.prototype = {
+ get$runtimeType: function(receiver) {
+ return C.Type_Int8List_ekJ;
+ },
+ $index: function(receiver, index) {
+ H._checkIntNullable(index);
+ H._checkValidIndex(index, receiver, receiver.length);
+ return receiver[index];
+ }
+ };
+ H.NativeUint16List.prototype = {
+ get$runtimeType: function(receiver) {
+ return C.Type_Uint16List_2bx;
+ },
+ $index: function(receiver, index) {
+ H._checkIntNullable(index);
+ H._checkValidIndex(index, receiver, receiver.length);
+ return receiver[index];
+ }
+ };
+ H.NativeUint32List.prototype = {
+ get$runtimeType: function(receiver) {
+ return C.Type_Uint32List_2bx;
+ },
+ $index: function(receiver, index) {
+ H._checkIntNullable(index);
+ H._checkValidIndex(index, receiver, receiver.length);
+ return receiver[index];
+ },
+ sublist$2: function(receiver, start, end) {
+ return new Uint32Array(receiver.subarray(start, H._checkValidRange(start, end, receiver.length)));
+ },
+ $isUint32List: 1
+ };
+ H.NativeUint8ClampedList.prototype = {
+ get$runtimeType: function(receiver) {
+ return C.Type_Uint8ClampedList_Jik;
+ },
+ get$length: function(receiver) {
+ return receiver.length;
+ },
+ $index: function(receiver, index) {
+ H._checkIntNullable(index);
+ H._checkValidIndex(index, receiver, receiver.length);
+ return receiver[index];
+ }
+ };
+ H.NativeUint8List.prototype = {
+ get$runtimeType: function(receiver) {
+ return C.Type_Uint8List_WLA;
+ },
+ get$length: function(receiver) {
+ return receiver.length;
+ },
+ $index: function(receiver, index) {
+ H._checkIntNullable(index);
+ H._checkValidIndex(index, receiver, receiver.length);
+ return receiver[index];
+ },
+ sublist$2: function(receiver, start, end) {
+ return new Uint8Array(receiver.subarray(start, H._checkValidRange(start, end, receiver.length)));
+ },
+ $isNativeUint8List: 1,
+ $isUint8List: 1
+ };
+ H._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin.prototype = {};
+ H._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin_FixedLengthListMixin.prototype = {};
+ H._NativeTypedArrayOfInt_NativeTypedArray_ListMixin.prototype = {};
+ H._NativeTypedArrayOfInt_NativeTypedArray_ListMixin_FixedLengthListMixin.prototype = {};
+ H.Rti.prototype = {
+ _eval$1: function(recipe) {
+ return H._Universe_evalInEnvironment(init.typeUniverse, this, recipe);
+ },
+ _bind$1: function(typeOrTuple) {
+ return H._Universe_bind(init.typeUniverse, this, typeOrTuple);
+ }
+ };
+ H._FunctionParameters.prototype = {};
+ H._Type.prototype = {
+ get$hashCode: function(_) {
+ var s,
+ t1 = this._hashCode;
+ if (t1 == null) {
+ s = this._rti._canonicalRecipe;
+ if (H.assertTest(typeof s == "string"))
+ H.assertThrow("Missing canonical recipe");
+ t1 = this._hashCode = C.JSString_methods.get$hashCode(s);
+ }
+ return t1;
+ },
+ $eq: function(_, other) {
+ if (other == null)
+ return false;
+ return other instanceof H._Type && this._rti == other._rti;
+ },
+ toString$0: function(_) {
+ return H._rtiToString(this._rti, null);
+ },
+ $isType: 1
+ };
+ H._Error.prototype = {
+ toString$0: function(_) {
+ return this._message;
+ }
+ };
+ H._CastError.prototype = {};
+ H._TypeError.prototype = {
+ get$message: function(_) {
+ return this._message;
+ }
+ };
+ P._AsyncRun__initializeScheduleImmediate_internalCallback.prototype = {
+ call$1: function(_) {
+ var t1 = this._box_0,
+ f = t1.storedCallback;
+ t1.storedCallback = null;
+ f.call$0();
+ },
+ $signature: 3
+ };
+ P._AsyncRun__initializeScheduleImmediate_closure.prototype = {
+ call$1: function(callback) {
+ var t1, t2;
+ type$.void_Function._check(callback);
+ t1 = this._box_0;
+ H.assertHelper(t1.storedCallback == null);
+ t1.storedCallback = callback;
+ t1 = this.div;
+ t2 = this.span;
+ t1.firstChild ? t1.removeChild(t2) : t1.appendChild(t2);
+ },
+ $signature: 91
+ };
+ P._AsyncRun__scheduleImmediateJsOverride_internalCallback.prototype = {
+ call$0: function() {
+ this.callback.call$0();
+ },
+ $signature: 0
+ };
+ P._AsyncRun__scheduleImmediateWithSetImmediate_internalCallback.prototype = {
+ call$0: function() {
+ this.callback.call$0();
+ },
+ $signature: 0
+ };
+ P._TimerImpl.prototype = {
+ _TimerImpl$2: function(milliseconds, callback) {
+ if (self.setTimeout != null)
+ this._handle = self.setTimeout(H.convertDartClosureToJS(new P._TimerImpl_internalCallback(this, callback), 0), milliseconds);
+ else
+ throw H.wrapException(P.UnsupportedError$("`setTimeout()` not found."));
+ },
+ _TimerImpl$periodic$2: function(milliseconds, callback) {
+ if (self.setTimeout != null)
+ this._handle = self.setInterval(H.convertDartClosureToJS(new P._TimerImpl$periodic_closure(this, milliseconds, Date.now(), callback), 0), milliseconds);
+ else
+ throw H.wrapException(P.UnsupportedError$("Periodic timer."));
+ },
+ cancel$0: function() {
+ if (self.setTimeout != null) {
+ var t1 = this._handle;
+ if (t1 == null)
+ return;
+ if (this._once)
+ self.clearTimeout(t1);
+ else
+ self.clearInterval(t1);
+ this._handle = null;
+ } else
+ throw H.wrapException(P.UnsupportedError$("Canceling a timer."));
+ },
+ $isTimer: 1
+ };
+ P._TimerImpl_internalCallback.prototype = {
+ call$0: function() {
+ var t1 = this.$this;
+ t1._handle = null;
+ t1._tick = 1;
+ this.callback.call$0();
+ },
+ $signature: 1
+ };
+ P._TimerImpl$periodic_closure.prototype = {
+ call$0: function() {
+ var duration, _this = this,
+ t1 = _this.$this,
+ tick = t1._tick + 1,
+ t2 = _this.milliseconds;
+ if (t2 > 0) {
+ duration = Date.now() - _this.start;
+ if (duration > (tick + 1) * t2)
+ tick = C.JSInt_methods.$tdiv(duration, t2);
+ }
+ t1._tick = tick;
+ _this.callback.call$1(t1);
+ },
+ $signature: 0
+ };
+ P._AsyncAwaitCompleter.prototype = {
+ complete$1: function(value) {
+ var t2, t3,
+ t1 = this.$ti;
+ t1._eval$1("1/")._check(value);
+ t2 = !this.isSync || t1._eval$1("Future<1>")._is(value);
+ t3 = this._future;
+ if (t2)
+ t3._asyncComplete$1(value);
+ else
+ t3._completeWithValue$1(t1._precomputed1._check(value));
+ },
+ completeError$2: function(e, st) {
+ var t1 = this._future;
+ if (this.isSync)
+ t1._completeError$2(e, st);
+ else
+ t1._asyncCompleteError$2(e, st);
+ },
+ $isCompleter: 1
+ };
+ P._awaitOnObject_closure.prototype = {
+ call$1: function(result) {
+ return this.bodyFunction.call$2(0, result);
+ },
+ $signature: 15
+ };
+ P._awaitOnObject_closure0.prototype = {
+ call$2: function(error, stackTrace) {
+ this.bodyFunction.call$2(1, new H.ExceptionAndStackTrace(error, type$.StackTrace._check(stackTrace)));
+ },
+ $signature: 5
+ };
+ P._wrapJsFunctionForAsync_closure.prototype = {
+ call$2: function(errorCode, result) {
+ this.$protected(H._checkIntNullable(errorCode), result);
+ },
+ $signature: 89
+ };
+ P._BroadcastStream.prototype = {
+ get$isBroadcast: function() {
+ return true;
+ }
+ };
+ P._BroadcastSubscription.prototype = {
+ _onPause$0: function() {
+ },
+ _onResume$0: function() {
+ },
+ set$_async$_next: function(_next) {
+ this._async$_next = this.$ti._check(_next);
+ },
+ set$_async$_previous: function(_previous) {
+ this._async$_previous = this.$ti._check(_previous);
+ }
+ };
+ P._BroadcastStreamController.prototype = {
+ get$_mayAddEvent: function() {
+ return this._state < 4;
+ },
+ _ensureDoneFuture$0: function() {
+ var t1 = this._doneFuture;
+ if (t1 != null)
+ return t1;
+ return this._doneFuture = new P._Future($.Zone__current, type$._Future_dynamic);
+ },
+ _removeListener$1: function(subscription) {
+ var previous, next, _this = this;
+ H._instanceType(_this)._eval$1("_BroadcastSubscription<1>")._check(subscription);
+ H.assertHelper(subscription._controller === _this);
+ H.assertHelper(subscription._async$_next !== subscription);
+ previous = subscription._async$_previous;
+ next = subscription._async$_next;
+ if (previous == null)
+ _this.set$_firstSubscription(next);
+ else
+ previous.set$_async$_next(next);
+ if (next == null)
+ _this.set$_lastSubscription(previous);
+ else
+ next.set$_async$_previous(previous);
+ subscription.set$_async$_previous(subscription);
+ subscription.set$_async$_next(subscription);
+ },
+ _subscribe$4: function(onData, onError, onDone, cancelOnError) {
+ var t2, t3, t4, subscription, oldLast, _this = this,
+ t1 = H._instanceType(_this);
+ t1._eval$1("~(1)")._check(onData);
+ type$.void_Function._check(onDone);
+ if ((_this._state & 4) !== 0) {
+ if (onDone == null)
+ onDone = P.async___nullDoneHandler$closure();
+ t1 = new P._DoneStreamSubscription($.Zone__current, onDone, t1._eval$1("_DoneStreamSubscription<1>"));
+ t1._schedule$0();
+ return t1;
+ }
+ t2 = $.Zone__current;
+ t3 = cancelOnError ? 1 : 0;
+ t4 = t1._eval$1("_BroadcastSubscription<1>");
+ subscription = new P._BroadcastSubscription(_this, t2, t3, t4);
+ subscription._BufferingStreamSubscription$4(onData, onError, onDone, cancelOnError, t1._precomputed1);
+ subscription.set$_async$_previous(subscription);
+ subscription.set$_async$_next(subscription);
+ t4._check(subscription);
+ H.assertHelper(subscription._async$_next === subscription);
+ subscription._eventState = _this._state & 1;
+ oldLast = _this._lastSubscription;
+ _this.set$_lastSubscription(subscription);
+ subscription.set$_async$_next(null);
+ subscription.set$_async$_previous(oldLast);
+ if (oldLast == null)
+ _this.set$_firstSubscription(subscription);
+ else
+ oldLast.set$_async$_next(subscription);
+ if (_this._firstSubscription == _this._lastSubscription)
+ P._runGuarded(_this.onListen);
+ return subscription;
+ },
+ _recordCancel$1: function(sub) {
+ var _this = this,
+ t1 = H._instanceType(_this);
+ sub = t1._eval$1("_BroadcastSubscription<1>")._check(t1._eval$1("StreamSubscription<1>")._check(sub));
+ if (sub._async$_next === sub)
+ return null;
+ t1 = (sub._eventState & 2) !== 0;
+ if (t1) {
+ H.assertHelper(t1);
+ sub._eventState |= 4;
+ } else {
+ _this._removeListener$1(sub);
+ if ((_this._state & 2) === 0 && _this._firstSubscription == null)
+ _this._callOnCancel$0();
+ }
+ return null;
+ },
+ _recordPause$1: function(subscription) {
+ H._instanceType(this)._eval$1("StreamSubscription<1>")._check(subscription);
+ },
+ _recordResume$1: function(subscription) {
+ H._instanceType(this)._eval$1("StreamSubscription<1>")._check(subscription);
+ },
+ _addEventError$0: function() {
+ var t1 = this._state;
+ if ((t1 & 4) !== 0)
+ return new P.StateError("Cannot add new events after calling close");
+ H.assertHelper((t1 & 8) !== 0);
+ return new P.StateError("Cannot add new events while doing an addStream");
+ },
+ add$1: function(_, data) {
+ var _this = this;
+ H._instanceType(_this)._precomputed1._check(data);
+ if (!_this.get$_mayAddEvent())
+ throw H.wrapException(_this._addEventError$0());
+ _this._sendData$1(data);
+ },
+ addError$2: function(error, stackTrace) {
+ var replacement;
+ type$.StackTrace._check(stackTrace);
+ if (error == null)
+ error = new P.NullThrownError();
+ if (!this.get$_mayAddEvent())
+ throw H.wrapException(this._addEventError$0());
+ replacement = $.Zone__current.errorCallback$2(error, stackTrace);
+ if (replacement != null) {
+ error = replacement.error;
+ if (error == null)
+ error = new P.NullThrownError();
+ stackTrace = replacement.stackTrace;
+ }
+ this._sendError$2(error, stackTrace);
+ },
+ addError$1: function(error) {
+ return this.addError$2(error, null);
+ },
+ close$0: function(_) {
+ var doneFuture, _this = this;
+ if ((_this._state & 4) !== 0) {
+ H.assertHelper(_this._doneFuture != null);
+ return _this._doneFuture;
+ }
+ if (!_this.get$_mayAddEvent())
+ throw H.wrapException(_this._addEventError$0());
+ _this._state |= 4;
+ doneFuture = _this._ensureDoneFuture$0();
+ _this._sendDone$0();
+ return doneFuture;
+ },
+ _async$_addError$2: function(error, stackTrace) {
+ this._sendError$2(error, type$.StackTrace._check(stackTrace));
+ },
+ _close$0: function() {
+ var addState, _this = this;
+ H.assertHelper((_this._state & 8) !== 0);
+ addState = _this._addStreamState;
+ _this.set$_addStreamState(null);
+ _this._state &= 4294967287;
+ addState.addStreamFuture._asyncComplete$1(null);
+ },
+ _forEachListener$1: function(action) {
+ var t1, subscription, id, next, _this = this;
+ H._instanceType(_this)._eval$1("~(_BufferingStreamSubscription<1>)")._check(action);
+ t1 = _this._state;
+ if ((t1 & 2) !== 0)
+ throw H.wrapException(P.StateError$("Cannot fire new event. Controller is already firing an event"));
+ subscription = _this._firstSubscription;
+ if (subscription == null)
+ return;
+ id = t1 & 1;
+ _this._state = t1 ^ 3;
+ for (; subscription != null;) {
+ t1 = subscription._eventState;
+ if ((t1 & 1) === id) {
+ subscription._eventState = t1 | 2;
+ action.call$1(subscription);
+ t1 = subscription._eventState ^= 1;
+ next = subscription._async$_next;
+ if ((t1 & 4) !== 0)
+ _this._removeListener$1(subscription);
+ subscription._eventState &= 4294967293;
+ subscription = next;
+ } else
+ subscription = subscription._async$_next;
+ }
+ _this._state &= 4294967293;
+ if (_this._firstSubscription == null)
+ _this._callOnCancel$0();
+ },
+ _callOnCancel$0: function() {
+ var _this = this;
+ H.assertHelper(_this._firstSubscription == null);
+ if ((_this._state & 4) !== 0 && _this._doneFuture._state === 0)
+ _this._doneFuture._asyncComplete$1(null);
+ P._runGuarded(_this.onCancel);
+ },
+ set$_firstSubscription: function(_firstSubscription) {
+ this._firstSubscription = H._instanceType(this)._eval$1("_BroadcastSubscription<1>")._check(_firstSubscription);
+ },
+ set$_lastSubscription: function(_lastSubscription) {
+ this._lastSubscription = H._instanceType(this)._eval$1("_BroadcastSubscription<1>")._check(_lastSubscription);
+ },
+ set$_addStreamState: function(_addStreamState) {
+ this._addStreamState = H._instanceType(this)._eval$1("_AddStreamState<1>")._check(_addStreamState);
+ },
+ $isEventSink: 1,
+ $isStreamConsumer: 1,
+ $isStreamSink: 1,
+ $isStreamController: 1,
+ $is_StreamControllerLifecycle: 1,
+ $is_EventDispatch: 1,
+ $isSink: 1
+ };
+ P._SyncBroadcastStreamController.prototype = {
+ get$_mayAddEvent: function() {
+ return P._BroadcastStreamController.prototype.get$_mayAddEvent.call(this) && (this._state & 2) === 0;
+ },
+ _addEventError$0: function() {
+ if ((this._state & 2) !== 0)
+ return new P.StateError("Cannot fire new event. Controller is already firing an event");
+ return this.super$_BroadcastStreamController$_addEventError();
+ },
+ _sendData$1: function(data) {
+ var t1, _this = this;
+ _this.$ti._precomputed1._check(data);
+ if (_this._firstSubscription == null)
+ return;
+ t1 = _this._firstSubscription;
+ if (t1 == _this._lastSubscription) {
+ _this._state |= 2;
+ t1._add$1(data);
+ _this._state &= 4294967293;
+ if (_this._firstSubscription == null)
+ _this._callOnCancel$0();
+ return;
+ }
+ _this._forEachListener$1(new P._SyncBroadcastStreamController__sendData_closure(_this, data));
+ },
+ _sendError$2: function(error, stackTrace) {
+ if (this._firstSubscription == null)
+ return;
+ this._forEachListener$1(new P._SyncBroadcastStreamController__sendError_closure(this, error, stackTrace));
+ },
+ _sendDone$0: function() {
+ var _this = this;
+ if (_this._firstSubscription != null)
+ _this._forEachListener$1(new P._SyncBroadcastStreamController__sendDone_closure(_this));
+ else {
+ H.assertHelper(_this._doneFuture != null);
+ H.assertHelper(_this._doneFuture._state === 0);
+ _this._doneFuture._asyncComplete$1(null);
+ }
+ }
+ };
+ P._SyncBroadcastStreamController__sendData_closure.prototype = {
+ call$1: function(subscription) {
+ this.$this.$ti._eval$1("_BufferingStreamSubscription<1>")._check(subscription)._add$1(this.data);
+ },
+ $signature: function() {
+ return this.$this.$ti._eval$1("Null(_BufferingStreamSubscription<1>)");
+ }
+ };
+ P._SyncBroadcastStreamController__sendError_closure.prototype = {
+ call$1: function(subscription) {
+ this.$this.$ti._eval$1("_BufferingStreamSubscription<1>")._check(subscription)._async$_addError$2(this.error, this.stackTrace);
+ },
+ $signature: function() {
+ return this.$this.$ti._eval$1("Null(_BufferingStreamSubscription<1>)");
+ }
+ };
+ P._SyncBroadcastStreamController__sendDone_closure.prototype = {
+ call$1: function(subscription) {
+ this.$this.$ti._eval$1("_BufferingStreamSubscription<1>")._check(subscription)._close$0();
+ },
+ $signature: function() {
+ return this.$this.$ti._eval$1("Null(_BufferingStreamSubscription<1>)");
+ }
+ };
+ P._AsyncBroadcastStreamController.prototype = {
+ _sendData$1: function(data) {
+ var subscription,
+ t1 = this.$ti;
+ t1._precomputed1._check(data);
+ for (subscription = this._firstSubscription, t1 = t1._eval$1("_DelayedData<1>"); subscription != null; subscription = subscription._async$_next)
+ subscription._addPending$1(new P._DelayedData(data, t1));
+ },
+ _sendError$2: function(error, stackTrace) {
+ var subscription;
+ for (subscription = this._firstSubscription; subscription != null; subscription = subscription._async$_next)
+ subscription._addPending$1(new P._DelayedError(error, stackTrace));
+ },
+ _sendDone$0: function() {
+ var _this = this,
+ subscription = _this._firstSubscription;
+ if (subscription != null)
+ for (; subscription != null; subscription = subscription._async$_next)
+ subscription._addPending$1(C.C__DelayedDone);
+ else {
+ H.assertHelper(_this._doneFuture != null);
+ H.assertHelper(_this._doneFuture._state === 0);
+ _this._doneFuture._asyncComplete$1(null);
+ }
+ }
+ };
+ P.Future.prototype = {};
+ P.Future_Future_closure.prototype = {
+ call$0: function() {
+ var e, s, exception;
+ try {
+ this.result._complete$1(this.computation.call$0());
+ } catch (exception) {
+ e = H.unwrapException(exception);
+ s = H.getTraceFromException(exception);
+ P._completeWithErrorCallback(this.result, e, s);
+ }
+ },
+ $signature: 0
+ };
+ P.Future_Future$microtask_closure.prototype = {
+ call$0: function() {
+ var e, s, exception;
+ try {
+ this.result._complete$1(this.computation.call$0());
+ } catch (exception) {
+ e = H.unwrapException(exception);
+ s = H.getTraceFromException(exception);
+ P._completeWithErrorCallback(this.result, e, s);
+ }
+ },
+ $signature: 0
+ };
+ P.Future_wait_handleError.prototype = {
+ call$2: function(theError, theStackTrace) {
+ var t1, t2, _this = this;
+ type$.StackTrace._check(theStackTrace);
+ t1 = _this._box_0;
+ t2 = --t1.remaining;
+ if (t1.values != null) {
+ t1.values = null;
+ if (t1.remaining === 0 || _this.eagerError)
+ _this.result._completeError$2(theError, theStackTrace);
+ else {
+ t1.error = theError;
+ t1.stackTrace = theStackTrace;
+ }
+ } else if (t2 === 0 && !_this.eagerError)
+ _this.result._completeError$2(t1.error, t1.stackTrace);
+ },
+ $signature: 5
+ };
+ P.Future_wait_closure.prototype = {
+ call$1: function(value) {
+ var t1, t2, _this = this;
+ _this.T._check(value);
+ t1 = _this._box_0;
+ --t1.remaining;
+ t2 = t1.values;
+ if (t2 != null) {
+ C.JSArray_methods.$indexSet(t2, _this.pos, value);
+ if (t1.remaining === 0)
+ _this.result._completeWithValue$1(t1.values);
+ } else if (t1.remaining === 0 && !_this.eagerError)
+ _this.result._completeError$2(t1.error, t1.stackTrace);
+ },
+ $signature: function() {
+ return this.T._eval$1("Null(0)");
+ }
+ };
+ P.Future_forEach_closure.prototype = {
+ call$0: function() {
+ var result,
+ t1 = this.iterator;
+ if (!t1.moveNext$0())
+ return false;
+ result = this.action.call$1(t1._current);
+ if (type$.Future_dynamic._is(result))
+ return result.then$1$1(P.async_Future__kTrue$closure(), type$.bool);
+ return true;
+ },
+ $signature: 81
+ };
+ P.Future_doWhile_closure.prototype = {
+ call$1: function(keepGoing) {
+ var result, error, stackTrace, t1, t2, exception, error0, stackTrace0, replacement, _this = this;
+ H._checkBoolNullable(keepGoing);
+ for (t1 = type$.Future_bool, t2 = _this.action; H.boolConversionCheck(keepGoing);) {
+ result = null;
+ try {
+ result = t2.call$0();
+ } catch (exception) {
+ error = H.unwrapException(exception);
+ stackTrace = H.getTraceFromException(exception);
+ error0 = error;
+ stackTrace0 = stackTrace;
+ replacement = $.Zone__current.errorCallback$2(error0, stackTrace0);
+ if (replacement != null) {
+ error = replacement.error;
+ if (error == null)
+ error = new P.NullThrownError();
+ stackTrace = replacement.stackTrace;
+ } else {
+ stackTrace = stackTrace0;
+ error = error0;
+ }
+ _this.doneSignal._asyncCompleteError$2(error, stackTrace);
+ return;
+ }
+ if (t1._is(result)) {
+ result.then$1$2$onError(_this._box_0.nextIteration, _this.doneSignal.get$_completeError(), type$.void);
+ return;
+ }
+ keepGoing = H._checkBoolNullable(result);
+ }
+ _this.doneSignal._complete$1(null);
+ },
+ $signature: 43
+ };
+ P.TimeoutException.prototype = {
+ toString$0: function(_) {
+ var t1 = this.duration,
+ result = (t1 != null ? "TimeoutException after " + t1.toString$0(0) : "TimeoutException") + ": " + this.message;
+ return result;
+ },
+ $isException: 1,
+ get$message: function(receiver) {
+ return this.message;
+ }
+ };
+ P.Completer.prototype = {};
+ P._Completer.prototype = {
+ completeError$2: function(error, stackTrace) {
+ var replacement;
+ if (error == null)
+ error = new P.NullThrownError();
+ if (this.future._state !== 0)
+ throw H.wrapException(P.StateError$("Future already completed"));
+ replacement = $.Zone__current.errorCallback$2(error, stackTrace);
+ if (replacement != null) {
+ error = replacement.error;
+ if (error == null)
+ error = new P.NullThrownError();
+ stackTrace = replacement.stackTrace;
+ }
+ this._completeError$2(error, stackTrace);
+ },
+ completeError$1: function(error) {
+ return this.completeError$2(error, null);
+ },
+ $isCompleter: 1
+ };
+ P._AsyncCompleter.prototype = {
+ complete$1: function(value) {
+ var t1;
+ this.$ti._eval$1("1/")._check(value);
+ t1 = this.future;
+ if (t1._state !== 0)
+ throw H.wrapException(P.StateError$("Future already completed"));
+ t1._asyncComplete$1(value);
+ },
+ complete$0: function() {
+ return this.complete$1(null);
+ },
+ _completeError$2: function(error, stackTrace) {
+ this.future._asyncCompleteError$2(error, stackTrace);
+ }
+ };
+ P._SyncCompleter.prototype = {
+ complete$1: function(value) {
+ var t1;
+ this.$ti._eval$1("1/")._check(value);
+ t1 = this.future;
+ if (t1._state !== 0)
+ throw H.wrapException(P.StateError$("Future already completed"));
+ t1._complete$1(value);
+ },
+ complete$0: function() {
+ return this.complete$1(null);
+ },
+ _completeError$2: function(error, stackTrace) {
+ this.future._completeError$2(error, stackTrace);
+ }
+ };
+ P._FutureListener.prototype = {
+ matchesErrorTest$1: function(asyncError) {
+ if ((this.state & 15) !== 6)
+ return true;
+ return this.result._zone.runUnary$2$2(type$.bool_Function_Object._check(this.callback), asyncError.error, type$.bool, type$.Object);
+ },
+ handleError$1: function(asyncError) {
+ var errorCallback, t2, t3, t4, _this = this,
+ t1 = (_this.state & 2) !== 0;
+ if (t1) {
+ H.assertHelper(t1);
+ t1 = _this.errorCallback != null;
+ } else
+ t1 = false;
+ H.assertHelper(t1);
+ errorCallback = _this.errorCallback;
+ t1 = type$.dynamic;
+ t2 = type$.Object;
+ t3 = _this.$ti._eval$1("2/");
+ t4 = _this.result._zone;
+ if (type$.dynamic_Function_Object_StackTrace._is(errorCallback))
+ return t3._check(t4.runBinary$3$3(errorCallback, asyncError.error, asyncError.stackTrace, t1, t2, type$.StackTrace));
+ else {
+ H.assertHelper(type$.dynamic_Function_Object._is(errorCallback));
+ return t3._check(t4.runUnary$2$2(errorCallback, asyncError.error, t1, t2));
+ }
+ }
+ };
+ P._Future.prototype = {
+ then$1$2$onError: function(f, onError, $R) {
+ var currentZone, result, t2,
+ t1 = this.$ti;
+ t1._bind$1($R)._eval$1("1/(2)")._check(f);
+ currentZone = $.Zone__current;
+ if (currentZone !== C.C__RootZone) {
+ f = currentZone.registerUnaryCallback$2$1(f, $R._eval$1("0/"), t1._precomputed1);
+ if (onError != null)
+ onError = P._registerErrorHandler(onError, currentZone);
+ }
+ result = new P._Future($.Zone__current, $R._eval$1("_Future<0>"));
+ t2 = onError == null ? 1 : 3;
+ this._addListener$1(new P._FutureListener(result, t2, f, onError, t1._eval$1("@<1>")._bind$1($R)._eval$1("_FutureListener<1,2>")));
+ return result;
+ },
+ then$1$1: function(f, $R) {
+ return this.then$1$2$onError(f, null, $R);
+ },
+ _thenAwait$1$2: function(f, onError, $E) {
+ var result,
+ t1 = this.$ti;
+ t1._bind$1($E)._eval$1("1/(2)")._check(f);
+ result = new P._Future($.Zone__current, $E._eval$1("_Future<0>"));
+ this._addListener$1(new P._FutureListener(result, (onError == null ? 1 : 3) | 16, f, onError, t1._eval$1("@<1>")._bind$1($E)._eval$1("_FutureListener<1,2>")));
+ return result;
+ },
+ catchError$1: function(onError) {
+ var t1, t2, result;
+ type$.bool_Function_dynamic._check(null);
+ t1 = this.$ti;
+ t2 = $.Zone__current;
+ result = new P._Future(t2, t1);
+ if (t2 !== C.C__RootZone)
+ onError = P._registerErrorHandler(onError, t2);
+ this._addListener$1(new P._FutureListener(result, 2, null, onError, t1._eval$1("@<1>")._bind$1(t1._precomputed1)._eval$1("_FutureListener<1,2>")));
+ return result;
+ },
+ whenComplete$1: function(action) {
+ var t1, t2, result;
+ type$.dynamic_Function._check(action);
+ t1 = this.$ti;
+ t2 = $.Zone__current;
+ result = new P._Future(t2, t1);
+ if (t2 !== C.C__RootZone)
+ action = t2.registerCallback$1$1(action, type$.dynamic);
+ this._addListener$1(new P._FutureListener(result, 8, action, null, t1._eval$1("@<1>")._bind$1(t1._precomputed1)._eval$1("_FutureListener<1,2>")));
+ return result;
+ },
+ _cloneResult$1: function(source) {
+ H.assertHelper(this._state < 4);
+ H.assertHelper(source._state >= 4);
+ this._state = source._state;
+ this._resultOrListeners = source._resultOrListeners;
+ },
+ _addListener$1: function(listener) {
+ var t1, source, _this = this;
+ H.assertHelper(listener._nextListener == null);
+ t1 = _this._state;
+ if (t1 <= 1) {
+ listener._nextListener = type$._FutureListener_dynamic_dynamic._check(_this._resultOrListeners);
+ _this._resultOrListeners = listener;
+ } else {
+ if (t1 === 2) {
+ source = type$._Future_dynamic._check(_this._resultOrListeners);
+ if (source._state < 4) {
+ source._addListener$1(listener);
+ return;
+ }
+ _this._cloneResult$1(source);
+ }
+ H.assertHelper(_this._state >= 4);
+ _this._zone.scheduleMicrotask$1(new P._Future__addListener_closure(_this, listener));
+ }
+ },
+ _prependListeners$1: function(listeners) {
+ var t1, existingListeners, cursor, cursor0, source, _this = this, _box_0 = {};
+ _box_0.listeners = listeners;
+ if (listeners == null)
+ return;
+ t1 = _this._state;
+ if (t1 <= 1) {
+ existingListeners = type$._FutureListener_dynamic_dynamic._check(_this._resultOrListeners);
+ cursor = _this._resultOrListeners = listeners;
+ if (existingListeners != null) {
+ for (; cursor0 = cursor._nextListener, cursor0 != null; cursor = cursor0)
+ ;
+ cursor._nextListener = existingListeners;
+ }
+ } else {
+ if (t1 === 2) {
+ source = type$._Future_dynamic._check(_this._resultOrListeners);
+ if (source._state < 4) {
+ source._prependListeners$1(listeners);
+ return;
+ }
+ _this._cloneResult$1(source);
+ }
+ H.assertHelper(_this._state >= 4);
+ _box_0.listeners = _this._reverseListeners$1(listeners);
+ _this._zone.scheduleMicrotask$1(new P._Future__prependListeners_closure(_box_0, _this));
+ }
+ },
+ _removeListeners$0: function() {
+ var current, _this = this;
+ H.assertHelper(_this._state < 4);
+ current = type$._FutureListener_dynamic_dynamic._check(_this._resultOrListeners);
+ _this._resultOrListeners = null;
+ return _this._reverseListeners$1(current);
+ },
+ _reverseListeners$1: function(listeners) {
+ var current, prev, next;
+ for (current = listeners, prev = null; current != null; prev = current, current = next) {
+ next = current._nextListener;
+ current._nextListener = prev;
+ }
+ return prev;
+ },
+ _complete$1: function(value) {
+ var listeners, _this = this,
+ t1 = _this.$ti;
+ t1._eval$1("1/")._check(value);
+ H.assertHelper(_this._state < 4);
+ if (t1._eval$1("Future<1>")._is(value))
+ if (t1._is(value))
+ P._Future__chainCoreFuture(value, _this);
+ else
+ P._Future__chainForeignFuture(value, _this);
+ else {
+ listeners = _this._removeListeners$0();
+ t1._precomputed1._check(value);
+ H.assertHelper(_this._state < 4);
+ _this._state = 4;
+ _this._resultOrListeners = value;
+ P._Future__propagateToListeners(_this, listeners);
+ }
+ },
+ _completeWithValue$1: function(value) {
+ var listeners, _this = this,
+ t1 = _this.$ti;
+ t1._precomputed1._check(value);
+ H.assertHelper(_this._state < 4);
+ H.assertHelper(!t1._eval$1("Future<1>")._is(value));
+ listeners = _this._removeListeners$0();
+ H.assertHelper(_this._state < 4);
+ _this._state = 4;
+ _this._resultOrListeners = value;
+ P._Future__propagateToListeners(_this, listeners);
+ },
+ _completeError$2: function(error, stackTrace) {
+ var listeners, _this = this;
+ type$.StackTrace._check(stackTrace);
+ H.assertHelper(_this._state < 4);
+ listeners = _this._removeListeners$0();
+ H.assertHelper(_this._state < 4);
+ _this._state = 8;
+ _this._resultOrListeners = new P.AsyncError(error, stackTrace);
+ P._Future__propagateToListeners(_this, listeners);
+ },
+ _completeError$1: function(error) {
+ return this._completeError$2(error, null);
+ },
+ _asyncComplete$1: function(value) {
+ var _this = this,
+ t1 = _this.$ti;
+ t1._eval$1("1/")._check(value);
+ H.assertHelper(_this._state < 4);
+ if (t1._eval$1("Future<1>")._is(value)) {
+ _this._chainFuture$1(value);
+ return;
+ }
+ H.assertHelper(_this._state === 0);
+ _this._state = 1;
+ _this._zone.scheduleMicrotask$1(new P._Future__asyncComplete_closure(_this, value));
+ },
+ _chainFuture$1: function(value) {
+ var _this = this,
+ t1 = _this.$ti;
+ t1._eval$1("Future<1>")._check(value);
+ if (t1._is(value)) {
+ if (value._state === 8) {
+ H.assertHelper(_this._state === 0);
+ _this._state = 1;
+ _this._zone.scheduleMicrotask$1(new P._Future__chainFuture_closure(_this, value));
+ } else
+ P._Future__chainCoreFuture(value, _this);
+ return;
+ }
+ P._Future__chainForeignFuture(value, _this);
+ },
+ _asyncCompleteError$2: function(error, stackTrace) {
+ var _this = this;
+ type$.StackTrace._check(stackTrace);
+ H.assertHelper(_this._state < 4);
+ H.assertHelper(_this._state === 0);
+ _this._state = 1;
+ _this._zone.scheduleMicrotask$1(new P._Future__asyncCompleteError_closure(_this, error, stackTrace));
+ },
+ $isFuture: 1
+ };
+ P._Future__addListener_closure.prototype = {
+ call$0: function() {
+ P._Future__propagateToListeners(this.$this, this.listener);
+ },
+ $signature: 0
+ };
+ P._Future__prependListeners_closure.prototype = {
+ call$0: function() {
+ P._Future__propagateToListeners(this.$this, this._box_0.listeners);
+ },
+ $signature: 0
+ };
+ P._Future__chainForeignFuture_closure.prototype = {
+ call$1: function(value) {
+ var t1 = this.target;
+ H.assertHelper(t1._state === 1);
+ H.assertHelper(t1._state === 1);
+ t1._state = 0;
+ t1._complete$1(value);
+ },
+ $signature: 3
+ };
+ P._Future__chainForeignFuture_closure0.prototype = {
+ call$2: function(error, stackTrace) {
+ var t1;
+ type$.StackTrace._check(stackTrace);
+ t1 = this.target;
+ H.assertHelper(t1._state === 1);
+ t1._completeError$2(error, stackTrace);
+ },
+ call$1: function(error) {
+ return this.call$2(error, null);
+ },
+ $signature: 80
+ };
+ P._Future__chainForeignFuture_closure1.prototype = {
+ call$0: function() {
+ this.target._completeError$2(this.e, this.s);
+ },
+ $signature: 0
+ };
+ P._Future__asyncComplete_closure.prototype = {
+ call$0: function() {
+ var t1 = this.$this;
+ t1._completeWithValue$1(t1.$ti._precomputed1._check(this.value));
+ },
+ $signature: 0
+ };
+ P._Future__chainFuture_closure.prototype = {
+ call$0: function() {
+ P._Future__chainCoreFuture(this.value, this.$this);
+ },
+ $signature: 0
+ };
+ P._Future__asyncCompleteError_closure.prototype = {
+ call$0: function() {
+ this.$this._completeError$2(this.error, this.stackTrace);
+ },
+ $signature: 0
+ };
+ P._Future__propagateToListeners_handleWhenCompleteCallback.prototype = {
+ call$0: function() {
+ var completeResult, e, s, t3, exception, originalSource, _this = this,
+ t1 = _this.listener,
+ t2 = t1.state;
+ H.assertHelper((t2 & 1) === 0);
+ t3 = (t2 & 2) === 0;
+ H.assertHelper(t3);
+ completeResult = null;
+ try {
+ H.assertHelper(t3);
+ H.assertHelper((t2 & 15) === 8);
+ completeResult = t1.result._zone.run$1$1(type$.dynamic_Function._check(t1.callback), type$.dynamic);
+ } catch (exception) {
+ e = H.unwrapException(exception);
+ s = H.getTraceFromException(exception);
+ if (_this.hasError) {
+ t1 = _this._box_1.source;
+ H.assertHelper(t1._state === 8);
+ t1 = type$.AsyncError._check(t1._resultOrListeners).error;
+ t2 = e;
+ t2 = t1 == null ? t2 == null : t1 === t2;
+ t1 = t2;
+ } else
+ t1 = false;
+ t2 = _this._box_0;
+ if (t1) {
+ t1 = _this._box_1.source;
+ H.assertHelper(t1._state === 8);
+ t2.listenerValueOrError = type$.AsyncError._check(t1._resultOrListeners);
+ } else
+ t2.listenerValueOrError = new P.AsyncError(e, s);
+ t2.listenerHasError = true;
+ return;
+ }
+ if (type$.Future_dynamic._is(completeResult)) {
+ if (completeResult instanceof P._Future && completeResult._state >= 4) {
+ if (completeResult._state === 8) {
+ t1 = completeResult;
+ H.assertHelper(t1._state === 8);
+ t2 = _this._box_0;
+ t2.listenerValueOrError = type$.AsyncError._check(t1._resultOrListeners);
+ t2.listenerHasError = true;
+ }
+ return;
+ }
+ originalSource = _this._box_1.source;
+ t1 = _this._box_0;
+ t1.listenerValueOrError = completeResult.then$1$1(new P._Future__propagateToListeners_handleWhenCompleteCallback_closure(originalSource), type$.dynamic);
+ t1.listenerHasError = false;
+ }
+ },
+ $signature: 1
+ };
+ P._Future__propagateToListeners_handleWhenCompleteCallback_closure.prototype = {
+ call$1: function(_) {
+ return this.originalSource;
+ },
+ $signature: 75
+ };
+ P._Future__propagateToListeners_handleValueCallback.prototype = {
+ call$0: function() {
+ var e, s, t1, t2, t3, t4, exception, _this = this;
+ try {
+ t1 = _this.listener;
+ t2 = t1.$ti;
+ t3 = t2._precomputed1;
+ t4 = t3._check(_this.sourceResult);
+ H.assertHelper((t1.state & 1) !== 0);
+ _this._box_0.listenerValueOrError = t1.result._zone.runUnary$2$2(t2._eval$1("2/(1)")._check(t1.callback), t4, t2._eval$1("2/"), t3);
+ } catch (exception) {
+ e = H.unwrapException(exception);
+ s = H.getTraceFromException(exception);
+ t1 = _this._box_0;
+ t1.listenerValueOrError = new P.AsyncError(e, s);
+ t1.listenerHasError = true;
+ }
+ },
+ $signature: 1
+ };
+ P._Future__propagateToListeners_handleError.prototype = {
+ call$0: function() {
+ var asyncError, e, s, t1, t2, exception, t3, t4, t5, _this = this;
+ try {
+ t1 = _this._box_1.source;
+ H.assertHelper(t1._state === 8);
+ asyncError = type$.AsyncError._check(t1._resultOrListeners);
+ t1 = _this.listener;
+ if (H.boolConversionCheck(t1.matchesErrorTest$1(asyncError))) {
+ H.assertHelper((t1.state & 2) !== 0);
+ t2 = t1.errorCallback != null;
+ } else
+ t2 = false;
+ if (t2) {
+ t2 = _this._box_0;
+ t2.listenerValueOrError = t1.handleError$1(asyncError);
+ t2.listenerHasError = false;
+ }
+ } catch (exception) {
+ e = H.unwrapException(exception);
+ s = H.getTraceFromException(exception);
+ t1 = _this._box_1;
+ t2 = t1.source;
+ H.assertHelper(t2._state === 8);
+ t3 = type$.AsyncError;
+ t2 = t3._check(t2._resultOrListeners).error;
+ t4 = e;
+ t5 = _this._box_0;
+ if (t2 == null ? t4 == null : t2 === t4) {
+ t1 = t1.source;
+ H.assertHelper(t1._state === 8);
+ t5.listenerValueOrError = t3._check(t1._resultOrListeners);
+ } else
+ t5.listenerValueOrError = new P.AsyncError(e, s);
+ t5.listenerHasError = true;
+ }
+ },
+ $signature: 1
+ };
+ P._AsyncCallbackEntry.prototype = {};
+ P.Stream.prototype = {
+ get$isBroadcast: function() {
+ return false;
+ },
+ pipe$1: function(streamConsumer) {
+ H._instanceType(this)._eval$1("StreamConsumer<Stream.T>")._check(streamConsumer);
+ return streamConsumer.addStream$1(this).then$1$1(new P.Stream_pipe_closure(streamConsumer), type$.dynamic);
+ },
+ get$length: function(_) {
+ var t1 = {},
+ future = new P._Future($.Zone__current, type$._Future_int);
+ t1.count = 0;
+ this.listen$4$cancelOnError$onDone$onError(new P.Stream_length_closure(t1, this), true, new P.Stream_length_closure0(t1, future), future.get$_completeError());
+ return future;
+ },
+ get$first: function(_) {
+ var t1 = {},
+ future = new P._Future($.Zone__current, H._instanceType(this)._eval$1("_Future<Stream.T>"));
+ t1.subscription = null;
+ t1.subscription = this.listen$4$cancelOnError$onDone$onError(new P.Stream_first_closure(t1, this, future), true, new P.Stream_first_closure0(future), future.get$_completeError());
+ return future;
+ },
+ get$last: function(_) {
+ var t1 = {},
+ future = new P._Future($.Zone__current, H._instanceType(this)._eval$1("_Future<Stream.T>"));
+ t1.result = null;
+ t1.foundResult = false;
+ this.listen$4$cancelOnError$onDone$onError(new P.Stream_last_closure(t1, this), true, new P.Stream_last_closure0(t1, future), future.get$_completeError());
+ return future;
+ },
+ firstWhere$1: function(_, test) {
+ var future, _this = this, t1 = {},
+ t2 = H._instanceType(_this);
+ t2._eval$1("bool(Stream.T)")._check(test);
+ t2._eval$1("Stream.T()")._check(null);
+ future = new P._Future($.Zone__current, t2._eval$1("_Future<Stream.T>"));
+ t1.subscription = null;
+ t1.subscription = _this.listen$4$cancelOnError$onDone$onError(new P.Stream_firstWhere_closure(t1, _this, test, future), true, new P.Stream_firstWhere_closure0(_this, null, future), future.get$_completeError());
+ return future;
+ }
+ };
+ P.Stream_Stream$fromFuture_closure.prototype = {
+ call$1: function(value) {
+ var t1 = this.controller;
+ t1._add$1(this.T._check(value));
+ t1._closeUnchecked$0();
+ },
+ $signature: function() {
+ return this.T._eval$1("Null(0)");
+ }
+ };
+ P.Stream_Stream$fromFuture_closure0.prototype = {
+ call$2: function(error, stackTrace) {
+ var t1 = this.controller;
+ t1._async$_addError$2(error, type$.StackTrace._check(stackTrace));
+ t1._closeUnchecked$0();
+ },
+ $signature: 6
+ };
+ P.Stream_Stream$fromIterable_closure.prototype = {
+ call$0: function() {
+ var t1 = this.elements;
+ return new P._IterablePendingEvents(new J.ArrayIterator(t1, 0, H._arrayInstanceType(t1)._eval$1("ArrayIterator<1>")), this.T._eval$1("_IterablePendingEvents<0>"));
+ },
+ $signature: function() {
+ return this.T._eval$1("_IterablePendingEvents<0>()");
+ }
+ };
+ P.Stream_pipe_closure.prototype = {
+ call$1: function(_) {
+ return this.streamConsumer.close$0(0);
+ },
+ $signature: 73
+ };
+ P.Stream_length_closure.prototype = {
+ call$1: function(_) {
+ H._instanceType(this.$this)._eval$1("Stream.T")._check(_);
+ ++this._box_0.count;
+ },
+ $signature: function() {
+ return H._instanceType(this.$this)._eval$1("Null(Stream.T)");
+ }
+ };
+ P.Stream_length_closure0.prototype = {
+ call$0: function() {
+ this.future._complete$1(this._box_0.count);
+ },
+ $signature: 0
+ };
+ P.Stream_first_closure.prototype = {
+ call$1: function(value) {
+ H._instanceType(this.$this)._eval$1("Stream.T")._check(value);
+ P._cancelAndValue(this._box_0.subscription, this.future, value);
+ },
+ $signature: function() {
+ return H._instanceType(this.$this)._eval$1("Null(Stream.T)");
+ }
+ };
+ P.Stream_first_closure0.prototype = {
+ call$0: function() {
+ var e, s, t1, exception;
+ try {
+ t1 = H.IterableElementError_noElement();
+ throw H.wrapException(t1);
+ } catch (exception) {
+ e = H.unwrapException(exception);
+ s = H.getTraceFromException(exception);
+ P._completeWithErrorCallback(this.future, e, s);
+ }
+ },
+ $signature: 0
+ };
+ P.Stream_last_closure.prototype = {
+ call$1: function(value) {
+ var t1;
+ H._instanceType(this.$this)._eval$1("Stream.T")._check(value);
+ t1 = this._box_0;
+ t1.foundResult = true;
+ t1.result = value;
+ },
+ $signature: function() {
+ return H._instanceType(this.$this)._eval$1("Null(Stream.T)");
+ }
+ };
+ P.Stream_last_closure0.prototype = {
+ call$0: function() {
+ var e, s, exception,
+ t1 = this._box_0;
+ if (t1.foundResult) {
+ this.future._complete$1(t1.result);
+ return;
+ }
+ try {
+ t1 = H.IterableElementError_noElement();
+ throw H.wrapException(t1);
+ } catch (exception) {
+ e = H.unwrapException(exception);
+ s = H.getTraceFromException(exception);
+ P._completeWithErrorCallback(this.future, e, s);
+ }
+ },
+ $signature: 0
+ };
+ P.Stream_firstWhere_closure.prototype = {
+ call$1: function(value) {
+ var t1, t2, _this = this;
+ H._instanceType(_this.$this)._eval$1("Stream.T")._check(value);
+ t1 = _this._box_0;
+ t2 = _this.future;
+ P._runUserCode(new P.Stream_firstWhere__closure(_this.test, value), new P.Stream_firstWhere__closure0(t1, t2, value), P._cancelAndErrorClosure(t1.subscription, t2), type$.bool);
+ },
+ $signature: function() {
+ return H._instanceType(this.$this)._eval$1("Null(Stream.T)");
+ }
+ };
+ P.Stream_firstWhere__closure.prototype = {
+ call$0: function() {
+ return this.test.call$1(this.value);
+ },
+ $signature: 49
+ };
+ P.Stream_firstWhere__closure0.prototype = {
+ call$1: function(isMatch) {
+ if (H.boolConversionCheck(H._checkBoolNullable(isMatch)))
+ P._cancelAndValue(this._box_0.subscription, this.future, this.value);
+ },
+ $signature: 43
+ };
+ P.Stream_firstWhere_closure0.prototype = {
+ call$0: function() {
+ var e, s, t1, exception;
+ try {
+ t1 = H.IterableElementError_noElement();
+ throw H.wrapException(t1);
+ } catch (exception) {
+ e = H.unwrapException(exception);
+ s = H.getTraceFromException(exception);
+ P._completeWithErrorCallback(this.future, e, s);
+ }
+ },
+ $signature: 0
+ };
+ P.StreamSubscription.prototype = {};
+ P.StreamTransformerBase.prototype = {$isStreamTransformer: 1};
+ P._StreamController.prototype = {
+ get$_pendingEvents: function() {
+ var t1, _this = this;
+ H.assertHelper((_this._state & 3) === 0);
+ if ((_this._state & 8) === 0)
+ return H._instanceType(_this)._eval$1("_PendingEvents<1>")._check(_this._varData);
+ t1 = H._instanceType(_this);
+ return t1._eval$1("_PendingEvents<1>")._check(t1._eval$1("_StreamControllerAddStreamState<1>")._check(_this._varData).varData);
+ },
+ _ensurePendingEvents$0: function() {
+ var t1, state, t2, _this = this;
+ H.assertHelper((_this._state & 3) === 0);
+ if ((_this._state & 8) === 0) {
+ t1 = _this._varData;
+ if (t1 == null)
+ t1 = _this._varData = new P._StreamImplEvents(H._instanceType(_this)._eval$1("_StreamImplEvents<1>"));
+ return H._instanceType(_this)._eval$1("_StreamImplEvents<1>")._check(t1);
+ }
+ t1 = H._instanceType(_this);
+ state = t1._eval$1("_StreamControllerAddStreamState<1>")._check(_this._varData);
+ t2 = state.varData;
+ if (t2 == null)
+ t2 = state.varData = new P._StreamImplEvents(t1._eval$1("_StreamImplEvents<1>"));
+ return t1._eval$1("_StreamImplEvents<1>")._check(t2);
+ },
+ get$_async$_subscription: function() {
+ var t1, _this = this;
+ H.assertHelper((_this._state & 1) !== 0);
+ if ((_this._state & 8) !== 0) {
+ t1 = H._instanceType(_this);
+ return t1._eval$1("_ControllerSubscription<1>")._check(t1._eval$1("_StreamControllerAddStreamState<1>")._check(_this._varData).varData);
+ }
+ return H._instanceType(_this)._eval$1("_ControllerSubscription<1>")._check(_this._varData);
+ },
+ _badEventState$0: function() {
+ var t1 = this._state;
+ if ((t1 & 4) !== 0)
+ return new P.StateError("Cannot add event after closing");
+ H.assertHelper((t1 & 8) !== 0);
+ return new P.StateError("Cannot add event while adding a stream");
+ },
+ addStream$2$cancelOnError: function(source, cancelOnError) {
+ var t2, t3, t4, t5, _this = this,
+ t1 = H._instanceType(_this);
+ t1._eval$1("Stream<1>")._check(source);
+ t2 = _this._state;
+ if (t2 >= 4)
+ throw H.wrapException(_this._badEventState$0());
+ if ((t2 & 2) !== 0) {
+ t1 = new P._Future($.Zone__current, type$._Future_dynamic);
+ t1._asyncComplete$1(null);
+ return t1;
+ }
+ t2 = _this._varData;
+ t3 = cancelOnError === true;
+ t4 = new P._Future($.Zone__current, type$._Future_dynamic);
+ t5 = t3 ? P._AddStreamState_makeErrorHandler(_this) : _this.get$_async$_addError();
+ t5 = source.listen$4$cancelOnError$onDone$onError(_this.get$_add(), t3, _this.get$_close(), t5);
+ t3 = _this._state;
+ if ((t3 & 1) !== 0 ? (_this.get$_async$_subscription()._state & 4) !== 0 : (t3 & 2) === 0)
+ t5.pause$0();
+ _this._varData = new P._StreamControllerAddStreamState(t2, t4, t5, t1._eval$1("_StreamControllerAddStreamState<1>"));
+ _this._state |= 8;
+ return t4;
+ },
+ _ensureDoneFuture$0: function() {
+ var t1 = this._doneFuture;
+ if (t1 == null)
+ t1 = this._doneFuture = (this._state & 2) !== 0 ? $.$get$Future__nullFuture() : new P._Future($.Zone__current, type$._Future_dynamic);
+ return t1;
+ },
+ add$1: function(_, value) {
+ var _this = this;
+ H._instanceType(_this)._precomputed1._check(value);
+ if (_this._state >= 4)
+ throw H.wrapException(_this._badEventState$0());
+ _this._add$1(value);
+ },
+ addError$2: function(error, stackTrace) {
+ var replacement;
+ type$.StackTrace._check(stackTrace);
+ if (this._state >= 4)
+ throw H.wrapException(this._badEventState$0());
+ if (error == null)
+ error = new P.NullThrownError();
+ replacement = $.Zone__current.errorCallback$2(error, stackTrace);
+ if (replacement != null) {
+ error = replacement.error;
+ if (error == null)
+ error = new P.NullThrownError();
+ stackTrace = replacement.stackTrace;
+ }
+ this._async$_addError$2(error, stackTrace);
+ },
+ addError$1: function(error) {
+ return this.addError$2(error, null);
+ },
+ close$0: function(_) {
+ var _this = this,
+ t1 = _this._state;
+ if ((t1 & 4) !== 0)
+ return _this._ensureDoneFuture$0();
+ if (t1 >= 4)
+ throw H.wrapException(_this._badEventState$0());
+ _this._closeUnchecked$0();
+ return _this._ensureDoneFuture$0();
+ },
+ _closeUnchecked$0: function() {
+ var t1 = this._state |= 4;
+ if ((t1 & 1) !== 0)
+ this._sendDone$0();
+ else if ((t1 & 3) === 0)
+ this._ensurePendingEvents$0().add$1(0, C.C__DelayedDone);
+ },
+ _add$1: function(value) {
+ var t2, _this = this,
+ t1 = H._instanceType(_this);
+ t1._precomputed1._check(value);
+ t2 = _this._state;
+ if ((t2 & 1) !== 0)
+ _this._sendData$1(value);
+ else if ((t2 & 3) === 0)
+ _this._ensurePendingEvents$0().add$1(0, new P._DelayedData(value, t1._eval$1("_DelayedData<1>")));
+ },
+ _async$_addError$2: function(error, stackTrace) {
+ var t1;
+ type$.StackTrace._check(stackTrace);
+ t1 = this._state;
+ if ((t1 & 1) !== 0)
+ this._sendError$2(error, stackTrace);
+ else if ((t1 & 3) === 0)
+ this._ensurePendingEvents$0().add$1(0, new P._DelayedError(error, stackTrace));
+ },
+ _close$0: function() {
+ var addState, _this = this;
+ H.assertHelper((_this._state & 8) !== 0);
+ addState = H._instanceType(_this)._eval$1("_StreamControllerAddStreamState<1>")._check(_this._varData);
+ _this._varData = addState.varData;
+ _this._state &= 4294967287;
+ addState.addStreamFuture._asyncComplete$1(null);
+ },
+ _subscribe$4: function(onData, onError, onDone, cancelOnError) {
+ var t2, t3, subscription, pendingEvents, addState, _this = this,
+ t1 = H._instanceType(_this);
+ t1._eval$1("~(1)")._check(onData);
+ type$.void_Function._check(onDone);
+ if ((_this._state & 3) !== 0)
+ throw H.wrapException(P.StateError$("Stream has already been listened to."));
+ t2 = $.Zone__current;
+ t3 = cancelOnError ? 1 : 0;
+ subscription = new P._ControllerSubscription(_this, t2, t3, t1._eval$1("_ControllerSubscription<1>"));
+ subscription._BufferingStreamSubscription$4(onData, onError, onDone, cancelOnError, t1._precomputed1);
+ pendingEvents = _this.get$_pendingEvents();
+ t3 = _this._state |= 1;
+ if ((t3 & 8) !== 0) {
+ addState = t1._eval$1("_StreamControllerAddStreamState<1>")._check(_this._varData);
+ addState.varData = subscription;
+ addState.addSubscription.resume$0();
+ } else
+ _this._varData = subscription;
+ subscription._setPendingEvents$1(pendingEvents);
+ subscription._guardCallback$1(new P._StreamController__subscribe_closure(_this));
+ return subscription;
+ },
+ _recordCancel$1: function(subscription) {
+ var result, e, s, exception, result0, _this = this,
+ t1 = H._instanceType(_this);
+ t1._eval$1("StreamSubscription<1>")._check(subscription);
+ result = null;
+ if ((_this._state & 8) !== 0)
+ result = t1._eval$1("_StreamControllerAddStreamState<1>")._check(_this._varData).cancel$0();
+ _this._varData = null;
+ _this._state = _this._state & 4294967286 | 2;
+ t1 = _this.onCancel;
+ if (t1 != null)
+ if (result == null)
+ try {
+ result = type$.Future_dynamic._check(t1.call$0());
+ } catch (exception) {
+ e = H.unwrapException(exception);
+ s = H.getTraceFromException(exception);
+ result0 = new P._Future($.Zone__current, type$._Future_dynamic);
+ result0._asyncCompleteError$2(e, s);
+ result = result0;
+ }
+ else
+ result = result.whenComplete$1(t1);
+ t1 = new P._StreamController__recordCancel_complete(_this);
+ if (result != null)
+ result = result.whenComplete$1(t1);
+ else
+ t1.call$0();
+ return result;
+ },
+ _recordPause$1: function(subscription) {
+ var _this = this,
+ t1 = H._instanceType(_this);
+ t1._eval$1("StreamSubscription<1>")._check(subscription);
+ if ((_this._state & 8) !== 0)
+ t1._eval$1("_StreamControllerAddStreamState<1>")._check(_this._varData).addSubscription.pause$0();
+ P._runGuarded(_this.onPause);
+ },
+ _recordResume$1: function(subscription) {
+ var _this = this,
+ t1 = H._instanceType(_this);
+ t1._eval$1("StreamSubscription<1>")._check(subscription);
+ if ((_this._state & 8) !== 0)
+ t1._eval$1("_StreamControllerAddStreamState<1>")._check(_this._varData).addSubscription.resume$0();
+ P._runGuarded(_this.onResume);
+ },
+ $isEventSink: 1,
+ $isStreamConsumer: 1,
+ $isStreamSink: 1,
+ $isStreamController: 1,
+ $is_StreamControllerLifecycle: 1,
+ $is_EventDispatch: 1,
+ $isSink: 1
+ };
+ P._StreamController__subscribe_closure.prototype = {
+ call$0: function() {
+ P._runGuarded(this.$this.onListen);
+ },
+ $signature: 0
+ };
+ P._StreamController__recordCancel_complete.prototype = {
+ call$0: function() {
+ var t1 = this.$this._doneFuture;
+ if (t1 != null && t1._state === 0)
+ t1._asyncComplete$1(null);
+ },
+ $signature: 1
+ };
+ P._SyncStreamControllerDispatch.prototype = {
+ _sendData$1: function(data) {
+ this.$ti._precomputed1._check(data);
+ this.get$_async$_subscription()._add$1(data);
+ },
+ _sendError$2: function(error, stackTrace) {
+ this.get$_async$_subscription()._async$_addError$2(error, stackTrace);
+ },
+ _sendDone$0: function() {
+ this.get$_async$_subscription()._close$0();
+ }
+ };
+ P._AsyncStreamControllerDispatch.prototype = {
+ _sendData$1: function(data) {
+ var t1 = this.$ti;
+ t1._precomputed1._check(data);
+ this.get$_async$_subscription()._addPending$1(new P._DelayedData(data, t1._eval$1("_DelayedData<1>")));
+ },
+ _sendError$2: function(error, stackTrace) {
+ this.get$_async$_subscription()._addPending$1(new P._DelayedError(error, stackTrace));
+ },
+ _sendDone$0: function() {
+ this.get$_async$_subscription()._addPending$1(C.C__DelayedDone);
+ }
+ };
+ P._AsyncStreamController.prototype = {};
+ P._SyncStreamController.prototype = {};
+ P._ControllerStream.prototype = {
+ _createSubscription$4: function(onData, onError, onDone, cancelOnError) {
+ return this._controller._subscribe$4(H._instanceType(this)._eval$1("~(1)")._check(onData), onError, type$.void_Function._check(onDone), cancelOnError);
+ },
+ get$hashCode: function(_) {
+ return (H.Primitives_objectHashCode(this._controller) ^ 892482866) >>> 0;
+ },
+ $eq: function(_, other) {
+ if (other == null)
+ return false;
+ if (this === other)
+ return true;
+ return other instanceof P._ControllerStream && other._controller === this._controller;
+ }
+ };
+ P._ControllerSubscription.prototype = {
+ _onCancel$0: function() {
+ return this._controller._recordCancel$1(this);
+ },
+ _onPause$0: function() {
+ this._controller._recordPause$1(this);
+ },
+ _onResume$0: function() {
+ this._controller._recordResume$1(this);
+ }
+ };
+ P._StreamSinkWrapper.prototype = {
+ add$1: function(_, data) {
+ this._async$_target.add$1(0, this.$ti._precomputed1._check(data));
+ },
+ $isEventSink: 1,
+ $isStreamConsumer: 1,
+ $isStreamSink: 1,
+ $isSink: 1
+ };
+ P._AddStreamState.prototype = {
+ cancel$0: function() {
+ var cancel = this.addSubscription.cancel$0();
+ if (cancel == null) {
+ this.addStreamFuture._asyncComplete$1(null);
+ return null;
+ }
+ return cancel.whenComplete$1(new P._AddStreamState_cancel_closure(this));
+ }
+ };
+ P._AddStreamState_makeErrorHandler_closure.prototype = {
+ call$2: function(e, s) {
+ var t1 = this.controller;
+ t1._async$_addError$2(e, type$.StackTrace._check(s));
+ t1._close$0();
+ },
+ $signature: 5
+ };
+ P._AddStreamState_cancel_closure.prototype = {
+ call$0: function() {
+ this.$this.addStreamFuture._asyncComplete$1(null);
+ },
+ $signature: 0
+ };
+ P._StreamControllerAddStreamState.prototype = {};
+ P._BufferingStreamSubscription.prototype = {
+ _BufferingStreamSubscription$4: function(onData, onError, onDone, cancelOnError, $T) {
+ this.onData$1(onData);
+ this.onError$1(0, onError);
+ this.onDone$1(onDone);
+ },
+ _setPendingEvents$1: function(pendingEvents) {
+ var _this = this;
+ H._instanceType(_this)._eval$1("_PendingEvents<_BufferingStreamSubscription.T>")._check(pendingEvents);
+ H.assertHelper(_this._pending == null);
+ if (pendingEvents == null)
+ return;
+ _this.set$_pending(pendingEvents);
+ if (!pendingEvents.get$isEmpty(pendingEvents)) {
+ _this._state = (_this._state | 64) >>> 0;
+ _this._pending.schedule$1(_this);
+ }
+ },
+ onData$1: function(handleData) {
+ var t1 = H._instanceType(this);
+ t1._eval$1("~(_BufferingStreamSubscription.T)")._check(handleData);
+ if (handleData == null)
+ handleData = P.async___nullDataHandler$closure();
+ this.set$_onData(this._zone.registerUnaryCallback$2$1(handleData, type$.dynamic, t1._eval$1("_BufferingStreamSubscription.T")));
+ },
+ onError$1: function(_, handleError) {
+ var _this = this;
+ if (handleError == null)
+ handleError = P.async___nullErrorHandler$closure();
+ if (type$.void_Function_Object_StackTrace._is(handleError))
+ _this._onError = _this._zone.registerBinaryCallback$3$1(handleError, type$.dynamic, type$.Object, type$.StackTrace);
+ else if (type$.void_Function_Object._is(handleError))
+ _this._onError = _this._zone.registerUnaryCallback$2$1(handleError, type$.dynamic, type$.Object);
+ else
+ throw H.wrapException(P.ArgumentError$("handleError callback must take either an Object (the error), or both an Object (the error) and a StackTrace."));
+ },
+ onDone$1: function(handleDone) {
+ type$.void_Function._check(handleDone);
+ if (handleDone == null)
+ handleDone = P.async___nullDoneHandler$closure();
+ this.set$_onDone(this._zone.registerCallback$1$1(handleDone, type$.void));
+ },
+ pause$1: function(resumeSignal) {
+ var t2, t3, _this = this,
+ t1 = _this._state;
+ if ((t1 & 8) !== 0)
+ return;
+ t2 = (t1 + 128 | 4) >>> 0;
+ _this._state = t2;
+ if (t1 < 128 && _this._pending != null) {
+ t3 = _this._pending;
+ if (t3._state === 1)
+ t3._state = 3;
+ }
+ if ((t1 & 4) === 0 && (t2 & 32) === 0)
+ _this._guardCallback$1(_this.get$_onPause());
+ },
+ pause$0: function() {
+ return this.pause$1(null);
+ },
+ resume$0: function() {
+ var _this = this,
+ t1 = _this._state;
+ if ((t1 & 8) !== 0)
+ return;
+ if (t1 >= 128) {
+ t1 = _this._state -= 128;
+ if (t1 < 128) {
+ if ((t1 & 64) !== 0) {
+ t1 = _this._pending;
+ t1 = !t1.get$isEmpty(t1);
+ } else
+ t1 = false;
+ if (t1)
+ _this._pending.schedule$1(_this);
+ else {
+ H.assertHelper(_this.get$_mayResumeInput());
+ t1 = (_this._state & 4294967291) >>> 0;
+ _this._state = t1;
+ if ((t1 & 32) === 0)
+ _this._guardCallback$1(_this.get$_onResume());
+ }
+ }
+ }
+ },
+ cancel$0: function() {
+ var _this = this,
+ t1 = (_this._state & 4294967279) >>> 0;
+ _this._state = t1;
+ if ((t1 & 8) === 0)
+ _this._cancel$0();
+ t1 = _this._cancelFuture;
+ return t1 == null ? $.$get$Future__nullFuture() : t1;
+ },
+ get$_mayResumeInput: function() {
+ if (this._state < 128) {
+ var t1 = this._pending;
+ t1 = t1 == null || t1.get$isEmpty(t1);
+ } else
+ t1 = false;
+ return t1;
+ },
+ get$isPaused: function() {
+ return this._state >= 128;
+ },
+ _cancel$0: function() {
+ var t2, _this = this,
+ t1 = _this._state = (_this._state | 8) >>> 0;
+ if ((t1 & 64) !== 0) {
+ t2 = _this._pending;
+ if (t2._state === 1)
+ t2._state = 3;
+ }
+ if ((t1 & 32) === 0)
+ _this.set$_pending(null);
+ _this._cancelFuture = _this._onCancel$0();
+ },
+ _add$1: function(data) {
+ var t2, _this = this,
+ t1 = H._instanceType(_this);
+ t1._eval$1("_BufferingStreamSubscription.T")._check(data);
+ H.assertHelper((_this._state & 2) === 0);
+ t2 = _this._state;
+ if ((t2 & 8) !== 0)
+ return;
+ if (t2 < 32)
+ _this._sendData$1(data);
+ else
+ _this._addPending$1(new P._DelayedData(data, t1._eval$1("_DelayedData<_BufferingStreamSubscription.T>")));
+ },
+ _async$_addError$2: function(error, stackTrace) {
+ var t1 = this._state;
+ if ((t1 & 8) !== 0)
+ return;
+ if (t1 < 32)
+ this._sendError$2(error, stackTrace);
+ else
+ this._addPending$1(new P._DelayedError(error, stackTrace));
+ },
+ _close$0: function() {
+ var t1, _this = this;
+ H.assertHelper((_this._state & 2) === 0);
+ t1 = _this._state;
+ if ((t1 & 8) !== 0)
+ return;
+ t1 = (t1 | 2) >>> 0;
+ _this._state = t1;
+ if (t1 < 32)
+ _this._sendDone$0();
+ else
+ _this._addPending$1(C.C__DelayedDone);
+ },
+ _onPause$0: function() {
+ H.assertHelper((this._state & 4) !== 0);
+ },
+ _onResume$0: function() {
+ H.assertHelper((this._state & 4) === 0);
+ },
+ _onCancel$0: function() {
+ H.assertHelper((this._state & 8) !== 0);
+ return null;
+ },
+ _addPending$1: function($event) {
+ var _this = this,
+ t1 = H._instanceType(_this)._eval$1("_StreamImplEvents<_BufferingStreamSubscription.T>"),
+ pending = t1._check(_this._pending);
+ if (pending == null) {
+ pending = new P._StreamImplEvents(t1);
+ _this.set$_pending(pending);
+ }
+ pending.add$1(0, $event);
+ t1 = _this._state;
+ if ((t1 & 64) === 0) {
+ t1 = (t1 | 64) >>> 0;
+ _this._state = t1;
+ if (t1 < 128)
+ _this._pending.schedule$1(_this);
+ }
+ },
+ _sendData$1: function(data) {
+ var t2, _this = this,
+ t1 = H._instanceType(_this)._eval$1("_BufferingStreamSubscription.T");
+ t1._check(data);
+ H.assertHelper((_this._state & 8) === 0);
+ H.assertHelper(_this._state < 128);
+ H.assertHelper((_this._state & 32) === 0);
+ t2 = _this._state;
+ _this._state = (t2 | 32) >>> 0;
+ _this._zone.runUnaryGuarded$1$2(_this._onData, data, t1);
+ _this._state = (_this._state & 4294967263) >>> 0;
+ _this._checkState$1((t2 & 4) !== 0);
+ },
+ _sendError$2: function(error, stackTrace) {
+ var t1, t2, _this = this;
+ type$.StackTrace._check(stackTrace);
+ H.assertHelper((_this._state & 8) === 0);
+ H.assertHelper(_this._state < 128);
+ H.assertHelper((_this._state & 32) === 0);
+ t1 = _this._state;
+ t2 = new P._BufferingStreamSubscription__sendError_sendError(_this, error, stackTrace);
+ if ((t1 & 1) !== 0) {
+ _this._state = (t1 | 16) >>> 0;
+ _this._cancel$0();
+ t1 = _this._cancelFuture;
+ if (t1 != null && t1 !== $.$get$Future__nullFuture())
+ t1.whenComplete$1(t2);
+ else
+ t2.call$0();
+ } else {
+ t2.call$0();
+ _this._checkState$1((t1 & 4) !== 0);
+ }
+ },
+ _sendDone$0: function() {
+ var t1, t2, _this = this;
+ H.assertHelper((_this._state & 8) === 0);
+ H.assertHelper(_this._state < 128);
+ H.assertHelper((_this._state & 32) === 0);
+ t1 = new P._BufferingStreamSubscription__sendDone_sendDone(_this);
+ _this._cancel$0();
+ _this._state = (_this._state | 16) >>> 0;
+ t2 = _this._cancelFuture;
+ if (t2 != null && t2 !== $.$get$Future__nullFuture())
+ t2.whenComplete$1(t1);
+ else
+ t1.call$0();
+ },
+ _guardCallback$1: function(callback) {
+ var t1, _this = this;
+ type$.void_Function._check(callback);
+ H.assertHelper((_this._state & 32) === 0);
+ t1 = _this._state;
+ _this._state = (t1 | 32) >>> 0;
+ callback.call$0();
+ _this._state = (_this._state & 4294967263) >>> 0;
+ _this._checkState$1((t1 & 4) !== 0);
+ },
+ _checkState$1: function(wasInputPaused) {
+ var t1, isInputPaused, _this = this;
+ H.assertHelper((_this._state & 32) === 0);
+ if ((_this._state & 64) !== 0) {
+ t1 = _this._pending;
+ t1 = t1.get$isEmpty(t1);
+ } else
+ t1 = false;
+ if (t1) {
+ t1 = (_this._state & 4294967231) >>> 0;
+ _this._state = t1;
+ if ((t1 & 4) !== 0 && _this.get$_mayResumeInput())
+ _this._state = (_this._state & 4294967291) >>> 0;
+ }
+ for (; true; wasInputPaused = isInputPaused) {
+ t1 = _this._state;
+ if ((t1 & 8) !== 0) {
+ _this.set$_pending(null);
+ return;
+ }
+ isInputPaused = (t1 & 4) !== 0;
+ if (wasInputPaused === isInputPaused)
+ break;
+ _this._state = (t1 ^ 32) >>> 0;
+ if (isInputPaused)
+ _this._onPause$0();
+ else
+ _this._onResume$0();
+ _this._state = (_this._state & 4294967263) >>> 0;
+ }
+ t1 = _this._state;
+ if ((t1 & 64) !== 0 && t1 < 128)
+ _this._pending.schedule$1(_this);
+ },
+ set$_onData: function(_onData) {
+ this._onData = H._instanceType(this)._eval$1("~(_BufferingStreamSubscription.T)")._check(_onData);
+ },
+ set$_onDone: function(_onDone) {
+ this._onDone = type$.void_Function._check(_onDone);
+ },
+ set$_pending: function(_pending) {
+ this._pending = H._instanceType(this)._eval$1("_PendingEvents<_BufferingStreamSubscription.T>")._check(_pending);
+ },
+ $isStreamSubscription: 1,
+ $is_EventDispatch: 1
+ };
+ P._BufferingStreamSubscription__sendError_sendError.prototype = {
+ call$0: function() {
+ var onError, t3, t4, t5,
+ t1 = this.$this,
+ t2 = t1._state;
+ if ((t2 & 8) !== 0 && (t2 & 16) === 0)
+ return;
+ t1._state = (t2 | 32) >>> 0;
+ onError = t1._onError;
+ t2 = this.error;
+ t3 = type$.Object;
+ t4 = t1._zone;
+ if (type$.void_Function_Object_StackTrace._is(onError))
+ t4.runBinaryGuarded$2$3(onError, t2, this.stackTrace, t3, type$.StackTrace);
+ else {
+ t5 = type$.void_Function_Object;
+ H.assertHelper(t5._is(onError));
+ t4.runUnaryGuarded$1$2(t5._check(t1._onError), t2, t3);
+ }
+ t1._state = (t1._state & 4294967263) >>> 0;
+ },
+ $signature: 1
+ };
+ P._BufferingStreamSubscription__sendDone_sendDone.prototype = {
+ call$0: function() {
+ var t1 = this.$this,
+ t2 = t1._state;
+ if ((t2 & 16) === 0)
+ return;
+ t1._state = (t2 | 42) >>> 0;
+ t1._zone.runGuarded$1(t1._onDone);
+ t1._state = (t1._state & 4294967263) >>> 0;
+ },
+ $signature: 1
+ };
+ P._StreamImpl.prototype = {
+ listen$4$cancelOnError$onDone$onError: function(onData, cancelOnError, onDone, onError) {
+ return this._createSubscription$4(H._instanceType(this)._eval$1("~(1)")._check(onData), onError, type$.void_Function._check(onDone), true === H._checkBoolNullable(cancelOnError));
+ },
+ listen$2$onDone: function(onData, onDone) {
+ return this.listen$4$cancelOnError$onDone$onError(onData, null, onDone, null);
+ },
+ listen$3$onDone$onError: function(onData, onDone, onError) {
+ return this.listen$4$cancelOnError$onDone$onError(onData, null, onDone, onError);
+ },
+ listen$1: function(onData) {
+ return this.listen$4$cancelOnError$onDone$onError(onData, null, null, null);
+ },
+ _createSubscription$4: function(onData, onError, onDone, cancelOnError) {
+ var t1 = H._instanceType(this);
+ return P._BufferingStreamSubscription$(t1._eval$1("~(1)")._check(onData), onError, type$.void_Function._check(onDone), cancelOnError, t1._precomputed1);
+ }
+ };
+ P._GeneratedStreamImpl.prototype = {
+ _createSubscription$4: function(onData, onError, onDone, cancelOnError) {
+ var _this = this,
+ t1 = _this.$ti;
+ t1._eval$1("~(1)")._check(onData);
+ type$.void_Function._check(onDone);
+ if (_this._isUsed)
+ throw H.wrapException(P.StateError$("Stream has already been listened to."));
+ _this._isUsed = true;
+ t1 = P._BufferingStreamSubscription$(onData, onError, onDone, cancelOnError, t1._precomputed1);
+ t1._setPendingEvents$1(_this._pending.call$0());
+ return t1;
+ }
+ };
+ P._IterablePendingEvents.prototype = {
+ get$isEmpty: function(_) {
+ return this._async$_iterator == null;
+ },
+ handleNext$1: function(dispatch) {
+ var hasMore, e, s, t1, exception, _this = this;
+ _this.$ti._eval$1("_EventDispatch<1>")._check(dispatch);
+ t1 = _this._async$_iterator;
+ if (t1 == null)
+ throw H.wrapException(P.StateError$("No events pending."));
+ hasMore = null;
+ try {
+ hasMore = t1.moveNext$0();
+ if (H.boolConversionCheck(hasMore))
+ dispatch._sendData$1(_this._async$_iterator.get$current());
+ else {
+ _this.set$_async$_iterator(null);
+ dispatch._sendDone$0();
+ }
+ } catch (exception) {
+ e = H.unwrapException(exception);
+ s = H.getTraceFromException(exception);
+ if (hasMore == null) {
+ _this.set$_async$_iterator(C.C_EmptyIterator);
+ dispatch._sendError$2(e, s);
+ } else
+ dispatch._sendError$2(e, s);
+ }
+ },
+ set$_async$_iterator: function(_iterator) {
+ this._async$_iterator = this.$ti._eval$1("Iterator<1>")._check(_iterator);
+ }
+ };
+ P._DelayedEvent.prototype = {
+ set$next: function(next) {
+ this.next = type$._DelayedEvent_dynamic._check(next);
+ },
+ get$next: function() {
+ return this.next;
+ }
+ };
+ P._DelayedData.prototype = {
+ perform$1: function(dispatch) {
+ this.$ti._eval$1("_EventDispatch<1>")._check(dispatch)._sendData$1(this.value);
+ }
+ };
+ P._DelayedError.prototype = {
+ perform$1: function(dispatch) {
+ dispatch._sendError$2(this.error, this.stackTrace);
+ }
+ };
+ P._DelayedDone.prototype = {
+ perform$1: function(dispatch) {
+ dispatch._sendDone$0();
+ },
+ get$next: function() {
+ return null;
+ },
+ set$next: function(_) {
+ throw H.wrapException(P.StateError$("No events after a done."));
+ },
+ $is_DelayedEvent: 1
+ };
+ P._PendingEvents.prototype = {
+ schedule$1: function(dispatch) {
+ var t1, _this = this;
+ H._instanceType(_this)._eval$1("_EventDispatch<1>")._check(dispatch);
+ if (_this._state === 1)
+ return;
+ H.assertHelper(!_this.get$isEmpty(_this));
+ t1 = _this._state;
+ if (t1 >= 1) {
+ H.assertHelper(t1 === 3);
+ _this._state = 1;
+ return;
+ }
+ P.scheduleMicrotask(new P._PendingEvents_schedule_closure(_this, dispatch));
+ _this._state = 1;
+ }
+ };
+ P._PendingEvents_schedule_closure.prototype = {
+ call$0: function() {
+ var t1 = this.$this,
+ oldState = t1._state;
+ t1._state = 0;
+ if (oldState === 3)
+ return;
+ t1.handleNext$1(this.dispatch);
+ },
+ $signature: 0
+ };
+ P._StreamImplEvents.prototype = {
+ get$isEmpty: function(_) {
+ return this.lastPendingEvent == null;
+ },
+ add$1: function(_, $event) {
+ var _this = this,
+ t1 = _this.lastPendingEvent;
+ if (t1 == null)
+ _this.firstPendingEvent = _this.lastPendingEvent = $event;
+ else {
+ t1.set$next($event);
+ _this.lastPendingEvent = $event;
+ }
+ },
+ handleNext$1: function(dispatch) {
+ var $event, t1, _this = this;
+ _this.$ti._eval$1("_EventDispatch<1>")._check(dispatch);
+ H.assertHelper(_this._state !== 1);
+ $event = _this.firstPendingEvent;
+ t1 = $event.get$next();
+ _this.firstPendingEvent = t1;
+ if (t1 == null)
+ _this.lastPendingEvent = null;
+ $event.perform$1(dispatch);
+ }
+ };
+ P._DoneStreamSubscription.prototype = {
+ get$isPaused: function() {
+ return this._state >= 4;
+ },
+ _schedule$0: function() {
+ var _this = this;
+ if ((_this._state & 2) !== 0)
+ return;
+ _this._zone.scheduleMicrotask$1(_this.get$_sendDone());
+ _this._state = (_this._state | 2) >>> 0;
+ },
+ onData$1: function(handleData) {
+ this.$ti._eval$1("~(1)")._check(handleData);
+ },
+ onError$1: function(_, handleError) {
+ },
+ onDone$1: function(handleDone) {
+ this.set$_onDone(type$.void_Function._check(handleDone));
+ },
+ pause$1: function(resumeSignal) {
+ this._state += 4;
+ },
+ pause$0: function() {
+ return this.pause$1(null);
+ },
+ resume$0: function() {
+ var t1 = this._state;
+ if (t1 >= 4) {
+ t1 = this._state = t1 - 4;
+ if (t1 < 4 && (t1 & 1) === 0)
+ this._schedule$0();
+ }
+ },
+ cancel$0: function() {
+ return $.$get$Future__nullFuture();
+ },
+ _sendDone$0: function() {
+ var _this = this,
+ t1 = _this._state = (_this._state & 4294967293) >>> 0;
+ if (t1 >= 4)
+ return;
+ _this._state = (t1 | 1) >>> 0;
+ t1 = _this._onDone;
+ if (t1 != null)
+ _this._zone.runGuarded$1(t1);
+ },
+ set$_onDone: function(_onDone) {
+ this._onDone = type$.void_Function._check(_onDone);
+ },
+ $isStreamSubscription: 1
+ };
+ P._StreamIterator.prototype = {};
+ P._EmptyStream.prototype = {
+ get$isBroadcast: function() {
+ return true;
+ },
+ listen$4$cancelOnError$onDone$onError: function(onData, cancelOnError, onDone, onError) {
+ var t1 = this.$ti;
+ t1._eval$1("~(1)")._check(onData);
+ type$.void_Function._check(onDone);
+ H._checkBoolNullable(cancelOnError);
+ t1 = new P._DoneStreamSubscription($.Zone__current, onDone, t1._eval$1("_DoneStreamSubscription<1>"));
+ t1._schedule$0();
+ return t1;
+ },
+ listen$3$onDone$onError: function(onData, onDone, onError) {
+ return this.listen$4$cancelOnError$onDone$onError(onData, null, onDone, onError);
+ },
+ listen$1: function(onData) {
+ return this.listen$4$cancelOnError$onDone$onError(onData, null, null, null);
+ }
+ };
+ P._cancelAndError_closure.prototype = {
+ call$0: function() {
+ return this.future._completeError$2(this.error, this.stackTrace);
+ },
+ $signature: 1
+ };
+ P._cancelAndErrorClosure_closure.prototype = {
+ call$2: function(error, stackTrace) {
+ P._cancelAndError(this.subscription, this.future, error, type$.StackTrace._check(stackTrace));
+ },
+ $signature: 5
+ };
+ P._cancelAndValue_closure.prototype = {
+ call$0: function() {
+ return this.future._complete$1(this.value);
+ },
+ $signature: 1
+ };
+ P.Timer.prototype = {};
+ P.AsyncError.prototype = {
+ toString$0: function(_) {
+ return H.S(this.error);
+ },
+ $isError: 1
+ };
+ P._ZoneFunction.prototype = {};
+ P.ZoneSpecification.prototype = {};
+ P._ZoneSpecification.prototype = {$isZoneSpecification: 1};
+ P.ZoneDelegate.prototype = {};
+ P.Zone.prototype = {};
+ P._ZoneDelegate.prototype = {
+ handleUncaughtError$3: function(zone, error, stackTrace) {
+ var implementation, implZone;
+ type$.StackTrace._check(stackTrace);
+ implementation = this._delegationTarget.get$_handleUncaughtError();
+ implZone = implementation.zone;
+ return implementation.$function.call$5(implZone, P._parentDelegate(implZone), zone, error, stackTrace);
+ },
+ registerCallback$1$2: function(zone, f, $R) {
+ var implementation, implZone;
+ $R._eval$1("0()")._check(f);
+ implementation = this._delegationTarget.get$_registerCallback();
+ implZone = implementation.zone;
+ return type$.A_Function_Function_A_4_Zone_and_ZoneDelegate_and_Zone_and_A_Function._check(implementation.$function).call$1$4(implZone, P._parentDelegate(implZone), zone, f, $R);
+ },
+ registerUnaryCallback$2$2: function(zone, f, $R, $T) {
+ var implementation, implZone;
+ $R._eval$1("@<0>")._bind$1($T)._eval$1("1(2)")._check(f);
+ implementation = this._delegationTarget.get$_registerUnaryCallback();
+ implZone = implementation.zone;
+ return type$.A_Function_B_Function_A_and_B_4_Zone_and_ZoneDelegate_and_Zone_and_A_Function_B._check(implementation.$function).call$2$4(implZone, P._parentDelegate(implZone), zone, f, $R, $T);
+ },
+ registerBinaryCallback$3$2: function(zone, f, $R, T1, T2) {
+ var implementation, implZone;
+ $R._eval$1("@<0>")._bind$1(T1)._bind$1(T2)._eval$1("1(2,3)")._check(f);
+ implementation = this._delegationTarget.get$_registerBinaryCallback();
+ implZone = implementation.zone;
+ return type$.A_Function_2_B_and_C_Function_A_and_B_and_C_4_Zone_and_ZoneDelegate_and_Zone_and_A_Function_2_B_and_C._check(implementation.$function).call$3$4(implZone, P._parentDelegate(implZone), zone, f, $R, T1, T2);
+ },
+ errorCallback$3: function(zone, error, stackTrace) {
+ var implementation = this._delegationTarget.get$_errorCallback(),
+ implZone = implementation.zone;
+ if (implZone === C.C__RootZone)
+ return null;
+ return implementation.$function.call$5(implZone, P._parentDelegate(implZone), zone, error, stackTrace);
+ },
+ $isZoneDelegate: 1
+ };
+ P._Zone.prototype = {$isZone: 1};
+ P._CustomZone.prototype = {
+ get$_delegate: function() {
+ var t1 = this._delegateCache;
+ if (t1 != null)
+ return t1;
+ return this._delegateCache = new P._ZoneDelegate(this);
+ },
+ get$errorZone: function() {
+ return this._handleUncaughtError.zone;
+ },
+ runGuarded$1: function(f) {
+ var e, s, exception;
+ type$.void_Function._check(f);
+ try {
+ this.run$1$1(f, type$.void);
+ } catch (exception) {
+ e = H.unwrapException(exception);
+ s = H.getTraceFromException(exception);
+ this.handleUncaughtError$2(e, s);
+ }
+ },
+ runUnaryGuarded$1$2: function(f, arg, $T) {
+ var e, s, exception;
+ $T._eval$1("~(0)")._check(f);
+ $T._check(arg);
+ try {
+ this.runUnary$2$2(f, arg, type$.void, $T);
+ } catch (exception) {
+ e = H.unwrapException(exception);
+ s = H.getTraceFromException(exception);
+ this.handleUncaughtError$2(e, s);
+ }
+ },
+ runBinaryGuarded$2$3: function(f, arg1, arg2, T1, T2) {
+ var e, s, exception;
+ T1._eval$1("@<0>")._bind$1(T2)._eval$1("~(1,2)")._check(f);
+ T1._check(arg1);
+ T2._check(arg2);
+ try {
+ this.runBinary$3$3(f, arg1, arg2, type$.void, T1, T2);
+ } catch (exception) {
+ e = H.unwrapException(exception);
+ s = H.getTraceFromException(exception);
+ this.handleUncaughtError$2(e, s);
+ }
+ },
+ bindCallback$1$1: function(f, $R) {
+ return new P._CustomZone_bindCallback_closure(this, this.registerCallback$1$1($R._eval$1("0()")._check(f), $R), $R);
+ },
+ bindUnaryCallback$2$1: function(f, $R, $T) {
+ return new P._CustomZone_bindUnaryCallback_closure(this, this.registerUnaryCallback$2$1($R._eval$1("@<0>")._bind$1($T)._eval$1("1(2)")._check(f), $R, $T), $T, $R);
+ },
+ bindCallbackGuarded$1: function(f) {
+ return new P._CustomZone_bindCallbackGuarded_closure(this, this.registerCallback$1$1(type$.void_Function._check(f), type$.void));
+ },
+ bindUnaryCallbackGuarded$1$1: function(f, $T) {
+ return new P._CustomZone_bindUnaryCallbackGuarded_closure(this, this.registerUnaryCallback$2$1($T._eval$1("~(0)")._check(f), type$.void, $T), $T);
+ },
+ $index: function(_, key) {
+ var value,
+ t1 = this._async$_map,
+ result = t1.$index(0, key);
+ if (result != null || t1.containsKey$1(key))
+ return result;
+ value = this.parent.$index(0, key);
+ if (value != null)
+ t1.$indexSet(0, key, value);
+ return value;
+ },
+ handleUncaughtError$2: function(error, stackTrace) {
+ var implementation, t1, parentDelegate;
+ type$.StackTrace._check(stackTrace);
+ implementation = this._handleUncaughtError;
+ H.assertHelper(implementation != null);
+ t1 = implementation.zone;
+ parentDelegate = P._parentDelegate(t1);
+ return implementation.$function.call$5(t1, parentDelegate, this, error, stackTrace);
+ },
+ fork$2$specification$zoneValues: function(specification, zoneValues) {
+ var t1, parentDelegate,
+ implementation = this._fork;
+ H.assertHelper(implementation != null);
+ t1 = implementation.zone;
+ parentDelegate = P._parentDelegate(t1);
+ return implementation.$function.call$5(t1, parentDelegate, this, specification, zoneValues);
+ },
+ run$1$1: function(f, $R) {
+ var implementation, t1, parentDelegate;
+ $R._eval$1("0()")._check(f);
+ implementation = this._run;
+ H.assertHelper(implementation != null);
+ t1 = implementation.zone;
+ parentDelegate = P._parentDelegate(t1);
+ return type$.A_Function_A_4_Zone_and_ZoneDelegate_and_Zone_and_A_Function._check(implementation.$function).call$1$4(t1, parentDelegate, this, f, $R);
+ },
+ runUnary$2$2: function(f, arg, $R, $T) {
+ var implementation, t1, parentDelegate;
+ $R._eval$1("@<0>")._bind$1($T)._eval$1("1(2)")._check(f);
+ $T._check(arg);
+ implementation = this._runUnary;
+ H.assertHelper(implementation != null);
+ t1 = implementation.zone;
+ parentDelegate = P._parentDelegate(t1);
+ return type$.A_Function_A_and_B_5_Zone_and_ZoneDelegate_and_Zone_and_A_Function_B_and_B._check(implementation.$function).call$2$5(t1, parentDelegate, this, f, arg, $R, $T);
+ },
+ runBinary$3$3: function(f, arg1, arg2, $R, T1, T2) {
+ var implementation, t1, parentDelegate;
+ $R._eval$1("@<0>")._bind$1(T1)._bind$1(T2)._eval$1("1(2,3)")._check(f);
+ T1._check(arg1);
+ T2._check(arg2);
+ implementation = this._runBinary;
+ H.assertHelper(implementation != null);
+ t1 = implementation.zone;
+ parentDelegate = P._parentDelegate(t1);
+ return type$.A_Function_A_and_B_and_C_6_Zone_and_ZoneDelegate_and_Zone_and_A_Function_2_B_and_C_and_B_and_C._check(implementation.$function).call$3$6(t1, parentDelegate, this, f, arg1, arg2, $R, T1, T2);
+ },
+ registerCallback$1$1: function(callback, $R) {
+ var implementation, t1, parentDelegate;
+ $R._eval$1("0()")._check(callback);
+ implementation = this._registerCallback;
+ H.assertHelper(implementation != null);
+ t1 = implementation.zone;
+ parentDelegate = P._parentDelegate(t1);
+ return type$.A_Function_Function_A_4_Zone_and_ZoneDelegate_and_Zone_and_A_Function._check(implementation.$function).call$1$4(t1, parentDelegate, this, callback, $R);
+ },
+ registerUnaryCallback$2$1: function(callback, $R, $T) {
+ var implementation, t1, parentDelegate;
+ $R._eval$1("@<0>")._bind$1($T)._eval$1("1(2)")._check(callback);
+ implementation = this._registerUnaryCallback;
+ H.assertHelper(implementation != null);
+ t1 = implementation.zone;
+ parentDelegate = P._parentDelegate(t1);
+ return type$.A_Function_B_Function_A_and_B_4_Zone_and_ZoneDelegate_and_Zone_and_A_Function_B._check(implementation.$function).call$2$4(t1, parentDelegate, this, callback, $R, $T);
+ },
+ registerBinaryCallback$3$1: function(callback, $R, T1, T2) {
+ var implementation, t1, parentDelegate;
+ $R._eval$1("@<0>")._bind$1(T1)._bind$1(T2)._eval$1("1(2,3)")._check(callback);
+ implementation = this._registerBinaryCallback;
+ H.assertHelper(implementation != null);
+ t1 = implementation.zone;
+ parentDelegate = P._parentDelegate(t1);
+ return type$.A_Function_2_B_and_C_Function_A_and_B_and_C_4_Zone_and_ZoneDelegate_and_Zone_and_A_Function_2_B_and_C._check(implementation.$function).call$3$4(t1, parentDelegate, this, callback, $R, T1, T2);
+ },
+ errorCallback$2: function(error, stackTrace) {
+ var implementation, implementationZone, parentDelegate;
+ type$.StackTrace._check(stackTrace);
+ implementation = this._errorCallback;
+ H.assertHelper(implementation != null);
+ implementationZone = implementation.zone;
+ if (implementationZone === C.C__RootZone)
+ return null;
+ parentDelegate = P._parentDelegate(implementationZone);
+ return implementation.$function.call$5(implementationZone, parentDelegate, this, error, stackTrace);
+ },
+ scheduleMicrotask$1: function(f) {
+ var implementation, t1, parentDelegate;
+ type$.void_Function._check(f);
+ implementation = this._scheduleMicrotask;
+ H.assertHelper(implementation != null);
+ t1 = implementation.zone;
+ parentDelegate = P._parentDelegate(t1);
+ return implementation.$function.call$4(t1, parentDelegate, this, f);
+ },
+ createTimer$2: function(duration, f) {
+ var implementation, t1, parentDelegate;
+ type$.void_Function._check(f);
+ implementation = this._createTimer;
+ H.assertHelper(implementation != null);
+ t1 = implementation.zone;
+ parentDelegate = P._parentDelegate(t1);
+ return implementation.$function.call$5(t1, parentDelegate, this, duration, f);
+ },
+ print$1: function(_, line) {
+ var t1, parentDelegate,
+ implementation = this._print;
+ H.assertHelper(implementation != null);
+ t1 = implementation.zone;
+ parentDelegate = P._parentDelegate(t1);
+ return implementation.$function.call$4(t1, parentDelegate, this, line);
+ },
+ set$_run: function(_run) {
+ this._run = type$._ZoneFunction_Function._check(_run);
+ },
+ set$_runUnary: function(_runUnary) {
+ this._runUnary = type$._ZoneFunction_Function._check(_runUnary);
+ },
+ set$_runBinary: function(_runBinary) {
+ this._runBinary = type$._ZoneFunction_Function._check(_runBinary);
+ },
+ set$_registerCallback: function(_registerCallback) {
+ this._registerCallback = type$._ZoneFunction_Function._check(_registerCallback);
+ },
+ set$_registerUnaryCallback: function(_registerUnaryCallback) {
+ this._registerUnaryCallback = type$._ZoneFunction_Function._check(_registerUnaryCallback);
+ },
+ set$_registerBinaryCallback: function(_registerBinaryCallback) {
+ this._registerBinaryCallback = type$._ZoneFunction_Function._check(_registerBinaryCallback);
+ },
+ set$_errorCallback: function(_errorCallback) {
+ this._errorCallback = type$._ZoneFunction_of_AsyncError_Function_Zone_ZoneDelegate_Zone_Object_StackTrace._check(_errorCallback);
+ },
+ set$_scheduleMicrotask: function(_scheduleMicrotask) {
+ this._scheduleMicrotask = type$._ZoneFunction_of_void_Function_4_Zone_and_ZoneDelegate_and_Zone_and_void_Function._check(_scheduleMicrotask);
+ },
+ set$_createTimer: function(_createTimer) {
+ this._createTimer = type$._ZoneFunction_of_Timer_Function_5_Zone_and_ZoneDelegate_and_Zone_and_Duration_and_void_Function._check(_createTimer);
+ },
+ set$_createPeriodicTimer: function(_createPeriodicTimer) {
+ this._createPeriodicTimer = type$._ZoneFunction_of_Timer_Function_5_Zone_and_ZoneDelegate_and_Zone_and_Duration_and_void_Function_Timer._check(_createPeriodicTimer);
+ },
+ set$_print: function(_print) {
+ this._print = type$._ZoneFunction_of_void_Function_Zone_ZoneDelegate_Zone_String._check(_print);
+ },
+ set$_fork: function(_fork) {
+ this._fork = type$._ZoneFunction_of_Zone_Function_5_Zone_and_ZoneDelegate_and_Zone_and_ZoneSpecification_and_Map_dynamic_dynamic._check(_fork);
+ },
+ set$_handleUncaughtError: function(_handleUncaughtError) {
+ this._handleUncaughtError = type$._ZoneFunction_of_void_Function_Zone_ZoneDelegate_Zone_Object_StackTrace._check(_handleUncaughtError);
+ },
+ get$_run: function() {
+ return this._run;
+ },
+ get$_runUnary: function() {
+ return this._runUnary;
+ },
+ get$_runBinary: function() {
+ return this._runBinary;
+ },
+ get$_registerCallback: function() {
+ return this._registerCallback;
+ },
+ get$_registerUnaryCallback: function() {
+ return this._registerUnaryCallback;
+ },
+ get$_registerBinaryCallback: function() {
+ return this._registerBinaryCallback;
+ },
+ get$_errorCallback: function() {
+ return this._errorCallback;
+ },
+ get$_scheduleMicrotask: function() {
+ return this._scheduleMicrotask;
+ },
+ get$_createTimer: function() {
+ return this._createTimer;
+ },
+ get$_createPeriodicTimer: function() {
+ return this._createPeriodicTimer;
+ },
+ get$_print: function() {
+ return this._print;
+ },
+ get$_fork: function() {
+ return this._fork;
+ },
+ get$_handleUncaughtError: function() {
+ return this._handleUncaughtError;
+ },
+ get$parent: function(receiver) {
+ return this.parent;
+ },
+ get$_async$_map: function() {
+ return this._async$_map;
+ }
+ };
+ P._CustomZone_bindCallback_closure.prototype = {
+ call$0: function() {
+ return this.$this.run$1$1(this.registered, this.R);
+ },
+ $signature: function() {
+ return this.R._eval$1("0()");
+ }
+ };
+ P._CustomZone_bindUnaryCallback_closure.prototype = {
+ call$1: function(arg) {
+ var _this = this,
+ t1 = _this.T;
+ return _this.$this.runUnary$2$2(_this.registered, t1._check(arg), _this.R, t1);
+ },
+ $signature: function() {
+ return this.R._eval$1("@<0>")._bind$1(this.T)._eval$1("1(2)");
+ }
+ };
+ P._CustomZone_bindCallbackGuarded_closure.prototype = {
+ call$0: function() {
+ return this.$this.runGuarded$1(this.registered);
+ },
+ $signature: 1
+ };
+ P._CustomZone_bindUnaryCallbackGuarded_closure.prototype = {
+ call$1: function(arg) {
+ var t1 = this.T;
+ return this.$this.runUnaryGuarded$1$2(this.registered, t1._check(arg), t1);
+ },
+ $signature: function() {
+ return this.T._eval$1("~(0)");
+ }
+ };
+ P._rootHandleUncaughtError_closure.prototype = {
+ call$0: function() {
+ var error,
+ t1 = this._box_0,
+ t2 = t1.error;
+ t1 = t2 == null ? t1.error = new P.NullThrownError() : t2;
+ t2 = this.stackTrace;
+ if (t2 == null)
+ throw H.wrapException(t1);
+ error = H.wrapException(t1);
+ error.stack = t2.toString$0(0);
+ throw error;
+ },
+ $signature: 0
+ };
+ P._RootZone.prototype = {
+ get$_run: function() {
+ return C._ZoneFunction__RootZone__rootRun;
+ },
+ get$_runUnary: function() {
+ return C._ZoneFunction__RootZone__rootRunUnary;
+ },
+ get$_runBinary: function() {
+ return C._ZoneFunction__RootZone__rootRunBinary;
+ },
+ get$_registerCallback: function() {
+ return C._ZoneFunction__RootZone__rootRegisterCallback;
+ },
+ get$_registerUnaryCallback: function() {
+ return C._ZoneFunction_Eeh;
+ },
+ get$_registerBinaryCallback: function() {
+ return C._ZoneFunction_7G2;
+ },
+ get$_errorCallback: function() {
+ return C._ZoneFunction__RootZone__rootErrorCallback;
+ },
+ get$_scheduleMicrotask: function() {
+ return C._ZoneFunction__RootZone__rootScheduleMicrotask;
+ },
+ get$_createTimer: function() {
+ return C._ZoneFunction__RootZone__rootCreateTimer;
+ },
+ get$_createPeriodicTimer: function() {
+ return C._ZoneFunction_3bB;
+ },
+ get$_print: function() {
+ return C._ZoneFunction__RootZone__rootPrint;
+ },
+ get$_fork: function() {
+ return C._ZoneFunction__RootZone__rootFork;
+ },
+ get$_handleUncaughtError: function() {
+ return C._ZoneFunction_NMc;
+ },
+ get$parent: function(_) {
+ return null;
+ },
+ get$_async$_map: function() {
+ return $.$get$_RootZone__rootMap();
+ },
+ get$_delegate: function() {
+ var t1 = $._RootZone__rootDelegate;
+ if (t1 != null)
+ return t1;
+ return $._RootZone__rootDelegate = new P._ZoneDelegate(this);
+ },
+ get$errorZone: function() {
+ return this;
+ },
+ runGuarded$1: function(f) {
+ var e, s, exception, _null = null;
+ type$.void_Function._check(f);
+ try {
+ if (C.C__RootZone === $.Zone__current) {
+ f.call$0();
+ return;
+ }
+ P._rootRun(_null, _null, this, f, type$.void);
+ } catch (exception) {
+ e = H.unwrapException(exception);
+ s = H.getTraceFromException(exception);
+ P._rootHandleUncaughtError(_null, _null, this, e, type$.StackTrace._check(s));
+ }
+ },
+ runUnaryGuarded$1$2: function(f, arg, $T) {
+ var e, s, exception, _null = null;
+ $T._eval$1("~(0)")._check(f);
+ $T._check(arg);
+ try {
+ if (C.C__RootZone === $.Zone__current) {
+ f.call$1(arg);
+ return;
+ }
+ P._rootRunUnary(_null, _null, this, f, arg, type$.void, $T);
+ } catch (exception) {
+ e = H.unwrapException(exception);
+ s = H.getTraceFromException(exception);
+ P._rootHandleUncaughtError(_null, _null, this, e, type$.StackTrace._check(s));
+ }
+ },
+ runBinaryGuarded$2$3: function(f, arg1, arg2, T1, T2) {
+ var e, s, exception, _null = null;
+ T1._eval$1("@<0>")._bind$1(T2)._eval$1("~(1,2)")._check(f);
+ T1._check(arg1);
+ T2._check(arg2);
+ try {
+ if (C.C__RootZone === $.Zone__current) {
+ f.call$2(arg1, arg2);
+ return;
+ }
+ P._rootRunBinary(_null, _null, this, f, arg1, arg2, type$.void, T1, T2);
+ } catch (exception) {
+ e = H.unwrapException(exception);
+ s = H.getTraceFromException(exception);
+ P._rootHandleUncaughtError(_null, _null, this, e, type$.StackTrace._check(s));
+ }
+ },
+ bindCallback$1$1: function(f, $R) {
+ return new P._RootZone_bindCallback_closure(this, $R._eval$1("0()")._check(f), $R);
+ },
+ bindCallbackGuarded$1: function(f) {
+ return new P._RootZone_bindCallbackGuarded_closure(this, type$.void_Function._check(f));
+ },
+ bindUnaryCallbackGuarded$1$1: function(f, $T) {
+ return new P._RootZone_bindUnaryCallbackGuarded_closure(this, $T._eval$1("~(0)")._check(f), $T);
+ },
+ $index: function(_, key) {
+ return null;
+ },
+ handleUncaughtError$2: function(error, stackTrace) {
+ P._rootHandleUncaughtError(null, null, this, error, type$.StackTrace._check(stackTrace));
+ },
+ fork$2$specification$zoneValues: function(specification, zoneValues) {
+ return P._rootFork(null, null, this, specification, zoneValues);
+ },
+ run$1$1: function(f, $R) {
+ $R._eval$1("0()")._check(f);
+ if ($.Zone__current === C.C__RootZone)
+ return f.call$0();
+ return P._rootRun(null, null, this, f, $R);
+ },
+ runUnary$2$2: function(f, arg, $R, $T) {
+ $R._eval$1("@<0>")._bind$1($T)._eval$1("1(2)")._check(f);
+ $T._check(arg);
+ if ($.Zone__current === C.C__RootZone)
+ return f.call$1(arg);
+ return P._rootRunUnary(null, null, this, f, arg, $R, $T);
+ },
+ runBinary$3$3: function(f, arg1, arg2, $R, T1, T2) {
+ $R._eval$1("@<0>")._bind$1(T1)._bind$1(T2)._eval$1("1(2,3)")._check(f);
+ T1._check(arg1);
+ T2._check(arg2);
+ if ($.Zone__current === C.C__RootZone)
+ return f.call$2(arg1, arg2);
+ return P._rootRunBinary(null, null, this, f, arg1, arg2, $R, T1, T2);
+ },
+ registerCallback$1$1: function(f, $R) {
+ return $R._eval$1("0()")._check(f);
+ },
+ registerUnaryCallback$2$1: function(f, $R, $T) {
+ return $R._eval$1("@<0>")._bind$1($T)._eval$1("1(2)")._check(f);
+ },
+ registerBinaryCallback$3$1: function(f, $R, T1, T2) {
+ return $R._eval$1("@<0>")._bind$1(T1)._bind$1(T2)._eval$1("1(2,3)")._check(f);
+ },
+ errorCallback$2: function(error, stackTrace) {
+ type$.StackTrace._check(stackTrace);
+ return null;
+ },
+ scheduleMicrotask$1: function(f) {
+ P._rootScheduleMicrotask(null, null, this, type$.void_Function._check(f));
+ },
+ createTimer$2: function(duration, f) {
+ return P.Timer__createTimer(duration, type$.void_Function._check(f));
+ },
+ print$1: function(_, line) {
+ H.printString(line);
+ }
+ };
+ P._RootZone_bindCallback_closure.prototype = {
+ call$0: function() {
+ return this.$this.run$1$1(this.f, this.R);
+ },
+ $signature: function() {
+ return this.R._eval$1("0()");
+ }
+ };
+ P._RootZone_bindCallbackGuarded_closure.prototype = {
+ call$0: function() {
+ return this.$this.runGuarded$1(this.f);
+ },
+ $signature: 1
+ };
+ P._RootZone_bindUnaryCallbackGuarded_closure.prototype = {
+ call$1: function(arg) {
+ var t1 = this.T;
+ return this.$this.runUnaryGuarded$1$2(this.f, t1._check(arg), t1);
+ },
+ $signature: function() {
+ return this.T._eval$1("~(0)");
+ }
+ };
+ P.runZoned_closure.prototype = {
+ call$5: function($self, $parent, zone, error, stackTrace) {
+ var e, s, t2, t3, t4, exception,
+ t1 = type$.StackTrace;
+ t1._check(stackTrace);
+ try {
+ t2 = this._box_0;
+ t3 = type$.void;
+ t4 = type$.Object;
+ if (t2.binaryOnError != null)
+ $self.get$parent($self).runBinary$3$3(t2.binaryOnError, error, stackTrace, t3, t4, t1);
+ else {
+ H.assertHelper(t2.unaryOnError != null);
+ $self.get$parent($self).runUnary$2$2(t2.unaryOnError, error, t3, t4);
+ }
+ } catch (exception) {
+ e = H.unwrapException(exception);
+ s = H.getTraceFromException(exception);
+ t1 = e;
+ if (t1 == null ? error == null : t1 === error)
+ $parent.handleUncaughtError$3(zone, error, stackTrace);
+ else
+ $parent.handleUncaughtError$3(zone, e, s);
+ }
+ },
+ $signature: 28
+ };
+ P._HashMap.prototype = {
+ get$length: function(_) {
+ return this._collection$_length;
+ },
+ get$isEmpty: function(_) {
+ return this._collection$_length === 0;
+ },
+ get$keys: function() {
+ return new P._HashMapKeyIterable(this, H._instanceType(this)._eval$1("_HashMapKeyIterable<1>"));
+ },
+ containsKey$1: function(key) {
+ var strings, nums;
+ if (typeof key == "string" && key !== "__proto__") {
+ strings = this._strings;
+ return strings == null ? false : strings[key] != null;
+ } else if (typeof key == "number" && (key & 1073741823) === key) {
+ nums = this._nums;
+ return nums == null ? false : nums[key] != null;
+ } else
+ return this._containsKey$1(key);
+ },
+ _containsKey$1: function(key) {
+ var rest = this._collection$_rest;
+ if (rest == null)
+ return false;
+ return this._findBucketIndex$2(this._getBucket$2(rest, key), key) >= 0;
+ },
+ $index: function(_, key) {
+ var strings, t1, nums;
+ if (typeof key == "string" && key !== "__proto__") {
+ strings = this._strings;
+ t1 = strings == null ? null : P._HashMap__getTableEntry(strings, key);
+ return t1;
+ } else if (typeof key == "number" && (key & 1073741823) === key) {
+ nums = this._nums;
+ t1 = nums == null ? null : P._HashMap__getTableEntry(nums, key);
+ return t1;
+ } else
+ return this._get$1(key);
+ },
+ _get$1: function(key) {
+ var bucket, index,
+ rest = this._collection$_rest;
+ if (rest == null)
+ return null;
+ bucket = this._getBucket$2(rest, key);
+ index = this._findBucketIndex$2(bucket, key);
+ return index < 0 ? null : bucket[index + 1];
+ },
+ $indexSet: function(_, key, value) {
+ var strings, nums, _this = this,
+ t1 = H._instanceType(_this);
+ t1._precomputed1._check(key);
+ t1._rest[1]._check(value);
+ if (typeof key == "string" && key !== "__proto__") {
+ strings = _this._strings;
+ _this._addHashTableEntry$3(strings == null ? _this._strings = P._HashMap__newHashTable() : strings, key, value);
+ } else if (typeof key == "number" && (key & 1073741823) === key) {
+ nums = _this._nums;
+ _this._addHashTableEntry$3(nums == null ? _this._nums = P._HashMap__newHashTable() : nums, key, value);
+ } else
+ _this._set$2(key, value);
+ },
+ _set$2: function(key, value) {
+ var rest, hash, bucket, index, _this = this,
+ t1 = H._instanceType(_this);
+ t1._precomputed1._check(key);
+ t1._rest[1]._check(value);
+ rest = _this._collection$_rest;
+ if (rest == null)
+ rest = _this._collection$_rest = P._HashMap__newHashTable();
+ hash = _this._computeHashCode$1(key);
+ bucket = rest[hash];
+ if (bucket == null) {
+ P._HashMap__setTableEntry(rest, hash, [key, value]);
+ ++_this._collection$_length;
+ _this._keys = null;
+ } else {
+ index = _this._findBucketIndex$2(bucket, key);
+ if (index >= 0)
+ bucket[index + 1] = value;
+ else {
+ bucket.push(key, value);
+ ++_this._collection$_length;
+ _this._keys = null;
+ }
+ }
+ },
+ remove$1: function(_, key) {
+ var t1 = this._remove$1(key);
+ return t1;
+ },
+ _remove$1: function(key) {
+ var hash, bucket, index, result, _this = this,
+ rest = _this._collection$_rest;
+ if (rest == null)
+ return null;
+ hash = _this._computeHashCode$1(key);
+ bucket = rest[hash];
+ index = _this._findBucketIndex$2(bucket, key);
+ if (index < 0)
+ return null;
+ --_this._collection$_length;
+ _this._keys = null;
+ result = bucket.splice(index, 2)[1];
+ if (0 === bucket.length)
+ delete rest[hash];
+ return result;
+ },
+ forEach$1: function(_, action) {
+ var keys, $length, i, key, _this = this,
+ t1 = H._instanceType(_this);
+ t1._eval$1("~(1,2)")._check(action);
+ keys = _this._computeKeys$0();
+ for ($length = keys.length, t1 = t1._precomputed1, i = 0; i < $length; ++i) {
+ key = keys[i];
+ action.call$2(t1._check(key), _this.$index(0, key));
+ if (keys !== _this._keys)
+ throw H.wrapException(P.ConcurrentModificationError$(_this));
+ }
+ },
+ _computeKeys$0: function() {
+ var result, strings, names, entries, index, i, nums, rest, bucket, $length, i0, _this = this,
+ t1 = _this._keys;
+ if (t1 != null)
+ return t1;
+ result = new Array(_this._collection$_length);
+ result.fixed$length = Array;
+ strings = _this._strings;
+ if (strings != null) {
+ names = Object.getOwnPropertyNames(strings);
+ entries = names.length;
+ for (index = 0, i = 0; i < entries; ++i) {
+ result[index] = names[i];
+ ++index;
+ }
+ } else
+ index = 0;
+ nums = _this._nums;
+ if (nums != null) {
+ names = Object.getOwnPropertyNames(nums);
+ entries = names.length;
+ for (i = 0; i < entries; ++i) {
+ result[index] = +names[i];
+ ++index;
+ }
+ }
+ rest = _this._collection$_rest;
+ if (rest != null) {
+ names = Object.getOwnPropertyNames(rest);
+ entries = names.length;
+ for (i = 0; i < entries; ++i) {
+ bucket = rest[names[i]];
+ $length = bucket.length;
+ for (i0 = 0; i0 < $length; i0 += 2) {
+ result[index] = bucket[i0];
+ ++index;
+ }
+ }
+ }
+ H.assertHelper(index === _this._collection$_length);
+ return _this._keys = result;
+ },
+ _addHashTableEntry$3: function(table, key, value) {
+ var t1 = H._instanceType(this);
+ t1._precomputed1._check(key);
+ t1._rest[1]._check(value);
+ if (table[key] == null) {
+ ++this._collection$_length;
+ this._keys = null;
+ }
+ P._HashMap__setTableEntry(table, key, value);
+ },
+ _computeHashCode$1: function(key) {
+ return J.get$hashCode$(key) & 1073741823;
+ },
+ _getBucket$2: function(table, key) {
+ return table[this._computeHashCode$1(key)];
+ },
+ _findBucketIndex$2: function(bucket, key) {
+ var $length, i;
+ if (bucket == null)
+ return -1;
+ $length = bucket.length;
+ for (i = 0; i < $length; i += 2)
+ if (J.$eq$(bucket[i], key))
+ return i;
+ return -1;
+ }
+ };
+ P._IdentityHashMap.prototype = {
+ _computeHashCode$1: function(key) {
+ return H.objectHashCode(key) & 1073741823;
+ },
+ _findBucketIndex$2: function(bucket, key) {
+ var $length, i, t1;
+ if (bucket == null)
+ return -1;
+ $length = bucket.length;
+ for (i = 0; i < $length; i += 2) {
+ t1 = bucket[i];
+ if (t1 == null ? key == null : t1 === key)
+ return i;
+ }
+ return -1;
+ }
+ };
+ P._HashMapKeyIterable.prototype = {
+ get$length: function(_) {
+ return this._map._collection$_length;
+ },
+ get$isEmpty: function(_) {
+ return this._map._collection$_length === 0;
+ },
+ get$iterator: function(_) {
+ var t1 = this._map;
+ return new P._HashMapKeyIterator(t1, t1._computeKeys$0(), this.$ti._eval$1("_HashMapKeyIterator<1>"));
+ },
+ contains$1: function(_, element) {
+ return this._map.containsKey$1(element);
+ }
+ };
+ P._HashMapKeyIterator.prototype = {
+ get$current: function() {
+ return this._collection$_current;
+ },
+ moveNext$0: function() {
+ var _this = this,
+ keys = _this._keys,
+ offset = _this._offset,
+ t1 = _this._map;
+ if (keys !== t1._keys)
+ throw H.wrapException(P.ConcurrentModificationError$(t1));
+ else if (offset >= keys.length) {
+ _this.set$_collection$_current(null);
+ return false;
+ } else {
+ _this.set$_collection$_current(keys[offset]);
+ _this._offset = offset + 1;
+ return true;
+ }
+ },
+ set$_collection$_current: function(_current) {
+ this._collection$_current = this.$ti._precomputed1._check(_current);
+ },
+ $isIterator: 1
+ };
+ P._LinkedHashSet.prototype = {
+ _newSet$0: function() {
+ return new P._LinkedHashSet(H._instanceType(this)._eval$1("_LinkedHashSet<1>"));
+ },
+ get$iterator: function(_) {
+ var _this = this,
+ t1 = new P._LinkedHashSetIterator(_this, _this._collection$_modifications, H._instanceType(_this)._eval$1("_LinkedHashSetIterator<1>"));
+ t1._collection$_cell = _this._collection$_first;
+ return t1;
+ },
+ get$length: function(_) {
+ return this._collection$_length;
+ },
+ get$isEmpty: function(_) {
+ return this._collection$_length === 0;
+ },
+ contains$1: function(_, object) {
+ var strings, nums;
+ if (typeof object == "string" && object !== "__proto__") {
+ strings = this._strings;
+ if (strings == null)
+ return false;
+ return type$._LinkedHashSetCell._check(strings[object]) != null;
+ } else if (typeof object == "number" && (object & 1073741823) === object) {
+ nums = this._nums;
+ if (nums == null)
+ return false;
+ return type$._LinkedHashSetCell._check(nums[object]) != null;
+ } else
+ return this._contains$1(object);
+ },
+ _contains$1: function(object) {
+ var rest = this._collection$_rest;
+ if (rest == null)
+ return false;
+ return this._findBucketIndex$2(rest[this._computeHashCode$1(object)], object) >= 0;
+ },
+ get$first: function(_) {
+ var t1 = this._collection$_first;
+ if (t1 == null)
+ throw H.wrapException(P.StateError$("No elements"));
+ return H._instanceType(this)._precomputed1._check(t1._element);
+ },
+ get$last: function(_) {
+ var t1 = this._collection$_last;
+ if (t1 == null)
+ throw H.wrapException(P.StateError$("No elements"));
+ return H._instanceType(this)._precomputed1._check(t1._element);
+ },
+ add$1: function(_, element) {
+ var strings, nums, _this = this;
+ H._instanceType(_this)._precomputed1._check(element);
+ if (typeof element == "string" && element !== "__proto__") {
+ strings = _this._strings;
+ return _this._addHashTableEntry$2(strings == null ? _this._strings = P._LinkedHashSet__newHashTable() : strings, element);
+ } else if (typeof element == "number" && (element & 1073741823) === element) {
+ nums = _this._nums;
+ return _this._addHashTableEntry$2(nums == null ? _this._nums = P._LinkedHashSet__newHashTable() : nums, element);
+ } else
+ return _this._collection$_add$1(element);
+ },
+ _collection$_add$1: function(element) {
+ var rest, hash, bucket, t1, _this = this;
+ H._instanceType(_this)._precomputed1._check(element);
+ rest = _this._collection$_rest;
+ if (rest == null)
+ rest = _this._collection$_rest = P._LinkedHashSet__newHashTable();
+ hash = _this._computeHashCode$1(element);
+ bucket = rest[hash];
+ if (bucket == null) {
+ t1 = [_this._collection$_newLinkedCell$1(element)];
+ H.assertHelper(t1 != null);
+ rest[hash] = t1;
+ } else {
+ if (_this._findBucketIndex$2(bucket, element) >= 0)
+ return false;
+ bucket.push(_this._collection$_newLinkedCell$1(element));
+ }
+ return true;
+ },
+ remove$1: function(_, object) {
+ var _this = this;
+ if (typeof object == "string" && object !== "__proto__")
+ return _this._removeHashTableEntry$2(_this._strings, object);
+ else if (typeof object == "number" && (object & 1073741823) === object)
+ return _this._removeHashTableEntry$2(_this._nums, object);
+ else
+ return _this._remove$1(object);
+ },
+ _remove$1: function(object) {
+ var hash, bucket, index, cell, _this = this,
+ rest = _this._collection$_rest;
+ if (rest == null)
+ return false;
+ hash = _this._computeHashCode$1(object);
+ bucket = rest[hash];
+ index = _this._findBucketIndex$2(bucket, object);
+ if (index < 0)
+ return false;
+ cell = bucket.splice(index, 1)[0];
+ if (0 === bucket.length)
+ delete rest[hash];
+ _this._unlinkCell$1(cell);
+ return true;
+ },
+ _addHashTableEntry$2: function(table, element) {
+ H._instanceType(this)._precomputed1._check(element);
+ if (type$._LinkedHashSetCell._check(table[element]) != null)
+ return false;
+ table[element] = this._collection$_newLinkedCell$1(element);
+ return true;
+ },
+ _removeHashTableEntry$2: function(table, element) {
+ var cell;
+ if (table == null)
+ return false;
+ cell = type$._LinkedHashSetCell._check(table[element]);
+ if (cell == null)
+ return false;
+ this._unlinkCell$1(cell);
+ delete table[element];
+ return true;
+ },
+ _collection$_modified$0: function() {
+ this._collection$_modifications = 1073741823 & this._collection$_modifications + 1;
+ },
+ _collection$_newLinkedCell$1: function(element) {
+ var last, _this = this,
+ cell = new P._LinkedHashSetCell(H._instanceType(_this)._precomputed1._check(element));
+ if (_this._collection$_first == null)
+ _this._collection$_first = _this._collection$_last = cell;
+ else {
+ last = _this._collection$_last;
+ cell._collection$_previous = last;
+ _this._collection$_last = last._collection$_next = cell;
+ }
+ ++_this._collection$_length;
+ _this._collection$_modified$0();
+ return cell;
+ },
+ _unlinkCell$1: function(cell) {
+ var _this = this,
+ previous = cell._collection$_previous,
+ next = cell._collection$_next;
+ if (previous == null) {
+ H.assertHelper(cell == _this._collection$_first);
+ _this._collection$_first = next;
+ } else
+ previous._collection$_next = next;
+ if (next == null) {
+ H.assertHelper(cell == _this._collection$_last);
+ _this._collection$_last = previous;
+ } else
+ next._collection$_previous = previous;
+ --_this._collection$_length;
+ _this._collection$_modified$0();
+ },
+ _computeHashCode$1: function(element) {
+ return J.get$hashCode$(element) & 1073741823;
+ },
+ _findBucketIndex$2: function(bucket, element) {
+ var $length, i;
+ if (bucket == null)
+ return -1;
+ $length = bucket.length;
+ for (i = 0; i < $length; ++i)
+ if (J.$eq$(bucket[i]._element, element))
+ return i;
+ return -1;
+ },
+ $isLinkedHashSet: 1
+ };
+ P._LinkedHashSetCell.prototype = {};
+ P._LinkedHashSetIterator.prototype = {
+ get$current: function() {
+ return this._collection$_current;
+ },
+ moveNext$0: function() {
+ var _this = this,
+ t1 = _this._set;
+ if (_this._collection$_modifications !== t1._collection$_modifications)
+ throw H.wrapException(P.ConcurrentModificationError$(t1));
+ else {
+ t1 = _this._collection$_cell;
+ if (t1 == null) {
+ _this.set$_collection$_current(null);
+ return false;
+ } else {
+ _this.set$_collection$_current(_this.$ti._precomputed1._check(t1._element));
+ _this._collection$_cell = _this._collection$_cell._collection$_next;
+ return true;
+ }
+ }
+ },
+ set$_collection$_current: function(_current) {
+ this._collection$_current = this.$ti._precomputed1._check(_current);
+ },
+ $isIterator: 1
+ };
+ P.UnmodifiableListView.prototype = {
+ get$length: function(_) {
+ var t1 = this._collection$_source;
+ return t1.get$length(t1);
+ },
+ $index: function(_, index) {
+ return this._collection$_source.$index(0, H._checkIntNullable(index));
+ }
+ };
+ P.HashMap_HashMap$from_closure.prototype = {
+ call$2: function(k, v) {
+ this.result.$indexSet(0, this.K._check(k), this.V._check(v));
+ },
+ $signature: 6
+ };
+ P.IterableBase.prototype = {};
+ P.LinkedHashMap_LinkedHashMap$from_closure.prototype = {
+ call$2: function(k, v) {
+ this.result.$indexSet(0, this.K._check(k), this.V._check(v));
+ },
+ $signature: 6
+ };
+ P.ListBase.prototype = {$isEfficientLengthIterable: 1, $isIterable: 1, $isList: 1};
+ P.ListMixin.prototype = {
+ get$iterator: function(receiver) {
+ return new H.ListIterator(receiver, this.get$length(receiver), H.instanceType(receiver)._eval$1("ListIterator<ListMixin.E>"));
+ },
+ elementAt$1: function(receiver, index) {
+ return this.$index(receiver, index);
+ },
+ get$isEmpty: function(receiver) {
+ return this.get$length(receiver) === 0;
+ },
+ get$first: function(receiver) {
+ if (this.get$length(receiver) === 0)
+ throw H.wrapException(H.IterableElementError_noElement());
+ return this.$index(receiver, 0);
+ },
+ get$last: function(receiver) {
+ if (this.get$length(receiver) === 0)
+ throw H.wrapException(H.IterableElementError_noElement());
+ return this.$index(receiver, this.get$length(receiver) - 1);
+ },
+ map$1$1: function(receiver, f, $T) {
+ var t1 = H.instanceType(receiver);
+ return new H.MappedListIterable(receiver, t1._bind$1($T)._eval$1("1(ListMixin.E)")._check(f), t1._eval$1("@<ListMixin.E>")._bind$1($T)._eval$1("MappedListIterable<1,2>"));
+ },
+ toList$1$growable: function(receiver, growable) {
+ var i,
+ result = H.setRuntimeTypeInfo([], H.instanceType(receiver)._eval$1("JSArray<ListMixin.E>"));
+ C.JSArray_methods.set$length(result, this.get$length(receiver));
+ for (i = 0; i < this.get$length(receiver); ++i)
+ C.JSArray_methods.$indexSet(result, i, this.$index(receiver, i));
+ return result;
+ },
+ toList$0: function($receiver) {
+ return this.toList$1$growable($receiver, true);
+ },
+ toSet$0: function(receiver) {
+ var i,
+ result = P.LinkedHashSet_LinkedHashSet(H.instanceType(receiver)._eval$1("ListMixin.E"));
+ for (i = 0; i < this.get$length(receiver); ++i)
+ result.add$1(0, this.$index(receiver, i));
+ return result;
+ },
+ remove$1: function(receiver, element) {
+ var i;
+ for (i = 0; i < this.get$length(receiver); ++i)
+ if (J.$eq$(this.$index(receiver, i), element)) {
+ this._closeGap$2(receiver, i, i + 1);
+ return true;
+ }
+ return false;
+ },
+ _closeGap$2: function(receiver, start, end) {
+ var size, i, _this = this,
+ $length = _this.get$length(receiver);
+ H.assertHelper(start < end);
+ H.assertHelper(end <= $length);
+ size = end - start;
+ for (i = end; i < $length; ++i)
+ _this.$indexSet(receiver, i - size, _this.$index(receiver, i));
+ _this.set$length(receiver, $length - size);
+ },
+ fillRange$3: function(receiver, start, end, fill) {
+ var i;
+ H.instanceType(receiver)._eval$1("ListMixin.E")._check(fill);
+ P.RangeError_checkValidRange(start, end, this.get$length(receiver));
+ for (i = start; i < end; ++i)
+ this.$indexSet(receiver, i, fill);
+ },
+ toString$0: function(receiver) {
+ return P.IterableBase_iterableToFullString(receiver, "[", "]");
+ }
+ };
+ P.MapBase.prototype = {};
+ P.MapBase_mapToString_closure.prototype = {
+ call$2: function(k, v) {
+ var t2,
+ t1 = this._box_0;
+ if (!t1.first)
+ this.result._contents += ", ";
+ t1.first = false;
+ t1 = this.result;
+ t2 = t1._contents += H.S(k);
+ t1._contents = t2 + ": ";
+ t1._contents += H.S(v);
+ },
+ $signature: 6
+ };
+ P.MapMixin.prototype = {
+ cast$2$0: function(_, RK, RV) {
+ var t1 = H._instanceType(this);
+ return P.Map_castFrom(this, t1._eval$1("MapMixin.K"), t1._eval$1("MapMixin.V"), RK, RV);
+ },
+ forEach$1: function(_, action) {
+ var t1, key;
+ H._instanceType(this)._eval$1("~(MapMixin.K,MapMixin.V)")._check(action);
+ for (t1 = this.get$keys(), t1 = t1.get$iterator(t1); t1.moveNext$0();) {
+ key = t1.get$current();
+ action.call$2(key, this.$index(0, key));
+ }
+ },
+ map$2$1: function(_, transform, K2, V2) {
+ var result, t1, key, entry;
+ H._instanceType(this)._bind$1(K2)._bind$1(V2)._eval$1("MapEntry<1,2>(MapMixin.K,MapMixin.V)")._check(transform);
+ result = P.LinkedHashMap_LinkedHashMap$_empty(K2, V2);
+ for (t1 = this.get$keys(), t1 = t1.get$iterator(t1); t1.moveNext$0();) {
+ key = t1.get$current();
+ entry = transform.call$2(key, this.$index(0, key));
+ result.$indexSet(0, entry.key, entry.value);
+ }
+ return result;
+ },
+ containsKey$1: function(key) {
+ return this.get$keys().contains$1(0, key);
+ },
+ get$length: function(_) {
+ var t1 = this.get$keys();
+ return t1.get$length(t1);
+ },
+ get$isEmpty: function(_) {
+ var t1 = this.get$keys();
+ return t1.get$isEmpty(t1);
+ },
+ toString$0: function(_) {
+ return P.MapBase_mapToString(this);
+ },
+ $isMap: 1
+ };
+ P._UnmodifiableMapMixin.prototype = {
+ remove$1: function(_, key) {
+ throw H.wrapException(P.UnsupportedError$("Cannot modify unmodifiable map"));
+ }
+ };
+ P.MapView.prototype = {
+ cast$2$0: function(_, RK, RV) {
+ return this._map.cast$2$0(0, RK, RV);
+ },
+ $index: function(_, key) {
+ return this._map.$index(0, key);
+ },
+ containsKey$1: function(key) {
+ return this._map.containsKey$1(key);
+ },
+ forEach$1: function(_, action) {
+ this._map.forEach$1(0, H._instanceType(this)._eval$1("~(1,2)")._check(action));
+ },
+ get$isEmpty: function(_) {
+ var t1 = this._map;
+ return t1.get$isEmpty(t1);
+ },
+ get$length: function(_) {
+ var t1 = this._map;
+ return t1.get$length(t1);
+ },
+ get$keys: function() {
+ return this._map.get$keys();
+ },
+ remove$1: function(_, key) {
+ return this._map.remove$1(0, key);
+ },
+ toString$0: function(_) {
+ return this._map.toString$0(0);
+ },
+ map$2$1: function(_, transform, K2, V2) {
+ return this._map.map$2$1(0, H._instanceType(this)._bind$1(K2)._bind$1(V2)._eval$1("MapEntry<1,2>(3,4)")._check(transform), K2, V2);
+ },
+ $isMap: 1
+ };
+ P.UnmodifiableMapView.prototype = {
+ cast$2$0: function(_, RK, RV) {
+ return new P.UnmodifiableMapView(this._map.cast$2$0(0, RK, RV), RK._eval$1("@<0>")._bind$1(RV)._eval$1("UnmodifiableMapView<1,2>"));
+ }
+ };
+ P.ListQueue.prototype = {
+ get$iterator: function(_) {
+ var _this = this;
+ return new P._ListQueueIterator(_this, _this._tail, _this._modificationCount, _this._head, _this.$ti._eval$1("_ListQueueIterator<1>"));
+ },
+ get$isEmpty: function(_) {
+ return this._head === this._tail;
+ },
+ get$length: function(_) {
+ return (this._tail - this._head & this._table.length - 1) >>> 0;
+ },
+ get$first: function(_) {
+ var t2,
+ t1 = this._head;
+ if (t1 === this._tail)
+ throw H.wrapException(H.IterableElementError_noElement());
+ t2 = this._table;
+ if (t1 >= t2.length)
+ return H.ioore(t2, t1);
+ return t2[t1];
+ },
+ get$last: function(_) {
+ var t3,
+ t1 = this._head,
+ t2 = this._tail;
+ if (t1 === t2)
+ throw H.wrapException(H.IterableElementError_noElement());
+ t1 = this._table;
+ t3 = t1.length;
+ t2 = (t2 - 1 & t3 - 1) >>> 0;
+ if (t2 < 0 || t2 >= t3)
+ return H.ioore(t1, t2);
+ return t1[t2];
+ },
+ elementAt$1: function(_, index) {
+ var t1, t2, t3, _this = this,
+ $length = _this.get$length(_this);
+ if (0 > index || index >= $length)
+ H.throwExpression(P.IndexError$(index, _this, "index", null, $length));
+ t1 = _this._table;
+ t2 = t1.length;
+ t3 = (_this._head + index & t2 - 1) >>> 0;
+ if (t3 < 0 || t3 >= t2)
+ return H.ioore(t1, t3);
+ return t1[t3];
+ },
+ clear$0: function(_) {
+ var _this = this,
+ i = _this._head;
+ if (i !== _this._tail) {
+ for (; i !== _this._tail; i = (i + 1 & _this._table.length - 1) >>> 0)
+ C.JSArray_methods.$indexSet(_this._table, i, null);
+ _this._head = _this._tail = 0;
+ ++_this._modificationCount;
+ }
+ },
+ toString$0: function(_) {
+ return P.IterableBase_iterableToFullString(this, "{", "}");
+ },
+ removeFirst$0: function() {
+ var t2, result, _this = this,
+ t1 = _this._head;
+ if (t1 === _this._tail)
+ throw H.wrapException(H.IterableElementError_noElement());
+ ++_this._modificationCount;
+ t2 = _this._table;
+ if (t1 >= t2.length)
+ return H.ioore(t2, t1);
+ result = t2[t1];
+ C.JSArray_methods.$indexSet(t2, t1, null);
+ _this._head = (_this._head + 1 & _this._table.length - 1) >>> 0;
+ return result;
+ },
+ _collection$_add$1: function(element) {
+ var t2, t3, newTable, split, _this = this,
+ t1 = _this.$ti;
+ t1._precomputed1._check(element);
+ C.JSArray_methods.$indexSet(_this._table, _this._tail, element);
+ t2 = _this._tail;
+ t3 = _this._table.length;
+ t2 = (t2 + 1 & t3 - 1) >>> 0;
+ _this._tail = t2;
+ if (_this._head === t2) {
+ t2 = new Array(t3 * 2);
+ t2.fixed$length = Array;
+ newTable = H.setRuntimeTypeInfo(t2, t1._eval$1("JSArray<1>"));
+ t1 = _this._table;
+ t2 = _this._head;
+ split = t1.length - t2;
+ C.JSArray_methods.setRange$4(newTable, 0, split, t1, t2);
+ C.JSArray_methods.setRange$4(newTable, split, split + _this._head, _this._table, 0);
+ _this._head = 0;
+ _this._tail = _this._table.length;
+ _this.set$_table(newTable);
+ }
+ ++_this._modificationCount;
+ },
+ set$_table: function(_table) {
+ this._table = this.$ti._eval$1("List<1>")._check(_table);
+ },
+ $isQueue: 1
+ };
+ P._ListQueueIterator.prototype = {
+ get$current: function() {
+ return this._collection$_current;
+ },
+ moveNext$0: function() {
+ var t2, t3, _this = this,
+ t1 = _this._queue;
+ if (_this._modificationCount !== t1._modificationCount)
+ H.throwExpression(P.ConcurrentModificationError$(t1));
+ t2 = _this._position;
+ if (t2 === _this._end) {
+ _this.set$_collection$_current(null);
+ return false;
+ }
+ t3 = t1._table;
+ if (t2 >= t3.length)
+ return H.ioore(t3, t2);
+ _this.set$_collection$_current(t3[t2]);
+ _this._position = (_this._position + 1 & t1._table.length - 1) >>> 0;
+ return true;
+ },
+ set$_collection$_current: function(_current) {
+ this._collection$_current = this.$ti._precomputed1._check(_current);
+ },
+ $isIterator: 1
+ };
+ P.SetMixin.prototype = {
+ get$isEmpty: function(_) {
+ return this.get$length(this) === 0;
+ },
+ map$1$1: function(_, f, $T) {
+ var t1 = H._instanceType(this);
+ return new H.EfficientLengthMappedIterable(this, t1._bind$1($T)._eval$1("1(2)")._check(f), t1._eval$1("@<1>")._bind$1($T)._eval$1("EfficientLengthMappedIterable<1,2>"));
+ },
+ toString$0: function(_) {
+ return P.IterableBase_iterableToFullString(this, "{", "}");
+ },
+ every$1: function(_, f) {
+ var t1;
+ H._instanceType(this)._eval$1("bool(1)")._check(f);
+ for (t1 = this.get$iterator(this); t1.moveNext$0();)
+ if (!H.boolConversionCheck(f.call$1(t1.get$current())))
+ return false;
+ return true;
+ },
+ get$first: function(_) {
+ var it = this.get$iterator(this);
+ if (!it.moveNext$0())
+ throw H.wrapException(H.IterableElementError_noElement());
+ return it.get$current();
+ },
+ get$last: function(_) {
+ var result,
+ it = this.get$iterator(this);
+ if (!it.moveNext$0())
+ throw H.wrapException(H.IterableElementError_noElement());
+ do
+ result = it.get$current();
+ while (it.moveNext$0());
+ return result;
+ },
+ $isEfficientLengthIterable: 1,
+ $isIterable: 1,
+ $isSet: 1
+ };
+ P.SetBase.prototype = {$isEfficientLengthIterable: 1, $isIterable: 1, $isSet: 1};
+ P._SetBase.prototype = {
+ toSet$0: function(_) {
+ var t1 = this._newSet$0();
+ t1.addAll$1(0, this);
+ return t1;
+ },
+ get$isEmpty: function(_) {
+ return this._collection$_length === 0;
+ },
+ addAll$1: function(_, elements) {
+ var t1;
+ H._instanceType(this)._eval$1("Iterable<1>")._check(elements);
+ for (t1 = elements.get$iterator(elements); t1.moveNext$0();)
+ this.add$1(0, t1.get$current());
+ },
+ union$1: function(other) {
+ var t1;
+ H._instanceType(this)._eval$1("Set<1>")._check(other);
+ t1 = this.toSet$0(0);
+ t1.addAll$1(0, other);
+ return t1;
+ },
+ toList$1$growable: function(_, growable) {
+ var i, i0, _this = this,
+ t1 = H._instanceType(_this),
+ result = H.setRuntimeTypeInfo([], t1._eval$1("JSArray<1>"));
+ C.JSArray_methods.set$length(result, _this._collection$_length);
+ for (t1 = P._LinkedHashSetIterator$(_this, _this._collection$_modifications, t1._precomputed1), i = 0; t1.moveNext$0(); i = i0) {
+ i0 = i + 1;
+ C.JSArray_methods.$indexSet(result, i, t1._collection$_current);
+ }
+ return result;
+ },
+ toList$0: function($receiver) {
+ return this.toList$1$growable($receiver, true);
+ },
+ map$1$1: function(_, f, $T) {
+ var t1 = H._instanceType(this);
+ return new H.EfficientLengthMappedIterable(this, t1._bind$1($T)._eval$1("1(2)")._check(f), t1._eval$1("@<1>")._bind$1($T)._eval$1("EfficientLengthMappedIterable<1,2>"));
+ },
+ toString$0: function(_) {
+ return P.IterableBase_iterableToFullString(this, "{", "}");
+ },
+ where$1: function(_, f) {
+ var t1 = H._instanceType(this);
+ return new H.WhereIterable(this, t1._eval$1("bool(1)")._check(f), t1._eval$1("WhereIterable<1>"));
+ },
+ fold$1$2: function(_, initialValue, combine, $T) {
+ var t1, value;
+ $T._check(initialValue);
+ t1 = H._instanceType(this);
+ t1._bind$1($T)._eval$1("1(1,2)")._check(combine);
+ for (t1 = P._LinkedHashSetIterator$(this, this._collection$_modifications, t1._precomputed1), value = initialValue; t1.moveNext$0();)
+ value = combine.call$2(value, t1._collection$_current);
+ return value;
+ },
+ every$1: function(_, f) {
+ var t1 = H._instanceType(this);
+ t1._eval$1("bool(1)")._check(f);
+ for (t1 = P._LinkedHashSetIterator$(this, this._collection$_modifications, t1._precomputed1); t1.moveNext$0();)
+ if (!H.boolConversionCheck(f.call$1(t1._collection$_current)))
+ return false;
+ return true;
+ },
+ any$1: function(_, test) {
+ var t1 = H._instanceType(this);
+ t1._eval$1("bool(1)")._check(test);
+ for (t1 = P._LinkedHashSetIterator$(this, this._collection$_modifications, t1._precomputed1); t1.moveNext$0();)
+ if (H.boolConversionCheck(test.call$1(t1._collection$_current)))
+ return true;
+ return false;
+ },
+ get$first: function(_) {
+ var it = P._LinkedHashSetIterator$(this, this._collection$_modifications, H._instanceType(this)._precomputed1);
+ if (!it.moveNext$0())
+ throw H.wrapException(H.IterableElementError_noElement());
+ return it._collection$_current;
+ },
+ get$last: function(_) {
+ var result,
+ it = P._LinkedHashSetIterator$(this, this._collection$_modifications, H._instanceType(this)._precomputed1);
+ if (!it.moveNext$0())
+ throw H.wrapException(H.IterableElementError_noElement());
+ do
+ result = it._collection$_current;
+ while (it.moveNext$0());
+ return result;
+ },
+ $isEfficientLengthIterable: 1,
+ $isIterable: 1,
+ $isSet: 1
+ };
+ P._ListBase_Object_ListMixin.prototype = {};
+ P._SetBase_Object_SetMixin.prototype = {};
+ P._UnmodifiableMapView_MapView__UnmodifiableMapMixin.prototype = {};
+ P._JsonMap.prototype = {
+ $index: function(_, key) {
+ var result,
+ t1 = this._processed;
+ if (t1 == null)
+ return this.get$_upgradedMap().$index(0, key);
+ else if (typeof key != "string")
+ return null;
+ else {
+ result = t1[key];
+ return typeof result == "undefined" ? this._process$1(key) : result;
+ }
+ },
+ get$length: function(_) {
+ var t1;
+ if (this._processed == null) {
+ t1 = this.get$_upgradedMap();
+ t1 = t1.get$length(t1);
+ } else
+ t1 = this._convert$_computeKeys$0().length;
+ return t1;
+ },
+ get$isEmpty: function(_) {
+ return this.get$length(this) === 0;
+ },
+ get$keys: function() {
+ if (this._processed == null)
+ return this.get$_upgradedMap().get$keys();
+ return new P._JsonMapKeyIterable(this);
+ },
+ containsKey$1: function(key) {
+ if (this._processed == null)
+ return this.get$_upgradedMap().containsKey$1(key);
+ if (typeof key != "string")
+ return false;
+ return Object.prototype.hasOwnProperty.call(this._original, key);
+ },
+ remove$1: function(_, key) {
+ if (this._processed != null && !this.containsKey$1(key))
+ return null;
+ return this._upgrade$0().remove$1(0, key);
+ },
+ forEach$1: function(_, f) {
+ var keys, i, key, value, _this = this;
+ type$.void_Function_String_dynamic._check(f);
+ if (_this._processed == null)
+ return _this.get$_upgradedMap().forEach$1(0, f);
+ keys = _this._convert$_computeKeys$0();
+ for (i = 0; i < keys.length; ++i) {
+ key = keys[i];
+ value = _this._processed[key];
+ if (typeof value == "undefined") {
+ value = P._convertJsonToDartLazy(_this._original[key]);
+ _this._processed[key] = value;
+ }
+ f.call$2(key, value);
+ if (keys !== _this._data)
+ throw H.wrapException(P.ConcurrentModificationError$(_this));
+ }
+ },
+ get$_upgradedMap: function() {
+ H.assertHelper(this._processed == null);
+ return this._data;
+ },
+ _convert$_computeKeys$0: function() {
+ var keys, _this = this;
+ H.assertHelper(_this._processed != null);
+ keys = type$.List_dynamic._check(_this._data);
+ if (keys == null)
+ keys = _this._data = H.setRuntimeTypeInfo(Object.keys(_this._original), type$.JSArray_String);
+ return keys;
+ },
+ _upgrade$0: function() {
+ var result, keys, i, t1, key, _this = this;
+ if (_this._processed == null)
+ return _this.get$_upgradedMap();
+ result = P.LinkedHashMap_LinkedHashMap$_empty(type$.String, type$.dynamic);
+ keys = _this._convert$_computeKeys$0();
+ for (i = 0; t1 = keys.length, i < t1; ++i) {
+ key = keys[i];
+ result.$indexSet(0, key, _this.$index(0, key));
+ }
+ if (t1 === 0)
+ C.JSArray_methods.add$1(keys, null);
+ else
+ C.JSArray_methods.set$length(keys, 0);
+ _this._original = _this._processed = null;
+ return _this._data = result;
+ },
+ _process$1: function(key) {
+ var result;
+ if (!Object.prototype.hasOwnProperty.call(this._original, key))
+ return null;
+ result = P._convertJsonToDartLazy(this._original[key]);
+ return this._processed[key] = result;
+ }
+ };
+ P._JsonMapKeyIterable.prototype = {
+ get$length: function(_) {
+ var t1 = this._parent;
+ return t1.get$length(t1);
+ },
+ elementAt$1: function(_, index) {
+ var t1 = this._parent;
+ if (t1._processed == null)
+ t1 = t1.get$keys().elementAt$1(0, index);
+ else {
+ t1 = t1._convert$_computeKeys$0();
+ if (index < 0 || index >= t1.length)
+ return H.ioore(t1, index);
+ t1 = t1[index];
+ }
+ return t1;
+ },
+ get$iterator: function(_) {
+ var t1 = this._parent;
+ if (t1._processed == null) {
+ t1 = t1.get$keys();
+ t1 = t1.get$iterator(t1);
+ } else {
+ t1 = t1._convert$_computeKeys$0();
+ t1 = new J.ArrayIterator(t1, t1.length, H._arrayInstanceType(t1)._eval$1("ArrayIterator<1>"));
+ }
+ return t1;
+ },
+ contains$1: function(_, key) {
+ return this._parent.containsKey$1(key);
+ }
+ };
+ P.AsciiCodec.prototype = {
+ encode$1: function(source) {
+ return C.AsciiEncoder_127.convert$1(source);
+ }
+ };
+ P._UnicodeSubsetEncoder.prototype = {
+ convert$1: function(string) {
+ var $length, result, t1, t2, t3, i, codeUnit;
+ H._checkStringNullable(string);
+ $length = P.RangeError_checkValidRange(0, null, string.length) - 0;
+ result = new Uint8Array($length);
+ for (t1 = result.length, t2 = ~this._subsetMask, t3 = J.getInterceptor$s(string), i = 0; i < $length; ++i) {
+ codeUnit = t3._codeUnitAt$1(string, i);
+ if ((codeUnit & t2) !== 0)
+ throw H.wrapException(P.ArgumentError$value(string, "string", "Contains invalid characters."));
+ if (i >= t1)
+ return H.ioore(result, i);
+ result[i] = codeUnit;
+ }
+ return result;
+ }
+ };
+ P.AsciiEncoder.prototype = {};
+ P.Base64Codec.prototype = {
+ normalize$3: function(source, start, end) {
+ var inverseAlphabet, i, sliceStart, buffer, firstPadding, firstPaddingSourceIndex, paddingCount, i0, char, i1, digit1, digit2, char0, value, t2, endLength, $length,
+ _s31_ = "Invalid base64 encoding length ",
+ t1 = source.length;
+ end = P.RangeError_checkValidRange(start, end, t1);
+ inverseAlphabet = $.$get$_Base64Decoder__inverseAlphabet();
+ for (i = start, sliceStart = i, buffer = null, firstPadding = -1, firstPaddingSourceIndex = -1, paddingCount = 0; i < end; i = i0) {
+ i0 = i + 1;
+ char = C.JSString_methods._codeUnitAt$1(source, i);
+ if (char === 37) {
+ i1 = i0 + 2;
+ if (i1 <= end) {
+ H.assertHelper(i1 <= t1);
+ digit1 = H.hexDigitValue(C.JSString_methods._codeUnitAt$1(source, i0));
+ digit2 = H.hexDigitValue(C.JSString_methods._codeUnitAt$1(source, i0 + 1));
+ char0 = digit1 * 16 + digit2 - (digit2 & 256);
+ if (char0 === 37)
+ char0 = -1;
+ i0 = i1;
+ } else
+ char0 = -1;
+ } else
+ char0 = char;
+ if (0 <= char0 && char0 <= 127) {
+ if (char0 < 0 || char0 >= inverseAlphabet.length)
+ return H.ioore(inverseAlphabet, char0);
+ value = inverseAlphabet[char0];
+ if (value >= 0) {
+ char0 = C.JSString_methods.codeUnitAt$1("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", value);
+ if (char0 === char)
+ continue;
+ char = char0;
+ } else {
+ if (value === -1) {
+ if (firstPadding < 0) {
+ t2 = buffer == null ? null : buffer._contents.length;
+ if (t2 == null)
+ t2 = 0;
+ firstPadding = t2 + (i - sliceStart);
+ firstPaddingSourceIndex = i;
+ }
+ ++paddingCount;
+ if (char === 61)
+ continue;
+ }
+ char = char0;
+ }
+ if (value !== -2) {
+ if (buffer == null)
+ buffer = new P.StringBuffer("");
+ t2 = buffer._contents += C.JSString_methods.substring$2(source, sliceStart, i);
+ buffer._contents = t2 + H.Primitives_stringFromCharCode(char);
+ sliceStart = i0;
+ continue;
+ }
+ }
+ throw H.wrapException(P.FormatException$("Invalid base64 data", source, i));
+ }
+ if (buffer != null) {
+ t1 = buffer._contents += C.JSString_methods.substring$2(source, sliceStart, end);
+ t2 = t1.length;
+ if (firstPadding >= 0)
+ P.Base64Codec__checkPadding(source, firstPaddingSourceIndex, end, firstPadding, paddingCount, t2);
+ else {
+ endLength = C.JSInt_methods.$mod(t2 - 1, 4) + 1;
+ if (endLength === 1)
+ throw H.wrapException(P.FormatException$(_s31_, source, end));
+ for (; endLength < 4;) {
+ t1 += "=";
+ buffer._contents = t1;
+ ++endLength;
+ }
+ }
+ t1 = buffer._contents;
+ return C.JSString_methods.replaceRange$3(source, start, end, t1.charCodeAt(0) == 0 ? t1 : t1);
+ }
+ $length = end - start;
+ if (firstPadding >= 0)
+ P.Base64Codec__checkPadding(source, firstPaddingSourceIndex, end, firstPadding, paddingCount, $length);
+ else {
+ endLength = C.JSInt_methods.$mod($length, 4);
+ if (endLength === 1)
+ throw H.wrapException(P.FormatException$(_s31_, source, end));
+ if (endLength > 1)
+ source = C.JSString_methods.replaceRange$3(source, end, end, endLength === 2 ? "==" : "=");
+ }
+ return source;
+ }
+ };
+ P.Base64Encoder.prototype = {};
+ P.Codec.prototype = {};
+ P._FusedCodec.prototype = {};
+ P.Converter.prototype = {};
+ P.Encoding.prototype = {};
+ P.JsonCodec.prototype = {
+ decode$2$reviver: function(source, reviver) {
+ var t1;
+ type$.dynamic_Function_Object_Object._check(reviver);
+ t1 = P._parseJson(source, this.get$decoder()._reviver);
+ return t1;
+ },
+ get$decoder: function() {
+ return C.JsonDecoder_null;
+ }
+ };
+ P.JsonDecoder.prototype = {};
+ P.Utf8Codec.prototype = {
+ get$encoder: function() {
+ return C.C_Utf8Encoder;
+ }
+ };
+ P.Utf8Encoder.prototype = {
+ convert$1: function(string) {
+ var end, $length, t1, encoder, endPosition, t2, lastCodeUnit;
+ H._checkStringNullable(string);
+ end = P.RangeError_checkValidRange(0, null, string.length);
+ $length = end - 0;
+ if ($length === 0)
+ return new Uint8Array(0);
+ t1 = new Uint8Array($length * 3);
+ encoder = new P._Utf8Encoder(t1);
+ endPosition = encoder._fillBuffer$3(string, 0, end);
+ t2 = end - 1;
+ H.assertHelper(endPosition >= t2);
+ if (endPosition !== end) {
+ lastCodeUnit = J.codeUnitAt$1$s(string, t2);
+ H.assertHelper((lastCodeUnit & 64512) === 55296);
+ H.assertHelper(!encoder._writeSurrogate$2(lastCodeUnit, 0));
+ }
+ return C.NativeUint8List_methods.sublist$2(t1, 0, encoder._bufferIndex);
+ }
+ };
+ P._Utf8Encoder.prototype = {
+ _writeSurrogate$2: function(leadingSurrogate, nextCodeUnit) {
+ var rune, t3, t4, _this = this,
+ t1 = _this._buffer,
+ t2 = t1.length;
+ if ((nextCodeUnit & 64512) === 56320) {
+ rune = 65536 + ((leadingSurrogate & 1023) << 10) | nextCodeUnit & 1023;
+ H.assertHelper(rune > 65535);
+ H.assertHelper(rune <= 1114111);
+ t3 = _this._bufferIndex;
+ t4 = _this._bufferIndex = t3 + 1;
+ if (t3 >= t2)
+ return H.ioore(t1, t3);
+ t1[t3] = 240 | rune >>> 18;
+ t3 = _this._bufferIndex = t4 + 1;
+ if (t4 >= t2)
+ return H.ioore(t1, t4);
+ t1[t4] = 128 | rune >>> 12 & 63;
+ t4 = _this._bufferIndex = t3 + 1;
+ if (t3 >= t2)
+ return H.ioore(t1, t3);
+ t1[t3] = 128 | rune >>> 6 & 63;
+ _this._bufferIndex = t4 + 1;
+ if (t4 >= t2)
+ return H.ioore(t1, t4);
+ t1[t4] = 128 | rune & 63;
+ return true;
+ } else {
+ t3 = _this._bufferIndex;
+ t4 = _this._bufferIndex = t3 + 1;
+ if (t3 >= t2)
+ return H.ioore(t1, t3);
+ t1[t3] = 224 | leadingSurrogate >>> 12;
+ t3 = _this._bufferIndex = t4 + 1;
+ if (t4 >= t2)
+ return H.ioore(t1, t4);
+ t1[t4] = 128 | leadingSurrogate >>> 6 & 63;
+ _this._bufferIndex = t3 + 1;
+ if (t3 >= t2)
+ return H.ioore(t1, t3);
+ t1[t3] = 128 | leadingSurrogate & 63;
+ return false;
+ }
+ },
+ _fillBuffer$3: function(str, start, end) {
+ var t1, t2, t3, stringIndex, codeUnit, t4, stringIndex0, t5, _this = this;
+ if (start !== end && (J.codeUnitAt$1$s(str, end - 1) & 64512) === 55296)
+ --end;
+ for (t1 = _this._buffer, t2 = t1.length, t3 = J.getInterceptor$s(str), stringIndex = start; stringIndex < end; ++stringIndex) {
+ codeUnit = t3._codeUnitAt$1(str, stringIndex);
+ if (codeUnit <= 127) {
+ t4 = _this._bufferIndex;
+ if (t4 >= t2)
+ break;
+ _this._bufferIndex = t4 + 1;
+ t1[t4] = codeUnit;
+ } else if ((codeUnit & 64512) === 55296) {
+ if (_this._bufferIndex + 3 >= t2)
+ break;
+ stringIndex0 = stringIndex + 1;
+ if (_this._writeSurrogate$2(codeUnit, C.JSString_methods._codeUnitAt$1(str, stringIndex0)))
+ stringIndex = stringIndex0;
+ } else if (codeUnit <= 2047) {
+ t4 = _this._bufferIndex;
+ t5 = t4 + 1;
+ if (t5 >= t2)
+ break;
+ _this._bufferIndex = t5;
+ if (t4 >= t2)
+ return H.ioore(t1, t4);
+ t1[t4] = 192 | codeUnit >>> 6;
+ _this._bufferIndex = t5 + 1;
+ t1[t5] = 128 | codeUnit & 63;
+ } else {
+ H.assertHelper(codeUnit <= 65535);
+ t4 = _this._bufferIndex;
+ if (t4 + 2 >= t2)
+ break;
+ t5 = _this._bufferIndex = t4 + 1;
+ if (t4 >= t2)
+ return H.ioore(t1, t4);
+ t1[t4] = 224 | codeUnit >>> 12;
+ t4 = _this._bufferIndex = t5 + 1;
+ if (t5 >= t2)
+ return H.ioore(t1, t5);
+ t1[t5] = 128 | codeUnit >>> 6 & 63;
+ _this._bufferIndex = t4 + 1;
+ if (t4 >= t2)
+ return H.ioore(t1, t4);
+ t1[t4] = 128 | codeUnit & 63;
+ }
+ }
+ return stringIndex;
+ }
+ };
+ P.Utf8Decoder.prototype = {
+ convert$1: function(codeUnits) {
+ var result, end, oneBytes, firstPart, buffer, start, isFirstCharacter, decoder, t1;
+ type$.List_int._check(codeUnits);
+ result = P.Utf8Decoder__convertIntercepted(false, codeUnits, 0, null);
+ if (result != null)
+ return result;
+ end = P.RangeError_checkValidRange(0, null, J.get$length$asx(codeUnits));
+ oneBytes = P._scanOneByteCharacters(codeUnits, 0, end);
+ if (oneBytes > 0) {
+ firstPart = P.String_String$fromCharCodes(codeUnits, 0, oneBytes);
+ if (oneBytes === end)
+ return firstPart;
+ buffer = new P.StringBuffer(firstPart);
+ start = oneBytes;
+ isFirstCharacter = false;
+ } else {
+ start = 0;
+ buffer = null;
+ isFirstCharacter = true;
+ }
+ if (buffer == null)
+ buffer = new P.StringBuffer("");
+ decoder = new P._Utf8Decoder(false, buffer);
+ decoder._isFirstCharacter = isFirstCharacter;
+ decoder.convert$3(codeUnits, start, end);
+ decoder.flush$2(codeUnits, end);
+ t1 = buffer._contents;
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ }
+ };
+ P._Utf8Decoder.prototype = {
+ flush$2: function(source, offset) {
+ var t1;
+ type$.List_int._check(source);
+ if (this._expectedUnits > 0) {
+ t1 = P.FormatException$("Unfinished UTF-8 octet sequence", source, offset);
+ throw H.wrapException(t1);
+ }
+ },
+ convert$3: function(codeUnits, startIndex, endIndex) {
+ var value, expectedUnits, extraUnits, t1, t2, i, unit, t3, oneBytes, i0, i1, t4, _this = this,
+ _s21_ = "Bad UTF-8 encoding 0x";
+ type$.List_int._check(codeUnits);
+ value = _this._convert$_value;
+ expectedUnits = _this._expectedUnits;
+ extraUnits = _this._extraUnits;
+ _this._extraUnits = _this._expectedUnits = _this._convert$_value = 0;
+ $label0$0:
+ for (t1 = J.getInterceptor$asx(codeUnits), t2 = _this._stringSink, i = startIndex; true; i = i1) {
+ $label1$1:
+ if (expectedUnits > 0) {
+ do {
+ if (i === endIndex)
+ break $label0$0;
+ unit = t1.$index(codeUnits, i);
+ if (typeof unit !== "number")
+ return unit.$and();
+ if ((unit & 192) !== 128) {
+ t3 = P.FormatException$(_s21_ + C.JSInt_methods.toRadixString$1(unit, 16), codeUnits, i);
+ throw H.wrapException(t3);
+ } else {
+ value = (value << 6 | unit & 63) >>> 0;
+ --expectedUnits;
+ ++i;
+ }
+ } while (expectedUnits > 0);
+ t3 = extraUnits - 1;
+ if (t3 < 0 || t3 >= 4)
+ return H.ioore(C.List_127_2047_65535_1114111, t3);
+ if (value <= C.List_127_2047_65535_1114111[t3]) {
+ t3 = P.FormatException$("Overlong encoding of 0x" + C.JSInt_methods.toRadixString$1(value, 16), codeUnits, i - extraUnits - 1);
+ throw H.wrapException(t3);
+ }
+ if (value > 1114111) {
+ t3 = P.FormatException$("Character outside valid Unicode range: 0x" + C.JSInt_methods.toRadixString$1(value, 16), codeUnits, i - extraUnits - 1);
+ throw H.wrapException(t3);
+ }
+ if (!_this._isFirstCharacter || value !== 65279)
+ t2._contents += H.Primitives_stringFromCharCode(value);
+ _this._isFirstCharacter = false;
+ }
+ for (t3 = i < endIndex; t3;) {
+ oneBytes = P._scanOneByteCharacters(codeUnits, i, endIndex);
+ if (oneBytes > 0) {
+ _this._isFirstCharacter = false;
+ i0 = i + oneBytes;
+ H.assertHelper(i0 <= endIndex);
+ t2._contents += P.String_String$fromCharCodes(codeUnits, i, i0);
+ if (i0 === endIndex)
+ break;
+ } else
+ i0 = i;
+ i1 = i0 + 1;
+ unit = t1.$index(codeUnits, i0);
+ if (typeof unit !== "number")
+ return unit.$lt();
+ if (unit < 0) {
+ t4 = P.FormatException$("Negative UTF-8 code unit: -0x" + C.JSInt_methods.toRadixString$1(-unit, 16), codeUnits, i1 - 1);
+ throw H.wrapException(t4);
+ } else {
+ H.assertHelper(unit > 127);
+ if ((unit & 224) === 192) {
+ value = unit & 31;
+ expectedUnits = 1;
+ extraUnits = 1;
+ continue $label0$0;
+ }
+ if ((unit & 240) === 224) {
+ value = unit & 15;
+ expectedUnits = 2;
+ extraUnits = 2;
+ continue $label0$0;
+ }
+ if ((unit & 248) === 240 && unit < 245) {
+ value = unit & 7;
+ expectedUnits = 3;
+ extraUnits = 3;
+ continue $label0$0;
+ }
+ t4 = P.FormatException$(_s21_ + C.JSInt_methods.toRadixString$1(unit, 16), codeUnits, i1 - 1);
+ throw H.wrapException(t4);
+ }
+ }
+ break $label0$0;
+ }
+ if (expectedUnits > 0) {
+ _this._convert$_value = value;
+ _this._expectedUnits = expectedUnits;
+ _this._extraUnits = extraUnits;
+ }
+ }
+ };
+ P.bool.prototype = {};
+ P.DateTime.prototype = {
+ $eq: function(_, other) {
+ if (other == null)
+ return false;
+ return other instanceof P.DateTime && this._value === other._value && true;
+ },
+ get$hashCode: function(_) {
+ var t1 = this._value;
+ return (t1 ^ C.JSInt_methods._shrOtherPositive$1(t1, 30)) & 1073741823;
+ },
+ toString$0: function(_) {
+ var _this = this,
+ y = P.DateTime__fourDigits(H.Primitives_getYear(_this)),
+ m = P.DateTime__twoDigits(H.Primitives_getMonth(_this)),
+ d = P.DateTime__twoDigits(H.Primitives_getDay(_this)),
+ h = P.DateTime__twoDigits(H.Primitives_getHours(_this)),
+ min = P.DateTime__twoDigits(H.Primitives_getMinutes(_this)),
+ sec = P.DateTime__twoDigits(H.Primitives_getSeconds(_this)),
+ ms = P.DateTime__threeDigits(H.Primitives_getMilliseconds(_this)),
+ t1 = y + "-" + m + "-" + d + " " + h + ":" + min + ":" + sec + "." + ms + "Z";
+ return t1;
+ }
+ };
+ P.double.prototype = {};
+ P.Duration.prototype = {
+ $eq: function(_, other) {
+ if (other == null)
+ return false;
+ return other instanceof P.Duration && this._duration === other._duration;
+ },
+ get$hashCode: function(_) {
+ return C.JSInt_methods.get$hashCode(this._duration);
+ },
+ toString$0: function(_) {
+ var twoDigitMinutes, twoDigitSeconds, sixDigitUs,
+ t1 = new P.Duration_toString_twoDigits(),
+ t2 = this._duration;
+ if (t2 < 0)
+ return "-" + new P.Duration(0 - t2).toString$0(0);
+ twoDigitMinutes = t1.call$1(C.JSInt_methods._tdivFast$1(t2, 60000000) % 60);
+ twoDigitSeconds = t1.call$1(C.JSInt_methods._tdivFast$1(t2, 1000000) % 60);
+ sixDigitUs = new P.Duration_toString_sixDigits().call$1(t2 % 1000000);
+ return "" + C.JSInt_methods._tdivFast$1(t2, 3600000000) + ":" + H.S(twoDigitMinutes) + ":" + H.S(twoDigitSeconds) + "." + H.S(sixDigitUs);
+ }
+ };
+ P.Duration_toString_sixDigits.prototype = {
+ call$1: function(n) {
+ if (n >= 100000)
+ return "" + n;
+ if (n >= 10000)
+ return "0" + n;
+ if (n >= 1000)
+ return "00" + n;
+ if (n >= 100)
+ return "000" + n;
+ if (n >= 10)
+ return "0000" + n;
+ return "00000" + n;
+ },
+ $signature: 25
+ };
+ P.Duration_toString_twoDigits.prototype = {
+ call$1: function(n) {
+ if (n >= 10)
+ return "" + n;
+ return "0" + n;
+ },
+ $signature: 25
+ };
+ P.Error.prototype = {};
+ P.AssertionError.prototype = {
+ toString$0: function(_) {
+ var t1 = this.message;
+ if (t1 != null)
+ return "Assertion failed: " + P.Error_safeToString(t1);
+ return "Assertion failed";
+ },
+ get$message: function(receiver) {
+ return this.message;
+ }
+ };
+ P.NullThrownError.prototype = {
+ toString$0: function(_) {
+ return "Throw of null.";
+ }
+ };
+ P.ArgumentError.prototype = {
+ get$_errorName: function() {
+ return "Invalid argument" + (!this._hasValue ? "(s)" : "");
+ },
+ get$_errorExplanation: function() {
+ return "";
+ },
+ toString$0: function(_) {
+ var message, prefix, explanation, errorValue, _this = this,
+ t1 = _this.name,
+ nameString = t1 != null ? " (" + t1 + ")" : "";
+ t1 = _this.message;
+ message = t1 == null ? "" : ": " + H.S(t1);
+ prefix = _this.get$_errorName() + nameString + message;
+ if (!_this._hasValue)
+ return prefix;
+ explanation = _this.get$_errorExplanation();
+ errorValue = P.Error_safeToString(_this.invalidValue);
+ return prefix + explanation + ": " + errorValue;
+ },
+ get$message: function(receiver) {
+ return this.message;
+ }
+ };
+ P.RangeError.prototype = {
+ get$_errorName: function() {
+ return "RangeError";
+ },
+ get$_errorExplanation: function() {
+ var t1, explanation, t2, _this = this;
+ H.assertHelper(_this._hasValue);
+ t1 = _this.start;
+ if (t1 == null) {
+ t1 = _this.end;
+ explanation = t1 != null ? ": Not less than or equal to " + H.S(t1) : "";
+ } else {
+ t2 = _this.end;
+ if (t2 == null)
+ explanation = ": Not greater than or equal to " + H.S(t1);
+ else if (t2 > t1)
+ explanation = ": Not in range " + H.S(t1) + ".." + H.S(t2) + ", inclusive";
+ else
+ explanation = t2 < t1 ? ": Valid value range is empty" : ": Only valid value is " + H.S(t1);
+ }
+ return explanation;
+ }
+ };
+ P.IndexError.prototype = {
+ get$_errorName: function() {
+ return "RangeError";
+ },
+ get$_errorExplanation: function() {
+ var invalidValue, t1;
+ H.assertHelper(this._hasValue);
+ invalidValue = H._checkIntNullable(this.invalidValue);
+ if (typeof invalidValue !== "number")
+ return invalidValue.$lt();
+ if (invalidValue < 0)
+ return ": index must not be negative";
+ t1 = this.length;
+ if (t1 === 0)
+ return ": no indices are valid";
+ return ": index should be less than " + H.S(t1);
+ },
+ get$length: function(receiver) {
+ return this.length;
+ }
+ };
+ P.UnsupportedError.prototype = {
+ toString$0: function(_) {
+ return "Unsupported operation: " + this.message;
+ },
+ get$message: function(receiver) {
+ return this.message;
+ }
+ };
+ P.UnimplementedError.prototype = {
+ toString$0: function(_) {
+ var t1 = this.message;
+ return t1 != null ? "UnimplementedError: " + t1 : "UnimplementedError";
+ },
+ get$message: function(receiver) {
+ return this.message;
+ }
+ };
+ P.StateError.prototype = {
+ toString$0: function(_) {
+ return "Bad state: " + this.message;
+ },
+ get$message: function(receiver) {
+ return this.message;
+ }
+ };
+ P.ConcurrentModificationError.prototype = {
+ toString$0: function(_) {
+ var t1 = this.modifiedObject;
+ if (t1 == null)
+ return "Concurrent modification during iteration.";
+ return "Concurrent modification during iteration: " + P.Error_safeToString(t1) + ".";
+ }
+ };
+ P.OutOfMemoryError.prototype = {
+ toString$0: function(_) {
+ return "Out of Memory";
+ },
+ $isError: 1
+ };
+ P.StackOverflowError.prototype = {
+ toString$0: function(_) {
+ return "Stack Overflow";
+ },
+ $isError: 1
+ };
+ P.CyclicInitializationError.prototype = {
+ toString$0: function(_) {
+ var t1 = this.variableName;
+ return t1 == null ? "Reading static variable during its initialization" : "Reading static variable '" + t1 + "' during its initialization";
+ }
+ };
+ P._Exception.prototype = {
+ toString$0: function(_) {
+ return "Exception: " + this.message;
+ },
+ $isException: 1,
+ get$message: function(receiver) {
+ return this.message;
+ }
+ };
+ P.FormatException.prototype = {
+ toString$0: function(_) {
+ var source, lineNum, lineStart, previousCharWasCR, i, char, lineEnd, end, start, prefix, postfix, slice,
+ t1 = this.message,
+ report = t1 != null && "" !== t1 ? "FormatException: " + H.S(t1) : "FormatException",
+ offset = this.offset,
+ objectSource = this.source;
+ if (typeof objectSource == "string") {
+ if (offset != null)
+ t1 = offset < 0 || offset > objectSource.length;
+ else
+ t1 = false;
+ if (t1)
+ offset = null;
+ if (offset == null) {
+ source = objectSource.length > 78 ? C.JSString_methods.substring$2(objectSource, 0, 75) + "..." : objectSource;
+ return report + "\n" + source;
+ }
+ for (lineNum = 1, lineStart = 0, previousCharWasCR = false, i = 0; i < offset; ++i) {
+ char = C.JSString_methods._codeUnitAt$1(objectSource, i);
+ if (char === 10) {
+ if (lineStart !== i || !previousCharWasCR)
+ ++lineNum;
+ lineStart = i + 1;
+ previousCharWasCR = false;
+ } else if (char === 13) {
+ ++lineNum;
+ lineStart = i + 1;
+ previousCharWasCR = true;
+ }
+ }
+ report = lineNum > 1 ? report + (" (at line " + lineNum + ", character " + (offset - lineStart + 1) + ")\n") : report + (" (at character " + (offset + 1) + ")\n");
+ lineEnd = objectSource.length;
+ for (i = offset; i < lineEnd; ++i) {
+ char = C.JSString_methods.codeUnitAt$1(objectSource, i);
+ if (char === 10 || char === 13) {
+ lineEnd = i;
+ break;
+ }
+ }
+ if (lineEnd - lineStart > 78)
+ if (offset - lineStart < 75) {
+ end = lineStart + 75;
+ start = lineStart;
+ prefix = "";
+ postfix = "...";
+ } else {
+ if (lineEnd - offset < 75) {
+ start = lineEnd - 75;
+ end = lineEnd;
+ postfix = "";
+ } else {
+ start = offset - 36;
+ end = offset + 36;
+ postfix = "...";
+ }
+ prefix = "...";
+ }
+ else {
+ end = lineEnd;
+ start = lineStart;
+ prefix = "";
+ postfix = "";
+ }
+ slice = C.JSString_methods.substring$2(objectSource, start, end);
+ return report + prefix + slice + postfix + "\n" + C.JSString_methods.$mul(" ", offset - start + prefix.length) + "^\n";
+ } else
+ return offset != null ? report + (" (at offset " + H.S(offset) + ")") : report;
+ },
+ $isException: 1,
+ get$message: function(receiver) {
+ return this.message;
+ }
+ };
+ P.Expando.prototype = {
+ $index: function(_, object) {
+ var t2, values,
+ t1 = this._jsWeakMapOrKey;
+ if (typeof t1 != "string") {
+ if (object != null)
+ t2 = typeof object == "number" || typeof object == "string";
+ else
+ t2 = true;
+ if (t2)
+ H.throwExpression(P.ArgumentError$value(object, "Expandos are not allowed on strings, numbers, booleans or null", null));
+ return t1.get(object);
+ }
+ values = H.Primitives_getProperty(object, "expando$values");
+ t1 = values == null ? null : H.Primitives_getProperty(values, t1);
+ return this.$ti._precomputed1._check(t1);
+ },
+ $indexSet: function(_, object, value) {
+ var t1, values,
+ _s14_ = "expando$values";
+ this.$ti._precomputed1._check(value);
+ t1 = this._jsWeakMapOrKey;
+ if (typeof t1 != "string")
+ t1.set(object, value);
+ else {
+ values = H.Primitives_getProperty(object, _s14_);
+ if (values == null) {
+ values = new P.Object();
+ H.Primitives_setProperty(object, _s14_, values);
+ }
+ H.Primitives_setProperty(values, t1, value);
+ }
+ },
+ toString$0: function(_) {
+ return "Expando:" + this.name;
+ }
+ };
+ P.Function.prototype = {};
+ P.int.prototype = {};
+ P.Iterable.prototype = {
+ map$1$1: function(_, f, $T) {
+ var t1 = H._instanceType(this);
+ return H.MappedIterable_MappedIterable(this, t1._bind$1($T)._eval$1("1(Iterable.E)")._check(f), t1._eval$1("Iterable.E"), $T);
+ },
+ where$1: function(_, test) {
+ var t1 = H._instanceType(this);
+ return new H.WhereIterable(this, t1._eval$1("bool(Iterable.E)")._check(test), t1._eval$1("WhereIterable<Iterable.E>"));
+ },
+ contains$1: function(_, element) {
+ var t1;
+ for (t1 = this.get$iterator(this); t1.moveNext$0();)
+ if (J.$eq$(t1.get$current(), element))
+ return true;
+ return false;
+ },
+ join$1: function(_, separator) {
+ var t1,
+ iterator = this.get$iterator(this);
+ if (!iterator.moveNext$0())
+ return "";
+ if (separator === "") {
+ t1 = "";
+ do
+ t1 += H.S(iterator.get$current());
+ while (iterator.moveNext$0());
+ } else {
+ t1 = H.S(iterator.get$current());
+ for (; iterator.moveNext$0();)
+ t1 = t1 + separator + H.S(iterator.get$current());
+ }
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ join$0: function($receiver) {
+ return this.join$1($receiver, "");
+ },
+ toList$1$growable: function(_, growable) {
+ return P.List_List$from(this, true, H._instanceType(this)._eval$1("Iterable.E"));
+ },
+ toList$0: function($receiver) {
+ return this.toList$1$growable($receiver, true);
+ },
+ toSet$0: function(_) {
+ return P.LinkedHashSet_LinkedHashSet$from(this, H._instanceType(this)._eval$1("Iterable.E"));
+ },
+ get$length: function(_) {
+ var it, count;
+ H.assertHelper(!type$.EfficientLengthIterable_dynamic._is(this));
+ it = this.get$iterator(this);
+ for (count = 0; it.moveNext$0();)
+ ++count;
+ return count;
+ },
+ get$isEmpty: function(_) {
+ return !this.get$iterator(this).moveNext$0();
+ },
+ skipWhile$1: function(_, test) {
+ var t1 = H._instanceType(this);
+ return new H.SkipWhileIterable(this, t1._eval$1("bool(Iterable.E)")._check(test), t1._eval$1("SkipWhileIterable<Iterable.E>"));
+ },
+ get$first: function(_) {
+ var it = this.get$iterator(this);
+ if (!it.moveNext$0())
+ throw H.wrapException(H.IterableElementError_noElement());
+ return it.get$current();
+ },
+ get$last: function(_) {
+ var result,
+ it = this.get$iterator(this);
+ if (!it.moveNext$0())
+ throw H.wrapException(H.IterableElementError_noElement());
+ do
+ result = it.get$current();
+ while (it.moveNext$0());
+ return result;
+ },
+ elementAt$1: function(_, index) {
+ var t1, elementIndex, element;
+ P.RangeError_checkNotNegative(index, "index");
+ for (t1 = this.get$iterator(this), elementIndex = 0; t1.moveNext$0();) {
+ element = t1.get$current();
+ if (index === elementIndex)
+ return element;
+ ++elementIndex;
+ }
+ throw H.wrapException(P.IndexError$(index, this, "index", null, elementIndex));
+ },
+ toString$0: function(_) {
+ return P.IterableBase_iterableToShortString(this, "(", ")");
+ }
+ };
+ P.Iterator.prototype = {};
+ P.List.prototype = {$isEfficientLengthIterable: 1, $isIterable: 1};
+ P.Map.prototype = {};
+ P.MapEntry.prototype = {
+ toString$0: function(_) {
+ return "MapEntry(" + H.S(this.key) + ": " + H.S(this.value) + ")";
+ }
+ };
+ P.Null.prototype = {
+ get$hashCode: function(_) {
+ return P.Object.prototype.get$hashCode.call(this, this);
+ },
+ toString$0: function(_) {
+ return "null";
+ }
+ };
+ P.num.prototype = {};
+ P.Object.prototype = {constructor: P.Object, $isObject: 1,
+ $eq: function(_, other) {
+ return this === other;
+ },
+ get$hashCode: function(_) {
+ return H.Primitives_objectHashCode(this);
+ },
+ toString$0: function(_) {
+ return "Instance of '" + H.S(H.Primitives_objectTypeName(this)) + "'";
+ },
+ get$runtimeType: function(_) {
+ return H.getRuntimeType(this);
+ },
+ toString: function() {
+ return this.toString$0(this);
+ }
+ };
+ P.Pattern.prototype = {};
+ P.Match.prototype = {};
+ P.RegExpMatch.prototype = {$isMatch: 1};
+ P.Set.prototype = {};
+ P.StackTrace.prototype = {};
+ P._StringStackTrace.prototype = {
+ toString$0: function(_) {
+ return this._stackTrace;
+ },
+ $isStackTrace: 1
+ };
+ P.Stopwatch.prototype = {
+ get$elapsedMicroseconds: function() {
+ var t2, ticks,
+ t1 = this._stop;
+ if (t1 == null)
+ t1 = H._checkIntNullable($.Primitives_timerTicks.call$0());
+ t2 = this._core$_start;
+ if (typeof t1 !== "number")
+ return t1.$sub();
+ ticks = t1 - t2;
+ t1 = $.Stopwatch__frequency;
+ if (t1 === 1000000)
+ return ticks;
+ H.assertHelper(t1 === 1000);
+ return ticks * 1000;
+ }
+ };
+ P.String.prototype = {$isPattern: 1};
+ P.Runes.prototype = {
+ get$iterator: function(_) {
+ return new P.RuneIterator(this.string);
+ },
+ get$last: function(_) {
+ var code, previousCode,
+ t1 = this.string,
+ t2 = t1.length;
+ if (t2 === 0)
+ throw H.wrapException(P.StateError$("No elements."));
+ code = C.JSString_methods.codeUnitAt$1(t1, t2 - 1);
+ if ((code & 64512) === 56320 && t2 > 1) {
+ previousCode = C.JSString_methods.codeUnitAt$1(t1, t2 - 2);
+ if ((previousCode & 64512) === 55296)
+ return P._combineSurrogatePair(previousCode, code);
+ }
+ return code;
+ }
+ };
+ P.RuneIterator.prototype = {
+ get$current: function() {
+ return this._currentCodePoint;
+ },
+ moveNext$0: function() {
+ var codeUnit, nextPosition, nextCodeUnit, _this = this,
+ t1 = _this._core$_position = _this._nextPosition,
+ t2 = _this.string,
+ t3 = t2.length;
+ if (t1 === t3) {
+ _this._currentCodePoint = null;
+ return false;
+ }
+ codeUnit = C.JSString_methods._codeUnitAt$1(t2, t1);
+ nextPosition = t1 + 1;
+ if ((codeUnit & 64512) === 55296 && nextPosition < t3) {
+ nextCodeUnit = C.JSString_methods._codeUnitAt$1(t2, nextPosition);
+ if ((nextCodeUnit & 64512) === 56320) {
+ _this._nextPosition = nextPosition + 1;
+ _this._currentCodePoint = P._combineSurrogatePair(codeUnit, nextCodeUnit);
+ return true;
+ }
+ }
+ _this._nextPosition = nextPosition;
+ _this._currentCodePoint = codeUnit;
+ return true;
+ },
+ $isIterator: 1
+ };
+ P.StringBuffer.prototype = {
+ get$length: function(_) {
+ return this._contents.length;
+ },
+ toString$0: function(_) {
+ var t1 = this._contents;
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ $isStringSink: 1
+ };
+ P.Uri.prototype = {};
+ P.Uri__parseIPv4Address_error.prototype = {
+ call$2: function(msg, position) {
+ throw H.wrapException(P.FormatException$("Illegal IPv4 address, " + msg, this.host, position));
+ },
+ $signature: 68
+ };
+ P.Uri_parseIPv6Address_error.prototype = {
+ call$2: function(msg, position) {
+ throw H.wrapException(P.FormatException$("Illegal IPv6 address, " + msg, this.host, position));
+ },
+ call$1: function(msg) {
+ return this.call$2(msg, null);
+ },
+ $signature: 67
+ };
+ P.Uri_parseIPv6Address_parseHex.prototype = {
+ call$2: function(start, end) {
+ var value;
+ if (end - start > 4)
+ this.error.call$2("an IPv6 part can only contain a maximum of 4 hex digits", start);
+ value = P.int_parse(C.JSString_methods.substring$2(this.host, start, end), null, 16);
+ if (typeof value !== "number")
+ return value.$lt();
+ if (value < 0 || value > 65535)
+ this.error.call$2("each part must be in the range of `0x0..0xFFFF`", start);
+ return value;
+ },
+ $signature: 65
+ };
+ P._Uri.prototype = {
+ get$userInfo: function() {
+ return this._userInfo;
+ },
+ get$host: function(_) {
+ var t1 = this._host;
+ if (t1 == null)
+ return "";
+ if (C.JSString_methods.startsWith$1(t1, "["))
+ return C.JSString_methods.substring$2(t1, 1, t1.length - 1);
+ return t1;
+ },
+ get$port: function(_) {
+ var t1 = this._port;
+ if (t1 == null)
+ return P._Uri__defaultPort(this.scheme);
+ return t1;
+ },
+ get$query: function() {
+ var t1 = this._query;
+ return t1 == null ? "" : t1;
+ },
+ get$fragment: function() {
+ var t1 = this._fragment;
+ return t1 == null ? "" : t1;
+ },
+ replace$2$path$pathSegments: function(_, path, pathSegments) {
+ var scheme, isFile, userInfo, port, host, hasAuthority, t1, _this = this;
+ type$.Iterable_String._check(pathSegments);
+ type$.Map_String_dynamic._check(null);
+ scheme = _this.scheme;
+ isFile = scheme === "file";
+ userInfo = _this._userInfo;
+ port = _this._port;
+ host = _this._host;
+ if (!(host != null))
+ host = userInfo.length !== 0 || port != null || isFile ? "" : null;
+ hasAuthority = host != null;
+ t1 = path == null;
+ if (!t1 || pathSegments != null)
+ path = P._Uri__makePath(path, 0, t1 ? 0 : path.length, pathSegments, scheme, hasAuthority);
+ else {
+ path = _this.path;
+ if (!isFile)
+ t1 = hasAuthority && path.length !== 0;
+ else
+ t1 = true;
+ if (t1 && !C.JSString_methods.startsWith$1(path, "/"))
+ path = "/" + path;
+ }
+ return new P._Uri(scheme, userInfo, host, port, path, _this._query, _this._fragment);
+ },
+ replace$1$path: function($receiver, path) {
+ return this.replace$2$path$pathSegments($receiver, path, null);
+ },
+ replace$1$pathSegments: function($receiver, pathSegments) {
+ return this.replace$2$path$pathSegments($receiver, null, pathSegments);
+ },
+ get$pathSegments: function() {
+ var pathToSplit,
+ result = this._pathSegments;
+ if (result != null)
+ return result;
+ pathToSplit = this.path;
+ if (pathToSplit.length !== 0 && C.JSString_methods._codeUnitAt$1(pathToSplit, 0) === 47)
+ pathToSplit = C.JSString_methods.substring$1(pathToSplit, 1);
+ result = pathToSplit === "" ? C.List_empty : P.List_List$unmodifiable(new H.MappedListIterable(H.setRuntimeTypeInfo(pathToSplit.split("/"), type$.JSArray_String), type$.dynamic_Function_String._check(P.core_Uri_decodeComponent$closure()), type$.MappedListIterable_String_dynamic), type$.String);
+ this.set$_pathSegments(result);
+ return result;
+ },
+ _mergePaths$2: function(base, reference) {
+ var backCount, refStart, baseEnd, newEnd, delta, t1;
+ for (backCount = 0, refStart = 0; C.JSString_methods.startsWith$2(reference, "../", refStart);) {
+ refStart += 3;
+ ++backCount;
+ }
+ baseEnd = C.JSString_methods.lastIndexOf$1(base, "/");
+ while (true) {
+ if (!(baseEnd > 0 && backCount > 0))
+ break;
+ newEnd = C.JSString_methods.lastIndexOf$2(base, "/", baseEnd - 1);
+ if (newEnd < 0)
+ break;
+ delta = baseEnd - newEnd;
+ t1 = delta !== 2;
+ if (!t1 || delta === 3)
+ if (C.JSString_methods.codeUnitAt$1(base, newEnd + 1) === 46)
+ t1 = !t1 || C.JSString_methods.codeUnitAt$1(base, newEnd + 2) === 46;
+ else
+ t1 = false;
+ else
+ t1 = false;
+ if (t1)
+ break;
+ --backCount;
+ baseEnd = newEnd;
+ }
+ return C.JSString_methods.replaceRange$3(base, baseEnd + 1, null, C.JSString_methods.substring$1(reference, refStart - 3 * backCount));
+ },
+ resolve$1: function(reference) {
+ return this.resolveUri$1(P.Uri_parse(reference));
+ },
+ resolveUri$1: function(reference) {
+ var targetScheme, targetUserInfo, targetHost, targetPort, targetPath, targetQuery, t1, mergedPath, t2, _this = this, _null = null;
+ if (reference.get$scheme().length !== 0) {
+ targetScheme = reference.get$scheme();
+ if (reference.get$hasAuthority()) {
+ targetUserInfo = reference.get$userInfo();
+ targetHost = reference.get$host(reference);
+ targetPort = reference.get$hasPort() ? reference.get$port(reference) : _null;
+ } else {
+ targetPort = _null;
+ targetHost = targetPort;
+ targetUserInfo = "";
+ }
+ targetPath = P._Uri__removeDotSegments(reference.get$path(reference));
+ targetQuery = reference.get$hasQuery() ? reference.get$query() : _null;
+ } else {
+ targetScheme = _this.scheme;
+ if (reference.get$hasAuthority()) {
+ targetUserInfo = reference.get$userInfo();
+ targetHost = reference.get$host(reference);
+ targetPort = P._Uri__makePort(reference.get$hasPort() ? reference.get$port(reference) : _null, targetScheme);
+ targetPath = P._Uri__removeDotSegments(reference.get$path(reference));
+ targetQuery = reference.get$hasQuery() ? reference.get$query() : _null;
+ } else {
+ targetUserInfo = _this._userInfo;
+ targetHost = _this._host;
+ targetPort = _this._port;
+ if (reference.get$path(reference) === "") {
+ targetPath = _this.path;
+ targetQuery = reference.get$hasQuery() ? reference.get$query() : _this._query;
+ } else {
+ if (reference.get$hasAbsolutePath())
+ targetPath = P._Uri__removeDotSegments(reference.get$path(reference));
+ else {
+ t1 = _this.path;
+ if (t1.length === 0)
+ if (targetHost == null)
+ targetPath = targetScheme.length === 0 ? reference.get$path(reference) : P._Uri__removeDotSegments(reference.get$path(reference));
+ else
+ targetPath = P._Uri__removeDotSegments("/" + reference.get$path(reference));
+ else {
+ mergedPath = _this._mergePaths$2(t1, reference.get$path(reference));
+ t2 = targetScheme.length === 0;
+ if (!t2 || targetHost != null || C.JSString_methods.startsWith$1(t1, "/"))
+ targetPath = P._Uri__removeDotSegments(mergedPath);
+ else
+ targetPath = P._Uri__normalizeRelativePath(mergedPath, !t2 || targetHost != null);
+ }
+ }
+ targetQuery = reference.get$hasQuery() ? reference.get$query() : _null;
+ }
+ }
+ }
+ return new P._Uri(targetScheme, targetUserInfo, targetHost, targetPort, targetPath, targetQuery, reference.get$hasFragment() ? reference.get$fragment() : _null);
+ },
+ get$hasAuthority: function() {
+ return this._host != null;
+ },
+ get$hasPort: function() {
+ return this._port != null;
+ },
+ get$hasQuery: function() {
+ return this._query != null;
+ },
+ get$hasFragment: function() {
+ return this._fragment != null;
+ },
+ get$hasAbsolutePath: function() {
+ return C.JSString_methods.startsWith$1(this.path, "/");
+ },
+ toFilePath$0: function() {
+ var windows, pathSegments, _this = this,
+ t1 = _this.scheme;
+ if (t1 !== "" && t1 !== "file")
+ throw H.wrapException(P.UnsupportedError$("Cannot extract a file path from a " + H.S(t1) + " URI"));
+ t1 = _this._query;
+ if ((t1 == null ? "" : t1) !== "")
+ throw H.wrapException(P.UnsupportedError$("Cannot extract a file path from a URI with a query component"));
+ t1 = _this._fragment;
+ if ((t1 == null ? "" : t1) !== "")
+ throw H.wrapException(P.UnsupportedError$("Cannot extract a file path from a URI with a fragment component"));
+ windows = $.$get$_Uri__isWindowsCached();
+ if (H.boolConversionCheck(windows))
+ t1 = P._Uri__toWindowsFilePath(_this);
+ else {
+ if (_this._host != null && _this.get$host(_this) !== "")
+ H.throwExpression(P.UnsupportedError$("Cannot extract a non-Windows file path from a file URI with an authority"));
+ pathSegments = _this.get$pathSegments();
+ P._Uri__checkNonWindowsPathReservedCharacters(pathSegments, false);
+ t1 = P.StringBuffer__writeAll(C.JSString_methods.startsWith$1(_this.path, "/") ? "/" : "", pathSegments, "/");
+ t1 = t1.charCodeAt(0) == 0 ? t1 : t1;
+ }
+ return t1;
+ },
+ toString$0: function(_) {
+ var t2, t3, t4, _this = this,
+ t1 = _this._text;
+ if (t1 == null) {
+ t1 = _this.scheme;
+ t2 = t1.length !== 0 ? t1 + ":" : "";
+ t3 = _this._host;
+ t4 = t3 == null;
+ if (!t4 || t1 === "file") {
+ t1 = t2 + "//";
+ t2 = _this._userInfo;
+ if (t2.length !== 0)
+ t1 = t1 + t2 + "@";
+ if (!t4)
+ t1 += t3;
+ t2 = _this._port;
+ if (t2 != null)
+ t1 = t1 + ":" + H.S(t2);
+ } else
+ t1 = t2;
+ t1 += _this.path;
+ t2 = _this._query;
+ if (t2 != null)
+ t1 = t1 + "?" + t2;
+ t2 = _this._fragment;
+ if (t2 != null)
+ t1 = t1 + "#" + t2;
+ t1 = _this._text = t1.charCodeAt(0) == 0 ? t1 : t1;
+ }
+ return t1;
+ },
+ $eq: function(_, other) {
+ var t1, t2, _this = this;
+ if (other == null)
+ return false;
+ if (_this === other)
+ return true;
+ if (type$.Uri._is(other))
+ if (_this.scheme == other.get$scheme())
+ if (_this._host != null === other.get$hasAuthority())
+ if (_this._userInfo == other.get$userInfo())
+ if (_this.get$host(_this) == other.get$host(other))
+ if (_this.get$port(_this) == other.get$port(other))
+ if (_this.path === other.get$path(other)) {
+ t1 = _this._query;
+ t2 = t1 == null;
+ if (!t2 === other.get$hasQuery()) {
+ if (t2)
+ t1 = "";
+ if (t1 === other.get$query()) {
+ t1 = _this._fragment;
+ t2 = t1 == null;
+ if (!t2 === other.get$hasFragment()) {
+ if (t2)
+ t1 = "";
+ t1 = t1 === other.get$fragment();
+ } else
+ t1 = false;
+ } else
+ t1 = false;
+ } else
+ t1 = false;
+ } else
+ t1 = false;
+ else
+ t1 = false;
+ else
+ t1 = false;
+ else
+ t1 = false;
+ else
+ t1 = false;
+ else
+ t1 = false;
+ else
+ t1 = false;
+ return t1;
+ },
+ get$hashCode: function(_) {
+ var t1 = this._hashCodeCache;
+ return t1 == null ? this._hashCodeCache = C.JSString_methods.get$hashCode(this.toString$0(0)) : t1;
+ },
+ set$_pathSegments: function(_pathSegments) {
+ this._pathSegments = type$.List_String._check(_pathSegments);
+ },
+ $isUri: 1,
+ get$scheme: function() {
+ return this.scheme;
+ },
+ get$path: function(receiver) {
+ return this.path;
+ }
+ };
+ P._Uri__Uri$notSimple_closure.prototype = {
+ call$1: function(_) {
+ throw H.wrapException(P.FormatException$("Invalid port", this.uri, this.portStart + 1));
+ },
+ $signature: 30
+ };
+ P._Uri__checkNonWindowsPathReservedCharacters_closure.prototype = {
+ call$1: function(segment) {
+ var _s23_ = "Illegal path character ";
+ H._checkStringNullable(segment);
+ if (J.contains$1$asx(segment, "/"))
+ if (this.argumentError)
+ throw H.wrapException(P.ArgumentError$(_s23_ + segment));
+ else
+ throw H.wrapException(P.UnsupportedError$(_s23_ + segment));
+ },
+ $signature: 30
+ };
+ P._Uri__makePath_closure.prototype = {
+ call$1: function(s) {
+ return P._Uri__uriEncode(C.List_qg40, H._checkStringNullable(s), C.C_Utf8Codec, false);
+ },
+ $signature: 7
+ };
+ P.UriData.prototype = {
+ get$uri: function() {
+ var t2, queryIndex, end, query, _this = this, _null = null,
+ t1 = _this._uriCache;
+ if (t1 != null)
+ return t1;
+ t1 = _this._separatorIndices;
+ if (0 >= t1.length)
+ return H.ioore(t1, 0);
+ t2 = _this._text;
+ t1 = t1[0] + 1;
+ queryIndex = C.JSString_methods.indexOf$2(t2, "?", t1);
+ end = t2.length;
+ if (queryIndex >= 0) {
+ query = P._Uri__normalizeOrSubstring(t2, queryIndex + 1, end, C.List_CVk, false);
+ end = queryIndex;
+ } else
+ query = _null;
+ return _this._uriCache = new P._DataUri("data", _null, _null, _null, P._Uri__normalizeOrSubstring(t2, t1, end, C.List_qg4, false), query, _null);
+ },
+ toString$0: function(_) {
+ var t2,
+ t1 = this._separatorIndices;
+ if (0 >= t1.length)
+ return H.ioore(t1, 0);
+ t2 = this._text;
+ return t1[0] === -1 ? "data:" + t2 : t2;
+ }
+ };
+ P._createTables_closure.prototype = {
+ call$1: function(_) {
+ return new Uint8Array(96);
+ },
+ $signature: 64
+ };
+ P._createTables_build.prototype = {
+ call$2: function(state, defaultTransition) {
+ var t1 = this.tables;
+ if (state >= t1.length)
+ return H.ioore(t1, state);
+ t1 = t1[state];
+ J.fillRange$3$ax(t1, 0, 96, defaultTransition);
+ return t1;
+ },
+ $signature: 62
+ };
+ P._createTables_setChars.prototype = {
+ call$3: function(target, chars, transition) {
+ var t1, t2, i, t3;
+ for (t1 = chars.length, t2 = target.length, i = 0; i < t1; ++i) {
+ t3 = C.JSString_methods._codeUnitAt$1(chars, i) ^ 96;
+ if (t3 >= t2)
+ return H.ioore(target, t3);
+ target[t3] = transition;
+ }
+ },
+ $signature: 41
+ };
+ P._createTables_setRange.prototype = {
+ call$3: function(target, range, transition) {
+ var i, n, t1, t2;
+ for (i = C.JSString_methods._codeUnitAt$1(range, 0), n = C.JSString_methods._codeUnitAt$1(range, 1), t1 = target.length; i <= n; ++i) {
+ t2 = (i ^ 96) >>> 0;
+ if (t2 >= t1)
+ return H.ioore(target, t2);
+ target[t2] = transition;
+ }
+ },
+ $signature: 41
+ };
+ P._SimpleUri.prototype = {
+ get$hasAuthority: function() {
+ return this._hostStart > 0;
+ },
+ get$hasPort: function() {
+ return this._hostStart > 0 && this._portStart + 1 < this._pathStart;
+ },
+ get$hasQuery: function() {
+ return this._queryStart < this._fragmentStart;
+ },
+ get$hasFragment: function() {
+ return this._fragmentStart < this._uri.length;
+ },
+ get$_isFile: function() {
+ return this._schemeEnd === 4 && C.JSString_methods.startsWith$1(this._uri, "file");
+ },
+ get$_isHttp: function() {
+ return this._schemeEnd === 4 && C.JSString_methods.startsWith$1(this._uri, "http");
+ },
+ get$_isHttps: function() {
+ return this._schemeEnd === 5 && C.JSString_methods.startsWith$1(this._uri, "https");
+ },
+ get$hasAbsolutePath: function() {
+ return C.JSString_methods.startsWith$2(this._uri, "/", this._pathStart);
+ },
+ get$scheme: function() {
+ var t2, _this = this,
+ _s7_ = "package",
+ t1 = _this._schemeEnd;
+ if (t1 <= 0)
+ return "";
+ t2 = _this._schemeCache;
+ if (t2 != null)
+ return t2;
+ if (_this.get$_isHttp())
+ t1 = _this._schemeCache = "http";
+ else if (_this.get$_isHttps()) {
+ _this._schemeCache = "https";
+ t1 = "https";
+ } else if (_this.get$_isFile()) {
+ _this._schemeCache = "file";
+ t1 = "file";
+ } else if (t1 === 7 && C.JSString_methods.startsWith$1(_this._uri, _s7_)) {
+ _this._schemeCache = _s7_;
+ t1 = _s7_;
+ } else {
+ t1 = C.JSString_methods.substring$2(_this._uri, 0, t1);
+ _this._schemeCache = t1;
+ }
+ return t1;
+ },
+ get$userInfo: function() {
+ var t1 = this._hostStart,
+ t2 = this._schemeEnd + 3;
+ return t1 > t2 ? C.JSString_methods.substring$2(this._uri, t2, t1 - 1) : "";
+ },
+ get$host: function(_) {
+ var t1 = this._hostStart;
+ return t1 > 0 ? C.JSString_methods.substring$2(this._uri, t1, this._portStart) : "";
+ },
+ get$port: function(_) {
+ var _this = this;
+ if (_this.get$hasPort())
+ return P.int_parse(C.JSString_methods.substring$2(_this._uri, _this._portStart + 1, _this._pathStart), null, null);
+ if (_this.get$_isHttp())
+ return 80;
+ if (_this.get$_isHttps())
+ return 443;
+ return 0;
+ },
+ get$path: function(_) {
+ return C.JSString_methods.substring$2(this._uri, this._pathStart, this._queryStart);
+ },
+ get$query: function() {
+ var t1 = this._queryStart,
+ t2 = this._fragmentStart;
+ return t1 < t2 ? C.JSString_methods.substring$2(this._uri, t1 + 1, t2) : "";
+ },
+ get$fragment: function() {
+ var t1 = this._fragmentStart,
+ t2 = this._uri;
+ return t1 < t2.length ? C.JSString_methods.substring$1(t2, t1 + 1) : "";
+ },
+ get$pathSegments: function() {
+ var parts, i,
+ start = this._pathStart,
+ end = this._queryStart,
+ t1 = this._uri;
+ if (C.JSString_methods.startsWith$2(t1, "/", start))
+ ++start;
+ if (start === end)
+ return C.List_empty;
+ parts = H.setRuntimeTypeInfo([], type$.JSArray_String);
+ for (i = start; i < end; ++i)
+ if (C.JSString_methods.codeUnitAt$1(t1, i) === 47) {
+ C.JSArray_methods.add$1(parts, C.JSString_methods.substring$2(t1, start, i));
+ start = i + 1;
+ }
+ C.JSArray_methods.add$1(parts, C.JSString_methods.substring$2(t1, start, end));
+ return P.List_List$unmodifiable(parts, type$.String);
+ },
+ _isPort$1: function(port) {
+ var portDigitStart = this._portStart + 1;
+ return portDigitStart + port.length === this._pathStart && C.JSString_methods.startsWith$2(this._uri, port, portDigitStart);
+ },
+ removeFragment$0: function() {
+ var _this = this,
+ t1 = _this._fragmentStart,
+ t2 = _this._uri;
+ if (t1 >= t2.length)
+ return _this;
+ return new P._SimpleUri(C.JSString_methods.substring$2(t2, 0, t1), _this._schemeEnd, _this._hostStart, _this._portStart, _this._pathStart, _this._queryStart, t1, _this._schemeCache);
+ },
+ replace$2$path$pathSegments: function(_, path, pathSegments) {
+ var scheme, isFile, t1, userInfo, port, host, hasAuthority, t2, query, fragment, _this = this, _null = null;
+ type$.Iterable_String._check(pathSegments);
+ type$.Map_String_dynamic._check(null);
+ scheme = _this.get$scheme();
+ isFile = scheme === "file";
+ t1 = _this._hostStart;
+ userInfo = t1 > 0 ? C.JSString_methods.substring$2(_this._uri, _this._schemeEnd + 3, t1) : "";
+ port = _this.get$hasPort() ? _this.get$port(_this) : _null;
+ t1 = _this._hostStart;
+ if (t1 > 0)
+ host = C.JSString_methods.substring$2(_this._uri, t1, _this._portStart);
+ else
+ host = userInfo.length !== 0 || port != null || isFile ? "" : _null;
+ hasAuthority = host != null;
+ t1 = path == null;
+ if (!t1 || pathSegments != null)
+ path = P._Uri__makePath(path, 0, t1 ? 0 : path.length, pathSegments, scheme, hasAuthority);
+ else {
+ path = C.JSString_methods.substring$2(_this._uri, _this._pathStart, _this._queryStart);
+ if (!isFile)
+ t1 = hasAuthority && path.length !== 0;
+ else
+ t1 = true;
+ if (t1 && !C.JSString_methods.startsWith$1(path, "/"))
+ path = "/" + path;
+ }
+ t1 = _this._queryStart;
+ t2 = _this._fragmentStart;
+ query = t1 < t2 ? C.JSString_methods.substring$2(_this._uri, t1 + 1, t2) : _null;
+ t1 = _this._fragmentStart;
+ t2 = _this._uri;
+ fragment = t1 < t2.length ? C.JSString_methods.substring$1(t2, t1 + 1) : _null;
+ return new P._Uri(scheme, userInfo, host, port, path, query, fragment);
+ },
+ replace$1$path: function($receiver, path) {
+ return this.replace$2$path$pathSegments($receiver, path, null);
+ },
+ replace$1$pathSegments: function($receiver, pathSegments) {
+ return this.replace$2$path$pathSegments($receiver, null, pathSegments);
+ },
+ resolve$1: function(reference) {
+ return this.resolveUri$1(P.Uri_parse(reference));
+ },
+ resolveUri$1: function(reference) {
+ if (reference instanceof P._SimpleUri)
+ return this._simpleMerge$2(this, reference);
+ return this._toNonSimple$0().resolveUri$1(reference);
+ },
+ _simpleMerge$2: function(base, ref) {
+ var t2, t3, isSimple, delta, refStart, baseStart, baseEnd, baseUri, baseStart0, backCount, refStart0, insert,
+ t1 = ref._schemeEnd;
+ if (t1 > 0)
+ return ref;
+ t2 = ref._hostStart;
+ if (t2 > 0) {
+ t3 = base._schemeEnd;
+ if (t3 <= 0)
+ return ref;
+ if (base.get$_isFile())
+ isSimple = ref._pathStart !== ref._queryStart;
+ else if (base.get$_isHttp())
+ isSimple = !ref._isPort$1("80");
+ else
+ isSimple = !base.get$_isHttps() || !ref._isPort$1("443");
+ if (isSimple) {
+ delta = t3 + 1;
+ return new P._SimpleUri(C.JSString_methods.substring$2(base._uri, 0, delta) + C.JSString_methods.substring$1(ref._uri, t1 + 1), t3, t2 + delta, ref._portStart + delta, ref._pathStart + delta, ref._queryStart + delta, ref._fragmentStart + delta, base._schemeCache);
+ } else
+ return this._toNonSimple$0().resolveUri$1(ref);
+ }
+ refStart = ref._pathStart;
+ t1 = ref._queryStart;
+ if (refStart === t1) {
+ t2 = ref._fragmentStart;
+ if (t1 < t2) {
+ t3 = base._queryStart;
+ delta = t3 - t1;
+ return new P._SimpleUri(C.JSString_methods.substring$2(base._uri, 0, t3) + C.JSString_methods.substring$1(ref._uri, t1), base._schemeEnd, base._hostStart, base._portStart, base._pathStart, t1 + delta, t2 + delta, base._schemeCache);
+ }
+ t1 = ref._uri;
+ if (t2 < t1.length) {
+ t3 = base._fragmentStart;
+ return new P._SimpleUri(C.JSString_methods.substring$2(base._uri, 0, t3) + C.JSString_methods.substring$1(t1, t2), base._schemeEnd, base._hostStart, base._portStart, base._pathStart, base._queryStart, t2 + (t3 - t2), base._schemeCache);
+ }
+ return base.removeFragment$0();
+ }
+ t2 = ref._uri;
+ if (C.JSString_methods.startsWith$2(t2, "/", refStart)) {
+ t3 = base._pathStart;
+ delta = t3 - refStart;
+ return new P._SimpleUri(C.JSString_methods.substring$2(base._uri, 0, t3) + C.JSString_methods.substring$1(t2, refStart), base._schemeEnd, base._hostStart, base._portStart, t3, t1 + delta, ref._fragmentStart + delta, base._schemeCache);
+ }
+ baseStart = base._pathStart;
+ baseEnd = base._queryStart;
+ if (baseStart === baseEnd && base._hostStart > 0) {
+ for (; C.JSString_methods.startsWith$2(t2, "../", refStart);)
+ refStart += 3;
+ delta = baseStart - refStart + 1;
+ return new P._SimpleUri(C.JSString_methods.substring$2(base._uri, 0, baseStart) + "/" + C.JSString_methods.substring$1(t2, refStart), base._schemeEnd, base._hostStart, base._portStart, baseStart, t1 + delta, ref._fragmentStart + delta, base._schemeCache);
+ }
+ baseUri = base._uri;
+ for (baseStart0 = baseStart; C.JSString_methods.startsWith$2(baseUri, "../", baseStart0);)
+ baseStart0 += 3;
+ backCount = 0;
+ while (true) {
+ refStart0 = refStart + 3;
+ if (!(refStart0 <= t1 && C.JSString_methods.startsWith$2(t2, "../", refStart)))
+ break;
+ ++backCount;
+ refStart = refStart0;
+ }
+ for (insert = ""; baseEnd > baseStart0;) {
+ --baseEnd;
+ if (C.JSString_methods.codeUnitAt$1(baseUri, baseEnd) === 47) {
+ if (backCount === 0) {
+ insert = "/";
+ break;
+ }
+ --backCount;
+ insert = "/";
+ }
+ }
+ if (baseEnd === baseStart0 && base._schemeEnd <= 0 && !C.JSString_methods.startsWith$2(baseUri, "/", baseStart)) {
+ refStart -= backCount * 3;
+ insert = "";
+ }
+ delta = baseEnd - refStart + insert.length;
+ return new P._SimpleUri(C.JSString_methods.substring$2(baseUri, 0, baseEnd) + insert + C.JSString_methods.substring$1(t2, refStart), base._schemeEnd, base._hostStart, base._portStart, baseStart, t1 + delta, ref._fragmentStart + delta, base._schemeCache);
+ },
+ toFilePath$0: function() {
+ var t1, t2, windows, _this = this;
+ if (_this._schemeEnd >= 0 && !_this.get$_isFile())
+ throw H.wrapException(P.UnsupportedError$("Cannot extract a file path from a " + H.S(_this.get$scheme()) + " URI"));
+ t1 = _this._queryStart;
+ t2 = _this._uri;
+ if (t1 < t2.length) {
+ if (t1 < _this._fragmentStart)
+ throw H.wrapException(P.UnsupportedError$("Cannot extract a file path from a URI with a query component"));
+ throw H.wrapException(P.UnsupportedError$("Cannot extract a file path from a URI with a fragment component"));
+ }
+ windows = $.$get$_Uri__isWindowsCached();
+ if (H.boolConversionCheck(windows))
+ t1 = P._Uri__toWindowsFilePath(_this);
+ else {
+ if (_this._hostStart < _this._portStart)
+ H.throwExpression(P.UnsupportedError$("Cannot extract a non-Windows file path from a file URI with an authority"));
+ t1 = C.JSString_methods.substring$2(t2, _this._pathStart, t1);
+ }
+ return t1;
+ },
+ get$hashCode: function(_) {
+ var t1 = this._hashCodeCache;
+ return t1 == null ? this._hashCodeCache = C.JSString_methods.get$hashCode(this._uri) : t1;
+ },
+ $eq: function(_, other) {
+ if (other == null)
+ return false;
+ if (this === other)
+ return true;
+ return type$.Uri._is(other) && this._uri === other.toString$0(0);
+ },
+ _toNonSimple$0: function() {
+ var _this = this, _null = null,
+ t1 = _this.get$scheme(),
+ t2 = _this.get$userInfo(),
+ t3 = _this._hostStart > 0 ? _this.get$host(_this) : _null,
+ t4 = _this.get$hasPort() ? _this.get$port(_this) : _null,
+ t5 = _this._uri,
+ t6 = _this._queryStart,
+ t7 = C.JSString_methods.substring$2(t5, _this._pathStart, t6),
+ t8 = _this._fragmentStart;
+ t6 = t6 < t8 ? _this.get$query() : _null;
+ return new P._Uri(t1, t2, t3, t4, t7, t6, t8 < t5.length ? _this.get$fragment() : _null);
+ },
+ toString$0: function(_) {
+ return this._uri;
+ },
+ $isUri: 1
+ };
+ P._DataUri.prototype = {};
+ W.ApplicationCacheErrorEvent.prototype = {
+ get$message: function(receiver) {
+ return receiver.message;
+ }
+ };
+ W.Blob.prototype = {$isBlob: 1};
+ W.DomError.prototype = {
+ get$message: function(receiver) {
+ return receiver.message;
+ }
+ };
+ W.DomException.prototype = {
+ toString$0: function(receiver) {
+ return String(receiver);
+ },
+ get$message: function(receiver) {
+ return receiver.message;
+ }
+ };
+ W.ErrorEvent.prototype = {
+ get$message: function(receiver) {
+ return receiver.message;
+ }
+ };
+ W.Event.prototype = {$isEvent: 1};
+ W.EventTarget.prototype = {
+ addEventListener$3: function(receiver, type, listener, useCapture) {
+ type$.dynamic_Function_Event._check(listener);
+ if (listener != null)
+ this._addEventListener$3(receiver, type, listener, false);
+ },
+ _addEventListener$3: function(receiver, type, listener, options) {
+ return receiver.addEventListener(type, H.convertDartClosureToJS(type$.dynamic_Function_Event._check(listener), 1), false);
+ },
+ _removeEventListener$3: function(receiver, type, listener, options) {
+ return receiver.removeEventListener(type, H.convertDartClosureToJS(type$.dynamic_Function_Event._check(listener), 1), false);
+ },
+ $isEventTarget: 1
+ };
+ W.File.prototype = {$isFile: 1};
+ W.Location.prototype = {
+ get$origin: function(receiver) {
+ if ("origin" in receiver)
+ return receiver.origin;
+ return H.S(receiver.protocol) + "//" + H.S(receiver.host);
+ },
+ toString$0: function(receiver) {
+ return String(receiver);
+ }
+ };
+ W.MediaError.prototype = {
+ get$message: function(receiver) {
+ return receiver.message;
+ }
+ };
+ W.MediaKeyMessageEvent.prototype = {
+ get$message: function(receiver) {
+ return receiver.message;
+ }
+ };
+ W.MessageEvent.prototype = {$isMessageEvent: 1};
+ W.MessagePort.prototype = {
+ addEventListener$3: function(receiver, type, listener, useCapture) {
+ type$.dynamic_Function_Event._check(listener);
+ if (type === "message")
+ receiver.start();
+ this.super$EventTarget$addEventListener(receiver, type, listener, false);
+ },
+ postMessage$1: function(receiver, message) {
+ type$.List_Object._check(null);
+ receiver.postMessage(new P._StructuredCloneDart2Js([], []).walk$1(message));
+ return;
+ },
+ $isMessagePort: 1
+ };
+ W.NavigatorUserMediaError.prototype = {
+ get$message: function(receiver) {
+ return receiver.message;
+ }
+ };
+ W.OverconstrainedError.prototype = {
+ get$message: function(receiver) {
+ return receiver.message;
+ }
+ };
+ W.PositionError.prototype = {
+ get$message: function(receiver) {
+ return receiver.message;
+ }
+ };
+ W.PresentationConnectionCloseEvent.prototype = {
+ get$message: function(receiver) {
+ return receiver.message;
+ }
+ };
+ W.SpeechRecognitionError.prototype = {
+ get$message: function(receiver) {
+ return receiver.message;
+ }
+ };
+ W.EventStreamProvider.prototype = {};
+ W._EventStream.prototype = {
+ get$isBroadcast: function() {
+ return true;
+ },
+ listen$4$cancelOnError$onDone$onError: function(onData, cancelOnError, onDone, onError) {
+ var t1 = this.$ti;
+ t1._eval$1("~(1)")._check(onData);
+ type$.void_Function._check(onDone);
+ H._checkBoolNullable(cancelOnError);
+ return W._EventStreamSubscription$(this._html$_target, this._eventType, onData, false, t1._precomputed1);
+ },
+ listen$3$onDone$onError: function(onData, onDone, onError) {
+ return this.listen$4$cancelOnError$onDone$onError(onData, null, onDone, onError);
+ },
+ listen$1: function(onData) {
+ return this.listen$4$cancelOnError$onDone$onError(onData, null, null, null);
+ }
+ };
+ W._EventStreamSubscription.prototype = {
+ cancel$0: function() {
+ var _this = this;
+ if (_this._html$_target == null)
+ return null;
+ _this._unlisten$0();
+ _this._html$_target = null;
+ _this.set$_html$_onData(null);
+ return null;
+ },
+ pause$1: function(resumeSignal) {
+ if (this._html$_target == null)
+ return;
+ ++this._pauseCount;
+ this._unlisten$0();
+ },
+ pause$0: function() {
+ return this.pause$1(null);
+ },
+ resume$0: function() {
+ var _this = this;
+ if (_this._html$_target == null || _this._pauseCount <= 0)
+ return;
+ --_this._pauseCount;
+ _this._tryResume$0();
+ },
+ _tryResume$0: function() {
+ var _this = this,
+ t1 = _this._html$_onData;
+ if (t1 != null && _this._pauseCount <= 0)
+ J.addEventListener$3$x(_this._html$_target, _this._eventType, t1, false);
+ },
+ _unlisten$0: function() {
+ var t3,
+ t1 = this._html$_onData,
+ t2 = t1 != null;
+ if (t2) {
+ t3 = this._html$_target;
+ t3.toString;
+ type$.dynamic_Function_Event._check(t1);
+ if (t2)
+ J._removeEventListener$3$x(t3, this._eventType, t1, false);
+ }
+ },
+ set$_html$_onData: function(_onData) {
+ this._html$_onData = type$.dynamic_Function_Event._check(_onData);
+ }
+ };
+ W._EventStreamSubscription_closure.prototype = {
+ call$1: function(e) {
+ return this.onData.call$1(type$.Event._check(e));
+ },
+ $signature: 61
+ };
+ P._StructuredClone.prototype = {
+ findSlot$1: function(value) {
+ var i,
+ t1 = this.values,
+ $length = t1.length;
+ for (i = 0; i < $length; ++i)
+ if (t1[i] === value)
+ return i;
+ C.JSArray_methods.add$1(t1, value);
+ C.JSArray_methods.add$1(this.copies, null);
+ return $length;
+ },
+ walk$1: function(e) {
+ var slot, t2, copy, _this = this, t1 = {};
+ if (e == null)
+ return e;
+ if (H._isBool(e))
+ return e;
+ if (typeof e == "number")
+ return e;
+ if (typeof e == "string")
+ return e;
+ if (e instanceof P.DateTime)
+ return new Date(e._value);
+ if (type$.RegExp._is(e))
+ throw H.wrapException(P.UnimplementedError$("structured clone of RegExp"));
+ if (type$.File._is(e))
+ return e;
+ if (type$.Blob._is(e))
+ return e;
+ if (type$.NativeByteBuffer._is(e) || type$.NativeTypedData._is(e) || type$.MessagePort._is(e))
+ return e;
+ if (type$.Map_dynamic_dynamic._is(e)) {
+ slot = _this.findSlot$1(e);
+ t2 = _this.copies;
+ if (slot >= t2.length)
+ return H.ioore(t2, slot);
+ copy = t1.copy = t2[slot];
+ if (copy != null)
+ return copy;
+ copy = {};
+ t1.copy = copy;
+ C.JSArray_methods.$indexSet(t2, slot, copy);
+ e.forEach$1(0, new P._StructuredClone_walk_closure(t1, _this));
+ return t1.copy;
+ }
+ if (type$.List_dynamic._is(e)) {
+ slot = _this.findSlot$1(e);
+ t1 = _this.copies;
+ if (slot >= t1.length)
+ return H.ioore(t1, slot);
+ copy = t1[slot];
+ if (copy != null)
+ return copy;
+ return _this.copyList$2(e, slot);
+ }
+ if (type$.JSObject._is(e)) {
+ slot = _this.findSlot$1(e);
+ t2 = _this.copies;
+ if (slot >= t2.length)
+ return H.ioore(t2, slot);
+ copy = t1.copy = t2[slot];
+ if (copy != null)
+ return copy;
+ copy = {};
+ t1.copy = copy;
+ C.JSArray_methods.$indexSet(t2, slot, copy);
+ _this.forEachObjectKey$2(e, new P._StructuredClone_walk_closure0(t1, _this));
+ return t1.copy;
+ }
+ throw H.wrapException(P.UnimplementedError$("structured clone of other type"));
+ },
+ copyList$2: function(e, slot) {
+ var i,
+ t1 = J.getInterceptor$asx(e),
+ $length = t1.get$length(e),
+ copy = new Array($length);
+ C.JSArray_methods.$indexSet(this.copies, slot, copy);
+ for (i = 0; i < $length; ++i)
+ C.JSArray_methods.$indexSet(copy, i, this.walk$1(t1.$index(e, i)));
+ return copy;
+ }
+ };
+ P._StructuredClone_walk_closure.prototype = {
+ call$2: function(key, value) {
+ this._box_0.copy[key] = this.$this.walk$1(value);
+ },
+ $signature: 6
+ };
+ P._StructuredClone_walk_closure0.prototype = {
+ call$2: function(key, value) {
+ this._box_0.copy[key] = this.$this.walk$1(value);
+ },
+ $signature: 6
+ };
+ P._AcceptStructuredClone.prototype = {
+ findSlot$1: function(value) {
+ var i,
+ t1 = this.values,
+ $length = t1.length;
+ for (i = 0; i < $length; ++i)
+ if (t1[i] === value)
+ return i;
+ C.JSArray_methods.add$1(t1, value);
+ C.JSArray_methods.add$1(this.copies, null);
+ return $length;
+ },
+ walk$1: function(e) {
+ var millisSinceEpoch, t1, proto, slot, copy, t2, l, $length, i, _this = this, _box_0 = {};
+ if (e == null)
+ return e;
+ if (H._isBool(e))
+ return e;
+ if (typeof e == "number")
+ return e;
+ if (typeof e == "string")
+ return e;
+ if (e instanceof Date) {
+ millisSinceEpoch = e.getTime();
+ if (Math.abs(millisSinceEpoch) <= 864e13)
+ t1 = false;
+ else
+ t1 = true;
+ if (t1)
+ H.throwExpression(P.ArgumentError$("DateTime is outside valid range: " + millisSinceEpoch));
+ return new P.DateTime(millisSinceEpoch, true);
+ }
+ if (e instanceof RegExp)
+ throw H.wrapException(P.UnimplementedError$("structured clone of RegExp"));
+ if (typeof Promise != "undefined" && e instanceof Promise)
+ return P.promiseToFuture(e, type$.dynamic);
+ proto = Object.getPrototypeOf(e);
+ if (proto === Object.prototype || proto === null) {
+ slot = _this.findSlot$1(e);
+ t1 = _this.copies;
+ if (slot >= t1.length)
+ return H.ioore(t1, slot);
+ copy = _box_0.copy = t1[slot];
+ if (copy != null)
+ return copy;
+ t2 = type$.dynamic;
+ copy = P.LinkedHashMap_LinkedHashMap$_empty(t2, t2);
+ _box_0.copy = copy;
+ C.JSArray_methods.$indexSet(t1, slot, copy);
+ _this.forEachJsField$2(e, new P._AcceptStructuredClone_walk_closure(_box_0, _this));
+ return _box_0.copy;
+ }
+ if (e instanceof Array) {
+ l = e;
+ slot = _this.findSlot$1(l);
+ t1 = _this.copies;
+ if (slot >= t1.length)
+ return H.ioore(t1, slot);
+ copy = t1[slot];
+ if (copy != null)
+ return copy;
+ t2 = J.getInterceptor$asx(l);
+ $length = t2.get$length(l);
+ copy = _this.mustCopy ? new Array($length) : l;
+ C.JSArray_methods.$indexSet(t1, slot, copy);
+ for (t1 = J.getInterceptor$ax(copy), i = 0; i < $length; ++i)
+ t1.$indexSet(copy, i, _this.walk$1(t2.$index(l, i)));
+ return copy;
+ }
+ return e;
+ },
+ convertNativeToDart_AcceptStructuredClone$2$mustCopy: function(object, mustCopy) {
+ this.mustCopy = true;
+ return this.walk$1(object);
+ }
+ };
+ P._AcceptStructuredClone_walk_closure.prototype = {
+ call$2: function(key, value) {
+ var t1 = this._box_0.copy,
+ t2 = this.$this.walk$1(value);
+ J.$indexSet$ax(t1, key, t2);
+ return t2;
+ },
+ $signature: 60
+ };
+ P._StructuredCloneDart2Js.prototype = {
+ forEachObjectKey$2: function(object, action) {
+ var t1, t2, _i, key;
+ type$.dynamic_Function_dynamic_dynamic._check(action);
+ for (t1 = Object.keys(object), t2 = t1.length, _i = 0; _i < t2; ++_i) {
+ key = t1[_i];
+ action.call$2(key, object[key]);
+ }
+ }
+ };
+ P._AcceptStructuredCloneDart2Js.prototype = {
+ forEachJsField$2: function(object, action) {
+ var t1, t2, _i, key;
+ type$.dynamic_Function_dynamic_dynamic._check(action);
+ for (t1 = Object.keys(object), t2 = t1.length, _i = 0; _i < t1.length; t1.length === t2 || (0, H.throwConcurrentModificationError)(t1), ++_i) {
+ key = t1[_i];
+ action.call$2(key, object[key]);
+ }
+ }
+ };
+ P._convertDataTree__convert.prototype = {
+ call$1: function(o) {
+ var convertedMap, key, convertedList,
+ t1 = this._convertedObjects;
+ if (t1.containsKey$1(o))
+ return t1.$index(0, o);
+ if (type$.Map_dynamic_dynamic._is(o)) {
+ convertedMap = {};
+ t1.$indexSet(0, o, convertedMap);
+ for (t1 = o.get$keys(), t1 = t1.get$iterator(t1); t1.moveNext$0();) {
+ key = t1.get$current();
+ convertedMap[key] = this.call$1(o.$index(0, key));
+ }
+ return convertedMap;
+ } else if (type$.Iterable_dynamic._is(o)) {
+ convertedList = [];
+ t1.$indexSet(0, o, convertedList);
+ C.JSArray_methods.addAll$1(convertedList, J.map$1$1$ax(o, this, type$.dynamic));
+ return convertedList;
+ } else
+ return o;
+ },
+ $signature: 18
+ };
+ P.promiseToFuture_closure.prototype = {
+ call$1: function(r) {
+ return this.completer.complete$1(this.T._eval$1("0/")._check(r));
+ },
+ $signature: 15
+ };
+ P.promiseToFuture_closure0.prototype = {
+ call$1: function(e) {
+ return this.completer.completeError$1(e);
+ },
+ $signature: 15
+ };
+ P.ByteBuffer.prototype = {};
+ P.ByteData.prototype = {};
+ P.Int8List.prototype = {$isEfficientLengthIterable: 1, $isIterable: 1, $isList: 1};
+ P.Uint8List.prototype = {$isEfficientLengthIterable: 1, $isIterable: 1, $isList: 1};
+ P.Uint8ClampedList.prototype = {$isEfficientLengthIterable: 1, $isIterable: 1, $isList: 1};
+ P.Int16List.prototype = {$isEfficientLengthIterable: 1, $isIterable: 1, $isList: 1};
+ P.Uint16List.prototype = {$isEfficientLengthIterable: 1, $isIterable: 1, $isList: 1};
+ P.Int32List.prototype = {$isEfficientLengthIterable: 1, $isIterable: 1, $isList: 1};
+ P.Uint32List.prototype = {$isEfficientLengthIterable: 1, $isIterable: 1, $isList: 1};
+ P.Float32List.prototype = {$isEfficientLengthIterable: 1, $isIterable: 1, $isList: 1};
+ P.Float64List.prototype = {$isEfficientLengthIterable: 1, $isIterable: 1, $isList: 1};
+ P.SqlError.prototype = {
+ get$message: function(receiver) {
+ return receiver.message;
+ }
+ };
+ S.AsyncMemoizer.prototype = {
+ runOnce$1: function(computation) {
+ var t2, t3,
+ t1 = this.$ti;
+ t1._eval$1("1/()")._check(computation);
+ t2 = this._async_memoizer$_completer;
+ t3 = t2.future;
+ if (t3._state === 0)
+ t2.complete$1(P.Future_Future$sync(computation, t1._precomputed1));
+ return t3;
+ }
+ };
+ O.DelegatingSink.prototype = {$isSink: 1};
+ Y.DelegatingStreamSubscription.prototype = {
+ onData$1: function(handleData) {
+ this._stream_subscription$_source.onData$1(this.$ti._eval$1("~(1)")._check(handleData));
+ },
+ onError$1: function(_, handleError) {
+ this._stream_subscription$_source.onError$1(0, handleError);
+ },
+ onDone$1: function(handleDone) {
+ this._stream_subscription$_source.onDone$1(type$.void_Function._check(handleDone));
+ },
+ pause$1: function(resumeFuture) {
+ this._stream_subscription$_source.pause$1(resumeFuture);
+ },
+ pause$0: function() {
+ return this.pause$1(null);
+ },
+ resume$0: function() {
+ this._stream_subscription$_source.resume$0();
+ },
+ cancel$0: function() {
+ return this._stream_subscription$_source.cancel$0();
+ },
+ $isStreamSubscription: 1
+ };
+ F.FutureGroup.prototype = {
+ add$1: function(_, task) {
+ var t1, index, _this = this;
+ _this.$ti._eval$1("Future<1>")._check(task);
+ if (_this._future_group$_closed)
+ throw H.wrapException(P.StateError$("The FutureGroup is closed."));
+ t1 = _this._values;
+ index = t1.length;
+ C.JSArray_methods.add$1(t1, null);
+ ++_this._future_group$_pending;
+ task.then$1$1(new F.FutureGroup_add_closure(_this, index), type$.Null).catchError$1(new F.FutureGroup_add_closure0(_this));
+ },
+ close$0: function(_) {
+ var t1, _this = this;
+ _this._future_group$_closed = true;
+ if (_this._future_group$_pending !== 0)
+ return;
+ t1 = _this._future_group$_completer;
+ if (t1.future._state !== 0)
+ return;
+ t1.complete$1(_this._values);
+ },
+ $isSink: 1
+ };
+ F.FutureGroup_add_closure.prototype = {
+ call$1: function(value) {
+ var t2, t3,
+ t1 = this.$this;
+ t1.$ti._precomputed1._check(value);
+ t2 = t1._future_group$_completer;
+ if (t2.future._state !== 0)
+ return null;
+ --t1._future_group$_pending;
+ t3 = t1._values;
+ C.JSArray_methods.$indexSet(t3, this.index, value);
+ if (t1._future_group$_pending !== 0)
+ return null;
+ if (!t1._future_group$_closed)
+ return null;
+ t2.complete$1(t3);
+ },
+ $signature: function() {
+ return this.$this.$ti._eval$1("Null(1)");
+ }
+ };
+ F.FutureGroup_add_closure0.prototype = {
+ call$2: function(error, stackTrace) {
+ var t1 = this.$this._future_group$_completer;
+ if (t1.future._state !== 0)
+ return null;
+ t1.completeError$2(error, type$.StackTrace._check(stackTrace));
+ },
+ $signature: 6
+ };
+ S.NullStreamSink.prototype = {
+ add$1: function(_, data) {
+ this.$ti._precomputed1._check(data);
+ this._checkEventAllowed$0();
+ },
+ addStream$1: function(stream) {
+ var future, _this = this;
+ _this.$ti._eval$1("Stream<1>")._check(stream);
+ _this._checkEventAllowed$0();
+ _this._addingStream = true;
+ future = stream.listen$1(null).cancel$0();
+ if (future == null) {
+ future = new P._Future($.Zone__current, type$._Future_dynamic);
+ future._asyncComplete$1(null);
+ }
+ return future.whenComplete$1(new S.NullStreamSink_addStream_closure(_this));
+ },
+ _checkEventAllowed$0: function() {
+ if (this._null_stream_sink$_closed)
+ throw H.wrapException(P.StateError$("Cannot add to a closed sink."));
+ if (this._addingStream)
+ throw H.wrapException(P.StateError$("Cannot add to a sink while adding a stream."));
+ },
+ close$0: function(_) {
+ this._null_stream_sink$_closed = true;
+ return this.done;
+ },
+ $isEventSink: 1,
+ $isStreamConsumer: 1,
+ $isStreamSink: 1,
+ $isSink: 1,
+ get$done: function() {
+ return this.done;
+ }
+ };
+ S.NullStreamSink_addStream_closure.prototype = {
+ call$0: function() {
+ this.$this._addingStream = false;
+ },
+ $signature: 0
+ };
+ V.ErrorResult.prototype = {
+ complete$1: function(completer) {
+ completer.completeError$2(this.error, this.stackTrace);
+ },
+ addTo$1: function(sink) {
+ sink.addError$2(this.error, this.stackTrace);
+ },
+ get$hashCode: function(_) {
+ return (J.get$hashCode$(this.error) ^ J.get$hashCode$(this.stackTrace) ^ 492929599) >>> 0;
+ },
+ $eq: function(_, other) {
+ if (other == null)
+ return false;
+ return other instanceof V.ErrorResult && J.$eq$(this.error, other.error) && this.stackTrace == other.stackTrace;
+ },
+ $isResult: 1
+ };
+ E.Result.prototype = {};
+ F.ValueResult.prototype = {
+ complete$1: function(completer) {
+ this.$ti._eval$1("Completer<1>")._check(completer).complete$1(this.value);
+ },
+ addTo$1: function(sink) {
+ this.$ti._eval$1("EventSink<1>")._check(sink).add$1(0, this.value);
+ },
+ get$hashCode: function(_) {
+ return (J.get$hashCode$(this.value) ^ 842997089) >>> 0;
+ },
+ $eq: function(_, other) {
+ if (other == null)
+ return false;
+ return other instanceof F.ValueResult && J.$eq$(this.value, other.value);
+ },
+ $isResult: 1
+ };
+ Y.StreamCompleter.prototype = {
+ setSourceStream$1: function(sourceStream) {
+ var t1;
+ this.$ti._eval$1("Stream<1>")._check(sourceStream);
+ t1 = this._stream;
+ if (t1._sourceStream != null)
+ throw H.wrapException(P.StateError$("Source stream already set"));
+ t1.set$_sourceStream(t1.$ti._eval$1("Stream<1>")._check(sourceStream));
+ if (t1._stream_completer$_controller != null)
+ t1._linkStreamToController$0();
+ }
+ };
+ Y._CompleterStream.prototype = {
+ listen$4$cancelOnError$onDone$onError: function(onData, cancelOnError, onDone, onError) {
+ var t1, _this = this;
+ _this.$ti._eval$1("@(1)")._check(onData);
+ type$.void_Function._check(onDone);
+ H._checkBoolNullable(cancelOnError);
+ if (_this._stream_completer$_controller == null) {
+ t1 = _this._sourceStream;
+ if (t1 != null && !t1.get$isBroadcast())
+ return _this._sourceStream.listen$4$cancelOnError$onDone$onError(onData, cancelOnError, onDone, onError);
+ _this._createController$0();
+ if (_this._sourceStream != null)
+ _this._linkStreamToController$0();
+ }
+ t1 = _this._stream_completer$_controller;
+ t1.toString;
+ return new P._ControllerStream(t1, H._instanceType(t1)._eval$1("_ControllerStream<1>")).listen$4$cancelOnError$onDone$onError(onData, cancelOnError, onDone, onError);
+ },
+ listen$3$onDone$onError: function(onData, onDone, onError) {
+ return this.listen$4$cancelOnError$onDone$onError(onData, null, onDone, onError);
+ },
+ listen$1: function(onData) {
+ return this.listen$4$cancelOnError$onDone$onError(onData, null, null, null);
+ },
+ _linkStreamToController$0: function() {
+ var t1, t2, _this = this;
+ H.assertHelper(_this._stream_completer$_controller != null);
+ H.assertHelper(_this._sourceStream != null);
+ t1 = _this._stream_completer$_controller.addStream$2$cancelOnError(_this._sourceStream, false);
+ t2 = _this._stream_completer$_controller;
+ t1.whenComplete$1(t2.get$close(t2));
+ },
+ _createController$0: function() {
+ H.assertHelper(this._stream_completer$_controller == null);
+ this.set$_stream_completer$_controller(P.StreamController_StreamController(null, null, true, this.$ti._precomputed1));
+ },
+ set$_stream_completer$_controller: function(_controller) {
+ this._stream_completer$_controller = this.$ti._eval$1("StreamController<1>")._check(_controller);
+ },
+ set$_sourceStream: function(_sourceStream) {
+ this._sourceStream = this.$ti._eval$1("Stream<1>")._check(_sourceStream);
+ }
+ };
+ L.StreamGroup.prototype = {
+ add$1: function(_, stream) {
+ var t1, _this = this;
+ _this.$ti._eval$1("Stream<1>")._check(stream);
+ if (_this._stream_group$_closed)
+ throw H.wrapException(P.StateError$("Can't add a Stream to a closed StreamGroup."));
+ t1 = _this._stream_group$_state;
+ if (t1 === C._StreamGroupState_dormant)
+ _this._stream_group$_subscriptions.putIfAbsent$2(stream, new L.StreamGroup_add_closure());
+ else if (t1 === C._StreamGroupState_canceled)
+ return stream.listen$1(null).cancel$0();
+ else
+ _this._stream_group$_subscriptions.putIfAbsent$2(stream, new L.StreamGroup_add_closure0(_this, stream));
+ return null;
+ },
+ _onListen$0: function() {
+ this._stream_group$_state = C._StreamGroupState_listening;
+ this._stream_group$_subscriptions.forEach$1(0, new L.StreamGroup__onListen_closure(this));
+ },
+ _onCancelBroadcast$0: function() {
+ this._stream_group$_state = C._StreamGroupState_dormant;
+ this._stream_group$_subscriptions.forEach$1(0, new L.StreamGroup__onCancelBroadcast_closure(this));
+ },
+ _listenToStream$1: function(stream) {
+ var t1, subscription, _this = this;
+ _this.$ti._eval$1("Stream<1>")._check(stream);
+ t1 = _this._stream_group$_controller;
+ subscription = stream.listen$3$onDone$onError(t1.get$add(t1), new L.StreamGroup__listenToStream_closure(_this, stream), t1.get$addError());
+ if (_this._stream_group$_state === C._StreamGroupState_paused)
+ subscription.pause$0();
+ return subscription;
+ },
+ close$0: function(_) {
+ var t1, _this = this;
+ if (_this._stream_group$_closed)
+ return _this._stream_group$_controller._ensureDoneFuture$0();
+ _this._stream_group$_closed = true;
+ t1 = _this._stream_group$_subscriptions;
+ if (t1.get$isEmpty(t1))
+ _this._stream_group$_controller.close$0(0);
+ return _this._stream_group$_controller._ensureDoneFuture$0();
+ },
+ set$_stream_group$_controller: function(_controller) {
+ this._stream_group$_controller = this.$ti._eval$1("StreamController<1>")._check(_controller);
+ },
+ $isSink: 1
+ };
+ L.StreamGroup_add_closure.prototype = {
+ call$0: function() {
+ return null;
+ },
+ $signature: 0
+ };
+ L.StreamGroup_add_closure0.prototype = {
+ call$0: function() {
+ return this.$this._listenToStream$1(this.stream);
+ },
+ $signature: function() {
+ return this.$this.$ti._eval$1("StreamSubscription<1>()");
+ }
+ };
+ L.StreamGroup__onListen_closure.prototype = {
+ call$2: function(stream, subscription) {
+ var t1 = this.$this,
+ t2 = t1.$ti;
+ t2._eval$1("Stream<1>")._check(stream);
+ if (t2._eval$1("StreamSubscription<1>")._check(subscription) != null)
+ return;
+ t1._stream_group$_subscriptions.$indexSet(0, stream, t1._listenToStream$1(stream));
+ },
+ $signature: function() {
+ return this.$this.$ti._eval$1("Null(Stream<1>,StreamSubscription<1>)");
+ }
+ };
+ L.StreamGroup__onCancelBroadcast_closure.prototype = {
+ call$2: function(stream, subscription) {
+ var t1 = this.$this,
+ t2 = t1.$ti;
+ t2._eval$1("Stream<1>")._check(stream);
+ t2._eval$1("StreamSubscription<1>")._check(subscription);
+ if (!stream.get$isBroadcast())
+ return;
+ subscription.cancel$0();
+ t1._stream_group$_subscriptions.$indexSet(0, stream, null);
+ },
+ $signature: function() {
+ return this.$this.$ti._eval$1("Null(Stream<1>,StreamSubscription<1>)");
+ }
+ };
+ L.StreamGroup__listenToStream_closure.prototype = {
+ call$0: function() {
+ var t1 = this.$this,
+ t2 = t1._stream_group$_subscriptions,
+ subscription = t2.remove$1(0, t1.$ti._eval$1("Stream<1>")._check(this.stream)),
+ future = subscription == null ? null : subscription.cancel$0();
+ if (t1._stream_group$_closed && t2.get$isEmpty(t2))
+ t1._stream_group$_controller.close$0(0);
+ return future;
+ },
+ $signature: 14
+ };
+ L._StreamGroupState.prototype = {
+ toString$0: function(_) {
+ return this.name;
+ }
+ };
+ G.StreamQueue.prototype = {
+ get$next: function() {
+ var t1, t2, _this = this;
+ if (!_this._isClosed) {
+ t1 = _this.$ti;
+ t2 = new P._Future($.Zone__current, t1._eval$1("_Future<1>"));
+ _this._addRequest$1(new G._NextRequest(new P._AsyncCompleter(t2, t1._eval$1("_AsyncCompleter<1>")), t1._eval$1("_NextRequest<1>")));
+ return t2;
+ }
+ throw H.wrapException(_this._failClosed$0());
+ },
+ _updateRequests$0: function() {
+ var t1, t2, t3, t4, _this = this;
+ for (t1 = _this._requestQueue, t2 = _this._eventQueue; !t1.get$isEmpty(t1);) {
+ t3 = t1._head;
+ if (t3 === t1._tail)
+ H.throwExpression(H.IterableElementError_noElement());
+ t4 = t1._table;
+ if (t3 >= t4.length)
+ return H.ioore(t4, t3);
+ if (t4[t3].update$2(t2, _this._isDone))
+ t1.removeFirst$0();
+ else
+ return;
+ }
+ if (!_this._isDone)
+ _this._stream_queue$_subscription.pause$0();
+ },
+ _extractStream$0: function() {
+ var t1, wasPaused, _this = this, _null = null;
+ H.assertHelper(_this._isClosed);
+ if (_this._isDone)
+ return new P._EmptyStream(_this.$ti._eval$1("_EmptyStream<1>"));
+ _this._isDone = true;
+ t1 = _this._stream_queue$_subscription;
+ if (t1 == null)
+ return _this._stream_queue$_source;
+ _this.set$_stream_queue$_subscription(_null);
+ wasPaused = t1.get$isPaused();
+ t1.pause$0();
+ t1.onData$1(_null);
+ t1.onError$1(0, _null);
+ t1.onDone$1(_null);
+ if (wasPaused)
+ t1.resume$0();
+ return new T.SubscriptionStream(t1, _this.$ti._eval$1("SubscriptionStream<1>"));
+ },
+ _ensureListening$0: function() {
+ var t1, _this = this;
+ if (_this._isDone)
+ return;
+ t1 = _this._stream_queue$_subscription;
+ if (t1 == null)
+ _this.set$_stream_queue$_subscription(_this._stream_queue$_source.listen$3$onDone$onError(new G.StreamQueue__ensureListening_closure(_this), new G.StreamQueue__ensureListening_closure0(_this), new G.StreamQueue__ensureListening_closure1(_this)));
+ else
+ t1.resume$0();
+ },
+ _addResult$1: function(result) {
+ var t1, _this = this;
+ _this.$ti._eval$1("Result<1>")._check(result);
+ ++_this._eventsReceived;
+ t1 = _this._eventQueue;
+ t1._queue_list$_add$1(t1.$ti._precomputed1._check(result));
+ _this._updateRequests$0();
+ },
+ _failClosed$0: function() {
+ return new P.StateError("Already cancelled");
+ },
+ _addRequest$1: function(request) {
+ var t1, _this = this;
+ _this.$ti._eval$1("_EventRequest<1>")._check(request);
+ t1 = _this._requestQueue;
+ if (t1._head === t1._tail) {
+ if (request.update$2(_this._eventQueue, _this._isDone))
+ return;
+ _this._ensureListening$0();
+ }
+ t1._collection$_add$1(t1.$ti._precomputed1._check(request));
+ },
+ set$_stream_queue$_subscription: function(_subscription) {
+ this._stream_queue$_subscription = this.$ti._eval$1("StreamSubscription<1>")._check(_subscription);
+ }
+ };
+ G.StreamQueue__ensureListening_closure.prototype = {
+ call$1: function(data) {
+ var t1 = this.$this,
+ t2 = t1.$ti;
+ t1._addResult$1(new F.ValueResult(t2._precomputed1._check(data), t2._eval$1("ValueResult<1>")));
+ },
+ $signature: function() {
+ return this.$this.$ti._eval$1("Null(1)");
+ }
+ };
+ G.StreamQueue__ensureListening_closure1.prototype = {
+ call$2: function(error, stackTrace) {
+ this.$this._addResult$1(new V.ErrorResult(error, type$.StackTrace._check(stackTrace)));
+ },
+ $signature: 5
+ };
+ G.StreamQueue__ensureListening_closure0.prototype = {
+ call$0: function() {
+ var t1 = this.$this;
+ t1.set$_stream_queue$_subscription(null);
+ t1._isDone = true;
+ t1._updateRequests$0();
+ },
+ $signature: 0
+ };
+ G._EventRequest.prototype = {};
+ G._NextRequest.prototype = {
+ update$2: function(events, isDone) {
+ this.$ti._eval$1("QueueList<Result<1>>")._check(events);
+ if (events.get$length(events) !== 0) {
+ events.removeFirst$0().complete$1(this._completer);
+ return true;
+ }
+ if (isDone) {
+ this._completer.completeError$2(new P.StateError("No elements"), P.StackTrace_current());
+ return true;
+ }
+ return false;
+ },
+ $is_EventRequest: 1
+ };
+ G._RestRequest.prototype = {
+ update$2: function(events, isDone) {
+ var t2, controller, _this = this,
+ t1 = _this.$ti;
+ t1._eval$1("QueueList<Result<1>>")._check(events);
+ if (events.get$length(events) === 0) {
+ t1 = _this._streamQueue;
+ t2 = _this._completer;
+ if (t1._isDone) {
+ t1 = t2._stream;
+ t2 = t1._sourceStream == null;
+ if (!t2)
+ H.throwExpression(P.StateError$("Source stream already set"));
+ H.assertHelper(t2);
+ if (t1._stream_completer$_controller == null)
+ t1._createController$0();
+ t2 = t1._stream_completer$_controller;
+ t2.toString;
+ t1.set$_sourceStream(new P._ControllerStream(t2, H._instanceType(t2)._eval$1("_ControllerStream<1>")));
+ t1._stream_completer$_controller.close$0(0);
+ } else
+ t2.setSourceStream$1(t1._extractStream$0());
+ } else {
+ controller = P.StreamController_StreamController(null, null, false, t1._precomputed1);
+ for (t1 = new H.ListIterator(events, events.get$length(events), events.$ti._eval$1("ListIterator<ListMixin.E>")); t1.moveNext$0();)
+ t1.__internal$_current.addTo$1(controller);
+ controller.addStream$2$cancelOnError(_this._streamQueue._extractStream$0(), false).whenComplete$1(controller.get$close(controller));
+ _this._completer.setSourceStream$1(new P._ControllerStream(controller, H._instanceType(controller)._eval$1("_ControllerStream<1>")));
+ }
+ return true;
+ },
+ $is_EventRequest: 1
+ };
+ T.StreamSinkCompleter.prototype = {};
+ T._CompleterSink.prototype = {
+ get$_canSendDirectly: function() {
+ return this._stream_sink_completer$_controller == null && this._destinationSink != null;
+ },
+ get$done: function() {
+ var t1 = this._stream_sink_completer$_doneCompleter;
+ if (t1 != null)
+ return t1.future;
+ t1 = this._destinationSink;
+ if (t1 == null) {
+ t1 = new P._Future($.Zone__current, type$._Future_dynamic);
+ this._stream_sink_completer$_doneCompleter = new P._SyncCompleter(t1, type$._SyncCompleter_dynamic);
+ return t1;
+ }
+ return t1.get$done();
+ },
+ addStream$1: function(stream) {
+ var _this = this;
+ _this.$ti._eval$1("Stream<1>")._check(stream);
+ if (_this.get$_canSendDirectly())
+ return _this._destinationSink.addStream$1(stream);
+ _this._ensureController$0();
+ return _this._stream_sink_completer$_controller.addStream$2$cancelOnError(stream, false);
+ },
+ close$0: function(_) {
+ var _this = this;
+ if (_this.get$_canSendDirectly())
+ _this._destinationSink.close$0(0);
+ else {
+ _this._ensureController$0();
+ _this._stream_sink_completer$_controller.close$0(0);
+ }
+ return _this.get$done();
+ },
+ _ensureController$0: function() {
+ if (this._stream_sink_completer$_controller == null)
+ this.set$_stream_sink_completer$_controller(P.StreamController_StreamController(null, null, true, this.$ti._precomputed1));
+ },
+ _setDestinationSink$1: function(sink) {
+ var t1, _this = this;
+ _this.$ti._eval$1("StreamSink<1>")._check(sink);
+ H.assertHelper(_this._destinationSink == null);
+ _this.set$_destinationSink(sink);
+ t1 = _this._stream_sink_completer$_controller;
+ if (t1 != null)
+ sink.addStream$1(new P._ControllerStream(t1, H._instanceType(t1)._eval$1("_ControllerStream<1>"))).whenComplete$1(sink.get$close(sink)).catchError$1(new T._CompleterSink__setDestinationSink_closure());
+ t1 = _this._stream_sink_completer$_doneCompleter;
+ if (t1 != null)
+ t1.complete$1(sink.get$done());
+ },
+ set$_stream_sink_completer$_controller: function(_controller) {
+ this._stream_sink_completer$_controller = this.$ti._eval$1("StreamController<1>")._check(_controller);
+ },
+ set$_destinationSink: function(_destinationSink) {
+ this._destinationSink = this.$ti._eval$1("StreamSink<1>")._check(_destinationSink);
+ },
+ $isEventSink: 1,
+ $isStreamConsumer: 1,
+ $isStreamSink: 1,
+ $isSink: 1
+ };
+ T._CompleterSink__setDestinationSink_closure.prototype = {
+ call$1: function(_) {
+ },
+ $signature: 3
+ };
+ T.SubscriptionStream.prototype = {
+ listen$4$cancelOnError$onDone$onError: function(onData, cancelOnError, onDone, onError) {
+ var result, result0,
+ t1 = this.$ti;
+ t1._eval$1("~(1)")._check(onData);
+ type$.void_Function._check(onDone);
+ H._checkBoolNullable(cancelOnError);
+ result = this._subscription_stream$_source;
+ if (result == null)
+ throw H.wrapException(P.StateError$("Stream has already been listened to."));
+ this.set$_subscription_stream$_source(null);
+ result0 = true === cancelOnError ? new T._CancelOnErrorSubscriptionWrapper(result, t1._eval$1("_CancelOnErrorSubscriptionWrapper<1>")) : result;
+ result0.onData$1(onData);
+ result0.onError$1(0, onError);
+ result0.onDone$1(onDone);
+ result.resume$0();
+ return result0;
+ },
+ listen$3$onDone$onError: function(onData, onDone, onError) {
+ return this.listen$4$cancelOnError$onDone$onError(onData, null, onDone, onError);
+ },
+ listen$1: function(onData) {
+ return this.listen$4$cancelOnError$onDone$onError(onData, null, null, null);
+ },
+ set$_subscription_stream$_source: function(_source) {
+ this._subscription_stream$_source = this.$ti._eval$1("StreamSubscription<1>")._check(_source);
+ }
+ };
+ T._CancelOnErrorSubscriptionWrapper.prototype = {
+ onError$1: function(_, handleError) {
+ this.super$DelegatingStreamSubscription$onError(0, new T._CancelOnErrorSubscriptionWrapper_onError_closure(this, handleError));
+ }
+ };
+ T._CancelOnErrorSubscriptionWrapper_onError_closure.prototype = {
+ call$2: function(error, stackTrace) {
+ var cancelFuture, t1;
+ type$.StackTrace._check(stackTrace);
+ cancelFuture = this.$this.super$DelegatingStreamSubscription$cancel();
+ if (cancelFuture != null)
+ cancelFuture.whenComplete$1(new T._CancelOnErrorSubscriptionWrapper_onError__closure(this.handleError, error, stackTrace));
+ else {
+ t1 = this.handleError;
+ if (type$.dynamic_Function_dynamic_dynamic._is(t1))
+ t1.call$2(error, stackTrace);
+ else
+ t1.call$1(error);
+ }
+ },
+ $signature: 5
+ };
+ T._CancelOnErrorSubscriptionWrapper_onError__closure.prototype = {
+ call$0: function() {
+ var t1 = this.handleError,
+ t2 = this.error;
+ if (type$.dynamic_Function_dynamic_dynamic._is(t1))
+ t1.call$2(t2, this.stackTrace);
+ else
+ t1.call$1(t2);
+ },
+ $signature: 0
+ };
+ X.BooleanSelector.prototype = {};
+ X.All.prototype = {
+ evaluate$1: function(semantics) {
+ return true;
+ },
+ intersection$1: function(other) {
+ return other;
+ },
+ validate$1: function(isDefined) {
+ type$.bool_Function_String._check(isDefined);
+ },
+ toString$0: function(_) {
+ return "<all>";
+ },
+ $isBooleanSelector: 1
+ };
+ U.VariableNode.prototype = {
+ accept$1: function(visitor) {
+ return visitor.visitVariable$1(this);
+ },
+ toString$0: function(_) {
+ return this.name;
+ },
+ $eq: function(_, other) {
+ if (other == null)
+ return false;
+ return other instanceof U.VariableNode && this.name == other.name;
+ },
+ get$hashCode: function(_) {
+ return J.get$hashCode$(this.name);
+ },
+ $isNode: 1,
+ get$span: function() {
+ return this.span;
+ }
+ };
+ U.NotNode.prototype = {
+ accept$1: function(visitor) {
+ return visitor.visitNot$1(this);
+ },
+ toString$0: function(_) {
+ var t1 = this.child;
+ return t1 instanceof U.VariableNode || t1 instanceof U.NotNode ? "!" + t1.toString$0(0) : "!(" + t1.toString$0(0) + ")";
+ },
+ $eq: function(_, other) {
+ if (other == null)
+ return false;
+ return other instanceof U.NotNode && this.child.$eq(0, other.child);
+ },
+ get$hashCode: function(_) {
+ var t1 = this.child;
+ return ~t1.get$hashCode(t1) >>> 0;
+ },
+ $isNode: 1,
+ get$span: function() {
+ return this.span;
+ }
+ };
+ U.OrNode.prototype = {
+ get$span: function() {
+ return U._expandSafe(this.left.get$span(), this.right.get$span());
+ },
+ accept$1: function(visitor) {
+ return visitor.visitOr$1(this);
+ },
+ toString$0: function(_) {
+ var string2,
+ string1 = this.left;
+ if (string1 instanceof U.AndNode || string1 instanceof U.ConditionalNode)
+ string1 = "(" + string1.toString$0(0) + ")";
+ string2 = this.right;
+ if (string2 instanceof U.AndNode || string2 instanceof U.ConditionalNode)
+ string2 = "(" + string2.toString$0(0) + ")";
+ return H.S(string1) + " || " + H.S(string2);
+ },
+ $eq: function(_, other) {
+ if (other == null)
+ return false;
+ return other instanceof U.OrNode && this.left.$eq(0, other.left) && this.right.$eq(0, other.right);
+ },
+ get$hashCode: function(_) {
+ var t1 = this.left,
+ t2 = this.right;
+ return (t1.get$hashCode(t1) ^ t2.get$hashCode(t2)) >>> 0;
+ },
+ $isNode: 1
+ };
+ U.AndNode.prototype = {
+ get$span: function() {
+ return U._expandSafe(this.left.get$span(), this.right.get$span());
+ },
+ accept$1: function(visitor) {
+ return visitor.visitAnd$1(this);
+ },
+ toString$0: function(_) {
+ var string2,
+ string1 = this.left;
+ if (string1 instanceof U.OrNode || string1 instanceof U.ConditionalNode)
+ string1 = "(" + string1.toString$0(0) + ")";
+ string2 = this.right;
+ if (string2 instanceof U.OrNode || string2 instanceof U.ConditionalNode)
+ string2 = "(" + string2.toString$0(0) + ")";
+ return H.S(string1) + " && " + H.S(string2);
+ },
+ $eq: function(_, other) {
+ if (other == null)
+ return false;
+ return other instanceof U.AndNode && this.left.$eq(0, other.left) && this.right.$eq(0, other.right);
+ },
+ get$hashCode: function(_) {
+ var t1 = this.left,
+ t2 = this.right;
+ return (t1.get$hashCode(t1) ^ t2.get$hashCode(t2)) >>> 0;
+ },
+ $isNode: 1
+ };
+ U.ConditionalNode.prototype = {
+ get$span: function() {
+ return U._expandSafe(this.condition.get$span(), this.whenFalse.get$span());
+ },
+ accept$1: function(visitor) {
+ return visitor.visitConditional$1(this);
+ },
+ toString$0: function(_) {
+ var trueString,
+ conditionString = this.condition;
+ if (conditionString instanceof U.ConditionalNode)
+ conditionString = "(" + conditionString.toString$0(0) + ")";
+ trueString = this.whenTrue;
+ if (trueString instanceof U.ConditionalNode)
+ trueString = "(" + trueString.toString$0(0) + ")";
+ return H.S(conditionString) + " ? " + H.S(trueString) + " : " + this.whenFalse.toString$0(0);
+ },
+ $eq: function(_, other) {
+ if (other == null)
+ return false;
+ return other instanceof U.ConditionalNode && this.condition.$eq(0, other.condition) && this.whenTrue.$eq(0, other.whenTrue) && this.whenFalse.$eq(0, other.whenFalse);
+ },
+ get$hashCode: function(_) {
+ var t1 = this.condition,
+ t2 = this.whenTrue,
+ t3 = this.whenFalse;
+ return (t1.get$hashCode(t1) ^ t2.get$hashCode(t2) ^ t3.get$hashCode(t3)) >>> 0;
+ },
+ $isNode: 1
+ };
+ T.Evaluator.prototype = {
+ visitVariable$1: function(node) {
+ return this._semantics.call$1(node.name);
+ },
+ visitNot$1: function(node) {
+ return !H.boolConversionCheck(node.child.accept$1(this));
+ },
+ visitOr$1: function(node) {
+ return H.boolConversionCheck(node.left.accept$1(this)) || H.boolConversionCheck(node.right.accept$1(this));
+ },
+ visitAnd$1: function(node) {
+ return H.boolConversionCheck(node.left.accept$1(this)) && H.boolConversionCheck(node.right.accept$1(this));
+ },
+ visitConditional$1: function(node) {
+ return H.boolConversionCheck(node.condition.accept$1(this)) ? node.whenTrue.accept$1(this) : node.whenFalse.accept$1(this);
+ },
+ $isVisitor: 1
+ };
+ Y.BooleanSelectorImpl.prototype = {
+ evaluate$1: function(semantics) {
+ var t1;
+ if (type$.Iterable_dynamic._is(semantics)) {
+ t1 = semantics.toSet$0(0);
+ t1 = t1.get$contains(t1);
+ } else {
+ type$.bool_Function_String._as(semantics);
+ t1 = semantics;
+ }
+ return this._selector.accept$1(new T.Evaluator(t1));
+ },
+ intersection$1: function(other) {
+ var t1 = J.getInterceptor$(other);
+ if (t1.$eq(other, C.C_All))
+ return this;
+ if (t1.$eq(other, C.C_None))
+ return other;
+ return other instanceof Y.BooleanSelectorImpl ? new Y.BooleanSelectorImpl(new U.AndNode(this._selector, other._selector)) : new R.IntersectionSelector(this, other);
+ },
+ validate$1: function(isDefined) {
+ this._selector.accept$1(new S.Validator(type$.bool_Function_String._check(isDefined)));
+ },
+ toString$0: function(_) {
+ return this._selector.toString$0(0);
+ },
+ $eq: function(_, other) {
+ if (other == null)
+ return false;
+ return other instanceof Y.BooleanSelectorImpl && this._selector.$eq(0, other._selector);
+ },
+ get$hashCode: function(_) {
+ var t1 = this._selector;
+ return t1.get$hashCode(t1);
+ },
+ $isBooleanSelector: 1
+ };
+ R.IntersectionSelector.prototype = {
+ evaluate$1: function(semantics) {
+ return H.boolConversionCheck(this._selector1.evaluate$1(semantics)) && H.boolConversionCheck(this._selector2.evaluate$1(semantics));
+ },
+ intersection$1: function(other) {
+ return new R.IntersectionSelector(this, other);
+ },
+ validate$1: function(isDefined) {
+ type$.bool_Function_String._check(isDefined);
+ this._selector1.validate$1(isDefined);
+ this._selector2.validate$1(isDefined);
+ },
+ toString$0: function(_) {
+ return "(" + this._selector1.toString$0(0) + ") && (" + H.S(this._selector2) + ")";
+ },
+ $eq: function(_, other) {
+ if (other == null)
+ return false;
+ return other instanceof R.IntersectionSelector && this._selector1.$eq(0, other._selector1) && J.$eq$(this._selector2, other._selector2);
+ },
+ get$hashCode: function(_) {
+ var t1 = this._selector1;
+ return (t1.get$hashCode(t1) ^ J.get$hashCode$(this._selector2)) >>> 0;
+ },
+ $isBooleanSelector: 1
+ };
+ O.None.prototype = {
+ evaluate$1: function(semantics) {
+ return false;
+ },
+ intersection$1: function(other) {
+ return this;
+ },
+ validate$1: function(isDefined) {
+ type$.bool_Function_String._check(isDefined);
+ },
+ toString$0: function(_) {
+ return "<none>";
+ },
+ $isBooleanSelector: 1
+ };
+ G.Parser.prototype = {
+ parse$0: function() {
+ var selector = this._conditional$0(),
+ t1 = this._parser0$_scanner,
+ t2 = t1.peek$0();
+ if (t2.get$type(t2) !== C.TokenType_wwi)
+ throw H.wrapException(G.SourceSpanFormatException$("Expected end of input.", t1.peek$0().get$span(), null));
+ return selector;
+ },
+ _conditional$0: function() {
+ var whenTrue, _this = this,
+ condition = _this._or$0(),
+ t1 = _this._parser0$_scanner;
+ if (!t1.scan$1(C.TokenType_69P))
+ return condition;
+ whenTrue = _this._conditional$0();
+ if (!t1.scan$1(C.TokenType_colon))
+ throw H.wrapException(G.SourceSpanFormatException$('Expected ":".', t1.peek$0().get$span(), null));
+ return new U.ConditionalNode(condition, whenTrue, _this._conditional$0());
+ },
+ _or$0: function() {
+ var left = this._and$0();
+ if (!this._parser0$_scanner.scan$1(C.TokenType_or))
+ return left;
+ return new U.OrNode(left, this._or$0());
+ },
+ _and$0: function() {
+ var left = this._simpleExpression$0();
+ if (!this._parser0$_scanner.scan$1(C.TokenType_and))
+ return left;
+ return new U.AndNode(left, this._and$0());
+ },
+ _simpleExpression$0: function() {
+ var child,
+ t1 = this._parser0$_scanner,
+ token = t1.next$0();
+ switch (token.get$type(token)) {
+ case C.TokenType_not:
+ child = this._simpleExpression$0();
+ return new U.NotNode(token.get$span().expand$1(0, child.get$span()), child);
+ case C.TokenType_e7P:
+ child = this._conditional$0();
+ if (!t1.scan$1(C.TokenType_31K))
+ throw H.wrapException(G.SourceSpanFormatException$('Expected ")".', t1.peek$0().get$span(), null));
+ return child;
+ case C.TokenType_identifier:
+ type$.IdentifierToken._as(token);
+ return new U.VariableNode(token.span, token.name);
+ default:
+ throw H.wrapException(G.SourceSpanFormatException$("Expected expression.", token.get$span(), null));
+ }
+ }
+ };
+ O.Scanner.prototype = {
+ peek$0: function() {
+ var t1 = this._scanner$_next;
+ return t1 == null ? this._scanner$_next = this._getNext$0() : t1;
+ },
+ next$0: function() {
+ var _this = this,
+ token = _this._scanner$_next;
+ if (token == null)
+ token = _this._getNext$0();
+ _this._endOfFileEmitted = token.get$type(token) === C.TokenType_wwi;
+ _this._scanner$_next = null;
+ return token;
+ },
+ scan$1: function(type) {
+ var t1 = this.peek$0();
+ if (t1.get$type(t1) !== type)
+ return false;
+ this.next$0();
+ return true;
+ },
+ _getNext$0: function() {
+ var t1, t2, _this = this;
+ if (_this._endOfFileEmitted)
+ throw H.wrapException(P.StateError$("No more tokens."));
+ _this._consumeWhitespace$0();
+ t1 = _this._scanner;
+ t2 = t1._string_scanner$_position;
+ if (t2 === t1.string.length)
+ return new L.Token(C.TokenType_wwi, t1.spanFrom$1(new S._SpanScannerState(t1, t2)));
+ switch (t1.peekChar$0()) {
+ case 40:
+ return _this._scanOperator$1(C.TokenType_e7P);
+ case 41:
+ return _this._scanOperator$1(C.TokenType_31K);
+ case 63:
+ return _this._scanOperator$1(C.TokenType_69P);
+ case 58:
+ return _this._scanOperator$1(C.TokenType_colon);
+ case 33:
+ return _this._scanOperator$1(C.TokenType_not);
+ case 124:
+ t2 = t1._string_scanner$_position;
+ t1.expect$1("||");
+ return new L.Token(C.TokenType_or, t1.spanFrom$1(new S._SpanScannerState(t1, t2)));
+ case 38:
+ t2 = t1._string_scanner$_position;
+ t1.expect$1("&&");
+ return new L.Token(C.TokenType_and, t1.spanFrom$1(new S._SpanScannerState(t1, t2)));
+ default:
+ t1.expect$2$name($.$get$_hyphenatedIdentifier0(), "expression");
+ t2 = t1.get$lastMatch().$index(0, 0);
+ if (t1.get$lastMatch() == null)
+ t1._lastSpan = null;
+ return new L.IdentifierToken(t1._lastSpan, t2);
+ }
+ },
+ _scanOperator$1: function(type) {
+ var t1 = this._scanner,
+ t2 = t1._string_scanner$_position,
+ t3 = t1.string;
+ if (t2 === t3.length)
+ t1.error$3$length$position(0, "expected more input.", 0, t2);
+ J.codeUnitAt$1$s(t3, t1._string_scanner$_position++);
+ return new L.Token(type, t1.spanFrom$1(new S._SpanScannerState(t1, t2)));
+ },
+ _consumeWhitespace$0: function() {
+ var success,
+ t1 = this._scanner;
+ while (true) {
+ success = t1.matches$1(0, $.$get$_whitespaceAndSingleLineComments());
+ if (success)
+ t1._lastMatchPosition = t1._string_scanner$_position = t1._lastMatch.get$end();
+ if (!(success || this._multiLineComment$0()))
+ break;
+ }
+ },
+ _multiLineComment$0: function() {
+ var success,
+ t1 = this._scanner;
+ if (!t1.scan$1("/*"))
+ return false;
+ while (true) {
+ success = t1.matches$1(0, $.$get$_multiLineCommentBody());
+ if (success)
+ t1._lastMatchPosition = t1._string_scanner$_position = t1._lastMatch.get$end();
+ if (!(success || this._multiLineComment$0()))
+ break;
+ }
+ t1.expect$1("*/");
+ return true;
+ }
+ };
+ L.Token.prototype = {
+ get$type: function(receiver) {
+ return this.type;
+ },
+ get$span: function() {
+ return this.span;
+ }
+ };
+ L.IdentifierToken.prototype = {
+ toString$0: function(_) {
+ return 'identifier "' + H.S(this.name) + '"';
+ },
+ $isToken: 1,
+ get$type: function() {
+ return C.TokenType_identifier;
+ },
+ get$span: function() {
+ return this.span;
+ }
+ };
+ L.TokenType.prototype = {
+ toString$0: function(_) {
+ return this.name;
+ }
+ };
+ S.Validator.prototype = {
+ visitVariable$1: function(node) {
+ if (H.boolConversionCheck(this._isDefined.call$1(node.name)))
+ return;
+ throw H.wrapException(G.SourceSpanFormatException$("Undefined variable.", node.span, null));
+ }
+ };
+ B.RecursiveVisitor.prototype = {
+ visitNot$1: function(node) {
+ node.child.accept$1(this);
+ },
+ visitOr$1: function(node) {
+ node.left.accept$1(this);
+ node.right.accept$1(this);
+ },
+ visitAnd$1: function(node) {
+ node.left.accept$1(this);
+ node.right.accept$1(this);
+ },
+ visitConditional$1: function(node) {
+ node.condition.accept$1(this);
+ node.whenTrue.accept$1(this);
+ node.whenFalse.accept$1(this);
+ },
+ $isVisitor: 1
+ };
+ O.EmptyUnmodifiableSet.prototype = {
+ get$iterator: function(_) {
+ return C.C_EmptyIterator;
+ },
+ get$length: function(_) {
+ return 0;
+ },
+ contains$1: function(_, element) {
+ return false;
+ },
+ toSet$0: function(_) {
+ return P.LinkedHashSet_LinkedHashSet(this.$ti._precomputed1);
+ },
+ $isEfficientLengthIterable: 1,
+ $isSet: 1
+ };
+ Y.mapMap_closure.prototype = {
+ call$2: function(mapKey, _) {
+ this.K1._check(mapKey);
+ this.V1._check(_);
+ return this.K2._as(mapKey);
+ },
+ $signature: function() {
+ return this.K2._eval$1("@<0>")._bind$1(this.K1)._bind$1(this.V1)._eval$1("1(2,3)");
+ }
+ };
+ Y.mapMap_closure0.prototype = {
+ call$2: function(mapKey, mapValue) {
+ var t1, _this = this;
+ _this.K1._check(mapKey);
+ _this.V1._check(mapValue);
+ t1 = _this._box_0;
+ _this.result.$indexSet(0, t1.key.call$2(mapKey, mapValue), t1.value.call$2(mapKey, mapValue));
+ },
+ $signature: function() {
+ return this.K1._eval$1("@<0>")._bind$1(this.V1)._eval$1("Null(1,2)");
+ }
+ };
+ Y.mergeMaps_closure.prototype = {
+ call$2: function(key, mapValue) {
+ var t1, _this = this;
+ _this.K._check(key);
+ _this.V._check(mapValue);
+ t1 = _this.result;
+ t1.$indexSet(0, key, t1.containsKey$1(key) ? _this.value.call$2(t1.$index(0, key), mapValue) : mapValue);
+ },
+ $signature: function() {
+ return this.K._eval$1("@<0>")._bind$1(this.V)._eval$1("Null(1,2)");
+ }
+ };
+ Q.QueueList.prototype = {
+ toString$0: function(_) {
+ return P.IterableBase_iterableToFullString(this, "{", "}");
+ },
+ removeFirst$0: function() {
+ var t2, result, _this = this,
+ t1 = _this._queue_list$_head;
+ if (t1 === _this._queue_list$_tail)
+ throw H.wrapException(P.StateError$("No element"));
+ t2 = _this._queue_list$_table;
+ if (t1 >= t2.length)
+ return H.ioore(t2, t1);
+ result = t2[t1];
+ C.JSArray_methods.$indexSet(t2, t1, null);
+ _this._queue_list$_head = (_this._queue_list$_head + 1 & _this._queue_list$_table.length - 1) >>> 0;
+ return result;
+ },
+ get$length: function(_) {
+ return (this._queue_list$_tail - this._queue_list$_head & this._queue_list$_table.length - 1) >>> 0;
+ },
+ set$length: function(_, value) {
+ var delta, t1, newTail, t2, _this = this;
+ if (value < 0)
+ throw H.wrapException(P.RangeError$("Length " + value + " may not be negative."));
+ delta = value - _this.get$length(_this);
+ if (delta >= 0) {
+ if (_this._queue_list$_table.length <= value)
+ _this._preGrow$1(value);
+ _this._queue_list$_tail = (_this._queue_list$_tail + delta & _this._queue_list$_table.length - 1) >>> 0;
+ return;
+ }
+ t1 = _this._queue_list$_tail;
+ newTail = t1 + delta;
+ t2 = _this._queue_list$_table;
+ if (newTail >= 0)
+ C.JSArray_methods.fillRange$3(t2, newTail, t1, null);
+ else {
+ newTail += t2.length;
+ C.JSArray_methods.fillRange$3(t2, 0, t1, null);
+ t1 = _this._queue_list$_table;
+ C.JSArray_methods.fillRange$3(t1, newTail, t1.length, null);
+ }
+ _this._queue_list$_tail = newTail;
+ },
+ $index: function(_, index) {
+ var t1, t2, t3, _this = this;
+ H._checkIntNullable(index);
+ if (index < 0 || index >= _this.get$length(_this))
+ throw H.wrapException(P.RangeError$("Index " + index + " must be in the range [0.." + _this.get$length(_this) + ")."));
+ t1 = _this._queue_list$_table;
+ t2 = t1.length;
+ t3 = (_this._queue_list$_head + index & t2 - 1) >>> 0;
+ if (t3 < 0 || t3 >= t2)
+ return H.ioore(t1, t3);
+ return t1[t3];
+ },
+ $indexSet: function(_, index, value) {
+ var t1, _this = this;
+ H._checkIntNullable(index);
+ _this.$ti._precomputed1._check(value);
+ if (typeof index !== "number")
+ return index.$lt();
+ if (index < 0 || index >= _this.get$length(_this))
+ throw H.wrapException(P.RangeError$("Index " + index + " must be in the range [0.." + _this.get$length(_this) + ")."));
+ t1 = _this._queue_list$_table;
+ C.JSArray_methods.$indexSet(t1, (_this._queue_list$_head + index & t1.length - 1) >>> 0, value);
+ },
+ _queue_list$_add$1: function(element) {
+ var t2, t3, newTable, split, _this = this,
+ t1 = _this.$ti;
+ t1._precomputed1._check(element);
+ C.JSArray_methods.$indexSet(_this._queue_list$_table, _this._queue_list$_tail, element);
+ t2 = _this._queue_list$_tail;
+ t3 = _this._queue_list$_table.length;
+ t2 = (t2 + 1 & t3 - 1) >>> 0;
+ _this._queue_list$_tail = t2;
+ if (_this._queue_list$_head === t2) {
+ t2 = new Array(t3 * 2);
+ t2.fixed$length = Array;
+ newTable = H.setRuntimeTypeInfo(t2, t1._eval$1("JSArray<1>"));
+ t1 = _this._queue_list$_table;
+ t2 = _this._queue_list$_head;
+ split = t1.length - t2;
+ C.JSArray_methods.setRange$4(newTable, 0, split, t1, t2);
+ C.JSArray_methods.setRange$4(newTable, split, split + _this._queue_list$_head, _this._queue_list$_table, 0);
+ _this._queue_list$_head = 0;
+ _this._queue_list$_tail = _this._queue_list$_table.length;
+ _this.set$_queue_list$_table(newTable);
+ }
+ },
+ _writeToList$1: function(target) {
+ var t1, t2, t3, $length, firstPartSize, _this = this;
+ _this.$ti._eval$1("List<1>")._check(target);
+ H.assertHelper(target.length >= _this.get$length(_this));
+ t1 = _this._queue_list$_head;
+ t2 = _this._queue_list$_tail;
+ t3 = _this._queue_list$_table;
+ if (t1 <= t2) {
+ $length = t2 - t1;
+ C.JSArray_methods.setRange$4(target, 0, $length, t3, t1);
+ return $length;
+ } else {
+ firstPartSize = t3.length - t1;
+ C.JSArray_methods.setRange$4(target, 0, firstPartSize, t3, t1);
+ C.JSArray_methods.setRange$4(target, firstPartSize, firstPartSize + _this._queue_list$_tail, _this._queue_list$_table, 0);
+ return _this._queue_list$_tail + firstPartSize;
+ }
+ },
+ _preGrow$1: function(newElementCount) {
+ var newCapacity, t1, newTable, _this = this;
+ H.assertHelper(newElementCount >= _this.get$length(_this));
+ newCapacity = Q.QueueList__nextPowerOf2(newElementCount + C.JSInt_methods._shrOtherPositive$1(newElementCount, 1));
+ if (typeof newCapacity !== "number")
+ return H.iae(newCapacity);
+ t1 = new Array(newCapacity);
+ t1.fixed$length = Array;
+ newTable = H.setRuntimeTypeInfo(t1, _this.$ti._eval$1("JSArray<1>"));
+ _this._queue_list$_tail = _this._writeToList$1(newTable);
+ _this.set$_queue_list$_table(newTable);
+ _this._queue_list$_head = 0;
+ },
+ set$_queue_list$_table: function(_table) {
+ this._queue_list$_table = this.$ti._eval$1("List<1>")._check(_table);
+ },
+ $isEfficientLengthIterable: 1,
+ $isQueue: 1,
+ $isIterable: 1,
+ $isList: 1
+ };
+ Q._QueueList_Object_ListMixin.prototype = {};
+ M.UnionSet.prototype = {
+ get$length: function(_) {
+ var t1 = this._sets.fold$1$2(0, 0, new M.UnionSet_length_closure(this), type$.int);
+ return t1;
+ },
+ get$iterator: function(_) {
+ var t1 = this.get$_union_set$_iterable();
+ return t1.get$iterator(t1);
+ },
+ get$_union_set$_iterable: function() {
+ var t1 = this._sets,
+ t2 = this.$ti._precomputed1,
+ t3 = H._instanceType(t1),
+ t4 = t3._bind$1(t2)._eval$1("Iterable<1>(2)")._check(new M.UnionSet__iterable_closure(this));
+ return new H.ExpandIterable(t1, t4, t3._eval$1("@<1>")._bind$1(t2)._eval$1("ExpandIterable<1,2>"));
+ },
+ toSet$0: function(_) {
+ var t1,
+ result = P.LinkedHashSet_LinkedHashSet(this.$ti._precomputed1);
+ for (t1 = this._sets, t1 = P._LinkedHashSetIterator$(t1, t1._collection$_modifications, H._instanceType(t1)._precomputed1); t1.moveNext$0();)
+ result.addAll$1(0, t1._collection$_current);
+ return result;
+ }
+ };
+ M.UnionSet_length_closure.prototype = {
+ call$2: function($length, set) {
+ var t1;
+ H._checkIntNullable($length);
+ this.$this.$ti._eval$1("Set<1>")._check(set);
+ t1 = set.get$length(set);
+ if (typeof $length !== "number")
+ return $length.$add();
+ if (typeof t1 !== "number")
+ return H.iae(t1);
+ return $length + t1;
+ },
+ $signature: function() {
+ return this.$this.$ti._eval$1("int(int,Set<1>)");
+ }
+ };
+ M.UnionSet__iterable_closure.prototype = {
+ call$1: function(set) {
+ return this.$this.$ti._eval$1("Set<1>")._check(set);
+ },
+ $signature: function() {
+ return this.$this.$ti._eval$1("Set<1>(Set<1>)");
+ }
+ };
+ M._UnionSet_SetBase_UnmodifiableSetMixin.prototype = {};
+ Y.UnionSetController.prototype = {
+ set$_union_set_controller$_set: function(_set) {
+ this._union_set_controller$_set = this.$ti._eval$1("UnionSet<1>")._check(_set);
+ }
+ };
+ L.UnmodifiableSetView.prototype = {};
+ L.UnmodifiableSetMixin.prototype = {};
+ L._UnmodifiableSetView_DelegatingSet_UnmodifiableSetMixin.prototype = {};
+ M._DelegatingIterableBase.prototype = {
+ contains$1: function(_, element) {
+ return this._base.contains$1(0, element);
+ },
+ every$1: function(_, test) {
+ return this._base.every$1(0, H._instanceType(this)._eval$1("bool(1)")._check(test));
+ },
+ get$first: function(_) {
+ var t1 = this._base;
+ return t1.get$first(t1);
+ },
+ get$isEmpty: function(_) {
+ var t1 = this._base;
+ return t1.get$isEmpty(t1);
+ },
+ get$iterator: function(_) {
+ var t1 = this._base;
+ return t1.get$iterator(t1);
+ },
+ get$last: function(_) {
+ var t1 = this._base;
+ return t1.get$last(t1);
+ },
+ get$length: function(_) {
+ var t1 = this._base;
+ return t1.get$length(t1);
+ },
+ map$1$1: function(_, f, $T) {
+ return this._base.map$1$1(0, H._instanceType(this)._bind$1($T)._eval$1("1(2)")._check(f), $T);
+ },
+ toList$1$growable: function(_, growable) {
+ return this._base.toList$1$growable(0, true);
+ },
+ toList$0: function($receiver) {
+ return this.toList$1$growable($receiver, true);
+ },
+ toSet$0: function(_) {
+ return this._base.toSet$0(0);
+ },
+ where$1: function(_, test) {
+ return this._base.where$1(0, H._instanceType(this)._eval$1("bool(1)")._check(test));
+ },
+ toString$0: function(_) {
+ return this._base.toString$0(0);
+ },
+ $isIterable: 1
+ };
+ M.DelegatingIterable.prototype = {};
+ M.DelegatingSet.prototype = {
+ union$1: function(other) {
+ var t1 = H._instanceType(this)._eval$1("Set<1>");
+ t1._check(other);
+ return t1._check(this._base).union$1(other);
+ },
+ toSet$0: function(_) {
+ var t1 = H._instanceType(this);
+ return new M.DelegatingSet(t1._eval$1("Set<1>")._check(this._base).toSet$0(0), t1._eval$1("DelegatingSet<1>"));
+ },
+ $isEfficientLengthIterable: 1,
+ $isSet: 1
+ };
+ Y._IsTrue.prototype = {
+ matches$2: function(_, item, matchState) {
+ return J.$eq$(item, true);
+ },
+ describe$1: function(description) {
+ description._out._contents += "true";
+ return description;
+ }
+ };
+ Y._Predicate.prototype = {
+ typedMatches$2: function(item, matchState) {
+ return this._matcher.call$1(this.$ti._precomputed1._check(item));
+ },
+ describe$1: function(description) {
+ description._out._contents += this._core_matchers$_description;
+ return description;
+ }
+ };
+ E.StringDescription.prototype = {
+ get$length: function(_) {
+ return this._out._contents.length;
+ },
+ toString$0: function(_) {
+ var t1 = this._out._contents;
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ addDescriptionOf$1: function(value) {
+ if (value instanceof G.Matcher)
+ value.describe$1(this);
+ else
+ this._out._contents += Z.prettyPrint(value, 25, 80);
+ return this;
+ },
+ $isDescription: 1
+ };
+ D._StringEqualsMatcher.prototype = {
+ typedMatches$2: function(item, matchState) {
+ return this._equals_matcher$_value === H._checkStringNullable(item);
+ },
+ describe$1: function(description) {
+ return description.addDescriptionOf$1(this._equals_matcher$_value);
+ },
+ describeTypedMismatch$4: function(item, mismatchDescription, matchState, verbose) {
+ var buff, escapedItem, escapedValue, minLength, minLength0, minLength1, start, t1, i;
+ H._checkStringNullable(true);
+ buff = new P.StringBuffer("");
+ buff._contents = "is different.";
+ escapedItem = M.escape(true);
+ escapedValue = M.escape(this._equals_matcher$_value);
+ minLength = escapedItem.length;
+ minLength0 = escapedValue.length;
+ minLength1 = minLength < minLength0 ? minLength : minLength0;
+ for (start = 0; start < minLength1; ++start)
+ if (C.JSString_methods._codeUnitAt$1(escapedValue, start) !== C.JSString_methods._codeUnitAt$1(escapedItem, start))
+ break;
+ if (start === minLength1) {
+ t1 = buff._contents;
+ if (minLength0 < minLength) {
+ buff._contents = t1 + " Both strings start the same, but the actual value also has the following trailing characters: ";
+ D._StringEqualsMatcher__writeTrailing(buff, escapedItem, minLength0);
+ } else {
+ buff._contents = t1 + " Both strings start the same, but the actual value is missing the following trailing characters: ";
+ D._StringEqualsMatcher__writeTrailing(buff, escapedValue, minLength);
+ }
+ } else {
+ buff._contents += "\nExpected: ";
+ D._StringEqualsMatcher__writeLeading(buff, escapedValue, start);
+ D._StringEqualsMatcher__writeTrailing(buff, escapedValue, start);
+ buff._contents += "\n Actual: ";
+ D._StringEqualsMatcher__writeLeading(buff, escapedItem, start);
+ D._StringEqualsMatcher__writeTrailing(buff, escapedItem, start);
+ t1 = buff._contents += "\n ";
+ i = start > 10 ? 14 : start;
+ for (; i > 0; --i) {
+ t1 += " ";
+ buff._contents = t1;
+ }
+ buff._contents += "^\n Differ at offset " + start;
+ }
+ t1 = buff._contents;
+ mismatchDescription._out._contents += t1.charCodeAt(0) == 0 ? t1 : t1;
+ return mismatchDescription;
+ }
+ };
+ D._DeepMatcher.prototype = {
+ _compareIterables$5: function(expected, actual, matcher, depth, $location) {
+ var expectedIterator, actualIterator, index, expectedNext, actualNext, t1, newLocation, rp;
+ type$.List_String_Function_dynamic_dynamic_String_int._check(matcher);
+ if (type$.Iterable_dynamic._is(actual)) {
+ expectedIterator = J.get$iterator$ax(expected);
+ actualIterator = J.get$iterator$ax(actual);
+ for (index = 0; true; ++index) {
+ expectedNext = expectedIterator.moveNext$0();
+ actualNext = actualIterator.moveNext$0();
+ t1 = !expectedNext;
+ if (t1 && !actualNext)
+ return null;
+ newLocation = $location + "[" + index + "]";
+ if (t1)
+ return H.setRuntimeTypeInfo(["longer than expected", newLocation], type$.JSArray_String);
+ if (!actualNext)
+ return H.setRuntimeTypeInfo(["shorter than expected", newLocation], type$.JSArray_String);
+ rp = matcher.call$4(expectedIterator.get$current(), actualIterator.get$current(), newLocation, depth);
+ if (rp != null)
+ return rp;
+ }
+ } else
+ return H.setRuntimeTypeInfo(["is not Iterable", $location], type$.JSArray_String);
+ },
+ _compareSets$5: function(expected, actual, matcher, depth, $location) {
+ var other, t1, expectedElement, t2;
+ type$.List_String_Function_dynamic_dynamic_String_int._check(matcher);
+ if (type$.Iterable_dynamic._is(actual)) {
+ other = J.toSet$0$ax(actual);
+ for (t1 = expected.get$iterator(expected); t1.moveNext$0();) {
+ expectedElement = t1.get$current();
+ if (other.every$1(0, new D._DeepMatcher__compareSets_closure(matcher, expectedElement, $location, depth)))
+ return H.setRuntimeTypeInfo(["does not contain " + H.S(expectedElement), $location], type$.JSArray_String);
+ }
+ t1 = other.get$length(other);
+ t2 = expected.get$length(expected);
+ if (typeof t2 !== "number")
+ return H.iae(t2);
+ if (t1 > t2)
+ return H.setRuntimeTypeInfo(["larger than expected", $location], type$.JSArray_String);
+ else {
+ t1 = other.get$length(other);
+ t2 = expected.get$length(expected);
+ if (typeof t2 !== "number")
+ return H.iae(t2);
+ if (t1 < t2)
+ return H.setRuntimeTypeInfo(["smaller than expected", $location], type$.JSArray_String);
+ else
+ return null;
+ }
+ } else
+ return H.setRuntimeTypeInfo(["is not Iterable", $location], type$.JSArray_String);
+ },
+ _recursiveMatch$4: function(expected, actual, $location, depth) {
+ var e, t1, description, exception, err, key, t2, rp, _this = this;
+ if (expected instanceof G.Matcher) {
+ t1 = type$.dynamic;
+ if (expected.matches$2(0, actual, P.LinkedHashMap_LinkedHashMap$_empty(t1, t1)))
+ return null;
+ description = new E.StringDescription(new P.StringBuffer(""));
+ expected.describe$1(description);
+ return H.setRuntimeTypeInfo(["does not match " + description.toString$0(0), $location], type$.JSArray_String);
+ } else
+ try {
+ if (J.$eq$(expected, actual))
+ return null;
+ } catch (exception) {
+ e = H.unwrapException(exception);
+ t1 = H.setRuntimeTypeInfo(['== threw "' + H.S(e) + '"', $location], type$.JSArray_String);
+ return t1;
+ }
+ t1 = _this._limit;
+ if (depth > t1)
+ return H.setRuntimeTypeInfo(["recursion depth limit exceeded", $location], type$.JSArray_String);
+ if (depth === 0 || t1 > 1)
+ if (type$.Set_dynamic._is(expected))
+ return _this._compareSets$5(expected, actual, _this.get$_recursiveMatch(), depth + 1, $location);
+ else if (type$.Iterable_dynamic._is(expected))
+ return _this._compareIterables$5(expected, actual, _this.get$_recursiveMatch(), depth + 1, $location);
+ else {
+ t1 = type$.Map_dynamic_dynamic;
+ if (t1._is(expected)) {
+ if (!t1._is(actual))
+ return H.setRuntimeTypeInfo(["expected a map", $location], type$.JSArray_String);
+ err = expected.get$length(expected) == actual.get$length(actual) ? "" : "has different length and ";
+ for (t1 = expected.get$keys(), t1 = t1.get$iterator(t1); t1.moveNext$0();) {
+ key = t1.get$current();
+ if (!actual.containsKey$1(key))
+ return H.setRuntimeTypeInfo([err + "is missing map key '" + H.S(key) + "'", $location], type$.JSArray_String);
+ }
+ for (t1 = actual.get$keys(), t1 = t1.get$iterator(t1); t1.moveNext$0();) {
+ key = t1.get$current();
+ if (!expected.containsKey$1(key))
+ return H.setRuntimeTypeInfo([err + "has extra map key '" + H.S(key) + "'", $location], type$.JSArray_String);
+ }
+ for (t1 = expected.get$keys(), t1 = t1.get$iterator(t1), t2 = depth + 1; t1.moveNext$0();) {
+ key = t1.get$current();
+ rp = _this._recursiveMatch$4(expected.$index(0, key), actual.$index(0, key), $location + "['" + H.S(key) + "']", t2);
+ if (rp != null)
+ return rp;
+ }
+ return null;
+ }
+ }
+ t1 = new P.StringBuffer("");
+ if (depth > 0) {
+ t1._contents = "was ";
+ t2 = new E.StringDescription(t1).addDescriptionOf$1(actual);
+ t2._out._contents += " instead of ";
+ t2.addDescriptionOf$1(expected);
+ t1 = t1._contents;
+ return H.setRuntimeTypeInfo([t1.charCodeAt(0) == 0 ? t1 : t1, $location], type$.JSArray_String);
+ }
+ return H.setRuntimeTypeInfo(["", $location], type$.JSArray_String);
+ },
+ _equals_matcher$_match$3: function(expected, actual, matchState) {
+ var t1, t2, reason, innerState,
+ rp = this._recursiveMatch$4(expected, actual, "", 0);
+ if (rp == null)
+ return null;
+ t1 = J.getInterceptor$asx(rp);
+ t2 = t1.$index(rp, 0);
+ t2.toString;
+ if (J.get$length$asx(t2) !== 0) {
+ t2 = t1.$index(rp, 1);
+ t2.toString;
+ reason = J.get$length$asx(t2) !== 0 ? H.S(t1.$index(rp, 0)) + " at location " + H.S(t1.$index(rp, 1)) : t1.$index(rp, 0);
+ } else
+ reason = "";
+ t1 = type$.dynamic;
+ t2 = P.LinkedHashMap_LinkedHashMap$_literal(["reason", reason], t1, t1);
+ innerState = P.LinkedHashMap_LinkedHashMap$from(matchState, t1, t1);
+ matchState.clear$0(0);
+ matchState.$indexSet(0, "state", innerState);
+ matchState.addAll$1(0, t2);
+ return reason;
+ },
+ matches$2: function(_, item, matchState) {
+ return this._equals_matcher$_match$3(this._expected, item, matchState) == null;
+ },
+ describe$1: function(description) {
+ return description.addDescriptionOf$1(this._expected);
+ },
+ describeMismatch$4: function(item, mismatchDescription, matchState, verbose) {
+ var t1, t2, t3,
+ reason = H._asStringNullable(matchState.$index(0, "reason"));
+ if (reason == null)
+ reason = "";
+ t1 = reason.length === 0 && mismatchDescription._out._contents.length > 0;
+ t2 = mismatchDescription._out;
+ t3 = t2._contents;
+ if (t1) {
+ t2._contents = t3 + "is ";
+ mismatchDescription.addDescriptionOf$1(true);
+ } else
+ t2._contents = t3 + reason;
+ return mismatchDescription;
+ }
+ };
+ D._DeepMatcher__compareSets_closure.prototype = {
+ call$1: function(actualElement) {
+ var _this = this;
+ return _this.matcher.call$4(_this.expectedElement, actualElement, _this.location, _this.depth) != null;
+ },
+ $signature: 9
+ };
+ E.FeatureMatcher.prototype = {
+ matches$2: function(_, item, matchState) {
+ return this.super$TypeMatcher$matches(0, item, matchState) && H.boolConversionCheck(this.typedMatches$2(H._instanceType(this)._eval$1("FeatureMatcher.T")._check(item), matchState));
+ },
+ describeMismatch$4: function(item, mismatchDescription, matchState, verbose) {
+ if (H._instanceType(this)._eval$1("FeatureMatcher.T")._is(true))
+ return this.describeTypedMismatch$4(true, mismatchDescription, matchState, false);
+ mismatchDescription._out._contents += "not an ";
+ return this.super$TypeMatcher$describe(mismatchDescription);
+ },
+ describeTypedMismatch$4: function(item, mismatchDescription, matchState, verbose) {
+ H._instanceType(this)._eval$1("FeatureMatcher.T")._check(true);
+ return mismatchDescription;
+ }
+ };
+ G.Matcher.prototype = {
+ describeMismatch$4: function(item, mismatchDescription, matchState, verbose) {
+ return mismatchDescription;
+ }
+ };
+ Z.prettyPrint__prettyPrint.prototype = {
+ call$4: function(object, indent, seen, $top) {
+ var description, type, t2, strings, t3, singleLine, value, defaultToString, _this = this, t1 = {};
+ t1.seen = seen;
+ if (object instanceof G.Matcher) {
+ description = new E.StringDescription(new P.StringBuffer(""));
+ object.describe$1(description);
+ return "<" + description.toString$0(0) + ">";
+ }
+ if (seen.contains$1(0, object))
+ return "(recursive)";
+ t1.seen = seen.union$1(P.LinkedHashSet_LinkedHashSet$_literal([object], type$.dynamic));
+ t1 = new Z.prettyPrint__prettyPrint_pp(t1, _this, indent);
+ if (type$.Iterable_dynamic._is(object)) {
+ type = type$.List_dynamic._is(object) ? "" : Z._typeName(object) + ":";
+ t2 = type$.String;
+ strings = J.map$1$1$ax(object, t1, t2).toList$0(0);
+ t1 = strings.length;
+ t3 = _this.maxItems;
+ if (t1 > t3)
+ C.JSArray_methods.replaceRange$3(strings, t3 - 1, t1, H.setRuntimeTypeInfo(["..."], type$.JSArray_String));
+ singleLine = type + "[" + C.JSArray_methods.join$1(strings, ", ") + "]";
+ if (singleLine.length + indent <= _this.maxLineLength && !C.JSString_methods.contains$1(singleLine, "\n"))
+ return singleLine;
+ t1 = H._arrayInstanceType(strings);
+ return type + "[\n" + new H.MappedListIterable(strings, t1._eval$1("String(1)")._check(new Z.prettyPrint__prettyPrint_closure(indent)), t1._eval$1("MappedListIterable<1,String>")).join$1(0, ",\n") + "\n" + C.JSArray_methods.join$1(P.List_List$filled(indent, " ", t2), "") + "]";
+ } else if (type$.Map_dynamic_dynamic._is(object)) {
+ t2 = type$.String;
+ strings = object.get$keys().map$1$1(0, new Z.prettyPrint__prettyPrint_closure0(t1, object), t2).toList$0(0);
+ t1 = strings.length;
+ t3 = _this.maxItems;
+ if (t1 > t3)
+ C.JSArray_methods.replaceRange$3(strings, t3 - 1, t1, H.setRuntimeTypeInfo(["..."], type$.JSArray_String));
+ singleLine = "{" + C.JSArray_methods.join$1(strings, ", ") + "}";
+ if (singleLine.length + indent <= _this.maxLineLength && !C.JSString_methods.contains$1(singleLine, "\n"))
+ return singleLine;
+ t1 = H._arrayInstanceType(strings);
+ return "{\n" + new H.MappedListIterable(strings, t1._eval$1("String(1)")._check(new Z.prettyPrint__prettyPrint_closure1(indent)), t1._eval$1("MappedListIterable<1,String>")).join$1(0, ",\n") + "\n" + C.JSArray_methods.join$1(P.List_List$filled(indent, " ", t2), "") + "}";
+ } else {
+ t1 = type$.String;
+ if (typeof object == "string")
+ return "'" + new H.MappedListIterable(H.setRuntimeTypeInfo(object.split("\n"), type$.JSArray_String), type$.String_Function_String._check(Z.pretty_print___escapeString$closure()), type$.MappedListIterable_String_String).join$1(0, "\\n'\n" + C.JSArray_methods.join$1(P.List_List$filled(indent + 2, " ", t1), "") + "'") + "'";
+ else {
+ t2 = J.toString$0$(object);
+ t1 = C.JSArray_methods.join$1(P.List_List$filled(indent, " ", t1), "") + "\n";
+ t2.toString;
+ value = H.stringReplaceAllUnchecked(t2, "\n", t1);
+ defaultToString = C.JSString_methods.startsWith$1(value, "Instance of ");
+ if ($top)
+ value = "<" + value + ">";
+ if (typeof object == "number" || H._isBool(object) || type$.Function._is(object) || type$.RegExp._is(object) || object instanceof P.MapEntry || object instanceof P.Expando || object == null || defaultToString)
+ return value;
+ else
+ return Z._typeName(object) + ":" + value;
+ }
+ }
+ },
+ $signature: 55
+ };
+ Z.prettyPrint__prettyPrint_pp.prototype = {
+ call$1: function(child) {
+ return this._prettyPrint.call$4(child, this.indent + 2, this._box_0.seen, false);
+ },
+ $signature: 40
+ };
+ Z.prettyPrint__prettyPrint_closure.prototype = {
+ call$1: function(string) {
+ H._checkStringNullable(string);
+ return C.JSString_methods.$add(C.JSArray_methods.join$1(P.List_List$filled(this.indent + 2, " ", type$.String), ""), string);
+ },
+ $signature: 7
+ };
+ Z.prettyPrint__prettyPrint_closure0.prototype = {
+ call$1: function(key) {
+ var t1 = this.pp;
+ return H.S(t1.call$1(key)) + ": " + H.S(t1.call$1(this.object.$index(0, key)));
+ },
+ $signature: 40
+ };
+ Z.prettyPrint__prettyPrint_closure1.prototype = {
+ call$1: function(string) {
+ H._checkStringNullable(string);
+ return C.JSString_methods.$add(C.JSArray_methods.join$1(P.List_List$filled(this.indent + 2, " ", type$.String), ""), string);
+ },
+ $signature: 7
+ };
+ M.TypeMatcher.prototype = {
+ describe$1: function(description) {
+ var $name,
+ t1 = H._rtiToString(H.createRuntimeType(H._instanceType(this)._eval$1("TypeMatcher.T"))._rti, null),
+ t2 = $.$get$_dart2DynamicArgs();
+ t1.toString;
+ $name = H.stringReplaceAllUnchecked(t1, t2, "");
+ description._out._contents += "<Instance of '" + $name + "'>";
+ return description;
+ },
+ matches$2: function(_, item, matchState) {
+ return H._instanceType(this)._eval$1("TypeMatcher.T")._is(item);
+ }
+ };
+ M.wrapMatcher_closure.prototype = {
+ call$1: function(a) {
+ return H._checkBoolNullable(this.x.call$1(a));
+ },
+ $signature: 9
+ };
+ M.escape_closure.prototype = {
+ call$1: function(match) {
+ var mapped = C.Map_F9GZw.$index(0, match.$index(0, 0));
+ if (mapped != null)
+ return mapped;
+ return M._getHexLiteral(match.$index(0, 0));
+ },
+ $signature: 24
+ };
+ F.PackageConfigResolver.prototype = {$isSyncPackageResolver: 1,
+ get$packageRoot: function() {
+ return null;
+ },
+ get$packageConfigMap: function() {
+ return this.packageConfigMap;
+ }
+ };
+ F.PackageConfigResolver__normalizeMap_closure.prototype = {
+ call$2: function(_, uri) {
+ return B.ensureTrailingSlash(type$.Uri._check(uri));
+ },
+ $signature: 51
+ };
+ D.PackageRootResolver.prototype = {$isSyncPackageResolver: 1,
+ get$packageConfigMap: function() {
+ return null;
+ },
+ get$packageRoot: function() {
+ return this.packageRoot;
+ }
+ };
+ M.Context.prototype = {
+ absolute$7: function(_, part1, part2, part3, part4, part5, part6, part7) {
+ var t1;
+ M._validateArgList("absolute", H.setRuntimeTypeInfo([part1, part2, part3, part4, part5, part6, part7], type$.JSArray_String));
+ t1 = this.style;
+ t1 = t1.rootLength$1(part1) > 0 && !t1.isRootRelative$1(part1);
+ if (t1)
+ return part1;
+ t1 = this._context$_current;
+ return this.join$8(0, t1 != null ? t1 : D.current(), part1, part2, part3, part4, part5, part6, part7);
+ },
+ absolute$1: function($receiver, part1) {
+ return this.absolute$7($receiver, part1, null, null, null, null, null, null);
+ },
+ join$8: function(_, part1, part2, part3, part4, part5, part6, part7, part8) {
+ var parts = H.setRuntimeTypeInfo([part1, part2, part3, part4, part5, part6, part7, part8], type$.JSArray_String);
+ M._validateArgList("join", parts);
+ return this.joinAll$1(new H.WhereIterable(parts, type$.bool_Function_String._check(new M.Context_join_closure()), type$.WhereIterable_String));
+ },
+ join$2: function($receiver, part1, part2) {
+ return this.join$8($receiver, part1, part2, null, null, null, null, null, null);
+ },
+ joinAll$1: function(parts) {
+ var t1, t2, t3, needsSeparator, isAbsoluteAndNotRootRelative, t4, t5, parsed, path, t6;
+ type$.Iterable_String._check(parts);
+ for (t1 = parts.$ti, t2 = t1._eval$1("bool(Iterable.E)")._check(new M.Context_joinAll_closure()), t3 = parts.get$iterator(parts), t1 = new H.WhereIterator(t3, t2, t1._eval$1("WhereIterator<Iterable.E>")), t2 = this.style, needsSeparator = false, isAbsoluteAndNotRootRelative = false, t4 = ""; t1.moveNext$0();) {
+ t5 = t3.get$current();
+ if (t2.isRootRelative$1(t5) && isAbsoluteAndNotRootRelative) {
+ parsed = X.ParsedPath_ParsedPath$parse(t5, t2);
+ path = t4.charCodeAt(0) == 0 ? t4 : t4;
+ t4 = C.JSString_methods.substring$2(path, 0, t2.rootLength$2$withDrive(path, true));
+ parsed.root = t4;
+ if (t2.needsSeparator$1(t4))
+ C.JSArray_methods.$indexSet(parsed.separators, 0, t2.get$separator());
+ t4 = parsed.toString$0(0);
+ } else if (t2.rootLength$1(t5) > 0) {
+ isAbsoluteAndNotRootRelative = !t2.isRootRelative$1(t5);
+ t4 = H.S(t5);
+ } else {
+ t6 = t5.length;
+ if (t6 !== 0) {
+ if (0 >= t6)
+ return H.ioore(t5, 0);
+ t6 = t2.containsSeparator$1(t5[0]);
+ } else
+ t6 = false;
+ if (!t6)
+ if (needsSeparator)
+ t4 += t2.get$separator();
+ t4 += t5;
+ }
+ needsSeparator = t2.needsSeparator$1(t5);
+ }
+ return t4.charCodeAt(0) == 0 ? t4 : t4;
+ },
+ split$1: function(_, path) {
+ var parsed = X.ParsedPath_ParsedPath$parse(path, this.style),
+ t1 = parsed.parts,
+ t2 = H._arrayInstanceType(t1),
+ t3 = t2._eval$1("WhereIterable<1>");
+ parsed.set$parts(P.List_List$from(new H.WhereIterable(t1, t2._eval$1("bool(1)")._check(new M.Context_split_closure()), t3), true, t3._eval$1("Iterable.E")));
+ t1 = parsed.root;
+ if (t1 != null)
+ C.JSArray_methods.insert$2(parsed.parts, 0, t1);
+ return parsed.parts;
+ },
+ normalize$1: function(path) {
+ var parsed;
+ if (!this._needsNormalization$1(path))
+ return path;
+ parsed = X.ParsedPath_ParsedPath$parse(path, this.style);
+ parsed.normalize$0();
+ return parsed.toString$0(0);
+ },
+ _needsNormalization$1: function(path) {
+ var t1, root, i, start, previous, t2, t3, previousPrevious, codeUnit, t4;
+ path.toString;
+ t1 = this.style;
+ root = t1.rootLength$1(path);
+ if (root !== 0) {
+ if (t1 === $.$get$Style_windows())
+ for (i = 0; i < root; ++i)
+ if (C.JSString_methods._codeUnitAt$1(path, i) === 47)
+ return true;
+ start = root;
+ previous = 47;
+ } else {
+ start = 0;
+ previous = null;
+ }
+ for (t2 = new H.CodeUnits(path)._string, t3 = t2.length, i = start, previousPrevious = null; i < t3; ++i, previousPrevious = previous, previous = codeUnit) {
+ codeUnit = C.JSString_methods.codeUnitAt$1(t2, i);
+ if (t1.isSeparator$1(codeUnit)) {
+ if (t1 === $.$get$Style_windows() && codeUnit === 47)
+ return true;
+ if (previous != null && t1.isSeparator$1(previous))
+ return true;
+ if (previous === 46)
+ t4 = previousPrevious == null || previousPrevious === 46 || t1.isSeparator$1(previousPrevious);
+ else
+ t4 = false;
+ if (t4)
+ return true;
+ }
+ }
+ if (previous == null)
+ return true;
+ if (t1.isSeparator$1(previous))
+ return true;
+ if (previous === 46)
+ t1 = previousPrevious == null || t1.isSeparator$1(previousPrevious) || previousPrevious === 46;
+ else
+ t1 = false;
+ if (t1)
+ return true;
+ return false;
+ },
+ relative$2$from: function(path, from) {
+ var fromParsed, pathParsed, t2, t3, t4, t5, _this = this,
+ _s26_ = 'Unable to find a path to "',
+ t1 = from == null;
+ if (t1 && _this.style.rootLength$1(path) <= 0)
+ return _this.normalize$1(path);
+ if (t1) {
+ t1 = _this._context$_current;
+ from = t1 != null ? t1 : D.current();
+ } else
+ from = _this.absolute$1(0, from);
+ t1 = _this.style;
+ if (t1.rootLength$1(from) <= 0 && t1.rootLength$1(path) > 0)
+ return _this.normalize$1(path);
+ if (t1.rootLength$1(path) <= 0 || t1.isRootRelative$1(path))
+ path = _this.absolute$1(0, path);
+ if (t1.rootLength$1(path) <= 0 && t1.rootLength$1(from) > 0)
+ throw H.wrapException(X.PathException$(_s26_ + H.S(path) + '" from "' + H.S(from) + '".'));
+ fromParsed = X.ParsedPath_ParsedPath$parse(from, t1);
+ fromParsed.normalize$0();
+ pathParsed = X.ParsedPath_ParsedPath$parse(path, t1);
+ pathParsed.normalize$0();
+ t2 = fromParsed.parts;
+ t3 = t2.length;
+ if (t3 !== 0) {
+ if (0 >= t3)
+ return H.ioore(t2, 0);
+ t2 = J.$eq$(t2[0], ".");
+ } else
+ t2 = false;
+ if (t2)
+ return pathParsed.toString$0(0);
+ t2 = fromParsed.root;
+ t3 = pathParsed.root;
+ if (t2 != t3)
+ t2 = t2 == null || t3 == null || !t1.pathsEqual$2(t2, t3);
+ else
+ t2 = false;
+ if (t2)
+ return pathParsed.toString$0(0);
+ while (true) {
+ t2 = fromParsed.parts;
+ t3 = t2.length;
+ if (t3 !== 0) {
+ t4 = pathParsed.parts;
+ t5 = t4.length;
+ if (t5 !== 0) {
+ if (0 >= t3)
+ return H.ioore(t2, 0);
+ t2 = t2[0];
+ if (0 >= t5)
+ return H.ioore(t4, 0);
+ t4 = t1.pathsEqual$2(t2, t4[0]);
+ t2 = t4;
+ } else
+ t2 = false;
+ } else
+ t2 = false;
+ if (!t2)
+ break;
+ C.JSArray_methods.removeAt$1(fromParsed.parts, 0);
+ C.JSArray_methods.removeAt$1(fromParsed.separators, 1);
+ C.JSArray_methods.removeAt$1(pathParsed.parts, 0);
+ C.JSArray_methods.removeAt$1(pathParsed.separators, 1);
+ }
+ t2 = fromParsed.parts;
+ t3 = t2.length;
+ if (t3 !== 0) {
+ if (0 >= t3)
+ return H.ioore(t2, 0);
+ t2 = J.$eq$(t2[0], "..");
+ } else
+ t2 = false;
+ if (t2)
+ throw H.wrapException(X.PathException$(_s26_ + H.S(path) + '" from "' + H.S(from) + '".'));
+ t2 = type$.String;
+ C.JSArray_methods.insertAll$2(pathParsed.parts, 0, P.List_List$filled(fromParsed.parts.length, "..", t2));
+ C.JSArray_methods.$indexSet(pathParsed.separators, 0, "");
+ C.JSArray_methods.insertAll$2(pathParsed.separators, 1, P.List_List$filled(fromParsed.parts.length, t1.get$separator(), t2));
+ t1 = pathParsed.parts;
+ t2 = t1.length;
+ if (t2 === 0)
+ return ".";
+ if (t2 > 1 && J.$eq$(C.JSArray_methods.get$last(t1), ".")) {
+ C.JSArray_methods.removeLast$0(pathParsed.parts);
+ t1 = pathParsed.separators;
+ C.JSArray_methods.removeLast$0(t1);
+ C.JSArray_methods.removeLast$0(t1);
+ C.JSArray_methods.add$1(t1, "");
+ }
+ pathParsed.root = "";
+ pathParsed.removeTrailingSeparators$0();
+ return pathParsed.toString$0(0);
+ },
+ relative$1: function(path) {
+ return this.relative$2$from(path, null);
+ },
+ _isWithinOrEquals$2: function($parent, child) {
+ var relative, childIsRootRelative, parentIsRootRelative, result, exception, _this = this,
+ t1 = _this.style,
+ parentIsAbsolute = t1.rootLength$1(H._checkStringNullable($parent)) > 0,
+ childIsAbsolute = t1.rootLength$1(H._checkStringNullable(child)) > 0;
+ if (parentIsAbsolute && !childIsAbsolute) {
+ child = _this.absolute$1(0, child);
+ if (t1.isRootRelative$1($parent))
+ $parent = _this.absolute$1(0, $parent);
+ } else if (childIsAbsolute && !parentIsAbsolute) {
+ $parent = _this.absolute$1(0, $parent);
+ if (t1.isRootRelative$1(child))
+ child = _this.absolute$1(0, child);
+ } else if (childIsAbsolute && parentIsAbsolute) {
+ childIsRootRelative = t1.isRootRelative$1(child);
+ parentIsRootRelative = t1.isRootRelative$1($parent);
+ if (childIsRootRelative && !parentIsRootRelative)
+ child = _this.absolute$1(0, child);
+ else if (parentIsRootRelative && !childIsRootRelative)
+ $parent = _this.absolute$1(0, $parent);
+ }
+ result = _this._isWithinOrEqualsFast$2($parent, child);
+ if (result !== C._PathRelation_inconclusive)
+ return result;
+ relative = null;
+ try {
+ relative = _this.relative$2$from(child, $parent);
+ } catch (exception) {
+ if (H.unwrapException(exception) instanceof X.PathException)
+ return C._PathRelation_different;
+ else
+ throw exception;
+ }
+ if (t1.rootLength$1(H._checkStringNullable(relative)) > 0)
+ return C._PathRelation_different;
+ if (J.$eq$(relative, "."))
+ return C._PathRelation_equal;
+ if (J.$eq$(relative, ".."))
+ return C._PathRelation_different;
+ return J.get$length$asx(relative) >= 3 && J.startsWith$1$s(relative, "..") && t1.isSeparator$1(J.codeUnitAt$1$s(relative, 2)) ? C._PathRelation_different : C._PathRelation_within;
+ },
+ _isWithinOrEqualsFast$2: function($parent, child) {
+ var t1, parentRootLength, childRootLength, t2, t3, i, childIndex, parentIndex, lastCodeUnit, lastParentSeparator, parentCodeUnit, childCodeUnit, parentIndex0, t4, direction, _this = this;
+ if ($parent === ".")
+ $parent = "";
+ t1 = _this.style;
+ parentRootLength = t1.rootLength$1($parent);
+ childRootLength = t1.rootLength$1(child);
+ if (parentRootLength !== childRootLength)
+ return C._PathRelation_different;
+ for (t2 = J.getInterceptor$s($parent), t3 = J.getInterceptor$s(child), i = 0; i < parentRootLength; ++i)
+ if (!t1.codeUnitsEqual$2(t2._codeUnitAt$1($parent, i), t3._codeUnitAt$1(child, i)))
+ return C._PathRelation_different;
+ t2 = $parent.length;
+ childIndex = childRootLength;
+ parentIndex = parentRootLength;
+ lastCodeUnit = 47;
+ lastParentSeparator = null;
+ while (true) {
+ if (!(parentIndex < t2 && childIndex < child.length))
+ break;
+ c$0: {
+ parentCodeUnit = C.JSString_methods.codeUnitAt$1($parent, parentIndex);
+ childCodeUnit = t3.codeUnitAt$1(child, childIndex);
+ if (t1.codeUnitsEqual$2(parentCodeUnit, childCodeUnit)) {
+ if (t1.isSeparator$1(parentCodeUnit))
+ lastParentSeparator = parentIndex;
+ ++parentIndex;
+ ++childIndex;
+ lastCodeUnit = parentCodeUnit;
+ break c$0;
+ }
+ if (t1.isSeparator$1(parentCodeUnit) && t1.isSeparator$1(lastCodeUnit)) {
+ parentIndex0 = parentIndex + 1;
+ lastParentSeparator = parentIndex;
+ parentIndex = parentIndex0;
+ break c$0;
+ } else if (t1.isSeparator$1(childCodeUnit) && t1.isSeparator$1(lastCodeUnit)) {
+ ++childIndex;
+ break c$0;
+ }
+ if (parentCodeUnit === 46 && t1.isSeparator$1(lastCodeUnit)) {
+ ++parentIndex;
+ if (parentIndex === t2)
+ break;
+ parentCodeUnit = C.JSString_methods.codeUnitAt$1($parent, parentIndex);
+ if (t1.isSeparator$1(parentCodeUnit)) {
+ parentIndex0 = parentIndex + 1;
+ lastParentSeparator = parentIndex;
+ parentIndex = parentIndex0;
+ break c$0;
+ }
+ if (parentCodeUnit === 46) {
+ ++parentIndex;
+ if (parentIndex === t2 || t1.isSeparator$1(C.JSString_methods.codeUnitAt$1($parent, parentIndex)))
+ return C._PathRelation_inconclusive;
+ }
+ }
+ if (childCodeUnit === 46 && t1.isSeparator$1(lastCodeUnit)) {
+ ++childIndex;
+ t4 = child.length;
+ if (childIndex === t4)
+ break;
+ childCodeUnit = C.JSString_methods.codeUnitAt$1(child, childIndex);
+ if (t1.isSeparator$1(childCodeUnit)) {
+ ++childIndex;
+ break c$0;
+ }
+ if (childCodeUnit === 46) {
+ ++childIndex;
+ if (childIndex === t4 || t1.isSeparator$1(C.JSString_methods.codeUnitAt$1(child, childIndex)))
+ return C._PathRelation_inconclusive;
+ }
+ }
+ if (_this._pathDirection$2(child, childIndex) !== C._PathDirection_988)
+ return C._PathRelation_inconclusive;
+ if (_this._pathDirection$2($parent, parentIndex) !== C._PathDirection_988)
+ return C._PathRelation_inconclusive;
+ return C._PathRelation_different;
+ }
+ }
+ if (childIndex === child.length) {
+ if (parentIndex === t2 || t1.isSeparator$1(C.JSString_methods.codeUnitAt$1($parent, parentIndex)))
+ lastParentSeparator = parentIndex;
+ else if (lastParentSeparator == null)
+ lastParentSeparator = Math.max(0, parentRootLength - 1);
+ direction = _this._pathDirection$2($parent, lastParentSeparator);
+ if (direction === C._PathDirection_8Gl)
+ return C._PathRelation_equal;
+ return direction === C._PathDirection_ZGD ? C._PathRelation_inconclusive : C._PathRelation_different;
+ }
+ direction = _this._pathDirection$2(child, childIndex);
+ if (direction === C._PathDirection_8Gl)
+ return C._PathRelation_equal;
+ if (direction === C._PathDirection_ZGD)
+ return C._PathRelation_inconclusive;
+ return t1.isSeparator$1(C.JSString_methods.codeUnitAt$1(child, childIndex)) || t1.isSeparator$1(lastCodeUnit) ? C._PathRelation_within : C._PathRelation_different;
+ },
+ _pathDirection$2: function(path, index) {
+ var t1, t2, i, depth, reachedRoot, i0, t3;
+ for (t1 = path.length, t2 = this.style, i = index, depth = 0, reachedRoot = false; i < t1;) {
+ while (true) {
+ if (!(i < t1 && t2.isSeparator$1(C.JSString_methods.codeUnitAt$1(path, i))))
+ break;
+ ++i;
+ }
+ if (i === t1)
+ break;
+ i0 = i;
+ while (true) {
+ if (!(i0 < t1 && !t2.isSeparator$1(C.JSString_methods.codeUnitAt$1(path, i0))))
+ break;
+ ++i0;
+ }
+ t3 = i0 - i;
+ if (!(t3 === 1 && C.JSString_methods.codeUnitAt$1(path, i) === 46))
+ if (t3 === 2 && C.JSString_methods.codeUnitAt$1(path, i) === 46 && C.JSString_methods.codeUnitAt$1(path, i + 1) === 46) {
+ --depth;
+ if (depth < 0)
+ break;
+ if (depth === 0)
+ reachedRoot = true;
+ } else
+ ++depth;
+ if (i0 === t1)
+ break;
+ i = i0 + 1;
+ }
+ if (depth < 0)
+ return C._PathDirection_ZGD;
+ if (depth === 0)
+ return C._PathDirection_8Gl;
+ if (reachedRoot)
+ return C._PathDirection_FIw;
+ return C._PathDirection_988;
+ },
+ toUri$1: function(path) {
+ var t2,
+ t1 = this.style;
+ if (t1.rootLength$1(path) <= 0)
+ return t1.relativePathToUri$1(path);
+ else {
+ t2 = this._context$_current;
+ return t1.absolutePathToUri$1(this.join$2(0, t2 != null ? t2 : D.current(), path));
+ }
+ },
+ prettyUri$1: function(uri) {
+ var path, rel, _this = this,
+ typedUri = M._parseUri(uri);
+ if (typedUri.get$scheme() === "file" && _this.style == $.$get$Style_url())
+ return typedUri.toString$0(0);
+ else if (typedUri.get$scheme() !== "file" && typedUri.get$scheme() !== "" && _this.style != $.$get$Style_url())
+ return typedUri.toString$0(0);
+ path = _this.normalize$1(_this.style.pathFromUri$1(M._parseUri(typedUri)));
+ rel = _this.relative$1(path);
+ return _this.split$1(0, rel).length > _this.split$1(0, path).length ? path : rel;
+ }
+ };
+ M.Context_join_closure.prototype = {
+ call$1: function(part) {
+ return H._checkStringNullable(part) != null;
+ },
+ $signature: 4
+ };
+ M.Context_joinAll_closure.prototype = {
+ call$1: function(part) {
+ return H._checkStringNullable(part) !== "";
+ },
+ $signature: 4
+ };
+ M.Context_split_closure.prototype = {
+ call$1: function(part) {
+ return H._checkStringNullable(part).length !== 0;
+ },
+ $signature: 4
+ };
+ M._validateArgList_closure.prototype = {
+ call$1: function(arg) {
+ H._checkStringNullable(arg);
+ return arg == null ? "null" : '"' + arg + '"';
+ },
+ $signature: 7
+ };
+ M._PathDirection.prototype = {
+ toString$0: function(_) {
+ return this.name;
+ }
+ };
+ M._PathRelation.prototype = {
+ toString$0: function(_) {
+ return this.name;
+ }
+ };
+ B.InternalStyle.prototype = {
+ getRoot$1: function(path) {
+ var t1,
+ $length = this.rootLength$1(path);
+ if ($length > 0)
+ return J.substring$2$s(path, 0, $length);
+ if (this.isRootRelative$1(path)) {
+ if (0 >= path.length)
+ return H.ioore(path, 0);
+ t1 = path[0];
+ } else
+ t1 = null;
+ return t1;
+ },
+ relativePathToUri$1: function(path) {
+ var segments = M.Context_Context(this).split$1(0, path);
+ if (this.isSeparator$1(J.codeUnitAt$1$s(path, path.length - 1)))
+ C.JSArray_methods.add$1(segments, "");
+ return P._Uri__Uri(null, null, segments, null);
+ },
+ codeUnitsEqual$2: function(codeUnit1, codeUnit2) {
+ return codeUnit1 === codeUnit2;
+ },
+ pathsEqual$2: function(path1, path2) {
+ return path1 == path2;
+ }
+ };
+ X.ParsedPath.prototype = {
+ get$hasTrailingSeparator: function() {
+ var t1 = this.parts;
+ if (t1.length !== 0)
+ t1 = J.$eq$(C.JSArray_methods.get$last(t1), "") || !J.$eq$(C.JSArray_methods.get$last(this.separators), "");
+ else
+ t1 = false;
+ return t1;
+ },
+ removeTrailingSeparators$0: function() {
+ var t1, t2, _this = this;
+ while (true) {
+ t1 = _this.parts;
+ if (!(t1.length !== 0 && J.$eq$(C.JSArray_methods.get$last(t1), "")))
+ break;
+ C.JSArray_methods.removeLast$0(_this.parts);
+ C.JSArray_methods.removeLast$0(_this.separators);
+ }
+ t1 = _this.separators;
+ t2 = t1.length;
+ if (t2 !== 0)
+ C.JSArray_methods.$indexSet(t1, t2 - 1, "");
+ },
+ normalize$0: function() {
+ var t1, t2, leadingDoubles, _i, part, t3, newSeparators, _this = this,
+ newParts = H.setRuntimeTypeInfo([], type$.JSArray_String);
+ for (t1 = _this.parts, t2 = t1.length, leadingDoubles = 0, _i = 0; _i < t1.length; t1.length === t2 || (0, H.throwConcurrentModificationError)(t1), ++_i) {
+ part = t1[_i];
+ t3 = J.getInterceptor$(part);
+ if (!(t3.$eq(part, ".") || t3.$eq(part, "")))
+ if (t3.$eq(part, "..")) {
+ t3 = newParts.length;
+ if (t3 !== 0) {
+ if (0 >= t3)
+ return H.ioore(newParts, -1);
+ newParts.pop();
+ } else
+ ++leadingDoubles;
+ } else
+ C.JSArray_methods.add$1(newParts, part);
+ }
+ if (_this.root == null)
+ C.JSArray_methods.insertAll$2(newParts, 0, P.List_List$filled(leadingDoubles, "..", type$.String));
+ if (newParts.length === 0 && _this.root == null)
+ C.JSArray_methods.add$1(newParts, ".");
+ newSeparators = P.List_List$generate(newParts.length, new X.ParsedPath_normalize_closure(_this), true, type$.String);
+ t1 = _this.root;
+ C.JSArray_methods.insert$2(newSeparators, 0, t1 != null && newParts.length !== 0 && _this.style.needsSeparator$1(t1) ? _this.style.get$separator() : "");
+ _this.set$parts(newParts);
+ _this.set$separators(newSeparators);
+ t1 = _this.root;
+ if (t1 != null && _this.style === $.$get$Style_windows()) {
+ t1.toString;
+ _this.root = H.stringReplaceAllUnchecked(t1, "/", "\\");
+ }
+ _this.removeTrailingSeparators$0();
+ },
+ toString$0: function(_) {
+ var i, t2, _this = this,
+ t1 = _this.root;
+ t1 = t1 != null ? t1 : "";
+ for (i = 0; i < _this.parts.length; ++i) {
+ t2 = _this.separators;
+ if (i >= t2.length)
+ return H.ioore(t2, i);
+ t2 = t1 + H.S(t2[i]);
+ t1 = _this.parts;
+ if (i >= t1.length)
+ return H.ioore(t1, i);
+ t1 = t2 + H.S(t1[i]);
+ }
+ t1 += H.S(C.JSArray_methods.get$last(_this.separators));
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ set$parts: function(parts) {
+ this.parts = type$.List_String._check(parts);
+ },
+ set$separators: function(separators) {
+ this.separators = type$.List_String._check(separators);
+ }
+ };
+ X.ParsedPath_normalize_closure.prototype = {
+ call$1: function(_) {
+ return this.$this.style.get$separator();
+ },
+ $signature: 25
+ };
+ X.PathException.prototype = {
+ toString$0: function(_) {
+ return "PathException: " + this.message;
+ },
+ $isException: 1,
+ get$message: function(receiver) {
+ return this.message;
+ }
+ };
+ O.Style.prototype = {
+ toString$0: function(_) {
+ return this.get$name(this);
+ }
+ };
+ E.PosixStyle.prototype = {
+ containsSeparator$1: function(path) {
+ return C.JSString_methods.contains$1(path, "/");
+ },
+ isSeparator$1: function(codeUnit) {
+ return codeUnit === 47;
+ },
+ needsSeparator$1: function(path) {
+ var t1 = path.length;
+ return t1 !== 0 && C.JSString_methods.codeUnitAt$1(path, t1 - 1) !== 47;
+ },
+ rootLength$2$withDrive: function(path, withDrive) {
+ if (path.length !== 0 && C.JSString_methods._codeUnitAt$1(path, 0) === 47)
+ return 1;
+ return 0;
+ },
+ rootLength$1: function(path) {
+ return this.rootLength$2$withDrive(path, false);
+ },
+ isRootRelative$1: function(path) {
+ return false;
+ },
+ pathFromUri$1: function(uri) {
+ var t1;
+ if (uri.get$scheme() === "" || uri.get$scheme() === "file") {
+ t1 = uri.get$path(uri);
+ return P._Uri__uriDecode(t1, 0, t1.length, C.C_Utf8Codec, false);
+ }
+ throw H.wrapException(P.ArgumentError$("Uri " + uri.toString$0(0) + " must have scheme 'file:'."));
+ },
+ absolutePathToUri$1: function(path) {
+ var parsed = X.ParsedPath_ParsedPath$parse(path, this),
+ t1 = parsed.parts;
+ if (t1.length === 0)
+ C.JSArray_methods.addAll$1(t1, H.setRuntimeTypeInfo(["", ""], type$.JSArray_String));
+ else if (parsed.get$hasTrailingSeparator())
+ C.JSArray_methods.add$1(parsed.parts, "");
+ return P._Uri__Uri(null, null, parsed.parts, "file");
+ },
+ get$name: function() {
+ return "posix";
+ },
+ get$separator: function() {
+ return "/";
+ }
+ };
+ F.UrlStyle.prototype = {
+ containsSeparator$1: function(path) {
+ return C.JSString_methods.contains$1(path, "/");
+ },
+ isSeparator$1: function(codeUnit) {
+ return codeUnit === 47;
+ },
+ needsSeparator$1: function(path) {
+ var t1 = path.length;
+ if (t1 === 0)
+ return false;
+ if (C.JSString_methods.codeUnitAt$1(path, t1 - 1) !== 47)
+ return true;
+ return C.JSString_methods.endsWith$1(path, "://") && this.rootLength$1(path) === t1;
+ },
+ rootLength$2$withDrive: function(path, withDrive) {
+ var i, codeUnit, index, t2,
+ t1 = path.length;
+ if (t1 === 0)
+ return 0;
+ if (C.JSString_methods._codeUnitAt$1(path, 0) === 47)
+ return 1;
+ for (i = 0; i < t1; ++i) {
+ codeUnit = C.JSString_methods._codeUnitAt$1(path, i);
+ if (codeUnit === 47)
+ return 0;
+ if (codeUnit === 58) {
+ if (i === 0)
+ return 0;
+ index = C.JSString_methods.indexOf$2(path, "/", C.JSString_methods.startsWith$2(path, "//", i + 1) ? i + 3 : i);
+ if (index <= 0)
+ return t1;
+ if (!withDrive || t1 < index + 3)
+ return index;
+ if (!C.JSString_methods.startsWith$1(path, "file://"))
+ return index;
+ if (!B.isDriveLetter(path, index + 1))
+ return index;
+ t2 = index + 3;
+ return t1 === t2 ? t2 : index + 4;
+ }
+ }
+ return 0;
+ },
+ rootLength$1: function(path) {
+ return this.rootLength$2$withDrive(path, false);
+ },
+ isRootRelative$1: function(path) {
+ return path.length !== 0 && C.JSString_methods._codeUnitAt$1(path, 0) === 47;
+ },
+ pathFromUri$1: function(uri) {
+ return J.toString$0$(uri);
+ },
+ relativePathToUri$1: function(path) {
+ return P.Uri_parse(path);
+ },
+ absolutePathToUri$1: function(path) {
+ return P.Uri_parse(path);
+ },
+ get$name: function() {
+ return "url";
+ },
+ get$separator: function() {
+ return "/";
+ }
+ };
+ L.WindowsStyle.prototype = {
+ containsSeparator$1: function(path) {
+ return C.JSString_methods.contains$1(path, "/");
+ },
+ isSeparator$1: function(codeUnit) {
+ return codeUnit === 47 || codeUnit === 92;
+ },
+ needsSeparator$1: function(path) {
+ var t1 = path.length;
+ if (t1 === 0)
+ return false;
+ t1 = C.JSString_methods.codeUnitAt$1(path, t1 - 1);
+ return !(t1 === 47 || t1 === 92);
+ },
+ rootLength$2$withDrive: function(path, withDrive) {
+ var t2, index,
+ t1 = path.length;
+ if (t1 === 0)
+ return 0;
+ t2 = C.JSString_methods._codeUnitAt$1(path, 0);
+ if (t2 === 47)
+ return 1;
+ if (t2 === 92) {
+ if (t1 < 2 || C.JSString_methods._codeUnitAt$1(path, 1) !== 92)
+ return 1;
+ index = C.JSString_methods.indexOf$2(path, "\\", 2);
+ if (index > 0) {
+ index = C.JSString_methods.indexOf$2(path, "\\", index + 1);
+ if (index > 0)
+ return index;
+ }
+ return t1;
+ }
+ if (t1 < 3)
+ return 0;
+ if (!B.isAlphabetic(t2))
+ return 0;
+ if (C.JSString_methods._codeUnitAt$1(path, 1) !== 58)
+ return 0;
+ t1 = C.JSString_methods._codeUnitAt$1(path, 2);
+ if (!(t1 === 47 || t1 === 92))
+ return 0;
+ return 3;
+ },
+ rootLength$1: function(path) {
+ return this.rootLength$2$withDrive(path, false);
+ },
+ isRootRelative$1: function(path) {
+ return this.rootLength$1(path) === 1;
+ },
+ pathFromUri$1: function(uri) {
+ var path, t1;
+ if (uri.get$scheme() !== "" && uri.get$scheme() !== "file")
+ throw H.wrapException(P.ArgumentError$("Uri " + uri.toString$0(0) + " must have scheme 'file:'."));
+ path = uri.get$path(uri);
+ if (uri.get$host(uri) === "") {
+ if (path.length >= 3 && C.JSString_methods.startsWith$1(path, "/") && B.isDriveLetter(path, 1))
+ path = C.JSString_methods.replaceFirst$2(path, "/", "");
+ } else
+ path = "\\\\" + H.S(uri.get$host(uri)) + path;
+ t1 = H.stringReplaceAllUnchecked(path, "/", "\\");
+ return P._Uri__uriDecode(t1, 0, t1.length, C.C_Utf8Codec, false);
+ },
+ absolutePathToUri$1: function(path) {
+ var rootParts, t2,
+ parsed = X.ParsedPath_ParsedPath$parse(path, this),
+ t1 = parsed.root;
+ if (J.startsWith$1$s(t1, "\\\\")) {
+ rootParts = new H.WhereIterable(H.setRuntimeTypeInfo(t1.split("\\"), type$.JSArray_String), type$.bool_Function_String._check(new L.WindowsStyle_absolutePathToUri_closure()), type$.WhereIterable_String);
+ C.JSArray_methods.insert$2(parsed.parts, 0, rootParts.get$last(rootParts));
+ if (parsed.get$hasTrailingSeparator())
+ C.JSArray_methods.add$1(parsed.parts, "");
+ return P._Uri__Uri(rootParts.get$first(rootParts), null, parsed.parts, "file");
+ } else {
+ if (parsed.parts.length === 0 || parsed.get$hasTrailingSeparator())
+ C.JSArray_methods.add$1(parsed.parts, "");
+ t1 = parsed.parts;
+ t2 = parsed.root;
+ t2.toString;
+ t2 = H.stringReplaceAllUnchecked(t2, "/", "");
+ C.JSArray_methods.insert$2(t1, 0, H.stringReplaceAllUnchecked(t2, "\\", ""));
+ return P._Uri__Uri(null, null, parsed.parts, "file");
+ }
+ },
+ codeUnitsEqual$2: function(codeUnit1, codeUnit2) {
+ var upperCase1;
+ if (codeUnit1 === codeUnit2)
+ return true;
+ if (codeUnit1 === 47)
+ return codeUnit2 === 92;
+ if (codeUnit1 === 92)
+ return codeUnit2 === 47;
+ if ((codeUnit1 ^ codeUnit2) !== 32)
+ return false;
+ upperCase1 = codeUnit1 | 32;
+ return upperCase1 >= 97 && upperCase1 <= 122;
+ },
+ pathsEqual$2: function(path1, path2) {
+ var t1, t2, i;
+ if (path1 == path2)
+ return true;
+ t1 = path1.length;
+ if (t1 !== path2.length)
+ return false;
+ for (t2 = J.getInterceptor$s(path2), i = 0; i < t1; ++i)
+ if (!this.codeUnitsEqual$2(C.JSString_methods._codeUnitAt$1(path1, i), t2._codeUnitAt$1(path2, i)))
+ return false;
+ return true;
+ },
+ get$name: function() {
+ return "windows";
+ },
+ get$separator: function() {
+ return "\\";
+ }
+ };
+ L.WindowsStyle_absolutePathToUri_closure.prototype = {
+ call$1: function(part) {
+ return H._checkStringNullable(part) !== "";
+ },
+ $signature: 4
+ };
+ O.Pool.prototype = {
+ request$0: function(_) {
+ var t1, t2, _this = this;
+ if (_this._pool$_closeMemo._async_memoizer$_completer.future._state !== 0)
+ throw H.wrapException(P.StateError$("request() may not be called on a closed Pool."));
+ t1 = _this._allocatedResources;
+ if (t1 < _this._maxAllocatedResources) {
+ _this._allocatedResources = t1 + 1;
+ t1 = new P._Future($.Zone__current, type$._Future_PoolResource);
+ t1._asyncComplete$1(new O.PoolResource(_this));
+ return t1;
+ } else {
+ t1 = _this._onReleaseCallbacks;
+ if (!t1.get$isEmpty(t1))
+ return _this._runOnRelease$1(t1.removeFirst$0());
+ else {
+ t1 = new P._Future($.Zone__current, type$._Future_PoolResource);
+ t2 = _this._requestedResources;
+ t2._collection$_add$1(t2.$ti._precomputed1._check(new P._AsyncCompleter(t1, type$._AsyncCompleter_PoolResource)));
+ _this._resetTimer$0();
+ return t1;
+ }
+ }
+ },
+ withResource$1$1: function(callback, $T) {
+ $T._eval$1("0/()")._check(callback);
+ return this.withResource$body$Pool(callback, $T, $T);
+ },
+ withResource$body$Pool: function(callback, $T, $async$type) {
+ var $async$goto = 0,
+ $async$completer = P._makeAsyncAwaitCompleter($async$type),
+ $async$returnValue, $async$handler = 2, $async$currentError, $async$next = [], $async$self = this, resource, t1;
+ var $async$withResource$1$1 = P._wrapJsFunctionForAsync(function($async$errorCode, $async$result) {
+ if ($async$errorCode === 1) {
+ $async$currentError = $async$result;
+ $async$goto = $async$handler;
+ }
+ while (true)
+ switch ($async$goto) {
+ case 0:
+ // Function start
+ if ($async$self._pool$_closeMemo._async_memoizer$_completer.future._state !== 0)
+ throw H.wrapException(P.StateError$("withResource() may not be called on a closed Pool."));
+ $async$goto = 3;
+ return P._asyncAwait($async$self.request$0(0), $async$withResource$1$1);
+ case 3:
+ // returning from await.
+ resource = $async$result;
+ $async$handler = 4;
+ $async$goto = 7;
+ return P._asyncAwait(callback.call$0(), $async$withResource$1$1);
+ case 7:
+ // returning from await.
+ t1 = $async$result;
+ $async$returnValue = t1;
+ $async$next = [1];
+ // goto finally
+ $async$goto = 5;
+ break;
+ $async$next.push(6);
+ // goto finally
+ $async$goto = 5;
+ break;
+ case 4:
+ // uncaught
+ $async$next = [2];
+ case 5:
+ // finally
+ $async$handler = 2;
+ resource.release$0();
+ // goto the next finally handler
+ $async$goto = $async$next.pop();
+ break;
+ case 6:
+ // after finally
+ case 1:
+ // return
+ return P._asyncReturn($async$returnValue, $async$completer);
+ case 2:
+ // rethrow
+ return P._asyncRethrow($async$currentError, $async$completer);
+ }
+ });
+ return P._asyncStartSync($async$withResource$1$1, $async$completer);
+ },
+ close$0: function(_) {
+ return this._pool$_closeMemo.runOnce$1(new O.Pool_close_closure(this));
+ },
+ _onResourceReleaseAllowed$1: function(onRelease) {
+ var t1, zone, t2, _this = this;
+ type$.dynamic_Function._check(onRelease);
+ _this._resetTimer$0();
+ t1 = _this._requestedResources;
+ if (!t1.get$isEmpty(t1))
+ t1.removeFirst$0().complete$1(_this._runOnRelease$1(onRelease));
+ else {
+ t1 = type$.dynamic;
+ if (_this._pool$_closeMemo._async_memoizer$_completer.future._state !== 0) {
+ _this._closeGroup.add$1(0, P.Future_Future$sync(onRelease, t1));
+ if (--_this._allocatedResources === 0)
+ _this._closeGroup.close$0(0);
+ } else {
+ zone = $.Zone__current;
+ t2 = _this._onReleaseCallbacks;
+ t2._collection$_add$1(t2.$ti._precomputed1._check(new O.Pool__onResourceReleaseAllowed_closure(zone, zone.registerCallback$1$1(onRelease, t1))));
+ }
+ }
+ },
+ _runOnRelease$1: function(onRelease) {
+ var t1, t2;
+ P.Future_Future$sync(type$.dynamic_Function._check(onRelease), type$.dynamic).then$1$1(new O.Pool__runOnRelease_closure(this), type$.Null).catchError$1(new O.Pool__runOnRelease_closure0(this));
+ t1 = new P._Future($.Zone__current, type$._Future_PoolResource);
+ t2 = this._onReleaseCompleters;
+ t2._collection$_add$1(t2.$ti._precomputed1._check(new P._SyncCompleter(t1, type$._SyncCompleter_PoolResource)));
+ return t1;
+ },
+ _resetTimer$0: function() {
+ var t2,
+ t1 = this._timer;
+ if (t1 == null)
+ return;
+ t2 = this._requestedResources;
+ if (t2._head === t2._tail)
+ t1._restartable_timer$_timer.cancel$0();
+ else {
+ t1._restartable_timer$_timer.cancel$0();
+ t1._restartable_timer$_timer = P.Timer_Timer(t1._restartable_timer$_duration, t1._callback);
+ }
+ }
+ };
+ O.Pool_close_closure.prototype = {
+ call$0: function() {
+ var t3, t4, callback,
+ t1 = this.$this,
+ t2 = t1._closeGroup;
+ if (t2 != null)
+ return t2._future_group$_completer.future;
+ t1._resetTimer$0();
+ t1._closeGroup = new F.FutureGroup(new P._AsyncCompleter(new P._Future($.Zone__current, type$._Future_List_dynamic), type$._AsyncCompleter_List_dynamic), H.setRuntimeTypeInfo([], type$.JSArray_dynamic), type$.FutureGroup_dynamic);
+ for (t2 = t1._onReleaseCallbacks, t3 = P._ListQueueIterator$(t2, t2.$ti._precomputed1), t4 = type$.dynamic; t3.moveNext$0();) {
+ callback = t3._collection$_current;
+ t1._closeGroup.add$1(0, P.Future_Future$sync(callback, t4));
+ }
+ t1._allocatedResources = t1._allocatedResources - t2.get$length(t2);
+ t2.clear$0(0);
+ if (t1._allocatedResources === 0)
+ t1._closeGroup.close$0(0);
+ return t1._closeGroup._future_group$_completer.future;
+ },
+ $signature: 50
+ };
+ O.Pool__onResourceReleaseAllowed_closure.prototype = {
+ call$0: function() {
+ return this.zone.run$1$1(this.registered, type$.void);
+ },
+ $signature: 1
+ };
+ O.Pool__runOnRelease_closure.prototype = {
+ call$1: function(value) {
+ var t1 = this.$this;
+ t1._onReleaseCompleters.removeFirst$0().complete$1(new O.PoolResource(t1));
+ },
+ $signature: 3
+ };
+ O.Pool__runOnRelease_closure0.prototype = {
+ call$2: function(error, stackTrace) {
+ type$.StackTrace._check(stackTrace);
+ this.$this._onReleaseCompleters.removeFirst$0().completeError$2(error, stackTrace);
+ },
+ $signature: 5
+ };
+ O.PoolResource.prototype = {
+ release$0: function() {
+ var t1, t2;
+ if (this._released)
+ throw H.wrapException(P.StateError$("A PoolResource may only be released once."));
+ this._released = true;
+ t1 = this._pool;
+ t1._resetTimer$0();
+ t2 = t1._requestedResources;
+ if (!t2.get$isEmpty(t2))
+ t2.removeFirst$0().complete$1(new O.PoolResource(t1));
+ else {
+ t2 = --t1._allocatedResources;
+ if (t1._pool$_closeMemo._async_memoizer$_completer.future._state !== 0 && t2 === 0)
+ t1._closeGroup.close$0(0);
+ }
+ }
+ };
+ O.mapStackTrace_closure.prototype = {
+ call$1: function(trace) {
+ var _this = this;
+ return Y.Trace_Trace$from(O.mapStackTrace(_this.sourceMap, type$.Trace._check(trace), _this.minified, _this._box_0.packageResolver, _this.sdkRoot));
+ },
+ $signature: 45
+ };
+ O.mapStackTrace_closure0.prototype = {
+ call$1: function(frame) {
+ var column, t1, t2, span, sourceUrl, t3, packageUrl, t4, _this = this;
+ type$.Frame._check(frame);
+ if (frame.get$line() == null)
+ return null;
+ column = frame.get$column() == null ? 0 : frame.get$column();
+ t1 = frame.get$line();
+ if (typeof t1 !== "number")
+ return t1.$sub();
+ if (typeof column !== "number")
+ return column.$sub();
+ t2 = frame.get$uri();
+ t2 = t2 == null ? null : t2.toString$0(0);
+ span = _this.sourceMap.spanFor$3$uri(t1 - 1, column - 1, t2);
+ if (span == null)
+ return null;
+ sourceUrl = J.toString$0$(span.get$sourceUrl());
+ if (_this.sdkRoot != null && $.$get$url()._isWithinOrEquals$2(_this.sdkLib, sourceUrl) === C._PathRelation_within)
+ sourceUrl = C.JSString_methods.$add("dart:", $.$get$url().relative$2$from(sourceUrl, _this.sdkLib));
+ else {
+ t1 = _this._box_0;
+ t2 = t1.packageResolver;
+ if (t2 != null)
+ if (t2.get$packageRoot() != null && $.$get$url()._isWithinOrEquals$2(J.toString$0$(t1.packageResolver.get$packageRoot()), sourceUrl) === C._PathRelation_within)
+ sourceUrl = C.JSString_methods.$add("package:", $.$get$url().relative$2$from(sourceUrl, J.toString$0$(t1.packageResolver.get$packageRoot())));
+ else if (t1.packageResolver.get$packageConfigMap() != null)
+ for (t2 = t1.packageResolver.get$packageConfigMap()._map.get$keys(), t2 = t2.get$iterator(t2); t2.moveNext$0();) {
+ t3 = t2.get$current();
+ packageUrl = J.toString$0$(t1.packageResolver.get$packageConfigMap()._map.$index(0, t3));
+ t4 = $.$get$url();
+ if (t4._isWithinOrEquals$2(packageUrl, sourceUrl) !== C._PathRelation_within)
+ continue;
+ sourceUrl = C.JSString_methods.$add("package:" + H.S(t3) + "/", t4.relative$2$from(sourceUrl, packageUrl));
+ break;
+ }
+ }
+ t1 = P.Uri_parse(sourceUrl);
+ t2 = span.get$start().get$line();
+ if (typeof t2 !== "number")
+ return t2.$add();
+ t3 = span.get$start().get$column();
+ if (_this.minified)
+ t4 = span.get$isIdentifier() ? span.get$text() : frame.get$member();
+ else
+ t4 = O._prettifyMember(frame.get$member());
+ return new A.Frame(t1, t2 + 1, t3 + 1, t4);
+ },
+ $signature: 46
+ };
+ O.mapStackTrace_closure1.prototype = {
+ call$1: function(frame) {
+ return type$.Frame._check(frame) != null;
+ },
+ $signature: 21
+ };
+ O._prettifyMember_closure.prototype = {
+ call$1: function(match) {
+ return C.JSString_methods.$mul(".<fn>", match.$index(0, 1).length);
+ },
+ $signature: 24
+ };
+ O._prettifyMember_closure0.prototype = {
+ call$1: function(match) {
+ return J.$add$ansx(match.$index(0, 1), ".");
+ },
+ $signature: 24
+ };
+ T.Mapping.prototype = {};
+ T.MultiSectionMapping.prototype = {
+ MultiSectionMapping$fromJson$3$mapUrl: function(sections, otherMaps, mapUrl) {
+ var t1, t2, t3, t4, t5, section, t6, line, column, url, map, _s6_ = "offset", _null = null;
+ for (t1 = J.get$iterator$ax(sections), t2 = this._maps, t3 = type$.Map_dynamic_dynamic, t4 = this._lineStart, t5 = this._columnStart; t1.moveNext$0();) {
+ section = t1.get$current();
+ t6 = J.getInterceptor$asx(section);
+ if (t6.$index(section, _s6_) == null)
+ throw H.wrapException(P.FormatException$("section missing offset", _null, _null));
+ line = J.$index$asx(t6.$index(section, _s6_), "line");
+ if (line == null)
+ throw H.wrapException(P.FormatException$("offset missing line", _null, _null));
+ column = J.$index$asx(t6.$index(section, _s6_), "column");
+ if (column == null)
+ throw H.wrapException(P.FormatException$("offset missing column", _null, _null));
+ C.JSArray_methods.add$1(t4, H._checkIntNullable(line));
+ C.JSArray_methods.add$1(t5, H._checkIntNullable(column));
+ url = t6.$index(section, "url");
+ map = t6.$index(section, "map");
+ t6 = url != null;
+ if (t6 && map != null)
+ throw H.wrapException(P.FormatException$("section can't use both url and map entries", _null, _null));
+ else if (t6) {
+ t6 = P.FormatException$("section contains refers to " + H.S(url) + ', but no map was given for it. Make sure a map is passed in "otherMaps"', _null, _null);
+ throw H.wrapException(t6);
+ } else if (map != null)
+ C.JSArray_methods.add$1(t2, T.parseJson(t3._check(map), mapUrl, otherMaps));
+ else
+ throw H.wrapException(P.FormatException$("section missing url or map", _null, _null));
+ }
+ if (t4.length === 0)
+ throw H.wrapException(P.FormatException$("expected at least one section", _null, _null));
+ },
+ _indexFor$2: function(line, column) {
+ var t1, t2, t3, t4, i, t5;
+ for (t1 = this._lineStart, t2 = t1.length, t3 = this._columnStart, t4 = t3.length, i = 0; i < t2; ++i) {
+ t5 = t1[i];
+ if (line < t5)
+ return i - 1;
+ if (line === t5) {
+ if (i >= t4)
+ return H.ioore(t3, i);
+ t5 = column < t3[i];
+ } else
+ t5 = false;
+ if (t5)
+ return i - 1;
+ }
+ return t2 - 1;
+ },
+ spanFor$4$files$uri: function(line, column, files, uri) {
+ var index, t1, t2, t3, _this = this;
+ type$.Map_String_SourceFile._check(files);
+ index = _this._indexFor$2(line, column);
+ t1 = _this._maps;
+ if (index < 0 || index >= t1.length)
+ return H.ioore(t1, index);
+ t1 = t1[index];
+ t2 = _this._lineStart;
+ if (index >= t2.length)
+ return H.ioore(t2, index);
+ t2 = t2[index];
+ t3 = _this._columnStart;
+ if (index >= t3.length)
+ return H.ioore(t3, index);
+ return t1.spanFor$3$files(line - t2, column - t3[index], files);
+ },
+ spanFor$3$uri: function(line, column, uri) {
+ return this.spanFor$4$files$uri(line, column, null, uri);
+ },
+ spanFor$3$files: function(line, column, files) {
+ return this.spanFor$4$files$uri(line, column, files, null);
+ },
+ toString$0: function(_) {
+ var t2, t3, t4, i, _this = this,
+ t1 = H.getRuntimeType(_this).toString$0(0) + " : [";
+ for (t2 = _this._lineStart, t3 = _this._columnStart, t4 = _this._maps, i = 0; i < t2.length; ++i) {
+ t1 = t1 + "(" + t2[i] + ",";
+ if (i >= t3.length)
+ return H.ioore(t3, i);
+ t1 = t1 + t3[i] + ":";
+ if (i >= t4.length)
+ return H.ioore(t4, i);
+ t1 = t1 + t4[i].toString$0(0) + ")";
+ }
+ t1 += "]";
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ }
+ };
+ T.MappingBundle.prototype = {
+ MappingBundle$fromJson$2$mapUrl: function(json, mapUrl) {
+ var t1, t2, t3, t4, t5;
+ for (t1 = J.get$iterator$ax(json), t2 = type$.Map_dynamic_dynamic, t3 = type$.SingleMapping, t4 = this._mappings; t1.moveNext$0();) {
+ t5 = t3._as(T.parseJson(t2._check(t1.get$current()), mapUrl, null));
+ t4.$indexSet(0, t5.targetUrl, t5);
+ }
+ },
+ toString$0: function(_) {
+ var t1, t2;
+ for (t1 = this._mappings.get$values(), t1 = t1.get$iterator(t1), t2 = ""; t1.moveNext$0();)
+ t2 += J.toString$0$(t1.get$current());
+ return t2.charCodeAt(0) == 0 ? t2 : t2;
+ },
+ spanFor$4$files$uri: function(line, column, files, uri) {
+ var separatorCodeUnits, t1, t2, onBoundary, i, candidate, $location;
+ type$.Map_String_SourceFile._check(files);
+ if (uri == null)
+ throw H.wrapException(P.ArgumentError$notNull("uri"));
+ separatorCodeUnits = H.setRuntimeTypeInfo([47, 58], type$.JSArray_int);
+ for (t1 = uri.length, t2 = this._mappings, onBoundary = true, i = 0; i < t1; ++i) {
+ if (onBoundary) {
+ candidate = C.JSString_methods.substring$1(uri, i);
+ if (t2.containsKey$1(candidate))
+ return t2.$index(0, candidate).spanFor$4$files$uri(line, column, files, candidate);
+ }
+ onBoundary = C.JSArray_methods.contains$1(separatorCodeUnits, C.JSString_methods._codeUnitAt$1(uri, i));
+ }
+ $location = V.SourceLocation$(line * 1000000 + column, column, line, P.Uri_parse(uri));
+ t1 = new G.SourceMapSpan(false, $location, $location, "");
+ t1.SourceSpanBase$3($location, $location, "");
+ return t1;
+ },
+ spanFor$3$uri: function(line, column, uri) {
+ return this.spanFor$4$files$uri(line, column, null, uri);
+ }
+ };
+ T.SingleMapping.prototype = {
+ SingleMapping$fromJson$2$mapUrl: function(map, mapUrl) {
+ var t5, source, t6, t7, tokenizer, entries, line, column, srcUrlId, srcLine, srcColumn, srcNameId, _this = this,
+ _s14_ = "sourcesContent",
+ _null = null,
+ sourcesContent = map.$index(0, _s14_) == null ? C.List_empty0 : P.List_List$from(type$.Iterable_dynamic._check(map.$index(0, _s14_)), true, type$.String),
+ t1 = type$.Uri,
+ t2 = _this.files,
+ t3 = _this.urls,
+ t4 = type$.JSArray_int,
+ i = 0;
+ while (true) {
+ t5 = t3.length;
+ if (!(i < t5 && i < sourcesContent.length))
+ break;
+ c$0: {
+ if (i >= sourcesContent.length)
+ return H.ioore(sourcesContent, i);
+ source = sourcesContent[i];
+ if (source == null)
+ break c$0;
+ H._checkStringNullable(source);
+ if (i >= t5)
+ return H.ioore(t3, i);
+ t5 = t3[i];
+ t6 = new H.CodeUnits(source);
+ t7 = H.setRuntimeTypeInfo([0], t4);
+ t7 = new Y.SourceFile(t1._check(typeof t5 == "string" ? P.Uri_parse(t5) : t5), t7, new Uint32Array(H._ensureNativeList(t6.toList$0(t6))));
+ t7.SourceFile$decoded$2$url(t6, t5);
+ C.JSArray_methods.$indexSet(t2, i, t7);
+ }
+ ++i;
+ }
+ t1 = H._checkStringNullable(map.$index(0, "mappings"));
+ t2 = t1.length;
+ tokenizer = new T._MappingTokenizer(t1, t2);
+ t1 = type$.JSArray_TargetEntry;
+ entries = H.setRuntimeTypeInfo([], t1);
+ t4 = _this.names;
+ t5 = t2 - 1;
+ t2 = t2 > 0;
+ t6 = _this.lines;
+ line = 0;
+ column = 0;
+ srcUrlId = 0;
+ srcLine = 0;
+ srcColumn = 0;
+ srcNameId = 0;
+ while (true) {
+ if (!(tokenizer.index < t5 && t2))
+ break;
+ c$1: {
+ if (tokenizer.get$nextKind().isNewLine) {
+ if (entries.length !== 0) {
+ C.JSArray_methods.add$1(t6, new T.TargetLineEntry(line, entries));
+ entries = H.setRuntimeTypeInfo([], t1);
+ }
+ ++line;
+ ++tokenizer.index;
+ column = 0;
+ break c$1;
+ }
+ if (tokenizer.get$nextKind().isNewSegment)
+ throw H.wrapException(_this._segmentError$2(0, line));
+ column += L.decodeVlq(tokenizer);
+ t7 = tokenizer.get$nextKind();
+ if (!(!t7.isNewLine && !t7.isNewSegment && !t7.isEof))
+ C.JSArray_methods.add$1(entries, new T.TargetEntry(column, _null, _null, _null, _null));
+ else {
+ srcUrlId += L.decodeVlq(tokenizer);
+ if (srcUrlId >= t3.length)
+ throw H.wrapException(P.StateError$("Invalid source url id. " + H.S(_this.targetUrl) + ", " + line + ", " + srcUrlId));
+ t7 = tokenizer.get$nextKind();
+ if (!(!t7.isNewLine && !t7.isNewSegment && !t7.isEof))
+ throw H.wrapException(_this._segmentError$2(2, line));
+ srcLine += L.decodeVlq(tokenizer);
+ t7 = tokenizer.get$nextKind();
+ if (!(!t7.isNewLine && !t7.isNewSegment && !t7.isEof))
+ throw H.wrapException(_this._segmentError$2(3, line));
+ srcColumn += L.decodeVlq(tokenizer);
+ t7 = tokenizer.get$nextKind();
+ if (!(!t7.isNewLine && !t7.isNewSegment && !t7.isEof))
+ C.JSArray_methods.add$1(entries, new T.TargetEntry(column, srcUrlId, srcLine, srcColumn, _null));
+ else {
+ srcNameId += L.decodeVlq(tokenizer);
+ if (srcNameId >= t4.length)
+ throw H.wrapException(P.StateError$("Invalid name id: " + H.S(_this.targetUrl) + ", " + line + ", " + srcNameId));
+ C.JSArray_methods.add$1(entries, new T.TargetEntry(column, srcUrlId, srcLine, srcColumn, srcNameId));
+ }
+ }
+ if (tokenizer.get$nextKind().isNewSegment)
+ ++tokenizer.index;
+ }
+ }
+ if (entries.length !== 0)
+ C.JSArray_methods.add$1(t6, new T.TargetLineEntry(line, entries));
+ map.forEach$1(0, new T.SingleMapping$fromJson_closure(_this));
+ },
+ _segmentError$2: function(seen, line) {
+ return new P.StateError("Invalid entry in sourcemap, expected 1, 4, or 5 values, but got " + seen + ".\ntargeturl: " + H.S(this.targetUrl) + ", line: " + line);
+ },
+ _findLine$1: function(line) {
+ var t2,
+ t1 = this.lines,
+ index = O.binarySearch(t1, new T.SingleMapping__findLine_closure(line));
+ if (index <= 0)
+ t1 = null;
+ else {
+ t2 = index - 1;
+ if (t2 >= t1.length)
+ return H.ioore(t1, t2);
+ t2 = t1[t2];
+ t1 = t2;
+ }
+ return t1;
+ },
+ _findColumn$3: function(line, column, lineEntry) {
+ var entries, index, t1;
+ if (lineEntry == null || lineEntry.entries.length === 0)
+ return null;
+ if (lineEntry.line !== line)
+ return C.JSArray_methods.get$last(lineEntry.entries);
+ entries = lineEntry.entries;
+ index = O.binarySearch(entries, new T.SingleMapping__findColumn_closure(column));
+ if (index <= 0)
+ t1 = null;
+ else {
+ t1 = index - 1;
+ if (t1 >= entries.length)
+ return H.ioore(entries, t1);
+ t1 = entries[t1];
+ }
+ return t1;
+ },
+ spanFor$4$files$uri: function(line, column, files, uri) {
+ var entry, url, t1, t2, start, t3, _this = this;
+ type$.Map_String_SourceFile._check(files);
+ entry = _this._findColumn$3(line, column, _this._findLine$1(line));
+ if (entry == null || entry.sourceUrlId == null)
+ return null;
+ url = C.JSArray_methods.$index(_this.urls, entry.sourceUrlId);
+ t1 = _this.sourceRoot;
+ if (t1 != null)
+ url = t1 + H.S(url);
+ t1 = _this._parser$_mapUrl;
+ t1 = t1 == null ? url : t1.resolve$1(url);
+ t2 = entry.sourceLine;
+ start = V.SourceLocation$(0, entry.sourceColumn, t2, t1);
+ t1 = entry.sourceNameId;
+ if (t1 != null) {
+ t2 = _this.names;
+ if (t1 >>> 0 !== t1 || t1 >= t2.length)
+ return H.ioore(t2, t1);
+ t1 = t2[t1];
+ t2 = t1.length;
+ t2 = V.SourceLocation$(start.offset + t2, start.column + t2, start.line, start.sourceUrl);
+ t3 = new G.SourceMapSpan(true, start, t2, t1);
+ t3.SourceSpanBase$3(start, t2, t1);
+ return t3;
+ } else {
+ t1 = new G.SourceMapSpan(false, start, start, "");
+ t1.SourceSpanBase$3(start, start, "");
+ return t1;
+ }
+ },
+ spanFor$3$uri: function(line, column, uri) {
+ return this.spanFor$4$files$uri(line, column, null, uri);
+ },
+ spanFor$3$files: function(line, column, files) {
+ return this.spanFor$4$files$uri(line, column, files, null);
+ },
+ toString$0: function(_) {
+ var _this = this,
+ t1 = H.getRuntimeType(_this).toString$0(0);
+ t1 + " : [";
+ t1 = t1 + " : [targetUrl: " + H.S(_this.targetUrl) + ", sourceRoot: " + H.S(_this.sourceRoot) + ", urls: " + H.S(_this.urls) + ", names: " + H.S(_this.names) + ", lines: " + H.S(_this.lines) + "]";
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ }
+ };
+ T.SingleMapping$fromJson_closure.prototype = {
+ call$2: function($name, value) {
+ if (J.startsWith$1$s($name, "x_"))
+ this.$this.extensions.$indexSet(0, H._checkStringNullable($name), value);
+ },
+ $signature: 6
+ };
+ T.SingleMapping__findLine_closure.prototype = {
+ call$1: function(e) {
+ return e.get$line() > this.line;
+ },
+ $signature: 9
+ };
+ T.SingleMapping__findColumn_closure.prototype = {
+ call$1: function(e) {
+ return e.get$column() > this.column;
+ },
+ $signature: 9
+ };
+ T.TargetLineEntry.prototype = {
+ toString$0: function(_) {
+ return H.getRuntimeType(this).toString$0(0) + ": " + this.line + " " + H.S(this.entries);
+ },
+ get$line: function() {
+ return this.line;
+ }
+ };
+ T.TargetEntry.prototype = {
+ toString$0: function(_) {
+ var _this = this;
+ return H.getRuntimeType(_this).toString$0(0) + ": (" + _this.column + ", " + H.S(_this.sourceUrlId) + ", " + H.S(_this.sourceLine) + ", " + H.S(_this.sourceColumn) + ", " + H.S(_this.sourceNameId) + ")";
+ },
+ get$column: function() {
+ return this.column;
+ }
+ };
+ T._MappingTokenizer.prototype = {
+ moveNext$0: function() {
+ return ++this.index < this._parser$_length;
+ },
+ get$current: function() {
+ var t2,
+ t1 = this.index;
+ if (t1 >= 0 && t1 < this._parser$_length) {
+ t2 = this._internal;
+ if (t1 < 0 || t1 >= t2.length)
+ return H.ioore(t2, t1);
+ t1 = t2[t1];
+ } else
+ t1 = null;
+ return t1;
+ },
+ get$hasTokens: function() {
+ var t1 = this._parser$_length;
+ return this.index < t1 - 1 && t1 > 0;
+ },
+ get$nextKind: function() {
+ var t1, t2, next;
+ if (!this.get$hasTokens())
+ return C._TokenKind_false_false_true;
+ t1 = this._internal;
+ t2 = this.index + 1;
+ if (t2 < 0 || t2 >= t1.length)
+ return H.ioore(t1, t2);
+ next = t1[t2];
+ if (next === ";")
+ return C._TokenKind_true_false_false;
+ if (next === ",")
+ return C._TokenKind_false_true_false;
+ return C._TokenKind_false_false_false;
+ },
+ toString$0: function(_) {
+ var t1, i, t2, t3, _this = this;
+ for (t1 = _this._internal, i = 0, t2 = ""; i < _this.index; ++i) {
+ if (i >= t1.length)
+ return H.ioore(t1, i);
+ t2 += t1[i];
+ }
+ t2 += "\x1b[31m";
+ t2 = t2 + H.S(_this.get$current() == null ? "" : _this.get$current()) + "\x1b[0m";
+ for (i = _this.index + 1, t3 = t1.length; i < t3; ++i) {
+ if (i < 0)
+ return H.ioore(t1, i);
+ t2 += t1[i];
+ }
+ t1 = t2 + (" (" + _this.index + ")");
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ $isIterator: 1
+ };
+ T._TokenKind.prototype = {};
+ G.SourceMapSpan.prototype = {
+ get$isIdentifier: function() {
+ return this.isIdentifier;
+ }
+ };
+ L.closure.prototype = {
+ call$0: function() {
+ var i,
+ map = P.LinkedHashMap_LinkedHashMap$_empty(type$.String, type$.int);
+ for (i = 0; i < 64; ++i)
+ map.$indexSet(0, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i], i);
+ return map;
+ },
+ $signature: 93
+ };
+ Y.SourceFile.prototype = {
+ get$length: function(_) {
+ return this._decodedChars.length;
+ },
+ get$lines: function() {
+ return this._lineStarts.length;
+ },
+ SourceFile$decoded$2$url: function(decodedChars, url) {
+ var t1, t2, t3, i, c, j, t4;
+ for (t1 = this._decodedChars, t2 = t1.length, t3 = this._lineStarts, i = 0; i < t2; ++i) {
+ c = t1[i];
+ if (c === 13) {
+ j = i + 1;
+ if (j < t2) {
+ if (j >= t2)
+ return H.ioore(t1, j);
+ t4 = t1[j] !== 10;
+ } else
+ t4 = true;
+ if (t4)
+ c = 10;
+ }
+ if (c === 10)
+ C.JSArray_methods.add$1(t3, i + 1);
+ }
+ },
+ span$2: function(start, end) {
+ return Y._FileSpan$(this, start, end);
+ },
+ getLine$1: function(offset) {
+ var t1, _this = this;
+ if (offset < 0)
+ throw H.wrapException(P.RangeError$("Offset may not be negative, was " + offset + "."));
+ else if (offset > _this._decodedChars.length)
+ throw H.wrapException(P.RangeError$("Offset " + offset + " must not be greater than the number of characters in the file, " + _this.get$length(_this) + "."));
+ t1 = _this._lineStarts;
+ if (offset < C.JSArray_methods.get$first(t1))
+ return -1;
+ if (offset >= C.JSArray_methods.get$last(t1))
+ return t1.length - 1;
+ if (_this._isNearCachedLine$1(offset))
+ return _this._cachedLine;
+ return _this._cachedLine = _this._binarySearch$1(offset) - 1;
+ },
+ _isNearCachedLine$1: function(offset) {
+ var t2, t3, t4, _this = this,
+ t1 = _this._cachedLine;
+ if (t1 == null)
+ return false;
+ t2 = _this._lineStarts;
+ if (t1 >>> 0 !== t1 || t1 >= t2.length)
+ return H.ioore(t2, t1);
+ if (offset < t2[t1])
+ return false;
+ t1 = _this._cachedLine;
+ t3 = t2.length;
+ if (typeof t1 !== "number")
+ return t1.$ge();
+ if (t1 < t3 - 1) {
+ t4 = t1 + 1;
+ if (t4 < 0 || t4 >= t3)
+ return H.ioore(t2, t4);
+ t4 = offset < t2[t4];
+ } else
+ t4 = true;
+ if (t4)
+ return true;
+ if (t1 < t3 - 2) {
+ t4 = t1 + 2;
+ if (t4 < 0 || t4 >= t3)
+ return H.ioore(t2, t4);
+ t4 = offset < t2[t4];
+ t2 = t4;
+ } else
+ t2 = true;
+ if (t2) {
+ _this._cachedLine = t1 + 1;
+ return true;
+ }
+ return false;
+ },
+ _binarySearch$1: function(offset) {
+ var min, half,
+ t1 = this._lineStarts,
+ t2 = t1.length,
+ max = t2 - 1;
+ for (min = 0; min < max;) {
+ half = min + C.JSInt_methods._tdivFast$1(max - min, 2);
+ if (half < 0 || half >= t2)
+ return H.ioore(t1, half);
+ if (t1[half] > offset)
+ max = half;
+ else
+ min = half + 1;
+ }
+ return max;
+ },
+ getColumn$1: function(offset) {
+ var line, lineStart, _this = this;
+ if (offset < 0)
+ throw H.wrapException(P.RangeError$("Offset may not be negative, was " + offset + "."));
+ else if (offset > _this._decodedChars.length)
+ throw H.wrapException(P.RangeError$("Offset " + offset + " must be not be greater than the number of characters in the file, " + _this.get$length(_this) + "."));
+ line = _this.getLine$1(offset);
+ lineStart = C.JSArray_methods.$index(_this._lineStarts, line);
+ if (lineStart > offset)
+ throw H.wrapException(P.RangeError$("Line " + H.S(line) + " comes after offset " + offset + "."));
+ return offset - lineStart;
+ },
+ getOffset$2: function(line, column) {
+ var t1, t2, result, t3;
+ if (typeof line !== "number")
+ return line.$lt();
+ if (line < 0)
+ throw H.wrapException(P.RangeError$("Line may not be negative, was " + line + "."));
+ else {
+ t1 = this._lineStarts;
+ t2 = t1.length;
+ if (line >= t2)
+ throw H.wrapException(P.RangeError$("Line " + line + " must be less than the number of lines in the file, " + this.get$lines() + "."));
+ }
+ result = t1[line];
+ if (result <= this._decodedChars.length) {
+ t3 = line + 1;
+ t1 = t3 < t2 && result >= t1[t3];
+ } else
+ t1 = true;
+ if (t1)
+ throw H.wrapException(P.RangeError$("Line " + line + " doesn't have 0 columns."));
+ return result;
+ },
+ getOffset$1: function(line) {
+ return this.getOffset$2(line, null);
+ }
+ };
+ Y.FileLocation.prototype = {
+ get$sourceUrl: function() {
+ return this.file.url;
+ },
+ get$line: function() {
+ return this.file.getLine$1(this.offset);
+ },
+ get$column: function() {
+ return this.file.getColumn$1(this.offset);
+ },
+ get$offset: function(receiver) {
+ return this.offset;
+ }
+ };
+ Y._FileSpan.prototype = {
+ get$sourceUrl: function() {
+ return this.file.url;
+ },
+ get$length: function(_) {
+ return this._file$_end - this._file$_start;
+ },
+ get$start: function() {
+ return Y.FileLocation$_(this.file, this._file$_start);
+ },
+ get$end: function() {
+ return Y.FileLocation$_(this.file, this._file$_end);
+ },
+ get$text: function() {
+ return P.String_String$fromCharCodes(C.NativeUint32List_methods.sublist$2(this.file._decodedChars, this._file$_start, this._file$_end), 0, null);
+ },
+ get$context: function() {
+ var t2, _this = this,
+ t1 = _this.file,
+ endOffset = _this._file$_end,
+ endLine = t1.getLine$1(endOffset);
+ if (t1.getColumn$1(endOffset) === 0 && endLine !== 0) {
+ if (endOffset - _this._file$_start === 0) {
+ if (endLine === t1._lineStarts.length - 1)
+ t1 = "";
+ else {
+ t2 = t1.getOffset$1(endLine);
+ if (typeof endLine !== "number")
+ return endLine.$add();
+ t1 = P.String_String$fromCharCodes(C.NativeUint32List_methods.sublist$2(t1._decodedChars, t2, t1.getOffset$1(endLine + 1)), 0, null);
+ }
+ return t1;
+ }
+ } else if (endLine === t1._lineStarts.length - 1)
+ endOffset = t1._decodedChars.length;
+ else {
+ if (typeof endLine !== "number")
+ return endLine.$add();
+ endOffset = t1.getOffset$1(endLine + 1);
+ }
+ return P.String_String$fromCharCodes(C.NativeUint32List_methods.sublist$2(t1._decodedChars, t1.getOffset$1(t1.getLine$1(_this._file$_start)), endOffset), 0, null);
+ },
+ $eq: function(_, other) {
+ var _this = this;
+ if (other == null)
+ return false;
+ if (!type$.FileSpan._is(other))
+ return _this.super$SourceSpanMixin$$eq(0, other);
+ if (!(other instanceof Y._FileSpan))
+ return _this.super$SourceSpanMixin$$eq(0, other) && J.$eq$(_this.file.url, other.get$sourceUrl());
+ return _this._file$_start === other._file$_start && _this._file$_end === other._file$_end && J.$eq$(_this.file.url, other.file.url);
+ },
+ get$hashCode: function(_) {
+ return Y.SourceSpanMixin.prototype.get$hashCode.call(this, this);
+ },
+ expand$1: function(_, other) {
+ var start, _this = this,
+ t1 = _this.file;
+ if (!J.$eq$(t1.url, other.file.url))
+ throw H.wrapException(P.ArgumentError$('Source URLs "' + H.S(_this.get$sourceUrl()) + '" and "' + H.S(other.get$sourceUrl()) + "\" don't match."));
+ start = Math.min(_this._file$_start, other._file$_start);
+ return Y._FileSpan$(t1, start, Math.max(_this._file$_end, other._file$_end));
+ },
+ $isFileSpan: 1,
+ $isSourceSpanWithContext: 1
+ };
+ U.Highlighter.prototype = {
+ highlight$0: function() {
+ var t1, t2, lineStart, context, lines, t3, t4, lineNumber, _i, line, lastLineIndex, _this = this;
+ _this._writeSidebar$1$end($._glyphs.get$downEnd());
+ t1 = _this._highlighter$_buffer;
+ t1._contents += "\n";
+ t2 = _this._span;
+ lineStart = B.findLineStart(t2.get$context(), t2.get$text(), t2.get$start().get$column());
+ H.assertHelper(lineStart != null);
+ context = t2.get$context();
+ if (typeof lineStart !== "number")
+ return lineStart.$gt();
+ if (lineStart > 0) {
+ lines = C.JSString_methods.substring$2(context, 0, lineStart - 1).split("\n");
+ t3 = t2.get$start().get$line();
+ t4 = lines.length;
+ if (typeof t3 !== "number")
+ return t3.$sub();
+ lineNumber = t3 - t4;
+ for (t3 = _this._multiline, _i = 0; _i < t4; ++_i) {
+ line = lines[_i];
+ _this._writeSidebar$1$line(lineNumber);
+ t1._contents += C.JSString_methods.$mul(" ", t3 ? 3 : 1);
+ _this._writeText$1(line);
+ t1._contents += "\n";
+ ++lineNumber;
+ }
+ context = C.JSString_methods.substring$1(context, lineStart);
+ }
+ lines = H.setRuntimeTypeInfo(context.split("\n"), type$.JSArray_String);
+ t3 = t2.get$end().get$line();
+ t2 = t2.get$start().get$line();
+ if (typeof t3 !== "number")
+ return t3.$sub();
+ if (typeof t2 !== "number")
+ return H.iae(t2);
+ lastLineIndex = t3 - t2;
+ t2 = C.JSArray_methods.get$last(lines);
+ t2.toString;
+ if (J.get$length$asx(t2) === 0 && lines.length > lastLineIndex + 1) {
+ if (0 >= lines.length)
+ return H.ioore(lines, -1);
+ lines.pop();
+ }
+ _this._writeFirstLine$1(C.JSArray_methods.get$first(lines));
+ if (_this._multiline) {
+ _this._writeIntermediateLines$1(H.SubListIterable$(lines, 1, null, type$.String).take$1(0, lastLineIndex - 1));
+ if (lastLineIndex < 0 || lastLineIndex >= lines.length)
+ return H.ioore(lines, lastLineIndex);
+ _this._writeLastLine$1(lines[lastLineIndex]);
+ }
+ _this._writeTrailingLines$1(H.SubListIterable$(lines, lastLineIndex + 1, null, type$.String));
+ _this._writeSidebar$1$end($._glyphs.get$upEnd());
+ t1 = t1._contents;
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ _writeFirstLine$1: function(line) {
+ var t3, t4, startColumn, endColumn, textBefore, textInside, tabsBefore, tabsInside, _this = this, t1 = {},
+ t2 = _this._span;
+ _this._writeSidebar$1$line(t2.get$start().get$line());
+ t3 = t2.get$start().get$column();
+ t4 = line.length;
+ startColumn = t1.startColumn = Math.min(t3, t4);
+ t3 = t2.get$end();
+ t3 = t3.get$offset(t3);
+ t2 = t2.get$start();
+ endColumn = t1.endColumn = Math.min(startColumn + t3 - t2.get$offset(t2), t4);
+ textBefore = J.substring$2$s(line, 0, startColumn);
+ t2 = _this._multiline;
+ if (t2 && _this._isOnlyWhitespace$1(textBefore)) {
+ t1 = _this._highlighter$_buffer;
+ t1._contents += " ";
+ _this._colorize$1(new U.Highlighter__writeFirstLine_closure(_this, line));
+ t1._contents += "\n";
+ return;
+ }
+ t3 = _this._highlighter$_buffer;
+ t3._contents += C.JSString_methods.$mul(" ", t2 ? 3 : 1);
+ _this._writeText$1(textBefore);
+ textInside = C.JSString_methods.substring$2(line, startColumn, endColumn);
+ _this._colorize$1(new U.Highlighter__writeFirstLine_closure0(_this, textInside));
+ _this._writeText$1(C.JSString_methods.substring$1(line, endColumn));
+ t3._contents += "\n";
+ tabsBefore = _this._countTabs$1(textBefore);
+ tabsInside = _this._countTabs$1(textInside);
+ startColumn += tabsBefore * 3;
+ t1.startColumn = startColumn;
+ t1.endColumn = endColumn + (tabsBefore + tabsInside) * 3;
+ _this._writeSidebar$0();
+ if (t2) {
+ t3._contents += " ";
+ _this._colorize$1(new U.Highlighter__writeFirstLine_closure1(t1, _this));
+ } else {
+ t3._contents += C.JSString_methods.$mul(" ", startColumn + 1);
+ _this._colorize$1(new U.Highlighter__writeFirstLine_closure2(t1, _this));
+ }
+ t3._contents += "\n";
+ },
+ _writeIntermediateLines$1: function(lines) {
+ var t1, lineNumber, t2, t3, _this = this;
+ type$.Iterable_String._check(lines);
+ H.assertHelper(_this._multiline);
+ t1 = _this._span.get$start().get$line();
+ if (typeof t1 !== "number")
+ return t1.$add();
+ lineNumber = t1 + 1;
+ for (t1 = new H.ListIterator(lines, lines.get$length(lines), lines.$ti._eval$1("ListIterator<ListIterable.E>")), t2 = _this._highlighter$_buffer; t1.moveNext$0();) {
+ t3 = t1.__internal$_current;
+ _this._writeSidebar$1$line(lineNumber);
+ t2._contents += " ";
+ _this._colorize$1(new U.Highlighter__writeIntermediateLines_closure(_this, t3));
+ t2._contents += "\n";
+ ++lineNumber;
+ }
+ },
+ _writeLastLine$1: function(line) {
+ var t3, t4, endColumn, textInside, _this = this, t1 = {},
+ t2 = _this._multiline;
+ H.assertHelper(t2);
+ t3 = _this._span;
+ _this._writeSidebar$1$line(t3.get$end().get$line());
+ t3 = t3.get$end().get$column();
+ t4 = line.length;
+ endColumn = t1.endColumn = Math.min(t3, t4);
+ if (t2 && endColumn === t4) {
+ t1 = _this._highlighter$_buffer;
+ t1._contents += " ";
+ _this._colorize$1(new U.Highlighter__writeLastLine_closure(_this, line));
+ t1._contents += "\n";
+ return;
+ }
+ t2 = _this._highlighter$_buffer;
+ t2._contents += " ";
+ textInside = J.substring$2$s(line, 0, endColumn);
+ _this._colorize$1(new U.Highlighter__writeLastLine_closure0(_this, textInside));
+ _this._writeText$1(C.JSString_methods.substring$1(line, endColumn));
+ t2._contents += "\n";
+ t1.endColumn = endColumn + _this._countTabs$1(textInside) * 3;
+ _this._writeSidebar$0();
+ t2._contents += " ";
+ _this._colorize$1(new U.Highlighter__writeLastLine_closure1(t1, _this));
+ t2._contents += "\n";
+ },
+ _writeTrailingLines$1: function(lines) {
+ var t1, lineNumber, t2, t3, t4, _this = this;
+ type$.Iterable_String._check(lines);
+ t1 = _this._span.get$end().get$line();
+ if (typeof t1 !== "number")
+ return t1.$add();
+ lineNumber = t1 + 1;
+ for (t1 = new H.ListIterator(lines, lines.get$length(lines), lines.$ti._eval$1("ListIterator<ListIterable.E>")), t2 = _this._highlighter$_buffer, t3 = _this._multiline; t1.moveNext$0();) {
+ t4 = t1.__internal$_current;
+ _this._writeSidebar$1$line(lineNumber);
+ t2._contents += C.JSString_methods.$mul(" ", t3 ? 3 : 1);
+ _this._writeText$1(t4);
+ t2._contents += "\n";
+ ++lineNumber;
+ }
+ },
+ _writeText$1: function(text) {
+ var t1, t2, t3;
+ text.toString;
+ t1 = new H.CodeUnits(text);
+ t1 = new H.ListIterator(t1, t1.get$length(t1), type$.CodeUnits._eval$1("ListIterator<ListMixin.E>"));
+ t2 = this._highlighter$_buffer;
+ for (; t1.moveNext$0();) {
+ t3 = t1.__internal$_current;
+ if (t3 === 9)
+ t2._contents += C.JSString_methods.$mul(" ", 4);
+ else
+ t2._contents += H.Primitives_stringFromCharCode(t3);
+ }
+ },
+ _writeSidebar$2$end$line: function(end, line) {
+ this._colorize$2$color(new U.Highlighter__writeSidebar_closure(this, line, end), "\x1b[34m");
+ },
+ _writeSidebar$1$end: function(end) {
+ return this._writeSidebar$2$end$line(end, null);
+ },
+ _writeSidebar$1$line: function(line) {
+ return this._writeSidebar$2$end$line(null, line);
+ },
+ _writeSidebar$0: function() {
+ return this._writeSidebar$2$end$line(null, null);
+ },
+ _countTabs$1: function(text) {
+ var t1, count;
+ for (t1 = new H.CodeUnits(text), t1 = new H.ListIterator(t1, t1.get$length(t1), type$.CodeUnits._eval$1("ListIterator<ListMixin.E>")), count = 0; t1.moveNext$0();)
+ if (t1.__internal$_current === 9)
+ ++count;
+ return count;
+ },
+ _isOnlyWhitespace$1: function(text) {
+ var t1, t2;
+ for (t1 = new H.CodeUnits(text), t1 = new H.ListIterator(t1, t1.get$length(t1), type$.CodeUnits._eval$1("ListIterator<ListMixin.E>")); t1.moveNext$0();) {
+ t2 = t1.__internal$_current;
+ if (t2 !== 32 && t2 !== 9)
+ return false;
+ }
+ return true;
+ },
+ _colorize$2$color: function(callback, color) {
+ var t1, t2;
+ type$.void_Function._check(callback);
+ t1 = this._color;
+ t2 = t1 != null;
+ if (t2) {
+ t1 = color == null ? t1 : color;
+ this._highlighter$_buffer._contents += t1;
+ }
+ callback.call$0();
+ if (t2)
+ this._highlighter$_buffer._contents += "\x1b[0m";
+ },
+ _colorize$1: function(callback) {
+ return this._colorize$2$color(callback, null);
+ }
+ };
+ U.Highlighter__writeFirstLine_closure.prototype = {
+ call$0: function() {
+ var t1 = this.$this,
+ t2 = t1._highlighter$_buffer,
+ t3 = t2._contents += $._glyphs.glyphOrAscii$2("\u250c", "/");
+ t2._contents = t3 + " ";
+ t1._writeText$1(this.line);
+ },
+ $signature: 0
+ };
+ U.Highlighter__writeFirstLine_closure0.prototype = {
+ call$0: function() {
+ return this.$this._writeText$1(this.textInside);
+ },
+ $signature: 1
+ };
+ U.Highlighter__writeFirstLine_closure1.prototype = {
+ call$0: function() {
+ var t2,
+ t1 = this.$this._highlighter$_buffer;
+ t1._contents += $._glyphs.get$topLeftCorner();
+ t2 = t1._contents += C.JSString_methods.$mul($._glyphs.get$horizontalLine(), this._box_0.startColumn + 1);
+ t1._contents = t2 + "^";
+ },
+ $signature: 0
+ };
+ U.Highlighter__writeFirstLine_closure2.prototype = {
+ call$0: function() {
+ var t1 = this._box_0;
+ this.$this._highlighter$_buffer._contents += C.JSString_methods.$mul("^", Math.max(t1.endColumn - t1.startColumn, 1));
+ return null;
+ },
+ $signature: 1
+ };
+ U.Highlighter__writeIntermediateLines_closure.prototype = {
+ call$0: function() {
+ var t1 = this.$this,
+ t2 = t1._highlighter$_buffer,
+ t3 = t2._contents += $._glyphs.get$verticalLine();
+ t2._contents = t3 + " ";
+ t1._writeText$1(this.line);
+ },
+ $signature: 0
+ };
+ U.Highlighter__writeLastLine_closure.prototype = {
+ call$0: function() {
+ var t1 = this.$this,
+ t2 = t1._highlighter$_buffer,
+ t3 = t2._contents += $._glyphs.glyphOrAscii$2("\u2514", "\\");
+ t2._contents = t3 + " ";
+ t1._writeText$1(this.line);
+ },
+ $signature: 0
+ };
+ U.Highlighter__writeLastLine_closure0.prototype = {
+ call$0: function() {
+ var t1 = this.$this,
+ t2 = t1._highlighter$_buffer,
+ t3 = t2._contents += $._glyphs.get$verticalLine();
+ t2._contents = t3 + " ";
+ t1._writeText$1(this.textInside);
+ },
+ $signature: 0
+ };
+ U.Highlighter__writeLastLine_closure1.prototype = {
+ call$0: function() {
+ var t2,
+ t1 = this.$this._highlighter$_buffer;
+ t1._contents += $._glyphs.get$bottomLeftCorner();
+ t2 = t1._contents += C.JSString_methods.$mul($._glyphs.get$horizontalLine(), this._box_0.endColumn);
+ t1._contents = t2 + "^";
+ },
+ $signature: 0
+ };
+ U.Highlighter__writeSidebar_closure.prototype = {
+ call$0: function() {
+ var t1 = this.line,
+ t2 = this.$this,
+ t3 = t2._highlighter$_buffer;
+ t2 = t2._paddingBeforeSidebar;
+ if (t1 != null)
+ t3._contents += C.JSString_methods.padRight$1(C.JSInt_methods.toString$0(t1 + 1), t2);
+ else
+ t3._contents += C.JSString_methods.$mul(" ", t2);
+ t1 = this.end;
+ t3._contents += t1 == null ? $._glyphs.get$verticalLine() : t1;
+ },
+ $signature: 0
+ };
+ V.SourceLocation.prototype = {
+ distance$1: function(other) {
+ var t1 = this.sourceUrl;
+ if (!J.$eq$(t1, other.get$sourceUrl()))
+ throw H.wrapException(P.ArgumentError$('Source URLs "' + H.S(t1) + '" and "' + H.S(other.get$sourceUrl()) + "\" don't match."));
+ return Math.abs(this.offset - other.get$offset(other));
+ },
+ $eq: function(_, other) {
+ if (other == null)
+ return false;
+ return type$.SourceLocation._is(other) && J.$eq$(this.sourceUrl, other.get$sourceUrl()) && this.offset === other.get$offset(other);
+ },
+ get$hashCode: function(_) {
+ return J.get$hashCode$(this.sourceUrl) + this.offset;
+ },
+ toString$0: function(_) {
+ var _this = this,
+ t1 = "<" + H.getRuntimeType(_this).toString$0(0) + ": " + _this.offset + " ",
+ source = _this.sourceUrl;
+ return t1 + (H.S(source == null ? "unknown source" : source) + ":" + (_this.line + 1) + ":" + (_this.column + 1)) + ">";
+ },
+ get$sourceUrl: function() {
+ return this.sourceUrl;
+ },
+ get$offset: function(receiver) {
+ return this.offset;
+ },
+ get$line: function() {
+ return this.line;
+ },
+ get$column: function() {
+ return this.column;
+ }
+ };
+ D.SourceLocationMixin.prototype = {
+ distance$1: function(other) {
+ if (!J.$eq$(this.file.url, other.get$sourceUrl()))
+ throw H.wrapException(P.ArgumentError$('Source URLs "' + H.S(this.get$sourceUrl()) + '" and "' + H.S(other.get$sourceUrl()) + "\" don't match."));
+ return Math.abs(this.offset - other.get$offset(other));
+ },
+ $eq: function(_, other) {
+ if (other == null)
+ return false;
+ return type$.SourceLocation._is(other) && J.$eq$(this.file.url, other.get$sourceUrl()) && this.offset === other.get$offset(other);
+ },
+ get$hashCode: function(_) {
+ return J.get$hashCode$(this.file.url) + this.offset;
+ },
+ toString$0: function(_) {
+ var t1 = this.offset,
+ t2 = "<" + H.getRuntimeType(this).toString$0(0) + ": " + t1 + " ",
+ t3 = this.file,
+ source = t3.url,
+ t4 = H.S(source == null ? "unknown source" : source) + ":",
+ t5 = t3.getLine$1(t1);
+ if (typeof t5 !== "number")
+ return t5.$add();
+ return t2 + (t4 + (t5 + 1) + ":" + (t3.getColumn$1(t1) + 1)) + ">";
+ },
+ $isSourceLocation: 1
+ };
+ V.SourceSpan.prototype = {};
+ V.SourceSpanBase.prototype = {
+ SourceSpanBase$3: function(start, end, text) {
+ var t3,
+ t1 = this.end,
+ t2 = this.start;
+ if (!J.$eq$(t1.get$sourceUrl(), t2.get$sourceUrl()))
+ throw H.wrapException(P.ArgumentError$('Source URLs "' + H.S(t2.get$sourceUrl()) + '" and "' + H.S(t1.get$sourceUrl()) + "\" don't match."));
+ else if (t1.get$offset(t1) < t2.get$offset(t2))
+ throw H.wrapException(P.ArgumentError$("End " + t1.toString$0(0) + " must come after start " + t2.toString$0(0) + "."));
+ else {
+ t3 = this.text;
+ if (t3.length !== t2.distance$1(t1))
+ throw H.wrapException(P.ArgumentError$('Text "' + t3 + '" must be ' + t2.distance$1(t1) + " characters long."));
+ }
+ },
+ get$start: function() {
+ return this.start;
+ },
+ get$end: function() {
+ return this.end;
+ },
+ get$text: function() {
+ return this.text;
+ }
+ };
+ G.SourceSpanException.prototype = {
+ get$message: function(_) {
+ return this._span_exception$_message;
+ },
+ toString$1$color: function(_, color) {
+ var t1 = this._span_exception$_span;
+ if (t1 == null)
+ return this._span_exception$_message;
+ return "Error on " + t1.message$2$color(0, this._span_exception$_message, color);
+ },
+ toString$0: function($receiver) {
+ return this.toString$1$color($receiver, null);
+ },
+ $isException: 1
+ };
+ G.SourceSpanFormatException.prototype = {$isFormatException: 1};
+ Y.SourceSpanMixin.prototype = {
+ get$sourceUrl: function() {
+ return this.get$start().get$sourceUrl();
+ },
+ get$length: function(_) {
+ var t2,
+ t1 = this.get$end();
+ t1 = t1.get$offset(t1);
+ t2 = this.get$start();
+ return t1 - t2.get$offset(t2);
+ },
+ message$2$color: function(_, message, color) {
+ var t2, highlight, _this = this,
+ t1 = _this.get$start().get$line();
+ if (typeof t1 !== "number")
+ return t1.$add();
+ t1 = "line " + (t1 + 1) + ", column " + (_this.get$start().get$column() + 1);
+ if (_this.get$sourceUrl() != null) {
+ t2 = _this.get$sourceUrl();
+ t2 = t1 + (" of " + H.S($.$get$context().prettyUri$1(t2)));
+ t1 = t2;
+ }
+ t1 += ": " + H.S(message);
+ highlight = _this.highlight$1$color(color);
+ if (highlight.length !== 0)
+ t1 = t1 + "\n" + highlight;
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ message$1: function($receiver, message) {
+ return this.message$2$color($receiver, message, null);
+ },
+ highlight$1$color: function(color) {
+ var t2, t3, t4, newSpan, _this = this,
+ t1 = type$.SourceSpanWithContext._is(_this);
+ if (!t1 && _this.get$length(_this) === 0)
+ return "";
+ if (t1 && B.findLineStart(_this.get$context(), _this.get$text(), _this.get$start().get$column()) != null)
+ t1 = _this;
+ else {
+ t1 = _this.get$start();
+ t1 = V.SourceLocation$(t1.get$offset(t1), 0, 0, _this.get$sourceUrl());
+ t2 = _this.get$end();
+ t2 = t2.get$offset(t2);
+ t3 = _this.get$sourceUrl();
+ t4 = B.countCodeUnits(_this.get$text(), 10);
+ t3 = X.SourceSpanWithContext$(t1, V.SourceLocation$(t2, U.Highlighter__lastLineLength(_this.get$text()), t4, t3), _this.get$text(), _this.get$text());
+ t1 = t3;
+ }
+ newSpan = U.Highlighter__normalizeEndOfLine(U.Highlighter__normalizeTrailingNewline(U.Highlighter__normalizeNewlines(t1)));
+ return new U.Highlighter(newSpan, color, newSpan.get$start().get$line() != newSpan.get$end().get$line(), J.toString$0$(newSpan.get$end().get$line()).length + 1, new P.StringBuffer("")).highlight$0();
+ },
+ $eq: function(_, other) {
+ if (other == null)
+ return false;
+ return type$.SourceSpan._is(other) && this.get$start().$eq(0, other.get$start()) && this.get$end().$eq(0, other.get$end());
+ },
+ get$hashCode: function(_) {
+ var t2,
+ t1 = this.get$start();
+ t1 = t1.get$hashCode(t1);
+ t2 = this.get$end();
+ return t1 + 31 * t2.get$hashCode(t2);
+ },
+ toString$0: function(_) {
+ var _this = this;
+ return "<" + H.getRuntimeType(_this).toString$0(0) + ": from " + _this.get$start().toString$0(0) + " to " + _this.get$end().toString$0(0) + ' "' + _this.get$text() + '">';
+ },
+ $isSourceSpan: 1
+ };
+ X.SourceSpanWithContext.prototype = {
+ get$context: function() {
+ return this._context;
+ }
+ };
+ U.Chain.prototype = {
+ foldFrames$2$terse: function(predicate, terse) {
+ var t1 = this.traces,
+ t2 = H._arrayInstanceType(t1),
+ t3 = t2._eval$1("MappedListIterable<1,Trace>"),
+ foldedTraces = new H.MappedListIterable(t1, t2._eval$1("Trace(1)")._check(new U.Chain_foldFrames_closure(type$.bool_Function_Frame._check(predicate), true)), t3),
+ nonEmptyTraces = foldedTraces.super$Iterable$where(0, t3._eval$1("bool(ListIterable.E)")._check(new U.Chain_foldFrames_closure0(true)));
+ if (!nonEmptyTraces.get$iterator(nonEmptyTraces).moveNext$0() && !foldedTraces.get$isEmpty(foldedTraces))
+ return new U.Chain(P.List_List$unmodifiable(H.setRuntimeTypeInfo([foldedTraces.get$last(foldedTraces)], type$.JSArray_Trace), type$.Trace));
+ return new U.Chain(P.List_List$unmodifiable(nonEmptyTraces, type$.Trace));
+ },
+ toTrace$0: function() {
+ var t1 = this.traces,
+ t2 = H._arrayInstanceType(t1);
+ return new Y.Trace(P.List_List$unmodifiable(new H.ExpandIterable(t1, t2._eval$1("Iterable<Frame>(1)")._check(new U.Chain_toTrace_closure()), t2._eval$1("ExpandIterable<1,Frame>")), type$.Frame), new P._StringStackTrace(null));
+ },
+ toString$0: function(_) {
+ var t1 = this.traces,
+ t2 = H._arrayInstanceType(t1),
+ t3 = type$.int;
+ return new H.MappedListIterable(t1, t2._eval$1("String(1)")._check(new U.Chain_toString_closure(new H.MappedListIterable(t1, t2._eval$1("int(1)")._check(new U.Chain_toString_closure0()), t2._eval$1("MappedListIterable<1,int>")).fold$1$2(0, 0, H.instantiate1(P.math__max$closure(), t3), t3))), t2._eval$1("MappedListIterable<1,String>")).join$1(0, "===== asynchronous gap ===========================\n");
+ },
+ $isStackTrace: 1,
+ get$traces: function() {
+ return this.traces;
+ }
+ };
+ U.Chain_capture_closure.prototype = {
+ call$0: function() {
+ var error, stackTrace, t1, exception;
+ try {
+ t1 = this.callback.call$0();
+ return t1;
+ } catch (exception) {
+ error = H.unwrapException(exception);
+ stackTrace = H.getTraceFromException(exception);
+ $.Zone__current.handleUncaughtError$2(error, stackTrace);
+ return null;
+ }
+ },
+ $signature: function() {
+ return this.T._eval$1("0()");
+ }
+ };
+ U.Chain_Chain$current_closure.prototype = {
+ call$0: function() {
+ var t3,
+ t1 = this.chain,
+ t2 = C.JSArray_methods.get$first(t1.get$traces()).get$frames();
+ t2 = H.SubListIterable$(t2, this.level + 2, null, H._arrayInstanceType(t2)._precomputed1);
+ t3 = C.JSArray_methods.get$first(t1.get$traces()).get$original();
+ t3 = H.setRuntimeTypeInfo([new Y.Trace(P.List_List$unmodifiable(t2, type$.Frame), new P._StringStackTrace(t3._stackTrace))], type$.JSArray_Trace);
+ t1 = t1.get$traces();
+ C.JSArray_methods.addAll$1(t3, H.SubListIterable$(t1, 1, null, H._arrayInstanceType(t1)._precomputed1));
+ return new U.Chain(P.List_List$unmodifiable(t3, type$.Trace));
+ },
+ $signature: 16
+ };
+ U.Chain_Chain$forTrace_closure.prototype = {
+ call$0: function() {
+ return U.Chain_Chain$parse(J.toString$0$(this.trace));
+ },
+ $signature: 16
+ };
+ U.Chain_Chain$parse_closure.prototype = {
+ call$1: function(trace) {
+ H._checkStringNullable(trace);
+ return new Y.Trace(P.List_List$unmodifiable(Y.Trace__parseVM(trace), type$.Frame), new P._StringStackTrace(trace));
+ },
+ $signature: 42
+ };
+ U.Chain_Chain$parse_closure0.prototype = {
+ call$1: function(trace) {
+ return Y.Trace$parseFriendly(H._checkStringNullable(trace));
+ },
+ $signature: 42
+ };
+ U.Chain_foldFrames_closure.prototype = {
+ call$1: function(trace) {
+ return type$.Trace._check(trace).foldFrames$2$terse(this.predicate, this.terse);
+ },
+ $signature: 45
+ };
+ U.Chain_foldFrames_closure0.prototype = {
+ call$1: function(trace) {
+ type$.Trace._check(trace);
+ if (trace.get$frames().length > 1)
+ return true;
+ if (trace.get$frames().length === 0)
+ return false;
+ if (!this.terse)
+ return false;
+ return C.JSArray_methods.get$single(trace.get$frames()).get$line() != null;
+ },
+ $signature: 52
+ };
+ U.Chain_toTrace_closure.prototype = {
+ call$1: function(trace) {
+ return type$.Trace._check(trace).get$frames();
+ },
+ $signature: 53
+ };
+ U.Chain_toString_closure0.prototype = {
+ call$1: function(trace) {
+ var t1 = type$.Trace._check(trace).get$frames(),
+ t2 = H._arrayInstanceType(t1),
+ t3 = type$.int;
+ return new H.MappedListIterable(t1, t2._eval$1("int(1)")._check(new U.Chain_toString__closure0()), t2._eval$1("MappedListIterable<1,int>")).fold$1$2(0, 0, H.instantiate1(P.math__max$closure(), t3), t3);
+ },
+ $signature: 54
+ };
+ U.Chain_toString__closure0.prototype = {
+ call$1: function(frame) {
+ type$.Frame._check(frame);
+ return frame.get$location(frame).length;
+ },
+ $signature: 39
+ };
+ U.Chain_toString_closure.prototype = {
+ call$1: function(trace) {
+ var t1 = type$.Trace._check(trace).get$frames(),
+ t2 = H._arrayInstanceType(t1);
+ return new H.MappedListIterable(t1, t2._eval$1("String(1)")._check(new U.Chain_toString__closure(this.longest)), t2._eval$1("MappedListIterable<1,String>")).join$0(0);
+ },
+ $signature: 56
+ };
+ U.Chain_toString__closure.prototype = {
+ call$1: function(frame) {
+ type$.Frame._check(frame);
+ return J.padRight$1$s(frame.get$location(frame), this.longest) + " " + H.S(frame.get$member()) + "\n";
+ },
+ $signature: 38
+ };
+ A.Frame.prototype = {
+ get$isCore: function() {
+ return this.uri.get$scheme() === "dart";
+ },
+ get$library: function() {
+ var t1 = this.uri;
+ if (t1.get$scheme() === "data")
+ return "data:...";
+ return $.$get$context().prettyUri$1(t1);
+ },
+ get$$package: function() {
+ var t1 = this.uri;
+ if (t1.get$scheme() !== "package")
+ return null;
+ return C.JSArray_methods.get$first(t1.get$path(t1).split("/"));
+ },
+ get$location: function(_) {
+ var t2, _this = this,
+ t1 = _this.line;
+ if (t1 == null)
+ return _this.get$library();
+ t2 = _this.column;
+ if (t2 == null)
+ return H.S(_this.get$library()) + " " + H.S(t1);
+ return H.S(_this.get$library()) + " " + H.S(t1) + ":" + H.S(t2);
+ },
+ toString$0: function(_) {
+ return H.S(this.get$location(this)) + " in " + H.S(this.member);
+ },
+ get$uri: function() {
+ return this.uri;
+ },
+ get$line: function() {
+ return this.line;
+ },
+ get$column: function() {
+ return this.column;
+ },
+ get$member: function() {
+ return this.member;
+ }
+ };
+ A.Frame_Frame$parseVM_closure.prototype = {
+ call$0: function() {
+ var match, t2, t3, member, uri, lineAndColumn, line, _null = null,
+ t1 = this.frame;
+ if (t1 === "...")
+ return new A.Frame(P._Uri__Uri(_null, _null, _null, _null), _null, _null, "...");
+ match = $.$get$_vmFrame().firstMatch$1(t1);
+ if (match == null)
+ return new N.UnparsedFrame(P._Uri__Uri(_null, "unparsed", _null, _null), t1);
+ t1 = match._match;
+ if (1 >= t1.length)
+ return H.ioore(t1, 1);
+ t2 = t1[1];
+ t3 = $.$get$_asyncBody();
+ t2.toString;
+ t2 = H.stringReplaceAllUnchecked(t2, t3, "<async>");
+ member = H.stringReplaceAllUnchecked(t2, "<anonymous closure>", "<fn>");
+ if (2 >= t1.length)
+ return H.ioore(t1, 2);
+ uri = P.Uri_parse(t1[2]);
+ if (3 >= t1.length)
+ return H.ioore(t1, 3);
+ lineAndColumn = t1[3].split(":");
+ t1 = lineAndColumn.length;
+ line = t1 > 1 ? P.int_parse(lineAndColumn[1], _null, _null) : _null;
+ return new A.Frame(uri, line, t1 > 2 ? P.int_parse(lineAndColumn[2], _null, _null) : _null, member);
+ },
+ $signature: 17
+ };
+ A.Frame_Frame$parseV8_closure.prototype = {
+ call$0: function() {
+ var t2, t3, t4, _s4_ = "<fn>",
+ t1 = this.frame,
+ match = $.$get$_v8Frame().firstMatch$1(t1);
+ if (match == null)
+ return new N.UnparsedFrame(P._Uri__Uri(null, "unparsed", null, null), t1);
+ t1 = new A.Frame_Frame$parseV8_closure_parseLocation(t1);
+ t2 = match._match;
+ t3 = t2.length;
+ if (2 >= t3)
+ return H.ioore(t2, 2);
+ t4 = t2[2];
+ if (t4 != null) {
+ t2 = t2[1];
+ t2.toString;
+ t2 = H.stringReplaceAllUnchecked(t2, "<anonymous>", _s4_);
+ t2 = H.stringReplaceAllUnchecked(t2, "Anonymous function", _s4_);
+ return t1.call$2(t4, H.stringReplaceAllUnchecked(t2, "(anonymous function)", _s4_));
+ } else {
+ if (3 >= t3)
+ return H.ioore(t2, 3);
+ return t1.call$2(t2[3], _s4_);
+ }
+ },
+ $signature: 17
+ };
+ A.Frame_Frame$parseV8_closure_parseLocation.prototype = {
+ call$2: function($location, member) {
+ var t2, urlMatch, t3, _null = null,
+ t1 = $.$get$_v8EvalLocation(),
+ evalMatch = t1.firstMatch$1($location);
+ for (; evalMatch != null;) {
+ t2 = evalMatch._match;
+ if (1 >= t2.length)
+ return H.ioore(t2, 1);
+ $location = t2[1];
+ evalMatch = t1.firstMatch$1($location);
+ }
+ if ($location === "native")
+ return new A.Frame(P.Uri_parse("native"), _null, _null, member);
+ urlMatch = $.$get$_v8UrlLocation().firstMatch$1($location);
+ if (urlMatch == null)
+ return new N.UnparsedFrame(P._Uri__Uri(_null, "unparsed", _null, _null), this.frame);
+ t1 = urlMatch._match;
+ if (1 >= t1.length)
+ return H.ioore(t1, 1);
+ t2 = A.Frame__uriOrPathToUri(t1[1]);
+ if (2 >= t1.length)
+ return H.ioore(t1, 2);
+ t3 = P.int_parse(t1[2], _null, _null);
+ if (3 >= t1.length)
+ return H.ioore(t1, 3);
+ return new A.Frame(t2, t3, P.int_parse(t1[3], _null, _null), member);
+ },
+ $signature: 59
+ };
+ A.Frame_Frame$parseFirefox_closure.prototype = {
+ call$0: function() {
+ var uri, t2, t3, member, line, _null = null,
+ t1 = this.frame,
+ match = $.$get$_firefoxSafariFrame().firstMatch$1(t1);
+ if (match == null)
+ return new N.UnparsedFrame(P._Uri__Uri(_null, "unparsed", _null, _null), t1);
+ t1 = match._match;
+ if (3 >= t1.length)
+ return H.ioore(t1, 3);
+ uri = A.Frame__uriOrPathToUri(t1[3]);
+ t2 = t1.length;
+ if (1 >= t2)
+ return H.ioore(t1, 1);
+ t3 = t1[1];
+ if (t3 != null) {
+ if (2 >= t2)
+ return H.ioore(t1, 2);
+ t2 = C.JSString_methods.allMatches$1("/", t1[2]);
+ member = J.$add$ansx(t3, C.JSArray_methods.join$0(P.List_List$filled(t2.get$length(t2), ".<fn>", type$.String)));
+ if (member === "")
+ member = "<fn>";
+ member = C.JSString_methods.replaceFirst$2(member, $.$get$_initialDot(), "");
+ } else
+ member = "<fn>";
+ if (4 >= t1.length)
+ return H.ioore(t1, 4);
+ t2 = t1[4];
+ line = t2 === "" ? _null : P.int_parse(t2, _null, _null);
+ if (5 >= t1.length)
+ return H.ioore(t1, 5);
+ t1 = t1[5];
+ return new A.Frame(uri, line, t1 == null || t1 === "" ? _null : P.int_parse(t1, _null, _null), member);
+ },
+ $signature: 17
+ };
+ A.Frame_Frame$parseFriendly_closure.prototype = {
+ call$0: function() {
+ var t2, buffer, indices, uri, line, column, _null = null,
+ t1 = this.frame,
+ match = $.$get$_friendlyFrame().firstMatch$1(t1);
+ if (match == null)
+ throw H.wrapException(P.FormatException$("Couldn't parse package:stack_trace stack trace line '" + H.S(t1) + "'.", _null, _null));
+ t1 = match._match;
+ if (1 >= t1.length)
+ return H.ioore(t1, 1);
+ t2 = t1[1];
+ if (t2 === "data:...") {
+ buffer = new P.StringBuffer("");
+ indices = H.setRuntimeTypeInfo([-1], type$.JSArray_int);
+ P.UriData__writeUri(_null, _null, _null, buffer, indices);
+ C.JSArray_methods.add$1(indices, buffer._contents.length);
+ buffer._contents += ",";
+ P.UriData__uriEncodeBytes(C.List_CVk, C.C_AsciiCodec.encode$1(""), buffer);
+ t2 = buffer._contents;
+ uri = new P.UriData(t2.charCodeAt(0) == 0 ? t2 : t2, indices, _null).get$uri();
+ } else
+ uri = P.Uri_parse(t2);
+ if (uri.get$scheme() === "") {
+ t2 = $.$get$context();
+ uri = t2.toUri$1(t2.absolute$7(0, t2.style.pathFromUri$1(M._parseUri(uri)), _null, _null, _null, _null, _null, _null));
+ }
+ if (2 >= t1.length)
+ return H.ioore(t1, 2);
+ t2 = t1[2];
+ line = t2 == null ? _null : P.int_parse(t2, _null, _null);
+ if (3 >= t1.length)
+ return H.ioore(t1, 3);
+ t2 = t1[3];
+ column = t2 == null ? _null : P.int_parse(t2, _null, _null);
+ if (4 >= t1.length)
+ return H.ioore(t1, 4);
+ return new A.Frame(uri, line, column, t1[4]);
+ },
+ $signature: 17
+ };
+ X.LazyChain.prototype = {
+ get$_chain: function() {
+ var _this = this;
+ if (_this._lazy_chain$_inner == null)
+ _this.set$_lazy_chain$_inner(_this._thunk.call$0());
+ return _this._lazy_chain$_inner;
+ },
+ get$traces: function() {
+ return this.get$_chain().get$traces();
+ },
+ foldFrames$2$terse: function(predicate, terse) {
+ return new X.LazyChain(new X.LazyChain_foldFrames_closure(this, type$.bool_Function_Frame._check(predicate), true));
+ },
+ toTrace$0: function() {
+ return new T.LazyTrace(new X.LazyChain_toTrace_closure(this));
+ },
+ toString$0: function(_) {
+ return J.toString$0$(this.get$_chain());
+ },
+ set$_lazy_chain$_inner: function(_inner) {
+ this._lazy_chain$_inner = type$.Chain._check(_inner);
+ },
+ $isStackTrace: 1,
+ $isChain: 1
+ };
+ X.LazyChain_foldFrames_closure.prototype = {
+ call$0: function() {
+ return this.$this.get$_chain().foldFrames$2$terse(this.predicate, this.terse);
+ },
+ $signature: 16
+ };
+ X.LazyChain_toTrace_closure.prototype = {
+ call$0: function() {
+ return this.$this.get$_chain().toTrace$0();
+ },
+ $signature: 8
+ };
+ T.LazyTrace.prototype = {
+ get$_lazy_trace$_trace: function() {
+ var _this = this;
+ if (_this._lazy_trace$_inner == null)
+ _this.set$_lazy_trace$_inner(_this._lazy_trace$_thunk.call$0());
+ return _this._lazy_trace$_inner;
+ },
+ get$frames: function() {
+ return this.get$_lazy_trace$_trace().get$frames();
+ },
+ get$original: function() {
+ return this.get$_lazy_trace$_trace().get$original();
+ },
+ foldFrames$2$terse: function(predicate, terse) {
+ return new T.LazyTrace(new T.LazyTrace_foldFrames_closure(this, type$.bool_Function_Frame._check(predicate), true));
+ },
+ toString$0: function(_) {
+ return J.toString$0$(this.get$_lazy_trace$_trace());
+ },
+ set$_lazy_trace$_inner: function(_inner) {
+ this._lazy_trace$_inner = type$.Trace._check(_inner);
+ },
+ $isStackTrace: 1,
+ $isTrace: 1
+ };
+ T.LazyTrace_foldFrames_closure.prototype = {
+ call$0: function() {
+ return this.$this.get$_lazy_trace$_trace().foldFrames$2$terse(this.predicate, this.terse);
+ },
+ $signature: 8
+ };
+ O.StackZoneSpecification.prototype = {
+ chainFor$1: function(trace) {
+ var t2, previous, t3, t1 = {};
+ t1.trace = trace;
+ if (type$.Chain._is(trace))
+ return trace;
+ if (trace == null) {
+ trace = P.StackTrace_current();
+ t1.trace = trace;
+ t2 = trace;
+ } else
+ t2 = trace;
+ previous = this._chains.$index(0, t2);
+ if (previous == null)
+ previous = this._currentNode;
+ if (previous == null) {
+ t3 = type$.Trace;
+ if (t3._is(t2))
+ return new U.Chain(P.List_List$unmodifiable(H.setRuntimeTypeInfo([t2], type$.JSArray_Trace), t3));
+ return new X.LazyChain(new O.StackZoneSpecification_chainFor_closure(t1));
+ } else
+ return new O._Node(Y.Trace_Trace$from(!type$.Trace._is(t2) ? t1.trace = new T.LazyTrace(new O.StackZoneSpecification_chainFor_closure0(this, t2)) : t2), previous).toChain$0();
+ },
+ _stack_zone_specification$_registerCallback$1$4: function($self, $parent, zone, f, $R) {
+ var t1, t2;
+ $R._eval$1("0()")._check(f);
+ if (f == null || J.$eq$($.Zone__current.$index(0, $.$get$StackZoneSpecification_disableKey()), true))
+ return $parent.registerCallback$1$2(zone, f, $R);
+ t1 = this._currentTrace$1(2);
+ t2 = this._currentNode;
+ return $parent.registerCallback$1$2(zone, new O.StackZoneSpecification__registerCallback_closure(this, f, new O._Node(Y.Trace_Trace$from(t1), t2), $R), $R);
+ },
+ _stack_zone_specification$_registerCallback$4: function($self, $parent, zone, f) {
+ return this._stack_zone_specification$_registerCallback$1$4($self, $parent, zone, f, type$.dynamic);
+ },
+ _stack_zone_specification$_registerUnaryCallback$2$4: function($self, $parent, zone, f, $R, $T) {
+ var t1, t2;
+ $R._eval$1("@<0>")._bind$1($T)._eval$1("1(2)")._check(f);
+ if (f == null || J.$eq$($.Zone__current.$index(0, $.$get$StackZoneSpecification_disableKey()), true))
+ return $parent.registerUnaryCallback$2$2(zone, f, $R, $T);
+ t1 = this._currentTrace$1(2);
+ t2 = this._currentNode;
+ return $parent.registerUnaryCallback$2$2(zone, new O.StackZoneSpecification__registerUnaryCallback_closure(this, f, new O._Node(Y.Trace_Trace$from(t1), t2), $T, $R), $R, $T);
+ },
+ _stack_zone_specification$_registerUnaryCallback$4: function($self, $parent, zone, f) {
+ return this._stack_zone_specification$_registerUnaryCallback$2$4($self, $parent, zone, f, type$.dynamic, type$.dynamic);
+ },
+ _stack_zone_specification$_registerBinaryCallback$3$4: function($self, $parent, zone, f, $R, T1, T2) {
+ var t1, t2;
+ type$.Function._check(f);
+ if (f == null || J.$eq$($.Zone__current.$index(0, $.$get$StackZoneSpecification_disableKey()), true))
+ return $parent.registerBinaryCallback$3$2(zone, $R._eval$1("@<0>")._bind$1(T1)._bind$1(T2)._eval$1("1(2,3)")._check(f), $R, T1, T2);
+ t1 = this._currentTrace$1(2);
+ t2 = this._currentNode;
+ return $parent.registerBinaryCallback$3$2(zone, new O.StackZoneSpecification__registerBinaryCallback_closure(this, f, new O._Node(Y.Trace_Trace$from(t1), t2), T1, T2, $R), $R, T1, T2);
+ },
+ _stack_zone_specification$_registerBinaryCallback$4: function($self, $parent, zone, f) {
+ return this._stack_zone_specification$_registerBinaryCallback$3$4($self, $parent, zone, f, type$.dynamic, type$.dynamic, type$.dynamic);
+ },
+ _stack_zone_specification$_errorCallback$5: function($self, $parent, zone, error, stackTrace) {
+ var t1, t2, t3, asyncError, _this = this;
+ type$.StackTrace._check(stackTrace);
+ if (J.$eq$($.Zone__current.$index(0, $.$get$StackZoneSpecification_disableKey()), true))
+ return $parent.errorCallback$3(zone, error, stackTrace);
+ if (stackTrace == null) {
+ t1 = _this._currentTrace$1(3);
+ t2 = _this._currentNode;
+ stackTrace = new O._Node(Y.Trace_Trace$from(t1), t2).toChain$0();
+ } else {
+ t1 = _this._chains;
+ if (t1.$index(0, stackTrace) == null) {
+ t2 = _this._currentTrace$1(3);
+ t3 = _this._currentNode;
+ t1.$indexSet(0, stackTrace, new O._Node(Y.Trace_Trace$from(t2), t3));
+ }
+ }
+ asyncError = $parent.errorCallback$3(zone, error, stackTrace);
+ return asyncError == null ? new P.AsyncError(error, stackTrace) : asyncError;
+ },
+ _stack_zone_specification$_run$1$2: function(f, node, $T) {
+ var previousNode, stackTrace, t1, exception, t2, _this = this;
+ $T._eval$1("0()")._check(f);
+ previousNode = _this._currentNode;
+ _this._currentNode = node;
+ try {
+ t1 = f.call$0();
+ return t1;
+ } catch (exception) {
+ H.unwrapException(exception);
+ stackTrace = H.getTraceFromException(exception);
+ t1 = _this._chains;
+ t2 = stackTrace;
+ if (t1.$index(0, t2) == null)
+ t1.$indexSet(0, t2, node);
+ throw exception;
+ } finally {
+ _this.set$_currentNode(previousNode);
+ }
+ },
+ _currentTrace$1: function(level) {
+ var t1 = {};
+ t1.level = level;
+ return new T.LazyTrace(new O.StackZoneSpecification__currentTrace_closure(t1, this, P.StackTrace_current()));
+ },
+ _trimVMChain$1: function(trace) {
+ var text = J.toString$0$(trace),
+ index = J.getInterceptor$s(text).indexOf$1(text, "<asynchronous suspension>\n");
+ return index === -1 ? text : C.JSString_methods.substring$2(text, 0, index);
+ },
+ set$_currentNode: function(_currentNode) {
+ this._currentNode = type$._Node._check(_currentNode);
+ }
+ };
+ O.StackZoneSpecification_chainFor_closure.prototype = {
+ call$0: function() {
+ return U.Chain_Chain$parse(J.toString$0$(this._box_0.trace));
+ },
+ $signature: 16
+ };
+ O.StackZoneSpecification_chainFor_closure0.prototype = {
+ call$0: function() {
+ return Y.Trace_Trace$parse(this.$this._trimVMChain$1(this.original));
+ },
+ $signature: 8
+ };
+ O.StackZoneSpecification__registerCallback_closure.prototype = {
+ call$0: function() {
+ var _this = this;
+ return _this.$this._stack_zone_specification$_run$1$2(_this.f, _this.node, _this.R);
+ },
+ $signature: function() {
+ return this.R._eval$1("0()");
+ }
+ };
+ O.StackZoneSpecification__registerUnaryCallback_closure.prototype = {
+ call$1: function(arg) {
+ var _this = this,
+ t1 = _this.R;
+ return _this.$this._stack_zone_specification$_run$1$2(new O.StackZoneSpecification__registerUnaryCallback__closure(_this.f, _this.T._check(arg), t1), _this.node, t1);
+ },
+ $signature: function() {
+ return this.R._eval$1("@<0>")._bind$1(this.T)._eval$1("1(2)");
+ }
+ };
+ O.StackZoneSpecification__registerUnaryCallback__closure.prototype = {
+ call$0: function() {
+ return this.f.call$1(this.arg);
+ },
+ $signature: function() {
+ return this.R._eval$1("0()");
+ }
+ };
+ O.StackZoneSpecification__registerBinaryCallback_closure.prototype = {
+ call$2: function(arg1, arg2) {
+ var _this = this,
+ t1 = _this.R;
+ return _this.$this._stack_zone_specification$_run$1$2(new O.StackZoneSpecification__registerBinaryCallback__closure(_this.f, _this.T1._check(arg1), _this.T2._check(arg2), t1), _this.node, t1);
+ },
+ $signature: function() {
+ return this.R._eval$1("@<0>")._bind$1(this.T1)._bind$1(this.T2)._eval$1("1(2,3)");
+ }
+ };
+ O.StackZoneSpecification__registerBinaryCallback__closure.prototype = {
+ call$0: function() {
+ var _this = this;
+ return _this.R._check(_this.f.call$2(_this.arg1, _this.arg2));
+ },
+ $signature: function() {
+ return this.R._eval$1("0()");
+ }
+ };
+ O.StackZoneSpecification__currentTrace_closure.prototype = {
+ call$0: function() {
+ var text = this.$this._trimVMChain$1(this.stackTrace),
+ t1 = Y.Trace_Trace$parse(text).frames,
+ t2 = this._box_0.level;
+ if (typeof t2 !== "number")
+ return t2.$add();
+ return new Y.Trace(P.List_List$unmodifiable(H.SubListIterable$(t1, t2 + 2, null, H._arrayInstanceType(t1)._precomputed1), type$.Frame), new P._StringStackTrace(text));
+ },
+ $signature: 8
+ };
+ O._Node.prototype = {
+ toChain$0: function() {
+ var node,
+ nodes = H.setRuntimeTypeInfo([], type$.JSArray_Trace);
+ for (node = this; node != null;) {
+ C.JSArray_methods.add$1(nodes, node.trace);
+ node = node.previous;
+ }
+ return new U.Chain(P.List_List$unmodifiable(nodes, type$.Trace));
+ }
+ };
+ Y.Trace.prototype = {
+ foldFrames$2$terse: function(predicate, terse) {
+ var newFrames, t1, t2, _box_0 = {};
+ _box_0.predicate = predicate;
+ _box_0.predicate = new Y.Trace_foldFrames_closure(type$.bool_Function_Frame._check(predicate));
+ newFrames = H.setRuntimeTypeInfo([], type$.JSArray_Frame);
+ for (t1 = this.frames, t2 = H._arrayInstanceType(t1)._eval$1("ReversedListIterable<1>"), t1 = new H.ReversedListIterable(t1, t2), t2 = new H.ListIterator(t1, t1.get$length(t1), t2._eval$1("ListIterator<ListIterable.E>")); t2.moveNext$0();) {
+ t1 = t2.__internal$_current;
+ if (t1 instanceof N.UnparsedFrame || !H.boolConversionCheck(_box_0.predicate.call$1(t1)))
+ C.JSArray_methods.add$1(newFrames, t1);
+ else if (newFrames.length === 0 || !H.boolConversionCheck(_box_0.predicate.call$1(C.JSArray_methods.get$last(newFrames))))
+ C.JSArray_methods.add$1(newFrames, new A.Frame(t1.get$uri(), t1.get$line(), t1.get$column(), t1.get$member()));
+ }
+ newFrames = new H.MappedListIterable(newFrames, type$.Frame_Function_Frame._check(new Y.Trace_foldFrames_closure0(_box_0)), type$.MappedListIterable_Frame_Frame).toList$0(0);
+ if (newFrames.length > 1 && H.boolConversionCheck(_box_0.predicate.call$1(C.JSArray_methods.get$first(newFrames))))
+ C.JSArray_methods.removeAt$1(newFrames, 0);
+ return new Y.Trace(P.List_List$unmodifiable(new H.ReversedListIterable(newFrames, H._arrayInstanceType(newFrames)._eval$1("ReversedListIterable<1>")), type$.Frame), new P._StringStackTrace(this.original._stackTrace));
+ },
+ toString$0: function(_) {
+ var t1 = this.frames,
+ t2 = H._arrayInstanceType(t1),
+ t3 = type$.int;
+ return new H.MappedListIterable(t1, t2._eval$1("String(1)")._check(new Y.Trace_toString_closure(new H.MappedListIterable(t1, t2._eval$1("int(1)")._check(new Y.Trace_toString_closure0()), t2._eval$1("MappedListIterable<1,int>")).fold$1$2(0, 0, H.instantiate1(P.math__max$closure(), t3), t3))), t2._eval$1("MappedListIterable<1,String>")).join$0(0);
+ },
+ $isStackTrace: 1,
+ get$frames: function() {
+ return this.frames;
+ },
+ get$original: function() {
+ return this.original;
+ }
+ };
+ Y.Trace_Trace$current_closure.prototype = {
+ call$0: function() {
+ var t1 = this.trace,
+ t2 = t1.get$frames();
+ t2 = H.SubListIterable$(t2, this.level + 2, null, H._arrayInstanceType(t2)._precomputed1);
+ t1 = t1.get$original();
+ return new Y.Trace(P.List_List$unmodifiable(t2, type$.Frame), new P._StringStackTrace(t1._stackTrace));
+ },
+ $signature: 8
+ };
+ Y.Trace_Trace$from_closure.prototype = {
+ call$0: function() {
+ return Y.Trace_Trace$parse(this.trace.toString$0(0));
+ },
+ $signature: 8
+ };
+ Y.Trace__parseVM_closure.prototype = {
+ call$1: function(line) {
+ return A.Frame_Frame$parseVM(H._checkStringNullable(line));
+ },
+ $signature: 10
+ };
+ Y.Trace$parseV8_closure.prototype = {
+ call$1: function(line) {
+ return !J.startsWith$1$s(H._checkStringNullable(line), $.$get$_v8TraceLine());
+ },
+ $signature: 4
+ };
+ Y.Trace$parseV8_closure0.prototype = {
+ call$1: function(line) {
+ return A.Frame_Frame$parseV8(H._checkStringNullable(line));
+ },
+ $signature: 10
+ };
+ Y.Trace$parseJSCore_closure.prototype = {
+ call$1: function(line) {
+ return H._checkStringNullable(line) !== "\tat ";
+ },
+ $signature: 4
+ };
+ Y.Trace$parseJSCore_closure0.prototype = {
+ call$1: function(line) {
+ return A.Frame_Frame$parseV8(H._checkStringNullable(line));
+ },
+ $signature: 10
+ };
+ Y.Trace$parseFirefox_closure.prototype = {
+ call$1: function(line) {
+ H._checkStringNullable(line);
+ return line.length !== 0 && line !== "[native code]";
+ },
+ $signature: 4
+ };
+ Y.Trace$parseFirefox_closure0.prototype = {
+ call$1: function(line) {
+ return A.Frame_Frame$parseFirefox(H._checkStringNullable(line));
+ },
+ $signature: 10
+ };
+ Y.Trace$parseFriendly_closure.prototype = {
+ call$1: function(line) {
+ return !J.startsWith$1$s(H._checkStringNullable(line), "=====");
+ },
+ $signature: 4
+ };
+ Y.Trace$parseFriendly_closure0.prototype = {
+ call$1: function(line) {
+ return A.Frame_Frame$parseFriendly(H._checkStringNullable(line));
+ },
+ $signature: 10
+ };
+ Y.Trace_foldFrames_closure.prototype = {
+ call$1: function(frame) {
+ if (H.boolConversionCheck(this.oldPredicate.call$1(frame)))
+ return true;
+ if (frame.get$isCore())
+ return true;
+ if (frame.get$$package() === "stack_trace")
+ return true;
+ if (!J.contains$1$asx(frame.get$member(), "<async>"))
+ return false;
+ return frame.get$line() == null;
+ },
+ $signature: 21
+ };
+ Y.Trace_foldFrames_closure0.prototype = {
+ call$1: function(frame) {
+ var t1, t2;
+ type$.Frame._check(frame);
+ if (frame instanceof N.UnparsedFrame || !H.boolConversionCheck(this._box_0.predicate.call$1(frame)))
+ return frame;
+ t1 = frame.get$library();
+ t2 = $.$get$_terseRegExp();
+ t1.toString;
+ return new A.Frame(P.Uri_parse(H.stringReplaceAllUnchecked(t1, t2, "")), null, null, frame.get$member());
+ },
+ $signature: 46
+ };
+ Y.Trace_toString_closure0.prototype = {
+ call$1: function(frame) {
+ type$.Frame._check(frame);
+ return frame.get$location(frame).length;
+ },
+ $signature: 39
+ };
+ Y.Trace_toString_closure.prototype = {
+ call$1: function(frame) {
+ type$.Frame._check(frame);
+ if (frame instanceof N.UnparsedFrame)
+ return frame.toString$0(0) + "\n";
+ return J.padRight$1$s(frame.get$location(frame), this.longest) + " " + H.S(frame.get$member()) + "\n";
+ },
+ $signature: 38
+ };
+ N.UnparsedFrame.prototype = {
+ toString$0: function(_) {
+ return this.member;
+ },
+ $isFrame: 1,
+ get$uri: function() {
+ return this.uri;
+ },
+ get$line: function() {
+ return null;
+ },
+ get$column: function() {
+ return null;
+ },
+ get$isCore: function() {
+ return false;
+ },
+ get$library: function() {
+ return "unparsed";
+ },
+ get$$package: function() {
+ return null;
+ },
+ get$location: function() {
+ return "unparsed";
+ },
+ get$member: function() {
+ return this.member;
+ }
+ };
+ K.GuaranteeChannel.prototype = {
+ get$stream: function(_) {
+ var t1 = this._streamController;
+ t1.toString;
+ return new P._ControllerStream(t1, H._instanceType(t1)._eval$1("_ControllerStream<1>"));
+ },
+ get$sink: function() {
+ return this._sink;
+ },
+ GuaranteeChannel$3$allowSinkErrors: function(innerSink, allowSinkErrors, _box_0, $T) {
+ var _this = this;
+ _this.set$_sink(new K._GuaranteeSink(innerSink, _this, new P._AsyncCompleter(new P._Future($.Zone__current, type$._Future_dynamic), type$._AsyncCompleter_dynamic), allowSinkErrors, $T._eval$1("_GuaranteeSink<0>")));
+ _this.set$_streamController(P.StreamController_StreamController(null, new K.GuaranteeChannel_closure(_box_0, _this), true, $T));
+ },
+ _onSinkDisconnected$0: function() {
+ this._disconnected = true;
+ var t1 = this._subscription;
+ if (t1 != null)
+ t1.cancel$0();
+ this._streamController.close$0(0);
+ },
+ set$_sink: function(_sink) {
+ this._sink = this.$ti._eval$1("_GuaranteeSink<1>")._check(_sink);
+ },
+ set$_streamController: function(_streamController) {
+ this._streamController = this.$ti._eval$1("StreamController<1>")._check(_streamController);
+ },
+ set$_subscription: function(_subscription) {
+ this._subscription = this.$ti._eval$1("StreamSubscription<1>")._check(_subscription);
+ }
+ };
+ K.GuaranteeChannel_closure.prototype = {
+ call$0: function() {
+ var t2, t3,
+ t1 = this.$this;
+ if (t1._disconnected)
+ return;
+ t2 = this._box_0.innerStream;
+ t3 = t1._streamController;
+ t1.set$_subscription(t2.listen$3$onDone$onError(t3.get$add(t3), new K.GuaranteeChannel__closure(t1), t3.get$addError()));
+ },
+ $signature: 0
+ };
+ K.GuaranteeChannel__closure.prototype = {
+ call$0: function() {
+ var t1 = this.$this;
+ t1._sink._onStreamDisconnected$0();
+ t1._streamController.close$0(0);
+ },
+ $signature: 0
+ };
+ K._GuaranteeSink.prototype = {
+ get$done: function() {
+ return this._doneCompleter.future;
+ },
+ add$1: function(_, data) {
+ var t1, _this = this;
+ _this.$ti._precomputed1._check(data);
+ if (_this._closed)
+ throw H.wrapException(P.StateError$("Cannot add event after closing."));
+ if (_this._addStreamSubscription != null)
+ throw H.wrapException(P.StateError$("Cannot add event while adding stream."));
+ if (_this._disconnected)
+ return;
+ t1 = _this._inner;
+ t1._async$_target.add$1(0, t1.$ti._precomputed1._check(data));
+ },
+ addError$2: function(error, stackTrace) {
+ var _this = this;
+ type$.StackTrace._check(stackTrace);
+ if (_this._closed)
+ throw H.wrapException(P.StateError$("Cannot add event after closing."));
+ if (_this._addStreamSubscription != null)
+ throw H.wrapException(P.StateError$("Cannot add event while adding stream."));
+ if (_this._disconnected)
+ return;
+ _this._addError$2(error, stackTrace);
+ },
+ addError$1: function(error) {
+ return this.addError$2(error, null);
+ },
+ _addError$2: function(error, stackTrace) {
+ var _this = this;
+ type$.StackTrace._check(stackTrace);
+ if (_this._allowErrors) {
+ _this._inner._async$_target.addError$2(error, stackTrace);
+ return;
+ }
+ _this._doneCompleter.completeError$2(error, stackTrace);
+ _this._onStreamDisconnected$0();
+ _this._channel._onSinkDisconnected$0();
+ _this._inner._async$_target.close$0(0).catchError$1(new K._GuaranteeSink__addError_closure());
+ },
+ _addError$1: function(error) {
+ return this._addError$2(error, null);
+ },
+ addStream$1: function(stream) {
+ var t1, t2, _this = this;
+ _this.$ti._eval$1("Stream<1>")._check(stream);
+ if (_this._closed)
+ throw H.wrapException(P.StateError$("Cannot add stream after closing."));
+ if (_this._addStreamSubscription != null)
+ throw H.wrapException(P.StateError$("Cannot add stream while adding stream."));
+ if (_this._disconnected) {
+ t1 = new P._Future($.Zone__current, type$._Future_void);
+ t1._asyncComplete$1(null);
+ return t1;
+ }
+ t1 = new P._SyncCompleter(new P._Future($.Zone__current, type$._Future_dynamic), type$._SyncCompleter_dynamic);
+ _this._addStreamCompleter = t1;
+ t2 = _this._inner;
+ _this.set$_addStreamSubscription(stream.listen$3$onDone$onError(t2.get$add(t2), t1.get$complete(), _this.get$_addError()));
+ return _this._addStreamCompleter.future.then$1$1(new K._GuaranteeSink_addStream_closure(_this), type$.void);
+ },
+ close$0: function(_) {
+ var _this = this;
+ if (_this._addStreamSubscription != null)
+ throw H.wrapException(P.StateError$("Cannot close sink while adding stream."));
+ if (_this._closed)
+ return _this._doneCompleter.future;
+ _this._closed = true;
+ if (!_this._disconnected) {
+ _this._channel._onSinkDisconnected$0();
+ _this._doneCompleter.complete$1(_this._inner._async$_target.close$0(0));
+ }
+ return _this._doneCompleter.future;
+ },
+ _onStreamDisconnected$0: function() {
+ var t1, _this = this;
+ _this._disconnected = true;
+ t1 = _this._doneCompleter;
+ if (t1.future._state === 0)
+ t1.complete$0();
+ t1 = _this._addStreamSubscription;
+ if (t1 == null)
+ return;
+ _this._addStreamCompleter.complete$1(t1.cancel$0());
+ _this._addStreamCompleter = null;
+ _this.set$_addStreamSubscription(null);
+ },
+ set$_addStreamSubscription: function(_addStreamSubscription) {
+ this._addStreamSubscription = this.$ti._eval$1("StreamSubscription<1>")._check(_addStreamSubscription);
+ },
+ $isEventSink: 1,
+ $isStreamConsumer: 1,
+ $isStreamSink: 1,
+ $isSink: 1
+ };
+ K._GuaranteeSink__addError_closure.prototype = {
+ call$1: function(_) {
+ },
+ $signature: 3
+ };
+ K._GuaranteeSink_addStream_closure.prototype = {
+ call$1: function(_) {
+ var t1 = this.$this;
+ t1._addStreamCompleter = null;
+ t1.set$_addStreamSubscription(null);
+ },
+ $signature: 3
+ };
+ D._MultiChannel.prototype = {
+ _MultiChannel$1: function(_inner, $T) {
+ var t2, _this = this,
+ t1 = _this._mainController;
+ _this._controllers.$indexSet(0, 0, t1);
+ t2 = t1._local._streamController;
+ t2.toString;
+ new P._ControllerStream(t2, H._instanceType(t2)._eval$1("_ControllerStream<1>")).listen$2$onDone(new D._MultiChannel_closure(_this, $T), new D._MultiChannel_closure0(_this));
+ t2 = _this._multi_channel$_inner._streamController;
+ t2.toString;
+ _this._innerStreamSubscription = new P._ControllerStream(t2, H._instanceType(t2)._eval$1("_ControllerStream<1>")).listen$3$onDone$onError(new D._MultiChannel_closure1(_this, $T), _this.get$_closeInnerChannel(), t1._local._sink.get$addError());
+ },
+ virtualChannel$1: function(id) {
+ var t2, t3, controller, _this = this, t1 = {};
+ t1.outputId = t1.inputId = null;
+ if (id != null) {
+ t1.inputId = id;
+ t1.outputId = id + 1;
+ t2 = id;
+ } else {
+ t2 = _this._nextId;
+ t3 = t1.inputId = t2 + 1;
+ t1.outputId = t2;
+ _this._nextId = t2 + 2;
+ t2 = t3;
+ }
+ if (_this._multi_channel$_inner == null) {
+ t1 = _this.$ti;
+ t3 = new P._Future($.Zone__current, type$._Future_dynamic);
+ t3._asyncComplete$1(null);
+ return new D.VirtualChannel(_this, t2, new P._EmptyStream(t1._eval$1("_EmptyStream<1>")), new S.NullStreamSink(t3, t1._eval$1("NullStreamSink<1>")), t1._eval$1("VirtualChannel<1>"));
+ }
+ if (_this._pendingIds.remove$1(0, t2))
+ controller = _this._controllers.$index(0, t2);
+ else {
+ t3 = _this._controllers;
+ if (t3.containsKey$1(t2) || _this._closedIds.contains$1(0, t2))
+ throw H.wrapException(P.ArgumentError$("A virtual channel with id " + H.S(id) + " already exists."));
+ else {
+ controller = B.StreamChannelController$(true, true, _this.$ti._precomputed1);
+ t3.$indexSet(0, t2, controller);
+ }
+ }
+ t2 = controller._local._streamController;
+ t2.toString;
+ new P._ControllerStream(t2, H._instanceType(t2)._eval$1("_ControllerStream<1>")).listen$2$onDone(new D._MultiChannel_virtualChannel_closure(t1, _this), new D._MultiChannel_virtualChannel_closure0(t1, _this));
+ t1 = t1.outputId;
+ t2 = controller._foreign;
+ t3 = t2._streamController;
+ t3.toString;
+ return new D.VirtualChannel(_this, t1, new P._ControllerStream(t3, H._instanceType(t3)._eval$1("_ControllerStream<1>")), t2._sink, _this.$ti._eval$1("VirtualChannel<1>"));
+ },
+ virtualChannel$0: function() {
+ return this.virtualChannel$1(null);
+ },
+ _closeChannel$2: function(inputId, outputId) {
+ var t1, t2, _this = this;
+ _this._closedIds.add$1(0, inputId);
+ t1 = _this._controllers;
+ t1.remove$1(0, inputId)._local._sink.close$0(0);
+ t2 = _this._multi_channel$_inner;
+ if (t2 == null)
+ return;
+ t2._sink.add$1(0, H.setRuntimeTypeInfo([outputId], type$.JSArray_int));
+ if (t1.get$isEmpty(t1))
+ _this._closeInnerChannel$0();
+ },
+ _closeInnerChannel$0: function() {
+ var t1, t2, t3, _i, _this = this;
+ _this._multi_channel$_inner._sink.close$0(0);
+ _this._innerStreamSubscription.cancel$0();
+ _this._multi_channel$_inner = null;
+ for (t1 = _this._controllers, t2 = P.List_List$from(t1.get$values(), true, type$.dynamic), t3 = t2.length, _i = 0; _i < t2.length; t2.length === t3 || (0, H.throwConcurrentModificationError)(t2), ++_i)
+ t2[_i].get$local()._sink.close$0(0);
+ t1.clear$0(0);
+ },
+ $isMultiChannel: 1
+ };
+ D._MultiChannel_closure.prototype = {
+ call$1: function(message) {
+ this.T._check(message);
+ return this.$this._multi_channel$_inner._sink.add$1(0, [0, message]);
+ },
+ $signature: function() {
+ return this.T._eval$1("~(0)");
+ }
+ };
+ D._MultiChannel_closure0.prototype = {
+ call$0: function() {
+ return this.$this._closeChannel$2(0, 0);
+ },
+ $signature: 1
+ };
+ D._MultiChannel_closure1.prototype = {
+ call$1: function(message) {
+ var t3, controller,
+ t1 = J.getInterceptor$asx(message),
+ id = t1.$index(message, 0),
+ t2 = this.$this;
+ if (t2._closedIds.contains$1(0, id))
+ return;
+ H._checkIntNullable(id);
+ t3 = this.T;
+ controller = t2._controllers.putIfAbsent$2(id, new D._MultiChannel__closure(t2, id, t3));
+ t2 = t1.get$length(message);
+ if (typeof t2 !== "number")
+ return t2.$gt();
+ if (t2 > 1)
+ controller._local._sink.add$1(0, t3._check(t1.$index(message, 1)));
+ else
+ controller._local._sink.close$0(0);
+ },
+ $signature: 3
+ };
+ D._MultiChannel__closure.prototype = {
+ call$0: function() {
+ this.$this._pendingIds.add$1(0, H._checkIntNullable(this.id));
+ return B.StreamChannelController$(true, true, this.T);
+ },
+ $signature: function() {
+ return this.T._eval$1("StreamChannelController<0>()");
+ }
+ };
+ D._MultiChannel_virtualChannel_closure.prototype = {
+ call$1: function(message) {
+ var t1 = this.$this;
+ t1.$ti._precomputed1._check(message);
+ return t1._multi_channel$_inner._sink.add$1(0, [this._box_0.outputId, message]);
+ },
+ $signature: function() {
+ return this.$this.$ti._eval$1("~(1)");
+ }
+ };
+ D._MultiChannel_virtualChannel_closure0.prototype = {
+ call$0: function() {
+ var t1 = this._box_0;
+ return this.$this._closeChannel$2(t1.inputId, t1.outputId);
+ },
+ $signature: 1
+ };
+ D.VirtualChannel.prototype = {$isMultiChannel: 1,
+ get$stream: function(receiver) {
+ return this.stream;
+ }
+ };
+ N.StreamChannelCompleter.prototype = {
+ set$_stream_channel_completer$_channel: function(_channel) {
+ this._stream_channel_completer$_channel = this.$ti._eval$1("StreamChannel<1>")._check(_channel);
+ }
+ };
+ B.StreamChannelController.prototype = {
+ get$local: function() {
+ return this._local;
+ },
+ set$_local: function(_local) {
+ this._local = this.$ti._eval$1("StreamChannel<1>")._check(_local);
+ },
+ set$_foreign: function(_foreign) {
+ this._foreign = this.$ti._eval$1("StreamChannel<1>")._check(_foreign);
+ }
+ };
+ R.StreamChannel.prototype = {};
+ R._StreamChannel.prototype = {
+ get$stream: function(receiver) {
+ return this.stream;
+ }
+ };
+ R.StreamChannelMixin.prototype = {$isStreamChannel: 1};
+ E.StringScannerException.prototype = {};
+ S.SpanScanner.prototype = {
+ spanFrom$1: function(startState) {
+ var endPosition = this._string_scanner$_position;
+ return this._sourceFile.span$2(startState.position, endPosition);
+ },
+ matches$1: function(_, pattern) {
+ var _this = this;
+ if (!_this.super$StringScanner$matches(0, pattern)) {
+ _this._lastSpan = null;
+ return false;
+ }
+ _this._lastSpan = _this._sourceFile.span$2(_this._string_scanner$_position, _this.get$lastMatch().get$end());
+ return true;
+ },
+ error$3$length$position: function(_, message, $length, position) {
+ var t1 = this.string;
+ B.validateErrorArgs(t1, null, position, $length);
+ throw H.wrapException(E.StringScannerException$(message, this._sourceFile.span$2(position, position + $length), t1));
+ }
+ };
+ S._SpanScannerState.prototype = {$isLineScannerState: 1};
+ X.StringScanner.prototype = {
+ get$lastMatch: function() {
+ var _this = this;
+ if (_this._string_scanner$_position !== _this._lastMatchPosition)
+ _this._lastMatch = null;
+ return _this._lastMatch;
+ },
+ peekChar$0: function() {
+ var index = this._string_scanner$_position;
+ if (index < 0 || index >= this.string.length)
+ return null;
+ return J.codeUnitAt$1$s(this.string, index);
+ },
+ scan$1: function(pattern) {
+ var _this = this,
+ success = _this.matches$1(0, pattern);
+ if (success)
+ _this._lastMatchPosition = _this._string_scanner$_position = _this._lastMatch.get$end();
+ return success;
+ },
+ expect$2$name: function(pattern, $name) {
+ var t1;
+ if (this.scan$1(pattern))
+ return;
+ if ($name == null)
+ if (type$.RegExp._is(pattern))
+ $name = "/" + pattern.pattern + "/";
+ else {
+ t1 = J.toString$0$(pattern);
+ t1 = H.stringReplaceAllUnchecked(t1, "\\", "\\\\");
+ $name = '"' + H.stringReplaceAllUnchecked(t1, '"', '\\"') + '"';
+ }
+ this.error$3$length$position(0, "expected " + $name + ".", 0, this._string_scanner$_position);
+ },
+ expect$1: function(pattern) {
+ return this.expect$2$name(pattern, null);
+ },
+ matches$1: function(_, pattern) {
+ var _this = this,
+ t1 = J.matchAsPrefix$2$s(pattern, _this.string, _this._string_scanner$_position);
+ _this._lastMatch = t1;
+ _this._lastMatchPosition = _this._string_scanner$_position;
+ return t1 != null;
+ }
+ };
+ A.AsciiGlyphSet.prototype = {
+ glyphOrAscii$2: function(glyph, alternative) {
+ return alternative;
+ },
+ get$horizontalLine: function() {
+ return "-";
+ },
+ get$verticalLine: function() {
+ return "|";
+ },
+ get$topLeftCorner: function() {
+ return ",";
+ },
+ get$bottomLeftCorner: function() {
+ return "'";
+ },
+ get$upEnd: function() {
+ return "'";
+ },
+ get$downEnd: function() {
+ return ",";
+ }
+ };
+ K.UnicodeGlyphSet.prototype = {
+ glyphOrAscii$2: function(glyph, alternative) {
+ return glyph;
+ },
+ get$horizontalLine: function() {
+ return "\u2500";
+ },
+ get$verticalLine: function() {
+ return "\u2502";
+ },
+ get$topLeftCorner: function() {
+ return "\u250c";
+ },
+ get$bottomLeftCorner: function() {
+ return "\u2514";
+ },
+ get$upEnd: function() {
+ return "\u2575";
+ },
+ get$downEnd: function() {
+ return "\u2577";
+ }
+ };
+ L.internalBootstrapBrowserTest_closure.prototype = {
+ call$0: function() {
+ var $async$goto = 0,
+ $async$completer = P._makeAsyncAwaitCompleter(type$.Null),
+ $async$returnValue, t1, serialized, formatter, manager, $async$temp1;
+ var $async$call$0 = P._wrapJsFunctionForAsync(function($async$errorCode, $async$result) {
+ if ($async$errorCode === 1)
+ return P._asyncRethrow($async$result, $async$completer);
+ while (true)
+ switch ($async$goto) {
+ case 0:
+ // Function start
+ manager = type$.SuiteChannelManager._as($.Zone__current.$index(0, $.$get$_currentKey()));
+ if (manager == null)
+ H.throwExpression(P.StateError$("suiteChannel() may only be called within a test worker."));
+ t1 = manager.connectOut$1("test.browser.mapper");
+ t1 = t1.get$stream(t1);
+ $async$temp1 = type$.Map_dynamic_dynamic;
+ $async$goto = 3;
+ return P._asyncAwait(t1.get$first(t1), $async$call$0);
+ case 3:
+ // returning from await.
+ serialized = $async$temp1._as($async$result);
+ if (serialized == null) {
+ // goto return
+ $async$goto = 1;
+ break;
+ }
+ t1 = E.JSStackTraceMapper_deserialize(serialized);
+ formatter = type$.StackTraceFormatter._as($.Zone__current.$index(0, $.$get$_currentKey0()));
+ if (formatter == null)
+ H.throwExpression(P.StateError$("setStackTraceMapper() may only be called within a test worker."));
+ formatter.configure$1$mapper(t1);
+ case 1:
+ // return
+ return P._asyncReturn($async$returnValue, $async$completer);
+ }
+ });
+ return P._asyncStartSync($async$call$0, $async$completer);
+ },
+ $signature: 2
+ };
+ N.postMessageChannel_closure.prototype = {
+ call$1: function(message) {
+ var t1, t2;
+ type$.MessageEvent._check(message);
+ t1 = message.origin;
+ t2 = window.location;
+ return t1 === (t2 && C.Location_methods).get$origin(t2) && J.$eq$(new P._AcceptStructuredCloneDart2Js([], []).convertNativeToDart_AcceptStructuredClone$2$mustCopy(message.data, true), "port");
+ },
+ $signature: 69
+ };
+ N.postMessageChannel_closure0.prototype = {
+ call$1: function(message) {
+ var t2, t3, portSubscription,
+ t1 = type$.MessageEvent,
+ port = J.get$first$ax(t1._check(message).ports);
+ port.toString;
+ t2 = this.controller;
+ t3 = type$.void_Function_MessageEvent._check(new N.postMessageChannel__closure(t2));
+ type$.void_Function._check(null);
+ portSubscription = W._EventStreamSubscription$(port, "message", t3, false, t1);
+ t2 = t2._local._streamController;
+ t2.toString;
+ new P._ControllerStream(t2, H._instanceType(t2)._eval$1("_ControllerStream<1>")).listen$2$onDone(new N.postMessageChannel__closure0(port), new N.postMessageChannel__closure1(port, portSubscription));
+ },
+ $signature: 29
+ };
+ N.postMessageChannel__closure.prototype = {
+ call$1: function(message) {
+ type$.MessageEvent._check(message);
+ this.controller._local._sink.add$1(0, new P._AcceptStructuredCloneDart2Js([], []).convertNativeToDart_AcceptStructuredClone$2$mustCopy(message.data, true));
+ },
+ $signature: 29
+ };
+ N.postMessageChannel__closure0.prototype = {
+ call$1: function(data) {
+ C.MessagePort_methods.postMessage$1(this.port, P.LinkedHashMap_LinkedHashMap$_literal(["data", data], type$.String, type$.dynamic));
+ },
+ $signature: 3
+ };
+ N.postMessageChannel__closure1.prototype = {
+ call$0: function() {
+ var t1 = type$.String;
+ C.MessagePort_methods.postMessage$1(this.port, P.LinkedHashMap_LinkedHashMap$_literal(["event", "done"], t1, t1));
+ this.portSubscription.cancel$0();
+ },
+ $signature: 0
+ };
+ K.ClosedException.prototype = {
+ toString$0: function(_) {
+ return "This test has been closed.";
+ },
+ $isException: 1
+ };
+ X.Declarer.prototype = {
+ test$9$onPlatform$retry$skip$solo$tags$testOn$timeout: function($name, body, onPlatform, retry, skip, solo, tags, testOn, timeout) {
+ var newMetadata, metadata, t1, t2, _this = this;
+ type$.dynamic_Function._check(body);
+ type$.Map_String_dynamic._check(onPlatform);
+ _this._checkNotBuilt$1("test");
+ newMetadata = O.Metadata$parse(onPlatform, H.boolConversionCheck(_this._noRetry) ? 0 : retry, skip, tags, testOn, timeout);
+ newMetadata.validatePlatformSelectors$1(_this._platformVariables);
+ metadata = _this._metadata.merge$1(newMetadata);
+ t1 = _this._declarer$_name;
+ t1 = t1 == null ? $name : t1 + " " + $name;
+ t2 = H.boolConversionCheck(_this._collectTraces) ? Y.Trace_Trace$current(2) : null;
+ C.JSArray_methods.add$1(_this._entries, new U.LocalTest(t1, metadata, t2, false, new X.Declarer_test_closure(_this, body), false));
+ },
+ group$9$onPlatform$retry$skip$solo$tags$testOn$timeout: function($name, body, onPlatform, retry, skip, solo, tags, testOn, timeout) {
+ var t2, newMetadata, t3, metadata, t4, trace, t5, t6, t7, t8, t9, t10, t11, t12, declarer, _this = this, _null = null,
+ t1 = type$.void_Function;
+ t1._check(body);
+ type$.Map_String_dynamic._check(onPlatform);
+ _this._checkNotBuilt$1("group");
+ t2 = H.boolConversionCheck(_this._noRetry);
+ newMetadata = O.Metadata$parse(onPlatform, t2 ? 0 : retry, skip, tags, testOn, timeout);
+ t3 = _this._platformVariables;
+ newMetadata.validatePlatformSelectors$1(t3);
+ metadata = _this._metadata.merge$1(newMetadata);
+ t4 = H.boolConversionCheck(_this._collectTraces);
+ trace = t4 ? Y.Trace_Trace$current(2) : _null;
+ t5 = _this._declarer$_name;
+ t5 = t5 == null ? $name : t5 + " " + $name;
+ t6 = type$.JSArray_of_dynamic_Function;
+ t7 = H.setRuntimeTypeInfo([], t6);
+ t8 = H.setRuntimeTypeInfo([], t6);
+ t9 = H.setRuntimeTypeInfo([], t6);
+ t10 = P.Duration$(0, 12);
+ t6 = H.setRuntimeTypeInfo([], t6);
+ t11 = type$.JSArray_GroupEntry;
+ t12 = H.setRuntimeTypeInfo([], t11);
+ t11 = H.setRuntimeTypeInfo([], t11);
+ declarer = new X.Declarer(_this, t5, metadata, t3, trace, t4, t2, t7, t8, t9, new R.Timeout(t10, _null), t6, t12, t11);
+ t12 = type$.dynamic;
+ P.runZoned(t1._check(new X.Declarer_group_closure(body)), _null, _null, P.LinkedHashMap_LinkedHashMap$_literal([C.Symbol_Drw, declarer], t12, t12), type$.void);
+ t12 = _this._entries;
+ C.JSArray_methods.add$1(t12, declarer.build$0());
+ t1 = t11.length;
+ if (t1 !== 0)
+ C.JSArray_methods.add$1(_this._soloEntries, C.JSArray_methods.get$last(t12));
+ },
+ build$0: function() {
+ var t1, t2, _this = this;
+ _this._checkNotBuilt$1("build");
+ _this._built = true;
+ t1 = _this._entries;
+ t2 = H._arrayInstanceType(t1);
+ return O.Group$(_this._declarer$_name, new H.MappedListIterable(t1, t2._eval$1("GroupEntry(1)")._check(new X.Declarer_build_closure(_this)), t2._eval$1("MappedListIterable<1,GroupEntry>")).toList$0(0), _this._metadata, _this.get$_setUpAll(), _this.get$_tearDownAll(), _this._declarer$_trace);
+ },
+ _checkNotBuilt$1: function($name) {
+ if (!this._built)
+ return;
+ throw H.wrapException(P.StateError$("Can't call " + $name + "() once tests have begun running."));
+ },
+ _runSetUps$0: function() {
+ var $async$goto = 0,
+ $async$completer = P._makeAsyncAwaitCompleter(type$.dynamic),
+ $async$self = this, t1;
+ var $async$_runSetUps$0 = P._wrapJsFunctionForAsync(function($async$errorCode, $async$result) {
+ if ($async$errorCode === 1)
+ return P._asyncRethrow($async$result, $async$completer);
+ while (true)
+ switch ($async$goto) {
+ case 0:
+ // Function start
+ t1 = $async$self._declarer$_parent;
+ $async$goto = t1 != null ? 2 : 3;
+ break;
+ case 2:
+ // then
+ $async$goto = 4;
+ return P._asyncAwait(t1._runSetUps$0(), $async$_runSetUps$0);
+ case 4:
+ // returning from await.
+ case 3:
+ // join
+ $async$goto = 5;
+ return P._asyncAwait(P.Future_forEach($async$self._setUps, new X.Declarer__runSetUps_closure(), type$.dynamic_Function), $async$_runSetUps$0);
+ case 5:
+ // returning from await.
+ // implicit return
+ return P._asyncReturn(null, $async$completer);
+ }
+ });
+ return P._asyncStartSync($async$_runSetUps$0, $async$completer);
+ },
+ get$_setUpAll: function() {
+ return null;
+ },
+ get$_tearDownAll: function() {
+ var _this = this,
+ t1 = _this._tearDownAlls.length;
+ if (t1 === 0)
+ return null;
+ t1 = _this._declarer$_name;
+ t1 = t1 == null ? "(tearDownAll)" : t1 + " (tearDownAll)";
+ return new U.LocalTest(t1, _this._metadata.change$1$timeout(_this._timeout), null, true, new X.Declarer__tearDownAll_closure(_this), false);
+ }
+ };
+ X.Declarer_test_closure.prototype = {
+ call$0: function() {
+ var $async$goto = 0,
+ $async$completer = P._makeAsyncAwaitCompleter(type$.Null),
+ $async$returnValue, $async$self = this, declarer, declarer0, t1, t2, t3, t4, t5, _i, tearDown, t6, parents;
+ var $async$call$0 = P._wrapJsFunctionForAsync(function($async$errorCode, $async$result) {
+ if ($async$errorCode === 1)
+ return P._asyncRethrow($async$result, $async$completer);
+ while (true)
+ $async$outer:
+ switch ($async$goto) {
+ case 0:
+ // Function start
+ parents = H.setRuntimeTypeInfo([], type$.JSArray_Declarer);
+ for (declarer = $async$self.$this, declarer0 = declarer; declarer0 != null; declarer0 = declarer0._declarer$_parent)
+ C.JSArray_methods.add$1(parents, declarer0);
+ for (t1 = type$.ReversedListIterable_Declarer, t2 = new H.ReversedListIterable(parents, t1), t1 = new H.ListIterator(t2, t2.get$length(t2), t1._eval$1("ListIterator<ListIterable.E>")), t2 = type$.Invoker, t3 = type$.dynamic_Function, t4 = type$.Declarer; t1.moveNext$0();)
+ for (t5 = t1.__internal$_current._tearDowns, _i = 0; false; ++_i) {
+ if (_i >= 0) {
+ $async$returnValue = H.ioore(t5, _i);
+ // goto return
+ $async$goto = 1;
+ break $async$outer;
+ }
+ tearDown = t5[_i];
+ t6 = t2._as($.Zone__current.$index(0, C.Symbol_cQL));
+ t6.toString;
+ t3._check(tearDown);
+ if (H.boolConversionCheck(H._asBoolNullable($.Zone__current.$index(0, t6._closableKey))) && t6._onCloseCompleter.future._state !== 0)
+ H.throwExpression(K.ClosedException$());
+ if (t6._invoker$_controller._liveTest._live_test_controller$_controller._test.isScaffoldAll)
+ C.JSArray_methods.add$1(t4._as($.Zone__current.$index(0, C.Symbol_Drw))._tearDownAlls, tearDown);
+ else
+ C.JSArray_methods.add$1(t6._invoker$_tearDowns, tearDown);
+ }
+ t1 = type$.dynamic;
+ $async$goto = 3;
+ return P._asyncAwait(P.runZoned(new X.Declarer_test__closure(declarer, $async$self.body), null, null, P.LinkedHashMap_LinkedHashMap$_literal([C.Symbol_Drw, declarer], t1, t1), type$.Future_void), $async$call$0);
+ case 3:
+ // returning from await.
+ case 1:
+ // return
+ return P._asyncReturn($async$returnValue, $async$completer);
+ }
+ });
+ return P._asyncStartSync($async$call$0, $async$completer);
+ },
+ $signature: 2
+ };
+ X.Declarer_test__closure.prototype = {
+ call$0: function() {
+ return type$.Invoker._as($.Zone__current.$index(0, C.Symbol_cQL)).waitForOutstandingCallbacks$1(new X.Declarer_test___closure(this.$this, this.body));
+ },
+ $signature: 13
+ };
+ X.Declarer_test___closure.prototype = {
+ call$0: function() {
+ var $async$goto = 0,
+ $async$completer = P._makeAsyncAwaitCompleter(type$.Null),
+ $async$self = this;
+ var $async$call$0 = P._wrapJsFunctionForAsync(function($async$errorCode, $async$result) {
+ if ($async$errorCode === 1)
+ return P._asyncRethrow($async$result, $async$completer);
+ while (true)
+ switch ($async$goto) {
+ case 0:
+ // Function start
+ $async$goto = 2;
+ return P._asyncAwait($async$self.$this._runSetUps$0(), $async$call$0);
+ case 2:
+ // returning from await.
+ $async$goto = 3;
+ return P._asyncAwait($async$self.body.call$0(), $async$call$0);
+ case 3:
+ // returning from await.
+ // implicit return
+ return P._asyncReturn(null, $async$completer);
+ }
+ });
+ return P._asyncStartSync($async$call$0, $async$completer);
+ },
+ $signature: 2
+ };
+ X.Declarer_group_closure.prototype = {
+ call$0: function() {
+ if (!type$.Future_dynamic._is(this.body.call$0()))
+ return;
+ throw H.wrapException(P.ArgumentError$("Groups may not be async."));
+ },
+ $signature: 0
+ };
+ X.Declarer_build_closure.prototype = {
+ call$1: function(entry) {
+ var t1;
+ type$.GroupEntry._check(entry);
+ t1 = this.$this._soloEntries;
+ return t1.length !== 0 && !C.JSArray_methods.contains$1(t1, entry) ? new U.LocalTest(entry.get$name(entry), entry.get$metadata().change$2$skip$skipReason(true, 'does not have "solo"'), null, false, null, true) : entry;
+ },
+ $signature: 22
+ };
+ X.Declarer__runSetUps_closure.prototype = {
+ call$1: function(setUp) {
+ return setUp.call$0();
+ },
+ $signature: 18
+ };
+ X.Declarer__tearDownAll_closure.prototype = {
+ call$0: function() {
+ var t1 = this.$this,
+ t2 = type$.dynamic;
+ return P.runZoned(new X.Declarer__tearDownAll__closure(t1), null, null, P.LinkedHashMap_LinkedHashMap$_literal([C.Symbol_Drw, t1], t2, t2), type$.Future_Null);
+ },
+ $signature: 2
+ };
+ X.Declarer__tearDownAll__closure.prototype = {
+ call$0: function() {
+ return type$.Invoker._as($.Zone__current.$index(0, C.Symbol_cQL)).unclosable$1$1(new X.Declarer__tearDownAll___closure(this.$this), type$.Future_Null);
+ },
+ $signature: 2
+ };
+ X.Declarer__tearDownAll___closure.prototype = {
+ call$0: function() {
+ var $async$goto = 0,
+ $async$completer = P._makeAsyncAwaitCompleter(type$.Null),
+ $async$returnValue, $async$self = this, t1, t2;
+ var $async$call$0 = P._wrapJsFunctionForAsync(function($async$errorCode, $async$result) {
+ if ($async$errorCode === 1)
+ return P._asyncRethrow($async$result, $async$completer);
+ while (true)
+ switch ($async$goto) {
+ case 0:
+ // Function start
+ t1 = $async$self.$this._tearDownAlls;
+ case 3:
+ // for condition
+ if (!(t2 = t1.length, t2 !== 0)) {
+ // goto after for
+ $async$goto = 4;
+ break;
+ }
+ if (0 >= t2) {
+ $async$returnValue = H.ioore(t1, -1);
+ // goto return
+ $async$goto = 1;
+ break;
+ }
+ $async$goto = 5;
+ return P._asyncAwait(V.errorsDontStopTest(t1.pop()), $async$call$0);
+ case 5:
+ // returning from await.
+ // goto for condition
+ $async$goto = 3;
+ break;
+ case 4:
+ // after for
+ case 1:
+ // return
+ return P._asyncReturn($async$returnValue, $async$completer);
+ }
+ });
+ return P._asyncStartSync($async$call$0, $async$completer);
+ },
+ $signature: 2
+ };
+ O.Group.prototype = {
+ forPlatform$1: function(platform) {
+ var newMetadata, filtered, _this = this,
+ t1 = _this.metadata;
+ if (!H.boolConversionCheck(t1.testOn.evaluate$1(platform)))
+ return null;
+ newMetadata = t1.forPlatform$1(platform);
+ filtered = _this._group$_map$1(new O.Group_forPlatform_closure(platform));
+ if (filtered.length === 0 && _this.entries.length !== 0)
+ return null;
+ return O.Group$(_this.name, filtered, newMetadata, _this.setUpAll, _this.tearDownAll, _this.trace);
+ },
+ _group$_map$1: function(callback) {
+ var t1 = this.entries,
+ t2 = H._arrayInstanceType(t1),
+ t3 = t2._eval$1("MappedListIterable<1,GroupEntry>");
+ t3 = new H.MappedListIterable(t1, t2._eval$1("GroupEntry(1)")._check(new O.Group__map_closure(type$.GroupEntry_Function_GroupEntry._check(callback))), t3).super$Iterable$where(0, t3._eval$1("bool(ListIterable.E)")._check(new O.Group__map_closure0()));
+ return P.List_List$from(t3, true, t3.$ti._eval$1("Iterable.E"));
+ },
+ $isGroupEntry: 1,
+ get$name: function(receiver) {
+ return this.name;
+ },
+ get$metadata: function() {
+ return this.metadata;
+ }
+ };
+ O.Group_forPlatform_closure.prototype = {
+ call$1: function(entry) {
+ return entry.forPlatform$1(this.platform);
+ },
+ $signature: 22
+ };
+ O.Group__map_closure.prototype = {
+ call$1: function(entry) {
+ return this.callback.call$1(type$.GroupEntry._check(entry));
+ },
+ $signature: 22
+ };
+ O.Group__map_closure0.prototype = {
+ call$1: function(entry) {
+ return type$.GroupEntry._check(entry) != null;
+ },
+ $signature: 72
+ };
+ V.GroupEntry.prototype = {};
+ U.LocalTest.prototype = {
+ load$2$groups: function(suite, groups) {
+ var t1, invoker;
+ type$.Iterable_Group._check(groups);
+ t1 = new P._AsyncCompleter(new P._Future($.Zone__current, type$._Future_void), type$._AsyncCompleter_void);
+ invoker = new U.Invoker(this._guarded, new P.Object(), t1, H.setRuntimeTypeInfo([], type$.JSArray_Zone), new P.Object(), H.setRuntimeTypeInfo([], type$.JSArray_of_dynamic_Function), H.setRuntimeTypeInfo([], type$.JSArray_String));
+ t1 = V.LiveTestController$(suite, this, invoker.get$_invoker$_onRun(), t1.get$complete(), groups);
+ invoker._invoker$_controller = t1;
+ return t1._liveTest;
+ },
+ forPlatform$1: function(platform) {
+ var _this = this,
+ t1 = _this.metadata;
+ if (!H.boolConversionCheck(t1.testOn.evaluate$1(platform)))
+ return null;
+ return new U.LocalTest(_this.name, t1.forPlatform$1(platform), _this.trace, _this.isScaffoldAll, _this._body, _this._guarded);
+ },
+ get$name: function(receiver) {
+ return this.name;
+ },
+ get$metadata: function() {
+ return this.metadata;
+ }
+ };
+ U.Invoker.prototype = {
+ get$_outstandingCallbacks: function() {
+ var counter = type$._AsyncCounter._as($.Zone__current.$index(0, this._counterKey));
+ if (counter != null)
+ return counter;
+ throw H.wrapException(P.StateError$("Can't add or remove outstanding callbacks outside of a test body."));
+ },
+ addOutstandingCallback$0: function() {
+ if (H.boolConversionCheck(H._asBoolNullable($.Zone__current.$index(0, this._closableKey))) && this._onCloseCompleter.future._state !== 0)
+ throw H.wrapException(K.ClosedException$());
+ ++this.get$_outstandingCallbacks()._count;
+ },
+ waitForOutstandingCallbacks$1: function(fn) {
+ var t2, counter, t3, _this = this, t1 = {};
+ type$.FutureOr_void_Function._check(fn);
+ _this.heartbeat$0();
+ t1.zone = null;
+ t2 = new P._Future($.Zone__current, type$._Future_void);
+ counter = new U._AsyncCounter(new P._AsyncCompleter(t2, type$._AsyncCompleter_void));
+ t3 = type$.dynamic;
+ P.runZoned(new U.Invoker_waitForOutstandingCallbacks_closure(t1, _this, fn, counter), null, null, P.LinkedHashMap_LinkedHashMap$_literal([_this._counterKey, counter], t3, t3), type$.Future_Null);
+ return t2.whenComplete$1(new U.Invoker_waitForOutstandingCallbacks_closure0(t1, _this));
+ },
+ unclosable$1$1: function(fn, $T) {
+ var t1;
+ $T._eval$1("0()")._check(fn);
+ this.heartbeat$0();
+ t1 = type$.dynamic;
+ return P.runZoned(fn, null, null, P.LinkedHashMap_LinkedHashMap$_literal([this._closableKey, false], t1, t1), $T);
+ },
+ heartbeat$0: function() {
+ var t1, timeout, _this = this;
+ if (_this._invoker$_controller._liveTest._live_test_controller$_controller._live_test_controller$_state.status === C.Status_complete)
+ return;
+ t1 = _this._timeoutTimer;
+ if (t1 != null)
+ t1.cancel$0();
+ timeout = _this._invoker$_controller._liveTest._live_test_controller$_controller._test.metadata.timeout.apply$1(C.Duration_30000000);
+ if (timeout == null)
+ return;
+ _this._timeoutTimer = _this._invokerZone.createTimer$2(timeout, new U.Invoker_heartbeat_closure(_this, new U.Invoker_heartbeat_message(timeout), timeout));
+ },
+ _handleError$3: function(zone, error, stackTrace) {
+ var t2, t3, t4, shouldBeDone, _this = this, t1 = {};
+ t1.stackTrace = stackTrace;
+ if (_this._runCount !== zone.$index(0, C.Symbol_runCount))
+ return;
+ zone.run$1$1(new U.Invoker__handleError_closure(t1), type$.Null);
+ t2 = _this._invoker$_controller;
+ t3 = t2._liveTest._live_test_controller$_controller._live_test_controller$_state;
+ if (t3.status === C.Status_complete) {
+ t4 = t3.result;
+ shouldBeDone = t4 === C.Result_success || t4 === C.Result_skipped;
+ } else
+ shouldBeDone = false;
+ if (!(error instanceof G.TestFailure))
+ t2.setState$1(C.State_Status_complete_Result_error);
+ else if (t3.result !== C.Result_error)
+ t2.setState$1(C.State_Status_complete_Result_failure);
+ _this._invoker$_controller.addError$2(error, t1.stackTrace);
+ zone.run$1$1(new U.Invoker__handleError_closure0(_this), type$.void);
+ t2 = _this._invoker$_controller._liveTest._live_test_controller$_controller;
+ if (t2._test.metadata._chainStackTraces === false)
+ C.JSArray_methods.add$1(_this._printsOnFailure, "Consider enabling the flag chain-stack-traces to receive more detailed exceptions.\nFor example, 'pub run test --chain-stack-traces'.");
+ t2 = _this._printsOnFailure;
+ if (t2.length !== 0) {
+ P.print(C.JSArray_methods.join$1(t2, "\n\n"));
+ C.JSArray_methods.set$length(t2, 0);
+ }
+ if (!shouldBeDone)
+ return;
+ _this._invoker$_controller._liveTest._live_test_controller$_controller._live_test_controller$_suite.toString;
+ _this._handleError$3(zone, "This test failed after it had already completed. Make sure to use [expectAsync]\nor the [completes] matcher when testing async code.", t1.stackTrace);
+ },
+ _handleError$2: function(zone, error) {
+ return this._handleError$3(zone, error, null);
+ },
+ _invoker$_onRun$0: function() {
+ var t1, t2, _this = this;
+ _this._invoker$_controller.setState$1(C.State_Status_running_Result_success);
+ t1 = $.Zone__current;
+ ++_this._runCount;
+ t2 = _this._invoker$_controller._liveTest._live_test_controller$_controller;
+ U.Chain_capture(new U.Invoker__onRun_closure(_this, new U._AsyncCounter(new P._AsyncCompleter(new P._Future(t1, type$._Future_void), type$._AsyncCompleter_void))), false, t2._test.metadata._chainStackTraces !== false, type$.Null);
+ },
+ _runTearDowns$0: function() {
+ var $async$goto = 0,
+ $async$completer = P._makeAsyncAwaitCompleter(type$.void),
+ $async$returnValue, $async$self = this, t1, t2;
+ var $async$_runTearDowns$0 = P._wrapJsFunctionForAsync(function($async$errorCode, $async$result) {
+ if ($async$errorCode === 1)
+ return P._asyncRethrow($async$result, $async$completer);
+ while (true)
+ switch ($async$goto) {
+ case 0:
+ // Function start
+ t1 = $async$self._invoker$_tearDowns;
+ case 3:
+ // for condition
+ if (!(t2 = t1.length, t2 !== 0)) {
+ // goto after for
+ $async$goto = 4;
+ break;
+ }
+ if (0 >= t2) {
+ $async$returnValue = H.ioore(t1, -1);
+ // goto return
+ $async$goto = 1;
+ break;
+ }
+ $async$goto = 5;
+ return P._asyncAwait(V.errorsDontStopTest(t1.pop()), $async$_runTearDowns$0);
+ case 5:
+ // returning from await.
+ // goto for condition
+ $async$goto = 3;
+ break;
+ case 4:
+ // after for
+ case 1:
+ // return
+ return P._asyncReturn($async$returnValue, $async$completer);
+ }
+ });
+ return P._asyncStartSync($async$_runTearDowns$0, $async$completer);
+ }
+ };
+ U.Invoker_guard_closure.prototype = {
+ call$5: function($self, _, zone, error, stackTrace) {
+ var invoker;
+ type$.StackTrace._check(stackTrace);
+ invoker = zone.$index(0, C.Symbol_cQL);
+ if (invoker != null)
+ $self.get$parent($self).run$1$1(new U.Invoker_guard__closure(invoker, zone, error, stackTrace), type$.dynamic);
+ else
+ $self.get$parent($self).handleUncaughtError$2(error, stackTrace);
+ },
+ $signature: 28
+ };
+ U.Invoker_guard__closure.prototype = {
+ call$0: function() {
+ var _this = this;
+ return _this.invoker._handleError$3(_this.zone, _this.error, _this.stackTrace);
+ },
+ $signature: 34
+ };
+ U.Invoker_waitForOutstandingCallbacks_closure.prototype = {
+ call$0: function() {
+ var $async$goto = 0,
+ $async$completer = P._makeAsyncAwaitCompleter(type$.Null),
+ $async$self = this, zone;
+ var $async$call$0 = P._wrapJsFunctionForAsync(function($async$errorCode, $async$result) {
+ if ($async$errorCode === 1)
+ return P._asyncRethrow($async$result, $async$completer);
+ while (true)
+ switch ($async$goto) {
+ case 0:
+ // Function start
+ zone = $.Zone__current;
+ $async$self._box_0.zone = zone;
+ C.JSArray_methods.add$1($async$self.$this._outstandingCallbackZones, zone);
+ $async$goto = 2;
+ return P._asyncAwait($async$self.fn.call$0(), $async$call$0);
+ case 2:
+ // returning from await.
+ $async$self.counter.decrement$0();
+ // implicit return
+ return P._asyncReturn(null, $async$completer);
+ }
+ });
+ return P._asyncStartSync($async$call$0, $async$completer);
+ },
+ $signature: 2
+ };
+ U.Invoker_waitForOutstandingCallbacks_closure0.prototype = {
+ call$0: function() {
+ C.JSArray_methods.remove$1(this.$this._outstandingCallbackZones, this._box_0.zone);
+ },
+ $signature: 0
+ };
+ U.Invoker_heartbeat_message.prototype = {
+ call$0: function() {
+ var message,
+ t1 = this.timeout._duration,
+ minutes = C.JSInt_methods._tdivFast$1(t1, 60000000),
+ seconds = C.JSInt_methods.$mod(C.JSInt_methods._tdivFast$1(t1, 1000000), 60),
+ decaseconds = C.JSInt_methods._tdivFast$1(C.JSInt_methods.$mod(C.JSInt_methods._tdivFast$1(t1, 1000), 1000), 100),
+ t2 = minutes !== 0,
+ t3 = t2 ? "" + minutes + " minutes" : "";
+ if (!t2 || seconds !== 0) {
+ t2 = t2 ? t3 + ", " : t3;
+ t2 += seconds;
+ t2 = (decaseconds !== 0 ? t2 + ("." + decaseconds) : t2) + " seconds";
+ } else
+ t2 = t3;
+ message = "Test timed out after " + (t2.charCodeAt(0) == 0 ? t2 : t2) + ".";
+ return t1 === 30000000 ? message + " See https://pub.dev/packages/test#timeouts" : message;
+ },
+ $signature: 74
+ };
+ U.Invoker_heartbeat_closure.prototype = {
+ call$0: function() {
+ var t1 = this.$this;
+ C.JSArray_methods.get$last(t1._outstandingCallbackZones).run$1$1(new U.Invoker_heartbeat__closure(t1, this.message, this.timeout), type$.Null);
+ },
+ $signature: 0
+ };
+ U.Invoker_heartbeat__closure.prototype = {
+ call$0: function() {
+ var t1 = this.$this;
+ if (t1._invoker$_controller._liveTest._live_test_controller$_controller._live_test_controller$_state.status === C.Status_complete)
+ return;
+ t1._handleError$2($.Zone__current, new P.TimeoutException(this.message.call$0(), this.timeout));
+ },
+ $signature: 0
+ };
+ U.Invoker__handleError_closure.prototype = {
+ call$0: function() {
+ var t1 = this._box_0,
+ t2 = t1.stackTrace;
+ if (t2 == null)
+ t1.stackTrace = U.Chain_Chain$current();
+ else
+ t1.stackTrace = U.Chain_Chain$forTrace(t2);
+ },
+ $signature: 0
+ };
+ U.Invoker__handleError_closure0.prototype = {
+ call$0: function() {
+ var t1 = this.$this.get$_outstandingCallbacks()._invoker$_completer;
+ if (t1.future._state === 0)
+ t1.complete$0();
+ return null;
+ },
+ $signature: 1
+ };
+ U.Invoker__onRun_closure.prototype = {
+ call$0: function() {
+ var t1 = this.$this,
+ t2 = type$.void_Function._check(new U.Invoker__onRun__closure(t1, this.outstandingCallbacksForBody));
+ if (t1._guarded)
+ U.Invoker_guard(t2, type$.void);
+ else
+ t2.call$0();
+ },
+ $signature: 0
+ };
+ U.Invoker__onRun__closure.prototype = {
+ call$0: function() {
+ var _null = null,
+ t1 = this.$this,
+ t2 = type$.dynamic;
+ t2 = P.LinkedHashMap_LinkedHashMap$_literal([C.Symbol_cQL, t1, t1._counterKey, this.outstandingCallbacksForBody, t1._closableKey, true, C.Symbol_runCount, t1._runCount], t2, t2);
+ P.runZoned(new U.Invoker__onRun___closure(t1), _null, P._ZoneSpecification$(_null, _null, _null, _null, _null, new U.Invoker__onRun___closure0(t1), _null, _null, _null, _null, _null, _null, _null), t2, type$.Future_Null);
+ },
+ $signature: 0
+ };
+ U.Invoker__onRun___closure.prototype = {
+ call$0: function() {
+ var $async$goto = 0,
+ $async$completer = P._makeAsyncAwaitCompleter(type$.Null),
+ $async$returnValue, $async$self = this, t3, t4, t5, t6, t1, t2;
+ var $async$call$0 = P._wrapJsFunctionForAsync(function($async$errorCode, $async$result) {
+ if ($async$errorCode === 1)
+ return P._asyncRethrow($async$result, $async$completer);
+ while (true)
+ switch ($async$goto) {
+ case 0:
+ // Function start
+ t1 = $async$self.$this;
+ t2 = $.Zone__current;
+ t1._invokerZone = t2;
+ C.JSArray_methods.add$1(t1._outstandingCallbackZones, t2);
+ P.Future_Future(new U.Invoker__onRun____closure(t1), type$.void);
+ $async$goto = 3;
+ return P._asyncAwait(t1.get$_outstandingCallbacks()._invoker$_completer.future, $async$call$0);
+ case 3:
+ // returning from await.
+ t2 = t1._timeoutTimer;
+ if (t2 != null)
+ t2.cancel$0();
+ t2 = t1._invoker$_controller;
+ t3 = t2._liveTest._live_test_controller$_controller;
+ t4 = t3._live_test_controller$_state.result;
+ if (t4 !== C.Result_success) {
+ t5 = t1._runCount;
+ t6 = t3._test.metadata._retry;
+ t5 = t5 < (t6 == null ? 0 : t6) + 1;
+ } else
+ t5 = false;
+ if (t5) {
+ t2.message$1(0, new D.Message(C.MessageType_print, "Retry: " + H.S(t3._test.name)));
+ t1._invoker$_onRun$0();
+ // goto return
+ $async$goto = 1;
+ break;
+ }
+ t2.setState$1(new G.State(C.Status_complete, t4));
+ t1._invoker$_controller.completer.complete$0();
+ case 1:
+ // return
+ return P._asyncReturn($async$returnValue, $async$completer);
+ }
+ });
+ return P._asyncStartSync($async$call$0, $async$completer);
+ },
+ $signature: 2
+ };
+ U.Invoker__onRun____closure.prototype = {
+ call$0: function() {
+ var $async$goto = 0,
+ $async$completer = P._makeAsyncAwaitCompleter(type$.Null),
+ $async$self = this, t1;
+ var $async$call$0 = P._wrapJsFunctionForAsync(function($async$errorCode, $async$result) {
+ if ($async$errorCode === 1)
+ return P._asyncRethrow($async$result, $async$completer);
+ while (true)
+ switch ($async$goto) {
+ case 0:
+ // Function start
+ t1 = $async$self.$this;
+ $async$goto = 2;
+ return P._asyncAwait(t1._invoker$_controller._liveTest._live_test_controller$_controller._test._body.call$0(), $async$call$0);
+ case 2:
+ // returning from await.
+ $async$goto = 3;
+ return P._asyncAwait(t1.unclosable$1$1(t1.get$_runTearDowns(), type$.Future_void), $async$call$0);
+ case 3:
+ // returning from await.
+ t1.heartbeat$0();
+ t1.get$_outstandingCallbacks().decrement$0();
+ // implicit return
+ return P._asyncReturn(null, $async$completer);
+ }
+ });
+ return P._asyncStartSync($async$call$0, $async$completer);
+ },
+ $signature: 2
+ };
+ U.Invoker__onRun___closure0.prototype = {
+ call$4: function(_, __, ___, line) {
+ H._checkStringNullable(line);
+ return this.$this._invoker$_controller.message$1(0, new D.Message(C.MessageType_print, line));
+ },
+ $signature: 37
+ };
+ U._AsyncCounter.prototype = {
+ decrement$0: function() {
+ if (--this._count !== 0)
+ return;
+ var t1 = this._invoker$_completer;
+ if (t1.future._state !== 0)
+ return;
+ t1.complete$0();
+ }
+ };
+ Z.LiveTest.prototype = {};
+ V._LiveTest.prototype = {
+ run$0: function() {
+ var t1 = this._live_test_controller$_controller;
+ if (t1._runCalled)
+ H.throwExpression(P.StateError$("LiveTest.run() may not be called more than once."));
+ else if ((t1._onErrorController._state & 4) !== 0)
+ H.throwExpression(P.StateError$("LiveTest.run() may not be called for a closed test."));
+ t1._runCalled = true;
+ t1._onRun.call$0();
+ return t1._liveTest._live_test_controller$_controller.completer.future;
+ }
+ };
+ V.LiveTestController.prototype = {
+ addError$2: function(error, stackTrace) {
+ var asyncError,
+ t1 = this._onErrorController;
+ if ((t1._state & 4) !== 0)
+ return;
+ asyncError = new P.AsyncError(error, U.Chain_Chain$forTrace(stackTrace));
+ C.JSArray_methods.add$1(this._errors, asyncError);
+ t1.add$1(0, asyncError);
+ },
+ setState$1: function(newState) {
+ var _this = this;
+ if ((_this._onErrorController._state & 4) !== 0)
+ return;
+ if (_this._live_test_controller$_state.$eq(0, newState))
+ return;
+ _this._live_test_controller$_state = newState;
+ _this._onStateChangeController.add$1(0, newState);
+ },
+ message$1: function(_, message) {
+ var t1 = this._onMessageController;
+ if (t1._firstSubscription != null)
+ t1.add$1(0, message);
+ else
+ H.printString(message.text);
+ },
+ _live_test_controller$_run$0: function() {
+ var _this = this;
+ if (_this._runCalled)
+ throw H.wrapException(P.StateError$("LiveTest.run() may not be called more than once."));
+ else if ((_this._onErrorController._state & 4) !== 0)
+ throw H.wrapException(P.StateError$("LiveTest.run() may not be called for a closed test."));
+ _this._runCalled = true;
+ _this._onRun.call$0();
+ return _this._liveTest._live_test_controller$_controller.completer.future;
+ },
+ _live_test_controller$_close$0: function() {
+ var _this = this,
+ t1 = _this._onErrorController;
+ if ((t1._state & 4) !== 0)
+ return _this.completer.future;
+ _this._onStateChangeController.close$0(0);
+ t1.close$0(0);
+ if (_this._runCalled)
+ _this._onClose.call$0();
+ else
+ _this.completer.complete$0();
+ return _this.completer.future;
+ }
+ };
+ D.Message.prototype = {};
+ D.MessageType.prototype = {
+ toString$0: function(_) {
+ return this.name;
+ }
+ };
+ O.Metadata.prototype = {
+ _validateTags$0: function() {
+ var t1 = this.tags.where$1(0, new O.Metadata__validateTags_closure()),
+ t2 = t1.$ti,
+ t3 = t2._eval$1("MappedIterable<1,String>"),
+ invalidTags = P.List_List$from(new H.MappedIterable(t1, t2._eval$1("String(1)")._check(new O.Metadata__validateTags_closure0()), t3), true, t3._eval$1("Iterable.E"));
+ t1 = invalidTags.length;
+ if (t1 === 0)
+ return;
+ throw H.wrapException(P.ArgumentError$("Invalid " + B.pluralize("tag", t1) + " " + H.S(B.toSentence(invalidTags)) + ". Tags must be (optionally hyphenated) Dart identifiers."));
+ },
+ validatePlatformSelectors$1: function(validVariables) {
+ type$.Set_String._check(validVariables);
+ this.testOn.validate$1(validVariables);
+ this.onPlatform.forEach$1(0, new O.Metadata_validatePlatformSelectors_closure(validVariables));
+ },
+ merge$1: function(other) {
+ var t4, t5, t6, t7, t8, t9, t10, _this = this,
+ t1 = _this.testOn.intersection$1(other.testOn),
+ t2 = _this.timeout.merge$1(other.timeout),
+ t3 = other._skip;
+ if (t3 == null)
+ t3 = _this._skip;
+ t4 = other.skipReason;
+ if (t4 == null)
+ t4 = _this.skipReason;
+ t5 = other._verboseTrace;
+ if (t5 == null)
+ t5 = _this._verboseTrace;
+ t6 = other._chainStackTraces;
+ if (t6 == null)
+ t6 = _this._chainStackTraces;
+ t7 = other._retry;
+ if (t7 == null)
+ t7 = _this._retry;
+ t8 = _this.tags.union$1(other.tags);
+ t9 = type$.Metadata;
+ t10 = Y.mergeMaps(_this.onPlatform, other.onPlatform, new O.Metadata_merge_closure(), type$.PlatformSelector, t9);
+ return O.Metadata_Metadata(t6, Y.mergeMaps(_this.forTag, other.forTag, new O.Metadata_merge_closure0(), type$.BooleanSelector, t9), t10, t7, t3, t4, t8, t1, t2, t5);
+ },
+ change$4$onPlatform$skip$skipReason$timeout: function(onPlatform, skip, skipReason, timeout) {
+ var _this = this;
+ type$.Map_PlatformSelector_Metadata._check(onPlatform);
+ type$.Set_String._check(null);
+ type$.Map_BooleanSelector_Metadata._check(null);
+ if (timeout == null)
+ timeout = _this.timeout;
+ if (skip == null)
+ skip = _this._skip;
+ if (skipReason == null)
+ skipReason = _this.skipReason;
+ if (onPlatform == null)
+ onPlatform = _this.onPlatform;
+ return O.Metadata_Metadata(_this._chainStackTraces, _this.forTag, onPlatform, _this._retry, skip, skipReason, _this.tags, _this.testOn, timeout, _this._verboseTrace);
+ },
+ change$1$onPlatform: function(onPlatform) {
+ return this.change$4$onPlatform$skip$skipReason$timeout(onPlatform, null, null, null);
+ },
+ change$1$timeout: function(timeout) {
+ return this.change$4$onPlatform$skip$skipReason$timeout(null, null, null, timeout);
+ },
+ change$2$skip$skipReason: function(skip, skipReason) {
+ return this.change$4$onPlatform$skip$skipReason$timeout(null, skip, skipReason, null);
+ },
+ forPlatform$1: function(platform) {
+ var t1 = {},
+ t2 = this.onPlatform;
+ if (t2.get$isEmpty(t2))
+ return this;
+ t1.metadata = this;
+ t2.forEach$1(0, new O.Metadata_forPlatform_closure(t1, platform));
+ return t1.metadata.change$1$onPlatform(P.LinkedHashMap_LinkedHashMap$_empty(type$.PlatformSelector, type$.Metadata));
+ },
+ serialize$0: function() {
+ var t1, t2, t3, _this = this, serializedOnPlatform = [];
+ _this.onPlatform.forEach$1(0, new O.Metadata_serialize_closure(serializedOnPlatform));
+ t1 = _this.testOn._platform_selector$_inner;
+ t2 = J.getInterceptor$(t1);
+ t3 = t2.$eq(t1, C.C_All);
+ t1 = t3 ? null : t2.toString$0(t1);
+ t2 = type$.String;
+ return P.LinkedHashMap_LinkedHashMap$_literal(["testOn", t1, "timeout", _this._serializeTimeout$1(_this.timeout), "skip", _this._skip, "skipReason", _this.skipReason, "verboseTrace", _this._verboseTrace, "chainStackTraces", _this._chainStackTraces, "retry", _this._retry, "tags", _this.tags.toList$0(0), "onPlatform", serializedOnPlatform, "forTag", _this.forTag.map$2$1(0, new O.Metadata_serialize_closure0(), t2, type$.Map_String_dynamic)], t2, type$.dynamic);
+ },
+ _serializeTimeout$1: function(timeout) {
+ var t1;
+ if (timeout.$eq(0, C.Timeout_null_null))
+ return "none";
+ t1 = timeout.duration;
+ t1 = t1 == null ? null : t1._duration;
+ return P.LinkedHashMap_LinkedHashMap$_literal(["duration", t1, "scaleFactor", timeout.scaleFactor], type$.String, type$.num);
+ }
+ };
+ O.Metadata_Metadata__unresolved.prototype = {
+ call$0: function() {
+ var _this = this,
+ t1 = _this._box_0,
+ t2 = t1.tags;
+ return O.Metadata$_(_this.chainStackTraces, t1.forTag, _this.onPlatform, _this.retry, _this.skip, _this.skipReason, t2, _this.testOn, _this.timeout, _this.verboseTrace);
+ },
+ $signature: 77
+ };
+ O.Metadata_Metadata_closure.prototype = {
+ call$2: function(merged, selector) {
+ var t1, t2;
+ type$.Metadata._check(merged);
+ type$.BooleanSelector._check(selector);
+ t1 = this._box_0;
+ t2 = t1.tags;
+ if (!H.boolConversionCheck(selector.evaluate$1(t2.get$contains(t2))))
+ return merged;
+ return merged.merge$1(t1.forTag.remove$1(0, selector));
+ },
+ $signature: 78
+ };
+ O.Metadata$deserialize_closure.prototype = {
+ call$2: function(key, nested) {
+ return new P.MapEntry(new Y.BooleanSelectorImpl(new G.Parser(new O.Scanner(S.SpanScanner$(H._asStringNullable(key)))).parse$0()), O.Metadata$deserialize(nested), type$.MapEntry_BooleanSelector_Metadata);
+ },
+ $signature: 79
+ };
+ O.Metadata__validateTags_closure.prototype = {
+ call$1: function(tag) {
+ return !J.contains$1$asx(H._checkStringNullable(tag), $.$get$anchoredHyphenatedIdentifier());
+ },
+ $signature: 4
+ };
+ O.Metadata__validateTags_closure0.prototype = {
+ call$1: function(tag) {
+ return '"' + H.S(H._checkStringNullable(tag)) + '"';
+ },
+ $signature: 7
+ };
+ O.Metadata_validatePlatformSelectors_closure.prototype = {
+ call$2: function(selector, metadata) {
+ var t1;
+ type$.PlatformSelector._check(selector);
+ type$.Metadata._check(metadata);
+ t1 = this.validVariables;
+ selector.validate$1(t1);
+ metadata.validatePlatformSelectors$1(t1);
+ },
+ $signature: 19
+ };
+ O.Metadata_merge_closure.prototype = {
+ call$2: function(metadata1, metadata2) {
+ var t1 = type$.Metadata;
+ return t1._check(metadata1).merge$1(t1._check(metadata2));
+ },
+ $signature: 44
+ };
+ O.Metadata_merge_closure0.prototype = {
+ call$2: function(metadata1, metadata2) {
+ var t1 = type$.Metadata;
+ return t1._check(metadata1).merge$1(t1._check(metadata2));
+ },
+ $signature: 44
+ };
+ O.Metadata_forPlatform_closure.prototype = {
+ call$2: function(platformSelector, platformMetadata) {
+ var t1;
+ type$.PlatformSelector._check(platformSelector);
+ type$.Metadata._check(platformMetadata);
+ if (!H.boolConversionCheck(platformSelector.evaluate$1(this.platform)))
+ return;
+ t1 = this._box_0;
+ t1.metadata = t1.metadata.merge$1(platformMetadata);
+ },
+ $signature: 19
+ };
+ O.Metadata_serialize_closure.prototype = {
+ call$2: function(key, value) {
+ type$.PlatformSelector._check(key);
+ type$.Metadata._check(value);
+ C.JSArray_methods.add$1(this.serializedOnPlatform, [J.toString$0$(key), value.serialize$0()]);
+ },
+ $signature: 19
+ };
+ O.Metadata_serialize_closure0.prototype = {
+ call$2: function(selector, metadata) {
+ type$.BooleanSelector._check(selector);
+ type$.Metadata._check(metadata);
+ return new P.MapEntry(J.toString$0$(selector), metadata.serialize$0(), type$.MapEntry_of_String_and_Map_String_dynamic);
+ },
+ $signature: 82
+ };
+ N.OperatingSystem.prototype = {
+ toString$0: function(_) {
+ return this.name;
+ }
+ };
+ N.OperatingSystem_find_closure.prototype = {
+ call$1: function(platform) {
+ return type$.OperatingSystem._check(platform).identifier === this.identifier;
+ },
+ $signature: 83
+ };
+ N.OperatingSystem_find_closure0.prototype = {
+ call$0: function() {
+ return null;
+ },
+ $signature: 0
+ };
+ E.PlatformSelector.prototype = {
+ validate$1: function(validVariables) {
+ type$.Set_String._check(validVariables);
+ if (this === C.PlatformSelector_All)
+ return;
+ E.PlatformSelector__wrapFormatException(new E.PlatformSelector_validate_closure(this, validVariables), null, type$.void);
+ },
+ evaluate$1: function(platform) {
+ return this._platform_selector$_inner.evaluate$1(new E.PlatformSelector_evaluate_closure(platform));
+ },
+ intersection$1: function(other) {
+ var t1 = other._platform_selector$_inner,
+ t2 = J.$eq$(t1, C.C_All);
+ if (t2)
+ return this;
+ return new E.PlatformSelector(this._platform_selector$_inner.intersection$1(t1));
+ },
+ toString$0: function(_) {
+ return J.toString$0$(this._platform_selector$_inner);
+ },
+ $eq: function(_, other) {
+ if (other == null)
+ return false;
+ return other instanceof E.PlatformSelector && J.$eq$(this._platform_selector$_inner, other._platform_selector$_inner);
+ },
+ get$hashCode: function(_) {
+ return J.get$hashCode$(this._platform_selector$_inner);
+ }
+ };
+ E.PlatformSelector$parse_closure.prototype = {
+ call$0: function() {
+ return new Y.BooleanSelectorImpl(new G.Parser(new O.Scanner(S.SpanScanner$(this.selector))).parse$0());
+ },
+ $signature: 84
+ };
+ E.PlatformSelector_validate_closure.prototype = {
+ call$0: function() {
+ return this.$this._platform_selector$_inner.validate$1(new E.PlatformSelector_validate__closure(this.validVariables));
+ },
+ $signature: 1
+ };
+ E.PlatformSelector_validate__closure.prototype = {
+ call$1: function($name) {
+ return $.$get$_universalValidVariables().contains$1(0, $name) || this.validVariables.contains$1(0, $name);
+ },
+ $signature: 4
+ };
+ E.PlatformSelector_evaluate_closure.prototype = {
+ call$1: function(variable) {
+ var t1, t2, t3;
+ H._checkStringNullable(variable);
+ t1 = this.platform;
+ t2 = t1.runtime;
+ if (variable == t2.identifier)
+ return true;
+ t3 = t2.parent;
+ if (variable == (t3 == null ? null : t3.identifier))
+ return true;
+ t3 = t1.os;
+ if (variable === t3.identifier)
+ return true;
+ switch (variable) {
+ case "dart-vm":
+ return t2.isDartVM;
+ case "browser":
+ return t2.isBrowser;
+ case "js":
+ return t2.isJS;
+ case "blink":
+ return t2.isBlink;
+ case "posix":
+ return t3 !== C.OperatingSystem_Windows_windows && t3 !== C.OperatingSystem_none_none;
+ case "google":
+ return t1.inGoogle;
+ default:
+ return false;
+ }
+ },
+ $signature: 4
+ };
+ B.Runtime.prototype = {
+ toString$0: function(_) {
+ return this.name;
+ }
+ };
+ B.Runtime_Runtime$deserialize_closure.prototype = {
+ call$1: function(platform) {
+ return type$.Runtime._check(platform).identifier === this.serialized;
+ },
+ $signature: 85
+ };
+ U.StackTraceFormatter.prototype = {
+ configure$3$except$mapper$only: function(except, mapper, only) {
+ var t1 = type$.Set_String;
+ t1._check(except);
+ t1._check(only);
+ if (mapper != null)
+ this._mapper = mapper;
+ if (except != null)
+ this.set$_except(except);
+ if (only != null)
+ this.set$_only(only);
+ },
+ configure$2$except$only: function(except, only) {
+ return this.configure$3$except$mapper$only(except, null, only);
+ },
+ configure$1$mapper: function(mapper) {
+ return this.configure$3$except$mapper$only(null, mapper, null);
+ },
+ formatStackTrace$2$verbose: function(stackTrace, verbose) {
+ var t2, t3, chain,
+ t1 = this._mapper;
+ if (t1 == null)
+ t1 = null;
+ else {
+ t2 = t1._mapping;
+ if (t2 == null) {
+ t2 = t1._mapContents;
+ t3 = t1._mapUrl;
+ t3 = t1._mapping = T.parseJsonExtended(C.C_JsonCodec.decode$2$reviver(t2, null), t3, null);
+ t2 = t3;
+ }
+ t1 = O.mapStackTrace(t2, stackTrace, false, t1._packageResolver, t1._sdkRoot);
+ }
+ chain = U.Chain_Chain$forTrace(t1 == null ? stackTrace : t1);
+ if (verbose)
+ return chain;
+ return chain.foldFrames$2$terse(new U.StackTraceFormatter_formatStackTrace_closure(this), true);
+ },
+ set$_except: function(_except) {
+ this._except = type$.Set_String._check(_except);
+ },
+ set$_only: function(_only) {
+ this._only = type$.Set_String._check(_only);
+ }
+ };
+ U.StackTraceFormatter_formatStackTrace_closure.prototype = {
+ call$1: function(frame) {
+ var t1 = this.$this,
+ t2 = t1._only;
+ if (t2._collection$_length !== 0)
+ return !t2.contains$1(0, frame.get$$package());
+ return t1._except.contains$1(0, frame.get$$package());
+ },
+ $signature: 21
+ };
+ G.State.prototype = {
+ $eq: function(_, other) {
+ if (other == null)
+ return false;
+ return other instanceof G.State && this.status === other.status && this.result === other.result;
+ },
+ get$hashCode: function(_) {
+ return (H.Primitives_objectHashCode(this.status) ^ 7 * H.Primitives_objectHashCode(this.result)) >>> 0;
+ },
+ toString$0: function(_) {
+ var t1 = this.status;
+ if (t1 === C.Status_pending)
+ return "pending";
+ if (t1 === C.Status_complete)
+ return this.result.name;
+ t1 = this.result;
+ if (t1 === C.Result_success)
+ return "running";
+ return "running with " + t1.toString$0(0);
+ }
+ };
+ G.Status.prototype = {
+ toString$0: function(_) {
+ return this.name;
+ }
+ };
+ G.Result0.prototype = {
+ toString$0: function(_) {
+ return this.name;
+ }
+ };
+ U.Suite.prototype = {};
+ E.SuitePlatform.prototype = {};
+ V.Test.prototype = {$isGroupEntry: 1};
+ G.TestFailure.prototype = {
+ toString$0: function(_) {
+ return this.message;
+ },
+ get$message: function(receiver) {
+ return this.message;
+ }
+ };
+ G._expect_closure0.prototype = {
+ call$5: function(actual, matcher, reason, matchState, verbose) {
+ var t1 = new P.StringBuffer("");
+ matcher.describeMismatch$4(true, new E.StringDescription(t1), matchState, false);
+ t1 = t1._contents;
+ return G.formatFailure(matcher, true, t1.charCodeAt(0) == 0 ? t1 : t1, reason);
+ },
+ $signature: 86
+ };
+ G._expect_closure.prototype = {
+ call$0: function() {
+ },
+ $signature: 0
+ };
+ R.Timeout.prototype = {
+ merge$1: function(other) {
+ var t1, t2;
+ if (this.$eq(0, C.Timeout_null_null) || other.$eq(0, C.Timeout_null_null))
+ return C.Timeout_null_null;
+ t1 = other.duration;
+ if (t1 != null)
+ return new R.Timeout(t1, null);
+ t1 = this.duration;
+ if (t1 != null) {
+ t2 = other.scaleFactor;
+ t1 = t1._duration;
+ if (typeof t2 !== "number")
+ return H.iae(t2);
+ return new R.Timeout(new P.Duration(C.JSNumber_methods.round$0(t1 * t2)), null);
+ }
+ t1 = this.scaleFactor;
+ t2 = other.scaleFactor;
+ if (typeof t1 !== "number")
+ return t1.$mul();
+ if (typeof t2 !== "number")
+ return H.iae(t2);
+ return new R.Timeout(null, t1 * t2);
+ },
+ apply$1: function(base) {
+ var t1;
+ if (this.$eq(0, C.Timeout_null_null))
+ return null;
+ t1 = this.duration;
+ if (t1 == null) {
+ t1 = this.scaleFactor;
+ if (typeof t1 !== "number")
+ return H.iae(t1);
+ t1 = new P.Duration(C.JSNumber_methods.round$0(base._duration * t1));
+ }
+ return t1;
+ },
+ get$hashCode: function(_) {
+ return (J.get$hashCode$(this.duration) ^ 5 * J.get$hashCode$(this.scaleFactor)) >>> 0;
+ },
+ $eq: function(_, other) {
+ if (other == null)
+ return false;
+ return other instanceof R.Timeout && J.$eq$(other.duration, this.duration) && other.scaleFactor == this.scaleFactor;
+ },
+ toString$0: function(_) {
+ var t1 = this.duration;
+ if (t1 != null)
+ return t1.toString$0(0);
+ t1 = this.scaleFactor;
+ if (t1 != null)
+ return H.S(t1) + "x";
+ return "none";
+ }
+ };
+ S.RemoteListener.prototype = {
+ _serializeGroup$3: function(channel, group, parents) {
+ var t2, t3, t4, t5, t6, t1 = {};
+ t1.parents = parents;
+ type$.Iterable_Group._check(parents);
+ parents = H.setRuntimeTypeInfo(parents.slice(0), H._arrayInstanceType(parents));
+ C.JSArray_methods.add$1(parents, group);
+ t1.parents = parents;
+ t2 = group.metadata.serialize$0();
+ t3 = group.trace;
+ t3 = t3 == null ? null : J.toString$0$(t3.get$_lazy_trace$_trace());
+ t4 = group.entries;
+ t5 = H._arrayInstanceType(t4);
+ t6 = type$.dynamic;
+ return P.LinkedHashMap_LinkedHashMap$_literal(["type", "group", "name", group.name, "metadata", t2, "trace", t3, "setUpAll", this._serializeTest$3(channel, group.setUpAll, parents), "tearDownAll", this._serializeTest$3(channel, group.tearDownAll, parents), "entries", new H.MappedListIterable(t4, t5._eval$1("Map<@,@>(1)")._check(new S.RemoteListener__serializeGroup_closure(t1, this, channel)), t5._eval$1("MappedListIterable<1,Map<@,@>>")).toList$0(0)], t6, t6);
+ },
+ _serializeTest$3: function(channel, test, groups) {
+ var testChannel, t1, t2, t3, t4;
+ type$.Iterable_Group._check(groups);
+ if (test == null)
+ return null;
+ testChannel = channel.virtualChannel$0();
+ testChannel.stream.listen$1(new S.RemoteListener__serializeTest_closure(this, test, groups, channel));
+ t1 = test.name;
+ t2 = test.metadata.serialize$0();
+ t3 = test.trace;
+ t3 = t3 == null ? null : J.toString$0$(t3.get$_lazy_trace$_trace());
+ t4 = type$.dynamic;
+ return P.LinkedHashMap_LinkedHashMap$_literal(["type", "test", "name", t1, "metadata", t2, "trace", t3, "channel", testChannel.id], t4, t4);
+ },
+ _runLiveTest$2: function(liveTest, channel) {
+ var t1, t2;
+ channel.stream.listen$1(new S.RemoteListener__runLiveTest_closure(liveTest));
+ t1 = liveTest._live_test_controller$_controller;
+ t2 = t1._onStateChangeController;
+ new P._BroadcastStream(t2, H._instanceType(t2)._eval$1("_BroadcastStream<1>")).listen$1(new S.RemoteListener__runLiveTest_closure0(channel));
+ t2 = t1._onErrorController;
+ new P._BroadcastStream(t2, H._instanceType(t2)._eval$1("_BroadcastStream<1>")).listen$1(new S.RemoteListener__runLiveTest_closure1(channel, liveTest));
+ t1 = t1._onMessageController;
+ new P._BroadcastStream(t1, H._instanceType(t1)._eval$1("_BroadcastStream<1>")).listen$1(new S.RemoteListener__runLiveTest_closure2(this, channel));
+ t1 = type$.dynamic;
+ P.runZoned(new S.RemoteListener__runLiveTest_closure3(liveTest, channel), null, null, P.LinkedHashMap_LinkedHashMap$_literal([C.Symbol_MAi, channel], t1, t1), type$.Null);
+ }
+ };
+ S.RemoteListener_start_closure.prototype = {
+ call$4: function(_, __, ___, line) {
+ var t1;
+ H._checkStringNullable(line);
+ t1 = this.printZone;
+ if (t1 != null)
+ t1.print$1(0, line);
+ t1 = type$.String;
+ this.channel._mainController._foreign._sink.add$1(0, P.LinkedHashMap_LinkedHashMap$_literal(["type", "print", "line", line], t1, t1));
+ },
+ $signature: 87
+ };
+ S.RemoteListener_start_closure0.prototype = {
+ call$1: function(_) {
+ },
+ $signature: 3
+ };
+ S.RemoteListener_start_closure1.prototype = {
+ call$0: function() {
+ var _this = this,
+ t1 = type$.String,
+ t2 = P.LinkedHashSet_LinkedHashSet$_literal(["test", "stream_channel", "test_api"], t1),
+ t3 = type$.dynamic;
+ P.runZoned(type$.Null_Function._check(new S.RemoteListener_start__closure(_this._box_0, _this.getMain, _this.channel, _this.controller, _this.beforeLoad, _this.printZone, _this.spec)), null, null, P.LinkedHashMap_LinkedHashMap$_literal([$.$get$_currentKey0(), new U.StackTraceFormatter(t2, P.LinkedHashSet_LinkedHashSet$_empty(t1))], t3, t3), type$.Null);
+ },
+ $signature: 0
+ };
+ S.RemoteListener_start__closure.prototype = {
+ call$0: function() {
+ var _this = this,
+ t1 = _this._box_0,
+ t2 = _this.channel;
+ P.runZoned(new S.RemoteListener_start___closure(t1, _this.getMain, t2, _this.controller, _this.beforeLoad, _this.printZone), new S.RemoteListener_start___closure0(t1, t2), _this.spec, null, type$.Future_Null);
+ },
+ $signature: 0
+ };
+ S.RemoteListener_start___closure.prototype = {
+ call$0: function() {
+ var $async$goto = 0,
+ $async$completer = P._makeAsyncAwaitCompleter(type$.Null),
+ $async$returnValue, $async$next = [], $async$self = this, error, stackTrace, exception, t1, t2, queue, message, t3, metadata, declarer, t4, map, t5, t6, main;
+ var $async$call$0 = P._wrapJsFunctionForAsync(function($async$errorCode, $async$result) {
+ if ($async$errorCode === 1)
+ return P._asyncRethrow($async$result, $async$completer);
+ while (true)
+ switch ($async$goto) {
+ case 0:
+ // Function start
+ main = null;
+ try {
+ main = $async$self.getMain.call$0();
+ } catch (exception) {
+ t1 = H.unwrapException(exception);
+ if (type$.NoSuchMethodError._is(t1)) {
+ S.RemoteListener__sendLoadException($async$self.channel, "No top-level main() function defined.");
+ // goto return
+ $async$goto = 1;
+ break;
+ } else {
+ error = t1;
+ stackTrace = H.getTraceFromException(exception);
+ S.RemoteListener__sendError($async$self.channel, error, stackTrace, $async$self._box_0.verboseChain);
+ // goto return
+ $async$goto = 1;
+ break;
+ }
+ }
+ if (!type$.Function._is(main)) {
+ S.RemoteListener__sendLoadException($async$self.channel, "Top-level main getter is not a function.");
+ // goto return
+ $async$goto = 1;
+ break;
+ } else if (!type$.dynamic_Function._is(main)) {
+ S.RemoteListener__sendLoadException($async$self.channel, "Top-level main() function takes arguments.");
+ // goto return
+ $async$goto = 1;
+ break;
+ }
+ t1 = $async$self.channel;
+ t2 = t1._mainController._foreign._streamController;
+ t2.toString;
+ queue = new G.StreamQueue(new P._ControllerStream(t2, H._instanceType(t2)._eval$1("_ControllerStream<1>")), Q.QueueList$(type$.Result_dynamic), P.ListQueue$(type$._EventRequest_dynamic), type$.StreamQueue_dynamic);
+ $async$goto = 3;
+ return P._asyncAwait(queue.get$next(), $async$call$0);
+ case 3:
+ // returning from await.
+ message = $async$result;
+ t2 = J.getInterceptor$asx(message);
+ H.assertHelper(J.$eq$(t2.$index(message, "type"), "initial"));
+ if (queue._isClosed)
+ H.throwExpression(queue._failClosed$0());
+ t3 = new Y._CompleterStream(type$._CompleterStream_dynamic);
+ queue._isClosed = true;
+ queue._addRequest$1(new G._RestRequest(new Y.StreamCompleter(t3, type$.StreamCompleter_dynamic), queue, type$._RestRequest_dynamic));
+ t3.listen$1(new S.RemoteListener_start____closure($async$self.controller, t1));
+ t3 = H._asBoolNullable(t2.$index(message, "asciiGlyphs"));
+ if (t3 === true)
+ $._glyphs = C.C_AsciiGlyphSet;
+ metadata = O.Metadata$deserialize(t2.$index(message, "metadata"));
+ $async$self._box_0.verboseChain = metadata._verboseTrace === true;
+ t3 = P.LinkedHashSet_LinkedHashSet$from(type$.Iterable_dynamic._as(t2.$index(message, "platformVariables")), type$.String);
+ declarer = X.Declarer$(H._asBoolNullable(t2.$index(message, "collectTraces")), metadata, H._asBoolNullable(t2.$index(message, "noRetry")), t3);
+ t3 = type$.List_dynamic;
+ type$.StackTraceFormatter._as($.Zone__current.$index(0, $.$get$_currentKey0())).configure$2$except$only(S.RemoteListener__deserializeSet(t3._as(t2.$index(message, "foldTraceExcept"))), S.RemoteListener__deserializeSet(t3._as(t2.$index(message, "foldTraceOnly"))));
+ $async$goto = 4;
+ return P._asyncAwait($async$self.beforeLoad.call$0(), $async$call$0);
+ case 4:
+ // returning from await.
+ t3 = type$.dynamic;
+ $async$goto = 5;
+ return P._asyncAwait(P.runZoned(type$.void_Function._check(main), null, null, P.LinkedHashMap_LinkedHashMap$_literal([C.Symbol_Drw, declarer], t3, t3), type$.void), $async$call$0);
+ case 5:
+ // returning from await.
+ t4 = declarer.build$0();
+ map = type$.Map_dynamic_dynamic._as(t2.$index(message, "platform"));
+ t5 = B.Runtime_Runtime$deserialize(map.$index(0, "runtime"));
+ t6 = N.OperatingSystem_find(H._asStringNullable(map.$index(0, "os")));
+ t6 = E.SuitePlatform$(t5, H._asBoolNullable(map.$index(0, "inGoogle")), t6);
+ P.runZoned(new S.RemoteListener_start____closure0(new U.Suite(t6, H._asStringNullable(t2.$index(message, "path")), U.Suite__filterGroup(t4, t6)), $async$self.printZone, t1), null, null, P.LinkedHashMap_LinkedHashMap$_literal([C.Symbol_Drw, declarer], t3, t3), type$.Null);
+ case 1:
+ // return
+ return P._asyncReturn($async$returnValue, $async$completer);
+ }
+ });
+ return P._asyncStartSync($async$call$0, $async$completer);
+ },
+ $signature: 2
+ };
+ S.RemoteListener_start____closure.prototype = {
+ call$1: function(message) {
+ var t2, t3, t4,
+ t1 = J.getInterceptor$asx(message);
+ if (J.$eq$(t1.$index(message, "type"), "close")) {
+ this.controller._local._sink.close$0(0);
+ return;
+ }
+ H.assertHelper(J.$eq$(t1.$index(message, "type"), "suiteChannel"));
+ t2 = type$.SuiteChannelManager._as($.Zone__current.$index(0, $.$get$_currentKey()));
+ t3 = H._asStringNullable(t1.$index(message, "name"));
+ t1 = this.channel.virtualChannel$1(H._asIntNullable(t1.$index(message, "id")));
+ t4 = t2._outgoingConnections;
+ if (t4.containsKey$1(t3)) {
+ t2 = t4.remove$1(0, t3);
+ t2.toString;
+ H.instanceType(t2)._eval$1("StreamChannel<1>")._check(t1);
+ if (t2._stream_channel_completer$_set)
+ H.throwExpression(P.StateError$("The channel has already been set."));
+ t2._stream_channel_completer$_set = true;
+ t2._streamCompleter.setSourceStream$1(t1.stream);
+ t2 = t2._sinkCompleter;
+ t3 = t2.$ti;
+ t1 = t3._eval$1("StreamSink<1>")._check(t1.sink);
+ t2 = t3._eval$1("_CompleterSink<1>")._check(t2.sink);
+ if (t2._destinationSink != null)
+ H.throwExpression(P.StateError$("Destination sink already set"));
+ t2._setDestinationSink$1(t1);
+ } else {
+ t2 = t2._incomingConnections;
+ if (t2.containsKey$1(t3))
+ H.throwExpression(P.StateError$('Duplicate RunnerSuite.channel() connection "' + H.S(t3) + '".'));
+ else
+ t2.$indexSet(0, t3, t1);
+ }
+ },
+ $signature: 3
+ };
+ S.RemoteListener_start____closure0.prototype = {
+ call$0: function() {
+ U.Invoker_guard(new S.RemoteListener_start_____closure(this.suite, this.printZone, this.channel), type$.void);
+ },
+ $signature: 0
+ };
+ S.RemoteListener_start_____closure.prototype = {
+ call$0: function() {
+ var t1 = this.suite,
+ t2 = this.channel;
+ t2._mainController._foreign._sink.add$1(0, P.LinkedHashMap_LinkedHashMap$_literal(["type", "success", "root", new S.RemoteListener(t1, this.printZone)._serializeGroup$3(t2, t1.group, H.setRuntimeTypeInfo([], type$.JSArray_Group))], type$.String, type$.Object));
+ return null;
+ },
+ $signature: 1
+ };
+ S.RemoteListener_start___closure0.prototype = {
+ call$2: function(error, stackTrace) {
+ S.RemoteListener__sendError(this.channel, error, type$.StackTrace._check(stackTrace), this._box_0.verboseChain);
+ },
+ $signature: 5
+ };
+ S.RemoteListener__serializeGroup_closure.prototype = {
+ call$1: function(entry) {
+ var t1, t2, t3;
+ type$.GroupEntry._check(entry);
+ t1 = this.$this;
+ t2 = this.channel;
+ t3 = this._box_0.parents;
+ return entry instanceof O.Group ? t1._serializeGroup$3(t2, entry, t3) : t1._serializeTest$3(t2, type$.Test._as(entry), t3);
+ },
+ $signature: 88
+ };
+ S.RemoteListener__serializeTest_closure.prototype = {
+ call$1: function(message) {
+ var t2, _this = this,
+ t1 = J.getInterceptor$asx(message);
+ H.assertHelper(J.$eq$(t1.$index(message, "command"), "run"));
+ t2 = _this.$this;
+ t2._runLiveTest$2(_this.test.load$2$groups(t2._suite, _this.groups), _this.channel.virtualChannel$1(H._asIntNullable(t1.$index(message, "channel"))));
+ },
+ $signature: 3
+ };
+ S.RemoteListener__runLiveTest_closure.prototype = {
+ call$1: function(message) {
+ H.assertHelper(J.$eq$(J.$index$asx(message, "command"), "close"));
+ this.liveTest._live_test_controller$_controller._live_test_controller$_close$0();
+ },
+ $signature: 3
+ };
+ S.RemoteListener__runLiveTest_closure0.prototype = {
+ call$1: function(state) {
+ var t1;
+ type$.State._check(state);
+ t1 = type$.String;
+ this.channel.sink.add$1(0, P.LinkedHashMap_LinkedHashMap$_literal(["type", "state-change", "status", state.status.name, "result", state.result.name], t1, t1));
+ },
+ $signature: 23
+ };
+ S.RemoteListener__runLiveTest_closure1.prototype = {
+ call$1: function(asyncError) {
+ var t1, t2, t3, t4;
+ type$.AsyncError._check(asyncError);
+ t1 = asyncError.error;
+ t2 = type$.StackTraceFormatter._as($.Zone__current.$index(0, $.$get$_currentKey0()));
+ t3 = asyncError.stackTrace;
+ t4 = this.liveTest._live_test_controller$_controller;
+ this.channel.sink.add$1(0, P.LinkedHashMap_LinkedHashMap$_literal(["type", "error", "error", U.RemoteException_serialize(t1, t2.formatStackTrace$2$verbose(t3, t4._test.metadata._verboseTrace === true))], type$.String, type$.Object));
+ },
+ $signature: 90
+ };
+ S.RemoteListener__runLiveTest_closure2.prototype = {
+ call$1: function(message) {
+ var t1;
+ type$.Message._check(message);
+ t1 = this.$this._printZone;
+ if (t1 != null)
+ t1.print$1(0, message.text);
+ t1 = type$.String;
+ this.channel.sink.add$1(0, P.LinkedHashMap_LinkedHashMap$_literal(["type", "message", "message-type", message.type.name, "text", message.text], t1, t1));
+ },
+ $signature: 27
+ };
+ S.RemoteListener__runLiveTest_closure3.prototype = {
+ call$0: function() {
+ this.liveTest._live_test_controller$_controller._live_test_controller$_run$0().then$1$1(new S.RemoteListener__runLiveTest__closure(this.channel), type$.void);
+ },
+ $signature: 0
+ };
+ S.RemoteListener__runLiveTest__closure.prototype = {
+ call$1: function(_) {
+ var t1 = type$.String;
+ return this.channel.sink.add$1(0, P.LinkedHashMap_LinkedHashMap$_literal(["type", "complete"], t1, t1));
+ },
+ $signature: 15
+ };
+ N.SuiteChannelManager.prototype = {
+ connectOut$1: function($name) {
+ var t2, completer,
+ t1 = this._incomingConnections;
+ if (t1.containsKey$1($name))
+ return t1.$index(0, $name);
+ else {
+ t1 = this._names;
+ if (t1.contains$1(0, $name))
+ throw H.wrapException(P.StateError$('Duplicate suiteChannel() connection "' + $name + '".'));
+ else {
+ t1.add$1(0, $name);
+ t1 = new Y._CompleterStream(type$._CompleterStream_dynamic);
+ t2 = new T._CompleterSink(type$._CompleterSink_dynamic);
+ completer = new N.StreamChannelCompleter(new Y.StreamCompleter(t1, type$.StreamCompleter_dynamic), new T.StreamSinkCompleter(t2, type$.StreamSinkCompleter_dynamic), type$.StreamChannelCompleter_dynamic);
+ completer.set$_stream_channel_completer$_channel(new R._StreamChannel(t1, t2, type$._StreamChannel_dynamic));
+ this._outgoingConnections.$indexSet(0, $name, completer);
+ return completer._stream_channel_completer$_channel;
+ }
+ }
+ }
+ };
+ O.IterableSet.prototype = {
+ get$length: function(_) {
+ var t1 = this._iterable_set$_base._collection$_source;
+ return t1.get$length(t1);
+ },
+ get$iterator: function(_) {
+ var t1 = this._iterable_set$_base;
+ return new H.ListIterator(t1, t1.get$length(t1), t1.$ti._eval$1("ListIterator<ListMixin.E>"));
+ },
+ toSet$0: function(_) {
+ var t1 = this._iterable_set$_base;
+ return t1.toSet$0(t1);
+ }
+ };
+ O._IterableSet_SetMixin_UnmodifiableSetMixin.prototype = {};
+ E.StackTraceMapper.prototype = {};
+ V.errorsDontStopTest_closure.prototype = {
+ call$0: function() {
+ P.Future_Future$sync(this.body, type$.dynamic).whenComplete$1(this.completer.get$complete());
+ },
+ $signature: 0
+ };
+ V.errorsDontStopTest_closure0.prototype = {
+ call$1: function(_) {
+ var t1 = type$.Invoker._as($.Zone__current.$index(0, C.Symbol_cQL));
+ t1.heartbeat$0();
+ t1.get$_outstandingCallbacks().decrement$0();
+ return null;
+ },
+ $signature: 92
+ };
+ B.closure0.prototype = {
+ call$0: function() {
+ var t1 = $.$get$context().style;
+ if (t1 == $.$get$Style_url())
+ return C.OperatingSystem_none_none;
+ if (t1 == $.$get$Style_windows())
+ return C.OperatingSystem_Windows_windows;
+ if ($._macOSDirectories.any$1(0, J.get$startsWith$s(D.current())))
+ return C.OperatingSystem_RkP;
+ return C.OperatingSystem_Linux_linux;
+ },
+ $signature: 115
+ };
+ O.Engine.prototype = {
+ get$_onUnpaused: function() {
+ var t1 = new P._Future($.Zone__current, type$._Future_dynamic);
+ t1._asyncComplete$1(null);
+ return t1;
+ },
+ get$success: function() {
+ var $async$goto = 0,
+ $async$completer = P._makeAsyncAwaitCompleter(type$.bool),
+ $async$returnValue, $async$self = this;
+ var $async$get$success = P._wrapJsFunctionForAsync(function($async$errorCode, $async$result) {
+ if ($async$errorCode === 1)
+ return P._asyncRethrow($async$result, $async$completer);
+ while (true)
+ switch ($async$goto) {
+ case 0:
+ // Function start
+ $async$goto = 3;
+ return P._asyncAwait(P.Future_wait(H.setRuntimeTypeInfo([$async$self._group._future_group$_completer.future, $async$self._loadPool._pool$_closeMemo._async_memoizer$_completer.future], type$.JSArray_Future_dynamic), true, type$.dynamic), $async$get$success);
+ case 3:
+ // returning from await.
+ if (H.boolConversionCheck($async$self._closedBeforeDone)) {
+ $async$returnValue = null;
+ // goto return
+ $async$goto = 1;
+ break;
+ }
+ $async$returnValue = $async$self.get$liveTests().every$1(0, new O.Engine_success_closure());
+ // goto return
+ $async$goto = 1;
+ break;
+ case 1:
+ // return
+ return P._asyncReturn($async$returnValue, $async$completer);
+ }
+ });
+ return P._asyncStartSync($async$get$success, $async$completer);
+ },
+ get$liveTests: function() {
+ var _this = this;
+ return new M.UnionSet(P.LinkedHashSet_LinkedHashSet$from(H.setRuntimeTypeInfo([_this._passedGroup._union_set_controller$_set, _this._skippedGroup._union_set_controller$_set, _this._failedGroup._union_set_controller$_set, new O.IterableSet(new P.UnmodifiableListView(_this._active, type$.UnmodifiableListView_LiveTest), type$.IterableSet_LiveTest)], type$.JSArray_Set_LiveTest), type$.Set_LiveTest), true, type$.UnionSet_LiveTest);
+ },
+ Engine$3$concurrency$coverage$maxSuites: function(concurrency, coverage, maxSuites) {
+ this._group._future_group$_completer.future.then$1$1(new O.Engine_closure(this), type$.Null).catchError$1(new O.Engine_closure0());
+ },
+ run$0: function() {
+ var t2, subscription, _this = this, t1 = {};
+ if (_this._engine$_runCalled)
+ throw H.wrapException(P.StateError$("Engine.run() may not be called more than once."));
+ _this._engine$_runCalled = true;
+ t1.subscription = null;
+ t2 = _this._suiteController;
+ subscription = new P._ControllerStream(t2, H._instanceType(t2)._eval$1("_ControllerStream<1>")).listen$2$onDone(new O.Engine_run_closure(_this), new O.Engine_run_closure0(t1, _this));
+ t1.subscription = subscription;
+ _this._engine$_subscriptions.add$1(0, subscription);
+ return _this.get$success();
+ },
+ _runGroup$3: function(suiteController, group, parents) {
+ type$.List_Group._check(parents);
+ return this._runGroup$body$Engine(suiteController, group, parents);
+ },
+ _runGroup$body$Engine: function(suiteController, group, parents) {
+ var $async$goto = 0,
+ $async$completer = P._makeAsyncAwaitCompleter(type$.dynamic),
+ $async$returnValue, $async$handler = 2, $async$currentError, $async$next = [], $async$self = this, suiteConfig, skipGroup, setUpAllSucceeded, liveTest, entries, entry, test, liveTest0, t1, skipGroup0, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, _i, t16, t17, t18, invoker, t19, t20, result, t21;
+ var $async$_runGroup$3 = P._wrapJsFunctionForAsync(function($async$errorCode, $async$result) {
+ if ($async$errorCode === 1) {
+ $async$currentError = $async$result;
+ $async$goto = $async$handler;
+ }
+ while (true)
+ switch ($async$goto) {
+ case 0:
+ // Function start
+ C.JSArray_methods.add$1(parents, group);
+ $async$handler = 3;
+ t1 = suiteController._liveSuite._live_suite_controller$_controller._live_suite_controller$_suite;
+ suiteConfig = t1._runner_suite$_controller._config;
+ suiteConfig.toString;
+ skipGroup0 = group.metadata._skip === true;
+ skipGroup = skipGroup0;
+ setUpAllSucceeded = true;
+ $async$goto = !H.boolConversionCheck(skipGroup) && group.setUpAll != null ? 6 : 7;
+ break;
+ case 6:
+ // then
+ liveTest = group.setUpAll.load$2$groups(t1, parents);
+ $async$goto = 8;
+ return P._asyncAwait($async$self._engine$_runLiveTest$3$countSuccess(suiteController, liveTest, false), $async$_runGroup$3);
+ case 8:
+ // returning from await.
+ t1 = liveTest._live_test_controller$_controller._live_test_controller$_state.result;
+ setUpAllSucceeded = t1 === C.Result_success || t1 === C.Result_skipped;
+ case 7:
+ // join
+ $async$goto = !$async$self._engine$_closed && H.boolConversionCheck(setUpAllSucceeded) ? 9 : 10;
+ break;
+ case 9:
+ // then
+ t1 = group.entries;
+ t1 = H.setRuntimeTypeInfo(t1.slice(0), H._arrayInstanceType(t1)._eval$1("JSArray<1>"));
+ entries = t1;
+ suiteConfig.toString;
+ t1 = entries, t2 = t1.length, t3 = type$.Group, t4 = type$.List_Group, t5 = type$._Future_void, t6 = type$._AsyncCompleter_void, t7 = type$._SyncBroadcastStreamController_Message, t8 = type$._SyncBroadcastStreamController_AsyncError, t9 = type$._SyncBroadcastStreamController_State, t10 = type$.Test, t11 = type$.Iterable_Group, t12 = type$.JSArray_Zone, t13 = type$.JSArray_of_dynamic_Function, t14 = type$.JSArray_String, t15 = type$.JSArray_AsyncError, _i = 0;
+ case 11:
+ // for condition
+ if (!(_i < t1.length)) {
+ // goto after for
+ $async$goto = 13;
+ break;
+ }
+ entry = t1[_i];
+ if ($async$self._engine$_closed) {
+ $async$next = [1];
+ // goto finally
+ $async$goto = 4;
+ break;
+ }
+ $async$goto = entry instanceof O.Group ? 14 : 16;
+ break;
+ case 14:
+ // then
+ $async$goto = 17;
+ return P._asyncAwait($async$self._runGroup$3(suiteController, entry, parents), $async$_runGroup$3);
+ case 17:
+ // returning from await.
+ // goto join
+ $async$goto = 15;
+ break;
+ case 16:
+ // else
+ suiteConfig.toString;
+ t16 = entry.get$metadata();
+ t16 = t16._skip === true;
+ $async$goto = t16 ? 18 : 20;
+ break;
+ case 18:
+ // then
+ $async$goto = 21;
+ return P._asyncAwait($async$self._runSkippedTest$3(suiteController, t10._as(entry), parents), $async$_runGroup$3);
+ case 21:
+ // returning from await.
+ // goto join
+ $async$goto = 19;
+ break;
+ case 20:
+ // else
+ test = t10._as(entry);
+ t16 = test;
+ t17 = suiteController._liveSuite._live_suite_controller$_controller;
+ t16.toString;
+ t11._check(parents);
+ t18 = new P._AsyncCompleter(new P._Future($.Zone__current, t5), t6);
+ invoker = new U.Invoker(t16._guarded, new P.Object(), t18, H.setRuntimeTypeInfo([], t12), new P.Object(), H.setRuntimeTypeInfo([], t13), H.setRuntimeTypeInfo([], t14));
+ t19 = H.setRuntimeTypeInfo([], t15);
+ t20 = $.Zone__current;
+ result = P.List_List$from(parents, false, t3);
+ result.fixed$length = Array;
+ result.immutable$list = Array;
+ t21 = t4._check(result);
+ t16 = new V.LiveTestController(t17._live_suite_controller$_suite, t21, t16, invoker.get$_invoker$_onRun(), t18.get$complete(), t19, C.State_Status_pending_Result_success, new P._SyncBroadcastStreamController(null, null, t9), new P._SyncBroadcastStreamController(null, null, t8), new P._SyncBroadcastStreamController(null, null, t7), new P._AsyncCompleter(new P._Future(t20, t5), t6));
+ t17 = new V._LiveTest(t16);
+ t16._liveTest = t17;
+ invoker._invoker$_controller = t16;
+ $async$goto = 22;
+ return P._asyncAwait($async$self._engine$_runLiveTest$2(suiteController, t17), $async$_runGroup$3);
+ case 22:
+ // returning from await.
+ case 19:
+ // join
+ case 15:
+ // join
+ case 12:
+ // for update
+ t1.length === t2 || (0, H.throwConcurrentModificationError)(t1), ++_i;
+ // goto for condition
+ $async$goto = 11;
+ break;
+ case 13:
+ // after for
+ case 10:
+ // join
+ $async$goto = !H.boolConversionCheck(skipGroup) && group.tearDownAll != null ? 23 : 24;
+ break;
+ case 23:
+ // then
+ liveTest0 = group.tearDownAll.load$2$groups(suiteController._liveSuite._live_suite_controller$_controller._live_suite_controller$_suite, parents);
+ $async$goto = 25;
+ return P._asyncAwait($async$self._engine$_runLiveTest$3$countSuccess(suiteController, liveTest0, false), $async$_runGroup$3);
+ case 25:
+ // returning from await.
+ $async$goto = $async$self._engine$_closed ? 26 : 27;
+ break;
+ case 26:
+ // then
+ $async$goto = 28;
+ return P._asyncAwait(liveTest0._live_test_controller$_controller._live_test_controller$_close$0(), $async$_runGroup$3);
+ case 28:
+ // returning from await.
+ case 27:
+ // join
+ case 24:
+ // join
+ $async$next.push(5);
+ // goto finally
+ $async$goto = 4;
+ break;
+ case 3:
+ // uncaught
+ $async$next = [2];
+ case 4:
+ // finally
+ $async$handler = 2;
+ C.JSArray_methods.remove$1(parents, group);
+ // goto the next finally handler
+ $async$goto = $async$next.pop();
+ break;
+ case 5:
+ // after finally
+ case 1:
+ // return
+ return P._asyncReturn($async$returnValue, $async$completer);
+ case 2:
+ // rethrow
+ return P._asyncRethrow($async$currentError, $async$completer);
+ }
+ });
+ return P._asyncStartSync($async$_runGroup$3, $async$completer);
+ },
+ _engine$_runLiveTest$3$countSuccess: function(suiteController, liveTest, countSuccess) {
+ return this._runLiveTest$body$Engine(suiteController, liveTest, countSuccess);
+ },
+ _engine$_runLiveTest$2: function(suiteController, liveTest) {
+ return this._engine$_runLiveTest$3$countSuccess(suiteController, liveTest, true);
+ },
+ _runLiveTest$body$Engine: function(suiteController, liveTest, countSuccess) {
+ var $async$goto = 0,
+ $async$completer = P._makeAsyncAwaitCompleter(type$.dynamic),
+ $async$returnValue, $async$self = this, t2, t3, subscription, t1;
+ var $async$_engine$_runLiveTest$3$countSuccess = P._wrapJsFunctionForAsync(function($async$errorCode, $async$result) {
+ if ($async$errorCode === 1)
+ return P._asyncRethrow($async$result, $async$completer);
+ while (true)
+ switch ($async$goto) {
+ case 0:
+ // Function start
+ t1 = {};
+ $async$goto = 3;
+ return P._asyncAwait($async$self.get$_onUnpaused(), $async$_engine$_runLiveTest$3$countSuccess);
+ case 3:
+ // returning from await.
+ t2 = $async$self._active;
+ t2._queue_list$_add$1(t2.$ti._precomputed1._check(liveTest));
+ t2.get$first(t2).toString;
+ t1.subscription = null;
+ t2 = liveTest._live_test_controller$_controller;
+ t3 = t2._onStateChangeController;
+ subscription = new P._BroadcastStream(t3, H._instanceType(t3)._eval$1("_BroadcastStream<1>")).listen$2$onDone(new O.Engine__runLiveTest_closure($async$self, liveTest), new O.Engine__runLiveTest_closure0(t1, $async$self));
+ t1.subscription = subscription;
+ $async$self._engine$_subscriptions.add$1(0, subscription);
+ suiteController.reportLiveTest$2$countSuccess(liveTest, countSuccess);
+ $async$goto = 4;
+ return P._asyncAwait(P.Future_Future$microtask(liveTest.get$run(), type$.dynamic), $async$_engine$_runLiveTest$3$countSuccess);
+ case 4:
+ // returning from await.
+ $async$goto = 5;
+ return P._asyncAwait(P.Future_Future(new O.Engine__runLiveTest_closure1(), type$.Null), $async$_engine$_runLiveTest$3$countSuccess);
+ case 5:
+ // returning from await.
+ t1 = $async$self._restarted;
+ if (!t1.contains$1(0, liveTest)) {
+ // goto return
+ $async$goto = 1;
+ break;
+ }
+ $async$goto = 6;
+ return P._asyncAwait($async$self._engine$_runLiveTest$3$countSuccess(suiteController, t2._test.load$2$groups(t2._live_test_controller$_suite, t2._groups), countSuccess), $async$_engine$_runLiveTest$3$countSuccess);
+ case 6:
+ // returning from await.
+ t1.remove$1(0, liveTest);
+ case 1:
+ // return
+ return P._asyncReturn($async$returnValue, $async$completer);
+ }
+ });
+ return P._asyncStartSync($async$_engine$_runLiveTest$3$countSuccess, $async$completer);
+ },
+ _runSkippedTest$3: function(suiteController, test, parents) {
+ return this._runSkippedTest$body$Engine(suiteController, test, type$.List_Group._check(parents));
+ },
+ _runSkippedTest$body$Engine: function(suiteController, test, parents) {
+ var $async$goto = 0,
+ $async$completer = P._makeAsyncAwaitCompleter(type$.dynamic),
+ $async$returnValue, $async$self = this, skipped, controller, t1;
+ var $async$_runSkippedTest$3 = P._wrapJsFunctionForAsync(function($async$errorCode, $async$result) {
+ if ($async$errorCode === 1)
+ return P._asyncRethrow($async$result, $async$completer);
+ while (true)
+ switch ($async$goto) {
+ case 0:
+ // Function start
+ t1 = {};
+ $async$goto = 3;
+ return P._asyncAwait($async$self.get$_onUnpaused(), $async$_runSkippedTest$3);
+ case 3:
+ // returning from await.
+ skipped = new U.LocalTest(test.name, test.metadata, test.trace, false, new O.Engine__runSkippedTest_closure(), true);
+ t1.controller = null;
+ controller = V.LiveTestController$(suiteController._liveSuite._live_suite_controller$_controller._live_suite_controller$_suite, skipped, new O.Engine__runSkippedTest_closure0(t1, skipped), new O.Engine__runSkippedTest_closure1(), parents);
+ t1.controller = controller;
+ $async$goto = 4;
+ return P._asyncAwait($async$self._engine$_runLiveTest$2(suiteController, controller._liveTest), $async$_runSkippedTest$3);
+ case 4:
+ // returning from await.
+ $async$returnValue = $async$result;
+ // goto return
+ $async$goto = 1;
+ break;
+ case 1:
+ // return
+ return P._asyncReturn($async$returnValue, $async$completer);
+ }
+ });
+ return P._asyncStartSync($async$_runSkippedTest$3, $async$completer);
+ },
+ _addLiveSuite$1: function(liveSuite) {
+ var t1, t2, t3, _this = this;
+ _this._liveSuites.add$1(0, liveSuite);
+ _this._onSuiteStartedController.add$1(0, liveSuite);
+ t1 = liveSuite._live_suite_controller$_controller;
+ t2 = t1._onTestStartedController;
+ _this._onTestStartedGroup.add$1(0, new P._BroadcastStream(t2, H._instanceType(t2)._eval$1("_BroadcastStream<1>")));
+ t2 = _this._passedGroup;
+ t3 = type$.UnmodifiableSetView_LiveTest;
+ t2._union_set_controller$_sets.add$1(0, t2.$ti._eval$1("Set<1>")._check(new L.UnmodifiableSetView(t1._passed, t3)));
+ t2 = _this._skippedGroup;
+ t2._union_set_controller$_sets.add$1(0, t2.$ti._eval$1("Set<1>")._check(new L.UnmodifiableSetView(t1._skipped, t3)));
+ t2 = _this._failedGroup;
+ t2._union_set_controller$_sets.add$1(0, t2.$ti._eval$1("Set<1>")._check(new L.UnmodifiableSetView(t1._failed, t3)));
+ }
+ };
+ O.Engine_success_closure.prototype = {
+ call$1: function(liveTest) {
+ var t1 = type$.LiveTest._check(liveTest)._live_test_controller$_controller._live_test_controller$_state,
+ t2 = t1.result;
+ return (t2 === C.Result_success || t2 === C.Result_skipped) && t1.status === C.Status_complete;
+ },
+ $signature: 95
+ };
+ O.Engine_closure.prototype = {
+ call$1: function(_) {
+ var t1;
+ type$.List_dynamic._check(_);
+ t1 = this.$this;
+ t1._onTestStartedGroup.close$0(0);
+ t1._onSuiteStartedController.close$0(0);
+ if (t1._closedBeforeDone == null)
+ t1._closedBeforeDone = false;
+ },
+ $signature: 48
+ };
+ O.Engine_closure0.prototype = {
+ call$1: function(_) {
+ },
+ $signature: 3
+ };
+ O.Engine_run_closure.prototype = {
+ call$1: function(suite) {
+ var t1;
+ type$.RunnerSuite._check(suite);
+ t1 = this.$this;
+ t1._addedSuites.add$1(0, suite);
+ t1._onSuiteAddedController.add$1(0, suite);
+ t1._group.add$1(0, new O.Engine_run__closure(t1, suite).call$0());
+ },
+ $signature: 97
+ };
+ O.Engine_run__closure.prototype = {
+ call$0: function() {
+ return this.$call$body$Engine_run__closure();
+ },
+ $call$body$Engine_run__closure: function() {
+ var $async$goto = 0,
+ $async$completer = P._makeAsyncAwaitCompleter(type$.Null),
+ $async$self = this, controller, t1, t2, loadResource;
+ var $async$call$0 = P._wrapJsFunctionForAsync(function($async$errorCode, $async$result) {
+ if ($async$errorCode === 1)
+ return P._asyncRethrow($async$result, $async$completer);
+ while (true)
+ switch ($async$goto) {
+ case 0:
+ // Function start
+ t1 = {};
+ t2 = $async$self.$this;
+ $async$goto = 2;
+ return P._asyncAwait(t2._loadPool.request$0(0), $async$call$0);
+ case 2:
+ // returning from await.
+ loadResource = $async$result;
+ t1.controller = null;
+ controller = B.LiveSuiteController$($async$self.suite);
+ t1.controller = controller;
+ t2._addLiveSuite$1(controller._liveSuite);
+ $async$goto = 3;
+ return P._asyncAwait(t2._runPool.withResource$1$1(new O.Engine_run___closure(t1, t2, loadResource), type$.Null), $async$call$0);
+ case 3:
+ // returning from await.
+ // implicit return
+ return P._asyncReturn(null, $async$completer);
+ }
+ });
+ return P._asyncStartSync($async$call$0, $async$completer);
+ },
+ $signature: 2
+ };
+ O.Engine_run___closure.prototype = {
+ call$0: function() {
+ var $async$goto = 0,
+ $async$completer = P._makeAsyncAwaitCompleter(type$.Null),
+ $async$returnValue, $async$self = this, t2, t3, t1;
+ var $async$call$0 = P._wrapJsFunctionForAsync(function($async$errorCode, $async$result) {
+ if ($async$errorCode === 1)
+ return P._asyncRethrow($async$result, $async$completer);
+ while (true)
+ switch ($async$goto) {
+ case 0:
+ // Function start
+ t1 = $async$self.$this;
+ if (t1._engine$_closed) {
+ // goto return
+ $async$goto = 1;
+ break;
+ }
+ t2 = $async$self._box_0;
+ t3 = t2.controller;
+ $async$goto = 3;
+ return P._asyncAwait(t1._runGroup$3(t3, t3._liveSuite._live_suite_controller$_controller._live_suite_controller$_suite.group, H.setRuntimeTypeInfo([], type$.JSArray_Group)), $async$call$0);
+ case 3:
+ // returning from await.
+ t3 = t2.controller;
+ t3._onTestStartedController.close$0(0);
+ t3._onCompleteGroup.close$0(0);
+ t1 = $async$self.loadResource;
+ t1.toString;
+ t2 = type$.dynamic_Function._check(new O.Engine_run____closure(t2));
+ if (t1._released)
+ H.throwExpression(P.StateError$("A PoolResource may only be released once."));
+ t1._released = true;
+ t1._pool._onResourceReleaseAllowed$1(t2);
+ case 1:
+ // return
+ return P._asyncReturn($async$returnValue, $async$completer);
+ }
+ });
+ return P._asyncStartSync($async$call$0, $async$completer);
+ },
+ $signature: 2
+ };
+ O.Engine_run____closure.prototype = {
+ call$0: function() {
+ return this._box_0.controller.close$0(0);
+ },
+ $signature: 14
+ };
+ O.Engine_run_closure0.prototype = {
+ call$0: function() {
+ var t1 = this.$this;
+ t1._engine$_subscriptions.remove$1(0, this._box_1.subscription);
+ t1._onSuiteAddedController.close$0(0);
+ t1._group.close$0(0);
+ t1._loadPool.close$0(0);
+ },
+ $signature: 0
+ };
+ O.Engine__runLiveTest_closure.prototype = {
+ call$1: function(state) {
+ var t1, t2;
+ if (type$.State._check(state).status !== C.Status_complete)
+ return;
+ t1 = this.$this;
+ t2 = t1._active;
+ t2.remove$1(t2, this.liveTest);
+ if (t2.get$length(t2) === 0 && t1._activeLoadTests._collection$_length !== 0) {
+ t1 = t1._activeLoadTests;
+ t2._queue_list$_add$1(t2.$ti._precomputed1._check(t1.get$first(t1)));
+ }
+ },
+ $signature: 23
+ };
+ O.Engine__runLiveTest_closure0.prototype = {
+ call$0: function() {
+ this.$this._engine$_subscriptions.remove$1(0, this._box_0.subscription);
+ },
+ $signature: 0
+ };
+ O.Engine__runLiveTest_closure1.prototype = {
+ call$0: function() {
+ },
+ $signature: 0
+ };
+ O.Engine__runSkippedTest_closure.prototype = {
+ call$0: function() {
+ },
+ $signature: 0
+ };
+ O.Engine__runSkippedTest_closure0.prototype = {
+ call$0: function() {
+ var t2,
+ t1 = this._box_0;
+ t1.controller.setState$1(C.State_Status_running_Result_success);
+ t1.controller.setState$1(C.State_Status_running_Result_skipped);
+ t2 = this.skipped.metadata.skipReason;
+ if (t2 != null)
+ t1.controller.message$1(0, new D.Message(C.MessageType_skip, "Skip: " + t2));
+ t1.controller.setState$1(C.State_Status_complete_Result_skipped);
+ t1.controller.completer.complete$0();
+ },
+ $signature: 0
+ };
+ O.Engine__runSkippedTest_closure1.prototype = {
+ call$0: function() {
+ },
+ $signature: 0
+ };
+ E.LiveSuite.prototype = {};
+ B._LiveSuite.prototype = {};
+ B.LiveSuiteController.prototype = {
+ LiveSuiteController$1: function(_suite) {
+ var _this = this;
+ _this._liveSuite = new B._LiveSuite(_this);
+ _this._onCompleteGroup._future_group$_completer.future.then$1$2$onError(new B.LiveSuiteController_closure(_this), new B.LiveSuiteController_closure0(), type$.Null);
+ },
+ reportLiveTest$2$countSuccess: function(liveTest, countSuccess) {
+ var t2, t3, _this = this,
+ t1 = _this._onTestStartedController;
+ if ((t1._state & 4) !== 0)
+ throw H.wrapException(P.StateError$("Can't call reportLiveTest() after noMoreTests()."));
+ t2 = liveTest._live_test_controller$_controller;
+ H.assertHelper(t2._live_test_controller$_suite == _this._live_suite_controller$_suite);
+ H.assertHelper(_this._live_suite_controller$_active == null);
+ _this._live_suite_controller$_active = liveTest;
+ t3 = t2._onStateChangeController;
+ new P._BroadcastStream(t3, H._instanceType(t3)._eval$1("_BroadcastStream<1>")).listen$1(new B.LiveSuiteController_reportLiveTest_closure(_this, liveTest, countSuccess));
+ t1.add$1(0, liveTest);
+ _this._onCompleteGroup.add$1(0, t2.completer.future);
+ },
+ close$0: function(_) {
+ return this._live_suite_controller$_closeMemo.runOnce$1(new B.LiveSuiteController_close_closure(this));
+ }
+ };
+ B.LiveSuiteController_closure.prototype = {
+ call$1: function(_) {
+ type$.List_dynamic._check(_);
+ },
+ $signature: 48
+ };
+ B.LiveSuiteController_closure0.prototype = {
+ call$1: function(_) {
+ },
+ $signature: 3
+ };
+ B.LiveSuiteController_reportLiveTest_closure.prototype = {
+ call$1: function(state) {
+ var t1, t2, _this = this;
+ type$.State._check(state);
+ if (state.status !== C.Status_complete)
+ return;
+ t1 = _this.$this;
+ t1._live_suite_controller$_active = null;
+ t2 = state.result;
+ if (t2 === C.Result_skipped)
+ t1._skipped.add$1(0, _this.liveTest);
+ else if (t2 !== C.Result_success) {
+ t2 = _this.liveTest;
+ t1._passed.remove$1(0, t2);
+ t1._failed.add$1(0, t2);
+ } else if (_this.countSuccess) {
+ t2 = _this.liveTest;
+ t1._passed.add$1(0, t2);
+ t1._failed.remove$1(0, t2);
+ }
+ },
+ $signature: 23
+ };
+ B.LiveSuiteController_close_closure.prototype = {
+ call$0: function() {
+ var $async$goto = 0,
+ $async$completer = P._makeAsyncAwaitCompleter(type$.Null),
+ $async$handler = 1, $async$currentError, $async$next = [], $async$self = this;
+ var $async$call$0 = P._wrapJsFunctionForAsync(function($async$errorCode, $async$result) {
+ if ($async$errorCode === 1) {
+ $async$currentError = $async$result;
+ $async$goto = $async$handler;
+ }
+ while (true)
+ switch ($async$goto) {
+ case 0:
+ // Function start
+ $async$handler = 2;
+ $async$goto = 5;
+ return P._asyncAwait($async$self.$this._live_suite_controller$_suite._runner_suite$_controller._runner_suite$_close$0(), $async$call$0);
+ case 5:
+ // returning from await.
+ $async$next.push(4);
+ // goto finally
+ $async$goto = 3;
+ break;
+ case 2:
+ // uncaught
+ $async$next = [1];
+ case 3:
+ // finally
+ $async$handler = 1;
+ $async$self.$this._live_suite_controller$_onCloseCompleter.complete$0();
+ // goto the next finally handler
+ $async$goto = $async$next.pop();
+ break;
+ case 4:
+ // after finally
+ // implicit return
+ return P._asyncReturn(null, $async$completer);
+ case 1:
+ // rethrow
+ return P._asyncRethrow($async$currentError, $async$completer);
+ }
+ });
+ return P._asyncStartSync($async$call$0, $async$completer);
+ },
+ $signature: 2
+ };
+ R.ExpandedReporter.prototype = {
+ _onTestStarted$1: function(liveTest) {
+ var t1, t2, t3, t4, _this = this;
+ type$.LiveTest._check(liveTest);
+ liveTest.toString;
+ t1 = _this._stopwatch;
+ t2 = t1._stop != null;
+ if (t2)
+ if (t2) {
+ t2 = t1._core$_start;
+ t3 = H._checkIntNullable($.Primitives_timerTicks.call$0());
+ t4 = t1._stop;
+ if (typeof t3 !== "number")
+ return t3.$sub();
+ if (typeof t4 !== "number")
+ return H.iae(t4);
+ t1._core$_start = t2 + (t3 - t4);
+ t1._stop = null;
+ }
+ t1 = _this._engine._active;
+ if (t1.get$length(t1) === 1)
+ _this._progressLine$1(_this._description$1(liveTest));
+ t1 = liveTest._live_test_controller$_controller._onStateChangeController;
+ _this._subscriptions.add$1(0, new P._BroadcastStream(t1, H._instanceType(t1)._eval$1("_BroadcastStream<1>")).listen$1(new R.ExpandedReporter__onTestStarted_closure(_this, liveTest)));
+ t1 = _this._subscriptions;
+ t2 = liveTest._live_test_controller$_controller;
+ t3 = t2._onErrorController;
+ t1.add$1(0, new P._BroadcastStream(t3, H._instanceType(t3)._eval$1("_BroadcastStream<1>")).listen$1(new R.ExpandedReporter__onTestStarted_closure0(_this, liveTest)));
+ t2 = t2._onMessageController;
+ t1.add$1(0, new P._BroadcastStream(t2, H._instanceType(t2)._eval$1("_BroadcastStream<1>")).listen$1(new R.ExpandedReporter__onTestStarted_closure1(_this, liveTest)));
+ },
+ _onStateChange$2: function(liveTest, state) {
+ var t1, t2, t3;
+ if (state.status !== C.Status_complete)
+ return;
+ t1 = this._engine._active;
+ t2 = type$.UnmodifiableListView_LiveTest;
+ t3 = new P.UnmodifiableListView(t1, t2);
+ if (t3.get$length(t3) !== 0) {
+ t1 = new P.UnmodifiableListView(t1, t2);
+ this._progressLine$1(this._description$1(t1.get$first(t1)));
+ }
+ },
+ _expanded$_onError$3: function(liveTest, error, stackTrace) {
+ var t1, _this = this;
+ if (liveTest._live_test_controller$_controller._live_test_controller$_state.status !== C.Status_complete)
+ return;
+ _this._progressLine$2$suffix(_this._description$1(liveTest), " " + _this._bold + _this._red + "[E]" + _this._noColor);
+ t1 = _this._expanded$_sink;
+ t1.writeln$1(B.indent(H.S(error), null));
+ t1.writeln$1(B.indent(H.S(stackTrace), null));
+ return;
+ },
+ _expanded$_onDone$1: function(success) {
+ var t1, t2, t3, t4, _this = this;
+ H._checkBoolNullable(success);
+ if (success == null)
+ return;
+ t1 = _this._engine;
+ t2 = t1.get$liveTests();
+ if (t2.get$length(t2) === 0)
+ _this._expanded$_sink.writeln$1("No tests ran.");
+ else if (!success) {
+ for (t2 = type$.UnmodifiableListView_LiveTest, t1 = new P.UnmodifiableListView(t1._active, t2), t2 = new H.ListIterator(t1, t1.get$length(t1), t2._eval$1("ListIterator<ListMixin.E>")), t1 = _this._bold, t3 = _this._red, t4 = _this._noColor; t2.moveNext$0();)
+ _this._progressLine$2$suffix(_this._description$1(t2.__internal$_current), " - did not complete " + t1 + t3 + "[E]" + t4);
+ _this._progressLine$2$color("Some tests failed.", t3);
+ } else {
+ t1 = t1._passedGroup._union_set_controller$_set;
+ if (t1.get$length(t1) === 0)
+ _this._progressLine$1("All tests skipped.");
+ else
+ _this._progressLine$1("All tests passed!");
+ }
+ },
+ _progressLine$3$color$suffix: function(message, color, suffix) {
+ var t4, t5, _this = this,
+ t1 = _this._engine,
+ t2 = t1._passedGroup,
+ t3 = t2._union_set_controller$_set;
+ if (t3.get$length(t3) == _this._lastProgressPassed) {
+ t3 = t1._skippedGroup._union_set_controller$_set;
+ if (t3.get$length(t3) == _this._lastProgressSkipped) {
+ t3 = t1._failedGroup._union_set_controller$_set;
+ if (t3.get$length(t3) == _this._lastProgressFailed)
+ if (message == _this._lastProgressMessage)
+ t3 = suffix == null || suffix === _this._lastProgressSuffix;
+ else
+ t3 = false;
+ else
+ t3 = false;
+ } else
+ t3 = false;
+ } else
+ t3 = false;
+ if (t3)
+ return;
+ t3 = t2._union_set_controller$_set;
+ _this._lastProgressPassed = t3.get$length(t3);
+ t3 = t1._skippedGroup;
+ t4 = t3._union_set_controller$_set;
+ _this._lastProgressSkipped = t4.get$length(t4);
+ t1 = t1._failedGroup;
+ t4 = t1._union_set_controller$_set;
+ _this._lastProgressFailed = t4.get$length(t4);
+ _this._lastProgressMessage = message;
+ _this._lastProgressSuffix = suffix;
+ if (suffix != null)
+ message = J.$add$ansx(message, suffix);
+ if (color == null)
+ color = "";
+ t4 = P.Duration$(_this._stopwatch.get$elapsedMicroseconds(), 0)._duration;
+ t4 = C.JSString_methods.padLeft$2(C.JSInt_methods.toString$0(C.JSInt_methods._tdivFast$1(t4, 60000000)), 2, "0") + ":" + C.JSString_methods.padLeft$2(C.JSInt_methods.toString$0(C.JSInt_methods.$mod(C.JSInt_methods._tdivFast$1(t4, 1000000), 60)), 2, "0") + " " + _this._green + "+";
+ t2 = t2._union_set_controller$_set;
+ t5 = _this._noColor;
+ t2 = t4 + H.S(t2.get$length(t2)) + t5;
+ t4 = t3._union_set_controller$_set;
+ if (t4.get$length(t4) !== 0) {
+ t2 = t2 + _this._yellow + " ~";
+ t3 = t3._union_set_controller$_set;
+ t3 = t2 + H.S(t3.get$length(t3)) + t5;
+ t2 = t3;
+ }
+ t3 = t1._union_set_controller$_set;
+ if (t3.get$length(t3) !== 0) {
+ t2 = t2 + _this._red + " -";
+ t1 = t1._union_set_controller$_set;
+ t1 = t2 + H.S(t1.get$length(t1)) + t5;
+ } else
+ t1 = t2;
+ t5 = t1 + ": " + color + H.S(message) + t5;
+ _this._expanded$_sink.writeln$1(t5.charCodeAt(0) == 0 ? t5 : t5);
+ },
+ _progressLine$2$suffix: function(message, suffix) {
+ return this._progressLine$3$color$suffix(message, null, suffix);
+ },
+ _progressLine$2$color: function(message, color) {
+ return this._progressLine$3$color$suffix(message, color, null);
+ },
+ _progressLine$1: function(message) {
+ return this._progressLine$3$color$suffix(message, null, null);
+ },
+ _description$1: function(liveTest) {
+ var t1 = liveTest._live_test_controller$_controller;
+ return t1._test.name;
+ }
+ };
+ R.ExpandedReporter__onTestStarted_closure.prototype = {
+ call$1: function(state) {
+ return this.$this._onStateChange$2(this.liveTest, type$.State._check(state));
+ },
+ $signature: 100
+ };
+ R.ExpandedReporter__onTestStarted_closure0.prototype = {
+ call$1: function(error) {
+ type$.AsyncError._check(error);
+ return this.$this._expanded$_onError$3(this.liveTest, error.error, error.stackTrace);
+ },
+ $signature: 101
+ };
+ R.ExpandedReporter__onTestStarted_closure1.prototype = {
+ call$1: function(message) {
+ var t1, text;
+ type$.Message._check(message);
+ t1 = this.$this;
+ t1._progressLine$1(t1._description$1(this.liveTest));
+ text = message.text;
+ if (message.type === C.MessageType_skip)
+ text = " " + t1._yellow + text + t1._noColor;
+ t1._expanded$_sink.writeln$1(text);
+ },
+ $signature: 27
+ };
+ Y.RunnerSuite.prototype = {};
+ Y.RunnerSuiteController.prototype = {
+ _runner_suite$_close$0: function() {
+ return this._closeMemo.runOnce$1(new Y.RunnerSuiteController__close_closure(this));
+ },
+ set$_runner_suite$_suite: function(_suite) {
+ type$.Future_RunnerSuite._check(_suite);
+ }
+ };
+ Y.RunnerSuiteController__close_closure.prototype = {
+ call$0: function() {
+ var $async$goto = 0,
+ $async$completer = P._makeAsyncAwaitCompleter(type$.Null),
+ $async$self = this;
+ var $async$call$0 = P._wrapJsFunctionForAsync(function($async$errorCode, $async$result) {
+ if ($async$errorCode === 1)
+ return P._asyncRethrow($async$result, $async$completer);
+ while (true)
+ switch ($async$goto) {
+ case 0:
+ // Function start
+ $async$goto = 2;
+ return P._asyncAwait($async$self.$this._onDebuggingController.close$0(0), $async$call$0);
+ case 2:
+ // returning from await.
+ // implicit return
+ return P._asyncReturn(null, $async$completer);
+ }
+ });
+ return P._asyncStartSync($async$call$0, $async$completer);
+ },
+ $signature: 2
+ };
+ T.RuntimeSelection.prototype = {};
+ U.SuiteConfiguration.prototype = {};
+ X.PrintSink.prototype = {
+ writeln$1: function(obj) {
+ this._print_sink$_buffer._contents += obj + "\n";
+ this._flush$0();
+ },
+ _flush$0: function() {
+ var t1 = this._print_sink$_buffer;
+ if (C.JSString_methods.endsWith$1(t1.toString$0(0), "\n")) {
+ P.print(t1);
+ t1._contents = "";
+ }
+ },
+ $isStringSink: 1
+ };
+ E.JSStackTraceMapper.prototype = {};
+ E.JSStackTraceMapper__deserializePackageConfigMap_closure.prototype = {
+ call$2: function(key, value) {
+ return new P.MapEntry(H._checkStringNullable(key), P.Uri_parse(H._checkStringNullable(value)), type$.MapEntry_String_Uri);
+ },
+ $signature: 102
+ };
+ R._declarer_closure.prototype = {
+ call$0: function() {
+ var $async$goto = 0,
+ $async$completer = P._makeAsyncAwaitCompleter(type$.Null),
+ $async$returnValue, controller, suite, engine, t1, t2, t3, t4, $async$temp1;
+ var $async$call$0 = P._wrapJsFunctionForAsync(function($async$errorCode, $async$result) {
+ if ($async$errorCode === 1)
+ return P._asyncRethrow($async$result, $async$completer);
+ while (true)
+ switch ($async$goto) {
+ case 0:
+ // Function start
+ t1 = $.$get$SuiteConfiguration_empty();
+ t2 = $._globalDeclarer.build$0();
+ t3 = E.SuitePlatform$(C.Runtime_ql0, false, $.$get$currentOSGuess());
+ t4 = P.Uri_base();
+ t4 = $.$get$context().prettyUri$1(t4);
+ controller = new Y.RunnerSuiteController(t1, null, new P._AsyncBroadcastStreamController(null, null, type$._AsyncBroadcastStreamController_bool), P.LinkedHashSet_LinkedHashSet$_empty(type$.String), new S.AsyncMemoizer(new P._AsyncCompleter(new P._Future($.Zone__current, type$._Future_dynamic), type$._AsyncCompleter_dynamic), type$.AsyncMemoizer_dynamic));
+ suite = new Y.RunnerSuite(controller, t3, t4, U.Suite__filterGroup(t2, t3));
+ t1 = new P._Future($.Zone__current, type$._Future_RunnerSuite);
+ t1._asyncComplete$1(suite);
+ controller.set$_runner_suite$_suite(t1);
+ engine = O.Engine$();
+ t1 = engine._suiteController;
+ t1.add$1(0, H._instanceType(t1)._precomputed1._check(type$.RunnerSuite._check(suite)));
+ t1.close$0(0);
+ if ($.Stopwatch__frequency == null) {
+ H.Primitives_initTicker();
+ $.Stopwatch__frequency = $.Primitives_timerFrequency;
+ }
+ t1 = P.LinkedHashSet_LinkedHashSet$_empty(type$.StreamSubscription_dynamic);
+ t2 = new R.ExpandedReporter(true, "\x1b[32m", "\x1b[31m", "\x1b[33m", "\x1b[1;30m", "\x1b[1m", "\x1b[0m", engine, false, false, new P.Stopwatch(), t1, new X.PrintSink(new P.StringBuffer("")));
+ t3 = engine._onTestStartedGroup._stream_group$_controller;
+ t3.toString;
+ t1.add$1(0, new P._BroadcastStream(t3, H._instanceType(t3)._eval$1("_BroadcastStream<1>")).listen$1(t2.get$_onTestStarted()));
+ t3 = engine.get$success();
+ t3.toString;
+ t1.add$1(0, P.Stream_Stream$fromFuture(t3, t3.$ti._precomputed1).listen$1(t2.get$_expanded$_onDone()));
+ t2 = type$.dynamic;
+ $async$temp1 = H;
+ $async$goto = 3;
+ return P._asyncAwait(P.runZoned(new R._declarer__closure(engine), null, null, P.LinkedHashMap_LinkedHashMap$_literal([C.Symbol_Drw, $._globalDeclarer], t2, t2), type$.Future_bool), $async$call$0);
+ case 3:
+ // returning from await.
+ if ($async$temp1.boolConversionCheck($async$result)) {
+ $async$returnValue = null;
+ // goto return
+ $async$goto = 1;
+ break;
+ }
+ P.print("");
+ P.Future_Future$error("Dummy exception to set exit code.", null, type$.void);
+ case 1:
+ // return
+ return P._asyncReturn($async$returnValue, $async$completer);
+ }
+ });
+ return P._asyncStartSync($async$call$0, $async$completer);
+ },
+ $signature: 2
+ };
+ R._declarer__closure.prototype = {
+ call$0: function() {
+ return U.Invoker_guard(this.engine.get$run(), type$.Future_bool);
+ },
+ $signature: 31
+ };
+ G.main_closure0.prototype = {
+ call$0: function() {
+ R.test("sample test", new G.main__closure());
+ },
+ $signature: 0
+ };
+ G.main__closure.prototype = {
+ call$0: function() {
+ var $async$goto = 0,
+ $async$completer = P._makeAsyncAwaitCompleter(type$.Null),
+ res;
+ var $async$call$0 = P._wrapJsFunctionForAsync(function($async$errorCode, $async$result) {
+ if ($async$errorCode === 1)
+ return P._asyncRethrow($async$result, $async$completer);
+ while (true)
+ switch ($async$goto) {
+ case 0:
+ // Function start
+ res = G.method1();
+ if (res === 0)
+ G.method2();
+ G.expect(true, C.C__IsTrue, null);
+ // implicit return
+ return P._asyncReturn(null, $async$completer);
+ }
+ });
+ return P._asyncStartSync($async$call$0, $async$completer);
+ },
+ $signature: 2
+ };
+ R.main_closure.prototype = {
+ call$0: function() {
+ return G.main_test__main$closure();
+ },
+ $signature: 103
+ };
+ (function aliases() {
+ var _ = J.JavaScriptObject.prototype;
+ _.super$JavaScriptObject$toString = _.toString$0;
+ _ = P._BroadcastStreamController.prototype;
+ _.super$_BroadcastStreamController$_addEventError = _._addEventError$0;
+ _ = P.Iterable.prototype;
+ _.super$Iterable$where = _.where$1;
+ _.super$Iterable$skipWhile = _.skipWhile$1;
+ _ = W.EventTarget.prototype;
+ _.super$EventTarget$addEventListener = _.addEventListener$3;
+ _ = Y.DelegatingStreamSubscription.prototype;
+ _.super$DelegatingStreamSubscription$onError = _.onError$1;
+ _.super$DelegatingStreamSubscription$cancel = _.cancel$0;
+ _ = M.TypeMatcher.prototype;
+ _.super$TypeMatcher$describe = _.describe$1;
+ _.super$TypeMatcher$matches = _.matches$2;
+ _ = Y.SourceSpanMixin.prototype;
+ _.super$SourceSpanMixin$$eq = _.$eq;
+ _ = X.StringScanner.prototype;
+ _.super$StringScanner$matches = _.matches$1;
+ })();
+ (function installTearOffs() {
+ var _instance = hunkHelpers.installInstanceTearOff,
+ _static_0 = hunkHelpers._static_0,
+ _static_1 = hunkHelpers._static_1,
+ _static = hunkHelpers.installStaticTearOff,
+ _instance_0_u = hunkHelpers._instance_0u,
+ _instance_1_i = hunkHelpers._instance_1i,
+ _instance_0_i = hunkHelpers._instance_0i,
+ _instance_1_u = hunkHelpers._instance_1u,
+ _instance_2_u = hunkHelpers._instance_2u;
+ _instance(J.JSString.prototype, "get$startsWith", 1, 1, null, ["call$2", "call$1"], ["startsWith$2", "startsWith$1"], 57, 0);
+ _static_0(H, "_js_helper_Primitives_dateNow$closure", "Primitives_dateNow", 36);
+ _static_1(P, "async__AsyncRun__scheduleImmediateJsOverride$closure", "_AsyncRun__scheduleImmediateJsOverride", 20);
+ _static_1(P, "async__AsyncRun__scheduleImmediateWithSetImmediate$closure", "_AsyncRun__scheduleImmediateWithSetImmediate", 20);
+ _static_1(P, "async__AsyncRun__scheduleImmediateWithTimer$closure", "_AsyncRun__scheduleImmediateWithTimer", 20);
+ _static_1(P, "async_Future__kTrue$closure", "Future__kTrue", 9);
+ _static_0(P, "async___startMicrotaskLoop$closure", "_startMicrotaskLoop", 1);
+ _static_1(P, "async___nullDataHandler$closure", "_nullDataHandler", 11);
+ _static(P, "async___nullErrorHandler$closure", 1, null, ["call$2", "call$1"], ["_nullErrorHandler", function(error) {
+ return P._nullErrorHandler(error, null);
+ }], 12, 0);
+ _static_0(P, "async___nullDoneHandler$closure", "_nullDoneHandler", 1);
+ _static(P, "async___rootHandleUncaughtError$closure", 5, null, ["call$5"], ["_rootHandleUncaughtError"], 105, 0);
+ _static(P, "async___rootRun$closure", 4, null, ["call$1$4", "call$4"], ["_rootRun", function($self, $parent, zone, f) {
+ return P._rootRun($self, $parent, zone, f, type$.dynamic);
+ }], 106, 0);
+ _static(P, "async___rootRunUnary$closure", 5, null, ["call$2$5", "call$5"], ["_rootRunUnary", function($self, $parent, zone, f, arg) {
+ return P._rootRunUnary($self, $parent, zone, f, arg, type$.dynamic, type$.dynamic);
+ }], 107, 0);
+ _static(P, "async___rootRunBinary$closure", 6, null, ["call$3$6"], ["_rootRunBinary"], 108, 0);
+ _static(P, "async___rootRegisterCallback$closure", 4, null, ["call$1$4", "call$4"], ["_rootRegisterCallback", function($self, $parent, zone, f) {
+ return P._rootRegisterCallback($self, $parent, zone, f, type$.dynamic);
+ }], 35, 0);
+ _static(P, "async___rootRegisterUnaryCallback$closure", 4, null, ["call$2$4", "call$4"], ["_rootRegisterUnaryCallback", function($self, $parent, zone, f) {
+ return P._rootRegisterUnaryCallback($self, $parent, zone, f, type$.dynamic, type$.dynamic);
+ }], 33, 0);
+ _static(P, "async___rootRegisterBinaryCallback$closure", 4, null, ["call$3$4", "call$4"], ["_rootRegisterBinaryCallback", function($self, $parent, zone, f) {
+ return P._rootRegisterBinaryCallback($self, $parent, zone, f, type$.dynamic, type$.dynamic, type$.dynamic);
+ }], 109, 0);
+ _static(P, "async___rootErrorCallback$closure", 5, null, ["call$5"], ["_rootErrorCallback"], 32, 0);
+ _static(P, "async___rootScheduleMicrotask$closure", 4, null, ["call$4"], ["_rootScheduleMicrotask"], 110, 0);
+ _static(P, "async___rootCreateTimer$closure", 5, null, ["call$5"], ["_rootCreateTimer"], 111, 0);
+ _static(P, "async___rootCreatePeriodicTimer$closure", 5, null, ["call$5"], ["_rootCreatePeriodicTimer"], 112, 0);
+ _static(P, "async___rootPrint$closure", 4, null, ["call$4"], ["_rootPrint"], 37, 0);
+ _static_1(P, "async___printToZone$closure", "_printToZone", 113);
+ _static(P, "async___rootFork$closure", 5, null, ["call$5"], ["_rootFork"], 114, 0);
+ var _;
+ _instance_0_u(_ = P._BroadcastSubscription.prototype, "get$_onPause", "_onPause$0", 1);
+ _instance_0_u(_, "get$_onResume", "_onResume$0", 1);
+ _instance_1_i(_ = P._BroadcastStreamController.prototype, "get$add", "add$1", 11);
+ _instance(_, "get$addError", 0, 1, null, ["call$2", "call$1"], ["addError$2", "addError$1"], 12, 0);
+ _instance(P._AsyncCompleter.prototype, "get$complete", 0, 0, null, ["call$1", "call$0"], ["complete$1", "complete$0"], 47, 0);
+ _instance(P._SyncCompleter.prototype, "get$complete", 0, 0, null, ["call$1", "call$0"], ["complete$1", "complete$0"], 47, 0);
+ _instance(P._Future.prototype, "get$_completeError", 0, 1, null, ["call$2", "call$1"], ["_completeError$2", "_completeError$1"], 12, 0);
+ _instance_1_i(_ = P._StreamController.prototype, "get$add", "add$1", 11);
+ _instance(_, "get$addError", 0, 1, null, ["call$2", "call$1"], ["addError$2", "addError$1"], 12, 0);
+ _instance_0_i(_, "get$close", "close$0", 14);
+ _instance_1_u(_, "get$_add", "_add$1", 11);
+ _instance_2_u(_, "get$_async$_addError", "_async$_addError$2", 70);
+ _instance_0_u(_, "get$_close", "_close$0", 1);
+ _instance_0_u(_ = P._ControllerSubscription.prototype, "get$_onPause", "_onPause$0", 1);
+ _instance_0_u(_, "get$_onResume", "_onResume$0", 1);
+ _instance_1_i(P._StreamSinkWrapper.prototype, "get$add", "add$1", 11);
+ _instance_0_u(_ = P._BufferingStreamSubscription.prototype, "get$_onPause", "_onPause$0", 1);
+ _instance_0_u(_, "get$_onResume", "_onResume$0", 1);
+ _instance_0_u(P._DoneStreamSubscription.prototype, "get$_sendDone", "_sendDone$0", 1);
+ _instance_1_i(P._LinkedHashSet.prototype, "get$contains", "contains$1", 26);
+ _static_1(P, "core_Uri_decodeComponent$closure", "Uri_decodeComponent", 7);
+ _instance_0_i(S.NullStreamSink.prototype, "get$close", "close$0", 14);
+ _instance_0_u(_ = L.StreamGroup.prototype, "get$_onListen", "_onListen$0", 1);
+ _instance_0_u(_, "get$_onCancelBroadcast", "_onCancelBroadcast$0", 1);
+ _instance_1_i(M._DelegatingIterableBase.prototype, "get$contains", "contains$1", 26);
+ _instance(D._DeepMatcher.prototype, "get$_recursiveMatch", 0, 4, null, ["call$4"], ["_recursiveMatch$4"], 58, 0);
+ _static_1(Z, "pretty_print___escapeString$closure", "_escapeString", 7);
+ _static_1(M, "util___getHexLiteral$closure", "_getHexLiteral", 7);
+ _instance(Y.SourceSpanMixin.prototype, "get$message", 1, 1, null, ["call$2$color", "call$1"], ["message$2$color", "message$1"], 71, 0);
+ _instance(_ = O.StackZoneSpecification.prototype, "get$_stack_zone_specification$_registerCallback", 0, 4, null, ["call$1$4", "call$4"], ["_stack_zone_specification$_registerCallback$1$4", "_stack_zone_specification$_registerCallback$4"], 35, 0);
+ _instance(_, "get$_stack_zone_specification$_registerUnaryCallback", 0, 4, null, ["call$2$4", "call$4"], ["_stack_zone_specification$_registerUnaryCallback$2$4", "_stack_zone_specification$_registerUnaryCallback$4"], 33, 0);
+ _instance(_, "get$_stack_zone_specification$_registerBinaryCallback", 0, 4, null, ["call$3$4", "call$4"], ["_stack_zone_specification$_registerBinaryCallback$3$4", "_stack_zone_specification$_registerBinaryCallback$4"], 63, 0);
+ _instance(_, "get$_stack_zone_specification$_errorCallback", 0, 5, null, ["call$5"], ["_stack_zone_specification$_errorCallback$5"], 32, 0);
+ _instance(_ = K._GuaranteeSink.prototype, "get$addError", 0, 1, null, ["call$2", "call$1"], ["addError$2", "addError$1"], 12, 0);
+ _instance(_, "get$_addError", 0, 1, null, ["call$2", "call$1"], ["_addError$2", "_addError$1"], 66, 0);
+ _instance_0_i(_, "get$close", "close$0", 13);
+ _instance_0_u(D._MultiChannel.prototype, "get$_closeInnerChannel", "_closeInnerChannel$0", 1);
+ _instance_0_u(_ = U.Invoker.prototype, "get$_invoker$_onRun", "_invoker$_onRun$0", 1);
+ _instance_0_u(_, "get$_runTearDowns", "_runTearDowns$0", 13);
+ _instance_0_u(V._LiveTest.prototype, "get$run", "run$0", 13);
+ _instance_1_i(V.LiveTestController.prototype, "get$message", "message$1", 96);
+ _instance_0_u(O.Engine.prototype, "get$run", "run$0", 31);
+ _instance_1_u(_ = R.ExpandedReporter.prototype, "get$_onTestStarted", "_onTestStarted$1", 98);
+ _instance_1_u(_, "get$_expanded$_onDone", "_expanded$_onDone$1", 99);
+ _static_0(G, "main_test__main$closure", "main0", 34);
+ _static(P, "math__max$closure", 2, null, ["call$1$2", "call$2"], ["max", function(a, b) {
+ return P.max(a, b, type$.num);
+ }], 76, 0);
+ })();
+ (function inheritance() {
+ var _mixin = hunkHelpers.mixin,
+ _inherit = hunkHelpers.inherit,
+ _inheritMany = hunkHelpers.inheritMany;
+ _inherit(P.Object, null);
+ _inheritMany(P.Object, [H.JS_CONST, J.Interceptor, J.JSObject, J.ArrayIterator, P.Iterable, H.CastIterator, P.MapMixin, H.Closure, P._ListBase_Object_ListMixin, H.ListIterator, P.Iterator, H.ExpandIterator, H.EmptyIterator, H.FixedLengthListMixin, H.UnmodifiableListMixin, H.Symbol, H.ConstantMap, H.TypeErrorDecoder, P.Error, H.ExceptionAndStackTrace, H._StackTrace, H.LinkedHashMapCell, H.LinkedHashMapKeyIterator, H.JSSyntaxRegExp, H._MatchImplementation, H._AllMatchesIterator, H.StringMatch, H._StringAllMatchesIterator, H.Rti, H._FunctionParameters, H._Type, P._TimerImpl, P._AsyncAwaitCompleter, P.Stream, P._BufferingStreamSubscription, P._BroadcastStreamController, P.Future, P.TimeoutException, P.Completer, P._Completer, P._FutureListener, P._Future, P._AsyncCallbackEntry, P.StreamSubscription, P.StreamTransformerBase, P._StreamController, P._SyncStreamControllerDispatch, P._AsyncStreamControllerDispatch, P._StreamSinkWrapper, P._AddStreamState, P._PendingEvents, P._DelayedEvent, P._DelayedDone, P._DoneStreamSubscription, P._StreamIterator, P.Timer, P.AsyncError, P._ZoneFunction, P.ZoneSpecification, P._ZoneSpecification, P.ZoneDelegate, P.Zone, P._ZoneDelegate, P._Zone, P._HashMapKeyIterator, P._SetBase, P._LinkedHashSetCell, P._LinkedHashSetIterator, P.ListMixin, P._UnmodifiableMapMixin, P.MapView, P._ListQueueIterator, P.SetMixin, P._SetBase_Object_SetMixin, P.Codec, P._Utf8Encoder, P._Utf8Decoder, P.bool, P.DateTime, P.num, P.Duration, P.OutOfMemoryError, P.StackOverflowError, P._Exception, P.FormatException, P.Expando, P.Function, P.List, P.Map, P.MapEntry, P.Null, P.Pattern, P.Match, P.RegExpMatch, P.StackTrace, P._StringStackTrace, P.Stopwatch, P.String, P.RuneIterator, P.StringBuffer, P.Uri, P._Uri, P.UriData, P._SimpleUri, W.EventStreamProvider, P._StructuredClone, P._AcceptStructuredClone, P.ByteBuffer, P.ByteData, P.Int8List, P.Uint8List, P.Uint8ClampedList, P.Int16List, P.Uint16List, P.Int32List, P.Uint32List, P.Float32List, P.Float64List, S.AsyncMemoizer, O.DelegatingSink, Y.DelegatingStreamSubscription, F.FutureGroup, S.NullStreamSink, V.ErrorResult, E.Result, F.ValueResult, Y.StreamCompleter, L.StreamGroup, L._StreamGroupState, G.StreamQueue, G._EventRequest, G._NextRequest, G._RestRequest, T.StreamSinkCompleter, T._CompleterSink, X.BooleanSelector, X.All, U.VariableNode, U.NotNode, U.OrNode, U.AndNode, U.ConditionalNode, T.Evaluator, Y.BooleanSelectorImpl, R.IntersectionSelector, O.None, G.Parser, O.Scanner, L.Token, L.IdentifierToken, L.TokenType, B.RecursiveVisitor, Q._QueueList_Object_ListMixin, Y.UnionSetController, M._DelegatingIterableBase, L.UnmodifiableSetMixin, G.Matcher, E.StringDescription, F.PackageConfigResolver, D.PackageRootResolver, M.Context, M._PathDirection, M._PathRelation, O.Style, X.ParsedPath, X.PathException, O.Pool, O.PoolResource, T.Mapping, T.TargetLineEntry, T.TargetEntry, T._MappingTokenizer, T._TokenKind, Y.SourceSpanMixin, Y.SourceFile, D.SourceLocationMixin, U.Highlighter, V.SourceLocation, V.SourceSpan, G.SourceSpanException, U.Chain, A.Frame, X.LazyChain, T.LazyTrace, O.StackZoneSpecification, O._Node, Y.Trace, N.UnparsedFrame, R.StreamChannelMixin, K._GuaranteeSink, N.StreamChannelCompleter, B.StreamChannelController, R.StreamChannel, X.StringScanner, S._SpanScannerState, A.AsciiGlyphSet, K.UnicodeGlyphSet, K.ClosedException, X.Declarer, O.Group, V.GroupEntry, V.Test, U.Invoker, U._AsyncCounter, Z.LiveTest, V.LiveTestController, D.Message, D.MessageType, O.Metadata, N.OperatingSystem, E.PlatformSelector, B.Runtime, U.StackTraceFormatter, G.State, G.Status, G.Result0, U.Suite, E.SuitePlatform, G.TestFailure, R.Timeout, S.RemoteListener, N.SuiteChannelManager, E.StackTraceMapper, O.Engine, E.LiveSuite, B.LiveSuiteController, R.ExpandedReporter, Y.RunnerSuiteController, T.RuntimeSelection, U.SuiteConfiguration, X.PrintSink]);
+ _inheritMany(J.Interceptor, [J.JSBool, J.JSNull, J.JavaScriptObject, J.JSArray, J.JSNumber, J.JSString, H.NativeByteBuffer, H.NativeTypedData, W.Event, W.Blob, W.DomError, W.DomException, W.EventTarget, W.Location, W.MediaError, W.NavigatorUserMediaError, W.OverconstrainedError, W.PositionError, P.SqlError]);
+ _inheritMany(J.JavaScriptObject, [J.PlainJavaScriptObject, J.UnknownJavaScriptObject, J.JavaScriptFunction]);
+ _inherit(J.JSUnmodifiableArray, J.JSArray);
+ _inheritMany(J.JSNumber, [J.JSInt, J.JSDouble]);
+ _inheritMany(P.Iterable, [H._CastIterableBase, H.EfficientLengthIterable, H.MappedIterable, H.WhereIterable, H.ExpandIterable, H.SkipWhileIterable, H._ConstantMapKeyIterable, P.IterableBase, H._StringAllMatchesIterable, P.Runes]);
+ _inherit(H.CastIterable, H._CastIterableBase);
+ _inherit(H._EfficientLengthCastIterable, H.CastIterable);
+ _inherit(P.MapBase, P.MapMixin);
+ _inheritMany(P.MapBase, [H.CastMap, H.JsLinkedHashMap, P._HashMap, P._JsonMap]);
+ _inheritMany(H.Closure, [H.CastMap_forEach_closure, H.ConstantMap_map_closure, H.Instantiation, H.Primitives_initTicker_closure, H.unwrapException_saveStackTrace, H.TearOffClosure, H.JsLinkedHashMap_values_closure, H.JsLinkedHashMap_addAll_closure, H.initHooks_closure, H.initHooks_closure0, H.initHooks_closure1, P._AsyncRun__initializeScheduleImmediate_internalCallback, P._AsyncRun__initializeScheduleImmediate_closure, P._AsyncRun__scheduleImmediateJsOverride_internalCallback, P._AsyncRun__scheduleImmediateWithSetImmediate_internalCallback, P._TimerImpl_internalCallback, P._TimerImpl$periodic_closure, P._awaitOnObject_closure, P._awaitOnObject_closure0, P._wrapJsFunctionForAsync_closure, P._SyncBroadcastStreamController__sendData_closure, P._SyncBroadcastStreamController__sendError_closure, P._SyncBroadcastStreamController__sendDone_closure, P.Future_Future_closure, P.Future_Future$microtask_closure, P.Future_wait_handleError, P.Future_wait_closure, P.Future_forEach_closure, P.Future_doWhile_closure, P._Future__addListener_closure, P._Future__prependListeners_closure, P._Future__chainForeignFuture_closure, P._Future__chainForeignFuture_closure0, P._Future__chainForeignFuture_closure1, P._Future__asyncComplete_closure, P._Future__chainFuture_closure, P._Future__asyncCompleteError_closure, P._Future__propagateToListeners_handleWhenCompleteCallback, P._Future__propagateToListeners_handleWhenCompleteCallback_closure, P._Future__propagateToListeners_handleValueCallback, P._Future__propagateToListeners_handleError, P.Stream_Stream$fromFuture_closure, P.Stream_Stream$fromFuture_closure0, P.Stream_Stream$fromIterable_closure, P.Stream_pipe_closure, P.Stream_length_closure, P.Stream_length_closure0, P.Stream_first_closure, P.Stream_first_closure0, P.Stream_last_closure, P.Stream_last_closure0, P.Stream_firstWhere_closure, P.Stream_firstWhere__closure, P.Stream_firstWhere__closure0, P.Stream_firstWhere_closure0, P._StreamController__subscribe_closure, P._StreamController__recordCancel_complete, P._AddStreamState_makeErrorHandler_closure, P._AddStreamState_cancel_closure, P._BufferingStreamSubscription__sendError_sendError, P._BufferingStreamSubscription__sendDone_sendDone, P._PendingEvents_schedule_closure, P._cancelAndError_closure, P._cancelAndErrorClosure_closure, P._cancelAndValue_closure, P._CustomZone_bindCallback_closure, P._CustomZone_bindUnaryCallback_closure, P._CustomZone_bindCallbackGuarded_closure, P._CustomZone_bindUnaryCallbackGuarded_closure, P._rootHandleUncaughtError_closure, P._RootZone_bindCallback_closure, P._RootZone_bindCallbackGuarded_closure, P._RootZone_bindUnaryCallbackGuarded_closure, P.runZoned_closure, P.HashMap_HashMap$from_closure, P.LinkedHashMap_LinkedHashMap$from_closure, P.MapBase_mapToString_closure, P.Duration_toString_sixDigits, P.Duration_toString_twoDigits, P.Uri__parseIPv4Address_error, P.Uri_parseIPv6Address_error, P.Uri_parseIPv6Address_parseHex, P._Uri__Uri$notSimple_closure, P._Uri__checkNonWindowsPathReservedCharacters_closure, P._Uri__makePath_closure, P._createTables_closure, P._createTables_build, P._createTables_setChars, P._createTables_setRange, W._EventStreamSubscription_closure, P._StructuredClone_walk_closure, P._StructuredClone_walk_closure0, P._AcceptStructuredClone_walk_closure, P._convertDataTree__convert, P.promiseToFuture_closure, P.promiseToFuture_closure0, F.FutureGroup_add_closure, F.FutureGroup_add_closure0, S.NullStreamSink_addStream_closure, L.StreamGroup_add_closure, L.StreamGroup_add_closure0, L.StreamGroup__onListen_closure, L.StreamGroup__onCancelBroadcast_closure, L.StreamGroup__listenToStream_closure, G.StreamQueue__ensureListening_closure, G.StreamQueue__ensureListening_closure1, G.StreamQueue__ensureListening_closure0, T._CompleterSink__setDestinationSink_closure, T._CancelOnErrorSubscriptionWrapper_onError_closure, T._CancelOnErrorSubscriptionWrapper_onError__closure, Y.mapMap_closure, Y.mapMap_closure0, Y.mergeMaps_closure, M.UnionSet_length_closure, M.UnionSet__iterable_closure, D._DeepMatcher__compareSets_closure, Z.prettyPrint__prettyPrint, Z.prettyPrint__prettyPrint_pp, Z.prettyPrint__prettyPrint_closure, Z.prettyPrint__prettyPrint_closure0, Z.prettyPrint__prettyPrint_closure1, M.wrapMatcher_closure, M.escape_closure, F.PackageConfigResolver__normalizeMap_closure, M.Context_join_closure, M.Context_joinAll_closure, M.Context_split_closure, M._validateArgList_closure, X.ParsedPath_normalize_closure, L.WindowsStyle_absolutePathToUri_closure, O.Pool_close_closure, O.Pool__onResourceReleaseAllowed_closure, O.Pool__runOnRelease_closure, O.Pool__runOnRelease_closure0, O.mapStackTrace_closure, O.mapStackTrace_closure0, O.mapStackTrace_closure1, O._prettifyMember_closure, O._prettifyMember_closure0, T.SingleMapping$fromJson_closure, T.SingleMapping__findLine_closure, T.SingleMapping__findColumn_closure, L.closure, U.Highlighter__writeFirstLine_closure, U.Highlighter__writeFirstLine_closure0, U.Highlighter__writeFirstLine_closure1, U.Highlighter__writeFirstLine_closure2, U.Highlighter__writeIntermediateLines_closure, U.Highlighter__writeLastLine_closure, U.Highlighter__writeLastLine_closure0, U.Highlighter__writeLastLine_closure1, U.Highlighter__writeSidebar_closure, U.Chain_capture_closure, U.Chain_Chain$current_closure, U.Chain_Chain$forTrace_closure, U.Chain_Chain$parse_closure, U.Chain_Chain$parse_closure0, U.Chain_foldFrames_closure, U.Chain_foldFrames_closure0, U.Chain_toTrace_closure, U.Chain_toString_closure0, U.Chain_toString__closure0, U.Chain_toString_closure, U.Chain_toString__closure, A.Frame_Frame$parseVM_closure, A.Frame_Frame$parseV8_closure, A.Frame_Frame$parseV8_closure_parseLocation, A.Frame_Frame$parseFirefox_closure, A.Frame_Frame$parseFriendly_closure, X.LazyChain_foldFrames_closure, X.LazyChain_toTrace_closure, T.LazyTrace_foldFrames_closure, O.StackZoneSpecification_chainFor_closure, O.StackZoneSpecification_chainFor_closure0, O.StackZoneSpecification__registerCallback_closure, O.StackZoneSpecification__registerUnaryCallback_closure, O.StackZoneSpecification__registerUnaryCallback__closure, O.StackZoneSpecification__registerBinaryCallback_closure, O.StackZoneSpecification__registerBinaryCallback__closure, O.StackZoneSpecification__currentTrace_closure, Y.Trace_Trace$current_closure, Y.Trace_Trace$from_closure, Y.Trace__parseVM_closure, Y.Trace$parseV8_closure, Y.Trace$parseV8_closure0, Y.Trace$parseJSCore_closure, Y.Trace$parseJSCore_closure0, Y.Trace$parseFirefox_closure, Y.Trace$parseFirefox_closure0, Y.Trace$parseFriendly_closure, Y.Trace$parseFriendly_closure0, Y.Trace_foldFrames_closure, Y.Trace_foldFrames_closure0, Y.Trace_toString_closure0, Y.Trace_toString_closure, K.GuaranteeChannel_closure, K.GuaranteeChannel__closure, K._GuaranteeSink__addError_closure, K._GuaranteeSink_addStream_closure, D._MultiChannel_closure, D._MultiChannel_closure0, D._MultiChannel_closure1, D._MultiChannel__closure, D._MultiChannel_virtualChannel_closure, D._MultiChannel_virtualChannel_closure0, L.internalBootstrapBrowserTest_closure, N.postMessageChannel_closure, N.postMessageChannel_closure0, N.postMessageChannel__closure, N.postMessageChannel__closure0, N.postMessageChannel__closure1, X.Declarer_test_closure, X.Declarer_test__closure, X.Declarer_test___closure, X.Declarer_group_closure, X.Declarer_build_closure, X.Declarer__runSetUps_closure, X.Declarer__tearDownAll_closure, X.Declarer__tearDownAll__closure, X.Declarer__tearDownAll___closure, O.Group_forPlatform_closure, O.Group__map_closure, O.Group__map_closure0, U.Invoker_guard_closure, U.Invoker_guard__closure, U.Invoker_waitForOutstandingCallbacks_closure, U.Invoker_waitForOutstandingCallbacks_closure0, U.Invoker_heartbeat_message, U.Invoker_heartbeat_closure, U.Invoker_heartbeat__closure, U.Invoker__handleError_closure, U.Invoker__handleError_closure0, U.Invoker__onRun_closure, U.Invoker__onRun__closure, U.Invoker__onRun___closure, U.Invoker__onRun____closure, U.Invoker__onRun___closure0, O.Metadata_Metadata__unresolved, O.Metadata_Metadata_closure, O.Metadata$deserialize_closure, O.Metadata__validateTags_closure, O.Metadata__validateTags_closure0, O.Metadata_validatePlatformSelectors_closure, O.Metadata_merge_closure, O.Metadata_merge_closure0, O.Metadata_forPlatform_closure, O.Metadata_serialize_closure, O.Metadata_serialize_closure0, N.OperatingSystem_find_closure, N.OperatingSystem_find_closure0, E.PlatformSelector$parse_closure, E.PlatformSelector_validate_closure, E.PlatformSelector_validate__closure, E.PlatformSelector_evaluate_closure, B.Runtime_Runtime$deserialize_closure, U.StackTraceFormatter_formatStackTrace_closure, G._expect_closure0, G._expect_closure, S.RemoteListener_start_closure, S.RemoteListener_start_closure0, S.RemoteListener_start_closure1, S.RemoteListener_start__closure, S.RemoteListener_start___closure, S.RemoteListener_start____closure, S.RemoteListener_start____closure0, S.RemoteListener_start_____closure, S.RemoteListener_start___closure0, S.RemoteListener__serializeGroup_closure, S.RemoteListener__serializeTest_closure, S.RemoteListener__runLiveTest_closure, S.RemoteListener__runLiveTest_closure0, S.RemoteListener__runLiveTest_closure1, S.RemoteListener__runLiveTest_closure2, S.RemoteListener__runLiveTest_closure3, S.RemoteListener__runLiveTest__closure, V.errorsDontStopTest_closure, V.errorsDontStopTest_closure0, B.closure0, O.Engine_success_closure, O.Engine_closure, O.Engine_closure0, O.Engine_run_closure, O.Engine_run__closure, O.Engine_run___closure, O.Engine_run____closure, O.Engine_run_closure0, O.Engine__runLiveTest_closure, O.Engine__runLiveTest_closure0, O.Engine__runLiveTest_closure1, O.Engine__runSkippedTest_closure, O.Engine__runSkippedTest_closure0, O.Engine__runSkippedTest_closure1, B.LiveSuiteController_closure, B.LiveSuiteController_closure0, B.LiveSuiteController_reportLiveTest_closure, B.LiveSuiteController_close_closure, R.ExpandedReporter__onTestStarted_closure, R.ExpandedReporter__onTestStarted_closure0, R.ExpandedReporter__onTestStarted_closure1, Y.RunnerSuiteController__close_closure, E.JSStackTraceMapper__deserializePackageConfigMap_closure, R._declarer_closure, R._declarer__closure, G.main_closure0, G.main__closure, R.main_closure]);
+ _inherit(P.ListBase, P._ListBase_Object_ListMixin);
+ _inherit(H.UnmodifiableListBase, P.ListBase);
+ _inheritMany(H.UnmodifiableListBase, [H.CodeUnits, P.UnmodifiableListView]);
+ _inheritMany(H.EfficientLengthIterable, [H.ListIterable, H.LinkedHashMapKeyIterable, P._HashMapKeyIterable, P.Set]);
+ _inheritMany(H.ListIterable, [H.SubListIterable, H.MappedListIterable, H.ReversedListIterable, P.ListQueue, P._JsonMapKeyIterable]);
+ _inherit(H.EfficientLengthMappedIterable, H.MappedIterable);
+ _inheritMany(P.Iterator, [H.MappedIterator, H.WhereIterator, H.SkipWhileIterator]);
+ _inherit(H.ConstantStringMap, H.ConstantMap);
+ _inherit(H.Instantiation1, H.Instantiation);
+ _inheritMany(P.Error, [H.NullError, H.JsNoSuchMethodError, H.UnknownJsTypeError, H.TypeErrorImplementation, H.RuntimeError, P.AssertionError, H._Error, P.NullThrownError, P.ArgumentError, P.UnsupportedError, P.UnimplementedError, P.StateError, P.ConcurrentModificationError, P.CyclicInitializationError]);
+ _inheritMany(H.TearOffClosure, [H.StaticClosure, H.BoundClosure]);
+ _inherit(H._AssertionError, P.AssertionError);
+ _inheritMany(P.IterableBase, [H._AllMatchesIterable, O.EmptyUnmodifiableSet]);
+ _inheritMany(H.NativeTypedData, [H.NativeByteData, H.NativeTypedArray]);
+ _inheritMany(H.NativeTypedArray, [H._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin, H._NativeTypedArrayOfInt_NativeTypedArray_ListMixin]);
+ _inherit(H._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin_FixedLengthListMixin, H._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin);
+ _inherit(H.NativeTypedArrayOfDouble, H._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin_FixedLengthListMixin);
+ _inherit(H._NativeTypedArrayOfInt_NativeTypedArray_ListMixin_FixedLengthListMixin, H._NativeTypedArrayOfInt_NativeTypedArray_ListMixin);
+ _inherit(H.NativeTypedArrayOfInt, H._NativeTypedArrayOfInt_NativeTypedArray_ListMixin_FixedLengthListMixin);
+ _inheritMany(H.NativeTypedArrayOfDouble, [H.NativeFloat32List, H.NativeFloat64List]);
+ _inheritMany(H.NativeTypedArrayOfInt, [H.NativeInt16List, H.NativeInt32List, H.NativeInt8List, H.NativeUint16List, H.NativeUint32List, H.NativeUint8ClampedList, H.NativeUint8List]);
+ _inheritMany(H._Error, [H._CastError, H._TypeError]);
+ _inheritMany(P.Stream, [P._StreamImpl, P._EmptyStream, W._EventStream, Y._CompleterStream, T.SubscriptionStream]);
+ _inheritMany(P._StreamImpl, [P._ControllerStream, P._GeneratedStreamImpl]);
+ _inherit(P._BroadcastStream, P._ControllerStream);
+ _inherit(P._ControllerSubscription, P._BufferingStreamSubscription);
+ _inherit(P._BroadcastSubscription, P._ControllerSubscription);
+ _inheritMany(P._BroadcastStreamController, [P._SyncBroadcastStreamController, P._AsyncBroadcastStreamController]);
+ _inheritMany(P._Completer, [P._AsyncCompleter, P._SyncCompleter]);
+ _inheritMany(P._StreamController, [P._AsyncStreamController, P._SyncStreamController]);
+ _inherit(P._StreamControllerAddStreamState, P._AddStreamState);
+ _inheritMany(P._PendingEvents, [P._IterablePendingEvents, P._StreamImplEvents]);
+ _inheritMany(P._DelayedEvent, [P._DelayedData, P._DelayedError]);
+ _inheritMany(P._Zone, [P._CustomZone, P._RootZone]);
+ _inherit(P._IdentityHashMap, P._HashMap);
+ _inherit(P._LinkedHashSet, P._SetBase);
+ _inherit(P._UnmodifiableMapView_MapView__UnmodifiableMapMixin, P.MapView);
+ _inherit(P.UnmodifiableMapView, P._UnmodifiableMapView_MapView__UnmodifiableMapMixin);
+ _inherit(P.SetBase, P._SetBase_Object_SetMixin);
+ _inheritMany(P.Codec, [P.Encoding, P.Base64Codec, P._FusedCodec, P.JsonCodec]);
+ _inheritMany(P.Encoding, [P.AsciiCodec, P.Utf8Codec]);
+ _inherit(P.Converter, P.StreamTransformerBase);
+ _inheritMany(P.Converter, [P._UnicodeSubsetEncoder, P.Base64Encoder, P.JsonDecoder, P.Utf8Encoder, P.Utf8Decoder]);
+ _inherit(P.AsciiEncoder, P._UnicodeSubsetEncoder);
+ _inheritMany(P.num, [P.double, P.int]);
+ _inheritMany(P.ArgumentError, [P.RangeError, P.IndexError]);
+ _inherit(P._DataUri, P._Uri);
+ _inheritMany(W.Event, [W.ApplicationCacheErrorEvent, W.ErrorEvent, W.MediaKeyMessageEvent, W.MessageEvent, W.PresentationConnectionCloseEvent, W.SpeechRecognitionError]);
+ _inherit(W.File, W.Blob);
+ _inherit(W.MessagePort, W.EventTarget);
+ _inherit(W._EventStreamSubscription, P.StreamSubscription);
+ _inherit(P._StructuredCloneDart2Js, P._StructuredClone);
+ _inherit(P._AcceptStructuredCloneDart2Js, P._AcceptStructuredClone);
+ _inherit(T._CancelOnErrorSubscriptionWrapper, Y.DelegatingStreamSubscription);
+ _inherit(S.Validator, B.RecursiveVisitor);
+ _inherit(Q.QueueList, Q._QueueList_Object_ListMixin);
+ _inherit(M._UnionSet_SetBase_UnmodifiableSetMixin, P.SetBase);
+ _inherit(M.UnionSet, M._UnionSet_SetBase_UnmodifiableSetMixin);
+ _inherit(M.DelegatingIterable, M._DelegatingIterableBase);
+ _inherit(M.DelegatingSet, M.DelegatingIterable);
+ _inherit(L._UnmodifiableSetView_DelegatingSet_UnmodifiableSetMixin, M.DelegatingSet);
+ _inherit(L.UnmodifiableSetView, L._UnmodifiableSetView_DelegatingSet_UnmodifiableSetMixin);
+ _inheritMany(G.Matcher, [Y._IsTrue, M.TypeMatcher, D._DeepMatcher]);
+ _inherit(E.FeatureMatcher, M.TypeMatcher);
+ _inheritMany(E.FeatureMatcher, [Y._Predicate, D._StringEqualsMatcher]);
+ _inherit(B.InternalStyle, O.Style);
+ _inheritMany(B.InternalStyle, [E.PosixStyle, F.UrlStyle, L.WindowsStyle]);
+ _inheritMany(T.Mapping, [T.MultiSectionMapping, T.MappingBundle, T.SingleMapping]);
+ _inheritMany(Y.SourceSpanMixin, [V.SourceSpanBase, Y._FileSpan]);
+ _inheritMany(V.SourceSpanBase, [G.SourceMapSpan, X.SourceSpanWithContext]);
+ _inherit(Y.FileLocation, D.SourceLocationMixin);
+ _inherit(G.SourceSpanFormatException, G.SourceSpanException);
+ _inheritMany(R.StreamChannelMixin, [K.GuaranteeChannel, D._MultiChannel, D.VirtualChannel, R._StreamChannel]);
+ _inherit(E.StringScannerException, G.SourceSpanFormatException);
+ _inherit(S.SpanScanner, X.StringScanner);
+ _inherit(U.LocalTest, V.Test);
+ _inherit(V._LiveTest, Z.LiveTest);
+ _inherit(O._IterableSet_SetMixin_UnmodifiableSetMixin, P.SetMixin);
+ _inherit(O.IterableSet, O._IterableSet_SetMixin_UnmodifiableSetMixin);
+ _inherit(B._LiveSuite, E.LiveSuite);
+ _inherit(Y.RunnerSuite, U.Suite);
+ _inherit(E.JSStackTraceMapper, E.StackTraceMapper);
+ _mixin(H.UnmodifiableListBase, H.UnmodifiableListMixin);
+ _mixin(H._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin, P.ListMixin);
+ _mixin(H._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin_FixedLengthListMixin, H.FixedLengthListMixin);
+ _mixin(H._NativeTypedArrayOfInt_NativeTypedArray_ListMixin, P.ListMixin);
+ _mixin(H._NativeTypedArrayOfInt_NativeTypedArray_ListMixin_FixedLengthListMixin, H.FixedLengthListMixin);
+ _mixin(P._AsyncStreamController, P._AsyncStreamControllerDispatch);
+ _mixin(P._SyncStreamController, P._SyncStreamControllerDispatch);
+ _mixin(P._ListBase_Object_ListMixin, P.ListMixin);
+ _mixin(P._SetBase_Object_SetMixin, P.SetMixin);
+ _mixin(P._UnmodifiableMapView_MapView__UnmodifiableMapMixin, P._UnmodifiableMapMixin);
+ _mixin(Q._QueueList_Object_ListMixin, P.ListMixin);
+ _mixin(M._UnionSet_SetBase_UnmodifiableSetMixin, L.UnmodifiableSetMixin);
+ _mixin(L._UnmodifiableSetView_DelegatingSet_UnmodifiableSetMixin, L.UnmodifiableSetMixin);
+ _mixin(O._IterableSet_SetMixin_UnmodifiableSetMixin, L.UnmodifiableSetMixin);
+ })();
+ var init = {typeUniverse: {eC: new Map(), tR: {}, eT: {}, tPV: {}, sEA: []}, mangledGlobalNames: {int: "int", double: "double", num: "num", String: "String", bool: "bool", Null: "Null", List: "List"}, mangledNames: {}, getTypeFromName: getGlobalFromName, metadata: [], types: ["Null()", "~()", "Future<Null>()", "Null(@)", "bool(String)", "Null(@,StackTrace)", "Null(@,@)", "String(String)", "Trace()", "bool(@)", "Frame(String)", "~(Object)", "~(Object[StackTrace])", "Future<~>()", "Future<@>()", "~(@)", "Chain()", "Frame()", "@(@)", "Null(PlatformSelector,Metadata)", "~(~())", "bool(Frame)", "GroupEntry(GroupEntry)", "Null(State)", "String(Match)", "String(int)", "bool(Object)", "Null(Message)", "Null(Zone,ZoneDelegate,Zone,Object,StackTrace)", "Null(MessageEvent)", "Null(String)", "Future<bool>()", "AsyncError(Zone,ZoneDelegate,Zone,Object,StackTrace)", "0^(1^)(Zone,ZoneDelegate,Zone,0^(1^))<Object,Object>", "@()", "0^()(Zone,ZoneDelegate,Zone,0^())<Object>", "int()", "~(Zone,ZoneDelegate,Zone,String)", "String(Frame)", "int(Frame)", "String(@)", "~(Uint8List,String,int)", "Trace(String)", "Null(bool)", "Metadata(Metadata,Metadata)", "Trace(Trace)", "Frame(Frame)", "~([Object])", "Null(List<@>)", "bool()", "Future<List<@>>()", "Uri(@,@)", "bool(Trace)", "List<Frame>(Trace)", "int(Trace)", "String(@,int,Set<@>,bool)", "String(Trace)", "bool(Pattern[int])", "List<String>(Object,Object,String,int)", "Frame(@,@)", "@(@,@)", "@(Event)", "Uint8List(@,@)", "0^(1^,2^)(Zone,ZoneDelegate,Zone,Function)<Object,Object,Object>", "Uint8List(int)", "int(int,int)", "~(@[StackTrace])", "~(String[@])", "~(String,int)", "bool(MessageEvent)", "~(Object,StackTrace)", "String(String{color:@})", "bool(GroupEntry)", "Future<@>(@)", "String()", "_Future<@>(@)", "0^(0^,0^)<num>", "Metadata()", "Metadata(Metadata,BooleanSelector)", "MapEntry<BooleanSelector,Metadata>(@,@)", "Null(@[StackTrace])", "bool/()", "MapEntry<String,Map<String,@>>(BooleanSelector,Metadata)", "bool(OperatingSystem)", "BooleanSelector()", "bool(Runtime)", "String(@,Matcher,String,Map<@,@>,bool)", "Null(Zone,ZoneDelegate,Zone,String)", "Map<@,@>(GroupEntry)", "Null(int,@)", "Null(AsyncError)", "Null(~())", "~(~)", "Map<String,int>()", "@(String)", "bool(LiveTest)", "~(Message)", "Null(RunnerSuite)", "~(LiveTest)", "~(bool)", "~(State)", "~(AsyncError)", "MapEntry<String,Uri>(String,String)", "@()()", "@(@,String)", "~(Zone,ZoneDelegate,Zone,@,StackTrace)", "0^(Zone,ZoneDelegate,Zone,0^())<Object>", "0^(Zone,ZoneDelegate,Zone,0^(1^),1^)<Object,Object>", "0^(Zone,ZoneDelegate,Zone,0^(1^,2^),1^,2^)<Object,Object,Object>", "0^(1^,2^)(Zone,ZoneDelegate,Zone,0^(1^,2^))<Object,Object,Object>", "~(Zone,ZoneDelegate,Zone,~())", "Timer(Zone,ZoneDelegate,Zone,Duration,~())", "Timer(Zone,ZoneDelegate,Zone,Duration,~(Timer))", "~(String)", "Zone(Zone,ZoneDelegate,Zone,ZoneSpecification,Map<@,@>)", "OperatingSystem()"], interceptorsByTag: null, leafTags: null};
+ H._Universe_addRules(init.typeUniverse, JSON.parse('{"JavaScriptFunction":"JavaScriptObject","PlainJavaScriptObject":"JavaScriptObject","UnknownJavaScriptObject":"JavaScriptObject","AbortPaymentEvent":"Event","ExtendableEvent":"Event","Window":"EventTarget","JSBool":{"bool":[]},"JSNull":{"Null":[]},"JavaScriptObject":{"JSObject":[],"Function":[]},"JSArray":{"List":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"]},"JSUnmodifiableArray":{"JSArray":["1"],"List":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"]},"ArrayIterator":{"Iterator":["1"]},"JSNumber":{"double":[],"num":[]},"JSInt":{"int":[],"double":[],"num":[]},"JSDouble":{"double":[],"num":[]},"JSString":{"String":[],"Pattern":[]},"_CastIterableBase":{"Iterable":["2"]},"CastIterator":{"Iterator":["2"]},"CastIterable":{"_CastIterableBase":["1","2"],"Iterable":["2"],"Iterable.E":"2"},"_EfficientLengthCastIterable":{"CastIterable":["1","2"],"EfficientLengthIterable":["2"],"_CastIterableBase":["1","2"],"Iterable":["2"],"Iterable.E":"2"},"CastMap":{"MapMixin":["3","4"],"Map":["3","4"],"MapMixin.K":"3","MapMixin.V":"4"},"CodeUnits":{"UnmodifiableListMixin":["int"],"ListMixin":["int"],"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"],"ListMixin.E":"int","UnmodifiableListMixin.E":"int"},"EfficientLengthIterable":{"Iterable":["1"]},"ListIterable":{"EfficientLengthIterable":["1"],"Iterable":["1"]},"SubListIterable":{"ListIterable":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"],"Iterable.E":"1","ListIterable.E":"1"},"ListIterator":{"Iterator":["1"]},"MappedIterable":{"Iterable":["2"],"Iterable.E":"2"},"EfficientLengthMappedIterable":{"MappedIterable":["1","2"],"EfficientLengthIterable":["2"],"Iterable":["2"],"Iterable.E":"2"},"MappedIterator":{"Iterator":["2"]},"MappedListIterable":{"ListIterable":["2"],"EfficientLengthIterable":["2"],"Iterable":["2"],"Iterable.E":"2","ListIterable.E":"2"},"WhereIterable":{"Iterable":["1"],"Iterable.E":"1"},"WhereIterator":{"Iterator":["1"]},"ExpandIterable":{"Iterable":["2"],"Iterable.E":"2"},"ExpandIterator":{"Iterator":["2"]},"SkipWhileIterable":{"Iterable":["1"],"Iterable.E":"1"},"SkipWhileIterator":{"Iterator":["1"]},"EmptyIterator":{"Iterator":["1"]},"UnmodifiableListBase":{"UnmodifiableListMixin":["1"],"ListMixin":["1"],"List":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"]},"ReversedListIterable":{"ListIterable":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"],"Iterable.E":"1","ListIterable.E":"1"},"ConstantMap":{"Map":["1","2"]},"ConstantStringMap":{"ConstantMap":["1","2"],"Map":["1","2"]},"_ConstantMapKeyIterable":{"Iterable":["1"],"Iterable.E":"1"},"Instantiation":{"Closure":[],"Function":[]},"Instantiation1":{"Closure":[],"Function":[]},"NullError":{"NoSuchMethodError":[],"Error":[]},"JsNoSuchMethodError":{"NoSuchMethodError":[],"Error":[]},"UnknownJsTypeError":{"Error":[]},"_StackTrace":{"StackTrace":[]},"Closure":{"Function":[]},"TearOffClosure":{"Closure":[],"Function":[]},"StaticClosure":{"Closure":[],"Function":[]},"BoundClosure":{"Closure":[],"Function":[]},"TypeErrorImplementation":{"Error":[]},"RuntimeError":{"Error":[]},"_AssertionError":{"Error":[]},"JsLinkedHashMap":{"LinkedHashMap":["1","2"],"MapMixin":["1","2"],"Map":["1","2"],"MapMixin.K":"1","MapMixin.V":"2"},"LinkedHashMapKeyIterable":{"EfficientLengthIterable":["1"],"Iterable":["1"],"Iterable.E":"1"},"LinkedHashMapKeyIterator":{"Iterator":["1"]},"JSSyntaxRegExp":{"RegExp":[],"Pattern":[]},"_MatchImplementation":{"RegExpMatch":[],"Match":[]},"_AllMatchesIterable":{"Iterable":["RegExpMatch"],"Iterable.E":"RegExpMatch"},"_AllMatchesIterator":{"Iterator":["RegExpMatch"]},"StringMatch":{"Match":[]},"_StringAllMatchesIterable":{"Iterable":["Match"],"Iterable.E":"Match"},"_StringAllMatchesIterator":{"Iterator":["Match"]},"NativeByteBuffer":{"ByteBuffer":[]},"NativeByteData":{"NativeTypedData":[]},"NativeTypedArray":{"JavaScriptIndexingBehavior":["@"],"NativeTypedData":[]},"NativeTypedArrayOfDouble":{"ListMixin":["double"],"JavaScriptIndexingBehavior":["@"],"List":["double"],"NativeTypedData":[],"EfficientLengthIterable":["double"],"FixedLengthListMixin":["double"],"Iterable":["double"]},"NativeTypedArrayOfInt":{"ListMixin":["int"],"List":["int"],"JavaScriptIndexingBehavior":["@"],"NativeTypedData":[],"EfficientLengthIterable":["int"],"FixedLengthListMixin":["int"],"Iterable":["int"]},"NativeFloat32List":{"ListMixin":["double"],"JavaScriptIndexingBehavior":["@"],"List":["double"],"NativeTypedData":[],"EfficientLengthIterable":["double"],"FixedLengthListMixin":["double"],"Iterable":["double"],"ListMixin.E":"double"},"NativeFloat64List":{"ListMixin":["double"],"JavaScriptIndexingBehavior":["@"],"List":["double"],"NativeTypedData":[],"EfficientLengthIterable":["double"],"FixedLengthListMixin":["double"],"Iterable":["double"],"ListMixin.E":"double"},"NativeInt16List":{"ListMixin":["int"],"List":["int"],"JavaScriptIndexingBehavior":["@"],"NativeTypedData":[],"EfficientLengthIterable":["int"],"FixedLengthListMixin":["int"],"Iterable":["int"],"ListMixin.E":"int"},"NativeInt32List":{"ListMixin":["int"],"List":["int"],"JavaScriptIndexingBehavior":["@"],"NativeTypedData":[],"EfficientLengthIterable":["int"],"FixedLengthListMixin":["int"],"Iterable":["int"],"ListMixin.E":"int"},"NativeInt8List":{"ListMixin":["int"],"List":["int"],"JavaScriptIndexingBehavior":["@"],"NativeTypedData":[],"EfficientLengthIterable":["int"],"FixedLengthListMixin":["int"],"Iterable":["int"],"ListMixin.E":"int"},"NativeUint16List":{"ListMixin":["int"],"List":["int"],"JavaScriptIndexingBehavior":["@"],"NativeTypedData":[],"EfficientLengthIterable":["int"],"FixedLengthListMixin":["int"],"Iterable":["int"],"ListMixin.E":"int"},"NativeUint32List":{"Uint32List":[],"ListMixin":["int"],"List":["int"],"JavaScriptIndexingBehavior":["@"],"NativeTypedData":[],"EfficientLengthIterable":["int"],"FixedLengthListMixin":["int"],"Iterable":["int"],"ListMixin.E":"int"},"NativeUint8ClampedList":{"ListMixin":["int"],"List":["int"],"JavaScriptIndexingBehavior":["@"],"NativeTypedData":[],"EfficientLengthIterable":["int"],"FixedLengthListMixin":["int"],"Iterable":["int"],"ListMixin.E":"int"},"NativeUint8List":{"Uint8List":[],"ListMixin":["int"],"List":["int"],"JavaScriptIndexingBehavior":["@"],"NativeTypedData":[],"EfficientLengthIterable":["int"],"FixedLengthListMixin":["int"],"Iterable":["int"],"ListMixin.E":"int"},"_Type":{"Type":[]},"_Error":{"Error":[]},"_CastError":{"Error":[]},"_TypeError":{"Error":[]},"_TimerImpl":{"Timer":[]},"_AsyncAwaitCompleter":{"Completer":["1"]},"_BroadcastStream":{"_ControllerStream":["1"],"_StreamImpl":["1"],"Stream":["1"],"Stream.T":"1"},"_BroadcastSubscription":{"_ControllerSubscription":["1"],"_BufferingStreamSubscription":["1"],"_EventDispatch":["1"],"StreamSubscription":["1"],"_BufferingStreamSubscription.T":"1"},"_BroadcastStreamController":{"StreamController":["1"],"StreamSink":["1"],"EventSink":["1"],"_EventDispatch":["1"],"_StreamControllerLifecycle":["1"],"StreamConsumer":["1"],"Sink":["1"]},"_SyncBroadcastStreamController":{"_BroadcastStreamController":["1"],"StreamController":["1"],"StreamSink":["1"],"EventSink":["1"],"_EventDispatch":["1"],"_StreamControllerLifecycle":["1"],"StreamConsumer":["1"],"Sink":["1"]},"_AsyncBroadcastStreamController":{"_BroadcastStreamController":["1"],"StreamController":["1"],"StreamSink":["1"],"EventSink":["1"],"_EventDispatch":["1"],"_StreamControllerLifecycle":["1"],"StreamConsumer":["1"],"Sink":["1"]},"TimeoutException":{"Exception":[]},"_Completer":{"Completer":["1"]},"_AsyncCompleter":{"_Completer":["1"],"Completer":["1"]},"_SyncCompleter":{"_Completer":["1"],"Completer":["1"]},"_Future":{"Future":["1"]},"StreamTransformerBase":{"StreamTransformer":["1","2"]},"_StreamController":{"StreamController":["1"],"StreamSink":["1"],"EventSink":["1"],"_EventDispatch":["1"],"_StreamControllerLifecycle":["1"],"StreamConsumer":["1"],"Sink":["1"]},"_AsyncStreamController":{"_AsyncStreamControllerDispatch":["1"],"_StreamController":["1"],"StreamController":["1"],"StreamSink":["1"],"EventSink":["1"],"_EventDispatch":["1"],"_StreamControllerLifecycle":["1"],"StreamConsumer":["1"],"Sink":["1"]},"_SyncStreamController":{"_SyncStreamControllerDispatch":["1"],"_StreamController":["1"],"StreamController":["1"],"StreamSink":["1"],"EventSink":["1"],"_EventDispatch":["1"],"_StreamControllerLifecycle":["1"],"StreamConsumer":["1"],"Sink":["1"]},"_ControllerStream":{"_StreamImpl":["1"],"Stream":["1"],"Stream.T":"1"},"_ControllerSubscription":{"_BufferingStreamSubscription":["1"],"_EventDispatch":["1"],"StreamSubscription":["1"],"_BufferingStreamSubscription.T":"1"},"_StreamSinkWrapper":{"StreamSink":["1"],"EventSink":["1"],"StreamConsumer":["1"],"Sink":["1"]},"_StreamControllerAddStreamState":{"_AddStreamState":["1"]},"_BufferingStreamSubscription":{"_EventDispatch":["1"],"StreamSubscription":["1"],"_BufferingStreamSubscription.T":"1"},"_StreamImpl":{"Stream":["1"]},"_GeneratedStreamImpl":{"_StreamImpl":["1"],"Stream":["1"],"Stream.T":"1"},"_IterablePendingEvents":{"_PendingEvents":["1"]},"_DelayedData":{"_DelayedEvent":["1"]},"_DelayedError":{"_DelayedEvent":["@"]},"_DelayedDone":{"_DelayedEvent":["@"]},"_StreamImplEvents":{"_PendingEvents":["1"]},"_DoneStreamSubscription":{"StreamSubscription":["1"]},"_EmptyStream":{"Stream":["1"],"Stream.T":"1"},"AsyncError":{"Error":[]},"_ZoneSpecification":{"ZoneSpecification":[]},"_ZoneDelegate":{"ZoneDelegate":[]},"_Zone":{"Zone":[]},"_CustomZone":{"_Zone":[],"Zone":[]},"_RootZone":{"_Zone":[],"Zone":[]},"_HashMap":{"MapMixin":["1","2"],"Map":["1","2"],"MapMixin.K":"1","MapMixin.V":"2"},"_IdentityHashMap":{"_HashMap":["1","2"],"MapMixin":["1","2"],"Map":["1","2"],"MapMixin.K":"1","MapMixin.V":"2"},"_HashMapKeyIterable":{"EfficientLengthIterable":["1"],"Iterable":["1"],"Iterable.E":"1"},"_HashMapKeyIterator":{"Iterator":["1"]},"_LinkedHashSet":{"_SetBase":["1"],"LinkedHashSet":["1"],"Set":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"]},"_LinkedHashSetIterator":{"Iterator":["1"]},"UnmodifiableListView":{"UnmodifiableListMixin":["1"],"ListMixin":["1"],"List":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"],"ListMixin.E":"1","UnmodifiableListMixin.E":"1"},"IterableBase":{"Iterable":["1"]},"ListBase":{"ListMixin":["1"],"List":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"]},"MapBase":{"MapMixin":["1","2"],"Map":["1","2"]},"MapMixin":{"Map":["1","2"]},"MapView":{"Map":["1","2"]},"UnmodifiableMapView":{"_UnmodifiableMapView_MapView__UnmodifiableMapMixin":["1","2"],"MapView":["1","2"],"_UnmodifiableMapMixin":["1","2"],"Map":["1","2"]},"ListQueue":{"ListIterable":["1"],"Queue":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"],"Iterable.E":"1","ListIterable.E":"1"},"_ListQueueIterator":{"Iterator":["1"]},"SetMixin":{"Set":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"]},"SetBase":{"SetMixin":["1"],"Set":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"]},"_SetBase":{"Set":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"]},"_JsonMap":{"MapMixin":["String","@"],"Map":["String","@"],"MapMixin.K":"String","MapMixin.V":"@"},"_JsonMapKeyIterable":{"ListIterable":["String"],"EfficientLengthIterable":["String"],"Iterable":["String"],"Iterable.E":"String","ListIterable.E":"String"},"AsciiCodec":{"Codec":["String","List<int>"],"Codec.S":"String"},"_UnicodeSubsetEncoder":{"Converter":["String","List<int>"],"StreamTransformer":["String","List<int>"]},"AsciiEncoder":{"Converter":["String","List<int>"],"StreamTransformer":["String","List<int>"]},"Base64Codec":{"Codec":["List<int>","String"],"Codec.S":"List<int>"},"Base64Encoder":{"Converter":["List<int>","String"],"StreamTransformer":["List<int>","String"]},"_FusedCodec":{"Codec":["1","3"],"Codec.S":"1"},"Converter":{"StreamTransformer":["1","2"]},"Encoding":{"Codec":["String","List<int>"]},"JsonCodec":{"Codec":["Object","String"],"Codec.S":"Object"},"JsonDecoder":{"Converter":["String","Object"],"StreamTransformer":["String","Object"]},"Utf8Codec":{"Codec":["String","List<int>"],"Codec.S":"String"},"Utf8Encoder":{"Converter":["String","List<int>"],"StreamTransformer":["String","List<int>"]},"Utf8Decoder":{"Converter":["List<int>","String"],"StreamTransformer":["List<int>","String"]},"double":{"num":[]},"AssertionError":{"Error":[]},"NullThrownError":{"Error":[]},"ArgumentError":{"Error":[]},"RangeError":{"Error":[]},"IndexError":{"Error":[]},"UnsupportedError":{"Error":[]},"UnimplementedError":{"Error":[]},"StateError":{"Error":[]},"ConcurrentModificationError":{"Error":[]},"OutOfMemoryError":{"Error":[]},"StackOverflowError":{"Error":[]},"CyclicInitializationError":{"Error":[]},"_Exception":{"Exception":[]},"FormatException":{"Exception":[]},"int":{"num":[]},"List":{"EfficientLengthIterable":["1"],"Iterable":["1"]},"RegExpMatch":{"Match":[]},"Set":{"EfficientLengthIterable":["1"],"Iterable":["1"]},"_StringStackTrace":{"StackTrace":[]},"String":{"Pattern":[]},"Runes":{"Iterable":["int"],"Iterable.E":"int"},"RuneIterator":{"Iterator":["int"]},"StringBuffer":{"StringSink":[]},"_Uri":{"Uri":[]},"_SimpleUri":{"Uri":[]},"_DataUri":{"Uri":[]},"ApplicationCacheErrorEvent":{"Event":[]},"ErrorEvent":{"Event":[]},"File":{"Blob":[]},"MediaKeyMessageEvent":{"Event":[]},"MessageEvent":{"Event":[]},"MessagePort":{"EventTarget":[]},"PresentationConnectionCloseEvent":{"Event":[]},"SpeechRecognitionError":{"Event":[]},"_EventStream":{"Stream":["1"],"Stream.T":"1"},"_EventStreamSubscription":{"StreamSubscription":["1"]},"Int8List":{"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"]},"Uint8List":{"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"]},"Uint8ClampedList":{"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"]},"Int16List":{"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"]},"Uint16List":{"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"]},"Int32List":{"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"]},"Uint32List":{"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"]},"Float32List":{"List":["double"],"EfficientLengthIterable":["double"],"Iterable":["double"]},"Float64List":{"List":["double"],"EfficientLengthIterable":["double"],"Iterable":["double"]},"DelegatingSink":{"Sink":["1"]},"DelegatingStreamSubscription":{"StreamSubscription":["1"]},"FutureGroup":{"Sink":["Future<1>"]},"NullStreamSink":{"StreamSink":["1"],"EventSink":["1"],"StreamConsumer":["1"],"Sink":["1"]},"ErrorResult":{"Result":["Null"]},"ValueResult":{"Result":["1"]},"_CompleterStream":{"Stream":["1"],"Stream.T":"1"},"StreamGroup":{"Sink":["Stream<1>"]},"_NextRequest":{"_EventRequest":["1"]},"_RestRequest":{"_EventRequest":["1"]},"_CompleterSink":{"StreamSink":["1"],"EventSink":["1"],"StreamConsumer":["1"],"Sink":["1"]},"SubscriptionStream":{"Stream":["1"],"Stream.T":"1"},"_CancelOnErrorSubscriptionWrapper":{"DelegatingStreamSubscription":["1"],"StreamSubscription":["1"]},"All":{"BooleanSelector":[]},"VariableNode":{"Node":[]},"NotNode":{"Node":[]},"OrNode":{"Node":[]},"AndNode":{"Node":[]},"ConditionalNode":{"Node":[]},"Evaluator":{"Visitor":["bool"]},"BooleanSelectorImpl":{"BooleanSelector":[]},"IntersectionSelector":{"BooleanSelector":[]},"None":{"BooleanSelector":[]},"IdentifierToken":{"Token":[]},"Validator":{"Visitor":["@"]},"RecursiveVisitor":{"Visitor":["@"]},"EmptyUnmodifiableSet":{"Set":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"],"Iterable.E":"1"},"QueueList":{"ListMixin":["1"],"Queue":["1"],"List":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"],"ListMixin.E":"1"},"UnionSet":{"UnmodifiableSetMixin":["1"],"SetMixin":["1"],"Set":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"]},"UnmodifiableSetView":{"_UnmodifiableSetView_DelegatingSet_UnmodifiableSetMixin":["1"],"DelegatingSet":["1"],"UnmodifiableSetMixin":["1"],"DelegatingIterable":["1"],"Set":["1"],"_DelegatingIterableBase":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"]},"_DelegatingIterableBase":{"Iterable":["1"]},"DelegatingIterable":{"_DelegatingIterableBase":["1"],"Iterable":["1"]},"DelegatingSet":{"DelegatingIterable":["1"],"Set":["1"],"_DelegatingIterableBase":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"]},"_IsTrue":{"Matcher":[]},"_Predicate":{"FeatureMatcher":["1"],"TypeMatcher":["1"],"Matcher":[],"TypeMatcher.T":"1","FeatureMatcher.T":"1"},"StringDescription":{"Description":[]},"_StringEqualsMatcher":{"FeatureMatcher":["String"],"TypeMatcher":["String"],"Matcher":[],"TypeMatcher.T":"String","FeatureMatcher.T":"String"},"_DeepMatcher":{"Matcher":[]},"FeatureMatcher":{"TypeMatcher":["1"],"Matcher":[]},"TypeMatcher":{"Matcher":[],"TypeMatcher.T":"1"},"PackageConfigResolver":{"SyncPackageResolver":[]},"PackageRootResolver":{"SyncPackageResolver":[]},"PathException":{"Exception":[]},"PosixStyle":{"InternalStyle":[]},"UrlStyle":{"InternalStyle":[]},"WindowsStyle":{"InternalStyle":[]},"MultiSectionMapping":{"Mapping":[]},"MappingBundle":{"Mapping":[]},"SingleMapping":{"Mapping":[]},"_MappingTokenizer":{"Iterator":["String"]},"SourceMapSpan":{"SourceSpan":[]},"FileLocation":{"SourceLocation":[]},"_FileSpan":{"FileSpan":[],"SourceSpanWithContext":[],"SourceSpan":[]},"SourceLocationMixin":{"SourceLocation":[]},"SourceSpanBase":{"SourceSpan":[]},"SourceSpanException":{"Exception":[]},"SourceSpanFormatException":{"FormatException":[],"Exception":[]},"SourceSpanMixin":{"SourceSpan":[]},"SourceSpanWithContext":{"SourceSpan":[]},"Chain":{"StackTrace":[]},"LazyChain":{"Chain":[],"StackTrace":[]},"LazyTrace":{"Trace":[],"StackTrace":[]},"Trace":{"StackTrace":[]},"UnparsedFrame":{"Frame":[]},"GuaranteeChannel":{"StreamChannelMixin":["1"],"StreamChannel":["1"]},"_GuaranteeSink":{"StreamSink":["1"],"EventSink":["1"],"StreamConsumer":["1"],"Sink":["1"]},"_MultiChannel":{"StreamChannelMixin":["1"],"MultiChannel":["1"],"StreamChannel":["1"]},"VirtualChannel":{"StreamChannelMixin":["1"],"MultiChannel":["1"],"StreamChannel":["1"]},"_StreamChannel":{"StreamChannelMixin":["1"],"StreamChannel":["1"]},"StreamChannelMixin":{"StreamChannel":["1"]},"StringScannerException":{"FormatException":[],"Exception":[]},"_SpanScannerState":{"LineScannerState":[]},"ClosedException":{"Exception":[]},"Group":{"GroupEntry":[]},"LocalTest":{"Test":[],"GroupEntry":[]},"_LiveTest":{"LiveTest":[]},"Test":{"GroupEntry":[]},"IterableSet":{"SetMixin":["1"],"UnmodifiableSetMixin":["1"],"Set":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"]},"_LiveSuite":{"LiveSuite":[]},"RunnerSuite":{"Suite":[]},"PrintSink":{"StringSink":[]},"JSStackTraceMapper":{"StackTraceMapper":[]}}'));
+ H._Universe_addErasedTypes(init.typeUniverse, JSON.parse('{"UnmodifiableListBase":1,"StreamTransformerBase":2,"IterableBase":1,"ListBase":1,"MapBase":2,"SetBase":1,"_ListBase_Object_ListMixin":1,"_SetBase_Object_SetMixin":1,"_QueueList_Object_ListMixin":1,"_UnionSet_SetBase_UnmodifiableSetMixin":1,"_IterableSet_SetMixin_UnmodifiableSetMixin":1}'));
+ var type$ = (function rtii() {
+ var findType = H.findType;
+ return {
+ A_Function_2_B_and_C_Function_A_and_B_and_C_4_Zone_and_ZoneDelegate_and_Zone_and_A_Function_2_B_and_C: findType("0^(1^,2^)(Zone,ZoneDelegate,Zone,0^(1^,2^))<Object,Object,Object>"),
+ A_Function_A_4_Zone_and_ZoneDelegate_and_Zone_and_A_Function: findType("0^(Zone,ZoneDelegate,Zone,0^())<Object>"),
+ A_Function_A_and_B_5_Zone_and_ZoneDelegate_and_Zone_and_A_Function_B_and_B: findType("0^(Zone,ZoneDelegate,Zone,0^(1^),1^)<Object,Object>"),
+ A_Function_A_and_B_and_C_6_Zone_and_ZoneDelegate_and_Zone_and_A_Function_2_B_and_C_and_B_and_C: findType("0^(Zone,ZoneDelegate,Zone,0^(1^,2^),1^,2^)<Object,Object,Object>"),
+ A_Function_B_Function_A_and_B_4_Zone_and_ZoneDelegate_and_Zone_and_A_Function_B: findType("0^(1^)(Zone,ZoneDelegate,Zone,0^(1^))<Object,Object>"),
+ A_Function_Function_A_4_Zone_and_ZoneDelegate_and_Zone_and_A_Function: findType("0^()(Zone,ZoneDelegate,Zone,0^())<Object>"),
+ AsyncError: findType("AsyncError"),
+ AsyncMemoizer_dynamic: findType("AsyncMemoizer<@>"),
+ Blob: findType("Blob"),
+ BooleanSelector: findType("BooleanSelector"),
+ Chain: findType("Chain"),
+ CodeUnits: findType("CodeUnits"),
+ Completer_PoolResource: findType("Completer<PoolResource>"),
+ Declarer: findType("Declarer"),
+ Duration: findType("Duration"),
+ EfficientLengthIterable_dynamic: findType("EfficientLengthIterable<@>"),
+ Error: findType("Error"),
+ Event: findType("Event"),
+ Exception: findType("Exception"),
+ Expando__Node: findType("Expando<_Node>"),
+ File: findType("File"),
+ FileSpan: findType("FileSpan"),
+ FormatException: findType("FormatException"),
+ Frame: findType("Frame"),
+ Frame_Function_Frame: findType("Frame(Frame)"),
+ Frame_Function_String: findType("Frame(String)"),
+ Function: findType("Function"),
+ FutureGroup_dynamic: findType("FutureGroup<@>"),
+ FutureOr_void_Function: findType("~/()"),
+ Future_Null: findType("Future<Null>"),
+ Future_RunnerSuite: findType("Future<RunnerSuite>"),
+ Future_bool: findType("Future<bool>"),
+ Future_dynamic: findType("Future<@>"),
+ Future_void: findType("Future<~>"),
+ Group: findType("Group"),
+ GroupEntry: findType("GroupEntry"),
+ GroupEntry_Function_GroupEntry: findType("GroupEntry(GroupEntry)"),
+ IdentifierToken: findType("IdentifierToken"),
+ Invoker: findType("Invoker"),
+ IterableSet_LiveTest: findType("IterableSet<LiveTest>"),
+ Iterable_Group: findType("Iterable<Group>"),
+ Iterable_String: findType("Iterable<String>"),
+ Iterable_dynamic: findType("Iterable<@>"),
+ Iterator_Match: findType("Iterator<Match>"),
+ JSArray_AsyncError: findType("JSArray<AsyncError>"),
+ JSArray_Declarer: findType("JSArray<Declarer>"),
+ JSArray_Frame: findType("JSArray<Frame>"),
+ JSArray_Future_dynamic: findType("JSArray<Future<@>>"),
+ JSArray_Group: findType("JSArray<Group>"),
+ JSArray_GroupEntry: findType("JSArray<GroupEntry>"),
+ JSArray_Mapping: findType("JSArray<Mapping>"),
+ JSArray_Set_LiveTest: findType("JSArray<Set<LiveTest>>"),
+ JSArray_SourceFile: findType("JSArray<SourceFile>"),
+ JSArray_String: findType("JSArray<String>"),
+ JSArray_TargetEntry: findType("JSArray<TargetEntry>"),
+ JSArray_TargetLineEntry: findType("JSArray<TargetLineEntry>"),
+ JSArray_Trace: findType("JSArray<Trace>"),
+ JSArray_Zone: findType("JSArray<Zone>"),
+ JSArray_dynamic: findType("JSArray<@>"),
+ JSArray_int: findType("JSArray<int>"),
+ JSArray_of_dynamic_Function: findType("JSArray<@()>"),
+ JSObject: findType("JSObject"),
+ JavaScriptFunction: findType("JavaScriptFunction"),
+ JavaScriptIndexingBehavior_dynamic: findType("JavaScriptIndexingBehavior<@>"),
+ JsLinkedHashMap_of_Stream_LiveTest_and_StreamSubscription_LiveTest: findType("JsLinkedHashMap<Stream<LiveTest>,StreamSubscription<LiveTest>>"),
+ List_Group: findType("List<Group>"),
+ List_Object: findType("List<Object>"),
+ List_String: findType("List<String>"),
+ List_String_Function_dynamic_dynamic_String_int: findType("List<String>(@,@,String,int)"),
+ List_dynamic: findType("List<@>"),
+ List_int: findType("List<int>"),
+ LiveSuite: findType("LiveSuite"),
+ LiveTest: findType("LiveTest"),
+ MapEntry_BooleanSelector_Metadata: findType("MapEntry<BooleanSelector,Metadata>"),
+ MapEntry_String_Uri: findType("MapEntry<String,Uri>"),
+ MapEntry_of_String_and_Map_String_dynamic: findType("MapEntry<String,Map<String,@>>"),
+ Map_BooleanSelector_Metadata: findType("Map<BooleanSelector,Metadata>"),
+ Map_PlatformSelector_Metadata: findType("Map<PlatformSelector,Metadata>"),
+ Map_String_SourceFile: findType("Map<String,SourceFile>"),
+ Map_String_dynamic: findType("Map<String,@>"),
+ Map_dynamic_dynamic: findType("Map<@,@>"),
+ MappedIterable_String_Frame: findType("MappedIterable<String,Frame>"),
+ MappedListIterable_Frame_Frame: findType("MappedListIterable<Frame,Frame>"),
+ MappedListIterable_String_String: findType("MappedListIterable<String,String>"),
+ MappedListIterable_String_Trace: findType("MappedListIterable<String,Trace>"),
+ MappedListIterable_String_dynamic: findType("MappedListIterable<String,@>"),
+ Message: findType("Message"),
+ MessageEvent: findType("MessageEvent"),
+ MessagePort: findType("MessagePort"),
+ Metadata: findType("Metadata"),
+ NativeByteBuffer: findType("NativeByteBuffer"),
+ NativeTypedData: findType("NativeTypedData"),
+ NativeUint8List: findType("NativeUint8List"),
+ NoSuchMethodError: findType("NoSuchMethodError"),
+ Null: findType("Null"),
+ Null_Function: findType("Null()"),
+ Object: findType("Object"),
+ OperatingSystem: findType("OperatingSystem"),
+ Pattern: findType("Pattern"),
+ PlatformSelector: findType("PlatformSelector"),
+ RegExp: findType("RegExp"),
+ Result_dynamic: findType("Result<@>"),
+ ReversedListIterable_Declarer: findType("ReversedListIterable<Declarer>"),
+ RunnerSuite: findType("RunnerSuite"),
+ Runtime: findType("Runtime"),
+ Set_LiveTest: findType("Set<LiveTest>"),
+ Set_String: findType("Set<String>"),
+ Set_dynamic: findType("Set<@>"),
+ SingleMapping: findType("SingleMapping"),
+ SourceLocation: findType("SourceLocation"),
+ SourceSpan: findType("SourceSpan"),
+ SourceSpanWithContext: findType("SourceSpanWithContext"),
+ StackTrace: findType("StackTrace"),
+ StackTraceFormatter: findType("StackTraceFormatter"),
+ StackZoneSpecification: findType("StackZoneSpecification"),
+ State: findType("State"),
+ StreamChannelCompleter_dynamic: findType("StreamChannelCompleter<@>"),
+ StreamChannel_dynamic: findType("StreamChannel<@>"),
+ StreamCompleter_dynamic: findType("StreamCompleter<@>"),
+ StreamGroup_LiveTest: findType("StreamGroup<LiveTest>"),
+ StreamQueue_dynamic: findType("StreamQueue<@>"),
+ StreamSinkCompleter_dynamic: findType("StreamSinkCompleter<@>"),
+ StreamSubscription_dynamic: findType("StreamSubscription<@>"),
+ String: findType("String"),
+ String_Function_Match: findType("String(Match)"),
+ String_Function_String: findType("String(String)"),
+ SuiteChannelManager: findType("SuiteChannelManager"),
+ Test: findType("Test"),
+ Timer: findType("Timer"),
+ Trace: findType("Trace"),
+ Trace_Function_String: findType("Trace(String)"),
+ Type: findType("Type"),
+ Uint8List: findType("Uint8List"),
+ UnionSet_LiveTest: findType("UnionSet<LiveTest>"),
+ UnknownJavaScriptObject: findType("UnknownJavaScriptObject"),
+ UnmodifiableListView_LiveTest: findType("UnmodifiableListView<LiveTest>"),
+ UnmodifiableMapView_BooleanSelector_Metadata: findType("UnmodifiableMapView<BooleanSelector,Metadata>"),
+ UnmodifiableMapView_PlatformSelector_Metadata: findType("UnmodifiableMapView<PlatformSelector,Metadata>"),
+ UnmodifiableMapView_String_Uri: findType("UnmodifiableMapView<String,Uri>"),
+ UnmodifiableSetView_LiveTest: findType("UnmodifiableSetView<LiveTest>"),
+ UnmodifiableSetView_String: findType("UnmodifiableSetView<String>"),
+ Uri: findType("Uri"),
+ WhereIterable_String: findType("WhereIterable<String>"),
+ Zone: findType("Zone"),
+ ZoneDelegate: findType("ZoneDelegate"),
+ ZoneSpecification: findType("ZoneSpecification"),
+ _AsyncBroadcastStreamController_LiveSuite: findType("_AsyncBroadcastStreamController<LiveSuite>"),
+ _AsyncBroadcastStreamController_RunnerSuite: findType("_AsyncBroadcastStreamController<RunnerSuite>"),
+ _AsyncBroadcastStreamController_bool: findType("_AsyncBroadcastStreamController<bool>"),
+ _AsyncCompleter_List_dynamic: findType("_AsyncCompleter<List<@>>"),
+ _AsyncCompleter_PoolResource: findType("_AsyncCompleter<PoolResource>"),
+ _AsyncCompleter_dynamic: findType("_AsyncCompleter<@>"),
+ _AsyncCompleter_void: findType("_AsyncCompleter<~>"),
+ _AsyncCounter: findType("_AsyncCounter"),
+ _CompleterSink_dynamic: findType("_CompleterSink<@>"),
+ _CompleterStream_dynamic: findType("_CompleterStream<@>"),
+ _DelayedEvent_dynamic: findType("_DelayedEvent<@>"),
+ _EventRequest_dynamic: findType("_EventRequest<@>"),
+ _EventStream_MessageEvent: findType("_EventStream<MessageEvent>"),
+ _FutureListener_dynamic_dynamic: findType("_FutureListener<@,@>"),
+ _Future_List_dynamic: findType("_Future<List<@>>"),
+ _Future_PoolResource: findType("_Future<PoolResource>"),
+ _Future_RunnerSuite: findType("_Future<RunnerSuite>"),
+ _Future_dynamic: findType("_Future<@>"),
+ _Future_int: findType("_Future<int>"),
+ _Future_void: findType("_Future<~>"),
+ _IdentityHashMap_dynamic_dynamic: findType("_IdentityHashMap<@,@>"),
+ _LinkedHashSetCell: findType("_LinkedHashSetCell"),
+ _Node: findType("_Node"),
+ _Predicate_Object: findType("_Predicate<Object>"),
+ _Predicate_dynamic: findType("_Predicate<@>"),
+ _RestRequest_dynamic: findType("_RestRequest<@>"),
+ _StreamChannel_dynamic: findType("_StreamChannel<@>"),
+ _SyncBroadcastStreamController_AsyncError: findType("_SyncBroadcastStreamController<AsyncError>"),
+ _SyncBroadcastStreamController_LiveTest: findType("_SyncBroadcastStreamController<LiveTest>"),
+ _SyncBroadcastStreamController_Message: findType("_SyncBroadcastStreamController<Message>"),
+ _SyncBroadcastStreamController_State: findType("_SyncBroadcastStreamController<State>"),
+ _SyncCompleter_PoolResource: findType("_SyncCompleter<PoolResource>"),
+ _SyncCompleter_dynamic: findType("_SyncCompleter<@>"),
+ _ZoneFunction_Function: findType("_ZoneFunction<Function>"),
+ _ZoneFunction_of_AsyncError_Function_Zone_ZoneDelegate_Zone_Object_StackTrace: findType("_ZoneFunction<AsyncError(Zone,ZoneDelegate,Zone,Object,StackTrace)>"),
+ _ZoneFunction_of_Timer_Function_5_Zone_and_ZoneDelegate_and_Zone_and_Duration_and_void_Function: findType("_ZoneFunction<Timer(Zone,ZoneDelegate,Zone,Duration,~())>"),
+ _ZoneFunction_of_Timer_Function_5_Zone_and_ZoneDelegate_and_Zone_and_Duration_and_void_Function_Timer: findType("_ZoneFunction<Timer(Zone,ZoneDelegate,Zone,Duration,~(Timer))>"),
+ _ZoneFunction_of_Zone_Function_5_Zone_and_ZoneDelegate_and_Zone_and_ZoneSpecification_and_Map_dynamic_dynamic: findType("_ZoneFunction<Zone(Zone,ZoneDelegate,Zone,ZoneSpecification,Map<@,@>)>"),
+ _ZoneFunction_of_void_Function_4_Zone_and_ZoneDelegate_and_Zone_and_void_Function: findType("_ZoneFunction<~(Zone,ZoneDelegate,Zone,~())>"),
+ _ZoneFunction_of_void_Function_Zone_ZoneDelegate_Zone_Object_StackTrace: findType("_ZoneFunction<~(Zone,ZoneDelegate,Zone,Object,StackTrace)>"),
+ _ZoneFunction_of_void_Function_Zone_ZoneDelegate_Zone_String: findType("_ZoneFunction<~(Zone,ZoneDelegate,Zone,String)>"),
+ bool: findType("bool"),
+ bool_Function_Frame: findType("bool(Frame)"),
+ bool_Function_Null: findType("bool(Null)"),
+ bool_Function_Object: findType("bool(Object)"),
+ bool_Function_String: findType("bool(String)"),
+ bool_Function_dynamic: findType("bool(@)"),
+ dynamic: findType("@"),
+ dynamic_Function: findType("@()"),
+ dynamic_Function_Event: findType("@(Event)"),
+ dynamic_Function_Object: findType("@(Object)"),
+ dynamic_Function_Object_Object: findType("@(Object,Object)"),
+ dynamic_Function_Object_StackTrace: findType("@(Object,StackTrace)"),
+ dynamic_Function_String: findType("@(String)"),
+ dynamic_Function_dynamic_dynamic: findType("@(@,@)"),
+ int: findType("int"),
+ num: findType("num"),
+ void: findType("~"),
+ void_Function: findType("~()"),
+ void_Function_MessageEvent: findType("~(MessageEvent)"),
+ void_Function_Object: findType("~(Object)"),
+ void_Function_Object_StackTrace: findType("~(Object,StackTrace)"),
+ void_Function_String_dynamic: findType("~(String,@)"),
+ void_Function_Timer: findType("~(Timer)")
+ };
+ })();
+ (function constants() {
+ var makeConstList = hunkHelpers.makeConstList;
+ C.Interceptor_methods = J.Interceptor.prototype;
+ C.JSArray_methods = J.JSArray.prototype;
+ C.JSInt_methods = J.JSInt.prototype;
+ C.JSNumber_methods = J.JSNumber.prototype;
+ C.JSString_methods = J.JSString.prototype;
+ C.JavaScriptFunction_methods = J.JavaScriptFunction.prototype;
+ C.Location_methods = W.Location.prototype;
+ C.MessagePort_methods = W.MessagePort.prototype;
+ C.NativeUint32List_methods = H.NativeUint32List.prototype;
+ C.NativeUint8List_methods = H.NativeUint8List.prototype;
+ C.PlainJavaScriptObject_methods = J.PlainJavaScriptObject.prototype;
+ C.UnknownJavaScriptObject_methods = J.UnknownJavaScriptObject.prototype;
+ C.AsciiEncoder_127 = new P.AsciiEncoder(127);
+ C.List_empty = H.setRuntimeTypeInfo(makeConstList([]), type$.JSArray_String);
+ C.C_All = new X.All();
+ C.C_AsciiCodec = new P.AsciiCodec();
+ C.C_AsciiGlyphSet = new A.AsciiGlyphSet();
+ C.C_Base64Encoder = new P.Base64Encoder();
+ C.C_Base64Codec = new P.Base64Codec();
+ C.C_EmptyIterator = new H.EmptyIterator(H.findType("EmptyIterator<Null>"));
+ C.C_EmptyUnmodifiableSet = new O.EmptyUnmodifiableSet(H.findType("EmptyUnmodifiableSet<String>"));
+ C.C_JS_CONST = function getTagFallback(o) {
+ var s = Object.prototype.toString.call(o);
+ return s.substring(8, s.length - 1);
+};
+ C.C_JS_CONST0 = function() {
+ var toStringFunction = Object.prototype.toString;
+ function getTag(o) {
+ var s = toStringFunction.call(o);
+ return s.substring(8, s.length - 1);
+ }
+ function getUnknownTag(object, tag) {
+ if (/^HTML[A-Z].*Element$/.test(tag)) {
+ var name = toStringFunction.call(object);
+ if (name == "[object Object]") return null;
+ return "HTMLElement";
+ }
+ }
+ function getUnknownTagGenericBrowser(object, tag) {
+ if (self.HTMLElement && object instanceof HTMLElement) return "HTMLElement";
+ return getUnknownTag(object, tag);
+ }
+ function prototypeForTag(tag) {
+ if (typeof window == "undefined") return null;
+ if (typeof window[tag] == "undefined") return null;
+ var constructor = window[tag];
+ if (typeof constructor != "function") return null;
+ return constructor.prototype;
+ }
+ function discriminator(tag) { return null; }
+ var isBrowser = typeof navigator == "object";
+ return {
+ getTag: getTag,
+ getUnknownTag: isBrowser ? getUnknownTagGenericBrowser : getUnknownTag,
+ prototypeForTag: prototypeForTag,
+ discriminator: discriminator };
+};
+ C.C_JS_CONST6 = function(getTagFallback) {
+ return function(hooks) {
+ if (typeof navigator != "object") return hooks;
+ var ua = navigator.userAgent;
+ if (ua.indexOf("DumpRenderTree") >= 0) return hooks;
+ if (ua.indexOf("Chrome") >= 0) {
+ function confirm(p) {
+ return typeof window == "object" && window[p] && window[p].name == p;
+ }
+ if (confirm("Window") && confirm("HTMLElement")) return hooks;
+ }
+ hooks.getTag = getTagFallback;
+ };
+};
+ C.C_JS_CONST1 = function(hooks) {
+ if (typeof dartExperimentalFixupGetTag != "function") return hooks;
+ hooks.getTag = dartExperimentalFixupGetTag(hooks.getTag);
+};
+ C.C_JS_CONST2 = function(hooks) {
+ var getTag = hooks.getTag;
+ var prototypeForTag = hooks.prototypeForTag;
+ function getTagFixed(o) {
+ var tag = getTag(o);
+ if (tag == "Document") {
+ if (!!o.xmlVersion) return "!Document";
+ return "!HTMLDocument";
+ }
+ return tag;
+ }
+ function prototypeForTagFixed(tag) {
+ if (tag == "Document") return null;
+ return prototypeForTag(tag);
+ }
+ hooks.getTag = getTagFixed;
+ hooks.prototypeForTag = prototypeForTagFixed;
+};
+ C.C_JS_CONST5 = function(hooks) {
+ var userAgent = typeof navigator == "object" ? navigator.userAgent : "";
+ if (userAgent.indexOf("Firefox") == -1) return hooks;
+ var getTag = hooks.getTag;
+ var quickMap = {
+ "BeforeUnloadEvent": "Event",
+ "DataTransfer": "Clipboard",
+ "GeoGeolocation": "Geolocation",
+ "Location": "!Location",
+ "WorkerMessageEvent": "MessageEvent",
+ "XMLDocument": "!Document"};
+ function getTagFirefox(o) {
+ var tag = getTag(o);
+ return quickMap[tag] || tag;
+ }
+ hooks.getTag = getTagFirefox;
+};
+ C.C_JS_CONST4 = function(hooks) {
+ var userAgent = typeof navigator == "object" ? navigator.userAgent : "";
+ if (userAgent.indexOf("Trident/") == -1) return hooks;
+ var getTag = hooks.getTag;
+ var quickMap = {
+ "BeforeUnloadEvent": "Event",
+ "DataTransfer": "Clipboard",
+ "HTMLDDElement": "HTMLElement",
+ "HTMLDTElement": "HTMLElement",
+ "HTMLPhraseElement": "HTMLElement",
+ "Position": "Geoposition"
+ };
+ function getTagIE(o) {
+ var tag = getTag(o);
+ var newTag = quickMap[tag];
+ if (newTag) return newTag;
+ if (tag == "Object") {
+ if (window.DataView && (o instanceof window.DataView)) return "DataView";
+ }
+ return tag;
+ }
+ function prototypeForTagIE(tag) {
+ var constructor = window[tag];
+ if (constructor == null) return null;
+ return constructor.prototype;
+ }
+ hooks.getTag = getTagIE;
+ hooks.prototypeForTag = prototypeForTagIE;
+};
+ C.C_JS_CONST3 = function(hooks) { return hooks; }
+;
+ C.C_JsonCodec = new P.JsonCodec();
+ C.C_None = new O.None();
+ C.C_OutOfMemoryError = new P.OutOfMemoryError();
+ C.C_UnicodeGlyphSet = new K.UnicodeGlyphSet();
+ C.C_Utf8Codec = new P.Utf8Codec();
+ C.C_Utf8Encoder = new P.Utf8Encoder();
+ C.C__DelayedDone = new P._DelayedDone();
+ C.C__IsTrue = new Y._IsTrue();
+ C.C__RootZone = new P._RootZone();
+ C.Duration_0 = new P.Duration(0);
+ C.Duration_30000000 = new P.Duration(30000000);
+ C.JsonDecoder_null = new P.JsonDecoder(null);
+ C.List_127_2047_65535_1114111 = H.setRuntimeTypeInfo(makeConstList([127, 2047, 65535, 1114111]), type$.JSArray_int);
+ C.List_2Vk = H.setRuntimeTypeInfo(makeConstList([0, 0, 32776, 33792, 1, 10240, 0, 0]), type$.JSArray_int);
+ C.List_CVk = H.setRuntimeTypeInfo(makeConstList([0, 0, 65490, 45055, 65535, 34815, 65534, 18431]), type$.JSArray_int);
+ C.List_JYB = H.setRuntimeTypeInfo(makeConstList([0, 0, 26624, 1023, 65534, 2047, 65534, 2047]), type$.JSArray_int);
+ C.List_empty1 = H.setRuntimeTypeInfo(makeConstList([]), H.findType("JSArray<Null>"));
+ C.List_empty0 = H.setRuntimeTypeInfo(makeConstList([]), type$.JSArray_dynamic);
+ C.List_gRj = H.setRuntimeTypeInfo(makeConstList([0, 0, 32722, 12287, 65534, 34815, 65534, 18431]), type$.JSArray_int);
+ C.Runtime_ql0 = new B.Runtime("VM", "vm", null, true, false, false, false, false);
+ C.Runtime_4e8 = new B.Runtime("Chrome", "chrome", null, false, true, true, true, false);
+ C.Runtime_IBZ = new B.Runtime("PhantomJS", "phantomjs", null, false, true, true, true, true);
+ C.Runtime_8Rg = new B.Runtime("Firefox", "firefox", null, false, true, true, false, false);
+ C.Runtime_ivT = new B.Runtime("Safari", "safari", null, false, true, true, false, false);
+ C.Runtime_Mzn = new B.Runtime("Internet Explorer", "ie", null, false, true, true, false, false);
+ C.Runtime_R5O = new B.Runtime("Node.js", "node", null, false, false, true, false, false);
+ C.List_m7e = H.setRuntimeTypeInfo(makeConstList([C.Runtime_ql0, C.Runtime_4e8, C.Runtime_IBZ, C.Runtime_8Rg, C.Runtime_ivT, C.Runtime_Mzn, C.Runtime_R5O]), H.findType("JSArray<Runtime>"));
+ C.List_nxB = H.setRuntimeTypeInfo(makeConstList([0, 0, 24576, 1023, 65534, 34815, 65534, 18431]), type$.JSArray_int);
+ C.OperatingSystem_Windows_windows = new N.OperatingSystem("Windows", "windows");
+ C.OperatingSystem_RkP = new N.OperatingSystem("OS X", "mac-os");
+ C.OperatingSystem_Linux_linux = new N.OperatingSystem("Linux", "linux");
+ C.OperatingSystem_Android_android = new N.OperatingSystem("Android", "android");
+ C.OperatingSystem_iOS_ios = new N.OperatingSystem("iOS", "ios");
+ C.List_opx = H.setRuntimeTypeInfo(makeConstList([C.OperatingSystem_Windows_windows, C.OperatingSystem_RkP, C.OperatingSystem_Linux_linux, C.OperatingSystem_Android_android, C.OperatingSystem_iOS_ios]), H.findType("JSArray<OperatingSystem>"));
+ C.List_qFt = H.setRuntimeTypeInfo(makeConstList([0, 0, 27858, 1023, 65534, 51199, 65535, 32767]), type$.JSArray_int);
+ C.List_qNA = H.setRuntimeTypeInfo(makeConstList([0, 0, 32754, 11263, 65534, 34815, 65534, 18431]), type$.JSArray_int);
+ C.List_qg40 = H.setRuntimeTypeInfo(makeConstList([0, 0, 32722, 12287, 65535, 34815, 65534, 18431]), type$.JSArray_int);
+ C.List_qg4 = H.setRuntimeTypeInfo(makeConstList([0, 0, 65490, 12287, 65535, 34815, 65534, 18431]), type$.JSArray_int);
+ C.List_F9h = H.setRuntimeTypeInfo(makeConstList(["\n", "\r", "\f", "\b", "\t", "\v", "\x7f"]), type$.JSArray_String);
+ C.Map_F9GZw = new H.ConstantStringMap(7, {"\n": "\\n", "\r": "\\r", "\f": "\\f", "\b": "\\b", "\t": "\\t", "\v": "\\v", "\x7f": "\\x7F"}, C.List_F9h, H.findType("ConstantStringMap<String,String>"));
+ C.List_empty2 = H.setRuntimeTypeInfo(makeConstList([]), H.findType("JSArray<BooleanSelector>"));
+ C.Map_empty0 = new H.ConstantStringMap(0, {}, C.List_empty2, H.findType("ConstantStringMap<BooleanSelector,Metadata>"));
+ C.Map_empty1 = new H.ConstantStringMap(0, {}, C.List_empty1, H.findType("ConstantStringMap<Null,Null>"));
+ C.List_empty3 = H.setRuntimeTypeInfo(makeConstList([]), H.findType("JSArray<PlatformSelector>"));
+ C.Map_empty = new H.ConstantStringMap(0, {}, C.List_empty3, H.findType("ConstantStringMap<PlatformSelector,Metadata>"));
+ C.MessageType_print = new D.MessageType("print");
+ C.MessageType_skip = new D.MessageType("skip");
+ C.OperatingSystem_none_none = new N.OperatingSystem("none", "none");
+ C.PlatformSelector_All = new E.PlatformSelector(C.C_All);
+ C.Result_error = new G.Result0("error");
+ C.Result_skipped = new G.Result0("skipped");
+ C.Result_success = new G.Result0("success");
+ C.Status_complete = new G.Status("complete");
+ C.State_Status_complete_Result_error = new G.State(C.Status_complete, C.Result_error);
+ C.Result_failure = new G.Result0("failure");
+ C.State_Status_complete_Result_failure = new G.State(C.Status_complete, C.Result_failure);
+ C.State_Status_complete_Result_skipped = new G.State(C.Status_complete, C.Result_skipped);
+ C.Status_pending = new G.Status("pending");
+ C.State_Status_pending_Result_success = new G.State(C.Status_pending, C.Result_success);
+ C.Status_running = new G.Status("running");
+ C.State_Status_running_Result_skipped = new G.State(C.Status_running, C.Result_skipped);
+ C.State_Status_running_Result_success = new G.State(C.Status_running, C.Result_success);
+ C.Symbol_Drw = new H.Symbol("test.declarer");
+ C.Symbol_MAi = new H.Symbol("test.runner.test_channel");
+ C.Symbol_cQL = new H.Symbol("test.invoker");
+ C.Symbol_runCount = new H.Symbol("runCount");
+ C.Timeout_null_1 = new R.Timeout(null, 1);
+ C.Timeout_null_null = new R.Timeout(null, null);
+ C.TokenType_31K = new L.TokenType("right paren");
+ C.TokenType_69P = new L.TokenType("question mark");
+ C.TokenType_and = new L.TokenType("and");
+ C.TokenType_colon = new L.TokenType("colon");
+ C.TokenType_e7P = new L.TokenType("left paren");
+ C.TokenType_identifier = new L.TokenType("identifier");
+ C.TokenType_not = new L.TokenType("not");
+ C.TokenType_or = new L.TokenType("or");
+ C.TokenType_wwi = new L.TokenType("end of file");
+ C.Type_ByteBuffer_RkP = H.typeLiteral("ByteBuffer");
+ C.Type_ByteData_zNC = H.typeLiteral("ByteData");
+ C.Type_Float32List_LB7 = H.typeLiteral("Float32List");
+ C.Type_Float64List_LB7 = H.typeLiteral("Float64List");
+ C.Type_Int16List_uXf = H.typeLiteral("Int16List");
+ C.Type_Int32List_O50 = H.typeLiteral("Int32List");
+ C.Type_Int8List_ekJ = H.typeLiteral("Int8List");
+ C.Type_JSObject_8k0 = H.typeLiteral("JSObject");
+ C.Type_Null_Yyn = H.typeLiteral("Null");
+ C.Type_String_k8F = H.typeLiteral("String");
+ C.Type_Uint16List_2bx = H.typeLiteral("Uint16List");
+ C.Type_Uint32List_2bx = H.typeLiteral("Uint32List");
+ C.Type_Uint8ClampedList_Jik = H.typeLiteral("Uint8ClampedList");
+ C.Type_Uint8List_WLA = H.typeLiteral("Uint8List");
+ C.Type_bool_lhE = H.typeLiteral("bool");
+ C.Type_double_K1J = H.typeLiteral("double");
+ C.Type_int_tHn = H.typeLiteral("int");
+ C.Type_num_cv7 = H.typeLiteral("num");
+ C._PathDirection_8Gl = new M._PathDirection("at root");
+ C._PathDirection_988 = new M._PathDirection("below root");
+ C._PathDirection_FIw = new M._PathDirection("reaches root");
+ C._PathDirection_ZGD = new M._PathDirection("above root");
+ C._PathRelation_different = new M._PathRelation("different");
+ C._PathRelation_equal = new M._PathRelation("equal");
+ C._PathRelation_inconclusive = new M._PathRelation("inconclusive");
+ C._PathRelation_within = new M._PathRelation("within");
+ C._StreamGroupState_canceled = new L._StreamGroupState("canceled");
+ C._StreamGroupState_dormant = new L._StreamGroupState("dormant");
+ C._StreamGroupState_listening = new L._StreamGroupState("listening");
+ C._StreamGroupState_paused = new L._StreamGroupState("paused");
+ C._TokenKind_false_false_false = new T._TokenKind(false, false, false);
+ C._TokenKind_false_false_true = new T._TokenKind(false, false, true);
+ C._TokenKind_false_true_false = new T._TokenKind(false, true, false);
+ C._TokenKind_true_false_false = new T._TokenKind(true, false, false);
+ C._ZoneFunction_3bB = new P._ZoneFunction(C.C__RootZone, P.async___rootCreatePeriodicTimer$closure(), type$._ZoneFunction_of_Timer_Function_5_Zone_and_ZoneDelegate_and_Zone_and_Duration_and_void_Function_Timer);
+ C._ZoneFunction_7G2 = new P._ZoneFunction(C.C__RootZone, P.async___rootRegisterBinaryCallback$closure(), type$._ZoneFunction_Function);
+ C._ZoneFunction_Eeh = new P._ZoneFunction(C.C__RootZone, P.async___rootRegisterUnaryCallback$closure(), type$._ZoneFunction_Function);
+ C._ZoneFunction_NMc = new P._ZoneFunction(C.C__RootZone, P.async___rootHandleUncaughtError$closure(), type$._ZoneFunction_of_void_Function_Zone_ZoneDelegate_Zone_Object_StackTrace);
+ C._ZoneFunction__RootZone__rootCreateTimer = new P._ZoneFunction(C.C__RootZone, P.async___rootCreateTimer$closure(), type$._ZoneFunction_of_Timer_Function_5_Zone_and_ZoneDelegate_and_Zone_and_Duration_and_void_Function);
+ C._ZoneFunction__RootZone__rootErrorCallback = new P._ZoneFunction(C.C__RootZone, P.async___rootErrorCallback$closure(), type$._ZoneFunction_of_AsyncError_Function_Zone_ZoneDelegate_Zone_Object_StackTrace);
+ C._ZoneFunction__RootZone__rootFork = new P._ZoneFunction(C.C__RootZone, P.async___rootFork$closure(), type$._ZoneFunction_of_Zone_Function_5_Zone_and_ZoneDelegate_and_Zone_and_ZoneSpecification_and_Map_dynamic_dynamic);
+ C._ZoneFunction__RootZone__rootPrint = new P._ZoneFunction(C.C__RootZone, P.async___rootPrint$closure(), type$._ZoneFunction_of_void_Function_Zone_ZoneDelegate_Zone_String);
+ C._ZoneFunction__RootZone__rootRegisterCallback = new P._ZoneFunction(C.C__RootZone, P.async___rootRegisterCallback$closure(), type$._ZoneFunction_Function);
+ C._ZoneFunction__RootZone__rootRun = new P._ZoneFunction(C.C__RootZone, P.async___rootRun$closure(), type$._ZoneFunction_Function);
+ C._ZoneFunction__RootZone__rootRunBinary = new P._ZoneFunction(C.C__RootZone, P.async___rootRunBinary$closure(), type$._ZoneFunction_Function);
+ C._ZoneFunction__RootZone__rootRunUnary = new P._ZoneFunction(C.C__RootZone, P.async___rootRunUnary$closure(), type$._ZoneFunction_Function);
+ C._ZoneFunction__RootZone__rootScheduleMicrotask = new P._ZoneFunction(C.C__RootZone, P.async___rootScheduleMicrotask$closure(), type$._ZoneFunction_of_void_Function_4_Zone_and_ZoneDelegate_and_Zone_and_void_Function);
+ C._ZoneSpecification_ALf = new P._ZoneSpecification(null, null, null, null, null, null, null, null, null, null, null, null, null);
+ })();
+ (function staticFields() {
+ $.printToZone = null;
+ $.Primitives_timerFrequency = null;
+ $.Primitives_timerTicks = null;
+ $.Closure_functionCounter = 0;
+ $.BoundClosure_selfFieldNameCache = null;
+ $.BoundClosure_receiverFieldNameCache = null;
+ $.getTagFunction = null;
+ $.alternateTagFunction = null;
+ $.prototypeForTagFunction = null;
+ $.dispatchRecordsForInstanceTags = null;
+ $.interceptorsForUncacheableTags = null;
+ $.initNativeDispatchFlag = null;
+ $._nextCallback = null;
+ $._lastCallback = null;
+ $._lastPriorityCallback = null;
+ $._isInCallbackLoop = false;
+ $.Zone__current = C.C__RootZone;
+ $._RootZone__rootDelegate = null;
+ $._toStringVisiting = [];
+ $.Expando__keyCount = 0;
+ $.Stopwatch__frequency = null;
+ $._currentUriBase = null;
+ $._current = null;
+ $._glyphs = C.C_UnicodeGlyphSet;
+ $._macOSDirectories = P.LinkedHashSet_LinkedHashSet$_literal(["/Applications", "/Library", "/Network", "/System", "/Users"], type$.String);
+ $._globalDeclarer = null;
+ $.x = 0;
+ })();
+ (function lazyInitializers() {
+ var _lazy = hunkHelpers.lazy;
+ _lazy($, "DART_CLOSURE_PROPERTY_NAME", "$get$DART_CLOSURE_PROPERTY_NAME", function() {
+ return H.getIsolateAffinityTag("_$dart_dartClosure");
+ });
+ _lazy($, "JS_INTEROP_INTERCEPTOR_TAG", "$get$JS_INTEROP_INTERCEPTOR_TAG", function() {
+ return H.getIsolateAffinityTag("_$dart_js");
+ });
+ _lazy($, "TypeErrorDecoder_noSuchMethodPattern", "$get$TypeErrorDecoder_noSuchMethodPattern", function() {
+ return H.TypeErrorDecoder_extractPattern(H.TypeErrorDecoder_provokeCallErrorOn({
+ toString: function() {
+ return "$receiver$";
+ }
+ }));
+ });
+ _lazy($, "TypeErrorDecoder_notClosurePattern", "$get$TypeErrorDecoder_notClosurePattern", function() {
+ return H.TypeErrorDecoder_extractPattern(H.TypeErrorDecoder_provokeCallErrorOn({$method$: null,
+ toString: function() {
+ return "$receiver$";
+ }
+ }));
+ });
+ _lazy($, "TypeErrorDecoder_nullCallPattern", "$get$TypeErrorDecoder_nullCallPattern", function() {
+ return H.TypeErrorDecoder_extractPattern(H.TypeErrorDecoder_provokeCallErrorOn(null));
+ });
+ _lazy($, "TypeErrorDecoder_nullLiteralCallPattern", "$get$TypeErrorDecoder_nullLiteralCallPattern", function() {
+ return H.TypeErrorDecoder_extractPattern(function() {
+ var $argumentsExpr$ = '$arguments$';
+ try {
+ null.$method$($argumentsExpr$);
+ } catch (e) {
+ return e.message;
+ }
+ }());
+ });
+ _lazy($, "TypeErrorDecoder_undefinedCallPattern", "$get$TypeErrorDecoder_undefinedCallPattern", function() {
+ return H.TypeErrorDecoder_extractPattern(H.TypeErrorDecoder_provokeCallErrorOn(void 0));
+ });
+ _lazy($, "TypeErrorDecoder_undefinedLiteralCallPattern", "$get$TypeErrorDecoder_undefinedLiteralCallPattern", function() {
+ return H.TypeErrorDecoder_extractPattern(function() {
+ var $argumentsExpr$ = '$arguments$';
+ try {
+ (void 0).$method$($argumentsExpr$);
+ } catch (e) {
+ return e.message;
+ }
+ }());
+ });
+ _lazy($, "TypeErrorDecoder_nullPropertyPattern", "$get$TypeErrorDecoder_nullPropertyPattern", function() {
+ return H.TypeErrorDecoder_extractPattern(H.TypeErrorDecoder_provokePropertyErrorOn(null));
+ });
+ _lazy($, "TypeErrorDecoder_nullLiteralPropertyPattern", "$get$TypeErrorDecoder_nullLiteralPropertyPattern", function() {
+ return H.TypeErrorDecoder_extractPattern(function() {
+ try {
+ null.$method$;
+ } catch (e) {
+ return e.message;
+ }
+ }());
+ });
+ _lazy($, "TypeErrorDecoder_undefinedPropertyPattern", "$get$TypeErrorDecoder_undefinedPropertyPattern", function() {
+ return H.TypeErrorDecoder_extractPattern(H.TypeErrorDecoder_provokePropertyErrorOn(void 0));
+ });
+ _lazy($, "TypeErrorDecoder_undefinedLiteralPropertyPattern", "$get$TypeErrorDecoder_undefinedLiteralPropertyPattern", function() {
+ return H.TypeErrorDecoder_extractPattern(function() {
+ try {
+ (void 0).$method$;
+ } catch (e) {
+ return e.message;
+ }
+ }());
+ });
+ _lazy($, "_AsyncRun__scheduleImmediateClosure", "$get$_AsyncRun__scheduleImmediateClosure", function() {
+ return P._AsyncRun__initializeScheduleImmediate();
+ });
+ _lazy($, "Future__nullFuture", "$get$Future__nullFuture", function() {
+ return P._Future$zoneValue(null, C.C__RootZone, type$.Null);
+ });
+ _lazy($, "_RootZone__rootMap", "$get$_RootZone__rootMap", function() {
+ var t1 = type$.dynamic;
+ return P.HashMap_HashMap(t1, t1);
+ });
+ _lazy($, "Utf8Decoder__decoder", "$get$Utf8Decoder__decoder", function() {
+ return P.Utf8Decoder__makeDecoder();
+ });
+ _lazy($, "_Base64Decoder__inverseAlphabet", "$get$_Base64Decoder__inverseAlphabet", function() {
+ return H.NativeInt8List__create1(H._ensureNativeList(H.setRuntimeTypeInfo([-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -2, -2, -2, -2, -2, 62, -2, 62, -2, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -2, -2, -2, -1, -2, -2, -2, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -2, -2, -2, -2, 63, -2, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -2, -2, -2, -2, -2], type$.JSArray_int)));
+ });
+ _lazy($, "_Uri__isWindowsCached", "$get$_Uri__isWindowsCached", function() {
+ return typeof process != "undefined" && Object.prototype.toString.call(process) == "[object process]" && process.platform == "win32";
+ });
+ _lazy($, "_Uri__needsNoEncoding", "$get$_Uri__needsNoEncoding", function() {
+ return P.RegExp_RegExp("^[\\-\\.0-9A-Z_a-z~]*$", false);
+ });
+ _lazy($, "_hasErrorStackProperty", "$get$_hasErrorStackProperty", function() {
+ return new Error().stack != void 0;
+ });
+ _lazy($, "_scannerTables", "$get$_scannerTables", function() {
+ return P._createTables();
+ });
+ _lazy($, "_whitespaceAndSingleLineComments", "$get$_whitespaceAndSingleLineComments", function() {
+ return P.RegExp_RegExp("([ \\t\\n]+|//[^\\n]*(\\n|$))+", false);
+ });
+ _lazy($, "_multiLineCommentBody", "$get$_multiLineCommentBody", function() {
+ return P.RegExp_RegExp("([^/*]|/[^*]|\\*[^/])+", false);
+ });
+ _lazy($, "_hyphenatedIdentifier", "$get$_hyphenatedIdentifier0", function() {
+ return P.RegExp_RegExp("[a-zA-Z_-][a-zA-Z0-9_-]*", false);
+ });
+ _lazy($, "_dart2DynamicArgs", "$get$_dart2DynamicArgs", function() {
+ return P.RegExp_RegExp("<dynamic(, dynamic)*>", false);
+ });
+ _lazy($, "_escapeRegExp", "$get$_escapeRegExp", function() {
+ return P.RegExp_RegExp("[\\x00-\\x07\\x0E-\\x1F" + C.Map_F9GZw.get$keys().map$1$1(0, M.util___getHexLiteral$closure(), type$.String).join$0(0) + "]", false);
+ });
+ _lazy($, "windows", "$get$windows", function() {
+ return M.Context_Context($.$get$Style_windows());
+ });
+ _lazy($, "url", "$get$url", function() {
+ return M.Context_Context($.$get$Style_url());
+ });
+ _lazy($, "context", "$get$context", function() {
+ return new M.Context($.$get$Style_platform(), null);
+ });
+ _lazy($, "Style_posix", "$get$Style_posix", function() {
+ return new E.PosixStyle(P.RegExp_RegExp("/", false), P.RegExp_RegExp("[^/]$", false), P.RegExp_RegExp("^/", false));
+ });
+ _lazy($, "Style_windows", "$get$Style_windows", function() {
+ return new L.WindowsStyle(P.RegExp_RegExp("[/\\\\]", false), P.RegExp_RegExp("[^/\\\\]$", false), P.RegExp_RegExp("^(\\\\\\\\[^\\\\]+\\\\[^\\\\/]+|[a-zA-Z]:[/\\\\])", false), P.RegExp_RegExp("^[/\\\\](?![/\\\\])", false));
+ });
+ _lazy($, "Style_url", "$get$Style_url", function() {
+ return new F.UrlStyle(P.RegExp_RegExp("/", false), P.RegExp_RegExp("(^[a-zA-Z][-+.a-zA-Z\\d]*://|[^/])$", false), P.RegExp_RegExp("[a-zA-Z][-+.a-zA-Z\\d]*://[^/]*", false), P.RegExp_RegExp("^/", false));
+ });
+ _lazy($, "Style_platform", "$get$Style_platform", function() {
+ return O.Style__getPlatformStyle();
+ });
+ _lazy($, "_digits", "$get$_digits", function() {
+ return new L.closure().call$0();
+ });
+ _lazy($, "MAX_INT32", "$get$MAX_INT32", function() {
+ return H._checkIntNullable(P.pow(2, 31) - 1);
+ });
+ _lazy($, "MIN_INT32", "$get$MIN_INT32", function() {
+ return H._checkIntNullable(-P.pow(2, 31));
+ });
+ _lazy($, "_specKey", "$get$_specKey", function() {
+ return new P.Object();
+ });
+ _lazy($, "_vmFrame", "$get$_vmFrame", function() {
+ return P.RegExp_RegExp("^#\\d+\\s+(\\S.*) \\((.+?)((?::\\d+){0,2})\\)$", false);
+ });
+ _lazy($, "_v8Frame", "$get$_v8Frame", function() {
+ return P.RegExp_RegExp("^\\s*at (?:(\\S.*?)(?: \\[as [^\\]]+\\])? \\((.*)\\)|(.*))$", false);
+ });
+ _lazy($, "_v8UrlLocation", "$get$_v8UrlLocation", function() {
+ return P.RegExp_RegExp("^(.*):(\\d+):(\\d+)|native$", false);
+ });
+ _lazy($, "_v8EvalLocation", "$get$_v8EvalLocation", function() {
+ return P.RegExp_RegExp("^eval at (?:\\S.*?) \\((.*)\\)(?:, .*?:\\d+:\\d+)?$", false);
+ });
+ _lazy($, "_firefoxSafariFrame", "$get$_firefoxSafariFrame", function() {
+ return P.RegExp_RegExp("^(?:([^@(/]*)(?:\\(.*\\))?((?:/[^/]*)*)(?:\\(.*\\))?@)?(.*?):(\\d*)(?::(\\d*))?$", false);
+ });
+ _lazy($, "_friendlyFrame", "$get$_friendlyFrame", function() {
+ return P.RegExp_RegExp("^(\\S+)(?: (\\d+)(?::(\\d+))?)?\\s+([^\\d].*)$", false);
+ });
+ _lazy($, "_asyncBody", "$get$_asyncBody", function() {
+ return P.RegExp_RegExp("<(<anonymous closure>|[^>]+)_async_body>", false);
+ });
+ _lazy($, "_initialDot", "$get$_initialDot", function() {
+ return P.RegExp_RegExp("^\\.", false);
+ });
+ _lazy($, "Frame__uriRegExp", "$get$Frame__uriRegExp", function() {
+ return P.RegExp_RegExp("^[a-zA-Z][-+.a-zA-Z\\d]*://", false);
+ });
+ _lazy($, "Frame__windowsRegExp", "$get$Frame__windowsRegExp", function() {
+ return P.RegExp_RegExp("^([a-zA-Z]:[\\\\/]|\\\\\\\\)", false);
+ });
+ _lazy($, "StackZoneSpecification_disableKey", "$get$StackZoneSpecification_disableKey", function() {
+ return new P.Object();
+ });
+ _lazy($, "_terseRegExp", "$get$_terseRegExp", function() {
+ return P.RegExp_RegExp("(-patch)?([/\\\\].*)?$", false);
+ });
+ _lazy($, "_v8Trace", "$get$_v8Trace", function() {
+ return P.RegExp_RegExp("\\n ?at ", false);
+ });
+ _lazy($, "_v8TraceLine", "$get$_v8TraceLine", function() {
+ return P.RegExp_RegExp(" ?at ", false);
+ });
+ _lazy($, "_firefoxSafariTrace", "$get$_firefoxSafariTrace", function() {
+ return P.RegExp_RegExp("^(([.0-9A-Za-z_$/<]|\\(.*\\))*@)?[^\\s]*:\\d*$", true);
+ });
+ _lazy($, "_friendlyTrace", "$get$_friendlyTrace", function() {
+ return P.RegExp_RegExp("^[^\\s<][^\\s]*( \\d+(:\\d+)?)?[ \\t]+[^\\s]+$", true);
+ });
+ _lazy($, "Metadata_empty", "$get$Metadata_empty", function() {
+ var _null = null;
+ return O.Metadata$_(_null, _null, _null, _null, _null, _null, _null, _null, _null, _null);
+ });
+ _lazy($, "_universalValidVariables", "$get$_universalValidVariables", function() {
+ var _i,
+ t1 = P.LinkedHashSet_LinkedHashSet(type$.String);
+ t1.add$1(0, "posix");
+ t1.add$1(0, "dart-vm");
+ t1.add$1(0, "browser");
+ t1.add$1(0, "js");
+ t1.add$1(0, "blink");
+ t1.add$1(0, "google");
+ for (_i = 0; _i < 7; ++_i)
+ t1.add$1(0, C.List_m7e[_i].identifier);
+ for (_i = 0; _i < 5; ++_i)
+ t1.add$1(0, C.List_opx[_i].identifier);
+ return t1;
+ });
+ _lazy($, "_currentKey", "$get$_currentKey0", function() {
+ return new P.Object();
+ });
+ _lazy($, "_currentKey0", "$get$_currentKey", function() {
+ return new P.Object();
+ });
+ _lazy($, "currentOSGuess", "$get$currentOSGuess", function() {
+ return new B.closure0().call$0();
+ });
+ _lazy($, "_hyphenatedIdentifier0", "$get$_hyphenatedIdentifier", function() {
+ return P.RegExp_RegExp("[a-zA-Z_-][a-zA-Z0-9_-]*", false);
+ });
+ _lazy($, "anchoredHyphenatedIdentifier", "$get$anchoredHyphenatedIdentifier", function() {
+ return P.RegExp_RegExp("^" + $.$get$_hyphenatedIdentifier().pattern + "$", false);
+ });
+ _lazy($, "SuiteConfiguration_empty", "$get$SuiteConfiguration_empty", function() {
+ var t1, _null = null;
+ U.SuiteConfiguration__list(_null, type$.String);
+ t1 = type$.Pattern;
+ L.UnmodifiableSetView$(P.LinkedHashSet_LinkedHashSet$_empty(t1), t1);
+ U.SuiteConfiguration__list(_null, H.findType("RuntimeSelection"));
+ t1 = H.findType("SuiteConfiguration");
+ U.SuiteConfiguration__map(_null, type$.BooleanSelector, t1);
+ U.SuiteConfiguration__map(_null, type$.PlatformSelector, t1);
+ $.$get$Metadata_empty();
+ return new U.SuiteConfiguration();
+ });
+ })();
+ (function nativeSupport() {
+ !function() {
+ var intern = function(s) {
+ var o = {};
+ o[s] = 1;
+ return Object.keys(hunkHelpers.convertToFastObject(o))[0];
+ };
+ init.getIsolateTag = function(name) {
+ return intern("___dart_" + name + init.isolateTag);
+ };
+ var tableProperty = "___dart_isolate_tags_";
+ var usedProperties = Object[tableProperty] || (Object[tableProperty] = Object.create(null));
+ var rootProperty = "_ZxYxX";
+ for (var i = 0;; i++) {
+ var property = intern(rootProperty + "_" + i + "_");
+ if (!(property in usedProperties)) {
+ usedProperties[property] = 1;
+ init.isolateTag = property;
+ break;
+ }
+ }
+ init.dispatchPropertyName = init.getIsolateTag("dispatch_record");
+ }();
+ hunkHelpers.setOrUpdateInterceptorsByTag({ArrayBuffer: H.NativeByteBuffer, ArrayBufferView: H.NativeTypedData, DataView: H.NativeByteData, Float32Array: H.NativeFloat32List, Float64Array: H.NativeFloat64List, Int16Array: H.NativeInt16List, Int32Array: H.NativeInt32List, Int8Array: H.NativeInt8List, Uint16Array: H.NativeUint16List, Uint32Array: H.NativeUint32List, Uint8ClampedArray: H.NativeUint8ClampedList, CanvasPixelArray: H.NativeUint8ClampedList, Uint8Array: H.NativeUint8List, ApplicationCacheErrorEvent: W.ApplicationCacheErrorEvent, Blob: W.Blob, DOMError: W.DomError, DOMException: W.DomException, ErrorEvent: W.ErrorEvent, AbortPaymentEvent: W.Event, AnimationEvent: W.Event, AnimationPlaybackEvent: W.Event, BackgroundFetchClickEvent: W.Event, BackgroundFetchEvent: W.Event, BackgroundFetchFailEvent: W.Event, BackgroundFetchedEvent: W.Event, BeforeInstallPromptEvent: W.Event, BeforeUnloadEvent: W.Event, BlobEvent: W.Event, CanMakePaymentEvent: W.Event, ClipboardEvent: W.Event, CloseEvent: W.Event, CompositionEvent: W.Event, CustomEvent: W.Event, DeviceMotionEvent: W.Event, DeviceOrientationEvent: W.Event, ExtendableEvent: W.Event, ExtendableMessageEvent: W.Event, FetchEvent: W.Event, FocusEvent: W.Event, FontFaceSetLoadEvent: W.Event, ForeignFetchEvent: W.Event, GamepadEvent: W.Event, HashChangeEvent: W.Event, InstallEvent: W.Event, KeyboardEvent: W.Event, MediaEncryptedEvent: W.Event, MediaQueryListEvent: W.Event, MediaStreamEvent: W.Event, MediaStreamTrackEvent: W.Event, MIDIConnectionEvent: W.Event, MIDIMessageEvent: W.Event, MouseEvent: W.Event, DragEvent: W.Event, MutationEvent: W.Event, NotificationEvent: W.Event, PageTransitionEvent: W.Event, PaymentRequestEvent: W.Event, PaymentRequestUpdateEvent: W.Event, PointerEvent: W.Event, PopStateEvent: W.Event, PresentationConnectionAvailableEvent: W.Event, ProgressEvent: W.Event, PromiseRejectionEvent: W.Event, PushEvent: W.Event, RTCDataChannelEvent: W.Event, RTCDTMFToneChangeEvent: W.Event, RTCPeerConnectionIceEvent: W.Event, RTCTrackEvent: W.Event, SecurityPolicyViolationEvent: W.Event, SensorErrorEvent: W.Event, SpeechRecognitionEvent: W.Event, SpeechSynthesisEvent: W.Event, StorageEvent: W.Event, SyncEvent: W.Event, TextEvent: W.Event, TouchEvent: W.Event, TrackEvent: W.Event, TransitionEvent: W.Event, WebKitTransitionEvent: W.Event, UIEvent: W.Event, VRDeviceEvent: W.Event, VRDisplayEvent: W.Event, VRSessionEvent: W.Event, WheelEvent: W.Event, MojoInterfaceRequestEvent: W.Event, ResourceProgressEvent: W.Event, USBConnectionEvent: W.Event, IDBVersionChangeEvent: W.Event, AudioProcessingEvent: W.Event, OfflineAudioCompletionEvent: W.Event, WebGLContextEvent: W.Event, Event: W.Event, InputEvent: W.Event, Window: W.EventTarget, DOMWindow: W.EventTarget, EventTarget: W.EventTarget, File: W.File, Location: W.Location, MediaError: W.MediaError, MediaKeyMessageEvent: W.MediaKeyMessageEvent, MessageEvent: W.MessageEvent, MessagePort: W.MessagePort, NavigatorUserMediaError: W.NavigatorUserMediaError, OverconstrainedError: W.OverconstrainedError, PositionError: W.PositionError, PresentationConnectionCloseEvent: W.PresentationConnectionCloseEvent, SpeechRecognitionError: W.SpeechRecognitionError, SQLError: P.SqlError});
+ hunkHelpers.setOrUpdateLeafTags({ArrayBuffer: true, ArrayBufferView: false, DataView: true, Float32Array: true, Float64Array: true, Int16Array: true, Int32Array: true, Int8Array: true, Uint16Array: true, Uint32Array: true, Uint8ClampedArray: true, CanvasPixelArray: true, Uint8Array: false, ApplicationCacheErrorEvent: true, Blob: false, DOMError: true, DOMException: true, ErrorEvent: true, AbortPaymentEvent: true, AnimationEvent: true, AnimationPlaybackEvent: true, BackgroundFetchClickEvent: true, BackgroundFetchEvent: true, BackgroundFetchFailEvent: true, BackgroundFetchedEvent: true, BeforeInstallPromptEvent: true, BeforeUnloadEvent: true, BlobEvent: true, CanMakePaymentEvent: true, ClipboardEvent: true, CloseEvent: true, CompositionEvent: true, CustomEvent: true, DeviceMotionEvent: true, DeviceOrientationEvent: true, ExtendableEvent: true, ExtendableMessageEvent: true, FetchEvent: true, FocusEvent: true, FontFaceSetLoadEvent: true, ForeignFetchEvent: true, GamepadEvent: true, HashChangeEvent: true, InstallEvent: true, KeyboardEvent: true, MediaEncryptedEvent: true, MediaQueryListEvent: true, MediaStreamEvent: true, MediaStreamTrackEvent: true, MIDIConnectionEvent: true, MIDIMessageEvent: true, MouseEvent: true, DragEvent: true, MutationEvent: true, NotificationEvent: true, PageTransitionEvent: true, PaymentRequestEvent: true, PaymentRequestUpdateEvent: true, PointerEvent: true, PopStateEvent: true, PresentationConnectionAvailableEvent: true, ProgressEvent: true, PromiseRejectionEvent: true, PushEvent: true, RTCDataChannelEvent: true, RTCDTMFToneChangeEvent: true, RTCPeerConnectionIceEvent: true, RTCTrackEvent: true, SecurityPolicyViolationEvent: true, SensorErrorEvent: true, SpeechRecognitionEvent: true, SpeechSynthesisEvent: true, StorageEvent: true, SyncEvent: true, TextEvent: true, TouchEvent: true, TrackEvent: true, TransitionEvent: true, WebKitTransitionEvent: true, UIEvent: true, VRDeviceEvent: true, VRDisplayEvent: true, VRSessionEvent: true, WheelEvent: true, MojoInterfaceRequestEvent: true, ResourceProgressEvent: true, USBConnectionEvent: true, IDBVersionChangeEvent: true, AudioProcessingEvent: true, OfflineAudioCompletionEvent: true, WebGLContextEvent: true, Event: false, InputEvent: false, Window: true, DOMWindow: true, EventTarget: false, File: true, Location: true, MediaError: true, MediaKeyMessageEvent: true, MessageEvent: true, MessagePort: true, NavigatorUserMediaError: true, OverconstrainedError: true, PositionError: true, PresentationConnectionCloseEvent: true, SpeechRecognitionError: true, SQLError: true});
+ H.NativeTypedArray.$nativeSuperclassTag = "ArrayBufferView";
+ H._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin.$nativeSuperclassTag = "ArrayBufferView";
+ H._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin_FixedLengthListMixin.$nativeSuperclassTag = "ArrayBufferView";
+ H.NativeTypedArrayOfDouble.$nativeSuperclassTag = "ArrayBufferView";
+ H._NativeTypedArrayOfInt_NativeTypedArray_ListMixin.$nativeSuperclassTag = "ArrayBufferView";
+ H._NativeTypedArrayOfInt_NativeTypedArray_ListMixin_FixedLengthListMixin.$nativeSuperclassTag = "ArrayBufferView";
+ H.NativeTypedArrayOfInt.$nativeSuperclassTag = "ArrayBufferView";
+ })();
+ Function.prototype.call$0 = function() {
+ return this();
+ };
+ Function.prototype.call$1 = function(a) {
+ return this(a);
+ };
+ Function.prototype.call$2 = function(a, b) {
+ return this(a, b);
+ };
+ Function.prototype.call$3$3 = function(a, b, c) {
+ return this(a, b, c);
+ };
+ Function.prototype.call$2$2 = function(a, b) {
+ return this(a, b);
+ };
+ Function.prototype.call$1$1 = function(a) {
+ return this(a);
+ };
+ Function.prototype.call$2$1 = function(a) {
+ return this(a);
+ };
+ Function.prototype.call$3 = function(a, b, c) {
+ return this(a, b, c);
+ };
+ Function.prototype.call$4 = function(a, b, c, d) {
+ return this(a, b, c, d);
+ };
+ Function.prototype.call$3$1 = function(a) {
+ return this(a);
+ };
+ Function.prototype.call$2$3 = function(a, b, c) {
+ return this(a, b, c);
+ };
+ Function.prototype.call$1$2 = function(a, b) {
+ return this(a, b);
+ };
+ Function.prototype.call$5 = function(a, b, c, d, e) {
+ return this(a, b, c, d, e);
+ };
+ Function.prototype.call$3$4 = function(a, b, c, d) {
+ return this(a, b, c, d);
+ };
+ Function.prototype.call$2$4 = function(a, b, c, d) {
+ return this(a, b, c, d);
+ };
+ Function.prototype.call$1$4 = function(a, b, c, d) {
+ return this(a, b, c, d);
+ };
+ Function.prototype.call$3$6 = function(a, b, c, d, e, f) {
+ return this(a, b, c, d, e, f);
+ };
+ Function.prototype.call$2$5 = function(a, b, c, d, e) {
+ return this(a, b, c, d, e);
+ };
+ Function.prototype.call$2$0 = function() {
+ return this();
+ };
+ convertAllToFastObject(holders);
+ convertToFastObject($);
+ (function(callback) {
+ if (typeof document === "undefined") {
+ callback(null);
+ return;
+ }
+ if (typeof document.currentScript != 'undefined') {
+ callback(document.currentScript);
+ return;
+ }
+ var scripts = document.scripts;
+ function onLoad(event) {
+ for (var i = 0; i < scripts.length; ++i)
+ scripts[i].removeEventListener("load", onLoad, false);
+ callback(event.target);
+ }
+ for (var i = 0; i < scripts.length; ++i)
+ scripts[i].addEventListener("load", onLoad, false);
+ })(function(currentScript) {
+ init.currentScript = currentScript;
+ if (typeof dartMainRunner === "function")
+ dartMainRunner(R.main, []);
+ else
+ R.main([]);
+ });
+})();
+
+//# sourceMappingURL=main_test.dart.browser_test.dart.js.map
diff --git a/pkgs/coverage/test/test_files/main_test.js.map b/pkgs/coverage/test/test_files/main_test.js.map
new file mode 100644
index 0000000..38669ba
--- /dev/null
+++ b/pkgs/coverage/test/test_files/main_test.js.map
@@ -0,0 +1 @@
+{"version":3,"engine":"v2","file":"main_test.dart.browser_test.dart.js","sourceRoot":"","sources":["google3:///third_party/dart_lang/v2/sdk/lib/internal/cast.dart","google3:///third_party/dart_lang/v2/sdk/lib/internal/internal.dart","google3:///third_party/dart_lang/v2/sdk/lib/internal/iterable.dart","google3:///third_party/dart_lang/v2/sdk/lib/core/errors.dart","google3:///third_party/dart_lang/v2/sdk/lib/_internal/js_runtime/lib/constant_map.dart","google3:///third_party/dart_lang/v2/sdk/lib/_internal/js_runtime/lib/instantiation.dart","google3:///third_party/dart_lang/v2/sdk/lib/_internal/js_runtime/lib/js_helper.dart","google3:///third_party/dart_lang/v2/sdk/lib/_internal/js_runtime/lib/rti.dart","google3:///third_party/dart_lang/v2/sdk/lib/_internal/js_runtime/lib/native_helper.dart","google3:///third_party/dart_lang/v2/sdk/lib/_internal/js_runtime/lib/core_patch.dart","google3:///third_party/dart_lang/v2/sdk/lib/_internal/js_runtime/lib/js_rti.dart","google3:///third_party/dart_lang/v2/sdk/lib/core/exceptions.dart","google3:///third_party/dart_lang/v2/sdk/lib/_internal/js_runtime/lib/interceptors.dart","google3:///third_party/dart_lang/v2/sdk/lib/_internal/js_runtime/lib/regexp_helper.dart","google3:///third_party/dart_lang/v2/sdk/lib/_internal/js_runtime/lib/string_helper.dart","google3:///third_party/dart_lang/v2/sdk/lib/core/iterable.dart","google3:///third_party/dart_lang/v2/sdk/lib/_internal/js_runtime/lib/native_typed_data.dart","google3:///third_party/dart_lang/v2/sdk/lib/_internal/js_runtime/lib/shared/recipe_syntax.dart","google3:///third_party/dart_lang/v2/sdk/lib/_internal/js_runtime/lib/js_names.dart","google3:///third_party/dart_lang/v2/sdk/lib/_internal/js_runtime/lib/js_primitives.dart","google3:///third_party/dart_lang/v2/sdk/lib/_internal/js_runtime/lib/js_array.dart","google3:///third_party/dart_lang/v2/sdk/lib/_internal/js_runtime/lib/js_string.dart","google3:///third_party/dart_lang/v2/sdk/lib/_internal/js_runtime/lib/async_patch.dart","google3:///third_party/dart_lang/v2/sdk/lib/core/duration.dart","google3:///third_party/dart_lang/v2/sdk/lib/async/future_impl.dart","google3:///third_party/dart_lang/v2/sdk/lib/async/zone.dart","google3:///third_party/dart_lang/v2/sdk/lib/async/future.dart","google3:///third_party/dart_lang/v2/sdk/lib/async/timer.dart","google3:///third_party/dart_lang/v2/sdk/lib/async/schedule_microtask.dart","google3:///third_party/dart_lang/v2/sdk/lib/async/stream.dart","google3:///third_party/dart_lang/v2/sdk/lib/async/stream_controller.dart","google3:///third_party/dart_lang/v2/sdk/lib/async/stream_impl.dart","google3:///third_party/dart_lang/v2/sdk/lib/async/stream_pipe.dart","google3:///third_party/dart_lang/v2/sdk/lib/_internal/js_runtime/lib/internal_patch.dart","google3:///third_party/dart_lang/v2/sdk/lib/_internal/js_runtime/lib/collection_patch.dart","google3:///third_party/dart_lang/v2/sdk/lib/_internal/js_runtime/lib/linked_hash_map.dart","google3:///third_party/dart_lang/v2/sdk/lib/collection/hash_map.dart","google3:///third_party/dart_lang/v2/sdk/lib/collection/iterable.dart","google3:///third_party/dart_lang/v2/sdk/lib/collection/linked_hash_map.dart","google3:///third_party/dart_lang/v2/sdk/lib/collection/linked_hash_set.dart","google3:///third_party/dart_lang/v2/sdk/lib/collection/maps.dart","google3:///third_party/dart_lang/v2/sdk/lib/collection/queue.dart","google3:///third_party/dart_lang/v2/sdk/lib/_internal/js_runtime/lib/convert_patch.dart","google3:///third_party/dart_lang/v2/sdk/lib/convert/base64.dart","google3:///third_party/dart_lang/v2/sdk/lib/convert/codec.dart","google3:///third_party/dart_lang/v2/sdk/lib/core/date_time.dart","google3:///third_party/dart_lang/v2/sdk/lib/core/list.dart","google3:///third_party/dart_lang/v2/sdk/lib/core/map.dart","google3:///third_party/dart_lang/v2/sdk/lib/core/print.dart","google3:///third_party/dart_lang/v2/sdk/lib/core/string.dart","google3:///third_party/dart_lang/v2/sdk/lib/core/uri.dart","google3:///third_party/dart_lang/v2/sdk/lib/convert/utf.dart","google3:///third_party/dart_lang/v2/sdk/lib/js_util/js_util.dart","google3:///third_party/dart_lang/v2/sdk/lib/_internal/js_runtime/lib/math_patch.dart","google3:///third_party/dart_lang/v2/sdk/lib/html/dart2js/html_dart2js.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.0.5/lib/src/span_scanner.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/source_span-1.5.5/lib/src/file.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_api/lib/src/remote_listener.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/stream_channel-2.0.0/lib/src/multi_channel.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/stream_channel-2.0.0/lib/src/stream_channel_controller.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_api/lib/src/suite_channel_manager.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/stream_channel-2.0.0/lib/src/guarantee_channel.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_api/lib/src/backend/stack_trace_formatter.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/path-1.6.4/lib/src/style.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/pool-1.4.0/lib/pool.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/async-2.4.0/lib/src/async_memoizer.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/source_map_stack_trace-1.1.5/lib/source_map_stack_trace.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.9.3/lib/src/chain.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.9.3/lib/src/trace.dart","google3:///third_party/dart_lang/v2/sdk/lib/core/stacktrace.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_api/lib/src/backend/group.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_api/lib/src/backend/metadata.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/collection-1.14.12/lib/src/unmodifiable_wrappers.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_api/lib/src/frontend/timeout.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_core/lib/src/runner/engine.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/async-2.4.0/lib/src/future_group.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/async-2.4.0/lib/src/stream_group.dart","google3:///third_party/dart_lang/v2/sdk/lib/async/broadcast_stream_controller.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/source_maps-0.10.8/lib/src/utils.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/collection-1.14.12/lib/src/functions.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/collection-1.14.12/lib/src/union_set_controller.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/collection-1.14.12/lib/src/union_set.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.9.3/lib/src/lazy_trace.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/package_resolver-1.0.10/lib/src/package_config_resolver.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/source_span-1.5.5/lib/src/location.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_api/lib/src/backend/live_test_controller.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_api/lib/src/util/test.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_api/lib/src/backend/invoker.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.0.5/lib/src/exception.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_api/lib/src/backend/platform_selector.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_api/lib/src/backend/suite_platform.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_core/lib/src/util/stack_trace_mapper.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/package_resolver-1.0.10/lib/src/package_root_resolver.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/source_maps-0.10.8/lib/src/vlq.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/source_maps-0.10.8/lib/parser.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test/lib/src/bootstrap/browser.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_core/lib/src/runner/plugin/remote_platform_helpers.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/stream_channel-2.0.0/lib/stream_channel.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/source_span-1.5.5/lib/src/span_exception.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_api/lib/src/frontend/expect.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.6/lib/src/description.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_api/lib/src/utils.dart","file:///usr/local/google/home/grouma/Projects/sample-test/test/main_test.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/path-1.6.4/lib/src/parsed_path.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/path-1.6.4/lib/src/path_exception.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/source_span-1.5.5/lib/src/span_with_context.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_api/lib/src/backend/declarer.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/boolean_selector-1.0.5/lib/src/ast.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/source_span-1.5.5/lib/src/highlighter.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.9.3/lib/src/stack_zone_specification.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.9.3/lib/src/lazy_chain.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_api/lib/src/backend/suite.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_core/lib/src/runner/suite.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_api/lib/src/util/remote_exception.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_core/lib/test_core.dart","","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_api/lib/src/backend/runtime.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_core/lib/src/runner/live_suite_controller.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/package_resolver-1.0.10/lib/src/utils.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/path-1.6.4/lib/src/utils.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/source_span-1.5.5/lib/src/utils.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.0.5/lib/src/utils.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/collection-1.14.12/lib/src/queue_list.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.6/lib/src/util.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.6/lib/src/core_matchers.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.6/lib/src/equals_matcher.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/path-1.6.4/lib/src/context.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/path-1.6.4/lib/path.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.6/lib/src/pretty_print.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.9.3/lib/src/frame.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.9.3/lib/src/unparsed_frame.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test/lib/src/runner/browser/post_message_channel.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_api/lib/src/backend/operating_system.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_api/lib/src/backend/closed_exception.dart","google3:///third_party/dart_lang/v2/sdk/lib/collection/list.dart","google3:///third_party/dart_lang/v2/sdk/lib/_internal/js_runtime/lib/js_number.dart","google3:///third_party/dart_lang/v2/sdk/lib/internal/list.dart","google3:///third_party/dart_lang/v2/sdk/lib/internal/symbol.dart","google3:///third_party/dart_lang/v2/sdk/lib/collection/collections.dart","google3:///third_party/dart_lang/v2/sdk/lib/collection/set.dart","google3:///third_party/dart_lang/v2/sdk/lib/convert/ascii.dart","google3:///third_party/dart_lang/v2/sdk/lib/convert/json.dart","google3:///third_party/dart_lang/v2/sdk/lib/core/object.dart","google3:///third_party/dart_lang/v2/sdk/lib/core/expando.dart","google3:///third_party/dart_lang/v2/sdk/lib/core/null.dart","google3:///third_party/dart_lang/v2/sdk/lib/core/stopwatch.dart","google3:///third_party/dart_lang/v2/sdk/lib/html/html_common/conversions_dart2js.dart","google3:///third_party/dart_lang/v2/sdk/lib/html/html_common/conversions.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/async-2.4.0/lib/src/delegate/stream_subscription.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/async-2.4.0/lib/src/null_stream_sink.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/async-2.4.0/lib/src/result/error.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/async-2.4.0/lib/src/result/value.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/async-2.4.0/lib/src/stream_completer.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/async-2.4.0/lib/src/stream_queue.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/async-2.4.0/lib/src/subscription_stream.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/async-2.4.0/lib/src/stream_sink_completer.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/boolean_selector-1.0.5/lib/src/all.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/boolean_selector-1.0.5/lib/src/evaluator.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/boolean_selector-1.0.5/lib/src/impl.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/boolean_selector-1.0.5/lib/src/intersection_selector.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/boolean_selector-1.0.5/lib/src/validator.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/boolean_selector-1.0.5/lib/src/none.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/boolean_selector-1.0.5/lib/src/parser.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/boolean_selector-1.0.5/lib/src/scanner.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.0.5/lib/src/string_scanner.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/boolean_selector-1.0.5/lib/src/token.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/boolean_selector-1.0.5/lib/src/visitor.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/collection-1.14.12/lib/src/empty_unmodifiable_set.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/collection-1.14.12/lib/src/wrappers.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.6/lib/src/feature_matcher.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.6/lib/src/interfaces.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.6/lib/src/type_matcher.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/path-1.6.4/lib/src/internal_style.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/path-1.6.4/lib/src/style/posix.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/path-1.6.4/lib/src/style/url.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/path-1.6.4/lib/src/style/windows.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/async-2.4.0/lib/src/restartable_timer.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/source_maps-0.10.8/lib/src/source_map_span.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.1.0/lib/src/generated/top_level.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.1.0/lib/term_glyph.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/source_span-1.5.5/lib/src/location_mixin.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/source_span-1.5.5/lib/src/span.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/source_span-1.5.5/lib/src/span_mixin.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/stream_channel-2.0.0/lib/src/stream_channel_completer.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.1.0/lib/src/generated/ascii_glyph_set.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.1.0/lib/src/generated/unicode_glyph_set.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_api/lib/src/backend/live_test.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_api/lib/src/backend/state.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_api/lib/src/backend/message.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_api/lib/src/util/iterable_set.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_core/lib/src/runner/runner_suite.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_core/lib/src/runner/reporter/expanded.dart","file:///usr/local/google/home/grouma/Projects/test/pkgs/test_core/lib/src/util/print_sink.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/async-2.4.0/lib/src/delegate/sink.dart","file:///usr/local/google/home/grouma/.pub-cache/hosted/pub.dartlang.org/async-2.4.0/lib/src/result/result.dart"],"names":["CastIterable","hexDigitValue","SubListIterable","MappedIterable","IterableElementError.noElement","IterableElementError.tooMany","IterableElementError.tooFew","ConstantMap._throwUnmodifiable","instantiate1","Instantiation1","unminifyOrTag","isJsIndexable","S","Primitives.objectHashCode","Primitives.parseInt","Primitives.objectTypeName","Primitives._objectTypeNameNewRti","Primitives._saneNativeClassName","Primitives.dateNow","Primitives.initTicker","Primitives.currentUri","Primitives._fromCharCodeApply","Primitives.stringFromCodePoints","Primitives.stringFromCharCodes","Primitives.stringFromNativeUint8List","Primitives.stringFromCharCode","Primitives.lazyAsJsDate","Primitives.getYear","Primitives.getMonth","Primitives.getDay","Primitives.getHours","Primitives.getMinutes","Primitives.getSeconds","Primitives.getMilliseconds","Primitives.getProperty","Primitives.setProperty","iae","ioore","diagnoseIndexError","diagnoseRangeError","argumentErrorValue","checkNum","wrapException","toStringWrapper","throwExpression","throwConcurrentModificationError","TypeErrorDecoder.extractPattern","TypeErrorDecoder.provokeCallErrorOn","TypeErrorDecoder.provokePropertyErrorOn","NullError","JsNoSuchMethodError","unwrapException","getTraceFromException","objectHashCode","fillLiteralMap","getLength","fillLiteralSet","invokeClosure","Exception","convertDartClosureToJS","Closure.fromTearOff","Closure._computeSignatureFunctionNewRti","Closure.cspForwardCall","Closure.forwardCallTo","Closure.cspForwardInterceptedCall","Closure.forwardInterceptedCallTo","closureFromTearOff","BoundClosure.evalRecipe","evalInInstance","BoundClosure.evalRecipeIntercepted","BoundClosure.selfOf","BoundClosure.receiverOf","BoundClosure.computeFieldNamed","boolConversionCheck","extractFunctionTypeObjectFromInternal","TypeErrorImplementation","_typeDescription","assertTest","assertThrow","assertHelper","throwCyclicInit","RuntimeError","getIsolateAffinityTag","setRuntimeTypeInfo","getRuntimeTypeInfo","getRuntimeTypeArguments","runtimeTypeToString","_runtimeTypeToString","_getRuntimeTypeAsString","getFutureOrArgument","_functionRtiToString","isInterestingBound","_joinArguments","getRuntimeType","_isClosure","substitute","computeSignature","invokeOn","defineProperty","lookupAndCacheInterceptor","patchProto","patchInteriorProto","makeLeafDispatchRecord","makeDefaultDispatchRecord","initNativeDispatch","initNativeDispatchContinue","lookupInterceptor","initHooks","applyHooksTransformer","JSSyntaxRegExp.makeNative","_MatchImplementation","stringContainsUnchecked","escapeReplacement","stringReplaceFirstRE","quoteStringForRegExp","stringReplaceAllUnchecked","stringReplaceAllUncheckedString","_stringIdentity","stringReplaceAllFuncUnchecked","_AllMatchesIterable.iterator","stringReplaceFirstUnchecked","stringReplaceRangeUnchecked","_ensureNativeList","NativeInt8List._create1","_checkValidIndex","_checkValidRange","Rti._getFutureFromFutureOr","Rti._isUnionOfFunctionType","Rti._getInterfaceTypeArguments","Rti._getBindingArguments","Rti._getFunctionParameters","Rti._getGenericFunctionBounds","Rti._getCanonicalRecipe","findType","instantiatedGenericFunctionType","_instantiate","_Universe._canonicalRecipeOfStar","_Universe._canonicalRecipeOfQuestion","_Universe._canonicalRecipeOfFutureOr","_instantiateArray","_instantiateNamed","_instantiateFunctionParameters","_FunctionParameters.allocate","closureFunctionType","instanceOrFunctionType","instanceType","_isDartObject","_arrayInstanceType","_instanceType","_instanceTypeFromConstructor","_instanceTypeFromConstructorMiss","getTypeFromTypesTable","createRuntimeType","typeLiteral","_installSpecializedIsTest","Rti._getInterfaceName","_generalIsTestImplementation","_isTestViaProperty","_generalAsCheckImplementation","_generalTypeCheckImplementation","checkTypeBound","_Error.compose","_CastError.fromMessage","_CastError.forType","_TypeError.fromMessage","_TypeError.forType","_isTop","_asTop","_isBool","_asBoolNullable","_checkBoolNullable","_asDoubleNullable","_checkDoubleNullable","_isInt","_asIntNullable","_checkIntNullable","_isNum","_asNumNullable","_checkNumNullable","_isString","_asStringNullable","_checkStringNullable","_rtiArrayToString","Rti._getReturnType","_rtiToString","Rti._getGenericFunctionParameterIndex","_unminifyOrTag","_Universe.findRule","_Universe.findErasedType","_Universe.addRules","_Universe.addErasedTypes","_Universe.eval","_Universe.evalInEnvironment","_Universe.bind","_Universe._parseRecipe","_Universe._finishRti","_Universe._lookupTerminalRti","Rti.allocate","_Universe._createTerminalRti","_Universe._lookupUnaryRti","_Universe._createUnaryRti","_Universe._lookupGenericFunctionParameterRti","_Universe._createGenericFunctionParameterRti","_Universe._canonicalRecipeJoin","_Universe._canonicalRecipeJoinNamed","_Universe._lookupInterfaceRti","_Universe._createInterfaceRti","_Universe._lookupBindingRti","_Universe._canonicalRecipeOfBinding","_Universe._createBindingRti","_Universe._lookupFunctionRti","_Universe._canonicalRecipeOfFunction","_Universe._createFunctionRti","_Universe._lookupGenericFunctionRti","_Universe._canonicalRecipeOfGenericFunction","_Universe._createGenericFunctionRti","_Parser.create","_Parser.parse","_Parser.pushStackFrame","_Parser.handleTypeArguments","_Parser.collectArray","_Parser.handleFunctionArguments","_Parser.handleOptionalGroup","_Parser.handleNamedGroup","_Parser.collectNamed","_Parser.handleDigit","_Parser.handleIdentifier","_Universe.evalTypeVariable","_Parser.handleExtendedOperations","_Parser.toType","_Parser.toTypes","_Parser.toTypesNamed","_Parser.indexToType","_isSubtype","Rti._getStarArgument","Rti._getGenericFunctionBase","_isFunctionSubtype","_isInterfaceSubtype","typeEqual","Rti._getBindingBase","typesEqual","namedTypesEqual","isLegacyTopType","isTopType","_Utils.objectAssign","extractKeys","unmangleGlobalNameIfPreservedAnyways","printString","makeDispatchRecord","getNativeInterceptor","JSArray.fixed","JSArray.markFixed","JSArray.markFixedList","JSString._isWhitespace","JSString._skipLeadingWhitespace","JSString._skipTrailingWhitespace","_AsyncRun._initializeScheduleImmediate","_AsyncRun._scheduleImmediateJsOverride","_AsyncRun._scheduleImmediateWithSetImmediate","_AsyncRun._scheduleImmediateWithTimer","Timer._createTimer","_TimerImpl","_TimerImpl.periodic","_makeAsyncAwaitCompleter","_AsyncAwaitCompleter._future","_Future","_asyncStartSync","_asyncAwait","_asyncReturn","_asyncRethrow","_awaitOnObject","_wrapJsFunctionForAsync","Future","Future.microtask","Future.sync","_Future.value","_Future.zoneValue","_nonNullError","Future.error","_Future.immediateError","Future.wait","Future.value","_Future.immediate","Future.forEach","JSArray.iterator","Future._kTrue","Future.doWhile","_completeWithErrorCallback","_Future._chainForeignFuture","_Future._setPendingComplete","_Future._chainCoreFuture","_Future._setChained","_Future._propagateToListeners","_Future._error","_Future._removeListeners","_Future._cloneResult","_Future._setErrorObject","_registerErrorHandler","_microtaskLoop","_startMicrotaskLoop","_scheduleAsyncCallback","_schedulePriorityAsyncCallback","scheduleMicrotask","Stream.fromFuture","_StreamController.stream","Stream.fromIterable","StreamIterator","StreamController","_runGuarded","_AddStreamState.makeErrorHandler","_BufferingStreamSubscription","_BufferingStreamSubscription._zone","_nullDataHandler","_nullErrorHandler","_nullDoneHandler","_runUserCode","_cancelAndError","_cancelAndErrorClosure","_cancelAndValue","Timer","_ZoneSpecification","Zone._enter","_parentDelegate","_rootHandleUncaughtError","_rootRun","_rootRunUnary","_rootRunBinary","_rootRegisterCallback","_rootRegisterUnaryCallback","_rootRegisterBinaryCallback","_rootErrorCallback","_rootScheduleMicrotask","_rootCreateTimer","_rootCreatePeriodicTimer","Timer._createPeriodicTimer","_rootPrint","_printToZone","_rootFork","_CustomZone","runZoned","_runZoned","HashMap","_HashMap._getTableEntry","_HashMap._setTableEntry","_HashMap._newHashTable","JsLinkedHashMap.es6","LinkedHashMap","LinkedHashMap._literal","LinkedHashMap._empty","LinkedHashSet","LinkedHashSet._empty","LinkedHashSet._literal","_LinkedHashSet._newHashTable","_LinkedHashSetIterator","HashMap.from","IterableBase.iterableToShortString","IterableBase.iterableToFullString","_isToStringVisiting","_iterablePartsToStrings","LinkedHashMap.from","LinkedHashSet.from","MapBase.mapToString","ListQueue","_ListQueueIterator","_parseJson","_convertJsonToDartLazy","Utf8Decoder._convertIntercepted","Utf8Decoder._convertInterceptedUint8List","Utf8Decoder._useTextDecoderChecked","Utf8Decoder._useTextDecoderUnchecked","Utf8Decoder._unsafe","Utf8Decoder._makeDecoder","_scanOneByteCharacters","Base64Codec._checkPadding","int.parse","Error._objectToString","List.filled","List.from","List.unmodifiable","String.fromCharCodes","String.fromCharCode","String._stringFromIterable","RegExp","StringBuffer._writeAll","Uri.base","_Uri._uriEncode","JSSyntaxRegExp.hasMatch","StringBuffer.writeCharCode","StackTrace.current","DateTime._fourDigits","DateTime._threeDigits","DateTime._twoDigits","Duration","Error.safeToString","AssertionError","ArgumentError","ArgumentError.value","ArgumentError.notNull","RangeError","RangeError.value","RangeError.range","RangeError.checkValueInInterval","RangeError.checkValidRange","RangeError.checkNotNegative","IndexError","UnsupportedError","UnimplementedError","StateError","ConcurrentModificationError","FormatException","List.generate","Map.castFrom","print","_combineSurrogatePair","Uri.parse","Uri.decodeComponent","Uri._parseIPv4Address","Uri.parseIPv6Address","NativeUint8List","_Uri.notSimple","_Uri","JSString.isNotEmpty","_Uri._defaultPort","_Uri._fail","_Uri.file","_Uri._checkNonWindowsPathReservedCharacters","_Uri._checkWindowsPathReservedCharacters","ListIterable.iterator","_Uri._checkWindowsDriveLetter","_Uri._makeFileUri","_Uri._makeWindowsFileUrl","_Uri._makePort","_Uri._makeHost","_Uri._checkZoneID","_Uri._normalizeZoneID","StringBuffer.write","_Uri._normalizeRegName","_Uri._makeScheme","_Uri._canonicalizeScheme","_Uri._makeUserInfo","_Uri._makePath","JSArray.map","_Uri._normalizePath","_Uri._makeQuery","_Uri._makeFragment","_Uri._normalizeEscape","_Uri._escapeChar","_Uri._normalizeOrSubstring","_Uri._normalize","_Uri._mayContainDotSegments","_Uri._removeDotSegments","JSArray.isNotEmpty","_Uri._normalizeRelativePath","_Uri._escapeScheme","_Uri._toWindowsFilePath","_Uri._hexCharPairToByte","_Uri._uriDecode","JSString.codeUnits","Utf8Codec.decode","_Uri._isAlphabeticCharacter","UriData._writeUri","UriData._validateMimeType","UriData._parse","UriData._uriEncodeBytes","_createTables","_scan","_convertDataTree","promiseToFuture","_Completer.future","Completer","max","pow","_EventStreamSubscription","_wrapZone","SpanScanner","SourceFile.decoded","SourceFile.fromString","RemoteListener.start","SuiteChannelManager","RemoteListener._deserializeSet","RemoteListener._sendLoadException","_MultiChannel.sink","RemoteListener._sendError","StackTraceFormatter.current","Style._getPlatformStyle","Pool","Pool._closeMemo","mapStackTrace","Trace","_prettifyMember","Group","Metadata._parseOnPlatform","Metadata._parseTags","Metadata","Metadata._","Metadata.parse","Metadata.deserialize","Metadata._deserializeTimeout","Engine","FutureGroup","Engine._onTestStartedGroup","StreamGroup.broadcast","StreamController.broadcast","Engine._group","binarySearch","mapMap","mergeMaps","UnionSetController","FileLocation._","_FileSpan","Trace.current","Trace.from","Trace.parse","Trace.parseVM","Trace._parseVM","ListIterable.map","Trace.parseV8","Trace.parseJSCore","WhereIterable.map","JSArray.where","Trace.parseFirefox","Trace.parseFriendly","PackageConfigResolver._normalizeMap","SourceLocation","LiveTestController","errorsDontStopTest","Invoker.current","StringScannerException","PlatformSelector.parse","PlatformSelector._wrapFormatException","SuitePlatform","JSStackTraceMapper.deserialize","JSStackTraceMapper._deserializePackageConfigMap","UnmodifiableSetView","decodeVlq","internalBootstrapBrowserTest","StreamChannelMixin.pipe","GuaranteeChannel.stream","SourceSpanFormatException","expect","_expect","Invoker.closed","Invoker._closable","_Completer.isCompleted","fail","formatFailure","prettyPrint","StringDescription._out","StringDescription.toString","StringBuffer.writeln","method1","method2","main","parseJsonExtended","parseJson","MultiSectionMapping.fromJson","MappingBundle.fromJson","SingleMapping.fromJson","ParsedPath.parse","PathException","SourceSpanWithContext","Declarer","Declarer._","Declarer._timeout","_expandSafe","Highlighter._normalizeNewlines","Highlighter._normalizeTrailingNewline","Highlighter._normalizeEndOfLine","Highlighter._lastLineLength","Chain.capture","Expando","StackZoneSpecification._chains","Chain.current","Chain._currentSpec","StackZoneSpecification.currentChain","StackZoneSpecification._createNode","Chain.forTrace","Chain.parse","Invoker.guard","Suite._filterGroup","Group.root","SuiteConfiguration._list","SuiteConfiguration._map","RemoteException.serialize","_declarer","Declarer.current","test","group","StreamChannelController","_StreamController.sink","Runtime.deserialize","indent","toSentence","pluralize","prefixLines","LiveSuiteController","LiveSuiteController._onCompleteGroup","LiveSuiteController._closeMemo","asUri","ensureTrailingSlash","JSArray.toList","isAlphabetic","isDriveLetter","countCodeUnits","ListMixin.iterator","findLineStart","validateErrorArgs","QueueList","QueueList._nextPowerOf2","wrapMatcher","predicate","equals","escape","_getHexLiteral","JSString.runes","Context","_parseUri","_validateArgList","_StringEqualsMatcher._writeLeading","_StringEqualsMatcher._writeTrailing","_MultiChannel","current","_typeName","_escapeString","Frame.parseVM","Frame.parseV8","Frame.parseFirefox","Frame.parseFriendly","Frame._uriOrPathToUri","Frame._catchFormatException","UnparsedFrame","postMessageChannel","EventStreamProvider.forTarget","OperatingSystem.find","GuaranteeChannel","ClosedException","Interceptor.hashCode","Interceptor.==","Interceptor.toString","Interceptor.runtimeType","JSBool.toString","JSBool.hashCode","JSBool.runtimeType","JSNull.==","JSNull.toString","JSNull.hashCode","JSNull.runtimeType","JavaScriptObject.hashCode","JavaScriptObject.runtimeType","JavaScriptObject.toString","JavaScriptFunction.toString","JSArray.add","JSArray.removeAt","JSArray.insert","JSArray.insertAll","JSArray.removeLast","JSArray.remove","JSArray.addAll","JSArray.forEach","JSArray.join","JSArray.join[function-entry$0]","JSArray.fold","JSArray.firstWhere","JSArray.firstWhere[function-entry$1]","JSArray.elementAt","JSArray.sublist","JSArray.first","JSArray.last","JSArray.single","JSArray.setRange","JSArray.setRange[function-entry$3]","JSArray.fillRange","JSArray.replaceRange","JSArray.contains","JSArray.isEmpty","JSArray.toString","JSArray.toSet","JSArray.hashCode","JSArray.length","JSArray.[]","JSArray.[]=","ArrayIterator.current","ArrayIterator.moveNext","ArrayIterator._current","JSNumber.floor","JSNumber.round","JSNumber.toRadixString","JSNumber.toString","JSNumber.hashCode","JSNumber.%","JSNumber.~/","JSNumber._tdivFast","JSNumber._tdivSlow","JSNumber._shlPositive","JSNumber._shrOtherPositive","JSNumber._shrReceiverPositive","JSNumber._shrBothPositive","JSNumber.runtimeType","JSInt.runtimeType","JSDouble.runtimeType","JSString.codeUnitAt","JSString._codeUnitAt","JSString.allMatches","allMatchesInStringUnchecked","JSString.allMatches[function-entry$1]","JSString.matchAsPrefix","JSString.+","JSString.endsWith","JSString.splitMapJoin","JSString.replaceFirst","JSString.replaceRange","JSString.startsWith","JSString.startsWith[function-entry$1]","JSString.substring","JSString.substring[function-entry$1]","JSString.trim","JSString.*","JSString.padLeft","JSString.padRight","JSString.indexOf","JSString.indexOf[function-entry$1]","JSString.lastIndexOf","JSString.lastIndexOf[function-entry$1]","JSString.contains","JSString.toString","JSString.hashCode","JSString.runtimeType","JSString.length","JSString.[]","_CastIterableBase.iterator","_CastIterableBase.length","_CastIterableBase.isEmpty","_CastIterableBase.first","_CastIterableBase.last","_CastIterableBase.contains","_CastIterableBase.toString","CastIterator.moveNext","CastIterator.current","CastMap.cast","CastMap.containsKey","CastMap.[]","CastMap.remove","CastMap.forEach","CastMap.keys","CastMap.length","CastMap.isEmpty","CastMap.forEach.<anonymous function>","CastMap_forEach_closure","CodeUnits.[]","CodeUnits.length","ListIterable.isEmpty","ListIterable.first","ListIterable.last","ListIterable.contains","ListIterable.join","ListIterable.join[function-entry$0]","ListIterable.fold","ListIterable.toList","ListIterable.toList[function-entry$0]","ListIterable.toSet","SubListIterable._endIndex","SubListIterable._startIndex","SubListIterable.length","SubListIterable.elementAt","SubListIterable.take","ListIterator.current","ListIterator.moveNext","ListIterator._current","MappedIterable.iterator","MappedIterable.length","MappedIterable.isEmpty","MappedIterable.first","MappedIterable.last","MappedIterator.moveNext","MappedIterator.current","MappedIterator._current","MappedListIterable.length","MappedListIterable.elementAt","WhereIterable.iterator","WhereIterator.moveNext","WhereIterator.current","ExpandIterable.iterator","ExpandIterator","ExpandIterator.current","ExpandIterator.moveNext","ExpandIterator._currentExpansion","ExpandIterator._current","SkipWhileIterable.iterator","SkipWhileIterator.moveNext","SkipWhileIterator.current","EmptyIterator.moveNext","EmptyIterator.current","FixedLengthListMixin.length","UnmodifiableListMixin.[]=","UnmodifiableListMixin.length","ReversedListIterable.length","ReversedListIterable.elementAt","Symbol.hashCode","Symbol.toString","Symbol.==","ConstantMap.cast","ConstantMap.isEmpty","ConstantMap.toString","ConstantMap.remove","ConstantMap.map","ConstantMap.map.<anonymous function>","ConstantMap_map_closure","ConstantStringMap.length","ConstantStringMap.containsKey","ConstantStringMap.[]","ConstantStringMap._fetch","ConstantStringMap.forEach","ConstantStringMap.keys","_ConstantMapKeyIterable.iterator","_ConstantMapKeyIterable.length","Instantiation","Instantiation.toString","Primitives.initTicker.<anonymous function>","TypeErrorDecoder.matchTypeError","NullError.toString","JsNoSuchMethodError.toString","UnknownJsTypeError.toString","unwrapException.saveStackTrace","_StackTrace.toString","Closure.toString","StaticClosure.toString","BoundClosure.==","BoundClosure.hashCode","BoundClosure.toString","TypeErrorImplementation.toString","RuntimeError.toString","_AssertionError.toString","JsLinkedHashMap.keys","JsLinkedHashMap.length","JsLinkedHashMap.isEmpty","JsLinkedHashMap.values","JsLinkedHashMap.containsKey","JsLinkedHashMap.internalContainsKey","JsLinkedHashMap.addAll","JsLinkedHashMap.[]","JsLinkedHashMap.internalGet","JsLinkedHashMap.[]=","JsLinkedHashMap.internalSet","JsLinkedHashMap.putIfAbsent","JsLinkedHashMap.remove","JsLinkedHashMap.internalRemove","JsLinkedHashMap.clear","JsLinkedHashMap.forEach","JsLinkedHashMap._addHashTableEntry","JsLinkedHashMap._removeHashTableEntry","JsLinkedHashMap._modified","JsLinkedHashMap._newLinkedCell","JsLinkedHashMap._unlinkCell","JsLinkedHashMap.internalComputeHashCode","JsLinkedHashMap.internalFindBucketIndex","JsLinkedHashMap.toString","JsLinkedHashMap._getTableCell","JsLinkedHashMap._getTableBucket","JsLinkedHashMap._setTableEntry","JsLinkedHashMap._deleteTableEntry","JsLinkedHashMap._containsTableEntry","JsLinkedHashMap._newHashTable","JsLinkedHashMap.values.<anonymous function>","JsLinkedHashMap_values_closure","JsLinkedHashMap.addAll.<anonymous function>","JsLinkedHashMap_addAll_closure","LinkedHashMapKeyIterable.length","LinkedHashMapKeyIterable.isEmpty","LinkedHashMapKeyIterable.iterator","LinkedHashMapKeyIterable.contains","LinkedHashMapKeyIterator","LinkedHashMapKeyIterator.current","LinkedHashMapKeyIterator.moveNext","LinkedHashMapKeyIterator._current","initHooks.<anonymous function>","JSSyntaxRegExp.toString","JSSyntaxRegExp._nativeGlobalVersion","JSSyntaxRegExp._nativeAnchoredVersion","JSSyntaxRegExp.firstMatch","JSSyntaxRegExp.allMatches","JSSyntaxRegExp.allMatches[function-entry$1]","JSSyntaxRegExp._execGlobal","JSSyntaxRegExp._execAnchored","JSSyntaxRegExp.matchAsPrefix","_MatchImplementation.start","_MatchImplementation.end","_MatchImplementation.[]","_AllMatchesIterator.current","_AllMatchesIterator.moveNext","JSSyntaxRegExp.isUnicode","StringMatch.end","StringMatch.[]","_StringAllMatchesIterable.iterator","_StringAllMatchesIterable.first","_StringAllMatchesIterator.moveNext","_StringAllMatchesIterator.current","NativeByteBuffer.runtimeType","NativeByteData.runtimeType","NativeTypedArray.length","NativeTypedArrayOfDouble.[]","NativeTypedArrayOfDouble.[]=","NativeTypedArrayOfInt.[]=","NativeFloat32List.runtimeType","NativeFloat64List.runtimeType","NativeInt16List.runtimeType","NativeInt16List.[]","NativeInt32List.runtimeType","NativeInt32List.[]","NativeInt8List.runtimeType","NativeInt8List.[]","NativeUint16List.runtimeType","NativeUint16List.[]","NativeUint32List.runtimeType","NativeUint32List.[]","NativeUint32List.sublist","NativeUint8ClampedList.runtimeType","NativeUint8ClampedList.length","NativeUint8ClampedList.[]","NativeUint8List.runtimeType","NativeUint8List.length","NativeUint8List.[]","NativeUint8List.sublist","Rti._eval","Rti._bind","_Type.hashCode","_Type.==","_Type.toString","_Error.toString","_TypeError.message","_AsyncRun._initializeScheduleImmediate.internalCallback","_AsyncRun._initializeScheduleImmediate.<anonymous function>","_AsyncRun._scheduleImmediateJsOverride.internalCallback","_AsyncRun._scheduleImmediateWithSetImmediate.internalCallback","_TimerImpl.cancel","_TimerImpl.internalCallback","_TimerImpl.periodic.<anonymous function>","_AsyncAwaitCompleter.complete","_AsyncAwaitCompleter.completeError","_awaitOnObject.<anonymous function>","_wrapJsFunctionForAsync.<anonymous function>","_BroadcastStream.isBroadcast","_BroadcastSubscription._onPause","_BroadcastSubscription._onResume","_BroadcastSubscription._next","_BroadcastSubscription._previous","_BroadcastStreamController._mayAddEvent","_BroadcastStreamController._ensureDoneFuture","_BroadcastStreamController._removeListener","_BroadcastStreamController._subscribe","_DoneStreamSubscription","_BroadcastSubscription","_BroadcastStreamController._recordCancel","_BroadcastStreamController._recordPause","_BroadcastStreamController._recordResume","_BroadcastStreamController._addEventError","_BroadcastStreamController.add","_BroadcastStreamController.addError","_BroadcastStreamController.addError[function-entry$1]","_BroadcastStreamController.close","_BroadcastStreamController._addError","_BroadcastStreamController._close","_BroadcastStreamController._forEachListener","_BroadcastStreamController._callOnCancel","_BroadcastStreamController._firstSubscription","_BroadcastStreamController._lastSubscription","_BroadcastStreamController._addStreamState","_SyncBroadcastStreamController._mayAddEvent","_SyncBroadcastStreamController._addEventError","_SyncBroadcastStreamController._sendData","_SyncBroadcastStreamController._sendError","_SyncBroadcastStreamController._sendDone","_SyncBroadcastStreamController._sendData.<anonymous function>","_SyncBroadcastStreamController__sendData_closure","_SyncBroadcastStreamController._sendError.<anonymous function>","_SyncBroadcastStreamController__sendError_closure","_SyncBroadcastStreamController._sendDone.<anonymous function>","_SyncBroadcastStreamController__sendDone_closure","_AsyncBroadcastStreamController._sendData","_AsyncBroadcastStreamController._sendError","_AsyncBroadcastStreamController._sendDone","Future.<anonymous function>","Future.microtask.<anonymous function>","Future.wait.handleError","Future.wait.<anonymous function>","Future_wait_closure","Future.forEach.<anonymous function>","Future.doWhile.<anonymous function>","_asyncCompleteWithErrorCallback","TimeoutException.toString","_Completer.completeError","_Completer.completeError[function-entry$1]","_AsyncCompleter.complete","_AsyncCompleter.complete[function-entry$0]","_AsyncCompleter._completeError","_SyncCompleter.complete","_SyncCompleter.complete[function-entry$0]","_SyncCompleter._completeError","_FutureListener.matchesErrorTest","_FutureListener.handleError","_FutureListener.hasErrorCallback","_Future.then","_Future.then[function-entry$1]","_Future._thenAwait","_Future.catchError","_Future.whenComplete","_Future._addListener","_Future._prependListeners","_Future._reverseListeners","_Future._complete","_Future._setValue","_Future._completeWithValue","_Future._completeError","_Future._setError","_Future._completeError[function-entry$1]","_Future._asyncComplete","_Future._chainFuture","_Future._asyncCompleteError","_Future._addListener.<anonymous function>","_Future._prependListeners.<anonymous function>","_Future._chainForeignFuture.<anonymous function>","_Future._clearPendingComplete","_Future._chainForeignFuture[function-entry$1].<anonymous function>","_Future._asyncComplete.<anonymous function>","_Future._chainFuture.<anonymous function>","_Future._asyncCompleteError.<anonymous function>","_Future._propagateToListeners.handleWhenCompleteCallback","_FutureListener.handleWhenComplete","_Future._propagateToListeners.handleWhenCompleteCallback.<anonymous function>","_Future._propagateToListeners.handleValueCallback","_FutureListener.handleValue","_FutureListener._onValue","_Future._propagateToListeners.handleError","Stream.isBroadcast","Stream.pipe","Stream.length","Stream.first","Stream.last","Stream.firstWhere","Stream.fromFuture.<anonymous function>","Stream_Stream$fromFuture_closure","Stream.fromIterable.<anonymous function>","_IterablePendingEvents","Stream_Stream$fromIterable_closure","Stream.pipe.<anonymous function>","Stream.length.<anonymous function>","Stream_length_closure","Stream.first.<anonymous function>","Stream_first_closure","Stream.last.<anonymous function>","Stream_last_closure","Stream.firstWhere.<anonymous function>","Stream_firstWhere_closure","Stream.firstWhere.<anonymous function>.<anonymous function>","_StreamController._pendingEvents","_StreamController._ensurePendingEvents","_StreamController._subscription","_StreamController._badEventState","_StreamController.addStream","_AddStreamState","_StreamControllerAddStreamState","_StreamController.isPaused","_StreamController._ensureDoneFuture","_StreamController.add","_StreamController.addError","_StreamController.addError[function-entry$1]","_StreamController.close","_StreamController._closeUnchecked","_StreamController._add","_StreamController._addError","_StreamController._close","_StreamController._subscribe","_ControllerSubscription","_StreamController._recordCancel","_StreamController._recordPause","_StreamController._recordResume","_StreamController._subscribe.<anonymous function>","_StreamController._recordCancel.complete","_SyncStreamControllerDispatch._sendData","_SyncStreamControllerDispatch._sendError","_SyncStreamControllerDispatch._sendDone","_AsyncStreamControllerDispatch._sendData","_AsyncStreamControllerDispatch._sendError","_AsyncStreamControllerDispatch._sendDone","_ControllerStream._createSubscription","_ControllerStream.hashCode","_ControllerStream.==","_ControllerSubscription._onCancel","_ControllerSubscription._onPause","_ControllerSubscription._onResume","_StreamSinkWrapper.add","_AddStreamState.cancel","_AddStreamState.makeErrorHandler.<anonymous function>","_AddStreamState.cancel.<anonymous function>","_BufferingStreamSubscription._setPendingEvents","_BufferingStreamSubscription.onData","_BufferingStreamSubscription.onError","_BufferingStreamSubscription.onDone","_BufferingStreamSubscription.pause","_BufferingStreamSubscription.pause[function-entry$0]","_BufferingStreamSubscription.resume","_BufferingStreamSubscription.cancel","_BufferingStreamSubscription._mayResumeInput","_BufferingStreamSubscription.isPaused","_BufferingStreamSubscription._cancel","_BufferingStreamSubscription._add","_BufferingStreamSubscription._addError","_BufferingStreamSubscription._close","_BufferingStreamSubscription._onPause","_BufferingStreamSubscription._onResume","_BufferingStreamSubscription._onCancel","_BufferingStreamSubscription._addPending","_BufferingStreamSubscription._sendData","_BufferingStreamSubscription._sendError","_BufferingStreamSubscription._sendDone","_BufferingStreamSubscription._guardCallback","_BufferingStreamSubscription._checkState","_BufferingStreamSubscription._onData","_BufferingStreamSubscription._onDone","_BufferingStreamSubscription._pending","_BufferingStreamSubscription._sendError.sendError","_BufferingStreamSubscription._sendDone.sendDone","_StreamImpl.listen","_StreamImpl.listen[function-entry$1$onDone]","_StreamImpl.listen[function-entry$1$onDone$onError]","_StreamImpl.listen[function-entry$1]","_StreamImpl._createSubscription","_GeneratedStreamImpl._createSubscription","_IterablePendingEvents.isEmpty","_IterablePendingEvents.handleNext","_IterablePendingEvents._iterator","_DelayedEvent.next","_DelayedData.perform","_DelayedError.perform","_DelayedDone.perform","_DelayedDone.next","_PendingEvents.schedule","_PendingEvents.schedule.<anonymous function>","_StreamImplEvents.isEmpty","_StreamImplEvents.add","_StreamImplEvents.handleNext","_DoneStreamSubscription.isPaused","_DoneStreamSubscription._schedule","_DoneStreamSubscription.onData","_DoneStreamSubscription.onError","_DoneStreamSubscription.onDone","_DoneStreamSubscription.pause","_DoneStreamSubscription.pause[function-entry$0]","_DoneStreamSubscription.resume","_DoneStreamSubscription.cancel","_DoneStreamSubscription._sendDone","_DoneStreamSubscription._onDone","_EmptyStream.isBroadcast","_EmptyStream.listen","_EmptyStream.listen[function-entry$1$onDone$onError]","_EmptyStream.listen[function-entry$1]","_cancelAndError.<anonymous function>","_cancelAndErrorClosure.<anonymous function>","_cancelAndValue.<anonymous function>","AsyncError.toString","_ZoneDelegate.handleUncaughtError","_ZoneDelegate.registerCallback","_ZoneDelegate.registerUnaryCallback","_ZoneDelegate.registerBinaryCallback","_ZoneDelegate.errorCallback","_CustomZone._delegate","_CustomZone.errorZone","_CustomZone.runGuarded","_CustomZone.runUnaryGuarded","_CustomZone.runBinaryGuarded","_CustomZone.bindCallback","_CustomZone.bindUnaryCallback","_CustomZone.bindCallbackGuarded","_CustomZone.bindUnaryCallbackGuarded","_CustomZone.[]","_CustomZone.handleUncaughtError","_CustomZone.fork","_CustomZone.run","_CustomZone.runUnary","_CustomZone.runBinary","_CustomZone.registerCallback","_CustomZone.registerUnaryCallback","_CustomZone.registerBinaryCallback","_CustomZone.errorCallback","_CustomZone.scheduleMicrotask","_CustomZone.createTimer","_CustomZone.print","_CustomZone._run","_CustomZone._runUnary","_CustomZone._runBinary","_CustomZone._registerCallback","_CustomZone._registerUnaryCallback","_CustomZone._registerBinaryCallback","_CustomZone._errorCallback","_CustomZone._scheduleMicrotask","_CustomZone._createTimer","_CustomZone._createPeriodicTimer","_CustomZone._print","_CustomZone._fork","_CustomZone._handleUncaughtError","_CustomZone.bindCallback.<anonymous function>","_CustomZone_bindCallback_closure","_CustomZone.bindUnaryCallback.<anonymous function>","_CustomZone_bindUnaryCallback_closure","_CustomZone.bindCallbackGuarded.<anonymous function>","_CustomZone.bindUnaryCallbackGuarded.<anonymous function>","_CustomZone_bindUnaryCallbackGuarded_closure","_rootHandleUncaughtError.<anonymous function>","_RootZone._map","_RootZone._run","_RootZone._runUnary","_RootZone._runBinary","_RootZone._registerCallback","_RootZone._registerUnaryCallback","_RootZone._registerBinaryCallback","_RootZone._errorCallback","_RootZone._scheduleMicrotask","_RootZone._createTimer","_RootZone._createPeriodicTimer","_RootZone._print","_RootZone._fork","_RootZone._handleUncaughtError","_RootZone.parent","_RootZone._delegate","_RootZone.errorZone","_RootZone.runGuarded","_RootZone.runUnaryGuarded","_RootZone.runBinaryGuarded","_RootZone.bindCallback","_RootZone.bindCallbackGuarded","_RootZone.bindUnaryCallbackGuarded","_RootZone.[]","_RootZone.handleUncaughtError","_RootZone.fork","_RootZone.run","_RootZone.runUnary","_RootZone.runBinary","_RootZone.registerCallback","_RootZone.registerUnaryCallback","_RootZone.registerBinaryCallback","_RootZone.errorCallback","_RootZone.scheduleMicrotask","_RootZone.createTimer","_RootZone.print","_RootZone.bindCallback.<anonymous function>","_RootZone_bindCallback_closure","_RootZone.bindCallbackGuarded.<anonymous function>","_RootZone.bindUnaryCallbackGuarded.<anonymous function>","_RootZone_bindUnaryCallbackGuarded_closure","runZoned.<anonymous function>","_HashMap.keys","_HashMap.length","_HashMap.isEmpty","_HashMap.containsKey","_HashMap._containsKey","_HashMap.[]","_HashMap._get","_HashMap.[]=","_HashMap._set","_HashMap.remove","_HashMap._remove","_HashMap.forEach","_HashMap._computeKeys","_HashMap._addHashTableEntry","_HashMap._computeHashCode","_HashMap._getBucket","_HashMap._findBucketIndex","_IdentityHashMap._computeHashCode","_IdentityHashMap._findBucketIndex","_HashMapKeyIterable.length","_HashMapKeyIterable.isEmpty","_HashMapKeyIterable.iterator","_HashMapKeyIterable.contains","_HashMapKeyIterator.current","_HashMapKeyIterator.moveNext","_HashMapKeyIterator._current","_LinkedHashSet._newSet","_LinkedHashSet.iterator","_LinkedHashSet.length","_LinkedHashSet.isEmpty","_LinkedHashSet.contains","_LinkedHashSet._contains","_LinkedHashSet.first","_LinkedHashSet.last","_LinkedHashSet.add","_LinkedHashSet._add","_LinkedHashSet.remove","_LinkedHashSet._remove","_LinkedHashSet._addHashTableEntry","_LinkedHashSet._removeHashTableEntry","_LinkedHashSet._modified","_LinkedHashSet._newLinkedCell","_LinkedHashSet._unlinkCell","_LinkedHashSet._computeHashCode","_LinkedHashSet._findBucketIndex","_LinkedHashSetIterator.current","_LinkedHashSetIterator.moveNext","_LinkedHashSetIterator._current","UnmodifiableListView.length","UnmodifiableListView.[]","HashMap.from.<anonymous function>","LinkedHashMap.from.<anonymous function>","ListMixin.elementAt","ListMixin.isEmpty","ListMixin.first","ListMixin.last","ListMixin.map","ListMixin.toList","ListMixin.toList[function-entry$0]","ListMixin.toSet","ListMixin.remove","ListMixin._closeGap","ListMixin.fillRange","ListMixin.toString","MapBase.mapToString.<anonymous function>","MapMixin.cast","MapMixin.forEach","MapMixin.map","MapMixin.containsKey","MapMixin.length","MapMixin.isEmpty","MapMixin.toString","_UnmodifiableMapMixin.remove","MapView.cast","MapView.[]","MapView.containsKey","MapView.forEach","MapView.isEmpty","MapView.length","MapView.keys","MapView.remove","MapView.toString","MapView.map","UnmodifiableMapView.cast","ListQueue.iterator","ListQueue.isEmpty","ListQueue.length","ListQueue.first","ListQueue.last","ListQueue.elementAt","ListQueue.clear","ListQueue.toString","ListQueue.removeFirst","ListQueue._add","ListQueue._table","_ListQueueIterator.current","_ListQueueIterator.moveNext","_ListQueueIterator._current","SetMixin.isEmpty","SetMixin.map","SetMixin.toString","SetMixin.every","SetMixin.first","SetMixin.last","_SetBase.toSet","_SetBase.isEmpty","_SetBase.addAll","_SetBase.union","_SetBase.toList","_SetBase.toList[function-entry$0]","_SetBase.map","_SetBase.toString","_SetBase.where","_SetBase.fold","_SetBase.every","_SetBase.any","_SetBase.first","_SetBase.last","_JsonMap.[]","_JsonMap.length","_JsonMap.isEmpty","_JsonMap.keys","_JsonMap.containsKey","_JsonMap.remove","_JsonMap.forEach","_JsonMap._upgradedMap","_JsonMap._computeKeys","_JsonMap._upgrade","_JsonMap._process","_JsonMapKeyIterable.length","_JsonMapKeyIterable.elementAt","_JsonMapKeyIterable.iterator","_JsonMapKeyIterable.contains","AsciiCodec.encode","_UnicodeSubsetEncoder.convert","Base64Codec.normalize","JsonCodec.decode","JsonCodec.decoder","Utf8Codec.encoder","Utf8Encoder.convert","_Utf8Encoder._writeSurrogate","_Utf8Encoder._fillBuffer","Utf8Decoder.convert","_Utf8Decoder.flush","_Utf8Decoder.convert","DateTime.==","DateTime.hashCode","DateTime.toString","Duration.==","Duration.hashCode","Duration.toString","Duration.unary-","Duration.toString.sixDigits","Duration.toString.twoDigits","AssertionError.toString","NullThrownError.toString","ArgumentError._errorName","ArgumentError._errorExplanation","ArgumentError.toString","RangeError._errorName","RangeError._errorExplanation","IndexError._errorName","IndexError._errorExplanation","UnsupportedError.toString","UnimplementedError.toString","StateError.toString","ConcurrentModificationError.toString","OutOfMemoryError.toString","StackOverflowError.toString","CyclicInitializationError.toString","_Exception.toString","FormatException.toString","Expando.[]","Expando.[]=","Expando._setOnObject","Expando.toString","Iterable.map","Iterable.where","Iterable.contains","Iterable.join","Iterable.join[function-entry$0]","Iterable.toList","Iterable.toList[function-entry$0]","Iterable.toSet","Iterable.length","Iterable.isEmpty","Iterable.skipWhile","Iterable.first","Iterable.last","Iterable.elementAt","Iterable.toString","MapEntry.toString","Null.hashCode","Null.toString","Object.hashCode","Object.==","Object.toString","Object.runtimeType","_StringStackTrace.toString","Stopwatch.elapsedMicroseconds","Stopwatch.elapsedTicks","Runes.iterator","Runes.last","RuneIterator.current","RuneIterator.moveNext","StringBuffer.length","StringBuffer.toString","Uri._parseIPv4Address.error","Uri.parseIPv6Address.error","Uri.parseIPv6Address[function-entry$1].error","Uri.parseIPv6Address.parseHex","_Uri.userInfo","_Uri.host","_Uri.port","_Uri.query","_Uri.fragment","_Uri.replace","_Uri.replace[function-entry$0$path]","_Uri.replace[function-entry$0$pathSegments]","_Uri.pathSegments","_Uri._mergePaths","_Uri.resolve","_Uri.resolveUri","_Uri.hasEmptyPath","_Uri.hasAuthority","_Uri.hasPort","_Uri.hasQuery","_Uri.hasFragment","_Uri.hasAbsolutePath","_Uri.toFilePath","_Uri._toFilePath","_Uri.toString","_Uri._initializeText","_Uri._writeAuthority","_Uri.==","_Uri.hashCode","_Uri._pathSegments","_Uri.notSimple.<anonymous function>","_Uri._checkNonWindowsPathReservedCharacters.<anonymous function>","_Uri._makePath.<anonymous function>","UriData.uri","UriData.toString","_createTables.<anonymous function>","_createTables.build","_createTables.setChars","_createTables.setRange","_SimpleUri._isFile","_SimpleUri.hasAuthority","_SimpleUri.hasPort","_SimpleUri.hasQuery","_SimpleUri.hasFragment","_SimpleUri._isHttp","_SimpleUri._isHttps","_SimpleUri.hasAbsolutePath","_SimpleUri.scheme","_SimpleUri.userInfo","_SimpleUri.host","_SimpleUri.port","_SimpleUri.path","_SimpleUri.query","_SimpleUri.fragment","_SimpleUri.pathSegments","_SimpleUri._isPort","_SimpleUri.removeFragment","_SimpleUri.replace","_SimpleUri.replace[function-entry$0$path]","_SimpleUri.replace[function-entry$0$pathSegments]","_SimpleUri.resolve","_SimpleUri.resolveUri","_SimpleUri._simpleMerge","_SimpleUri.toFilePath","_SimpleUri._toFilePath","_SimpleUri.hashCode","_SimpleUri.==","_SimpleUri._toNonSimple","_SimpleUri.toString","DomException.toString","EventTarget.addEventListener","EventTarget._addEventListener","EventTarget._removeEventListener","Location.origin","Location.toString","MessagePort.addEventListener","MessagePort.postMessage","convertDartToNative_PrepareForStructuredClone","_EventStream.isBroadcast","_EventStream.listen","_EventStream.listen[function-entry$1$onDone$onError]","_EventStream.listen[function-entry$1]","_EventStreamSubscription.cancel","_EventStreamSubscription.pause","_EventStreamSubscription.pause[function-entry$0]","_EventStreamSubscription.resume","_EventStreamSubscription._tryResume","_EventStreamSubscription._unlisten","_EventStreamSubscription._onData","_EventStreamSubscription.<anonymous function>","_StructuredClone.findSlot","_StructuredClone.walk","convertDartToNative_DateTime","_StructuredClone.copyList","_StructuredClone.walk.<anonymous function>","_AcceptStructuredClone.findSlot","_AcceptStructuredClone.walk","DateTime._withValue","convertNativeToDart_DateTime","_AcceptStructuredClone.convertNativeToDart_AcceptStructuredClone","_AcceptStructuredClone.walk.<anonymous function>","_StructuredCloneDart2Js.forEachObjectKey","_AcceptStructuredCloneDart2Js.forEachJsField","_convertDataTree._convert","promiseToFuture.<anonymous function>","AsyncMemoizer.runOnce","AsyncMemoizer.hasRun","DelegatingStreamSubscription.onData","DelegatingStreamSubscription.onError","DelegatingStreamSubscription.onDone","DelegatingStreamSubscription.pause","DelegatingStreamSubscription.pause[function-entry$0]","DelegatingStreamSubscription.resume","DelegatingStreamSubscription.cancel","FutureGroup.add","FutureGroup.close","FutureGroup.add.<anonymous function>","FutureGroup_add_closure","NullStreamSink.add","NullStreamSink.addStream","NullStreamSink._checkEventAllowed","NullStreamSink.close","NullStreamSink.addStream.<anonymous function>","ErrorResult.complete","ErrorResult.addTo","ErrorResult.hashCode","ErrorResult.==","ValueResult.complete","ValueResult.addTo","ValueResult.hashCode","ValueResult.==","StreamCompleter.setSourceStream","_CompleterStream.listen","_CompleterStream.listen[function-entry$1$onDone$onError]","_CompleterStream.listen[function-entry$1]","_CompleterStream._linkStreamToController","_CompleterStream._createController","_CompleterStream._controller","_CompleterStream._sourceStream","StreamGroup.add","StreamGroup._onListen","StreamGroup._onCancelBroadcast","StreamGroup._listenToStream","StreamGroup.close","StreamGroup._controller","StreamGroup.add.<anonymous function>","StreamGroup_add_closure","StreamGroup._onListen.<anonymous function>","StreamGroup__onListen_closure","StreamGroup._onCancelBroadcast.<anonymous function>","StreamGroup__onCancelBroadcast_closure","StreamGroup._listenToStream.<anonymous function>","_StreamGroupState.toString","StreamQueue.next","StreamQueue._updateRequests","StreamQueue._extractStream","SubscriptionStream","StreamQueue._ensureListening","StreamQueue._addResult","StreamQueue._failClosed","StreamQueue._addRequest","StreamQueue._subscription","StreamQueue._ensureListening.<anonymous function>","StreamQueue__ensureListening_closure","Result.error","_NextRequest.update","ListMixin.isNotEmpty","_RestRequest.update","StreamCompleter.setEmpty","_CompleterStream._setEmpty","_CompleterSink._canSendDirectly","_CompleterSink.done","Completer.sync","_CompleterSink.addStream","_CompleterSink.close","_CompleterSink._ensureController","_CompleterSink._setDestinationSink","_CompleterSink._controller","_CompleterSink._destinationSink","_CompleterSink._setDestinationSink.<anonymous function>","SubscriptionStream.listen","SubscriptionStream.listen[function-entry$1$onDone$onError]","SubscriptionStream.listen[function-entry$1]","SubscriptionStream._source","_CancelOnErrorSubscriptionWrapper.onError","_CancelOnErrorSubscriptionWrapper.onError.<anonymous function>","_CancelOnErrorSubscriptionWrapper.onError.<anonymous function>.<anonymous function>","All.evaluate","All.intersection","All.validate","All.toString","VariableNode.accept","VariableNode.toString","VariableNode.==","VariableNode.hashCode","NotNode.accept","NotNode.toString","NotNode.==","NotNode.hashCode","OrNode.span","OrNode.accept","OrNode.toString","OrNode.==","OrNode.hashCode","AndNode.span","AndNode.accept","AndNode.toString","AndNode.==","AndNode.hashCode","ConditionalNode.span","ConditionalNode.accept","ConditionalNode.toString","ConditionalNode.==","ConditionalNode.hashCode","Evaluator.visitVariable","Evaluator.visitNot","Evaluator.visitOr","Evaluator.visitAnd","Evaluator.visitConditional","BooleanSelectorImpl.evaluate","BooleanSelectorImpl.intersection","BooleanSelectorImpl.validate","BooleanSelectorImpl.toString","BooleanSelectorImpl.==","BooleanSelectorImpl.hashCode","IntersectionSelector.evaluate","IntersectionSelector.intersection","IntersectionSelector.validate","IntersectionSelector.toString","IntersectionSelector.==","IntersectionSelector.hashCode","None.evaluate","None.intersection","None.validate","None.toString","Parser.parse","Parser._conditional","Parser._or","Parser._and","Parser._simpleExpression","Scanner.peek","Scanner.next","Scanner.scan","Scanner._getNext","StringScanner.isDone","SpanScanner.state","Scanner._scanOr","Scanner._scanAnd","Scanner._scanIdentifier","Scanner._scanOperator","StringScanner.readChar","Scanner._consumeWhitespace","Scanner._multiLineComment","IdentifierToken.toString","TokenType.toString","Validator.visitVariable","RecursiveVisitor.visitNot","RecursiveVisitor.visitOr","RecursiveVisitor.visitAnd","RecursiveVisitor.visitConditional","EmptyUnmodifiableSet.toSet","EmptyUnmodifiableSet.iterator","EmptyUnmodifiableSet.length","EmptyUnmodifiableSet.contains","mapMap.<anonymous function>","mapMap_closure","mergeMaps.<anonymous function>","mergeMaps_closure","QueueList.toString","QueueList.removeFirst","QueueList.length","QueueList.[]","QueueList.[]=","QueueList._add","QueueList._writeToList","QueueList._preGrow","QueueList._table","UnionSet.length","UnionSet.iterator","UnionSet._iterable","_SetBase.expand","UnionSet.toSet","UnionSet.length.<anonymous function>","UnionSet_length_closure","UnionSet._iterable.<anonymous function>","UnionSet__iterable_closure","UnionSetController._set","_DelegatingIterableBase.contains","_DelegatingIterableBase.every","_DelegatingIterableBase.first","_DelegatingIterableBase.isEmpty","_DelegatingIterableBase.iterator","_DelegatingIterableBase.last","_DelegatingIterableBase.length","_DelegatingIterableBase.map","_DelegatingIterableBase.toList","_DelegatingIterableBase.toList[function-entry$0]","_DelegatingIterableBase.toSet","_DelegatingIterableBase.where","_DelegatingIterableBase.toString","DelegatingSet.union","DelegatingSet.toSet","_IsTrue.matches","_IsTrue.describe","_Predicate.typedMatches","_Predicate.describe","StringDescription.length","StringDescription.addDescriptionOf","_StringEqualsMatcher.typedMatches","_StringEqualsMatcher.describe","_StringEqualsMatcher.describeTypedMismatch","_DeepMatcher._compareIterables","_DeepMatcher._compareSets","_DeepMatcher._recursiveMatch","_DeepMatcher._match","_DeepMatcher.matches","_DeepMatcher.describe","_DeepMatcher.describeMismatch","_DeepMatcher._compareSets.<anonymous function>","FeatureMatcher.matches","FeatureMatcher.describeMismatch","FeatureMatcher.describeTypedMismatch","Matcher.describeMismatch","prettyPrint._prettyPrint","prettyPrint._prettyPrint.pp","prettyPrint._prettyPrint.<anonymous function>","TypeMatcher.describe","_stripDynamic","TypeMatcher.matches","wrapMatcher.<anonymous function>","escape.<anonymous function>","PackageConfigResolver._normalizeMap.<anonymous function>","Context.absolute","Context.absolute[function-entry$1]","Context.join","Context.join[function-entry$2]","Context.joinAll","Context.split","Context.normalize","Context._needsNormalization","Context.relative","Context.isRelative","Context.relative[function-entry$1]","Context._isWithinOrEquals","Context._isWithinOrEqualsFast","Context._pathDirection","Context.toUri","Context.prettyUri","Context.join.<anonymous function>","Context.joinAll.<anonymous function>","Context.split.<anonymous function>","_validateArgList.<anonymous function>","_PathDirection.toString","_PathRelation.toString","InternalStyle.getRoot","InternalStyle.relativePathToUri","InternalStyle.codeUnitsEqual","InternalStyle.pathsEqual","ParsedPath.hasTrailingSeparator","ParsedPath.removeTrailingSeparators","ParsedPath.normalize","ParsedPath.toString","ParsedPath.parts","ParsedPath.separators","ParsedPath.normalize.<anonymous function>","PathException.toString","Style.toString","PosixStyle.containsSeparator","PosixStyle.isSeparator","PosixStyle.needsSeparator","PosixStyle.rootLength","PosixStyle.rootLength[function-entry$1]","PosixStyle.isRootRelative","PosixStyle.pathFromUri","PosixStyle.absolutePathToUri","UrlStyle.containsSeparator","UrlStyle.isSeparator","UrlStyle.needsSeparator","UrlStyle.rootLength","UrlStyle.rootLength[function-entry$1]","UrlStyle.isRootRelative","UrlStyle.pathFromUri","UrlStyle.relativePathToUri","UrlStyle.absolutePathToUri","WindowsStyle.containsSeparator","WindowsStyle.isSeparator","WindowsStyle.needsSeparator","WindowsStyle.rootLength","WindowsStyle.rootLength[function-entry$1]","WindowsStyle.isRootRelative","WindowsStyle.pathFromUri","WindowsStyle.absolutePathToUri","WindowsStyle.codeUnitsEqual","WindowsStyle.pathsEqual","WindowsStyle.absolutePathToUri.<anonymous function>","Pool.request","Pool.isClosed","Pool.withResource","Pool.close","Pool._onResourceReleaseAllowed","Pool._runOnRelease","Pool._resetTimer","Pool.close.<anonymous function>","Pool._onResourceReleaseAllowed.<anonymous function>","Pool._runOnRelease.<anonymous function>","PoolResource.release","Pool._onResourceReleased","mapStackTrace.<anonymous function>","_prettifyMember.<anonymous function>","MultiSectionMapping._indexFor","MultiSectionMapping.spanFor","MultiSectionMapping.spanFor[function-entry$2$uri]","MultiSectionMapping.spanFor[function-entry$2$files]","MultiSectionMapping.toString","MappingBundle.toString","MappingBundle.spanFor","SourceMapSpan","MappingBundle.spanFor[function-entry$2$uri]","SingleMapping._segmentError","SingleMapping._findLine","SingleMapping._findColumn","SingleMapping.spanFor","SourceMapSpan.identifier","SingleMapping.spanFor[function-entry$2$uri]","SingleMapping.spanFor[function-entry$2$files]","SingleMapping.toString","SingleMapping.fromJson.<anonymous function>","SingleMapping._findLine.<anonymous function>","SingleMapping._findColumn.<anonymous function>","TargetLineEntry.toString","TargetEntry.toString","_MappingTokenizer.moveNext","_MappingTokenizer.current","_MappingTokenizer.hasTokens","_MappingTokenizer.nextKind","_MappingTokenizer.toString","_digits.<anonymous function>","SourceFile.length","SourceFile.lines","SourceFile.span","SourceFile.getLine","SourceFile._isNearCachedLine","SourceFile._binarySearch","SourceFile.getColumn","SourceFile.getOffset","SourceFile.getOffset[function-entry$1]","FileLocation.line","FileLocation.sourceUrl","FileLocation.column","_FileSpan.start","_FileSpan.sourceUrl","_FileSpan.length","_FileSpan.end","_FileSpan.text","_FileSpan.context","_FileSpan.==","_FileSpan.hashCode","_FileSpan.expand","Highlighter.highlight","downEnd","upEnd","Highlighter._writeFirstLine","Highlighter._writeIntermediateLines","Highlighter._writeLastLine","Highlighter._writeTrailingLines","Highlighter._writeText","Highlighter._writeSidebar","Highlighter._writeSidebar[function-entry$0$end]","Highlighter._writeSidebar[function-entry$0$line]","Highlighter._writeSidebar[function-entry$0]","Highlighter._countTabs","Highlighter._isOnlyWhitespace","Highlighter._colorize","Highlighter._colorize[function-entry$1]","Highlighter._writeFirstLine.<anonymous function>","glyphOrAscii","topLeftCorner","horizontalLine","Highlighter._writeIntermediateLines.<anonymous function>","verticalLine","Highlighter._writeLastLine.<anonymous function>","bottomLeftCorner","Highlighter._writeSidebar.<anonymous function>","SourceLocation.distance","SourceLocation.==","SourceLocation.hashCode","SourceLocation.toString","SourceLocationMixin.distance","SourceLocationMixin.==","SourceLocationMixin.hashCode","SourceLocationMixin.toString","SourceLocationMixin.toolString","SourceSpanBase","SourceSpanException.message","SourceSpanException.toString","SourceSpanException.toString[function-entry$0]","SourceSpanMixin.sourceUrl","SourceSpanMixin.length","SourceSpanMixin.message","SourceSpanMixin.message[function-entry$1]","SourceSpanMixin.highlight","Highlighter","Highlighter._buffer","SourceSpanMixin.==","SourceSpanMixin.hashCode","SourceSpanMixin.toString","SourceSpanWithContext.context","Chain.foldFrames","Chain.toTrace","JSArray.expand","Chain.toString","Chain.capture.<anonymous function>","Chain_capture_closure","Chain.current.<anonymous function>","Chain.forTrace.<anonymous function>","Chain.parse.<anonymous function>","Chain.foldFrames.<anonymous function>","Chain.toTrace.<anonymous function>","Chain.toString.<anonymous function>","Chain.toString.<anonymous function>.<anonymous function>","Frame.isCore","Frame.library","Frame.package","Frame.location","Frame.toString","Frame.parseVM.<anonymous function>","Frame.parseV8.<anonymous function>","Frame.parseV8.<anonymous function>.parseLocation","Frame.parseFirefox.<anonymous function>","Frame.parseFriendly.<anonymous function>","UriData.fromString","Uri.dataFromString","fromUri","LazyChain._chain","LazyChain.traces","LazyChain.foldFrames","LazyChain.toTrace","LazyChain.toString","LazyChain._inner","LazyChain.foldFrames.<anonymous function>","LazyChain.toTrace.<anonymous function>","LazyTrace._trace","LazyTrace.frames","LazyTrace.original","LazyTrace.foldFrames","LazyTrace.toString","LazyTrace._inner","LazyTrace.foldFrames.<anonymous function>","StackZoneSpecification.chainFor","StackZoneSpecification._registerCallback","StackZoneSpecification._disabled","StackZoneSpecification._registerCallback[function-entry$4]","StackZoneSpecification._registerUnaryCallback","StackZoneSpecification._registerUnaryCallback[function-entry$4]","StackZoneSpecification._registerBinaryCallback","StackZoneSpecification._registerBinaryCallback[function-entry$4]","StackZoneSpecification._errorCallback","StackZoneSpecification._run","StackZoneSpecification._currentTrace","StackZoneSpecification._trimVMChain","StackZoneSpecification._currentNode","StackZoneSpecification.chainFor.<anonymous function>","StackZoneSpecification._registerCallback.<anonymous function>","StackZoneSpecification__registerCallback_closure","StackZoneSpecification._registerUnaryCallback.<anonymous function>","StackZoneSpecification__registerUnaryCallback_closure","StackZoneSpecification._registerUnaryCallback.<anonymous function>.<anonymous function>","StackZoneSpecification__registerUnaryCallback__closure","StackZoneSpecification._registerBinaryCallback.<anonymous function>","StackZoneSpecification__registerBinaryCallback_closure","StackZoneSpecification._registerBinaryCallback.<anonymous function>.<anonymous function>","StackZoneSpecification__registerBinaryCallback__closure","StackZoneSpecification._currentTrace.<anonymous function>","_Node.toChain","Trace.foldFrames","JSArray.reversed","Trace.toString","Trace.current.<anonymous function>","Trace.from.<anonymous function>","Trace._parseVM.<anonymous function>","Trace.parseV8.<anonymous function>","Trace.parseJSCore.<anonymous function>","Trace.parseFirefox.<anonymous function>","Trace.parseFriendly.<anonymous function>","Trace.foldFrames.<anonymous function>","Trace.toString.<anonymous function>","UnparsedFrame.toString","GuaranteeChannel.sink","GuaranteeChannel._onSinkDisconnected","GuaranteeChannel._sink","GuaranteeChannel._streamController","GuaranteeChannel._subscription","GuaranteeChannel.<anonymous function>","GuaranteeChannel.<anonymous function>.<anonymous function>","_GuaranteeSink.done","_GuaranteeSink.add","_GuaranteeSink.addError","_GuaranteeSink.addError[function-entry$1]","_GuaranteeSink._addError","_GuaranteeSink._addError[function-entry$1]","_GuaranteeSink.addStream","_GuaranteeSink.close","_GuaranteeSink._onStreamDisconnected","_GuaranteeSink._addStreamSubscription","_GuaranteeSink._addError.<anonymous function>","_GuaranteeSink.addStream.<anonymous function>","_MultiChannel.virtualChannel","_MultiChannel.virtualChannel[function-entry$0]","_MultiChannel._closeChannel","_MultiChannel._closeInnerChannel","_MultiChannel.<anonymous function>","_MultiChannel_closure","_MultiChannel.<anonymous function>.<anonymous function>","_MultiChannel__closure","_MultiChannel.virtualChannel.<anonymous function>","_MultiChannel_virtualChannel_closure","StreamChannelCompleter._channel","StreamChannelController.local","StreamChannelController._local","StreamChannelController._foreign","SpanScanner.spanFrom","SpanScanner.matches","SpanScanner.error","StringScanner.lastMatch","StringScanner.peekChar","StringScanner.scan","StringScanner.expect","StringScanner._fail","StringScanner.expect[function-entry$1]","StringScanner.matches","AsciiGlyphSet.glyphOrAscii","AsciiGlyphSet.horizontalLine","AsciiGlyphSet.verticalLine","AsciiGlyphSet.topLeftCorner","AsciiGlyphSet.bottomLeftCorner","AsciiGlyphSet.upEnd","AsciiGlyphSet.downEnd","UnicodeGlyphSet.glyphOrAscii","UnicodeGlyphSet.horizontalLine","UnicodeGlyphSet.verticalLine","UnicodeGlyphSet.topLeftCorner","UnicodeGlyphSet.bottomLeftCorner","UnicodeGlyphSet.upEnd","UnicodeGlyphSet.downEnd","internalBootstrapBrowserTest.<anonymous function>","suiteChannel","setStackTraceMapper","postMessageChannel.<anonymous function>","convertNativeToDart_AcceptStructuredClone","convertNativeToDart_SerializedScriptValue","postMessageChannel.<anonymous function>.<anonymous function>","ClosedException.toString","Declarer.test","Declarer.group","Declarer.build","Declarer._checkNotBuilt","Declarer._runSetUps","Declarer._setUpAll","Declarer._tearDownAll","Declarer.test.<anonymous function>","Invoker.addTearDown","Declarer.test.<anonymous function>.<anonymous function>","Declarer.test.<anonymous function>.<anonymous function>.<anonymous function>","Declarer.group.<anonymous function>","Declarer.build.<anonymous function>","Declarer._runSetUps.<anonymous function>","Declarer._tearDownAll.<anonymous function>","Declarer._tearDownAll.<anonymous function>.<anonymous function>","Declarer._tearDownAll.<anonymous function>.<anonymous function>.<anonymous function>","Group.forPlatform","Group._map","Group.forPlatform.<anonymous function>","Group._map.<anonymous function>","LocalTest.load","Invoker._closableKey","Invoker._","Invoker._counterKey","Invoker.liveTest","LocalTest.forPlatform","Invoker._outstandingCallbacks","Invoker.addOutstandingCallback","Invoker.waitForOutstandingCallbacks","Invoker.unclosable","Invoker.heartbeat","LiveTest.isComplete","Invoker._handleError","State.shouldBeDone","Invoker._handleError[function-entry$2]","Invoker._onRun","Invoker._runTearDowns","Invoker.guard.<anonymous function>","Invoker.guard.<anonymous function>.<anonymous function>","Invoker.waitForOutstandingCallbacks.<anonymous function>","Invoker.heartbeat.message","niceDuration","Invoker.heartbeat.<anonymous function>","Invoker.heartbeat.<anonymous function>.<anonymous function>","Invoker._handleError.<anonymous function>","_AsyncCounter.complete","Invoker._onRun.<anonymous function>","Invoker._onRun.<anonymous function>.<anonymous function>","Invoker._onRun.<anonymous function>.<anonymous function>.<anonymous function>","Invoker._onRun.<anonymous function>.<anonymous function>.<anonymous function>.<anonymous function>","Invoker._print","_AsyncCounter.decrement","_LiveTest.run","LiveTestController._run","LiveTestController._isClosed","_LiveTest.onComplete","LiveTestController.addError","LiveTestController.setState","LiveTestController.message","_BroadcastStreamController.hasListener","LiveTestController._close","MessageType.toString","Metadata._validateTags","Metadata.validatePlatformSelectors","Metadata.merge","Metadata.change","Metadata.change[function-entry$0$onPlatform]","Metadata.change[function-entry$0$timeout]","Metadata.change[function-entry$0$skip$skipReason]","Metadata.forPlatform","Metadata.serialize","Metadata._serializeTimeout","Metadata._unresolved","Metadata.<anonymous function>","Metadata.deserialize.<anonymous function>","BooleanSelectorImpl.parse","Parser","Metadata._validateTags.<anonymous function>","Metadata.validatePlatformSelectors.<anonymous function>","Metadata.merge.<anonymous function>","Metadata.forPlatform.<anonymous function>","Metadata.serialize.<anonymous function>","OperatingSystem.toString","OperatingSystem.find.<anonymous function>","PlatformSelector.validate","PlatformSelector.evaluate","PlatformSelector.intersection","PlatformSelector.toString","PlatformSelector.==","PlatformSelector.hashCode","PlatformSelector.parse.<anonymous function>","PlatformSelector.validate.<anonymous function>","PlatformSelector.validate.<anonymous function>.<anonymous function>","PlatformSelector.evaluate.<anonymous function>","Runtime.toString","Runtime.deserialize.<anonymous function>","StackTraceFormatter.configure","StackTraceFormatter.configure[function-entry$0$except$only]","StackTraceFormatter.configure[function-entry$0$mapper]","StackTraceFormatter.formatStackTrace","parseExtended","JSStackTraceMapper.mapStackTrace","StackTraceFormatter._except","StackTraceFormatter._only","StackTraceFormatter.formatStackTrace.<anonymous function>","_LinkedHashSet.isNotEmpty","State.==","State.hashCode","State.toString","Status.toString","Result.toString","TestFailure.toString","_expect.<anonymous function>","Timeout.merge","Duration.*","Timeout.apply","Timeout.hashCode","Timeout.==","Timeout.toString","RemoteListener._serializeGroup","RemoteListener._serializeTest","RemoteListener._runLiveTest","_BroadcastStreamController.stream","_LiveTest.onStateChange","_LiveTest.onError","_LiveTest.onMessage","RemoteListener.start.<anonymous function>","StackTraceFormatter","RemoteListener.start.<anonymous function>.<anonymous function>","RemoteListener.start.<anonymous function>.<anonymous function>.<anonymous function>","StreamQueue._","RemoteListener.start.<anonymous function>.<anonymous function>.<anonymous function>.<anonymous function>","SuiteChannelManager.current","SuiteChannelManager.connectIn","StreamChannelCompleter.setChannel","StreamSinkCompleter.setDestinationSink","RemoteListener.start.<anonymous function>.<anonymous function>.<anonymous function>.<anonymous function>.<anonymous function>","RemoteListener._listen","RemoteListener._serializeGroup.<anonymous function>","RemoteListener._serializeTest.<anonymous function>","RemoteListener._runLiveTest.<anonymous function>","RemoteListener._runLiveTest.<anonymous function>.<anonymous function>","SuiteChannelManager.connectOut","StreamCompleter._stream","StreamSinkCompleter.sink","StreamChannelCompleter._streamCompleter","StreamChannelCompleter._sinkCompleter","StreamChannelCompleter","StreamChannel","IterableSet.length","IterableSet.iterator","IterableSet.toSet","errorsDontStopTest.<anonymous function>","currentOSGuess.<anonymous function>","Engine._onUnpaused","Engine.success","Engine.liveTests","UnionSet.from","Engine.passed","Engine.skipped","Engine.failed","Engine.active","Engine.run","Engine._runGroup","JSArray._toListGrowable","Engine._runLiveTest","Engine._runLiveTest[function-entry$2]","Engine._runSkippedTest","Engine._addLiveSuite","_LiveSuite.onTestStarted","_LiveSuite.passed","_LiveSuite.skipped","_LiveSuite.failed","Engine.success.<anonymous function>","Engine.<anonymous function>","Engine.run.<anonymous function>","Engine.run.<anonymous function>.<anonymous function>","Engine_run__closure","Engine.run.<anonymous function>.<anonymous function>.<anonymous function>","Engine.run.<anonymous function>.<anonymous function>.<anonymous function>.<anonymous function>","Engine._runLiveTest.<anonymous function>","Engine._runSkippedTest.<anonymous function>","LiveSuiteController.reportLiveTest","LiveSuiteController.close","LiveSuiteController.<anonymous function>","LiveSuiteController.reportLiveTest.<anonymous function>","LiveSuiteController.close.<anonymous function>","ExpandedReporter._onTestStarted","Stopwatch.start","ExpandedReporter._onStateChange","ExpandedReporter._onError","ExpandedReporter._onDone","ExpandedReporter._progressLine","ExpandedReporter._timeString","ExpandedReporter._progressLine[function-entry$1$suffix]","ExpandedReporter._progressLine[function-entry$1$color]","ExpandedReporter._progressLine[function-entry$1]","ExpandedReporter._description","ExpandedReporter._onTestStarted.<anonymous function>","RunnerSuiteController._close","RunnerSuiteController._suite","RunnerSuiteController._close.<anonymous function>","PrintSink.writeln","PrintSink._flush","JSStackTraceMapper._deserializePackageConfigMap.<anonymous function>","_declarer.<anonymous function>","RunnerSuiteController._local","RunnerSuite._","DelegatingSink.add","Engine.suiteSink","DelegatingSink.close","Stopwatch","ExpandedReporter._","StreamGroup.stream","_declarer.<anonymous function>.<anonymous function>","main.<anonymous function>","main.<anonymous function>.<anonymous function>","_nullErrorHandler[function-entry$1]","_rootRun[function-entry$4]","_rootRunUnary[function-entry$5]","_rootRegisterCallback[function-entry$4]","_rootRegisterUnaryCallback[function-entry$4]","_rootRegisterBinaryCallback[function-entry$4]","max[function-entry$2]","DART_CLOSURE_PROPERTY_NAME","JS_INTEROP_INTERCEPTOR_TAG","TypeErrorDecoder.noSuchMethodPattern","TypeErrorDecoder.notClosurePattern","TypeErrorDecoder.nullCallPattern","TypeErrorDecoder.nullLiteralCallPattern","TypeErrorDecoder.undefinedCallPattern","TypeErrorDecoder.undefinedLiteralCallPattern","TypeErrorDecoder.nullPropertyPattern","TypeErrorDecoder.nullLiteralPropertyPattern","TypeErrorDecoder.undefinedPropertyPattern","TypeErrorDecoder.undefinedLiteralPropertyPattern","_AsyncRun._scheduleImmediateClosure","Future._nullFuture","_RootZone._rootMap","Utf8Decoder._decoder","_Base64Decoder._inverseAlphabet","_Uri._isWindowsCached","_Uri._needsNoEncoding","_hasErrorStackProperty","_scannerTables","_whitespaceAndSingleLineComments","_multiLineCommentBody","_hyphenatedIdentifier","_dart2DynamicArgs","_escapeRegExp","windows","url","context","createInternal","Style.posix","PosixStyle","Style.windows","WindowsStyle","Style.url","UrlStyle","Style.platform","_digits","MAX_INT32","MIN_INT32","_specKey","_vmFrame","_v8Frame","_v8UrlLocation","_v8EvalLocation","_firefoxSafariFrame","_friendlyFrame","_asyncBody","_initialDot","Frame._uriRegExp","Frame._windowsRegExp","StackZoneSpecification.disableKey","_terseRegExp","_v8Trace","_v8TraceLine","_firefoxSafariTrace","_friendlyTrace","Metadata.empty","_universalValidVariables","_currentKey","currentOSGuess","anchoredHyphenatedIdentifier","SuiteConfiguration.empty","patchInstance","main_closure","fromTearOff","StaticClosure","BoundClosure","functionCounter","forwardCallTo","_computeSignatureFunctionNewRti","CyclicInitializationError","NullThrownError","safeToString","_objectToString","Closure","objectTypeName","_objectTypeNameNewRti","Object","_saneNativeClassName","_getInterfaceTypeArguments","_getGenericFunctionBounds","","value","_getFunctionParameters","markFixed","JSArray","markFixedList","StringBuffer","iterableToFullString","_toStringVisiting","_writeAll","noElement","ArrayIterator","ListIterator","ListMixin","Iterable","initNativeDispatchFlag","getTagFunction","dispatchRecordsForInstanceTags","interceptorsForUncacheableTags","alternateTagFunction","JavaScriptIndexingBehavior","prototypeForTagFunction","initHooks_closure","forType","_TypeError","compose","_CastError","fromMessage","_isUnionOfFunctionType","eval","_parseRecipe","create","parse","handleDigit","handleIdentifier","toType","_lookupGenericFunctionParameterRti","_lookupTerminalRti","toTypes","_lookupInterfaceRti","_lookupGenericFunctionRti","_lookupBindingRti","handleExtendedOperations","_lookupUnaryRti","_FunctionParameters","_lookupFunctionRti","toTypesNamed","_canonicalRecipeJoin","_canonicalRecipeJoinNamed","Rti","_finishRti","int","double","num","String","bool","_AssertionError","_getBindingArguments","indexToType","findRule","_getCanonicalRecipe","evalInEnvironment","_getFutureFromFutureOr","bind","findErasedType","evalRecipeIntercepted","evalRecipe","forwardInterceptedCallTo","cspForwardCall","selfFieldNameCache","computeFieldNamed","selfOf","receiverFieldNameCache","cspForwardInterceptedCall","receiverOf","internalBootstrapBrowserTest_closure","start","_ControllerStream","_current","Null","Stream","AsyncError","_propagateToListeners","_Future__propagateToListeners_handleWhenCompleteCallback","_Future__propagateToListeners_handleValueCallback","_Future__propagateToListeners_handleError","_chainCoreFuture","_Future__prependListeners_closure","ExceptionAndStackTrace","_StackTrace","unwrapException_saveStackTrace","UnknownJsTypeError","StackOverflowError","extractPattern","TypeErrorDecoder","provokePropertyErrorOn","provokeCallErrorOn","_Future__propagateToListeners_handleWhenCompleteCallback_closure","_FutureListener","_RootZone_bindCallbackGuarded_closure","_rootHandleUncaughtError_closure","_nextCallback","_lastPriorityCallback","_lastCallback","_AsyncCallbackEntry","_enter","_isInCallbackLoop","_initializeScheduleImmediate","_AsyncRun__initializeScheduleImmediate_internalCallback","_AsyncRun__initializeScheduleImmediate_closure","_createTimer","Duration_toString_twoDigits","Duration_toString_sixDigits","_TimerImpl_internalCallback","_AsyncRun__scheduleImmediateWithSetImmediate_internalCallback","_AsyncRun__scheduleImmediateJsOverride_internalCallback","_Exception","_Future__addListener_closure","_PendingEvents","_chainForeignFuture","_Future__chainForeignFuture_closure","_StreamControllerLifecycle","StreamConsumer","Stream_pipe_closure","_GuaranteeSink","_EventStream","postMessageChannel_closure","_literal","_EventStreamSubscription_closure","_convertDataTree__convert","_IdentityHashMap","mapToString","MapBase_mapToString_closure","MapMixin","_getTableEntry","_HashMapKeyIterable","iterableToShortString","checkNotNegative","range","_HashMapKeyIterator","MappedIterator","Iterator","EfficientLengthMappedIterable","MappedListIterable","ListIterable","_newHashTable","_setTableEntry","JsLinkedHashMap","LinkedHashMapKeyIterable","LinkedHashMapCell","postMessageChannel__closure","_StructuredCloneDart2Js","DateTime","_StructuredClone_walk_closure","_AcceptStructuredCloneDart2Js","StreamSink","StreamSubscription","_empty","_AcceptStructuredClone_walk_closure","getYear","_fourDigits","getMonth","_twoDigits","getDay","getHours","getMinutes","getSeconds","getMilliseconds","_threeDigits","lazyAsJsDate","_AsyncCompleter","promiseToFuture_closure","_Future__asyncCompleteError_closure","_Future__asyncComplete_closure","_Future__chainFuture_closure","Stream_firstWhere__closure","_cancelAndErrorClosure_closure","_cancelAndError_closure","zoneValue","_cancelAndValue_closure","_StreamSinkWrapper","GuaranteeChannel_closure","_StreamImplEvents","_SyncCompleter","_GuaranteeSink_addStream_closure","_GuaranteeSink__addError_closure","GuaranteeChannel__closure","_SyncStreamController","_AsyncStreamController","_EventDispatch","_PendingEvents_schedule_closure","_BufferingStreamSubscription__sendDone_sendDone","_StreamController__subscribe_closure","_StreamController__recordCancel_complete","_AddStreamState_cancel_closure","_DelayedError","_BufferingStreamSubscription__sendError_sendError","_DelayedData","RemoteListener_start_closure","fromIterable","Map","runZoned_closure","_rootDelegate","_ZoneDelegate","printToZone","from","_ZoneFunction","_CustomZone_bindCallbackGuarded_closure","periodic","_TimerImpl$periodic_closure","_HashMap","HashMap_HashMap$from_closure","_LinkedHashSet","RemoteListener_start__closure","RemoteListener_start___closure","_sendError","serialize","TestFailure","forTrace","Chain_toString_closure","Chain_toString__closure","_Type","Chain","unmodifiable","LazyChain","Chain_Chain$forTrace_closure","Chain_Chain$parse_closure","Trace_toString_closure","parseFriendly","WhereIterable","Trace$parseFriendly_closure","_StringStackTrace","WhereIterator","Context_split_closure","List","ParsedPath","filled","ParsedPath_normalize_closure","generate","JSSyntaxRegExp","makeNative","_uriDecode","StringMatch","CodeUnits","_hexCharPairToByte","Utf8Decoder","UnmodifiableListMixin","_convertIntercepted","checkValidRange","fromCharCodes","_Utf8Decoder","stringFromCharCode","stringFromCharCodes","stringFromNativeUint8List","_stringFromIterable","stringFromCodePoints","_fromCharCodeApply","_convertInterceptedUint8List","_useTextDecoderChecked","_unsafe","_useTextDecoderUnchecked","_makeDecoder","checkValueInInterval","_StringAllMatchesIterable","_StringAllMatchesIterator","_AllMatchesIterable","_AllMatchesIterator","tooFew","fixed","Context_join_closure","Context_joinAll_closure","_validateArgList_closure","base","_currentUriBase","currentUri","_parse","_SimpleUri","notSimple","parseInt","_defaultPort","_toWindowsFilePath","_checkNonWindowsPathReservedCharacters","_Uri__checkNonWindowsPathReservedCharacters_closure","_checkWindowsDriveLetter","_checkWindowsPathReservedCharacters","fromCharCode","_removeDotSegments","_makePort","_normalizeRelativePath","_mayContainDotSegments","_escapeScheme","_isAlphabeticCharacter","_makeScheme","_fail","_makeUserInfo","_makeHost","_Uri__Uri$notSimple_closure","_makePath","_makeQuery","_makeFragment","_normalizeOrSubstring","_normalize","_normalizeEscape","_escapeChar","_Uri__makePath_closure","_normalizePath","_uriEncode","Codec","_Utf8Encoder","_checkZoneID","_normalizeZoneID","parseIPv6Address","_normalizeRegName","Uri_parseIPv6Address_error","Uri_parseIPv6Address_parseHex","_parseIPv4Address","Uri__parseIPv4Address_error","_canonicalizeScheme","_createTables_closure","_createTables_build","_createTables_setChars","_createTables_setRange","_DataUri","UriData","_checkPadding","_create1","_getPlatformStyle","Frame_Frame$parseFriendly_closure","_catchFormatException","_writeUri","_uriEncodeBytes","Frame","WindowsStyle_absolutePathToUri_closure","_validateMimeType","_skipLeadingWhitespace","_skipTrailingWhitespace","_isWhitespace","parseV8","parseJSCore","parseFirefox","_parseVM","Trace__parseVM_closure","parseVM","Frame_Frame$parseVM_closure","ExpandIterable","Chain_toTrace_closure","Trace$parseFirefox_closure","Frame_Frame$parseFirefox_closure","_uriOrPathToUri","file","_makeWindowsFileUrl","_makeFileUri","Trace$parseJSCore_closure","Frame_Frame$parseV8_closure","Frame_Frame$parseV8_closure_parseLocation","Trace$parseV8_closure","SkipWhileIterable","SkipWhileIterator","StackZoneSpecification_chainFor_closure","_Node","LazyTrace","Trace_Trace$from_closure","LazyChain_toTrace_closure","getProperty","StackTraceFormatter_formatStackTrace_closure","LazyChain_foldFrames_closure","Chain_foldFrames_closure","tooMany","LazyTrace_foldFrames_closure","Trace_foldFrames_closure","ReversedListIterable","mapStackTrace_closure","_prettifyMember_closure","fromJson","notNull","_lastLineLength","_normalizeNewlines","_normalizeTrailingNewline","_normalizeEndOfLine","_glyphs","Highlighter__writeSidebar_closure","Highlighter__writeLastLine_closure","Highlighter__writeIntermediateLines_closure","Highlighter__writeFirstLine_closure","SingleMapping__findColumn_closure","SingleMapping__findLine_closure","SingleMapping","SourceFile","_MappingTokenizer","TargetLineEntry","TargetEntry","SingleMapping$fromJson_closure","closure","MultiSectionMapping","MappingBundle","_JsonMap","_JsonMapKeyIterable","_sendLoadException","StreamQueue","_CompleterStream","_RestRequest","StreamCompleter","RemoteListener_start____closure","deserialize","_deserializeSet","find","Suite","_filterGroup","Symbol","makeErrorHandler","_AddStreamState_makeErrorHandler_closure","Result","_wrapJsFunctionForAsync_closure","_StreamIterator","_awaitOnObject_closure","_AsyncAwaitCompleter","RemoteListener_start_____closure","guard","Invoker_guard_closure","Invoker_guard__closure","RemoteListener","RemoteListener__serializeGroup_closure","RemoteListener__serializeTest_closure","VirtualChannel","_EmptyStream","NullStreamSink","MultiChannel","NullStreamSink_addStream_closure","_LinkedHashSetCell","RemoteListener__runLiveTest_closure","_BroadcastStream","RemoteListener__runLiveTest__closure","Invoker","State","Invoker__onRun_closure","_AsyncCounter","capture","_keyCount","StackZoneSpecification","setProperty","StackZoneSpecification__currentTrace_closure","Invoker__onRun__closure","Invoker__onRun___closure","Message","Invoker__onRun____closure","Future_Future_closure","errorsDontStopTest_closure","Invoker_waitForOutstandingCallbacks_closure","sync","Invoker_heartbeat_closure","Invoker_heartbeat_message","Invoker_heartbeat__closure","TimeoutException","Timeout","Invoker__handleError_closure","Chain_Chain$current_closure","_SyncBroadcastStreamController","_LiveTest","_BroadcastStreamController","Metadata_serialize_closure","MapEntry","PlatformSelector","Group_forPlatform_closure","GroupEntry","Group__map_closure","Metadata_forPlatform_closure","Metadata_Metadata__unresolved","_","Metadata_Metadata_closure","Metadata_merge_closure","Set","UnmodifiableMapView","_throwUnmodifiable","_ConstantMapKeyIterable","ConstantStringMap","DelegatingSet","Metadata__validateTags_closure","LinkedHashMap_LinkedHashMap$from_closure","PlatformSelector_evaluate_closure","OperatingSystem_find_closure","Runtime_Runtime$deserialize_closure","Runtime","Declarer_build_closure","LocalTest","Declarer__tearDownAll_closure","Declarer__tearDownAll__closure","Declarer__tearDownAll___closure","_deserializeTimeout","Metadata$deserialize_closure","BooleanSelectorImpl","Scanner","AndNode","IntersectionSelector","OrNode","ConditionalNode","Evaluator","Token","_SpanScannerState","IdentifierToken","SourceSpanMixin","FileLocation","NotNode","VariableNode","PlatformSelector$parse_closure","_wrapFormatException","_CompleterSink","_CompleterSink__setDestinationSink_closure","StreamSinkCompleter","_EventRequest","_CancelOnErrorSubscriptionWrapper","_CancelOnErrorSubscriptionWrapper_onError_closure","_CancelOnErrorSubscriptionWrapper_onError__closure","ErrorResult","ValueResult","EventSink","_NextRequest","_GeneratedStreamImpl","PackageRootResolver","_deserializePackageConfigMap","_normalizeMap","PackageConfigResolver","JSStackTraceMapper","castFrom","CastMap","CastIterator","EfficientLengthIterable","_EfficientLengthCastIterable","PackageConfigResolver__normalizeMap_closure","JSStackTraceMapper__deserializePackageConfigMap_closure","_StreamChannel","Declarer_group_closure","Trace_Trace$current_closure","Metadata_validatePlatformSelectors_closure","PlatformSelector_validate_closure","PlatformSelector_validate__closure","Validator","_parseOnPlatform","_parseTags","_globalDeclarer","_declarer_closure","RunnerSuiteController","_AsyncBroadcastStreamController","AsyncMemoizer","RunnerSuite","_frequency","initTicker","timerFrequency","ExpandedReporter","PrintSink","fromFuture","_declarer__closure","error","UnmodifiableListView","timerTicks","UnionSet","IterableSet","ExpandedReporter__onTestStarted_closure","Engine_run_closure","Pool_close_closure","Engine_run___closure","Engine_run____closure","Pool__onResourceReleaseAllowed_closure","Pool__runOnRelease_closure","PoolResource","LiveSuiteController_close_closure","RunnerSuiteController__close_closure","Engine__runSkippedTest_closure","Engine__runLiveTest_closure","microtask","Future_Future$microtask_closure","LiveSuiteController_reportLiveTest_closure","_nextPowerOf2","StreamGroup__listenToStream_closure","_LiveSuite","LiveSuiteController_closure","wait","Engine_success_closure","Future_wait_handleError","Primitives_initTicker_closure","StreamGroup","Engine_closure","_macOSDirectories","_list","_map","SuiteConfiguration","main__closure","Declarer_test_closure","Declarer_test__closure","Declarer_test___closure","Declarer__runSetUps_closure","forEach","Future_forEach_closure","doWhile","Future_doWhile_closure","_expect_closure","StringDescription","Matcher","prettyPrint__prettyPrint","prettyPrint__prettyPrint_pp","prettyPrint__prettyPrint_closure","escape_closure","Runes","RuneIterator","_Predicate","wrapMatcher_closure","_StringEqualsMatcher","_DeepMatcher","_DeepMatcher__compareSets_closure","TypeMatcher","FeatureMatcher","_writeTrailing","_writeLeading","x","objectAssign","JS_CONST","Interceptor","JSBool","JSNull","JSObject","JavaScriptObject","PlainJavaScriptObject","UnknownJavaScriptObject","Function","JavaScriptFunction","JSUnmodifiableArray","JSNumber","JSInt","JSDouble","Pattern","JSString","_CastIterableBase","EmptyIterator","FixedLengthListMixin","UnmodifiableListBase","ConstantMap","noSuchMethodPattern","notClosurePattern","nullCallPattern","nullLiteralCallPattern","undefinedCallPattern","undefinedLiteralCallPattern","nullPropertyPattern","nullLiteralPropertyPattern","undefinedPropertyPattern","undefinedLiteralPropertyPattern","NoSuchMethodError","StackTrace","TearOffClosure","RegExpMatch","Match","NativeByteBuffer","ByteBuffer","NativeTypedData","NativeByteData","NativeTypedArray","NativeTypedArrayOfDouble","NativeTypedArrayOfInt","NativeFloat32List","NativeFloat64List","NativeInt16List","NativeInt32List","NativeInt8List","NativeUint16List","Uint32List","NativeUint32List","NativeUint8ClampedList","Uint8List","Type","_Error","Sink","_Completer","StreamTransformer","StreamTransformerBase","_StreamController","_SyncStreamControllerDispatch","_AsyncStreamControllerDispatch","_StreamImpl","_DelayedEvent","_DelayedDone","Error","ZoneSpecification","ZoneDelegate","Zone","_Zone","_rootMap","_RootZone","IterableBase","ListBase","MapBase","_UnmodifiableMapMixin","MapView","Queue","SetMixin","SetBase","_SetBase","AsciiCodec","_UnicodeSubsetEncoder","AsciiEncoder","Base64Codec","Base64Encoder","_FusedCodec","Converter","Encoding","JsonCodec","JsonDecoder","Utf8Codec","Utf8Encoder","_decoder","OutOfMemoryError","StringSink","Uri","_isWindowsCached","_needsNoEncoding","AbortPaymentEvent","AnimationEvent","AnimationPlaybackEvent","ApplicationCacheErrorEvent","BackgroundFetchClickEvent","BackgroundFetchEvent","BackgroundFetchFailEvent","BackgroundFetchedEvent","BeforeInstallPromptEvent","BeforeUnloadEvent","Blob","BlobEvent","CanMakePaymentEvent","ClipboardEvent","CloseEvent","CompositionEvent","CustomEvent","DeviceMotionEvent","DeviceOrientationEvent","DomError","DomException","ErrorEvent","Event","EventTarget","ExtendableEvent","ExtendableMessageEvent","FetchEvent","File","FocusEvent","FontFaceSetLoadEvent","ForeignFetchEvent","GamepadEvent","HashChangeEvent","InstallEvent","KeyboardEvent","Location","MediaEncryptedEvent","MediaError","MediaKeyMessageEvent","MediaQueryListEvent","MediaStreamEvent","MediaStreamTrackEvent","MessageEvent","MessagePort","MidiConnectionEvent","MidiMessageEvent","MouseEvent","MutationEvent","NavigatorUserMediaError","NotificationEvent","OverconstrainedError","PageTransitionEvent","PaymentRequestEvent","PaymentRequestUpdateEvent","PointerEvent","PopStateEvent","PositionError","PresentationConnectionAvailableEvent","PresentationConnectionCloseEvent","ProgressEvent","PromiseRejectionEvent","PushEvent","RtcDataChannelEvent","RtcDtmfToneChangeEvent","RtcPeerConnectionIceEvent","RtcTrackEvent","SecurityPolicyViolationEvent","SensorErrorEvent","SpeechRecognitionError","SpeechRecognitionEvent","SpeechSynthesisEvent","StorageEvent","SyncEvent","TextEvent","TouchEvent","TrackEvent","TransitionEvent","UIEvent","VRDeviceEvent","VRDisplayEvent","VRSessionEvent","WheelEvent","Window","_MojoInterfaceRequestEvent","_ResourceProgressEvent","_USBConnectionEvent","EventStreamProvider","_StructuredClone","_AcceptStructuredClone","VersionChangeEvent","ByteData","Int8List","Uint8ClampedList","Int16List","Uint16List","Int32List","Float32List","Float64List","AudioProcessingEvent","OfflineAudioCompletionEvent","ContextEvent","SqlError","DelegatingSink","DelegatingStreamSubscription","_StreamGroupState","BooleanSelector","All","Node","Visitor","None","TokenType","RecursiveVisitor","EmptyUnmodifiableSet","UnmodifiableSetMixin","_DelegatingIterableBase","DelegatingIterable","_IsTrue","Description","SyncPackageResolver","_PathDirection","_PathRelation","InternalStyle","Style","Mapping","_TokenKind","FileSpan","SourceLocationMixin","SourceSpan","SourceSpanException","_uriRegExp","_windowsRegExp","disableKey","StreamChannelMixin","LineScannerState","StringScanner","AsciiGlyphSet","UnicodeGlyphSet","LiveTest","MessageType","empty","OperatingSystem","Status","Test","StackTraceMapper","LiveSuite","RuntimeSelection","_NativeTypedArrayOfDouble&NativeTypedArray&ListMixin","_NativeTypedArrayOfDouble&NativeTypedArray&ListMixin&FixedLengthListMixin","_NativeTypedArrayOfInt&NativeTypedArray&ListMixin","_NativeTypedArrayOfInt&NativeTypedArray&ListMixin&FixedLengthListMixin","_ListBase&Object&ListMixin","_SetBase&Object&SetMixin","_UnmodifiableMapView&MapView&_UnmodifiableMapMixin","_QueueList&Object&ListMixin","_UnionSet&SetBase&UnmodifiableSetMixin","_UnmodifiableSetView&DelegatingSet&UnmodifiableSetMixin","_IterableSet&SetMixin&UnmodifiableSetMixin","dateNow","addRules","addErasedTypes","_scheduleImmediateJsOverride","_scheduleImmediateWithSetImmediate","_scheduleImmediateWithTimer","_kTrue","decodeComponent","_scheduleImmediateClosure","_nullFuture","_inverseAlphabet","posix","platform","getInterceptor$","getInterceptor$asx","async___startMicrotaskLoop$closure","async__AsyncRun__scheduleImmediateJsOverride$closure","async__AsyncRun__scheduleImmediateWithSetImmediate$closure","async__AsyncRun__scheduleImmediateWithTimer$closure","async___nullDoneHandler$closure","async___nullErrorHandler$closure","async___nullDataHandler$closure","getInterceptor$ax","async___printToZone$closure","math__max$closure","getInterceptor$s","core_Uri_decodeComponent$closure","main_test__main$closure","_js_helper_Primitives_dateNow$closure","async_Future__kTrue$closure","pretty_print___escapeString$closure","util___getHexLiteral$closure","getInterceptor$x","getInterceptor$ansx","getInterceptor$n","async___rootHandleUncaughtError$closure","async___rootRun$closure","async___rootRunUnary$closure","async___rootRunBinary$closure","async___rootRegisterCallback$closure","async___rootRegisterUnaryCallback$closure","async___rootRegisterBinaryCallback$closure","async___rootErrorCallback$closure","async___rootScheduleMicrotask$closure","async___rootCreateTimer$closure","async___rootCreatePeriodicTimer$closure","async___rootPrint$closure","async___rootFork$closure","_captured_K1_1","_captured_this_1","dart.async#_box_0","_captured_matcher_0","future","_captured_V1_2","_captured_listener_2","_captured_frame_0","_captured_expectedElement_1","_captured_getMain_1","location","channel","_captured_depth_3","controller","matches","_captured_beforeLoad_4","_captured_f_1","line","_captured_printZone_5","_captured_R_3","end","_captured_transform_1","result","_captured_selector_0","handleError","_captured_body_1","_captured_elements_0","_captured_x_0","suite","_captured_T_3","index","_captured_validVariables_0","_captured_terse_2","addEventListener","stackTrace","timeout","_withValue","message","immediate","_captured_performance_0","trace","_captured_div_1","level","span","_captured_registered_1","where","toString","callback","decoded","groups","liveTest","_captured_engine_0","_captured_chain_0","completer","_captured_longest_0","dart.async#_box_1","skipWhile","_captured_fn_2","call","_captured_oldPredicate_0","_captured_counter_3","_captured_hasError_3","_captured_loadResource_2","describe","id","_captured_milliseconds_1","_captured_originalSource_0","stream","immediateError","_captured_outstandingCallbacksForBody_1","_captured_sourceResult_2","_captured_K_1","_captured_serializedOnPlatform_0","_captured_V_2","broadcast","onData","_captured_target_0","_captured_sourceMap_1","_captured_invoker_0","_captured_minified_4","zone","_captured_sdkRoot_2","_captured_streamConsumer_0","_captured_e_1","_captured_s_2","_captured_sdkLib_3","_addEventError","column","_captured_cleanUp_3","_captured_eagerError_4","iterator","identifier","port","_captured_action_1","_captured_node_2","_captured_T1_3","_captured_T2_4","withResource","_captured_portSubscription_1","_captured_serialized_0","_captured_doneSignal_2","_captured_arg1_1","_captured_pos_1","_captured_arg2_2","_captured_dispatch_1","_captured_getTag_0","testOn","_captured_getUnknownTag_0","skip","verboseTrace","_captured_prototypeForTag_0","chainStackTraces","_captured_argumentError_0","==","retry","_captured_arg_1","skipReason","_captured__convertedObjects_0","onPlatform","uri","_captured_portStart_1","skipped","_captured_textInside_1","_captured_maxItems_0","_captured_maxLineLength_1","original","_runGroup","host","_captured__prettyPrint_1","_runLiveTest","_captured_indent_0","_runSkippedTest","_captured_computation_1","_captured_orElse_1","_captured_countSuccess_2","_captured_K2_0","_captured_protected_0","_captured_spec_6","_captured_pp_0","onError","_captured_object_1","_captured_ex_0","subscription","cancel","_captured_bodyFunction_0","_captured_tables_0","data","stream_channel_controller.dart#_foreign","stack_zone_specification.dart#_disabled","expanded.dart#_onTestStarted","newDartList","configure","elapsedMicroseconds","dart.core#_captured_argumentError_0","platform_selector.dart#_captured_this_0","dart.async#_completeWithValue","source_maps.parser#_captured_this_0","dart.async#_isScheduled","hashMapCellKey","dart.async#_captured_future_1","multi_channel.dart#_closeChannel","encode","dart.async#_error","dart.core#_start","onClose","dart.async#_captured_callback_1","dart.async#_completeError","dart.async#_captured_doneSignal_2","_js_helper#_captured_result_2","separators","async_memoizer.dart#_completer","invoker.dart#_counterKey","functions.dart#_captured_V1_2","prettyUri","metadata.dart#_captured_serializedOnPlatform_0","equals_matcher.dart#_compareSets","fold","getRoot","dart.collection#_newSet","invoker.dart#_captured_stackTrace_3","dart.core#_isPort","catchError","*","parser.dart#_and","metadata.dart#_captured_verboseTrace_4","scanner.dart#_scanOperator","highlighter.dart#_writeFirstLine","dart.async#_handleUncaughtError","engine.dart#_failedGroup","rti#_kind","isOdd","source_maps.parser#_findLine","functions.dart#_captured_V_3","stack_zone_specification.dart#_currentNode","engine.dart#_onSuiteStartedController","stack_zone_specification.dart#_captured_f_0","pool.dart#_requestedResources","indexOf","rti#_precomputed4","source_maps.parser#_consumeValue","replaceAll","stream_queue.dart#_completer","tags","source_map_stack_trace.dart#_captured_sdkRoot_3","_js_helper#_previous","needsSeparatorPattern","dart.async#_run","replace","dart.collection#_add","_interceptors#_shlPositive","stream_sink_completer.dart#_ensureController","dart.dom.html#_location","dart.async#_mayAddListener","lazy_chain.dart#_captured_terse_2","queue_list.dart#_preGrow","evaluate","wrappers.dart#_base","context.dart#_isWithinOrEqualsFast","_js_helper#_modified","shouldBeDone","isBlink","dart.core#_currentCodePoint","stream_queue.dart#_addResult","source_map_stack_trace.dart#_box_0","typedMatches","moveNext","_js_helper#_execAnchored","duration","guarantee_channel.dart#_onSinkDisconnected","isCompleted","query","visitVariable","bindCallback","stack_zone_specification.dart#_currentTrace","sourceRoot","take","expanded.dart#_progressLine","listenerHasError","liveTests","createPeriodicTimer","live_suite_controller.dart#_onCloseCompleter","remote_listener.dart#_captured_controller_0","dart.async#_onCancel","pathsEqual","distance","dart.core#_position","asStream","abs","listenerValueOrError","dart.async#_decrementPauseCount","html_common#_captured_this_1","hasTrailingSeparator","stack_trace_formatter.dart#_captured_this_0","declarer.dart#_name","stack_zone_specification.dart#_registerBinaryCallback","dart.collection#_tail","asUint8List","isScaffoldAll","remote_listener.dart#_captured_test_1","isWithin","platform_selector.dart#_captured_validVariables_0","functions.dart#_box_0","live_suite_controller.dart#_passed","addTearDown","dart._internal#_iterator","hashMapCellValue","functions.dart#_captured_K1_1","lastSpan","runner_suite.dart#_environment","registerCallback","handleUncaughtError","stack_zone_specification.dart#_captured_this_1","allMatches","dart.collection#_position","_js_helper#_expr","dart.convert#_data","runner_suite.dart#_suiteChannel","engine.dart#_captured_suite_1","onListen","equals_matcher.dart#_recursiveMatch","highlighter.dart#_writeSidebar","stream_group.dart#_onCancelBroadcast","dart._internal#_iterable","dart.convert#_buffer","print_sink.dart#_flush","operating_system.dart#_captured_identifier_0","invoker.dart#_print","scan","onCancel","dart.async#_schedule","declarer.dart#_metadata","highlighter.dart#_colorize","source_map_stack_trace.dart#_captured_sdkRoot_2","_js_helper#_target","highlighter.dart#_multiline","highlighter.dart#_paddingBeforeSidebar","dart._internal#_startIndex","rti#_specializedTestResource","_interceptors#_isInt32","dart.collection#_contains","invoker.dart#_handleError","engine.dart#_runPool","dart.async#_setPendingComplete","parser.dart#_simpleExpression","dart.async#_rootRunUnary","dart.async#_cloneResult","stack_zone_specification.dart#_trimVMChain","live_test_controller.dart#_run","dart.collection#_first","metadata.dart#_serializeTimeout","relative","declarer.dart#_collectTraces","span_exception.dart#_message","live_test_controller.dart#_onRun","join","stack_zone_specification.dart#_errorCallback","stack_zone_specification.dart#_registerUnaryCallback","runner_suite.dart#_closeMemo","change","addAll","dart.async#_isPaused","previous","dart.async#_addError","source_map_stack_trace.dart#_captured_minified_4","dart.core#_queryStart","dart.async#_captured_eagerError_4","dart.async#_captured_T_2","whenComplete","position","_js_helper#_newLinkedCell","asCurrent","addDescriptionOf","dart.async#_captured_orElse_1","dart.async#_captured_subscription_0","invoker.dart#_count","rti#_eval","dart.async#_addEventError","rti#_optionalPositional","queue_list.dart#_writeToList","dart.async#_hasPending","live_test_controller.dart#_test","runner_suite.dart#_captured_this_0","pool.dart#_captured_zone_0","invoker.dart#_controller","setRange","write","pretty_print.dart#_captured_maxItems_0","stream_queue.dart#_isClosed","multi_channel.dart#_closeInnerChannel","invoker.dart#_closable","update","test.src.runner.browser.post_message_channel#_captured_controller_0","dart.collection#_current=","dart.async#_thenAwait","dart.convert#_upgrade","_js_helper#_fetch","isNewSegment","evaluator.dart#_semantics","internalFindBucketIndex","stack_zone_specification.dart#_registerCallback","file.dart#_cachedLine","live_suite_controller.dart#_controller","dart.async#_cancel","isPassing","source_maps.parser#_lineStart","expanded.dart#_noColor","dart.async#_forEachListener","runner_suite.dart#_isDebugging","fillRange","dart.convert#_second","removeTrailingSeparators","dart.core#_errorName","union_set_controller.dart#_set","visitAnd","pool.dart#_closeMemo","dart.async#_chainFuture","declare","needsSeparator","second","dart.async#_isCanceled","handleNext","union_set.dart#_disjoint","live_test_controller.dart#_liveTest","dart.async#_ensurePendingEvents","failed","context.dart#_parse","string_scanner.dart#_lastMatch","replaceRange","dart.convert#_carry","expand","stack_zone_specification.dart#_captured_this_0","isAbsolute","peek","pretty_print.dart#_captured_object_1","dart.async#_rootRegisterCallback","guarantee_channel.dart#_doneCompleter","isValue","_interceptors#_defaultSplit","_js_helper#_execGlobal","pool.dart#_onResourceReleaseAllowed","_interceptors#_shrBothPositive","chain.dart#_captured_longest_0","dart.core#_captured_error_0","dart.collection#_remove","dart._internal#_string","padRight","dart.async#_iterator","success","stack_zone_specification.dart#_captured_stackTrace_2","multi_channel.dart#_pendingIds","dart.core#_toNonSimple","remote_listener.dart#_captured_spec_6","readChar","scaleFactor","_js_helper#_string","stream_group.dart#_controller","remote_listener.dart#_serializeGroup","microsecond","declarer.dart#_checkNotBuilt","dart.collection#_computeKeys","binaryOnError","parser.dart#_or","dart.async#_captured_computation_1","dart.collection#_findBucketIndex","release","runner_suite.dart#_close","union","_interceptors#_iterable","dart.typed_data.implementation#_setRangeFast","live_suite_controller.dart#_captured_countSuccess_2","dart.collection#_map","internalSet","dart.collection#_checkModification","single","functions.dart#_captured_K_2","joinAll","dart.async#_isEmpty","load","pipe","identicalInJs","dart.async#_asyncCompleteError","lazy_trace.dart#_trace","hashCode","pool.dart#_pool","decrement","registerUnaryCallback","pool.dart#_maxAllocatedResources","isComplete","indexable","hasTokens","dart.dom.html#_onData","_js_helper#_strings","pathSegments","expanded.dart#_green","dart.async#_captured_registered_1","describeTypedMismatch","equals_matcher.dart#_expected","whenFalse","decode","dart._internal#_captured_f_1","any","convert","[]=","future_group.dart#_completer","dart.async#_add","matchTypeError","_js_helper#_last","unclosable","containsKey","engine.dart#_runGroup","queue_list.dart#_grow","chain.dart#_captured_predicate_0","minute","source_maps.parser#_length","declarer.dart#_setUpAllTrace","codeUnitAt","modifiedObject","dart.js_util#_captured_completer_0","newJsObject","active","expanded.dart#_lastProgressSuffix","future_group.dart#_captured_index_1","union_set.dart#_dedupIterable","live_suite_controller.dart#_captured_this_0","endsWith","dart.async#_captured_streamConsumer_0","removeLast","urls","dart.async#_captured_hasError_3","remote_listener.dart#_captured_this_1","dart.async#_varData","_js_helper#_name","engine.dart#_coverage","dart._internal#_endIndex","codeUnitsEqual","dart.async#_captured_s_2","toTrace","source_maps.parser#_segmentError","pool.dart#_closeGroup","bindUnaryCallback","dart.collection#_length","runner_suite.dart#_controller","dart.js_util#_captured_T_1","dart.async#_delegationTarget","storedCallback","declarer.dart#_solo","round","rti#_bind","errorCallback","functions.dart#_captured_V1_3","dart.convert#_allowMalformed","lastIndexOf","expanded.dart#_onError","declarer.dart#_setUpAll","core_matchers.dart#_description","restartable_timer.dart#_callback","metadata.dart#_skip","dart2jsArgs","subscription_stream.dart#_captured_error_1","dart.async#_resultOrListeners","relativeRootPattern","dart.async#_onError","stream_completer.dart#_createController","dart.collection#_previous","dart.async#_setError","file.dart#_isNearCachedLine","wrappers.dart#_setBase","equals_matcher.dart#_compareIterables","add","stream_group.dart#_listenToStream","html_common#_box_0","visitNot","complete","cast","engine.dart#_skippedGroup","type_matcher.dart#_name","dart.core#_errorExplanation","close","highlight","source_maps.parser#_mappings","putIfAbsent","_js_helper#_captured_performance_0","isFinite","runSkipped","isUnicode","addError","toChain","_js_helper#_map","dart.async#_captured_this_1","setUpAll","declarer.dart#_noRetry","spanFor","dart.async#_errorCallback=","dart.dom.html#_captured_onData_0","count","dart.async#_isComplete","dart.async#_captured_span_2","remote_listener.dart#_captured_groups_2","setAll","dart.async#_onValue","setDestinationSink","scanner.dart#_getNext","packageConfigMap","dart.core#_captured_tables_0","writeln","expanded.dart#_printPlatform","_js_helper#_trace","dart.async#_mayResumeInput","invalidValue","dart.async#_recordPause","stack_zone_specification.dart#_captured_T1_3","span_scanner.dart#_scanner","expanded.dart#_bold","file.dart#_start","noMoreLiveTests","dart.dom.html#_unlisten","engine.dart#_runCalled","string_scanner.dart#_position","removeFragment","pool.dart#_released","dart.async#_setValue","firstMatch","dart.async#_captured_originalSource_0","liveSuite","_js_helper#_next","source_maps.parser#_internal","dart.core#_isFile","isLoadSuite","guarantee_channel.dart#_streamController","dart.dom.html#_addEventListener","dart._internal#_endOrLength","resolveUri","_js_helper#_addHashTableEntry","whenTrue","isEmpty","scanner.dart#_multiLineComment","iterable_set.dart#_base","dart.async#_checkState","dart.async#_captured_future_3","dart.async#_inCallback","expanded.dart#_captured_this_0","files","splitMapJoin","toFilePath","internalRemove","toolString","stream_queue.dart#_failClosed","_interceptors#_toListFixed","group.dart#_captured_platform_0","lazy_trace.dart#_captured_this_0","remaining","dart.core#_captured_portStart_1","parser.dart#_conditional","equals_matcher.dart#_match","dart.core#_pathSegments","firstWhere","floor","padLeft","stack_zone_specification.dart#_captured_R_5","trace.dart#_captured_longest_0","onMessage","engine.dart#_addedSuites","dart.core#_captured_host_0","engine.dart#_captured_skipped_1","expanded.dart#_color","stack_zone_specification.dart#_run","dart.core#_mergePaths","getText","declarer.dart#_trace","subscription_stream.dart#_captured_handleError_1","dart.core#_isHttp","expanded.dart#_lastProgressPassed","month","apply","invoker.dart#_captured_outstandingCallbacksForBody_1","dart.async#_badEventState","metadata.dart#_captured_validVariables_0","canonicalizePart","toRadixString","dart.collection#_cell","dart.core#_simpleMerge","remote_listener.dart#_runLiveTest","stream_queue.dart#_addRequest","dart.collection#_removeHashTableEntry","validate","queue_list.dart#_table","future_group.dart#_pending","matchesErrorTest","dart.async#_removeListener","hasErrorTest","expanded.dart#_onDone","dart.async#_onPause","packageConfigUri","expanded.dart#_lastProgressSkipped","engine.dart#_box_0","rti#_check","isRelative","platform_selector.dart#_captured_selector_0","metadata.dart#_retry","_js_helper#_input","nextIteration","expanded.dart#_timeString","pointSpan","variableName","dart.core#_schemeCache","insert","day","lazy_trace.dart#_captured_predicate_1","putIntoMap","live_test_controller.dart#_state","lines","dart.async#_recordCancel","dart.async#_sendError","_js_helper#_newHashTable","removeFirst","pool.dart#_runOnRelease","source_maps.parser#_findColumn","dart.async#_removeListeners","trim","stream_queue.dart#_close","queue_list.dart#_head","_js_helper#_index","isDartVM","stream_sink_completer.dart#_sink","virtualChannel","listeners","dart.core#_host","stream_queue.dart#_subscription","bindCallbackGuarded","containsSeparator","inMinutes","endColumn","stream_channel_completer.dart#_streamCompleter","right","hasAbsolutePath","_js_helper#_re","varData","onComplete","dart.collection#_get","toUri","dart.core#_fragmentStart","suite_channel_manager.dart#_outgoingConnections","hasPort","dart.collection#_modified","runtime","getLine","highlighter.dart#_countTabs","_js_helper#_nativeAnchoredRegExp","dart.async#_waitsForCancel","run","_js_helper#_captured_ex_0","errorZone","foundResult","engine.dart#_onSuiteAddedController","dart.async#_captured_future_2","stream_completer.dart#_setSourceStream","suite_channel_manager.dart#_incomingConnections","declarer.dart#_setUpAlls","getColumn","dart.collection#_element","dart.async#_errorTest","_js_helper#_argumentsExpr","schedule","dart.collection#_captured_result_0","stream_channel_completer.dart#_sinkCompleter","dart.async#_eventScheduled","highlighter.dart#_writeTrailingLines","rootLength","live_suite_controller.dart#_captured_liveTest_1","normalize","invoker.dart#_closableKey","live_test_controller.dart#_controller","isSync","dart.convert#_parent","source","engine.dart#_runLiveTest","dart.async#_isPendingComplete","source_maps.parser#_captured_line_0","copyList","dart.convert#_writeSurrogate","remote_listener.dart#_serializeTest","dart.convert#_fillBuffer","toJson","highlighter.dart#_writeLastLine","addOutstandingCallback","_interceptors#_codeUnitAt","chainFor","~/","dart.collection#_addHashTableEntry","package","isHeadless","completeError","verboseChain","declarer.dart#_parent","stream_group.dart#_subscriptions","listen","describeMismatch","closed","stream_queue.dart#_streamQueue","declarer.dart#_runSetUps","pretty_print.dart#_captured_indent_0","dart.typed_data.implementation#_checkPosition","declarer.dart#_prefix","stream_completer.dart#_linkStreamToController","setSourceStream","targetUrl","_js_helper#_message","inMilliseconds","dart.async#_previous","dart.js_util#_captured__convertedObjects_0","declarer.dart#_entries","dart.async#_setPendingEvents","stack_zone_specification.dart#_captured_node_2","dart._internal#_length","invoker.dart#_guardIfGuarded","done","declarer.dart#_platformVariables","highlighter.dart#_isOnlyWhitespace","isJS","isEof","sourceLine","dart._internal#_f","isBroadcast","invoker.dart#_captured_invoker_0","remote_listener.dart#_box_0","declarer.dart#_tearDowns","dart.async#_close","multi_channel.dart#_nextId","remote_listener.dart#_captured_liveTest_0","engine.dart#_closed","highlighter.dart#_writeText","matchAsPrefix","invoker.dart#_guarded","lazy_trace.dart#_inner","_js_helper#_unlinkCell","live_suite_controller.dart#_failed","stack_zone_specification.dart#_box_0","lazy_chain.dart#_captured_this_0","relativePathToUri","dart.async#_isChained","newJsMap","pool.dart#_allocatedResources","highlighter.dart#_color","dart.async#_mayAddEvent","group.dart#_map","multi_channel.dart#_captured_this_1","dart.collection#_computeHashCode","dart.async#_complete","dart.async#_captured_result_2","dart.async#_captured_e_1","null_stream_sink.dart#_captured_this_0","remote_listener.dart#_captured_printZone_0","dart.async#_isAddingStream","dart.collection#_newLinkedCell","_interceptors#_shrReceiverPositive","fragment","dart.async#_sendData","dart.dom.html#_pauseCount","runUnary","dart.convert#_upgradedMap","scanner.dart#_scanner","precompiledPath","dart.dom.html#_target","dart.core#_hashCodeCache","runGuarded","span_scanner.dart#_lastSpan","toList","metadata.dart#_verboseTrace","dart.async#_whenCompleteAction","replaceAllMapped","rti#_as","invoker.dart#_captured_counter_3","_js_helper#_nativeGlobalRegExp","declarer.dart#_soloEntries","addSubscription","increment","stack_zone_specification.dart#_captured_R_4","pool.dart#_onResourceReleased","dart.collection#_strings","dart.async#_handle","hasListener","dart.async#_ensureDoneFuture","stack_zone_specification.dart#_handleUncaughtError","remote_listener.dart#_captured_printZone_1","invoker.dart#_invokerZone","_js_helper#_captured_transform_1","pause","runUnaryGuarded","spanFrom","runner_suite.dart#_config","runner_suite.dart#_onClose","dart.async#_zone","stream_sink_completer.dart#_controller","highlighter.dart#_captured_textInside_1","[]","expanded.dart#_lastProgressMessage","invoker.dart#_captured_fn_2","lastPendingEvent","inMicroseconds","visitConditional","dart._internal#_index","stream_sink_completer.dart#_doneCompleter","inSeconds","live_test_controller.dart#_onStateChangeController","rti#_cachedRuntimeType","dart.async#_captured_controller_0","packageResolver","dart.async#_captured_eagerError_2","stack_zone_specification.dart#_captured_T2_4","live_test_controller.dart#_onClose","live_test_controller.dart#_groups","dart.core#_duration","file.dart#_decodedChars","parents","parent","_js_helper#_jsObject","dart.async#_captured_test_0","stream_group.dart#_state","startsWith","dart.collection#_set","absolute","foreign","dart.async#_hasError","merge","handleValue","resolve","rti#_message","stream_queue.dart#_updateRequests","dart.async#_delegateCache","hasFragment","traces","isScheduled","union_set.dart#_iterable","invoker.dart#_onCloseCompleter","isNotEmpty","test.dart#_captured_body_0","hasScheme","engine.dart#_closedBeforeDone","span_exception.dart#_span","dart.async#_captured_cleanUp_1","dart._internal#_source","fuse","live_suite_controller.dart#_suite","invoker.dart#_runTearDowns","dart.async#_captured_elements_0","addStream","rti#_precomputed3","null_stream_sink.dart#_checkEventAllowed","chain.dart#_captured_terse_0","patterns","stream_completer.dart#_setEmpty","dart.core#_fragment","createTimer","variables","runner_suite.dart#_onDebuggingController","dart.async#_captured_protected_0","every","trace.dart#_captured_trace_0","perform","dart.async#_captured_bodyFunction_0","dart.convert#_allowInvalid","pool.dart#_captured_this_0","outputId","dart.async#_onResume","dart.convert#_extraUnits","toUpperCase","equals_matcher.dart#_value","dart.async#_controller","multi_channel.dart#_captured_this_0","engine.dart#_group","guarantee_channel.dart#_addStreamSubscription","queue_list.dart#_add","forEachJsField","_js_helper#_regExp","_js_helper#_nums","_js_helper#_getTableBucket","dart.convert#_computeKeys","stack_trace_mapper.dart#_mapping","millisecondsSinceEpoch","_js_helper#_cell","connectOut","union_set.dart#_sets","engine.dart#_onUnpaused","_js_helper#_genericClosure","stack_trace_mapper.dart#_mapContents","pretty_print.dart#_captured_indent_2","multi_channel.dart#_innerStreamSubscription","substring","entries","multi_channel.dart#_inner","hasRun","stack_zone_specification.dart#_onError","elapsedTicks","engine.dart#_onTestStartedGroup","tearDownAll","core_matchers.dart#_matcher","expanded.dart#_engine","dart.async#_captured_future_0","_js_helper#_captured_getTag_0","names","internalContainsKey","dart.async#_captured_start_2","dart.async#_addStreamState","stream_queue.dart#_extractStream","setState","clear","dart.core#_stop","stream_group.dart#_closed","os","hasPartialInput","dart.collection#_getBucket","declarer.dart#_timeout","parts","context.dart#_pathDirection","findSlot","dart.async#_scheduleMicrotask","engine.dart#_loadPool","stream_sink_completer.dart#_canSendDirectly","local","hour","packageRoot","scanner.dart#_endOfFileEmitted","dart.async#_captured_callback_0","chain.dart#_captured_terse_1","future_group.dart#_closed","dart.convert#_value","readSlot","fork","elementAt","dart.async#_runUnary","writeAll","dart.async#_captured_callback_3","stack_zone_specification.dart#_captured_arg_1","writeSlot","dart.async#_next","rti#_hashCode","stream_group.dart#_onListen","dart.async#_captured_sourceResult_2","runOnce","dart.convert#_reviver","invoker.dart#_captured_this_1","innerStream","lazy_chain.dart#_thunk","stream_queue.dart#_ensureListening","stack_zone_specification.dart#_chains","dart.async#_captured_listener_1","startColumn","status","dart.async#_removeAfterFiring","multi_channel.dart#_box_0","dart.dom.html#_eventType","dart.collection#_head","resume","_js_helper#_removeHashTableEntry","_js_helper#_match","dart.async#_isFiring","includeTags","_interceptors#_current=","intersection_selector.dart#_selector2","rti#_rti","dart.async#_asyncComplete","request","expanded.dart#_gray","stack_trace_formatter.dart#_except","engine.dart#_subscriptions","subscription_stream.dart#_source","stack_trace_mapper.dart#_sdkRoot","dart.async#_recordResume","scheme","dart.convert#_expectedUnits","source_maps.parser#_captured_column_0","dart.collection#_closeGap","highlighter.dart#_buffer","sink.dart#_sink","map","parser.dart#_scanner","rti#_bindCache","dart._internal#_name","stack_trace_formatter.dart#_only","removeEventListener","_js_helper#_setTableEntry","live_test_controller.dart#_close","dart.core#_queryParameters","_js_helper#_length","engine.dart#_captured_this_1","pretty_print.dart#_box_0","addMapping","description.dart#_out","dart.async#_captured_R_3","dart.async#_isClosed","flush","remote_listener.dart#_captured_getMain_1","expanded.dart#_onStateChange","functions.dart#_captured_K1_2","toLowerCase","heartbeat","registerBinaryCallback","_js_helper#_method","dart.collection#_writeToList","contains","removeOutstandingCallback","functions.dart#_captured_value_1","dart.async#_prependListeners","dart.core#_data","chain.dart#_captured_callback_0","guarantee_channel.dart#_inner","dart.async#_fork","_js_helper#_nextIndex","dart.core#_captured_uri_0","source_maps.parser#_maps","dart.convert#_bufferIndex","expanded.dart#_red","dart.collection#_unlinkCell","forEachObjectKey","isRootRelative","engine.dart#_restarted","platform_selector.dart#_captured_validVariables_1","dart.async#_doneFuture","dart.convert#_original","dart.async#_registerCallback=","foldFrames","remote_listener.dart#_captured_channel_3","live_test_controller.dart#_onMessageController","separatorPattern","dart.convert#_isFirstCharacter","stack_zone_specification.dart#_errorZone","addStreamFuture","declarer.dart#_built","rti#_requiredPositional","dart.async#_captured_test_2","dart.collection#_table","_js_helper#_deleteTableEntry","split","group.dart#_captured_callback_0","remote_listener.dart#_listen","source_maps.parser#_mapUrl","validatePlatformSelectors","dart.async#_map","hasEmptyPath","sublist","stream_completer.dart#_stream","invoker.dart#_onRun","dart.async#_subscribe","util.dart#_captured_x_0","stream_completer.dart#_sourceStream","metadata.dart#_chainStackTraces","stream_sink_completer.dart#_setDestinationSink","addTearDownAll","live_suite_controller.dart#_isComplete","dart.async#_captured_result_0","equals_matcher.dart#_captured_expectedElement_1","package_config_resolver.dart#_uri","dart.async#_reverseListeners","dart.collection#_last","invoker.dart#_captured_timeout_0","walk","dart.core#_userInfo","formatStackTrace","remove","dart.async#_captured_listener_2","_js_helper#_keys","dart.convert#_encoder","runBinary","dart.async#_cancelFuture","future_group.dart#_captured_this_0","handlesComplete","trace.dart#_box_0","dart.async#_captured_pos_1","invoker.dart#_test","metadata.dart#_captured_timeout_2","guarantee_channel.dart#_box_0","cancelSchedule","sourceNameId","runtimeType","dart.collection#_modifications","then","pretty_print.dart#_captured_maxLineLength_1","dart.convert#_subsetMask","testRandomizeOrderingSeed","visitOr","expanded.dart#_lastProgressFailed","dart.convert#_first","millisecond","highlighter.dart#_captured_this_1","dart.async#_rootRegisterUnaryCallback","dart.async#_captured_f_1","remote_listener.dart#_captured_this_0","reversed","dart.core#_queryParameterLists","hasErrorCallback","metadata.dart#_captured_testOn_1","left","live_test_controller.dart#_isClosed","highlighter.dart#_captured_this_0","dart.dom.html#_start","lazy_chain.dart#_chain","null_stream_sink.dart#_addingStream","_js_helper#_getTableCell","forTarget","source_map_stack_trace.dart#_captured_minified_2","peekChar","dart.collection#_next","build","dart.convert#_process","rti#_precomputed2","lastMatch","expanded.dart#_description","metadata.dart#_captured_chainStackTraces_5","remote_listener.dart#_captured_printZone_5","decoder","hasAuthority","unaryOnError","context.dart#_needsNormalization","onResume","expanded.dart#_printPath","_interceptors#_tdivSlow","_js_helper#_rest","expanded.dart#_paused","declarer.dart#_tearDownAll","dart.async#_captured_result_3","lazy_trace.dart#_thunk","waitForOutstandingCallbacks","dart.async#_mayComplete","dart.async#_captured_T_5","function","dart.collection#_offset","dart.async#_captured_value_1","bindUnaryCallbackGuarded","dart.async#_sendDone","invoker.dart#_outstandingCallbacks","extensions","dart.async#_closeUnchecked","_interceptors#_toListGrowable","highlighter.dart#_captured_end_2","invoker.dart#_captured_this_0","codeUnits","rti#_precomputed1","stack_zone_specification.dart#_captured_f_1","internalGet","suite_channel_manager.dart#_names","pattern","dart.async#_captured_stackTrace_1","live_suite_controller.dart#_liveSuite","source_maps.parser#_consumeNewLine","metadata.dart#_box_0","suiteSink","dart.core#_port","engine.dart#_passedGroup","dart.core#_hostStart","context.dart#_isWithinOrEquals","expanded.dart#_captured_liveTest_1","live_suite_controller.dart#_closeMemo","dart.async#_captured_target_0","dart.core#_captured_host_1","dart.core#_uriCache","_js_helper#_nativeAnchoredVersion","scanner.dart#_consumeWhitespace","scanner.dart#_scanOr","dart.dom.html#_tryResume","runner_suite.dart#_suite=","toSet","chain.dart#_captured_level_1","sourceColumn","dart.async#_nextListener","connectIn","_js_helper#_isCaseSensitive","invoker.dart#_body","encoder","metadata","dart.async#_isInputPaused","inHours","allowRelease","_js_helper#_captured_getUnknownTag_0","invoker.dart#_completer","intersection","state","dart.async#_state","reportLiveTest","dart.async#_clearPendingComplete","invoker.dart#_captured_timeout_2","stack_trace_mapper.dart#_mapUrl","multi_channel.dart#_controllers","declarer.dart#_tearDownAllTrace","chain.dart#_captured_T_1","highlighter.dart#_box_0","multi_channel.dart#_captured_T_2","expanded.dart#_subscriptions","dart.collection#_captured_V_2","stack_zone_specification.dart#_captured_original_1","subscription_stream.dart#_captured_this_0","stream_queue.dart#_captured_this_0","live_test_controller.dart#_runCalled","group.dart#_testCount","onDone","dart.async#_isInitialState","isEven","_js_helper#_types","root","highlighter.dart#_writeIntermediateLines","library","dart.core#_hasValue","member","dart.async#_lastSubscription=","dartException","dart.core#_schemeEnd","_js_helper#_captured_this_0","isPosix","dart._internal#_hasSkipped","dart.convert#_isUpgraded","dart.async#_print=","dart.async#_rootRegisterBinaryCallback","dart.dom.html#_useCapture","test.src.runner.browser.post_message_channel#_captured_port_0","type","scanner.dart#_scanIdentifier","dart.async#_onDone","origin","multi_channel.dart#_parent","%","_js_helper#_keysArray","span_scanner.dart#_sourceFile","metadata.dart#_captured_skip_3","insertAll","platform_selector.dart#_inner","pretty_print.dart#_captured_pp_0","engine.dart#_captured_this_0","dart.async#_eventState","child","forTag","guarantee_channel.dart#_channel","dart.async#_captured_iterator_0","checkMutable","remote_listener.dart#_captured_controller_3","replaceFirst","file.dart#_binarySearch","transform","stack_trace_formatter.dart#_mapper","_interceptors#_tdivFast","trace.dart#_captured_oldPredicate_0","_js_helper#_exception","stack_zone_specification.dart#_captured_arg2_2","remote_listener.dart#_suite","isIdentifier","rti#_primary","removeAt","dart.async#_captured_div_1","dart._internal#_current=","_js_helper#_current=","dart.dom.html#_removeEventListener","rti#_optionalNamed","dart.async#_isSent","_js_helper#_receiver","forPlatform","dart.async#_delegate","firstPendingEvent","inSameErrorZone","frames","highlighter.dart#_span","remote_listener.dart#_captured_channel_2","dart._internal#_currentExpansion","union_set_controller.dart#_sets","dart.async#_captured_this_0","_js_helper#_isUnicode","lazy_trace.dart#_captured_terse_2","span_exception.dart#_source","dart._internal#_start","_js_helper#_modifications","dart.core#_nextPosition","dart.convert#_toEncodable","hasMatch","dart.async#_canFire","postMessage","internalComputeHashCode","invoker.dart#_outstandingCallbackZones","invoker.dart#_captured_zone_1","rootPattern","multi_channel.dart#_captured_id_1","platform_selector.dart#_span","isUtc","dart.dom.html#_postMessage_2","set","pathFromUri","dart.async#_hasOneListener","engine.dart#_activeLoadTests","_js_helper#_containsTableEntry","restartable_timer.dart#_timer","dart.async#_pendingEvents","guarantee_channel.dart#_addError","_js_helper#_captured_prototypeForTag_0","dart.async#_nullErrorHandler","dart.async#_captured_cleanUp_3","+","dart.async#_chainSource","dart.core#_portStart","remote_listener.dart#_printZone","multi_channel.dart#_captured_T_1","frame.dart#_captured_frame_0","source_maps.parser#_columnStart","functions.dart#_captured_result_0","engine.dart#_addLiveSuite","_js_helper#_pattern","guarantee_channel.dart#_onStreamDisconnected","dart.core#_stackTrace","elapsed","accept","dart.async#_subscription","stack_trace_mapper.dart#_packageResolver","stream_queue.dart#_eventsReceived","_js_helper#_isMultiLine","live_test_controller.dart#_errors","_interceptors#_index","onZero","chain.dart#_captured_chain_0","engine.dart#_runSkippedTest","pretty_print.dart#_captured__prettyPrint_1","removeAll","stack_zone_specification.dart#_captured_arg1_1","dart.async#_callOnCancel","live_suite_controller.dart#_onCompleteGroup","last","runner_suite.dart#_channelNames","sourceUrl","dart.async#_onListen","pool.dart#_resetTimer","engine.dart#_liveSuites","stream_channel_completer.dart#_set","file.dart#_end","dart.async#_captured_value_2","runner_suite.dart#_gatherCoverage","dart.core#_isHttps","dart.collection#_captured_result_1","multi_channel.dart#_closedIds","remote_listener.dart#_captured_channel_0","isNewLine","invoker.dart#_runCount","union_set.dart#_captured_this_0","suite.dart#_runSkipped","live_suite_controller.dart#_skipped","stack_zone_specification.dart#_captured_R_2","dart.core#_isPackage","dart.async#_expectsEvent","metadata.dart#_validateTags","copy","invoker.dart#_captured_error_2","dart.async#_addPending","dart.convert#_stringSink","remainder","guarantee_channel.dart#_inAddStream","pool.dart#_captured_registered_1","runtime.dart#_captured_serialized_0","pool.dart#_onReleaseCompleters","functions.dart#_captured_result_1","inGoogle","runBinaryGuarded","guarantee_channel.dart#_allowErrors","declarer.dart#_captured_body_0","dart.async#_createSubscription","condition","checkGrowable","absolutePathToUri","suite.dart#_runtimes","engine.dart#_captured_loadResource_2","dart.collection#_rest","dart.async#_setRemoveAfterFiring","guarantee_channel.dart#_sink","metadata.dart#_captured_retry_6","context.dart#_current","dart.async#_registerBinaryCallback=","stream_completer.dart#_controller","isCore","highlighter.dart#_captured_line_1","getOffset","dart.async#_createPeriodicTimer","pool.dart#_timeout","mustCopy","dart.async#_firstSubscription","scanner.dart#_next","onTestStarted","scanner.dart#_scanAnd","dart.async#_once","dart.core#_query","config","dart.async#_future","next","style","invoker.dart#_tearDowns","_js_helper#_getBucket","expanded.dart#_stopwatch","engine.dart#_active","dart.async#_rootRun","dart.async#_guardCallback","live_suite_controller.dart#_onTestStartedController","path","dart.async#_target","functions.dart#_captured_K2_0","suite.dart#_knownTags","dart.async#_tick","pool.dart#_onReleaseCallbacks","remote_listener.dart#_captured_suite_0","dart.core#_pathStart","platform_selector.dart#_captured_platform_0","restartable_timer.dart#_duration","test.src.runner.browser.post_message_channel#_captured_portSubscription_1","guarantee_channel.dart#_captured_this_0","setEmpty","dart.typed_data.implementation#_invalidPosition","dart.collection#_containsKey","string_scanner.dart#_lastMatchPosition","metadata.dart#_captured_onPlatform_8","lazy_chain.dart#_captured_predicate_1","dart.async#_addListener","multi_channel.dart#_mainController","guarantee_channel.dart#_captured_this_1","invoker.dart#_box_0","_interceptors#_length","nextKind","dart.collection#_box_0","guarantee_channel.dart#_subscription","addTo","dart.core#_uri","isDone","dart.async#_stateData","setChannel","dart.async#_captured_R_2","stream_channel_completer.dart#_channel=","runes","dart.core#_jsWeakMapOrKey","dart.collection#_queue","null_stream_sink.dart#_closed","file.dart#_lineStarts","stream_queue.dart#_eventQueue","isSeparator","_js_helper#_arguments","guarantee_channel.dart#_disconnected","span_with_context.dart#_context","stream_group.dart#_captured_stream_1","source_maps.parser#_indexFor","dart.async#_cancelOnError","rest","rti#_rest","dart.async#_captured_stackTrace_2","stream_subscription.dart#_source","dart.core#_toFilePath","sourceUrlId","_interceptors#_shrOtherPositive","guarantee_channel.dart#_closed","future_group.dart#_onIdleController","dart.core#_value","dart.async#_captured_error_1","writeCharCode","declarer.dart#_tearDownAlls","source_map_stack_trace.dart#_captured_sourceMap_1","_js_helper#_self","onStateChange","invoker.dart#_timeoutTimer","currentChain","print_sink.dart#_buffer","live_test_controller.dart#_suite","string","engine.dart#_captured_liveTest_1","isBrowser","engine.dart#_suiteController","rti#_canonicalRecipe","dart.dom.html#_canceled","_js_helper#_nativeGlobalVersion","dart.async#_pending","queue_list.dart#_tail","dart.async#_createTimer","passed","dart.async#_onData","dart.async#_captured_action_1","declarer.dart#_captured_body_1","stream_group.dart#_captured_this_0","remote_listener.dart#_captured_liveTest_1","remote_listener.dart#_captured_beforeLoad_4","invoker.dart#_printsOnFailure","dart.core#_writeAuthority","pool.dart#_onTimeout","startChunkedConversion","handleWhenComplete","guarantee_channel.dart#_addStreamCompleter","dart.collection#_captured_K_1","inputId","cloneNotRequired","first","shuffle","engine.dart#_pauseCompleter","newJsList","dart.async#_registerUnaryCallback=","key","_js_helper#_first","dart.async#_captured_T_1","name","dart.dom.html#_postMessage_1","dart.collection#_keys","onPause","dart.async#_captured_data_1","hasQuery","dart.collection#_nums","dart.async#_setErrorObject","dart.collection#_source","handlesValue","stack_zone_specification.dart#_captured_R_3","remote_listener.dart#_captured_channel_1","seen","lazy_chain.dart#_inner","intersection_selector.dart#_selector1","source_maps.parser#_consumeNewSegment","dart.convert#_urlSafe","input","subscription_stream.dart#_captured_stackTrace_2","isRunning","trace.dart#_captured_level_1","impl.dart#_selector","stream_queue.dart#_pause","dart.core#_isScheme","parsed_path.dart#_captured_this_0","declarer.dart#_captured_this_0","stream_completer.dart#_isSourceStreamSet","stream_channel_controller.dart#_local","length","separator","stack_zone_specification.dart#_captured_T_3","subscription_stream.dart#_captured_handleError_0","dart.async#_setChained","expanded.dart#_yellow","equals_matcher.dart#_captured_location_2","cleanupSlots","equals_matcher.dart#_captured_depth_3","suite.dart#_jsTrace","dart.async#_captured_dispatch_1","dart.collection#_end","equals_matcher.dart#_captured_matcher_0","dart.collection#_modificationCount","dart.convert#_processed","pool.dart#_timer","dart.core#_writeString","metadata.dart#_captured_platform_1","string_scanner.dart#_fail","isClosed","keys","isPaused","test_core#_captured_engine_0","declarer.dart#_setUps","expanded.dart#_sink","dart.collection#_grow","text","live_test_controller.dart#_onErrorController","dart.core#_text","dart._internal#_captured_this_0","handlesError","dart.core#_contents","dart.async#_captured_milliseconds_1","source_map_stack_trace.dart#_captured_sdkLib_3","dart.core#_initializeText","chain.dart#_captured_trace_0","rti#_evalCache","equals_matcher.dart#_limit","dart.async#_toggleEventId","live_suite_controller.dart#_active","stack_zone_specification.dart#_createNode","toSpec","userInfo","excludeTags","dart.core#_separatorIndices","test.dart#_captured_completer_1","rti#_is","dart.async#_isUsed","highlighter.dart#_paddingAfterSidebar","code","sink","metadata.dart#_captured_skipReason_7","stream_sink_completer.dart#_destinationSink","future_group.dart#_values","suite.dart#_metadata","engine.dart#_addLoadSuite","invoker.dart#_captured_message_1","values","_js_helper#_nativeRegExp","validator.dart#_isDefined","stream_queue.dart#_isDone","_js_helper#_isDotAll","offset","putIntoObject","copies","dart.async#_runBinary","_js_helper#_start","stream_queue.dart#_source","year","stream_queue.dart#_requestQueue","$le","$indexSet","$index","$xor","$or","$eq","$mod","$shr","$and","$ge","$gt","$lt","$mul","$not","$shl","$div","$sub","$add","$negate","$tdiv","checkString","instanceTypeName","constructorNameFallback","isJsArray","getIndex","selfFieldName","receiverFieldName","_rtiEval","extractFunctionTypeObjectFrom","rawRtiToJsConstructorName","isGenericFunctionTypeParameter","isDartFunctionType","isDartFutureOrType","isNotIdentical","_instanceFunctionType","instanceOf","invoke","setDispatchProperty","propertyGet","regExpGetGlobalNative","stringReplaceJS","checkNull","regExpGetNative","_getFutureOrArgument","_lookupFutureRti","_setPrecomputed1","_getBindCache","_setBindCache","_getGenericFunctionBase","_getKind","_lookupStarRti","_canonicalRecipeOfStar","_Universe._lookupStarRti","_lookupQuestionRti","_canonicalRecipeOfQuestion","_Universe._lookupQuestionRti","_lookupFutureOrRti","_canonicalRecipeOfFutureOr","_Universe._lookupFutureOrRti","_getInterfaceName","_getBindingBase","_getReturnType","_getGenericFunctionParameterIndex","asString","allocate","_setRequiredPositional","_setOptionalPositional","_setOptionalNamed","_setCachedRuntimeType","_setTypeCheckFunction","_setAsCheckFunction","_getPrimary","_setSpecializedTestResource","_setIsTestFunction","isLegacySubtype","_getSpecializedTestResource","_getStarArgument","_getQuestionArgument","_lookupErasedRti","_getEvalCache","_setEvalCache","_cacheSet","_createTerminalRti","_setKind","_setCanonicalRecipe","_createUnaryRti","_setPrimary","_createGenericFunctionParameterRti","_canonicalRecipeOfInterface","_createInterfaceRti","_setRest","_canonicalRecipeOfBinding","_createBindingRti","_canonicalRecipeOfFunction","_canonicalRecipeOfFunctionParameters","_createFunctionRti","_canonicalRecipeOfGenericFunction","_createGenericFunctionRti","charCodeAt","toGenericFunctionParameter","_lookupDynamicRti","_lookupVoidRti","pushStackFrame","push","setPosition","handleTypeArguments","collectArray","arraySplice","handleFunctionArguments","handleOptionalGroup","handleNamedGroup","collectNamed","isDigit","evalTypeVariable","_lookupNeverRti","_lookupAnyRti","_castToRti","stringLessThan","functionParametersEqual","lookupInterceptorByConstructor","cacheInterceptorOnConstructor","_future","_setValue","_isComplete","_setPendingComplete","_mayComplete","_mayAddListener","_chainSource","_setChained","_hasError","_error","_zone","_removeListeners","_cloneResult","_setErrorObject","_scheduleImmediate","_leave","_createPeriodicTimer","printToConsole","es6","tryParse","objectToHumanReadableString","makeListFixedLength","makeFixedListUnmodifiable","_stringFromJSArray","_stringFromUint8List","_writeOne","_startsWithData","_checkLength","_internal","_stringOrNullLength","_writeString","_isZoneIDChar","_isRegNameChar","_isGeneralDelimiter","_isSchemeCharacter","_isUnreservedChar","fromString","_lineStarts","fromList","_incomingConnections","_outgoingConnections","_names","_requestedResources","_onReleaseCallbacks","_onReleaseCompleters","_closeMemo","_completer","AsyncMemoizer._completer","factor","_group","_values","FutureGroup._completer","_suiteController","_onTestStartedGroup","_state","_subscriptions","StreamGroup._subscriptions","_passedGroup","_skippedGroup","_failedGroup","_active","_addedSuites","_onSuiteAddedController","Engine._onSuiteAddedController","_liveSuites","_onSuiteStartedController","Engine._onSuiteStartedController","_restarted","_activeLoadTests","_sets","_errors","LiveTestController.completer","_onStateChangeController","LiveTestController._onStateChangeController","_onErrorController","LiveTestController._onErrorController","_onMessageController","LiveTestController._onMessageController","serializeSuite","_closable","_out","myInlinedMethod","_lineStart","_columnStart","_maps","_mappings","_setUps","_tearDowns","_setUpAlls","_timeout","_tearDownAlls","_entries","_soloEntries","_isTextAtEndOfContext","_chains","_createKey","_currentSpec","_createNode","withGuarantees","_child","_onCompleteGroup","_onCloseCompleter","LiveSuiteController._onCloseCompleter","_onTestStartedController","LiveSuiteController._onTestStartedController","_passed","_skipped","_failed","_toListGrowable","_mainController","_controllers","_pendingIds","_closedIds","Window.onMessage","jsify","listToString","_handleIEtoString","_codeUnitAt","_currentExpansion","_types","_getBucket","_isMultiLine","_isUnicode","_isTrailSurrogate","_rtiBind","_addListener","_isFiring","_setRemoveAfterFiring","_isEmpty","_isAddingStream","_expectsEvent","_hasOneListener","_errorTest","_onError","thenAwait","_setError","_isPendingComplete","_clearPendingComplete","_whenCompleteAction","_onValue","_isInitialState","_mayAddEvent","_isCanceled","_isPaused","_isClosed","_isInputPaused","_hasPending","_inCallback","_waitsForCancel","_eventScheduled","_isScheduled","_rethrow","_hasTableEntry","identityHashCode","checkValidIndex","_grow","_checkModification","_isUpgraded","parseHexByte","withBufferSize","unary-","_microseconds","_checkType","_getFromObject","_setOnObject","_now","_isWindows","_toFilePath","_initializeText","_writeAuthority","_isPackage","convertDartToNative_SerializedScriptValue","_canceled","fromMillisecondsSinceEpoch","isJavaScriptSimpleObject","_isSourceStreamSet","_setSourceStream","_NextRequest._completer","_pause","_close","_setEmpty","_scanOr","_scanAnd","_scanIdentifier","_setBase","StringDescription.add","addStateInfo","_indent","reset","_onResourceReleased","_consumeValue","glyphs","_paddingAfterSidebar","_normalizeContext","_buffer","Highlighter._","dataFromString","_disabled","_doneCompleter","_GuaranteeSink._doneCompleter","_inAddStream","MessageEvent.data","_prefix","_solo","Declarer._solo","Invoker._onCloseCompleter","_closableKey","_outstandingCallbackZones","_counterKey","_printsOnFailure","_AsyncCounter._completer","_guardIfGuarded","_print","_run","jsonDecode","_except","_only","_MultiChannel.stream","_eventQueue","_requestQueue","ascii","_sink","_listen","_streamCompleter","_stream","_sinkCompleter","markGrowable","_timeString","_local","_channelNames","watch","ExpandedReporter.watch","_stopwatch","ExpandedReporter._stopwatch","_initTicker","Engine.onTestStarted","provokeCallErrorOnNull","provokeCallErrorOnUndefined","provokePropertyErrorOnNull","provokePropertyErrorOnUndefined"],"mappings":"A;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+BAuEUA;mDACKA;QACTA,OAUJA,uHAPAA;MADEA,OANFA,uFAOAA;K;mBCmBEC;MAAaA;;MAKHA;MACZA;QAAgBA,YAIlBA;MAHgBA;MACdA;QAAgCA,kBAElCA;MADEA,SACFA;K;sBCuIEC;MACaA;MACXA;QACaA;QACXA;UACEA,kBAAUA;;MALhBA;IAQAA,C;mCA6GQC;MACOA;QACXA,OAsBJA,sIAnBAA;MADEA,OAGFA,wGAFAA;K;oCAmhBkBC;MAAeA,OCjWjCA,8BDiW6DA;K;kCAE3CC;MAAaA,OCnW/BA,qCDmWkEA;K;iCAEhDC;MAAYA,OCrW9BA,oCDqWgEA;K;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oCEv0BpDC;MACVA,sBAAUA;IACZA,C;kBC+KYC;MA/LZA;MAAAC;MAgMAD,SACFA;K;mBC1GOE;MACcA;MACnBA;QAAyBA,gBAG3BA;MADEA,mBACFA;K;mBA6GKC;MACHA;;uBAEMA;QAAJA;UAAoBA,aAGxBA;;MADEA,OAAcA,oDAChBA;K;OAEOC;MACLA;;QAAqBA,YAgBvBA;MAfEA;QACEA;UAEEA,iBAYNA;aAVSA;QACLA,aASJA;WARSA;QACLA,cAOJA;WANSA;QACLA,aAKJA;MAHYA;MACVA;QAAoBA,sBAAMA;MAC1BA,UACFA;K;+BAmIaC;uBAELA;MAAJA;;;;MAIAA,WACFA;K;yBAEWC;MAAQA;MAs5BnBA;QAAsBA,kBAAMA;MA94BtBA;MAAJA;QAIEA,YA0DJA;MAxDwBA;gCAAKA;MAApBA,2CAAeA;MACtBA;QACEA;UAEEA,2BAoDNA;iBAlDQA;UAEFA,2BAgDNA;QA9CIA,YA8CJA;;MAxCEA;QACEA,sBAAUA;MAEZA;QAEEA,2BAmCJA;MA/BEA;;mCAmBSA;;4BAEwBA,gBAA/BA;UACsBA;YAElBA,YAORA;;MADEA,8BACFA;K;+BAiDcC;MAEHA;MAAPA,SAKJA;K;sCAyEcC;MACRA;MC6FCA,uBD3FoCA;QACvCA,sBCyFMA,6BDvDVA;MA/BoBA,2DAEPA;QAiBgBA,gBEpXDA;QFoXpBA;UAAoCA,mBAY5CA;6BAV6CA;QAAzCA;wCAEMA;UACAA;YACFA,sBAMRA;;;MADEA,OCyDKA,eADGA,6BDvDVA;K;qCAEYC;MACRA;eAA8CA;K;wBAQvCC;MAAaA,iBAAwBA;K;2BAEpCC;MACVA;WAAIA;QAAwBA,MAY9BA;;MATeA;MACbA;QAAgDA,MAQlDA;MANMA;MAAJA;QAAoBA,MAMtBA;2BAJMA;MAAJA;QAAyBA,MAI3BA;;QAH6DA,MAG7DA;;MADeA;IACfA,C;2BAKcC;;QAIVA,yBAIJA;MADEA,WACFA;K;mCAOcC;MAEIA;;MAChBA;QACEA,6CAcJA;MAXEA;QACkBA;QAOZA;;;MAENA,aACFA;K;qCAEcC;MACOA;;MACnBA,4BAAcA,4CAAdA;;;UACiBA,sBAAMA;QACrBA;UACEA;aACKA;UACLA,oCAAqBA;UACrBA;;UAEAA,sBAAMA;;MAGVA,OAAOA,kCACTA;K;oCAEcC;MACZA;MAAcA,oDAAdA;;;UACiBA,sBAAMA;QACrBA;UAAWA,sBAAMA;QACjBA;UAAgBA,OAAOA,4CAG3BA;;MADEA,OAAOA,gCAAmBA,qCAC5BA;K;0CAGcC;MAGZA;wDAAuDA;QACrDA,iDAcJA;MAXEA;QACkBA;QAOZA;;;MAENA,aACFA;K;mCAEcC;MACZA;MAAMA;;MAANA;QACEA;UACEA,oCAYNA;QATIA;UACaA;UAGXA,oCADqBA,0EAM3BA;;;MADEA,sBAAUA;IACZA,C;6BAyFOC;;yCGphB2BA;MHyhBhCA,oBACFA;K;wBAmBOC;MAEwCA;MAD7CA,SAGFA;K;yBAKOC;MAEwCA;MAD7CA,SAGFA;K;uBAKOC;MAEyCA;MAD9CA,SAGFA;K;yBAKOC;MAE0CA;MAD/CA,SAGFA;K;2BAKOC;MAE4CA;MADjDA,SAGFA;K;2BAKOC;MAE4CA;MADjDA,SAGFA;K;gCAKOC;MAGgDA;MAFrDA,SAIFA;K;4BAoBOC;MACwBA;QAC3BA,sBAAMA;MAERA,kBACFA;K;4BAEYC;MACmBA;QAC3BA,sBAAMA;;IAGVA,C;SA0QFC;MACEA,sBAAMA;IACRA,C;WAQAC;MACEA;QAA+BA;MAC/BA,sBAAMA;IACRA,C;wBAKMC;MACJA;;QAAmBA,OHpkCnBA,4CG6kCFA;MARMA,8BAAmBA;MAGvBA;QAAiBA;+BAAMA;QAANA;;QAAjBA;;QACEA,OAAWA,oDAIfA;MADEA,OAAWA,+BACbA;K;wBAKMC;MACJA;MAGAA;QACEA,OHhgCFA,yDG4gCFA;MAVEA;QAIEA;UACEA,OHvgCJA,yDG4gCFA;MADEA,OHlmCAA,2CGmmCFA;K;wBAOcC;MACZA,OH3mCAA,6CG4mCFA;K;cAQIC;MACFA;QAAmBA,sBAAMA;MACzBA,YACFA;K;mBAsBAC;MACEA;;QH1rCAA;MG6rCkCA;;MAElCA;;;;QAqBOC;MAPPD,cACFA;K;qBAGAC;MAGEA,wCACFA;K;qBAMAC;YACwBA;IACxBA,C;sCAmCAC;MACEA,sBAAUA;IACZA,C;qCAqJSC;MAAcA;MAcTA,iCAAqBA;MAO3BA;MAAJA;QAA2BA;MA2BvBA;MAAWA;MAAeA;MAAMA;MAAQA;MAD5CA,OArHFA,+SAsHwDA,uHACxDA;K;yCAMcC;MAmDZA,OAA8BA;;;;;;;mBAChCA;K;6CAkCcC;MASZA,OAA8BA;;;;;;mBAChCA;K;gBAmCAC;;IACgEA,C;0BAahEC;;;;IAGuEA,C;qBAmCzEC;MAIgBA;;MAYdA;QAAgBA,YAkHlBA;MAhHWA;QAAPA,mBAAyBA,eAgH7BA;MA9GEA;QAA6CA,SA8G/CA;MA5GEA;QACEA,OAAOA,2BA2GXA;WA1GSA;QACLA,SAyGJA;kBAhFwCA;;mBATlBA;;QACMA;UAKtBA;;cAEIA,OAAOA,UACCA,uBAAsBA,sDAgFxCA;;;cA7EUA,OAAOA,UACCA,aAAYA,sDA4E9BA;;;MAvEEA;QAI8BA;QACMA;QACFA;QACOA;QACNA;QACOA;QACJA;QACOA;QACNA;QACOA;QAC/BA;QAAbA;UACEA,OAAOA,UAAmBA,uBAAoBA,wCAwDpDA;;UAvDwBA;UAAbA;YAMEA;YAAPA,iBAA0BA,uBAAoBA,wCAiDpDA;;YAhDwBA;YAAbA;cACMA;cADNA;gBAEMA;gBAFNA;kBAGMA;kBAHNA;oBAIMA;oBAJNA;sBAKMA;sBALNA;wBAMMA;wBANNA;0BAOMA;0BAPNA;;;;;;;;;;;;;;;;cAQLA,OAAOA,UAAmBA,aAAUA,wCAwC1CA;;;QAlCIA,OAAOA,UApHTA,oEAsJFA;;MA9BEA;;UAEIA,OH3yCEA,0BGu0CRA;;;;;;;SApBQA;QAGJA,OAAOA,UH7tDTA,wHG8uDFA;;MAbEA;QAIEA;UACEA,OH/zCEA,0BGu0CRA;MADEA,SACFA;K;2BAqBWC;MACTA;;QACEA,gBAAiBA,WAOrBA;MALEA;QAAuBA,OAUvBA,4BALFA;uBAHMA;MAAJA;QAAmBA,YAGrBA;MADEA,gCAMAA,4BALFA;K;oBAmBIC;MACFA;QACEA,OAAcA,uBAIlBA;;QAFIA,OAAkBA,mCAEtBA;K;oBAIAC;MIz3BeC;;MAPFD;;MJq4BXA;QACoCA;QIp5B9BA;2BJs5BGA;QAD6BA;QIr5BhCA;QJs5BJA,sCAAcA;;MAEhBA,aACFA;K;oBAGAE;MIv4BeD;;MAPFC;;MJk5BXA;QIh6BMA;QJi6BJA;;MAEFA,aACFA;K;mBAEAC;MAIaA;MAFHA;;UAEJA,OAAOA,gBAWbA;;UATMA,OAAOA,oBASbA;;UAPMA,OAAOA,0BAObA;;UALMA,OAAOA,gCAKbA;;UAHMA,OAAOA,sCAGbA;;MADEA,sBK18DAC;IL28DFD,C;4BAIAE;MACEA;;QAAqBA,WAkBvBA;yBAhByBA;MAAvBA;QAAkCA,gBAgBpCA;;;;;OAF0CA;;MACxCA,gBACFA;K;yBA+CSC;;6BAwB2CA;4BAiFlBA;8CAoY5BA,6DA0BJA;;;;;;;cAzbcA;QACAA;0BAAeA;;;;;;;MAU3BA;QACeA;;;;QAcOA;;MANhBA;;;MAQNA,uDAA8BA,SAA9BA;wBACaA;2BAGPA;QAAJA;UAC2BA;;;QAG3BA;;;;;;;;MAaFA,mBACFA;K;6CAyCOC;MAELA;;QAOEA;;;;gDA4BJA;MApBEA;QAEEA;UAEEA;;QAKFA;;;;uCAWJA;;MADEA;IACFA,C;4BAEOC;qBAeGA;MAVRA;;UAEIA;;;;8BAsENA;;UA5DMA;;;;8BA4DNA;;UAlDMA;;;;8BAkDNA;;UAxCMA;;;;8BAwCNA;;UA9BMA;;;;8BA8BNA;;UApBMA;;;;8BAoBNA;;UAVMA;;;;+BAUNA;;K;2BAIOC;MACLA;;QAAmBA,OAAOA,uDAmC5BA;0BAhCkDA;uBAOpBA;iCAFYA;MAApBA;MAEPA;MAAbA;QACEA,OAAOA,yDAwBXA;MArBEA;cAE2BA;;0BAAeA;;QAK1BA;;cAyQZA;QA7QFA,8EA8QqBA,+EAzQMA,yBAa/BA;;MApCoBA;;YA6BFA;;wBAAeA;;MAA/BA;;YAkQIA;MAjQJA,8EAkQuBA,uDA9P+BA,2CAExDA;K;uCAEOC;qBAoBGA;uBACAA;MAfRA;;UAIIA,sBAAUA;;UAEVA;;;;wCA+ENA;;UApEMA;;;;wCAoENA;;UAzDMA;;;;wCAyDNA;;UA9CMA;;;;wCA8CNA;;UAnCMA;;;;wCAmCNA;;UAxBMA;;;;wCAwBNA;;UAbMA;;;;;;4CAaNA;;K;sCAEOC;;cA4JDA;MAAJA;QACuBA;YAQnBA;MAAJA;QAC2BA;0BAhKqBA;uBAOpBA;iCAFYA;MAApBA;MAEPA;MAAbA;QACEA,OAAOA,oEAuBXA;MArBEA;QAKwBA,wDAAWA,2BAAeA;cACrCA;;0BAAeA;;QAL1BA,oCAoBJA;;MArC+BA;+EAtG7BF,AAuIsBE;MACAA,0EAAWA,2BAAeA;YACrCA;;wBAAeA;;MAL1BA,oCAOFA;K;wBAqBFC;MAEEA,OAAeA,oHAQjBA;K;6BAmESC;MACLA,OCttEeC,iDAmBDD,sBDmsEuBA,gBACvCA;K;wCAEOE;MACLA,OC1tEeD,iDAmBDC,sBDusEuBA,oBACvCA;K;yBAGOC;MAAgCA,cAAQA,MAAKA;K;6BAK7CC;MAAoCA,cAAQA,UAASA;K;oCAwB9CC;MA1EdA;;gBA4EsBA;qBAEMA,gBAA1BA;qBACaA;;UAETA,YAGNA;;K;yBAgGFC;MAEEA;QAAmBA;MACnBA,YACFA;K;2CAqNAC;MACMA;MACJA;qBAEyCA;QAAvCA;UACEA,kBAAeA,+BAMrBA;;UAJMA,qBAINA;;MADEA,WACFA;K;8BA2EEC;2DACoCA,2CACtBA,6EAFdA;IAEyEA,C;sBAoBpEC;MACLA;;QAhHOA,6DADWA;QAmHhBA;UACEA,OAAOA,yCAKbA;QAHIA,gBAGJA;;MADEA,OAAkBA,kCACpBA;K;gBAYKC;MAEHA;QAAuBA,YAGzBA;MAFgBA;QAASA,iBAEzBA;MADEA,sBAAUA;IACZA,C;iBAIKC;MACHA,sBAqXAA;IApXFA,C;kBAKKC;MACCA;QAAuBA,sBAAUA;IACvCA,C;qBAYKC;MACHA,sBHnhFAA;IGohFFA,C;mBAKEC;;IAA0BA,C;2BA4CrBC;MAELA,gCACFA;K;wBIvjGOC;MACLA;;MAOAA,aACFA;K;wBAMAC;MACEA;QAAoBA,WAGtBA;MADEA,iBACFA;K;6BAGAC;MAGEA,OAAOA,wBAAWA,SADgCA,wBAClBA,6BAClCA;K;yBAuDOC;MACLA,OAAOA,iCACTA;K;0BAEOC;MACLA;;QACEA,gBAiCJA;MA/BEA;QACEA,aA8BJA;MAg3BeA;QA14BXA,OArBiBA,mBJ5C8CC,GAAjEA,SI6CoBD,wCA8CtBA;MAxBEA;QAEEA,OAAOA,mBJrETA,MI2FFA;MApBEA;QACEA,gBAmBJA;MAsrBmCA;MAvsBjCA;QACMA;sEAC+DA;UACjEA,wCAcNA;2BAZ4CA;QAAOA;QAArCA;4CAAcA;QAAxBA,OAAUA,uBAYdA;;MJzHuBA;;QAGwBA;;QIs+BhCC;MJz+BQD;MI+GrBA;QAEEA,OAAOA,4CAQXA;MJ9GuBA;QAGwBA;;QI29BhCC;MJ99BQD;MIwGrBA;QJxGqBE;UAGwBA;;UI29BhCD;QJ99BQC;QIgfGF;QAtYtBA,qBAAmBA,6EAIvBA;;MADEA,6BACFA;K;2BAaOG;MACEA;MAIPA;uBAQeA;QANbA;UAC2BA;;;6CAEWA;+BAEVA;0BACLA,iBAAvBA;UACEA;QAKFA;UACEA;6BACgDA;UAAOA;UAArCA;8CAAcA;UAAhCA,uEAAkBA;8BAEHA;gDAi2ByCC;YA91BpCD;;QAGtBA;;QAoEQA;;;MA1DSA,8DAAqBA;MAQxCA;wBAEuBA;4BAArBA;;UAEmBA;;;QAUnBA;QAAmBA;;MAFrBA;+BAIuBA;QAFrBA;mCAEAA;;UAEmBA;;QAGnBA;;MAMFA;4BAIkCA;QAFhCA;QAEoBA,gDAApBA;;UAEmBA,6GAEGA;;QAGtBA;;MAGFA;;MASAA,sEACFA;K;oBASOE;MACLA;;QAAmBA,SAerBA;MAwuBeA;MAxvBMA;MDoOnBA;MC9NAA,4DA2uBWA,0CA3uBXA;;QA6tBMA;wBAztBAA;QAAJA;;QAGaA;;MAEfA,aAAUA,0BACZA;K;oBA6CKC;MHmVoBA,6BAsiEaC;MGx3EJD,OHoWzBA,kCADoCA,6BGjW7CA;K;gBAMAE;MACEA;QAA0BA,iBAiB5BA;MAlBUA;2CAkrBKA;MAtCwBA;MAvoBrCA;QAA0BA,WAa5BA;MAgqBeA;QAvqBXA,mBAOJA;MALEA;QAEEA,OA6nBmCA,0CA1nBvCA;MADEA,iBACFA;K;sBAoHAC;MAIEA,OAAOA,+BAvaAA,aAoaWA,0BApaAA,SADgCA,mBAClBA,+BAwalCA;K;cAqgBOC;MAAQA;2CAkCAA;MA/BbA,4CACFA;K;oBFn+BKC;;IAQLA,C;+BAoEAC;MAAyBA;;MAEhBA,6BAAMA;gBAIYA,+BACrBA;MAAJA;;QAAoBA,eAkEtBA;;qBAjEgCA,+BAC1BA;MAAJA;QAAyBA,kBAgE3BA;+CA3DMA;MAAJA;QACEA,6BAAMA;QACNA;oBAEuBA,+BACjBA;UAAJA;;YAAoBA,eAsD1BA;;yBArDgCA,+BACtBA;UAAJA;YAAyBA,kBAoD/BA;;;;MA9CEA;QAQEA,WAsCJA;oCA9BoCA;gBAD9BA;MAAJA;QACWA;SACGA;;QACZA,eA4BJA;;MAzBEA;SACcA;QACZA,kBAuBJA;;MApBEA;QACyBA;8BIvHrBC;QJuHFD,WAmBJA;;MAhBEA;QACEA,OAAOA,sCAeXA;MAZEA;QAEEA,sBAAUA;;QAMaA;8BItIrBC;QJsIFD,WAIJA;;QAFIA,OAAOA,sCAEXA;K;wBAYAE;MAE+CA;sEAAhCA;MAEbA,kBACFA;K;4BAEAC;MAGEA,OAAOA,2FACTA;K;+BAEAC;wCAIkCA;MAAvBA;QAAPA,4CAIJA;;QAFIA,OAAOA,oDAEXA;K;wBAeKC;oBACSA;QAAwBA,MAGtCA;;MADEA;IACFA,C;gCAGKC;MAA0BA;;;MAI7BA;gBA7PyBC,AAmQwCD;;MAEjEA;;;;QAGEA,oBAAyBA,SAAzBA;oBACYA;UACEA;UACZA;YAEeA,6CAA+BA;YAC5CA;;;;;;;MAYNA,oBAAyBA,SAAzBA;kBAEyCA;;gCAEQA;;;;;;;;IAOnDA,C;eAmCKE;MAOiEA;iBAL1CA;MAiBlBA,iCACJA,cALIA,yBAAsBA,cAFtBA,yBADsBA,cAAtBA,yBAAsBA,cADtBA,yBAAsBA,cADtBA,yBAAsBA,cAHtBA,wBAAsBA,CAD1BA,cAA+CA;MAqBnDA;QAE2CA;QAAzCA;UAGyCA;;UACvCA;sCAE2CA;YAAzCA;cAoBkBA;;;;;;MATPA;MAEbA;MAEAA;IACNA,C;2BAEAC;MAEEA,OAAwBA,2BAC1BA;K;+BK3USC;;;;;;;;;;;;SA0BiCA;MAAtCA;QAA+CA,aAKjDA;MADEA,sBAAUA,gDAA0CA;IACtDA,C;2BAuEAC;MAAoBA;qBAEkBA;MAFtCA;IAGAA,C;6BC3EFC;MACEA;;QACEA,+CAOJA;;QAL0BA;kBDOUA;QCPhCA,kBAKJA;;QAFWA,6BADMA;QACbA,QCqUsBA,kBDnU1BA;;K;uBAOOC;;QASHA,yCAGJA;MADEA,kBACFA;K;0BAEAC;MACcA;MACZA;QAAmBA,eAIrBA;MADEA,OAAOA,6CD6C6DA,OC7CvBA,QAD7BA,6BAElBA;K;0BAIAC;;QAIIA,oDAGJA;MADEA,aACFA;K;+BAEAC;MAAyBA;MAEvBA;QACEA,OAAOA,iEAWXA;;QD1J4BA;;QCoJxBA,sCA5CEA,iCAkDNA;;MR8mCEA;QAAoBA,kBAAMA;MQ/mC1BA;IACFA,C;qCAMOC;MAELA;;QACEA;UACEA,kBA+BNA;0BA5B0BA;QAEtBA;4BACeA;QAGfA,sCAsBJA;;MAhBQA;MACJA;QAAeA,eAenBA;kBARiBA;QAEXA,gDAMNA;MADEA,wBAAiCA,WAFpBA,uCAxFTA,iCA2FNA;K;qBAOOC;MAAkCA,aAAMA;K;mCAE/CC;MACEA;MASYA;QACVA,sBAAUA;MAIQA,kDDcpBC,4BAT4BD,QAAKA,wBAASA,mCCL1CA;eDgB2BA;eCfoCA;;QL6U9CA,cK7UFA,kBAAWA,6DL6UTA,IK5UFA;;;ML4UEA,cKzUJA,kBAAWA;MACxBA,sCACFA;K;iCAmDAE;MACEA;;QACMA;QACJA;UAAeA,eAcnBA;QAZIA,OAAOA,8DADmBA,qBAa9BA;;;QATIA,kDDvS6CA,gBCkH3CA,oCAuLIA,kEAOVA;MRk+BEA;QAAoBA,kBAAMA;MQt+BAA;MAAVA,sCAAmDA;MAC9DA;QAAoBA,eAG3BA;MAFwBA;MACtBA,OAAOA,4CAA4BA,mBAAaA,6BAClDA;K;iCAcOC;MAIIA;iBAAmBA;MAA5BA,oCACFA;K;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uBE8CKC;MACsBA,WAM3BA;K;6BAkhBwBC;MAClBA,yBAA6CA;K;sBAo3B9CC;MACHA;QACEA,sBAAMA;IAEVA,C;sBASIC;MACFA;;;UAEUA;;UAFVA;;;;QAIEA,sBAAMA;MAERA;QAAiBA,cAEnBA;MADEA,UACFA;K;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCT7pDaC;MAAsBA;;kBAG3BA;MAAJA;QAoI6BA;QA46C2BA,YAhkDpDA,yEAmkDYA;;MA/iDhBA,aACFA;K;gCAyDYC;oBAENA;MAAJA;QACEA,OAAOA,0CAGXA;MADEA,iCACFA;K;oCAqCeC;MAA0BA;MAIvCA,gBACFA;K;8BAOeC;MAAoBA;MAEjCA,gBACFA;K;gCAsB2BC;MAAsBA;MAE/CA,gBACFA;K;mCAOeC;MAAyBA;MAEtCA,gBACFA;K;6BAiDcC;iBACGA;MADgBA;;MAG/BA,QACFA;K;cA8DEC;MACFA,OAAiBA,2CACnBA;K;qCAeIC;MAMFA;;QAAgCA,WAiBlCA;MAhBmBA;MACOA;MARSA;gCA9FWA;MA0G5CA;QAEwCA,0BA1GlCA;0BAUWA;MADgBA;;MAoGAA;MAC7BA;MAAJA;QAAmBA,YAKrBA;MAnKoCA;MA+JxBA;;MAGVA,UACFA;K;kBAUIC;;;gBAzR+CA;;MA2RjDA;;;;;;UAMIA,UAsENA;;wBAlEiCA;UAAvBA;UACJA;YAAwDA,UAiE9DA;kCAjMmBC;UADgBA;;UAkI7BD,OAonCiDA,uEApjCvDA;;wBA5DiCA;UAAvBA;UACJA;YAAwDA,UA2D9DA;kCAjMmBE;UADgBA;;UAwI7BF,OAinCqDA,uEAvjC3DA;;wBAtDiCA;UAAvBA;UACJA;YAAwDA,UAqD9DA;kCAjMmBG;UADgBA;;UA8I7BH,OAinCqDA,uEA7jC3DA;;UAlD0CA;UACQA;UAE5CA;YAEEA,UA6CRA;UA1SiCA;UA8P3BA,OAAiBA,2FA4CvBA;;UA9R4BA;oBAsPwBA;UAAvBA;UACAA;UAEnBA;UACJA;YAC0DA,UAmChEA;UAlCMA,OAAiBA,gFAkCvBA;;UArQ2BA;0BAwOMA;UAAvBA;UACyCA;UAEzCA;UAEJA;YAE6DA,UAsBnEA;UArBMA,OAAiBA,gGAqBvBA;;UAlB0BA;;UAGhBA;UA5O0BA;oBA8OgBA;UAAvBA;UACvBA;YACgDA,UAWtDA;UAVMA,OAAiBA,qFAUvBA;;UAjP8CA;qBA2OpCA;UAAJA;YAAmBA,WAMzBA;UALMA,mCAKNA;;UAHMA,sBAAMA;;IAGZA,C;uBAEOI;;;;MAKLA;sBAE+CA;QAAvBA;QACtBA;;;;MAKFA,kCACFA;K;uBAEOC;;4BAIEA;MAJeA;;MAMtBA;uBAutEsDA;wBAptEPA;QAAvBA;QACtBA;;;;;MAMFA,oCACFA;K;oCAGoBC;MAKdA;+CAA4BA;;+CAIAA;yCAA5BA;0CAI4BA;oCAA5BA;MACJA;QAGEA,yBAQJA;MAtPMC;YAUSD;YAQAA;YAiBAA;MAkNbA,aACFA;K;yBAWIE;6BAGEA;MAAJA;QACEA;UACEA,OAAOA,kCAKbA;QAHIA,2BAGJA;;MADEA,WACFA;K;4BAOIC;MACFA;MAAQA;+BAypE4BlD;UAppEtBkD;UACVA;YAAiBA,UAIvBA;;MADEA,OAAOA,sBACTA;K;kBAKIC;MASFA;6BAioEoCC;QA7kEGD,YAAhCA;QAnDLA,iEASJA;;;QALIA,OAAOA,4BAKXA;MADEA,OAAOA,+BADWA,0BAEpBA;K;wBAIIE;sBAqBEA;;MAAJA;QAAiBA,iBAUnBA;;QALIA,iBAKJA;MADEA,UACFA;K;mBAKIC;MAEqCA,gBAAhCA;MAAPA,iEACFA;K;kCAOIC;iCAE0BA;4BACxBA;MAAJA;QAAmBA,YAErBA;MADEA,OAAOA,0DACTA;K;sCAGIC;MAckBA,gDA+iEgBxD;;;MA5iEpCwD,UACFA;K;2BASIC;;;oBAGwBA;oBACNA;MAApBA;QAtViBA;;QAyVfA,UAGJA;;MADEA,WACFA;K;uBAQKC;oBAECA;MAAJA;QAAkBA,WAOpBA;MADEA,UA/lBMA,sBA4mBNA,gBAZFA;K;iBAGKC;MACHA,OAAOA,oBAjXUA,4CAkXnBA;K;+BAiCKC;;oBA3nB8CA;;MAkjFpBA;;eAtpFvBA,iBAJAA,QA+uB6BA;aAE5BA;mBAneUA;QADgBA;;QAueUA;QAAzCA;;aAEOA;;aAEAA;;aAEAA;;aAEAA;;;UAvlBsBA;sBAnBWC;UA8mBlBD;mBA3qBlBA;;;;;aA7EAA;MAwwBNA,0BACFA;K;kCAGKE;MAGCA;MAGJA,OA08COA,gCA58CSA,qEAGlBA;K;wBAGKC;oBA7sBUA;MA0tBKA,uBAy5DkBX;QA55DlCW,oBAKJA;MADEA,uCACFA;K;mCAGAC;MACEA;;QAAoBA,aAUtBA;MANmBA;;QAAkBA,aAMrCA;MADEA,sBAAiBA,yBADNA,yBAFKA,2CAEsBA;IAExCA,C;qCAGAC;MACEA;;QAAoBA,aAUtBA;MANmBA;;QAAkBA,aAMrCA;MADEA,sBAAiBA,yBADNA,yBAFKA,2CAEsBA;IAExCA,C;oBAGAC;MAEEA;MAs5COA;QAt5C2CA,WAKpDA;MADEA,sBAAiBA,iDAHsBA,uFACOA,mFACaA;IAE7DA,C;oBAOgBC;MACqBA;gCAEFA,mCADjBA;MAEdA,wCACcA,8DACoBA,iCACpCA;K;4BAOAC;;IAAqEA,C;mCAE7DC;MACNA,OAHFA,iCAGuCA,qCACvCA;K;4BAIAC;;IAAqEA,C;mCAE7DC;MACNA,OAHFA,iCAGuCA,qCACvCA;K;YAaGC;MACHA,WACFA;K;YAIQC;MACNA,aACFA;K;aAIKC;MACHA,0CACFA;K;qBAIWC;MACTA;QAAqBA,aAGvBA;MAFEA;QAAoBA,aAEtBA;MADEA,sBAAiBA;IACnBA,C;wBAIWC;MACTA;QAAqBA,aAGvBA;MAFEA;QAAoBA,aAEtBA;MADEA,sBAAiBA;IACnBA,C;uBAIaC;MACXA;QAAoBA,aAGtBA;MAFEA;QAAoBA,aAEtBA;MADEA,sBAAiBA;IACnBA,C;0BAIaC;MACXA;QAAoBA,aAGtBA;MAFEA;QAAoBA,aAEtBA;MADEA,sBAAiBA;IACnBA,C;YAIKC;MACHA,iEAEFA;K;oBAIUC;;QACYA,aAGtBA;MAFEA;QAAoBA,aAEtBA;MADEA,sBAAiBA;IACnBA,C;uBAIUC;;QACYA,aAGtBA;MAFEA;QAAoBA,aAEtBA;MADEA,sBAAiBA;IACnBA,C;YAIKC;MACHA,gCACFA;K;oBAIUC;MACRA;QAAoBA,aAGtBA;MAFEA;QAAoBA,aAEtBA;MADEA,sBAAiBA;IACnBA,C;uBAIUC;MACRA;QAAoBA,aAGtBA;MAFEA;QAAoBA,aAEtBA;MADEA,sBAAiBA;IACnBA,C;eAIKC;MACHA,gCACFA;K;uBAIaC;MACXA;QAAuBA,aAGzBA;MAFEA;QAAoBA,aAEtBA;MADEA,sBAAiBA;IACnBA,C;0BAIaC;MACXA;QAAuBA,aAGzBA;MAFEA;QAAoBA,aAEtBA;MADEA,sBAAiBA;IACnBA,C;uBAEOC;MACEA;MACPA;QACOA,kCACDA;MAGNA,QACFA;K;0BAEO5F;MAEEA;MAGPA;6BAQeA;QANbA;UAC2BA;;;6CAEWA;+BAEVA;QAC5BA;UACEA;QAKFA;UACEA;6BACoDA;UAAOA;UAArCA;8CAAcA;UAApCA,+EAAsBA;2BAGDA;UAooDIA;YAloDPA,2DAAEA;;QAItBA;;QA8DQA;;;MAh5BeA;uBAxDiB6F;MA+4BL7F;qCAGaA;;qCAGAA;mDAG3CA;gCADsCA;yCACLA;MA5CfA;MA8CDA;MAIxBA;QACmBA,8CACbA;MAKNA;QACEA;QAEAA;UACmBA,8CACbA;QAINA;;MAGFA;QACEA;QAEAA;UACmBA,8CACbA;QAMNA;;MAGFA;QASqDA;MAArDA,+EACFA;K;kBAEO8F;;gBAr/B4CA;;MAw/BjDA;QAA4BA,eAgD9BA;MA/CEA;QAA6BA,gBA+C/BA;MA9CEA;QAA0BA,aA8C5BA;MA7CEA;QAA2BA,cA6C7BA;MA5CEA;QAAyBA,YA4C3BA;MA1CEA;QA36B2BA;QA66BzBA,OAAUA,sBAAaA,iCAwC3BA;;MArCEA;QA36B+BA;QA66B7BA,OAAUA,sBAAaA,iCAmC3BA;;MAhCEA;QA36B+BA;QA66B7BA,qBAAmBA,sBAAaA,iCA8BpCA;;MA3BEA;QAh9B+BA;QAk9BtBA,4BAAeA;QACFA;QAIpBA,iBAHcA,+BACEA,8DAsBpBA;;MAjBEA;QACEA,OAAOA,iDAgBXA;MAbEA;QA/6BkCA;QAk7BhCA,OAAOA,0BAAqBA,2BADRA,qCAWxBA;;MANEA;QA56B4CA;gBA5EFC;2BA0/BHD;QAAOA;QAArCA;4CAAcA;QAArBA,qBAAOA,IAIXA;;MADEA,UACFA;K;oBAEOE;MACcA;MACnBA;QAAuBA,gBAEzBA;MADEA,mBACFA;K;wBAqKgBC;;aAEZA;;MAGAA,WACFA;K;8BAEWC;;2BAEiBA;wBACtBA;MAAJA;QACEA,OAAOA,+BAcXA;WAbSA;;QAuJAA;;QAnJLA;;QAGgBA;QAYTC;QAVPD,iBAIJA;;QAFIA,YAEJA;K;wBAKYC;MACRA,gDAA+CA;K;8BAEvCC;MACRA,OAAOA,yCAA0CA;K;oBAS1CC;MAGLA;wBADkBA;;MACtBA;QAAmBA,YAIrBA;MAHYA;;MAEVA,UACFA;K;iCAEWC;;2BAloCiCA;MAqoC1CA;QAEiCA,mBAroC7BA;MAwoCAA;MAAJA;QAAmBA,YAIrBA;MAHYA;;MAEVA,UACFA;K;oBAEWC;;2BA5nCiCA;MA8nC1CA;QAEiCA,mBA9nC7BA;sBAUWA;MADgBA;;MAwnCFA;MACzBA;MAAJA;QAAmBA,YAUrBA;MAHYA,uFAJaA;;MAMvBA,UACFA;K;4BA2BWC;MAESA,0BADMA;MAEPA,UAEnBA;K;0BAEWC;iBApqCMA;MADgBA;;cA2pCTA;SAx6ClBA,QA87CyBA;SA17CzBA,WA27C2BA;SAv7C3BA,QAw7CwBA;MAC5BA,UACFA;K;kCAmDWC;MAGLA;wBADkBA;MACtBA;QAAmBA,YAErBA;MA5gDIC;SA4HEC;MAs5CGF,GA5uCHE;MAquCJF,4CACFA;K;+BAwBWG;MAILA;wBADkBA;MACtBA;QAAmBA,YAErBA;MA1iDIF;SA4HEG;SA0CAA;MA44CGD,GA5wCHC;MAmwCJD,4CACFA;K;kDAWWE;MAILA;;wBADkBA;MACtBA;QAAmBA,YAErBA;MA3jDIJ;SA4HEK;SA0CAA;MA65CGD,GA7xCHC;MAoxCJD,4CACFA;K;oCAWcE;;;MAGZA;uBAE6CA,GA3yC9BA;QADgBA;;QA6yC7BA;;MAGFA,QACFA;K;yCAEcC;;4BAGLA;MAH8BA;MAIrCA;uBA0nCoDA;uBAvnCPA,OAzzC9BA;QADgBA;;QA2zC7BA;;MAGFA,QACFA;K;mCAcWC;MAEFA;MAdgCA;MAKrCA;oBADEA;QAEEA;MAWFA,gBADkBA;MACtBA;QAAmBA,YAErBA;MAnnDIR;SA4HES;SA0CAA;SAcAA;oBAw8CAD;WAzjDAC,2BAAgBA;MA6jDbD,GA11CHC;MA40CJD,4CACFA;K;iCAgCWE;;iBAzhDsCA;MA4hD/CA;QAn9CwBA;;QAs9CKA;;QAEmBA;QAATA;;iBA33CxBC;MADgBA;;MA82C3BD;MAiBAA,gBADkBA;MACtBA;QAAmBA,YAErBA;MAhqDIV;SA4HEY;SA0CAA;SAcAA;MAq/CGF,GAn4CHE;MAy3CJF,4CACFA;K;kCAoDWG;;sBAp7CMC;MADgBA;;qCAu5CmBA;mDAYnCA;qCATmCA;mDAG3CA;gCADsCA;yCACLA;MAVQA;MAa5CA;MAEJA;QAIMA;MAINA;QAIMA;MA/BAD;MA2CFA,gBADkBA;MACtBA;QAAmBA,YAErBA;MA3tDIb;SA4HEe;SA0CAA;SAcAA;MAgjDGF,GA97CHE;MAo7CJF,4CACFA;K;yCAmBWG;;4BA98CMC;MADgBA;;MA48C7BD;MAQEA,gBADkBA;MACtBA;QAAmBA,YAErBA;MArvDIhB;SA4HEkB;SA0CAA;SAcAA;MA0kDGF,GAx9CHE;MA88CJF,4CACFA;K;oBAuIcG;MACZA,4DAYFA;K;mBAmBWC;;;uBAP4DA;;sBAWnDA,gBAAlBA;QAEqBA;QAAnBA;UACMA;aACCA;UACDA;aACCA;UACDA;;UAEJA;UACAA;;cAEIA;;cAGAA;;yBAIIA;cACJA;;yBAmSJA;cAFgBA;cADeA;yBAElBA;cA7RTA;;yBAxcDA,qCACHA;cA2cIA;;yBAvcDA,qCACHA;cA0cIA;;yBAtcDA,qCAAmBA;cA0clBA;;+BA9CmBC;8BALDA;cAuDlBD;;+BAmHoCA;cAgFWE,2BA0mBjBC;cA1mBtCD;cA1P0BC;cAwKNH;cAApBA;2BAEwBA;;gBAEXA;;;+BAIOA;oBACdA;;+BAGsBA;oBACtBA;;;cA1HEA;;cAGAA;cACAA;;wBAOQA;cAAGA;oBAxrDJjF;cADgBA;;yBAsvCoBiF;cAoc3CA;;wBAOQA;cAAGA;oBAhsDJhF;cADgBA;;yBAyvCwBgF;cAyc/CA;;wBAOQA;cAAGA;oBAxsDJ/E;cADgBA;;yBA+vCwB+E;cA2c/CA;;+BAlFmBC;8BALDA;cA2FlBD;;+BAqG4CA;cAxyDlD3E;;;cA4yDe2E;cAAjBA;gBAEEA;;;oBAGIA;;;oBAIAA;;;oBAIAA;;;;cAyCiDI,2BA0mBjBD;cA1mBtCC;cA1P0BD;wBA9lDfC;wBAQAA;wBAiBAA;yBAiyDWJ,yCADLA;cA5HTA;;+BA1FmBC;8BALDA;cAmGlBD;;cAuJ+CK,2BA0mBjBF;cA1mBtCE;cA1P0BF;;;cAuGlBH;;+BAlGmBC;8BALDA;cA2GlBD;;cAsJoDM,2BAmmBtBC;cAnmBtCD;cAjQ0BC;;;cA+GlBP;;;;;;MAQ6CA;MAArDA,OAAOA,0CACTA;K;yBAOWQ;MACLA;;sBACcA,SAAlBA;QAEsBA;QAApBA;UAAyBA;QACXA;;;MAGhBA,QACFA;K;8BAEWC;MAELA;;sBACcA,SAAlBA;QAEMA;QAAJA;UACEA;YAAeA;;;UAEVA;YUhhEsBA;;YVghEtBA;;YAGLA;;;MAQ6CA;MAJjDA;mBA5nBiCA;4BANTA;wBA/yCuBC;QAgzC/CD;UAvuCwBC;;;QAZKA;QAwvCLD,2CAAmBA;QAPlBA;qBAUrBA;QAAJA;UACEA,+CAA4BA;mBAEbA;;;MA8nBjBA,QACFA;K;sCAyEYE;MAEDA;MAATA;mBAhoBOA,qCACHA;QAioBFA,MAOJA;;MALEA;mBA/nBOA,qCAAmBA;QAioBxBA,MAGJA;;MADEA,sBAAMA,qDAA+CA;IACvDA,C;oBAkBWC;MACTA;QAEEA,OAAiBA,6DAOrBA;WALSA;QACLA,OAAeA,kDAInBA;;QAFIA,WAEJA;K;qBAEYC;;;MAEVA;QAEaA,wDAA8BA;IAG7CA,C;0BAEYC;;uBAEHA;MAFeA;MAGtBA;QAEaA,wDAA8BA;IAG7CA,C;yBAEWC;;wBA/jEsCA;;MAikE/CA;QACEA;UAz/DsBA;UAy/DNA,2BAsBpBA;;QArB4BA;2BAEXA;QAAbA;UACEA,+BAkBNA;QAfIA;QAhgEsBA;iCAkgEFA;;aAEpBA;QAAgBA,kBAWpBA;MATEA;QACEA,sBAAMA;MAEgBA;gCAEXA;QACXA,+BAGJA;MADEA,sBAAMA,mDAAsCA;IAC9CA,C;gBAkEGC;MAEHA;;QAA8BA,WAyJhCA;MAtJMA;QAAwBA,WAsJ9BA;eAnJMA;MAAJA;QAA0BA,WAmJ5BA;MAhJMA;QAAwBA,YAgJ9BA;;QA5IuBA,WA4IvBA;MAtI0BA;MACxBA;QA1jE4CA;QA6jEtCA,+BAAqBA,EADmBA;UACcA,WAkI9DA;;YArzEmDA;MA4rE3CA;MAqBNA;QA9nE2BA;QA+nEzBA,OAAOA,uDAmGXA;;MA9FEA;QApoE2BA;QAqoElBA,MA9qEiCC;QA8qExCD,sDA6FJA;;MAjFEA;QAvoE+BA;QAwoExBA;UAEHA,YA8ENA;QA5EIA,OAAOA,uBAAyBA,+DA4EpCA;;MAvEEA;QAtpE+BA;QAypEzBA;QAFJA,SAsEJA;;MA1DEA;QA9pE+BA;QA+pEzBA;UAEFA,WAuDNA;QArDIA,OAAOA,gCACCA,sDAoDZA;;MAhDEA;QA7qE+BA;QAgrEzBA;QAFJA,SA+CJA;;MAtCEA;QAAsBA,YAsCxBA;MAnCEA;;QAEEA,WAiCJA;MA3BEA;;UAC2BA,WA0B7BA;QAzBIA;UAAsCA,YAyB1CA;QAvBsBA;QACAA;QACbA;UAAwCA,YAqBjDA;;;QAzsEoCA;cAlEQE;QAkERF;QAyrEhCA,OAAOA,gEAgBXA;;MAbEA;;UAC2BA,WAY7BA;QAXIA;UAA+BA,YAWnCA;QAVIA,OAAOA,sDAUXA;;MANEA;QACEA;UAAgCA,YAKpCA;QAJIA,OAAOA,uDAIXA;;MADEA,YACFA;K;wBAGKG;MAAkBA;;;MAttEIA;YAxDiBrD;MAwDjBqD;MA4tEpBA,uCAAwCA;QAC3CA,YAuEJA;MApEwCA;MACAA;uCAMaA;uCACAA;qDAC/CA;qDAA4BA;MAAhCA;QAA2DA,YA2D7DA;MAzDMA;uCAM+CA;uCACAA;qDACnBA;qDACAA;MADhCA;QAC2DA,YAgD7DA;MA9CEA;gCAsPkCA;QAnP3BA,+CAAqBA;UACxBA,YA0CNA;;MAtCEA;gCA8OkCA;QA1O3BA,+CAAqBA;UACxBA,YAiCNA;;MA7BEA;gCAqOkCA;QAjO3BA,+CAAqBA;UACxBA,YAwBNA;;kCAlBgDA;kCACAA;;;MAE9CA;8BAkR4BA;;UA9QxBA;YAA+BA,YAWrCA;gCARmCA;UAD7BA;iBAHFA;QAKAA;UAAyCA,YAO7CA;2BAwMoCA;QA5M3BA,0CAAqBA;UAAqCA,YAInEA;;MADEA,WACFA;K;yBAEKC;MACIA;MA50EwBA;eAg1E3BA;MAh1E2BA;eAg1ElBA;MAAbA;QACkBA;QACAA;uBAETA;QAJTA;QAcEA;oBAmLgCA;;UArJvBA;YACHA,YAiCVA;;QA7BIA,WA6BJA;;MAhB0BA;MACxBA;QAAkBA,YAepBA;0BAbMA;MAAJA;QAA2BA,YAa7BA;6BAVSA;MADSA;MAxEMA;MA0EtBA;QAIOA,4BAFwBA,wDAA+BA,iBAEdA;UAC5CA,YAINA;MADEA,WACFA;K;eAOKC;MACHA;;QAA8BA,WAyChCA;MAvCMA;QAAwBA,OAAOA,oBAuCrCA;YA5gFmDA;MAy+E7CA;qBAASA;QAAOA,YAmCtBA;MAjCEA;;;;UAIIA,OAAOA,yCA6BbA;;UA/8EiCA;gBAnBWnF;UAmBXmF;UAu7EpBA;YADmDA,YAyBhEA;UAxBMA,oBAAsBA,qCACdA,0CAuBdA;;UAn8E4BA;gBA/BgBC;UA+BhBD;UA+6EtBA,OAAOA,qCAEHA,aAAeA,+BAA6BA,oCAkBtDA;;UA16E2BA;gBAxDiBvD;UAwDjBuD;UA45EdA;YAEyBA;YACpBA;YA0CdA,2EAEAA,sEAEAA;;YAjDEA;mBAcNA;;UAh6EoCA;gBAlEQH;UAkERG;UAw5E9BA,OAAOA,qCAEHA,aAAeA,oCACPA,yCAKlBA;;UAFMA,YAENA;;K;gBAEKE;;wBAGCA;4BAAWA;QAASA,YAM1BA;MALEA;QACOA;UACiDA,YAG1DA;MADEA,WACFA;K;qBAEKC;;wBAGIA;wBACAA;MAJWA;;MAKlBA;QAAwBA,YAQ1BA;MAPEA;;UAEkDA,YAKpDA;QAJqDA;QAA5CA;UACqDA,YAG9DA;;MADEA,WACFA;K;qBAYKC;MAA0BA,2BAAkBA;K;eAE5CC;MACHA;;QACuBA,WAWzBA;YAjkFmDA;MA2jF1CA;MAAPA;QACSA;UACAA;YACAA;cAHTA;gBA99E+BA;gBAm+EvBA;;gBALRA;;cANIA;;;;;;;MAMJA,SAMFA;K;yBAmCcC;MAGeA;;;MACzBA;kBAE2BA;;;IAE7BA,C;;;;;;;;;;;;;;;;;;;;;;;MW3oFWC;;iBAFRA;MAEHA,oFACFA;K;0CAWOC;MAELA,WAA4CA,0BAC9CA;K;iBCxIKC;MACHA;;QAGEA,MAyBJA;;;;QAlBIA,MAkBJA;;MAdEA;QACEA,MAaJA;MATEA;;QAEEA,MAOJA;;;K;;;wBPyCAC;MA6BEA,gEAEFA;K;0BASAC;;uBAGMA;MAAJA;aACMA;UACFA;;;MAKJA;sBAEeA;QAAbA;UAAoBA,eAuDxBA;QAtDIA;UAAmBA,aAsDvBA;QApDqCA;QAAjCA;UACEA,eAmDNA;kBA/C8BA;UAKxBA,sBAAUA,kDAA4CA;;2BAOTA;MA2CfA;MA1ClCA;QAAyBA,kBAkC3BA;MA9BgBA;MACdA;QAAyBA,kBA6B3BA;MAvBEA;QAIEA,QAAOA,2BAmBXA;MAhB8BA;MAA5BA;QAEEA,QAOOA,8BAOXA;;QAPIA,QAAOA,8BAOXA;MALEA;4CAiB4BA,8CAhBiBA;QAC3CA,QAD2CA,gCAI/CA;;MADEA,QAH6CA,gCAI/CA;K;2BQnKUC;;QAKJA,sBAAUA;MAKZA;QACEA,sBAAUA;MAEZA,OAAWA,mDACbA;K;+BA+BQC;MACJA,+BAA0CA,2DAA8BA;K;2BAKhEC;;MAKVA,WACFA;K;4BCiGYC;MAGVA;QACEA;;;;;;;;;YASIA,WA4BRA;;YA1BQA,YA0BRA;;MAvBEA;;;;;;;;;;;;;;;;;;;UAmBIA,WAINA;;UAFMA,YAENA;;K;qCAIWC;MAAsBA;sBAGTA,SAAtBA;QACiBA;QAGVA;UACHA;QAEFA;;MAEFA,YACFA;K;sCAIWC;MAAuBA;aAGhCA;QACmCA;QAAlBA;QAGVA;UACHA;;MAIJA,YACFA;K;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4CCnOgBC;MAA4BA;MAGjCA;QAAPA,+DAgCJA;;QAf0DA;;;QAAVA,0BADxCA,yBAPYA;QAUhBA,OAAOA,mEAaXA;aAJWA;QAAPA,qEAIJA;MADEA,OAAOA,uDACTA;K;4CAEYC;6BAMNA,yBALYA;IAMlBA,C;kDAEYC;wBAMNA,yBALYA;IAMlBA,C;2CAEYC;MACJA,sBAAsBA,aAAMA;IACpCA,C;wBAeaC;MCoGaA,uDDnGIA;MAE5BA,OAAWA,4DACbA;K;iBAgBAC;;;;IAaAA,C;yBAEAC;;;;IAsBAA,C;8BA0DWC;MACXA,OA1BAA,2BEuBAC,eCyE2BC,gBDzE3BD,2BFvBAD,sCA2BFA;K;qBAUQG;MAENA;eACUA;MACVA,gBAvBwBA,QAwB1BA;K;iBASQC;MACNA;IACFA,C;kBAQQC;MACNA;IACFA,C;mBAOQC;MAENA,0BACIA,2BAAyBA;IAC/BA,C;oBASKC;MAECA;;wBAEqBA;;QASvBA;;;QACgBA;UAChBA;;UE3EFA,wBCyE2BL;gBDqDzBK;gBACAA;UF/CAA;;;IAEJA,C;6BAIkBC;;;;;;;;;;;;;MAwBhBA,OAAYA,CGrCeA,0CHqCgBA,wFAG7CA;K;mBIlKUC;MFiDRA,4BCyE2BP,gBDzE3BO;MGlIMA,eAAeA,aDmFTA;MAOVA,aACFA;K;6BAgBQC;MFuBRA,4BCyE2BR,gBDzE3BQ;MErBEA,oBAAkBA;MAOlBA,aACFA;K;wBAcQC;MAAWA;;QAEFA;oCACFA;UACTA,aAeNA;;UAbkCA;UFUlCA,oBCwD2BC,gBDxD3BD;YA6GEE;YACAA;UExHIF,SAaNA;;;QAnBmBA;QAQfA;cDgEuBT;QDzE3BS;QEWkCA;QAC9BA;0BAEgCA;;YvB1GpCG;UuByGMH,4CACkDA;;UAElDA;QAEFA,aAEJA;;K;yBAgCQI;;cDqBmBA;kBCnBIA;QACGA;QAC9BA;6BACoCA;;YvBrJxCD;kCuBsJ+BC;;;MF/C/BA,oBC8D2BC,gBD9D3BD;MAEEC;MEgDAD,SACFA;K;iBAuEuBE;MFrIvBA;;gCCyE2Bf;;YCgErBe;;MAOOA;;yBA4BTA;;UACMA;UACJA,wBAAYA;;;QAwBdA;UFpMJC,oBCuE2BC;UDtEzBA,qBEoM4BF;UAAxBA,SAuBNA;;QArBiBA;;cAAbA;;QApE0BA;QAqE1BA;QAUaA;QAAXA,SAUNA;;MADEA,aACFA;K;oBA6CcG;MAEZA,OAAOA,iBAAQA,6BN6NjBC,iCAxGgCD,+BAwGhCC,sCMvNAD;K;mBAGYE;MAAaA,WAAIA;K;oBAwBfC;MF9SdA;cCyE2BrB;;;MC4OJqB,gDAAiCA;QAAtDA;MAmBAA;MACAA,iBACFA;K;gCAqWGC;MAC2BA,mBDvmBHA;MCwmB3BA;2BACoCA;;UvB9wBpCV;gCuB+wB2BU;;MAE3BA;IACFA,C;uBF/qBEX;;MAwHuBA;QADrBA;QACAA;MAxHFA;IAEAA,C;iCA+NYY;MAAmBA;2BAjNPA;MAiNOA;MAjIPA,qBApFCC;YAsFvBD;;QAsIEA,wBAAYA,mDAYCA;;QAnBcA;QAuB3BA;QAKAA,oBAAkBA;;IAItBA,C;8BAIYE;MAAgBA;2BAvPAA;kDAyPZA;QAxJSA;QAEvBA,yBAAOA;;MAyJPA;QAC8BA;QAC5BA;QACAA;;QAEgBA,+DAAmBA;QAvOvBA,qBA1BYC;cA4B1BD;cACAA;QAsOEA;;IAEJA,C;mCAuFYE;;;yGACVA;;yBA3VsBA;mBA6VJA;qBA5VEA;QA6VlBA;UACEA;YAnQJA,yBAAOA;YAqQMA,yCAC6BA,kBAAkBA;;UAExDA,MA2JNA;;qCAtJqBA,oCAAjBA;mBAGWA;UACTA,sCAAsBA;;mBAGHA;yBAAOA;cAQvBA;cACDA;QAKJA;;wBArgBsBA;UAqgBGA;;UCiCXA;QDjCdA;wBAvgBcA;mBAAOA;UAygBnBA;mBAAwBA;YCqMdA,6CAAqBA;;YArK5BA;UDhCHA;uBAE0BA;YAxSVA,iBAzFAC;YA2FpBD,yBAAOA;YAuSMA,MAAPA,OAAOA,uCAC6BA,kBAAkBA;YACtDA,MA0HRA;;gBC7c2BA;UDuVrBA;YCsBYA;wBAGAA;;YD0DRA;;;wBAhmBmBA;UAmlBvBA;YA/D+BA,oGAgEHA;eACrBA;YACLA;cA9BsBA,yFA+BDA;iBAGrBA;YAzBcA,2EA0BDA;UAKfA;;qBAIIA;UAAqBA;kBAhePA;cAwLQA,iBAxLRE;cA4LNF,sBAAUA;gBAC1BA;cACOA;cAtEQA,iBAxHOG;cAwHPH,iBAxHOG;gBA2HtBH,YAAgBA;gBAChBA,wBAA4BA;oBA6WlBA;cACAA;;cAEAA;YAKJA,MAcRA;;;0BAX8BA;QA5TEA,qBAxLRE;QA4LNF,0BAAUA;cAC1BA;QACOA;mBAwTAA;mBAGqBA;mBAzfNI;QAsfpBJ;UA9YmBA;UAHTA;gBAEZA;gBACAA;;UAiZeA;UA9YGA;gBAElBA;gBACAA;;cA+YEA;;;IAEJA,C;2BAgDOK;MACUA;QACfA,OAAOA,4FAWXA;MARmBA;QACfA,OAAOA,yEAOXA;MALEA,sBAAUA;IAKZA,C;oBI3wBKC;MACHA;mBAAOA;;eAGiBA;;QACtBA;;QACOA;;IAEXA,C;yBAEKC;;;QAKDA;;;;aAIIA;UN3BJA,6CAAyBA,OM4BMA;;IAGnCA,C;4BAQKC;MAtDHA;WAwDIA;;cAEGA;UN3CLA,6CAAyBA,OM4CMA;;2BAG/BA,cAAcA;IAGlBA,C;oCAUKC;;cACCA;MAAJA;QACEA;mCACwBA;QACxBA,MAcJA;;MA7FEA;YAkFIA;MAAJA;aACQA;;;aAGAA,UAA6BA;oCACbA;iBAEZA;;;IAIdA,C;uBA2BKC;;uBHgKwBA;WG9JbA;QAGZA,wCAHYA;QAIZA,MAUJA;;MAR6CA,KAN7BA,qDAO0BA;QH0qBxBA,MGjrBFA,iCHirBuBA;;QG1qBrCA;;QAEEA,oDAC6BA;QAC7BA,MAGJA;;YHgJ6BA;MGjJtBA,uBAA+BA;IACtCA,C;8BCiBUC;MC+nBJA;;;MDznBFA,wBAAYA,wDAGAA;MAIZA,OCmoBFC,uEDloBAD;K;gCA6DQE;MACNA,OEqQFA,2BFpQMA,wDEoQNA,sCFnQAA;K;mCAg6DQC;;QEtsCsBA,kBAAoBA;MF0sC9CA,OE3sCJA,uDF2sCkCA;K;uCCzkE1BC;MAMNA;oBAgtBEA,wGAHAA,uGA1sBJA;K;iBAktBGC;MACHA;;QAAiCA,MAMnCA;;QAJIA;;QAHYA;QAIZA;QACKA,CL1gBoBA;;IK4gB7BA,C;sCAmFSC;MAA2CA,iEAG7CA;K;mCCxxBLC;gBNsL2BC;;MMtL3BD;;;IAMAA,C;sBAqcGE;IAAgCA,C;uBAGhCC;MACqCA;MAAnCA,CNzRsBA;IM0R7BA,C;sBAGKC;IAAoBA,C;kBCzjBzBC;MAAeA;;QAGXA,iBAAUA;;QAHCA;QAIXA;QAC8BA,ePuRLA;QOtRzBA;UACEA;;8BAEsCA;U7B8G1CtC;kC6B7GiCsC;UAC7BA;;;IAGNA,C;qBAIKC;MAEgBA;MACyCA;QAC1DA,4BAA0BA;;QAE1BA;IAEJA,C;4BAeeC;MAEbA,OAAOA,0DAGTA;K;qBAIKC;MACgBA;MACyCA;QAC1DA,4BAA0BA;;QAE1BA;IAEJA,C;iBLpBUC;gBFuPmBA;MEnPXA,YAHWA;QAGvBA,2CAIJA;MAFEA,OAAYA,2BACoBA,mCAClCA;K;yBFmGMC;;IAaSA,C;iBA4eHC;MAAMA;;+BAEQA;kBACRA;;MAEhBA,eACFA;K;qBA0BWC;MACFA;QAAgBA,WAE3BA;MADEA,OAAYA,sBAAOA,eACrBA;K;8BAgaKC;MAAwBA;;MAE3BA,iCAA+BA;IAKjCA,C;cAIEC;;;;;MACqBA;;MAAaA,KAAzBA;QAAkBA,iBAQ7BA;MANkBA;;QAEPA;QAAPA,SAIJA;;QAFgBA;QAxcIA;;;IA0cpBA,C;mBAEEC;;;;;MAEqBA;;;MAAaA,KAAzBA;QAAkBA,oBAQ7BA;MANkBA;;QAEPA;QAAPA,SAIJA;;QAFgBA;QApdIA;;;IAsdpBA,C;oBAEEC;;;;;MAEqBA;;;;MAAaA,KAAzBA;QAAkBA,2BAQ7BA;MANkBA;;QAEPA;QAAPA,SAIJA;;QAFgBA;QAheIA;;;IAkepBA,C;2BAEgBC;MAEdA,yBAAOA,SACTA;K;gCAEwBC;MAEtBA,sDAAOA,SACTA;K;iCAE8BC;MAE5BA,oEAAOA,SACTA;K;wBAEWC;;MAEPA,WAAIA;K;4BAEHC;MAEHA;MAGiCA;YAHlBA;MAAfA;QAhWgBA,cAgWDA,iCAhWsBA,wBAmW7BA,gCAEAA;MAKRA;IACFA,C;sBAEMC;MAKsBA;MAFbA,iCAAkBA;MAE/BA,OAAaA,wCACfA;K;8BAEMC;MAEJA;MAIkCA;MAFrBA,sCAAuCA;MFl+B1BC,mDD3FID;MG+jC9BA,OH7jCaA,oEG8jCfA;K;gBAEKE;MQ/oCHA,cRgpCeA;IACjBA,C;kBAEKC;MACEA,CAt5BsBA;IAu5B7BA,C;eAEKC;MAASA;MAORA;MAOAA;MATUA;MAEdA;yBACwBA;MAMxBA;QAEoBA;;;QAKHA;;MAnXjBA;MAMeC;MAFbA;MAKaA;MAFbA;MAKaA;MAFbA;wBAGmCA;MAAnCA,sCAxzBIA,4DA0zBSA;wBAC2BA;MAAxCA,2CA3zBIA,4DA6zBSA;wBAC4BA;MAAzCA,4CA9zBIA,4DAi0BSA;wBACmBA;MAAhCA,mCAl0BIA,mHAq0BSA;MAIAA;MAHbA;MAMaA;MAFbA;MAMaA;MAHbA;wBAIwBA;MAAxBA,2BAj1BIA,kGAm1BSA;MAGAA;MAFbA;wBAGsCA;MAAtCA,yCAv1BIA,6GA01BSA;MAsUfD,SACFA;K;cA+NEE;MAEAA;;QACEA,OAAOA,oDA+CXA;;MA3CcA;UACVA;WACiBA;UACjBA;;QAEAA,sBAAUA;MAG8BA;MAiB1CA;QAEUA;;QAEuCA;eAv2CzBA;eACUA;eACEA;eACcA;eAETA;eAECA;eACEA;gBACQA;gBACZA;gBACgBA;gBAC5BA;QAdfA,0DAeaA;;;QA81CjBA;QAAPA,SAUJA;;QAlDaA;QAyCTA;eACIA;QAAJA;UACEA;;UADFA,iBAGSA;UACPA;;;MAGJA,YACFA;K;eAGEC;MACEA,OAAKA,CAtsCoBA,0EAwsCpBA,iBAAYA;K;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MSv5CnBC;;qBAvDQA;MAOAA,sBAgDRA,wDA3BAA;K;6BAmROC;uBAIwBA;MAA7BA,qCACFA;K;6BAEYC;MAIVA;;;;IAQFA,C;4BAoBOC;MAQUA;MAAfA;MCjVFC;MDmVED,YACFA;K;iCA2HQE;MAOAA,6BCtdRD,+DD2eAC;K;0CAOQC;MACNA,2EAAOA,gCCnfTF,uFDofAE;K;wCAMQC;MACNA,OC3fFH,qFD4fAG;K;iCA2pBQC;MAOAA,OA6ERA,qDAxDAA;K;wCASQC;MAA0BA,OA+ClCA,qDA/CyDA;K;0CAIjDC;MACJA,sEA0CJA,uDA1CmDA;K;kCA6T5CC;MAQUA;MAhCSA;;;MAkCxBA,YACFA;K;6BAwGAC;;QACEA,yBAAaA;MADfA;IAEAA,C;0BExjDQC;MACaA;MACnBA,mBAAcA;MAGdA,aACFA;K;wCCsHcC;MAEZA;MAAIA;QACFA;UAEEA,cAgBNA;QAdIA,6CAcJA;;MAZ+BA;MAC7BA;;QAEEA;;QAZ+BA,eAcIA,4BAJrCA;QAKEA,UALFA;UAKEA,gBALFA,uBAKoBA;QAAlBA,CALFA;;M5ByVYA,6CAAqBA;M4BlVjCA,sCAIFA;K;uCAYcC;MAEZA;MAAIA;QACFA,6CAYJA;M5BkSAA;M4B3SEA;;QAEEA;Q5B0TUA,EAAZA,wCAAsBA;;Q4BlUUA,eAUKA,4BAJrCA;QAKEA,UALFA;UAKEA,gBALFA,uBAKoBA;QAAlBA,CALFA;;;iB5B0U4CA;M4BlU5CA,sCACFA;K;yBAOGC;MACHA;iBAAoBA,kBAAkBA,gBAAtCA;mBAAoBA,kBACDA;UAAuBA,WAG5CA;MADEA,YACFA;K;6BAGKC;MAyBoBA;;;MAGvBA;;;QACOA;UAAeA,MAoFxBA;QAnFwBA;QACpBA;uBACeA;QACfA;;MAUGA;QACHA;UAAoCA,MAqExCA;QApEqBA;mCAAMA;QAANA;QACGA;mCAAMA;QAANA;;QAEHA;QACjBA;QACKA;UACHA;YACEA,+BAAYA;YACZA,MA4DRA;;UA1DyBA;UACCA;qCAAMA;UAANA;mCACKA;;UAEXA;UACdA;UAVFA;iBAaSA,iBAAPA;YAEgBA;YACdA;YACAA;cAQEA;;;gBAEYA;2CAAMA;gBAANA,sBAAmBA;gBAC7BA;;cAEFA;cACAA,MAgCVA;;;UA7B4BA;UACHA;mCACMA,2BAA2BA;;;uBAOtCA;QAEhBA;;;;MAMFA;mCAAqCA;;QACzBA;mCAAMA;QAANA,sBAAmBA;QAC7BA;UAEEA;;;;MAGJA;QACEA;MAEFA;MACAA;IACFA,C;sCC5TUC;MACuBA;MAC7BA,mBAAcA;MAGdA,aACFA;K;sCCMQC;MACoBA;;MAC1BA;QACEA,gBAAWA,UADbA;MAGAA,aACFA;K;yBClFcC;MAEZA;MAAIA;QACFA,cAwBJA;M/BmgBAA;;Q+BthBIA;;UAEKA;QACLA,eAAUA;;;QAXWA,eAsBcA,4BAAlBA;QACjBA,UADiBA;UACjBA,gBADiBA,uBACCA;QAAlBA,CADiBA;;iB/BuiByBA;M+BniB5CA,sCACFA;K;gBCudAC;;aASWA;;MAATA,cAASA;MATXA;IAUAA,C;yBAmVAC;kDAEmBA,aACcA,0BACTA,QAJxBA;IAI6BA,C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBC50B/BC;MACEA;;QAAuBA,sBAAMA;;;;;QADrBA;QAQIA;QAAVA;;MAIOA;MAAPA,SAIJA;K;4BAiDAC;MAEEA;;QAAoBA,WAyBtBA;MAtBEA;QACEA,aAqBJA;;QAdIA,OA8BFA,2CAhBFA;MAVEA;QAO8BA,2CAAuBA;MAErDA,aACFA;K;qCAiRgBC;MAIZA;QAGEA,OAAOA,wEAGXA;MADEA,WACFA;K;8CAEcC;MAeEA;;MACdA;QAAqBA,WAcvBA;MAbQA;MAANA;QACEA,OAAOA,wDAYXA;yBATyBA;MACNA;MAEjBA;QACEA,OAAOA,wDAKXA;MAFEA,OAAOA,6EAETA;K;wCAEcC;MACRA;QAAoBA,WAE1BA;MADEA,OAAOA,0DACTA;K;0CAEcC;MAAwBA;;;QAKlCA,SAGJA;;QARsCA;;MAOpCA,WACFA;K;yBAcYC;;yBAEYA;MACtBA;qBACcA;wBAEEA;YACgBA,WAIlCA;MADEA,YACFA;K;8BAKOC;MAAYA;;;QAOfA,SAGJA;;QAVmBA;;MASjBA,WACFA;K;4BAIEC;MACIA;MAESA,iDADfA;QACeA;QACRA;4BAAKA;QAAVA;UAAsCA,eAG1CA;;MADEA,sBACFA;K;+BCpTcC;MAENA;QACFA,sBAAMA;MAMRA;QACEA,sBAAMA;MAGRA;QACEA,sBAAMA;IAKVA,C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;elC1DWC;MAUSA;MAPlBA;QAAmBA,YAGrBA;MAFEA;QAAqBA,OAAOA,sBAE9BA;MADEA,sBAAUA;IACZA,C;2BAiDcC;MAEkBA;QAAPA,2BAEzBA;MADEA,yBHybsBA,IADRA,0CGvbhBA;K;sBA6MQC;MAGIA;;MACVA;QACEA,sBAA2BA,SAA3BA;UACEA;MAGJA,aACFA;K;oBAGQC;MACYA;;MAClBA;QACEA,8BADFA;MAGAA;QAAcA,WAEhBA;MADEA,OwB3YFA,6BAAeA,8BxB4YfA;K;4BASQC;MACYA;;MwBjZpBA;MxBkZEA,6BwBlZFA,cxBmZAA;K;iCAeQC;MAENA;MAAcA;QACCA;uBAeAA;QACMA;QAfnBA,OAmBgBA,0DAFTA,+DAXXA;;MAJgBA;QACZA,OAuBgBA,yDADGA,kDADDA,SAlBtBA;MADEA,OAAOA,mDACTA;K;gCAGQC;MACNA,OAAkBA,yCACpBA;K;gCAkBcC;MAEZA;;QAAeA,sBAAUA,6BAAqCA;MAC1DA;MAAJA;QACEA,sBAAUA,+BAAuCA;MAEhCA;MACnBA;QACOA;UACHA,sBAAUA;;MAIdA;eACSA;UAAeA,UAAYA;;QAElCA;UACOA;YACHA,sBAAUA;UAEZA,UAAYA;;MAGhBA,OAAkBA,sCACpBA;K;mBAaQC;MAKJA,OI7eJA,6BAM2BA,0EJ2eJA;K;4BAwDTC;MACgBA;MACvBA;QAAqBA,aAa5BA;mBYzLoBA;;UZ4LgCA,cAbVA;eAC7BA;;QAYuCA,cAVZA;eAC7BA;UASyCA,kCAPVA;;MAGxCA,aACFA;K;cA8EeC;MACWA;MACxBA;QAAiBA,OAAWA,gBAE9BA;MADEA,sBAAUA;IACZA,C;qBAuBcC;MAEZA;;wBAAwBA;QAASA;QH4lBnCC;UAAsBA,kBAAMA;;;QG5lB1BD;;QACEA,WAsBJA;mDmCrtBqCA;MAAhBA,+BAAQA;qBnCssBDA,yBAA1BA;oBACaA;QACXA;UACqBA;UAAfA;8CAAcA;8BAAdA;;UADNA;;UA7PgBE;;uEAqQDF,yBACAA;;MAGjBA,sCACFA;K;wBASsBG;MACpBA;gCAAIA;QACFA,OAAOA,oCASXA;;QAJIA;;QAP0BA;QAQ1BA;QACAA,iBAEJA;;K;0BoC9OcC;MACDA;;MAEXA;QAAkBA,aAIpBA;MAHEA;QAAiBA,wBAGnBA;MAFEA;QAAgBA,yBAElBA;MADEA,0BACFA;K;2BAUcC;MACZA;QAAcA,aAGhBA;MAFEA;QAAaA,cAEfA;MADEA,eACFA;K;yBAEcC;MACZA;QAAaA,aAEfA;MADEA,cACFA;K;etB7cMC;MAUoCA;kCACUA;MAX9CA;IAYiBA,C;wBpB5BTC;MACgBA;QAC1BA,OAAOA,qBAMXA;MAJEA;QACEA,6BAGJA;MADEA,OAAOA,+BACTA;K;qBAiBAC;;IAA8BA,C;oBAkD9BC;;IAGiBA,C;yBAgBjBC;;IAEsBA,C;2BAKtBC;;IAGyBA,C;iBA4CzBC;MAAUA;MAAVA;IAGoBA,C;sBASpBC;;IAI0EA,C;sBAiB1EC;;IAK4EA,C;qCAsBhEC;MAEVA;QACEA,sBAAiBA;IAErBA,C;gCAuCWC;MAITA;QAEEA,sBAAiBA;MAEnBA;QACEA;UAEEA,sBAAiBA;QAEnBA,UAGJA;;MADEA,cACFA;K;iCAOYC;MACNA;0BAAMA;MAAVA;QAAeA,sBAAiBA;IAClCA,C;iBA+CAC;MAGkBA,+CAAsBA;MAHxCA;IAK6DA,C;uBAuG7DC;;IAA8BA,C;yBAgB9BC;;IAAkCA,C;iBAclCC;;IAAwBA,C;kCAexBC;;IAAkDA,C;sBQ3d5CC;;IAA8DA,C;wBmC0E5DC;MAIQA;;MAAIA;MAIlBA;QACEA,uCAAYA;MAEdA,aACFA;K;kBCOmBC;MACfA,O/CoFJA,yG+CpFiCA;K;WCrK9BC;MACaA;cACZA;MAAJA;Qf4BAA;;QezBEA;IAEJA,C;2BC4oBIC;MACFA,oDACFA;K;eCkEaC;;iBAyDGA;MAGZA;QA44HWA,gDACJA,iDACAA,gDACAA,iDACAA;QA94HLA;UAGEA,OAAeA,6BAD0BA,6DACLA,SAgO1CA;aA/NWA;UACLA,OAAeA,iBAAOA,uDAAwCA,SA8NpEA;;MAtNgBA;;;MAKdA;;;;;;;;MASYA;QAIVA;yBAEcA;MACZA;8BAAUA;MAAdA;QAEUA;;kBAaMA;MAAOA;wBAAkBA;MAAlBA;yBACPA;yBACAA;0BACCA;6BACGA;MAMhBA;kCAAcA;MAAdA;gCAAcA;MAAlBA;QAOcA;MAHVA;8BAAUA;MAAdA;QAYuCA;WARhCA;QAEOA;MAMVA;8BAAUA;MAAdA;QAGoBA;MA5INA;;;;;;kBAkJEA;MAAOA;uBAAkBA;MAAlBA;MAEvBA;QAIEA;;UAxFMA;;UA6FKA;UAAJA;;YA7FDA;;YAmGMA;cAEJA;;cA5GVA;YAwGSA;;cAjGDA;;cAgHJA;gBAEMA;kBAEFA;oBAKOA;sBAICA;sBA+wHOA;;sBA/wHPA;sBA8wHOA;;oBA9wHMA;oBACnBA;oBAIcA;oBAAdA;oBACAA;6BAEUA;oBAjHfA;;;yBAkHUA;oBAIHA;oBACAA;oBAFMA;oBAGNA;;;;uBAcKA;kBAKLA;oBAGAA;oBACAA;oBACAA;oBAHMA;oBAINA;;;;;;mBAe6BA;gBAK/BA;kBAGAA;kBACAA;kBACAA;kBAHMA;kBAINA;;;;;;cAtMVA;;;;;QAmOiCA;MAXjCA;gBAC6BA;QAA3BA;UACQA;UACNA;UACAA;UACAA;UACAA;UACAA;UACAA;;QAEFA,OAitGJA,oGA3sGAA;;MAFEA,OAAYA,iHAEdA;K;yBA+FcC;MAERA;MADJA,OAAYA,uDAC8BA,UAAQA,oBACpDA;K;2BAmFiBC;MACLA;;;;;wEAOVA;QACaA;QACXA;UACEA;YAEEA;;UAGFA;YACEA;UAEaA,mBAAMA;UACjBA;6BAAKA;UAATA;YACEA;UAEKA;UAAPA;6CAAMA;;UACMA;;;;MAIhBA;QACEA;MAGaA,mBAAMA;MACjBA;yBAAKA;MAATA;QACEA;MAEFA;yCAAMA;;MAENA,aACFA;K;0BAmBiBC;MAULA;;mBAKEA;cAWHA;QAAYA;MACHA;MAMlBA;QACaA;QACXA;UACEA;YAEEA;YACIA;cACFA;YAIAA;;UAAJA;YAEEA;cACEA;YAGFA;YlC/7BMC;;YkCk8BND,+BAAUA;UAEAA;eACPA;UlCr8BGC;;ekCy8BFD;QAAaA;MACTA;MACeA;MAC7BA;QACEA;MAEFA;QACEA;UACEA,+BAAUA;;UAEOA;UACjBA,oCAAUA,eAAeA;UACzBA,oCAAUA,eAAeA;;MAG7BA;iBACYA;UACRA;sBAEaA;QACfA;;qBAGmCA,uEAArCA;qBACcA;QACZA;UAEEA;YACEA;0CAAKA;;YACCA;YAANA;uCAAKA;;YACLA;;;UAGaA;UAAfA;wCAAKA;;UACCA;UAANA;qCAAKA;;UACLA;;;MAGJA,YACFA;K;yBAyFQE;MAWNA;;QAEEA;UACWA;;UACJA;YACLA;;;MAMJA;QACsBA;QAEPA;QAENA;QACHA;QAKKA,yCAHIA,YAAMA,qCAAkDA;;QAgBzBA;;QAAhBA;;MAT1BA;MAGMA;MAMVA,OAtDFA,4EAoDeA,0DAGfA;K;eAGQC;MAAIA;MAUDA,kEAysG4CA;MAxsG1CA;MACJA,wDAusG8CA;MApsG7CA;MACGA;MACJA;MACQA;MACfA;qB7BjkCkBC;;Q6B4jCdD;MAKJA;QAGqBA;;MAAhBA;MACEA,wDA4rG8CA;iB7BjwInCA;M6BukCqBA;QAE9BA;;QAEAA;MAKTA,OA5FFA,mCAyFsBA,qFAItBA;K;uBAqCWE;MACTA;QAAsBA,SAGxBA;MAFEA;QAAuBA,UAEzBA;MADEA,QACFA;K;gBA6CYC;MACVA,sBAAMA;IACRA,C;oBAuEQC;MAENA,iBACMA,0CACAA,gCACRA;K;iDAWOC;MAELA,sCAAiBA;IASnBA,C;8CAEOC;MAGLA;M9Bp6CWA,+GlB4CbC,uBAEyBA,mBAFzBA,iDgDw3CED;ehDn3CeA;QgDo3CTA,yBAAiBA;UACnBA;YACEA,sBAAMA;;YAENA,sBAAMA;;IAIdA,C;mCAEOE;MACLA;;;;;;;QAEEA,MASJA;MAPEA;QACEA,sBAAMA,yBAC+BA;;QAErCA,sBAAMA,4BAC+BA;IAEzCA,C;uBAEOC;MAEUA;;MAIXA;QAEFA,OAAOA,2CAKXA;;QAFIA,OAAOA,0CAEXA;K;8BAEOC;MACLA;MAAIA;QACEA;UACKA;;UAEAA;UAEHA,QADKA,mEAELA;YACFA,sBAAMA;;;Q7BlrDLA;e6B0rDEA;MAAcA;QACrBA,gCAAyBA;QACDA;UACtBA,sBAAMA;QAGWA;QAInBA;QACAA,OAAOA,6CAoCXA;;MAjCMA;QACEA;UAEcA;UAEXA;UAAiBA,0DAAoBA;UAEvBA,+CADsBA,qDACbA;UAC5BA;UAIAA,OAAOA,gDAqBbA;;UAlByBA;UAInBA;UACAA,OAAOA,6CAabA;;;QATuBA;QACnBA;QAMAA,OAAOA,8CAEXA;;K;oBAsHWC;MAEmBA;QAAsBA,WAEpDA;MADEA,WACFA;K;oBAacC;MAEZA;;QAAkBA,WAmCpBA;MAlCEA;QAAkBA,SAkCpBA;MAhCMA;QACkBA;QAAhBA;UACFA;QAG6BA;QAAnBA;QACZA;UAE6BA;UAClBA,uCADJA;;UAK6CA;QAFhDA;QAEJA,OAAOA,+EAmBXA;;MAfIA;QACMA;UAmBIA;UAELA;UAlBDA;YAE6BA;YAClBA,uCADJA;;YAIqCA;UADxCA;UACJA,aAAWA,iEAKnBA;;MADEA,OAAOA,0CACTA;K;uBAIWC;MACGA;MAEZA,kDACFA;K;2BAccC;MzCr7CdA;;MyCg8CEA;QACaA;QACXA;UACuBA;UACjBA;UAAJA;YACEA;YACAA;;;YzCt8CRA;UyCy8CqBA;UAGfA;YACgBA;eACTA;YACLA;gBzC76CNC;UyCg7CID;;;;UAxCJA;YAAmCA;YAAbA;4CAAYA;mBAAZA;;YAAtBA;UA2CSA;YACLA;;gBzCt9CNA;cyCy9CQA;gBACeA;;;;;YAKjBA;;YAGAA;cACaA;cACXA;gBACiBA;;;;;cAQVA;;czC7+CfA;YyC0+CqBA;YAEFA;YACbA;;;;;MAIJA;QAAoBA,OAAOA,gDAM7BA;MALEA;QACiBA;iBzCp9C2BA;MyCu9C5CA,sCACFA;K;4BAacE;MACCA;MAMbA;QACaA;QACXA;UAEuBA;UACjBA;UAAJA;YACEA;YACAA;;;YzClhDRA;UyCqhDqBA;;UAIfA;YACgBA;YAMPA;iBALFA;;;;;gBzCz/CXD;UyC8/CIC;;;;UAtCJA;YAAoCA;YAAdA;4CAAaA;mBAAbA;;YAAtBA;UAyCSA;YACLA;;gBzCpiDNA;cyCuiDQA;gBACeA;;;;;YAKjBA;;YAgUJA;cAC0BA;cAApBA;8CAAmBA;qBAAnBA;;cADNA;YA/TSA;cACLA;;cAGAA;gBACaA;gBACXA;kBACiBA;;;;;gBASVA;;gBzC9jDfA;cyC0jDqBA;;cAGFA;cACbA;;;;;;MAIJA;QAAoBA,OAAOA,gDAO7BA;MANEA;QACiBA;;;iBzCriD2BA;MyCyiD5CA,sCACFA;K;sBAOcC;MACZA;;QAAkBA,SAkBpBA;MAhBOA,mCADqBA,2BAAOA;QAE/BA;MAGFA;QACuBA;QAiRvBA;UAAkCA;UAAbA;0CAAYA;iBAAZA;;UAArBA;QAhREA;UACEA;QAEFA;;;MAIOA;MAETA,OAAOA,6EACTA;K;8BAKcC;MACZA;QAAsBA,aAKxBA;MAJEA;QAAsBA,aAIxBA;MAHEA;QAAuBA,cAGzBA;MAFEA;QAAyBA,gBAE3BA;MADEA,aACFA;K;wBAEcC;MACZA;QAAsBA,SAExBA;MADEA,OAAOA,oDAA4CA,iBACrDA;K;oBAEcC;MAEPA;;;;MAELA;QAA0CA,wBAoB5CA;MAnBEA;;QACEA,sBAAMA;MAGRA;QACWA,yDAAwCA;;oBAGxCA;;QhD1xDbC,wEkBrJ4CD,O8Bg7D/BA,iChD3xDbC,4CgD4xDSD;;gB7BzwDWA;Q6B4wDhBA;UAAYA,UAMhBA;aALoCA;QACnBA;MAGfA,OADSA,mDAEXA;K;yBAOcE;qBACDA;MAA6BA;QACtCA,OAAOA,wDAGXA;MADEA,OAAOA,+BACTA;K;qBAEcC;MAEZA;QAIEA,OAAOA,iDAAyCA,gBA6BpDA;MA1B+BA,WA0B/BA;K;wBAEcC;MACZA;QAAsBA,WAGxBA;MAFEA,OAAOA,oDAA4CA,gBAErDA;K;2BAecC;MAAgBA;qBACrBA;MACHA;sBAAoBA;QACtBA,UAuBJA;MArBmBA;MACCA;MACIA;MACCA;MACvBA;QACEA,UAgBJA;MAd8BA;MA4oB5BA;QACuBA;QAAjBA;wCAAgBA;eAAhBA;;QADNA;MA3oBAA;QAIEA,OzCxzDgBA,qGyCi0DpBA;MAPEA;QAEEA,OAAOA,sEAKXA;MADEA,WACFA;K;sBAEcC;MAAWA;;;MAGvBA;QAEcA;;;QACZA;QACAA,0CAAeA;QACfA,0CAAeA;;QAKfA;UAGEA;;;;;;;;;UAKmBA;;QAATA;;;QAEZA;UACeA;UACbA;UACAA,kDAAuBA;UACvBA,kDAAuBA;UACvBA;;;MAIJA,OAAcA,iDAChBA;K;gCAQcC;MAGLA;MAAPA,oBAEIA,0DACNA;K;qBAacC;MAGCA;MAIbA;QACaA;QACXA;UAA6BA;UAAVA;yCAASA;yBAATA;;UAAnBA;;UACEA;;UAIAA;YACgBA;YAEdA;cACEA;cACAA;;YAGFA;;;;;;YAMKA;cAsCXA;gBAC0BA;gBAApBA;gDAAmBA;uBAAnBA;;gBADNA;;cAtCWA;;cACLA;;;;cAGAA;gBAEMA;gBAAJA;kBACaA;kBACXA;oBAGiBA;;;;;;;;cAIPA;;;;YzCt1DtBA;UyCy1DmBA;UzCl1DFA;UyCo1DXA;sCAAMA;UAANA;;;;MAIJA;QACEA,YAMJA;MAJEA;QACeA;iBzCp0D6BA;MyCs0D5CA,sCACFA;K;iCAsDYC;MACNA;QAAsBA,WAG5BA;MADEA,OADYA,+CAEdA;K;6BAOcC;MACZA;MAAKA;QAA8BA,WAsBrCA;MAvBgCA,mB7B5iEZ7B;M6B+iEI6B;MAECA,kCAAvBA;;QAEMA;qB9Bv5DYC;U8Bw5DdD;YACEA;wCAAOA;YAAPA;sB9Bz5DYA;c8B25DVA;;UANRA;eAUSA;UAVTA;;UAaIA;;;;MAGJA;QAAiBA;MACjBA,OAAOA,qCACTA;K;iCAacE;MAAsBA;sBAC1BA;MACHA;QAEHA,sBADyBA,iCA2B7BA;MA9BoCA,mB7BhlEhB/B;M6BulEI+B;MAECA,kCAAvBA;;QAEEA;UACgCA,U9Bh8DhBA;Y8Bi8DZA;wCAAOA;YAAPA;YAJNA;;YAOMA;;;aAEGA;UATTA;;UAYIA;;;;iB9Bz8DcA;M8B48DlBA;;UAA6CA;qCAAMA;qBAANA,GAAUA;;UAAvDA;;QAfAA;MAeAA;QACEA,WAKJA;MAH4BA;QAAcA;MACxCA;QAA4CA;mCAAMA;QAAhCA,uCAAYA,2BAAcA;;MAC5CA,OAAOA,qCACTA;K;wBAGcC;;iBACHA;MAAeA,6CAAuBA;QAC7CA;UACaA;UACXA;YACEA,OAAUA,qDAA0BA,2CAS5CA;UAPMA;YACmBA;YAAbA;4CAAYA;mBAAZA;;YADNA;;YAEEA;;MAINA,WACFA;K;6BAoJcC;MAEOA;;qBACNA;MACGA,uCAAZA,cACQA,yBAARA;QACuBA;qCAAQA;QAAjCA,gCAAiCA,yBAARA;QACzBA;QAM0BA;;QAH1BA;QAG0BA;;MAApBA;MACAA;QACSA;gBACNA;;;MzC7oECA;;MyCqpEZA,sCACFA;K;6BAgHWC;MACLA;MACJA;QACiBA;QACfA;UACmBA;;UAGjBA;UACAA;YACmBA;;YAEjBA,sBAAMA;;;MAIZA,WACFA;K;qBAccC;MAAUA;;eAIHA;MAJGA;MASLA;MADGA;MAApBA;;UARsBA;;;QASLA;QACfA;UACaA;YANRA;;YALeA;;;QAUpBA;UALKA;UASHA;;QANyBA;;MAU7BA;aACMA;UAdCA;;UALeA;QAmBpBA;UACEA,OAAOA,gCAyBbA;;UjDn1FAC,wBiD4zFcD;;QAGFA;QACRA;UACiBA;UACfA;YACEA,sBAAMA;UAERA;YACEA;cACEA,sBAAMA;YAERA,+BAAUA;YACVA;;YAIAA;;;MCv1FqDA;MD21F3DA,OCtnFIE,yBArO+CF,gBD41FrDA;K;iCAEYG;MACNA;MACJA,0CACFA;K;uBA6bYC;MAEVA;MAGAA;czCxwFAzB,mBAA6CA;;QyC2wF1ByB;QACjBA;UACEA,sBAAoBA;QzCxyFTA,6ByC0yFKA,mBACdA,WAAiBA,oDAAmCA;czChxF1DzB;QA3BeyB,wByC6yFKA,mBAFdA,WAGiBA,qDAHmCA;;IA4B5DA,C;+BAWWC;MACLA;wBACyBA,iCAA7BA;QACaA;UACSA;QACpBA;;UAEEA;;QAEFA,SAGJA;;MADEA,iBACFA;K;oBA0PeC;MAAMA;;;qCAEIA;MAOHA;oBAIJA,kDAAhBA;QACSA;QACPA;UAAwCA;QACxCA;UACEA;;YAEEA;;UAEFA,sBAAMA;;;MAGVA;QAGEA,sBAAMA;aAERA;QAEEA;QACAA;QAEAA;UACSA;UACPA;YACEA;;iBACKA;YACLA;;QAGJA;UACEA;;UAG4BA;UAGvBA;YACHA,sBAAMA;UAERA;;;MAGJA;MAGgCA;kBAFRA;QAEfA;;QAKSA,wCAAqCA;QAErDA;UACSA;;MAGXA,OAveFA,uCAweAA;K;6BAOYC;MAINA;;MACsBA,oFAA1BA;QACaA;QACXA;4BAAOA;QAAPA;QACAA;UACqBA;UAAfA;8CAAcA;8BAAdA;;UADNA;mBzClpG2C5E;QyCkpG3C4E;UzCxwGgB5E,MAsHlBA;;UAtHkBA;gBAsHlBA;UAtHkBA,sCyC6wGO4E,wCAAsBA;gBzCvpG/C5E;UAtHkBA,MAsHlBA,kDyCwpGyB4E;;;MAGzBA;QACEA,YAA0BA,0BAA1BA;UACaA;UACPA;6BAAKA;UAATA;YACEA,sBAAoBA;;IAI5BA,C;mBAgMcC;MAmDHA;;;0CAAuCA;aAInCA;aAOFA;aAaAA;aAWJA,UADLA;MACJA;MACAA;MACAA;MACAA;MACAA;MACAA;MAGSA,eADLA;MACJA;MACAA;MACAA;MACAA;MACAA;MACAA;MAGSA,eADLA;MACJA;MACAA;MACAA;MACAA;MACAA;MACAA;MAGSA,eADLA;MACJA;MACAA;MACAA;MACAA;MACAA;MAGSA,eADLA;MACJA;MACAA;MACAA;MACAA;MACAA;MAGSA,eADLA;MACJA;MACAA;MACAA;MACAA;MACAA;MAGSA,eADLA;MACJA;MACAA;MACAA;MACAA;MACAA;MACAA;MACAA;MACAA;MAGSA,eADLA;MACJA;MACAA;MACAA;MACAA;MACAA;MACAA;MACAA;MAGSA,eADLA;MACJA;MACAA;MACAA;MACAA;MACAA;MAGSA,eADLA;MACJA;MACAA;MACAA;MACAA;MACAA;MAGAA,UAASA,UADLA;MAIKA,eADLA;MACJA;MACAA;MACAA;MACAA;MACAA;MAGSA,eADLA;MACJA;MACAA;MACAA;MACAA;MACAA;MAGSA,eADLA;MACJA;MACAA;MACAA;MACAA;MAGSA,eADLA;MACJA;MACAA;MACAA;MACAA;MACAA;MAGSA,eADLA;MACJA;MACAA;MACAA;MACAA;MACAA;MAGSA,eADLA;MACJA;MACAA;MACAA;MACAA;MAGSA,eADLA;MACJA;MACAA;MACAA;MACAA;MAGSA,eADLA;MACJA;MACAA;MACAA;MAGSA,eADLA;MACJA;MACAA;MAKAA,UAASA,UADLA;MAIKA,eADLA;MACJA;MACAA;MACAA;MAEAA,aACFA;K;WAWIC;MACWA;;MADNA,yBAEWA;MAILA,8CAHbA;QACcA;uCAAMA;sBAANA;QAEDA;QAEXA;UACuBA;QAANA;qCAAKA;0BAALA;QACTA;QACRA;;MAEFA,YACFA;K;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MEnpIUC;;sBAHVA;MAwBEA,uClBsUIA,gEkBtUWA,YACjBA;K;qBAoFUC;M5B0ERC,wBCyE2B3M,gBDzE3B2M;oBAtLIC;qB4B+GYF,yBAAuBA,kDACzBA,yBAAuBA;MAGrCA,SACFA;K;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;SCvIEG;;MAGWA;MACAA;MAJqBA,gBAG9BA,eACAA,cAAYA;K;SAkCZC;MAGFA,4BACFA;K;;;+BCyzlCEC;MAIYA,6CAAiBA;MAJ7BA;MAKEA;MALFA;IAMAA,C;eAo4GeC;gB7B39rCYA;M6B+9rCfA,YAFaA;QAAMA,eAGjCA;MADEA,oDACFA;K;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBC3ssCEC;MAAWA;YCGaA;MvDQxBjB;MuDrCyBkB;MA4BzBD,gDxC44BaC,oBwC93BiCC;MAd9CA;MDFAF;IAE6DA,C;;;;;;;;;;;;;;0BEdxCG;MAMfA;;;kBCUgDA,2BC5BxBA;QFqBxBA;mBhC8OqBA;MgC3OdA,gEAAyBA;MAO7BA,qCAAiBA,SAAOA,uCAAQA;;MG/BrCA,WAASA,2BHiCqBA,mHGjCHA,wCAACA,sBAtB5BA,0BAGgDC,uEAISA,gFAGtCA;MH0HrBD,iBEvG8BA,SFwGhCA;K;oCAGmBE;MACjBA;QAAkBA,WAGpBA;MAFWA;QAASA,WAEpBA;MADEA,OAAWA,sDACbA;K;uCAKYC;;MACFA,OC/DgBA,gBCtDMC,SEvBNA,gBJ4IPD;IACnBA,C;+BAGYE;MAEFA,OCrEgBA,gBCtDMD,SEvBNA,gBJkJPC,kEAEUA,mCKpICA,8BAArBA,CrCmQkBC,yBqCnQVD,wBLuIJA;IAEbA,C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6BMtIaE;MAKHA,iBAAKA;QAAkBA,OAAaA,kBAI9CA;MAHWA;MAAKA;QAAoBA,OAAaA,kBAGjDA;MAFMA,yCAAiBA;QAAwBA,OAAaA,sBAE5DA;MADEA,OAAaA,oBACfA;K;;;WCkCAC;MAzD4BA;;;aAMAA;MAOCA;YvCiQF5N;MuCpNzB4N;QACEA,kBAAoBA;MAFxBA,sDCpDIC,oBzCUAjB,sBAsLJD,uGwC5IAiB;IAWAA,C;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBEnESE;MAKTA;;MASeA;QACeA;;QAA5BA,OCkJFA,YAA6CA,yBjEoO7C9C,oDkBrJ4C8C,O8CjOHA,6DhEsXzC9C,yDgErTF8C;;MAzDMA;MAAsCA;MAKFA;MAExBA,oCACOA;;MhEsWvB9C;MgEtWA8C,OEiLAA,YACmBA,yBlEoLnB9C,oDkBrJ4C8C,O8CjNVA,0EhEwHeA,2DAAMA,OgExE9CA,gDG5CTC,8BH6CFD;K;qBAGOE;MAGeA;;YAFbA;M7C7CEA;M6CiDWA;;M7C7CNA,+CAJLA,yC6CoDGA,mD7ChD8BA,U6CmDlCA;MAEYA;M7CzDXA;M6C2DWA;M7C3DXA;M6C6DWA;M7C7DXA;M6CgEWA;MAnBpBA,O7CzCcA,0CAJLA,yC6CqEGA,2C7CjE8BA,U6CiEFA,kCAC1CA;K;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YIlFEC;MAEcA;MAFdA;IAGuCA,C;;;;;;;;;;;;;;MCmBNC;;;;+BAFMA;MAEbA,mFAuC1BA;K;yBAKmBC;MACCA,OAAOA,kDAY3BA;K;uBASQC;MAYNA;;;MAAoBA;MAcpBA;QAAoCA,OAAOA,WAe7CA;MAdaA,EAAXA;;MACaA,EAAbA,+CAAkBA;MAKGA;MACDA,OAAPA;MAAYA,oCxDkOzBA,8EwDlO8CA;MAK9CA;QAAqBA,OAAOA,WAE9BA;MADEA,OAAOA,eAAaA,YACtBA;K;gBAKAC;MAiBgDA,2BANNA;gCACTA;iFAKoBA;mCAEtBA,a/B2K/BA;+B+B1KgCA,c/B0KhCA;M+B9LAA,8EC3FAA;MDgHEA;QAA8BA;MAC9BA;MAtBFA;IAuBAA,C;oBAMAC;MAkBmBA;gCANcA;;;;MAZjCA,qBAU6BA,oDAShBA,yCACEA;MACuBA;MAIpCA;QAA8BA;MAE9BA;MA3BFA;IA4BAA,C;0BAGAC;MACeA;;wDACcA,wBACAA,yBAA2BA,oBAArBA;aACnBA,+BAAoBA;aACHA,kBAAnBA;aAC8BA,oBAAzBA;aAC8BA,kBAA3BA;aACmCA,kBAA/BA;aACSA,iBAApBA;;cACEA,mCAAwBA,OAAnBA;;cACHA;MACXA,4BAA2BA,UAAVA,uCAAjBA;;QAC8BA;QAFnBA,iBAEQA,yBAAiBA,oBAANA,uBACfA,uBAAiBA;;MAdxCA,4DAgBqCA,8BAArBA,iCAA6BA,WAAIA,kEAhBjDA;IAkBwCA,C;kCAGzBC;MACTA;;;QAAsBA,QAAeA,kBAI3CA;MAHoBA;MAClBA;QAAyBA,OE7NrBA,oBF6NuDA,8BAE7DA;MADEA,OEjOIA,cFiOWA,YAA8CA,iBAAvBA,6CACxCA;K;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aGnDAC;MCpKgBC;clD6OW1O;;;aiD7KFyO;aEpCzBE,mBA7C+BC,4BzCK/B5J;MyCyCE4J,iCC+QFC,qCD9QgBD,oBAAqBA;;MF8FhBH;MAICA;MAIDA;MAILA;MA6BCA;MADjBA,uBAEkBA,YC1MdK,kBnDkBAlC,sBAsLJD,oHkD9G2C8B,4EAiBTA,0CG0RlCI,wGHrQ+BJ,uDGqQ/BI,2HHnN6BJ,0CAMMA;MAgBnCA;;IAWAA,C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBI3NEM;MACFA;cAASA;QAAaA,SAexBA;gCAdMA,eAAaA;QAAQA,QAc3BA;iCAbOA,eAAaA;QAAOA,WAAYA,OAavCA;gBAViBA;MACfA;QACyBA;QACXA;oCAAIA;kCAAZA,mBAAQA;;;UAGJA;;MAGVA,UACFA;K;;;;;;;;;;;;YCbYC;MAEyBA;;;;MAA3BA,EAARA;MAGqBA;MACrBA,iBAAYA;MAGZA,aACFA;K;eAOUC;MAEGA;MAGXA,kBAAaA;MAIbA,aACFA;K;;;;;;;;;;;;;;;;;;;;yBCNEC;MAPcA;aAOdA;MACEA,kCCNFA;MDKAA;IAEAA,C;;;;;;;;;;;;;oBxBoMAC;MACEA;QACEA,kBAAUA;4BAtMIA,cAAcA;QAwM5BA,kBAAUA,wGAC6BA;MAL3CA;IAOAA,C;gBAsFAC;MACEA;QACEA,kBAAUA;0BAnSIA,cAAcA;QAqS5BA,kBAAUA,mGAC6BA;WAClCA;QACLA,kBAAUA;MAPdA;IASAA,C;;;;;;;;;;;;;;;M0B/TAC;;;;yBd+DQA;MAONA,uBAAqBA,kCADLA,mBAAsBA,gCAOxCA;K;sBAMQC;MAINA;QACEA,sBAAUA;MAGFA;QAAUA,YAGtBA;MAFYA;QAAUA,OAAOA,iBAE7BA;MADEA,Oc5FFA,gBd4FuBA,sCACvBA;K;uBAOQC;MACNA;;iBACYA;UAkHKA,8BAlH4BA;UAAxBA,OAiHvBA,gBC7KAxB,8BD8EAwB;;QAjBQA,yCAAeA;UAAsBA;UAAXA,SAiBlCA;;QAhBQA;UAAoCA;UAAXA,SAgBjCA;;QAfQA,yCAAeA;UACNA;UAAXA,SAcNA;;QAZQA;UAAqCA,gCAAmBA;UAA9BA,SAYlCA;;QAXQA,yCAAeA;UACNA;UAAXA,SAUNA;;QAgGmBC,8BA7FgBD;QAP/BA,OAOJA,gBCjFAxB,+BD8EAwB;;QAnBEA;QAgBEA;;UACAA,sBAAUA,mBAAyBA,mDAAyBA;;UAjB9DA;;IAmBFA,C;oBAKmBE;MAGLA;;gBAAaA,qB/ClFlBA,mE+CkF6CA;MhDmIzCA,uCgDjIKA;;MlE0QlBC,2EA5OyED,OkE7B9DA,iClEyQXC,wDkExQOD;MAGMA;QACTA,iCAAeA,sBAAoBA;MAGrCA,cACFA;K;mBAGAE;MhD4HaA;gCgD1HHA;MlE4C2CA,2EAAUA,OkEtCtCA;;MARzBA;yBAyEmBA,yBrDrCaA,oEAA2BA,OqD3BxCA,iCrD2BaA,oCsDzIhC5B,+BDqGA4B;IAU0BA,C;uBAG1BC;yBA4DmBA,yBlEmInBC,qBA6DAC,oBkE1PUF,+DhDwD0BA,kCgDtDfA,iElE6PkDA,mCkE5PpDA,uFCvHnB7B,+BDkHA6B;IAM0BA,C;wBAS1BG;yBA6CmBA,yBlEmInBF,qBA6DAC,oBkE1OeC,qBADLA,iCAEKA,oChDuCqBA,kCgDtCfA,kElE6OkDA,mCkE5OpDA,wFCvInBhC,+BDiIAgC;IAO0BA,C;yBAwB1BC;MAGgBA,cADAA,gElE+IhBH,qBA6DAC,oBkEzMmBE,qBADHA,iCAEGA,oChDMiBA,kCgDJXA,mElE2M8CA,mCkE1MhDA;MATvBA,mBAcmBA,2CC9KnBjC,+BDgKAiC;IAU0BA,C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yCe1LFC;M3C4UxBA;;M2C3UIA,iCACIA,cAAmBA,2GAAsCA;K;;;;;;;;;;;;;;;;;;qBCDjEC;MAC4CA;;MAA5BA;MAEHA;;MACEA;;MACbA;QACEA,kBAAUA;WACLA;QACLA,kBAAUA,iDAA2CA;WAChDA;QACLA,kBAAUA,mDAA6CA;MAV3DA;IAYAA,C;;;;;;;;;;;;;;yBCkFAC;MAjD4BA;;c5D4MDnQ;a4DrJ2BmQ;MANtDA,qEA9CmBA,sCRwQnBtB,uSrD9TIjC,sBAsLJD;M6DtKAwD,EA2FEA;MAPFA;IAQAA,C;;;;;;;;;;;;;;;;;;;;;;wBCnIKC;M9D6MLzD,UCyE2B3M;;;M8DxJUoQ,OAAvBA,cAAQA,cD3HNA;MC2HqBA,OAAvBA,C9DwJaC,0B8DxJLD,cD1HNA,8BAA4BA,uC9DmBxCxD,2D8DjBDwD,SAAKA;MAERA,SACFA;K;;;;;;;;;;;;;;;MEPEE;;;;IACkCA,C;;;MCyBlCC;;;oCAEUA,wCAAqBA,6EAF/BA;IAGkBA,C;2CAQTC;MACkBA;MAAPA,SAOpBA;K;;;;;;;;;;;;;;;;;oBCpCAC;6BACiCA;uCACnBA,uBADmBA;QAE7BA,kBAAMA,0DAAoDA;MAH9DA;IAKAA,C;;;;;;;;;;oCC0BwBC;MAEsBA;;0CAA1BA;;QAGGA;MAF+BA,yBAA1BA;MACTA,iBAA4BA,oBAAtBA;qBtEmXL9G;QuE1ZpB8G,+BACoBA,sBAAoBA,QDyCtBA,YAAgCA,oBAA1BA;;;QR1BCA,2CQ2BcA,kDACOA,8BAA/BA,0CACIA;QR9BnBA;;MQuBEA,OAhCFA,qCAwCkBA,YAA2BA,oBAArBA,iCACxBA;K;qDAYwBC;MAGtBA,OAAOA,sBAAeA,yFACxBA;K;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MnBSAC;;;;IAAoDA,C;;;;;;;;;;;;;;;;;;eqB5BlDC;MACEA;qBCyjByBA,4BAESA,kDDxjBtCA;;QACEA;UAAuBA,sBAAUA;;UCujBGA;kCAASA;mBAATA;;UDrjBXA;QAApBA;;UACHA,sBAAUA,2DAAqDA;QAErDA;QACJA;6BAAMA;;QAEHA;QACXA;;MAYOA;MAILA;MAASA,4CAAsBA;QACjCA,sBAAUA;MAGZA,aACFA;K;;;kCE3FKC;MCwBcA;kDDtB0CA;aAM3DA;yCEsIcA;MAAZA,kBAAOA,cpCtIiBC;kBAHFA;QAAkBA;M/BuyB1CxO,4BAvV4ByO,oBAuV5BzO,iCmE7pBeuO,OAAKA;IFtItBA,C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MGyBEG;;;;IAC0BA,C;;;;;;;;;;;;;;;;;YCSvBC;MAKHA;IAEFA,C;aAiBOC;;;MZ+DgCA,WAAvBA,C9DwJad,0B8DxJLc;QYpDpBA,sBAAMA;MZoD6BA,YAAvBA,C9DwJad,0B8DxJLc;gCAxD2BC,kBAAtBA,C9DgNAC,2B0EzMPF,sBZOYA,kB/DhEPC,OA4MAE;Q2EnJGH,sBAAMA;MAMxBA;;MAyCOA;;QAEFA;UACGA,0BAAKA;UAAnBA,SAMNA;;;;QAREA;QAIEA;QACaA,wCAAMA;;MAErBA,OAnEcA,yBAmEAA;IAChBA,C;UAKKI;MAAwBA,yBAvI3BA,2BAuIqDA;K;mBAIhDC;MC/ILC;qC3FuiBAC,wB4FzPsBF,4BDrSDC,K3F6jByBE;M0Frb/BH;MCjJfC,6B3FuiBAC,wB4FzPsBF,wBDrSDC,K3F6jByBE;M0Fpb/BH;MACsBA,U9E+QjB5H;M8E9QpB4H;Q1Fqb0DI;M0Fpb1DJ,sCACFA;K;;;;;;;;aG1JIK;MAEFA,cANAA,MAOFA;K;aAGIC;MAEFA,cAZAA,MAaFA;K;WAEAC;MACEA,mBAAiBA;IAanBA,C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uBROQC;MAEGA;QACPA,OAAWA,+BAAqCA,+BAGpDA;MADEA,OAAOA,YAAeA,gDACxBA;K;eAOQC;MACNA;MAAOA,YAAHA;QACFA,sBAAUA,qDAAgDA;MAIxDA;QACEA,qCACAA,gCACAA;UACFA,sBAAUA;QAGZA,OAAWA,+BAAgCA,0BAAHA,wCAI5CA;;MADEA,OAAWA,qCACbA;K;kCAiCEC;;qCAVkCA,8BAGEA,8BAICA;MAGrCA;;IAoCAA,C;4BA0CAC;mCAJuCA;MAIvCA;;IAIAA,C;4BAwJAC;MACgBA;oCAAEA;;;aACDA,iBAAqBA,UAAHA;MACjBA,sBAAqBA,UAAHA;MACEA,0CAAfA;MAALA;;;;;MACDA,4BAAEA;MACYA;MACQA;MAPvCA,iDAOcA,sBACKA;MARnBA;;IAqFAA,C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iCShYQC;MAEKA;;MACUA;MACrBA;QAAyBA,iCAAoBA;;MAGzBA;MACKA;eAIhBA;MAAcA,oCAAkBA;QACxBA;iCAAIA;QAAnBA,wCAAeA;QAMJA;;QAHXA;QAGWA;;MAAbA;QACMA,wBAAkBA;UACpBA,+BAAUA;UACVA,wCAAeA;UACPA;;MAKZA;QACEA,+BAAUA;QACVA;;MAGFA,OAGFA,gDAFAA;K;;;;;;;;;MCjEAC;;;;IAA2BA,C;;;;4BCc3BC;;;MAGOA;QACHA,kBAAUA;MAIRA,oCAAmCA;QACrCA,kBAAUA,yEACUA;MAVxBA;IAYAA,C;;;;;;;;;;;;;;;;;;;;;;eC0EAC;MAQsBA;;0CACeA;;;MATrCA,2EAzDoCC,8BAGGA,8BAGAA,8BjCbjCC,ciCgBmBD,4BAUSA,8BASLA,8BAMIA,6BAuBjCD;IAYkBA,C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBCsDXG;MACPA;QAAkCA,WAGpCA;MADSA,SADGA,aAAYA;QAAMA,WAE9BA;MADEA,6BACFA;K;;;;;;;;;;;;;;;;;;;;;;oCCzF+BC;MACXA;;MACXA;QAAuBA,WAiB9BA;MAfuBA;MAAIA;oBACAA,oBAAzBA;QACMA,wDAA6BA;UAC/BA;MAKKA;MAEeA;MACLA,oBAAIA;MAFfA,kCAGaA,eAAIA;MvF3ClBA;MuF6CEA;MAPTA,OAAWA,qCvFtCJA,8CuF8CTA;K;2CAM6BC;MAE3BA;MAAUA;QAAwBA,WAmBpCA;MAfWA;QAAuBA,WAelCA;MAbqBA,gEAA0BA,kBAAQA;MACrCA;MACCA;MACFA;MACNA;QA8CPA,qBAAmBA,oBAAcA,iBAAWA,iBAAMA;QACzCA,sBAAMA;QADfA;0BAA0DA;QAEjDA,yCACJA,kBAAQA;QAjDcA;;;MAA7BA;QACcA,0DAAuBA,eAAKA;QACVA;QAAIA;QACdA;QACLA,oBAAIA;;0BAAKA;QAFdA,gCAGEA;QACCA;QAAMA;QAAeA;QAAIA,yCAAoBA;;MAE5DA,OAAWA,mDACbA;K;qCAI6BC;MAC3BA;MAASA,mBAAIA;QAAaA,WAa5BA;MAZWA,mBAAIA,cAAaA,iBAAMA;QAAMA,WAYxCA;MAVkBA,0DAAuBA,eAAKA;MAGnCA;MACmBA;MAAIA;MACRA;MACLA,oBAAIA;;wBAAKA;MAJ5BA,OAAWA,6BAEHA,0BAGQA,wDAEPA,mBACXA;K;iCAIWC;mBvFkRSA;MuFjRlBA;QAAkBA,QAUpBA;MAPMA;QACFA,2BAEoBA,wDAIxBA;;QAFIA,YAAqBA,gDAEzBA;K;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBzChGSC;MAIPA;MAKAA;QAYEA,OAAOA,6CAeXA;;;;c1DkC+CC;;;;MoG7E/CD,oCpGuCAE;M0DPSF;MAAPA,kBAAgBA,kD0C3BLA,oCAKQA,0EADSA,8DAFNA,wDACKA,0F1CkCXA,wCAACA,yBAAuCA,gEAC1DA;K;yBA6BQG;MAzFuDA,U1CoPpCC;;;M0CpPuBD,cAAKA;QAALA,eAAKA,C1CoP5BC;QoFjGbC;QAwDdC,OAxDwCD;Q1CzDZF,mB0CiH2BG,4BAxKOH,W1CkE9DA;;MAREA,O2C3HFA,gB3C2HuBA,kCADLA,uBAA0BA,4BAS5CA;K;0BAUQI;MACNA;MAAUA;QAAUA,YAItBA;Y1CgI2BH;M0CpPoCG;;MAAbA,cAAKA;QAiH3BA,OAjHsBA,UAAKA,C1CoP5BH,8B0CnIqBG,iBAGhDA;;MAFYA;QAAUA,OAsBtBA,YAA6CA,yBAtBNA,wDAEvCA;MADEA,O2CjJFA,gB3CiJuBA,0CACvBA;K;uBAOQC;MACNA;;MAYFA,SAZYA;QAASA,mBAYwBA,yBAZPA,4DAStCA;MARMA;QACFA,OAUJA,YAA6CA,yBjEoO7CxI,yBiE7OQwI,gE/CwFoCA,mC+CxFRA,yFAMpCA;MAJOA;QAA0BA,OAOjCA,YAA6CA,yBAPKA,sBAAKA,gEAIvDA;MAFEA,OAKFA,YAA6CA,yBjEoO7CxI,yBiExOMwI,gE/CmFsCA,mC+CnFZA,0FAChCA;K;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBoBpCSC;MACLA;yCAAsCA,kDAIbA,kGAOtBA;K;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBwBtHMC;MACIA;;MACfA;QAAsBA,eAExBA;MzCScC,8ByCVMD;MAAlBA,OzCfFA,wByCewCA,mCACxCA;K;;;;;;8BC+KeE;MACMA,WAIrBA;K;6BAGiBC;MACqBA,QAAOA,WAE7CA;K;;;+BCvM4BC;MACnBA;MACPA;QACEA;;;UAGkBA;;UAChBA,iCAFFA;;;;MAWcA;MAFhBA,OAAOA,oEAESA,0BAAYA,mDAEdA,oCACQA,gFAExBA;K;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eCTWC;MRiDiDA;sCAAxBA,CjFyMTC,0BiFzMiBD;MQ/C5CA;QAAsBA,eA0BxBA;YAzBMA;MAAJA;QAA6BA,SAyB/BA;MAnBoBA;MAClBA,oBAAkBA;MAiBlBA,QAAOA,gBACTA;K;UA0DKE;MAeYA;MAPfA,cAAUA;MAYVA,MAEFA;K;WAwDKC;MAeYA;MAPfA,cAAUA;MAYVA,MAEFA;K;;;;;;UCjOanC;MACHA,+BAA6BA;IAC/BA,C;;;;;;;8BxDgDNoC;;;mCACiCA;mCACAA;a7BuaLA;aAKEA;M6B3a5BA,csC8BEA,oBnE+tBJ5R,uFA0CA6R;M6BryBED,gBsC4BEA,oBnE+tBJ5R,uFA0CA6R;M6B1yBAD;IAQAA,C;;;;;iCyDmCQE;MACNA;;;QACEA,OAAOA,2CACSA,sDAoBpBA;MAjBuBA;MACRA;MACbA;QAKoCA,yBAAZA;QAAyCA,yBAAlBA;QACjCA;QADZA,OAtBJA,4BACwBA,aACCA,cACLA,SACGA,YACGA,YA2B1BA;;MANEA,OAlCIA,cAkCuBA,oBAAZA,+BAAyCA,oBAAlBA,oCACRA,kBAAhBA,mCACkBA,kBAAjBA,oCACOA,kBAAZA,+BACkBA,kBAAfA,kCACqBA,kBAAlBA,oCAClBA;K;;;;;;;;;;;;;;;YfZKC;MAEEA,oCAD4BA;MACnCA,6BAA2BA,0CAC7BA;K;gBAOOC;;iBACIA;MAATA;QAAsBA,OAAYA,gDAKpCA;MjFwJeA,uFiF3J2BA;cAC/BA;QAAYA;MACrBA,0BAA+CA,qCACjDA;K;eAMOC;MACLA;QAAiBA,YAGnBA;MADEA,kBACFA;K;iBA8JOC;MAEWA;;QAELA;;MAECA;eACFA;QAAaA,oBAUzBA;MAR2CA;MjFjB5BA,2DiFoBkBA,eAAWA,mBnGwB1CvK,uBAEyBA,mBAFzBA,iDmGxBAuK;QAC0BA,qBnG4BTA;MmG1BUA;MAC3BA,sCACFA;K;;;0BgB5MEC;M1CjDgBhG,UlD6OW1O;;;;;;M4F5L3B0U,uC1CrFIC,kBnDkBA/H,sBAsLJD,oHAtLIC,sBAsLJD,4BqDwIAkC,iGwC5Q0B6F,0CAGCA,0CAGDA,0CpDlEtBE,oBzCUAhI,sBAsLJD;M6FnHA+H;;IAMAA,C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WC3CEG;MACMA;QAAQA,UAIlBA;MADEA,sBAAUA;IACZA,C;yBAKIC;MACFA;MAAQA,0BlG6fYA;QkG7fUA,OAAOA,0BAGvCA;MAFuBA,gCAAbA;QAAkBA;MjGgWNA;QiGhWeA,UAErCA;MADuCA;6ClGsgB7BC;MkGtgB0CD;MAAlDA,OAAOA,iCACTA;K;kBC1EKE;MACDA;MAA+CA;QACzBA;;QADyBA;MAA/CA,SAC8CA;K;mBAO7CC;mBACMA;;MAATA;QAA6BA,YAK/BA;MAJOA,oBAAaA;QAAyBA,YAI7CA;MAHMA;QAA2CA,YAGjDA;MAFEA;QAA8BA,WAEhCA;MADEA,OAAOA,gDACTA;K;oBCRIC;MACEA;MvHqDJlJ,wCCmQAmJ,uBAEyBA,mBAFzBA,kEsHvTAD;ctH4TiBA;UsH3TkBA;MAEnCA,YACFA;K;mBAMIE;MAGFA;cnGkZoBA;QmGhZlBA;UACcA;UACZA;YACEA,cAAeA,4DAmBvBA;UAdMA;YAAuCA,sBAc7CA;UAbwBA;;MAIVA;aACZA;QAEmCA;QAEjCA;UAA0BA,gBAI9BA;QAHYA;;MAEVA,WACFA;K;uBC/CKC;MACHA;MAKEA;QACEA,sBAAMA;gCACqBA;QAC3BA,sBAAMA;sCAS2DA;MAArEA;QACEA,sBAAMA;IAGVA,C;;;gBCOEC;;aASWA;;MAATA,0BAASA;MATXA;IAUAA,C;6BAkJWC;MACTA;;MACiBA;mBACjBA;QACmBA;QACjBA;UAAqBA,aAGzBA;;K;;;;;;;;;;;;;;;;;;;;;;;;;;iBClKMC;MACNA;;QACEA,QAYJA;WAXeA;QAEXA,OCkRFC,mDDzQFD;WAReA;QAIXA,OC6QFC,iBD7QmBD,8DAIrBA;;QAFIA,8BErBFE,gCA2EAA,0BFpDFF;K;YAMOG;MtGSSA,GsGRRA;MACNA,iDtGGSA,gDsGHmBA,wBtGOcA,mCsGPCA,wBAK7CA;K;oBAGOC;MACDA;MAAOA;WAAMA;M1E4jBjBC;MlCzFmBD;MACZA;QAAeA,kBAA2BA;MACjCA;MACVA;QAAeA,kBAA2BA;M4GrehDA,eAAsCA,6BAAvBA,sDACjBA;K;;;;;;qBG5CUE;MAGUA;MAMhBA;QACgBA;MAMhBA,OAQFA,6BAPAA;K;eAw/BEC;MAEMA;QAAQA,UAElBA;MADEA,sBAAoBA;IACtBA,C;sBAIKC;MACHA;yBAAyBA,gBAAzBA;gBAEMA,mBAAmBA;UAAqBA;eAG5CA;UACWA;kBAALA;YAA2BA;;QrH9fnCA;QAOiBA;eA2BftL;QWnUWsL;;QlByIbtG,4EA5OyEsG,O4H04B9DA,mC5H9pBXtG,yD4H+pBOsG;erHreLtL;;QqHueAsL,sBAAMA,iBAAcA;;IAExBA,C;;;;;;;;;;;;;;;;;;;;;wCD/+BcC;mBpHsgBmCvL;MoHrgB7CuL;QpH0eeA;YA2BfvL;QoHngBauL,IpHmgBbvL;;QoHjgBauL,IpHigBbvL;IoH/fFuL,C;yCAEYC;;iBpH6fmCxL;gBoH5f1BwL;QACNA,IpH2fbxL;;QoHzfawL;YpHyfbxL;;;IoHtfFwL,C;;;;;;;;;;;;;;;;;;;;oBnEqCAC;;uCApCwBA,4CAI8BA,oFAIlCA,mCAIDA,mCAwBnBA;;;IAmCAA,C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aqEvFSC;MAKLA;;QAEQA;;QACVA,wBAFFA;gBAGMA;UAAJA;YAAsBA,SAqB1BA;UApBIA;;UAJFA;;MASIA,iBAAOA;QAAiBA,QAAOA,SAerCA;;MAZYA,+BAAkBA;QAE1BA,oBADWA,mBAAiBA,aAWhCA;;QAReA;iBAGUA;;QACdA;yCAAIA;iBAAJA;QAAPA;QAEAA,6CADmCA,kDAGvCA;;K;;;iBC/FO3E;MAqGLA,OApGmBA,wDAoGAA,kBAAoBA,yDACzCA;K;eAMO4E;MACCA;QAASA,aAKjBA;MAJQA;QAAQA,YAIhBA;MAHQA;QAAQA,YAGhBA;MADEA,OAAYA,mCACdA;K;mBAOOC;MAAgCA,kBAAOA;MAAPA,O3G3E5BA,2C2G2EiEA;K;;;;;;;;;;;;;;;;;;MCRnCC;;;;;;yBAA/BA;MAA+BA,4CAA6BA,yCAuB9DA;K;yBAGEC;MAA+BA,4CAA6BA,yCAwC9DA;K;8BAYEC;MAAoCA,4CAA6BA,8CAyBnEA;K;+BAcEC;MAAqCA,4CAA6BA,+CAoBpEA;K;2BAUKC;MACLA,oCAAUA,sBAASA;QACrBA,OAAWA,sBAYfA;WAXaA,6CAAmBA;QAC5BA,OAAWA,iCAUfA;WATaA;QACTA,OAAWA,kCAQfA;MAFMA;QAA0BA,OAAYA,iBAAQA,kBAEpDA;MADEA,OAAWA,sBACbA;K;iCAMaC;MACXA;;QACSA;QAAPA,SAIJA;;QAHIA,8BAFFA;UAGEA,OCzRJA,oBAVoBC,gDDqSpBD;;UALEA;;IAKFA,C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBE5RYE;MACKA;;M7EqvlCjBC,8E6E/ulCiBD,gBAAWA,oCAMzBA,SAAKA;MAiBiBA,2D7Euz7BAA,S6Evz7ByBA;M/Ef3CA;iBEs07BkBA;oE6Etz7BLA;MAEpBA,iBxEXgCA,SwEYlCA;K;;;;;;;;;;;;;;MCTME;;0BADmBA;MACnBA,yDAAeA,gDACHA,sCAAWA;K;;;;;;;;;;;;;;;;;uBvEX3BC;MAE0EA;;MAF1EA;;;IAwBAA,C;;;;;;;;;;;;;;;;;;;;;;;;;;;MwEnDAC;;;;;;IAAiBA,C;;;;;;;;EzH+TcC;SAFjBC;MAAaA,yBAAsBA;K;kBAEzCD;MAAYA,4CAA+BA;K;gBAE5CE;MAAcA,yBNuUGA,IADRA,4CMtUiDA;K;qBAqBxDC;MAAeA,iCAAoBA;K;;;gBAQrCC;MAAcA,uBAAgCA;K;kBAU7CC;MAAYA,iCAAwCA;K;qBAEnDC;MAAeA,sBAAIA;K;;;;SAWdC;MAAaA,oBAAsBA;K;gBAG1CC;MAAcA,aAAMA;K;kBAEnBC;MAAYA,QAACA;K;qBAKZC;MAAeA,sBAAIA;K;;;;;kBAgCpBC;MAAYA,QAACA;K;qBAEZC;MAAeA,0BAAQA;K;gBAGzBC;MAAcA,uBAA+BA;K;;;;;;gBAwB7CC;MACiCA,0BAClCA;MAAJA;QAAyBA,OAAaA,8CAExCA;MADEA,oCAAkCA,+BACpCA;K;;;;;;;WQrUKC;mDAE4BA;;QAP7BA,kBAAMA;;IAQVA,C;gBAEEC;MAAQA;;QAVNA,kBAAMA;mBAakBA;MAA1BA;QACEA,sBAAUA;MAEZA,mCACFA;K;cAEKC;MAAMA;mDAMqCA;;QAzB5CA,kBAAMA;mBAsBiBA;MAAzBA;QACEA,sBAAUA;;IAGdA,C;iBAEKC;MAASA;4DAGRA;;QA/BFA,kBAAMA;mBA8BuCA;MAApCA;gCAIoBA;MAC1BA;MACKA;MACLA,uCAAmBA;MACnBA;IACPA,C;kBAUEC;;QAjDEA,kBAAMA;kBAmDJA;QAAaA,sBAAMA;MACvBA,qBACFA;K;cAEKC;MAAMA;;QAvDPA,kBAAMA;MAyDRA,wBAAyBA,SAAzBA;QACUA;;UAENA,WAINA;;MADEA,YACFA;K;cAiDKC;MACCA;4DAEQA;kBAFCA;;QAlHXA,kBAAMA;MAoHRA;;QAEMA;QAFNA,6BAEkBA,WAAWA,kBAAUA;;;IAGzCA,C;eAMKC;MACCA;;oBAAWA;MACfA;QAIEA,iBAAEA;oBACOA;UAAeA,sBAAUA;;IAEtCA,C;aAEYzN;;MACVA,OlBqJFA,kEkBrJ4CA,WlBqJ5CA,kEkBpJAA;K;YAEO0N;MACUA;iCAAUA;;MACzBA,wBAAyBA,SAAzBA;QACEA,wBAAiBA;MAEnBA,2BACFA;K;YANOC;;K;cAsCLC;MACIA;MAAQA;;wBACMA;MAClBA;QAIUA,sCAAeA;oBACdA;UAAkBA,sBAAUA;;MAEvCA,YACFA;K;yBAEEC;;;;;oBACeA;MACfA;0BAIWA;kCAALA;UAAeA,cAKvBA;oBAJaA;UAAeA,sBAAUA;;MAEpCA;QAAoBA,OAAOA,eAE7BA;MADEA,sBAA2BA;IAC7BA,C;kBAXEC;;K;iBAoDAC;MACWA;;MAAXA,eAAWA,OACbA;K;eAEQC;uCAGmBA;QACvBA,sBAAUA,qCAA2BA;uCAMZA;QACvBA,sBAAUA,uCAA6BA;MAG3CA;QAAkBA,OAAUA,wDAG9BA;MAFEA,wDAAWA,+BAEbA;K;eAOMC;kBACAA;QAAYA,eAAWA,GAE7BA;MADEA,sBAA2BA;IAC7BA,C;cAEMC;uBACAA;MAAJA;QAAgBA,eAAWA,QAE7BA;MADEA,sBAA2BA;IAC7BA,C;gBAEMC;uBACAA;MAAJA;QAA4BA;;QAAXA,eAAWA,GAG9BA;;MAFEA;QAAiBA,sBAA2BA;MAC5CA,sBAA2BA;IAC7BA,C;gBASKC;;;gCAWCA;;QAzTFA,kBAAMA;MAiTGA,iDAAiCA;MAC/BA;MACbA;QAAiBA,MAiCnBA;MAhCaA;MAMTA;MAMkCA;;QAClCA,sBAA2BA;MAE7BA;QAIEA;UAIcA;;QAIdA;UACcA;IAIlBA,C;gBAtCKC;;K;iBAwCAC;MAASA;;;QAtVVA,kBAAMA;MAwVGA,iDAAiCA;MAC5CA;;IAIFA,C;oBAEKC;MAAYA;4DAGXA;;QA5VFA,kBAAMA;mBA2VoCA;MAAjCA;MAIQA;MAIDA;MAFlBA;QACcA;QAESA;QAChBA;QACLA;UACOA;UACAA;;;QAIcA;QAEhBA;QACAA;QACAA;;IAETA,C;gBAsFKC;MACHA;8BAAoBA,SAApBA;QACUA;UAAcA,WAG1BA;MADEA,YACFA;K;iBAESC;MAAWA,4BAAWA;K;gBAIxBC;MAAcA,OkH9iBJA,uDlH8iB+BA;K;aAYzCC;MAAWA,OAAIA,0FAAiBA;K;kBAEvBxY;MAAYA,OAwG5BA,sCAEyBA,SA1GOA,+BAwGhCA,4BAxGsDA;K;kBAE9CyY;MAAYA,OAAWA,qCAAoBA;K;gBAE3CC;MAAUA,sBAAiCA;K;gBAE/CA;MAAMA;;QAveNA,kBAAMA;;QA0eNA,sBAAUA;MAGZA;QACEA,sBAAUA;;IAKdA,C;YAEWC;MACLA;;QAAeA,sBAAMA;2BACZA;QAAqBA,sBAAMA;MACxCA,sBACFA;K;eAEcC;MAERA;mDAEiCA;;QArgBnCA,kBAAMA;;QAmgBWA,sBAAMA;2BACZA;QAAqBA,sBAAMA;;IAE1CA,C;;;;;;;iBA8EMC;MAAWA,oBAAQA;K;gBAEpBC;;kBACUA;oBAAUA;eAKnBA;QACFA,sBAAMA;gBAGJA;MAAJA;QACEA;QACAA,YAKJA;;MAHEA,qBAAWA;;MAEXA,WACFA;K;kBA1BEC;;K;;;;amHxlBEC;MACFA;;QACEA;UACEA,mBAcNA;aAXIA;QAEiBA;QAAfA,yDASNA;;MALiCA;;QAC7BA,QAIJA;MADEA,sBAAUA;IACZA,C;aAEIC;MACFA;QAGEA;UACEA,2BAYNA;aAVSA;QAMLA,mCAIJA;MADEA,sBAAUA;IACZA,C;qBAkEOC;MAAaA;MAElBA;QACEA,sBAAUA;MAIRA;wDAAyBA;QAC3BA,aAGJA;MAOMA;MAAJA;QAEEA,kBAAUA;;MAEeA;gCAAKA;oBAGCA;MAFFA;gCAAKA;uBAALA;gBAC3BA;MAAJA;;QAIoBA;;MAlBpBA,sDACFA;K;gBAqBOC;MACLA;QACEA,aAIJA;;QAFIA,oBAEJA;K;kBAEQC;MACFA;;MAGJA;QAAsBA,2BA6BxBA;MAvB4CA;MAC/BA;MAI4BA;MAUvBA;MAOhBA,kHACFA;K;UAwBkBC;MAChBA;MAGAA;QAAiBA,QAOnBA;MANEA;QAAgBA,aAMlBA;MALEA;QACEA,qBAIJA;;QAFIA,qBAEJA;K;WAIaC;MAGXA;QACEA;UACEA,2BAINA;MADEA,OAAOA,iCACTA;K;iBAEIC;MAEFA,4DAEMA,iCACRA;K;iBAEIC;MACEA;MACJA;QAEEA,mBAgBJA;MAdEA;QAGEA;UACEA,2BAUNA;aARSA;QAELA,0BAMJA;MAFEA,sBAAUA,0DAC6BA,uBAA0BA;IACnEA,C;oBAaIC;MAGFA,+CAGFA;K;yBASIC;MACFA;;QACMA;;;;;MADNA,SAOFA;K;4BAEIC;MACFA;QAA+BA,sBAAMA;MACrCA,OAAOA,wCACTA;K;wBAEIC;MACFA,0CASFA;K;qBAqCSC;MAAeA,qBAAGA;K;;;;;qBAoPlBC;MAAeA,qBAAGA;K;;;;qBAOlBC;MAAeA,wBAAMA;K;;;kBlH7qB1BC;MAEFA;QAAeA,sBAAMA;2BAKRA;QAAQA,kBAAMA;MAJ3BA,iCACFA;K;mBAEIC;2BACWA;QAAQA,sBAAMA;MAC3BA,iCACFA;K;kBAEgBC;MAAUA;MfqxC1BA;QAAsBA,kBAAMA;iBelxCMA;MAAhCA;QACEA,sBAAUA;MAEZA,OPiCFC,wDOhCAD;K;kBAPgBE;;K;qBASVC;MACJA;qCAAgCA;QAC9BA,sBAAUA,mCAAkCA;mBAE7BA;6BAAgBA;QAAQA,YAQ3CA;MALQA,6CADNA;QACMA,2CAAqCA;UACvCA,YAINA;MADEA,OPjBIA,kCOkBNA;K;UAEgBC;MACdA;QAAsBA,sBAAUA;MAChCA,uBACFA;K;gBAEKC;6BAEqBA;qBACNA;MAAlBA;QAA0BA,YAE5BA;MADEA,iBAAgBA,4CAClBA;K;4BAUOC;MAELA,OAAOA,gDAA0CA,6CAASA,0CAC5DA;K;oBAEOC;MAGMA,gDAAyCA;MACpDA,OAAOA,oDACTA;K;oBAsBOC;MAGYA,uDAAiCA;MAElDA,OAAOA,gEACTA;K;kBA8BKC;MAAUA;MAKTA;uCAH0BA;QAC5BA,sBAAUA,qCAAgCA;MAE5CA;kCAE0BA;+BAETA;UAAQA,YAI3BA;QAHIA,sDAGJA;;MADEA,OAAOA,qDACTA;K;kBAbKC;;K;iBAeEC;MAELA;2BAAiCA;MAEjCA;QAAoBA,sBAAUA;MAC9BA;QAA2BA,sBAAUA;6BACtBA;QAAQA,sBAAUA;MACjCA,+CACFA;K;iBAROC;;K;YAkHAC;MAMDA;;yBAAOA;MAAXA;QAAwBA,aAiB1BA;MAhBkBA;QAGDA;QACbA;UAAiCA,SAYrCA;;QAFMA;MAJ6BA;MAAlBA,oDAEFA;MAEbA;QAAkDA,aAEpDA;MADEA,8CACFA;K;UA0DgBC;MACdA;;QAAgBA,SAelBA;iCAdyBA;QAAaA,eActCA;MAbEA;QAEEA,uBAAYA;MAIdA;QACEA;UAA6BA;QAEzBA;QAAJA;UAAgBA;QAChBA;;MAEFA,aACFA;K;eAEOC;kCACoBA;MACzBA;QAAgBA,eAElBA;MADEA,OAAOA,oCACTA;K;gBAEOC;MACDA;MAAQA;2BAAMA;8BAAOA;MACzBA;QAAgBA,eAElBA;MADEA,kBAAcA,qBAChBA;K;eAMIC;MAAOA;uCAGqBA;QAC5BA,sBAAUA,qCAAgCA;MAG1CA;eAWJA;K;eAlBIC;;K;mBAoBAC;MAAWA;MAEbA;wBACUA;4CAG2BA;QACnCA,sBAAUA,qCAAgCA;kBAIxBA;mBAAcA;MAAhCA;QACeA;MAEfA,2CAMJA;K;mBApBIC;;K;gBAsBCC;Mfm3BLA;QAAoBA,kBAAMA;Me92BxBA,OAAOA,6CACTA;K;gBAYOC;MAAcA,eAAIA;K;kBAMjBC;MAGFA;wBACgBA,0BAApBA;;QAEoBA;;;MAGFA;MAEGA;MAArBA,gDACFA;K;qBAESC;MAAeA,wBAAMA;K;gBAEtBC;MAAUA,sBAA4BA;K;YAE9BC;MACVA;2BACSA;QAAqBA,sBAAMA;MACxCA,sBACFA;K;;;;;kBrBzcgBC;MAAgBA,aAAmBA;;MAAvBA,OAgD5BA,mBAhD2DA,qBAgD3DA,6BAhDgCA,WAgDhCA,6BAhDoEA;K;gBAuB5DC;MAAkBA,aAARA;8BAAcA;K;iBACvBC;MAAmBA,aAARA;+BAAeA;K;eAO7BC;MAAuBA,aAAdA;kCAAcA,cAANA,iBAAUA;K;cAC3BC;MAAqBA,aAAbA;kCAAaA,cAALA,gBAASA;K;gBAG1BC;MAA0BA,wCAAuBA;K;gBAQ/CC;MAAcA,iCAAkBA;K;;EAMpBC;gBAAdA;MAAcA,gCAAkBA;K;iBAC/BC;MAAWA,OAAgBA,sBAARA,IAARA,uBAAoBA;K;;;;;;cAqMzBC;MAFZA;MAE8BA,yBAA4BA,UAF1DA,6BAEkCA,mCAFlCA,4BAEkEA;K;mBAI7DC;MAA2BA,sCAAwBA;K;YAE7CC;MAAkBA,OAAaA,sBAAbA,4BAAiBA;K;cAa5CC;MAAsBA,OAAoBA,sBAApBA,8BAAwBA;K;eAM3CC;MACHA,0BAAgBA;IAGlBA,C;cAEgBC;MAAYA;MAAJA,mCAAgCA,IAARA,uBAApBA,4BAAiCA;K;gBAIrDC;MAAkBA,aAARA;8BAAcA;K;iBAEvBC;MAAmBA,aAARA;+BAAeA;K;;;YAXjBC;;MACZA;MAAUA;MAAZA,cAAMA,sBAAYA;IACnBA,C;gBAFeC;;K;;ECtNQC;gBADlBC;MAAUA,mBAAQA,OAAMA;K;YACnBD;MAAaA,qDAAmBA,uBAAEA;K;;;;kBCrC/BjU;MAAYA;aAqS5BA,0BAEyBA,yBAvSOA,uBAqShCA,wCArSqDA;K;iBAY5CmU;MAAWA,kCAAWA;K;eAEzBC;MACAA;QAAaA,sBAA2BA;MAC5CA,OAAOA,sBACTA;K;cAEMC;MACJA;MAAIA;QAAaA,sBAA2BA;MAC5CA,OAAOA,qBAAUA,4BACnBA;K;gBAQKC;MACeA;;MAClBA;QACMA;UAAyBA,WAMjCA;QALuBA;UACjBA,sBAAUA;;MAGdA,YACFA;K;YAwEOC;MACaA;;mBmB6RAA;QnB3RhBA;UAAiBA,SAwBrBA;QAvBsBA;QACCA;UACjBA,sBAAUA;QAGZA;UOiaaA,0BP/ZEA;UACMA;YACjBA,sBAAUA;;QAGdA,sCAWJA;;QARIA;UOuZaA,UPtZEA;UACMA;YACjBA,sBAAUA;;QAGdA,sCAEJA;;K;YA3BOC;;K;aA+BKhP;;MAA0BA,OA4OtCA,2EA5OyEA,WA4OzEA,+EA5O2EA;K;cAezEiP;MACIA;MAAQA;;MACMA;MAClBA;QACUA,8BAAeA;QACJA;UACjBA,sBAAUA;;MAGdA,YACFA;K;uBAUQC;MAGQA;;MAAIA,qCAASA;MAI3BA,YAAoBA,6BAApBA;QACEA,uCAAYA;MAEdA,aACFA;K;cAXQC;;K;aAaDC;MACeA;;MACpBA,YAAoBA,6BAApBA;QACEA,gBAAWA;MAEbA,aACFA;K;;;mBAmBQC;MACiBA,mCAAVA;iBACTA;MAAJA;QAAmDA,cAErDA;MADEA,SACFA;K;qBAEQC;MACiBA,mCAAVA;iBACTA;MAAJA;QAAqBA,cAEvBA;MADEA,SACFA;K;gBAEQC;MACiBA;uCAAVA;iBACTA;MAAJA;QAAsBA,QAKxBA;eAJMA;MAAJA;QACEA,mBAGJA;MADSA;wBAAaA;MAApBA,cACFA;K;iBAEEC;MACgBA;;MAChBA;QAA8BA;QAAbA;0BAAUA;QAAVA;;QAAjBA;;QACEA,sBAAUA;MAEZA,OAAOA,uDACTA;K;YAWYC;MAAIA;MACHA;gBACPA;gBAGWA;;MAHfA;QACEA,OAAWA,wBAAmBA,4CAAnBA,eAMfA;;QAHIA;UAA2BA,YAG/BA;QAFIA,OAAWA,wBAAmBA,4CAAnBA,eAEfA;;K;;;iBAoCMC;MAAWA,+BAAQA;K;gBAGpBC;MACoBA;kBAAVA;;kBAAUA;eACnBA;QACFA,sBAAUA;gBAERA;MAAJA;QACEA;QACAA,YAKJA;;MAHEA,8BAAWA;;MAEXA,WACFA;K;6BAtBEC;;K;;;;kBAwCcC;MAAgBA;MAAJA,OAwB5BA,qBAxB+DA,sBAAVA,4BAAoBA,KAwBzEA,6BAxBgCA,WAwBhCA,+BAxB4EA;K;gBAGpEC;MAAUA,OAAUA,qBAAVA,sBAAgBA;K;iBACzBC;MAAWA,OAAUA,sBAAVA,sBAAiBA;K;eAG/BC;MAASA,sBAAaA,mBAAVA,uBAAgBA;K;cAC5BC;MAAQA,sBAAaA,kBAAVA,uBAAeA;K;;;;gBAkB3BC;;kBACCA;;QACFA,8BAAWA,gBAAaA;QACxBA,WAIJA;;MAFEA;MACAA,YACFA;K;iBAEMC;MAAWA,+BAAQA;K;6BAfvBC;;K;;EA6BwBC;gBAAlBA;MAAUA,qCAAcA;K;iBAC9BC;MAAwBA,sBAAGA,sCAAyBA;K;;EAqBtDC;kBAVgBA;MAAYA,2BAA+BA,sBAAVA,4BAAoBA,KAUrEA,qCAVwEA;K;aAG5DpQ;MAlEZA;MAkEsCA,iEAAiCA,WAlEvEA,8DAkEyEA;K;;;gBASpEqQ;MACHA;oBAAOA,qBACDA,KADCA;kCACDA,UAAaA;UACfA,WAINA;MADEA,YACFA;K;iBAEMC;MAAWA,OAAUA,IAAVA,wBAAiBA;K;;;kBAWlBC;MAYhBA;MAZ4BA,4BAAmCA,sBAAVA,4BAAoBA,MASnCC,kBAGtCD,6BAZgCA,WAYhCA,+BAZ4EA;K;;;iBActEE;MAAWA,+BAAQA;K;gBAEpBC;MACHA;eAAIA;QAA2BA,YAcjCA;qBAP6BA,sBAAHA,KANhBA;QACNA;QACIA;UAGFA;UACAA,4BAA0CA,kBAAtBA,UAAaA;;UAEjCA,YAKNA;;MAFEA,8BAA6BA,KAAlBA;MACXA,WACFA;K;2BAtBYC;;K;6BACVC;;K;;;EAkMFC;kBAVgBA;MACdA,+BAA0CA,sBAAVA,4BAAoBA,KAStDA,yCARAA;K;;;gBAUKC;MACHA;gBAAKA;aACHA;uBACOA,sBACAA,KADAA;qCACAA,UAAaA;YAAUA,WAIlCA;;MADEA,OAAOA,4BACTA;K;iBAEMC;MAAWA,OAAUA,IAAVA,wBAAiBA;K;;;gBA0F7BC;MAAcA,YAAKA;K;iBAClBC;MAAWA,WAAIA;K;;;;gBsI/uBjBC;MACFA,sBAAUA;IAEZA,C;;;eAwEcC;;;MACZA,sBAAUA;IACZA,C;gBAGIC;MACFA,sBAAUA;IAEZA,C;;;EA6J0BC;gBAAlBA;MAAUA,qCAAcA;K;iBAE9BC;MAAkDA,aAA1BA;;gCAA0BA,8BAAmBA;K;;;kBvG9O/DC;qBAEFA;MAAJA;QAAkBA,WAKpBA;MAH8CA,gDAANA;MAMhBC;MAJtBD,WACFA;K;gBAGAC;MAAcA,4BAAUA,yBAAQA;K;SwGoFlBC;MAAEA;oBAAkDA;MAAvCA,wCAAmBA,0BAAeA,iBAAKA;K;;;crI9DtDC;MAAsBA;MAAJA,OAAIA,2DAA4BA;K;iBACrDC;MAAWA,kCAAWA;K;gBAIxBC;MAAcA,OAAQA,2BAAiBA;K;cAQ5CC;MAAsBA,yCAAoBA;K;aAYhCC;MACWA;MAChBA,kBAAQA;MAIbA,aACFA;K;;;;YALeC;;gBACCA,sBAAUA,8BAAKA;MAC3BA,8BAAaA,WAAaA;IAC3BA,C;gBAHYC;;K;;;gBAgCPC;MAAUA,+BAA4BA;K;mBAOzCC;MACHA;QAAoBA,YAGtBA;MAFEA;QAAwBA,YAE1BA;MADEA,WAAwBA,8BAC1BA;K;YAEWC;MACJA;QAAkBA,WAEzBA;MADEA,yBACFA;K;cAGAC;MAAeA,WAAiBA,WAAWA,4BAAIA;K;eAE1CC;;;;;oBAKsBA,gBAEhBA,kBAFTA;kBACYA;QACVA,cAAOA;;IAEXA,C;cAEgBC;MACdA,OA4BFA,oCA5BaA,sBA4BbA,sCA3BAA;K;;;kBA6BgBC;MgByhBhBjhB,ahBzhB4BihB,iBgBibIA;MhBjbJA,iCgB2hBHjhB,SA1GOihB,yBAwGhCjhB,4BhBzhBoDihB;K;gBAE5CC;MAAUA,8CAAgBA,OAAMA;K;;;qBCnKxCC;MAOEA;QAIIA;IAKNA,C;gBAKOC;MACWA,4CAWCA;MARjBA,OAASA,4CACXA;K;;;;;;;;;;;;;EC0nB2BC;YAAZA;MAAMA,6CAA6BA,mBAAqBA;K;;;;sBAy0BvEC;;gCAEyDA,WACnDA;MAAJA;QAAmBA,WAmBrBA;MAhBqCA;gBAD/BA;MAAJA;;gBAGIA;MAAJA;;gBAGIA;MAAJA;;gBAGIA;MAAJA;;gBAGIA;MAAJA;;MAIAA,aACFA;K;;;gBAqNOC;mBACDA;MAAJA;QAAqBA,+BAA4BA,8BAEnDA;MADEA,kEACFA;K;;;;gBAaOC;;;kBACDA;MAAJA;QAAqBA,+BAA4BA,+BAMnDA;gBALMA;MAAJA;QACEA,4BAA0DA,qCAI9DA;MAFEA,4CACoDA,qCACtDA;K;;;;gBAQOC;mBAAcA;een0CDA,wCfm0CgDA;K;;;;YAsBpEC;MACYA;iBAEJA;qCACyCA;MAG/CA,YACFA;K;;;;gBA2JOC;;iBACDA;MAAJA;QAAoBA,SAQtBA;eAL+BA;;MAI7BA,WAAOA,oCACTA;K;;;;gBA6kBOC;MAMcA,uBAFfA;;MAEJA,6EACFA;K;;;;;;;;;;;gBAoBOC;sBAGDA;MAAJA;QAAkBA,yCAEpBA;MADEA,qBAAmBA,4BACrBA;K;;;SAsBcC;MACZA;MADcA;oBAMhBA;MALEA;QAA4BA,WAK9BA;;QAJ8BA,YAI9BA;MAHEA,YAA6BA,gBAAaA,eAChBA,kBAAeA,iBACfA,oBAAiBA,UAC7CA;K;kBAEQC;;iBAEFA;MAAJA;QAGgCA,mDAAeA;;QAIhBA,kEAICA;MAEhCA,2BAAqCA,gCAAeA,gBACtDA;K;gBAEAC;yBACiBA;;QAGGA,eAHiBA;MAGnCA,6BAAkBA,uCAv8DIA,IADRA,6CA08DhBA;K;;;gBAucOC;MAAcA,mBAAOA;K;;;;;EA2ESC;gBAA9BA;MAAcA,kCAAgBA,SAAQA;K;;;;;EA2VKC;gBAA3CA;MAAcA,uDAA0CA,SAAQA;K;;E6B/jGvEC;gBAhVQC;MAAUA,+BAAOA;K;iBAChBC;MAAWA,qCAAYA;K;cAGhBF;MACdA,4CAAWA,sBA2UbA,uCA1UAA;K;gBAEgBG;MACHA;MAAXA,OAAWA,gCAAqBA,iBAAMA,8CAA3BA,4BACbA;K;mBAEKC;MACHA;;uBACgBA;QACdA;UAAqBA,YASzBA;QARIA,OAAOA,yCAQXA;aAPSA;oBACMA;QACXA;UAAkBA,YAKtBA;QAJIA,OAAOA,sCAIXA;;QAFIA,OAAOA,gCAEXA;K;2BAEKC;;oBACQA;MACXA;QAAkBA,YAGpBA;MADEA,OAAOA,gCAmNAA,8BADIA,gDAjNbA;K;cAMKC;gDACHA,cAAMA,aAAQA;IAGhBA,C;YAEWC;MACTA;;uBACgBA;QACdA;UAAqBA,YAWzBA;QAV6BA;wCACqBA;QAA9CA,SASJA;aARSA;oBACMA;QACXA;UAAkBA,YAMtBA;QAL6BA;QAGlBA,gCAFuCA;QAA9CA,SAIJA;;QAFIA,+BAEJA;K;mBAEEC;;oBACWA;MACXA;QAAkBA,WAMpBA;MA8KSA,uCADIA;MAjLCA;MACZA;QAAeA,WAGjBA;MADEA,aAAmBA,wBACrBA;K;eAEcC;;;MACKA;MAGkBA;MAHnCA;uBACgBA;QAEdA,8DADqBA,wBAAqBA;aAErCA;oBACMA;QAEXA,2DADkBA,qBAAeA;;QAGjCA;IAEJA,C;mBAEKC;;;MAGgCA;MAGYA;kBALpCA;MACXA;QAAiCA,YAAfA;MACPA;MACEA;MACbA;QAEEA,oCADyBA;;QAGbA;QACZA;gBAEEA,OAAKA;;sBAEoBA;;IAI/BA,C;mBAEEC;;;MACgBA;wBACNA;MADNA;QAAkBA,OAAWA,oBAInCA;MAHYA;MACNA;MACJA,YACFA;K;cAEEC;MACAA;;QACEA,OAAOA,+CAAsBA,2BAMjCA;WALSA;QACLA,OAAOA,+CAAsBA,wBAIjCA;;QAFIA,OAAOA,2BAEXA;K;sBAEEC;;oBACWA;MACXA;QAAkBA,WAcpBA;MAbaA;MACEA;MACDA;MACZA;QAAeA,WAUjBA;oCANcA;MAAZA;;QAGEA;MAEFA,4BACFA;K;aAEKC;MACHA;eAAIA;aACFA,6BAAWA,0BAAQA,0BAAQA,eAASA;aACpCA;QACAA;;IAEJA,C;eAEKC;MACeA;;kBAAOA;2BACLA;aACpBA;QAGEA,kBAAOA,qBAAKA;mCACSA;UACnBA,sBAAUA;mBAEAA;;IAEhBA,C;sCAEKC;;;MAC2CA;MAEGA;MAFxBA;MACzBA;QACEA,mCAA2BA;;YAEtBA;IAETA,C;yCAEEC;MACAA;;QAAmBA,WAMrBA;MAL2BA;MACzBA;QAAkBA,WAIpBA;MAHEA;MACAA;MACAA,4BACFA;K;iBAEKC;UAKHA,sBAAkBA;IACpBA,C;sBAGkBC;;;eAgJlBA,wBA/IiDA,8BAAKA;eAChDA;aACFA,eAASA;;oBAEgBA;YACpBA;aACLA,aAAaA;;;MAGfA;MACAA,WACFA;K;+BAGKC;;uBAC+BA;mBACJA;MAC9BA;oCACiBA;aACfA;;gBAESA;MAEXA;oCACiBA;aACfA;;YAEKA;;MAGPA;IACFA,C;+BAaIC;MAIFA,OAAsCA,gCACxCA;K;+BAOIC;MACFA;;QAAoBA,SAOtBA;;MALEA;QAEWA,iBAALA,GAAKA;UAAuBA,QAGpCA;MADEA,SACFA;K;gBAEOC;MAAcA,OAAQA,2BAAiBA;K;qBAE5BC;MAChBA,iBACFA;K;uBAEwBC;MACtBA,iBACFA;K;sBAEKC;MAAcA;;IAGnBA,C;yBAEKC;MAKsBC;IAH3BD,C;2BAEKC;MAEHA,+CACFA;K;qBAEAC;MAQiBA;;MAAfA;MACAA;MACAA,YACFA;K;;;;YAxRwCC;MAAcA;MAAJA,sDAAKA,aAAKA;K;gBAApBC;;K;;;YA6BxBC;;;MACRA,gBAACA,8BAAOA;IACbA,C;gBAFaC;;K;;;;gBA4SRC;MAAUA,4BAAKA,oBAAOA;K;iBACrBC;MAAWA,4BAAKA,0BAAYA;K;kBAErBC;MA2BhBA,aA1ByCA;kDAAWA,iBA0BpDA;MAtBSC,EAuBPC,WAAaA;MA3BbF,SACFA;K;gBAEKC;MACHA,WAAOA,wCACTA;K;;;iBAyBME;MAAWA,gCAAQA;K;gBAEpBC;;kBACmBA;eAAlBA,sBAAuBA;QACzBA,sBAAUA;;kBACDA;QAAJA;UACLA;UACAA,YAMJA;;UAJIA;eACAA,cAAQA,MAAMA;UACdA,WAEJA;;;K;8BAnBEC;;K;;;;Y3BnBeC;MAAOA,WAA0BA,UAAUA;K;;;;YAExDA;MAAmBA,WAA6BA,sBAAsBA;K;;;;YAEtEA;MAAgBA,WAAeA,iBAAiBA,4BAAIA;K;;;;gBKvWjDC;MACHA,uBAASA,qBAAoCA,oBAAcA;K;8BAW3DC;;kBACEA;MAAJA;QAAiCA,SAGnCA;MAF+BA,UAeoBA;MAfjDA,YAAOA,wDACHA,oEACNA;K;gCAEIC;;kBACEA;MAAJA;QAAmCA,SAQrCA;MAFiCA,UAIkBA;MAJjDA,YAAOA,0DAAqCA,4EAE9CA;K;kBAwCYC;MACLA;MP8rCPA;QAAsBA,kBAAMA;MO5rCtBA,QAFgDA;MAEpDA;QAAeA,WAEjBA;MADEA,OAAWA,gCACbA;K;kBAYsBC;qBAGYA;MAAhCA;QACEA,sBAAUA;MAEZA,OAwGFA,8CAvGAA;K;kBAPsBC;;K;mBASVC;MACMA;;;MAGZA;MAAJA;QAAmBA,WAErBA;MADEA,OAAWA,oCACbA;K;qBAEYC;MACMA;;;MAGZA;MAAJA;QAAmBA,WAKrBA;MAFMA;iCAAMA;MAANA;QAA4BA,WAElCA;MADEA,OAAWA,oCACbA;K;qBAEYC;qCACsBA;QAC9BA,sBAAUA,mCAAkCA;MAE9CA,OAAOA,mCACTA;K;;;;;eA0BQC;MACJA,WAAgEA,aAAOA;K;aAEnEC;mBAF4DA;MAGhEA,8BAEWA;K;YAMCC;MAAiBA;MAAMA;eAFWA;;iCAAMA;MAEvBA,gBAAYA;K;;;;EAmD7C/vB;kBAV0BA;MACtBA,qCAAwBA,UAAKA,0BAASA,QAAOA;K;;;iBAWjCgwB;MAAWA,gCAAQA;K;gBAU9BC;;kBACCA;MAAJA;QAAqBA,YAyBvBA;gBAxBMA;kBAAsBA;kBACZA;;QACZA;eACEA;UACsBA;mBAhFwCA;kBAjHrBC;wBAuMnCD;;wBAAiBA;yBAAQA;gBACRA,4BAAQA;gBAAzBA;kBACkBA;kBAjB5BA;;kBAgBUA;;gBADeA;;cADnBA;YAMAA;;eAEFA;UACAA,WAMNA;;;WAFEA,4BADAA;MAEAA,YACFA;K;;;;aClQQE;MAAOA,wBAAQA,QAAQA,OAAMA;K;YACrBC;MAAmBA;MAIjCA;QACEA,kBAAUA;MALeA,WAOpBA,QAP4BA;K;;;;;;EAqDrCC;kBAlBoBA;MAChBA,2CAA8BA,aAAQA,eAAUA,oBAAOA;K;eAEjDC;MACJA,aAA6CA;oBAARA,wBAAkBA;MAC3DA;QACEA,OA5CEA,4BA+CNA;MADEA,sBAA2BA;IAC7BA,C;;;gBAWKC;;kBACCA;kBAASA;eAASA;kBAASA;eAAOA;MAAtCA;aACEA;QACAA,YAcJA;;MAXMA;MAAJA;aACEA;aACAA;QACAA,YAQJA;;MANYA;MArENA,KAsEJA;WAGAA,mCADWA;MAEXA,WACFA;K;iBAEUC;MAAWA,gCAAQA;K;;;;qBE3EpBC;MAAeA,4BAAUA;K;;;;;;qBA2XzBC;MAAeA,0BAAQA;K;;;gBAwQxBC;MAAUA,sBAAgCA;K;;;;YA2BlCC;MACGA;MAAjBA,4CAAmCA;MACnCA,sBACFA;K;eAEcC;MACKA;MACmBA;MADpCA,4CAAmCA;;IAErCA,C;;;;;;eAkBcC;MACKA;MACmBA;MADpCA,4CAAmCA;;IAErCA,C;;;;;;qBA4BSC;MAAeA,6BAAWA;K;;;qBAkC1BC;MAAeA,6BAAWA;K;;;qBAiC1BC;MAAeA,2BAASA;K;YAEpBC;MACMA;MAAjBA,4CAAmCA;MACnCA,sBACFA;K;;;qBAiCSC;MAAeA,2BAASA;K;YAEpBC;MACMA;MAAjBA,4CAAmCA;MACnCA,sBACFA;K;;;qBAiCSC;MAAeA,0BAAQA;K;YAEnBC;MACMA;MAAjBA,4CAAmCA;MACnCA,sBACFA;K;;;qBAiCSC;MAAeA,4BAAUA;K;YAErBC;MACMA;MAAjBA,4CAAmCA;MACnCA,sBACFA;K;;;qBAiCSC;MAAeA,4BAAUA;K;YAErBC;MACMA;MAAjBA,4CAAmCA;MACnCA,sBACFA;K;eAEWC;MAGTA,uBAAgBA,yBAFVA,uCAAkCA,UAG1CA;K;;;;qBA4BSC;MAAeA,kCAAgBA;K;gBAEhCC;MAAUA,sBAAgCA;K;YAErCC;MACMA;MAAjBA,4CAAmCA;MACnCA,sBACFA;K;;;qBA0CSC;MAAeA,2BAASA;K;gBAEzBC;MAAUA,sBAAgCA;K;YAErCC;MACMA;MAAjBA,4CAAmCA;MACnCA,sBACFA;K;eAEUC;MAGRA,sBAAgBA,yBAFVA,uCAAkCA,UAG1CA;K;;;;;;;;ETlrBiBC;aA/SbA;MAEFA,qEACFA;K;aAKIC;MAAsBA,OA+STA,qBA05DC/vB,AAAAI,iCAzsE+C2vB;K;;;;kBAgrBzDC;;iBAAYA;;gBAAsCA,KA7bzCA;QADgBA;;QA8b+BA,SAA5CA;;eAAoDA;K;SAG1DC;MAAEA;oBAEhBA;MADEA,uCAAqCA,cAAYA,KACnDA;K;gBAGOC;MAAcA,0BAAaA,YAAWA;K;;;;gBAiJtCC;MAAcA,oBAAQA;K;;;;iBAmBlBC;MAAWA,oBAAQA;K;;;Ye34B1BC;;cACUA;QACRA;MACAA;IACFA,C;;;;YAMOC;;MAEYA;;MAFZA,iBACEA;QACPA;eAG4DA;eACxDA;;IACLA,C;;;;YASHC;MACEA;IACFA,C;;;;YAOAC;MACEA;IACFA,C;;;;kBA2CF/pB;;YAQIA,2BACIA,yBAPiBA;;QASrBA,sBAAUA;IAEdA,C;2BAEAC;;YAIIA,4BAGIA,yBAAuBA;;QAa3BA,sBAAUA;IAEdA,C;cASK+pB;;qBAEGA;QAAJA;UAAqBA,MAUzBA;gBATQA;;;;YAKJA;;QAEAA,sBAAUA;IAEdA,C;;;;YAxDIC;mBACEA;;QACKA;MACLA;IACFA,C;;;;YAgB2BC;;kBACLA;;kBACZA;MAAJA;qCAC2CA;QACzCA;UACSA;;QAGNA;MACLA;IACDA,C;;;;gBAwCJC;;;uBACYA;gBAAVA,mCAAgBA;eACnBA;MADFA;QACEA;;QAEAA,wBAA2BA;IAE/BA,C;qBAEKC;mBAEDA;cADEA;QACFA;;QAEAA;IAEJA,C;;;EAqEgBC;YAAZA;MAAYA,0CAA+CA;K;;;;YAEtCA;MAGvBA,4BhB69CFA,oCgB99CwCA;IAEvCA,C;;;;YA0C0CC;UACvBA,YAAWA;IAC9BA,C;;;;qBuDhUQC;MAAeA,WAAIA;K;;;gBAuCvBC;IAAYA,C;iBAIZC;IAAaA,C;sBA/BQC;;K;0BACAC;;K;;;sBAgIjBC;MAAgBA,WAACA,WAAuBA;K;yBAEzCC;mBACFA;MAAJA;QAAyBA,SAE3BA;MADEA,WAAOA,erD+DTA,eCyE2BzqB,sCoDvI3ByqB;K;uBAsBKC;MAAeA;kEACDA;MADCA,2BACYA;MADZA,2BAEaA;6BACmBA;yBACJA;MAC9CA;QAEEA;;QAESA;MAEXA;QAEEA;;QAEKA;MAG2BA;MAArBA;IACfA,C;kBAIsBC;;;yBAOVA;MAJRA;gBAjFkBA;;UAiFPA;Q9C8gBfA,oCNvb2BC,wBMub3BD;QACEC;Q8C9gBED,SAUJA;;YpD4E2B7nB;;MoD1Q3B6nB;;MAAAE,sFAsL2CF;MAnLjCE;MAARA;MAwIiBF;MADFA,2BACeA;kBACjBA,oBAAeA;qBAEQA;MACpCA;MACaA;MACAA;MACbA;QACEA;;QAEQA;eAoCIA,4BAAoBA;QAEhCA,mBAAYA;MAEdA,mBACFA;K;qBAEOG;;;MACqBA,yFAAeA;aAEdA;QAAsBA,WAYnDA;eAnMuBA;MAwLrBA;QAtLwBA;;;QAyLtBA;kBA7EmBA,2BAaFA;UAoEfA;;MAGJA,WACFA;K;oBAEKC;;IAAkDA,C;qBAClDC;;IAAmDA,C;sBAIlDC;mBApHgBA;MAqHpBA;QACEA,O1EySJA,6D0ErSAA;MANoBA;MAKlBA,O1EsSFA,kE0ErSAA;K;WAEKC;MACHA;2CACUA;MADLA;QAAcA,sBAAMA;MACzBA;IACFA,C;gBAEKC;MAAQA;MAGgDA;;Q1EjI7DvqB;M0EgIOuqB;QAAcA,sBAAMA;MACKA,epDoCLA;MoDnCzBA;2BACoCA;;U1EnItCvqB;gC0EoI6BuqB;;MAE3BA;IACFA,C;gBATKC;;K;aAWEC;MACLA;gBA7IoBA;QA6IpBA,oBACSA;QACPA,YAAOA,YAOXA;;MALOA;QAAcA,sBAAMA;;MAELA;MACpBA;MACAA,iBACFA;K;wBAgBKC;MACHA,yBAAkBA;IACpBA,C;cAEKC;MAAMA;2BAhJkBA;sBAkJAA;MAC3BA;;M/C2nBAA,Q+CznBAA;IACFA,C;wBAGKC;MAEHA;;gBA7JqBA;MA6JrBA;QACEA,sBAAUA;0BAjJOA;MAoJnBA;QAAcA,MAgChBA;MA7BYA;WAOVA;aAEAA;yBAjSkCA;QAkShCA;sBACeA;UACbA;;6BAE8CA;UAC9CA;YACEA;;;;qCAK0BA;;;eA5KbA;QAkLjBA;IAEJA,C;qBAEKC;MACIA;MADSA,oBAtLGA;gBArCCA,2BA6NJA,YAAYA;QAE1BA;MAEFA,mBAAYA;IACdA,C;4BApR0BC;;K;2BACAC;;K;yBAGPC;;K;;;;;;;;;EA0RYC;sBAAtBA;MAAgBA,kFApNFA,kBAoNkCA;K;sBAEzDC;M1EiLAA,S0EvYuBA;QAwNnBA,uFAIJA;MADEA,OAAaA,sDACfA;K;iBAEKC;MACHA;MAIoBA;eAtNDA;QAkNLA,MAchBA;gBAjPmBA;qBAAoBA;;QAuOnCA;;iBAtNiBA;UAyNfA;QAEFA,MAKJA;;MAHEA,yBAAiBA;IAGnBA,C;kBAEKC;cAlOgBA;QAmOLA,MAIhBA;MAHEA,wBAAiBA;IAGnBA,C;iBAEKC;MACEA;eA1OcA;QA2OjBA,yBAAiBA;;QADnBA,oBAKSA;QALTA,oBAMSA,YAAYA;QACnBA;;IAEJA,C;;;YAtBmBC;gEACfA,qBAAaA,WAAKA;IACnBA,C;gBAFgBC;;K;;;YAOAC;gEACfA,qBAAaA,uBAAUA,YAAOA;IAC/BA,C;gBAFgBC;;K;;;YAOEC;gEACfA,qBAAaA;IACdA,C;gBAFgBC;;K;;;iBAiBhBC;;;;M9CiLLA,wB8ChLgDA,yDAA9CA,iDAEgCA;QAC9BA,2B9C6KJA;I8C3KAA,C;kBAEKC;MACHA;8BAA8CA,qBAA9CA,iDAEgCA;QAC9BA,2B9CgLJA;I8C9KAA,C;iBAEKC;;4BA5QgBA;MA6QnBA;eACEA,iDAEgCA;UAC9BA,4BAA+BA;;QAJnCA,oBAOSA;QAPTA,oBAQSA,YAAYA;QACnBA;;IAEJA,C;;;;YnD7QYC;;;QAENA,wBAAiBA;;QAFXA;QAGNA;QACAA,iCAA2BA;;IAE9BA,C;;;;YAoBiBC;;;QAEdA,wBAAiBA;;QAFHA;QAGdA;QACAA,iCAA2BA;;IAE9BA,C;;;;YA+JDC;MAAWA;;;;YAELA;UAWFA;cACIA,yBAAkBA;UACpBA;;YAEAA;YACAA;;mCAE0BA;QAC5BA,gCAAsBA,UAAOA;IAEjCA,C;;;;YAOgBC;;;;;aAENA;MAAJA;QACEA,qCAAOA;cACHA;UACFA,oCAA0BA;mBASxBA,0BAAmBA;QACrBA,gCAAsBA,UAAOA;IAGlCA,C;gBAlBWC;;K;;;YA+FDC;;iBACRA;;QAAqBA,YAI3BA;MAHcA,8BNgOAA;MM/NFA;QAAWA,OAAOA,gBAAYA,4CAE1CA;MADCA,WACDA;K;;;;YAmCqDC;MACpDA;;6CAGaA;;;;;UAHbA;UAIIA;UAG4CA;UAAOA;UA8X7BA,eDlnBHC;UCmnB3BD;+BACoCA;;cvBzxBpCrsB;oCuB0xB2BqsB;;YAEOA;YAAPA;;UAA3BA,KAnYwCA;UAChCA,MASLA;;QAPcA;UACTA,oCAAYA,gBAAmCA,KAAXA;UACpCA,MAKLA;;QAHGA;;MAEFA;IACDA,C;;;;gBA0KIE;MAEmDA,aAApDA;+GACqCA;MACzCA,aACFA;K;;;;;;;;qBF9rBKC;MAAaA;;QrB4GlBxsB;cqB1GOwsB,OAyNkBA;QAzNGA,sBAAUA;MACNA,eC8QLA;MD7QzBA;2BACoCA;;UrBuGtCxsB;gCqBtG6BwsB;;MAE3BA;IACFA,C;qBATKC;;K;;;;gBAmBAC;MACHA;6BACsBA;eADjBA;YAuMkBA;QAvMGA,sBAAUA;MACpCA;IACFA,C;gBAHKC;;K;sBAKAC;MACHA;IACFA,C;;;gBAIKC;MACHA;6BACiBA;eADZA;YA4LkBA;QA5LGA,sBAAUA;MACpCA;IACFA,C;gBAHKC;;K;sBAKAC;MACHA;IACFA,C;;;wBAqFKC;MAEIA,SAlCiBA;QAiCLA,WAErBA;MADEA,WAtCgBA,OAAOA,oBAgBvBA,sCAAOA,sBAsBoDA,iCAC7DA;K;mBAEYC;;mBAtCcA;MAsCHA;QAdGA;kBAdDC;;QA4BFD;;2BAEIA;;;MAIvBA;gBA/CcA,OAAOA;MA8CLA;QAChBA,iBAAOA,0CACuBA,kBAAkBA,uCAKpDA;;QAPEA,eAIuBA;QACrBA,iBAAOA,yCAA0DA,gBAErEA;;K;;;sBAkHUE;;;sCAGgDA;qBCiB/BA;2BDlBGA;QACtBA;QACJA;UAIYA;;MA7DhBA,wBCyE2B/tB,gBDzE3B+tB;;MAiEEA,oBAvMFA;MAwMEA,aACFA;K;cAdUC;;K;oBAsBAC;;;sCAGiDA;MA9E3DA,wBCyE2BjuB,gBDzE3BiuB;MA8EEA,oBA9MFA;MA+MEA,aACFA;K;kBAEUC;MACGA;MAGLA;;YCbmBluB;MDzE3BkuB;kBAoF+BA;QACjBA;MAGZA,oBAjNFA,6EAiNmBA,gBAjNnBA;MAkNEA,aACFA;K;oBAEUC;MACGA;MAEuCA;;YCtBzBnuB;MDzE3BmuB;kBA8F+BA;QACXA;MAElBA,oBAvNFA,4EAuNmBA,gBAvNnBA;MAwNEA,aACFA;K;oBA4CKrsB;MAAYA,mBAxHOA;MAwHPA,qBAxHOA;UA2HtBA,gBAAgBA;UAChBA,4BAA4BA;IAC9BA,C;oBAEKssB;MAAYA;6BACCA;gBAlIUA;MAmI1BA;QACWA,2EAAgBA;aACzBA;;QAEAA;UApCFA,2CAAOA;oBAyCSA;YACVA;YACAA,MAURA;;UARMA;;QAbJA,oBAjIsBA;QAkJpBA,gCAAwBA;;IAI5BA,C;yBAEKC;MACHA;;;QAAuBA,MA6BzBA;gBAxL4BA;MA4J1BA;QACkBA,sEAAoBA;sBACpCA;QACAA;iCAEgBA,iCAAdA;;gBAGOA;;;QAGTA;UApEFA,2CAAOA;oBAyESA;YACVA;YACAA,MAURA;;UARMA;;QApBJA,oBA1JsBA;QAiLRA,MAAZA;QACAA,gCAAwBA;;IAI5BA,C;wBAEgBxsB;MAAgBA;0BAxLRA;MA4LNA,4DAAUA;MAEnBA,KADPA;MACAA,yCACFA;K;yBAEgBysB;MACEA;MAEhBA;sBACiCA;eACvBA;;MAIVA,WACFA;K;iBA0DKC;;;uBAECA;MAFQA,oBArQUA;kCAuQZA;QACEA;UACRA;;UAEAA;;QAG0BA;QAClBA;QA1KAA,oBArGUC;aAuGtBD;aACAA;QAwKEA;;IAEJA,C;0BAEKE;;;MAEIA;MAFcA,oBApRCA;MAoRDA,wCAERA;MAEeA;MAnLhBA,oBArGUD;WAuGtBC;WACAA;MAkLAA;IACFA,C;sBAEKC;MAAcA;MAIAA;MAJAA,oBA7RKA;MAgSMA;MArLVC,oBA3GI5sB;WA6GtB4sB;MCnTFA,KDoTEA;MAoLAD;IACFA,C;sBANKE;;K;sBAQAC;;;uBAaCA;MAbaA,oBArSKA;kCAkTZA;QACRA;QACAA,MAMJA;;MA1OwBA,oBApFCrtB;WAsFvBqtB;MAqOAA,gCAAwBA;IAG1BA,C;oBAEKC;;;8BACCA;MAAMA;iBA5TUA;UA+EEA,oBApFCttB;eAsFvBstB;UA+OIA,gCAAwBA;;UAIxBA;QAEFA,MAIJA;;MADEA;IACFA,C;2BAEKC;MAAmBA;;0BA7UAA;MAgFAA,oBApFCvtB;WAsFvButB;MA+PAA,gCAAwBA;IAG1BA,C;;;;YAlM4BC;MACtBA,gDAA4BA;IAC7BA,C;;;;YA8BuBC;MACtBA,uDAA4BA;IAC7BA,C;;;;YAoCWC;mBACHA;MADGA,iBA3NeA;MAwFLA,iBAxFKC;QA0F7BD;MAuIIA;IACDA,C;;;;YAKYA;;;eACJA;MADIA,iBAvOcA;MAyOzBA;IACDA,C;YAHYE;;K;;;;YASKF;MAChBA,iCAAsBA,QAAGA;IAC1BA,C;;;;YAwEqBG;mBACtBA;8BAAmBA;IACpBA,C;;;;YAQ2BC;MACtBA,+BAAiBA,YAAjBA;IACDA,C;;;;YAcmBC;MACtBA,gCAAeA,YAAOA;IACvBA,C;;;;YA6DGC;;kBAIUA;eA3hBUA;MAuhBWA;MAthBLA;MAshBKA;;;QAleXA;QApCYC;QAsC7BD,mBAzDSC,OAAOA,eAqBvBA,gCAAOA;;QAogB4BD;QAS3BA;iBACIA;2BAAsBA;UAhUdA,iBAzFA5tB;UA2FpB4tB,+BAAOA,oBA8TyCA;UAAOA;UAA/BA;UAAhBA;;;;;2BACyBA;UAjUbA,iBAzFA5tB;UA2FpB4tB,EA+TUA,kDA/THA;;UClSTA,EDmmBYA;UAEFA;QACAA,MAkBJA;;MAhBqBA;iEAlaHA;4BACFA;YAoaeA;YA3UfA,iBAzFA5tB;;YA2FpB4tB,EAyUYA,kDAzULA;cA0UKA;;UAGFA,MASNA;;qCAJyBA;;QACEA,EAAvBA,gDAA2CA;UAC3CA;;IAEJA,C;;;;YAH+CE;MAAOA,0BAAcA;K;;;;YAKpEC;MAAwBA;;kBAEGA;;;QA9hBiBA,oBA8hBIA;QAvjBpBC,kBANRC;QA+BjBF,YA8hBCA,0BA/jBQC,OAAOA,oBAUvBA,6BAAOA;;QAmjBqBD;QAGpBA;;QC7nBVA,ED8nBUA;UACAA;;IAEJA,C;;;;YAEAG;MAAgBA;;yBAEYA;QArWZA,iBAzFAluB;QA2FpBkuB,uCAAOA;kBAoWKA;;UA9iBYA,kBAxBAhC;iBAUDA;;UA4jBfgC;;;UAEyBA,EAAvBA;YACAA;;;QANUA;QAQZA;;eACcA;QA5WFA,iBAzFAluB;;QA2FpBkuB,iBAAOA,oBA0W6BA;QAAOA;;QAAnCA;iBACyBA;UA7WbA,iBAzFAluB;UA2FpBkuB,EA2WUA,oCA3WHA;;UClSTA,ED+oBYA;UAEFA;;IAEJA,C;;;;;qBK1UGC;MAAeA,YAAKA;K;YAwTtBC;gEACEA;MAAPA,OAAOA,iCAA+BA,SAAKA,yDAC7CA;K;gBA0SgBC;ML9vBhBA;gCCyE2BjwB;QIurBrBiwB;MACCA,2CACDA,6CAIQA,0CADQA;MAKpBA,aACFA;K;eAwPcC;MLngCdA;gCCyE2BlwB,gBI27BDkwB,sBLpgC1BA;;MKsgCsBA,EAApBA,2DACIA,oDAIQA,qCADQA;MASpBA,aACFA;K;cAYcC;MLhiCdA;gCCyE2BnwB,gBIw9BDmwB,sBLjiC1BA;;QKmiCOA;MACLA,2CACIA,2CAKQA,wCADQA;MAapBA,aACFA;K;kBAwEUC;;;;;ML/nCVA,wBCyE2BpwB,gBDzE3BowB;;MKkoCsBA,EAApBA,4DACIA,gEAQQA,uDADQA;MAapBA,aACFA;K;;;YAxsCcC;mBACVA;gBAAgBA;MAChBA;IACDA,C;gBAHWC;;K;;;YAGAD;mBACVA;mCAA4BA;MAC5BA;IACDA,C;;;;YAiEGE;MEsRNA,aFtR0CA;MAA9BA,oCTmdZpvB,2BAxGgCqvB,yBAwGhCrvB,8BW7LAovB,IFtRgBA,wCAAmCA;K;gBAA7CE;;K;;EA4b8CC;YAAPA;MAAOA,qCAAsBA;K;;;;YA+SpEC;;;IAECA,C;gBAFDC;;K;;;YAIQD;MACNA,mCAAiBA;IAClBA,C;;;;YA+PDE;sDACwCA;MAAtCA,6BAAgBA,mBAAcA;IAC/BA,C;gBAFDC;;K;;;YAIQD;;;QAEuBA;QAA3BA;;QAFIA;QAGJA;QACAA,iCAA2BA;;IAE9BA,C;;;;YAoBDE;;sDAEWA;;QADTA;QACAA;IACDA,C;gBAHDC;;K;;;YAKQD;;;YACFA;QACFA,0BAAiBA;QACjBA,MAOHA;;;QAJ8BA;QAA3BA;;QANIA;QAOJA;QACAA,iCAA2BA;;IAE9BA,C;;;;YA+EDE;;;;;MACEA,eAAaA,qDAAmBA,kDAI7BA,2BAAuBA;IAC3BA,C;gBANDC;;K;;EACqBC;YAANA;MAAMA,4BAAKA,OAAMA;K;;;;YAAEA;gCAC1BA;QACFA,6BAAgBA,mBAAcA,aAAQA;IAEzCA,C;;;;YAGKF;MACNA;;QAK6BA;QAA3BA;;QANIA;QAOJA;QACAA,iCAA2BA;;IAE9BA,C;;;;;;wBC52BeG;MAAeA;2BAnBhCA;MAsBDA,UAfyBA;QAezBA,uEAAOA,UAIXA;MAFqCA;MACnCA,8CADmCA,6DAAQA,UAC9BA,SACfA;K;4BAGqBC;MAAoBA;2BA7BpCA;gBAOwBA;kBAyBzBA;;UC2JAA,UD3JAA,oCAAiBA,uBC2JjBA;QD1JAA,wEAKJA;;MAHqCA;2EAAQA;gBACjCA;MAAVA;QCuJEA,UDvJ+BA,mCCuJ/BA;MDtJFA,oDACFA;K;8BAK+BC;MAAcA;2BA/CpBA;gBAWIA;QAuCUA;QACnCA,uDADmCA,6DAAWA,UAC9BA,SAGpBA;;MADEA,gFAAOA,UACTA;K;sBAOMC;mBAvDgBA;MAwDpBA;QACEA,O3BjBJA,kD2BqBAA;MANoBA;MAKlBA,O3BpBFA,0D2BqBAA;K;+BAGOC;;;8BAKmBA;gBA7DAA;MAyDxBA;QAAmBA,sBAAMA;MACzBA;QNxVFA,oBCuE2BvwB;QDtEzBA;QMuViBuwB,SAOnBA;;gBAJgBA;MAwXgBA;MNrtBhCC,oBCyE2BzxB;MK0lBX0xB,sDACWA;MAHHA,kDAAyBA,sBAIpBA;gBAnZJC;MASPD,sDAAcA;QA4b5BA;MAJJF,KArXEA,8DAqXFA;;MAnXEA,SACFA;K;yBAWOI;mBACLA;;QAAqCA,SAArCA,oBA9FuBA,mDN/QzBA,eCyE2B5xB;MKqSzB4xB,SACFA;K;WAKKC;MACHA;2CACKA;eAtFmBA;QAqFLA,sBAAMA;MACzBA;IACFA,C;gBAKKC;MACHA;MAE2DA;cA/FnCA;QA6FLA,sBAAMA;;Q3Bzd3BlxB;M2B2dgCkxB,eLtTLA;MKuTzBA;2BACoCA;;U3B7dtClxB;gC2B8d6BkxB;;MAE3BA;IACFA,C;gBATKC;;K;aAyBEC;;kBA7HeA;MA8HpBA;QACEA,OAAOA,2BAKXA;MAHEA;QAAmBA,sBAAMA;MACzBA;MACAA,OAAOA,2BACTA;K;uBAEKC;;MAEHA;QACEA;WACKA;QACLA,8BAAuBA,UAAUA;IAErCA,C;YAKKC;;;MAESA;gBA1JWA;MAyJvBA;QACEA;WACKA;QACLA,+BAAuBA,SC3D3BA;ID6DAA,C;wBAEKC;MACHA;MACoBA;eAlKGA;MAiKvBA;QACEA;WACKA;QACLA,8BAAuBA,SCxD3BA;ID0DAA,C;cAEKC;MAAMA;2BA7JkBA;MAgKQA,4FAAWA;WAC9CA,oBAAoBA;;MA2QpBA;IAxQFA,C;kBAIsBC;;;yBAMVA;MAAiBA;gBArLxBA;QAkLDA,sBAAUA;YL/XavvB;;MKuiB3BuvB;MAAAC,sFAtKgDD;MAGZA;;MAElCA;QACqCA,wEAAWA;gBACrCA;QAiOXA;;aA9NEA;MAEFA;MACAA,8BAA4BA;MAI5BA,mBACFA;K;qBAEOE;;;;;gBAjMsBA;QA4MUA,sEAAWA,WAC5BA;WAEpBA;WACAA,eACKA;gBAEDA;MAAJA;QACEA;;YAIIA,qCAASA;;YAJbA;YAKIA;YNtfRA,yBCyE2BvyB;YKibNuyB;YAAbA;;;UAIOA;MAIAA;MAMbA;QACWA;;QAETA;MAGFA,aACFA;K;oBAEKC;;;;gBApPwBA;QAsPUA,6DAAWA,UAC9CA,iBA0JcA;MAxJhBA,mBAAYA;IACdA,C;qBAEKC;;;;gBA5PwBA;QA8PUA,6DAAWA,UAC9CA,iBAsJcA;MApJhBA,mBAAYA;IACdA,C;;;;;;;;;;YAxE8BC;MAC1BA,kBAAYA;IACbA,C;;;;YAyCDC;mBACMA;0BNhfiBA;QMifnBA;IAEJA,C;;;;iBAiCGC;MACgBA;MAAnBA,gCAAcA;IAChBA,C;kBAEKC;MACHA,gCAAcA;IAChBA,C;iBAEKC;MACHA,gCAAcA;IAChBA,C;;;iBAKKC;;MAC2CA;MAA9CA,gCAAcA,cCpMhBA;IDqMAA,C;kBAEKC;MACHA,gCAAcA,cC7LhBA;ID8LAA,C;iBAEKC;MACHA,gCAAcA,eAAkBA;IAClCA,C;;;;EA8BIC;2BAFkBA;MAElBA,2EAAuBA,yBAAiBA,kDAAsBA;K;kBAM1DC;MAAYA,QrBvwBWA,gCqBuwBXA,gCAAiCA;K;SAEvCC;MAAEA;oBAIhBA;MAHEA;QAA4BA,WAG9BA;MAFEA,oDACoBA,qBAAkBA,YACxCA;K;;EAWSC;iBADFA;MACLA,WAAOA,kCACTA;K;gBAEKC;MACHA;IACFA,C;iBAEKC;MACHA;IACFA,C;;;WAOKC;MACHA,6BAAYA;IACdA,C;;;;;;;cAsDOC;MACQA;MACbA;QACEA;QACAA,WAKJA;;MAHEA,OAAOA,sBAAoBA,2CAG7BA;K;;;YA9BkDC;mBAC5CA;+BAAwBA;MACxBA;IACDA,C;;;;YAwBwBC;MACzBA;IACDA,C;;;;;oCClzBH9wB;MAGOA;MACAA;MACAA;IACPA,C;yBAQK+wB;MAAiBA;uFAEhBA;MAFgBA,oBACbA;MACPA;QAA2BA,MAM7BA;MALEA;MACmBA;aACjBA;QACAA;;IAEJA,C;cAIKC;;sDACHA;;QAAeA;MAGfA,iBAAUA;IACZA,C;eAEKC;MAAOA;;QACMA;MACAA;QACHA,KAAXA,iBAAWA;WAEUA;QACVA,KAAXA,iBAAWA;;QAEXA,sBAAUA;IAGdA,C;cAEKC;MACHA;;QAAeA;MACfA,iBAAUA;IACZA,C;aAEKC;;kBAgEoBA;MA/DvBA;QAAiBA,MAQnBA;MAJmBA;WAAjBA;2BAEkBA;kBAAkBA;;YA0fnBA;;MAzfjBA;QAAqCA,uBAAeA;IACtDA,C;aATKC;;K;cAWAC;;kBAqDoBA;MApDvBA;QAAiBA,MAcnBA;MAbEA;;QAEEA;UACEA;sBAAoBA;YAASA;;YAA7BA;;YAEEA;;YAFFA,eAISA;uBACPA;;YACAA;cAAkBA,uBAAeA;;;;IAIzCA,C;cAEOC;;mBAILA;;MACAA;QACEA;MAE6BA,UAAxBA;MAAPA,oDACFA;K;yBAiCSC;cAFaA;qBAGHA;QAA6BA;;QAA5CA;eAAoDA;K;kBAG/CC;MAAYA,WANCA,cAMQA;K;eAEzBC;;kBACHA;MACAA;kBACEA;;YAkbeA;;MAhbjBA;QAAkBA;MACFA,KAAhBA;IACFA,C;YAgBKC;;;mDAISA;MAJLA,qBApCcA;gBACEA;MAqCvBA;QAAiBA,MAMnBA;MALEA;QACEA;;QAEAA,oBAoUJA;IAlUAA,C;wBAEKC;mBA7CoBA;MA8CvBA;QAAiBA,MAMnBA;MALEA;QACEA;;QAEAA,mBAsUJA;IApUAA,C;cAEKC;MAAMA;2BAvDYA;gBACEA;MAwDvBA;QAAiBA,MAOnBA;MANEA;;MACAA;QACEA;;QAEAA,qBAAkBA;IAEtBA,C;gBAMKC;MAAQA,oBAvEeA;IAyE5BA,C;iBAEKC;MAASA,oBA3EcA;IA6E5BA,C;iBAEOC;MAASA,oBA7ESA;MA+EvBA,WACFA;K;mBAUKC;MACkBA;;iCAAUA;MAC/BA;QA+WEA;QA9WUA;;MAEZA;gBA5FuBA;MA6FvBA;QACEA;;QACAA;UACEA;;IAGNA,C;iBAIKC;;;MAM4BA;MANnBA,qBA1GWA;MA0GXA,oBAtGQA;MAsGRA,qBAxGWA;gBAJGA;WAiH1BA;MACAA,qCAAsBA;WACtBA;MACAA;IACFA,C;kBAEKC;MAAUA;;2BArHUA;MAqHVA,oBAjHOA;MAiHPA,qBAnHUA;gBAJGA;MA6HZA;MAgBdA;aACEA;QACAA;kBACIA;QACiCA;UACnCA;;UAEAA;;QAGFA;QAEAA;;IAEJA,C;iBAEKC;MAASA;2BA3JWA;MA2JXA,oBAvJQA;MAuJRA,qBAzJWA;MA8JVA;MASbA;WACAA;gBACIA;MACiCA;QACnCA;;QAEAA;IAEJA,C;sBASKC;MAAcA;MAIjBA;MAJiBA,qBAxLMA;gBAJGA;WA+L1BA;MACAA;WACAA;MACAA;IACFA,C;mBAYKC;MAAWA;2BA3MSA;gBACAA;kBA4MJA;QAASA;;QAA5BA;;mBACEA;;QACsBA;eACpBA;;mBAKJA;kBAvNuBA;QAwNrBA;UACEA;UACAA,MAgBNA;;QA5O2BA;QA+NvBA;UAAqCA;aACrCA;QACAA;UACEA;;UAEAA;aAEFA;;gBAjOqBA;MAoOvBA;QACEA;IAEJA,C;iBAhXgBC;;K;iBAEHC;;K;kBAeKC;;K;;;;;YAgPhBC;;iBAGMA;eA9HiBA;MA8HrBA;QAAqCA,MAWvCA;QAVEA;kBAEcA;eAK4BA;;aAAxCA;MAJUA;QACVA,yCAA2DA;;;QAD7DA,eAGkBA;QAChBA,uBAA8BA;;QAEhCA;IACFA,C;;;;YAuBAC;mBAGOA;eAlKoBA;MAkKzBA;QAAsBA,MAIxBA;QAHEA;MACAA,wBAAiBA;QACjBA;IACFA,C;;;EAiFIC;2CAJgBA;MAMpBA,wEAFwBA,yBAAiBA,6CAFTA,oCAKlCA;K;qBAPsBC;;K;6BAAAC;;K;cAAAC;;K;2BAWAC;;MAEpBA,OAAWA,mDACPA,yBAAiBA,qDADVA,eAEbA;K;;;2BAoBsBC;;;yBAKhBA;MAAiBA;eAHjBA;QAASA,sBAAUA;WACvBA;MACWA;6BAEWA;MAFtBA,SAGFA;K;;;iBAWSC;MAAWA,oCAAiBA;K;kBAEhCC;MACHA;;gBAAIA;MAAJA;QACEA,sBAAUA;;;QAUAA;;UAERA,qBAA6BA,KAAVA;;UAEnBA;UACAA;;;QAjBSA;QAmBXA;QACAA;UAGEA,4BAAkBA;UAClBA;;UAGAA;;IAGNA,C;0BApCYC;;K;;;cAgEEC;;K;;;;;;eASTC;4CACHA,iBAASA,gBAAUA;IACrBA,C;;;eASKC;MACHA,0BAAoBA,YAAOA;IAC7BA,C;;;eAMKC;MACHA;IACFA,C;cAEkBC;MAAQA,WAAIA;K;cAErBA;MACPA,sBAAUA;IACZA,C;;;;gBAsCKC;MACHA;;eAVsBA;QAULA,MAcnBA;MAfaA,gBAEHA;gBAVkBA;MAW1BA;;aAEEA;QACAA,MASJA;;MAPEA,oBAAkBA;WAMlBA;IACFA,C;;;YAPoBC;mBACDA;;QACfA;MACAA;QAAiCA,MAElCA;MADCA,oBAAWA;IACZA,C;;;;iBAsBMC;MAAWA,oCAAwBA;K;WAEvCC;;kBACCA;MAAJA;aACEA,0BAAoBA;;QAEgBA;aAApCA;;IAEJA,C;kBAEKC;MAAUA;6CAOCA;MAPDA,oBAtDSA;oBAwDAA;MACIA;WAA1BA;MACAA;aACEA;MAEFA;IACFA,C;;;kBA4BSC;MAAYA,uBAAiBA;K;iBAEjCC;MACCA;gBAJoBA;QAINA,MAGpBA;MAFEA,gCAAwBA;WACxBA;IACFA,C;cAEKC;;IAAiCA,C;eACjCC;IAA+BA,C;cAC/BC;MACHA,iBAAUA;IACZA,C;aAEKC;;IAGLA,C;aAHKC;;K;cAKAC;mBAnBgBA;MAoBnBA;iBACEA;QACAA;UACEA;;IAGNA,C;cAEOC;MAAYA,OAAOA,2BAAWA;K;iBAUhCC;;kBACHA;MACAA;QAAcA,MAGhBA;WAFEA;gBACIA;MAAJA;QAAqBA;IACvBA,C;iBAnDaC;;K;;;;;qBA0UJC;MAAeA,WAAIA;K;2CACNC;;;MAEkBA;;MA3UxCA,oCNvb2B3M,wBMub3B2M;MACE3M;MA0UA2M,SACFA;K;6BAHsBC;;K;cAAAC;;K;;ECtgCYC;YAANA;MAAMA,wCAAsBA,YAAOA,YAAWA;K;;;;YAqBnEC;MACLA,sBAAgBA,mBAAcA,gBAAeA;IAC9CA,C;;;EAQiCC;YAANA;MAAMA,mCAAiBA,OAAMA;K;;;;EPflCC;gBAAhBA;MAAcA,eAAEA,OAAMA;K;;;;;;;;;2BA6oBxBC;MACCA;MAIkDA;MAJfA,qBAAlBA;MAGdA,yBAFyBA;MAEhCA,qBADoDA,4BAEtCA,qDAChBA;K;0BAuBgBC;MACVA;wBAGsDA;MAHnBA,qBAAlBA;MAEGA,yBADQA;MAEhCA,wGADiDA,YACnCA,mBAAWA,yCAC3BA;K;+BAEwBC;MAClBA;qDAGsDA;MAHnBA,qBAAlBA;MAEQA,yBADGA;MAEhCA,kHADsDA,YACxCA,mBAAWA,6CAC3BA;K;gCAE8BC;MAExBA;mEAGsDA;MAHnBA,qBAAlBA;MAESA,yBADEA;MAEhCA,wIADuDA,YACzCA,mBAAWA,iDAC3BA;K;qBAEWC;MAC8BA,yBAAlBA;iCACWA;MAGzBA,kBAFiBA;QAAYA,WAItCA;MAFEA,qBAD8CA,4BAEhCA,qDAChBA;K;;;;;mBAoGiBC;mBACXA;MAAJA;QAA4BA,SAG9BA;MADEA,WADAA,kBAlKFA,yBAoKAA;K;mBA0DSC;MAAaA,gCAAqBA,KAAIA;K;kBAE1CC;MAAUA;;;QAEXA;;QAFWA;QAGXA;QACAA;;IAEJA,C;yBAEKC;MAAkBA;;;;QAEnBA;;QAFmBA;QAGnBA;QACAA;;IAEJA,C;0BAEKC;MAAwBA;;;;;QAEzBA;;QAFyBA;QAGzBA;QACAA;;IAEJA,C;sBAEgBC;MAEdA,OAAOA,6CADUA,4CAAiBA,mBAEpCA;K;2BAEwBC;MAEtBA,OAAOA,kDADUA,8EAAsBA,2BAEzCA;K;2BAQgBC;MAEdA,OAAOA,oDADUA,0BAAiBA,2CAEpCA;K;kCAEiBC;MAEfA,OAAOA,yDADUA,kDAAsBA,+BAEzCA;K;YAQSC;MACMA;;;MACSA;QAAuBA,aAe/CA;MARgBA;MACZA;QACEA;MAEFA,YAIJA;K;2BAIKC;MACCA;MAKkDA;2BAL5BA;MADJA;yBAGuCA;MAA/BA;MAE9BA,OAAOA,cAD6CA,8DAGtDA;K;qCAEKC;;6BACuBA;MADnBA;yBAGsDA;MAA/BA;MAE9BA,OAAOA,cAD8BA,sEAGvCA;K;aAEEC;MACIA;wBAIsDA;2BAJhCA;MADpBA;yBAGuDA;MAA/BA;MAE9BA,OADWA,wFAAyBA,YACtBA,yCAChBA;K;kBAEEC;MACIA;qDAIsDA;MAAGA;2BAJnCA;MADZA;yBAG+CA;MAA/BA;MAE9BA,OADgBA,sGAAyBA,YAC3BA,kDAChBA;K;mBAEEC;MACIA;mEAIsDA;MAAGA;MAAMA;2BAJzCA;MADNA;yBAGyCA;MAA/BA;MAE9BA,OADiBA,0HAAyBA,YAC5BA,6DAChBA;K;0BAEgBC;MACVA;wBAIsDA;2BAJhCA;MADOA;yBAG4BA;MAA/BA;MAE9BA,OADwBA,iGAAyBA,YACnCA,gDAChBA;K;+BAEwBC;MAClBA;qDAIsDA;2BAJhCA;MADuBA;yBAGYA;MAA/BA;MAE9BA,OAD6BA,2GAAyBA,YACxCA,oDAChBA;K;gCAE8BC;MAExBA;mEAIsDA;2BAJhCA;MAFmCA;yBAIAA;MAA/BA;MAE9BA,OAD8BA,iIAAyBA,YACzCA,wDAChBA;K;qBAEWC;MACLA;MAM4DA;2BANtCA;MADJA;yCAGyBA;kCACbA;QAAYA,WAIhDA;MAHsCA;MAEpCA,OAAOA,cADuCA,8EAEhDA;K;yBAEKC;MACCA;MAIsDA;2BAJhCA;MADNA;yBAGyCA;MAA/BA;MAE9BA,OAAOA,cAD2CA,8CAEpDA;K;mBAEMC;MACAA;MAIgEA;2BAJ1CA;MADXA;yBAG8CA;MAA/BA;MAE9BA,OAAOA,cADqCA,wDAE9CA;K;aAUKC;;6BACuBA;MADlBA;yBAGqDA;MAA/BA;MAE9BA,OAAOA,cAD+BA,iDAExCA;K;cAjRwBC;;K;mBACAC;;K;oBACAC;;K;2BACAC;;K;gCACAC;;K;iCACAC;;K;wBACYC;;K;4BACIC;;K;sBACNC;;K;8BACQC;;K;gBACdC;;K;eACDC;;K;8BACeC;;K;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuGtBC;YAAXA;MAAMA,WAAKA,mBAAIA,oBAAWA;K;gBAA1BC;;K;;;YAKAC;MAAcA;;MAALA,YAAKA,yBAASA,aAAYA,4BAAIA;K;gBAAvCC;;K;;EAWWC;YAAXA;MAAMA,WAAKA,wBAAWA,YAAWA;K;;;;YAKjCC;MAAcA;MAALA,WAAKA,+BAAgBA,aAAYA,mBAAIA;K;gBAA9CC;;K;;;YA8IsBC;;;eAC7BA;MtBx9BFA,oBsBw9BEA;eACIA;MAAJA;QAAwBA;MHralBA;MACyBA;;IGsahCA,C;;;EAoKeC;cApCYC;MACxBA,QAAMA,iCAA4CA;K;mBAC1BC;MACxBA,QAAMA,sCAAiDA;K;oBAC/BC;MACxBA,QAAMA,uCAAkDA;K;2BAChCC;MACxBA,QAAMA,8CAAyDA;K;gCACvCC;MACxBA,QAAMA,kBAA8DA;K;iCAC5CC;MACxBA,QAAMA,kBAA+DA;K;wBACjCC;MACpCA,QAAMA,2CAAkEA;K;4BAChCC;MACxCA,QAAMA,+CACgCA;K;sBACJC;MAClCA,QAAMA,yCAA8DA;K;8BAC1BC;MAC1CA,QAAMA,kBACkCA;K;gBACZC;MAC5BA,QAAMA,mCAAkDA;K;eAC7BC;MAC3BA,QAAMA,kCAAgDA;K;8BACZC;MAC1CA,QAAMA,kBACkCA;K;gBAGlCC;MAAUA,WAAIA;K;qBAKhBd;MAAQA,kCAAQA;K;mBAMPe;gBACXA;MAAJA;QAA2BA,SAE7BA;MADEA,mCA7kBFA,yBA8kBAA;K;mBAQSC;MAAaA,WAAIA;K;kBAIrBC;MAAUA;;;aAEGA,kBAAgBA;UAC5BA;UACAA,MAMNA;;QAJIA;;QANWA;QAOXA;QA4DFA,kDAAkDA;;IAzDpDA,C;yBAEKC;MAAkBA;;;;aAELA,kBAAgBA;UAC5BA;UACAA,MAMNA;;QAJIA;;QANmBA;QAOnBA;QAgDFA,kDAAkDA;;IA7CpDA,C;0BAEKC;MAAwBA;;;;;aAEXA,kBAAgBA;UAC5BA;UACAA,MAMNA;;QAJIA;;QANyBA;QAOzBA;QAoCFA,kDAAkDA;;IAjCpDA,C;sBAEgBC;MACdA,OAAOA,2EACTA;K;2BAWgBC;MACdA,OAAOA,gFACTA;K;kCAEiBC;MACfA,OAAOA,wFACTA;K;YAOSC;MAAkBA,WAAIA;K;2BAI1BC;MACHA,oDAAkDA;IACpDA,C;qCAEKC;MACHA,OAAOA,wDACTA;K;aAEEC;wBACgDA;WAA7BA,oBAAUA;QAAYA,iBAE3CA;MADEA,OAAOA,mCACTA;K;kBAEEC;qDACgDA;MAAEA;MAAFA,KAA7BA,oBAAUA;QAAYA,oBAE3CA;MADEA,OAAOA,iDACTA;K;mBAEEC;mEACgDA;MAAEA;MAAMA;MAARA,KAA7BA,oBAAUA;QAAYA,2BAE3CA;MADEA,OAAOA,6DACTA;K;0BAEgBC;MAA8BA,kCAACA;K;+BAEvBC;MAA2CA,+DAACA;K;gCAEtCC;MAE1BA,6EAACA;K;qBAEMC;;MAAsDA,WAAIA;K;yBAEhEC;MACHA,2CAAyCA;IAC3CA,C;mBAEMC;MACJA,OAAaA,+BAAuBA,8BACtCA;K;aAMKC;MQ51CLA;IR81CAA,C;;EA5EoBC;YAAXA;MAAMA,WAAKA,mBAAOA,WAAEA;K;gBAApBC;;K;;EAaWC;YAAXA;MAAMA,WAAKA,wBAAWA,GAAEA;K;;;;YAIxBC;MAAcA;MAALA,WAAKA,+BAAgBA,IAAGA,mBAAIA;K;gBAArCC;;K;;;YA0HiCC;;;;;;;;cAGlCA;UACGA,wBAAOA,gBAAUA;;UADxBA,iBAGSA;UACFA,wBAAOA,eAASA;;;QAPeA;QAStCA;QACcA;QAAdA;UACEA;;UAEAA;;IAGLA,C;;;ESjhCDC;gBA3WQC;MAAUA,+BAAOA;K;iBAChBC;MAAWA,qCAAYA;K;cAGhBF;MACdA,uCAAWA,sBAsWbA,kCArWAA;K;mBAMKG;MACHA;;sBACgBA;QACdA,wCAoOKA,aA7NTA;aANSA;QAIEA,WAHIA;QACXA,kCAiOKA,aA7NTA;;QAFIA,+BAEJA;K;oBAEKC;qBACQA;MACXA;QAAkBA,YAGpBA;MADEA,OAAOA,wBADMA,uCAEfA;K;YAYWC;MACTA;;sBACgBA;QAC8BA;QAA5CA,SAOJA;aANSA;mBACMA;QAC8BA;QAAzCA,SAIJA;;QAFIA,OAAOA,gBAEXA;K;YAEEC;;mBACWA;MACXA;QAAkBA,WAIpBA;MAHeA;MACDA;MACZA,2CACFA;K;eAEcC;;;MACKA;MAGkBA;MAHnCA;uBACgBA;QAEdA,kDADqBA,YAAqBA;aAErCA;oBACMA;QAEXA,+CADkBA,SAAeA;;QAGjCA;IAEJA,C;YAEKC;;;MAGyBA;MAG0BA;kBAL3CA;MACXA;QAAiCA,YAAfA;MACPA;mBAEPA;MAAJA;QACEA;;aAEAA;;QAEYA;QACZA;;;;;eAKEA;;;IAGNA,C;cASEC;MAMSA;MAAPA,SAEJA;K;eAEEC;;oBACWA;MACXA;QAAkBA,WAcpBA;MAbaA;mBAEkBA;MAAjBA;MACZA;QAAeA,WAUjBA;;WAREA;sCAOOA;;;MAAPA,aACFA;K;eASKC;;;;MACSA;yBACkBA,gBAErBA,uBAFTA;kBAESA;QAAPA,cAAOA,gBAASA;0BACgBA;UAC9BA,sBAAUA;;IAGhBA,C;oBAEKC;;kBACCA;MAAJA;QAAmBA,SAgDrBA;MA/CoBA,wBAAKA;;qBAITA;MACdA;QAEsCA;;QACpCA;+BAEwCA;UACtCA;;;;kBAKOA;MACXA;QAEsCA;;QACpCA;;UAKEA;;;kBAKOA;MACXA;QAEsCA;;QACpCA;uBAGqCA,MADEA;;UAErCA;kCAEwCA;YACtCA;;;;MA3CSA,8BA+CCA;MAChBA,YAAOA,eACTA;K;0BAEKC;;MACwBA;MAIAA;eAsCpBA;;YAxCLA;;MAEFA;IACFA,C;wBAyBIC;MAIFA,OAA8BA,iCAChCA;K;kBAmCKC;MAEHA,aADWA,6BAEbA;K;wBAEIC;MACFA;;QAAoBA,SAMtBA;;MAJEA;QACMA;UAAqCA,QAG7CA;MADEA,SACFA;K;;EzBnUqCC;wByBoVjCA;MAIFA,yCACFA;K;wBAEIC;MACFA;;QAAoBA,SAMtBA;;MAJEA;;QACEA;UAAkDA,QAGtDA;;MADEA,SACFA;K;;;gBAmDQC;MAAUA,gBAAKA,oBAAOA;K;iBACrBC;MAAWA,gBAAKA,0BAAYA;K;kBAErBC;MAyBhBA,aAxBoCA;MAAlCA,qCAAwCA,qBAwB1CA,2CAvBAA;K;gBAEKC;MACHA,OAAOA,gCACTA;K;;;iBAqBMC;MAAWA,gCAAQA;K;gBAEpBC;;oBACQA;sBACEA;kBACmBA;qBAAKA;QACnCA,sBAAUA;;QAEVA;QACAA,YASJA;;QAPIA;aAIAA;QACAA,WAEJA;;K;8BAtBEC;;K;;;EAwzBFC;eAEOA;MAAaA,4BAAIA,sBAFxBA,6BAE2CA;K;kBAQ3BC;MA4XhBA;sDA3X0CA,6BAA7BA,uBA2XbA;QACE35B,0BAAaA;MA5Xb25B,SACFA;K;gBAEQC;MAAUA,+BAAOA;K;iBAChBC;MAAWA,qCAAYA;K;gBAG3BC;MACHA;;sBACgBA;QACdA;UAAqBA,YAWzBA;QATIA,OADmBA,wDAUvBA;aARSA;mBACMA;QACXA;UAAkBA,YAMtBA;QAJIA,OADmBA,qDAKvBA;;QAFIA,OAAOA,wBAEXA;K;iBAEKC;qBACQA;MACXA;QAAkBA,YAGpBA;MADEA,OAAOA,4BAAiBA,CA+NbA,8CA9NbA;K;eA+BMC;mBACAA;MAAJA;QAAoBA,sBAAUA;MAC9BA,oDAAcA,UAChBA;K;cAEMC;mBACAA;MAAJA;QAAmBA,sBAAUA;MAC7BA,oDAAaA,UACfA;K;WAGKC;MACHA;2CAAqBA;MAArBA;QAGSA,eAFOA;QAEdA,yDADqBA,YAAqBA,oDAS9CA;aAPSA;QAGEA,YAFIA;QAEXA,sDADkBA,SAAeA,iDAKrCA;;QAFIA,OAAOA,iCAEXA;K;wBAEKC;MACCA;2CAEwBA;kBAFjBA;MACXA;QAAiCA,YAAfA;MACPA;mBAEPA;MAAJA;QAC4BA;QAsJJA;;;QAnJVA;UACIA,YAKpBA;oBAJ8BA;;MAG5BA,WACFA;K;cAEKC;MACHA;;QACEA,OAAOA,mCAAsBA,kBAMjCA;WALSA;QACLA,OAAOA,mCAAsBA,eAIjCA;;QAFIA,OAAOA,uBAEXA;K;eAEKC;;oBACQA;MACXA;QAAkBA,YAYpBA;MAXaA;mBAEkBA;MAAjBA;MACZA;QAAeA,YAQjBA;oCAFcA;;;MAAZA;MACAA,WACFA;K;0BAiCKC;0CAC6CA;MAA7BA;QACDA,YAGpBA;MAFiCA;MAC/BA,WACFA;K;6BAEKC;MACHA;;QAAmBA,YAMrBA;MALqBA;MACnBA;QAAkBA,YAIpBA;MAHEA;;MAEAA,WACFA;K;6BAEKC;UAIHA,+CAA4BA;IAC9BA,C;kCAGmBC;MA2LnBA;6EA1LmDA;eAC7CA;aACFA,2BAASA;;oBAEiBA;YACrBA;aACLA,yBAAaA;;;MAGfA;MACAA,WACFA;K;mBAGKC;;uBACgCA;mBACJA;MAC/BA;oCACiBA;aACfA;;gBAESA;MAEXA;oCACiBA;aACfA;;YAEKA;;MAGPA;IACFA,C;wBAcIC;MAKFA,OAAkCA,qCACpCA;K;wBAoBIC;MACFA;;QAAoBA,SAOtBA;;MALEA;QAEWA,iBAALA,GAAKA;UAAqBA,QAGlCA;MADEA,SACFA;K;;;;;iBAyHMC;MAAWA,gCAAQA;K;gBAEpBC;;kBACmBA;eAAlBA,kCAAuBA;QACzBA,sBAAUA;;kBACDA;QAAJA;UACLA;UACAA,YAMJA;;UAJIA,gEAAiBA;eACjBA,0BAAQA,kBAAMA;UACdA,WAEJA;;;K;8BAnBEC;;K;;;;gBwGroDMC;MAAkBA,aAARA;8BAAcA;K;YAErBC;MAAiBA,OJoCEA,IIpCFA,+BAAkBA,2BAAMA;K;;;YtGiFpCC;MACZA,yBAAOA,kBAAKA;IACbA,C;;;;;YElBaC;MACZA,yBAAOA,kBAAKA;IACbA,C;;;;EpC6OHxrB;kBoI9QgBA;MAAYA,oCpIgRHA,2BoIhRGA,yBpI8Q5BA,qCoI9QiDA;K;iBAE/CyrB;MAAwBA,OAAIA,4BAAOA;K;iBAgB5BC;MAAWA,sCAAWA;K;eAIzBC;MACAA;QAAaA,sBAA2BA;MAC5CA,OAAWA,wBACbA;K;cAOMC;MACAA;QAAaA,sBAA2BA;MAC5CA,OAAWA,sBAACA,8BACdA;K;aAwGYC;;MAA0BA,OpIwNtCA,4EoIxNqEA,WpIwNrEA,4EoIxNuEA;K;uBA0C/DC;MAGQA;;MAAIA,qCAASA;MAI3BA,YAAoBA,+BAApBA;QACEA,uCAAgBA;MAElBA,aACFA;K;cAXQC;;K;aAaDC;MACWA;;MAChBA,YAAoBA,+BAApBA;QACEA,gBAAeA;MAEjBA,aACFA;K;cAiBKC;MACHA;kBAAyBA,+BAAzBA;QACUA;UACDA;UACLA,WAINA;;MADEA,YACFA;K;iBAIKC;MACeA;;MADNA;;MAKDA;MACXA;QACMA,oCAAiBA;MAElBA;IACPA,C;iBAmGKC;MAASA;;MACDA,yCAAiCA;MAC5CA;QACMA;IAERA,C;gBA0IOC;MAAcA,OAAaA,uDAAoCA;K;;;;Y9FzfxDC;;;aACHA;YACHA;QAEFA;eACAA;M/BqhBWA;QA2Bf92B;MA3Be82B;I+BlhBZA,C;;;;cAwEOC;MAAsBA;MAAJA,OAAIA,gFAA4BA;K;eACzDC;MACHA;;MAAcA,gCAAdA;;QACEA,mBAAgBA;;IAEpBA,C;aA0CYC;MACNA;;MAAiBA;MACAA,gCAArBA;;QACcA,8BAAmBA;QAC/BA,yBAAaA,WAAaA;;MAE5BA,aACFA;K;mBAkBKC;MAA2BA,uBAAKA,kBAAaA;K;gBAC1CC;MAAUA;aAAKA,iBAAMA;K;iBACpBC;MAAWA;aAAKA,kBAAOA;K;gBAGzBC;MAAcA,OAAQA,2BAAiBA;K;;;;cA2F5CC;MACAA,sBAAMA;IACRA,C;;EAkC8BC;cAAlBA;MAAkBA,oCAAmBA;K;YACtCC;MAAkBA,+BAASA;K;mBAcjCC;MAA2BA,mCAAqBA;K;eAEhDC;MACHA,+DAAaA;IACfA,C;iBAESC;MAAgBA,aAALA;+BAAYA;K;gBAExBC;MAAeA,aAALA;8BAAWA;K;cACbC;MAAQA,OAAKA,IAALA,gBAASA;K;cAC/BC;MAAsBA,iCAAgBA;K;gBACjCC;MAAcA,8BAAeA;K;aASxBC;MACRA,wGAAiBA,0BAAUA;K;;;EAqB/BC;cAEYA;MACRA,iCAA4BA,+BAHhCA,mEAGoDA;K;;;kBCyMpCC;MAAYA;aAmS5BA,qCAEmBA,aACcA,0BACTA,QAJxBA,2CAnSuDA;K;iBAU9CC;MAAWA,0BAASA,MAAKA;K;gBAE1BC;MAAUA,YAACA,aAAQA,aAAUA,OAAOA,kBAAWA;K;eAEjDC;;iBACAA;qBAASA;QAAOA,sBAA2BA;eACxCA;;8BAAMA;MAAbA,SAAOA,IACTA;K;cAEMC;;iBACAA;iBAASA;MAAbA;QAAoBA,sBAA2BA;eACxCA;aAA6BA;MAAfA;MAAdA;8BAAMA;MAAbA,SAAOA,IACTA;K;iBAQEC;MtC/SqBA;;MAErBA;QAEEA,kBAAiBA;gBsC6SZA;aAAiCA;iBAAzBA;MAARA;8BAAMA;MAAbA,SAAOA,IACTA;K;aA4FKC;;iBACCA;qBAASA;0BACcA,0BAAsBA,OAAOA;UACpDA;aAEFA,cAAQA;QAKsBC;;IAFlCD,C;gBAEOC;MAAcA,0DAAiDA;K;mBAepEC;;kBACIA;sBAASA;QAAOA,sBAA2BA;;gBAEpCA;;8BAAMA;iBAANA;MACXA;WACAA,eAASA,kBAAcA,OAAOA;MAC9BA,aACFA;K;wBAyCKC;;;MACaA;MAAhBA,+CAAOA;gBACEA;gBAAcA,OAAOA;MAAfA;WAAfA;eACIA;QA0CeA;;;kBACPA;kBAAgBA;kBAATA;QACnBA;QACAA,2DAAiCA,aAAOA;aACxCA;aACAA,cAAQA,OAAOA;QACfA;;;IA9CFA,C;gBA9QQC;;K;;;;iBA4WFC;MAAWA,gCAAQA;K;gBAEpBC;;kBACHA;eAA0BA,0BA5GOA;QAC/BA,kBAAMA;gBA4GJA;sBAAaA;QACfA;QACAA,YAKJA;;aAHoBA;;8BAAMA;MAAxBA,iCAAkBA;WAClBA,mBAAaA,mBAAyBA,OAAOA;MAC7CA,WACFA;K;8BAnBEC;;K;;;EkGzzBkBC;iBAAXA;MAAWA,kCAAWA;K;aAkFnBC;;MACRA,OzI+PJA,yEyI/P8CA,WzI+P9CA,6EyI/PgDA;K;gBAUzCC;MAAcA,OAAaA,mDAAoCA;K;aAgCjEC;MACHA;;;mCACOA,SADPA;UACmBA,YAGrBA;MADEA,WACFA;K;eA2CMC;MACaA;MACZA;QACHA,sBAA2BA;MAE7BA,OAAUA,gBACZA;K;cAEMC;MACaA;;MACZA;QACHA,sBAA2BA;;QAIfA;aACLA;MACTA,aACFA;K;;;;;;;aAkHOC;MAAWA;;eAAuBA;K;iBAKhCC;MAAWA,WzGo8BFA,0ByGp8BaA;K;cAa1BC;MACHA;mDAAkBA;MAAlBA;QAA4BA,cAA5BA;IACFA,C;aAuCOC;MACLA;8CAAuBA;MAAhBA;;MAAPA,SACFA;K;uBAEQC;;;iBAC0BA;MAAIA,0CzGy4BpBA;MAHLA,gDAA6BA,+BAA7BA,wByGp4BXA;QAA+BA;QAAPA,yCzGmwCTA;;MyGlwCfA,aACFA;K;cALQC;;K;aAOIC;;MACRA,OzItCJA,yEyIsC8CA,WzItC9CA,6EyIsCgDA;K;gBAUzCC;MAAcA,OAAaA,mDAAoCA;K;aAE1DC;;MAA4BA,OzINxCA,gDyIM+DA,WzIN/DA,+ByIMiEA;K;cAqB/DC;MACIA;MAAQA;;;MzG61BDA,8CAA6BA,+BAA7BA,uCyG51BXA;QAAgCA,gCzG2tCjBA;MyG1tCfA,YACFA;K;aAEKC;;;MzGw1BQA,8CAA6BA,+BAA7BA,iByGv1BXA;mCACOA,WzGqtCQA;UyGrtCIA,YAGrBA;MADEA,WACFA;K;WAoBKC;;;MzG+zBQA,8CAA6BA,+BAA7BA,iByG9zBXA;kCACMA,czG4rCSA;UyG5rCMA,WAGvBA;MADEA,YACFA;K;eAkBMC;MzGwyBOA,6CAA6BA,6BAA7BA;MyGtyBNA;QACHA,sBAA2BA;MAE7BA,SzGkqCeA,qByGjqCjBA;K;cAEMC;MzGgyBOA;iDAA6BA,6BAA7BA;MyG9xBNA;QACHA,sBAA2BA;;mBzG4pCdA;ayGvpCNA;MACTA,aACFA;K;;;;;;;;;YjGhYSC;;iBAwHeA;MAvHtBA;QACEA,OAAOA,wBAAYA,cAQvBA;WAPSA;QACLA,WAMJA;;QAHyCA,WAAlBA;QACnBA,mEAEJA;;K;gBAEQC;MAAUA;cA4GMA;QA5GQA;QAAaA;;QAASA,mCAAeA;MAAnDA,SAAyDA;K;iBAElEC;MAAWA,kCAAWA;K;cAGVC;MACKA,QAsGFA;QAtGLA,+BAAoBA,UAEvCA;MADEA,OA8KFA,+BA7KAA;K;mBAsCKC;MACqBA,QA6DFA;QA7DLA,+BAAoBA,kBAGvCA;MAFEA;QAAoBA,YAEtBA;MADEA,gDAAoBA,gBACtBA;K;cASAC;MACuBA,QAgDCA;QAhDiBA,WAEzCA;MADEA,OAAOA,kBAAWA,gBACpBA;K;eAiBKC;MACHA;MAA6CA;MAArBA,SA4BFA;QA5BLA,gCAAoBA,eAsBvCA;MArBsBA;MACpBA,oBAAyBA,SAAzBA;kBACeA;qBAIYA,WACNA;QAAnBA;UACUA,sCAAoCA;eAC/BA;;QAIfA;0BAIqBA;UACnBA,sBAAUA;;IAGhBA,C;sBAQyBC;MAAaA,mBAFdA;MAOtBA,iBACFA;K;6BAEaC;MAAYA;0BAVDA;MAYjBA,sCAAOA;MACZA;QACqBA,YAAZA,+CAAoDA;MAE7DA,WACFA;K;gBAEqBC;MACnBA;MAAwBA,SApBFA;QAoBLA,+BA0BnBA;MAtBgCA;MACVA;MACpBA,qBAAyBA,iBAAzBA;kBACeA;QACbA,yBAAkBA;;MAMpBA;QACEA;;QtB9CFA;WsBqDAA,kBAAYA;MAGZA,YAFAA,eAGFA;K;gBAEAC;MACEA;oDAAkBA;QAAiBA,WAGrCA;MAFeA,sCAAoCA;MACjDA,WAAoBA,yBACtBA;K;;;gBAuBQC;MAAkBA,aAARA;8BAAcA;K;iBAEzBC;mBACEA;YA9EeA;QA+ERA,mBAAKA;;QACbA;QAAQA;mCAAcA;eAAdA;;MAFdA,SAGFA;K;kBAKqBC;mBACZA;YAvFeA;QAwFRA;QAAKA;;QACbA;QtByWRxkC,+BAEyBA,SA1GOwkC,yBAwGhCxkC;;MsB3WEwkC,SAGFA;K;gBAIKC;MAAwBA,sCAAwBA;K;;EkGxTlBC;cAAzBA;MAAyBA,QAmBDA,mCAnBwBA;K;;;eAqChDC;MACJA;MAAeA;MACFA,sDADSA;;MAKTA,mCACEA,sDAFnBA;QACiBA;QACfA;UACEA,sBAAoBA;QAGtBA;mCAAMA;;;MAERA,aACFA;K;;;;iBjGIOC;;;mBAC+CA;MAAnCA;MAMoBA;MAIrCA;QAE+BA;QAAlBA;QAGXA;UACMA;UAAJA;Y1CJQA;YAEDA,yBAAcA;YACdA,yBAAcA;YACRA;Y0CKXA;cAaiCA;;;;;UAL5BA;QAATA;UACcA;kDAAeA;iCAAfA;UACZA;YACSA;YACPA;cAA0BA;YAeLA;;YAdhBA;cAELA;mDlCkbUA,UAAUA;;;gBkChbmBA;;;cAGvCA;cAEAA;gBAA4BA;;YAKPA;;UAHvBA;;clCsaNA;YkCpaqBA;YlCgVDr+B,MAsHlBA;;YkCncMq+B;;;QAGJA,sBAAMA;;MAERA;QACeA;elC8ZWA;QkC7ZxBA;UAIEA;;UAIgCA;UAChCA;YAEEA,sBAAMA;iBAERA;YlCoZWA;kBA2Bfr7B;YkC7aMq7B;;;QAGGA,WlCuamCA;QkCva1CA,6FAoBJA;;MAjBeA;MACbA;QACEA;;QAIgBA;QAChBA;UAEEA,sBAAMA;QAERA;UAEWA;;MAGbA,aACFA;K;;;;;;;;sBkG3CQC;MAAMA;MACZA;MAuV+BA,0BAtVHA,kBAsVqBA;MAtV5BA,SAEvBA;K;iBAsBgBC;MACQA,QAAaA,iBAErCA;K;;;;iB1FpHgBC;MAAWA,QAAMA,cAAaA;K;;;eAgBpCC;MACJA;MAAeA;MACFA,kDADSA;MAEbA;MACbA;QAAiBA,wBAkBnBA;;MA0BAA;MAxCoBA;MACIA;MATPA;MAUfA;QAIqBA;QAJrBA;wBAQoBA;;MAGpBA,OAAeA,kDAA2BA,cAC5CA;K;;;uBAyCKC;;kBAODA;;MANFA;QAsOQA;QAtORA;;kBAMUA;;QAARA;gCAAOA;;kBACCA;QAARA;gCAAOA;;kBACCA;QAARA;gCAAOA;;aACCA;QAARA;gCAAOA;;QACPA,WAYJA;;kBALYA;;QAARA;gCAAOA;;kBACCA;QAARA;gCAAOA;;aACCA;QAARA;gCAAOA;;QACPA,YAEJA;;K;mBASIC;MACFA;MAAqCA;QAGnCA;MAIeA,eAqBXA,6EAtBNA;QACiBA;QAEfA;oBACMA;UAAJA;YAAoCA;eAC5BA;;eACHA;mBACDA;YAAoCA;UAGNA;UAChBA,sCADCA;;eAKnBA;oBACMA;;UAAJA;YAAwCA;eAChCA;UAARA;kCAAOA;;eACCA;;;UAHVA;oBAMMA;UAAJA;YAAwCA;oBAChCA;UAARA;kCAAOA;;oBACCA;UAARA;kCAAOA;;eACCA;UAARA;kCAAOA;;;;MAIbA,kBACFA;K;;;eA0FOC;MAGDA;MAA8CA;MAArCA;MACbA;QACEA,aA0BJA;MAtBmBA,4CADMA;MAIRA;MAGfA;QACyBA;QAEvBA;UACEA,gBAYNA;Q1C0OAA;Q0C7O6BA;QA4D7BA;;QArF+DA;QAAOA;QAwBxCA;;;Q1C8O9BA;M0CjLAA;aA7DUA;MACRA;MACAA;iB1C2Q4CA;M0C1Q5CA,sCACFA;K;;;aAgFKC;MACHA;MAG2CA;cAzBjBA;QAwBhBA;QAANA;;IAQNA,C;eAEKC;MACCA;;;mBAAQA;2BACQA;wBACHA;WAGjBA,oBADAA,uBADAA;;QAqEeA,qDALTA,8BA1DNA;;YAEEA;;gBAEIA;kBACEA;gBAESA;gBACNA;oCAAKA;gBAAVA;kBAGUA,gCACsBA;kBAD5BA;;kBASaA;kBACfA;kBACAA;;uBAnBJA;cAsBqBA;cAARA;iEAAOA;4BAAPA;gBAIHA,oDACwBA;gBAD9BA;;cAQJA;gBAEUA,sEAEGA;gBAFTA;;wBAQCA;gB1CAS9+B;mB0CGd8+B;;UAGFA;YACiBA;YACfA;mBACEA;cACOA;cAFTA;cAG2BA;cAGzBA;gBAAmBA;;cAEAA;;YAAVA;YAMPA;+BAAKA;YAATA;cAGUA,0DAC+BA;cADrCA;;cAHJA;cAWEA;gBACUA;;;gBAERA;;cAEFA;gBACUA;;;gBAERA;;cAGFA;gBACUA;;;gBAERA;;cAGMA,gCACsBA;cAD5BA;;;UAWNA;;MAEFA;aACEA;aACAA;aACAA;;IAEJA,C;;;;S1CzLcC;MAAEA;oBAGQA;MAFpBA,0CACAA,iBAnC8BA,eAoCVA;K;kBoC8HhBC;MAAuBA,aAAVA;MAADA,sEAAsCA;K;gBA2EnDC;MACMA;mCpCxOcA;YoCyOdA,sBpCtOeA;YoCuOfA,sBpCpOaA;YoCqObA,sBpClOcA;coCmOZA,sBpChOcA;coCiOdA,sBpC9NcA;aoC+NfA,wBpC5NoBA;;MoC+N9BA,SAIJA;K;;;;StB7WcC;MAAEA;oBAC0CA;MAAtDA,0CAAqBA,oBANCA,UAMgCA;K;kBAElDC;MAAYA,OAAUA,iCAAVA,WAAkBA;K;gBAwB/BC;MAUWA;;iBA1CQA;MA+CxBA;QACEA,aAtJEC,oCA6JND;MAL2BA,4BArENA;MAsEMA,4BA/DNA;MA6CHA,iDAoBHA;MACbA,YAhFiBA,oDAgFCA,6BAAiBA,6BAAiBA,eACtDA;K;;;YAtBEE;MACEA;QAAiBA,aAMnBA;MALEA;QAAgBA,cAKlBA;MAJEA;QAAeA,eAIjBA;MAHEA;QAAcA,gBAGhBA;MAFEA;QAAaA,iBAEfA;MADEA,kBACFA;K;;;;YAEAC;MACEA;QAAaA,aAEfA;MADEA,cACFA;K;;;;;gBpBxJKC;mBACDA;MAAJA;QACEA,8BAAkCA,wBAGtCA;MADEA,yBACFA;K;;;;;;gBAmBOC;MAAcA,uBAAgBA;K;;;oBAgE1BC;MAAcA,kCAAoBA,wBAAwBA;K;2BAC1DC;MAAqBA,SAAEA;K;gBAE3BC;;kBAEDA;;gBAGgBA;MAAkCA;MACpCA;gBACbA;QAAWA,aAKlBA;MAHuBA;MACKA,uCAAaA;MACvCA,+CACFA;K;;;;;;oBAqJWC;MAAcA,mBAAYA;K;2BAC1BC;MAAkBA;0BACpBA;gBAEHA;MAAJA;kBACMA;QAC0CA;;kBAGrCA;QAAJA;UAC0CA;aAC1CA;UAC0BA,mDAAQA;;UAKDA;;MAExCA,kBACFA;K;;;oBAoCWC;MAAcA,mBAAYA;K;2BAC1BC;MAAkBA;yBACpBA;MACHA,uCAAoBA;MACpBA;iCAAaA;MAAjBA;QACEA,qCAMJA;eAJMA;MAAJA;QACEA,+BAGJA;MADEA,wCAAqCA,OACvCA;K;;;;;;gBAuFOC;MAAcA,uCAAyBA,QAAQA;K;;;;;;gBAgB/CC;mBAAoBA;MAANA,sEAEMA;K;;;;;;gBAYpBC;MAAcA,2BAAaA,QAAQA;K;;;;;;gBAgBnCC;mBACDA;MAAJA;QACEA,kDAIJA;MAFEA,sDACaA,8BACfA;K;;;gBAMOC;MAAcA,sBAAeA;K;;;;gBAQ7BC;MAAcA,uBAAgBA;K;;;;gBAgB9BC;mBAAcA;gJAEoDA;K;;;gBQnjBlEC;MAELA,2BAAoBA,QACtBA;K;;;;;;;gBA8DOC;MAGiBA;iBADlBA;;qBAGcA;2BACSA;MAC3BA;QAEEA;kDAAqDA;;UAArDA;;UAIIA;QAAJA;UAEaA,qBADAA;UAGXA,6BAgENA;;QA3DIA;UACaA;UACXA;YACEA;cACEA;YAEUA;;iBAEPA;YACLA;YACYA;;;;QAyCPA;8BAhCYA;QACrBA;UACaA;UACXA;YAKWA;YAHTA;;;QAQJA;UAIEA;YACQA;;;;;YAEDA;cACGA;;;;cAIAA;cACFA;;;;;;UAI6BA;UAAPA;UACEA;UACLA;;QAFdA;QAEfA,kDAA4CA,oDADHA,gBAS7CA;;QAFIA,mDAF0BA,2BAI9BA;K;;;;;;;YFlEWC;;iBACLA;MAAJA;QAiCAA;UAA+CA;;UAAlBA;QAA7BA;UACEA,kBAAUA;QAhCVA,qBAGJA;;MAY0BA;MACoBA;MAd5CA,wCACFA;K;eAGcC;MACZA;;MACqDA;eADjDA;MAAJA;;;QAawBA;QACxBA;UqIzGIC;UrI2GSD;;QAEFA;;IAbbA,C;gBsInFOE;MAAcA,wBAAUA,KAAKA;K;;;;;ahIwJxBC;;MAAoBA,qFAA2BA,WAA3BA,6BAA6BA;K;aAgBjDC;;MAA+BA,ObqN3CA,yDarNkEA,cbqNlEA,wCarNuEA;K;gBAsDlEC;MACHA;;QACMA,WADNA;UACoBA,WAGtBA;MADEA,YACFA;K;YA0FOC;MACuBA;;MACvBA;QAAqBA,SAc5BA;MAZEA;QNwMkDA;;UMtMrBA;eAClBA;;QAEgBA;eAClBA;UAEoBA;;MAG7BA,sCACFA;K;YAhBOC;;K;uBAqCCC;MACNA,gFACFA;K;cAFQC;;K;aAaDC;MAAWA,OAAHA,qFAAoBA;K;gBAS3BC;MAAOA;sBACDA;MAEEA;MACdA,gBAAOA;QACLA;MAEFA,YACFA;K;iBAOSC;MAAWA,QAACA,wBAASA,YAAUA;K;iBAsE5BC;;MACVA,ObiJFA,6DajJoCA,cbiJpCA,4CahJAA;K;eASMC;MACaA;MACZA;QACHA,sBAA2BA;MAE7BA,OAAUA,gBACZA;K;cAYMC;MACaA;;MACZA;QACHA,sBAA2BA;;QAIfA;aACLA;MACTA,aACFA;K;iBAiGEC;MAASA;MAEEA;MAEXA;;QACEA;UAA2BA,cAI/BA;QAHIA;;MAEFA,sBAAiBA;IACnBA,C;gBAkBOC;MAAcA,OAAaA,oDAAqCA;K;;;;;EgCxQvCC;gBAAzBA;MAAcA,6BAAWA,eAAMA,qBAAOA;K;;EtCzUnBC;kBAAlBA;MAAYA,uDAAcA;K;gBuInD3BC;MAAcA,aAAMA;K;;;EvIiCIC;SAHjBC;MAAaA,qBAAsBA;K;kBAGzCD;MAAYA,wCAA+BA;K;gBAG5CE;MAAcA,yBHmlBGA,IADRA,wCGllBiDA;K;qBASxDC;MAAeA,6BAAoBA;K;;;;;;;;;;;gB4DTrCC;MAAcA,uBAAWA;K;;;;6B5DiVxBC;;iBwIhTEA;;QxI6SWC,yBAAWA;ewI7SHD;MAAFA;;;YxIkTrBA;MAAJA;QAA2BA,YAG7BA;MAL4BA;MAI1BA,mBACFA;K;;;EwCiSAE;kBAhDiBA;MAAYA,8BAAaA,QAAOA;K;cAEzCC;;iBACFA;eAAOA;MAAXA;QACEA,sBAAMA;MAGGA;MACXA;QACqBA;QACnBA;UACEA,OAAOA,2CAIbA;;MADEA,WACFA;K;;;iBA4GQC;MAAWA,6BAAiBA;K;gBAuB/BC;;kBACHA,wBAAYA;kBACKA;eAAOA;MAAxBA;aACEA;QACAA,YAeJA;;MAbiBA;MACIA;MACnBA;QACqBA;QACnBA;eACEA;UACoBA,KAApBA;UACAA,WAMNA;;;WAHEA;WACAA;MACAA,WACFA;K;;;;gBxChPQC;MAAUA,qBAAUA,OAAMA;K;gBA4B3BC;mBAAuCA;MAAzBA,sCAAmCA;K;;;;;YyCmmBtDC;MACEA,sBAAMA,uDAA8CA;IACtDA,C;;;;YAiEAC;MACEA,sBAAMA,uDAA8CA;IACtDA,C;YAFAC;;K;;;;YAKAC;MACEA;;QACEA;MAEcA,oBAAMA;MAClBA;0BAAMA;MAAVA;QACEA;MAEFA,YACFA;K;;;;kBA8QSC;MAAYA,qBAASA;K;cAErBC;mBACLA;MAAJA;QAAmBA,SAKrBA;MAJMA;QACFA,OAAOA,wCAAyBA,YAGpCA;MADEA,SACFA;K;cAEQC;mBACFA;MAAJA;QAAmBA,OAAOA,wBAAaA,QAEzCA;MADEA,SACFA;K;eASWC;mBAASA;iCAAYA;K;kBAErBC;mBAAYA;iCAAeA;K;iCA4PlCC;MAaGA;MA+BeA;MAWCA;oBArCLA;MAEDA;sBAIGA;kBAKJA;kBAi0BSA;MAzzBhBA;uBAEaA;MAIfA;MACDA;MAAJA;QACSA,8CA01F4CA;;oBAv1FvCA;QACZA;mC7B36CgBA;;U6Bw4CdA;QAoCGA;UACQA;;MAgBfA,OAzcFA,oDAgciBA,cAMGA,WAIpBA;K;oBApEIC;;K;4BAAAC;;K;sBA2EaC;;qBACFA;MACbA;QAAoBA,aAYtBA;wBAVoBA;MACYA,e7B18CZvhC;Q6B28CFuhC;MAIZA,+BADEA,uChDj+CRngC,yBgDm+CUmgC,oE9BxnDkCA,qC8BwnDHA;MACvCA;MACAA,aACFA;K;mBAwiBOC;MAEDA;MAGJA,kCAAOA;QACLA;QACAA;;MAIYA;MAEdA;;;QACeA;QACbA;UACEA;QAEUA;QAGZA;;UACIA;YACeA;;YADaA;;UADhCA;;UAGEA;QAGFA;QAdKA;;MAgBPA,OAAOA,2DACgBA,oEACzBA;K;eAuGIC;MACFA,OAAOA,kBAAeA,uBACxBA;K;kBAEIC;MAEKA;MAMOA,0BAAOA;QACMA;QACXA;UACeA;UACJA;UACAA,uCAAoBA;;;;;;QAEhCA,uCAA6BA;QAC5BA,yCACYA;;4BAGNA;QACNA;UACeA;UACJA;UAEnBA,8BAAoBA,0BAAoBA;UAC/BA,uCAA6BA;UAC5BA,yCAAkCA;;gCAE1BA;4BACJA;4BACAA;UACJA;8BACMA;YACJA,yCACYA,6BAELA;;YAGPA;cACCA,uCAA6BA;;wBAkD3BA;oB7BhuELC;gB6BkrERD;kBAG2BA,yBAiCVA,gDA7BAA,0BAA6BA;;kBAI/BA,6CAAmCA;;gBAGjCA,qCAAiCA;iCAsBjCA;gBAYDA;kBAhCDA;;kBAMAA;;;YAKLA,yCAAkCA;;;;MAKtDA,OA9tCFA,0FA6tC8BA,8BAAwBA,iCAGtDA;K;sBAISE;MAAgBA,yBAAaA;K;iBAE7BC;MAAWA,yBAAaA;K;kBAExBC;MAAYA,0BAAcA;K;qBAE1BC;MAAeA,6BAAiBA;K;yBAIhCC;MAAmBA,sDAAoBA;K;kBAkBzCC;;kBACDA;MAAJA;QACEA,sBAAMA,2DAAqDA;gBA1nC3CA;MA4nClBA;QACEA,sBAAMA;gBA3nCaA;MA8nCrBA;QACEA,sBAAMA;MzC5+DoBA;;QyCg/DXA;;QAIGA,SA7CGC;UA8CrBD,kBAAMA;QAKgBA;QACxBA;QzCtnEYC,8ByC4kEcA;;;MA+B1BD,SACFA;K;gBAsEOE;;kBACEA;;kBAMHA;eAAOA;kBAvHYC;;QAwHvBD;;oBA7BIC;gBAAUA;;UAIdA;;oBACIA;UAAJA;YzC5qEeC;;;mByC0sENF;kBACLA;QAAJA;;kBACIA;QAAJA;;kBAfOA;;MAAPA,SACFA;K;SAkBcG;MACZA;MADcA;oBAahBA;MAZEA;QAA4BA,WAY9BA;MAXeA;QACOA,SAAhBA;UACsBA,SAxIHA;YAyIDA,SA3vCDA;cA4vCjBA,6BAAcA;gBACdA,6BAAcA;kBACAA,SAAdA;8BAxIeA;;oBAyIGA;;;sBACHA;kCAxIGA;;wBAyIGA;;;0BACHA;;0BADNA;;wBADNA;;sBADGA;;oBADJA;;kBADAA;;gBADAA;;cADIA;;YADIA;;UADNA;;QADXA;eAWFA;K;kBAEQC;MAC+BA,aAA9BA;MAAPA,wBAAOA,kDAAmBA,wBAC5BA;K;uBAx5CaC;;K;;;;;;;;;;YAqE4DC;MACjEA,sBAAMA,uCAAgCA,UAAKA;IAC5CA,C;;;;YAsOYC;MACfA;MAAIA;;gBACEA;UACFA,sBAAMA;;UAENA,sBAAMA;IAGXA,C;;;EA8fiBC;YAAPA;MAAOA,0BAAWA,YAAgBA,4BAAGA,oBAAYA;K;;;;aAsxCtDC;;kBACFA;MAAJA;QAAuBA,SAezBA;gBAZmBA;;6BAAiBA;gBACjBA;aADAA;MACAA;cACDA;MAChBA;QACeA,+DACwBA;QAIZA;;QACMA;MACjCA,YADAA,aA4rCFA,4CA9rCcA,2CACyBA,gCAGvCA;K;gBA8ROC;;iBACFA;;6BAAiBA;eAA2BA;MAA7CA,SAACA,8BAA0DA;K;;;YAgPbC;MAAOA,yBAAaA;K;;;;YAItEC;mBACIA;;iCAAMA;aAANA;MAAMA;MAANA,SAAkDA;K;;;;YAMtDC;MACEA;qBAA0BA,oCAA1BA;QACaA;QACXA;oCAAMA;;;IAEVA,C;;;;YAQAC;MACEA;MAAaA,yDAAyBA,gEAAtCA;QACSA;QAAPA;oCAAMA;;;IAEVA,C;;;EAoNuCC;sBAN9BC;MAAgBA,0BAAcA;K;iBAE9BC;MAAWA,kCAAkBA,sBAAiBA,WAAUA;K;kBACxDC;MAAYA,8BAAcA,eAAcA;K;qBACxCC;MAAeA,iCAAiBA,KAAKA,OAAMA;K;iBAE3CJ;MAAWA,oEAAmBA,cAAuBA;K;iBACrDK;MAAWA,gCAAmBA,kDAAuBA;K;kBACrDC;MAAYA,gCAAmBA,mDAAwBA;K;yBAOvDC;MAAmBA,2DAAqBA,YAAWA;K;gBAWjDC;;;kBACLA;MAAJA;QAAqBA,SAcvBA;gBAbMA;MAAJA;QAA0BA,SAa5BA;MAZMA;kBACFA;WACSA;aACTA;;aACSA;aACTA;;aAzBsCA;aA2BtCA;;;QAEeA;aAAfA;;MAEFA,SACFA;K;kBAIWC;MACLA,aADkBA;iBAAaA;MAAdA,oDACjBA,uBACEA;K;cACGC;MACUA,aAAjBA;yDAAiBA,eAA2BA,iBAAgBA;K;cACxDC;MACNA;MAAIA;QAASA,OAAWA,YAAMA,gDAAeA,sBAAgBA,yBAI/DA;MAHMA;QAASA,SAGfA;MAFMA;QAAUA,UAEhBA;MADEA,QACFA;K;cAEWC;MAAQA,qDAAeA,iBAAYA,aAAYA;K;eAC/CC;MACLA,aADeA;iBAAcA;MAAfA,oDACdA,uBACEA;K;kBACGC;MAC0BA,aAAhCA;iBAAiBA;MAAlBA,cAAuBA,yDAAiDA;K;sBAwB3DC;;oBACHA;kBACFA;iBACNA;;QAA6BA;MACjCA;QAAkBA,QAAOA,WAW3BA;MAVuBA;MACrBA;QACaA;UAETA,+BAAUA;UACFA;;MAGZA,+BAAUA;MACVA,oDACFA;K;eAiBKC;MAGCA,yBAFiBA;MACrBA,4BAA6BA,gBAAUA,mDACnCA,4BACNA;K;sBAIIC;;kBA1HoBA;kBAAiBA;MAfzCA,YAe8CA;QA2H1BA,YAGpBA;MAFEA,wBAAkBA,gDAAmCA,kBAAYA,kBAC7DA,kBAAYA,kBAAYA,uBAA6BA,cAC3DA;K;iCAEIC;MAUGA;MAiCeA;MAWCA;MAvCLA;MAEDA;gBAGJA;MACEA,oEAAeA;MAOdA,6BAAeA;gBAQlBA;MAAJA;QACEA,2DAA2BA;;uB7Bh9HlBpkC;M6Bq9HbokC;MACDA;MAAJA;QACcA,8CA0SuCA;;QAvS5CA,uDAAeA,kBAAYA;QAClCA;mC7B39HgBA;;U6Bs7HdA;QAsCGA;UACQA;;gBAOJA;gBAAcA;MACfA;gBAKCA;gBAAiBA;MACfA,kBADoBA;MAIjCA,OA1/FFA,+DA2/FAA;K;oBApEIC;;K;4BAAAC;;K;eAsEAC;MACFA,OAAOA,kBAAeA,uBACxBA;K;kBAEIC;MAEOA;QAAPA,2CAGJA;MADEA,OAAOA,sBAAeA,uBACxBA;K;oBAOIC;;gBA3NkBA;MA4NpBA;QAAmBA,UA4KrBA;cAvYyBA;MA4NvBA;iBA7NoBA;QA8NlBA;UAAqBA,UA0KzBA;QAxKaA;wBA/MYA,mBAAcA;aAiNnBA;UACFA;;UACEA,oCACFA;QAEdA;UACmBA;UAGjBA,OArPNA,iBAmPwBA,sDACVA,qEAKAA,wBACAA,wBACAA,yBACAA,6BACCA,cAqJfA;;UAlJMA,OAAOA,sBAAeA,iBAkJ5BA;;oBAvXyBA;cAAcA;MAwOrCA;gBArPiCA;QAsP/BA;mBACmBA;;UAGjBA,OAxQNA,iBAsQwBA,mDACVA,kDAGCA,iBACAA,iBACAA,iBACAA,yCAGAA,cAkIfA;;gBAnYyCA;mBAAKA;UAf9CA,SAmRuBA;UAGjBA,wBAFkBA,mDACVA,4CAGCA,iBACAA,iBACAA,iBACAA,iBACAA,kCAEAA,cAoHfA;;QAlHIA,OAAOA,uBAkHXA;;cAxX4BA;;iBAyQPA;;QAGjBA,OAtSJA,iBAoSsBA,mDACVA,kDAGCA,iBACAA,iBACAA,gCAGDA,6BACCA,cAoGbA;;sBAvXyBA;oBAAcA;uCAhBdA;eAySVA;UACTA;QAE0BA;QAG5BA,OA1TJA,iBAwTyBA,gEACVA,kDAGFA,iBACAA,iBACAA,uCAGDA,6BACCA,cAgFbA;;oBAnEwBA;MAItBA,6BAAOA;QAAsCA;MAY3CA;MAFFA;QAAOA;QAA0BA;;QAE/BA;QAFKA;;MAePA;QACEA;QACWA;UAGTA;;YAAoBA;;UACpBA;;;;MAxVsBA,kCAhBNA;QAwXlBA;QAG+BA;;MArYnCA,mCAqY0CA;MAIxCA,wBAHqBA,+DACVA,kDAIFA,iBACAA,iBACAA,uCAGDA,6BACCA,cACXA;K;kBAEOC;MACLA;MAAwBA,SAApBA;QACFA,sBAAMA,2DAAqDA;gBAEzDA;gBAAcA;iBAAKA;sBACHA;UAChBA,sBAAMA;QAGRA,sBAAMA;;MzCx6HoBA;;QyC46HNA;;iBAIlBA,mBAAaA;UAEfA,kBAAMA;QAnWSC,6CAAeA;;MA6VhCD,SACFA;K;kBAiBQE;MAAoCA,aAAxBA;oFAAmBA,WAAaA;K;SAEtCC;MAAEA;oBAGhBA;MAFEA;QAA4BA,WAE9BA;MADEA,OAAaA,4BAAUA,UAAQA,mBACjCA;K;oBAEIC;MAEOA;;aACAA;kBAlbcA,kBAmbMA;aACpBA,sBAAeA;kBA3XPA;kBAA2BA;aAA3BA,wCAAeA;kBAtDCA;MAmbRA;MANzBA,OAnuGFA,0CAuzF8CA,UAmbhBA,6BAC9BA;K;gBAEOC;MAAcA,gBAAIA;K;;;;;;;;;;;;;;;;gBI4uKlBC;MAAcA,uBAA+BA;K;;;;;;;;;;;;wBAo5J/CC;MAKCA;MAAJA;QACEA;IAEJA,C;yBAkBKC;MAAiBA,yHACZA;K;4BAKLC;MAAoBA,4HACfA;K;;;;;gBA8pICC;MACTA;QACEA,sBAGJA;MADEA,OAAeA,gCAAkBA,kBACnCA;K;gBAEOC;MAAcA,uBAA+BA;K;;;;;;;;;;;;;;wBAonC/CC;MAQ0BA;MAJ7BA;QACEA;MAGIA;IACRA,C;mBAkBKC;MACCA;MAMJA,qB4FrjoBEC,sCCiHSA;M7Fq8nBXD,MACFA;K;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAiqdSE;MAAeA,WAAIA;K;2CAKNC;;yBAGeA;;;MADnCA,OAAWA,gCACFA,oBAAcA,8BADZA,eAEbA;K;6BAJsBC;;K;cAAAC;;K;;;cAyGfC;MACDA;eASgBA;QATLA,WAOjBA;MALEA;WAEAA;MACAA;MACAA,WACFA;K;aAoBKC;cAlBiBA;QAmBLA,MAOjBA;;MALEA;IAKFA,C;aARKC;;K;cAYAC;MACCA;eA/BgBA,+BA4BDA;QAGSA,MAG9BA;;MADEA;IACFA,C;kBAEKC;;kBACCA;6BATeA;QAUjBA,iDAAyBA;IAE7BA,C;iBAEKC;;iBACCA;;MAAJA;iBACEA;;QAppoBEA;QAAJA;UACEA,mCAmpoB4BA;;IAEhCA,C;uBA5EcC;;K;;EAgB8BC;YAAfA;MAAOA,WAACA,eAAmBA,sBAAEA;K;;;;gB6F3zlCtDC;;iBACWA;oBAAOA;MACpBA;cACgBA;UAAmBA,QAKrCA;MAHEA;MACAA;MACAA,cACFA;K;YAiBAC;MACEA;;QAAeA,QAkEjBA;MAjEQA;QAASA,QAiEjBA;MAhEEA;QAAcA,QAgEhBA;MA/DEA;QAAiBA,QA+DnBA;;QA7DIA,iB1I6O8BC,Q0IhLlCD;MA3DQA;QAEJA,sBAAUA;MAUNA;QAASA,QA+CjBA;MA9CQA;QAASA,QA8CjBA;MDlFOA,qCAAyBA,gCAAwBA;QCyC7BA,QAyC3BA;MAvCQA;QACOA;kBA5CIA;;kCAAMA;iBA6CjBA,UA7CWA;QA8CfA;UAAkBA,WAoCtBA;;UAnCIA;QA7CFA;QA+CEA,eAAUA;QAGVA,SAAOA,KA8BXA;;MA3BQA;QAMOA;kBA7DIA;;kCAAMA;iBA+DjBA;QAAJA;UAAkBA,WAmBtBA;QAjBIA,OADOA,yBAkBXA;;MAdQA;QACOA;kBArEIA;;kCAAMA;iBAsEjBA,UAtEWA;QAuEfA;UAAkBA,WAWtBA;;UAVIA;QAtEFA;QAyEEA,4BAAoBA;QAGpBA,SAAOA,KAIXA;;MADEA,sBAAUA;IACZA,C;gBAEKE;MAEYA;;;eAECA;MAtFhBA;MAuFAA;QACEA,qCAAUA,YAAKA;MAEjBA,WACFA;K;;;YA5CcC;MACcA,WAAXA,iBAAWA;IACvBA,C;;;;YAwBmBA;MACOA,WAAXA,iBAAWA;IAC1BA,C;;;;gBAiDDC;;iBACWA;oBAAOA;MACpBA;cACoBA;UAAmBA,QAKzCA;MAHEA;MACAA;MACAA,cACFA;K;YAiBAC;MACEA;;QAAeA,QAoDjBA;MAnDQA;QAASA,QAmDjBA;MAlDEA;QAAcA,QAkDhBA;MAjDEA;QAAiBA,QAiDnBA;MA/CEA;QD7L6CA;QrG4VzCC;UAAJA;;UpC7IuDC;QoC6IvDD;UAGEA,kBAAMA;QsGjKND,O1IcJE,sC0IgCAF;;MA3CEA;QAEEA,sBAAUA;MAGZA;QACEA,OAAOA,mCAqCXA;MDxL4CA;;QCyJ7BA;kBAlCIA;;kCAAMA;qBAmCjBA,UAnCWA;QAoCfA;UAAkBA,WA6BtBA;;QA5BWA;cAAPA;QAnCFA;QAsCEA,0BAAkBA;QAClBA,aAAOA,KAwBXA;;MArBEA;QAEsBA;QAATA;kBA9CIA;;kCAAMA;iBAgDjBA;QAAJA;UAAkBA,WAiBtBA;QAfmBA;;oBAGRA;QAnDTA;QAuDIA,4CADFA;UACEA,sBAAUA,aAAKA;QAEjBA,WAMJA;;MADEA,QACFA;K;0DAEAG;MAEaA,IADNA;MAELA,0BACFA;K;;;YA/BsBC;MAA4BA,oBAAZA;iBAAYA;MAAZA;eAAuBA;K;;;;wBDjMxDC;MACHA;;;;QAEEA;;IAEJA,C;;;sBAiBKC;MACHA;;;;QACEA;;IAEJA,C;;;Y9F7CAC;;iBACMA;;QACFA,OAAOA,eAiBXA;MAfQA;QAEmBA;QAAvBA;QACkBA,6BAAlBA;;UAC6CA,gCAASA;;QAEtDA,mBASJA;aAReA;QAEYA;QAAvBA;QACAA,0CAAqBA;QACrBA,oBAIJA;;QAFIA,QAEJA;K;;;EA0F8CC;YAAPA;MAAOA,iCAAmBA,+BAAEA;K;;;EACvBA;YAAPA;MAAOA,wCAA0BA;K;;;;;;;;;;;;;;;;;;;;ea7G5DC;;;yBACqCA;eAN5BA;azCDMC;YA4MA3/B;QyCrMV0/B,cAA2BA;MACxCA,SACFA;K;;;;cmFdKE;MACHA,oEAAeA;IACjBA,C;eAEKC;MACHA;IACFA,C;cAEKC;MACHA,2CAAeA;IACjBA,C;aAEKC;MACHA;IACFA,C;aAFKC;;K;cAIAC;MACHA;IACFA,C;cAEOC;MAAYA,mDAAgBA;K;;;;WzES9BC;MACHA;qCASAA;eATIA;QAASA,sBAAMA;gBAKPA;gBAAQA;MACpBA;;MAGAA,cAAUA,yDAYPA,aAAWA;IAIhBA,C;aAIKC;MACWA;WAAdA;eACIA;QAAeA,MAGrBA;gBAFMA;YnDzDmBA,OA4MApgC;QmDnJKogC,MAE9BA;MADEA,mBAAoBA;IACtBA,C;;;;YAzBYC;;;MAISA;aAHbA;YnDnCiBA,OA4MArgC;QmDzKOqgC,WAW7BA;;aARCA;0CAAQA;YAEJA;QAAeA,WAMpBA;aAHMA;QAASA,WAGfA;MADCA;IACDA,C;gBAZSC;;K;;;YAYID;mBACRA;YnD/CiBA,OA4MArgC;QmD7JOqgC,WAE7BA;MADCA,0BAAgCA;IACjCA,C;;;;W0E5BEE;;MACHA;IACFA,C;iBAMOC;MACeA;qCAGPA;MAHbA;WAEAA;MACaA,+BAAoBA;;Q7H0JnC9wC,wBCuE2BC;QDtEzBA;;M6H1JA6wC,OAAOA,sBAAoBA,8CAG7BA;K;0BAIKC;cACCA;QAASA,sBAAMA;cACfA;QACFA,sBAAMA;IAEVA,C;aAEOC;UACLA;MACAA,WAAOA,KACTA;K;;;;;;;;;;YAjB6BC;UACzBA;IACDA,C;;;;gBC/CEC;MACHA,8BAAwBA,YAAOA;IACjCA,C;aAEKC;MACHA,oBAAcA,YAAOA;IACvBA,C;kBAkBQC;MAAYA,QAAMA,oBAANA,UAA4BA,oBAAXA,+BAAgCA;K;SAGvDC;MAAEA;oBAGkBA;MAF9BA,yCACAA,wBAAeA,eACfA,oBAAoBA,WAAUA;K;;;;;gBCjC7BC;uCACHA,kBAAUA,eAASA;IACrBA,C;aAEKC;uCACHA,aAAKA,aAAIA;IACXA,C;kBAIQC;MAAYA,QAAMA,oBAANA,0BAA2BA;K;SAEjCC;MAAEA;oBACgCA;MAA5CA,yCAAwBA,wBAAeA,OAAKA;K;;;;uBCyC3CC;MACHA;oCAGyBA;eAHrBA;YA+DyBA;QA9D3BA,sBAAMA;MAwERA,iDAAgBA;YACZA;QAEFA;IAxEJA,C;;;2CAsCsBC;MAEpBA;gCAIgCA;MACEA;MAAuBA;eALrDA;kBACEA;QAAwCA;UAG1CA,OAAOA,iGAUbA;QAPIA;iBACIA;UACFA;;gBAGGA;M1HkrBTpwC,E0HlrBqBowC;MAAnBA,mC1H2V0BA,oBAuV5BpwC,iC0HlrB4BowC,6EAE5BA;K;6BAhBsBC;;K;cAAAC;;K;+BAuCjBC;MACHA;0BAAOA;MACPA,oBAAOA;MACPA,wEACeA;gBACGA;MADbA,kBACyBA;IAChCA,C;yBAgBKC;MACHA,mBAAOA;MACPA,uCAAcA;IAChBA,C;uCAxEoBC;;K;uBAMVC;;K;;;W5EfHC;MACLA;qCAK6BA;eALzBA;QACFA,sBAAMA;gBAGJA;kBAA4BA;QAC9BA,yDAAmCA;WAK5BA,YAJ8BA;QAIrCA,6BAA2BA,UAM/BA;;QAJIA,yDAAmCA;MAGrCA,WACFA;K;iBAuBKC;UACHA,yBAA2BA;MAC3BA,+CAAuBA;IAOzBA,C;0BAoCKC;UACHA,yBAA2BA;MAE3BA,+CAAuBA;IASzBA,C;uBAKsBC;MAChBA;qCAAeA;gBAAcA;MAAdA,8CAA0BA,gBACFA,0DAAlBA;eACrBA,2BAA4BA;QAAQA;MACxCA,mBACFA;K;aAQOC;MACLA;MC4DiBA,SD5DbA;QAASA,YAAOA,gDAMtBA;WAJEA;gBACIA;MAAeA;QAASA;MAE5BA,OCuDiBA,KDvDVA,gDACTA;K;mCAhMoBC;;K;;;;YA4EmBC;MAAMA,WAAIA;K;;;EAOJA;YAANA;MAAMA,wCAAgBA,QAAOA;K;gBAA7BC;;K;;;YA6BdC;;;8BAKNA;8CADXA;QAAsBA,MAE3BA;MADCA,qDAAyBA;IAC1BA,C;gBANsBC;;K;;;YA8CAC;;;8BAKhBA;0CACLA;MADYA;QAAaA,MAG1BA;MAFCA;MACAA;IACDA,C;gBARsBC;;K;;;YAgBoBC;MAzExBA,aAyE8BA;eAzE9BA;kEAAsBA,WAyEeA;+CAxEbA;MACbA,MAA1BA;QAAmCA;MAuEUA,aAAcA;K;;;;gBAyD1DC;MAAcA,gBAAIA;K;;;c6E3FXC;MACZA;gBAAKA;;QjIgDPrnC,oBCyE2B3M,gBDzE3B2M;QiI9CIqnC,oBAwgBJA,mBjIhpBIpnC,6DiIgpBJonC;QAvgBIA,SAGJA;;MADEA,sBAAMA;IACRA,C;uBAqPKC;MACHA;qBAAOA,0BAC0BA,c1IiBXA;e0BwJlBA;qBAASA;UAAOA,kBAA2BA;eACxCA;;gCAAMA;QgH1KOA,MhH0KbA,uBgH1KuCA;UAC1CA;;UAEAA,MAONA;;gBAHOA;QAuCLA;IApCFA,C;sBAQUC;MACRA;0BAAOA;M1HumBHA,S0HtmBAA;QACFA,0B1HqmBEA,qC0HplBNA;WAfEA;gBAEIA;MAAJA;QACEA,YAAOA,sBAYXA;MAREA;MAE6BA;MCxa7BC;MAEAA;MACAA;MACAA;MDwaAD;QAAeA;MACfA,OC/aFA,wEDgbAA;K;wBAgBKE;MACHA;eAAIA;QAASA,MAafA;gBAZMA;MAAJA;QACEA,sCAAgBA,oDAAeA,mDAIpBA,oDAFCA;;QAOZA;IAEJA,C;kBAiBKC;MACcA;qCACDA;;gBAAhBA;M/B3bAA,sBAAKA;M+B4bLA;IACFA,C;mBAgBMC;MACJA,OtJSFA,qCsJRAA;K;mBAMKC;MACHA;4CACMA;gBADFA;YhHkDcA,aAASA;QgHjDrBA,0BAAeA,mBAAaA;UAAUA,MAI9CA;QAHIA;;MhHuFFA,sBAAKA;IgHpFPA,C;qCAvcsBC;;K;;;YAyYaC;;;MAC7BA,gBFhdNA,kBEgd8BA,+BFhd9BA;IEidKA,C;gBAF8BC;;K;;;YAEnBD;MACVA,wBH/cNE,yBG+cqCF;IAChCA,C;;;;YAAUA;mBACTA;;QAgCJA;MACAA;IA/BGA,C;;;;;cAiNAG;+CACCA;MnB/mBcC;QmBgnBhBD,uBAAqBA,eAASA;QAC9BA,WAOJA;;MALEA;QACEA,gCtJpKJA,iCsJoKmEA;QAC/DA,WAGJA;;MADEA,YACFA;K;;;;cAoLKE;;;yCACCA;MnB7yBcA;kBmB8yBZA;kBACFA;cADeA;iBDnyBfA;iBAoDyBC;UApD7BD;YACEA,kBAAMA;UAkFRC;gBACIA;YACFA;iBAEcA;YAAYA;UAA5BA,qB1HwoBFxyC,4BAvV4ByyC,oBAuV5BzyC;U0HvoBEwyC;;UC8sBID,qBAA2BA;;QAKZA;QvJ1jBrB3/B,qCAEyBA,2BAFzBA,kDuJ2jBI2/B;UACEA,EvJvjBWA;QuJyjBbA,qCACeA,8CACVA,eAAwBA;QAC7BA,mC3HnFJvyC,oCAvV4BuyC,4BAuV5BvyC;;M2HqFEuyC,WACFA;K;;;;;0BE1yBSG;MAAoBA,8DAAuBA,yBAAwBA;K;cAEjEC;mBACLA;MAAJA;QAA4BA,SAAsBA,OAMpDA;eALMA;MAAJA;QnIsHFvoC,oBCyE2B3M;QDpPvBm1C,ImIsDAD;QACAA,SAGJA;;MADEA,OAAwBA,aAC1BA;K;iBAoBOE;MACLA;qCAAwDA;MAApDA;QAAkBA,OAAOA,0CAI/BA;MAFEA;MACAA,OAAOA,iFACTA;K;aAEOC;MACLA;MAAIA;QACFA;;QAEAA;QACAA;;MAEFA,OAAOA,gBACTA;K;yBAGKC;cACCA;QAAqBA,4CAAcA;IACzCA,C;2BAQKC;MACHA;yCACmBA;MADnBA,oBAAOA;MACPA;gBAIIA;MAAJA;QAGEA,iB7HkpBJhzC,4BAvV4BgzC,oBAuV5BhzC,kC6HhpBSgzC,eAAkBA,sBAClBA,aAAWA;gBAKdA;MAAJA;QACEA,cAA6BA;IAEjCA,C;4CA5FoBC;;K;0BAWNC;;K;;;;;;;YAyEMC;IAAMA,C;;;;2CD/HJC;;;yBAYNA;MAEAA;MATWA;mBAHrBA;MAAJA;QACEA,sBAAMA;MAIRA;MAqBFA;MAhBEA;MACAA;MACAA;MACAA;MACAA,cACFA;K;6BAjBsBC;;K;cAAAC;;K;sCApBAC;;K;;;eAmDjBC;MAEGA,mDAAQA;IAmBhBA,C;;;YAnBgBC;MACRA;MAYmBA;MAZEA;MACzBA;QAEEA,4BAA0BA;;iBAQtBA;QAAYA;UACdA;;UAEAA;;IAGLA,C;;;;YAd6BC;mBACpBA;iBAGUA;MAHEA;QACdA,kBAAmBA;;QAEnBA;IAEHA,C;;;;;gBErEFC;MAAuBA,WAAIA;K;oBAEhBC;MAAuCA,YAAKA;K;gBAIvDC;;IAA2CA,C;gBAEzCC;MAAcA,cAAOA;K;;;EjDeDC;cAA3BA;MAA2BA,oCAA2BA;K;gBAE/CC;MAAcA,gBAAIA;K;SAEXC;MAAEA;oBAAsDA;MAA3CA,8CAAyBA,cAAcA,KAAIA;K;kBAE9DC;MAAYA,OAAKA,oBAALA,MAAaA;K;;;;;;EAcNC;cAA3BA;MAA2BA,+BAAsBA;K;gBAE1CC;MAC4CA,aAA/CA;uGAA4DA,sBAAOA;K;SAEzDC;MAAEA;oBAAmDA;MAAxCA,qCAAoBA,uBAAeA,OAAKA;K;kBAE3DC;MAAmBA,aAANA;MAADA,iCAAeA;K;;;;;;EAKdC;cAARA;MAAQA,qBAAiBA,IAALA,kBAAiBA,IAANA,kBAAWA;K;cAevDC;MAA2BA,8BAAqBA;K;gBAEzCC;;sBACSA;;QAAgDA;oBAE1DA;;QAAkDA;MAEtDA,OAASA,wBAAYA,YACvBA;K;SAEcC;MAAEA;oBACiDA;MAA7DA,oCAAmBA,sBAAcA,UAAQA,uBAAeA,OAAKA;K;kBAEzDC;MAAiBA,aAALA;iBAAgBA;MAAhBA,8BAAsBA,0BAAQA;K;;;EAK7BC;cAARA;MAAQA,qBAAiBA,IAALA,kBAAiBA,IAANA,kBAAWA;K;cAevDC;MAA2BA,+BAAsBA;K;gBAE1CC;;sBACSA;;QAA+CA;oBAEzDA;;QAAiDA;MAErDA,OAASA,wBAAYA,YACvBA;K;SAEcC;MAAEA;oBACkDA;MAA9DA,qCAAoBA,sBAAcA,UAAQA,uBAAeA,OAAKA;K;kBAE1DC;MAAiBA,aAALA;iBAAgBA;MAAhBA,8BAAsBA,0BAAQA;K;;;EAK7BC;cAARA;MAAQA,qBAAsBA,IAAVA,uBAA0BA,IAAVA,sBAAeA;K;cAmBhEC;MAA2BA,uCAA8BA;K;gBAElDC;;8BAEDA;;QAAkCA;uBACrBA;;QAAiCA;MAClDA,OAASA,+BAAmBA,0BAAcA,4BAC5CA;K;SAEcC;MAAEA;oBAIgBA;MAH5BA,6CACAA,2BAAmBA,eACnBA,0BAAkBA,cAClBA,2BAAmBA,WAASA;K;kBAExBC;MACMA,aAAVA;iBAAqBA;iBAAoBA;MAAzCA,8BAA8BA,sBAAqBA,0BAAQA;K;;;EkDtJtBC;qBAApCA;MAAoCA,kCAAgBA,MAAKA;K;gBAEzDC;MAA0BA,8BAAMA,0BAAkBA;K;eAElDC;MACDA,6BAAKA,mDAA0BA,0BAAkBA;K;gBAEhDC;MACDA,6BAAKA,mDAA0BA,0BAAkBA;K;wBAEhDC;MAA0CA,6BAAKA,iCACzCA,+BACAA,6BAAsBA;K;;;;gBCA5BC;MAAuBA;MDhBCA;QACjBA;QAAkBA;;QACRA;;;MCcMA,+BDjB5BA,oBCiBkEA;K;oBAElDC;MACVA;yBAAyBA;QAAKA,WAKpCA;MAJMA,mBAAyBA;QAAMA,YAIrCA;MAHEA,gDATFA,0BnDuFAA,kBmD7EsCA,iBAAiBA,eCrBvDA,uCDuBAA;K;gBAUKC;MACHA,wBEpCFA,gBFoC6BA;IAC7BA,C;gBAEOC;MAAcA,mCAAoBA;K;SAE3BC;MAAEA;oBACgDA;MAA5DA,iDAAgCA,2BAAmBA,WAASA;K;kBAExDC;MAAsBA,aAAVA;gCAAkBA;K;;;;gBCxCjCC;MACDA,6FAAkCA,sCAA8BA;K;oBAEpDC;MACZA,OANJA,uCAMqCA;K;gBAIhCC;MACiBA;MAApBA;MACAA;IACFA,C;gBAEOC;MAAcA,aAAGA,2CAAiBA,0BAAYA;K;SAEvCC;MAAEA;oBAGkBA;MAF9BA,kDACAA,4BAAoBA,gBACpBA,6BAAoBA,YAAUA;K;kBAE1BC;MAAuBA,aAAXA;oCAAiCA,oBAAXA,mBAAmBA;K;;;;gBEzBxDC;MAAuBA,YAAKA;K;oBAEjBC;MAAuCA,WAAIA;K;gBAItDC;;IAA2CA,C;gBAEzCC;MAAcA,eAAQA;K;;;;aCGxBC;MACYA;iBAEXA;;MAAgBA,yBAAkBA;QACpCA,sBAAMA,uDACwBA,YAAgBA;MAGhDA,eACFA;K;oBAOKC;MACaA;;kBACXA;sBAAwBA;QAAeA,gBAS9CA;MAPiBA;MACVA,gBAAwBA;QAC3BA,sBAAMA,8CAA2CA,YAAgBA;MAInEA,OvDqGFA,2CuDtGkBA,uBAElBA;K;WAMKC;MACQA;MACNA,oCAAwBA;QAAKA,WAEpCA;MADEA,OvDqBFA,mBuDrBsBA,aACtBA;K;YAMKC;MACQA;MACNA,oCAAwBA;QAAMA,WAErCA;MADEA,OvD4CFA,oBuD5CuBA,cACvBA;K;yBAQKC;MACSA;;;MACEA;;UAEEA;UACZA,OvD9BNA,cuD8BkCA,iBAAKA,YAAaA,yBAgBpDA;;UAbkBA;UACPA,gBAAwBA;YAC3BA,sBAAMA,8CACeA,YAAgBA;UAEvCA,YAQNA;;UALiCA;UAA3BA,OvD7DNA,wBuD6DiEA,YAAZA,MAKrDA;;UAFMA,sBAAMA,qDAAwDA;;IAEpEA,C;;;YC3DMC;MACuBA,aAAvBA;MACJA,wBADmBA,wCAErBA;K;YAMMC;;qBACQA;;QAAgBA;MACFA,KAA1BA,gDAA4CA;WAC5CA;MACAA,YACFA;K;YAOKC;MACCA;MAAOA;QAAcA,YAG3BA;MAFEA;MACAA,WACFA;K;gBAGMC;MACJA;eAAIA;QAAmBA,sBAAMA;MAE7BA;gBACIA;aCnDcC;MCLpBD,aDkC+BA,OAAOA;QDuBlCA,oBAAuBA,gBAAWA,c5G+CtCE,iC4G1BAF;MAlBUA;;UAEJA,OAAOA,uBAAwBA,eAgBrCA;;UAdMA,OAAOA,uBAAwBA,eAcrCA;;UAZMA,OAAOA,uBAAwBA,eAYrCA;;UAVMA,OAAOA,uBAAwBA,iBAUrCA;;UARMA,OAAOA,uBAAwBA,eAQrCA;;iBCzEoBE;UD0FlBF;UAvBIA,OExENG,aFgGyBH,eAAIA,c5GQ7BE,iC4G1BAF;;iBCzEoBE;UDmGlBF;UA9BIA,OE1ENI,aFyGyBJ,gBAAKA,c5GD9BE,iC4G1BAF;;UAgCEA,iBAAgBA;UACgBA,wBAASA;U5GxFrCK;Y8GZNA,E9GYyBA;U4GqDnBL,+B5GpDGK,e4GsDTL;;K;qBAMMM;mBACQA;eChFMJ;eA6BWK;mBAAOA;QAgKpCA;MA7IOD;MDkCPA,OEvFFA,kBFuFqBA,c5GiBrBJ,iC4GhBAI;K;0BA4BKE;;iBACIA;MAAPA;QCVcA,0BDUOA;QCTrBA;UACyBA,EACvBA,wBADAA,+BAAYA;QDSVA;;;IAGNA,C;yBAKKC;;iBACEA;;QAAqBA,YAQ5BA;MANEA;QCtBcA,0BDsBOA;QCrBrBA;UACyBA,EACvBA,wBADAA,+BAAYA;QDoBiCA;;;MAG/CA;MAEAA,WACFA;K;;;;;;;;;;EE1HmCC;gBAA5BA;MAAcA,gCAAcA,YAAMA;K;;;;;;;;;;gBAqClCC;MAAcA,gBAAIA;K;;;qBLnDpBC;gCACCA,2BAAgBA;QAAOA,MAE7BA;MADEA,sBAAMA,wDAAsDA;IAC9DA,C;;;gBMIKC;MACEA;IACPA,C;eAEKC;MACEA;MACAA;IACPA,C;gBAEKC;MACEA;MACAA;IACPA,C;wBAEKC;MACEA;MACAA;MACAA;IACPA,C;;;ECXkBC;kBAdFC;MAAYA,QrK2pBMA,gBqK3pBsBA;K;gBAChDC;MAAUA,QAACA;K;gBAKdC;MAA4BA,YAAKA;K;aAQ/BH;MAAWA,4DAAKA;K;;;;;YxFdfI;MAAeA;;aAAOA,mBAAKA;K;gBAA3BC;;K;;;YAIID;MAC6CA;MAA5CA;MAAQA;;MAAnBA,0BAAOA,iCAAyBA;IACjCA,C;gBAFWC;;K;;;YAgBCC;MAE0DA;MAD9DA;MAC0CA;gBADjDA;2BACIA,wBAA0BA,mBAAMA;IACrCA,C;gBAHYC;;K;;E2CsEqBC;gBAA3BA;MAAcA,0DAAiDA;K;mBAcpEC;;kBACIA;sBAASA;QAAOA,sBAAMA;gBACfA;;8BAAMA;iBAANA;MACXA;WACAA,2BAASA,8BAAcA,mBAAOA;MAC9BA,aACFA;K;gBAYQC;MAAUA,YAACA,yBAAQA,yBAAUA,mBAAOA,kBAAWA;K;gBAEnDA;MACFA;;QAAeA,sBAAMA;MAEDA;MACpBA;iBACMA,mBAAOA;UACTA;aAEFA,2BAASA,kCAAkBA,mBAAOA;QAClCA,MAYJA;;gBATgBA;;gBAEZA;MADFA;QACEA;;qBAEkBA;QAClBA;kBACAA;qDAAiCA;;WAEnCA;IACFA,C;YAEWC;MACTA;MAAIA;MAAsBA;QACxBA,sBAAMA,gEAAmDA;gBAGpDA;aAAiCA;iBAAzBA;MAARA;8BAAMA;MAAbA,SAAOA,IACTA;K;eAEcC;MACZA;MAAIA;MAI4CA;MAJ5CA;0BAAMA;MAAgBA;QACxBA,sBAAMA,gEAAmDA;gBAG3DA;4CAAQA,+BAAyBA;IACnCA,C;wBAyBKC;;;MACaA;MAAhBA,2DAAOA;gBACEA;gBAAcA,mBAAOA;MAAfA;WAAfA;eACIA;QAKeA;;;kBACPA;kBAAgBA;kBAATA;QACnBA;QACAA,2DAAiCA,yBAAOA;aACxCA;aACAA,0BAAQA,mBAAOA;QACfA;;IAVFA,C;oBAaIC;MACFA;mCAAOA;MAAPA,qBAAcA,WAAUA;gBACpBA;gBAASA;gBAKSA;MALtBA;QACeA;QACbA;QACAA,cAOJA;;0BAL+BA;QAC3BA;QACAA,yEAA+CA,yBAAOA;QACtDA,YAAOA,kCAEXA;;K;gBAGKC;MACHA;wCAA0BA;MAKRA,0DADCA;MAEAA;;;;;MACXA,KAARA;MACAA;WACAA;IACFA,C;4BA1NQC;;K;;;;;;;;gBzCkBAC;MACFA,mCAAcA;MADFA,SAEIA;K;kBAENC;MAAYA;aAAUA,mBAAQA;K;8BAG9BC;mBACAA;qB0D4XZA;;sDAA2BA,O1D5XFA;MAAzBA,O/EmZJC,2F+EnZ2DD;K;aA0BpDE;MACQA;;M/CitCFA,c+ChtCKA,6C/CgtCwBA,6BAA7BA,oC+ChtCXA;QACEA,qB/C8kDaA;M+C5kDfA,aACFA;K;;;YAvCoBC;MAAiBA;;uCAASA;MAAIA;MAAbA;6BAAOA;MAAPA;wBAAOA;MAAPA,mBAAmBA;K;gBAApCC;;K;;;YAOSC;MAASA,mDAAGA;K;gBAAZC;;K;;;;oCD5BjBC;;K;;;;;EwFFqBC;gBAA5BA;MAA4BA,wCAAuBA;K;aAInDC;MAA+BA,sEAAYA,aAAKA;K;eAI/CC;MAAeA,aAANA;6BAAWA;K;iBAYjBC;MAAiBA,aAANA;+BAAaA;K;kBAIjBC;MAAkBA,aAANA;gCAAcA;K;cAIpCC;MAAcA,aAANA;4BAAUA;K;gBAKhBC;MAAgBA,aAANA;8BAAYA;K;aAElBC;MAA0BA,+EAAUA,cAAEA;K;uBAqB1CC;MAAkCA,4CAAgCA;K;cAAlEC;;K;aAEDC;MAAWA,4BAAaA;K;aAEnBC;MAA+BA,sEAAYA,aAAKA;K;gBAIrDC;MAAcA,+BAAgBA;K;;;;;aAgO9BC;;MAAsCA;MAAfA,OA3CPA,sBA2CgBA,cAAYA;K;aAE5CC;MA7CgBA;MA6CLA,OA7DZA,oBAgBiBA,wCA6CqBA,YA7DtCA,+BA6D8CA;K;;;;E5CxPdC;eAAjCA;MAAiCA,yBAAYA;K;gBAEtCC;iBxBxCVA;MwBwC+CA,kBAAuBA;K;;EA4P3BC;oBAAxCA;MAAwCA,4BAASA,oCAAKA;K;gBAG/CC;iBxBvSVA,uBwBwSkBA;MAAhBA,kBAA6BA;K;;;gBxBjTzBC;MAAUA,gB3FqiBAA,UAAUA,O2FriBCA;K;gBAItB9rC;mBAAcA,K3F6jByBA;M2F7jBzBA,sCAAeA;K;wBAqBxB+rC;;QAERA;;QAEIA,IApBNA;MAsBAA,WACFA;K;;;EyBnB4DC;oBAAvDA;MAA6CA,mEAAcA;K;gBAGpDC;MACRA,0CAA6BA,wBAAOA;K;6BAG5BC;MAENA;MAEqBA;MpH0gB3BA;UAkCEnzC;MoH5iBkBmzC;MACCA,4BAAOA;6BACEA;+BAAsBA;;MAIlDA;QACMA,8DAAkCA;UACpCA;MAGJA;iBpHiiB6CnzC;QoHhiB3CmzC;cpHgiBFnzC;UoH7hBImzC;;cpH6hBJnzC;UoHzhBImzC;;;;QAIFA;QACAA;;QAEAA;QACAA;;;eAEAA;UpHofaA;cA2BfnzC;;;;eAH4CmzC;yB2FxjB5CA;MyBkDAA,0BACFA;K;;;yBA4BaC;MAEXA;;MAAWA;QACuBA;QACJA;QAC5BA;UAEqBA;UACFA;UAGbA;UAAJA;YAAkCA,WAexCA;UAXyDA;UAAnDA;YAAmBA,OAAOA,iFAWhCA;UAVMA;YAAiBA,OAAOA,kFAU9BA;UAPeA,oBAAyBA,gCAAwBA;UAE1DA;YAAgBA,SAKtBA;;;QAFIA,OAAOA,0EAEXA;K;oBAEaC;MAEXA;;MAAWA;QACGA;QAEZA;;UACMA,qBAAYA;YAEdA,OAAOA,4CAAoBA,uDAcnCA;;QAVcA;QAAkBA;QAAlBA;0BAAOA;QAAjBA;UACEA,OAAOA,+EASbA;;UARqBA;UAAkBA;UAAlBA;4BAAOA;UAAjBA;YACLA,OAAOA,gFAObA;;YALMA,WAKNA;;;QAFIA,OAAOA,0EAEXA;K;uBAEaC;MAGXA;;;QAEMA,kCADaA;UACyBA,WAmE9CA;QzBzNAA,sC3FuiBAtsC;QoH9YIssC;QACAA,OAAOA,0CAAkBA,4DA+D7BA;;;UA3DUA;YAAoBA,WA2D9BA;;UA5DIA;UAISA,0CAAaA;UAApBA,SAwDNA;;gBApDcA;MAAZA;QAAoBA,OAAOA,yFAoD7BA;MAjDEA;QACeA;UACXA,OAAOA,uCACeA,kDA8C5BA;aA7CwBA;UAClBA,OAAOA,4CACeA,kDA2C5BA;;;UA1CwBA;YACPA;cAASA,OAAOA,yEAyCjCA;YAtCoBA,uCAAcA;YACHA,oCAAzBA;;cACOA;gBACHA,OAAOA,qDAA6BA,iDAmC9CA;;YA/B0BA,kCAApBA;;cACOA;gBACHA,OAAOA,oDAA4BA,iDA6B7CA;;YAzB+BA,oCAAzBA;;cACWA,6BACLA,yBAAeA,0CAAuBA;cAC1CA;gBAAgBA,SAsBxBA;;YAnBMA,WAmBNA;;;MpH8UAtsC;MoHzVEssC;UpH2XAtzC;Q2FzkBFszC,iCyBiNSA;UzBnMPA;QyBoMOA;QAEEA,OpHkXmCrsC;QoHlX1CqsC,+FAKJA;;MADEA,OAAOA,2DACTA;K;8BAEOC;MACIA;;MACTA;QAAgBA,WAclBA;MAZMA;;QAAMA;MxGmMQr0C;QwGlMZq0C;UAAMA;QxGkMMr0C,sCwGjMFq0C,0CAAqBA,wBAExBA;;QAMuBA;;MAAXA;MF5NNA;MACrBA;MACAA;MACAA;ME0NEA,aACFA;K;eAGKC;MACDA,yCAAOA,qCAAoCA;K;gBAGnCC;MACRA,0CAA6BA,WAAUA;K;wBAG/BC;MAEwBA;qCAArBA;;QAKTA;iBxGqKcA,oC+E7ZFA,K3FqiBAX,UAAUA;8B2F5hB1BW;a3F2jB6C1zC;MoH5U7C0zC;UpH4UA1zC;QoH3UsB0zC;;UpH2UtB1zC;MoHvUA0zC,0BACFA;K;;;YApIsBC;MACZA;uCAAQA,sCAAgCA,gBAAUA,eAAcA;K;;;E4C/HhEC;eADLA;MACDA,oFAAmCA,oBAAaA,4EAAiBA;K;wBAKzDC;4DAEDA;QACPA,OAAOA,0EAKXA;MADeA,mBrECbA;MqEDAA,2DACFA;K;6BAEYC;;MAERA,0BAAmBA;K;;;wBCwBXC;MAERA,0BAAmBA;K;;;Y1CxCvBC;MAEEA;;;Q5BNFA,sC3FuiBAhtC;QuH/hBIgtC;QACAA,aAAUA,+BA6FdA;;MAzFMA;QAAuBA,oBAyF7BA;MAxFSA,EAAPA,qBAAkBA;MACTA;MAEEA;QAESA,6CAAeA;;QAGnBA,uCAAeA;oBACGA;kBAASA;QAAzCA;UACEA,sDAAmDA;QAK3BA;QAGrBA,cADcA,yBAAmBA;UAEpCA,iBAqENA;;QAjEIA,sB9H0WJ1zC,0DkBrJ4C0zC,O4GpNxBA,iD9HyWpB1zC,4C8HvWW0zC,0BAmEsBA,uEALjCA;aA1DoBA;;QAEKA,4BAAKA,WAAIA,yDAE3BA;oBAG6BA;kBAASA;QAAzCA;UACEA,sDAAmDA;QAKhCA;QAGhBA,cADcA,yBAAmBA;UAEpCA,iBAyCNA;;QArCIA,e9H8UJ1zC,0DkBrJ4C0zC,O4GxLxBA,kD9H6UpB1zC,4C8H3UW0zC,0BAuCsBA,uEALjCA;;;QA9BSA;UAGLA,a9HoUJ1zC,yB8HrUgB0zC,gE5GgL4BA,oC4G9K1BA,kFAAeA,qBA+BAA,kFALjCA;;UAvBgBA;UA4BiBA;YA5BVA;U3G9BdA;U2G+BiBA;UAItBA;YAaSA;UAPEA,sDACAA,8BACAA;YAKTA,YAKNA;;YAHMA,OAAUA,iCAGhBA;;;K;;;EAvFsBC;YAApBA;MAAoBA,2CAAoBA,wBAAYA,aAAYA;K;;;;YAuB9CC;MACmBA;MAA7BA,OAAOA,wBAoEgBA,gDApERA,6CAChBA;K;;;;YAMyBA;MAClBA;MAAVA,WAAUA,yBAAYA,cAAGA,4BAC1BA;K;;;;YAkBeA;MACmBA;MAA7BA,OAAOA,wBAwCgBA,gDAxCRA,6CAChBA;K;;;;gB2CIGC;MpKgsBSC;4BoK/rBeD,mEpK+rBFC;aoK9qBLD;QAAtBA;MtJzCEC;iB+EnCPD;MuE4DAA,kBACFA;K;eAGKE;MAAwCA,sDAAKA,SAAIA;K;;EhD5C5BC;YAAPA;MAAOA,4BAAGA,IAAFA,aAAgBA;K;;;;YAYAC;MAC5BA,mCAAWA;MACxBA;QAAoBA,aAErBA;MADCA,OAAOA,iBAAeA,mBACvBA;K;;;;;;;;;;;ExCnBsCC;YAAZA;MAAYA,6BAAoBA,sBAAIA;K;;;;;;;;;;;;gB2C6BxDC;MAQ6DA;MADlEA,+BACgBA;eAmGcA;0CAgBIA;MA/GlCA;QACEA,YAIJA;MADSA,SA7BaA;MA6BpBA,wCA7BoDA,6DA8BtDA;K;gBAjBOC;;K;YA0IAC;MAQeA;MAUpBA;MACAA,OAAOA,e5HkMTvvC,2BkBlMoCuvC,kC0GAPA,2DAC7BA;K;YApBOC;;K;eAoCAC;MACDA;MAIaA;+D/GzC+CA,O+GyCnCA,uC5H8K4BA,gCAU3Dt/B,+E4H1JqBs/B,+EA9BnBA;Q5HmMyBA;Q4H3PSA;UAy1BSA;;UA1xBnCA,6CAAkBA;gBADfA;UAEHA;YACKA,kDAAsBA;UAGlBA;eApFaA;UAgBIA;UrHuXnBA;;iBqH5SFA;UAAWA;YAA2BA;mCAAIA;YAA5BA,8BAAwBA;;YAA3BA;UAApBA;YAEOA;cAjNiBA;;;QA0NTA;;MAGnBA,sCACFA;K;aAoBaC;MA2uBgCA,qDAAYA;mBAxuBjCA;;a5HmHxB1vC;M4HnHS0vC,iB/GyFPA,iBb0BF1vC,8CkBlMoC0vC,O0G+EAA,2C/GyFlCA;iB+GxFWA;MAAXA;QAAgCA;MAChCA,aAAcA,MAChBA;K;iBA+BOC;MACLA;MAAKA;QAA2BA,WAKlCA;MAgsB6CA,iDAAYA;MAlsBvDA;MACAA,OAAOA,oBACTA;K;2BAGKC;MACCA;UACiBA;eAOVA;;MACXA;QAMqBA;UACjBA;Y7H1SoBA;c6H2SeA,WA6CzCA;QAxCeA;QAXMA;;QAWNA;;;M7HnTf1zC,+BAEkB0zC,iBAAQA,6C6HiTxBA;Q7HhTwBA;Q6HkTlBA;UAEiBA;YAAoCA,WAoC7DA;UAjC8BA;YAA6BA,WAiC3DA;UA3BmCA;YAGrBA;;YAHqBA;UAA7BA;YAIEA,WAuBRA;;;MAdEA;QAAsBA,WAcxBA;MAXMA;QAA6BA,WAWnCA;MAR+BA;QAErBA;;QAFqBA;MAA7BA;QAIEA,WAIJA;MADEA,YACFA;K;qBAkCOC;MAELA;;;MA/Q8BC;QA+QaD,OAAOA,uBA6EpDA;;kBApdsBA;QAAgCA;;QAyYpBA;gBAjRFC;wCAAAD;QAqR5BA,OAAOA,uBAuEXA;MA5VgCC,kCAgBID;QA2QzBA;MA3RqBC,kCAAAD;QAiS5BA,sBAAMA,yBAA0CA,yBAAaA;MAwkBpBA;MArkB1BA;MAqkB0BA;MApkB1BA;qBAEFA;a1GgHGj0C;M0GhHci0C;QAAcA;+BAAKA;sBAALA;;QAAdA;MAAhCA;QACEA,OAAOA,wBAoDXA;qBA7CiBA;qBAAmBA;MAAKA;QAE9BA;;QAF8BA;MAAvCA;QAGEA,OAAOA,wBA0CXA;MAtCEA;uBAAkBA;e1GiGAj0C;Q0GhGci0C;yBAAjBA;iB1GgGGj0C;U0GhGGi0C;YACWA;mCAAKA;mBAALA;YAAqBA;mCAAKA;YAAtDA,2BAAiDA;YADhCA;;;;UAAWA;;;QAEnBA;QACAA;QACAA;QACAA;;qBAMEA;a1GqFGj0C;M0GrFci0C;QAAcA;+BAAKA;sBAALA;;QAAdA;MAAhCA;QACEA,sBAAMA,yBAA0CA,yBAAaA;;MAEpDA,mDAAwBA,6BAAkBA,MAAMA;MAChDA;MACAA,wDACYA,6BAAkBA,MAAMA,SAAcA;qBAG9CA;a1G4EGA;M0G5ElBA;QAA8BA,UAiBhCA;MAbsDA;QACvCA;uBACAA;;;;;gBAOFA;MACXA;MAEAA,OAAOA,wBACTA;K;gBA/EOE;;K;yBAsGOC;MAnXkBA;;2CAAiBA;0BAAjBA,gBAAiBA;MAyX/CA;QACUA;QACJA;UAAuCA;aACtCA;QACIA;QACLA;UAAqCA;aACpCA;QACqBA;QACCA;QAE3BA;UACUA;aACHA;UACIA;;MAIAA;sBACeA;QAAcA,aAmB5CA;;;QAfeA;;QADbA;UAKEA,QAAqBA,wBAWzBA;;UAhBEA;;MA9Y8BF,oBAMiBE;QAgZfA,QAHTA,wBAWzBA;MAPMA;QAAiBA,QAAqBA,oBAO5CA;MANMA;QAAkBA,QALCA,wBAWzBA;MALEA,OAAiBA,mCACTA,oCACAA,iBAAkBA,kCARHA,4BAUHA,qBACtBA;K;6BAIcC;MAGZA;;QAEwCA;gBAAjBA;;MACDA;MAQtBA;QAAyCA,QAAqBA,wBAoLhEA;MA7KyBA,4CACDA,kCAFtBA;QAGOA,yBAFgBA,8BACDA;UAElBA,QAV0DA,wBAoLhEA;kBA3J8BA;MAAUA;MAA/BA;MARkBA;;MAQzBA;oDAAyDA;;;UAClCA;UACDA;UAChBA;YACEA;;YAKJA;YACAA;;;;UAKEA,wCACAA;YAEFA;;;;iBAESA,uCACPA;YACFA;;;UAUoCA;YACpCA;YAIAA;cAAkCA;YACjBA;YAGbA;cAEFA;;;;;YAMFA;cACEA;cAEIA,2CAAkBA;gBACpBA,QAAqBA,2BAsG/BA;;;UA5FyCA;YACnCA;sBACwBA;YAAxBA;cAAgCA;YAChBA;YAEZA;cACFA;;;YAIFA;cACEA;cAEIA,0CAAkBA;gBACpBA,QAxBqBA,2BAsG/BA;;;UArEyBA,mDACgBA;YACnCA,QAnCyBA,2BAsG/BA;UAhE0BA,sDAJeA;YAMnCA,QAxCyBA,2BAsG/BA;UA3DIA,QAzH4DA,wBAoLhEA;;;8BAlD0BA;QAElBA,2CAAkBA;UAOKA;;;QAAvBA;2BAC4BA;UAAQA,QAAqBA,oBAwCjEA;QAvCIA,sBAAmCA,uBA/DRA,+BA9EiCA,wBAoLhEA;;MA/BkBA;yBATkBA;QAkBMA,QAlBuBA,oBAwCjEA;MAJUA,mBAnC6BA;QA0BnCA,QAzF2BA,2BAsG/BA;MAJEA,wBAA0BA,uDAClBA,kCACYA,yBAlL0CA,wBAoLhEA;K;sBAeeC;MACTA;oBAGYA,kBAWaA,mDAX7BA;QAEEA;UAA0BA,iCAAkBA;;UAC1CA;;QAIFA;UAAsBA;QAIfA;QAAPA;UAA2BA,mCAAkBA;;UAC3CA;;QAIEA;QAAkBA;UAGlBA,mEACAA;YAEFA;YAGAA;cAAeA;YAIfA;;;YAGAA;QAIFA;UAAsBA;QAGtBA;;MAGFA;QAAeA,QAAsBA,mBAIvCA;MAHEA;QAAgBA,QAAsBA,mBAGxCA;MAFEA;QAAiBA,QAAsBA,mBAEzCA;MADEA,QAAsBA,mBACxBA;K;aAgJIC;;iBArzB4BL;;QAuzB5BK,OAAOA,4BAIXA;;QAFWA,SAj7BWA;QAi7BlBA,8BAA+BA,iCAj7BmBA,mBAm7BtDA;;K;iBA2BOC;MACUA;;MACFA,6CAAoBA,UAAeA;QAC9CA,OAAOA,sBAcXA;WAbsBA,wCACPA,qCACTA,UAAeA;QACjBA,OAAOA,sBAUXA;MAPaA,yBA7DUA,0BAAkBA;MA8D7BA;MAKVA,OAAOA,qBAAWA,UAASA,sBAAYA,oBACzCA;K;;EArzBuCC;YAAVA;MAAUA,2CAAYA;K;;;EAsBVC;YAAVA;MAAUA,0CAAUA;K;;;EAyDLC;YAAVA;MAAUA,mCAAKA,aAAUA;K;;;;YAqwBlDC;MAASA;mDAA+BA;K;;;;gBA+B5CC;MAAcA,gBAAIA;K;;;gBA4BlBC;MAAcA,gBAAIA;K;;;e8C3kClBC;MACQA;;MACbA;QAAgBA,OAAOA,iCAEzBA;MADSA;QAAuBA;iCAAIA;iBAAJA;;;MAA9BA,SACFA;K;yBAWIC;M7GNmBA,uC6GOEA;MAInBA,uBAAYA,2BAAqBA;QAAcA;MACnDA,OAAOA,uCACTA;K;sBAOKC;MAAgDA,8BAAsBA;K;kBAMtEC;MAA0CA,qBAAcA;K;;;8BrEUpDC;mBACLA;YnFmfgBn1C;QmFnfWm1C,oDAAyBA,sCAAXA;;QAAxBA;MAAjBA,SAA+DA;K;gCAE9DC;MACHA;;kBAAOA;QAA0BA,QnFgffp1C,wBmFhfeo1C;;QAC/BA;QACAA;;gBAEEA;anF4ecp1C;MmF5elBo1C;QAA2BA;IAC7BA,C;iBAEKC;MAGoBA;;qBACNA,eAAjBA;;QACMA;mCAAeA;UAERA;yBnFkeKr1C;YmFhedq1C;cACEA;4CAASA;cAATA;;cAGAA;;YAGFA;;eA7EiBA;QAmFnBA,2CAA2BA;kBnFmdXA,sBmFtiBGA;QAwFnBA;MAIgBA,6CACLA,SAAQA;gBA7FAA;MA+FrBA,mEnFuckBr1C,iBmFrcuBq1C,mCACzBA,KAANA;MAGVA;MACAA;gBAGIA;MAA+BA,uBAAfA;UAEXA;QlFlFFA,KkFkFLA;;MAEFA;IACFA,C;gBAEOC;;kBAEDA;;MACJA,qBAAoBA,MAAMA,SAA1BA;kBACgBA;;+BAAUA;Q9FkaXA,gB8FlaCA;kBACAA;;+BAAKA;Q9FiaNA,gB8FjaCA;;M9FiaDA,U8F/ZUA,gCAAXA;MAEdA,sCACFA;K;eAxIaC;;K;oBAOAC;;K;;EAoGuBC;YAAbA;MAAOA,uCAAeA;K;;;;gBCxHtCC;MAAcA,+BAAiBA,QAAQA;K;;;;;;EzCyEzBC;gBAAdA;MAAcA,0BAAIA;K;;E8G7DcC;yBAAlCA;MAAkCA,+CAAkBA;K;mBAEpDC;MAA6BA,sBAAuBA;K;sBAEpDC;MAC+BA,aAA3BA;MAALA,uEAAiEA;K;4BAEjEC;MACiCA,QAA1BA;QAA+CA,QAE1DA;MADEA,QACFA;K;kBAHIC;;K;sBAKCC;MAA+BA,YAAKA;K;mBAIlCC;MACLA;MAAQA,+BAAoBA;QACKA;QAA/BA,O3HwjCUA,2BAC8BA,UAAQA,oB2HtjCpDA;;MADEA,sBAAMA,0BAAoBA;IAC5BA,C;yBAEIC;MACsBA;mBACbA;YzJ4hBOA;QyJxhBTA,+BAAaA;WACJA;QAGTA;MAGTA,OAAOA,8BAAyCA,eAClDA;K;;;;;;;;ECrCuCC;yBAAlCA;MAAkCA,+CAAkBA;K;mBAEpDC;MAA6BA,sBAAuBA;K;sBAEpDC;mBACMA;MAATA;QAAkBA,YAQpBA;MALmBA;QAAmCA,WAKtDA;MADEA,OAAOA,8CAAwBA,8BACjCA;K;4BAEIC;;iBACOA;MAATA;QAAkBA,QAyBpBA;MAxBkBA;QAAqBA,QAwBvCA;MAtBEA;QACiBA;QACfA;UAA2BA,QAoB/BA;QAnBIA;UACEA;YAAYA,QAkBlBA;UAbkBA,gDADRA;UAEJA;YAAgBA,SAYtBA;UARMA;YAA2CA,YAQjDA;UAPWA;YAA4BA,YAOvCA;UANWA;YAAgCA,YAM3CA;UAL4BA;UAAtBA,iCAKNA;;;MADEA,QACFA;K;kBA1BIC;;K;sBA4BCC;MACDA,WAAKA,iBAA0BA,gDAAmBA;K;mBAI/CC;MAAwBA,yBAAcA;K;yBAEzCC;MAAkCA,OAAIA,iBAAWA;K;yBACjDC;MAAkCA,OAAIA,iBAAWA;K;;;;;;;;EC9CdC;yBAAlCA;MAAkCA,+CAAkBA;K;mBAEpDC;MACDA,yCAAsDA;K;sBAErDC;mBACMA;MAATA;QAAkBA,YAEpBA;MADsBA;MAApBA,gCACFA;K;4BAEIC;;iBACOA;MAATA;QAAkBA,QAuBpBA;MAtBMA;MAAJA;QAAuCA,QAsBzCA;MArBEA;QACyBA;UAAuCA,QAoBlEA;QAjBgBA;QACZA;UACUA;UACRA;YAAeA,YAcrBA;;QAZIA,SAYJA;;MAREA;QAAqBA,QAQvBA;MANOA;QAAkCA,QAMzCA;MAJMA;QAAmCA,QAIzCA;MAFmBA;MAAjBA;QAAsCA,QAExCA;MADEA,QACFA;K;kBAxBIC;;K;sBA0BCC;MAA+BA,oCAAqBA;K;mBAQlDC;MACLA;MAAQA,+BAAoBA;QAC1BA,sBAAMA,0BAAoBA;MAGbA;MACPA;QAIkBA,QAAfA,8DAAuCA;UACvCA;;QAISA;M1JxBbA;M0J0BPA,O7HwgCYA,2BAC8BA,UAAQA,oB6HxgCpDA;K;yBAEIC;MACsBA;;mBACbA;;Q7K2UbhzC,gC6KtU2BgzC,4D3JoISA,kC2JpIcA;QACvCA,4CAA0BA;QAEtBA;UAGFA;QAGTA,OAAOA,YAC6BA,4CAA4BA,eAmBpEA;;QAXuCA,UAAxBA,M3JudKA;U2JtdPA;mBAKFA;mBACeA;;Q1J7DjBA;Q0J4DEA,kC1J5DFA;Q0J+DLA,OAAOA,8BAAyCA,eAEpDA;;K;sBAEKC;MACHA;;QAA4BA,WAa9BA;MAVEA;QAA8BA,uBAUhCA;MATEA;QAAkCA,uBASpCA;MALEA;QAA4CA,YAK9CA;MAFmBA;MACjBA,4CACFA;K;kBAEKC;MACHA;;QAA6BA,WAQ/BA;gBAPYA;sBAAgBA;QAAQA,YAOpCA;MAL6CA,4CAD3CA;QACOA,2BAAeA,4CAAqBA;UACvCA,YAINA;MADEA,WACFA;K;;;;;;;;EAxD4DC;YAAVA;MAAUA,0CAAUA;K;;;;e/GFjDC;MACnBA;eAjCmBA,iBC7BFC,2BzCDMlS,OA4MA3/B;QwC5IrB4xC,sBAAMA;gBAGJA;oBAAsBA;aACxBA;QxCuHJliD,oBCuE2BC;QDtEzBA,oBwCyHFiiD;QAhPIA,SASJA;;kBARaA;QjDiVWA;UiDhVpBA,OAAOA,sBAAcA,mBAOzBA;;UxC2GAv2C,oBCyE2B3M;oBuCxLvBkjD;UvBugBFA,sBAAKA,4BjB9kBHt2C;UwCwEAs2C;UACAA,SAEJA;;;K;sBAMUE;;MACRA;IAUFA,C;4BAXUA;MACRA;;;8DADQA;QACRA;;;;;;;;;gBACEA,sBAAMA;cAGOA;mCAAMA,kDAANA;;;;;cAENA;mCAAMA,2CAANA;;;;cAAPA;;;;;;;;;;;;;;;cAEAA;;;;;;;;cATMA;;;;;;MACRA;IADQA,C;aAyHHC;MAAWA,uCAAmBA,+BAe/BA;K;iCAkBDC;MACUA;MAIoBA;MAJjCA;gBAEIA;MjDsKkBA;QiDrKNA,mBACNA,WAASA;;;iBArNAA,iBC7BFH,2BzCDMlS,OA4MA3/B;UwCyCrBgyC,2BAAuBA;;YAEOA;;kBvCWPA;oBuCPvBA;UvBsVFA,sBAAKA,4BuBtVqBA,mDADPA;;;IAGrBA,C;qBAOqBC;MAKjBA;MAJKA,qBAAKA,yDAAWA,SAAKA,oDAEzBA,aAAWA;MxC9EhB52C,oBCyE2B3M;euCUzBujD;MvBqUAA,sBAAKA,4BjBnkBHpO;MwC+PFoO,SACFA;K;mBAGKC;;iBACCA;MAAJA;QAAoBA,MAOtBA;eALMA;YvBsRcA,aAASA;QuIniB3BA;;QALAA;QACSA,EAATA,6CAAeA,iCAAWA;;IhHsR5BA,C;;;YA5EqCC;;iBAC3BA;;MAAJA;QAAyBA,SWjNDA,yBAAWA,OX+NpCA;MAZCA;MW/NFA,EXiOEA,iCxC/MF72C,sBAsLJD,eCyE2B3M,kFkD7OX0O;MlC2gBY+0C,YuB7UDA,2DvB6UCA,qCuB7UtBA;qBvBsnBWA;QuBrnBTA,wBAAuBA;;MAGkBA,EAA3CA;MACAA;YAEIA;QAA0BA;MAC9BA,SAAOA,YAAYA,yBW9NgBA,OX+NpCA;K;;;EA+B6BC;YAANA;MAAMA,6BAASA,wBAAWA;K;;;;YAUxBC;mBAC1BA;8CAAmCA,WA+CvCA;IA9CGA,C;;;;YAAaA;MAC4CA;MAAxDA,gDAAmCA;IACpCA,C;;;;eAgDEC;MACHA;cAAIA;QACFA,sBAAMA;UAERA;eACAA;MA7FAA;aAEIA;MjDoLkBC;QiDnLND,mBACNA,WAgFZC;;;cAvRqBA,iBC7BFV,2BzCDMlS,OA4MA3/B;UwC4BqBsyC;;IAuF9CA,C;;;YE3TyCE;MACrCA;aAAWA,mBAAWA,qBAAcA,YAAWA,gCACjCA,uBACOA,uBACRA,UACdA;K;;;;YAW+BA;MAGhCA;MAAIA;MAAMA;QAAcA,WA6CzBA;MA1CoBA,0CAA2BA;MAIXA;;wBAAKA;MAAKA;4BAAOA;MACrCA;;MADJA;MAKXA;QAAkBA,WAiCnBA;MA/BsBA;MACIA,SAArBA,iC4DidFA,yB5DjdoCA,yB4DidcA;Q5DhdtCA,6CAAYA,aAAIA,gCAA0BA;;;eAC7CA;QAAJA;UACeA,oCACdA,a4D6cNA,oB5D7cmCA,gBAAhBA,qD4D6c+BA;Y5D5cpCA,gDACNA,aAAIA,2BACoBA,gBAAhBA;eACWA,MAAhBA;YAC2BA,YAAhBA,uCAAiCA,M1BoQ9BA,iB0BpQvBA;;cACmDA,2BAAhBA,EAAhBA,uCAAgBA,M1B4ORA;c0B3OlBA;c4DscXA,uDAAkDA;gB5DtcFA;cAGpBA,iDAAVA,eAAcA;cAC5BA;;;MAMEA;MACCA,sBAAMA;;wBAAKA;MACXA,sBAAMA;eAIXA;QACYA,+BAAoBA,kBAAaA;;QACvCA,uBAAsBA;MAThCA,O+DoNFA,mC/D1MCA;K;;;EAAmBA;YAAXA;MAAWA,wCAAaA;K;;;EAedC;YAAXA;MAAWA,wCAAUA,kBAASA,QAAMA;K;;;EAcYA;YAAhBA;MAAWA,2CAAcA;K;;;;;2C4B3BjE7xC;MAEEA;sDA0BIA,iDAhBFA,sBACAA,eAXFA;;QACeA;;UACOA,sBAAUA;QAEZA,oBAAPA;QACXA;UAAkBA,sBAAUA;QAERA,sBAAPA;QACbA;UAAoBA,sBAAUA;QAE9BA,4BAAeA;QACfA,4BAAiBA;QAEPA;QACAA;QAEFA;QAARA;UACEA,sBAAUA;aACLA;UAEOA,wDACuBA;UADjCA;eAKGA;UACLA,4BAAUA,YAAUA;;UAEpBA,sBAAUA;;YAGCA;QACbA,sBAAUA;IAEdA,C;iBAEI8xC;MACFA;oBAAoBA,oBAAWA,kBAESA,sCAFxCA;eACaA;QAAXA;UAA0BA,YAI9BA;QAH8BA;UAAYA;iCAAYA;0BAAZA;;UAAZA;QAA1BA;UAAuDA,YAG3DA;;MADEA,aACFA;K;yBAEcC;MAIRA;MAGOA;MAHCA;gBACLA;;iCAAKA;aAALA;gBACIA;;iCAAUA;aAAVA;gBAA4BA;;iCAAYA;MADnDA,OAAYA,yCAC2BA,eAEzCA;K;mBARcC;;K;qBAAAC;;K;gBAUPC;MrF5FiBA;;qBqF8FFA,uBAKRA,yBAEAA,qBAPmBA,SAA/BA;0BAGYA;QAEAA;+BAAYA;oBAAZA;QAEAA;+BAAKA;QrFkZFA,YqFlZHA;;MrFkZGA;MqF9YfA,sCACFA;K;;;qCAQAjyC;MACEA;4GAQAA,YARAA;QAC4CA,YAA/BA,YAAUA,UADvBA;QAQAA,kBAAkBA;;IALpBA,C;gBAWOkyC;MACDA;MACsBA,cAAVA,8BAAhBA;QACaA,oBADbA;MAGAA,sCACFA;K;yBAIcC;MAEZA;;;QACEA,sBAAUA;MAeaA;mBACDA,kBAGhBA,sCAHRA;QACEA;UACkBA;UACZA;YACFA,OAAOA,wBACFA,mDAgBbA;;QAbiBA,8DAA4BA;;MAUxBA,qEAC4BA;MmFpOjDA;MAAAC;MnFqOED,SACFA;K;mBAxCcE;;K;;;qCAmIdpyC;MASuBA;;;yDACfA,eACIA,iBAAqBA,8BAAHA;;kBAI1BA;kBAAkDA;;;MAHpDA;eAAyBA;0CAA6BA;;;UACvCA;6CAAcA;iCAAdA;UACbA;;UACqCA;UAAaA;iCAAIA;iBAAJA;U7F7RtDpG;UuDrCyBkB;UA4BzBkF,sBAaUjF,kCAAsBA,2CxC+3BnBD,oBwC93BiCC;UAd9CA;UsCsSIiF;;QAH4DA;;MAYrBA,4BAAHA;aA8QfA;MAFzBA;;MA3Q6BA;gBA2CJA;MAyOKA;MAAeA;gBA/QrCA;MAA8BA;MAqBlCA;MAIEA;MAMAA;MAEAA;MAIEA;MAxCNA;uBAkRoBA;;;UAjRJA,4BAASA;uB1E6NPA;c0E3NZA,4BA0ORA;cAzO+BA;;YAEzBA;;;;;UAiBYA,4BAASA;YAAcA,sBAAMA;UAqQxBA;UAnQJA;mBAsSEA,iBAAcA,oBAAiBA;YArS9CA,iCAgONA;;YAkCuBA;8BA/PIA;cACnBA,sBAAUA,0CACmBA;YAEhBA;qBA8RAA,iBAAcA,oBAAiBA;cA9RbA,sBAAMA;YA2PtBA;YAzPFA;qBA4RAA,iBAAcA,oBAAiBA;cA5RbA,sBAAMA;YAyPtBA;YAvPFA;qBA0RAA,iBAAcA,oBAAiBA;cAzR5CA,iCAoNRA;;cAkCuBA;iCAnPQA;gBACrBA,sBAAUA,oCACaA;cAEzBA,iCA6MRA;;;UAzMkBA,4BAASA;;;;iB1E6KPA;Q0E1KhBA,4BAyLJA;MAtLEA,iBAAYA;IAGdA,C;qBAqEAqyC;MACIA,O3F8CJA,gH2F7C8CA,wCAAwBA;K;iBAKtDC;MACFA;iBAAaA;mCAAOA;;;;QACGA;QAANA;gCAAKA;eAALA;;;MAA7BA,SACFA;K;mBAOYC;MACVA;wCAAmCA,QAAQA;QAAaA,WAK1DA;MAJuDA,aAAvCA;QAAcA,2CAAiBA,SAI/CA;yBAH0BA;MACZA,gCAAsBA;;;;QACGA;QAARA;qCAAOA;oBAAPA;;MAA7BA,SACFA;K;yBAEcC;MAERA;MAMAA;MANQA,0CAA0BA;gCACXA;QAAqBA,WA6BlDA;MA5BYA,gDAAWA;gBACjBA;MAAJA;QACwBA;gBAePA;MAAwBA;gBACvBA;MAFAA,kCAGEA;gBAGRA;MAAVA;kBAC6CA;;gCAAKA;eAALA;emFpgBAA;QAAnCA,4BAAqBA,mBAGPA,mBADFA,YADKA;QAJ/BA;QAAAC;QnFugBMD,SAKNA;;QmFphBAA;QAAAL;QnFihBMK,SAGNA;;K;mBAhCcE;;K;qBAAAC;;K;gBAkCPC;MrFneiBA;;MAgf4BA;MAOnCA,uCqFjBDA,iCrFiBCA,SqFfDA,4BrFeCA,SqFbDA,uBrFaCA,SqFXDA,wBrFWCA,SqFTDA;MAVdA,sCAaFA;K;;;YAjJcC;MACNA;QAAuBA,mCAAWA;IACvCA,C;;;EA8EwCC;YAATA;MAAOA,0BAASA,KAAIA;K;;;EAaTC;YAATA;MAAOA,4BAAWA,OAAMA;K;;;ErF7bpCC;gBqFwhBjBA;MAAcA,yDAAgBA,cAAMA,iBAAQA;K;;;;;;gBAiB5CC;MAAcA;arFziBGA,qDqF0iBjBA,iBAASA,gCAAcA,+BAAaA,iCAAeA,6BAAcA;K;;;;;;gBAanEC;MAAcA,0BAAUA,gBAAOA;K;iBACzBC;;iBACNA;8BAAsBA;iBAAWA;;gCAASA;eAATA;;;MAAlCA,SAAyDA;K;mBAEpDC;mBAAqBA;MAARA,oCAAkCA;K;kBAEzCC;MACbA;MAAKA;QAAWA,QAAkBA,4BAKpCA;eAJaA;eAAUA;MAAVA;8BAASA;eAATA;MACXA;QAAiBA,QAAkBA,4BAGrCA;MAFEA;QAAiBA,QAAkBA,4BAErCA;MADEA,QAAkBA,6BACpBA;K;gBAaOC;MACDA;qBAESA,qCADOA,QAApBA;QACaA;+BAASA;gBAATA;;MrF5FEA;oBqF+FJA,mCAAuBA;oBAErBA,mBAAyBA,SAAtCA;QACaA;+BAASA;gBAATA;;6BAEEA;MACfA,sCACFA;K;;;;;;;;;;YDtoB+BC;MACRA;;MACvBA;QACEA,mFAAIA;MAENA,UACDA;K;;;;gBrCISC;MAAUA,yBAAcA,OAAMA;K;eAG9BC;MAASA,uBAAYA,OAAMA;K;8BA+BnC34C;MAGEA;oBAAoBA,uBAAcA,kBAOlBA,qBAPhBA;cACUA;QACRA;UAEUA;UACsBA;YAAGA;mCAAaA;mBAAbA;;YAAHA;UAA9BA;YAAqDA;;QAEvDA;UAAcA;;IAElBA,C;YAKS44C;MAEPA,OAAWA,8BACbA;K;eAMIC;MACFA;;QACEA,sBAAUA;6BA9DIA,cAAcA;QAgE5BA,sBAAUA,wGACuBA;gBAGtBA;MAAYA;QAAOA,SAOlCA;MAN4BA;QAAMA,SAAmBA,WAMrDA;MAJMA;QAA2BA,YAAOA,YAIxCA;MADEA,YADAA,eAAcA,iCAEhBA;K;yBAMKC;;kBACCA;MAAJA;QAAyBA,YAmB3BA;gBAhBeA;;8BAAWA;qBAAXA;QAA0BA,YAgBzCA;gBAbMA;aAA2BA;MAA3BA;uBAAYA;MAA0BA;QACjBA;QAAZA;gCAAWA;wBAAXA;;QAD6BA;MAA1CA;QAEEA,WAWJA;MAP4CA;QACjBA;QAAZA;gCAAWA;wBAAXA;QAD6BA;;;MAA1CA;aAEEA;QACAA,WAIJA;;MADEA,YACFA;K;qBAKIC;;iBAEQA;eAAYA;;MACtBA;QACyBA;QACnBA;kCAAWA;cAAXA;;;UAGIA;;MAIVA,UACFA;K;iBAMIC;MACFA;;QACEA,sBAAUA;6BA/HIA,cAAcA;QAiI5BA,sBAAUA,2GAC8BA;MAIjCA;MAQOA;MAChBA;QACEA,sBAAUA,wBAAkBA;MAG9BA,yBACFA;K;iBAKIC;MACFA;MAEIA;yBAAKA;MAATA;QACEA,sBAAUA;;iBA1JGA;eAAYA;QA2JpBA;UACLA,sBAAUA,wFACeA;;iBAKdA;wBArKGA,cAAcA;QAuKzBA;oCAA8BA;;QAb/BA;MAYJA;QAEEA,sBAAUA;MAGZA,aACFA;K;iBAnBIC;;K;;EAwCYC;mBADRC;MAAaA,gBAAKA,IAAGA;K;cACrBD;MAAQA,+BAAaA,QAAOA;K;gBAC5BE;MAAUA,iCAAeA,QAAOA;K;;;;;EA0DVC;mBAFtBC;MAAaA,gBAAKA,IAAGA;K;gBACrBC;MAAUA,6BAAOA,aAAMA;K;eACdF;MAASA,4BAAmBA,WAAMA,cAAOA;K;aACzCG;MAAOA,OAAIA,qBAAeA,WAAMA,YAAKA;K;cAC3CC;MAAQA,OA5EXA,8BAAqBA,yCA4EVA,KA5EUA,oBA4EGA,mBAAQA,sBAAKA;K;iBAElCC;MACKA;;yBAAaA;;MACXA;6BAPOA;4BAxPRA,YAAYA;;;YA6QfA;YAAwCA;mCAAQA;YA9FtDA,mCAAqBA,2DA8FUA;;UAHjCA,SAkBNA;;+BA5RiBA,YAAYA;sBAHXA,cAAcA;;QA2RDA;+BAAQA;QAAvBA;;MAGdA,OA5GMA,8BAAqBA,uDA4GPA,eAAeA,kBAAaA,qCAClDA;K;SAuCcC;MACZA;MADcA;oBAShBA;MARYA;QAAcA,OAAaA,yCAQvCA;MANiBA;QAAbA,oDAAyBA,YA/ERA,KAAKA,MA+EsBA,sBAMhDA;MAHEA,YAAOA,uBAAgBA,sBACnBA,qBAAcA,eACdA,YApFeA,KAAKA,WAALA,KAAKA,KAqF1BA;K;kBAGQC;MAAYA,OAAMA,yDAAQA;K;cAMzBC;;kBA9FYA;MA+FfA,cA/FoBA,WA+FDA,KA/FCA;QAgGtBA,sBAAUA,mCAA+BA,0CACzBA;4BAIUA,oBAAcA;MAExCA,OAAWA,sCADaA,kBAAYA,aAOxCA;K;;;;;iBoDjNOC;MAC4BA;MAAjCA,0BsEzD+BA,CChHZC;gBvE0KnBD;;gBAKkBA;MAAdA,4BAAoBA,kBAAeA,eAAYA,eAAMA;MACzDA;MAEoBA;MAChBA;8BAAUA;MAAdA;QAKcA,kEAAoCA;QACzBA,oBAAMA;kBAAaA;QAAbA;0BAAKA;QAALA;uBA1K7BA,qBA2KAA;;UACEA;UACcA;UACdA;;UAEAA;;QAEQA;;MAGAA;MAEcA,kBAAIA;MAAaA,oBAAMA;MAAnBA;wBAAKA;MAALA;wBAAKA;MAALA;MACpBA;QAAKA;MvFqNGA,uCuFrNcA;QAG9BA;mCAAMA;QAANA;;MAGFA,wBAAsBA;eAClBA;QACFA,gCxFyDSA,iDwFzD6BA;QACvBA;8CAAKA;QAApBA,4BAAeA;;MAEjBA,4BxFsDWA;MwFpDXA,0BsE1G6BA,CC1GVE;a1KgkByBF;MmG1W5CA,sCACFA;K;uBAGKG;;kBACiBA;MAApBA,2BAA0BA,eAAMA;MAECA,oBAAMA;eAAaA;sBAAhDA;MAEoBA;MAAIA;MAAeA;oBADvCA,yCAC6CA;MAChCA;gBAIbA;MAAcA;kBAChBA;;QACAA,kBAAUA;;QAMVA,MAiCJA;;gBA9BEA;MAAcA;MACdA;MACiBA;MACjBA,kBAAUA;MACVA,mBAAWA;;MAKMA;MACAA;MACHA;QAAdA;QACAA;MAIAA;MACAA;;QAEEA,kBAAUA;;QAMIA;QACdA,kBACIA;;;IAGRA,C;+BAGKC;MACHA;MAIiBA;MAJjBA,oBAAOA;MAGgBA,UAANA,mBAAYA;;wBAAKA;MAALA;M1GgC/Bn9C,oCAEyBA,yBAFzBA,8D0G5BIm9C,uBAHFA;e1GoCeA;Q0GnCbA;;QAGAA,kBAAUA;;QAOVA;;IAEJA,C;sBAGKC;;kBACIA;MAAPA;gBAEoBA;MAApBA,2BAA0BA,aAAIA;MAECA,kBAAIA;eAAaA;oBAA5CA;MAIJA;kBACEA;;QACAA,kBAAUA;;QAMVA,MA4BJA;;gBAzBEA;;MACiBA;MACjBA,kBAAUA;MAKVA,mBAAWA;;MAKMA,EACjBA;MAIAA;;MAEAA,kBAAUA;;IAMZA,C;2BAGKC;MAECA;MACaA;MADMA,UAANA,iBAAUA;;wBAAKA;MAALA;M1GnC7Br9C,oCAEyBA,yBAFzBA,8D0GwCIq9C,iCA7UAA,aAyUFA;e1G/BeA;Q0GgCbA;QACcA;QACdA;;QAEAA;;IAEJA,C;kBAIKC;MACHA;UAAsBA;M3GnTxBx7C;MCmQAmJ,4BAEyBA,mBAFzBA;e0GoDMqyC;aAJJA;e1G3CeA;Q0G4CbA;UACgBA;;UnGuGA9/C;;ImGlGpB8/C,C;8BAIKC;MACHA,uBAAUA;IAUZA,C;yBAXKC;;K;0BAAAC;;K;qBAAAC;;K;kBAcDC;MACEA;M3G7UN77C,sCCmQAmJ,uBAEyBA,mBAFzBA,kE0G2EE0yC;c1GtEeA;U0GuEKA;MAEpBA,YACFA;K;yBAGKC;MACHA;M3GtVF97C,sCCmQAmJ,uBAEyBA,mBAFzBA,uD0GmFE2yC;e1G9EeA;Q0G+EbA;UAAoCA,YAGxCA;;MADEA,WACFA;K;uBAMKC;MACHA;MACAA;eADIA;MAAOA;MAAXA;QnGsJeA;YmGtJKA;;MACpBA;MACAA;YAAoBA;IACtBA,C;iBAJKC;;K;;;YA3KSC;MuEtNZA,avEuNIA;;8BuEvOeC;Q1KmkBnBx9C;MmG1VIu9C,oBAAWA;IACZA,C;;;EAQaA;YAANA;MAAMA,mCAAWA,YAAWA;K;;;;YAgB1BA;;iBACRA;MsE/MiCA,iBCpDlBE;MvEoQKF,6CsE5NcA,CCxCnBG,0CvEoQuBH;QnG+T1Cv9C;ImG7TGu9C,C;;;;YAIGA;;MAAoBA,IAAdA,iFAA6BA,eAAYA;MAAzCA,WAAyDA;K;;;;YAezDI;MsE3OwBA,atE4OhCA;;8BuE1ReC;Q1KmkBnB59C;MmGvSI29C,oBAAWA;IACZA,C;;;;YAmBSE;MuEhSZA,avEiSIA;;8BuEjTeL;Q1KmkBnBx9C;MmGhRI69C,oBAAWA;IACZA,C;;;;YAOOA;MsE7Q0BA,atE8QlCA;;8BuE5TiBD;Q1KmkBnB59C;MmGrQE69C,oBAAWA;IACZA,C;;;;YAaSA;;iBACRA;MsE7QsCA,iBChErBC;MvE8UGD,6CsEtSgBA,CCxCnBH,0CvE8UoBG;QnGqPvC79C;ImGnPC69C,C;;;;YAgCSE;mBACJA;iBAGFA;;aAA6CA;MAH/CA;QAG2BA,8CAALA;;QAENA;eAEFA;MsE1UoBA,8BC9CjBH;IvEyXlBG,C;;;;gBxBhVCC;mBACEA;sBAAmBA;QACrBA,sBAAUA,mCAA+BA,sBAC1BA;MAEjBA,OAAeA,aAAPA,UAAeA,wBACzBA;K;SAgBcC;MAAEA;oBAGUA;MAFtBA,OAAMA,mCACNA,uBAAmBA,8BACnBA,YAAgBA,uBAAMA;K;kBAElBC;MAAYA,OAAUA,oBAAVA,kBAAqBA,OAAMA;K;gBAExCC;M3ErBiBA;uE2EqBcA;sBAxDvBA;MAwDMA,aAvDVA,8DAASA,yBAAYA,mBAuD0BA;K;;;;;;;;;;;;;;;gBgGrEtDC;MACEA,gB5HgNeA,KAAKA,M4HhNDA;QACrBA,sBAAUA,mCAA+BA,wCAC1BA;MAEjBA,OAAeA,aAAPA,UAAeA,wBACzBA;K;SAYcC;MAAEA;oBAGUA;MAFtBA,OAAMA,mCACNA,W5H6LiBA,KAAKA,M4H7LHA,8BACnBA,YAAgBA,uBAAMA;K;kBAElBC;MAAYA,OAAUA,oB5H0LTA,KAAKA,Y4H1LeA,OAAMA;K;gBAExCC;M3KuBiBA,a2KvBcA;;iB5HwLjBC;mBAAKA;a4HpNfD;a5HqNKC;M4HrNID;wBAAKA;MA4BJA,oC5H0LHC,8B4H1LwCD;K;;;;;sBC6C1DE;;iBACMA;iBAAiBA;MAAbA,gCAAmBA;QACzBA,sBAAUA,mCAAqCA,uCACjCA;WACDA,wBAAeA;QAC5BA,sBAAUA,0BAAoBA,+CAA2BA;;iBAChDA;QAAeA,MAAVA;UACdA,sBAAUA,gDAAsCA;;IAGpDA,C;;;;;;;;;;;;iBnF3FWC;MAAWA,oCAAQA;K;sBAmBvBC;mBAZgBA;MAarBA;QAAkBA,WApBEA,yBAsBtBA;MADEA,qBAAqBA,0BArBDA,iCAsBtBA;K;gBAHOC;;K;;;;EoFXcC;mBAAbA;MAAaA,wBAAMA,eAASA;K;gBAC5BC;MAAUA;;MAAIA;MAASA;MAAbA,YAAmBA,iBAAMA;K;qBA2BpCC;MAEgBA;+BAAMA;;wBAAKA;MAAeA,2DAAMA;MACjDA;QAAmDA;gCvD8ZlCA,iBAAQA;;;MuD7ZZA;MAEDA;mBjK4XEA;;MiKtXlBA,sCACFA;K;eAbOC;;K;uBAeAC;MACIA;;MAAkCA;QAAaA,SAE1DA;M1EYYC,0BAAmBA,qBAAcA,kBAAWA,kBAAMA;;;QAG1BA;QAApBA,uBAA0BA,yBACVA;QACIA;QAAIA;QACRA;QACVA,sBAAoBA;QAL5BA,kCAGEA,sBAGQA,8BAAqBA,4BAC5BA,kBACAA;;;MAvBLD,4CADAA,wCADAA;M0EEVA,O1EmHFC,kCACyBA,oBAAMA,cAAcA,kBAAIA,YAITA,cAAJA,kBAAIA,WAAgBA,anG8XxDC,wB6Ktf6CF,aAC7CA;K;SAEcG;MAAEA;oBACmDA;MAA/DA,OAAMA,+BAAiBA,iBAAMA,OAASA,sBAASA,eAAIA,OAASA,gBAAGA;K;kBAE3DC;MAAYA;;MAAMA;MAAiBA;MAAvBA,iBAA2BA,mBAASA;K;gBAEjDC;MAAcA;mB7KFGA,oD6KEmBA,2CAAUA,uCAAMA,uBAAOA;K;;;;iB7E3DvDC;MAAWA,oBAAQA;K;;;wBtCyMxBC;mBAEAA;;ajEsMNl/C;2EkBrJ4Ck/C,O+CjD3BA;yBjExCgCA,wEAAMA,OiEyCbA;MpD2NrBA,iDAASA,iBAONA;QoDnNpBA,OA9CJA,YAA6CA,yBA8CxBA,sBAAcA,yEAInCA;MADEA,OAjDFA,YAA6CA,sDAkD7CA;K;eAMMC;mBAAuBA;;MAAVA,OCTnBA,YACmBA,yBlEgOnBC,0DkB9NwCD,O+CMGA,gCjEwN3CC,sDmE9YAr8C,8BFsLoEo8C;K;gBAE7DE;mBAESA;;;MAQdA,OjEgKFr/C,qDkBrJ4Cq/C,O+CXxBA,6BjEgKpBr/C,kDkBrJ4Cq/C,O+CnBjBA,kCjEwK3Br/C,yCiEpKKq/C,8BAAaA,mCjEoKlBr/C,4CiE5JKq/C,iEACLA;K;;;;;;;YAhKkBC;MACdA;;QACSA;QAAPA,SAMHA;;QAPCA;QAEEA;QAEKA,C1C8LgBA;Q0C7LrBA,WAEHA;;K;gBAReC;;K;;;YA4CKC;MAIFA;iBAAbA;yCAAMA,iBAAaA;M/C0IdA,gC+C1I0BA,kB/C0I1BA;M+CzIkBA,iCAAPA,iBAAaA;MAChBA,2BCoFrBA,YACmBA,2CC9KnBz8C,0BACqBy8C;MFwFsBA;MAAtBA,+B/CwIRA;M+CxITA,OAqCJA,YAA6CA,0CApC1CA;K;;;EAe8BC;YAAVA;MAAMA,2BAAgBA,0BAAiBA;K;;;;YAY1BC;MAA6BA;MAAlBA,OCpC/CA,YA6FmBl7C,yBA7FgBk7C,uCCjFnC38C,+BFqHuE28C;K;;;EAKxBA;YAAfA;MAAWA,6BAAwBA,8BAAMA;K;;;EAkC7CC;YAAXA;MAAWA,iCAAMA,uBAAWA,gBAAkBA,OAAMA;K;;;;YAC3BA;MAElCA;MAAMA,sBAAOA;QAAYA,WAQ9BA;MAPWA,sB/CiXMA;Q+CjXUA,YAO3BA;MADqBA,SADfA;QAAOA,YAEbA;MADCA,oCAAaA,oBAAcA,kBAC5BA;K;;;EAemDC;YAAXA;MAAWA,iCAAMA,YAAMA;K;;;;YAIvCC;MAChBA,mCAAMA;;;MAAbA,OjEuKJ7/C,kDkBrJ4C6/C,O+CjB/BA,mCjEsKb7/C,yCiErKS6/C,8BAAaA,+BACnBA;K;;;;YAFUC;MAAWA;aAAMA,yBAASA,OAAMA;K;;;;YAMzBD;MACTA,mCAAMA;;MAAbA,OjE+JJ7/C,qDkBrJ4C6/C,O+CVhBA,8CjE+J5B7/C,4CiE7JO6/C,SACJA;K;;;;YAHyBC;MACZA;MAAVA,OAAgBA,8CAAkBA,mBAAmBA,8BACtDA;K;;;E8DvKkBC;gBAAdA;MAAUA,uCAAoBA;K;iBAO5BC;mBACLA;MAAIA;QAAkBA,iBAE5BA;MADEA,OF6WqBA,iBAAQA,eE5W/BA;K;kBAIWC;mBACLA;MAAIA;QAAqBA,WAE/BA;MADEA,OAA2BA,4BAAhBA,gBAAKA,WAClBA;K;kBAGWC;;kBACLA;MAAJA;QAAkBA,OAAOA,mBAG3BA;gBAFMA;MAAJA;QAAoBA,OAASA,iCAASA,OAExCA;MADEA,OAASA,iCAASA,gBAAMA,OAC1BA;K;gBAkMOC;MAAcA,OAAEA,wCAAaA,gBAAOA;K;;;;;;;;;;;;;;;YAjLyBC;;iBAG1DA;MAAJA;QACEA,OA2KRA,YA3K6BA,6DAmBxBA;MAhBaA,0BAASA;MACrBA;QAAmBA,OCtHzBA,oBAVoBv0C,iDD+Ifu0C;gBpH8C6CvkC;;6BAAMA;;MoHxDlCukC;QADEA;M5G/EfA;;MRwIyCvkC;6BAAMA;MoHtDpCukC;MpHsD8BvkC;6BAAMA;MoHpDzBukC;wBAEPA;MAAiBA,yCAAMA;MAGzCA,OAyJNA,gCA1JyCA,yBAAMA,mCAE1CA;K;;;;YAG+DC;MAClDA;iBAAoBA;kCAAXA;MACrBA;QAAmBA,OC1IzBA,oBAVoBx0C,8CD0Lfw0C;MAlCcA;gBpHqC+BxkC;;;6BAAMA;aAANA;MoHlB5CwkC;;UAMWA;Q5G5HRA;;Q4G0HDA,OAAOA,c5G1HNA,8D4GqIJA;;QpHG6CxkC;+BAAMA;QoHLhDwkC,OAAOA,sBAEVA;;K;;;;YAlCCC;MACkBA;;;aAChBA;sBpHmC0CzkC;;+BAAMA;sBoHjCPykC;QAA3BA;;MAGdA;QACEA,OAuIVA,YAvI+BA,4CAQzBA;MALiBA,mCAAeA;MAC9BA;QAAsBA,OC1J9BA,oBAVoBz0C,kDDoKmCy0C,OAIjDA;mBpHqB4CzkC;;6BAAMA;MoHvB/BykC;MpHuByBzkC;6BAAMA;MoHvBGykC;MpHuBTzkC;6BAAMA;MoHvBhDykC,OAiIRA,oBAhIgBA,yCACVA;K;;;;YA8BmEC;MACvDA;iBAA+BA;6CAAXA;MAChCA;QAAmBA,OC9LzBA,oBAVoB10C,iDD+Nf00C;gBpHlC6C1kC;;6BAAMA;MoHcxC0kC;;MpHdkC1kC;6BAAMA;aAANA;MoHiB5C0kC;QpHjB4C1kC;+BAAMA;QoHoB5B0kC;QADpBA,yBACQA,4CAAqCA;QAC7CA;UAISA;2DAAoBA;;QAQKA;MpHjCQ1kC;6BAAMA;aAANA;MoH8BL0kC;MpH9BK1kC;6BAAMA;MoH0GxD0kC,OpH1GkD1kC;MoHiC5C0kC,gEADoDA,sCAErDA;K;;;;YAcqEC;MACxDA;iBAA0BA;wCAAXA;MAC3BA;QACEA,sBAAUA,6EACiDA;gBpHpDjB3kC;;6BAAMA;aAANA;;QJ8WlD4kC;QyC8sFsBC;QAapBA;QACAA,uCzCztFgBD,UAAUA;;QyCguFxBC,2BAAgBA,WAAYA,CATjBA;mBzC3rF+BD;QyC8pF9CA,qEA57FcD;;Q+ErDEA;MAGFA;QF+JOA;QAsBGA,iBA7StBA,iBD40BqBG,ECrjBFH,qBDqjBoBG;;MjHjxBS9kC;6BAAMA;aAANA;MoHiEH2kC;MpHjEG3kC;6BAAMA;aAANA;MoHkED2kC;MpHlEC3kC;6BAAMA;MoHmElD2kC,OAuCNA,iCAA6CA,IAtCxCA;K;;;;gBnBtPKI;MACJA;;QAAgBA,6BAASA;MAC7BA,YAAOA,mBACTA;K;gBAEgBC;MAAUA,yBAAOA,YAAMA;K;wBAEjCC;MACFA,OAVJA,gBAUkBA,4FAAiDA;K;eAC7DC;MAAaA,O5BbnBA,gB4BaiCA,sCAAuBA;K;gBACjDC;MAAcA,uCAAiBA;K;4BAdhCC;;K;;;;EAYkBC;YAANA;MAAMA,+BAAOA,uBAAWA,gBAAkBA,OAAMA;K;;;EAC3BC;YAANA;MAAMA,+BAAOA,WAASA;K;;;;4B5BX7CC;MACJA;;QAAgBA,6BAASA;MAC7BA,YAAOA,mBACTA;K;gBAEgBC;MAAUA,qCAAOA,YAAMA;K;kBACxBC;MAAYA,qCAAOA,cAAQA;K;wBAGpCC;MACFA,OAZJA,gBAYkBA,4FAAiDA;K;gBAC5DC;MAAcA,mDAAiBA;K;4BAfhCC;;K;;;;EAckBC;YAANA;MAAMA,2CAAOA,uBAAWA,gBAAkBA,OAAMA;K;;;;gB2B8D5DC;MACJA;;MAAUA;QAAUA,YAkBtBA;;QAjBuBA;UAArBA;;;;MAEeA;;uBAAkBA;MACjCA;;QAIYA;UAAUA,O1CqFxBA,YAA6CA,yB0CrFJA,qDAUzCA;QATIA,OClFJA,gBDkFyBA,kDASzBA;;QAFIA,OAgJJA,YAAuDA,oBArJzCA,wBAERA,S3BxFNA,gB2BwF4BA,2EAGUA,WAEtCA;K;qDAIgBC;MAEdA;wBAAIA;MA1E4BA,wBAAPA,CpF2PAC,yBoF3PQD;QA0ELA,OAAOA,yCAGrCA;MA6EcA;MA9ELA,SA8E+BA;MA9EtCA,0CAAqCA,gEAsIvCt5C,YAAuDA,qCArIvDs5C;K;mDALgBE;;K;0DASQC;MAEtBA;qDAAIA;MAnF4BA,wBAAPA,CpF2PAF,yBoF3PQE;QAmFLA,OAAOA,kDAKrCA;MAkEcA;MArELA,SAqE+BA;MArEtCA,+CAA0CA,qEA6H5Cz5C,YAAuDA,6CA1HvDy5C;K;wDAPwBC;;K;2DAWMC;MAE5BA;MAAIA;MA9F4BA,wBAAPA,CpF2PAJ,yBoF3PQI;QA8FLA,OAAOA,yCAAoCA,mFAMzEA;MAsDcA;MAzDLA,SAyD+BA;MAzDtCA,gDAA2CA,sEAiH7C35C,YAAuDA,qDA9GvD25C;K;yDAR8BC;;K;gDAwCnBC;MAETA;MAAwDA;MAtIxBA,WAAPA,CpF2PAN,yBoF3PQM;QAsIlBA,OAAOA,gDAWxBA;MAREA;QAiBYA;kBAA0BA;QAwDxC75C,yBAAuDA,4BAxEvB65C;;kBAExBA;;UAcMA;oBAA0BA;UAdHA,4BAsErC75C,YAAuDA;;;MAnEpC65C;MACjBA,4BpF7IFA,gDoF8IAA;K;wCAiBEC;MACIA;;0BAAeA;WACnBA;;QAESA;QAAPA,SAUJA;;QAXEA;QAEEA;kBAIAA;QAAQA;QAARA;;QACAA;;QAEAA;;IAEJA,C;qBAIMC;MACOA;M3B9MbA;M2BgNEA,uBAAqBA,6DADOA,wBAS9BA;K;oBAIOC;MACMA;gBACCA,yBAAKA;MACjBA,6BAA4BA,8CAC9BA;K;sBApLMC;;K;;EAwC6BC;YAAVA;MAAMA,2BAAgBA,iCAAiBA;K;;;EAI1BA;YAAVA;MAAMA,2BAAgBA,8BAAaA,WAAUA;K;;;;YAalCC;;MAAMA,2DAAKA,SAAGA,eAAKA;K;gBAAnBC;;K;;;YASKC;MACjCA;;MAAPA,YAAOA,0CAAKA,qGAAcA,UAC3BA;K;gBAFyCC;;K;;EACtBC;YAANA;MAAMA,yBAAEA,KAAIA;K;gBAAZC;;K;;;YAW6BC;MAClCA;;MAAPA,YAAOA,0CAAKA,+HAAqBA,UAClCA;K;gBAF0CC;;K;;;YAC7BC;;MAAMA,0CAAEA,YAAMA,OAAKA;K;gBAAnBC;;K;;;YAqFOC;MACRA,yCAAaA;aACRA,yBAGOA;wBAAYA;;wBAAMA;MAAzCA,OzCEJA,YACmBA,yBhDmDNA,4FiDjObpgD,8BwC6KGogD;K;;;;eAuBGC;MACeA;;MAEnBA;QACEA,mCAAeA;Q1C1EnBA,W0C2EgBA;;MAEdA,mB1C7E2CA,6C0C8E7CA;K;;;wBzCSMC;MACJA;;MAEcA,MAAZA;MAkBqBA;MhDoOOA,cgDnOZA,uCoEzCpBC,kFtI2EApkD,uBAEyBA,mBAFzBA,6CkElCEmkD;elEuCeA;oEkEtCkBA;UAC7BA;0BhDoScA,wCgDnSiBA,wBAAoBA;UACnDA,mC6DONA,Y7DN+BA,cAAWA,eAAYA,iBAAcA;;MlEkHpErjD,gDkBrJ4CqjD,kCgDwCdA,gFAIvBA;mBAEWA,qCAAcA,wBAAoBA;QAC9CA;MAIJA,OAnFFA,YACmBA,yBoEoBnBC,sCpH4QgCD,gCoH5QhCC,mDnElMAvgD,4BDgQsDsgD,SC/PjCA,cDgQrBA;K;gBAGOE;mBAGDA;;;MAGJA,OlEwFFvjD,qDkBrJ4CujD,OgD6DxBA,6BlEwFpBvjD,kDkBrJ4CujD,OgD0D3BA,kClE2FjBvjD,yCkE3FmDujD,8BAAaA,mClE2FhEvjD,4CkErFKujD,SACLA;K;;;;;;;;;;YAlPuBC;MAGIA,aAANA;;MhDkMRA,gCgDlM0BA,kBhDkM1BA;MgDjMWA;MADpBA,OA8IJA,YACmBA,2CC9KnBzgD,0BACqBygD,cDgClBA;K;;;EAiB8BC;YAAVA;MAAMA,2BAAgBA,yBAAiBA;K;;;EAuCrCC;YAAdA;MAAUA,6BAAkBA,6BAAKA;K;;;EAoBRC;YAAXA;MAAUA,yBAACA,8BAAgBA,sBAAaA;K;;;EAChCA;YAAdA;MAAUA,6BAAkBA,6BAAKA;K;;;EAQrBC;YAAVA;MAAUA,+CAAeA;K;;;EACbA;YAAdA;MAAUA,6BAAkBA,6BAAKA;K;;;;YAe/BC;MAAUA;iBAAKA,yCAAqCA;K;;;EACxCA;YAAdA;MAAUA,kCAAuBA,6BAAKA;K;;;EAiCrBC;YAAXA;MAAUA,yBAACA,sCAAwBA;K;;;EACvBA;YAAdA;MAAUA,mCAAwBA,6BAAKA;K;;;;YA+C9CC;gCACNA;QAAqBA,WAc1BA;MAZWA;QAAQA,WAYnBA;MAXWA;QAA0BA,WAWrCA;MAFYA;QAA4BA,YAExCA;MADCA,OAAaA,wBACdA;K;;;;YAcyBA;MACxBA;MAAIA;qEAA2BA;QAAkBA,YAGlDA;MAFqBA;MAAmBA;M6DD7CA,E7DC0BA;MACpBA,mBAAqBA,Y/CpPlBA,sD+CoPoDA,mBACxDA;K;;;;YAcYC;MAAWA;aAAMA,yBAASA,OAAMA;K;;;;YAG7BA;MACZA;MAAiCA;QAATA,iCAE7BA;MADCA,OAAgBA,8CAAkBA,mBAAmBA,8BACtDA;K;;;;gB8DlTIC;MAAcA,kBAAMA;K;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBrEPbj+C;mBAAUA;M/BuyBxBzO,E+BvyB0CyO;MAAlBA,mC/BgdIA,oBAuV5BzO,gC+BvyBgDyO;K;cAG9Bk+C;MAAQA,iBAAKA;K;wCAgB/Bj4C;MAE0EA;MAAxEA,gBA4EFA,uCrC1EIrK,sBAsLJD,eCyE2B3M,yFoCrL3BiX;MAnEEA,4BAAoBA,0CACNA;IAYhBA,C;2BAMKk4C;UACHA;mBACIA;MAAJA;QAA2BA;MAC3BA;IACFA,C;eAjDkBC;;K;2BAOEC;;K;uBAGEC;;K;;;YAiBNC;;iBAGJA;;QAAeA,MAOpBA;sBALiBA;aAAmBA;MAAnCA,qBAAgBA,2BAAqCA,gBACJA,qCAAlBA;IAIhCA,C;;;;YAJkDC;mBAC/CA;;MACAA;IACDA,C;;;;cA4BQC;MAAQA,0BAAeA,OAAMA;K;WAiCzCC;MACHA;MAMWA;eANPA;QAASA,sBAAMA;eAbIA;QAerBA,sBAAMA;eAEJA;QAAeA,MAGrBA;gBADEA;M/BuuBAA,2BAAYA;I+BtuBdA,C;gBAGKC;MACHA;MAMiBA;eANbA;QAASA,sBAAMA;eAxBIA;QA0BrBA,sBAAMA;eAEJA;QAAeA,MAGrBA;MADEA;IACFA,C;gBARKC;;K;iBAcAC;MACHA;MACyBA;eADrBA;Q/BwtBJA,K+BvtBEA,O/ButBFA;Q+BttBEA,MAYJA;;MATEA;MAGAA;MACAA;M/BktBgBA,K+B9sBhBA,O/B8sBgBA,2B+B9sBDA,aAAWA;IAC5BA,C;iBAfKC;;K;iBAkBQC;MACXA;qCAOyBA;eAPrBA;QAASA,sBAAMA;eAxDIA;QA0DrBA,sBAAMA;eAEJA;QrC0DN/uD,oBCuE2BC;QDtEzBA;QqC3DmB8uD,SASrBA;;MrC5HI5a,0BA2KJxoC,eCyE2B3M;WoC/HzB+vD;gBACuCA;MAAvCA,iCAAyBA,+BAAqBA,gBACMA,mBAAvCA;MACbA,OAA2BA,KAApBA,oBAAoBA,iBAAYA,0DAIzCA;K;aAGaC;MACXA;eAzEuBA;QA0ErBA,sBAAMA;eAGJA;QAASA,YAlGUA,eAAeA,OA2GxCA;WAREA;gBAEKA;QACHA;QACAA,gC/B+qBcA,K+B/qBUA,O/B+qBVA;;M+B5qBhBA,YA1GuBA,eAAeA,OA2GxCA;K;6BAMKC;MACiBA;WAApBA;gBACKA;YrCnKkBA,OA4MA3+C;QqCzCU2+C;gBA9FVA;MAgGvBA;QAAmBA,MAIrBA;MAHEA,qCAA6BA;WAC7BA;MACAA;IACFA,C;gCA3GsBC;;K;;;;;;;YA0DMC;IAAMA,C;;;;YAcOC;mBACrCA;;MACAA;IACDA,C;;;;qBHtCHj6C;;kBAGoBA;MAAlBA;aCvG4BA,OEnBNA;QAAkBA;M/BuyB1C5T,4BAvV4ByO,oBAuV5BzO,iC4B5qB+B4T,gBACzBA,wCACQA;gBAEeA,sBAAOA;QG/HMA;M/BuyB1C5T,K4BxqBE4T,wD5BiV0BnF,oBAuV5BzO,iC4BxqB2C4T,wBAAOA,yCAyBpCA,gCAC4BA,ECtIZA,OEhBJA;IHuJ1BA,C;sBAGkBk6C;MACZA;;MAEJA;UAIEA;UACAA;;;kBAKUA;eAAVA;UACAA;aACAA;;;eAKEA;;QlC8BNrvD,oBCuE2BC;QDtEzBA;QkC9BEovD,OA2EJA,gC3BuxBMA,mDsHv/BNA,2D3FgOAA,gCAtDAA;;MAjBMA;QAGWA;;kBACJA;oCACPA;UACFA,sBAAMA,gDAA0CA;;UAEnCA;UACbA;;;qBAGSA,OGlMWA;QAAkBA;M/BuyB1C9tD,4BAvV4ByO,oBAuV5BzO,iC4BrmB0B8tD,gBACpBA,uDACQA;aAEFA;qBC5KoBA;aE1BRA;MH6PxBA,EG7P0CA;MHqMxCA,uC5BkmBF9tD,4BAvV4ByO,oBAuV5BzO,oC+BpyB0B8tD,QH0P1BA,uCAtDAA;K;sBA1CkBC;;K;qBA8CbC;MACoBA;MAAvBA;gBACiBA;6BACNA,OG3MaA,OH2MFA;gBAElBA;MAAJA;QAAoBA,MAMtBA;MAFSA,EGjNiBA,gBHiNRA;MACCA;QAASA;IAC5BA,C;0BAGKC;MACgBA;MAAZA,KAAPA,sBAAOA;MACPA;WACAA;MAI4BA,eAAKA,qCAAaA,8CAA9CA;QACaA,EADbA,gBG7NwBA,OH8NAA;MAExBA;IACFA,C;;;;YAxGMC;MAAiCA;MAApBA,OAAOA,IAAPA,4BAAOA,6BAAsBA;K;gBAA1CC;;K;;EACcD;YAANA;MAAMA,uCAAmBA;K;;;;YAEWA;MACrCA;;;iBAILA;;QAAyBA,MAmB9BA;MAjB2CA;;MAAzBA,+CAA6BA;MAQlCA;;uBAAOA;MAAnBA;QACmBA,UAANA,OG5ISA,gBH4IaA,UAAPA;;QAMTA,UAANA,OGlJSA;IHoJvBA,C;;;;YAjB+CE;MAI5CA,gCAAgBA;MAChBA,OAAOA,8CACRA;K;gBAN6CC;;K;;;YA6D5CC;;MAAwCA;MAA3BA,OAAOA,EAAPA,sBAAOA,4BAAUA,oBAAmBA;K;gBAAjDC;;K;;;YACQD;MAAMA;0CAAcA,YAASA,UAASA;K;;;;;;;;;4C6H3LnCE;;K;;;e5HUIC;MAASA,kBAAMA;K;gBACnBC;;K;kBAOAC;;K;;;;;;;;;;;gBJ4CRC;MAEAA,sB6GjEWA;M7GiElBA,WAAOA,8BAA4BA,uBACrCA;K;eAEKC;MACHA;MAAWA;aACTA;QACAA,YAKJA;;MAFcA,KAAZA,kBAAYA,yB6G1EMA,4B7G0EqBA,sBAAUA;MACjDA,WACFA;K;6BAEKC;mBACeA;MAAlBA;MAOAA,sBAAMA,mCADKA;IAEbA,C;;;;mB6GxEUC;MAGJA;mDAAaA;aAAoBA;MACrCA,YAAOA,WACTA;K;gBAsCIC;sBA1DgBA;MA8DXA,8BADmBA,OAAOA;QAAQA,WAE3CA;MADEA,4BAAOA,eACTA;K;YAsCKC;MACWA;;MACdA;QACyBA,KACvBA,2BADAA,kCAAYA;MAGdA,cACFA;K;mBASKC;MACHA;MAAIA;QAAeA,MAarBA;MAXEA;QACcA;+BACWA;;UAIjBA;U/ItFDA;;;M+IqJPA,kEA7LkBC;IAmIpBD,C;cAdKE;;K;eA2BAC;MACUA;+CAAsBA,cAjJjBA;WAiJlBA;WACAA,2BAAqBA;MACrBA,iBACFA;K;;;oBoB7JOC;MAAkDA,kBAAWA;K;wBA8CzDC;MAAkBA,UAAGA;K;sBAKrBC;MAAgBA,UAAGA;K;uBAKnBC;MAAiBA,UAAGA;K;0BAUpBC;MAAoBA,UAAGA;K;eAmCvBC;MAASA,UAAGA;K;iBAKZC;MAAWA,UAAGA;K;;;oBC1GlBC;MAAkDA,YAAKA;K;wBA8CnDC;MAAkBA,eAAGA;K;sBAKrBC;MAAgBA,eAAGA;K;uBAKnBC;MAAiBA,eAAGA;K;0BAUpBC;MAAoBA,eAAGA;K;eAmCvBC;MAASA,eAAGA;K;iBAKZC;MAAWA,eAAGA;K;;;Y1F5GkCC;MACrDA;;;oDADqDA;QACrDA;;;;;;cnCcwBC,wCAArBA,0BAAQA;;gBoCsBfD,kBAAMA;cAGDA;cDtCyCA;;cAA1CA;mCAAiDA,gCAAjDA;;;cAAuDA;;;gBACnCA;;;cACeA;cjCkBXE,0CAArBA,0BAAQA;;gBkC6BfF,kBAAMA;cAIRA;;;cDlDCA;;;MAJKA;IAILA,C;;;;YoCS2BG;MAK1BA;MAAOA;kBAAQA;MAA0BA,W7Ey07BlBA;M6Ez07BvBA,2DAA2DA,Oe6CzDC,4CAtBGC,4D5Fq9nB4DF,qB6E3+nBlEA;K;;;;YAAOA;MACmBA;;8BAAdA,kBAAQA;UACSA;;M7EsvlCOA,6C6EtvlCUA;;M7EqvlClCA;aKrvlCiBA,OEnBNA;QAAkBA;M/BuyB1CvwD,4BAvV4ByO,oBAuV5BzO,iCqGhxB0BuwD,gBAAOA,0CAEpBA;IAIZA,C;;;;YAV8CG;MACjBA;MAATA,IAAjBA,WxED0BA,OEhBJA,gBqF0DtBF,4CAtBGC,4DfnB+BC;IACnCA,C;;;;YAE8BA;MAC7BA,+CAAiBA;IAClBA,C;;;;YAAUA;;MACTA,+CAAiBA;MACjBA;IACDA,C;;;;gBEjCIC;MAAcA,mCAA4BA;K;;;;2D3B2H5CC;MAQmBA;;MAMNA;MANhBA;MAE2BA,sEAMhBA;MACXA,6CAAsCA;MACvBA;gBAuEcA;MnB/KhBA;sCmBiIHA,mBAAuBA;MAvBjCA,wCnB1GFA,yCmB0GkDA;IA4BlDA,C;4DAGKC;;;;MAcaA;MANhBA;sCAQWA;MANgBA;gBAOWA;MAAtCA;MACeA;sCACHA;MAAuBA;gBAmBNA;MAnGDA;;MAvEM3gD;MAGGA;MAGAA;MAGdA;MAUSA;;MASLA;MAMIA;MAqCjC2gD,8EjC9EM1gD;;MiCqFF0gD,WAASA,UA6EMA,mDA7EYA,yCAACA;iBAoF9BA;mCAAaA;ctFsXK1nD;MsFpXlB0nD;QACEA,4CAA0BA;IAE9BA,C;aAwCMC;MACmBA;MAAvBA;WAEAA;gBACcA;;MAWdA,OAAOA,cAAMA,kBxG2IfroD,yDkBrJ4CqoD,OsFDfA,sCxGsJ7BroD,gDwG7IKqoD,kBAGWA,YAEAA,uBACGA,+BAFNA,kBAGbA;K;sBAKKC;eACEA;QAAQA,MAEfA;MADEA,sBAAMA;IACRA,C;kBAMOC;MACLA;;;0DADKA;QACLA;;;;;;;;;;;cAAqBA;mCAAMA,uCAANA;;;;;cACrBA;mCAAaA,wHAAbA;;;;cACFA;;;MAFEA;IAEFA,C;mBAGSC;MACiBA,WAS1BA;K;sBAGSC;;kBAGmBA,ctFiRRA;MsFjRlBA;QAAiDA,WAenDA;gBA5G+BA;MnB/KhBA;MmB8QbA,OnB9QFA,oBmB+QgCA,sCAA0BA,wBAAWA,kDAYrEA;K;;;YAjLkDC;MAC1CA;;;oDAD0CA;QAC1CA;;;;;;;gBAAoBA;;kBAEtBA;yIxG8KmBxpD,mBAFzBA,mHwGtKIwpD;;;sBACEA;;;;;;oBnBtB+BA,YAAvBA,2BAAQA;;oBAoDcA;8CA5GatiD,kBAAtBA;sBAyGbsiD,kBAAMA;;sBmB+DhBC,wBAjKwDA,OAAxBA,2BAAQA;;sBnBuGxCD;;;gBmB3BAA;qCAAMA,iFAOUA,yCAnFwBC,mEA4ExCD;;;;;gBAQDA;;;MAtBKA;IAsBLA,C;;;EnBnCkCE;YmB4B7BA;MAAMA,yBnB5BAA,C9DwJavjD,0B8DxJLujD,cmB4BQA,8BAA4BA,qDAG5CA;K;;;;YAH4CC;MAC1BA;;;oDAD0BA;QAC1BA;;;;;;cAAlBA;mCAAMA,gDAANA;;;cACAA;mCAAMA,yCAANA;;;;cACDA;;;MAFmBA;IAEnBA,C;;;;YAmCMC;MAIJA,8BADQA,IAALA;QACSA,MAExBA;MADCA,sBAAMA;IACPA,C;;;;YAkD0BC;MACzBA;MAAoCA;MAAtBA,SAAVA,MA/KUA;MAsLdA,StF6TgBroD,4DmE7hBpBqoD,gBmB2NgBA,uBACAA,qBACDA,wFAIZA;K;;;EAuBwCC;YAAXA;MAAWA,qBAAOA;K;;;;YAuBmBC;MAC1DA;;MAAPA,kBAAgBA,sDASAA,yCAACA,6CAClBA;K;;;EnB5LkCC;YmBkLjBA;MACdA,yBnBnLQA,C9DwJa7jD,0B8DxJL6jD,cmBmLOA,eAAWA,qEAKnCA;K;;;;YALmCC;MAChCA;;;oDADgCA;QAChCA;;;;;;;;;;;;;;;gBAC2BA;;;;;cAAzBA;mCAAMA,qBAAmBA,yBAAzBA;;;;cADFA;;;;;;cAGDA;;;MAHCA;IAGDA,C;;;;mBpC3QDC;;kBACCA;iCAASA;QAA2BA,WAS3CA;MARoBA;MACHA,+BAAKA;MAEbA,YlD8gBWA,sBkD/gBMA,QlD+gBN1oD;QkD/gB0B0oD,WAM9CA;MALEA,qBAAaA,mCAGCA,gBACGA,mBAFNA,OAGbA;K;mBAgBiBC;mBACRA;;apEyUTrpD;oEkBrJ4CqpD,OkDnLjCA,sFpE0FsCA,2DAAMA,OoEzF1CA;MAFXA,OvD2TAA,wDuDvTFA;K;;;;;;;;;EA5BiCC;YAAXA;MAAWA,+BAAkBA,UAASA;K;;;EAyBtCC;YAAXA;MAAWA,4BAASA,+BAAMA;K;;;EACbA;YAAXA;MAAWA,6CAAaA;K;;;;;mBiB9B5BC;MACHA;MAAyCA;M/DnB3C5nD,2BAsLJD,eCyE2B3M;M8DpH3Bw0D,4BAxHgEA,WuD1B1DC,oBvD6FkCC,8CuD7FlCC,gBvD6IyBD,6DAGEA;MAKjBA,wCACGA,+BAA0BA;aAD3CA;MA1HAF,SFUuBI,UETzBJ;K;mBAGKK;;kBACEA;iCAASA;QAA2BA,WAG3CA;MAFEA,OAbFA,qBAaqBA,OAAMA,iCAAuCA,aAClDA,qBAD2CA,aACrDA,UACNA;K;;;;;;;;;+BA+CkBC;MACwBA,sCAArBA,C9DmLMA,6B8DnLEA;MAC3BA;QAAqBA,cAGvBA;MAFEA,sBAAMA;IAERA,C;8BA2FKC;gCA5H4C3jD,kBAAtBA,C9DgNAC,6B8DhNQD,wBAcH2jD,kB/DhEP3jD,OA4MAE;Q+D7BXyjD,sBAAMA;MAClBA;IACFA,C;mCAyBaC;MACAA;;MAAXA;;M/DlBFroD,oBCyE2B3M;M8DkKvBg1D,8B/DjaApoD;;M+D4MFooD,WAASA,uFAKMA,6CAACA;MAEhBA,OAAeA,kBAAoBA,8DAGrCA;K;oBAOEC;MACWA;wBAEKA;MAFhBA;MAEOA;MAAPA,kCAAgCA,4CAACA,mCACnCA;K;iBAMKC;MACHA;eAhMuBA,qBAAYA,UAgMtBA,kCFhPgBC,6BqG8BFD,aAAiBA;QnGkNnBA,MAoB3BA;gBAnBMA;MAAJA;QAA2BA;MAGUA,eApMdA,qBAAYA,UAoMZA,kCFvPIA,MEuPCA,SAASA,kBAAcA;MACnDA;QAAqBA,MAevBA;MANkBA,KAAhBA,sBAAgBA,qCAAkCA,uCARpCA;IAchBA,C;oBAoCKE;MAEHA;;MAAiBA,SAAbA,+BAAkBA;QAAYA,MAgDpCA;MA7CEA,aAASA;gBA7PcA;aAAYA,UAsQPA,kCFtTCA;YsGLNA,aAAiBA;eAAYA;+BA0FxBC,2BAAmBA;;QA1FED;;QpG8T/CA,eAA2BA;iBACHA,aAAiBA;QACzCA,eAA2BA;MAG7BA,+CAA4BA;MAC5BA,aAASA;gBA/QcA,qBAAYA,UAiRrBA;YFpUaA,MEoURA,ShBnTQA;QgBoTzBA;gBAKEA;YnEuOc1pD;QmEtOhB0pD,QAAMA;QnE7GRA;;MmEmHAA;QAAmBA,MAYrBA;WA1SyBA,qBAAYA,UAkStBA,kCF3VgBA,6BE2VVA;MAEnBA,qKAKIA;IACNA,C;oBAlDKE;;K;uBAqDAC;MAC8DA;MAAjEA,uCAA2BA;Y9DvFFv1D;;gB8DvNFu1D,qBAAYA,UAgWjBA;MA7CZA,gBAAQA,oCAsEZA,oB/DjaA3oD,sBAsLJD,+E6DjM6B4oD,MEmZJA,ShBlYIA;IgBmY7BA,C;qBAeaC;MACXA;;;6DADWA;QACXA;;;;;;;;;;;;;;;gBAC2BA;;;;;cAAzBA;mCAAMA,qBAAmBA,kCAAzBA;;;;cADFA;;;;;;cAGFA;;;MAHEA;IAGFA,C;;;YA3S6BC;MACnBA;MAIqCA;MAJ3BA,0BAAKA;MACnBA;QACOA,wBAAOA,QAAIA;;QAEXA,wBAAOA;IAEfA,C;;;;YAJmBC;;MAAMA,yCAAqBA,YAAMA,aAAOA,YAAWA;K;;;;YAwFhEC;MACYA;;;oDADZA;QACYA;;;;;;;;cACnBA;cACAA;mCAAMA,uCAANA;;;cACAA;;cACDA;;;MAJoBA;IAIpBA,C;;;;YAEkCA;MACjCA,4EAAiCA;IAClCA,C;;;;YAyBDC;MhEnFmBC;iBgEoFkCD,QhEpFlCC;;kB8EQED,qB9EDFC;sB8EEsBD,4BAAfA,qB9EKFC;;;M8EA1BD;;;;;;;Md0EIA,0FACFA;K;;;;YAEkDE;mBAChDA;MAA0BA,6BAA1BA,4BAA+BA,QAAIA;IAIpCA,C;;;;YAJoCC;mBAC7BA;YAhNeA,qBAAYA,UAgNlBA,kCFhQYZ,6BqG8BFY,aAAiBA;QnGkOfA,MAE1BA;MADCA,mB9DMqBA,gBCsa3BA,uB6D5akDA,2BAAWA;IACxDA,C;;;;YA2CMC;;eACHA;MAAJA;QACqBA,EAAnBA;;QAEmBA,EAAnBA;IAEHA,C;;;;YAYQA;MAAMA,+CAgIVA;Y/D1bkBC,OA4MA3kD;Q+D8OM0kD;MAhIdA,WAAgCA;K;;;;YAoCjCE;MAmDEA,aAlDdA;wCAAgBA;YAiDdA;QACMA;;QAERA;IARDA,C;;;;YA5CiBC;;;;MAiCEA,8CACVA,mBAGAA,kBAAaA,gCACbA,sBACAA,oBAAWA;MAtCjBA,WAASA,2CAyCDA,yDAAyBA;IAClCA,C;;;;YA1CUC;MACoBA;;;oDADpBA;QACoBA;;;;;;;;;cAC3BA;cAUUA;cAMVA;mCAAMA,yEAANA;;;;;gBAC2BA;;;;0BAESA;;;;;;;gBAElCA,+BqG1YoCA,gCrG0YsBA;gBAC1DA;;gBACAA;;;cAGFA,2BAAkCA;cAEtBA;;;cACbA;;;MA9B4BA;IA8B5BA,C;;;;YAnBkBC;MACIA;;;oDADJA;QACIA;;;;;;;cAAnBA;mCAAMA,wGAANA;;;cACAA;mCAAMA,kBAAWA,0DAAjBA;;;cAxLRA;cACAA,+BAAsBA;;cAyLfA;;;MAHoBA;IAGpBA,C;;;;YAyB8BD;MAA6BA;MAAPA,OAejCA,IAfiCA,MAejCA,mCqG3a5BE,eAA8CA,0BrG4Z2BF;K;;;;iBAsCpEG;;QAEcA,MAGnBA;mBAFMA;Y/DlbmBA,OA4MAjlD;Q+DsOKilD,MAE9BA;MADEA;IACFA,C;;;;WFtaaC;mBAASA;YA2IhBA;QACFA,kBAAMA;kBArEYC,mBRKAC;QQkElBF,kBAAMA;QAGRA;MAEAA;MAnJoBA,SAmBGC,UAiIPD,kCAMaG,UAAUA,OA1JDH;K;;;gBAwGnCI;;iBAjCiBA;aRKAF;QQ6BLE,MAKjBA;M5DlHAA,qC4D+G2CA;MACzCA;MACAA;IACFA,C;gBAOKC;MACHA;gBA/CoBA,mBRKAH;QQ0CLG,MAKjBA;MAJMA;QAAoBA,MAI1BA;WAFEA;MACAA;IACFA,C;eAGKC;mBACCA;YRdeC;QQejBD;;QpD1IJ75B,qBoD8I4B65B;IAE5BA,C;kCAIaL;MACPA;;QACFA,sBAAMA;qBArEYA,mBRKAC;QQkElBD,sBAAMA;WAGRA;MAEAA;MACAA,YAjIuBA,UAiIPA,kCAMaE,UAAUA,OALzCF;K;oCAQaO;;kBAtFSA;aRKAN;QQkFLM,YAJcA,UAAUA,OAgBzCA;MAVEA;MACAA;eAEIA;QACFA;;QAEAA;MAGFA,YAf6BA,UAAUA,OAgBzCA;K;;;;gBuG/KOC;MAAcA,gBAAIA;K;;;qBrHuOpBC;MACeA,8BACPA;;arEuFbrnD;sBamCEqnD,iBbnCFrnD,iDAkEuEqnD,OqExJ5DA,qDxDyHTA;sBK6LkBA;MmDnTlBA;QAAyBA,MAK3BA;MAHEA,sBAAMA,8BAAyBA,+BACxBA;IAETA,C;iCAKKC;MACaA;MAAhBA;MACAA,6BAAmBA;IAIrBA,C;aAOSC;MACGA;8CAA0BA;aACzBA,2BAAoBA;kBACjBA;;kBAASA;gBACHA;;kBAAcA;gBACZA;;kBAAiBA;gBACbA;;kBAAqBA;gBAChCA;;kBAAUA;MACjBA,6BAAiBA;;MACXA,uBAAUA,kBAAkBA,aAC7BA;MAVmBA,+BAWtBA,iBAAUA,cAAcA,SACrBA,6FAAsDA;K;iDAG5DC;MAWeA;MAOtBA;MACAA;MACAA;;uBARiBA;;oBACRA;;0BAIWA;;QAIbA,kBAHaA;MAGpBA,gCANqBA,yBAKLA,0BAJNA,gCAGIA,YAREA,uBAGCA,eAkBnBA;K;yBAhCSC;;K;sBAAAC;;K;8BAAAC;;K;mBAoCAC;;iBACHA;MAAWA;QAASA,WAQ1BA;QANMA;MACJA,gBAAmBA;MAInBA,OAAOA,gCAA4BA,6EACrCA;K;iBAIqBC;MAEfA;MACJA,8BAAmBA;gBAKPA,OkBpQmBA;;uBAAgBA;MAJ5BA;MlBuQZA;MAAPA,wEAEaA,+BAAkBA,wBACrBA,2BACMA,kCACEA,yCACIA,kCACXA,iBACDA,sEAEEA,wBAAWA,uFAGzBA;K;yBAGQC;MACNA;MAAIA,oBAAmBA;QAAMA,aAM/BA;kBAHgBA;MAFPA,2BhDrLiBA;MgDqLxBA,qFAGyBA,uCAE3BA;K;;;YAvPEC;MAAmCA;;eAQzBA;MARgBA,yBAKJA,qBAKVA,cADIA,kBAHLA,aAHDA,YAIMA,sBANJA,cACCA,eAEKA,cAMCA;K;;;;YAY2BC;MAC5CA;MAA8CA;MAAzCA;;aAAkBA;iCAAlBA,oBAAuBA;QAAWA,aAExCA;MADCA,OAAOA,eAAaA,gCACrBA;K;;;ExBkOGC;YwB7I2CA;MAAiBA,sBuF5OlEA,0BILAC,aCgBAC,cAAsCA,e5FkOFF,4BuF5ODA,YvF6OhBA,wEAAoBA;K;;;EAchBG;YAAVA;MAASA,yBAACA,6BAAaA,sCAA6BA;K;;;EAC1CA;YAAZA;MAASA,iBAAGA,kCAAKA;K;;;;YAePC;MACgBA;MAAjCA;MACAA;eADkBA;MAAlBA;MACAA;IACDA,C;;;;YAkBYC;MAA0BA;kCAAUA,QAAMA,qBAAUA;K;;;;YAEpDA;MAA0BA;kCAAUA,QAAMA,qBAAUA;K;;;;YA2C9CC;MACjBA;MAAKA;MACqBA;iCADrBA,gCAA0BA;QAAWA,MAE3CA;;MADYA,EAAXA,cAAWA;IACZA,C;;;;YASkBC;MACSA;MAAgBA;MAA1CA,oDAA0BA,oBAAgBA;IAC3CA,C;;;;YAYsBA;MACRA;MAAqBA;MAA9BA,OxBgBFA,ewBhBWA,yBAAqBA,wEAAqBA;K;;;;gB6DlTpDC;MAAcA,gBAAIA;K;;EApCQC;YAAdA;MAAcA,6CAASA,oBAAcA,WAAUA;K;;;;YAClDA;MAAMA,WAAIA;K;;;;gB3CsBrBC;;oBACiBA;QAAMA,MAO5BA;MALEA,wCACIA;IAINA,C;gBAKKC;MACHA,OAAOA,0CAAgBA,kDAqBzBA;K;oBAIiBC;MAUgBA;yBAAgBA;MAT/CA;QAAmCA,WAErCA;MADEA,OA5DIA,uBA4DsBA,kDAC5BA;K;gBAGOC;MAAcA,oDAAiBA;K;SAGxBC;MAAEA;oBACuCA;MAAnDA,8CAA6BA,4CAAgBA,2BAAMA;K;kBAG/CC;MAAYA,OAAOA,oBAAPA,2BAAeA;K;;EqE9FnCC;YrEoB+BA;MAAMA,iCyEzBrCf,aCgBAC,cAAsCA,mB1ESqBc,aqEnBxBA,UrEmBiCA;K;;;EA0BxDC;YAANA;MAAMA,uDAAgBA,8DAEYA;K;;;EAD9BC;YADkBA;MAClBA,yCAAyBA,wBACzBA,wCAA6BA;K;;;;YAQdC;MACrBA;MAAIA;eAAYA;aAASA;wBAAQA;QAAYA,WAmB9CA;aAlBkCA;6CAAQA;QAAYA,WAkBtDA;aAjB0BA;yBAAGA;QAAYA,WAiBzCA;MAhBCA;;UAEIA,SAAwBA,SAc7BA;;UAZKA,SAAwBA,UAY7BA;;UAVKA,SAAwBA,KAU7BA;;UARKA,SAAwBA,QAQ7BA;;UANKA,e2CpBoBA,4CAAmBA,0B3C0B5CA;;UAJKA,SAAgBA,SAIrBA;;UAFKA,YAELA;;K;;;;gB2BuDIC;MAAcA,gBAAIA;K;;EAxDSC;YAAdA;MAAcA,qCAASA,oBAAcA,WAAUA;K;;;;oCtDnD9DC;;MAGCA;MACAA;MAFJA;YAAoBA;MACpBA;QAAoBA;MACpBA;QAAkBA;IACpBA,C;6BALKC;;K;wBAAAC;;K;gCAaCC;;iBAKeA;;Q+E4BUC;;elD1D7BD;;iBAA2BA;iBAAsBA;UGHjDE,OHGAF,gCkD0DAC;;;QlDzDOD,8CACcA,qBAA2BA;;M7B4BtCA;MACVA;QAAaA,YAMfA;MAJEA,OAAOA,yBAAiBA,+DAI1BA;K;iBAjDIG;;K;eAIAC;;K;;;YAyCsBC;mBAClBA;;MAA0BA,M5B+tCdC;Q4B/tCMD,yBAA6BA,qBAEpDA;MADCA,OAAOA,yBAAuBA,qBAC/BA;K;;;;S6H3CWE;MAAEA;oBACsDA;MAAlEA,uCAAkBA,iBAAgBA,eAAUA,iBAAgBA,OAAMA;K;kBAG9DC;MAAYA,QlLuBWA,gCkLvBXA,elLuBWA,gCkLvBYA,eAAgBA;K;gBAGpDC;mBACDA;kBAAiBA;QAASA,gBAIhCA;kBAHuBA;QAAUA,WAAOA,OAsGnBA,KAnGrBA;eAFMA;MACkBA,YADDA;QAASA,gBAEhCA;MADEA,yCACFA;K;;;gBAuCOC;MAAcA,gBAAIA;K;;;gBA4DlBC;MAAcA,gBAAIA;K;;;;;;gBxFxHlBC;MAAcA,mBAAOA;K;;;;;;YA2DdC;M1Fqed1oD;M0FneE0oD,iCCpEFA;MDsESA,O1FggBqCzoD;M0FhgB5CyoD,8EAEDA;K;;;;YAwDsBA;IAAKA,C;;;;a1BXpBC;MACNA;MAAIA,iBAAQA,uBAAQA,cAARA;QAAuBA,QAAvBA,kBAIdA;gBAHYA;MAAVA;QAA4BA,OAxFxBA,uBA2FNA;eAFMA;MAAJA;kBAAsDA;elDEvBA;;0BAAUA;QkDFnBA,OAzFlBA,clDiEAC,eA0BqCD,2CkDA3CA;;eADwBA;gBAAoBA;MAApBA;wBAAYA;MAAZA;wBAAYA;MAAlCA,OAvFIA,4BAwFNA;K;aAKSE;MACPA;MAAIA,iBAAQA;QAAMA,WAEpBA;eADSA;;iBAAmBA;QlDPKA;0BAAUA;QA1BrCD,oBA0BqCC,+BAAVA;;MkDO/BA,SACFA;K;kBAGQC;MAAYA,QAASA,oBAATA,iBAAoCA,oBAAZA,oBAAoBA;K;SAGlDC;MAAEA;oBAGoBA;MAFhCA,qCACMA,2BAAYA,mBACZA,oBAAeA,YAAWA;K;gBAG7BC;mBACDA;MAAJA;QAAsBA,OAAOA,gBAG/BA;eAFMA;MAAJA;QAAyBA,OAAUA,aAErCA;MADEA,aACFA;K;;;uBhB2BIC;MAEoCA;;;uDrC0ZhC5lD;MqC1ZY4lD;QAAlBA;MAIoBA;gBACHA;MyBpKEA;gBzBuKAA;;MAPZA;MAAPA,6EAEgBA,gDAGFA,oCAA8BA,oCAC3BA,oCAA8BA,mCvDyNjD3vD,uDkBrJ4C2vD,OqCnEXA,kEvDwNjC3vD,8CuDpNO2vD,qBAEPA;K;sBAMIC;MACFA;;;QAAkBA,WAgBpBA;MAdoBA;MACNA,4BAAcA;eAQXA;MACIA;eACHA;MyBjMGA;MzB6LZA;MAAPA,8HAKyBA,aAE3BA;K;oBAGKC;MAIDA;MAHMA,wBAAcA;mBAKbA;a4BzMKA;MRzBhBC,2BAgH4BC,oBAhH5BD,gCpBkOyBD,SAAOA;a4BnMcA;MR/B9CC,2BAgH4BE,oBAhH5BF,gCpB0OmBD,SAAOA;a4BxMmBA;MRlC7CC,2BAgH4BG,oBAhH5BH,gCpBoPqBD,SAAOA;;MAS1BA,WAASA,2EAEMA,yCAACA;IAClBA,C;;;YA/MsCK;MAClCA;MAAuCA;eAAnCA;MAAJA;QAAuBA;;MACfA,IAARA,QCmCsBA,gBCtDM1tD,SEvBNA,gBJ0CL0tD;IAClBA,C;;;;YAI8BA;IAAMA,C;;;;YAELA;MK1CpBC;;;;MAgBVD,WAASA,2BL2BuBA,+JK3BLA,wCAACA,uBAtB5BA,8BAUgBC;ILiHjBD,C;;;;YA1EiCE;;;;MAC9BA,WAASA,kHAqEGA,oDAEUA;IACvBA,C;;;;YAxEUC;MACCA;;;oDADDA;QACCA;;;;;;;;gBAECA;;gBADTA;gBAEEA;kBACAA;;kBAEAA;;;;kBACAA;kBACAA;;kBACAA;;;;cAGOA;gBACPA;;gBAEAA;;qBACcA;gBACdA;;gBAEAA;;;;;;oE3BsYkBrqD,oBAuV5BzO,kC2HzsByC+4D,oCAKEA;chGrBrBD;mCAAYA,gCAAZA;;;;cACPA;cAAPA,eAAcA,OAAPA;;gBgG8GXA,kBAAMA;;;cAIRA;chGhHYA;cAWsBA,uBAAvBA;;6B0H7EKA;c1H8EcA,kCAAYA;;cAKxBA,wCAAkCA,2BAA7BA;cAHFA,uBAI6BA,kBAAzBA,gDACaA,kBAAnBA;;cKjFSA,8BAArBA,0BAAQA,wBLmFmBA,wBAChBA,iCAA2CA,OAA3BA,yCAClBA,iCAAyCA,OAAzBA;cAEFA;mCAAMA,+CAANA;;;;cAGxBA;mCiDMJA,WAASA,8CAAkBA,yCAACA,4DjDNxBA;;;cAGIA;ciC3FWA,oCjC2FiCA;ciC1FzBA,mCAAYA;cACjBA,4BAAeA,oBAAVA;cADtBA,0BAEuBA,kBAAhBA;cjC2FRA,kEAF0BA,oBAAhBA,6BsD7FJA,uEtDsGUA,yCiDPQA;;;cjDQzBA;;;MApESA;IAoETA,C;;;;YA1CmBE;MACZA;;MAAOA,WAAPA;QACeA,IAAjBA,WEhEkBA,OEhBJA;QJiFdA,MAMHA;;MAHCA,eAAcA,OAAPA;MG3EaA,mCAArBA,CnC0QkBC,yBmC1QVD;MH4E+CA,yBAAhBA;MAClCA,mCAAqCA,iBAAdA;aADPA;MGnDxBA;QACFA;UAAqBA;uD2HIUE;cAH7BA;UAAMA,kBAAMA;UAChBA;QAEAA,wCAAyCA;eACzCA;;yC5BK0BC,S4BLgBD;Q5B7BbE;cA+BnBD;UACRA,kBAAMA;QAERA;;e/FTWH;;UACTA,kBAAMA,+DAAyDA;;UAE/DA;;IHgDKA,C;;;;YAyBQA;MACCA,gBACJA;IACLA,C;;;;YADKK;mBAAuBA;iBAA0BA;MA8CrDA,ECnFgBC,gBCtDMruD,SEvBNA,gBJgKPouD,mEALnBA,6BAzC4CA,YAgDhCA,wBAAgCA,QAAOA;MAhD/BA,WAAmDA;K;;;;YAMrDP;MACVA,gCAAWA,iBAAgBA,gDAAYA;IACxCA,C;;;;YAyD0BS;MAC3BA;MAAOA;eACDA;eAAgBA;MAAhBA,gBAAgCA;MADtCA,wEAEMA,wBAA8BA,0BACrCA;K;;;;YAYuBC;MACjBA;;MAAPA,eAAcA,OAAPA;gBACPA;wBAAaA,2BAAUA,cAAgBA,UACnCA,+BAA0CA,iBAAnBA;IAC5BA,C;;;;YAaqBC;MACpBA,eAAcA,OAAPA;M4BnLaA,I5BoLpBA;IACDA,C;;;;YAE6BA;MAK1BA;MAFUA;;MAFJA,IAARA,QAAQA,eAASA,+EAECA,OAAOA,sBACPA,OAAOA;IAE1BA,C;;;;YAEuBA;MAOpBA;MAHIA;qBAAWA;MKrNSA,mCAArBA,CrCmQkBtuD,yBqCnQVsuD;qBLsNiDA;eAC3CA,SAASA;MALpBA,IAARA,QAAQA,eAASA,kEAEUA,gCAEDA,oC4B7NDA,M5B8NQA,ScjNZA;IdmNtBA,C;;;;YAEyBA;MACxBA;MAAyCA;eAArCA;MAAJA;QAAwBA,qBAAyBA;;MACzCA,IAARA,QAAQA,eAASA,kFAESA,KAAKA,sBACbA;IAEnBA,C;;;;YAEQA;M4BrNWA,I5BsNlBA,2EAAeA,SAAKA;IACrBA,C;;;;YADqBC;MAAeA;MAARA,mBAAQA,eAASA,qEAAqBA;K;;;;kBGlOvDC;;iBACRA;;QACFA,OAAOA,mBASXA;;iBARaA;;UACTA,sBAAMA;;UAENA;U4F8DAC;UG3BAC;U4BnCJF,yC/BnBIG,0DGNAC;U4B0BFC,iDtFoGFC;UrCnGIN;UACAA,gB2HvB4BA,mC3HyBhCA;;;K;;;gBiI1BQO;MnDAkBA,amDARA,oBnDAAA;MmDAAA,wBAAYA;K;kBAGdC;M3L+ShBvnD,a2L/S4BunD;oC3LiTHvnD,mBAFzBA,4C2L/S0CunD;K;aAanCC;MAAWA;2BAAaA;K;;;;;YvGnBaC;MACnCA,yBAAKA,sBAAMA,eAAuBA,IAAVA;IAChCA,C;;;;YAAOA;MCwH6BA,2BAAvBA,C9DwJavsD,0B8DxJLusD;MA4EpBA;MACAA,+BAAsBA;MDrMTA,WAA2CA;K;;;;YesCpBC;M0BkBrBA,yBAAQA;M1BjBFA;QAAKA,QAAuBA,0BAIpDA;MAHwBA;QAASA,QAAuBA,gCAGxDA;MAFKA,iCAAgCA,mBAARA;QAAqBA,QAAuBA,oBAEzEA;MADCA,QAAuBA,4BACxBA;K;;;;qB3ByBYC;MlDqIX97D,wBCuE2BC;MDtEzBA;MkDrIE67D,SAAiEA;K;iBAOpDC;MAEMA;;;yDAFNA;QAEMA;;;;;;cADrBA;mCAAaA,cAAaA,6NAA1BA;;;;gBAEuBA;;;;;cAChBA,iDAAUA;;cAAjBA;;;;cAGFA;;;MALuBA;IAKvBA,C;mBAgEkBC;MACCA;MAAfA,OO9HJA,e7DijBsBC,mCsDnbJD,2BAUUA,aM1JLE,kCN8JMF,cM9JNG,kCNkKKH,aMlKLI,6B6GGvBJ,kBnDTAK,gChE4KkDL,8JArB3BA;K;6CAkDvBvuD;MAISA,IAAPA,OChM4BA,yBAAWA,iBDgMpBA,wCAIhBA,aAAWA;IAGhBA,C;WAwBa6uD;MACXA;eAAIA;QACFA,sBAAMA;WAERA;;gBAGeA;M5CgjBjB/6D,2CAvV4B+6D,oBAuV5B/6D,iC4ChjByC+6D,gBAAOA,iCA6BnCA;QA7BXA;MAmCAA;MAEAA,OAAOA,mBACTA;K;iBAQOC;;MAEaA;IA8CpBA,C;2BAhDOA;MAEaA;;;yDAFbA;QAEaA;;;;;;;;cAAlBA;;;;;;;csCtRqBA;;ctC2RnBA;;;cACuBA;cAErBA;mCAAMA,sGAANA;;;;0CiH3MwBA,2BAAmBA;;;;cjH+M7CA;;;;ctDhPiCC,uCAqgB7BzoD;;;;;;;;gBsD7QFwoD;;;;;gBACeA;;;;;;cAEbA;;;cACEA;mCAAMA,6EAANA;;;;cADFA;;;;;cAE4CA;;;cAArCA;;;cACLA;mCAAMA,+CAAuCA,6CAA7CA;;;;cADKA;;;;cAGYA;;;;cazRoBA;;yEAmEP7I,+CAgDTA,+BAGEA;cFtFLvkD;;c5EyWRA;;;cwBjZpBlJ;0FsDoImBytD,+BAA0BA,0BFzF1BvkD;;;;cXuPTotD;mCAAMA,6EAANA;;;;;;;;;cATJA;;;;;;;;;cAiBFA;;;cACuBA;cAErBA;mCAAMA,uGAANA;;;;cACAA;;;cAAaA;mCW3SKA,iGX2SLA;;;;;;;;;;;;;;;;;cAGfA;;;;;;;;cA9CGA;;;;;;MAEaA;IAFbA,C;yCAsDAE;MAEYA;IAoCnBA,C;4BAtCOC;;K;8BAAAD;MAEYA;;;iFAFZA;QAEYA;;;;;;;cAAjBA;mCAAMA,0EAANA;;;;cgDvSAA,sBAAKA;chD6SOA;;;;wDGzPc1C,oBAhH5BD,gCH4WwC2C;;cAWtCA;cAEAA;cAIAA;mCAAaA,0BAAmBA,+EAAhCA;;;cAIAA;mCAAMA,8GAANA;;;;cAEKA;;gBAA+BA;;;cACpCA;mCAAMA,iEgHxRWA,+HhHwRjBA;;;cAEAA;;;cArCKA;;;MAEYA;IAFZA,C;uBA4CAE;MAEYA;IAmBnBA,C;iCArBOA;MAEYA;;;+DAFZA;QAEYA;;;;;;;cAAjBA;mCAAMA,wDAANA;;;;;cAKIA;;cAaGA;mCAAMA,oGAANA;;;cAAPA;;;;;;cApBKA;;;MAEYA;IAFZA,C;qBAiGFC;MACuBA;MAA1BA;MACAA;oBAEkCA;a2CtdpBA;M3CsddA,mCGlfF9C,2BAgH4B+C,oBAhH5B/C;gBHmfE8C;;MM/cAA,iEAAUA,ORiDZE,4B6CtD4DF;gB3Cqd1DA;MMhdAA,iEAAUA,ORiDZG,4B6CnD6DH;gB3Cmd3DA;MMjdAA,iEAAUA,ORiDZI,4B6ChD4DJ;I3Cid5DA,C;;;YA1ZyBK;MACnBA,wCAASA,kCWxEgBA;eXwEVA;MAAfA,gBiHawBA,2BAAmBA,sBjHZ5BA,aAAiBA,gBAAQA;K;;;;YAyHzBC;MACUA;;eAA3BA;;MACAA;YACAA;;IACDA,C;;;;YAAaA;IAEbA,C;;;;YAgC6CC;MACrBA;MAANA;eAAjBA;;MACAA;MAEAA,mBAAWA,qCAwBVA;IACFA,C;;;EAxBOC;YADKA;MACLA;IAuBLA,C;oCAxBUC;MACLA;;;oDADKA;QACLA;;;;;;;;cAAeD;mCAAMA,yCAANA;;;;;cAWJA;;cAGfA;cAEAA;mCAAMA,0GAANA;;;;cAjBSC;;;MACLA;IADKA,C;;;;YAiBmBC;MAC1BA;;;oDAD0BA;QAC1BA;;;;;;;;;gBAAaA;;;;;cACbA;mCAAMA,yGAAwDA,8DAA9DA;;;;c2CnINA;cACAA;;;crD0NgCA;;gBAH9BA,kBAAMA;;cAGRA;;;cUpFKA;;;MALCA;IAKDA,C;;;EADiCC;YAANA;MAAMA,wCAAkBA;K;;;;YAG7CJ;mBACTA;uDAAsBA;MACtBA;MACAA;MACAA;IACDA,C;;;;YA6E4CK;MAC3CA;MAAIA,6BAAMA,aAAiBA;QAAUA,MAOtCA;eANCA;;0BAAeA;M4D3SCA,iC5D8SOA,iBxC46BP3E;ewC36BF2E;QgDtThBA,sBAAKA,4BhDsT4BA;;IAEhCA,C;;;;YAAUA;MACTA,yDAAsBA;IACvBA,C;;;;YAWYA;IAAKA,C;;;;YAegCC;IAAKA,C;;;;YAIUA;;;MAC/DA,0BAA0BA;MAC1BA,0BAA0BA;eAEtBA,QAAQA,SAASA;MAArBA;QACEA,2BkHjZNA,eAA6CA;MlHqZzCA,0BAA0BA;MACfA,EAAXA,WAAWA;IACZA,C;;;;YAAEA;IAAKA,C;;;;;;2B2C/TV/pD;MAC+BA;MAvD/BA,KAuDEA;MAEiBA,KAAjBA,iB1C5E4BA,yBAAWA,yB0C4EVA,0CAEjBA;IACdA,C;mCASKgqD;;kBACCA;axCIgBA;QwCHlBA,sBAAMA;mBhCrGSA;MgCwGjBA,iBhCxG6BA,sCgCwGJA;MACzBA,oBAAOA;WAEPA;ahC9FcA;MRzBhB5D,2BAgH4BC,oBAhH5BD,gCwCyHyB4D,SAAOA;MAgB9BA;MAEAA,kChCuD6B/H,UAAUA;IgCtDzC+H,C;aAUOC;MAAWA,wDAAmBA,8CAM/BA;K;;;YAzDyBC;;IAE5BA,C;;;;YAAWA;IAAMA,C;;;;YAoBYC;MAC5BA;MAAIA;eAAMA,aAAiBA;QAAUA,MAatCA;gBAZCA;;gBAEUA;kBAAiBA;QACzBA,0BAAaA;uBACmBA;kBACjBA;QAAfA;QACAA;sBACSA;kBACGA;QAAZA;QAEAA;;IAEHA,C;;;;YAekCC;MAC/BA;;;oDAD+BA;QAC/BA;;;;;;;;;cACEA;mCyEpFUA,kHzEoFVA;;;;;;;;;;;;;cAEAA;;;;;;;cAEHA;;;;;;MALCA;IAKDA,C;;;;sB0EbAC;MACHA;MAAIA;cAASA;gBACNA;a9CtCaA;M8CsClBA;Q9CrGFA;iBAGEA;UxIkViBC,yBAAWA;iBwIlVTD;UAATA;4BAAOA;UAAPA;4BAAOA;YAAjBA;YACAA;;gB8CqGIA,QrHiC0CA;MgEzKxBA;QqDwIUA,sBAAcA;mB1G5H9CA,kCAAYA;M0GiIZA,8BlH1JJjE,2BAgH4BC,oBAhH5BD,gCkH2JSiE,SAAOA;gBASdA;mB1GrIgCA;aAAYA;M0GqI5CA,YlHpKFjE,2BAgH4BE,oBAhH5BF,gCkHqKOiE,SAAOA;a1GnI+BA;M0GqI3CA,YlHvKFjE,2BAgH4BG,oBAhH5BH,gCkHuKwCiE,SAAOA;IAM/CA,C;sBAGKE;MACHA;eAAUA,aAAiBA;QAAUA,MAOvCA;eAHMA,QrHC4CA;;MgE5KlD5B;MJyDoBxoB;QIzDpBwoB;QqD4KI4B,qBAAcA,oBAA4BA;;IAE9CA,C;0BAGKC;MACHA;kB1GvKiBA,kCAAYA,6B0GuKVA,aAAiBA;QAAUA,MAgBhDA;MAdEA,6BAAcA,2CAAmCA,cAAMA,qBAAQA;gBAG7DA;mBAAeA,SAASA;MAAxBA,aAA0CA,SAASA;MACnDA,MAUJA;K;yBAMKC;MAIHA;MAAIA;MAAJA;QAAqBA,MAevBA;gBAbMA;MAAQA;MpDtLMA;QoDuLhBA;WACKA;QrDhNT9B,iFhE4KkD8B,oBxEyIlDhqD,uBAEyBA,mBAFzBA,oD6LlGwCgqD,kBAAMA,iBAAQA,WAFlDA;UACEA,6BAAcA,uB7LwGHA;Q6LrGbA;;erHrDwBA,aM1JLjC;Q2DkBHiC;UoD+LhBA;;UAEAA;;IAEJA,C;kCAOKC;;kBAECA;erHpEsBA;eM1JLlC;M+G8NFkC,8BAAUA;erHhEFA,cM9JNjC;Q+G+NDiC,8BAAUA;iBrH7DJA,aMlKLhC;U+GgOFgC,8BAAUA;gCACdA;qDAEkBA;;cAFrBA;;YADcA;;UADCA;;QAESA;MAHpCA;QAMEA,MAyCJA;a/G7QuBlC;M+GuOgBkC,KAArCA;arHzE2BA;aM9JNjC;M+GwOkBiC,KAAvCA;arHtE0BA;aMlKLhC;M+GyOgBgC,KAArCA;WACAA;WACAA;MAEAA;QAAoBA;;QtL+S8BA;MwIjd3CA,iBAAuBA,K8CoKfA,yCxKvEIC;MwKyGUD,kCAAVA,2BxKzGAC,4DwK0GcD,6BAANA,2BAAVA,qBxKnGEC,qEwKqEND;a/GpPQlC;gB+GuPRkC;MtL4SEA,csL7SaA;a/GtPPjC;M2DoBAiC;uBoDsONA;e/G1PMjC;QvEmiBNiC,csLvSgBA;;;a/G5PVhC;M2DoBAgC;uBoD6ONA;e/GjQMhC;QvEmiBNgC,csLhSeA;;;MtLgSfA;MsLvRfA;IACFA,C;4BAjDKE;;K;2BAAAC;;K;qBAAAC;;K;oBA6DEC;uBACeA;MAcpBA,S1GtS2BA,M0GwRFA,KAe3BA;K;;EA9J2BC;YAAXA;MAAWA,uCAAeA,WAAUA,0BAAMA;K;;;;YAU5CA;MAA8BA;MAAnBA,2CAASA,gBAAgBA,aAAaA,YAAWA;K;;;;YAE3BA;MACNA;MAC1BA;eADXA;yBAAcA,sBAAaA;oBACRA;iBACPA,WAAoBA;wBAAiBA,oBAAaA;MAC9DA;IACDA,C;;;;EDbgBC;4BAAZA;MAAYA,iCAAmBA,iDAGhCA;K;8BAhFcC;;K;;;YA6EkBC;MACIA;;;oDADJA;QACIA;;;;;;cAApCA;mCAAMA,mEAANA;;;;cAEDA;;;MAFqCA;IAErCA,C;;;;;;eEjJAC;UACHA;MACAA;IACFA,C;cAGKC;mBACGA;MAAQA,kCAARA;QACJA;UvL8iBFA;;IuL3iBFA,C;;;;EjJ6WMC;Y4C9TkBA;MAAgBA,sBAASA,6BAASA,YAAMA,0DAAOA;K;;;;YuB/BrDC;MACZA;;;oDADYA;QACZA;;;;;;cAAkEA;cAClEA;cAAyBA,uBAAsBA,qBAAQA;cACjCA;cauZLA,sBAAQA;gK+D/VDC;4D/E7EhBC;;cvF4LZl/D;csKlKWg/D;c5EHEA;;cpFwyBbG,YA9X4BC,kCA8XhBD,OmKx0BFH;cnK+0BMK;;gBrBpeLC;;;csLjT8BC;;;;cA8BzCA,uClHO0BC,oBAhH5B3F,gCkHyG2C0F,SAAOA;cAIrBA;;cAA3BA,YvK2M0BA,qDuK3MoBA,SAAOA;;;c7EtDvCP;mCAAMA,yDACJA,yCAACA,4EADHA;;;;gBAEDA;;;;;cACbA;cACiBA;;;cAClBA;;;MAfKA;IAeLA,C;;;EAL4CS;YAAdA;MAAMA,uBAAqBA,IAAPA,qCAAWA;K;;;;YZ1C7CC;MACfA,sBAAoBA;IAWrBA,C;;;;YAXqBC;MAClBA;;;oDADkBA;QAClBA;;;;;;cAGYA;;gBAERA;cAEFA,gBAAaA;;cAEhBA;;;MATCA;IASDA,C;;;Ea5B6CD;YAAXA;MAAMA,kCAASA;K;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wGpFwjBjDE;;K;;;wFNmiBHC;;K;kGAWAC;;K;;kHAwBcC;;K;4HAKQC;;K;8HAKMC;;K;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6E4B1nC5BC;;K;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8EzC2CWC;aACTA,6CADSA;K;8EAyIPC;aAA6BA,oCAA7BA;K;kGN0tC0BC;aAC1BA,kCAAeA;;;;SADWA;K;8FAKAC;aAC1BA,kCAAeA;;;;SADWA;K;0FAKAC;aAC1BA,kCAAeA,4CADWA;K;wGAKAC;aAC1BA,kCA+N2BA;;;;;;;UAhODA;K;oGAKAC;aAC1BA,kCAAeA,8CADWA;K;kHAKAC;aAC1BA,kCAoO2BA;;;;;;;UArODA;K;kGAKAC;aAC1BA,kCAAeA,gDADWA;K;gHAKAC;aAC1BA,kCAsP2BA;;;;;;UAvPDA;K;4GAKAC;aAC1BA,kCAAeA,kDADWA;K;0HAKAC;aAC1BA,kCA0P2BA;;;;;;UA3PDA;K;gGgB96CRC;aAClBA,0CADkBA;K;8DIsHKC;aACnBA,2BAAmCA,yBADhBA;K;8DD2mCdC;MAAeA;MAAfA;K;kEiBhyBAC;aAAWA,4BAAXA;K;wFC2CUC;a3BgXnBA,0BAASA,oB2BhX+CA,iiBAArCA;K;oElCuLLC;;K;oEASEC;aAAuBA,gDAAvBA;K;sEAkCjBC;;K;sDyCsoGiBC;aAAiBA,iBAAjBA;K;0FiHz2HhBC;aAAmCA,wDAAnCA;K;oEAMAC;aAAwBA,gDAAxBA;K;qEAMAC;aAAwBA,kDAAxBA;K;4DQwEAC;aAAoBA,+CAApBA;K;oDhDzEAC;aAAgBA,4CACmBA,CAAXA,sBAAgBA,WAAIA,gDAAgBA,uBAD5DA;K;wCIqCQC;aAAUA,kBAAqBA,uBAA/BA;K;gCAMAC;aAAMA,kBAAqBA,mBAA3BA;K;wCAOAC;aDzBZC,cACoBA,8BCwBRD;K;gDhE3DOE;a8GHnBA,iBAQyBC,6BACKA,iCACVA,6B9GPDD;K;oDAOAE;agHLnBA,mBAQyBC,mCACKA,qCACVA,6EACQA,8ChHNTD;K;4CAQAE;a+GlBnBA,eAQyBC,6BACKA,+DACVA,2DACQA,6B/GOTD;K;sDAMAE;aAAWA,2BAAXA;K;wC8BNEC;aAAUA,gBAMhCA,QANsBA;K;4CAQbC;aAAuBA,oBAAXA,iBAAZA;K;4CACAC;aAAYA,qBAACA,aAAbA;K;0C1BnBJC;a2EaEA,c3EbFA;K;0C8DNAC;aAAeA,wEAAfA;K;0CAOAC;aACEA,qFADFA;K;sDAIAC;aAAqBA,qDAArBA;K;wDAMAC;aACEA,6EADFA;K;gEAQAC;aAA0BA,0GAA1BA;K;sDAmBAC;aAAqBA,wEAArBA;K;8CAIAC;aAAiBA,kEAAjBA;K;gDAEAC;aAAkBA,8BAAlBA;K;0DAgNSC;aAAiBA,qDAAjBA;K;kEAGAC;aAAqBA,sDAArBA;K;4FpB1OAC;aiCRPA,cjCQOA;K;kDzC1BTC;aAAmBA,gDAAnBA;K;0CAQAC;aAAeA,qCAAfA;K;kDAMAC;aAAmBA,kCAAnBA;K;gEAeAC;aAA0BA,uEAA1BA;K;sDAYAC;aACEA,kEoDvBJxvD,AEmKAG,ArGkKA4G,AUoFA+Y,A8FzUE4oB,KzD1DA8mB;K;sDG9BSC;;aAAiBA,kFAAjBA;K;0EkBZTC;MAA2BA;;;;;;;;MAO/BA;QAP+BA,aAOHA,SAA5BA,IAA6CA;MAC7CA;QAR+BA,aAQAA,SAA/BA,IAAuCA;MARnCA;K;iD3BAAC;agFmBEA,chFnBFA;K;iDFHAA;akFsBEA,clFtBFA;K;sDyCkDgBC;aAAkBA,iBAKtCA,QALoBA;K;qEAWhBrC;aAAwBA,kDAAxBA;K;kFAIAsC;aACFA,sBAAWA,8BAAsBA,sBAD/BA;K;0EWlDSC;MAA2BA;MA4KpBA;;MACHA,uBAAyCA;MACxCA;;MAGLA;MACMA;MAEoBA;MApL1BA,OA6JbA,0BA7JaA;K;;;;;;;;;;;;;;;;;;;;;;;UpG2DDC,AAAAxvE,AAAAC,AAAAI,4BNokGgBvB","x_org_dartlang_dart2js":{"minified_names":{"global":"main_closure,2397,internalBootstrapBrowserTest,535,Closure_fromTearOff,2398,StaticClosure,2399,BoundClosure,2400,Closure_functionCounter,2401,Closure_forwardCallTo,2402,Closure__computeSignatureFunctionNewRti,2403,_checkStringNullable,181,wrapException,42,CyclicInitializationError,2404,toStringWrapper,43,NullThrownError,2405,S,12,argumentErrorValue,40,ArgumentError,395,Error_safeToString,2406,_isBool,168,Error__objectToString,2407,Closure,2408,Primitives_objectTypeName,2409,Primitives__objectTypeNameNewRti,2410,Object,2411,instanceType,145,_rtiToString,184,Primitives__saneNativeClassName,2412,assertHelper,79,_unminifyOrTag,186,Rti__getInterfaceTypeArguments,2413,_rtiArrayToString,182,_functionRtiToString,90,Rti__getGenericFunctionBounds,2414,ioore,37,diagnoseIndexError,38,_isInt,173,_checkIntNullable,175,iae,36,IndexError$,2415,RangeError$value,2416,_checkNumNullable,178,RangeError,398,IndexError,404,setRuntimeTypeInfo,83,isTopType,243,Rti__getFunctionParameters,2417,ArgumentError$value,2416,_arrayInstanceType,147,UnsupportedError$,2415,throwExpression,44,UnsupportedError,405,unmangleGlobalNameIfPreservedAnyways,246,assertTest,77,AssertionError$,2415,AssertionError,394,TypeErrorImplementation$,2415,TypeErrorImplementation,75,_typeDescription,76,extractFunctionTypeObjectFromInternal,74,runtimeTypeToString,86,_runtimeTypeToString,87,unminifyOrTag,10,_joinArguments,92,_functionRtiToString0,90,extractKeys,245,JSArray_JSArray$markFixed,2418,JSArray,2419,JSArray_markFixedList,2420,StringBuffer,2421,_checkBoolNullable,170,getIsolateAffinityTag,82,IterableBase_iterableToFullString,2422,_isToStringVisiting,357,_toStringVisiting,2423,StringBuffer__writeAll,2424,IterableElementError_noElement,2425,StateError,407,ArrayIterator,2426,throwConcurrentModificationError,45,ConcurrentModificationError$,2415,ConcurrentModificationError,408,ListIterator,2427,ListMixin,2428,_checkValidIndex,124,Iterable,2429,initNativeDispatchFlag,2430,initNativeDispatch,104,UnimplementedError$,2415,lookupAndCacheInterceptor,99,getTagFunction,2431,dispatchRecordsForInstanceTags,2432,interceptorsForUncacheableTags,2433,alternateTagFunction,2434,makeLeafDispatchRecord,102,patchInteriorProto,101,makeDispatchRecord,248,JavaScriptIndexingBehavior,2435,UnimplementedError,406,initNativeDispatchContinue,105,initHooks,107,prototypeForTagFunction,2436,makeDefaultDispatchRecord,103,applyHooksTransformer,108,initHooks_closure,2437,initHooks_closure0,2437,initHooks_closure1,2437,_TypeError__TypeError$forType,2438,_TypeError,2439,_Error_compose,2440,_CastError__CastError$forType,2438,_CastError,2441,instanceOrFunctionType,144,_TypeError$fromMessage,2442,Rti__isUnionOfFunctionType,2443,closureFunctionType,143,getTypeFromTypesTable,151,_Universe_eval,2444,_Universe__parseRecipe,2445,_Parser_create,2446,_Parser_parse,2447,_Parser_handleDigit,2448,_Parser_handleIdentifier,2449,_Parser_toType,2450,_Universe__lookupGenericFunctionParameterRti,2451,_Universe__lookupTerminalRti,2452,_Parser_toTypes,2453,_Universe__lookupInterfaceRti,2454,_Universe__lookupGenericFunctionRti,2455,_Universe__lookupBindingRti,2456,_Parser_handleExtendedOperations,2457,assertThrow,78,_Universe__lookupUnaryRti,2458,_FunctionParameters,2459,_Universe__lookupFunctionRti,2460,_Parser_toTypesNamed,2461,_Universe__canonicalRecipeJoin,2462,_Universe__canonicalRecipeJoinNamed,2463,Rti,2464,_Universe__finishRti,2465,_generalTypeCheckImplementation,159,_generalAsCheckImplementation,158,_installSpecializedIsTest,154,_generalIsTestImplementation,156,_isTop,166,_asTop,167,int,2466,double,2467,_isNum,176,num,2468,String,2469,_isString,179,bool,2470,isLegacyTopType,242,_isTestViaProperty,157,_AssertionError,2471,Rti__getBindingArguments,2472,_Parser_indexToType,2473,_Universe_findRule,2474,Rti__getCanonicalRecipe,2475,_Universe_evalInEnvironment,2476,_CastError$fromMessage,2442,_isSubtype,233,Rti__getFutureFromFutureOr,2477,typesEqual,240,_isFunctionSubtype,236,_isInterfaceSubtype,237,typeEqual,238,namedTypesEqual,241,Future,272,_Universe_bind,2478,_instanceTypeFromConstructor,149,_instanceTypeFromConstructorMiss,150,_Universe_findErasedType,2479,BoundClosure_evalRecipeIntercepted,2480,BoundClosure_evalRecipe,2481,Closure_forwardInterceptedCallTo,2482,Closure_cspForwardCall,2483,BoundClosure_selfFieldNameCache,2484,BoundClosure_computeFieldNamed,2485,BoundClosure_selfOf,2486,BoundClosure_receiverFieldNameCache,2487,Closure_cspForwardInterceptedCall,2488,BoundClosure_receiverOf,2489,RuntimeError$,2415,RuntimeError,81,getRuntimeTypeInfo,84,substitute,95,invokeOn,97,internalBootstrapBrowserTest_closure,2490,RemoteListener_start,2491,postMessageChannel,631,StreamChannel,2257,_ControllerStream,2492,_instanceType,148,_Future,265,Zone__current,2493,Stream_length_closure,1045,Stream_length_closure0,1045,Null,2494,Stream,2495,AsyncError,2496,_Future__propagateToListeners,2497,_Future__propagateToListeners_handleWhenCompleteCallback,2498,_Future__propagateToListeners_handleValueCallback,2499,_Future__propagateToListeners_handleError,2500,_Future__chainCoreFuture,2501,_Future__prependListeners_closure,2502,boolConversionCheck,73,unwrapException,51,getTraceFromException,52,ExceptionAndStackTrace,2503,_StackTrace,2504,unwrapException_saveStackTrace,2505,JsNoSuchMethodError$,2415,NullError$,2415,UnknownJsTypeError,2506,StackOverflowError,2507,TypeErrorDecoder_extractPattern,2508,quoteStringForRegExp,114,TypeErrorDecoder,2509,TypeErrorDecoder_provokePropertyErrorOn,2510,TypeErrorDecoder_provokeCallErrorOn,2511,NullError,49,JsNoSuchMethodError,50,_Future__propagateToListeners_handleWhenCompleteCallback_closure,2512,_registerErrorHandler,297,_FutureListener,2513,_rootScheduleMicrotask,331,_scheduleAsyncCallback,300,_RootZone_bindCallback_closure,1244,_RootZone_bindCallbackGuarded_closure,2514,_rootRun,324,_rootHandleUncaughtError,323,_rootHandleUncaughtError_closure,2515,_schedulePriorityAsyncCallback,301,_nextCallback,2516,_lastPriorityCallback,2517,_lastCallback,2518,_AsyncCallbackEntry,2519,Zone__enter,2520,_isInCallbackLoop,2521,_microtaskLoop,298,_AsyncRun__initializeScheduleImmediate,2522,_AsyncRun__initializeScheduleImmediate_internalCallback,2523,convertDartClosureToJS,59,_AsyncRun__initializeScheduleImmediate_closure,2524,Timer__createTimer,2525,Duration_toString_twoDigits,2526,Duration,392,Duration_toString_sixDigits,2527,_TimerImpl$,2415,_TimerImpl,261,_TimerImpl_internalCallback,2528,_AsyncRun__scheduleImmediateWithSetImmediate_internalCallback,2529,_AsyncRun__scheduleImmediateJsOverride_internalCallback,2530,invokeClosure,57,_Exception,2531,_rootRunBinary,326,_rootRunUnary,325,_Future__addListener_closure,2532,_BufferingStreamSubscription$,2415,_PendingEvents,2533,_BufferingStreamSubscription,310,ArgumentError$,2415,_Future__chainForeignFuture,2534,_Future__chainForeignFuture_closure,2535,_Future__chainForeignFuture_closure0,2535,_Future__chainForeignFuture_closure1,2535,scheduleMicrotask,302,get$scheduleMicrotask,302,_StreamControllerLifecycle,2536,StreamController,307,StreamConsumer,2537,Stream_pipe_closure,2538,_GuaranteeSink,2539,StreamChannelController$,2415,_EventStream,2540,postMessageChannel_closure,2541,postMessageChannel_closure0,2541,LinkedHashMap_LinkedHashMap$_literal,2542,_convertDataTree,466,_EventStreamSubscription$,2415,_EventStreamSubscription_closure,2543,_wrapZone,473,_EventStreamSubscription,472,_RootZone_bindUnaryCallbackGuarded_closure,1247,_convertDataTree__convert,2544,_IdentityHashMap,2545,MapBase_mapToString,2546,MapBase_mapToString_closure,2547,MapMixin,2548,_HashMap__getTableEntry,2549,Primitives_objectHashCode,53,objectHashCode,53,_HashMapKeyIterable,2550,IterableBase_iterableToShortString,2551,_iterablePartsToStrings,358,RangeError_checkNotNegative,2552,RangeError$range,2553,_HashMapKeyIterator,2554,MappedIterable_MappedIterable,2415,MappedIterator,2555,Iterator,2556,EfficientLengthMappedIterable,2557,MappedIterable,3,MappedListIterable,2558,ListIterable,2559,_HashMap__newHashTable,2560,_HashMap__setTableEntry,2561,LinkedHashMap,346,JsLinkedHashMap,2562,fillLiteralMap,54,LinkedHashMapKeyIterable,2563,LinkedHashMapKeyIterator,870,LinkedHashMapCell,2564,postMessageChannel__closure,2565,postMessageChannel__closure0,2565,postMessageChannel__closure1,2565,_StructuredCloneDart2Js,2566,DateTime,2567,_StructuredClone_walk_closure,2568,_StructuredClone_walk_closure0,2568,_AcceptStructuredCloneDart2Js,2569,StateError$,2415,StreamSink,2570,StreamSubscription,2571,promiseToFuture,467,LinkedHashMap_LinkedHashMap$_empty,2572,_AcceptStructuredClone_walk_closure,2573,Primitives_getYear,2574,DateTime__fourDigits,2575,Primitives_getMonth,2576,DateTime__twoDigits,2577,Primitives_getDay,2578,Primitives_getHours,2579,Primitives_getMinutes,2580,Primitives_getSeconds,2581,Primitives_getMilliseconds,2582,DateTime__threeDigits,2583,Primitives_lazyAsJsDate,2584,_checkDoubleNullable,172,_AsyncCompleter,2585,promiseToFuture_closure,2586,promiseToFuture_closure0,2586,_Future__asyncCompleteError_closure,2587,_Future__asyncComplete_closure,2588,_Future__chainFuture_closure,2589,Stream_firstWhere_closure,1051,Stream_firstWhere_closure0,1051,_completeWithErrorCallback,287,Stream_firstWhere__closure,2590,Stream_firstWhere__closure0,2590,_cancelAndErrorClosure,317,_runUserCode,315,_cancelAndErrorClosure_closure,2591,_cancelAndError,316,_cancelAndError_closure,2592,_Future$zoneValue,2593,_cancelAndValue,318,_cancelAndValue_closure,2594,StreamChannelController,588,StreamController_StreamController,2415,_StreamSinkWrapper,2595,GuaranteeChannel$,2415,GuaranteeChannel,634,GuaranteeChannel_closure,2596,_StreamImplEvents,2597,_StreamControllerAddStreamState,1059,_SyncCompleter,2598,_GuaranteeSink_addStream_closure,2599,_GuaranteeSink__addError_closure,2600,GuaranteeChannel__closure,2601,_SyncStreamController,2602,_AsyncStreamController,2603,_EventDispatch,2604,_PendingEvents_schedule_closure,2605,_BufferingStreamSubscription__sendDone_sendDone,2606,_ControllerSubscription,1071,_StreamController__subscribe_closure,2607,_StreamController__recordCancel_complete,2608,_AddStreamState_cancel_closure,2609,_runGuarded,308,_DelayedError,2610,_BufferingStreamSubscription__sendError_sendError,2611,_DelayedData,2612,_MultiChannel$,2415,RemoteListener_start_closure,2613,_ZoneSpecification$,2415,Stream_Stream$fromIterable,2614,RemoteListener_start_closure0,2613,RemoteListener_start_closure1,2613,SuiteChannelManager,478,LinkedHashSet_LinkedHashSet$_empty,2572,runZoned,339,Map,2615,_runZoned,340,runZoned_closure,2616,_parentDelegate,322,_RootZone__rootDelegate,2617,_ZoneDelegate,2618,_rootFork,337,printToZone,2619,HashMap_HashMap$from,2620,_CustomZone,338,_ZoneFunction,2621,printString,247,_CustomZone_bindUnaryCallbackGuarded_closure,1205,_CustomZone_bindCallbackGuarded_closure,2622,_CustomZone_bindCallback_closure,1200,_TimerImpl$periodic,2623,_TimerImpl$periodic_closure,2624,_CustomZone_bindUnaryCallback_closure,1202,HashMap_HashMap,2415,_HashMap,2625,HashMap_HashMap$from_closure,2626,_LinkedHashSet,2627,_LinkedHashSetIterator,353,LinkedHashSet_LinkedHashSet$_literal,2542,RemoteListener_start__closure,2628,StackTraceFormatter,2236,RemoteListener_start___closure,2629,RemoteListener_start___closure0,2629,RemoteListener__sendError,2630,RemoteException_serialize,2631,TestFailure,2632,Chain_Chain$forTrace,2633,Chain_toString_closure,2634,Chain_toString_closure0,2634,instantiate1,8,checkTypeBound,160,checkNum,41,Chain_toString__closure,2635,Instantiation1,9,createRuntimeType,152,_Type,2636,instantiatedGenericFunctionType,134,_instantiate,135,_instantiateArray,139,_instantiateFunctionParameters,141,_instantiateNamed,140,Chain_toString__closure0,2635,getRuntimeType,93,Chain,2637,List_List$unmodifiable,2638,LazyChain,2639,Chain_Chain$forTrace_closure,2640,Chain_Chain$parse,2447,Chain_Chain$parse_closure,2641,Trace_Trace$parse,2447,Chain_Chain$parse_closure0,2641,Trace_toString_closure,2642,Trace_toString_closure0,2642,UnparsedFrame,630,Trace$parseFriendly,2643,WhereIterable,2644,Trace$parseFriendly_closure,2645,Trace$parseFriendly_closure0,2645,Trace,488,_StringStackTrace,2646,WhereIterator,2647,Frame_Frame$parseFriendly,2643,_parseUri,616,ParsedPath_ParsedPath$parse,2447,Context_split_closure,2648,List_List$from,2620,List,2649,ParsedPath,2650,List_List$filled,2651,ParsedPath_normalize_closure,2652,List_List$generate,2653,stringReplaceAllUnchecked,115,stringReplaceAllUncheckedString,116,JSSyntaxRegExp,2654,escapeReplacement,112,JSSyntaxRegExp_makeNative,2655,FormatException$,2415,FormatException,409,WindowsStyle,2366,RegExp_RegExp,2415,isDriveLetter,602,_Uri__uriDecode,2656,StringMatch,2657,CodeUnits,2658,_Uri__hexCharPairToByte,2659,Utf8Decoder,2660,UnmodifiableListMixin,2661,Utf8Decoder__convertIntercepted,2662,RangeError_checkValidRange,2663,_scanOneByteCharacters,372,String_String$fromCharCodes,2664,_Utf8Decoder,2665,Primitives_stringFromCharCode,2666,Primitives_stringFromCharCodes,2667,Primitives_stringFromNativeUint8List,2668,String__stringFromIterable,2669,Primitives_stringFromCodePoints,2670,Primitives__fromCharCodeApply,2671,Utf8Decoder__convertInterceptedUint8List,2672,Utf8Decoder__useTextDecoderChecked,2673,Utf8Decoder__unsafe,2674,Utf8Decoder__useTextDecoderUnchecked,2675,Utf8Decoder__makeDecoder,2676,RangeError_checkValueInInterval,2677,stringReplaceFirstUnchecked,120,stringReplaceRangeUnchecked,121,stringReplaceFirstRE,113,_StringAllMatchesIterable,2678,_StringAllMatchesIterator,2679,_MatchImplementation$,2415,_MatchImplementation,110,isAlphabetic,601,_AllMatchesIterable,2680,_AllMatchesIterator,2681,IterableElementError_tooFew,2682,JSArray_JSArray$fixed,2683,UrlStyle,2368,Context,615,current,621,get$current,621,PathException$,2415,_validateArgList,617,Context_join_closure,2684,Context_joinAll_closure,2685,SubListIterable$,2415,_validateArgList_closure,2686,SubListIterable,2,PathException,559,Uri_base,2687,_current,2493,set$_current,2493,_currentUriBase,2688,Primitives_currentUri,2689,Uri_parse,2447,UriData__parse,2690,_scan,465,_SimpleUri,2691,_Uri__Uri$notSimple,2692,int_parse,2447,Primitives_parseInt,2693,_Uri__defaultPort,2694,_Uri__toWindowsFilePath,2695,_Uri__checkNonWindowsPathReservedCharacters,2696,_Uri__checkNonWindowsPathReservedCharacters_closure,2697,_Uri__checkWindowsDriveLetter,2698,_Uri__checkWindowsPathReservedCharacters,2699,String_String$fromCharCode,2700,_Uri__removeDotSegments,2701,_Uri__makePort,2702,_Uri__normalizeRelativePath,2703,_Uri,420,_Uri__mayContainDotSegments,2704,_Uri__escapeScheme,2705,_Uri__isAlphabeticCharacter,2706,_Uri__makeScheme,2707,_Uri__fail,2708,_Uri__makeUserInfo,2709,_Uri__makeHost,2710,_Uri__Uri$notSimple_closure,2711,_Uri__makePath,2712,_Uri__makeQuery,2713,_Uri__makeFragment,2714,_Uri__normalizeOrSubstring,2715,_Uri__normalize,2716,_Uri__normalizeEscape,2717,_Uri__escapeChar,2718,hexDigitValue,1,_Uri__makePath_closure,2719,_Uri__normalizePath,2720,_Uri__uriEncode,2721,Codec,2722,_Utf8Encoder,2723,_checkValidRange,125,diagnoseRangeError,39,_Uri__checkZoneID,2724,_Uri__normalizeZoneID,2725,Uri_parseIPv6Address,2726,_Uri__normalizeRegName,2727,Uri_parseIPv6Address_error,2728,Uri_parseIPv6Address_parseHex,2729,Uri__parseIPv4Address,2730,Uri__parseIPv4Address_error,2731,_Uri__canonicalizeScheme,2732,_createTables,464,_createTables_closure,2733,_createTables_build,2734,_createTables_setChars,2735,_createTables_setRange,2736,_DataUri,2737,UriData,2738,Base64Codec__checkPadding,2739,_ensureNativeList,122,NativeInt8List__create1,2740,Style__getPlatformStyle,2741,_Uri__Uri,2415,PosixStyle,2364,Frame_Frame$parseFriendly_closure,2742,Frame__catchFormatException,2743,UriData__writeUri,2744,UriData__uriEncodeBytes,2745,Frame,2746,WindowsStyle_absolutePathToUri_closure,2747,Context_Context,2415,UriData__validateMimeType,2748,JSString__skipLeadingWhitespace,2749,JSString__skipTrailingWhitespace,2750,JSString__isWhitespace,2751,Trace$parseV8,2752,Trace$parseJSCore,2753,Trace$parseFirefox,2754,Trace__parseVM,2755,Trace__parseVM_closure,2756,Frame_Frame$parseVM,2757,Frame_Frame$parseVM_closure,2758,ExpandIterable,2759,Chain_toTrace_closure,2760,ExpandIterator,782,Trace$parseFirefox_closure,2761,Trace$parseFirefox_closure0,2761,Frame_Frame$parseFirefox,2754,Frame_Frame$parseFirefox_closure,2762,Frame__uriOrPathToUri,2763,_Uri__Uri$file,2764,_Uri__makeWindowsFileUrl,2765,_Uri__makeFileUri,2766,Trace$parseJSCore_closure,2767,Trace$parseJSCore_closure0,2767,Frame_Frame$parseV8,2752,Frame_Frame$parseV8_closure,2768,Frame_Frame$parseV8_closure_parseLocation,2769,Trace$parseV8_closure,2770,Trace$parseV8_closure0,2770,SkipWhileIterable,2771,SkipWhileIterator,2772,stringContainsUnchecked,111,StackTrace_current,621,StackZoneSpecification_chainFor_closure,2773,_Node,2774,LazyTrace,2775,StackZoneSpecification_chainFor_closure0,2773,Trace_Trace$from,2620,Trace_Trace$from_closure,2776,LazyChain_toTrace_closure,2777,Primitives_getProperty,2778,parseJsonExtended,553,mapStackTrace,487,StackTraceFormatter_formatStackTrace_closure,2779,LazyChain_foldFrames_closure,2780,Chain_foldFrames_closure,2781,Chain_foldFrames_closure0,2781,IterableElementError_tooMany,2782,LazyTrace_foldFrames_closure,2783,Trace_foldFrames_closure,2784,ReversedListIterable,2785,Trace_foldFrames_closure0,2784,mapStackTrace_closure,2786,mapStackTrace_closure0,2786,mapStackTrace_closure1,2786,_prettifyMember,489,_prettifyMember_closure,2787,_prettifyMember_closure0,2787,stringReplaceAllFuncUnchecked,118,_stringIdentity,117,MappingBundle$fromJson,2788,parseJson,554,ArgumentError$notNull,2789,SourceLocation$,2415,SourceMapSpan,1872,findLineStart,605,countCodeUnits,603,Highlighter__lastLineLength,2790,SourceSpanWithContext$,2415,Highlighter__normalizeNewlines,2791,Highlighter__normalizeTrailingNewline,2792,Highlighter__normalizeEndOfLine,2793,Highlighter,1957,_glyphs,2794,Highlighter__writeSidebar_closure,2795,Highlighter__writeLastLine_closure,2796,Highlighter__writeLastLine_closure0,2796,Highlighter__writeLastLine_closure1,2796,Highlighter__writeIntermediateLines_closure,2797,Highlighter__writeFirstLine_closure,2798,Highlighter__writeFirstLine_closure0,2798,Highlighter__writeFirstLine_closure1,2798,Highlighter__writeFirstLine_closure2,2798,SourceSpanWithContext,560,RangeError$,2415,SourceLocation,523,SingleMapping__findColumn_closure,2799,binarySearch,504,SingleMapping__findLine_closure,2800,JsLinkedHashMap_values_closure,863,MultiSectionMapping$fromJson,2788,SingleMapping$fromJson,2788,SingleMapping,2801,SourceFile,2802,_MappingTokenizer,2803,TargetLineEntry,2804,decodeVlq,534,TargetEntry,2805,SingleMapping$fromJson_closure,2806,pow,471,closure,2807,MultiSectionMapping,2808,MappingBundle,2809,_parseJson,364,_convertJsonToDartLazy,365,_JsonMap,2810,_JsonMapKeyIterable,2811,RemoteListener__sendLoadException,2812,StreamQueue,2813,QueueList$,2415,ListQueue$,2415,_CompleterStream,2814,_RestRequest,2815,StreamCompleter,2816,RemoteListener_start____closure,2817,_asBoolNullable,169,Metadata$deserialize,2818,LinkedHashSet_LinkedHashSet$from,2620,Declarer$,2415,RemoteListener__deserializeSet,2819,Runtime_Runtime$deserialize,2818,_asStringNullable,180,OperatingSystem_find,2820,SuitePlatform$,2415,RemoteListener_start____closure0,2817,Suite,2821,Suite__filterGroup,2822,_asyncStartSync,266,_asyncAwait,267,_asyncReturn,268,_asyncRethrow,269,_wrapJsFunctionForAsync,271,_makeAsyncAwaitCompleter,263,Symbol,2823,_AddStreamState_makeErrorHandler,2824,_AddStreamState_makeErrorHandler_closure,2825,_ListQueueIterator,363,ListQueue,362,QueueList,607,Result,2826,_wrapJsFunctionForAsync_closure,2827,_StreamIterator,2828,_awaitOnObject,270,_awaitOnObject_closure,2829,_awaitOnObject_closure0,2829,_AsyncAwaitCompleter,2830,RemoteListener_start_____closure,2831,Invoker_guard,2832,Invoker_guard_closure,2833,Invoker_guard__closure,2834,RemoteListener,2835,RemoteListener__serializeGroup_closure,2836,Group,490,RemoteListener__serializeTest_closure,2837,VirtualChannel,2838,_EmptyStream,2839,NullStreamSink,2840,_MultiChannel_virtualChannel_closure,2071,_MultiChannel_virtualChannel_closure0,2071,MultiChannel,2841,_DoneStreamSubscription,949,NullStreamSink_addStream_closure,2842,_LinkedHashSet__newHashTable,2560,_LinkedHashSetCell,2843,_asIntNullable,174,RemoteListener__runLiveTest_closure,2844,_BroadcastStream,2845,RemoteListener__runLiveTest_closure0,2844,RemoteListener__runLiveTest_closure1,2844,RemoteListener__runLiveTest_closure2,2844,RemoteListener__runLiveTest_closure3,2844,RemoteListener__runLiveTest__closure,2846,Invoker,2847,LiveTestController$,2415,_BroadcastSubscription,950,State,2848,Invoker__onRun_closure,2849,_AsyncCounter,2850,Chain_capture,2851,Expando__keyCount,2852,StackZoneSpecification,2853,Expando,570,Chain_capture_closure,1968,Primitives_setProperty,2854,StackZoneSpecification__currentTrace_closure,2855,StackZoneSpecification__registerBinaryCallback_closure,2025,StackZoneSpecification__registerBinaryCallback__closure,2027,StackZoneSpecification__registerUnaryCallback_closure,2021,StackZoneSpecification__registerUnaryCallback__closure,2023,StackZoneSpecification__registerCallback_closure,2019,Invoker__onRun__closure,2856,Invoker__onRun___closure,2857,Invoker__onRun___closure0,2857,Message,2858,Invoker__onRun____closure,2859,Future_Future,2415,Future_Future_closure,2860,Timer_Timer,2415,errorsDontStopTest,525,errorsDontStopTest_closure,2861,errorsDontStopTest_closure0,2861,Invoker_waitForOutstandingCallbacks_closure,2862,Invoker_waitForOutstandingCallbacks_closure0,2862,Future_Future$sync,2863,ClosedException$,2415,ClosedException,635,Invoker_heartbeat_closure,2864,Invoker_heartbeat_message,2865,Invoker_heartbeat__closure,2866,TimeoutException,2867,Timeout,2868,Invoker__handleError_closure,2869,Invoker__handleError_closure0,2869,print,412,get$print,412,Chain_Chain$current,621,Chain_Chain$current_closure,2870,LiveTestController,524,_SyncBroadcastStreamController,2871,_LiveTest,2872,_SyncBroadcastStreamController__sendError_closure,974,_AddStreamState,1058,_SyncBroadcastStreamController__sendDone_closure,976,_SyncBroadcastStreamController__sendData_closure,972,_BroadcastStreamController,2873,Metadata_serialize_closure,2874,Metadata_serialize_closure0,2874,MapEntry,2875,_LinkedHashSetIterator$,2415,PlatformSelector,2876,Group_forPlatform_closure,2877,Group$,2415,GroupEntry,2878,Group__map_closure,2879,Group__map_closure0,2879,Metadata_forPlatform_closure,2880,Metadata_Metadata,2415,Metadata_Metadata__unresolved,2881,LinkedHashMap_LinkedHashMap$from,2620,Metadata$_,2882,Metadata_Metadata_closure,2883,Metadata_merge_closure,2884,mergeMaps,506,Metadata_merge_closure0,2884,Set,2885,mergeMaps_closure,1723,UnmodifiableMapView,2886,Metadata,493,UnmodifiableSetView,533,ConstantMap_map_closure,806,ConstantMap__throwUnmodifiable,2887,_ConstantMapKeyIterable,2888,ConstantStringMap,2889,DelegatingSet,2890,Metadata__validateTags_closure,2891,Metadata__validateTags_closure0,2891,pluralize,593,toSentence,592,LinkedHashMap_LinkedHashMap,2415,LinkedHashMap_LinkedHashMap$from_closure,2892,PlatformSelector_evaluate_closure,2893,SuitePlatform,530,OperatingSystem_find_closure,2894,OperatingSystem_find_closure0,2894,Runtime_Runtime$deserialize_closure,2895,Runtime,2896,Declarer_build_closure,2897,LocalTest,2898,Declarer__tearDownAll_closure,2899,Declarer__tearDownAll__closure,2900,Declarer__tearDownAll___closure,2901,Declarer,561,Duration$,2415,LinkedHashSet_LinkedHashSet,2415,PlatformSelector$parse,2447,Metadata__deserializeTimeout,2902,Metadata$deserialize_closure,2903,Stream_last_closure,1049,Stream_last_closure0,1049,Stream_first_closure,1047,Stream_first_closure0,1047,BooleanSelectorImpl,2904,Parser,2185,Scanner,2905,SpanScanner$,2415,AndNode,2906,IntersectionSelector,2907,OrNode,2908,ConditionalNode,2909,Evaluator,2910,SourceSpanFormatException$,2415,SourceSpanFormatException,538,Token,2911,_SpanScannerState,2912,IdentifierToken,2913,validateErrorArgs,606,StringScannerException$,2415,StringScannerException,527,_FileSpan$,2415,SourceSpanMixin,2914,_FileSpan,509,FileLocation$_,2882,FileLocation,2915,NotNode,2916,VariableNode,2917,_expandSafe,564,SpanScanner,474,_asNumNullable,177,PlatformSelector$parse_closure,2918,PlatformSelector__wrapFormatException,2919,_CompleterSink,2920,_CompleterSink__setDestinationSink_closure,2921,StreamSinkCompleter,2922,_EventRequest,2923,SubscriptionStream,1612,_CancelOnErrorSubscriptionWrapper,2924,_CancelOnErrorSubscriptionWrapper_onError_closure,2925,_CancelOnErrorSubscriptionWrapper_onError__closure,2926,StreamQueue__ensureListening_closure,1619,StreamQueue__ensureListening_closure0,1619,StreamQueue__ensureListening_closure1,1619,ErrorResult,2927,ValueResult,2928,EventSink,2929,_NextRequest,2930,Completer,469,LinkedHashSet,349,fillLiteralSet,56,_GeneratedStreamImpl,2931,Stream_Stream$fromIterable_closure,1042,_IterablePendingEvents,1041,_ZoneSpecification,320,_MultiChannel,620,_MultiChannel_closure,2067,_MultiChannel_closure0,2067,_MultiChannel_closure1,2067,_MultiChannel__closure,2069,JSStackTraceMapper_deserialize,2818,PackageRootResolver,2932,asUri,598,ensureTrailingSlash,599,JSStackTraceMapper__deserializePackageConfigMap,2933,PackageConfigResolver__normalizeMap,2934,PackageConfigResolver,2935,JSStackTraceMapper,2936,Map_castFrom,2937,CastMap,2938,CastIterable_CastIterable,2415,CastIterator,2939,EfficientLengthIterable,2940,_EfficientLengthCastIterable,2941,CastIterable,0,CastMap_forEach_closure,747,PackageConfigResolver__normalizeMap_closure,2942,mapMap,505,mapMap_closure,1721,mapMap_closure0,1721,JSStackTraceMapper__deserializePackageConfigMap_closure,2943,StreamChannelCompleter,2256,_StreamChannel,2944,main_closure0,2397,group,587,get$group,587,_declarer,584,Metadata$parse,2447,Trace_Trace$current,621,Declarer_group_closure,2945,Trace_Trace$current_closure,2946,Metadata_validatePlatformSelectors_closure,2947,PlatformSelector_validate_closure,2948,PlatformSelector_validate__closure,2949,Validator,2950,Metadata__parseOnPlatform,2951,Metadata__parseTags,2952,_globalDeclarer,2953,_declarer_closure,2954,RunnerSuiteController,2955,_AsyncBroadcastStreamController,2956,AsyncMemoizer,2957,RunnerSuite,2958,Engine$,2415,Stopwatch__frequency,2959,Primitives_initTicker,2960,Primitives_timerFrequency,2961,ExpandedReporter,2962,Stopwatch,2320,PrintSink,2963,Stream_Stream$fromFuture,2964,_declarer__closure,2965,Future_Future$error,2966,UnmodifiableListView,2967,Primitives_timerTicks,2968,UnionSet,2969,UnionSet_length_closure,1739,IterableSet,2970,UnionSet__iterable_closure,1741,ExpandedReporter__onTestStarted_closure,2971,ExpandedReporter__onTestStarted_closure0,2971,ExpandedReporter__onTestStarted_closure1,2971,indent,591,prefixLines,594,Engine_run_closure,2972,Engine_run_closure0,2972,Pool_close_closure,2973,FutureGroup,499,_ListQueueIterator$,2415,FutureGroup_add_closure,1573,FutureGroup_add_closure0,1573,Engine_run__closure,2286,LiveSuiteController$,2415,Engine_run___closure,2974,Engine_run____closure,2975,Pool__onResourceReleaseAllowed_closure,2976,Pool__runOnRelease_closure,2977,Pool__runOnRelease_closure0,2977,PoolResource,2978,LiveSuiteController_close_closure,2979,RunnerSuiteController__close_closure,2980,Engine__runSkippedTest_closure,2981,Engine__runSkippedTest_closure0,2981,Engine__runSkippedTest_closure1,2981,Engine__runLiveTest_closure,2982,Engine__runLiveTest_closure0,2982,Future_Future$microtask,2983,Engine__runLiveTest_closure1,2982,Future_Future$microtask_closure,2984,LiveSuiteController_reportLiveTest_closure,2985,QueueList__nextPowerOf2,2986,StreamGroup_add_closure,1602,StreamGroup_add_closure0,1602,StreamGroup__listenToStream_closure,2987,LiveSuiteController,595,_LiveSuite,2988,LiveSuiteController_closure,2989,LiveSuiteController_closure0,2989,Stream_Stream$fromFuture_closure,1039,Stream_Stream$fromFuture_closure0,1039,Future_wait,2990,Engine_success_closure,2991,Future_wait_handleError,2992,Future_wait_closure,984,Primitives_initTicker_closure,2993,StreamGroup,2994,UnionSetController$,2415,Pool$,2415,Engine,498,StreamGroup__onCancelBroadcast_closure,1606,StreamGroup__onListen_closure,1604,Engine_closure,2995,Engine_closure0,2995,Pool,485,UnionSetController,507,closure0,2807,_macOSDirectories,2996,SuiteConfiguration__list,2997,UnmodifiableSetView$,2415,SuiteConfiguration__map,2998,SuiteConfiguration,2999,main__closure,3000,test,586,get$test,586,Declarer_test_closure,3001,Declarer_test__closure,3002,Declarer_test___closure,3003,Declarer__runSetUps_closure,3004,Future_forEach,3005,Future_forEach_closure,3006,Future_doWhile,3007,Future_doWhile_closure,3008,method1,550,method2,551,expect,539,get$expect,539,_expect,540,wrapMatcher,609,_expect_closure,3009,_expect_closure0,3009,fail,544,StringDescription,3010,formatFailure,545,Matcher,3011,prettyPrint,546,prettyPrint__prettyPrint,3012,prettyPrint__prettyPrint_pp,3013,_typeName,622,prettyPrint__prettyPrint_closure,3014,prettyPrint__prettyPrint_closure0,3014,prettyPrint__prettyPrint_closure1,3014,escape,612,escape_closure,3015,_getHexLiteral,613,Runes,3016,_combineSurrogatePair,413,RuneIterator,3017,_Predicate,3018,wrapMatcher_closure,3019,_StringEqualsMatcher,3020,_DeepMatcher,3021,JsLinkedHashMap_addAll_closure,865,_DeepMatcher__compareSets_closure,3022,TypeMatcher,3023,FeatureMatcher,3024,_StringEqualsMatcher__writeTrailing,3025,_StringEqualsMatcher__writeLeading,3026,x,3027,_Utils_objectAssign,3028,JS_CONST,3029,Interceptor,3030,JSBool,3031,JSNull,3032,JSObject,3033,JavaScriptObject,3034,PlainJavaScriptObject,3035,UnknownJavaScriptObject,3036,Function,3037,JavaScriptFunction,3038,JSUnmodifiableArray,3039,JSNumber,3040,JSInt,3041,JSDouble,3042,Pattern,3043,JSString,3044,_CastIterableBase,3045,EmptyIterator,3046,FixedLengthListMixin,3047,UnmodifiableListBase,3048,ConstantMap,3049,Instantiation,815,TypeErrorDecoder_noSuchMethodPattern,3050,TypeErrorDecoder_notClosurePattern,3051,TypeErrorDecoder_nullCallPattern,3052,TypeErrorDecoder_nullLiteralCallPattern,3053,TypeErrorDecoder_undefinedCallPattern,3054,TypeErrorDecoder_undefinedLiteralCallPattern,3055,TypeErrorDecoder_nullPropertyPattern,3056,TypeErrorDecoder_nullLiteralPropertyPattern,3057,TypeErrorDecoder_undefinedPropertyPattern,3058,TypeErrorDecoder_undefinedLiteralPropertyPattern,3059,NoSuchMethodError,3060,StackTrace,3061,TearOffClosure,3062,RegExp,382,RegExpMatch,3063,Match,3064,NativeByteBuffer,3065,ByteBuffer,3066,NativeTypedData,3067,NativeByteData,3068,NativeTypedArray,3069,NativeTypedArrayOfDouble,3070,NativeTypedArrayOfInt,3071,NativeFloat32List,3072,NativeFloat64List,3073,NativeInt16List,3074,NativeInt32List,3075,NativeInt8List,3076,NativeUint16List,3077,Uint32List,3078,NativeUint32List,3079,NativeUint8ClampedList,3080,NativeUint8List,418,Uint8List,3081,Type,3082,_Error,3083,Timer,319,Sink,3084,Exception,58,_Completer,3085,StreamTransformer,3086,StreamTransformerBase,3087,_StreamController,3088,_SyncStreamControllerDispatch,3089,_AsyncStreamControllerDispatch,3090,_StreamImpl,3091,_DelayedEvent,3092,_DelayedDone,3093,Error,3094,ZoneSpecification,3095,ZoneDelegate,3096,Zone,3097,_Zone,3098,_RootZone__rootMap,3099,_RootZone,3100,IterableBase,3101,ListBase,3102,MapBase,3103,_UnmodifiableMapMixin,3104,MapView,3105,Queue,3106,SetMixin,3107,SetBase,3108,_SetBase,3109,AsciiCodec,3110,_UnicodeSubsetEncoder,3111,AsciiEncoder,3112,Base64Codec,3113,Base64Encoder,3114,_FusedCodec,3115,Converter,3116,Encoding,3117,JsonCodec,3118,JsonDecoder,3119,Utf8Codec,3120,Utf8Encoder,3121,Utf8Decoder__decoder,3122,OutOfMemoryError,3123,StringSink,3124,Uri,3125,_Uri__isWindowsCached,3126,_Uri__needsNoEncoding,3127,AbortPaymentEvent,3128,AnimationEvent,3129,AnimationPlaybackEvent,3130,ApplicationCacheErrorEvent,3131,BackgroundFetchClickEvent,3132,BackgroundFetchEvent,3133,BackgroundFetchFailEvent,3134,BackgroundFetchedEvent,3135,BeforeInstallPromptEvent,3136,BeforeUnloadEvent,3137,Blob,3138,BlobEvent,3139,CanMakePaymentEvent,3140,ClipboardEvent,3141,CloseEvent,3142,CompositionEvent,3143,CustomEvent,3144,DeviceMotionEvent,3145,DeviceOrientationEvent,3146,DomError,3147,DomException,3148,ErrorEvent,3149,Event,3150,EventTarget,3151,ExtendableEvent,3152,ExtendableMessageEvent,3153,FetchEvent,3154,File,3155,FocusEvent,3156,FontFaceSetLoadEvent,3157,ForeignFetchEvent,3158,GamepadEvent,3159,HashChangeEvent,3160,InstallEvent,3161,KeyboardEvent,3162,Location,3163,MediaEncryptedEvent,3164,MediaError,3165,MediaKeyMessageEvent,3166,MediaQueryListEvent,3167,MediaStreamEvent,3168,MediaStreamTrackEvent,3169,MessageEvent,3170,MessagePort,3171,MidiConnectionEvent,3172,MidiMessageEvent,3173,MouseEvent,3174,MutationEvent,3175,NavigatorUserMediaError,3176,NotificationEvent,3177,OverconstrainedError,3178,PageTransitionEvent,3179,PaymentRequestEvent,3180,PaymentRequestUpdateEvent,3181,PointerEvent,3182,PopStateEvent,3183,PositionError,3184,PresentationConnectionAvailableEvent,3185,PresentationConnectionCloseEvent,3186,ProgressEvent,3187,PromiseRejectionEvent,3188,PushEvent,3189,RtcDataChannelEvent,3190,RtcDtmfToneChangeEvent,3191,RtcPeerConnectionIceEvent,3192,RtcTrackEvent,3193,SecurityPolicyViolationEvent,3194,SensorErrorEvent,3195,SpeechRecognitionError,3196,SpeechRecognitionEvent,3197,SpeechSynthesisEvent,3198,StorageEvent,3199,SyncEvent,3200,TextEvent,3201,TouchEvent,3202,TrackEvent,3203,TransitionEvent,3204,UIEvent,3205,VRDeviceEvent,3206,VRDisplayEvent,3207,VRSessionEvent,3208,WheelEvent,3209,Window,3210,_MojoInterfaceRequestEvent,3211,_ResourceProgressEvent,3212,_USBConnectionEvent,3213,EventStreamProvider,3214,_StructuredClone,3215,_AcceptStructuredClone,3216,VersionChangeEvent,3217,ByteData,3218,Int8List,3219,Uint8ClampedList,3220,Int16List,3221,Uint16List,3222,Int32List,3223,Float32List,3224,Float64List,3225,AudioProcessingEvent,3226,OfflineAudioCompletionEvent,3227,ContextEvent,3228,SqlError,3229,DelegatingSink,3230,DelegatingStreamSubscription,3231,_StreamGroupState,3232,BooleanSelector,3233,All,3234,Node,3235,Visitor,3236,None,3237,TokenType,3238,RecursiveVisitor,3239,EmptyUnmodifiableSet,3240,UnmodifiableSetMixin,3241,_DelegatingIterableBase,3242,DelegatingIterable,3243,_IsTrue,3244,Description,3245,SyncPackageResolver,3246,_PathDirection,3247,_PathRelation,3248,InternalStyle,3249,Style,3250,Mapping,3251,_TokenKind,3252,FileSpan,3253,SourceLocationMixin,3254,SourceSpan,3255,SourceSpanBase,1948,SourceSpanException,3256,Frame__uriRegExp,3257,Frame__windowsRegExp,3258,StackZoneSpecification_disableKey,3259,StreamChannelMixin,3260,LineScannerState,3261,StringScanner,3262,AsciiGlyphSet,3263,UnicodeGlyphSet,3264,LiveTest,3265,MessageType,3266,Metadata_empty,3267,OperatingSystem,3268,Status,3269,Result0,2826,Test,3270,StackTraceMapper,3271,LiveSuite,3272,RuntimeSelection,3273,SuiteConfiguration_empty,3267,_NativeTypedArrayOfDouble_NativeTypedArray_ListMixin,3274,_NativeTypedArrayOfDouble_NativeTypedArray_ListMixin_FixedLengthListMixin,3275,_NativeTypedArrayOfInt_NativeTypedArray_ListMixin,3276,_NativeTypedArrayOfInt_NativeTypedArray_ListMixin_FixedLengthListMixin,3277,_ListBase_Object_ListMixin,3278,_SetBase_Object_SetMixin,3279,_UnmodifiableMapView_MapView__UnmodifiableMapMixin,3280,_QueueList_Object_ListMixin,3281,_UnionSet_SetBase_UnmodifiableSetMixin,3282,_UnmodifiableSetView_DelegatingSet_UnmodifiableSetMixin,3283,_IterableSet_SetMixin_UnmodifiableSetMixin,3284,main,552,getNativeInterceptor,249,isJsIndexable,11,Primitives_dateNow,3285,closureFromTearOff,66,throwCyclicInit,80,getRuntimeTypeArguments,85,computeSignature,96,defineProperty,98,findType,133,typeLiteral,153,_asDoubleNullable,171,_Universe_addRules,3286,_Universe_addErasedTypes,3287,_AsyncRun__scheduleImmediateJsOverride,3288,_AsyncRun__scheduleImmediateWithSetImmediate,3289,_AsyncRun__scheduleImmediateWithTimer,3290,Future__kTrue,3291,_startMicrotaskLoop,299,StreamIterator_StreamIterator,2415,_nullDataHandler,312,_nullErrorHandler,313,_nullDoneHandler,314,_rootRegisterCallback,327,_rootRegisterUnaryCallback,328,_rootRegisterBinaryCallback,329,_rootErrorCallback,330,_rootCreateTimer,332,_rootCreatePeriodicTimer,333,_rootPrint,335,_printToZone,336,Uri_decodeComponent,3292,_escapeString,623,main0,552,max,470,DART_CLOSURE_PROPERTY_NAME,2333,JS_INTEROP_INTERCEPTOR_TAG,2334,_AsyncRun__scheduleImmediateClosure,3293,Future__nullFuture,3294,_Base64Decoder__inverseAlphabet,3295,_hasErrorStackProperty,2352,_scannerTables,2353,_whitespaceAndSingleLineComments,2354,_multiLineCommentBody,2355,_hyphenatedIdentifier,2356,_dart2DynamicArgs,2357,_escapeRegExp,2358,windows,2359,url,2360,context,2361,get$context,2361,Style_posix,3296,Style_windows,2359,Style_url,2360,Style_platform,3297,_digits,2370,MAX_INT32,2371,MIN_INT32,2372,_specKey,2373,_vmFrame,2374,_v8Frame,2375,_v8UrlLocation,2376,_v8EvalLocation,2377,_firefoxSafariFrame,2378,_friendlyFrame,2379,_asyncBody,2380,_initialDot,2381,_terseRegExp,2385,_v8Trace,2386,_v8TraceLine,2387,_firefoxSafariTrace,2388,_friendlyTrace,2389,_universalValidVariables,2391,_currentKey,2392,_currentKey0,2392,currentOSGuess,2393,_hyphenatedIdentifier0,2356,anchoredHyphenatedIdentifier,2394,$get$DART_CLOSURE_PROPERTY_NAME,2333,$get$JS_INTEROP_INTERCEPTOR_TAG,2334,$get$TypeErrorDecoder_noSuchMethodPattern,3050,$get$TypeErrorDecoder_notClosurePattern,3051,$get$TypeErrorDecoder_nullCallPattern,3052,$get$TypeErrorDecoder_nullLiteralCallPattern,3053,$get$TypeErrorDecoder_undefinedCallPattern,3054,$get$TypeErrorDecoder_undefinedLiteralCallPattern,3055,$get$TypeErrorDecoder_nullPropertyPattern,3056,$get$TypeErrorDecoder_nullLiteralPropertyPattern,3057,$get$TypeErrorDecoder_undefinedPropertyPattern,3058,$get$TypeErrorDecoder_undefinedLiteralPropertyPattern,3059,$get$_AsyncRun__scheduleImmediateClosure,3293,$get$Future__nullFuture,3294,$get$_currentKey,2392,$get$_RootZone__rootMap,3099,$get$_currentKey0,2392,$get$_specKey,2373,$get$context,2361,$get$Style_url,2360,$get$Style_windows,2359,$get$Utf8Decoder__decoder,3122,$get$Style_platform,3297,$get$_Uri__isWindowsCached,3126,$get$_Uri__needsNoEncoding,3127,$get$_scannerTables,2353,$get$_Base64Decoder__inverseAlphabet,3295,$get$Style_posix,3296,$get$_friendlyFrame,2379,$get$_v8Trace,2386,$get$_firefoxSafariTrace,2388,$get$_friendlyTrace,2389,$get$_vmFrame,2374,$get$_asyncBody,2380,$get$_firefoxSafariFrame,2378,$get$_initialDot,2381,$get$Frame__uriRegExp,3257,$get$Frame__windowsRegExp,3258,$get$windows,2359,$get$_v8Frame,2375,$get$_v8EvalLocation,2377,$get$_v8UrlLocation,2376,$get$_v8TraceLine,2387,$get$_hasErrorStackProperty,2352,$get$_terseRegExp,2385,$get$url,2360,$get$_digits,2370,$get$MIN_INT32,2372,$get$MAX_INT32,2371,$get$StackZoneSpecification_disableKey,3259,$get$anchoredHyphenatedIdentifier,2394,$get$_hyphenatedIdentifier,2356,$get$_hyphenatedIdentifier0,2356,$get$_whitespaceAndSingleLineComments,2354,$get$_multiLineCommentBody,2355,$get$_universalValidVariables,2391,$get$SuiteConfiguration_empty,3267,$get$currentOSGuess,2393,$get$Metadata_empty,3267,$get$_escapeRegExp,2358,$get$_dart2DynamicArgs,2357,getInterceptor$,3298,getInterceptor$asx,3299,async___startMicrotaskLoop$closure,3300,async__AsyncRun__scheduleImmediateJsOverride$closure,3301,async__AsyncRun__scheduleImmediateWithSetImmediate$closure,3302,async__AsyncRun__scheduleImmediateWithTimer$closure,3303,async___nullDoneHandler$closure,3304,async___nullErrorHandler$closure,3305,async___nullDataHandler$closure,3306,getInterceptor$ax,3307,async___printToZone$closure,3308,math__max$closure,3309,getInterceptor$s,3310,core_Uri_decodeComponent$closure,3311,main_test__main$closure,3312,_js_helper_Primitives_dateNow$closure,3313,async_Future__kTrue$closure,3314,pretty_print___escapeString$closure,3315,util___getHexLiteral$closure,3316,getInterceptor$x,3317,getInterceptor$ansx,3318,getInterceptor$n,3319,async___rootHandleUncaughtError$closure,3320,async___rootRun$closure,3321,async___rootRunUnary$closure,3322,async___rootRunBinary$closure,3323,async___rootRegisterCallback$closure,3324,async___rootRegisterUnaryCallback$closure,3325,async___rootRegisterBinaryCallback$closure,3326,async___rootErrorCallback$closure,3327,async___rootScheduleMicrotask$closure,3328,async___rootCreateTimer$closure,3329,async___rootCreatePeriodicTimer$closure,3330,async___rootPrint$closure,3331,async___rootFork$closure,3332","instance":"K1,3333,$this,3334,_box_0,3335,matcher,3336,future,3337,get$future,3337,V1,3338,listener,3339,frame,3340,expectedElement,3341,getMain,3342,value,2416,location,3343,get$location,3343,channel,3344,get$channel,3344,depth,3345,controller,3346,super$StringScanner$matches,3347,beforeLoad,3348,f,3349,line,3350,get$line,3350,printZone,3351,R,3352,end,3353,get$end,3353,transform,3354,result,3355,selector,3356,handleError,3357,get$handleError,3357,body,3358,elements,3359,x,3360,suite,3361,get$suite,3361,T,3362,index,3363,predicate,610,error,2966,get$error,2966,validVariables,3364,terse,3365,super$EventTarget$addEventListener,3366,_TimerImpl$2,2415,stackTrace,3367,_BufferingStreamSubscription$4,2415,_EventStreamSubscription$4,2415,LinkedHashMapKeyIterator$2,2415,timeout,3368,DateTime$_withValue$2$isUtc,3369,_Future$zoneValue$2,2593,StreamChannelController$2$allowForeignErrors$sync,2415,GuaranteeChannel$3$allowSinkErrors,2415,message,3370,get$message,3370,_Future$immediate$1,3371,_CustomZone$3,2415,performance,3372,trace,3373,div,3374,_TimerImpl$periodic$2,2623,level,3375,span,3376,get$span,3376,_LinkedHashSetIterator$2,2415,registered,3377,Instantiation$1,2415,_MatchImplementation$2,2415,SubListIterable$3,2415,super$Iterable$where,3378,SourceSpanBase$3,2415,SourceSpanWithContext$4,2415,SourceLocation$4$column$line$sourceUrl,2415,super$JavaScriptObject$toString,3379,SingleMapping$fromJson$2$mapUrl,2788,callback,3380,SourceFile$decoded$2$url,3381,test,586,get$test,586,MultiSectionMapping$fromJson$3$mapUrl,2788,groups,3382,get$groups,3382,liveTest,3383,get$liveTest,3383,engine,3384,MappingBundle$fromJson$2$mapUrl,2788,_StreamControllerAddStreamState$4,2415,_DoneStreamSubscription$1,2415,Invoker$_$4$groups$guarded,2882,chain,3385,LiveTestController$5$groups,2415,_BroadcastSubscription$5,2415,Metadata$_$10$chainStackTraces$forTag$onPlatform$retry$skip$skipReason$tags$testOn$timeout$verboseTrace,2882,completer,3386,longest,3387,SuitePlatform$3$inGoogle$os,2415,FileLocation$_$2,2882,_FileSpan$3,2415,StringScanner$3$position$sourceUrl,2415,_box_1,3388,SubscriptionStream$1,2415,super$Iterable$skipWhile,3389,fn,3390,$call$body$Engine_run__closure,3391,oldPredicate,3392,ListQueue$1,2415,counter,3393,QueueList$1,2415,hasError,3394,_MultiChannel$1,2415,loadResource,3395,StreamChannelCompleter$0,2415,super$TypeMatcher$describe,3396,id,3397,Metadata$parse$8$chainStackTraces$onPlatform$retry$skip$tags$testOn$timeout$verboseTrace,2447,milliseconds,3398,super$TypeMatcher$matches,3347,originalSource,3399,Stopwatch$0,2415,start,2491,get$start,2491,stream,3400,get$stream,3400,ExpandedReporter$_$5$color$printPath$printPlatform,2882,_Future$immediateError$2,3401,outstandingCallbacksForBody,3402,sourceResult,3403,K,3404,serializedOnPlatform,3405,V,3406,LiveSuiteController$1,2415,StreamGroup$broadcast$0,3407,onData,3408,get$onData,3408,Engine$3$concurrency$coverage$maxSuites,2415,Pool$2$timeout,2415,UnionSetController$1$disjoint,2415,target,3409,StringDescription$1,2415,sourceMap,3410,invoker,3411,minified,3412,zone,3413,sdkRoot,3414,streamConsumer,3415,e,3416,platform,3297,s,3417,sdkLib,3418,super$_BroadcastStreamController$_addEventError,3419,column,3420,get$column,3420,cleanUp,3421,eagerError,3422,iterator,3423,get$iterator,3423,identifier,3424,port,3425,get$port,3425,action,3426,node,3427,T1,3428,T2,3429,withResource$body$Pool,3430,portSubscription,3431,serialized,3432,doneSignal,3433,arg1,3434,pos,3435,arg2,3436,dispatch,3437,getTag,3438,testOn,3439,getUnknownTag,3440,skip,3441,get$skip,3441,verboseTrace,3442,get$verboseTrace,3442,prototypeForTag,3443,chainStackTraces,3444,get$chainStackTraces,3444,argumentError,3445,super$SourceSpanMixin$$eq,3446,retry,3447,get$retry,3447,arg,3448,skipReason,3449,_convertedObjects,3450,onPlatform,3451,uri,3452,get$uri,3452,portStart,3453,skipped,3454,get$skipped,3454,textInside,3455,maxItems,3456,maxLineLength,3457,original,3458,get$original,3458,_runGroup$body$Engine,3459,host,3460,get$host,3460,_prettyPrint,3461,_runLiveTest$body$Engine,3462,indent,3463,_runSkippedTest$body$Engine,3464,computation,3465,orElse,3466,countSuccess,3467,K2,3468,$protected,3469,spec,3470,pp,3471,super$DelegatingStreamSubscription$onError,3472,object,3473,ex,3474,subscription,3475,super$DelegatingStreamSubscription$cancel,3476,bodyFunction,3477,tables,3478,data,3479,get$data,3479,_foreign,3480,set$_foreign,3480,_disabled,3481,get$_disabled,3481,test$9$onPlatform$retry$skip$solo$tags$testOn$timeout,586,_onTestStarted,3482,get$_onTestStarted,3482,newDartList$1,3483,configure$1$mapper,3484,elapsedMicroseconds,3485,get$elapsedMicroseconds,3485,_captured_argumentError_0,3486,_platform_selector$_captured_this_0,3487,_completeWithValue,3488,get$_completeWithValue,3488,_parser$_captured_this_0,3489,_isScheduled,3490,get$_isScheduled,3490,hashMapCellKey,3491,_captured_future_1,3492,_closeChannel,3493,get$_closeChannel,3493,encode$1,3494,_error,3495,get$_error,3495,_core$_start,3496,onClose,3497,get$onClose,3497,_captured_callback_1,3498,_completeError,3499,get$_completeError,3499,onData$1,3408,_captured_doneSignal_2,3500,_captured_result_2,3501,configure$3$except$mapper$only,3484,separators,3502,set$separators,3502,_async_memoizer$_completer,3503,_counterKey,3504,_captured_V1_2,3505,prettyUri,3506,get$prettyUri,3506,_captured_serializedOnPlatform_0,3507,_compareSets,3508,get$_compareSets,3508,fold$1$2,3509,getRoot,3510,get$getRoot,3510,_newSet$0,3511,_captured_stackTrace_3,3512,_isPort$1,3513,catchError$1,3514,*,3515,get$*,3515,_and$0,3516,_captured_verboseTrace_4,3517,_scanOperator$1,3518,_writeFirstLine$1,3519,_handleUncaughtError,3520,get$_handleUncaughtError,3520,set$_handleUncaughtError,3520,_failedGroup,3521,_kind,3522,isOdd,3523,get$isOdd,3523,_findLine,3524,get$_findLine,3524,_captured_V_3,3525,_currentNode,3526,set$_currentNode,3526,_onSuiteStartedController,3527,_captured_f_0,3528,_requestedResources,3529,indexOf$2,3530,_precomputed4,3531,_consumeValue$0,3532,replaceAll$2,3533,_completer,3534,tags,3535,_captured_sdkRoot_3,3536,_previous,3537,needsSeparatorPattern,3538,_run,3539,get$_run,3539,set$_run,3539,replace$1$path,3540,_collection$_add,3541,get$_collection$_add,3541,_shlPositive$1,3542,_ensureController,3543,get$_ensureController,3543,_location,3544,get$_location,3544,_mayAddListener,3545,get$_mayAddListener,3545,_captured_terse_2,3546,_preGrow,3547,get$_preGrow,3547,evaluate$1,3548,_base,3549,_isWithinOrEqualsFast,3550,get$_isWithinOrEqualsFast,3550,_modified$0,3551,shouldBeDone,3552,get$shouldBeDone,3552,isBlink,3553,_currentCodePoint,3554,_addResult,3555,get$_addResult,3555,_source_map_stack_trace$_box_0,3556,typedMatches$2,3557,moveNext$0,3558,_execAnchored$2,3559,duration,3560,_onSinkDisconnected$0,3561,isCompleted,3562,get$isCompleted,3562,query,3563,get$query,3563,visitVariable$1,3564,bindCallback,3565,get$bindCallback,3565,_currentTrace,3566,get$_currentTrace,3566,sourceRoot,3567,take,3568,get$take,3568,_progressLine$2$suffix,3569,listenerHasError,3570,liveTests,3571,get$liveTests,3571,createPeriodicTimer,3572,_live_suite_controller$_onCloseCompleter,3573,_remote_listener$_captured_controller_0,3574,_onCancel$0,3575,pathsEqual,3576,get$pathsEqual,3576,distance,3577,get$distance,3577,_core$_position,3578,asStream$0,3579,abs$0,3580,verticalLine,1935,get$verticalLine,1935,listenerValueOrError,3581,_decrementPauseCount$0,3582,_html_common$_captured_this_1,3583,hasTrailingSeparator,3584,get$hasTrailingSeparator,3584,_stack_trace_formatter$_captured_this_0,3585,_declarer$_name,3586,_stack_zone_specification$_registerBinaryCallback$4,3587,_tail,3588,asUint8List$2,3589,isScaffoldAll,3590,_captured_test_1,3591,isWithin$2,3592,_platform_selector$_captured_validVariables_0,3593,_functions$_box_0,3594,_passed,3595,addTearDown$1,3596,_iterator,3597,hashMapCellValue,3598,_captured_K1_1,3599,lastSpan,3600,get$lastSpan,3600,_environment,3601,registerCallback$1$1,3602,handleUncaughtError$3,3603,_stack_zone_specification$_captured_this_1,3604,allMatches$2,3605,_position,3606,_expr,3607,_data,3608,_suiteChannel,3609,_captured_suite_1,3610,onListen,3611,_recursiveMatch$4,3612,_writeSidebar$2$end$line,3613,_onCancelBroadcast$0,3614,__internal$_iterable,3615,_buffer,3616,_flush$0,3617,_captured_identifier_0,3618,_invoker$_print$1,3619,handleUncaughtError,3603,get$handleUncaughtError,3603,scan,3620,get$scan,3620,onCancel,3621,_schedule$0,3622,_metadata,3623,_colorize$1,3624,_captured_sdkRoot_2,3625,_target,3626,_multiline,3627,_paddingBeforeSidebar,3628,_startIndex,3629,get$_startIndex,3629,_specializedTestResource,3630,_isInt32$1,3631,_contains,3632,get$_contains,3632,_handleError$3,3633,_modified,3551,get$_modified,3551,_runPool,3634,_setPendingComplete$0,3635,_simpleExpression$0,3636,_rootRunUnary$5,3637,_cloneResult$1,3638,_trimVMChain,3639,get$_trimVMChain,3639,_live_test_controller$_run,3640,get$_live_test_controller$_run,3640,_collection$_first,3641,_serializeTimeout,3642,get$_serializeTimeout,3642,relative$1,3643,_collectTraces,3644,_span_exception$_message,3645,_onRun,3646,join,3647,get$join,3647,_stack_zone_specification$_errorCallback$5,3648,_stack_zone_specification$_registerUnaryCallback$4,3649,_closeMemo,3650,change$4$onPlatform$skip$skipReason$timeout,3651,addAll$1,3652,_isPaused,3653,get$_isPaused,3653,previous,3654,_async$_addError$2,3655,_captured_minified_4,3656,_queryStart,3657,_captured_eagerError_4,3658,_captured_T_2,3659,join$8,3647,whenComplete$1,3660,position,3661,get$position,3661,set$position,3661,_newLinkedCell$2,3662,asCurrent$1$1,3663,addDescriptionOf$1,3664,_captured_orElse_1,3665,_captured_subscription_0,3666,_count,3667,_isPort,3513,get$_isPort,3513,_eval,3668,get$_eval,3668,_addEventError,3669,get$_addEventError,3669,_optionalPositional,3670,encode,3494,get$encode,3494,_writeToList$1,3671,getRoot$1,3510,_hasPending,3672,get$_hasPending,3672,_test,3673,_runner_suite$_captured_this_0,3674,_captured_zone_0,3675,_invoker$_controller,3676,setRange$4,3677,write$1,3678,_captured_maxItems_0,3679,_isClosed,3680,_closeInnerChannel$0,3681,_closable,3682,get$_closable,3682,update,3683,get$update,3683,_newSet,3511,get$_newSet,3511,_post_message_channel$_captured_controller_0,3684,_collection$_current,3685,set$_collection$_current,3685,_thenAwait$1$2,3686,_upgrade$0,3687,_fetch,3688,get$_fetch,3688,isNewSegment,3689,where$1,3378,_semantics,3690,internalFindBucketIndex,3691,get$internalFindBucketIndex,3691,_stack_zone_specification$_registerCallback$4,3692,_cachedLine,3693,_live_suite_controller$_controller,3694,_cancel,3695,get$_cancel,3695,isPassing,3696,get$isPassing,3696,_lineStart,3697,_noColor,3698,_forEachListener$1,3699,_isDebugging,3700,fillRange$3,3701,_second,3702,removeTrailingSeparators,3703,get$removeTrailingSeparators,3703,_errorName,3704,get$_errorName,3704,_union_set_controller$_set,3705,set$_union_set_controller$_set,3705,visitAnd$1,3706,_pool$_closeMemo,3707,_chainFuture,3708,get$_chainFuture,3708,declare$1,3709,needsSeparator$1,3710,second,3711,get$second,3711,_isCanceled,3712,get$_isCanceled,3712,handleNext$1,3713,_disjoint,3714,_liveTest,3715,_ensurePendingEvents$0,3716,failed,3717,get$failed,3717,_parse$1,3718,_lastMatch,3719,replaceRange$3,3720,_carry,3721,expand$1$1,3722,_stack_zone_specification$_captured_this_0,3723,isAbsolute,3724,get$isAbsolute,3724,peek,3725,get$peek,3725,_captured_object_1,3726,_rootRegisterCallback$4,3727,_doneCompleter,3728,isValue,3729,get$isValue,3729,distance$1,3577,_defaultSplit$1,3730,_execGlobal$2,3731,_simpleExpression,3636,get$_simpleExpression,3636,_onResourceReleaseAllowed$1,3732,topLeftCorner,1932,get$topLeftCorner,1932,_shrBothPositive$1,3733,_captured_longest_0,3734,_stack_zone_specification$_registerCallback,3692,get$_stack_zone_specification$_registerCallback,3692,_captured_error_0,3735,_remove$1,3736,_string,3737,padRight$1,3738,_async$_iterator,3739,set$_async$_iterator,3739,success,3740,get$success,3740,_stack_zone_specification$_captured_stackTrace_2,3741,_pendingIds,3742,_toNonSimple,3743,get$_toNonSimple,3743,_captured_spec_6,3744,readChar$0,3745,scaleFactor,3746,__js_helper$_string,3747,_stream_group$_controller,3748,set$_stream_group$_controller,3748,_serializeGroup,3749,get$_serializeGroup,3749,microsecond,3750,get$microsecond,3750,_checkNotBuilt,3751,get$_checkNotBuilt,3751,_computeKeys$0,3752,binaryOnError,3753,_or$0,3754,_captured_computation_1,3755,_execAnchored,3559,get$_execAnchored,3559,_findBucketIndex,3756,get$_findBucketIndex,3756,release,3757,get$release,3757,_runner_suite$_close$0,3758,union,3759,get$union,3759,_iterable,3760,_setRangeFast$4,3761,_captured_countSuccess_2,3762,_map,3763,internalSet$2,3764,_checkModification$1,3765,single,3766,get$single,3766,_captured_K_2,3767,joinAll$1,3768,configure,3484,get$configure,3484,_isEmpty,3769,get$_isEmpty,3769,load,3770,get$load,3770,pipe,3771,get$pipe,3771,identicalInJs$2,3772,_asyncCompleteError,3773,get$_asyncCompleteError,3773,_lazy_trace$_trace,3774,get$_lazy_trace$_trace,3774,hashCode,3775,get$hashCode,3775,_pool,3776,onError$1,3472,decrement$0,3777,registerUnaryCallback,3778,get$registerUnaryCallback,3778,_maxAllocatedResources,3779,isComplete,3780,get$isComplete,3780,indexable,3781,hasTokens,3782,get$hasTokens,3782,_html$_onData,3783,set$_html$_onData,3783,__js_helper$_strings,3784,pathSegments,3785,get$pathSegments,3785,_green,3786,_captured_registered_1,3787,describeTypedMismatch$4,3788,_expected,3789,parse,2447,get$parse,2447,whenFalse,3790,decode$2$reviver,3791,_captured_f_1,3792,any$1,3793,convert,3794,get$convert,3794,[]=,3795,get$[]=,3795,_future_group$_completer,3796,_add$1,3797,_fetch$1,3688,matchTypeError$1,3798,_last,3799,unclosable,3800,get$unclosable,3800,replace,3540,get$replace,3540,containsKey$1,3801,_runGroup$3,3802,_queue_list$_grow$0,3803,_captured_predicate_0,3804,minute,3805,get$minute,3805,_parser$_length,3806,_setUpAllTrace,3807,codeUnitAt,3808,get$codeUnitAt,3808,modifiedObject,3809,_captured_completer_0,3810,newJsObject$0,3811,active,3812,get$active,3812,_lastProgressSuffix,3813,_captured_index_1,3814,padRight,3738,get$padRight,3738,_dedupIterable,3815,get$_dedupIterable,3815,_eval$1,3668,_live_suite_controller$_captured_this_0,3816,endsWith,3817,get$endsWith,3817,_captured_streamConsumer_0,3818,removeLast$0,3819,urls,3820,_captured_hasError_3,3821,_remote_listener$_captured_this_1,3822,_varData,3823,_name,3824,_coverage,3825,_endIndex,3826,get$_endIndex,3826,codeUnitsEqual,3827,get$codeUnitsEqual,3827,_captured_s_2,3828,toTrace,3829,get$toTrace,3829,_segmentError$2,3830,_closeGroup,3831,bindUnaryCallback,3832,get$bindUnaryCallback,3832,_collection$_length,3833,_runner_suite$_controller,3834,_js_util$_captured_T_1,3835,_delegationTarget,3836,storedCallback,3837,_solo,3838,get$_solo,3838,round$0,3839,_bind$1,3840,errorCallback$2,3841,_captured_V1_3,3842,_allowMalformed,3843,lastIndexOf,3844,get$lastIndexOf,3844,_expanded$_onError,3845,get$_expanded$_onError,3845,_setUpAll,3846,get$_setUpAll,3846,_core_matchers$_description,3847,_callback,3848,_skip,3849,dart2jsArgs,3850,cancel,3476,get$cancel,3476,skipWhile$1,3389,group$1,587,_subscription_stream$_captured_error_1,3851,_resultOrListeners,3852,relativeRootPattern,3853,_onError,3854,get$_onError,3854,_createController$0,3855,_collection$_previous,3856,_setError$2,3857,_isNearCachedLine,3858,get$_isNearCachedLine,3858,convert$3,3794,_setBase,3859,get$_setBase,3859,_compareIterables$5,3860,add$1,3861,bindUnaryCallback$2$1,3832,_listenToStream,3862,get$_listenToStream,3862,_html_common$_box_0,3863,visitNot$1,3864,upEnd,1916,get$upEnd,1916,complete,3865,get$complete,3865,cast,3866,get$cast,3866,_skippedGroup,3867,_type_matcher$_name,3868,lastIndexOf$1,3844,_errorExplanation,3869,get$_errorExplanation,3869,close$0,3870,highlight$1$color,3871,_onCancelBroadcast,3614,get$_onCancelBroadcast,3614,_mappings,3872,putIfAbsent,3873,get$putIfAbsent,3873,_captured_performance_0,3874,_completeError$2,3499,isFinite,3875,get$isFinite,3875,runSkipped,3876,get$runSkipped,3876,isUnicode,3877,get$isUnicode,3877,_upgrade,3687,get$_upgrade,3687,span$2,3376,addError$1,3878,toChain$0,3879,__js_helper$_map,3880,_captured_this_1,3881,setUpAll,3882,_noRetry,3883,spanFor$4$files$uri,3884,_errorCallback,3885,get$_errorCallback,3885,set$_errorCallback,3885,_captured_onData_0,3886,count,3887,whenComplete,3660,get$whenComplete,3660,_isComplete,3888,get$_isComplete,3888,_captured_span_2,3889,_captured_groups_2,3890,setAll$2,3891,_onValue,3892,get$_onValue,3892,setDestinationSink$1,3893,_getNext$0,3894,packageConfigMap,3895,get$packageConfigMap,3895,_captured_tables_0,3896,writeln$1,3897,registerUnaryCallback$2$2,3778,_printPlatform,3898,skip$1,3441,_trace,3899,_mayResumeInput,3900,get$_mayResumeInput,3900,invalidValue,3901,_recordPause,3902,get$_recordPause,3902,_captured_T1_3,3903,_span_scanner$_scanner,3904,_bold,3905,_file$_start,3906,noMoreLiveTests$0,3907,change,3651,get$change,3651,_unlisten$0,3908,_engine$_runCalled,3909,_string_scanner$_position,3910,removeFragment$0,3911,_released,3912,_setValue$1,3913,firstMatch$1,3914,_captured_originalSource_0,3915,union$1,3759,liveSuite,3916,get$liveSuite,3916,_next,3917,_internal,3918,_isFile,3919,get$_isFile,3919,isLoadSuite,3920,get$isLoadSuite,3920,where,3378,get$where,3378,_streamController,3921,set$_streamController,3921,unclosable$1$1,3800,_addEventListener,3922,get$_addEventListener,3922,_endOrLength,3923,resolveUri$1,3924,__js_helper$_addHashTableEntry,3925,get$__js_helper$_addHashTableEntry,3925,whenTrue,3926,serialize$0,2631,highlight$0,3871,isEmpty,3927,get$isEmpty,3927,_multiLineComment,3928,get$_multiLineComment,3928,_iterable_set$_base,3929,_checkState,3930,get$_checkState,3930,_captured_future_3,3931,addAll$4,3652,_thenAwait,3686,get$_thenAwait,3686,_inCallback,3932,get$_inCallback,3932,_expanded$_captured_this_0,3933,files,3934,splitMapJoin,3935,get$splitMapJoin,3935,visitNot,3864,get$visitNot,3864,toFilePath$0,3936,internalRemove$1,3937,toolString,3938,get$toolString,3938,toString,3379,get$toString,3379,_failClosed$0,3939,_toListFixed$0,3940,glyphOrAscii,1931,get$glyphOrAscii,1931,_captured_platform_0,3941,_lazy_trace$_captured_this_0,3942,remaining,3943,_captured_portStart_1,3944,_conditional,3945,get$_conditional,3945,_equals_matcher$_match,3946,get$_equals_matcher$_match,3946,_pathSegments,3947,set$_pathSegments,3947,firstWhere,3948,get$firstWhere,3948,floor,3949,get$floor,3949,padLeft$2,3950,_captured_R_5,3951,_trace$_captured_longest_0,3952,onMessage,3953,get$onMessage,3953,_addedSuites,3954,_captured_host_0,3955,_captured_skipped_1,3956,_expanded$_color,3957,_stack_zone_specification$_run,3958,get$_stack_zone_specification$_run,3958,change$1$onPlatform,3651,_mergePaths$2,3959,getText$2,3960,_declarer$_trace,3961,_captured_handleError_1,3962,_isHttp,3963,get$_isHttp,3963,_lastProgressPassed,3964,month,3965,get$month,3965,apply$1,3966,_captured_outstandingCallbacksForBody_1,3967,_createController,3855,get$_createController,3855,_badEventState$0,3968,_captured_validVariables_0,3969,canonicalizePart$1,3970,forEach,3005,get$forEach,3005,toRadixString$1,3971,message$2$color,3370,_asyncCompleteError$2,3773,join$2,3647,update$2,3683,_collection$_cell,3972,_simpleMerge,3973,get$_simpleMerge,3973,_runLiveTest$2,3974,_addRequest$1,3975,_removeHashTableEntry,3976,get$_removeHashTableEntry,3976,_onResourceReleaseAllowed,3732,get$_onResourceReleaseAllowed,3732,validate$1,3977,_queue_list$_table,3978,set$_queue_list$_table,3978,_future_group$_pending,3979,matchesErrorTest,3980,get$matchesErrorTest,3980,_removeListener,3981,get$_removeListener,3981,hasErrorTest,3982,get$hasErrorTest,3982,_expanded$_onDone$1,3983,_onPause$0,3984,packageConfigUri,3985,_lastProgressSkipped,3986,_engine$_box_0,3987,_check,3988,isRelative$1,3989,_captured_selector_0,3990,_retry,3991,_input,3992,addEventListener$3,3366,nextIteration,3993,_timeString$1,3994,pointSpan$0,3995,variableName,3996,_schemeCache,3997,insert,3998,get$insert,3998,day,3999,get$day,3999,_lazy_trace$_captured_predicate_1,4000,putIntoMap$3,4001,_live_test_controller$_state,4002,lines,4003,get$lines,4003,_recordCancel$1,4004,_sendError$2,4005,_newHashTable,4006,get$_newHashTable,4006,removeFirst$0,4007,_runOnRelease$1,4008,_findColumn,4009,get$_findColumn,4009,_removeListeners$0,4010,trim,4011,get$trim,4011,_stream_queue$_close$0,4012,_queue_list$_head,4013,indexOf$1,3530,__js_helper$_index,4014,isDartVM,4015,_stream_sink_completer$_sink,4016,get$_stream_sink_completer$_sink,4016,_flush,3617,get$_flush,3617,virtualChannel$0,4017,listeners,4018,_host,4019,_stream_queue$_subscription,4020,set$_stream_queue$_subscription,4020,_addRequest,3975,get$_addRequest,3975,bindCallbackGuarded,4021,get$bindCallbackGuarded,4021,handleError$1,3357,containsSeparator$1,4022,inMinutes,4023,get$inMinutes,4023,endColumn,4024,_streamCompleter,4025,right,4026,setRange$3,3677,hasAbsolutePath,4027,get$hasAbsolutePath,4027,_mergePaths,3959,get$_mergePaths,3959,_re,4028,varData,4029,expand$1,3722,onComplete,4030,get$onComplete,4030,_get,4031,get$_get,4031,toUri,4032,get$toUri,4032,_fragmentStart,4033,_outgoingConnections,4034,hasPort,4035,get$hasPort,4035,_collection$_modified,4036,get$_collection$_modified,4036,runtime,4037,getLine,4038,get$getLine,4038,_countTabs$1,4039,_nativeAnchoredRegExp,4040,_waitsForCancel,4041,get$_waitsForCancel,4041,file,2764,run$1$1,4042,_captured_ex_0,4043,errorZone,4044,get$errorZone,4044,foundResult,4045,relative$2$from,3643,_onSuiteAddedController,4046,_captured_future_2,4047,run$0,4042,virtualChannel,4017,get$virtualChannel,4017,_setSourceStream$1,4048,_incomingConnections,4049,_setUpAlls,4050,getColumn$1,4051,_element,4052,_errorTest,4053,get$_errorTest,4053,_argumentsExpr,4054,schedule$1,4055,_collection$_captured_result_0,4056,_sinkCompleter,4057,_eventScheduled,4058,get$_eventScheduled,4058,_writeTrailingLines,4059,get$_writeTrailingLines,4059,removeTrailingSeparators$0,3703,rootLength$2$withDrive,4060,_live_suite_controller$_captured_liveTest_1,4061,normalize$1,4062,_closableKey,4063,_live_test_controller$_controller,4064,isSync,4065,_parent,4066,source,4067,scheduleMicrotask,302,get$scheduleMicrotask,302,_engine$_runLiveTest$3$countSuccess,4068,_isPendingComplete,4069,get$_isPendingComplete,4069,message$1,3370,_captured_line_0,4070,copyList$2,4071,_writeSurrogate$2,4072,_serializeTest,4073,get$_serializeTest,4073,_fillBuffer$3,4074,toJson$0,4075,_writeLastLine,4076,get$_writeLastLine,4076,_schedule,3622,get$_schedule,3622,addOutstandingCallback,4077,get$addOutstandingCallback,4077,_codeUnitAt$1,4078,chainFor$1,4079,_serializeTimeout$1,3642,~/,4080,get$~/,4080,print$1,412,_addHashTableEntry$2,4081,$package,4082,get$$package,4082,_stack_zone_specification$_registerBinaryCallback,3587,get$_stack_zone_specification$_registerBinaryCallback,3587,isHeadless,4083,completeError,4084,get$completeError,4084,verboseChain,4085,containsSeparator,4022,get$containsSeparator,4022,_addHashTableEntry,4081,get$_addHashTableEntry,4081,_declarer$_parent,4086,_stream_group$_subscriptions,4087,listen$4$cancelOnError$onDone$onError,4088,describeMismatch$4,4089,closed,4090,get$closed,4090,_streamQueue,4091,_runSetUps$0,4092,_captured_indent_0,4093,_checkPosition$3,4094,_prefix$1,4095,_linkStreamToController,4096,get$_linkStreamToController,4096,setSourceStream,4097,get$setSourceStream,4097,targetUrl,4098,_and,3516,get$_and,3516,peek$0,3725,__js_helper$_message,4099,inMilliseconds,4100,get$inMilliseconds,4100,_async$_previous,4101,set$_async$_previous,4101,_captured__convertedObjects_0,4102,_entries,4103,_setPendingEvents,4104,get$_setPendingEvents,4104,listen$2$onDone,4088,_captured_node_2,4105,addDescriptionOf,3664,get$addDescriptionOf,3664,__internal$_length,4106,_guardIfGuarded$1,4107,scan$1,3620,_runOnRelease,4008,get$_runOnRelease,4008,done,4108,get$done,4108,_platformVariables,4109,_isOnlyWhitespace$1,4110,isJS,4111,isEof,4112,sourceLine,4113,_runSetUps,4092,get$_runSetUps,4092,matchesErrorTest$1,3980,_f,4114,isBroadcast,4115,get$isBroadcast,4115,_captured_invoker_0,4116,_remote_listener$_box_0,4117,_conditional$0,3945,_tearDowns,4118,_close,4119,get$_close,4119,endsWith$1,3817,_nextId,4120,addAll,3652,get$addAll,3652,_captured_liveTest_0,4121,_engine$_closed,4122,_writeText,4123,get$_writeText,4123,matchAsPrefix,4124,get$matchAsPrefix,4124,_guarded,4125,_lazy_trace$_inner,4126,set$_lazy_trace$_inner,4126,_runGroup,3802,get$_runGroup,3802,__js_helper$_unlinkCell$1,4127,describe,3396,get$describe,3396,setSourceStream$1,4097,_failed,4128,_stack_zone_specification$_box_0,4129,_lazy_chain$_captured_this_0,4130,_colorize,3624,get$_colorize,3624,relativePathToUri,4131,get$relativePathToUri,4131,_isChained,4132,get$_isChained,4132,newJsMap$0,4133,_allocatedResources,4134,_color,4135,_mayAddEvent,4136,get$_mayAddEvent,4136,_group$_map$1,4137,_multi_channel$_captured_this_1,4138,_computeHashCode$1,4139,_complete$1,4140,_async$_captured_result_2,4141,_ensurePendingEvents,3716,get$_ensurePendingEvents,3716,_captured_e_1,4142,_null_stream_sink$_captured_this_0,4143,_captured_printZone_0,4144,_isAddingStream,4145,get$_isAddingStream,4145,_collection$_newLinkedCell,4146,get$_collection$_newLinkedCell,4146,_shrReceiverPositive,4147,get$_shrReceiverPositive,4147,fragment,4148,get$fragment,4148,_sendData,4149,get$_sendData,4149,_pauseCount,4150,runUnary$2$2,4151,_upgradedMap,4152,get$_upgradedMap,4152,_scanner,4153,precompiledPath,4154,_html$_target,4155,_hashCodeCache,4156,runGuarded,4157,get$runGuarded,4157,_lastSpan,4158,toList,4159,get$toList,4159,_verboseTrace,4160,_whenCompleteAction,4161,get$_whenCompleteAction,4161,relativePathToUri$1,4131,replaceAllMapped$2,4162,_as,4163,_captured_counter_3,4164,_nativeGlobalRegExp,4165,allMatches,3605,get$allMatches,3605,_soloEntries,4166,_writeTrailingLines$1,4059,addSubscription,4167,_writeSurrogate,4072,get$_writeSurrogate,4072,increment$0,4168,_captured_R_4,4169,_onResourceReleased$0,4170,_strings,4171,_handle,4172,hasListener,4173,get$hasListener,4173,_ensureDoneFuture$0,4174,_stack_zone_specification$_handleUncaughtError$5,4175,_captured_printZone_1,4176,_invokerZone,4177,_captured_transform_1,4178,evaluate,3548,get$evaluate,3548,_findBucketIndex$2,3756,pause$0,4179,_compareSets$5,3508,runUnaryGuarded$1$2,4180,spanFrom,4181,get$spanFrom,4181,fold,3509,get$fold,3509,_config,4182,_runner_suite$_onClose,4183,_zone,4184,get$_zone,4184,_stream_sink_completer$_controller,4185,set$_stream_sink_completer$_controller,4185,_captured_textInside_1,4186,pipe$1,3771,[],4187,get$[],4187,_lastProgressMessage,4188,matches$2,3347,_captured_fn_2,4189,_ensureController$0,3543,lastPendingEvent,4190,inMicroseconds,4191,get$inMicroseconds,4191,joinAll,3768,get$joinAll,3768,visitConditional$1,4192,__internal$_index,4193,_stream_sink_completer$_doneCompleter,4194,inSeconds,4195,get$inSeconds,4195,_onStateChangeController,4196,_cachedRuntimeType,4197,_captured_controller_0,4198,packageResolver,4199,_captured_eagerError_2,4200,_captured_T2_4,4201,_onClose,4202,_groups,4203,_addEventError$0,3669,convertNativeToDart_AcceptStructuredClone$2$mustCopy,2104,_duration,4204,_addHashTableEntry$3,4081,_decodedChars,4205,parents,4206,parent,4207,get$parent,4207,skipWhile,3389,get$skipWhile,3389,runGuarded$1,4157,join$1,3647,_jsObject,4208,_captured_test_0,4209,_stream_group$_state,4210,startsWith$2,4211,_set,4212,get$_set,4212,absolute$1,4213,foreign,4214,get$foreign,4214,_collection$_add$1,3541,_hasError,4215,get$_hasError,4215,merge,4216,get$merge,4216,registerUnaryCallback$2$1,3778,firstWhere$2$orElse,3948,handleValue$1,4217,resolve,4218,get$resolve,4218,_message,4219,bindCallbackGuarded$1,4021,_updateRequests,4220,get$_updateRequests,4220,_delegateCache,4221,_recordCancel,4004,get$_recordCancel,4004,hasFragment,4222,get$hasFragment,4222,traces,4223,get$traces,4223,isScheduled,4224,get$isScheduled,4224,_union_set$_iterable,4225,get$_union_set$_iterable,4225,parse$0,2447,_onCloseCompleter,4226,isNotEmpty,4227,get$isNotEmpty,4227,_test$_captured_body_0,4228,hasScheme,4229,get$hasScheme,4229,_closedBeforeDone,4230,_span_exception$_span,4231,_captured_cleanUp_1,4232,_source,4233,fuse$1$1,4234,_isNearCachedLine$1,3858,_live_suite_controller$_suite,4235,_runTearDowns,4236,get$_runTearDowns,4236,rootLength,4060,get$rootLength,4060,trim$0,4011,_captured_elements_0,4237,addStream$2$cancelOnError,4238,_precomputed3,4239,_completeError$1,3499,_checkEventAllowed,4240,get$_checkEventAllowed,4240,_captured_terse_0,4241,patterns,4242,_setEmpty$0,4243,_fragment,4244,createTimer,4245,get$createTimer,4245,variables,4246,_chainFuture$1,3708,_onDebuggingController,4247,_captured_protected_0,4248,every,4249,get$every,4249,_trace$_captured_trace_0,4250,location$1,3343,perform,4251,get$perform,4251,completeError$1,4084,_captured_bodyFunction_0,4252,_allowInvalid,4253,change$1$timeout,3651,_countTabs,4039,get$_countTabs,4039,_pool$_captured_this_0,4254,outputId,4255,_onResume$0,4256,_extraUnits,4257,toUpperCase$0,4258,__js_helper$_addHashTableEntry$3,3925,_equals_matcher$_value,4259,_controller,4260,_multi_channel$_captured_this_0,4261,_group,4262,_addStreamSubscription,4263,set$_addStreamSubscription,4263,_queue_list$_add$1,4264,forEachJsField,4265,get$forEachJsField,4265,glyphOrAscii$2,1931,_writeLastLine$1,4076,_regExp,4266,__js_helper$_nums,4267,_getTableBucket,4268,get$_getTableBucket,4268,_convert$_computeKeys,4269,get$_convert$_computeKeys,4269,_mapping,4270,millisecondsSinceEpoch,4271,get$millisecondsSinceEpoch,4271,_cell,4272,_checkNotBuilt$1,3751,connectOut$1,4273,_sets,4274,_onUnpaused,4275,get$_onUnpaused,4275,_genericClosure,4276,_mapContents,4277,_captured_indent_2,4278,_innerStreamSubscription,4279,startsWith,4211,get$startsWith,4211,context,2361,get$context,2361,substring$1,4280,matchAsPrefix$2,4124,entries,4281,_multi_channel$_inner,4282,_fillBuffer,4074,get$_fillBuffer,4074,hasRun,4283,get$hasRun,4283,forEachJsField$2,4265,_stack_zone_specification$_onError,4284,elapsedTicks,4285,get$elapsedTicks,4285,_onTestStartedGroup,4286,tearDownAll,4287,_matcher,4288,_engine,4289,_onSinkDisconnected,3561,get$_onSinkDisconnected,3561,containsKey,3801,get$containsKey,3801,_captured_future_0,4290,_captured_getTag_0,4291,error$3$length$position,2966,names,4292,internalContainsKey$1,4293,_runTearDowns$0,4236,_captured_start_2,4294,_addStreamState,4295,set$_addStreamState,4295,_extractStream,4296,get$_extractStream,4296,cancel$0,3476,setState$1,4297,substring,4280,get$substring,4280,clear,4298,get$clear,4298,errorCallback$3,3841,_stop,4299,_stream_group$_closed,4300,os,4301,hasPartialInput,4302,get$hasPartialInput,4302,_getBucket,4303,get$_getBucket,4303,_timeout,4304,parts,4305,set$parts,4305,_pathDirection,4306,get$_pathDirection,4306,describeTypedMismatch,3788,get$describeTypedMismatch,3788,findSlot$1,4307,_scheduleMicrotask,4308,get$_scheduleMicrotask,4308,set$_scheduleMicrotask,4308,_loadPool,4309,_canSendDirectly,4310,get$_canSendDirectly,4310,local,4311,get$local,4311,hour,4312,get$hour,4312,packageRoot,4313,get$packageRoot,4313,_endOfFileEmitted,4314,_captured_callback_0,4315,_captured_terse_1,4316,_future_group$_closed,4317,_convert$_value,4318,readSlot$1,4319,fork,4320,get$fork,4320,elementAt$1,4321,_runUnary,4322,get$_runUnary,4322,set$_runUnary,4322,writeAll$2,4323,_captured_callback_3,4324,_captured_arg_1,4325,_progressLine$2$color,3569,writeSlot$2,4326,_async$_next,4327,set$_async$_next,4327,toChain,3879,get$toChain,3879,_hashCode,4328,_onListen,4329,get$_onListen,4329,_captured_sourceResult_2,4330,runOnce$1,4331,_reviver,4332,_invoker$_captured_this_1,4333,toList$1$growable,4159,innerStream,4334,expect$2$name,539,_thunk,4335,_writeToList,3671,get$_writeToList,3671,_ensureListening$0,4336,_chains,4337,visitConditional,4192,get$visitConditional,4192,_captured_listener_1,4338,_failClosed,3939,get$_failClosed,3939,startColumn,4339,matchTypeError,3798,get$matchTypeError,3798,status,4340,toString$0,3379,_runLiveTest,3974,get$_runLiveTest,3974,_removeAfterFiring,4341,get$_removeAfterFiring,4341,_multi_channel$_box_0,4342,_eventType,4343,_head,4344,url,2360,resume,4345,get$resume,4345,__js_helper$_removeHashTableEntry$2,4346,_removeListeners,4010,get$_removeListeners,4010,codeUnitsEqual$2,3827,_match,4347,_isFiring,4348,get$_isFiring,4348,includeTags,4349,_pathDirection$2,4306,_stack_zone_specification$_registerBinaryCallback$3$4,3587,add,3861,get$add,3861,_current,4350,set$_current,4350,_selector2,4351,toString$1$color,3379,_rti,4352,_asyncComplete,4353,get$_asyncComplete,4353,request,4354,get$request,4354,_sendData$1,4149,_gray,4355,listen$3$onDone$onError,4088,matches,3347,get$matches,3347,_stack_zone_specification$_errorCallback,3648,get$_stack_zone_specification$_errorCallback,3648,toFilePath,3936,get$toFilePath,3936,setRange,3677,get$setRange,3677,_except,4356,set$_except,4356,_engine$_subscriptions,4357,_subscription_stream$_source,4358,set$_subscription_stream$_source,4358,_sdkRoot,4359,_recordResume$1,4360,scheme,4361,get$scheme,4361,_expectedUnits,4362,_captured_column_0,4363,convertDartToNative_PrepareForStructuredClone$1,1533,_closeGap$2,4364,_highlighter$_buffer,4365,_sink$_sink,4366,$call,3391,get$$call,3391,map$2$1,4367,_parser0$_scanner,4368,_bindCache,4369,__internal$_name,4370,_only,4371,set$_only,4371,removeEventListener$3,4372,_setTableEntry,4373,get$_setTableEntry,4373,_live_test_controller$_close$0,4374,toTrace$0,3829,_queryParameters,4375,resolveUri,3924,get$resolveUri,3924,__js_helper$_length,4376,_engine$_captured_this_1,4377,_pretty_print$_box_0,4378,addMapping$1,4379,absolute,4213,get$absolute,4213,_out,4380,_captured_R_3,4381,_async$_isClosed,4382,get$_async$_isClosed,4382,flush,4383,get$flush,4383,_captured_getMain_1,4384,_onStateChange,4385,get$_onStateChange,4385,_captured_K1_2,4386,toLowerCase$0,4387,heartbeat$0,4388,_writeFirstLine,3519,get$_writeFirstLine,3519,registerBinaryCallback$3$1,4389,_method,4390,_collection$_writeToList$1,4391,contains$1,4392,_writeSidebar$1$line,3613,removeOutstandingCallback$0,4393,_functions$_captured_value_1,4394,_prependListeners,4395,get$_prependListeners,4395,_core$_data,4396,_chain$_captured_callback_0,4397,_inner,4398,_fork,4399,get$_fork,4399,set$_fork,4399,_extractStream$0,4296,catchError,3514,get$catchError,3514,_nextIndex,4400,_captured_uri_0,4401,_maps,4402,_async$_addError,3655,get$_async$_addError,3655,_bufferIndex,4403,_red,4404,_unlinkCell,4405,get$_unlinkCell,4405,forEachObjectKey,4406,get$forEachObjectKey,4406,isRootRelative$1,4407,addStream,4238,get$addStream,4238,_restarted,4408,_captured_validVariables_1,4409,_doneFuture,4410,_original,4411,_registerCallback,4412,get$_registerCallback,4412,set$_registerCallback,4412,foldFrames$2$terse,4413,normalize$0,4062,_captured_channel_3,4414,_onMessageController,4415,separatorPattern,4416,_isFirstCharacter,4417,_errorZone,4418,contains,4392,get$contains,4392,addStreamFuture,4419,_built,4420,_requiredPositional,4421,_captured_test_2,4422,_table,4423,set$_table,4423,_deleteTableEntry$2,4424,split$1,4425,padLeft,3950,get$padLeft,3950,_group$_captured_callback_0,4426,_listen$1,4427,_parser$_mapUrl,4428,validatePlatformSelectors$1,4429,_shrBothPositive,3733,get$_shrBothPositive,3733,_async$_map,4430,get$_async$_map,4430,hasEmptyPath,4431,get$hasEmptyPath,4431,sublist,4432,get$sublist,4432,_stream,4433,_invoker$_onRun$0,4434,complete$1,3865,_subscribe,4435,get$_subscribe,4435,_captured_x_0,4436,_sourceStream,4437,set$_sourceStream,4437,_completeWithValue$1,3488,_chainStackTraces,4438,_setDestinationSink$1,4439,internalFindBucketIndex$2,3691,addTearDownAll$1,4440,_live_suite_controller$_isComplete,4441,_captured_result_0,4442,_captured_expectedElement_1,4443,getColumn,4051,get$getColumn,4051,_package_config_resolver$_uri,4444,_reverseListeners$1,4445,_collection$_last,4446,findSlot,4307,get$findSlot,4307,_captured_timeout_0,4447,walk$1,4448,_userInfo,4449,formatStackTrace$2$verbose,4450,replaceRange,3720,get$replaceRange,3720,_addResult$1,3555,remove,4451,get$remove,4451,_queue_list$_add,4264,get$_queue_list$_add,4264,_captured_listener_2,4452,__js_helper$_keys,4453,_encoder,4454,runBinary,4455,get$runBinary,4455,_cancelFuture,4456,_future_group$_captured_this_0,4457,handlesComplete,4458,get$handlesComplete,4458,_trace$_box_0,4459,_captured_pos_1,4460,sublist$2,4432,_invoker$_test,4461,get$_invoker$_test,4461,_metadata$_captured_timeout_2,4462,_guarantee_channel$_box_0,4463,cancelSchedule$0,4464,sourceNameId,4465,runtimeType,4466,get$runtimeType,4466,_collection$_modifications,4467,then,4468,get$then,4468,absolute$7,4213,_captured_maxLineLength_1,4469,_subsetMask,4470,_recordResume,4360,get$_recordResume,4360,_forEachListener,3699,get$_forEachListener,3699,testRandomizeOrderingSeed,4471,visitOr,4472,get$visitOr,4472,expect,539,get$expect,539,_lastProgressFailed,4473,internalContainsKey,4293,get$internalContainsKey,4293,splitMapJoin$2$onMatch,3935,_getTableBucket$2,4268,writeln,3897,get$writeln,3897,_convert$_first,4474,_expanded$_onError$3,3845,_removeListener$1,3981,millisecond,4475,get$millisecond,4475,_highlighter$_captured_this_1,4476,validatePlatformSelectors,4429,get$validatePlatformSelectors,4429,_preGrow$1,3547,configure$2$except$only,3484,request$0,4354,_rootRegisterUnaryCallback$4,4477,_async$_captured_f_1,4478,_remote_listener$_captured_this_0,4479,reversed,4480,get$reversed,4480,_queryParameterLists,4481,hasErrorCallback,4482,get$hasErrorCallback,4482,_scanOperator,3518,get$_scanOperator,3518,_captured_testOn_1,4483,split,4425,get$split,4425,left,4484,_listenToStream$1,3862,_live_test_controller$_isClosed,4485,get$_live_test_controller$_isClosed,4485,_codeUnitAt,4078,get$_codeUnitAt,4078,_highlighter$_captured_this_0,4486,_html$_start$0,4487,_chain,4488,get$_chain,4488,_addingStream,4489,_getTableCell$2,4490,forTarget$1,4491,_captured_minified_2,4492,connectOut,4273,get$connectOut,4273,peekChar,4493,get$peekChar,4493,_collection$_next,4494,_equals_matcher$_match$3,3946,cast$2$0,3866,build,4495,get$build,4495,_engine$_runLiveTest$2,4068,_segmentError,3830,get$_segmentError,3830,_process$1,4496,_precomputed2,4497,lastMatch,4498,get$lastMatch,4498,_description,4499,get$_description,4499,_captured_chainStackTraces_5,4500,startsWith$1,4211,_captured_printZone_5,4501,decoder,4502,get$decoder,4502,hasAuthority,4503,get$hasAuthority,4503,unaryOnError,4504,_needsNormalization$1,4505,_collection$_modified$0,4036,onResume,4506,build$0,4495,_or,3754,get$_or,3754,_printPath,4507,_tdivSlow$1,4508,withResource$1$1,3430,_writeText$1,4123,handleNext,3713,get$handleNext,3713,__js_helper$_rest,4509,_paused,4510,_tearDownAll,4511,get$_tearDownAll,4511,_captured_result_3,4512,_lazy_trace$_thunk,4513,waitForOutstandingCallbacks,4514,get$waitForOutstandingCallbacks,4514,_mayComplete,4515,get$_mayComplete,4515,_captured_T_5,4516,$function,4517,_offset,4518,_asyncComplete$1,4353,load$2$groups,3770,_captured_value_1,4519,runUnaryGuarded,4180,get$runUnaryGuarded,4180,bindUnaryCallbackGuarded$1$1,4520,_sendDone$0,4521,_outstandingCallbacks,4522,get$_outstandingCallbacks,4522,extensions,4523,_closeUnchecked,4524,get$_closeUnchecked,4524,_toListGrowable$0,4525,_captured_end_2,4526,_invoker$_captured_this_0,4527,_convert$_computeKeys$0,4269,codeUnits,4528,get$codeUnits,4528,mapStackTrace$1,487,_precomputed1,4529,_stack_zone_specification$_captured_f_1,4530,insert$2,3998,floor$0,3949,internalGet$1,4531,_names,4532,internalGet,4531,get$internalGet,4531,pattern,4533,_captured_stackTrace_1,4534,_liveSuite,4535,_collection$_newLinkedCell$1,4146,then$1$2$onError,4468,_consumeNewLine$0,4536,_live_test_controller$_close,4374,get$_live_test_controller$_close,4374,_newHashTable$0,4006,_metadata$_box_0,4537,suiteSink,4538,get$suiteSink,4538,_port,4539,_passedGroup,4540,_hostStart,4541,_isWithinOrEquals$2,4542,_unlisten,3908,get$_unlisten,3908,_expanded$_captured_liveTest_1,4543,_live_suite_controller$_closeMemo,4544,_captured_target_0,4545,_captured_host_1,4546,_uriCache,4547,_nativeAnchoredVersion,4548,get$_nativeAnchoredVersion,4548,relative,3643,get$relative,3643,_consumeWhitespace,4549,get$_consumeWhitespace,4549,_scanOr$0,4550,_tryResume,4551,get$_tryResume,4551,_runner_suite$_suite,4552,set$_runner_suite$_suite,4552,bind$1,2478,toSet,4553,get$toSet,4553,_captured_level_1,4554,sourceColumn,4555,_nextListener,4556,connectIn$2,4557,_isCaseSensitive,4558,get$_isCaseSensitive,4558,_progressLine,3569,get$_progressLine,3569,_body,4559,waitForOutstandingCallbacks$1,4514,_description$1,4499,encoder,4560,get$encoder,4560,metadata,4561,get$metadata,4561,_isInputPaused,4562,get$_isInputPaused,4562,inHours,4563,get$inHours,4563,allowRelease$1,4564,serialize,2631,get$serialize,2631,_captured_getUnknownTag_0,4565,_invoker$_completer,4566,intersection$1,4567,_ensureDoneFuture,4174,get$_ensureDoneFuture,4174,state,4568,get$state,4568,_state,4569,reportLiveTest$2$countSuccess,4570,_clearPendingComplete$0,4571,_captured_timeout_2,4572,_mapUrl,4573,_controllers,4574,forEachObjectKey$2,4406,fromUri$1,1988,_tearDownAllTrace,4575,_chain$_captured_T_1,4576,_highlighter$_box_0,4577,_multi_channel$_captured_T_2,4578,_subscriptions,4579,every$1,4249,_captured_V_2,4580,_captured_original_1,4581,formatStackTrace,4450,get$formatStackTrace,4450,_subscription_stream$_captured_this_0,4582,_newLinkedCell,3662,get$_newLinkedCell,3662,_stream_queue$_captured_this_0,4583,_runCalled,4584,_testCount,4585,onDone$1,4586,_isInitialState,4587,get$_isInitialState,4587,_stack_zone_specification$_registerUnaryCallback$2$4,3649,isEven,4588,get$isEven,4588,completeError$2,4084,_checkState$1,3930,_types,4589,get$_types,4589,_stack_zone_specification$_registerUnaryCallback,3649,get$_stack_zone_specification$_registerUnaryCallback,3649,_closeInnerChannel,3681,get$_closeInnerChannel,3681,root,4590,_writeIntermediateLines$1,4591,library,4592,get$library,4592,_multiLineComment$0,3928,_hasValue,4593,member,4594,get$member,4594,addStream$1,4238,_lastSubscription,4595,set$_lastSubscription,4595,_onListen$0,4329,dartException,4596,==,3446,get$==,3446,_schemeEnd,4597,__js_helper$_captured_this_0,4598,isPosix,4599,get$isPosix,4599,_hasSkipped,4600,heartbeat,4388,get$heartbeat,4388,_cancel$0,3695,_isUpgraded,4601,get$_isUpgraded,4601,_writeSidebar$0,3613,_print,4602,get$_print,4602,set$_print,4602,_tryResume$0,4551,_rootRegisterBinaryCallback$4,4603,_recordPause$1,3902,_useCapture,4604,firstMatch,3914,get$firstMatch,3914,_captured_port_0,4605,pause$1,4179,type,4606,get$type,4606,_scanIdentifier$0,4607,substring$2,4280,_onDone,4608,set$_onDone,4608,_computeKeys,3752,get$_computeKeys,3752,origin,4609,get$origin,4609,bindUnaryCallbackGuarded,4520,get$bindUnaryCallbackGuarded,4520,convert$1,3794,_multi_channel$_parent,4610,%,4611,get$%,4611,_keysArray,4612,get$_keysArray,4612,runUnary,4151,get$runUnary,4151,_sourceFile,4613,_captured_skip_3,4614,registerBinaryCallback,4389,get$registerBinaryCallback,4389,insertAll$2,4615,_platform_selector$_inner,4616,_captured_pp_0,4617,_engine$_captured_this_0,4618,_eventState,4619,child,4620,forTag,4621,_channel,4622,_captured_iterator_0,4623,checkMutable$1,4624,_captured_controller_3,4625,flush$2,4383,replaceFirst$2,4626,_binarySearch$1,4627,transform$1$1,4628,_mapper,4629,start$0,2491,replace$2$path$pathSegments,3540,_tdivFast$1,4630,_captured_oldPredicate_0,4631,_exception,4632,replace$1$pathSegments,3540,_captured_arg2_2,4633,toUri$1,4032,_suite,4634,isIdentifier,4635,get$isIdentifier,4635,convertNativeToDart_AcceptStructuredClone,2104,get$convertNativeToDart_AcceptStructuredClone,2104,_primary,4636,then$1$1,4468,removeAt,4637,get$removeAt,4637,_captured_div_1,4638,__internal$_current,4639,set$__internal$_current,4639,__js_helper$_current,4640,set$__js_helper$_current,4640,_removeEventListener,4641,get$_removeEventListener,4641,_optionalNamed,4642,onDone,4586,get$onDone,4586,reportLiveTest,4570,get$reportLiveTest,4570,_isSent,4643,get$_isSent,4643,_receiver,4644,forPlatform$1,4645,_runner_suite$_close,3758,get$_runner_suite$_close,3758,_delegate,4646,get$_delegate,4646,firstPendingEvent,4647,inSameErrorZone$1,4648,expect$1,539,intersection,4567,get$intersection,4567,horizontalLine,1933,get$horizontalLine,1933,frames,4649,get$frames,4649,onError,3472,get$onError,3472,_span,4650,_captured_channel_2,4651,group,587,get$group,587,_currentExpansion,4652,set$_currentExpansion,4652,_union_set_controller$_sets,4653,_async$_captured_this_0,4654,_serializeTest$3,4073,_isUnicode,4655,get$_isUnicode,4655,_shlPositive,3542,get$_shlPositive,3542,_lazy_trace$_captured_terse_2,4656,spanFor,3884,get$spanFor,3884,peekChar$0,4493,_writeSidebar,3613,get$_writeSidebar,3613,_expanded$_onDone,3983,get$_expanded$_onDone,3983,_span_exception$_source,4657,__internal$_start,4658,_modifications,4659,_nextPosition,4660,_toEncodable,4661,hasMatch$1,4662,_bind,3840,get$_bind,3840,_canFire,4663,get$_canFire,4663,postMessage$1,4664,_setPendingEvents$1,4104,internalComputeHashCode,4665,get$internalComputeHashCode,4665,_isOnlyWhitespace,4110,get$_isOnlyWhitespace,4110,_outstandingCallbackZones,4666,_captured_zone_1,4667,_isWithinOrEqualsFast$2,3550,rootPattern,4668,_captured_id_1,4669,pause,4179,get$pause,4179,_platform_selector$_span,4670,decode$1,3791,replaceFirst,4626,get$replaceFirst,4626,isUtc,4671,_postMessage_2$1,4672,set,4673,get$set,4673,pathFromUri,4674,get$pathFromUri,4674,_hasOneListener,4675,get$_hasOneListener,4675,_activeLoadTests,4676,_containsTableEntry$2,4677,_restartable_timer$_timer,4678,_recursiveMatch,3612,get$_recursiveMatch,3612,_pendingEvents,4679,get$_pendingEvents,4679,_addError$2,4680,_captured_prototypeForTag_0,4681,_nullErrorHandler$1,4682,_captured_cleanUp_3,4683,+,4684,get$+,4684,_chainSource,4685,get$_chainSource,4685,_complete,4140,get$_complete,4140,_portStart,4686,_isWithinOrEquals,4542,get$_isWithinOrEquals,4542,_printZone,4687,_multi_channel$_captured_T_1,4688,_captured_frame_0,4689,_columnStart,4690,_functions$_captured_result_0,4691,_addLiveSuite$1,4692,_pattern,4693,_onStreamDisconnected$0,4694,describeMismatch,4089,get$describeMismatch,4089,_stackTrace,4695,elapsed,4696,get$elapsed,4696,_process,4496,get$_process,4496,_closeChannel$2,3493,accept$1,4697,_containsTableEntry,4677,get$_containsTableEntry,4677,_async$_subscription,4698,get$_async$_subscription,4698,_packageResolver,4699,_eventsReceived,4700,_isMultiLine,4701,get$_isMultiLine,4701,_errors,4702,_index,4703,onZero,4704,get$onZero,4704,internalRemove,3937,get$internalRemove,3937,_captured_chain_0,4705,_runSkippedTest,4706,get$_runSkippedTest,4706,take$1,3568,visitAnd,3706,get$visitAnd,3706,_captured__prettyPrint_1,4707,_shrReceiverPositive$1,4147,removeAll$1,4708,apply,3966,get$apply,3966,_captured_arg1_1,4709,_callOnCancel$0,4710,firstWhere$1,3948,spanFrom$1,4181,_binarySearch,4627,get$_binarySearch,4627,_computeHashCode,4139,get$_computeHashCode,4139,_onCompleteGroup,4711,last,4712,get$last,4712,_getNext,3894,get$_getNext,3894,_channelNames,4713,sourceUrl,4714,get$sourceUrl,4714,_async$_onListen$1,4715,_resetTimer,4716,get$_resetTimer,4716,runBinary$3$3,4455,pathsEqual$2,3576,_currentTrace$1,3566,perform$1,4251,_liveSuites,4717,_stream_channel_completer$_set,4718,_file$_end,4719,forPlatform,4645,get$forPlatform,4645,_captured_value_2,4720,remove$1,4451,_gatherCoverage,4721,_isHttps,4722,get$_isHttps,4722,_closeUnchecked$0,4524,_captured_result_1,4723,removeFragment,3911,get$removeFragment,3911,_closedIds,4724,_captured_channel_0,4725,isNewLine,4726,handleUncaughtError$2,3603,elementAt,4321,get$elementAt,4321,highlight,3871,get$highlight,3871,_runCount,4727,close,3870,get$close,3870,_add,3797,get$_add,3797,_union_set$_captured_this_0,4728,_runSkipped,4729,_skipped,4730,_stack_zone_specification$_captured_R_2,4731,_writeIntermediateLines,4591,get$_writeIntermediateLines,4591,_isPackage,4732,get$_isPackage,4732,_findLine$1,3524,_expectsEvent$1,4733,_validateTags,4734,get$_validateTags,4734,copy,4735,_captured_error_2,4736,bottomLeftCorner,1937,get$bottomLeftCorner,1937,_addPending$1,4737,_stringSink,4738,expand,3722,get$expand,3722,_get$1,4031,_progressLine$1,3569,toRadixString,3971,get$toRadixString,3971,resolve$1,4218,insertAll,4615,get$insertAll,4615,remainder$1,4739,_inAddStream,4740,get$_inAddStream,4740,_pool$_captured_registered_1,4741,_captured_serialized_0,4742,_addError$1,4680,_onReleaseCompleters,4743,_functions$_captured_result_1,4744,inGoogle,4745,runBinaryGuarded$2$3,4746,_allowErrors,4747,_captured_body_0,4748,registerBinaryCallback$3$2,4389,_createSubscription$4,4749,condition,4750,map,4367,get$map,4367,checkGrowable$1,4751,moveNext,3558,get$moveNext,3558,absolutePathToUri$1,4752,_runtimes,4753,_captured_loadResource_2,4754,_collection$_rest,4755,_addError,4680,get$_addError,4680,registerCallback$1$2,3602,_setRemoveAfterFiring$0,4756,_sink,4757,set$_sink,4757,_captured_retry_6,4758,internalComputeHashCode$1,4665,_linkStreamToController$0,4096,_context$_current,4759,_onResume,4256,get$_onResume,4256,_registerBinaryCallback,4760,get$_registerBinaryCallback,4760,set$_registerBinaryCallback,4760,_ensureListening,4336,get$_ensureListening,4336,_stream_completer$_controller,4761,set$_stream_completer$_controller,4761,isCore,4762,get$isCore,4762,_setDestinationSink,4439,get$_setDestinationSink,4439,_captured_line_1,4763,_invoker$_onRun,4434,get$_invoker$_onRun,4434,getOffset,4764,get$getOffset,4764,listen$1,4088,indexOf,3530,get$indexOf,3530,_createPeriodicTimer,4765,get$_createPeriodicTimer,4765,set$_createPeriodicTimer,4765,_pool$_timeout,4766,mustCopy,4767,_firstSubscription,4768,set$_firstSubscription,4768,_scanner$_next,4769,foldFrames,4413,get$foldFrames,4413,_callOnCancel,4710,get$_callOnCancel,4710,onTestStarted,4770,get$onTestStarted,4770,_scanAnd$0,4771,__js_helper$_removeHashTableEntry,4346,get$__js_helper$_removeHashTableEntry,4346,_sendDone,4521,get$_sendDone,4521,_once,4772,absolutePathToUri,4752,get$absolutePathToUri,4752,_query,4773,config,4774,get$config,4774,_future,4775,removeFirst,4007,get$removeFirst,4007,_subscribe$4,4435,next,4776,get$next,4776,set$next,4776,_removeEventListener$3,4641,style,4777,_invoker$_tearDowns,4778,map$1$1,4367,_contains$1,3632,__js_helper$_getBucket$2,4779,_execGlobal,3731,get$_execGlobal,3731,_removeHashTableEntry$2,3976,complete$0,3865,_stopwatch,4780,decode,3791,get$decode,3791,addOutstandingCallback$0,4077,internalSet,3764,get$internalSet,3764,_getBucket$2,4303,join$0,3647,toSet$0,4553,_active,4781,_rootRun$4,4782,_remove,3736,get$_remove,3736,decrement,3777,get$decrement,3777,_guardCallback,4783,get$_guardCallback,4783,_onTestStartedController,4784,isAbsolute$1,3724,path,4785,get$path,4785,_async$_target,4786,_handleError,3633,get$_handleError,3633,_captured_K2_0,4787,_knownTags,4788,_tick,4789,_onReleaseCallbacks,4790,schedule,4055,get$schedule,4055,_captured_suite_0,4791,_pathStart,4792,_platform_selector$_captured_platform_0,4793,_restartable_timer$_duration,4794,_captured_portSubscription_1,4795,_badEventState,3968,get$_badEventState,3968,_guarantee_channel$_captured_this_0,4796,setEmpty$0,4797,_invalidPosition$3,4798,visitVariable,3564,get$visitVariable,3564,_containsKey$1,4799,_lastMatchPosition,4800,_captured_onPlatform_8,4801,_captured_predicate_1,4802,_addListener$1,4803,_mainController,4804,_colorize$2$color,3624,_guarantee_channel$_captured_this_1,4805,_invoker$_box_0,4806,_length,4807,typedMatches,3557,get$typedMatches,3557,_sendError,4005,get$_sendError,4005,nextKind,4808,get$nextKind,4808,_collection$_box_0,4809,isRootRelative,4407,get$isRootRelative,4407,_subscription,4810,set$_subscription,4810,addTo$1,4811,_tdivFast,4630,get$_tdivFast,4630,_uri,4812,isDone,4813,get$isDone,4813,_stateData,4814,setChannel$1,4815,_writeSidebar$1$end,3613,_captured_R_2,4816,_stream_channel_completer$_channel,4817,set$_stream_channel_completer$_channel,4817,runes,4818,get$runes,4818,_jsWeakMapOrKey,4819,_containsKey,4799,get$_containsKey,4799,_trimVMChain$1,3639,_queue,4820,_null_stream_sink$_closed,4821,_lineStarts,4822,listen,4088,get$listen,4088,_eventQueue,4823,bindCallback$1$1,3565,_engine$_runLiveTest,4068,get$_engine$_runLiveTest,4068,isSeparator$1,4824,_arguments,4825,_disconnected,4826,_validateTags$0,4734,_context,4827,_createSubscription,4749,get$_createSubscription,4749,_captured_stream_1,4828,_indexFor$2,4829,scheduleMicrotask$1,302,_cancelOnError,4830,get$_cancelOnError,4830,runOnce,4331,get$runOnce,4331,_tdivSlow,4508,get$_tdivSlow,4508,needsSeparator,3710,get$needsSeparator,3710,rest,4831,get$rest,4831,_rest,4832,errorCallback,3841,get$errorCallback,3841,rootLength$1,4060,_captured_stackTrace_2,4833,downEnd,1915,get$downEnd,1915,group$9$onPlatform$retry$skip$solo$tags$testOn$timeout,587,_onPause,3984,get$_onPause,3984,release$0,3757,_guardCallback$1,4783,forEach$1,3005,_stream_subscription$_source,4834,_checkEventAllowed$0,4240,_stack_zone_specification$_run$1$2,3958,_toFilePath$0,4835,sourceUrlId,4836,_shrOtherPositive,4837,get$_shrOtherPositive,4837,_closed,4838,_onIdleController,4839,_value,4840,_captured_error_1,4841,writeCharCode$1,4842,_close$0,4119,_tearDownAlls,4843,addError,3878,get$addError,3878,_captured_sourceMap_1,4844,walk,4448,get$walk,4448,_self,4845,onStateChange,4846,get$onStateChange,4846,addError$2,3878,_timeoutTimer,4847,currentChain$1,4848,_print_sink$_buffer,4849,_live_test_controller$_suite,4850,string,4851,_engine$_captured_liveTest_1,4852,isBrowser,4853,getOffset$1,4764,_suiteController,4854,_canonicalRecipe,4855,_canceled,4856,get$_canceled,4856,_nativeGlobalVersion,4857,get$_nativeGlobalVersion,4857,_pending,4858,set$_pending,4858,_queue_list$_tail,4859,_createTimer,4860,get$_createTimer,4860,set$_createTimer,4860,passed,4861,get$passed,4861,_onData,4862,set$_onData,4862,change$2$skip$skipReason,3651,_getTableCell,4490,get$_getTableCell,4490,_captured_action_1,4863,_captured_body_1,4864,_stream_group$_captured_this_0,4865,_captured_liveTest_1,4866,addEventListener,3366,get$addEventListener,3366,_captured_beforeLoad_4,4867,spanFor$3$files,3884,_printsOnFailure,4868,merge$1,4216,_writeAuthority$1,4869,_runSkippedTest$3,4706,spanFor$3$uri,3884,current,621,get$current,621,_onTimeout$0,4870,_onStateChange$2,4385,startChunkedConversion$1,4871,next$0,4776,accept,4697,get$accept,4697,handleWhenComplete$0,4872,_addStreamCompleter,4873,_group$_map,4137,get$_group$_map,4137,_captured_K_1,4874,runBinaryGuarded,4746,get$runBinaryGuarded,4746,_toNonSimple$0,3743,normalize$3,4062,getOffset$2,4764,clear$0,4298,inputId,4875,cloneNotRequired$1,4876,first,4877,get$first,4877,matches$1,3347,_handleError$2,3633,shuffle$1,4878,_pauseCompleter,4879,newJsList$1,4880,createTimer$2,4245,_registerUnaryCallback,4881,get$_registerUnaryCallback,4881,set$_registerUnaryCallback,4881,key,4882,_first,4883,_captured_T_1,4884,name,4885,get$name,4885,_simpleMerge$2,3973,_postMessage_1$2,4886,_addEventListener$3,3922,_resetTimer$0,4716,_consumeWhitespace$0,4549,_keys,4887,_live_test_controller$_run$0,3640,allMatches$1,3605,onPause,4888,fork$2$specification$zoneValues,4320,_captured_data_1,4889,isSeparator,4824,get$isSeparator,4824,withResource,3430,get$withResource,3430,hasQuery,4890,get$hasQuery,4890,_nums,4891,_setErrorObject$1,4892,_collection$_source,4893,handlesValue,4894,get$handlesValue,4894,_progressLine$3$color$suffix,3569,_stack_zone_specification$_captured_R_3,4895,_captured_channel_1,4896,run,4042,get$run,4042,getLine$1,4038,_stack_zone_specification$_registerCallback$1$4,3692,seen,4897,_lazy_chain$_inner,4898,set$_lazy_chain$_inner,4898,_selector1,4899,_consumeNewSegment$0,4900,lastIndexOf$2,3844,_urlSafe,4901,input,4902,_subscription_stream$_captured_stackTrace_2,4903,_reverseListeners,4445,get$_reverseListeners,4445,normalize,4062,get$normalize,4062,isRunning,4904,get$isRunning,4904,postMessage,4664,get$postMessage,4664,round,3839,get$round,3839,_deleteTableEntry,4424,get$_deleteTableEntry,4424,prettyUri$1,3506,_trace$_captured_level_1,4905,_selector,4906,virtualChannel$1,4017,_pause$0,4907,_isScheme$1,4908,_onStreamDisconnected,4694,get$_onStreamDisconnected,4694,_parsed_path$_captured_this_0,4909,_declarer$_captured_this_0,4910,_isSourceStreamSet,4911,get$_isSourceStreamSet,4911,_local,4912,set$_local,4912,length,4913,get$length,4913,set$length,4913,separator,4914,get$separator,4914,_captured_T_3,4915,_captured_handleError_0,4916,toList$0,4159,_serializeGroup$3,3749,fillRange,3701,get$fillRange,3701,_setChained$1,4917,_yellow,4918,visitOr$1,4472,_captured_location_2,4919,describe$1,3396,removeLast,3819,get$removeLast,3819,registerCallback,3602,get$registerCallback,3602,validate,3977,get$validate,3977,cleanupSlots$0,4920,max$2,470,_captured_depth_3,4921,_jsTrace,4922,codeUnitAt$1,3808,_captured_dispatch_1,4923,_end,4924,__js_helper$_unlinkCell,4127,get$__js_helper$_unlinkCell,4127,_captured_matcher_0,4925,addTo,4811,get$addTo,4811,_modificationCount,4926,_processed,4927,_timer,4928,_writeString$1,4929,_cloneResult,3638,get$_cloneResult,3638,_captured_platform_1,4930,_fail$1,4931,isClosed,4932,get$isClosed,4932,keys,4933,get$keys,4933,isPaused,4934,get$isPaused,4934,pathFromUri$1,4674,_onCancel,3575,get$_onCancel,3575,_captured_engine_0,4935,_set$2,4212,_setUps,4936,_expanded$_sink,4937,_grow$0,4938,text,4939,get$text,4939,any,3793,get$any,3793,_findColumn$3,4009,_onErrorController,4940,_text,4941,_captured_this_0,4942,copyList,4071,get$copyList,4071,_prependListeners$1,4395,handlesError,4943,get$handlesError,4943,_contents,4944,_captured_milliseconds_1,4945,_updateRequests$0,4220,_captured_sdkLib_3,4946,_initializeText$0,4947,_captured_trace_0,4948,_evalCache,4949,_closeGap,4364,get$_closeGap,4364,_limit,4950,_toggleEventId$0,4951,_live_suite_controller$_active,4952,_addLiveSuite,4692,get$_addLiveSuite,4692,_createNode$1,4953,copy$0,4735,toSpec$0,4954,userInfo,4955,get$userInfo,4955,_addListener,4803,get$_addListener,4803,excludeTags,4956,_indexFor,4829,get$_indexFor,4829,_separatorIndices,4957,setState,4297,get$setState,4297,_captured_completer_1,4958,_is,4959,_shrOtherPositive$1,4837,_isUsed,4960,putIfAbsent$2,3873,_paddingAfterSidebar,4961,get$_paddingAfterSidebar,4961,code,4962,sink,4963,get$sink,4963,_captured_skipReason_7,4964,_destinationSink,4965,set$_destinationSink,4965,_values,4966,_suite$_metadata,4967,_addLoadSuite$1,4968,_captured_message_1,4969,_compareIterables,3860,get$_compareIterables,3860,values,4970,get$values,4970,_nativeRegExp,4971,chainFor,4079,get$chainFor,4079,_setTableEntry$3,4373,removeAt$1,4637,_isDefined,4972,_isDone,4973,resume$0,4345,_isDotAll,4974,get$_isDotAll,4974,_unlinkCell$1,4405,offset,4975,get$offset,4975,putIntoObject$3,4976,copies,4977,print,412,get$print,412,_runBinary,4978,get$_runBinary,4978,set$_runBinary,4978,_addPending,4737,get$_addPending,4737,_start,4979,_onTestStarted$1,3482,_stream_queue$_source,4980,year,4981,get$year,4981,_requestQueue,4982,_needsNormalization,4505,get$_needsNormalization,4505,$le,4983,$indexSet,4984,$index,4985,$xor,4986,$or,4987,$eq,4988,$mod,4989,$shr,4990,$and,4991,$ge,4992,$gt,4993,$lt,4994,$mul,4995,$not,4996,$shl,4997,$div,4998,$sub,4999,$add,5000,$negate,5001,$tdiv,5002"},"frames":"qsTAyEiB+2EuH;eAEFmduF;mXEqKbndAAAAAwR,A;iQAuHeAsI;eAEFmdwG;uEAohBwBnd8B;qEAEFAqC;oEAEDAoC;w7GGrpBvBAyEAhOXA6B,A;u1CC+YE4hF8F;qxCA0MSCuB;uCAAAA6B;wIAqBeC6B;0YAYjBD4C;uyHA4NQ7tBgB;+5DA8Ya/zD4C;igBAmBjByIyD;oFAOEAyD;eAIJzI2C;iEASAA6C;uPAmCcDoC;urCA4QZA+S;wnJAgSHAoE;iJAKKA0B;qLAWaAwH;oLASbA0B;sOAiCmBA4B;6HAGtBA4B;iRAmCCxzEAIn4BNu1JkF,gE;wEJq4BKC+C;6CACEA4B;0IAUDx1JAIh5BNu1J8D,yD;oDJk5BMC4B;2kBAmBHhiFAKj9D0BA8E,A;q1BLqlEtBA6D;AAEAA0Q;w+IAwPmCiiF0C;8EAAAA+E;wZAYXAwC;8EAAAAuD;49DAkGJA+J;AACICyL;w6CAsI7B70JACnsEF80JiD,sB;0GDusEE90JACvsEF80JiD,sB;qQDyuEcniF+G;0iCAgbMoiFgG;+dA6BjBpiFqC;qNAqBAAkD;m4BIt6FN+hFgC;OAEKtzJmBArBwB4zJY,wC;wEAyBVAM;wDAKnBCqC;sYAOACwF;AAhBK9zJAAtBFszJiB,A;AAsCHQ2B;sFAIACgE;AApBK/zJAAtBFszJiB,A;AA0CHS2B;iCACiB9zJAAwYd8zJsE,A;AA7ZE/zJAAtBFszJmB,A;AA2CcrzJAAwYd8zJ6B,2C;8xCA9VC5zJAAq2BN6zJqB,A;4zDAhxBKViC;0BAGmB/hFuC;4DACWxzE0C;4EAGpBw1J4C;8PAqDsBlzJAHmW7B4zJ6BAjBR3zJAA3I8B4zJuD,A,A,A;OGvMO7zJ+D;6NAWXizJkC;AACbaiE;qDAEXbyC;iFASKa0C;gJA0HLt0Ja;0BAAAA2D;oKAygBwByzJkC;gnEF/1BnB1yJAA2BTwzJoI,A;kTAZSxzJAAYTwzJoI,A;6yCApEuBlzJAAzChBmzJwE,A;0zDA+QSjzJsL;0gCKjNhBmwEAAAAAqG,A;+WCpESusEwB;+IAGyBnbkB;0bA0BhBxsDO;qfAuBPm+E+F;sCACFCiC;gBAGTCqF;6vBA2CODiC;0YAyBPhkDADKQh/B4B,mE;2CCLRlwDsC;wCACEi/Fc;+EACAAI;2EAGFAc;kmBA8DkCm0CgB;AAA1BFoC;0EAGRCqF;grPP/KkDEyC;AAA3BCY;AACfCyE;AADeD2B;m8CAySPEmB;qDAGVCmD;AAEW3/E0H;4FAIT4/EuD;yiBAcOCiC;+WAaMCAAqnCiBCAA/C9B//EiH,A,A;OAtkCa8/EuE;2PAMAGAAqnCjBCAAnDIlgFiH,A,A;OAlkCaigFuE;2PAMAGAAqnCjBCAAvDIrgFiH,A,A;OA9jCaogFuE;uUAQkCG2C;+HAGpCC+C;geAUMCqD;wvBAiBNbsD;mUAOCcgD;qiCAgCGC6B;ymCA+B0BCAA5ORxkFkD,A;AA6OrBykFmE;AAEACmE;AAEACmD;ycAgCd51JAAzB0B4zJsB,A;+NAgD5BxwJAAnD+BwwJmB,A;AAoD1BtwJ0B;mhCA8EkBtDAA/HK4zJoM,A;8MAoJpBrxJqF;2LAoBRszJsB;AADG5kFgB;8EAOkB1uE4C;iHAsCVmyJ6D;AAKXtrJ0E;AAIE0sJiB;AADACQ;yDAGalhFgI;+UAaGugF2DAxlBGYqB,A;6FAkmBfC4H;AAMNCmB;+HAWGCgC;6IAQOCiC;uBAKVhzJAAvR+BwwJiB,A;83BA8T/BuC8E;u2BA6BgBvjFiC;sOAQAAiC;+1IAmKXxpE8C;oPAUYksJkEAp1BDUiB,A;mnDAk5BLtBiC;gQASU2BkC;wGAKICkC;wGAKAlCkC;sHAKTgBkC;8VAcSXmC;4IAOXc2CA36BISsC,A;+4BA0mCLO4F;u4BAwCCCmB;8CAGVC+B;+UAUUlCmB;8CAGVC+C;AAEuB3/E0H;6jBAgDZAkI;AACjB6hF0B;AASIXQ;yCACADW;2CACAIQ;yPAyDGSAAIOlBAArvCHxkF4C,A,AAsvCP2lFqB,GACAC2C,A;gQAwBGCAAKOrBAApxCHxkF4C,A,AAqxCP2lFwB,AACAG4B,GACAF2C,A;qSASGGAAKOvBAAryCHxkF4C,A,AAsyCP2lFsB,AACAGyB,GACAF2C,A;2QASqBhiFiI;wTAYF2gF4B;OAEE3gFiI;mKAqBZoiF2K;iFAINCAAKOzBAA71CHxkF4C,A,AA81CP2lFqB,AACAG4B,AACAIyC,wBAGE7CsC,GAEFuC6B,A;8LAuBInCc;yBACQWoE;uKAIH+BAAlBEviF0H,kF;iFAsBRwiFAAKO5BAA14CHxkF4C,A,AA24CP2lFsB,AACAG8B,AACAI6B,GACAN6B,A;6TA8CSSAAxCPziFyJ,AACJ0iF2xB,gC;mFA2CKCAAKO/BAAr8CHxkF4C,A,AAs8CP2lFsB,AACAGiC,AACAI2B,GACAN+B,A;oMAaSYAAPP5iF0H,sE;mFAWC6iFAAKOjCAA/9CHxkF4C,A,AAg+CP2lFsB,AACAGuC,AACAIuB,GACAN+B,A;4ZAoKOcoD;itBAuBCCyL;qDAIkBrBiE;qDAIAsBiE;sDAIACiE;2DAItBCAAqERCmC,AACACuB,A;2DAlEQCkBA4GWC2BAmFACmB,4DAEnBHsC,A,imB;sPAtLsBtDAAjccCAA/C9B//EwI,A,+D;2IAwfgBigFAAncpBCAAnDIlgFwI,A,+D;2IA8fgBogFAArcpBCAAvDIrgFwI,A,+D;2DAigBEkjFAAiCRCmC,AACACuB,A;2DA9BQIkBAiG6C5CAApyDdxkFqJ,A,meA6zDvBknF2BAiCGCmB,4DAEnBHgD,A,AApCoBvCsD,AAEACmE,AACAC0D,wH;2DAxHZmCAAyBRCmC,AACACuB,A;0CAtBQKAA0HYH2BAyBDCmB,4DAEnBHqG,A,A;4DAjJQFAAiBRCmC,AACACuB,A;2CAdQMAAwHYC2BA0BDJmB,iEAEnBHqG,A,A;o9BAhHoDQsD;wJAUpCC4DAroBDhEgB,2BAEKW2F,AAGODiD,2X;0JAktBHuD4D;2DAIAC4D;uhCAsDTlEiC;wDAEcWqC;+OAQTA4D;w7BAuGJEwC;8GAKFbc;6CA6BI2BuC;yGAUJAkC;MAAAAAAxoEILmB,A;iGAipEA5BuC;sOAYIkCuC;0HAWKlCkC;oOAYIkCkC;4rBA4BS7B8CAvrEtBuBmB,A;AAwrEVvBwC;s9BAsBYa4CAxtEFUiB,A;AAytEEVsC;gkCA4BHuDa;iMAQAAa;+NASAAuC;oZAoBDC4B;+NAEJDiB;mSASKzD8C;iBACAA8C;gSAkBJyDyB;AACAAe;80BA0ECnEc;4NAYJU0CAp7EaYqB,A;AAo7EeZyC;2LAMxBC2CA96EIWqB,A;AA86EoBX0C;yJAMxBC2CA35EIUqB,A;AA25EmBV0C;yJAC/ByD6O;4DAIiBtE2CAt5ELuBqB,A;AAu5EJvB0C;qrCAqDDCc;+JAMON0C;w2FK1kFJ4EqG;oiBA+BhBC8C;grZUpF4B3+BuD;sMAmB9BrpD+C;4HAeAgNwD;4EAiFWhN2BA7BSioFAAAAjoFeE0BKlwDgB,2B,A,sC;2JFkBR4pFQ;y1BA2DM15BwBE7EElwDwD,A;AF6EFo4IqE;mdAiCXp4I0C;sJI9JckwD4BFgDDlwDgB,iC;AE/CjB61G4B;8IAyBkB3lD4BFsBDlwDgB,iC;saEORmwDoBFUmCnwDgB,uCAVpDo7DAAUAAAATEg9E6D,A,A,A;sKEEmBloFAFVIlwDwB,8D;gKEcfxVAAsrBuC0lEwC,A;0QA9oB3BlwDkC;8LAGRxVAA2oBmC0lEmE,A;qCAvoBpC09BoBFjDM5tFgB,iCADnB4tFAAAAAmD,A,A;6EE4HsC19B0KFvIblwD6D,A;kkBEuMRmwDAAlKJ27BoBFnCwC9rF+B,AAArD8rFAAAAAqB,A,A,A;uhBE0Q0BoDANsHMh/BiC,+BAAAAsC,A;iHMnFLA0CF/SFlwDoH,A;4TEgrBKAyD;mGAEpBxVAAkBuC0lEiE,A;uMF7rBjDkLAAAAAAACEg9EkF,A,A;oIAiOeCoB;sDAIfCqBArIOC4B,iC;6dAqKOCiE;uCAEICyF;uRAQhBCqBAvOKF6B,iE;ycAmUSHkC;8BACSMuB;+DAGYC2D;yjBA8BV3NkB;iEAaNt0EmB;kCAZIkiF8C;uDACGldiG;AAWPhlEsB;sDATgBiiFiBAvS9BD4B,2D;oIA8SiB34I0B;+BAEH22DqH;yFAoEJk5DkB;+lBAuBOwoB8B;AACFSiBA5SdT4B,iK;AA6SEUiBA/WFV4B,iBACMA+B,sG;gMA2XASqBA1TNTsB,sK;gFA+TJWAA9YIXsB,A;qBA2YJDsK;yBAGAYoH;mqCIrrBQCoD;+GAYqB/oF+D;4HAIrB+oFoD;yXAsBkB/oFyD;qZAwCPlwD2B;gNASrB27HM;iCAAAAgD;qLAKC37HsB;8HCuBKkwD+J;+JAQUy9BAC4SQz9BuE,A;+ED7OfA2B;wDAAAAsC;qGAs6DLA4D;OAAAAuD;qIClkEIAwG;AACAAuG;6TAwtBLlwDwD;0PChtBiB64IAAAA74I0D,A;sGAgBxBkwDqF;wKA+cKlwD6D;qXChjB2BA8C;wHAIhBxVAN84BiC0lE6F,A;u9BCj3BtClwDsB;2pDFwjCJk5IsE;weAYAAsE;wiBAYAAsE;2vBA0BmBvdc;iCAAAAwB;uoBA0BbwdAH/jCiB5/BmD,yB;OG+jCjB4/BoE;sEAIbCc;gFAIKp5IqC;4hBA0BMkwD8CArXXAAAAAAgQAcYA4D,gIAGAA4D,uIAGAA4D,sHAIAAmH,kUAeAAkG,sKAMAA6G,wC,A,A;u/BA4kBc6Mie;ygBAkBnB/8D0E;6/gBSr8CYkwD8C;+CAAAAwD;0cA4dAmpFAC/cPnpF4C,A;4GD+cOmpFAC/cPnpF+D,A;sMD4e+BmpFAC5e/BnpFuF,A;iFDofCmpFACpfDnpFqF,A;sEDupCOAqD;6EA8BmBAqD;sJAKPAuD;qGAqU7BkJ8H;2MA2GFlJAAAAAoD,A;u8BGz6CUo3DiH;wQAqBcp3DkD;iGAGpBo3DiG;oRAMKh7BkB;4gHGrPMp8BqD;gdAoBNo8BkB;uJCwdTp8BAAAAA4H,A;+kIC7ZaA2C;26IjCkDCopFwD;gVAwDMC8C;mmBAiOXC2D;iKAYACqC;6BAAAAc;qMAoBEC8E;OAAAAyH;yEAGACoH;yrCAoEHzpFuG;yMA+DQw+CsC;AAEDkrCc;uFAGFAc;0EAGEAkC;qhBA+GoBndAI3lBcqVuI,A;kGJkmBnCr+CyF;0TAKRo0CAA/Ja9lEiI,A;+7IsCnZf7RyG;+HClKFkpFwC;kZEixBkBSgQ;usGAAAA8D;8CAAAAqD;ovEA8NT3pFoG;82FAuUSAAlCrS4B4pFkD,A;sIkCqS5B5pFAlCrS4B4pFuC,A;24FkC6blCC4E;8RAcoBCgB;4GAENAuB;gOAOI14BA7B/jCR5S4D,A;8J6BokCTsrC+D;AACFtrCqB;8MASCqrCmC;y1BAgMQ3pD+G;AAApBlBAhD7pD8Bh/B2F,A;mCgD6pD9BlwD8B;00DAsDSg2FoE;szFAkNSzyBiI;6sBAsCPrTgK;uZAeIA2C;8TASX+uCAzC18CJg7CyC,A;sGyC88CaCoH;qBAAAA4F;wHAGIhqF+C;2rBAiBFA6C;2cAaRo8BkB;+nBA8BQp8B2C;icAWX+uCAzCxhDJg7CyC,A;iHyC4hDaEoH;qBAAAA4F;wHAGIjqF+C;uRAQJkqF0H;uBAAAAkG;+hBAWElqF+C;ymBAeRo8BkB;ggBAiBA+tDsH;mBAAAA0F;w6CAyCInwBA9B/6DAh6DwE,O;iC8B+6DAg6DA9B/6DAh6D4C,A;wC8Bm7DAw+CyB;gkDA0FP4rC4I;iBAAAAiF;uBAIYv4EqG;q+FAmGoBq4EgI;yBAAAAyH;w0BAiBrBlqF2C;8FAEX+uCgD;0VAWG3SkB;qbAoEKg1BA7B5iEU5SsB,A;4O6BkjEP4SA9Bt5DO5SmB,A;mI8Bw5DLA6B;osBAgCL4SA7BplEU5SsB,A;oO6B0lENA4E;obAYLAe;knEAiMX4YiH;s7CAwKuC6MA7BvgFVjkEwB,A;yoB6B6hFtBg2CoC;OAAAAAC31FAh2CyB,gB;2SDoyGL+uCAzCpyFFg7CoD,A;+KyC0yFEh7C6B;uHAEAAAzC5yFFg7C+B,A;AyC6yFEh7CwB;49EAgXa5xBuC;gpBAkBXw6DAzC1qGJoSoB,A;kByC0qGIpSAzC1qGa9lEM,AAAjBk4EkF,A;AyC4qGIpSAzC5qGa9lE+D,AAAjBk4E2B,A;AyC6qGIpSAzC7qGa9lEsC,A;wGyC6qGb8lEAzC7qGJoS2B,A;AyC8qGIpSAzC9qGa9lEM,AAAjBk4EkD,A;gxX2C3hB8B/pFgE;0EA6GdAA1BooBSAAFjvBvBAAAtB0B05BAAAA15BwBA4MLlwDgB,+C,A,A,kF,A;uoD8B0plCzBkwDAAAAAyB,A;0EA44GSlwDsB;wwECvssCsBu6IiBCEPpmBAnC6TOjkEoC,A,AmC7TpBs8BAA7BcguDyD,A,gDA6BdhuDAAaeiuDoB,0B,AAF1BjuD6C,A;urBCnBgBt8B2B;AAAwBw2DsB;yCAIG1mHsB;gTAUzCk+FsC;mHAAAA8D;AAAAhuC0BGpDkDwqFuE,AAISCgF,AAGtCCwE,A;iBH0HHn6BS;6VAcV6uBgBC/DgC7uBS,AAAQ6uBgB,A;wMDqExCAgBCrEgC7uBS,AAAQ6uBgB,A;qGDyEtBtvI+BKtInBAyB,wB;m8HEXmB66I+F;AAMACyC;AAOCCmC;AA4NVCAAAA9qFAC/NA+qFAAAA/qFAvCsvBQAAFjvBvBAAAtB0B05BAAAA15BAA4MLlwDsB,A,A,A,A,A,A,A,A;AwC5IzBkwDAAAAAwK,A;sDAgLmB8qFAAAA9qFoBC/NA+qFAAAA/qFAvCsvBQAsBFjvBvBAAAtB0B05BAAAA15BuG,A,A,A,A,A,A,A;yqC0CuBjBAqC;AAAiBg6DA9CiOjBh6DoD,O;6D8CjOiBg6DA9CiOjBh6DyD,A;+M8CjNUg6DA9CiNVh6DsD,A;O8CjNFAqC;AAAYg6DA9CiNVh6DoD,O;0E8CjNiBm8BkE;gDAAnBn8BAEmLUA8B,A;6IF9Hd8lCwD;0GAIFqnB+C;AAFArnByC;mDAEAqnBU;wFAEArnBoD;+CAMAAoD;uDAEAAoD;2EAKAqnB0C;AAHArnByC;2CAGAqnBU;m4IK8CiBH8E;giBA+BoBhtD2H;cACCA6F;8EAH9BAuF;AAjBbmdAAAAA4G,A;ibA6BAnbAAAAAuH,A;gxDAuD0CipFoB;6CACjCjrFc;89DGnKMkrFAAAAlrFACnDCmrF0D,AAvBGJAAAA/qFAjDsvBQAAFjvBvBAAAtB0B05BAAAA15BAA4MLlwD2H,A,A,A,A,A,A,A,A;AkDpGAu7I2E;AAwDCCAAAAttDmBEzIKutD4B,AAURCAAAArCAzCEXnpFoI,A,A,AyCiCZg+BAAAAAiCACcAA9C2DFh+BqC,A,mI,A,A,A;A4CoCS0rF4C;AAICC4C;AAIDC4C;AAILC8B;2DArFDXAAAAlrFkBC1EI+qFAAAA/qFAjDsvBQAsBFjvBvBAAAtB0B05BAAAA15BoH,A,A,A,A,A,A,A;AkD8FawrF4E;AAiBTM0C;AAUJCAAAA/tDA5CQlBh+BwG,A,A;A4CGmBisFuD;AAQCCAAAAluDA5CXpBh+B2H,A,A;A4CqDiBosF0C;AAMMCiD;AAgBnCrsFuE;6xIM/LcssF2E;yEAOdtsFAAAAAkCACSA4D,A,A;wbxBqMTmdAAAAAyIAG2B++D+B,+J,A;qGA0F3Bl8EAAAAAoJAGyBk8E+B,iR,A;qbYnPZl8E+G;4aAsBAAgB;gLAUqBA8B;8EAAAAgBAmHbA8B,A;gyBArGNsVAAOetV8B,+C;OAPfsVgBAOetVAA8FTA+B,A,A;ucAzFD8lCmE;0CACLmCuC;qDACR+xBAlE8BmCh6D2E,O;iCkE9BnCg6DAlE8BmCh6DwD,A;8PkEd3BkgC4C;6FACApDkF;qGAHT98ByB;AAOSg6D2E;iCAAAAoC;AAPTh6DAAyEeA+B,A;2EA5DfAyB;AAGSg6DAlE6P2B78CqB,A;AkE9P3BgfAhDuDFn8BoB,A;+DgDvDEm8BkC;iEACA69BmC;uFAHTh6DAA4DeA+B,A;4EA7CfAyB;AAISg6DAlE6O2B78CqB,A;AkE9O3BgfAhDuCFn8BoB,A;0FgDvCEm8BkC;kEACA69BmC;wFAJTh6DAA6CeA+B,A;wIAPFg6DAlE2MuB78CqB,A;AkE7MvBgfAhDMNn8BoB,A;0FgDNMm8BkC;mEAEA69BmC;oGAPbh6D2CAceA+B,A;2hFe9LbAqD;s/BCARAAAAAAqX,A;2cC6C4BusFiG;AAyBV5vDAAAA38BA3DqqBSAAFjvBvBAAAtB0B05BAAAA15BAA4MLlwD6B,A,A,A,A,A,A;0H6DhINy7IsC;AAMYkBAAAAzuDAvD8CnBh+BgG,A,A;AuDvCa2sFAAAA3uDAvDuCbh+BqG,A,A;AuDjCe6sFAAAA7uDAvDiCfh+BkG,A,A;AuD9BM28BAAAA38BA3DqqBSAsBFjvBvBAAAtB0B05BAAAA15B2E,A,A,A,A,A;A6D0H9BAAAAAAAAOcAE,wC,A;gsBCjIEAA5DuwBWAAFjvBvBAAAtB0B05BAAAA15BUA4MLlwDyG,A,A,A,A,A;A8D1MjBAmC;kCACAAQC0HMA0B,A;AD3HNAc;qEAFQkwDA5DuwBWA2D,A;8+DgE7vB3BAAAAAAuB,A;oCAAAAAAAAAkH,A;isBCoCmCoxDAtEoXX5SuB,A;AsEnXQx+C6D;yHAEAA2C;yJAAAAoD;OALvBAqC;iqFEWFunC4B;AACYz3FkD;8KAAAAiE;qBAAAA+B;80BE5Dfi9I0D;oIAMJn4CuDEsIoBwqC2B,AACZ3hD6CpC1IkCAA/Bgddz9B4B,oBAAAAiC,A,A,2B;4oEqEvYhBlwDYZqDEA0B,+B;0FYjDFAaZiDEA0B,A;AYrDFA+C;AAIQ64GAZODqkCmBAdQl9I2B,A,A;sBYOP64GkBZO8BhhBO/DhElB0gDuB,A,A;wsB2EsHCroF2B;8EAMX70DAE6JpB60D6CDjTwBitFAAAAjtFwB,A,A,4BCiTJo8BKDrSDAkB,A,A;2EDyICjxFAE4JpB60D6BDjTwBitFAAAAjtFwB,A,A,wBCiTJo8BKDrSDAkB,A,A;4FD0IXg1BA9EiRc5SwE,A;4B8EhRJ9BA1FyalBqtC2B,A;6R6FhkBFmDM;uDAMAAM;u+ER8EkCC8B;AAGEC8B;AAICCwD;AAGrCh2E6E;0GA0EuCi2EgF;AAIvCj2EwD;08BA4JAAuD;olGS1QoB8FgD;qlB8EkBpBnd6C;A5EpEAAAAAAA0Z,A;mmCC2FWmdAA9DyBowE8B,AAGGC8B,AAGAC8B,AAGtBCAAAA1tFc,4B,AAUiB2tF8B,AASLC8B,AAMIC6B,A;05FEqB3B/nD4D;2EACKA8C;gjBAmBuBgoDoM;UAAAA4E;+6CAgCvBtvCe;inEzC9DMx+CA0C7CG+tFAAAA/tFApGyDRguF0G,A,A,oCoGzDQDAAAA/tF6E,A,A;8F1CuDK2+E8T;sLAgCnBsPUA1FiDn+IiG,mD;AA0FpBm+IgBA1FoBn+IuC,A;AA0FpBmoIA0CvDIiWoCAgH/BluFO,uB,A;mB1CzD2Bi4EA0CvDIiWAAgH/BluF4B,A,W;uB1CtDKAgB;oOAoBPiuFAAjHiDn+IsB,wH;OAiHpBm+IWAjHoBn+I8B,A;mFAkHtBkwDqC;uEACpBAgB;2NASmBAS;0CAAAAyB;oIAEjBAqC;AACDg6DA/CwFDh6DyB,A;gE+CxFCg6DmC;kKAE8Bh6DqC;qGAE/BAqC;AACDg6DA/CmFCh6DyB,A;gE+CnFDg6DmC;kmJ4C1IG+NAzCdT/nE8B,A;oFyCcS+nEwB;urFGCSj4H+CRgDYA0B,qB;40C/CtCpCkwDAAAAA0KAI+By9BwD,AAAiC2hDiD,cADvD+OoB,AACsB1wDA7BqaHz9BuF,A,A6BraoCo/EA7B0alCp/E2G,A,gB6BzanBmuFoB,AACoB1wDA7BmaHz9BuF,A,A6BnaoCo/EA7BwalCp/EyH,A,A,A;6uBsFtXXouFwF;uBAIVpuFc;6lCfOIioCuF;mnBAuLI/H2D;kCAAjBlBAnG7QgCh/B2F,A;+CmG6QhClwDoC;0MgBvOyBu+IAAAAruFA1CjBTmrFU,AAvBGJAAAA/qFAjDsvBQAAFjvBvBAAAtB0B05BAAAA15BAA4MLlwD4F,A,A,A,A,A,A,A,A;A6F3ICw+IAAAAtuFA3FssBCAAFjvBvBAAAtB0B05BAAAA15BAA4MLlwDmI,A,A,A,A,A,A;uC6FnJAu+IAAAAruFkB1CxCN+qFAAAA/qFAjDsvBQAsBFjvBvBAAAtB0B05BAAAA15BoH,A,A,A,A,A,A,A;A6FiEJsuFAAAAtuFA3FssBCAsBFjvBvBAAAtB0B05BAAAA15B4B,A,A,A,A,A;A6FoECwuFAAAAxwDAvF4DnBh+BiG,A,A;AuFxDc0uF0C;AAGCC0C;AAGDC0C;AAwEP9DAAAA9qFoBpDrIA+qFAAAA/qFAvCsvBQAsBFjvBvBAAAtB0B05BAAAA15BiE,A,A,A,A,A,A,A;A6FyF9BAwC;21CC1BqBw+CuB;2HACKAwC;4FACWwOAlGkgBtB6hCuD,A;0wBoGlkBoB5qBAnGyWJjkEwC,A;AmGzW/Bg/BAcyC4Bh/B4G,A;gCdzC5BlwD6C;oJAaS0uGuB;g2CEMTx+CAAAAAwI,A;s7CCOS7wDAC0QP6wDmD,A;iEDrQO7wDACqQP6wDiB,A;iHDnQO5wDAE/BL4wDgC,AACAA0B,A;2CFwCGmtDG;kEADDrnBgD;wBACCqnBmC;kKASUgpBAtGuTMn2E+B,A;AsGvTAu0CoP;qaG1BNp3B6B;6kBA4gCDnd0C;AACd+uCkCrH5fAg7CyB,A;AqH6fc9hDkH;AACT+xBA5Hz4BmCh6D4E,O;mC4Hy4BnCg6DA5Hz4BmCh6DyD,A;gC4Hw4BxC+uCArH7fAg7CgC,A;AqHigBAh7CArHjgBAg7C6F,A;ktBoHteEh7CApHseFg7CkB,A;0BoHzeEh7C0BpHyeFg7CyB,A;IoHxeEh7CApHweFg7C6F,A;IoHteEh7CApHseFg7CmE,A;mHoHheEh7CApHgeFg7C4B,A;qBoHheEh7CApHgeFg7CiF,A;oEoH9dEh7CApH8dFg7C6B,A;AoH7dEh7CApH6dFg7CsC,A;usBiD1dsB+E4C;AAI8BCoF;AAIlCCmC;AAIDCmC;uCAwBnBjvFsC;i9FsEIqC8lC2C;yoECmKtB9lCoBCnSK6gCgD,A;k1CCgBbqfA7Eor+B+B0hBAA49G9B5hE8E,A,A;0L6EznlCiCg6BS;0DAAtBm1D8C;AACRn1D+E;wCAEOu2BS;y8CtElBlBvwDiF;ulDjDsSgCqpFgD;8qDQrM9BrX+E;wGAKAAuE;8TASAAqE;kWASAAwE;saAqBAAwE;0NAMAAwD;4YA2DAAwD;wsBAwBWhyEkE;WAAAAkE;srGA8JXiqE0D;09BAwCAA4D;4YASA+H2E;ygCAyH4BoduD;+MAcEpvF+C;+BAAAA4B;8SAO9BgyE8F;suBAoBA/H2F;2kEmH7ZOolBgQ;sBAAAAwD;yBAAAAqH;2gGlHzMACiF;6TASP1NyG;yHAKO5sIAPyBEgrDwD,A;2mBOXEAkC;k2JA0XXijFiF;smCrB5Z8BjjFmB;qBAAAA6B;WAAAA6B;8hCAuPEAyB;mCAAAA6B;mCAAAA4B;i/CEjOFAmD;uBAAAAwC;w/BAiHfw+CyB;6QASXzP0B;2RASAAU;uYAWoC/uC2E;WAAAA+E;q/HA6LVAqB;uDAAAA6B;WAAAA+B;4uCA6DAAgD;sGAAAAqC;iDAGUmdyB;4EAAAA8D;yaA6BVndyB;oFAAAAAASMuvFkB,6B;WATNvvF+B;y+BAmMnBAoD;0GAAAAyC;2nIE/fAAoC;sBAAAAsC;6FA8B+Bg/BAgBibZh/Ba,A;iBhBjbYg/B0B;iCAAAAAgBibZh/BS,yBAAAA4B,A;yVf5jBdwvFiE;6xECitDYhxCwC;wuEAo2BV6qCiD;2hB6B5hFPrpFgD;kPAAAAuC;48BAwBEyvF8E;qhCAiCAAoF;opJA8HgBzvFwB;85FA0JlBAa;sFAAAAwD;EAAAAAA0BbAAAAAA0B,A,A;knDtBzWe0vFsB;yTAU4CAsB;mNA4CrD9N8F;kUAqBO5hF8C;qtCAyDT4Ee;uIAQ6Bh3DwE;+EA0CzBoyDsD;6oBA4BQ4EiD;AAII02CAA5GEq0CkD,A;gUA+GVC0E;4iBCrPiBhiJ2E;WAAAAQ;4FAoCrBoyD4D;0RAKOA4B;wnBA0BEAK;urJPZRmiFiC;yJAMiB0NqB;AA+vBnB3KiC;oMA/E+BthFgI;0hIehf5B5DoC;+6BuDjIiBAerD+DFlwDsC,A;igCqDfnButI4D;sDAESr9EoC9C6gBsClwDwB,mDAArDkwDAAAAA0B,A,A;+B8C3gB2CAAApLrCAA/CizBAAAC/uBkB2oFAAAA74IwD,A,A,A,0HAgBxBkwDsF,A;uB8CkG2CAAAtL3CAAAAAA0G,A,A;AAwLE8vF8d;gZAYiBC+B;kBACfCyE;gDAKKD2B;AAAaEuC;gYAahB5Se;mCACSr9E6D;qDAGFAkE;8YASH1lEAnDiqBuC0lEuC,A;2GmD/pBjBlwDyD;mGAEpBxVAnD6pBqC0lEiE,A;2OmDtpB3Cq9E8B;ihBA8BG6S2C;iGAIPx1CQ;0PAMIq1Ce;4JAIAE2B;yJAaEEsB;meAgBFFqC;oHAMGA8C;AACH5S2B;8+BAe0C0SkB;kDAIjC/vFS;AADT+vF4B;8RAQAEqC;wBACAGmG;sEAKEHuC;2NAWFAqC;qMAOCAqC;m8CAsB0BjwFwB;4JAAAAmC;iOAQAA6C;qFAK1BiwF2B;u/EnD4D0BngJkB;gvBA8CzB8WeA8XsB9W+D,+GAEpBxVAAOuC0lEqE,A,6H;43BF34BvC1lEAE24BuC0lE+C,A;OF14BnCqoFuB;uFACkBv4IyD;mGAEpBxVAEu4BqC0lEiE,A;qWFx3BnCqoFuB;+cAWAAuB;oYA4FPtmC4B;8BACE4mC2B;AAA6B0H4D;0JAI7BrSyB;kBAAgB7csCAbhBmvB6C,A;wLAkBE3HqB;wkBAyHe74I2C;wNAUAkwDwBAhEDlwDgB,qE;oBAiENuwHsH;6PAYOrgEwBA7EDlwDgB,iC;oBA8ENygJkJ;uKAKOvwFAAnFDlwDsB,kD;gGAwFN60F6E;gBAAAAyC;qKAKO3kCAA7FDlwDsB,kD;yGAiGN+9F4E;gBAAAAyC;sFA+CTs6CoB;qBACMAyB;8OAOVGe;sNAQiBCqF;0KAOZJuB;0UAULGe;oaAeiBCqF;gLAOZJuB;yQAWDAoB;2mBA2EAAgD;4QASNDoBAzKMC2B,+D;yOA+KAAoB;yGAIRDoBAnLQCyB,2D;sNAwLAAoB;8CAGRqIAA/KA1HoBANQXyB,mB,AAMYnoFK,AAApB8oFiE,A;kTAoLQXgD;iFAgBRCoBArOOC2B,mB;2QA6OKI2B;AAERLoBA/OGC+B,uB;8XA6PCFoB;AAERCoBA/POC2B,mB;umBAwIWoIsB;AAIdCiBAvIGDwB,mB;8PA+IWAsB;0iCA+KK1Vc;sCACAiD2B;qEAGEvE4BAxeRkXyC,mBAAVhIsB,AAAUgIgG,A;8JA0ewBjIiBA/TlCD0B,mD;iOAgUiCCiBAhUjCD0B,E;kDAgUiCC+C;AAEH1oFE;mOAMmBmoF4C;AAC3BM6B;iCACqBCiBA1U3CD2D,E;kDA0U2CCoC;iyBAiBnBh4BoB;wBAAAAAA9hBOkgCkBAxB/B7V4B,A,Y;0BAsjBwBrqBAA9hBxBi4B2B,AAA+BiIwG,A;qHAgiBH5wFE;8UAOI0oFiBApWhCDwB,+E;6FAsWctnBkBA9iBd6cqC,AACAsSiD,A;+VAkjBsB5HiBA3WtBDuD,qC;0IA4WiCCiBA5WjCD0B,E;oCA4WiCC+C;AAEH1oFE;ohBK+RXA6CL/vBHlwD4C,A;iPKogCCkwD6CLpgCDlwDgB,A;sBKogCCkwDmE;kPA6BAA6CLjiCDlwDgB,A;sBKiiCCkwD+D;4aA+FAAwBLhoCDlwDgB,sE;u1BKuBTkwDa;qDAAAAAEsR4Cg/BAXqF5Bh/B2B,yBAAAA8B,A,I;yxHUxFvB6wF2B;UACFX4B;8WASEWqC;AACFXwC;6CACclwFU;2DAAAA0C;oQAI4BAU;mCAAAA2C;+JAQxC8tDqC;AACHoiC8B;qVAaA7Se;mCACSr9EkD;qDAGFA0D;qNAKN8wFe;0GACuBl1DoBNxVuB9rFgD,AAArD8rFAAAAAmC,A,A;oDM0VU57BmCAyXJAAArDsBAoBNhqBHlwD8C,A,2M,AMmtBzBkwDAAAAAAAGiBu9EAA3bbzvBe,mG,oB,A,K;8DAiEM9tDkF;oJAiBQ+wFmD;AAAuC/wFeN7WhClwD8C,A;mJMqXlBghJsB;iNAQAAsB;iFACGx2JAJsUuC0lEuC,A;eIrUjBlwDyD;mGAEpBxVAJmUqC0lEiE,A;uOI9S3Cq9Ee;slBAqBAvvBe;qIAG6B9tD+D;iIAK7B8tDe;gJAG6B9tD6C;2FAM1BkwF2B;yLAIPx1CqD;uRAOKm2C4B;0FAGyC7wFAAwK1CAAC/uBkB2oFAAAA74IwD,A,A,6GAgBxBkwDsF,A;0QD+jBI04D+D;6bAsBEw3B4B;wdAkBelwFyBN1fIlwDoD,A;wgBMkhBnBogJ4B;wFAEF9hCiB;gOAME8hC4B;wFAEFx3BkB;wgBAzBuC2vBuB;qpBAoDXroF8D;uGAIAA6C;6gBAyCAg1CgC;unHC7pB1B+7Ce;oNAMgC9wB2B;2OAKhC8wBe;86BA2DDCsC;mKAGgBAc;0NAKjB/wB2B;ySAqBMgxBqC;AACJFe;+HAIc/wF2F;4EAKd+wFe;2IAIc/wF6C;qFAKVixFqC;AACJFe;gQAcGGyB;2DAICAyB;2DAIDH2B;sQAeoB/wF+C;qFAGtBmxFe;gWAWGJ2B;oBACACsB;qBACAIsC;AACcFoB;2VAQdH2B;oBACACsB;qBACAIsC;AACcFe;qhBAmCdH2B;oBACACsB;qBACAI4B;4bA6BAAsC;AACcFoB;kRAkBdEsC;AACJDyC;8WASEJiB;8FAIiBGwC;sRAWnBCe;mtBAzGEJe;2sBAqCCMe;q0HA6QHpgCuB;yEAEAqgCe;mhCA2CIrgCoC;qXAsCJsgC4B;6iBAiBAhUe;6+BAiTOv9EoCA3UwClwDwB,iDAArDkwDAAAAAwB,A,A;mgHNgIuBAyB;8rYA4PPAoB;kIAEdwxF0F;mhDA6K2BxxFyB;qdAqBzBoqC2F;2cAYAA2F;giBAYAA2F;m3EAgFF8+CyB;6iDSvyCWlpFyC;6OAAAAkC;iNAU0ByxFa;gJAGHAa;wiKAuL7BAiE;kiBAuGqBCyD;+nBAiEf1xFa;uEAAAA2C;+1BAg1BWA8C;qEAAAA6B;+CASXAwE;oDAAAA+CA2XbAAAAAAqD,A,A;u5BAjWeyvF8C;2jDAiEXvmF4E;y1DA4F4BlJqG;w9EwGj8CJk3DI;wiBJkCAl3D4C;+DAAAA2B;yBAAAAqC;6rBA2IUA4E;WAAAA4E;0kE9F9JhC+uCqC;AACAAA/BohBJg7C8B,A;A+BnhBIh7C4B;+lFAiVF/uCkD;4FAAAAmE;8GCyMwBA+H;k2BA+Bf2xFkN;m8CA0KSC0gB;qXAmGpBCoG;yrBkGjvBE7xFyE;WAAAA6E;gsCAmOgBk8E0B;wpBA2D2BA6B;AAE7Cl9CuG;gGAAAlvFsC;4MAKEkwDyE;WAAAA6E;iMAYoCAgD;WAAAA+B;oNAuBtCg/BoH;0DAAAlvF8B;sIAKAkvF8F;gEAAAlvFmC;6JAyBAkvF8F;kEAAAlvFmC;2EAuBiBkvFsH;wGAIPlvFqB;2CAIOkvFsI;qHAMHlvFoC;wXjG5XVgiJmB;gUAWYA+B;gRAMZA6B;wDACO9xF+B;0DAwCP8xF6B;yOAaCAyD;qNAoBDA6B;0jBA+BGA4B;4HAQCA4B;sSASJA6B;+XAiBFn8BkD;yiBAyCam8B6B;qTASAA+B;wHAEc9yDAtBiQCh/BwC,yBAAAA4C,A;0LwHpjBGimEmC;4+CjGyEpB8rB6Q;+sBAqBmB7V6F;0TAQfl8E6C;4FAEX23EAlC+aW9lEM,AAAjBk4EkG,A;kQkCja6B7NiB;uWASvBntC6BlCmZNg7C6B,A;sDkC/YyC3tDoB;w5BkGrBb+Z0B;kBAAAAkB;6kB1FlED67CwC;0pBA0DdlvJgF;0mHAiLFk9DgD;0BAKGAiD;6GADHAuC;AACGA0D;+IAIPo8BkB;2LAkFH25B+B;4jEAsEE4hBA1C+FW9lE2E,A;wiEAnNCmiDe;mPoC2MKssB0C;sBACDx/B2C;sBACAkCyC;sBACAyT6C;sBACE3f+C;sBACA9F8C;wBACC4vBmI;6NtBrWiB5RU;oQAyCtCAkB;iCACSijCAAiCmBCoC,A;oCA/BG1tCuD;4BACA4KsD;yFAG1BgXoD;0qPdxJP+rByR;qCAGKC4I;uTAQLC0FAYaryFmC,4H;ycM2E0BAyD;cAAAAwC;oZAwJnBA8B;gpCA8IfA6D;cAAAA4C;qvDN9buBqpF4C;wiBAkVlBv0BsCwIjTKw9B2E,yE;QxIiTLx9B6B;0LwCqPe90DwC;62HC4sCXwiEc;0PAQYsnB+E;+DAIYtrCsC;4HAkB5BqrCoD;wfAaIz4BA7Bx8CM5SkF,A;0I6B88CFwbA9BxnDTh6DyB,A;oE8BwnDSg6DqC;4jGAiuBHgE2BA+CWxf+B,A;mEA7CN8SgD;yTAcHA+B;AAAuC1M6F;2UAiB9CilC0F;suBAsCRjiDe;uKAIAykBkB;gKAIQkmC4F;4DACgCCSAIxChwB0D,yPAUJpL8B,AADIxS6I,A;wJA2Da6tCyEAObjwByC,0EAIFkwBsLA1BA3jD8E,A,8L;0UAsCEyzByD;SACAockD;oNAIAjEkE;2LAEA5pB6E;yiFAqsBQ/wD4C;y4FA8xBD2yFkF;kzEA4FN5hCgD;AACE/wDY;AADF+wDiB;k5BA2CeKA7B/8HE5S4D,A;mJ6Bq9HWsrCmE;gJAIOtrCsC;iaAmB5BqrC+D;0vBAoBJv4BmB;8CACAkRmB;+BACIlRqB;2FAGQ0M8C;uNAUTh+DiB;sVAcHg+D+C;uCACE2cyB;4FAIC36EiB;2OAUD+wD+C;AAIC/wDS;gVAYH4kDwF;2DAIC5kDiB;kPAUAg+DsF;AAAqBwEkC;yIAUrBxiEiB;k6BA4D4C4kDkC;AAAnB0MyF;8DAmB3BtxDmC;s4BAuBUuyF4F;qEACgCCsKAUrCte6E,A;siBAmBH1RkB;mHAEA0RgI;AACAyGuB;uDANGkP0C;AAOH94BU;y5EI89eO6hCA6F3koBX9pHADgBDk3BsC,AAAAl3BwB,A,A;kgD5F80lCA+pHgC;6LA4BAAgC;uMAYAA+B;AAActV2B;4LAMMA2B;qQAOtBljBsG;89B6Ft1lCOxwFADjDuBmqFQ,A;yNCqE5B6lBuG;8GAIS7iB0E;6BAAAAgB;4FAGXOqD;6LAckDP0E;uNAQvCA0E;6BAAAAgB;4FAGXOqD;+WAeFA4D;+oCAsESptFwCD9LA2oHArG2VXp3DApC/IAAuF,A,mBoC+IAAApC/IAAuH,A,A,A;O0IdWvxDAD9LA2oHsC,A;wPC0MLCoG;8CAIS/7B0E;iCAAAAgB;sKAIXOqD;8NAQwDP0E;sNAOxDOqD;qjHlFhOG3C0CANuBjtBqBzCDE0gDuB,A,A;2qDmDyDf1gDOnDzDe0gDuB,A;wRmDmCb1gDOnDnCa0gDuB,A;wgBmD+Cb1gDOnD/Ca0gDuB,A;0hB6HiCsBpoFA3H6LzC27BwBFnCwC9rFgD,AAArD8rFAAAAA6C,A,A,A;glEgIlJco3DgC;yEAGZCwK;kuBAqDmBx1DA1H2VOz9BE,A;oD0H3VPy9BoB1H2VOz9BiC,A;i5F8CtQM6pDS;sRAKbAK;4qDAjB8BuVa;uBAAAA4G;0DAAAA0I;0S6EhC7Bp/DAAugBH+qFAAAA/qFA/HmGQAAFjvBvBAAAtB0B05BAAAA15BoBA4MLlwDgB,mC,A,A,A,A,A,A;oBiI/CHkwDmBAugBH+qFAAAA/qFA/HmGQA6D,A,A,yC;uN+H/WJoxDuC;AACD0oB2L;gBAAAAuB;kHAQlBqZiD;wIAaAnzFS;4CAAAAqC;6OAYWAAC3afAAAAAAsG,A,A;mDD2aeAwE;wpBAsDbs6CkE;+EAmBOt6CqC;qLAQWw+C+B;4HAIlBlEiE;iVA5DsBt6CkB;+BAAAAoC;2NAEAuiBAyCtZpBviByB,A;yOzCyZEozFmD;yMAmNOhiCAnB7mBW5S+C,A;6ImBknBKx+CiC;qTAyLhBw+CyD;yFAEPs2B2BDpyBQkekC,8FAGZKyNAoF4B51DA1HiTFz9B4B,oBAAAA6C,A,sE,A;mK2HoaxBg/BAnBz0BwBh/BkH,A;8BmBy0BxBlwDkD;gLAMsC2tFA3H1adz9BoC,4BAAAAiD,A;+c6HxXGgcAjIouBChcAF1xB5BAAAjC0B05BAAAA15BoBA4MLlwDgD,A,A,A,I,A;60CmIvDM2tFA7H0THz9B4B,oBAAAAkC,A;gxC4H7apBAuJ;irOIlBqCA0N;+BAAAAoB;8PAMjBmd0B;AAAEndkB;gCACtBAuC;6EAYWAgB;mtBC5BfAuC;qyDG4BKA2C;oLAUAAmB;yLAUAAoB;oRAcIAc;oZAWAAwB;2/BCtBE81EACtBIhoCkC,A;ADuBR9tCa;AADI81EwB;kDACkDrPA5GnDnCzmEiC,A;0b4GkEjBszFAAsBU7sBA5GxF+B34BsC,A,6B;O4GkEzCwlDAAwBJtzFa,6BAFcymEA5GxFOzmEiC,A,A;oC4GoEjBuzFAA6BU9sBA5GjG+B34BsC,A,6B;O4GoEzCylDAA+BJvzFa,8BAFcymEA5GjGOzmEiC,A,A;6B4GsEjBwzF2HAmC4CvpD4C,AAAhDjqCE,AAAgDiqC6B,A;+BAnC5CupDAAmC4CvpDe,A;oGA1BlCw8BA5G/E+B34B2C,A;A4GgFpDoFAClCI4iC6C,AAAQzjEoE,4D;ODmCLrSkB;cAFcymEA5G/EOzmEiC,A;4H4G+GrBqrC0B;oDAAAA+G;kPAYAA0B;yCAAAA+G;+8CIlIuCrMgB;miJ7C6L1B4yDwkB;sjEzCxJNjgD+G;sDAAAAA0D4XZ3xC2F,A;gI1DhWFg/Bc;6CAAAAiE;+CAAAlvF8B;oqFuFsO4B2jJsB;wDAEKAsC;OAAjBzzFoB;AAAiByzFwC;YAAjBzzF+B;yO5CtP+Bs6CiC;qPAgQ7CAuB;2JxBjTmB4hCiB;+DAIF9/CkB;sLAyBjBkeuD;gjByBPSt6CyC;AACX+uCApHkhBAg7CoC,A;+doHpgBIh7CApHogBJg7CoB,A;4CoHpgBIh7CApHogBJg7C+H,A;qGoHhgBIh7CApHggBJg7CiI,A;klBoHnfIh7CyBpHmfJg7C6G,A;AoH9e+B3tDqC;AAAxBke0D;67FAwFat6CsCzB3JIitFAAAAjtFiC,A,A;6wEyB6MNAAzB7MMitFAAAAjtFmC,A,A;2ByBkNtBs6CAzBjMFvLA3FgiBAg7C6B,A,A;AoHpWkB/pFiC;sCAOXs6C2C;gDAGGleAzB3MSAoB,A;2eyBsNTg1BAxGqMY5S0C,A;oDwGpMV4SAxGoMU5SsC,A;qOwG3LtBm1C8L;6fAoBWn1CoC;AAA+B09BKzBxPrBAqD,A;AyByPnB5hCoBzBhPFvLA3FgiBAg7CkB,A,A;oBoHhTEzvCAzBhPFvLA3FgiBAg7CiC,A,A;qEoH9SEzvCAzBlPFvLA3FgiBAg7CgC,A,A;s0BgKjiBsBzvCoC;urBzCNFt6CsC5BVIitFAAAAjtFiC,A,A;ygC4ByClBg6DA5GoNKh6D0D,O;iD4GpNLg6DA5GoNKh6D4C,A;0B4GhNL4zFuE;8qBAwBA55BA5GwLKh6D0D,O;kD4GxLLg6DA5GwLKh6D4C,A;0B4GpLL4zFuE;0GAMA55BA5G8KKh6DyB,A;gE4G9KLg6DoC;uGAAuC45BkF;iEAGIA2F;qBAA5B9tD6D;i1BAxCN8tDgD;wcA4BAAgD;yJ2CMKp7GAAiBpB4jDuC,A;mEAjBoB5jDAAiBpB4jD2B,qDAAK0JkE,A;AAhBEwU0D;k5C7CFczIkD;AAAsB2qBkC;6CAI/B1sH0B;wCAAAA6D;gaA6IGqsFA1GAJn8B2B,kC;2b0GsBMm8BO;uCAAjB6CgC5H8K8Bh/B+E,A;2G4H9K9BlwD+B;AACM0sHyE;AAGWrrD4G;+RASJ0gCyC;AACuB2qBmE;AAGhCztBgD;2PAKeotCgE;8IAiCNhrEiF;4DAESgrBA1G/EXn8BsC,A;iB0G+EiBgtDiB;AAAN7wBA1G/EXn8B8C,O;2C0G+EiBgtD6C;oPAqCf77CgE;gYAwBHy9CoE;mIAhBWqVAzGuBQjkE+B,A;AyGFKk8E8D;yEACjBttB2D;w8BA2EGtMAAzQWzQuD,A;kEA2QT/hGuF;kDAGlBwyGAA9Q2BzQgD,A;AA8QFAmC;sCAMzByQAApR2BzQkC,A;AAoRF2qBmC;wCAMzBlaAA1R2BzQkC,A;AA0RFAmC;kGAIZ1gC4D;gCACAA4D;oEAEIigDA1GkHC5Se,A;ue0GnGE4SA1GmGF5SiB,A;kE0GlGD4SA1GkGC5SmB,A;8nB0GvFD4SA1GuFC5Se,A;onB0G9EDAe;slBA4CE3M+N;AACDA0D;2qCA8BjByQAAhZ0BzQoB,+C;qmMAgzB3ByQAAhzB2BzQsD,A;mEAmzBO/hG4B;+DAAAAmB;8aAuCjBu1CyD;k3C8C19BNqXuC;2erE8BP00DAnFqfc5SuB,A;gTmFlfT4SAnFkfS5SwB,A;wNmF9eP4SAnF8eO5Se,A;0kBmFleL4SAnFkeK5SqB,A;uQmFtdjB3MuB;sHAKQ2MsB;AAAY3MuB;oLAUrBAa;mEAAuBufAnFucL5SiB,A;wQmF7bb1YK;yXASPiJgB;qGACAAgB;oBAEFAU;umDsEpHalYyD;0QAOI2nBuB;u4IEyCU1YyD;OAAhBjPyD;oNAUmBsFA3JoInBn8BgC,A;4D2JpImBm8BkC;qYAkBXqiB8D;6HAOK1YuD;kCAAKAoD;6vC/G1BzBu3CiBAjC0BzoB2BC7BFjtBOzCDE0gDuB,A,A,A;gOwCqEdpoFAtCyJL27BoBFnCwC9rFqD,AAArD8rFAAAAAoB,A,A,A;AwCtHqCzeoC;0EACJi0CmC;2EAGbpxDAtC2qBOAAFjvBvBAAAtB0B05BAAAA15BoBA4MLlwDiE,A,A,A,A,A;gCwC/GrBwqGkD;AADgBt6CAtC2qBOA2E,A;+vFsCngBDoxDiC;2HAGbisBiBAtNmBzoB2BC7BFjtBOzCDE0gD2B,A,A,A;+LwCyPZv4IoC;gCAEhBwqGkD;oYAgBYt+BAtC6hBgBhcAF1xB5BAAAjC0B05BAAAA15BoBA4MLlwD4D,A,A,A,A,A;6BwCmFvBwqGkD;AADct+BAtC6hBgBhcqE,A;iLsCphBNw+C6B;AACtBnc8D;AAEAwxD0J;8LAzE8Cn6DgC;kCAI9B15BE;iCAAAAAWpND+qFAAAA/qFAjDsvBQAsBFjvBvBAAAtB0B05BAAAA15BeA4MLlwDkF,A,A,A,A,A,A,AmDpKTq7ImF,A;AX8LVnsDY;2DAAAAqC;yCAAAlvF+B;oSAQmB4pFO;6WA2CoCvc6B;ihBAwD3D22E6DA3FwB1iCiC,8BAEQj0C2F,AAG1BkgEiBA1MwBzoB2BC7BFjtBOzCDE0gDqC,A,A,A,uC;mhC0CsCL5+CyB;yBAAAA+B;iQAIjBAoB;qEAJiBAmC;0RASgC6zCiB;sKAClB1uB8B;iCAC1BnlBuD;AAXYAuC;mhBAoBdzpCmC;wxH4BqEmBmgEmH;2RAE5BpxBY;uCASFAiB;2RAYE6rBY;yGAAAA+C;2nCA8DS56DiEuF5JbAqD,A;gnCvFuQmBqqFAtCrSKpmBAnC6TOjkEwC,A,AmC7TpBs8BAA7BcguD8C,A,sBA6BdhuD6EAaeiuDoB,8B,AAF1BjuD+C,A;kJsCmSsBt8BwG;sEAGHu1CgD;uKAAAA8D;iEAEAiJ+B;4BACGx+CqD;sSAqBR+zF4C;kDACc7hD0D;iCACNlyCqF;AAEJ+zFkE;+MAKY7hD4D;mEACb6hD+C;oDACa7hD4D;mEACX6hDiD;oDACW7hD4D;iCACNlyCkG;AAEH+zFsE;2LAML/zFyG;6GAKDw+CuB;4BACGx+C6C;+HA4EVAgH;2zDAmDSi/B2KuFtcjBj/B4C,A;kCvFwciBA2DuFxcjBA+C,A;oTvF8c6BmgE4E;AAAfngEmB;AAAA+uCuC;iCAAAAS;4BAAAAS;uBAAAAS;wBAAAAS;imBAoDSoxBgD;oRAiBAAqD;y7CA4CrBpxBwB;AACAAc;wxDtCtjBoBmtC+B;siEAiEAA+B;otBA8BD94B8C;2NAQN84BiC;kiCAwFIz7BuE;KAAAAoB;iTAYXy7BmD;AAGqB94B2E;4HAEjB3C8F;kHAKiB2C+C;AAGR84B4C;wIAOZz7BqF;uYA2CoBkvBW;uIAKvBAgB;AAAmBAU;4MAWnBAa;cAAAAW;KAAmBAe;6iBoDlME/uFCsEzDDozGyC,A;0mBtE4EACqB;8sBAWTz1CuC;yQAQWteiD;+OAGNAyE;0BAEKr/CCsE1GHmzGoC,A;AtE4Gf53DkB;ytEA+DP4CA1GtQ8Bh/B2H,A;0D0GsQ9BlwD8B;snDAmEAkvFA1GzU8Bh/B2H,A;iC0G2URi0Fa;mCAFtBnkJ8B;iSAYsBm0HAvFEOjkEkC,A;AuFF7Bg/BA0B9T0Bh/BqH,A;uE1B8T1BlwD8B;gGAII6nIAnGqMa9lEiE,A;khBmG9KKoyDAvFzBOjkEsC,A;AuFyB7Bg/BA0BzV0Bh/B4G,A;gC1ByV1BlwDsC;sGAQsBm0HAvFjCOjkEsC,A;AuFiC7Bg/BA0BjW0Bh/BiG,A;mC1BiW1BlwD8B;6QAWoBi/F6C;kVA3KFntDa;4EAAAAAuEvNlBoyGgD,A;AvEwNIjlDAnGgUJg7C6B,A;iXmGrSwBloGiBsE/MMmyGoC,A;6CtEgNNlyGCsE5NOkyG0C,A;2BtE6N3BjlDAnGmSJg7C2B,A;iZmG9QwB/nGa;4EAAAAAsE5OKgyGqC,A;AtE6OzBjlDAnG6QJg7C6B,A;8ImGvPkBnoGa;4EAAAAAuEjSlBoyGiD,A;AvEkSIjlDAnGsPJg7C6B,A;+ImG5OsB/nGa;4EAAAAAsE9QOgyGqC,A;AtE+Q3BjlDAnG2OFg7C6B,A;iNmG3NsB7nGiBsE7QW8xGuC,A;6CtE8QXlyGCsEtSSkyG0C,A;qBtEuS7BjlDAnGyNFg7C2B,A;0bmGhL6B/nG8BsE1UAgyGsC,A;6tB9FwBP7zByF;qCAAsBhhBkB;aAAAA0G;6WgGpExCwwBW;gYAmBFAW;2IAGgBAiB;oDAEIxPa;qGAAsBhhBAA7B/BwwB6C,oEACKp1CwB,oD;4CA4B0B4kBAA5BdtgB8B,A;qqClFY1B5C8B;mCAAqBNyB;uDACSAiC;m3BoFkBSwImE;gGAI5BqayD;qXAUJx+CA1EHGk0FglB,kI;O0EGHl0FA1EEAmd0JA3BOg3EAAAAn0FwB,A,A,A;gf0EiCImgEoD;sZnHgJlBnGA/CiDOh6DsH,O;kH+ChDUm8B+E;+CAcFqiBkE;AAAwB4SgD;OAC9BpxDqC;8GAGFAkE;qHAOUAqC;AAAM2xCA/CNhB3xC0D,O;gC+CMgB2xCA/CNhB3xCsD,A;A+CMUAACPFA8B,A;+IDmBZg6DA/CWIh6DqD,O;6B+CnBGg6DA/CmBHh6DkD,O;kC+CnBGg6DA/CmBHh6DyC,A;iE+CXJg6DA/CWIh6D4C,A;+d+CjKFlwDiE;oSA2CgBowFgC;kBAAAA+C;mGADPlgCuDCyFCA0B,A;ADvFgBo8B6C;2DACM8DgF;OAA5BlgCsD;iUA4BoCsVYCpCrBtVyB,uCAAAAAA8FTA+B,A,A;klBDfAw+CuB;ihBA2BJwbA/CkBJh6DkD,O;mC+ClBIg6DA/CkBJh6DyC,A;wc+CVIg6DA/CUJh6DqD,O;8C+CVIg6DA/CUJh6D4C,A;ghB6GtKJmkCgC;+jCAoCUnkCY;yJAIiBAoBChIhB6gCiD,A;kBDoID+tBApH2DchhHgE,A;6EoH3DTk4F2D;AACbA+E;AAEe8oBApHwDOhhHiD,A;wCoHtDPghHApHsDOhhHiD,A;mKoHjDhBoyDgC;8TAMmBAoBCpJhB6gC8C,A;kFD2KV+tBApHoBuBhhH+E,A;eoHpBvBghHApHoBuBhhHW,A;mEoHdhBk4FmE;AACAA0E;qBACAA8D;yBAKY8oBApHOIhhH4C,A;gXoHpCZghHApHoCYhhHoE,A;sIoH/BZoyDY;2JAIoBAoBCpKrB6gCkD,A;4BDsKqB+tBApHyBRhhHgE,A;mDoHzBgCghHApHyBhChhHiD,A;qDoHxBXghHApHwBWhhHiD,A;eoHzBdoyDoB;yUAkCiBAoBCxMhB6gCiD,A;kBD2MY+tBApHZChhHgE,A;0EoHevBghHApHfuBhhH0C,A;eoHevBghHApHfuBhhHW,A;0BoHkBUghHApHlBVhhH4C,A;8WoH4BhBghHApH5BgBhhHiD,A;eoH4BhBghHApH5BgBhhHW,A;gEoH8BvBghHApH9BuBhhHiD,A;QoH+BhBoyDO;AADP4uDApH9BuBhhHW,A;2hBoHsDjBghHApHtDiBhhHgE,A;eoHsDjBghHApHtDiBhhH4C,A;AoHuDjBymJA/EiDShKAAi9FDrqFyC,2KAeHk8E6D,+FASF9/CoB,AAAFjfqE,A,gC;oE+ErhGsB93B+B;AAAzB4/DiB;AAAWqLiB;AAAcjrEAF8JlBAE,qBAAAA6E,A;AE3JJupEApH/DgBhhHiD,A;eoH+DhBghHApH/DgBhhHW,A;iEoHgEdghHApHhEchhHiD,A;eoHgEdghHApHhEchhHW,A;mEoHiESghHApHjEThhHiD,A;eoHiEhBoyDqC;4ZnB7OTAgB;8IACeAgB;inC5BDfAgB;2kC2BuE2BAqC;sEACpBAgB;gFAOAAgC;iCAHGAgB;uPAWCs0FyBA1EQxkJyB,4D;wDA2Edo+IoC;SAAAAqB;0GAAAAAA+ELluFiD,A;gZAvEWs0FyBAnFQxkJyB,4D;iEAoFdo+IoC;SAAAAqB;oHAAAAAAsELluFyD,A;8YA5DWs0FyBA9FQxkJyB,4D;2IAgGdo+IoC;SAAAAqB;sHAAAAAA0DLluFiE,A;icApBFs0FYAtIqBxkJyB,4D;iGA0IVo+IwEAgBTluFqD,A;kHAdmDkuF4E;4BAAAAAAcnDluFkE,A;iGAV0BAgD;stBAuCrBAwB;wzFAKEAqC;AAAYkgC4F;AAAZlgCAzCIMA8B,A;wQyC4BRAW;4CAAAA6C;qVzCgCcihEc;uCAAAAAhDmOKjhEkF,A;AgDnO9Bg/BAlEnQ8Bh/BuF,A;mCkEmQ9BlwD0F;0GAGuB0uGwC;8GAENx+CY;+EAKHg6DAhDxCHh6DgD,kC;wRgDmDAAqC;AAAgBihEAhD8MGjhEsC,gCAAAAmD,A;AgD9MnBAAAjFQA4B,A;SAiFiCo8Bc;+IAU7C49BAhD7DIh6DqD,O;6BgD0DPg6DAhD1DOh6DkD,O;kCgD0DPg6DAhD1DOh6DyC,A;iEgD6DJg6DAhD7DIh6D4C,A;kTgD9KckgCgC;kBAAAA+C;qCAAZlgCuDAgJMA0B,A;AA/IGo8Bc;i+EAwNPp8BE;gDADS8lCsD;yuCPnSgBrIA/Bgddz9BE,A;oD+Bhdcy9BoB/Bgddz9BgC,A;6M+B3blBAuCAgDau0FAAAAv0FAnCmsBIAsBFjvBvBAAAtB0B05BAAAA15BeA4MLlwDyF,A,A,A,A,A,A,yC;opDqCtGnB2kJyC;6JAKJn6CmE;6OAMIm6CyC;+ZAcFl5CK;OAAAAuD;0KAYFRK;OAAAA2B;wYAMI05CyC;yHAG6Bx0FAnC6FtB27BoBFnCwC9rF6C,AAArD8rFAAAAAmC,A,A,A;yBqCxDkC5fAnCqqBFhc0BF1xB5BAAAjC0B05BAAAA15BeA4MLlwDkF,A,A,A,A,A;kXqC3CnB2kJyC;6HAIgB5qCsB;wJAKM9OK;OAAAA2C;YAGnB8OsB;8JASaliBOrCnKU0gDuB,A;iCqCqKzBoM+B;i9BH9DWj+BO;AAAM/4B6CG3HkBAA/Bgddz9B4B,oBAAAAiC,A,A;gK4BjVQy9BiBG/HMAA/Bgddz9BK,A,A;wD4BjVQy9BAG/HMAoB/Bgddz9BiC,A,A;mG4BvTGw2DO;AAAM4oB4B;qdAyBsBp/EA2FrJLCA3HqNzC27BoBFnCwC9rFgD,AAArD8rFAAAAAmC,A,A,A,A;OkC7B0BzegC;AAAwBndmD;AAASA2D;AAAjCmdgC;ohBAgBPsgB6CGlMuBAA/Bgddz9B4B,oBAAAAiC,A,A;6K4B1QKuwDwB;AAAQ9yB0B;AADjBtgBE;AACiBsgBiB;uCAAAAAGtMCAA/Bgddz9B4B,oBAAAAoC,A,A;A4B1QwCo/EQ;AAD5CjiEuC;6RASLiiEO;kGAMVAgB;ykBAaYAO;2/BAlFEAgB;uEAMAAuB;82DH7EgBtxCkC;uTAURA4B;orB6GdjBAkC;gpBAkEIhI8D;AAAWAiF;AAI3BzzBkEA2DmCy7BiC,A;uNA5CQAwC;moDrE1JjCxhDACiCsBx8C6I,2K;ygBD/BhCy8CAC6CkCz8CkJ,8M;uWmC9BFkqFiB;kEAA2BwIA7E4+nBzC91CA6Fp+nBbDADcDuT4C,4D,A,qB;oSfnBwByoD6C;sFAAAA6F;AAIjB+NO;AAAM/4B6CtEvBuBAA/Bgddz9B4B,oBAAAAiC,A,A;mSqG5bbw2DO;AAAM4oBgB;AAAiB58CA7Ew+nBlB91CA6Fp+nBbDADcDuT4C,4D,A,A;m0CxCoGmB20FwB;AAAV30FkF;kGAAAAyC;i3BAoDmB20FwB;AAARx3EgGAzJUowEyC,AAGGCyC,AAGACyC,AAGtBCgC,AAUiBC+E,AASLC2C,AAMIC2C,8EAzBhBHAAAA1tF2E,A,A;AAkJf8wCqB;mDAAAA0G;iFASqB8jDAA/HQxjCAtFqfP5Se,A,A;oVsFvURwbAtFCHh6DyD,O;sCsFDGg6DAtFCHh6DgD,A;qsDsFmD6Bw+Ce;mDAGpCm2CwB;AADG30FiE;OAAAAoB;wnCA3JLg/BAxG/H4Bh/BsI,A;8YwGiIhBlwD2G;AAAA+5FmEnB0BV8eAA3FaqkCkI,A,gLA8FNvuBwB,AAAA3uHyH,mI;gLmB7BG+5FAnB6BH/5FmE,A;uTmBxBSAmD;4CAAAACnB5BRA0B,c;ghDmB2HN8kJqB;SAAAAAA/KuBxjCAtFqfP5S4D,A,A;AsFrUVx+CgB;mnBAuDOlwD2D;4CAAAACnBnLPA0B,c;w9DjBhFC0uGsB;QAAmB4SAlDihBV5SuB,A;uPkD1ffwbAlDoLIh6DoH,O;sFkDnLNm8BkE;4CACA6wBwD;8yBiB7BiB7vCAAkDEmxEAAAAtuFA7D4qBCA2BFjvBvBAAAtB0B05BAAAA15BeA4MLlwDwE,A,A,A,A,A,A,4B;W+DnKDqtEAAmCH43EAAAA/0FoB,A,AAgCmBg1F8C,AAOpBCAAAAj1FgB,A,AAyCWwtF6D,AAGE0HuD,AAEjC/3EAAAAAkJ,A,A;SAvHiBqfAAoBoBAU,A;0MAdlBrfqB;qWAkDErtE6B;sQAgGf64GAA/GaqkCmBAdQl9I6B,wB,kBAcuB63FO/DhElB0gDuB,A,A;+S+D8MhBroFAA2NG+qFAAAA/qFA7D2UQAAFjvBvBAAAtB0B05BAAAA15BoBA4MLlwD2C,A,A,A,A,A,A,8B+DgPNi7IAAAA/qFA7D2UQAwF,A,A,A;yoB6DzgBrBw8B+B;kCAAS6YAmGlNQoxB6B,uC;0GnGsNPjqC+B;kCAAS7uFM;kkBAkEJ6uF8C;kCAASiqC2C;AAAMx/BwFoG3TyBgJiE,2E;gUpGsUtDzT+B;gDAAS7uFM;SAAc0yFsC;yOAMP+wBAnEyOC5SyB,A;uDmEvOpBmXmD;8CASEn5B+B;kCAAStB6B;udAcqBl7BAA8EjB+qFAAAA/qFA7D2UQAAFjvBvBAAAtB0B05BAAAA15BAA4MLlwDyD,A,A,A,A,A,A,A;A+DkNd0sF+B;8FAhDyBx8BoBA8EjB+qFAAAA/qFA7D2UQAsBFjvBvBAAAtB0B05BAAAA15B+E,A,A,A,A,A,A;A+D8ZVryDM;SAAc0yF+C;++GAzJQ9wCAc7EnBi1D8B,A;Qd6EmBj1DAc7EnBi1D2F,qBACA4KqE,iDACK/FwI,2R;4fdoFlB7sB+B;kCAAS6YAmGlOIoxB6B,uC;iCnGmOC32HgB;AAASkwDuB;ueA8DhB06CkCAgIC/SO/D1bc0gDuB,A,uB;mH+D+V5B+Ma;kIAAAAwF;uoEAwBkCvyJgC;0+CAR5B64H6H;0SA0BmD25BI;MAAAAmCAeLxyJyC,A;mMA0BvC8kGO/Dlbe0gDuB,A;8L6DcViN6HA6ITrEmBAtE4B5T4B,A,qJ;SAvEnBiYAAoJb94DU,kCAASuoBAAvJyBAiB,A,A;uIA4GrCksCkCAlCmC5T4B,A;cAoCtBr9EqC;iNAWbixFmBA/CmC5T4B,A;wSAwDdvvBARxCFmiCqC,A;yCQ6ChBptJA5D+sCPqmJqB,A;yO4DtsCW+HmBAtE4B5T4B,A;2KA6EhC7gDU;kCAASuoBAAvJyBAiB,A;oGAiKrCksCkCAvFmC5T4B,A;YAuFjBt4BiB;wMAWfAiB;oTd2DFiVArEyJmC78C8D,A;AqExJnC6vCiB;AADAgNArEyJmC78CiD,O;qDqExJnC6vCiD;AAGWxOe;snGA+FJjeoG;AAAwCnE0C;8qBAmBK4yBkB;k5BAtIO7xCyD;oDACxCnb0BuF5ORhCaINmBA6B,A,A;4B3FkPXgCY;+mEA4HlBmbe;oiCkBvRFojBkF;0CACoBpjBuB;wdA/D2Bnb2D;oDAAAAAqEnBnChCaINmBAiC,A,A;azEyBgBgCU;krCAkD1BwmEsE;8pC3B5BNhhIA6B9BN0rDAGHKqiGuC,A,0GHGLriGO,gCAAAAAGHKqiG0F,A,6F;kjBhCqCNnkCA5BguCU5SoC,A;6YyJrwCGxJgC;eAAuBAgC;6MAKV5YK;qsBxF0CZp8BACtEFitFAAAAjtFuC,A,A;iCDsEEAuD;OAGYo8BAC7DnBAkB,A;wc3B8GgBp8BuB;mGACE4kC0E;iBAAR5kCc;AAAQ4kCAlDErBstDe,2C;0MkDDDjH4B;+MAQIrmDsD;UAAAAAlDPHstDoB,gE;20BkCqDNllCArCsZG6hCsC,A;4IqCjZIzyDqF;2VAGE49BArCmERh6DuD,O;kEqCnEQg6DArCmERh6D8C,A;6cqCzCKo8B2E;0YAYP27CiC4BzM8Bt6CARuFbz9B2B,oBAAAAgC,A,A;4EpB0HjBiiC2B4B3MsDxEARiFrCz9B2B,oBAAAAgC,A,A;sFpBoIjBkgD6B4BlNuDziBAR8EtCz9B2B,oBAAAAgC,A,A;+hBpB7DhBo/EgBCmC8B7uBS,AAAQ6uBgB,A;wSD3B9Cp/EAK3CUw1FgL,A;AL2CVxnDsC;+JAAAA+D;AAAAhuC8BKvCgBy1FsE,A;+jFL+DoBh4DACCQAAG9ENAoB/Bgddz9BkC,A,A,A;A2BnYRAAgGyBiCmdAARZw4EoC,AAKECqF,A,A;0WhGlB7B5e6T;qNAW+C6egC;ifAUjC/lJgF;2cAObghG8J;kHAG6B33BoC;+CAAAA4P;mHADxBnZuE;yCAFL8wCwG;sZA1BQ0lBO;AAAM4oB2B;gGAKCtvIoCG5ErBAyB,8B;sLH4EqBg2HyIGlDLkQsV2HKvB15BS,gBAAAAA5BEIw5CoE,2K,A,kO;iYlGwE6BCEA8CzB3WgBCnFgC7uBS,AAAQ6uBgB,A,mE;ADqCfjiE6B;YAAA44EqH;4vCAoG/Bh7CI;gpBAgB0BjrGoCKtNrBAyB,8C;gPLuNuBnCM;SAAcwyF8D;gjBAc1CwlBI;k3BG3NgB3lDA2H3BKg2FAAAAh2FA/BSTi2FAAAAj2FuE,A,A,A,A+BNOk2FAAAAl2FA5BMIo/EAAAAp/EmE,A,A,A,yC4BTFg2FAAAAh2F0D,A,AAGFk2FAAAAl2FmH,A,AAuBvBAAAAAAiDACaAAtF6BTAuE,A,A,A,A;oFrC3BiBi6BmC;iGiIxBGiiDa;oBAAAA4B;uEAGUl9CAvDiCNh/Ba,A;0DuDjCMg/BAvDiCNh/B+D,A;+ehDrCLlwD4BCwHTA0B,qB;ADxHS4rH4E;sGeuCjBgYuC;ga3B8B+BzzEAhDuKxB27BwBFnCwC9rF8C,AAArD8rFAAAAAiC,A,A,A;qhDkDrDa/uBeO7HF64DmC,A;2BP6HQoTaAUsB5LkC,A;AAVdnsCcAcgBmsCkC,A;AAdP57BaAkBK47B6B,A;AAlBGltEkB;AAAYq3CAAsB3Br3CgC,8J;oHAiCpB05B0C;mZAsCyB+DA5CyNNz9B2C,oBAAAAiC,A;qnD4CtKKq7CyI;ueAMepLmN;iGAKtB+cAtD+QX6hCAAIPsHuC,4tB,A;i9CsDnQMxhDiLa3RUx3BAAmEgB63E+C,AAgDTxH+B,AAGE0H8C,AAEjC/3EAAAAAAAGgBndAF3FYusFwF,AAuD0Bx+EqJ5EmT7Cw7EmH,A,A,yD8E/QOvpFAFxFGurFyY,A,A,A,A,A;q0CXmQMxwCiG;spEAcvBT4E;wOAQwBy9BAWnVet6CoBRuFbz9BgC,A,A;umCHoRUgxE+H;2hFA8GFmCiC;mCAAAAA2CtdK11CAxCoFbz9B2B,oBAAAAkD,A,A;oEHmY1Bs6CwE;AAA2Bw+BA2CpdD94E4B,iC;sB3Cqd1Bs6CwE;AAA4BvZA2CldD/gC4B,kC;qB3Cmd3Bs6CwE;AAA2BhJA2ChdDtxC4B,qB;iK3CwDbymE8C;+BAAax2BiD;k6GAwLpBmN4K;AAEAipByV;m3BA0FQ7nBiC;iBAA4B4SAxC66BpB5S6C,A;2BwC56BlBlEkD;qyBAwCqBpayD;oa2CzTZlgCK;mEAEI05BkD;oQAaY2jD4B;6GAIbniD0C;iBAAAAsC;yKAKP68CiChChG8Bt6CARuFbz9B2B,oBAAAAgC,A,A;gKwC2BI+kDAhCtGWAwB,A;wwDgCmH7BhKkH;0nC0EPM0gCsB;gBAAW72EoD9ClGjB0tF6E,0E;Y8CkGiB1tFwD;cAAAAqF;QAIfyyCgB;AAAO6kCqC;0EAKSnEmE;8BAAAAA1GjISt6CARuFbz9B2B,oBAAAAgC,A,A;+HkHoDEiiC4E;YAAAAA1GrImCxEARiFrCz9B2B,oBAAAAgC,A,A;yFkHuDEkgD6B;YAAAAA1GrIoCziBAR8EtCz9B2B,oBAAAAgC,A,A;gPkHqEdq3CgErHCer3C+C,A;AqHDRoxDAzDhHG5SuC,A;AyDiHenHArHAVr3CiD,A;4LqHMdymE+D;4iBA4BSjoBqC;qFAGSnHArHrCJr3CiF,oB;AqHqCzBg/BAzD1KwBh/B8F,A;8HyD0KxBlwD+E;qFAKiBgpIarHtDoB5LqC,A;AqHsDb1uBuC;+QAcds6B8BrHpE2B5LmC,A;qEqHqE3BnsCcrHjE6BmsCqC,A;wEqHkE7B57BarH9D2B47BuC,A;yXqHqET4LArH7ES5LmC,A;4DqH8ERnsC6BrH1EUmsCmC,A;6DqH2EX57B4BrHvES47BmC,A;gPqH8E1BltEkB;AADayuEsB;yCAIV2nBAA8BG5xCkB,6DAAAA4D,6EACF4KqE,A;4BA5BI0pBArH5FkB5L6C,A;iBqH4FvCn+Bc;sCAGYhOArH3F6BmsCmC,A;AqH2FrB9bsD;gCAGGrwBArH9FkBmsCqC,A;AqH8FvCn+Bc;+DAIUuCArH9F2B47BmC,A;AqH8FpB9bsD;6BAGI9fArHjGgB47BqC,A;AqHiGrCn+Bc;6DAMFAkD;0jBAiBoBphGM;09EChRlBgoH6B;sErGiDoCx4CoF;k8BuB5B5BgnBuM;AAFEnkCA4EKcq2FAAqDECgR,A,AApDJn5EAAOpBnd2H,A,AANuBCApKoMhB27BAFnCbAAAAAA0C,A,A,A,uD;gF0FnKS0eA+EjCPAY,A;A/EiCOqqBAxC4D0DyakC,A;AwC5D1D9kCA+EjCPAO,iD;A/EkCOSA+E9BPAmF,A;A/E+BiBw7CA6EsCOp5EAAvCPs5EAAAAz2FA9C1BnBAAAAAAAAC0B22F+H,A,A,A,A,A8CiDiBnL8Y,AAmB3CruEAAAAAuCAW6Bg2DArH6D6B11CAEjJtBAoBC2FRz9BgC,A,A,A,kIkHHC4oCqD,uH,A,A,A;i5qFzL2zCViuDoL;oZAUACwL;kZAUACsH;waAUAC0H;+tBqCn8BgCzM8C;6xEoFpc7B5tFAD3DYktF4C,A;oF/DAP7pFiB8GKFi9D6B,AACK92BiC,AACV0mC6B,A;wF9GAS7sEmBgHGJi9DmC,AACK92BqC,AACV0mC6E,AACQ/yB8C,A;gFhHEH95Ce+GVAi9D6B,AACK92B+D,AACV0mC2D,AACQ/yB6B,A;8gB3GHT95Cc;spD0CqBWAc;09C/C3BZAc;qFFHAAc;ilBoDesBmdyb;OAAAA0B;kwBxGmIbteAAqE3BgkF4B,A;"}}
\ No newline at end of file
diff --git a/pkgs/coverage/test/test_files/test_app.dart b/pkgs/coverage/test/test_files/test_app.dart
new file mode 100644
index 0000000..1e50703
--- /dev/null
+++ b/pkgs/coverage/test/test_files/test_app.dart
@@ -0,0 +1,61 @@
+// Copyright (c) 2015, 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.
+
+// coverage:ignore-file
+
+import 'dart:async';
+import 'dart:developer';
+import 'dart:isolate';
+
+import 'package:coverage/src/util.dart';
+
+import 'test_app_isolate.dart';
+
+part 'test_app.g.dart';
+
+Future<void> main() async {
+ for (var i = 0; i < 10; i++) {
+ for (var j = 0; j < 10; j++) {
+ final sum = usedMethod(i, j);
+ if (sum != (i + j)) {
+ throw 'bad method!';
+ }
+
+ final multiplication = usedGeneratedMethod(i, j);
+ if (multiplication != i * j) {
+ throw 'bad generated method!';
+ }
+ }
+ }
+
+ final port = ReceivePort();
+
+ final isolate =
+ await Isolate.spawn(isolateTask, [port.sendPort, 1, 2], paused: true);
+ await Service.controlWebServer(enable: true);
+ final isolateID = Service.getIsolateID(isolate);
+ print('isolateId = $isolateID');
+
+ isolate.addOnExitListener(port.sendPort);
+ isolate.resume(isolate.pauseCapability!);
+
+ final value = await port.first as int;
+ if (value != 3) {
+ throw 'expected 3!';
+ }
+
+ final result = await retry(() async => 42, const Duration(seconds: 1)) as int;
+ print(result);
+}
+
+int usedMethod(int a, int b) {
+ return a + b;
+}
+
+int unusedMethod(int a, int b) {
+ return a - b;
+}
+
+// ignore_for_file: unreachable_from_main, only_throw_errors
+// ignore_for_file: deprecated_member_use
diff --git a/pkgs/coverage/test/test_files/test_app.g.dart b/pkgs/coverage/test/test_files/test_app.g.dart
new file mode 100644
index 0000000..c80718a
--- /dev/null
+++ b/pkgs/coverage/test/test_files/test_app.g.dart
@@ -0,0 +1,5 @@
+part of 'test_app.dart';
+
+int usedGeneratedMethod(int a, int b) {
+ return a * b;
+}
diff --git a/pkgs/coverage/test/test_files/test_app_isolate.dart b/pkgs/coverage/test/test_files/test_app_isolate.dart
new file mode 100644
index 0000000..8bbc6be
--- /dev/null
+++ b/pkgs/coverage/test/test_files/test_app_isolate.dart
@@ -0,0 +1,73 @@
+// Copyright (c) 2015, 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';
+import 'dart:isolate';
+
+const int answer = 42;
+
+String fooSync(int x) {
+ if (x == answer) {
+ return '*' * x;
+ }
+ return List.generate(x, (_) => 'xyzzy').join(' ');
+}
+
+class BarClass {
+ BarClass(this.x);
+
+ int x;
+
+ void baz() {
+ print(x);
+ }
+}
+
+Future<String> fooAsync(int x) async {
+ if (x == answer) {
+ return '*' * x;
+ }
+ return List.generate(x, (_) => 'xyzzy').join(' ');
+}
+
+/// The number of covered lines is tested and expected to be 4.
+///
+/// If you modify this method, you may have to update the tests!
+void isolateTask(List threeThings) async {
+ sleep(const Duration(milliseconds: 500));
+
+ fooSync(answer);
+ unawaited(fooAsync(answer).then((_) {
+ final port = threeThings.first as SendPort;
+ final sum = (threeThings[1] as int) + (threeThings[2] as int);
+ port.send(sum);
+ }));
+
+ final bar = BarClass(123);
+ bar.baz();
+
+ print('678'); // coverage:ignore-line
+
+ // coverage:ignore-start
+ print('1');
+ print('2');
+ print('3');
+ // coverage:ignore-end
+
+ print('4 // coverage:ignore-line');
+ print('5 // coverage:ignore-file');
+
+ print('6'); // coverage:ignore-start
+ print('7');
+ print('8');
+ // coverage:ignore-end
+ print('9'); // coverage:ignore-start
+ print('10');
+ print('11'); // coverage:ignore-line
+
+ // Regression test for https://github.com/dart-lang/tools/issues/520.
+ await Isolate.run(() => print('Isolate.run'), debugName: 'Grandchild');
+ // coverage:ignore-end
+}
diff --git a/pkgs/coverage/test/test_files/test_library.dart b/pkgs/coverage/test/test_files/test_library.dart
new file mode 100644
index 0000000..d2fe49f
--- /dev/null
+++ b/pkgs/coverage/test/test_files/test_library.dart
@@ -0,0 +1,9 @@
+// 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.
+
+part 'test_library_part.dart';
+
+int libraryFunction() {
+ return 123;
+}
diff --git a/pkgs/coverage/test/test_files/test_library_part.dart b/pkgs/coverage/test/test_files/test_library_part.dart
new file mode 100644
index 0000000..c99876d
--- /dev/null
+++ b/pkgs/coverage/test/test_files/test_library_part.dart
@@ -0,0 +1,9 @@
+// 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.
+
+part of 'test_library.dart';
+
+int otherLibraryFunction() {
+ return 123;
+}
diff --git a/pkgs/coverage/test/test_util.dart b/pkgs/coverage/test/test_util.dart
new file mode 100644
index 0000000..e0a3edb
--- /dev/null
+++ b/pkgs/coverage/test/test_util.dart
@@ -0,0 +1,91 @@
+// Copyright (c) 2015, 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';
+
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+import 'package:test_process/test_process.dart';
+
+final String testAppPath = p.join('test', 'test_files', 'test_app.dart');
+
+const Duration timeout = Duration(seconds: 20);
+
+Future<TestProcess> runTestApp(int openPort) => TestProcess.start(
+ Platform.resolvedExecutable,
+ [
+ '--enable-vm-service=$openPort',
+ '--pause_isolates_on_exit',
+ '--branch-coverage',
+ testAppPath
+ ],
+ );
+
+List<Map<String, dynamic>> coverageDataFromJson(Map<String, dynamic> json) {
+ expect(json.keys, unorderedEquals(<String>['type', 'coverage']));
+ expect(json, containsPair('type', 'CodeCoverage'));
+
+ return (json['coverage'] as List).cast<Map<String, dynamic>>();
+}
+
+final _versionPattern = RegExp('([0-9]+)\\.([0-9]+)\\.([0-9]+)');
+
+bool platformVersionCheck(int minMajor, int minMinor) {
+ final match = _versionPattern.matchAsPrefix(Platform.version);
+ if (match == null) return false;
+ if (match.groupCount < 3) return false;
+ final major = int.parse(match.group(1)!);
+ final minor = int.parse(match.group(2)!);
+ return major > minMajor || (major == minMajor && minor >= minMinor);
+}
+
+/// Returns a mapping of (URL: (function_name: hit_count)) from [sources].
+Map<String, Map<String, int>> functionInfoFromSources(
+ Map<String, List<Map<dynamic, dynamic>>> sources,
+) {
+ Map<int, String> getFuncNames(List list) {
+ return {
+ for (var i = 0; i < list.length; i += 2)
+ list[i] as int: list[i + 1] as String,
+ };
+ }
+
+ Map<int, int> getFuncHits(List list) {
+ return {
+ for (var i = 0; i < list.length; i += 2)
+ list[i] as int: list[i + 1] as int,
+ };
+ }
+
+ return {
+ for (var entry in sources.entries)
+ entry.key: entry.value.fold(
+ {},
+ (previousValue, element) {
+ expect(element['source'], entry.key);
+ final names = getFuncNames(element['funcNames'] as List);
+ final hits = getFuncHits(element['funcHits'] as List);
+
+ for (var pair in hits.entries) {
+ previousValue[names[pair.key]!] =
+ (previousValue[names[pair.key]!] ?? 0) + pair.value;
+ }
+
+ return previousValue;
+ },
+ ),
+ };
+}
+
+extension ListTestExtension on List {
+ Map<String, List<Map<dynamic, dynamic>>> sources() => cast<Map>().fold(
+ <String, List<Map>>{},
+ (Map<String, List<Map>> map, value) {
+ final sourceUri = value['source'] as String;
+ map.putIfAbsent(sourceUri, () => <Map>[]).add(value);
+ return map;
+ },
+ );
+}
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
new file mode 100644
index 0000000..771c040
--- /dev/null
+++ b/pkgs/coverage/test/test_with_coverage_package/lib/validate_lib.dart
@@ -0,0 +1,19 @@
+// 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.
+
+int sum(Iterable<int> values) {
+ var val = 0;
+ for (var value in values) {
+ val += value;
+ }
+ return val;
+}
+
+int product(Iterable<int> values) {
+ var val = 1;
+ for (var value in values) {
+ val *= value;
+ }
+ return val;
+}
diff --git a/pkgs/coverage/test/test_with_coverage_package/pubspec.yaml b/pkgs/coverage/test/test_with_coverage_package/pubspec.yaml
new file mode 100644
index 0000000..3fa7ee0
--- /dev/null
+++ b/pkgs/coverage/test/test_with_coverage_package/pubspec.yaml
@@ -0,0 +1,12 @@
+name: coverage_integration_test_for_test_with_coverage
+publish_to: none
+
+environment:
+ sdk: '>=2.15.0 <3.0.0'
+
+dev_dependencies:
+ test: ^1.16.0
+
+dependency_overrides:
+ coverage:
+ path: ../../
diff --git a/pkgs/coverage/test/test_with_coverage_package/test/product_test.dart b/pkgs/coverage/test/test_with_coverage_package/test/product_test.dart
new file mode 100644
index 0000000..91eac53
--- /dev/null
+++ b/pkgs/coverage/test/test_with_coverage_package/test/product_test.dart
@@ -0,0 +1,14 @@
+// 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() {
+ test('product', () {
+ expect(product([2, 3]), 6);
+ });
+}
diff --git a/pkgs/coverage/test/test_with_coverage_package/test/sum_test.dart b/pkgs/coverage/test/test_with_coverage_package/test/sum_test.dart
new file mode 100644
index 0000000..f5b1ea4
--- /dev/null
+++ b/pkgs/coverage/test/test_with_coverage_package/test/sum_test.dart
@@ -0,0 +1,14 @@
+// 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() {
+ test('sum', () {
+ expect(sum([1, 2]), 3);
+ });
+}
diff --git a/pkgs/coverage/test/test_with_coverage_test.dart b/pkgs/coverage/test/test_with_coverage_test.dart
new file mode 100644
index 0000000..689452d
--- /dev/null
+++ b/pkgs/coverage/test/test_with_coverage_test.dart
@@ -0,0 +1,157 @@
+// 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.
+
+@Timeout(Duration(seconds: 60))
+library;
+
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+import 'package:test_process/test_process.dart';
+
+import 'test_util.dart';
+
+// this package
+final _pkgDir = p.absolute('');
+final _testWithCoveragePath = p.join(_pkgDir, 'bin', 'test_with_coverage.dart');
+
+// test package
+final _testPkgDirPath = p.join(_pkgDir, 'test', 'test_with_coverage_package');
+
+/// Override PUB_CACHE
+///
+/// Use a subdirectory different from `test/` just in case there is a problem
+/// with the clean up. If other packages are present under the `test/`
+/// subdirectory their tests may accidentally get run when running `dart test`
+final _pubCachePathInTestPkgSubDir = p.join(_pkgDir, 'var', 'pub-cache');
+final _env = {'PUB_CACHE': _pubCachePathInTestPkgSubDir};
+
+const _testPackageName = 'coverage_integration_test_for_test_with_coverage';
+
+int _port = 9300;
+
+Iterable<File> _dartFiles(String dir) =>
+ Directory(p.join(_testPkgDirPath, dir)).listSync().whereType<File>();
+
+String _fixTestFile(String content) => content.replaceAll(
+ "import '../lib/",
+ "import 'package:$_testPackageName/",
+ );
+
+void main() {
+ setUpAll(() async {
+ for (var dir in const ['lib', 'test']) {
+ await d.dir(dir, [
+ for (var dartFile in _dartFiles(dir))
+ d.file(
+ p.basename(dartFile.path),
+ _fixTestFile(dartFile.readAsStringSync()),
+ ),
+ ]).create();
+ }
+
+ var pubspecContent =
+ File(p.join(_testPkgDirPath, 'pubspec.yaml')).readAsStringSync();
+
+ expect(
+ pubspecContent.replaceAll('\r\n', '\n'),
+ contains(r'''
+dependency_overrides:
+ coverage:
+ path: ../../
+'''),
+ );
+
+ pubspecContent =
+ pubspecContent.replaceFirst('path: ../../', 'path: $_pkgDir');
+
+ await d.file('pubspec.yaml', pubspecContent).create();
+
+ final localPub = await _run(['pub', 'get']);
+ await localPub.shouldExit(0);
+ });
+
+ test('dart run bin/test_with_coverage.dart -f', () async {
+ final list = await _runTest(['run', _testWithCoveragePath, '-f']);
+
+ final sources = list.sources();
+ final functionHits = functionInfoFromSources(sources);
+
+ expect(
+ functionHits['package:$_testPackageName/validate_lib.dart'],
+ {
+ 'product': 1,
+ 'sum': 1,
+ },
+ );
+ });
+
+ test('dart run bin/test_with_coverage.dart -f -- -N sum', () async {
+ final list = await _runTest(
+ ['run', _testWithCoveragePath, '-f'],
+ extraArgs: ['--', '-N', 'sum'],
+ );
+
+ final sources = list.sources();
+ final functionHits = functionInfoFromSources(sources);
+
+ expect(
+ functionHits['package:$_testPackageName/validate_lib.dart'],
+ {
+ 'product': 0,
+ 'sum': 1,
+ },
+ reason: 'only `sum` tests should be run',
+ );
+ });
+
+ test('dart run coverage:test_with_coverage', () async {
+ await _runTest(['run', 'coverage:test_with_coverage']);
+ });
+
+ test('dart pub global run coverage:test_with_coverage', () async {
+ final globalPub =
+ await _run(['pub', 'global', 'activate', '-s', 'path', _pkgDir]);
+ await globalPub.shouldExit(0);
+
+ await _runTest(
+ ['pub', 'global', 'run', 'coverage:test_with_coverage'],
+ );
+ });
+}
+
+Future<TestProcess> _run(List<String> args) => TestProcess.start(
+ Platform.executable,
+ args,
+ workingDirectory: d.sandbox,
+ environment: _env,
+ );
+
+Future<List<Map<String, dynamic>>> _runTest(
+ List<String> invokeArgs, {
+ List<String>? extraArgs,
+}) async {
+ final process = await _run([
+ ...invokeArgs,
+ '--port',
+ '${_port++}',
+ ...?extraArgs,
+ ]);
+
+ await process.shouldExit(0);
+
+ await d.dir(
+ 'coverage',
+ [d.file('coverage.json', isNotEmpty), d.file('lcov.info', isNotEmpty)],
+ ).validate();
+
+ final coverageDataFile = File(p.join(d.sandbox, 'coverage', 'coverage.json'));
+
+ final json = jsonDecode(coverageDataFile.readAsStringSync());
+
+ return coverageDataFromJson(json as Map<String, dynamic>);
+}
diff --git a/pkgs/coverage/test/util_test.dart b/pkgs/coverage/test/util_test.dart
new file mode 100644
index 0000000..6a4e556
--- /dev/null
+++ b/pkgs/coverage/test/util_test.dart
@@ -0,0 +1,358 @@
+// Copyright (c) 2015, 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: only_throw_errors
+
+import 'dart:async';
+
+import 'package:coverage/src/util.dart';
+import 'package:test/test.dart';
+
+const _failCount = 5;
+const _delay = Duration(milliseconds: 10);
+
+void main() {
+ test('retry', () async {
+ var count = 0;
+ final stopwatch = Stopwatch()..start();
+
+ Future failCountTimes() async {
+ expect(stopwatch.elapsed, greaterThanOrEqualTo(_delay * count));
+
+ while (count < _failCount) {
+ count++;
+ throw 'not yet!';
+ }
+ return 42;
+ }
+
+ final value = await retry(failCountTimes, _delay) as int;
+
+ expect(value, 42);
+ expect(count, _failCount);
+ expect(stopwatch.elapsed, greaterThanOrEqualTo(_delay * count));
+ });
+
+ group('retry with timeout', () {
+ test('if it finishes', () async {
+ var count = 0;
+ final stopwatch = Stopwatch()..start();
+
+ Future failCountTimes() async {
+ expect(stopwatch.elapsed, greaterThanOrEqualTo(_delay * count));
+
+ while (count < _failCount) {
+ count++;
+ throw 'not yet!';
+ }
+ return 42;
+ }
+
+ final safeTimoutDuration = _delay * _failCount * 10;
+ final value = await retry(
+ failCountTimes,
+ _delay,
+ timeout: safeTimoutDuration,
+ ) as int;
+
+ expect(value, 42);
+ expect(count, _failCount);
+ expect(stopwatch.elapsed, greaterThanOrEqualTo(_delay * count));
+ });
+
+ test('if it does not finish', () async {
+ var count = 0;
+ final stopwatch = Stopwatch()..start();
+
+ var caught = false;
+ var countAfterError = 0;
+
+ Future failCountTimes() async {
+ if (caught) {
+ countAfterError++;
+ }
+ expect(stopwatch.elapsed, greaterThanOrEqualTo(_delay * count));
+
+ count++;
+ throw 'never';
+ }
+
+ final unsafeTimeoutDuration = _delay * (_failCount / 2);
+
+ try {
+ await retry(failCountTimes, _delay, timeout: unsafeTimeoutDuration);
+ // ignore: avoid_catching_errors
+ } on StateError catch (e) {
+ expect(e.message, 'Failed to complete within 25ms');
+ caught = true;
+
+ expect(countAfterError, 0,
+ reason: 'Execution should stop after a timeout');
+
+ await Future<dynamic>.delayed(_delay * 3);
+
+ expect(countAfterError, 0, reason: 'Even after a delay');
+ }
+
+ expect(caught, isTrue);
+ });
+ });
+
+ group('extractVMServiceUri', () {
+ test('returns null when not found', () {
+ expect(extractVMServiceUri('foo bar baz'), isNull);
+ });
+
+ test('returns null for an incorrectly formatted URI', () {
+ const msg = 'Observatory listening on :://';
+ expect(extractVMServiceUri(msg), null);
+ });
+
+ test('returns URI at end of string', () {
+ const msg = 'Observatory listening on http://foo.bar:9999/';
+ expect(extractVMServiceUri(msg), Uri.parse('http://foo.bar:9999/'));
+ });
+
+ test('returns URI with auth token at end of string', () {
+ const msg = 'Observatory listening on http://foo.bar:9999/cG90YXRv/';
+ expect(
+ extractVMServiceUri(msg), Uri.parse('http://foo.bar:9999/cG90YXRv/'));
+ });
+
+ test('return URI embedded within string', () {
+ const msg = '1985-10-26 Observatory listening on http://foo.bar:9999/ **';
+ expect(extractVMServiceUri(msg), Uri.parse('http://foo.bar:9999/'));
+ });
+
+ test('return URI with auth token embedded within string', () {
+ const msg =
+ '1985-10-26 Observatory listening on http://foo.bar:9999/cG90YXRv/ **';
+ expect(
+ extractVMServiceUri(msg), Uri.parse('http://foo.bar:9999/cG90YXRv/'));
+ });
+
+ test('handles new Dart VM service message format', () {
+ const msg =
+ 'The Dart VM service is listening on http://foo.bar:9999/cG90YXRv/';
+ expect(
+ extractVMServiceUri(msg), Uri.parse('http://foo.bar:9999/cG90YXRv/'));
+ });
+ });
+
+ group('getIgnoredLines', () {
+ const invalidSources = [
+ '''final str = ''; // coverage:ignore-start
+ final str = '';
+ final str = ''; // coverage:ignore-start
+ ''',
+ '''final str = ''; // coverage:ignore-start
+ final str = '';
+ final str = ''; // coverage:ignore-start
+ final str = ''; // coverage:ignore-end
+ final str = '';
+ final str = ''; // coverage:ignore-end
+ ''',
+ '''final str = ''; // coverage:ignore-start
+ final str = '';
+ final str = ''; // coverage:ignore-end
+ final str = '';
+ final str = ''; // coverage:ignore-end
+ ''',
+ '''final str = ''; // coverage:ignore-end
+ final str = '';
+ final str = ''; // coverage:ignore-start
+ final str = '';
+ final str = ''; // coverage:ignore-end
+ ''',
+ '''final str = ''; // coverage:ignore-end
+ final str = '';
+ final str = ''; // coverage:ignore-end
+ ''',
+ '''final str = ''; // coverage:ignore-end
+ final str = '';
+ final str = ''; // coverage:ignore-start
+ ''',
+ '''final str = ''; // coverage:ignore-end
+ ''',
+ '''final str = ''; // coverage:ignore-start
+ ''',
+ ];
+
+ test('throws FormatException when the annotations are not balanced', () {
+ void runTest(int index, String errMsg) {
+ final content = invalidSources[index].split('\n');
+ expect(
+ () => getIgnoredLines('content-$index.dart', content),
+ throwsA(
+ allOf(
+ isFormatException,
+ predicate((FormatException e) => e.message == errMsg),
+ ),
+ ),
+ reason: 'expected FormatException with message "$errMsg"',
+ );
+ }
+
+ runTest(
+ 0,
+ 'coverage:ignore-start found at content-0.dart:'
+ '3 before previous coverage:ignore-start ended',
+ );
+ runTest(
+ 1,
+ 'coverage:ignore-start found at content-1.dart:'
+ '3 before previous coverage:ignore-start ended',
+ );
+ runTest(
+ 2,
+ 'unmatched coverage:ignore-end found at content-2.dart:5',
+ );
+ runTest(
+ 3,
+ 'unmatched coverage:ignore-end found at content-3.dart:1',
+ );
+ runTest(
+ 4,
+ 'unmatched coverage:ignore-end found at content-4.dart:1',
+ );
+ runTest(
+ 5,
+ 'unmatched coverage:ignore-end found at content-5.dart:1',
+ );
+ runTest(
+ 6,
+ 'unmatched coverage:ignore-end found at content-6.dart:1',
+ );
+ runTest(
+ 7,
+ 'coverage:ignore-start found at content-7.dart:'
+ '1 has no matching coverage:ignore-end',
+ );
+ });
+
+ test(
+ 'returns [[0,lines.length]] 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]
+ ]);
+ }
+ });
+
+ test('returns [[0,lines.length]] 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]
+ ]);
+ });
+
+ test('return the correct range of lines ignored', () {
+ final lines = '''
+ final str = ''; // coverage:ignore-start
+ final str = ''; // coverage:ignore-line
+ final str = ''; // coverage:ignore-end
+ final str = ''; // coverage:ignore-start
+ final str = ''; // coverage:ignore-line
+ final str = ''; // coverage:ignore-end
+ '''
+ .split('\n');
+
+ expect(getIgnoredLines('', lines), [
+ [1, 3],
+ [4, 6],
+ ]);
+ });
+
+ test('return the correct list of lines ignored', () {
+ final lines = '''
+ final str = ''; // coverage:ignore-line
+ final str = ''; // coverage:ignore-line
+ final str = ''; // coverage:ignore-line
+ '''
+ .split('\n');
+
+ expect(getIgnoredLines('', lines), [
+ [1, 1],
+ [2, 2],
+ [3, 3],
+ ]);
+ });
+
+ test('ignore comments have no effect inside string literals', () {
+ final lines = '''
+ final str = '// coverage:ignore-file';
+ final str = '// coverage:ignore-line';
+ final str = ''; // coverage:ignore-line
+ final str = '// coverage:ignore-start';
+ final str = '// coverage:ignore-end';
+ '''
+ .split('\n');
+
+ expect(getIgnoredLines('', lines), [
+ [3, 3],
+ ]);
+ });
+
+ test('allow white-space after ignore comments', () {
+ // Using multiple strings, rather than splitting a multi-line string,
+ // because many code editors remove trailing white-space.
+ final lines = [
+ "final str = ''; // coverage:ignore-start ",
+ "final str = ''; // coverage:ignore-line\t",
+ "final str = ''; // coverage:ignore-end \t \t ",
+ "final str = ''; // coverage:ignore-line \t ",
+ "final str = ''; // coverage:ignore-start \t ",
+ "final str = ''; // coverage:ignore-end \t \t ",
+ ];
+
+ expect(getIgnoredLines('', lines), [
+ [1, 3],
+ [4, 4],
+ [5, 6],
+ ]);
+ });
+
+ test('allow omitting space after //', () {
+ final lines = [
+ "final str = ''; //coverage:ignore-start",
+ "final str = ''; //coverage:ignore-line",
+ "final str = ''; //coverage:ignore-end",
+ "final str = ''; //coverage:ignore-line",
+ "final str = ''; //coverage:ignore-start",
+ "final str = ''; //coverage:ignore-end",
+ ];
+
+ expect(getIgnoredLines('', lines), [
+ [1, 3],
+ [4, 4],
+ [5, 6],
+ ]);
+ });
+
+ test('allow text after ignore comments', () {
+ final lines = [
+ "final str = ''; // coverage:ignore-start due to XYZ",
+ "final str = ''; // coverage:ignore-line",
+ "final str = ''; // coverage:ignore-end due to XYZ",
+ "final str = ''; // coverage:ignore-line due to 123",
+ "final str = ''; // coverage:ignore-start",
+ "final str = ''; // coverage:ignore-end it is done",
+ ];
+
+ expect(getIgnoredLines('', lines), [
+ [1, 3],
+ [4, 4],
+ [5, 6],
+ ]);
+ });
+ });
+}
diff --git a/pkgs/csslib/.gitignore b/pkgs/csslib/.gitignore
new file mode 100644
index 0000000..c107fc6
--- /dev/null
+++ b/pkgs/csslib/.gitignore
@@ -0,0 +1,4 @@
+.dart_tool
+.packages
+pubspec.lock
+doc/api/
diff --git a/pkgs/csslib/CHANGELOG.md b/pkgs/csslib/CHANGELOG.md
new file mode 100644
index 0000000..366c3a6
--- /dev/null
+++ b/pkgs/csslib/CHANGELOG.md
@@ -0,0 +1,233 @@
+## 1.0.2
+
+- Require Dart 3.1
+- Move to `dart-lang/tools` monorepo.
+
+## 1.0.1
+
+- Update `ExpressionsProcessor.processFont` to handle null expressions.
+- Require Dart 3.0.
+
+## 1.0.0
+
+- Rev to `1.0.0` (note however that there are no API changes from `0.17.x`).
+
+## 0.17.3
+
+- Add markdown badges to the readme.
+- Adopted `package:dart_flutter_team_lints` linting rules.
+- Addressed an issue parsing font names not surrounded by quotes.
+- Fixed the reported span for `Expression` nodes.
+- Fixed a regression parsing declaration values containing spaces.
+- Add support for `lh` and `rlh` units.
+- Refactor the package example.
+- Addressed an issue with the indent level of the `CssPrinter` output.
+- Require Dart 2.19.
+
+## 0.17.2
+
+- Fixed a crash caused by `min()`, `max()` and `clamp()` functions that contain
+ mathematical expressions.
+- Add commas between PercentageTerms in keyframe rules.
+
+## 0.17.1
+
+- Fix `Color.css` constructor when there are double values in the `rgba` string.
+
+## 0.17.0
+
+- Migrate to null safety.
+- `Font.merge` and `BoxEdge.merge` are now static methods instead of factory
+ constructors.
+- Add a type on the `identList` argument to `TokenKind.matchList`.
+- Remove workaround for https://github.com/dart-lang/sdk/issues/43136, which is
+ now fixed.
+
+## 0.16.2
+
+- Added support for escape codes in identifiers.
+
+## 0.16.1
+
+- Fixed a crash caused by parsing certain calc() expressions and variables names that contain numbers.
+
+## 0.16.0
+
+- Removed support for the shadow-piercing comibnators `/deep/` and `>>>`. These
+ were dropped from the Shadow DOM specification.
+
+## 0.15.0
+
+- **BREAKING**
+ - Removed `css` executable from `bin` directory.
+ - Removed the deprecated `css.dart` library.
+ - `Message.level` is now of type `MessageLevel` defined in this package.
+- Removed dependencies on `package:args` and `package:logging`.
+- Require Dart SDK `>=2.1.0`.
+
+## 0.14.6
+
+* Removed whitespace between comma-delimited expressions in compact output.
+
+ Before:
+ ```css
+ div{color:rgba(0, 0, 0, 0.5);}
+ ```
+
+ After:
+ ```css
+ div{color:rgba(0,0,0,0.5);}
+ ```
+
+* Removed last semicolon from declaration groups in compact output.
+
+ Before:
+ ```css
+ div{color:red;background:blue;}
+ ```
+
+ After:
+ ```css
+ div{color:red;background:blue}
+ ```
+
+## 0.14.5
+
+* Fixed a crashed caused by parsing `:host()` without an argument and added an
+ error message explaining that a selector argument is expected.
+
+## 0.14.4+1
+
+* Set max SDK version to `<3.0.0`, and adjust other dependencies.
+
+## 0.14.4
+
+* Reduced whitespace in compact output for the `@page` at-rule and margin boxes.
+* Updated SDK version to 2.0.0-dev.17.0.
+* Stop using deprecated constants.
+
+## 0.14.3
+
+* Reduced the amount of whitespace in compact output around braces.
+
+## 0.14.2
+
+* Fixed Dart 2 runtime failure.
+
+## 0.14.1
+
+* Deprecated `package:csslib/css.dart`.
+ Use `parser.dart` and `visitor.dart` instead.
+
+## 0.14.0
+
+### New features
+
+* Supports nested at-rules.
+* Supports nested HTML comments in CSS comments and vice-versa.
+
+### Breaking changes
+
+* The `List<RuleSet> rulesets` field on `MediaDirective`, `HostDirective`, and
+ `StyletDirective` has been replaced by `List<TreeNode> rules` to allow nested
+ at-rules in addition to rulesets.
+
+## 0.13.6
+
+* Adds support for `@viewport`.
+* Adds support for `-webkit-calc()` and `-moz-calc()`.
+* Adds support for querying media features without specifying an expression. For
+ example: `@media (transform-3d) { ... }`.
+* Prevents exception being thrown for invalid dimension terms, and instead
+ issues an error.
+
+## 0.13.5
+
+* Adds support for `@-moz-document`.
+* Adds support for `@supports`.
+
+## 0.13.4
+
+* Parses CSS 2.1 pseudo-elements as pseudo-elements instead of pseudo-classes.
+* Supports signed decimal numbers with no integer part.
+* Fixes parsing hexadecimal numbers when followed by an identifier.
+* Fixes parsing strings which contain unicode-range character sequences.
+
+## 0.13.3+1
+
+* Fixes analyzer error.
+
+## 0.13.3
+
+* Adds support for shadow host selectors `:host()` and `:host-context()`.
+* Adds support for shadow-piercing descendant combinator `>>>` and its alias
+ `/deep/` for backwards compatibility.
+* Adds support for non-functional IE filter properties (i.e. `filter: FlipH`).
+* Fixes emitted CSS for `@page` directive when body includes declarations and
+ page-margin boxes.
+* Exports `Message` from `parser.dart` so it's no longer necessary to import
+ `src/messages.dart` to use the parser API.
+
+## 0.13.2+2
+
+* Fix static warnings.
+
+## 0.13.2+1
+
+* Fix new strong mode error.
+
+## 0.13.2
+
+* Relax type of TreeNode.visit, to allow returning values from visitors.
+
+## 0.13.1
+
+* Fix two checked mode bugs introduced in 0.13.0.
+
+## 0.13.0
+
+ * **BREAKING** Fix all [strong mode][] errors and warnings.
+ This involved adding more precise on some public APIs, which
+ is why it may break users.
+
+[strong mode]: https://github.com/dart-lang/dev_compiler/blob/master/STRONG_MODE.md
+
+## 0.12.2
+
+ * Fix to handle calc functions however, the expressions are treated as a
+ LiteralTerm and not fully parsed into the AST.
+
+## 0.12.1
+
+ * Fix to handling of escapes in strings.
+
+## 0.12.0+1
+
+* Allow the latest version of `logging` package.
+
+## 0.12.0
+
+* Top-level methods in `parser.dart` now take `PreprocessorOptions` instead of
+ `List<String>`.
+
+* `PreprocessorOptions.inputFile` is now final.
+
+## 0.11.0+4
+
+* Cleanup some ambiguous and some incorrect type signatures.
+
+## 0.11.0+3
+
+* Improve the speed and memory efficiency of parsing.
+
+## 0.11.0+2
+
+* Fix another test that was failing on IE10.
+
+## 0.11.0+1
+
+* Fix a test that was failing on IE10.
+
+## 0.11.0
+
+* Switch from `source_maps`' `Span` class to `source_span`'s `SourceSpan` class.
diff --git a/pkgs/csslib/LICENSE b/pkgs/csslib/LICENSE
new file mode 100644
index 0000000..ab3bfa0
--- /dev/null
+++ b/pkgs/csslib/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.
diff --git a/pkgs/csslib/README.md b/pkgs/csslib/README.md
new file mode 100644
index 0000000..edbd80d
--- /dev/null
+++ b/pkgs/csslib/README.md
@@ -0,0 +1,21 @@
+[](https://github.com/dart-lang/tools/actions/workflows/csslib.yaml)
+[](https://pub.dev/packages/csslib)
+[](https://pub.dev/packages/csslib/publisher)
+
+A Dart [CSS](https://developer.mozilla.org/en-US/docs/Web/CSS) parser.
+
+## Usage
+
+Parsing CSS is easy!
+
+```dart
+import 'package:csslib/parser.dart';
+
+void main() {
+ var stylesheet = parse(
+ '.foo { color: red; left: 20px; top: 20px; width: 100px; height:200px }');
+ print(stylesheet.toDebugString());
+}
+```
+
+You can pass a `String` or `List<int>` to `parse`.
diff --git a/pkgs/csslib/analysis_options.yaml b/pkgs/csslib/analysis_options.yaml
new file mode 100644
index 0000000..6c38fe4
--- /dev/null
+++ b/pkgs/csslib/analysis_options.yaml
@@ -0,0 +1,13 @@
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-casts: true
+ strict-inference: true
+ strict-raw-types: true
+ errors:
+ comment_references: ignore # too many false positives
+
+linter:
+ rules:
+ - prefer_expression_function_bodies
diff --git a/pkgs/csslib/example/main.dart b/pkgs/csslib/example/main.dart
new file mode 100644
index 0000000..88dc748
--- /dev/null
+++ b/pkgs/csslib/example/main.dart
@@ -0,0 +1,99 @@
+// 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
+
+import 'package:csslib/parser.dart' as css;
+import 'package:csslib/visitor.dart';
+
+void main() {
+ var errors = <css.Message>[];
+
+ // Parse a simple stylesheet.
+ print('1. Good CSS, parsed CSS emitted:');
+ print(' =============================');
+ var stylesheet = parseCss('''
+@import "support/at-charset-019.css";
+div { color: red; }
+button[type] { background-color: red; }
+.foo {
+ color: red; left: 20px; top: 20px; width: 100px; height:200px
+}
+#div {
+ color : #00F578; border-color: #878787;
+}
+''', errors: errors);
+
+ if (errors.isNotEmpty) {
+ print('Got ${errors.length} errors.\n');
+ for (var error in errors) {
+ print(error);
+ }
+ } else {
+ print(prettyPrint(stylesheet));
+ }
+
+ // Parse a stylesheet with errors
+ print('\n2. Catch severe syntax errors:');
+ print(' ===========================');
+ var stylesheetError = parseCss('''
+.foo #%^&*asdf {
+ color: red; left: 20px; top: 20px; width: 100px; height:200px
+}
+''', errors: errors);
+
+ if (errors.isNotEmpty) {
+ print('Got ${errors.length} errors.\n');
+ for (var error in errors) {
+ print(error);
+ }
+ } else {
+ print(stylesheetError.toString());
+ }
+
+ // Parse a stylesheet that warns (checks) problematic CSS.
+ print('\n3. Detect CSS problem with checking on:');
+ print(' ===================================');
+ stylesheetError = parseCss('# div1 { color: red; }', errors: errors);
+
+ if (errors.isNotEmpty) {
+ print('Detected ${errors.length} problem in checked mode.\n');
+ for (var error in errors) {
+ print(error);
+ }
+ } else {
+ print(stylesheetError.toString());
+ }
+
+ // Parse a CSS selector.
+ print('\n4. Parse a selector only:');
+ print(' ======================');
+ var selectorAst = css.selector('#div .foo', errors: errors);
+ if (errors.isNotEmpty) {
+ print('Got ${errors.length} errors.\n');
+ for (var error in errors) {
+ print(error);
+ }
+ } else {
+ print(prettyPrint(selectorAst));
+ }
+}
+
+/// Spin-up CSS parser in checked mode to detect any problematic CSS. Normally,
+/// CSS will allow any property/value pairs regardless of validity; all of our
+/// tests (by default) will ensure that the CSS is really valid.
+StyleSheet parseCss(
+ String cssInput, {
+ List<css.Message>? errors,
+ css.PreprocessorOptions? opts,
+}) =>
+ css.parse(cssInput, errors: errors, options: opts ?? _default);
+
+/// Pretty printer for CSS.
+String prettyPrint(StyleSheet ss) =>
+ (CssPrinter()..visitTree(ss, pretty: true)).toString();
+
+const _default = css.PreprocessorOptions(
+ useColors: false,
+ checked: true,
+ warningsAsErrors: true,
+ inputFile: 'memory',
+);
diff --git a/pkgs/csslib/lib/parser.dart b/pkgs/csslib/lib/parser.dart
new file mode 100644
index 0000000..4469298
--- /dev/null
+++ b/pkgs/csslib/lib/parser.dart
@@ -0,0 +1,2870 @@
+// 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:math' as math;
+
+import 'package:source_span/source_span.dart';
+
+import 'src/messages.dart';
+import 'src/preprocessor_options.dart';
+import 'visitor.dart';
+
+export 'src/messages.dart' show Message, MessageLevel;
+export 'src/preprocessor_options.dart';
+
+part 'src/analyzer.dart';
+part 'src/polyfill.dart';
+part 'src/property.dart';
+part 'src/token.dart';
+part 'src/token_kind.dart';
+part 'src/tokenizer.dart';
+part 'src/tokenizer_base.dart';
+
+enum ClauseType {
+ none,
+ conjunction,
+ disjunction,
+}
+
+/// Used for parser lookup ahead (used for nested selectors Less support).
+class ParserState extends TokenizerState {
+ final Token peekToken;
+ final Token? previousToken;
+
+ ParserState(this.peekToken, this.previousToken, Tokenizer tokenizer)
+ : super(tokenizer);
+}
+
+void _createMessages({List<Message>? errors, PreprocessorOptions? options}) {
+ errors ??= [];
+ options ??= const PreprocessorOptions(useColors: false, inputFile: 'memory');
+
+ messages = Messages(options: options, printHandler: errors.add);
+}
+
+/// CSS checked mode enabled.
+bool get isChecked => messages.options.checked;
+
+// TODO(terry): Remove nested name parameter.
+/// Parse and analyze the CSS file.
+StyleSheet compile(Object input,
+ {List<Message>? errors,
+ PreprocessorOptions? options,
+ bool nested = true,
+ bool polyfill = false,
+ List<StyleSheet>? includes}) {
+ includes ??= [];
+
+ var source = _inputAsString(input);
+
+ _createMessages(errors: errors, options: options);
+
+ var file = SourceFile.fromString(source);
+
+ var tree = _Parser(file, source).parse();
+
+ analyze([tree], errors: errors, options: options);
+
+ if (polyfill) {
+ var processCss = PolyFill(messages);
+ processCss.process(tree, includes: includes);
+ }
+
+ return tree;
+}
+
+/// Analyze the CSS file.
+void analyze(List<StyleSheet> styleSheets,
+ {List<Message>? errors, PreprocessorOptions? options}) {
+ _createMessages(errors: errors, options: options);
+ Analyzer(styleSheets, messages).run();
+}
+
+/// Parse the [input] CSS stylesheet into a tree.
+///
+/// The [input] can be a [String], or [List]`<int>` of bytes and returns a
+/// [StyleSheet] AST. The optional [errors] list will collect any error
+/// encountered.
+StyleSheet parse(
+ Object input, {
+ List<Message>? errors,
+ PreprocessorOptions? options,
+}) {
+ var source = _inputAsString(input);
+
+ _createMessages(errors: errors, options: options);
+
+ var file = SourceFile.fromString(source);
+ return _Parser(file, source).parse();
+}
+
+/// Parse the [input] CSS selector into a tree. The [input] can be a [String],
+/// or [List]`<int>` of bytes and returns a [StyleSheet] AST. The optional
+/// [errors] list will contain each error/warning as a [Message].
+// TODO(jmesserly): should rename "parseSelector" and return Selector
+StyleSheet selector(Object input, {List<Message>? errors}) {
+ var source = _inputAsString(input);
+
+ _createMessages(errors: errors);
+
+ var file = SourceFile.fromString(source);
+ return (_Parser(file, source)..tokenizer.inSelector = true).parseSelector();
+}
+
+SelectorGroup? parseSelectorGroup(Object input, {List<Message>? errors}) {
+ var source = _inputAsString(input);
+
+ _createMessages(errors: errors);
+
+ var file = SourceFile.fromString(source);
+ return (_Parser(file, source)
+ // TODO(jmesserly): this fix should be applied to the parser. It's
+ // tricky because by the time the flag is set one token has already
+ // been fetched.
+ ..tokenizer.inSelector = true)
+ .processSelectorGroup();
+}
+
+String _inputAsString(Object input) {
+ String source;
+
+ if (input is String) {
+ source = input;
+ } else if (input is List) {
+ // TODO(terry): The parse function needs an "encoding" argument and will
+ // default to whatever encoding CSS defaults to.
+ //
+ // Here's some info about CSS encodings:
+ // http://www.w3.org/International/questions/qa-css-charset.en.php
+ //
+ // As JMesserly suggests it will probably need a "pre-parser" html5lib
+ // (encoding_parser.dart) that interprets the bytes as ASCII and scans for
+ // @charset. But for now an "encoding" argument would work. Often the
+ // HTTP header will indicate the correct encoding.
+ //
+ // See encoding helpers at: package:html5lib/lib/src/char_encodings.dart
+ // These helpers can decode in different formats given an encoding name
+ // (mostly unicode, ascii, windows-1252 which is html5 default encoding).
+ source = String.fromCharCodes(input as List<int>);
+ } else {
+ // TODO(terry): Support RandomAccessFile using console.
+ throw ArgumentError("'source' must be a String or "
+ 'List<int> (of bytes). RandomAccessFile not supported from this '
+ 'simple interface');
+ }
+
+ return source;
+}
+
+// TODO(terry): Consider removing this class when all usages can be eliminated
+// or replaced with compile API.
+/// Public parsing interface for csslib.
+class Parser {
+ final _Parser _parser;
+
+ // TODO(jmesserly): having file and text is redundant.
+ // TODO(rnystrom): baseUrl isn't used. Remove from API.
+ Parser(SourceFile file, String text, {int start = 0, String? baseUrl})
+ : _parser = _Parser(file, text, start: start);
+
+ StyleSheet parse() => _parser.parse();
+}
+
+// CSS2.1 pseudo-elements which were defined with a single ':'.
+const _legacyPseudoElements = <String>{
+ 'after',
+ 'before',
+ 'first-letter',
+ 'first-line',
+};
+
+/// A simple recursive descent parser for CSS.
+class _Parser {
+ final Tokenizer tokenizer;
+
+ /// File containing the source being parsed, used to report errors with
+ /// source-span locations.
+ final SourceFile file;
+
+ Token? _previousToken;
+ late Token _peekToken;
+
+ _Parser(this.file, String text, {int start = 0})
+ : tokenizer = Tokenizer(file, text, true, start) {
+ _peekToken = tokenizer.next();
+ }
+
+ /// Main entry point for parsing an entire CSS file.
+ StyleSheet parse() {
+ var productions = <TreeNode>[];
+
+ var start = _peekToken.span;
+ while (!_maybeEat(TokenKind.END_OF_FILE) && !_peekKind(TokenKind.RBRACE)) {
+ // TODO(terry): Need to handle charset.
+ final rule = processRule();
+ if (rule != null) {
+ productions.add(rule);
+ } else {
+ break;
+ }
+ }
+
+ checkEndOfFile();
+
+ return StyleSheet(productions, _makeSpan(start));
+ }
+
+ /// Main entry point for parsing a simple selector sequence.
+ StyleSheet parseSelector() {
+ var productions = <TreeNode>[];
+
+ var start = _peekToken.span;
+ while (!_maybeEat(TokenKind.END_OF_FILE) && !_peekKind(TokenKind.RBRACE)) {
+ var selector = processSelector();
+ if (selector != null) {
+ productions.add(selector);
+ } else {
+ break; // Prevent infinite loop if we can't parse something.
+ }
+ }
+
+ checkEndOfFile();
+
+ return StyleSheet.selector(productions, _makeSpan(start));
+ }
+
+ /// Generate an error if [file] has not been completely consumed.
+ void checkEndOfFile() {
+ if (!(_peekKind(TokenKind.END_OF_FILE) ||
+ _peekKind(TokenKind.INCOMPLETE_COMMENT))) {
+ _error('premature end of file unknown CSS', _peekToken.span);
+ }
+ }
+
+ /// Guard to break out of parser when an unexpected end of file is found.
+ // TODO(jimhug): Failure to call this method can lead to infinite parser
+ // loops. Consider embracing exceptions for more errors to reduce
+ // the danger here.
+ bool isPrematureEndOfFile() {
+ if (_maybeEat(TokenKind.END_OF_FILE)) {
+ _error('unexpected end of file', _peekToken.span);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////
+ // Basic support methods
+ ///////////////////////////////////////////////////////////////////
+ int _peek() => _peekToken.kind;
+
+ Token _next({bool unicodeRange = false}) {
+ final next = _previousToken = _peekToken;
+ _peekToken = tokenizer.next(unicodeRange: unicodeRange);
+ return next;
+ }
+
+ bool _peekKind(int kind) => _peekToken.kind == kind;
+
+ // Is the next token a legal identifier? This includes pseudo-keywords.
+ bool _peekIdentifier() => TokenKind.isIdentifier(_peekToken.kind);
+
+ /// Marks the parser/tokenizer look ahead to support Less nested selectors.
+ ParserState get _mark => ParserState(_peekToken, _previousToken, tokenizer);
+
+ /// Restores the parser/tokenizer state to state remembered by _mark.
+ void _restore(ParserState markedData) {
+ tokenizer.restore(markedData);
+ _peekToken = markedData.peekToken;
+ _previousToken = markedData.previousToken;
+ }
+
+ bool _maybeEat(int kind, {bool unicodeRange = false}) {
+ if (_peekToken.kind == kind) {
+ _previousToken = _peekToken;
+ _peekToken = tokenizer.next(unicodeRange: unicodeRange);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ void _eat(int kind, {bool unicodeRange = false}) {
+ if (!_maybeEat(kind, unicodeRange: unicodeRange)) {
+ _errorExpected(TokenKind.kindToString(kind));
+ }
+ }
+
+ void _errorExpected(String expected) {
+ var tok = _next();
+ String message;
+ try {
+ message = 'expected $expected, but found $tok';
+ } catch (e) {
+ message = 'parsing error expected $expected';
+ }
+ _error(message, tok.span);
+ }
+
+ void _error(String message, SourceSpan? location) {
+ location ??= _peekToken.span;
+ messages.error(message, location);
+ }
+
+ void _warning(String message, SourceSpan? location) {
+ location ??= _peekToken.span;
+ messages.warning(message, location);
+ }
+
+ FileSpan _makeSpan(FileSpan start) {
+ // TODO(terry): there are places where we are creating spans before we eat
+ // the tokens, so using _previousToken is not always valid.
+ // TODO(nweiz): use < rather than compareTo when SourceSpan supports it.
+ if (_previousToken == null || _previousToken!.span.compareTo(start) < 0) {
+ return start;
+ }
+ return start.expand(_previousToken!.span);
+ }
+
+ ///////////////////////////////////////////////////////////////////
+ // Top level productions
+ ///////////////////////////////////////////////////////////////////
+
+ /// The media_query_list production below replaces the media_list production
+ /// from CSS2 the new grammar is:
+ ///
+ /// media_query_list
+ /// : S* [media_query [ ',' S* media_query ]* ]?
+ /// media_query
+ /// : [ONLY | NOT]? S* media_type S* [ AND S* expression ]*
+ /// | expression [ AND S* expression ]*
+ /// media_type
+ /// : IDENT
+ /// expression
+ /// : '(' S* media_feature S* [ ':' S* expr ]? ')' S*
+ /// media_feature
+ /// : IDENT
+ List<MediaQuery> processMediaQueryList() {
+ var mediaQueries = <MediaQuery>[];
+
+ do {
+ var mediaQuery = processMediaQuery();
+ if (mediaQuery != null) {
+ mediaQueries.add(mediaQuery);
+ } else {
+ break;
+ }
+ } while (_maybeEat(TokenKind.COMMA));
+
+ return mediaQueries;
+ }
+
+ MediaQuery? processMediaQuery() {
+ // Grammar: [ONLY | NOT]? S* media_type S*
+ // [ AND S* MediaExpr ]* | MediaExpr [ AND S* MediaExpr ]*
+
+ var start = _peekToken.span;
+
+ // Is it a unary media operator?
+ var op = _peekToken.text;
+ var opLen = op.length;
+ var unaryOp = TokenKind.matchMediaOperator(op, 0, opLen);
+ if (unaryOp != -1) {
+ if (isChecked) {
+ if (unaryOp != TokenKind.MEDIA_OP_NOT ||
+ unaryOp != TokenKind.MEDIA_OP_ONLY) {
+ _warning('Only the unary operators NOT and ONLY allowed',
+ _makeSpan(start));
+ }
+ }
+ _next();
+ start = _peekToken.span;
+ }
+
+ Identifier? type;
+ // Get the media type.
+ if (_peekIdentifier()) type = identifier();
+
+ var exprs = <MediaExpression>[];
+
+ while (true) {
+ // Parse AND if query has a media_type or previous expression.
+ var andOp = exprs.isNotEmpty || type != null;
+ if (andOp) {
+ op = _peekToken.text;
+ opLen = op.length;
+ if (TokenKind.matchMediaOperator(op, 0, opLen) !=
+ TokenKind.MEDIA_OP_AND) {
+ break;
+ }
+ _next();
+ }
+
+ var expr = processMediaExpression(andOp);
+ if (expr == null) break;
+
+ exprs.add(expr);
+ }
+
+ if (unaryOp != -1 || type != null || exprs.isNotEmpty) {
+ return MediaQuery(unaryOp, type, exprs, _makeSpan(start));
+ }
+ return null;
+ }
+
+ MediaExpression? processMediaExpression([bool andOperator = false]) {
+ var start = _peekToken.span;
+
+ // Grammar: '(' S* media_feature S* [ ':' S* expr ]? ')' S*
+ if (_maybeEat(TokenKind.LPAREN)) {
+ if (_peekIdentifier()) {
+ var feature = identifier(); // Media feature.
+ var exprs = _maybeEat(TokenKind.COLON)
+ ? processExpr()
+ : Expressions(_makeSpan(_peekToken.span));
+ if (_maybeEat(TokenKind.RPAREN)) {
+ return MediaExpression(andOperator, feature, exprs, _makeSpan(start));
+ } else if (isChecked) {
+ _warning(
+ 'Missing parenthesis around media expression', _makeSpan(start));
+ return null;
+ }
+ } else if (isChecked) {
+ _warning('Missing media feature in media expression', _makeSpan(start));
+ }
+ }
+ return null;
+ }
+
+ /// Directive grammar:
+ ///
+ /// import: '@import' [string | URI] media_list?
+ /// media: '@media' media_query_list '{' ruleset '}'
+ /// page: '@page' [':' IDENT]? '{' declarations '}'
+ /// stylet: '@stylet' IDENT '{' ruleset '}'
+ /// media_query_list: IDENT [',' IDENT]
+ /// keyframes: '@-webkit-keyframes ...' (see grammar below).
+ /// font_face: '@font-face' '{' declarations '}'
+ /// namespace: '@namespace name url("xmlns")
+ /// host: '@host '{' ruleset '}'
+ /// mixin: '@mixin name [(args,...)] '{' declarations/ruleset '}'
+ /// include: '@include name [(@arg,@arg1)]
+ /// '@include name [(@arg...)]
+ /// content: '@content'
+ /// -moz-document: '@-moz-document' [ <url> | url-prefix(<string>) |
+ /// domain(<string>) | regexp(<string) ]# '{'
+ /// declarations
+ /// '}'
+ /// supports: '@supports' supports_condition group_rule_body
+ Directive? processDirective() {
+ var start = _peekToken.span;
+
+ var tokId = processVariableOrDirective();
+ if (tokId is VarDefinitionDirective) return tokId;
+ final tokenId = tokId as int;
+ switch (tokenId) {
+ case TokenKind.DIRECTIVE_IMPORT:
+ _next();
+
+ // @import "uri_string" or @import url("uri_string") are identical; only
+ // a url can follow an @import.
+ String? importStr;
+ if (_peekIdentifier()) {
+ var func = processFunction(identifier());
+ if (func is UriTerm) {
+ importStr = func.text;
+ }
+ } else {
+ importStr = processQuotedString(false);
+ }
+
+ // Any medias?
+ var medias = processMediaQueryList();
+
+ if (importStr == null) {
+ _error('missing import string', _peekToken.span);
+ }
+
+ return ImportDirective(importStr!.trim(), medias, _makeSpan(start));
+
+ case TokenKind.DIRECTIVE_MEDIA:
+ _next();
+
+ // Any medias?
+ var media = processMediaQueryList();
+
+ var rules = <TreeNode>[];
+ if (_maybeEat(TokenKind.LBRACE)) {
+ while (!_maybeEat(TokenKind.END_OF_FILE)) {
+ final rule = processRule();
+ if (rule == null) break;
+ rules.add(rule);
+ }
+
+ if (!_maybeEat(TokenKind.RBRACE)) {
+ _error('expected } after ruleset for @media', _peekToken.span);
+ }
+ } else {
+ _error('expected { after media before ruleset', _peekToken.span);
+ }
+ return MediaDirective(media, rules, _makeSpan(start));
+
+ case TokenKind.DIRECTIVE_HOST:
+ _next();
+
+ var rules = <TreeNode>[];
+ if (_maybeEat(TokenKind.LBRACE)) {
+ while (!_maybeEat(TokenKind.END_OF_FILE)) {
+ final rule = processRule();
+ if (rule == null) break;
+ rules.add(rule);
+ }
+
+ if (!_maybeEat(TokenKind.RBRACE)) {
+ _error('expected } after ruleset for @host', _peekToken.span);
+ }
+ } else {
+ _error('expected { after host before ruleset', _peekToken.span);
+ }
+ return HostDirective(rules, _makeSpan(start));
+
+ case TokenKind.DIRECTIVE_PAGE:
+ // @page S* IDENT? pseudo_page?
+ // S* '{' S*
+ // [ declaration | margin ]?
+ // [ ';' S* [ declaration | margin ]? ]* '}' S*
+ //
+ // pseudo_page :
+ // ':' [ "left" | "right" | "first" ]
+ //
+ // margin :
+ // margin_sym S* '{' declaration [ ';' S* declaration? ]* '}' S*
+ //
+ // margin_sym : @top-left-corner, @top-left, @bottom-left, etc.
+ //
+ // See http://www.w3.org/TR/css3-page/#CSS21
+ _next();
+
+ // Page name
+ Identifier? name;
+ if (_peekIdentifier()) {
+ name = identifier();
+ }
+
+ // Any pseudo page?
+ Identifier? pseudoPage;
+ if (_maybeEat(TokenKind.COLON)) {
+ if (_peekIdentifier()) {
+ pseudoPage = identifier();
+ // TODO(terry): Normalize pseudoPage to lowercase.
+ if (isChecked &&
+ !(pseudoPage.name == 'left' ||
+ pseudoPage.name == 'right' ||
+ pseudoPage.name == 'first')) {
+ _warning(
+ 'Pseudo page must be left, top or first', pseudoPage.span);
+ return null;
+ }
+ }
+ }
+
+ var pseudoName = pseudoPage is Identifier ? pseudoPage.name : '';
+ var ident = name is Identifier ? name.name : '';
+ return PageDirective(
+ ident, pseudoName, processMarginsDeclarations(), _makeSpan(start));
+
+ case TokenKind.DIRECTIVE_CHARSET:
+ // @charset S* STRING S* ';'
+ _next();
+
+ var charEncoding = processQuotedString(false);
+ return CharsetDirective(charEncoding, _makeSpan(start));
+
+ // TODO(terry): Workaround Dart2js bug continue not implemented in switch
+ // see https://code.google.com/p/dart/issues/detail?id=8270
+ /*
+ case TokenKind.DIRECTIVE_MS_KEYFRAMES:
+ // TODO(terry): For now only IE 10 (are base level) supports @keyframes,
+ // -moz- has only been optional since Oct 2012 release of Firefox, not
+ // all versions of webkit support @keyframes and opera doesn't yet
+ // support w/o -o- prefix. Add more warnings for other prefixes when
+ // they become optional.
+ if (isChecked) {
+ _warning('@-ms-keyframes should be @keyframes', _makeSpan(start));
+ }
+ continue keyframeDirective;
+
+ keyframeDirective:
+ */
+ case TokenKind.DIRECTIVE_KEYFRAMES:
+ case TokenKind.DIRECTIVE_WEB_KIT_KEYFRAMES:
+ case TokenKind.DIRECTIVE_MOZ_KEYFRAMES:
+ case TokenKind.DIRECTIVE_O_KEYFRAMES:
+ // TODO(terry): Remove workaround when bug 8270 is fixed.
+ case TokenKind.DIRECTIVE_MS_KEYFRAMES:
+ if (tokenId == TokenKind.DIRECTIVE_MS_KEYFRAMES && isChecked) {
+ _warning('@-ms-keyframes should be @keyframes', _makeSpan(start));
+ }
+ // TODO(terry): End of workaround.
+
+ // Key frames grammar:
+ //
+ // @[browser]? keyframes [IDENT|STRING] '{' keyframes-blocks '}';
+ //
+ // browser: [-webkit-, -moz-, -ms-, -o-]
+ //
+ // keyframes-blocks:
+ // [keyframe-selectors '{' declarations '}']* ;
+ //
+ // keyframe-selectors:
+ // ['from'|'to'|PERCENTAGE] [',' ['from'|'to'|PERCENTAGE] ]* ;
+ _next();
+
+ Identifier? name;
+ if (_peekIdentifier()) {
+ name = identifier();
+ }
+
+ _eat(TokenKind.LBRACE);
+
+ var keyframe = KeyFrameDirective(tokenId, name, _makeSpan(start));
+
+ do {
+ var selectors = Expressions(_makeSpan(start));
+
+ do {
+ var term = processTerm() as Expression;
+
+ // TODO(terry): Only allow from, to and PERCENTAGE ...
+
+ selectors.add(term);
+ } while (_maybeEat(TokenKind.COMMA));
+
+ keyframe.add(KeyFrameBlock(
+ selectors, processDeclarations(), _makeSpan(start)));
+ } while (!_maybeEat(TokenKind.RBRACE) && !isPrematureEndOfFile());
+
+ return keyframe;
+
+ case TokenKind.DIRECTIVE_FONTFACE:
+ _next();
+ return FontFaceDirective(processDeclarations(), _makeSpan(start));
+
+ case TokenKind.DIRECTIVE_STYLET:
+ // Stylet grammar:
+ //
+ // @stylet IDENT '{'
+ // ruleset
+ // '}'
+ _next();
+
+ dynamic name;
+ if (_peekIdentifier()) {
+ name = identifier();
+ }
+
+ _eat(TokenKind.LBRACE);
+
+ var productions = <TreeNode>[];
+
+ start = _peekToken.span;
+ while (!_maybeEat(TokenKind.END_OF_FILE)) {
+ final rule = processRule();
+ if (rule == null) {
+ break;
+ }
+ productions.add(rule);
+ }
+
+ _eat(TokenKind.RBRACE);
+
+ return StyletDirective(name as String, productions, _makeSpan(start));
+
+ case TokenKind.DIRECTIVE_NAMESPACE:
+ // Namespace grammar:
+ //
+ // @namespace S* [namespace_prefix S*]? [STRING|URI] S* ';' S*
+ // namespace_prefix : IDENT
+ _next();
+
+ Identifier? prefix;
+ if (_peekIdentifier()) {
+ prefix = identifier();
+ }
+
+ // The namespace URI can be either a quoted string url("uri_string")
+ // are identical.
+ String? namespaceUri;
+ if (_peekIdentifier()) {
+ var func = processFunction(identifier());
+ if (func is UriTerm) {
+ namespaceUri = func.text;
+ }
+ } else {
+ if (prefix != null && prefix.name == 'url') {
+ var func = processFunction(prefix);
+ if (func is UriTerm) {
+ // @namespace url("");
+ namespaceUri = func.text;
+ prefix = null;
+ }
+ } else {
+ namespaceUri = processQuotedString(false);
+ }
+ }
+
+ return NamespaceDirective(
+ prefix?.name ?? '', namespaceUri, _makeSpan(start));
+
+ case TokenKind.DIRECTIVE_MIXIN:
+ return processMixin();
+
+ case TokenKind.DIRECTIVE_INCLUDE:
+ return processInclude(_makeSpan(start));
+ case TokenKind.DIRECTIVE_CONTENT:
+ // TODO(terry): TBD
+ _warning('@content not implemented.', _makeSpan(start));
+ return null;
+ case TokenKind.DIRECTIVE_MOZ_DOCUMENT:
+ return processDocumentDirective();
+ case TokenKind.DIRECTIVE_SUPPORTS:
+ return processSupportsDirective();
+ case TokenKind.DIRECTIVE_VIEWPORT:
+ case TokenKind.DIRECTIVE_MS_VIEWPORT:
+ return processViewportDirective();
+ }
+ return null;
+ }
+
+ /// Parse the mixin beginning token offset [start]. Returns a
+ /// [MixinDefinition] node.
+ ///
+ /// Mixin grammar:
+ ///
+ /// @mixin IDENT [(args,...)] '{'
+ /// [ruleset | property | directive]*
+ /// '}'
+ MixinDefinition? processMixin() {
+ _next();
+
+ var name = identifier();
+
+ var params = <TreeNode>[];
+ // Any parameters?
+ if (_maybeEat(TokenKind.LPAREN)) {
+ var mustHaveParam = false;
+ var keepGoing = true;
+ while (keepGoing) {
+ var varDef = processVariableOrDirective(mixinParameter: true);
+ if (varDef is VarDefinitionDirective || varDef is VarDefinition) {
+ params.add(varDef as TreeNode);
+ } else if (mustHaveParam) {
+ _warning('Expecting parameter', _makeSpan(_peekToken.span));
+ keepGoing = false;
+ }
+ if (_maybeEat(TokenKind.COMMA)) {
+ mustHaveParam = true;
+ continue;
+ }
+ keepGoing = !_maybeEat(TokenKind.RPAREN);
+ }
+ }
+
+ _eat(TokenKind.LBRACE);
+
+ var productions = <TreeNode>[];
+ MixinDefinition? mixinDirective;
+
+ var start = _peekToken.span;
+ while (!_maybeEat(TokenKind.END_OF_FILE)) {
+ var directive = processDirective();
+ if (directive != null) {
+ productions.add(directive);
+ continue;
+ }
+
+ var declGroup = processDeclarations(checkBrace: false);
+ if (declGroup.declarations.any((decl) =>
+ decl is Declaration && decl is! IncludeMixinAtDeclaration)) {
+ var newDecls = <Declaration>[];
+ for (var include in productions) {
+ // If declGroup has items that are declarations then we assume
+ // this mixin is a declaration mixin not a top-level mixin.
+ if (include is IncludeDirective) {
+ newDecls.add(IncludeMixinAtDeclaration(include, include.span));
+ } else {
+ _warning('Error mixing of top-level vs declarations mixins',
+ _makeSpan(include.span as FileSpan));
+ }
+ }
+ declGroup.declarations.insertAll(0, newDecls);
+ productions = [];
+ } else {
+ // Declarations are just @includes make it a list of productions
+ // not a declaration group (anything else is a ruleset). Make it a
+ // list of productions, not a declaration group.
+ for (var decl in declGroup.declarations) {
+ productions
+ .add(decl is IncludeMixinAtDeclaration ? decl.include : decl);
+ }
+ declGroup.declarations.clear();
+ }
+
+ if (declGroup.declarations.isNotEmpty) {
+ if (productions.isEmpty) {
+ mixinDirective = MixinDeclarationDirective(
+ name.name, params, false, declGroup, _makeSpan(start));
+ break;
+ } else {
+ for (var decl in declGroup.declarations) {
+ productions
+ .add(decl is IncludeMixinAtDeclaration ? decl.include : decl);
+ }
+ }
+ } else {
+ mixinDirective = MixinRulesetDirective(
+ name.name, params, false, productions, _makeSpan(start));
+ break;
+ }
+ }
+
+ if (productions.isNotEmpty) {
+ mixinDirective = MixinRulesetDirective(
+ name.name, params, false, productions, _makeSpan(start));
+ }
+
+ _eat(TokenKind.RBRACE);
+
+ return mixinDirective;
+ }
+
+ /// Returns a VarDefinitionDirective or VarDefinition if a varaible otherwise
+ /// return the token id of a directive or -1 if neither.
+ dynamic // VarDefinitionDirective | VarDefinition | int
+ processVariableOrDirective({bool mixinParameter = false}) {
+ var start = _peekToken.span;
+
+ var tokId = _peek();
+ // Handle case for @ directive (where there's a whitespace between the @
+ // sign and the directive name. Technically, it's not valid grammar but
+ // a number of CSS tests test for whitespace between @ and name.
+ if (tokId == TokenKind.AT) {
+ _next();
+ tokId = _peek();
+ if (_peekIdentifier()) {
+ // Is it a directive?
+ var directive = _peekToken.text;
+ var directiveLen = directive.length;
+ tokId = TokenKind.matchDirectives(directive, 0, directiveLen);
+ if (tokId == -1) {
+ tokId = TokenKind.matchMarginDirectives(directive, 0, directiveLen);
+ }
+ }
+
+ if (tokId == -1) {
+ if (messages.options.lessSupport) {
+ // Less compatibility:
+ // @name: value; => var-name: value; (VarDefinition)
+ // property: @name; => property: var(name); (VarUsage)
+ Identifier? name;
+ if (_peekIdentifier()) {
+ name = identifier();
+ }
+
+ Expressions? exprs;
+ if (mixinParameter && _maybeEat(TokenKind.COLON)) {
+ exprs = processExpr();
+ } else if (!mixinParameter) {
+ _eat(TokenKind.COLON);
+ exprs = processExpr();
+ }
+
+ var span = _makeSpan(start);
+ return VarDefinitionDirective(VarDefinition(name, exprs, span), span);
+ } else if (isChecked) {
+ _error('unexpected directive @$_peekToken', _peekToken.span);
+ }
+ }
+ } else if (mixinParameter && _peekToken.kind == TokenKind.VAR_DEFINITION) {
+ _next();
+ Identifier? definedName;
+ if (_peekIdentifier()) definedName = identifier();
+
+ Expressions? exprs;
+ if (_maybeEat(TokenKind.COLON)) {
+ exprs = processExpr();
+ }
+
+ return VarDefinition(definedName, exprs, _makeSpan(start));
+ }
+
+ return tokId;
+ }
+
+ IncludeDirective processInclude(SourceSpan span, {bool eatSemiColon = true}) {
+ // Stylet grammar:
+ //
+ // @include IDENT [(args,...)];
+ _next();
+
+ Identifier? name;
+ if (_peekIdentifier()) {
+ name = identifier();
+ }
+
+ var params = <List<Expression>>[];
+
+ // Any parameters? Parameters can be multiple terms per argument e.g.,
+ // 3px solid yellow, green is two parameters:
+ // 1. 3px solid yellow
+ // 2. green
+ // the first has 3 terms and the second has 1 term.
+ if (_maybeEat(TokenKind.LPAREN)) {
+ var terms = <Expression>[];
+ dynamic expr;
+ var keepGoing = true;
+ while (keepGoing && (expr = processTerm()) != null) {
+ // VarUsage is returned as a list
+ terms.add((expr is List ? expr[0] : expr) as Expression);
+ keepGoing = !_peekKind(TokenKind.RPAREN);
+ if (keepGoing) {
+ if (_maybeEat(TokenKind.COMMA)) {
+ params.add(terms);
+ terms = [];
+ }
+ }
+ }
+ params.add(terms);
+ _maybeEat(TokenKind.RPAREN);
+ }
+
+ if (eatSemiColon) {
+ _eat(TokenKind.SEMICOLON);
+ }
+
+ return IncludeDirective(name!.name, params, span);
+ }
+
+ DocumentDirective processDocumentDirective() {
+ var start = _peekToken.span;
+ _next(); // '@-moz-document'
+ var functions = <LiteralTerm>[];
+ do {
+ LiteralTerm function;
+
+ // Consume function token: IDENT '('
+ var ident = identifier();
+ _eat(TokenKind.LPAREN);
+
+ // Consume function arguments.
+ if (ident.name == 'url-prefix' || ident.name == 'domain') {
+ // @-moz-document allows the 'url-prefix' and 'domain' functions to
+ // omit quotations around their argument, contrary to the standard
+ // in which they must be strings. To support this we consume a
+ // string with optional quotation marks, then reapply quotation
+ // marks so they're present in the emitted CSS.
+ var argumentStart = _peekToken.span;
+ var value = processQuotedString(true);
+ // Don't quote the argument if it's empty. '@-moz-document url-prefix()'
+ // is a common pattern used for browser detection.
+ var argument = value.isNotEmpty ? '"$value"' : '';
+ var argumentSpan = _makeSpan(argumentStart);
+
+ _eat(TokenKind.RPAREN);
+
+ var arguments = Expressions(_makeSpan(argumentSpan))
+ ..add(LiteralTerm(argument, argument, argumentSpan));
+ function = FunctionTerm(ident.name, ident.name, arguments,
+ _makeSpan(ident.span as FileSpan));
+ } else {
+ function = processFunction(ident) as LiteralTerm;
+ }
+
+ functions.add(function);
+ } while (_maybeEat(TokenKind.COMMA));
+
+ _eat(TokenKind.LBRACE);
+ var groupRuleBody = processGroupRuleBody();
+ _eat(TokenKind.RBRACE);
+ return DocumentDirective(functions, groupRuleBody, _makeSpan(start));
+ }
+
+ SupportsDirective processSupportsDirective() {
+ var start = _peekToken.span;
+ _next(); // '@supports'
+ var condition = processSupportsCondition();
+ _eat(TokenKind.LBRACE);
+ var groupRuleBody = processGroupRuleBody();
+ _eat(TokenKind.RBRACE);
+ return SupportsDirective(condition, groupRuleBody, _makeSpan(start));
+ }
+
+ SupportsCondition? processSupportsCondition() {
+ if (_peekKind(TokenKind.IDENTIFIER)) {
+ return processSupportsNegation();
+ }
+
+ var start = _peekToken.span;
+ var conditions = <SupportsConditionInParens>[];
+ var clauseType = ClauseType.none;
+
+ while (true) {
+ conditions.add(processSupportsConditionInParens());
+
+ ClauseType type;
+ var text = _peekToken.text.toLowerCase();
+
+ if (text == 'and') {
+ type = ClauseType.conjunction;
+ } else if (text == 'or') {
+ type = ClauseType.disjunction;
+ } else {
+ break; // Done parsing clause.
+ }
+
+ if (clauseType == ClauseType.none) {
+ clauseType = type; // First operand and operator of clause.
+ } else if (clauseType != type) {
+ _error("Operators can't be mixed without a layer of parentheses",
+ _peekToken.span);
+ break;
+ }
+
+ _next(); // Consume operator.
+ }
+
+ if (clauseType == ClauseType.conjunction) {
+ return SupportsConjunction(conditions, _makeSpan(start));
+ } else if (clauseType == ClauseType.disjunction) {
+ return SupportsDisjunction(conditions, _makeSpan(start));
+ } else {
+ return conditions.first;
+ }
+ }
+
+ SupportsNegation? processSupportsNegation() {
+ var start = _peekToken.span;
+ var text = _peekToken.text.toLowerCase();
+ if (text != 'not') return null;
+ _next(); // 'not'
+ var condition = processSupportsConditionInParens();
+ return SupportsNegation(condition, _makeSpan(start));
+ }
+
+ SupportsConditionInParens processSupportsConditionInParens() {
+ var start = _peekToken.span;
+ _eat(TokenKind.LPAREN);
+ // Try to parse a condition.
+ var condition = processSupportsCondition();
+ if (condition != null) {
+ _eat(TokenKind.RPAREN);
+ return SupportsConditionInParens.nested(condition, _makeSpan(start));
+ }
+ // Otherwise, parse a declaration.
+ var declaration = processDeclaration([]);
+ _eat(TokenKind.RPAREN);
+ return SupportsConditionInParens(declaration, _makeSpan(start));
+ }
+
+ ViewportDirective processViewportDirective() {
+ var start = _peekToken.span;
+ var name = _next().text;
+ var declarations = processDeclarations();
+ return ViewportDirective(name, declarations, _makeSpan(start));
+ }
+
+ TreeNode? processRule([SelectorGroup? selectorGroup]) {
+ if (selectorGroup == null) {
+ final directive = processDirective();
+ if (directive != null) {
+ _maybeEat(TokenKind.SEMICOLON);
+ return directive;
+ }
+ selectorGroup = processSelectorGroup();
+ }
+ if (selectorGroup != null) {
+ return RuleSet(selectorGroup, processDeclarations(), selectorGroup.span);
+ }
+ return null;
+ }
+
+ List<TreeNode> processGroupRuleBody() {
+ var nodes = <TreeNode>[];
+ while (!(_peekKind(TokenKind.RBRACE) || _peekKind(TokenKind.END_OF_FILE))) {
+ var rule = processRule();
+ if (rule != null) {
+ nodes.add(rule);
+ continue;
+ }
+ break;
+ }
+ return nodes;
+ }
+
+ /// Look ahead to see if what should be a declaration is really a selector.
+ /// If it's a selector than it's a nested selector. This support's Less'
+ /// nested selector syntax (requires a look ahead). E.g.,
+ ///
+ /// div {
+ /// width : 20px;
+ /// span {
+ /// color: red;
+ /// }
+ /// }
+ ///
+ /// Two tag name selectors div and span equivalent to:
+ ///
+ /// div {
+ /// width: 20px;
+ /// }
+ /// div span {
+ /// color: red;
+ /// }
+ ///
+ /// Return `null` if no selector or [SelectorGroup] if a selector was parsed.
+ SelectorGroup? _nestedSelector() {
+ var oldMessages = messages;
+ _createMessages();
+
+ var markedData = _mark;
+
+ // Look a head do we have a nested selector instead of a declaration?
+ var selGroup = processSelectorGroup();
+
+ var nestedSelector = selGroup != null &&
+ _peekKind(TokenKind.LBRACE) &&
+ messages.messages.isEmpty;
+
+ if (!nestedSelector) {
+ // Not a selector so restore the world.
+ _restore(markedData);
+ messages = oldMessages;
+ return null;
+ } else {
+ // Remember any messages from look ahead.
+ oldMessages.mergeMessages(messages);
+ messages = oldMessages;
+ return selGroup;
+ }
+ }
+
+ DeclarationGroup processDeclarations({bool checkBrace = true}) {
+ var start = _peekToken.span;
+
+ if (checkBrace) _eat(TokenKind.LBRACE);
+
+ var decls = <TreeNode>[];
+ var dartStyles =
+ <DartStyleExpression>[]; // List of latest styles exposed to Dart.
+
+ do {
+ var selectorGroup = _nestedSelector();
+ while (selectorGroup != null) {
+ // Nested selector so process as a ruleset.
+ var ruleset = processRule(selectorGroup)!;
+ decls.add(ruleset);
+ selectorGroup = _nestedSelector();
+ }
+
+ var decl = processDeclaration(dartStyles);
+ if (decl != null) {
+ if (decl.hasDartStyle) {
+ var newDartStyle = decl.dartStyle!;
+
+ // Replace or add latest Dart style.
+ var replaced = false;
+ for (var i = 0; i < dartStyles.length; i++) {
+ var dartStyle = dartStyles[i];
+ if (dartStyle.isSame(newDartStyle)) {
+ dartStyles[i] = newDartStyle;
+ replaced = true;
+ break;
+ }
+ }
+ if (!replaced) {
+ dartStyles.add(newDartStyle);
+ }
+ }
+ decls.add(decl);
+ }
+ } while (_maybeEat(TokenKind.SEMICOLON));
+
+ if (checkBrace) _eat(TokenKind.RBRACE);
+
+ // Fixup declaration to only have dartStyle that are live for this set of
+ // declarations.
+ for (var decl in decls) {
+ if (decl is Declaration) {
+ if (decl.hasDartStyle && !dartStyles.contains(decl.dartStyle)) {
+ // Dart style not live, ignore these styles in this Declarations.
+ decl.dartStyle = null;
+ }
+ }
+ }
+
+ return DeclarationGroup(decls, _makeSpan(start));
+ }
+
+ List<DeclarationGroup> processMarginsDeclarations() {
+ var groups = <DeclarationGroup>[];
+
+ var start = _peekToken.span;
+
+ _eat(TokenKind.LBRACE);
+
+ var decls = <Declaration>[];
+ var dartStyles =
+ <DartStyleExpression>[]; // List of latest styles exposed to Dart.
+
+ do {
+ switch (_peek()) {
+ case TokenKind.MARGIN_DIRECTIVE_TOPLEFTCORNER:
+ case TokenKind.MARGIN_DIRECTIVE_TOPLEFT:
+ case TokenKind.MARGIN_DIRECTIVE_TOPCENTER:
+ case TokenKind.MARGIN_DIRECTIVE_TOPRIGHT:
+ case TokenKind.MARGIN_DIRECTIVE_TOPRIGHTCORNER:
+ case TokenKind.MARGIN_DIRECTIVE_BOTTOMLEFTCORNER:
+ case TokenKind.MARGIN_DIRECTIVE_BOTTOMLEFT:
+ case TokenKind.MARGIN_DIRECTIVE_BOTTOMCENTER:
+ case TokenKind.MARGIN_DIRECTIVE_BOTTOMRIGHT:
+ case TokenKind.MARGIN_DIRECTIVE_BOTTOMRIGHTCORNER:
+ case TokenKind.MARGIN_DIRECTIVE_LEFTTOP:
+ case TokenKind.MARGIN_DIRECTIVE_LEFTMIDDLE:
+ case TokenKind.MARGIN_DIRECTIVE_LEFTBOTTOM:
+ case TokenKind.MARGIN_DIRECTIVE_RIGHTTOP:
+ case TokenKind.MARGIN_DIRECTIVE_RIGHTMIDDLE:
+ case TokenKind.MARGIN_DIRECTIVE_RIGHTBOTTOM:
+ // Margin syms processed.
+ // margin :
+ // margin_sym S* '{' declaration [ ';' S* declaration? ]* '}' S*
+ //
+ // margin_sym : @top-left-corner, @top-left, @bottom-left, etc.
+ var marginSym = _peek();
+
+ _next();
+
+ var declGroup = processDeclarations();
+ groups.add(
+ MarginGroup(marginSym, declGroup.declarations, _makeSpan(start)));
+ break;
+ default:
+ var decl = processDeclaration(dartStyles);
+ if (decl != null) {
+ if (decl.hasDartStyle) {
+ var newDartStyle = decl.dartStyle!;
+
+ // Replace or add latest Dart style.
+ var replaced = false;
+ for (var i = 0; i < dartStyles.length; i++) {
+ var dartStyle = dartStyles[i];
+ if (dartStyle.isSame(newDartStyle)) {
+ dartStyles[i] = newDartStyle;
+ replaced = true;
+ break;
+ }
+ }
+ if (!replaced) {
+ dartStyles.add(newDartStyle);
+ }
+ }
+ decls.add(decl);
+ }
+ _maybeEat(TokenKind.SEMICOLON);
+ break;
+ }
+ } while (!_maybeEat(TokenKind.RBRACE) && !isPrematureEndOfFile());
+
+ // Fixup declaration to only have dartStyle that are live for this set of
+ // declarations.
+ for (var decl in decls) {
+ if (decl.hasDartStyle && !dartStyles.contains(decl.dartStyle)) {
+ // Dart style not live, ignore these styles in this Declarations.
+ decl.dartStyle = null;
+ }
+ }
+
+ if (decls.isNotEmpty) {
+ groups.add(DeclarationGroup(decls, _makeSpan(start)));
+ }
+
+ return groups;
+ }
+
+ SelectorGroup? processSelectorGroup() {
+ var selectors = <Selector>[];
+ var start = _peekToken.span;
+
+ tokenizer.inSelector = true;
+ do {
+ var selector = processSelector();
+ if (selector != null) {
+ selectors.add(selector);
+ }
+ } while (_maybeEat(TokenKind.COMMA));
+ tokenizer.inSelector = false;
+
+ if (selectors.isNotEmpty) {
+ return SelectorGroup(selectors, _makeSpan(start));
+ }
+ return null;
+ }
+
+ /// Return list of selectors
+ Selector? processSelector() {
+ var simpleSequences = <SimpleSelectorSequence>[];
+ var start = _peekToken.span;
+ while (true) {
+ // First item is never descendant make sure it's COMBINATOR_NONE.
+ var selectorItem = simpleSelectorSequence(simpleSequences.isEmpty);
+ if (selectorItem != null) {
+ simpleSequences.add(selectorItem);
+ } else {
+ break;
+ }
+ }
+
+ if (simpleSequences.isEmpty) return null;
+
+ return Selector(simpleSequences, _makeSpan(start));
+ }
+
+ /// Same as [processSelector] but reports an error for each combinator.
+ ///
+ /// This is a quick fix for parsing `<compound-selectors>` until the parser
+ /// supports Selector Level 4 grammar:
+ /// https://drafts.csswg.org/selectors-4/#typedef-compound-selector
+ Selector? processCompoundSelector() {
+ var selector = processSelector();
+ if (selector != null) {
+ for (var sequence in selector.simpleSelectorSequences) {
+ if (!sequence.isCombinatorNone) {
+ _error('compound selector can not contain combinator', sequence.span);
+ }
+ }
+ }
+ return selector;
+ }
+
+ SimpleSelectorSequence? simpleSelectorSequence(bool forceCombinatorNone) {
+ var start = _peekToken.span;
+ var combinatorType = TokenKind.COMBINATOR_NONE;
+ var thisOperator = false;
+
+ switch (_peek()) {
+ case TokenKind.PLUS:
+ _eat(TokenKind.PLUS);
+ combinatorType = TokenKind.COMBINATOR_PLUS;
+ break;
+ case TokenKind.GREATER:
+ _eat(TokenKind.GREATER);
+ combinatorType = TokenKind.COMBINATOR_GREATER;
+ break;
+ case TokenKind.TILDE:
+ _eat(TokenKind.TILDE);
+ combinatorType = TokenKind.COMBINATOR_TILDE;
+ break;
+ case TokenKind.AMPERSAND:
+ _eat(TokenKind.AMPERSAND);
+ thisOperator = true;
+ break;
+ }
+
+ // Check if WHITESPACE existed between tokens if so we're descendent.
+ if (combinatorType == TokenKind.COMBINATOR_NONE && !forceCombinatorNone) {
+ if (_previousToken != null && _previousToken!.end != _peekToken.start) {
+ combinatorType = TokenKind.COMBINATOR_DESCENDANT;
+ }
+ }
+
+ var span = _makeSpan(start);
+ var simpleSel = thisOperator
+ ? ElementSelector(ThisOperator(span), span)
+ : simpleSelector();
+ if (simpleSel == null &&
+ (combinatorType == TokenKind.COMBINATOR_PLUS ||
+ combinatorType == TokenKind.COMBINATOR_GREATER ||
+ combinatorType == TokenKind.COMBINATOR_TILDE)) {
+ // For "+ &", "~ &" or "> &" a selector sequence with no name is needed
+ // so that the & will have a combinator too. This is needed to
+ // disambiguate selector expressions:
+ // .foo&:hover combinator before & is NONE
+ // .foo & combinator before & is DESCDENDANT
+ // .foo > & combinator before & is GREATER
+ simpleSel = ElementSelector(Identifier('', span), span);
+ }
+ if (simpleSel != null) {
+ return SimpleSelectorSequence(simpleSel, span, combinatorType);
+ }
+ return null;
+ }
+
+ /// Simple selector grammar:
+ ///
+ /// simple_selector_sequence
+ /// : [ type_selector | universal ]
+ /// [ HASH | class | attrib | pseudo | negation ]*
+ /// | [ HASH | class | attrib | pseudo | negation ]+
+ /// type_selector
+ /// : [ namespace_prefix ]? element_name
+ /// namespace_prefix
+ /// : [ IDENT | '*' ]? '|'
+ /// element_name
+ /// : IDENT
+ /// universal
+ /// : [ namespace_prefix ]? '*'
+ /// class
+ /// : '.' IDENT
+ SimpleSelector? simpleSelector() {
+ // TODO(terry): Natalie makes a good point parsing of namespace and element
+ // are essentially the same (asterisk or identifier) other
+ // than the error message for element. Should consolidate the
+ // code.
+ // TODO(terry): Need to handle attribute namespace too.
+ dynamic first;
+ var start = _peekToken.span;
+ switch (_peek()) {
+ case TokenKind.ASTERISK:
+ // Mark as universal namespace.
+ var tok = _next();
+ first = Wildcard(_makeSpan(tok.span));
+ break;
+ case TokenKind.IDENTIFIER:
+ first = identifier();
+ break;
+ default:
+ // Expecting simple selector.
+ // TODO(terry): Could be a synthesized token like value, etc.
+ if (TokenKind.isKindIdentifier(_peek())) {
+ first = identifier();
+ } else if (_peekKind(TokenKind.SEMICOLON)) {
+ // Can't be a selector if we found a semi-colon.
+ return null;
+ }
+ break;
+ }
+
+ if (_maybeEat(TokenKind.NAMESPACE)) {
+ TreeNode? element;
+ switch (_peek()) {
+ case TokenKind.ASTERISK:
+ // Mark as universal element
+ var tok = _next();
+ element = Wildcard(_makeSpan(tok.span));
+ break;
+ case TokenKind.IDENTIFIER:
+ element = identifier();
+ break;
+ default:
+ _error('expected element name or universal(*), but found $_peekToken',
+ _peekToken.span);
+ break;
+ }
+
+ return NamespaceSelector(
+ first, ElementSelector(element, element!.span!), _makeSpan(start));
+ } else if (first != null) {
+ return ElementSelector(first, _makeSpan(start));
+ } else {
+ // Check for HASH | class | attrib | pseudo | negation
+ return simpleSelectorTail();
+ }
+ }
+
+ bool _anyWhiteSpaceBeforePeekToken(int kind) {
+ if (_previousToken != null && _previousToken!.kind == kind) {
+ // If end of previous token isn't same as the start of peek token then
+ // there's something between these tokens probably whitespace.
+ return _previousToken!.end != _peekToken.start;
+ }
+
+ return false;
+ }
+
+ /// type_selector | universal | HASH | class | attrib | pseudo
+ SimpleSelector? simpleSelectorTail() {
+ // Check for HASH | class | attrib | pseudo | negation
+ var start = _peekToken.span;
+ switch (_peek()) {
+ case TokenKind.HASH:
+ _eat(TokenKind.HASH);
+
+ if (_anyWhiteSpaceBeforePeekToken(TokenKind.HASH)) {
+ _error('Not a valid ID selector expected #id', _makeSpan(start));
+ return null;
+ }
+ return IdSelector(identifier(), _makeSpan(start));
+ case TokenKind.DOT:
+ _eat(TokenKind.DOT);
+
+ if (_anyWhiteSpaceBeforePeekToken(TokenKind.DOT)) {
+ _error('Not a valid class selector expected .className',
+ _makeSpan(start));
+ return null;
+ }
+ return ClassSelector(identifier(), _makeSpan(start));
+ case TokenKind.COLON:
+ // :pseudo-class ::pseudo-element
+ return processPseudoSelector(start);
+ case TokenKind.LBRACK:
+ return processAttribute();
+ case TokenKind.DOUBLE:
+ _error('name must start with a alpha character, but found a number',
+ _peekToken.span);
+ _next();
+ break;
+ }
+ return null;
+ }
+
+ SimpleSelector? processPseudoSelector(FileSpan start) {
+ // :pseudo-class ::pseudo-element
+ // TODO(terry): '::' should be token.
+ _eat(TokenKind.COLON);
+ var pseudoElement = _maybeEat(TokenKind.COLON);
+
+ // TODO(terry): If no identifier specified consider optimizing out the
+ // : or :: and making this a normal selector. For now,
+ // create an empty pseudoName.
+ Identifier pseudoName;
+ if (_peekIdentifier()) {
+ pseudoName = identifier();
+ } else {
+ return null;
+ }
+ var name = pseudoName.name.toLowerCase();
+
+ // Functional pseudo?
+ if (_peekToken.kind == TokenKind.LPAREN) {
+ if (!pseudoElement && name == 'not') {
+ _eat(TokenKind.LPAREN);
+
+ // Negation : ':NOT(' S* negation_arg S* ')'
+ var negArg = simpleSelector();
+
+ _eat(TokenKind.RPAREN);
+ return NegationSelector(negArg, _makeSpan(start));
+ } else if (!pseudoElement &&
+ (name == 'host' ||
+ name == 'host-context' ||
+ name == 'global-context' ||
+ name == '-acx-global-context')) {
+ _eat(TokenKind.LPAREN);
+ var selector = processCompoundSelector();
+ if (selector == null) {
+ _errorExpected('a selector argument');
+ return null;
+ }
+ _eat(TokenKind.RPAREN);
+ var span = _makeSpan(start);
+ return PseudoClassFunctionSelector(pseudoName, selector, span);
+ } else {
+ // Special parsing for expressions in pseudo functions. Minus is used
+ // as operator not identifier.
+ // TODO(jmesserly): we need to flip this before we eat the "(" as the
+ // next token will be fetched when we do that. I think we should try to
+ // refactor so we don't need this boolean; it seems fragile.
+ tokenizer.inSelectorExpression = true;
+ _eat(TokenKind.LPAREN);
+
+ // Handle function expression.
+ var span = _makeSpan(start);
+ var expr = processSelectorExpression();
+
+ tokenizer.inSelectorExpression = false;
+
+ // Used during selector look-a-head if not a SelectorExpression is
+ // bad.
+ if (expr is SelectorExpression) {
+ _eat(TokenKind.RPAREN);
+ return pseudoElement
+ ? PseudoElementFunctionSelector(pseudoName, expr, span)
+ : PseudoClassFunctionSelector(pseudoName, expr, span);
+ } else {
+ _errorExpected('CSS expression');
+ return null;
+ }
+ }
+ }
+
+ // Treat CSS2.1 pseudo-elements defined with pseudo class syntax as pseudo-
+ // elements for backwards compatibility.
+ return pseudoElement || _legacyPseudoElements.contains(name)
+ ? PseudoElementSelector(pseudoName, _makeSpan(start),
+ isLegacy: !pseudoElement)
+ : PseudoClassSelector(pseudoName, _makeSpan(start));
+ }
+
+ /// In CSS3, the expressions are identifiers, strings, or of the form "an+b".
+ ///
+ /// : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+
+ ///
+ /// num [0-9]+|[0-9]*\.[0-9]+
+ /// PLUS '+'
+ /// DIMENSION {num}{ident}
+ /// NUMBER {num}
+ TreeNode /* SelectorExpression | LiteralTerm */ processSelectorExpression() {
+ var start = _peekToken.span;
+
+ var expressions = <Expression>[];
+
+ Token? termToken;
+ dynamic value;
+
+ var keepParsing = true;
+ while (keepParsing) {
+ switch (_peek()) {
+ case TokenKind.PLUS:
+ start = _peekToken.span;
+ termToken = _next();
+ expressions.add(OperatorPlus(_makeSpan(start)));
+ break;
+ case TokenKind.MINUS:
+ start = _peekToken.span;
+ termToken = _next();
+ expressions.add(OperatorMinus(_makeSpan(start)));
+ break;
+ case TokenKind.INTEGER:
+ termToken = _next();
+ value = int.parse(termToken.text);
+ break;
+ case TokenKind.DOUBLE:
+ termToken = _next();
+ value = double.parse(termToken.text);
+ break;
+ case TokenKind.SINGLE_QUOTE:
+ value = processQuotedString(false);
+ value = "'${_escapeString(value as String, single: true)}'";
+ return LiteralTerm(value, value, _makeSpan(start));
+ case TokenKind.DOUBLE_QUOTE:
+ value = processQuotedString(false);
+ value = '"${_escapeString(value as String)}"';
+ return LiteralTerm(value, value, _makeSpan(start));
+ case TokenKind.IDENTIFIER:
+ value = identifier(); // Snarf up the ident we'll remap, maybe.
+ break;
+ default:
+ keepParsing = false;
+ }
+
+ if (keepParsing && value != null) {
+ var unitTerm =
+ processDimension(termToken, value as Object, _makeSpan(start));
+ expressions.add(unitTerm);
+
+ value = null;
+ }
+ }
+
+ return SelectorExpression(expressions, _makeSpan(start));
+ }
+
+ // Attribute grammar:
+ //
+ // attributes :
+ // '[' S* IDENT S* [ ATTRIB_MATCHES S* [ IDENT | STRING ] S* ]? ']'
+ //
+ // ATTRIB_MATCHES :
+ // [ '=' | INCLUDES | DASHMATCH | PREFIXMATCH | SUFFIXMATCH | SUBSTRMATCH ]
+ //
+ // INCLUDES: '~='
+ //
+ // DASHMATCH: '|='
+ //
+ // PREFIXMATCH: '^='
+ //
+ // SUFFIXMATCH: '$='
+ //
+ // SUBSTRMATCH: '*='
+ AttributeSelector? processAttribute() {
+ var start = _peekToken.span;
+
+ if (_maybeEat(TokenKind.LBRACK)) {
+ var attrName = identifier();
+
+ int op;
+ switch (_peek()) {
+ case TokenKind.EQUALS:
+ case TokenKind.INCLUDES: // ~=
+ case TokenKind.DASH_MATCH: // |=
+ case TokenKind.PREFIX_MATCH: // ^=
+ case TokenKind.SUFFIX_MATCH: // $=
+ case TokenKind.SUBSTRING_MATCH: // *=
+ op = _peek();
+ _next();
+ break;
+ default:
+ op = TokenKind.NO_MATCH;
+ }
+
+ dynamic value;
+ if (op != TokenKind.NO_MATCH) {
+ // Operator hit so we require a value too.
+ if (_peekIdentifier()) {
+ value = identifier();
+ } else {
+ value = processQuotedString(false);
+ }
+
+ if (value == null) {
+ _error('expected attribute value string or ident', _peekToken.span);
+ }
+ }
+
+ _eat(TokenKind.RBRACK);
+
+ return AttributeSelector(attrName, op, value, _makeSpan(start));
+ }
+ return null;
+ }
+
+ // Declaration grammar:
+ //
+ // declaration: property ':' expr prio?
+ //
+ // property: IDENT [or IE hacks]
+ // prio: !important
+ // expr: (see processExpr)
+ //
+ // Here are the ugly IE hacks we need to support:
+ // property: expr prio? \9; - IE8 and below property, /9 before semi-colon
+ // *IDENT - IE7 or below
+ // _IDENT - IE6 property (automatically a valid ident)
+ Declaration? processDeclaration(List<DartStyleExpression> dartStyles) {
+ Declaration? decl;
+
+ var start = _peekToken.span;
+
+ // IE7 hack of * before property name if so the property is IE7 or below.
+ var ie7 = _peekKind(TokenKind.ASTERISK);
+ if (ie7) {
+ _next();
+ }
+
+ // IDENT ':' expr '!important'?
+ if (TokenKind.isIdentifier(_peekToken.kind)) {
+ var propertyIdent = identifier();
+
+ var ieFilterProperty = propertyIdent.name.toLowerCase() == 'filter';
+
+ _eat(TokenKind.COLON);
+
+ var exprs = processExpr(ieFilterProperty);
+
+ var dartComposite = _styleForDart(propertyIdent, exprs, dartStyles);
+
+ // Handle !important (prio)
+ var importantPriority = _maybeEat(TokenKind.IMPORTANT);
+
+ decl = Declaration(propertyIdent, exprs, dartComposite, _makeSpan(start),
+ important: importantPriority, ie7: ie7);
+ } else if (_peekToken.kind == TokenKind.VAR_DEFINITION) {
+ _next();
+ Identifier? definedName;
+ if (_peekIdentifier()) definedName = identifier();
+
+ _eat(TokenKind.COLON);
+
+ var exprs = processExpr();
+
+ decl = VarDefinition(definedName, exprs, _makeSpan(start));
+ } else if (_peekToken.kind == TokenKind.DIRECTIVE_INCLUDE) {
+ // @include mixinName in the declaration area.
+ var span = _makeSpan(start);
+ var include = processInclude(span, eatSemiColon: false);
+ decl = IncludeMixinAtDeclaration(include, span);
+ } else if (_peekToken.kind == TokenKind.DIRECTIVE_EXTEND) {
+ var simpleSequences = <TreeNode>[];
+
+ _next();
+ var span = _makeSpan(start);
+ var selector = simpleSelector();
+ if (selector == null) {
+ _warning('@extends expecting simple selector name', span);
+ } else {
+ simpleSequences.add(selector);
+ }
+ if (_peekKind(TokenKind.COLON)) {
+ var pseudoSelector = processPseudoSelector(_peekToken.span);
+ if (pseudoSelector is PseudoElementSelector ||
+ pseudoSelector is PseudoClassSelector) {
+ simpleSequences.add(pseudoSelector!);
+ } else {
+ _warning('not a valid selector', span);
+ }
+ }
+ decl = ExtendDeclaration(simpleSequences, span);
+ }
+
+ return decl;
+ }
+
+ /// List of styles exposed to the Dart UI framework.
+ static const int _fontPartFont = 0;
+ static const int _fontPartVariant = 1;
+ static const int _fontPartWeight = 2;
+ static const int _fontPartSize = 3;
+ static const int _fontPartFamily = 4;
+ static const int _fontPartStyle = 5;
+ static const int _marginPartMargin = 6;
+ static const int _marginPartLeft = 7;
+ static const int _marginPartTop = 8;
+ static const int _marginPartRight = 9;
+ static const int _marginPartBottom = 10;
+ static const int _lineHeightPart = 11;
+ static const int _borderPartBorder = 12;
+ static const int _borderPartLeft = 13;
+ static const int _borderPartTop = 14;
+ static const int _borderPartRight = 15;
+ static const int _borderPartBottom = 16;
+ static const int _borderPartWidth = 17;
+ static const int _borderPartLeftWidth = 18;
+ static const int _borderPartTopWidth = 19;
+ static const int _borderPartRightWidth = 20;
+ static const int _borderPartBottomWidth = 21;
+ static const int _heightPart = 22;
+ static const int _widthPart = 23;
+ static const int _paddingPartPadding = 24;
+ static const int _paddingPartLeft = 25;
+ static const int _paddingPartTop = 26;
+ static const int _paddingPartRight = 27;
+ static const int _paddingPartBottom = 28;
+
+ static const Map<String, int> _stylesToDart = {
+ 'font': _fontPartFont,
+ 'font-family': _fontPartFamily,
+ 'font-size': _fontPartSize,
+ 'font-style': _fontPartStyle,
+ 'font-variant': _fontPartVariant,
+ 'font-weight': _fontPartWeight,
+ 'line-height': _lineHeightPart,
+ 'margin': _marginPartMargin,
+ 'margin-left': _marginPartLeft,
+ 'margin-right': _marginPartRight,
+ 'margin-top': _marginPartTop,
+ 'margin-bottom': _marginPartBottom,
+ 'border': _borderPartBorder,
+ 'border-left': _borderPartLeft,
+ 'border-right': _borderPartRight,
+ 'border-top': _borderPartTop,
+ 'border-bottom': _borderPartBottom,
+ 'border-width': _borderPartWidth,
+ 'border-left-width': _borderPartLeftWidth,
+ 'border-top-width': _borderPartTopWidth,
+ 'border-right-width': _borderPartRightWidth,
+ 'border-bottom-width': _borderPartBottomWidth,
+ 'height': _heightPart,
+ 'width': _widthPart,
+ 'padding': _paddingPartPadding,
+ 'padding-left': _paddingPartLeft,
+ 'padding-top': _paddingPartTop,
+ 'padding-right': _paddingPartRight,
+ 'padding-bottom': _paddingPartBottom
+ };
+
+ static const Map<String, int> _nameToFontWeight = {
+ 'bold': FontWeight.bold,
+ 'normal': FontWeight.normal
+ };
+
+ static int? _findStyle(String styleName) => _stylesToDart[styleName];
+
+ DartStyleExpression? _styleForDart(Identifier property, Expressions exprs,
+ List<DartStyleExpression> dartStyles) {
+ var styleType = _findStyle(property.name.toLowerCase());
+ if (styleType != null) {
+ return buildDartStyleNode(styleType, exprs, dartStyles);
+ }
+ return null;
+ }
+
+ FontExpression _mergeFontStyles(
+ FontExpression fontExpr, List<DartStyleExpression> dartStyles) {
+ // Merge all font styles for this class selector.
+ for (var dartStyle in dartStyles) {
+ if (dartStyle.isFont) {
+ fontExpr = FontExpression.merge(dartStyle as FontExpression, fontExpr);
+ }
+ }
+
+ return fontExpr;
+ }
+
+ DartStyleExpression? buildDartStyleNode(
+ int styleType, Expressions exprs, List<DartStyleExpression> dartStyles) {
+ switch (styleType) {
+ // Properties in order:
+ //
+ // font-style font-variant font-weight font-size/line-height font-family
+ //
+ // The font-size and font-family values are required. If other values are
+ // missing; a default, if it exist, will be used.
+ case _fontPartFont:
+ var processor = ExpressionsProcessor(exprs);
+ return _mergeFontStyles(processor.processFont(), dartStyles);
+ case _fontPartFamily:
+ var processor = ExpressionsProcessor(exprs);
+
+ try {
+ return _mergeFontStyles(processor.processFontFamily(), dartStyles);
+ } catch (fontException) {
+ _error('$fontException', _peekToken.span);
+ }
+ break;
+ case _fontPartSize:
+ var processor = ExpressionsProcessor(exprs);
+ return _mergeFontStyles(processor.processFontSize(), dartStyles);
+ case _fontPartStyle:
+ // Possible style values:
+ // normal [default]
+ // italic
+ // oblique
+ // inherit
+
+ // TODO(terry): TBD
+ break;
+ case _fontPartVariant:
+ // Possible variant values:
+ // normal [default]
+ // small-caps
+ // inherit
+
+ // TODO(terry): TBD
+ break;
+ case _fontPartWeight:
+ // Possible weight values:
+ // normal [default]
+ // bold
+ // bolder
+ // lighter
+ // 100 - 900
+ // inherit
+
+ // TODO(terry): Only 'normal', 'bold', or values of 100-900 supported
+ // need to handle bolder, lighter, and inherit. See
+ // https://github.com/dart-lang/csslib/issues/1
+ var expr = exprs.expressions[0];
+ if (expr is NumberTerm) {
+ var fontExpr = FontExpression(expr.span, weight: expr.value as int?);
+ return _mergeFontStyles(fontExpr, dartStyles);
+ } else if (expr is LiteralTerm) {
+ var weight = _nameToFontWeight[expr.value.toString()];
+ if (weight != null) {
+ var fontExpr = FontExpression(expr.span, weight: weight);
+ return _mergeFontStyles(fontExpr, dartStyles);
+ }
+ }
+ break;
+ case _lineHeightPart:
+ if (exprs.expressions.length == 1) {
+ var expr = exprs.expressions[0];
+ if (expr is UnitTerm) {
+ var unitTerm = expr;
+ // TODO(terry): Need to handle other units and LiteralTerm normal
+ // See https://github.com/dart-lang/csslib/issues/2.
+ if (unitTerm.unit == TokenKind.UNIT_LENGTH_PX ||
+ unitTerm.unit == TokenKind.UNIT_LENGTH_PT) {
+ var fontExpr = FontExpression(expr.span,
+ lineHeight: LineHeight(expr.value as num, inPixels: true));
+ return _mergeFontStyles(fontExpr, dartStyles);
+ } else if (isChecked) {
+ _warning('Unexpected unit for line-height', expr.span);
+ }
+ } else if (expr is NumberTerm) {
+ var fontExpr = FontExpression(expr.span,
+ lineHeight: LineHeight(expr.value as num, inPixels: false));
+ return _mergeFontStyles(fontExpr, dartStyles);
+ } else if (isChecked) {
+ _warning('Unexpected value for line-height', expr.span);
+ }
+ }
+ break;
+ case _marginPartMargin:
+ return MarginExpression.boxEdge(exprs.span, processFourNums(exprs));
+ case _borderPartBorder:
+ for (var expr in exprs.expressions) {
+ var v = marginValue(expr);
+ if (v != null) {
+ final box = BoxEdge.uniform(v);
+ return BorderExpression.boxEdge(exprs.span, box);
+ }
+ }
+ break;
+ case _borderPartWidth:
+ var v = marginValue(exprs.expressions[0]);
+ if (v != null) {
+ final box = BoxEdge.uniform(v);
+ return BorderExpression.boxEdge(exprs.span, box);
+ }
+ break;
+ case _paddingPartPadding:
+ return PaddingExpression.boxEdge(exprs.span, processFourNums(exprs));
+ case _marginPartLeft:
+ case _marginPartTop:
+ case _marginPartRight:
+ case _marginPartBottom:
+ case _borderPartLeft:
+ case _borderPartTop:
+ case _borderPartRight:
+ case _borderPartBottom:
+ case _borderPartLeftWidth:
+ case _borderPartTopWidth:
+ case _borderPartRightWidth:
+ case _borderPartBottomWidth:
+ case _heightPart:
+ case _widthPart:
+ case _paddingPartLeft:
+ case _paddingPartTop:
+ case _paddingPartRight:
+ case _paddingPartBottom:
+ if (exprs.expressions.isNotEmpty) {
+ return processOneNumber(exprs, styleType);
+ }
+ break;
+ }
+ return null;
+ }
+
+ // TODO(terry): Look at handling width of thin, thick, etc. any none numbers
+ // to convert to a number.
+ DartStyleExpression? processOneNumber(Expressions exprs, int part) {
+ var value = marginValue(exprs.expressions[0]);
+ if (value != null) {
+ return switch (part) {
+ _marginPartLeft => MarginExpression(exprs.span, left: value),
+ _marginPartTop => MarginExpression(exprs.span, top: value),
+ _marginPartRight => MarginExpression(exprs.span, right: value),
+ _marginPartBottom => MarginExpression(exprs.span, bottom: value),
+ _borderPartLeft ||
+ _borderPartLeftWidth =>
+ BorderExpression(exprs.span, left: value),
+ _borderPartTop ||
+ _borderPartTopWidth =>
+ BorderExpression(exprs.span, top: value),
+ _borderPartRight ||
+ _borderPartRightWidth =>
+ BorderExpression(exprs.span, right: value),
+ _borderPartBottom ||
+ _borderPartBottomWidth =>
+ BorderExpression(exprs.span, bottom: value),
+ _heightPart => HeightExpression(exprs.span, value),
+ _widthPart => WidthExpression(exprs.span, value),
+ _paddingPartLeft => PaddingExpression(exprs.span, left: value),
+ _paddingPartTop => PaddingExpression(exprs.span, top: value),
+ _paddingPartRight => PaddingExpression(exprs.span, right: value),
+ _paddingPartBottom => PaddingExpression(exprs.span, bottom: value),
+ _ => null
+ };
+ }
+ return null;
+ }
+
+ /// Margins are of the format:
+ ///
+ /// * top,right,bottom,left (4 parameters)
+ /// * top,right/left, bottom (3 parameters)
+ /// * top/bottom,right/left (2 parameters)
+ /// * top/right/bottom/left (1 parameter)
+ ///
+ /// The values of the margins can be a unit or unitless or auto.
+ BoxEdge? processFourNums(Expressions exprs) {
+ num? top;
+ num? right;
+ num? bottom;
+ num? left;
+
+ var totalExprs = exprs.expressions.length;
+ switch (totalExprs) {
+ case 1:
+ top = marginValue(exprs.expressions[0]);
+ right = top;
+ bottom = top;
+ left = top;
+ break;
+ case 2:
+ top = marginValue(exprs.expressions[0]);
+ bottom = top;
+ right = marginValue(exprs.expressions[1]);
+ left = right;
+ break;
+ case 3:
+ top = marginValue(exprs.expressions[0]);
+ right = marginValue(exprs.expressions[1]);
+ left = right;
+ bottom = marginValue(exprs.expressions[2]);
+ break;
+ case 4:
+ top = marginValue(exprs.expressions[0]);
+ right = marginValue(exprs.expressions[1]);
+ bottom = marginValue(exprs.expressions[2]);
+ left = marginValue(exprs.expressions[3]);
+ break;
+ default:
+ return null;
+ }
+
+ return BoxEdge.clockwiseFromTop(top, right, bottom, left);
+ }
+
+ // TODO(terry): Need to handle auto.
+ num? marginValue(Expression exprTerm) {
+ if (exprTerm is UnitTerm) {
+ return exprTerm.value as num;
+ } else if (exprTerm is NumberTerm) {
+ return exprTerm.value as num;
+ }
+ return null;
+ }
+
+ // Expression grammar:
+ //
+ // expression: term [ operator? term]*
+ //
+ // operator: '/' | ','
+ // term: (see processTerm)
+ Expressions processExpr([bool ieFilter = false]) {
+ var start = _peekToken.span;
+ var expressions = Expressions(_makeSpan(start));
+
+ var keepGoing = true;
+ dynamic expr;
+ while (keepGoing && (expr = processTerm(ieFilter)) != null) {
+ Expression? op;
+
+ var opStart = _peekToken.span;
+
+ switch (_peek()) {
+ case TokenKind.SLASH:
+ op = OperatorSlash(_makeSpan(opStart));
+ break;
+ case TokenKind.COMMA:
+ op = OperatorComma(_makeSpan(opStart));
+ break;
+ case TokenKind.BACKSLASH:
+ // Backslash outside of string; detected IE8 or older signaled by \9
+ // at end of an expression.
+ var ie8Start = _peekToken.span;
+
+ _next();
+ if (_peekKind(TokenKind.INTEGER)) {
+ var numToken = _next();
+ var value = int.parse(numToken.text);
+ if (value == 9) {
+ op = IE8Term(_makeSpan(ie8Start));
+ } else if (isChecked) {
+ _warning(
+ '\$value is not valid in an expression', _makeSpan(start));
+ }
+ }
+ break;
+ }
+
+ if (expr != null) {
+ if (expr is List<Expression>) {
+ for (var exprItem in expr) {
+ expressions.add(exprItem);
+ }
+ } else {
+ expressions.add(expr as Expression);
+ }
+ } else {
+ keepGoing = false;
+ }
+
+ if (op != null) {
+ expressions.add(op);
+ if (op is IE8Term) {
+ keepGoing = false;
+ } else {
+ _next();
+ }
+ }
+ }
+
+ return expressions;
+ }
+
+ // ignore: constant_identifier_names
+ static const int MAX_UNICODE = 0x10FFFF;
+
+ // Term grammar:
+ //
+ // term:
+ // unary_operator?
+ // [ term_value ]
+ // | STRING S* | IDENT S* | URI S* | UNICODERANGE S* | hexcolor
+ //
+ // term_value:
+ // NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* | ANGLE S* |
+ // TIME S* | FREQ S* | function
+ //
+ // NUMBER: {num}
+ // PERCENTAGE: {num}%
+ // LENGTH: {num}['px' | 'cm' | 'mm' | 'in' | 'pt' | 'pc']
+ // EMS: {num}'em'
+ // EXS: {num}'ex'
+ // ANGLE: {num}['deg' | 'rad' | 'grad']
+ // TIME: {num}['ms' | 's']
+ // FREQ: {num}['hz' | 'khz']
+ // function: IDENT '(' expr ')'
+ //
+ dynamic /* Expression | List<Expression> | ... */ processTerm(
+ [bool ieFilter = false]) {
+ var start = _peekToken.span;
+ var unary = '';
+
+ dynamic processIdentifier() {
+ var nameValue = identifier(); // Snarf up the ident we'll remap, maybe.
+
+ if (!ieFilter && _maybeEat(TokenKind.LPAREN)) {
+ var calc = processCalc(nameValue);
+ if (calc != null) return calc;
+ // FUNCTION
+ return processFunction(nameValue);
+ }
+ if (ieFilter) {
+ if (_maybeEat(TokenKind.COLON) &&
+ nameValue.name.toLowerCase() == 'progid') {
+ // IE filter:progid:
+ return processIEFilter(start);
+ } else {
+ // Handle filter:<name> where name is any filter e.g., alpha,
+ // chroma, Wave, blur, etc.
+ return processIEFilter(start);
+ }
+ }
+
+ // TODO(terry): Need to have a list of known identifiers today only
+ // 'from' is special.
+ if (nameValue.name == 'from') {
+ return LiteralTerm(nameValue, nameValue.name, _makeSpan(start));
+ }
+
+ // What kind of identifier is it, named color?
+ var colorEntry = TokenKind.matchColorName(nameValue.name);
+ if (colorEntry == null) {
+ if (isChecked) {
+ var propName = nameValue.name;
+ var errMsg = TokenKind.isPredefinedName(propName)
+ ? 'Improper use of property value $propName'
+ : 'Unknown property value $propName';
+ _warning(errMsg, _makeSpan(start));
+ }
+ return LiteralTerm(nameValue, nameValue.name, _makeSpan(start));
+ }
+
+ // Yes, process the color as an RGB value.
+ var rgbColor =
+ TokenKind.decimalToHex(TokenKind.colorValue(colorEntry), 6);
+ return _parseHex(rgbColor, _makeSpan(start));
+ }
+
+ switch (_peek()) {
+ case TokenKind.HASH:
+ _eat(TokenKind.HASH);
+ if (!_anyWhiteSpaceBeforePeekToken(TokenKind.HASH)) {
+ String? hexText;
+ if (_peekKind(TokenKind.INTEGER)) {
+ var hexText1 = _peekToken.text;
+ _next();
+ // Append identifier only if there's no delimiting whitespace.
+ if (_peekIdentifier() && _previousToken!.end == _peekToken.start) {
+ hexText = '$hexText1${identifier().name}';
+ } else {
+ hexText = hexText1;
+ }
+ } else if (_peekIdentifier()) {
+ hexText = identifier().name;
+ }
+ if (hexText != null) {
+ return _parseHex(hexText, _makeSpan(start));
+ }
+ }
+
+ if (isChecked) {
+ _warning('Expected hex number', _makeSpan(start));
+ }
+ // Construct the bad hex value with a #<space>number.
+ return _parseHex(
+ ' ${(processTerm() as LiteralTerm).text}', _makeSpan(start));
+ case TokenKind.INTEGER:
+ var t = _next();
+ var value = int.parse('$unary${t.text}');
+ return processDimension(t, value, _makeSpan(start));
+ case TokenKind.DOUBLE:
+ var t = _next();
+ var value = double.parse('$unary${t.text}');
+ return processDimension(t, value, _makeSpan(start));
+ case TokenKind.SINGLE_QUOTE:
+ var value = processQuotedString(false);
+ value = "'${_escapeString(value, single: true)}'";
+ return LiteralTerm(value, value, _makeSpan(start));
+ case TokenKind.DOUBLE_QUOTE:
+ var value = processQuotedString(false);
+ value = '"${_escapeString(value)}"';
+ return LiteralTerm(value, value, _makeSpan(start));
+ case TokenKind.LPAREN:
+ _next();
+
+ var group = GroupTerm(_makeSpan(start));
+
+ dynamic /* Expression | List<Expression> | ... */ term;
+ do {
+ term = processTerm();
+ if (term != null && term is LiteralTerm) {
+ group.add(term);
+ }
+ } while (term != null &&
+ !_maybeEat(TokenKind.RPAREN) &&
+ !isPrematureEndOfFile());
+
+ return group;
+ case TokenKind.LBRACK:
+ _next();
+
+ var term = processTerm() as LiteralTerm;
+ if (term is! NumberTerm) {
+ _error('Expecting a positive number', _makeSpan(start));
+ }
+
+ _eat(TokenKind.RBRACK);
+
+ return ItemTerm(term.value, term.text, _makeSpan(start));
+ case TokenKind.IDENTIFIER:
+ return processIdentifier();
+ case TokenKind.UNICODE_RANGE:
+ String? first;
+ String? second;
+ int firstNumber;
+ int secondNumber;
+ _eat(TokenKind.UNICODE_RANGE, unicodeRange: true);
+ if (_maybeEat(TokenKind.HEX_INTEGER, unicodeRange: true)) {
+ first = _previousToken!.text;
+ firstNumber = int.parse('0x$first');
+ if (firstNumber > MAX_UNICODE) {
+ _error('unicode range must be less than 10FFFF', _makeSpan(start));
+ }
+ if (_maybeEat(TokenKind.MINUS, unicodeRange: true)) {
+ if (_maybeEat(TokenKind.HEX_INTEGER, unicodeRange: true)) {
+ second = _previousToken!.text;
+ secondNumber = int.parse('0x$second');
+ if (secondNumber > MAX_UNICODE) {
+ _error(
+ 'unicode range must be less than 10FFFF', _makeSpan(start));
+ }
+ if (firstNumber > secondNumber) {
+ _error('unicode first range can not be greater than last',
+ _makeSpan(start));
+ }
+ }
+ }
+ } else if (_maybeEat(TokenKind.HEX_RANGE, unicodeRange: true)) {
+ first = _previousToken!.text;
+ }
+
+ return UnicodeRangeTerm(first, second, _makeSpan(start));
+ case TokenKind.AT:
+ if (messages.options.lessSupport) {
+ _next();
+
+ var expr = processExpr();
+ if (isChecked && expr.expressions.length > 1) {
+ _error('only @name for Less syntax', _peekToken.span);
+ }
+
+ var param = expr.expressions[0];
+ var varUsage =
+ VarUsage((param as LiteralTerm).text, [], _makeSpan(start));
+ expr.expressions[0] = varUsage;
+ return expr.expressions;
+ }
+ break;
+ default:
+ // For tokens that we don't match above, but that are identifier like
+ // ('PT', 'PX', ...) handle them like identifiers.
+ if (TokenKind.isKindIdentifier(_peek())) {
+ return processIdentifier();
+ } else {
+ return null;
+ }
+ }
+ }
+
+ /// Process all dimension units.
+ LiteralTerm processDimension(Token? t, Object value, FileSpan span) {
+ LiteralTerm term;
+ var unitType = _peek();
+
+ switch (unitType) {
+ case TokenKind.UNIT_EM:
+ span = span.expand(_next().span);
+ term = EmTerm(value, t!.text, span);
+ break;
+ case TokenKind.UNIT_EX:
+ span = span.expand(_next().span);
+ term = ExTerm(value, t!.text, span);
+ break;
+ case TokenKind.UNIT_LENGTH_PX:
+ case TokenKind.UNIT_LENGTH_CM:
+ case TokenKind.UNIT_LENGTH_MM:
+ case TokenKind.UNIT_LENGTH_IN:
+ case TokenKind.UNIT_LENGTH_PT:
+ case TokenKind.UNIT_LENGTH_PC:
+ span = span.expand(_next().span);
+ term = LengthTerm(value, t!.text, span, unitType);
+ break;
+ case TokenKind.UNIT_ANGLE_DEG:
+ case TokenKind.UNIT_ANGLE_RAD:
+ case TokenKind.UNIT_ANGLE_GRAD:
+ case TokenKind.UNIT_ANGLE_TURN:
+ span = span.expand(_next().span);
+ term = AngleTerm(value, t!.text, span, unitType);
+ break;
+ case TokenKind.UNIT_TIME_MS:
+ case TokenKind.UNIT_TIME_S:
+ span = span.expand(_next().span);
+ term = TimeTerm(value, t!.text, span, unitType);
+ break;
+ case TokenKind.UNIT_FREQ_HZ:
+ case TokenKind.UNIT_FREQ_KHZ:
+ span = span.expand(_next().span);
+ term = FreqTerm(value, t!.text, span, unitType);
+ break;
+ case TokenKind.PERCENT:
+ span = span.expand(_next().span);
+ term = PercentageTerm(value, t!.text, span);
+ break;
+ case TokenKind.UNIT_FRACTION:
+ span = span.expand(_next().span);
+ term = FractionTerm(value, t!.text, span);
+ break;
+ case TokenKind.UNIT_RESOLUTION_DPI:
+ case TokenKind.UNIT_RESOLUTION_DPCM:
+ case TokenKind.UNIT_RESOLUTION_DPPX:
+ span = span.expand(_next().span);
+ term = ResolutionTerm(value, t!.text, span, unitType);
+ break;
+ case TokenKind.UNIT_CH:
+ span = span.expand(_next().span);
+ term = ChTerm(value, t!.text, span, unitType);
+ break;
+ case TokenKind.UNIT_REM:
+ span = span.expand(_next().span);
+ term = RemTerm(value, t!.text, span, unitType);
+ break;
+ case TokenKind.UNIT_VIEWPORT_VW:
+ case TokenKind.UNIT_VIEWPORT_VH:
+ case TokenKind.UNIT_VIEWPORT_VMIN:
+ case TokenKind.UNIT_VIEWPORT_VMAX:
+ span = span.expand(_next().span);
+ term = ViewportTerm(value, t!.text, span, unitType);
+ break;
+ case TokenKind.UNIT_LH:
+ case TokenKind.UNIT_RLH:
+ span = span.expand(_next().span);
+ term = LineHeightTerm(value, t!.text, span, unitType);
+ break;
+ default:
+ if (value is Identifier) {
+ term = LiteralTerm(value, value.name, span);
+ } else {
+ term = NumberTerm(value, t!.text, span);
+ }
+ }
+
+ return term;
+ }
+
+ String processQuotedString([bool urlString = false]) {
+ var start = _peekToken.span;
+
+ // URI term sucks up everything inside of quotes(' or ") or between parens
+ var stopToken = urlString ? TokenKind.RPAREN : -1;
+
+ // Note: disable skipping whitespace tokens inside a string.
+ // TODO(jmesserly): the layering here feels wrong.
+ var inString = tokenizer._inString;
+ tokenizer._inString = false;
+
+ switch (_peek()) {
+ case TokenKind.SINGLE_QUOTE:
+ stopToken = TokenKind.SINGLE_QUOTE;
+ _next(); // Skip the SINGLE_QUOTE.
+ start = _peekToken.span;
+ break;
+ case TokenKind.DOUBLE_QUOTE:
+ stopToken = TokenKind.DOUBLE_QUOTE;
+ _next(); // Skip the DOUBLE_QUOTE.
+ start = _peekToken.span;
+ break;
+ default:
+ if (urlString) {
+ if (_peek() == TokenKind.LPAREN) {
+ _next(); // Skip the LPAREN.
+ start = _peekToken.span;
+ }
+ stopToken = TokenKind.RPAREN;
+ } else {
+ _error('unexpected string', _makeSpan(start));
+ }
+ break;
+ }
+
+ // Gobble up everything until we hit our stop token.
+ var stringValue = StringBuffer();
+ while (_peek() != stopToken && _peek() != TokenKind.END_OF_FILE) {
+ stringValue.write(_next().text);
+ }
+
+ tokenizer._inString = inString;
+
+ // All characters between quotes is the string.
+ if (stopToken != TokenKind.RPAREN) {
+ _next(); // Skip the SINGLE_QUOTE or DOUBLE_QUOTE;
+ }
+
+ return stringValue.toString();
+ }
+
+ // TODO(terry): Should probably understand IE's non-standard filter syntax to
+ // fully support calc, var(), etc.
+ /// IE's filter property breaks CSS value parsing. IE's format can be:
+ ///
+ /// filter: progid:DXImageTransform.MS.gradient(Type=0, Color='#9d8b83');
+ ///
+ /// We'll just parse everything after the 'progid:' look for the left paren
+ /// then parse to the right paren ignoring everything in between.
+ dynamic processIEFilter(FileSpan startAfterProgidColon) {
+ // Support non-functional filters (i.e. filter: FlipH)
+ var kind = _peek();
+ if (kind == TokenKind.SEMICOLON || kind == TokenKind.RBRACE) {
+ var tok = tokenizer.makeIEFilter(
+ startAfterProgidColon.start.offset, _peekToken.start);
+ return LiteralTerm(tok.text, tok.text, tok.span);
+ }
+
+ var parens = 0;
+ while (_peek() != TokenKind.END_OF_FILE) {
+ switch (_peek()) {
+ case TokenKind.LPAREN:
+ _eat(TokenKind.LPAREN);
+ parens++;
+ break;
+ case TokenKind.RPAREN:
+ _eat(TokenKind.RPAREN);
+ if (--parens == 0) {
+ var tok = tokenizer.makeIEFilter(
+ startAfterProgidColon.start.offset, _peekToken.start);
+ return LiteralTerm(tok.text, tok.text, tok.span);
+ }
+ break;
+ default:
+ _eat(_peek());
+ }
+ }
+ }
+
+ // TODO(terry): Hack to gobble up the calc expression as a string looking
+ // for the matching RPAREN the expression is not parsed into the
+ // AST.
+ //
+ // grammar should be:
+ //
+ // <calc()> = calc( <calc-sum> )
+ // <calc-sum> = <calc-product> [ [ '+' | '-' ] <calc-product> ]*
+ // <calc-product> = <calc-value> [ '*' <calc-value> | '/' <number> ]*
+ // <calc-value> = <number> | <dimension> | <percentage> | ( <calc-sum> )
+ //
+ String processCalcExpression() {
+ var inString = tokenizer._inString;
+ tokenizer._inString = false;
+
+ // Gobble up everything until we hit our stop token.
+ var stringValue = StringBuffer();
+ var left = 1;
+ var matchingParens = false;
+ while (_peek() != TokenKind.END_OF_FILE && !matchingParens) {
+ var token = _peek();
+ if (token == TokenKind.LPAREN) {
+ left++;
+ } else if (token == TokenKind.RPAREN) {
+ left--;
+ }
+
+ matchingParens = left == 0;
+ if (!matchingParens) stringValue.write(_next().text);
+ }
+
+ if (!matchingParens) {
+ _error('problem parsing function expected ), ', _peekToken.span);
+ }
+
+ tokenizer._inString = inString;
+
+ return stringValue.toString();
+ }
+
+ CalcTerm? processCalc(Identifier func) {
+ var start = _peekToken.span;
+
+ var name = func.name;
+ if (const {'calc', '-webkit-calc', '-moz-calc', 'min', 'max', 'clamp'}
+ .contains(name)) {
+ // TODO(terry): Implement expression parsing properly.
+ var expression = processCalcExpression();
+ var calcExpr = LiteralTerm(expression, expression, _makeSpan(start));
+
+ if (!_maybeEat(TokenKind.RPAREN)) {
+ _error('problem parsing function expected ), ', _peekToken.span);
+ }
+
+ return CalcTerm(name, name, calcExpr, _makeSpan(start));
+ }
+
+ return null;
+ }
+
+ // Function grammar:
+ //
+ // function: IDENT '(' expr ')'
+ //
+ TreeNode /* LiteralTerm | Expression */ processFunction(Identifier func) {
+ var start = _peekToken.span;
+ var name = func.name;
+
+ switch (name) {
+ case 'url':
+ // URI term sucks up everything inside of quotes(' or ") or between
+ // parens.
+ var urlParam = processQuotedString(true);
+
+ // TODO(terry): Better error message and checking for mismatched quotes.
+ if (_peek() == TokenKind.END_OF_FILE) {
+ _error('problem parsing URI', _peekToken.span);
+ }
+
+ if (_peek() == TokenKind.RPAREN) {
+ _next();
+ }
+
+ return UriTerm(urlParam, _makeSpan(start));
+ case 'var':
+ // TODO(terry): Consider handling var in IE specific filter/progid.
+ // This will require parsing entire IE specific syntax
+ // e.g. `param = value` or `progid:com_id`, etc.
+ // for example:
+ //
+ // var-blur: Blur(Add = 0, Direction = 225, Strength = 10);
+ // var-gradient: progid:DXImageTransform.Microsoft.gradient"
+ // (GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670');
+ var expr = processExpr();
+ if (!_maybeEat(TokenKind.RPAREN)) {
+ _error('problem parsing var expected ), ', _peekToken.span);
+ }
+ if (isChecked &&
+ expr.expressions.whereType<OperatorComma>().length > 1) {
+ _error('too many parameters to var()', _peekToken.span);
+ }
+
+ var paramName = (expr.expressions[0] as LiteralTerm).text;
+
+ // [0] - var name, [1] - OperatorComma, [2] - default value.
+ var defaultValues = expr.expressions.length >= 3
+ ? expr.expressions.sublist(2)
+ : <Expression>[];
+ return VarUsage(paramName, defaultValues, _makeSpan(start));
+ default:
+ var expr = processExpr();
+ if (!_maybeEat(TokenKind.RPAREN)) {
+ _error('problem parsing function expected ), ', _peekToken.span);
+ }
+
+ return FunctionTerm(name, name, expr, _makeSpan(start));
+ }
+ }
+
+ Identifier identifier() {
+ var tok = _next();
+
+ if (!TokenKind.isIdentifier(tok.kind) &&
+ !TokenKind.isKindIdentifier(tok.kind)) {
+ if (isChecked) {
+ _warning('expected identifier, but found $tok', tok.span);
+ }
+ return Identifier('', _makeSpan(tok.span));
+ }
+
+ return Identifier(tok.text, _makeSpan(tok.span));
+ }
+
+ // TODO(terry): Move this to base <= 36 and into shared code.
+ static int _hexDigit(int c) {
+ if (c >= 48 /*0*/ && c <= 57 /*9*/) {
+ return c - 48;
+ } else if (c >= 97 /*a*/ && c <= 102 /*f*/) {
+ return c - 87;
+ } else if (c >= 65 /*A*/ && c <= 70 /*F*/) {
+ return c - 55;
+ } else {
+ return -1;
+ }
+ }
+
+ HexColorTerm _parseHex(String hexText, SourceSpan span) {
+ var hexValue = 0;
+
+ for (var i = 0; i < hexText.length; i++) {
+ var digit = _hexDigit(hexText.codeUnitAt(i));
+ if (digit < 0) {
+ _warning('Bad hex number', span);
+ return HexColorTerm(BAD_HEX_VALUE(), hexText, span);
+ }
+ hexValue = (hexValue << 4) + digit;
+ }
+
+ // Make 3 character hex value #RRGGBB => #RGB iff:
+ // high/low nibble of RR is the same, high/low nibble of GG is the same and
+ // high/low nibble of BB is the same.
+ if (hexText.length == 6 &&
+ hexText[0] == hexText[1] &&
+ hexText[2] == hexText[3] &&
+ hexText[4] == hexText[5]) {
+ hexText = '${hexText[0]}${hexText[2]}${hexText[4]}';
+ } else if (hexText.length == 4 &&
+ hexText[0] == hexText[1] &&
+ hexText[2] == hexText[3]) {
+ hexText = '${hexText[0]}${hexText[2]}';
+ } else if (hexText.length == 2 && hexText[0] == hexText[1]) {
+ hexText = hexText[0];
+ }
+ return HexColorTerm(hexValue, hexText, span);
+ }
+}
+
+class ExpressionsProcessor {
+ final Expressions _exprs;
+ int _index = 0;
+
+ ExpressionsProcessor(this._exprs);
+
+ // TODO(terry): Only handles ##px unit.
+ FontExpression processFontSize() {
+ // font-size[/line-height]
+ //
+ // Possible size values:
+ // * xx-small
+ // * small
+ // * medium [default]
+ // * large
+ // * x-large
+ // * xx-large
+ // * smaller
+ // * larger
+ // * ##length in px, pt, etc.
+ // * ##%, percent of parent elem's font-size
+ // * inherit
+ LengthTerm? size;
+ LineHeight? lineHt;
+ var nextIsLineHeight = false;
+ for (; _index < _exprs.expressions.length; _index++) {
+ var expr = _exprs.expressions[_index];
+ if (size == null && expr is LengthTerm) {
+ // font-size part.
+ size = expr;
+ } else if (size != null) {
+ if (expr is OperatorSlash) {
+ // LineHeight could follow?
+ nextIsLineHeight = true;
+ } else if (nextIsLineHeight && expr is LengthTerm) {
+ assert(expr.unit == TokenKind.UNIT_LENGTH_PX);
+ lineHt = LineHeight(expr.value as num, inPixels: true);
+ nextIsLineHeight = false;
+ _index++;
+ break;
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+
+ return FontExpression(_exprs.span, size: size, lineHeight: lineHt);
+ }
+
+ FontExpression processFontFamily() {
+ var family = <String>[];
+
+ // Possible family values:
+ // * font-family: arial, Times new roman ,Lucida Sans Unicode,Courier;
+ // * font-family: "Times New Roman", arial, Lucida Sans Unicode, Courier;
+ var moreFamilies = false;
+
+ for (; _index < _exprs.expressions.length; _index++) {
+ var expr = _exprs.expressions[_index];
+ if (expr is LiteralTerm) {
+ if (family.isEmpty || moreFamilies) {
+ // It's font-family now.
+ family.add(expr.toString());
+ moreFamilies = false;
+ } else if (isChecked) {
+ messages.warning('Only font-family can be a list', _exprs.span);
+ }
+ } else if (expr is OperatorComma && family.isNotEmpty) {
+ moreFamilies = true;
+ } else {
+ break;
+ }
+ }
+
+ return FontExpression(_exprs.span, family: family);
+ }
+
+ FontExpression processFont() {
+ // Process all parts of the font expression.
+ FontExpression? fontSize;
+ FontExpression? fontFamily;
+ for (; _index < _exprs.expressions.length; _index++) {
+ // Order is font-size font-family
+ fontSize ??= processFontSize();
+ fontFamily ??= processFontFamily();
+ // TODO(terry): Handle font-weight, font-style, and font-variant. See
+ // https://github.com/dart-lang/csslib/issues/3
+ // https://github.com/dart-lang/csslib/issues/4
+ // https://github.com/dart-lang/csslib/issues/5
+ }
+
+ return FontExpression(_exprs.span,
+ size: fontSize?.font.size,
+ lineHeight: fontSize?.font.lineHeight,
+ family: fontFamily?.font.family);
+ }
+}
+
+/// Escapes [text] for use in a CSS string.
+/// [single] specifies single quote `'` vs double quote `"`.
+String _escapeString(String text, {bool single = false}) {
+ StringBuffer? result;
+
+ for (var i = 0; i < text.length; i++) {
+ var code = text.codeUnitAt(i);
+ String? replace;
+ switch (code) {
+ case 34 /*'"'*/ :
+ if (!single) replace = r'\"';
+ break;
+ case 39 /*"'"*/ :
+ if (single) replace = r"\'";
+ break;
+ }
+
+ if (replace != null && result == null) {
+ result = StringBuffer(text.substring(0, i));
+ }
+
+ if (result != null) result.write(replace ?? text[i]);
+ }
+
+ return result == null ? text : result.toString();
+}
diff --git a/pkgs/csslib/lib/src/analyzer.dart b/pkgs/csslib/lib/src/analyzer.dart
new file mode 100644
index 0000000..9d6f4ff
--- /dev/null
+++ b/pkgs/csslib/lib/src/analyzer.dart
@@ -0,0 +1,1014 @@
+// 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.
+
+part of '../parser.dart';
+
+// TODO(terry): Add optimizing phase to remove duplicated selectors in the same
+// selector group (e.g., .btn, .btn { color: red; }). Also, look
+// at simplifying selectors expressions too (much harder).
+// TODO(terry): Detect invalid directive usage. All @imports must occur before
+// all rules other than @charset directive. Any @import directive
+// after any non @charset or @import directive are ignored. e.g.,
+// @import "a.css";
+// div { color: red; }
+// @import "b.css";
+// becomes:
+// @import "a.css";
+// div { color: red; }
+// <http://www.w3.org/TR/css3-syntax/#at-rules>
+
+/// Analysis phase will validate/fixup any new CSS feature or any Sass style
+/// feature.
+class Analyzer {
+ final List<StyleSheet> _styleSheets;
+ final Messages _messages;
+
+ Analyzer(this._styleSheets, this._messages);
+
+ // TODO(terry): Currently each feature walks the AST each time. Once we have
+ // our complete feature set consider benchmarking the cost and
+ // possibly combine in one walk.
+ void run() {
+ // Expand top-level @include.
+ TopLevelIncludes.expand(_messages, _styleSheets);
+
+ // Expand @include in declarations.
+ DeclarationIncludes.expand(_messages, _styleSheets);
+
+ // Remove all @mixin and @include
+ for (var styleSheet in _styleSheets) {
+ MixinsAndIncludes.remove(styleSheet);
+ }
+
+ // Expand any nested selectors using selector descendant combinator to
+ // signal CSS inheritance notation.
+ for (var styleSheet in _styleSheets) {
+ ExpandNestedSelectors()
+ ..visitStyleSheet(styleSheet)
+ ..flatten(styleSheet);
+ }
+
+ // Expand any @extend.
+ for (var styleSheet in _styleSheets) {
+ var allExtends = AllExtends()..visitStyleSheet(styleSheet);
+ InheritExtends(_messages, allExtends).visitStyleSheet(styleSheet);
+ }
+ }
+}
+
+/// Traverse all rulesets looking for nested ones. If a ruleset is in a
+/// declaration group (implies nested selector) then generate new ruleset(s) at
+/// level 0 of CSS using selector inheritance syntax (flattens the nesting).
+///
+/// How the AST works for a rule [RuleSet] and nested rules. First of all a
+/// CSS rule [RuleSet] consist of a selector and a declaration e.g.,
+///
+/// selector {
+/// declaration
+/// }
+///
+/// AST structure of a [RuleSet] is:
+///
+/// RuleSet
+/// SelectorGroup
+/// List<Selector>
+/// List<SimpleSelectorSequence>
+/// Combinator // +, >, ~, DESCENDENT, or NONE
+/// SimpleSelector // class, id, element, namespace, attribute
+/// DeclarationGroup
+/// List // Declaration or RuleSet
+///
+/// For the simple rule:
+///
+/// div + span { color: red; }
+///
+/// the AST [RuleSet] is:
+///
+/// RuleSet
+/// SelectorGroup
+/// List<Selector>
+/// [0]
+/// List<SimpleSelectorSequence>
+/// [0] Combinator = COMBINATOR_NONE
+/// ElementSelector (name = div)
+/// [1] Combinator = COMBINATOR_PLUS
+/// ElementSelector (name = span)
+/// DeclarationGroup
+/// List // Declarations or RuleSets
+/// [0]
+/// Declaration (property = color, expression = red)
+///
+/// Usually a SelectorGroup contains 1 Selector. Consider the selectors:
+///
+/// div { color: red; }
+/// a { color: red; }
+///
+/// are equivalent to
+///
+/// div, a { color : red; }
+///
+/// In the above the RuleSet would have a SelectorGroup with 2 selectors e.g.,
+///
+/// RuleSet
+/// SelectorGroup
+/// List<Selector>
+/// [0]
+/// List<SimpleSelectorSequence>
+/// [0] Combinator = COMBINATOR_NONE
+/// ElementSelector (name = div)
+/// [1]
+/// List<SimpleSelectorSequence>
+/// [0] Combinator = COMBINATOR_NONE
+/// ElementSelector (name = a)
+/// DeclarationGroup
+/// List // Declarations or RuleSets
+/// [0]
+/// Declaration (property = color, expression = red)
+///
+/// For a nested rule e.g.,
+///
+/// div {
+/// color : blue;
+/// a { color : red; }
+/// }
+///
+/// Would map to the follow CSS rules:
+///
+/// div { color: blue; }
+/// div a { color: red; }
+///
+/// The AST for the former nested rule is:
+///
+/// RuleSet
+/// SelectorGroup
+/// List<Selector>
+/// [0]
+/// List<SimpleSelectorSequence>
+/// [0] Combinator = COMBINATOR_NONE
+/// ElementSelector (name = div)
+/// DeclarationGroup
+/// List // Declarations or RuleSets
+/// [0]
+/// Declaration (property = color, expression = blue)
+/// [1]
+/// RuleSet
+/// SelectorGroup
+/// List<Selector>
+/// [0]
+/// List<SimpleSelectorSequence>
+/// [0] Combinator = COMBINATOR_NONE
+/// ElementSelector (name = a)
+/// DeclarationGroup
+/// List // Declarations or RuleSets
+/// [0]
+/// Declaration (property = color, expression = red)
+///
+/// Nested rules is a terse mechanism to describe CSS inheritance. The analyzer
+/// will flatten and expand the nested rules to it's flatten structure. Using
+/// the all parent [RuleSets] (selector expressions) and applying each nested
+/// [RuleSet] to the list of [Selectors] in a [SelectorGroup].
+///
+/// Then result is a style sheet where all nested rules have been flatten and
+/// expanded.
+class ExpandNestedSelectors extends Visitor {
+ /// Parent [RuleSet] if a nested rule otherwise `null`.
+ RuleSet? _parentRuleSet;
+
+ /// Top-most rule if nested rules.
+ SelectorGroup? _topLevelSelectorGroup;
+
+ /// SelectorGroup at each nesting level.
+ SelectorGroup? _nestedSelectorGroup;
+
+ /// Declaration (sans the nested selectors).
+ DeclarationGroup? _flatDeclarationGroup;
+
+ /// Each nested selector get's a flatten RuleSet.
+ List<RuleSet> _expandedRuleSets = [];
+
+ /// Maping of a nested rule set to the fully expanded list of RuleSet(s).
+ final _expansions = <RuleSet, List<RuleSet>>{};
+
+ @override
+ void visitRuleSet(RuleSet node) {
+ final oldParent = _parentRuleSet;
+
+ var oldNestedSelectorGroups = _nestedSelectorGroup;
+
+ if (_nestedSelectorGroup == null) {
+ // Create top-level selector (may have nested rules).
+ final newSelectors = node.selectorGroup!.selectors.toList();
+ _topLevelSelectorGroup = SelectorGroup(newSelectors, node.span);
+ _nestedSelectorGroup = _topLevelSelectorGroup;
+ } else {
+ // Generate new selector groups from the nested rules.
+ _nestedSelectorGroup = _mergeToFlatten(node);
+ }
+
+ _parentRuleSet = node;
+
+ super.visitRuleSet(node);
+
+ _parentRuleSet = oldParent;
+
+ // Remove nested rules; they're all flatten and in the _expandedRuleSets.
+ node.declarationGroup.declarations
+ .removeWhere((declaration) => declaration is RuleSet);
+
+ _nestedSelectorGroup = oldNestedSelectorGroups;
+
+ // If any expandedRuleSets and we're back at the top-level rule set then
+ // there were nested rule set(s).
+ if (_parentRuleSet == null) {
+ if (_expandedRuleSets.isNotEmpty) {
+ // Remember ruleset to replace with these flattened rulesets.
+ _expansions[node] = _expandedRuleSets;
+ _expandedRuleSets = [];
+ }
+ assert(_flatDeclarationGroup == null);
+ assert(_nestedSelectorGroup == null);
+ }
+ }
+
+ /// Build up the list of all inherited sequences from the parent selector
+ /// [node] is the current nested selector and it's parent is the last entry in
+ /// the [_nestedSelectorGroup].
+ SelectorGroup _mergeToFlatten(RuleSet node) {
+ // Create a new SelectorGroup for this nesting level.
+ var nestedSelectors = _nestedSelectorGroup!.selectors;
+ var selectors = node.selectorGroup!.selectors;
+
+ // Create a merged set of previous parent selectors and current selectors.
+ var newSelectors = <Selector>[];
+ for (var selector in selectors) {
+ for (var nestedSelector in nestedSelectors) {
+ var seq = _mergeNestedSelector(nestedSelector.simpleSelectorSequences,
+ selector.simpleSelectorSequences);
+ newSelectors.add(Selector(seq, node.span));
+ }
+ }
+
+ return SelectorGroup(newSelectors, node.span);
+ }
+
+ /// Merge the nested selector sequences [current] to the [parent] sequences or
+ /// substitute any & with the parent selector.
+ List<SimpleSelectorSequence> _mergeNestedSelector(
+ List<SimpleSelectorSequence> parent,
+ List<SimpleSelectorSequence> current) {
+ // If any & operator then the parent selector will be substituted otherwise
+ // the parent selector is pre-pended to the current selector.
+ var hasThis = current.any((s) => s.simpleSelector.isThis);
+
+ var newSequence = <SimpleSelectorSequence>[];
+
+ if (!hasThis) {
+ // If no & in the sector group then prefix with the parent selector.
+ newSequence.addAll(parent);
+ newSequence.addAll(_convertToDescendentSequence(current));
+ } else {
+ for (var sequence in current) {
+ if (sequence.simpleSelector.isThis) {
+ // Substitute the & with the parent selector and only use a combinator
+ // descendant if & is prefix by a sequence with an empty name e.g.,
+ // "... + &", "&", "... ~ &", etc.
+ var hasPrefix = newSequence.isNotEmpty &&
+ newSequence.last.simpleSelector.name.isNotEmpty;
+ newSequence.addAll(
+ hasPrefix ? _convertToDescendentSequence(parent) : parent);
+ } else {
+ newSequence.add(sequence);
+ }
+ }
+ }
+
+ return newSequence;
+ }
+
+ /// Return selector sequences with first sequence combinator being a
+ /// descendant. Used for nested selectors when the parent selector needs to
+ /// be prefixed to a nested selector or to substitute the this (&) with the
+ /// parent selector.
+ List<SimpleSelectorSequence> _convertToDescendentSequence(
+ List<SimpleSelectorSequence> sequences) {
+ if (sequences.isEmpty) return sequences;
+
+ var newSequences = <SimpleSelectorSequence>[];
+ var first = sequences.first;
+ newSequences.add(SimpleSelectorSequence(
+ first.simpleSelector, first.span, TokenKind.COMBINATOR_DESCENDANT));
+ newSequences.addAll(sequences.skip(1));
+
+ return newSequences;
+ }
+
+ @override
+ void visitDeclarationGroup(DeclarationGroup node) {
+ var span = node.span;
+
+ var currentGroup = DeclarationGroup([], span);
+
+ var oldGroup = _flatDeclarationGroup;
+ _flatDeclarationGroup = currentGroup;
+
+ var expandedLength = _expandedRuleSets.length;
+
+ super.visitDeclarationGroup(node);
+
+ // We're done with the group.
+ _flatDeclarationGroup = oldGroup;
+
+ // No nested rule to process it's a top-level rule.
+ if (_nestedSelectorGroup == _topLevelSelectorGroup) return;
+
+ // If flatten selector's declaration is empty skip this selector, no need
+ // to emit an empty nested selector.
+ if (currentGroup.declarations.isEmpty) return;
+
+ var selectorGroup = _nestedSelectorGroup;
+
+ // Build new rule set from the nested selectors and declarations.
+ var newRuleSet = RuleSet(selectorGroup, currentGroup, span);
+
+ // Place in order so outer-most rule is first.
+ if (expandedLength == _expandedRuleSets.length) {
+ _expandedRuleSets.add(newRuleSet);
+ } else {
+ _expandedRuleSets.insert(expandedLength, newRuleSet);
+ }
+ }
+
+ // Record all declarations in a nested selector (Declaration, VarDefinition
+ // and MarginGroup) but not the nested rule in the Declaration.
+
+ @override
+ void visitDeclaration(Declaration node) {
+ if (_parentRuleSet != null) {
+ _flatDeclarationGroup!.declarations.add(node);
+ }
+ super.visitDeclaration(node);
+ }
+
+ @override
+ void visitVarDefinition(VarDefinition node) {
+ if (_parentRuleSet != null) {
+ _flatDeclarationGroup!.declarations.add(node);
+ }
+ super.visitVarDefinition(node);
+ }
+
+ @override
+ void visitExtendDeclaration(ExtendDeclaration node) {
+ if (_parentRuleSet != null) {
+ _flatDeclarationGroup!.declarations.add(node);
+ }
+ super.visitExtendDeclaration(node);
+ }
+
+ @override
+ void visitMarginGroup(MarginGroup node) {
+ if (_parentRuleSet != null) {
+ _flatDeclarationGroup!.declarations.add(node);
+ }
+ super.visitMarginGroup(node);
+ }
+
+ /// Replace the rule set that contains nested rules with the flatten rule
+ /// sets.
+ void flatten(StyleSheet styleSheet) {
+ // TODO(terry): Iterate over topLevels instead of _expansions it's already
+ // a map (this maybe quadratic).
+ _expansions.forEach((RuleSet ruleSet, List<RuleSet> newRules) {
+ var index = styleSheet.topLevels.indexOf(ruleSet);
+ if (index == -1) {
+ // Check any @media directives for nested rules and replace them.
+ var found = _MediaRulesReplacer.replace(styleSheet, ruleSet, newRules);
+ assert(found);
+ } else {
+ styleSheet.topLevels.insertAll(index + 1, newRules);
+ }
+ });
+ _expansions.clear();
+ }
+}
+
+class _MediaRulesReplacer extends Visitor {
+ final RuleSet _ruleSet;
+ final List<RuleSet> _newRules;
+ bool _foundAndReplaced = false;
+
+ /// Look for the [ruleSet] inside of an @media directive; if found then
+ /// replace with the [newRules]. If [ruleSet] is found and replaced return
+ /// true.
+ static bool replace(
+ StyleSheet styleSheet, RuleSet ruleSet, List<RuleSet> newRules) {
+ var visitor = _MediaRulesReplacer(ruleSet, newRules);
+ visitor.visitStyleSheet(styleSheet);
+ return visitor._foundAndReplaced;
+ }
+
+ _MediaRulesReplacer(this._ruleSet, this._newRules);
+
+ @override
+ void visitMediaDirective(MediaDirective node) {
+ var index = node.rules.indexOf(_ruleSet);
+ if (index != -1) {
+ node.rules.insertAll(index + 1, _newRules);
+ _foundAndReplaced = true;
+ }
+ }
+}
+
+/// Expand all @include at the top-level the ruleset(s) associated with the
+/// mixin.
+class TopLevelIncludes extends Visitor {
+ StyleSheet? _styleSheet;
+ final Messages _messages;
+
+ /// Map of variable name key to it's definition.
+ final map = <String, MixinDefinition>{};
+ MixinDefinition? currDef;
+
+ static void expand(Messages messages, List<StyleSheet> styleSheets) {
+ TopLevelIncludes(messages, styleSheets);
+ }
+
+ bool _anyRulesets(MixinRulesetDirective def) =>
+ def.rulesets.any((rule) => rule is RuleSet);
+
+ TopLevelIncludes(this._messages, List<StyleSheet> styleSheets) {
+ for (var styleSheet in styleSheets) {
+ visitTree(styleSheet);
+ }
+ }
+
+ @override
+ void visitStyleSheet(StyleSheet ss) {
+ _styleSheet = ss;
+ super.visitStyleSheet(ss);
+ _styleSheet = null;
+ }
+
+ @override
+ void visitIncludeDirective(IncludeDirective node) {
+ final currDef = this.currDef;
+ if (map.containsKey(node.name)) {
+ var mixinDef = map[node.name];
+ if (mixinDef is MixinRulesetDirective) {
+ _TopLevelIncludeReplacer.replace(_styleSheet!, node, mixinDef.rulesets);
+ } else if (currDef is MixinRulesetDirective && _anyRulesets(currDef)) {
+ final mixinRuleset = currDef;
+ var index = mixinRuleset.rulesets.indexOf(node);
+ mixinRuleset.rulesets.removeAt(index);
+ _messages.warning(
+ 'Using declaration mixin ${node.name} as top-level mixin',
+ node.span);
+ }
+ } else {
+ if (currDef is MixinRulesetDirective) {
+ var rulesetDirect = currDef;
+ rulesetDirect.rulesets.removeWhere((entry) {
+ if (entry == node) {
+ _messages.warning('Undefined mixin ${node.name}', node.span);
+ return true;
+ }
+ return false;
+ });
+ }
+ }
+ super.visitIncludeDirective(node);
+ }
+
+ @override
+ void visitMixinRulesetDirective(MixinRulesetDirective node) {
+ currDef = node;
+
+ super.visitMixinRulesetDirective(node);
+
+ // Replace with latest top-level mixin definition.
+ map[node.name] = node;
+ currDef = null;
+ }
+
+ @override
+ void visitMixinDeclarationDirective(MixinDeclarationDirective node) {
+ currDef = node;
+
+ super.visitMixinDeclarationDirective(node);
+
+ // Replace with latest mixin definition.
+ map[node.name] = node;
+ currDef = null;
+ }
+}
+
+/// @include as a top-level with ruleset(s).
+class _TopLevelIncludeReplacer extends Visitor {
+ final IncludeDirective _include;
+ final List<TreeNode> _newRules;
+
+ /// Look for the [ruleSet] inside of an @media directive; if found then
+ /// replace with the [newRules]. If [ruleSet] is found and replaced return
+ /// true.
+ static void replace(StyleSheet styleSheet, IncludeDirective include,
+ List<TreeNode> newRules) {
+ _TopLevelIncludeReplacer(include, newRules).visitStyleSheet(styleSheet);
+ }
+
+ _TopLevelIncludeReplacer(this._include, this._newRules);
+
+ @override
+ void visitStyleSheet(StyleSheet node) {
+ var index = node.topLevels.indexOf(_include);
+ if (index != -1) {
+ node.topLevels.insertAll(index + 1, _newRules);
+ node.topLevels.replaceRange(index, index + 1, [NoOp()]);
+ }
+ super.visitStyleSheet(node);
+ }
+
+ @override
+ void visitMixinRulesetDirective(MixinRulesetDirective node) {
+ var index = node.rulesets.indexOf(_include);
+ if (index != -1) {
+ node.rulesets.insertAll(index + 1, _newRules);
+ // Only the resolve the @include once.
+ node.rulesets.replaceRange(index, index + 1, [NoOp()]);
+ }
+ super.visitMixinRulesetDirective(node);
+ }
+}
+
+/// Utility function to match an include to a list of either Declarations or
+/// RuleSets, depending on type of mixin (ruleset or declaration). The include
+/// can be an include in a declaration or an include directive (top-level).
+int _findInclude(List<Object> list, TreeNode node) {
+ final matchNode = (node is IncludeMixinAtDeclaration)
+ ? node.include
+ : node as IncludeDirective;
+
+ var index = 0;
+ for (var item in list) {
+ var includeNode = (item is IncludeMixinAtDeclaration) ? item.include : item;
+ if (includeNode == matchNode) return index;
+ index++;
+ }
+ return -1;
+}
+
+/// Stamp out a mixin with the defined args substituted with the user's
+/// parameters.
+class CallMixin extends Visitor {
+ final MixinDefinition mixinDef;
+ List<TreeNode>? _definedArgs;
+ Expressions? _currExpressions;
+ int _currIndex = -1;
+
+ final varUsages = <String, Map<Expressions, Set<int>>>{};
+
+ /// Only var defs with more than one expression (comma separated).
+ final Map<String, VarDefinition>? varDefs;
+
+ CallMixin(this.mixinDef, [this.varDefs]) {
+ if (mixinDef is MixinRulesetDirective) {
+ visitMixinRulesetDirective(mixinDef as MixinRulesetDirective);
+ } else {
+ visitMixinDeclarationDirective(mixinDef as MixinDeclarationDirective);
+ }
+ }
+
+ /// Given a mixin's defined arguments return a cloned mixin definition that
+ /// has replaced all defined arguments with user's supplied VarUsages.
+ MixinDefinition transform(List<List<Expression>> callArgs) {
+ // TODO(terry): Handle default arguments and varArgs.
+ // Transform mixin with callArgs.
+ for (var index = 0; index < _definedArgs!.length; index++) {
+ var definedArg = _definedArgs![index];
+ VarDefinition? varDef;
+ if (definedArg is VarDefinition) {
+ varDef = definedArg;
+ } else if (definedArg is VarDefinitionDirective) {
+ var varDirective = definedArg;
+ varDef = varDirective.def;
+ }
+ var callArg = callArgs[index];
+
+ // Is callArg a var definition with multi-args (expressions > 1).
+ var defArgs = _varDefsAsCallArgs(callArg);
+ if (defArgs.isNotEmpty) {
+ // Replace call args with the var def parameters.
+ callArgs.insertAll(index, defArgs);
+ callArgs.removeAt(index + defArgs.length);
+ callArg = callArgs[index];
+ }
+
+ var expressions = varUsages[varDef!.definedName];
+ expressions!.forEach((k, v) {
+ for (var usagesIndex in v) {
+ k.expressions.replaceRange(usagesIndex, usagesIndex + 1, callArg);
+ }
+ });
+ }
+
+ // Clone the mixin
+ return mixinDef.clone();
+ }
+
+ /// Rip apart var def with multiple parameters.
+ List<List<Expression>> _varDefsAsCallArgs(List<Expression> callArg) {
+ var defArgs = <List<Expression>>[];
+ var firstCallArg = callArg[0];
+ if (firstCallArg is VarUsage) {
+ var varDef = varDefs![firstCallArg.name];
+ var expressions = (varDef!.expression as Expressions).expressions;
+ assert(expressions.length > 1);
+ for (var expr in expressions) {
+ if (expr is! OperatorComma) {
+ defArgs.add([expr]);
+ }
+ }
+ }
+ return defArgs;
+ }
+
+ @override
+ void visitExpressions(Expressions node) {
+ var oldExpressions = _currExpressions;
+ var oldIndex = _currIndex;
+
+ _currExpressions = node;
+ for (_currIndex = 0; _currIndex < node.expressions.length; _currIndex++) {
+ node.expressions[_currIndex].visit(this);
+ }
+
+ _currIndex = oldIndex;
+ _currExpressions = oldExpressions;
+ }
+
+ void _addExpression(Map<Expressions, Set<int>> expressions) {
+ var indexSet = <int>{};
+ indexSet.add(_currIndex);
+ expressions[_currExpressions!] = indexSet;
+ }
+
+ @override
+ void visitVarUsage(VarUsage node) {
+ assert(_currIndex != -1);
+ assert(_currExpressions != null);
+ if (varUsages.containsKey(node.name)) {
+ var expressions = varUsages[node.name];
+ var allIndexes = expressions![_currExpressions];
+ if (allIndexes == null) {
+ _addExpression(expressions);
+ } else {
+ allIndexes.add(_currIndex);
+ }
+ } else {
+ var newExpressions = <Expressions, Set<int>>{};
+ _addExpression(newExpressions);
+ varUsages[node.name] = newExpressions;
+ }
+ super.visitVarUsage(node);
+ }
+
+ @override
+ void visitMixinDeclarationDirective(MixinDeclarationDirective node) {
+ _definedArgs = node.definedArgs;
+ super.visitMixinDeclarationDirective(node);
+ }
+
+ @override
+ void visitMixinRulesetDirective(MixinRulesetDirective node) {
+ _definedArgs = node.definedArgs;
+ super.visitMixinRulesetDirective(node);
+ }
+}
+
+/// Expand all @include inside of a declaration associated with a mixin.
+class DeclarationIncludes extends Visitor {
+ StyleSheet? _styleSheet;
+ final Messages _messages;
+
+ /// Map of variable name key to it's definition.
+ final Map<String, MixinDefinition> map = <String, MixinDefinition>{};
+
+ /// Cache of mixin called with parameters.
+ final Map<String, CallMixin> callMap = <String, CallMixin>{};
+ MixinDefinition? currDef;
+ DeclarationGroup? currDeclGroup;
+
+ /// Var definitions with more than 1 expression.
+ final varDefs = <String, VarDefinition>{};
+
+ static void expand(Messages messages, List<StyleSheet> styleSheets) {
+ DeclarationIncludes(messages, styleSheets);
+ }
+
+ DeclarationIncludes(this._messages, List<StyleSheet> styleSheets) {
+ for (var styleSheet in styleSheets) {
+ visitTree(styleSheet);
+ }
+ }
+
+ bool _allIncludes(List<TreeNode> rulesets) =>
+ rulesets.every((rule) => rule is IncludeDirective || rule is NoOp);
+
+ CallMixin _createCallDeclMixin(MixinDefinition mixinDef) =>
+ callMap[mixinDef.name] ??= CallMixin(mixinDef, varDefs);
+
+ @override
+ void visitStyleSheet(StyleSheet ss) {
+ _styleSheet = ss;
+ super.visitStyleSheet(ss);
+ _styleSheet = null;
+ }
+
+ @override
+ void visitDeclarationGroup(DeclarationGroup node) {
+ currDeclGroup = node;
+ super.visitDeclarationGroup(node);
+ currDeclGroup = null;
+ }
+
+ @override
+ void visitIncludeMixinAtDeclaration(IncludeMixinAtDeclaration node) {
+ if (map.containsKey(node.include.name)) {
+ var mixinDef = map[node.include.name];
+
+ // Fix up any mixin that is really a Declaration but has includes.
+ if (mixinDef is MixinRulesetDirective) {
+ if (!_allIncludes(mixinDef.rulesets) && currDeclGroup != null) {
+ var index = _findInclude(currDeclGroup!.declarations, node);
+ if (index != -1) {
+ currDeclGroup!.declarations
+ .replaceRange(index, index + 1, [NoOp()]);
+ }
+ _messages.warning(
+ 'Using top-level mixin ${node.include.name} as a declaration',
+ node.span);
+ } else {
+ // We're a list of @include(s) inside of a mixin ruleset - convert
+ // to a list of IncludeMixinAtDeclaration(s).
+ var origRulesets = mixinDef.rulesets;
+ var rulesets = <Declaration>[];
+ if (origRulesets.every((ruleset) => ruleset is IncludeDirective)) {
+ for (var ruleset in origRulesets) {
+ rulesets.add(IncludeMixinAtDeclaration(
+ ruleset as IncludeDirective, ruleset.span));
+ }
+ _IncludeReplacer.replace(_styleSheet!, node, rulesets);
+ }
+ }
+ }
+
+ if (mixinDef!.definedArgs.isNotEmpty && node.include.args.isNotEmpty) {
+ var callMixin = _createCallDeclMixin(mixinDef);
+ mixinDef = callMixin.transform(node.include.args);
+ }
+
+ if (mixinDef is MixinDeclarationDirective) {
+ _IncludeReplacer.replace(
+ _styleSheet!, node, mixinDef.declarations.declarations);
+ }
+ } else {
+ _messages.warning('Undefined mixin ${node.include.name}', node.span);
+ }
+
+ super.visitIncludeMixinAtDeclaration(node);
+ }
+
+ @override
+ void visitIncludeDirective(IncludeDirective node) {
+ if (map.containsKey(node.name)) {
+ var mixinDef = map[node.name];
+ if (currDef is MixinDeclarationDirective &&
+ mixinDef is MixinDeclarationDirective) {
+ _IncludeReplacer.replace(
+ _styleSheet!, node, mixinDef.declarations.declarations);
+ } else if (currDef is MixinDeclarationDirective) {
+ var decls =
+ (currDef as MixinDeclarationDirective).declarations.declarations;
+ var index = _findInclude(decls, node);
+ if (index != -1) {
+ decls.replaceRange(index, index + 1, [NoOp()]);
+ }
+ }
+ }
+
+ super.visitIncludeDirective(node);
+ }
+
+ @override
+ void visitMixinRulesetDirective(MixinRulesetDirective node) {
+ currDef = node;
+
+ super.visitMixinRulesetDirective(node);
+
+ // Replace with latest top-level mixin definition.
+ map[node.name] = node;
+ currDef = null;
+ }
+
+ @override
+ void visitMixinDeclarationDirective(MixinDeclarationDirective node) {
+ currDef = node;
+
+ super.visitMixinDeclarationDirective(node);
+
+ // Replace with latest mixin definition.
+ map[node.name] = node;
+ currDef = null;
+ }
+
+ @override
+ void visitVarDefinition(VarDefinition node) {
+ // Only record var definitions that have multiple expressions (comma
+ // separated for mixin parameter substitution.
+ var exprs = (node.expression as Expressions).expressions;
+ if (exprs.length > 1) {
+ varDefs[node.definedName] = node;
+ }
+ super.visitVarDefinition(node);
+ }
+
+ @override
+ void visitVarDefinitionDirective(VarDefinitionDirective node) {
+ visitVarDefinition(node.def);
+ }
+}
+
+/// @include as a top-level with ruleset(s).
+class _IncludeReplacer extends Visitor {
+ final TreeNode _include;
+ final List<TreeNode> _newDeclarations;
+
+ /// Look for the [ruleSet] inside of a @media directive; if found then replace
+ /// with the [newRules].
+ static void replace(
+ StyleSheet ss, TreeNode include, List<TreeNode> newDeclarations) {
+ var visitor = _IncludeReplacer(include, newDeclarations);
+ visitor.visitStyleSheet(ss);
+ }
+
+ _IncludeReplacer(this._include, this._newDeclarations);
+
+ @override
+ void visitDeclarationGroup(DeclarationGroup node) {
+ var index = _findInclude(node.declarations, _include);
+ if (index != -1) {
+ node.declarations.insertAll(index + 1, _newDeclarations);
+ // Change @include to NoOp so it's processed only once.
+ node.declarations.replaceRange(index, index + 1, [NoOp()]);
+ }
+ super.visitDeclarationGroup(node);
+ }
+}
+
+/// Remove all @mixin and @include and any NoOp used as placeholder for
+/// @include.
+class MixinsAndIncludes extends Visitor {
+ static void remove(StyleSheet styleSheet) {
+ MixinsAndIncludes().visitStyleSheet(styleSheet);
+ }
+
+ bool _nodesToRemove(Object node) =>
+ node is IncludeDirective || node is MixinDefinition || node is NoOp;
+
+ @override
+ void visitStyleSheet(StyleSheet ss) {
+ var index = ss.topLevels.length;
+ while (--index >= 0) {
+ if (_nodesToRemove(ss.topLevels[index])) {
+ ss.topLevels.removeAt(index);
+ }
+ }
+ super.visitStyleSheet(ss);
+ }
+
+ @override
+ void visitDeclarationGroup(DeclarationGroup node) {
+ var index = node.declarations.length;
+ while (--index >= 0) {
+ if (_nodesToRemove(node.declarations[index])) {
+ node.declarations.removeAt(index);
+ }
+ }
+ super.visitDeclarationGroup(node);
+ }
+}
+
+/// Find all @extend to create inheritance.
+class AllExtends extends Visitor {
+ final inherits = <String, List<SelectorGroup>>{};
+
+ SelectorGroup? _currSelectorGroup;
+ int? _currDeclIndex;
+ final _extendsToRemove = <int>[];
+
+ @override
+ void visitRuleSet(RuleSet node) {
+ var oldSelectorGroup = _currSelectorGroup;
+ _currSelectorGroup = node.selectorGroup;
+
+ super.visitRuleSet(node);
+
+ _currSelectorGroup = oldSelectorGroup;
+ }
+
+ @override
+ void visitExtendDeclaration(ExtendDeclaration node) {
+ var inheritName = '';
+ for (var selector in node.selectors) {
+ inheritName += selector.toString();
+ }
+ if (inherits.containsKey(inheritName)) {
+ inherits[inheritName]!.add(_currSelectorGroup!);
+ } else {
+ inherits[inheritName] = [_currSelectorGroup!];
+ }
+
+ // Remove this @extend
+ _extendsToRemove.add(_currDeclIndex!);
+
+ super.visitExtendDeclaration(node);
+ }
+
+ @override
+ void visitDeclarationGroup(DeclarationGroup node) {
+ var oldDeclIndex = _currDeclIndex;
+
+ var decls = node.declarations;
+ for (_currDeclIndex = 0;
+ _currDeclIndex! < decls.length;
+ _currDeclIndex = _currDeclIndex! + 1) {
+ decls[_currDeclIndex!].visit(this);
+ }
+
+ if (_extendsToRemove.isNotEmpty) {
+ var removeTotal = _extendsToRemove.length - 1;
+ for (var index = removeTotal; index >= 0; index--) {
+ decls.removeAt(_extendsToRemove[index]);
+ }
+ _extendsToRemove.clear();
+ }
+
+ _currDeclIndex = oldDeclIndex;
+ }
+}
+
+// TODO(terry): Need to handle merging selector sequences
+// TODO(terry): Need to handle @extend-Only selectors.
+// TODO(terry): Need to handle !optional glag.
+/// Changes any selector that matches @extend.
+class InheritExtends extends Visitor {
+ final AllExtends _allExtends;
+
+ InheritExtends(Messages messages, this._allExtends);
+
+ @override
+ void visitSelectorGroup(SelectorGroup node) {
+ for (var selectorsIndex = 0;
+ selectorsIndex < node.selectors.length;
+ selectorsIndex++) {
+ var selectors = node.selectors[selectorsIndex];
+ var isLastNone = false;
+ var selectorName = '';
+ for (var index = 0;
+ index < selectors.simpleSelectorSequences.length;
+ index++) {
+ var simpleSeq = selectors.simpleSelectorSequences[index];
+ var namePart = simpleSeq.simpleSelector.toString();
+ selectorName = isLastNone ? (selectorName + namePart) : namePart;
+ var matches = _allExtends.inherits[selectorName];
+ if (matches != null) {
+ for (var match in matches) {
+ // Create a new group.
+ var newSelectors = selectors.clone();
+ var newSeq = match.selectors[0].clone();
+ if (isLastNone) {
+ // Add the inherited selector.
+ node.selectors.add(newSeq);
+ } else {
+ // Replace the selector sequence to the left of the pseudo class
+ // or pseudo element.
+
+ // Make new selector seq combinator the same as the original.
+ var orgCombinator =
+ newSelectors.simpleSelectorSequences[index].combinator;
+ newSeq.simpleSelectorSequences[0].combinator = orgCombinator;
+
+ newSelectors.simpleSelectorSequences.replaceRange(
+ index, index + 1, newSeq.simpleSelectorSequences);
+ node.selectors.add(newSelectors);
+ }
+ isLastNone = false;
+ }
+ } else {
+ isLastNone = simpleSeq.isCombinatorNone;
+ }
+ }
+ }
+ super.visitSelectorGroup(node);
+ }
+}
diff --git a/pkgs/csslib/lib/src/css_printer.dart b/pkgs/csslib/lib/src/css_printer.dart
new file mode 100644
index 0000000..463c855
--- /dev/null
+++ b/pkgs/csslib/lib/src/css_printer.dart
@@ -0,0 +1,737 @@
+// 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.
+
+part of '../visitor.dart';
+
+/// Visitor that produces a formatted string representation of the CSS tree.
+class CssPrinter extends Visitor {
+ StringBuffer _buff = StringBuffer();
+ bool _prettyPrint = true;
+ bool _isInKeyframes = false;
+ int _indent = 0;
+ bool _startOfLine = true;
+
+ /// Walk the [tree] Stylesheet. [pretty] if true emits line breaks, extra
+ /// spaces, friendly property values, etc., if false emits compacted output.
+ @override
+ void visitTree(StyleSheet tree, {bool pretty = false}) {
+ _prettyPrint = pretty;
+ _buff = StringBuffer();
+ _indent = 0;
+ _startOfLine = true;
+
+ visitStyleSheet(tree);
+ }
+
+ /// Appends [str] to the output buffer.
+ void emit(String str) {
+ if (_prettyPrint) {
+ if (_startOfLine) {
+ _startOfLine = false;
+ _buff.write(' ' * _indent);
+ }
+ _buff.write(str);
+ } else {
+ _buff.write(str);
+ }
+ }
+
+ void _emitLBrace() {
+ _indent += 2;
+ _buff.write('{');
+
+ if (_prettyPrint) {
+ _buff.writeln();
+ _startOfLine = true;
+ }
+ }
+
+ void _emitRBrace() {
+ _indent -= 2;
+
+ if (_prettyPrint) {
+ if (!_startOfLine) _buff.write('\n');
+ _buff.write('${' ' * _indent}}\n');
+ _startOfLine = true;
+ } else {
+ _buff.write('}');
+ }
+ }
+
+ void _emitSemicolon({bool forceLf = false}) {
+ if (_prettyPrint) {
+ _buff.write(';\n');
+ _startOfLine = true;
+ } else {
+ _buff.write(';');
+ if (forceLf) _buff.write('\n');
+ }
+ }
+
+ void _emitLf({bool force = false}) {
+ if (_prettyPrint) {
+ _buff.write('\n');
+ _startOfLine = true;
+ } else if (force) {
+ _buff.write('\n');
+ }
+ }
+
+ /// Returns the output buffer.
+ @override
+ String toString() => _buff.toString().trim();
+
+ String get _sp => _prettyPrint ? ' ' : '';
+
+ // TODO(terry): When adding obfuscation we'll need isOptimized (compact w/
+ // obfuscation) and have isTesting (compact no obfuscation) and
+ // isCompact would be !prettyPrint. We'll need another boolean
+ // flag for obfuscation.
+ bool get _isTesting => !_prettyPrint;
+
+ @override
+ void visitCalcTerm(CalcTerm node) {
+ emit('${node.text}(');
+ node.expr.visit(this);
+ emit(')');
+ }
+
+ @override
+ void visitCssComment(CssComment node) {
+ emit('/* ${node.comment} */');
+ }
+
+ @override
+ void visitCommentDefinition(CommentDefinition node) {
+ emit('<!-- ${node.comment} -->');
+ }
+
+ @override
+ void visitMediaExpression(MediaExpression node) {
+ emit(node.andOperator ? ' AND ' : ' ');
+ emit('(${node.mediaFeature}');
+ if (node.exprs.expressions.isNotEmpty) {
+ emit(':');
+ visitExpressions(node.exprs);
+ }
+ emit(')');
+ }
+
+ @override
+ void visitMediaQuery(MediaQuery node) {
+ var unary = node.hasUnary ? ' ${node.unary}' : '';
+ var mediaType = node.hasMediaType ? ' ${node.mediaType}' : '';
+ emit('$unary$mediaType');
+ for (var expression in node.expressions) {
+ visitMediaExpression(expression);
+ }
+ }
+
+ void emitMediaQueries(List<MediaQuery> queries) {
+ var queriesLen = queries.length;
+ for (var i = 0; i < queriesLen; i++) {
+ var query = queries[i];
+ if (i > 0) emit(',');
+ visitMediaQuery(query);
+ }
+ }
+
+ @override
+ void visitDocumentDirective(DocumentDirective node) {
+ emit('@-moz-document ');
+ node.functions.first.visit(this);
+ for (var function in node.functions.skip(1)) {
+ emit(',$_sp');
+ function.visit(this);
+ }
+ emit(_sp);
+ _emitLBrace();
+ for (var ruleSet in node.groupRuleBody) {
+ ruleSet.visit(this);
+ }
+ _emitRBrace();
+ }
+
+ @override
+ void visitSupportsDirective(SupportsDirective node) {
+ emit('@supports ');
+ node.condition!.visit(this);
+ emit(_sp);
+ _emitLBrace();
+ for (var rule in node.groupRuleBody) {
+ rule.visit(this);
+ }
+ _emitRBrace();
+ }
+
+ @override
+ void visitSupportsConditionInParens(SupportsConditionInParens node) {
+ emit('(');
+ node.condition!.visit(this);
+ emit(')');
+ }
+
+ @override
+ void visitSupportsNegation(SupportsNegation node) {
+ emit('not$_sp');
+ node.condition.visit(this);
+ }
+
+ @override
+ void visitSupportsConjunction(SupportsConjunction node) {
+ node.conditions.first.visit(this);
+ for (var condition in node.conditions.skip(1)) {
+ emit('${_sp}and$_sp');
+ condition.visit(this);
+ }
+ }
+
+ @override
+ void visitSupportsDisjunction(SupportsDisjunction node) {
+ node.conditions.first.visit(this);
+ for (var condition in node.conditions.skip(1)) {
+ emit('${_sp}or$_sp');
+ condition.visit(this);
+ }
+ }
+
+ @override
+ void visitViewportDirective(ViewportDirective node) {
+ emit('@${node.name}$_sp');
+ _emitLBrace();
+ node.declarations.visit(this);
+ _emitRBrace();
+ }
+
+ @override
+ void visitMediaDirective(MediaDirective node) {
+ emit('@media');
+ emitMediaQueries(node.mediaQueries.cast<MediaQuery>());
+ emit(_sp);
+ _emitLBrace();
+ for (var ruleset in node.rules) {
+ ruleset.visit(this);
+ }
+ _emitRBrace();
+ }
+
+ @override
+ void visitHostDirective(HostDirective node) {
+ emit('@host$_sp');
+ _emitLBrace();
+ for (var ruleset in node.rules) {
+ ruleset.visit(this);
+ }
+ _emitRBrace();
+ }
+
+ /// @page : pseudoPage {
+ /// decls
+ /// }
+ @override
+ void visitPageDirective(PageDirective node) {
+ emit('@page');
+ if (node.hasIdent || node.hasPseudoPage) {
+ if (node.hasIdent) emit(' ');
+ emit(node._ident!);
+ emit(node.hasPseudoPage ? ':${node._pseudoPage}' : '');
+ }
+
+ var declsMargin = node._declsMargin;
+ var declsMarginLength = declsMargin.length;
+ emit(_sp);
+ _emitLBrace();
+ for (var i = 0; i < declsMarginLength; i++) {
+ declsMargin[i].visit(this);
+ }
+ _emitRBrace();
+ }
+
+ /// @charset "charset encoding"
+ @override
+ void visitCharsetDirective(CharsetDirective node) {
+ emit('@charset "${node.charEncoding}"');
+ _emitSemicolon(forceLf: true);
+ }
+
+ @override
+ void visitImportDirective(ImportDirective node) {
+ bool isStartingQuote(String ch) => '\'"'.contains(ch[0]);
+
+ if (_isTesting) {
+ // Emit assuming url() was parsed; most suite tests use url function.
+ emit('@import url(${node.import})');
+ } else if (isStartingQuote(node.import)) {
+ emit('@import ${node.import}');
+ } else {
+ // url(...) isn't needed only a URI can follow an @import directive; emit
+ // url as a string.
+ emit('@import "${node.import}"');
+ }
+ emitMediaQueries(node.mediaQueries);
+ _emitSemicolon(forceLf: true);
+ }
+
+ @override
+ void visitKeyFrameDirective(KeyFrameDirective node) {
+ emit('${node.keyFrameName} ');
+ node.name!.visit(this);
+ emit(_sp);
+ _emitLBrace();
+ _isInKeyframes = true;
+ for (final block in node._blocks) {
+ block.visit(this);
+ }
+ _isInKeyframes = false;
+ _emitRBrace();
+ }
+
+ @override
+ void visitFontFaceDirective(FontFaceDirective node) {
+ emit('@font-face');
+ emit(_sp);
+ _emitLBrace();
+ node._declarations.visit(this);
+ _emitRBrace();
+ }
+
+ @override
+ void visitKeyFrameBlock(KeyFrameBlock node) {
+ node._blockSelectors.visit(this);
+ emit(_sp);
+ _emitLBrace();
+ node._declarations.visit(this);
+ _emitRBrace();
+ }
+
+ @override
+ void visitStyletDirective(StyletDirective node) {
+ emit('/* @stylet export as ${node.dartClassName} */');
+ _emitLf(force: true);
+ }
+
+ @override
+ void visitNamespaceDirective(NamespaceDirective node) {
+ bool isStartingQuote(String ch) => '\'"'.contains(ch);
+
+ if (isStartingQuote(node._uri!)) {
+ emit('@namespace ${node.prefix}"${node._uri}"');
+ } else {
+ if (_isTesting) {
+ // Emit exactly was we parsed.
+ emit('@namespace ${node.prefix}url(${node._uri})');
+ } else {
+ // url(...) isn't needed only a URI can follow a:
+ // @namespace prefix directive.
+ emit('@namespace ${node.prefix}${node._uri}');
+ }
+ }
+ _emitSemicolon(forceLf: true);
+ }
+
+ @override
+ void visitVarDefinitionDirective(VarDefinitionDirective node) {
+ visitVarDefinition(node.def);
+ _emitSemicolon();
+ }
+
+ @override
+ void visitMixinRulesetDirective(MixinRulesetDirective node) {
+ emit('@mixin ${node.name} ');
+ _emitLBrace();
+ for (var ruleset in node.rulesets) {
+ ruleset.visit(this);
+ }
+ _emitRBrace();
+ }
+
+ @override
+ void visitMixinDeclarationDirective(MixinDeclarationDirective node) {
+ emit('@mixin ${node.name}$_sp');
+ _emitLBrace();
+ visitDeclarationGroup(node.declarations);
+ _emitRBrace();
+ }
+
+ /// Added optional newLine for handling @include at top-level vs/ inside of
+ /// a declaration group.
+ @override
+ void visitIncludeDirective(IncludeDirective node, [bool topLevel = true]) {
+ if (topLevel) _emitLf();
+ emit('@include ${node.name}');
+ _emitSemicolon(forceLf: true);
+ }
+
+ @override
+ void visitContentDirective(ContentDirective node) {
+ // TODO(terry): TBD
+ }
+
+ @override
+ void visitRuleSet(RuleSet node) {
+ node.selectorGroup!.visit(this);
+ emit(_sp);
+ _emitLBrace();
+ node.declarationGroup.visit(this);
+ _emitRBrace();
+ }
+
+ @override
+ void visitDeclarationGroup(DeclarationGroup node) {
+ var declarations = node.declarations;
+ var declarationsLength = declarations.length;
+ for (var i = 0; i < declarationsLength; i++) {
+ declarations[i].visit(this);
+ // Don't emit the last semicolon in compact mode.
+ if (_prettyPrint || i < declarationsLength - 1) {
+ _emitSemicolon();
+ }
+ }
+ }
+
+ @override
+ void visitMarginGroup(MarginGroup node) {
+ var marginSymName =
+ TokenKind.idToValue(TokenKind.MARGIN_DIRECTIVES, node.margin_sym);
+
+ emit('@$marginSymName$_sp');
+ _emitLBrace();
+ visitDeclarationGroup(node);
+ _emitRBrace();
+ }
+
+ @override
+ void visitDeclaration(Declaration node) {
+ emit('${node.property}:$_sp');
+ node.expression!.visit(this);
+ if (node.important) {
+ emit('$_sp!important');
+ }
+ }
+
+ @override
+ void visitVarDefinition(VarDefinition node) {
+ emit('var-${node.definedName}: ');
+ node.expression!.visit(this);
+ }
+
+ @override
+ void visitIncludeMixinAtDeclaration(IncludeMixinAtDeclaration node) {
+ // Don't emit a new line we're inside of a declaration group.
+ visitIncludeDirective(node.include, false);
+ }
+
+ @override
+ void visitExtendDeclaration(ExtendDeclaration node) {
+ emit('@extend ');
+ for (var selector in node.selectors) {
+ selector.visit(this);
+ }
+ }
+
+ @override
+ void visitSelectorGroup(SelectorGroup node) {
+ var selectors = node.selectors;
+ var selectorsLength = selectors.length;
+ for (var i = 0; i < selectorsLength; i++) {
+ if (i > 0) emit(',$_sp');
+ selectors[i].visit(this);
+ }
+ }
+
+ @override
+ void visitSimpleSelectorSequence(SimpleSelectorSequence node) {
+ emit(node._combinatorToString);
+ node.simpleSelector.visit(this);
+ }
+
+ @override
+ void visitSimpleSelector(SimpleSelector node) {
+ emit(node.name);
+ }
+
+ @override
+ void visitNamespaceSelector(NamespaceSelector node) {
+ emit(node.toString());
+ }
+
+ @override
+ void visitElementSelector(ElementSelector node) {
+ emit(node.toString());
+ }
+
+ @override
+ void visitAttributeSelector(AttributeSelector node) {
+ emit(node.toString());
+ }
+
+ @override
+ void visitIdSelector(IdSelector node) {
+ emit(node.toString());
+ }
+
+ @override
+ void visitClassSelector(ClassSelector node) {
+ emit(node.toString());
+ }
+
+ @override
+ void visitPseudoClassSelector(PseudoClassSelector node) {
+ emit(node.toString());
+ }
+
+ @override
+ void visitPseudoElementSelector(PseudoElementSelector node) {
+ emit(node.toString());
+ }
+
+ @override
+ void visitPseudoClassFunctionSelector(PseudoClassFunctionSelector node) {
+ emit(':${node.name}(');
+ node.argument.visit(this);
+ emit(')');
+ }
+
+ @override
+ void visitPseudoElementFunctionSelector(PseudoElementFunctionSelector node) {
+ emit('::${node.name}(');
+ node.expression.visit(this);
+ emit(')');
+ }
+
+ @override
+ void visitNegationSelector(NegationSelector node) {
+ emit(':not(');
+ node.negationArg!.visit(this);
+ emit(')');
+ }
+
+ @override
+ void visitSelectorExpression(SelectorExpression node) {
+ var expressions = node.expressions;
+ var expressionsLength = expressions.length;
+ for (var i = 0; i < expressionsLength; i++) {
+ // Add space seperator between terms without an operator.
+ var expression = expressions[i];
+ expression.visit(this);
+ }
+ }
+
+ @override
+ void visitUnicodeRangeTerm(UnicodeRangeTerm node) {
+ if (node.hasSecond) {
+ emit('U+${node.first}-${node.second}');
+ } else {
+ emit('U+${node.first}');
+ }
+ }
+
+ @override
+ void visitLiteralTerm(LiteralTerm node) {
+ emit(node.text);
+ }
+
+ @override
+ void visitHexColorTerm(HexColorTerm node) {
+ String? mappedName;
+ if (_isTesting && (node.value is! BAD_HEX_VALUE)) {
+ mappedName = TokenKind.hexToColorName(node.value);
+ }
+ mappedName ??= '#${node.text}';
+
+ emit(mappedName);
+ }
+
+ @override
+ void visitNumberTerm(NumberTerm node) {
+ visitLiteralTerm(node);
+ }
+
+ @override
+ void visitUnitTerm(UnitTerm node) {
+ emit(node.toString());
+ }
+
+ @override
+ void visitLengthTerm(LengthTerm node) {
+ emit(node.toString());
+ }
+
+ @override
+ void visitPercentageTerm(PercentageTerm node) {
+ emit('${node.text}%');
+ }
+
+ @override
+ void visitEmTerm(EmTerm node) {
+ emit('${node.text}em');
+ }
+
+ @override
+ void visitExTerm(ExTerm node) {
+ emit('${node.text}ex');
+ }
+
+ @override
+ void visitAngleTerm(AngleTerm node) {
+ emit(node.toString());
+ }
+
+ @override
+ void visitTimeTerm(TimeTerm node) {
+ emit(node.toString());
+ }
+
+ @override
+ void visitFreqTerm(FreqTerm node) {
+ emit(node.toString());
+ }
+
+ @override
+ void visitFractionTerm(FractionTerm node) {
+ emit('${node.text}fr');
+ }
+
+ @override
+ void visitUriTerm(UriTerm node) {
+ emit('url("${node.text}")');
+ }
+
+ @override
+ void visitResolutionTerm(ResolutionTerm node) {
+ emit(node.toString());
+ }
+
+ @override
+ void visitViewportTerm(ViewportTerm node) {
+ emit(node.toString());
+ }
+
+ @override
+ void visitLineHeightTerm(LineHeightTerm node) {
+ emit(node.toString());
+ }
+
+ @override
+ void visitFunctionTerm(FunctionTerm node) {
+ // TODO(terry): Optimize rgb to a hexcolor.
+ emit('${node.text}(');
+ node._params.visit(this);
+ emit(')');
+ }
+
+ @override
+ void visitGroupTerm(GroupTerm node) {
+ emit('(');
+ var terms = node._terms;
+ var termsLength = terms.length;
+ for (var i = 0; i < termsLength; i++) {
+ if (i > 0) emit(_sp);
+ terms[i].visit(this);
+ }
+ emit(')');
+ }
+
+ @override
+ void visitItemTerm(ItemTerm node) {
+ emit('[${node.text}]');
+ }
+
+ @override
+ void visitIE8Term(IE8Term node) {
+ visitLiteralTerm(node);
+ }
+
+ @override
+ void visitOperatorSlash(OperatorSlash node) {
+ emit('/');
+ }
+
+ @override
+ void visitOperatorComma(OperatorComma node) {
+ emit(',');
+ }
+
+ @override
+ void visitOperatorPlus(OperatorPlus node) {
+ emit('+');
+ }
+
+ @override
+ void visitOperatorMinus(OperatorMinus node) {
+ emit('-');
+ }
+
+ @override
+ void visitVarUsage(VarUsage node) {
+ emit('var(${node.name}');
+ if (node.defaultValues.isNotEmpty) {
+ emit(',');
+ for (var defaultValue in node.defaultValues) {
+ emit(' ');
+ defaultValue.visit(this);
+ }
+ }
+ emit(')');
+ }
+
+ @override
+ void visitExpressions(Expressions node) {
+ var expressions = node.expressions;
+ var expressionsLength = expressions.length;
+
+ for (var i = 0; i < expressionsLength; i++) {
+ // Add space seperator between terms without an operator.
+ // TODO(terry): Should have a BinaryExpression to solve this problem.
+ var expression = expressions[i];
+ if (i > 0 &&
+ !(expression is OperatorComma || expression is OperatorSlash)) {
+ // If the previous expression is an operator, use `_sp` so the space is
+ // collapsed when emitted in compact mode. If the previous expression
+ // isn't an operator, the space is significant to delimit the two
+ // expressions and can't be collapsed.
+ var previous = expressions[i - 1];
+ if (previous is OperatorComma || previous is OperatorSlash) {
+ emit(_sp);
+ } else if (previous is PercentageTerm &&
+ expression is PercentageTerm &&
+ _isInKeyframes) {
+ emit(',');
+ emit(_sp);
+ } else {
+ emit(' ');
+ }
+ }
+ expression.visit(this);
+ }
+ }
+
+ @override
+ void visitBinaryExpression(BinaryExpression node) {
+ // TODO(terry): TBD
+ throw UnimplementedError('visitBinaryExpression');
+ }
+
+ @override
+ void visitUnaryExpression(UnaryExpression node) {
+ // TODO(terry): TBD
+ throw UnimplementedError('visitUnaryExpression');
+ }
+
+ @override
+ void visitIdentifier(Identifier node) {
+ emit(node.name);
+ }
+
+ @override
+ void visitWildcard(Wildcard node) {
+ emit('*');
+ }
+
+ @override
+ void visitDartStyleExpression(DartStyleExpression node) {
+ // TODO(terry): TBD
+ throw UnimplementedError('visitDartStyleExpression');
+ }
+}
diff --git a/pkgs/csslib/lib/src/messages.dart b/pkgs/csslib/lib/src/messages.dart
new file mode 100644
index 0000000..fb1516c
--- /dev/null
+++ b/pkgs/csslib/lib/src/messages.dart
@@ -0,0 +1,131 @@
+// 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 'package:source_span/source_span.dart';
+
+import 'preprocessor_options.dart';
+
+enum MessageLevel { info, warning, severe }
+
+// TODO(#159): Remove the global messages, use some object that tracks
+// compilation state.
+
+/// The global [Messages] for tracking info/warnings/messages.
+late Messages messages;
+
+// Color constants used for generating messages.
+const _greenColor = '\u001b[32m';
+const _redColor = '\u001b[31m';
+const _magentaColor = '\u001b[35m';
+const _noColor = '\u001b[0m';
+
+/// Map between error levels and their display color.
+const Map<MessageLevel, String> _errorColors = {
+ MessageLevel.severe: _redColor,
+ MessageLevel.warning: _magentaColor,
+ MessageLevel.info: _greenColor,
+};
+
+/// Map between error levels and their friendly name.
+const Map<MessageLevel, String> _errorLabel = {
+ MessageLevel.severe: 'error',
+ MessageLevel.warning: 'warning',
+ MessageLevel.info: 'info',
+};
+
+/// A single message from the compiler.
+class Message {
+ final MessageLevel level;
+ final String message;
+ final SourceSpan? span;
+ final bool useColors;
+
+ Message(this.level, this.message, {this.span, this.useColors = false});
+
+ String get describe {
+ var span = this.span;
+ if (span == null) {
+ return message;
+ }
+
+ var start = span.start;
+ return '${start.line + 1}:${start.column + 1}:$message';
+ }
+
+ @override
+ String toString() {
+ var output = StringBuffer();
+ var colors = useColors && _errorColors.containsKey(level);
+ var levelColor = colors ? _errorColors[level] : null;
+ if (colors) output.write(levelColor);
+ output
+ ..write(_errorLabel[level])
+ ..write(' ');
+ if (colors) output.write(_noColor);
+
+ if (span == null) {
+ output.write(message);
+ } else {
+ output.write('on ');
+ output.write(span!.message(message, color: levelColor));
+ }
+
+ return output.toString();
+ }
+}
+
+/// This class tracks and prints information, warnings, and errors emitted by
+/// the compiler.
+class Messages {
+ /// Called on every error. Set to blank function to suppress printing.
+ final void Function(Message obj) printHandler;
+
+ final PreprocessorOptions options;
+
+ final List<Message> messages = <Message>[];
+
+ Messages({PreprocessorOptions? options, this.printHandler = print})
+ : options = options ?? const PreprocessorOptions();
+
+ /// Report a compile-time CSS error.
+ void error(String message, SourceSpan? span) {
+ var msg = Message(MessageLevel.severe, message,
+ span: span, useColors: options.useColors);
+
+ messages.add(msg);
+
+ printHandler(msg);
+ }
+
+ /// Report a compile-time CSS warning.
+ void warning(String message, SourceSpan? span) {
+ if (options.warningsAsErrors) {
+ error(message, span);
+ } else {
+ var msg = Message(MessageLevel.warning, message,
+ span: span, useColors: options.useColors);
+
+ messages.add(msg);
+ }
+ }
+
+ /// Report and informational message about what the compiler is doing.
+ void info(String message, SourceSpan span) {
+ var msg = Message(MessageLevel.info, message,
+ span: span, useColors: options.useColors);
+
+ messages.add(msg);
+
+ if (options.verbose) printHandler(msg);
+ }
+
+ /// Merge [newMessages] to this message list.
+ void mergeMessages(Messages newMessages) {
+ messages.addAll(newMessages.messages);
+ newMessages.messages
+ .where((message) =>
+ message.level == MessageLevel.severe || options.verbose)
+ .forEach(printHandler);
+ }
+}
diff --git a/pkgs/csslib/lib/src/polyfill.dart b/pkgs/csslib/lib/src/polyfill.dart
new file mode 100644
index 0000000..b65ec61
--- /dev/null
+++ b/pkgs/csslib/lib/src/polyfill.dart
@@ -0,0 +1,258 @@
+// 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.
+
+part of '../parser.dart';
+
+/// CSS polyfill emits CSS to be understood by older parsers that which do not
+/// understand (var, calc, etc.).
+class PolyFill {
+ final Messages _messages;
+ Map<String, VarDefinition> _allVarDefinitions = <String, VarDefinition>{};
+
+ Set<StyleSheet> allStyleSheets = <StyleSheet>{};
+
+ /// [_pseudoElements] list of known pseudo attributes found in HTML, any
+ /// CSS pseudo-elements 'name::custom-element' is mapped to the manged name
+ /// associated with the pseudo-element key.
+ PolyFill(this._messages);
+
+ /// Run the analyzer on every file that is a style sheet or any component that
+ /// has a style tag.
+ void process(StyleSheet styleSheet, {List<StyleSheet>? includes}) {
+ if (includes != null) {
+ processVarDefinitions(includes);
+ }
+ processVars(styleSheet);
+
+ // Remove all var definitions for this style sheet.
+ _RemoveVarDefinitions().visitTree(styleSheet);
+ }
+
+ /// Process all includes looking for var definitions.
+ void processVarDefinitions(List<StyleSheet> includes) {
+ for (var include in includes) {
+ _allVarDefinitions = (_VarDefinitionsIncludes(_allVarDefinitions)
+ ..visitTree(include))
+ .varDefs;
+ }
+ }
+
+ void processVars(StyleSheet styleSheet) {
+ // Build list of all var definitions.
+ var mainStyleSheetVarDefs = (_VarDefAndUsage(_messages, _allVarDefinitions)
+ ..visitTree(styleSheet))
+ .varDefs;
+
+ // Resolve all definitions to a non-VarUsage (terminal expression).
+ mainStyleSheetVarDefs.forEach((key, value) {
+ for (var _ in (value.expression as Expressions).expressions) {
+ mainStyleSheetVarDefs[key] =
+ _findTerminalVarDefinition(_allVarDefinitions, value);
+ }
+ });
+ }
+}
+
+/// Build list of all var definitions in all includes.
+class _VarDefinitionsIncludes extends Visitor {
+ final Map<String, VarDefinition> varDefs;
+
+ _VarDefinitionsIncludes(this.varDefs);
+
+ @override
+ void visitTree(StyleSheet tree) {
+ visitStyleSheet(tree);
+ }
+
+ @override
+ void visitVarDefinition(VarDefinition node) {
+ // Replace with latest variable definition.
+ varDefs[node.definedName] = node;
+ super.visitVarDefinition(node);
+ }
+
+ @override
+ void visitVarDefinitionDirective(VarDefinitionDirective node) {
+ visitVarDefinition(node.def);
+ }
+}
+
+/// Find var- definitions in a style sheet.
+/// [found] list of known definitions.
+class _VarDefAndUsage extends Visitor {
+ final Messages _messages;
+ final Map<String, VarDefinition> _knownVarDefs;
+ final varDefs = <String, VarDefinition>{};
+
+ VarDefinition? currVarDefinition;
+ List<Expression>? currentExpressions;
+
+ _VarDefAndUsage(this._messages, this._knownVarDefs);
+
+ @override
+ void visitTree(StyleSheet tree) {
+ visitStyleSheet(tree);
+ }
+
+ @override
+ void visitVarDefinition(VarDefinition node) {
+ // Replace with latest variable definition.
+ currVarDefinition = node;
+
+ _knownVarDefs[node.definedName] = node;
+ varDefs[node.definedName] = node;
+
+ super.visitVarDefinition(node);
+
+ currVarDefinition = null;
+ }
+
+ @override
+ void visitVarDefinitionDirective(VarDefinitionDirective node) {
+ visitVarDefinition(node.def);
+ }
+
+ @override
+ void visitExpressions(Expressions node) {
+ currentExpressions = node.expressions;
+ super.visitExpressions(node);
+ currentExpressions = null;
+ }
+
+ @override
+ void visitVarUsage(VarUsage node) {
+ if (currVarDefinition != null && currVarDefinition!.badUsage) return;
+
+ // Don't process other var() inside of a varUsage. That implies that the
+ // default is a var() too. Also, don't process any var() inside of a
+ // varDefinition (they're just place holders until we've resolved all real
+ // usages.
+ var expressions = currentExpressions;
+ var index = expressions!.indexOf(node);
+ assert(index >= 0);
+ var def = _knownVarDefs[node.name];
+ if (def != null) {
+ if (def.badUsage) {
+ // Remove any expressions pointing to a bad var definition.
+ expressions.removeAt(index);
+ return;
+ }
+ _resolveVarUsage(currentExpressions!, index,
+ _findTerminalVarDefinition(_knownVarDefs, def));
+ } else if (node.defaultValues.any((e) => e is VarUsage)) {
+ // Don't have a VarDefinition need to use default values resolve all
+ // default values.
+ var terminalDefaults = <Expression>[];
+ for (var defaultValue in node.defaultValues) {
+ terminalDefaults.addAll(resolveUsageTerminal(defaultValue as VarUsage));
+ }
+ expressions.replaceRange(index, index + 1, terminalDefaults);
+ } else if (node.defaultValues.isNotEmpty) {
+ // No VarDefinition but default value is a terminal expression; use it.
+ expressions.replaceRange(index, index + 1, node.defaultValues);
+ } else {
+ if (currVarDefinition != null) {
+ currVarDefinition!.badUsage = true;
+ var mainStyleSheetDef = varDefs[node.name];
+ if (mainStyleSheetDef != null) {
+ varDefs.remove(currVarDefinition!.property);
+ }
+ }
+ // Remove var usage that points at an undefined definition.
+ expressions.removeAt(index);
+ _messages.warning('Variable is not defined.', node.span);
+ }
+
+ var oldExpressions = currentExpressions;
+ currentExpressions = node.defaultValues;
+ super.visitVarUsage(node);
+ currentExpressions = oldExpressions;
+ }
+
+ List<Expression> resolveUsageTerminal(VarUsage usage) {
+ var result = <Expression>[];
+
+ var varDef = _knownVarDefs[usage.name];
+ List<Expression> expressions;
+ if (varDef == null) {
+ // VarDefinition not found try the defaultValues.
+ expressions = usage.defaultValues;
+ } else {
+ // Use the VarDefinition found.
+ expressions = (varDef.expression as Expressions).expressions;
+ }
+
+ for (var expr in expressions) {
+ if (expr is VarUsage) {
+ // Get terminal value.
+ result.addAll(resolveUsageTerminal(expr));
+ }
+ }
+
+ // We're at a terminal just return the VarDefinition expression.
+ if (result.isEmpty && varDef != null) {
+ result = (varDef.expression as Expressions).expressions;
+ }
+
+ return result;
+ }
+
+ void _resolveVarUsage(
+ List<Expression> expressions, int index, VarDefinition def) {
+ var defExpressions = (def.expression as Expressions).expressions;
+ expressions.replaceRange(index, index + 1, defExpressions);
+ }
+}
+
+/// Remove all var definitions.
+class _RemoveVarDefinitions extends Visitor {
+ @override
+ void visitTree(StyleSheet tree) {
+ visitStyleSheet(tree);
+ }
+
+ @override
+ void visitStyleSheet(StyleSheet ss) {
+ ss.topLevels.removeWhere((e) => e is VarDefinitionDirective);
+ super.visitStyleSheet(ss);
+ }
+
+ @override
+ void visitDeclarationGroup(DeclarationGroup node) {
+ node.declarations.removeWhere((e) => e is VarDefinition);
+ super.visitDeclarationGroup(node);
+ }
+}
+
+/// Find terminal definition (non VarUsage implies real CSS value).
+VarDefinition _findTerminalVarDefinition(
+ Map<String, VarDefinition> varDefs, VarDefinition varDef) {
+ var expressions = varDef.expression as Expressions;
+ for (var expr in expressions.expressions) {
+ if (expr is VarUsage) {
+ var usageName = expr.name;
+ var foundDef = varDefs[usageName];
+
+ // If foundDef is unknown check if defaultValues; if it exist then resolve
+ // to terminal value.
+ if (foundDef == null) {
+ // We're either a VarUsage or terminal definition if in varDefs;
+ // either way replace VarUsage with it's default value because the
+ // VarDefinition isn't found.
+ var defaultValues = expr.defaultValues;
+ var replaceExprs = expressions.expressions;
+ assert(replaceExprs.length == 1);
+ replaceExprs.replaceRange(0, 1, defaultValues);
+ return varDef;
+ }
+ return _findTerminalVarDefinition(varDefs, foundDef);
+ } else {
+ // Return real CSS property.
+ return varDef;
+ }
+ }
+
+ // Didn't point to a var definition that existed.
+ return varDef;
+}
diff --git a/pkgs/csslib/lib/src/preprocessor_options.dart b/pkgs/csslib/lib/src/preprocessor_options.dart
new file mode 100644
index 0000000..3879ead
--- /dev/null
+++ b/pkgs/csslib/lib/src/preprocessor_options.dart
@@ -0,0 +1,50 @@
+// 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.
+
+class PreprocessorOptions {
+ /// Generate polyfill code (e.g., var, etc.)
+ final bool polyfill;
+
+ /// Report warnings as errors.
+ final bool warningsAsErrors;
+
+ /// Throw an exception on warnings (not used by command line tool).
+ final bool throwOnWarnings;
+
+ /// Throw an exception on errors (not used by command line tool).
+ final bool throwOnErrors;
+
+ /// True to show informational messages. The `--verbose` flag.
+ final bool verbose;
+
+ /// True to show warning messages for bad CSS. The '--checked' flag.
+ final bool checked;
+
+ // TODO(terry): Add mixin support and nested rules.
+ /// Subset of Less commands enabled; disable with '--no-less'.
+ /// Less syntax supported:
+ /// - @name at root level statically defines variables resolved at compilation
+ /// time. Essentially a directive e.g., @var-name.
+ final bool lessSupport;
+
+ /// Whether to use colors to print messages on the terminal.
+ final bool useColors;
+
+ /// File to process by the compiler.
+ final String? inputFile;
+
+ // TODO: Should less support really default to being enabled?
+
+ const PreprocessorOptions({
+ this.verbose = false,
+ this.checked = false,
+ this.lessSupport = true,
+ this.warningsAsErrors = false,
+ this.throwOnErrors = false,
+ this.throwOnWarnings = false,
+ this.useColors = true,
+ this.polyfill = false,
+ this.inputFile,
+ });
+}
diff --git a/pkgs/csslib/lib/src/property.dart b/pkgs/csslib/lib/src/property.dart
new file mode 100644
index 0000000..ec6b854
--- /dev/null
+++ b/pkgs/csslib/lib/src/property.dart
@@ -0,0 +1,1183 @@
+// 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.
+
+/// Representations of CSS styles.
+
+part of '../parser.dart';
+
+// TODO(terry): Prune down this file we do need some of the code in this file
+// for darker, lighter, how to represent a Font, etc but alot of
+// the complexity can be removed.
+// See https://github.com/dart-lang/csslib/issues/7
+
+/// Base for all style properties (e.g., Color, Font, Border, Margin, etc.)
+abstract class _StyleProperty {
+ /// Returns the expression part of a CSS declaration. Declaration is:
+ ///
+ /// property:expression;
+ ///
+ /// E.g., if property is color then expression could be rgba(255,255,0) the
+ /// CSS declaration would be 'color:rgba(255,255,0);'.
+ ///
+ /// then _cssExpression would return 'rgba(255,255,0)'. See
+ /// <http://www.w3.org/TR/CSS21/grammar.html>
+ String? get cssExpression;
+}
+
+/// Base interface for Color, HSL and RGB.
+abstract class ColorBase {
+ /// Canonical form for color #rrggbb with alpha blending (0.0 == full
+ /// transparency and 1.0 == fully opaque). If _argb length is 6 it's an
+ /// rrggbb otherwise it's aarrggbb.
+ String toHexArgbString();
+
+ /// Return argb as a value (int).
+ int get argbValue;
+}
+
+/// General purpose Color class. Represent a color as an ARGB value that can be
+/// converted to and from num, hex string, hsl, hsla, rgb, rgba and SVG pre-
+/// defined color constant.
+class Color implements _StyleProperty, ColorBase {
+ // If _argb length is 6 it's an rrggbb otherwise it's aarrggbb.
+ final String _argb;
+
+ // TODO(terry): Look at reducing Rgba and Hsla classes as factories for
+ // converting from Color to an Rgba or Hsla for reading only.
+ // Usefulness of creating an Rgba or Hsla is limited.
+
+ /// Create a color with an integer representing the rgb value of red, green,
+ /// and blue. The value 0xffffff is the color white #ffffff (CSS style).
+ /// The [rgb] value of 0xffd700 would map to #ffd700 or the constant
+ /// Color.gold, where ff is red intensity, d7 is green intensity, and 00 is
+ /// blue intensity.
+ Color(int rgb, [num? alpha]) : _argb = Color._rgbToArgbString(rgb, alpha);
+
+ /// RGB takes three values. The [red], [green], and [blue] parameters are
+ /// the intensity of those components where '0' is the least and '256' is the
+ /// greatest.
+ ///
+ /// If [alpha] is provided, it is the level of translucency which ranges from
+ /// '0' (completely transparent) to '1.0' (completely opaque). It will
+ /// internally be mapped to an int between '0' and '255' like the other color
+ /// components.
+ Color.createRgba(int red, int green, int blue, [num? alpha])
+ : _argb = Color.convertToHexString(
+ Color._clamp(red, 0, 255),
+ Color._clamp(green, 0, 255),
+ Color._clamp(blue, 0, 255),
+ alpha != null ? Color._clamp(alpha, 0, 1) : alpha);
+
+ /// Creates a new color from a CSS color string. For more information, see
+ /// <https://developer.mozilla.org/en/CSS/color>.
+ Color.css(String color) : _argb = Color._convertCssToArgb(color)!;
+
+ // TODO(jmesserly): I found the use of percents a bit suprising.
+ /// HSL takes three values. The [hueDegree] degree on the color wheel; '0' is
+ /// the least and '100' is the greatest. The value '0' or '360' is red, '120'
+ /// is green, '240' is blue. Numbers in between reflect different shades.
+ /// The [saturationPercent] percentage; where'0' is the least and '100' is the
+ /// greatest (100 represents full color). The [lightnessPercent] percentage;
+ /// where'0' is the least and '100' is the greatest. The value 0 is dark or
+ /// black, 100 is light or white and 50 is a medium lightness.
+ ///
+ /// If [alpha] is provided, it is the level of translucency which ranges from
+ /// '0' (completely transparent foreground) to '1.0' (completely opaque
+ /// foreground).
+ Color.createHsla(num hueDegree, num saturationPercent, num lightnessPercent,
+ [num? alpha])
+ : _argb = Hsla(
+ Color._clamp(hueDegree, 0, 360) / 360,
+ Color._clamp(saturationPercent, 0, 100) / 100,
+ Color._clamp(lightnessPercent, 0, 100) / 100,
+ alpha != null ? Color._clamp(alpha, 0, 1) : alpha)
+ .toHexArgbString();
+
+ /// The hslaRaw takes three values. The [hue] degree on the color wheel; '0'
+ /// is the least and '1' is the greatest. The value '0' or '1' is red, the
+ /// ratio of 120/360 is green, and the ratio of 240/360 is blue. Numbers in
+ /// between reflect different shades. The [saturation] is a percentage; '0'
+ /// is the least and '1' is the greatest. The value of '1' is equivalent to
+ /// 100% (full colour). The [lightness] is a percentage; '0' is the least and
+ /// '1' is the greatest. The value of '0' is dark (black), the value of '1'
+ /// is light (white), and the value of '.50' is a medium lightness.
+ ///
+ /// The fourth optional parameter is:
+ /// [alpha] level of translucency range of values is 0..1, zero is a
+ /// completely transparent foreground and 1 is a completely
+ /// opaque foreground.
+ Color.hslaRaw(num hue, num saturation, num lightness, [num? alpha])
+ : _argb = Hsla(
+ Color._clamp(hue, 0, 1),
+ Color._clamp(saturation, 0, 1),
+ Color._clamp(lightness, 0, 1),
+ alpha != null ? Color._clamp(alpha, 0, 1) : alpha)
+ .toHexArgbString();
+
+ /// Generate a real constant for pre-defined colors (no leading #).
+ const Color.hex(this._argb);
+
+ // TODO(jmesserly): this is needed by the example so leave it exposed for now.
+ @override
+ String toString() => cssExpression;
+
+ // TODO(terry): Regardless of how color is set (rgb, num, css or hsl) we'll
+ // always return a rgb or rgba loses fidelity when debugging in
+ // CSS if user uses hsl and would like to edit as hsl, etc. If
+ // this is an issue we should keep the original value and not re-
+ // create the CSS from the normalized value.
+ @override
+ String get cssExpression {
+ if (_argb.length == 6) {
+ return '#$_argb'; // RGB only, no alpha blending.
+ } else {
+ num alpha = Color.hexToInt(_argb.substring(0, 2));
+ var a = (alpha / 255).toStringAsPrecision(2);
+ var r = Color.hexToInt(_argb.substring(2, 4));
+ var g = Color.hexToInt(_argb.substring(4, 6));
+ var b = Color.hexToInt(_argb.substring(6, 8));
+ return 'rgba($r,$g,$b,$a)';
+ }
+ }
+
+ Rgba get rgba {
+ var nextIndex = 0;
+ num? a;
+ if (_argb.length == 8) {
+ // Get alpha blending value 0..255
+ var alpha = Color.hexToInt(_argb.substring(nextIndex, nextIndex + 2));
+ // Convert to value from 0..1
+ a = double.parse((alpha / 255).toStringAsPrecision(2));
+ nextIndex += 2;
+ }
+ var r = Color.hexToInt(_argb.substring(nextIndex, nextIndex + 2));
+ nextIndex += 2;
+ var g = Color.hexToInt(_argb.substring(nextIndex, nextIndex + 2));
+ nextIndex += 2;
+ var b = Color.hexToInt(_argb.substring(nextIndex, nextIndex + 2));
+ return Rgba(r, g, b, a);
+ }
+
+ Hsla get hsla => Hsla.fromRgba(rgba);
+
+ @override
+ int get argbValue => Color.hexToInt(_argb);
+
+ @override
+ bool operator ==(Object other) => Color.equal(this, other);
+
+ @override
+ String toHexArgbString() => _argb;
+
+ Color darker(num amount) {
+ var newRgba = Color._createNewTintShadeFromRgba(rgba, -amount);
+ return Color.hex(newRgba.toHexArgbString());
+ }
+
+ Color lighter(num amount) {
+ var newRgba = Color._createNewTintShadeFromRgba(rgba, amount);
+ return Color.hex(newRgba.toHexArgbString());
+ }
+
+ static bool equal(ColorBase curr, Object other) {
+ if (other is Color) {
+ var o = other;
+ return o.toHexArgbString() == curr.toHexArgbString();
+ } else if (other is Rgba) {
+ var rgb = other;
+ return rgb.toHexArgbString() == curr.toHexArgbString();
+ } else if (other is Hsla) {
+ var hsla = other;
+ return hsla.toHexArgbString() == curr.toHexArgbString();
+ } else {
+ return false;
+ }
+ }
+
+ @override
+ int get hashCode => _argb.hashCode;
+
+ // Conversion routines:
+
+ static String _rgbToArgbString(int rgba, num? alpha) {
+ num? a;
+ // If alpha is defined then adjust from 0..1 to 0..255 value, if not set
+ // then a is left as undefined and passed to convertToHexString.
+ if (alpha != null) {
+ a = (Color._clamp(alpha, 0, 1) * 255).round();
+ }
+
+ var r = (rgba & 0xff0000) >> 0x10;
+ var g = (rgba & 0xff00) >> 8;
+ var b = rgba & 0xff;
+
+ return Color.convertToHexString(r, g, b, a);
+ }
+
+ static const int _rgbCss = 1;
+ static const int _rgbaCss = 2;
+ static const int _hslCss = 3;
+ static const int _hslaCss = 4;
+
+ /// Parse CSS expressions of the from #rgb, rgb(r,g,b), rgba(r,g,b,a),
+ /// hsl(h,s,l), hsla(h,s,l,a) and SVG colors (e.g., darkSlateblue, etc.) and
+ /// convert to argb.
+ static String? _convertCssToArgb(String value) {
+ // TODO(terry): Better parser/regex for converting CSS properties.
+ var color = value.trim().replaceAll('\\s', '');
+ if (color[0] == '#') {
+ var v = color.substring(1);
+ Color.hexToInt(v); // Valid hexadecimal, throws if not.
+ return v;
+ } else if (color.isNotEmpty && color[color.length - 1] == ')') {
+ int type;
+ if (color.indexOf('rgb(') == 0 || color.indexOf('RGB(') == 0) {
+ color = color.substring(4);
+ type = _rgbCss;
+ } else if (color.indexOf('rgba(') == 0 || color.indexOf('RGBA(') == 0) {
+ type = _rgbaCss;
+ color = color.substring(5);
+ } else if (color.indexOf('hsl(') == 0 || color.indexOf('HSL(') == 0) {
+ type = _hslCss;
+ color = color.substring(4);
+ } else if (color.indexOf('hsla(') == 0 || color.indexOf('HSLA(') == 0) {
+ type = _hslaCss;
+ color = color.substring(5);
+ } else {
+ throw UnsupportedError('CSS property not implemented');
+ }
+
+ color = color.substring(0, color.length - 1); // Strip close paren.
+
+ var args = <num>[];
+ var params = color.split(',');
+ for (var param in params) {
+ args.add(double.parse(param));
+ }
+ switch (type) {
+ case _rgbCss:
+ return Color.convertToHexString(
+ args[0].toInt(), args[1].toInt(), args[2].toInt());
+ case _rgbaCss:
+ return Color.convertToHexString(
+ args[0].toInt(), args[1].toInt(), args[2].toInt(), args[3]);
+ case _hslCss:
+ return Hsla(args[0], args[1], args[2]).toHexArgbString();
+ case _hslaCss:
+ return Hsla(args[0], args[1], args[2], args[3]).toHexArgbString();
+ default:
+ // Type not defined UnsupportedOperationException should have thrown.
+ assert(false);
+ break;
+ }
+ }
+ return null;
+ }
+
+ static int hexToInt(String hex) => int.parse(hex, radix: 16);
+
+ static String convertToHexString(int r, int g, int b, [num? a]) {
+ var rHex = Color._numAs2DigitHex(Color._clamp(r, 0, 255));
+ var gHex = Color._numAs2DigitHex(Color._clamp(g, 0, 255));
+ var bHex = Color._numAs2DigitHex(Color._clamp(b, 0, 255));
+ var aHex = (a != null)
+ ? Color._numAs2DigitHex((Color._clamp(a, 0, 1) * 255).round())
+ : '';
+
+ // TODO(terry) 15.toRadixString(16) return 'F' on Dartium not f as in JS.
+ // bug: <http://code.google.com/p/dart/issues/detail?id=2670>
+ return '$aHex$rHex$gHex$bHex'.toLowerCase();
+ }
+
+ static String _numAs2DigitHex(int v) => v.toRadixString(16).padLeft(2, '0');
+
+ static T _clamp<T extends num>(T value, T min, T max) =>
+ math.max(math.min(max, value), min);
+
+ /// Change the tint (make color lighter) or shade (make color darker) of all
+ /// parts of [rgba] (r, g and b). The [amount] is percentage darker between
+ /// -1 to 0 for darker and 0 to 1 for lighter; '0' is no change. The [amount]
+ /// will darken or lighten the rgb values; it will not change the alpha value.
+ /// If [amount] is outside of the value -1 to +1 then [amount] is changed to
+ /// either the min or max direction -1 or 1.
+ ///
+ /// Darker will approach the color #000000 (black) and lighter will approach
+ /// the color #ffffff (white).
+ static Rgba _createNewTintShadeFromRgba(Rgba rgba, num amount) {
+ int r, g, b;
+ var tintShade = Color._clamp(amount, -1, 1);
+ if (amount < 0 && rgba.r == 255 && rgba.g == 255 && rgba.b == 255) {
+ // TODO(terry): See TODO in _changeTintShadeColor; eliminate this test
+ // by converting to HSL and adjust lightness although this
+ // is fastest lighter/darker algorithm.
+ // Darkening white special handling.
+ r = Color._clamp((255 + (255 * tintShade)).round().toInt(), 0, 255);
+ g = Color._clamp((255 + (255 * tintShade)).round().toInt(), 0, 255);
+ b = Color._clamp((255 + (255 * tintShade)).round().toInt(), 0, 255);
+ } else {
+ // All other colors then darkening white go here.
+ r = Color._changeTintShadeColor(rgba.r, tintShade).round().toInt();
+ g = Color._changeTintShadeColor(rgba.g, tintShade).round().toInt();
+ b = Color._changeTintShadeColor(rgba.b, tintShade).round().toInt();
+ }
+ return Rgba(r, g, b, rgba.a);
+ }
+
+ // TODO(terry): This does an okay lighter/darker; better would be convert to
+ // HSL then change the lightness.
+ /// The parameter [v] is the color to change (r, g, or b) in the range '0' to
+ /// '255'. The parameter [delta] is a number between '-1' and '1'. A value
+ /// between '-1' and '0' is darker and a value between '0' and '1' is lighter
+ /// ('0' imples no change).
+ static num _changeTintShadeColor(num v, num delta) =>
+ Color._clamp(((1 - delta) * v + (delta * 255)).round(), 0, 255);
+
+ // Predefined CSS colors see <http://www.w3.org/TR/css3-color/>
+ static final Color transparent = const Color.hex('00ffffff'); // Alpha 0.0
+ static final Color aliceBlue = const Color.hex('0f08ff');
+ static final Color antiqueWhite = const Color.hex('0faebd7');
+ static final Color aqua = const Color.hex('00ffff');
+ static final Color aquaMarine = const Color.hex('7fffd4');
+ static final Color azure = const Color.hex('f0ffff');
+ static final Color beige = const Color.hex('f5f5dc');
+ static final Color bisque = const Color.hex('ffe4c4');
+ static final Color black = const Color.hex('000000');
+ static final Color blanchedAlmond = const Color.hex('ffebcd');
+ static final Color blue = const Color.hex('0000ff');
+ static final Color blueViolet = const Color.hex('8a2be2');
+ static final Color brown = const Color.hex('a52a2a');
+ static final Color burlyWood = const Color.hex('deb887');
+ static final Color cadetBlue = const Color.hex('5f9ea0');
+ static final Color chartreuse = const Color.hex('7fff00');
+ static final Color chocolate = const Color.hex('d2691e');
+ static final Color coral = const Color.hex('ff7f50');
+ static final Color cornFlowerBlue = const Color.hex('6495ed');
+ static final Color cornSilk = const Color.hex('fff8dc');
+ static final Color crimson = const Color.hex('dc143c');
+ static final Color cyan = const Color.hex('00ffff');
+ static final Color darkBlue = const Color.hex('00008b');
+ static final Color darkCyan = const Color.hex('008b8b');
+ static final Color darkGoldenRod = const Color.hex('b8860b');
+ static final Color darkGray = const Color.hex('a9a9a9');
+ static final Color darkGreen = const Color.hex('006400');
+ static final Color darkGrey = const Color.hex('a9a9a9');
+ static final Color darkKhaki = const Color.hex('bdb76b');
+ static final Color darkMagenta = const Color.hex('8b008b');
+ static final Color darkOliveGreen = const Color.hex('556b2f');
+ static final Color darkOrange = const Color.hex('ff8c00');
+ static final Color darkOrchid = const Color.hex('9932cc');
+ static final Color darkRed = const Color.hex('8b0000');
+ static final Color darkSalmon = const Color.hex('e9967a');
+ static final Color darkSeaGreen = const Color.hex('8fbc8f');
+ static final Color darkSlateBlue = const Color.hex('483d8b');
+ static final Color darkSlateGray = const Color.hex('2f4f4f');
+ static final Color darkSlateGrey = const Color.hex('2f4f4f');
+ static final Color darkTurquoise = const Color.hex('00ced1');
+ static final Color darkViolet = const Color.hex('9400d3');
+ static final Color deepPink = const Color.hex('ff1493');
+ static final Color deepSkyBlue = const Color.hex('00bfff');
+ static final Color dimGray = const Color.hex('696969');
+ static final Color dimGrey = const Color.hex('696969');
+ static final Color dodgerBlue = const Color.hex('1e90ff');
+ static final Color fireBrick = const Color.hex('b22222');
+ static final Color floralWhite = const Color.hex('fffaf0');
+ static final Color forestGreen = const Color.hex('228b22');
+ static final Color fuchsia = const Color.hex('ff00ff');
+ static final Color gainsboro = const Color.hex('dcdcdc');
+ static final Color ghostWhite = const Color.hex('f8f8ff');
+ static final Color gold = const Color.hex('ffd700');
+ static final Color goldenRod = const Color.hex('daa520');
+ static final Color gray = const Color.hex('808080');
+ static final Color green = const Color.hex('008000');
+ static final Color greenYellow = const Color.hex('adff2f');
+ static final Color grey = const Color.hex('808080');
+ static final Color honeydew = const Color.hex('f0fff0');
+ static final Color hotPink = const Color.hex('ff69b4');
+ static final Color indianRed = const Color.hex('cd5c5c');
+ static final Color indigo = const Color.hex('4b0082');
+ static final Color ivory = const Color.hex('fffff0');
+ static final Color khaki = const Color.hex('f0e68c');
+ static final Color lavender = const Color.hex('e6e6fa');
+ static final Color lavenderBlush = const Color.hex('fff0f5');
+ static final Color lawnGreen = const Color.hex('7cfc00');
+ static final Color lemonChiffon = const Color.hex('fffacd');
+ static final Color lightBlue = const Color.hex('add8e6');
+ static final Color lightCoral = const Color.hex('f08080');
+ static final Color lightCyan = const Color.hex('e0ffff');
+ static final Color lightGoldenRodYellow = const Color.hex('fafad2');
+ static final Color lightGray = const Color.hex('d3d3d3');
+ static final Color lightGreen = const Color.hex('90ee90');
+ static final Color lightGrey = const Color.hex('d3d3d3');
+ static final Color lightPink = const Color.hex('ffb6c1');
+ static final Color lightSalmon = const Color.hex('ffa07a');
+ static final Color lightSeaGreen = const Color.hex('20b2aa');
+ static final Color lightSkyBlue = const Color.hex('87cefa');
+ static final Color lightSlateGray = const Color.hex('778899');
+ static final Color lightSlateGrey = const Color.hex('778899');
+ static final Color lightSteelBlue = const Color.hex('b0c4de');
+ static final Color lightYellow = const Color.hex('ffffe0');
+ static final Color lime = const Color.hex('00ff00');
+ static final Color limeGreen = const Color.hex('32cd32');
+ static final Color linen = const Color.hex('faf0e6');
+ static final Color magenta = const Color.hex('ff00ff');
+ static final Color maroon = const Color.hex('800000');
+ static final Color mediumAquaMarine = const Color.hex('66cdaa');
+ static final Color mediumBlue = const Color.hex('0000cd');
+ static final Color mediumOrchid = const Color.hex('ba55d3');
+ static final Color mediumPurple = const Color.hex('9370db');
+ static final Color mediumSeaGreen = const Color.hex('3cb371');
+ static final Color mediumSlateBlue = const Color.hex('7b68ee');
+ static final Color mediumSpringGreen = const Color.hex('00fa9a');
+ static final Color mediumTurquoise = const Color.hex('48d1cc');
+ static final Color mediumVioletRed = const Color.hex('c71585');
+ static final Color midnightBlue = const Color.hex('191970');
+ static final Color mintCream = const Color.hex('f5fffa');
+ static final Color mistyRose = const Color.hex('ffe4e1');
+ static final Color moccasin = const Color.hex('ffe4b5');
+ static final Color navajoWhite = const Color.hex('ffdead');
+ static final Color navy = const Color.hex('000080');
+ static final Color oldLace = const Color.hex('fdf5e6');
+ static final Color olive = const Color.hex('808000');
+ static final Color oliveDrab = const Color.hex('6b8e23');
+ static final Color orange = const Color.hex('ffa500');
+ static final Color orangeRed = const Color.hex('ff4500');
+ static final Color orchid = const Color.hex('da70d6');
+ static final Color paleGoldenRod = const Color.hex('eee8aa');
+ static final Color paleGreen = const Color.hex('98fb98');
+ static final Color paleTurquoise = const Color.hex('afeeee');
+ static final Color paleVioletRed = const Color.hex('db7093');
+ static final Color papayaWhip = const Color.hex('ffefd5');
+ static final Color peachPuff = const Color.hex('ffdab9');
+ static final Color peru = const Color.hex('cd85ef');
+ static final Color pink = const Color.hex('ffc0cb');
+ static final Color plum = const Color.hex('dda0dd');
+ static final Color powderBlue = const Color.hex('b0e0e6');
+ static final Color purple = const Color.hex('800080');
+ static final Color red = const Color.hex('ff0000');
+ static final Color rosyBrown = const Color.hex('bc8f8f');
+ static final Color royalBlue = const Color.hex('4169e1');
+ static final Color saddleBrown = const Color.hex('8b4513');
+ static final Color salmon = const Color.hex('fa8072');
+ static final Color sandyBrown = const Color.hex('f4a460');
+ static final Color seaGreen = const Color.hex('2e8b57');
+ static final Color seashell = const Color.hex('fff5ee');
+ static final Color sienna = const Color.hex('a0522d');
+ static final Color silver = const Color.hex('c0c0c0');
+ static final Color skyBlue = const Color.hex('87ceeb');
+ static final Color slateBlue = const Color.hex('6a5acd');
+ static final Color slateGray = const Color.hex('708090');
+ static final Color slateGrey = const Color.hex('708090');
+ static final Color snow = const Color.hex('fffafa');
+ static final Color springGreen = const Color.hex('00ff7f');
+ static final Color steelBlue = const Color.hex('4682b4');
+ static final Color tan = const Color.hex('d2b48c');
+ static final Color teal = const Color.hex('008080');
+ static final Color thistle = const Color.hex('d8bfd8');
+ static final Color tomato = const Color.hex('ff6347');
+ static final Color turquoise = const Color.hex('40e0d0');
+ static final Color violet = const Color.hex('ee82ee');
+ static final Color wheat = const Color.hex('f5deb3');
+ static final Color white = const Color.hex('ffffff');
+ static final Color whiteSmoke = const Color.hex('f5f5f5');
+ static final Color yellow = const Color.hex('ffff00');
+ static final Color yellowGreen = const Color.hex('9acd32');
+}
+
+/// Rgba class for users that want to interact with a color as a RGBA value.
+class Rgba implements _StyleProperty, ColorBase {
+ // TODO(terry): Consider consolidating rgba to a single 32-bit int, make sure
+ // it works under JS and Dart VM.
+ final int r;
+ final int g;
+ final int b;
+ final num? a;
+
+ Rgba(int red, int green, int blue, [num? alpha])
+ : r = Color._clamp(red, 0, 255),
+ g = Color._clamp(green, 0, 255),
+ b = Color._clamp(blue, 0, 255),
+ a = (alpha != null) ? Color._clamp(alpha, 0, 1) : alpha;
+
+ factory Rgba.fromString(String hexValue) =>
+ Color.css('#${Color._convertCssToArgb(hexValue)}').rgba;
+
+ factory Rgba.fromColor(Color color) => color.rgba;
+
+ factory Rgba.fromArgbValue(num value) => Rgba(
+ (value.toInt() & 0xff000000) >> 0x18, // a
+ (value.toInt() & 0xff0000) >> 0x10, // r
+ (value.toInt() & 0xff00) >> 8, // g
+ value.toInt() & 0xff,
+ ); // b
+
+ factory Rgba.fromHsla(Hsla hsla) {
+ // Convert to Rgba.
+ // See site <http://easyrgb.com/index.php?X=MATH> for good documentation
+ // and color conversion routines.
+
+ var h = hsla.hue;
+ var s = hsla.saturation;
+ var l = hsla.lightness;
+ var a = hsla.alpha;
+
+ int r;
+ int g;
+ int b;
+
+ if (s == 0) {
+ r = (l * 255).round().toInt();
+ g = r;
+ b = r;
+ } else {
+ num var2;
+
+ if (l < 0.5) {
+ var2 = l * (1 + s);
+ } else {
+ var2 = (l + s) - (s * l);
+ }
+ var var1 = 2 * l - var2;
+
+ r = (255 * Rgba._hueToRGB(var1, var2, h + (1 / 3))).round().toInt();
+ g = (255 * Rgba._hueToRGB(var1, var2, h)).round().toInt();
+ b = (255 * Rgba._hueToRGB(var1, var2, h - (1 / 3))).round().toInt();
+ }
+
+ return Rgba(r, g, b, a);
+ }
+
+ static num _hueToRGB(num v1, num v2, num vH) {
+ if (vH < 0) {
+ vH += 1;
+ }
+
+ if (vH > 1) {
+ vH -= 1;
+ }
+
+ if ((6 * vH) < 1) {
+ return v1 + (v2 - v1) * 6 * vH;
+ }
+
+ if ((2 * vH) < 1) {
+ return v2;
+ }
+
+ if ((3 * vH) < 2) {
+ return v1 + (v2 - v1) * ((2 / 3 - vH) * 6);
+ }
+
+ return v1;
+ }
+
+ @override
+ bool operator ==(Object other) => Color.equal(this, other);
+
+ @override
+ String get cssExpression {
+ if (a == null) {
+ return '#${Color.convertToHexString(r, g, b)}';
+ } else {
+ return 'rgba($r,$g,$b,$a)';
+ }
+ }
+
+ @override
+ String toHexArgbString() => Color.convertToHexString(r, g, b, a);
+
+ @override
+ int get argbValue {
+ var value = 0;
+ if (a != null) {
+ value = a!.toInt() << 0x18;
+ }
+ value += r << 0x10;
+ value += g << 0x08;
+ value += b;
+ return value;
+ }
+
+ Color get color => Color.createRgba(r, g, b, a);
+ Hsla get hsla => Hsla.fromRgba(this);
+
+ Rgba darker(num amount) => Color._createNewTintShadeFromRgba(this, -amount);
+ Rgba lighter(num amount) => Color._createNewTintShadeFromRgba(this, amount);
+
+ @override
+ int get hashCode => toHexArgbString().hashCode;
+}
+
+/// Hsl class support to interact with a color as a hsl with hue, saturation,
+/// and lightness with optional alpha blending. The hue is a ratio of 360
+/// degrees 360° = 1 or 0, (1° == (1/360)), saturation and lightness is a 0..1
+/// fraction (1 == 100%) and alpha is a 0..1 fraction.
+class Hsla implements _StyleProperty, ColorBase {
+ final num _h; // Value from 0..1
+ final num _s; // Value from 0..1
+ final num _l; // Value from 0..1
+ final num? _a; // Value from 0..1
+
+ /// [hue] is a 0..1 fraction of 360 degrees (360 == 0).
+ /// [saturation] is a 0..1 fraction (100% == 1).
+ /// [lightness] is a 0..1 fraction (100% == 1).
+ /// [alpha] is a 0..1 fraction, alpha blending between 0..1, 1 == 100% opaque.
+ Hsla(num hue, num saturation, num lightness, [num? alpha])
+ : _h = (hue == 1) ? 0 : Color._clamp(hue, 0, 1),
+ _s = Color._clamp(saturation, 0, 1),
+ _l = Color._clamp(lightness, 0, 1),
+ _a = (alpha != null) ? Color._clamp(alpha, 0, 1) : alpha;
+
+ factory Hsla.fromString(String hexValue) {
+ var rgba = Color.css('#${Color._convertCssToArgb(hexValue)}').rgba;
+ return _createFromRgba(rgba.r, rgba.g, rgba.b, rgba.a);
+ }
+
+ factory Hsla.fromColor(Color color) {
+ var rgba = color.rgba;
+ return _createFromRgba(rgba.r, rgba.g, rgba.b, rgba.a);
+ }
+
+ factory Hsla.fromArgbValue(num value) {
+ num a = (value.toInt() & 0xff000000) >> 0x18;
+ var r = (value.toInt() & 0xff0000) >> 0x10;
+ var g = (value.toInt() & 0xff00) >> 8;
+ var b = value.toInt() & 0xff;
+
+ // Convert alpha to 0..1 from (0..255).
+ a = double.parse((a / 255).toStringAsPrecision(2));
+
+ return _createFromRgba(r, g, b, a);
+ }
+
+ factory Hsla.fromRgba(Rgba rgba) =>
+ _createFromRgba(rgba.r, rgba.g, rgba.b, rgba.a);
+
+ static Hsla _createFromRgba(num r, num g, num b, num? a) {
+ // Convert RGB to hsl.
+ // See site <http://easyrgb.com/index.php?X=MATH> for good documentation
+ // and color conversion routines.
+ r /= 255;
+ g /= 255;
+ b /= 255;
+
+ // Hue, saturation and lightness.
+ num h;
+ num s;
+ num l;
+
+ var minRgb = math.min(r, math.min(g, b));
+ var maxRgb = math.max(r, math.max(g, b));
+ l = (maxRgb + minRgb) / 2;
+ if (l <= 0) {
+ return Hsla(0, 0, l); // Black;
+ }
+
+ var vm = maxRgb - minRgb;
+ s = vm;
+ if (s > 0) {
+ s /= (l < 0.5) ? (maxRgb + minRgb) : (2 - maxRgb - minRgb);
+ } else {
+ return Hsla(0, 0, l); // White
+ }
+
+ num r2, g2, b2;
+ r2 = (maxRgb - r) / vm;
+ g2 = (maxRgb - g) / vm;
+ b2 = (maxRgb - b) / vm;
+ if (r == maxRgb) {
+ h = (g == minRgb) ? 5.0 + b2 : 1 - g2;
+ } else if (g == maxRgb) {
+ h = (b == minRgb) ? 1 + r2 : 3 - b2;
+ } else {
+ h = (r == minRgb) ? 3 + g2 : 5 - r2;
+ }
+ h /= 6;
+
+ return Hsla(h, s, l, a);
+ }
+
+ /// Returns 0..1 fraction (ratio of 360°, e.g. 1° == 1/360).
+ num get hue => _h;
+
+ /// Returns 0..1 fraction (1 == 100%)
+ num get saturation => _s;
+
+ /// Returns 0..1 fraction (1 == 100%).
+ num get lightness => _l;
+
+ /// Returns number as degrees 0..360.
+ num get hueDegrees => (_h * 360).round();
+
+ /// Returns number as percentage 0..100
+ num get saturationPercentage => (_s * 100).round();
+
+ /// Returns number as percentage 0..100.
+ num get lightnessPercentage => (_l * 100).round();
+
+ /// Returns number as 0..1
+ num? get alpha => _a;
+
+ @override
+ bool operator ==(Object other) => Color.equal(this, other);
+
+ @override
+ String get cssExpression => (_a == null)
+ ? 'hsl($hueDegrees,$saturationPercentage,$lightnessPercentage)'
+ : 'hsla($hueDegrees,$saturationPercentage,$lightnessPercentage,$_a)';
+
+ @override
+ String toHexArgbString() => Rgba.fromHsla(this).toHexArgbString();
+
+ @override
+ int get argbValue => Color.hexToInt(toHexArgbString());
+
+ Color get color => Color.createHsla(_h, _s, _l, _a);
+ Rgba get rgba => Rgba.fromHsla(this);
+
+ Hsla darker(num amount) => Hsla.fromRgba(Rgba.fromHsla(this).darker(amount));
+
+ Hsla lighter(num amount) =>
+ Hsla.fromRgba(Rgba.fromHsla(this).lighter(amount));
+
+ @override
+ int get hashCode => toHexArgbString().hashCode;
+}
+
+/// X,Y position.
+class PointXY implements _StyleProperty {
+ final num x, y;
+ const PointXY(this.x, this.y);
+
+ @override
+ String? get cssExpression =>
+ // TODO(terry): TBD
+ null;
+}
+
+// TODO(terry): Implement style and color.
+/// Supports border for measuring with layout.
+class Border implements _StyleProperty {
+ final int? top, left, bottom, right;
+
+ // TODO(terry): Just like CSS, 1-arg -> set all properties, 2-args -> top and
+ // bottom are first arg, left and right are second, 3-args, and
+ // 4-args -> tlbr or trbl.
+ const Border([this.top, this.left, this.bottom, this.right]);
+
+ // TODO(terry): Consider using Size or width and height.
+ Border.uniform(int amount)
+ : top = amount,
+ left = amount,
+ bottom = amount,
+ right = amount;
+
+ int get width => left! + right!;
+ int get height => top! + bottom!;
+
+ @override
+ String get cssExpression => (top == left && bottom == right && top == right)
+ ? '${left}px'
+ : "${top != null ? '$top' : '0'}px "
+ "${right != null ? '$right' : '0'}px "
+ "${bottom != null ? '$bottom' : '0'}px "
+ "${left != null ? '$left' : '0'}px";
+}
+
+/// Font style constants.
+class FontStyle {
+ /// Font style [normal] default.
+ static const String normal = 'normal';
+
+ /// Font style [italic] use explicity crafted italic font otherwise inclined
+ /// on the fly like oblique.
+ static const String italic = 'italic';
+
+ /// Font style [oblique] is rarely used. The normal style of a font is
+ /// inclined on the fly to the right by 8-12 degrees.
+ static const String oblique = 'oblique';
+}
+
+/// Font variant constants.
+class FontVariant {
+ /// Font style [normal] default.
+ static const String normal = 'normal';
+
+ /// Font variant [smallCaps].
+ static const String smallCaps = 'small-caps';
+}
+
+/// Font weight constants values 100, 200, 300, 400, 500, 600, 700, 800, 900.
+class FontWeight {
+ /// Font weight normal [default]
+ static const int normal = 400;
+
+ /// Font weight bold
+ static const int bold = 700;
+
+ static const int wt100 = 100;
+ static const int wt200 = 200;
+ static const int wt300 = 300;
+ static const int wt400 = 400;
+ static const int wt500 = 500;
+ static const int wt600 = 600;
+ static const int wt700 = 700;
+ static const int wt800 = 800;
+ static const int wt900 = 900;
+}
+
+/// Generic font family names.
+class FontGeneric {
+ /// Generic family sans-serif font (w/o serifs).
+ static const String sansSerif = 'sans-serif';
+
+ /// Generic family serif font.
+ static const String serif = 'serif';
+
+ /// Generic family fixed-width font.
+ static const monospace = 'monospace';
+
+ /// Generic family emulate handwriting font.
+ static const String cursive = 'cursive';
+
+ /// Generic family decorative font.
+ static const String fantasy = 'fantasy';
+}
+
+/// List of most common font families across different platforms. Use the
+/// collection names in the Font class (e.g., Font.SANS_SERIF, Font.FONT_SERIF,
+/// Font.MONOSPACE, Font.CURSIVE or Font.FANTASY). These work best on all
+/// platforms using the fonts that best match availability on each platform.
+/// See <http://www.angelfire.com/al4/rcollins/style/fonts.html> for a good
+/// description of fonts available between platforms and browsers.
+class FontFamily {
+ /// Sans-Serif font for Windows similar to Helvetica on Mac bold/italic.
+ static const String arial = 'arial';
+
+ /// Sans-Serif font for Windows less common already bolded.
+ static const String arialBlack = 'arial black';
+
+ /// Sans-Serif font for Mac since 1984, similar to Arial/Helvetica.
+ static const String geneva = 'geneva';
+
+ /// Sans-Serif font for Windows most readable sans-serif font for displays.
+ static const String verdana = 'verdana';
+
+ /// Sans-Serif font for Mac since 1984 is identical to Arial.
+ static const String helvetica = 'helvetica';
+
+ /// Serif font for Windows traditional font with “old-style” numerals.
+ static const String georgia = 'georgia';
+
+ /// Serif font for Mac. PCs may have the non-scalable Times use Times New
+ /// Roman instead. Times is more compact than Times New Roman.
+ static const String times = 'times';
+
+ /// Serif font for Windows most common serif font and default serif font for
+ /// most browsers.
+ static const String timesNewRoman = 'times new roman';
+
+ /// Monospace font for Mac/Windows most common. Scalable on Mac not scalable
+ /// on Windows.
+ static const String courier = 'courier';
+
+ /// Monospace font for Mac/Windows scalable on both platforms.
+ static const String courierNew = 'courier new';
+
+ /// Cursive font for Windows and default cursive font for IE.
+ static const String comicSansMs = 'comic sans ms';
+
+ /// Cursive font for Mac on Macs 2000 and newer.
+ static const String textile = 'textile';
+
+ /// Cursive font for older Macs.
+ static const String appleChancery = 'apple chancery';
+
+ /// Cursive font for some PCs.
+ static const String zaphChancery = 'zaph chancery';
+
+ /// Fantasy font on most Mac/Windows/Linux platforms.
+ static const String impact = 'impact';
+
+ /// Fantasy font for Windows.
+ static const String webdings = 'webdings';
+}
+
+class LineHeight {
+ final num height;
+ final bool inPixels;
+ const LineHeight(this.height, {this.inPixels = true});
+}
+
+// TODO(terry): Support @font-face fule.
+/// Font style support for size, family, weight, style, variant, and lineheight.
+class Font implements _StyleProperty {
+ /// Collection of most common sans-serif fonts in order.
+ static const List<String> sansSerif = [
+ FontFamily.arial,
+ FontFamily.verdana,
+ FontFamily.geneva,
+ FontFamily.helvetica,
+ FontGeneric.sansSerif
+ ];
+
+ /// Collection of most common serif fonts in order.
+ static const List<String> serif = [
+ FontFamily.georgia,
+ FontFamily.timesNewRoman,
+ FontFamily.times,
+ FontGeneric.serif
+ ];
+
+ /// Collection of most common monospace fonts in order.
+ static const List<String> monospace = [
+ FontFamily.courierNew,
+ FontFamily.courier,
+ FontGeneric.monospace
+ ];
+
+ /// Collection of most common cursive fonts in order.
+ static const List<String> cursive = [
+ FontFamily.textile,
+ FontFamily.appleChancery,
+ FontFamily.zaphChancery,
+ FontGeneric.fantasy
+ ];
+
+ /// Collection of most common fantasy fonts in order.
+ static const List<String> fantasy = [
+ FontFamily.comicSansMs,
+ FontFamily.impact,
+ FontFamily.webdings,
+ FontGeneric.fantasy
+ ];
+
+ // TODO(terry): Should support the values xx-small, small, large, xx-large,
+ // etc. (mapped to a pixel sized font)?
+ /// Font size in pixels.
+ final num? size;
+
+ // TODO(terry): _family should be an immutable list, wrapper class to do this
+ // should exist in Dart.
+ /// Family specifies a list of fonts, the browser will sequentially select the
+ /// the first known/supported font. There are two types of font families the
+ /// family-name (e.g., arial, times, courier, etc) or the generic-family
+ /// (e.g., serif, sans-seric, etc.)
+ final List<String>? family;
+
+ /// Font weight from 100, 200, 300, 400, 500, 600, 700, 800, 900
+ final int? weight;
+
+ /// Style of a font normal, italic, oblique.
+ final String? style;
+
+ /// Font variant NORMAL (default) or SMALL_CAPS. Different set of font glyph
+ /// lower case letters designed to have to fit within the font-height and
+ /// weight of the corresponding lowercase letters.
+ final String? variant;
+
+ final LineHeight? lineHeight;
+
+ // TODO(terry): Size and computedLineHeight are in pixels. Need to figure out
+ // how to handle in other units (specified in other units) like
+ // points, inches, etc. Do we have helpers like Units.Points(12)
+ // where 12 is in points and that's converted to pixels?
+ // TODO(terry): lineHeight is computed as 1.2 although CSS_RESET is 1.0 we
+ // need to be consistent some browsers use 1 others 1.2.
+ // TODO(terry): There is a school of thought "Golden Ratio Typography".
+ // Where width to display the text is also important in computing the line
+ // height. Classic typography suggest the ratio be 1.5. See
+ // <http://www.pearsonified.com/2011/12/golden-ratio-typography.php> and
+ // <http://meyerweb.com/eric/thoughts/2008/05/06/line-height-abnormal/>.
+ /// Create a font using [size] of font in pixels, [family] name of font(s)
+ /// using [FontFamily], [style] of the font using [FontStyle], [variant] using
+ /// [FontVariant], and [lineHeight] extra space (leading) around the font in
+ /// pixels, if not specified it's 1.2 the font size.
+ const Font(
+ {this.size,
+ this.family,
+ this.weight,
+ this.style,
+ this.variant,
+ this.lineHeight});
+
+ /// Merge the two fonts and return the result. See [Style.merge] for
+ /// more information.
+ static Font? merge(Font? a, Font? b) {
+ if (a == null) return b;
+ if (b == null) return a;
+ return Font._merge(a, b);
+ }
+
+ Font._merge(Font a, Font b)
+ : size = _mergeVal(a.size, b.size),
+ family = _mergeVal(a.family, b.family),
+ weight = _mergeVal(a.weight, b.weight),
+ style = _mergeVal(a.style, b.style),
+ variant = _mergeVal(a.variant, b.variant),
+ lineHeight = _mergeVal(a.lineHeight, b.lineHeight);
+
+ /// Shorthand CSS format for font is:
+ ///
+ /// font-style font-variant font-weight font-size/line-height font-family
+ ///
+ /// The font-size and font-family values are required. If any of the other
+ /// values are missing the default value is used.
+ @override
+ String get cssExpression {
+ // TODO(jimhug): include variant, style, other options
+ if (weight != null) {
+ // TODO(jacobr): is this really correct for lineHeight?
+ if (lineHeight != null) {
+ return '$weight ${size}px/$lineHeightInPixels $_fontsAsString';
+ }
+ return '$weight ${size}px $_fontsAsString';
+ }
+
+ return '${size}px $_fontsAsString';
+ }
+
+ Font scale(num ratio) => Font(
+ size: size! * ratio,
+ family: family,
+ weight: weight,
+ style: style,
+ variant: variant);
+
+ /// The lineHeight, provides an indirect means to specify the leading. The
+ /// leading is the difference between the font-size height and the (used)
+ /// value of line height in pixels. If lineHeight is not specified it's
+ /// automatically computed as 1.2 of the font size. Firefox is 1.2, Safari is
+ /// ~1.2, and CSS suggest a ration from 1 to 1.2 of the font-size when
+ /// computing line-height. The Font class constructor has the computation for
+ /// _lineHeight.
+ num? get lineHeightInPixels {
+ if (lineHeight != null) {
+ if (lineHeight!.inPixels) {
+ return lineHeight!.height;
+ } else {
+ return (size != null) ? lineHeight!.height * size! : null;
+ }
+ } else {
+ return (size != null) ? size! * 1.2 : null;
+ }
+ }
+
+ @override
+ int get hashCode =>
+ // TODO(jimhug): Lot's of potential collisions here. List of fonts, etc.
+ size!.toInt() % family![0].hashCode;
+
+ @override
+ bool operator ==(Object other) {
+ if (other is! Font) return false;
+ return other.size == size &&
+ other.family == family &&
+ other.weight == weight &&
+ other.lineHeight == lineHeight &&
+ other.style == style &&
+ other.variant == variant;
+ }
+
+ // TODO(terry): This is fragile should probably just iterate through the list
+ // of fonts construction the font-family string.
+ /// Return fonts as a comma seperated list sans the square brackets.
+ String get _fontsAsString {
+ var fonts = family.toString();
+ return fonts.length > 2 ? fonts.substring(1, fonts.length - 1) : '';
+ }
+}
+
+/// This class stores the sizes of the box edges in the CSS [box model][]. Each
+/// edge area is placed around the sides of the content box. The innermost area
+/// is the [Style.padding] area which has a background and surrounds the
+/// content. The content and padding area is surrounded by the [Style.border],
+/// which itself is surrounded by the transparent [Style.margin]. This box
+/// represents the eges of padding, border, or margin depending on which
+/// accessor was used to retrieve it.
+///
+/// [box model]: https://developer.mozilla.org/en/CSS/box_model
+class BoxEdge {
+ /// The size of the left edge, or null if the style has no edge.
+ final num? left;
+
+ /// The size of the top edge, or null if the style has no edge.
+ final num? top;
+
+ /// The size of the right edge, or null if the style has no edge.
+ final num? right;
+
+ /// The size of the bottom edge, or null if the style has no edge.
+ final num? bottom;
+
+ /// Creates a box edge with the specified [left], [top], [right], and
+ /// [bottom] width.
+ const BoxEdge([this.left, this.top, this.right, this.bottom]);
+
+ /// Creates a box edge with the specified [top], [right], [bottom], and
+ /// [left] width. This matches the typical CSS order:
+ /// <https://developer.mozilla.org/en/CSS/margin>
+ /// <https://developer.mozilla.org/en/CSS/border-width>
+ /// <https://developer.mozilla.org/en/CSS/padding>.
+ const BoxEdge.clockwiseFromTop(this.top, this.right, this.bottom, this.left);
+
+ /// This is a helper to creates a box edge with the same [left], [top]
+ /// [right], and [bottom] widths.
+ const BoxEdge.uniform(num size)
+ : top = size,
+ left = size,
+ bottom = size,
+ right = size;
+
+ /// Takes a possibly null box edge, with possibly null metrics, and fills
+ /// them in with 0 instead.
+ factory BoxEdge.nonNull(BoxEdge? other) {
+ if (other == null) return const BoxEdge(0, 0, 0, 0);
+ var left = other.left;
+ var top = other.top;
+ var right = other.right;
+ var bottom = other.bottom;
+ var make = false;
+ if (left == null) {
+ make = true;
+ left = 0;
+ }
+ if (top == null) {
+ make = true;
+ top = 0;
+ }
+ if (right == null) {
+ make = true;
+ right = 0;
+ }
+ if (bottom == null) {
+ make = true;
+ bottom = 0;
+ }
+ return make ? BoxEdge(left, top, right, bottom) : other;
+ }
+
+ /// Merge the two box edge sizes and return the result. See [Style.merge] for
+ /// more information.
+ static BoxEdge? merge(BoxEdge? x, BoxEdge? y) {
+ if (x == null) return y;
+ if (y == null) return x;
+ return BoxEdge._merge(x, y);
+ }
+
+ BoxEdge._merge(BoxEdge x, BoxEdge y)
+ : left = _mergeVal(x.left, y.left),
+ top = _mergeVal(x.top, y.top),
+ right = _mergeVal(x.right, y.right),
+ bottom = _mergeVal(x.bottom, y.bottom);
+
+ /// The total size of the horizontal edges. Equal to [left] + [right], where
+ /// null is interpreted as 0px.
+ num get width => (left ?? 0) + (right ?? 0);
+
+ /// The total size of the vertical edges. Equal to [top] + [bottom], where
+ /// null is interpreted as 0px.
+ num get height => (top ?? 0) + (bottom ?? 0);
+}
+
+T _mergeVal<T>(T x, T y) => y ?? x;
diff --git a/pkgs/csslib/lib/src/token.dart b/pkgs/csslib/lib/src/token.dart
new file mode 100644
index 0000000..5a07535
--- /dev/null
+++ b/pkgs/csslib/lib/src/token.dart
@@ -0,0 +1,63 @@
+// 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.
+
+part of '../parser.dart';
+
+/// A single token in the Dart language.
+class Token {
+ /// A member of [TokenKind] specifying what kind of token this is.
+ final int kind;
+
+ /// The location where this token was parsed from.
+ final FileSpan span;
+
+ /// The start offset of this token.
+ int get start => span.start.offset;
+
+ /// The end offset of this token.
+ int get end => span.end.offset;
+
+ /// Returns the source text corresponding to this [Token].
+ String get text => span.text;
+
+ Token(this.kind, this.span);
+
+ /// Returns a pretty representation of this token for error messages.
+ @override
+ String toString() {
+ var kindText = TokenKind.kindToString(kind);
+ var actualText = text.trim();
+ if (actualText.isNotEmpty && kindText != actualText) {
+ if (actualText.length > 10) {
+ actualText = '${actualText.substring(0, 8)}...';
+ }
+ return '$kindText($actualText)';
+ } else {
+ return kindText;
+ }
+ }
+}
+
+/// A token containing a parsed literal value.
+class LiteralToken extends Token {
+ dynamic value;
+ LiteralToken(super.kind, super.span, this.value);
+}
+
+/// A token containing error information.
+class ErrorToken extends Token {
+ String? message;
+ ErrorToken(super.kind, super.span, this.message);
+}
+
+/// CSS ident-token.
+///
+/// See <http://dev.w3.org/csswg/css-syntax/#typedef-ident-token> and
+/// <http://dev.w3.org/csswg/css-syntax/#ident-token-diagram>.
+class IdentifierToken extends Token {
+ @override
+ final String text;
+
+ IdentifierToken(this.text, int kind, FileSpan span) : super(kind, span);
+}
diff --git a/pkgs/csslib/lib/src/token_kind.dart b/pkgs/csslib/lib/src/token_kind.dart
new file mode 100644
index 0000000..3f50402
--- /dev/null
+++ b/pkgs/csslib/lib/src/token_kind.dart
@@ -0,0 +1,728 @@
+// 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.
+
+// ignore_for_file: constant_identifier_names
+
+part of '../parser.dart';
+
+// TODO(terry): Need to be consistent with tokens either they're ASCII tokens
+// e.g., ASTERISK or they're CSS e.g., PSEUDO, COMBINATOR_*.
+class TokenKind {
+ // Common shared tokens used in TokenizerBase.
+ static const int UNUSED = 0; // Unused place holder...
+ static const int END_OF_FILE = 1; // EOF
+ static const int LPAREN = 2; // (
+ static const int RPAREN = 3; // )
+ static const int LBRACK = 4; // [
+ static const int RBRACK = 5; // ]
+ static const int LBRACE = 6; // {
+ static const int RBRACE = 7; // }
+ static const int DOT = 8; // .
+ static const int SEMICOLON = 9; // ;
+
+ // Unique tokens for CSS.
+ static const int AT = 10; // @
+ static const int HASH = 11; // #
+ static const int PLUS = 12; // +
+ static const int GREATER = 13; // >
+ static const int TILDE = 14; // ~
+ static const int ASTERISK = 15; // *
+ static const int NAMESPACE = 16; // |
+ static const int COLON = 17; // :
+ static const int PRIVATE_NAME = 18; // _ prefix private class or id
+ static const int COMMA = 19; // ,
+ static const int SPACE = 20;
+ static const int TAB = 21; // /t
+ static const int NEWLINE = 22; // /n
+ static const int RETURN = 23; // /r
+ static const int PERCENT = 24; // %
+ static const int SINGLE_QUOTE = 25; // '
+ static const int DOUBLE_QUOTE = 26; // "
+ static const int SLASH = 27; // /
+ static const int EQUALS = 28; // =
+ static const int CARET = 30; // ^
+ static const int DOLLAR = 31; // $
+ static const int LESS = 32; // <
+ static const int BANG = 33; // !
+ static const int MINUS = 34; // -
+ static const int BACKSLASH = 35; // \
+ static const int AMPERSAND = 36; // &
+
+ // WARNING: Tokens from this point and above must have the corresponding ASCII
+ // character in the TokenChar list at the bottom of this file. The
+ // order of the above tokens should be the same order as TokenChar.
+
+ /// [TokenKind] representing integer tokens.
+ static const int INTEGER = 60;
+
+ /// [TokenKind] representing hex integer tokens.
+ static const int HEX_INTEGER = 61;
+
+ /// [TokenKind] representing double tokens.
+ static const int DOUBLE = 62;
+
+ /// [TokenKind] representing whitespace tokens.
+ static const int WHITESPACE = 63;
+
+ /// [TokenKind] representing comment tokens.
+ static const int COMMENT = 64;
+
+ /// [TokenKind] representing error tokens.
+ static const int ERROR = 65;
+
+ /// [TokenKind] representing incomplete string tokens.
+ static const int INCOMPLETE_STRING = 66;
+
+ /// [TokenKind] representing incomplete comment tokens.
+ static const int INCOMPLETE_COMMENT = 67;
+
+ static const int VAR_DEFINITION = 400; // var-NNN-NNN
+ static const int VAR_USAGE = 401; // var(NNN-NNN [,default])
+
+ // Synthesized Tokens (no character associated with TOKEN).
+ static const int STRING = 500;
+ static const int STRING_PART = 501;
+ static const int NUMBER = 502;
+ static const int HEX_NUMBER = 503;
+ static const int HTML_COMMENT = 504; // <!--
+ static const int IMPORTANT = 505; // !important
+ static const int CDATA_START = 506; // <![CDATA[
+ static const int CDATA_END = 507; // ]]>
+ // U+uNumber[-U+uNumber]
+ // uNumber = 0..10FFFF | ?[?]*
+ static const int UNICODE_RANGE = 508;
+ static const int HEX_RANGE = 509; // ? in the hex range
+ static const int IDENTIFIER = 511;
+
+ // Uniquely synthesized tokens for CSS.
+ static const int SELECTOR_EXPRESSION = 512;
+ static const int COMBINATOR_NONE = 513;
+ static const int COMBINATOR_DESCENDANT = 514; // Space combinator
+ static const int COMBINATOR_PLUS = 515; // + combinator
+ static const int COMBINATOR_GREATER = 516; // > combinator
+ static const int COMBINATOR_TILDE = 517; // ~ combinator
+
+ static const int UNARY_OP_NONE = 518; // No unary operator present.
+
+ // Attribute match types:
+ static const int INCLUDES = 530; // '~='
+ static const int DASH_MATCH = 531; // '|='
+ static const int PREFIX_MATCH = 532; // '^='
+ static const int SUFFIX_MATCH = 533; // '$='
+ static const int SUBSTRING_MATCH = 534; // '*='
+ static const int NO_MATCH = 535; // No operator.
+
+ // Unit types:
+ static const int UNIT_EM = 600;
+ static const int UNIT_EX = 601;
+ static const int UNIT_LENGTH_PX = 602;
+ static const int UNIT_LENGTH_CM = 603;
+ static const int UNIT_LENGTH_MM = 604;
+ static const int UNIT_LENGTH_IN = 605;
+ static const int UNIT_LENGTH_PT = 606;
+ static const int UNIT_LENGTH_PC = 607;
+ static const int UNIT_ANGLE_DEG = 608;
+ static const int UNIT_ANGLE_RAD = 609;
+ static const int UNIT_ANGLE_GRAD = 610;
+ static const int UNIT_ANGLE_TURN = 611;
+ static const int UNIT_TIME_MS = 612;
+ static const int UNIT_TIME_S = 613;
+ static const int UNIT_FREQ_HZ = 614;
+ static const int UNIT_FREQ_KHZ = 615;
+ static const int UNIT_PERCENT = 616;
+ static const int UNIT_FRACTION = 617;
+ static const int UNIT_RESOLUTION_DPI = 618;
+ static const int UNIT_RESOLUTION_DPCM = 619;
+ static const int UNIT_RESOLUTION_DPPX = 620;
+ static const int UNIT_CH = 621; // Measure of "0" U+0030 glyph.
+ static const int UNIT_REM = 622; // computed value ‘font-size’ on root elem.
+ static const int UNIT_VIEWPORT_VW = 623;
+ static const int UNIT_VIEWPORT_VH = 624;
+ static const int UNIT_VIEWPORT_VMIN = 625;
+ static const int UNIT_VIEWPORT_VMAX = 626;
+ static const int UNIT_LH = 627; // Computed height of the element.
+ static const int UNIT_RLH = 628; // Line height of the root element.
+
+ // Directives (@nnnn)
+ static const int DIRECTIVE_NONE = 640;
+ static const int DIRECTIVE_IMPORT = 641;
+ static const int DIRECTIVE_MEDIA = 642;
+ static const int DIRECTIVE_PAGE = 643;
+ static const int DIRECTIVE_CHARSET = 644;
+ static const int DIRECTIVE_STYLET = 645;
+ static const int DIRECTIVE_KEYFRAMES = 646;
+ static const int DIRECTIVE_WEB_KIT_KEYFRAMES = 647;
+ static const int DIRECTIVE_MOZ_KEYFRAMES = 648;
+ static const int DIRECTIVE_MS_KEYFRAMES = 649;
+ static const int DIRECTIVE_O_KEYFRAMES = 650;
+ static const int DIRECTIVE_FONTFACE = 651;
+ static const int DIRECTIVE_NAMESPACE = 652;
+ static const int DIRECTIVE_HOST = 653;
+ static const int DIRECTIVE_MIXIN = 654;
+ static const int DIRECTIVE_INCLUDE = 655;
+ static const int DIRECTIVE_CONTENT = 656;
+ static const int DIRECTIVE_EXTEND = 657;
+ static const int DIRECTIVE_MOZ_DOCUMENT = 658;
+ static const int DIRECTIVE_SUPPORTS = 659;
+ static const int DIRECTIVE_VIEWPORT = 660;
+ static const int DIRECTIVE_MS_VIEWPORT = 661;
+
+ // Media query operators
+ static const int MEDIA_OP_ONLY = 665; // Unary.
+ static const int MEDIA_OP_NOT = 666; // Unary.
+ static const int MEDIA_OP_AND = 667; // Binary.
+
+ // Directives inside of a @page (margin sym).
+ static const int MARGIN_DIRECTIVE_TOPLEFTCORNER = 670;
+ static const int MARGIN_DIRECTIVE_TOPLEFT = 671;
+ static const int MARGIN_DIRECTIVE_TOPCENTER = 672;
+ static const int MARGIN_DIRECTIVE_TOPRIGHT = 673;
+ static const int MARGIN_DIRECTIVE_TOPRIGHTCORNER = 674;
+ static const int MARGIN_DIRECTIVE_BOTTOMLEFTCORNER = 675;
+ static const int MARGIN_DIRECTIVE_BOTTOMLEFT = 676;
+ static const int MARGIN_DIRECTIVE_BOTTOMCENTER = 677;
+ static const int MARGIN_DIRECTIVE_BOTTOMRIGHT = 678;
+ static const int MARGIN_DIRECTIVE_BOTTOMRIGHTCORNER = 679;
+ static const int MARGIN_DIRECTIVE_LEFTTOP = 680;
+ static const int MARGIN_DIRECTIVE_LEFTMIDDLE = 681;
+ static const int MARGIN_DIRECTIVE_LEFTBOTTOM = 682;
+ static const int MARGIN_DIRECTIVE_RIGHTTOP = 683;
+ static const int MARGIN_DIRECTIVE_RIGHTMIDDLE = 684;
+ static const int MARGIN_DIRECTIVE_RIGHTBOTTOM = 685;
+
+ // Simple selector type.
+ // TODO: These are unused and should be removed in a future version.
+ static const int CLASS_NAME = 700; // .class
+ static const int ELEMENT_NAME = 701; // tagName
+ static const int HASH_NAME = 702; // #elementId
+ static const int ATTRIBUTE_NAME = 703; // [attrib]
+ static const int PSEUDO_ELEMENT_NAME = 704; // ::pseudoElement
+ static const int PSEUDO_CLASS_NAME = 705; // :pseudoClass
+ static const int NEGATION = 706; // NOT
+
+ static const List<Map<String, Object>> _DIRECTIVES = [
+ {'type': TokenKind.DIRECTIVE_IMPORT, 'value': 'import'},
+ {'type': TokenKind.DIRECTIVE_MEDIA, 'value': 'media'},
+ {'type': TokenKind.DIRECTIVE_PAGE, 'value': 'page'},
+ {'type': TokenKind.DIRECTIVE_CHARSET, 'value': 'charset'},
+ {'type': TokenKind.DIRECTIVE_STYLET, 'value': 'stylet'},
+ {'type': TokenKind.DIRECTIVE_KEYFRAMES, 'value': 'keyframes'},
+ {
+ 'type': TokenKind.DIRECTIVE_WEB_KIT_KEYFRAMES,
+ 'value': '-webkit-keyframes'
+ },
+ {'type': TokenKind.DIRECTIVE_MOZ_KEYFRAMES, 'value': '-moz-keyframes'},
+ {'type': TokenKind.DIRECTIVE_MS_KEYFRAMES, 'value': '-ms-keyframes'},
+ {'type': TokenKind.DIRECTIVE_O_KEYFRAMES, 'value': '-o-keyframes'},
+ {'type': TokenKind.DIRECTIVE_FONTFACE, 'value': 'font-face'},
+ {'type': TokenKind.DIRECTIVE_NAMESPACE, 'value': 'namespace'},
+ {'type': TokenKind.DIRECTIVE_HOST, 'value': 'host'},
+ {'type': TokenKind.DIRECTIVE_MIXIN, 'value': 'mixin'},
+ {'type': TokenKind.DIRECTIVE_INCLUDE, 'value': 'include'},
+ {'type': TokenKind.DIRECTIVE_CONTENT, 'value': 'content'},
+ {'type': TokenKind.DIRECTIVE_EXTEND, 'value': 'extend'},
+ {'type': TokenKind.DIRECTIVE_MOZ_DOCUMENT, 'value': '-moz-document'},
+ {'type': TokenKind.DIRECTIVE_SUPPORTS, 'value': 'supports'},
+ {'type': TokenKind.DIRECTIVE_VIEWPORT, 'value': 'viewport'},
+ {'type': TokenKind.DIRECTIVE_MS_VIEWPORT, 'value': '-ms-viewport'},
+ ];
+
+ static const List<Map<String, Object>> MEDIA_OPERATORS = [
+ {'type': TokenKind.MEDIA_OP_ONLY, 'value': 'only'},
+ {'type': TokenKind.MEDIA_OP_NOT, 'value': 'not'},
+ {'type': TokenKind.MEDIA_OP_AND, 'value': 'and'},
+ ];
+
+ static const List<Map<String, Object>> MARGIN_DIRECTIVES = [
+ {
+ 'type': TokenKind.MARGIN_DIRECTIVE_TOPLEFTCORNER,
+ 'value': 'top-left-corner'
+ },
+ {'type': TokenKind.MARGIN_DIRECTIVE_TOPLEFT, 'value': 'top-left'},
+ {'type': TokenKind.MARGIN_DIRECTIVE_TOPCENTER, 'value': 'top-center'},
+ {'type': TokenKind.MARGIN_DIRECTIVE_TOPRIGHT, 'value': 'top-right'},
+ {
+ 'type': TokenKind.MARGIN_DIRECTIVE_TOPRIGHTCORNER,
+ 'value': 'top-right-corner'
+ },
+ {
+ 'type': TokenKind.MARGIN_DIRECTIVE_BOTTOMLEFTCORNER,
+ 'value': 'bottom-left-corner'
+ },
+ {'type': TokenKind.MARGIN_DIRECTIVE_BOTTOMLEFT, 'value': 'bottom-left'},
+ {'type': TokenKind.MARGIN_DIRECTIVE_BOTTOMCENTER, 'value': 'bottom-center'},
+ {'type': TokenKind.MARGIN_DIRECTIVE_BOTTOMRIGHT, 'value': 'bottom-right'},
+ {
+ 'type': TokenKind.MARGIN_DIRECTIVE_BOTTOMRIGHTCORNER,
+ 'value': 'bottom-right-corner'
+ },
+ {'type': TokenKind.MARGIN_DIRECTIVE_LEFTTOP, 'value': 'left-top'},
+ {'type': TokenKind.MARGIN_DIRECTIVE_LEFTMIDDLE, 'value': 'left-middle'},
+ {'type': TokenKind.MARGIN_DIRECTIVE_LEFTBOTTOM, 'value': 'right-bottom'},
+ {'type': TokenKind.MARGIN_DIRECTIVE_RIGHTTOP, 'value': 'right-top'},
+ {'type': TokenKind.MARGIN_DIRECTIVE_RIGHTMIDDLE, 'value': 'right-middle'},
+ {'type': TokenKind.MARGIN_DIRECTIVE_RIGHTBOTTOM, 'value': 'right-bottom'},
+ ];
+
+ static const List<Map<String, Object>> _UNITS = [
+ {'unit': TokenKind.UNIT_EM, 'value': 'em'},
+ {'unit': TokenKind.UNIT_EX, 'value': 'ex'},
+ {'unit': TokenKind.UNIT_LENGTH_PX, 'value': 'px'},
+ {'unit': TokenKind.UNIT_LENGTH_CM, 'value': 'cm'},
+ {'unit': TokenKind.UNIT_LENGTH_MM, 'value': 'mm'},
+ {'unit': TokenKind.UNIT_LENGTH_IN, 'value': 'in'},
+ {'unit': TokenKind.UNIT_LENGTH_PT, 'value': 'pt'},
+ {'unit': TokenKind.UNIT_LENGTH_PC, 'value': 'pc'},
+ {'unit': TokenKind.UNIT_ANGLE_DEG, 'value': 'deg'},
+ {'unit': TokenKind.UNIT_ANGLE_RAD, 'value': 'rad'},
+ {'unit': TokenKind.UNIT_ANGLE_GRAD, 'value': 'grad'},
+ {'unit': TokenKind.UNIT_ANGLE_TURN, 'value': 'turn'},
+ {'unit': TokenKind.UNIT_TIME_MS, 'value': 'ms'},
+ {'unit': TokenKind.UNIT_TIME_S, 'value': 's'},
+ {'unit': TokenKind.UNIT_FREQ_HZ, 'value': 'hz'},
+ {'unit': TokenKind.UNIT_FREQ_KHZ, 'value': 'khz'},
+ {'unit': TokenKind.UNIT_FRACTION, 'value': 'fr'},
+ {'unit': TokenKind.UNIT_RESOLUTION_DPI, 'value': 'dpi'},
+ {'unit': TokenKind.UNIT_RESOLUTION_DPCM, 'value': 'dpcm'},
+ {'unit': TokenKind.UNIT_RESOLUTION_DPPX, 'value': 'dppx'},
+ {'unit': TokenKind.UNIT_CH, 'value': 'ch'},
+ {'unit': TokenKind.UNIT_REM, 'value': 'rem'},
+ {'unit': TokenKind.UNIT_VIEWPORT_VW, 'value': 'vw'},
+ {'unit': TokenKind.UNIT_VIEWPORT_VH, 'value': 'vh'},
+ {'unit': TokenKind.UNIT_VIEWPORT_VMIN, 'value': 'vmin'},
+ {'unit': TokenKind.UNIT_VIEWPORT_VMAX, 'value': 'vmax'},
+ {'unit': TokenKind.UNIT_LH, 'value': 'lh'},
+ {'unit': TokenKind.UNIT_RLH, 'value': 'rlh'},
+ ];
+
+ // Some more constants:
+ static const int ASCII_UPPER_A = 65; // ASCII value for uppercase A
+ static const int ASCII_UPPER_Z = 90; // ASCII value for uppercase Z
+
+ // Extended color keywords:
+ static const List<Map<String, Object>> _EXTENDED_COLOR_NAMES = [
+ {'name': 'aliceblue', 'value': 0xF08FF},
+ {'name': 'antiquewhite', 'value': 0xFAEBD7},
+ {'name': 'aqua', 'value': 0x00FFFF},
+ {'name': 'aquamarine', 'value': 0x7FFFD4},
+ {'name': 'azure', 'value': 0xF0FFFF},
+ {'name': 'beige', 'value': 0xF5F5DC},
+ {'name': 'bisque', 'value': 0xFFE4C4},
+ {'name': 'black', 'value': 0x000000},
+ {'name': 'blanchedalmond', 'value': 0xFFEBCD},
+ {'name': 'blue', 'value': 0x0000FF},
+ {'name': 'blueviolet', 'value': 0x8A2BE2},
+ {'name': 'brown', 'value': 0xA52A2A},
+ {'name': 'burlywood', 'value': 0xDEB887},
+ {'name': 'cadetblue', 'value': 0x5F9EA0},
+ {'name': 'chartreuse', 'value': 0x7FFF00},
+ {'name': 'chocolate', 'value': 0xD2691E},
+ {'name': 'coral', 'value': 0xFF7F50},
+ {'name': 'cornflowerblue', 'value': 0x6495ED},
+ {'name': 'cornsilk', 'value': 0xFFF8DC},
+ {'name': 'crimson', 'value': 0xDC143C},
+ {'name': 'cyan', 'value': 0x00FFFF},
+ {'name': 'darkblue', 'value': 0x00008B},
+ {'name': 'darkcyan', 'value': 0x008B8B},
+ {'name': 'darkgoldenrod', 'value': 0xB8860B},
+ {'name': 'darkgray', 'value': 0xA9A9A9},
+ {'name': 'darkgreen', 'value': 0x006400},
+ {'name': 'darkgrey', 'value': 0xA9A9A9},
+ {'name': 'darkkhaki', 'value': 0xBDB76B},
+ {'name': 'darkmagenta', 'value': 0x8B008B},
+ {'name': 'darkolivegreen', 'value': 0x556B2F},
+ {'name': 'darkorange', 'value': 0xFF8C00},
+ {'name': 'darkorchid', 'value': 0x9932CC},
+ {'name': 'darkred', 'value': 0x8B0000},
+ {'name': 'darksalmon', 'value': 0xE9967A},
+ {'name': 'darkseagreen', 'value': 0x8FBC8F},
+ {'name': 'darkslateblue', 'value': 0x483D8B},
+ {'name': 'darkslategray', 'value': 0x2F4F4F},
+ {'name': 'darkslategrey', 'value': 0x2F4F4F},
+ {'name': 'darkturquoise', 'value': 0x00CED1},
+ {'name': 'darkviolet', 'value': 0x9400D3},
+ {'name': 'deeppink', 'value': 0xFF1493},
+ {'name': 'deepskyblue', 'value': 0x00BFFF},
+ {'name': 'dimgray', 'value': 0x696969},
+ {'name': 'dimgrey', 'value': 0x696969},
+ {'name': 'dodgerblue', 'value': 0x1E90FF},
+ {'name': 'firebrick', 'value': 0xB22222},
+ {'name': 'floralwhite', 'value': 0xFFFAF0},
+ {'name': 'forestgreen', 'value': 0x228B22},
+ {'name': 'fuchsia', 'value': 0xFF00FF},
+ {'name': 'gainsboro', 'value': 0xDCDCDC},
+ {'name': 'ghostwhite', 'value': 0xF8F8FF},
+ {'name': 'gold', 'value': 0xFFD700},
+ {'name': 'goldenrod', 'value': 0xDAA520},
+ {'name': 'gray', 'value': 0x808080},
+ {'name': 'green', 'value': 0x008000},
+ {'name': 'greenyellow', 'value': 0xADFF2F},
+ {'name': 'grey', 'value': 0x808080},
+ {'name': 'honeydew', 'value': 0xF0FFF0},
+ {'name': 'hotpink', 'value': 0xFF69B4},
+ {'name': 'indianred', 'value': 0xCD5C5C},
+ {'name': 'indigo', 'value': 0x4B0082},
+ {'name': 'ivory', 'value': 0xFFFFF0},
+ {'name': 'khaki', 'value': 0xF0E68C},
+ {'name': 'lavender', 'value': 0xE6E6FA},
+ {'name': 'lavenderblush', 'value': 0xFFF0F5},
+ {'name': 'lawngreen', 'value': 0x7CFC00},
+ {'name': 'lemonchiffon', 'value': 0xFFFACD},
+ {'name': 'lightblue', 'value': 0xADD8E6},
+ {'name': 'lightcoral', 'value': 0xF08080},
+ {'name': 'lightcyan', 'value': 0xE0FFFF},
+ {'name': 'lightgoldenrodyellow', 'value': 0xFAFAD2},
+ {'name': 'lightgray', 'value': 0xD3D3D3},
+ {'name': 'lightgreen', 'value': 0x90EE90},
+ {'name': 'lightgrey', 'value': 0xD3D3D3},
+ {'name': 'lightpink', 'value': 0xFFB6C1},
+ {'name': 'lightsalmon', 'value': 0xFFA07A},
+ {'name': 'lightseagreen', 'value': 0x20B2AA},
+ {'name': 'lightskyblue', 'value': 0x87CEFA},
+ {'name': 'lightslategray', 'value': 0x778899},
+ {'name': 'lightslategrey', 'value': 0x778899},
+ {'name': 'lightsteelblue', 'value': 0xB0C4DE},
+ {'name': 'lightyellow', 'value': 0xFFFFE0},
+ {'name': 'lime', 'value': 0x00FF00},
+ {'name': 'limegreen', 'value': 0x32CD32},
+ {'name': 'linen', 'value': 0xFAF0E6},
+ {'name': 'magenta', 'value': 0xFF00FF},
+ {'name': 'maroon', 'value': 0x800000},
+ {'name': 'mediumaquamarine', 'value': 0x66CDAA},
+ {'name': 'mediumblue', 'value': 0x0000CD},
+ {'name': 'mediumorchid', 'value': 0xBA55D3},
+ {'name': 'mediumpurple', 'value': 0x9370DB},
+ {'name': 'mediumseagreen', 'value': 0x3CB371},
+ {'name': 'mediumslateblue', 'value': 0x7B68EE},
+ {'name': 'mediumspringgreen', 'value': 0x00FA9A},
+ {'name': 'mediumturquoise', 'value': 0x48D1CC},
+ {'name': 'mediumvioletred', 'value': 0xC71585},
+ {'name': 'midnightblue', 'value': 0x191970},
+ {'name': 'mintcream', 'value': 0xF5FFFA},
+ {'name': 'mistyrose', 'value': 0xFFE4E1},
+ {'name': 'moccasin', 'value': 0xFFE4B5},
+ {'name': 'navajowhite', 'value': 0xFFDEAD},
+ {'name': 'navy', 'value': 0x000080},
+ {'name': 'oldlace', 'value': 0xFDF5E6},
+ {'name': 'olive', 'value': 0x808000},
+ {'name': 'olivedrab', 'value': 0x6B8E23},
+ {'name': 'orange', 'value': 0xFFA500},
+ {'name': 'orangered', 'value': 0xFF4500},
+ {'name': 'orchid', 'value': 0xDA70D6},
+ {'name': 'palegoldenrod', 'value': 0xEEE8AA},
+ {'name': 'palegreen', 'value': 0x98FB98},
+ {'name': 'paleturquoise', 'value': 0xAFEEEE},
+ {'name': 'palevioletred', 'value': 0xDB7093},
+ {'name': 'papayawhip', 'value': 0xFFEFD5},
+ {'name': 'peachpuff', 'value': 0xFFDAB9},
+ {'name': 'peru', 'value': 0xCD853F},
+ {'name': 'pink', 'value': 0xFFC0CB},
+ {'name': 'plum', 'value': 0xDDA0DD},
+ {'name': 'powderblue', 'value': 0xB0E0E6},
+ {'name': 'purple', 'value': 0x800080},
+ {'name': 'red', 'value': 0xFF0000},
+ {'name': 'rosybrown', 'value': 0xBC8F8F},
+ {'name': 'royalblue', 'value': 0x4169E1},
+ {'name': 'saddlebrown', 'value': 0x8B4513},
+ {'name': 'salmon', 'value': 0xFA8072},
+ {'name': 'sandybrown', 'value': 0xF4A460},
+ {'name': 'seagreen', 'value': 0x2E8B57},
+ {'name': 'seashell', 'value': 0xFFF5EE},
+ {'name': 'sienna', 'value': 0xA0522D},
+ {'name': 'silver', 'value': 0xC0C0C0},
+ {'name': 'skyblue', 'value': 0x87CEEB},
+ {'name': 'slateblue', 'value': 0x6A5ACD},
+ {'name': 'slategray', 'value': 0x708090},
+ {'name': 'slategrey', 'value': 0x708090},
+ {'name': 'snow', 'value': 0xFFFAFA},
+ {'name': 'springgreen', 'value': 0x00FF7F},
+ {'name': 'steelblue', 'value': 0x4682B4},
+ {'name': 'tan', 'value': 0xD2B48C},
+ {'name': 'teal', 'value': 0x008080},
+ {'name': 'thistle', 'value': 0xD8BFD8},
+ {'name': 'tomato', 'value': 0xFF6347},
+ {'name': 'turquoise', 'value': 0x40E0D0},
+ {'name': 'violet', 'value': 0xEE82EE},
+ {'name': 'wheat', 'value': 0xF5DEB3},
+ {'name': 'white', 'value': 0xFFFFFF},
+ {'name': 'whitesmoke', 'value': 0xF5F5F5},
+ {'name': 'yellow', 'value': 0xFFFF00},
+ {'name': 'yellowgreen', 'value': 0x9ACD32},
+ ];
+
+ // TODO(terry): Should used Dart mirroring for parameter values and types
+ // especially for enumeration (e.g., counter's second parameter
+ // is list-style-type which is an enumerated list for ordering
+ // of a list 'circle', 'decimal', 'lower-roman', 'square', etc.
+ // see http://www.w3schools.com/cssref/pr_list-style-type.asp
+ // for list of possible values.
+
+ /// Check if a name is a pre-defined CSS name.
+ ///
+ /// This is used by the error handler to report if a name is unknown or used
+ /// improperly.
+ static bool isPredefinedName(String name) {
+ final len = name.length;
+
+ // TODO(terry): Add more pre-defined names (hidden, bolder, inherit, etc.).
+ if (matchColorName(name) != null) return true;
+ if (matchDirectives(name, 0, len) != -1) return true;
+ if (matchMarginDirectives(name, 0, len) != -1) return true;
+ if (matchUnits(name, 0, len) != -1) return true;
+
+ return false;
+ }
+
+ /// Return the token that matches the unit ident found.
+ static int matchList(List<Map<String, dynamic>> identList, String tokenField,
+ String text, int offset, int length) {
+ for (final entry in identList) {
+ final ident = entry['value'] as String;
+
+ if (length == ident.length) {
+ var idx = offset;
+ var match = true;
+ for (var i = 0; i < ident.length; i++) {
+ var identChar = ident.codeUnitAt(i);
+ var char = text.codeUnitAt(idx++);
+ // Compare lowercase to lowercase then check if char is uppercase.
+ match = match &&
+ (char == identChar ||
+ ((char >= ASCII_UPPER_A && char <= ASCII_UPPER_Z) &&
+ (char + 32) == identChar));
+ if (!match) {
+ break;
+ }
+ }
+
+ if (match) {
+ // Completely matched; return the token for this unit.
+ return entry[tokenField] as int;
+ }
+ }
+ }
+
+ return -1; // Not a unit token.
+ }
+
+ /// Return the token that matches the unit ident found.
+ static int matchUnits(String text, int offset, int length) =>
+ matchList(_UNITS, 'unit', text, offset, length);
+
+ /// Return the token that matches the directive name found.
+ static int matchDirectives(String text, int offset, int length) =>
+ matchList(_DIRECTIVES, 'type', text, offset, length);
+
+ /// Return the token that matches the margin directive name found.
+ static int matchMarginDirectives(String text, int offset, int length) =>
+ matchList(MARGIN_DIRECTIVES, 'type', text, offset, length);
+
+ /// Return the token that matches the media operator found.
+ static int matchMediaOperator(String text, int offset, int length) =>
+ matchList(MEDIA_OPERATORS, 'type', text, offset, length);
+
+ static String? idToValue(Iterable<Object?> identList, int tokenId) {
+ for (var entry in identList) {
+ entry as Map<String, Object?>;
+ if (tokenId == entry['type']) {
+ return entry['value'] as String?;
+ }
+ }
+
+ return null;
+ }
+
+ /// Return the unit token as its pretty name.
+ static String? unitToString(int unitTokenToFind) {
+ if (unitTokenToFind == TokenKind.PERCENT) {
+ return '%';
+ } else {
+ for (final entry in _UNITS) {
+ final unit = entry['unit'] as int;
+ if (unit == unitTokenToFind) {
+ return entry['value'] as String?;
+ }
+ }
+ }
+
+ return '<BAD UNIT>'; // Not a unit token.
+ }
+
+ /// Match color name, case insensitive match and return the associated color
+ /// entry from _EXTENDED_COLOR_NAMES list, return `null` if not found.
+ static Map<String, Object>? matchColorName(String text) {
+ var name = text.toLowerCase();
+ for (var color in _EXTENDED_COLOR_NAMES) {
+ if (color['name'] == name) return color;
+ }
+ return null;
+ }
+
+ /// Return RGB value as [int] from a color entry in _EXTENDED_COLOR_NAMES.
+ static int colorValue(Map<String, Object> entry) => entry['value'] as int;
+
+ static String? hexToColorName(Object hexValue) {
+ for (final entry in _EXTENDED_COLOR_NAMES) {
+ if (entry['value'] == hexValue) {
+ return entry['name'] as String?;
+ }
+ }
+
+ return null;
+ }
+
+ static String decimalToHex(int number, [int minDigits = 1]) {
+ final hexDigits = '0123456789abcdef';
+
+ var result = <String>[];
+
+ var dividend = number >> 4;
+ var remain = number % 16;
+ result.add(hexDigits[remain]);
+ while (dividend != 0) {
+ remain = dividend % 16;
+ dividend >>= 4;
+ result.add(hexDigits[remain]);
+ }
+
+ var invertResult = StringBuffer();
+ var paddings = minDigits - result.length;
+ while (paddings-- > 0) {
+ invertResult.write('0');
+ }
+ for (var i = result.length - 1; i >= 0; i--) {
+ invertResult.write(result[i]);
+ }
+
+ return invertResult.toString();
+ }
+
+ static String kindToString(int kind) => switch (kind) {
+ TokenKind.UNUSED => 'ERROR',
+ TokenKind.END_OF_FILE => 'end of file',
+ TokenKind.LPAREN => '(',
+ TokenKind.RPAREN => ')',
+ TokenKind.LBRACK => '[',
+ TokenKind.RBRACK => ']',
+ TokenKind.LBRACE => '{',
+ TokenKind.RBRACE => '}',
+ TokenKind.DOT => '.',
+ TokenKind.SEMICOLON => ';',
+ TokenKind.AT => '@',
+ TokenKind.HASH => '#',
+ TokenKind.PLUS => '+',
+ TokenKind.GREATER => '>',
+ TokenKind.TILDE => '~',
+ TokenKind.ASTERISK => '*',
+ TokenKind.NAMESPACE => '|',
+ TokenKind.COLON => ':',
+ TokenKind.PRIVATE_NAME => '_',
+ TokenKind.COMMA => ',',
+ TokenKind.SPACE => ' ',
+ TokenKind.TAB => '\t',
+ TokenKind.NEWLINE => '\n',
+ TokenKind.RETURN => '\r',
+ TokenKind.PERCENT => '%',
+ TokenKind.SINGLE_QUOTE => "'",
+ TokenKind.DOUBLE_QUOTE => '"',
+ TokenKind.SLASH => '/',
+ TokenKind.EQUALS => '=',
+ TokenKind.CARET => '^',
+ TokenKind.DOLLAR => '\$',
+ TokenKind.LESS => '<',
+ TokenKind.BANG => '!',
+ TokenKind.MINUS => '-',
+ TokenKind.BACKSLASH => '\\',
+ _ => throw StateError('Unknown TOKEN')
+ };
+
+ static bool isKindIdentifier(int kind) {
+ switch (kind) {
+ // Synthesized tokens.
+ case TokenKind.DIRECTIVE_IMPORT:
+ case TokenKind.DIRECTIVE_MEDIA:
+ case TokenKind.DIRECTIVE_PAGE:
+ case TokenKind.DIRECTIVE_CHARSET:
+ case TokenKind.DIRECTIVE_STYLET:
+ case TokenKind.DIRECTIVE_KEYFRAMES:
+ case TokenKind.DIRECTIVE_WEB_KIT_KEYFRAMES:
+ case TokenKind.DIRECTIVE_MOZ_KEYFRAMES:
+ case TokenKind.DIRECTIVE_MS_KEYFRAMES:
+ case TokenKind.DIRECTIVE_O_KEYFRAMES:
+ case TokenKind.DIRECTIVE_FONTFACE:
+ case TokenKind.DIRECTIVE_NAMESPACE:
+ case TokenKind.DIRECTIVE_HOST:
+ case TokenKind.DIRECTIVE_MIXIN:
+ case TokenKind.DIRECTIVE_INCLUDE:
+ case TokenKind.DIRECTIVE_CONTENT:
+ case TokenKind.UNIT_EM:
+ case TokenKind.UNIT_EX:
+ case TokenKind.UNIT_LENGTH_PX:
+ case TokenKind.UNIT_LENGTH_CM:
+ case TokenKind.UNIT_LENGTH_MM:
+ case TokenKind.UNIT_LENGTH_IN:
+ case TokenKind.UNIT_LENGTH_PT:
+ case TokenKind.UNIT_LENGTH_PC:
+ case TokenKind.UNIT_ANGLE_DEG:
+ case TokenKind.UNIT_ANGLE_RAD:
+ case TokenKind.UNIT_ANGLE_GRAD:
+ case TokenKind.UNIT_TIME_MS:
+ case TokenKind.UNIT_TIME_S:
+ case TokenKind.UNIT_FREQ_HZ:
+ case TokenKind.UNIT_FREQ_KHZ:
+ case TokenKind.UNIT_FRACTION:
+ case TokenKind.UNIT_LH:
+ case TokenKind.UNIT_RLH:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ static bool isIdentifier(int kind) => kind == IDENTIFIER;
+}
+
+// Note: these names should match TokenKind names
+class TokenChar {
+ static const int UNUSED = -1;
+ static const int END_OF_FILE = 0;
+ static const int LPAREN = 0x28; // "(".codeUnitAt(0)
+ static const int RPAREN = 0x29; // ")".codeUnitAt(0)
+ static const int LBRACK = 0x5b; // "[".codeUnitAt(0)
+ static const int RBRACK = 0x5d; // "]".codeUnitAt(0)
+ static const int LBRACE = 0x7b; // "{".codeUnitAt(0)
+ static const int RBRACE = 0x7d; // "}".codeUnitAt(0)
+ static const int DOT = 0x2e; // ".".codeUnitAt(0)
+ static const int SEMICOLON = 0x3b; // ";".codeUnitAt(0)
+ static const int AT = 0x40; // "@".codeUnitAt(0)
+ static const int HASH = 0x23; // "#".codeUnitAt(0)
+ static const int PLUS = 0x2b; // "+".codeUnitAt(0)
+ static const int GREATER = 0x3e; // ">".codeUnitAt(0)
+ static const int TILDE = 0x7e; // "~".codeUnitAt(0)
+ static const int ASTERISK = 0x2a; // "*".codeUnitAt(0)
+ static const int NAMESPACE = 0x7c; // "|".codeUnitAt(0)
+ static const int COLON = 0x3a; // ":".codeUnitAt(0)
+ static const int PRIVATE_NAME = 0x5f; // "_".codeUnitAt(0)
+ static const int COMMA = 0x2c; // ",".codeUnitAt(0)
+ static const int SPACE = 0x20; // " ".codeUnitAt(0)
+ static const int TAB = 0x9; // "\t".codeUnitAt(0)
+ static const int NEWLINE = 0xa; // "\n".codeUnitAt(0)
+ static const int RETURN = 0xd; // "\r".codeUnitAt(0)
+ static const int BACKSPACE = 0x8; // "/b".codeUnitAt(0)
+ static const int FF = 0xc; // "/f".codeUnitAt(0)
+ static const int VT = 0xb; // "/v".codeUnitAt(0)
+ static const int PERCENT = 0x25; // "%".codeUnitAt(0)
+ static const int SINGLE_QUOTE = 0x27; // "'".codeUnitAt(0)
+ static const int DOUBLE_QUOTE = 0x22; // '"'.codeUnitAt(0)
+ static const int SLASH = 0x2f; // "/".codeUnitAt(0)
+ static const int EQUALS = 0x3d; // "=".codeUnitAt(0)
+ static const int OR = 0x7c; // "|".codeUnitAt(0)
+ static const int CARET = 0x5e; // "^".codeUnitAt(0)
+ static const int DOLLAR = 0x24; // "\$".codeUnitAt(0)
+ static const int LESS = 0x3c; // "<".codeUnitAt(0)
+ static const int BANG = 0x21; // "!".codeUnitAt(0)
+ static const int MINUS = 0x2d; // "-".codeUnitAt(0)
+ static const int BACKSLASH = 0x5c; // "\".codeUnitAt(0)
+ static const int AMPERSAND = 0x26; // "&".codeUnitAt(0)
+}
diff --git a/pkgs/csslib/lib/src/tokenizer.dart b/pkgs/csslib/lib/src/tokenizer.dart
new file mode 100644
index 0000000..d090106
--- /dev/null
+++ b/pkgs/csslib/lib/src/tokenizer.dart
@@ -0,0 +1,471 @@
+// 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.
+
+part of '../parser.dart';
+
+// TODO: We should update the tokenization to follow what's described in the
+// spec: https://www.w3.org/TR/css-syntax-3/#tokenization.
+
+class Tokenizer extends TokenizerBase {
+ /// U+ prefix for unicode characters.
+ // ignore: non_constant_identifier_names
+ final UNICODE_U = 'U'.codeUnitAt(0);
+ // ignore: non_constant_identifier_names
+ final UNICODE_LOWER_U = 'u'.codeUnitAt(0);
+ // ignore: non_constant_identifier_names
+ final UNICODE_PLUS = '+'.codeUnitAt(0);
+
+ // ignore: non_constant_identifier_names
+ final QUESTION_MARK = '?'.codeUnitAt(0);
+
+ /// CDATA keyword.
+ // ignore: non_constant_identifier_names
+ final List<int> CDATA_NAME = 'CDATA'.codeUnits;
+
+ Tokenizer(super.file, super.text, super.skipWhitespace, [super.index]);
+
+ @override
+ Token next({bool unicodeRange = false}) {
+ // keep track of our starting position
+ _startIndex = _index;
+
+ int ch;
+ ch = _nextChar();
+ switch (ch) {
+ case TokenChar.NEWLINE:
+ case TokenChar.RETURN:
+ case TokenChar.SPACE:
+ case TokenChar.TAB:
+ return finishWhitespace();
+ case TokenChar.END_OF_FILE:
+ return _finishToken(TokenKind.END_OF_FILE);
+ case TokenChar.AT:
+ var peekCh = _peekChar();
+ if (TokenizerHelpers.isIdentifierStart(peekCh)) {
+ var oldIndex = _index;
+ var oldStartIndex = _startIndex;
+
+ _startIndex = _index;
+ ch = _nextChar();
+ finishIdentifier();
+
+ // Is it a directive?
+ var tokId = TokenKind.matchDirectives(
+ _text, _startIndex, _index - _startIndex);
+ if (tokId == -1) {
+ // No, is it a margin directive?
+ tokId = TokenKind.matchMarginDirectives(
+ _text, _startIndex, _index - _startIndex);
+ }
+
+ if (tokId != -1) {
+ return _finishToken(tokId);
+ } else {
+ // Didn't find a CSS directive or margin directive so the @name is
+ // probably the Less definition '@name: value_variable_definition'.
+ _startIndex = oldStartIndex;
+ _index = oldIndex;
+ }
+ }
+ return _finishToken(TokenKind.AT);
+ case TokenChar.DOT:
+ var start = _startIndex; // Start where the dot started.
+ if (maybeEatDigit()) {
+ // looks like a number dot followed by digit(s).
+ var number = finishNumber();
+ if (number.kind == TokenKind.INTEGER) {
+ // It's a number but it's preceded by a dot, so make it a double.
+ _startIndex = start;
+ return _finishToken(TokenKind.DOUBLE);
+ } else {
+ // Don't allow dot followed by a double (e.g, '..1').
+ return _errorToken();
+ }
+ }
+ // It's really a dot.
+ return _finishToken(TokenKind.DOT);
+ case TokenChar.LPAREN:
+ return _finishToken(TokenKind.LPAREN);
+ case TokenChar.RPAREN:
+ return _finishToken(TokenKind.RPAREN);
+ case TokenChar.LBRACE:
+ return _finishToken(TokenKind.LBRACE);
+ case TokenChar.RBRACE:
+ return _finishToken(TokenKind.RBRACE);
+ case TokenChar.LBRACK:
+ return _finishToken(TokenKind.LBRACK);
+ case TokenChar.RBRACK:
+ if (_maybeEatChar(TokenChar.RBRACK) &&
+ _maybeEatChar(TokenChar.GREATER)) {
+ // ]]>
+ return next();
+ }
+ return _finishToken(TokenKind.RBRACK);
+ case TokenChar.HASH:
+ return _finishToken(TokenKind.HASH);
+ case TokenChar.PLUS:
+ if (_nextCharsAreNumber(ch)) return finishNumber();
+ return _finishToken(TokenKind.PLUS);
+ case TokenChar.MINUS:
+ if (inSelectorExpression || unicodeRange) {
+ // If parsing in pseudo function expression then minus is an operator
+ // not part of identifier e.g., interval value range (e.g. U+400-4ff)
+ // or minus operator in selector expression.
+ return _finishToken(TokenKind.MINUS);
+ } else if (_nextCharsAreNumber(ch)) {
+ return finishNumber();
+ } else if (TokenizerHelpers.isIdentifierStart(ch)) {
+ return finishIdentifier();
+ }
+ return _finishToken(TokenKind.MINUS);
+ case TokenChar.GREATER:
+ return _finishToken(TokenKind.GREATER);
+ case TokenChar.TILDE:
+ if (_maybeEatChar(TokenChar.EQUALS)) {
+ return _finishToken(TokenKind.INCLUDES); // ~=
+ }
+ return _finishToken(TokenKind.TILDE);
+ case TokenChar.ASTERISK:
+ if (_maybeEatChar(TokenChar.EQUALS)) {
+ return _finishToken(TokenKind.SUBSTRING_MATCH); // *=
+ }
+ return _finishToken(TokenKind.ASTERISK);
+ case TokenChar.AMPERSAND:
+ return _finishToken(TokenKind.AMPERSAND);
+ case TokenChar.NAMESPACE:
+ if (_maybeEatChar(TokenChar.EQUALS)) {
+ return _finishToken(TokenKind.DASH_MATCH); // |=
+ }
+ return _finishToken(TokenKind.NAMESPACE);
+ case TokenChar.COLON:
+ return _finishToken(TokenKind.COLON);
+ case TokenChar.COMMA:
+ return _finishToken(TokenKind.COMMA);
+ case TokenChar.SEMICOLON:
+ return _finishToken(TokenKind.SEMICOLON);
+ case TokenChar.PERCENT:
+ return _finishToken(TokenKind.PERCENT);
+ case TokenChar.SINGLE_QUOTE:
+ return _finishToken(TokenKind.SINGLE_QUOTE);
+ case TokenChar.DOUBLE_QUOTE:
+ return _finishToken(TokenKind.DOUBLE_QUOTE);
+ case TokenChar.SLASH:
+ if (_maybeEatChar(TokenChar.ASTERISK)) return finishMultiLineComment();
+ return _finishToken(TokenKind.SLASH);
+ case TokenChar.LESS: // <!--
+ if (_maybeEatChar(TokenChar.BANG)) {
+ if (_maybeEatChar(TokenChar.MINUS) &&
+ _maybeEatChar(TokenChar.MINUS)) {
+ return finishHtmlComment();
+ } else if (_maybeEatChar(TokenChar.LBRACK) &&
+ _maybeEatChar(CDATA_NAME[0]) &&
+ _maybeEatChar(CDATA_NAME[1]) &&
+ _maybeEatChar(CDATA_NAME[2]) &&
+ _maybeEatChar(CDATA_NAME[3]) &&
+ _maybeEatChar(CDATA_NAME[4]) &&
+ _maybeEatChar(TokenChar.LBRACK)) {
+ // <![CDATA[
+ return next();
+ }
+ }
+ return _finishToken(TokenKind.LESS);
+ case TokenChar.EQUALS:
+ return _finishToken(TokenKind.EQUALS);
+ case TokenChar.CARET:
+ if (_maybeEatChar(TokenChar.EQUALS)) {
+ return _finishToken(TokenKind.PREFIX_MATCH); // ^=
+ }
+ return _finishToken(TokenKind.CARET);
+ case TokenChar.DOLLAR:
+ if (_maybeEatChar(TokenChar.EQUALS)) {
+ return _finishToken(TokenKind.SUFFIX_MATCH); // $=
+ }
+ return _finishToken(TokenKind.DOLLAR);
+ case TokenChar.BANG:
+ return finishIdentifier();
+ default:
+ // TODO(jmesserly): this is used for IE8 detection; I'm not sure it's
+ // appropriate outside of a few specific places; certainly shouldn't
+ // be parsed in selectors.
+ if (!inSelector && ch == TokenChar.BACKSLASH) {
+ return _finishToken(TokenKind.BACKSLASH);
+ }
+
+ if (unicodeRange) {
+ // Three types of unicode ranges:
+ // - single code point (e.g. U+416)
+ // - interval value range (e.g. U+400-4ff)
+ // - range where trailing ‘?’ characters imply ‘any digit value’
+ // (e.g. U+4??)
+ if (maybeEatHexDigit()) {
+ var t = finishHexNumber();
+ // Any question marks then it's a HEX_RANGE not HEX_NUMBER.
+ if (maybeEatQuestionMark()) finishUnicodeRange();
+ return t;
+ } else if (maybeEatQuestionMark()) {
+ // HEX_RANGE U+N???
+ return finishUnicodeRange();
+ } else {
+ return _errorToken();
+ }
+ } else if (_inString &&
+ (ch == UNICODE_U || ch == UNICODE_LOWER_U) &&
+ (_peekChar() == UNICODE_PLUS)) {
+ // `_inString` is misleading. We actually DON'T want to enter this
+ // block while tokenizing a string, but the parser sets this value to
+ // false while it IS consuming tokens within a string.
+ //
+ // Unicode range: U+uNumber[-U+uNumber]
+ // uNumber = 0..10FFFF
+ _nextChar(); // Skip +
+ _startIndex = _index; // Starts at the number
+ return _finishToken(TokenKind.UNICODE_RANGE);
+ } else if (varDef(ch)) {
+ return _finishToken(TokenKind.VAR_DEFINITION);
+ } else if (varUsage(ch)) {
+ return _finishToken(TokenKind.VAR_USAGE);
+ } else if (TokenizerHelpers.isIdentifierStart(ch)) {
+ return finishIdentifier();
+ } else if (TokenizerHelpers.isDigit(ch)) {
+ return finishNumber();
+ }
+ return _errorToken();
+ }
+ }
+
+ bool varDef(int ch) =>
+ ch == 'v'.codeUnitAt(0) &&
+ _maybeEatChar('a'.codeUnitAt(0)) &&
+ _maybeEatChar('r'.codeUnitAt(0)) &&
+ _maybeEatChar('-'.codeUnitAt(0));
+
+ bool varUsage(int ch) =>
+ ch == 'v'.codeUnitAt(0) &&
+ _maybeEatChar('a'.codeUnitAt(0)) &&
+ _maybeEatChar('r'.codeUnitAt(0)) &&
+ (_peekChar() == '-'.codeUnitAt(0));
+
+ @override
+ Token _errorToken([String? message]) => _finishToken(TokenKind.ERROR);
+
+ @override
+ int getIdentifierKind() {
+ // Is the identifier a unit type?
+ var tokId = -1;
+
+ // Don't match units in selectors or selector expressions.
+ if (!inSelectorExpression && !inSelector) {
+ tokId = TokenKind.matchUnits(_text, _startIndex, _index - _startIndex);
+ }
+ if (tokId == -1) {
+ tokId = (_text.substring(_startIndex, _index) == '!important')
+ ? TokenKind.IMPORTANT
+ : -1;
+ }
+
+ return tokId >= 0 ? tokId : TokenKind.IDENTIFIER;
+ }
+
+ Token finishIdentifier() {
+ // If we encounter an escape sequence, remember it so we can post-process
+ // to unescape.
+ var chars = <int>[];
+
+ // backup so we can start with the first character
+ var validateFrom = _index;
+ _index = _startIndex;
+ while (_index < _text.length) {
+ var ch = _text.codeUnitAt(_index);
+
+ // If the previous character was "\" we need to escape. T
+ // http://www.w3.org/TR/CSS21/syndata.html#characters
+ // if followed by hexadecimal digits, create the appropriate character.
+ // otherwise, include the character in the identifier and don't treat it
+ // specially.
+ if (ch == 92 /*\*/ && _inString) {
+ var startHex = ++_index;
+ eatHexDigits(startHex + 6);
+ if (_index != startHex) {
+ // Parse the hex digits and add that character.
+ chars.add(int.parse('0x${_text.substring(startHex, _index)}'));
+
+ if (_index == _text.length) break;
+
+ // if we stopped the hex because of a whitespace char, skip it
+ ch = _text.codeUnitAt(_index);
+ if (_index - startHex != 6 &&
+ (ch == TokenChar.SPACE ||
+ ch == TokenChar.TAB ||
+ ch == TokenChar.RETURN ||
+ ch == TokenChar.NEWLINE)) {
+ _index++;
+ }
+ } else {
+ // not a digit, just add the next character literally
+ if (_index == _text.length) break;
+ chars.add(_text.codeUnitAt(_index++));
+ }
+ } else if (_index < validateFrom ||
+ (inSelectorExpression
+ ? TokenizerHelpers.isIdentifierPartExpr(ch)
+ : TokenizerHelpers.isIdentifierPart(ch))) {
+ chars.add(ch);
+ _index++;
+ } else {
+ // Not an identifier or escaped character.
+ break;
+ }
+ }
+
+ var span = _file.span(_startIndex, _index);
+ var text = String.fromCharCodes(chars);
+
+ return IdentifierToken(text, getIdentifierKind(), span);
+ }
+
+ @override
+ Token finishNumber() {
+ eatDigits();
+
+ if (_peekChar() == 46 /*.*/) {
+ // Handle the case of 1.toString().
+ _nextChar();
+ if (TokenizerHelpers.isDigit(_peekChar())) {
+ eatDigits();
+ return _finishToken(TokenKind.DOUBLE);
+ } else {
+ _index -= 1;
+ }
+ }
+
+ return _finishToken(TokenKind.INTEGER);
+ }
+
+ bool maybeEatDigit() {
+ if (_index < _text.length &&
+ TokenizerHelpers.isDigit(_text.codeUnitAt(_index))) {
+ _index += 1;
+ return true;
+ }
+ return false;
+ }
+
+ Token finishHexNumber() {
+ eatHexDigits(_text.length);
+ return _finishToken(TokenKind.HEX_INTEGER);
+ }
+
+ void eatHexDigits(int end) {
+ end = math.min(end, _text.length);
+ while (_index < end) {
+ if (TokenizerHelpers.isHexDigit(_text.codeUnitAt(_index))) {
+ _index += 1;
+ } else {
+ return;
+ }
+ }
+ }
+
+ bool maybeEatHexDigit() {
+ if (_index < _text.length &&
+ TokenizerHelpers.isHexDigit(_text.codeUnitAt(_index))) {
+ _index += 1;
+ return true;
+ }
+ return false;
+ }
+
+ bool maybeEatQuestionMark() {
+ if (_index < _text.length && _text.codeUnitAt(_index) == QUESTION_MARK) {
+ _index += 1;
+ return true;
+ }
+ return false;
+ }
+
+ void eatQuestionMarks() {
+ while (_index < _text.length) {
+ if (_text.codeUnitAt(_index) == QUESTION_MARK) {
+ _index += 1;
+ } else {
+ return;
+ }
+ }
+ }
+
+ Token finishUnicodeRange() {
+ eatQuestionMarks();
+ return _finishToken(TokenKind.HEX_RANGE);
+ }
+
+ Token finishHtmlComment() {
+ while (true) {
+ var ch = _nextChar();
+ if (ch == 0) {
+ return _finishToken(TokenKind.INCOMPLETE_COMMENT);
+ } else if (ch == TokenChar.MINUS) {
+ /* Check if close part of Comment Definition --> (CDC). */
+ if (_maybeEatChar(TokenChar.MINUS)) {
+ if (_maybeEatChar(TokenChar.GREATER)) {
+ if (_inString) {
+ return next();
+ } else {
+ return _finishToken(TokenKind.HTML_COMMENT);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @override
+ Token finishMultiLineComment() {
+ while (true) {
+ var ch = _nextChar();
+ if (ch == 0) {
+ return _finishToken(TokenKind.INCOMPLETE_COMMENT);
+ } else if (ch == 42 /*'*'*/) {
+ if (_maybeEatChar(47 /*'/'*/)) {
+ if (_inString) {
+ return next();
+ } else {
+ return _finishToken(TokenKind.COMMENT);
+ }
+ }
+ }
+ }
+ }
+}
+
+/// Static helper methods.
+class TokenizerHelpers {
+ static bool isIdentifierStart(int c) =>
+ isIdentifierStartExpr(c) || c == 45 /*-*/;
+
+ static bool isDigit(int c) => c >= 48 /*0*/ && c <= 57 /*9*/;
+
+ static bool isHexDigit(int c) =>
+ isDigit(c) ||
+ (c >= 97 /*a*/ && c <= 102 /*f*/) ||
+ (c >= 65 /*A*/ && c <= 70 /*F*/);
+
+ static bool isIdentifierPart(int c) =>
+ isIdentifierPartExpr(c) || c == 45 /*-*/;
+
+ /// Pseudo function expressions identifiers can't have a minus sign.
+ static bool isIdentifierStartExpr(int c) =>
+ (c >= 97 /*a*/ && c <= 122 /*z*/) ||
+ (c >= 65 /*A*/ && c <= 90 /*Z*/) ||
+ // Note: Unicode 10646 chars U+00A0 or higher are allowed, see:
+ // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
+ // http://www.w3.org/TR/CSS21/syndata.html#characters
+ // Also, escaped character should be allowed.
+ c == 95 /*_*/ ||
+ c >= 0xA0 ||
+ c == 92 /*\*/;
+
+ /// Pseudo function expressions identifiers can't have a minus sign.
+ static bool isIdentifierPartExpr(int c) =>
+ isIdentifierStartExpr(c) || isDigit(c);
+}
diff --git a/pkgs/csslib/lib/src/tokenizer_base.dart b/pkgs/csslib/lib/src/tokenizer_base.dart
new file mode 100644
index 0000000..bcac7be
--- /dev/null
+++ b/pkgs/csslib/lib/src/tokenizer_base.dart
@@ -0,0 +1,419 @@
+// 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.
+// Generated by scripts/tokenizer_gen.py.
+
+part of '../parser.dart';
+
+/// Tokenizer state to support look ahead for Less' nested selectors.
+class TokenizerState {
+ final int index;
+ final int startIndex;
+ final bool inSelectorExpression;
+ final bool inSelector;
+
+ TokenizerState(TokenizerBase base)
+ : index = base._index,
+ startIndex = base._startIndex,
+ inSelectorExpression = base.inSelectorExpression,
+ inSelector = base.inSelector;
+}
+
+/// The base class for our tokenizer. The hand coded parts are in this file,
+/// with the generated parts in the subclass Tokenizer.
+abstract class TokenizerBase {
+ final SourceFile _file;
+ final String _text;
+
+ // TODO: this seems like a bug – this field *is* used
+ // ignore: prefer_final_fields
+ bool _inString;
+
+ /// Changes tokenization when in a pseudo function expression. If true then
+ /// minus signs are handled as operators instead of identifiers.
+ bool inSelectorExpression = false;
+
+ /// Changes tokenization when in selectors. If true, it prevents identifiers
+ /// from being treated as units. This would break things like ":lang(fr)" or
+ /// the HTML (unknown) tag name "px", which is legal to use in a selector.
+ // TODO(jmesserly): is this a problem elsewhere? "fr" for example will be
+ // processed as a "fraction" unit token, preventing it from working in
+ // places where an identifier is expected. This was breaking selectors like:
+ // :lang(fr)
+ // The assumption that "fr" always means fraction (and similar issue with
+ // other units) doesn't seem valid. We probably should defer this
+ // analysis until we reach places in the parser where units are expected.
+ // I'm not sure this is tokenizing as described in the specs:
+ // http://dev.w3.org/csswg/css-syntax/
+ // http://dev.w3.org/csswg/selectors4/
+ bool inSelector = false;
+
+ int _index = 0;
+ int _startIndex = 0;
+
+ TokenizerBase(this._file, this._text, this._inString, [this._index = 0]);
+
+ Token next();
+ int getIdentifierKind();
+
+ /// Snapshot of Tokenizer scanning state.
+ TokenizerState get mark => TokenizerState(this);
+
+ /// Restore Tokenizer scanning state.
+ void restore(TokenizerState markedData) {
+ _index = markedData.index;
+ _startIndex = markedData.startIndex;
+ inSelectorExpression = markedData.inSelectorExpression;
+ inSelector = markedData.inSelector;
+ }
+
+ int _nextChar() {
+ if (_index < _text.length) {
+ return _text.codeUnitAt(_index++);
+ } else {
+ return 0;
+ }
+ }
+
+ int _peekChar([int offset = 0]) {
+ if (_index + offset < _text.length) {
+ return _text.codeUnitAt(_index + offset);
+ } else {
+ return 0;
+ }
+ }
+
+ bool _maybeEatChar(int ch) {
+ if (_index < _text.length) {
+ if (_text.codeUnitAt(_index) == ch) {
+ _index++;
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ bool _nextCharsAreNumber(int first) {
+ if (TokenizerHelpers.isDigit(first)) return true;
+ var second = _peekChar();
+ if (first == TokenChar.DOT) return TokenizerHelpers.isDigit(second);
+ if (first == TokenChar.PLUS || first == TokenChar.MINUS) {
+ return TokenizerHelpers.isDigit(second) ||
+ (second == TokenChar.DOT && TokenizerHelpers.isDigit(_peekChar(1)));
+ }
+ return false;
+ }
+
+ Token _finishToken(int kind) => Token(kind, _file.span(_startIndex, _index));
+
+ Token _errorToken([String? message]) =>
+ ErrorToken(TokenKind.ERROR, _file.span(_startIndex, _index), message);
+
+ Token finishWhitespace() {
+ _index--;
+ while (_index < _text.length) {
+ final ch = _text.codeUnitAt(_index++);
+ if (ch == TokenChar.SPACE ||
+ ch == TokenChar.TAB ||
+ ch == TokenChar.RETURN) {
+ // do nothing
+ } else if (ch == TokenChar.NEWLINE) {
+ if (!_inString) {
+ return _finishToken(TokenKind.WHITESPACE); // note the newline?
+ }
+ } else {
+ _index--;
+ if (_inString) {
+ return next();
+ } else {
+ return _finishToken(TokenKind.WHITESPACE);
+ }
+ }
+ }
+ return _finishToken(TokenKind.END_OF_FILE);
+ }
+
+ Token finishMultiLineComment() {
+ var nesting = 1;
+ do {
+ var ch = _nextChar();
+ if (ch == 0) {
+ return _errorToken();
+ } else if (ch == TokenChar.ASTERISK) {
+ if (_maybeEatChar(TokenChar.SLASH)) {
+ nesting--;
+ }
+ } else if (ch == TokenChar.SLASH) {
+ if (_maybeEatChar(TokenChar.ASTERISK)) {
+ nesting++;
+ }
+ }
+ } while (nesting > 0);
+
+ if (_inString) {
+ return next();
+ } else {
+ return _finishToken(TokenKind.COMMENT);
+ }
+ }
+
+ void eatDigits() {
+ while (_index < _text.length) {
+ if (TokenizerHelpers.isDigit(_text.codeUnitAt(_index))) {
+ _index++;
+ } else {
+ return;
+ }
+ }
+ }
+
+ static int _hexDigit(int c) {
+ if (c >= 48 /*0*/ && c <= 57 /*9*/) {
+ return c - 48;
+ } else if (c >= 97 /*a*/ && c <= 102 /*f*/) {
+ return c - 87;
+ } else if (c >= 65 /*A*/ && c <= 70 /*F*/) {
+ return c - 55;
+ } else {
+ return -1;
+ }
+ }
+
+ int readHex([int? hexLength]) {
+ int maxIndex;
+ if (hexLength == null) {
+ maxIndex = _text.length - 1;
+ } else {
+ // TODO(jimhug): What if this is too long?
+ maxIndex = _index + hexLength;
+ if (maxIndex >= _text.length) return -1;
+ }
+ var result = 0;
+ while (_index < maxIndex) {
+ final digit = _hexDigit(_text.codeUnitAt(_index));
+ if (digit == -1) {
+ if (hexLength == null) {
+ return result;
+ } else {
+ return -1;
+ }
+ }
+ _hexDigit(_text.codeUnitAt(_index));
+ // Multiply by 16 rather than shift by 4 since that will result in a
+ // correct value for numbers that exceed the 32 bit precision of JS
+ // 'integers'.
+ // TODO: Figure out a better solution to integer truncation. Issue 638.
+ result = (result * 16) + digit;
+ _index++;
+ }
+
+ return result;
+ }
+
+ Token finishNumber() {
+ eatDigits();
+
+ if (_peekChar() == TokenChar.DOT) {
+ // Handle the case of 1.toString().
+ _nextChar();
+ if (TokenizerHelpers.isDigit(_peekChar())) {
+ eatDigits();
+ return finishNumberExtra(TokenKind.DOUBLE);
+ } else {
+ _index--;
+ }
+ }
+
+ return finishNumberExtra(TokenKind.INTEGER);
+ }
+
+ Token finishNumberExtra(int kind) {
+ if (_maybeEatChar(101 /*e*/) || _maybeEatChar(69 /*E*/)) {
+ kind = TokenKind.DOUBLE;
+ _maybeEatChar(TokenKind.MINUS);
+ _maybeEatChar(TokenKind.PLUS);
+ eatDigits();
+ }
+ if (_peekChar() != 0 && TokenizerHelpers.isIdentifierStart(_peekChar())) {
+ _nextChar();
+ return _errorToken('illegal character in number');
+ }
+
+ return _finishToken(kind);
+ }
+
+ Token _makeStringToken(List<int> buf, bool isPart) {
+ final s = String.fromCharCodes(buf);
+ final kind = isPart ? TokenKind.STRING_PART : TokenKind.STRING;
+ return LiteralToken(kind, _file.span(_startIndex, _index), s);
+ }
+
+ Token makeIEFilter(int start, int end) {
+ var filter = _text.substring(start, end);
+ return LiteralToken(TokenKind.STRING, _file.span(start, end), filter);
+ }
+
+ Token _makeRawStringToken(bool isMultiline) {
+ String s;
+ if (isMultiline) {
+ // Skip initial newline in multiline strings
+ var start = _startIndex + 4;
+ if (_text[start] == '\n') start++;
+ s = _text.substring(start, _index - 3);
+ } else {
+ s = _text.substring(_startIndex + 2, _index - 1);
+ }
+ return LiteralToken(TokenKind.STRING, _file.span(_startIndex, _index), s);
+ }
+
+ Token finishMultilineString(int quote) {
+ var buf = <int>[];
+ while (true) {
+ var ch = _nextChar();
+ if (ch == 0) {
+ return _errorToken();
+ } else if (ch == quote) {
+ if (_maybeEatChar(quote)) {
+ if (_maybeEatChar(quote)) {
+ return _makeStringToken(buf, false);
+ }
+ buf.add(quote);
+ }
+ buf.add(quote);
+ } else if (ch == TokenChar.BACKSLASH) {
+ var escapeVal = readEscapeSequence();
+ if (escapeVal == -1) {
+ return _errorToken('invalid hex escape sequence');
+ } else {
+ buf.add(escapeVal);
+ }
+ } else {
+ buf.add(ch);
+ }
+ }
+ }
+
+ Token finishString(int quote) {
+ if (_maybeEatChar(quote)) {
+ if (_maybeEatChar(quote)) {
+ // skip an initial newline
+ _maybeEatChar(TokenChar.NEWLINE);
+ return finishMultilineString(quote);
+ } else {
+ return _makeStringToken(<int>[], false);
+ }
+ }
+ return finishStringBody(quote);
+ }
+
+ Token finishRawString(int quote) {
+ if (_maybeEatChar(quote)) {
+ if (_maybeEatChar(quote)) {
+ return finishMultilineRawString(quote);
+ } else {
+ return _makeStringToken(<int>[], false);
+ }
+ }
+ while (true) {
+ var ch = _nextChar();
+ if (ch == quote) {
+ return _makeRawStringToken(false);
+ } else if (ch == 0) {
+ return _errorToken();
+ }
+ }
+ }
+
+ Token finishMultilineRawString(int quote) {
+ while (true) {
+ var ch = _nextChar();
+ if (ch == 0) {
+ return _errorToken();
+ } else if (ch == quote && _maybeEatChar(quote) && _maybeEatChar(quote)) {
+ return _makeRawStringToken(true);
+ }
+ }
+ }
+
+ Token finishStringBody(int quote) {
+ var buf = <int>[];
+ while (true) {
+ var ch = _nextChar();
+ if (ch == quote) {
+ return _makeStringToken(buf, false);
+ } else if (ch == 0) {
+ return _errorToken();
+ } else if (ch == TokenChar.BACKSLASH) {
+ var escapeVal = readEscapeSequence();
+ if (escapeVal == -1) {
+ return _errorToken('invalid hex escape sequence');
+ } else {
+ buf.add(escapeVal);
+ }
+ } else {
+ buf.add(ch);
+ }
+ }
+ }
+
+ int readEscapeSequence() {
+ final ch = _nextChar();
+ int hexValue;
+ switch (ch) {
+ case 110 /*n*/ :
+ return TokenChar.NEWLINE;
+ case 114 /*r*/ :
+ return TokenChar.RETURN;
+ case 102 /*f*/ :
+ return TokenChar.FF;
+ case 98 /*b*/ :
+ return TokenChar.BACKSPACE;
+ case 116 /*t*/ :
+ return TokenChar.TAB;
+ case 118 /*v*/ :
+ return TokenChar.FF;
+ case 120 /*x*/ :
+ hexValue = readHex(2);
+ break;
+ case 117 /*u*/ :
+ if (_maybeEatChar(TokenChar.LBRACE)) {
+ hexValue = readHex();
+ if (!_maybeEatChar(TokenChar.RBRACE)) {
+ return -1;
+ }
+ } else {
+ hexValue = readHex(4);
+ }
+ break;
+ default:
+ return ch;
+ }
+
+ if (hexValue == -1) return -1;
+
+ // According to the Unicode standard the high and low surrogate halves
+ // used by UTF-16 (U+D800 through U+DFFF) and values above U+10FFFF
+ // are not legal Unicode values.
+ if (hexValue < 0xD800 || hexValue > 0xDFFF && hexValue <= 0xFFFF) {
+ return hexValue;
+ } else if (hexValue <= 0x10FFFF) {
+ messages.error('unicode values greater than 2 bytes not implemented yet',
+ _file.span(_startIndex, _startIndex + 1));
+ return -1;
+ } else {
+ return -1;
+ }
+ }
+
+ Token finishDot() {
+ if (TokenizerHelpers.isDigit(_peekChar())) {
+ eatDigits();
+ return finishNumberExtra(TokenKind.DOUBLE);
+ } else {
+ return _finishToken(TokenKind.DOT);
+ }
+ }
+}
diff --git a/pkgs/csslib/lib/src/tree.dart b/pkgs/csslib/lib/src/tree.dart
new file mode 100644
index 0000000..94655cf
--- /dev/null
+++ b/pkgs/csslib/lib/src/tree.dart
@@ -0,0 +1,1775 @@
+// 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.
+
+// ignore_for_file: prefer_asserts_in_initializer_lists
+
+part of '../visitor.dart';
+
+/////////////////////////////////////////////////////////////////////////
+// CSS specific types:
+/////////////////////////////////////////////////////////////////////////
+
+class Identifier extends TreeNode {
+ String name;
+
+ Identifier(this.name, SourceSpan? span) : super(span);
+
+ @override
+ Identifier clone() => Identifier(name, span);
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitIdentifier(this);
+
+ @override
+ String toString() =>
+ // Try to use the identifier's original lexeme to preserve any escape
+ // codes as authored. The name, which may include escaped values, may no
+ // longer be a valid identifier.
+ span?.text ?? name;
+}
+
+class Wildcard extends TreeNode {
+ Wildcard(super.span);
+ @override
+ Wildcard clone() => Wildcard(span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitWildcard(this);
+
+ String get name => '*';
+}
+
+class ThisOperator extends TreeNode {
+ ThisOperator(super.span);
+ @override
+ ThisOperator clone() => ThisOperator(span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitThisOperator(this);
+
+ String get name => '&';
+}
+
+class Negation extends TreeNode {
+ Negation(super.span);
+ @override
+ Negation clone() => Negation(span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitNegation(this);
+
+ String get name => 'not';
+}
+
+// calc(...)
+// TODO(terry): Hack to handle calc however the expressions should be fully
+// parsed and in the AST.
+class CalcTerm extends LiteralTerm {
+ final LiteralTerm expr;
+
+ CalcTerm(Object value, String text, this.expr, SourceSpan? span)
+ : super(value, text, span);
+
+ @override
+ CalcTerm clone() => CalcTerm(value, text, expr.clone(), span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitCalcTerm(this);
+
+ @override
+ String toString() => '$text($expr)';
+}
+
+// /* .... */
+class CssComment extends TreeNode {
+ final String comment;
+
+ CssComment(this.comment, SourceSpan? span) : super(span);
+ @override
+ CssComment clone() => CssComment(comment, span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitCssComment(this);
+}
+
+// CDO/CDC (Comment Definition Open <!-- and Comment Definition Close -->).
+class CommentDefinition extends CssComment {
+ CommentDefinition(super.comment, super.span);
+ @override
+ CommentDefinition clone() => CommentDefinition(comment, span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitCommentDefinition(this);
+}
+
+class SelectorGroup extends TreeNode {
+ final List<Selector> selectors;
+
+ SelectorGroup(this.selectors, SourceSpan? span) : super(span);
+
+ @override
+ SelectorGroup clone() => SelectorGroup(selectors, span);
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitSelectorGroup(this);
+}
+
+class Selector extends TreeNode {
+ final List<SimpleSelectorSequence> simpleSelectorSequences;
+
+ Selector(this.simpleSelectorSequences, SourceSpan? span) : super(span);
+
+ void add(SimpleSelectorSequence seq) => simpleSelectorSequences.add(seq);
+
+ int get length => simpleSelectorSequences.length;
+
+ @override
+ Selector clone() {
+ var simpleSequences =
+ simpleSelectorSequences.map((ss) => ss.clone()).toList();
+
+ return Selector(simpleSequences, span);
+ }
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitSelector(this);
+}
+
+class SimpleSelectorSequence extends TreeNode {
+ /// +, >, ~, NONE
+ int combinator;
+ final SimpleSelector simpleSelector;
+
+ SimpleSelectorSequence(this.simpleSelector, SourceSpan? span,
+ [this.combinator = TokenKind.COMBINATOR_NONE])
+ : super(span);
+
+ bool get isCombinatorNone => combinator == TokenKind.COMBINATOR_NONE;
+ bool get isCombinatorPlus => combinator == TokenKind.COMBINATOR_PLUS;
+ bool get isCombinatorGreater => combinator == TokenKind.COMBINATOR_GREATER;
+ bool get isCombinatorTilde => combinator == TokenKind.COMBINATOR_TILDE;
+ bool get isCombinatorDescendant =>
+ combinator == TokenKind.COMBINATOR_DESCENDANT;
+
+ String get _combinatorToString => switch (combinator) {
+ TokenKind.COMBINATOR_DESCENDANT => ' ',
+ TokenKind.COMBINATOR_GREATER => ' > ',
+ TokenKind.COMBINATOR_PLUS => ' + ',
+ TokenKind.COMBINATOR_TILDE => ' ~ ',
+ _ => ''
+ };
+
+ @override
+ SimpleSelectorSequence clone() =>
+ SimpleSelectorSequence(simpleSelector, span, combinator);
+
+ @override
+ dynamic visit(VisitorBase visitor) =>
+ visitor.visitSimpleSelectorSequence(this);
+
+ @override
+ String toString() => simpleSelector.name;
+}
+
+// All other selectors (element, #id, .class, attribute, pseudo, negation,
+// namespace, *) are derived from this selector.
+abstract class SimpleSelector extends TreeNode {
+ final dynamic _name; // Wildcard, ThisOperator, Identifier, Negation, others?
+
+ SimpleSelector(this._name, SourceSpan? span) : super(span);
+
+ // TOOD(srawlins): Figure this one out.
+ // ignore: avoid_dynamic_calls
+ String get name => _name.name as String;
+
+ bool get isWildcard => _name is Wildcard;
+
+ bool get isThis => _name is ThisOperator;
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitSimpleSelector(this);
+}
+
+// element name
+class ElementSelector extends SimpleSelector {
+ ElementSelector(super.name, super.span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitElementSelector(this);
+
+ @override
+ ElementSelector clone() => ElementSelector(_name, span);
+
+ @override
+ String toString() => name;
+}
+
+// namespace|element
+class NamespaceSelector extends SimpleSelector {
+ final dynamic _namespace; // null, Wildcard or Identifier
+
+ NamespaceSelector(this._namespace, Object name, SourceSpan? span)
+ : super(name, span);
+
+ String get namespace => _namespace is Wildcard
+ ? '*'
+ : _namespace == null
+ ? ''
+ : (_namespace as Identifier).name;
+
+ bool get isNamespaceWildcard => _namespace is Wildcard;
+
+ SimpleSelector? get nameAsSimpleSelector => _name as SimpleSelector?;
+
+ @override
+ NamespaceSelector clone() => NamespaceSelector(_namespace, '', span);
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitNamespaceSelector(this);
+
+ @override
+ String toString() => '$namespace|${nameAsSimpleSelector!.name}';
+}
+
+// [attr op value]
+class AttributeSelector extends SimpleSelector {
+ final int _op;
+ final dynamic value;
+
+ AttributeSelector(Identifier name, this._op, this.value, SourceSpan? span)
+ : super(name, span);
+
+ int get operatorKind => _op;
+
+ String? matchOperator() => switch (_op) {
+ TokenKind.EQUALS => '=',
+ TokenKind.INCLUDES => '~=',
+ TokenKind.DASH_MATCH => '|=',
+ TokenKind.PREFIX_MATCH => '^=',
+ TokenKind.SUFFIX_MATCH => '\$=',
+ TokenKind.SUBSTRING_MATCH => '*=',
+ TokenKind.NO_MATCH => '',
+ _ => null
+ };
+
+ // Return the TokenKind for operator used by visitAttributeSelector.
+ String? matchOperatorAsTokenString() => switch (_op) {
+ TokenKind.EQUALS => 'EQUALS',
+ TokenKind.INCLUDES => 'INCLUDES',
+ TokenKind.DASH_MATCH => 'DASH_MATCH',
+ TokenKind.PREFIX_MATCH => 'PREFIX_MATCH',
+ TokenKind.SUFFIX_MATCH => 'SUFFIX_MATCH',
+ TokenKind.SUBSTRING_MATCH => 'SUBSTRING_MATCH',
+ _ => null
+ };
+
+ String valueToString() {
+ if (value != null) {
+ if (value is Identifier) {
+ return value.toString();
+ } else {
+ return '"$value"';
+ }
+ } else {
+ return '';
+ }
+ }
+
+ @override
+ AttributeSelector clone() =>
+ AttributeSelector(_name as Identifier, _op, value, span);
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitAttributeSelector(this);
+
+ @override
+ String toString() => '[$name${matchOperator()}${valueToString()}]';
+}
+
+// #id
+class IdSelector extends SimpleSelector {
+ IdSelector(Identifier super.name, super.span);
+ @override
+ IdSelector clone() => IdSelector(_name as Identifier, span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitIdSelector(this);
+
+ @override
+ String toString() => '#$_name';
+}
+
+// .class
+class ClassSelector extends SimpleSelector {
+ ClassSelector(Identifier super.name, super.span);
+ @override
+ ClassSelector clone() => ClassSelector(_name as Identifier, span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitClassSelector(this);
+
+ @override
+ String toString() => '.$_name';
+}
+
+// :pseudoClass
+class PseudoClassSelector extends SimpleSelector {
+ PseudoClassSelector(Identifier super.name, super.span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitPseudoClassSelector(this);
+
+ @override
+ PseudoClassSelector clone() => PseudoClassSelector(_name as Identifier, span);
+
+ @override
+ String toString() => ':$name';
+}
+
+// ::pseudoElement
+class PseudoElementSelector extends SimpleSelector {
+ // If true, this is a CSS2.1 pseudo-element with only a single ':'.
+ final bool isLegacy;
+
+ PseudoElementSelector(Identifier super.name, super.span,
+ {this.isLegacy = false});
+ @override
+ dynamic visit(VisitorBase visitor) =>
+ visitor.visitPseudoElementSelector(this);
+
+ @override
+ PseudoElementSelector clone() =>
+ PseudoElementSelector(_name as Identifier, span);
+
+ @override
+ String toString() => "${isLegacy ? ':' : '::'}$name";
+}
+
+// :pseudoClassFunction(argument)
+class PseudoClassFunctionSelector extends PseudoClassSelector {
+ final TreeNode argument; // Selector, SelectorExpression
+
+ PseudoClassFunctionSelector(Identifier name, this.argument, SourceSpan? span)
+ : super(name, span);
+
+ @override
+ PseudoClassFunctionSelector clone() =>
+ PseudoClassFunctionSelector(_name as Identifier, argument, span);
+
+ Selector get selector => argument as Selector;
+ SelectorExpression get expression => argument as SelectorExpression;
+
+ @override
+ dynamic visit(VisitorBase visitor) =>
+ visitor.visitPseudoClassFunctionSelector(this);
+}
+
+// ::pseudoElementFunction(expression)
+class PseudoElementFunctionSelector extends PseudoElementSelector {
+ final SelectorExpression expression;
+
+ PseudoElementFunctionSelector(
+ Identifier name, this.expression, SourceSpan? span)
+ : super(name, span);
+
+ @override
+ PseudoElementFunctionSelector clone() =>
+ PseudoElementFunctionSelector(_name as Identifier, expression, span);
+
+ @override
+ dynamic visit(VisitorBase visitor) =>
+ visitor.visitPseudoElementFunctionSelector(this);
+}
+
+class SelectorExpression extends TreeNode {
+ final List<Expression> expressions;
+
+ SelectorExpression(this.expressions, SourceSpan? span) : super(span);
+
+ @override
+ SourceSpan get span => super.span!;
+
+ @override
+ SelectorExpression clone() =>
+ SelectorExpression(expressions.map((e) => e.clone()).toList(), span);
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitSelectorExpression(this);
+}
+
+// :NOT(negation_arg)
+class NegationSelector extends SimpleSelector {
+ final SimpleSelector? negationArg;
+
+ NegationSelector(this.negationArg, SourceSpan? span)
+ : super(Negation(span), span);
+
+ @override
+ NegationSelector clone() => NegationSelector(negationArg, span);
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitNegationSelector(this);
+}
+
+class NoOp extends TreeNode {
+ NoOp() : super(null);
+
+ @override
+ NoOp clone() => NoOp();
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitNoOp(this);
+}
+
+class StyleSheet extends TreeNode {
+ /// Contains charset, ruleset, directives (media, page, etc.), and selectors.
+ final List<TreeNode> topLevels;
+
+ StyleSheet(this.topLevels, SourceSpan? span) : super(span) {
+ for (final node in topLevels) {
+ assert(node is TopLevelProduction || node is Directive);
+ }
+ }
+
+ /// Selectors only in this tree.
+ StyleSheet.selector(this.topLevels, SourceSpan? span) : super(span);
+
+ @override
+ SourceSpan get span => super.span!;
+
+ @override
+ StyleSheet clone() {
+ var clonedTopLevels = topLevels.map((e) => e.clone()).toList();
+ return StyleSheet(clonedTopLevels, span);
+ }
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitStyleSheet(this);
+}
+
+class TopLevelProduction extends TreeNode {
+ TopLevelProduction(super.span);
+ @override
+ SourceSpan get span => super.span!;
+ @override
+ TopLevelProduction clone() => TopLevelProduction(span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitTopLevelProduction(this);
+}
+
+class RuleSet extends TopLevelProduction {
+ final SelectorGroup? selectorGroup;
+ final DeclarationGroup declarationGroup;
+
+ RuleSet(this.selectorGroup, this.declarationGroup, SourceSpan? span)
+ : super(span);
+
+ @override
+ RuleSet clone() {
+ var cloneSelectorGroup = selectorGroup!.clone();
+ var cloneDeclarationGroup = declarationGroup.clone();
+ return RuleSet(cloneSelectorGroup, cloneDeclarationGroup, span);
+ }
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitRuleSet(this);
+}
+
+class Directive extends TreeNode {
+ Directive(super.span);
+
+ bool get isBuiltIn => true; // Known CSS directive?
+ bool get isExtension => false; // SCSS extension?
+
+ @override
+ SourceSpan get span => super.span!;
+
+ @override
+ Directive clone() => Directive(span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitDirective(this);
+}
+
+class DocumentDirective extends Directive {
+ final List<LiteralTerm> functions;
+ final List<TreeNode> groupRuleBody;
+
+ DocumentDirective(this.functions, this.groupRuleBody, SourceSpan? span)
+ : super(span);
+
+ @override
+ DocumentDirective clone() {
+ var clonedFunctions = <LiteralTerm>[];
+ for (var function in functions) {
+ clonedFunctions.add(function.clone());
+ }
+ var clonedGroupRuleBody = <TreeNode>[];
+ for (var rule in groupRuleBody) {
+ clonedGroupRuleBody.add(rule.clone());
+ }
+ return DocumentDirective(clonedFunctions, clonedGroupRuleBody, span);
+ }
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitDocumentDirective(this);
+}
+
+class SupportsDirective extends Directive {
+ final SupportsCondition? condition;
+ final List<TreeNode> groupRuleBody;
+
+ SupportsDirective(this.condition, this.groupRuleBody, SourceSpan? span)
+ : super(span);
+
+ @override
+ SupportsDirective clone() {
+ var clonedCondition = condition!.clone() as SupportsCondition;
+ var clonedGroupRuleBody = <TreeNode>[];
+ for (var rule in groupRuleBody) {
+ clonedGroupRuleBody.add(rule.clone());
+ }
+ return SupportsDirective(clonedCondition, clonedGroupRuleBody, span);
+ }
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitSupportsDirective(this);
+}
+
+abstract class SupportsCondition extends TreeNode {
+ SupportsCondition(super.span);
+ @override
+ SourceSpan get span => super.span!;
+}
+
+class SupportsConditionInParens extends SupportsCondition {
+ /// A [Declaration] or nested [SupportsCondition].
+ final TreeNode? condition;
+
+ SupportsConditionInParens(Declaration? declaration, super.span)
+ : condition = declaration;
+
+ SupportsConditionInParens.nested(this.condition, SourceSpan? span)
+ : super(span);
+
+ @override
+ SupportsConditionInParens clone() =>
+ SupportsConditionInParens(condition!.clone() as Declaration, span);
+
+ @override
+ dynamic visit(VisitorBase visitor) =>
+ visitor.visitSupportsConditionInParens(this);
+}
+
+class SupportsNegation extends SupportsCondition {
+ final SupportsConditionInParens condition;
+
+ SupportsNegation(this.condition, SourceSpan? span) : super(span);
+
+ @override
+ SupportsNegation clone() => SupportsNegation(condition.clone(), span);
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitSupportsNegation(this);
+}
+
+class SupportsConjunction extends SupportsCondition {
+ final List<SupportsConditionInParens> conditions;
+
+ SupportsConjunction(this.conditions, SourceSpan? span) : super(span);
+
+ @override
+ SupportsConjunction clone() {
+ var clonedConditions = <SupportsConditionInParens>[];
+ for (var condition in conditions) {
+ clonedConditions.add(condition.clone());
+ }
+ return SupportsConjunction(clonedConditions, span);
+ }
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitSupportsConjunction(this);
+}
+
+class SupportsDisjunction extends SupportsCondition {
+ final List<SupportsConditionInParens> conditions;
+
+ SupportsDisjunction(this.conditions, SourceSpan? span) : super(span);
+
+ @override
+ SupportsDisjunction clone() {
+ var clonedConditions = <SupportsConditionInParens>[];
+ for (var condition in conditions) {
+ clonedConditions.add(condition.clone());
+ }
+ return SupportsDisjunction(clonedConditions, span);
+ }
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitSupportsDisjunction(this);
+}
+
+class ViewportDirective extends Directive {
+ final String name;
+ final DeclarationGroup declarations;
+
+ ViewportDirective(this.name, this.declarations, SourceSpan? span)
+ : super(span);
+
+ @override
+ ViewportDirective clone() =>
+ ViewportDirective(name, declarations.clone(), span);
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitViewportDirective(this);
+}
+
+class ImportDirective extends Directive {
+ /// import name specified.
+ final String import;
+
+ /// Any media queries for this import.
+ final List<MediaQuery> mediaQueries;
+
+ ImportDirective(this.import, this.mediaQueries, SourceSpan? span)
+ : super(span);
+
+ @override
+ ImportDirective clone() {
+ var cloneMediaQueries = <MediaQuery>[];
+ for (var mediaQuery in mediaQueries) {
+ cloneMediaQueries.add(mediaQuery.clone());
+ }
+ return ImportDirective(import, cloneMediaQueries, span);
+ }
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitImportDirective(this);
+}
+
+/// MediaExpression grammar:
+///
+/// '(' S* media_feature S* [ ':' S* expr ]? ')' S*
+class MediaExpression extends TreeNode {
+ final bool andOperator;
+ final Identifier _mediaFeature;
+ final Expressions exprs;
+
+ MediaExpression(
+ this.andOperator, this._mediaFeature, this.exprs, SourceSpan? span)
+ : super(span);
+
+ String get mediaFeature => _mediaFeature.name;
+
+ @override
+ SourceSpan get span => super.span!;
+
+ @override
+ MediaExpression clone() {
+ var clonedExprs = exprs.clone();
+ return MediaExpression(andOperator, _mediaFeature, clonedExprs, span);
+ }
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitMediaExpression(this);
+}
+
+/// MediaQuery grammar:
+///
+/// : [ONLY | NOT]? S* media_type S* [ AND S* media_expression ]*
+/// | media_expression [ AND S* media_expression ]*
+/// media_type
+/// : IDENT
+/// media_expression
+/// : '(' S* media_feature S* [ ':' S* expr ]? ')' S*
+/// media_feature
+/// : IDENT
+class MediaQuery extends TreeNode {
+ /// not, only or no operator.
+ final int _mediaUnary;
+ final Identifier? _mediaType;
+ final List<MediaExpression> expressions;
+
+ MediaQuery(
+ this._mediaUnary, this._mediaType, this.expressions, SourceSpan? span)
+ : super(span);
+
+ bool get hasMediaType => _mediaType != null;
+ String get mediaType => _mediaType!.name;
+
+ bool get hasUnary => _mediaUnary != -1;
+ String get unary =>
+ TokenKind.idToValue(TokenKind.MEDIA_OPERATORS, _mediaUnary)!
+ .toUpperCase();
+
+ @override
+ SourceSpan get span => super.span!;
+
+ @override
+ MediaQuery clone() {
+ var cloneExpressions = <MediaExpression>[];
+ for (var expr in expressions) {
+ cloneExpressions.add(expr.clone());
+ }
+ return MediaQuery(_mediaUnary, _mediaType, cloneExpressions, span);
+ }
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitMediaQuery(this);
+}
+
+class MediaDirective extends Directive {
+ final List<MediaQuery> mediaQueries;
+ final List<TreeNode> rules;
+
+ MediaDirective(this.mediaQueries, this.rules, SourceSpan? span) : super(span);
+
+ @override
+ MediaDirective clone() {
+ var cloneQueries = <MediaQuery>[];
+ for (var mediaQuery in mediaQueries) {
+ cloneQueries.add(mediaQuery.clone());
+ }
+ var cloneRules = <TreeNode>[];
+ for (var rule in rules) {
+ cloneRules.add(rule.clone());
+ }
+ return MediaDirective(cloneQueries, cloneRules, span);
+ }
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitMediaDirective(this);
+}
+
+class HostDirective extends Directive {
+ final List<TreeNode> rules;
+
+ HostDirective(this.rules, SourceSpan? span) : super(span);
+
+ @override
+ HostDirective clone() {
+ var cloneRules = <TreeNode>[];
+ for (var rule in rules) {
+ cloneRules.add(rule.clone());
+ }
+ return HostDirective(cloneRules, span);
+ }
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitHostDirective(this);
+}
+
+class PageDirective extends Directive {
+ final String? _ident;
+ final String? _pseudoPage;
+ final List<DeclarationGroup> _declsMargin;
+
+ PageDirective(
+ this._ident, this._pseudoPage, this._declsMargin, SourceSpan? span)
+ : super(span);
+
+ @override
+ PageDirective clone() {
+ var cloneDeclsMargin = <DeclarationGroup>[];
+ for (var declMargin in _declsMargin) {
+ cloneDeclsMargin.add(declMargin.clone());
+ }
+ return PageDirective(_ident, _pseudoPage, cloneDeclsMargin, span);
+ }
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitPageDirective(this);
+
+ bool get hasIdent => _ident?.isNotEmpty ?? false;
+ bool get hasPseudoPage => _pseudoPage?.isNotEmpty ?? false;
+}
+
+class CharsetDirective extends Directive {
+ final String charEncoding;
+
+ CharsetDirective(this.charEncoding, SourceSpan? span) : super(span);
+ @override
+ CharsetDirective clone() => CharsetDirective(charEncoding, span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitCharsetDirective(this);
+}
+
+class KeyFrameDirective extends Directive {
+ // Either @keyframe or keyframe prefixed with @-webkit-, @-moz-, @-ms-, @-o-.
+ final int _keyframeName;
+ final Identifier? name;
+ final List<KeyFrameBlock> _blocks;
+
+ KeyFrameDirective(this._keyframeName, this.name, SourceSpan? span)
+ : _blocks = [],
+ super(span);
+
+ void add(KeyFrameBlock block) {
+ _blocks.add(block);
+ }
+
+ String? get keyFrameName => switch (_keyframeName) {
+ TokenKind.DIRECTIVE_KEYFRAMES ||
+ TokenKind.DIRECTIVE_MS_KEYFRAMES =>
+ '@keyframes',
+ TokenKind.DIRECTIVE_WEB_KIT_KEYFRAMES => '@-webkit-keyframes',
+ TokenKind.DIRECTIVE_MOZ_KEYFRAMES => '@-moz-keyframes',
+ TokenKind.DIRECTIVE_O_KEYFRAMES => '@-o-keyframes',
+ _ => null
+ };
+
+ @override
+ KeyFrameDirective clone() {
+ var directive = KeyFrameDirective(_keyframeName, name!.clone(), span);
+ for (var block in _blocks) {
+ directive.add(block.clone());
+ }
+ return directive;
+ }
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitKeyFrameDirective(this);
+}
+
+class KeyFrameBlock extends Expression {
+ final Expressions _blockSelectors;
+ final DeclarationGroup _declarations;
+
+ KeyFrameBlock(this._blockSelectors, this._declarations, SourceSpan? span)
+ : super(span);
+
+ @override
+ KeyFrameBlock clone() =>
+ KeyFrameBlock(_blockSelectors.clone(), _declarations.clone(), span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitKeyFrameBlock(this);
+}
+
+class FontFaceDirective extends Directive {
+ final DeclarationGroup _declarations;
+
+ FontFaceDirective(this._declarations, SourceSpan? span) : super(span);
+
+ @override
+ FontFaceDirective clone() => FontFaceDirective(_declarations.clone(), span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitFontFaceDirective(this);
+}
+
+class StyletDirective extends Directive {
+ final String dartClassName;
+ final List<TreeNode> rules;
+
+ StyletDirective(this.dartClassName, this.rules, SourceSpan? span)
+ : super(span);
+
+ @override
+ bool get isBuiltIn => false;
+ @override
+ bool get isExtension => true;
+
+ @override
+ StyletDirective clone() {
+ var cloneRules = <TreeNode>[];
+ for (var rule in rules) {
+ cloneRules.add(rule.clone());
+ }
+ return StyletDirective(dartClassName, cloneRules, span);
+ }
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitStyletDirective(this);
+}
+
+class NamespaceDirective extends Directive {
+ /// Namespace prefix.
+ final String _prefix;
+
+ /// URI associated with this namespace.
+ final String? _uri;
+
+ NamespaceDirective(this._prefix, this._uri, SourceSpan? span) : super(span);
+
+ @override
+ NamespaceDirective clone() => NamespaceDirective(_prefix, _uri, span);
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitNamespaceDirective(this);
+
+ String get prefix => _prefix.isNotEmpty ? '$_prefix ' : '';
+}
+
+/// To support Less syntax @name: expression
+class VarDefinitionDirective extends Directive {
+ final VarDefinition def;
+
+ VarDefinitionDirective(this.def, SourceSpan? span) : super(span);
+
+ @override
+ VarDefinitionDirective clone() => VarDefinitionDirective(def.clone(), span);
+
+ @override
+ dynamic visit(VisitorBase visitor) =>
+ visitor.visitVarDefinitionDirective(this);
+}
+
+class MixinDefinition extends Directive {
+ final String name;
+ final List<TreeNode> definedArgs;
+ final bool varArgs;
+
+ MixinDefinition(this.name, this.definedArgs, this.varArgs, SourceSpan? span)
+ : super(span);
+
+ @override
+ MixinDefinition clone() {
+ var cloneDefinedArgs = <TreeNode>[];
+ for (var definedArg in definedArgs) {
+ cloneDefinedArgs.add(definedArg.clone());
+ }
+ return MixinDefinition(name, cloneDefinedArgs, varArgs, span);
+ }
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitMixinDefinition(this);
+}
+
+/// Support a Sass @mixin. See http://sass-lang.com for description.
+class MixinRulesetDirective extends MixinDefinition {
+ final List<TreeNode> rulesets;
+
+ MixinRulesetDirective(String name, List<TreeNode> args, bool varArgs,
+ this.rulesets, SourceSpan? span)
+ : super(name, args, varArgs, span);
+
+ @override
+ MixinRulesetDirective clone() {
+ var clonedArgs = <VarDefinition>[];
+ for (var arg in definedArgs) {
+ clonedArgs.add(arg.clone() as VarDefinition);
+ }
+ var clonedRulesets = <TreeNode>[];
+ for (var ruleset in rulesets) {
+ clonedRulesets.add(ruleset.clone());
+ }
+ return MixinRulesetDirective(
+ name, clonedArgs, varArgs, clonedRulesets, span);
+ }
+
+ @override
+ dynamic visit(VisitorBase visitor) =>
+ visitor.visitMixinRulesetDirective(this);
+}
+
+class MixinDeclarationDirective extends MixinDefinition {
+ final DeclarationGroup declarations;
+
+ MixinDeclarationDirective(String name, List<TreeNode> args, bool varArgs,
+ this.declarations, SourceSpan? span)
+ : super(name, args, varArgs, span);
+
+ @override
+ MixinDeclarationDirective clone() {
+ var clonedArgs = <TreeNode>[];
+ for (var arg in definedArgs) {
+ clonedArgs.add(arg.clone());
+ }
+ return MixinDeclarationDirective(
+ name, clonedArgs, varArgs, declarations.clone(), span);
+ }
+
+ @override
+ dynamic visit(VisitorBase visitor) =>
+ visitor.visitMixinDeclarationDirective(this);
+}
+
+/// To support consuming a Sass mixin @include.
+class IncludeDirective extends Directive {
+ final String name;
+ final List<List<Expression>> args;
+
+ IncludeDirective(this.name, this.args, SourceSpan? span) : super(span);
+
+ @override
+ IncludeDirective clone() {
+ var cloneArgs = <List<Expression>>[];
+ for (var arg in args) {
+ cloneArgs.add(arg.map((term) => term.clone()).toList());
+ }
+ return IncludeDirective(name, cloneArgs, span);
+ }
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitIncludeDirective(this);
+}
+
+/// To support Sass @content.
+class ContentDirective extends Directive {
+ ContentDirective(super.span);
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitContentDirective(this);
+}
+
+class Declaration extends TreeNode {
+ final Identifier? _property;
+ final Expression? expression;
+
+ /// Style exposed to Dart.
+ DartStyleExpression? dartStyle;
+ final bool important;
+
+ /// IE CSS hacks that can only be read by a particular IE version.
+ /// 7 implies IE 7 or older property (e.g., `*background: blue;`)
+ ///
+ /// * Note: IE 8 or older property (e.g., `background: green\9;`) is handled
+ /// by IE8Term in declaration expression handling.
+ /// * Note: IE 6 only property with a leading underscore is a valid IDENT
+ /// since an ident can start with underscore (e.g., `_background: red;`)
+ final bool isIE7;
+
+ Declaration(this._property, this.expression, this.dartStyle, SourceSpan? span,
+ {this.important = false, bool ie7 = false})
+ : isIE7 = ie7,
+ super(span);
+
+ String get property => isIE7 ? '*${_property!.name}' : _property!.name;
+
+ bool get hasDartStyle => dartStyle != null;
+
+ @override
+ SourceSpan get span => super.span!;
+
+ @override
+ Declaration clone() =>
+ Declaration(_property!.clone(), expression!.clone(), dartStyle, span,
+ important: important);
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitDeclaration(this);
+}
+
+// TODO(terry): Consider 2 kinds of VarDefinitions static at top-level and
+// dynamic when in a declaration. Currently, Less syntax
+// '@foo: expression' and 'var-foo: expression' in a declaration
+// are statically resolved. Better solution, if @foo or var-foo
+// are top-level are then statically resolved and var-foo in a
+// declaration group (surrounded by a selector) would be dynamic.
+class VarDefinition extends Declaration {
+ bool badUsage = false;
+
+ VarDefinition(Identifier? definedName, Expression? expr, SourceSpan? span)
+ : super(definedName, expr, null, span);
+
+ String get definedName => _property!.name;
+
+ @override
+ VarDefinition clone() =>
+ VarDefinition(_property!.clone(), expression?.clone(), span);
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitVarDefinition(this);
+}
+
+/// Node for usage of @include mixin[(args,...)] found in a declaration group
+/// instead of at a ruleset (toplevel) e.g.,
+///
+/// div {
+/// @include mixin1;
+/// }
+class IncludeMixinAtDeclaration extends Declaration {
+ final IncludeDirective include;
+
+ IncludeMixinAtDeclaration(this.include, SourceSpan? span)
+ : super(null, null, null, span);
+
+ @override
+ IncludeMixinAtDeclaration clone() =>
+ IncludeMixinAtDeclaration(include.clone(), span);
+
+ @override
+ dynamic visit(VisitorBase visitor) =>
+ visitor.visitIncludeMixinAtDeclaration(this);
+}
+
+class ExtendDeclaration extends Declaration {
+ final List<TreeNode> selectors;
+
+ ExtendDeclaration(this.selectors, SourceSpan? span)
+ : super(null, null, null, span);
+
+ @override
+ ExtendDeclaration clone() {
+ var newSelector = selectors.map((s) => s.clone()).toList();
+ return ExtendDeclaration(newSelector, span);
+ }
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitExtendDeclaration(this);
+}
+
+class DeclarationGroup extends TreeNode {
+ /// Can be either Declaration or RuleSet (if nested selector).
+ final List<TreeNode> declarations;
+
+ DeclarationGroup(this.declarations, SourceSpan? span) : super(span);
+
+ @override
+ SourceSpan get span => super.span!;
+
+ @override
+ DeclarationGroup clone() {
+ var clonedDecls = declarations.map((d) => d.clone()).toList();
+ return DeclarationGroup(clonedDecls, span);
+ }
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitDeclarationGroup(this);
+}
+
+class MarginGroup extends DeclarationGroup {
+ // ignore: non_constant_identifier_names
+ final int margin_sym; // TokenType for for @margin sym.
+
+ MarginGroup(this.margin_sym, List<TreeNode> decls, SourceSpan? span)
+ : super(decls, span);
+ @override
+ MarginGroup clone() =>
+ MarginGroup(margin_sym, super.clone().declarations, span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitMarginGroup(this);
+}
+
+class VarUsage extends Expression {
+ final String name;
+ final List<Expression> defaultValues;
+
+ VarUsage(this.name, this.defaultValues, SourceSpan? span) : super(span);
+
+ @override
+ VarUsage clone() {
+ var clonedValues = <Expression>[];
+ for (var expr in defaultValues) {
+ clonedValues.add(expr.clone());
+ }
+ return VarUsage(name, clonedValues, span);
+ }
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitVarUsage(this);
+}
+
+class OperatorSlash extends Expression {
+ OperatorSlash(super.span);
+ @override
+ OperatorSlash clone() => OperatorSlash(span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitOperatorSlash(this);
+}
+
+class OperatorComma extends Expression {
+ OperatorComma(super.span);
+ @override
+ OperatorComma clone() => OperatorComma(span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitOperatorComma(this);
+}
+
+class OperatorPlus extends Expression {
+ OperatorPlus(super.span);
+ @override
+ OperatorPlus clone() => OperatorPlus(span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitOperatorPlus(this);
+}
+
+class OperatorMinus extends Expression {
+ OperatorMinus(super.span);
+ @override
+ OperatorMinus clone() => OperatorMinus(span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitOperatorMinus(this);
+}
+
+class UnicodeRangeTerm extends Expression {
+ final String? first;
+ final String? second;
+
+ UnicodeRangeTerm(this.first, this.second, SourceSpan? span) : super(span);
+
+ bool get hasSecond => second != null;
+
+ @override
+ UnicodeRangeTerm clone() => UnicodeRangeTerm(first, second, span);
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitUnicodeRangeTerm(this);
+}
+
+class LiteralTerm extends Expression {
+ // TODO(terry): value and text fields can be made final once all CSS resources
+ // are copied/symlink'd in the build tool and UriVisitor in
+ // web_ui is removed.
+ Object value;
+ String text;
+
+ LiteralTerm(this.value, this.text, SourceSpan? span) : super(span);
+
+ @override
+ LiteralTerm clone() => LiteralTerm(value, text, span);
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitLiteralTerm(this);
+}
+
+class NumberTerm extends LiteralTerm {
+ NumberTerm(super.value, super.text, super.span);
+ @override
+ NumberTerm clone() => NumberTerm(value, text, span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitNumberTerm(this);
+}
+
+class UnitTerm extends LiteralTerm {
+ final int unit;
+
+ UnitTerm(super.value, super.text, super.span, this.unit);
+
+ @override
+ UnitTerm clone() => UnitTerm(value, text, span, unit);
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitUnitTerm(this);
+
+ String? unitToString() => TokenKind.unitToString(unit);
+
+ @override
+ String toString() => '$text${unitToString()}';
+}
+
+class LengthTerm extends UnitTerm {
+ LengthTerm(super.value, super.text, super.span,
+ [super.unit = TokenKind.UNIT_LENGTH_PX]) {
+ assert(unit == TokenKind.UNIT_LENGTH_PX ||
+ unit == TokenKind.UNIT_LENGTH_CM ||
+ unit == TokenKind.UNIT_LENGTH_MM ||
+ unit == TokenKind.UNIT_LENGTH_IN ||
+ unit == TokenKind.UNIT_LENGTH_PT ||
+ unit == TokenKind.UNIT_LENGTH_PC);
+ }
+ @override
+ LengthTerm clone() => LengthTerm(value, text, span, unit);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitLengthTerm(this);
+}
+
+class PercentageTerm extends LiteralTerm {
+ PercentageTerm(super.value, super.text, super.span);
+ @override
+ PercentageTerm clone() => PercentageTerm(value, text, span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitPercentageTerm(this);
+}
+
+class EmTerm extends LiteralTerm {
+ EmTerm(super.value, super.text, super.span);
+ @override
+ EmTerm clone() => EmTerm(value, text, span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitEmTerm(this);
+}
+
+class ExTerm extends LiteralTerm {
+ ExTerm(super.value, super.text, super.span);
+ @override
+ ExTerm clone() => ExTerm(value, text, span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitExTerm(this);
+}
+
+class AngleTerm extends UnitTerm {
+ AngleTerm(super.value, super.text, super.span,
+ [super.unit = TokenKind.UNIT_LENGTH_PX]) {
+ assert(unit == TokenKind.UNIT_ANGLE_DEG ||
+ unit == TokenKind.UNIT_ANGLE_RAD ||
+ unit == TokenKind.UNIT_ANGLE_GRAD ||
+ unit == TokenKind.UNIT_ANGLE_TURN);
+ }
+
+ @override
+ AngleTerm clone() => AngleTerm(value, text, span, unit);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitAngleTerm(this);
+}
+
+class TimeTerm extends UnitTerm {
+ TimeTerm(super.value, super.text, super.span,
+ [super.unit = TokenKind.UNIT_LENGTH_PX]) {
+ assert(unit == TokenKind.UNIT_ANGLE_DEG ||
+ unit == TokenKind.UNIT_TIME_MS ||
+ unit == TokenKind.UNIT_TIME_S);
+ }
+
+ @override
+ TimeTerm clone() => TimeTerm(value, text, span, unit);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitTimeTerm(this);
+}
+
+class FreqTerm extends UnitTerm {
+ FreqTerm(Object value, String text, SourceSpan? span,
+ [int unit = TokenKind.UNIT_LENGTH_PX])
+ : super(value, text, span, unit) {
+ assert(unit == TokenKind.UNIT_FREQ_HZ || unit == TokenKind.UNIT_FREQ_KHZ);
+ }
+
+ @override
+ FreqTerm clone() => FreqTerm(value, text, span, unit);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitFreqTerm(this);
+}
+
+class FractionTerm extends LiteralTerm {
+ FractionTerm(super.value, super.text, super.span);
+
+ @override
+ FractionTerm clone() => FractionTerm(value, text, span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitFractionTerm(this);
+}
+
+class UriTerm extends LiteralTerm {
+ UriTerm(String value, SourceSpan? span) : super(value, value, span);
+
+ @override
+ UriTerm clone() => UriTerm(value as String, span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitUriTerm(this);
+}
+
+class ResolutionTerm extends UnitTerm {
+ ResolutionTerm(Object value, String text, SourceSpan? span,
+ [int unit = TokenKind.UNIT_LENGTH_PX])
+ : super(value, text, span, unit) {
+ assert(unit == TokenKind.UNIT_RESOLUTION_DPI ||
+ unit == TokenKind.UNIT_RESOLUTION_DPCM ||
+ unit == TokenKind.UNIT_RESOLUTION_DPPX);
+ }
+
+ @override
+ ResolutionTerm clone() => ResolutionTerm(value, text, span, unit);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitResolutionTerm(this);
+}
+
+class ChTerm extends UnitTerm {
+ ChTerm(Object value, String text, SourceSpan? span,
+ [int unit = TokenKind.UNIT_LENGTH_PX])
+ : super(value, text, span, unit) {
+ assert(unit == TokenKind.UNIT_CH);
+ }
+
+ @override
+ ChTerm clone() => ChTerm(value, text, span, unit);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitChTerm(this);
+}
+
+class RemTerm extends UnitTerm {
+ RemTerm(Object value, String text, SourceSpan? span,
+ [int unit = TokenKind.UNIT_LENGTH_PX])
+ : super(value, text, span, unit) {
+ assert(unit == TokenKind.UNIT_REM);
+ }
+
+ @override
+ RemTerm clone() => RemTerm(value, text, span, unit);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitRemTerm(this);
+}
+
+class LineHeightTerm extends UnitTerm {
+ LineHeightTerm(Object value, String text, SourceSpan? span, int unit)
+ : super(value, text, span, unit) {
+ assert(unit == TokenKind.UNIT_LH || unit == TokenKind.UNIT_RLH);
+ }
+ @override
+ LineHeightTerm clone() => LineHeightTerm(value, text, span, unit);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitLineHeightTerm(this);
+}
+
+class ViewportTerm extends UnitTerm {
+ ViewportTerm(Object value, String text, SourceSpan? span,
+ [int unit = TokenKind.UNIT_LENGTH_PX])
+ : super(value, text, span, unit) {
+ assert(unit == TokenKind.UNIT_VIEWPORT_VW ||
+ unit == TokenKind.UNIT_VIEWPORT_VH ||
+ unit == TokenKind.UNIT_VIEWPORT_VMIN ||
+ unit == TokenKind.UNIT_VIEWPORT_VMAX);
+ }
+
+ @override
+ ViewportTerm clone() => ViewportTerm(value, text, span, unit);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitViewportTerm(this);
+}
+
+/// Type to signal a bad hex value for HexColorTerm.value.
+// ignore: camel_case_types
+class BAD_HEX_VALUE {}
+
+class HexColorTerm extends LiteralTerm {
+ HexColorTerm(super.value, super.text, super.span);
+
+ @override
+ HexColorTerm clone() => HexColorTerm(value, text, span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitHexColorTerm(this);
+}
+
+class FunctionTerm extends LiteralTerm {
+ final Expressions _params;
+
+ FunctionTerm(Object value, String text, this._params, SourceSpan? span)
+ : super(value, text, span);
+
+ @override
+ FunctionTerm clone() => FunctionTerm(value, text, _params.clone(), span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitFunctionTerm(this);
+}
+
+/// A "\9" was encountered at the end of the expression and before a semi-colon.
+/// This is an IE trick to ignore a property or value except by IE 8 and older
+/// browsers.
+class IE8Term extends LiteralTerm {
+ IE8Term(SourceSpan? span) : super('\\9', '\\9', span);
+ @override
+ IE8Term clone() => IE8Term(span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitIE8Term(this);
+}
+
+class GroupTerm extends Expression {
+ final List<LiteralTerm> _terms;
+
+ GroupTerm(super.span) : _terms = [];
+
+ void add(LiteralTerm term) {
+ _terms.add(term);
+ }
+
+ @override
+ GroupTerm clone() => GroupTerm(span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitGroupTerm(this);
+}
+
+class ItemTerm extends NumberTerm {
+ ItemTerm(super.value, super.text, super.span);
+
+ @override
+ ItemTerm clone() => ItemTerm(value, text, span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitItemTerm(this);
+}
+
+class Expressions extends Expression {
+ final List<Expression> expressions = [];
+
+ Expressions(super.span);
+
+ void add(Expression expression) {
+ expressions.add(expression);
+ }
+
+ @override
+ Expressions clone() {
+ var clonedExprs = Expressions(span);
+ for (var expr in expressions) {
+ clonedExprs.add(expr.clone());
+ }
+ return clonedExprs;
+ }
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitExpressions(this);
+}
+
+class BinaryExpression extends Expression {
+ final Token op;
+ final Expression x;
+ final Expression y;
+
+ BinaryExpression(this.op, this.x, this.y, SourceSpan? span) : super(span);
+
+ @override
+ BinaryExpression clone() => BinaryExpression(op, x.clone(), y.clone(), span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitBinaryExpression(this);
+}
+
+class UnaryExpression extends Expression {
+ final Token op;
+ final Expression self;
+
+ UnaryExpression(this.op, this.self, SourceSpan? span) : super(span);
+
+ @override
+ UnaryExpression clone() => UnaryExpression(op, self.clone(), span);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitUnaryExpression(this);
+}
+
+abstract class DartStyleExpression extends TreeNode {
+ static const int unknownType = 0;
+ static const int fontStyle = 1;
+ static const int marginStyle = 2;
+ static const int borderStyle = 3;
+ static const int paddingStyle = 4;
+ static const int heightStyle = 5;
+ static const int widthStyle = 6;
+
+ final int? _styleType;
+ int? priority;
+
+ DartStyleExpression(this._styleType, SourceSpan? span) : super(span);
+
+ // Merges give 2 DartStyleExpression (or derived from DartStyleExpression,
+ // e.g., FontExpression, etc.) will merge if the two expressions are of the
+ // same property name (implies same exact type e.g, FontExpression).
+ DartStyleExpression? merged(DartStyleExpression newDartExpr);
+
+ bool get isUnknown => _styleType == 0 || _styleType == null;
+ bool get isFont => _styleType == fontStyle;
+ bool get isMargin => _styleType == marginStyle;
+ bool get isBorder => _styleType == borderStyle;
+ bool get isPadding => _styleType == paddingStyle;
+ bool get isHeight => _styleType == heightStyle;
+ bool get isWidth => _styleType == widthStyle;
+ bool get isBoxExpression => isMargin || isBorder || isPadding;
+
+ bool isSame(DartStyleExpression other) => _styleType == other._styleType;
+
+ @override
+ SourceSpan get span => super.span!;
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitDartStyleExpression(this);
+}
+
+class FontExpression extends DartStyleExpression {
+ final Font font;
+
+ // font-style font-variant font-weight font-size/line-height font-family
+ // TODO(terry): Only px/pt for now need to handle all possible units to
+ // support calc expressions on units.
+ FontExpression(SourceSpan? span,
+ {Object? /* LengthTerm | num */ size,
+ List<String>? family,
+ int? weight,
+ String? style,
+ String? variant,
+ LineHeight? lineHeight})
+ : font = Font(
+ size: (size is LengthTerm ? size.value : size) as num?,
+ family: family,
+ weight: weight,
+ style: style,
+ variant: variant,
+ lineHeight: lineHeight),
+ super(DartStyleExpression.fontStyle, span);
+
+ @override
+ FontExpression? merged(DartStyleExpression newDartExpr) {
+ if (newDartExpr is FontExpression && isFont && newDartExpr.isFont) {
+ return FontExpression.merge(this, newDartExpr);
+ }
+ return null;
+ }
+
+ /// Merge the two FontExpression and return the result.
+ factory FontExpression.merge(FontExpression x, FontExpression y) =>
+ FontExpression._merge(x, y, y.span);
+
+ FontExpression._merge(FontExpression x, FontExpression y, SourceSpan? span)
+ : font = Font.merge(x.font, y.font)!,
+ super(DartStyleExpression.fontStyle, span);
+
+ @override
+ FontExpression clone() => FontExpression(span,
+ size: font.size,
+ family: font.family,
+ weight: font.weight,
+ style: font.style,
+ variant: font.variant,
+ lineHeight: font.lineHeight);
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitFontExpression(this);
+}
+
+abstract class BoxExpression extends DartStyleExpression {
+ final BoxEdge? box;
+
+ BoxExpression(super.styleType, super.span, this.box);
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitBoxExpression(this);
+
+ String get formattedBoxEdge {
+ if (box!.top == box!.left &&
+ box!.top == box!.bottom &&
+ box!.top == box!.right) {
+ return '.uniform(${box!.top})';
+ } else {
+ var left = box!.left ?? 0;
+ var top = box!.top ?? 0;
+ var right = box!.right ?? 0;
+ var bottom = box!.bottom ?? 0;
+ return '.clockwiseFromTop($top,$right,$bottom,$left)';
+ }
+ }
+}
+
+class MarginExpression extends BoxExpression {
+ // TODO(terry): Does auto for margin need to be exposed to Dart UI framework?
+ /// Margin expression ripped apart.
+ MarginExpression(SourceSpan? span,
+ {num? top, num? right, num? bottom, num? left})
+ : super(DartStyleExpression.marginStyle, span,
+ BoxEdge(left, top, right, bottom));
+
+ MarginExpression.boxEdge(SourceSpan? span, BoxEdge? box)
+ : super(DartStyleExpression.marginStyle, span, box);
+
+ @override
+ MarginExpression? merged(DartStyleExpression newDartExpr) {
+ if (newDartExpr is MarginExpression && isMargin && newDartExpr.isMargin) {
+ return MarginExpression.merge(this, newDartExpr);
+ }
+
+ return null;
+ }
+
+ /// Merge the two MarginExpressions and return the result.
+ factory MarginExpression.merge(MarginExpression x, MarginExpression y) =>
+ MarginExpression._merge(x, y, y.span);
+
+ MarginExpression._merge(
+ MarginExpression x, MarginExpression y, SourceSpan? span)
+ : super(x._styleType, span, BoxEdge.merge(x.box, y.box));
+
+ @override
+ MarginExpression clone() => MarginExpression(span,
+ top: box!.top, right: box!.right, bottom: box!.bottom, left: box!.left);
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitMarginExpression(this);
+}
+
+class BorderExpression extends BoxExpression {
+ /// Border expression ripped apart.
+ BorderExpression(SourceSpan? span,
+ {num? top, num? right, num? bottom, num? left})
+ : super(DartStyleExpression.borderStyle, span,
+ BoxEdge(left, top, right, bottom));
+
+ BorderExpression.boxEdge(SourceSpan? span, BoxEdge box)
+ : super(DartStyleExpression.borderStyle, span, box);
+
+ @override
+ BorderExpression? merged(DartStyleExpression newDartExpr) {
+ if (newDartExpr is BorderExpression && isBorder && newDartExpr.isBorder) {
+ return BorderExpression.merge(this, newDartExpr);
+ }
+
+ return null;
+ }
+
+ /// Merge the two BorderExpression and return the result.
+ factory BorderExpression.merge(BorderExpression x, BorderExpression y) =>
+ BorderExpression._merge(x, y, y.span);
+
+ BorderExpression._merge(
+ BorderExpression x, BorderExpression y, SourceSpan? span)
+ : super(
+ DartStyleExpression.borderStyle, span, BoxEdge.merge(x.box, y.box));
+
+ @override
+ BorderExpression clone() => BorderExpression(span,
+ top: box!.top, right: box!.right, bottom: box!.bottom, left: box!.left);
+
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitBorderExpression(this);
+}
+
+class HeightExpression extends DartStyleExpression {
+ final dynamic height;
+
+ HeightExpression(SourceSpan? span, this.height)
+ : super(DartStyleExpression.heightStyle, span);
+
+ @override
+ HeightExpression? merged(DartStyleExpression newDartExpr) {
+ if (isHeight && newDartExpr.isHeight) {
+ return newDartExpr as HeightExpression;
+ }
+
+ return null;
+ }
+
+ @override
+ HeightExpression clone() => HeightExpression(span, height);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitHeightExpression(this);
+}
+
+class WidthExpression extends DartStyleExpression {
+ final dynamic width;
+
+ WidthExpression(SourceSpan? span, this.width)
+ : super(DartStyleExpression.widthStyle, span);
+
+ @override
+ WidthExpression? merged(DartStyleExpression newDartExpr) {
+ if (newDartExpr is WidthExpression && isWidth && newDartExpr.isWidth) {
+ return newDartExpr;
+ }
+
+ return null;
+ }
+
+ @override
+ WidthExpression clone() => WidthExpression(span, width);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitWidthExpression(this);
+}
+
+class PaddingExpression extends BoxExpression {
+ /// Padding expression ripped apart.
+ PaddingExpression(SourceSpan? span,
+ {num? top, num? right, num? bottom, num? left})
+ : super(DartStyleExpression.paddingStyle, span,
+ BoxEdge(left, top, right, bottom));
+
+ PaddingExpression.boxEdge(SourceSpan? span, BoxEdge? box)
+ : super(DartStyleExpression.paddingStyle, span, box);
+
+ @override
+ PaddingExpression? merged(DartStyleExpression newDartExpr) {
+ if (newDartExpr is PaddingExpression &&
+ isPadding &&
+ newDartExpr.isPadding) {
+ return PaddingExpression.merge(this, newDartExpr);
+ }
+
+ return null;
+ }
+
+ /// Merge the two PaddingExpression and return the result.
+ factory PaddingExpression.merge(PaddingExpression x, PaddingExpression y) =>
+ PaddingExpression._merge(x, y, y.span);
+
+ PaddingExpression._merge(
+ PaddingExpression x, PaddingExpression y, SourceSpan? span)
+ : super(DartStyleExpression.paddingStyle, span,
+ BoxEdge.merge(x.box, y.box));
+
+ @override
+ PaddingExpression clone() => PaddingExpression(span,
+ top: box!.top, right: box!.right, bottom: box!.bottom, left: box!.left);
+ @override
+ dynamic visit(VisitorBase visitor) => visitor.visitPaddingExpression(this);
+}
diff --git a/pkgs/csslib/lib/src/tree_base.dart b/pkgs/csslib/lib/src/tree_base.dart
new file mode 100644
index 0000000..af55fdd
--- /dev/null
+++ b/pkgs/csslib/lib/src/tree_base.dart
@@ -0,0 +1,101 @@
+// 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.
+
+part of '../visitor.dart';
+
+/// The base type for all nodes in a CSS abstract syntax tree.
+abstract class TreeNode {
+ /// The source code this [TreeNode] represents.
+ final SourceSpan? span;
+
+ TreeNode(this.span);
+
+ TreeNode clone();
+
+ /// Classic double-dispatch visitor for implementing passes.
+ dynamic visit(VisitorBase visitor);
+
+ /// A multiline string showing the node and its children.
+ String toDebugString() {
+ var to = TreeOutput();
+ var tp = _TreePrinter(to, true);
+ visit(tp);
+ return to.buf.toString();
+ }
+}
+
+/// The base type for expressions.
+abstract class Expression extends TreeNode {
+ Expression(super.span);
+ @override
+ Expression clone();
+}
+
+/// Simple class to provide a textual dump of trees for debugging.
+class TreeOutput {
+ int depth = 0;
+ final StringBuffer buf = StringBuffer();
+ VisitorBase? printer;
+
+ void write(String s) {
+ for (var i = 0; i < depth; i++) {
+ buf.write(' ');
+ }
+ buf.write(s);
+ }
+
+ void writeln(String s) {
+ write(s);
+ buf.write('\n');
+ }
+
+ void heading(String name, [SourceSpan? span]) {
+ write(name);
+ if (span != null) {
+ buf.write(' (${span.message('')})');
+ }
+ buf.write('\n');
+ }
+
+ String toValue(dynamic value) {
+ if (value == null) {
+ return 'null';
+ } else if (value is Identifier) {
+ return value.name;
+ } else {
+ return value.toString();
+ }
+ }
+
+ void writeNode(String label, TreeNode? node) {
+ write('$label: ');
+ depth += 1;
+ if (node != null) {
+ node.visit(printer!);
+ } else {
+ writeln('null');
+ }
+ depth -= 1;
+ }
+
+ void writeValue(String label, dynamic value) {
+ var v = toValue(value);
+ writeln('$label: $v');
+ }
+
+ void writeNodeList(String label, List<TreeNode>? list) {
+ writeln('$label [');
+ if (list != null) {
+ depth += 1;
+ for (var node in list) {
+ node.visit(printer!);
+ }
+ depth -= 1;
+ writeln(']');
+ }
+ }
+
+ @override
+ String toString() => buf.toString();
+}
diff --git a/pkgs/csslib/lib/src/tree_printer.dart b/pkgs/csslib/lib/src/tree_printer.dart
new file mode 100644
index 0000000..2e11e9e
--- /dev/null
+++ b/pkgs/csslib/lib/src/tree_printer.dart
@@ -0,0 +1,700 @@
+// 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.
+
+part of '../visitor.dart';
+
+// TODO(terry): Enable class for debug only; when conditional imports enabled.
+
+/// Helper function to dump the CSS AST.
+String treeToDebugString(StyleSheet styleSheet, [bool useSpan = false]) {
+ var to = TreeOutput();
+ _TreePrinter(to, useSpan).visitTree(styleSheet);
+ return to.toString();
+}
+
+/// Tree dump for debug output of the CSS AST.
+class _TreePrinter extends Visitor {
+ final TreeOutput output;
+ final bool useSpan;
+
+ _TreePrinter(this.output, this.useSpan) {
+ output.printer = this;
+ }
+
+ @override
+ void visitTree(StyleSheet tree) => visitStylesheet(tree);
+
+ void heading(String heading, TreeNode node) {
+ if (useSpan) {
+ output.heading(heading, node.span);
+ } else {
+ output.heading(heading);
+ }
+ }
+
+ void visitStylesheet(StyleSheet node) {
+ heading('Stylesheet', node);
+ output.depth++;
+ super.visitStyleSheet(node);
+ output.depth--;
+ }
+
+ @override
+ void visitTopLevelProduction(TopLevelProduction node) {
+ heading('TopLevelProduction', node);
+ }
+
+ @override
+ void visitDirective(Directive node) {
+ heading('Directive', node);
+ }
+
+ @override
+ void visitCalcTerm(CalcTerm node) {
+ heading('CalcTerm', node);
+ output.depth++;
+ super.visitCalcTerm(node);
+ output.depth--;
+ }
+
+ @override
+ void visitCssComment(CssComment node) {
+ heading('Comment', node);
+ output.depth++;
+ output.writeValue('comment value', node.comment);
+ output.depth--;
+ }
+
+ @override
+ void visitCommentDefinition(CommentDefinition node) {
+ heading('CommentDefinition (CDO/CDC)', node);
+ output.depth++;
+ output.writeValue('comment value', node.comment);
+ output.depth--;
+ }
+
+ @override
+ void visitMediaExpression(MediaExpression node) {
+ heading('MediaExpression', node);
+ output.writeValue('feature', node.mediaFeature);
+ if (node.andOperator) output.writeValue('AND operator', '');
+ visitExpressions(node.exprs);
+ }
+
+ void visitMediaQueries(MediaQuery query) {
+ output.heading('MediaQueries');
+ output.writeValue('unary', query.unary);
+ output.writeValue('media type', query.mediaType);
+ output.writeNodeList('media expressions', query.expressions);
+ }
+
+ @override
+ void visitMediaDirective(MediaDirective node) {
+ heading('MediaDirective', node);
+ output.depth++;
+ output.writeNodeList('media queries', node.mediaQueries);
+ output.writeNodeList('rule sets', node.rules);
+ super.visitMediaDirective(node);
+ output.depth--;
+ }
+
+ @override
+ void visitDocumentDirective(DocumentDirective node) {
+ heading('DocumentDirective', node);
+ output.depth++;
+ output.writeNodeList('functions', node.functions);
+ output.writeNodeList('group rule body', node.groupRuleBody);
+ output.depth--;
+ }
+
+ @override
+ void visitSupportsDirective(SupportsDirective node) {
+ heading('SupportsDirective', node);
+ output.depth++;
+ output.writeNode('condition', node.condition);
+ output.writeNodeList('group rule body', node.groupRuleBody);
+ output.depth--;
+ }
+
+ @override
+ void visitSupportsConditionInParens(SupportsConditionInParens node) {
+ heading('SupportsConditionInParens', node);
+ output.depth++;
+ output.writeNode('condition', node.condition);
+ output.depth--;
+ }
+
+ @override
+ void visitSupportsNegation(SupportsNegation node) {
+ heading('SupportsNegation', node);
+ output.depth++;
+ output.writeNode('condition', node.condition);
+ output.depth--;
+ }
+
+ @override
+ void visitSupportsConjunction(SupportsConjunction node) {
+ heading('SupportsConjunction', node);
+ output.depth++;
+ output.writeNodeList('conditions', node.conditions);
+ output.depth--;
+ }
+
+ @override
+ void visitSupportsDisjunction(SupportsDisjunction node) {
+ heading('SupportsDisjunction', node);
+ output.depth++;
+ output.writeNodeList('conditions', node.conditions);
+ output.depth--;
+ }
+
+ @override
+ void visitViewportDirective(ViewportDirective node) {
+ heading('ViewportDirective', node);
+ output.depth++;
+ super.visitViewportDirective(node);
+ output.depth--;
+ }
+
+ @override
+ void visitPageDirective(PageDirective node) {
+ heading('PageDirective', node);
+ output.depth++;
+ output.writeValue('pseudo page', node._pseudoPage);
+ super.visitPageDirective(node);
+ output.depth--;
+ }
+
+ @override
+ void visitCharsetDirective(CharsetDirective node) {
+ heading('Charset Directive', node);
+ output.writeValue('charset encoding', node.charEncoding);
+ }
+
+ @override
+ void visitImportDirective(ImportDirective node) {
+ heading('ImportDirective', node);
+ output.depth++;
+ output.writeValue('import', node.import);
+ super.visitImportDirective(node);
+ output.writeNodeList('media', node.mediaQueries);
+ output.depth--;
+ }
+
+ @override
+ void visitContentDirective(ContentDirective node) {
+ print('ContentDirective not implemented');
+ }
+
+ @override
+ void visitKeyFrameDirective(KeyFrameDirective node) {
+ heading('KeyFrameDirective', node);
+ output.depth++;
+ output.writeValue('keyframe', node.keyFrameName);
+ output.writeValue('name', node.name);
+ output.writeNodeList('blocks', node._blocks);
+ output.depth--;
+ }
+
+ @override
+ void visitKeyFrameBlock(KeyFrameBlock node) {
+ heading('KeyFrameBlock', node);
+ output.depth++;
+ super.visitKeyFrameBlock(node);
+ output.depth--;
+ }
+
+ @override
+ void visitFontFaceDirective(FontFaceDirective node) {
+ // TODO(terry): To Be Implemented
+ }
+
+ @override
+ void visitStyletDirective(StyletDirective node) {
+ heading('StyletDirective', node);
+ output.writeValue('dartClassName', node.dartClassName);
+ output.depth++;
+ output.writeNodeList('rulesets', node.rules);
+ output.depth--;
+ }
+
+ @override
+ void visitNamespaceDirective(NamespaceDirective node) {
+ heading('NamespaceDirective', node);
+ output.depth++;
+ output.writeValue('prefix', node._prefix);
+ output.writeValue('uri', node._uri);
+ output.depth--;
+ }
+
+ @override
+ void visitVarDefinitionDirective(VarDefinitionDirective node) {
+ heading('Less variable definition', node);
+ output.depth++;
+ visitVarDefinition(node.def);
+ output.depth--;
+ }
+
+ @override
+ void visitMixinRulesetDirective(MixinRulesetDirective node) {
+ heading('Mixin top-level ${node.name}', node);
+ output.writeNodeList('parameters', node.definedArgs);
+ output.depth++;
+ _visitNodeList(node.rulesets);
+ output.depth--;
+ }
+
+ @override
+ void visitMixinDeclarationDirective(MixinDeclarationDirective node) {
+ heading('Mixin declaration ${node.name}', node);
+ output.writeNodeList('parameters', node.definedArgs);
+ output.depth++;
+ visitDeclarationGroup(node.declarations);
+ output.depth--;
+ }
+
+ /// Added optional newLine for handling @include at top-level vs/ inside of
+ /// a declaration group.
+ @override
+ void visitIncludeDirective(IncludeDirective node) {
+ heading('IncludeDirective ${node.name}', node);
+ var flattened = node.args.expand((e) => e).toList();
+ output.writeNodeList('parameters', flattened);
+ }
+
+ @override
+ void visitIncludeMixinAtDeclaration(IncludeMixinAtDeclaration node) {
+ heading('IncludeMixinAtDeclaration ${node.include.name}', node);
+ output.depth++;
+ visitIncludeDirective(node.include);
+ output.depth--;
+ }
+
+ @override
+ void visitExtendDeclaration(ExtendDeclaration node) {
+ heading('ExtendDeclaration', node);
+ output.depth++;
+ _visitNodeList(node.selectors);
+ output.depth--;
+ }
+
+ @override
+ void visitRuleSet(RuleSet node) {
+ heading('Ruleset', node);
+ output.depth++;
+ super.visitRuleSet(node);
+ output.depth--;
+ }
+
+ @override
+ void visitDeclarationGroup(DeclarationGroup node) {
+ heading('DeclarationGroup', node);
+ output.depth++;
+ output.writeNodeList('declarations', node.declarations);
+ output.depth--;
+ }
+
+ @override
+ void visitMarginGroup(MarginGroup node) {
+ heading('MarginGroup', node);
+ output.depth++;
+ output.writeValue('@directive', node.margin_sym);
+ output.writeNodeList('declarations', node.declarations);
+ output.depth--;
+ }
+
+ @override
+ void visitDeclaration(Declaration node) {
+ heading('Declaration', node);
+ output.depth++;
+ if (node.isIE7) output.write('IE7 property');
+ output.write('property');
+ super.visitDeclaration(node);
+ output.writeNode('expression', node.expression);
+ if (node.important) {
+ output.writeValue('!important', 'true');
+ }
+ output.depth--;
+ }
+
+ @override
+ void visitVarDefinition(VarDefinition node) {
+ heading('Var', node);
+ output.depth++;
+ output.write('definition');
+ super.visitVarDefinition(node);
+ output.writeNode('expression', node.expression);
+ output.depth--;
+ }
+
+ @override
+ void visitSelectorGroup(SelectorGroup node) {
+ heading('Selector Group', node);
+ output.depth++;
+ output.writeNodeList('selectors', node.selectors);
+ output.depth--;
+ }
+
+ @override
+ void visitSelector(Selector node) {
+ heading('Selector', node);
+ output.depth++;
+ output.writeNodeList(
+ 'simpleSelectorsSequences', node.simpleSelectorSequences);
+ output.depth--;
+ }
+
+ @override
+ void visitSimpleSelectorSequence(SimpleSelectorSequence node) {
+ heading('SimpleSelectorSequence', node);
+ output.depth++;
+ if (node.isCombinatorNone) {
+ output.writeValue('combinator', 'NONE');
+ } else if (node.isCombinatorDescendant) {
+ output.writeValue('combinator', 'descendant');
+ } else if (node.isCombinatorPlus) {
+ output.writeValue('combinator', '+');
+ } else if (node.isCombinatorGreater) {
+ output.writeValue('combinator', '>');
+ } else if (node.isCombinatorTilde) {
+ output.writeValue('combinator', '~');
+ } else {
+ output.writeValue('combinator', 'ERROR UNKNOWN');
+ }
+
+ super.visitSimpleSelectorSequence(node);
+
+ output.depth--;
+ }
+
+ @override
+ void visitNamespaceSelector(NamespaceSelector node) {
+ heading('Namespace Selector', node);
+ output.depth++;
+
+ super.visitNamespaceSelector(node);
+
+ visitSimpleSelector(node.nameAsSimpleSelector!);
+ output.depth--;
+ }
+
+ @override
+ void visitElementSelector(ElementSelector node) {
+ heading('Element Selector', node);
+ output.depth++;
+ super.visitElementSelector(node);
+ output.depth--;
+ }
+
+ @override
+ void visitAttributeSelector(AttributeSelector node) {
+ heading('AttributeSelector', node);
+ output.depth++;
+ super.visitAttributeSelector(node);
+ var tokenStr = node.matchOperatorAsTokenString();
+ output.writeValue('operator', '${node.matchOperator()} ($tokenStr)');
+ output.writeValue('value', node.valueToString());
+ output.depth--;
+ }
+
+ @override
+ void visitIdSelector(IdSelector node) {
+ heading('Id Selector', node);
+ output.depth++;
+ super.visitIdSelector(node);
+ output.depth--;
+ }
+
+ @override
+ void visitClassSelector(ClassSelector node) {
+ heading('Class Selector', node);
+ output.depth++;
+ super.visitClassSelector(node);
+ output.depth--;
+ }
+
+ @override
+ void visitPseudoClassSelector(PseudoClassSelector node) {
+ heading('Pseudo Class Selector', node);
+ output.depth++;
+ super.visitPseudoClassSelector(node);
+ output.depth--;
+ }
+
+ @override
+ void visitPseudoElementSelector(PseudoElementSelector node) {
+ heading('Pseudo Element Selector', node);
+ output.depth++;
+ super.visitPseudoElementSelector(node);
+ output.depth--;
+ }
+
+ @override
+ void visitPseudoClassFunctionSelector(PseudoClassFunctionSelector node) {
+ heading('Pseudo Class Function Selector', node);
+ output.depth++;
+ node.argument.visit(this);
+ super.visitPseudoClassFunctionSelector(node);
+ output.depth--;
+ }
+
+ @override
+ void visitPseudoElementFunctionSelector(PseudoElementFunctionSelector node) {
+ heading('Pseudo Element Function Selector', node);
+ output.depth++;
+ visitSelectorExpression(node.expression);
+ super.visitPseudoElementFunctionSelector(node);
+ output.depth--;
+ }
+
+ @override
+ void visitSelectorExpression(SelectorExpression node) {
+ heading('Selector Expression', node);
+ output.depth++;
+ output.writeNodeList('expressions', node.expressions);
+ output.depth--;
+ }
+
+ @override
+ void visitNegationSelector(NegationSelector node) {
+ super.visitNegationSelector(node);
+ output.depth++;
+ heading('Negation Selector', node);
+ output.writeNode('Negation arg', node.negationArg);
+ output.depth--;
+ }
+
+ @override
+ void visitUnicodeRangeTerm(UnicodeRangeTerm node) {
+ heading('UnicodeRangeTerm', node);
+ output.depth++;
+ output.writeValue('1st value', node.first);
+ output.writeValue('2nd value', node.second);
+ output.depth--;
+ }
+
+ @override
+ void visitLiteralTerm(LiteralTerm node) {
+ heading('LiteralTerm', node);
+ output.depth++;
+ output.writeValue('value', node.text);
+ output.depth--;
+ }
+
+ @override
+ void visitHexColorTerm(HexColorTerm node) {
+ heading('HexColorTerm', node);
+ output.depth++;
+ output.writeValue('hex value', node.text);
+ output.writeValue('decimal value', node.value);
+ output.depth--;
+ }
+
+ @override
+ void visitNumberTerm(NumberTerm node) {
+ heading('NumberTerm', node);
+ output.depth++;
+ output.writeValue('value', node.text);
+ output.depth--;
+ }
+
+ @override
+ void visitUnitTerm(UnitTerm node) {
+ output.depth++;
+ output.writeValue('value', node.text);
+ output.writeValue('unit', node.unitToString());
+ output.depth--;
+ }
+
+ @override
+ void visitLengthTerm(LengthTerm node) {
+ heading('LengthTerm', node);
+ super.visitLengthTerm(node);
+ }
+
+ @override
+ void visitPercentageTerm(PercentageTerm node) {
+ heading('PercentageTerm', node);
+ output.depth++;
+ super.visitPercentageTerm(node);
+ output.depth--;
+ }
+
+ @override
+ void visitEmTerm(EmTerm node) {
+ heading('EmTerm', node);
+ output.depth++;
+ super.visitEmTerm(node);
+ output.depth--;
+ }
+
+ @override
+ void visitExTerm(ExTerm node) {
+ heading('ExTerm', node);
+ output.depth++;
+ super.visitExTerm(node);
+ output.depth--;
+ }
+
+ @override
+ void visitAngleTerm(AngleTerm node) {
+ heading('AngleTerm', node);
+ super.visitAngleTerm(node);
+ }
+
+ @override
+ void visitTimeTerm(TimeTerm node) {
+ heading('TimeTerm', node);
+ super.visitTimeTerm(node);
+ }
+
+ @override
+ void visitFreqTerm(FreqTerm node) {
+ heading('FreqTerm', node);
+ super.visitFreqTerm(node);
+ }
+
+ @override
+ void visitFractionTerm(FractionTerm node) {
+ heading('FractionTerm', node);
+ output.depth++;
+ super.visitFractionTerm(node);
+ output.depth--;
+ }
+
+ @override
+ void visitUriTerm(UriTerm node) {
+ heading('UriTerm', node);
+ output.depth++;
+ super.visitUriTerm(node);
+ output.depth--;
+ }
+
+ @override
+ void visitFunctionTerm(FunctionTerm node) {
+ heading('FunctionTerm', node);
+ output.depth++;
+ super.visitFunctionTerm(node);
+ output.depth--;
+ }
+
+ @override
+ void visitGroupTerm(GroupTerm node) {
+ heading('GroupTerm', node);
+ output.depth++;
+ output.writeNodeList('grouped terms', node._terms);
+ output.depth--;
+ }
+
+ @override
+ void visitItemTerm(ItemTerm node) {
+ heading('ItemTerm', node);
+ super.visitItemTerm(node);
+ }
+
+ @override
+ void visitIE8Term(IE8Term node) {
+ heading('IE8Term', node);
+ visitLiteralTerm(node);
+ }
+
+ @override
+ void visitOperatorSlash(OperatorSlash node) {
+ heading('OperatorSlash', node);
+ }
+
+ @override
+ void visitOperatorComma(OperatorComma node) {
+ heading('OperatorComma', node);
+ }
+
+ @override
+ void visitOperatorPlus(OperatorPlus node) {
+ heading('OperatorPlus', node);
+ }
+
+ @override
+ void visitOperatorMinus(OperatorMinus node) {
+ heading('OperatorMinus', node);
+ }
+
+ @override
+ void visitVarUsage(VarUsage node) {
+ heading('Var', node);
+ output.depth++;
+ output.write('usage ${node.name}');
+ output.writeNodeList('default values', node.defaultValues);
+ output.depth--;
+ }
+
+ @override
+ void visitExpressions(Expressions node) {
+ heading('Expressions', node);
+ output.depth++;
+ output.writeNodeList('expressions', node.expressions);
+ output.depth--;
+ }
+
+ @override
+ void visitBinaryExpression(BinaryExpression node) {
+ heading('BinaryExpression', node);
+ // TODO(terry): TBD
+ }
+
+ @override
+ void visitUnaryExpression(UnaryExpression node) {
+ heading('UnaryExpression', node);
+ // TODO(terry): TBD
+ }
+
+ @override
+ void visitIdentifier(Identifier node) {
+ heading('Identifier(${output.toValue(node.name)})', node);
+ }
+
+ @override
+ void visitWildcard(Wildcard node) {
+ heading('Wildcard(*)', node);
+ }
+
+ @override
+ void visitDartStyleExpression(DartStyleExpression node) {
+ heading('DartStyleExpression', node);
+ }
+
+ @override
+ void visitFontExpression(FontExpression node) {
+ heading('Dart Style FontExpression', node);
+ }
+
+ @override
+ void visitBoxExpression(BoxExpression node) {
+ heading('Dart Style BoxExpression', node);
+ }
+
+ @override
+ void visitMarginExpression(MarginExpression node) {
+ heading('Dart Style MarginExpression', node);
+ }
+
+ @override
+ void visitBorderExpression(BorderExpression node) {
+ heading('Dart Style BorderExpression', node);
+ }
+
+ @override
+ void visitHeightExpression(HeightExpression node) {
+ heading('Dart Style HeightExpression', node);
+ }
+
+ @override
+ void visitPaddingExpression(PaddingExpression node) {
+ heading('Dart Style PaddingExpression', node);
+ }
+
+ @override
+ void visitWidthExpression(WidthExpression node) {
+ heading('Dart Style WidthExpression', node);
+ }
+}
diff --git a/pkgs/csslib/lib/src/validate.dart b/pkgs/csslib/lib/src/validate.dart
new file mode 100644
index 0000000..608c093
--- /dev/null
+++ b/pkgs/csslib/lib/src/validate.dart
@@ -0,0 +1,121 @@
+// 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 'package:source_span/source_span.dart';
+
+import '../visitor.dart';
+
+/// Can be thrown on any Css runtime problem includes source location.
+class CssSelectorException extends SourceSpanException {
+ CssSelectorException(super.message, [super.span]);
+}
+
+List<String> classes = [];
+List<String> ids = [];
+
+class Validate {
+ static int _classNameCheck(SimpleSelectorSequence selector, int matches) {
+ if (selector.isCombinatorDescendant ||
+ (selector.isCombinatorNone && matches == 0)) {
+ if (matches < 0) {
+ var tooMany = selector.simpleSelector.toString();
+ throw CssSelectorException(
+ 'Can not mix Id selector with class selector(s). Id '
+ 'selector must be singleton too many starting at $tooMany');
+ }
+
+ return matches + 1;
+ } else {
+ var error = selector.toString();
+ throw CssSelectorException(
+ 'Selectors can not have combinators (>, +, or ~) before $error');
+ }
+ }
+
+ static int _elementIdCheck(SimpleSelectorSequence selector, int matches) {
+ if (selector.isCombinatorNone && matches == 0) {
+ // Perfect just one element id returns matches of -1.
+ return -1;
+ } else if (selector.isCombinatorDescendant) {
+ var tooMany = selector.simpleSelector.toString();
+ throw CssSelectorException(
+ 'Use of Id selector must be singleton starting at $tooMany');
+ } else {
+ var error = selector.simpleSelector.toString();
+ throw CssSelectorException(
+ 'Selectors can not have combinators (>, +, or ~) before $error');
+ }
+ }
+
+ // Validate the @{css expression} only .class and #elementId are valid inside
+ // of @{...}.
+ static void template(List<Selector> selectors) {
+ var found = false; // signal if a selector is matched.
+ var matches = 0; // < 0 IdSelectors, > 0 ClassSelector
+
+ // At most one selector group (any number of simple selector sequences).
+ assert(selectors.length <= 1);
+
+ for (final sels in selectors) {
+ for (final selector in sels.simpleSelectorSequences) {
+ found = false;
+ var simpleSelector = selector.simpleSelector;
+ if (simpleSelector is ClassSelector) {
+ // Any class name starting with an underscore is a private class name
+ // that doesn't have to match the world of known classes.
+ if (!simpleSelector.name.startsWith('_')) {
+ // TODO(terry): For now iterate through all classes look for faster
+ // mechanism hash map, etc.
+ for (final className in classes) {
+ if (selector.simpleSelector.name == className) {
+ matches = _classNameCheck(selector, matches);
+ found = true; // .class found.
+ break;
+ }
+ for (final className2 in classes) {
+ print(className2);
+ }
+ }
+ } else {
+ // Don't check any class name that is prefixed with an underscore.
+ // However, signal as found and bump up matches; it's a valid class
+ // name.
+ matches = _classNameCheck(selector, matches);
+ found = true; // ._class are always okay.
+ }
+ } else if (simpleSelector is IdSelector) {
+ // Any element id starting with an underscore is a private element id
+ // that doesn't have to match the world of known element ids.
+ if (!simpleSelector.name.startsWith('_')) {
+ for (final id in ids) {
+ if (simpleSelector.name == id) {
+ matches = _elementIdCheck(selector, matches);
+ found = true; // #id found.
+ break;
+ }
+ }
+ } else {
+ // Don't check any element ID that is prefixed with an underscore.
+ // Signal as found and bump up matches; it's a valid element ID.
+ matches = _elementIdCheck(selector, matches);
+ found = true; // #_id are always okay
+ }
+ } else {
+ var badSelector = simpleSelector.toString();
+ throw CssSelectorException('Invalid template selector $badSelector');
+ }
+
+ if (!found) {
+ var unknownName = simpleSelector.toString();
+ throw CssSelectorException('Unknown selector name $unknownName');
+ }
+ }
+ }
+
+ // Every selector must match.
+ var selector = selectors[0];
+ assert((matches >= 0 ? matches : -matches) ==
+ selector.simpleSelectorSequences.length);
+ }
+}
diff --git a/pkgs/csslib/lib/visitor.dart b/pkgs/csslib/lib/visitor.dart
new file mode 100644
index 0000000..4cfd9f0
--- /dev/null
+++ b/pkgs/csslib/lib/visitor.dart
@@ -0,0 +1,596 @@
+// 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 'package:source_span/source_span.dart';
+import 'parser.dart';
+
+part 'src/css_printer.dart';
+part 'src/tree.dart';
+part 'src/tree_base.dart';
+part 'src/tree_printer.dart';
+
+abstract class VisitorBase {
+ dynamic visitCalcTerm(CalcTerm node);
+ dynamic visitCssComment(CssComment node);
+ dynamic visitCommentDefinition(CommentDefinition node);
+ dynamic visitStyleSheet(StyleSheet node);
+ dynamic visitNoOp(NoOp node);
+ dynamic visitTopLevelProduction(TopLevelProduction node);
+ dynamic visitDirective(Directive node);
+ dynamic visitDocumentDirective(DocumentDirective node);
+ dynamic visitSupportsDirective(SupportsDirective node);
+ dynamic visitSupportsConditionInParens(SupportsConditionInParens node);
+ dynamic visitSupportsNegation(SupportsNegation node);
+ dynamic visitSupportsConjunction(SupportsConjunction node);
+ dynamic visitSupportsDisjunction(SupportsDisjunction node);
+ dynamic visitViewportDirective(ViewportDirective node);
+ dynamic visitMediaExpression(MediaExpression node);
+ dynamic visitMediaQuery(MediaQuery node);
+ dynamic visitMediaDirective(MediaDirective node);
+ dynamic visitHostDirective(HostDirective node);
+ dynamic visitPageDirective(PageDirective node);
+ dynamic visitCharsetDirective(CharsetDirective node);
+ dynamic visitImportDirective(ImportDirective node);
+ dynamic visitKeyFrameDirective(KeyFrameDirective node);
+ dynamic visitKeyFrameBlock(KeyFrameBlock node);
+ dynamic visitFontFaceDirective(FontFaceDirective node);
+ dynamic visitStyletDirective(StyletDirective node);
+ dynamic visitNamespaceDirective(NamespaceDirective node);
+ dynamic visitVarDefinitionDirective(VarDefinitionDirective node);
+ dynamic visitMixinDefinition(MixinDefinition node);
+ dynamic visitMixinRulesetDirective(MixinRulesetDirective node);
+ dynamic visitMixinDeclarationDirective(MixinDeclarationDirective node);
+ dynamic visitIncludeDirective(IncludeDirective node);
+ dynamic visitContentDirective(ContentDirective node);
+
+ dynamic visitRuleSet(RuleSet node);
+ dynamic visitDeclarationGroup(DeclarationGroup node);
+ dynamic visitMarginGroup(MarginGroup node);
+ dynamic visitDeclaration(Declaration node);
+ dynamic visitVarDefinition(VarDefinition node);
+ dynamic visitIncludeMixinAtDeclaration(IncludeMixinAtDeclaration node);
+ dynamic visitExtendDeclaration(ExtendDeclaration node);
+ dynamic visitSelectorGroup(SelectorGroup node);
+ dynamic visitSelector(Selector node);
+ dynamic visitSimpleSelectorSequence(SimpleSelectorSequence node);
+ dynamic visitSimpleSelector(SimpleSelector node);
+ dynamic visitElementSelector(ElementSelector node);
+ dynamic visitNamespaceSelector(NamespaceSelector node);
+ dynamic visitAttributeSelector(AttributeSelector node);
+ dynamic visitIdSelector(IdSelector node);
+ dynamic visitClassSelector(ClassSelector node);
+ dynamic visitPseudoClassSelector(PseudoClassSelector node);
+ dynamic visitPseudoElementSelector(PseudoElementSelector node);
+ dynamic visitPseudoClassFunctionSelector(PseudoClassFunctionSelector node);
+ dynamic visitPseudoElementFunctionSelector(
+ PseudoElementFunctionSelector node);
+ dynamic visitNegationSelector(NegationSelector node);
+ dynamic visitSelectorExpression(SelectorExpression node);
+
+ dynamic visitUnicodeRangeTerm(UnicodeRangeTerm node);
+ dynamic visitLiteralTerm(LiteralTerm node);
+ dynamic visitHexColorTerm(HexColorTerm node);
+ dynamic visitNumberTerm(NumberTerm node);
+ dynamic visitUnitTerm(UnitTerm node);
+ dynamic visitLengthTerm(LengthTerm node);
+ dynamic visitPercentageTerm(PercentageTerm node);
+ dynamic visitEmTerm(EmTerm node);
+ dynamic visitExTerm(ExTerm node);
+ dynamic visitAngleTerm(AngleTerm node);
+ dynamic visitTimeTerm(TimeTerm node);
+ dynamic visitFreqTerm(FreqTerm node);
+ dynamic visitFractionTerm(FractionTerm node);
+ dynamic visitUriTerm(UriTerm node);
+ dynamic visitResolutionTerm(ResolutionTerm node);
+ dynamic visitChTerm(ChTerm node);
+ dynamic visitRemTerm(RemTerm node);
+ dynamic visitViewportTerm(ViewportTerm node);
+ dynamic visitLineHeightTerm(LineHeightTerm node);
+ dynamic visitFunctionTerm(FunctionTerm node);
+ dynamic visitGroupTerm(GroupTerm node);
+ dynamic visitItemTerm(ItemTerm node);
+ dynamic visitIE8Term(IE8Term node);
+ dynamic visitOperatorSlash(OperatorSlash node);
+ dynamic visitOperatorComma(OperatorComma node);
+ dynamic visitOperatorPlus(OperatorPlus node);
+ dynamic visitOperatorMinus(OperatorMinus node);
+ dynamic visitVarUsage(VarUsage node);
+
+ dynamic visitExpressions(Expressions node);
+ dynamic visitBinaryExpression(BinaryExpression node);
+ dynamic visitUnaryExpression(UnaryExpression node);
+
+ dynamic visitIdentifier(Identifier node);
+ dynamic visitWildcard(Wildcard node);
+ dynamic visitThisOperator(ThisOperator node);
+ dynamic visitNegation(Negation node);
+
+ dynamic visitDartStyleExpression(DartStyleExpression node);
+ dynamic visitFontExpression(FontExpression node);
+ dynamic visitBoxExpression(BoxExpression node);
+ dynamic visitMarginExpression(MarginExpression node);
+ dynamic visitBorderExpression(BorderExpression node);
+ dynamic visitHeightExpression(HeightExpression node);
+ dynamic visitPaddingExpression(PaddingExpression node);
+ dynamic visitWidthExpression(WidthExpression node);
+}
+
+/// Base vistor class for the style sheet AST.
+class Visitor implements VisitorBase {
+ /// Helper function to walk a list of nodes.
+ void _visitNodeList(List<TreeNode> list) {
+ // Don't use iterable otherwise the list can't grow while using Visitor.
+ // It certainly can't have items deleted before the index being iterated
+ // but items could be added after the index.
+ for (var index = 0; index < list.length; index++) {
+ list[index].visit(this);
+ }
+ }
+
+ dynamic visitTree(StyleSheet tree) => visitStyleSheet(tree);
+
+ @override
+ dynamic visitStyleSheet(StyleSheet ss) {
+ _visitNodeList(ss.topLevels);
+ }
+
+ @override
+ dynamic visitNoOp(NoOp node) {}
+
+ @override
+ dynamic visitTopLevelProduction(TopLevelProduction node) {}
+
+ @override
+ dynamic visitDirective(Directive node) {}
+
+ @override
+ dynamic visitCalcTerm(CalcTerm node) {
+ visitLiteralTerm(node);
+ visitLiteralTerm(node.expr);
+ }
+
+ @override
+ dynamic visitCssComment(CssComment node) {}
+
+ @override
+ dynamic visitCommentDefinition(CommentDefinition node) {}
+
+ @override
+ dynamic visitMediaExpression(MediaExpression node) {
+ visitExpressions(node.exprs);
+ }
+
+ @override
+ dynamic visitMediaQuery(MediaQuery node) {
+ for (var mediaExpr in node.expressions) {
+ visitMediaExpression(mediaExpr);
+ }
+ }
+
+ @override
+ dynamic visitDocumentDirective(DocumentDirective node) {
+ _visitNodeList(node.functions);
+ _visitNodeList(node.groupRuleBody);
+ }
+
+ @override
+ dynamic visitSupportsDirective(SupportsDirective node) {
+ node.condition!.visit(this);
+ _visitNodeList(node.groupRuleBody);
+ }
+
+ @override
+ dynamic visitSupportsConditionInParens(SupportsConditionInParens node) {
+ node.condition!.visit(this);
+ }
+
+ @override
+ dynamic visitSupportsNegation(SupportsNegation node) {
+ node.condition.visit(this);
+ }
+
+ @override
+ dynamic visitSupportsConjunction(SupportsConjunction node) {
+ _visitNodeList(node.conditions);
+ }
+
+ @override
+ dynamic visitSupportsDisjunction(SupportsDisjunction node) {
+ _visitNodeList(node.conditions);
+ }
+
+ @override
+ dynamic visitViewportDirective(ViewportDirective node) {
+ node.declarations.visit(this);
+ }
+
+ @override
+ dynamic visitMediaDirective(MediaDirective node) {
+ _visitNodeList(node.mediaQueries);
+ _visitNodeList(node.rules);
+ }
+
+ @override
+ dynamic visitHostDirective(HostDirective node) {
+ _visitNodeList(node.rules);
+ }
+
+ @override
+ dynamic visitPageDirective(PageDirective node) {
+ for (var declGroup in node._declsMargin) {
+ if (declGroup is MarginGroup) {
+ visitMarginGroup(declGroup);
+ } else {
+ visitDeclarationGroup(declGroup);
+ }
+ }
+ }
+
+ @override
+ dynamic visitCharsetDirective(CharsetDirective node) {}
+
+ @override
+ dynamic visitImportDirective(ImportDirective node) {
+ for (var mediaQuery in node.mediaQueries) {
+ visitMediaQuery(mediaQuery);
+ }
+ }
+
+ @override
+ dynamic visitKeyFrameDirective(KeyFrameDirective node) {
+ visitIdentifier(node.name!);
+ _visitNodeList(node._blocks);
+ }
+
+ @override
+ dynamic visitKeyFrameBlock(KeyFrameBlock node) {
+ visitExpressions(node._blockSelectors);
+ visitDeclarationGroup(node._declarations);
+ }
+
+ @override
+ dynamic visitFontFaceDirective(FontFaceDirective node) {
+ visitDeclarationGroup(node._declarations);
+ }
+
+ @override
+ dynamic visitStyletDirective(StyletDirective node) {
+ _visitNodeList(node.rules);
+ }
+
+ @override
+ dynamic visitNamespaceDirective(NamespaceDirective node) {}
+
+ @override
+ dynamic visitVarDefinitionDirective(VarDefinitionDirective node) {
+ visitVarDefinition(node.def);
+ }
+
+ @override
+ dynamic visitMixinRulesetDirective(MixinRulesetDirective node) {
+ _visitNodeList(node.rulesets);
+ }
+
+ @override
+ dynamic visitMixinDefinition(MixinDefinition node) {}
+
+ @override
+ dynamic visitMixinDeclarationDirective(MixinDeclarationDirective node) {
+ visitDeclarationGroup(node.declarations);
+ }
+
+ @override
+ dynamic visitIncludeDirective(IncludeDirective node) {
+ for (var index = 0; index < node.args.length; index++) {
+ var param = node.args[index];
+ _visitNodeList(param);
+ }
+ }
+
+ @override
+ dynamic visitContentDirective(ContentDirective node) {
+ // TODO(terry): TBD
+ }
+
+ @override
+ dynamic visitRuleSet(RuleSet node) {
+ visitSelectorGroup(node.selectorGroup!);
+ visitDeclarationGroup(node.declarationGroup);
+ }
+
+ @override
+ dynamic visitDeclarationGroup(DeclarationGroup node) {
+ _visitNodeList(node.declarations);
+ }
+
+ @override
+ dynamic visitMarginGroup(MarginGroup node) => visitDeclarationGroup(node);
+
+ @override
+ dynamic visitDeclaration(Declaration node) {
+ visitIdentifier(node._property!);
+ if (node.expression != null) node.expression!.visit(this);
+ }
+
+ @override
+ dynamic visitVarDefinition(VarDefinition node) {
+ visitIdentifier(node._property!);
+ if (node.expression != null) node.expression!.visit(this);
+ }
+
+ @override
+ dynamic visitIncludeMixinAtDeclaration(IncludeMixinAtDeclaration node) {
+ visitIncludeDirective(node.include);
+ }
+
+ @override
+ dynamic visitExtendDeclaration(ExtendDeclaration node) {
+ _visitNodeList(node.selectors);
+ }
+
+ @override
+ dynamic visitSelectorGroup(SelectorGroup node) {
+ _visitNodeList(node.selectors);
+ }
+
+ @override
+ dynamic visitSelector(Selector node) {
+ _visitNodeList(node.simpleSelectorSequences);
+ }
+
+ @override
+ dynamic visitSimpleSelectorSequence(SimpleSelectorSequence node) {
+ node.simpleSelector.visit(this);
+ }
+
+ @override
+ dynamic visitSimpleSelector(SimpleSelector node) =>
+ (node._name as TreeNode).visit(this);
+
+ @override
+ dynamic visitNamespaceSelector(NamespaceSelector node) {
+ if (node._namespace != null) (node._namespace as TreeNode).visit(this);
+ if (node.nameAsSimpleSelector != null) {
+ node.nameAsSimpleSelector!.visit(this);
+ }
+ }
+
+ @override
+ dynamic visitElementSelector(ElementSelector node) =>
+ visitSimpleSelector(node);
+
+ @override
+ dynamic visitAttributeSelector(AttributeSelector node) {
+ visitSimpleSelector(node);
+ }
+
+ @override
+ dynamic visitIdSelector(IdSelector node) => visitSimpleSelector(node);
+
+ @override
+ dynamic visitClassSelector(ClassSelector node) => visitSimpleSelector(node);
+
+ @override
+ dynamic visitPseudoClassSelector(PseudoClassSelector node) =>
+ visitSimpleSelector(node);
+
+ @override
+ dynamic visitPseudoElementSelector(PseudoElementSelector node) =>
+ visitSimpleSelector(node);
+
+ @override
+ dynamic visitPseudoClassFunctionSelector(PseudoClassFunctionSelector node) =>
+ visitSimpleSelector(node);
+
+ @override
+ dynamic visitPseudoElementFunctionSelector(
+ PseudoElementFunctionSelector node) =>
+ visitSimpleSelector(node);
+
+ @override
+ dynamic visitNegationSelector(NegationSelector node) =>
+ visitSimpleSelector(node);
+
+ @override
+ dynamic visitSelectorExpression(SelectorExpression node) {
+ _visitNodeList(node.expressions);
+ }
+
+ @override
+ dynamic visitUnicodeRangeTerm(UnicodeRangeTerm node) {}
+
+ @override
+ dynamic visitLiteralTerm(LiteralTerm node) {}
+
+ @override
+ dynamic visitHexColorTerm(HexColorTerm node) {}
+
+ @override
+ dynamic visitNumberTerm(NumberTerm node) {}
+
+ @override
+ dynamic visitUnitTerm(UnitTerm node) {}
+
+ @override
+ dynamic visitLengthTerm(LengthTerm node) {
+ visitUnitTerm(node);
+ }
+
+ @override
+ dynamic visitPercentageTerm(PercentageTerm node) {
+ visitLiteralTerm(node);
+ }
+
+ @override
+ dynamic visitEmTerm(EmTerm node) {
+ visitLiteralTerm(node);
+ }
+
+ @override
+ dynamic visitExTerm(ExTerm node) {
+ visitLiteralTerm(node);
+ }
+
+ @override
+ dynamic visitAngleTerm(AngleTerm node) {
+ visitUnitTerm(node);
+ }
+
+ @override
+ dynamic visitTimeTerm(TimeTerm node) {
+ visitUnitTerm(node);
+ }
+
+ @override
+ dynamic visitFreqTerm(FreqTerm node) {
+ visitUnitTerm(node);
+ }
+
+ @override
+ dynamic visitFractionTerm(FractionTerm node) {
+ visitLiteralTerm(node);
+ }
+
+ @override
+ dynamic visitUriTerm(UriTerm node) {
+ visitLiteralTerm(node);
+ }
+
+ @override
+ dynamic visitResolutionTerm(ResolutionTerm node) {
+ visitUnitTerm(node);
+ }
+
+ @override
+ dynamic visitChTerm(ChTerm node) {
+ visitUnitTerm(node);
+ }
+
+ @override
+ dynamic visitRemTerm(RemTerm node) {
+ visitUnitTerm(node);
+ }
+
+ @override
+ dynamic visitViewportTerm(ViewportTerm node) {
+ visitUnitTerm(node);
+ }
+
+ @override
+ dynamic visitLineHeightTerm(LineHeightTerm node) {
+ visitUnitTerm(node);
+ }
+
+ @override
+ dynamic visitFunctionTerm(FunctionTerm node) {
+ visitLiteralTerm(node);
+ visitExpressions(node._params);
+ }
+
+ @override
+ dynamic visitGroupTerm(GroupTerm node) {
+ for (var term in node._terms) {
+ term.visit(this);
+ }
+ }
+
+ @override
+ dynamic visitItemTerm(ItemTerm node) {
+ visitNumberTerm(node);
+ }
+
+ @override
+ dynamic visitIE8Term(IE8Term node) {}
+
+ @override
+ dynamic visitOperatorSlash(OperatorSlash node) {}
+
+ @override
+ dynamic visitOperatorComma(OperatorComma node) {}
+
+ @override
+ dynamic visitOperatorPlus(OperatorPlus node) {}
+
+ @override
+ dynamic visitOperatorMinus(OperatorMinus node) {}
+
+ @override
+ dynamic visitVarUsage(VarUsage node) {
+ _visitNodeList(node.defaultValues);
+ }
+
+ @override
+ dynamic visitExpressions(Expressions node) {
+ _visitNodeList(node.expressions);
+ }
+
+ @override
+ dynamic visitBinaryExpression(BinaryExpression node) {
+ // TODO(terry): TBD
+ throw UnimplementedError();
+ }
+
+ @override
+ dynamic visitUnaryExpression(UnaryExpression node) {
+ // TODO(terry): TBD
+ throw UnimplementedError();
+ }
+
+ @override
+ dynamic visitIdentifier(Identifier node) {}
+
+ @override
+ dynamic visitWildcard(Wildcard node) {}
+
+ @override
+ dynamic visitThisOperator(ThisOperator node) {}
+
+ @override
+ dynamic visitNegation(Negation node) {}
+
+ @override
+ dynamic visitDartStyleExpression(DartStyleExpression node) {}
+
+ @override
+ dynamic visitFontExpression(FontExpression node) {
+ // TODO(terry): TBD
+ throw UnimplementedError();
+ }
+
+ @override
+ dynamic visitBoxExpression(BoxExpression node) {
+ // TODO(terry): TBD
+ throw UnimplementedError();
+ }
+
+ @override
+ dynamic visitMarginExpression(MarginExpression node) {
+ // TODO(terry): TBD
+ throw UnimplementedError();
+ }
+
+ @override
+ dynamic visitBorderExpression(BorderExpression node) {
+ // TODO(terry): TBD
+ throw UnimplementedError();
+ }
+
+ @override
+ dynamic visitHeightExpression(HeightExpression node) {
+ // TODO(terry): TB
+ throw UnimplementedError();
+ }
+
+ @override
+ dynamic visitPaddingExpression(PaddingExpression node) {
+ // TODO(terry): TBD
+ throw UnimplementedError();
+ }
+
+ @override
+ dynamic visitWidthExpression(WidthExpression node) {
+ // TODO(terry): TBD
+ throw UnimplementedError();
+ }
+}
diff --git a/pkgs/csslib/pubspec.yaml b/pkgs/csslib/pubspec.yaml
new file mode 100644
index 0000000..74e2069
--- /dev/null
+++ b/pkgs/csslib/pubspec.yaml
@@ -0,0 +1,20 @@
+name: csslib
+version: 1.0.2
+description: A library for parsing and analyzing CSS (Cascading Style Sheets).
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/csslib
+issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Acsslib
+
+topics:
+ - css
+
+environment:
+ sdk: ^3.1.0
+
+dependencies:
+ source_span: ^1.8.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ path: ^1.8.0
+ term_glyph: ^1.2.0
+ test: ^1.16.0
diff --git a/pkgs/csslib/test/big_1_test.dart b/pkgs/csslib/test/big_1_test.dart
new file mode 100644
index 0000000..6db7980
--- /dev/null
+++ b/pkgs/csslib/test/big_1_test.dart
@@ -0,0 +1,1165 @@
+// 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:csslib/src/messages.dart';
+import 'package:test/test.dart';
+
+import 'testing.dart';
+
+void compilePolyfillAndValidate(String input, String generated) {
+ var errors = <Message>[];
+ var stylesheet = polyFillCompileCss(input, errors: errors, opts: options);
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated);
+}
+
+void bigTest() {
+ var input = r'''
+/********************************************************************
+ * Kennedy colors
+ ********************************************************************/
+@kennedy-red: #dd4b39;
+@kennedy-blue: #4d90fe;
+@kennedy-green: #3d9400;
+
+/********************************************************************
+ * link colors
+ ********************************************************************/
+@link-color-1: #1155cc;
+@link-color-2: #666666;
+
+/********************************************************************
+ * text and header colors
+ ********************************************************************/
+@text-color-emphasized-1: #222222;
+@text-color-emphasized-2: #333333;
+@text-color-regular: #666666;
+@text-color-deemphasized-1: #777777;
+@text-color-deemphasized-2: #999999;
+
+/********************************************************************
+ * icon colors
+ ********************************************************************/
+@zippy-icon-color: #b2b2b2;
+@zippy-icon-color-hover: #666666;
+
+@mutate-icon-color: #b2b2b2;
+
+@silhouette-color: #8bb7fe;
+
+/********************************************************************
+ * Panel and Card colors
+ ********************************************************************/
+@panel-header-color: #f7f7f7;
+@panel-body-color: #ffffff;
+@panel-border-color: #dcdcdc;
+
+/********************************************************************
+ * App area colors
+ ********************************************************************/
+ @apparea-background-color: #f2f2f2;
+
+/********************************************************************
+ * Table colors
+ ********************************************************************/
+@table-row-numbers: #666666;
+@table-row-item-links: #1155cc;
+@table-row-item: #666666;
+
+/* Borders */
+@table-border-color: #dcdcdc;
+@table-header-border-color: #dcdcdc;
+@table-row-data-border-color: #eaeaea;
+
+/* General column - USED: We are not currently spec'ing different colors
+ * for the currently sorted/unsorted on column.
+ */
+@table-header-label-color: #666666;
+@table-header-background-color: #f8f8f8;
+@table-column-background-color: #ffffff;
+
+
+/* Sorted column - UNUSED: We are not currently spec'ing different colors
+ * for the currently sorted/unsorted on column.
+ */
+@table-sorted-header-label-color: #666666;
+@table-sorted-header-background-color: #e6e6e6;
+@table-sorted-column-background-color: #f8f8f8;
+
+/* Unsorted column - UNUSED: We are not currently spec'ing different colors
+ * for the currently sorted/unsorted on column.
+ */
+@table-unsorted-header-label-color: #999999;
+@table-unsorted-header-background-color: #f8f8f8;
+@table-unsorted-column-background-color: #ffffff;
+
+@acux-border-color-1: #e5e5e5;
+@acux-border-color-2: #3b7bea;
+@acux-link-color: #3b7bea;
+@acux-shell-background-color: #f2f2f2;
+
+/********************************************************************
+ * Tooltip and popup colors
+ ********************************************************************/
+@tooltip-border-color: #333;
+@tooltip-color: #fff;
+@popup-border-color: #fff;
+
+/* Border radii */
+@button-radius: 2px;
+
+@mixin button-gradient(@from, @to) {
+ background-color: @from;
+ background-image: -webkit-linear-gradient(top, @from, @to);
+ background-image: linear-gradient(top, @from, @to);
+}
+
+@mixin button-transition(@property, @time) {
+ -webkit-transition: @property @time;
+ transition: @property @time;
+}
+
+@mixin text-not-selectable() {
+ -webkit-user-select: none;
+ user-select: none;
+}
+
+/*
+ * Buttons and their states
+ */
+@mixin btn-base {
+ display: inline-block;
+ min-width: 62px;
+ text-align: center;
+ font-size: 11px;
+ font-weight: bold;
+ height: 28px;
+ padding: 0 8px;
+ line-height: 27px;
+ border-radius: @button-radius;
+ cursor: default;
+
+ color: #444;
+ border: 1px solid rgba(0,0,0,0.1);
+ @include button-transition(all, 0.218s);
+ @include button-gradient(#f5f5f5, #f1f1f1);
+
+ &:hover {
+ border: 1px solid #C6C6C6;
+ color: #222;
+ box-shadow: 0px 1px 1px rgba(0,0,0,0.1);
+ @include button-transition(all, 0s);
+ @include button-gradient(#f8f8f8, #f1f1f1);
+ }
+
+ &:active {
+ border: 1px solid #C6C6C6;
+ color: #666;
+ box-shadow: inset 0px 1px 2px rgba(0,0,0,0.1);
+ @include button-gradient(#f6f6f6, #f1f1f1);
+ }
+
+ &:focus {
+ outline: none;
+ border: 1px solid #4D90FE;
+ z-index: 4 !important;
+ }
+
+ &.selected, &.popup-open {
+ border: 1px solid #CCC;
+ color: #666;
+ box-shadow: inset 0px 1px 2px rgba(0,0,0,0.1);
+ @include button-gradient(#EEEEEE, #E0E0E0);
+ }
+
+ &.disabled, &.disabled:hover, &.disabled:active,
+ &[disabled], &[disabled]:hover, &[disabled]:active {
+ background: none;
+ color: #b8b8b8;
+ border: 1px solid rgba(0,0,0,0.05);
+ cursor: default;
+ pointer-events: none;
+ }
+
+ &.flat {
+ background: none;
+ border-color: transparent;
+ padding: 0;
+ box-shadow: none;
+ }
+
+ &.invalid {
+ outline: none;
+ border: 1px solid @kennedy-red;
+ box-shadow: inset 0px 1px 2px rgba(0,0,0,0.3);
+ }
+}
+
+.btn-container {
+ padding: 10px;
+}
+
+.btn {
+ @include btn-base;
+}
+
+.btn-small {
+ /* TODO(prsd): Implement using a mix-in. */
+ min-width: 30px;
+}
+
+.btn-left {
+ @include btn-base;
+ border-radius: @button-radius 0 0 @button-radius;
+ margin-right: 0;
+ padding: 0;
+ min-width: 30px;
+}
+
+.btn-right {
+ @include btn-base;
+ border-radius: 0 @button-radius @button-radius 0;
+ border-left: none;
+ margin-left: 0;
+ padding: 0;
+ min-width: 30px;
+}
+
+.btn + .btn {
+ margin-left: 5px;
+}
+
+/* Primary Button and it's states */
+.btn-primary {
+ color: #FFF !important;
+ width: 94px;
+ border-color: #3079ed;
+ @include button-gradient(#4d90fe, #4787ed);
+
+ &:hover, &:active {
+ border-color: #2f5bb7;
+ @include button-gradient(#4d90fe, #357ae8);
+ }
+
+ &:focus {
+ border-color: #4D90FE;
+ box-shadow:inset 0 0 0 1px rgba(255,255,255,0.5);
+ }
+
+ &:focus:hover {
+ box-shadow:inset 0 0 0 1px #fff, 0px 1px 1px rgba(0,0,0,0.1);
+ }
+
+ &.disabled, &.disabled:hover, &.disabled:active,
+ &[disabled], &[disabled]:hover, &[disabled]:active {
+ border-color:#3079ed;
+ background-color: #4d90fe;
+ opacity: 0.7;
+ }
+}
+
+/* Checkbox displayed as a toggled button
+ * Invisible checkbox followed by a label with 'for' set to checkbox */
+input[type="checkbox"].toggle-button {
+ display: none;
+
+ & + label {
+ @extend .btn;
+ }
+
+ &:checked + label,
+ & + label.popup-open {
+ border: 1px solid #CCC;
+ color: #666;
+ box-shadow: inset 0px 1px 2px rgba(0,0,0,0.1);
+ @include button-gradient(#EEEEEE, #E0E0E0);
+ }
+}
+
+.txt-input {
+ display:inline-block;
+ *display:inline;
+ *zoom:1;
+ padding:4px 12px;
+ margin-bottom:0;
+ font-size:14px;
+ line-height:20px;
+ vertical-align:middle;
+ color:#333333;
+ border-color:#e6e6e6 #e6e6e6 #bfbfbf;
+ border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ border:1px solid #cccccc;
+ *border:0;
+ border-bottom-color:#b3b3b3;
+ -webkit-border-radius:4px;
+ -moz-border-radius:4px;
+ border-radius:4px;
+ *margin-left:.3em;
+ -webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px
+ rgba(0,0,0,.05);
+ -moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
+ box-shadow:inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
+}
+
+input[type="text"], input:not([type]), .txt-input {
+ height: 29px;
+ background-color: white;
+ padding: 4px 0 4px 8px;
+ color: 333;
+ border: 1px solid #d9d9d9;
+ border-top: 1px solid #c0c0c0;
+ display: inline-block;
+ vertical-align: top;
+ box-sizing: border-box;
+ border-radius: 1px;
+
+ &:hover {
+ border: 1px solid #b9b9b9;
+ border-top: 1px solid #a0a0a0;
+ box-shadow: inset 0px 1px 2px rgba(0,0,0,0.1);
+ }
+
+ &:focus {
+ outline: none;
+ border: 1px solid @kennedy-blue;
+ box-shadow: inset 0px 1px 2px rgba(0,0,0,0.3);
+ }
+
+ &.disabled, &.disabled:hover, &.disabled:active, &:disabled {
+ background: #fff;
+ border: 1px solid #f3f3f3;
+ border: 1px solid rgba(0,0,0,0.05);
+ color: #b8b8b8;
+ cursor: default;
+ pointer-events: none;
+ }
+
+ &.invalid, &:focus:invalid, &:required:invalid {
+ outline: none;
+ border: 1px solid @kennedy-red;
+ box-shadow: inset 0px 1px 2px rgba(0,0,0,0.3);
+ }
+}
+
+/* Text area */
+textarea {
+ @extend .txt-input;
+ height: 3em;
+}
+
+/* Hide the spin button in datepickers */
+input[type="date"]::-webkit-inner-spin-button,
+input[type="datetime"]::-webkit-inner-spin-button,
+input[type="datetime-local"]::-webkit-inner-spin-button,
+input[type="month"]::-webkit-inner-spin-button,
+input[type="time"]::-webkit-inner-spin-button,
+input[type="week"]::-webkit-inner-spin-button {
+ display: none;
+}
+
+
+/*
+ * Selects & Dropdowns
+ */
+.dropdown-menu,
+.popup {
+ width: auto;
+ padding: 0;
+ margin: 0 0 0 1px;
+ background: white;
+ text-align: left;
+ z-index: 1000;
+ outline: 1px solid rgba(0,0,0,0.2);
+ white-space: nowrap;
+ list-style: none;
+ box-shadow: 0px 2px 4px rgba(0,0,0,0.2);
+ @include button-transition(opacity, 0.218s);
+}
+
+.popup {
+ padding: 0 0 6px;
+}
+.dropdown-menu,
+.popup {
+ pointer-events: all;
+}
+
+.popup ul {
+ margin: 0;
+ padding: 0;
+}
+.popup li {
+ list-style-type: none;
+ padding: 5px 10px;
+ cursor: default;
+}
+.popup .header {
+ padding: 5px 10px;
+}
+
+ /* existing styles defined here */
+.popup .divider,
+.dropdown-menu .divider {
+ width:100%;
+ height:1px;
+ padding: 0;
+ overflow:hidden;
+ background-color:#c0c0c0;
+ border-bottom:1px solid @popup-border-color;
+}
+
+.dropdown-menu {
+ max-height: 600px;
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+.popup {
+ overflow: hidden;
+}
+
+.dropdown-menuitem,
+.dropdown-menu > li {
+ display: block;
+ padding: 6px 44px 6px 16px;
+ color: #666;
+ font-size:13px;
+ font-weight: normal;
+ cursor: default;
+ margin: 0;
+ text-decoration: none;
+ @include text-not-selectable();
+
+ &.disabled {
+ color: #CCC;
+ background-color: #FFF;
+ }
+
+ &:hover, &.selected {
+ color: #222;
+ background-color: #F1F1F1;
+ }
+}
+
+.dropdown-menuheader {
+ padding: 6px 44px 6px 16px;
+ color: #666;
+ font-size:11px;
+ font-weight: bold;
+ cursor: default;
+ margin: 0;
+ text-decoration: none;
+ background-color: #F1F1F1;
+ @include text-not-selectable();
+}
+
+li.dropdown-menudivider {
+ width:100%;
+ height:1px;
+ padding: 0;
+ overflow:hidden;
+ background-color:#D0D0D0;
+ border-bottom:1px solid #ffffff;
+}
+
+.btn-container {
+ padding: 10px;
+}
+
+/*
+ * Modal dialogs
+ */
+
+.modal-frame {
+ position: relative;
+ background-color: white;
+ outline: 1px solid rgba(0, 0, 0, 0.2);
+ padding: 30px 42px;
+ min-width: 480px;
+ z-index: 9000;
+ pointer-events: auto;
+ box-shadow: 0 4px 16px 0 rgba(0,0,0,0.2);
+
+ @include button-transition(all, 0.218s);
+
+ &.medium {
+ padding: 28px 32px;
+ min-width: 280px;
+ }
+
+ &.small {
+ padding: 16px 20px;
+ }
+
+}
+
+.modal-backdrop {
+ background-color: rgba(0,0,0,0.1);
+ position: fixed;
+ height: 100%;
+ width: 100%;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ z-index: 99;
+ margin: 0;
+
+ display: none;
+ opacity: 0;
+ @include button-transition(all, 0.218s);
+
+ &.visible {
+ display: -webkit-flex;
+ display: flex;
+ -webkit-align-items: center;
+ align-items: center;
+ -webkit-justify-content: space-around;
+ justify-content: space-around;
+ opacity: 1;
+ }
+}
+
+/*
+ * Scrollbars
+ */
+
+::-webkit-scrollbar {
+ width: 10px;
+ height: 10px;
+ background: white;
+}
+
+::-webkit-scrollbar-button {
+ height: 0px;
+ width: 0px;
+
+ &:start:decrement,
+ &:end:increment {
+ display: block;
+ }
+
+ &:vertical:start:increment,
+ &:vertical:end:decrement {
+ display: none;
+ }
+}
+
+::-webkit-scrollbar-thumb {
+ background-color: rgba(0, 0, 0, .2);
+ background-clip: padding-box;
+ border: solid transparent;
+ border-width: 1px 1px 1px 2px;
+ min-height: 28px;
+ padding: 100px 0 0;
+ box-shadow: inset 1px 1px 0 rgba(0, 0, 0, .1),
+ inset 0 -1px 0 rgba(0, 0, 0, .07);
+
+ &:hover {
+ background-color: rgba(0,0,0,0.4);
+ -webkit-box-shadow: inset 1px 1px 1px rgba(0,0,0,0.25);
+ }
+
+ &:active {
+ -webkit-box-shadow: inset 1px 1px 3px rgba(0,0,0,0.35);
+ background-color: rgba(0,0,0,0.5);
+ }
+
+ &:vertical {
+ border-top: 0px solid transparent;
+ border-bottom: 0px solid transparent;
+ border-right: 0px solid transparent;
+ border-left: 1px solid transparent;
+ }
+
+ &:horizontal {
+ border-top: 1px solid transparent;
+ border-bottom: 0px solid transparent;
+ border-right: 0px solid transparent;
+ border-left: 0px solid transparent;
+ }
+}
+
+::-webkit-scrollbar-track {
+ background-clip: padding-box;
+ background-color: white;
+
+ &:hover {
+ background-color: rgba(0,0,0,0.05);
+ -webkit-box-shadow: inset 1px 0px 0px rgba(0,0,0,0.10);
+ }
+
+ &:active {
+ background-color: rgba(0,0,0,0.05);
+ -webkit-box-shadow: inset 1px 0px 0px rgba(0,0,0,0.14),
+ inset -1px -1px 0px rgba(0,0,0,0.07);
+ }
+
+ &:vertical {
+ border-right: 0px solid transparent;
+ border-left: 1px solid transparent;
+ }
+
+ &:horizontal {
+ border-bottom: 0px solid transparent;
+ border-top: 1px solid transparent;
+ }
+}
+
+/* Tooltips */
+.tooltip {
+ background: @tooltip-border-color;
+ border-radius: 2px;
+ color: @tooltip-color;
+ padding: 4px 8px;
+ font-size: 10px;
+}
+
+.tooltip a,
+.tooltip div,
+.tooltip span {
+ color: @tooltip-color;
+}
+''';
+
+ var generated = r'''.btn-container {
+ padding: 10px;
+}
+.btn, input[type="checkbox"].toggle-button + label {
+ display: inline-block;
+ min-width: 62px;
+ text-align: center;
+ font-size: 11px;
+ font-weight: bold;
+ height: 28px;
+ padding: 0 8px;
+ line-height: 27px;
+ border-radius: 2px;
+ cursor: default;
+ color: #444;
+ border: 1px solid rgba(0, 0, 0, 0.1);
+ -webkit-transition: all 0.218s;
+ transition: all 0.218s;
+ background-color: #f5f5f5;
+ background-image: -webkit-linear-gradient(top, #f5f5f5, #f1f1f1);
+ background-image: linear-gradient(top, #f5f5f5, #f1f1f1);
+}
+.btn:hover, input[type="checkbox"].toggle-button + label:hover {
+ border: 1px solid #C6C6C6;
+ color: #222;
+ box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.1);
+ -webkit-transition: all 0s;
+ transition: all 0s;
+ background-color: #f8f8f8;
+ background-image: -webkit-linear-gradient(top, #f8f8f8, #f1f1f1);
+ background-image: linear-gradient(top, #f8f8f8, #f1f1f1);
+}
+.btn:active, input[type="checkbox"].toggle-button + label:active {
+ border: 1px solid #C6C6C6;
+ color: #666;
+ box-shadow: inset 0px 1px 2px rgba(0, 0, 0, 0.1);
+ background-color: #f6f6f6;
+ background-image: -webkit-linear-gradient(top, #f6f6f6, #f1f1f1);
+ background-image: linear-gradient(top, #f6f6f6, #f1f1f1);
+}
+.btn:focus, input[type="checkbox"].toggle-button + label:focus {
+ outline: none;
+ border: 1px solid #4D90FE;
+ z-index: 4 !important;
+}
+.btn.selected, .btn.popup-open, input[type="checkbox"].toggle-button + label.selected, input[type="checkbox"].toggle-button + label.popup-open {
+ border: 1px solid #CCC;
+ color: #666;
+ box-shadow: inset 0px 1px 2px rgba(0, 0, 0, 0.1);
+ background-color: #EEE;
+ background-image: -webkit-linear-gradient(top, #EEE, #E0E0E0);
+ background-image: linear-gradient(top, #EEE, #E0E0E0);
+}
+.btn.disabled, .btn.disabled:hover, .btn.disabled:active, .btn[disabled], .btn[disabled]:hover, .btn[disabled]:active, input[type="checkbox"].toggle-button + label.disabled, input[type="checkbox"].toggle-button + label.disabled:hover, input[type="checkbox"].toggle-button + label.disabled:active, input[type="checkbox"].toggle-button + label[disabled], input[type="checkbox"].toggle-button + label[disabled]:hover, input[type="checkbox"].toggle-button + label[disabled]:active {
+ background: none;
+ color: #b8b8b8;
+ border: 1px solid rgba(0, 0, 0, 0.05);
+ cursor: default;
+ pointer-events: none;
+}
+.btn.flat, input[type="checkbox"].toggle-button + label.flat {
+ background: none;
+ border-color: transparent;
+ padding: 0;
+ box-shadow: none;
+}
+.btn.invalid, input[type="checkbox"].toggle-button + label.invalid {
+ outline: none;
+ border: 1px solid #dd4b39;
+ box-shadow: inset 0px 1px 2px rgba(0, 0, 0, 0.3);
+}
+.btn-small {
+ min-width: 30px;
+}
+.btn-left {
+ display: inline-block;
+ min-width: 62px;
+ text-align: center;
+ font-size: 11px;
+ font-weight: bold;
+ height: 28px;
+ padding: 0 8px;
+ line-height: 27px;
+ border-radius: 2px;
+ cursor: default;
+ color: #444;
+ border: 1px solid rgba(0, 0, 0, 0.1);
+ -webkit-transition: all 0.218s;
+ transition: all 0.218s;
+ background-color: #f5f5f5;
+ background-image: -webkit-linear-gradient(top, #f5f5f5, #f1f1f1);
+ background-image: linear-gradient(top, #f5f5f5, #f1f1f1);
+ border-radius: 2px 0 0 2px;
+ margin-right: 0;
+ padding: 0;
+ min-width: 30px;
+}
+.btn-left:hover {
+ border: 1px solid #C6C6C6;
+ color: #222;
+ box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.1);
+ -webkit-transition: all 0s;
+ transition: all 0s;
+ background-color: #f8f8f8;
+ background-image: -webkit-linear-gradient(top, #f8f8f8, #f1f1f1);
+ background-image: linear-gradient(top, #f8f8f8, #f1f1f1);
+}
+.btn-left:active {
+ border: 1px solid #C6C6C6;
+ color: #666;
+ box-shadow: inset 0px 1px 2px rgba(0, 0, 0, 0.1);
+ background-color: #f6f6f6;
+ background-image: -webkit-linear-gradient(top, #f6f6f6, #f1f1f1);
+ background-image: linear-gradient(top, #f6f6f6, #f1f1f1);
+}
+.btn-left:focus {
+ outline: none;
+ border: 1px solid #4D90FE;
+ z-index: 4 !important;
+}
+.btn-left.selected, .btn-left.popup-open {
+ border: 1px solid #CCC;
+ color: #666;
+ box-shadow: inset 0px 1px 2px rgba(0, 0, 0, 0.1);
+ background-color: #EEE;
+ background-image: -webkit-linear-gradient(top, #EEE, #E0E0E0);
+ background-image: linear-gradient(top, #EEE, #E0E0E0);
+}
+.btn-left.disabled, .btn-left.disabled:hover, .btn-left.disabled:active, .btn-left[disabled], .btn-left[disabled]:hover, .btn-left[disabled]:active {
+ background: none;
+ color: #b8b8b8;
+ border: 1px solid rgba(0, 0, 0, 0.05);
+ cursor: default;
+ pointer-events: none;
+}
+.btn-left.flat {
+ background: none;
+ border-color: transparent;
+ padding: 0;
+ box-shadow: none;
+}
+.btn-left.invalid {
+ outline: none;
+ border: 1px solid #dd4b39;
+ box-shadow: inset 0px 1px 2px rgba(0, 0, 0, 0.3);
+}
+.btn-right {
+ display: inline-block;
+ min-width: 62px;
+ text-align: center;
+ font-size: 11px;
+ font-weight: bold;
+ height: 28px;
+ padding: 0 8px;
+ line-height: 27px;
+ border-radius: 2px;
+ cursor: default;
+ color: #444;
+ border: 1px solid rgba(0, 0, 0, 0.1);
+ -webkit-transition: all 0.218s;
+ transition: all 0.218s;
+ background-color: #f5f5f5;
+ background-image: -webkit-linear-gradient(top, #f5f5f5, #f1f1f1);
+ background-image: linear-gradient(top, #f5f5f5, #f1f1f1);
+ border-radius: 0 2px 2px 0;
+ border-left: none;
+ margin-left: 0;
+ padding: 0;
+ min-width: 30px;
+}
+.btn-right:hover {
+ border: 1px solid #C6C6C6;
+ color: #222;
+ box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.1);
+ -webkit-transition: all 0s;
+ transition: all 0s;
+ background-color: #f8f8f8;
+ background-image: -webkit-linear-gradient(top, #f8f8f8, #f1f1f1);
+ background-image: linear-gradient(top, #f8f8f8, #f1f1f1);
+}
+.btn-right:active {
+ border: 1px solid #C6C6C6;
+ color: #666;
+ box-shadow: inset 0px 1px 2px rgba(0, 0, 0, 0.1);
+ background-color: #f6f6f6;
+ background-image: -webkit-linear-gradient(top, #f6f6f6, #f1f1f1);
+ background-image: linear-gradient(top, #f6f6f6, #f1f1f1);
+}
+.btn-right:focus {
+ outline: none;
+ border: 1px solid #4D90FE;
+ z-index: 4 !important;
+}
+.btn-right.selected, .btn-right.popup-open {
+ border: 1px solid #CCC;
+ color: #666;
+ box-shadow: inset 0px 1px 2px rgba(0, 0, 0, 0.1);
+ background-color: #EEE;
+ background-image: -webkit-linear-gradient(top, #EEE, #E0E0E0);
+ background-image: linear-gradient(top, #EEE, #E0E0E0);
+}
+.btn-right.disabled, .btn-right.disabled:hover, .btn-right.disabled:active, .btn-right[disabled], .btn-right[disabled]:hover, .btn-right[disabled]:active {
+ background: none;
+ color: #b8b8b8;
+ border: 1px solid rgba(0, 0, 0, 0.05);
+ cursor: default;
+ pointer-events: none;
+}
+.btn-right.flat {
+ background: none;
+ border-color: transparent;
+ padding: 0;
+ box-shadow: none;
+}
+.btn-right.invalid {
+ outline: none;
+ border: 1px solid #dd4b39;
+ box-shadow: inset 0px 1px 2px rgba(0, 0, 0, 0.3);
+}
+.btn + .btn, input[type="checkbox"].toggle-button + label + .btn, .btn + input[type="checkbox"].toggle-button + label, input[type="checkbox"].toggle-button + label + input[type="checkbox"].toggle-button + label, input[type="checkbox"].toggle-button + label + input[type="checkbox"].toggle-button + label {
+ margin-left: 5px;
+}
+.btn-primary {
+ color: #FFF !important;
+ width: 94px;
+ border-color: #3079ed;
+ background-color: #4d90fe;
+ background-image: -webkit-linear-gradient(top, #4d90fe, #4787ed);
+ background-image: linear-gradient(top, #4d90fe, #4787ed);
+}
+.btn-primary:hover, .btn-primary:active {
+ border-color: #2f5bb7;
+ background-color: #4d90fe;
+ background-image: -webkit-linear-gradient(top, #4d90fe, #357ae8);
+ background-image: linear-gradient(top, #4d90fe, #357ae8);
+}
+.btn-primary:focus {
+ border-color: #4D90FE;
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.5);
+}
+.btn-primary:focus:hover {
+ box-shadow: inset 0 0 0 1px #fff, 0px 1px 1px rgba(0, 0, 0, 0.1);
+}
+.btn-primary.disabled, .btn-primary.disabled:hover, .btn-primary.disabled:active, .btn-primary[disabled], .btn-primary[disabled]:hover, .btn-primary[disabled]:active {
+ border-color: #3079ed;
+ background-color: #4d90fe;
+ opacity: 0.7;
+}
+input[type="checkbox"].toggle-button {
+ display: none;
+}
+input[type="checkbox"].toggle-button + label {
+}
+input[type="checkbox"].toggle-button:checked + label, input[type="checkbox"].toggle-button + label.popup-open {
+ border: 1px solid #CCC;
+ color: #666;
+ box-shadow: inset 0px 1px 2px rgba(0, 0, 0, 0.1);
+ background-color: #EEE;
+ background-image: -webkit-linear-gradient(top, #EEE, #E0E0E0);
+ background-image: linear-gradient(top, #EEE, #E0E0E0);
+}
+.txt-input, textarea {
+ display: inline-block;
+ *display: inline;
+ *zoom: 1;
+ padding: 4px 12px;
+ margin-bottom: 0;
+ font-size: 14px;
+ line-height: 20px;
+ vertical-align: middle;
+ color: #333;
+ border-color: #e6e6e6 #e6e6e6 #bfbfbf;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ border: 1px solid #ccc;
+ *border: 0;
+ border-bottom-color: #b3b3b3;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ *margin-left: .3em;
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .2), 0 1px 2px rgba(0, 0, 0, .05);
+ -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .2), 0 1px 2px rgba(0, 0, 0, .05);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, .2), 0 1px 2px rgba(0, 0, 0, .05);
+}
+input[type="text"], input:not([type]), .txt-input, textarea {
+ height: 29px;
+ background-color: #fff;
+ padding: 4px 0 4px 8px;
+ color: 333;
+ border: 1px solid #d9d9d9;
+ border-top: 1px solid #c0c0c0;
+ display: inline-block;
+ vertical-align: top;
+ box-sizing: border-box;
+ border-radius: 1px;
+}
+input[type="text"]:hover, input:not([type]):hover, .txt-input:hover, textarea:hover {
+ border: 1px solid #b9b9b9;
+ border-top: 1px solid #a0a0a0;
+ box-shadow: inset 0px 1px 2px rgba(0, 0, 0, 0.1);
+}
+input[type="text"]:focus, input:not([type]):focus, .txt-input:focus, textarea:focus {
+ outline: none;
+ border: 1px solid #4d90fe;
+ box-shadow: inset 0px 1px 2px rgba(0, 0, 0, 0.3);
+}
+input[type="text"].disabled, input:not([type]).disabled, .txt-input.disabled, input[type="text"].disabled:hover, input:not([type]).disabled:hover, .txt-input.disabled:hover, input[type="text"].disabled:active, input:not([type]).disabled:active, .txt-input.disabled:active, input[type="text"]:disabled, input:not([type]):disabled, .txt-input:disabled, textarea.disabled, textarea.disabled:hover, textarea.disabled:active, textarea:disabled {
+ background: #fff;
+ border: 1px solid #f3f3f3;
+ border: 1px solid rgba(0, 0, 0, 0.05);
+ color: #b8b8b8;
+ cursor: default;
+ pointer-events: none;
+}
+input[type="text"].invalid, input:not([type]).invalid, .txt-input.invalid, input[type="text"]:focus:invalid, input:not([type]):focus:invalid, .txt-input:focus:invalid, input[type="text"]:required:invalid, input:not([type]):required:invalid, .txt-input:required:invalid, textarea.invalid, textarea:focus:invalid, textarea:required:invalid {
+ outline: none;
+ border: 1px solid #dd4b39;
+ box-shadow: inset 0px 1px 2px rgba(0, 0, 0, 0.3);
+}
+textarea {
+ height: 3em;
+}
+input[type="date"]::-webkit-inner-spin-button, input[type="datetime"]::-webkit-inner-spin-button, input[type="datetime-local"]::-webkit-inner-spin-button, input[type="month"]::-webkit-inner-spin-button, input[type="time"]::-webkit-inner-spin-button, input[type="week"]::-webkit-inner-spin-button {
+ display: none;
+}
+.dropdown-menu, .popup {
+ width: auto;
+ padding: 0;
+ margin: 0 0 0 1px;
+ background: #fff;
+ text-align: left;
+ z-index: 1000;
+ outline: 1px solid rgba(0, 0, 0, 0.2);
+ white-space: nowrap;
+ list-style: none;
+ box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.2);
+ -webkit-transition: opacity 0.218s;
+ transition: opacity 0.218s;
+}
+.popup {
+ padding: 0 0 6px;
+}
+.dropdown-menu, .popup {
+ pointer-events: all;
+}
+.popup ul {
+ margin: 0;
+ padding: 0;
+}
+.popup li {
+ list-style-type: none;
+ padding: 5px 10px;
+ cursor: default;
+}
+.popup .header {
+ padding: 5px 10px;
+}
+.popup .divider, .dropdown-menu .divider {
+ width: 100%;
+ height: 1px;
+ padding: 0;
+ overflow: hidden;
+ background-color: #c0c0c0;
+ border-bottom: 1px solid #fff;
+}
+.dropdown-menu {
+ max-height: 600px;
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+.popup {
+ overflow: hidden;
+}
+.dropdown-menuitem, .dropdown-menu > li {
+ display: block;
+ padding: 6px 44px 6px 16px;
+ color: #666;
+ font-size: 13px;
+ font-weight: normal;
+ cursor: default;
+ margin: 0;
+ text-decoration: none;
+ -webkit-user-select: none;
+ user-select: none;
+}
+.dropdown-menuitem.disabled, .dropdown-menu > li.disabled {
+ color: #CCC;
+ background-color: #FFF;
+}
+.dropdown-menuitem:hover, .dropdown-menu > li:hover, .dropdown-menuitem.selected, .dropdown-menu > li.selected {
+ color: #222;
+ background-color: #F1F1F1;
+}
+.dropdown-menuheader {
+ padding: 6px 44px 6px 16px;
+ color: #666;
+ font-size: 11px;
+ font-weight: bold;
+ cursor: default;
+ margin: 0;
+ text-decoration: none;
+ background-color: #F1F1F1;
+ -webkit-user-select: none;
+ user-select: none;
+}
+li.dropdown-menudivider {
+ width: 100%;
+ height: 1px;
+ padding: 0;
+ overflow: hidden;
+ background-color: #D0D0D0;
+ border-bottom: 1px solid #fff;
+}
+.btn-container {
+ padding: 10px;
+}
+.modal-frame {
+ position: relative;
+ background-color: #fff;
+ outline: 1px solid rgba(0, 0, 0, 0.2);
+ padding: 30px 42px;
+ min-width: 480px;
+ z-index: 9000;
+ pointer-events: auto;
+ box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.2);
+ -webkit-transition: all 0.218s;
+ transition: all 0.218s;
+}
+.modal-frame.medium {
+ padding: 28px 32px;
+ min-width: 280px;
+}
+.modal-frame.small {
+ padding: 16px 20px;
+}
+.modal-backdrop {
+ background-color: rgba(0, 0, 0, 0.1);
+ position: fixed;
+ height: 100%;
+ width: 100%;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ z-index: 99;
+ margin: 0;
+ display: none;
+ opacity: 0;
+ -webkit-transition: all 0.218s;
+ transition: all 0.218s;
+}
+.modal-backdrop.visible {
+ display: -webkit-flex;
+ display: flex;
+ -webkit-align-items: center;
+ align-items: center;
+ -webkit-justify-content: space-around;
+ justify-content: space-around;
+ opacity: 1;
+}
+::-webkit-scrollbar {
+ width: 10px;
+ height: 10px;
+ background: #fff;
+}
+::-webkit-scrollbar-button {
+ height: 0px;
+ width: 0px;
+}
+::-webkit-scrollbar-button:start:decrement, ::-webkit-scrollbar-button:end:increment {
+ display: block;
+}
+::-webkit-scrollbar-button:vertical:start:increment, ::-webkit-scrollbar-button:vertical:end:decrement {
+ display: none;
+}
+::-webkit-scrollbar-thumb {
+ background-color: rgba(0, 0, 0, .2);
+ background-clip: padding-box;
+ border: solid transparent;
+ border-width: 1px 1px 1px 2px;
+ min-height: 28px;
+ padding: 100px 0 0;
+ box-shadow: inset 1px 1px 0 rgba(0, 0, 0, .1), inset 0 -1px 0 rgba(0, 0, 0, .07);
+}
+::-webkit-scrollbar-thumb:hover {
+ background-color: rgba(0, 0, 0, 0.4);
+ -webkit-box-shadow: inset 1px 1px 1px rgba(0, 0, 0, 0.25);
+}
+::-webkit-scrollbar-thumb:active {
+ -webkit-box-shadow: inset 1px 1px 3px rgba(0, 0, 0, 0.35);
+ background-color: rgba(0, 0, 0, 0.5);
+}
+::-webkit-scrollbar-thumb:vertical {
+ border-top: 0px solid transparent;
+ border-bottom: 0px solid transparent;
+ border-right: 0px solid transparent;
+ border-left: 1px solid transparent;
+}
+::-webkit-scrollbar-thumb:horizontal {
+ border-top: 1px solid transparent;
+ border-bottom: 0px solid transparent;
+ border-right: 0px solid transparent;
+ border-left: 0px solid transparent;
+}
+::-webkit-scrollbar-track {
+ background-clip: padding-box;
+ background-color: #fff;
+}
+::-webkit-scrollbar-track:hover {
+ background-color: rgba(0, 0, 0, 0.05);
+ -webkit-box-shadow: inset 1px 0px 0px rgba(0, 0, 0, 0.10);
+}
+::-webkit-scrollbar-track:active {
+ background-color: rgba(0, 0, 0, 0.05);
+ -webkit-box-shadow: inset 1px 0px 0px rgba(0, 0, 0, 0.14), inset -1px -1px 0px rgba(0, 0, 0, 0.07);
+}
+::-webkit-scrollbar-track:vertical {
+ border-right: 0px solid transparent;
+ border-left: 1px solid transparent;
+}
+::-webkit-scrollbar-track:horizontal {
+ border-bottom: 0px solid transparent;
+ border-top: 1px solid transparent;
+}
+.tooltip {
+ background: #333;
+ border-radius: 2px;
+ color: #fff;
+ padding: 4px 8px;
+ font-size: 10px;
+}
+.tooltip a, .tooltip div, .tooltip span {
+ color: #fff;
+}''';
+
+ compilePolyfillAndValidate(input, generated);
+}
+
+void main() {
+ test('big #1', bigTest);
+}
diff --git a/pkgs/csslib/test/color_test.dart b/pkgs/csslib/test/color_test.dart
new file mode 100644
index 0000000..9231848
--- /dev/null
+++ b/pkgs/csslib/test/color_test.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2021, 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:csslib/parser.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('css', () {
+ test('rgb', () {
+ final color = Color.css('rgb(0, 0, 255)');
+ expect(color, equals(Color(0x0000FF)));
+ });
+
+ test('rgba', () {
+ final color = Color.css('rgba(0, 0, 255, 1.0)');
+ expect(color, equals(Color.createRgba(0, 0, 255, 1.0)));
+ });
+ });
+}
diff --git a/pkgs/csslib/test/compiler_test.dart b/pkgs/csslib/test/compiler_test.dart
new file mode 100644
index 0000000..ba0c71e
--- /dev/null
+++ b/pkgs/csslib/test/compiler_test.dart
@@ -0,0 +1,750 @@
+// 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:convert';
+
+import 'package:csslib/parser.dart';
+import 'package:csslib/visitor.dart';
+import 'package:test/test.dart';
+
+import 'testing.dart';
+
+void testClass() {
+ var errors = <Message>[];
+ var input = '.foobar {}';
+ var stylesheet = parseCss(input, errors: errors);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+
+ walkTree(stylesheet);
+
+ expect(stylesheet.topLevels.length, 1);
+
+ expect(stylesheet.topLevels[0] is RuleSet, true);
+
+ var ruleset = stylesheet.topLevels[0] as RuleSet;
+ expect(ruleset.selectorGroup!.selectors.length, 1);
+ expect(ruleset.declarationGroup.declarations.length, 0);
+
+ var selectorSeqs =
+ ruleset.selectorGroup!.selectors[0].simpleSelectorSequences;
+ expect(selectorSeqs.length, 1);
+ final simpSelector = selectorSeqs[0].simpleSelector;
+ expect(simpSelector is ClassSelector, true);
+ expect(selectorSeqs[0].isCombinatorNone, true);
+ expect(simpSelector.name, 'foobar');
+}
+
+void testClass2() {
+ var errors = <Message>[];
+ var input = '.foobar .bar .no-story {}';
+ var stylesheet = parseCss(input, errors: errors);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+
+ walkTree(stylesheet);
+
+ expect(stylesheet.topLevels.length, 1);
+
+ expect(stylesheet.topLevels[0] is RuleSet, true);
+ var ruleset = stylesheet.topLevels[0] as RuleSet;
+ expect(ruleset.selectorGroup!.selectors.length, 1);
+ expect(ruleset.declarationGroup.declarations.length, 0);
+
+ var simpleSeqs = ruleset.selectorGroup!.selectors[0].simpleSelectorSequences;
+ expect(simpleSeqs.length, 3);
+
+ var simpSelector0 = simpleSeqs[0].simpleSelector;
+ expect(simpSelector0 is ClassSelector, true);
+ expect(simpleSeqs[0].isCombinatorNone, true);
+ expect(simpSelector0.name, 'foobar');
+
+ var simpSelector1 = simpleSeqs[1].simpleSelector;
+ expect(simpSelector1 is ClassSelector, true);
+ expect(simpleSeqs[1].isCombinatorDescendant, true);
+ expect(simpSelector1.name, 'bar');
+
+ var simpSelector2 = simpleSeqs[2].simpleSelector;
+ expect(simpSelector2 is ClassSelector, true);
+ expect(simpleSeqs[2].isCombinatorDescendant, true);
+ expect(simpSelector2.name, 'no-story');
+}
+
+void testId() {
+ var errors = <Message>[];
+ var input = '#elemId {}';
+ var stylesheet = parseCss(input, errors: errors);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+
+ walkTree(stylesheet);
+
+ expect(stylesheet.topLevels.length, 1);
+
+ expect(stylesheet.topLevels[0] is RuleSet, true);
+ var ruleset = stylesheet.topLevels[0] as RuleSet;
+ expect(ruleset.selectorGroup!.selectors.length, 1);
+ expect(ruleset.declarationGroup.declarations.length, 0);
+
+ var simpleSeqs = ruleset.selectorGroup!.selectors[0].simpleSelectorSequences;
+
+ expect(simpleSeqs.length, 1);
+ var simpSelector = simpleSeqs[0].simpleSelector;
+ expect(simpSelector is IdSelector, true);
+ expect(simpleSeqs[0].isCombinatorNone, true);
+ expect(simpSelector.name, 'elemId');
+}
+
+void testElement() {
+ var errors = <Message>[];
+ var input = 'div {}';
+ var stylesheet = parseCss(input, errors: errors);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+
+ walkTree(stylesheet);
+
+ expect(stylesheet.topLevels.length, 1);
+
+ expect(stylesheet.topLevels[0] is RuleSet, true);
+ var ruleset = stylesheet.topLevels[0] as RuleSet;
+ expect(ruleset.selectorGroup!.selectors.length, 1);
+ expect(ruleset.declarationGroup.declarations.length, 0);
+
+ var simpleSeqs = ruleset.selectorGroup!.selectors[0].simpleSelectorSequences;
+
+ expect(simpleSeqs.length, 1);
+
+ final simpSelector = simpleSeqs[0].simpleSelector;
+ expect(simpSelector is ElementSelector, true);
+ expect(simpleSeqs[0].isCombinatorNone, true);
+ expect(simpSelector.name, 'div');
+
+ input = 'div div span {}';
+ stylesheet = parseCss(input, errors: errors..clear());
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+
+ walkTree(stylesheet);
+
+ expect(stylesheet.topLevels.length, 1);
+
+ expect(stylesheet.topLevels[0] is RuleSet, true);
+ ruleset = stylesheet.topLevels[0] as RuleSet;
+ expect(ruleset.selectorGroup!.selectors.length, 1);
+ expect(ruleset.declarationGroup.declarations.length, 0);
+
+ simpleSeqs = ruleset.selectorGroup!.selectors[0].simpleSelectorSequences;
+
+ expect(simpleSeqs.length, 3);
+
+ var simpSelector0 = simpleSeqs[0].simpleSelector;
+ expect(simpSelector0 is ElementSelector, true);
+ expect(simpleSeqs[0].isCombinatorNone, true);
+ expect(simpSelector0.name, 'div');
+
+ var simpSelector1 = simpleSeqs[1].simpleSelector;
+ expect(simpSelector1 is ElementSelector, true);
+ expect(simpleSeqs[1].isCombinatorDescendant, true);
+ expect(simpSelector1.name, 'div');
+
+ var simpSelector2 = simpleSeqs[2].simpleSelector;
+ expect(simpSelector2 is ElementSelector, true);
+ expect(simpleSeqs[2].isCombinatorDescendant, true);
+ expect(simpSelector2.name, 'span');
+}
+
+void testNamespace() {
+ var errors = <Message>[];
+ var input = 'ns1|div {}';
+ var stylesheet = parseCss(input, errors: errors);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+
+ walkTree(stylesheet);
+
+ expect(stylesheet.topLevels.length, 1);
+
+ expect(stylesheet.topLevels[0] is RuleSet, true);
+ var ruleset = stylesheet.topLevels[0] as RuleSet;
+ expect(ruleset.selectorGroup!.selectors.length, 1);
+ expect(ruleset.declarationGroup.declarations.length, 0);
+
+ var simpleSeqs = ruleset.selectorGroup!.selectors[0].simpleSelectorSequences;
+
+ expect(simpleSeqs.length, 1);
+ expect(simpleSeqs[0].simpleSelector is NamespaceSelector, true);
+ var simpSelector = simpleSeqs[0].simpleSelector as NamespaceSelector;
+ expect(simpleSeqs[0].isCombinatorNone, true);
+ expect(simpSelector.isNamespaceWildcard, false);
+ expect(simpSelector.namespace, 'ns1');
+ var elementSelector = simpSelector.nameAsSimpleSelector;
+ expect(elementSelector is ElementSelector, true);
+ expect(elementSelector!.isWildcard, false);
+ expect(elementSelector.name, 'div');
+}
+
+void testNamespace2() {
+ var errors = <Message>[];
+ var input = 'ns1|div div ns2|span .foobar {}';
+ var stylesheet = parseCss(input, errors: errors);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+
+ walkTree(stylesheet);
+
+ expect(stylesheet.topLevels.length, 1);
+
+ expect(stylesheet.topLevels[0] is RuleSet, true);
+ var ruleset = stylesheet.topLevels[0] as RuleSet;
+ expect(ruleset.selectorGroup!.selectors.length, 1);
+ expect(ruleset.declarationGroup.declarations.length, 0);
+
+ var simpleSeqs = ruleset.selectorGroup!.selectors[0].simpleSelectorSequences;
+
+ expect(simpleSeqs.length, 4);
+
+ expect(simpleSeqs[0].simpleSelector is NamespaceSelector, true);
+ var simpSelector0 = simpleSeqs[0].simpleSelector as NamespaceSelector;
+ expect(simpleSeqs[0].isCombinatorNone, true);
+ expect(simpSelector0.namespace, 'ns1');
+ var elementSelector0 = simpSelector0.nameAsSimpleSelector;
+ expect(elementSelector0 is ElementSelector, true);
+ expect(elementSelector0!.isWildcard, false);
+ expect(elementSelector0.name, 'div');
+
+ var simpSelector1 = simpleSeqs[1].simpleSelector;
+ expect(simpSelector1 is ElementSelector, true);
+ expect(simpleSeqs[1].isCombinatorDescendant, true);
+ expect(simpSelector1.name, 'div');
+
+ expect(simpleSeqs[2].simpleSelector is NamespaceSelector, true);
+ var simpSelector2 = simpleSeqs[2].simpleSelector as NamespaceSelector;
+ expect(simpleSeqs[2].isCombinatorDescendant, true);
+ expect(simpSelector2.namespace, 'ns2');
+ var elementSelector2 = simpSelector2.nameAsSimpleSelector;
+ expect(elementSelector2 is ElementSelector, true);
+ expect(elementSelector2!.isWildcard, false);
+ expect(elementSelector2.name, 'span');
+
+ var simpSelector3 = simpleSeqs[3].simpleSelector;
+ expect(simpSelector3 is ClassSelector, true);
+ expect(simpleSeqs[3].isCombinatorDescendant, true);
+ expect(simpSelector3.name, 'foobar');
+}
+
+void testSelectorGroups() {
+ var errors = <Message>[];
+ var input =
+ 'div, .foobar ,#elemId, .xyzzy .test, ns1|div div #elemId .foobar {}';
+ var stylesheet = parseCss(input, errors: errors);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+
+ walkTree(stylesheet);
+
+ expect(stylesheet.topLevels.length, 1);
+
+ expect(stylesheet.topLevels[0] is RuleSet, true);
+ var ruleset = stylesheet.topLevels[0] as RuleSet;
+ expect(ruleset.selectorGroup!.selectors.length, 5);
+ expect(ruleset.declarationGroup.declarations.length, 0);
+
+ var groupSelector0 = ruleset.selectorGroup!.selectors[0];
+ expect(groupSelector0.simpleSelectorSequences.length, 1);
+ var selector0 = groupSelector0.simpleSelectorSequences[0];
+ var simpleSelector0 = selector0.simpleSelector;
+ expect(simpleSelector0 is ElementSelector, true);
+ expect(selector0.isCombinatorNone, true);
+ expect(simpleSelector0.name, 'div');
+
+ var groupSelector1 = ruleset.selectorGroup!.selectors[1];
+ expect(groupSelector1.simpleSelectorSequences.length, 1);
+ var selector1 = groupSelector1.simpleSelectorSequences[0];
+ var simpleSelector1 = selector1.simpleSelector;
+ expect(simpleSelector1 is ClassSelector, true);
+ expect(selector1.isCombinatorNone, true);
+ expect(simpleSelector1.name, 'foobar');
+
+ var groupSelector2 = ruleset.selectorGroup!.selectors[2];
+ expect(groupSelector2.simpleSelectorSequences.length, 1);
+ var selector2 = groupSelector2.simpleSelectorSequences[0];
+ var simpleSelector2 = selector2.simpleSelector;
+ expect(simpleSelector2 is IdSelector, true);
+ expect(selector2.isCombinatorNone, true);
+ expect(simpleSelector2.name, 'elemId');
+
+ var groupSelector3 = ruleset.selectorGroup!.selectors[3];
+ expect(groupSelector3.simpleSelectorSequences.length, 2);
+
+ var selector30 = groupSelector3.simpleSelectorSequences[0];
+ var simpleSelector30 = selector30.simpleSelector;
+ expect(simpleSelector30 is ClassSelector, true);
+ expect(selector30.isCombinatorNone, true);
+ expect(simpleSelector30.name, 'xyzzy');
+
+ var selector31 = groupSelector3.simpleSelectorSequences[1];
+ var simpleSelector31 = selector31.simpleSelector;
+ expect(simpleSelector31 is ClassSelector, true);
+ expect(selector31.isCombinatorDescendant, true);
+ expect(simpleSelector31.name, 'test');
+
+ var groupSelector4 = ruleset.selectorGroup!.selectors[4];
+ expect(groupSelector4.simpleSelectorSequences.length, 4);
+
+ var selector40 = groupSelector4.simpleSelectorSequences[0];
+ expect(selector40.simpleSelector is NamespaceSelector, true);
+ var simpleSelector40 = selector40.simpleSelector as NamespaceSelector;
+ expect(selector40.isCombinatorNone, true);
+ expect(simpleSelector40.namespace, 'ns1');
+ var elementSelector = simpleSelector40.nameAsSimpleSelector;
+ expect(elementSelector is ElementSelector, true);
+ expect(elementSelector!.isWildcard, false);
+ expect(elementSelector.name, 'div');
+
+ var selector41 = groupSelector4.simpleSelectorSequences[1];
+ var simpleSelector41 = selector41.simpleSelector;
+ expect(simpleSelector41 is ElementSelector, true);
+ expect(selector41.isCombinatorDescendant, true);
+ expect(simpleSelector41.name, 'div');
+
+ var selector42 = groupSelector4.simpleSelectorSequences[2];
+ var simpleSelector42 = selector42.simpleSelector;
+ expect(simpleSelector42 is IdSelector, true);
+ expect(selector42.isCombinatorDescendant, true);
+ expect(simpleSelector42.name, 'elemId');
+
+ var selector43 = groupSelector4.simpleSelectorSequences[3];
+ var simpleSelector43 = selector43.simpleSelector;
+ expect(selector43.isCombinatorDescendant, true);
+ expect(simpleSelector43.name, 'foobar');
+}
+
+void testCombinator() {
+ var errors = <Message>[];
+ var input = '.foobar > .bar + .no-story ~ myNs|div #elemId {}';
+ var stylesheet = parseCss(input, errors: errors);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+
+ walkTree(stylesheet);
+
+ expect(stylesheet.topLevels.length, 1);
+
+ expect(stylesheet.topLevels[0] is RuleSet, true);
+ var ruleset = stylesheet.topLevels[0] as RuleSet;
+ expect(ruleset.selectorGroup!.selectors.length, 1);
+ expect(ruleset.declarationGroup.declarations.length, 0);
+
+ var simpleSeqs = ruleset.selectorGroup!.selectors[0].simpleSelectorSequences;
+
+ expect(simpleSeqs.length, 5);
+
+ var selector0 = simpleSeqs[0];
+ var simpleSelector0 = selector0.simpleSelector;
+ expect(simpleSelector0 is ClassSelector, true);
+ expect(selector0.isCombinatorNone, true);
+ expect(simpleSelector0.name, 'foobar');
+
+ var selector1 = simpleSeqs[1];
+ var simpleSelector1 = selector1.simpleSelector;
+ expect(simpleSelector1 is ClassSelector, true);
+ expect(selector1.isCombinatorGreater, true);
+ expect(simpleSelector1.name, 'bar');
+
+ var selector2 = simpleSeqs[2];
+ var simpleSelector2 = selector2.simpleSelector;
+ expect(simpleSelector2 is ClassSelector, true);
+ expect(selector2.isCombinatorPlus, true);
+ expect(simpleSelector2.name, 'no-story');
+
+ var selector3 = simpleSeqs[3];
+ expect(selector3.simpleSelector is NamespaceSelector, true);
+ var simpleSelector3 = selector3.simpleSelector as NamespaceSelector;
+ expect(selector3.isCombinatorTilde, true);
+ expect(simpleSelector3.namespace, 'myNs');
+ var elementSelector = simpleSelector3.nameAsSimpleSelector;
+ expect(elementSelector is ElementSelector, true);
+ expect(elementSelector!.isWildcard, false);
+ expect(elementSelector.name, 'div');
+
+ var selector4 = simpleSeqs[4];
+ var simpleSelector4 = selector4.simpleSelector;
+ expect(simpleSelector4 is IdSelector, true);
+ expect(selector4.isCombinatorDescendant, true);
+ expect(simpleSelector4.name, 'elemId');
+}
+
+void testWildcard() {
+ var errors = <Message>[];
+ var input = '* {}';
+ var stylesheet = parseCss(input, errors: errors);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+
+ walkTree(stylesheet);
+
+ expect(stylesheet.topLevels.length, 1);
+
+ expect(stylesheet.topLevels[0] is RuleSet, true);
+ var ruleset = stylesheet.topLevels[0] as RuleSet;
+ expect(ruleset.selectorGroup!.selectors.length, 1);
+ expect(ruleset.declarationGroup.declarations.length, 0);
+
+ var simpleSeqs = ruleset.selectorGroup!.selectors[0].simpleSelectorSequences;
+
+ expect(simpleSeqs.length, 1);
+ var simpSelector = simpleSeqs[0].simpleSelector;
+ expect(simpSelector is ElementSelector, true);
+ expect(simpleSeqs[0].isCombinatorNone, true);
+ expect(simpSelector.isWildcard, true);
+ expect(simpSelector.name, '*');
+
+ input = '*.foobar {}';
+ stylesheet = parseCss(input, errors: errors..clear());
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+
+ walkTree(stylesheet);
+
+ expect(stylesheet.topLevels.length, 1);
+
+ expect(stylesheet.topLevels[0] is RuleSet, true);
+ ruleset = stylesheet.topLevels[0] as RuleSet;
+ expect(ruleset.selectorGroup!.selectors.length, 1);
+ expect(ruleset.declarationGroup.declarations.length, 0);
+
+ simpleSeqs = ruleset.selectorGroup!.selectors[0].simpleSelectorSequences;
+
+ expect(simpleSeqs.length, 2);
+
+ {
+ var selector0 = simpleSeqs[0];
+ var simpleSelector0 = selector0.simpleSelector;
+ expect(simpleSelector0 is ElementSelector, true);
+ expect(selector0.isCombinatorNone, true);
+ expect(simpleSelector0.isWildcard, true);
+ expect(simpleSelector0.name, '*');
+ }
+
+ var selector1 = simpleSeqs[1];
+ var simpleSelector1 = selector1.simpleSelector;
+ expect(simpleSelector1 is ClassSelector, true);
+ expect(selector1.isCombinatorNone, true);
+ expect(simpleSelector1.name, 'foobar');
+
+ input = 'myNs|*.foobar {}';
+ stylesheet = parseCss(input, errors: errors..clear());
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+
+ walkTree(stylesheet);
+
+ expect(stylesheet.topLevels.length, 1);
+
+ expect(stylesheet.topLevels[0] is RuleSet, true);
+ ruleset = stylesheet.topLevels[0] as RuleSet;
+ expect(ruleset.selectorGroup!.selectors.length, 1);
+ expect(ruleset.declarationGroup.declarations.length, 0);
+
+ simpleSeqs = ruleset.selectorGroup!.selectors[0].simpleSelectorSequences;
+
+ expect(simpleSeqs.length, 2);
+
+ {
+ var selector0 = simpleSeqs[0];
+ expect(selector0.simpleSelector is NamespaceSelector, true);
+ var simpleSelector0 = selector0.simpleSelector as NamespaceSelector;
+ expect(selector0.isCombinatorNone, true);
+ expect(simpleSelector0.isNamespaceWildcard, false);
+ var elementSelector = simpleSelector0.nameAsSimpleSelector;
+ expect('myNs', simpleSelector0.namespace);
+ expect(elementSelector!.isWildcard, true);
+ expect('*', elementSelector.name);
+ }
+
+ selector1 = simpleSeqs[1];
+ simpleSelector1 = selector1.simpleSelector;
+ expect(simpleSelector1 is ClassSelector, true);
+ expect(selector1.isCombinatorNone, true);
+ expect('foobar', simpleSelector1.name);
+
+ input = '*|*.foobar {}';
+ stylesheet = parseCss(input, errors: errors..clear());
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+
+ walkTree(stylesheet);
+
+ expect(stylesheet.topLevels[0] is RuleSet, true);
+ ruleset = stylesheet.topLevels[0] as RuleSet;
+ expect(ruleset.selectorGroup!.selectors.length, 1);
+ expect(ruleset.declarationGroup.declarations.length, 0);
+
+ simpleSeqs = ruleset.selectorGroup!.selectors[0].simpleSelectorSequences;
+
+ expect(simpleSeqs.length, 2);
+
+ {
+ var selector0 = simpleSeqs[0];
+ expect(selector0.simpleSelector is NamespaceSelector, true);
+ var simpleSelector0 = selector0.simpleSelector as NamespaceSelector;
+ expect(selector0.isCombinatorNone, true);
+ expect(simpleSelector0.isNamespaceWildcard, true);
+ expect('*', simpleSelector0.namespace);
+ var elementSelector = simpleSelector0.nameAsSimpleSelector;
+ expect(elementSelector!.isWildcard, true);
+ expect('*', elementSelector.name);
+ }
+
+ selector1 = simpleSeqs[1];
+ simpleSelector1 = selector1.simpleSelector;
+ expect(simpleSelector1 is ClassSelector, true);
+ expect(selector1.isCombinatorNone, true);
+ expect('foobar', simpleSelector1.name);
+}
+
+// Test List<int> as input to parser.
+void testArrayOfChars() {
+ var errors = <Message>[];
+ var input = '<![CDATA[.foo { '
+ 'color: red; left: 20px; top: 20px; width: 100px; height:200px'
+ '}'
+ '#div {'
+ 'color : #00F578; border-color: #878787;'
+ '}]]>';
+
+ var stylesheet = parse(utf8.encode(input), errors: errors);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+
+ expect(prettyPrint(stylesheet), r'''
+.foo {
+ color: #f00;
+ left: 20px;
+ top: 20px;
+ width: 100px;
+ height: 200px;
+}
+#div {
+ color: #00F578;
+ border-color: #878787;
+}''');
+}
+
+void testPseudo() {
+ var errors = <Message>[];
+
+ final input = r'''
+html:lang(fr-ca) { quotes: '" ' ' "' }
+zoom: { }
+
+a:link { color: red }
+:link { color: blue }
+
+a:focus { background: yellow }
+a:focus:hover { background: white }
+
+p.special:first-letter {color: #ffd800}
+
+p:not(#example){
+ background-color: yellow;
+}
+
+input:not([DISABLED]){
+ background-color: yellow;
+}
+
+html|*:not(:link):not(:visited) {
+ border: 1px solid black;
+}
+
+*:not(FOO) {
+ height: 20px;
+}
+
+*|*:not(*) {
+ color: orange;
+}
+
+*|*:not(:hover) {
+ color: magenta;
+}
+
+p:nth-child(3n-3) { }
+
+div:nth-child(2n) { color : red; }
+''';
+
+ var stylesheet = parseCss(input, errors: errors, opts: simpleOptions);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), r'''
+html:lang(fr-ca) {
+ quotes: '" ' ' "';
+}
+zoom {
+}
+a:link {
+ color: #f00;
+}
+:link {
+ color: #00f;
+}
+a:focus {
+ background: #ff0;
+}
+a:focus:hover {
+ background: #fff;
+}
+p.special:first-letter {
+ color: #ffd800;
+}
+p:not(#example) {
+ background-color: #ff0;
+}
+input:not([DISABLED]) {
+ background-color: #ff0;
+}
+html|*:not(:link):not(:visited) {
+ border: 1px solid #000;
+}
+*:not(FOO) {
+ height: 20px;
+}
+*|*:not(*) {
+ color: #ffa500;
+}
+*|*:not(:hover) {
+ color: #f0f;
+}
+p:nth-child(3n-3) {
+}
+div:nth-child(2n) {
+ color: #f00;
+}''');
+}
+
+void testAttribute() {
+ // TODO(terry): Implement
+}
+
+void testNegation() {
+ // TODO(terry): Implement
+}
+
+void testHost() {
+ var errors = <Message>[];
+ var input = '@host { '
+ ':scope {'
+ 'white-space: nowrap;'
+ 'overflow-style: marquee-line;'
+ 'overflow-x: marquee;'
+ '}'
+ '* { color: red; }'
+ '*:hover { font-weight: bold; }'
+ ':nth-child(odd) { color: blue; }'
+ '}';
+ var stylesheet = parseCss(input, errors: errors, opts: simpleOptions);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), r'''
+@host {
+ :scope {
+ white-space: nowrap;
+ overflow-style: marquee-line;
+ overflow-x: marquee;
+ }
+ * {
+ color: #f00;
+ }
+ *:hover {
+ font-weight: bold;
+ }
+ :nth-child(odd) {
+ color: #00f;
+ }
+}''');
+}
+
+void testStringEscape() {
+ var errors = <Message>[];
+ var input = r'''a { foo: '{"text" : "a\\\""}' }''';
+ var stylesheet = parseCss(input, errors: errors, opts: simpleOptions);
+ expect(errors.isEmpty, true, reason: errors.toString());
+
+ expect(prettyPrint(stylesheet), r'''
+a {
+ foo: '{"text" : "a\\\""}';
+}''');
+}
+
+// TODO(terry): Move to emitter_test.dart when real emitter exist.
+void testEmitter() {
+ var errors = <Message>[];
+ var input = '.foo { '
+ 'color: red; left: 20px; top: 20px; width: 100px; height:200px'
+ '}'
+ '#div {'
+ 'color : #00F578; border-color: #878787;'
+ '}';
+ var stylesheet = parseCss(input, errors: errors);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+
+ walkTree(stylesheet);
+
+ expect(prettyPrint(stylesheet), r'''
+.foo {
+ color: #f00;
+ left: 20px;
+ top: 20px;
+ width: 100px;
+ height: 200px;
+}
+#div {
+ color: #00F578;
+ border-color: #878787;
+}''');
+}
+
+void testExpressionParsing() {
+ var errors = <Message>[];
+ var input = r'''
+.foobar {
+ border-radius: calc(0 - 1px);
+ border-width: calc(0 + 1px);
+}''';
+ var stylesheet = parseCss(input, errors: errors);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+
+ walkTree(stylesheet);
+
+ expect(prettyPrint(stylesheet), r'''
+.foobar {
+ border-radius: calc(0 - 1px);
+ border-width: calc(0 + 1px);
+}''');
+}
+
+void main() {
+ test('Classes', testClass);
+ test('Classes 2', testClass2);
+ test('Ids', testId);
+ test('Elements', testElement);
+ test('Namespace', testNamespace);
+ test('Namespace 2', testNamespace2);
+ test('Selector Groups', testSelectorGroups);
+ test('Combinator', testCombinator);
+ test('Wildcards', testWildcard);
+ test('Pseudo', testPseudo);
+ test('Attributes', testAttribute);
+ test('Negation', testNegation);
+ test('@host', testHost);
+ test('stringEscape', testStringEscape);
+ test('Parse List<int> as input', testArrayOfChars);
+ test('Simple Emitter', testEmitter);
+ test('Expression parsing', testExpressionParsing);
+}
diff --git a/pkgs/csslib/test/debug_test.dart b/pkgs/csslib/test/debug_test.dart
new file mode 100644
index 0000000..878493b
--- /dev/null
+++ b/pkgs/csslib/test/debug_test.dart
@@ -0,0 +1,33 @@
+// 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 'package:test/test.dart';
+
+import 'testing.dart';
+
+void main() {
+ test('exercise debug', () {
+ var style = parseCss(_input);
+
+ var debugValue = style.toDebugString();
+ expect(debugValue, isNotNull);
+
+ var style2 = style.clone();
+
+ expect(style2.toDebugString(), debugValue);
+ });
+}
+
+const String _input = r'''
+.foo {
+background-color: #191919;
+width: 10PX;
+height: 22mM !important;
+border-width: 20cm;
+margin-width: 33%;
+border-height: 30EM;
+width: .6in;
+length: 1.2in;
+-web-stuff: -10Px;
+}''';
diff --git a/pkgs/csslib/test/declaration_test.dart b/pkgs/csslib/test/declaration_test.dart
new file mode 100644
index 0000000..6406efd
--- /dev/null
+++ b/pkgs/csslib/test/declaration_test.dart
@@ -0,0 +1,1489 @@
+// 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.
+
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:csslib/src/messages.dart';
+import 'package:csslib/visitor.dart';
+import 'package:test/test.dart';
+
+import 'testing.dart';
+
+void expectCss(String css, String expected) {
+ var errors = <Message>[];
+ var styleSheet = parseCss(css, errors: errors, opts: simpleOptions);
+ expect(styleSheet, isNotNull);
+ expect(errors, isEmpty);
+ expect(prettyPrint(styleSheet), expected);
+}
+
+void testSimpleTerms() {
+ var errors = <Message>[];
+ final input = r'''
+@ import url("test.css");
+.foo {
+ background-color: #191919;
+ content: "u+0041";
+ width: 10PX;
+ height: 22mM !important;
+ border-width: 20cm;
+ margin-width: 33%;
+ border-height: 30EM;
+ width: .6in;
+ length: 1.2in;
+ -web-stuff: -10Px;
+}''';
+ final generated = r'''
+@import "test.css";
+.foo {
+ background-color: #191919;
+ content: "u+0041";
+ width: 10px;
+ height: 22mm !important;
+ border-width: 20cm;
+ margin-width: 33%;
+ border-height: 30em;
+ width: .6in;
+ length: 1.2in;
+ -web-stuff: -10px;
+}''';
+
+ var stylesheet = parseCss(input, errors: errors);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated);
+
+ final input2 = r'''
+* {
+ border-color: green;
+}''';
+ final generated2 = r'''
+* {
+ border-color: #008000;
+}''';
+
+ stylesheet = parseCss(input2, errors: errors..clear());
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated2);
+
+ // Regression test to ensure invalid percentages don't throw an exception and
+ // instead print a useful error message when not in checked mode.
+ var css = '''
+.foo {
+ width: Infinity%;
+}''';
+ stylesheet = parseCss(css, errors: errors..clear(), opts: simpleOptions);
+ expect(errors, isNotEmpty);
+ expect(errors.first.message, 'expected }, but found %');
+ expect(errors.first.span!.text, '%');
+}
+
+/// Declarations with comments, references with single-quotes, double-quotes,
+/// no quotes. Hex values with # and letters, and functions (rgba, url, etc.)
+void testDeclarations() {
+ var errors = <Message>[];
+ final input = r'''
+.more {
+ color: white;
+ color: black;
+ color: cyan;
+ color: red;
+ color: #aabbcc; /* test -- 3 */
+ color: blue;
+ background-image: url(http://test.jpeg);
+ background-image: url("http://double_quote.html");
+ background-image: url('http://single_quote.html');
+ color: rgba(10,20,255); <!-- test CDO/CDC -->
+ color: #123aef; /* hex # part integer and part identifier */
+}''';
+ final generated = r'''
+.more {
+ color: #fff;
+ color: #000;
+ color: #0ff;
+ color: #f00;
+ color: #abc;
+ color: #00f;
+ background-image: url("http://test.jpeg");
+ background-image: url("http://double_quote.html");
+ background-image: url("http://single_quote.html");
+ color: rgba(10, 20, 255);
+ color: #123aef;
+}''';
+
+ var stylesheet = parseCss(input, errors: errors);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated);
+}
+
+void testIdentifiers() {
+ var errors = <Message>[];
+ final input = r'''
+#da {
+ height: 100px;
+}
+#foo {
+ width: 10px;
+ color: #ff00cc;
+}
+''';
+ final generated = r'''
+#da {
+ height: 100px;
+}
+#foo {
+ width: 10px;
+ color: #f0c;
+}''';
+
+ var stylesheet = parseCss(input, errors: errors);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated);
+}
+
+void testComposites() {
+ var errors = <Message>[];
+ final input = r'''
+.xyzzy {
+ border: 10px 80px 90px 100px;
+ width: 99%;
+}
+@-webkit-keyframes pulsate {
+ 0% {
+ -webkit-transform: translate3d(0, 0, 0) scale(1.0);
+ }
+}''';
+ final generated = r'''
+.xyzzy {
+ border: 10px 80px 90px 100px;
+ width: 99%;
+}
+@-webkit-keyframes pulsate {
+ 0% {
+ -webkit-transform: translate3d(0, 0, 0) scale(1.0);
+ }
+}''';
+
+ var stylesheet = parseCss(input, errors: errors);
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated);
+}
+
+void testUnits() {
+ var errors = <Message>[];
+ final input = r'''
+#id-1 {
+ transition: color 0.4s;
+ animation-duration: 500ms;
+ top: 1em;
+ left: 200ex;
+ right: 300px;
+ bottom: 400cm;
+ border-width: 2.5mm;
+ margin-top: -.5in;
+ margin-left: +5pc;
+ margin-right: 5ex;
+ margin-bottom: 5ch;
+ font-size: 10pt;
+ padding-top: 22rem;
+ padding-left: 33vw;
+ padding-right: 34vh;
+ padding-bottom: 3vmin;
+ transform: rotate(20deg);
+ voice-pitch: 10hz;
+ height: 4lh;
+ width: 40rlh;
+}
+#id-2 {
+ left: 2fr;
+ font-size: 10vmax;
+ transform: rotatex(20rad);
+ voice-pitch: 10khz;
+ -web-kit-resolution: 2dpi; /* Bogus property name testing dpi unit. */
+}
+#id-3 {
+ -web-kit-resolution: 3dpcm; /* Bogus property name testing dpi unit. */
+ transform: rotatey(20grad);
+}
+#id-4 {
+ -web-kit-resolution: 4dppx; /* Bogus property name testing dpi unit. */
+ transform: rotatez(20turn);
+}
+''';
+
+ final generated = r'''
+#id-1 {
+ transition: color 0.4s;
+ animation-duration: 500ms;
+ top: 1em;
+ left: 200ex;
+ right: 300px;
+ bottom: 400cm;
+ border-width: 2.5mm;
+ margin-top: -.5in;
+ margin-left: +5pc;
+ margin-right: 5ex;
+ margin-bottom: 5ch;
+ font-size: 10pt;
+ padding-top: 22rem;
+ padding-left: 33vw;
+ padding-right: 34vh;
+ padding-bottom: 3vmin;
+ transform: rotate(20deg);
+ voice-pitch: 10hz;
+ height: 4lh;
+ width: 40rlh;
+}
+#id-2 {
+ left: 2fr;
+ font-size: 10vmax;
+ transform: rotatex(20rad);
+ voice-pitch: 10khz;
+ -web-kit-resolution: 2dpi;
+}
+#id-3 {
+ -web-kit-resolution: 3dpcm;
+ transform: rotatey(20grad);
+}
+#id-4 {
+ -web-kit-resolution: 4dppx;
+ transform: rotatez(20turn);
+}''';
+
+ var stylesheet = parseCss(input, errors: errors, opts: simpleOptions);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated);
+}
+
+void testNoValues() {
+ var errors = <Message>[];
+ final input = r'''
+.foo {
+ color: ;
+}
+.bar {
+ font:;
+ color: blue;
+}
+''';
+
+ final generated = r'''
+.foo {
+ color: ;
+}
+.bar {
+ font: ;
+ color: #00f;
+}''';
+
+ var stylesheet = parseCss(input, errors: errors, opts: simpleOptions);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated);
+}
+
+void testUnicode() {
+ var errors = <Message>[];
+ final input = r'''
+.toggle:after {
+ content: '✔';
+ line-height: 43px;
+ font-size: 20px;
+ color: #d9d9d9;
+ text-shadow: 0 -1px 0 #bfbfbf;
+}
+''';
+
+ final generated = r'''
+.toggle:after {
+ content: '✔';
+ line-height: 43px;
+ font-size: 20px;
+ color: #d9d9d9;
+ text-shadow: 0 -1px 0 #bfbfbf;
+}''';
+
+ var stylesheet = parseCss(input, errors: errors);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated);
+}
+
+void testNewerCss() {
+ var errors = <Message>[];
+ final input = r'''
+@media screen,print {
+ .foobar_screen {
+ width: 10px;
+ }
+}
+@page {
+ height: 22px;
+ size: 3in 3in;
+}
+@page : left {
+ width: 10px;
+}
+@page bar : left { @top-left { margin: 8px; } }
+@page { @top-left { margin: 8px; } width: 10px; }
+@charset "ISO-8859-1";
+@charset 'ASCII';''';
+
+ final generated = r'''
+@media screen, print {
+ .foobar_screen {
+ width: 10px;
+ }
+}
+@page {
+ height: 22px;
+ size: 3in 3in;
+}
+@page:left {
+ width: 10px;
+}
+@page bar:left {
+ @top-left {
+ margin: 8px;
+ }
+}
+@page {
+ @top-left {
+ margin: 8px;
+ }
+ width: 10px;
+}
+@charset "ISO-8859-1";
+@charset "ASCII";''';
+
+ var stylesheet = parseCss(input, errors: errors);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated);
+}
+
+void testMediaQueries() {
+ var errors = <Message>[];
+ var input = '''
+@media screen and (-webkit-min-device-pixel-ratio:0) {
+ .todo-item .toggle {
+ background: none;
+ }
+ #todo-item .toggle {
+ height: 40px;
+ }
+}''';
+ var generated = '''
+@media screen AND (-webkit-min-device-pixel-ratio:0) {
+ .todo-item .toggle {
+ background: none;
+ }
+ #todo-item .toggle {
+ height: 40px;
+ }
+}''';
+
+ var stylesheet = parseCss(input, errors: errors, opts: simpleOptions);
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated);
+
+ input = '''
+ @media handheld and (min-width: 20em),
+ screen and (min-width: 20em) {
+ #id { color: red; }
+ .myclass { height: 20px; }
+ }
+ @media print and (min-resolution: 300dpi) {
+ #anotherId {
+ color: #fff;
+ }
+ }
+ @media print and (min-resolution: 280dpcm) {
+ #finalId {
+ color: #aaa;
+ }
+ .class2 {
+ border: 20px;
+ }
+ }''';
+ generated = '''
+@media handheld AND (min-width:20em), screen AND (min-width:20em) {
+ #id {
+ color: #f00;
+ }
+ .myclass {
+ height: 20px;
+ }
+}
+@media print AND (min-resolution:300dpi) {
+ #anotherId {
+ color: #fff;
+ }
+}
+@media print AND (min-resolution:280dpcm) {
+ #finalId {
+ color: #aaa;
+ }
+ .class2 {
+ border: 20px;
+ }
+}''';
+
+ stylesheet = parseCss(input, errors: errors..clear(), opts: simpleOptions);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated);
+
+ input = '''
+@media only screen and (min-device-width: 4000px) and
+ (min-device-height: 2000px), screen AND (another: 100px) {
+ html {
+ font-size: 10em;
+ }
+ }''';
+ generated = '''
+@media ONLY screen AND (min-device-width:4000px) AND (min-device-height:2000px), screen AND (another:100px) {
+ html {
+ font-size: 10em;
+ }
+}''';
+
+ stylesheet = parseCss(input, errors: errors..clear(), opts: simpleOptions);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated);
+
+ input = '''
+@media screen,print AND (min-device-width: 4000px) and
+ (min-device-height: 2000px), screen AND (another: 100px) {
+ html {
+ font-size: 10em;
+ }
+ }''';
+ generated = '@media screen, print AND (min-device-width:4000px) AND '
+ '(min-device-height:2000px), screen AND (another:100px) {\n'
+ ' html {\n font-size: 10em;\n }\n}';
+
+ stylesheet = parseCss(input, errors: errors..clear(), opts: simpleOptions);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated);
+
+ input = '''
+@import "test.css" ONLY screen, NOT print AND (min-device-width: 4000px);''';
+ generated = '@import "test.css" ONLY screen, '
+ 'NOT print AND (min-device-width:4000px);';
+
+ stylesheet = parseCss(input, errors: errors..clear(), opts: simpleOptions);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated);
+
+ var css = '@media (min-device-width:400px) {\n}';
+ expectCss(css, css);
+
+ css = '@media all AND (tranform-3d), (-webkit-transform-3d) {\n}';
+ expectCss(css, css);
+
+ // Test that AND operator is required between media type and expressions.
+ css = '@media screen (min-device-width:400px';
+ stylesheet = parseCss(css, errors: errors..clear(), opts: simpleOptions);
+ expect(errors, isNotEmpty);
+ expect(
+ errors.first.message, contains('expected { after media before ruleset'));
+ expect(errors.first.span!.text, '(');
+
+ // Test nested at-rules.
+ input = '''
+@media (min-width: 840px) {
+ .cell {
+ width: calc(33% - 16px);
+ }
+ @supports (display: grid) {
+ .cell {
+ grid-column-end: span 4;
+ }
+ }
+}''';
+ generated = '''
+@media (min-width:840px) {
+ .cell {
+ width: calc(33% - 16px);
+ }
+ @supports (display: grid) {
+ .cell {
+ grid-column-end: span 4;
+ }
+ }
+}''';
+ expectCss(input, generated);
+}
+
+void testMozDocument() {
+ var errors = <Message>[];
+ // Test empty url-prefix, commonly used for browser detection.
+ var css = '''
+@-moz-document url-prefix() {
+ div {
+ color: #000;
+ }
+}''';
+ var expected = '''
+@-moz-document url-prefix() {
+ div {
+ color: #000;
+ }
+}''';
+ var styleSheet = parseCss(css, errors: errors);
+ expect(styleSheet, isNotNull);
+ expect(errors, isEmpty);
+ expect(prettyPrint(styleSheet), expected);
+
+ // Test url-prefix with unquoted parameter
+ css = '''
+@-moz-document url-prefix(http://www.w3.org/Style/) {
+ div {
+ color: #000;
+ }
+}''';
+ expected = '''
+@-moz-document url-prefix("http://www.w3.org/Style/") {
+ div {
+ color: #000;
+ }
+}''';
+ styleSheet = parseCss(css, errors: errors);
+ expect(styleSheet, isNotNull);
+ expect(errors, isEmpty);
+ expect(prettyPrint(styleSheet), expected);
+
+ // Test domain with unquoted parameter
+ css = '''
+@-moz-document domain(google.com) {
+ div {
+ color: #000;
+ }
+}''';
+ expected = '''
+@-moz-document domain("google.com") {
+ div {
+ color: #000;
+ }
+}''';
+ styleSheet = parseCss(css, errors: errors);
+ expect(styleSheet, isNotNull);
+ expect(errors, isEmpty);
+ expect(prettyPrint(styleSheet), expected);
+
+ // Test all document functions combined.
+ css = '@-moz-document '
+ 'url(http://www.w3.org/), '
+ "url-prefix('http://www.w3.org/Style/'), "
+ 'domain("google.com"), '
+ 'regexp("https:.*") { div { color: #000; } }';
+ expected = '@-moz-document '
+ 'url("http://www.w3.org/"), '
+ 'url-prefix("http://www.w3.org/Style/"), '
+ 'domain("google.com"), '
+ 'regexp("https:.*") {\n div {\n color: #000;\n }\n}';
+ styleSheet = parseCss(css, errors: errors);
+ expect(styleSheet, isNotNull);
+ expect(errors, isEmpty);
+ expect(prettyPrint(styleSheet), expected);
+}
+
+void testSupports() {
+ // Test single declaration condition.
+ var css = '''
+@supports (-webkit-appearance: none) {
+ div {
+ -webkit-appearance: none;
+ }
+}''';
+ var expected = '''
+@supports (-webkit-appearance: none) {
+ div {
+ -webkit-appearance: none;
+ }
+}''';
+ expectCss(css, expected);
+
+ // Test negation.
+ css = '''
+@supports not ( display: flex ) {
+ body { width: 100%; }
+}''';
+ expected = '''
+@supports not (display: flex) {
+ body {
+ width: 100%;
+ }
+}''';
+ expectCss(css, expected);
+
+ // Test clause with multiple conditions.
+ css = '''
+@supports (box-shadow: 0 0 2px black inset) or
+ (-moz-box-shadow: 0 0 2px black inset) or
+ (-webkit-box-shadow: 0 0 2px black inset) or
+ (-o-box-shadow: 0 0 2px black inset) {
+ .box {
+ box-shadow: 0 0 2px black inset;
+ }
+}''';
+ expected = '@supports (box-shadow: 0 0 2px #000 inset) or '
+ '(-moz-box-shadow: 0 0 2px #000 inset) or '
+ '(-webkit-box-shadow: 0 0 2px #000 inset) or '
+ '(-o-box-shadow: 0 0 2px #000 inset) {\n'
+ ' .box {\n'
+ ' box-shadow: 0 0 2px #000 inset;\n'
+ ' }\n'
+ '}';
+ expectCss(css, expected);
+
+ // Test conjunction and disjunction together.
+ css = '''
+@supports ((transition-property: color) or (animation-name: foo)) and
+ (transform: rotate(10deg)) {
+ div {
+ transition-property: color;
+ transform: rotate(10deg);
+ }
+}''';
+
+ expected = '@supports '
+ '((transition-property: color) or (animation-name: foo)) and '
+ '(transform: rotate(10deg)) {\n'
+ ' div {\n'
+ ' transition-property: color;\n'
+ ' transform: rotate(10deg);\n'
+ ' }\n'
+ '}';
+ expectCss(css, expected);
+
+ // Test that operators can't be mixed without parentheses.
+ css = '@supports (a: 1) and (b: 2) or (c: 3) {}';
+ var errors = <Message>[];
+ var styleSheet = parseCss(css, errors: errors, opts: simpleOptions);
+ expect(styleSheet, isNotNull);
+ expect(errors, isNotEmpty);
+ expect(errors.first.message,
+ "Operators can't be mixed without a layer of parentheses");
+ expect(errors.first.span!.text, 'or');
+}
+
+void testViewport() {
+ // No declarations.
+ var css = '@viewport {\n}';
+ expectCss(css, css);
+
+ // All declarations.
+ css = '''
+@viewport {
+ min-width: auto;
+ max-width: 800px;
+ width: 400px;
+ min-height: 50%;
+ max-height: 200px;
+ height: 100px 200px;
+ zoom: auto;
+ min-zoom: 0.75;
+ max-zoom: 40%;
+ user-zoom: fixed;
+ orientation: landscape;
+}''';
+ expectCss(css, css);
+
+ // Vendor specific.
+ css = '''
+@-ms-viewport {
+ width: device-width;
+}''';
+ expectCss(css, css);
+}
+
+void testFontFace() {
+ var errors = <Message>[];
+
+ final input = '''
+@font-face {
+ font-family: BBCBengali;
+ src: url(fonts/BBCBengali.ttf) format("opentype");
+ unicode-range: U+0A-FF, U+980-9FF, U+????, U+3???;
+}''';
+ final generated = '''@font-face {
+ font-family: BBCBengali;
+ src: url("fonts/BBCBengali.ttf") format("opentype");
+ unicode-range: U+0A-FF, U+980-9FF, U+????, U+3???;
+}''';
+ var stylesheet = parseCss(input, errors: errors, opts: simpleOptions);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated);
+
+ final input1 = '''
+@font-face {
+ font-family: Gentium;
+ src: url(http://example.com/fonts/Gentium.ttf);
+ src: url(http://example.com/fonts/Gentium.ttf);
+}''';
+ final generated1 = '''@font-face {
+ font-family: Gentium;
+ src: url("http://example.com/fonts/Gentium.ttf");
+ src: url("http://example.com/fonts/Gentium.ttf");
+}''';
+
+ stylesheet = parseCss(input1, errors: errors..clear(), opts: simpleOptions);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated1);
+
+ final input2 = '''
+@font-face {
+src: url(ideal-sans-serif.woff) format("woff"),
+ url(basic-sans-serif.ttf) format("opentype"),
+ local(Gentium Bold);
+}''';
+ final generated2 = '@font-face {\n'
+ ' src: url("ideal-sans-serif.woff") '
+ 'format("woff"), url("basic-sans-serif.ttf") '
+ 'format("opentype"), local(Gentium Bold);\n}';
+
+ stylesheet = parseCss(input2, errors: errors..clear(), opts: simpleOptions);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated2);
+
+ final input3 = '''
+@font-face {
+ font-family: MyGentium Text Ornaments;
+ src: local(Gentium Bold), /* full font name */
+ local(Gentium-Bold), /* Postscript name */
+ url(GentiumBold.ttf); /* otherwise, download it */
+ font-weight: bold;
+}''';
+ final generated3 = '''
+@font-face {
+ font-family: MyGentium Text Ornaments;
+ src: local(Gentium Bold), local(Gentium-Bold), url("GentiumBold.ttf");
+ font-weight: bold;
+}''';
+
+ stylesheet = parseCss(input3, errors: errors..clear(), opts: simpleOptions);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated3);
+
+ final input4 = '''
+@font-face {
+ font-family: STIXGeneral;
+ src: local(STIXGeneral), url(/stixfonts/STIXGeneral.otf);
+ unicode-range: U+000-49F, U+2000-27FF, U+2900-2BFF, U+1D400-1D7FF;
+}''';
+ final generated4 = '''@font-face {
+ font-family: STIXGeneral;
+ src: local(STIXGeneral), url("/stixfonts/STIXGeneral.otf");
+ unicode-range: U+000-49F, U+2000-27FF, U+2900-2BFF, U+1D400-1D7FF;
+}''';
+ stylesheet = parseCss(input4, errors: errors..clear(), opts: simpleOptions);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated4);
+}
+
+void testFontFamily() {
+ test('quoted', () {
+ var errors = <Message>[];
+ var stylesheet = parseCss('''
+body {
+ font-family: "Arial Narrow";
+}''', errors: errors..clear(), opts: simpleOptions);
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), '''
+body {
+ font-family: "Arial Narrow";
+}''');
+ var ruleSet = stylesheet.topLevels.first as RuleSet;
+ var declaration =
+ ruleSet.declarationGroup.declarations.first as Declaration;
+ var expressions = declaration.expression as Expressions;
+ expect(declaration.property, 'font-family');
+ expect(printExpressions(expressions), '"Arial Narrow"');
+ });
+
+ test('without quotes', () {
+ var errors = <Message>[];
+ var stylesheet = parseCss('''
+body {
+ font-family: Arial Narrow;
+}''', errors: errors..clear(), opts: simpleOptions);
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), '''
+body {
+ font-family: Arial Narrow;
+}''');
+ var ruleSet = stylesheet.topLevels.first as RuleSet;
+ var declaration =
+ ruleSet.declarationGroup.declarations.first as Declaration;
+ var expressions = declaration.expression as Expressions;
+ expect(declaration.property, 'font-family');
+ expect(printExpressions(expressions), 'Arial Narrow');
+ });
+
+ test('starts with identifier', () {
+ var errors = <Message>[];
+ var stylesheet = parseCss('''
+body {
+ font-family: PT Sans;
+}''', errors: errors..clear(), opts: simpleOptions);
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), '''
+body {
+ font-family: PT Sans;
+}''');
+ var ruleSet = stylesheet.topLevels.first as RuleSet;
+ var declaration =
+ ruleSet.declarationGroup.declarations.first as Declaration;
+ var expressions = declaration.expression as Expressions;
+ expect(declaration.property, 'font-family');
+ expect(printExpressions(expressions), 'PT Sans');
+ });
+}
+
+void testCssFile() {
+ var errors = <Message>[];
+ final input = r'''
+@import 'simple.css'
+@import "test.css" print
+@import url(test.css) screen, print
+@import url(http://google.com/maps/maps.css);
+
+div[href^='test'] {
+ height: 10px;
+}
+
+@-webkit-keyframes pulsate {
+ from {
+ -webkit-transform: translate3d(0, 0, 0) scale(1.0);
+ }
+ 10% {
+ -webkit-transform: translate3d(0, 0, 0) scale(1.0);
+ }
+ 30% {
+ -webkit-transform: translate3d(0, 2, 0) scale(1.0);
+ }
+}
+
+.foobar {
+ grid-columns: 10px ("content" 1fr 10px)[4];
+}
+
+.test-background {
+ background: url(http://www.foo.com/bar.png);
+}
+
+.test-background-with-multiple-properties {
+ background: #000 url(http://www.foo.com/bar.png);
+}
+''';
+
+ final generated = '''
+@import "simple.css";
+@import "test.css" print;
+@import "test.css" screen, print;
+@import "http://google.com/maps/maps.css";
+div[href^="test"] {
+ height: 10px;
+}
+@-webkit-keyframes pulsate {
+ from {
+ -webkit-transform: translate3d(0, 0, 0) scale(1.0);
+ }
+ 10% {
+ -webkit-transform: translate3d(0, 0, 0) scale(1.0);
+ }
+ 30% {
+ -webkit-transform: translate3d(0, 2, 0) scale(1.0);
+ }
+}
+.foobar {
+ grid-columns: 10px ("content" 1fr 10px) [4];
+}
+.test-background {
+ background: url("http://www.foo.com/bar.png");
+}
+.test-background-with-multiple-properties {
+ background: #000 url("http://www.foo.com/bar.png");
+}''';
+ var stylesheet = parseCss(input, errors: errors);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated);
+}
+
+void testCompactEmitter() {
+ var errors = <Message>[];
+
+ // Check !import compactly emitted.
+ final input = r'''
+div {
+ color: green !important;
+ background: red blue green;
+}
+.foo p[bar] {
+ color: blue;
+}
+@page {
+ @top-left {
+ color: red;
+ }
+}
+@page : first{}
+@page foo : first {}
+@media screen AND (max-width: 800px) {
+ div {
+ font-size: 24px;
+ }
+}
+@keyframes foo {
+ 0% {
+ transform: scaleX(0);
+ }
+}
+div {
+ color: rgba(0, 0, 0, 0.2);
+}
+''';
+ final generated = '''
+div{color:green!important;background:red blue green}.foo p[bar]{color:blue}@page{@top-left{color:red}}@page:first{}@page foo:first{}@media screen AND (max-width:800px){div{font-size:24px}}@keyframes foo{0%{transform:scaleX(0)}}div{color:rgba(0,0,0,0.2)}''';
+
+ var stylesheet = parseCss(input, errors: errors);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(compactOutput(stylesheet), generated);
+
+ // Check namespace directive compactly emitted.
+ final input2 = '@namespace a url(http://www.example.org/a);';
+ final generated2 = '@namespace a url(http://www.example.org/a);';
+
+ var stylesheet2 = parseCss(input2, errors: errors..clear());
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(compactOutput(stylesheet2), generated2);
+}
+
+void testNotSelectors() {
+ var errors = <Message>[];
+
+ final input = r'''
+.details:not(.open-details) x-element,
+.details:not(.open-details) .summary {
+ overflow: hidden;
+}
+
+.details:not(.open-details) x-icon {
+ margin-left: 99px;
+}
+
+.kind-class .details:not(.open-details) x-icon {
+ margin-left: 0px;
+}
+
+.name {
+ margin-left: 0px;
+}
+
+.details:not(.open-details) .the-class {
+ width: 80px;
+}
+
+*:focus
+{
+ outline: none;
+}
+
+body > h2:not(:first-of-type):not(:last-of-type) {
+ color: red;
+}
+
+.details-1:not([DISABLED]) {
+ outline: none;
+}
+
+html|*:not(:link):not(:visited) {
+ width: 92%;
+}
+
+*|*:not(*) {
+ font-weight: bold;
+}
+
+*:not(:not([disabled])) { color: blue; }
+''';
+ final generated = r'''
+.details:not(.open-details) x-element, .details:not(.open-details) .summary {
+ overflow: hidden;
+}
+.details:not(.open-details) x-icon {
+ margin-left: 99px;
+}
+.kind-class .details:not(.open-details) x-icon {
+ margin-left: 0px;
+}
+.name {
+ margin-left: 0px;
+}
+.details:not(.open-details) .the-class {
+ width: 80px;
+}
+*:focus {
+ outline: none;
+}
+body > h2:not(:first-of-type):not(:last-of-type) {
+ color: #f00;
+}
+.details-1:not([DISABLED]) {
+ outline: none;
+}
+html|*:not(:link):not(:visited) {
+ width: 92%;
+}
+*|*:not(*) {
+ font-weight: bold;
+}
+*:not(:not([disabled])) {
+ color: #00f;
+}''';
+
+ var stylesheet = parseCss(input, errors: errors, opts: simpleOptions);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated);
+}
+
+void testIE() {
+ var errors = <Message>[];
+ final input = '.test {\n'
+ ' filter: progid:DXImageTransform.Microsoft.gradient'
+ "(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670');\n"
+ '}';
+ final generated = '.test {\n'
+ ' filter: progid:DXImageTransform.Microsoft.gradient'
+ "(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670');\n"
+ '}';
+
+ var stylesheet = parseCss(input, errors: errors, opts: simpleOptions);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated);
+
+ final input2 = '.test {\n'
+ ' filter: progid:DXImageTransform.Microsoft.gradient'
+ "(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670')\n"
+ ' progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);\n'
+ '}';
+
+ final generated2 = '.test {\n'
+ ' filter: progid:DXImageTransform.Microsoft.gradient'
+ "(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670')\n"
+ ' progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);\n'
+ '}';
+
+ stylesheet = parseCss(input2, errors: errors..clear(), opts: simpleOptions);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated2);
+
+ final input3 = '''
+div {
+ filter: alpha(opacity=80); /* IE7 and under */
+ -ms-filter: "Alpha(Opacity=40)"; /* IE8 and newer */
+
+ Filter: Blur(Add = 0, Direction = 225, Strength = 10);
+ Filter: FlipV;
+ Filter: Gray;
+ FILTER: Chroma(Color = #000000) Mask(Color=#00FF00);
+ Filter: Alpha(Opacity=100, FinishOpacity=0, Style=2, StartX=20, StartY=40,
+ FinishX=0, FinishY=0) Wave(Add=0, Freq=5, LightStrength=20,
+ Phase=220, Strength=10);
+}
+''';
+ final generated3 = 'div {\n filter: alpha(opacity=80);\n'
+ ' -ms-filter: "Alpha(Opacity=40)";\n'
+ ' Filter: Blur(Add = 0, Direction = 225, Strength = 10);\n'
+ ' Filter: FlipV;\n Filter: Gray;\n'
+ ' FILTER: Chroma(Color = #000000) Mask(Color=#00FF00);\n'
+ ' Filter: Alpha(Opacity=100, FinishOpacity=0, Style=2, '
+ 'StartX=20, StartY=40,\n'
+ ' FinishX=0, FinishY=0) Wave(Add=0, Freq=5, LightStrength=20,\n'
+ ' Phase=220, Strength=10);\n}';
+
+ stylesheet = parseCss(input3, errors: errors..clear(), opts: simpleOptions);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated3);
+
+ final input4 = '''
+div {
+ filter: FlipH;
+}''';
+
+ stylesheet = parseCss(input4, errors: errors..clear(), opts: simpleOptions);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), input4);
+}
+
+/// Test IE specific declaration syntax:
+/// IE6 property name prefixed with _ (normal CSS property name can start
+/// with an underscore).
+///
+/// IE7 or below property add asterisk before the CSS property.
+///
+/// IE8 or below add \9 at end of declaration expression e.g.,
+/// background: red\9;
+void testIEDeclaration() {
+ var errors = <Message>[];
+
+ final input = '''
+.testIE-6 {
+ _zoom : 5;
+}
+.clearfix {
+ *zoom: 1;
+}
+audio, video {
+ display: inline-block;
+ *display: inline;
+ *zoom: 1;
+}
+input {
+ *overflow: visible;
+ line-height: normal;
+}
+.uneditable-input:focus {
+ border-color: rgba(82, 168, 236, 0.8);
+ outline: 0;
+ outline: thin dotted \\9; /* IE6-9 */
+}
+
+input[type="radio"], input[type="checkbox"] {
+ margin-top: 1px \\9;
+ *margin-top: 0;
+}
+
+input.search-query {
+ padding-right: 14px;
+ padding-right: 4px \\9;
+ padding-left: 14px;
+ padding-left: 4px \\9; /* IE7-8 no border-radius, don't indent padding. */
+}
+
+.btn.active {
+ background-color: #cccccc \\9;
+}
+
+@-webkit-keyframes progress-bar-stripes {
+from {
+background-position: 40px 0;
+}
+to {
+background-position: 0 0;
+}
+}
+
+@-moz-keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}
+
+@-ms-keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}
+
+@-o-keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}
+
+@keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}''';
+
+ final generated = '''
+.testIE-6 {
+ _zoom: 5;
+}
+.clearfix {
+ *zoom: 1;
+}
+audio, video {
+ display: inline-block;
+ *display: inline;
+ *zoom: 1;
+}
+input {
+ *overflow: visible;
+ line-height: normal;
+}
+.uneditable-input:focus {
+ border-color: rgba(82, 168, 236, 0.8);
+ outline: 0;
+ outline: thin dotted \\9;
+}
+input[type="radio"], input[type="checkbox"] {
+ margin-top: 1px \\9;
+ *margin-top: 0;
+}
+input.search-query {
+ padding-right: 14px;
+ padding-right: 4px \\9;
+ padding-left: 14px;
+ padding-left: 4px \\9;
+}
+.btn.active {
+ background-color: #ccc \\9;
+}
+@-webkit-keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}
+@-moz-keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}
+@keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}
+@-o-keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}
+@keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}''';
+
+ var stylesheet = parseCss(input, errors: errors, opts: simpleOptions);
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated);
+}
+
+void testHangs() {
+ var errors = <Message>[];
+
+ // Bad hexvalue had caused a hang in processTerm.
+ final input = r'''#a { color: #ebebeburl(0/IE8+9+); }''';
+ parseCss(input, errors: errors, opts: options);
+
+ expect(errors.length, 3, reason: errors.toString());
+
+ var errorMessage = errors[0];
+ expect(errorMessage.message, contains('Bad hex number'));
+ expect(errorMessage.span, isNotNull);
+ expect(errorMessage.span!.start.line, 0);
+ expect(errorMessage.span!.start.column, 12);
+ expect(errorMessage.span!.text, '#ebebeburl');
+
+ errorMessage = errors[1];
+ expect(errorMessage.message, contains('expected }, but found +'));
+ expect(errorMessage.span, isNotNull);
+ expect(errorMessage.span!.start.line, 0);
+ expect(errorMessage.span!.start.column, 30);
+ expect(errorMessage.span!.text, '+');
+
+ errorMessage = errors[2];
+ expect(errorMessage.message, contains('premature end of file unknown CSS'));
+ expect(errorMessage.span, isNotNull);
+ expect(errorMessage.span!.start.line, 0);
+ expect(errorMessage.span!.start.column, 31);
+ expect(errorMessage.span!.text, ')');
+
+ // Missing closing parenthesis for keyframes.
+ final input2 = r'''@-ms-keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+''';
+
+ parseCss(input2, errors: errors..clear(), opts: options);
+
+ expect(errors.length, 1, reason: errors.toString());
+
+ errorMessage = errors[0];
+ expect(errorMessage.message, contains('unexpected end of file'));
+ expect(errorMessage.span, isNotNull);
+ expect(errorMessage.span!.start.line, 7);
+ expect(errorMessage.span!.start.column, 0);
+ expect(errorMessage.span!.text.trim(), '');
+}
+
+void testExpressionSpans() {
+ final input = r'''.foo { width: 50px; }''';
+
+ var stylesheet = parseCss(input);
+ var ruleSet = stylesheet.topLevels.single as RuleSet;
+
+ var declaration = ruleSet.declarationGroup.declarations.single as Declaration;
+ expect(declaration.span.text, 'width: 50px');
+
+ var expressions = declaration.expression as Expressions;
+ expect(expressions.expressions.first.span!.text, '50px');
+}
+
+void testComments() {
+ final css = '''/* This comment has a nested HTML comment...
+* <html>
+* <!-- Nested HTML comment... -->
+* <div></div>
+* </html>
+*/''';
+ expectCss(css, '');
+}
+
+void simpleCalc() {
+ final input = r'''.foo { height: calc(100% - 55px); }''';
+ var stylesheet = parseCss(input);
+ var decl = (stylesheet.topLevels.single as RuleSet)
+ .declarationGroup
+ .declarations
+ .single;
+ expect(decl.span!.text, 'height: calc(100% - 55px)');
+}
+
+void complexCalc() {
+ final input = r'''.foo { left: calc((100%/3 - 2) * 1em - 2 * 1px); }''';
+ var stylesheet = parseCss(input);
+ var decl = (stylesheet.topLevels.single as RuleSet)
+ .declarationGroup
+ .declarations
+ .single;
+ expect(decl.span!.text, 'left: calc((100%/3 - 2) * 1em - 2 * 1px)');
+}
+
+void twoCalcs() {
+ final input = r'''.foo { margin: calc(1rem - 2px) calc(1rem - 1px); }''';
+ var stylesheet = parseCss(input);
+ var decl = (stylesheet.topLevels.single as RuleSet)
+ .declarationGroup
+ .declarations
+ .single;
+ expect(decl.span!.text, 'margin: calc(1rem - 2px) calc(1rem - 1px)');
+}
+
+void selectorWithCalcs() {
+ var errors = <Message>[];
+ final input = r'''
+.foo {
+ width: calc(1em + 5 * 2em);
+ height: calc(1px + 2%) !important;
+ border: 5px calc(1pt + 2cm) 6px calc(1em + 1in + 2px) red;
+ border: calc(5px + 1em) 0px 1px calc(10 + 20 + 1px);
+ margin: 25px calc(50px + (100% / (3 - 1em) - 20%)) calc(10px + 10 * 20) calc(100% - 10px);
+}''';
+ final generated = r'''
+.foo {
+ width: calc(1em + 5 * 2em);
+ height: calc(1px + 2%) !important;
+ border: 5px calc(1pt + 2cm) 6px calc(1em + 1in + 2px) #f00;
+ border: calc(5px + 1em) 0px 1px calc(10 + 20 + 1px);
+ margin: 25px calc(50px + (100% / (3 - 1em) - 20%)) calc(10px + 10 * 20) calc(100% - 10px);
+}''';
+
+ var stylesheet = parseCss(input, errors: errors);
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated);
+}
+
+void vendorPrefixedCalc() {
+ var css = '''
+.foo {
+ width: -webkit-calc((100% - 15px*1) / 1);
+}''';
+ expectCss(css, css);
+
+ css = '''
+.foo {
+ width: -moz-calc((100% - 15px*1) / 1);
+}''';
+ expectCss(css, css);
+}
+
+void main() {
+ test('Simple Terms', testSimpleTerms);
+ test('Declarations', testDeclarations);
+ test('Identifiers', testIdentifiers);
+ test('Composites', testComposites);
+ test('Units', testUnits);
+ test('No Values', testNoValues);
+ test('Unicode', testUnicode);
+ test('Newer CSS', testNewerCss);
+ test('Media Queries', testMediaQueries);
+ test('Document', testMozDocument);
+ test('Supports', testSupports);
+ test('Viewport', testViewport);
+ test('Font-Face', testFontFace);
+ group('font-family', testFontFamily);
+ test('CSS file', testCssFile);
+ test('Compact Emitter', testCompactEmitter);
+ test('Selector Negation', testNotSelectors);
+ test('IE stuff', testIE);
+ test('IE declaration syntax', testIEDeclaration);
+ test('Hanging bugs', testHangs);
+ test('Expression spans', testExpressionSpans);
+ test('Comments', testComments);
+ group('calc function', () {
+ test('simple calc', simpleCalc);
+ test('single complex', complexCalc);
+ test('two calc terms for same declaration', twoCalcs);
+ test('selector with many calc declarations', selectorWithCalcs);
+ test('vendor prefixed calc', vendorPrefixedCalc);
+ });
+}
diff --git a/pkgs/csslib/test/error_test.dart b/pkgs/csslib/test/error_test.dart
new file mode 100644
index 0000000..030fc24
--- /dev/null
+++ b/pkgs/csslib/test/error_test.dart
@@ -0,0 +1,359 @@
+// 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:csslib/src/messages.dart';
+import 'package:term_glyph/term_glyph.dart' as glyph;
+import 'package:test/test.dart';
+
+import 'testing.dart';
+
+/// Test for unsupported font-weights values of bolder, lighter and inherit.
+void testUnsupportedFontWeights() {
+ var errors = <Message>[];
+
+ // TODO(terry): Need to support bolder.
+ // font-weight value bolder.
+ var input = '.foobar { font-weight: bolder; }';
+ var stylesheet = parseCss(input, errors: errors);
+
+ expect(errors.isEmpty, false);
+ expect(errors[0].toString(), '''
+error on line 1, column 24: Unknown property value bolder
+ ,
+1 | .foobar { font-weight: bolder; }
+ | ^^^^^^
+ \'''');
+
+ expect(prettyPrint(stylesheet), r'''
+.foobar {
+ font-weight: bolder;
+}''');
+
+ // TODO(terry): Need to support lighter.
+ // font-weight value lighter.
+ input = '.foobar { font-weight: lighter; }';
+ stylesheet = parseCss(input, errors: errors..clear());
+
+ expect(errors.isEmpty, false);
+ expect(errors[0].toString(), '''
+error on line 1, column 24: Unknown property value lighter
+ ,
+1 | .foobar { font-weight: lighter; }
+ | ^^^^^^^
+ \'''');
+ expect(prettyPrint(stylesheet), r'''
+.foobar {
+ font-weight: lighter;
+}''');
+
+ // TODO(terry): Need to support inherit.
+ // font-weight value inherit.
+ input = '.foobar { font-weight: inherit; }';
+ stylesheet = parseCss(input, errors: errors..clear());
+
+ expect(errors.isEmpty, false);
+ expect(errors[0].toString(), '''
+error on line 1, column 24: Unknown property value inherit
+ ,
+1 | .foobar { font-weight: inherit; }
+ | ^^^^^^^
+ \'''');
+ expect(prettyPrint(stylesheet), r'''
+.foobar {
+ font-weight: inherit;
+}''');
+}
+
+/// Test for unsupported line-height values of units other than px, pt and
+/// inherit.
+void testUnsupportedLineHeights() {
+ var errors = <Message>[];
+
+ // line-height value in percentage unit.
+ var input = '.foobar { line-height: 120%; }';
+ var stylesheet = parseCss(input, errors: errors);
+
+ expect(errors.isEmpty, false);
+ expect(errors[0].toString(), '''
+error on line 1, column 24: Unexpected value for line-height
+ ,
+1 | .foobar { line-height: 120%; }
+ | ^^^^
+ \'''');
+ expect(prettyPrint(stylesheet), r'''
+.foobar {
+ line-height: 120%;
+}''');
+
+ // TODO(terry): Need to support all units.
+ // line-height value in cm unit.
+ input = '.foobar { line-height: 20cm; }';
+ stylesheet = parseCss(input, errors: errors..clear());
+
+ expect(errors.isEmpty, false);
+ expect(errors[0].toString(), '''
+error on line 1, column 24: Unexpected unit for line-height
+ ,
+1 | .foobar { line-height: 20cm; }
+ | ^^^^
+ \'''');
+ expect(prettyPrint(stylesheet), r'''
+.foobar {
+ line-height: 20cm;
+}''');
+
+ // TODO(terry): Need to support inherit.
+ // line-height value inherit.
+ input = '.foobar { line-height: inherit; }';
+ stylesheet = parseCss(input, errors: errors..clear());
+
+ expect(errors.isEmpty, false);
+ expect(errors[0].toString(), '''
+error on line 1, column 24: Unknown property value inherit
+ ,
+1 | .foobar { line-height: inherit; }
+ | ^^^^^^^
+ \'''');
+ expect(prettyPrint(stylesheet), r'''
+.foobar {
+ line-height: inherit;
+}''');
+}
+
+/// Test for bad selectors.
+void testBadSelectors() {
+ var errors = <Message>[];
+
+ // Invalid id selector.
+ var input = '# foo { color: #ff00ff; }';
+ parseCss(input, errors: errors);
+
+ expect(errors, isNotEmpty);
+ expect(errors[0].toString(), '''
+error on line 1, column 1: Not a valid ID selector expected #id
+ ,
+1 | # foo { color: #ff00ff; }
+ | ^
+ \'''');
+
+ // Invalid class selector.
+ input = '. foo { color: #ff00ff; }';
+ parseCss(input, errors: errors..clear());
+
+ expect(errors, isNotEmpty);
+ expect(errors[0].toString(), '''
+error on line 1, column 1: Not a valid class selector expected .className
+ ,
+1 | . foo { color: #ff00ff; }
+ | ^
+ \'''');
+}
+
+/// Test for bad hex values.
+void testBadHexValues() {
+ var errors = <Message>[];
+
+ // Invalid hex value.
+ var input = '.foobar { color: #AH787; }';
+ var stylesheet = parseCss(input, errors: errors);
+
+ expect(errors.isEmpty, false);
+ expect(errors[0].toString(), '''
+error on line 1, column 18: Bad hex number
+ ,
+1 | .foobar { color: #AH787; }
+ | ^^^^^^
+ \'''');
+ expect(prettyPrint(stylesheet), r'''
+.foobar {
+ color: #AH787;
+}''');
+
+ // Bad color constant.
+ input = '.foobar { color: redder; }';
+ stylesheet = parseCss(input, errors: errors..clear());
+
+ expect(errors.isEmpty, false);
+ expect(errors[0].toString(), '''
+error on line 1, column 18: Unknown property value redder
+ ,
+1 | .foobar { color: redder; }
+ | ^^^^^^
+ \'''');
+
+ expect(prettyPrint(stylesheet), r'''
+.foobar {
+ color: redder;
+}''');
+
+ // Bad hex color #<space>ffffff.
+ input = '.foobar { color: # ffffff; }';
+ stylesheet = parseCss(input, errors: errors..clear());
+
+ expect(errors.isEmpty, false);
+ expect(errors[0].toString(), '''
+error on line 1, column 18: Expected hex number
+ ,
+1 | .foobar { color: # ffffff; }
+ | ^
+ \'''');
+
+ expect(prettyPrint(stylesheet), r'''
+.foobar {
+ color: # ffffff;
+}''');
+
+ // Bad hex color #<space>123fff.
+ input = '.foobar { color: # 123fff; }';
+ stylesheet = parseCss(input, errors: errors..clear());
+
+ expect(errors.isEmpty, false);
+ expect(errors[0].toString(), '''
+error on line 1, column 18: Expected hex number
+ ,
+1 | .foobar { color: # 123fff; }
+ | ^
+ \'''');
+
+ // Formating is off with an extra space. However, the entire value is bad
+ // and isn't processed anyway.
+ expect(prettyPrint(stylesheet), r'''
+.foobar {
+ color: # 123 fff;
+}''');
+}
+
+void testBadUnicode() {
+ var errors = <Message>[];
+ final input = '''
+@font-face {
+ src: url(fonts/BBCBengali.ttf) format("opentype");
+ unicode-range: U+400-200;
+}''';
+
+ parseCss(input, errors: errors);
+
+ expect(errors.isEmpty, false);
+ expect(
+ errors[0].toString(),
+ 'error on line 3, column 20: unicode first range can not be greater than '
+ 'last\n'
+ ' ,\n'
+ '3 | unicode-range: U+400-200;\n'
+ ' | ^^^^^^^\n'
+ ' \'');
+
+ final input2 = '''
+@font-face {
+ src: url(fonts/BBCBengali.ttf) format("opentype");
+ unicode-range: U+12FFFF;
+}''';
+
+ parseCss(input2, errors: errors..clear());
+
+ expect(errors.isEmpty, false);
+ expect(
+ errors[0].toString(),
+ 'error on line 3, column 20: unicode range must be less than 10FFFF\n'
+ ' ,\n'
+ '3 | unicode-range: U+12FFFF;\n'
+ ' | ^^^^^^\n'
+ ' \'');
+}
+
+void testBadNesting() {
+ var errors = <Message>[];
+
+ // Test for bad declaration in a nested rule.
+ final input = '''
+div {
+ width: 20px;
+ span + ul { color: blue; }
+ span + ul > #aaaa {
+ color: #ffghghgh;
+ }
+ background-color: red;
+}
+''';
+
+ parseCss(input, errors: errors);
+ expect(errors.length, 1);
+ var errorMessage = messages.messages[0];
+ expect(errorMessage.message, contains('Bad hex number'));
+ expect(errorMessage.span, isNotNull);
+ expect(errorMessage.span!.start.line, 4);
+ expect(errorMessage.span!.start.column, 11);
+ expect(errorMessage.span!.text, '#ffghghgh');
+
+ // Test for bad selector syntax.
+ final input2 = '''
+div {
+ span + ul #aaaa > (3333) {
+ color: #ffghghgh;
+ }
+}
+''';
+ parseCss(input2, errors: errors..clear());
+ expect(errors.length, 4);
+ errorMessage = messages.messages[0];
+ expect(errorMessage.message, contains(':, but found +'));
+ expect(errorMessage.span, isNotNull);
+ expect(errorMessage.span!.start.line, 1);
+ expect(errorMessage.span!.start.column, 7);
+ expect(errorMessage.span!.text, '+');
+
+ errorMessage = messages.messages[1];
+ expect(errorMessage.message, contains('Unknown property value ul'));
+ expect(errorMessage.span, isNotNull);
+ expect(errorMessage.span!.start.line, 1);
+ expect(errorMessage.span!.start.column, 9);
+ expect(errorMessage.span!.text, 'ul');
+
+ errorMessage = messages.messages[2];
+ expect(errorMessage.message, contains('expected }, but found >'));
+ expect(errorMessage.span, isNotNull);
+ expect(errorMessage.span!.start.line, 1);
+ expect(errorMessage.span!.start.column, 18);
+ expect(errorMessage.span!.text, '>');
+
+ errorMessage = messages.messages[3];
+ expect(errorMessage.message, contains('premature end of file unknown CSS'));
+ expect(errorMessage.span, isNotNull);
+ expect(errorMessage.span!.start.line, 1);
+ expect(errorMessage.span!.start.column, 20);
+ expect(errorMessage.span!.text, '(');
+
+ // Test for missing close braces and bad declaration.
+ final input3 = '''
+div {
+ span {
+ color: #green;
+}
+''';
+ parseCss(input3, errors: errors..clear());
+ expect(errors.length, 2);
+ errorMessage = messages.messages[0];
+ expect(errorMessage.message, contains('Bad hex number'));
+ expect(errorMessage.span, isNotNull);
+ expect(errorMessage.span!.start.line, 2);
+ expect(errorMessage.span!.start.column, 11);
+ expect(errorMessage.span!.text, '#green');
+
+ errorMessage = messages.messages[1];
+ expect(errorMessage.message, contains('expected }, but found end of file'));
+ expect(errorMessage.span, isNotNull);
+ expect(errorMessage.span!.start.line, 3);
+ expect(errorMessage.span!.start.column, 1);
+ expect(errorMessage.span!.text, '\n');
+}
+
+void main() {
+ glyph.ascii = true;
+ test('font-weight value errors', testUnsupportedFontWeights);
+ test('line-height value errors', testUnsupportedLineHeights);
+ test('bad selectors', testBadSelectors);
+ test('bad Hex values', testBadHexValues);
+ test('bad unicode ranges', testBadUnicode);
+ test('nested rules', testBadNesting);
+}
diff --git a/pkgs/csslib/test/escape_codes_test.dart b/pkgs/csslib/test/escape_codes_test.dart
new file mode 100644
index 0000000..0973021
--- /dev/null
+++ b/pkgs/csslib/test/escape_codes_test.dart
@@ -0,0 +1,30 @@
+// 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 'package:csslib/parser.dart';
+import 'package:test/test.dart';
+
+import 'testing.dart';
+
+void main() {
+ final errors = <Message>[];
+
+ tearDown(errors.clear);
+
+ group('handles escape codes', () {
+ group('in an identifier', () {
+ test('with trailing space', () {
+ final selectorAst = selector(r'.\35 00px', errors: errors);
+ expect(errors, isEmpty);
+ expect(compactOutput(selectorAst), r'.\35 00px');
+ });
+
+ test('in an attribute selector value', () {
+ final selectorAst = selector(r'[elevation=\31]', errors: errors);
+ expect(errors, isEmpty);
+ expect(compactOutput(selectorAst), r'[elevation=\31]');
+ });
+ });
+ });
+}
diff --git a/pkgs/csslib/test/extend_test.dart b/pkgs/csslib/test/extend_test.dart
new file mode 100644
index 0000000..0990490
--- /dev/null
+++ b/pkgs/csslib/test/extend_test.dart
@@ -0,0 +1,243 @@
+// 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:csslib/src/messages.dart';
+import 'package:test/test.dart';
+
+import 'testing.dart';
+
+void compileAndValidate(String input, String generated) {
+ var errors = <Message>[];
+ var stylesheet = compileCss(input, errors: errors, opts: options);
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated);
+}
+
+void simpleExtend() {
+ compileAndValidate(r'''
+.error {
+ border: 1px red;
+ background-color: #fdd;
+}
+.seriousError {
+ @extend .error;
+ border-width: 3px;
+}
+''', r'''
+.error, .seriousError {
+ border: 1px #f00;
+ background-color: #fdd;
+}
+.seriousError {
+ border-width: 3px;
+}''');
+}
+
+void complexSelectors() {
+ compileAndValidate(r'''
+.error {
+ border: 1px #f00;
+ background-color: #fdd;
+}
+.error.intrusion {
+ background-image: url("/image/hacked.png");
+}
+.seriousError {
+ @extend .error;
+ border-width: 3px;
+}
+''', r'''
+.error, .seriousError {
+ border: 1px #f00;
+ background-color: #fdd;
+}
+.error.intrusion, .seriousError.intrusion {
+ background-image: url("/image/hacked.png");
+}
+.seriousError {
+ border-width: 3px;
+}''');
+
+ compileAndValidate(r'''
+a:hover {
+ text-decoration: underline;
+}
+.hoverlink {
+ @extend a:hover;
+}
+''', r'''
+a:hover, .hoverlink {
+ text-decoration: underline;
+}
+.hoverlink {
+}''');
+}
+
+void multipleExtends() {
+ compileAndValidate(r'''
+.error {
+ border: 1px #f00;
+ background-color: #fdd;
+}
+.attention {
+ font-size: 3em;
+ background-color: #ff0;
+}
+.seriousError {
+ @extend .error;
+ @extend .attention;
+ border-width: 3px;
+}
+''', r'''
+.error, .seriousError {
+ border: 1px #f00;
+ background-color: #fdd;
+}
+.attention, .seriousError {
+ font-size: 3em;
+ background-color: #ff0;
+}
+.seriousError {
+ border-width: 3px;
+}''');
+}
+
+void chaining() {
+ compileAndValidate(r'''
+.error {
+ border: 1px #f00;
+ background-color: #fdd;
+}
+.seriousError {
+ @extend .error;
+ border-width: 3px;
+}
+.criticalError {
+ @extend .seriousError;
+ position: fixed;
+ top: 10%;
+ bottom: 10%;
+ left: 10%;
+ right: 10%;
+}
+''', r'''
+.error, .seriousError, .criticalError {
+ border: 1px #f00;
+ background-color: #fdd;
+}
+.seriousError, .criticalError {
+ border-width: 3px;
+}
+.criticalError {
+ position: fixed;
+ top: 10%;
+ bottom: 10%;
+ left: 10%;
+ right: 10%;
+}''');
+}
+
+void nestedSelectors() {
+ compileAndValidate(r'''
+a {
+ color: blue;
+ &:hover {
+ text-decoration: underline;
+ }
+}
+
+#fake-links .link {
+ @extend a;
+}
+''', r'''
+a, #fake-links .link {
+ color: #00f;
+}
+a:hover, #fake-links .link:hover {
+ text-decoration: underline;
+}
+#fake-links .link {
+}''');
+}
+
+void nestedMulty() {
+ compileAndValidate(r'''
+.btn {
+ display: inline-block;
+}
+
+input[type="checkbox"].toggle-button {
+ color: red;
+
+ + label {
+ @extend .btn;
+ }
+}
+''', r'''
+.btn, input[type="checkbox"].toggle-button label {
+ display: inline-block;
+}
+input[type="checkbox"].toggle-button {
+ color: #f00;
+}
+input[type="checkbox"].toggle-button label {
+}''');
+}
+
+void nWayExtends() {
+ compileAndValidate(
+ r'''
+.btn > .btn {
+ margin-left: 5px;
+}
+input.second + label {
+ @extend .btn;
+}
+''',
+ '.btn > .btn, '
+ 'input.second + label > .btn, '
+ '.btn > input.second + label, '
+ 'input.second + label > input.second + label, '
+ 'input.second + label > input.second + label {\n'
+ ' margin-left: 5px;\n}\n'
+ 'input.second + label {\n'
+ '}');
+
+ // TODO(terry): Optimize merge selectors would be:
+ //
+ // .btn + .btn, input.second + label + .btn, input.second.btn + label {
+ // margin-left: 5px;
+ // }
+ // input.second + label {
+ // color: blue;
+ // }
+ compileAndValidate(
+ r'''
+.btn + .btn {
+ margin-left: 5px;
+}
+input.second + label {
+ @extend .btn;
+ color: blue;
+}
+''',
+ '.btn + .btn, '
+ 'input.second + label + .btn, '
+ '.btn + input.second + label, '
+ 'input.second + label + input.second + label, '
+ 'input.second + label + input.second + label {\n'
+ ' margin-left: 5px;\n}\n'
+ 'input.second + label {\n'
+ ' color: #00f;\n}');
+}
+
+void main() {
+ test('Simple Extend', simpleExtend);
+ test('complex', complexSelectors);
+ test('multiple', multipleExtends);
+ test('chaining', chaining);
+ test('nested selectors', nestedSelectors);
+ test('nested many selector sequences', nestedMulty);
+ test('N-way extends', nWayExtends);
+}
diff --git a/pkgs/csslib/test/keyframes_test.dart b/pkgs/csslib/test/keyframes_test.dart
new file mode 100644
index 0000000..3560c0d
--- /dev/null
+++ b/pkgs/csslib/test/keyframes_test.dart
@@ -0,0 +1,26 @@
+// 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 'package:csslib/src/messages.dart';
+import 'package:test/test.dart';
+
+import 'testing.dart';
+
+void main() {
+ test('keyframes', () {
+ final input =
+ r'@keyframes ping { 75%, 100% { transform: scale(2); opacity: 0; } }';
+ var errors = <Message>[];
+ var stylesheet = compileCss(input, errors: errors, opts: options);
+ expect(errors, isEmpty);
+ final expected = r'''
+@keyframes ping {
+ 75%, 100% {
+ transform: scale(2);
+ opacity: 0;
+ }
+}''';
+ expect(prettyPrint(stylesheet), expected);
+ });
+}
diff --git a/pkgs/csslib/test/mixin_test.dart b/pkgs/csslib/test/mixin_test.dart
new file mode 100644
index 0000000..684f914
--- /dev/null
+++ b/pkgs/csslib/test/mixin_test.dart
@@ -0,0 +1,644 @@
+// 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:csslib/src/messages.dart';
+import 'package:test/test.dart';
+
+import 'testing.dart';
+
+void compileAndValidate(String input, String generated) {
+ var errors = <Message>[];
+ var stylesheet = compileCss(input, errors: errors, opts: options);
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated);
+}
+
+void compilePolyfillAndValidate(String input, String generated) {
+ var errors = <Message>[];
+ var stylesheet = polyFillCompileCss(input, errors: errors, opts: options);
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated);
+}
+
+void topLevelMixin() {
+ compileAndValidate(r'''
+@mixin silly-links {
+ a {
+ color: blue;
+ background-color: red;
+ }
+}
+
+@include silly-links;
+''', r'''
+a {
+ color: #00f;
+ background-color: #f00;
+}''');
+}
+
+void topLevelMixinTwoIncludes() {
+ compileAndValidate(r'''
+@mixin a {
+ a {
+ color: blue;
+ background-color: red;
+ }
+}
+@mixin b {
+ span {
+ color: black;
+ background-color: orange;
+ }
+}
+@include a;
+@include b;
+''', r'''
+a {
+ color: #00f;
+ background-color: #f00;
+}
+span {
+ color: #000;
+ background-color: #ffa500;
+}''');
+}
+
+/// Tests top-level mixins that includes another mixin.
+void topLevelMixinMultiRulesets() {
+ compileAndValidate(r'''
+@mixin a {
+ a {
+ color: blue;
+ background-color: red;
+ }
+}
+@mixin b {
+ #foo-id {
+ border-top: 1px solid red;
+ border-bottom: 2px solid green;
+ }
+}
+@mixin c {
+ span {
+ color: black;
+ background-color: orange;
+ }
+ @include b;
+}
+@include a;
+@include c;
+''', r'''
+a {
+ color: #00f;
+ background-color: #f00;
+}
+span {
+ color: #000;
+ background-color: #ffa500;
+}
+#foo-id {
+ border-top: 1px solid #f00;
+ border-bottom: 2px solid #008000;
+}''');
+}
+
+void topLevelMixinDeeplyNestedRulesets() {
+ compileAndValidate(r'''
+@mixin a {
+ a {
+ color: blue;
+ background-color: red;
+ }
+}
+@mixin b {
+ #foo-id {
+ border-top: 1px solid red;
+ border-bottom: 2px solid green;
+ }
+}
+@mixin f {
+ #split-bar div {
+ border: 1px solid lightgray;
+ }
+}
+@mixin e {
+ #split-bar:visited {
+ color: gray;
+ }
+ @include f;
+}
+@mixin d {
+ a:hover {
+ cursor: arrow;
+ }
+ @include e
+}
+@mixin c {
+ @include a;
+ span {
+ color: black;
+ background-color: orange;
+ }
+ @include b;
+ @include d;
+}
+@include c;
+''', r'''
+a {
+ color: #00f;
+ background-color: #f00;
+}
+span {
+ color: #000;
+ background-color: #ffa500;
+}
+#foo-id {
+ border-top: 1px solid #f00;
+ border-bottom: 2px solid #008000;
+}
+a:hover {
+ cursor: arrow;
+}
+#split-bar:visited {
+ color: #808080;
+}
+#split-bar div {
+ border: 1px solid #d3d3d3;
+}''');
+}
+
+/// Tests selector groups and other combinators.
+void topLevelMixinSelectors() {
+ compileAndValidate(r'''
+@mixin a {
+ a, b {
+ color: blue;
+ background-color: red;
+ }
+ div > span {
+ color: black;
+ background-color: orange;
+ }
+}
+
+@include a;
+''', r'''
+a, b {
+ color: #00f;
+ background-color: #f00;
+}
+div > span {
+ color: #000;
+ background-color: #ffa500;
+}''');
+}
+
+void declSimpleMixin() {
+ compileAndValidate(r'''
+@mixin div-border {
+ border: 2px dashed red;
+}
+div {
+ @include div-border;
+}
+''', r'''
+div {
+ border: 2px dashed #f00;
+}''');
+}
+
+void declMixinTwoIncludes() {
+ compileAndValidate(r'''
+@mixin div-border {
+ border: 2px dashed red;
+}
+@mixin div-color {
+ color: blue;
+}
+div {
+ @include div-border;
+ @include div-color;
+}
+''', r'''
+div {
+ border: 2px dashed #f00;
+ color: #00f;
+}''');
+}
+
+void declMixinNestedIncludes() {
+ compileAndValidate(r'''
+@mixin div-border {
+ border: 2px dashed red;
+}
+@mixin div-padding {
+ padding: .5em;
+}
+@mixin div-margin {
+ margin: 5px;
+}
+@mixin div-color {
+ @include div-padding;
+ color: blue;
+ @include div-margin;
+}
+div {
+ @include div-border;
+ @include div-color;
+}
+''', r'''
+div {
+ border: 2px dashed #f00;
+ padding: .5em;
+ color: #00f;
+ margin: 5px;
+}''');
+}
+
+void declMixinDeeperNestedIncludes() {
+ compileAndValidate(r'''
+@mixin div-border {
+ border: 2px dashed red;
+}
+@mixin div-padding {
+ padding: .5em;
+}
+@mixin div-margin {
+ margin: 5px;
+}
+@mixin div-color {
+ @include div-padding;
+ @include div-margin;
+}
+div {
+ @include div-border;
+ @include div-color;
+}
+''', r'''
+div {
+ border: 2px dashed #f00;
+ padding: .5em;
+ margin: 5px;
+}''');
+}
+
+void mixinArg() {
+ compileAndValidate(r'''
+@mixin div-border-1 {
+ border: 2px dashed red;
+}
+@mixin div-border-2() {
+ border: 22px solid blue;
+}
+@mixin div-left(@dist) {
+ margin-left: @dist;
+}
+@mixin div-right(var-margin) {
+ margin-right: var(margin);
+}
+div-1 {
+ @include div-left(10px);
+ @include div-right(100px);
+ @include div-border-1;
+}
+div-2 {
+ @include div-left(20em);
+ @include div-right(5in);
+ @include div-border-2();
+}
+div-3 {
+ @include div-border-1();
+}
+div-4 {
+ @include div-border-2;
+}
+''', r'''
+div-1 {
+ margin-left: 10px;
+ margin-right: 100px;
+ border: 2px dashed #f00;
+}
+div-2 {
+ margin-left: 20em;
+ margin-right: 5in;
+ border: 22px solid #00f;
+}
+div-3 {
+ border: 2px dashed #f00;
+}
+div-4 {
+ border: 22px solid #00f;
+}''');
+}
+
+void mixinArgs() {
+ compileAndValidate(r'''
+@mixin box-shadow(@shadows...) {
+ -moz-box-shadow: @shadows;
+ -webkit-box-shadow: @shadows;
+ box-shadow: var(shadows);
+}
+
+.shadows {
+ @include box-shadow(0px 4px 5px #666, 2px 6px 10px #999);
+}''', r'''
+.shadowed {
+ -moz-box-shadow: 0px 4px 5px #666, 2px 6px 10px #999;
+ -webkit-box-shadow: 0px 4px 5px #666, 2px 6px 10px #999;
+ box-shadow: 0px 4px 5px #666, 2px 6px 10px #999;
+}
+''');
+}
+
+void mixinManyArgs() {
+ compileAndValidate(r'''
+@mixin border(@border-values) {
+ border: @border-values
+}
+
+.primary {
+ @include border(3px solid green);
+}
+''', r'''
+.primary {
+ border: 3px solid #008000;
+}''');
+
+ compileAndValidate(r'''
+@mixin setup(@border-color, @border-style, @border-size, @color) {
+ border: @border-size @border-style @border-color;
+ color: @color;
+}
+
+.primary {
+ @include setup(red, solid, 5px, blue);
+}
+''', r'''
+.primary {
+ border: 5px solid #f00;
+ color: #00f;
+}''');
+
+ // Test passing a declaration that is multiple parameters.
+ compileAndValidate(r'''
+@mixin colors(@text, @background, @border) {
+ color: @text;
+ background-color: @background;
+ border-color: @border;
+}
+
+@values: #ff0000, #00ff00, #0000ff;
+.primary {
+ @include colors(@values);
+}
+''', r'''
+var-values: #f00, #0f0, #00f;
+.primary {
+ color: #f00;
+ background-color: #0f0;
+ border-color: #00f;
+}''');
+
+ compilePolyfillAndValidate(r'''
+@mixin colors(@text, @background, @border) {
+ color: @text;
+ background-color: @background;
+ border-color: @border;
+}
+
+@values: #ff0000, #00ff00, #0000ff;
+.primary {
+ @include colors(@values);
+}
+''', r'''
+.primary {
+ color: #f00;
+ background-color: #0f0;
+ border-color: #00f;
+}''');
+}
+
+void badDeclarationInclude() {
+ final errors = <Message>[];
+ final input = r'''
+@mixin a {
+ #foo-id {
+ color: red;
+ }
+}
+@mixin b {
+ span {
+ border: 2px dashed red;
+ @include a;
+ }
+}
+@include b;
+''';
+
+ compileCss(input, errors: errors, opts: options);
+
+ expect(errors.isNotEmpty, true);
+ expect(errors.length, 1, reason: errors.toString());
+ var error = errors[0];
+ expect(error.message, 'Using top-level mixin a as a declaration');
+ expect(error.span!.start.line, 8);
+ expect(error.span!.end.offset, 105);
+}
+
+void badTopInclude() {
+ final errors = <Message>[];
+ final input = r'''
+@mixin b {
+ color: red;
+}
+
+@mixin a {
+ span {
+ border: 2px dashed red;
+ }
+ @include b;
+}
+
+@include a;
+ ''';
+
+ compileCss(input, errors: errors, opts: options);
+
+ expect(errors.length, 1, reason: errors.toString());
+ var error = errors[0];
+ expect(error.message, 'Using declaration mixin b as top-level mixin');
+ expect(error.span!.start.line, 8);
+ expect(error.span!.end.offset, 90);
+}
+
+void emptyMixin() {
+ final errors = <Message>[];
+ final input = r'''
+@mixin a {
+}
+@mixin b {
+ border: 2px dashed red;
+ @include a;
+}
+div {
+ @include b;
+}
+ ''';
+
+ var generated = r'''
+div {
+ border: 2px dashed #f00;
+}''';
+
+ var stylesheet = compileCss(input, errors: errors, opts: options);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated);
+}
+
+void undefinedTopLevel() {
+ final errors = <Message>[];
+ final input = r'''
+@mixin a {
+ @include b;
+}
+@mixin b {
+ span {
+ border: 2px dashed red;
+ }
+ @include a;
+}
+
+@include b;
+
+ ''';
+
+ compileCss(input, errors: errors, opts: options);
+
+ expect(errors.isNotEmpty, true);
+ expect(errors.length, 1, reason: errors.toString());
+ var error = errors[0];
+ expect(error.message, 'Undefined mixin b');
+ expect(error.span!.start.line, 1);
+ expect(error.span!.start.offset, 14);
+}
+
+void undefinedDeclaration() {
+ final errors = <Message>[];
+ final input = r'''
+@mixin a {
+ @include b;
+}
+@mixin b {
+ border: 2px dashed red;
+ @include a;
+}
+div {
+ @include b;
+}
+ ''';
+
+ compileCss(input, errors: errors, opts: options);
+
+ expect(errors.isNotEmpty, true);
+ expect(errors.length, 1, reason: errors.toString());
+ var error = errors[0];
+ expect(error.message, 'Undefined mixin b');
+ expect(error.span!.start.line, 1);
+ expect(error.span!.start.offset, 14);
+}
+
+void includeGrammar() {
+ compileAndValidate(r'''
+@mixin a {
+ foo { color: red }
+}
+
+@mixin b {
+ @include a;
+ @include a;
+}
+
+@include b;
+''', r'''
+foo {
+ color: #f00;
+}
+foo {
+ color: #f00;
+}''');
+
+ compileAndValidate(r'''
+@mixin a {
+ color: red
+}
+
+foo {
+ @include a;
+ @include a
+}
+''', r'''
+foo {
+ color: #f00;
+ color: #f00;
+}''');
+
+ var errors = <Message>[];
+ var input = r'''
+@mixin a {
+ foo { color: red }
+}
+
+@mixin b {
+ @include a
+ @include a
+}
+
+@include b
+''';
+
+ compileCss(input, errors: errors, opts: options);
+
+ expect(errors, hasLength(4));
+ expect(errors[0].describe, '7:4:parsing error expected ;');
+ expect(errors[1].describe, '8:1:expected :, but found }');
+ expect(errors[2].describe, '10:11:expected }, but found end of file');
+ expect(errors[3].describe, '6:4:Using top-level mixin a as a declaration');
+}
+
+void main() {
+ group('Basic mixin', () {
+ test('include grammar', includeGrammar);
+ test('empty mixin content', emptyMixin);
+ });
+
+ group('Top-level mixin', () {
+ test('simple mixin', topLevelMixin);
+ test('mixin with two @includes', topLevelMixinTwoIncludes);
+ test('multi rulesets', topLevelMixinMultiRulesets);
+ test('multi rulesets and nesting', topLevelMixinDeeplyNestedRulesets);
+ test('selector groups', topLevelMixinSelectors);
+ });
+
+ group('Declaration mixin', () {
+ test('simple', declSimpleMixin);
+ test('with two @includes', declMixinTwoIncludes);
+ test('with includes', declMixinNestedIncludes);
+ test('with deeper nesting', declMixinDeeperNestedIncludes);
+ });
+
+ group('Mixin arguments', () {
+ test('simple arg', mixinArg);
+ test('many args', mixinArgs, skip: 'hangs at runtime');
+ test('multiple args and var decls as args', mixinManyArgs);
+ });
+
+ group('Mixin warnings', () {
+ test('undefined top-level', undefinedTopLevel);
+ test('undefined declaration', undefinedDeclaration);
+ test('detect bad top-level as declaration', badDeclarationInclude);
+ test('detect bad declaration as top-level', badTopInclude);
+ });
+}
diff --git a/pkgs/csslib/test/nested_test.dart b/pkgs/csslib/test/nested_test.dart
new file mode 100644
index 0000000..4ec72ea
--- /dev/null
+++ b/pkgs/csslib/test/nested_test.dart
@@ -0,0 +1,630 @@
+// 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:csslib/src/messages.dart';
+import 'package:test/test.dart';
+
+import 'testing.dart';
+
+void compileAndValidate(String input, String generated) {
+ var errors = <Message>[];
+ var stylesheet = compileCss(input, errors: errors, opts: simpleOptions);
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated);
+}
+
+void selectorVariations() {
+ final input1 = r'''html { color: red; }''';
+ final generated1 = r'''html {
+ color: #f00;
+}''';
+ compileAndValidate(input1, generated1);
+
+ final input2 = r'''button { span { height: 200 } }''';
+ final generated2 = r'''button {
+}
+button span {
+ height: 200;
+}''';
+ compileAndValidate(input2, generated2);
+
+ final input3 = r'''div { color: red; } button { span { height: 200 } }''';
+ final generated3 = r'''div {
+ color: #f00;
+}
+button {
+}
+button span {
+ height: 200;
+}''';
+ compileAndValidate(input3, generated3);
+
+ final input4 = r'''#header { color: red; h1 { font-size: 26px; } }''';
+ final generated4 = r'''#header {
+ color: #f00;
+}
+#header h1 {
+ font-size: 26px;
+}''';
+ compileAndValidate(input4, generated4);
+
+ final input5 = r'''
+#header {
+ color: red;
+ h1 { font-size: 26px; }
+ background-color: blue;
+}''';
+ final generated5 = r'''#header {
+ color: #f00;
+ background-color: #00f;
+}
+#header h1 {
+ font-size: 26px;
+}''';
+ compileAndValidate(input5, generated5);
+
+ final input6 = r'''html { body {color: red; }}''';
+ final generated6 = r'''html {
+}
+html body {
+ color: #f00;
+}''';
+ compileAndValidate(input6, generated6);
+
+ final input7 = r'''html body {color: red; }''';
+ final generated7 = r'''html body {
+ color: #f00;
+}''';
+ compileAndValidate(input7, generated7);
+
+ final input8 = r'''
+html, body { color: red; }
+button { height: 200 }
+body { width: 300px; }''';
+ final generated8 = r'''html, body {
+ color: #f00;
+}
+button {
+ height: 200;
+}
+body {
+ width: 300px;
+}''';
+ compileAndValidate(input8, generated8);
+
+ final input9 = '''
+html, body {
+ color: red;
+ button { height: 200 }
+ div { width: 300px; }
+}''';
+ final generated9 = r'''html, body {
+ color: #f00;
+}
+html button, body button {
+ height: 200;
+}
+html div, body div {
+ width: 300px;
+}''';
+ compileAndValidate(input9, generated9);
+
+ final input10 = '''
+html {
+ color: red;
+ button, div { height: 200 }
+ body { width: 300px; }
+}''';
+ final generated10 = r'''html {
+ color: #f00;
+}
+html button, html div {
+ height: 200;
+}
+html body {
+ width: 300px;
+}''';
+ compileAndValidate(input10, generated10);
+
+ final input11 = '''
+html, body {
+ color: red;
+ button, div { height: 200 }
+ table { width: 300px; }
+}''';
+ final generated11 = r'''html, body {
+ color: #f00;
+}
+html button, body button, html div, body div {
+ height: 200;
+}
+html table, body table {
+ width: 300px;
+}''';
+ compileAndValidate(input11, generated11);
+
+ final input12 = '''
+html, body {
+ color: red;
+ button, div {
+ span, a, ul { height: 200 }
+ }
+ table { width: 300px; }
+}''';
+ final generated12 = r'''html, body {
+ color: #f00;
+}
+'''
+ 'html button span, body button span, html div span, body div span, '
+ 'html button a, body button a, html div a, body div a, html button ul, '
+ r'''body button ul, html div ul, body div ul {
+ height: 200;
+}
+html table, body table {
+ width: 300px;
+}''';
+ compileAndValidate(input12, generated12);
+
+ final input13 = r'''
+#header {
+ div {
+ width: 100px;
+ a { height: 200px; }
+ }
+ color: blue;
+}
+span { color: #1f1f1f; }
+''';
+ final generated13 = r'''#header {
+ color: #00f;
+}
+#header div {
+ width: 100px;
+}
+#header div a {
+ height: 200px;
+}
+span {
+ color: #1f1f1f;
+}''';
+ compileAndValidate(input13, generated13);
+}
+
+void simpleNest() {
+ final input = '''
+div span { color: green; }
+#header {
+ color: red;
+ h1 {
+ font-size: 26px;
+ font-weight: bold;
+ }
+ p {
+ font-size: 12px;
+ a {
+ text-decoration: none;
+ }
+ }
+ background-color: blue;
+}
+div > span[attr="foo"] { color: yellow; }
+''';
+
+ final generated = r'''div span {
+ color: #008000;
+}
+#header {
+ color: #f00;
+ background-color: #00f;
+}
+#header h1 {
+ font-size: 26px;
+ font-weight: bold;
+}
+#header p {
+ font-size: 12px;
+}
+#header p a {
+ text-decoration: none;
+}
+div > span[attr="foo"] {
+ color: #ff0;
+}''';
+ compileAndValidate(input, generated);
+}
+
+void complexNest() {
+ final input = '''
+@font-face { font-family: arial; }
+div { color: #f0f0f0; }
+#header + div {
+ color: url(abc.png);
+ *[attr="bar"] {
+ font-size: 26px;
+ font-weight: bold;
+ }
+ p~ul {
+ font-size: 12px;
+ :not(p) {
+ text-decoration: none;
+ div > span[attr="foo"] { color: yellow; }
+ }
+ }
+ background-color: blue;
+ span {
+ color: red;
+ .one { color: blue; }
+ .two { color: green; }
+ .three { color: yellow; }
+ .four {
+ .four-1 { background-color: #00000f; }
+ .four-2 { background-color: #0000ff; }
+ .four-3 { background-color: #000fff; }
+ .four-4 {
+ height: 44px;
+ .four-4-1 { height: 10px; }
+ .four-4-2 { height: 20px; }
+ .four-4-3 { height: 30px; }
+ width: 44px;
+ }
+ }
+ }
+}
+span { color: #1f1f2f; }
+''';
+
+ final generated = r'''@font-face {
+ font-family: arial;
+}
+div {
+ color: #f0f0f0;
+}
+#header + div {
+ color: url("abc.png");
+ background-color: #00f;
+}
+#header + div *[attr="bar"] {
+ font-size: 26px;
+ font-weight: bold;
+}
+#header + div p ~ ul {
+ font-size: 12px;
+}
+#header + div p ~ ul :not(p) {
+ text-decoration: none;
+}
+#header + div p ~ ul :not(p) div > span[attr="foo"] {
+ color: #ff0;
+}
+#header + div span {
+ color: #f00;
+}
+#header + div span .one {
+ color: #00f;
+}
+#header + div span .two {
+ color: #008000;
+}
+#header + div span .three {
+ color: #ff0;
+}
+#header + div span .four .four-1 {
+ background-color: #00000f;
+}
+#header + div span .four .four-2 {
+ background-color: #00f;
+}
+#header + div span .four .four-3 {
+ background-color: #000fff;
+}
+#header + div span .four .four-4 {
+ height: 44px;
+ width: 44px;
+}
+#header + div span .four .four-4 .four-4-1 {
+ height: 10px;
+}
+#header + div span .four .four-4 .four-4-2 {
+ height: 20px;
+}
+#header + div span .four .four-4 .four-4-3 {
+ height: 30px;
+}
+span {
+ color: #1f1f2f;
+}''';
+
+ compileAndValidate(input, generated);
+}
+
+void mediaNesting() {
+ final input = r'''
+@media screen and (-webkit-min-device-pixel-ratio:0) {
+ #toggle-all {
+ image: url(test.jpb);
+ div, table {
+ background: none;
+ a { width: 100px; }
+ }
+ color: red;
+ }
+}
+''';
+ final generated = r'''
+@media screen AND (-webkit-min-device-pixel-ratio:0) {
+ #toggle-all {
+ image: url("test.jpb");
+ color: #f00;
+ }
+ #toggle-all div, #toggle-all table {
+ background: none;
+ }
+ #toggle-all div a, #toggle-all table a {
+ width: 100px;
+ }
+}''';
+
+ compileAndValidate(input, generated);
+}
+
+void simpleThis() {
+ final input = '''#header {
+ h1 {
+ font-size: 26px;
+ font-weight: bold;
+ }
+ p { font-size: 12px;
+ a { text-decoration: none;
+ &:hover { border-width: 1px }
+ }
+ }
+}
+''';
+
+ final generated = r'''#header {
+}
+#header h1 {
+ font-size: 26px;
+ font-weight: bold;
+}
+#header p {
+ font-size: 12px;
+}
+#header p a {
+ text-decoration: none;
+}
+#header p a:hover {
+ border-width: 1px;
+}''';
+
+ compileAndValidate(input, generated);
+}
+
+void complexThis() {
+ final input1 = r'''
+.light {
+ .leftCol {
+ .textLink {
+ color: fooL1;
+ &:hover { color: barL1;}
+ }
+ .picLink {
+ background-image: url(/fooL1.jpg);
+ &:hover { background-image: url(/barL1.jpg);}
+ }
+ .textWithIconLink {
+ color: fooL2;
+ background-image: url(/fooL2.jpg);
+ &:hover { color: barL2; background-image: url(/barL2.jpg);}
+ }
+ }
+}''';
+
+ final generated1 = r'''.light {
+}
+.light .leftCol .textLink {
+ color: fooL1;
+}
+.light .leftCol .textLink:hover {
+ color: barL1;
+}
+.light .leftCol .picLink {
+ background-image: url("/fooL1.jpg");
+}
+.light .leftCol .picLink:hover {
+ background-image: url("/barL1.jpg");
+}
+.light .leftCol .textWithIconLink {
+ color: fooL2;
+ background-image: url("/fooL2.jpg");
+}
+.light .leftCol .textWithIconLink:hover {
+ color: barL2;
+ background-image: url("/barL2.jpg");
+}''';
+
+ compileAndValidate(input1, generated1);
+
+ final input2 = r'''
+.textLink {
+ .light .leftCol & {
+ color: fooL1;
+ &:hover { color: barL1; }
+ }
+ .light .rightCol & {
+ color: fooL3;
+ &:hover { color: barL3; }
+ }
+}''';
+
+ final generated2 = r'''
+.textLink {
+}
+.light .leftCol .textLink {
+ color: fooL1;
+}
+.light .leftCol .textLink:hover {
+ color: barL1;
+}
+.light .rightCol .textLink {
+ color: fooL3;
+}
+.light .rightCol .textLink:hover {
+ color: barL3;
+}''';
+
+ compileAndValidate(input2, generated2);
+}
+
+void variationsThis() {
+ final input1 = r'''
+.textLink {
+ a {
+ light .leftCol & {
+ color: red;
+ }
+ }
+}''';
+ final generated1 = r'''.textLink {
+}
+light .leftCol .textLink a {
+ color: #f00;
+}''';
+
+ compileAndValidate(input1, generated1);
+
+ final input2 = r'''.textLink {
+ a {
+ & light .leftCol & {
+ color: red;
+ }
+ }
+}''';
+ final generated2 = r'''.textLink {
+}
+.textLink a light .leftCol .textLink a {
+ color: #f00;
+}''';
+ compileAndValidate(input2, generated2);
+
+ final input3 = r'''
+.textLink {
+ a {
+ & light .leftCol { color: red; }
+ }
+}''';
+ final generated3 = r'''.textLink {
+}
+.textLink a light .leftCol {
+ color: #f00;
+}''';
+ compileAndValidate(input3, generated3);
+
+ final input4 = r'''
+.textLink {
+ a {
+ & light .leftCol { color: red; }
+ &:hover { width: 100px; }
+ }
+}''';
+ final generated4 = r'''.textLink {
+}
+.textLink a light .leftCol {
+ color: #f00;
+}
+.textLink a:hover {
+ width: 100px;
+}''';
+ compileAndValidate(input4, generated4);
+
+ final input5 = r'''.textLink { a { &:hover { color: red; } } }''';
+ final generated5 = r'''.textLink {
+}
+.textLink a:hover {
+ color: #f00;
+}''';
+
+ compileAndValidate(input5, generated5);
+
+ final input6 = r'''.textLink { &:hover { color: red; } }''';
+ final generated6 = r'''.textLink {
+}
+.textLink:hover {
+ color: #f00;
+}''';
+ compileAndValidate(input6, generated6);
+
+ final input7 = r'''.textLink { a { & + & { color: red; } } }''';
+ final generated7 = r'''.textLink {
+}
+.textLink a + .textLink a {
+ color: #f00;
+}''';
+ compileAndValidate(input7, generated7);
+
+ final input8 = r'''.textLink { a { & { color: red; } } }''';
+ final generated8 = r'''.textLink {
+}
+.textLink a {
+ color: #f00;
+}''';
+ compileAndValidate(input8, generated8);
+
+ final input9 = r'''.textLink { a { & ~ & { color: red; } } }''';
+ final generated9 = r'''.textLink {
+}
+.textLink a ~ .textLink a {
+ color: #f00;
+}''';
+ compileAndValidate(input9, generated9);
+
+ final input10 = r'''.textLink { a { & & { color: red; } } }''';
+ final generated10 = r'''.textLink {
+}
+.textLink a .textLink a {
+ color: #f00;
+}''';
+ compileAndValidate(input10, generated10);
+}
+
+void thisCombinator() {
+ var input = r'''
+.btn {
+ color: red;
+}
+.btn + .btn {
+ margin-left: 5px;
+}
+input.second {
+ & + label {
+ color: blue;
+ }
+}
+''';
+
+ var generated = r'''.btn {
+ color: #f00;
+}
+.btn + .btn {
+ margin-left: 5px;
+}
+input.second {
+}
+input.second + label {
+ color: #00f;
+}''';
+
+ compileAndValidate(input, generated);
+}
+
+void main() {
+ test('Selector and Nested Variations', selectorVariations);
+ test('Simple nesting', simpleNest);
+ test('Complex nesting', complexNest);
+ test('@media nesting', mediaNesting);
+ test('Simple &', simpleThis);
+ test('Variations &', variationsThis);
+ test('Complex &', complexThis);
+ test('& with + selector', thisCombinator);
+}
diff --git a/pkgs/csslib/test/repros_test.dart b/pkgs/csslib/test/repros_test.dart
new file mode 100644
index 0000000..ef359ca
--- /dev/null
+++ b/pkgs/csslib/test/repros_test.dart
@@ -0,0 +1,61 @@
+// Copyright (c) 2023, 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:csslib/parser.dart';
+import 'package:csslib/visitor.dart';
+import 'package:test/test.dart';
+
+const options = PreprocessorOptions(
+ useColors: false,
+ warningsAsErrors: true,
+ inputFile: 'memory',
+);
+
+void main() {
+ // Repro test for https://github.com/dart-lang/csslib/issues/136.
+ test('repro_136', () {
+ final errors = <Message>[];
+
+ final css = '''
+.hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),
+.hero.is-white strong {
+ color: inherit;
+}
+''';
+ final stylesheet = parse(css, errors: errors, options: options);
+ expect(stylesheet.topLevels, hasLength(1));
+ var ruleset = stylesheet.topLevels.first as RuleSet;
+
+ // This should be length 2.
+ expect(ruleset.selectorGroup!.selectors, hasLength(1));
+
+ // This test is expected to start to fail once we better support pseudo
+ // selectors.
+ // This should be empty.
+ expect(errors, hasLength(3));
+ });
+
+ // Repro test for https://github.com/dart-lang/csslib/issues/171.
+ test('repro_171', () {
+ final errors = <Message>[];
+ var stylesheet =
+ parse('body { width: 1000 px; }', errors: errors, options: options);
+ expect(errors, isEmpty);
+
+ expect(stylesheet.topLevels, hasLength(1));
+ var ruleset = stylesheet.topLevels.first as RuleSet;
+ expect(ruleset.selectorGroup!.selectors, hasLength(1));
+ expect(ruleset.declarationGroup.declarations, hasLength(1));
+
+ errors.clear();
+ stylesheet =
+ parse('body { width: 1000px; }', errors: errors, options: options);
+ expect(errors, isEmpty);
+
+ expect(stylesheet.topLevels, hasLength(1));
+ ruleset = stylesheet.topLevels.first as RuleSet;
+ expect(ruleset.selectorGroup!.selectors, hasLength(1));
+ expect(ruleset.declarationGroup.declarations, hasLength(1));
+ });
+}
diff --git a/pkgs/csslib/test/selector_test.dart b/pkgs/csslib/test/selector_test.dart
new file mode 100644
index 0000000..6d63cf8
--- /dev/null
+++ b/pkgs/csslib/test/selector_test.dart
@@ -0,0 +1,92 @@
+// 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 'package:csslib/parser.dart';
+import 'package:term_glyph/term_glyph.dart' as glyph;
+import 'package:test/test.dart';
+
+import 'testing.dart';
+
+void testSelectorSuccesses() {
+ var errors = <Message>[];
+ var selectorAst = selector('#div .foo', errors: errors);
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect('#div .foo', compactOutput(selectorAst));
+
+ // Valid selectors for class names.
+ selectorAst = selector('.foo', errors: errors..clear());
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect('.foo', compactOutput(selectorAst));
+
+ selectorAst = selector('.foobar .xyzzy', errors: errors..clear());
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect('.foobar .xyzzy', compactOutput(selectorAst));
+
+ selectorAst = selector('.foobar .a-story .xyzzy', errors: errors..clear());
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect('.foobar .a-story .xyzzy', compactOutput(selectorAst));
+
+ selectorAst =
+ selector('.foobar .xyzzy .a-story .b-story', errors: errors..clear());
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect('.foobar .xyzzy .a-story .b-story', compactOutput(selectorAst));
+
+ // Valid selectors for element IDs.
+ selectorAst = selector('#id1', errors: errors..clear());
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect('#id1', compactOutput(selectorAst));
+
+ selectorAst = selector('#id-number-3', errors: errors..clear());
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect('#id-number-3', compactOutput(selectorAst));
+
+ selectorAst = selector('#_privateId', errors: errors..clear());
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect('#_privateId', compactOutput(selectorAst));
+
+ selectorAst = selector(':host', errors: errors..clear());
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(compactOutput(selectorAst), ':host');
+
+ selectorAst = selector(':host(.foo)', errors: errors..clear());
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(compactOutput(selectorAst), ':host(.foo)');
+
+ selectorAst = selector(':host-context(.foo)', errors: errors..clear());
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(compactOutput(selectorAst), ':host-context(.foo)');
+}
+
+// TODO(terry): Move this failure case to a failure_test.dart when the analyzer
+// and validator exit then they'll be a bunch more checks.
+void testSelectorFailures() {
+ var errors = <Message>[];
+
+ // Test for invalid class name (can't start with number).
+ selector('.foobar .1a-story .xyzzy', errors: errors);
+ expect(errors.isEmpty, false);
+ expect(
+ errors[0].toString(),
+ 'error on line 1, column 9: name must start with a alpha character, but '
+ 'found a number\n'
+ ' ,\n'
+ '1 | .foobar .1a-story .xyzzy\n'
+ ' | ^^\n'
+ ' \'');
+
+ selector(':host()', errors: errors..clear());
+ expect(
+ errors.first.toString(),
+ 'error on line 1, column 7: expected a selector argument, but found )\n'
+ ' ,\n'
+ '1 | :host()\n'
+ ' | ^\n'
+ ' \'');
+}
+
+void main() {
+ glyph.ascii = true;
+ test('Valid Selectors', testSelectorSuccesses);
+ test('Invalid Selectors', testSelectorFailures);
+}
diff --git a/pkgs/csslib/test/testing.dart b/pkgs/csslib/test/testing.dart
new file mode 100644
index 0000000..2c76e58
--- /dev/null
+++ b/pkgs/csslib/test/testing.dart
@@ -0,0 +1,81 @@
+// 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.
+
+/// Common definitions used for setting up the test environment.
+library;
+
+import 'package:csslib/parser.dart';
+import 'package:csslib/visitor.dart';
+
+export 'package:csslib/src/preprocessor_options.dart';
+
+const PreprocessorOptions simpleOptionsWithCheckedAndWarningsAsErrors =
+ PreprocessorOptions(
+ useColors: false,
+ checked: true,
+ warningsAsErrors: true,
+ inputFile: 'memory',
+);
+
+const PreprocessorOptions simpleOptions =
+ PreprocessorOptions(useColors: false, inputFile: 'memory');
+
+const PreprocessorOptions options = PreprocessorOptions(
+ useColors: false, warningsAsErrors: true, inputFile: 'memory');
+
+/// Spin-up CSS parser in checked mode to detect any problematic CSS. Normally,
+/// CSS will allow any property/value pairs regardless of validity; all of our
+/// tests (by default) will ensure that the CSS is really valid.
+StyleSheet parseCss(String cssInput,
+ {List<Message>? errors, PreprocessorOptions? opts}) =>
+ parse(cssInput,
+ errors: errors,
+ options: opts ?? simpleOptionsWithCheckedAndWarningsAsErrors);
+
+/// Spin-up CSS parser in checked mode to detect any problematic CSS. Normally,
+/// CSS will allow any property/value pairs regardless of validity; all of our
+/// tests (by default) will ensure that the CSS is really valid.
+StyleSheet compileCss(String cssInput,
+ {List<Message>? errors,
+ PreprocessorOptions? opts,
+ bool polyfill = false,
+ List<StyleSheet>? includes}) =>
+ compile(cssInput,
+ errors: errors,
+ options: opts ?? simpleOptionsWithCheckedAndWarningsAsErrors,
+ polyfill: polyfill,
+ includes: includes);
+
+StyleSheet polyFillCompileCss(String input,
+ {List<Message>? errors, PreprocessorOptions? opts}) =>
+ compileCss(input, errors: errors, polyfill: true, opts: opts);
+
+/// Pretty printer for CSS.
+String prettyPrint(StyleSheet ss) {
+ // Walk the tree testing basic Visitor class.
+ walkTree(ss);
+ return (CssPrinter()..visitTree(ss, pretty: true)).toString();
+}
+
+/// Helper function to emit compact (non-pretty printed) CSS for suite test
+/// comparisons. Spaces, new lines, etc. are reduced for easier comparisons of
+/// expected suite test results.
+String compactOutput(StyleSheet ss) {
+ walkTree(ss);
+ return (CssPrinter()..visitTree(ss, pretty: false)).toString();
+}
+
+String printExpressions(Expressions node) {
+ var printer = CssPrinter();
+ // TODO: It would be nice if TreeNode had an `accept` method.
+ printer.visitExpressions(node);
+ return printer.toString();
+}
+
+/// Walks the style sheet tree does nothing; insures the basic walker works.
+void walkTree(StyleSheet ss) {
+ Visitor().visitTree(ss);
+}
+
+String dumpTree(StyleSheet ss) => treeToDebugString(ss);
diff --git a/pkgs/csslib/test/third_party_samples_test.dart b/pkgs/csslib/test/third_party_samples_test.dart
new file mode 100644
index 0000000..f4aee70
--- /dev/null
+++ b/pkgs/csslib/test/third_party_samples_test.dart
@@ -0,0 +1,43 @@
+// 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.
+
+@TestOn('vm')
+library;
+
+import 'dart:io';
+
+import 'package:csslib/parser.dart';
+import 'package:path/path.dart' as path;
+import 'package:test/test.dart';
+
+const testOptions = PreprocessorOptions(
+ useColors: false,
+ checked: false,
+ warningsAsErrors: true,
+ inputFile: 'memory',
+);
+
+void testCSSFile(File cssFile) {
+ final errors = <Message>[];
+ final css = cssFile.readAsStringSync();
+ final stylesheet = parse(css, errors: errors, options: testOptions);
+
+ expect(stylesheet, isNotNull);
+ expect(errors, isEmpty, reason: errors.toString());
+}
+
+void main() async {
+ // Iterate over all sub-folders of third_party,
+ // and then all css files in those.
+ final thirdParty = path.join(Directory.current.path, 'third_party');
+ for (var entity in Directory(thirdParty).listSync()) {
+ if (await FileSystemEntity.isDirectory(entity.path)) {
+ for (var element in Directory(entity.path).listSync()) {
+ if (element is File && element.uri.pathSegments.last.endsWith('.css')) {
+ test(element.uri.pathSegments.last, () => testCSSFile(element));
+ }
+ }
+ }
+ }
+}
diff --git a/pkgs/csslib/test/var_test.dart b/pkgs/csslib/test/var_test.dart
new file mode 100644
index 0000000..b3b0933
--- /dev/null
+++ b/pkgs/csslib/test/var_test.dart
@@ -0,0 +1,952 @@
+// 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:csslib/src/messages.dart';
+import 'package:term_glyph/term_glyph.dart' as glyph;
+import 'package:test/test.dart';
+
+import 'testing.dart';
+
+void compileAndValidate(String input, String generated) {
+ var errors = <Message>[];
+ var stylesheet = compileCss(input, errors: errors, opts: options);
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated);
+}
+
+void compilePolyfillAndValidate(String input, String generated) {
+ var errors = <Message>[];
+ var stylesheet = polyFillCompileCss(input, errors: errors, opts: options);
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated);
+}
+
+void simpleVar() {
+ final input = '''
+:root {
+ var-color-background: red;
+ var-color-foreground: blue;
+
+ var-c: #00ff00;
+ var-b: var(c);
+ var-a: var(b);
+ var-level-1-normal: 1px;
+}
+.testIt {
+ color: var(color-foreground);
+ background: var(color-background);
+ border-radius: var(level-1-normal);
+}
+''';
+
+ final generated = '''
+:root {
+ var-color-background: #f00;
+ var-color-foreground: #00f;
+ var-c: #0f0;
+ var-b: var(c);
+ var-a: var(b);
+ var-level-1-normal: 1px;
+}
+.testIt {
+ color: var(color-foreground);
+ background: var(color-background);
+ border-radius: var(level-1-normal);
+}''';
+
+ final generatedPolyfill = '''
+:root {
+}
+.testIt {
+ color: #00f;
+ background: #f00;
+ border-radius: 1px;
+}''';
+
+ compileAndValidate(input, generated);
+ compilePolyfillAndValidate(input, generatedPolyfill);
+}
+
+void expressionsVar() {
+ final input = '''
+:root {
+ var-color-background: red;
+ var-color-foreground: blue;
+
+ var-c: #00ff00;
+ var-b: var(c);
+ var-a: var(b);
+
+ var-image: url(test.png);
+
+ var-b-width: 20cm;
+ var-m-width: 33%;
+ var-b-height: 30EM;
+ var-width: .6in;
+ var-length: 1.2in;
+ var-web-stuff: -10Px;
+ var-rgba: rgba(10,20,255);
+ var-transition: color 0.4s;
+ var-transform: rotate(20deg);
+ var-content: "✔";
+ var-text-shadow: 0 -1px 0 #bfbfbf;
+ var-font-family: Gentium;
+ var-src: url("http://example.com/fonts/Gentium.ttf");
+ var-src-1: local(Gentium Bold), local(Gentium-Bold), url("GentiumBold.ttf");
+ var-unicode-range: U+000-49F, U+2000-27FF, U+2900-2BFF, U+1D400-1D7FF;
+ var-unicode-range-1: U+0A-FF, U+980-9FF, U+????, U+3???;
+ var-grid-columns: 10px ("content" 1fr 10px) [4];
+}
+
+.testIt {
+ color: var(color-foreground);
+ background: var(c);
+ background-image: var(image);
+
+ border-width: var(b-width);
+ margin-width: var(m-width);
+ border-height: var(b-height);
+ width: var(width);
+ length: var(length);
+ -web-stuff: var(web-stuff);
+ background-color: var(rgba);
+
+ transition: var(transition);
+ transform: var(transform);
+ content: var(content);
+ text-shadow: var(text-shadow);
+}
+
+@font-face {
+ font-family: var(font-family);
+ src: var(src);
+ unicode-range: var(unicode-range);
+}
+
+@font-face {
+ font-family: var(font-family);
+ src: var(src-1);
+ unicode-range: var(unicode-range-1);
+}
+
+.foobar {
+ grid-columns: var(grid-columns);
+}
+''';
+
+ final generated = '''
+:root {
+ var-color-background: #f00;
+ var-color-foreground: #00f;
+ var-c: #0f0;
+ var-b: var(c);
+ var-a: var(b);
+ var-image: url("test.png");
+ var-b-width: 20cm;
+ var-m-width: 33%;
+ var-b-height: 30em;
+ var-width: .6in;
+ var-length: 1.2in;
+ var-web-stuff: -10px;
+ var-rgba: rgba(10, 20, 255);
+ var-transition: color 0.4s;
+ var-transform: rotate(20deg);
+ var-content: "✔";
+ var-text-shadow: 0 -1px 0 #bfbfbf;
+ var-font-family: Gentium;
+ var-src: url("http://example.com/fonts/Gentium.ttf");
+ var-src-1: local(Gentium Bold), local(Gentium-Bold), url("GentiumBold.ttf");
+ var-unicode-range: U+000-49F, U+2000-27FF, U+2900-2BFF, U+1D400-1D7FF;
+ var-unicode-range-1: U+0A-FF, U+980-9FF, U+????, U+3???;
+ var-grid-columns: 10px ("content" 1fr 10px) [4];
+}
+.testIt {
+ color: var(color-foreground);
+ background: var(c);
+ background-image: var(image);
+ border-width: var(b-width);
+ margin-width: var(m-width);
+ border-height: var(b-height);
+ width: var(width);
+ length: var(length);
+ -web-stuff: var(web-stuff);
+ background-color: var(rgba);
+ transition: var(transition);
+ transform: var(transform);
+ content: var(content);
+ text-shadow: var(text-shadow);
+}
+@font-face {
+ font-family: var(font-family);
+ src: var(src);
+ unicode-range: var(unicode-range);
+}
+@font-face {
+ font-family: var(font-family);
+ src: var(src-1);
+ unicode-range: var(unicode-range-1);
+}
+.foobar {
+ grid-columns: var(grid-columns);
+}''';
+
+ compileAndValidate(input, generated);
+
+ var generatedPolyfill = r'''
+:root {
+}
+.testIt {
+ color: #00f;
+ background: #0f0;
+ background-image: url("test.png");
+ border-width: 20cm;
+ margin-width: 33%;
+ border-height: 30em;
+ width: .6in;
+ length: 1.2in;
+ -web-stuff: -10px;
+ background-color: rgba(10, 20, 255);
+ transition: color 0.4s;
+ transform: rotate(20deg);
+ content: "✔";
+ text-shadow: 0 -1px 0 #bfbfbf;
+}
+@font-face {
+ font-family: Gentium;
+ src: url("http://example.com/fonts/Gentium.ttf");
+ unicode-range: U+000-49F, U+2000-27FF, U+2900-2BFF, U+1D400-1D7FF;
+}
+@font-face {
+ font-family: Gentium;
+ src: local(Gentium Bold), local(Gentium-Bold), url("GentiumBold.ttf");
+ unicode-range: U+0A-FF, U+980-9FF, U+????, U+3???;
+}
+.foobar {
+ grid-columns: 10px ("content" 1fr 10px) [4];
+}''';
+
+ compilePolyfillAndValidate(input, generatedPolyfill);
+}
+
+void defaultVar() {
+ final input = '''
+:root {
+ var-color-background: red;
+ var-color-foreground: blue;
+
+ var-a: var(b, #0a0);
+ var-b: var(c, #0b0);
+ var-c: #00ff00;
+
+ var-image: url(test.png);
+
+ var-b-width: 20cm;
+ var-m-width: 33%;
+ var-b-height: 30EM;
+}
+
+.test {
+ background-color: var(test, orange);
+}
+
+body {
+ background: var(a) var(image) no-repeat right top;
+}
+
+div {
+ background: var(color-background) url('img_tree.png') no-repeat right top;
+}
+
+.test-2 {
+ background: var(color-background) var(image-2, url('img_1.png'))
+ no-repeat right top;
+}
+
+.test-3 {
+ background: var(color-background) var(image) no-repeat right top;
+}
+
+.test-4 {
+ background: #ffff00 var(image) no-repeat right top;
+}
+
+.test-5 {
+ background: var(test-color, var(a)) var(image) no-repeat right top;
+}
+
+.test-6 {
+ border: red var(a-1, solid 20px);
+}
+''';
+
+ final generated = '''
+:root {
+ var-color-background: #f00;
+ var-color-foreground: #00f;
+ var-a: var(b, #0a0);
+ var-b: var(c, #0b0);
+ var-c: #0f0;
+ var-image: url("test.png");
+ var-b-width: 20cm;
+ var-m-width: 33%;
+ var-b-height: 30em;
+}
+.test {
+ background-color: var(test, #ffa500);
+}
+body {
+ background: var(a) var(image) no-repeat right top;
+}
+div {
+ background: var(color-background) url("img_tree.png") no-repeat right top;
+}
+.test-2 {
+ background: var(color-background) var(image-2, url("img_1.png")) no-repeat right top;
+}
+.test-3 {
+ background: var(color-background) var(image) no-repeat right top;
+}
+.test-4 {
+ background: #ff0 var(image) no-repeat right top;
+}
+.test-5 {
+ background: var(test-color, var(a)) var(image) no-repeat right top;
+}
+.test-6 {
+ border: #f00 var(a-1, solid 20px);
+}''';
+
+ compileAndValidate(input, generated);
+
+ var generatedPolyfill = r'''
+:root {
+}
+.test {
+ background-color: #ffa500;
+}
+body {
+ background: #0a0 url("test.png") no-repeat right top;
+}
+div {
+ background: #f00 url("img_tree.png") no-repeat right top;
+}
+.test-2 {
+ background: #f00 url("img_1.png") no-repeat right top;
+}
+.test-3 {
+ background: #f00 url("test.png") no-repeat right top;
+}
+.test-4 {
+ background: #ff0 url("test.png") no-repeat right top;
+}
+.test-5 {
+ background: #0a0 url("test.png") no-repeat right top;
+}
+.test-6 {
+ border: #f00 solid 20px;
+}''';
+
+ compilePolyfillAndValidate(input, generatedPolyfill);
+}
+
+void undefinedVars() {
+ final errors = <Message>[];
+ final input = '''
+:root {
+ var-color-background: red;
+ var-color-foreground: blue;
+
+ var-a: var(b);
+ var-b: var(c);
+ var-c: #00ff00;
+
+ var-one: var(two);
+ var-two: var(one);
+
+ var-four: var(five);
+ var-five: var(six);
+ var-six: var(four);
+
+ var-def-1: var(def-2);
+ var-def-2: var(def-3);
+ var-def-3: var(def-2);
+}
+
+.testIt {
+ color: var(color-foreground);
+ background: var(color-background);
+}
+.test-1 {
+ color: var(c);
+}
+.test-2 {
+ color: var(one);
+ background: var(six);
+}
+''';
+
+ final generatedPolyfill = '''
+:root {
+}
+.testIt {
+ color: #00f;
+ background: #f00;
+}
+.test-1 {
+ color: #0f0;
+}
+.test-2 {
+ color: ;
+ background: ;
+}''';
+
+ var errorStrings = [
+ 'error on line 5, column 14: Variable is not defined.\n'
+ ' ,\n'
+ '5 | var-a: var(b);\n'
+ ' | ^^\n'
+ ' \'',
+ 'error on line 6, column 14: Variable is not defined.\n'
+ ' ,\n'
+ '6 | var-b: var(c);\n'
+ ' | ^^\n'
+ ' \'',
+ 'error on line 9, column 16: Variable is not defined.\n'
+ ' ,\n'
+ '9 | var-one: var(two);\n'
+ ' | ^^^^\n'
+ ' \'',
+ 'error on line 12, column 17: Variable is not defined.\n'
+ ' ,\n'
+ '12 | var-four: var(five);\n'
+ ' | ^^^^^\n'
+ ' \'',
+ 'error on line 13, column 17: Variable is not defined.\n'
+ ' ,\n'
+ '13 | var-five: var(six);\n'
+ ' | ^^^^\n'
+ ' \'',
+ 'error on line 16, column 18: Variable is not defined.\n'
+ ' ,\n'
+ '16 | var-def-1: var(def-2);\n'
+ ' | ^^^^^^\n'
+ ' \'',
+ 'error on line 17, column 18: Variable is not defined.\n'
+ ' ,\n'
+ '17 | var-def-2: var(def-3);\n'
+ ' | ^^^^^^\n'
+ ' \'',
+ ];
+
+ var generated = r'''
+:root {
+ var-color-background: #f00;
+ var-color-foreground: #00f;
+ var-a: var(b);
+ var-b: var(c);
+ var-c: #0f0;
+ var-one: var(two);
+ var-two: var(one);
+ var-four: var(five);
+ var-five: var(six);
+ var-six: var(four);
+ var-def-1: var(def-2);
+ var-def-2: var(def-3);
+ var-def-3: var(def-2);
+}
+.testIt {
+ color: var(color-foreground);
+ background: var(color-background);
+}
+.test-1 {
+ color: var(c);
+}
+.test-2 {
+ color: var(one);
+ background: var(six);
+}''';
+ var testBitMap = 0;
+
+ compileAndValidate(input, generated);
+
+ var stylesheet =
+ polyFillCompileCss(input, errors: errors..clear(), opts: options);
+
+ expect(errors.length, errorStrings.length, reason: errors.toString());
+ testBitMap = 0;
+
+ outer:
+ for (var error in errors) {
+ var errorString = error.toString();
+ for (var i = 0; i < errorStrings.length; i++) {
+ if (errorString == errorStrings[i]) {
+ testBitMap |= 1 << i;
+ continue outer;
+ }
+ }
+ fail('Unexpected error string: $errorString');
+ }
+ expect(testBitMap, equals((1 << errorStrings.length) - 1));
+ expect(prettyPrint(stylesheet), generatedPolyfill);
+}
+
+void parserVar() {
+ final input = '''
+:root {
+ var-color-background: red;
+ var-color-foreground: blue;
+
+ var-c: #00ff00;
+ var-b: var(c);
+ var-a: var(b);
+
+ var-image: url(test.png);
+
+ var-b-width: 20cm;
+ var-m-width: 33%;
+ var-b-height: 30EM;
+ var-width: .6in;
+ var-length: 1.2in;
+ var-web-stuff: -10Px;
+ var-rgba: rgba(10,20,255);
+ var-transition: color 0.4s;
+ var-transform: rotate(20deg);
+ var-content: "✔";
+ var-text-shadow: 0 -1px 0 #bfbfbf;
+ var-font-family: Gentium;
+ var-src: url("http://example.com/fonts/Gentium.ttf");
+ var-src-1: local(Gentium Bold), local(Gentium-Bold), url("GentiumBold.ttf");
+ var-unicode-range: U+000-49F, U+2000-27FF, U+2900-2BFF, U+1D400-1D7FF;
+ var-unicode-range-1: U+0A-FF, U+980-9FF, U+????, U+3???;
+ var-grid-columns: 10px ("content" 1fr 10px) [4];
+}
+
+.testIt {
+ color: var(color-foreground);
+ background: var(c);
+ background-image: var(image);
+
+ border-width: var(b-width);
+ margin-width: var(m-width);
+ border-height: var(b-height);
+ width: var(width);
+ length: var(length);
+ -web-stuff: var(web-stuff);
+ background-color: var(rgba);
+
+ transition: var(transition);
+ transform: var(transform);
+ content: var(content);
+ text-shadow: var(text-shadow);
+}
+
+@font-face {
+ font-family: var(font-family);
+ src: var(src);
+ unicode-range: var(unicode-range);
+}
+
+@font-face {
+ font-family: var(font-family);
+ src: var(src-1);
+ unicode-range: var(unicode-range-1);
+}
+
+.foobar {
+ grid-columns: var(grid-columns);
+}
+''';
+
+ final generated = '''
+:root {
+ var-color-background: #f00;
+ var-color-foreground: #00f;
+ var-c: #0f0;
+ var-b: var(c);
+ var-a: var(b);
+ var-image: url("test.png");
+ var-b-width: 20cm;
+ var-m-width: 33%;
+ var-b-height: 30em;
+ var-width: .6in;
+ var-length: 1.2in;
+ var-web-stuff: -10px;
+ var-rgba: rgba(10, 20, 255);
+ var-transition: color 0.4s;
+ var-transform: rotate(20deg);
+ var-content: "✔";
+ var-text-shadow: 0 -1px 0 #bfbfbf;
+ var-font-family: Gentium;
+ var-src: url("http://example.com/fonts/Gentium.ttf");
+ var-src-1: local(Gentium Bold), local(Gentium-Bold), url("GentiumBold.ttf");
+ var-unicode-range: U+000-49F, U+2000-27FF, U+2900-2BFF, U+1D400-1D7FF;
+ var-unicode-range-1: U+0A-FF, U+980-9FF, U+????, U+3???;
+ var-grid-columns: 10px ("content" 1fr 10px) [4];
+}
+.testIt {
+ color: var(color-foreground);
+ background: var(c);
+ background-image: var(image);
+ border-width: var(b-width);
+ margin-width: var(m-width);
+ border-height: var(b-height);
+ width: var(width);
+ length: var(length);
+ -web-stuff: var(web-stuff);
+ background-color: var(rgba);
+ transition: var(transition);
+ transform: var(transform);
+ content: var(content);
+ text-shadow: var(text-shadow);
+}
+@font-face {
+ font-family: var(font-family);
+ src: var(src);
+ unicode-range: var(unicode-range);
+}
+@font-face {
+ font-family: var(font-family);
+ src: var(src-1);
+ unicode-range: var(unicode-range-1);
+}
+.foobar {
+ grid-columns: var(grid-columns);
+}''';
+
+ compileAndValidate(input, generated);
+
+ var generatedPolyfill = r'''
+:root {
+}
+.testIt {
+ color: #00f;
+ background: #0f0;
+ background-image: url("test.png");
+ border-width: 20cm;
+ margin-width: 33%;
+ border-height: 30em;
+ width: .6in;
+ length: 1.2in;
+ -web-stuff: -10px;
+ background-color: rgba(10, 20, 255);
+ transition: color 0.4s;
+ transform: rotate(20deg);
+ content: "✔";
+ text-shadow: 0 -1px 0 #bfbfbf;
+}
+@font-face {
+ font-family: Gentium;
+ src: url("http://example.com/fonts/Gentium.ttf");
+ unicode-range: U+000-49F, U+2000-27FF, U+2900-2BFF, U+1D400-1D7FF;
+}
+@font-face {
+ font-family: Gentium;
+ src: local(Gentium Bold), local(Gentium-Bold), url("GentiumBold.ttf");
+ unicode-range: U+0A-FF, U+980-9FF, U+????, U+3???;
+}
+.foobar {
+ grid-columns: 10px ("content" 1fr 10px) [4];
+}''';
+ compilePolyfillAndValidate(input, generatedPolyfill);
+}
+
+void testVar() {
+ final errors = <Message>[];
+ final input = '''
+@color-background: red;
+@color-foreground: blue;
+
+.test {
+ background-color: var(color-background);
+ color: var(color-foreground);
+}
+''';
+ final generated = '''
+var-color-background: #f00;
+var-color-foreground: #00f;
+.test {
+ background-color: var(color-background);
+ color: var(color-foreground);
+}''';
+
+ var stylesheet = parseCss(input, errors: errors, opts: simpleOptions);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated);
+
+ compileAndValidate(input, generated);
+
+ final input2 = '''
+@color-background: red;
+@color-foreground: blue;
+
+.test {
+ background-color: @color-background;
+ color: @color-foreground;
+}
+''';
+ final generated2 = '''
+var-color-background: #f00;
+var-color-foreground: #00f;
+.test {
+ background-color: var(color-background);
+ color: var(color-foreground);
+}''';
+
+ stylesheet = parseCss(input, errors: errors..clear(), opts: simpleOptions);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated2);
+
+ compileAndValidate(input2, generated2);
+}
+
+void testLess() {
+ final errors = <Message>[];
+ final input = '''
+@color-background: red;
+@color-foreground: blue;
+
+.test {
+ background-color: var(color-background);
+ color: var(color-foreground);
+}
+''';
+ final generated = '''
+var-color-background: #f00;
+var-color-foreground: #00f;
+.test {
+ background-color: var(color-background);
+ color: var(color-foreground);
+}''';
+
+ var stylesheet = parseCss(input, errors: errors, opts: simpleOptions);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated);
+
+ compileAndValidate(input, generated);
+
+ final input2 = '''
+@color-background: red;
+@color-foreground: blue;
+
+.test {
+ background-color: @color-background;
+ color: @color-foreground;
+}
+''';
+ final generated2 = '''
+var-color-background: #f00;
+var-color-foreground: #00f;
+.test {
+ background-color: var(color-background);
+ color: var(color-foreground);
+}''';
+
+ stylesheet = parseCss(input, errors: errors..clear(), opts: simpleOptions);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated2);
+
+ compileAndValidate(input2, generated2);
+}
+
+void polyfill() {
+ compilePolyfillAndValidate(r'''
+@color-background: red;
+@color-foreground: blue;
+.test {
+ background-color: @color-background;
+ color: @color-foreground;
+}''', r'''
+.test {
+ background-color: #f00;
+ color: #00f;
+}''');
+}
+
+void testIndirects() {
+ compilePolyfillAndValidate('''
+:root {
+ var-redef: #0f0;
+
+ var-a1: #fff;
+ var-a2: var(a1);
+ var-a3: var(a2);
+
+ var-redef: #000;
+}
+.test {
+ background-color: @a1;
+ color: @a2;
+ border-color: @a3;
+}
+.test-1 {
+ color: @redef;
+}''', r'''
+:root {
+}
+.test {
+ background-color: #fff;
+ color: #fff;
+ border-color: #fff;
+}
+.test-1 {
+ color: #000;
+}''');
+}
+
+void includes() {
+ var errors = <Message>[];
+ var file1Input = r'''
+:root {
+ var-redef: #0f0;
+
+ var-a1: #fff;
+ var-a2: var(a1);
+ var-a3: var(a2);
+
+ var-redef: #000;
+}
+.test-1 {
+ background-color: @a1;
+ color: @a2;
+ border-color: @a3;
+}
+.test-1a {
+ color: @redef;
+}
+''';
+
+ var file2Input = r'''
+:root {
+ var-redef: #0b0;
+ var-b3: var(a3);
+}
+.test-2 {
+ color: var(b3);
+ background-color: var(redef);
+ border-color: var(a3);
+}
+''';
+
+ var input = r'''
+:root {
+ var-redef: #0c0;
+}
+.test-main {
+ color: var(b3);
+ background-color: var(redef);
+ border-color: var(a3);
+}
+''';
+
+ var generated1 = r'''
+:root {
+ var-redef: #0f0;
+ var-a1: #fff;
+ var-a2: var(a1);
+ var-a3: var(a2);
+ var-redef: #000;
+}
+.test-1 {
+ background-color: var(a1);
+ color: var(a2);
+ border-color: var(a3);
+}
+.test-1a {
+ color: var(redef);
+}''';
+
+ var stylesheet1 = compileCss(file1Input, errors: errors, opts: options);
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet1), generated1);
+
+ var generated2 = r'''
+:root {
+ var-redef: #0b0;
+ var-b3: var(a3);
+}
+.test-2 {
+ color: var(b3);
+ background-color: var(redef);
+ border-color: var(a3);
+}''';
+
+ var stylesheet2 = compileCss(file2Input,
+ includes: [stylesheet1], errors: errors..clear(), opts: options);
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet2), generated2);
+
+ var generatedPolyfill1 = r'''
+:root {
+}
+.test-1 {
+ background-color: #fff;
+ color: #fff;
+ border-color: #fff;
+}
+.test-1a {
+ color: #000;
+}''';
+ var styleSheet1Polyfill = compileCss(file1Input,
+ errors: errors..clear(), polyfill: true, opts: options);
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(styleSheet1Polyfill), generatedPolyfill1);
+
+ var generatedPolyfill2 = r'''
+:root {
+}
+.test-2 {
+ color: #fff;
+ background-color: #0b0;
+ border-color: #fff;
+}''';
+ var styleSheet2Polyfill = compileCss(file2Input,
+ includes: [stylesheet1],
+ errors: errors..clear(),
+ polyfill: true,
+ opts: options);
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(styleSheet2Polyfill), generatedPolyfill2);
+
+ // Make sure includes didn't change.
+ expect(prettyPrint(stylesheet1), generated1);
+
+ var generatedPolyfill = r'''
+:root {
+}
+.test-main {
+ color: #fff;
+ background-color: #0c0;
+ border-color: #fff;
+}''';
+ var stylesheetPolyfill = compileCss(input,
+ includes: [stylesheet1, stylesheet2],
+ errors: errors..clear(),
+ polyfill: true,
+ opts: options);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheetPolyfill), generatedPolyfill);
+
+ // Make sure includes didn't change.
+ expect(prettyPrint(stylesheet1), generated1);
+ expect(prettyPrint(stylesheet2), generated2);
+}
+
+void main() {
+ glyph.ascii = true;
+ test('Simple var', simpleVar);
+ test('Expressions var', expressionsVar);
+ test('Default value in var()', defaultVar);
+ test('CSS Parser only var', parserVar);
+ test('Var syntax', testVar);
+ test('Indirects', testIndirects);
+ test('Forward Refs', undefinedVars);
+ test('Less syntax', testLess);
+ test('Polyfill', polyfill);
+ test('Multi-file', includes);
+}
diff --git a/pkgs/csslib/test/visitor_test.dart b/pkgs/csslib/test/visitor_test.dart
new file mode 100644
index 0000000..5442903
--- /dev/null
+++ b/pkgs/csslib/test/visitor_test.dart
@@ -0,0 +1,136 @@
+// 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:csslib/src/messages.dart';
+import 'package:csslib/visitor.dart';
+import 'package:test/test.dart';
+
+import 'testing.dart';
+
+class ClassVisitor extends Visitor {
+ final List<String> expectedClasses;
+ final foundClasses = <String>{};
+
+ ClassVisitor(this.expectedClasses);
+
+ @override
+ void visitClassSelector(ClassSelector node) {
+ foundClasses.add(node.name);
+ }
+
+ bool get matches {
+ var match = true;
+ for (var value in foundClasses) {
+ match = match && expectedClasses.contains(value);
+ }
+ for (var value in expectedClasses) {
+ match = match && foundClasses.contains(value);
+ }
+
+ return match;
+ }
+}
+
+void testClassVisitors() {
+ var errors = <Message>[];
+ var in1 = '.foobar { }';
+
+ var s = parseCss(in1, errors: errors);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+
+ var clsVisits = ClassVisitor(['foobar'])..visitTree(s);
+ expect(clsVisits.matches, true);
+
+ in1 = '''
+ .foobar1 { }
+ .xyzzy .foo #my-div { color: red; }
+ div.hello { font: arial; }
+ ''';
+
+ s = parseCss(in1, errors: errors..clear(), opts: simpleOptions);
+
+ expect(errors.isEmpty, true, reason: errors.toString());
+
+ clsVisits = ClassVisitor(['foobar1', 'xyzzy', 'foo', 'hello'])..visitTree(s);
+ expect(clsVisits.matches, true);
+
+ expect(prettyPrint(s), r'''
+.foobar1 {
+}
+.xyzzy .foo #my-div {
+ color: #f00;
+}
+div.hello {
+ font: arial;
+}''');
+}
+
+class PolyfillEmitter extends CssPrinter {
+ final String _prefix;
+
+ PolyfillEmitter(this._prefix);
+
+ @override
+ void visitClassSelector(ClassSelector node) {
+ emit('.${_prefix}_${node.name}');
+ }
+}
+
+String polyfillPrint(String prefix, StyleSheet ss) =>
+ (PolyfillEmitter(prefix)..visitTree(ss, pretty: true)).toString();
+
+void testPolyFill() {
+ var errors = <Message>[];
+ final input = r'''
+.foobar { }
+div.xyzzy { }
+#foo .foo .bar .foobar { }
+''';
+
+ final generated = r'''
+.myComponent_foobar {
+}
+div.myComponent_xyzzy {
+}
+#foo .myComponent_foo .myComponent_bar .myComponent_foobar {
+}''';
+
+ var s = parseCss(input, errors: errors);
+ expect(errors.isEmpty, true, reason: errors.toString());
+
+ final emitted = polyfillPrint('myComponent', s);
+ expect(emitted, generated);
+}
+
+void main() {
+ test('Class Visitors', testClassVisitors);
+ test('Polyfill', testPolyFill);
+ test('pretty-print', testPrettyPrint);
+}
+
+void testPrettyPrint() {
+ final input = '''
+.good { color: red; }@media screen { .better { color: blue; } }.best { color: green }''';
+
+ var styleSheet = parseCss(input);
+
+ // pretty print
+ expect(prettyPrint(styleSheet), '''
+.good {
+ color: #f00;
+}
+@media screen {
+ .better {
+ color: #00f;
+ }
+}
+.best {
+ color: #008000;
+}''');
+
+ // compact output
+ expect(compactOutput(styleSheet), '''
+.good{color:red}@media screen{.better{color:blue}}.best{color:green}''');
+}
diff --git a/pkgs/csslib/third_party/README.md b/pkgs/csslib/third_party/README.md
new file mode 100644
index 0000000..a68a963
--- /dev/null
+++ b/pkgs/csslib/third_party/README.md
@@ -0,0 +1,2 @@
+This folder contains resources from third parties.
+See each subfolder for details.
diff --git a/pkgs/csslib/third_party/base/README.md b/pkgs/csslib/third_party/base/README.md
new file mode 100644
index 0000000..3e2b261
--- /dev/null
+++ b/pkgs/csslib/third_party/base/README.md
@@ -0,0 +1,5 @@
+This folder contains a sample css file from the open-source project
+https://github.com/getbase/base.
+
+This code was included under the
+[MIT Open Source](https://opensource.org/licenses/MIT) license.
\ No newline at end of file
diff --git a/pkgs/csslib/third_party/base/index.css b/pkgs/csslib/third_party/base/index.css
new file mode 100644
index 0000000..95156ce
--- /dev/null
+++ b/pkgs/csslib/third_party/base/index.css
@@ -0,0 +1 @@
+*,:after,:before{-webkit-box-sizing:border-box;box-sizing:border-box}html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}.sr{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1rem}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}table{text-align:left;border-collapse:collapse;border-spacing:0;width:100%;margin:0;border:1px solid #e9e9e9}table td,table th{padding:1rem;border-bottom:1px solid #e9e9e9;border-right:1px solid #e9e9e9}table tr:nth-child(2n){background-color:#f6f8fa}body{font-family:sans-serif;font-size:16px;line-height:22px;color:#232323;font-weight:400;background:#fff}details,main{display:block}a{background-color:rgba(0,0,0,0)}.b,.strong,b,strong{font-weight:700}.em,em{font-style:italic}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}.small,small{font-size:.8125rem}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}[hidden],template{display:none}h1,h2,h3,h4,h5,h6{font-family:sans-serif;margin:.5rem 0}.h1,h1{font-size:32px;line-height:38px}.h2,h2{font-size:26px;line-height:32px}.h3,h3{font-size:22px;line-height:28px}.h4,h4{font-size:18px;line-height:24px}.h5,h5{font-size:16px;line-height:22px}.h6,h6{font-size:14px;line-height:20px}.container{max-width:1168px;padding-left:16px;padding-right:16px;margin-left:auto;margin-right:auto}@media only screen and (min-width:768px){.container-m{width:736px;max-width:auto;padding-left:16px;padding-right:16px;margin-left:auto;margin-right:auto}}@media only screen and (min-width:980px){.container-l{width:948px;max-width:auto;padding-left:16px;padding-right:16px;margin-left:auto;margin-right:auto}}@media only screen and (min-width:1200px){.container-xl{width:1168px;max-width:auto;padding-left:16px;padding-right:16px;margin-left:auto;margin-right:auto}}.row{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;position:relative;margin-left:-16px;margin-right:-16px}.col-1,.col-1-2,.col-1-3,.col-1-4,.col-1-5,.col-2,.col-2-3,.col-2-5,.col-3,.col-3-4,.col-3-5,.col-4,.col-4-5,.col-5,.col-6,.col-7,.col-8,.col-9,.col-10,.col-11,.col-12{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;padding-left:16px;padding-right:16px}.col-1{-ms-flex:0 0 8.33333%;flex:0 0 8.33333%;max-width:8.33333%}.col-1,.col-2{-webkit-box-flex:0}.col-2{-ms-flex:0 0 16.66667%;flex:0 0 16.66667%;max-width:16.66667%}.col-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-3,.col-4{-webkit-box-flex:0}.col-4{-ms-flex:0 0 33.33333%;flex:0 0 33.33333%;max-width:33.33333%}.col-5{-ms-flex:0 0 41.66667%;flex:0 0 41.66667%;max-width:41.66667%}.col-5,.col-6{-webkit-box-flex:0}.col-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-ms-flex:0 0 58.33333%;flex:0 0 58.33333%;max-width:58.33333%}.col-7,.col-8{-webkit-box-flex:0}.col-8{-ms-flex:0 0 66.66667%;flex:0 0 66.66667%;max-width:66.66667%}.col-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-9,.col-10{-webkit-box-flex:0}.col-10{-ms-flex:0 0 83.33333%;flex:0 0 83.33333%;max-width:83.33333%}.col-11{-ms-flex:0 0 91.66667%;flex:0 0 91.66667%;max-width:91.66667%}.col-11,.col-12{-webkit-box-flex:0}.col-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.col-1-2{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-1-2,.col-1-3{-webkit-box-flex:0}.col-1-3{-ms-flex:0 0 33.33333%;flex:0 0 33.33333%;max-width:33.33333%}.col-2-3{-ms-flex:0 0 66.66667%;flex:0 0 66.66667%;max-width:66.66667%}.col-1-4,.col-2-3{-webkit-box-flex:0}.col-1-4{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-3-4{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-1-5,.col-3-4{-webkit-box-flex:0}.col-1-5{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.col-2-5{-ms-flex:0 0 40%;flex:0 0 40%;max-width:40%}.col-2-5,.col-3-5{-webkit-box-flex:0}.col-3-5{-ms-flex:0 0 60%;flex:0 0 60%;max-width:60%}.col-4-5{-webkit-box-flex:0;-ms-flex:0 0 80%;flex:0 0 80%;max-width:80%}.col-full{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-preferred-size:0;flex-basis:0;max-width:100%}@media only screen and (min-width:768px){.row-m{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;position:relative;margin-left:-16px;margin-right:-16px}.col-1-2-m,.col-1-3-m,.col-1-4-m,.col-1-5-m,.col-1-m,.col-2-3-m,.col-2-5-m,.col-2-m,.col-3-4-m,.col-3-5-m,.col-3-m,.col-4-5-m,.col-4-m,.col-5-m,.col-6-m,.col-7-m,.col-8-m,.col-9-m,.col-10-m,.col-11-m,.col-12-m{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;padding-left:16px;padding-right:16px}.col-1-m{-ms-flex:0 0 8.33333%;flex:0 0 8.33333%;max-width:8.33333%}.col-1-m,.col-2-m{-webkit-box-flex:0}.col-2-m{-ms-flex:0 0 16.66667%;flex:0 0 16.66667%;max-width:16.66667%}.col-3-m{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-3-m,.col-4-m{-webkit-box-flex:0}.col-4-m{-ms-flex:0 0 33.33333%;flex:0 0 33.33333%;max-width:33.33333%}.col-5-m{-ms-flex:0 0 41.66667%;flex:0 0 41.66667%;max-width:41.66667%}.col-5-m,.col-6-m{-webkit-box-flex:0}.col-6-m{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7-m{-ms-flex:0 0 58.33333%;flex:0 0 58.33333%;max-width:58.33333%}.col-7-m,.col-8-m{-webkit-box-flex:0}.col-8-m{-ms-flex:0 0 66.66667%;flex:0 0 66.66667%;max-width:66.66667%}.col-9-m{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10-m{-webkit-box-flex:0;-ms-flex:0 0 83.33333%;flex:0 0 83.33333%;max-width:83.33333%}.col-11-m{-webkit-box-flex:0;-ms-flex:0 0 91.66667%;flex:0 0 91.66667%;max-width:91.66667%}.col-12-m{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.col-1-2-m{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-1-3-m{-webkit-box-flex:0;-ms-flex:0 0 33.33333%;flex:0 0 33.33333%;max-width:33.33333%}.col-2-3-m{-webkit-box-flex:0;-ms-flex:0 0 66.66667%;flex:0 0 66.66667%;max-width:66.66667%}.col-1-4-m{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-3-4-m{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-1-5-m{-webkit-box-flex:0;-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.col-2-5-m{-webkit-box-flex:0;-ms-flex:0 0 40%;flex:0 0 40%;max-width:40%}.col-3-5-m{-webkit-box-flex:0;-ms-flex:0 0 60%;flex:0 0 60%;max-width:60%}.col-4-5-m{-webkit-box-flex:0;-ms-flex:0 0 80%;flex:0 0 80%;max-width:80%}.col-full-m{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-preferred-size:0;flex-basis:0;max-width:100%}}@media only screen and (min-width:980px){.row-l{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;position:relative;margin-left:-16px;margin-right:-16px}.col-1-2-l,.col-1-3-l,.col-1-4-l,.col-1-5-l,.col-1-l,.col-2-3-l,.col-2-5-l,.col-2-l,.col-3-4-l,.col-3-5-l,.col-3-l,.col-4-5-l,.col-4-l,.col-5-l,.col-6-l,.col-7-l,.col-8-l,.col-9-l,.col-10-l,.col-11-l,.col-12-l{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;padding-left:16px;padding-right:16px}.col-1-l{-ms-flex:0 0 8.33333%;flex:0 0 8.33333%;max-width:8.33333%}.col-1-l,.col-2-l{-webkit-box-flex:0}.col-2-l{-ms-flex:0 0 16.66667%;flex:0 0 16.66667%;max-width:16.66667%}.col-3-l{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-3-l,.col-4-l{-webkit-box-flex:0}.col-4-l{-ms-flex:0 0 33.33333%;flex:0 0 33.33333%;max-width:33.33333%}.col-5-l{-ms-flex:0 0 41.66667%;flex:0 0 41.66667%;max-width:41.66667%}.col-5-l,.col-6-l{-webkit-box-flex:0}.col-6-l{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7-l{-ms-flex:0 0 58.33333%;flex:0 0 58.33333%;max-width:58.33333%}.col-7-l,.col-8-l{-webkit-box-flex:0}.col-8-l{-ms-flex:0 0 66.66667%;flex:0 0 66.66667%;max-width:66.66667%}.col-9-l{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10-l{-webkit-box-flex:0;-ms-flex:0 0 83.33333%;flex:0 0 83.33333%;max-width:83.33333%}.col-11-l{-webkit-box-flex:0;-ms-flex:0 0 91.66667%;flex:0 0 91.66667%;max-width:91.66667%}.col-12-l{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.col-1-2-l{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-1-3-l{-webkit-box-flex:0;-ms-flex:0 0 33.33333%;flex:0 0 33.33333%;max-width:33.33333%}.col-2-3-l{-webkit-box-flex:0;-ms-flex:0 0 66.66667%;flex:0 0 66.66667%;max-width:66.66667%}.col-1-4-l{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-3-4-l{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-1-5-l{-webkit-box-flex:0;-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.col-2-5-l{-webkit-box-flex:0;-ms-flex:0 0 40%;flex:0 0 40%;max-width:40%}.col-3-5-l{-webkit-box-flex:0;-ms-flex:0 0 60%;flex:0 0 60%;max-width:60%}.col-4-5-l{-webkit-box-flex:0;-ms-flex:0 0 80%;flex:0 0 80%;max-width:80%}.col-full-l{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-preferred-size:0;flex-basis:0;max-width:100%}}@media only screen and (min-width:1200px){.row-xl{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;position:relative;margin-left:-16px;margin-right:-16px}.col-1-2-xl,.col-1-3-xl,.col-1-4-xl,.col-1-5-xl,.col-1-xl,.col-2-3-xl,.col-2-5-xl,.col-2-xl,.col-3-4-xl,.col-3-5-xl,.col-3-xl,.col-4-5-xl,.col-4-xl,.col-5-xl,.col-6-xl,.col-7-xl,.col-8-xl,.col-9-xl,.col-10-xl,.col-11-xl,.col-12-xl{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;padding-left:16px;padding-right:16px}.col-1-xl{-webkit-box-flex:0;-ms-flex:0 0 8.33333%;flex:0 0 8.33333%;max-width:8.33333%}.col-2-xl{-webkit-box-flex:0;-ms-flex:0 0 16.66667%;flex:0 0 16.66667%;max-width:16.66667%}.col-3-xl{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4-xl{-webkit-box-flex:0;-ms-flex:0 0 33.33333%;flex:0 0 33.33333%;max-width:33.33333%}.col-5-xl{-webkit-box-flex:0;-ms-flex:0 0 41.66667%;flex:0 0 41.66667%;max-width:41.66667%}.col-6-xl{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7-xl{-webkit-box-flex:0;-ms-flex:0 0 58.33333%;flex:0 0 58.33333%;max-width:58.33333%}.col-8-xl{-webkit-box-flex:0;-ms-flex:0 0 66.66667%;flex:0 0 66.66667%;max-width:66.66667%}.col-9-xl{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10-xl{-webkit-box-flex:0;-ms-flex:0 0 83.33333%;flex:0 0 83.33333%;max-width:83.33333%}.col-11-xl{-webkit-box-flex:0;-ms-flex:0 0 91.66667%;flex:0 0 91.66667%;max-width:91.66667%}.col-12-xl{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.col-1-2-xl{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-1-3-xl{-webkit-box-flex:0;-ms-flex:0 0 33.33333%;flex:0 0 33.33333%;max-width:33.33333%}.col-2-3-xl{-webkit-box-flex:0;-ms-flex:0 0 66.66667%;flex:0 0 66.66667%;max-width:66.66667%}.col-1-4-xl{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-3-4-xl{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-1-5-xl{-webkit-box-flex:0;-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.col-2-5-xl{-webkit-box-flex:0;-ms-flex:0 0 40%;flex:0 0 40%;max-width:40%}.col-3-5-xl{-webkit-box-flex:0;-ms-flex:0 0 60%;flex:0 0 60%;max-width:60%}.col-4-5-xl{-webkit-box-flex:0;-ms-flex:0 0 80%;flex:0 0 80%;max-width:80%}.col-full-xl{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-preferred-size:0;flex-basis:0;max-width:100%}}.padding-left-0-25rem{padding-left:.25rem}.padding-left-0-5rem{padding-left:.5rem}.padding-left-1rem{padding-left:1rem}.padding-left-1-5rem{padding-left:1.5rem}.padding-left-2rem{padding-left:2rem}.padding-left-3rem{padding-left:3rem}.padding-left-4rem{padding-left:4rem}.padding-left-5rem{padding-left:5rem}.padding-right-0-25rem{padding-right:.25rem}.padding-right-0-5rem{padding-right:.5rem}.padding-right-1rem{padding-right:1rem}.padding-right-1-5rem{padding-right:1.5rem}.padding-right-2rem{padding-right:2rem}.padding-right-3rem{padding-right:3rem}.padding-right-4rem{padding-right:4rem}.padding-right-5rem{padding-right:5rem}@media only screen and (min-width:768px){.padding-left-0-m{padding-left:0}.padding-left-0-25rem-m{padding-left:.25rem}.padding-left-0-5rem-m{padding-left:.5rem}.padding-left-1rem-m{padding-left:1rem}.padding-left-1-5rem-m{padding-left:1.5rem}.padding-left-2rem-m{padding-left:2rem}.padding-left-3rem-m{padding-left:3rem}.padding-left-4rem-m{padding-left:4rem}.padding-left-5rem-m{padding-left:5rem}.padding-right-0-m{padding-right:0}.padding-right-0-25rem-m{padding-right:.25rem}.padding-right-0-5rem-m{padding-right:.5rem}.padding-right-1rem-m{padding-right:1rem}.padding-right-1-5rem-m{padding-right:1.5rem}.padding-right-2rem-m{padding-right:2rem}.padding-right-3rem-m{padding-right:3rem}.padding-right-4rem-m{padding-right:4rem}.padding-right-5rem-m{padding-right:5rem}}@media only screen and (min-width:980px){.padding-left-0-l{padding-left:0}.padding-left-0-25rem-l{padding-left:.25rem}.padding-left-0-5rem-l{padding-left:.5rem}.padding-left-1rem-l{padding-left:1rem}.padding-left-1-5rem-l{padding-left:1.5rem}.padding-left-2rem-l{padding-left:2rem}.padding-left-3rem-l{padding-left:3rem}.padding-left-4rem-l{padding-left:4rem}.padding-left-5rem-l{padding-left:5rem}.padding-right-0-l{padding-right:0}.padding-right-0-25rem-l{padding-right:.25rem}.padding-right-0-5rem-l{padding-right:.5rem}.padding-right-1rem-l{padding-right:1rem}.padding-right-1-5rem-l{padding-right:1.5rem}.padding-right-2rem-l{padding-right:2rem}.padding-right-3rem-l{padding-right:3rem}.padding-right-4rem-l{padding-right:4rem}.padding-right-5rem-l{padding-right:5rem}}@media only screen and (min-width:1200px){.padding-left-0-xl{padding-left:0}.padding-left-0-25rem-xl{padding-left:.25rem}.padding-left-0-5rem-xl{padding-left:.5rem}.padding-left-1rem-xl{padding-left:1rem}.padding-left-1-5rem-xl{padding-left:1.5rem}.padding-left-2rem-xl{padding-left:2rem}.padding-left-3rem-xl{padding-left:3rem}.padding-left-4rem-xl{padding-left:4rem}.padding-left-5rem-xl{padding-left:5rem}.padding-right-0-xl{padding-right:0}.padding-right-0-25rem-xl{padding-right:.25rem}.padding-right-0-5rem-xl{padding-right:.5rem}.padding-right-1rem-xl{padding-right:1rem}.padding-right-1-5rem-xl{padding-right:1.5rem}.padding-right-2rem-xl{padding-right:2rem}.padding-right-3rem-xl{padding-right:3rem}.padding-right-4rem-xl{padding-right:4rem}.padding-right-5rem-xl{padding-right:5rem}}.padding-top-0-25rem{padding-top:.25rem}.padding-top-0-5rem{padding-top:.5rem}.padding-top-1rem{padding-top:1rem}.padding-top-1-5rem{padding-top:1.5rem}.padding-top-2rem{padding-top:2rem}.padding-top-3rem{padding-top:3rem}.padding-top-4rem{padding-top:4rem}.padding-top-5rem{padding-top:5rem}.padding-bottom-0-25rem{padding-bottom:.25rem}.padding-bottom-0-5rem{padding-bottom:.5rem}.padding-bottom-1rem{padding-bottom:1rem}.padding-bottom-1-5rem{padding-bottom:1.5rem}.padding-bottom-2rem{padding-bottom:2rem}.padding-bottom-3rem{padding-bottom:3rem}.padding-bottom-4rem{padding-bottom:4rem}.padding-bottom-5rem{padding-bottom:5rem}@media only screen and (min-width:768px){.padding-top-0-m{padding-top:0}.padding-top-0-25rem-m{padding-top:.25rem}.padding-top-0-5rem-m{padding-top:.5rem}.padding-top-1rem-m{padding-top:1rem}.padding-top-1-5rem-m{padding-top:1.5rem}.padding-top-2rem-m{padding-top:2rem}.padding-top-3rem-m{padding-top:3rem}.padding-top-4rem-m{padding-top:4rem}.padding-top-5rem-m{padding-top:5rem}.padding-bottom-0-m{padding-bottom:0}.padding-bottom-0-25rem-m{padding-bottom:.25rem}.padding-bottom-0-5rem-m{padding-bottom:.5rem}.padding-bottom-1rem-m{padding-bottom:1rem}.padding-bottom-1-5rem-m{padding-bottom:1.5rem}.padding-bottom-2rem-m{padding-bottom:2rem}.padding-bottom-3rem-m{padding-bottom:3rem}.padding-bottom-4rem-m{padding-bottom:4rem}.padding-bottom-5rem-m{padding-bottom:5rem}}@media only screen and (min-width:980px){.padding-top-0-l{padding-top:0}.padding-top-0-25rem-l{padding-top:.25rem}.padding-top-0-5rem-l{padding-top:.5rem}.padding-top-1rem-l{padding-top:1rem}.padding-top-1-5rem-l{padding-top:1.5rem}.padding-top-2rem-l{padding-top:2rem}.padding-top-3rem-l{padding-top:3rem}.padding-top-4rem-l{padding-top:4rem}.padding-top-5rem-l{padding-top:5rem}.padding-bottom-0-l{padding-bottom:0}.padding-bottom-0-25rem-l{padding-bottom:.25rem}.padding-bottom-0-5rem-l{padding-bottom:.5rem}.padding-bottom-1rem-l{padding-bottom:1rem}.padding-bottom-1-5rem-l{padding-bottom:1.5rem}.padding-bottom-2rem-l{padding-bottom:2rem}.padding-bottom-3rem-l{padding-bottom:3rem}.padding-bottom-4rem-l{padding-bottom:4rem}.padding-bottom-5rem-l{padding-bottom:5rem}}@media only screen and (min-width:1200px){.padding-top-0-xl{padding-top:0}.padding-top-0-25rem-xl{padding-top:.25rem}.padding-top-0-5rem-xl{padding-top:.5rem}.padding-top-1rem-xl{padding-top:1rem}.padding-top-1-5rem-xl{padding-top:1.5rem}.padding-top-2rem-xl{padding-top:2rem}.padding-top-3rem-xl{padding-top:3rem}.padding-top-4rem-xl{padding-top:4rem}.padding-top-5rem-xl{padding-top:5rem}.padding-bottom-0-xl{padding-bottom:0}.padding-bottom-0-25rem-xl{padding-bottom:.25rem}.padding-bottom-0-5rem-xl{padding-bottom:.5rem}.padding-bottom-1rem-xl{padding-bottom:1rem}.padding-bottom-1-5rem-xl{padding-bottom:1.5rem}.padding-bottom-2rem-xl{padding-bottom:2rem}.padding-bottom-3rem-xl{padding-bottom:3rem}.padding-bottom-4rem-xl{padding-bottom:4rem}.padding-bottom-5rem-xl{padding-bottom:5rem}}.padding-0{padding:0}.padding-0-25rem{padding:.25rem}.padding-0-5rem{padding:.5rem}.padding-1rem{padding:1rem}.padding-1-5rem{padding:1.5rem}.padding-2rem{padding:2rem}.padding-3rem{padding:3rem}.padding-4rem{padding:4rem}.padding-5rem{padding:5rem}@media only screen and (min-width:768px){.padding-0-m{padding:0}.padding-0-25rem-m{padding:.25rem}.padding-0-5rem-m{padding:.5rem}.padding-1rem-m{padding:1rem}.padding-1-5rem-m{padding:1.5rem}.padding-2rem-m{padding:2rem}.padding-3rem-m{padding:3rem}.padding-4rem-m{padding:4rem}.padding-5rem-m{padding:5rem}}@media only screen and (min-width:980px){.padding-0-l{padding:0}.padding-0-25rem-l{padding:.25rem}.padding-0-5rem-l{padding:.5rem}.padding-1rem-l{padding:1rem}.padding-1-5rem-l{padding:1.5rem}.padding-2rem-l{padding:2rem}.padding-3rem-l{padding:3rem}.padding-4rem-l{padding:4rem}.padding-5rem-l{padding:5rem}}@media only screen and (min-width:1200px){.padding-0-xl{padding:0}.padding-0-25rem-xl{padding:.25rem}.padding-0-5rem-xl{padding:.5rem}.padding-1rem-xl{padding:1rem}.padding-1-5rem-xl{padding:1.5rem}.padding-2rem-xl{padding:2rem}.padding-3rem-xl{padding:3rem}.padding-4rem-xl{padding:4rem}.padding-5rem-xl{padding:5rem}}.none{display:none}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:-webkit-box;display:-ms-flexbox;display:flex}@media only screen and (min-width:768px){.none-m{display:none}.block-m{display:block}.inline-block-m{display:inline-block}.inline-m{display:inline}.flex-m{display:-webkit-box;display:-ms-flexbox;display:flex}}@media only screen and (min-width:980px){.none-l{display:none}.block-l{display:block}.inline-block-l{display:inline-block}.inline-l{display:inline}.flex-l{display:-webkit-box;display:-ms-flexbox;display:flex}}@media only screen and (min-width:1200px){.block-xl,.none-xl{display:block}.inline-block-xl{display:inline-block}.inline-xl{display:inline}.flex-xl{display:-webkit-box;display:-ms-flexbox;display:flex}}.flex-wrap{-ms-flex-wrap:wrap;flex-wrap:wrap}.flex-nowrap{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.flex-align-items-bottom,.flex-align-items-center,.flex-align-items-top{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.flex-justify-left{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.flex-justify-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.flex-justify-right{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.flex-space-around{-ms-flex-pack:distribute;justify-content:space-around}.flex-space-between{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.flex-row{-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.flex-row,.flex-row-reverse{-webkit-box-orient:horizontal}.flex-row-reverse{-webkit-box-direction:reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}.flex-column{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.flex-column-reverse{-webkit-box-orient:vertical;-webkit-box-direction:reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}@media only screen and (min-width:768px){.flex-wrap-m{-ms-flex-wrap:wrap;flex-wrap:wrap}.flex-nowrap-m{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.flex-align-items-bottom-m,.flex-align-items-center-m,.flex-align-items-top-m{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.flex-justify-left-m{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.flex-justify-center-m{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.flex-justify-right-m{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.flex-space-around-m{-ms-flex-pack:distribute;justify-content:space-around}.flex-space-between-m{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.flex-row-m{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.flex-row-reverse-m{-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}.flex-column-m{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.flex-column-reverse-m{-webkit-box-orient:vertical;-webkit-box-direction:reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}}@media only screen and (min-width:980px){.flex-wrap-l{-ms-flex-wrap:wrap;flex-wrap:wrap}.flex-nowrap-l{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.flex-align-items-bottom-l,.flex-align-items-center-l,.flex-align-items-top-l{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.flex-justify-left-l{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.flex-justify-center-l{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.flex-justify-right-l{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.flex-space-around-l{-ms-flex-pack:distribute;justify-content:space-around}.flex-space-between-l{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.flex-row-l{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.flex-row-reverse-l{-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}.flex-column-l{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.flex-column-reverse-l{-webkit-box-orient:vertical;-webkit-box-direction:reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}}@media only screen and (min-width:1200px){.flex-wrap-xl{-ms-flex-wrap:wrap;flex-wrap:wrap}.flex-nowrap-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.flex-align-items-bottom-xl,.flex-align-items-center-xl,.flex-align-items-top-xl{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.flex-justify-left-xl{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.flex-justify-center-xl{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.flex-justify-right-xl{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.flex-space-around-xl{-ms-flex-pack:distribute;justify-content:space-around}.flex-space-between-xl{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.flex-row-xl{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.flex-row-reverse-xl{-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}.flex-column-xl{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.flex-column-reverse-xl{-webkit-box-orient:vertical;-webkit-box-direction:reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}}.relative{position:relative}.absolute{position:absolute}.static{position:static}.fixed{position:fixed}.sticky{position:-webkit-sticky;position:sticky}@media only screen and (min-width:768px){.relative-m{position:relative}.absolute-m{position:absolute}.static-m{position:static}.fixed-m{position:fixed}.sticky-m{position:-webkit-sticky;position:sticky}}@media only screen and (min-width:980px){.relative-l{position:relative}.absolute-l{position:absolute}.static-l{position:static}.fixed-l{position:fixed}.sticky-l{position:-webkit-sticky;position:sticky}}@media only screen and (min-width:1200px){.relative-xl{position:relative}.absolute-xl{position:absolute}.static-xl{position:static}.fixed-xl{position:fixed}.sticky-xl{position:-webkit-sticky;position:sticky}}.no-select{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:default}.font-100{font-weight:100}.font-200{font-weight:200}.font-300{font-weight:300}.font-400{font-weight:400}.font-500{font-weight:500}.font-600{font-weight:600}.font-700{font-weight:700}.font-800{font-weight:800}.font-900{font-weight:900}.uppercase{text-transform:uppercase}.lowercase{text-transform:lowercase}.capitalize{text-transform:capitalize}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}@media only screen and (min-width:768px){.text-left-m{text-align:left}.text-right-m{text-align:right}.text-center-m{text-align:center}}@media only screen and (min-width:980px){.text-left-l{text-align:left}.text-right-l{text-align:right}.text-center-l{text-align:center}}@media only screen and (min-width:1200px){.text-left-xl{text-align:left}.text-right-xl{text-align:right}.text-center-xl{text-align:center}}
\ No newline at end of file
diff --git a/pkgs/csslib/third_party/bootstrap/LICENSE b/pkgs/csslib/third_party/bootstrap/LICENSE
new file mode 100644
index 0000000..72dda23
--- /dev/null
+++ b/pkgs/csslib/third_party/bootstrap/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2011-2021 Twitter, Inc.
+Copyright (c) 2011-2021 The Bootstrap Authors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/pkgs/csslib/third_party/bootstrap/README.md b/pkgs/csslib/third_party/bootstrap/README.md
new file mode 100644
index 0000000..bb1d3bb
--- /dev/null
+++ b/pkgs/csslib/third_party/bootstrap/README.md
@@ -0,0 +1,4 @@
+This folder contains sample css files from the open-source project
+https://github.com/twbs/bootstrap.
+
+This code was included under the terms in the `LICENSE` file.
\ No newline at end of file
diff --git a/pkgs/csslib/third_party/bootstrap/bootstrap-grid.css b/pkgs/csslib/third_party/bootstrap/bootstrap-grid.css
new file mode 100644
index 0000000..e6af9ab
--- /dev/null
+++ b/pkgs/csslib/third_party/bootstrap/bootstrap-grid.css
@@ -0,0 +1,5051 @@
+/*!
+ * Bootstrap Grid v5.1.1 (https://getbootstrap.com/)
+ * Copyright 2011-2021 The Bootstrap Authors
+ * Copyright 2011-2021 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
+ */
+:root {
+ --bs-blue: #0d6efd;
+ --bs-indigo: #6610f2;
+ --bs-purple: #6f42c1;
+ --bs-pink: #d63384;
+ --bs-red: #dc3545;
+ --bs-orange: #fd7e14;
+ --bs-yellow: #ffc107;
+ --bs-green: #198754;
+ --bs-teal: #20c997;
+ --bs-cyan: #0dcaf0;
+ --bs-white: #fff;
+ --bs-gray: #6c757d;
+ --bs-gray-dark: #343a40;
+ --bs-gray-100: #f8f9fa;
+ --bs-gray-200: #e9ecef;
+ --bs-gray-300: #dee2e6;
+ --bs-gray-400: #ced4da;
+ --bs-gray-500: #adb5bd;
+ --bs-gray-600: #6c757d;
+ --bs-gray-700: #495057;
+ --bs-gray-800: #343a40;
+ --bs-gray-900: #212529;
+ --bs-primary: #0d6efd;
+ --bs-secondary: #6c757d;
+ --bs-success: #198754;
+ --bs-info: #0dcaf0;
+ --bs-warning: #ffc107;
+ --bs-danger: #dc3545;
+ --bs-light: #f8f9fa;
+ --bs-dark: #212529;
+ --bs-primary-rgb: 13, 110, 253;
+ --bs-secondary-rgb: 108, 117, 125;
+ --bs-success-rgb: 25, 135, 84;
+ --bs-info-rgb: 13, 202, 240;
+ --bs-warning-rgb: 255, 193, 7;
+ --bs-danger-rgb: 220, 53, 69;
+ --bs-light-rgb: 248, 249, 250;
+ --bs-dark-rgb: 33, 37, 41;
+ --bs-white-rgb: 255, 255, 255;
+ --bs-black-rgb: 0, 0, 0;
+ --bs-body-color-rgb: 33, 37, 41;
+ --bs-body-bg-rgb: 255, 255, 255;
+ --bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+ --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+ --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
+ --bs-body-font-family: var(--bs-font-sans-serif);
+ --bs-body-font-size: 1rem;
+ --bs-body-font-weight: 400;
+ --bs-body-line-height: 1.5;
+ --bs-body-color: #212529;
+ --bs-body-bg: #fff;
+}
+
+.container,
+.container-fluid,
+.container-xxl,
+.container-xl,
+.container-lg,
+.container-md,
+.container-sm {
+ width: 100%;
+ padding-right: var(--bs-gutter-x, 0.75rem);
+ padding-left: var(--bs-gutter-x, 0.75rem);
+ margin-right: auto;
+ margin-left: auto;
+}
+
+@media (min-width: 576px) {
+ .container-sm, .container {
+ max-width: 540px;
+ }
+}
+@media (min-width: 768px) {
+ .container-md, .container-sm, .container {
+ max-width: 720px;
+ }
+}
+@media (min-width: 992px) {
+ .container-lg, .container-md, .container-sm, .container {
+ max-width: 960px;
+ }
+}
+@media (min-width: 1200px) {
+ .container-xl, .container-lg, .container-md, .container-sm, .container {
+ max-width: 1140px;
+ }
+}
+@media (min-width: 1400px) {
+ .container-xxl, .container-xl, .container-lg, .container-md, .container-sm, .container {
+ max-width: 1320px;
+ }
+}
+.row {
+ --bs-gutter-x: 1.5rem;
+ --bs-gutter-y: 0;
+ display: flex;
+ flex-wrap: wrap;
+ margin-top: calc(var(--bs-gutter-y) * -1);
+ margin-right: calc(var(--bs-gutter-x) * -.5);
+ margin-left: calc(var(--bs-gutter-x) * -.5);
+}
+.row > * {
+ box-sizing: border-box;
+ flex-shrink: 0;
+ width: 100%;
+ max-width: 100%;
+ padding-right: calc(var(--bs-gutter-x) * .5);
+ padding-left: calc(var(--bs-gutter-x) * .5);
+ margin-top: var(--bs-gutter-y);
+}
+
+.col {
+ flex: 1 0 0%;
+}
+
+.row-cols-auto > * {
+ flex: 0 0 auto;
+ width: auto;
+}
+
+.row-cols-1 > * {
+ flex: 0 0 auto;
+ width: 100%;
+}
+
+.row-cols-2 > * {
+ flex: 0 0 auto;
+ width: 50%;
+}
+
+.row-cols-3 > * {
+ flex: 0 0 auto;
+ width: 33.3333333333%;
+}
+
+.row-cols-4 > * {
+ flex: 0 0 auto;
+ width: 25%;
+}
+
+.row-cols-5 > * {
+ flex: 0 0 auto;
+ width: 20%;
+}
+
+.row-cols-6 > * {
+ flex: 0 0 auto;
+ width: 16.6666666667%;
+}
+
+.col-auto {
+ flex: 0 0 auto;
+ width: auto;
+}
+
+.col-1 {
+ flex: 0 0 auto;
+ width: 8.33333333%;
+}
+
+.col-2 {
+ flex: 0 0 auto;
+ width: 16.66666667%;
+}
+
+.col-3 {
+ flex: 0 0 auto;
+ width: 25%;
+}
+
+.col-4 {
+ flex: 0 0 auto;
+ width: 33.33333333%;
+}
+
+.col-5 {
+ flex: 0 0 auto;
+ width: 41.66666667%;
+}
+
+.col-6 {
+ flex: 0 0 auto;
+ width: 50%;
+}
+
+.col-7 {
+ flex: 0 0 auto;
+ width: 58.33333333%;
+}
+
+.col-8 {
+ flex: 0 0 auto;
+ width: 66.66666667%;
+}
+
+.col-9 {
+ flex: 0 0 auto;
+ width: 75%;
+}
+
+.col-10 {
+ flex: 0 0 auto;
+ width: 83.33333333%;
+}
+
+.col-11 {
+ flex: 0 0 auto;
+ width: 91.66666667%;
+}
+
+.col-12 {
+ flex: 0 0 auto;
+ width: 100%;
+}
+
+.offset-1 {
+ margin-left: 8.33333333%;
+}
+
+.offset-2 {
+ margin-left: 16.66666667%;
+}
+
+.offset-3 {
+ margin-left: 25%;
+}
+
+.offset-4 {
+ margin-left: 33.33333333%;
+}
+
+.offset-5 {
+ margin-left: 41.66666667%;
+}
+
+.offset-6 {
+ margin-left: 50%;
+}
+
+.offset-7 {
+ margin-left: 58.33333333%;
+}
+
+.offset-8 {
+ margin-left: 66.66666667%;
+}
+
+.offset-9 {
+ margin-left: 75%;
+}
+
+.offset-10 {
+ margin-left: 83.33333333%;
+}
+
+.offset-11 {
+ margin-left: 91.66666667%;
+}
+
+.g-0,
+.gx-0 {
+ --bs-gutter-x: 0;
+}
+
+.g-0,
+.gy-0 {
+ --bs-gutter-y: 0;
+}
+
+.g-1,
+.gx-1 {
+ --bs-gutter-x: 0.25rem;
+}
+
+.g-1,
+.gy-1 {
+ --bs-gutter-y: 0.25rem;
+}
+
+.g-2,
+.gx-2 {
+ --bs-gutter-x: 0.5rem;
+}
+
+.g-2,
+.gy-2 {
+ --bs-gutter-y: 0.5rem;
+}
+
+.g-3,
+.gx-3 {
+ --bs-gutter-x: 1rem;
+}
+
+.g-3,
+.gy-3 {
+ --bs-gutter-y: 1rem;
+}
+
+.g-4,
+.gx-4 {
+ --bs-gutter-x: 1.5rem;
+}
+
+.g-4,
+.gy-4 {
+ --bs-gutter-y: 1.5rem;
+}
+
+.g-5,
+.gx-5 {
+ --bs-gutter-x: 3rem;
+}
+
+.g-5,
+.gy-5 {
+ --bs-gutter-y: 3rem;
+}
+
+@media (min-width: 576px) {
+ .col-sm {
+ flex: 1 0 0%;
+ }
+
+ .row-cols-sm-auto > * {
+ flex: 0 0 auto;
+ width: auto;
+ }
+
+ .row-cols-sm-1 > * {
+ flex: 0 0 auto;
+ width: 100%;
+ }
+
+ .row-cols-sm-2 > * {
+ flex: 0 0 auto;
+ width: 50%;
+ }
+
+ .row-cols-sm-3 > * {
+ flex: 0 0 auto;
+ width: 33.3333333333%;
+ }
+
+ .row-cols-sm-4 > * {
+ flex: 0 0 auto;
+ width: 25%;
+ }
+
+ .row-cols-sm-5 > * {
+ flex: 0 0 auto;
+ width: 20%;
+ }
+
+ .row-cols-sm-6 > * {
+ flex: 0 0 auto;
+ width: 16.6666666667%;
+ }
+
+ .col-sm-auto {
+ flex: 0 0 auto;
+ width: auto;
+ }
+
+ .col-sm-1 {
+ flex: 0 0 auto;
+ width: 8.33333333%;
+ }
+
+ .col-sm-2 {
+ flex: 0 0 auto;
+ width: 16.66666667%;
+ }
+
+ .col-sm-3 {
+ flex: 0 0 auto;
+ width: 25%;
+ }
+
+ .col-sm-4 {
+ flex: 0 0 auto;
+ width: 33.33333333%;
+ }
+
+ .col-sm-5 {
+ flex: 0 0 auto;
+ width: 41.66666667%;
+ }
+
+ .col-sm-6 {
+ flex: 0 0 auto;
+ width: 50%;
+ }
+
+ .col-sm-7 {
+ flex: 0 0 auto;
+ width: 58.33333333%;
+ }
+
+ .col-sm-8 {
+ flex: 0 0 auto;
+ width: 66.66666667%;
+ }
+
+ .col-sm-9 {
+ flex: 0 0 auto;
+ width: 75%;
+ }
+
+ .col-sm-10 {
+ flex: 0 0 auto;
+ width: 83.33333333%;
+ }
+
+ .col-sm-11 {
+ flex: 0 0 auto;
+ width: 91.66666667%;
+ }
+
+ .col-sm-12 {
+ flex: 0 0 auto;
+ width: 100%;
+ }
+
+ .offset-sm-0 {
+ margin-left: 0;
+ }
+
+ .offset-sm-1 {
+ margin-left: 8.33333333%;
+ }
+
+ .offset-sm-2 {
+ margin-left: 16.66666667%;
+ }
+
+ .offset-sm-3 {
+ margin-left: 25%;
+ }
+
+ .offset-sm-4 {
+ margin-left: 33.33333333%;
+ }
+
+ .offset-sm-5 {
+ margin-left: 41.66666667%;
+ }
+
+ .offset-sm-6 {
+ margin-left: 50%;
+ }
+
+ .offset-sm-7 {
+ margin-left: 58.33333333%;
+ }
+
+ .offset-sm-8 {
+ margin-left: 66.66666667%;
+ }
+
+ .offset-sm-9 {
+ margin-left: 75%;
+ }
+
+ .offset-sm-10 {
+ margin-left: 83.33333333%;
+ }
+
+ .offset-sm-11 {
+ margin-left: 91.66666667%;
+ }
+
+ .g-sm-0,
+.gx-sm-0 {
+ --bs-gutter-x: 0;
+ }
+
+ .g-sm-0,
+.gy-sm-0 {
+ --bs-gutter-y: 0;
+ }
+
+ .g-sm-1,
+.gx-sm-1 {
+ --bs-gutter-x: 0.25rem;
+ }
+
+ .g-sm-1,
+.gy-sm-1 {
+ --bs-gutter-y: 0.25rem;
+ }
+
+ .g-sm-2,
+.gx-sm-2 {
+ --bs-gutter-x: 0.5rem;
+ }
+
+ .g-sm-2,
+.gy-sm-2 {
+ --bs-gutter-y: 0.5rem;
+ }
+
+ .g-sm-3,
+.gx-sm-3 {
+ --bs-gutter-x: 1rem;
+ }
+
+ .g-sm-3,
+.gy-sm-3 {
+ --bs-gutter-y: 1rem;
+ }
+
+ .g-sm-4,
+.gx-sm-4 {
+ --bs-gutter-x: 1.5rem;
+ }
+
+ .g-sm-4,
+.gy-sm-4 {
+ --bs-gutter-y: 1.5rem;
+ }
+
+ .g-sm-5,
+.gx-sm-5 {
+ --bs-gutter-x: 3rem;
+ }
+
+ .g-sm-5,
+.gy-sm-5 {
+ --bs-gutter-y: 3rem;
+ }
+}
+@media (min-width: 768px) {
+ .col-md {
+ flex: 1 0 0%;
+ }
+
+ .row-cols-md-auto > * {
+ flex: 0 0 auto;
+ width: auto;
+ }
+
+ .row-cols-md-1 > * {
+ flex: 0 0 auto;
+ width: 100%;
+ }
+
+ .row-cols-md-2 > * {
+ flex: 0 0 auto;
+ width: 50%;
+ }
+
+ .row-cols-md-3 > * {
+ flex: 0 0 auto;
+ width: 33.3333333333%;
+ }
+
+ .row-cols-md-4 > * {
+ flex: 0 0 auto;
+ width: 25%;
+ }
+
+ .row-cols-md-5 > * {
+ flex: 0 0 auto;
+ width: 20%;
+ }
+
+ .row-cols-md-6 > * {
+ flex: 0 0 auto;
+ width: 16.6666666667%;
+ }
+
+ .col-md-auto {
+ flex: 0 0 auto;
+ width: auto;
+ }
+
+ .col-md-1 {
+ flex: 0 0 auto;
+ width: 8.33333333%;
+ }
+
+ .col-md-2 {
+ flex: 0 0 auto;
+ width: 16.66666667%;
+ }
+
+ .col-md-3 {
+ flex: 0 0 auto;
+ width: 25%;
+ }
+
+ .col-md-4 {
+ flex: 0 0 auto;
+ width: 33.33333333%;
+ }
+
+ .col-md-5 {
+ flex: 0 0 auto;
+ width: 41.66666667%;
+ }
+
+ .col-md-6 {
+ flex: 0 0 auto;
+ width: 50%;
+ }
+
+ .col-md-7 {
+ flex: 0 0 auto;
+ width: 58.33333333%;
+ }
+
+ .col-md-8 {
+ flex: 0 0 auto;
+ width: 66.66666667%;
+ }
+
+ .col-md-9 {
+ flex: 0 0 auto;
+ width: 75%;
+ }
+
+ .col-md-10 {
+ flex: 0 0 auto;
+ width: 83.33333333%;
+ }
+
+ .col-md-11 {
+ flex: 0 0 auto;
+ width: 91.66666667%;
+ }
+
+ .col-md-12 {
+ flex: 0 0 auto;
+ width: 100%;
+ }
+
+ .offset-md-0 {
+ margin-left: 0;
+ }
+
+ .offset-md-1 {
+ margin-left: 8.33333333%;
+ }
+
+ .offset-md-2 {
+ margin-left: 16.66666667%;
+ }
+
+ .offset-md-3 {
+ margin-left: 25%;
+ }
+
+ .offset-md-4 {
+ margin-left: 33.33333333%;
+ }
+
+ .offset-md-5 {
+ margin-left: 41.66666667%;
+ }
+
+ .offset-md-6 {
+ margin-left: 50%;
+ }
+
+ .offset-md-7 {
+ margin-left: 58.33333333%;
+ }
+
+ .offset-md-8 {
+ margin-left: 66.66666667%;
+ }
+
+ .offset-md-9 {
+ margin-left: 75%;
+ }
+
+ .offset-md-10 {
+ margin-left: 83.33333333%;
+ }
+
+ .offset-md-11 {
+ margin-left: 91.66666667%;
+ }
+
+ .g-md-0,
+.gx-md-0 {
+ --bs-gutter-x: 0;
+ }
+
+ .g-md-0,
+.gy-md-0 {
+ --bs-gutter-y: 0;
+ }
+
+ .g-md-1,
+.gx-md-1 {
+ --bs-gutter-x: 0.25rem;
+ }
+
+ .g-md-1,
+.gy-md-1 {
+ --bs-gutter-y: 0.25rem;
+ }
+
+ .g-md-2,
+.gx-md-2 {
+ --bs-gutter-x: 0.5rem;
+ }
+
+ .g-md-2,
+.gy-md-2 {
+ --bs-gutter-y: 0.5rem;
+ }
+
+ .g-md-3,
+.gx-md-3 {
+ --bs-gutter-x: 1rem;
+ }
+
+ .g-md-3,
+.gy-md-3 {
+ --bs-gutter-y: 1rem;
+ }
+
+ .g-md-4,
+.gx-md-4 {
+ --bs-gutter-x: 1.5rem;
+ }
+
+ .g-md-4,
+.gy-md-4 {
+ --bs-gutter-y: 1.5rem;
+ }
+
+ .g-md-5,
+.gx-md-5 {
+ --bs-gutter-x: 3rem;
+ }
+
+ .g-md-5,
+.gy-md-5 {
+ --bs-gutter-y: 3rem;
+ }
+}
+@media (min-width: 992px) {
+ .col-lg {
+ flex: 1 0 0%;
+ }
+
+ .row-cols-lg-auto > * {
+ flex: 0 0 auto;
+ width: auto;
+ }
+
+ .row-cols-lg-1 > * {
+ flex: 0 0 auto;
+ width: 100%;
+ }
+
+ .row-cols-lg-2 > * {
+ flex: 0 0 auto;
+ width: 50%;
+ }
+
+ .row-cols-lg-3 > * {
+ flex: 0 0 auto;
+ width: 33.3333333333%;
+ }
+
+ .row-cols-lg-4 > * {
+ flex: 0 0 auto;
+ width: 25%;
+ }
+
+ .row-cols-lg-5 > * {
+ flex: 0 0 auto;
+ width: 20%;
+ }
+
+ .row-cols-lg-6 > * {
+ flex: 0 0 auto;
+ width: 16.6666666667%;
+ }
+
+ .col-lg-auto {
+ flex: 0 0 auto;
+ width: auto;
+ }
+
+ .col-lg-1 {
+ flex: 0 0 auto;
+ width: 8.33333333%;
+ }
+
+ .col-lg-2 {
+ flex: 0 0 auto;
+ width: 16.66666667%;
+ }
+
+ .col-lg-3 {
+ flex: 0 0 auto;
+ width: 25%;
+ }
+
+ .col-lg-4 {
+ flex: 0 0 auto;
+ width: 33.33333333%;
+ }
+
+ .col-lg-5 {
+ flex: 0 0 auto;
+ width: 41.66666667%;
+ }
+
+ .col-lg-6 {
+ flex: 0 0 auto;
+ width: 50%;
+ }
+
+ .col-lg-7 {
+ flex: 0 0 auto;
+ width: 58.33333333%;
+ }
+
+ .col-lg-8 {
+ flex: 0 0 auto;
+ width: 66.66666667%;
+ }
+
+ .col-lg-9 {
+ flex: 0 0 auto;
+ width: 75%;
+ }
+
+ .col-lg-10 {
+ flex: 0 0 auto;
+ width: 83.33333333%;
+ }
+
+ .col-lg-11 {
+ flex: 0 0 auto;
+ width: 91.66666667%;
+ }
+
+ .col-lg-12 {
+ flex: 0 0 auto;
+ width: 100%;
+ }
+
+ .offset-lg-0 {
+ margin-left: 0;
+ }
+
+ .offset-lg-1 {
+ margin-left: 8.33333333%;
+ }
+
+ .offset-lg-2 {
+ margin-left: 16.66666667%;
+ }
+
+ .offset-lg-3 {
+ margin-left: 25%;
+ }
+
+ .offset-lg-4 {
+ margin-left: 33.33333333%;
+ }
+
+ .offset-lg-5 {
+ margin-left: 41.66666667%;
+ }
+
+ .offset-lg-6 {
+ margin-left: 50%;
+ }
+
+ .offset-lg-7 {
+ margin-left: 58.33333333%;
+ }
+
+ .offset-lg-8 {
+ margin-left: 66.66666667%;
+ }
+
+ .offset-lg-9 {
+ margin-left: 75%;
+ }
+
+ .offset-lg-10 {
+ margin-left: 83.33333333%;
+ }
+
+ .offset-lg-11 {
+ margin-left: 91.66666667%;
+ }
+
+ .g-lg-0,
+.gx-lg-0 {
+ --bs-gutter-x: 0;
+ }
+
+ .g-lg-0,
+.gy-lg-0 {
+ --bs-gutter-y: 0;
+ }
+
+ .g-lg-1,
+.gx-lg-1 {
+ --bs-gutter-x: 0.25rem;
+ }
+
+ .g-lg-1,
+.gy-lg-1 {
+ --bs-gutter-y: 0.25rem;
+ }
+
+ .g-lg-2,
+.gx-lg-2 {
+ --bs-gutter-x: 0.5rem;
+ }
+
+ .g-lg-2,
+.gy-lg-2 {
+ --bs-gutter-y: 0.5rem;
+ }
+
+ .g-lg-3,
+.gx-lg-3 {
+ --bs-gutter-x: 1rem;
+ }
+
+ .g-lg-3,
+.gy-lg-3 {
+ --bs-gutter-y: 1rem;
+ }
+
+ .g-lg-4,
+.gx-lg-4 {
+ --bs-gutter-x: 1.5rem;
+ }
+
+ .g-lg-4,
+.gy-lg-4 {
+ --bs-gutter-y: 1.5rem;
+ }
+
+ .g-lg-5,
+.gx-lg-5 {
+ --bs-gutter-x: 3rem;
+ }
+
+ .g-lg-5,
+.gy-lg-5 {
+ --bs-gutter-y: 3rem;
+ }
+}
+@media (min-width: 1200px) {
+ .col-xl {
+ flex: 1 0 0%;
+ }
+
+ .row-cols-xl-auto > * {
+ flex: 0 0 auto;
+ width: auto;
+ }
+
+ .row-cols-xl-1 > * {
+ flex: 0 0 auto;
+ width: 100%;
+ }
+
+ .row-cols-xl-2 > * {
+ flex: 0 0 auto;
+ width: 50%;
+ }
+
+ .row-cols-xl-3 > * {
+ flex: 0 0 auto;
+ width: 33.3333333333%;
+ }
+
+ .row-cols-xl-4 > * {
+ flex: 0 0 auto;
+ width: 25%;
+ }
+
+ .row-cols-xl-5 > * {
+ flex: 0 0 auto;
+ width: 20%;
+ }
+
+ .row-cols-xl-6 > * {
+ flex: 0 0 auto;
+ width: 16.6666666667%;
+ }
+
+ .col-xl-auto {
+ flex: 0 0 auto;
+ width: auto;
+ }
+
+ .col-xl-1 {
+ flex: 0 0 auto;
+ width: 8.33333333%;
+ }
+
+ .col-xl-2 {
+ flex: 0 0 auto;
+ width: 16.66666667%;
+ }
+
+ .col-xl-3 {
+ flex: 0 0 auto;
+ width: 25%;
+ }
+
+ .col-xl-4 {
+ flex: 0 0 auto;
+ width: 33.33333333%;
+ }
+
+ .col-xl-5 {
+ flex: 0 0 auto;
+ width: 41.66666667%;
+ }
+
+ .col-xl-6 {
+ flex: 0 0 auto;
+ width: 50%;
+ }
+
+ .col-xl-7 {
+ flex: 0 0 auto;
+ width: 58.33333333%;
+ }
+
+ .col-xl-8 {
+ flex: 0 0 auto;
+ width: 66.66666667%;
+ }
+
+ .col-xl-9 {
+ flex: 0 0 auto;
+ width: 75%;
+ }
+
+ .col-xl-10 {
+ flex: 0 0 auto;
+ width: 83.33333333%;
+ }
+
+ .col-xl-11 {
+ flex: 0 0 auto;
+ width: 91.66666667%;
+ }
+
+ .col-xl-12 {
+ flex: 0 0 auto;
+ width: 100%;
+ }
+
+ .offset-xl-0 {
+ margin-left: 0;
+ }
+
+ .offset-xl-1 {
+ margin-left: 8.33333333%;
+ }
+
+ .offset-xl-2 {
+ margin-left: 16.66666667%;
+ }
+
+ .offset-xl-3 {
+ margin-left: 25%;
+ }
+
+ .offset-xl-4 {
+ margin-left: 33.33333333%;
+ }
+
+ .offset-xl-5 {
+ margin-left: 41.66666667%;
+ }
+
+ .offset-xl-6 {
+ margin-left: 50%;
+ }
+
+ .offset-xl-7 {
+ margin-left: 58.33333333%;
+ }
+
+ .offset-xl-8 {
+ margin-left: 66.66666667%;
+ }
+
+ .offset-xl-9 {
+ margin-left: 75%;
+ }
+
+ .offset-xl-10 {
+ margin-left: 83.33333333%;
+ }
+
+ .offset-xl-11 {
+ margin-left: 91.66666667%;
+ }
+
+ .g-xl-0,
+.gx-xl-0 {
+ --bs-gutter-x: 0;
+ }
+
+ .g-xl-0,
+.gy-xl-0 {
+ --bs-gutter-y: 0;
+ }
+
+ .g-xl-1,
+.gx-xl-1 {
+ --bs-gutter-x: 0.25rem;
+ }
+
+ .g-xl-1,
+.gy-xl-1 {
+ --bs-gutter-y: 0.25rem;
+ }
+
+ .g-xl-2,
+.gx-xl-2 {
+ --bs-gutter-x: 0.5rem;
+ }
+
+ .g-xl-2,
+.gy-xl-2 {
+ --bs-gutter-y: 0.5rem;
+ }
+
+ .g-xl-3,
+.gx-xl-3 {
+ --bs-gutter-x: 1rem;
+ }
+
+ .g-xl-3,
+.gy-xl-3 {
+ --bs-gutter-y: 1rem;
+ }
+
+ .g-xl-4,
+.gx-xl-4 {
+ --bs-gutter-x: 1.5rem;
+ }
+
+ .g-xl-4,
+.gy-xl-4 {
+ --bs-gutter-y: 1.5rem;
+ }
+
+ .g-xl-5,
+.gx-xl-5 {
+ --bs-gutter-x: 3rem;
+ }
+
+ .g-xl-5,
+.gy-xl-5 {
+ --bs-gutter-y: 3rem;
+ }
+}
+@media (min-width: 1400px) {
+ .col-xxl {
+ flex: 1 0 0%;
+ }
+
+ .row-cols-xxl-auto > * {
+ flex: 0 0 auto;
+ width: auto;
+ }
+
+ .row-cols-xxl-1 > * {
+ flex: 0 0 auto;
+ width: 100%;
+ }
+
+ .row-cols-xxl-2 > * {
+ flex: 0 0 auto;
+ width: 50%;
+ }
+
+ .row-cols-xxl-3 > * {
+ flex: 0 0 auto;
+ width: 33.3333333333%;
+ }
+
+ .row-cols-xxl-4 > * {
+ flex: 0 0 auto;
+ width: 25%;
+ }
+
+ .row-cols-xxl-5 > * {
+ flex: 0 0 auto;
+ width: 20%;
+ }
+
+ .row-cols-xxl-6 > * {
+ flex: 0 0 auto;
+ width: 16.6666666667%;
+ }
+
+ .col-xxl-auto {
+ flex: 0 0 auto;
+ width: auto;
+ }
+
+ .col-xxl-1 {
+ flex: 0 0 auto;
+ width: 8.33333333%;
+ }
+
+ .col-xxl-2 {
+ flex: 0 0 auto;
+ width: 16.66666667%;
+ }
+
+ .col-xxl-3 {
+ flex: 0 0 auto;
+ width: 25%;
+ }
+
+ .col-xxl-4 {
+ flex: 0 0 auto;
+ width: 33.33333333%;
+ }
+
+ .col-xxl-5 {
+ flex: 0 0 auto;
+ width: 41.66666667%;
+ }
+
+ .col-xxl-6 {
+ flex: 0 0 auto;
+ width: 50%;
+ }
+
+ .col-xxl-7 {
+ flex: 0 0 auto;
+ width: 58.33333333%;
+ }
+
+ .col-xxl-8 {
+ flex: 0 0 auto;
+ width: 66.66666667%;
+ }
+
+ .col-xxl-9 {
+ flex: 0 0 auto;
+ width: 75%;
+ }
+
+ .col-xxl-10 {
+ flex: 0 0 auto;
+ width: 83.33333333%;
+ }
+
+ .col-xxl-11 {
+ flex: 0 0 auto;
+ width: 91.66666667%;
+ }
+
+ .col-xxl-12 {
+ flex: 0 0 auto;
+ width: 100%;
+ }
+
+ .offset-xxl-0 {
+ margin-left: 0;
+ }
+
+ .offset-xxl-1 {
+ margin-left: 8.33333333%;
+ }
+
+ .offset-xxl-2 {
+ margin-left: 16.66666667%;
+ }
+
+ .offset-xxl-3 {
+ margin-left: 25%;
+ }
+
+ .offset-xxl-4 {
+ margin-left: 33.33333333%;
+ }
+
+ .offset-xxl-5 {
+ margin-left: 41.66666667%;
+ }
+
+ .offset-xxl-6 {
+ margin-left: 50%;
+ }
+
+ .offset-xxl-7 {
+ margin-left: 58.33333333%;
+ }
+
+ .offset-xxl-8 {
+ margin-left: 66.66666667%;
+ }
+
+ .offset-xxl-9 {
+ margin-left: 75%;
+ }
+
+ .offset-xxl-10 {
+ margin-left: 83.33333333%;
+ }
+
+ .offset-xxl-11 {
+ margin-left: 91.66666667%;
+ }
+
+ .g-xxl-0,
+.gx-xxl-0 {
+ --bs-gutter-x: 0;
+ }
+
+ .g-xxl-0,
+.gy-xxl-0 {
+ --bs-gutter-y: 0;
+ }
+
+ .g-xxl-1,
+.gx-xxl-1 {
+ --bs-gutter-x: 0.25rem;
+ }
+
+ .g-xxl-1,
+.gy-xxl-1 {
+ --bs-gutter-y: 0.25rem;
+ }
+
+ .g-xxl-2,
+.gx-xxl-2 {
+ --bs-gutter-x: 0.5rem;
+ }
+
+ .g-xxl-2,
+.gy-xxl-2 {
+ --bs-gutter-y: 0.5rem;
+ }
+
+ .g-xxl-3,
+.gx-xxl-3 {
+ --bs-gutter-x: 1rem;
+ }
+
+ .g-xxl-3,
+.gy-xxl-3 {
+ --bs-gutter-y: 1rem;
+ }
+
+ .g-xxl-4,
+.gx-xxl-4 {
+ --bs-gutter-x: 1.5rem;
+ }
+
+ .g-xxl-4,
+.gy-xxl-4 {
+ --bs-gutter-y: 1.5rem;
+ }
+
+ .g-xxl-5,
+.gx-xxl-5 {
+ --bs-gutter-x: 3rem;
+ }
+
+ .g-xxl-5,
+.gy-xxl-5 {
+ --bs-gutter-y: 3rem;
+ }
+}
+.d-inline {
+ display: inline !important;
+}
+
+.d-inline-block {
+ display: inline-block !important;
+}
+
+.d-block {
+ display: block !important;
+}
+
+.d-grid {
+ display: grid !important;
+}
+
+.d-table {
+ display: table !important;
+}
+
+.d-table-row {
+ display: table-row !important;
+}
+
+.d-table-cell {
+ display: table-cell !important;
+}
+
+.d-flex {
+ display: flex !important;
+}
+
+.d-inline-flex {
+ display: inline-flex !important;
+}
+
+.d-none {
+ display: none !important;
+}
+
+.flex-fill {
+ flex: 1 1 auto !important;
+}
+
+.flex-row {
+ flex-direction: row !important;
+}
+
+.flex-column {
+ flex-direction: column !important;
+}
+
+.flex-row-reverse {
+ flex-direction: row-reverse !important;
+}
+
+.flex-column-reverse {
+ flex-direction: column-reverse !important;
+}
+
+.flex-grow-0 {
+ flex-grow: 0 !important;
+}
+
+.flex-grow-1 {
+ flex-grow: 1 !important;
+}
+
+.flex-shrink-0 {
+ flex-shrink: 0 !important;
+}
+
+.flex-shrink-1 {
+ flex-shrink: 1 !important;
+}
+
+.flex-wrap {
+ flex-wrap: wrap !important;
+}
+
+.flex-nowrap {
+ flex-wrap: nowrap !important;
+}
+
+.flex-wrap-reverse {
+ flex-wrap: wrap-reverse !important;
+}
+
+.justify-content-start {
+ justify-content: flex-start !important;
+}
+
+.justify-content-end {
+ justify-content: flex-end !important;
+}
+
+.justify-content-center {
+ justify-content: center !important;
+}
+
+.justify-content-between {
+ justify-content: space-between !important;
+}
+
+.justify-content-around {
+ justify-content: space-around !important;
+}
+
+.justify-content-evenly {
+ justify-content: space-evenly !important;
+}
+
+.align-items-start {
+ align-items: flex-start !important;
+}
+
+.align-items-end {
+ align-items: flex-end !important;
+}
+
+.align-items-center {
+ align-items: center !important;
+}
+
+.align-items-baseline {
+ align-items: baseline !important;
+}
+
+.align-items-stretch {
+ align-items: stretch !important;
+}
+
+.align-content-start {
+ align-content: flex-start !important;
+}
+
+.align-content-end {
+ align-content: flex-end !important;
+}
+
+.align-content-center {
+ align-content: center !important;
+}
+
+.align-content-between {
+ align-content: space-between !important;
+}
+
+.align-content-around {
+ align-content: space-around !important;
+}
+
+.align-content-stretch {
+ align-content: stretch !important;
+}
+
+.align-self-auto {
+ align-self: auto !important;
+}
+
+.align-self-start {
+ align-self: flex-start !important;
+}
+
+.align-self-end {
+ align-self: flex-end !important;
+}
+
+.align-self-center {
+ align-self: center !important;
+}
+
+.align-self-baseline {
+ align-self: baseline !important;
+}
+
+.align-self-stretch {
+ align-self: stretch !important;
+}
+
+.order-first {
+ order: -1 !important;
+}
+
+.order-0 {
+ order: 0 !important;
+}
+
+.order-1 {
+ order: 1 !important;
+}
+
+.order-2 {
+ order: 2 !important;
+}
+
+.order-3 {
+ order: 3 !important;
+}
+
+.order-4 {
+ order: 4 !important;
+}
+
+.order-5 {
+ order: 5 !important;
+}
+
+.order-last {
+ order: 6 !important;
+}
+
+.m-0 {
+ margin: 0 !important;
+}
+
+.m-1 {
+ margin: 0.25rem !important;
+}
+
+.m-2 {
+ margin: 0.5rem !important;
+}
+
+.m-3 {
+ margin: 1rem !important;
+}
+
+.m-4 {
+ margin: 1.5rem !important;
+}
+
+.m-5 {
+ margin: 3rem !important;
+}
+
+.m-auto {
+ margin: auto !important;
+}
+
+.mx-0 {
+ margin-right: 0 !important;
+ margin-left: 0 !important;
+}
+
+.mx-1 {
+ margin-right: 0.25rem !important;
+ margin-left: 0.25rem !important;
+}
+
+.mx-2 {
+ margin-right: 0.5rem !important;
+ margin-left: 0.5rem !important;
+}
+
+.mx-3 {
+ margin-right: 1rem !important;
+ margin-left: 1rem !important;
+}
+
+.mx-4 {
+ margin-right: 1.5rem !important;
+ margin-left: 1.5rem !important;
+}
+
+.mx-5 {
+ margin-right: 3rem !important;
+ margin-left: 3rem !important;
+}
+
+.mx-auto {
+ margin-right: auto !important;
+ margin-left: auto !important;
+}
+
+.my-0 {
+ margin-top: 0 !important;
+ margin-bottom: 0 !important;
+}
+
+.my-1 {
+ margin-top: 0.25rem !important;
+ margin-bottom: 0.25rem !important;
+}
+
+.my-2 {
+ margin-top: 0.5rem !important;
+ margin-bottom: 0.5rem !important;
+}
+
+.my-3 {
+ margin-top: 1rem !important;
+ margin-bottom: 1rem !important;
+}
+
+.my-4 {
+ margin-top: 1.5rem !important;
+ margin-bottom: 1.5rem !important;
+}
+
+.my-5 {
+ margin-top: 3rem !important;
+ margin-bottom: 3rem !important;
+}
+
+.my-auto {
+ margin-top: auto !important;
+ margin-bottom: auto !important;
+}
+
+.mt-0 {
+ margin-top: 0 !important;
+}
+
+.mt-1 {
+ margin-top: 0.25rem !important;
+}
+
+.mt-2 {
+ margin-top: 0.5rem !important;
+}
+
+.mt-3 {
+ margin-top: 1rem !important;
+}
+
+.mt-4 {
+ margin-top: 1.5rem !important;
+}
+
+.mt-5 {
+ margin-top: 3rem !important;
+}
+
+.mt-auto {
+ margin-top: auto !important;
+}
+
+.me-0 {
+ margin-right: 0 !important;
+}
+
+.me-1 {
+ margin-right: 0.25rem !important;
+}
+
+.me-2 {
+ margin-right: 0.5rem !important;
+}
+
+.me-3 {
+ margin-right: 1rem !important;
+}
+
+.me-4 {
+ margin-right: 1.5rem !important;
+}
+
+.me-5 {
+ margin-right: 3rem !important;
+}
+
+.me-auto {
+ margin-right: auto !important;
+}
+
+.mb-0 {
+ margin-bottom: 0 !important;
+}
+
+.mb-1 {
+ margin-bottom: 0.25rem !important;
+}
+
+.mb-2 {
+ margin-bottom: 0.5rem !important;
+}
+
+.mb-3 {
+ margin-bottom: 1rem !important;
+}
+
+.mb-4 {
+ margin-bottom: 1.5rem !important;
+}
+
+.mb-5 {
+ margin-bottom: 3rem !important;
+}
+
+.mb-auto {
+ margin-bottom: auto !important;
+}
+
+.ms-0 {
+ margin-left: 0 !important;
+}
+
+.ms-1 {
+ margin-left: 0.25rem !important;
+}
+
+.ms-2 {
+ margin-left: 0.5rem !important;
+}
+
+.ms-3 {
+ margin-left: 1rem !important;
+}
+
+.ms-4 {
+ margin-left: 1.5rem !important;
+}
+
+.ms-5 {
+ margin-left: 3rem !important;
+}
+
+.ms-auto {
+ margin-left: auto !important;
+}
+
+.p-0 {
+ padding: 0 !important;
+}
+
+.p-1 {
+ padding: 0.25rem !important;
+}
+
+.p-2 {
+ padding: 0.5rem !important;
+}
+
+.p-3 {
+ padding: 1rem !important;
+}
+
+.p-4 {
+ padding: 1.5rem !important;
+}
+
+.p-5 {
+ padding: 3rem !important;
+}
+
+.px-0 {
+ padding-right: 0 !important;
+ padding-left: 0 !important;
+}
+
+.px-1 {
+ padding-right: 0.25rem !important;
+ padding-left: 0.25rem !important;
+}
+
+.px-2 {
+ padding-right: 0.5rem !important;
+ padding-left: 0.5rem !important;
+}
+
+.px-3 {
+ padding-right: 1rem !important;
+ padding-left: 1rem !important;
+}
+
+.px-4 {
+ padding-right: 1.5rem !important;
+ padding-left: 1.5rem !important;
+}
+
+.px-5 {
+ padding-right: 3rem !important;
+ padding-left: 3rem !important;
+}
+
+.py-0 {
+ padding-top: 0 !important;
+ padding-bottom: 0 !important;
+}
+
+.py-1 {
+ padding-top: 0.25rem !important;
+ padding-bottom: 0.25rem !important;
+}
+
+.py-2 {
+ padding-top: 0.5rem !important;
+ padding-bottom: 0.5rem !important;
+}
+
+.py-3 {
+ padding-top: 1rem !important;
+ padding-bottom: 1rem !important;
+}
+
+.py-4 {
+ padding-top: 1.5rem !important;
+ padding-bottom: 1.5rem !important;
+}
+
+.py-5 {
+ padding-top: 3rem !important;
+ padding-bottom: 3rem !important;
+}
+
+.pt-0 {
+ padding-top: 0 !important;
+}
+
+.pt-1 {
+ padding-top: 0.25rem !important;
+}
+
+.pt-2 {
+ padding-top: 0.5rem !important;
+}
+
+.pt-3 {
+ padding-top: 1rem !important;
+}
+
+.pt-4 {
+ padding-top: 1.5rem !important;
+}
+
+.pt-5 {
+ padding-top: 3rem !important;
+}
+
+.pe-0 {
+ padding-right: 0 !important;
+}
+
+.pe-1 {
+ padding-right: 0.25rem !important;
+}
+
+.pe-2 {
+ padding-right: 0.5rem !important;
+}
+
+.pe-3 {
+ padding-right: 1rem !important;
+}
+
+.pe-4 {
+ padding-right: 1.5rem !important;
+}
+
+.pe-5 {
+ padding-right: 3rem !important;
+}
+
+.pb-0 {
+ padding-bottom: 0 !important;
+}
+
+.pb-1 {
+ padding-bottom: 0.25rem !important;
+}
+
+.pb-2 {
+ padding-bottom: 0.5rem !important;
+}
+
+.pb-3 {
+ padding-bottom: 1rem !important;
+}
+
+.pb-4 {
+ padding-bottom: 1.5rem !important;
+}
+
+.pb-5 {
+ padding-bottom: 3rem !important;
+}
+
+.ps-0 {
+ padding-left: 0 !important;
+}
+
+.ps-1 {
+ padding-left: 0.25rem !important;
+}
+
+.ps-2 {
+ padding-left: 0.5rem !important;
+}
+
+.ps-3 {
+ padding-left: 1rem !important;
+}
+
+.ps-4 {
+ padding-left: 1.5rem !important;
+}
+
+.ps-5 {
+ padding-left: 3rem !important;
+}
+
+@media (min-width: 576px) {
+ .d-sm-inline {
+ display: inline !important;
+ }
+
+ .d-sm-inline-block {
+ display: inline-block !important;
+ }
+
+ .d-sm-block {
+ display: block !important;
+ }
+
+ .d-sm-grid {
+ display: grid !important;
+ }
+
+ .d-sm-table {
+ display: table !important;
+ }
+
+ .d-sm-table-row {
+ display: table-row !important;
+ }
+
+ .d-sm-table-cell {
+ display: table-cell !important;
+ }
+
+ .d-sm-flex {
+ display: flex !important;
+ }
+
+ .d-sm-inline-flex {
+ display: inline-flex !important;
+ }
+
+ .d-sm-none {
+ display: none !important;
+ }
+
+ .flex-sm-fill {
+ flex: 1 1 auto !important;
+ }
+
+ .flex-sm-row {
+ flex-direction: row !important;
+ }
+
+ .flex-sm-column {
+ flex-direction: column !important;
+ }
+
+ .flex-sm-row-reverse {
+ flex-direction: row-reverse !important;
+ }
+
+ .flex-sm-column-reverse {
+ flex-direction: column-reverse !important;
+ }
+
+ .flex-sm-grow-0 {
+ flex-grow: 0 !important;
+ }
+
+ .flex-sm-grow-1 {
+ flex-grow: 1 !important;
+ }
+
+ .flex-sm-shrink-0 {
+ flex-shrink: 0 !important;
+ }
+
+ .flex-sm-shrink-1 {
+ flex-shrink: 1 !important;
+ }
+
+ .flex-sm-wrap {
+ flex-wrap: wrap !important;
+ }
+
+ .flex-sm-nowrap {
+ flex-wrap: nowrap !important;
+ }
+
+ .flex-sm-wrap-reverse {
+ flex-wrap: wrap-reverse !important;
+ }
+
+ .justify-content-sm-start {
+ justify-content: flex-start !important;
+ }
+
+ .justify-content-sm-end {
+ justify-content: flex-end !important;
+ }
+
+ .justify-content-sm-center {
+ justify-content: center !important;
+ }
+
+ .justify-content-sm-between {
+ justify-content: space-between !important;
+ }
+
+ .justify-content-sm-around {
+ justify-content: space-around !important;
+ }
+
+ .justify-content-sm-evenly {
+ justify-content: space-evenly !important;
+ }
+
+ .align-items-sm-start {
+ align-items: flex-start !important;
+ }
+
+ .align-items-sm-end {
+ align-items: flex-end !important;
+ }
+
+ .align-items-sm-center {
+ align-items: center !important;
+ }
+
+ .align-items-sm-baseline {
+ align-items: baseline !important;
+ }
+
+ .align-items-sm-stretch {
+ align-items: stretch !important;
+ }
+
+ .align-content-sm-start {
+ align-content: flex-start !important;
+ }
+
+ .align-content-sm-end {
+ align-content: flex-end !important;
+ }
+
+ .align-content-sm-center {
+ align-content: center !important;
+ }
+
+ .align-content-sm-between {
+ align-content: space-between !important;
+ }
+
+ .align-content-sm-around {
+ align-content: space-around !important;
+ }
+
+ .align-content-sm-stretch {
+ align-content: stretch !important;
+ }
+
+ .align-self-sm-auto {
+ align-self: auto !important;
+ }
+
+ .align-self-sm-start {
+ align-self: flex-start !important;
+ }
+
+ .align-self-sm-end {
+ align-self: flex-end !important;
+ }
+
+ .align-self-sm-center {
+ align-self: center !important;
+ }
+
+ .align-self-sm-baseline {
+ align-self: baseline !important;
+ }
+
+ .align-self-sm-stretch {
+ align-self: stretch !important;
+ }
+
+ .order-sm-first {
+ order: -1 !important;
+ }
+
+ .order-sm-0 {
+ order: 0 !important;
+ }
+
+ .order-sm-1 {
+ order: 1 !important;
+ }
+
+ .order-sm-2 {
+ order: 2 !important;
+ }
+
+ .order-sm-3 {
+ order: 3 !important;
+ }
+
+ .order-sm-4 {
+ order: 4 !important;
+ }
+
+ .order-sm-5 {
+ order: 5 !important;
+ }
+
+ .order-sm-last {
+ order: 6 !important;
+ }
+
+ .m-sm-0 {
+ margin: 0 !important;
+ }
+
+ .m-sm-1 {
+ margin: 0.25rem !important;
+ }
+
+ .m-sm-2 {
+ margin: 0.5rem !important;
+ }
+
+ .m-sm-3 {
+ margin: 1rem !important;
+ }
+
+ .m-sm-4 {
+ margin: 1.5rem !important;
+ }
+
+ .m-sm-5 {
+ margin: 3rem !important;
+ }
+
+ .m-sm-auto {
+ margin: auto !important;
+ }
+
+ .mx-sm-0 {
+ margin-right: 0 !important;
+ margin-left: 0 !important;
+ }
+
+ .mx-sm-1 {
+ margin-right: 0.25rem !important;
+ margin-left: 0.25rem !important;
+ }
+
+ .mx-sm-2 {
+ margin-right: 0.5rem !important;
+ margin-left: 0.5rem !important;
+ }
+
+ .mx-sm-3 {
+ margin-right: 1rem !important;
+ margin-left: 1rem !important;
+ }
+
+ .mx-sm-4 {
+ margin-right: 1.5rem !important;
+ margin-left: 1.5rem !important;
+ }
+
+ .mx-sm-5 {
+ margin-right: 3rem !important;
+ margin-left: 3rem !important;
+ }
+
+ .mx-sm-auto {
+ margin-right: auto !important;
+ margin-left: auto !important;
+ }
+
+ .my-sm-0 {
+ margin-top: 0 !important;
+ margin-bottom: 0 !important;
+ }
+
+ .my-sm-1 {
+ margin-top: 0.25rem !important;
+ margin-bottom: 0.25rem !important;
+ }
+
+ .my-sm-2 {
+ margin-top: 0.5rem !important;
+ margin-bottom: 0.5rem !important;
+ }
+
+ .my-sm-3 {
+ margin-top: 1rem !important;
+ margin-bottom: 1rem !important;
+ }
+
+ .my-sm-4 {
+ margin-top: 1.5rem !important;
+ margin-bottom: 1.5rem !important;
+ }
+
+ .my-sm-5 {
+ margin-top: 3rem !important;
+ margin-bottom: 3rem !important;
+ }
+
+ .my-sm-auto {
+ margin-top: auto !important;
+ margin-bottom: auto !important;
+ }
+
+ .mt-sm-0 {
+ margin-top: 0 !important;
+ }
+
+ .mt-sm-1 {
+ margin-top: 0.25rem !important;
+ }
+
+ .mt-sm-2 {
+ margin-top: 0.5rem !important;
+ }
+
+ .mt-sm-3 {
+ margin-top: 1rem !important;
+ }
+
+ .mt-sm-4 {
+ margin-top: 1.5rem !important;
+ }
+
+ .mt-sm-5 {
+ margin-top: 3rem !important;
+ }
+
+ .mt-sm-auto {
+ margin-top: auto !important;
+ }
+
+ .me-sm-0 {
+ margin-right: 0 !important;
+ }
+
+ .me-sm-1 {
+ margin-right: 0.25rem !important;
+ }
+
+ .me-sm-2 {
+ margin-right: 0.5rem !important;
+ }
+
+ .me-sm-3 {
+ margin-right: 1rem !important;
+ }
+
+ .me-sm-4 {
+ margin-right: 1.5rem !important;
+ }
+
+ .me-sm-5 {
+ margin-right: 3rem !important;
+ }
+
+ .me-sm-auto {
+ margin-right: auto !important;
+ }
+
+ .mb-sm-0 {
+ margin-bottom: 0 !important;
+ }
+
+ .mb-sm-1 {
+ margin-bottom: 0.25rem !important;
+ }
+
+ .mb-sm-2 {
+ margin-bottom: 0.5rem !important;
+ }
+
+ .mb-sm-3 {
+ margin-bottom: 1rem !important;
+ }
+
+ .mb-sm-4 {
+ margin-bottom: 1.5rem !important;
+ }
+
+ .mb-sm-5 {
+ margin-bottom: 3rem !important;
+ }
+
+ .mb-sm-auto {
+ margin-bottom: auto !important;
+ }
+
+ .ms-sm-0 {
+ margin-left: 0 !important;
+ }
+
+ .ms-sm-1 {
+ margin-left: 0.25rem !important;
+ }
+
+ .ms-sm-2 {
+ margin-left: 0.5rem !important;
+ }
+
+ .ms-sm-3 {
+ margin-left: 1rem !important;
+ }
+
+ .ms-sm-4 {
+ margin-left: 1.5rem !important;
+ }
+
+ .ms-sm-5 {
+ margin-left: 3rem !important;
+ }
+
+ .ms-sm-auto {
+ margin-left: auto !important;
+ }
+
+ .p-sm-0 {
+ padding: 0 !important;
+ }
+
+ .p-sm-1 {
+ padding: 0.25rem !important;
+ }
+
+ .p-sm-2 {
+ padding: 0.5rem !important;
+ }
+
+ .p-sm-3 {
+ padding: 1rem !important;
+ }
+
+ .p-sm-4 {
+ padding: 1.5rem !important;
+ }
+
+ .p-sm-5 {
+ padding: 3rem !important;
+ }
+
+ .px-sm-0 {
+ padding-right: 0 !important;
+ padding-left: 0 !important;
+ }
+
+ .px-sm-1 {
+ padding-right: 0.25rem !important;
+ padding-left: 0.25rem !important;
+ }
+
+ .px-sm-2 {
+ padding-right: 0.5rem !important;
+ padding-left: 0.5rem !important;
+ }
+
+ .px-sm-3 {
+ padding-right: 1rem !important;
+ padding-left: 1rem !important;
+ }
+
+ .px-sm-4 {
+ padding-right: 1.5rem !important;
+ padding-left: 1.5rem !important;
+ }
+
+ .px-sm-5 {
+ padding-right: 3rem !important;
+ padding-left: 3rem !important;
+ }
+
+ .py-sm-0 {
+ padding-top: 0 !important;
+ padding-bottom: 0 !important;
+ }
+
+ .py-sm-1 {
+ padding-top: 0.25rem !important;
+ padding-bottom: 0.25rem !important;
+ }
+
+ .py-sm-2 {
+ padding-top: 0.5rem !important;
+ padding-bottom: 0.5rem !important;
+ }
+
+ .py-sm-3 {
+ padding-top: 1rem !important;
+ padding-bottom: 1rem !important;
+ }
+
+ .py-sm-4 {
+ padding-top: 1.5rem !important;
+ padding-bottom: 1.5rem !important;
+ }
+
+ .py-sm-5 {
+ padding-top: 3rem !important;
+ padding-bottom: 3rem !important;
+ }
+
+ .pt-sm-0 {
+ padding-top: 0 !important;
+ }
+
+ .pt-sm-1 {
+ padding-top: 0.25rem !important;
+ }
+
+ .pt-sm-2 {
+ padding-top: 0.5rem !important;
+ }
+
+ .pt-sm-3 {
+ padding-top: 1rem !important;
+ }
+
+ .pt-sm-4 {
+ padding-top: 1.5rem !important;
+ }
+
+ .pt-sm-5 {
+ padding-top: 3rem !important;
+ }
+
+ .pe-sm-0 {
+ padding-right: 0 !important;
+ }
+
+ .pe-sm-1 {
+ padding-right: 0.25rem !important;
+ }
+
+ .pe-sm-2 {
+ padding-right: 0.5rem !important;
+ }
+
+ .pe-sm-3 {
+ padding-right: 1rem !important;
+ }
+
+ .pe-sm-4 {
+ padding-right: 1.5rem !important;
+ }
+
+ .pe-sm-5 {
+ padding-right: 3rem !important;
+ }
+
+ .pb-sm-0 {
+ padding-bottom: 0 !important;
+ }
+
+ .pb-sm-1 {
+ padding-bottom: 0.25rem !important;
+ }
+
+ .pb-sm-2 {
+ padding-bottom: 0.5rem !important;
+ }
+
+ .pb-sm-3 {
+ padding-bottom: 1rem !important;
+ }
+
+ .pb-sm-4 {
+ padding-bottom: 1.5rem !important;
+ }
+
+ .pb-sm-5 {
+ padding-bottom: 3rem !important;
+ }
+
+ .ps-sm-0 {
+ padding-left: 0 !important;
+ }
+
+ .ps-sm-1 {
+ padding-left: 0.25rem !important;
+ }
+
+ .ps-sm-2 {
+ padding-left: 0.5rem !important;
+ }
+
+ .ps-sm-3 {
+ padding-left: 1rem !important;
+ }
+
+ .ps-sm-4 {
+ padding-left: 1.5rem !important;
+ }
+
+ .ps-sm-5 {
+ padding-left: 3rem !important;
+ }
+}
+@media (min-width: 768px) {
+ .d-md-inline {
+ display: inline !important;
+ }
+
+ .d-md-inline-block {
+ display: inline-block !important;
+ }
+
+ .d-md-block {
+ display: block !important;
+ }
+
+ .d-md-grid {
+ display: grid !important;
+ }
+
+ .d-md-table {
+ display: table !important;
+ }
+
+ .d-md-table-row {
+ display: table-row !important;
+ }
+
+ .d-md-table-cell {
+ display: table-cell !important;
+ }
+
+ .d-md-flex {
+ display: flex !important;
+ }
+
+ .d-md-inline-flex {
+ display: inline-flex !important;
+ }
+
+ .d-md-none {
+ display: none !important;
+ }
+
+ .flex-md-fill {
+ flex: 1 1 auto !important;
+ }
+
+ .flex-md-row {
+ flex-direction: row !important;
+ }
+
+ .flex-md-column {
+ flex-direction: column !important;
+ }
+
+ .flex-md-row-reverse {
+ flex-direction: row-reverse !important;
+ }
+
+ .flex-md-column-reverse {
+ flex-direction: column-reverse !important;
+ }
+
+ .flex-md-grow-0 {
+ flex-grow: 0 !important;
+ }
+
+ .flex-md-grow-1 {
+ flex-grow: 1 !important;
+ }
+
+ .flex-md-shrink-0 {
+ flex-shrink: 0 !important;
+ }
+
+ .flex-md-shrink-1 {
+ flex-shrink: 1 !important;
+ }
+
+ .flex-md-wrap {
+ flex-wrap: wrap !important;
+ }
+
+ .flex-md-nowrap {
+ flex-wrap: nowrap !important;
+ }
+
+ .flex-md-wrap-reverse {
+ flex-wrap: wrap-reverse !important;
+ }
+
+ .justify-content-md-start {
+ justify-content: flex-start !important;
+ }
+
+ .justify-content-md-end {
+ justify-content: flex-end !important;
+ }
+
+ .justify-content-md-center {
+ justify-content: center !important;
+ }
+
+ .justify-content-md-between {
+ justify-content: space-between !important;
+ }
+
+ .justify-content-md-around {
+ justify-content: space-around !important;
+ }
+
+ .justify-content-md-evenly {
+ justify-content: space-evenly !important;
+ }
+
+ .align-items-md-start {
+ align-items: flex-start !important;
+ }
+
+ .align-items-md-end {
+ align-items: flex-end !important;
+ }
+
+ .align-items-md-center {
+ align-items: center !important;
+ }
+
+ .align-items-md-baseline {
+ align-items: baseline !important;
+ }
+
+ .align-items-md-stretch {
+ align-items: stretch !important;
+ }
+
+ .align-content-md-start {
+ align-content: flex-start !important;
+ }
+
+ .align-content-md-end {
+ align-content: flex-end !important;
+ }
+
+ .align-content-md-center {
+ align-content: center !important;
+ }
+
+ .align-content-md-between {
+ align-content: space-between !important;
+ }
+
+ .align-content-md-around {
+ align-content: space-around !important;
+ }
+
+ .align-content-md-stretch {
+ align-content: stretch !important;
+ }
+
+ .align-self-md-auto {
+ align-self: auto !important;
+ }
+
+ .align-self-md-start {
+ align-self: flex-start !important;
+ }
+
+ .align-self-md-end {
+ align-self: flex-end !important;
+ }
+
+ .align-self-md-center {
+ align-self: center !important;
+ }
+
+ .align-self-md-baseline {
+ align-self: baseline !important;
+ }
+
+ .align-self-md-stretch {
+ align-self: stretch !important;
+ }
+
+ .order-md-first {
+ order: -1 !important;
+ }
+
+ .order-md-0 {
+ order: 0 !important;
+ }
+
+ .order-md-1 {
+ order: 1 !important;
+ }
+
+ .order-md-2 {
+ order: 2 !important;
+ }
+
+ .order-md-3 {
+ order: 3 !important;
+ }
+
+ .order-md-4 {
+ order: 4 !important;
+ }
+
+ .order-md-5 {
+ order: 5 !important;
+ }
+
+ .order-md-last {
+ order: 6 !important;
+ }
+
+ .m-md-0 {
+ margin: 0 !important;
+ }
+
+ .m-md-1 {
+ margin: 0.25rem !important;
+ }
+
+ .m-md-2 {
+ margin: 0.5rem !important;
+ }
+
+ .m-md-3 {
+ margin: 1rem !important;
+ }
+
+ .m-md-4 {
+ margin: 1.5rem !important;
+ }
+
+ .m-md-5 {
+ margin: 3rem !important;
+ }
+
+ .m-md-auto {
+ margin: auto !important;
+ }
+
+ .mx-md-0 {
+ margin-right: 0 !important;
+ margin-left: 0 !important;
+ }
+
+ .mx-md-1 {
+ margin-right: 0.25rem !important;
+ margin-left: 0.25rem !important;
+ }
+
+ .mx-md-2 {
+ margin-right: 0.5rem !important;
+ margin-left: 0.5rem !important;
+ }
+
+ .mx-md-3 {
+ margin-right: 1rem !important;
+ margin-left: 1rem !important;
+ }
+
+ .mx-md-4 {
+ margin-right: 1.5rem !important;
+ margin-left: 1.5rem !important;
+ }
+
+ .mx-md-5 {
+ margin-right: 3rem !important;
+ margin-left: 3rem !important;
+ }
+
+ .mx-md-auto {
+ margin-right: auto !important;
+ margin-left: auto !important;
+ }
+
+ .my-md-0 {
+ margin-top: 0 !important;
+ margin-bottom: 0 !important;
+ }
+
+ .my-md-1 {
+ margin-top: 0.25rem !important;
+ margin-bottom: 0.25rem !important;
+ }
+
+ .my-md-2 {
+ margin-top: 0.5rem !important;
+ margin-bottom: 0.5rem !important;
+ }
+
+ .my-md-3 {
+ margin-top: 1rem !important;
+ margin-bottom: 1rem !important;
+ }
+
+ .my-md-4 {
+ margin-top: 1.5rem !important;
+ margin-bottom: 1.5rem !important;
+ }
+
+ .my-md-5 {
+ margin-top: 3rem !important;
+ margin-bottom: 3rem !important;
+ }
+
+ .my-md-auto {
+ margin-top: auto !important;
+ margin-bottom: auto !important;
+ }
+
+ .mt-md-0 {
+ margin-top: 0 !important;
+ }
+
+ .mt-md-1 {
+ margin-top: 0.25rem !important;
+ }
+
+ .mt-md-2 {
+ margin-top: 0.5rem !important;
+ }
+
+ .mt-md-3 {
+ margin-top: 1rem !important;
+ }
+
+ .mt-md-4 {
+ margin-top: 1.5rem !important;
+ }
+
+ .mt-md-5 {
+ margin-top: 3rem !important;
+ }
+
+ .mt-md-auto {
+ margin-top: auto !important;
+ }
+
+ .me-md-0 {
+ margin-right: 0 !important;
+ }
+
+ .me-md-1 {
+ margin-right: 0.25rem !important;
+ }
+
+ .me-md-2 {
+ margin-right: 0.5rem !important;
+ }
+
+ .me-md-3 {
+ margin-right: 1rem !important;
+ }
+
+ .me-md-4 {
+ margin-right: 1.5rem !important;
+ }
+
+ .me-md-5 {
+ margin-right: 3rem !important;
+ }
+
+ .me-md-auto {
+ margin-right: auto !important;
+ }
+
+ .mb-md-0 {
+ margin-bottom: 0 !important;
+ }
+
+ .mb-md-1 {
+ margin-bottom: 0.25rem !important;
+ }
+
+ .mb-md-2 {
+ margin-bottom: 0.5rem !important;
+ }
+
+ .mb-md-3 {
+ margin-bottom: 1rem !important;
+ }
+
+ .mb-md-4 {
+ margin-bottom: 1.5rem !important;
+ }
+
+ .mb-md-5 {
+ margin-bottom: 3rem !important;
+ }
+
+ .mb-md-auto {
+ margin-bottom: auto !important;
+ }
+
+ .ms-md-0 {
+ margin-left: 0 !important;
+ }
+
+ .ms-md-1 {
+ margin-left: 0.25rem !important;
+ }
+
+ .ms-md-2 {
+ margin-left: 0.5rem !important;
+ }
+
+ .ms-md-3 {
+ margin-left: 1rem !important;
+ }
+
+ .ms-md-4 {
+ margin-left: 1.5rem !important;
+ }
+
+ .ms-md-5 {
+ margin-left: 3rem !important;
+ }
+
+ .ms-md-auto {
+ margin-left: auto !important;
+ }
+
+ .p-md-0 {
+ padding: 0 !important;
+ }
+
+ .p-md-1 {
+ padding: 0.25rem !important;
+ }
+
+ .p-md-2 {
+ padding: 0.5rem !important;
+ }
+
+ .p-md-3 {
+ padding: 1rem !important;
+ }
+
+ .p-md-4 {
+ padding: 1.5rem !important;
+ }
+
+ .p-md-5 {
+ padding: 3rem !important;
+ }
+
+ .px-md-0 {
+ padding-right: 0 !important;
+ padding-left: 0 !important;
+ }
+
+ .px-md-1 {
+ padding-right: 0.25rem !important;
+ padding-left: 0.25rem !important;
+ }
+
+ .px-md-2 {
+ padding-right: 0.5rem !important;
+ padding-left: 0.5rem !important;
+ }
+
+ .px-md-3 {
+ padding-right: 1rem !important;
+ padding-left: 1rem !important;
+ }
+
+ .px-md-4 {
+ padding-right: 1.5rem !important;
+ padding-left: 1.5rem !important;
+ }
+
+ .px-md-5 {
+ padding-right: 3rem !important;
+ padding-left: 3rem !important;
+ }
+
+ .py-md-0 {
+ padding-top: 0 !important;
+ padding-bottom: 0 !important;
+ }
+
+ .py-md-1 {
+ padding-top: 0.25rem !important;
+ padding-bottom: 0.25rem !important;
+ }
+
+ .py-md-2 {
+ padding-top: 0.5rem !important;
+ padding-bottom: 0.5rem !important;
+ }
+
+ .py-md-3 {
+ padding-top: 1rem !important;
+ padding-bottom: 1rem !important;
+ }
+
+ .py-md-4 {
+ padding-top: 1.5rem !important;
+ padding-bottom: 1.5rem !important;
+ }
+
+ .py-md-5 {
+ padding-top: 3rem !important;
+ padding-bottom: 3rem !important;
+ }
+
+ .pt-md-0 {
+ padding-top: 0 !important;
+ }
+
+ .pt-md-1 {
+ padding-top: 0.25rem !important;
+ }
+
+ .pt-md-2 {
+ padding-top: 0.5rem !important;
+ }
+
+ .pt-md-3 {
+ padding-top: 1rem !important;
+ }
+
+ .pt-md-4 {
+ padding-top: 1.5rem !important;
+ }
+
+ .pt-md-5 {
+ padding-top: 3rem !important;
+ }
+
+ .pe-md-0 {
+ padding-right: 0 !important;
+ }
+
+ .pe-md-1 {
+ padding-right: 0.25rem !important;
+ }
+
+ .pe-md-2 {
+ padding-right: 0.5rem !important;
+ }
+
+ .pe-md-3 {
+ padding-right: 1rem !important;
+ }
+
+ .pe-md-4 {
+ padding-right: 1.5rem !important;
+ }
+
+ .pe-md-5 {
+ padding-right: 3rem !important;
+ }
+
+ .pb-md-0 {
+ padding-bottom: 0 !important;
+ }
+
+ .pb-md-1 {
+ padding-bottom: 0.25rem !important;
+ }
+
+ .pb-md-2 {
+ padding-bottom: 0.5rem !important;
+ }
+
+ .pb-md-3 {
+ padding-bottom: 1rem !important;
+ }
+
+ .pb-md-4 {
+ padding-bottom: 1.5rem !important;
+ }
+
+ .pb-md-5 {
+ padding-bottom: 3rem !important;
+ }
+
+ .ps-md-0 {
+ padding-left: 0 !important;
+ }
+
+ .ps-md-1 {
+ padding-left: 0.25rem !important;
+ }
+
+ .ps-md-2 {
+ padding-left: 0.5rem !important;
+ }
+
+ .ps-md-3 {
+ padding-left: 1rem !important;
+ }
+
+ .ps-md-4 {
+ padding-left: 1.5rem !important;
+ }
+
+ .ps-md-5 {
+ padding-left: 3rem !important;
+ }
+}
+@media (min-width: 992px) {
+ .d-lg-inline {
+ display: inline !important;
+ }
+
+ .d-lg-inline-block {
+ display: inline-block !important;
+ }
+
+ .d-lg-block {
+ display: block !important;
+ }
+
+ .d-lg-grid {
+ display: grid !important;
+ }
+
+ .d-lg-table {
+ display: table !important;
+ }
+
+ .d-lg-table-row {
+ display: table-row !important;
+ }
+
+ .d-lg-table-cell {
+ display: table-cell !important;
+ }
+
+ .d-lg-flex {
+ display: flex !important;
+ }
+
+ .d-lg-inline-flex {
+ display: inline-flex !important;
+ }
+
+ .d-lg-none {
+ display: none !important;
+ }
+
+ .flex-lg-fill {
+ flex: 1 1 auto !important;
+ }
+
+ .flex-lg-row {
+ flex-direction: row !important;
+ }
+
+ .flex-lg-column {
+ flex-direction: column !important;
+ }
+
+ .flex-lg-row-reverse {
+ flex-direction: row-reverse !important;
+ }
+
+ .flex-lg-column-reverse {
+ flex-direction: column-reverse !important;
+ }
+
+ .flex-lg-grow-0 {
+ flex-grow: 0 !important;
+ }
+
+ .flex-lg-grow-1 {
+ flex-grow: 1 !important;
+ }
+
+ .flex-lg-shrink-0 {
+ flex-shrink: 0 !important;
+ }
+
+ .flex-lg-shrink-1 {
+ flex-shrink: 1 !important;
+ }
+
+ .flex-lg-wrap {
+ flex-wrap: wrap !important;
+ }
+
+ .flex-lg-nowrap {
+ flex-wrap: nowrap !important;
+ }
+
+ .flex-lg-wrap-reverse {
+ flex-wrap: wrap-reverse !important;
+ }
+
+ .justify-content-lg-start {
+ justify-content: flex-start !important;
+ }
+
+ .justify-content-lg-end {
+ justify-content: flex-end !important;
+ }
+
+ .justify-content-lg-center {
+ justify-content: center !important;
+ }
+
+ .justify-content-lg-between {
+ justify-content: space-between !important;
+ }
+
+ .justify-content-lg-around {
+ justify-content: space-around !important;
+ }
+
+ .justify-content-lg-evenly {
+ justify-content: space-evenly !important;
+ }
+
+ .align-items-lg-start {
+ align-items: flex-start !important;
+ }
+
+ .align-items-lg-end {
+ align-items: flex-end !important;
+ }
+
+ .align-items-lg-center {
+ align-items: center !important;
+ }
+
+ .align-items-lg-baseline {
+ align-items: baseline !important;
+ }
+
+ .align-items-lg-stretch {
+ align-items: stretch !important;
+ }
+
+ .align-content-lg-start {
+ align-content: flex-start !important;
+ }
+
+ .align-content-lg-end {
+ align-content: flex-end !important;
+ }
+
+ .align-content-lg-center {
+ align-content: center !important;
+ }
+
+ .align-content-lg-between {
+ align-content: space-between !important;
+ }
+
+ .align-content-lg-around {
+ align-content: space-around !important;
+ }
+
+ .align-content-lg-stretch {
+ align-content: stretch !important;
+ }
+
+ .align-self-lg-auto {
+ align-self: auto !important;
+ }
+
+ .align-self-lg-start {
+ align-self: flex-start !important;
+ }
+
+ .align-self-lg-end {
+ align-self: flex-end !important;
+ }
+
+ .align-self-lg-center {
+ align-self: center !important;
+ }
+
+ .align-self-lg-baseline {
+ align-self: baseline !important;
+ }
+
+ .align-self-lg-stretch {
+ align-self: stretch !important;
+ }
+
+ .order-lg-first {
+ order: -1 !important;
+ }
+
+ .order-lg-0 {
+ order: 0 !important;
+ }
+
+ .order-lg-1 {
+ order: 1 !important;
+ }
+
+ .order-lg-2 {
+ order: 2 !important;
+ }
+
+ .order-lg-3 {
+ order: 3 !important;
+ }
+
+ .order-lg-4 {
+ order: 4 !important;
+ }
+
+ .order-lg-5 {
+ order: 5 !important;
+ }
+
+ .order-lg-last {
+ order: 6 !important;
+ }
+
+ .m-lg-0 {
+ margin: 0 !important;
+ }
+
+ .m-lg-1 {
+ margin: 0.25rem !important;
+ }
+
+ .m-lg-2 {
+ margin: 0.5rem !important;
+ }
+
+ .m-lg-3 {
+ margin: 1rem !important;
+ }
+
+ .m-lg-4 {
+ margin: 1.5rem !important;
+ }
+
+ .m-lg-5 {
+ margin: 3rem !important;
+ }
+
+ .m-lg-auto {
+ margin: auto !important;
+ }
+
+ .mx-lg-0 {
+ margin-right: 0 !important;
+ margin-left: 0 !important;
+ }
+
+ .mx-lg-1 {
+ margin-right: 0.25rem !important;
+ margin-left: 0.25rem !important;
+ }
+
+ .mx-lg-2 {
+ margin-right: 0.5rem !important;
+ margin-left: 0.5rem !important;
+ }
+
+ .mx-lg-3 {
+ margin-right: 1rem !important;
+ margin-left: 1rem !important;
+ }
+
+ .mx-lg-4 {
+ margin-right: 1.5rem !important;
+ margin-left: 1.5rem !important;
+ }
+
+ .mx-lg-5 {
+ margin-right: 3rem !important;
+ margin-left: 3rem !important;
+ }
+
+ .mx-lg-auto {
+ margin-right: auto !important;
+ margin-left: auto !important;
+ }
+
+ .my-lg-0 {
+ margin-top: 0 !important;
+ margin-bottom: 0 !important;
+ }
+
+ .my-lg-1 {
+ margin-top: 0.25rem !important;
+ margin-bottom: 0.25rem !important;
+ }
+
+ .my-lg-2 {
+ margin-top: 0.5rem !important;
+ margin-bottom: 0.5rem !important;
+ }
+
+ .my-lg-3 {
+ margin-top: 1rem !important;
+ margin-bottom: 1rem !important;
+ }
+
+ .my-lg-4 {
+ margin-top: 1.5rem !important;
+ margin-bottom: 1.5rem !important;
+ }
+
+ .my-lg-5 {
+ margin-top: 3rem !important;
+ margin-bottom: 3rem !important;
+ }
+
+ .my-lg-auto {
+ margin-top: auto !important;
+ margin-bottom: auto !important;
+ }
+
+ .mt-lg-0 {
+ margin-top: 0 !important;
+ }
+
+ .mt-lg-1 {
+ margin-top: 0.25rem !important;
+ }
+
+ .mt-lg-2 {
+ margin-top: 0.5rem !important;
+ }
+
+ .mt-lg-3 {
+ margin-top: 1rem !important;
+ }
+
+ .mt-lg-4 {
+ margin-top: 1.5rem !important;
+ }
+
+ .mt-lg-5 {
+ margin-top: 3rem !important;
+ }
+
+ .mt-lg-auto {
+ margin-top: auto !important;
+ }
+
+ .me-lg-0 {
+ margin-right: 0 !important;
+ }
+
+ .me-lg-1 {
+ margin-right: 0.25rem !important;
+ }
+
+ .me-lg-2 {
+ margin-right: 0.5rem !important;
+ }
+
+ .me-lg-3 {
+ margin-right: 1rem !important;
+ }
+
+ .me-lg-4 {
+ margin-right: 1.5rem !important;
+ }
+
+ .me-lg-5 {
+ margin-right: 3rem !important;
+ }
+
+ .me-lg-auto {
+ margin-right: auto !important;
+ }
+
+ .mb-lg-0 {
+ margin-bottom: 0 !important;
+ }
+
+ .mb-lg-1 {
+ margin-bottom: 0.25rem !important;
+ }
+
+ .mb-lg-2 {
+ margin-bottom: 0.5rem !important;
+ }
+
+ .mb-lg-3 {
+ margin-bottom: 1rem !important;
+ }
+
+ .mb-lg-4 {
+ margin-bottom: 1.5rem !important;
+ }
+
+ .mb-lg-5 {
+ margin-bottom: 3rem !important;
+ }
+
+ .mb-lg-auto {
+ margin-bottom: auto !important;
+ }
+
+ .ms-lg-0 {
+ margin-left: 0 !important;
+ }
+
+ .ms-lg-1 {
+ margin-left: 0.25rem !important;
+ }
+
+ .ms-lg-2 {
+ margin-left: 0.5rem !important;
+ }
+
+ .ms-lg-3 {
+ margin-left: 1rem !important;
+ }
+
+ .ms-lg-4 {
+ margin-left: 1.5rem !important;
+ }
+
+ .ms-lg-5 {
+ margin-left: 3rem !important;
+ }
+
+ .ms-lg-auto {
+ margin-left: auto !important;
+ }
+
+ .p-lg-0 {
+ padding: 0 !important;
+ }
+
+ .p-lg-1 {
+ padding: 0.25rem !important;
+ }
+
+ .p-lg-2 {
+ padding: 0.5rem !important;
+ }
+
+ .p-lg-3 {
+ padding: 1rem !important;
+ }
+
+ .p-lg-4 {
+ padding: 1.5rem !important;
+ }
+
+ .p-lg-5 {
+ padding: 3rem !important;
+ }
+
+ .px-lg-0 {
+ padding-right: 0 !important;
+ padding-left: 0 !important;
+ }
+
+ .px-lg-1 {
+ padding-right: 0.25rem !important;
+ padding-left: 0.25rem !important;
+ }
+
+ .px-lg-2 {
+ padding-right: 0.5rem !important;
+ padding-left: 0.5rem !important;
+ }
+
+ .px-lg-3 {
+ padding-right: 1rem !important;
+ padding-left: 1rem !important;
+ }
+
+ .px-lg-4 {
+ padding-right: 1.5rem !important;
+ padding-left: 1.5rem !important;
+ }
+
+ .px-lg-5 {
+ padding-right: 3rem !important;
+ padding-left: 3rem !important;
+ }
+
+ .py-lg-0 {
+ padding-top: 0 !important;
+ padding-bottom: 0 !important;
+ }
+
+ .py-lg-1 {
+ padding-top: 0.25rem !important;
+ padding-bottom: 0.25rem !important;
+ }
+
+ .py-lg-2 {
+ padding-top: 0.5rem !important;
+ padding-bottom: 0.5rem !important;
+ }
+
+ .py-lg-3 {
+ padding-top: 1rem !important;
+ padding-bottom: 1rem !important;
+ }
+
+ .py-lg-4 {
+ padding-top: 1.5rem !important;
+ padding-bottom: 1.5rem !important;
+ }
+
+ .py-lg-5 {
+ padding-top: 3rem !important;
+ padding-bottom: 3rem !important;
+ }
+
+ .pt-lg-0 {
+ padding-top: 0 !important;
+ }
+
+ .pt-lg-1 {
+ padding-top: 0.25rem !important;
+ }
+
+ .pt-lg-2 {
+ padding-top: 0.5rem !important;
+ }
+
+ .pt-lg-3 {
+ padding-top: 1rem !important;
+ }
+
+ .pt-lg-4 {
+ padding-top: 1.5rem !important;
+ }
+
+ .pt-lg-5 {
+ padding-top: 3rem !important;
+ }
+
+ .pe-lg-0 {
+ padding-right: 0 !important;
+ }
+
+ .pe-lg-1 {
+ padding-right: 0.25rem !important;
+ }
+
+ .pe-lg-2 {
+ padding-right: 0.5rem !important;
+ }
+
+ .pe-lg-3 {
+ padding-right: 1rem !important;
+ }
+
+ .pe-lg-4 {
+ padding-right: 1.5rem !important;
+ }
+
+ .pe-lg-5 {
+ padding-right: 3rem !important;
+ }
+
+ .pb-lg-0 {
+ padding-bottom: 0 !important;
+ }
+
+ .pb-lg-1 {
+ padding-bottom: 0.25rem !important;
+ }
+
+ .pb-lg-2 {
+ padding-bottom: 0.5rem !important;
+ }
+
+ .pb-lg-3 {
+ padding-bottom: 1rem !important;
+ }
+
+ .pb-lg-4 {
+ padding-bottom: 1.5rem !important;
+ }
+
+ .pb-lg-5 {
+ padding-bottom: 3rem !important;
+ }
+
+ .ps-lg-0 {
+ padding-left: 0 !important;
+ }
+
+ .ps-lg-1 {
+ padding-left: 0.25rem !important;
+ }
+
+ .ps-lg-2 {
+ padding-left: 0.5rem !important;
+ }
+
+ .ps-lg-3 {
+ padding-left: 1rem !important;
+ }
+
+ .ps-lg-4 {
+ padding-left: 1.5rem !important;
+ }
+
+ .ps-lg-5 {
+ padding-left: 3rem !important;
+ }
+}
+@media (min-width: 1200px) {
+ .d-xl-inline {
+ display: inline !important;
+ }
+
+ .d-xl-inline-block {
+ display: inline-block !important;
+ }
+
+ .d-xl-block {
+ display: block !important;
+ }
+
+ .d-xl-grid {
+ display: grid !important;
+ }
+
+ .d-xl-table {
+ display: table !important;
+ }
+
+ .d-xl-table-row {
+ display: table-row !important;
+ }
+
+ .d-xl-table-cell {
+ display: table-cell !important;
+ }
+
+ .d-xl-flex {
+ display: flex !important;
+ }
+
+ .d-xl-inline-flex {
+ display: inline-flex !important;
+ }
+
+ .d-xl-none {
+ display: none !important;
+ }
+
+ .flex-xl-fill {
+ flex: 1 1 auto !important;
+ }
+
+ .flex-xl-row {
+ flex-direction: row !important;
+ }
+
+ .flex-xl-column {
+ flex-direction: column !important;
+ }
+
+ .flex-xl-row-reverse {
+ flex-direction: row-reverse !important;
+ }
+
+ .flex-xl-column-reverse {
+ flex-direction: column-reverse !important;
+ }
+
+ .flex-xl-grow-0 {
+ flex-grow: 0 !important;
+ }
+
+ .flex-xl-grow-1 {
+ flex-grow: 1 !important;
+ }
+
+ .flex-xl-shrink-0 {
+ flex-shrink: 0 !important;
+ }
+
+ .flex-xl-shrink-1 {
+ flex-shrink: 1 !important;
+ }
+
+ .flex-xl-wrap {
+ flex-wrap: wrap !important;
+ }
+
+ .flex-xl-nowrap {
+ flex-wrap: nowrap !important;
+ }
+
+ .flex-xl-wrap-reverse {
+ flex-wrap: wrap-reverse !important;
+ }
+
+ .justify-content-xl-start {
+ justify-content: flex-start !important;
+ }
+
+ .justify-content-xl-end {
+ justify-content: flex-end !important;
+ }
+
+ .justify-content-xl-center {
+ justify-content: center !important;
+ }
+
+ .justify-content-xl-between {
+ justify-content: space-between !important;
+ }
+
+ .justify-content-xl-around {
+ justify-content: space-around !important;
+ }
+
+ .justify-content-xl-evenly {
+ justify-content: space-evenly !important;
+ }
+
+ .align-items-xl-start {
+ align-items: flex-start !important;
+ }
+
+ .align-items-xl-end {
+ align-items: flex-end !important;
+ }
+
+ .align-items-xl-center {
+ align-items: center !important;
+ }
+
+ .align-items-xl-baseline {
+ align-items: baseline !important;
+ }
+
+ .align-items-xl-stretch {
+ align-items: stretch !important;
+ }
+
+ .align-content-xl-start {
+ align-content: flex-start !important;
+ }
+
+ .align-content-xl-end {
+ align-content: flex-end !important;
+ }
+
+ .align-content-xl-center {
+ align-content: center !important;
+ }
+
+ .align-content-xl-between {
+ align-content: space-between !important;
+ }
+
+ .align-content-xl-around {
+ align-content: space-around !important;
+ }
+
+ .align-content-xl-stretch {
+ align-content: stretch !important;
+ }
+
+ .align-self-xl-auto {
+ align-self: auto !important;
+ }
+
+ .align-self-xl-start {
+ align-self: flex-start !important;
+ }
+
+ .align-self-xl-end {
+ align-self: flex-end !important;
+ }
+
+ .align-self-xl-center {
+ align-self: center !important;
+ }
+
+ .align-self-xl-baseline {
+ align-self: baseline !important;
+ }
+
+ .align-self-xl-stretch {
+ align-self: stretch !important;
+ }
+
+ .order-xl-first {
+ order: -1 !important;
+ }
+
+ .order-xl-0 {
+ order: 0 !important;
+ }
+
+ .order-xl-1 {
+ order: 1 !important;
+ }
+
+ .order-xl-2 {
+ order: 2 !important;
+ }
+
+ .order-xl-3 {
+ order: 3 !important;
+ }
+
+ .order-xl-4 {
+ order: 4 !important;
+ }
+
+ .order-xl-5 {
+ order: 5 !important;
+ }
+
+ .order-xl-last {
+ order: 6 !important;
+ }
+
+ .m-xl-0 {
+ margin: 0 !important;
+ }
+
+ .m-xl-1 {
+ margin: 0.25rem !important;
+ }
+
+ .m-xl-2 {
+ margin: 0.5rem !important;
+ }
+
+ .m-xl-3 {
+ margin: 1rem !important;
+ }
+
+ .m-xl-4 {
+ margin: 1.5rem !important;
+ }
+
+ .m-xl-5 {
+ margin: 3rem !important;
+ }
+
+ .m-xl-auto {
+ margin: auto !important;
+ }
+
+ .mx-xl-0 {
+ margin-right: 0 !important;
+ margin-left: 0 !important;
+ }
+
+ .mx-xl-1 {
+ margin-right: 0.25rem !important;
+ margin-left: 0.25rem !important;
+ }
+
+ .mx-xl-2 {
+ margin-right: 0.5rem !important;
+ margin-left: 0.5rem !important;
+ }
+
+ .mx-xl-3 {
+ margin-right: 1rem !important;
+ margin-left: 1rem !important;
+ }
+
+ .mx-xl-4 {
+ margin-right: 1.5rem !important;
+ margin-left: 1.5rem !important;
+ }
+
+ .mx-xl-5 {
+ margin-right: 3rem !important;
+ margin-left: 3rem !important;
+ }
+
+ .mx-xl-auto {
+ margin-right: auto !important;
+ margin-left: auto !important;
+ }
+
+ .my-xl-0 {
+ margin-top: 0 !important;
+ margin-bottom: 0 !important;
+ }
+
+ .my-xl-1 {
+ margin-top: 0.25rem !important;
+ margin-bottom: 0.25rem !important;
+ }
+
+ .my-xl-2 {
+ margin-top: 0.5rem !important;
+ margin-bottom: 0.5rem !important;
+ }
+
+ .my-xl-3 {
+ margin-top: 1rem !important;
+ margin-bottom: 1rem !important;
+ }
+
+ .my-xl-4 {
+ margin-top: 1.5rem !important;
+ margin-bottom: 1.5rem !important;
+ }
+
+ .my-xl-5 {
+ margin-top: 3rem !important;
+ margin-bottom: 3rem !important;
+ }
+
+ .my-xl-auto {
+ margin-top: auto !important;
+ margin-bottom: auto !important;
+ }
+
+ .mt-xl-0 {
+ margin-top: 0 !important;
+ }
+
+ .mt-xl-1 {
+ margin-top: 0.25rem !important;
+ }
+
+ .mt-xl-2 {
+ margin-top: 0.5rem !important;
+ }
+
+ .mt-xl-3 {
+ margin-top: 1rem !important;
+ }
+
+ .mt-xl-4 {
+ margin-top: 1.5rem !important;
+ }
+
+ .mt-xl-5 {
+ margin-top: 3rem !important;
+ }
+
+ .mt-xl-auto {
+ margin-top: auto !important;
+ }
+
+ .me-xl-0 {
+ margin-right: 0 !important;
+ }
+
+ .me-xl-1 {
+ margin-right: 0.25rem !important;
+ }
+
+ .me-xl-2 {
+ margin-right: 0.5rem !important;
+ }
+
+ .me-xl-3 {
+ margin-right: 1rem !important;
+ }
+
+ .me-xl-4 {
+ margin-right: 1.5rem !important;
+ }
+
+ .me-xl-5 {
+ margin-right: 3rem !important;
+ }
+
+ .me-xl-auto {
+ margin-right: auto !important;
+ }
+
+ .mb-xl-0 {
+ margin-bottom: 0 !important;
+ }
+
+ .mb-xl-1 {
+ margin-bottom: 0.25rem !important;
+ }
+
+ .mb-xl-2 {
+ margin-bottom: 0.5rem !important;
+ }
+
+ .mb-xl-3 {
+ margin-bottom: 1rem !important;
+ }
+
+ .mb-xl-4 {
+ margin-bottom: 1.5rem !important;
+ }
+
+ .mb-xl-5 {
+ margin-bottom: 3rem !important;
+ }
+
+ .mb-xl-auto {
+ margin-bottom: auto !important;
+ }
+
+ .ms-xl-0 {
+ margin-left: 0 !important;
+ }
+
+ .ms-xl-1 {
+ margin-left: 0.25rem !important;
+ }
+
+ .ms-xl-2 {
+ margin-left: 0.5rem !important;
+ }
+
+ .ms-xl-3 {
+ margin-left: 1rem !important;
+ }
+
+ .ms-xl-4 {
+ margin-left: 1.5rem !important;
+ }
+
+ .ms-xl-5 {
+ margin-left: 3rem !important;
+ }
+
+ .ms-xl-auto {
+ margin-left: auto !important;
+ }
+
+ .p-xl-0 {
+ padding: 0 !important;
+ }
+
+ .p-xl-1 {
+ padding: 0.25rem !important;
+ }
+
+ .p-xl-2 {
+ padding: 0.5rem !important;
+ }
+
+ .p-xl-3 {
+ padding: 1rem !important;
+ }
+
+ .p-xl-4 {
+ padding: 1.5rem !important;
+ }
+
+ .p-xl-5 {
+ padding: 3rem !important;
+ }
+
+ .px-xl-0 {
+ padding-right: 0 !important;
+ padding-left: 0 !important;
+ }
+
+ .px-xl-1 {
+ padding-right: 0.25rem !important;
+ padding-left: 0.25rem !important;
+ }
+
+ .px-xl-2 {
+ padding-right: 0.5rem !important;
+ padding-left: 0.5rem !important;
+ }
+
+ .px-xl-3 {
+ padding-right: 1rem !important;
+ padding-left: 1rem !important;
+ }
+
+ .px-xl-4 {
+ padding-right: 1.5rem !important;
+ padding-left: 1.5rem !important;
+ }
+
+ .px-xl-5 {
+ padding-right: 3rem !important;
+ padding-left: 3rem !important;
+ }
+
+ .py-xl-0 {
+ padding-top: 0 !important;
+ padding-bottom: 0 !important;
+ }
+
+ .py-xl-1 {
+ padding-top: 0.25rem !important;
+ padding-bottom: 0.25rem !important;
+ }
+
+ .py-xl-2 {
+ padding-top: 0.5rem !important;
+ padding-bottom: 0.5rem !important;
+ }
+
+ .py-xl-3 {
+ padding-top: 1rem !important;
+ padding-bottom: 1rem !important;
+ }
+
+ .py-xl-4 {
+ padding-top: 1.5rem !important;
+ padding-bottom: 1.5rem !important;
+ }
+
+ .py-xl-5 {
+ padding-top: 3rem !important;
+ padding-bottom: 3rem !important;
+ }
+
+ .pt-xl-0 {
+ padding-top: 0 !important;
+ }
+
+ .pt-xl-1 {
+ padding-top: 0.25rem !important;
+ }
+
+ .pt-xl-2 {
+ padding-top: 0.5rem !important;
+ }
+
+ .pt-xl-3 {
+ padding-top: 1rem !important;
+ }
+
+ .pt-xl-4 {
+ padding-top: 1.5rem !important;
+ }
+
+ .pt-xl-5 {
+ padding-top: 3rem !important;
+ }
+
+ .pe-xl-0 {
+ padding-right: 0 !important;
+ }
+
+ .pe-xl-1 {
+ padding-right: 0.25rem !important;
+ }
+
+ .pe-xl-2 {
+ padding-right: 0.5rem !important;
+ }
+
+ .pe-xl-3 {
+ padding-right: 1rem !important;
+ }
+
+ .pe-xl-4 {
+ padding-right: 1.5rem !important;
+ }
+
+ .pe-xl-5 {
+ padding-right: 3rem !important;
+ }
+
+ .pb-xl-0 {
+ padding-bottom: 0 !important;
+ }
+
+ .pb-xl-1 {
+ padding-bottom: 0.25rem !important;
+ }
+
+ .pb-xl-2 {
+ padding-bottom: 0.5rem !important;
+ }
+
+ .pb-xl-3 {
+ padding-bottom: 1rem !important;
+ }
+
+ .pb-xl-4 {
+ padding-bottom: 1.5rem !important;
+ }
+
+ .pb-xl-5 {
+ padding-bottom: 3rem !important;
+ }
+
+ .ps-xl-0 {
+ padding-left: 0 !important;
+ }
+
+ .ps-xl-1 {
+ padding-left: 0.25rem !important;
+ }
+
+ .ps-xl-2 {
+ padding-left: 0.5rem !important;
+ }
+
+ .ps-xl-3 {
+ padding-left: 1rem !important;
+ }
+
+ .ps-xl-4 {
+ padding-left: 1.5rem !important;
+ }
+
+ .ps-xl-5 {
+ padding-left: 3rem !important;
+ }
+}
+@media (min-width: 1400px) {
+ .d-xxl-inline {
+ display: inline !important;
+ }
+
+ .d-xxl-inline-block {
+ display: inline-block !important;
+ }
+
+ .d-xxl-block {
+ display: block !important;
+ }
+
+ .d-xxl-grid {
+ display: grid !important;
+ }
+
+ .d-xxl-table {
+ display: table !important;
+ }
+
+ .d-xxl-table-row {
+ display: table-row !important;
+ }
+
+ .d-xxl-table-cell {
+ display: table-cell !important;
+ }
+
+ .d-xxl-flex {
+ display: flex !important;
+ }
+
+ .d-xxl-inline-flex {
+ display: inline-flex !important;
+ }
+
+ .d-xxl-none {
+ display: none !important;
+ }
+
+ .flex-xxl-fill {
+ flex: 1 1 auto !important;
+ }
+
+ .flex-xxl-row {
+ flex-direction: row !important;
+ }
+
+ .flex-xxl-column {
+ flex-direction: column !important;
+ }
+
+ .flex-xxl-row-reverse {
+ flex-direction: row-reverse !important;
+ }
+
+ .flex-xxl-column-reverse {
+ flex-direction: column-reverse !important;
+ }
+
+ .flex-xxl-grow-0 {
+ flex-grow: 0 !important;
+ }
+
+ .flex-xxl-grow-1 {
+ flex-grow: 1 !important;
+ }
+
+ .flex-xxl-shrink-0 {
+ flex-shrink: 0 !important;
+ }
+
+ .flex-xxl-shrink-1 {
+ flex-shrink: 1 !important;
+ }
+
+ .flex-xxl-wrap {
+ flex-wrap: wrap !important;
+ }
+
+ .flex-xxl-nowrap {
+ flex-wrap: nowrap !important;
+ }
+
+ .flex-xxl-wrap-reverse {
+ flex-wrap: wrap-reverse !important;
+ }
+
+ .justify-content-xxl-start {
+ justify-content: flex-start !important;
+ }
+
+ .justify-content-xxl-end {
+ justify-content: flex-end !important;
+ }
+
+ .justify-content-xxl-center {
+ justify-content: center !important;
+ }
+
+ .justify-content-xxl-between {
+ justify-content: space-between !important;
+ }
+
+ .justify-content-xxl-around {
+ justify-content: space-around !important;
+ }
+
+ .justify-content-xxl-evenly {
+ justify-content: space-evenly !important;
+ }
+
+ .align-items-xxl-start {
+ align-items: flex-start !important;
+ }
+
+ .align-items-xxl-end {
+ align-items: flex-end !important;
+ }
+
+ .align-items-xxl-center {
+ align-items: center !important;
+ }
+
+ .align-items-xxl-baseline {
+ align-items: baseline !important;
+ }
+
+ .align-items-xxl-stretch {
+ align-items: stretch !important;
+ }
+
+ .align-content-xxl-start {
+ align-content: flex-start !important;
+ }
+
+ .align-content-xxl-end {
+ align-content: flex-end !important;
+ }
+
+ .align-content-xxl-center {
+ align-content: center !important;
+ }
+
+ .align-content-xxl-between {
+ align-content: space-between !important;
+ }
+
+ .align-content-xxl-around {
+ align-content: space-around !important;
+ }
+
+ .align-content-xxl-stretch {
+ align-content: stretch !important;
+ }
+
+ .align-self-xxl-auto {
+ align-self: auto !important;
+ }
+
+ .align-self-xxl-start {
+ align-self: flex-start !important;
+ }
+
+ .align-self-xxl-end {
+ align-self: flex-end !important;
+ }
+
+ .align-self-xxl-center {
+ align-self: center !important;
+ }
+
+ .align-self-xxl-baseline {
+ align-self: baseline !important;
+ }
+
+ .align-self-xxl-stretch {
+ align-self: stretch !important;
+ }
+
+ .order-xxl-first {
+ order: -1 !important;
+ }
+
+ .order-xxl-0 {
+ order: 0 !important;
+ }
+
+ .order-xxl-1 {
+ order: 1 !important;
+ }
+
+ .order-xxl-2 {
+ order: 2 !important;
+ }
+
+ .order-xxl-3 {
+ order: 3 !important;
+ }
+
+ .order-xxl-4 {
+ order: 4 !important;
+ }
+
+ .order-xxl-5 {
+ order: 5 !important;
+ }
+
+ .order-xxl-last {
+ order: 6 !important;
+ }
+
+ .m-xxl-0 {
+ margin: 0 !important;
+ }
+
+ .m-xxl-1 {
+ margin: 0.25rem !important;
+ }
+
+ .m-xxl-2 {
+ margin: 0.5rem !important;
+ }
+
+ .m-xxl-3 {
+ margin: 1rem !important;
+ }
+
+ .m-xxl-4 {
+ margin: 1.5rem !important;
+ }
+
+ .m-xxl-5 {
+ margin: 3rem !important;
+ }
+
+ .m-xxl-auto {
+ margin: auto !important;
+ }
+
+ .mx-xxl-0 {
+ margin-right: 0 !important;
+ margin-left: 0 !important;
+ }
+
+ .mx-xxl-1 {
+ margin-right: 0.25rem !important;
+ margin-left: 0.25rem !important;
+ }
+
+ .mx-xxl-2 {
+ margin-right: 0.5rem !important;
+ margin-left: 0.5rem !important;
+ }
+
+ .mx-xxl-3 {
+ margin-right: 1rem !important;
+ margin-left: 1rem !important;
+ }
+
+ .mx-xxl-4 {
+ margin-right: 1.5rem !important;
+ margin-left: 1.5rem !important;
+ }
+
+ .mx-xxl-5 {
+ margin-right: 3rem !important;
+ margin-left: 3rem !important;
+ }
+
+ .mx-xxl-auto {
+ margin-right: auto !important;
+ margin-left: auto !important;
+ }
+
+ .my-xxl-0 {
+ margin-top: 0 !important;
+ margin-bottom: 0 !important;
+ }
+
+ .my-xxl-1 {
+ margin-top: 0.25rem !important;
+ margin-bottom: 0.25rem !important;
+ }
+
+ .my-xxl-2 {
+ margin-top: 0.5rem !important;
+ margin-bottom: 0.5rem !important;
+ }
+
+ .my-xxl-3 {
+ margin-top: 1rem !important;
+ margin-bottom: 1rem !important;
+ }
+
+ .my-xxl-4 {
+ margin-top: 1.5rem !important;
+ margin-bottom: 1.5rem !important;
+ }
+
+ .my-xxl-5 {
+ margin-top: 3rem !important;
+ margin-bottom: 3rem !important;
+ }
+
+ .my-xxl-auto {
+ margin-top: auto !important;
+ margin-bottom: auto !important;
+ }
+
+ .mt-xxl-0 {
+ margin-top: 0 !important;
+ }
+
+ .mt-xxl-1 {
+ margin-top: 0.25rem !important;
+ }
+
+ .mt-xxl-2 {
+ margin-top: 0.5rem !important;
+ }
+
+ .mt-xxl-3 {
+ margin-top: 1rem !important;
+ }
+
+ .mt-xxl-4 {
+ margin-top: 1.5rem !important;
+ }
+
+ .mt-xxl-5 {
+ margin-top: 3rem !important;
+ }
+
+ .mt-xxl-auto {
+ margin-top: auto !important;
+ }
+
+ .me-xxl-0 {
+ margin-right: 0 !important;
+ }
+
+ .me-xxl-1 {
+ margin-right: 0.25rem !important;
+ }
+
+ .me-xxl-2 {
+ margin-right: 0.5rem !important;
+ }
+
+ .me-xxl-3 {
+ margin-right: 1rem !important;
+ }
+
+ .me-xxl-4 {
+ margin-right: 1.5rem !important;
+ }
+
+ .me-xxl-5 {
+ margin-right: 3rem !important;
+ }
+
+ .me-xxl-auto {
+ margin-right: auto !important;
+ }
+
+ .mb-xxl-0 {
+ margin-bottom: 0 !important;
+ }
+
+ .mb-xxl-1 {
+ margin-bottom: 0.25rem !important;
+ }
+
+ .mb-xxl-2 {
+ margin-bottom: 0.5rem !important;
+ }
+
+ .mb-xxl-3 {
+ margin-bottom: 1rem !important;
+ }
+
+ .mb-xxl-4 {
+ margin-bottom: 1.5rem !important;
+ }
+
+ .mb-xxl-5 {
+ margin-bottom: 3rem !important;
+ }
+
+ .mb-xxl-auto {
+ margin-bottom: auto !important;
+ }
+
+ .ms-xxl-0 {
+ margin-left: 0 !important;
+ }
+
+ .ms-xxl-1 {
+ margin-left: 0.25rem !important;
+ }
+
+ .ms-xxl-2 {
+ margin-left: 0.5rem !important;
+ }
+
+ .ms-xxl-3 {
+ margin-left: 1rem !important;
+ }
+
+ .ms-xxl-4 {
+ margin-left: 1.5rem !important;
+ }
+
+ .ms-xxl-5 {
+ margin-left: 3rem !important;
+ }
+
+ .ms-xxl-auto {
+ margin-left: auto !important;
+ }
+
+ .p-xxl-0 {
+ padding: 0 !important;
+ }
+
+ .p-xxl-1 {
+ padding: 0.25rem !important;
+ }
+
+ .p-xxl-2 {
+ padding: 0.5rem !important;
+ }
+
+ .p-xxl-3 {
+ padding: 1rem !important;
+ }
+
+ .p-xxl-4 {
+ padding: 1.5rem !important;
+ }
+
+ .p-xxl-5 {
+ padding: 3rem !important;
+ }
+
+ .px-xxl-0 {
+ padding-right: 0 !important;
+ padding-left: 0 !important;
+ }
+
+ .px-xxl-1 {
+ padding-right: 0.25rem !important;
+ padding-left: 0.25rem !important;
+ }
+
+ .px-xxl-2 {
+ padding-right: 0.5rem !important;
+ padding-left: 0.5rem !important;
+ }
+
+ .px-xxl-3 {
+ padding-right: 1rem !important;
+ padding-left: 1rem !important;
+ }
+
+ .px-xxl-4 {
+ padding-right: 1.5rem !important;
+ padding-left: 1.5rem !important;
+ }
+
+ .px-xxl-5 {
+ padding-right: 3rem !important;
+ padding-left: 3rem !important;
+ }
+
+ .py-xxl-0 {
+ padding-top: 0 !important;
+ padding-bottom: 0 !important;
+ }
+
+ .py-xxl-1 {
+ padding-top: 0.25rem !important;
+ padding-bottom: 0.25rem !important;
+ }
+
+ .py-xxl-2 {
+ padding-top: 0.5rem !important;
+ padding-bottom: 0.5rem !important;
+ }
+
+ .py-xxl-3 {
+ padding-top: 1rem !important;
+ padding-bottom: 1rem !important;
+ }
+
+ .py-xxl-4 {
+ padding-top: 1.5rem !important;
+ padding-bottom: 1.5rem !important;
+ }
+
+ .py-xxl-5 {
+ padding-top: 3rem !important;
+ padding-bottom: 3rem !important;
+ }
+
+ .pt-xxl-0 {
+ padding-top: 0 !important;
+ }
+
+ .pt-xxl-1 {
+ padding-top: 0.25rem !important;
+ }
+
+ .pt-xxl-2 {
+ padding-top: 0.5rem !important;
+ }
+
+ .pt-xxl-3 {
+ padding-top: 1rem !important;
+ }
+
+ .pt-xxl-4 {
+ padding-top: 1.5rem !important;
+ }
+
+ .pt-xxl-5 {
+ padding-top: 3rem !important;
+ }
+
+ .pe-xxl-0 {
+ padding-right: 0 !important;
+ }
+
+ .pe-xxl-1 {
+ padding-right: 0.25rem !important;
+ }
+
+ .pe-xxl-2 {
+ padding-right: 0.5rem !important;
+ }
+
+ .pe-xxl-3 {
+ padding-right: 1rem !important;
+ }
+
+ .pe-xxl-4 {
+ padding-right: 1.5rem !important;
+ }
+
+ .pe-xxl-5 {
+ padding-right: 3rem !important;
+ }
+
+ .pb-xxl-0 {
+ padding-bottom: 0 !important;
+ }
+
+ .pb-xxl-1 {
+ padding-bottom: 0.25rem !important;
+ }
+
+ .pb-xxl-2 {
+ padding-bottom: 0.5rem !important;
+ }
+
+ .pb-xxl-3 {
+ padding-bottom: 1rem !important;
+ }
+
+ .pb-xxl-4 {
+ padding-bottom: 1.5rem !important;
+ }
+
+ .pb-xxl-5 {
+ padding-bottom: 3rem !important;
+ }
+
+ .ps-xxl-0 {
+ padding-left: 0 !important;
+ }
+
+ .ps-xxl-1 {
+ padding-left: 0.25rem !important;
+ }
+
+ .ps-xxl-2 {
+ padding-left: 0.5rem !important;
+ }
+
+ .ps-xxl-3 {
+ padding-left: 1rem !important;
+ }
+
+ .ps-xxl-4 {
+ padding-left: 1.5rem !important;
+ }
+
+ .ps-xxl-5 {
+ padding-left: 3rem !important;
+ }
+}
+@media print {
+ .d-print-inline {
+ display: inline !important;
+ }
+
+ .d-print-inline-block {
+ display: inline-block !important;
+ }
+
+ .d-print-block {
+ display: block !important;
+ }
+
+ .d-print-grid {
+ display: grid !important;
+ }
+
+ .d-print-table {
+ display: table !important;
+ }
+
+ .d-print-table-row {
+ display: table-row !important;
+ }
+
+ .d-print-table-cell {
+ display: table-cell !important;
+ }
+
+ .d-print-flex {
+ display: flex !important;
+ }
+
+ .d-print-inline-flex {
+ display: inline-flex !important;
+ }
+
+ .d-print-none {
+ display: none !important;
+ }
+}
+
+/*# sourceMappingURL=bootstrap-grid.css.map */
\ No newline at end of file
diff --git a/pkgs/csslib/third_party/bootstrap/bootstrap.css b/pkgs/csslib/third_party/bootstrap/bootstrap.css
new file mode 100644
index 0000000..f78e177
--- /dev/null
+++ b/pkgs/csslib/third_party/bootstrap/bootstrap.css
@@ -0,0 +1,11222 @@
+@charset "UTF-8";
+/*!
+ * Bootstrap v5.1.1 (https://getbootstrap.com/)
+ * Copyright 2011-2021 The Bootstrap Authors
+ * Copyright 2011-2021 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
+ */
+:root {
+ --bs-blue: #0d6efd;
+ --bs-indigo: #6610f2;
+ --bs-purple: #6f42c1;
+ --bs-pink: #d63384;
+ --bs-red: #dc3545;
+ --bs-orange: #fd7e14;
+ --bs-yellow: #ffc107;
+ --bs-green: #198754;
+ --bs-teal: #20c997;
+ --bs-cyan: #0dcaf0;
+ --bs-white: #fff;
+ --bs-gray: #6c757d;
+ --bs-gray-dark: #343a40;
+ --bs-gray-100: #f8f9fa;
+ --bs-gray-200: #e9ecef;
+ --bs-gray-300: #dee2e6;
+ --bs-gray-400: #ced4da;
+ --bs-gray-500: #adb5bd;
+ --bs-gray-600: #6c757d;
+ --bs-gray-700: #495057;
+ --bs-gray-800: #343a40;
+ --bs-gray-900: #212529;
+ --bs-primary: #0d6efd;
+ --bs-secondary: #6c757d;
+ --bs-success: #198754;
+ --bs-info: #0dcaf0;
+ --bs-warning: #ffc107;
+ --bs-danger: #dc3545;
+ --bs-light: #f8f9fa;
+ --bs-dark: #212529;
+ --bs-primary-rgb: 13, 110, 253;
+ --bs-secondary-rgb: 108, 117, 125;
+ --bs-success-rgb: 25, 135, 84;
+ --bs-info-rgb: 13, 202, 240;
+ --bs-warning-rgb: 255, 193, 7;
+ --bs-danger-rgb: 220, 53, 69;
+ --bs-light-rgb: 248, 249, 250;
+ --bs-dark-rgb: 33, 37, 41;
+ --bs-white-rgb: 255, 255, 255;
+ --bs-black-rgb: 0, 0, 0;
+ --bs-body-color-rgb: 33, 37, 41;
+ --bs-body-bg-rgb: 255, 255, 255;
+ --bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+ --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+ --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
+ --bs-body-font-family: var(--bs-font-sans-serif);
+ --bs-body-font-size: 1rem;
+ --bs-body-font-weight: 400;
+ --bs-body-line-height: 1.5;
+ --bs-body-color: #212529;
+ --bs-body-bg: #fff;
+}
+
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ :root {
+ scroll-behavior: smooth;
+ }
+}
+
+body {
+ margin: 0;
+ font-family: var(--bs-body-font-family);
+ font-size: var(--bs-body-font-size);
+ font-weight: var(--bs-body-font-weight);
+ line-height: var(--bs-body-line-height);
+ color: var(--bs-body-color);
+ text-align: var(--bs-body-text-align);
+ background-color: var(--bs-body-bg);
+ -webkit-text-size-adjust: 100%;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+}
+
+hr {
+ margin: 1rem 0;
+ color: inherit;
+ background-color: currentColor;
+ border: 0;
+ opacity: 0.25;
+}
+
+hr:not([size]) {
+ height: 1px;
+}
+
+h6, .h6, h5, .h5, h4, .h4, h3, .h3, h2, .h2, h1, .h1 {
+ margin-top: 0;
+ margin-bottom: 0.5rem;
+ font-weight: 500;
+ line-height: 1.2;
+}
+
+h1, .h1 {
+ font-size: calc(1.375rem + 1.5vw);
+}
+@media (min-width: 1200px) {
+ h1, .h1 {
+ font-size: 2.5rem;
+ }
+}
+
+h2, .h2 {
+ font-size: calc(1.325rem + 0.9vw);
+}
+@media (min-width: 1200px) {
+ h2, .h2 {
+ font-size: 2rem;
+ }
+}
+
+h3, .h3 {
+ font-size: calc(1.3rem + 0.6vw);
+}
+@media (min-width: 1200px) {
+ h3, .h3 {
+ font-size: 1.75rem;
+ }
+}
+
+h4, .h4 {
+ font-size: calc(1.275rem + 0.3vw);
+}
+@media (min-width: 1200px) {
+ h4, .h4 {
+ font-size: 1.5rem;
+ }
+}
+
+h5, .h5 {
+ font-size: 1.25rem;
+}
+
+h6, .h6 {
+ font-size: 1rem;
+}
+
+p {
+ margin-top: 0;
+ margin-bottom: 1rem;
+}
+
+abbr[title],
+abbr[data-bs-original-title] {
+ -webkit-text-decoration: underline dotted;
+ text-decoration: underline dotted;
+ cursor: help;
+ -webkit-text-decoration-skip-ink: none;
+ text-decoration-skip-ink: none;
+}
+
+address {
+ margin-bottom: 1rem;
+ font-style: normal;
+ line-height: inherit;
+}
+
+ol,
+ul {
+ padding-left: 2rem;
+}
+
+ol,
+ul,
+dl {
+ margin-top: 0;
+ margin-bottom: 1rem;
+}
+
+ol ol,
+ul ul,
+ol ul,
+ul ol {
+ margin-bottom: 0;
+}
+
+dt {
+ font-weight: 700;
+}
+
+dd {
+ margin-bottom: 0.5rem;
+ margin-left: 0;
+}
+
+blockquote {
+ margin: 0 0 1rem;
+}
+
+b,
+strong {
+ font-weight: bolder;
+}
+
+small, .small {
+ font-size: 0.875em;
+}
+
+mark, .mark {
+ padding: 0.2em;
+ background-color: #fcf8e3;
+}
+
+sub,
+sup {
+ position: relative;
+ font-size: 0.75em;
+ line-height: 0;
+ vertical-align: baseline;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+sup {
+ top: -0.5em;
+}
+
+a {
+ color: #0d6efd;
+ text-decoration: underline;
+}
+a:hover {
+ color: #0a58ca;
+}
+
+a:not([href]):not([class]), a:not([href]):not([class]):hover {
+ color: inherit;
+ text-decoration: none;
+}
+
+pre,
+code,
+kbd,
+samp {
+ font-family: var(--bs-font-monospace);
+ font-size: 1em;
+ direction: ltr /* rtl:ignore */;
+ unicode-bidi: bidi-override;
+}
+
+pre {
+ display: block;
+ margin-top: 0;
+ margin-bottom: 1rem;
+ overflow: auto;
+ font-size: 0.875em;
+}
+pre code {
+ font-size: inherit;
+ color: inherit;
+ word-break: normal;
+}
+
+code {
+ font-size: 0.875em;
+ color: #d63384;
+ word-wrap: break-word;
+}
+a > code {
+ color: inherit;
+}
+
+kbd {
+ padding: 0.2rem 0.4rem;
+ font-size: 0.875em;
+ color: #fff;
+ background-color: #212529;
+ border-radius: 0.2rem;
+}
+kbd kbd {
+ padding: 0;
+ font-size: 1em;
+ font-weight: 700;
+}
+
+figure {
+ margin: 0 0 1rem;
+}
+
+img,
+svg {
+ vertical-align: middle;
+}
+
+table {
+ caption-side: bottom;
+ border-collapse: collapse;
+}
+
+caption {
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+ color: #6c757d;
+ text-align: left;
+}
+
+th {
+ text-align: inherit;
+ text-align: -webkit-match-parent;
+}
+
+thead,
+tbody,
+tfoot,
+tr,
+td,
+th {
+ border-color: inherit;
+ border-style: solid;
+ border-width: 0;
+}
+
+label {
+ display: inline-block;
+}
+
+button {
+ border-radius: 0;
+}
+
+button:focus:not(:focus-visible) {
+ outline: 0;
+}
+
+input,
+button,
+select,
+optgroup,
+textarea {
+ margin: 0;
+ font-family: inherit;
+ font-size: inherit;
+ line-height: inherit;
+}
+
+button,
+select {
+ text-transform: none;
+}
+
+[role=button] {
+ cursor: pointer;
+}
+
+select {
+ word-wrap: normal;
+}
+select:disabled {
+ opacity: 1;
+}
+
+[list]::-webkit-calendar-picker-indicator {
+ display: none;
+}
+
+button,
+[type=button],
+[type=reset],
+[type=submit] {
+ -webkit-appearance: button;
+}
+button:not(:disabled),
+[type=button]:not(:disabled),
+[type=reset]:not(:disabled),
+[type=submit]:not(:disabled) {
+ cursor: pointer;
+}
+
+::-moz-focus-inner {
+ padding: 0;
+ border-style: none;
+}
+
+textarea {
+ resize: vertical;
+}
+
+fieldset {
+ min-width: 0;
+ padding: 0;
+ margin: 0;
+ border: 0;
+}
+
+legend {
+ float: left;
+ width: 100%;
+ padding: 0;
+ margin-bottom: 0.5rem;
+ font-size: calc(1.275rem + 0.3vw);
+ line-height: inherit;
+}
+@media (min-width: 1200px) {
+ legend {
+ font-size: 1.5rem;
+ }
+}
+legend + * {
+ clear: left;
+}
+
+::-webkit-datetime-edit-fields-wrapper,
+::-webkit-datetime-edit-text,
+::-webkit-datetime-edit-minute,
+::-webkit-datetime-edit-hour-field,
+::-webkit-datetime-edit-day-field,
+::-webkit-datetime-edit-month-field,
+::-webkit-datetime-edit-year-field {
+ padding: 0;
+}
+
+::-webkit-inner-spin-button {
+ height: auto;
+}
+
+[type=search] {
+ outline-offset: -2px;
+ -webkit-appearance: textfield;
+}
+
+/* rtl:raw:
+[type="tel"],
+[type="url"],
+[type="email"],
+[type="number"] {
+ direction: ltr;
+}
+*/
+::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+::-webkit-color-swatch-wrapper {
+ padding: 0;
+}
+
+::file-selector-button {
+ font: inherit;
+}
+
+::-webkit-file-upload-button {
+ font: inherit;
+ -webkit-appearance: button;
+}
+
+output {
+ display: inline-block;
+}
+
+iframe {
+ border: 0;
+}
+
+summary {
+ display: list-item;
+ cursor: pointer;
+}
+
+progress {
+ vertical-align: baseline;
+}
+
+[hidden] {
+ display: none !important;
+}
+
+.lead {
+ font-size: 1.25rem;
+ font-weight: 300;
+}
+
+.display-1 {
+ font-size: calc(1.625rem + 4.5vw);
+ font-weight: 300;
+ line-height: 1.2;
+}
+@media (min-width: 1200px) {
+ .display-1 {
+ font-size: 5rem;
+ }
+}
+
+.display-2 {
+ font-size: calc(1.575rem + 3.9vw);
+ font-weight: 300;
+ line-height: 1.2;
+}
+@media (min-width: 1200px) {
+ .display-2 {
+ font-size: 4.5rem;
+ }
+}
+
+.display-3 {
+ font-size: calc(1.525rem + 3.3vw);
+ font-weight: 300;
+ line-height: 1.2;
+}
+@media (min-width: 1200px) {
+ .display-3 {
+ font-size: 4rem;
+ }
+}
+
+.display-4 {
+ font-size: calc(1.475rem + 2.7vw);
+ font-weight: 300;
+ line-height: 1.2;
+}
+@media (min-width: 1200px) {
+ .display-4 {
+ font-size: 3.5rem;
+ }
+}
+
+.display-5 {
+ font-size: calc(1.425rem + 2.1vw);
+ font-weight: 300;
+ line-height: 1.2;
+}
+@media (min-width: 1200px) {
+ .display-5 {
+ font-size: 3rem;
+ }
+}
+
+.display-6 {
+ font-size: calc(1.375rem + 1.5vw);
+ font-weight: 300;
+ line-height: 1.2;
+}
+@media (min-width: 1200px) {
+ .display-6 {
+ font-size: 2.5rem;
+ }
+}
+
+.list-unstyled {
+ padding-left: 0;
+ list-style: none;
+}
+
+.list-inline {
+ padding-left: 0;
+ list-style: none;
+}
+
+.list-inline-item {
+ display: inline-block;
+}
+.list-inline-item:not(:last-child) {
+ margin-right: 0.5rem;
+}
+
+.initialism {
+ font-size: 0.875em;
+ text-transform: uppercase;
+}
+
+.blockquote {
+ margin-bottom: 1rem;
+ font-size: 1.25rem;
+}
+.blockquote > :last-child {
+ margin-bottom: 0;
+}
+
+.blockquote-footer {
+ margin-top: -1rem;
+ margin-bottom: 1rem;
+ font-size: 0.875em;
+ color: #6c757d;
+}
+.blockquote-footer::before {
+ content: "— ";
+}
+
+.img-fluid {
+ max-width: 100%;
+ height: auto;
+}
+
+.img-thumbnail {
+ padding: 0.25rem;
+ background-color: #fff;
+ border: 1px solid #dee2e6;
+ border-radius: 0.25rem;
+ max-width: 100%;
+ height: auto;
+}
+
+.figure {
+ display: inline-block;
+}
+
+.figure-img {
+ margin-bottom: 0.5rem;
+ line-height: 1;
+}
+
+.figure-caption {
+ font-size: 0.875em;
+ color: #6c757d;
+}
+
+.container,
+.container-fluid,
+.container-xxl,
+.container-xl,
+.container-lg,
+.container-md,
+.container-sm {
+ width: 100%;
+ padding-right: var(--bs-gutter-x, 0.75rem);
+ padding-left: var(--bs-gutter-x, 0.75rem);
+ margin-right: auto;
+ margin-left: auto;
+}
+
+@media (min-width: 576px) {
+ .container-sm, .container {
+ max-width: 540px;
+ }
+}
+@media (min-width: 768px) {
+ .container-md, .container-sm, .container {
+ max-width: 720px;
+ }
+}
+@media (min-width: 992px) {
+ .container-lg, .container-md, .container-sm, .container {
+ max-width: 960px;
+ }
+}
+@media (min-width: 1200px) {
+ .container-xl, .container-lg, .container-md, .container-sm, .container {
+ max-width: 1140px;
+ }
+}
+@media (min-width: 1400px) {
+ .container-xxl, .container-xl, .container-lg, .container-md, .container-sm, .container {
+ max-width: 1320px;
+ }
+}
+.row {
+ --bs-gutter-x: 1.5rem;
+ --bs-gutter-y: 0;
+ display: flex;
+ flex-wrap: wrap;
+ margin-top: calc(var(--bs-gutter-y) * -1);
+ margin-right: calc(var(--bs-gutter-x) * -.5);
+ margin-left: calc(var(--bs-gutter-x) * -.5);
+}
+.row > * {
+ flex-shrink: 0;
+ width: 100%;
+ max-width: 100%;
+ padding-right: calc(var(--bs-gutter-x) * .5);
+ padding-left: calc(var(--bs-gutter-x) * .5);
+ margin-top: var(--bs-gutter-y);
+}
+
+.col {
+ flex: 1 0 0%;
+}
+
+.row-cols-auto > * {
+ flex: 0 0 auto;
+ width: auto;
+}
+
+.row-cols-1 > * {
+ flex: 0 0 auto;
+ width: 100%;
+}
+
+.row-cols-2 > * {
+ flex: 0 0 auto;
+ width: 50%;
+}
+
+.row-cols-3 > * {
+ flex: 0 0 auto;
+ width: 33.3333333333%;
+}
+
+.row-cols-4 > * {
+ flex: 0 0 auto;
+ width: 25%;
+}
+
+.row-cols-5 > * {
+ flex: 0 0 auto;
+ width: 20%;
+}
+
+.row-cols-6 > * {
+ flex: 0 0 auto;
+ width: 16.6666666667%;
+}
+
+.col-auto {
+ flex: 0 0 auto;
+ width: auto;
+}
+
+.col-1 {
+ flex: 0 0 auto;
+ width: 8.33333333%;
+}
+
+.col-2 {
+ flex: 0 0 auto;
+ width: 16.66666667%;
+}
+
+.col-3 {
+ flex: 0 0 auto;
+ width: 25%;
+}
+
+.col-4 {
+ flex: 0 0 auto;
+ width: 33.33333333%;
+}
+
+.col-5 {
+ flex: 0 0 auto;
+ width: 41.66666667%;
+}
+
+.col-6 {
+ flex: 0 0 auto;
+ width: 50%;
+}
+
+.col-7 {
+ flex: 0 0 auto;
+ width: 58.33333333%;
+}
+
+.col-8 {
+ flex: 0 0 auto;
+ width: 66.66666667%;
+}
+
+.col-9 {
+ flex: 0 0 auto;
+ width: 75%;
+}
+
+.col-10 {
+ flex: 0 0 auto;
+ width: 83.33333333%;
+}
+
+.col-11 {
+ flex: 0 0 auto;
+ width: 91.66666667%;
+}
+
+.col-12 {
+ flex: 0 0 auto;
+ width: 100%;
+}
+
+.offset-1 {
+ margin-left: 8.33333333%;
+}
+
+.offset-2 {
+ margin-left: 16.66666667%;
+}
+
+.offset-3 {
+ margin-left: 25%;
+}
+
+.offset-4 {
+ margin-left: 33.33333333%;
+}
+
+.offset-5 {
+ margin-left: 41.66666667%;
+}
+
+.offset-6 {
+ margin-left: 50%;
+}
+
+.offset-7 {
+ margin-left: 58.33333333%;
+}
+
+.offset-8 {
+ margin-left: 66.66666667%;
+}
+
+.offset-9 {
+ margin-left: 75%;
+}
+
+.offset-10 {
+ margin-left: 83.33333333%;
+}
+
+.offset-11 {
+ margin-left: 91.66666667%;
+}
+
+.g-0,
+.gx-0 {
+ --bs-gutter-x: 0;
+}
+
+.g-0,
+.gy-0 {
+ --bs-gutter-y: 0;
+}
+
+.g-1,
+.gx-1 {
+ --bs-gutter-x: 0.25rem;
+}
+
+.g-1,
+.gy-1 {
+ --bs-gutter-y: 0.25rem;
+}
+
+.g-2,
+.gx-2 {
+ --bs-gutter-x: 0.5rem;
+}
+
+.g-2,
+.gy-2 {
+ --bs-gutter-y: 0.5rem;
+}
+
+.g-3,
+.gx-3 {
+ --bs-gutter-x: 1rem;
+}
+
+.g-3,
+.gy-3 {
+ --bs-gutter-y: 1rem;
+}
+
+.g-4,
+.gx-4 {
+ --bs-gutter-x: 1.5rem;
+}
+
+.g-4,
+.gy-4 {
+ --bs-gutter-y: 1.5rem;
+}
+
+.g-5,
+.gx-5 {
+ --bs-gutter-x: 3rem;
+}
+
+.g-5,
+.gy-5 {
+ --bs-gutter-y: 3rem;
+}
+
+@media (min-width: 576px) {
+ .col-sm {
+ flex: 1 0 0%;
+ }
+
+ .row-cols-sm-auto > * {
+ flex: 0 0 auto;
+ width: auto;
+ }
+
+ .row-cols-sm-1 > * {
+ flex: 0 0 auto;
+ width: 100%;
+ }
+
+ .row-cols-sm-2 > * {
+ flex: 0 0 auto;
+ width: 50%;
+ }
+
+ .row-cols-sm-3 > * {
+ flex: 0 0 auto;
+ width: 33.3333333333%;
+ }
+
+ .row-cols-sm-4 > * {
+ flex: 0 0 auto;
+ width: 25%;
+ }
+
+ .row-cols-sm-5 > * {
+ flex: 0 0 auto;
+ width: 20%;
+ }
+
+ .row-cols-sm-6 > * {
+ flex: 0 0 auto;
+ width: 16.6666666667%;
+ }
+
+ .col-sm-auto {
+ flex: 0 0 auto;
+ width: auto;
+ }
+
+ .col-sm-1 {
+ flex: 0 0 auto;
+ width: 8.33333333%;
+ }
+
+ .col-sm-2 {
+ flex: 0 0 auto;
+ width: 16.66666667%;
+ }
+
+ .col-sm-3 {
+ flex: 0 0 auto;
+ width: 25%;
+ }
+
+ .col-sm-4 {
+ flex: 0 0 auto;
+ width: 33.33333333%;
+ }
+
+ .col-sm-5 {
+ flex: 0 0 auto;
+ width: 41.66666667%;
+ }
+
+ .col-sm-6 {
+ flex: 0 0 auto;
+ width: 50%;
+ }
+
+ .col-sm-7 {
+ flex: 0 0 auto;
+ width: 58.33333333%;
+ }
+
+ .col-sm-8 {
+ flex: 0 0 auto;
+ width: 66.66666667%;
+ }
+
+ .col-sm-9 {
+ flex: 0 0 auto;
+ width: 75%;
+ }
+
+ .col-sm-10 {
+ flex: 0 0 auto;
+ width: 83.33333333%;
+ }
+
+ .col-sm-11 {
+ flex: 0 0 auto;
+ width: 91.66666667%;
+ }
+
+ .col-sm-12 {
+ flex: 0 0 auto;
+ width: 100%;
+ }
+
+ .offset-sm-0 {
+ margin-left: 0;
+ }
+
+ .offset-sm-1 {
+ margin-left: 8.33333333%;
+ }
+
+ .offset-sm-2 {
+ margin-left: 16.66666667%;
+ }
+
+ .offset-sm-3 {
+ margin-left: 25%;
+ }
+
+ .offset-sm-4 {
+ margin-left: 33.33333333%;
+ }
+
+ .offset-sm-5 {
+ margin-left: 41.66666667%;
+ }
+
+ .offset-sm-6 {
+ margin-left: 50%;
+ }
+
+ .offset-sm-7 {
+ margin-left: 58.33333333%;
+ }
+
+ .offset-sm-8 {
+ margin-left: 66.66666667%;
+ }
+
+ .offset-sm-9 {
+ margin-left: 75%;
+ }
+
+ .offset-sm-10 {
+ margin-left: 83.33333333%;
+ }
+
+ .offset-sm-11 {
+ margin-left: 91.66666667%;
+ }
+
+ .g-sm-0,
+.gx-sm-0 {
+ --bs-gutter-x: 0;
+ }
+
+ .g-sm-0,
+.gy-sm-0 {
+ --bs-gutter-y: 0;
+ }
+
+ .g-sm-1,
+.gx-sm-1 {
+ --bs-gutter-x: 0.25rem;
+ }
+
+ .g-sm-1,
+.gy-sm-1 {
+ --bs-gutter-y: 0.25rem;
+ }
+
+ .g-sm-2,
+.gx-sm-2 {
+ --bs-gutter-x: 0.5rem;
+ }
+
+ .g-sm-2,
+.gy-sm-2 {
+ --bs-gutter-y: 0.5rem;
+ }
+
+ .g-sm-3,
+.gx-sm-3 {
+ --bs-gutter-x: 1rem;
+ }
+
+ .g-sm-3,
+.gy-sm-3 {
+ --bs-gutter-y: 1rem;
+ }
+
+ .g-sm-4,
+.gx-sm-4 {
+ --bs-gutter-x: 1.5rem;
+ }
+
+ .g-sm-4,
+.gy-sm-4 {
+ --bs-gutter-y: 1.5rem;
+ }
+
+ .g-sm-5,
+.gx-sm-5 {
+ --bs-gutter-x: 3rem;
+ }
+
+ .g-sm-5,
+.gy-sm-5 {
+ --bs-gutter-y: 3rem;
+ }
+}
+@media (min-width: 768px) {
+ .col-md {
+ flex: 1 0 0%;
+ }
+
+ .row-cols-md-auto > * {
+ flex: 0 0 auto;
+ width: auto;
+ }
+
+ .row-cols-md-1 > * {
+ flex: 0 0 auto;
+ width: 100%;
+ }
+
+ .row-cols-md-2 > * {
+ flex: 0 0 auto;
+ width: 50%;
+ }
+
+ .row-cols-md-3 > * {
+ flex: 0 0 auto;
+ width: 33.3333333333%;
+ }
+
+ .row-cols-md-4 > * {
+ flex: 0 0 auto;
+ width: 25%;
+ }
+
+ .row-cols-md-5 > * {
+ flex: 0 0 auto;
+ width: 20%;
+ }
+
+ .row-cols-md-6 > * {
+ flex: 0 0 auto;
+ width: 16.6666666667%;
+ }
+
+ .col-md-auto {
+ flex: 0 0 auto;
+ width: auto;
+ }
+
+ .col-md-1 {
+ flex: 0 0 auto;
+ width: 8.33333333%;
+ }
+
+ .col-md-2 {
+ flex: 0 0 auto;
+ width: 16.66666667%;
+ }
+
+ .col-md-3 {
+ flex: 0 0 auto;
+ width: 25%;
+ }
+
+ .col-md-4 {
+ flex: 0 0 auto;
+ width: 33.33333333%;
+ }
+
+ .col-md-5 {
+ flex: 0 0 auto;
+ width: 41.66666667%;
+ }
+
+ .col-md-6 {
+ flex: 0 0 auto;
+ width: 50%;
+ }
+
+ .col-md-7 {
+ flex: 0 0 auto;
+ width: 58.33333333%;
+ }
+
+ .col-md-8 {
+ flex: 0 0 auto;
+ width: 66.66666667%;
+ }
+
+ .col-md-9 {
+ flex: 0 0 auto;
+ width: 75%;
+ }
+
+ .col-md-10 {
+ flex: 0 0 auto;
+ width: 83.33333333%;
+ }
+
+ .col-md-11 {
+ flex: 0 0 auto;
+ width: 91.66666667%;
+ }
+
+ .col-md-12 {
+ flex: 0 0 auto;
+ width: 100%;
+ }
+
+ .offset-md-0 {
+ margin-left: 0;
+ }
+
+ .offset-md-1 {
+ margin-left: 8.33333333%;
+ }
+
+ .offset-md-2 {
+ margin-left: 16.66666667%;
+ }
+
+ .offset-md-3 {
+ margin-left: 25%;
+ }
+
+ .offset-md-4 {
+ margin-left: 33.33333333%;
+ }
+
+ .offset-md-5 {
+ margin-left: 41.66666667%;
+ }
+
+ .offset-md-6 {
+ margin-left: 50%;
+ }
+
+ .offset-md-7 {
+ margin-left: 58.33333333%;
+ }
+
+ .offset-md-8 {
+ margin-left: 66.66666667%;
+ }
+
+ .offset-md-9 {
+ margin-left: 75%;
+ }
+
+ .offset-md-10 {
+ margin-left: 83.33333333%;
+ }
+
+ .offset-md-11 {
+ margin-left: 91.66666667%;
+ }
+
+ .g-md-0,
+.gx-md-0 {
+ --bs-gutter-x: 0;
+ }
+
+ .g-md-0,
+.gy-md-0 {
+ --bs-gutter-y: 0;
+ }
+
+ .g-md-1,
+.gx-md-1 {
+ --bs-gutter-x: 0.25rem;
+ }
+
+ .g-md-1,
+.gy-md-1 {
+ --bs-gutter-y: 0.25rem;
+ }
+
+ .g-md-2,
+.gx-md-2 {
+ --bs-gutter-x: 0.5rem;
+ }
+
+ .g-md-2,
+.gy-md-2 {
+ --bs-gutter-y: 0.5rem;
+ }
+
+ .g-md-3,
+.gx-md-3 {
+ --bs-gutter-x: 1rem;
+ }
+
+ .g-md-3,
+.gy-md-3 {
+ --bs-gutter-y: 1rem;
+ }
+
+ .g-md-4,
+.gx-md-4 {
+ --bs-gutter-x: 1.5rem;
+ }
+
+ .g-md-4,
+.gy-md-4 {
+ --bs-gutter-y: 1.5rem;
+ }
+
+ .g-md-5,
+.gx-md-5 {
+ --bs-gutter-x: 3rem;
+ }
+
+ .g-md-5,
+.gy-md-5 {
+ --bs-gutter-y: 3rem;
+ }
+}
+@media (min-width: 992px) {
+ .col-lg {
+ flex: 1 0 0%;
+ }
+
+ .row-cols-lg-auto > * {
+ flex: 0 0 auto;
+ width: auto;
+ }
+
+ .row-cols-lg-1 > * {
+ flex: 0 0 auto;
+ width: 100%;
+ }
+
+ .row-cols-lg-2 > * {
+ flex: 0 0 auto;
+ width: 50%;
+ }
+
+ .row-cols-lg-3 > * {
+ flex: 0 0 auto;
+ width: 33.3333333333%;
+ }
+
+ .row-cols-lg-4 > * {
+ flex: 0 0 auto;
+ width: 25%;
+ }
+
+ .row-cols-lg-5 > * {
+ flex: 0 0 auto;
+ width: 20%;
+ }
+
+ .row-cols-lg-6 > * {
+ flex: 0 0 auto;
+ width: 16.6666666667%;
+ }
+
+ .col-lg-auto {
+ flex: 0 0 auto;
+ width: auto;
+ }
+
+ .col-lg-1 {
+ flex: 0 0 auto;
+ width: 8.33333333%;
+ }
+
+ .col-lg-2 {
+ flex: 0 0 auto;
+ width: 16.66666667%;
+ }
+
+ .col-lg-3 {
+ flex: 0 0 auto;
+ width: 25%;
+ }
+
+ .col-lg-4 {
+ flex: 0 0 auto;
+ width: 33.33333333%;
+ }
+
+ .col-lg-5 {
+ flex: 0 0 auto;
+ width: 41.66666667%;
+ }
+
+ .col-lg-6 {
+ flex: 0 0 auto;
+ width: 50%;
+ }
+
+ .col-lg-7 {
+ flex: 0 0 auto;
+ width: 58.33333333%;
+ }
+
+ .col-lg-8 {
+ flex: 0 0 auto;
+ width: 66.66666667%;
+ }
+
+ .col-lg-9 {
+ flex: 0 0 auto;
+ width: 75%;
+ }
+
+ .col-lg-10 {
+ flex: 0 0 auto;
+ width: 83.33333333%;
+ }
+
+ .col-lg-11 {
+ flex: 0 0 auto;
+ width: 91.66666667%;
+ }
+
+ .col-lg-12 {
+ flex: 0 0 auto;
+ width: 100%;
+ }
+
+ .offset-lg-0 {
+ margin-left: 0;
+ }
+
+ .offset-lg-1 {
+ margin-left: 8.33333333%;
+ }
+
+ .offset-lg-2 {
+ margin-left: 16.66666667%;
+ }
+
+ .offset-lg-3 {
+ margin-left: 25%;
+ }
+
+ .offset-lg-4 {
+ margin-left: 33.33333333%;
+ }
+
+ .offset-lg-5 {
+ margin-left: 41.66666667%;
+ }
+
+ .offset-lg-6 {
+ margin-left: 50%;
+ }
+
+ .offset-lg-7 {
+ margin-left: 58.33333333%;
+ }
+
+ .offset-lg-8 {
+ margin-left: 66.66666667%;
+ }
+
+ .offset-lg-9 {
+ margin-left: 75%;
+ }
+
+ .offset-lg-10 {
+ margin-left: 83.33333333%;
+ }
+
+ .offset-lg-11 {
+ margin-left: 91.66666667%;
+ }
+
+ .g-lg-0,
+.gx-lg-0 {
+ --bs-gutter-x: 0;
+ }
+
+ .g-lg-0,
+.gy-lg-0 {
+ --bs-gutter-y: 0;
+ }
+
+ .g-lg-1,
+.gx-lg-1 {
+ --bs-gutter-x: 0.25rem;
+ }
+
+ .g-lg-1,
+.gy-lg-1 {
+ --bs-gutter-y: 0.25rem;
+ }
+
+ .g-lg-2,
+.gx-lg-2 {
+ --bs-gutter-x: 0.5rem;
+ }
+
+ .g-lg-2,
+.gy-lg-2 {
+ --bs-gutter-y: 0.5rem;
+ }
+
+ .g-lg-3,
+.gx-lg-3 {
+ --bs-gutter-x: 1rem;
+ }
+
+ .g-lg-3,
+.gy-lg-3 {
+ --bs-gutter-y: 1rem;
+ }
+
+ .g-lg-4,
+.gx-lg-4 {
+ --bs-gutter-x: 1.5rem;
+ }
+
+ .g-lg-4,
+.gy-lg-4 {
+ --bs-gutter-y: 1.5rem;
+ }
+
+ .g-lg-5,
+.gx-lg-5 {
+ --bs-gutter-x: 3rem;
+ }
+
+ .g-lg-5,
+.gy-lg-5 {
+ --bs-gutter-y: 3rem;
+ }
+}
+@media (min-width: 1200px) {
+ .col-xl {
+ flex: 1 0 0%;
+ }
+
+ .row-cols-xl-auto > * {
+ flex: 0 0 auto;
+ width: auto;
+ }
+
+ .row-cols-xl-1 > * {
+ flex: 0 0 auto;
+ width: 100%;
+ }
+
+ .row-cols-xl-2 > * {
+ flex: 0 0 auto;
+ width: 50%;
+ }
+
+ .row-cols-xl-3 > * {
+ flex: 0 0 auto;
+ width: 33.3333333333%;
+ }
+
+ .row-cols-xl-4 > * {
+ flex: 0 0 auto;
+ width: 25%;
+ }
+
+ .row-cols-xl-5 > * {
+ flex: 0 0 auto;
+ width: 20%;
+ }
+
+ .row-cols-xl-6 > * {
+ flex: 0 0 auto;
+ width: 16.6666666667%;
+ }
+
+ .col-xl-auto {
+ flex: 0 0 auto;
+ width: auto;
+ }
+
+ .col-xl-1 {
+ flex: 0 0 auto;
+ width: 8.33333333%;
+ }
+
+ .col-xl-2 {
+ flex: 0 0 auto;
+ width: 16.66666667%;
+ }
+
+ .col-xl-3 {
+ flex: 0 0 auto;
+ width: 25%;
+ }
+
+ .col-xl-4 {
+ flex: 0 0 auto;
+ width: 33.33333333%;
+ }
+
+ .col-xl-5 {
+ flex: 0 0 auto;
+ width: 41.66666667%;
+ }
+
+ .col-xl-6 {
+ flex: 0 0 auto;
+ width: 50%;
+ }
+
+ .col-xl-7 {
+ flex: 0 0 auto;
+ width: 58.33333333%;
+ }
+
+ .col-xl-8 {
+ flex: 0 0 auto;
+ width: 66.66666667%;
+ }
+
+ .col-xl-9 {
+ flex: 0 0 auto;
+ width: 75%;
+ }
+
+ .col-xl-10 {
+ flex: 0 0 auto;
+ width: 83.33333333%;
+ }
+
+ .col-xl-11 {
+ flex: 0 0 auto;
+ width: 91.66666667%;
+ }
+
+ .col-xl-12 {
+ flex: 0 0 auto;
+ width: 100%;
+ }
+
+ .offset-xl-0 {
+ margin-left: 0;
+ }
+
+ .offset-xl-1 {
+ margin-left: 8.33333333%;
+ }
+
+ .offset-xl-2 {
+ margin-left: 16.66666667%;
+ }
+
+ .offset-xl-3 {
+ margin-left: 25%;
+ }
+
+ .offset-xl-4 {
+ margin-left: 33.33333333%;
+ }
+
+ .offset-xl-5 {
+ margin-left: 41.66666667%;
+ }
+
+ .offset-xl-6 {
+ margin-left: 50%;
+ }
+
+ .offset-xl-7 {
+ margin-left: 58.33333333%;
+ }
+
+ .offset-xl-8 {
+ margin-left: 66.66666667%;
+ }
+
+ .offset-xl-9 {
+ margin-left: 75%;
+ }
+
+ .offset-xl-10 {
+ margin-left: 83.33333333%;
+ }
+
+ .offset-xl-11 {
+ margin-left: 91.66666667%;
+ }
+
+ .g-xl-0,
+.gx-xl-0 {
+ --bs-gutter-x: 0;
+ }
+
+ .g-xl-0,
+.gy-xl-0 {
+ --bs-gutter-y: 0;
+ }
+
+ .g-xl-1,
+.gx-xl-1 {
+ --bs-gutter-x: 0.25rem;
+ }
+
+ .g-xl-1,
+.gy-xl-1 {
+ --bs-gutter-y: 0.25rem;
+ }
+
+ .g-xl-2,
+.gx-xl-2 {
+ --bs-gutter-x: 0.5rem;
+ }
+
+ .g-xl-2,
+.gy-xl-2 {
+ --bs-gutter-y: 0.5rem;
+ }
+
+ .g-xl-3,
+.gx-xl-3 {
+ --bs-gutter-x: 1rem;
+ }
+
+ .g-xl-3,
+.gy-xl-3 {
+ --bs-gutter-y: 1rem;
+ }
+
+ .g-xl-4,
+.gx-xl-4 {
+ --bs-gutter-x: 1.5rem;
+ }
+
+ .g-xl-4,
+.gy-xl-4 {
+ --bs-gutter-y: 1.5rem;
+ }
+
+ .g-xl-5,
+.gx-xl-5 {
+ --bs-gutter-x: 3rem;
+ }
+
+ .g-xl-5,
+.gy-xl-5 {
+ --bs-gutter-y: 3rem;
+ }
+}
+@media (min-width: 1400px) {
+ .col-xxl {
+ flex: 1 0 0%;
+ }
+
+ .row-cols-xxl-auto > * {
+ flex: 0 0 auto;
+ width: auto;
+ }
+
+ .row-cols-xxl-1 > * {
+ flex: 0 0 auto;
+ width: 100%;
+ }
+
+ .row-cols-xxl-2 > * {
+ flex: 0 0 auto;
+ width: 50%;
+ }
+
+ .row-cols-xxl-3 > * {
+ flex: 0 0 auto;
+ width: 33.3333333333%;
+ }
+
+ .row-cols-xxl-4 > * {
+ flex: 0 0 auto;
+ width: 25%;
+ }
+
+ .row-cols-xxl-5 > * {
+ flex: 0 0 auto;
+ width: 20%;
+ }
+
+ .row-cols-xxl-6 > * {
+ flex: 0 0 auto;
+ width: 16.6666666667%;
+ }
+
+ .col-xxl-auto {
+ flex: 0 0 auto;
+ width: auto;
+ }
+
+ .col-xxl-1 {
+ flex: 0 0 auto;
+ width: 8.33333333%;
+ }
+
+ .col-xxl-2 {
+ flex: 0 0 auto;
+ width: 16.66666667%;
+ }
+
+ .col-xxl-3 {
+ flex: 0 0 auto;
+ width: 25%;
+ }
+
+ .col-xxl-4 {
+ flex: 0 0 auto;
+ width: 33.33333333%;
+ }
+
+ .col-xxl-5 {
+ flex: 0 0 auto;
+ width: 41.66666667%;
+ }
+
+ .col-xxl-6 {
+ flex: 0 0 auto;
+ width: 50%;
+ }
+
+ .col-xxl-7 {
+ flex: 0 0 auto;
+ width: 58.33333333%;
+ }
+
+ .col-xxl-8 {
+ flex: 0 0 auto;
+ width: 66.66666667%;
+ }
+
+ .col-xxl-9 {
+ flex: 0 0 auto;
+ width: 75%;
+ }
+
+ .col-xxl-10 {
+ flex: 0 0 auto;
+ width: 83.33333333%;
+ }
+
+ .col-xxl-11 {
+ flex: 0 0 auto;
+ width: 91.66666667%;
+ }
+
+ .col-xxl-12 {
+ flex: 0 0 auto;
+ width: 100%;
+ }
+
+ .offset-xxl-0 {
+ margin-left: 0;
+ }
+
+ .offset-xxl-1 {
+ margin-left: 8.33333333%;
+ }
+
+ .offset-xxl-2 {
+ margin-left: 16.66666667%;
+ }
+
+ .offset-xxl-3 {
+ margin-left: 25%;
+ }
+
+ .offset-xxl-4 {
+ margin-left: 33.33333333%;
+ }
+
+ .offset-xxl-5 {
+ margin-left: 41.66666667%;
+ }
+
+ .offset-xxl-6 {
+ margin-left: 50%;
+ }
+
+ .offset-xxl-7 {
+ margin-left: 58.33333333%;
+ }
+
+ .offset-xxl-8 {
+ margin-left: 66.66666667%;
+ }
+
+ .offset-xxl-9 {
+ margin-left: 75%;
+ }
+
+ .offset-xxl-10 {
+ margin-left: 83.33333333%;
+ }
+
+ .offset-xxl-11 {
+ margin-left: 91.66666667%;
+ }
+
+ .g-xxl-0,
+.gx-xxl-0 {
+ --bs-gutter-x: 0;
+ }
+
+ .g-xxl-0,
+.gy-xxl-0 {
+ --bs-gutter-y: 0;
+ }
+
+ .g-xxl-1,
+.gx-xxl-1 {
+ --bs-gutter-x: 0.25rem;
+ }
+
+ .g-xxl-1,
+.gy-xxl-1 {
+ --bs-gutter-y: 0.25rem;
+ }
+
+ .g-xxl-2,
+.gx-xxl-2 {
+ --bs-gutter-x: 0.5rem;
+ }
+
+ .g-xxl-2,
+.gy-xxl-2 {
+ --bs-gutter-y: 0.5rem;
+ }
+
+ .g-xxl-3,
+.gx-xxl-3 {
+ --bs-gutter-x: 1rem;
+ }
+
+ .g-xxl-3,
+.gy-xxl-3 {
+ --bs-gutter-y: 1rem;
+ }
+
+ .g-xxl-4,
+.gx-xxl-4 {
+ --bs-gutter-x: 1.5rem;
+ }
+
+ .g-xxl-4,
+.gy-xxl-4 {
+ --bs-gutter-y: 1.5rem;
+ }
+
+ .g-xxl-5,
+.gx-xxl-5 {
+ --bs-gutter-x: 3rem;
+ }
+
+ .g-xxl-5,
+.gy-xxl-5 {
+ --bs-gutter-y: 3rem;
+ }
+}
+.table {
+ --bs-table-bg: transparent;
+ --bs-table-accent-bg: transparent;
+ --bs-table-striped-color: #212529;
+ --bs-table-striped-bg: rgba(0, 0, 0, 0.05);
+ --bs-table-active-color: #212529;
+ --bs-table-active-bg: rgba(0, 0, 0, 0.1);
+ --bs-table-hover-color: #212529;
+ --bs-table-hover-bg: rgba(0, 0, 0, 0.075);
+ width: 100%;
+ margin-bottom: 1rem;
+ color: #212529;
+ vertical-align: top;
+ border-color: #dee2e6;
+}
+.table > :not(caption) > * > * {
+ padding: 0.5rem 0.5rem;
+ background-color: var(--bs-table-bg);
+ border-bottom-width: 1px;
+ box-shadow: inset 0 0 0 9999px var(--bs-table-accent-bg);
+}
+.table > tbody {
+ vertical-align: inherit;
+}
+.table > thead {
+ vertical-align: bottom;
+}
+.table > :not(:last-child) > :last-child > * {
+ border-bottom-color: currentColor;
+}
+
+.caption-top {
+ caption-side: top;
+}
+
+.table-sm > :not(caption) > * > * {
+ padding: 0.25rem 0.25rem;
+}
+
+.table-bordered > :not(caption) > * {
+ border-width: 1px 0;
+}
+.table-bordered > :not(caption) > * > * {
+ border-width: 0 1px;
+}
+
+.table-borderless > :not(caption) > * > * {
+ border-bottom-width: 0;
+}
+
+.table-striped > tbody > tr:nth-of-type(odd) {
+ --bs-table-accent-bg: var(--bs-table-striped-bg);
+ color: var(--bs-table-striped-color);
+}
+
+.table-active {
+ --bs-table-accent-bg: var(--bs-table-active-bg);
+ color: var(--bs-table-active-color);
+}
+
+.table-hover > tbody > tr:hover {
+ --bs-table-accent-bg: var(--bs-table-hover-bg);
+ color: var(--bs-table-hover-color);
+}
+
+.table-primary {
+ --bs-table-bg: #cfe2ff;
+ --bs-table-striped-bg: #c5d7f2;
+ --bs-table-striped-color: #000;
+ --bs-table-active-bg: #bacbe6;
+ --bs-table-active-color: #000;
+ --bs-table-hover-bg: #bfd1ec;
+ --bs-table-hover-color: #000;
+ color: #000;
+ border-color: #bacbe6;
+}
+
+.table-secondary {
+ --bs-table-bg: #e2e3e5;
+ --bs-table-striped-bg: #d7d8da;
+ --bs-table-striped-color: #000;
+ --bs-table-active-bg: #cbccce;
+ --bs-table-active-color: #000;
+ --bs-table-hover-bg: #d1d2d4;
+ --bs-table-hover-color: #000;
+ color: #000;
+ border-color: #cbccce;
+}
+
+.table-success {
+ --bs-table-bg: #d1e7dd;
+ --bs-table-striped-bg: #c7dbd2;
+ --bs-table-striped-color: #000;
+ --bs-table-active-bg: #bcd0c7;
+ --bs-table-active-color: #000;
+ --bs-table-hover-bg: #c1d6cc;
+ --bs-table-hover-color: #000;
+ color: #000;
+ border-color: #bcd0c7;
+}
+
+.table-info {
+ --bs-table-bg: #cff4fc;
+ --bs-table-striped-bg: #c5e8ef;
+ --bs-table-striped-color: #000;
+ --bs-table-active-bg: #badce3;
+ --bs-table-active-color: #000;
+ --bs-table-hover-bg: #bfe2e9;
+ --bs-table-hover-color: #000;
+ color: #000;
+ border-color: #badce3;
+}
+
+.table-warning {
+ --bs-table-bg: #fff3cd;
+ --bs-table-striped-bg: #f2e7c3;
+ --bs-table-striped-color: #000;
+ --bs-table-active-bg: #e6dbb9;
+ --bs-table-active-color: #000;
+ --bs-table-hover-bg: #ece1be;
+ --bs-table-hover-color: #000;
+ color: #000;
+ border-color: #e6dbb9;
+}
+
+.table-danger {
+ --bs-table-bg: #f8d7da;
+ --bs-table-striped-bg: #eccccf;
+ --bs-table-striped-color: #000;
+ --bs-table-active-bg: #dfc2c4;
+ --bs-table-active-color: #000;
+ --bs-table-hover-bg: #e5c7ca;
+ --bs-table-hover-color: #000;
+ color: #000;
+ border-color: #dfc2c4;
+}
+
+.table-light {
+ --bs-table-bg: #f8f9fa;
+ --bs-table-striped-bg: #ecedee;
+ --bs-table-striped-color: #000;
+ --bs-table-active-bg: #dfe0e1;
+ --bs-table-active-color: #000;
+ --bs-table-hover-bg: #e5e6e7;
+ --bs-table-hover-color: #000;
+ color: #000;
+ border-color: #dfe0e1;
+}
+
+.table-dark {
+ --bs-table-bg: #212529;
+ --bs-table-striped-bg: #2c3034;
+ --bs-table-striped-color: #fff;
+ --bs-table-active-bg: #373b3e;
+ --bs-table-active-color: #fff;
+ --bs-table-hover-bg: #323539;
+ --bs-table-hover-color: #fff;
+ color: #fff;
+ border-color: #373b3e;
+}
+
+.table-responsive {
+ overflow-x: auto;
+ -webkit-overflow-scrolling: touch;
+}
+
+@media (max-width: 575.98px) {
+ .table-responsive-sm {
+ overflow-x: auto;
+ -webkit-overflow-scrolling: touch;
+ }
+}
+@media (max-width: 767.98px) {
+ .table-responsive-md {
+ overflow-x: auto;
+ -webkit-overflow-scrolling: touch;
+ }
+}
+@media (max-width: 991.98px) {
+ .table-responsive-lg {
+ overflow-x: auto;
+ -webkit-overflow-scrolling: touch;
+ }
+}
+@media (max-width: 1199.98px) {
+ .table-responsive-xl {
+ overflow-x: auto;
+ -webkit-overflow-scrolling: touch;
+ }
+}
+@media (max-width: 1399.98px) {
+ .table-responsive-xxl {
+ overflow-x: auto;
+ -webkit-overflow-scrolling: touch;
+ }
+}
+.form-label {
+ margin-bottom: 0.5rem;
+}
+
+.col-form-label {
+ padding-top: calc(0.375rem + 1px);
+ padding-bottom: calc(0.375rem + 1px);
+ margin-bottom: 0;
+ font-size: inherit;
+ line-height: 1.5;
+}
+
+.col-form-label-lg {
+ padding-top: calc(0.5rem + 1px);
+ padding-bottom: calc(0.5rem + 1px);
+ font-size: 1.25rem;
+}
+
+.col-form-label-sm {
+ padding-top: calc(0.25rem + 1px);
+ padding-bottom: calc(0.25rem + 1px);
+ font-size: 0.875rem;
+}
+
+.form-text {
+ margin-top: 0.25rem;
+ font-size: 0.875em;
+ color: #6c757d;
+}
+
+.form-control {
+ display: block;
+ width: 100%;
+ padding: 0.375rem 0.75rem;
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 1.5;
+ color: #212529;
+ background-color: #fff;
+ background-clip: padding-box;
+ border: 1px solid #ced4da;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ border-radius: 0.25rem;
+ transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+}
+@media (prefers-reduced-motion: reduce) {
+ .form-control {
+ transition: none;
+ }
+}
+.form-control[type=file] {
+ overflow: hidden;
+}
+.form-control[type=file]:not(:disabled):not([readonly]) {
+ cursor: pointer;
+}
+.form-control:focus {
+ color: #212529;
+ background-color: #fff;
+ border-color: #86b7fe;
+ outline: 0;
+ box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
+}
+.form-control::-webkit-date-and-time-value {
+ height: 1.5em;
+}
+.form-control::-moz-placeholder {
+ color: #6c757d;
+ opacity: 1;
+}
+.form-control::placeholder {
+ color: #6c757d;
+ opacity: 1;
+}
+.form-control:disabled, .form-control[readonly] {
+ background-color: #e9ecef;
+ opacity: 1;
+}
+.form-control::file-selector-button {
+ padding: 0.375rem 0.75rem;
+ margin: -0.375rem -0.75rem;
+ -webkit-margin-end: 0.75rem;
+ margin-inline-end: 0.75rem;
+ color: #212529;
+ background-color: #e9ecef;
+ pointer-events: none;
+ border-color: inherit;
+ border-style: solid;
+ border-width: 0;
+ border-inline-end-width: 1px;
+ border-radius: 0;
+ transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+}
+@media (prefers-reduced-motion: reduce) {
+ .form-control::file-selector-button {
+ transition: none;
+ }
+}
+.form-control:hover:not(:disabled):not([readonly])::file-selector-button {
+ background-color: #dde0e3;
+}
+.form-control::-webkit-file-upload-button {
+ padding: 0.375rem 0.75rem;
+ margin: -0.375rem -0.75rem;
+ -webkit-margin-end: 0.75rem;
+ margin-inline-end: 0.75rem;
+ color: #212529;
+ background-color: #e9ecef;
+ pointer-events: none;
+ border-color: inherit;
+ border-style: solid;
+ border-width: 0;
+ border-inline-end-width: 1px;
+ border-radius: 0;
+ -webkit-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+ transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+}
+@media (prefers-reduced-motion: reduce) {
+ .form-control::-webkit-file-upload-button {
+ -webkit-transition: none;
+ transition: none;
+ }
+}
+.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button {
+ background-color: #dde0e3;
+}
+
+.form-control-plaintext {
+ display: block;
+ width: 100%;
+ padding: 0.375rem 0;
+ margin-bottom: 0;
+ line-height: 1.5;
+ color: #212529;
+ background-color: transparent;
+ border: solid transparent;
+ border-width: 1px 0;
+}
+.form-control-plaintext.form-control-sm, .form-control-plaintext.form-control-lg {
+ padding-right: 0;
+ padding-left: 0;
+}
+
+.form-control-sm {
+ min-height: calc(1.5em + 0.5rem + 2px);
+ padding: 0.25rem 0.5rem;
+ font-size: 0.875rem;
+ border-radius: 0.2rem;
+}
+.form-control-sm::file-selector-button {
+ padding: 0.25rem 0.5rem;
+ margin: -0.25rem -0.5rem;
+ -webkit-margin-end: 0.5rem;
+ margin-inline-end: 0.5rem;
+}
+.form-control-sm::-webkit-file-upload-button {
+ padding: 0.25rem 0.5rem;
+ margin: -0.25rem -0.5rem;
+ -webkit-margin-end: 0.5rem;
+ margin-inline-end: 0.5rem;
+}
+
+.form-control-lg {
+ min-height: calc(1.5em + 1rem + 2px);
+ padding: 0.5rem 1rem;
+ font-size: 1.25rem;
+ border-radius: 0.3rem;
+}
+.form-control-lg::file-selector-button {
+ padding: 0.5rem 1rem;
+ margin: -0.5rem -1rem;
+ -webkit-margin-end: 1rem;
+ margin-inline-end: 1rem;
+}
+.form-control-lg::-webkit-file-upload-button {
+ padding: 0.5rem 1rem;
+ margin: -0.5rem -1rem;
+ -webkit-margin-end: 1rem;
+ margin-inline-end: 1rem;
+}
+
+textarea.form-control {
+ min-height: calc(1.5em + 0.75rem + 2px);
+}
+textarea.form-control-sm {
+ min-height: calc(1.5em + 0.5rem + 2px);
+}
+textarea.form-control-lg {
+ min-height: calc(1.5em + 1rem + 2px);
+}
+
+.form-control-color {
+ width: 3rem;
+ height: auto;
+ padding: 0.375rem;
+}
+.form-control-color:not(:disabled):not([readonly]) {
+ cursor: pointer;
+}
+.form-control-color::-moz-color-swatch {
+ height: 1.5em;
+ border-radius: 0.25rem;
+}
+.form-control-color::-webkit-color-swatch {
+ height: 1.5em;
+ border-radius: 0.25rem;
+}
+
+.form-select {
+ display: block;
+ width: 100%;
+ padding: 0.375rem 2.25rem 0.375rem 0.75rem;
+ -moz-padding-start: calc(0.75rem - 3px);
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 1.5;
+ color: #212529;
+ background-color: #fff;
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");
+ background-repeat: no-repeat;
+ background-position: right 0.75rem center;
+ background-size: 16px 12px;
+ border: 1px solid #ced4da;
+ border-radius: 0.25rem;
+ transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+}
+@media (prefers-reduced-motion: reduce) {
+ .form-select {
+ transition: none;
+ }
+}
+.form-select:focus {
+ border-color: #86b7fe;
+ outline: 0;
+ box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
+}
+.form-select[multiple], .form-select[size]:not([size="1"]) {
+ padding-right: 0.75rem;
+ background-image: none;
+}
+.form-select:disabled {
+ background-color: #e9ecef;
+}
+.form-select:-moz-focusring {
+ color: transparent;
+ text-shadow: 0 0 0 #212529;
+}
+
+.form-select-sm {
+ padding-top: 0.25rem;
+ padding-bottom: 0.25rem;
+ padding-left: 0.5rem;
+ font-size: 0.875rem;
+}
+
+.form-select-lg {
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+ padding-left: 1rem;
+ font-size: 1.25rem;
+}
+
+.form-check {
+ display: block;
+ min-height: 1.5rem;
+ padding-left: 1.5em;
+ margin-bottom: 0.125rem;
+}
+.form-check .form-check-input {
+ float: left;
+ margin-left: -1.5em;
+}
+
+.form-check-input {
+ width: 1em;
+ height: 1em;
+ margin-top: 0.25em;
+ vertical-align: top;
+ background-color: #fff;
+ background-repeat: no-repeat;
+ background-position: center;
+ background-size: contain;
+ border: 1px solid rgba(0, 0, 0, 0.25);
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ -webkit-print-color-adjust: exact;
+ color-adjust: exact;
+}
+.form-check-input[type=checkbox] {
+ border-radius: 0.25em;
+}
+.form-check-input[type=radio] {
+ border-radius: 50%;
+}
+.form-check-input:active {
+ filter: brightness(90%);
+}
+.form-check-input:focus {
+ border-color: #86b7fe;
+ outline: 0;
+ box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
+}
+.form-check-input:checked {
+ background-color: #0d6efd;
+ border-color: #0d6efd;
+}
+.form-check-input:checked[type=checkbox] {
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e");
+}
+.form-check-input:checked[type=radio] {
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e");
+}
+.form-check-input[type=checkbox]:indeterminate {
+ background-color: #0d6efd;
+ border-color: #0d6efd;
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e");
+}
+.form-check-input:disabled {
+ pointer-events: none;
+ filter: none;
+ opacity: 0.5;
+}
+.form-check-input[disabled] ~ .form-check-label, .form-check-input:disabled ~ .form-check-label {
+ opacity: 0.5;
+}
+
+.form-switch {
+ padding-left: 2.5em;
+}
+.form-switch .form-check-input {
+ width: 2em;
+ margin-left: -2.5em;
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");
+ background-position: left center;
+ border-radius: 2em;
+ transition: background-position 0.15s ease-in-out;
+}
+@media (prefers-reduced-motion: reduce) {
+ .form-switch .form-check-input {
+ transition: none;
+ }
+}
+.form-switch .form-check-input:focus {
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e");
+}
+.form-switch .form-check-input:checked {
+ background-position: right center;
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e");
+}
+
+.form-check-inline {
+ display: inline-block;
+ margin-right: 1rem;
+}
+
+.btn-check {
+ position: absolute;
+ clip: rect(0, 0, 0, 0);
+ pointer-events: none;
+}
+.btn-check[disabled] + .btn, .btn-check:disabled + .btn {
+ pointer-events: none;
+ filter: none;
+ opacity: 0.65;
+}
+
+.form-range {
+ width: 100%;
+ height: 1.5rem;
+ padding: 0;
+ background-color: transparent;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+}
+.form-range:focus {
+ outline: 0;
+}
+.form-range:focus::-webkit-slider-thumb {
+ box-shadow: 0 0 0 1px #fff, 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
+}
+.form-range:focus::-moz-range-thumb {
+ box-shadow: 0 0 0 1px #fff, 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
+}
+.form-range::-moz-focus-outer {
+ border: 0;
+}
+.form-range::-webkit-slider-thumb {
+ width: 1rem;
+ height: 1rem;
+ margin-top: -0.25rem;
+ background-color: #0d6efd;
+ border: 0;
+ border-radius: 1rem;
+ -webkit-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+ transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+ -webkit-appearance: none;
+ appearance: none;
+}
+@media (prefers-reduced-motion: reduce) {
+ .form-range::-webkit-slider-thumb {
+ -webkit-transition: none;
+ transition: none;
+ }
+}
+.form-range::-webkit-slider-thumb:active {
+ background-color: #b6d4fe;
+}
+.form-range::-webkit-slider-runnable-track {
+ width: 100%;
+ height: 0.5rem;
+ color: transparent;
+ cursor: pointer;
+ background-color: #dee2e6;
+ border-color: transparent;
+ border-radius: 1rem;
+}
+.form-range::-moz-range-thumb {
+ width: 1rem;
+ height: 1rem;
+ background-color: #0d6efd;
+ border: 0;
+ border-radius: 1rem;
+ -moz-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+ transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+ -moz-appearance: none;
+ appearance: none;
+}
+@media (prefers-reduced-motion: reduce) {
+ .form-range::-moz-range-thumb {
+ -moz-transition: none;
+ transition: none;
+ }
+}
+.form-range::-moz-range-thumb:active {
+ background-color: #b6d4fe;
+}
+.form-range::-moz-range-track {
+ width: 100%;
+ height: 0.5rem;
+ color: transparent;
+ cursor: pointer;
+ background-color: #dee2e6;
+ border-color: transparent;
+ border-radius: 1rem;
+}
+.form-range:disabled {
+ pointer-events: none;
+}
+.form-range:disabled::-webkit-slider-thumb {
+ background-color: #adb5bd;
+}
+.form-range:disabled::-moz-range-thumb {
+ background-color: #adb5bd;
+}
+
+.form-floating {
+ position: relative;
+}
+.form-floating > .form-control,
+.form-floating > .form-select {
+ height: calc(3.5rem + 2px);
+ line-height: 1.25;
+}
+.form-floating > label {
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 100%;
+ padding: 1rem 0.75rem;
+ pointer-events: none;
+ border: 1px solid transparent;
+ transform-origin: 0 0;
+ transition: opacity 0.1s ease-in-out, transform 0.1s ease-in-out;
+}
+@media (prefers-reduced-motion: reduce) {
+ .form-floating > label {
+ transition: none;
+ }
+}
+.form-floating > .form-control {
+ padding: 1rem 0.75rem;
+}
+.form-floating > .form-control::-moz-placeholder {
+ color: transparent;
+}
+.form-floating > .form-control::placeholder {
+ color: transparent;
+}
+.form-floating > .form-control:not(:-moz-placeholder-shown) {
+ padding-top: 1.625rem;
+ padding-bottom: 0.625rem;
+}
+.form-floating > .form-control:focus, .form-floating > .form-control:not(:placeholder-shown) {
+ padding-top: 1.625rem;
+ padding-bottom: 0.625rem;
+}
+.form-floating > .form-control:-webkit-autofill {
+ padding-top: 1.625rem;
+ padding-bottom: 0.625rem;
+}
+.form-floating > .form-select {
+ padding-top: 1.625rem;
+ padding-bottom: 0.625rem;
+}
+.form-floating > .form-control:not(:-moz-placeholder-shown) ~ label {
+ opacity: 0.65;
+ transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem);
+}
+.form-floating > .form-control:focus ~ label,
+.form-floating > .form-control:not(:placeholder-shown) ~ label,
+.form-floating > .form-select ~ label {
+ opacity: 0.65;
+ transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem);
+}
+.form-floating > .form-control:-webkit-autofill ~ label {
+ opacity: 0.65;
+ transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem);
+}
+
+.input-group {
+ position: relative;
+ display: flex;
+ flex-wrap: wrap;
+ align-items: stretch;
+ width: 100%;
+}
+.input-group > .form-control,
+.input-group > .form-select {
+ position: relative;
+ flex: 1 1 auto;
+ width: 1%;
+ min-width: 0;
+}
+.input-group > .form-control:focus,
+.input-group > .form-select:focus {
+ z-index: 3;
+}
+.input-group .btn {
+ position: relative;
+ z-index: 2;
+}
+.input-group .btn:focus {
+ z-index: 3;
+}
+
+.input-group-text {
+ display: flex;
+ align-items: center;
+ padding: 0.375rem 0.75rem;
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 1.5;
+ color: #212529;
+ text-align: center;
+ white-space: nowrap;
+ background-color: #e9ecef;
+ border: 1px solid #ced4da;
+ border-radius: 0.25rem;
+}
+
+.input-group-lg > .form-control,
+.input-group-lg > .form-select,
+.input-group-lg > .input-group-text,
+.input-group-lg > .btn {
+ padding: 0.5rem 1rem;
+ font-size: 1.25rem;
+ border-radius: 0.3rem;
+}
+
+.input-group-sm > .form-control,
+.input-group-sm > .form-select,
+.input-group-sm > .input-group-text,
+.input-group-sm > .btn {
+ padding: 0.25rem 0.5rem;
+ font-size: 0.875rem;
+ border-radius: 0.2rem;
+}
+
+.input-group-lg > .form-select,
+.input-group-sm > .form-select {
+ padding-right: 3rem;
+}
+
+.input-group:not(.has-validation) > :not(:last-child):not(.dropdown-toggle):not(.dropdown-menu),
+.input-group:not(.has-validation) > .dropdown-toggle:nth-last-child(n+3) {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+}
+.input-group.has-validation > :nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu),
+.input-group.has-validation > .dropdown-toggle:nth-last-child(n+4) {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+}
+.input-group > :not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback) {
+ margin-left: -1px;
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+}
+
+.valid-feedback {
+ display: none;
+ width: 100%;
+ margin-top: 0.25rem;
+ font-size: 0.875em;
+ color: #198754;
+}
+
+.valid-tooltip {
+ position: absolute;
+ top: 100%;
+ z-index: 5;
+ display: none;
+ max-width: 100%;
+ padding: 0.25rem 0.5rem;
+ margin-top: 0.1rem;
+ font-size: 0.875rem;
+ color: #fff;
+ background-color: rgba(25, 135, 84, 0.9);
+ border-radius: 0.25rem;
+}
+
+.was-validated :valid ~ .valid-feedback,
+.was-validated :valid ~ .valid-tooltip,
+.is-valid ~ .valid-feedback,
+.is-valid ~ .valid-tooltip {
+ display: block;
+}
+
+.was-validated .form-control:valid, .form-control.is-valid {
+ border-color: #198754;
+ padding-right: calc(1.5em + 0.75rem);
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");
+ background-repeat: no-repeat;
+ background-position: right calc(0.375em + 0.1875rem) center;
+ background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
+}
+.was-validated .form-control:valid:focus, .form-control.is-valid:focus {
+ border-color: #198754;
+ box-shadow: 0 0 0 0.25rem rgba(25, 135, 84, 0.25);
+}
+
+.was-validated textarea.form-control:valid, textarea.form-control.is-valid {
+ padding-right: calc(1.5em + 0.75rem);
+ background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem);
+}
+
+.was-validated .form-select:valid, .form-select.is-valid {
+ border-color: #198754;
+}
+.was-validated .form-select:valid:not([multiple]):not([size]), .was-validated .form-select:valid:not([multiple])[size="1"], .form-select.is-valid:not([multiple]):not([size]), .form-select.is-valid:not([multiple])[size="1"] {
+ padding-right: 4.125rem;
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"), url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");
+ background-position: right 0.75rem center, center right 2.25rem;
+ background-size: 16px 12px, calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
+}
+.was-validated .form-select:valid:focus, .form-select.is-valid:focus {
+ border-color: #198754;
+ box-shadow: 0 0 0 0.25rem rgba(25, 135, 84, 0.25);
+}
+
+.was-validated .form-check-input:valid, .form-check-input.is-valid {
+ border-color: #198754;
+}
+.was-validated .form-check-input:valid:checked, .form-check-input.is-valid:checked {
+ background-color: #198754;
+}
+.was-validated .form-check-input:valid:focus, .form-check-input.is-valid:focus {
+ box-shadow: 0 0 0 0.25rem rgba(25, 135, 84, 0.25);
+}
+.was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label {
+ color: #198754;
+}
+
+.form-check-inline .form-check-input ~ .valid-feedback {
+ margin-left: 0.5em;
+}
+
+.was-validated .input-group .form-control:valid, .input-group .form-control.is-valid,
+.was-validated .input-group .form-select:valid,
+.input-group .form-select.is-valid {
+ z-index: 1;
+}
+.was-validated .input-group .form-control:valid:focus, .input-group .form-control.is-valid:focus,
+.was-validated .input-group .form-select:valid:focus,
+.input-group .form-select.is-valid:focus {
+ z-index: 3;
+}
+
+.invalid-feedback {
+ display: none;
+ width: 100%;
+ margin-top: 0.25rem;
+ font-size: 0.875em;
+ color: #dc3545;
+}
+
+.invalid-tooltip {
+ position: absolute;
+ top: 100%;
+ z-index: 5;
+ display: none;
+ max-width: 100%;
+ padding: 0.25rem 0.5rem;
+ margin-top: 0.1rem;
+ font-size: 0.875rem;
+ color: #fff;
+ background-color: rgba(220, 53, 69, 0.9);
+ border-radius: 0.25rem;
+}
+
+.was-validated :invalid ~ .invalid-feedback,
+.was-validated :invalid ~ .invalid-tooltip,
+.is-invalid ~ .invalid-feedback,
+.is-invalid ~ .invalid-tooltip {
+ display: block;
+}
+
+.was-validated .form-control:invalid, .form-control.is-invalid {
+ border-color: #dc3545;
+ padding-right: calc(1.5em + 0.75rem);
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");
+ background-repeat: no-repeat;
+ background-position: right calc(0.375em + 0.1875rem) center;
+ background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
+}
+.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus {
+ border-color: #dc3545;
+ box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25);
+}
+
+.was-validated textarea.form-control:invalid, textarea.form-control.is-invalid {
+ padding-right: calc(1.5em + 0.75rem);
+ background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem);
+}
+
+.was-validated .form-select:invalid, .form-select.is-invalid {
+ border-color: #dc3545;
+}
+.was-validated .form-select:invalid:not([multiple]):not([size]), .was-validated .form-select:invalid:not([multiple])[size="1"], .form-select.is-invalid:not([multiple]):not([size]), .form-select.is-invalid:not([multiple])[size="1"] {
+ padding-right: 4.125rem;
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"), url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");
+ background-position: right 0.75rem center, center right 2.25rem;
+ background-size: 16px 12px, calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
+}
+.was-validated .form-select:invalid:focus, .form-select.is-invalid:focus {
+ border-color: #dc3545;
+ box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25);
+}
+
+.was-validated .form-check-input:invalid, .form-check-input.is-invalid {
+ border-color: #dc3545;
+}
+.was-validated .form-check-input:invalid:checked, .form-check-input.is-invalid:checked {
+ background-color: #dc3545;
+}
+.was-validated .form-check-input:invalid:focus, .form-check-input.is-invalid:focus {
+ box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25);
+}
+.was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label {
+ color: #dc3545;
+}
+
+.form-check-inline .form-check-input ~ .invalid-feedback {
+ margin-left: 0.5em;
+}
+
+.was-validated .input-group .form-control:invalid, .input-group .form-control.is-invalid,
+.was-validated .input-group .form-select:invalid,
+.input-group .form-select.is-invalid {
+ z-index: 2;
+}
+.was-validated .input-group .form-control:invalid:focus, .input-group .form-control.is-invalid:focus,
+.was-validated .input-group .form-select:invalid:focus,
+.input-group .form-select.is-invalid:focus {
+ z-index: 3;
+}
+
+.btn {
+ display: inline-block;
+ font-weight: 400;
+ line-height: 1.5;
+ color: #212529;
+ text-align: center;
+ text-decoration: none;
+ vertical-align: middle;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+ background-color: transparent;
+ border: 1px solid transparent;
+ padding: 0.375rem 0.75rem;
+ font-size: 1rem;
+ border-radius: 0.25rem;
+ transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+}
+@media (prefers-reduced-motion: reduce) {
+ .btn {
+ transition: none;
+ }
+}
+.btn:hover {
+ color: #212529;
+}
+.btn-check:focus + .btn, .btn:focus {
+ outline: 0;
+ box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
+}
+.btn:disabled, .btn.disabled, fieldset:disabled .btn {
+ pointer-events: none;
+ opacity: 0.65;
+}
+
+.btn-primary {
+ color: #fff;
+ background-color: #0d6efd;
+ border-color: #0d6efd;
+}
+.btn-primary:hover {
+ color: #fff;
+ background-color: #0b5ed7;
+ border-color: #0a58ca;
+}
+.btn-check:focus + .btn-primary, .btn-primary:focus {
+ color: #fff;
+ background-color: #0b5ed7;
+ border-color: #0a58ca;
+ box-shadow: 0 0 0 0.25rem rgba(49, 132, 253, 0.5);
+}
+.btn-check:checked + .btn-primary, .btn-check:active + .btn-primary, .btn-primary:active, .btn-primary.active, .show > .btn-primary.dropdown-toggle {
+ color: #fff;
+ background-color: #0a58ca;
+ border-color: #0a53be;
+}
+.btn-check:checked + .btn-primary:focus, .btn-check:active + .btn-primary:focus, .btn-primary:active:focus, .btn-primary.active:focus, .show > .btn-primary.dropdown-toggle:focus {
+ box-shadow: 0 0 0 0.25rem rgba(49, 132, 253, 0.5);
+}
+.btn-primary:disabled, .btn-primary.disabled {
+ color: #fff;
+ background-color: #0d6efd;
+ border-color: #0d6efd;
+}
+
+.btn-secondary {
+ color: #fff;
+ background-color: #6c757d;
+ border-color: #6c757d;
+}
+.btn-secondary:hover {
+ color: #fff;
+ background-color: #5c636a;
+ border-color: #565e64;
+}
+.btn-check:focus + .btn-secondary, .btn-secondary:focus {
+ color: #fff;
+ background-color: #5c636a;
+ border-color: #565e64;
+ box-shadow: 0 0 0 0.25rem rgba(130, 138, 145, 0.5);
+}
+.btn-check:checked + .btn-secondary, .btn-check:active + .btn-secondary, .btn-secondary:active, .btn-secondary.active, .show > .btn-secondary.dropdown-toggle {
+ color: #fff;
+ background-color: #565e64;
+ border-color: #51585e;
+}
+.btn-check:checked + .btn-secondary:focus, .btn-check:active + .btn-secondary:focus, .btn-secondary:active:focus, .btn-secondary.active:focus, .show > .btn-secondary.dropdown-toggle:focus {
+ box-shadow: 0 0 0 0.25rem rgba(130, 138, 145, 0.5);
+}
+.btn-secondary:disabled, .btn-secondary.disabled {
+ color: #fff;
+ background-color: #6c757d;
+ border-color: #6c757d;
+}
+
+.btn-success {
+ color: #fff;
+ background-color: #198754;
+ border-color: #198754;
+}
+.btn-success:hover {
+ color: #fff;
+ background-color: #157347;
+ border-color: #146c43;
+}
+.btn-check:focus + .btn-success, .btn-success:focus {
+ color: #fff;
+ background-color: #157347;
+ border-color: #146c43;
+ box-shadow: 0 0 0 0.25rem rgba(60, 153, 110, 0.5);
+}
+.btn-check:checked + .btn-success, .btn-check:active + .btn-success, .btn-success:active, .btn-success.active, .show > .btn-success.dropdown-toggle {
+ color: #fff;
+ background-color: #146c43;
+ border-color: #13653f;
+}
+.btn-check:checked + .btn-success:focus, .btn-check:active + .btn-success:focus, .btn-success:active:focus, .btn-success.active:focus, .show > .btn-success.dropdown-toggle:focus {
+ box-shadow: 0 0 0 0.25rem rgba(60, 153, 110, 0.5);
+}
+.btn-success:disabled, .btn-success.disabled {
+ color: #fff;
+ background-color: #198754;
+ border-color: #198754;
+}
+
+.btn-info {
+ color: #000;
+ background-color: #0dcaf0;
+ border-color: #0dcaf0;
+}
+.btn-info:hover {
+ color: #000;
+ background-color: #31d2f2;
+ border-color: #25cff2;
+}
+.btn-check:focus + .btn-info, .btn-info:focus {
+ color: #000;
+ background-color: #31d2f2;
+ border-color: #25cff2;
+ box-shadow: 0 0 0 0.25rem rgba(11, 172, 204, 0.5);
+}
+.btn-check:checked + .btn-info, .btn-check:active + .btn-info, .btn-info:active, .btn-info.active, .show > .btn-info.dropdown-toggle {
+ color: #000;
+ background-color: #3dd5f3;
+ border-color: #25cff2;
+}
+.btn-check:checked + .btn-info:focus, .btn-check:active + .btn-info:focus, .btn-info:active:focus, .btn-info.active:focus, .show > .btn-info.dropdown-toggle:focus {
+ box-shadow: 0 0 0 0.25rem rgba(11, 172, 204, 0.5);
+}
+.btn-info:disabled, .btn-info.disabled {
+ color: #000;
+ background-color: #0dcaf0;
+ border-color: #0dcaf0;
+}
+
+.btn-warning {
+ color: #000;
+ background-color: #ffc107;
+ border-color: #ffc107;
+}
+.btn-warning:hover {
+ color: #000;
+ background-color: #ffca2c;
+ border-color: #ffc720;
+}
+.btn-check:focus + .btn-warning, .btn-warning:focus {
+ color: #000;
+ background-color: #ffca2c;
+ border-color: #ffc720;
+ box-shadow: 0 0 0 0.25rem rgba(217, 164, 6, 0.5);
+}
+.btn-check:checked + .btn-warning, .btn-check:active + .btn-warning, .btn-warning:active, .btn-warning.active, .show > .btn-warning.dropdown-toggle {
+ color: #000;
+ background-color: #ffcd39;
+ border-color: #ffc720;
+}
+.btn-check:checked + .btn-warning:focus, .btn-check:active + .btn-warning:focus, .btn-warning:active:focus, .btn-warning.active:focus, .show > .btn-warning.dropdown-toggle:focus {
+ box-shadow: 0 0 0 0.25rem rgba(217, 164, 6, 0.5);
+}
+.btn-warning:disabled, .btn-warning.disabled {
+ color: #000;
+ background-color: #ffc107;
+ border-color: #ffc107;
+}
+
+.btn-danger {
+ color: #fff;
+ background-color: #dc3545;
+ border-color: #dc3545;
+}
+.btn-danger:hover {
+ color: #fff;
+ background-color: #bb2d3b;
+ border-color: #b02a37;
+}
+.btn-check:focus + .btn-danger, .btn-danger:focus {
+ color: #fff;
+ background-color: #bb2d3b;
+ border-color: #b02a37;
+ box-shadow: 0 0 0 0.25rem rgba(225, 83, 97, 0.5);
+}
+.btn-check:checked + .btn-danger, .btn-check:active + .btn-danger, .btn-danger:active, .btn-danger.active, .show > .btn-danger.dropdown-toggle {
+ color: #fff;
+ background-color: #b02a37;
+ border-color: #a52834;
+}
+.btn-check:checked + .btn-danger:focus, .btn-check:active + .btn-danger:focus, .btn-danger:active:focus, .btn-danger.active:focus, .show > .btn-danger.dropdown-toggle:focus {
+ box-shadow: 0 0 0 0.25rem rgba(225, 83, 97, 0.5);
+}
+.btn-danger:disabled, .btn-danger.disabled {
+ color: #fff;
+ background-color: #dc3545;
+ border-color: #dc3545;
+}
+
+.btn-light {
+ color: #000;
+ background-color: #f8f9fa;
+ border-color: #f8f9fa;
+}
+.btn-light:hover {
+ color: #000;
+ background-color: #f9fafb;
+ border-color: #f9fafb;
+}
+.btn-check:focus + .btn-light, .btn-light:focus {
+ color: #000;
+ background-color: #f9fafb;
+ border-color: #f9fafb;
+ box-shadow: 0 0 0 0.25rem rgba(211, 212, 213, 0.5);
+}
+.btn-check:checked + .btn-light, .btn-check:active + .btn-light, .btn-light:active, .btn-light.active, .show > .btn-light.dropdown-toggle {
+ color: #000;
+ background-color: #f9fafb;
+ border-color: #f9fafb;
+}
+.btn-check:checked + .btn-light:focus, .btn-check:active + .btn-light:focus, .btn-light:active:focus, .btn-light.active:focus, .show > .btn-light.dropdown-toggle:focus {
+ box-shadow: 0 0 0 0.25rem rgba(211, 212, 213, 0.5);
+}
+.btn-light:disabled, .btn-light.disabled {
+ color: #000;
+ background-color: #f8f9fa;
+ border-color: #f8f9fa;
+}
+
+.btn-dark {
+ color: #fff;
+ background-color: #212529;
+ border-color: #212529;
+}
+.btn-dark:hover {
+ color: #fff;
+ background-color: #1c1f23;
+ border-color: #1a1e21;
+}
+.btn-check:focus + .btn-dark, .btn-dark:focus {
+ color: #fff;
+ background-color: #1c1f23;
+ border-color: #1a1e21;
+ box-shadow: 0 0 0 0.25rem rgba(66, 70, 73, 0.5);
+}
+.btn-check:checked + .btn-dark, .btn-check:active + .btn-dark, .btn-dark:active, .btn-dark.active, .show > .btn-dark.dropdown-toggle {
+ color: #fff;
+ background-color: #1a1e21;
+ border-color: #191c1f;
+}
+.btn-check:checked + .btn-dark:focus, .btn-check:active + .btn-dark:focus, .btn-dark:active:focus, .btn-dark.active:focus, .show > .btn-dark.dropdown-toggle:focus {
+ box-shadow: 0 0 0 0.25rem rgba(66, 70, 73, 0.5);
+}
+.btn-dark:disabled, .btn-dark.disabled {
+ color: #fff;
+ background-color: #212529;
+ border-color: #212529;
+}
+
+.btn-outline-primary {
+ color: #0d6efd;
+ border-color: #0d6efd;
+}
+.btn-outline-primary:hover {
+ color: #fff;
+ background-color: #0d6efd;
+ border-color: #0d6efd;
+}
+.btn-check:focus + .btn-outline-primary, .btn-outline-primary:focus {
+ box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.5);
+}
+.btn-check:checked + .btn-outline-primary, .btn-check:active + .btn-outline-primary, .btn-outline-primary:active, .btn-outline-primary.active, .btn-outline-primary.dropdown-toggle.show {
+ color: #fff;
+ background-color: #0d6efd;
+ border-color: #0d6efd;
+}
+.btn-check:checked + .btn-outline-primary:focus, .btn-check:active + .btn-outline-primary:focus, .btn-outline-primary:active:focus, .btn-outline-primary.active:focus, .btn-outline-primary.dropdown-toggle.show:focus {
+ box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.5);
+}
+.btn-outline-primary:disabled, .btn-outline-primary.disabled {
+ color: #0d6efd;
+ background-color: transparent;
+}
+
+.btn-outline-secondary {
+ color: #6c757d;
+ border-color: #6c757d;
+}
+.btn-outline-secondary:hover {
+ color: #fff;
+ background-color: #6c757d;
+ border-color: #6c757d;
+}
+.btn-check:focus + .btn-outline-secondary, .btn-outline-secondary:focus {
+ box-shadow: 0 0 0 0.25rem rgba(108, 117, 125, 0.5);
+}
+.btn-check:checked + .btn-outline-secondary, .btn-check:active + .btn-outline-secondary, .btn-outline-secondary:active, .btn-outline-secondary.active, .btn-outline-secondary.dropdown-toggle.show {
+ color: #fff;
+ background-color: #6c757d;
+ border-color: #6c757d;
+}
+.btn-check:checked + .btn-outline-secondary:focus, .btn-check:active + .btn-outline-secondary:focus, .btn-outline-secondary:active:focus, .btn-outline-secondary.active:focus, .btn-outline-secondary.dropdown-toggle.show:focus {
+ box-shadow: 0 0 0 0.25rem rgba(108, 117, 125, 0.5);
+}
+.btn-outline-secondary:disabled, .btn-outline-secondary.disabled {
+ color: #6c757d;
+ background-color: transparent;
+}
+
+.btn-outline-success {
+ color: #198754;
+ border-color: #198754;
+}
+.btn-outline-success:hover {
+ color: #fff;
+ background-color: #198754;
+ border-color: #198754;
+}
+.btn-check:focus + .btn-outline-success, .btn-outline-success:focus {
+ box-shadow: 0 0 0 0.25rem rgba(25, 135, 84, 0.5);
+}
+.btn-check:checked + .btn-outline-success, .btn-check:active + .btn-outline-success, .btn-outline-success:active, .btn-outline-success.active, .btn-outline-success.dropdown-toggle.show {
+ color: #fff;
+ background-color: #198754;
+ border-color: #198754;
+}
+.btn-check:checked + .btn-outline-success:focus, .btn-check:active + .btn-outline-success:focus, .btn-outline-success:active:focus, .btn-outline-success.active:focus, .btn-outline-success.dropdown-toggle.show:focus {
+ box-shadow: 0 0 0 0.25rem rgba(25, 135, 84, 0.5);
+}
+.btn-outline-success:disabled, .btn-outline-success.disabled {
+ color: #198754;
+ background-color: transparent;
+}
+
+.btn-outline-info {
+ color: #0dcaf0;
+ border-color: #0dcaf0;
+}
+.btn-outline-info:hover {
+ color: #000;
+ background-color: #0dcaf0;
+ border-color: #0dcaf0;
+}
+.btn-check:focus + .btn-outline-info, .btn-outline-info:focus {
+ box-shadow: 0 0 0 0.25rem rgba(13, 202, 240, 0.5);
+}
+.btn-check:checked + .btn-outline-info, .btn-check:active + .btn-outline-info, .btn-outline-info:active, .btn-outline-info.active, .btn-outline-info.dropdown-toggle.show {
+ color: #000;
+ background-color: #0dcaf0;
+ border-color: #0dcaf0;
+}
+.btn-check:checked + .btn-outline-info:focus, .btn-check:active + .btn-outline-info:focus, .btn-outline-info:active:focus, .btn-outline-info.active:focus, .btn-outline-info.dropdown-toggle.show:focus {
+ box-shadow: 0 0 0 0.25rem rgba(13, 202, 240, 0.5);
+}
+.btn-outline-info:disabled, .btn-outline-info.disabled {
+ color: #0dcaf0;
+ background-color: transparent;
+}
+
+.btn-outline-warning {
+ color: #ffc107;
+ border-color: #ffc107;
+}
+.btn-outline-warning:hover {
+ color: #000;
+ background-color: #ffc107;
+ border-color: #ffc107;
+}
+.btn-check:focus + .btn-outline-warning, .btn-outline-warning:focus {
+ box-shadow: 0 0 0 0.25rem rgba(255, 193, 7, 0.5);
+}
+.btn-check:checked + .btn-outline-warning, .btn-check:active + .btn-outline-warning, .btn-outline-warning:active, .btn-outline-warning.active, .btn-outline-warning.dropdown-toggle.show {
+ color: #000;
+ background-color: #ffc107;
+ border-color: #ffc107;
+}
+.btn-check:checked + .btn-outline-warning:focus, .btn-check:active + .btn-outline-warning:focus, .btn-outline-warning:active:focus, .btn-outline-warning.active:focus, .btn-outline-warning.dropdown-toggle.show:focus {
+ box-shadow: 0 0 0 0.25rem rgba(255, 193, 7, 0.5);
+}
+.btn-outline-warning:disabled, .btn-outline-warning.disabled {
+ color: #ffc107;
+ background-color: transparent;
+}
+
+.btn-outline-danger {
+ color: #dc3545;
+ border-color: #dc3545;
+}
+.btn-outline-danger:hover {
+ color: #fff;
+ background-color: #dc3545;
+ border-color: #dc3545;
+}
+.btn-check:focus + .btn-outline-danger, .btn-outline-danger:focus {
+ box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.5);
+}
+.btn-check:checked + .btn-outline-danger, .btn-check:active + .btn-outline-danger, .btn-outline-danger:active, .btn-outline-danger.active, .btn-outline-danger.dropdown-toggle.show {
+ color: #fff;
+ background-color: #dc3545;
+ border-color: #dc3545;
+}
+.btn-check:checked + .btn-outline-danger:focus, .btn-check:active + .btn-outline-danger:focus, .btn-outline-danger:active:focus, .btn-outline-danger.active:focus, .btn-outline-danger.dropdown-toggle.show:focus {
+ box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.5);
+}
+.btn-outline-danger:disabled, .btn-outline-danger.disabled {
+ color: #dc3545;
+ background-color: transparent;
+}
+
+.btn-outline-light {
+ color: #f8f9fa;
+ border-color: #f8f9fa;
+}
+.btn-outline-light:hover {
+ color: #000;
+ background-color: #f8f9fa;
+ border-color: #f8f9fa;
+}
+.btn-check:focus + .btn-outline-light, .btn-outline-light:focus {
+ box-shadow: 0 0 0 0.25rem rgba(248, 249, 250, 0.5);
+}
+.btn-check:checked + .btn-outline-light, .btn-check:active + .btn-outline-light, .btn-outline-light:active, .btn-outline-light.active, .btn-outline-light.dropdown-toggle.show {
+ color: #000;
+ background-color: #f8f9fa;
+ border-color: #f8f9fa;
+}
+.btn-check:checked + .btn-outline-light:focus, .btn-check:active + .btn-outline-light:focus, .btn-outline-light:active:focus, .btn-outline-light.active:focus, .btn-outline-light.dropdown-toggle.show:focus {
+ box-shadow: 0 0 0 0.25rem rgba(248, 249, 250, 0.5);
+}
+.btn-outline-light:disabled, .btn-outline-light.disabled {
+ color: #f8f9fa;
+ background-color: transparent;
+}
+
+.btn-outline-dark {
+ color: #212529;
+ border-color: #212529;
+}
+.btn-outline-dark:hover {
+ color: #fff;
+ background-color: #212529;
+ border-color: #212529;
+}
+.btn-check:focus + .btn-outline-dark, .btn-outline-dark:focus {
+ box-shadow: 0 0 0 0.25rem rgba(33, 37, 41, 0.5);
+}
+.btn-check:checked + .btn-outline-dark, .btn-check:active + .btn-outline-dark, .btn-outline-dark:active, .btn-outline-dark.active, .btn-outline-dark.dropdown-toggle.show {
+ color: #fff;
+ background-color: #212529;
+ border-color: #212529;
+}
+.btn-check:checked + .btn-outline-dark:focus, .btn-check:active + .btn-outline-dark:focus, .btn-outline-dark:active:focus, .btn-outline-dark.active:focus, .btn-outline-dark.dropdown-toggle.show:focus {
+ box-shadow: 0 0 0 0.25rem rgba(33, 37, 41, 0.5);
+}
+.btn-outline-dark:disabled, .btn-outline-dark.disabled {
+ color: #212529;
+ background-color: transparent;
+}
+
+.btn-link {
+ font-weight: 400;
+ color: #0d6efd;
+ text-decoration: underline;
+}
+.btn-link:hover {
+ color: #0a58ca;
+}
+.btn-link:disabled, .btn-link.disabled {
+ color: #6c757d;
+}
+
+.btn-lg, .btn-group-lg > .btn {
+ padding: 0.5rem 1rem;
+ font-size: 1.25rem;
+ border-radius: 0.3rem;
+}
+
+.btn-sm, .btn-group-sm > .btn {
+ padding: 0.25rem 0.5rem;
+ font-size: 0.875rem;
+ border-radius: 0.2rem;
+}
+
+.fade {
+ transition: opacity 0.15s linear;
+}
+@media (prefers-reduced-motion: reduce) {
+ .fade {
+ transition: none;
+ }
+}
+.fade:not(.show) {
+ opacity: 0;
+}
+
+.collapse:not(.show) {
+ display: none;
+}
+
+.collapsing {
+ height: 0;
+ overflow: hidden;
+ transition: height 0.35s ease;
+}
+@media (prefers-reduced-motion: reduce) {
+ .collapsing {
+ transition: none;
+ }
+}
+.collapsing.collapse-horizontal {
+ width: 0;
+ height: auto;
+ transition: width 0.35s ease;
+}
+@media (prefers-reduced-motion: reduce) {
+ .collapsing.collapse-horizontal {
+ transition: none;
+ }
+}
+
+.dropup,
+.dropend,
+.dropdown,
+.dropstart {
+ position: relative;
+}
+
+.dropdown-toggle {
+ white-space: nowrap;
+}
+.dropdown-toggle::after {
+ display: inline-block;
+ margin-left: 0.255em;
+ vertical-align: 0.255em;
+ content: "";
+ border-top: 0.3em solid;
+ border-right: 0.3em solid transparent;
+ border-bottom: 0;
+ border-left: 0.3em solid transparent;
+}
+.dropdown-toggle:empty::after {
+ margin-left: 0;
+}
+
+.dropdown-menu {
+ position: absolute;
+ z-index: 1000;
+ display: none;
+ min-width: 10rem;
+ padding: 0.5rem 0;
+ margin: 0;
+ font-size: 1rem;
+ color: #212529;
+ text-align: left;
+ list-style: none;
+ background-color: #fff;
+ background-clip: padding-box;
+ border: 1px solid rgba(0, 0, 0, 0.15);
+ border-radius: 0.25rem;
+}
+.dropdown-menu[data-bs-popper] {
+ top: 100%;
+ left: 0;
+ margin-top: 0.125rem;
+}
+
+.dropdown-menu-start {
+ --bs-position: start;
+}
+.dropdown-menu-start[data-bs-popper] {
+ right: auto;
+ left: 0;
+}
+
+.dropdown-menu-end {
+ --bs-position: end;
+}
+.dropdown-menu-end[data-bs-popper] {
+ right: 0;
+ left: auto;
+}
+
+@media (min-width: 576px) {
+ .dropdown-menu-sm-start {
+ --bs-position: start;
+ }
+ .dropdown-menu-sm-start[data-bs-popper] {
+ right: auto;
+ left: 0;
+ }
+
+ .dropdown-menu-sm-end {
+ --bs-position: end;
+ }
+ .dropdown-menu-sm-end[data-bs-popper] {
+ right: 0;
+ left: auto;
+ }
+}
+@media (min-width: 768px) {
+ .dropdown-menu-md-start {
+ --bs-position: start;
+ }
+ .dropdown-menu-md-start[data-bs-popper] {
+ right: auto;
+ left: 0;
+ }
+
+ .dropdown-menu-md-end {
+ --bs-position: end;
+ }
+ .dropdown-menu-md-end[data-bs-popper] {
+ right: 0;
+ left: auto;
+ }
+}
+@media (min-width: 992px) {
+ .dropdown-menu-lg-start {
+ --bs-position: start;
+ }
+ .dropdown-menu-lg-start[data-bs-popper] {
+ right: auto;
+ left: 0;
+ }
+
+ .dropdown-menu-lg-end {
+ --bs-position: end;
+ }
+ .dropdown-menu-lg-end[data-bs-popper] {
+ right: 0;
+ left: auto;
+ }
+}
+@media (min-width: 1200px) {
+ .dropdown-menu-xl-start {
+ --bs-position: start;
+ }
+ .dropdown-menu-xl-start[data-bs-popper] {
+ right: auto;
+ left: 0;
+ }
+
+ .dropdown-menu-xl-end {
+ --bs-position: end;
+ }
+ .dropdown-menu-xl-end[data-bs-popper] {
+ right: 0;
+ left: auto;
+ }
+}
+@media (min-width: 1400px) {
+ .dropdown-menu-xxl-start {
+ --bs-position: start;
+ }
+ .dropdown-menu-xxl-start[data-bs-popper] {
+ right: auto;
+ left: 0;
+ }
+
+ .dropdown-menu-xxl-end {
+ --bs-position: end;
+ }
+ .dropdown-menu-xxl-end[data-bs-popper] {
+ right: 0;
+ left: auto;
+ }
+}
+.dropup .dropdown-menu[data-bs-popper] {
+ top: auto;
+ bottom: 100%;
+ margin-top: 0;
+ margin-bottom: 0.125rem;
+}
+.dropup .dropdown-toggle::after {
+ display: inline-block;
+ margin-left: 0.255em;
+ vertical-align: 0.255em;
+ content: "";
+ border-top: 0;
+ border-right: 0.3em solid transparent;
+ border-bottom: 0.3em solid;
+ border-left: 0.3em solid transparent;
+}
+.dropup .dropdown-toggle:empty::after {
+ margin-left: 0;
+}
+
+.dropend .dropdown-menu[data-bs-popper] {
+ top: 0;
+ right: auto;
+ left: 100%;
+ margin-top: 0;
+ margin-left: 0.125rem;
+}
+.dropend .dropdown-toggle::after {
+ display: inline-block;
+ margin-left: 0.255em;
+ vertical-align: 0.255em;
+ content: "";
+ border-top: 0.3em solid transparent;
+ border-right: 0;
+ border-bottom: 0.3em solid transparent;
+ border-left: 0.3em solid;
+}
+.dropend .dropdown-toggle:empty::after {
+ margin-left: 0;
+}
+.dropend .dropdown-toggle::after {
+ vertical-align: 0;
+}
+
+.dropstart .dropdown-menu[data-bs-popper] {
+ top: 0;
+ right: 100%;
+ left: auto;
+ margin-top: 0;
+ margin-right: 0.125rem;
+}
+.dropstart .dropdown-toggle::after {
+ display: inline-block;
+ margin-left: 0.255em;
+ vertical-align: 0.255em;
+ content: "";
+}
+.dropstart .dropdown-toggle::after {
+ display: none;
+}
+.dropstart .dropdown-toggle::before {
+ display: inline-block;
+ margin-right: 0.255em;
+ vertical-align: 0.255em;
+ content: "";
+ border-top: 0.3em solid transparent;
+ border-right: 0.3em solid;
+ border-bottom: 0.3em solid transparent;
+}
+.dropstart .dropdown-toggle:empty::after {
+ margin-left: 0;
+}
+.dropstart .dropdown-toggle::before {
+ vertical-align: 0;
+}
+
+.dropdown-divider {
+ height: 0;
+ margin: 0.5rem 0;
+ overflow: hidden;
+ border-top: 1px solid rgba(0, 0, 0, 0.15);
+}
+
+.dropdown-item {
+ display: block;
+ width: 100%;
+ padding: 0.25rem 1rem;
+ clear: both;
+ font-weight: 400;
+ color: #212529;
+ text-align: inherit;
+ text-decoration: none;
+ white-space: nowrap;
+ background-color: transparent;
+ border: 0;
+}
+.dropdown-item:hover, .dropdown-item:focus {
+ color: #1e2125;
+ background-color: #e9ecef;
+}
+.dropdown-item.active, .dropdown-item:active {
+ color: #fff;
+ text-decoration: none;
+ background-color: #0d6efd;
+}
+.dropdown-item.disabled, .dropdown-item:disabled {
+ color: #adb5bd;
+ pointer-events: none;
+ background-color: transparent;
+}
+
+.dropdown-menu.show {
+ display: block;
+}
+
+.dropdown-header {
+ display: block;
+ padding: 0.5rem 1rem;
+ margin-bottom: 0;
+ font-size: 0.875rem;
+ color: #6c757d;
+ white-space: nowrap;
+}
+
+.dropdown-item-text {
+ display: block;
+ padding: 0.25rem 1rem;
+ color: #212529;
+}
+
+.dropdown-menu-dark {
+ color: #dee2e6;
+ background-color: #343a40;
+ border-color: rgba(0, 0, 0, 0.15);
+}
+.dropdown-menu-dark .dropdown-item {
+ color: #dee2e6;
+}
+.dropdown-menu-dark .dropdown-item:hover, .dropdown-menu-dark .dropdown-item:focus {
+ color: #fff;
+ background-color: rgba(255, 255, 255, 0.15);
+}
+.dropdown-menu-dark .dropdown-item.active, .dropdown-menu-dark .dropdown-item:active {
+ color: #fff;
+ background-color: #0d6efd;
+}
+.dropdown-menu-dark .dropdown-item.disabled, .dropdown-menu-dark .dropdown-item:disabled {
+ color: #adb5bd;
+}
+.dropdown-menu-dark .dropdown-divider {
+ border-color: rgba(0, 0, 0, 0.15);
+}
+.dropdown-menu-dark .dropdown-item-text {
+ color: #dee2e6;
+}
+.dropdown-menu-dark .dropdown-header {
+ color: #adb5bd;
+}
+
+.btn-group,
+.btn-group-vertical {
+ position: relative;
+ display: inline-flex;
+ vertical-align: middle;
+}
+.btn-group > .btn,
+.btn-group-vertical > .btn {
+ position: relative;
+ flex: 1 1 auto;
+}
+.btn-group > .btn-check:checked + .btn,
+.btn-group > .btn-check:focus + .btn,
+.btn-group > .btn:hover,
+.btn-group > .btn:focus,
+.btn-group > .btn:active,
+.btn-group > .btn.active,
+.btn-group-vertical > .btn-check:checked + .btn,
+.btn-group-vertical > .btn-check:focus + .btn,
+.btn-group-vertical > .btn:hover,
+.btn-group-vertical > .btn:focus,
+.btn-group-vertical > .btn:active,
+.btn-group-vertical > .btn.active {
+ z-index: 1;
+}
+
+.btn-toolbar {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: flex-start;
+}
+.btn-toolbar .input-group {
+ width: auto;
+}
+
+.btn-group > .btn:not(:first-child),
+.btn-group > .btn-group:not(:first-child) {
+ margin-left: -1px;
+}
+.btn-group > .btn:not(:last-child):not(.dropdown-toggle),
+.btn-group > .btn-group:not(:last-child) > .btn {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+}
+.btn-group > .btn:nth-child(n+3),
+.btn-group > :not(.btn-check) + .btn,
+.btn-group > .btn-group:not(:first-child) > .btn {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+}
+
+.dropdown-toggle-split {
+ padding-right: 0.5625rem;
+ padding-left: 0.5625rem;
+}
+.dropdown-toggle-split::after, .dropup .dropdown-toggle-split::after, .dropend .dropdown-toggle-split::after {
+ margin-left: 0;
+}
+.dropstart .dropdown-toggle-split::before {
+ margin-right: 0;
+}
+
+.btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split {
+ padding-right: 0.375rem;
+ padding-left: 0.375rem;
+}
+
+.btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split {
+ padding-right: 0.75rem;
+ padding-left: 0.75rem;
+}
+
+.btn-group-vertical {
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: center;
+}
+.btn-group-vertical > .btn,
+.btn-group-vertical > .btn-group {
+ width: 100%;
+}
+.btn-group-vertical > .btn:not(:first-child),
+.btn-group-vertical > .btn-group:not(:first-child) {
+ margin-top: -1px;
+}
+.btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle),
+.btn-group-vertical > .btn-group:not(:last-child) > .btn {
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+}
+.btn-group-vertical > .btn ~ .btn,
+.btn-group-vertical > .btn-group:not(:first-child) > .btn {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+}
+
+.nav {
+ display: flex;
+ flex-wrap: wrap;
+ padding-left: 0;
+ margin-bottom: 0;
+ list-style: none;
+}
+
+.nav-link {
+ display: block;
+ padding: 0.5rem 1rem;
+ color: #0d6efd;
+ text-decoration: none;
+ transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out;
+}
+@media (prefers-reduced-motion: reduce) {
+ .nav-link {
+ transition: none;
+ }
+}
+.nav-link:hover, .nav-link:focus {
+ color: #0a58ca;
+}
+.nav-link.disabled {
+ color: #6c757d;
+ pointer-events: none;
+ cursor: default;
+}
+
+.nav-tabs {
+ border-bottom: 1px solid #dee2e6;
+}
+.nav-tabs .nav-link {
+ margin-bottom: -1px;
+ background: none;
+ border: 1px solid transparent;
+ border-top-left-radius: 0.25rem;
+ border-top-right-radius: 0.25rem;
+}
+.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus {
+ border-color: #e9ecef #e9ecef #dee2e6;
+ isolation: isolate;
+}
+.nav-tabs .nav-link.disabled {
+ color: #6c757d;
+ background-color: transparent;
+ border-color: transparent;
+}
+.nav-tabs .nav-link.active,
+.nav-tabs .nav-item.show .nav-link {
+ color: #495057;
+ background-color: #fff;
+ border-color: #dee2e6 #dee2e6 #fff;
+}
+.nav-tabs .dropdown-menu {
+ margin-top: -1px;
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+}
+
+.nav-pills .nav-link {
+ background: none;
+ border: 0;
+ border-radius: 0.25rem;
+}
+.nav-pills .nav-link.active,
+.nav-pills .show > .nav-link {
+ color: #fff;
+ background-color: #0d6efd;
+}
+
+.nav-fill > .nav-link,
+.nav-fill .nav-item {
+ flex: 1 1 auto;
+ text-align: center;
+}
+
+.nav-justified > .nav-link,
+.nav-justified .nav-item {
+ flex-basis: 0;
+ flex-grow: 1;
+ text-align: center;
+}
+
+.nav-fill .nav-item .nav-link,
+.nav-justified .nav-item .nav-link {
+ width: 100%;
+}
+
+.tab-content > .tab-pane {
+ display: none;
+}
+.tab-content > .active {
+ display: block;
+}
+
+.navbar {
+ position: relative;
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: space-between;
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+}
+.navbar > .container,
+.navbar > .container-fluid,
+.navbar > .container-sm,
+.navbar > .container-md,
+.navbar > .container-lg,
+.navbar > .container-xl,
+.navbar > .container-xxl {
+ display: flex;
+ flex-wrap: inherit;
+ align-items: center;
+ justify-content: space-between;
+}
+.navbar-brand {
+ padding-top: 0.3125rem;
+ padding-bottom: 0.3125rem;
+ margin-right: 1rem;
+ font-size: 1.25rem;
+ text-decoration: none;
+ white-space: nowrap;
+}
+.navbar-nav {
+ display: flex;
+ flex-direction: column;
+ padding-left: 0;
+ margin-bottom: 0;
+ list-style: none;
+}
+.navbar-nav .nav-link {
+ padding-right: 0;
+ padding-left: 0;
+}
+.navbar-nav .dropdown-menu {
+ position: static;
+}
+
+.navbar-text {
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+}
+
+.navbar-collapse {
+ flex-basis: 100%;
+ flex-grow: 1;
+ align-items: center;
+}
+
+.navbar-toggler {
+ padding: 0.25rem 0.75rem;
+ font-size: 1.25rem;
+ line-height: 1;
+ background-color: transparent;
+ border: 1px solid transparent;
+ border-radius: 0.25rem;
+ transition: box-shadow 0.15s ease-in-out;
+}
+@media (prefers-reduced-motion: reduce) {
+ .navbar-toggler {
+ transition: none;
+ }
+}
+.navbar-toggler:hover {
+ text-decoration: none;
+}
+.navbar-toggler:focus {
+ text-decoration: none;
+ outline: 0;
+ box-shadow: 0 0 0 0.25rem;
+}
+
+.navbar-toggler-icon {
+ display: inline-block;
+ width: 1.5em;
+ height: 1.5em;
+ vertical-align: middle;
+ background-repeat: no-repeat;
+ background-position: center;
+ background-size: 100%;
+}
+
+.navbar-nav-scroll {
+ max-height: var(--bs-scroll-height, 75vh);
+ overflow-y: auto;
+}
+
+@media (min-width: 576px) {
+ .navbar-expand-sm {
+ flex-wrap: nowrap;
+ justify-content: flex-start;
+ }
+ .navbar-expand-sm .navbar-nav {
+ flex-direction: row;
+ }
+ .navbar-expand-sm .navbar-nav .dropdown-menu {
+ position: absolute;
+ }
+ .navbar-expand-sm .navbar-nav .nav-link {
+ padding-right: 0.5rem;
+ padding-left: 0.5rem;
+ }
+ .navbar-expand-sm .navbar-nav-scroll {
+ overflow: visible;
+ }
+ .navbar-expand-sm .navbar-collapse {
+ display: flex !important;
+ flex-basis: auto;
+ }
+ .navbar-expand-sm .navbar-toggler {
+ display: none;
+ }
+ .navbar-expand-sm .offcanvas-header {
+ display: none;
+ }
+ .navbar-expand-sm .offcanvas {
+ position: inherit;
+ bottom: 0;
+ z-index: 1000;
+ flex-grow: 1;
+ visibility: visible !important;
+ background-color: transparent;
+ border-right: 0;
+ border-left: 0;
+ transition: none;
+ transform: none;
+ }
+ .navbar-expand-sm .offcanvas-top,
+.navbar-expand-sm .offcanvas-bottom {
+ height: auto;
+ border-top: 0;
+ border-bottom: 0;
+ }
+ .navbar-expand-sm .offcanvas-body {
+ display: flex;
+ flex-grow: 0;
+ padding: 0;
+ overflow-y: visible;
+ }
+}
+@media (min-width: 768px) {
+ .navbar-expand-md {
+ flex-wrap: nowrap;
+ justify-content: flex-start;
+ }
+ .navbar-expand-md .navbar-nav {
+ flex-direction: row;
+ }
+ .navbar-expand-md .navbar-nav .dropdown-menu {
+ position: absolute;
+ }
+ .navbar-expand-md .navbar-nav .nav-link {
+ padding-right: 0.5rem;
+ padding-left: 0.5rem;
+ }
+ .navbar-expand-md .navbar-nav-scroll {
+ overflow: visible;
+ }
+ .navbar-expand-md .navbar-collapse {
+ display: flex !important;
+ flex-basis: auto;
+ }
+ .navbar-expand-md .navbar-toggler {
+ display: none;
+ }
+ .navbar-expand-md .offcanvas-header {
+ display: none;
+ }
+ .navbar-expand-md .offcanvas {
+ position: inherit;
+ bottom: 0;
+ z-index: 1000;
+ flex-grow: 1;
+ visibility: visible !important;
+ background-color: transparent;
+ border-right: 0;
+ border-left: 0;
+ transition: none;
+ transform: none;
+ }
+ .navbar-expand-md .offcanvas-top,
+.navbar-expand-md .offcanvas-bottom {
+ height: auto;
+ border-top: 0;
+ border-bottom: 0;
+ }
+ .navbar-expand-md .offcanvas-body {
+ display: flex;
+ flex-grow: 0;
+ padding: 0;
+ overflow-y: visible;
+ }
+}
+@media (min-width: 992px) {
+ .navbar-expand-lg {
+ flex-wrap: nowrap;
+ justify-content: flex-start;
+ }
+ .navbar-expand-lg .navbar-nav {
+ flex-direction: row;
+ }
+ .navbar-expand-lg .navbar-nav .dropdown-menu {
+ position: absolute;
+ }
+ .navbar-expand-lg .navbar-nav .nav-link {
+ padding-right: 0.5rem;
+ padding-left: 0.5rem;
+ }
+ .navbar-expand-lg .navbar-nav-scroll {
+ overflow: visible;
+ }
+ .navbar-expand-lg .navbar-collapse {
+ display: flex !important;
+ flex-basis: auto;
+ }
+ .navbar-expand-lg .navbar-toggler {
+ display: none;
+ }
+ .navbar-expand-lg .offcanvas-header {
+ display: none;
+ }
+ .navbar-expand-lg .offcanvas {
+ position: inherit;
+ bottom: 0;
+ z-index: 1000;
+ flex-grow: 1;
+ visibility: visible !important;
+ background-color: transparent;
+ border-right: 0;
+ border-left: 0;
+ transition: none;
+ transform: none;
+ }
+ .navbar-expand-lg .offcanvas-top,
+.navbar-expand-lg .offcanvas-bottom {
+ height: auto;
+ border-top: 0;
+ border-bottom: 0;
+ }
+ .navbar-expand-lg .offcanvas-body {
+ display: flex;
+ flex-grow: 0;
+ padding: 0;
+ overflow-y: visible;
+ }
+}
+@media (min-width: 1200px) {
+ .navbar-expand-xl {
+ flex-wrap: nowrap;
+ justify-content: flex-start;
+ }
+ .navbar-expand-xl .navbar-nav {
+ flex-direction: row;
+ }
+ .navbar-expand-xl .navbar-nav .dropdown-menu {
+ position: absolute;
+ }
+ .navbar-expand-xl .navbar-nav .nav-link {
+ padding-right: 0.5rem;
+ padding-left: 0.5rem;
+ }
+ .navbar-expand-xl .navbar-nav-scroll {
+ overflow: visible;
+ }
+ .navbar-expand-xl .navbar-collapse {
+ display: flex !important;
+ flex-basis: auto;
+ }
+ .navbar-expand-xl .navbar-toggler {
+ display: none;
+ }
+ .navbar-expand-xl .offcanvas-header {
+ display: none;
+ }
+ .navbar-expand-xl .offcanvas {
+ position: inherit;
+ bottom: 0;
+ z-index: 1000;
+ flex-grow: 1;
+ visibility: visible !important;
+ background-color: transparent;
+ border-right: 0;
+ border-left: 0;
+ transition: none;
+ transform: none;
+ }
+ .navbar-expand-xl .offcanvas-top,
+.navbar-expand-xl .offcanvas-bottom {
+ height: auto;
+ border-top: 0;
+ border-bottom: 0;
+ }
+ .navbar-expand-xl .offcanvas-body {
+ display: flex;
+ flex-grow: 0;
+ padding: 0;
+ overflow-y: visible;
+ }
+}
+@media (min-width: 1400px) {
+ .navbar-expand-xxl {
+ flex-wrap: nowrap;
+ justify-content: flex-start;
+ }
+ .navbar-expand-xxl .navbar-nav {
+ flex-direction: row;
+ }
+ .navbar-expand-xxl .navbar-nav .dropdown-menu {
+ position: absolute;
+ }
+ .navbar-expand-xxl .navbar-nav .nav-link {
+ padding-right: 0.5rem;
+ padding-left: 0.5rem;
+ }
+ .navbar-expand-xxl .navbar-nav-scroll {
+ overflow: visible;
+ }
+ .navbar-expand-xxl .navbar-collapse {
+ display: flex !important;
+ flex-basis: auto;
+ }
+ .navbar-expand-xxl .navbar-toggler {
+ display: none;
+ }
+ .navbar-expand-xxl .offcanvas-header {
+ display: none;
+ }
+ .navbar-expand-xxl .offcanvas {
+ position: inherit;
+ bottom: 0;
+ z-index: 1000;
+ flex-grow: 1;
+ visibility: visible !important;
+ background-color: transparent;
+ border-right: 0;
+ border-left: 0;
+ transition: none;
+ transform: none;
+ }
+ .navbar-expand-xxl .offcanvas-top,
+.navbar-expand-xxl .offcanvas-bottom {
+ height: auto;
+ border-top: 0;
+ border-bottom: 0;
+ }
+ .navbar-expand-xxl .offcanvas-body {
+ display: flex;
+ flex-grow: 0;
+ padding: 0;
+ overflow-y: visible;
+ }
+}
+.navbar-expand {
+ flex-wrap: nowrap;
+ justify-content: flex-start;
+}
+.navbar-expand .navbar-nav {
+ flex-direction: row;
+}
+.navbar-expand .navbar-nav .dropdown-menu {
+ position: absolute;
+}
+.navbar-expand .navbar-nav .nav-link {
+ padding-right: 0.5rem;
+ padding-left: 0.5rem;
+}
+.navbar-expand .navbar-nav-scroll {
+ overflow: visible;
+}
+.navbar-expand .navbar-collapse {
+ display: flex !important;
+ flex-basis: auto;
+}
+.navbar-expand .navbar-toggler {
+ display: none;
+}
+.navbar-expand .offcanvas-header {
+ display: none;
+}
+.navbar-expand .offcanvas {
+ position: inherit;
+ bottom: 0;
+ z-index: 1000;
+ flex-grow: 1;
+ visibility: visible !important;
+ background-color: transparent;
+ border-right: 0;
+ border-left: 0;
+ transition: none;
+ transform: none;
+}
+.navbar-expand .offcanvas-top,
+.navbar-expand .offcanvas-bottom {
+ height: auto;
+ border-top: 0;
+ border-bottom: 0;
+}
+.navbar-expand .offcanvas-body {
+ display: flex;
+ flex-grow: 0;
+ padding: 0;
+ overflow-y: visible;
+}
+
+.navbar-light .navbar-brand {
+ color: rgba(0, 0, 0, 0.9);
+}
+.navbar-light .navbar-brand:hover, .navbar-light .navbar-brand:focus {
+ color: rgba(0, 0, 0, 0.9);
+}
+.navbar-light .navbar-nav .nav-link {
+ color: rgba(0, 0, 0, 0.55);
+}
+.navbar-light .navbar-nav .nav-link:hover, .navbar-light .navbar-nav .nav-link:focus {
+ color: rgba(0, 0, 0, 0.7);
+}
+.navbar-light .navbar-nav .nav-link.disabled {
+ color: rgba(0, 0, 0, 0.3);
+}
+.navbar-light .navbar-nav .show > .nav-link,
+.navbar-light .navbar-nav .nav-link.active {
+ color: rgba(0, 0, 0, 0.9);
+}
+.navbar-light .navbar-toggler {
+ color: rgba(0, 0, 0, 0.55);
+ border-color: rgba(0, 0, 0, 0.1);
+}
+.navbar-light .navbar-toggler-icon {
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
+}
+.navbar-light .navbar-text {
+ color: rgba(0, 0, 0, 0.55);
+}
+.navbar-light .navbar-text a,
+.navbar-light .navbar-text a:hover,
+.navbar-light .navbar-text a:focus {
+ color: rgba(0, 0, 0, 0.9);
+}
+
+.navbar-dark .navbar-brand {
+ color: #fff;
+}
+.navbar-dark .navbar-brand:hover, .navbar-dark .navbar-brand:focus {
+ color: #fff;
+}
+.navbar-dark .navbar-nav .nav-link {
+ color: rgba(255, 255, 255, 0.55);
+}
+.navbar-dark .navbar-nav .nav-link:hover, .navbar-dark .navbar-nav .nav-link:focus {
+ color: rgba(255, 255, 255, 0.75);
+}
+.navbar-dark .navbar-nav .nav-link.disabled {
+ color: rgba(255, 255, 255, 0.25);
+}
+.navbar-dark .navbar-nav .show > .nav-link,
+.navbar-dark .navbar-nav .nav-link.active {
+ color: #fff;
+}
+.navbar-dark .navbar-toggler {
+ color: rgba(255, 255, 255, 0.55);
+ border-color: rgba(255, 255, 255, 0.1);
+}
+.navbar-dark .navbar-toggler-icon {
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
+}
+.navbar-dark .navbar-text {
+ color: rgba(255, 255, 255, 0.55);
+}
+.navbar-dark .navbar-text a,
+.navbar-dark .navbar-text a:hover,
+.navbar-dark .navbar-text a:focus {
+ color: #fff;
+}
+
+.card {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ min-width: 0;
+ word-wrap: break-word;
+ background-color: #fff;
+ background-clip: border-box;
+ border: 1px solid rgba(0, 0, 0, 0.125);
+ border-radius: 0.25rem;
+}
+.card > hr {
+ margin-right: 0;
+ margin-left: 0;
+}
+.card > .list-group {
+ border-top: inherit;
+ border-bottom: inherit;
+}
+.card > .list-group:first-child {
+ border-top-width: 0;
+ border-top-left-radius: calc(0.25rem - 1px);
+ border-top-right-radius: calc(0.25rem - 1px);
+}
+.card > .list-group:last-child {
+ border-bottom-width: 0;
+ border-bottom-right-radius: calc(0.25rem - 1px);
+ border-bottom-left-radius: calc(0.25rem - 1px);
+}
+.card > .card-header + .list-group,
+.card > .list-group + .card-footer {
+ border-top: 0;
+}
+
+.card-body {
+ flex: 1 1 auto;
+ padding: 1rem 1rem;
+}
+
+.card-title {
+ margin-bottom: 0.5rem;
+}
+
+.card-subtitle {
+ margin-top: -0.25rem;
+ margin-bottom: 0;
+}
+
+.card-text:last-child {
+ margin-bottom: 0;
+}
+
+.card-link + .card-link {
+ margin-left: 1rem;
+}
+
+.card-header {
+ padding: 0.5rem 1rem;
+ margin-bottom: 0;
+ background-color: rgba(0, 0, 0, 0.03);
+ border-bottom: 1px solid rgba(0, 0, 0, 0.125);
+}
+.card-header:first-child {
+ border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0;
+}
+
+.card-footer {
+ padding: 0.5rem 1rem;
+ background-color: rgba(0, 0, 0, 0.03);
+ border-top: 1px solid rgba(0, 0, 0, 0.125);
+}
+.card-footer:last-child {
+ border-radius: 0 0 calc(0.25rem - 1px) calc(0.25rem - 1px);
+}
+
+.card-header-tabs {
+ margin-right: -0.5rem;
+ margin-bottom: -0.5rem;
+ margin-left: -0.5rem;
+ border-bottom: 0;
+}
+
+.card-header-pills {
+ margin-right: -0.5rem;
+ margin-left: -0.5rem;
+}
+
+.card-img-overlay {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ padding: 1rem;
+ border-radius: calc(0.25rem - 1px);
+}
+
+.card-img,
+.card-img-top,
+.card-img-bottom {
+ width: 100%;
+}
+
+.card-img,
+.card-img-top {
+ border-top-left-radius: calc(0.25rem - 1px);
+ border-top-right-radius: calc(0.25rem - 1px);
+}
+
+.card-img,
+.card-img-bottom {
+ border-bottom-right-radius: calc(0.25rem - 1px);
+ border-bottom-left-radius: calc(0.25rem - 1px);
+}
+
+.card-group > .card {
+ margin-bottom: 0.75rem;
+}
+@media (min-width: 576px) {
+ .card-group {
+ display: flex;
+ flex-flow: row wrap;
+ }
+ .card-group > .card {
+ flex: 1 0 0%;
+ margin-bottom: 0;
+ }
+ .card-group > .card + .card {
+ margin-left: 0;
+ border-left: 0;
+ }
+ .card-group > .card:not(:last-child) {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+ }
+ .card-group > .card:not(:last-child) .card-img-top,
+.card-group > .card:not(:last-child) .card-header {
+ border-top-right-radius: 0;
+ }
+ .card-group > .card:not(:last-child) .card-img-bottom,
+.card-group > .card:not(:last-child) .card-footer {
+ border-bottom-right-radius: 0;
+ }
+ .card-group > .card:not(:first-child) {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+ }
+ .card-group > .card:not(:first-child) .card-img-top,
+.card-group > .card:not(:first-child) .card-header {
+ border-top-left-radius: 0;
+ }
+ .card-group > .card:not(:first-child) .card-img-bottom,
+.card-group > .card:not(:first-child) .card-footer {
+ border-bottom-left-radius: 0;
+ }
+}
+
+.accordion-button {
+ position: relative;
+ display: flex;
+ align-items: center;
+ width: 100%;
+ padding: 1rem 1.25rem;
+ font-size: 1rem;
+ color: #212529;
+ text-align: left;
+ background-color: #fff;
+ border: 0;
+ border-radius: 0;
+ overflow-anchor: none;
+ transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, border-radius 0.15s ease;
+}
+@media (prefers-reduced-motion: reduce) {
+ .accordion-button {
+ transition: none;
+ }
+}
+.accordion-button:not(.collapsed) {
+ color: #0c63e4;
+ background-color: #e7f1ff;
+ box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.125);
+}
+.accordion-button:not(.collapsed)::after {
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230c63e4'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
+ transform: rotate(-180deg);
+}
+.accordion-button::after {
+ flex-shrink: 0;
+ width: 1.25rem;
+ height: 1.25rem;
+ margin-left: auto;
+ content: "";
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
+ background-repeat: no-repeat;
+ background-size: 1.25rem;
+ transition: transform 0.2s ease-in-out;
+}
+@media (prefers-reduced-motion: reduce) {
+ .accordion-button::after {
+ transition: none;
+ }
+}
+.accordion-button:hover {
+ z-index: 2;
+}
+.accordion-button:focus {
+ z-index: 3;
+ border-color: #86b7fe;
+ outline: 0;
+ box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
+}
+
+.accordion-header {
+ margin-bottom: 0;
+}
+
+.accordion-item {
+ background-color: #fff;
+ border: 1px solid rgba(0, 0, 0, 0.125);
+}
+.accordion-item:first-of-type {
+ border-top-left-radius: 0.25rem;
+ border-top-right-radius: 0.25rem;
+}
+.accordion-item:first-of-type .accordion-button {
+ border-top-left-radius: calc(0.25rem - 1px);
+ border-top-right-radius: calc(0.25rem - 1px);
+}
+.accordion-item:not(:first-of-type) {
+ border-top: 0;
+}
+.accordion-item:last-of-type {
+ border-bottom-right-radius: 0.25rem;
+ border-bottom-left-radius: 0.25rem;
+}
+.accordion-item:last-of-type .accordion-button.collapsed {
+ border-bottom-right-radius: calc(0.25rem - 1px);
+ border-bottom-left-radius: calc(0.25rem - 1px);
+}
+.accordion-item:last-of-type .accordion-collapse {
+ border-bottom-right-radius: 0.25rem;
+ border-bottom-left-radius: 0.25rem;
+}
+
+.accordion-body {
+ padding: 1rem 1.25rem;
+}
+
+.accordion-flush .accordion-collapse {
+ border-width: 0;
+}
+.accordion-flush .accordion-item {
+ border-right: 0;
+ border-left: 0;
+ border-radius: 0;
+}
+.accordion-flush .accordion-item:first-child {
+ border-top: 0;
+}
+.accordion-flush .accordion-item:last-child {
+ border-bottom: 0;
+}
+.accordion-flush .accordion-item .accordion-button {
+ border-radius: 0;
+}
+
+.breadcrumb {
+ display: flex;
+ flex-wrap: wrap;
+ padding: 0 0;
+ margin-bottom: 1rem;
+ list-style: none;
+}
+
+.breadcrumb-item + .breadcrumb-item {
+ padding-left: 0.5rem;
+}
+.breadcrumb-item + .breadcrumb-item::before {
+ float: left;
+ padding-right: 0.5rem;
+ color: #6c757d;
+ content: var(--bs-breadcrumb-divider, "/") /* rtl: var(--bs-breadcrumb-divider, "/") */;
+}
+.breadcrumb-item.active {
+ color: #6c757d;
+}
+
+.pagination {
+ display: flex;
+ padding-left: 0;
+ list-style: none;
+}
+
+.page-link {
+ position: relative;
+ display: block;
+ color: #0d6efd;
+ text-decoration: none;
+ background-color: #fff;
+ border: 1px solid #dee2e6;
+ transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+}
+@media (prefers-reduced-motion: reduce) {
+ .page-link {
+ transition: none;
+ }
+}
+.page-link:hover {
+ z-index: 2;
+ color: #0a58ca;
+ background-color: #e9ecef;
+ border-color: #dee2e6;
+}
+.page-link:focus {
+ z-index: 3;
+ color: #0a58ca;
+ background-color: #e9ecef;
+ outline: 0;
+ box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
+}
+
+.page-item:not(:first-child) .page-link {
+ margin-left: -1px;
+}
+.page-item.active .page-link {
+ z-index: 3;
+ color: #fff;
+ background-color: #0d6efd;
+ border-color: #0d6efd;
+}
+.page-item.disabled .page-link {
+ color: #6c757d;
+ pointer-events: none;
+ background-color: #fff;
+ border-color: #dee2e6;
+}
+
+.page-link {
+ padding: 0.375rem 0.75rem;
+}
+
+.page-item:first-child .page-link {
+ border-top-left-radius: 0.25rem;
+ border-bottom-left-radius: 0.25rem;
+}
+.page-item:last-child .page-link {
+ border-top-right-radius: 0.25rem;
+ border-bottom-right-radius: 0.25rem;
+}
+
+.pagination-lg .page-link {
+ padding: 0.75rem 1.5rem;
+ font-size: 1.25rem;
+}
+.pagination-lg .page-item:first-child .page-link {
+ border-top-left-radius: 0.3rem;
+ border-bottom-left-radius: 0.3rem;
+}
+.pagination-lg .page-item:last-child .page-link {
+ border-top-right-radius: 0.3rem;
+ border-bottom-right-radius: 0.3rem;
+}
+
+.pagination-sm .page-link {
+ padding: 0.25rem 0.5rem;
+ font-size: 0.875rem;
+}
+.pagination-sm .page-item:first-child .page-link {
+ border-top-left-radius: 0.2rem;
+ border-bottom-left-radius: 0.2rem;
+}
+.pagination-sm .page-item:last-child .page-link {
+ border-top-right-radius: 0.2rem;
+ border-bottom-right-radius: 0.2rem;
+}
+
+.badge {
+ display: inline-block;
+ padding: 0.35em 0.65em;
+ font-size: 0.75em;
+ font-weight: 700;
+ line-height: 1;
+ color: #fff;
+ text-align: center;
+ white-space: nowrap;
+ vertical-align: baseline;
+ border-radius: 0.25rem;
+}
+.badge:empty {
+ display: none;
+}
+
+.btn .badge {
+ position: relative;
+ top: -1px;
+}
+
+.alert {
+ position: relative;
+ padding: 1rem 1rem;
+ margin-bottom: 1rem;
+ border: 1px solid transparent;
+ border-radius: 0.25rem;
+}
+
+.alert-heading {
+ color: inherit;
+}
+
+.alert-link {
+ font-weight: 700;
+}
+
+.alert-dismissible {
+ padding-right: 3rem;
+}
+.alert-dismissible .btn-close {
+ position: absolute;
+ top: 0;
+ right: 0;
+ z-index: 2;
+ padding: 1.25rem 1rem;
+}
+
+.alert-primary {
+ color: #084298;
+ background-color: #cfe2ff;
+ border-color: #b6d4fe;
+}
+.alert-primary .alert-link {
+ color: #06357a;
+}
+
+.alert-secondary {
+ color: #41464b;
+ background-color: #e2e3e5;
+ border-color: #d3d6d8;
+}
+.alert-secondary .alert-link {
+ color: #34383c;
+}
+
+.alert-success {
+ color: #0f5132;
+ background-color: #d1e7dd;
+ border-color: #badbcc;
+}
+.alert-success .alert-link {
+ color: #0c4128;
+}
+
+.alert-info {
+ color: #055160;
+ background-color: #cff4fc;
+ border-color: #b6effb;
+}
+.alert-info .alert-link {
+ color: #04414d;
+}
+
+.alert-warning {
+ color: #664d03;
+ background-color: #fff3cd;
+ border-color: #ffecb5;
+}
+.alert-warning .alert-link {
+ color: #523e02;
+}
+
+.alert-danger {
+ color: #842029;
+ background-color: #f8d7da;
+ border-color: #f5c2c7;
+}
+.alert-danger .alert-link {
+ color: #6a1a21;
+}
+
+.alert-light {
+ color: #636464;
+ background-color: #fefefe;
+ border-color: #fdfdfe;
+}
+.alert-light .alert-link {
+ color: #4f5050;
+}
+
+.alert-dark {
+ color: #141619;
+ background-color: #d3d3d4;
+ border-color: #bcbebf;
+}
+.alert-dark .alert-link {
+ color: #101214;
+}
+
+@-webkit-keyframes progress-bar-stripes {
+ 0% {
+ background-position-x: 1rem;
+ }
+}
+
+@keyframes progress-bar-stripes {
+ 0% {
+ background-position-x: 1rem;
+ }
+}
+.progress {
+ display: flex;
+ height: 1rem;
+ overflow: hidden;
+ font-size: 0.75rem;
+ background-color: #e9ecef;
+ border-radius: 0.25rem;
+}
+
+.progress-bar {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ overflow: hidden;
+ color: #fff;
+ text-align: center;
+ white-space: nowrap;
+ background-color: #0d6efd;
+ transition: width 0.6s ease;
+}
+@media (prefers-reduced-motion: reduce) {
+ .progress-bar {
+ transition: none;
+ }
+}
+
+.progress-bar-striped {
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-size: 1rem 1rem;
+}
+
+.progress-bar-animated {
+ -webkit-animation: 1s linear infinite progress-bar-stripes;
+ animation: 1s linear infinite progress-bar-stripes;
+}
+@media (prefers-reduced-motion: reduce) {
+ .progress-bar-animated {
+ -webkit-animation: none;
+ animation: none;
+ }
+}
+
+.list-group {
+ display: flex;
+ flex-direction: column;
+ padding-left: 0;
+ margin-bottom: 0;
+ border-radius: 0.25rem;
+}
+
+.list-group-numbered {
+ list-style-type: none;
+ counter-reset: section;
+}
+.list-group-numbered > li::before {
+ content: counters(section, ".") ". ";
+ counter-increment: section;
+}
+
+.list-group-item-action {
+ width: 100%;
+ color: #495057;
+ text-align: inherit;
+}
+.list-group-item-action:hover, .list-group-item-action:focus {
+ z-index: 1;
+ color: #495057;
+ text-decoration: none;
+ background-color: #f8f9fa;
+}
+.list-group-item-action:active {
+ color: #212529;
+ background-color: #e9ecef;
+}
+
+.list-group-item {
+ position: relative;
+ display: block;
+ padding: 0.5rem 1rem;
+ color: #212529;
+ text-decoration: none;
+ background-color: #fff;
+ border: 1px solid rgba(0, 0, 0, 0.125);
+}
+.list-group-item:first-child {
+ border-top-left-radius: inherit;
+ border-top-right-radius: inherit;
+}
+.list-group-item:last-child {
+ border-bottom-right-radius: inherit;
+ border-bottom-left-radius: inherit;
+}
+.list-group-item.disabled, .list-group-item:disabled {
+ color: #6c757d;
+ pointer-events: none;
+ background-color: #fff;
+}
+.list-group-item.active {
+ z-index: 2;
+ color: #fff;
+ background-color: #0d6efd;
+ border-color: #0d6efd;
+}
+.list-group-item + .list-group-item {
+ border-top-width: 0;
+}
+.list-group-item + .list-group-item.active {
+ margin-top: -1px;
+ border-top-width: 1px;
+}
+
+.list-group-horizontal {
+ flex-direction: row;
+}
+.list-group-horizontal > .list-group-item:first-child {
+ border-bottom-left-radius: 0.25rem;
+ border-top-right-radius: 0;
+}
+.list-group-horizontal > .list-group-item:last-child {
+ border-top-right-radius: 0.25rem;
+ border-bottom-left-radius: 0;
+}
+.list-group-horizontal > .list-group-item.active {
+ margin-top: 0;
+}
+.list-group-horizontal > .list-group-item + .list-group-item {
+ border-top-width: 1px;
+ border-left-width: 0;
+}
+.list-group-horizontal > .list-group-item + .list-group-item.active {
+ margin-left: -1px;
+ border-left-width: 1px;
+}
+
+@media (min-width: 576px) {
+ .list-group-horizontal-sm {
+ flex-direction: row;
+ }
+ .list-group-horizontal-sm > .list-group-item:first-child {
+ border-bottom-left-radius: 0.25rem;
+ border-top-right-radius: 0;
+ }
+ .list-group-horizontal-sm > .list-group-item:last-child {
+ border-top-right-radius: 0.25rem;
+ border-bottom-left-radius: 0;
+ }
+ .list-group-horizontal-sm > .list-group-item.active {
+ margin-top: 0;
+ }
+ .list-group-horizontal-sm > .list-group-item + .list-group-item {
+ border-top-width: 1px;
+ border-left-width: 0;
+ }
+ .list-group-horizontal-sm > .list-group-item + .list-group-item.active {
+ margin-left: -1px;
+ border-left-width: 1px;
+ }
+}
+@media (min-width: 768px) {
+ .list-group-horizontal-md {
+ flex-direction: row;
+ }
+ .list-group-horizontal-md > .list-group-item:first-child {
+ border-bottom-left-radius: 0.25rem;
+ border-top-right-radius: 0;
+ }
+ .list-group-horizontal-md > .list-group-item:last-child {
+ border-top-right-radius: 0.25rem;
+ border-bottom-left-radius: 0;
+ }
+ .list-group-horizontal-md > .list-group-item.active {
+ margin-top: 0;
+ }
+ .list-group-horizontal-md > .list-group-item + .list-group-item {
+ border-top-width: 1px;
+ border-left-width: 0;
+ }
+ .list-group-horizontal-md > .list-group-item + .list-group-item.active {
+ margin-left: -1px;
+ border-left-width: 1px;
+ }
+}
+@media (min-width: 992px) {
+ .list-group-horizontal-lg {
+ flex-direction: row;
+ }
+ .list-group-horizontal-lg > .list-group-item:first-child {
+ border-bottom-left-radius: 0.25rem;
+ border-top-right-radius: 0;
+ }
+ .list-group-horizontal-lg > .list-group-item:last-child {
+ border-top-right-radius: 0.25rem;
+ border-bottom-left-radius: 0;
+ }
+ .list-group-horizontal-lg > .list-group-item.active {
+ margin-top: 0;
+ }
+ .list-group-horizontal-lg > .list-group-item + .list-group-item {
+ border-top-width: 1px;
+ border-left-width: 0;
+ }
+ .list-group-horizontal-lg > .list-group-item + .list-group-item.active {
+ margin-left: -1px;
+ border-left-width: 1px;
+ }
+}
+@media (min-width: 1200px) {
+ .list-group-horizontal-xl {
+ flex-direction: row;
+ }
+ .list-group-horizontal-xl > .list-group-item:first-child {
+ border-bottom-left-radius: 0.25rem;
+ border-top-right-radius: 0;
+ }
+ .list-group-horizontal-xl > .list-group-item:last-child {
+ border-top-right-radius: 0.25rem;
+ border-bottom-left-radius: 0;
+ }
+ .list-group-horizontal-xl > .list-group-item.active {
+ margin-top: 0;
+ }
+ .list-group-horizontal-xl > .list-group-item + .list-group-item {
+ border-top-width: 1px;
+ border-left-width: 0;
+ }
+ .list-group-horizontal-xl > .list-group-item + .list-group-item.active {
+ margin-left: -1px;
+ border-left-width: 1px;
+ }
+}
+@media (min-width: 1400px) {
+ .list-group-horizontal-xxl {
+ flex-direction: row;
+ }
+ .list-group-horizontal-xxl > .list-group-item:first-child {
+ border-bottom-left-radius: 0.25rem;
+ border-top-right-radius: 0;
+ }
+ .list-group-horizontal-xxl > .list-group-item:last-child {
+ border-top-right-radius: 0.25rem;
+ border-bottom-left-radius: 0;
+ }
+ .list-group-horizontal-xxl > .list-group-item.active {
+ margin-top: 0;
+ }
+ .list-group-horizontal-xxl > .list-group-item + .list-group-item {
+ border-top-width: 1px;
+ border-left-width: 0;
+ }
+ .list-group-horizontal-xxl > .list-group-item + .list-group-item.active {
+ margin-left: -1px;
+ border-left-width: 1px;
+ }
+}
+.list-group-flush {
+ border-radius: 0;
+}
+.list-group-flush > .list-group-item {
+ border-width: 0 0 1px;
+}
+.list-group-flush > .list-group-item:last-child {
+ border-bottom-width: 0;
+}
+
+.list-group-item-primary {
+ color: #084298;
+ background-color: #cfe2ff;
+}
+.list-group-item-primary.list-group-item-action:hover, .list-group-item-primary.list-group-item-action:focus {
+ color: #084298;
+ background-color: #bacbe6;
+}
+.list-group-item-primary.list-group-item-action.active {
+ color: #fff;
+ background-color: #084298;
+ border-color: #084298;
+}
+
+.list-group-item-secondary {
+ color: #41464b;
+ background-color: #e2e3e5;
+}
+.list-group-item-secondary.list-group-item-action:hover, .list-group-item-secondary.list-group-item-action:focus {
+ color: #41464b;
+ background-color: #cbccce;
+}
+.list-group-item-secondary.list-group-item-action.active {
+ color: #fff;
+ background-color: #41464b;
+ border-color: #41464b;
+}
+
+.list-group-item-success {
+ color: #0f5132;
+ background-color: #d1e7dd;
+}
+.list-group-item-success.list-group-item-action:hover, .list-group-item-success.list-group-item-action:focus {
+ color: #0f5132;
+ background-color: #bcd0c7;
+}
+.list-group-item-success.list-group-item-action.active {
+ color: #fff;
+ background-color: #0f5132;
+ border-color: #0f5132;
+}
+
+.list-group-item-info {
+ color: #055160;
+ background-color: #cff4fc;
+}
+.list-group-item-info.list-group-item-action:hover, .list-group-item-info.list-group-item-action:focus {
+ color: #055160;
+ background-color: #badce3;
+}
+.list-group-item-info.list-group-item-action.active {
+ color: #fff;
+ background-color: #055160;
+ border-color: #055160;
+}
+
+.list-group-item-warning {
+ color: #664d03;
+ background-color: #fff3cd;
+}
+.list-group-item-warning.list-group-item-action:hover, .list-group-item-warning.list-group-item-action:focus {
+ color: #664d03;
+ background-color: #e6dbb9;
+}
+.list-group-item-warning.list-group-item-action.active {
+ color: #fff;
+ background-color: #664d03;
+ border-color: #664d03;
+}
+
+.list-group-item-danger {
+ color: #842029;
+ background-color: #f8d7da;
+}
+.list-group-item-danger.list-group-item-action:hover, .list-group-item-danger.list-group-item-action:focus {
+ color: #842029;
+ background-color: #dfc2c4;
+}
+.list-group-item-danger.list-group-item-action.active {
+ color: #fff;
+ background-color: #842029;
+ border-color: #842029;
+}
+
+.list-group-item-light {
+ color: #636464;
+ background-color: #fefefe;
+}
+.list-group-item-light.list-group-item-action:hover, .list-group-item-light.list-group-item-action:focus {
+ color: #636464;
+ background-color: #e5e5e5;
+}
+.list-group-item-light.list-group-item-action.active {
+ color: #fff;
+ background-color: #636464;
+ border-color: #636464;
+}
+
+.list-group-item-dark {
+ color: #141619;
+ background-color: #d3d3d4;
+}
+.list-group-item-dark.list-group-item-action:hover, .list-group-item-dark.list-group-item-action:focus {
+ color: #141619;
+ background-color: #bebebf;
+}
+.list-group-item-dark.list-group-item-action.active {
+ color: #fff;
+ background-color: #141619;
+ border-color: #141619;
+}
+
+.btn-close {
+ box-sizing: content-box;
+ width: 1em;
+ height: 1em;
+ padding: 0.25em 0.25em;
+ color: #000;
+ background: transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;
+ border: 0;
+ border-radius: 0.25rem;
+ opacity: 0.5;
+}
+.btn-close:hover {
+ color: #000;
+ text-decoration: none;
+ opacity: 0.75;
+}
+.btn-close:focus {
+ outline: 0;
+ box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
+ opacity: 1;
+}
+.btn-close:disabled, .btn-close.disabled {
+ pointer-events: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+ opacity: 0.25;
+}
+
+.btn-close-white {
+ filter: invert(1) grayscale(100%) brightness(200%);
+}
+
+.toast {
+ width: 350px;
+ max-width: 100%;
+ font-size: 0.875rem;
+ pointer-events: auto;
+ background-color: rgba(255, 255, 255, 0.85);
+ background-clip: padding-box;
+ border: 1px solid rgba(0, 0, 0, 0.1);
+ box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
+ border-radius: 0.25rem;
+}
+.toast.showing {
+ opacity: 0;
+}
+.toast:not(.show) {
+ display: none;
+}
+
+.toast-container {
+ width: -webkit-max-content;
+ width: -moz-max-content;
+ width: max-content;
+ max-width: 100%;
+ pointer-events: none;
+}
+.toast-container > :not(:last-child) {
+ margin-bottom: 0.75rem;
+}
+
+.toast-header {
+ display: flex;
+ align-items: center;
+ padding: 0.5rem 0.75rem;
+ color: #6c757d;
+ background-color: rgba(255, 255, 255, 0.85);
+ background-clip: padding-box;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.05);
+ border-top-left-radius: calc(0.25rem - 1px);
+ border-top-right-radius: calc(0.25rem - 1px);
+}
+.toast-header .btn-close {
+ margin-right: -0.375rem;
+ margin-left: 0.75rem;
+}
+
+.toast-body {
+ padding: 0.75rem;
+ word-wrap: break-word;
+}
+
+.modal {
+ position: fixed;
+ top: 0;
+ left: 0;
+ z-index: 1055;
+ display: none;
+ width: 100%;
+ height: 100%;
+ overflow-x: hidden;
+ overflow-y: auto;
+ outline: 0;
+}
+
+.modal-dialog {
+ position: relative;
+ width: auto;
+ margin: 0.5rem;
+ pointer-events: none;
+}
+.modal.fade .modal-dialog {
+ transition: transform 0.3s ease-out;
+ transform: translate(0, -50px);
+}
+@media (prefers-reduced-motion: reduce) {
+ .modal.fade .modal-dialog {
+ transition: none;
+ }
+}
+.modal.show .modal-dialog {
+ transform: none;
+}
+.modal.modal-static .modal-dialog {
+ transform: scale(1.02);
+}
+
+.modal-dialog-scrollable {
+ height: calc(100% - 1rem);
+}
+.modal-dialog-scrollable .modal-content {
+ max-height: 100%;
+ overflow: hidden;
+}
+.modal-dialog-scrollable .modal-body {
+ overflow-y: auto;
+}
+
+.modal-dialog-centered {
+ display: flex;
+ align-items: center;
+ min-height: calc(100% - 1rem);
+}
+
+.modal-content {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ pointer-events: auto;
+ background-color: #fff;
+ background-clip: padding-box;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ border-radius: 0.3rem;
+ outline: 0;
+}
+
+.modal-backdrop {
+ position: fixed;
+ top: 0;
+ left: 0;
+ z-index: 1050;
+ width: 100vw;
+ height: 100vh;
+ background-color: #000;
+}
+.modal-backdrop.fade {
+ opacity: 0;
+}
+.modal-backdrop.show {
+ opacity: 0.5;
+}
+
+.modal-header {
+ display: flex;
+ flex-shrink: 0;
+ align-items: center;
+ justify-content: space-between;
+ padding: 1rem 1rem;
+ border-bottom: 1px solid #dee2e6;
+ border-top-left-radius: calc(0.3rem - 1px);
+ border-top-right-radius: calc(0.3rem - 1px);
+}
+.modal-header .btn-close {
+ padding: 0.5rem 0.5rem;
+ margin: -0.5rem -0.5rem -0.5rem auto;
+}
+
+.modal-title {
+ margin-bottom: 0;
+ line-height: 1.5;
+}
+
+.modal-body {
+ position: relative;
+ flex: 1 1 auto;
+ padding: 1rem;
+}
+
+.modal-footer {
+ display: flex;
+ flex-wrap: wrap;
+ flex-shrink: 0;
+ align-items: center;
+ justify-content: flex-end;
+ padding: 0.75rem;
+ border-top: 1px solid #dee2e6;
+ border-bottom-right-radius: calc(0.3rem - 1px);
+ border-bottom-left-radius: calc(0.3rem - 1px);
+}
+.modal-footer > * {
+ margin: 0.25rem;
+}
+
+@media (min-width: 576px) {
+ .modal-dialog {
+ max-width: 500px;
+ margin: 1.75rem auto;
+ }
+
+ .modal-dialog-scrollable {
+ height: calc(100% - 3.5rem);
+ }
+
+ .modal-dialog-centered {
+ min-height: calc(100% - 3.5rem);
+ }
+
+ .modal-sm {
+ max-width: 300px;
+ }
+}
+@media (min-width: 992px) {
+ .modal-lg,
+.modal-xl {
+ max-width: 800px;
+ }
+}
+@media (min-width: 1200px) {
+ .modal-xl {
+ max-width: 1140px;
+ }
+}
+.modal-fullscreen {
+ width: 100vw;
+ max-width: none;
+ height: 100%;
+ margin: 0;
+}
+.modal-fullscreen .modal-content {
+ height: 100%;
+ border: 0;
+ border-radius: 0;
+}
+.modal-fullscreen .modal-header {
+ border-radius: 0;
+}
+.modal-fullscreen .modal-body {
+ overflow-y: auto;
+}
+.modal-fullscreen .modal-footer {
+ border-radius: 0;
+}
+
+@media (max-width: 575.98px) {
+ .modal-fullscreen-sm-down {
+ width: 100vw;
+ max-width: none;
+ height: 100%;
+ margin: 0;
+ }
+ .modal-fullscreen-sm-down .modal-content {
+ height: 100%;
+ border: 0;
+ border-radius: 0;
+ }
+ .modal-fullscreen-sm-down .modal-header {
+ border-radius: 0;
+ }
+ .modal-fullscreen-sm-down .modal-body {
+ overflow-y: auto;
+ }
+ .modal-fullscreen-sm-down .modal-footer {
+ border-radius: 0;
+ }
+}
+@media (max-width: 767.98px) {
+ .modal-fullscreen-md-down {
+ width: 100vw;
+ max-width: none;
+ height: 100%;
+ margin: 0;
+ }
+ .modal-fullscreen-md-down .modal-content {
+ height: 100%;
+ border: 0;
+ border-radius: 0;
+ }
+ .modal-fullscreen-md-down .modal-header {
+ border-radius: 0;
+ }
+ .modal-fullscreen-md-down .modal-body {
+ overflow-y: auto;
+ }
+ .modal-fullscreen-md-down .modal-footer {
+ border-radius: 0;
+ }
+}
+@media (max-width: 991.98px) {
+ .modal-fullscreen-lg-down {
+ width: 100vw;
+ max-width: none;
+ height: 100%;
+ margin: 0;
+ }
+ .modal-fullscreen-lg-down .modal-content {
+ height: 100%;
+ border: 0;
+ border-radius: 0;
+ }
+ .modal-fullscreen-lg-down .modal-header {
+ border-radius: 0;
+ }
+ .modal-fullscreen-lg-down .modal-body {
+ overflow-y: auto;
+ }
+ .modal-fullscreen-lg-down .modal-footer {
+ border-radius: 0;
+ }
+}
+@media (max-width: 1199.98px) {
+ .modal-fullscreen-xl-down {
+ width: 100vw;
+ max-width: none;
+ height: 100%;
+ margin: 0;
+ }
+ .modal-fullscreen-xl-down .modal-content {
+ height: 100%;
+ border: 0;
+ border-radius: 0;
+ }
+ .modal-fullscreen-xl-down .modal-header {
+ border-radius: 0;
+ }
+ .modal-fullscreen-xl-down .modal-body {
+ overflow-y: auto;
+ }
+ .modal-fullscreen-xl-down .modal-footer {
+ border-radius: 0;
+ }
+}
+@media (max-width: 1399.98px) {
+ .modal-fullscreen-xxl-down {
+ width: 100vw;
+ max-width: none;
+ height: 100%;
+ margin: 0;
+ }
+ .modal-fullscreen-xxl-down .modal-content {
+ height: 100%;
+ border: 0;
+ border-radius: 0;
+ }
+ .modal-fullscreen-xxl-down .modal-header {
+ border-radius: 0;
+ }
+ .modal-fullscreen-xxl-down .modal-body {
+ overflow-y: auto;
+ }
+ .modal-fullscreen-xxl-down .modal-footer {
+ border-radius: 0;
+ }
+}
+.tooltip {
+ position: absolute;
+ z-index: 1080;
+ display: block;
+ margin: 0;
+ font-family: var(--bs-font-sans-serif);
+ font-style: normal;
+ font-weight: 400;
+ line-height: 1.5;
+ text-align: left;
+ text-align: start;
+ text-decoration: none;
+ text-shadow: none;
+ text-transform: none;
+ letter-spacing: normal;
+ word-break: normal;
+ word-spacing: normal;
+ white-space: normal;
+ line-break: auto;
+ font-size: 0.875rem;
+ word-wrap: break-word;
+ opacity: 0;
+}
+.tooltip.show {
+ opacity: 0.9;
+}
+.tooltip .tooltip-arrow {
+ position: absolute;
+ display: block;
+ width: 0.8rem;
+ height: 0.4rem;
+}
+.tooltip .tooltip-arrow::before {
+ position: absolute;
+ content: "";
+ border-color: transparent;
+ border-style: solid;
+}
+
+.bs-tooltip-top, .bs-tooltip-auto[data-popper-placement^=top] {
+ padding: 0.4rem 0;
+}
+.bs-tooltip-top .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow {
+ bottom: 0;
+}
+.bs-tooltip-top .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before {
+ top: -1px;
+ border-width: 0.4rem 0.4rem 0;
+ border-top-color: #000;
+}
+
+.bs-tooltip-end, .bs-tooltip-auto[data-popper-placement^=right] {
+ padding: 0 0.4rem;
+}
+.bs-tooltip-end .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow {
+ left: 0;
+ width: 0.4rem;
+ height: 0.8rem;
+}
+.bs-tooltip-end .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before {
+ right: -1px;
+ border-width: 0.4rem 0.4rem 0.4rem 0;
+ border-right-color: #000;
+}
+
+.bs-tooltip-bottom, .bs-tooltip-auto[data-popper-placement^=bottom] {
+ padding: 0.4rem 0;
+}
+.bs-tooltip-bottom .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow {
+ top: 0;
+}
+.bs-tooltip-bottom .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before {
+ bottom: -1px;
+ border-width: 0 0.4rem 0.4rem;
+ border-bottom-color: #000;
+}
+
+.bs-tooltip-start, .bs-tooltip-auto[data-popper-placement^=left] {
+ padding: 0 0.4rem;
+}
+.bs-tooltip-start .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow {
+ right: 0;
+ width: 0.4rem;
+ height: 0.8rem;
+}
+.bs-tooltip-start .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before {
+ left: -1px;
+ border-width: 0.4rem 0 0.4rem 0.4rem;
+ border-left-color: #000;
+}
+
+.tooltip-inner {
+ max-width: 200px;
+ padding: 0.25rem 0.5rem;
+ color: #fff;
+ text-align: center;
+ background-color: #000;
+ border-radius: 0.25rem;
+}
+
+.popover {
+ position: absolute;
+ top: 0;
+ left: 0 /* rtl:ignore */;
+ z-index: 1070;
+ display: block;
+ max-width: 276px;
+ font-family: var(--bs-font-sans-serif);
+ font-style: normal;
+ font-weight: 400;
+ line-height: 1.5;
+ text-align: left;
+ text-align: start;
+ text-decoration: none;
+ text-shadow: none;
+ text-transform: none;
+ letter-spacing: normal;
+ word-break: normal;
+ word-spacing: normal;
+ white-space: normal;
+ line-break: auto;
+ font-size: 0.875rem;
+ word-wrap: break-word;
+ background-color: #fff;
+ background-clip: padding-box;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ border-radius: 0.3rem;
+}
+.popover .popover-arrow {
+ position: absolute;
+ display: block;
+ width: 1rem;
+ height: 0.5rem;
+}
+.popover .popover-arrow::before, .popover .popover-arrow::after {
+ position: absolute;
+ display: block;
+ content: "";
+ border-color: transparent;
+ border-style: solid;
+}
+
+.bs-popover-top > .popover-arrow, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow {
+ bottom: calc(-0.5rem - 1px);
+}
+.bs-popover-top > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::before {
+ bottom: 0;
+ border-width: 0.5rem 0.5rem 0;
+ border-top-color: rgba(0, 0, 0, 0.25);
+}
+.bs-popover-top > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::after {
+ bottom: 1px;
+ border-width: 0.5rem 0.5rem 0;
+ border-top-color: #fff;
+}
+
+.bs-popover-end > .popover-arrow, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow {
+ left: calc(-0.5rem - 1px);
+ width: 0.5rem;
+ height: 1rem;
+}
+.bs-popover-end > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::before {
+ left: 0;
+ border-width: 0.5rem 0.5rem 0.5rem 0;
+ border-right-color: rgba(0, 0, 0, 0.25);
+}
+.bs-popover-end > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::after {
+ left: 1px;
+ border-width: 0.5rem 0.5rem 0.5rem 0;
+ border-right-color: #fff;
+}
+
+.bs-popover-bottom > .popover-arrow, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow {
+ top: calc(-0.5rem - 1px);
+}
+.bs-popover-bottom > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::before {
+ top: 0;
+ border-width: 0 0.5rem 0.5rem 0.5rem;
+ border-bottom-color: rgba(0, 0, 0, 0.25);
+}
+.bs-popover-bottom > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::after {
+ top: 1px;
+ border-width: 0 0.5rem 0.5rem 0.5rem;
+ border-bottom-color: #fff;
+}
+.bs-popover-bottom .popover-header::before, .bs-popover-auto[data-popper-placement^=bottom] .popover-header::before {
+ position: absolute;
+ top: 0;
+ left: 50%;
+ display: block;
+ width: 1rem;
+ margin-left: -0.5rem;
+ content: "";
+ border-bottom: 1px solid #f0f0f0;
+}
+
+.bs-popover-start > .popover-arrow, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow {
+ right: calc(-0.5rem - 1px);
+ width: 0.5rem;
+ height: 1rem;
+}
+.bs-popover-start > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::before {
+ right: 0;
+ border-width: 0.5rem 0 0.5rem 0.5rem;
+ border-left-color: rgba(0, 0, 0, 0.25);
+}
+.bs-popover-start > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::after {
+ right: 1px;
+ border-width: 0.5rem 0 0.5rem 0.5rem;
+ border-left-color: #fff;
+}
+
+.popover-header {
+ padding: 0.5rem 1rem;
+ margin-bottom: 0;
+ font-size: 1rem;
+ background-color: #f0f0f0;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.2);
+ border-top-left-radius: calc(0.3rem - 1px);
+ border-top-right-radius: calc(0.3rem - 1px);
+}
+.popover-header:empty {
+ display: none;
+}
+
+.popover-body {
+ padding: 1rem 1rem;
+ color: #212529;
+}
+
+.carousel {
+ position: relative;
+}
+
+.carousel.pointer-event {
+ touch-action: pan-y;
+}
+
+.carousel-inner {
+ position: relative;
+ width: 100%;
+ overflow: hidden;
+}
+.carousel-inner::after {
+ display: block;
+ clear: both;
+ content: "";
+}
+
+.carousel-item {
+ position: relative;
+ display: none;
+ float: left;
+ width: 100%;
+ margin-right: -100%;
+ -webkit-backface-visibility: hidden;
+ backface-visibility: hidden;
+ transition: transform 0.6s ease-in-out;
+}
+@media (prefers-reduced-motion: reduce) {
+ .carousel-item {
+ transition: none;
+ }
+}
+
+.carousel-item.active,
+.carousel-item-next,
+.carousel-item-prev {
+ display: block;
+}
+
+/* rtl:begin:ignore */
+.carousel-item-next:not(.carousel-item-start),
+.active.carousel-item-end {
+ transform: translateX(100%);
+}
+
+.carousel-item-prev:not(.carousel-item-end),
+.active.carousel-item-start {
+ transform: translateX(-100%);
+}
+
+/* rtl:end:ignore */
+.carousel-fade .carousel-item {
+ opacity: 0;
+ transition-property: opacity;
+ transform: none;
+}
+.carousel-fade .carousel-item.active,
+.carousel-fade .carousel-item-next.carousel-item-start,
+.carousel-fade .carousel-item-prev.carousel-item-end {
+ z-index: 1;
+ opacity: 1;
+}
+.carousel-fade .active.carousel-item-start,
+.carousel-fade .active.carousel-item-end {
+ z-index: 0;
+ opacity: 0;
+ transition: opacity 0s 0.6s;
+}
+@media (prefers-reduced-motion: reduce) {
+ .carousel-fade .active.carousel-item-start,
+.carousel-fade .active.carousel-item-end {
+ transition: none;
+ }
+}
+
+.carousel-control-prev,
+.carousel-control-next {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ z-index: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 15%;
+ padding: 0;
+ color: #fff;
+ text-align: center;
+ background: none;
+ border: 0;
+ opacity: 0.5;
+ transition: opacity 0.15s ease;
+}
+@media (prefers-reduced-motion: reduce) {
+ .carousel-control-prev,
+.carousel-control-next {
+ transition: none;
+ }
+}
+.carousel-control-prev:hover, .carousel-control-prev:focus,
+.carousel-control-next:hover,
+.carousel-control-next:focus {
+ color: #fff;
+ text-decoration: none;
+ outline: 0;
+ opacity: 0.9;
+}
+
+.carousel-control-prev {
+ left: 0;
+}
+
+.carousel-control-next {
+ right: 0;
+}
+
+.carousel-control-prev-icon,
+.carousel-control-next-icon {
+ display: inline-block;
+ width: 2rem;
+ height: 2rem;
+ background-repeat: no-repeat;
+ background-position: 50%;
+ background-size: 100% 100%;
+}
+
+/* rtl:options: {
+ "autoRename": true,
+ "stringMap":[ {
+ "name" : "prev-next",
+ "search" : "prev",
+ "replace" : "next"
+ } ]
+} */
+.carousel-control-prev-icon {
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e");
+}
+
+.carousel-control-next-icon {
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
+}
+
+.carousel-indicators {
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 2;
+ display: flex;
+ justify-content: center;
+ padding: 0;
+ margin-right: 15%;
+ margin-bottom: 1rem;
+ margin-left: 15%;
+ list-style: none;
+}
+.carousel-indicators [data-bs-target] {
+ box-sizing: content-box;
+ flex: 0 1 auto;
+ width: 30px;
+ height: 3px;
+ padding: 0;
+ margin-right: 3px;
+ margin-left: 3px;
+ text-indent: -999px;
+ cursor: pointer;
+ background-color: #fff;
+ background-clip: padding-box;
+ border: 0;
+ border-top: 10px solid transparent;
+ border-bottom: 10px solid transparent;
+ opacity: 0.5;
+ transition: opacity 0.6s ease;
+}
+@media (prefers-reduced-motion: reduce) {
+ .carousel-indicators [data-bs-target] {
+ transition: none;
+ }
+}
+.carousel-indicators .active {
+ opacity: 1;
+}
+
+.carousel-caption {
+ position: absolute;
+ right: 15%;
+ bottom: 1.25rem;
+ left: 15%;
+ padding-top: 1.25rem;
+ padding-bottom: 1.25rem;
+ color: #fff;
+ text-align: center;
+}
+
+.carousel-dark .carousel-control-prev-icon,
+.carousel-dark .carousel-control-next-icon {
+ filter: invert(1) grayscale(100);
+}
+.carousel-dark .carousel-indicators [data-bs-target] {
+ background-color: #000;
+}
+.carousel-dark .carousel-caption {
+ color: #000;
+}
+
+@-webkit-keyframes spinner-border {
+ to {
+ transform: rotate(360deg) /* rtl:ignore */;
+ }
+}
+
+@keyframes spinner-border {
+ to {
+ transform: rotate(360deg) /* rtl:ignore */;
+ }
+}
+.spinner-border {
+ display: inline-block;
+ width: 2rem;
+ height: 2rem;
+ vertical-align: -0.125em;
+ border: 0.25em solid currentColor;
+ border-right-color: transparent;
+ border-radius: 50%;
+ -webkit-animation: 0.75s linear infinite spinner-border;
+ animation: 0.75s linear infinite spinner-border;
+}
+
+.spinner-border-sm {
+ width: 1rem;
+ height: 1rem;
+ border-width: 0.2em;
+}
+
+@-webkit-keyframes spinner-grow {
+ 0% {
+ transform: scale(0);
+ }
+ 50% {
+ opacity: 1;
+ transform: none;
+ }
+}
+
+@keyframes spinner-grow {
+ 0% {
+ transform: scale(0);
+ }
+ 50% {
+ opacity: 1;
+ transform: none;
+ }
+}
+.spinner-grow {
+ display: inline-block;
+ width: 2rem;
+ height: 2rem;
+ vertical-align: -0.125em;
+ background-color: currentColor;
+ border-radius: 50%;
+ opacity: 0;
+ -webkit-animation: 0.75s linear infinite spinner-grow;
+ animation: 0.75s linear infinite spinner-grow;
+}
+
+.spinner-grow-sm {
+ width: 1rem;
+ height: 1rem;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .spinner-border,
+.spinner-grow {
+ -webkit-animation-duration: 1.5s;
+ animation-duration: 1.5s;
+ }
+}
+.offcanvas {
+ position: fixed;
+ bottom: 0;
+ z-index: 1045;
+ display: flex;
+ flex-direction: column;
+ max-width: 100%;
+ visibility: hidden;
+ background-color: #fff;
+ background-clip: padding-box;
+ outline: 0;
+ transition: transform 0.3s ease-in-out;
+}
+@media (prefers-reduced-motion: reduce) {
+ .offcanvas {
+ transition: none;
+ }
+}
+
+.offcanvas-backdrop {
+ position: fixed;
+ top: 0;
+ left: 0;
+ z-index: 1040;
+ width: 100vw;
+ height: 100vh;
+ background-color: #000;
+}
+.offcanvas-backdrop.fade {
+ opacity: 0;
+}
+.offcanvas-backdrop.show {
+ opacity: 0.5;
+}
+
+.offcanvas-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 1rem 1rem;
+}
+.offcanvas-header .btn-close {
+ padding: 0.5rem 0.5rem;
+ margin-top: -0.5rem;
+ margin-right: -0.5rem;
+ margin-bottom: -0.5rem;
+}
+
+.offcanvas-title {
+ margin-bottom: 0;
+ line-height: 1.5;
+}
+
+.offcanvas-body {
+ flex-grow: 1;
+ padding: 1rem 1rem;
+ overflow-y: auto;
+}
+
+.offcanvas-start {
+ top: 0;
+ left: 0;
+ width: 400px;
+ border-right: 1px solid rgba(0, 0, 0, 0.2);
+ transform: translateX(-100%);
+}
+
+.offcanvas-end {
+ top: 0;
+ right: 0;
+ width: 400px;
+ border-left: 1px solid rgba(0, 0, 0, 0.2);
+ transform: translateX(100%);
+}
+
+.offcanvas-top {
+ top: 0;
+ right: 0;
+ left: 0;
+ height: 30vh;
+ max-height: 100%;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.2);
+ transform: translateY(-100%);
+}
+
+.offcanvas-bottom {
+ right: 0;
+ left: 0;
+ height: 30vh;
+ max-height: 100%;
+ border-top: 1px solid rgba(0, 0, 0, 0.2);
+ transform: translateY(100%);
+}
+
+.offcanvas.show {
+ transform: none;
+}
+
+.placeholder {
+ display: inline-block;
+ min-height: 1em;
+ vertical-align: middle;
+ cursor: wait;
+ background-color: currentColor;
+ opacity: 0.5;
+}
+.placeholder.btn::before {
+ display: inline-block;
+ content: "";
+}
+
+.placeholder-xs {
+ min-height: 0.6em;
+}
+
+.placeholder-sm {
+ min-height: 0.8em;
+}
+
+.placeholder-lg {
+ min-height: 1.2em;
+}
+
+.placeholder-glow .placeholder {
+ -webkit-animation: placeholder-glow 2s ease-in-out infinite;
+ animation: placeholder-glow 2s ease-in-out infinite;
+}
+
+@-webkit-keyframes placeholder-glow {
+ 50% {
+ opacity: 0.2;
+ }
+}
+
+@keyframes placeholder-glow {
+ 50% {
+ opacity: 0.2;
+ }
+}
+.placeholder-wave {
+ -webkit-mask-image: linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);
+ mask-image: linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);
+ -webkit-mask-size: 200% 100%;
+ mask-size: 200% 100%;
+ -webkit-animation: placeholder-wave 2s linear infinite;
+ animation: placeholder-wave 2s linear infinite;
+}
+
+@-webkit-keyframes placeholder-wave {
+ 100% {
+ -webkit-mask-position: -200% 0%;
+ mask-position: -200% 0%;
+ }
+}
+
+@keyframes placeholder-wave {
+ 100% {
+ -webkit-mask-position: -200% 0%;
+ mask-position: -200% 0%;
+ }
+}
+.clearfix::after {
+ display: block;
+ clear: both;
+ content: "";
+}
+
+.link-primary {
+ color: #0d6efd;
+}
+.link-primary:hover, .link-primary:focus {
+ color: #0a58ca;
+}
+
+.link-secondary {
+ color: #6c757d;
+}
+.link-secondary:hover, .link-secondary:focus {
+ color: #565e64;
+}
+
+.link-success {
+ color: #198754;
+}
+.link-success:hover, .link-success:focus {
+ color: #146c43;
+}
+
+.link-info {
+ color: #0dcaf0;
+}
+.link-info:hover, .link-info:focus {
+ color: #3dd5f3;
+}
+
+.link-warning {
+ color: #ffc107;
+}
+.link-warning:hover, .link-warning:focus {
+ color: #ffcd39;
+}
+
+.link-danger {
+ color: #dc3545;
+}
+.link-danger:hover, .link-danger:focus {
+ color: #b02a37;
+}
+
+.link-light {
+ color: #f8f9fa;
+}
+.link-light:hover, .link-light:focus {
+ color: #f9fafb;
+}
+
+.link-dark {
+ color: #212529;
+}
+.link-dark:hover, .link-dark:focus {
+ color: #1a1e21;
+}
+
+.ratio {
+ position: relative;
+ width: 100%;
+}
+.ratio::before {
+ display: block;
+ padding-top: var(--bs-aspect-ratio);
+ content: "";
+}
+.ratio > * {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+}
+
+.ratio-1x1 {
+ --bs-aspect-ratio: 100%;
+}
+
+.ratio-4x3 {
+ --bs-aspect-ratio: calc(3 / 4 * 100%);
+}
+
+.ratio-16x9 {
+ --bs-aspect-ratio: calc(9 / 16 * 100%);
+}
+
+.ratio-21x9 {
+ --bs-aspect-ratio: calc(9 / 21 * 100%);
+}
+
+.fixed-top {
+ position: fixed;
+ top: 0;
+ right: 0;
+ left: 0;
+ z-index: 1030;
+}
+
+.fixed-bottom {
+ position: fixed;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 1030;
+}
+
+.sticky-top {
+ position: -webkit-sticky;
+ position: sticky;
+ top: 0;
+ z-index: 1020;
+}
+
+@media (min-width: 576px) {
+ .sticky-sm-top {
+ position: -webkit-sticky;
+ position: sticky;
+ top: 0;
+ z-index: 1020;
+ }
+}
+@media (min-width: 768px) {
+ .sticky-md-top {
+ position: -webkit-sticky;
+ position: sticky;
+ top: 0;
+ z-index: 1020;
+ }
+}
+@media (min-width: 992px) {
+ .sticky-lg-top {
+ position: -webkit-sticky;
+ position: sticky;
+ top: 0;
+ z-index: 1020;
+ }
+}
+@media (min-width: 1200px) {
+ .sticky-xl-top {
+ position: -webkit-sticky;
+ position: sticky;
+ top: 0;
+ z-index: 1020;
+ }
+}
+@media (min-width: 1400px) {
+ .sticky-xxl-top {
+ position: -webkit-sticky;
+ position: sticky;
+ top: 0;
+ z-index: 1020;
+ }
+}
+.hstack {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ align-self: stretch;
+}
+
+.vstack {
+ display: flex;
+ flex: 1 1 auto;
+ flex-direction: column;
+ align-self: stretch;
+}
+
+.visually-hidden,
+.visually-hidden-focusable:not(:focus):not(:focus-within) {
+ position: absolute !important;
+ width: 1px !important;
+ height: 1px !important;
+ padding: 0 !important;
+ margin: -1px !important;
+ overflow: hidden !important;
+ clip: rect(0, 0, 0, 0) !important;
+ white-space: nowrap !important;
+ border: 0 !important;
+}
+
+.stretched-link::after {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 1;
+ content: "";
+}
+
+.text-truncate {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.vr {
+ display: inline-block;
+ align-self: stretch;
+ width: 1px;
+ min-height: 1em;
+ background-color: currentColor;
+ opacity: 0.25;
+}
+
+.align-baseline {
+ vertical-align: baseline !important;
+}
+
+.align-top {
+ vertical-align: top !important;
+}
+
+.align-middle {
+ vertical-align: middle !important;
+}
+
+.align-bottom {
+ vertical-align: bottom !important;
+}
+
+.align-text-bottom {
+ vertical-align: text-bottom !important;
+}
+
+.align-text-top {
+ vertical-align: text-top !important;
+}
+
+.float-start {
+ float: left !important;
+}
+
+.float-end {
+ float: right !important;
+}
+
+.float-none {
+ float: none !important;
+}
+
+.opacity-0 {
+ opacity: 0 !important;
+}
+
+.opacity-25 {
+ opacity: 0.25 !important;
+}
+
+.opacity-50 {
+ opacity: 0.5 !important;
+}
+
+.opacity-75 {
+ opacity: 0.75 !important;
+}
+
+.opacity-100 {
+ opacity: 1 !important;
+}
+
+.overflow-auto {
+ overflow: auto !important;
+}
+
+.overflow-hidden {
+ overflow: hidden !important;
+}
+
+.overflow-visible {
+ overflow: visible !important;
+}
+
+.overflow-scroll {
+ overflow: scroll !important;
+}
+
+.d-inline {
+ display: inline !important;
+}
+
+.d-inline-block {
+ display: inline-block !important;
+}
+
+.d-block {
+ display: block !important;
+}
+
+.d-grid {
+ display: grid !important;
+}
+
+.d-table {
+ display: table !important;
+}
+
+.d-table-row {
+ display: table-row !important;
+}
+
+.d-table-cell {
+ display: table-cell !important;
+}
+
+.d-flex {
+ display: flex !important;
+}
+
+.d-inline-flex {
+ display: inline-flex !important;
+}
+
+.d-none {
+ display: none !important;
+}
+
+.shadow {
+ box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;
+}
+
+.shadow-sm {
+ box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important;
+}
+
+.shadow-lg {
+ box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important;
+}
+
+.shadow-none {
+ box-shadow: none !important;
+}
+
+.position-static {
+ position: static !important;
+}
+
+.position-relative {
+ position: relative !important;
+}
+
+.position-absolute {
+ position: absolute !important;
+}
+
+.position-fixed {
+ position: fixed !important;
+}
+
+.position-sticky {
+ position: -webkit-sticky !important;
+ position: sticky !important;
+}
+
+.top-0 {
+ top: 0 !important;
+}
+
+.top-50 {
+ top: 50% !important;
+}
+
+.top-100 {
+ top: 100% !important;
+}
+
+.bottom-0 {
+ bottom: 0 !important;
+}
+
+.bottom-50 {
+ bottom: 50% !important;
+}
+
+.bottom-100 {
+ bottom: 100% !important;
+}
+
+.start-0 {
+ left: 0 !important;
+}
+
+.start-50 {
+ left: 50% !important;
+}
+
+.start-100 {
+ left: 100% !important;
+}
+
+.end-0 {
+ right: 0 !important;
+}
+
+.end-50 {
+ right: 50% !important;
+}
+
+.end-100 {
+ right: 100% !important;
+}
+
+.translate-middle {
+ transform: translate(-50%, -50%) !important;
+}
+
+.translate-middle-x {
+ transform: translateX(-50%) !important;
+}
+
+.translate-middle-y {
+ transform: translateY(-50%) !important;
+}
+
+.border {
+ border: 1px solid #dee2e6 !important;
+}
+
+.border-0 {
+ border: 0 !important;
+}
+
+.border-top {
+ border-top: 1px solid #dee2e6 !important;
+}
+
+.border-top-0 {
+ border-top: 0 !important;
+}
+
+.border-end {
+ border-right: 1px solid #dee2e6 !important;
+}
+
+.border-end-0 {
+ border-right: 0 !important;
+}
+
+.border-bottom {
+ border-bottom: 1px solid #dee2e6 !important;
+}
+
+.border-bottom-0 {
+ border-bottom: 0 !important;
+}
+
+.border-start {
+ border-left: 1px solid #dee2e6 !important;
+}
+
+.border-start-0 {
+ border-left: 0 !important;
+}
+
+.border-primary {
+ border-color: #0d6efd !important;
+}
+
+.border-secondary {
+ border-color: #6c757d !important;
+}
+
+.border-success {
+ border-color: #198754 !important;
+}
+
+.border-info {
+ border-color: #0dcaf0 !important;
+}
+
+.border-warning {
+ border-color: #ffc107 !important;
+}
+
+.border-danger {
+ border-color: #dc3545 !important;
+}
+
+.border-light {
+ border-color: #f8f9fa !important;
+}
+
+.border-dark {
+ border-color: #212529 !important;
+}
+
+.border-white {
+ border-color: #fff !important;
+}
+
+.border-1 {
+ border-width: 1px !important;
+}
+
+.border-2 {
+ border-width: 2px !important;
+}
+
+.border-3 {
+ border-width: 3px !important;
+}
+
+.border-4 {
+ border-width: 4px !important;
+}
+
+.border-5 {
+ border-width: 5px !important;
+}
+
+.w-25 {
+ width: 25% !important;
+}
+
+.w-50 {
+ width: 50% !important;
+}
+
+.w-75 {
+ width: 75% !important;
+}
+
+.w-100 {
+ width: 100% !important;
+}
+
+.w-auto {
+ width: auto !important;
+}
+
+.mw-100 {
+ max-width: 100% !important;
+}
+
+.vw-100 {
+ width: 100vw !important;
+}
+
+.min-vw-100 {
+ min-width: 100vw !important;
+}
+
+.h-25 {
+ height: 25% !important;
+}
+
+.h-50 {
+ height: 50% !important;
+}
+
+.h-75 {
+ height: 75% !important;
+}
+
+.h-100 {
+ height: 100% !important;
+}
+
+.h-auto {
+ height: auto !important;
+}
+
+.mh-100 {
+ max-height: 100% !important;
+}
+
+.vh-100 {
+ height: 100vh !important;
+}
+
+.min-vh-100 {
+ min-height: 100vh !important;
+}
+
+.flex-fill {
+ flex: 1 1 auto !important;
+}
+
+.flex-row {
+ flex-direction: row !important;
+}
+
+.flex-column {
+ flex-direction: column !important;
+}
+
+.flex-row-reverse {
+ flex-direction: row-reverse !important;
+}
+
+.flex-column-reverse {
+ flex-direction: column-reverse !important;
+}
+
+.flex-grow-0 {
+ flex-grow: 0 !important;
+}
+
+.flex-grow-1 {
+ flex-grow: 1 !important;
+}
+
+.flex-shrink-0 {
+ flex-shrink: 0 !important;
+}
+
+.flex-shrink-1 {
+ flex-shrink: 1 !important;
+}
+
+.flex-wrap {
+ flex-wrap: wrap !important;
+}
+
+.flex-nowrap {
+ flex-wrap: nowrap !important;
+}
+
+.flex-wrap-reverse {
+ flex-wrap: wrap-reverse !important;
+}
+
+.gap-0 {
+ gap: 0 !important;
+}
+
+.gap-1 {
+ gap: 0.25rem !important;
+}
+
+.gap-2 {
+ gap: 0.5rem !important;
+}
+
+.gap-3 {
+ gap: 1rem !important;
+}
+
+.gap-4 {
+ gap: 1.5rem !important;
+}
+
+.gap-5 {
+ gap: 3rem !important;
+}
+
+.justify-content-start {
+ justify-content: flex-start !important;
+}
+
+.justify-content-end {
+ justify-content: flex-end !important;
+}
+
+.justify-content-center {
+ justify-content: center !important;
+}
+
+.justify-content-between {
+ justify-content: space-between !important;
+}
+
+.justify-content-around {
+ justify-content: space-around !important;
+}
+
+.justify-content-evenly {
+ justify-content: space-evenly !important;
+}
+
+.align-items-start {
+ align-items: flex-start !important;
+}
+
+.align-items-end {
+ align-items: flex-end !important;
+}
+
+.align-items-center {
+ align-items: center !important;
+}
+
+.align-items-baseline {
+ align-items: baseline !important;
+}
+
+.align-items-stretch {
+ align-items: stretch !important;
+}
+
+.align-content-start {
+ align-content: flex-start !important;
+}
+
+.align-content-end {
+ align-content: flex-end !important;
+}
+
+.align-content-center {
+ align-content: center !important;
+}
+
+.align-content-between {
+ align-content: space-between !important;
+}
+
+.align-content-around {
+ align-content: space-around !important;
+}
+
+.align-content-stretch {
+ align-content: stretch !important;
+}
+
+.align-self-auto {
+ align-self: auto !important;
+}
+
+.align-self-start {
+ align-self: flex-start !important;
+}
+
+.align-self-end {
+ align-self: flex-end !important;
+}
+
+.align-self-center {
+ align-self: center !important;
+}
+
+.align-self-baseline {
+ align-self: baseline !important;
+}
+
+.align-self-stretch {
+ align-self: stretch !important;
+}
+
+.order-first {
+ order: -1 !important;
+}
+
+.order-0 {
+ order: 0 !important;
+}
+
+.order-1 {
+ order: 1 !important;
+}
+
+.order-2 {
+ order: 2 !important;
+}
+
+.order-3 {
+ order: 3 !important;
+}
+
+.order-4 {
+ order: 4 !important;
+}
+
+.order-5 {
+ order: 5 !important;
+}
+
+.order-last {
+ order: 6 !important;
+}
+
+.m-0 {
+ margin: 0 !important;
+}
+
+.m-1 {
+ margin: 0.25rem !important;
+}
+
+.m-2 {
+ margin: 0.5rem !important;
+}
+
+.m-3 {
+ margin: 1rem !important;
+}
+
+.m-4 {
+ margin: 1.5rem !important;
+}
+
+.m-5 {
+ margin: 3rem !important;
+}
+
+.m-auto {
+ margin: auto !important;
+}
+
+.mx-0 {
+ margin-right: 0 !important;
+ margin-left: 0 !important;
+}
+
+.mx-1 {
+ margin-right: 0.25rem !important;
+ margin-left: 0.25rem !important;
+}
+
+.mx-2 {
+ margin-right: 0.5rem !important;
+ margin-left: 0.5rem !important;
+}
+
+.mx-3 {
+ margin-right: 1rem !important;
+ margin-left: 1rem !important;
+}
+
+.mx-4 {
+ margin-right: 1.5rem !important;
+ margin-left: 1.5rem !important;
+}
+
+.mx-5 {
+ margin-right: 3rem !important;
+ margin-left: 3rem !important;
+}
+
+.mx-auto {
+ margin-right: auto !important;
+ margin-left: auto !important;
+}
+
+.my-0 {
+ margin-top: 0 !important;
+ margin-bottom: 0 !important;
+}
+
+.my-1 {
+ margin-top: 0.25rem !important;
+ margin-bottom: 0.25rem !important;
+}
+
+.my-2 {
+ margin-top: 0.5rem !important;
+ margin-bottom: 0.5rem !important;
+}
+
+.my-3 {
+ margin-top: 1rem !important;
+ margin-bottom: 1rem !important;
+}
+
+.my-4 {
+ margin-top: 1.5rem !important;
+ margin-bottom: 1.5rem !important;
+}
+
+.my-5 {
+ margin-top: 3rem !important;
+ margin-bottom: 3rem !important;
+}
+
+.my-auto {
+ margin-top: auto !important;
+ margin-bottom: auto !important;
+}
+
+.mt-0 {
+ margin-top: 0 !important;
+}
+
+.mt-1 {
+ margin-top: 0.25rem !important;
+}
+
+.mt-2 {
+ margin-top: 0.5rem !important;
+}
+
+.mt-3 {
+ margin-top: 1rem !important;
+}
+
+.mt-4 {
+ margin-top: 1.5rem !important;
+}
+
+.mt-5 {
+ margin-top: 3rem !important;
+}
+
+.mt-auto {
+ margin-top: auto !important;
+}
+
+.me-0 {
+ margin-right: 0 !important;
+}
+
+.me-1 {
+ margin-right: 0.25rem !important;
+}
+
+.me-2 {
+ margin-right: 0.5rem !important;
+}
+
+.me-3 {
+ margin-right: 1rem !important;
+}
+
+.me-4 {
+ margin-right: 1.5rem !important;
+}
+
+.me-5 {
+ margin-right: 3rem !important;
+}
+
+.me-auto {
+ margin-right: auto !important;
+}
+
+.mb-0 {
+ margin-bottom: 0 !important;
+}
+
+.mb-1 {
+ margin-bottom: 0.25rem !important;
+}
+
+.mb-2 {
+ margin-bottom: 0.5rem !important;
+}
+
+.mb-3 {
+ margin-bottom: 1rem !important;
+}
+
+.mb-4 {
+ margin-bottom: 1.5rem !important;
+}
+
+.mb-5 {
+ margin-bottom: 3rem !important;
+}
+
+.mb-auto {
+ margin-bottom: auto !important;
+}
+
+.ms-0 {
+ margin-left: 0 !important;
+}
+
+.ms-1 {
+ margin-left: 0.25rem !important;
+}
+
+.ms-2 {
+ margin-left: 0.5rem !important;
+}
+
+.ms-3 {
+ margin-left: 1rem !important;
+}
+
+.ms-4 {
+ margin-left: 1.5rem !important;
+}
+
+.ms-5 {
+ margin-left: 3rem !important;
+}
+
+.ms-auto {
+ margin-left: auto !important;
+}
+
+.p-0 {
+ padding: 0 !important;
+}
+
+.p-1 {
+ padding: 0.25rem !important;
+}
+
+.p-2 {
+ padding: 0.5rem !important;
+}
+
+.p-3 {
+ padding: 1rem !important;
+}
+
+.p-4 {
+ padding: 1.5rem !important;
+}
+
+.p-5 {
+ padding: 3rem !important;
+}
+
+.px-0 {
+ padding-right: 0 !important;
+ padding-left: 0 !important;
+}
+
+.px-1 {
+ padding-right: 0.25rem !important;
+ padding-left: 0.25rem !important;
+}
+
+.px-2 {
+ padding-right: 0.5rem !important;
+ padding-left: 0.5rem !important;
+}
+
+.px-3 {
+ padding-right: 1rem !important;
+ padding-left: 1rem !important;
+}
+
+.px-4 {
+ padding-right: 1.5rem !important;
+ padding-left: 1.5rem !important;
+}
+
+.px-5 {
+ padding-right: 3rem !important;
+ padding-left: 3rem !important;
+}
+
+.py-0 {
+ padding-top: 0 !important;
+ padding-bottom: 0 !important;
+}
+
+.py-1 {
+ padding-top: 0.25rem !important;
+ padding-bottom: 0.25rem !important;
+}
+
+.py-2 {
+ padding-top: 0.5rem !important;
+ padding-bottom: 0.5rem !important;
+}
+
+.py-3 {
+ padding-top: 1rem !important;
+ padding-bottom: 1rem !important;
+}
+
+.py-4 {
+ padding-top: 1.5rem !important;
+ padding-bottom: 1.5rem !important;
+}
+
+.py-5 {
+ padding-top: 3rem !important;
+ padding-bottom: 3rem !important;
+}
+
+.pt-0 {
+ padding-top: 0 !important;
+}
+
+.pt-1 {
+ padding-top: 0.25rem !important;
+}
+
+.pt-2 {
+ padding-top: 0.5rem !important;
+}
+
+.pt-3 {
+ padding-top: 1rem !important;
+}
+
+.pt-4 {
+ padding-top: 1.5rem !important;
+}
+
+.pt-5 {
+ padding-top: 3rem !important;
+}
+
+.pe-0 {
+ padding-right: 0 !important;
+}
+
+.pe-1 {
+ padding-right: 0.25rem !important;
+}
+
+.pe-2 {
+ padding-right: 0.5rem !important;
+}
+
+.pe-3 {
+ padding-right: 1rem !important;
+}
+
+.pe-4 {
+ padding-right: 1.5rem !important;
+}
+
+.pe-5 {
+ padding-right: 3rem !important;
+}
+
+.pb-0 {
+ padding-bottom: 0 !important;
+}
+
+.pb-1 {
+ padding-bottom: 0.25rem !important;
+}
+
+.pb-2 {
+ padding-bottom: 0.5rem !important;
+}
+
+.pb-3 {
+ padding-bottom: 1rem !important;
+}
+
+.pb-4 {
+ padding-bottom: 1.5rem !important;
+}
+
+.pb-5 {
+ padding-bottom: 3rem !important;
+}
+
+.ps-0 {
+ padding-left: 0 !important;
+}
+
+.ps-1 {
+ padding-left: 0.25rem !important;
+}
+
+.ps-2 {
+ padding-left: 0.5rem !important;
+}
+
+.ps-3 {
+ padding-left: 1rem !important;
+}
+
+.ps-4 {
+ padding-left: 1.5rem !important;
+}
+
+.ps-5 {
+ padding-left: 3rem !important;
+}
+
+.font-monospace {
+ font-family: var(--bs-font-monospace) !important;
+}
+
+.fs-1 {
+ font-size: calc(1.375rem + 1.5vw) !important;
+}
+
+.fs-2 {
+ font-size: calc(1.325rem + 0.9vw) !important;
+}
+
+.fs-3 {
+ font-size: calc(1.3rem + 0.6vw) !important;
+}
+
+.fs-4 {
+ font-size: calc(1.275rem + 0.3vw) !important;
+}
+
+.fs-5 {
+ font-size: 1.25rem !important;
+}
+
+.fs-6 {
+ font-size: 1rem !important;
+}
+
+.fst-italic {
+ font-style: italic !important;
+}
+
+.fst-normal {
+ font-style: normal !important;
+}
+
+.fw-light {
+ font-weight: 300 !important;
+}
+
+.fw-lighter {
+ font-weight: lighter !important;
+}
+
+.fw-normal {
+ font-weight: 400 !important;
+}
+
+.fw-bold {
+ font-weight: 700 !important;
+}
+
+.fw-bolder {
+ font-weight: bolder !important;
+}
+
+.lh-1 {
+ line-height: 1 !important;
+}
+
+.lh-sm {
+ line-height: 1.25 !important;
+}
+
+.lh-base {
+ line-height: 1.5 !important;
+}
+
+.lh-lg {
+ line-height: 2 !important;
+}
+
+.text-start {
+ text-align: left !important;
+}
+
+.text-end {
+ text-align: right !important;
+}
+
+.text-center {
+ text-align: center !important;
+}
+
+.text-decoration-none {
+ text-decoration: none !important;
+}
+
+.text-decoration-underline {
+ text-decoration: underline !important;
+}
+
+.text-decoration-line-through {
+ text-decoration: line-through !important;
+}
+
+.text-lowercase {
+ text-transform: lowercase !important;
+}
+
+.text-uppercase {
+ text-transform: uppercase !important;
+}
+
+.text-capitalize {
+ text-transform: capitalize !important;
+}
+
+.text-wrap {
+ white-space: normal !important;
+}
+
+.text-nowrap {
+ white-space: nowrap !important;
+}
+
+/* rtl:begin:remove */
+.text-break {
+ word-wrap: break-word !important;
+ word-break: break-word !important;
+}
+
+/* rtl:end:remove */
+.text-primary {
+ --bs-text-opacity: 1;
+ color: rgba(var(--bs-primary-rgb), var(--bs-text-opacity)) !important;
+}
+
+.text-secondary {
+ --bs-text-opacity: 1;
+ color: rgba(var(--bs-secondary-rgb), var(--bs-text-opacity)) !important;
+}
+
+.text-success {
+ --bs-text-opacity: 1;
+ color: rgba(var(--bs-success-rgb), var(--bs-text-opacity)) !important;
+}
+
+.text-info {
+ --bs-text-opacity: 1;
+ color: rgba(var(--bs-info-rgb), var(--bs-text-opacity)) !important;
+}
+
+.text-warning {
+ --bs-text-opacity: 1;
+ color: rgba(var(--bs-warning-rgb), var(--bs-text-opacity)) !important;
+}
+
+.text-danger {
+ --bs-text-opacity: 1;
+ color: rgba(var(--bs-danger-rgb), var(--bs-text-opacity)) !important;
+}
+
+.text-light {
+ --bs-text-opacity: 1;
+ color: rgba(var(--bs-light-rgb), var(--bs-text-opacity)) !important;
+}
+
+.text-dark {
+ --bs-text-opacity: 1;
+ color: rgba(var(--bs-dark-rgb), var(--bs-text-opacity)) !important;
+}
+
+.text-black {
+ --bs-text-opacity: 1;
+ color: rgba(var(--bs-black-rgb), var(--bs-text-opacity)) !important;
+}
+
+.text-white {
+ --bs-text-opacity: 1;
+ color: rgba(var(--bs-white-rgb), var(--bs-text-opacity)) !important;
+}
+
+.text-body {
+ --bs-text-opacity: 1;
+ color: rgba(var(--bs-body-color-rgb), var(--bs-text-opacity)) !important;
+}
+
+.text-muted {
+ --bs-text-opacity: 1;
+ color: #6c757d !important;
+}
+
+.text-black-50 {
+ --bs-text-opacity: 1;
+ color: rgba(0, 0, 0, 0.5) !important;
+}
+
+.text-white-50 {
+ --bs-text-opacity: 1;
+ color: rgba(255, 255, 255, 0.5) !important;
+}
+
+.text-reset {
+ --bs-text-opacity: 1;
+ color: inherit !important;
+}
+
+.text-opacity-25 {
+ --bs-text-opacity: 0.25;
+}
+
+.text-opacity-50 {
+ --bs-text-opacity: 0.5;
+}
+
+.text-opacity-75 {
+ --bs-text-opacity: 0.75;
+}
+
+.text-opacity-100 {
+ --bs-text-opacity: 1;
+}
+
+.bg-primary {
+ --bs-bg-opacity: 1;
+ background-color: rgba(var(--bs-primary-rgb), var(--bs-bg-opacity)) !important;
+}
+
+.bg-secondary {
+ --bs-bg-opacity: 1;
+ background-color: rgba(var(--bs-secondary-rgb), var(--bs-bg-opacity)) !important;
+}
+
+.bg-success {
+ --bs-bg-opacity: 1;
+ background-color: rgba(var(--bs-success-rgb), var(--bs-bg-opacity)) !important;
+}
+
+.bg-info {
+ --bs-bg-opacity: 1;
+ background-color: rgba(var(--bs-info-rgb), var(--bs-bg-opacity)) !important;
+}
+
+.bg-warning {
+ --bs-bg-opacity: 1;
+ background-color: rgba(var(--bs-warning-rgb), var(--bs-bg-opacity)) !important;
+}
+
+.bg-danger {
+ --bs-bg-opacity: 1;
+ background-color: rgba(var(--bs-danger-rgb), var(--bs-bg-opacity)) !important;
+}
+
+.bg-light {
+ --bs-bg-opacity: 1;
+ background-color: rgba(var(--bs-light-rgb), var(--bs-bg-opacity)) !important;
+}
+
+.bg-dark {
+ --bs-bg-opacity: 1;
+ background-color: rgba(var(--bs-dark-rgb), var(--bs-bg-opacity)) !important;
+}
+
+.bg-black {
+ --bs-bg-opacity: 1;
+ background-color: rgba(var(--bs-black-rgb), var(--bs-bg-opacity)) !important;
+}
+
+.bg-white {
+ --bs-bg-opacity: 1;
+ background-color: rgba(var(--bs-white-rgb), var(--bs-bg-opacity)) !important;
+}
+
+.bg-body {
+ --bs-bg-opacity: 1;
+ background-color: rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important;
+}
+
+.bg-transparent {
+ --bs-bg-opacity: 1;
+ background-color: transparent !important;
+}
+
+.bg-opacity-10 {
+ --bs-bg-opacity: 0.1;
+}
+
+.bg-opacity-25 {
+ --bs-bg-opacity: 0.25;
+}
+
+.bg-opacity-50 {
+ --bs-bg-opacity: 0.5;
+}
+
+.bg-opacity-75 {
+ --bs-bg-opacity: 0.75;
+}
+
+.bg-opacity-100 {
+ --bs-bg-opacity: 1;
+}
+
+.bg-gradient {
+ background-image: var(--bs-gradient) !important;
+}
+
+.user-select-all {
+ -webkit-user-select: all !important;
+ -moz-user-select: all !important;
+ user-select: all !important;
+}
+
+.user-select-auto {
+ -webkit-user-select: auto !important;
+ -moz-user-select: auto !important;
+ user-select: auto !important;
+}
+
+.user-select-none {
+ -webkit-user-select: none !important;
+ -moz-user-select: none !important;
+ user-select: none !important;
+}
+
+.pe-none {
+ pointer-events: none !important;
+}
+
+.pe-auto {
+ pointer-events: auto !important;
+}
+
+.rounded {
+ border-radius: 0.25rem !important;
+}
+
+.rounded-0 {
+ border-radius: 0 !important;
+}
+
+.rounded-1 {
+ border-radius: 0.2rem !important;
+}
+
+.rounded-2 {
+ border-radius: 0.25rem !important;
+}
+
+.rounded-3 {
+ border-radius: 0.3rem !important;
+}
+
+.rounded-circle {
+ border-radius: 50% !important;
+}
+
+.rounded-pill {
+ border-radius: 50rem !important;
+}
+
+.rounded-top {
+ border-top-left-radius: 0.25rem !important;
+ border-top-right-radius: 0.25rem !important;
+}
+
+.rounded-end {
+ border-top-right-radius: 0.25rem !important;
+ border-bottom-right-radius: 0.25rem !important;
+}
+
+.rounded-bottom {
+ border-bottom-right-radius: 0.25rem !important;
+ border-bottom-left-radius: 0.25rem !important;
+}
+
+.rounded-start {
+ border-bottom-left-radius: 0.25rem !important;
+ border-top-left-radius: 0.25rem !important;
+}
+
+.visible {
+ visibility: visible !important;
+}
+
+.invisible {
+ visibility: hidden !important;
+}
+
+@media (min-width: 576px) {
+ .float-sm-start {
+ float: left !important;
+ }
+
+ .float-sm-end {
+ float: right !important;
+ }
+
+ .float-sm-none {
+ float: none !important;
+ }
+
+ .d-sm-inline {
+ display: inline !important;
+ }
+
+ .d-sm-inline-block {
+ display: inline-block !important;
+ }
+
+ .d-sm-block {
+ display: block !important;
+ }
+
+ .d-sm-grid {
+ display: grid !important;
+ }
+
+ .d-sm-table {
+ display: table !important;
+ }
+
+ .d-sm-table-row {
+ display: table-row !important;
+ }
+
+ .d-sm-table-cell {
+ display: table-cell !important;
+ }
+
+ .d-sm-flex {
+ display: flex !important;
+ }
+
+ .d-sm-inline-flex {
+ display: inline-flex !important;
+ }
+
+ .d-sm-none {
+ display: none !important;
+ }
+
+ .flex-sm-fill {
+ flex: 1 1 auto !important;
+ }
+
+ .flex-sm-row {
+ flex-direction: row !important;
+ }
+
+ .flex-sm-column {
+ flex-direction: column !important;
+ }
+
+ .flex-sm-row-reverse {
+ flex-direction: row-reverse !important;
+ }
+
+ .flex-sm-column-reverse {
+ flex-direction: column-reverse !important;
+ }
+
+ .flex-sm-grow-0 {
+ flex-grow: 0 !important;
+ }
+
+ .flex-sm-grow-1 {
+ flex-grow: 1 !important;
+ }
+
+ .flex-sm-shrink-0 {
+ flex-shrink: 0 !important;
+ }
+
+ .flex-sm-shrink-1 {
+ flex-shrink: 1 !important;
+ }
+
+ .flex-sm-wrap {
+ flex-wrap: wrap !important;
+ }
+
+ .flex-sm-nowrap {
+ flex-wrap: nowrap !important;
+ }
+
+ .flex-sm-wrap-reverse {
+ flex-wrap: wrap-reverse !important;
+ }
+
+ .gap-sm-0 {
+ gap: 0 !important;
+ }
+
+ .gap-sm-1 {
+ gap: 0.25rem !important;
+ }
+
+ .gap-sm-2 {
+ gap: 0.5rem !important;
+ }
+
+ .gap-sm-3 {
+ gap: 1rem !important;
+ }
+
+ .gap-sm-4 {
+ gap: 1.5rem !important;
+ }
+
+ .gap-sm-5 {
+ gap: 3rem !important;
+ }
+
+ .justify-content-sm-start {
+ justify-content: flex-start !important;
+ }
+
+ .justify-content-sm-end {
+ justify-content: flex-end !important;
+ }
+
+ .justify-content-sm-center {
+ justify-content: center !important;
+ }
+
+ .justify-content-sm-between {
+ justify-content: space-between !important;
+ }
+
+ .justify-content-sm-around {
+ justify-content: space-around !important;
+ }
+
+ .justify-content-sm-evenly {
+ justify-content: space-evenly !important;
+ }
+
+ .align-items-sm-start {
+ align-items: flex-start !important;
+ }
+
+ .align-items-sm-end {
+ align-items: flex-end !important;
+ }
+
+ .align-items-sm-center {
+ align-items: center !important;
+ }
+
+ .align-items-sm-baseline {
+ align-items: baseline !important;
+ }
+
+ .align-items-sm-stretch {
+ align-items: stretch !important;
+ }
+
+ .align-content-sm-start {
+ align-content: flex-start !important;
+ }
+
+ .align-content-sm-end {
+ align-content: flex-end !important;
+ }
+
+ .align-content-sm-center {
+ align-content: center !important;
+ }
+
+ .align-content-sm-between {
+ align-content: space-between !important;
+ }
+
+ .align-content-sm-around {
+ align-content: space-around !important;
+ }
+
+ .align-content-sm-stretch {
+ align-content: stretch !important;
+ }
+
+ .align-self-sm-auto {
+ align-self: auto !important;
+ }
+
+ .align-self-sm-start {
+ align-self: flex-start !important;
+ }
+
+ .align-self-sm-end {
+ align-self: flex-end !important;
+ }
+
+ .align-self-sm-center {
+ align-self: center !important;
+ }
+
+ .align-self-sm-baseline {
+ align-self: baseline !important;
+ }
+
+ .align-self-sm-stretch {
+ align-self: stretch !important;
+ }
+
+ .order-sm-first {
+ order: -1 !important;
+ }
+
+ .order-sm-0 {
+ order: 0 !important;
+ }
+
+ .order-sm-1 {
+ order: 1 !important;
+ }
+
+ .order-sm-2 {
+ order: 2 !important;
+ }
+
+ .order-sm-3 {
+ order: 3 !important;
+ }
+
+ .order-sm-4 {
+ order: 4 !important;
+ }
+
+ .order-sm-5 {
+ order: 5 !important;
+ }
+
+ .order-sm-last {
+ order: 6 !important;
+ }
+
+ .m-sm-0 {
+ margin: 0 !important;
+ }
+
+ .m-sm-1 {
+ margin: 0.25rem !important;
+ }
+
+ .m-sm-2 {
+ margin: 0.5rem !important;
+ }
+
+ .m-sm-3 {
+ margin: 1rem !important;
+ }
+
+ .m-sm-4 {
+ margin: 1.5rem !important;
+ }
+
+ .m-sm-5 {
+ margin: 3rem !important;
+ }
+
+ .m-sm-auto {
+ margin: auto !important;
+ }
+
+ .mx-sm-0 {
+ margin-right: 0 !important;
+ margin-left: 0 !important;
+ }
+
+ .mx-sm-1 {
+ margin-right: 0.25rem !important;
+ margin-left: 0.25rem !important;
+ }
+
+ .mx-sm-2 {
+ margin-right: 0.5rem !important;
+ margin-left: 0.5rem !important;
+ }
+
+ .mx-sm-3 {
+ margin-right: 1rem !important;
+ margin-left: 1rem !important;
+ }
+
+ .mx-sm-4 {
+ margin-right: 1.5rem !important;
+ margin-left: 1.5rem !important;
+ }
+
+ .mx-sm-5 {
+ margin-right: 3rem !important;
+ margin-left: 3rem !important;
+ }
+
+ .mx-sm-auto {
+ margin-right: auto !important;
+ margin-left: auto !important;
+ }
+
+ .my-sm-0 {
+ margin-top: 0 !important;
+ margin-bottom: 0 !important;
+ }
+
+ .my-sm-1 {
+ margin-top: 0.25rem !important;
+ margin-bottom: 0.25rem !important;
+ }
+
+ .my-sm-2 {
+ margin-top: 0.5rem !important;
+ margin-bottom: 0.5rem !important;
+ }
+
+ .my-sm-3 {
+ margin-top: 1rem !important;
+ margin-bottom: 1rem !important;
+ }
+
+ .my-sm-4 {
+ margin-top: 1.5rem !important;
+ margin-bottom: 1.5rem !important;
+ }
+
+ .my-sm-5 {
+ margin-top: 3rem !important;
+ margin-bottom: 3rem !important;
+ }
+
+ .my-sm-auto {
+ margin-top: auto !important;
+ margin-bottom: auto !important;
+ }
+
+ .mt-sm-0 {
+ margin-top: 0 !important;
+ }
+
+ .mt-sm-1 {
+ margin-top: 0.25rem !important;
+ }
+
+ .mt-sm-2 {
+ margin-top: 0.5rem !important;
+ }
+
+ .mt-sm-3 {
+ margin-top: 1rem !important;
+ }
+
+ .mt-sm-4 {
+ margin-top: 1.5rem !important;
+ }
+
+ .mt-sm-5 {
+ margin-top: 3rem !important;
+ }
+
+ .mt-sm-auto {
+ margin-top: auto !important;
+ }
+
+ .me-sm-0 {
+ margin-right: 0 !important;
+ }
+
+ .me-sm-1 {
+ margin-right: 0.25rem !important;
+ }
+
+ .me-sm-2 {
+ margin-right: 0.5rem !important;
+ }
+
+ .me-sm-3 {
+ margin-right: 1rem !important;
+ }
+
+ .me-sm-4 {
+ margin-right: 1.5rem !important;
+ }
+
+ .me-sm-5 {
+ margin-right: 3rem !important;
+ }
+
+ .me-sm-auto {
+ margin-right: auto !important;
+ }
+
+ .mb-sm-0 {
+ margin-bottom: 0 !important;
+ }
+
+ .mb-sm-1 {
+ margin-bottom: 0.25rem !important;
+ }
+
+ .mb-sm-2 {
+ margin-bottom: 0.5rem !important;
+ }
+
+ .mb-sm-3 {
+ margin-bottom: 1rem !important;
+ }
+
+ .mb-sm-4 {
+ margin-bottom: 1.5rem !important;
+ }
+
+ .mb-sm-5 {
+ margin-bottom: 3rem !important;
+ }
+
+ .mb-sm-auto {
+ margin-bottom: auto !important;
+ }
+
+ .ms-sm-0 {
+ margin-left: 0 !important;
+ }
+
+ .ms-sm-1 {
+ margin-left: 0.25rem !important;
+ }
+
+ .ms-sm-2 {
+ margin-left: 0.5rem !important;
+ }
+
+ .ms-sm-3 {
+ margin-left: 1rem !important;
+ }
+
+ .ms-sm-4 {
+ margin-left: 1.5rem !important;
+ }
+
+ .ms-sm-5 {
+ margin-left: 3rem !important;
+ }
+
+ .ms-sm-auto {
+ margin-left: auto !important;
+ }
+
+ .p-sm-0 {
+ padding: 0 !important;
+ }
+
+ .p-sm-1 {
+ padding: 0.25rem !important;
+ }
+
+ .p-sm-2 {
+ padding: 0.5rem !important;
+ }
+
+ .p-sm-3 {
+ padding: 1rem !important;
+ }
+
+ .p-sm-4 {
+ padding: 1.5rem !important;
+ }
+
+ .p-sm-5 {
+ padding: 3rem !important;
+ }
+
+ .px-sm-0 {
+ padding-right: 0 !important;
+ padding-left: 0 !important;
+ }
+
+ .px-sm-1 {
+ padding-right: 0.25rem !important;
+ padding-left: 0.25rem !important;
+ }
+
+ .px-sm-2 {
+ padding-right: 0.5rem !important;
+ padding-left: 0.5rem !important;
+ }
+
+ .px-sm-3 {
+ padding-right: 1rem !important;
+ padding-left: 1rem !important;
+ }
+
+ .px-sm-4 {
+ padding-right: 1.5rem !important;
+ padding-left: 1.5rem !important;
+ }
+
+ .px-sm-5 {
+ padding-right: 3rem !important;
+ padding-left: 3rem !important;
+ }
+
+ .py-sm-0 {
+ padding-top: 0 !important;
+ padding-bottom: 0 !important;
+ }
+
+ .py-sm-1 {
+ padding-top: 0.25rem !important;
+ padding-bottom: 0.25rem !important;
+ }
+
+ .py-sm-2 {
+ padding-top: 0.5rem !important;
+ padding-bottom: 0.5rem !important;
+ }
+
+ .py-sm-3 {
+ padding-top: 1rem !important;
+ padding-bottom: 1rem !important;
+ }
+
+ .py-sm-4 {
+ padding-top: 1.5rem !important;
+ padding-bottom: 1.5rem !important;
+ }
+
+ .py-sm-5 {
+ padding-top: 3rem !important;
+ padding-bottom: 3rem !important;
+ }
+
+ .pt-sm-0 {
+ padding-top: 0 !important;
+ }
+
+ .pt-sm-1 {
+ padding-top: 0.25rem !important;
+ }
+
+ .pt-sm-2 {
+ padding-top: 0.5rem !important;
+ }
+
+ .pt-sm-3 {
+ padding-top: 1rem !important;
+ }
+
+ .pt-sm-4 {
+ padding-top: 1.5rem !important;
+ }
+
+ .pt-sm-5 {
+ padding-top: 3rem !important;
+ }
+
+ .pe-sm-0 {
+ padding-right: 0 !important;
+ }
+
+ .pe-sm-1 {
+ padding-right: 0.25rem !important;
+ }
+
+ .pe-sm-2 {
+ padding-right: 0.5rem !important;
+ }
+
+ .pe-sm-3 {
+ padding-right: 1rem !important;
+ }
+
+ .pe-sm-4 {
+ padding-right: 1.5rem !important;
+ }
+
+ .pe-sm-5 {
+ padding-right: 3rem !important;
+ }
+
+ .pb-sm-0 {
+ padding-bottom: 0 !important;
+ }
+
+ .pb-sm-1 {
+ padding-bottom: 0.25rem !important;
+ }
+
+ .pb-sm-2 {
+ padding-bottom: 0.5rem !important;
+ }
+
+ .pb-sm-3 {
+ padding-bottom: 1rem !important;
+ }
+
+ .pb-sm-4 {
+ padding-bottom: 1.5rem !important;
+ }
+
+ .pb-sm-5 {
+ padding-bottom: 3rem !important;
+ }
+
+ .ps-sm-0 {
+ padding-left: 0 !important;
+ }
+
+ .ps-sm-1 {
+ padding-left: 0.25rem !important;
+ }
+
+ .ps-sm-2 {
+ padding-left: 0.5rem !important;
+ }
+
+ .ps-sm-3 {
+ padding-left: 1rem !important;
+ }
+
+ .ps-sm-4 {
+ padding-left: 1.5rem !important;
+ }
+
+ .ps-sm-5 {
+ padding-left: 3rem !important;
+ }
+
+ .text-sm-start {
+ text-align: left !important;
+ }
+
+ .text-sm-end {
+ text-align: right !important;
+ }
+
+ .text-sm-center {
+ text-align: center !important;
+ }
+}
+@media (min-width: 768px) {
+ .float-md-start {
+ float: left !important;
+ }
+
+ .float-md-end {
+ float: right !important;
+ }
+
+ .float-md-none {
+ float: none !important;
+ }
+
+ .d-md-inline {
+ display: inline !important;
+ }
+
+ .d-md-inline-block {
+ display: inline-block !important;
+ }
+
+ .d-md-block {
+ display: block !important;
+ }
+
+ .d-md-grid {
+ display: grid !important;
+ }
+
+ .d-md-table {
+ display: table !important;
+ }
+
+ .d-md-table-row {
+ display: table-row !important;
+ }
+
+ .d-md-table-cell {
+ display: table-cell !important;
+ }
+
+ .d-md-flex {
+ display: flex !important;
+ }
+
+ .d-md-inline-flex {
+ display: inline-flex !important;
+ }
+
+ .d-md-none {
+ display: none !important;
+ }
+
+ .flex-md-fill {
+ flex: 1 1 auto !important;
+ }
+
+ .flex-md-row {
+ flex-direction: row !important;
+ }
+
+ .flex-md-column {
+ flex-direction: column !important;
+ }
+
+ .flex-md-row-reverse {
+ flex-direction: row-reverse !important;
+ }
+
+ .flex-md-column-reverse {
+ flex-direction: column-reverse !important;
+ }
+
+ .flex-md-grow-0 {
+ flex-grow: 0 !important;
+ }
+
+ .flex-md-grow-1 {
+ flex-grow: 1 !important;
+ }
+
+ .flex-md-shrink-0 {
+ flex-shrink: 0 !important;
+ }
+
+ .flex-md-shrink-1 {
+ flex-shrink: 1 !important;
+ }
+
+ .flex-md-wrap {
+ flex-wrap: wrap !important;
+ }
+
+ .flex-md-nowrap {
+ flex-wrap: nowrap !important;
+ }
+
+ .flex-md-wrap-reverse {
+ flex-wrap: wrap-reverse !important;
+ }
+
+ .gap-md-0 {
+ gap: 0 !important;
+ }
+
+ .gap-md-1 {
+ gap: 0.25rem !important;
+ }
+
+ .gap-md-2 {
+ gap: 0.5rem !important;
+ }
+
+ .gap-md-3 {
+ gap: 1rem !important;
+ }
+
+ .gap-md-4 {
+ gap: 1.5rem !important;
+ }
+
+ .gap-md-5 {
+ gap: 3rem !important;
+ }
+
+ .justify-content-md-start {
+ justify-content: flex-start !important;
+ }
+
+ .justify-content-md-end {
+ justify-content: flex-end !important;
+ }
+
+ .justify-content-md-center {
+ justify-content: center !important;
+ }
+
+ .justify-content-md-between {
+ justify-content: space-between !important;
+ }
+
+ .justify-content-md-around {
+ justify-content: space-around !important;
+ }
+
+ .justify-content-md-evenly {
+ justify-content: space-evenly !important;
+ }
+
+ .align-items-md-start {
+ align-items: flex-start !important;
+ }
+
+ .align-items-md-end {
+ align-items: flex-end !important;
+ }
+
+ .align-items-md-center {
+ align-items: center !important;
+ }
+
+ .align-items-md-baseline {
+ align-items: baseline !important;
+ }
+
+ .align-items-md-stretch {
+ align-items: stretch !important;
+ }
+
+ .align-content-md-start {
+ align-content: flex-start !important;
+ }
+
+ .align-content-md-end {
+ align-content: flex-end !important;
+ }
+
+ .align-content-md-center {
+ align-content: center !important;
+ }
+
+ .align-content-md-between {
+ align-content: space-between !important;
+ }
+
+ .align-content-md-around {
+ align-content: space-around !important;
+ }
+
+ .align-content-md-stretch {
+ align-content: stretch !important;
+ }
+
+ .align-self-md-auto {
+ align-self: auto !important;
+ }
+
+ .align-self-md-start {
+ align-self: flex-start !important;
+ }
+
+ .align-self-md-end {
+ align-self: flex-end !important;
+ }
+
+ .align-self-md-center {
+ align-self: center !important;
+ }
+
+ .align-self-md-baseline {
+ align-self: baseline !important;
+ }
+
+ .align-self-md-stretch {
+ align-self: stretch !important;
+ }
+
+ .order-md-first {
+ order: -1 !important;
+ }
+
+ .order-md-0 {
+ order: 0 !important;
+ }
+
+ .order-md-1 {
+ order: 1 !important;
+ }
+
+ .order-md-2 {
+ order: 2 !important;
+ }
+
+ .order-md-3 {
+ order: 3 !important;
+ }
+
+ .order-md-4 {
+ order: 4 !important;
+ }
+
+ .order-md-5 {
+ order: 5 !important;
+ }
+
+ .order-md-last {
+ order: 6 !important;
+ }
+
+ .m-md-0 {
+ margin: 0 !important;
+ }
+
+ .m-md-1 {
+ margin: 0.25rem !important;
+ }
+
+ .m-md-2 {
+ margin: 0.5rem !important;
+ }
+
+ .m-md-3 {
+ margin: 1rem !important;
+ }
+
+ .m-md-4 {
+ margin: 1.5rem !important;
+ }
+
+ .m-md-5 {
+ margin: 3rem !important;
+ }
+
+ .m-md-auto {
+ margin: auto !important;
+ }
+
+ .mx-md-0 {
+ margin-right: 0 !important;
+ margin-left: 0 !important;
+ }
+
+ .mx-md-1 {
+ margin-right: 0.25rem !important;
+ margin-left: 0.25rem !important;
+ }
+
+ .mx-md-2 {
+ margin-right: 0.5rem !important;
+ margin-left: 0.5rem !important;
+ }
+
+ .mx-md-3 {
+ margin-right: 1rem !important;
+ margin-left: 1rem !important;
+ }
+
+ .mx-md-4 {
+ margin-right: 1.5rem !important;
+ margin-left: 1.5rem !important;
+ }
+
+ .mx-md-5 {
+ margin-right: 3rem !important;
+ margin-left: 3rem !important;
+ }
+
+ .mx-md-auto {
+ margin-right: auto !important;
+ margin-left: auto !important;
+ }
+
+ .my-md-0 {
+ margin-top: 0 !important;
+ margin-bottom: 0 !important;
+ }
+
+ .my-md-1 {
+ margin-top: 0.25rem !important;
+ margin-bottom: 0.25rem !important;
+ }
+
+ .my-md-2 {
+ margin-top: 0.5rem !important;
+ margin-bottom: 0.5rem !important;
+ }
+
+ .my-md-3 {
+ margin-top: 1rem !important;
+ margin-bottom: 1rem !important;
+ }
+
+ .my-md-4 {
+ margin-top: 1.5rem !important;
+ margin-bottom: 1.5rem !important;
+ }
+
+ .my-md-5 {
+ margin-top: 3rem !important;
+ margin-bottom: 3rem !important;
+ }
+
+ .my-md-auto {
+ margin-top: auto !important;
+ margin-bottom: auto !important;
+ }
+
+ .mt-md-0 {
+ margin-top: 0 !important;
+ }
+
+ .mt-md-1 {
+ margin-top: 0.25rem !important;
+ }
+
+ .mt-md-2 {
+ margin-top: 0.5rem !important;
+ }
+
+ .mt-md-3 {
+ margin-top: 1rem !important;
+ }
+
+ .mt-md-4 {
+ margin-top: 1.5rem !important;
+ }
+
+ .mt-md-5 {
+ margin-top: 3rem !important;
+ }
+
+ .mt-md-auto {
+ margin-top: auto !important;
+ }
+
+ .me-md-0 {
+ margin-right: 0 !important;
+ }
+
+ .me-md-1 {
+ margin-right: 0.25rem !important;
+ }
+
+ .me-md-2 {
+ margin-right: 0.5rem !important;
+ }
+
+ .me-md-3 {
+ margin-right: 1rem !important;
+ }
+
+ .me-md-4 {
+ margin-right: 1.5rem !important;
+ }
+
+ .me-md-5 {
+ margin-right: 3rem !important;
+ }
+
+ .me-md-auto {
+ margin-right: auto !important;
+ }
+
+ .mb-md-0 {
+ margin-bottom: 0 !important;
+ }
+
+ .mb-md-1 {
+ margin-bottom: 0.25rem !important;
+ }
+
+ .mb-md-2 {
+ margin-bottom: 0.5rem !important;
+ }
+
+ .mb-md-3 {
+ margin-bottom: 1rem !important;
+ }
+
+ .mb-md-4 {
+ margin-bottom: 1.5rem !important;
+ }
+
+ .mb-md-5 {
+ margin-bottom: 3rem !important;
+ }
+
+ .mb-md-auto {
+ margin-bottom: auto !important;
+ }
+
+ .ms-md-0 {
+ margin-left: 0 !important;
+ }
+
+ .ms-md-1 {
+ margin-left: 0.25rem !important;
+ }
+
+ .ms-md-2 {
+ margin-left: 0.5rem !important;
+ }
+
+ .ms-md-3 {
+ margin-left: 1rem !important;
+ }
+
+ .ms-md-4 {
+ margin-left: 1.5rem !important;
+ }
+
+ .ms-md-5 {
+ margin-left: 3rem !important;
+ }
+
+ .ms-md-auto {
+ margin-left: auto !important;
+ }
+
+ .p-md-0 {
+ padding: 0 !important;
+ }
+
+ .p-md-1 {
+ padding: 0.25rem !important;
+ }
+
+ .p-md-2 {
+ padding: 0.5rem !important;
+ }
+
+ .p-md-3 {
+ padding: 1rem !important;
+ }
+
+ .p-md-4 {
+ padding: 1.5rem !important;
+ }
+
+ .p-md-5 {
+ padding: 3rem !important;
+ }
+
+ .px-md-0 {
+ padding-right: 0 !important;
+ padding-left: 0 !important;
+ }
+
+ .px-md-1 {
+ padding-right: 0.25rem !important;
+ padding-left: 0.25rem !important;
+ }
+
+ .px-md-2 {
+ padding-right: 0.5rem !important;
+ padding-left: 0.5rem !important;
+ }
+
+ .px-md-3 {
+ padding-right: 1rem !important;
+ padding-left: 1rem !important;
+ }
+
+ .px-md-4 {
+ padding-right: 1.5rem !important;
+ padding-left: 1.5rem !important;
+ }
+
+ .px-md-5 {
+ padding-right: 3rem !important;
+ padding-left: 3rem !important;
+ }
+
+ .py-md-0 {
+ padding-top: 0 !important;
+ padding-bottom: 0 !important;
+ }
+
+ .py-md-1 {
+ padding-top: 0.25rem !important;
+ padding-bottom: 0.25rem !important;
+ }
+
+ .py-md-2 {
+ padding-top: 0.5rem !important;
+ padding-bottom: 0.5rem !important;
+ }
+
+ .py-md-3 {
+ padding-top: 1rem !important;
+ padding-bottom: 1rem !important;
+ }
+
+ .py-md-4 {
+ padding-top: 1.5rem !important;
+ padding-bottom: 1.5rem !important;
+ }
+
+ .py-md-5 {
+ padding-top: 3rem !important;
+ padding-bottom: 3rem !important;
+ }
+
+ .pt-md-0 {
+ padding-top: 0 !important;
+ }
+
+ .pt-md-1 {
+ padding-top: 0.25rem !important;
+ }
+
+ .pt-md-2 {
+ padding-top: 0.5rem !important;
+ }
+
+ .pt-md-3 {
+ padding-top: 1rem !important;
+ }
+
+ .pt-md-4 {
+ padding-top: 1.5rem !important;
+ }
+
+ .pt-md-5 {
+ padding-top: 3rem !important;
+ }
+
+ .pe-md-0 {
+ padding-right: 0 !important;
+ }
+
+ .pe-md-1 {
+ padding-right: 0.25rem !important;
+ }
+
+ .pe-md-2 {
+ padding-right: 0.5rem !important;
+ }
+
+ .pe-md-3 {
+ padding-right: 1rem !important;
+ }
+
+ .pe-md-4 {
+ padding-right: 1.5rem !important;
+ }
+
+ .pe-md-5 {
+ padding-right: 3rem !important;
+ }
+
+ .pb-md-0 {
+ padding-bottom: 0 !important;
+ }
+
+ .pb-md-1 {
+ padding-bottom: 0.25rem !important;
+ }
+
+ .pb-md-2 {
+ padding-bottom: 0.5rem !important;
+ }
+
+ .pb-md-3 {
+ padding-bottom: 1rem !important;
+ }
+
+ .pb-md-4 {
+ padding-bottom: 1.5rem !important;
+ }
+
+ .pb-md-5 {
+ padding-bottom: 3rem !important;
+ }
+
+ .ps-md-0 {
+ padding-left: 0 !important;
+ }
+
+ .ps-md-1 {
+ padding-left: 0.25rem !important;
+ }
+
+ .ps-md-2 {
+ padding-left: 0.5rem !important;
+ }
+
+ .ps-md-3 {
+ padding-left: 1rem !important;
+ }
+
+ .ps-md-4 {
+ padding-left: 1.5rem !important;
+ }
+
+ .ps-md-5 {
+ padding-left: 3rem !important;
+ }
+
+ .text-md-start {
+ text-align: left !important;
+ }
+
+ .text-md-end {
+ text-align: right !important;
+ }
+
+ .text-md-center {
+ text-align: center !important;
+ }
+}
+@media (min-width: 992px) {
+ .float-lg-start {
+ float: left !important;
+ }
+
+ .float-lg-end {
+ float: right !important;
+ }
+
+ .float-lg-none {
+ float: none !important;
+ }
+
+ .d-lg-inline {
+ display: inline !important;
+ }
+
+ .d-lg-inline-block {
+ display: inline-block !important;
+ }
+
+ .d-lg-block {
+ display: block !important;
+ }
+
+ .d-lg-grid {
+ display: grid !important;
+ }
+
+ .d-lg-table {
+ display: table !important;
+ }
+
+ .d-lg-table-row {
+ display: table-row !important;
+ }
+
+ .d-lg-table-cell {
+ display: table-cell !important;
+ }
+
+ .d-lg-flex {
+ display: flex !important;
+ }
+
+ .d-lg-inline-flex {
+ display: inline-flex !important;
+ }
+
+ .d-lg-none {
+ display: none !important;
+ }
+
+ .flex-lg-fill {
+ flex: 1 1 auto !important;
+ }
+
+ .flex-lg-row {
+ flex-direction: row !important;
+ }
+
+ .flex-lg-column {
+ flex-direction: column !important;
+ }
+
+ .flex-lg-row-reverse {
+ flex-direction: row-reverse !important;
+ }
+
+ .flex-lg-column-reverse {
+ flex-direction: column-reverse !important;
+ }
+
+ .flex-lg-grow-0 {
+ flex-grow: 0 !important;
+ }
+
+ .flex-lg-grow-1 {
+ flex-grow: 1 !important;
+ }
+
+ .flex-lg-shrink-0 {
+ flex-shrink: 0 !important;
+ }
+
+ .flex-lg-shrink-1 {
+ flex-shrink: 1 !important;
+ }
+
+ .flex-lg-wrap {
+ flex-wrap: wrap !important;
+ }
+
+ .flex-lg-nowrap {
+ flex-wrap: nowrap !important;
+ }
+
+ .flex-lg-wrap-reverse {
+ flex-wrap: wrap-reverse !important;
+ }
+
+ .gap-lg-0 {
+ gap: 0 !important;
+ }
+
+ .gap-lg-1 {
+ gap: 0.25rem !important;
+ }
+
+ .gap-lg-2 {
+ gap: 0.5rem !important;
+ }
+
+ .gap-lg-3 {
+ gap: 1rem !important;
+ }
+
+ .gap-lg-4 {
+ gap: 1.5rem !important;
+ }
+
+ .gap-lg-5 {
+ gap: 3rem !important;
+ }
+
+ .justify-content-lg-start {
+ justify-content: flex-start !important;
+ }
+
+ .justify-content-lg-end {
+ justify-content: flex-end !important;
+ }
+
+ .justify-content-lg-center {
+ justify-content: center !important;
+ }
+
+ .justify-content-lg-between {
+ justify-content: space-between !important;
+ }
+
+ .justify-content-lg-around {
+ justify-content: space-around !important;
+ }
+
+ .justify-content-lg-evenly {
+ justify-content: space-evenly !important;
+ }
+
+ .align-items-lg-start {
+ align-items: flex-start !important;
+ }
+
+ .align-items-lg-end {
+ align-items: flex-end !important;
+ }
+
+ .align-items-lg-center {
+ align-items: center !important;
+ }
+
+ .align-items-lg-baseline {
+ align-items: baseline !important;
+ }
+
+ .align-items-lg-stretch {
+ align-items: stretch !important;
+ }
+
+ .align-content-lg-start {
+ align-content: flex-start !important;
+ }
+
+ .align-content-lg-end {
+ align-content: flex-end !important;
+ }
+
+ .align-content-lg-center {
+ align-content: center !important;
+ }
+
+ .align-content-lg-between {
+ align-content: space-between !important;
+ }
+
+ .align-content-lg-around {
+ align-content: space-around !important;
+ }
+
+ .align-content-lg-stretch {
+ align-content: stretch !important;
+ }
+
+ .align-self-lg-auto {
+ align-self: auto !important;
+ }
+
+ .align-self-lg-start {
+ align-self: flex-start !important;
+ }
+
+ .align-self-lg-end {
+ align-self: flex-end !important;
+ }
+
+ .align-self-lg-center {
+ align-self: center !important;
+ }
+
+ .align-self-lg-baseline {
+ align-self: baseline !important;
+ }
+
+ .align-self-lg-stretch {
+ align-self: stretch !important;
+ }
+
+ .order-lg-first {
+ order: -1 !important;
+ }
+
+ .order-lg-0 {
+ order: 0 !important;
+ }
+
+ .order-lg-1 {
+ order: 1 !important;
+ }
+
+ .order-lg-2 {
+ order: 2 !important;
+ }
+
+ .order-lg-3 {
+ order: 3 !important;
+ }
+
+ .order-lg-4 {
+ order: 4 !important;
+ }
+
+ .order-lg-5 {
+ order: 5 !important;
+ }
+
+ .order-lg-last {
+ order: 6 !important;
+ }
+
+ .m-lg-0 {
+ margin: 0 !important;
+ }
+
+ .m-lg-1 {
+ margin: 0.25rem !important;
+ }
+
+ .m-lg-2 {
+ margin: 0.5rem !important;
+ }
+
+ .m-lg-3 {
+ margin: 1rem !important;
+ }
+
+ .m-lg-4 {
+ margin: 1.5rem !important;
+ }
+
+ .m-lg-5 {
+ margin: 3rem !important;
+ }
+
+ .m-lg-auto {
+ margin: auto !important;
+ }
+
+ .mx-lg-0 {
+ margin-right: 0 !important;
+ margin-left: 0 !important;
+ }
+
+ .mx-lg-1 {
+ margin-right: 0.25rem !important;
+ margin-left: 0.25rem !important;
+ }
+
+ .mx-lg-2 {
+ margin-right: 0.5rem !important;
+ margin-left: 0.5rem !important;
+ }
+
+ .mx-lg-3 {
+ margin-right: 1rem !important;
+ margin-left: 1rem !important;
+ }
+
+ .mx-lg-4 {
+ margin-right: 1.5rem !important;
+ margin-left: 1.5rem !important;
+ }
+
+ .mx-lg-5 {
+ margin-right: 3rem !important;
+ margin-left: 3rem !important;
+ }
+
+ .mx-lg-auto {
+ margin-right: auto !important;
+ margin-left: auto !important;
+ }
+
+ .my-lg-0 {
+ margin-top: 0 !important;
+ margin-bottom: 0 !important;
+ }
+
+ .my-lg-1 {
+ margin-top: 0.25rem !important;
+ margin-bottom: 0.25rem !important;
+ }
+
+ .my-lg-2 {
+ margin-top: 0.5rem !important;
+ margin-bottom: 0.5rem !important;
+ }
+
+ .my-lg-3 {
+ margin-top: 1rem !important;
+ margin-bottom: 1rem !important;
+ }
+
+ .my-lg-4 {
+ margin-top: 1.5rem !important;
+ margin-bottom: 1.5rem !important;
+ }
+
+ .my-lg-5 {
+ margin-top: 3rem !important;
+ margin-bottom: 3rem !important;
+ }
+
+ .my-lg-auto {
+ margin-top: auto !important;
+ margin-bottom: auto !important;
+ }
+
+ .mt-lg-0 {
+ margin-top: 0 !important;
+ }
+
+ .mt-lg-1 {
+ margin-top: 0.25rem !important;
+ }
+
+ .mt-lg-2 {
+ margin-top: 0.5rem !important;
+ }
+
+ .mt-lg-3 {
+ margin-top: 1rem !important;
+ }
+
+ .mt-lg-4 {
+ margin-top: 1.5rem !important;
+ }
+
+ .mt-lg-5 {
+ margin-top: 3rem !important;
+ }
+
+ .mt-lg-auto {
+ margin-top: auto !important;
+ }
+
+ .me-lg-0 {
+ margin-right: 0 !important;
+ }
+
+ .me-lg-1 {
+ margin-right: 0.25rem !important;
+ }
+
+ .me-lg-2 {
+ margin-right: 0.5rem !important;
+ }
+
+ .me-lg-3 {
+ margin-right: 1rem !important;
+ }
+
+ .me-lg-4 {
+ margin-right: 1.5rem !important;
+ }
+
+ .me-lg-5 {
+ margin-right: 3rem !important;
+ }
+
+ .me-lg-auto {
+ margin-right: auto !important;
+ }
+
+ .mb-lg-0 {
+ margin-bottom: 0 !important;
+ }
+
+ .mb-lg-1 {
+ margin-bottom: 0.25rem !important;
+ }
+
+ .mb-lg-2 {
+ margin-bottom: 0.5rem !important;
+ }
+
+ .mb-lg-3 {
+ margin-bottom: 1rem !important;
+ }
+
+ .mb-lg-4 {
+ margin-bottom: 1.5rem !important;
+ }
+
+ .mb-lg-5 {
+ margin-bottom: 3rem !important;
+ }
+
+ .mb-lg-auto {
+ margin-bottom: auto !important;
+ }
+
+ .ms-lg-0 {
+ margin-left: 0 !important;
+ }
+
+ .ms-lg-1 {
+ margin-left: 0.25rem !important;
+ }
+
+ .ms-lg-2 {
+ margin-left: 0.5rem !important;
+ }
+
+ .ms-lg-3 {
+ margin-left: 1rem !important;
+ }
+
+ .ms-lg-4 {
+ margin-left: 1.5rem !important;
+ }
+
+ .ms-lg-5 {
+ margin-left: 3rem !important;
+ }
+
+ .ms-lg-auto {
+ margin-left: auto !important;
+ }
+
+ .p-lg-0 {
+ padding: 0 !important;
+ }
+
+ .p-lg-1 {
+ padding: 0.25rem !important;
+ }
+
+ .p-lg-2 {
+ padding: 0.5rem !important;
+ }
+
+ .p-lg-3 {
+ padding: 1rem !important;
+ }
+
+ .p-lg-4 {
+ padding: 1.5rem !important;
+ }
+
+ .p-lg-5 {
+ padding: 3rem !important;
+ }
+
+ .px-lg-0 {
+ padding-right: 0 !important;
+ padding-left: 0 !important;
+ }
+
+ .px-lg-1 {
+ padding-right: 0.25rem !important;
+ padding-left: 0.25rem !important;
+ }
+
+ .px-lg-2 {
+ padding-right: 0.5rem !important;
+ padding-left: 0.5rem !important;
+ }
+
+ .px-lg-3 {
+ padding-right: 1rem !important;
+ padding-left: 1rem !important;
+ }
+
+ .px-lg-4 {
+ padding-right: 1.5rem !important;
+ padding-left: 1.5rem !important;
+ }
+
+ .px-lg-5 {
+ padding-right: 3rem !important;
+ padding-left: 3rem !important;
+ }
+
+ .py-lg-0 {
+ padding-top: 0 !important;
+ padding-bottom: 0 !important;
+ }
+
+ .py-lg-1 {
+ padding-top: 0.25rem !important;
+ padding-bottom: 0.25rem !important;
+ }
+
+ .py-lg-2 {
+ padding-top: 0.5rem !important;
+ padding-bottom: 0.5rem !important;
+ }
+
+ .py-lg-3 {
+ padding-top: 1rem !important;
+ padding-bottom: 1rem !important;
+ }
+
+ .py-lg-4 {
+ padding-top: 1.5rem !important;
+ padding-bottom: 1.5rem !important;
+ }
+
+ .py-lg-5 {
+ padding-top: 3rem !important;
+ padding-bottom: 3rem !important;
+ }
+
+ .pt-lg-0 {
+ padding-top: 0 !important;
+ }
+
+ .pt-lg-1 {
+ padding-top: 0.25rem !important;
+ }
+
+ .pt-lg-2 {
+ padding-top: 0.5rem !important;
+ }
+
+ .pt-lg-3 {
+ padding-top: 1rem !important;
+ }
+
+ .pt-lg-4 {
+ padding-top: 1.5rem !important;
+ }
+
+ .pt-lg-5 {
+ padding-top: 3rem !important;
+ }
+
+ .pe-lg-0 {
+ padding-right: 0 !important;
+ }
+
+ .pe-lg-1 {
+ padding-right: 0.25rem !important;
+ }
+
+ .pe-lg-2 {
+ padding-right: 0.5rem !important;
+ }
+
+ .pe-lg-3 {
+ padding-right: 1rem !important;
+ }
+
+ .pe-lg-4 {
+ padding-right: 1.5rem !important;
+ }
+
+ .pe-lg-5 {
+ padding-right: 3rem !important;
+ }
+
+ .pb-lg-0 {
+ padding-bottom: 0 !important;
+ }
+
+ .pb-lg-1 {
+ padding-bottom: 0.25rem !important;
+ }
+
+ .pb-lg-2 {
+ padding-bottom: 0.5rem !important;
+ }
+
+ .pb-lg-3 {
+ padding-bottom: 1rem !important;
+ }
+
+ .pb-lg-4 {
+ padding-bottom: 1.5rem !important;
+ }
+
+ .pb-lg-5 {
+ padding-bottom: 3rem !important;
+ }
+
+ .ps-lg-0 {
+ padding-left: 0 !important;
+ }
+
+ .ps-lg-1 {
+ padding-left: 0.25rem !important;
+ }
+
+ .ps-lg-2 {
+ padding-left: 0.5rem !important;
+ }
+
+ .ps-lg-3 {
+ padding-left: 1rem !important;
+ }
+
+ .ps-lg-4 {
+ padding-left: 1.5rem !important;
+ }
+
+ .ps-lg-5 {
+ padding-left: 3rem !important;
+ }
+
+ .text-lg-start {
+ text-align: left !important;
+ }
+
+ .text-lg-end {
+ text-align: right !important;
+ }
+
+ .text-lg-center {
+ text-align: center !important;
+ }
+}
+@media (min-width: 1200px) {
+ .float-xl-start {
+ float: left !important;
+ }
+
+ .float-xl-end {
+ float: right !important;
+ }
+
+ .float-xl-none {
+ float: none !important;
+ }
+
+ .d-xl-inline {
+ display: inline !important;
+ }
+
+ .d-xl-inline-block {
+ display: inline-block !important;
+ }
+
+ .d-xl-block {
+ display: block !important;
+ }
+
+ .d-xl-grid {
+ display: grid !important;
+ }
+
+ .d-xl-table {
+ display: table !important;
+ }
+
+ .d-xl-table-row {
+ display: table-row !important;
+ }
+
+ .d-xl-table-cell {
+ display: table-cell !important;
+ }
+
+ .d-xl-flex {
+ display: flex !important;
+ }
+
+ .d-xl-inline-flex {
+ display: inline-flex !important;
+ }
+
+ .d-xl-none {
+ display: none !important;
+ }
+
+ .flex-xl-fill {
+ flex: 1 1 auto !important;
+ }
+
+ .flex-xl-row {
+ flex-direction: row !important;
+ }
+
+ .flex-xl-column {
+ flex-direction: column !important;
+ }
+
+ .flex-xl-row-reverse {
+ flex-direction: row-reverse !important;
+ }
+
+ .flex-xl-column-reverse {
+ flex-direction: column-reverse !important;
+ }
+
+ .flex-xl-grow-0 {
+ flex-grow: 0 !important;
+ }
+
+ .flex-xl-grow-1 {
+ flex-grow: 1 !important;
+ }
+
+ .flex-xl-shrink-0 {
+ flex-shrink: 0 !important;
+ }
+
+ .flex-xl-shrink-1 {
+ flex-shrink: 1 !important;
+ }
+
+ .flex-xl-wrap {
+ flex-wrap: wrap !important;
+ }
+
+ .flex-xl-nowrap {
+ flex-wrap: nowrap !important;
+ }
+
+ .flex-xl-wrap-reverse {
+ flex-wrap: wrap-reverse !important;
+ }
+
+ .gap-xl-0 {
+ gap: 0 !important;
+ }
+
+ .gap-xl-1 {
+ gap: 0.25rem !important;
+ }
+
+ .gap-xl-2 {
+ gap: 0.5rem !important;
+ }
+
+ .gap-xl-3 {
+ gap: 1rem !important;
+ }
+
+ .gap-xl-4 {
+ gap: 1.5rem !important;
+ }
+
+ .gap-xl-5 {
+ gap: 3rem !important;
+ }
+
+ .justify-content-xl-start {
+ justify-content: flex-start !important;
+ }
+
+ .justify-content-xl-end {
+ justify-content: flex-end !important;
+ }
+
+ .justify-content-xl-center {
+ justify-content: center !important;
+ }
+
+ .justify-content-xl-between {
+ justify-content: space-between !important;
+ }
+
+ .justify-content-xl-around {
+ justify-content: space-around !important;
+ }
+
+ .justify-content-xl-evenly {
+ justify-content: space-evenly !important;
+ }
+
+ .align-items-xl-start {
+ align-items: flex-start !important;
+ }
+
+ .align-items-xl-end {
+ align-items: flex-end !important;
+ }
+
+ .align-items-xl-center {
+ align-items: center !important;
+ }
+
+ .align-items-xl-baseline {
+ align-items: baseline !important;
+ }
+
+ .align-items-xl-stretch {
+ align-items: stretch !important;
+ }
+
+ .align-content-xl-start {
+ align-content: flex-start !important;
+ }
+
+ .align-content-xl-end {
+ align-content: flex-end !important;
+ }
+
+ .align-content-xl-center {
+ align-content: center !important;
+ }
+
+ .align-content-xl-between {
+ align-content: space-between !important;
+ }
+
+ .align-content-xl-around {
+ align-content: space-around !important;
+ }
+
+ .align-content-xl-stretch {
+ align-content: stretch !important;
+ }
+
+ .align-self-xl-auto {
+ align-self: auto !important;
+ }
+
+ .align-self-xl-start {
+ align-self: flex-start !important;
+ }
+
+ .align-self-xl-end {
+ align-self: flex-end !important;
+ }
+
+ .align-self-xl-center {
+ align-self: center !important;
+ }
+
+ .align-self-xl-baseline {
+ align-self: baseline !important;
+ }
+
+ .align-self-xl-stretch {
+ align-self: stretch !important;
+ }
+
+ .order-xl-first {
+ order: -1 !important;
+ }
+
+ .order-xl-0 {
+ order: 0 !important;
+ }
+
+ .order-xl-1 {
+ order: 1 !important;
+ }
+
+ .order-xl-2 {
+ order: 2 !important;
+ }
+
+ .order-xl-3 {
+ order: 3 !important;
+ }
+
+ .order-xl-4 {
+ order: 4 !important;
+ }
+
+ .order-xl-5 {
+ order: 5 !important;
+ }
+
+ .order-xl-last {
+ order: 6 !important;
+ }
+
+ .m-xl-0 {
+ margin: 0 !important;
+ }
+
+ .m-xl-1 {
+ margin: 0.25rem !important;
+ }
+
+ .m-xl-2 {
+ margin: 0.5rem !important;
+ }
+
+ .m-xl-3 {
+ margin: 1rem !important;
+ }
+
+ .m-xl-4 {
+ margin: 1.5rem !important;
+ }
+
+ .m-xl-5 {
+ margin: 3rem !important;
+ }
+
+ .m-xl-auto {
+ margin: auto !important;
+ }
+
+ .mx-xl-0 {
+ margin-right: 0 !important;
+ margin-left: 0 !important;
+ }
+
+ .mx-xl-1 {
+ margin-right: 0.25rem !important;
+ margin-left: 0.25rem !important;
+ }
+
+ .mx-xl-2 {
+ margin-right: 0.5rem !important;
+ margin-left: 0.5rem !important;
+ }
+
+ .mx-xl-3 {
+ margin-right: 1rem !important;
+ margin-left: 1rem !important;
+ }
+
+ .mx-xl-4 {
+ margin-right: 1.5rem !important;
+ margin-left: 1.5rem !important;
+ }
+
+ .mx-xl-5 {
+ margin-right: 3rem !important;
+ margin-left: 3rem !important;
+ }
+
+ .mx-xl-auto {
+ margin-right: auto !important;
+ margin-left: auto !important;
+ }
+
+ .my-xl-0 {
+ margin-top: 0 !important;
+ margin-bottom: 0 !important;
+ }
+
+ .my-xl-1 {
+ margin-top: 0.25rem !important;
+ margin-bottom: 0.25rem !important;
+ }
+
+ .my-xl-2 {
+ margin-top: 0.5rem !important;
+ margin-bottom: 0.5rem !important;
+ }
+
+ .my-xl-3 {
+ margin-top: 1rem !important;
+ margin-bottom: 1rem !important;
+ }
+
+ .my-xl-4 {
+ margin-top: 1.5rem !important;
+ margin-bottom: 1.5rem !important;
+ }
+
+ .my-xl-5 {
+ margin-top: 3rem !important;
+ margin-bottom: 3rem !important;
+ }
+
+ .my-xl-auto {
+ margin-top: auto !important;
+ margin-bottom: auto !important;
+ }
+
+ .mt-xl-0 {
+ margin-top: 0 !important;
+ }
+
+ .mt-xl-1 {
+ margin-top: 0.25rem !important;
+ }
+
+ .mt-xl-2 {
+ margin-top: 0.5rem !important;
+ }
+
+ .mt-xl-3 {
+ margin-top: 1rem !important;
+ }
+
+ .mt-xl-4 {
+ margin-top: 1.5rem !important;
+ }
+
+ .mt-xl-5 {
+ margin-top: 3rem !important;
+ }
+
+ .mt-xl-auto {
+ margin-top: auto !important;
+ }
+
+ .me-xl-0 {
+ margin-right: 0 !important;
+ }
+
+ .me-xl-1 {
+ margin-right: 0.25rem !important;
+ }
+
+ .me-xl-2 {
+ margin-right: 0.5rem !important;
+ }
+
+ .me-xl-3 {
+ margin-right: 1rem !important;
+ }
+
+ .me-xl-4 {
+ margin-right: 1.5rem !important;
+ }
+
+ .me-xl-5 {
+ margin-right: 3rem !important;
+ }
+
+ .me-xl-auto {
+ margin-right: auto !important;
+ }
+
+ .mb-xl-0 {
+ margin-bottom: 0 !important;
+ }
+
+ .mb-xl-1 {
+ margin-bottom: 0.25rem !important;
+ }
+
+ .mb-xl-2 {
+ margin-bottom: 0.5rem !important;
+ }
+
+ .mb-xl-3 {
+ margin-bottom: 1rem !important;
+ }
+
+ .mb-xl-4 {
+ margin-bottom: 1.5rem !important;
+ }
+
+ .mb-xl-5 {
+ margin-bottom: 3rem !important;
+ }
+
+ .mb-xl-auto {
+ margin-bottom: auto !important;
+ }
+
+ .ms-xl-0 {
+ margin-left: 0 !important;
+ }
+
+ .ms-xl-1 {
+ margin-left: 0.25rem !important;
+ }
+
+ .ms-xl-2 {
+ margin-left: 0.5rem !important;
+ }
+
+ .ms-xl-3 {
+ margin-left: 1rem !important;
+ }
+
+ .ms-xl-4 {
+ margin-left: 1.5rem !important;
+ }
+
+ .ms-xl-5 {
+ margin-left: 3rem !important;
+ }
+
+ .ms-xl-auto {
+ margin-left: auto !important;
+ }
+
+ .p-xl-0 {
+ padding: 0 !important;
+ }
+
+ .p-xl-1 {
+ padding: 0.25rem !important;
+ }
+
+ .p-xl-2 {
+ padding: 0.5rem !important;
+ }
+
+ .p-xl-3 {
+ padding: 1rem !important;
+ }
+
+ .p-xl-4 {
+ padding: 1.5rem !important;
+ }
+
+ .p-xl-5 {
+ padding: 3rem !important;
+ }
+
+ .px-xl-0 {
+ padding-right: 0 !important;
+ padding-left: 0 !important;
+ }
+
+ .px-xl-1 {
+ padding-right: 0.25rem !important;
+ padding-left: 0.25rem !important;
+ }
+
+ .px-xl-2 {
+ padding-right: 0.5rem !important;
+ padding-left: 0.5rem !important;
+ }
+
+ .px-xl-3 {
+ padding-right: 1rem !important;
+ padding-left: 1rem !important;
+ }
+
+ .px-xl-4 {
+ padding-right: 1.5rem !important;
+ padding-left: 1.5rem !important;
+ }
+
+ .px-xl-5 {
+ padding-right: 3rem !important;
+ padding-left: 3rem !important;
+ }
+
+ .py-xl-0 {
+ padding-top: 0 !important;
+ padding-bottom: 0 !important;
+ }
+
+ .py-xl-1 {
+ padding-top: 0.25rem !important;
+ padding-bottom: 0.25rem !important;
+ }
+
+ .py-xl-2 {
+ padding-top: 0.5rem !important;
+ padding-bottom: 0.5rem !important;
+ }
+
+ .py-xl-3 {
+ padding-top: 1rem !important;
+ padding-bottom: 1rem !important;
+ }
+
+ .py-xl-4 {
+ padding-top: 1.5rem !important;
+ padding-bottom: 1.5rem !important;
+ }
+
+ .py-xl-5 {
+ padding-top: 3rem !important;
+ padding-bottom: 3rem !important;
+ }
+
+ .pt-xl-0 {
+ padding-top: 0 !important;
+ }
+
+ .pt-xl-1 {
+ padding-top: 0.25rem !important;
+ }
+
+ .pt-xl-2 {
+ padding-top: 0.5rem !important;
+ }
+
+ .pt-xl-3 {
+ padding-top: 1rem !important;
+ }
+
+ .pt-xl-4 {
+ padding-top: 1.5rem !important;
+ }
+
+ .pt-xl-5 {
+ padding-top: 3rem !important;
+ }
+
+ .pe-xl-0 {
+ padding-right: 0 !important;
+ }
+
+ .pe-xl-1 {
+ padding-right: 0.25rem !important;
+ }
+
+ .pe-xl-2 {
+ padding-right: 0.5rem !important;
+ }
+
+ .pe-xl-3 {
+ padding-right: 1rem !important;
+ }
+
+ .pe-xl-4 {
+ padding-right: 1.5rem !important;
+ }
+
+ .pe-xl-5 {
+ padding-right: 3rem !important;
+ }
+
+ .pb-xl-0 {
+ padding-bottom: 0 !important;
+ }
+
+ .pb-xl-1 {
+ padding-bottom: 0.25rem !important;
+ }
+
+ .pb-xl-2 {
+ padding-bottom: 0.5rem !important;
+ }
+
+ .pb-xl-3 {
+ padding-bottom: 1rem !important;
+ }
+
+ .pb-xl-4 {
+ padding-bottom: 1.5rem !important;
+ }
+
+ .pb-xl-5 {
+ padding-bottom: 3rem !important;
+ }
+
+ .ps-xl-0 {
+ padding-left: 0 !important;
+ }
+
+ .ps-xl-1 {
+ padding-left: 0.25rem !important;
+ }
+
+ .ps-xl-2 {
+ padding-left: 0.5rem !important;
+ }
+
+ .ps-xl-3 {
+ padding-left: 1rem !important;
+ }
+
+ .ps-xl-4 {
+ padding-left: 1.5rem !important;
+ }
+
+ .ps-xl-5 {
+ padding-left: 3rem !important;
+ }
+
+ .text-xl-start {
+ text-align: left !important;
+ }
+
+ .text-xl-end {
+ text-align: right !important;
+ }
+
+ .text-xl-center {
+ text-align: center !important;
+ }
+}
+@media (min-width: 1400px) {
+ .float-xxl-start {
+ float: left !important;
+ }
+
+ .float-xxl-end {
+ float: right !important;
+ }
+
+ .float-xxl-none {
+ float: none !important;
+ }
+
+ .d-xxl-inline {
+ display: inline !important;
+ }
+
+ .d-xxl-inline-block {
+ display: inline-block !important;
+ }
+
+ .d-xxl-block {
+ display: block !important;
+ }
+
+ .d-xxl-grid {
+ display: grid !important;
+ }
+
+ .d-xxl-table {
+ display: table !important;
+ }
+
+ .d-xxl-table-row {
+ display: table-row !important;
+ }
+
+ .d-xxl-table-cell {
+ display: table-cell !important;
+ }
+
+ .d-xxl-flex {
+ display: flex !important;
+ }
+
+ .d-xxl-inline-flex {
+ display: inline-flex !important;
+ }
+
+ .d-xxl-none {
+ display: none !important;
+ }
+
+ .flex-xxl-fill {
+ flex: 1 1 auto !important;
+ }
+
+ .flex-xxl-row {
+ flex-direction: row !important;
+ }
+
+ .flex-xxl-column {
+ flex-direction: column !important;
+ }
+
+ .flex-xxl-row-reverse {
+ flex-direction: row-reverse !important;
+ }
+
+ .flex-xxl-column-reverse {
+ flex-direction: column-reverse !important;
+ }
+
+ .flex-xxl-grow-0 {
+ flex-grow: 0 !important;
+ }
+
+ .flex-xxl-grow-1 {
+ flex-grow: 1 !important;
+ }
+
+ .flex-xxl-shrink-0 {
+ flex-shrink: 0 !important;
+ }
+
+ .flex-xxl-shrink-1 {
+ flex-shrink: 1 !important;
+ }
+
+ .flex-xxl-wrap {
+ flex-wrap: wrap !important;
+ }
+
+ .flex-xxl-nowrap {
+ flex-wrap: nowrap !important;
+ }
+
+ .flex-xxl-wrap-reverse {
+ flex-wrap: wrap-reverse !important;
+ }
+
+ .gap-xxl-0 {
+ gap: 0 !important;
+ }
+
+ .gap-xxl-1 {
+ gap: 0.25rem !important;
+ }
+
+ .gap-xxl-2 {
+ gap: 0.5rem !important;
+ }
+
+ .gap-xxl-3 {
+ gap: 1rem !important;
+ }
+
+ .gap-xxl-4 {
+ gap: 1.5rem !important;
+ }
+
+ .gap-xxl-5 {
+ gap: 3rem !important;
+ }
+
+ .justify-content-xxl-start {
+ justify-content: flex-start !important;
+ }
+
+ .justify-content-xxl-end {
+ justify-content: flex-end !important;
+ }
+
+ .justify-content-xxl-center {
+ justify-content: center !important;
+ }
+
+ .justify-content-xxl-between {
+ justify-content: space-between !important;
+ }
+
+ .justify-content-xxl-around {
+ justify-content: space-around !important;
+ }
+
+ .justify-content-xxl-evenly {
+ justify-content: space-evenly !important;
+ }
+
+ .align-items-xxl-start {
+ align-items: flex-start !important;
+ }
+
+ .align-items-xxl-end {
+ align-items: flex-end !important;
+ }
+
+ .align-items-xxl-center {
+ align-items: center !important;
+ }
+
+ .align-items-xxl-baseline {
+ align-items: baseline !important;
+ }
+
+ .align-items-xxl-stretch {
+ align-items: stretch !important;
+ }
+
+ .align-content-xxl-start {
+ align-content: flex-start !important;
+ }
+
+ .align-content-xxl-end {
+ align-content: flex-end !important;
+ }
+
+ .align-content-xxl-center {
+ align-content: center !important;
+ }
+
+ .align-content-xxl-between {
+ align-content: space-between !important;
+ }
+
+ .align-content-xxl-around {
+ align-content: space-around !important;
+ }
+
+ .align-content-xxl-stretch {
+ align-content: stretch !important;
+ }
+
+ .align-self-xxl-auto {
+ align-self: auto !important;
+ }
+
+ .align-self-xxl-start {
+ align-self: flex-start !important;
+ }
+
+ .align-self-xxl-end {
+ align-self: flex-end !important;
+ }
+
+ .align-self-xxl-center {
+ align-self: center !important;
+ }
+
+ .align-self-xxl-baseline {
+ align-self: baseline !important;
+ }
+
+ .align-self-xxl-stretch {
+ align-self: stretch !important;
+ }
+
+ .order-xxl-first {
+ order: -1 !important;
+ }
+
+ .order-xxl-0 {
+ order: 0 !important;
+ }
+
+ .order-xxl-1 {
+ order: 1 !important;
+ }
+
+ .order-xxl-2 {
+ order: 2 !important;
+ }
+
+ .order-xxl-3 {
+ order: 3 !important;
+ }
+
+ .order-xxl-4 {
+ order: 4 !important;
+ }
+
+ .order-xxl-5 {
+ order: 5 !important;
+ }
+
+ .order-xxl-last {
+ order: 6 !important;
+ }
+
+ .m-xxl-0 {
+ margin: 0 !important;
+ }
+
+ .m-xxl-1 {
+ margin: 0.25rem !important;
+ }
+
+ .m-xxl-2 {
+ margin: 0.5rem !important;
+ }
+
+ .m-xxl-3 {
+ margin: 1rem !important;
+ }
+
+ .m-xxl-4 {
+ margin: 1.5rem !important;
+ }
+
+ .m-xxl-5 {
+ margin: 3rem !important;
+ }
+
+ .m-xxl-auto {
+ margin: auto !important;
+ }
+
+ .mx-xxl-0 {
+ margin-right: 0 !important;
+ margin-left: 0 !important;
+ }
+
+ .mx-xxl-1 {
+ margin-right: 0.25rem !important;
+ margin-left: 0.25rem !important;
+ }
+
+ .mx-xxl-2 {
+ margin-right: 0.5rem !important;
+ margin-left: 0.5rem !important;
+ }
+
+ .mx-xxl-3 {
+ margin-right: 1rem !important;
+ margin-left: 1rem !important;
+ }
+
+ .mx-xxl-4 {
+ margin-right: 1.5rem !important;
+ margin-left: 1.5rem !important;
+ }
+
+ .mx-xxl-5 {
+ margin-right: 3rem !important;
+ margin-left: 3rem !important;
+ }
+
+ .mx-xxl-auto {
+ margin-right: auto !important;
+ margin-left: auto !important;
+ }
+
+ .my-xxl-0 {
+ margin-top: 0 !important;
+ margin-bottom: 0 !important;
+ }
+
+ .my-xxl-1 {
+ margin-top: 0.25rem !important;
+ margin-bottom: 0.25rem !important;
+ }
+
+ .my-xxl-2 {
+ margin-top: 0.5rem !important;
+ margin-bottom: 0.5rem !important;
+ }
+
+ .my-xxl-3 {
+ margin-top: 1rem !important;
+ margin-bottom: 1rem !important;
+ }
+
+ .my-xxl-4 {
+ margin-top: 1.5rem !important;
+ margin-bottom: 1.5rem !important;
+ }
+
+ .my-xxl-5 {
+ margin-top: 3rem !important;
+ margin-bottom: 3rem !important;
+ }
+
+ .my-xxl-auto {
+ margin-top: auto !important;
+ margin-bottom: auto !important;
+ }
+
+ .mt-xxl-0 {
+ margin-top: 0 !important;
+ }
+
+ .mt-xxl-1 {
+ margin-top: 0.25rem !important;
+ }
+
+ .mt-xxl-2 {
+ margin-top: 0.5rem !important;
+ }
+
+ .mt-xxl-3 {
+ margin-top: 1rem !important;
+ }
+
+ .mt-xxl-4 {
+ margin-top: 1.5rem !important;
+ }
+
+ .mt-xxl-5 {
+ margin-top: 3rem !important;
+ }
+
+ .mt-xxl-auto {
+ margin-top: auto !important;
+ }
+
+ .me-xxl-0 {
+ margin-right: 0 !important;
+ }
+
+ .me-xxl-1 {
+ margin-right: 0.25rem !important;
+ }
+
+ .me-xxl-2 {
+ margin-right: 0.5rem !important;
+ }
+
+ .me-xxl-3 {
+ margin-right: 1rem !important;
+ }
+
+ .me-xxl-4 {
+ margin-right: 1.5rem !important;
+ }
+
+ .me-xxl-5 {
+ margin-right: 3rem !important;
+ }
+
+ .me-xxl-auto {
+ margin-right: auto !important;
+ }
+
+ .mb-xxl-0 {
+ margin-bottom: 0 !important;
+ }
+
+ .mb-xxl-1 {
+ margin-bottom: 0.25rem !important;
+ }
+
+ .mb-xxl-2 {
+ margin-bottom: 0.5rem !important;
+ }
+
+ .mb-xxl-3 {
+ margin-bottom: 1rem !important;
+ }
+
+ .mb-xxl-4 {
+ margin-bottom: 1.5rem !important;
+ }
+
+ .mb-xxl-5 {
+ margin-bottom: 3rem !important;
+ }
+
+ .mb-xxl-auto {
+ margin-bottom: auto !important;
+ }
+
+ .ms-xxl-0 {
+ margin-left: 0 !important;
+ }
+
+ .ms-xxl-1 {
+ margin-left: 0.25rem !important;
+ }
+
+ .ms-xxl-2 {
+ margin-left: 0.5rem !important;
+ }
+
+ .ms-xxl-3 {
+ margin-left: 1rem !important;
+ }
+
+ .ms-xxl-4 {
+ margin-left: 1.5rem !important;
+ }
+
+ .ms-xxl-5 {
+ margin-left: 3rem !important;
+ }
+
+ .ms-xxl-auto {
+ margin-left: auto !important;
+ }
+
+ .p-xxl-0 {
+ padding: 0 !important;
+ }
+
+ .p-xxl-1 {
+ padding: 0.25rem !important;
+ }
+
+ .p-xxl-2 {
+ padding: 0.5rem !important;
+ }
+
+ .p-xxl-3 {
+ padding: 1rem !important;
+ }
+
+ .p-xxl-4 {
+ padding: 1.5rem !important;
+ }
+
+ .p-xxl-5 {
+ padding: 3rem !important;
+ }
+
+ .px-xxl-0 {
+ padding-right: 0 !important;
+ padding-left: 0 !important;
+ }
+
+ .px-xxl-1 {
+ padding-right: 0.25rem !important;
+ padding-left: 0.25rem !important;
+ }
+
+ .px-xxl-2 {
+ padding-right: 0.5rem !important;
+ padding-left: 0.5rem !important;
+ }
+
+ .px-xxl-3 {
+ padding-right: 1rem !important;
+ padding-left: 1rem !important;
+ }
+
+ .px-xxl-4 {
+ padding-right: 1.5rem !important;
+ padding-left: 1.5rem !important;
+ }
+
+ .px-xxl-5 {
+ padding-right: 3rem !important;
+ padding-left: 3rem !important;
+ }
+
+ .py-xxl-0 {
+ padding-top: 0 !important;
+ padding-bottom: 0 !important;
+ }
+
+ .py-xxl-1 {
+ padding-top: 0.25rem !important;
+ padding-bottom: 0.25rem !important;
+ }
+
+ .py-xxl-2 {
+ padding-top: 0.5rem !important;
+ padding-bottom: 0.5rem !important;
+ }
+
+ .py-xxl-3 {
+ padding-top: 1rem !important;
+ padding-bottom: 1rem !important;
+ }
+
+ .py-xxl-4 {
+ padding-top: 1.5rem !important;
+ padding-bottom: 1.5rem !important;
+ }
+
+ .py-xxl-5 {
+ padding-top: 3rem !important;
+ padding-bottom: 3rem !important;
+ }
+
+ .pt-xxl-0 {
+ padding-top: 0 !important;
+ }
+
+ .pt-xxl-1 {
+ padding-top: 0.25rem !important;
+ }
+
+ .pt-xxl-2 {
+ padding-top: 0.5rem !important;
+ }
+
+ .pt-xxl-3 {
+ padding-top: 1rem !important;
+ }
+
+ .pt-xxl-4 {
+ padding-top: 1.5rem !important;
+ }
+
+ .pt-xxl-5 {
+ padding-top: 3rem !important;
+ }
+
+ .pe-xxl-0 {
+ padding-right: 0 !important;
+ }
+
+ .pe-xxl-1 {
+ padding-right: 0.25rem !important;
+ }
+
+ .pe-xxl-2 {
+ padding-right: 0.5rem !important;
+ }
+
+ .pe-xxl-3 {
+ padding-right: 1rem !important;
+ }
+
+ .pe-xxl-4 {
+ padding-right: 1.5rem !important;
+ }
+
+ .pe-xxl-5 {
+ padding-right: 3rem !important;
+ }
+
+ .pb-xxl-0 {
+ padding-bottom: 0 !important;
+ }
+
+ .pb-xxl-1 {
+ padding-bottom: 0.25rem !important;
+ }
+
+ .pb-xxl-2 {
+ padding-bottom: 0.5rem !important;
+ }
+
+ .pb-xxl-3 {
+ padding-bottom: 1rem !important;
+ }
+
+ .pb-xxl-4 {
+ padding-bottom: 1.5rem !important;
+ }
+
+ .pb-xxl-5 {
+ padding-bottom: 3rem !important;
+ }
+
+ .ps-xxl-0 {
+ padding-left: 0 !important;
+ }
+
+ .ps-xxl-1 {
+ padding-left: 0.25rem !important;
+ }
+
+ .ps-xxl-2 {
+ padding-left: 0.5rem !important;
+ }
+
+ .ps-xxl-3 {
+ padding-left: 1rem !important;
+ }
+
+ .ps-xxl-4 {
+ padding-left: 1.5rem !important;
+ }
+
+ .ps-xxl-5 {
+ padding-left: 3rem !important;
+ }
+
+ .text-xxl-start {
+ text-align: left !important;
+ }
+
+ .text-xxl-end {
+ text-align: right !important;
+ }
+
+ .text-xxl-center {
+ text-align: center !important;
+ }
+}
+@media (min-width: 1200px) {
+ .fs-1 {
+ font-size: 2.5rem !important;
+ }
+
+ .fs-2 {
+ font-size: 2rem !important;
+ }
+
+ .fs-3 {
+ font-size: 1.75rem !important;
+ }
+
+ .fs-4 {
+ font-size: 1.5rem !important;
+ }
+}
+@media print {
+ .d-print-inline {
+ display: inline !important;
+ }
+
+ .d-print-inline-block {
+ display: inline-block !important;
+ }
+
+ .d-print-block {
+ display: block !important;
+ }
+
+ .d-print-grid {
+ display: grid !important;
+ }
+
+ .d-print-table {
+ display: table !important;
+ }
+
+ .d-print-table-row {
+ display: table-row !important;
+ }
+
+ .d-print-table-cell {
+ display: table-cell !important;
+ }
+
+ .d-print-flex {
+ display: flex !important;
+ }
+
+ .d-print-inline-flex {
+ display: inline-flex !important;
+ }
+
+ .d-print-none {
+ display: none !important;
+ }
+}
+
+/*# sourceMappingURL=bootstrap.css.map */
\ No newline at end of file
diff --git a/pkgs/csslib/third_party/foundation/LICENSE b/pkgs/csslib/third_party/foundation/LICENSE
new file mode 100644
index 0000000..cfa9e05
--- /dev/null
+++ b/pkgs/csslib/third_party/foundation/LICENSE
@@ -0,0 +1,22 @@
+Copyright © 2011-2020 ZURB, Inc.
+
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/pkgs/csslib/third_party/foundation/README.md b/pkgs/csslib/third_party/foundation/README.md
new file mode 100644
index 0000000..a79a2a8
--- /dev/null
+++ b/pkgs/csslib/third_party/foundation/README.md
@@ -0,0 +1,4 @@
+This folder contains sample css files from the open-source project
+https://github.com/foundation/foundation-sites.
+
+This code was included under the terms in the `LICENSE` file.
\ No newline at end of file
diff --git a/pkgs/csslib/third_party/foundation/foundation.css b/pkgs/csslib/third_party/foundation/foundation.css
new file mode 100644
index 0000000..eb68cfd
--- /dev/null
+++ b/pkgs/csslib/third_party/foundation/foundation.css
@@ -0,0 +1,5616 @@
+@charset "UTF-8";
+/**
+ * Foundation for Sites
+ * Version 6.7.2
+ * https://get.foundation
+ * Licensed under MIT Open Source
+ */
+@media print, screen and (min-width: 40em) {
+ .reveal, .reveal.tiny, .reveal.small, .reveal.large {
+ right: auto;
+ left: auto;
+ margin: 0 auto; } }
+
+/*! normalize.css v8.0.0 | MIT License | github.com/necolas/normalize.css */
+html {
+ line-height: 1.15;
+ -webkit-text-size-adjust: 100%; }
+
+body {
+ margin: 0; }
+
+h1 {
+ font-size: 2em;
+ margin: 0.67em 0; }
+
+hr {
+ -webkit-box-sizing: content-box;
+ box-sizing: content-box;
+ height: 0;
+ overflow: visible; }
+
+pre {
+ font-family: monospace, monospace;
+ font-size: 1em; }
+
+a {
+ background-color: transparent; }
+
+abbr[title] {
+ border-bottom: none;
+ text-decoration: underline;
+ -webkit-text-decoration: underline dotted;
+ text-decoration: underline dotted; }
+
+b,
+strong {
+ font-weight: bolder; }
+
+code,
+kbd,
+samp {
+ font-family: monospace, monospace;
+ font-size: 1em; }
+
+small {
+ font-size: 80%; }
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline; }
+
+sub {
+ bottom: -0.25em; }
+
+sup {
+ top: -0.5em; }
+
+img {
+ border-style: none; }
+
+button,
+input,
+optgroup,
+select,
+textarea {
+ font-family: inherit;
+ font-size: 100%;
+ line-height: 1.15;
+ margin: 0; }
+
+button,
+input {
+ overflow: visible; }
+
+button,
+select {
+ text-transform: none; }
+
+button,
+[type="button"],
+[type="reset"],
+[type="submit"] {
+ -webkit-appearance: button; }
+
+button::-moz-focus-inner,
+[type="button"]::-moz-focus-inner,
+[type="reset"]::-moz-focus-inner,
+[type="submit"]::-moz-focus-inner {
+ border-style: none;
+ padding: 0; }
+
+button:-moz-focusring,
+[type="button"]:-moz-focusring,
+[type="reset"]:-moz-focusring,
+[type="submit"]:-moz-focusring {
+ outline: 1px dotted ButtonText; }
+
+fieldset {
+ padding: 0.35em 0.75em 0.625em; }
+
+legend {
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+ color: inherit;
+ display: table;
+ max-width: 100%;
+ padding: 0;
+ white-space: normal; }
+
+progress {
+ vertical-align: baseline; }
+
+textarea {
+ overflow: auto; }
+
+[type="checkbox"],
+[type="radio"] {
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+ padding: 0; }
+
+[type="number"]::-webkit-inner-spin-button,
+[type="number"]::-webkit-outer-spin-button {
+ height: auto; }
+
+[type="search"] {
+ -webkit-appearance: textfield;
+ outline-offset: -2px; }
+
+[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none; }
+
+::-webkit-file-upload-button {
+ -webkit-appearance: button;
+ font: inherit; }
+
+details {
+ display: block; }
+
+summary {
+ display: list-item; }
+
+template {
+ display: none; }
+
+[hidden] {
+ display: none; }
+
+.foundation-mq {
+ font-family: "small=0em&medium=40em&large=64em&xlarge=75em&xxlarge=90em"; }
+
+html {
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+ font-size: 100%; }
+
+*,
+*::before,
+*::after {
+ -webkit-box-sizing: inherit;
+ box-sizing: inherit; }
+
+body {
+ margin: 0;
+ padding: 0;
+ background: #fefefe;
+ font-family: "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif;
+ font-weight: normal;
+ line-height: 1.5;
+ color: #0a0a0a;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale; }
+
+img {
+ display: inline-block;
+ vertical-align: middle;
+ max-width: 100%;
+ height: auto;
+ -ms-interpolation-mode: bicubic; }
+
+textarea {
+ height: auto;
+ min-height: 50px;
+ border-radius: 0; }
+
+select {
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+ width: 100%;
+ border-radius: 0; }
+
+.map_canvas img,
+.map_canvas embed,
+.map_canvas object,
+.mqa-display img,
+.mqa-display embed,
+.mqa-display object {
+ max-width: none !important; }
+
+button {
+ padding: 0;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ border: 0;
+ border-radius: 0;
+ background: transparent;
+ line-height: 1;
+ cursor: auto; }
+ [data-whatinput='mouse'] button {
+ outline: 0; }
+
+pre {
+ overflow: auto; }
+
+button,
+input,
+optgroup,
+select,
+textarea {
+ font-family: inherit; }
+
+.is-visible {
+ display: block !important; }
+
+.is-hidden {
+ display: none !important; }
+
+[type='text'], [type='password'], [type='date'], [type='datetime'], [type='datetime-local'], [type='month'], [type='week'], [type='email'], [type='number'], [type='search'], [type='tel'], [type='time'], [type='url'], [type='color'],
+textarea {
+ display: block;
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+ width: 100%;
+ height: 2.4375rem;
+ margin: 0 0 1rem;
+ padding: 0.5rem;
+ border: 1px solid #cacaca;
+ border-radius: 0;
+ background-color: #fefefe;
+ -webkit-box-shadow: inset 0 1px 2px rgba(10, 10, 10, 0.1);
+ box-shadow: inset 0 1px 2px rgba(10, 10, 10, 0.1);
+ font-family: inherit;
+ font-size: 1rem;
+ font-weight: normal;
+ line-height: 1.5;
+ color: #0a0a0a;
+ -webkit-transition: border-color 0.25s ease-in-out, -webkit-box-shadow 0.5s;
+ transition: border-color 0.25s ease-in-out, -webkit-box-shadow 0.5s;
+ transition: box-shadow 0.5s, border-color 0.25s ease-in-out;
+ transition: box-shadow 0.5s, border-color 0.25s ease-in-out, -webkit-box-shadow 0.5s;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none; }
+ [type='text']:focus, [type='password']:focus, [type='date']:focus, [type='datetime']:focus, [type='datetime-local']:focus, [type='month']:focus, [type='week']:focus, [type='email']:focus, [type='number']:focus, [type='search']:focus, [type='tel']:focus, [type='time']:focus, [type='url']:focus, [type='color']:focus,
+ textarea:focus {
+ outline: none;
+ border: 1px solid #8a8a8a;
+ background-color: #fefefe;
+ -webkit-box-shadow: 0 0 5px #cacaca;
+ box-shadow: 0 0 5px #cacaca;
+ -webkit-transition: border-color 0.25s ease-in-out, -webkit-box-shadow 0.5s;
+ transition: border-color 0.25s ease-in-out, -webkit-box-shadow 0.5s;
+ transition: box-shadow 0.5s, border-color 0.25s ease-in-out;
+ transition: box-shadow 0.5s, border-color 0.25s ease-in-out, -webkit-box-shadow 0.5s; }
+
+textarea {
+ max-width: 100%; }
+ textarea[rows] {
+ height: auto; }
+
+input:disabled, input[readonly],
+textarea:disabled,
+textarea[readonly] {
+ background-color: #e6e6e6;
+ cursor: not-allowed; }
+
+[type='submit'],
+[type='button'] {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ border-radius: 0; }
+
+input[type='search'] {
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box; }
+
+::-webkit-input-placeholder {
+ color: #cacaca; }
+
+::-moz-placeholder {
+ color: #cacaca; }
+
+:-ms-input-placeholder {
+ color: #cacaca; }
+
+::-ms-input-placeholder {
+ color: #cacaca; }
+
+::placeholder {
+ color: #cacaca; }
+
+[type='file'],
+[type='checkbox'],
+[type='radio'] {
+ margin: 0 0 1rem; }
+
+[type='checkbox'] + label,
+[type='radio'] + label {
+ display: inline-block;
+ vertical-align: baseline;
+ margin-left: 0.5rem;
+ margin-right: 1rem;
+ margin-bottom: 0; }
+ [type='checkbox'] + label[for],
+ [type='radio'] + label[for] {
+ cursor: pointer; }
+
+label > [type='checkbox'],
+label > [type='radio'] {
+ margin-right: 0.5rem; }
+
+[type='file'] {
+ width: 100%; }
+
+label {
+ display: block;
+ margin: 0;
+ font-size: 0.875rem;
+ font-weight: normal;
+ line-height: 1.8;
+ color: #0a0a0a; }
+ label.middle {
+ margin: 0 0 1rem;
+ line-height: 1.5;
+ padding: 0.5625rem 0; }
+
+.help-text {
+ margin-top: -0.5rem;
+ font-size: 0.8125rem;
+ font-style: italic;
+ color: #0a0a0a; }
+
+.input-group {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ width: 100%;
+ margin-bottom: 1rem;
+ -webkit-box-align: stretch;
+ -webkit-align-items: stretch;
+ -ms-flex-align: stretch;
+ align-items: stretch; }
+ .input-group > :first-child, .input-group > :first-child.input-group-button > * {
+ border-radius: 0 0 0 0; }
+ .input-group > :last-child, .input-group > :last-child.input-group-button > * {
+ border-radius: 0 0 0 0; }
+
+.input-group-label, .input-group-field, .input-group-button, .input-group-button a,
+.input-group-button input,
+.input-group-button button,
+.input-group-button label {
+ margin: 0;
+ white-space: nowrap; }
+
+.input-group-label {
+ padding: 0 1rem;
+ border: 1px solid #cacaca;
+ background: #e6e6e6;
+ color: #0a0a0a;
+ text-align: center;
+ white-space: nowrap;
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-flex: 0;
+ -webkit-flex: 0 0 auto;
+ -ms-flex: 0 0 auto;
+ flex: 0 0 auto;
+ -webkit-box-align: center;
+ -webkit-align-items: center;
+ -ms-flex-align: center;
+ align-items: center; }
+ .input-group-label:first-child {
+ border-right: 0; }
+ .input-group-label:last-child {
+ border-left: 0; }
+
+.input-group-field {
+ border-radius: 0;
+ -webkit-box-flex: 1;
+ -webkit-flex: 1 1 0px;
+ -ms-flex: 1 1 0px;
+ flex: 1 1 0px;
+ min-width: 0; }
+
+.input-group-button {
+ padding-top: 0;
+ padding-bottom: 0;
+ text-align: center;
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-flex: 0;
+ -webkit-flex: 0 0 auto;
+ -ms-flex: 0 0 auto;
+ flex: 0 0 auto; }
+ .input-group-button a,
+ .input-group-button input,
+ .input-group-button button,
+ .input-group-button label {
+ -webkit-align-self: stretch;
+ -ms-flex-item-align: stretch;
+ align-self: stretch;
+ height: auto;
+ padding-top: 0;
+ padding-bottom: 0;
+ font-size: 1rem; }
+
+fieldset {
+ margin: 0;
+ padding: 0;
+ border: 0; }
+
+legend {
+ max-width: 100%;
+ margin-bottom: 0.5rem; }
+
+.fieldset {
+ margin: 1.125rem 0;
+ padding: 1.25rem;
+ border: 1px solid #cacaca; }
+ .fieldset legend {
+ margin: 0;
+ margin-left: -0.1875rem;
+ padding: 0 0.1875rem; }
+
+select {
+ height: 2.4375rem;
+ margin: 0 0 1rem;
+ padding: 0.5rem;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ border: 1px solid #cacaca;
+ border-radius: 0;
+ background-color: #fefefe;
+ font-family: inherit;
+ font-size: 1rem;
+ font-weight: normal;
+ line-height: 1.5;
+ color: #0a0a0a;
+ background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='32' height='24' viewBox='0 0 32 24'><polygon points='0,0 32,0 16,24' style='fill: rgb%28138, 138, 138%29'></polygon></svg>");
+ background-origin: content-box;
+ background-position: right -1rem center;
+ background-repeat: no-repeat;
+ background-size: 9px 6px;
+ padding-right: 1.5rem;
+ -webkit-transition: border-color 0.25s ease-in-out, -webkit-box-shadow 0.5s;
+ transition: border-color 0.25s ease-in-out, -webkit-box-shadow 0.5s;
+ transition: box-shadow 0.5s, border-color 0.25s ease-in-out;
+ transition: box-shadow 0.5s, border-color 0.25s ease-in-out, -webkit-box-shadow 0.5s; }
+ @media screen and (min-width: 0\0) {
+ select {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAYCAYAAACbU/80AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAIpJREFUeNrEkckNgDAMBBfRkEt0ObRBBdsGXUDgmQfK4XhH2m8czQAAy27R3tsw4Qfe2x8uOO6oYLb6GlOor3GF+swURAOmUJ+RwtEJs9WvTGEYxBXqI1MQAZhCfUQKRzDMVj+TwrAIV6jvSUEkYAr1LSkcyTBb/V+KYfX7xAeusq3sLDtGH3kEGACPWIflNZfhRQAAAABJRU5ErkJggg=="); } }
+ select:focus {
+ outline: none;
+ border: 1px solid #8a8a8a;
+ background-color: #fefefe;
+ -webkit-box-shadow: 0 0 5px #cacaca;
+ box-shadow: 0 0 5px #cacaca;
+ -webkit-transition: border-color 0.25s ease-in-out, -webkit-box-shadow 0.5s;
+ transition: border-color 0.25s ease-in-out, -webkit-box-shadow 0.5s;
+ transition: box-shadow 0.5s, border-color 0.25s ease-in-out;
+ transition: box-shadow 0.5s, border-color 0.25s ease-in-out, -webkit-box-shadow 0.5s; }
+ select:disabled {
+ background-color: #e6e6e6;
+ cursor: not-allowed; }
+ select::-ms-expand {
+ display: none; }
+ select[multiple] {
+ height: auto;
+ background-image: none; }
+ select:not([multiple]) {
+ padding-top: 0;
+ padding-bottom: 0; }
+
+.is-invalid-input:not(:focus) {
+ border-color: #cc4b37;
+ background-color: #f9ecea; }
+ .is-invalid-input:not(:focus)::-webkit-input-placeholder {
+ color: #cc4b37; }
+ .is-invalid-input:not(:focus)::-moz-placeholder {
+ color: #cc4b37; }
+ .is-invalid-input:not(:focus):-ms-input-placeholder {
+ color: #cc4b37; }
+ .is-invalid-input:not(:focus)::-ms-input-placeholder {
+ color: #cc4b37; }
+ .is-invalid-input:not(:focus)::placeholder {
+ color: #cc4b37; }
+
+.is-invalid-label {
+ color: #cc4b37; }
+
+.form-error {
+ display: none;
+ margin-top: -0.5rem;
+ margin-bottom: 1rem;
+ font-size: 0.75rem;
+ font-weight: bold;
+ color: #cc4b37; }
+ .form-error.is-visible {
+ display: block; }
+
+div,
+dl,
+dt,
+dd,
+ul,
+ol,
+li,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+pre,
+form,
+p,
+blockquote,
+th,
+td {
+ margin: 0;
+ padding: 0; }
+
+p {
+ margin-bottom: 1rem;
+ font-size: inherit;
+ line-height: 1.6;
+ text-rendering: optimizeLegibility; }
+
+em,
+i {
+ font-style: italic;
+ line-height: inherit; }
+
+strong,
+b {
+ font-weight: bold;
+ line-height: inherit; }
+
+small {
+ font-size: 80%;
+ line-height: inherit; }
+
+h1, .h1,
+h2, .h2,
+h3, .h3,
+h4, .h4,
+h5, .h5,
+h6, .h6 {
+ font-family: "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif;
+ font-style: normal;
+ font-weight: normal;
+ color: inherit;
+ text-rendering: optimizeLegibility; }
+ h1 small, .h1 small,
+ h2 small, .h2 small,
+ h3 small, .h3 small,
+ h4 small, .h4 small,
+ h5 small, .h5 small,
+ h6 small, .h6 small {
+ line-height: 0;
+ color: #cacaca; }
+
+h1, .h1 {
+ font-size: 1.5rem;
+ line-height: 1.4;
+ margin-top: 0;
+ margin-bottom: 0.5rem; }
+
+h2, .h2 {
+ font-size: 1.25rem;
+ line-height: 1.4;
+ margin-top: 0;
+ margin-bottom: 0.5rem; }
+
+h3, .h3 {
+ font-size: 1.1875rem;
+ line-height: 1.4;
+ margin-top: 0;
+ margin-bottom: 0.5rem; }
+
+h4, .h4 {
+ font-size: 1.125rem;
+ line-height: 1.4;
+ margin-top: 0;
+ margin-bottom: 0.5rem; }
+
+h5, .h5 {
+ font-size: 1.0625rem;
+ line-height: 1.4;
+ margin-top: 0;
+ margin-bottom: 0.5rem; }
+
+h6, .h6 {
+ font-size: 1rem;
+ line-height: 1.4;
+ margin-top: 0;
+ margin-bottom: 0.5rem; }
+
+@media print, screen and (min-width: 40em) {
+ h1, .h1 {
+ font-size: 3rem; }
+ h2, .h2 {
+ font-size: 2.5rem; }
+ h3, .h3 {
+ font-size: 1.9375rem; }
+ h4, .h4 {
+ font-size: 1.5625rem; }
+ h5, .h5 {
+ font-size: 1.25rem; }
+ h6, .h6 {
+ font-size: 1rem; } }
+
+a {
+ line-height: inherit;
+ color: #1779ba;
+ text-decoration: none;
+ cursor: pointer; }
+ a:hover, a:focus {
+ color: #1468a0; }
+ a img {
+ border: 0; }
+
+hr {
+ clear: both;
+ max-width: 75rem;
+ height: 0;
+ margin: 1.25rem auto;
+ border-top: 0;
+ border-right: 0;
+ border-bottom: 1px solid #cacaca;
+ border-left: 0; }
+
+ul,
+ol,
+dl {
+ margin-bottom: 1rem;
+ list-style-position: outside;
+ line-height: 1.6; }
+
+li {
+ font-size: inherit; }
+
+ul {
+ margin-left: 1.25rem;
+ list-style-type: disc; }
+
+ol {
+ margin-left: 1.25rem; }
+
+ul ul, ol ul, ul ol, ol ol {
+ margin-left: 1.25rem;
+ margin-bottom: 0; }
+
+dl {
+ margin-bottom: 1rem; }
+ dl dt {
+ margin-bottom: 0.3rem;
+ font-weight: bold; }
+
+blockquote {
+ margin: 0 0 1rem;
+ padding: 0.5625rem 1.25rem 0 1.1875rem;
+ border-left: 1px solid #cacaca; }
+ blockquote, blockquote p {
+ line-height: 1.6;
+ color: #8a8a8a; }
+
+abbr, abbr[title] {
+ border-bottom: 1px dotted #0a0a0a;
+ cursor: help;
+ text-decoration: none; }
+
+figure {
+ margin: 0; }
+
+kbd {
+ margin: 0;
+ padding: 0.125rem 0.25rem 0;
+ background-color: #e6e6e6;
+ font-family: Consolas, "Liberation Mono", Courier, monospace;
+ color: #0a0a0a; }
+
+.subheader {
+ margin-top: 0.2rem;
+ margin-bottom: 0.5rem;
+ font-weight: normal;
+ line-height: 1.4;
+ color: #8a8a8a; }
+
+.lead {
+ font-size: 125%;
+ line-height: 1.6; }
+
+.stat {
+ font-size: 2.5rem;
+ line-height: 1; }
+ p + .stat {
+ margin-top: -1rem; }
+
+ul.no-bullet, ol.no-bullet {
+ margin-left: 0;
+ list-style: none; }
+
+.cite-block, cite {
+ display: block;
+ color: #8a8a8a;
+ font-size: 0.8125rem; }
+ .cite-block:before, cite:before {
+ content: "— "; }
+
+.code-inline, code {
+ border: 1px solid #cacaca;
+ background-color: #e6e6e6;
+ font-family: Consolas, "Liberation Mono", Courier, monospace;
+ font-weight: normal;
+ color: #0a0a0a;
+ display: inline;
+ max-width: 100%;
+ word-wrap: break-word;
+ padding: 0.125rem 0.3125rem 0.0625rem; }
+
+.code-block {
+ border: 1px solid #cacaca;
+ background-color: #e6e6e6;
+ font-family: Consolas, "Liberation Mono", Courier, monospace;
+ font-weight: normal;
+ color: #0a0a0a;
+ display: block;
+ overflow: auto;
+ white-space: pre;
+ padding: 1rem;
+ margin-bottom: 1.5rem; }
+
+.text-left {
+ text-align: left; }
+
+.text-right {
+ text-align: right; }
+
+.text-center {
+ text-align: center; }
+
+.text-justify {
+ text-align: justify; }
+
+@media print, screen and (min-width: 40em) {
+ .medium-text-left {
+ text-align: left; }
+ .medium-text-right {
+ text-align: right; }
+ .medium-text-center {
+ text-align: center; }
+ .medium-text-justify {
+ text-align: justify; } }
+
+@media print, screen and (min-width: 64em) {
+ .large-text-left {
+ text-align: left; }
+ .large-text-right {
+ text-align: right; }
+ .large-text-center {
+ text-align: center; }
+ .large-text-justify {
+ text-align: justify; } }
+
+.show-for-print {
+ display: none !important; }
+
+@media print {
+ * {
+ background: transparent !important;
+ color: black !important;
+ -webkit-print-color-adjust: economy;
+ color-adjust: economy;
+ -webkit-box-shadow: none !important;
+ box-shadow: none !important;
+ text-shadow: none !important; }
+ .show-for-print {
+ display: block !important; }
+ .hide-for-print {
+ display: none !important; }
+ table.show-for-print {
+ display: table !important; }
+ thead.show-for-print {
+ display: table-header-group !important; }
+ tbody.show-for-print {
+ display: table-row-group !important; }
+ tr.show-for-print {
+ display: table-row !important; }
+ td.show-for-print {
+ display: table-cell !important; }
+ th.show-for-print {
+ display: table-cell !important; }
+ a,
+ a:visited {
+ text-decoration: underline; }
+ a[href]:after {
+ content: " (" attr(href) ")"; }
+ .ir a:after,
+ a[href^='javascript:']:after,
+ a[href^='#']:after {
+ content: ''; }
+ abbr[title]:after {
+ content: " (" attr(title) ")"; }
+ pre,
+ blockquote {
+ border: 1px solid #8a8a8a;
+ page-break-inside: avoid; }
+ thead {
+ display: table-header-group; }
+ tr,
+ img {
+ page-break-inside: avoid; }
+ img {
+ max-width: 100% !important; }
+ @page {
+ margin: 0.5cm; }
+ p,
+ h2,
+ h3 {
+ orphans: 3;
+ widows: 3; }
+ h2,
+ h3 {
+ page-break-after: avoid; }
+ .print-break-inside {
+ page-break-inside: auto; } }
+
+.grid-container {
+ padding-right: 0.625rem;
+ padding-left: 0.625rem;
+ max-width: 75rem;
+ margin-left: auto;
+ margin-right: auto; }
+ @media print, screen and (min-width: 40em) {
+ .grid-container {
+ padding-right: 0.9375rem;
+ padding-left: 0.9375rem; } }
+ .grid-container.fluid {
+ padding-right: 0.625rem;
+ padding-left: 0.625rem;
+ max-width: 100%;
+ margin-left: auto;
+ margin-right: auto; }
+ @media print, screen and (min-width: 40em) {
+ .grid-container.fluid {
+ padding-right: 0.9375rem;
+ padding-left: 0.9375rem; } }
+ .grid-container.full {
+ padding-right: 0;
+ padding-left: 0;
+ max-width: 100%;
+ margin-left: auto;
+ margin-right: auto; }
+
+.grid-x {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-orient: horizontal;
+ -webkit-box-direction: normal;
+ -webkit-flex-flow: row wrap;
+ -ms-flex-flow: row wrap;
+ flex-flow: row wrap; }
+
+.cell {
+ -webkit-box-flex: 0;
+ -webkit-flex: 0 0 auto;
+ -ms-flex: 0 0 auto;
+ flex: 0 0 auto;
+ min-height: 0px;
+ min-width: 0px;
+ width: 100%; }
+ .cell.auto {
+ -webkit-box-flex: 1;
+ -webkit-flex: 1 1 0px;
+ -ms-flex: 1 1 0px;
+ flex: 1 1 0px; }
+ .cell.shrink {
+ -webkit-box-flex: 0;
+ -webkit-flex: 0 0 auto;
+ -ms-flex: 0 0 auto;
+ flex: 0 0 auto; }
+
+.grid-x > .auto {
+ width: auto; }
+
+.grid-x > .shrink {
+ width: auto; }
+
+.grid-x > .small-shrink, .grid-x > .small-full, .grid-x > .small-1, .grid-x > .small-2, .grid-x > .small-3, .grid-x > .small-4, .grid-x > .small-5, .grid-x > .small-6, .grid-x > .small-7, .grid-x > .small-8, .grid-x > .small-9, .grid-x > .small-10, .grid-x > .small-11, .grid-x > .small-12 {
+ -webkit-flex-basis: auto;
+ -ms-flex-preferred-size: auto;
+ flex-basis: auto; }
+
+@media print, screen and (min-width: 40em) {
+ .grid-x > .medium-shrink, .grid-x > .medium-full, .grid-x > .medium-1, .grid-x > .medium-2, .grid-x > .medium-3, .grid-x > .medium-4, .grid-x > .medium-5, .grid-x > .medium-6, .grid-x > .medium-7, .grid-x > .medium-8, .grid-x > .medium-9, .grid-x > .medium-10, .grid-x > .medium-11, .grid-x > .medium-12 {
+ -webkit-flex-basis: auto;
+ -ms-flex-preferred-size: auto;
+ flex-basis: auto; } }
+
+@media print, screen and (min-width: 64em) {
+ .grid-x > .large-shrink, .grid-x > .large-full, .grid-x > .large-1, .grid-x > .large-2, .grid-x > .large-3, .grid-x > .large-4, .grid-x > .large-5, .grid-x > .large-6, .grid-x > .large-7, .grid-x > .large-8, .grid-x > .large-9, .grid-x > .large-10, .grid-x > .large-11, .grid-x > .large-12 {
+ -webkit-flex-basis: auto;
+ -ms-flex-preferred-size: auto;
+ flex-basis: auto; } }
+
+.grid-x > .small-1, .grid-x > .small-2, .grid-x > .small-3, .grid-x > .small-4, .grid-x > .small-5, .grid-x > .small-6, .grid-x > .small-7, .grid-x > .small-8, .grid-x > .small-9, .grid-x > .small-10, .grid-x > .small-11, .grid-x > .small-12 {
+ -webkit-box-flex: 0;
+ -webkit-flex: 0 0 auto;
+ -ms-flex: 0 0 auto;
+ flex: 0 0 auto; }
+
+.grid-x > .small-1 {
+ width: 8.33333%; }
+
+.grid-x > .small-2 {
+ width: 16.66667%; }
+
+.grid-x > .small-3 {
+ width: 25%; }
+
+.grid-x > .small-4 {
+ width: 33.33333%; }
+
+.grid-x > .small-5 {
+ width: 41.66667%; }
+
+.grid-x > .small-6 {
+ width: 50%; }
+
+.grid-x > .small-7 {
+ width: 58.33333%; }
+
+.grid-x > .small-8 {
+ width: 66.66667%; }
+
+.grid-x > .small-9 {
+ width: 75%; }
+
+.grid-x > .small-10 {
+ width: 83.33333%; }
+
+.grid-x > .small-11 {
+ width: 91.66667%; }
+
+.grid-x > .small-12 {
+ width: 100%; }
+
+@media print, screen and (min-width: 40em) {
+ .grid-x > .medium-auto {
+ -webkit-box-flex: 1;
+ -webkit-flex: 1 1 0px;
+ -ms-flex: 1 1 0px;
+ flex: 1 1 0px;
+ width: auto; }
+ .grid-x > .medium-shrink, .grid-x > .medium-1, .grid-x > .medium-2, .grid-x > .medium-3, .grid-x > .medium-4, .grid-x > .medium-5, .grid-x > .medium-6, .grid-x > .medium-7, .grid-x > .medium-8, .grid-x > .medium-9, .grid-x > .medium-10, .grid-x > .medium-11, .grid-x > .medium-12 {
+ -webkit-box-flex: 0;
+ -webkit-flex: 0 0 auto;
+ -ms-flex: 0 0 auto;
+ flex: 0 0 auto; }
+ .grid-x > .medium-shrink {
+ width: auto; }
+ .grid-x > .medium-1 {
+ width: 8.33333%; }
+ .grid-x > .medium-2 {
+ width: 16.66667%; }
+ .grid-x > .medium-3 {
+ width: 25%; }
+ .grid-x > .medium-4 {
+ width: 33.33333%; }
+ .grid-x > .medium-5 {
+ width: 41.66667%; }
+ .grid-x > .medium-6 {
+ width: 50%; }
+ .grid-x > .medium-7 {
+ width: 58.33333%; }
+ .grid-x > .medium-8 {
+ width: 66.66667%; }
+ .grid-x > .medium-9 {
+ width: 75%; }
+ .grid-x > .medium-10 {
+ width: 83.33333%; }
+ .grid-x > .medium-11 {
+ width: 91.66667%; }
+ .grid-x > .medium-12 {
+ width: 100%; } }
+
+@media print, screen and (min-width: 64em) {
+ .grid-x > .large-auto {
+ -webkit-box-flex: 1;
+ -webkit-flex: 1 1 0px;
+ -ms-flex: 1 1 0px;
+ flex: 1 1 0px;
+ width: auto; }
+ .grid-x > .large-shrink, .grid-x > .large-1, .grid-x > .large-2, .grid-x > .large-3, .grid-x > .large-4, .grid-x > .large-5, .grid-x > .large-6, .grid-x > .large-7, .grid-x > .large-8, .grid-x > .large-9, .grid-x > .large-10, .grid-x > .large-11, .grid-x > .large-12 {
+ -webkit-box-flex: 0;
+ -webkit-flex: 0 0 auto;
+ -ms-flex: 0 0 auto;
+ flex: 0 0 auto; }
+ .grid-x > .large-shrink {
+ width: auto; }
+ .grid-x > .large-1 {
+ width: 8.33333%; }
+ .grid-x > .large-2 {
+ width: 16.66667%; }
+ .grid-x > .large-3 {
+ width: 25%; }
+ .grid-x > .large-4 {
+ width: 33.33333%; }
+ .grid-x > .large-5 {
+ width: 41.66667%; }
+ .grid-x > .large-6 {
+ width: 50%; }
+ .grid-x > .large-7 {
+ width: 58.33333%; }
+ .grid-x > .large-8 {
+ width: 66.66667%; }
+ .grid-x > .large-9 {
+ width: 75%; }
+ .grid-x > .large-10 {
+ width: 83.33333%; }
+ .grid-x > .large-11 {
+ width: 91.66667%; }
+ .grid-x > .large-12 {
+ width: 100%; } }
+
+.grid-margin-x:not(.grid-x) > .cell {
+ width: auto; }
+
+.grid-margin-y:not(.grid-y) > .cell {
+ height: auto; }
+
+.grid-margin-x {
+ margin-left: -0.625rem;
+ margin-right: -0.625rem; }
+ @media print, screen and (min-width: 40em) {
+ .grid-margin-x {
+ margin-left: -0.9375rem;
+ margin-right: -0.9375rem; } }
+ .grid-margin-x > .cell {
+ width: calc(100% - 1.25rem);
+ margin-left: 0.625rem;
+ margin-right: 0.625rem; }
+ @media print, screen and (min-width: 40em) {
+ .grid-margin-x > .cell {
+ width: calc(100% - 1.875rem);
+ margin-left: 0.9375rem;
+ margin-right: 0.9375rem; } }
+ .grid-margin-x > .auto {
+ width: auto; }
+ .grid-margin-x > .shrink {
+ width: auto; }
+ .grid-margin-x > .small-1 {
+ width: calc(8.33333% - 1.25rem); }
+ .grid-margin-x > .small-2 {
+ width: calc(16.66667% - 1.25rem); }
+ .grid-margin-x > .small-3 {
+ width: calc(25% - 1.25rem); }
+ .grid-margin-x > .small-4 {
+ width: calc(33.33333% - 1.25rem); }
+ .grid-margin-x > .small-5 {
+ width: calc(41.66667% - 1.25rem); }
+ .grid-margin-x > .small-6 {
+ width: calc(50% - 1.25rem); }
+ .grid-margin-x > .small-7 {
+ width: calc(58.33333% - 1.25rem); }
+ .grid-margin-x > .small-8 {
+ width: calc(66.66667% - 1.25rem); }
+ .grid-margin-x > .small-9 {
+ width: calc(75% - 1.25rem); }
+ .grid-margin-x > .small-10 {
+ width: calc(83.33333% - 1.25rem); }
+ .grid-margin-x > .small-11 {
+ width: calc(91.66667% - 1.25rem); }
+ .grid-margin-x > .small-12 {
+ width: calc(100% - 1.25rem); }
+ @media print, screen and (min-width: 40em) {
+ .grid-margin-x > .auto {
+ width: auto; }
+ .grid-margin-x > .shrink {
+ width: auto; }
+ .grid-margin-x > .small-1 {
+ width: calc(8.33333% - 1.875rem); }
+ .grid-margin-x > .small-2 {
+ width: calc(16.66667% - 1.875rem); }
+ .grid-margin-x > .small-3 {
+ width: calc(25% - 1.875rem); }
+ .grid-margin-x > .small-4 {
+ width: calc(33.33333% - 1.875rem); }
+ .grid-margin-x > .small-5 {
+ width: calc(41.66667% - 1.875rem); }
+ .grid-margin-x > .small-6 {
+ width: calc(50% - 1.875rem); }
+ .grid-margin-x > .small-7 {
+ width: calc(58.33333% - 1.875rem); }
+ .grid-margin-x > .small-8 {
+ width: calc(66.66667% - 1.875rem); }
+ .grid-margin-x > .small-9 {
+ width: calc(75% - 1.875rem); }
+ .grid-margin-x > .small-10 {
+ width: calc(83.33333% - 1.875rem); }
+ .grid-margin-x > .small-11 {
+ width: calc(91.66667% - 1.875rem); }
+ .grid-margin-x > .small-12 {
+ width: calc(100% - 1.875rem); }
+ .grid-margin-x > .medium-auto {
+ width: auto; }
+ .grid-margin-x > .medium-shrink {
+ width: auto; }
+ .grid-margin-x > .medium-1 {
+ width: calc(8.33333% - 1.875rem); }
+ .grid-margin-x > .medium-2 {
+ width: calc(16.66667% - 1.875rem); }
+ .grid-margin-x > .medium-3 {
+ width: calc(25% - 1.875rem); }
+ .grid-margin-x > .medium-4 {
+ width: calc(33.33333% - 1.875rem); }
+ .grid-margin-x > .medium-5 {
+ width: calc(41.66667% - 1.875rem); }
+ .grid-margin-x > .medium-6 {
+ width: calc(50% - 1.875rem); }
+ .grid-margin-x > .medium-7 {
+ width: calc(58.33333% - 1.875rem); }
+ .grid-margin-x > .medium-8 {
+ width: calc(66.66667% - 1.875rem); }
+ .grid-margin-x > .medium-9 {
+ width: calc(75% - 1.875rem); }
+ .grid-margin-x > .medium-10 {
+ width: calc(83.33333% - 1.875rem); }
+ .grid-margin-x > .medium-11 {
+ width: calc(91.66667% - 1.875rem); }
+ .grid-margin-x > .medium-12 {
+ width: calc(100% - 1.875rem); } }
+ @media print, screen and (min-width: 64em) {
+ .grid-margin-x > .large-auto {
+ width: auto; }
+ .grid-margin-x > .large-shrink {
+ width: auto; }
+ .grid-margin-x > .large-1 {
+ width: calc(8.33333% - 1.875rem); }
+ .grid-margin-x > .large-2 {
+ width: calc(16.66667% - 1.875rem); }
+ .grid-margin-x > .large-3 {
+ width: calc(25% - 1.875rem); }
+ .grid-margin-x > .large-4 {
+ width: calc(33.33333% - 1.875rem); }
+ .grid-margin-x > .large-5 {
+ width: calc(41.66667% - 1.875rem); }
+ .grid-margin-x > .large-6 {
+ width: calc(50% - 1.875rem); }
+ .grid-margin-x > .large-7 {
+ width: calc(58.33333% - 1.875rem); }
+ .grid-margin-x > .large-8 {
+ width: calc(66.66667% - 1.875rem); }
+ .grid-margin-x > .large-9 {
+ width: calc(75% - 1.875rem); }
+ .grid-margin-x > .large-10 {
+ width: calc(83.33333% - 1.875rem); }
+ .grid-margin-x > .large-11 {
+ width: calc(91.66667% - 1.875rem); }
+ .grid-margin-x > .large-12 {
+ width: calc(100% - 1.875rem); } }
+
+.grid-padding-x .grid-padding-x {
+ margin-right: -0.625rem;
+ margin-left: -0.625rem; }
+ @media print, screen and (min-width: 40em) {
+ .grid-padding-x .grid-padding-x {
+ margin-right: -0.9375rem;
+ margin-left: -0.9375rem; } }
+
+.grid-container:not(.full) > .grid-padding-x {
+ margin-right: -0.625rem;
+ margin-left: -0.625rem; }
+ @media print, screen and (min-width: 40em) {
+ .grid-container:not(.full) > .grid-padding-x {
+ margin-right: -0.9375rem;
+ margin-left: -0.9375rem; } }
+
+.grid-padding-x > .cell {
+ padding-right: 0.625rem;
+ padding-left: 0.625rem; }
+ @media print, screen and (min-width: 40em) {
+ .grid-padding-x > .cell {
+ padding-right: 0.9375rem;
+ padding-left: 0.9375rem; } }
+
+.small-up-1 > .cell {
+ width: 100%; }
+
+.small-up-2 > .cell {
+ width: 50%; }
+
+.small-up-3 > .cell {
+ width: 33.33333%; }
+
+.small-up-4 > .cell {
+ width: 25%; }
+
+.small-up-5 > .cell {
+ width: 20%; }
+
+.small-up-6 > .cell {
+ width: 16.66667%; }
+
+.small-up-7 > .cell {
+ width: 14.28571%; }
+
+.small-up-8 > .cell {
+ width: 12.5%; }
+
+@media print, screen and (min-width: 40em) {
+ .medium-up-1 > .cell {
+ width: 100%; }
+ .medium-up-2 > .cell {
+ width: 50%; }
+ .medium-up-3 > .cell {
+ width: 33.33333%; }
+ .medium-up-4 > .cell {
+ width: 25%; }
+ .medium-up-5 > .cell {
+ width: 20%; }
+ .medium-up-6 > .cell {
+ width: 16.66667%; }
+ .medium-up-7 > .cell {
+ width: 14.28571%; }
+ .medium-up-8 > .cell {
+ width: 12.5%; } }
+
+@media print, screen and (min-width: 64em) {
+ .large-up-1 > .cell {
+ width: 100%; }
+ .large-up-2 > .cell {
+ width: 50%; }
+ .large-up-3 > .cell {
+ width: 33.33333%; }
+ .large-up-4 > .cell {
+ width: 25%; }
+ .large-up-5 > .cell {
+ width: 20%; }
+ .large-up-6 > .cell {
+ width: 16.66667%; }
+ .large-up-7 > .cell {
+ width: 14.28571%; }
+ .large-up-8 > .cell {
+ width: 12.5%; } }
+
+.grid-margin-x.small-up-1 > .cell {
+ width: calc(100% - 1.25rem); }
+
+.grid-margin-x.small-up-2 > .cell {
+ width: calc(50% - 1.25rem); }
+
+.grid-margin-x.small-up-3 > .cell {
+ width: calc(33.33333% - 1.25rem); }
+
+.grid-margin-x.small-up-4 > .cell {
+ width: calc(25% - 1.25rem); }
+
+.grid-margin-x.small-up-5 > .cell {
+ width: calc(20% - 1.25rem); }
+
+.grid-margin-x.small-up-6 > .cell {
+ width: calc(16.66667% - 1.25rem); }
+
+.grid-margin-x.small-up-7 > .cell {
+ width: calc(14.28571% - 1.25rem); }
+
+.grid-margin-x.small-up-8 > .cell {
+ width: calc(12.5% - 1.25rem); }
+
+@media print, screen and (min-width: 40em) {
+ .grid-margin-x.small-up-1 > .cell {
+ width: calc(100% - 1.875rem); }
+ .grid-margin-x.small-up-2 > .cell {
+ width: calc(50% - 1.875rem); }
+ .grid-margin-x.small-up-3 > .cell {
+ width: calc(33.33333% - 1.875rem); }
+ .grid-margin-x.small-up-4 > .cell {
+ width: calc(25% - 1.875rem); }
+ .grid-margin-x.small-up-5 > .cell {
+ width: calc(20% - 1.875rem); }
+ .grid-margin-x.small-up-6 > .cell {
+ width: calc(16.66667% - 1.875rem); }
+ .grid-margin-x.small-up-7 > .cell {
+ width: calc(14.28571% - 1.875rem); }
+ .grid-margin-x.small-up-8 > .cell {
+ width: calc(12.5% - 1.875rem); }
+ .grid-margin-x.medium-up-1 > .cell {
+ width: calc(100% - 1.875rem); }
+ .grid-margin-x.medium-up-2 > .cell {
+ width: calc(50% - 1.875rem); }
+ .grid-margin-x.medium-up-3 > .cell {
+ width: calc(33.33333% - 1.875rem); }
+ .grid-margin-x.medium-up-4 > .cell {
+ width: calc(25% - 1.875rem); }
+ .grid-margin-x.medium-up-5 > .cell {
+ width: calc(20% - 1.875rem); }
+ .grid-margin-x.medium-up-6 > .cell {
+ width: calc(16.66667% - 1.875rem); }
+ .grid-margin-x.medium-up-7 > .cell {
+ width: calc(14.28571% - 1.875rem); }
+ .grid-margin-x.medium-up-8 > .cell {
+ width: calc(12.5% - 1.875rem); } }
+
+@media print, screen and (min-width: 64em) {
+ .grid-margin-x.large-up-1 > .cell {
+ width: calc(100% - 1.875rem); }
+ .grid-margin-x.large-up-2 > .cell {
+ width: calc(50% - 1.875rem); }
+ .grid-margin-x.large-up-3 > .cell {
+ width: calc(33.33333% - 1.875rem); }
+ .grid-margin-x.large-up-4 > .cell {
+ width: calc(25% - 1.875rem); }
+ .grid-margin-x.large-up-5 > .cell {
+ width: calc(20% - 1.875rem); }
+ .grid-margin-x.large-up-6 > .cell {
+ width: calc(16.66667% - 1.875rem); }
+ .grid-margin-x.large-up-7 > .cell {
+ width: calc(14.28571% - 1.875rem); }
+ .grid-margin-x.large-up-8 > .cell {
+ width: calc(12.5% - 1.875rem); } }
+
+.small-margin-collapse {
+ margin-right: 0;
+ margin-left: 0; }
+ .small-margin-collapse > .cell {
+ margin-right: 0;
+ margin-left: 0; }
+ .small-margin-collapse > .small-1 {
+ width: 8.33333%; }
+ .small-margin-collapse > .small-2 {
+ width: 16.66667%; }
+ .small-margin-collapse > .small-3 {
+ width: 25%; }
+ .small-margin-collapse > .small-4 {
+ width: 33.33333%; }
+ .small-margin-collapse > .small-5 {
+ width: 41.66667%; }
+ .small-margin-collapse > .small-6 {
+ width: 50%; }
+ .small-margin-collapse > .small-7 {
+ width: 58.33333%; }
+ .small-margin-collapse > .small-8 {
+ width: 66.66667%; }
+ .small-margin-collapse > .small-9 {
+ width: 75%; }
+ .small-margin-collapse > .small-10 {
+ width: 83.33333%; }
+ .small-margin-collapse > .small-11 {
+ width: 91.66667%; }
+ .small-margin-collapse > .small-12 {
+ width: 100%; }
+ @media print, screen and (min-width: 40em) {
+ .small-margin-collapse > .medium-1 {
+ width: 8.33333%; }
+ .small-margin-collapse > .medium-2 {
+ width: 16.66667%; }
+ .small-margin-collapse > .medium-3 {
+ width: 25%; }
+ .small-margin-collapse > .medium-4 {
+ width: 33.33333%; }
+ .small-margin-collapse > .medium-5 {
+ width: 41.66667%; }
+ .small-margin-collapse > .medium-6 {
+ width: 50%; }
+ .small-margin-collapse > .medium-7 {
+ width: 58.33333%; }
+ .small-margin-collapse > .medium-8 {
+ width: 66.66667%; }
+ .small-margin-collapse > .medium-9 {
+ width: 75%; }
+ .small-margin-collapse > .medium-10 {
+ width: 83.33333%; }
+ .small-margin-collapse > .medium-11 {
+ width: 91.66667%; }
+ .small-margin-collapse > .medium-12 {
+ width: 100%; } }
+ @media print, screen and (min-width: 64em) {
+ .small-margin-collapse > .large-1 {
+ width: 8.33333%; }
+ .small-margin-collapse > .large-2 {
+ width: 16.66667%; }
+ .small-margin-collapse > .large-3 {
+ width: 25%; }
+ .small-margin-collapse > .large-4 {
+ width: 33.33333%; }
+ .small-margin-collapse > .large-5 {
+ width: 41.66667%; }
+ .small-margin-collapse > .large-6 {
+ width: 50%; }
+ .small-margin-collapse > .large-7 {
+ width: 58.33333%; }
+ .small-margin-collapse > .large-8 {
+ width: 66.66667%; }
+ .small-margin-collapse > .large-9 {
+ width: 75%; }
+ .small-margin-collapse > .large-10 {
+ width: 83.33333%; }
+ .small-margin-collapse > .large-11 {
+ width: 91.66667%; }
+ .small-margin-collapse > .large-12 {
+ width: 100%; } }
+
+.small-padding-collapse {
+ margin-right: 0;
+ margin-left: 0; }
+ .small-padding-collapse > .cell {
+ padding-right: 0;
+ padding-left: 0; }
+
+@media print, screen and (min-width: 40em) {
+ .medium-margin-collapse {
+ margin-right: 0;
+ margin-left: 0; }
+ .medium-margin-collapse > .cell {
+ margin-right: 0;
+ margin-left: 0; } }
+
+@media print, screen and (min-width: 40em) {
+ .medium-margin-collapse > .small-1 {
+ width: 8.33333%; }
+ .medium-margin-collapse > .small-2 {
+ width: 16.66667%; }
+ .medium-margin-collapse > .small-3 {
+ width: 25%; }
+ .medium-margin-collapse > .small-4 {
+ width: 33.33333%; }
+ .medium-margin-collapse > .small-5 {
+ width: 41.66667%; }
+ .medium-margin-collapse > .small-6 {
+ width: 50%; }
+ .medium-margin-collapse > .small-7 {
+ width: 58.33333%; }
+ .medium-margin-collapse > .small-8 {
+ width: 66.66667%; }
+ .medium-margin-collapse > .small-9 {
+ width: 75%; }
+ .medium-margin-collapse > .small-10 {
+ width: 83.33333%; }
+ .medium-margin-collapse > .small-11 {
+ width: 91.66667%; }
+ .medium-margin-collapse > .small-12 {
+ width: 100%; } }
+
+@media print, screen and (min-width: 40em) {
+ .medium-margin-collapse > .medium-1 {
+ width: 8.33333%; }
+ .medium-margin-collapse > .medium-2 {
+ width: 16.66667%; }
+ .medium-margin-collapse > .medium-3 {
+ width: 25%; }
+ .medium-margin-collapse > .medium-4 {
+ width: 33.33333%; }
+ .medium-margin-collapse > .medium-5 {
+ width: 41.66667%; }
+ .medium-margin-collapse > .medium-6 {
+ width: 50%; }
+ .medium-margin-collapse > .medium-7 {
+ width: 58.33333%; }
+ .medium-margin-collapse > .medium-8 {
+ width: 66.66667%; }
+ .medium-margin-collapse > .medium-9 {
+ width: 75%; }
+ .medium-margin-collapse > .medium-10 {
+ width: 83.33333%; }
+ .medium-margin-collapse > .medium-11 {
+ width: 91.66667%; }
+ .medium-margin-collapse > .medium-12 {
+ width: 100%; } }
+
+@media print, screen and (min-width: 64em) {
+ .medium-margin-collapse > .large-1 {
+ width: 8.33333%; }
+ .medium-margin-collapse > .large-2 {
+ width: 16.66667%; }
+ .medium-margin-collapse > .large-3 {
+ width: 25%; }
+ .medium-margin-collapse > .large-4 {
+ width: 33.33333%; }
+ .medium-margin-collapse > .large-5 {
+ width: 41.66667%; }
+ .medium-margin-collapse > .large-6 {
+ width: 50%; }
+ .medium-margin-collapse > .large-7 {
+ width: 58.33333%; }
+ .medium-margin-collapse > .large-8 {
+ width: 66.66667%; }
+ .medium-margin-collapse > .large-9 {
+ width: 75%; }
+ .medium-margin-collapse > .large-10 {
+ width: 83.33333%; }
+ .medium-margin-collapse > .large-11 {
+ width: 91.66667%; }
+ .medium-margin-collapse > .large-12 {
+ width: 100%; } }
+
+@media print, screen and (min-width: 40em) {
+ .medium-padding-collapse {
+ margin-right: 0;
+ margin-left: 0; }
+ .medium-padding-collapse > .cell {
+ padding-right: 0;
+ padding-left: 0; } }
+
+@media print, screen and (min-width: 64em) {
+ .large-margin-collapse {
+ margin-right: 0;
+ margin-left: 0; }
+ .large-margin-collapse > .cell {
+ margin-right: 0;
+ margin-left: 0; } }
+
+@media print, screen and (min-width: 64em) {
+ .large-margin-collapse > .small-1 {
+ width: 8.33333%; }
+ .large-margin-collapse > .small-2 {
+ width: 16.66667%; }
+ .large-margin-collapse > .small-3 {
+ width: 25%; }
+ .large-margin-collapse > .small-4 {
+ width: 33.33333%; }
+ .large-margin-collapse > .small-5 {
+ width: 41.66667%; }
+ .large-margin-collapse > .small-6 {
+ width: 50%; }
+ .large-margin-collapse > .small-7 {
+ width: 58.33333%; }
+ .large-margin-collapse > .small-8 {
+ width: 66.66667%; }
+ .large-margin-collapse > .small-9 {
+ width: 75%; }
+ .large-margin-collapse > .small-10 {
+ width: 83.33333%; }
+ .large-margin-collapse > .small-11 {
+ width: 91.66667%; }
+ .large-margin-collapse > .small-12 {
+ width: 100%; } }
+
+@media print, screen and (min-width: 64em) {
+ .large-margin-collapse > .medium-1 {
+ width: 8.33333%; }
+ .large-margin-collapse > .medium-2 {
+ width: 16.66667%; }
+ .large-margin-collapse > .medium-3 {
+ width: 25%; }
+ .large-margin-collapse > .medium-4 {
+ width: 33.33333%; }
+ .large-margin-collapse > .medium-5 {
+ width: 41.66667%; }
+ .large-margin-collapse > .medium-6 {
+ width: 50%; }
+ .large-margin-collapse > .medium-7 {
+ width: 58.33333%; }
+ .large-margin-collapse > .medium-8 {
+ width: 66.66667%; }
+ .large-margin-collapse > .medium-9 {
+ width: 75%; }
+ .large-margin-collapse > .medium-10 {
+ width: 83.33333%; }
+ .large-margin-collapse > .medium-11 {
+ width: 91.66667%; }
+ .large-margin-collapse > .medium-12 {
+ width: 100%; } }
+
+@media print, screen and (min-width: 64em) {
+ .large-margin-collapse > .large-1 {
+ width: 8.33333%; }
+ .large-margin-collapse > .large-2 {
+ width: 16.66667%; }
+ .large-margin-collapse > .large-3 {
+ width: 25%; }
+ .large-margin-collapse > .large-4 {
+ width: 33.33333%; }
+ .large-margin-collapse > .large-5 {
+ width: 41.66667%; }
+ .large-margin-collapse > .large-6 {
+ width: 50%; }
+ .large-margin-collapse > .large-7 {
+ width: 58.33333%; }
+ .large-margin-collapse > .large-8 {
+ width: 66.66667%; }
+ .large-margin-collapse > .large-9 {
+ width: 75%; }
+ .large-margin-collapse > .large-10 {
+ width: 83.33333%; }
+ .large-margin-collapse > .large-11 {
+ width: 91.66667%; }
+ .large-margin-collapse > .large-12 {
+ width: 100%; } }
+
+@media print, screen and (min-width: 64em) {
+ .large-padding-collapse {
+ margin-right: 0;
+ margin-left: 0; }
+ .large-padding-collapse > .cell {
+ padding-right: 0;
+ padding-left: 0; } }
+
+.small-offset-0 {
+ margin-left: 0%; }
+
+.grid-margin-x > .small-offset-0 {
+ margin-left: calc(0% + 1.25rem / 2); }
+
+.small-offset-1 {
+ margin-left: 8.33333%; }
+
+.grid-margin-x > .small-offset-1 {
+ margin-left: calc(8.33333% + 1.25rem / 2); }
+
+.small-offset-2 {
+ margin-left: 16.66667%; }
+
+.grid-margin-x > .small-offset-2 {
+ margin-left: calc(16.66667% + 1.25rem / 2); }
+
+.small-offset-3 {
+ margin-left: 25%; }
+
+.grid-margin-x > .small-offset-3 {
+ margin-left: calc(25% + 1.25rem / 2); }
+
+.small-offset-4 {
+ margin-left: 33.33333%; }
+
+.grid-margin-x > .small-offset-4 {
+ margin-left: calc(33.33333% + 1.25rem / 2); }
+
+.small-offset-5 {
+ margin-left: 41.66667%; }
+
+.grid-margin-x > .small-offset-5 {
+ margin-left: calc(41.66667% + 1.25rem / 2); }
+
+.small-offset-6 {
+ margin-left: 50%; }
+
+.grid-margin-x > .small-offset-6 {
+ margin-left: calc(50% + 1.25rem / 2); }
+
+.small-offset-7 {
+ margin-left: 58.33333%; }
+
+.grid-margin-x > .small-offset-7 {
+ margin-left: calc(58.33333% + 1.25rem / 2); }
+
+.small-offset-8 {
+ margin-left: 66.66667%; }
+
+.grid-margin-x > .small-offset-8 {
+ margin-left: calc(66.66667% + 1.25rem / 2); }
+
+.small-offset-9 {
+ margin-left: 75%; }
+
+.grid-margin-x > .small-offset-9 {
+ margin-left: calc(75% + 1.25rem / 2); }
+
+.small-offset-10 {
+ margin-left: 83.33333%; }
+
+.grid-margin-x > .small-offset-10 {
+ margin-left: calc(83.33333% + 1.25rem / 2); }
+
+.small-offset-11 {
+ margin-left: 91.66667%; }
+
+.grid-margin-x > .small-offset-11 {
+ margin-left: calc(91.66667% + 1.25rem / 2); }
+
+@media print, screen and (min-width: 40em) {
+ .medium-offset-0 {
+ margin-left: 0%; }
+ .grid-margin-x > .medium-offset-0 {
+ margin-left: calc(0% + 1.875rem / 2); }
+ .medium-offset-1 {
+ margin-left: 8.33333%; }
+ .grid-margin-x > .medium-offset-1 {
+ margin-left: calc(8.33333% + 1.875rem / 2); }
+ .medium-offset-2 {
+ margin-left: 16.66667%; }
+ .grid-margin-x > .medium-offset-2 {
+ margin-left: calc(16.66667% + 1.875rem / 2); }
+ .medium-offset-3 {
+ margin-left: 25%; }
+ .grid-margin-x > .medium-offset-3 {
+ margin-left: calc(25% + 1.875rem / 2); }
+ .medium-offset-4 {
+ margin-left: 33.33333%; }
+ .grid-margin-x > .medium-offset-4 {
+ margin-left: calc(33.33333% + 1.875rem / 2); }
+ .medium-offset-5 {
+ margin-left: 41.66667%; }
+ .grid-margin-x > .medium-offset-5 {
+ margin-left: calc(41.66667% + 1.875rem / 2); }
+ .medium-offset-6 {
+ margin-left: 50%; }
+ .grid-margin-x > .medium-offset-6 {
+ margin-left: calc(50% + 1.875rem / 2); }
+ .medium-offset-7 {
+ margin-left: 58.33333%; }
+ .grid-margin-x > .medium-offset-7 {
+ margin-left: calc(58.33333% + 1.875rem / 2); }
+ .medium-offset-8 {
+ margin-left: 66.66667%; }
+ .grid-margin-x > .medium-offset-8 {
+ margin-left: calc(66.66667% + 1.875rem / 2); }
+ .medium-offset-9 {
+ margin-left: 75%; }
+ .grid-margin-x > .medium-offset-9 {
+ margin-left: calc(75% + 1.875rem / 2); }
+ .medium-offset-10 {
+ margin-left: 83.33333%; }
+ .grid-margin-x > .medium-offset-10 {
+ margin-left: calc(83.33333% + 1.875rem / 2); }
+ .medium-offset-11 {
+ margin-left: 91.66667%; }
+ .grid-margin-x > .medium-offset-11 {
+ margin-left: calc(91.66667% + 1.875rem / 2); } }
+
+@media print, screen and (min-width: 64em) {
+ .large-offset-0 {
+ margin-left: 0%; }
+ .grid-margin-x > .large-offset-0 {
+ margin-left: calc(0% + 1.875rem / 2); }
+ .large-offset-1 {
+ margin-left: 8.33333%; }
+ .grid-margin-x > .large-offset-1 {
+ margin-left: calc(8.33333% + 1.875rem / 2); }
+ .large-offset-2 {
+ margin-left: 16.66667%; }
+ .grid-margin-x > .large-offset-2 {
+ margin-left: calc(16.66667% + 1.875rem / 2); }
+ .large-offset-3 {
+ margin-left: 25%; }
+ .grid-margin-x > .large-offset-3 {
+ margin-left: calc(25% + 1.875rem / 2); }
+ .large-offset-4 {
+ margin-left: 33.33333%; }
+ .grid-margin-x > .large-offset-4 {
+ margin-left: calc(33.33333% + 1.875rem / 2); }
+ .large-offset-5 {
+ margin-left: 41.66667%; }
+ .grid-margin-x > .large-offset-5 {
+ margin-left: calc(41.66667% + 1.875rem / 2); }
+ .large-offset-6 {
+ margin-left: 50%; }
+ .grid-margin-x > .large-offset-6 {
+ margin-left: calc(50% + 1.875rem / 2); }
+ .large-offset-7 {
+ margin-left: 58.33333%; }
+ .grid-margin-x > .large-offset-7 {
+ margin-left: calc(58.33333% + 1.875rem / 2); }
+ .large-offset-8 {
+ margin-left: 66.66667%; }
+ .grid-margin-x > .large-offset-8 {
+ margin-left: calc(66.66667% + 1.875rem / 2); }
+ .large-offset-9 {
+ margin-left: 75%; }
+ .grid-margin-x > .large-offset-9 {
+ margin-left: calc(75% + 1.875rem / 2); }
+ .large-offset-10 {
+ margin-left: 83.33333%; }
+ .grid-margin-x > .large-offset-10 {
+ margin-left: calc(83.33333% + 1.875rem / 2); }
+ .large-offset-11 {
+ margin-left: 91.66667%; }
+ .grid-margin-x > .large-offset-11 {
+ margin-left: calc(91.66667% + 1.875rem / 2); } }
+
+.grid-y {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: normal;
+ -webkit-flex-flow: column nowrap;
+ -ms-flex-flow: column nowrap;
+ flex-flow: column nowrap; }
+ .grid-y > .cell {
+ height: auto;
+ max-height: none; }
+ .grid-y > .auto {
+ height: auto; }
+ .grid-y > .shrink {
+ height: auto; }
+ .grid-y > .small-shrink, .grid-y > .small-full, .grid-y > .small-1, .grid-y > .small-2, .grid-y > .small-3, .grid-y > .small-4, .grid-y > .small-5, .grid-y > .small-6, .grid-y > .small-7, .grid-y > .small-8, .grid-y > .small-9, .grid-y > .small-10, .grid-y > .small-11, .grid-y > .small-12 {
+ -webkit-flex-basis: auto;
+ -ms-flex-preferred-size: auto;
+ flex-basis: auto; }
+ @media print, screen and (min-width: 40em) {
+ .grid-y > .medium-shrink, .grid-y > .medium-full, .grid-y > .medium-1, .grid-y > .medium-2, .grid-y > .medium-3, .grid-y > .medium-4, .grid-y > .medium-5, .grid-y > .medium-6, .grid-y > .medium-7, .grid-y > .medium-8, .grid-y > .medium-9, .grid-y > .medium-10, .grid-y > .medium-11, .grid-y > .medium-12 {
+ -webkit-flex-basis: auto;
+ -ms-flex-preferred-size: auto;
+ flex-basis: auto; } }
+ @media print, screen and (min-width: 64em) {
+ .grid-y > .large-shrink, .grid-y > .large-full, .grid-y > .large-1, .grid-y > .large-2, .grid-y > .large-3, .grid-y > .large-4, .grid-y > .large-5, .grid-y > .large-6, .grid-y > .large-7, .grid-y > .large-8, .grid-y > .large-9, .grid-y > .large-10, .grid-y > .large-11, .grid-y > .large-12 {
+ -webkit-flex-basis: auto;
+ -ms-flex-preferred-size: auto;
+ flex-basis: auto; } }
+ .grid-y > .small-1, .grid-y > .small-2, .grid-y > .small-3, .grid-y > .small-4, .grid-y > .small-5, .grid-y > .small-6, .grid-y > .small-7, .grid-y > .small-8, .grid-y > .small-9, .grid-y > .small-10, .grid-y > .small-11, .grid-y > .small-12 {
+ -webkit-box-flex: 0;
+ -webkit-flex: 0 0 auto;
+ -ms-flex: 0 0 auto;
+ flex: 0 0 auto; }
+ .grid-y > .small-1 {
+ height: 8.33333%; }
+ .grid-y > .small-2 {
+ height: 16.66667%; }
+ .grid-y > .small-3 {
+ height: 25%; }
+ .grid-y > .small-4 {
+ height: 33.33333%; }
+ .grid-y > .small-5 {
+ height: 41.66667%; }
+ .grid-y > .small-6 {
+ height: 50%; }
+ .grid-y > .small-7 {
+ height: 58.33333%; }
+ .grid-y > .small-8 {
+ height: 66.66667%; }
+ .grid-y > .small-9 {
+ height: 75%; }
+ .grid-y > .small-10 {
+ height: 83.33333%; }
+ .grid-y > .small-11 {
+ height: 91.66667%; }
+ .grid-y > .small-12 {
+ height: 100%; }
+ @media print, screen and (min-width: 40em) {
+ .grid-y > .medium-auto {
+ -webkit-box-flex: 1;
+ -webkit-flex: 1 1 0px;
+ -ms-flex: 1 1 0px;
+ flex: 1 1 0px;
+ height: auto; }
+ .grid-y > .medium-shrink, .grid-y > .medium-1, .grid-y > .medium-2, .grid-y > .medium-3, .grid-y > .medium-4, .grid-y > .medium-5, .grid-y > .medium-6, .grid-y > .medium-7, .grid-y > .medium-8, .grid-y > .medium-9, .grid-y > .medium-10, .grid-y > .medium-11, .grid-y > .medium-12 {
+ -webkit-box-flex: 0;
+ -webkit-flex: 0 0 auto;
+ -ms-flex: 0 0 auto;
+ flex: 0 0 auto; }
+ .grid-y > .medium-shrink {
+ height: auto; }
+ .grid-y > .medium-1 {
+ height: 8.33333%; }
+ .grid-y > .medium-2 {
+ height: 16.66667%; }
+ .grid-y > .medium-3 {
+ height: 25%; }
+ .grid-y > .medium-4 {
+ height: 33.33333%; }
+ .grid-y > .medium-5 {
+ height: 41.66667%; }
+ .grid-y > .medium-6 {
+ height: 50%; }
+ .grid-y > .medium-7 {
+ height: 58.33333%; }
+ .grid-y > .medium-8 {
+ height: 66.66667%; }
+ .grid-y > .medium-9 {
+ height: 75%; }
+ .grid-y > .medium-10 {
+ height: 83.33333%; }
+ .grid-y > .medium-11 {
+ height: 91.66667%; }
+ .grid-y > .medium-12 {
+ height: 100%; } }
+ @media print, screen and (min-width: 64em) {
+ .grid-y > .large-auto {
+ -webkit-box-flex: 1;
+ -webkit-flex: 1 1 0px;
+ -ms-flex: 1 1 0px;
+ flex: 1 1 0px;
+ height: auto; }
+ .grid-y > .large-shrink, .grid-y > .large-1, .grid-y > .large-2, .grid-y > .large-3, .grid-y > .large-4, .grid-y > .large-5, .grid-y > .large-6, .grid-y > .large-7, .grid-y > .large-8, .grid-y > .large-9, .grid-y > .large-10, .grid-y > .large-11, .grid-y > .large-12 {
+ -webkit-box-flex: 0;
+ -webkit-flex: 0 0 auto;
+ -ms-flex: 0 0 auto;
+ flex: 0 0 auto; }
+ .grid-y > .large-shrink {
+ height: auto; }
+ .grid-y > .large-1 {
+ height: 8.33333%; }
+ .grid-y > .large-2 {
+ height: 16.66667%; }
+ .grid-y > .large-3 {
+ height: 25%; }
+ .grid-y > .large-4 {
+ height: 33.33333%; }
+ .grid-y > .large-5 {
+ height: 41.66667%; }
+ .grid-y > .large-6 {
+ height: 50%; }
+ .grid-y > .large-7 {
+ height: 58.33333%; }
+ .grid-y > .large-8 {
+ height: 66.66667%; }
+ .grid-y > .large-9 {
+ height: 75%; }
+ .grid-y > .large-10 {
+ height: 83.33333%; }
+ .grid-y > .large-11 {
+ height: 91.66667%; }
+ .grid-y > .large-12 {
+ height: 100%; } }
+
+.grid-padding-y .grid-padding-y {
+ margin-top: -0.625rem;
+ margin-bottom: -0.625rem; }
+ @media print, screen and (min-width: 40em) {
+ .grid-padding-y .grid-padding-y {
+ margin-top: -0.9375rem;
+ margin-bottom: -0.9375rem; } }
+
+.grid-padding-y > .cell {
+ padding-top: 0.625rem;
+ padding-bottom: 0.625rem; }
+ @media print, screen and (min-width: 40em) {
+ .grid-padding-y > .cell {
+ padding-top: 0.9375rem;
+ padding-bottom: 0.9375rem; } }
+
+.grid-margin-y {
+ margin-top: -0.625rem;
+ margin-bottom: -0.625rem; }
+ @media print, screen and (min-width: 40em) {
+ .grid-margin-y {
+ margin-top: -0.9375rem;
+ margin-bottom: -0.9375rem; } }
+ .grid-margin-y > .cell {
+ height: calc(100% - 1.25rem);
+ margin-top: 0.625rem;
+ margin-bottom: 0.625rem; }
+ @media print, screen and (min-width: 40em) {
+ .grid-margin-y > .cell {
+ height: calc(100% - 1.875rem);
+ margin-top: 0.9375rem;
+ margin-bottom: 0.9375rem; } }
+ .grid-margin-y > .auto {
+ height: auto; }
+ .grid-margin-y > .shrink {
+ height: auto; }
+ .grid-margin-y > .small-1 {
+ height: calc(8.33333% - 1.25rem); }
+ .grid-margin-y > .small-2 {
+ height: calc(16.66667% - 1.25rem); }
+ .grid-margin-y > .small-3 {
+ height: calc(25% - 1.25rem); }
+ .grid-margin-y > .small-4 {
+ height: calc(33.33333% - 1.25rem); }
+ .grid-margin-y > .small-5 {
+ height: calc(41.66667% - 1.25rem); }
+ .grid-margin-y > .small-6 {
+ height: calc(50% - 1.25rem); }
+ .grid-margin-y > .small-7 {
+ height: calc(58.33333% - 1.25rem); }
+ .grid-margin-y > .small-8 {
+ height: calc(66.66667% - 1.25rem); }
+ .grid-margin-y > .small-9 {
+ height: calc(75% - 1.25rem); }
+ .grid-margin-y > .small-10 {
+ height: calc(83.33333% - 1.25rem); }
+ .grid-margin-y > .small-11 {
+ height: calc(91.66667% - 1.25rem); }
+ .grid-margin-y > .small-12 {
+ height: calc(100% - 1.25rem); }
+ @media print, screen and (min-width: 40em) {
+ .grid-margin-y > .auto {
+ height: auto; }
+ .grid-margin-y > .shrink {
+ height: auto; }
+ .grid-margin-y > .small-1 {
+ height: calc(8.33333% - 1.875rem); }
+ .grid-margin-y > .small-2 {
+ height: calc(16.66667% - 1.875rem); }
+ .grid-margin-y > .small-3 {
+ height: calc(25% - 1.875rem); }
+ .grid-margin-y > .small-4 {
+ height: calc(33.33333% - 1.875rem); }
+ .grid-margin-y > .small-5 {
+ height: calc(41.66667% - 1.875rem); }
+ .grid-margin-y > .small-6 {
+ height: calc(50% - 1.875rem); }
+ .grid-margin-y > .small-7 {
+ height: calc(58.33333% - 1.875rem); }
+ .grid-margin-y > .small-8 {
+ height: calc(66.66667% - 1.875rem); }
+ .grid-margin-y > .small-9 {
+ height: calc(75% - 1.875rem); }
+ .grid-margin-y > .small-10 {
+ height: calc(83.33333% - 1.875rem); }
+ .grid-margin-y > .small-11 {
+ height: calc(91.66667% - 1.875rem); }
+ .grid-margin-y > .small-12 {
+ height: calc(100% - 1.875rem); }
+ .grid-margin-y > .medium-auto {
+ height: auto; }
+ .grid-margin-y > .medium-shrink {
+ height: auto; }
+ .grid-margin-y > .medium-1 {
+ height: calc(8.33333% - 1.875rem); }
+ .grid-margin-y > .medium-2 {
+ height: calc(16.66667% - 1.875rem); }
+ .grid-margin-y > .medium-3 {
+ height: calc(25% - 1.875rem); }
+ .grid-margin-y > .medium-4 {
+ height: calc(33.33333% - 1.875rem); }
+ .grid-margin-y > .medium-5 {
+ height: calc(41.66667% - 1.875rem); }
+ .grid-margin-y > .medium-6 {
+ height: calc(50% - 1.875rem); }
+ .grid-margin-y > .medium-7 {
+ height: calc(58.33333% - 1.875rem); }
+ .grid-margin-y > .medium-8 {
+ height: calc(66.66667% - 1.875rem); }
+ .grid-margin-y > .medium-9 {
+ height: calc(75% - 1.875rem); }
+ .grid-margin-y > .medium-10 {
+ height: calc(83.33333% - 1.875rem); }
+ .grid-margin-y > .medium-11 {
+ height: calc(91.66667% - 1.875rem); }
+ .grid-margin-y > .medium-12 {
+ height: calc(100% - 1.875rem); } }
+ @media print, screen and (min-width: 64em) {
+ .grid-margin-y > .large-auto {
+ height: auto; }
+ .grid-margin-y > .large-shrink {
+ height: auto; }
+ .grid-margin-y > .large-1 {
+ height: calc(8.33333% - 1.875rem); }
+ .grid-margin-y > .large-2 {
+ height: calc(16.66667% - 1.875rem); }
+ .grid-margin-y > .large-3 {
+ height: calc(25% - 1.875rem); }
+ .grid-margin-y > .large-4 {
+ height: calc(33.33333% - 1.875rem); }
+ .grid-margin-y > .large-5 {
+ height: calc(41.66667% - 1.875rem); }
+ .grid-margin-y > .large-6 {
+ height: calc(50% - 1.875rem); }
+ .grid-margin-y > .large-7 {
+ height: calc(58.33333% - 1.875rem); }
+ .grid-margin-y > .large-8 {
+ height: calc(66.66667% - 1.875rem); }
+ .grid-margin-y > .large-9 {
+ height: calc(75% - 1.875rem); }
+ .grid-margin-y > .large-10 {
+ height: calc(83.33333% - 1.875rem); }
+ .grid-margin-y > .large-11 {
+ height: calc(91.66667% - 1.875rem); }
+ .grid-margin-y > .large-12 {
+ height: calc(100% - 1.875rem); } }
+
+.grid-frame {
+ overflow: hidden;
+ position: relative;
+ -webkit-flex-wrap: nowrap;
+ -ms-flex-wrap: nowrap;
+ flex-wrap: nowrap;
+ -webkit-box-align: stretch;
+ -webkit-align-items: stretch;
+ -ms-flex-align: stretch;
+ align-items: stretch;
+ width: 100vw; }
+
+.cell .grid-frame {
+ width: 100%; }
+
+.cell-block {
+ overflow-x: auto;
+ max-width: 100%;
+ -webkit-overflow-scrolling: touch;
+ -ms-overflow-style: -ms-autohiding-scrollbar; }
+
+.cell-block-y {
+ overflow-y: auto;
+ max-height: 100%;
+ min-height: 100%;
+ -webkit-overflow-scrolling: touch;
+ -ms-overflow-style: -ms-autohiding-scrollbar; }
+
+.cell-block-container {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: normal;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ max-height: 100%; }
+ .cell-block-container > .grid-x {
+ max-height: 100%;
+ -webkit-flex-wrap: nowrap;
+ -ms-flex-wrap: nowrap;
+ flex-wrap: nowrap; }
+
+@media print, screen and (min-width: 40em) {
+ .medium-grid-frame {
+ overflow: hidden;
+ position: relative;
+ -webkit-flex-wrap: nowrap;
+ -ms-flex-wrap: nowrap;
+ flex-wrap: nowrap;
+ -webkit-box-align: stretch;
+ -webkit-align-items: stretch;
+ -ms-flex-align: stretch;
+ align-items: stretch;
+ width: 100vw; }
+ .cell .medium-grid-frame {
+ width: 100%; }
+ .medium-cell-block {
+ overflow-x: auto;
+ max-width: 100%;
+ -webkit-overflow-scrolling: touch;
+ -ms-overflow-style: -ms-autohiding-scrollbar; }
+ .medium-cell-block-container {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: normal;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ max-height: 100%; }
+ .medium-cell-block-container > .grid-x {
+ max-height: 100%;
+ -webkit-flex-wrap: nowrap;
+ -ms-flex-wrap: nowrap;
+ flex-wrap: nowrap; }
+ .medium-cell-block-y {
+ overflow-y: auto;
+ max-height: 100%;
+ min-height: 100%;
+ -webkit-overflow-scrolling: touch;
+ -ms-overflow-style: -ms-autohiding-scrollbar; } }
+
+@media print, screen and (min-width: 64em) {
+ .large-grid-frame {
+ overflow: hidden;
+ position: relative;
+ -webkit-flex-wrap: nowrap;
+ -ms-flex-wrap: nowrap;
+ flex-wrap: nowrap;
+ -webkit-box-align: stretch;
+ -webkit-align-items: stretch;
+ -ms-flex-align: stretch;
+ align-items: stretch;
+ width: 100vw; }
+ .cell .large-grid-frame {
+ width: 100%; }
+ .large-cell-block {
+ overflow-x: auto;
+ max-width: 100%;
+ -webkit-overflow-scrolling: touch;
+ -ms-overflow-style: -ms-autohiding-scrollbar; }
+ .large-cell-block-container {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: normal;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ max-height: 100%; }
+ .large-cell-block-container > .grid-x {
+ max-height: 100%;
+ -webkit-flex-wrap: nowrap;
+ -ms-flex-wrap: nowrap;
+ flex-wrap: nowrap; }
+ .large-cell-block-y {
+ overflow-y: auto;
+ max-height: 100%;
+ min-height: 100%;
+ -webkit-overflow-scrolling: touch;
+ -ms-overflow-style: -ms-autohiding-scrollbar; } }
+
+.grid-y.grid-frame {
+ width: auto;
+ overflow: hidden;
+ position: relative;
+ -webkit-flex-wrap: nowrap;
+ -ms-flex-wrap: nowrap;
+ flex-wrap: nowrap;
+ -webkit-box-align: stretch;
+ -webkit-align-items: stretch;
+ -ms-flex-align: stretch;
+ align-items: stretch;
+ height: 100vh; }
+
+@media print, screen and (min-width: 40em) {
+ .grid-y.medium-grid-frame {
+ width: auto;
+ overflow: hidden;
+ position: relative;
+ -webkit-flex-wrap: nowrap;
+ -ms-flex-wrap: nowrap;
+ flex-wrap: nowrap;
+ -webkit-box-align: stretch;
+ -webkit-align-items: stretch;
+ -ms-flex-align: stretch;
+ align-items: stretch;
+ height: 100vh; } }
+
+@media print, screen and (min-width: 64em) {
+ .grid-y.large-grid-frame {
+ width: auto;
+ overflow: hidden;
+ position: relative;
+ -webkit-flex-wrap: nowrap;
+ -ms-flex-wrap: nowrap;
+ flex-wrap: nowrap;
+ -webkit-box-align: stretch;
+ -webkit-align-items: stretch;
+ -ms-flex-align: stretch;
+ align-items: stretch;
+ height: 100vh; } }
+
+.cell .grid-y.grid-frame {
+ height: 100%; }
+
+@media print, screen and (min-width: 40em) {
+ .cell .grid-y.medium-grid-frame {
+ height: 100%; } }
+
+@media print, screen and (min-width: 64em) {
+ .cell .grid-y.large-grid-frame {
+ height: 100%; } }
+
+.grid-margin-y {
+ margin-top: -0.625rem;
+ margin-bottom: -0.625rem; }
+ @media print, screen and (min-width: 40em) {
+ .grid-margin-y {
+ margin-top: -0.9375rem;
+ margin-bottom: -0.9375rem; } }
+ .grid-margin-y > .cell {
+ height: calc(100% - 1.25rem);
+ margin-top: 0.625rem;
+ margin-bottom: 0.625rem; }
+ @media print, screen and (min-width: 40em) {
+ .grid-margin-y > .cell {
+ height: calc(100% - 1.875rem);
+ margin-top: 0.9375rem;
+ margin-bottom: 0.9375rem; } }
+ .grid-margin-y > .auto {
+ height: auto; }
+ .grid-margin-y > .shrink {
+ height: auto; }
+ .grid-margin-y > .small-1 {
+ height: calc(8.33333% - 1.25rem); }
+ .grid-margin-y > .small-2 {
+ height: calc(16.66667% - 1.25rem); }
+ .grid-margin-y > .small-3 {
+ height: calc(25% - 1.25rem); }
+ .grid-margin-y > .small-4 {
+ height: calc(33.33333% - 1.25rem); }
+ .grid-margin-y > .small-5 {
+ height: calc(41.66667% - 1.25rem); }
+ .grid-margin-y > .small-6 {
+ height: calc(50% - 1.25rem); }
+ .grid-margin-y > .small-7 {
+ height: calc(58.33333% - 1.25rem); }
+ .grid-margin-y > .small-8 {
+ height: calc(66.66667% - 1.25rem); }
+ .grid-margin-y > .small-9 {
+ height: calc(75% - 1.25rem); }
+ .grid-margin-y > .small-10 {
+ height: calc(83.33333% - 1.25rem); }
+ .grid-margin-y > .small-11 {
+ height: calc(91.66667% - 1.25rem); }
+ .grid-margin-y > .small-12 {
+ height: calc(100% - 1.25rem); }
+ @media print, screen and (min-width: 40em) {
+ .grid-margin-y > .auto {
+ height: auto; }
+ .grid-margin-y > .shrink {
+ height: auto; }
+ .grid-margin-y > .small-1 {
+ height: calc(8.33333% - 1.875rem); }
+ .grid-margin-y > .small-2 {
+ height: calc(16.66667% - 1.875rem); }
+ .grid-margin-y > .small-3 {
+ height: calc(25% - 1.875rem); }
+ .grid-margin-y > .small-4 {
+ height: calc(33.33333% - 1.875rem); }
+ .grid-margin-y > .small-5 {
+ height: calc(41.66667% - 1.875rem); }
+ .grid-margin-y > .small-6 {
+ height: calc(50% - 1.875rem); }
+ .grid-margin-y > .small-7 {
+ height: calc(58.33333% - 1.875rem); }
+ .grid-margin-y > .small-8 {
+ height: calc(66.66667% - 1.875rem); }
+ .grid-margin-y > .small-9 {
+ height: calc(75% - 1.875rem); }
+ .grid-margin-y > .small-10 {
+ height: calc(83.33333% - 1.875rem); }
+ .grid-margin-y > .small-11 {
+ height: calc(91.66667% - 1.875rem); }
+ .grid-margin-y > .small-12 {
+ height: calc(100% - 1.875rem); }
+ .grid-margin-y > .medium-auto {
+ height: auto; }
+ .grid-margin-y > .medium-shrink {
+ height: auto; }
+ .grid-margin-y > .medium-1 {
+ height: calc(8.33333% - 1.875rem); }
+ .grid-margin-y > .medium-2 {
+ height: calc(16.66667% - 1.875rem); }
+ .grid-margin-y > .medium-3 {
+ height: calc(25% - 1.875rem); }
+ .grid-margin-y > .medium-4 {
+ height: calc(33.33333% - 1.875rem); }
+ .grid-margin-y > .medium-5 {
+ height: calc(41.66667% - 1.875rem); }
+ .grid-margin-y > .medium-6 {
+ height: calc(50% - 1.875rem); }
+ .grid-margin-y > .medium-7 {
+ height: calc(58.33333% - 1.875rem); }
+ .grid-margin-y > .medium-8 {
+ height: calc(66.66667% - 1.875rem); }
+ .grid-margin-y > .medium-9 {
+ height: calc(75% - 1.875rem); }
+ .grid-margin-y > .medium-10 {
+ height: calc(83.33333% - 1.875rem); }
+ .grid-margin-y > .medium-11 {
+ height: calc(91.66667% - 1.875rem); }
+ .grid-margin-y > .medium-12 {
+ height: calc(100% - 1.875rem); } }
+ @media print, screen and (min-width: 64em) {
+ .grid-margin-y > .large-auto {
+ height: auto; }
+ .grid-margin-y > .large-shrink {
+ height: auto; }
+ .grid-margin-y > .large-1 {
+ height: calc(8.33333% - 1.875rem); }
+ .grid-margin-y > .large-2 {
+ height: calc(16.66667% - 1.875rem); }
+ .grid-margin-y > .large-3 {
+ height: calc(25% - 1.875rem); }
+ .grid-margin-y > .large-4 {
+ height: calc(33.33333% - 1.875rem); }
+ .grid-margin-y > .large-5 {
+ height: calc(41.66667% - 1.875rem); }
+ .grid-margin-y > .large-6 {
+ height: calc(50% - 1.875rem); }
+ .grid-margin-y > .large-7 {
+ height: calc(58.33333% - 1.875rem); }
+ .grid-margin-y > .large-8 {
+ height: calc(66.66667% - 1.875rem); }
+ .grid-margin-y > .large-9 {
+ height: calc(75% - 1.875rem); }
+ .grid-margin-y > .large-10 {
+ height: calc(83.33333% - 1.875rem); }
+ .grid-margin-y > .large-11 {
+ height: calc(91.66667% - 1.875rem); }
+ .grid-margin-y > .large-12 {
+ height: calc(100% - 1.875rem); } }
+
+.grid-frame.grid-margin-y {
+ height: calc(100vh + 1.25rem); }
+ @media print, screen and (min-width: 40em) {
+ .grid-frame.grid-margin-y {
+ height: calc(100vh + 1.875rem); } }
+ @media print, screen and (min-width: 64em) {
+ .grid-frame.grid-margin-y {
+ height: calc(100vh + 1.875rem); } }
+
+@media print, screen and (min-width: 40em) {
+ .grid-margin-y.medium-grid-frame {
+ height: calc(100vh + 1.875rem); } }
+
+@media print, screen and (min-width: 64em) {
+ .grid-margin-y.large-grid-frame {
+ height: calc(100vh + 1.875rem); } }
+
+.button {
+ display: inline-block;
+ vertical-align: middle;
+ margin: 0 0 1rem 0;
+ padding: 0.85em 1em;
+ border: 1px solid transparent;
+ border-radius: 0;
+ -webkit-transition: background-color 0.25s ease-out, color 0.25s ease-out;
+ transition: background-color 0.25s ease-out, color 0.25s ease-out;
+ font-family: inherit;
+ font-size: 0.9rem;
+ -webkit-appearance: none;
+ line-height: 1;
+ text-align: center;
+ cursor: pointer; }
+ [data-whatinput='mouse'] .button {
+ outline: 0; }
+ .button.tiny {
+ font-size: 0.6rem; }
+ .button.small {
+ font-size: 0.75rem; }
+ .button.large {
+ font-size: 1.25rem; }
+ .button.expanded {
+ display: block;
+ width: 100%;
+ margin-right: 0;
+ margin-left: 0; }
+ .button, .button.disabled, .button[disabled], .button.disabled:hover, .button[disabled]:hover, .button.disabled:focus, .button[disabled]:focus {
+ background-color: #1779ba;
+ color: #fefefe; }
+ .button:hover, .button:focus {
+ background-color: #14679e;
+ color: #fefefe; }
+ .button.primary, .button.primary.disabled, .button.primary[disabled], .button.primary.disabled:hover, .button.primary[disabled]:hover, .button.primary.disabled:focus, .button.primary[disabled]:focus {
+ background-color: #1779ba;
+ color: #fefefe; }
+ .button.primary:hover, .button.primary:focus {
+ background-color: #126195;
+ color: #fefefe; }
+ .button.secondary, .button.secondary.disabled, .button.secondary[disabled], .button.secondary.disabled:hover, .button.secondary[disabled]:hover, .button.secondary.disabled:focus, .button.secondary[disabled]:focus {
+ background-color: #767676;
+ color: #fefefe; }
+ .button.secondary:hover, .button.secondary:focus {
+ background-color: #5e5e5e;
+ color: #fefefe; }
+ .button.success, .button.success.disabled, .button.success[disabled], .button.success.disabled:hover, .button.success[disabled]:hover, .button.success.disabled:focus, .button.success[disabled]:focus {
+ background-color: #3adb76;
+ color: #0a0a0a; }
+ .button.success:hover, .button.success:focus {
+ background-color: #22bb5b;
+ color: #0a0a0a; }
+ .button.warning, .button.warning.disabled, .button.warning[disabled], .button.warning.disabled:hover, .button.warning[disabled]:hover, .button.warning.disabled:focus, .button.warning[disabled]:focus {
+ background-color: #ffae00;
+ color: #0a0a0a; }
+ .button.warning:hover, .button.warning:focus {
+ background-color: #cc8b00;
+ color: #0a0a0a; }
+ .button.alert, .button.alert.disabled, .button.alert[disabled], .button.alert.disabled:hover, .button.alert[disabled]:hover, .button.alert.disabled:focus, .button.alert[disabled]:focus {
+ background-color: #cc4b37;
+ color: #fefefe; }
+ .button.alert:hover, .button.alert:focus {
+ background-color: #a53b2a;
+ color: #fefefe; }
+ .button.hollow, .button.hollow:hover, .button.hollow:focus, .button.hollow.disabled, .button.hollow.disabled:hover, .button.hollow.disabled:focus, .button.hollow[disabled], .button.hollow[disabled]:hover, .button.hollow[disabled]:focus {
+ background-color: transparent; }
+ .button.hollow, .button.hollow.disabled, .button.hollow[disabled], .button.hollow.disabled:hover, .button.hollow[disabled]:hover, .button.hollow.disabled:focus, .button.hollow[disabled]:focus {
+ border: 1px solid #1779ba;
+ color: #1779ba; }
+ .button.hollow:hover, .button.hollow:focus {
+ border-color: #0c3d5d;
+ color: #0c3d5d; }
+ .button.hollow.primary, .button.hollow.primary.disabled, .button.hollow.primary[disabled], .button.hollow.primary.disabled:hover, .button.hollow.primary[disabled]:hover, .button.hollow.primary.disabled:focus, .button.hollow.primary[disabled]:focus {
+ border: 1px solid #1779ba;
+ color: #1779ba; }
+ .button.hollow.primary:hover, .button.hollow.primary:focus {
+ border-color: #0c3d5d;
+ color: #0c3d5d; }
+ .button.hollow.secondary, .button.hollow.secondary.disabled, .button.hollow.secondary[disabled], .button.hollow.secondary.disabled:hover, .button.hollow.secondary[disabled]:hover, .button.hollow.secondary.disabled:focus, .button.hollow.secondary[disabled]:focus {
+ border: 1px solid #767676;
+ color: #767676; }
+ .button.hollow.secondary:hover, .button.hollow.secondary:focus {
+ border-color: #3b3b3b;
+ color: #3b3b3b; }
+ .button.hollow.success, .button.hollow.success.disabled, .button.hollow.success[disabled], .button.hollow.success.disabled:hover, .button.hollow.success[disabled]:hover, .button.hollow.success.disabled:focus, .button.hollow.success[disabled]:focus {
+ border: 1px solid #3adb76;
+ color: #3adb76; }
+ .button.hollow.success:hover, .button.hollow.success:focus {
+ border-color: #157539;
+ color: #157539; }
+ .button.hollow.warning, .button.hollow.warning.disabled, .button.hollow.warning[disabled], .button.hollow.warning.disabled:hover, .button.hollow.warning[disabled]:hover, .button.hollow.warning.disabled:focus, .button.hollow.warning[disabled]:focus {
+ border: 1px solid #ffae00;
+ color: #ffae00; }
+ .button.hollow.warning:hover, .button.hollow.warning:focus {
+ border-color: #805700;
+ color: #805700; }
+ .button.hollow.alert, .button.hollow.alert.disabled, .button.hollow.alert[disabled], .button.hollow.alert.disabled:hover, .button.hollow.alert[disabled]:hover, .button.hollow.alert.disabled:focus, .button.hollow.alert[disabled]:focus {
+ border: 1px solid #cc4b37;
+ color: #cc4b37; }
+ .button.hollow.alert:hover, .button.hollow.alert:focus {
+ border-color: #67251a;
+ color: #67251a; }
+ .button.clear, .button.clear:hover, .button.clear:focus, .button.clear.disabled, .button.clear.disabled:hover, .button.clear.disabled:focus, .button.clear[disabled], .button.clear[disabled]:hover, .button.clear[disabled]:focus {
+ border-color: transparent;
+ background-color: transparent; }
+ .button.clear, .button.clear.disabled, .button.clear[disabled], .button.clear.disabled:hover, .button.clear[disabled]:hover, .button.clear.disabled:focus, .button.clear[disabled]:focus {
+ color: #1779ba; }
+ .button.clear:hover, .button.clear:focus {
+ color: #0c3d5d; }
+ .button.clear.primary, .button.clear.primary.disabled, .button.clear.primary[disabled], .button.clear.primary.disabled:hover, .button.clear.primary[disabled]:hover, .button.clear.primary.disabled:focus, .button.clear.primary[disabled]:focus {
+ color: #1779ba; }
+ .button.clear.primary:hover, .button.clear.primary:focus {
+ color: #0c3d5d; }
+ .button.clear.secondary, .button.clear.secondary.disabled, .button.clear.secondary[disabled], .button.clear.secondary.disabled:hover, .button.clear.secondary[disabled]:hover, .button.clear.secondary.disabled:focus, .button.clear.secondary[disabled]:focus {
+ color: #767676; }
+ .button.clear.secondary:hover, .button.clear.secondary:focus {
+ color: #3b3b3b; }
+ .button.clear.success, .button.clear.success.disabled, .button.clear.success[disabled], .button.clear.success.disabled:hover, .button.clear.success[disabled]:hover, .button.clear.success.disabled:focus, .button.clear.success[disabled]:focus {
+ color: #3adb76; }
+ .button.clear.success:hover, .button.clear.success:focus {
+ color: #157539; }
+ .button.clear.warning, .button.clear.warning.disabled, .button.clear.warning[disabled], .button.clear.warning.disabled:hover, .button.clear.warning[disabled]:hover, .button.clear.warning.disabled:focus, .button.clear.warning[disabled]:focus {
+ color: #ffae00; }
+ .button.clear.warning:hover, .button.clear.warning:focus {
+ color: #805700; }
+ .button.clear.alert, .button.clear.alert.disabled, .button.clear.alert[disabled], .button.clear.alert.disabled:hover, .button.clear.alert[disabled]:hover, .button.clear.alert.disabled:focus, .button.clear.alert[disabled]:focus {
+ color: #cc4b37; }
+ .button.clear.alert:hover, .button.clear.alert:focus {
+ color: #67251a; }
+ .button.disabled, .button[disabled] {
+ opacity: 0.25;
+ cursor: not-allowed; }
+ .button.dropdown::after {
+ display: block;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 0.4em;
+ content: '';
+ border-bottom-width: 0;
+ border-color: #fefefe transparent transparent;
+ position: relative;
+ top: 0.4em;
+ display: inline-block;
+ float: right;
+ margin-left: 1em; }
+ .button.dropdown.hollow::after, .button.dropdown.clear::after {
+ border-top-color: #1779ba; }
+ .button.dropdown.hollow.primary::after, .button.dropdown.clear.primary::after {
+ border-top-color: #1779ba; }
+ .button.dropdown.hollow.secondary::after, .button.dropdown.clear.secondary::after {
+ border-top-color: #767676; }
+ .button.dropdown.hollow.success::after, .button.dropdown.clear.success::after {
+ border-top-color: #3adb76; }
+ .button.dropdown.hollow.warning::after, .button.dropdown.clear.warning::after {
+ border-top-color: #ffae00; }
+ .button.dropdown.hollow.alert::after, .button.dropdown.clear.alert::after {
+ border-top-color: #cc4b37; }
+ .button.arrow-only::after {
+ top: -0.1em;
+ float: none;
+ margin-left: 0; }
+
+a.button:hover, a.button:focus {
+ text-decoration: none; }
+
+.button-group {
+ margin-bottom: 1rem;
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-wrap: wrap;
+ -ms-flex-wrap: wrap;
+ flex-wrap: wrap;
+ -webkit-box-align: stretch;
+ -webkit-align-items: stretch;
+ -ms-flex-align: stretch;
+ align-items: stretch;
+ -webkit-box-flex: 1;
+ -webkit-flex-grow: 1;
+ -ms-flex-positive: 1;
+ flex-grow: 1; }
+ .button-group::before, .button-group::after {
+ display: table;
+ content: ' ';
+ -webkit-flex-basis: 0;
+ -ms-flex-preferred-size: 0;
+ flex-basis: 0;
+ -webkit-box-ordinal-group: 2;
+ -webkit-order: 1;
+ -ms-flex-order: 1;
+ order: 1; }
+ .button-group::after {
+ clear: both; }
+ .button-group::before, .button-group::after {
+ display: none; }
+ .button-group .button {
+ margin: 0;
+ margin-right: 1px;
+ margin-bottom: 1px;
+ font-size: 0.9rem;
+ -webkit-box-flex: 0;
+ -webkit-flex: 0 0 auto;
+ -ms-flex: 0 0 auto;
+ flex: 0 0 auto; }
+ .button-group .button:last-child {
+ margin-right: 0; }
+ .button-group.tiny .button {
+ font-size: 0.6rem; }
+ .button-group.small .button {
+ font-size: 0.75rem; }
+ .button-group.large .button {
+ font-size: 1.25rem; }
+ .button-group.expanded .button {
+ -webkit-box-flex: 1;
+ -webkit-flex: 1 1 0px;
+ -ms-flex: 1 1 0px;
+ flex: 1 1 0px; }
+ .button-group.primary .button, .button-group.primary .button.disabled, .button-group.primary .button[disabled], .button-group.primary .button.disabled:hover, .button-group.primary .button[disabled]:hover, .button-group.primary .button.disabled:focus, .button-group.primary .button[disabled]:focus {
+ background-color: #1779ba;
+ color: #fefefe; }
+ .button-group.primary .button:hover, .button-group.primary .button:focus {
+ background-color: #126195;
+ color: #fefefe; }
+ .button-group.secondary .button, .button-group.secondary .button.disabled, .button-group.secondary .button[disabled], .button-group.secondary .button.disabled:hover, .button-group.secondary .button[disabled]:hover, .button-group.secondary .button.disabled:focus, .button-group.secondary .button[disabled]:focus {
+ background-color: #767676;
+ color: #fefefe; }
+ .button-group.secondary .button:hover, .button-group.secondary .button:focus {
+ background-color: #5e5e5e;
+ color: #fefefe; }
+ .button-group.success .button, .button-group.success .button.disabled, .button-group.success .button[disabled], .button-group.success .button.disabled:hover, .button-group.success .button[disabled]:hover, .button-group.success .button.disabled:focus, .button-group.success .button[disabled]:focus {
+ background-color: #3adb76;
+ color: #0a0a0a; }
+ .button-group.success .button:hover, .button-group.success .button:focus {
+ background-color: #22bb5b;
+ color: #0a0a0a; }
+ .button-group.warning .button, .button-group.warning .button.disabled, .button-group.warning .button[disabled], .button-group.warning .button.disabled:hover, .button-group.warning .button[disabled]:hover, .button-group.warning .button.disabled:focus, .button-group.warning .button[disabled]:focus {
+ background-color: #ffae00;
+ color: #0a0a0a; }
+ .button-group.warning .button:hover, .button-group.warning .button:focus {
+ background-color: #cc8b00;
+ color: #0a0a0a; }
+ .button-group.alert .button, .button-group.alert .button.disabled, .button-group.alert .button[disabled], .button-group.alert .button.disabled:hover, .button-group.alert .button[disabled]:hover, .button-group.alert .button.disabled:focus, .button-group.alert .button[disabled]:focus {
+ background-color: #cc4b37;
+ color: #fefefe; }
+ .button-group.alert .button:hover, .button-group.alert .button:focus {
+ background-color: #a53b2a;
+ color: #fefefe; }
+ .button-group.hollow .button, .button-group.hollow .button:hover, .button-group.hollow .button:focus, .button-group.hollow .button.disabled, .button-group.hollow .button.disabled:hover, .button-group.hollow .button.disabled:focus, .button-group.hollow .button[disabled], .button-group.hollow .button[disabled]:hover, .button-group.hollow .button[disabled]:focus {
+ background-color: transparent; }
+ .button-group.hollow .button, .button-group.hollow .button.disabled, .button-group.hollow .button[disabled], .button-group.hollow .button.disabled:hover, .button-group.hollow .button[disabled]:hover, .button-group.hollow .button.disabled:focus, .button-group.hollow .button[disabled]:focus {
+ border: 1px solid #1779ba;
+ color: #1779ba; }
+ .button-group.hollow .button:hover, .button-group.hollow .button:focus {
+ border-color: #0c3d5d;
+ color: #0c3d5d; }
+ .button-group.hollow.primary .button, .button-group.hollow.primary .button.disabled, .button-group.hollow.primary .button[disabled], .button-group.hollow.primary .button.disabled:hover, .button-group.hollow.primary .button[disabled]:hover, .button-group.hollow.primary .button.disabled:focus, .button-group.hollow.primary .button[disabled]:focus, .button-group.hollow .button.primary, .button-group.hollow .button.primary.disabled, .button-group.hollow .button.primary[disabled], .button-group.hollow .button.primary.disabled:hover, .button-group.hollow .button.primary[disabled]:hover, .button-group.hollow .button.primary.disabled:focus, .button-group.hollow .button.primary[disabled]:focus {
+ border: 1px solid #1779ba;
+ color: #1779ba; }
+ .button-group.hollow.primary .button:hover, .button-group.hollow.primary .button:focus, .button-group.hollow .button.primary:hover, .button-group.hollow .button.primary:focus {
+ border-color: #0c3d5d;
+ color: #0c3d5d; }
+ .button-group.hollow.secondary .button, .button-group.hollow.secondary .button.disabled, .button-group.hollow.secondary .button[disabled], .button-group.hollow.secondary .button.disabled:hover, .button-group.hollow.secondary .button[disabled]:hover, .button-group.hollow.secondary .button.disabled:focus, .button-group.hollow.secondary .button[disabled]:focus, .button-group.hollow .button.secondary, .button-group.hollow .button.secondary.disabled, .button-group.hollow .button.secondary[disabled], .button-group.hollow .button.secondary.disabled:hover, .button-group.hollow .button.secondary[disabled]:hover, .button-group.hollow .button.secondary.disabled:focus, .button-group.hollow .button.secondary[disabled]:focus {
+ border: 1px solid #767676;
+ color: #767676; }
+ .button-group.hollow.secondary .button:hover, .button-group.hollow.secondary .button:focus, .button-group.hollow .button.secondary:hover, .button-group.hollow .button.secondary:focus {
+ border-color: #3b3b3b;
+ color: #3b3b3b; }
+ .button-group.hollow.success .button, .button-group.hollow.success .button.disabled, .button-group.hollow.success .button[disabled], .button-group.hollow.success .button.disabled:hover, .button-group.hollow.success .button[disabled]:hover, .button-group.hollow.success .button.disabled:focus, .button-group.hollow.success .button[disabled]:focus, .button-group.hollow .button.success, .button-group.hollow .button.success.disabled, .button-group.hollow .button.success[disabled], .button-group.hollow .button.success.disabled:hover, .button-group.hollow .button.success[disabled]:hover, .button-group.hollow .button.success.disabled:focus, .button-group.hollow .button.success[disabled]:focus {
+ border: 1px solid #3adb76;
+ color: #3adb76; }
+ .button-group.hollow.success .button:hover, .button-group.hollow.success .button:focus, .button-group.hollow .button.success:hover, .button-group.hollow .button.success:focus {
+ border-color: #157539;
+ color: #157539; }
+ .button-group.hollow.warning .button, .button-group.hollow.warning .button.disabled, .button-group.hollow.warning .button[disabled], .button-group.hollow.warning .button.disabled:hover, .button-group.hollow.warning .button[disabled]:hover, .button-group.hollow.warning .button.disabled:focus, .button-group.hollow.warning .button[disabled]:focus, .button-group.hollow .button.warning, .button-group.hollow .button.warning.disabled, .button-group.hollow .button.warning[disabled], .button-group.hollow .button.warning.disabled:hover, .button-group.hollow .button.warning[disabled]:hover, .button-group.hollow .button.warning.disabled:focus, .button-group.hollow .button.warning[disabled]:focus {
+ border: 1px solid #ffae00;
+ color: #ffae00; }
+ .button-group.hollow.warning .button:hover, .button-group.hollow.warning .button:focus, .button-group.hollow .button.warning:hover, .button-group.hollow .button.warning:focus {
+ border-color: #805700;
+ color: #805700; }
+ .button-group.hollow.alert .button, .button-group.hollow.alert .button.disabled, .button-group.hollow.alert .button[disabled], .button-group.hollow.alert .button.disabled:hover, .button-group.hollow.alert .button[disabled]:hover, .button-group.hollow.alert .button.disabled:focus, .button-group.hollow.alert .button[disabled]:focus, .button-group.hollow .button.alert, .button-group.hollow .button.alert.disabled, .button-group.hollow .button.alert[disabled], .button-group.hollow .button.alert.disabled:hover, .button-group.hollow .button.alert[disabled]:hover, .button-group.hollow .button.alert.disabled:focus, .button-group.hollow .button.alert[disabled]:focus {
+ border: 1px solid #cc4b37;
+ color: #cc4b37; }
+ .button-group.hollow.alert .button:hover, .button-group.hollow.alert .button:focus, .button-group.hollow .button.alert:hover, .button-group.hollow .button.alert:focus {
+ border-color: #67251a;
+ color: #67251a; }
+ .button-group.clear .button, .button-group.clear .button:hover, .button-group.clear .button:focus, .button-group.clear .button.disabled, .button-group.clear .button.disabled:hover, .button-group.clear .button.disabled:focus, .button-group.clear .button[disabled], .button-group.clear .button[disabled]:hover, .button-group.clear .button[disabled]:focus {
+ border-color: transparent;
+ background-color: transparent; }
+ .button-group.clear .button, .button-group.clear .button.disabled, .button-group.clear .button[disabled], .button-group.clear .button.disabled:hover, .button-group.clear .button[disabled]:hover, .button-group.clear .button.disabled:focus, .button-group.clear .button[disabled]:focus {
+ color: #1779ba; }
+ .button-group.clear .button:hover, .button-group.clear .button:focus {
+ color: #0c3d5d; }
+ .button-group.clear.primary .button, .button-group.clear.primary .button.disabled, .button-group.clear.primary .button[disabled], .button-group.clear.primary .button.disabled:hover, .button-group.clear.primary .button[disabled]:hover, .button-group.clear.primary .button.disabled:focus, .button-group.clear.primary .button[disabled]:focus, .button-group.clear .button.primary, .button-group.clear .button.primary.disabled, .button-group.clear .button.primary[disabled], .button-group.clear .button.primary.disabled:hover, .button-group.clear .button.primary[disabled]:hover, .button-group.clear .button.primary.disabled:focus, .button-group.clear .button.primary[disabled]:focus {
+ color: #1779ba; }
+ .button-group.clear.primary .button:hover, .button-group.clear.primary .button:focus, .button-group.clear .button.primary:hover, .button-group.clear .button.primary:focus {
+ color: #0c3d5d; }
+ .button-group.clear.secondary .button, .button-group.clear.secondary .button.disabled, .button-group.clear.secondary .button[disabled], .button-group.clear.secondary .button.disabled:hover, .button-group.clear.secondary .button[disabled]:hover, .button-group.clear.secondary .button.disabled:focus, .button-group.clear.secondary .button[disabled]:focus, .button-group.clear .button.secondary, .button-group.clear .button.secondary.disabled, .button-group.clear .button.secondary[disabled], .button-group.clear .button.secondary.disabled:hover, .button-group.clear .button.secondary[disabled]:hover, .button-group.clear .button.secondary.disabled:focus, .button-group.clear .button.secondary[disabled]:focus {
+ color: #767676; }
+ .button-group.clear.secondary .button:hover, .button-group.clear.secondary .button:focus, .button-group.clear .button.secondary:hover, .button-group.clear .button.secondary:focus {
+ color: #3b3b3b; }
+ .button-group.clear.success .button, .button-group.clear.success .button.disabled, .button-group.clear.success .button[disabled], .button-group.clear.success .button.disabled:hover, .button-group.clear.success .button[disabled]:hover, .button-group.clear.success .button.disabled:focus, .button-group.clear.success .button[disabled]:focus, .button-group.clear .button.success, .button-group.clear .button.success.disabled, .button-group.clear .button.success[disabled], .button-group.clear .button.success.disabled:hover, .button-group.clear .button.success[disabled]:hover, .button-group.clear .button.success.disabled:focus, .button-group.clear .button.success[disabled]:focus {
+ color: #3adb76; }
+ .button-group.clear.success .button:hover, .button-group.clear.success .button:focus, .button-group.clear .button.success:hover, .button-group.clear .button.success:focus {
+ color: #157539; }
+ .button-group.clear.warning .button, .button-group.clear.warning .button.disabled, .button-group.clear.warning .button[disabled], .button-group.clear.warning .button.disabled:hover, .button-group.clear.warning .button[disabled]:hover, .button-group.clear.warning .button.disabled:focus, .button-group.clear.warning .button[disabled]:focus, .button-group.clear .button.warning, .button-group.clear .button.warning.disabled, .button-group.clear .button.warning[disabled], .button-group.clear .button.warning.disabled:hover, .button-group.clear .button.warning[disabled]:hover, .button-group.clear .button.warning.disabled:focus, .button-group.clear .button.warning[disabled]:focus {
+ color: #ffae00; }
+ .button-group.clear.warning .button:hover, .button-group.clear.warning .button:focus, .button-group.clear .button.warning:hover, .button-group.clear .button.warning:focus {
+ color: #805700; }
+ .button-group.clear.alert .button, .button-group.clear.alert .button.disabled, .button-group.clear.alert .button[disabled], .button-group.clear.alert .button.disabled:hover, .button-group.clear.alert .button[disabled]:hover, .button-group.clear.alert .button.disabled:focus, .button-group.clear.alert .button[disabled]:focus, .button-group.clear .button.alert, .button-group.clear .button.alert.disabled, .button-group.clear .button.alert[disabled], .button-group.clear .button.alert.disabled:hover, .button-group.clear .button.alert[disabled]:hover, .button-group.clear .button.alert.disabled:focus, .button-group.clear .button.alert[disabled]:focus {
+ color: #cc4b37; }
+ .button-group.clear.alert .button:hover, .button-group.clear.alert .button:focus, .button-group.clear .button.alert:hover, .button-group.clear .button.alert:focus {
+ color: #67251a; }
+ .button-group.no-gaps .button {
+ margin-right: -0.0625rem; }
+ .button-group.no-gaps .button + .button {
+ border-left-color: transparent; }
+ .button-group.stacked, .button-group.stacked-for-small, .button-group.stacked-for-medium {
+ -webkit-flex-wrap: wrap;
+ -ms-flex-wrap: wrap;
+ flex-wrap: wrap; }
+ .button-group.stacked .button, .button-group.stacked-for-small .button, .button-group.stacked-for-medium .button {
+ -webkit-box-flex: 0;
+ -webkit-flex: 0 0 100%;
+ -ms-flex: 0 0 100%;
+ flex: 0 0 100%; }
+ .button-group.stacked .button:last-child, .button-group.stacked-for-small .button:last-child, .button-group.stacked-for-medium .button:last-child {
+ margin-bottom: 0; }
+ .button-group.stacked.expanded .button, .button-group.stacked-for-small.expanded .button, .button-group.stacked-for-medium.expanded .button {
+ -webkit-box-flex: 1;
+ -webkit-flex: 1 1 0px;
+ -ms-flex: 1 1 0px;
+ flex: 1 1 0px; }
+ @media print, screen and (min-width: 40em) {
+ .button-group.stacked-for-small .button {
+ -webkit-box-flex: 0;
+ -webkit-flex: 0 0 auto;
+ -ms-flex: 0 0 auto;
+ flex: 0 0 auto;
+ margin-bottom: 0; } }
+ @media print, screen and (min-width: 64em) {
+ .button-group.stacked-for-medium .button {
+ -webkit-box-flex: 0;
+ -webkit-flex: 0 0 auto;
+ -ms-flex: 0 0 auto;
+ flex: 0 0 auto;
+ margin-bottom: 0; } }
+ @media print, screen and (max-width: 39.99875em) {
+ .button-group.stacked-for-small.expanded {
+ display: block; }
+ .button-group.stacked-for-small.expanded .button {
+ display: block;
+ margin-right: 0; } }
+ @media print, screen and (max-width: 63.99875em) {
+ .button-group.stacked-for-medium.expanded {
+ display: block; }
+ .button-group.stacked-for-medium.expanded .button {
+ display: block;
+ margin-right: 0; } }
+
+.close-button {
+ position: absolute;
+ z-index: 10;
+ color: #8a8a8a;
+ cursor: pointer; }
+ [data-whatinput='mouse'] .close-button {
+ outline: 0; }
+ .close-button:hover, .close-button:focus {
+ color: #0a0a0a; }
+ .close-button.small {
+ right: 0.66rem;
+ top: 0.33em;
+ font-size: 1.5em;
+ line-height: 1; }
+ .close-button.medium, .close-button {
+ right: 1rem;
+ top: 0.5rem;
+ font-size: 2em;
+ line-height: 1; }
+
+.label {
+ display: inline-block;
+ padding: 0.33333rem 0.5rem;
+ border-radius: 0;
+ font-size: 0.8rem;
+ line-height: 1;
+ white-space: nowrap;
+ cursor: default;
+ background: #1779ba;
+ color: #fefefe; }
+ .label.primary {
+ background: #1779ba;
+ color: #fefefe; }
+ .label.secondary {
+ background: #767676;
+ color: #fefefe; }
+ .label.success {
+ background: #3adb76;
+ color: #0a0a0a; }
+ .label.warning {
+ background: #ffae00;
+ color: #0a0a0a; }
+ .label.alert {
+ background: #cc4b37;
+ color: #fefefe; }
+
+.progress {
+ height: 1rem;
+ margin-bottom: 1rem;
+ border-radius: 0;
+ background-color: #cacaca; }
+ .progress.primary .progress-meter {
+ background-color: #1779ba; }
+ .progress.secondary .progress-meter {
+ background-color: #767676; }
+ .progress.success .progress-meter {
+ background-color: #3adb76; }
+ .progress.warning .progress-meter {
+ background-color: #ffae00; }
+ .progress.alert .progress-meter {
+ background-color: #cc4b37; }
+
+.progress-meter {
+ position: relative;
+ display: block;
+ width: 0%;
+ height: 100%;
+ background-color: #1779ba; }
+
+.progress-meter-text {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ -webkit-transform: translate(-50%, -50%);
+ -ms-transform: translate(-50%, -50%);
+ transform: translate(-50%, -50%);
+ margin: 0;
+ font-size: 0.75rem;
+ font-weight: bold;
+ color: #fefefe;
+ white-space: nowrap; }
+
+.slider {
+ position: relative;
+ height: 0.5rem;
+ margin-top: 1.25rem;
+ margin-bottom: 2.25rem;
+ background-color: #e6e6e6;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ -ms-touch-action: none;
+ touch-action: none; }
+
+.slider-fill {
+ position: absolute;
+ top: 0;
+ left: 0;
+ display: inline-block;
+ max-width: 100%;
+ height: 0.5rem;
+ background-color: #cacaca;
+ -webkit-transition: all 0.2s ease-in-out;
+ transition: all 0.2s ease-in-out; }
+ .slider-fill.is-dragging {
+ -webkit-transition: all 0s linear;
+ transition: all 0s linear; }
+
+.slider-handle {
+ position: absolute;
+ top: 50%;
+ -webkit-transform: translateY(-50%);
+ -ms-transform: translateY(-50%);
+ transform: translateY(-50%);
+ left: 0;
+ z-index: 1;
+ display: inline-block;
+ width: 1.4rem;
+ height: 1.4rem;
+ border-radius: 0;
+ background-color: #1779ba;
+ -webkit-transition: all 0.2s ease-in-out;
+ transition: all 0.2s ease-in-out;
+ -ms-touch-action: manipulation;
+ touch-action: manipulation; }
+ [data-whatinput='mouse'] .slider-handle {
+ outline: 0; }
+ .slider-handle:hover {
+ background-color: #14679e; }
+ .slider-handle.is-dragging {
+ -webkit-transition: all 0s linear;
+ transition: all 0s linear; }
+
+.slider.disabled,
+.slider[disabled] {
+ opacity: 0.25;
+ cursor: not-allowed; }
+
+.slider.vertical {
+ display: inline-block;
+ width: 0.5rem;
+ height: 12.5rem;
+ margin: 0 1.25rem;
+ -webkit-transform: scale(1, -1);
+ -ms-transform: scale(1, -1);
+ transform: scale(1, -1); }
+ .slider.vertical .slider-fill {
+ top: 0;
+ width: 0.5rem;
+ max-height: 100%; }
+ .slider.vertical .slider-handle {
+ position: absolute;
+ top: 0;
+ left: 50%;
+ width: 1.4rem;
+ height: 1.4rem;
+ -webkit-transform: translateX(-50%);
+ -ms-transform: translateX(-50%);
+ transform: translateX(-50%); }
+
+.switch {
+ height: 2rem;
+ position: relative;
+ margin-bottom: 1rem;
+ outline: 0;
+ font-size: 0.875rem;
+ font-weight: bold;
+ color: #fefefe;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none; }
+
+.switch-input {
+ position: absolute;
+ margin-bottom: 0;
+ opacity: 0; }
+
+.switch-paddle {
+ position: relative;
+ display: block;
+ width: 4rem;
+ height: 2rem;
+ border-radius: 0;
+ background: #cacaca;
+ -webkit-transition: all 0.25s ease-out;
+ transition: all 0.25s ease-out;
+ font-weight: inherit;
+ color: inherit;
+ cursor: pointer; }
+ input + .switch-paddle {
+ margin: 0; }
+ .switch-paddle::after {
+ position: absolute;
+ top: 0.25rem;
+ left: 0.25rem;
+ display: block;
+ width: 1.5rem;
+ height: 1.5rem;
+ -webkit-transform: translate3d(0, 0, 0);
+ transform: translate3d(0, 0, 0);
+ border-radius: 0;
+ background: #fefefe;
+ -webkit-transition: all 0.25s ease-out;
+ transition: all 0.25s ease-out;
+ content: ''; }
+ input:checked ~ .switch-paddle {
+ background: #1779ba; }
+ input:checked ~ .switch-paddle::after {
+ left: 2.25rem; }
+ input:disabled ~ .switch-paddle {
+ cursor: not-allowed;
+ opacity: 0.5; }
+ [data-whatinput='mouse'] input:focus ~ .switch-paddle {
+ outline: 0; }
+
+.switch-active, .switch-inactive {
+ position: absolute;
+ top: 50%;
+ -webkit-transform: translateY(-50%);
+ -ms-transform: translateY(-50%);
+ transform: translateY(-50%); }
+
+.switch-active {
+ left: 8%;
+ display: none; }
+ input:checked + label > .switch-active {
+ display: block; }
+
+.switch-inactive {
+ right: 15%; }
+ input:checked + label > .switch-inactive {
+ display: none; }
+
+.switch.tiny {
+ height: 1.5rem; }
+ .switch.tiny .switch-paddle {
+ width: 3rem;
+ height: 1.5rem;
+ font-size: 0.625rem; }
+ .switch.tiny .switch-paddle::after {
+ top: 0.25rem;
+ left: 0.25rem;
+ width: 1rem;
+ height: 1rem; }
+ .switch.tiny input:checked ~ .switch-paddle::after {
+ left: 1.75rem; }
+
+.switch.small {
+ height: 1.75rem; }
+ .switch.small .switch-paddle {
+ width: 3.5rem;
+ height: 1.75rem;
+ font-size: 0.75rem; }
+ .switch.small .switch-paddle::after {
+ top: 0.25rem;
+ left: 0.25rem;
+ width: 1.25rem;
+ height: 1.25rem; }
+ .switch.small input:checked ~ .switch-paddle::after {
+ left: 2rem; }
+
+.switch.large {
+ height: 2.5rem; }
+ .switch.large .switch-paddle {
+ width: 5rem;
+ height: 2.5rem;
+ font-size: 1rem; }
+ .switch.large .switch-paddle::after {
+ top: 0.25rem;
+ left: 0.25rem;
+ width: 2rem;
+ height: 2rem; }
+ .switch.large input:checked ~ .switch-paddle::after {
+ left: 2.75rem; }
+
+table {
+ border-collapse: collapse;
+ width: 100%;
+ margin-bottom: 1rem;
+ border-radius: 0; }
+ thead,
+ tbody,
+ tfoot {
+ border: 1px solid #f1f1f1;
+ background-color: #fefefe; }
+ caption {
+ padding: 0.5rem 0.625rem 0.625rem;
+ font-weight: bold; }
+ thead {
+ background: #f8f8f8;
+ color: #0a0a0a; }
+ tfoot {
+ background: #f1f1f1;
+ color: #0a0a0a; }
+ thead tr,
+ tfoot tr {
+ background: transparent; }
+ thead th,
+ thead td,
+ tfoot th,
+ tfoot td {
+ padding: 0.5rem 0.625rem 0.625rem;
+ font-weight: bold;
+ text-align: left; }
+ tbody th,
+ tbody td {
+ padding: 0.5rem 0.625rem 0.625rem; }
+ tbody tr:nth-child(even) {
+ border-bottom: 0;
+ background-color: #f1f1f1; }
+ table.unstriped tbody {
+ background-color: #fefefe; }
+ table.unstriped tbody tr {
+ border-bottom: 0;
+ border-bottom: 1px solid #f1f1f1;
+ background-color: #fefefe; }
+
+@media print, screen and (max-width: 63.99875em) {
+ table.stack thead {
+ display: none; }
+ table.stack tfoot {
+ display: none; }
+ table.stack tr,
+ table.stack th,
+ table.stack td {
+ display: block; }
+ table.stack td {
+ border-top: 0; } }
+
+table.scroll {
+ display: block;
+ width: 100%;
+ overflow-x: auto; }
+
+table.hover thead tr:hover {
+ background-color: #f3f3f3; }
+
+table.hover tfoot tr:hover {
+ background-color: #ececec; }
+
+table.hover tbody tr:hover {
+ background-color: #f9f9f9; }
+
+table.hover:not(.unstriped) tr:nth-of-type(even):hover {
+ background-color: #ececec; }
+
+.table-scroll {
+ overflow-x: auto; }
+
+.badge {
+ display: inline-block;
+ min-width: 2.1em;
+ padding: 0.3em;
+ border-radius: 50%;
+ font-size: 0.6rem;
+ text-align: center;
+ background: #1779ba;
+ color: #fefefe; }
+ .badge.primary {
+ background: #1779ba;
+ color: #fefefe; }
+ .badge.secondary {
+ background: #767676;
+ color: #fefefe; }
+ .badge.success {
+ background: #3adb76;
+ color: #0a0a0a; }
+ .badge.warning {
+ background: #ffae00;
+ color: #0a0a0a; }
+ .badge.alert {
+ background: #cc4b37;
+ color: #fefefe; }
+
+.breadcrumbs {
+ margin: 0 0 1rem 0;
+ list-style: none; }
+ .breadcrumbs::before, .breadcrumbs::after {
+ display: table;
+ content: ' ';
+ -webkit-flex-basis: 0;
+ -ms-flex-preferred-size: 0;
+ flex-basis: 0;
+ -webkit-box-ordinal-group: 2;
+ -webkit-order: 1;
+ -ms-flex-order: 1;
+ order: 1; }
+ .breadcrumbs::after {
+ clear: both; }
+ .breadcrumbs li {
+ float: left;
+ font-size: 0.6875rem;
+ color: #0a0a0a;
+ cursor: default;
+ text-transform: uppercase; }
+ .breadcrumbs li:not(:last-child)::after {
+ position: relative;
+ margin: 0 0.75rem;
+ opacity: 1;
+ content: "/";
+ color: #cacaca; }
+ .breadcrumbs a {
+ color: #1779ba; }
+ .breadcrumbs a:hover {
+ text-decoration: underline; }
+ .breadcrumbs .disabled {
+ color: #cacaca;
+ cursor: not-allowed; }
+
+.callout {
+ position: relative;
+ margin: 0 0 1rem 0;
+ padding: 1rem;
+ border: 1px solid rgba(10, 10, 10, 0.25);
+ border-radius: 0;
+ background-color: white;
+ color: #0a0a0a; }
+ .callout > :first-child {
+ margin-top: 0; }
+ .callout > :last-child {
+ margin-bottom: 0; }
+ .callout.primary {
+ background-color: #d7ecfa;
+ color: #0a0a0a; }
+ .callout.secondary {
+ background-color: #eaeaea;
+ color: #0a0a0a; }
+ .callout.success {
+ background-color: #e1faea;
+ color: #0a0a0a; }
+ .callout.warning {
+ background-color: #fff3d9;
+ color: #0a0a0a; }
+ .callout.alert {
+ background-color: #f7e4e1;
+ color: #0a0a0a; }
+ .callout.small {
+ padding-top: 0.5rem;
+ padding-right: 0.5rem;
+ padding-bottom: 0.5rem;
+ padding-left: 0.5rem; }
+ .callout.large {
+ padding-top: 3rem;
+ padding-right: 3rem;
+ padding-bottom: 3rem;
+ padding-left: 3rem; }
+
+.card {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: normal;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ -webkit-box-flex: 1;
+ -webkit-flex-grow: 1;
+ -ms-flex-positive: 1;
+ flex-grow: 1;
+ margin-bottom: 1rem;
+ border: 1px solid #e6e6e6;
+ border-radius: 0;
+ background: #fefefe;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ overflow: hidden;
+ color: #0a0a0a; }
+ .card > :last-child {
+ margin-bottom: 0; }
+
+.card-divider {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-flex: 0;
+ -webkit-flex: 0 1 auto;
+ -ms-flex: 0 1 auto;
+ flex: 0 1 auto;
+ padding: 1rem;
+ background: #e6e6e6; }
+ .card-divider > :last-child {
+ margin-bottom: 0; }
+
+.card-section {
+ -webkit-box-flex: 1;
+ -webkit-flex: 1 0 auto;
+ -ms-flex: 1 0 auto;
+ flex: 1 0 auto;
+ padding: 1rem; }
+ .card-section > :last-child {
+ margin-bottom: 0; }
+
+.card-image {
+ min-height: 1px; }
+
+.dropdown-pane {
+ position: absolute;
+ z-index: 10;
+ display: none;
+ width: 300px;
+ padding: 1rem;
+ visibility: hidden;
+ border: 1px solid #cacaca;
+ border-radius: 0;
+ background-color: #fefefe;
+ font-size: 1rem; }
+ .dropdown-pane.is-opening {
+ display: block; }
+ .dropdown-pane.is-open {
+ display: block;
+ visibility: visible; }
+
+.dropdown-pane.tiny {
+ width: 100px; }
+
+.dropdown-pane.small {
+ width: 200px; }
+
+.dropdown-pane.large {
+ width: 400px; }
+
+.pagination {
+ margin-left: 0;
+ margin-bottom: 1rem; }
+ .pagination::before, .pagination::after {
+ display: table;
+ content: ' ';
+ -webkit-flex-basis: 0;
+ -ms-flex-preferred-size: 0;
+ flex-basis: 0;
+ -webkit-box-ordinal-group: 2;
+ -webkit-order: 1;
+ -ms-flex-order: 1;
+ order: 1; }
+ .pagination::after {
+ clear: both; }
+ .pagination li {
+ margin-right: 0.0625rem;
+ border-radius: 0;
+ font-size: 0.875rem;
+ display: none; }
+ .pagination li:last-child, .pagination li:first-child {
+ display: inline-block; }
+ @media print, screen and (min-width: 40em) {
+ .pagination li {
+ display: inline-block; } }
+ .pagination a,
+ .pagination button {
+ display: block;
+ padding: 0.1875rem 0.625rem;
+ border-radius: 0;
+ color: #0a0a0a; }
+ .pagination a:hover,
+ .pagination button:hover {
+ background: #e6e6e6; }
+ .pagination .current {
+ padding: 0.1875rem 0.625rem;
+ background: #1779ba;
+ color: #fefefe;
+ cursor: default; }
+ .pagination .disabled {
+ padding: 0.1875rem 0.625rem;
+ color: #cacaca;
+ cursor: not-allowed; }
+ .pagination .disabled:hover {
+ background: transparent; }
+ .pagination .ellipsis::after {
+ padding: 0.1875rem 0.625rem;
+ content: '\2026';
+ color: #0a0a0a; }
+
+.pagination-previous a::before,
+.pagination-previous.disabled::before {
+ display: inline-block;
+ margin-right: 0.5rem;
+ content: "«"; }
+
+.pagination-next a::after,
+.pagination-next.disabled::after {
+ display: inline-block;
+ margin-left: 0.5rem;
+ content: "»"; }
+
+.has-tip {
+ position: relative;
+ display: inline-block;
+ border-bottom: dotted 1px #8a8a8a;
+ font-weight: bold;
+ cursor: help; }
+
+.tooltip {
+ position: absolute;
+ top: calc(100% + 0.6495rem);
+ z-index: 1200;
+ max-width: 10rem;
+ padding: 0.75rem;
+ border-radius: 0;
+ background-color: #0a0a0a;
+ font-size: 80%;
+ color: #fefefe; }
+ .tooltip::before {
+ position: absolute; }
+ .tooltip.bottom::before {
+ display: block;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 0.75rem;
+ content: '';
+ border-top-width: 0;
+ border-color: transparent transparent #0a0a0a;
+ bottom: 100%; }
+ .tooltip.bottom.align-center::before {
+ left: 50%;
+ -webkit-transform: translateX(-50%);
+ -ms-transform: translateX(-50%);
+ transform: translateX(-50%); }
+ .tooltip.top::before {
+ display: block;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 0.75rem;
+ content: '';
+ border-bottom-width: 0;
+ border-color: #0a0a0a transparent transparent;
+ top: 100%;
+ bottom: auto; }
+ .tooltip.top.align-center::before {
+ left: 50%;
+ -webkit-transform: translateX(-50%);
+ -ms-transform: translateX(-50%);
+ transform: translateX(-50%); }
+ .tooltip.left::before {
+ display: block;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 0.75rem;
+ content: '';
+ border-right-width: 0;
+ border-color: transparent transparent transparent #0a0a0a;
+ left: 100%; }
+ .tooltip.left.align-center::before {
+ bottom: auto;
+ top: 50%;
+ -webkit-transform: translateY(-50%);
+ -ms-transform: translateY(-50%);
+ transform: translateY(-50%); }
+ .tooltip.right::before {
+ display: block;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 0.75rem;
+ content: '';
+ border-left-width: 0;
+ border-color: transparent #0a0a0a transparent transparent;
+ right: 100%;
+ left: auto; }
+ .tooltip.right.align-center::before {
+ bottom: auto;
+ top: 50%;
+ -webkit-transform: translateY(-50%);
+ -ms-transform: translateY(-50%);
+ transform: translateY(-50%); }
+ .tooltip.align-top::before {
+ bottom: auto;
+ top: 10%; }
+ .tooltip.align-bottom::before {
+ bottom: 10%;
+ top: auto; }
+ .tooltip.align-left::before {
+ left: 10%;
+ right: auto; }
+ .tooltip.align-right::before {
+ left: auto;
+ right: 10%; }
+
+.accordion {
+ margin-left: 0;
+ background: #fefefe;
+ list-style-type: none; }
+ .accordion[disabled] .accordion-title {
+ cursor: not-allowed; }
+
+.accordion-item:first-child > :first-child {
+ border-radius: 0 0 0 0; }
+
+.accordion-item:last-child > :last-child {
+ border-radius: 0 0 0 0; }
+
+.accordion-title {
+ position: relative;
+ display: block;
+ padding: 1.25rem 1rem;
+ border: 1px solid #e6e6e6;
+ border-bottom: 0;
+ font-size: 0.75rem;
+ line-height: 1;
+ color: #1779ba; }
+ :last-child:not(.is-active) > .accordion-title {
+ border-bottom: 1px solid #e6e6e6;
+ border-radius: 0 0 0 0; }
+ .accordion-title:hover, .accordion-title:focus {
+ background-color: #e6e6e6; }
+ .accordion-title::before {
+ position: absolute;
+ top: 50%;
+ right: 1rem;
+ margin-top: -0.5rem;
+ content: "+"; }
+ .is-active > .accordion-title::before {
+ content: "–"; }
+
+.accordion-content {
+ display: none;
+ padding: 1rem;
+ border: 1px solid #e6e6e6;
+ border-bottom: 0;
+ background-color: #fefefe;
+ color: #0a0a0a; }
+ :last-child > .accordion-content:last-child {
+ border-bottom: 1px solid #e6e6e6; }
+
+.media-object {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ margin-bottom: 1rem;
+ -webkit-flex-wrap: nowrap;
+ -ms-flex-wrap: nowrap;
+ flex-wrap: nowrap; }
+ .media-object img {
+ max-width: none; }
+ @media print, screen and (max-width: 39.99875em) {
+ .media-object.stack-for-small {
+ -webkit-flex-wrap: wrap;
+ -ms-flex-wrap: wrap;
+ flex-wrap: wrap; } }
+
+.media-object-section {
+ -webkit-box-flex: 0;
+ -webkit-flex: 0 1 auto;
+ -ms-flex: 0 1 auto;
+ flex: 0 1 auto; }
+ .media-object-section:first-child {
+ padding-right: 1rem; }
+ .media-object-section:last-child:not(:nth-child(2)) {
+ padding-left: 1rem; }
+ .media-object-section > :last-child {
+ margin-bottom: 0; }
+ @media print, screen and (max-width: 39.99875em) {
+ .stack-for-small .media-object-section {
+ padding: 0;
+ padding-bottom: 1rem;
+ -webkit-flex-basis: 100%;
+ -ms-flex-preferred-size: 100%;
+ flex-basis: 100%;
+ max-width: 100%; }
+ .stack-for-small .media-object-section img {
+ width: 100%; } }
+ .media-object-section.main-section {
+ -webkit-box-flex: 1;
+ -webkit-flex: 1 1 0px;
+ -ms-flex: 1 1 0px;
+ flex: 1 1 0px; }
+
+.orbit {
+ position: relative; }
+
+.orbit-container {
+ position: relative;
+ height: 0;
+ margin: 0;
+ list-style: none;
+ overflow: hidden; }
+
+.orbit-slide {
+ width: 100%;
+ position: absolute; }
+ .orbit-slide.no-motionui.is-active {
+ top: 0;
+ left: 0; }
+
+.orbit-figure {
+ margin: 0; }
+
+.orbit-image {
+ width: 100%;
+ max-width: 100%;
+ margin: 0; }
+
+.orbit-caption {
+ position: absolute;
+ bottom: 0;
+ width: 100%;
+ margin-bottom: 0;
+ padding: 1rem;
+ background-color: rgba(10, 10, 10, 0.5);
+ color: #fefefe; }
+
+.orbit-previous, .orbit-next {
+ position: absolute;
+ top: 50%;
+ -webkit-transform: translateY(-50%);
+ -ms-transform: translateY(-50%);
+ transform: translateY(-50%);
+ z-index: 10;
+ padding: 1rem;
+ color: #fefefe; }
+ [data-whatinput='mouse'] .orbit-previous, [data-whatinput='mouse'] .orbit-next {
+ outline: 0; }
+ .orbit-previous:hover, .orbit-next:hover, .orbit-previous:active, .orbit-next:active, .orbit-previous:focus, .orbit-next:focus {
+ background-color: rgba(10, 10, 10, 0.5); }
+
+.orbit-previous {
+ left: 0; }
+
+.orbit-next {
+ left: auto;
+ right: 0; }
+
+.orbit-bullets {
+ position: relative;
+ margin-top: 0.8rem;
+ margin-bottom: 0.8rem;
+ text-align: center; }
+ [data-whatinput='mouse'] .orbit-bullets {
+ outline: 0; }
+ .orbit-bullets button {
+ width: 1.2rem;
+ height: 1.2rem;
+ margin: 0.1rem;
+ border-radius: 50%;
+ background-color: #cacaca; }
+ .orbit-bullets button:hover {
+ background-color: #8a8a8a; }
+ .orbit-bullets button.is-active {
+ background-color: #8a8a8a; }
+
+.responsive-embed,
+.flex-video {
+ position: relative;
+ height: 0;
+ margin-bottom: 1rem;
+ padding-bottom: 75%;
+ overflow: hidden; }
+ .responsive-embed iframe,
+ .responsive-embed object,
+ .responsive-embed embed,
+ .responsive-embed video,
+ .flex-video iframe,
+ .flex-video object,
+ .flex-video embed,
+ .flex-video video {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%; }
+ .responsive-embed.widescreen,
+ .flex-video.widescreen {
+ padding-bottom: 56.25%; }
+
+.tabs {
+ margin: 0;
+ border: 1px solid #e6e6e6;
+ background: #fefefe;
+ list-style-type: none; }
+ .tabs::before, .tabs::after {
+ display: table;
+ content: ' ';
+ -webkit-flex-basis: 0;
+ -ms-flex-preferred-size: 0;
+ flex-basis: 0;
+ -webkit-box-ordinal-group: 2;
+ -webkit-order: 1;
+ -ms-flex-order: 1;
+ order: 1; }
+ .tabs::after {
+ clear: both; }
+
+.tabs.vertical > li {
+ display: block;
+ float: none;
+ width: auto; }
+
+.tabs.simple > li > a {
+ padding: 0; }
+ .tabs.simple > li > a:hover {
+ background: transparent; }
+
+.tabs.primary {
+ background: #1779ba; }
+ .tabs.primary > li > a {
+ color: #fefefe; }
+ .tabs.primary > li > a:hover, .tabs.primary > li > a:focus {
+ background: #1673b1; }
+
+.tabs-title {
+ float: left; }
+ .tabs-title > a {
+ display: block;
+ padding: 1.25rem 1.5rem;
+ font-size: 0.75rem;
+ line-height: 1;
+ color: #1779ba; }
+ [data-whatinput='mouse'] .tabs-title > a {
+ outline: 0; }
+ .tabs-title > a:hover {
+ background: #fefefe;
+ color: #1468a0; }
+ .tabs-title > a:focus, .tabs-title > a[aria-selected='true'] {
+ background: #e6e6e6;
+ color: #1779ba; }
+
+.tabs-content {
+ border: 1px solid #e6e6e6;
+ border-top: 0;
+ background: #fefefe;
+ color: #0a0a0a;
+ -webkit-transition: all 0.5s ease;
+ transition: all 0.5s ease; }
+
+.tabs-content.vertical {
+ border: 1px solid #e6e6e6;
+ border-left: 0; }
+
+.tabs-panel {
+ display: none;
+ padding: 1rem; }
+ .tabs-panel.is-active {
+ display: block; }
+
+.thumbnail {
+ display: inline-block;
+ max-width: 100%;
+ margin-bottom: 1rem;
+ border: 4px solid #fefefe;
+ border-radius: 0;
+ -webkit-box-shadow: 0 0 0 1px rgba(10, 10, 10, 0.2);
+ box-shadow: 0 0 0 1px rgba(10, 10, 10, 0.2);
+ line-height: 0; }
+
+a.thumbnail {
+ -webkit-transition: -webkit-box-shadow 200ms ease-out;
+ transition: -webkit-box-shadow 200ms ease-out;
+ transition: box-shadow 200ms ease-out;
+ transition: box-shadow 200ms ease-out, -webkit-box-shadow 200ms ease-out; }
+ a.thumbnail:hover, a.thumbnail:focus {
+ -webkit-box-shadow: 0 0 6px 1px rgba(23, 121, 186, 0.5);
+ box-shadow: 0 0 6px 1px rgba(23, 121, 186, 0.5); }
+ a.thumbnail image {
+ -webkit-box-shadow: none;
+ box-shadow: none; }
+
+.menu {
+ padding: 0;
+ margin: 0;
+ list-style: none;
+ position: relative;
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-wrap: wrap;
+ -ms-flex-wrap: wrap;
+ flex-wrap: wrap; }
+ [data-whatinput='mouse'] .menu li {
+ outline: 0; }
+ .menu a,
+ .menu .button {
+ line-height: 1;
+ text-decoration: none;
+ display: block;
+ padding: 0.7rem 1rem; }
+ .menu input,
+ .menu select,
+ .menu a,
+ .menu button {
+ margin-bottom: 0; }
+ .menu input {
+ display: inline-block; }
+ .menu, .menu.horizontal {
+ -webkit-flex-wrap: wrap;
+ -ms-flex-wrap: wrap;
+ flex-wrap: wrap;
+ -webkit-box-orient: horizontal;
+ -webkit-box-direction: normal;
+ -webkit-flex-direction: row;
+ -ms-flex-direction: row;
+ flex-direction: row; }
+ .menu.vertical {
+ -webkit-flex-wrap: nowrap;
+ -ms-flex-wrap: nowrap;
+ flex-wrap: nowrap;
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: normal;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column; }
+ .menu.vertical.icon-top li a img,
+ .menu.vertical.icon-top li a i,
+ .menu.vertical.icon-top li a svg, .menu.vertical.icon-bottom li a img,
+ .menu.vertical.icon-bottom li a i,
+ .menu.vertical.icon-bottom li a svg {
+ text-align: left; }
+ .menu.expanded li {
+ -webkit-box-flex: 1;
+ -webkit-flex: 1 1 0px;
+ -ms-flex: 1 1 0px;
+ flex: 1 1 0px; }
+ .menu.expanded.icon-top li a img,
+ .menu.expanded.icon-top li a i,
+ .menu.expanded.icon-top li a svg, .menu.expanded.icon-bottom li a img,
+ .menu.expanded.icon-bottom li a i,
+ .menu.expanded.icon-bottom li a svg {
+ text-align: left; }
+ .menu.simple {
+ -webkit-box-align: center;
+ -webkit-align-items: center;
+ -ms-flex-align: center;
+ align-items: center; }
+ .menu.simple li + li {
+ margin-left: 1rem; }
+ .menu.simple a {
+ padding: 0; }
+ @media print, screen and (min-width: 40em) {
+ .menu.medium-horizontal {
+ -webkit-flex-wrap: wrap;
+ -ms-flex-wrap: wrap;
+ flex-wrap: wrap;
+ -webkit-box-orient: horizontal;
+ -webkit-box-direction: normal;
+ -webkit-flex-direction: row;
+ -ms-flex-direction: row;
+ flex-direction: row; }
+ .menu.medium-vertical {
+ -webkit-flex-wrap: nowrap;
+ -ms-flex-wrap: nowrap;
+ flex-wrap: nowrap;
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: normal;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column; }
+ .menu.medium-expanded li {
+ -webkit-box-flex: 1;
+ -webkit-flex: 1 1 0px;
+ -ms-flex: 1 1 0px;
+ flex: 1 1 0px; }
+ .menu.medium-simple li {
+ -webkit-box-flex: 1;
+ -webkit-flex: 1 1 0px;
+ -ms-flex: 1 1 0px;
+ flex: 1 1 0px; } }
+ @media print, screen and (min-width: 64em) {
+ .menu.large-horizontal {
+ -webkit-flex-wrap: wrap;
+ -ms-flex-wrap: wrap;
+ flex-wrap: wrap;
+ -webkit-box-orient: horizontal;
+ -webkit-box-direction: normal;
+ -webkit-flex-direction: row;
+ -ms-flex-direction: row;
+ flex-direction: row; }
+ .menu.large-vertical {
+ -webkit-flex-wrap: nowrap;
+ -ms-flex-wrap: nowrap;
+ flex-wrap: nowrap;
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: normal;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column; }
+ .menu.large-expanded li {
+ -webkit-box-flex: 1;
+ -webkit-flex: 1 1 0px;
+ -ms-flex: 1 1 0px;
+ flex: 1 1 0px; }
+ .menu.large-simple li {
+ -webkit-box-flex: 1;
+ -webkit-flex: 1 1 0px;
+ -ms-flex: 1 1 0px;
+ flex: 1 1 0px; } }
+ .menu.nested {
+ margin-right: 0;
+ margin-left: 1rem; }
+ .menu.icons a {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex; }
+ .menu.icon-top a, .menu.icon-right a, .menu.icon-bottom a, .menu.icon-left a {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex; }
+ .menu.icon-left li a, .menu.nested.icon-left li a {
+ -webkit-box-orient: horizontal;
+ -webkit-box-direction: normal;
+ -webkit-flex-flow: row nowrap;
+ -ms-flex-flow: row nowrap;
+ flex-flow: row nowrap; }
+ .menu.icon-left li a img,
+ .menu.icon-left li a i,
+ .menu.icon-left li a svg, .menu.nested.icon-left li a img,
+ .menu.nested.icon-left li a i,
+ .menu.nested.icon-left li a svg {
+ margin-right: 0.25rem; }
+ .menu.icon-right li a, .menu.nested.icon-right li a {
+ -webkit-box-orient: horizontal;
+ -webkit-box-direction: normal;
+ -webkit-flex-flow: row nowrap;
+ -ms-flex-flow: row nowrap;
+ flex-flow: row nowrap; }
+ .menu.icon-right li a img,
+ .menu.icon-right li a i,
+ .menu.icon-right li a svg, .menu.nested.icon-right li a img,
+ .menu.nested.icon-right li a i,
+ .menu.nested.icon-right li a svg {
+ margin-left: 0.25rem; }
+ .menu.icon-top li a, .menu.nested.icon-top li a {
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: normal;
+ -webkit-flex-flow: column nowrap;
+ -ms-flex-flow: column nowrap;
+ flex-flow: column nowrap; }
+ .menu.icon-top li a img,
+ .menu.icon-top li a i,
+ .menu.icon-top li a svg, .menu.nested.icon-top li a img,
+ .menu.nested.icon-top li a i,
+ .menu.nested.icon-top li a svg {
+ -webkit-align-self: stretch;
+ -ms-flex-item-align: stretch;
+ align-self: stretch;
+ margin-bottom: 0.25rem;
+ text-align: center; }
+ .menu.icon-bottom li a, .menu.nested.icon-bottom li a {
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: normal;
+ -webkit-flex-flow: column nowrap;
+ -ms-flex-flow: column nowrap;
+ flex-flow: column nowrap; }
+ .menu.icon-bottom li a img,
+ .menu.icon-bottom li a i,
+ .menu.icon-bottom li a svg, .menu.nested.icon-bottom li a img,
+ .menu.nested.icon-bottom li a i,
+ .menu.nested.icon-bottom li a svg {
+ -webkit-align-self: stretch;
+ -ms-flex-item-align: stretch;
+ align-self: stretch;
+ margin-bottom: 0.25rem;
+ text-align: center; }
+ .menu .is-active > a {
+ background: #1779ba;
+ color: #fefefe; }
+ .menu .active > a {
+ background: #1779ba;
+ color: #fefefe; }
+ .menu.align-left {
+ -webkit-box-pack: start;
+ -webkit-justify-content: flex-start;
+ -ms-flex-pack: start;
+ justify-content: flex-start; }
+ .menu.align-right li {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-pack: end;
+ -webkit-justify-content: flex-end;
+ -ms-flex-pack: end;
+ justify-content: flex-end; }
+ .menu.align-right li .submenu li {
+ -webkit-box-pack: start;
+ -webkit-justify-content: flex-start;
+ -ms-flex-pack: start;
+ justify-content: flex-start; }
+ .menu.align-right.vertical li {
+ display: block;
+ text-align: right; }
+ .menu.align-right.vertical li .submenu li {
+ text-align: right; }
+ .menu.align-right.icon-top li a img,
+ .menu.align-right.icon-top li a i,
+ .menu.align-right.icon-top li a svg, .menu.align-right.icon-bottom li a img,
+ .menu.align-right.icon-bottom li a i,
+ .menu.align-right.icon-bottom li a svg {
+ text-align: right; }
+ .menu.align-right .nested {
+ margin-right: 1rem;
+ margin-left: 0; }
+ .menu.align-center li {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-pack: center;
+ -webkit-justify-content: center;
+ -ms-flex-pack: center;
+ justify-content: center; }
+ .menu.align-center li .submenu li {
+ -webkit-box-pack: start;
+ -webkit-justify-content: flex-start;
+ -ms-flex-pack: start;
+ justify-content: flex-start; }
+ .menu .menu-text {
+ padding: 0.7rem 1rem;
+ font-weight: bold;
+ line-height: 1;
+ color: inherit; }
+
+.menu-centered > .menu {
+ -webkit-box-pack: center;
+ -webkit-justify-content: center;
+ -ms-flex-pack: center;
+ justify-content: center; }
+ .menu-centered > .menu li {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-pack: center;
+ -webkit-justify-content: center;
+ -ms-flex-pack: center;
+ justify-content: center; }
+ .menu-centered > .menu li .submenu li {
+ -webkit-box-pack: start;
+ -webkit-justify-content: flex-start;
+ -ms-flex-pack: start;
+ justify-content: flex-start; }
+
+.no-js [data-responsive-menu] ul {
+ display: none; }
+
+.menu-icon {
+ position: relative;
+ display: inline-block;
+ vertical-align: middle;
+ width: 20px;
+ height: 16px;
+ cursor: pointer; }
+ .menu-icon::after {
+ position: absolute;
+ top: 0;
+ left: 0;
+ display: block;
+ width: 100%;
+ height: 2px;
+ background: #fefefe;
+ -webkit-box-shadow: 0 7px 0 #fefefe, 0 14px 0 #fefefe;
+ box-shadow: 0 7px 0 #fefefe, 0 14px 0 #fefefe;
+ content: ''; }
+ .menu-icon:hover::after {
+ background: #cacaca;
+ -webkit-box-shadow: 0 7px 0 #cacaca, 0 14px 0 #cacaca;
+ box-shadow: 0 7px 0 #cacaca, 0 14px 0 #cacaca; }
+
+.menu-icon.dark {
+ position: relative;
+ display: inline-block;
+ vertical-align: middle;
+ width: 20px;
+ height: 16px;
+ cursor: pointer; }
+ .menu-icon.dark::after {
+ position: absolute;
+ top: 0;
+ left: 0;
+ display: block;
+ width: 100%;
+ height: 2px;
+ background: #0a0a0a;
+ -webkit-box-shadow: 0 7px 0 #0a0a0a, 0 14px 0 #0a0a0a;
+ box-shadow: 0 7px 0 #0a0a0a, 0 14px 0 #0a0a0a;
+ content: ''; }
+ .menu-icon.dark:hover::after {
+ background: #8a8a8a;
+ -webkit-box-shadow: 0 7px 0 #8a8a8a, 0 14px 0 #8a8a8a;
+ box-shadow: 0 7px 0 #8a8a8a, 0 14px 0 #8a8a8a; }
+
+.accordion-menu li {
+ width: 100%; }
+
+.accordion-menu a {
+ padding: 0.7rem 1rem; }
+
+.accordion-menu .is-accordion-submenu a {
+ padding: 0.7rem 1rem; }
+
+.accordion-menu .nested.is-accordion-submenu {
+ margin-right: 0;
+ margin-left: 1rem; }
+
+.accordion-menu.align-right .nested.is-accordion-submenu {
+ margin-right: 1rem;
+ margin-left: 0; }
+
+.accordion-menu .is-accordion-submenu-parent:not(.has-submenu-toggle) > a {
+ position: relative; }
+ .accordion-menu .is-accordion-submenu-parent:not(.has-submenu-toggle) > a::after {
+ display: block;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 6px;
+ content: '';
+ border-bottom-width: 0;
+ border-color: #1779ba transparent transparent;
+ position: absolute;
+ top: 50%;
+ margin-top: -3px;
+ right: 1rem; }
+
+.accordion-menu.align-left .is-accordion-submenu-parent > a::after {
+ right: 1rem;
+ left: auto; }
+
+.accordion-menu.align-right .is-accordion-submenu-parent > a::after {
+ right: auto;
+ left: 1rem; }
+
+.accordion-menu .is-accordion-submenu-parent[aria-expanded='true'] > a::after {
+ -webkit-transform: rotate(180deg);
+ -ms-transform: rotate(180deg);
+ transform: rotate(180deg);
+ -webkit-transform-origin: 50% 50%;
+ -ms-transform-origin: 50% 50%;
+ transform-origin: 50% 50%; }
+
+.is-accordion-submenu-parent {
+ position: relative; }
+
+.has-submenu-toggle > a {
+ margin-right: 40px; }
+
+.submenu-toggle {
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 40px;
+ height: 40px;
+ cursor: pointer; }
+ .submenu-toggle::after {
+ display: block;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 6px;
+ content: '';
+ border-bottom-width: 0;
+ border-color: #1779ba transparent transparent;
+ top: 0;
+ bottom: 0;
+ margin: auto; }
+
+.submenu-toggle[aria-expanded='true']::after {
+ -webkit-transform: scaleY(-1);
+ -ms-transform: scaleY(-1);
+ transform: scaleY(-1);
+ -webkit-transform-origin: 50% 50%;
+ -ms-transform-origin: 50% 50%;
+ transform-origin: 50% 50%; }
+
+.submenu-toggle-text {
+ position: absolute !important;
+ width: 1px !important;
+ height: 1px !important;
+ padding: 0 !important;
+ overflow: hidden !important;
+ clip: rect(0, 0, 0, 0) !important;
+ white-space: nowrap !important;
+ border: 0 !important; }
+
+.is-drilldown {
+ position: relative;
+ overflow: hidden; }
+ .is-drilldown li {
+ display: block; }
+ .is-drilldown.animate-height {
+ -webkit-transition: height 0.5s;
+ transition: height 0.5s; }
+
+.drilldown a {
+ padding: 0.7rem 1rem;
+ background: #fefefe; }
+
+.drilldown .is-drilldown-submenu {
+ position: absolute;
+ top: 0;
+ left: 100%;
+ z-index: -1;
+ width: 100%;
+ background: #fefefe;
+ -webkit-transition: -webkit-transform 0.15s linear;
+ transition: -webkit-transform 0.15s linear;
+ transition: transform 0.15s linear;
+ transition: transform 0.15s linear, -webkit-transform 0.15s linear; }
+ .drilldown .is-drilldown-submenu.is-active {
+ z-index: 1;
+ display: block;
+ -webkit-transform: translateX(-100%);
+ -ms-transform: translateX(-100%);
+ transform: translateX(-100%); }
+ .drilldown .is-drilldown-submenu.is-closing {
+ -webkit-transform: translateX(100%);
+ -ms-transform: translateX(100%);
+ transform: translateX(100%); }
+ .drilldown .is-drilldown-submenu a {
+ padding: 0.7rem 1rem; }
+
+.drilldown .nested.is-drilldown-submenu {
+ margin-right: 0;
+ margin-left: 0; }
+
+.drilldown .drilldown-submenu-cover-previous {
+ min-height: 100%; }
+
+.drilldown .is-drilldown-submenu-parent > a {
+ position: relative; }
+ .drilldown .is-drilldown-submenu-parent > a::after {
+ display: block;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 6px;
+ content: '';
+ border-right-width: 0;
+ border-color: transparent transparent transparent #1779ba;
+ position: absolute;
+ top: 50%;
+ margin-top: -6px;
+ right: 1rem; }
+
+.drilldown.align-left .is-drilldown-submenu-parent > a::after {
+ display: block;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 6px;
+ content: '';
+ border-right-width: 0;
+ border-color: transparent transparent transparent #1779ba;
+ right: 1rem;
+ left: auto; }
+
+.drilldown.align-right .is-drilldown-submenu-parent > a::after {
+ display: block;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 6px;
+ content: '';
+ border-left-width: 0;
+ border-color: transparent #1779ba transparent transparent;
+ right: auto;
+ left: 1rem; }
+
+.drilldown .js-drilldown-back > a::before {
+ display: block;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 6px;
+ content: '';
+ border-left-width: 0;
+ border-color: transparent #1779ba transparent transparent;
+ display: inline-block;
+ vertical-align: middle;
+ margin-right: 0.75rem; }
+
+.dropdown.menu > li.opens-left > .is-dropdown-submenu {
+ top: 100%;
+ right: 0;
+ left: auto; }
+
+.dropdown.menu > li.opens-right > .is-dropdown-submenu {
+ top: 100%;
+ right: auto;
+ left: 0; }
+
+.dropdown.menu > li.is-dropdown-submenu-parent > a {
+ position: relative;
+ padding-right: 1.5rem; }
+
+.dropdown.menu > li.is-dropdown-submenu-parent > a::after {
+ display: block;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 6px;
+ content: '';
+ border-bottom-width: 0;
+ border-color: #1779ba transparent transparent;
+ right: 5px;
+ left: auto;
+ margin-top: -3px; }
+
+[data-whatinput='mouse'] .dropdown.menu a {
+ outline: 0; }
+
+.dropdown.menu > li > a {
+ padding: 0.7rem 1rem; }
+
+.dropdown.menu > li.is-active > a {
+ background: transparent;
+ color: #1779ba; }
+
+.no-js .dropdown.menu ul {
+ display: none; }
+
+.dropdown.menu .nested.is-dropdown-submenu {
+ margin-right: 0;
+ margin-left: 0; }
+
+.dropdown.menu.vertical > li .is-dropdown-submenu {
+ top: 0; }
+
+.dropdown.menu.vertical > li.opens-left > .is-dropdown-submenu {
+ top: 0;
+ right: 100%;
+ left: auto; }
+
+.dropdown.menu.vertical > li.opens-right > .is-dropdown-submenu {
+ right: auto;
+ left: 100%; }
+
+.dropdown.menu.vertical > li > a::after {
+ right: 14px; }
+
+.dropdown.menu.vertical > li.opens-left > a::after {
+ display: block;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 6px;
+ content: '';
+ border-left-width: 0;
+ border-color: transparent #1779ba transparent transparent;
+ right: auto;
+ left: 5px; }
+
+.dropdown.menu.vertical > li.opens-right > a::after {
+ display: block;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 6px;
+ content: '';
+ border-right-width: 0;
+ border-color: transparent transparent transparent #1779ba; }
+
+@media print, screen and (min-width: 40em) {
+ .dropdown.menu.medium-horizontal > li.opens-left > .is-dropdown-submenu {
+ top: 100%;
+ right: 0;
+ left: auto; }
+ .dropdown.menu.medium-horizontal > li.opens-right > .is-dropdown-submenu {
+ top: 100%;
+ right: auto;
+ left: 0; }
+ .dropdown.menu.medium-horizontal > li.is-dropdown-submenu-parent > a {
+ position: relative;
+ padding-right: 1.5rem; }
+ .dropdown.menu.medium-horizontal > li.is-dropdown-submenu-parent > a::after {
+ display: block;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 6px;
+ content: '';
+ border-bottom-width: 0;
+ border-color: #1779ba transparent transparent;
+ right: 5px;
+ left: auto;
+ margin-top: -3px; }
+ .dropdown.menu.medium-vertical > li .is-dropdown-submenu {
+ top: 0; }
+ .dropdown.menu.medium-vertical > li.opens-left > .is-dropdown-submenu {
+ top: 0;
+ right: 100%;
+ left: auto; }
+ .dropdown.menu.medium-vertical > li.opens-right > .is-dropdown-submenu {
+ right: auto;
+ left: 100%; }
+ .dropdown.menu.medium-vertical > li > a::after {
+ right: 14px; }
+ .dropdown.menu.medium-vertical > li.opens-left > a::after {
+ display: block;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 6px;
+ content: '';
+ border-left-width: 0;
+ border-color: transparent #1779ba transparent transparent;
+ right: auto;
+ left: 5px; }
+ .dropdown.menu.medium-vertical > li.opens-right > a::after {
+ display: block;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 6px;
+ content: '';
+ border-right-width: 0;
+ border-color: transparent transparent transparent #1779ba; } }
+
+@media print, screen and (min-width: 64em) {
+ .dropdown.menu.large-horizontal > li.opens-left > .is-dropdown-submenu {
+ top: 100%;
+ right: 0;
+ left: auto; }
+ .dropdown.menu.large-horizontal > li.opens-right > .is-dropdown-submenu {
+ top: 100%;
+ right: auto;
+ left: 0; }
+ .dropdown.menu.large-horizontal > li.is-dropdown-submenu-parent > a {
+ position: relative;
+ padding-right: 1.5rem; }
+ .dropdown.menu.large-horizontal > li.is-dropdown-submenu-parent > a::after {
+ display: block;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 6px;
+ content: '';
+ border-bottom-width: 0;
+ border-color: #1779ba transparent transparent;
+ right: 5px;
+ left: auto;
+ margin-top: -3px; }
+ .dropdown.menu.large-vertical > li .is-dropdown-submenu {
+ top: 0; }
+ .dropdown.menu.large-vertical > li.opens-left > .is-dropdown-submenu {
+ top: 0;
+ right: 100%;
+ left: auto; }
+ .dropdown.menu.large-vertical > li.opens-right > .is-dropdown-submenu {
+ right: auto;
+ left: 100%; }
+ .dropdown.menu.large-vertical > li > a::after {
+ right: 14px; }
+ .dropdown.menu.large-vertical > li.opens-left > a::after {
+ display: block;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 6px;
+ content: '';
+ border-left-width: 0;
+ border-color: transparent #1779ba transparent transparent;
+ right: auto;
+ left: 5px; }
+ .dropdown.menu.large-vertical > li.opens-right > a::after {
+ display: block;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 6px;
+ content: '';
+ border-right-width: 0;
+ border-color: transparent transparent transparent #1779ba; } }
+
+.dropdown.menu.align-right .is-dropdown-submenu.first-sub {
+ top: 100%;
+ right: 0;
+ left: auto; }
+
+.is-dropdown-menu.vertical {
+ width: 100px; }
+ .is-dropdown-menu.vertical.align-right {
+ float: right; }
+
+.is-dropdown-submenu-parent {
+ position: relative; }
+ .is-dropdown-submenu-parent a::after {
+ position: absolute;
+ top: 50%;
+ right: 5px;
+ left: auto;
+ margin-top: -6px; }
+ .is-dropdown-submenu-parent.opens-inner > .is-dropdown-submenu {
+ top: 100%;
+ left: auto; }
+ .is-dropdown-submenu-parent.opens-left > .is-dropdown-submenu {
+ right: 100%;
+ left: auto; }
+ .is-dropdown-submenu-parent.opens-right > .is-dropdown-submenu {
+ right: auto;
+ left: 100%; }
+
+.is-dropdown-submenu {
+ position: absolute;
+ top: 0;
+ left: 100%;
+ z-index: 1;
+ display: none;
+ min-width: 200px;
+ border: 1px solid #cacaca;
+ background: #fefefe; }
+ .dropdown .is-dropdown-submenu a {
+ padding: 0.7rem 1rem; }
+ .is-dropdown-submenu .is-dropdown-submenu-parent > a::after {
+ right: 14px; }
+ .is-dropdown-submenu .is-dropdown-submenu-parent.opens-left > a::after {
+ display: block;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 6px;
+ content: '';
+ border-left-width: 0;
+ border-color: transparent #1779ba transparent transparent;
+ right: auto;
+ left: 5px; }
+ .is-dropdown-submenu .is-dropdown-submenu-parent.opens-right > a::after {
+ display: block;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 6px;
+ content: '';
+ border-right-width: 0;
+ border-color: transparent transparent transparent #1779ba; }
+ .is-dropdown-submenu .is-dropdown-submenu {
+ margin-top: -1px; }
+ .is-dropdown-submenu > li {
+ width: 100%; }
+ .is-dropdown-submenu.js-dropdown-active {
+ display: block; }
+
+.is-off-canvas-open {
+ overflow: hidden; }
+
+.js-off-canvas-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 11;
+ width: 100%;
+ height: 100%;
+ -webkit-transition: opacity 0.5s ease, visibility 0.5s ease;
+ transition: opacity 0.5s ease, visibility 0.5s ease;
+ background: rgba(254, 254, 254, 0.25);
+ opacity: 0;
+ visibility: hidden;
+ overflow: hidden; }
+ .js-off-canvas-overlay.is-visible {
+ opacity: 1;
+ visibility: visible; }
+ .js-off-canvas-overlay.is-closable {
+ cursor: pointer; }
+ .js-off-canvas-overlay.is-overlay-absolute {
+ position: absolute; }
+ .js-off-canvas-overlay.is-overlay-fixed {
+ position: fixed; }
+
+.off-canvas-wrapper {
+ position: relative;
+ overflow: hidden; }
+
+.off-canvas {
+ position: fixed;
+ z-index: 12;
+ -webkit-transition: -webkit-transform 0.5s ease;
+ transition: -webkit-transform 0.5s ease;
+ transition: transform 0.5s ease;
+ transition: transform 0.5s ease, -webkit-transform 0.5s ease;
+ -webkit-backface-visibility: hidden;
+ backface-visibility: hidden;
+ background: #e6e6e6; }
+ [data-whatinput='mouse'] .off-canvas {
+ outline: 0; }
+ .off-canvas.is-transition-push {
+ z-index: 12; }
+ .off-canvas.is-closed {
+ visibility: hidden; }
+ .off-canvas.is-transition-overlap {
+ z-index: 13; }
+ .off-canvas.is-transition-overlap.is-open {
+ -webkit-box-shadow: 0 0 10px rgba(10, 10, 10, 0.7);
+ box-shadow: 0 0 10px rgba(10, 10, 10, 0.7); }
+ .off-canvas.is-open {
+ -webkit-transform: translate(0, 0);
+ -ms-transform: translate(0, 0);
+ transform: translate(0, 0); }
+
+.off-canvas-absolute {
+ position: absolute;
+ z-index: 12;
+ -webkit-transition: -webkit-transform 0.5s ease;
+ transition: -webkit-transform 0.5s ease;
+ transition: transform 0.5s ease;
+ transition: transform 0.5s ease, -webkit-transform 0.5s ease;
+ -webkit-backface-visibility: hidden;
+ backface-visibility: hidden;
+ background: #e6e6e6; }
+ [data-whatinput='mouse'] .off-canvas-absolute {
+ outline: 0; }
+ .off-canvas-absolute.is-transition-push {
+ z-index: 12; }
+ .off-canvas-absolute.is-closed {
+ visibility: hidden; }
+ .off-canvas-absolute.is-transition-overlap {
+ z-index: 13; }
+ .off-canvas-absolute.is-transition-overlap.is-open {
+ -webkit-box-shadow: 0 0 10px rgba(10, 10, 10, 0.7);
+ box-shadow: 0 0 10px rgba(10, 10, 10, 0.7); }
+ .off-canvas-absolute.is-open {
+ -webkit-transform: translate(0, 0);
+ -ms-transform: translate(0, 0);
+ transform: translate(0, 0); }
+
+.position-left {
+ top: 0;
+ left: 0;
+ height: 100%;
+ overflow-y: auto;
+ width: 250px;
+ -webkit-transform: translateX(-250px);
+ -ms-transform: translateX(-250px);
+ transform: translateX(-250px); }
+ .off-canvas-content .off-canvas.position-left {
+ -webkit-transform: translateX(-250px);
+ -ms-transform: translateX(-250px);
+ transform: translateX(-250px); }
+ .off-canvas-content .off-canvas.position-left.is-transition-overlap.is-open {
+ -webkit-transform: translate(0, 0);
+ -ms-transform: translate(0, 0);
+ transform: translate(0, 0); }
+ .off-canvas-content.is-open-left.has-transition-push {
+ -webkit-transform: translateX(250px);
+ -ms-transform: translateX(250px);
+ transform: translateX(250px); }
+ .position-left.is-transition-push {
+ -webkit-box-shadow: inset -13px 0 20px -13px rgba(10, 10, 10, 0.25);
+ box-shadow: inset -13px 0 20px -13px rgba(10, 10, 10, 0.25); }
+
+.position-right {
+ top: 0;
+ right: 0;
+ height: 100%;
+ overflow-y: auto;
+ width: 250px;
+ -webkit-transform: translateX(250px);
+ -ms-transform: translateX(250px);
+ transform: translateX(250px); }
+ .off-canvas-content .off-canvas.position-right {
+ -webkit-transform: translateX(250px);
+ -ms-transform: translateX(250px);
+ transform: translateX(250px); }
+ .off-canvas-content .off-canvas.position-right.is-transition-overlap.is-open {
+ -webkit-transform: translate(0, 0);
+ -ms-transform: translate(0, 0);
+ transform: translate(0, 0); }
+ .off-canvas-content.is-open-right.has-transition-push {
+ -webkit-transform: translateX(-250px);
+ -ms-transform: translateX(-250px);
+ transform: translateX(-250px); }
+ .position-right.is-transition-push {
+ -webkit-box-shadow: inset 13px 0 20px -13px rgba(10, 10, 10, 0.25);
+ box-shadow: inset 13px 0 20px -13px rgba(10, 10, 10, 0.25); }
+
+.position-top {
+ top: 0;
+ left: 0;
+ width: 100%;
+ overflow-x: auto;
+ height: 250px;
+ -webkit-transform: translateY(-250px);
+ -ms-transform: translateY(-250px);
+ transform: translateY(-250px); }
+ .off-canvas-content .off-canvas.position-top {
+ -webkit-transform: translateY(-250px);
+ -ms-transform: translateY(-250px);
+ transform: translateY(-250px); }
+ .off-canvas-content .off-canvas.position-top.is-transition-overlap.is-open {
+ -webkit-transform: translate(0, 0);
+ -ms-transform: translate(0, 0);
+ transform: translate(0, 0); }
+ .off-canvas-content.is-open-top.has-transition-push {
+ -webkit-transform: translateY(250px);
+ -ms-transform: translateY(250px);
+ transform: translateY(250px); }
+ .position-top.is-transition-push {
+ -webkit-box-shadow: inset 0 -13px 20px -13px rgba(10, 10, 10, 0.25);
+ box-shadow: inset 0 -13px 20px -13px rgba(10, 10, 10, 0.25); }
+
+.position-bottom {
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ overflow-x: auto;
+ height: 250px;
+ -webkit-transform: translateY(250px);
+ -ms-transform: translateY(250px);
+ transform: translateY(250px); }
+ .off-canvas-content .off-canvas.position-bottom {
+ -webkit-transform: translateY(250px);
+ -ms-transform: translateY(250px);
+ transform: translateY(250px); }
+ .off-canvas-content .off-canvas.position-bottom.is-transition-overlap.is-open {
+ -webkit-transform: translate(0, 0);
+ -ms-transform: translate(0, 0);
+ transform: translate(0, 0); }
+ .off-canvas-content.is-open-bottom.has-transition-push {
+ -webkit-transform: translateY(-250px);
+ -ms-transform: translateY(-250px);
+ transform: translateY(-250px); }
+ .position-bottom.is-transition-push {
+ -webkit-box-shadow: inset 0 13px 20px -13px rgba(10, 10, 10, 0.25);
+ box-shadow: inset 0 13px 20px -13px rgba(10, 10, 10, 0.25); }
+
+.off-canvas-content {
+ -webkit-transform: none;
+ -ms-transform: none;
+ transform: none;
+ -webkit-backface-visibility: hidden;
+ backface-visibility: hidden; }
+ .off-canvas-content.has-transition-overlap, .off-canvas-content.has-transition-push {
+ -webkit-transition: -webkit-transform 0.5s ease;
+ transition: -webkit-transform 0.5s ease;
+ transition: transform 0.5s ease;
+ transition: transform 0.5s ease, -webkit-transform 0.5s ease; }
+ .off-canvas-content.has-transition-push {
+ -webkit-transform: translate(0, 0);
+ -ms-transform: translate(0, 0);
+ transform: translate(0, 0); }
+ .off-canvas-content .off-canvas.is-open {
+ -webkit-transform: translate(0, 0);
+ -ms-transform: translate(0, 0);
+ transform: translate(0, 0); }
+
+@media print, screen and (min-width: 40em) {
+ .position-left.reveal-for-medium {
+ -webkit-transform: none;
+ -ms-transform: none;
+ transform: none;
+ z-index: 12;
+ -webkit-transition: none;
+ transition: none;
+ visibility: visible; }
+ .position-left.reveal-for-medium .close-button {
+ display: none; }
+ .off-canvas-content .position-left.reveal-for-medium {
+ -webkit-transform: none;
+ -ms-transform: none;
+ transform: none; }
+ .off-canvas-content.has-reveal-left {
+ margin-left: 250px; }
+ .position-left.reveal-for-medium ~ .off-canvas-content {
+ margin-left: 250px; }
+ .position-right.reveal-for-medium {
+ -webkit-transform: none;
+ -ms-transform: none;
+ transform: none;
+ z-index: 12;
+ -webkit-transition: none;
+ transition: none;
+ visibility: visible; }
+ .position-right.reveal-for-medium .close-button {
+ display: none; }
+ .off-canvas-content .position-right.reveal-for-medium {
+ -webkit-transform: none;
+ -ms-transform: none;
+ transform: none; }
+ .off-canvas-content.has-reveal-right {
+ margin-right: 250px; }
+ .position-right.reveal-for-medium ~ .off-canvas-content {
+ margin-right: 250px; }
+ .position-top.reveal-for-medium {
+ -webkit-transform: none;
+ -ms-transform: none;
+ transform: none;
+ z-index: 12;
+ -webkit-transition: none;
+ transition: none;
+ visibility: visible; }
+ .position-top.reveal-for-medium .close-button {
+ display: none; }
+ .off-canvas-content .position-top.reveal-for-medium {
+ -webkit-transform: none;
+ -ms-transform: none;
+ transform: none; }
+ .off-canvas-content.has-reveal-top {
+ margin-top: 250px; }
+ .position-top.reveal-for-medium ~ .off-canvas-content {
+ margin-top: 250px; }
+ .position-bottom.reveal-for-medium {
+ -webkit-transform: none;
+ -ms-transform: none;
+ transform: none;
+ z-index: 12;
+ -webkit-transition: none;
+ transition: none;
+ visibility: visible; }
+ .position-bottom.reveal-for-medium .close-button {
+ display: none; }
+ .off-canvas-content .position-bottom.reveal-for-medium {
+ -webkit-transform: none;
+ -ms-transform: none;
+ transform: none; }
+ .off-canvas-content.has-reveal-bottom {
+ margin-bottom: 250px; }
+ .position-bottom.reveal-for-medium ~ .off-canvas-content {
+ margin-bottom: 250px; } }
+
+@media print, screen and (min-width: 64em) {
+ .position-left.reveal-for-large {
+ -webkit-transform: none;
+ -ms-transform: none;
+ transform: none;
+ z-index: 12;
+ -webkit-transition: none;
+ transition: none;
+ visibility: visible; }
+ .position-left.reveal-for-large .close-button {
+ display: none; }
+ .off-canvas-content .position-left.reveal-for-large {
+ -webkit-transform: none;
+ -ms-transform: none;
+ transform: none; }
+ .off-canvas-content.has-reveal-left {
+ margin-left: 250px; }
+ .position-left.reveal-for-large ~ .off-canvas-content {
+ margin-left: 250px; }
+ .position-right.reveal-for-large {
+ -webkit-transform: none;
+ -ms-transform: none;
+ transform: none;
+ z-index: 12;
+ -webkit-transition: none;
+ transition: none;
+ visibility: visible; }
+ .position-right.reveal-for-large .close-button {
+ display: none; }
+ .off-canvas-content .position-right.reveal-for-large {
+ -webkit-transform: none;
+ -ms-transform: none;
+ transform: none; }
+ .off-canvas-content.has-reveal-right {
+ margin-right: 250px; }
+ .position-right.reveal-for-large ~ .off-canvas-content {
+ margin-right: 250px; }
+ .position-top.reveal-for-large {
+ -webkit-transform: none;
+ -ms-transform: none;
+ transform: none;
+ z-index: 12;
+ -webkit-transition: none;
+ transition: none;
+ visibility: visible; }
+ .position-top.reveal-for-large .close-button {
+ display: none; }
+ .off-canvas-content .position-top.reveal-for-large {
+ -webkit-transform: none;
+ -ms-transform: none;
+ transform: none; }
+ .off-canvas-content.has-reveal-top {
+ margin-top: 250px; }
+ .position-top.reveal-for-large ~ .off-canvas-content {
+ margin-top: 250px; }
+ .position-bottom.reveal-for-large {
+ -webkit-transform: none;
+ -ms-transform: none;
+ transform: none;
+ z-index: 12;
+ -webkit-transition: none;
+ transition: none;
+ visibility: visible; }
+ .position-bottom.reveal-for-large .close-button {
+ display: none; }
+ .off-canvas-content .position-bottom.reveal-for-large {
+ -webkit-transform: none;
+ -ms-transform: none;
+ transform: none; }
+ .off-canvas-content.has-reveal-bottom {
+ margin-bottom: 250px; }
+ .position-bottom.reveal-for-large ~ .off-canvas-content {
+ margin-bottom: 250px; } }
+
+@media print, screen and (min-width: 40em) {
+ .off-canvas.in-canvas-for-medium {
+ visibility: visible;
+ height: auto;
+ position: static;
+ background: none;
+ width: auto;
+ overflow: visible;
+ -webkit-transition: none;
+ transition: none; }
+ .off-canvas.in-canvas-for-medium.position-left, .off-canvas.in-canvas-for-medium.position-right, .off-canvas.in-canvas-for-medium.position-top, .off-canvas.in-canvas-for-medium.position-bottom {
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ -webkit-transform: none;
+ -ms-transform: none;
+ transform: none; }
+ .off-canvas.in-canvas-for-medium .close-button {
+ display: none; } }
+
+@media print, screen and (min-width: 64em) {
+ .off-canvas.in-canvas-for-large {
+ visibility: visible;
+ height: auto;
+ position: static;
+ background: none;
+ width: auto;
+ overflow: visible;
+ -webkit-transition: none;
+ transition: none; }
+ .off-canvas.in-canvas-for-large.position-left, .off-canvas.in-canvas-for-large.position-right, .off-canvas.in-canvas-for-large.position-top, .off-canvas.in-canvas-for-large.position-bottom {
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ -webkit-transform: none;
+ -ms-transform: none;
+ transform: none; }
+ .off-canvas.in-canvas-for-large .close-button {
+ display: none; } }
+
+html.is-reveal-open {
+ position: fixed;
+ width: 100%;
+ overflow-y: hidden; }
+ html.is-reveal-open.zf-has-scroll {
+ overflow-y: scroll; }
+ html.is-reveal-open body {
+ overflow-y: hidden; }
+
+.reveal-overlay {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 1005;
+ display: none;
+ background-color: rgba(10, 10, 10, 0.45);
+ overflow-y: auto; }
+
+.reveal {
+ z-index: 1006;
+ -webkit-backface-visibility: hidden;
+ backface-visibility: hidden;
+ display: none;
+ padding: 1rem;
+ border: 1px solid #cacaca;
+ border-radius: 0;
+ background-color: #fefefe;
+ position: relative;
+ top: 100px;
+ margin-right: auto;
+ margin-left: auto;
+ overflow-y: auto; }
+ [data-whatinput='mouse'] .reveal {
+ outline: 0; }
+ @media print, screen and (min-width: 40em) {
+ .reveal {
+ min-height: 0; } }
+ .reveal .column {
+ min-width: 0; }
+ .reveal > :last-child {
+ margin-bottom: 0; }
+ @media print, screen and (min-width: 40em) {
+ .reveal {
+ width: 600px;
+ max-width: 75rem; } }
+ .reveal.collapse {
+ padding: 0; }
+ @media print, screen and (min-width: 40em) {
+ .reveal.tiny {
+ width: 30%;
+ max-width: 75rem; } }
+ @media print, screen and (min-width: 40em) {
+ .reveal.small {
+ width: 50%;
+ max-width: 75rem; } }
+ @media print, screen and (min-width: 40em) {
+ .reveal.large {
+ width: 90%;
+ max-width: 75rem; } }
+ .reveal.full {
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ max-width: none;
+ height: 100%;
+ min-height: 100%;
+ margin-left: 0;
+ border: 0;
+ border-radius: 0; }
+ @media print, screen and (max-width: 39.99875em) {
+ .reveal {
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ max-width: none;
+ height: 100%;
+ min-height: 100%;
+ margin-left: 0;
+ border: 0;
+ border-radius: 0; } }
+ .reveal.without-overlay {
+ position: fixed; }
+
+.sticky-container {
+ position: relative; }
+
+.sticky {
+ position: relative;
+ z-index: 0;
+ -webkit-transform: translate3d(0, 0, 0);
+ transform: translate3d(0, 0, 0); }
+
+.sticky.is-stuck {
+ position: fixed;
+ z-index: 5;
+ width: 100%; }
+ .sticky.is-stuck.is-at-top {
+ top: 0; }
+ .sticky.is-stuck.is-at-bottom {
+ bottom: 0; }
+
+.sticky.is-anchored {
+ position: relative;
+ right: auto;
+ left: auto; }
+ .sticky.is-anchored.is-at-bottom {
+ bottom: 0; }
+
+.title-bar {
+ padding: 0.5rem;
+ background: #0a0a0a;
+ color: #fefefe;
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-pack: start;
+ -webkit-justify-content: flex-start;
+ -ms-flex-pack: start;
+ justify-content: flex-start;
+ -webkit-box-align: center;
+ -webkit-align-items: center;
+ -ms-flex-align: center;
+ align-items: center; }
+ .title-bar .menu-icon {
+ margin-left: 0.25rem;
+ margin-right: 0.25rem; }
+
+.title-bar-left,
+.title-bar-right {
+ -webkit-box-flex: 1;
+ -webkit-flex: 1 1 0px;
+ -ms-flex: 1 1 0px;
+ flex: 1 1 0px; }
+
+.title-bar-right {
+ text-align: right; }
+
+.title-bar-title {
+ display: inline-block;
+ vertical-align: middle;
+ font-weight: bold; }
+
+.top-bar {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-wrap: nowrap;
+ -ms-flex-wrap: nowrap;
+ flex-wrap: nowrap;
+ -webkit-box-pack: justify;
+ -webkit-justify-content: space-between;
+ -ms-flex-pack: justify;
+ justify-content: space-between;
+ -webkit-box-align: center;
+ -webkit-align-items: center;
+ -ms-flex-align: center;
+ align-items: center;
+ padding: 0.5rem;
+ -webkit-flex-wrap: wrap;
+ -ms-flex-wrap: wrap;
+ flex-wrap: wrap; }
+ .top-bar,
+ .top-bar ul {
+ background-color: #e6e6e6; }
+ .top-bar input {
+ max-width: 200px;
+ margin-right: 1rem; }
+ .top-bar .input-group-field {
+ width: 100%;
+ margin-right: 0; }
+ .top-bar input.button {
+ width: auto; }
+ .top-bar .top-bar-left,
+ .top-bar .top-bar-right {
+ -webkit-box-flex: 0;
+ -webkit-flex: 0 0 100%;
+ -ms-flex: 0 0 100%;
+ flex: 0 0 100%;
+ max-width: 100%; }
+ @media print, screen and (min-width: 40em) {
+ .top-bar {
+ -webkit-flex-wrap: nowrap;
+ -ms-flex-wrap: nowrap;
+ flex-wrap: nowrap; }
+ .top-bar .top-bar-left {
+ -webkit-box-flex: 1;
+ -webkit-flex: 1 1 auto;
+ -ms-flex: 1 1 auto;
+ flex: 1 1 auto;
+ margin-right: auto; }
+ .top-bar .top-bar-right {
+ -webkit-box-flex: 0;
+ -webkit-flex: 0 1 auto;
+ -ms-flex: 0 1 auto;
+ flex: 0 1 auto;
+ margin-left: auto; } }
+ @media print, screen and (max-width: 63.99875em) {
+ .top-bar.stacked-for-medium {
+ -webkit-flex-wrap: wrap;
+ -ms-flex-wrap: wrap;
+ flex-wrap: wrap; }
+ .top-bar.stacked-for-medium .top-bar-left,
+ .top-bar.stacked-for-medium .top-bar-right {
+ -webkit-box-flex: 0;
+ -webkit-flex: 0 0 100%;
+ -ms-flex: 0 0 100%;
+ flex: 0 0 100%;
+ max-width: 100%; } }
+ @media print, screen and (max-width: 74.99875em) {
+ .top-bar.stacked-for-large {
+ -webkit-flex-wrap: wrap;
+ -ms-flex-wrap: wrap;
+ flex-wrap: wrap; }
+ .top-bar.stacked-for-large .top-bar-left,
+ .top-bar.stacked-for-large .top-bar-right {
+ -webkit-box-flex: 0;
+ -webkit-flex: 0 0 100%;
+ -ms-flex: 0 0 100%;
+ flex: 0 0 100%;
+ max-width: 100%; } }
+
+.top-bar-title {
+ -webkit-box-flex: 0;
+ -webkit-flex: 0 0 auto;
+ -ms-flex: 0 0 auto;
+ flex: 0 0 auto;
+ margin: 0.5rem 1rem 0.5rem 0; }
+
+.top-bar-left,
+.top-bar-right {
+ -webkit-box-flex: 0;
+ -webkit-flex: 0 0 auto;
+ -ms-flex: 0 0 auto;
+ flex: 0 0 auto; }
+
+.float-left {
+ float: left !important; }
+
+.float-right {
+ float: right !important; }
+
+.float-center {
+ display: block;
+ margin-right: auto;
+ margin-left: auto; }
+
+.clearfix::before, .clearfix::after {
+ display: table;
+ content: ' ';
+ -webkit-flex-basis: 0;
+ -ms-flex-preferred-size: 0;
+ flex-basis: 0;
+ -webkit-box-ordinal-group: 2;
+ -webkit-order: 1;
+ -ms-flex-order: 1;
+ order: 1; }
+
+.clearfix::after {
+ clear: both; }
+
+.align-left {
+ -webkit-box-pack: start;
+ -webkit-justify-content: flex-start;
+ -ms-flex-pack: start;
+ justify-content: flex-start; }
+
+.align-right {
+ -webkit-box-pack: end;
+ -webkit-justify-content: flex-end;
+ -ms-flex-pack: end;
+ justify-content: flex-end; }
+
+.align-center {
+ -webkit-box-pack: center;
+ -webkit-justify-content: center;
+ -ms-flex-pack: center;
+ justify-content: center; }
+
+.align-justify {
+ -webkit-box-pack: justify;
+ -webkit-justify-content: space-between;
+ -ms-flex-pack: justify;
+ justify-content: space-between; }
+
+.align-spaced {
+ -webkit-justify-content: space-around;
+ -ms-flex-pack: distribute;
+ justify-content: space-around; }
+
+.align-left.vertical.menu > li > a {
+ -webkit-box-pack: start;
+ -webkit-justify-content: flex-start;
+ -ms-flex-pack: start;
+ justify-content: flex-start; }
+
+.align-right.vertical.menu > li > a {
+ -webkit-box-pack: end;
+ -webkit-justify-content: flex-end;
+ -ms-flex-pack: end;
+ justify-content: flex-end; }
+
+.align-center.vertical.menu > li > a {
+ -webkit-box-pack: center;
+ -webkit-justify-content: center;
+ -ms-flex-pack: center;
+ justify-content: center; }
+
+.align-top {
+ -webkit-box-align: start;
+ -webkit-align-items: flex-start;
+ -ms-flex-align: start;
+ align-items: flex-start; }
+
+.align-self-top {
+ -webkit-align-self: flex-start;
+ -ms-flex-item-align: start;
+ align-self: flex-start; }
+
+.align-bottom {
+ -webkit-box-align: end;
+ -webkit-align-items: flex-end;
+ -ms-flex-align: end;
+ align-items: flex-end; }
+
+.align-self-bottom {
+ -webkit-align-self: flex-end;
+ -ms-flex-item-align: end;
+ align-self: flex-end; }
+
+.align-middle {
+ -webkit-box-align: center;
+ -webkit-align-items: center;
+ -ms-flex-align: center;
+ align-items: center; }
+
+.align-self-middle {
+ -webkit-align-self: center;
+ -ms-flex-item-align: center;
+ align-self: center; }
+
+.align-stretch {
+ -webkit-box-align: stretch;
+ -webkit-align-items: stretch;
+ -ms-flex-align: stretch;
+ align-items: stretch; }
+
+.align-self-stretch {
+ -webkit-align-self: stretch;
+ -ms-flex-item-align: stretch;
+ align-self: stretch; }
+
+.align-center-middle {
+ -webkit-box-pack: center;
+ -webkit-justify-content: center;
+ -ms-flex-pack: center;
+ justify-content: center;
+ -webkit-box-align: center;
+ -webkit-align-items: center;
+ -ms-flex-align: center;
+ align-items: center;
+ -webkit-align-content: center;
+ -ms-flex-line-pack: center;
+ align-content: center; }
+
+.small-order-1 {
+ -webkit-box-ordinal-group: 2;
+ -webkit-order: 1;
+ -ms-flex-order: 1;
+ order: 1; }
+
+.small-order-2 {
+ -webkit-box-ordinal-group: 3;
+ -webkit-order: 2;
+ -ms-flex-order: 2;
+ order: 2; }
+
+.small-order-3 {
+ -webkit-box-ordinal-group: 4;
+ -webkit-order: 3;
+ -ms-flex-order: 3;
+ order: 3; }
+
+.small-order-4 {
+ -webkit-box-ordinal-group: 5;
+ -webkit-order: 4;
+ -ms-flex-order: 4;
+ order: 4; }
+
+.small-order-5 {
+ -webkit-box-ordinal-group: 6;
+ -webkit-order: 5;
+ -ms-flex-order: 5;
+ order: 5; }
+
+.small-order-6 {
+ -webkit-box-ordinal-group: 7;
+ -webkit-order: 6;
+ -ms-flex-order: 6;
+ order: 6; }
+
+@media print, screen and (min-width: 40em) {
+ .medium-order-1 {
+ -webkit-box-ordinal-group: 2;
+ -webkit-order: 1;
+ -ms-flex-order: 1;
+ order: 1; }
+ .medium-order-2 {
+ -webkit-box-ordinal-group: 3;
+ -webkit-order: 2;
+ -ms-flex-order: 2;
+ order: 2; }
+ .medium-order-3 {
+ -webkit-box-ordinal-group: 4;
+ -webkit-order: 3;
+ -ms-flex-order: 3;
+ order: 3; }
+ .medium-order-4 {
+ -webkit-box-ordinal-group: 5;
+ -webkit-order: 4;
+ -ms-flex-order: 4;
+ order: 4; }
+ .medium-order-5 {
+ -webkit-box-ordinal-group: 6;
+ -webkit-order: 5;
+ -ms-flex-order: 5;
+ order: 5; }
+ .medium-order-6 {
+ -webkit-box-ordinal-group: 7;
+ -webkit-order: 6;
+ -ms-flex-order: 6;
+ order: 6; } }
+
+@media print, screen and (min-width: 64em) {
+ .large-order-1 {
+ -webkit-box-ordinal-group: 2;
+ -webkit-order: 1;
+ -ms-flex-order: 1;
+ order: 1; }
+ .large-order-2 {
+ -webkit-box-ordinal-group: 3;
+ -webkit-order: 2;
+ -ms-flex-order: 2;
+ order: 2; }
+ .large-order-3 {
+ -webkit-box-ordinal-group: 4;
+ -webkit-order: 3;
+ -ms-flex-order: 3;
+ order: 3; }
+ .large-order-4 {
+ -webkit-box-ordinal-group: 5;
+ -webkit-order: 4;
+ -ms-flex-order: 4;
+ order: 4; }
+ .large-order-5 {
+ -webkit-box-ordinal-group: 6;
+ -webkit-order: 5;
+ -ms-flex-order: 5;
+ order: 5; }
+ .large-order-6 {
+ -webkit-box-ordinal-group: 7;
+ -webkit-order: 6;
+ -ms-flex-order: 6;
+ order: 6; } }
+
+.flex-container {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex; }
+
+.flex-child-auto {
+ -webkit-box-flex: 1;
+ -webkit-flex: 1 1 auto;
+ -ms-flex: 1 1 auto;
+ flex: 1 1 auto; }
+
+.flex-child-grow {
+ -webkit-box-flex: 1;
+ -webkit-flex: 1 0 auto;
+ -ms-flex: 1 0 auto;
+ flex: 1 0 auto; }
+
+.flex-child-shrink {
+ -webkit-box-flex: 0;
+ -webkit-flex: 0 1 auto;
+ -ms-flex: 0 1 auto;
+ flex: 0 1 auto; }
+
+.flex-dir-row {
+ -webkit-box-orient: horizontal;
+ -webkit-box-direction: normal;
+ -webkit-flex-direction: row;
+ -ms-flex-direction: row;
+ flex-direction: row; }
+
+.flex-dir-row-reverse {
+ -webkit-box-orient: horizontal;
+ -webkit-box-direction: reverse;
+ -webkit-flex-direction: row-reverse;
+ -ms-flex-direction: row-reverse;
+ flex-direction: row-reverse; }
+
+.flex-dir-column {
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: normal;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column; }
+
+.flex-dir-column-reverse {
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: reverse;
+ -webkit-flex-direction: column-reverse;
+ -ms-flex-direction: column-reverse;
+ flex-direction: column-reverse; }
+
+@media print, screen and (min-width: 40em) {
+ .medium-flex-container {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex; }
+ .medium-flex-child-auto {
+ -webkit-box-flex: 1;
+ -webkit-flex: 1 1 auto;
+ -ms-flex: 1 1 auto;
+ flex: 1 1 auto; }
+ .medium-flex-child-grow {
+ -webkit-box-flex: 1;
+ -webkit-flex: 1 0 auto;
+ -ms-flex: 1 0 auto;
+ flex: 1 0 auto; }
+ .medium-flex-child-shrink {
+ -webkit-box-flex: 0;
+ -webkit-flex: 0 1 auto;
+ -ms-flex: 0 1 auto;
+ flex: 0 1 auto; }
+ .medium-flex-dir-row {
+ -webkit-box-orient: horizontal;
+ -webkit-box-direction: normal;
+ -webkit-flex-direction: row;
+ -ms-flex-direction: row;
+ flex-direction: row; }
+ .medium-flex-dir-row-reverse {
+ -webkit-box-orient: horizontal;
+ -webkit-box-direction: reverse;
+ -webkit-flex-direction: row-reverse;
+ -ms-flex-direction: row-reverse;
+ flex-direction: row-reverse; }
+ .medium-flex-dir-column {
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: normal;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column; }
+ .medium-flex-dir-column-reverse {
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: reverse;
+ -webkit-flex-direction: column-reverse;
+ -ms-flex-direction: column-reverse;
+ flex-direction: column-reverse; } }
+
+@media print, screen and (min-width: 64em) {
+ .large-flex-container {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex; }
+ .large-flex-child-auto {
+ -webkit-box-flex: 1;
+ -webkit-flex: 1 1 auto;
+ -ms-flex: 1 1 auto;
+ flex: 1 1 auto; }
+ .large-flex-child-grow {
+ -webkit-box-flex: 1;
+ -webkit-flex: 1 0 auto;
+ -ms-flex: 1 0 auto;
+ flex: 1 0 auto; }
+ .large-flex-child-shrink {
+ -webkit-box-flex: 0;
+ -webkit-flex: 0 1 auto;
+ -ms-flex: 0 1 auto;
+ flex: 0 1 auto; }
+ .large-flex-dir-row {
+ -webkit-box-orient: horizontal;
+ -webkit-box-direction: normal;
+ -webkit-flex-direction: row;
+ -ms-flex-direction: row;
+ flex-direction: row; }
+ .large-flex-dir-row-reverse {
+ -webkit-box-orient: horizontal;
+ -webkit-box-direction: reverse;
+ -webkit-flex-direction: row-reverse;
+ -ms-flex-direction: row-reverse;
+ flex-direction: row-reverse; }
+ .large-flex-dir-column {
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: normal;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column; }
+ .large-flex-dir-column-reverse {
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: reverse;
+ -webkit-flex-direction: column-reverse;
+ -ms-flex-direction: column-reverse;
+ flex-direction: column-reverse; } }
+
+.hide {
+ display: none !important; }
+
+.invisible {
+ visibility: hidden; }
+
+.visible {
+ visibility: visible; }
+
+@media print, screen and (max-width: 39.99875em) {
+ .hide-for-small-only {
+ display: none !important; } }
+
+@media screen and (max-width: 0em), screen and (min-width: 40em) {
+ .show-for-small-only {
+ display: none !important; } }
+
+@media print, screen and (min-width: 40em) {
+ .hide-for-medium {
+ display: none !important; } }
+
+@media screen and (max-width: 39.99875em) {
+ .show-for-medium {
+ display: none !important; } }
+
+@media print, screen and (min-width: 40em) and (max-width: 63.99875em) {
+ .hide-for-medium-only {
+ display: none !important; } }
+
+@media screen and (max-width: 39.99875em), screen and (min-width: 64em) {
+ .show-for-medium-only {
+ display: none !important; } }
+
+@media print, screen and (min-width: 64em) {
+ .hide-for-large {
+ display: none !important; } }
+
+@media screen and (max-width: 63.99875em) {
+ .show-for-large {
+ display: none !important; } }
+
+@media print, screen and (min-width: 64em) and (max-width: 74.99875em) {
+ .hide-for-large-only {
+ display: none !important; } }
+
+@media screen and (max-width: 63.99875em), screen and (min-width: 75em) {
+ .show-for-large-only {
+ display: none !important; } }
+
+.show-for-sr,
+.show-on-focus {
+ position: absolute !important;
+ width: 1px !important;
+ height: 1px !important;
+ padding: 0 !important;
+ overflow: hidden !important;
+ clip: rect(0, 0, 0, 0) !important;
+ white-space: nowrap !important;
+ border: 0 !important; }
+
+.show-on-focus:active, .show-on-focus:focus {
+ position: static !important;
+ width: auto !important;
+ height: auto !important;
+ overflow: visible !important;
+ clip: auto !important;
+ white-space: normal !important; }
+
+.show-for-landscape,
+.hide-for-portrait {
+ display: block !important; }
+ @media screen and (orientation: landscape) {
+ .show-for-landscape,
+ .hide-for-portrait {
+ display: block !important; } }
+ @media screen and (orientation: portrait) {
+ .show-for-landscape,
+ .hide-for-portrait {
+ display: none !important; } }
+
+.hide-for-landscape,
+.show-for-portrait {
+ display: none !important; }
+ @media screen and (orientation: landscape) {
+ .hide-for-landscape,
+ .show-for-portrait {
+ display: none !important; } }
+ @media screen and (orientation: portrait) {
+ .hide-for-landscape,
+ .show-for-portrait {
+ display: block !important; } }
+
+/*# sourceMappingURL=foundation.css.map */
diff --git a/pkgs/csslib/third_party/foundation/foundation.min.css b/pkgs/csslib/third_party/foundation/foundation.min.css
new file mode 100644
index 0000000..dec4fbf
--- /dev/null
+++ b/pkgs/csslib/third_party/foundation/foundation.min.css
@@ -0,0 +1,2 @@
+@charset "UTF-8";@media print,screen and (min-width:40em){.reveal,.reveal.large,.reveal.small,.reveal.tiny{right:auto;left:auto;margin:0 auto}}/*! normalize.css v8.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}h1{font-size:2em;margin:.67em 0}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}.foundation-mq{font-family:"small=0em&medium=40em&large=64em&xlarge=75em&xxlarge=90em"}html{-webkit-box-sizing:border-box;box-sizing:border-box;font-size:100%}*,::after,::before{-webkit-box-sizing:inherit;box-sizing:inherit}body{margin:0;padding:0;background:#fefefe;font-family:"Helvetica Neue",Helvetica,Roboto,Arial,sans-serif;font-weight:400;line-height:1.5;color:#0a0a0a;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}img{display:inline-block;vertical-align:middle;max-width:100%;height:auto;-ms-interpolation-mode:bicubic}textarea{height:auto;min-height:50px;border-radius:0}select{-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;border-radius:0}.map_canvas embed,.map_canvas img,.map_canvas object,.mqa-display embed,.mqa-display img,.mqa-display object{max-width:none!important}button{padding:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;border:0;border-radius:0;background:0 0;line-height:1;cursor:auto}[data-whatinput=mouse] button{outline:0}pre{overflow:auto}button,input,optgroup,select,textarea{font-family:inherit}.is-visible{display:block!important}.is-hidden{display:none!important}[type=color],[type=date],[type=datetime-local],[type=datetime],[type=email],[type=month],[type=number],[type=password],[type=search],[type=tel],[type=text],[type=time],[type=url],[type=week],textarea{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;height:2.4375rem;margin:0 0 1rem;padding:.5rem;border:1px solid #cacaca;border-radius:0;background-color:#fefefe;-webkit-box-shadow:inset 0 1px 2px rgba(10,10,10,.1);box-shadow:inset 0 1px 2px rgba(10,10,10,.1);font-family:inherit;font-size:1rem;font-weight:400;line-height:1.5;color:#0a0a0a;-webkit-transition:border-color .25s ease-in-out,-webkit-box-shadow .5s;transition:border-color .25s ease-in-out,-webkit-box-shadow .5s;transition:box-shadow .5s,border-color .25s ease-in-out;transition:box-shadow .5s,border-color .25s ease-in-out,-webkit-box-shadow .5s;-webkit-appearance:none;-moz-appearance:none;appearance:none}[type=color]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=datetime]:focus,[type=email]:focus,[type=month]:focus,[type=number]:focus,[type=password]:focus,[type=search]:focus,[type=tel]:focus,[type=text]:focus,[type=time]:focus,[type=url]:focus,[type=week]:focus,textarea:focus{outline:0;border:1px solid #8a8a8a;background-color:#fefefe;-webkit-box-shadow:0 0 5px #cacaca;box-shadow:0 0 5px #cacaca;-webkit-transition:border-color .25s ease-in-out,-webkit-box-shadow .5s;transition:border-color .25s ease-in-out,-webkit-box-shadow .5s;transition:box-shadow .5s,border-color .25s ease-in-out;transition:box-shadow .5s,border-color .25s ease-in-out,-webkit-box-shadow .5s}textarea{max-width:100%}textarea[rows]{height:auto}input:disabled,input[readonly],textarea:disabled,textarea[readonly]{background-color:#e6e6e6;cursor:not-allowed}[type=button],[type=submit]{-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:0}input[type=search]{-webkit-box-sizing:border-box;box-sizing:border-box}::-webkit-input-placeholder{color:#cacaca}::-moz-placeholder{color:#cacaca}:-ms-input-placeholder{color:#cacaca}::-ms-input-placeholder{color:#cacaca}::placeholder{color:#cacaca}[type=checkbox],[type=file],[type=radio]{margin:0 0 1rem}[type=checkbox]+label,[type=radio]+label{display:inline-block;vertical-align:baseline;margin-left:.5rem;margin-right:1rem;margin-bottom:0}[type=checkbox]+label[for],[type=radio]+label[for]{cursor:pointer}label>[type=checkbox],label>[type=radio]{margin-right:.5rem}[type=file]{width:100%}label{display:block;margin:0;font-size:.875rem;font-weight:400;line-height:1.8;color:#0a0a0a}label.middle{margin:0 0 1rem;line-height:1.5;padding:.5625rem 0}.help-text{margin-top:-.5rem;font-size:.8125rem;font-style:italic;color:#0a0a0a}.input-group{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:100%;margin-bottom:1rem;-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch}.input-group>:first-child,.input-group>:first-child.input-group-button>*{border-radius:0}.input-group>:last-child,.input-group>:last-child.input-group-button>*{border-radius:0}.input-group-button,.input-group-button a,.input-group-button button,.input-group-button input,.input-group-button label,.input-group-field,.input-group-label{margin:0;white-space:nowrap}.input-group-label{padding:0 1rem;border:1px solid #cacaca;background:#e6e6e6;color:#0a0a0a;text-align:center;white-space:nowrap;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.input-group-label:first-child{border-right:0}.input-group-label:last-child{border-left:0}.input-group-field{border-radius:0;-webkit-box-flex:1;-webkit-flex:1 1 0px;-ms-flex:1 1 0px;flex:1 1 0px;min-width:0}.input-group-button{padding-top:0;padding-bottom:0;text-align:center;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto}.input-group-button a,.input-group-button button,.input-group-button input,.input-group-button label{-webkit-align-self:stretch;-ms-flex-item-align:stretch;align-self:stretch;height:auto;padding-top:0;padding-bottom:0;font-size:1rem}fieldset{margin:0;padding:0;border:0}legend{max-width:100%;margin-bottom:.5rem}.fieldset{margin:1.125rem 0;padding:1.25rem;border:1px solid #cacaca}.fieldset legend{margin:0;margin-left:-.1875rem;padding:0 .1875rem}select{height:2.4375rem;margin:0 0 1rem;padding:.5rem;-webkit-appearance:none;-moz-appearance:none;appearance:none;border:1px solid #cacaca;border-radius:0;background-color:#fefefe;font-family:inherit;font-size:1rem;font-weight:400;line-height:1.5;color:#0a0a0a;background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='32' height='24' viewBox='0 0 32 24'><polygon points='0,0 32,0 16,24' style='fill: rgb%28138, 138, 138%29'></polygon></svg>");background-origin:content-box;background-position:right -1rem center;background-repeat:no-repeat;background-size:9px 6px;padding-right:1.5rem;-webkit-transition:border-color .25s ease-in-out,-webkit-box-shadow .5s;transition:border-color .25s ease-in-out,-webkit-box-shadow .5s;transition:box-shadow .5s,border-color .25s ease-in-out;transition:box-shadow .5s,border-color .25s ease-in-out,-webkit-box-shadow .5s}@media screen and (min-width:0\0){select{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAYCAYAAACbU/80AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAIpJREFUeNrEkckNgDAMBBfRkEt0ObRBBdsGXUDgmQfK4XhH2m8czQAAy27R3tsw4Qfe2x8uOO6oYLb6GlOor3GF+swURAOmUJ+RwtEJs9WvTGEYxBXqI1MQAZhCfUQKRzDMVj+TwrAIV6jvSUEkYAr1LSkcyTBb/V+KYfX7xAeusq3sLDtGH3kEGACPWIflNZfhRQAAAABJRU5ErkJggg==)}}select:focus{outline:0;border:1px solid #8a8a8a;background-color:#fefefe;-webkit-box-shadow:0 0 5px #cacaca;box-shadow:0 0 5px #cacaca;-webkit-transition:border-color .25s ease-in-out,-webkit-box-shadow .5s;transition:border-color .25s ease-in-out,-webkit-box-shadow .5s;transition:box-shadow .5s,border-color .25s ease-in-out;transition:box-shadow .5s,border-color .25s ease-in-out,-webkit-box-shadow .5s}select:disabled{background-color:#e6e6e6;cursor:not-allowed}select::-ms-expand{display:none}select[multiple]{height:auto;background-image:none}select:not([multiple]){padding-top:0;padding-bottom:0}.is-invalid-input:not(:focus){border-color:#cc4b37;background-color:#f9ecea}.is-invalid-input:not(:focus)::-webkit-input-placeholder{color:#cc4b37}.is-invalid-input:not(:focus)::-moz-placeholder{color:#cc4b37}.is-invalid-input:not(:focus):-ms-input-placeholder{color:#cc4b37}.is-invalid-input:not(:focus)::-ms-input-placeholder{color:#cc4b37}.is-invalid-input:not(:focus)::placeholder{color:#cc4b37}.is-invalid-label{color:#cc4b37}.form-error{display:none;margin-top:-.5rem;margin-bottom:1rem;font-size:.75rem;font-weight:700;color:#cc4b37}.form-error.is-visible{display:block}blockquote,dd,div,dl,dt,form,h1,h2,h3,h4,h5,h6,li,ol,p,pre,td,th,ul{margin:0;padding:0}p{margin-bottom:1rem;font-size:inherit;line-height:1.6;text-rendering:optimizeLegibility}em,i{font-style:italic;line-height:inherit}b,strong{font-weight:700;line-height:inherit}small{font-size:80%;line-height:inherit}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:"Helvetica Neue",Helvetica,Roboto,Arial,sans-serif;font-style:normal;font-weight:400;color:inherit;text-rendering:optimizeLegibility}.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{line-height:0;color:#cacaca}.h1,h1{font-size:1.5rem;line-height:1.4;margin-top:0;margin-bottom:.5rem}.h2,h2{font-size:1.25rem;line-height:1.4;margin-top:0;margin-bottom:.5rem}.h3,h3{font-size:1.1875rem;line-height:1.4;margin-top:0;margin-bottom:.5rem}.h4,h4{font-size:1.125rem;line-height:1.4;margin-top:0;margin-bottom:.5rem}.h5,h5{font-size:1.0625rem;line-height:1.4;margin-top:0;margin-bottom:.5rem}.h6,h6{font-size:1rem;line-height:1.4;margin-top:0;margin-bottom:.5rem}@media print,screen and (min-width:40em){.h1,h1{font-size:3rem}.h2,h2{font-size:2.5rem}.h3,h3{font-size:1.9375rem}.h4,h4{font-size:1.5625rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}}a{line-height:inherit;color:#1779ba;text-decoration:none;cursor:pointer}a:focus,a:hover{color:#1468a0}a img{border:0}hr{clear:both;max-width:75rem;height:0;margin:1.25rem auto;border-top:0;border-right:0;border-bottom:1px solid #cacaca;border-left:0}dl,ol,ul{margin-bottom:1rem;list-style-position:outside;line-height:1.6}li{font-size:inherit}ul{margin-left:1.25rem;list-style-type:disc}ol{margin-left:1.25rem}ol ol,ol ul,ul ol,ul ul{margin-left:1.25rem;margin-bottom:0}dl{margin-bottom:1rem}dl dt{margin-bottom:.3rem;font-weight:700}blockquote{margin:0 0 1rem;padding:.5625rem 1.25rem 0 1.1875rem;border-left:1px solid #cacaca}blockquote,blockquote p{line-height:1.6;color:#8a8a8a}abbr,abbr[title]{border-bottom:1px dotted #0a0a0a;cursor:help;text-decoration:none}figure{margin:0}kbd{margin:0;padding:.125rem .25rem 0;background-color:#e6e6e6;font-family:Consolas,"Liberation Mono",Courier,monospace;color:#0a0a0a}.subheader{margin-top:.2rem;margin-bottom:.5rem;font-weight:400;line-height:1.4;color:#8a8a8a}.lead{font-size:125%;line-height:1.6}.stat{font-size:2.5rem;line-height:1}p+.stat{margin-top:-1rem}ol.no-bullet,ul.no-bullet{margin-left:0;list-style:none}.cite-block,cite{display:block;color:#8a8a8a;font-size:.8125rem}.cite-block:before,cite:before{content:"— "}.code-inline,code{border:1px solid #cacaca;background-color:#e6e6e6;font-family:Consolas,"Liberation Mono",Courier,monospace;font-weight:400;color:#0a0a0a;display:inline;max-width:100%;word-wrap:break-word;padding:.125rem .3125rem .0625rem}.code-block{border:1px solid #cacaca;background-color:#e6e6e6;font-family:Consolas,"Liberation Mono",Courier,monospace;font-weight:400;color:#0a0a0a;display:block;overflow:auto;white-space:pre;padding:1rem;margin-bottom:1.5rem}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}@media print,screen and (min-width:40em){.medium-text-left{text-align:left}.medium-text-right{text-align:right}.medium-text-center{text-align:center}.medium-text-justify{text-align:justify}}@media print,screen and (min-width:64em){.large-text-left{text-align:left}.large-text-right{text-align:right}.large-text-center{text-align:center}.large-text-justify{text-align:justify}}.show-for-print{display:none!important}@media print{*{background:0 0!important;color:#000!important;-webkit-print-color-adjust:economy;color-adjust:economy;-webkit-box-shadow:none!important;box-shadow:none!important;text-shadow:none!important}.show-for-print{display:block!important}.hide-for-print{display:none!important}table.show-for-print{display:table!important}thead.show-for-print{display:table-header-group!important}tbody.show-for-print{display:table-row-group!important}tr.show-for-print{display:table-row!important}td.show-for-print{display:table-cell!important}th.show-for-print{display:table-cell!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}.ir a:after,a[href^='#']:after,a[href^='javascript:']:after{content:''}abbr[title]:after{content:" (" attr(title) ")"}blockquote,pre{border:1px solid #8a8a8a;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.print-break-inside{page-break-inside:auto}}.grid-container{padding-right:.625rem;padding-left:.625rem;max-width:75rem;margin-left:auto;margin-right:auto}@media print,screen and (min-width:40em){.grid-container{padding-right:.9375rem;padding-left:.9375rem}}.grid-container.fluid{padding-right:.625rem;padding-left:.625rem;max-width:100%;margin-left:auto;margin-right:auto}@media print,screen and (min-width:40em){.grid-container.fluid{padding-right:.9375rem;padding-left:.9375rem}}.grid-container.full{padding-right:0;padding-left:0;max-width:100%;margin-left:auto;margin-right:auto}.grid-x{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-flow:row wrap;-ms-flex-flow:row wrap;flex-flow:row wrap}.cell{-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;min-height:0;min-width:0;width:100%}.cell.auto{-webkit-box-flex:1;-webkit-flex:1 1 0px;-ms-flex:1 1 0px;flex:1 1 0px}.cell.shrink{-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto}.grid-x>.auto{width:auto}.grid-x>.shrink{width:auto}.grid-x>.small-1,.grid-x>.small-10,.grid-x>.small-11,.grid-x>.small-12,.grid-x>.small-2,.grid-x>.small-3,.grid-x>.small-4,.grid-x>.small-5,.grid-x>.small-6,.grid-x>.small-7,.grid-x>.small-8,.grid-x>.small-9,.grid-x>.small-full,.grid-x>.small-shrink{-webkit-flex-basis:auto;-ms-flex-preferred-size:auto;flex-basis:auto}@media print,screen and (min-width:40em){.grid-x>.medium-1,.grid-x>.medium-10,.grid-x>.medium-11,.grid-x>.medium-12,.grid-x>.medium-2,.grid-x>.medium-3,.grid-x>.medium-4,.grid-x>.medium-5,.grid-x>.medium-6,.grid-x>.medium-7,.grid-x>.medium-8,.grid-x>.medium-9,.grid-x>.medium-full,.grid-x>.medium-shrink{-webkit-flex-basis:auto;-ms-flex-preferred-size:auto;flex-basis:auto}}@media print,screen and (min-width:64em){.grid-x>.large-1,.grid-x>.large-10,.grid-x>.large-11,.grid-x>.large-12,.grid-x>.large-2,.grid-x>.large-3,.grid-x>.large-4,.grid-x>.large-5,.grid-x>.large-6,.grid-x>.large-7,.grid-x>.large-8,.grid-x>.large-9,.grid-x>.large-full,.grid-x>.large-shrink{-webkit-flex-basis:auto;-ms-flex-preferred-size:auto;flex-basis:auto}}.grid-x>.small-1,.grid-x>.small-10,.grid-x>.small-11,.grid-x>.small-12,.grid-x>.small-2,.grid-x>.small-3,.grid-x>.small-4,.grid-x>.small-5,.grid-x>.small-6,.grid-x>.small-7,.grid-x>.small-8,.grid-x>.small-9{-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto}.grid-x>.small-1{width:8.33333%}.grid-x>.small-2{width:16.66667%}.grid-x>.small-3{width:25%}.grid-x>.small-4{width:33.33333%}.grid-x>.small-5{width:41.66667%}.grid-x>.small-6{width:50%}.grid-x>.small-7{width:58.33333%}.grid-x>.small-8{width:66.66667%}.grid-x>.small-9{width:75%}.grid-x>.small-10{width:83.33333%}.grid-x>.small-11{width:91.66667%}.grid-x>.small-12{width:100%}@media print,screen and (min-width:40em){.grid-x>.medium-auto{-webkit-box-flex:1;-webkit-flex:1 1 0px;-ms-flex:1 1 0px;flex:1 1 0px;width:auto}.grid-x>.medium-1,.grid-x>.medium-10,.grid-x>.medium-11,.grid-x>.medium-12,.grid-x>.medium-2,.grid-x>.medium-3,.grid-x>.medium-4,.grid-x>.medium-5,.grid-x>.medium-6,.grid-x>.medium-7,.grid-x>.medium-8,.grid-x>.medium-9,.grid-x>.medium-shrink{-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto}.grid-x>.medium-shrink{width:auto}.grid-x>.medium-1{width:8.33333%}.grid-x>.medium-2{width:16.66667%}.grid-x>.medium-3{width:25%}.grid-x>.medium-4{width:33.33333%}.grid-x>.medium-5{width:41.66667%}.grid-x>.medium-6{width:50%}.grid-x>.medium-7{width:58.33333%}.grid-x>.medium-8{width:66.66667%}.grid-x>.medium-9{width:75%}.grid-x>.medium-10{width:83.33333%}.grid-x>.medium-11{width:91.66667%}.grid-x>.medium-12{width:100%}}@media print,screen and (min-width:64em){.grid-x>.large-auto{-webkit-box-flex:1;-webkit-flex:1 1 0px;-ms-flex:1 1 0px;flex:1 1 0px;width:auto}.grid-x>.large-1,.grid-x>.large-10,.grid-x>.large-11,.grid-x>.large-12,.grid-x>.large-2,.grid-x>.large-3,.grid-x>.large-4,.grid-x>.large-5,.grid-x>.large-6,.grid-x>.large-7,.grid-x>.large-8,.grid-x>.large-9,.grid-x>.large-shrink{-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto}.grid-x>.large-shrink{width:auto}.grid-x>.large-1{width:8.33333%}.grid-x>.large-2{width:16.66667%}.grid-x>.large-3{width:25%}.grid-x>.large-4{width:33.33333%}.grid-x>.large-5{width:41.66667%}.grid-x>.large-6{width:50%}.grid-x>.large-7{width:58.33333%}.grid-x>.large-8{width:66.66667%}.grid-x>.large-9{width:75%}.grid-x>.large-10{width:83.33333%}.grid-x>.large-11{width:91.66667%}.grid-x>.large-12{width:100%}}.grid-margin-x:not(.grid-x)>.cell{width:auto}.grid-margin-y:not(.grid-y)>.cell{height:auto}.grid-margin-x{margin-left:-.625rem;margin-right:-.625rem}@media print,screen and (min-width:40em){.grid-margin-x{margin-left:-.9375rem;margin-right:-.9375rem}}.grid-margin-x>.cell{width:calc(100% - 1.25rem);margin-left:.625rem;margin-right:.625rem}@media print,screen and (min-width:40em){.grid-margin-x>.cell{width:calc(100% - 1.875rem);margin-left:.9375rem;margin-right:.9375rem}}.grid-margin-x>.auto{width:auto}.grid-margin-x>.shrink{width:auto}.grid-margin-x>.small-1{width:calc(8.33333% - 1.25rem)}.grid-margin-x>.small-2{width:calc(16.66667% - 1.25rem)}.grid-margin-x>.small-3{width:calc(25% - 1.25rem)}.grid-margin-x>.small-4{width:calc(33.33333% - 1.25rem)}.grid-margin-x>.small-5{width:calc(41.66667% - 1.25rem)}.grid-margin-x>.small-6{width:calc(50% - 1.25rem)}.grid-margin-x>.small-7{width:calc(58.33333% - 1.25rem)}.grid-margin-x>.small-8{width:calc(66.66667% - 1.25rem)}.grid-margin-x>.small-9{width:calc(75% - 1.25rem)}.grid-margin-x>.small-10{width:calc(83.33333% - 1.25rem)}.grid-margin-x>.small-11{width:calc(91.66667% - 1.25rem)}.grid-margin-x>.small-12{width:calc(100% - 1.25rem)}@media print,screen and (min-width:40em){.grid-margin-x>.auto{width:auto}.grid-margin-x>.shrink{width:auto}.grid-margin-x>.small-1{width:calc(8.33333% - 1.875rem)}.grid-margin-x>.small-2{width:calc(16.66667% - 1.875rem)}.grid-margin-x>.small-3{width:calc(25% - 1.875rem)}.grid-margin-x>.small-4{width:calc(33.33333% - 1.875rem)}.grid-margin-x>.small-5{width:calc(41.66667% - 1.875rem)}.grid-margin-x>.small-6{width:calc(50% - 1.875rem)}.grid-margin-x>.small-7{width:calc(58.33333% - 1.875rem)}.grid-margin-x>.small-8{width:calc(66.66667% - 1.875rem)}.grid-margin-x>.small-9{width:calc(75% - 1.875rem)}.grid-margin-x>.small-10{width:calc(83.33333% - 1.875rem)}.grid-margin-x>.small-11{width:calc(91.66667% - 1.875rem)}.grid-margin-x>.small-12{width:calc(100% - 1.875rem)}.grid-margin-x>.medium-auto{width:auto}.grid-margin-x>.medium-shrink{width:auto}.grid-margin-x>.medium-1{width:calc(8.33333% - 1.875rem)}.grid-margin-x>.medium-2{width:calc(16.66667% - 1.875rem)}.grid-margin-x>.medium-3{width:calc(25% - 1.875rem)}.grid-margin-x>.medium-4{width:calc(33.33333% - 1.875rem)}.grid-margin-x>.medium-5{width:calc(41.66667% - 1.875rem)}.grid-margin-x>.medium-6{width:calc(50% - 1.875rem)}.grid-margin-x>.medium-7{width:calc(58.33333% - 1.875rem)}.grid-margin-x>.medium-8{width:calc(66.66667% - 1.875rem)}.grid-margin-x>.medium-9{width:calc(75% - 1.875rem)}.grid-margin-x>.medium-10{width:calc(83.33333% - 1.875rem)}.grid-margin-x>.medium-11{width:calc(91.66667% - 1.875rem)}.grid-margin-x>.medium-12{width:calc(100% - 1.875rem)}}@media print,screen and (min-width:64em){.grid-margin-x>.large-auto{width:auto}.grid-margin-x>.large-shrink{width:auto}.grid-margin-x>.large-1{width:calc(8.33333% - 1.875rem)}.grid-margin-x>.large-2{width:calc(16.66667% - 1.875rem)}.grid-margin-x>.large-3{width:calc(25% - 1.875rem)}.grid-margin-x>.large-4{width:calc(33.33333% - 1.875rem)}.grid-margin-x>.large-5{width:calc(41.66667% - 1.875rem)}.grid-margin-x>.large-6{width:calc(50% - 1.875rem)}.grid-margin-x>.large-7{width:calc(58.33333% - 1.875rem)}.grid-margin-x>.large-8{width:calc(66.66667% - 1.875rem)}.grid-margin-x>.large-9{width:calc(75% - 1.875rem)}.grid-margin-x>.large-10{width:calc(83.33333% - 1.875rem)}.grid-margin-x>.large-11{width:calc(91.66667% - 1.875rem)}.grid-margin-x>.large-12{width:calc(100% - 1.875rem)}}.grid-padding-x .grid-padding-x{margin-right:-.625rem;margin-left:-.625rem}@media print,screen and (min-width:40em){.grid-padding-x .grid-padding-x{margin-right:-.9375rem;margin-left:-.9375rem}}.grid-container:not(.full)>.grid-padding-x{margin-right:-.625rem;margin-left:-.625rem}@media print,screen and (min-width:40em){.grid-container:not(.full)>.grid-padding-x{margin-right:-.9375rem;margin-left:-.9375rem}}.grid-padding-x>.cell{padding-right:.625rem;padding-left:.625rem}@media print,screen and (min-width:40em){.grid-padding-x>.cell{padding-right:.9375rem;padding-left:.9375rem}}.small-up-1>.cell{width:100%}.small-up-2>.cell{width:50%}.small-up-3>.cell{width:33.33333%}.small-up-4>.cell{width:25%}.small-up-5>.cell{width:20%}.small-up-6>.cell{width:16.66667%}.small-up-7>.cell{width:14.28571%}.small-up-8>.cell{width:12.5%}@media print,screen and (min-width:40em){.medium-up-1>.cell{width:100%}.medium-up-2>.cell{width:50%}.medium-up-3>.cell{width:33.33333%}.medium-up-4>.cell{width:25%}.medium-up-5>.cell{width:20%}.medium-up-6>.cell{width:16.66667%}.medium-up-7>.cell{width:14.28571%}.medium-up-8>.cell{width:12.5%}}@media print,screen and (min-width:64em){.large-up-1>.cell{width:100%}.large-up-2>.cell{width:50%}.large-up-3>.cell{width:33.33333%}.large-up-4>.cell{width:25%}.large-up-5>.cell{width:20%}.large-up-6>.cell{width:16.66667%}.large-up-7>.cell{width:14.28571%}.large-up-8>.cell{width:12.5%}}.grid-margin-x.small-up-1>.cell{width:calc(100% - 1.25rem)}.grid-margin-x.small-up-2>.cell{width:calc(50% - 1.25rem)}.grid-margin-x.small-up-3>.cell{width:calc(33.33333% - 1.25rem)}.grid-margin-x.small-up-4>.cell{width:calc(25% - 1.25rem)}.grid-margin-x.small-up-5>.cell{width:calc(20% - 1.25rem)}.grid-margin-x.small-up-6>.cell{width:calc(16.66667% - 1.25rem)}.grid-margin-x.small-up-7>.cell{width:calc(14.28571% - 1.25rem)}.grid-margin-x.small-up-8>.cell{width:calc(12.5% - 1.25rem)}@media print,screen and (min-width:40em){.grid-margin-x.small-up-1>.cell{width:calc(100% - 1.875rem)}.grid-margin-x.small-up-2>.cell{width:calc(50% - 1.875rem)}.grid-margin-x.small-up-3>.cell{width:calc(33.33333% - 1.875rem)}.grid-margin-x.small-up-4>.cell{width:calc(25% - 1.875rem)}.grid-margin-x.small-up-5>.cell{width:calc(20% - 1.875rem)}.grid-margin-x.small-up-6>.cell{width:calc(16.66667% - 1.875rem)}.grid-margin-x.small-up-7>.cell{width:calc(14.28571% - 1.875rem)}.grid-margin-x.small-up-8>.cell{width:calc(12.5% - 1.875rem)}.grid-margin-x.medium-up-1>.cell{width:calc(100% - 1.875rem)}.grid-margin-x.medium-up-2>.cell{width:calc(50% - 1.875rem)}.grid-margin-x.medium-up-3>.cell{width:calc(33.33333% - 1.875rem)}.grid-margin-x.medium-up-4>.cell{width:calc(25% - 1.875rem)}.grid-margin-x.medium-up-5>.cell{width:calc(20% - 1.875rem)}.grid-margin-x.medium-up-6>.cell{width:calc(16.66667% - 1.875rem)}.grid-margin-x.medium-up-7>.cell{width:calc(14.28571% - 1.875rem)}.grid-margin-x.medium-up-8>.cell{width:calc(12.5% - 1.875rem)}}@media print,screen and (min-width:64em){.grid-margin-x.large-up-1>.cell{width:calc(100% - 1.875rem)}.grid-margin-x.large-up-2>.cell{width:calc(50% - 1.875rem)}.grid-margin-x.large-up-3>.cell{width:calc(33.33333% - 1.875rem)}.grid-margin-x.large-up-4>.cell{width:calc(25% - 1.875rem)}.grid-margin-x.large-up-5>.cell{width:calc(20% - 1.875rem)}.grid-margin-x.large-up-6>.cell{width:calc(16.66667% - 1.875rem)}.grid-margin-x.large-up-7>.cell{width:calc(14.28571% - 1.875rem)}.grid-margin-x.large-up-8>.cell{width:calc(12.5% - 1.875rem)}}.small-margin-collapse{margin-right:0;margin-left:0}.small-margin-collapse>.cell{margin-right:0;margin-left:0}.small-margin-collapse>.small-1{width:8.33333%}.small-margin-collapse>.small-2{width:16.66667%}.small-margin-collapse>.small-3{width:25%}.small-margin-collapse>.small-4{width:33.33333%}.small-margin-collapse>.small-5{width:41.66667%}.small-margin-collapse>.small-6{width:50%}.small-margin-collapse>.small-7{width:58.33333%}.small-margin-collapse>.small-8{width:66.66667%}.small-margin-collapse>.small-9{width:75%}.small-margin-collapse>.small-10{width:83.33333%}.small-margin-collapse>.small-11{width:91.66667%}.small-margin-collapse>.small-12{width:100%}@media print,screen and (min-width:40em){.small-margin-collapse>.medium-1{width:8.33333%}.small-margin-collapse>.medium-2{width:16.66667%}.small-margin-collapse>.medium-3{width:25%}.small-margin-collapse>.medium-4{width:33.33333%}.small-margin-collapse>.medium-5{width:41.66667%}.small-margin-collapse>.medium-6{width:50%}.small-margin-collapse>.medium-7{width:58.33333%}.small-margin-collapse>.medium-8{width:66.66667%}.small-margin-collapse>.medium-9{width:75%}.small-margin-collapse>.medium-10{width:83.33333%}.small-margin-collapse>.medium-11{width:91.66667%}.small-margin-collapse>.medium-12{width:100%}}@media print,screen and (min-width:64em){.small-margin-collapse>.large-1{width:8.33333%}.small-margin-collapse>.large-2{width:16.66667%}.small-margin-collapse>.large-3{width:25%}.small-margin-collapse>.large-4{width:33.33333%}.small-margin-collapse>.large-5{width:41.66667%}.small-margin-collapse>.large-6{width:50%}.small-margin-collapse>.large-7{width:58.33333%}.small-margin-collapse>.large-8{width:66.66667%}.small-margin-collapse>.large-9{width:75%}.small-margin-collapse>.large-10{width:83.33333%}.small-margin-collapse>.large-11{width:91.66667%}.small-margin-collapse>.large-12{width:100%}}.small-padding-collapse{margin-right:0;margin-left:0}.small-padding-collapse>.cell{padding-right:0;padding-left:0}@media print,screen and (min-width:40em){.medium-margin-collapse{margin-right:0;margin-left:0}.medium-margin-collapse>.cell{margin-right:0;margin-left:0}}@media print,screen and (min-width:40em){.medium-margin-collapse>.small-1{width:8.33333%}.medium-margin-collapse>.small-2{width:16.66667%}.medium-margin-collapse>.small-3{width:25%}.medium-margin-collapse>.small-4{width:33.33333%}.medium-margin-collapse>.small-5{width:41.66667%}.medium-margin-collapse>.small-6{width:50%}.medium-margin-collapse>.small-7{width:58.33333%}.medium-margin-collapse>.small-8{width:66.66667%}.medium-margin-collapse>.small-9{width:75%}.medium-margin-collapse>.small-10{width:83.33333%}.medium-margin-collapse>.small-11{width:91.66667%}.medium-margin-collapse>.small-12{width:100%}}@media print,screen and (min-width:40em){.medium-margin-collapse>.medium-1{width:8.33333%}.medium-margin-collapse>.medium-2{width:16.66667%}.medium-margin-collapse>.medium-3{width:25%}.medium-margin-collapse>.medium-4{width:33.33333%}.medium-margin-collapse>.medium-5{width:41.66667%}.medium-margin-collapse>.medium-6{width:50%}.medium-margin-collapse>.medium-7{width:58.33333%}.medium-margin-collapse>.medium-8{width:66.66667%}.medium-margin-collapse>.medium-9{width:75%}.medium-margin-collapse>.medium-10{width:83.33333%}.medium-margin-collapse>.medium-11{width:91.66667%}.medium-margin-collapse>.medium-12{width:100%}}@media print,screen and (min-width:64em){.medium-margin-collapse>.large-1{width:8.33333%}.medium-margin-collapse>.large-2{width:16.66667%}.medium-margin-collapse>.large-3{width:25%}.medium-margin-collapse>.large-4{width:33.33333%}.medium-margin-collapse>.large-5{width:41.66667%}.medium-margin-collapse>.large-6{width:50%}.medium-margin-collapse>.large-7{width:58.33333%}.medium-margin-collapse>.large-8{width:66.66667%}.medium-margin-collapse>.large-9{width:75%}.medium-margin-collapse>.large-10{width:83.33333%}.medium-margin-collapse>.large-11{width:91.66667%}.medium-margin-collapse>.large-12{width:100%}}@media print,screen and (min-width:40em){.medium-padding-collapse{margin-right:0;margin-left:0}.medium-padding-collapse>.cell{padding-right:0;padding-left:0}}@media print,screen and (min-width:64em){.large-margin-collapse{margin-right:0;margin-left:0}.large-margin-collapse>.cell{margin-right:0;margin-left:0}}@media print,screen and (min-width:64em){.large-margin-collapse>.small-1{width:8.33333%}.large-margin-collapse>.small-2{width:16.66667%}.large-margin-collapse>.small-3{width:25%}.large-margin-collapse>.small-4{width:33.33333%}.large-margin-collapse>.small-5{width:41.66667%}.large-margin-collapse>.small-6{width:50%}.large-margin-collapse>.small-7{width:58.33333%}.large-margin-collapse>.small-8{width:66.66667%}.large-margin-collapse>.small-9{width:75%}.large-margin-collapse>.small-10{width:83.33333%}.large-margin-collapse>.small-11{width:91.66667%}.large-margin-collapse>.small-12{width:100%}}@media print,screen and (min-width:64em){.large-margin-collapse>.medium-1{width:8.33333%}.large-margin-collapse>.medium-2{width:16.66667%}.large-margin-collapse>.medium-3{width:25%}.large-margin-collapse>.medium-4{width:33.33333%}.large-margin-collapse>.medium-5{width:41.66667%}.large-margin-collapse>.medium-6{width:50%}.large-margin-collapse>.medium-7{width:58.33333%}.large-margin-collapse>.medium-8{width:66.66667%}.large-margin-collapse>.medium-9{width:75%}.large-margin-collapse>.medium-10{width:83.33333%}.large-margin-collapse>.medium-11{width:91.66667%}.large-margin-collapse>.medium-12{width:100%}}@media print,screen and (min-width:64em){.large-margin-collapse>.large-1{width:8.33333%}.large-margin-collapse>.large-2{width:16.66667%}.large-margin-collapse>.large-3{width:25%}.large-margin-collapse>.large-4{width:33.33333%}.large-margin-collapse>.large-5{width:41.66667%}.large-margin-collapse>.large-6{width:50%}.large-margin-collapse>.large-7{width:58.33333%}.large-margin-collapse>.large-8{width:66.66667%}.large-margin-collapse>.large-9{width:75%}.large-margin-collapse>.large-10{width:83.33333%}.large-margin-collapse>.large-11{width:91.66667%}.large-margin-collapse>.large-12{width:100%}}@media print,screen and (min-width:64em){.large-padding-collapse{margin-right:0;margin-left:0}.large-padding-collapse>.cell{padding-right:0;padding-left:0}}.small-offset-0{margin-left:0}.grid-margin-x>.small-offset-0{margin-left:calc(0% + 1.25rem / 2)}.small-offset-1{margin-left:8.33333%}.grid-margin-x>.small-offset-1{margin-left:calc(8.33333% + 1.25rem / 2)}.small-offset-2{margin-left:16.66667%}.grid-margin-x>.small-offset-2{margin-left:calc(16.66667% + 1.25rem / 2)}.small-offset-3{margin-left:25%}.grid-margin-x>.small-offset-3{margin-left:calc(25% + 1.25rem / 2)}.small-offset-4{margin-left:33.33333%}.grid-margin-x>.small-offset-4{margin-left:calc(33.33333% + 1.25rem / 2)}.small-offset-5{margin-left:41.66667%}.grid-margin-x>.small-offset-5{margin-left:calc(41.66667% + 1.25rem / 2)}.small-offset-6{margin-left:50%}.grid-margin-x>.small-offset-6{margin-left:calc(50% + 1.25rem / 2)}.small-offset-7{margin-left:58.33333%}.grid-margin-x>.small-offset-7{margin-left:calc(58.33333% + 1.25rem / 2)}.small-offset-8{margin-left:66.66667%}.grid-margin-x>.small-offset-8{margin-left:calc(66.66667% + 1.25rem / 2)}.small-offset-9{margin-left:75%}.grid-margin-x>.small-offset-9{margin-left:calc(75% + 1.25rem / 2)}.small-offset-10{margin-left:83.33333%}.grid-margin-x>.small-offset-10{margin-left:calc(83.33333% + 1.25rem / 2)}.small-offset-11{margin-left:91.66667%}.grid-margin-x>.small-offset-11{margin-left:calc(91.66667% + 1.25rem / 2)}@media print,screen and (min-width:40em){.medium-offset-0{margin-left:0}.grid-margin-x>.medium-offset-0{margin-left:calc(0% + 1.875rem / 2)}.medium-offset-1{margin-left:8.33333%}.grid-margin-x>.medium-offset-1{margin-left:calc(8.33333% + 1.875rem / 2)}.medium-offset-2{margin-left:16.66667%}.grid-margin-x>.medium-offset-2{margin-left:calc(16.66667% + 1.875rem / 2)}.medium-offset-3{margin-left:25%}.grid-margin-x>.medium-offset-3{margin-left:calc(25% + 1.875rem / 2)}.medium-offset-4{margin-left:33.33333%}.grid-margin-x>.medium-offset-4{margin-left:calc(33.33333% + 1.875rem / 2)}.medium-offset-5{margin-left:41.66667%}.grid-margin-x>.medium-offset-5{margin-left:calc(41.66667% + 1.875rem / 2)}.medium-offset-6{margin-left:50%}.grid-margin-x>.medium-offset-6{margin-left:calc(50% + 1.875rem / 2)}.medium-offset-7{margin-left:58.33333%}.grid-margin-x>.medium-offset-7{margin-left:calc(58.33333% + 1.875rem / 2)}.medium-offset-8{margin-left:66.66667%}.grid-margin-x>.medium-offset-8{margin-left:calc(66.66667% + 1.875rem / 2)}.medium-offset-9{margin-left:75%}.grid-margin-x>.medium-offset-9{margin-left:calc(75% + 1.875rem / 2)}.medium-offset-10{margin-left:83.33333%}.grid-margin-x>.medium-offset-10{margin-left:calc(83.33333% + 1.875rem / 2)}.medium-offset-11{margin-left:91.66667%}.grid-margin-x>.medium-offset-11{margin-left:calc(91.66667% + 1.875rem / 2)}}@media print,screen and (min-width:64em){.large-offset-0{margin-left:0}.grid-margin-x>.large-offset-0{margin-left:calc(0% + 1.875rem / 2)}.large-offset-1{margin-left:8.33333%}.grid-margin-x>.large-offset-1{margin-left:calc(8.33333% + 1.875rem / 2)}.large-offset-2{margin-left:16.66667%}.grid-margin-x>.large-offset-2{margin-left:calc(16.66667% + 1.875rem / 2)}.large-offset-3{margin-left:25%}.grid-margin-x>.large-offset-3{margin-left:calc(25% + 1.875rem / 2)}.large-offset-4{margin-left:33.33333%}.grid-margin-x>.large-offset-4{margin-left:calc(33.33333% + 1.875rem / 2)}.large-offset-5{margin-left:41.66667%}.grid-margin-x>.large-offset-5{margin-left:calc(41.66667% + 1.875rem / 2)}.large-offset-6{margin-left:50%}.grid-margin-x>.large-offset-6{margin-left:calc(50% + 1.875rem / 2)}.large-offset-7{margin-left:58.33333%}.grid-margin-x>.large-offset-7{margin-left:calc(58.33333% + 1.875rem / 2)}.large-offset-8{margin-left:66.66667%}.grid-margin-x>.large-offset-8{margin-left:calc(66.66667% + 1.875rem / 2)}.large-offset-9{margin-left:75%}.grid-margin-x>.large-offset-9{margin-left:calc(75% + 1.875rem / 2)}.large-offset-10{margin-left:83.33333%}.grid-margin-x>.large-offset-10{margin-left:calc(83.33333% + 1.875rem / 2)}.large-offset-11{margin-left:91.66667%}.grid-margin-x>.large-offset-11{margin-left:calc(91.66667% + 1.875rem / 2)}}.grid-y{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-flow:column nowrap;-ms-flex-flow:column nowrap;flex-flow:column nowrap}.grid-y>.cell{height:auto;max-height:none}.grid-y>.auto{height:auto}.grid-y>.shrink{height:auto}.grid-y>.small-1,.grid-y>.small-10,.grid-y>.small-11,.grid-y>.small-12,.grid-y>.small-2,.grid-y>.small-3,.grid-y>.small-4,.grid-y>.small-5,.grid-y>.small-6,.grid-y>.small-7,.grid-y>.small-8,.grid-y>.small-9,.grid-y>.small-full,.grid-y>.small-shrink{-webkit-flex-basis:auto;-ms-flex-preferred-size:auto;flex-basis:auto}@media print,screen and (min-width:40em){.grid-y>.medium-1,.grid-y>.medium-10,.grid-y>.medium-11,.grid-y>.medium-12,.grid-y>.medium-2,.grid-y>.medium-3,.grid-y>.medium-4,.grid-y>.medium-5,.grid-y>.medium-6,.grid-y>.medium-7,.grid-y>.medium-8,.grid-y>.medium-9,.grid-y>.medium-full,.grid-y>.medium-shrink{-webkit-flex-basis:auto;-ms-flex-preferred-size:auto;flex-basis:auto}}@media print,screen and (min-width:64em){.grid-y>.large-1,.grid-y>.large-10,.grid-y>.large-11,.grid-y>.large-12,.grid-y>.large-2,.grid-y>.large-3,.grid-y>.large-4,.grid-y>.large-5,.grid-y>.large-6,.grid-y>.large-7,.grid-y>.large-8,.grid-y>.large-9,.grid-y>.large-full,.grid-y>.large-shrink{-webkit-flex-basis:auto;-ms-flex-preferred-size:auto;flex-basis:auto}}.grid-y>.small-1,.grid-y>.small-10,.grid-y>.small-11,.grid-y>.small-12,.grid-y>.small-2,.grid-y>.small-3,.grid-y>.small-4,.grid-y>.small-5,.grid-y>.small-6,.grid-y>.small-7,.grid-y>.small-8,.grid-y>.small-9{-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto}.grid-y>.small-1{height:8.33333%}.grid-y>.small-2{height:16.66667%}.grid-y>.small-3{height:25%}.grid-y>.small-4{height:33.33333%}.grid-y>.small-5{height:41.66667%}.grid-y>.small-6{height:50%}.grid-y>.small-7{height:58.33333%}.grid-y>.small-8{height:66.66667%}.grid-y>.small-9{height:75%}.grid-y>.small-10{height:83.33333%}.grid-y>.small-11{height:91.66667%}.grid-y>.small-12{height:100%}@media print,screen and (min-width:40em){.grid-y>.medium-auto{-webkit-box-flex:1;-webkit-flex:1 1 0px;-ms-flex:1 1 0px;flex:1 1 0px;height:auto}.grid-y>.medium-1,.grid-y>.medium-10,.grid-y>.medium-11,.grid-y>.medium-12,.grid-y>.medium-2,.grid-y>.medium-3,.grid-y>.medium-4,.grid-y>.medium-5,.grid-y>.medium-6,.grid-y>.medium-7,.grid-y>.medium-8,.grid-y>.medium-9,.grid-y>.medium-shrink{-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto}.grid-y>.medium-shrink{height:auto}.grid-y>.medium-1{height:8.33333%}.grid-y>.medium-2{height:16.66667%}.grid-y>.medium-3{height:25%}.grid-y>.medium-4{height:33.33333%}.grid-y>.medium-5{height:41.66667%}.grid-y>.medium-6{height:50%}.grid-y>.medium-7{height:58.33333%}.grid-y>.medium-8{height:66.66667%}.grid-y>.medium-9{height:75%}.grid-y>.medium-10{height:83.33333%}.grid-y>.medium-11{height:91.66667%}.grid-y>.medium-12{height:100%}}@media print,screen and (min-width:64em){.grid-y>.large-auto{-webkit-box-flex:1;-webkit-flex:1 1 0px;-ms-flex:1 1 0px;flex:1 1 0px;height:auto}.grid-y>.large-1,.grid-y>.large-10,.grid-y>.large-11,.grid-y>.large-12,.grid-y>.large-2,.grid-y>.large-3,.grid-y>.large-4,.grid-y>.large-5,.grid-y>.large-6,.grid-y>.large-7,.grid-y>.large-8,.grid-y>.large-9,.grid-y>.large-shrink{-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto}.grid-y>.large-shrink{height:auto}.grid-y>.large-1{height:8.33333%}.grid-y>.large-2{height:16.66667%}.grid-y>.large-3{height:25%}.grid-y>.large-4{height:33.33333%}.grid-y>.large-5{height:41.66667%}.grid-y>.large-6{height:50%}.grid-y>.large-7{height:58.33333%}.grid-y>.large-8{height:66.66667%}.grid-y>.large-9{height:75%}.grid-y>.large-10{height:83.33333%}.grid-y>.large-11{height:91.66667%}.grid-y>.large-12{height:100%}}.grid-padding-y .grid-padding-y{margin-top:-.625rem;margin-bottom:-.625rem}@media print,screen and (min-width:40em){.grid-padding-y .grid-padding-y{margin-top:-.9375rem;margin-bottom:-.9375rem}}.grid-padding-y>.cell{padding-top:.625rem;padding-bottom:.625rem}@media print,screen and (min-width:40em){.grid-padding-y>.cell{padding-top:.9375rem;padding-bottom:.9375rem}}.grid-margin-y{margin-top:-.625rem;margin-bottom:-.625rem}@media print,screen and (min-width:40em){.grid-margin-y{margin-top:-.9375rem;margin-bottom:-.9375rem}}.grid-margin-y>.cell{height:calc(100% - 1.25rem);margin-top:.625rem;margin-bottom:.625rem}@media print,screen and (min-width:40em){.grid-margin-y>.cell{height:calc(100% - 1.875rem);margin-top:.9375rem;margin-bottom:.9375rem}}.grid-margin-y>.auto{height:auto}.grid-margin-y>.shrink{height:auto}.grid-margin-y>.small-1{height:calc(8.33333% - 1.25rem)}.grid-margin-y>.small-2{height:calc(16.66667% - 1.25rem)}.grid-margin-y>.small-3{height:calc(25% - 1.25rem)}.grid-margin-y>.small-4{height:calc(33.33333% - 1.25rem)}.grid-margin-y>.small-5{height:calc(41.66667% - 1.25rem)}.grid-margin-y>.small-6{height:calc(50% - 1.25rem)}.grid-margin-y>.small-7{height:calc(58.33333% - 1.25rem)}.grid-margin-y>.small-8{height:calc(66.66667% - 1.25rem)}.grid-margin-y>.small-9{height:calc(75% - 1.25rem)}.grid-margin-y>.small-10{height:calc(83.33333% - 1.25rem)}.grid-margin-y>.small-11{height:calc(91.66667% - 1.25rem)}.grid-margin-y>.small-12{height:calc(100% - 1.25rem)}@media print,screen and (min-width:40em){.grid-margin-y>.auto{height:auto}.grid-margin-y>.shrink{height:auto}.grid-margin-y>.small-1{height:calc(8.33333% - 1.875rem)}.grid-margin-y>.small-2{height:calc(16.66667% - 1.875rem)}.grid-margin-y>.small-3{height:calc(25% - 1.875rem)}.grid-margin-y>.small-4{height:calc(33.33333% - 1.875rem)}.grid-margin-y>.small-5{height:calc(41.66667% - 1.875rem)}.grid-margin-y>.small-6{height:calc(50% - 1.875rem)}.grid-margin-y>.small-7{height:calc(58.33333% - 1.875rem)}.grid-margin-y>.small-8{height:calc(66.66667% - 1.875rem)}.grid-margin-y>.small-9{height:calc(75% - 1.875rem)}.grid-margin-y>.small-10{height:calc(83.33333% - 1.875rem)}.grid-margin-y>.small-11{height:calc(91.66667% - 1.875rem)}.grid-margin-y>.small-12{height:calc(100% - 1.875rem)}.grid-margin-y>.medium-auto{height:auto}.grid-margin-y>.medium-shrink{height:auto}.grid-margin-y>.medium-1{height:calc(8.33333% - 1.875rem)}.grid-margin-y>.medium-2{height:calc(16.66667% - 1.875rem)}.grid-margin-y>.medium-3{height:calc(25% - 1.875rem)}.grid-margin-y>.medium-4{height:calc(33.33333% - 1.875rem)}.grid-margin-y>.medium-5{height:calc(41.66667% - 1.875rem)}.grid-margin-y>.medium-6{height:calc(50% - 1.875rem)}.grid-margin-y>.medium-7{height:calc(58.33333% - 1.875rem)}.grid-margin-y>.medium-8{height:calc(66.66667% - 1.875rem)}.grid-margin-y>.medium-9{height:calc(75% - 1.875rem)}.grid-margin-y>.medium-10{height:calc(83.33333% - 1.875rem)}.grid-margin-y>.medium-11{height:calc(91.66667% - 1.875rem)}.grid-margin-y>.medium-12{height:calc(100% - 1.875rem)}}@media print,screen and (min-width:64em){.grid-margin-y>.large-auto{height:auto}.grid-margin-y>.large-shrink{height:auto}.grid-margin-y>.large-1{height:calc(8.33333% - 1.875rem)}.grid-margin-y>.large-2{height:calc(16.66667% - 1.875rem)}.grid-margin-y>.large-3{height:calc(25% - 1.875rem)}.grid-margin-y>.large-4{height:calc(33.33333% - 1.875rem)}.grid-margin-y>.large-5{height:calc(41.66667% - 1.875rem)}.grid-margin-y>.large-6{height:calc(50% - 1.875rem)}.grid-margin-y>.large-7{height:calc(58.33333% - 1.875rem)}.grid-margin-y>.large-8{height:calc(66.66667% - 1.875rem)}.grid-margin-y>.large-9{height:calc(75% - 1.875rem)}.grid-margin-y>.large-10{height:calc(83.33333% - 1.875rem)}.grid-margin-y>.large-11{height:calc(91.66667% - 1.875rem)}.grid-margin-y>.large-12{height:calc(100% - 1.875rem)}}.grid-frame{overflow:hidden;position:relative;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;width:100vw}.cell .grid-frame{width:100%}.cell-block{overflow-x:auto;max-width:100%;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.cell-block-y{overflow-y:auto;max-height:100%;min-height:100%;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.cell-block-container{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;max-height:100%}.cell-block-container>.grid-x{max-height:100%;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap}@media print,screen and (min-width:40em){.medium-grid-frame{overflow:hidden;position:relative;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;width:100vw}.cell .medium-grid-frame{width:100%}.medium-cell-block{overflow-x:auto;max-width:100%;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.medium-cell-block-container{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;max-height:100%}.medium-cell-block-container>.grid-x{max-height:100%;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap}.medium-cell-block-y{overflow-y:auto;max-height:100%;min-height:100%;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}}@media print,screen and (min-width:64em){.large-grid-frame{overflow:hidden;position:relative;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;width:100vw}.cell .large-grid-frame{width:100%}.large-cell-block{overflow-x:auto;max-width:100%;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.large-cell-block-container{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;max-height:100%}.large-cell-block-container>.grid-x{max-height:100%;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap}.large-cell-block-y{overflow-y:auto;max-height:100%;min-height:100%;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}}.grid-y.grid-frame{width:auto;overflow:hidden;position:relative;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;height:100vh}@media print,screen and (min-width:40em){.grid-y.medium-grid-frame{width:auto;overflow:hidden;position:relative;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;height:100vh}}@media print,screen and (min-width:64em){.grid-y.large-grid-frame{width:auto;overflow:hidden;position:relative;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;height:100vh}}.cell .grid-y.grid-frame{height:100%}@media print,screen and (min-width:40em){.cell .grid-y.medium-grid-frame{height:100%}}@media print,screen and (min-width:64em){.cell .grid-y.large-grid-frame{height:100%}}.grid-margin-y{margin-top:-.625rem;margin-bottom:-.625rem}@media print,screen and (min-width:40em){.grid-margin-y{margin-top:-.9375rem;margin-bottom:-.9375rem}}.grid-margin-y>.cell{height:calc(100% - 1.25rem);margin-top:.625rem;margin-bottom:.625rem}@media print,screen and (min-width:40em){.grid-margin-y>.cell{height:calc(100% - 1.875rem);margin-top:.9375rem;margin-bottom:.9375rem}}.grid-margin-y>.auto{height:auto}.grid-margin-y>.shrink{height:auto}.grid-margin-y>.small-1{height:calc(8.33333% - 1.25rem)}.grid-margin-y>.small-2{height:calc(16.66667% - 1.25rem)}.grid-margin-y>.small-3{height:calc(25% - 1.25rem)}.grid-margin-y>.small-4{height:calc(33.33333% - 1.25rem)}.grid-margin-y>.small-5{height:calc(41.66667% - 1.25rem)}.grid-margin-y>.small-6{height:calc(50% - 1.25rem)}.grid-margin-y>.small-7{height:calc(58.33333% - 1.25rem)}.grid-margin-y>.small-8{height:calc(66.66667% - 1.25rem)}.grid-margin-y>.small-9{height:calc(75% - 1.25rem)}.grid-margin-y>.small-10{height:calc(83.33333% - 1.25rem)}.grid-margin-y>.small-11{height:calc(91.66667% - 1.25rem)}.grid-margin-y>.small-12{height:calc(100% - 1.25rem)}@media print,screen and (min-width:40em){.grid-margin-y>.auto{height:auto}.grid-margin-y>.shrink{height:auto}.grid-margin-y>.small-1{height:calc(8.33333% - 1.875rem)}.grid-margin-y>.small-2{height:calc(16.66667% - 1.875rem)}.grid-margin-y>.small-3{height:calc(25% - 1.875rem)}.grid-margin-y>.small-4{height:calc(33.33333% - 1.875rem)}.grid-margin-y>.small-5{height:calc(41.66667% - 1.875rem)}.grid-margin-y>.small-6{height:calc(50% - 1.875rem)}.grid-margin-y>.small-7{height:calc(58.33333% - 1.875rem)}.grid-margin-y>.small-8{height:calc(66.66667% - 1.875rem)}.grid-margin-y>.small-9{height:calc(75% - 1.875rem)}.grid-margin-y>.small-10{height:calc(83.33333% - 1.875rem)}.grid-margin-y>.small-11{height:calc(91.66667% - 1.875rem)}.grid-margin-y>.small-12{height:calc(100% - 1.875rem)}.grid-margin-y>.medium-auto{height:auto}.grid-margin-y>.medium-shrink{height:auto}.grid-margin-y>.medium-1{height:calc(8.33333% - 1.875rem)}.grid-margin-y>.medium-2{height:calc(16.66667% - 1.875rem)}.grid-margin-y>.medium-3{height:calc(25% - 1.875rem)}.grid-margin-y>.medium-4{height:calc(33.33333% - 1.875rem)}.grid-margin-y>.medium-5{height:calc(41.66667% - 1.875rem)}.grid-margin-y>.medium-6{height:calc(50% - 1.875rem)}.grid-margin-y>.medium-7{height:calc(58.33333% - 1.875rem)}.grid-margin-y>.medium-8{height:calc(66.66667% - 1.875rem)}.grid-margin-y>.medium-9{height:calc(75% - 1.875rem)}.grid-margin-y>.medium-10{height:calc(83.33333% - 1.875rem)}.grid-margin-y>.medium-11{height:calc(91.66667% - 1.875rem)}.grid-margin-y>.medium-12{height:calc(100% - 1.875rem)}}@media print,screen and (min-width:64em){.grid-margin-y>.large-auto{height:auto}.grid-margin-y>.large-shrink{height:auto}.grid-margin-y>.large-1{height:calc(8.33333% - 1.875rem)}.grid-margin-y>.large-2{height:calc(16.66667% - 1.875rem)}.grid-margin-y>.large-3{height:calc(25% - 1.875rem)}.grid-margin-y>.large-4{height:calc(33.33333% - 1.875rem)}.grid-margin-y>.large-5{height:calc(41.66667% - 1.875rem)}.grid-margin-y>.large-6{height:calc(50% - 1.875rem)}.grid-margin-y>.large-7{height:calc(58.33333% - 1.875rem)}.grid-margin-y>.large-8{height:calc(66.66667% - 1.875rem)}.grid-margin-y>.large-9{height:calc(75% - 1.875rem)}.grid-margin-y>.large-10{height:calc(83.33333% - 1.875rem)}.grid-margin-y>.large-11{height:calc(91.66667% - 1.875rem)}.grid-margin-y>.large-12{height:calc(100% - 1.875rem)}}.grid-frame.grid-margin-y{height:calc(100vh + 1.25rem)}@media print,screen and (min-width:40em){.grid-frame.grid-margin-y{height:calc(100vh + 1.875rem)}}@media print,screen and (min-width:64em){.grid-frame.grid-margin-y{height:calc(100vh + 1.875rem)}}@media print,screen and (min-width:40em){.grid-margin-y.medium-grid-frame{height:calc(100vh + 1.875rem)}}@media print,screen and (min-width:64em){.grid-margin-y.large-grid-frame{height:calc(100vh + 1.875rem)}}.button{display:inline-block;vertical-align:middle;margin:0 0 1rem 0;padding:.85em 1em;border:1px solid transparent;border-radius:0;-webkit-transition:background-color .25s ease-out,color .25s ease-out;transition:background-color .25s ease-out,color .25s ease-out;font-family:inherit;font-size:.9rem;-webkit-appearance:none;line-height:1;text-align:center;cursor:pointer}[data-whatinput=mouse] .button{outline:0}.button.tiny{font-size:.6rem}.button.small{font-size:.75rem}.button.large{font-size:1.25rem}.button.expanded{display:block;width:100%;margin-right:0;margin-left:0}.button,.button.disabled,.button.disabled:focus,.button.disabled:hover,.button[disabled],.button[disabled]:focus,.button[disabled]:hover{background-color:#1779ba;color:#fefefe}.button:focus,.button:hover{background-color:#14679e;color:#fefefe}.button.primary,.button.primary.disabled,.button.primary.disabled:focus,.button.primary.disabled:hover,.button.primary[disabled],.button.primary[disabled]:focus,.button.primary[disabled]:hover{background-color:#1779ba;color:#fefefe}.button.primary:focus,.button.primary:hover{background-color:#126195;color:#fefefe}.button.secondary,.button.secondary.disabled,.button.secondary.disabled:focus,.button.secondary.disabled:hover,.button.secondary[disabled],.button.secondary[disabled]:focus,.button.secondary[disabled]:hover{background-color:#767676;color:#fefefe}.button.secondary:focus,.button.secondary:hover{background-color:#5e5e5e;color:#fefefe}.button.success,.button.success.disabled,.button.success.disabled:focus,.button.success.disabled:hover,.button.success[disabled],.button.success[disabled]:focus,.button.success[disabled]:hover{background-color:#3adb76;color:#0a0a0a}.button.success:focus,.button.success:hover{background-color:#22bb5b;color:#0a0a0a}.button.warning,.button.warning.disabled,.button.warning.disabled:focus,.button.warning.disabled:hover,.button.warning[disabled],.button.warning[disabled]:focus,.button.warning[disabled]:hover{background-color:#ffae00;color:#0a0a0a}.button.warning:focus,.button.warning:hover{background-color:#cc8b00;color:#0a0a0a}.button.alert,.button.alert.disabled,.button.alert.disabled:focus,.button.alert.disabled:hover,.button.alert[disabled],.button.alert[disabled]:focus,.button.alert[disabled]:hover{background-color:#cc4b37;color:#fefefe}.button.alert:focus,.button.alert:hover{background-color:#a53b2a;color:#fefefe}.button.hollow,.button.hollow.disabled,.button.hollow.disabled:focus,.button.hollow.disabled:hover,.button.hollow:focus,.button.hollow:hover,.button.hollow[disabled],.button.hollow[disabled]:focus,.button.hollow[disabled]:hover{background-color:transparent}.button.hollow,.button.hollow.disabled,.button.hollow.disabled:focus,.button.hollow.disabled:hover,.button.hollow[disabled],.button.hollow[disabled]:focus,.button.hollow[disabled]:hover{border:1px solid #1779ba;color:#1779ba}.button.hollow:focus,.button.hollow:hover{border-color:#0c3d5d;color:#0c3d5d}.button.hollow.primary,.button.hollow.primary.disabled,.button.hollow.primary.disabled:focus,.button.hollow.primary.disabled:hover,.button.hollow.primary[disabled],.button.hollow.primary[disabled]:focus,.button.hollow.primary[disabled]:hover{border:1px solid #1779ba;color:#1779ba}.button.hollow.primary:focus,.button.hollow.primary:hover{border-color:#0c3d5d;color:#0c3d5d}.button.hollow.secondary,.button.hollow.secondary.disabled,.button.hollow.secondary.disabled:focus,.button.hollow.secondary.disabled:hover,.button.hollow.secondary[disabled],.button.hollow.secondary[disabled]:focus,.button.hollow.secondary[disabled]:hover{border:1px solid #767676;color:#767676}.button.hollow.secondary:focus,.button.hollow.secondary:hover{border-color:#3b3b3b;color:#3b3b3b}.button.hollow.success,.button.hollow.success.disabled,.button.hollow.success.disabled:focus,.button.hollow.success.disabled:hover,.button.hollow.success[disabled],.button.hollow.success[disabled]:focus,.button.hollow.success[disabled]:hover{border:1px solid #3adb76;color:#3adb76}.button.hollow.success:focus,.button.hollow.success:hover{border-color:#157539;color:#157539}.button.hollow.warning,.button.hollow.warning.disabled,.button.hollow.warning.disabled:focus,.button.hollow.warning.disabled:hover,.button.hollow.warning[disabled],.button.hollow.warning[disabled]:focus,.button.hollow.warning[disabled]:hover{border:1px solid #ffae00;color:#ffae00}.button.hollow.warning:focus,.button.hollow.warning:hover{border-color:#805700;color:#805700}.button.hollow.alert,.button.hollow.alert.disabled,.button.hollow.alert.disabled:focus,.button.hollow.alert.disabled:hover,.button.hollow.alert[disabled],.button.hollow.alert[disabled]:focus,.button.hollow.alert[disabled]:hover{border:1px solid #cc4b37;color:#cc4b37}.button.hollow.alert:focus,.button.hollow.alert:hover{border-color:#67251a;color:#67251a}.button.clear,.button.clear.disabled,.button.clear.disabled:focus,.button.clear.disabled:hover,.button.clear:focus,.button.clear:hover,.button.clear[disabled],.button.clear[disabled]:focus,.button.clear[disabled]:hover{border-color:transparent;background-color:transparent}.button.clear,.button.clear.disabled,.button.clear.disabled:focus,.button.clear.disabled:hover,.button.clear[disabled],.button.clear[disabled]:focus,.button.clear[disabled]:hover{color:#1779ba}.button.clear:focus,.button.clear:hover{color:#0c3d5d}.button.clear.primary,.button.clear.primary.disabled,.button.clear.primary.disabled:focus,.button.clear.primary.disabled:hover,.button.clear.primary[disabled],.button.clear.primary[disabled]:focus,.button.clear.primary[disabled]:hover{color:#1779ba}.button.clear.primary:focus,.button.clear.primary:hover{color:#0c3d5d}.button.clear.secondary,.button.clear.secondary.disabled,.button.clear.secondary.disabled:focus,.button.clear.secondary.disabled:hover,.button.clear.secondary[disabled],.button.clear.secondary[disabled]:focus,.button.clear.secondary[disabled]:hover{color:#767676}.button.clear.secondary:focus,.button.clear.secondary:hover{color:#3b3b3b}.button.clear.success,.button.clear.success.disabled,.button.clear.success.disabled:focus,.button.clear.success.disabled:hover,.button.clear.success[disabled],.button.clear.success[disabled]:focus,.button.clear.success[disabled]:hover{color:#3adb76}.button.clear.success:focus,.button.clear.success:hover{color:#157539}.button.clear.warning,.button.clear.warning.disabled,.button.clear.warning.disabled:focus,.button.clear.warning.disabled:hover,.button.clear.warning[disabled],.button.clear.warning[disabled]:focus,.button.clear.warning[disabled]:hover{color:#ffae00}.button.clear.warning:focus,.button.clear.warning:hover{color:#805700}.button.clear.alert,.button.clear.alert.disabled,.button.clear.alert.disabled:focus,.button.clear.alert.disabled:hover,.button.clear.alert[disabled],.button.clear.alert[disabled]:focus,.button.clear.alert[disabled]:hover{color:#cc4b37}.button.clear.alert:focus,.button.clear.alert:hover{color:#67251a}.button.disabled,.button[disabled]{opacity:.25;cursor:not-allowed}.button.dropdown::after{display:block;width:0;height:0;border-style:solid;border-width:.4em;content:'';border-bottom-width:0;border-color:#fefefe transparent transparent;position:relative;top:.4em;display:inline-block;float:right;margin-left:1em}.button.dropdown.clear::after,.button.dropdown.hollow::after{border-top-color:#1779ba}.button.dropdown.clear.primary::after,.button.dropdown.hollow.primary::after{border-top-color:#1779ba}.button.dropdown.clear.secondary::after,.button.dropdown.hollow.secondary::after{border-top-color:#767676}.button.dropdown.clear.success::after,.button.dropdown.hollow.success::after{border-top-color:#3adb76}.button.dropdown.clear.warning::after,.button.dropdown.hollow.warning::after{border-top-color:#ffae00}.button.dropdown.clear.alert::after,.button.dropdown.hollow.alert::after{border-top-color:#cc4b37}.button.arrow-only::after{top:-.1em;float:none;margin-left:0}a.button:focus,a.button:hover{text-decoration:none}.button-group{margin-bottom:1rem;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.button-group::after,.button-group::before{display:table;content:' ';-webkit-flex-basis:0;-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-ordinal-group:2;-webkit-order:1;-ms-flex-order:1;order:1}.button-group::after{clear:both}.button-group::after,.button-group::before{display:none}.button-group .button{margin:0;margin-right:1px;margin-bottom:1px;font-size:.9rem;-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto}.button-group .button:last-child{margin-right:0}.button-group.tiny .button{font-size:.6rem}.button-group.small .button{font-size:.75rem}.button-group.large .button{font-size:1.25rem}.button-group.expanded .button{-webkit-box-flex:1;-webkit-flex:1 1 0px;-ms-flex:1 1 0px;flex:1 1 0px}.button-group.primary .button,.button-group.primary .button.disabled,.button-group.primary .button.disabled:focus,.button-group.primary .button.disabled:hover,.button-group.primary .button[disabled],.button-group.primary .button[disabled]:focus,.button-group.primary .button[disabled]:hover{background-color:#1779ba;color:#fefefe}.button-group.primary .button:focus,.button-group.primary .button:hover{background-color:#126195;color:#fefefe}.button-group.secondary .button,.button-group.secondary .button.disabled,.button-group.secondary .button.disabled:focus,.button-group.secondary .button.disabled:hover,.button-group.secondary .button[disabled],.button-group.secondary .button[disabled]:focus,.button-group.secondary .button[disabled]:hover{background-color:#767676;color:#fefefe}.button-group.secondary .button:focus,.button-group.secondary .button:hover{background-color:#5e5e5e;color:#fefefe}.button-group.success .button,.button-group.success .button.disabled,.button-group.success .button.disabled:focus,.button-group.success .button.disabled:hover,.button-group.success .button[disabled],.button-group.success .button[disabled]:focus,.button-group.success .button[disabled]:hover{background-color:#3adb76;color:#0a0a0a}.button-group.success .button:focus,.button-group.success .button:hover{background-color:#22bb5b;color:#0a0a0a}.button-group.warning .button,.button-group.warning .button.disabled,.button-group.warning .button.disabled:focus,.button-group.warning .button.disabled:hover,.button-group.warning .button[disabled],.button-group.warning .button[disabled]:focus,.button-group.warning .button[disabled]:hover{background-color:#ffae00;color:#0a0a0a}.button-group.warning .button:focus,.button-group.warning .button:hover{background-color:#cc8b00;color:#0a0a0a}.button-group.alert .button,.button-group.alert .button.disabled,.button-group.alert .button.disabled:focus,.button-group.alert .button.disabled:hover,.button-group.alert .button[disabled],.button-group.alert .button[disabled]:focus,.button-group.alert .button[disabled]:hover{background-color:#cc4b37;color:#fefefe}.button-group.alert .button:focus,.button-group.alert .button:hover{background-color:#a53b2a;color:#fefefe}.button-group.hollow .button,.button-group.hollow .button.disabled,.button-group.hollow .button.disabled:focus,.button-group.hollow .button.disabled:hover,.button-group.hollow .button:focus,.button-group.hollow .button:hover,.button-group.hollow .button[disabled],.button-group.hollow .button[disabled]:focus,.button-group.hollow .button[disabled]:hover{background-color:transparent}.button-group.hollow .button,.button-group.hollow .button.disabled,.button-group.hollow .button.disabled:focus,.button-group.hollow .button.disabled:hover,.button-group.hollow .button[disabled],.button-group.hollow .button[disabled]:focus,.button-group.hollow .button[disabled]:hover{border:1px solid #1779ba;color:#1779ba}.button-group.hollow .button:focus,.button-group.hollow .button:hover{border-color:#0c3d5d;color:#0c3d5d}.button-group.hollow .button.primary,.button-group.hollow .button.primary.disabled,.button-group.hollow .button.primary.disabled:focus,.button-group.hollow .button.primary.disabled:hover,.button-group.hollow .button.primary[disabled],.button-group.hollow .button.primary[disabled]:focus,.button-group.hollow .button.primary[disabled]:hover,.button-group.hollow.primary .button,.button-group.hollow.primary .button.disabled,.button-group.hollow.primary .button.disabled:focus,.button-group.hollow.primary .button.disabled:hover,.button-group.hollow.primary .button[disabled],.button-group.hollow.primary .button[disabled]:focus,.button-group.hollow.primary .button[disabled]:hover{border:1px solid #1779ba;color:#1779ba}.button-group.hollow .button.primary:focus,.button-group.hollow .button.primary:hover,.button-group.hollow.primary .button:focus,.button-group.hollow.primary .button:hover{border-color:#0c3d5d;color:#0c3d5d}.button-group.hollow .button.secondary,.button-group.hollow .button.secondary.disabled,.button-group.hollow .button.secondary.disabled:focus,.button-group.hollow .button.secondary.disabled:hover,.button-group.hollow .button.secondary[disabled],.button-group.hollow .button.secondary[disabled]:focus,.button-group.hollow .button.secondary[disabled]:hover,.button-group.hollow.secondary .button,.button-group.hollow.secondary .button.disabled,.button-group.hollow.secondary .button.disabled:focus,.button-group.hollow.secondary .button.disabled:hover,.button-group.hollow.secondary .button[disabled],.button-group.hollow.secondary .button[disabled]:focus,.button-group.hollow.secondary .button[disabled]:hover{border:1px solid #767676;color:#767676}.button-group.hollow .button.secondary:focus,.button-group.hollow .button.secondary:hover,.button-group.hollow.secondary .button:focus,.button-group.hollow.secondary .button:hover{border-color:#3b3b3b;color:#3b3b3b}.button-group.hollow .button.success,.button-group.hollow .button.success.disabled,.button-group.hollow .button.success.disabled:focus,.button-group.hollow .button.success.disabled:hover,.button-group.hollow .button.success[disabled],.button-group.hollow .button.success[disabled]:focus,.button-group.hollow .button.success[disabled]:hover,.button-group.hollow.success .button,.button-group.hollow.success .button.disabled,.button-group.hollow.success .button.disabled:focus,.button-group.hollow.success .button.disabled:hover,.button-group.hollow.success .button[disabled],.button-group.hollow.success .button[disabled]:focus,.button-group.hollow.success .button[disabled]:hover{border:1px solid #3adb76;color:#3adb76}.button-group.hollow .button.success:focus,.button-group.hollow .button.success:hover,.button-group.hollow.success .button:focus,.button-group.hollow.success .button:hover{border-color:#157539;color:#157539}.button-group.hollow .button.warning,.button-group.hollow .button.warning.disabled,.button-group.hollow .button.warning.disabled:focus,.button-group.hollow .button.warning.disabled:hover,.button-group.hollow .button.warning[disabled],.button-group.hollow .button.warning[disabled]:focus,.button-group.hollow .button.warning[disabled]:hover,.button-group.hollow.warning .button,.button-group.hollow.warning .button.disabled,.button-group.hollow.warning .button.disabled:focus,.button-group.hollow.warning .button.disabled:hover,.button-group.hollow.warning .button[disabled],.button-group.hollow.warning .button[disabled]:focus,.button-group.hollow.warning .button[disabled]:hover{border:1px solid #ffae00;color:#ffae00}.button-group.hollow .button.warning:focus,.button-group.hollow .button.warning:hover,.button-group.hollow.warning .button:focus,.button-group.hollow.warning .button:hover{border-color:#805700;color:#805700}.button-group.hollow .button.alert,.button-group.hollow .button.alert.disabled,.button-group.hollow .button.alert.disabled:focus,.button-group.hollow .button.alert.disabled:hover,.button-group.hollow .button.alert[disabled],.button-group.hollow .button.alert[disabled]:focus,.button-group.hollow .button.alert[disabled]:hover,.button-group.hollow.alert .button,.button-group.hollow.alert .button.disabled,.button-group.hollow.alert .button.disabled:focus,.button-group.hollow.alert .button.disabled:hover,.button-group.hollow.alert .button[disabled],.button-group.hollow.alert .button[disabled]:focus,.button-group.hollow.alert .button[disabled]:hover{border:1px solid #cc4b37;color:#cc4b37}.button-group.hollow .button.alert:focus,.button-group.hollow .button.alert:hover,.button-group.hollow.alert .button:focus,.button-group.hollow.alert .button:hover{border-color:#67251a;color:#67251a}.button-group.clear .button,.button-group.clear .button.disabled,.button-group.clear .button.disabled:focus,.button-group.clear .button.disabled:hover,.button-group.clear .button:focus,.button-group.clear .button:hover,.button-group.clear .button[disabled],.button-group.clear .button[disabled]:focus,.button-group.clear .button[disabled]:hover{border-color:transparent;background-color:transparent}.button-group.clear .button,.button-group.clear .button.disabled,.button-group.clear .button.disabled:focus,.button-group.clear .button.disabled:hover,.button-group.clear .button[disabled],.button-group.clear .button[disabled]:focus,.button-group.clear .button[disabled]:hover{color:#1779ba}.button-group.clear .button:focus,.button-group.clear .button:hover{color:#0c3d5d}.button-group.clear .button.primary,.button-group.clear .button.primary.disabled,.button-group.clear .button.primary.disabled:focus,.button-group.clear .button.primary.disabled:hover,.button-group.clear .button.primary[disabled],.button-group.clear .button.primary[disabled]:focus,.button-group.clear .button.primary[disabled]:hover,.button-group.clear.primary .button,.button-group.clear.primary .button.disabled,.button-group.clear.primary .button.disabled:focus,.button-group.clear.primary .button.disabled:hover,.button-group.clear.primary .button[disabled],.button-group.clear.primary .button[disabled]:focus,.button-group.clear.primary .button[disabled]:hover{color:#1779ba}.button-group.clear .button.primary:focus,.button-group.clear .button.primary:hover,.button-group.clear.primary .button:focus,.button-group.clear.primary .button:hover{color:#0c3d5d}.button-group.clear .button.secondary,.button-group.clear .button.secondary.disabled,.button-group.clear .button.secondary.disabled:focus,.button-group.clear .button.secondary.disabled:hover,.button-group.clear .button.secondary[disabled],.button-group.clear .button.secondary[disabled]:focus,.button-group.clear .button.secondary[disabled]:hover,.button-group.clear.secondary .button,.button-group.clear.secondary .button.disabled,.button-group.clear.secondary .button.disabled:focus,.button-group.clear.secondary .button.disabled:hover,.button-group.clear.secondary .button[disabled],.button-group.clear.secondary .button[disabled]:focus,.button-group.clear.secondary .button[disabled]:hover{color:#767676}.button-group.clear .button.secondary:focus,.button-group.clear .button.secondary:hover,.button-group.clear.secondary .button:focus,.button-group.clear.secondary .button:hover{color:#3b3b3b}.button-group.clear .button.success,.button-group.clear .button.success.disabled,.button-group.clear .button.success.disabled:focus,.button-group.clear .button.success.disabled:hover,.button-group.clear .button.success[disabled],.button-group.clear .button.success[disabled]:focus,.button-group.clear .button.success[disabled]:hover,.button-group.clear.success .button,.button-group.clear.success .button.disabled,.button-group.clear.success .button.disabled:focus,.button-group.clear.success .button.disabled:hover,.button-group.clear.success .button[disabled],.button-group.clear.success .button[disabled]:focus,.button-group.clear.success .button[disabled]:hover{color:#3adb76}.button-group.clear .button.success:focus,.button-group.clear .button.success:hover,.button-group.clear.success .button:focus,.button-group.clear.success .button:hover{color:#157539}.button-group.clear .button.warning,.button-group.clear .button.warning.disabled,.button-group.clear .button.warning.disabled:focus,.button-group.clear .button.warning.disabled:hover,.button-group.clear .button.warning[disabled],.button-group.clear .button.warning[disabled]:focus,.button-group.clear .button.warning[disabled]:hover,.button-group.clear.warning .button,.button-group.clear.warning .button.disabled,.button-group.clear.warning .button.disabled:focus,.button-group.clear.warning .button.disabled:hover,.button-group.clear.warning .button[disabled],.button-group.clear.warning .button[disabled]:focus,.button-group.clear.warning .button[disabled]:hover{color:#ffae00}.button-group.clear .button.warning:focus,.button-group.clear .button.warning:hover,.button-group.clear.warning .button:focus,.button-group.clear.warning .button:hover{color:#805700}.button-group.clear .button.alert,.button-group.clear .button.alert.disabled,.button-group.clear .button.alert.disabled:focus,.button-group.clear .button.alert.disabled:hover,.button-group.clear .button.alert[disabled],.button-group.clear .button.alert[disabled]:focus,.button-group.clear .button.alert[disabled]:hover,.button-group.clear.alert .button,.button-group.clear.alert .button.disabled,.button-group.clear.alert .button.disabled:focus,.button-group.clear.alert .button.disabled:hover,.button-group.clear.alert .button[disabled],.button-group.clear.alert .button[disabled]:focus,.button-group.clear.alert .button[disabled]:hover{color:#cc4b37}.button-group.clear .button.alert:focus,.button-group.clear .button.alert:hover,.button-group.clear.alert .button:focus,.button-group.clear.alert .button:hover{color:#67251a}.button-group.no-gaps .button{margin-right:-.0625rem}.button-group.no-gaps .button+.button{border-left-color:transparent}.button-group.stacked,.button-group.stacked-for-medium,.button-group.stacked-for-small{-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.button-group.stacked .button,.button-group.stacked-for-medium .button,.button-group.stacked-for-small .button{-webkit-box-flex:0;-webkit-flex:0 0 100%;-ms-flex:0 0 100%;flex:0 0 100%}.button-group.stacked .button:last-child,.button-group.stacked-for-medium .button:last-child,.button-group.stacked-for-small .button:last-child{margin-bottom:0}.button-group.stacked-for-medium.expanded .button,.button-group.stacked-for-small.expanded .button,.button-group.stacked.expanded .button{-webkit-box-flex:1;-webkit-flex:1 1 0px;-ms-flex:1 1 0px;flex:1 1 0px}@media print,screen and (min-width:40em){.button-group.stacked-for-small .button{-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;margin-bottom:0}}@media print,screen and (min-width:64em){.button-group.stacked-for-medium .button{-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;margin-bottom:0}}@media print,screen and (max-width:39.99875em){.button-group.stacked-for-small.expanded{display:block}.button-group.stacked-for-small.expanded .button{display:block;margin-right:0}}@media print,screen and (max-width:63.99875em){.button-group.stacked-for-medium.expanded{display:block}.button-group.stacked-for-medium.expanded .button{display:block;margin-right:0}}.close-button{position:absolute;z-index:10;color:#8a8a8a;cursor:pointer}[data-whatinput=mouse] .close-button{outline:0}.close-button:focus,.close-button:hover{color:#0a0a0a}.close-button.small{right:.66rem;top:.33em;font-size:1.5em;line-height:1}.close-button,.close-button.medium{right:1rem;top:.5rem;font-size:2em;line-height:1}.label{display:inline-block;padding:.33333rem .5rem;border-radius:0;font-size:.8rem;line-height:1;white-space:nowrap;cursor:default;background:#1779ba;color:#fefefe}.label.primary{background:#1779ba;color:#fefefe}.label.secondary{background:#767676;color:#fefefe}.label.success{background:#3adb76;color:#0a0a0a}.label.warning{background:#ffae00;color:#0a0a0a}.label.alert{background:#cc4b37;color:#fefefe}.progress{height:1rem;margin-bottom:1rem;border-radius:0;background-color:#cacaca}.progress.primary .progress-meter{background-color:#1779ba}.progress.secondary .progress-meter{background-color:#767676}.progress.success .progress-meter{background-color:#3adb76}.progress.warning .progress-meter{background-color:#ffae00}.progress.alert .progress-meter{background-color:#cc4b37}.progress-meter{position:relative;display:block;width:0%;height:100%;background-color:#1779ba}.progress-meter-text{position:absolute;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%);margin:0;font-size:.75rem;font-weight:700;color:#fefefe;white-space:nowrap}.slider{position:relative;height:.5rem;margin-top:1.25rem;margin-bottom:2.25rem;background-color:#e6e6e6;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-ms-touch-action:none;touch-action:none}.slider-fill{position:absolute;top:0;left:0;display:inline-block;max-width:100%;height:.5rem;background-color:#cacaca;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.slider-fill.is-dragging{-webkit-transition:all 0s linear;transition:all 0s linear}.slider-handle{position:absolute;top:50%;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%);left:0;z-index:1;display:inline-block;width:1.4rem;height:1.4rem;border-radius:0;background-color:#1779ba;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out;-ms-touch-action:manipulation;touch-action:manipulation}[data-whatinput=mouse] .slider-handle{outline:0}.slider-handle:hover{background-color:#14679e}.slider-handle.is-dragging{-webkit-transition:all 0s linear;transition:all 0s linear}.slider.disabled,.slider[disabled]{opacity:.25;cursor:not-allowed}.slider.vertical{display:inline-block;width:.5rem;height:12.5rem;margin:0 1.25rem;-webkit-transform:scale(1,-1);-ms-transform:scale(1,-1);transform:scale(1,-1)}.slider.vertical .slider-fill{top:0;width:.5rem;max-height:100%}.slider.vertical .slider-handle{position:absolute;top:0;left:50%;width:1.4rem;height:1.4rem;-webkit-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%)}.switch{height:2rem;position:relative;margin-bottom:1rem;outline:0;font-size:.875rem;font-weight:700;color:#fefefe;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.switch-input{position:absolute;margin-bottom:0;opacity:0}.switch-paddle{position:relative;display:block;width:4rem;height:2rem;border-radius:0;background:#cacaca;-webkit-transition:all .25s ease-out;transition:all .25s ease-out;font-weight:inherit;color:inherit;cursor:pointer}input+.switch-paddle{margin:0}.switch-paddle::after{position:absolute;top:.25rem;left:.25rem;display:block;width:1.5rem;height:1.5rem;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);border-radius:0;background:#fefefe;-webkit-transition:all .25s ease-out;transition:all .25s ease-out;content:''}input:checked~.switch-paddle{background:#1779ba}input:checked~.switch-paddle::after{left:2.25rem}input:disabled~.switch-paddle{cursor:not-allowed;opacity:.5}[data-whatinput=mouse] input:focus~.switch-paddle{outline:0}.switch-active,.switch-inactive{position:absolute;top:50%;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)}.switch-active{left:8%;display:none}input:checked+label>.switch-active{display:block}.switch-inactive{right:15%}input:checked+label>.switch-inactive{display:none}.switch.tiny{height:1.5rem}.switch.tiny .switch-paddle{width:3rem;height:1.5rem;font-size:.625rem}.switch.tiny .switch-paddle::after{top:.25rem;left:.25rem;width:1rem;height:1rem}.switch.tiny input:checked~.switch-paddle::after{left:1.75rem}.switch.small{height:1.75rem}.switch.small .switch-paddle{width:3.5rem;height:1.75rem;font-size:.75rem}.switch.small .switch-paddle::after{top:.25rem;left:.25rem;width:1.25rem;height:1.25rem}.switch.small input:checked~.switch-paddle::after{left:2rem}.switch.large{height:2.5rem}.switch.large .switch-paddle{width:5rem;height:2.5rem;font-size:1rem}.switch.large .switch-paddle::after{top:.25rem;left:.25rem;width:2rem;height:2rem}.switch.large input:checked~.switch-paddle::after{left:2.75rem}table{border-collapse:collapse;width:100%;margin-bottom:1rem;border-radius:0}tbody,tfoot,thead{border:1px solid #f1f1f1;background-color:#fefefe}caption{padding:.5rem .625rem .625rem;font-weight:700}thead{background:#f8f8f8;color:#0a0a0a}tfoot{background:#f1f1f1;color:#0a0a0a}tfoot tr,thead tr{background:0 0}tfoot td,tfoot th,thead td,thead th{padding:.5rem .625rem .625rem;font-weight:700;text-align:left}tbody td,tbody th{padding:.5rem .625rem .625rem}tbody tr:nth-child(even){border-bottom:0;background-color:#f1f1f1}table.unstriped tbody{background-color:#fefefe}table.unstriped tbody tr{border-bottom:0;border-bottom:1px solid #f1f1f1;background-color:#fefefe}@media print,screen and (max-width:63.99875em){table.stack thead{display:none}table.stack tfoot{display:none}table.stack td,table.stack th,table.stack tr{display:block}table.stack td{border-top:0}}table.scroll{display:block;width:100%;overflow-x:auto}table.hover thead tr:hover{background-color:#f3f3f3}table.hover tfoot tr:hover{background-color:#ececec}table.hover tbody tr:hover{background-color:#f9f9f9}table.hover:not(.unstriped) tr:nth-of-type(even):hover{background-color:#ececec}.table-scroll{overflow-x:auto}.badge{display:inline-block;min-width:2.1em;padding:.3em;border-radius:50%;font-size:.6rem;text-align:center;background:#1779ba;color:#fefefe}.badge.primary{background:#1779ba;color:#fefefe}.badge.secondary{background:#767676;color:#fefefe}.badge.success{background:#3adb76;color:#0a0a0a}.badge.warning{background:#ffae00;color:#0a0a0a}.badge.alert{background:#cc4b37;color:#fefefe}.breadcrumbs{margin:0 0 1rem 0;list-style:none}.breadcrumbs::after,.breadcrumbs::before{display:table;content:' ';-webkit-flex-basis:0;-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-ordinal-group:2;-webkit-order:1;-ms-flex-order:1;order:1}.breadcrumbs::after{clear:both}.breadcrumbs li{float:left;font-size:.6875rem;color:#0a0a0a;cursor:default;text-transform:uppercase}.breadcrumbs li:not(:last-child)::after{position:relative;margin:0 .75rem;opacity:1;content:"/";color:#cacaca}.breadcrumbs a{color:#1779ba}.breadcrumbs a:hover{text-decoration:underline}.breadcrumbs .disabled{color:#cacaca;cursor:not-allowed}.callout{position:relative;margin:0 0 1rem 0;padding:1rem;border:1px solid rgba(10,10,10,.25);border-radius:0;background-color:#fff;color:#0a0a0a}.callout>:first-child{margin-top:0}.callout>:last-child{margin-bottom:0}.callout.primary{background-color:#d7ecfa;color:#0a0a0a}.callout.secondary{background-color:#eaeaea;color:#0a0a0a}.callout.success{background-color:#e1faea;color:#0a0a0a}.callout.warning{background-color:#fff3d9;color:#0a0a0a}.callout.alert{background-color:#f7e4e1;color:#0a0a0a}.callout.small{padding-top:.5rem;padding-right:.5rem;padding-bottom:.5rem;padding-left:.5rem}.callout.large{padding-top:3rem;padding-right:3rem;padding-bottom:3rem;padding-left:3rem}.card{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;margin-bottom:1rem;border:1px solid #e6e6e6;border-radius:0;background:#fefefe;-webkit-box-shadow:none;box-shadow:none;overflow:hidden;color:#0a0a0a}.card>:last-child{margin-bottom:0}.card-divider{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-flex:0;-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto;padding:1rem;background:#e6e6e6}.card-divider>:last-child{margin-bottom:0}.card-section{-webkit-box-flex:1;-webkit-flex:1 0 auto;-ms-flex:1 0 auto;flex:1 0 auto;padding:1rem}.card-section>:last-child{margin-bottom:0}.card-image{min-height:1px}.dropdown-pane{position:absolute;z-index:10;display:none;width:300px;padding:1rem;visibility:hidden;border:1px solid #cacaca;border-radius:0;background-color:#fefefe;font-size:1rem}.dropdown-pane.is-opening{display:block}.dropdown-pane.is-open{display:block;visibility:visible}.dropdown-pane.tiny{width:100px}.dropdown-pane.small{width:200px}.dropdown-pane.large{width:400px}.pagination{margin-left:0;margin-bottom:1rem}.pagination::after,.pagination::before{display:table;content:' ';-webkit-flex-basis:0;-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-ordinal-group:2;-webkit-order:1;-ms-flex-order:1;order:1}.pagination::after{clear:both}.pagination li{margin-right:.0625rem;border-radius:0;font-size:.875rem;display:none}.pagination li:first-child,.pagination li:last-child{display:inline-block}@media print,screen and (min-width:40em){.pagination li{display:inline-block}}.pagination a,.pagination button{display:block;padding:.1875rem .625rem;border-radius:0;color:#0a0a0a}.pagination a:hover,.pagination button:hover{background:#e6e6e6}.pagination .current{padding:.1875rem .625rem;background:#1779ba;color:#fefefe;cursor:default}.pagination .disabled{padding:.1875rem .625rem;color:#cacaca;cursor:not-allowed}.pagination .disabled:hover{background:0 0}.pagination .ellipsis::after{padding:.1875rem .625rem;content:'\2026';color:#0a0a0a}.pagination-previous a::before,.pagination-previous.disabled::before{display:inline-block;margin-right:.5rem;content:"«"}.pagination-next a::after,.pagination-next.disabled::after{display:inline-block;margin-left:.5rem;content:"»"}.has-tip{position:relative;display:inline-block;border-bottom:dotted 1px #8a8a8a;font-weight:700;cursor:help}.tooltip{position:absolute;top:calc(100% + .6495rem);z-index:1200;max-width:10rem;padding:.75rem;border-radius:0;background-color:#0a0a0a;font-size:80%;color:#fefefe}.tooltip::before{position:absolute}.tooltip.bottom::before{display:block;width:0;height:0;border-style:solid;border-width:.75rem;content:'';border-top-width:0;border-color:transparent transparent #0a0a0a;bottom:100%}.tooltip.bottom.align-center::before{left:50%;-webkit-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%)}.tooltip.top::before{display:block;width:0;height:0;border-style:solid;border-width:.75rem;content:'';border-bottom-width:0;border-color:#0a0a0a transparent transparent;top:100%;bottom:auto}.tooltip.top.align-center::before{left:50%;-webkit-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%)}.tooltip.left::before{display:block;width:0;height:0;border-style:solid;border-width:.75rem;content:'';border-right-width:0;border-color:transparent transparent transparent #0a0a0a;left:100%}.tooltip.left.align-center::before{bottom:auto;top:50%;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)}.tooltip.right::before{display:block;width:0;height:0;border-style:solid;border-width:.75rem;content:'';border-left-width:0;border-color:transparent #0a0a0a transparent transparent;right:100%;left:auto}.tooltip.right.align-center::before{bottom:auto;top:50%;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)}.tooltip.align-top::before{bottom:auto;top:10%}.tooltip.align-bottom::before{bottom:10%;top:auto}.tooltip.align-left::before{left:10%;right:auto}.tooltip.align-right::before{left:auto;right:10%}.accordion{margin-left:0;background:#fefefe;list-style-type:none}.accordion[disabled] .accordion-title{cursor:not-allowed}.accordion-item:first-child>:first-child{border-radius:0}.accordion-item:last-child>:last-child{border-radius:0}.accordion-title{position:relative;display:block;padding:1.25rem 1rem;border:1px solid #e6e6e6;border-bottom:0;font-size:.75rem;line-height:1;color:#1779ba}:last-child:not(.is-active)>.accordion-title{border-bottom:1px solid #e6e6e6;border-radius:0}.accordion-title:focus,.accordion-title:hover{background-color:#e6e6e6}.accordion-title::before{position:absolute;top:50%;right:1rem;margin-top:-.5rem;content:"+"}.is-active>.accordion-title::before{content:"–"}.accordion-content{display:none;padding:1rem;border:1px solid #e6e6e6;border-bottom:0;background-color:#fefefe;color:#0a0a0a}:last-child>.accordion-content:last-child{border-bottom:1px solid #e6e6e6}.media-object{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;margin-bottom:1rem;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap}.media-object img{max-width:none}@media print,screen and (max-width:39.99875em){.media-object.stack-for-small{-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}}.media-object-section{-webkit-box-flex:0;-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto}.media-object-section:first-child{padding-right:1rem}.media-object-section:last-child:not(:nth-child(2)){padding-left:1rem}.media-object-section>:last-child{margin-bottom:0}@media print,screen and (max-width:39.99875em){.stack-for-small .media-object-section{padding:0;padding-bottom:1rem;-webkit-flex-basis:100%;-ms-flex-preferred-size:100%;flex-basis:100%;max-width:100%}.stack-for-small .media-object-section img{width:100%}}.media-object-section.main-section{-webkit-box-flex:1;-webkit-flex:1 1 0px;-ms-flex:1 1 0px;flex:1 1 0px}.orbit{position:relative}.orbit-container{position:relative;height:0;margin:0;list-style:none;overflow:hidden}.orbit-slide{width:100%;position:absolute}.orbit-slide.no-motionui.is-active{top:0;left:0}.orbit-figure{margin:0}.orbit-image{width:100%;max-width:100%;margin:0}.orbit-caption{position:absolute;bottom:0;width:100%;margin-bottom:0;padding:1rem;background-color:rgba(10,10,10,.5);color:#fefefe}.orbit-next,.orbit-previous{position:absolute;top:50%;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%);z-index:10;padding:1rem;color:#fefefe}[data-whatinput=mouse] .orbit-next,[data-whatinput=mouse] .orbit-previous{outline:0}.orbit-next:active,.orbit-next:focus,.orbit-next:hover,.orbit-previous:active,.orbit-previous:focus,.orbit-previous:hover{background-color:rgba(10,10,10,.5)}.orbit-previous{left:0}.orbit-next{left:auto;right:0}.orbit-bullets{position:relative;margin-top:.8rem;margin-bottom:.8rem;text-align:center}[data-whatinput=mouse] .orbit-bullets{outline:0}.orbit-bullets button{width:1.2rem;height:1.2rem;margin:.1rem;border-radius:50%;background-color:#cacaca}.orbit-bullets button:hover{background-color:#8a8a8a}.orbit-bullets button.is-active{background-color:#8a8a8a}.flex-video,.responsive-embed{position:relative;height:0;margin-bottom:1rem;padding-bottom:75%;overflow:hidden}.flex-video embed,.flex-video iframe,.flex-video object,.flex-video video,.responsive-embed embed,.responsive-embed iframe,.responsive-embed object,.responsive-embed video{position:absolute;top:0;left:0;width:100%;height:100%}.flex-video.widescreen,.responsive-embed.widescreen{padding-bottom:56.25%}.tabs{margin:0;border:1px solid #e6e6e6;background:#fefefe;list-style-type:none}.tabs::after,.tabs::before{display:table;content:' ';-webkit-flex-basis:0;-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-ordinal-group:2;-webkit-order:1;-ms-flex-order:1;order:1}.tabs::after{clear:both}.tabs.vertical>li{display:block;float:none;width:auto}.tabs.simple>li>a{padding:0}.tabs.simple>li>a:hover{background:0 0}.tabs.primary{background:#1779ba}.tabs.primary>li>a{color:#fefefe}.tabs.primary>li>a:focus,.tabs.primary>li>a:hover{background:#1673b1}.tabs-title{float:left}.tabs-title>a{display:block;padding:1.25rem 1.5rem;font-size:.75rem;line-height:1;color:#1779ba}[data-whatinput=mouse] .tabs-title>a{outline:0}.tabs-title>a:hover{background:#fefefe;color:#1468a0}.tabs-title>a:focus,.tabs-title>a[aria-selected=true]{background:#e6e6e6;color:#1779ba}.tabs-content{border:1px solid #e6e6e6;border-top:0;background:#fefefe;color:#0a0a0a;-webkit-transition:all .5s ease;transition:all .5s ease}.tabs-content.vertical{border:1px solid #e6e6e6;border-left:0}.tabs-panel{display:none;padding:1rem}.tabs-panel.is-active{display:block}.thumbnail{display:inline-block;max-width:100%;margin-bottom:1rem;border:4px solid #fefefe;border-radius:0;-webkit-box-shadow:0 0 0 1px rgba(10,10,10,.2);box-shadow:0 0 0 1px rgba(10,10,10,.2);line-height:0}a.thumbnail{-webkit-transition:-webkit-box-shadow .2s ease-out;transition:-webkit-box-shadow .2s ease-out;transition:box-shadow .2s ease-out;transition:box-shadow .2s ease-out,-webkit-box-shadow .2s ease-out}a.thumbnail:focus,a.thumbnail:hover{-webkit-box-shadow:0 0 6px 1px rgba(23,121,186,.5);box-shadow:0 0 6px 1px rgba(23,121,186,.5)}a.thumbnail image{-webkit-box-shadow:none;box-shadow:none}.menu{padding:0;margin:0;list-style:none;position:relative;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}[data-whatinput=mouse] .menu li{outline:0}.menu .button,.menu a{line-height:1;text-decoration:none;display:block;padding:.7rem 1rem}.menu a,.menu button,.menu input,.menu select{margin-bottom:0}.menu input{display:inline-block}.menu,.menu.horizontal{-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.menu.vertical{-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}.menu.vertical.icon-bottom li a i,.menu.vertical.icon-bottom li a img,.menu.vertical.icon-bottom li a svg,.menu.vertical.icon-top li a i,.menu.vertical.icon-top li a img,.menu.vertical.icon-top li a svg{text-align:left}.menu.expanded li{-webkit-box-flex:1;-webkit-flex:1 1 0px;-ms-flex:1 1 0px;flex:1 1 0px}.menu.expanded.icon-bottom li a i,.menu.expanded.icon-bottom li a img,.menu.expanded.icon-bottom li a svg,.menu.expanded.icon-top li a i,.menu.expanded.icon-top li a img,.menu.expanded.icon-top li a svg{text-align:left}.menu.simple{-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.menu.simple li+li{margin-left:1rem}.menu.simple a{padding:0}@media print,screen and (min-width:40em){.menu.medium-horizontal{-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.menu.medium-vertical{-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}.menu.medium-expanded li{-webkit-box-flex:1;-webkit-flex:1 1 0px;-ms-flex:1 1 0px;flex:1 1 0px}.menu.medium-simple li{-webkit-box-flex:1;-webkit-flex:1 1 0px;-ms-flex:1 1 0px;flex:1 1 0px}}@media print,screen and (min-width:64em){.menu.large-horizontal{-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.menu.large-vertical{-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}.menu.large-expanded li{-webkit-box-flex:1;-webkit-flex:1 1 0px;-ms-flex:1 1 0px;flex:1 1 0px}.menu.large-simple li{-webkit-box-flex:1;-webkit-flex:1 1 0px;-ms-flex:1 1 0px;flex:1 1 0px}}.menu.nested{margin-right:0;margin-left:1rem}.menu.icons a{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.menu.icon-bottom a,.menu.icon-left a,.menu.icon-right a,.menu.icon-top a{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.menu.icon-left li a,.menu.nested.icon-left li a{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap}.menu.icon-left li a i,.menu.icon-left li a img,.menu.icon-left li a svg,.menu.nested.icon-left li a i,.menu.nested.icon-left li a img,.menu.nested.icon-left li a svg{margin-right:.25rem}.menu.icon-right li a,.menu.nested.icon-right li a{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap}.menu.icon-right li a i,.menu.icon-right li a img,.menu.icon-right li a svg,.menu.nested.icon-right li a i,.menu.nested.icon-right li a img,.menu.nested.icon-right li a svg{margin-left:.25rem}.menu.icon-top li a,.menu.nested.icon-top li a{-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-flow:column nowrap;-ms-flex-flow:column nowrap;flex-flow:column nowrap}.menu.icon-top li a i,.menu.icon-top li a img,.menu.icon-top li a svg,.menu.nested.icon-top li a i,.menu.nested.icon-top li a img,.menu.nested.icon-top li a svg{-webkit-align-self:stretch;-ms-flex-item-align:stretch;align-self:stretch;margin-bottom:.25rem;text-align:center}.menu.icon-bottom li a,.menu.nested.icon-bottom li a{-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-flow:column nowrap;-ms-flex-flow:column nowrap;flex-flow:column nowrap}.menu.icon-bottom li a i,.menu.icon-bottom li a img,.menu.icon-bottom li a svg,.menu.nested.icon-bottom li a i,.menu.nested.icon-bottom li a img,.menu.nested.icon-bottom li a svg{-webkit-align-self:stretch;-ms-flex-item-align:stretch;align-self:stretch;margin-bottom:.25rem;text-align:center}.menu .is-active>a{background:#1779ba;color:#fefefe}.menu .active>a{background:#1779ba;color:#fefefe}.menu.align-left{-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start}.menu.align-right li{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:end;-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end}.menu.align-right li .submenu li{-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start}.menu.align-right.vertical li{display:block;text-align:right}.menu.align-right.vertical li .submenu li{text-align:right}.menu.align-right.icon-bottom li a i,.menu.align-right.icon-bottom li a img,.menu.align-right.icon-bottom li a svg,.menu.align-right.icon-top li a i,.menu.align-right.icon-top li a img,.menu.align-right.icon-top li a svg{text-align:right}.menu.align-right .nested{margin-right:1rem;margin-left:0}.menu.align-center li{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}.menu.align-center li .submenu li{-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start}.menu .menu-text{padding:.7rem 1rem;font-weight:700;line-height:1;color:inherit}.menu-centered>.menu{-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}.menu-centered>.menu li{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}.menu-centered>.menu li .submenu li{-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start}.no-js [data-responsive-menu] ul{display:none}.menu-icon{position:relative;display:inline-block;vertical-align:middle;width:20px;height:16px;cursor:pointer}.menu-icon::after{position:absolute;top:0;left:0;display:block;width:100%;height:2px;background:#fefefe;-webkit-box-shadow:0 7px 0 #fefefe,0 14px 0 #fefefe;box-shadow:0 7px 0 #fefefe,0 14px 0 #fefefe;content:''}.menu-icon:hover::after{background:#cacaca;-webkit-box-shadow:0 7px 0 #cacaca,0 14px 0 #cacaca;box-shadow:0 7px 0 #cacaca,0 14px 0 #cacaca}.menu-icon.dark{position:relative;display:inline-block;vertical-align:middle;width:20px;height:16px;cursor:pointer}.menu-icon.dark::after{position:absolute;top:0;left:0;display:block;width:100%;height:2px;background:#0a0a0a;-webkit-box-shadow:0 7px 0 #0a0a0a,0 14px 0 #0a0a0a;box-shadow:0 7px 0 #0a0a0a,0 14px 0 #0a0a0a;content:''}.menu-icon.dark:hover::after{background:#8a8a8a;-webkit-box-shadow:0 7px 0 #8a8a8a,0 14px 0 #8a8a8a;box-shadow:0 7px 0 #8a8a8a,0 14px 0 #8a8a8a}.accordion-menu li{width:100%}.accordion-menu a{padding:.7rem 1rem}.accordion-menu .is-accordion-submenu a{padding:.7rem 1rem}.accordion-menu .nested.is-accordion-submenu{margin-right:0;margin-left:1rem}.accordion-menu.align-right .nested.is-accordion-submenu{margin-right:1rem;margin-left:0}.accordion-menu .is-accordion-submenu-parent:not(.has-submenu-toggle)>a{position:relative}.accordion-menu .is-accordion-submenu-parent:not(.has-submenu-toggle)>a::after{display:block;width:0;height:0;border-style:solid;border-width:6px;content:'';border-bottom-width:0;border-color:#1779ba transparent transparent;position:absolute;top:50%;margin-top:-3px;right:1rem}.accordion-menu.align-left .is-accordion-submenu-parent>a::after{right:1rem;left:auto}.accordion-menu.align-right .is-accordion-submenu-parent>a::after{right:auto;left:1rem}.accordion-menu .is-accordion-submenu-parent[aria-expanded=true]>a::after{-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg);-webkit-transform-origin:50% 50%;-ms-transform-origin:50% 50%;transform-origin:50% 50%}.is-accordion-submenu-parent{position:relative}.has-submenu-toggle>a{margin-right:40px}.submenu-toggle{position:absolute;top:0;right:0;width:40px;height:40px;cursor:pointer}.submenu-toggle::after{display:block;width:0;height:0;border-style:solid;border-width:6px;content:'';border-bottom-width:0;border-color:#1779ba transparent transparent;top:0;bottom:0;margin:auto}.submenu-toggle[aria-expanded=true]::after{-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1);-webkit-transform-origin:50% 50%;-ms-transform-origin:50% 50%;transform-origin:50% 50%}.submenu-toggle-text{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.is-drilldown{position:relative;overflow:hidden}.is-drilldown li{display:block}.is-drilldown.animate-height{-webkit-transition:height .5s;transition:height .5s}.drilldown a{padding:.7rem 1rem;background:#fefefe}.drilldown .is-drilldown-submenu{position:absolute;top:0;left:100%;z-index:-1;width:100%;background:#fefefe;-webkit-transition:-webkit-transform .15s linear;transition:-webkit-transform .15s linear;transition:transform .15s linear;transition:transform .15s linear,-webkit-transform .15s linear}.drilldown .is-drilldown-submenu.is-active{z-index:1;display:block;-webkit-transform:translateX(-100%);-ms-transform:translateX(-100%);transform:translateX(-100%)}.drilldown .is-drilldown-submenu.is-closing{-webkit-transform:translateX(100%);-ms-transform:translateX(100%);transform:translateX(100%)}.drilldown .is-drilldown-submenu a{padding:.7rem 1rem}.drilldown .nested.is-drilldown-submenu{margin-right:0;margin-left:0}.drilldown .drilldown-submenu-cover-previous{min-height:100%}.drilldown .is-drilldown-submenu-parent>a{position:relative}.drilldown .is-drilldown-submenu-parent>a::after{display:block;width:0;height:0;border-style:solid;border-width:6px;content:'';border-right-width:0;border-color:transparent transparent transparent #1779ba;position:absolute;top:50%;margin-top:-6px;right:1rem}.drilldown.align-left .is-drilldown-submenu-parent>a::after{display:block;width:0;height:0;border-style:solid;border-width:6px;content:'';border-right-width:0;border-color:transparent transparent transparent #1779ba;right:1rem;left:auto}.drilldown.align-right .is-drilldown-submenu-parent>a::after{display:block;width:0;height:0;border-style:solid;border-width:6px;content:'';border-left-width:0;border-color:transparent #1779ba transparent transparent;right:auto;left:1rem}.drilldown .js-drilldown-back>a::before{display:block;width:0;height:0;border-style:solid;border-width:6px;content:'';border-left-width:0;border-color:transparent #1779ba transparent transparent;display:inline-block;vertical-align:middle;margin-right:.75rem}.dropdown.menu>li.opens-left>.is-dropdown-submenu{top:100%;right:0;left:auto}.dropdown.menu>li.opens-right>.is-dropdown-submenu{top:100%;right:auto;left:0}.dropdown.menu>li.is-dropdown-submenu-parent>a{position:relative;padding-right:1.5rem}.dropdown.menu>li.is-dropdown-submenu-parent>a::after{display:block;width:0;height:0;border-style:solid;border-width:6px;content:'';border-bottom-width:0;border-color:#1779ba transparent transparent;right:5px;left:auto;margin-top:-3px}[data-whatinput=mouse] .dropdown.menu a{outline:0}.dropdown.menu>li>a{padding:.7rem 1rem}.dropdown.menu>li.is-active>a{background:0 0;color:#1779ba}.no-js .dropdown.menu ul{display:none}.dropdown.menu .nested.is-dropdown-submenu{margin-right:0;margin-left:0}.dropdown.menu.vertical>li .is-dropdown-submenu{top:0}.dropdown.menu.vertical>li.opens-left>.is-dropdown-submenu{top:0;right:100%;left:auto}.dropdown.menu.vertical>li.opens-right>.is-dropdown-submenu{right:auto;left:100%}.dropdown.menu.vertical>li>a::after{right:14px}.dropdown.menu.vertical>li.opens-left>a::after{display:block;width:0;height:0;border-style:solid;border-width:6px;content:'';border-left-width:0;border-color:transparent #1779ba transparent transparent;right:auto;left:5px}.dropdown.menu.vertical>li.opens-right>a::after{display:block;width:0;height:0;border-style:solid;border-width:6px;content:'';border-right-width:0;border-color:transparent transparent transparent #1779ba}@media print,screen and (min-width:40em){.dropdown.menu.medium-horizontal>li.opens-left>.is-dropdown-submenu{top:100%;right:0;left:auto}.dropdown.menu.medium-horizontal>li.opens-right>.is-dropdown-submenu{top:100%;right:auto;left:0}.dropdown.menu.medium-horizontal>li.is-dropdown-submenu-parent>a{position:relative;padding-right:1.5rem}.dropdown.menu.medium-horizontal>li.is-dropdown-submenu-parent>a::after{display:block;width:0;height:0;border-style:solid;border-width:6px;content:'';border-bottom-width:0;border-color:#1779ba transparent transparent;right:5px;left:auto;margin-top:-3px}.dropdown.menu.medium-vertical>li .is-dropdown-submenu{top:0}.dropdown.menu.medium-vertical>li.opens-left>.is-dropdown-submenu{top:0;right:100%;left:auto}.dropdown.menu.medium-vertical>li.opens-right>.is-dropdown-submenu{right:auto;left:100%}.dropdown.menu.medium-vertical>li>a::after{right:14px}.dropdown.menu.medium-vertical>li.opens-left>a::after{display:block;width:0;height:0;border-style:solid;border-width:6px;content:'';border-left-width:0;border-color:transparent #1779ba transparent transparent;right:auto;left:5px}.dropdown.menu.medium-vertical>li.opens-right>a::after{display:block;width:0;height:0;border-style:solid;border-width:6px;content:'';border-right-width:0;border-color:transparent transparent transparent #1779ba}}@media print,screen and (min-width:64em){.dropdown.menu.large-horizontal>li.opens-left>.is-dropdown-submenu{top:100%;right:0;left:auto}.dropdown.menu.large-horizontal>li.opens-right>.is-dropdown-submenu{top:100%;right:auto;left:0}.dropdown.menu.large-horizontal>li.is-dropdown-submenu-parent>a{position:relative;padding-right:1.5rem}.dropdown.menu.large-horizontal>li.is-dropdown-submenu-parent>a::after{display:block;width:0;height:0;border-style:solid;border-width:6px;content:'';border-bottom-width:0;border-color:#1779ba transparent transparent;right:5px;left:auto;margin-top:-3px}.dropdown.menu.large-vertical>li .is-dropdown-submenu{top:0}.dropdown.menu.large-vertical>li.opens-left>.is-dropdown-submenu{top:0;right:100%;left:auto}.dropdown.menu.large-vertical>li.opens-right>.is-dropdown-submenu{right:auto;left:100%}.dropdown.menu.large-vertical>li>a::after{right:14px}.dropdown.menu.large-vertical>li.opens-left>a::after{display:block;width:0;height:0;border-style:solid;border-width:6px;content:'';border-left-width:0;border-color:transparent #1779ba transparent transparent;right:auto;left:5px}.dropdown.menu.large-vertical>li.opens-right>a::after{display:block;width:0;height:0;border-style:solid;border-width:6px;content:'';border-right-width:0;border-color:transparent transparent transparent #1779ba}}.dropdown.menu.align-right .is-dropdown-submenu.first-sub{top:100%;right:0;left:auto}.is-dropdown-menu.vertical{width:100px}.is-dropdown-menu.vertical.align-right{float:right}.is-dropdown-submenu-parent{position:relative}.is-dropdown-submenu-parent a::after{position:absolute;top:50%;right:5px;left:auto;margin-top:-6px}.is-dropdown-submenu-parent.opens-inner>.is-dropdown-submenu{top:100%;left:auto}.is-dropdown-submenu-parent.opens-left>.is-dropdown-submenu{right:100%;left:auto}.is-dropdown-submenu-parent.opens-right>.is-dropdown-submenu{right:auto;left:100%}.is-dropdown-submenu{position:absolute;top:0;left:100%;z-index:1;display:none;min-width:200px;border:1px solid #cacaca;background:#fefefe}.dropdown .is-dropdown-submenu a{padding:.7rem 1rem}.is-dropdown-submenu .is-dropdown-submenu-parent>a::after{right:14px}.is-dropdown-submenu .is-dropdown-submenu-parent.opens-left>a::after{display:block;width:0;height:0;border-style:solid;border-width:6px;content:'';border-left-width:0;border-color:transparent #1779ba transparent transparent;right:auto;left:5px}.is-dropdown-submenu .is-dropdown-submenu-parent.opens-right>a::after{display:block;width:0;height:0;border-style:solid;border-width:6px;content:'';border-right-width:0;border-color:transparent transparent transparent #1779ba}.is-dropdown-submenu .is-dropdown-submenu{margin-top:-1px}.is-dropdown-submenu>li{width:100%}.is-dropdown-submenu.js-dropdown-active{display:block}.is-off-canvas-open{overflow:hidden}.js-off-canvas-overlay{position:absolute;top:0;left:0;z-index:11;width:100%;height:100%;-webkit-transition:opacity .5s ease,visibility .5s ease;transition:opacity .5s ease,visibility .5s ease;background:rgba(254,254,254,.25);opacity:0;visibility:hidden;overflow:hidden}.js-off-canvas-overlay.is-visible{opacity:1;visibility:visible}.js-off-canvas-overlay.is-closable{cursor:pointer}.js-off-canvas-overlay.is-overlay-absolute{position:absolute}.js-off-canvas-overlay.is-overlay-fixed{position:fixed}.off-canvas-wrapper{position:relative;overflow:hidden}.off-canvas{position:fixed;z-index:12;-webkit-transition:-webkit-transform .5s ease;transition:-webkit-transform .5s ease;transition:transform .5s ease;transition:transform .5s ease,-webkit-transform .5s ease;-webkit-backface-visibility:hidden;backface-visibility:hidden;background:#e6e6e6}[data-whatinput=mouse] .off-canvas{outline:0}.off-canvas.is-transition-push{z-index:12}.off-canvas.is-closed{visibility:hidden}.off-canvas.is-transition-overlap{z-index:13}.off-canvas.is-transition-overlap.is-open{-webkit-box-shadow:0 0 10px rgba(10,10,10,.7);box-shadow:0 0 10px rgba(10,10,10,.7)}.off-canvas.is-open{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}.off-canvas-absolute{position:absolute;z-index:12;-webkit-transition:-webkit-transform .5s ease;transition:-webkit-transform .5s ease;transition:transform .5s ease;transition:transform .5s ease,-webkit-transform .5s ease;-webkit-backface-visibility:hidden;backface-visibility:hidden;background:#e6e6e6}[data-whatinput=mouse] .off-canvas-absolute{outline:0}.off-canvas-absolute.is-transition-push{z-index:12}.off-canvas-absolute.is-closed{visibility:hidden}.off-canvas-absolute.is-transition-overlap{z-index:13}.off-canvas-absolute.is-transition-overlap.is-open{-webkit-box-shadow:0 0 10px rgba(10,10,10,.7);box-shadow:0 0 10px rgba(10,10,10,.7)}.off-canvas-absolute.is-open{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}.position-left{top:0;left:0;height:100%;overflow-y:auto;width:250px;-webkit-transform:translateX(-250px);-ms-transform:translateX(-250px);transform:translateX(-250px)}.off-canvas-content .off-canvas.position-left{-webkit-transform:translateX(-250px);-ms-transform:translateX(-250px);transform:translateX(-250px)}.off-canvas-content .off-canvas.position-left.is-transition-overlap.is-open{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}.off-canvas-content.is-open-left.has-transition-push{-webkit-transform:translateX(250px);-ms-transform:translateX(250px);transform:translateX(250px)}.position-left.is-transition-push{-webkit-box-shadow:inset -13px 0 20px -13px rgba(10,10,10,.25);box-shadow:inset -13px 0 20px -13px rgba(10,10,10,.25)}.position-right{top:0;right:0;height:100%;overflow-y:auto;width:250px;-webkit-transform:translateX(250px);-ms-transform:translateX(250px);transform:translateX(250px)}.off-canvas-content .off-canvas.position-right{-webkit-transform:translateX(250px);-ms-transform:translateX(250px);transform:translateX(250px)}.off-canvas-content .off-canvas.position-right.is-transition-overlap.is-open{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}.off-canvas-content.is-open-right.has-transition-push{-webkit-transform:translateX(-250px);-ms-transform:translateX(-250px);transform:translateX(-250px)}.position-right.is-transition-push{-webkit-box-shadow:inset 13px 0 20px -13px rgba(10,10,10,.25);box-shadow:inset 13px 0 20px -13px rgba(10,10,10,.25)}.position-top{top:0;left:0;width:100%;overflow-x:auto;height:250px;-webkit-transform:translateY(-250px);-ms-transform:translateY(-250px);transform:translateY(-250px)}.off-canvas-content .off-canvas.position-top{-webkit-transform:translateY(-250px);-ms-transform:translateY(-250px);transform:translateY(-250px)}.off-canvas-content .off-canvas.position-top.is-transition-overlap.is-open{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}.off-canvas-content.is-open-top.has-transition-push{-webkit-transform:translateY(250px);-ms-transform:translateY(250px);transform:translateY(250px)}.position-top.is-transition-push{-webkit-box-shadow:inset 0 -13px 20px -13px rgba(10,10,10,.25);box-shadow:inset 0 -13px 20px -13px rgba(10,10,10,.25)}.position-bottom{bottom:0;left:0;width:100%;overflow-x:auto;height:250px;-webkit-transform:translateY(250px);-ms-transform:translateY(250px);transform:translateY(250px)}.off-canvas-content .off-canvas.position-bottom{-webkit-transform:translateY(250px);-ms-transform:translateY(250px);transform:translateY(250px)}.off-canvas-content .off-canvas.position-bottom.is-transition-overlap.is-open{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}.off-canvas-content.is-open-bottom.has-transition-push{-webkit-transform:translateY(-250px);-ms-transform:translateY(-250px);transform:translateY(-250px)}.position-bottom.is-transition-push{-webkit-box-shadow:inset 0 13px 20px -13px rgba(10,10,10,.25);box-shadow:inset 0 13px 20px -13px rgba(10,10,10,.25)}.off-canvas-content{-webkit-transform:none;-ms-transform:none;transform:none;-webkit-backface-visibility:hidden;backface-visibility:hidden}.off-canvas-content.has-transition-overlap,.off-canvas-content.has-transition-push{-webkit-transition:-webkit-transform .5s ease;transition:-webkit-transform .5s ease;transition:transform .5s ease;transition:transform .5s ease,-webkit-transform .5s ease}.off-canvas-content.has-transition-push{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}.off-canvas-content .off-canvas.is-open{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}@media print,screen and (min-width:40em){.position-left.reveal-for-medium{-webkit-transform:none;-ms-transform:none;transform:none;z-index:12;-webkit-transition:none;transition:none;visibility:visible}.position-left.reveal-for-medium .close-button{display:none}.off-canvas-content .position-left.reveal-for-medium{-webkit-transform:none;-ms-transform:none;transform:none}.off-canvas-content.has-reveal-left{margin-left:250px}.position-left.reveal-for-medium~.off-canvas-content{margin-left:250px}.position-right.reveal-for-medium{-webkit-transform:none;-ms-transform:none;transform:none;z-index:12;-webkit-transition:none;transition:none;visibility:visible}.position-right.reveal-for-medium .close-button{display:none}.off-canvas-content .position-right.reveal-for-medium{-webkit-transform:none;-ms-transform:none;transform:none}.off-canvas-content.has-reveal-right{margin-right:250px}.position-right.reveal-for-medium~.off-canvas-content{margin-right:250px}.position-top.reveal-for-medium{-webkit-transform:none;-ms-transform:none;transform:none;z-index:12;-webkit-transition:none;transition:none;visibility:visible}.position-top.reveal-for-medium .close-button{display:none}.off-canvas-content .position-top.reveal-for-medium{-webkit-transform:none;-ms-transform:none;transform:none}.off-canvas-content.has-reveal-top{margin-top:250px}.position-top.reveal-for-medium~.off-canvas-content{margin-top:250px}.position-bottom.reveal-for-medium{-webkit-transform:none;-ms-transform:none;transform:none;z-index:12;-webkit-transition:none;transition:none;visibility:visible}.position-bottom.reveal-for-medium .close-button{display:none}.off-canvas-content .position-bottom.reveal-for-medium{-webkit-transform:none;-ms-transform:none;transform:none}.off-canvas-content.has-reveal-bottom{margin-bottom:250px}.position-bottom.reveal-for-medium~.off-canvas-content{margin-bottom:250px}}@media print,screen and (min-width:64em){.position-left.reveal-for-large{-webkit-transform:none;-ms-transform:none;transform:none;z-index:12;-webkit-transition:none;transition:none;visibility:visible}.position-left.reveal-for-large .close-button{display:none}.off-canvas-content .position-left.reveal-for-large{-webkit-transform:none;-ms-transform:none;transform:none}.off-canvas-content.has-reveal-left{margin-left:250px}.position-left.reveal-for-large~.off-canvas-content{margin-left:250px}.position-right.reveal-for-large{-webkit-transform:none;-ms-transform:none;transform:none;z-index:12;-webkit-transition:none;transition:none;visibility:visible}.position-right.reveal-for-large .close-button{display:none}.off-canvas-content .position-right.reveal-for-large{-webkit-transform:none;-ms-transform:none;transform:none}.off-canvas-content.has-reveal-right{margin-right:250px}.position-right.reveal-for-large~.off-canvas-content{margin-right:250px}.position-top.reveal-for-large{-webkit-transform:none;-ms-transform:none;transform:none;z-index:12;-webkit-transition:none;transition:none;visibility:visible}.position-top.reveal-for-large .close-button{display:none}.off-canvas-content .position-top.reveal-for-large{-webkit-transform:none;-ms-transform:none;transform:none}.off-canvas-content.has-reveal-top{margin-top:250px}.position-top.reveal-for-large~.off-canvas-content{margin-top:250px}.position-bottom.reveal-for-large{-webkit-transform:none;-ms-transform:none;transform:none;z-index:12;-webkit-transition:none;transition:none;visibility:visible}.position-bottom.reveal-for-large .close-button{display:none}.off-canvas-content .position-bottom.reveal-for-large{-webkit-transform:none;-ms-transform:none;transform:none}.off-canvas-content.has-reveal-bottom{margin-bottom:250px}.position-bottom.reveal-for-large~.off-canvas-content{margin-bottom:250px}}@media print,screen and (min-width:40em){.off-canvas.in-canvas-for-medium{visibility:visible;height:auto;position:static;background:0 0;width:auto;overflow:visible;-webkit-transition:none;transition:none}.off-canvas.in-canvas-for-medium.position-bottom,.off-canvas.in-canvas-for-medium.position-left,.off-canvas.in-canvas-for-medium.position-right,.off-canvas.in-canvas-for-medium.position-top{-webkit-box-shadow:none;box-shadow:none;-webkit-transform:none;-ms-transform:none;transform:none}.off-canvas.in-canvas-for-medium .close-button{display:none}}@media print,screen and (min-width:64em){.off-canvas.in-canvas-for-large{visibility:visible;height:auto;position:static;background:0 0;width:auto;overflow:visible;-webkit-transition:none;transition:none}.off-canvas.in-canvas-for-large.position-bottom,.off-canvas.in-canvas-for-large.position-left,.off-canvas.in-canvas-for-large.position-right,.off-canvas.in-canvas-for-large.position-top{-webkit-box-shadow:none;box-shadow:none;-webkit-transform:none;-ms-transform:none;transform:none}.off-canvas.in-canvas-for-large .close-button{display:none}}html.is-reveal-open{position:fixed;width:100%;overflow-y:hidden}html.is-reveal-open.zf-has-scroll{overflow-y:scroll}html.is-reveal-open body{overflow-y:hidden}.reveal-overlay{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1005;display:none;background-color:rgba(10,10,10,.45);overflow-y:auto}.reveal{z-index:1006;-webkit-backface-visibility:hidden;backface-visibility:hidden;display:none;padding:1rem;border:1px solid #cacaca;border-radius:0;background-color:#fefefe;position:relative;top:100px;margin-right:auto;margin-left:auto;overflow-y:auto}[data-whatinput=mouse] .reveal{outline:0}@media print,screen and (min-width:40em){.reveal{min-height:0}}.reveal .column{min-width:0}.reveal>:last-child{margin-bottom:0}@media print,screen and (min-width:40em){.reveal{width:600px;max-width:75rem}}.reveal.collapse{padding:0}@media print,screen and (min-width:40em){.reveal.tiny{width:30%;max-width:75rem}}@media print,screen and (min-width:40em){.reveal.small{width:50%;max-width:75rem}}@media print,screen and (min-width:40em){.reveal.large{width:90%;max-width:75rem}}.reveal.full{top:0;right:0;bottom:0;left:0;width:100%;max-width:none;height:100%;min-height:100%;margin-left:0;border:0;border-radius:0}@media print,screen and (max-width:39.99875em){.reveal{top:0;right:0;bottom:0;left:0;width:100%;max-width:none;height:100%;min-height:100%;margin-left:0;border:0;border-radius:0}}.reveal.without-overlay{position:fixed}.sticky-container{position:relative}.sticky{position:relative;z-index:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.sticky.is-stuck{position:fixed;z-index:5;width:100%}.sticky.is-stuck.is-at-top{top:0}.sticky.is-stuck.is-at-bottom{bottom:0}.sticky.is-anchored{position:relative;right:auto;left:auto}.sticky.is-anchored.is-at-bottom{bottom:0}.title-bar{padding:.5rem;background:#0a0a0a;color:#fefefe;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.title-bar .menu-icon{margin-left:.25rem;margin-right:.25rem}.title-bar-left,.title-bar-right{-webkit-box-flex:1;-webkit-flex:1 1 0px;-ms-flex:1 1 0px;flex:1 1 0px}.title-bar-right{text-align:right}.title-bar-title{display:inline-block;vertical-align:middle;font-weight:700}.top-bar{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;padding:.5rem;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.top-bar,.top-bar ul{background-color:#e6e6e6}.top-bar input{max-width:200px;margin-right:1rem}.top-bar .input-group-field{width:100%;margin-right:0}.top-bar input.button{width:auto}.top-bar .top-bar-left,.top-bar .top-bar-right{-webkit-box-flex:0;-webkit-flex:0 0 100%;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}@media print,screen and (min-width:40em){.top-bar{-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap}.top-bar .top-bar-left{-webkit-box-flex:1;-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto;margin-right:auto}.top-bar .top-bar-right{-webkit-box-flex:0;-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto;margin-left:auto}}@media print,screen and (max-width:63.99875em){.top-bar.stacked-for-medium{-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.top-bar.stacked-for-medium .top-bar-left,.top-bar.stacked-for-medium .top-bar-right{-webkit-box-flex:0;-webkit-flex:0 0 100%;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}}@media print,screen and (max-width:74.99875em){.top-bar.stacked-for-large{-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.top-bar.stacked-for-large .top-bar-left,.top-bar.stacked-for-large .top-bar-right{-webkit-box-flex:0;-webkit-flex:0 0 100%;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}}.top-bar-title{-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;margin:.5rem 1rem .5rem 0}.top-bar-left,.top-bar-right{-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto}.float-left{float:left!important}.float-right{float:right!important}.float-center{display:block;margin-right:auto;margin-left:auto}.clearfix::after,.clearfix::before{display:table;content:' ';-webkit-flex-basis:0;-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-ordinal-group:2;-webkit-order:1;-ms-flex-order:1;order:1}.clearfix::after{clear:both}.align-left{-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start}.align-right{-webkit-box-pack:end;-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end}.align-center{-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}.align-justify{-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.align-spaced{-webkit-justify-content:space-around;-ms-flex-pack:distribute;justify-content:space-around}.align-left.vertical.menu>li>a{-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start}.align-right.vertical.menu>li>a{-webkit-box-pack:end;-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end}.align-center.vertical.menu>li>a{-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}.align-top{-webkit-box-align:start;-webkit-align-items:flex-start;-ms-flex-align:start;align-items:flex-start}.align-self-top{-webkit-align-self:flex-start;-ms-flex-item-align:start;align-self:flex-start}.align-bottom{-webkit-box-align:end;-webkit-align-items:flex-end;-ms-flex-align:end;align-items:flex-end}.align-self-bottom{-webkit-align-self:flex-end;-ms-flex-item-align:end;align-self:flex-end}.align-middle{-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.align-self-middle{-webkit-align-self:center;-ms-flex-item-align:center;align-self:center}.align-stretch{-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch}.align-self-stretch{-webkit-align-self:stretch;-ms-flex-item-align:stretch;align-self:stretch}.align-center-middle{-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-align-content:center;-ms-flex-line-pack:center;align-content:center}.small-order-1{-webkit-box-ordinal-group:2;-webkit-order:1;-ms-flex-order:1;order:1}.small-order-2{-webkit-box-ordinal-group:3;-webkit-order:2;-ms-flex-order:2;order:2}.small-order-3{-webkit-box-ordinal-group:4;-webkit-order:3;-ms-flex-order:3;order:3}.small-order-4{-webkit-box-ordinal-group:5;-webkit-order:4;-ms-flex-order:4;order:4}.small-order-5{-webkit-box-ordinal-group:6;-webkit-order:5;-ms-flex-order:5;order:5}.small-order-6{-webkit-box-ordinal-group:7;-webkit-order:6;-ms-flex-order:6;order:6}@media print,screen and (min-width:40em){.medium-order-1{-webkit-box-ordinal-group:2;-webkit-order:1;-ms-flex-order:1;order:1}.medium-order-2{-webkit-box-ordinal-group:3;-webkit-order:2;-ms-flex-order:2;order:2}.medium-order-3{-webkit-box-ordinal-group:4;-webkit-order:3;-ms-flex-order:3;order:3}.medium-order-4{-webkit-box-ordinal-group:5;-webkit-order:4;-ms-flex-order:4;order:4}.medium-order-5{-webkit-box-ordinal-group:6;-webkit-order:5;-ms-flex-order:5;order:5}.medium-order-6{-webkit-box-ordinal-group:7;-webkit-order:6;-ms-flex-order:6;order:6}}@media print,screen and (min-width:64em){.large-order-1{-webkit-box-ordinal-group:2;-webkit-order:1;-ms-flex-order:1;order:1}.large-order-2{-webkit-box-ordinal-group:3;-webkit-order:2;-ms-flex-order:2;order:2}.large-order-3{-webkit-box-ordinal-group:4;-webkit-order:3;-ms-flex-order:3;order:3}.large-order-4{-webkit-box-ordinal-group:5;-webkit-order:4;-ms-flex-order:4;order:4}.large-order-5{-webkit-box-ordinal-group:6;-webkit-order:5;-ms-flex-order:5;order:5}.large-order-6{-webkit-box-ordinal-group:7;-webkit-order:6;-ms-flex-order:6;order:6}}.flex-container{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.flex-child-auto{-webkit-box-flex:1;-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto}.flex-child-grow{-webkit-box-flex:1;-webkit-flex:1 0 auto;-ms-flex:1 0 auto;flex:1 0 auto}.flex-child-shrink{-webkit-box-flex:0;-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto}.flex-dir-row{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.flex-dir-row-reverse{-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-webkit-flex-direction:row-reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}.flex-dir-column{-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}.flex-dir-column-reverse{-webkit-box-orient:vertical;-webkit-box-direction:reverse;-webkit-flex-direction:column-reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}@media print,screen and (min-width:40em){.medium-flex-container{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.medium-flex-child-auto{-webkit-box-flex:1;-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto}.medium-flex-child-grow{-webkit-box-flex:1;-webkit-flex:1 0 auto;-ms-flex:1 0 auto;flex:1 0 auto}.medium-flex-child-shrink{-webkit-box-flex:0;-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto}.medium-flex-dir-row{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.medium-flex-dir-row-reverse{-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-webkit-flex-direction:row-reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}.medium-flex-dir-column{-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}.medium-flex-dir-column-reverse{-webkit-box-orient:vertical;-webkit-box-direction:reverse;-webkit-flex-direction:column-reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}}@media print,screen and (min-width:64em){.large-flex-container{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.large-flex-child-auto{-webkit-box-flex:1;-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto}.large-flex-child-grow{-webkit-box-flex:1;-webkit-flex:1 0 auto;-ms-flex:1 0 auto;flex:1 0 auto}.large-flex-child-shrink{-webkit-box-flex:0;-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto}.large-flex-dir-row{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.large-flex-dir-row-reverse{-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-webkit-flex-direction:row-reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}.large-flex-dir-column{-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}.large-flex-dir-column-reverse{-webkit-box-orient:vertical;-webkit-box-direction:reverse;-webkit-flex-direction:column-reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}}.hide{display:none!important}.invisible{visibility:hidden}.visible{visibility:visible}@media print,screen and (max-width:39.99875em){.hide-for-small-only{display:none!important}}@media screen and (max-width:0em),screen and (min-width:40em){.show-for-small-only{display:none!important}}@media print,screen and (min-width:40em){.hide-for-medium{display:none!important}}@media screen and (max-width:39.99875em){.show-for-medium{display:none!important}}@media print,screen and (min-width:40em) and (max-width:63.99875em){.hide-for-medium-only{display:none!important}}@media screen and (max-width:39.99875em),screen and (min-width:64em){.show-for-medium-only{display:none!important}}@media print,screen and (min-width:64em){.hide-for-large{display:none!important}}@media screen and (max-width:63.99875em){.show-for-large{display:none!important}}@media print,screen and (min-width:64em) and (max-width:74.99875em){.hide-for-large-only{display:none!important}}@media screen and (max-width:63.99875em),screen and (min-width:75em){.show-for-large-only{display:none!important}}.show-for-sr,.show-on-focus{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.show-on-focus:active,.show-on-focus:focus{position:static!important;width:auto!important;height:auto!important;overflow:visible!important;clip:auto!important;white-space:normal!important}.hide-for-portrait,.show-for-landscape{display:block!important}@media screen and (orientation:landscape){.hide-for-portrait,.show-for-landscape{display:block!important}}@media screen and (orientation:portrait){.hide-for-portrait,.show-for-landscape{display:none!important}}.hide-for-landscape,.show-for-portrait{display:none!important}@media screen and (orientation:landscape){.hide-for-landscape,.show-for-portrait{display:none!important}}@media screen and (orientation:portrait){.hide-for-landscape,.show-for-portrait{display:block!important}}
+/*# sourceMappingURL=foundation.min.css.map */
diff --git a/pkgs/csslib/third_party/html5-boilerplate/LICENSE.txt b/pkgs/csslib/third_party/html5-boilerplate/LICENSE.txt
new file mode 100644
index 0000000..294e91d
--- /dev/null
+++ b/pkgs/csslib/third_party/html5-boilerplate/LICENSE.txt
@@ -0,0 +1,19 @@
+Copyright (c) HTML5 Boilerplate
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/pkgs/csslib/third_party/html5-boilerplate/README.md b/pkgs/csslib/third_party/html5-boilerplate/README.md
new file mode 100644
index 0000000..3ca556d
--- /dev/null
+++ b/pkgs/csslib/third_party/html5-boilerplate/README.md
@@ -0,0 +1,4 @@
+This folder contains sample css files from the open-source project
+https://github.com/h5bp/html5-boilerplate.
+
+This code was included under the terms in the `LICENSE.txt` file.
\ No newline at end of file
diff --git a/pkgs/csslib/third_party/html5-boilerplate/normalize.css b/pkgs/csslib/third_party/html5-boilerplate/normalize.css
new file mode 100644
index 0000000..192eb9c
--- /dev/null
+++ b/pkgs/csslib/third_party/html5-boilerplate/normalize.css
@@ -0,0 +1,349 @@
+/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
+
+/* Document
+ ========================================================================== */
+
+/**
+ * 1. Correct the line height in all browsers.
+ * 2. Prevent adjustments of font size after orientation changes in iOS.
+ */
+
+html {
+ line-height: 1.15; /* 1 */
+ -webkit-text-size-adjust: 100%; /* 2 */
+}
+
+/* Sections
+ ========================================================================== */
+
+/**
+ * Remove the margin in all browsers.
+ */
+
+body {
+ margin: 0;
+}
+
+/**
+ * Render the `main` element consistently in IE.
+ */
+
+main {
+ display: block;
+}
+
+/**
+ * Correct the font size and margin on `h1` elements within `section` and
+ * `article` contexts in Chrome, Firefox, and Safari.
+ */
+
+h1 {
+ font-size: 2em;
+ margin: 0.67em 0;
+}
+
+/* Grouping content
+ ========================================================================== */
+
+/**
+ * 1. Add the correct box sizing in Firefox.
+ * 2. Show the overflow in Edge and IE.
+ */
+
+hr {
+ box-sizing: content-box; /* 1 */
+ height: 0; /* 1 */
+ overflow: visible; /* 2 */
+}
+
+/**
+ * 1. Correct the inheritance and scaling of font size in all browsers.
+ * 2. Correct the odd `em` font sizing in all browsers.
+ */
+
+pre {
+ font-family: monospace, monospace; /* 1 */
+ font-size: 1em; /* 2 */
+}
+
+/* Text-level semantics
+ ========================================================================== */
+
+/**
+ * Remove the gray background on active links in IE 10.
+ */
+
+a {
+ background-color: transparent;
+}
+
+/**
+ * 1. Remove the bottom border in Chrome 57-
+ * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
+ */
+
+abbr[title] {
+ border-bottom: none; /* 1 */
+ text-decoration: underline; /* 2 */
+ text-decoration: underline dotted; /* 2 */
+}
+
+/**
+ * Add the correct font weight in Chrome, Edge, and Safari.
+ */
+
+b,
+strong {
+ font-weight: bolder;
+}
+
+/**
+ * 1. Correct the inheritance and scaling of font size in all browsers.
+ * 2. Correct the odd `em` font sizing in all browsers.
+ */
+
+code,
+kbd,
+samp {
+ font-family: monospace, monospace; /* 1 */
+ font-size: 1em; /* 2 */
+}
+
+/**
+ * Add the correct font size in all browsers.
+ */
+
+small {
+ font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` elements from affecting the line height in
+ * all browsers.
+ */
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+sup {
+ top: -0.5em;
+}
+
+/* Embedded content
+ ========================================================================== */
+
+/**
+ * Remove the border on images inside links in IE 10.
+ */
+
+img {
+ border-style: none;
+}
+
+/* Forms
+ ========================================================================== */
+
+/**
+ * 1. Change the font styles in all browsers.
+ * 2. Remove the margin in Firefox and Safari.
+ */
+
+button,
+input,
+optgroup,
+select,
+textarea {
+ font-family: inherit; /* 1 */
+ font-size: 100%; /* 1 */
+ line-height: 1.15; /* 1 */
+ margin: 0; /* 2 */
+}
+
+/**
+ * Show the overflow in IE.
+ * 1. Show the overflow in Edge.
+ */
+
+button,
+input { /* 1 */
+ overflow: visible;
+}
+
+/**
+ * Remove the inheritance of text transform in Edge, Firefox, and IE.
+ * 1. Remove the inheritance of text transform in Firefox.
+ */
+
+button,
+select { /* 1 */
+ text-transform: none;
+}
+
+/**
+ * Correct the inability to style clickable types in iOS and Safari.
+ */
+
+button,
+[type="button"],
+[type="reset"],
+[type="submit"] {
+ -webkit-appearance: button;
+}
+
+/**
+ * Remove the inner border and padding in Firefox.
+ */
+
+button::-moz-focus-inner,
+[type="button"]::-moz-focus-inner,
+[type="reset"]::-moz-focus-inner,
+[type="submit"]::-moz-focus-inner {
+ border-style: none;
+ padding: 0;
+}
+
+/**
+ * Restore the focus styles unset by the previous rule.
+ */
+
+button:-moz-focusring,
+[type="button"]:-moz-focusring,
+[type="reset"]:-moz-focusring,
+[type="submit"]:-moz-focusring {
+ outline: 1px dotted ButtonText;
+}
+
+/**
+ * Correct the padding in Firefox.
+ */
+
+fieldset {
+ padding: 0.35em 0.75em 0.625em;
+}
+
+/**
+ * 1. Correct the text wrapping in Edge and IE.
+ * 2. Correct the color inheritance from `fieldset` elements in IE.
+ * 3. Remove the padding so developers are not caught out when they zero out
+ * `fieldset` elements in all browsers.
+ */
+
+legend {
+ box-sizing: border-box; /* 1 */
+ color: inherit; /* 2 */
+ display: table; /* 1 */
+ max-width: 100%; /* 1 */
+ padding: 0; /* 3 */
+ white-space: normal; /* 1 */
+}
+
+/**
+ * Add the correct vertical alignment in Chrome, Firefox, and Opera.
+ */
+
+progress {
+ vertical-align: baseline;
+}
+
+/**
+ * Remove the default vertical scrollbar in IE 10+.
+ */
+
+textarea {
+ overflow: auto;
+}
+
+/**
+ * 1. Add the correct box sizing in IE 10.
+ * 2. Remove the padding in IE 10.
+ */
+
+[type="checkbox"],
+[type="radio"] {
+ box-sizing: border-box; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * Correct the cursor style of increment and decrement buttons in Chrome.
+ */
+
+[type="number"]::-webkit-inner-spin-button,
+[type="number"]::-webkit-outer-spin-button {
+ height: auto;
+}
+
+/**
+ * 1. Correct the odd appearance in Chrome and Safari.
+ * 2. Correct the outline style in Safari.
+ */
+
+[type="search"] {
+ -webkit-appearance: textfield; /* 1 */
+ outline-offset: -2px; /* 2 */
+}
+
+/**
+ * Remove the inner padding in Chrome and Safari on macOS.
+ */
+
+[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/**
+ * 1. Correct the inability to style clickable types in iOS and Safari.
+ * 2. Change font properties to `inherit` in Safari.
+ */
+
+::-webkit-file-upload-button {
+ -webkit-appearance: button; /* 1 */
+ font: inherit; /* 2 */
+}
+
+/* Interactive
+ ========================================================================== */
+
+/*
+ * Add the correct display in Edge, IE 10+, and Firefox.
+ */
+
+details {
+ display: block;
+}
+
+/*
+ * Add the correct display in all browsers.
+ */
+
+summary {
+ display: list-item;
+}
+
+/* Misc
+ ========================================================================== */
+
+/**
+ * Add the correct display in IE 10+.
+ */
+
+template {
+ display: none;
+}
+
+/**
+ * Add the correct display in IE 10.
+ */
+
+[hidden] {
+ display: none;
+}
diff --git a/pkgs/csslib/third_party/html5-boilerplate/style.css b/pkgs/csslib/third_party/html5-boilerplate/style.css
new file mode 100644
index 0000000..416a37e
--- /dev/null
+++ b/pkgs/csslib/third_party/html5-boilerplate/style.css
@@ -0,0 +1,263 @@
+/*! HTML5 Boilerplate v8.0.0 | MIT License | https://html5boilerplate.com/ */
+
+/* main.css 2.1.0 | MIT License | https://github.com/h5bp/main.css#readme */
+/*
+ * What follows is the result of much research on cross-browser styling.
+ * Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal,
+ * Kroc Camen, and the H5BP dev community and team.
+ */
+
+/* ==========================================================================
+ Base styles: opinionated defaults
+ ========================================================================== */
+
+html {
+ color: #222;
+ font-size: 1em;
+ line-height: 1.4;
+}
+
+/*
+ * Remove text-shadow in selection highlight:
+ * https://twitter.com/miketaylr/status/12228805301
+ *
+ * Vendor-prefixed and regular ::selection selectors cannot be combined:
+ * https://stackoverflow.com/a/16982510/7133471
+ *
+ * Customize the background color to match your design.
+ */
+
+::-moz-selection {
+ background: #b3d4fc;
+ text-shadow: none;
+}
+
+::selection {
+ background: #b3d4fc;
+ text-shadow: none;
+}
+
+/*
+ * A better looking default horizontal rule
+ */
+
+hr {
+ display: block;
+ height: 1px;
+ border: 0;
+ border-top: 1px solid #ccc;
+ margin: 1em 0;
+ padding: 0;
+}
+
+/*
+ * Remove the gap between audio, canvas, iframes,
+ * images, videos and the bottom of their containers:
+ * https://github.com/h5bp/html5-boilerplate/issues/440
+ */
+
+audio,
+canvas,
+iframe,
+img,
+svg,
+video {
+ vertical-align: middle;
+}
+
+/*
+ * Remove default fieldset styles.
+ */
+
+fieldset {
+ border: 0;
+ margin: 0;
+ padding: 0;
+}
+
+/*
+ * Allow only vertical resizing of textareas.
+ */
+
+textarea {
+ resize: vertical;
+}
+
+/* ==========================================================================
+ Author's custom styles
+ ========================================================================== */
+
+/* ==========================================================================
+ Helper classes
+ ========================================================================== */
+
+/*
+ * Hide visually and from screen readers
+ */
+
+.hidden,
+[hidden] {
+ display: none !important;
+}
+
+/*
+ * Hide only visually, but have it available for screen readers:
+ * https://snook.ca/archives/html_and_css/hiding-content-for-accessibility
+ *
+ * 1. For long content, line feeds are not interpreted as spaces and small width
+ * causes content to wrap 1 word per line:
+ * https://medium.com/@jessebeach/beware-smushed-off-screen-accessible-text-5952a4c2cbfe
+ */
+
+.sr-only {
+ border: 0;
+ clip: rect(0, 0, 0, 0);
+ height: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ white-space: nowrap;
+ width: 1px;
+ /* 1 */
+}
+
+/*
+ * Extends the .sr-only class to allow the element
+ * to be focusable when navigated to via the keyboard:
+ * https://www.drupal.org/node/897638
+ */
+
+.sr-only.focusable:active,
+.sr-only.focusable:focus {
+ clip: auto;
+ height: auto;
+ margin: 0;
+ overflow: visible;
+ position: static;
+ white-space: inherit;
+ width: auto;
+}
+
+/*
+ * Hide visually and from screen readers, but maintain layout
+ */
+
+.invisible {
+ visibility: hidden;
+}
+
+/*
+ * Clearfix: contain floats
+ *
+ * For modern browsers
+ * 1. The space content is one way to avoid an Opera bug when the
+ * `contenteditable` attribute is included anywhere else in the document.
+ * Otherwise it causes space to appear at the top and bottom of elements
+ * that receive the `clearfix` class.
+ * 2. The use of `table` rather than `block` is only necessary if using
+ * `:before` to contain the top-margins of child elements.
+ */
+
+.clearfix::before,
+.clearfix::after {
+ content: " ";
+ display: table;
+}
+
+.clearfix::after {
+ clear: both;
+}
+
+/* ==========================================================================
+ EXAMPLE Media Queries for Responsive Design.
+ These examples override the primary ('mobile first') styles.
+ Modify as content requires.
+ ========================================================================== */
+
+@media only screen and (min-width: 35em) {
+ /* Style adjustments for viewports that meet the condition */
+}
+
+@media print,
+ (-webkit-min-device-pixel-ratio: 1.25),
+ (min-resolution: 1.25dppx),
+ (min-resolution: 120dpi) {
+ /* Style adjustments for high resolution devices */
+}
+
+/* ==========================================================================
+ Print styles.
+ Inlined to avoid the additional HTTP request:
+ https://www.phpied.com/delay-loading-your-print-css/
+ ========================================================================== */
+
+@media print {
+ *,
+ *::before,
+ *::after {
+ background: #fff !important;
+ color: #000 !important;
+ /* Black prints faster */
+ box-shadow: none !important;
+ text-shadow: none !important;
+ }
+
+ a,
+ a:visited {
+ text-decoration: underline;
+ }
+
+ a[href]::after {
+ content: " (" attr(href) ")";
+ }
+
+ abbr[title]::after {
+ content: " (" attr(title) ")";
+ }
+
+ /*
+ * Don't show links that are fragment identifiers,
+ * or use the `javascript:` pseudo protocol
+ */
+ a[href^="#"]::after,
+ a[href^="javascript:"]::after {
+ content: "";
+ }
+
+ pre {
+ white-space: pre-wrap !important;
+ }
+
+ pre,
+ blockquote {
+ border: 1px solid #999;
+ page-break-inside: avoid;
+ }
+
+ /*
+ * Printing Tables:
+ * https://web.archive.org/web/20180815150934/http://css-discuss.incutio.com/wiki/Printing_Tables
+ */
+ thead {
+ display: table-header-group;
+ }
+
+ tr,
+ img {
+ page-break-inside: avoid;
+ }
+
+ p,
+ h2,
+ h3 {
+ orphans: 3;
+ widows: 3;
+ }
+
+ h2,
+ h3 {
+ page-break-after: avoid;
+ }
+}
+
diff --git a/pkgs/csslib/third_party/materialize/LICENSE b/pkgs/csslib/third_party/materialize/LICENSE
new file mode 100644
index 0000000..c790fc2
--- /dev/null
+++ b/pkgs/csslib/third_party/materialize/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014-2019 Materialize
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/pkgs/csslib/third_party/materialize/README.md b/pkgs/csslib/third_party/materialize/README.md
new file mode 100644
index 0000000..2be2627
--- /dev/null
+++ b/pkgs/csslib/third_party/materialize/README.md
@@ -0,0 +1,4 @@
+This folder contains sample css files from the open-source project
+https://github.com/Dogfalo/materialize.
+
+This code was included under the terms in the `LICENSE` file.
\ No newline at end of file
diff --git a/pkgs/csslib/third_party/materialize/materialize.css b/pkgs/csslib/third_party/materialize/materialize.css
new file mode 100644
index 0000000..bc6c1fe
--- /dev/null
+++ b/pkgs/csslib/third_party/materialize/materialize.css
Binary files differ
diff --git a/pkgs/csslib/third_party/materialize/materialize.min.css b/pkgs/csslib/third_party/materialize/materialize.min.css
new file mode 100644
index 0000000..74b1741
--- /dev/null
+++ b/pkgs/csslib/third_party/materialize/materialize.min.css
Binary files differ
diff --git a/pkgs/csslib/third_party/mdc/LICENSE b/pkgs/csslib/third_party/mdc/LICENSE
new file mode 100644
index 0000000..0324838
--- /dev/null
+++ b/pkgs/csslib/third_party/mdc/LICENSE
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2014-2020 Google, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/pkgs/csslib/third_party/mdc/README.md b/pkgs/csslib/third_party/mdc/README.md
new file mode 100644
index 0000000..f388348
--- /dev/null
+++ b/pkgs/csslib/third_party/mdc/README.md
@@ -0,0 +1,7 @@
+This folder contains sample css files from the open-source project
+https://github.com/material-components/material-components-web.
+
+The generated .css files were retrieved from:
+https://unpkg.com/browse/material-components-web@12.0.0/dist/
+
+This code was included under the terms in the `LICENSE` file.
\ No newline at end of file
diff --git a/pkgs/csslib/third_party/mdc/material-components-web.css b/pkgs/csslib/third_party/mdc/material-components-web.css
new file mode 100644
index 0000000..2099afe
--- /dev/null
+++ b/pkgs/csslib/third_party/mdc/material-components-web.css
@@ -0,0 +1,16511 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://github.com/material-components/material-components-web/blob/master/LICENSE
+ */
+@charset "UTF-8";
+.mdc-banner__text {
+ color: #000;
+ /* @alternate */
+ color: var(--mdc-theme-on-surface, #000);
+}
+
+.mdc-banner__graphic {
+ color: #fff;
+ /* @alternate */
+ color: var(--mdc-theme-surface, #fff);
+}
+
+.mdc-banner__graphic {
+ background-color: #6200ee;
+ /* @alternate */
+ background-color: var(--mdc-theme-primary, #6200ee);
+}
+
+.mdc-banner__graphic {
+ border-radius: 50%;
+}
+
+.mdc-banner__content,
+.mdc-banner__fixed {
+ min-width: 344px;
+}
+@media (max-width: 480px), (max-width: 344px) {
+ .mdc-banner__content,
+.mdc-banner__fixed {
+ min-width: 100%;
+ }
+}
+
+.mdc-banner__content {
+ max-width: 720px;
+}
+
+.mdc-banner {
+ z-index: 1;
+ border-bottom-style: solid;
+ border-bottom-width: 1px;
+ box-sizing: border-box;
+ display: none;
+ flex-shrink: 0;
+ height: 0;
+ position: relative;
+ width: 100%;
+}
+@media (max-width: 480px) {
+ .mdc-banner .mdc-banner__fixed {
+ left: 0;
+ right: 0;
+ }
+ .mdc-banner .mdc-banner__text {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 36px;
+ }
+ [dir=rtl] .mdc-banner .mdc-banner__text, .mdc-banner .mdc-banner__text[dir=rtl] {
+ /* @noflip */
+ margin-left: 36px;
+ /* @noflip */
+ margin-right: 16px;
+ }
+}
+@media (max-width: 480px) {
+ .mdc-banner.mdc-banner--mobile-stacked .mdc-banner__content {
+ flex-wrap: wrap;
+ }
+ .mdc-banner.mdc-banner--mobile-stacked .mdc-banner__graphic {
+ margin-bottom: 12px;
+ }
+ .mdc-banner.mdc-banner--mobile-stacked .mdc-banner__text {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 8px;
+ padding-bottom: 4px;
+ }
+ [dir=rtl] .mdc-banner.mdc-banner--mobile-stacked .mdc-banner__text, .mdc-banner.mdc-banner--mobile-stacked .mdc-banner__text[dir=rtl] {
+ /* @noflip */
+ margin-left: 8px;
+ /* @noflip */
+ margin-right: 16px;
+ }
+
+ .mdc-banner.mdc-banner--mobile-stacked .mdc-banner__actions {
+ margin-left: auto;
+ }
+}
+
+.mdc-banner--opening,
+.mdc-banner--open,
+.mdc-banner--closing {
+ display: flex;
+}
+
+.mdc-banner--open {
+ transition: height 300ms ease;
+}
+.mdc-banner--open .mdc-banner__content {
+ transition: -webkit-transform 300ms ease;
+ transition: transform 300ms ease;
+ transition: transform 300ms ease, -webkit-transform 300ms ease;
+ -webkit-transform: translateY(0);
+ transform: translateY(0);
+}
+
+.mdc-banner--closing {
+ transition: height 250ms ease;
+}
+.mdc-banner--closing .mdc-banner__content {
+ transition: -webkit-transform 250ms ease;
+ transition: transform 250ms ease;
+ transition: transform 250ms ease, -webkit-transform 250ms ease;
+}
+
+.mdc-banner--centered .mdc-banner__content {
+ left: 0;
+ margin-left: auto;
+ margin-right: auto;
+ right: 0;
+}
+
+.mdc-banner__fixed {
+ border-bottom-style: solid;
+ border-bottom-width: 1px;
+ box-sizing: border-box;
+ height: inherit;
+ position: fixed;
+ width: 100%;
+}
+
+.mdc-banner__content {
+ display: flex;
+ min-height: 52px;
+ position: absolute;
+ -webkit-transform: translateY(-100%);
+ transform: translateY(-100%);
+ width: 100%;
+}
+
+.mdc-banner__graphic-text-wrapper {
+ display: flex;
+ width: 100%;
+}
+
+.mdc-banner__graphic {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 0;
+ flex-shrink: 0;
+ height: 40px;
+ margin-top: 16px;
+ margin-bottom: 16px;
+ text-align: center;
+ width: 40px;
+}
+[dir=rtl] .mdc-banner__graphic, .mdc-banner__graphic[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 16px;
+}
+
+.mdc-banner__icon {
+ position: relative;
+ top: 50%;
+ -webkit-transform: translateY(-50%);
+ transform: translateY(-50%);
+}
+
+.mdc-banner__text {
+ /* @noflip */
+ margin-left: 24px;
+ /* @noflip */
+ margin-right: 90px;
+ align-self: center;
+ flex-grow: 1;
+ padding-top: 16px;
+ padding-bottom: 16px;
+}
+[dir=rtl] .mdc-banner__text, .mdc-banner__text[dir=rtl] {
+ /* @noflip */
+ margin-left: 90px;
+ /* @noflip */
+ margin-right: 24px;
+}
+
+.mdc-banner__actions {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: 8px;
+ align-self: flex-end;
+ display: flex;
+ flex-shrink: 0;
+ padding-bottom: 8px;
+ padding-top: 8px;
+}
+[dir=rtl] .mdc-banner__actions, .mdc-banner__actions[dir=rtl] {
+ /* @noflip */
+ padding-left: 8px;
+ /* @noflip */
+ padding-right: 0;
+}
+
+.mdc-banner__secondary-action {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 8px;
+}
+[dir=rtl] .mdc-banner__secondary-action, .mdc-banner__secondary-action[dir=rtl] {
+ /* @noflip */
+ margin-left: 8px;
+ /* @noflip */
+ margin-right: 0;
+}
+
+.mdc-banner {
+ background-color: #fff;
+ /* @alternate */
+ background-color: var(--mdc-theme-surface, #fff);
+ border-bottom-color: rgba(0, 0, 0, 0.12);
+}
+.mdc-banner .mdc-banner__fixed {
+ background-color: #fff;
+ /* @alternate */
+ background-color: var(--mdc-theme-surface, #fff);
+}
+.mdc-banner .mdc-banner__fixed {
+ border-bottom-color: rgba(0, 0, 0, 0.12);
+}
+
+.mdc-banner__text {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.875rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-body2-font-size, 0.875rem);
+ line-height: 1.25rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-body2-line-height, 1.25rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-body2-font-weight, 400);
+ letter-spacing: 0.0178571429em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-body2-letter-spacing, 0.0178571429em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-body2-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-body2-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-body2-text-transform, inherit);
+}
+
+.mdc-banner__primary-action:not(:disabled) {
+ color: #6200ee;
+ /* @alternate */
+ color: var(--mdc-theme-primary, #6200ee);
+}
+.mdc-banner__primary-action::before, .mdc-banner__primary-action::after {
+ background-color: #6200ee;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee));
+}
+.mdc-banner__primary-action:hover::before, .mdc-banner__primary-action.mdc-ripple-surface--hover::before {
+ opacity: 0.04;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.04);
+}
+.mdc-banner__primary-action.mdc-ripple-upgraded--background-focused::before, .mdc-banner__primary-action:not(.mdc-ripple-upgraded):focus::before {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.12);
+}
+.mdc-banner__primary-action:not(.mdc-ripple-upgraded)::after {
+ transition: opacity 150ms linear;
+}
+.mdc-banner__primary-action:not(.mdc-ripple-upgraded):active::after {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-banner__primary-action.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+
+.mdc-banner__secondary-action {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 8px;
+}
+.mdc-banner__secondary-action:not(:disabled) {
+ color: #6200ee;
+ /* @alternate */
+ color: var(--mdc-theme-primary, #6200ee);
+}
+.mdc-banner__secondary-action::before, .mdc-banner__secondary-action::after {
+ background-color: #6200ee;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee));
+}
+.mdc-banner__secondary-action:hover::before, .mdc-banner__secondary-action.mdc-ripple-surface--hover::before {
+ opacity: 0.04;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.04);
+}
+.mdc-banner__secondary-action.mdc-ripple-upgraded--background-focused::before, .mdc-banner__secondary-action:not(.mdc-ripple-upgraded):focus::before {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.12);
+}
+.mdc-banner__secondary-action:not(.mdc-ripple-upgraded)::after {
+ transition: opacity 150ms linear;
+}
+.mdc-banner__secondary-action:not(.mdc-ripple-upgraded):active::after {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-banner__secondary-action.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+[dir=rtl] .mdc-banner__secondary-action, .mdc-banner__secondary-action[dir=rtl] {
+ /* @noflip */
+ margin-left: 8px;
+ /* @noflip */
+ margin-right: 0;
+}
+
+.mdc-touch-target-wrapper {
+ display: inline;
+}
+
+.mdc-elevation-overlay {
+ position: absolute;
+ border-radius: inherit;
+ pointer-events: none;
+ opacity: 0;
+ /* @alternate */
+ opacity: var(--mdc-elevation-overlay-opacity, 0);
+ transition: opacity 280ms cubic-bezier(0.4, 0, 0.2, 1);
+ background-color: #fff;
+ /* @alternate */
+ background-color: var(--mdc-elevation-overlay-color, #fff);
+}
+
+.mdc-button {
+ /* @alternate */
+ position: relative;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ box-sizing: border-box;
+ min-width: 64px;
+ border: none;
+ outline: none;
+ /* @alternate */
+ line-height: inherit;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ -webkit-appearance: none;
+ overflow: visible;
+ vertical-align: middle;
+ background: transparent;
+}
+.mdc-button .mdc-elevation-overlay {
+ width: 100%;
+ height: 100%;
+ top: 0;
+ /* @noflip */
+ left: 0;
+}
+.mdc-button::-moz-focus-inner {
+ padding: 0;
+ border: 0;
+}
+.mdc-button:active {
+ outline: none;
+}
+.mdc-button:hover {
+ cursor: pointer;
+}
+.mdc-button:disabled {
+ cursor: default;
+ pointer-events: none;
+}
+.mdc-button .mdc-button__icon {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 8px;
+ display: inline-block;
+ position: relative;
+ vertical-align: top;
+}
+[dir=rtl] .mdc-button .mdc-button__icon, .mdc-button .mdc-button__icon[dir=rtl] {
+ /* @noflip */
+ margin-left: 8px;
+ /* @noflip */
+ margin-right: 0;
+}
+
+.mdc-button .mdc-button__touch {
+ position: absolute;
+ top: 50%;
+ height: 48px;
+ left: 0;
+ right: 0;
+ -webkit-transform: translateY(-50%);
+ transform: translateY(-50%);
+}
+
+.mdc-button__label + .mdc-button__icon {
+ /* @noflip */
+ margin-left: 8px;
+ /* @noflip */
+ margin-right: 0;
+}
+[dir=rtl] .mdc-button__label + .mdc-button__icon, .mdc-button__label + .mdc-button__icon[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 8px;
+}
+
+svg.mdc-button__icon {
+ fill: currentColor;
+}
+
+.mdc-button--raised .mdc-button__icon,
+.mdc-button--unelevated .mdc-button__icon,
+.mdc-button--outlined .mdc-button__icon {
+ /* @noflip */
+ margin-left: -4px;
+ /* @noflip */
+ margin-right: 8px;
+}
+[dir=rtl] .mdc-button--raised .mdc-button__icon, [dir=rtl] .mdc-button--unelevated .mdc-button__icon, [dir=rtl] .mdc-button--outlined .mdc-button__icon, .mdc-button--raised .mdc-button__icon[dir=rtl], .mdc-button--unelevated .mdc-button__icon[dir=rtl], .mdc-button--outlined .mdc-button__icon[dir=rtl] {
+ /* @noflip */
+ margin-left: 8px;
+ /* @noflip */
+ margin-right: -4px;
+}
+
+.mdc-button--raised .mdc-button__label + .mdc-button__icon,
+.mdc-button--unelevated .mdc-button__label + .mdc-button__icon,
+.mdc-button--outlined .mdc-button__label + .mdc-button__icon {
+ /* @noflip */
+ margin-left: 8px;
+ /* @noflip */
+ margin-right: -4px;
+}
+[dir=rtl] .mdc-button--raised .mdc-button__label + .mdc-button__icon, [dir=rtl] .mdc-button--unelevated .mdc-button__label + .mdc-button__icon, [dir=rtl] .mdc-button--outlined .mdc-button__label + .mdc-button__icon, .mdc-button--raised .mdc-button__label + .mdc-button__icon[dir=rtl], .mdc-button--unelevated .mdc-button__label + .mdc-button__icon[dir=rtl], .mdc-button--outlined .mdc-button__label + .mdc-button__icon[dir=rtl] {
+ /* @noflip */
+ margin-left: -4px;
+ /* @noflip */
+ margin-right: 8px;
+}
+
+.mdc-button--touch {
+ margin-top: 6px;
+ margin-bottom: 6px;
+}
+
+.mdc-button {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-button-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ text-decoration: none;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-button-text-decoration, none);
+ text-decoration: var(--mdc-typography-button-text-decoration, none);
+}
+
+@-webkit-keyframes mdc-ripple-fg-radius-in {
+ from {
+ -webkit-animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ -webkit-transform: translate(var(--mdc-ripple-fg-translate-start, 0)) scale(1);
+ transform: translate(var(--mdc-ripple-fg-translate-start, 0)) scale(1);
+ }
+ to {
+ -webkit-transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+ transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+ }
+}
+
+@keyframes mdc-ripple-fg-radius-in {
+ from {
+ -webkit-animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ -webkit-transform: translate(var(--mdc-ripple-fg-translate-start, 0)) scale(1);
+ transform: translate(var(--mdc-ripple-fg-translate-start, 0)) scale(1);
+ }
+ to {
+ -webkit-transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+ transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+ }
+}
+@-webkit-keyframes mdc-ripple-fg-opacity-in {
+ from {
+ -webkit-animation-timing-function: linear;
+ animation-timing-function: linear;
+ opacity: 0;
+ }
+ to {
+ opacity: var(--mdc-ripple-fg-opacity, 0);
+ }
+}
+@keyframes mdc-ripple-fg-opacity-in {
+ from {
+ -webkit-animation-timing-function: linear;
+ animation-timing-function: linear;
+ opacity: 0;
+ }
+ to {
+ opacity: var(--mdc-ripple-fg-opacity, 0);
+ }
+}
+@-webkit-keyframes mdc-ripple-fg-opacity-out {
+ from {
+ -webkit-animation-timing-function: linear;
+ animation-timing-function: linear;
+ opacity: var(--mdc-ripple-fg-opacity, 0);
+ }
+ to {
+ opacity: 0;
+ }
+}
+@keyframes mdc-ripple-fg-opacity-out {
+ from {
+ -webkit-animation-timing-function: linear;
+ animation-timing-function: linear;
+ opacity: var(--mdc-ripple-fg-opacity, 0);
+ }
+ to {
+ opacity: 0;
+ }
+}
+.mdc-button {
+ --mdc-ripple-fg-size: 0;
+ --mdc-ripple-left: 0;
+ --mdc-ripple-top: 0;
+ --mdc-ripple-fg-scale: 1;
+ --mdc-ripple-fg-translate-end: 0;
+ --mdc-ripple-fg-translate-start: 0;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ will-change: transform, opacity;
+}
+.mdc-button .mdc-button__ripple::before,
+.mdc-button .mdc-button__ripple::after {
+ position: absolute;
+ border-radius: 50%;
+ opacity: 0;
+ pointer-events: none;
+ content: "";
+}
+.mdc-button .mdc-button__ripple::before {
+ transition: opacity 15ms linear, background-color 15ms linear;
+ z-index: 1;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 1);
+}
+.mdc-button .mdc-button__ripple::after {
+ z-index: 0;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 0);
+}
+.mdc-button.mdc-ripple-upgraded .mdc-button__ripple::before {
+ -webkit-transform: scale(var(--mdc-ripple-fg-scale, 1));
+ transform: scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-button.mdc-ripple-upgraded .mdc-button__ripple::after {
+ top: 0;
+ /* @noflip */
+ left: 0;
+ -webkit-transform: scale(0);
+ transform: scale(0);
+ -webkit-transform-origin: center center;
+ transform-origin: center center;
+}
+.mdc-button.mdc-ripple-upgraded--unbounded .mdc-button__ripple::after {
+ top: var(--mdc-ripple-top, 0);
+ /* @noflip */
+ left: var(--mdc-ripple-left, 0);
+}
+.mdc-button.mdc-ripple-upgraded--foreground-activation .mdc-button__ripple::after {
+ -webkit-animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+ animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+}
+.mdc-button.mdc-ripple-upgraded--foreground-deactivation .mdc-button__ripple::after {
+ -webkit-animation: mdc-ripple-fg-opacity-out 150ms;
+ animation: mdc-ripple-fg-opacity-out 150ms;
+ -webkit-transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+ transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-button .mdc-button__ripple::before,
+.mdc-button .mdc-button__ripple::after {
+ top: calc(50% - 100%);
+ /* @noflip */
+ left: calc(50% - 100%);
+ width: 200%;
+ height: 200%;
+}
+.mdc-button.mdc-ripple-upgraded .mdc-button__ripple::after {
+ width: var(--mdc-ripple-fg-size, 100%);
+ height: var(--mdc-ripple-fg-size, 100%);
+}
+.mdc-button .mdc-button__ripple {
+ position: absolute;
+ box-sizing: content-box;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+}
+.mdc-button:not(.mdc-button--outlined) .mdc-button__ripple {
+ top: 0;
+ left: 0;
+}
+
+.mdc-button--raised {
+ transition: box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.mdc-button--outlined {
+ border-style: solid;
+}
+
+.mdc-button {
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-button-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.875rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-button-font-size, 0.875rem);
+ letter-spacing: 0.0892857143em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-button-letter-spacing, 0.0892857143em);
+ font-weight: 500;
+ /* @alternate */
+ font-weight: var(--mdc-typography-button-font-weight, 500);
+ text-transform: uppercase;
+ /* @alternate */
+ text-transform: var(--mdc-typography-button-text-transform, uppercase);
+ height: 36px;
+ border-radius: 4px;
+ /* @alternate */
+ border-radius: var(--mdc-shape-small, 4px);
+ padding: 0 8px 0 8px;
+}
+.mdc-button:not(:disabled) {
+ color: #6200ee;
+ /* @alternate */
+ color: var(--mdc-theme-primary, #6200ee);
+}
+.mdc-button:disabled {
+ color: rgba(0, 0, 0, 0.38);
+}
+.mdc-button .mdc-button__icon {
+ font-size: 1.125rem;
+ height: 1.125rem;
+ width: 1.125rem;
+}
+.mdc-button .mdc-button__ripple::before,
+.mdc-button .mdc-button__ripple::after {
+ background-color: #6200ee;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee));
+}
+.mdc-button:hover .mdc-button__ripple::before, .mdc-button.mdc-ripple-surface--hover .mdc-button__ripple::before {
+ opacity: 0.04;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.04);
+}
+.mdc-button.mdc-ripple-upgraded--background-focused .mdc-button__ripple::before, .mdc-button:not(.mdc-ripple-upgraded):focus .mdc-button__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.12);
+}
+.mdc-button:not(.mdc-ripple-upgraded) .mdc-button__ripple::after {
+ transition: opacity 150ms linear;
+}
+.mdc-button:not(.mdc-ripple-upgraded):active .mdc-button__ripple::after {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-button.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-button .mdc-button__ripple {
+ border-radius: 4px;
+ /* @alternate */
+ border-radius: var(--mdc-shape-small, 4px);
+}
+
+.mdc-button--unelevated {
+ padding: 0 16px 0 16px;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-button-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.875rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-button-font-size, 0.875rem);
+ letter-spacing: 0.0892857143em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-button-letter-spacing, 0.0892857143em);
+ font-weight: 500;
+ /* @alternate */
+ font-weight: var(--mdc-typography-button-font-weight, 500);
+ text-transform: uppercase;
+ /* @alternate */
+ text-transform: var(--mdc-typography-button-text-transform, uppercase);
+ height: 36px;
+ border-radius: 4px;
+ /* @alternate */
+ border-radius: var(--mdc-shape-small, 4px);
+}
+.mdc-button--unelevated.mdc-button--icon-trailing {
+ padding: 0 12px 0 16px;
+}
+.mdc-button--unelevated.mdc-button--icon-leading {
+ padding: 0 16px 0 12px;
+}
+.mdc-button--unelevated:not(:disabled) {
+ background-color: #6200ee;
+ /* @alternate */
+ background-color: var(--mdc-theme-primary, #6200ee);
+}
+.mdc-button--unelevated:disabled {
+ background-color: rgba(0, 0, 0, 0.12);
+}
+.mdc-button--unelevated:not(:disabled) {
+ color: #fff;
+ /* @alternate */
+ color: var(--mdc-theme-on-primary, #fff);
+}
+.mdc-button--unelevated:disabled {
+ color: rgba(0, 0, 0, 0.38);
+}
+.mdc-button--unelevated .mdc-button__icon {
+ font-size: 1.125rem;
+ height: 1.125rem;
+ width: 1.125rem;
+}
+.mdc-button--unelevated .mdc-button__ripple::before,
+.mdc-button--unelevated .mdc-button__ripple::after {
+ background-color: #fff;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, var(--mdc-theme-on-primary, #fff));
+}
+.mdc-button--unelevated:hover .mdc-button__ripple::before, .mdc-button--unelevated.mdc-ripple-surface--hover .mdc-button__ripple::before {
+ opacity: 0.08;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.08);
+}
+.mdc-button--unelevated.mdc-ripple-upgraded--background-focused .mdc-button__ripple::before, .mdc-button--unelevated:not(.mdc-ripple-upgraded):focus .mdc-button__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.24;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.24);
+}
+.mdc-button--unelevated:not(.mdc-ripple-upgraded) .mdc-button__ripple::after {
+ transition: opacity 150ms linear;
+}
+.mdc-button--unelevated:not(.mdc-ripple-upgraded):active .mdc-button__ripple::after {
+ transition-duration: 75ms;
+ opacity: 0.24;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.24);
+}
+.mdc-button--unelevated.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.24);
+}
+.mdc-button--unelevated .mdc-button__ripple {
+ border-radius: 4px;
+ /* @alternate */
+ border-radius: var(--mdc-shape-small, 4px);
+}
+
+.mdc-button--raised {
+ padding: 0 16px 0 16px;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-button-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.875rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-button-font-size, 0.875rem);
+ letter-spacing: 0.0892857143em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-button-letter-spacing, 0.0892857143em);
+ font-weight: 500;
+ /* @alternate */
+ font-weight: var(--mdc-typography-button-font-weight, 500);
+ text-transform: uppercase;
+ /* @alternate */
+ text-transform: var(--mdc-typography-button-text-transform, uppercase);
+ height: 36px;
+ border-radius: 4px;
+ /* @alternate */
+ border-radius: var(--mdc-shape-small, 4px);
+ /* @alternate */
+ box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12);
+}
+.mdc-button--raised.mdc-button--icon-trailing {
+ padding: 0 12px 0 16px;
+}
+.mdc-button--raised.mdc-button--icon-leading {
+ padding: 0 16px 0 12px;
+}
+.mdc-button--raised:not(:disabled) {
+ background-color: #6200ee;
+ /* @alternate */
+ background-color: var(--mdc-theme-primary, #6200ee);
+}
+.mdc-button--raised:disabled {
+ background-color: rgba(0, 0, 0, 0.12);
+}
+.mdc-button--raised:not(:disabled) {
+ color: #fff;
+ /* @alternate */
+ color: var(--mdc-theme-on-primary, #fff);
+}
+.mdc-button--raised:disabled {
+ color: rgba(0, 0, 0, 0.38);
+}
+.mdc-button--raised .mdc-button__icon {
+ font-size: 1.125rem;
+ height: 1.125rem;
+ width: 1.125rem;
+}
+.mdc-button--raised .mdc-button__ripple::before,
+.mdc-button--raised .mdc-button__ripple::after {
+ background-color: #fff;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, var(--mdc-theme-on-primary, #fff));
+}
+.mdc-button--raised:hover .mdc-button__ripple::before, .mdc-button--raised.mdc-ripple-surface--hover .mdc-button__ripple::before {
+ opacity: 0.08;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.08);
+}
+.mdc-button--raised.mdc-ripple-upgraded--background-focused .mdc-button__ripple::before, .mdc-button--raised:not(.mdc-ripple-upgraded):focus .mdc-button__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.24;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.24);
+}
+.mdc-button--raised:not(.mdc-ripple-upgraded) .mdc-button__ripple::after {
+ transition: opacity 150ms linear;
+}
+.mdc-button--raised:not(.mdc-ripple-upgraded):active .mdc-button__ripple::after {
+ transition-duration: 75ms;
+ opacity: 0.24;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.24);
+}
+.mdc-button--raised.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.24);
+}
+.mdc-button--raised .mdc-button__ripple {
+ border-radius: 4px;
+ /* @alternate */
+ border-radius: var(--mdc-shape-small, 4px);
+}
+.mdc-button--raised.mdc-ripple-upgraded--background-focused, .mdc-button--raised:not(.mdc-ripple-upgraded):focus {
+ /* @alternate */
+ box-shadow: 0px 2px 4px -1px rgba(0, 0, 0, 0.2), 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12);
+}
+.mdc-button--raised:hover {
+ /* @alternate */
+ box-shadow: 0px 2px 4px -1px rgba(0, 0, 0, 0.2), 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12);
+}
+.mdc-button--raised:not(:disabled):active {
+ /* @alternate */
+ box-shadow: 0px 5px 5px -3px rgba(0, 0, 0, 0.2), 0px 8px 10px 1px rgba(0, 0, 0, 0.14), 0px 3px 14px 2px rgba(0, 0, 0, 0.12);
+}
+.mdc-button--raised:disabled {
+ /* @alternate */
+ box-shadow: 0px 0px 0px 0px rgba(0, 0, 0, 0.2), 0px 0px 0px 0px rgba(0, 0, 0, 0.14), 0px 0px 0px 0px rgba(0, 0, 0, 0.12);
+}
+.mdc-button--outlined {
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-button-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.875rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-button-font-size, 0.875rem);
+ letter-spacing: 0.0892857143em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-button-letter-spacing, 0.0892857143em);
+ font-weight: 500;
+ /* @alternate */
+ font-weight: var(--mdc-typography-button-font-weight, 500);
+ text-transform: uppercase;
+ /* @alternate */
+ text-transform: var(--mdc-typography-button-text-transform, uppercase);
+ height: 36px;
+ border-radius: 4px;
+ /* @alternate */
+ border-radius: var(--mdc-shape-small, 4px);
+ padding: 0 15px 0 15px;
+ border-width: 1px;
+}
+.mdc-button--outlined:not(:disabled) {
+ color: #6200ee;
+ /* @alternate */
+ color: var(--mdc-theme-primary, #6200ee);
+}
+.mdc-button--outlined:disabled {
+ color: rgba(0, 0, 0, 0.38);
+}
+.mdc-button--outlined .mdc-button__icon {
+ font-size: 1.125rem;
+ height: 1.125rem;
+ width: 1.125rem;
+}
+.mdc-button--outlined .mdc-button__ripple::before,
+.mdc-button--outlined .mdc-button__ripple::after {
+ background-color: #6200ee;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee));
+}
+.mdc-button--outlined:hover .mdc-button__ripple::before, .mdc-button--outlined.mdc-ripple-surface--hover .mdc-button__ripple::before {
+ opacity: 0.04;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.04);
+}
+.mdc-button--outlined.mdc-ripple-upgraded--background-focused .mdc-button__ripple::before, .mdc-button--outlined:not(.mdc-ripple-upgraded):focus .mdc-button__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.12);
+}
+.mdc-button--outlined:not(.mdc-ripple-upgraded) .mdc-button__ripple::after {
+ transition: opacity 150ms linear;
+}
+.mdc-button--outlined:not(.mdc-ripple-upgraded):active .mdc-button__ripple::after {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-button--outlined.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-button--outlined .mdc-button__ripple {
+ border-radius: 4px;
+ /* @alternate */
+ border-radius: var(--mdc-shape-small, 4px);
+}
+.mdc-button--outlined:not(:disabled) {
+ border-color: rgba(0, 0, 0, 0.12);
+}
+.mdc-button--outlined:disabled {
+ border-color: rgba(0, 0, 0, 0.12);
+}
+.mdc-button--outlined.mdc-button--icon-trailing {
+ padding: 0 11px 0 15px;
+}
+.mdc-button--outlined.mdc-button--icon-leading {
+ padding: 0 15px 0 11px;
+}
+.mdc-button--outlined .mdc-button__ripple {
+ top: -1px;
+ left: -1px;
+ border: 1px solid transparent;
+}
+.mdc-button--outlined .mdc-button__touch {
+ left: -1px;
+ width: calc(100% + 2 * 1px);
+}
+
+.mdc-card {
+ border-radius: 4px;
+ /* @alternate */
+ border-radius: var(--mdc-shape-medium, 4px);
+ background-color: #fff;
+ /* @alternate */
+ background-color: var(--mdc-theme-surface, #fff);
+ /* @alternate */
+ position: relative;
+ /* @alternate */
+ box-shadow: 0px 2px 1px -1px rgba(0, 0, 0, 0.2), 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 1px 3px 0px rgba(0, 0, 0, 0.12);
+ display: flex;
+ flex-direction: column;
+ box-sizing: border-box;
+}
+.mdc-card .mdc-elevation-overlay {
+ width: 100%;
+ height: 100%;
+ top: 0;
+ /* @noflip */
+ left: 0;
+}
+.mdc-card::after {
+ border-radius: 4px;
+ /* @alternate */
+ border-radius: var(--mdc-shape-medium, 4px);
+ position: absolute;
+ box-sizing: border-box;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ border: 1px solid transparent;
+ border-radius: inherit;
+ content: "";
+ pointer-events: none;
+ pointer-events: none;
+}
+
+.mdc-card--outlined {
+ /* @alternate */
+ box-shadow: 0px 0px 0px 0px rgba(0, 0, 0, 0.2), 0px 0px 0px 0px rgba(0, 0, 0, 0.14), 0px 0px 0px 0px rgba(0, 0, 0, 0.12);
+ border-width: 1px;
+ border-style: solid;
+ border-color: #e0e0e0;
+}
+.mdc-card--outlined::after {
+ border: none;
+}
+
+.mdc-card__content {
+ border-radius: inherit;
+ height: 100%;
+}
+
+.mdc-card__media {
+ position: relative;
+ box-sizing: border-box;
+ background-repeat: no-repeat;
+ background-position: center;
+ background-size: cover;
+}
+.mdc-card__media::before {
+ display: block;
+ content: "";
+}
+
+.mdc-card__media:first-child {
+ border-top-left-radius: inherit;
+ border-top-right-radius: inherit;
+}
+
+.mdc-card__media:last-child {
+ border-bottom-left-radius: inherit;
+ border-bottom-right-radius: inherit;
+}
+
+.mdc-card__media--square::before {
+ margin-top: 100%;
+}
+
+.mdc-card__media--16-9::before {
+ margin-top: 56.25%;
+}
+
+.mdc-card__media-content {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ box-sizing: border-box;
+}
+
+.mdc-card__primary-action {
+ display: flex;
+ flex-direction: column;
+ box-sizing: border-box;
+ position: relative;
+ outline: none;
+ color: inherit;
+ text-decoration: none;
+ cursor: pointer;
+ overflow: hidden;
+}
+
+.mdc-card__primary-action:first-child {
+ border-top-left-radius: inherit;
+ border-top-right-radius: inherit;
+}
+
+.mdc-card__primary-action:last-child {
+ border-bottom-left-radius: inherit;
+ border-bottom-right-radius: inherit;
+}
+
+.mdc-card__actions {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ box-sizing: border-box;
+ min-height: 52px;
+ padding: 8px;
+}
+
+.mdc-card__actions--full-bleed {
+ padding: 0;
+}
+
+.mdc-card__action-buttons,
+.mdc-card__action-icons {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ box-sizing: border-box;
+}
+
+.mdc-card__action-icons {
+ color: rgba(0, 0, 0, 0.6);
+ flex-grow: 1;
+ justify-content: flex-end;
+}
+
+.mdc-card__action-buttons + .mdc-card__action-icons {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 0;
+}
+[dir=rtl] .mdc-card__action-buttons + .mdc-card__action-icons, .mdc-card__action-buttons + .mdc-card__action-icons[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 16px;
+}
+
+.mdc-card__action {
+ display: inline-flex;
+ flex-direction: row;
+ align-items: center;
+ box-sizing: border-box;
+ justify-content: center;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+.mdc-card__action:focus {
+ outline: none;
+}
+
+.mdc-card__action--button {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 8px;
+ padding: 0 8px;
+}
+[dir=rtl] .mdc-card__action--button, .mdc-card__action--button[dir=rtl] {
+ /* @noflip */
+ margin-left: 8px;
+ /* @noflip */
+ margin-right: 0;
+}
+
+.mdc-card__action--button:last-child {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 0;
+}
+[dir=rtl] .mdc-card__action--button:last-child, .mdc-card__action--button:last-child[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 0;
+}
+
+.mdc-card__actions--full-bleed .mdc-card__action--button {
+ justify-content: space-between;
+ width: 100%;
+ height: auto;
+ max-height: none;
+ margin: 0;
+ padding: 8px 16px;
+ /* @noflip */
+ text-align: left;
+}
+[dir=rtl] .mdc-card__actions--full-bleed .mdc-card__action--button, .mdc-card__actions--full-bleed .mdc-card__action--button[dir=rtl] {
+ /* @noflip */
+ text-align: right;
+}
+
+.mdc-card__action--icon {
+ margin: -6px 0;
+ padding: 12px;
+}
+
+.mdc-card__action--icon:not(:disabled) {
+ color: rgba(0, 0, 0, 0.6);
+}
+
+.mdc-card__primary-action {
+ --mdc-ripple-fg-size: 0;
+ --mdc-ripple-left: 0;
+ --mdc-ripple-top: 0;
+ --mdc-ripple-fg-scale: 1;
+ --mdc-ripple-fg-translate-end: 0;
+ --mdc-ripple-fg-translate-start: 0;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ will-change: transform, opacity;
+}
+.mdc-card__primary-action .mdc-card__ripple::before,
+.mdc-card__primary-action .mdc-card__ripple::after {
+ position: absolute;
+ border-radius: 50%;
+ opacity: 0;
+ pointer-events: none;
+ content: "";
+}
+.mdc-card__primary-action .mdc-card__ripple::before {
+ transition: opacity 15ms linear, background-color 15ms linear;
+ z-index: 1;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 1);
+}
+.mdc-card__primary-action .mdc-card__ripple::after {
+ z-index: 0;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 0);
+}
+.mdc-card__primary-action.mdc-ripple-upgraded .mdc-card__ripple::before {
+ -webkit-transform: scale(var(--mdc-ripple-fg-scale, 1));
+ transform: scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-card__primary-action.mdc-ripple-upgraded .mdc-card__ripple::after {
+ top: 0;
+ /* @noflip */
+ left: 0;
+ -webkit-transform: scale(0);
+ transform: scale(0);
+ -webkit-transform-origin: center center;
+ transform-origin: center center;
+}
+.mdc-card__primary-action.mdc-ripple-upgraded--unbounded .mdc-card__ripple::after {
+ top: var(--mdc-ripple-top, 0);
+ /* @noflip */
+ left: var(--mdc-ripple-left, 0);
+}
+.mdc-card__primary-action.mdc-ripple-upgraded--foreground-activation .mdc-card__ripple::after {
+ -webkit-animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+ animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+}
+.mdc-card__primary-action.mdc-ripple-upgraded--foreground-deactivation .mdc-card__ripple::after {
+ -webkit-animation: mdc-ripple-fg-opacity-out 150ms;
+ animation: mdc-ripple-fg-opacity-out 150ms;
+ -webkit-transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+ transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-card__primary-action .mdc-card__ripple::before,
+.mdc-card__primary-action .mdc-card__ripple::after {
+ top: calc(50% - 100%);
+ /* @noflip */
+ left: calc(50% - 100%);
+ width: 200%;
+ height: 200%;
+}
+.mdc-card__primary-action.mdc-ripple-upgraded .mdc-card__ripple::after {
+ width: var(--mdc-ripple-fg-size, 100%);
+ height: var(--mdc-ripple-fg-size, 100%);
+}
+.mdc-card__primary-action .mdc-card__ripple::before, .mdc-card__primary-action .mdc-card__ripple::after {
+ background-color: #000;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, #000);
+}
+.mdc-card__primary-action:hover .mdc-card__ripple::before, .mdc-card__primary-action.mdc-ripple-surface--hover .mdc-card__ripple::before {
+ opacity: 0.04;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.04);
+}
+.mdc-card__primary-action.mdc-ripple-upgraded--background-focused .mdc-card__ripple::before, .mdc-card__primary-action:not(.mdc-ripple-upgraded):focus .mdc-card__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.12);
+}
+.mdc-card__primary-action:not(.mdc-ripple-upgraded) .mdc-card__ripple::after {
+ transition: opacity 150ms linear;
+}
+.mdc-card__primary-action:not(.mdc-ripple-upgraded):active .mdc-card__ripple::after {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-card__primary-action.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-card__primary-action .mdc-card__ripple {
+ box-sizing: content-box;
+ height: 100%;
+ overflow: hidden;
+ left: 0;
+ pointer-events: none;
+ position: absolute;
+ top: 0;
+ width: 100%;
+}
+.mdc-card__primary-action.mdc-ripple-upgraded--background-focused::after, .mdc-card__primary-action:not(.mdc-ripple-upgraded):focus::after {
+ position: absolute;
+ box-sizing: border-box;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ border: 5px double transparent;
+ border-radius: inherit;
+ content: "";
+ pointer-events: none;
+}
+
+.mdc-checkbox {
+ padding: calc((40px - 18px) / 2);
+ /* @alternate */
+ padding: calc((var(--mdc-checkbox-ripple-size, 40px) - 18px) / 2);
+ margin: calc((40px - 40px) / 2);
+ /* @alternate */
+ margin: calc((var(--mdc-checkbox-touch-target-size, 40px) - 40px) / 2);
+}
+.mdc-checkbox .mdc-checkbox__ripple::before, .mdc-checkbox .mdc-checkbox__ripple::after {
+ background-color: #000;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, #000);
+}
+.mdc-checkbox:hover .mdc-checkbox__ripple::before, .mdc-checkbox.mdc-ripple-surface--hover .mdc-checkbox__ripple::before {
+ opacity: 0.04;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.04);
+}
+.mdc-checkbox.mdc-ripple-upgraded--background-focused .mdc-checkbox__ripple::before, .mdc-checkbox:not(.mdc-ripple-upgraded):focus .mdc-checkbox__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.12);
+}
+.mdc-checkbox:not(.mdc-ripple-upgraded) .mdc-checkbox__ripple::after {
+ transition: opacity 150ms linear;
+}
+.mdc-checkbox:not(.mdc-ripple-upgraded):active .mdc-checkbox__ripple::after {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-checkbox.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-checkbox.mdc-checkbox--selected .mdc-checkbox__ripple::before, .mdc-checkbox.mdc-checkbox--selected .mdc-checkbox__ripple::after {
+ background-color: #018786;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, var(--mdc-theme-secondary, #018786));
+}
+.mdc-checkbox.mdc-checkbox--selected:hover .mdc-checkbox__ripple::before, .mdc-checkbox.mdc-checkbox--selected.mdc-ripple-surface--hover .mdc-checkbox__ripple::before {
+ opacity: 0.04;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.04);
+}
+.mdc-checkbox.mdc-checkbox--selected.mdc-ripple-upgraded--background-focused .mdc-checkbox__ripple::before, .mdc-checkbox.mdc-checkbox--selected:not(.mdc-ripple-upgraded):focus .mdc-checkbox__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.12);
+}
+.mdc-checkbox.mdc-checkbox--selected:not(.mdc-ripple-upgraded) .mdc-checkbox__ripple::after {
+ transition: opacity 150ms linear;
+}
+.mdc-checkbox.mdc-checkbox--selected:not(.mdc-ripple-upgraded):active .mdc-checkbox__ripple::after {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-checkbox.mdc-checkbox--selected.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-checkbox.mdc-ripple-upgraded--background-focused.mdc-checkbox--selected .mdc-checkbox__ripple::before,
+.mdc-checkbox.mdc-ripple-upgraded--background-focused.mdc-checkbox--selected .mdc-checkbox__ripple::after {
+ background-color: #018786;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, var(--mdc-theme-secondary, #018786));
+}
+.mdc-checkbox .mdc-checkbox__background {
+ top: calc((40px - 18px) / 2);
+ /* @alternate */
+ top: calc((var(--mdc-checkbox-ripple-size, 40px) - 18px) / 2);
+ left: calc((40px - 18px) / 2);
+ /* @alternate */
+ left: calc((var(--mdc-checkbox-ripple-size, 40px) - 18px) / 2);
+}
+.mdc-checkbox .mdc-checkbox__native-control {
+ top: calc((40px - 40px) / 2);
+ /* @alternate */
+ top: calc((40px - var(--mdc-checkbox-touch-target-size, 40px)) / 2);
+ right: calc((40px - 40px) / 2);
+ /* @alternate */
+ right: calc((40px - var(--mdc-checkbox-touch-target-size, 40px)) / 2);
+ left: calc((40px - 40px) / 2);
+ /* @alternate */
+ left: calc((40px - var(--mdc-checkbox-touch-target-size, 40px)) / 2);
+ width: 40px;
+ /* @alternate */
+ width: var(--mdc-checkbox-touch-target-size, 40px);
+ height: 40px;
+ /* @alternate */
+ height: var(--mdc-checkbox-touch-target-size, 40px);
+}
+.mdc-checkbox .mdc-checkbox__native-control:enabled:not(:checked):not(:indeterminate):not([data-indeterminate=true]) ~ .mdc-checkbox__background {
+ border-color: rgba(0, 0, 0, 0.54);
+ /* @alternate */
+ border-color: var(--mdc-checkbox-unchecked-color, rgba(0, 0, 0, 0.54));
+ background-color: transparent;
+}
+.mdc-checkbox .mdc-checkbox__native-control:enabled:checked ~ .mdc-checkbox__background,
+.mdc-checkbox .mdc-checkbox__native-control:enabled:indeterminate ~ .mdc-checkbox__background,
+.mdc-checkbox .mdc-checkbox__native-control[data-indeterminate=true]:enabled ~ .mdc-checkbox__background {
+ border-color: #018786;
+ /* @alternate */
+ border-color: var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #018786));
+ background-color: #018786;
+ /* @alternate */
+ background-color: var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #018786));
+}
+@-webkit-keyframes mdc-checkbox-fade-in-background-8A000000FF01878600000000FF018786 {
+ 0% {
+ border-color: rgba(0, 0, 0, 0.54);
+ /* @alternate */
+ border-color: var(--mdc-checkbox-unchecked-color, rgba(0, 0, 0, 0.54));
+ background-color: transparent;
+ }
+ 50% {
+ border-color: #018786;
+ /* @alternate */
+ border-color: var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #018786));
+ background-color: #018786;
+ /* @alternate */
+ background-color: var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #018786));
+ }
+}
+@keyframes mdc-checkbox-fade-in-background-8A000000FF01878600000000FF018786 {
+ 0% {
+ border-color: rgba(0, 0, 0, 0.54);
+ /* @alternate */
+ border-color: var(--mdc-checkbox-unchecked-color, rgba(0, 0, 0, 0.54));
+ background-color: transparent;
+ }
+ 50% {
+ border-color: #018786;
+ /* @alternate */
+ border-color: var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #018786));
+ background-color: #018786;
+ /* @alternate */
+ background-color: var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #018786));
+ }
+}
+@-webkit-keyframes mdc-checkbox-fade-out-background-8A000000FF01878600000000FF018786 {
+ 0%, 80% {
+ border-color: #018786;
+ /* @alternate */
+ border-color: var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #018786));
+ background-color: #018786;
+ /* @alternate */
+ background-color: var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #018786));
+ }
+ 100% {
+ border-color: rgba(0, 0, 0, 0.54);
+ /* @alternate */
+ border-color: var(--mdc-checkbox-unchecked-color, rgba(0, 0, 0, 0.54));
+ background-color: transparent;
+ }
+}
+@keyframes mdc-checkbox-fade-out-background-8A000000FF01878600000000FF018786 {
+ 0%, 80% {
+ border-color: #018786;
+ /* @alternate */
+ border-color: var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #018786));
+ background-color: #018786;
+ /* @alternate */
+ background-color: var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #018786));
+ }
+ 100% {
+ border-color: rgba(0, 0, 0, 0.54);
+ /* @alternate */
+ border-color: var(--mdc-checkbox-unchecked-color, rgba(0, 0, 0, 0.54));
+ background-color: transparent;
+ }
+}
+.mdc-checkbox.mdc-checkbox--anim-unchecked-checked .mdc-checkbox__native-control:enabled ~ .mdc-checkbox__background, .mdc-checkbox.mdc-checkbox--anim-unchecked-indeterminate .mdc-checkbox__native-control:enabled ~ .mdc-checkbox__background {
+ -webkit-animation-name: mdc-checkbox-fade-in-background-8A000000FF01878600000000FF018786;
+ animation-name: mdc-checkbox-fade-in-background-8A000000FF01878600000000FF018786;
+}
+.mdc-checkbox.mdc-checkbox--anim-checked-unchecked .mdc-checkbox__native-control:enabled ~ .mdc-checkbox__background, .mdc-checkbox.mdc-checkbox--anim-indeterminate-unchecked .mdc-checkbox__native-control:enabled ~ .mdc-checkbox__background {
+ -webkit-animation-name: mdc-checkbox-fade-out-background-8A000000FF01878600000000FF018786;
+ animation-name: mdc-checkbox-fade-out-background-8A000000FF01878600000000FF018786;
+}
+.mdc-checkbox .mdc-checkbox__native-control[disabled]:not(:checked):not(:indeterminate):not([data-indeterminate=true]) ~ .mdc-checkbox__background {
+ border-color: rgba(0, 0, 0, 0.38);
+ /* @alternate */
+ border-color: var(--mdc-checkbox-disabled-color, rgba(0, 0, 0, 0.38));
+ background-color: transparent;
+}
+.mdc-checkbox .mdc-checkbox__native-control[disabled]:checked ~ .mdc-checkbox__background,
+.mdc-checkbox .mdc-checkbox__native-control[disabled]:indeterminate ~ .mdc-checkbox__background,
+.mdc-checkbox .mdc-checkbox__native-control[data-indeterminate=true][disabled] ~ .mdc-checkbox__background {
+ border-color: transparent;
+ background-color: rgba(0, 0, 0, 0.38);
+ /* @alternate */
+ background-color: var(--mdc-checkbox-disabled-color, rgba(0, 0, 0, 0.38));
+}
+.mdc-checkbox .mdc-checkbox__native-control:enabled ~ .mdc-checkbox__background .mdc-checkbox__checkmark {
+ color: #fff;
+ /* @alternate */
+ color: var(--mdc-checkbox-ink-color, #fff);
+}
+.mdc-checkbox .mdc-checkbox__native-control:enabled ~ .mdc-checkbox__background .mdc-checkbox__mixedmark {
+ border-color: #fff;
+ /* @alternate */
+ border-color: var(--mdc-checkbox-ink-color, #fff);
+}
+.mdc-checkbox .mdc-checkbox__native-control:disabled ~ .mdc-checkbox__background .mdc-checkbox__checkmark {
+ color: #fff;
+ /* @alternate */
+ color: var(--mdc-checkbox-ink-color, #fff);
+}
+.mdc-checkbox .mdc-checkbox__native-control:disabled ~ .mdc-checkbox__background .mdc-checkbox__mixedmark {
+ border-color: #fff;
+ /* @alternate */
+ border-color: var(--mdc-checkbox-ink-color, #fff);
+}
+
+@-webkit-keyframes mdc-checkbox-unchecked-checked-checkmark-path {
+ 0%, 50% {
+ stroke-dashoffset: 29.7833385;
+ }
+ 50% {
+ -webkit-animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
+ animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
+ }
+ 100% {
+ stroke-dashoffset: 0;
+ }
+}
+
+@keyframes mdc-checkbox-unchecked-checked-checkmark-path {
+ 0%, 50% {
+ stroke-dashoffset: 29.7833385;
+ }
+ 50% {
+ -webkit-animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
+ animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
+ }
+ 100% {
+ stroke-dashoffset: 0;
+ }
+}
+@-webkit-keyframes mdc-checkbox-unchecked-indeterminate-mixedmark {
+ 0%, 68.2% {
+ -webkit-transform: scaleX(0);
+ transform: scaleX(0);
+ }
+ 68.2% {
+ -webkit-animation-timing-function: cubic-bezier(0, 0, 0, 1);
+ animation-timing-function: cubic-bezier(0, 0, 0, 1);
+ }
+ 100% {
+ -webkit-transform: scaleX(1);
+ transform: scaleX(1);
+ }
+}
+@keyframes mdc-checkbox-unchecked-indeterminate-mixedmark {
+ 0%, 68.2% {
+ -webkit-transform: scaleX(0);
+ transform: scaleX(0);
+ }
+ 68.2% {
+ -webkit-animation-timing-function: cubic-bezier(0, 0, 0, 1);
+ animation-timing-function: cubic-bezier(0, 0, 0, 1);
+ }
+ 100% {
+ -webkit-transform: scaleX(1);
+ transform: scaleX(1);
+ }
+}
+@-webkit-keyframes mdc-checkbox-checked-unchecked-checkmark-path {
+ from {
+ -webkit-animation-timing-function: cubic-bezier(0.4, 0, 1, 1);
+ animation-timing-function: cubic-bezier(0.4, 0, 1, 1);
+ opacity: 1;
+ stroke-dashoffset: 0;
+ }
+ to {
+ opacity: 0;
+ stroke-dashoffset: -29.7833385;
+ }
+}
+@keyframes mdc-checkbox-checked-unchecked-checkmark-path {
+ from {
+ -webkit-animation-timing-function: cubic-bezier(0.4, 0, 1, 1);
+ animation-timing-function: cubic-bezier(0.4, 0, 1, 1);
+ opacity: 1;
+ stroke-dashoffset: 0;
+ }
+ to {
+ opacity: 0;
+ stroke-dashoffset: -29.7833385;
+ }
+}
+@-webkit-keyframes mdc-checkbox-checked-indeterminate-checkmark {
+ from {
+ -webkit-animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
+ animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ opacity: 1;
+ }
+ to {
+ -webkit-transform: rotate(45deg);
+ transform: rotate(45deg);
+ opacity: 0;
+ }
+}
+@keyframes mdc-checkbox-checked-indeterminate-checkmark {
+ from {
+ -webkit-animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
+ animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ opacity: 1;
+ }
+ to {
+ -webkit-transform: rotate(45deg);
+ transform: rotate(45deg);
+ opacity: 0;
+ }
+}
+@-webkit-keyframes mdc-checkbox-indeterminate-checked-checkmark {
+ from {
+ -webkit-animation-timing-function: cubic-bezier(0.14, 0, 0, 1);
+ animation-timing-function: cubic-bezier(0.14, 0, 0, 1);
+ -webkit-transform: rotate(45deg);
+ transform: rotate(45deg);
+ opacity: 0;
+ }
+ to {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg);
+ opacity: 1;
+ }
+}
+@keyframes mdc-checkbox-indeterminate-checked-checkmark {
+ from {
+ -webkit-animation-timing-function: cubic-bezier(0.14, 0, 0, 1);
+ animation-timing-function: cubic-bezier(0.14, 0, 0, 1);
+ -webkit-transform: rotate(45deg);
+ transform: rotate(45deg);
+ opacity: 0;
+ }
+ to {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg);
+ opacity: 1;
+ }
+}
+@-webkit-keyframes mdc-checkbox-checked-indeterminate-mixedmark {
+ from {
+ -webkit-animation-timing-function: mdc-animation-deceleration-curve-timing-function;
+ animation-timing-function: mdc-animation-deceleration-curve-timing-function;
+ -webkit-transform: rotate(-45deg);
+ transform: rotate(-45deg);
+ opacity: 0;
+ }
+ to {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ opacity: 1;
+ }
+}
+@keyframes mdc-checkbox-checked-indeterminate-mixedmark {
+ from {
+ -webkit-animation-timing-function: mdc-animation-deceleration-curve-timing-function;
+ animation-timing-function: mdc-animation-deceleration-curve-timing-function;
+ -webkit-transform: rotate(-45deg);
+ transform: rotate(-45deg);
+ opacity: 0;
+ }
+ to {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ opacity: 1;
+ }
+}
+@-webkit-keyframes mdc-checkbox-indeterminate-checked-mixedmark {
+ from {
+ -webkit-animation-timing-function: cubic-bezier(0.14, 0, 0, 1);
+ animation-timing-function: cubic-bezier(0.14, 0, 0, 1);
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ opacity: 1;
+ }
+ to {
+ -webkit-transform: rotate(315deg);
+ transform: rotate(315deg);
+ opacity: 0;
+ }
+}
+@keyframes mdc-checkbox-indeterminate-checked-mixedmark {
+ from {
+ -webkit-animation-timing-function: cubic-bezier(0.14, 0, 0, 1);
+ animation-timing-function: cubic-bezier(0.14, 0, 0, 1);
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ opacity: 1;
+ }
+ to {
+ -webkit-transform: rotate(315deg);
+ transform: rotate(315deg);
+ opacity: 0;
+ }
+}
+@-webkit-keyframes mdc-checkbox-indeterminate-unchecked-mixedmark {
+ 0% {
+ -webkit-animation-timing-function: linear;
+ animation-timing-function: linear;
+ -webkit-transform: scaleX(1);
+ transform: scaleX(1);
+ opacity: 1;
+ }
+ 32.8%, 100% {
+ -webkit-transform: scaleX(0);
+ transform: scaleX(0);
+ opacity: 0;
+ }
+}
+@keyframes mdc-checkbox-indeterminate-unchecked-mixedmark {
+ 0% {
+ -webkit-animation-timing-function: linear;
+ animation-timing-function: linear;
+ -webkit-transform: scaleX(1);
+ transform: scaleX(1);
+ opacity: 1;
+ }
+ 32.8%, 100% {
+ -webkit-transform: scaleX(0);
+ transform: scaleX(0);
+ opacity: 0;
+ }
+}
+.mdc-checkbox {
+ display: inline-block;
+ position: relative;
+ flex: 0 0 18px;
+ box-sizing: content-box;
+ width: 18px;
+ height: 18px;
+ line-height: 0;
+ white-space: nowrap;
+ cursor: pointer;
+ vertical-align: bottom;
+}
+
+@media screen and (forced-colors: active), (-ms-high-contrast: active) {
+ .mdc-checkbox__native-control[disabled]:not(:checked):not(:indeterminate):not([data-indeterminate=true]) ~ .mdc-checkbox__background {
+ border-color: GrayText;
+ /* @alternate */
+ border-color: var(--mdc-checkbox-disabled-color, GrayText);
+ background-color: transparent;
+ }
+
+ .mdc-checkbox__native-control[disabled]:checked ~ .mdc-checkbox__background,
+.mdc-checkbox__native-control[disabled]:indeterminate ~ .mdc-checkbox__background,
+.mdc-checkbox__native-control[data-indeterminate=true][disabled] ~ .mdc-checkbox__background {
+ border-color: GrayText;
+ background-color: transparent;
+ /* @alternate */
+ background-color: var(--mdc-checkbox-disabled-color, transparent);
+ }
+
+ .mdc-checkbox__native-control:disabled ~ .mdc-checkbox__background .mdc-checkbox__checkmark {
+ color: GrayText;
+ /* @alternate */
+ color: var(--mdc-checkbox-ink-color, GrayText);
+ }
+ .mdc-checkbox__native-control:disabled ~ .mdc-checkbox__background .mdc-checkbox__mixedmark {
+ border-color: GrayText;
+ /* @alternate */
+ border-color: var(--mdc-checkbox-ink-color, GrayText);
+ }
+
+ .mdc-checkbox__mixedmark {
+ margin: 0 1px;
+ }
+}
+.mdc-checkbox--disabled {
+ cursor: default;
+ pointer-events: none;
+}
+
+.mdc-checkbox__background {
+ display: inline-flex;
+ position: absolute;
+ align-items: center;
+ justify-content: center;
+ box-sizing: border-box;
+ width: 18px;
+ height: 18px;
+ border: 2px solid currentColor;
+ border-radius: 2px;
+ background-color: transparent;
+ pointer-events: none;
+ will-change: background-color, border-color;
+ transition: background-color 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1), border-color 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1);
+}
+
+.mdc-checkbox__checkmark {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ opacity: 0;
+ transition: opacity 180ms 0ms cubic-bezier(0.4, 0, 0.6, 1);
+}
+.mdc-checkbox--upgraded .mdc-checkbox__checkmark {
+ opacity: 1;
+}
+
+.mdc-checkbox__checkmark-path {
+ transition: stroke-dashoffset 180ms 0ms cubic-bezier(0.4, 0, 0.6, 1);
+ stroke: currentColor;
+ stroke-width: 3.12px;
+ stroke-dashoffset: 29.7833385;
+ stroke-dasharray: 29.7833385;
+}
+
+.mdc-checkbox__mixedmark {
+ width: 100%;
+ height: 0;
+ -webkit-transform: scaleX(0) rotate(0deg);
+ transform: scaleX(0) rotate(0deg);
+ border-width: 1px;
+ border-style: solid;
+ opacity: 0;
+ transition: opacity 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1), -webkit-transform 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1);
+ transition: opacity 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1), transform 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1);
+ transition: opacity 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1), transform 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1), -webkit-transform 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1);
+}
+
+.mdc-checkbox--anim-unchecked-checked .mdc-checkbox__background, .mdc-checkbox--anim-unchecked-indeterminate .mdc-checkbox__background, .mdc-checkbox--anim-checked-unchecked .mdc-checkbox__background, .mdc-checkbox--anim-indeterminate-unchecked .mdc-checkbox__background {
+ -webkit-animation-duration: 180ms;
+ animation-duration: 180ms;
+ -webkit-animation-timing-function: linear;
+ animation-timing-function: linear;
+}
+.mdc-checkbox--anim-unchecked-checked .mdc-checkbox__checkmark-path {
+ -webkit-animation: mdc-checkbox-unchecked-checked-checkmark-path 180ms linear 0s;
+ animation: mdc-checkbox-unchecked-checked-checkmark-path 180ms linear 0s;
+ transition: none;
+}
+.mdc-checkbox--anim-unchecked-indeterminate .mdc-checkbox__mixedmark {
+ -webkit-animation: mdc-checkbox-unchecked-indeterminate-mixedmark 90ms linear 0s;
+ animation: mdc-checkbox-unchecked-indeterminate-mixedmark 90ms linear 0s;
+ transition: none;
+}
+.mdc-checkbox--anim-checked-unchecked .mdc-checkbox__checkmark-path {
+ -webkit-animation: mdc-checkbox-checked-unchecked-checkmark-path 90ms linear 0s;
+ animation: mdc-checkbox-checked-unchecked-checkmark-path 90ms linear 0s;
+ transition: none;
+}
+.mdc-checkbox--anim-checked-indeterminate .mdc-checkbox__checkmark {
+ -webkit-animation: mdc-checkbox-checked-indeterminate-checkmark 90ms linear 0s;
+ animation: mdc-checkbox-checked-indeterminate-checkmark 90ms linear 0s;
+ transition: none;
+}
+.mdc-checkbox--anim-checked-indeterminate .mdc-checkbox__mixedmark {
+ -webkit-animation: mdc-checkbox-checked-indeterminate-mixedmark 90ms linear 0s;
+ animation: mdc-checkbox-checked-indeterminate-mixedmark 90ms linear 0s;
+ transition: none;
+}
+.mdc-checkbox--anim-indeterminate-checked .mdc-checkbox__checkmark {
+ -webkit-animation: mdc-checkbox-indeterminate-checked-checkmark 500ms linear 0s;
+ animation: mdc-checkbox-indeterminate-checked-checkmark 500ms linear 0s;
+ transition: none;
+}
+.mdc-checkbox--anim-indeterminate-checked .mdc-checkbox__mixedmark {
+ -webkit-animation: mdc-checkbox-indeterminate-checked-mixedmark 500ms linear 0s;
+ animation: mdc-checkbox-indeterminate-checked-mixedmark 500ms linear 0s;
+ transition: none;
+}
+.mdc-checkbox--anim-indeterminate-unchecked .mdc-checkbox__mixedmark {
+ -webkit-animation: mdc-checkbox-indeterminate-unchecked-mixedmark 300ms linear 0s;
+ animation: mdc-checkbox-indeterminate-unchecked-mixedmark 300ms linear 0s;
+ transition: none;
+}
+
+.mdc-checkbox__native-control:checked ~ .mdc-checkbox__background,
+.mdc-checkbox__native-control:indeterminate ~ .mdc-checkbox__background,
+.mdc-checkbox__native-control[data-indeterminate=true] ~ .mdc-checkbox__background {
+ transition: border-color 90ms 0ms cubic-bezier(0, 0, 0.2, 1), background-color 90ms 0ms cubic-bezier(0, 0, 0.2, 1);
+}
+.mdc-checkbox__native-control:checked ~ .mdc-checkbox__background .mdc-checkbox__checkmark-path,
+.mdc-checkbox__native-control:indeterminate ~ .mdc-checkbox__background .mdc-checkbox__checkmark-path,
+.mdc-checkbox__native-control[data-indeterminate=true] ~ .mdc-checkbox__background .mdc-checkbox__checkmark-path {
+ stroke-dashoffset: 0;
+}
+
+.mdc-checkbox__native-control {
+ position: absolute;
+ margin: 0;
+ padding: 0;
+ opacity: 0;
+ cursor: inherit;
+}
+.mdc-checkbox__native-control:disabled {
+ cursor: default;
+ pointer-events: none;
+}
+
+.mdc-checkbox--touch {
+ margin: calc((48px - 40px) / 2);
+ /* @alternate */
+ margin: calc((var(--mdc-checkbox-state-layer-size, 48px) - var(--mdc-checkbox-state-layer-size, 40px)) / 2);
+}
+.mdc-checkbox--touch .mdc-checkbox__native-control {
+ top: calc((40px - 48px) / 2);
+ /* @alternate */
+ top: calc((var(--mdc-checkbox-state-layer-size, 40px) - var(--mdc-checkbox-state-layer-size, 48px)) / 2);
+ right: calc((40px - 48px) / 2);
+ /* @alternate */
+ right: calc((var(--mdc-checkbox-state-layer-size, 40px) - var(--mdc-checkbox-state-layer-size, 48px)) / 2);
+ left: calc((40px - 48px) / 2);
+ /* @alternate */
+ left: calc((var(--mdc-checkbox-state-layer-size, 40px) - var(--mdc-checkbox-state-layer-size, 48px)) / 2);
+ width: 48px;
+ /* @alternate */
+ width: var(--mdc-checkbox-state-layer-size, 48px);
+ height: 48px;
+ /* @alternate */
+ height: var(--mdc-checkbox-state-layer-size, 48px);
+}
+
+.mdc-checkbox__native-control:checked ~ .mdc-checkbox__background .mdc-checkbox__checkmark {
+ transition: opacity 180ms 0ms cubic-bezier(0, 0, 0.2, 1), -webkit-transform 180ms 0ms cubic-bezier(0, 0, 0.2, 1);
+ transition: opacity 180ms 0ms cubic-bezier(0, 0, 0.2, 1), transform 180ms 0ms cubic-bezier(0, 0, 0.2, 1);
+ transition: opacity 180ms 0ms cubic-bezier(0, 0, 0.2, 1), transform 180ms 0ms cubic-bezier(0, 0, 0.2, 1), -webkit-transform 180ms 0ms cubic-bezier(0, 0, 0.2, 1);
+ opacity: 1;
+}
+.mdc-checkbox__native-control:checked ~ .mdc-checkbox__background .mdc-checkbox__mixedmark {
+ -webkit-transform: scaleX(1) rotate(-45deg);
+ transform: scaleX(1) rotate(-45deg);
+}
+
+.mdc-checkbox__native-control:indeterminate ~ .mdc-checkbox__background .mdc-checkbox__checkmark,
+.mdc-checkbox__native-control[data-indeterminate=true] ~ .mdc-checkbox__background .mdc-checkbox__checkmark {
+ -webkit-transform: rotate(45deg);
+ transform: rotate(45deg);
+ opacity: 0;
+ transition: opacity 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1), -webkit-transform 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1);
+ transition: opacity 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1), transform 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1);
+ transition: opacity 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1), transform 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1), -webkit-transform 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1);
+}
+.mdc-checkbox__native-control:indeterminate ~ .mdc-checkbox__background .mdc-checkbox__mixedmark,
+.mdc-checkbox__native-control[data-indeterminate=true] ~ .mdc-checkbox__background .mdc-checkbox__mixedmark {
+ -webkit-transform: scaleX(1) rotate(0deg);
+ transform: scaleX(1) rotate(0deg);
+ opacity: 1;
+}
+
+.mdc-checkbox.mdc-checkbox--upgraded .mdc-checkbox__background,
+.mdc-checkbox.mdc-checkbox--upgraded .mdc-checkbox__checkmark,
+.mdc-checkbox.mdc-checkbox--upgraded .mdc-checkbox__checkmark-path,
+.mdc-checkbox.mdc-checkbox--upgraded .mdc-checkbox__mixedmark {
+ transition: none;
+}
+
+.mdc-checkbox {
+ --mdc-ripple-fg-size: 0;
+ --mdc-ripple-left: 0;
+ --mdc-ripple-top: 0;
+ --mdc-ripple-fg-scale: 1;
+ --mdc-ripple-fg-translate-end: 0;
+ --mdc-ripple-fg-translate-start: 0;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ will-change: transform, opacity;
+}
+.mdc-checkbox .mdc-checkbox__ripple::before,
+.mdc-checkbox .mdc-checkbox__ripple::after {
+ position: absolute;
+ border-radius: 50%;
+ opacity: 0;
+ pointer-events: none;
+ content: "";
+}
+.mdc-checkbox .mdc-checkbox__ripple::before {
+ transition: opacity 15ms linear, background-color 15ms linear;
+ z-index: 1;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 1);
+}
+.mdc-checkbox .mdc-checkbox__ripple::after {
+ z-index: 0;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 0);
+}
+.mdc-checkbox.mdc-ripple-upgraded .mdc-checkbox__ripple::before {
+ -webkit-transform: scale(var(--mdc-ripple-fg-scale, 1));
+ transform: scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-checkbox.mdc-ripple-upgraded .mdc-checkbox__ripple::after {
+ top: 0;
+ /* @noflip */
+ left: 0;
+ -webkit-transform: scale(0);
+ transform: scale(0);
+ -webkit-transform-origin: center center;
+ transform-origin: center center;
+}
+.mdc-checkbox.mdc-ripple-upgraded--unbounded .mdc-checkbox__ripple::after {
+ top: var(--mdc-ripple-top, 0);
+ /* @noflip */
+ left: var(--mdc-ripple-left, 0);
+}
+.mdc-checkbox.mdc-ripple-upgraded--foreground-activation .mdc-checkbox__ripple::after {
+ -webkit-animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+ animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+}
+.mdc-checkbox.mdc-ripple-upgraded--foreground-deactivation .mdc-checkbox__ripple::after {
+ -webkit-animation: mdc-ripple-fg-opacity-out 150ms;
+ animation: mdc-ripple-fg-opacity-out 150ms;
+ -webkit-transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+ transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-checkbox .mdc-checkbox__ripple::before,
+.mdc-checkbox .mdc-checkbox__ripple::after {
+ top: calc(50% - 50%);
+ /* @noflip */
+ left: calc(50% - 50%);
+ width: 100%;
+ height: 100%;
+}
+.mdc-checkbox.mdc-ripple-upgraded .mdc-checkbox__ripple::before,
+.mdc-checkbox.mdc-ripple-upgraded .mdc-checkbox__ripple::after {
+ top: var(--mdc-ripple-top, calc(50% - 50%));
+ /* @noflip */
+ left: var(--mdc-ripple-left, calc(50% - 50%));
+ width: var(--mdc-ripple-fg-size, 100%);
+ height: var(--mdc-ripple-fg-size, 100%);
+}
+.mdc-checkbox.mdc-ripple-upgraded .mdc-checkbox__ripple::after {
+ width: var(--mdc-ripple-fg-size, 100%);
+ height: var(--mdc-ripple-fg-size, 100%);
+}
+.mdc-checkbox {
+ z-index: 0;
+}
+.mdc-checkbox .mdc-checkbox__ripple::before,
+.mdc-checkbox .mdc-checkbox__ripple::after {
+ z-index: -1;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, -1);
+}
+
+.mdc-checkbox__ripple {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+}
+
+.mdc-deprecated-chip-trailing-action__touch {
+ position: absolute;
+ top: 50%;
+ height: 48px;
+ /* @noflip */
+ left: 50%;
+ width: 48px;
+ -webkit-transform: translate(-50%, -50%);
+ transform: translate(-50%, -50%);
+}
+
+.mdc-deprecated-chip-trailing-action {
+ border: none;
+ display: inline-flex;
+ position: relative;
+ align-items: center;
+ justify-content: center;
+ box-sizing: border-box;
+ padding: 0;
+ outline: none;
+ cursor: pointer;
+ -webkit-appearance: none;
+ background: none;
+}
+.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__icon {
+ height: 18px;
+ width: 18px;
+ font-size: 18px;
+}
+.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action {
+ color: #000;
+ /* @alternate */
+ color: var(--mdc-theme-on-surface, #000);
+}
+.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__touch {
+ width: 26px;
+}
+.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__icon {
+ fill: currentColor;
+ color: inherit;
+}
+
+.mdc-deprecated-chip-trailing-action {
+ --mdc-ripple-fg-size: 0;
+ --mdc-ripple-left: 0;
+ --mdc-ripple-top: 0;
+ --mdc-ripple-fg-scale: 1;
+ --mdc-ripple-fg-translate-end: 0;
+ --mdc-ripple-fg-translate-start: 0;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ will-change: transform, opacity;
+}
+.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__ripple::before,
+.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__ripple::after {
+ position: absolute;
+ border-radius: 50%;
+ opacity: 0;
+ pointer-events: none;
+ content: "";
+}
+.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__ripple::before {
+ transition: opacity 15ms linear, background-color 15ms linear;
+ z-index: 1;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 1);
+}
+.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__ripple::after {
+ z-index: 0;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 0);
+}
+.mdc-deprecated-chip-trailing-action.mdc-ripple-upgraded .mdc-deprecated-chip-trailing-action__ripple::before {
+ -webkit-transform: scale(var(--mdc-ripple-fg-scale, 1));
+ transform: scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-deprecated-chip-trailing-action.mdc-ripple-upgraded .mdc-deprecated-chip-trailing-action__ripple::after {
+ top: 0;
+ /* @noflip */
+ left: 0;
+ -webkit-transform: scale(0);
+ transform: scale(0);
+ -webkit-transform-origin: center center;
+ transform-origin: center center;
+}
+.mdc-deprecated-chip-trailing-action.mdc-ripple-upgraded--unbounded .mdc-deprecated-chip-trailing-action__ripple::after {
+ top: var(--mdc-ripple-top, 0);
+ /* @noflip */
+ left: var(--mdc-ripple-left, 0);
+}
+.mdc-deprecated-chip-trailing-action.mdc-ripple-upgraded--foreground-activation .mdc-deprecated-chip-trailing-action__ripple::after {
+ -webkit-animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+ animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+}
+.mdc-deprecated-chip-trailing-action.mdc-ripple-upgraded--foreground-deactivation .mdc-deprecated-chip-trailing-action__ripple::after {
+ -webkit-animation: mdc-ripple-fg-opacity-out 150ms;
+ animation: mdc-ripple-fg-opacity-out 150ms;
+ -webkit-transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+ transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__ripple::before,
+.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__ripple::after {
+ top: calc(50% - 50%);
+ /* @noflip */
+ left: calc(50% - 50%);
+ width: 100%;
+ height: 100%;
+}
+.mdc-deprecated-chip-trailing-action.mdc-ripple-upgraded .mdc-deprecated-chip-trailing-action__ripple::before,
+.mdc-deprecated-chip-trailing-action.mdc-ripple-upgraded .mdc-deprecated-chip-trailing-action__ripple::after {
+ top: var(--mdc-ripple-top, calc(50% - 50%));
+ /* @noflip */
+ left: var(--mdc-ripple-left, calc(50% - 50%));
+ width: var(--mdc-ripple-fg-size, 100%);
+ height: var(--mdc-ripple-fg-size, 100%);
+}
+.mdc-deprecated-chip-trailing-action.mdc-ripple-upgraded .mdc-deprecated-chip-trailing-action__ripple::after {
+ width: var(--mdc-ripple-fg-size, 100%);
+ height: var(--mdc-ripple-fg-size, 100%);
+}
+.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__ripple::before, .mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__ripple::after {
+ background-color: #000;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, var(--mdc-theme-on-surface, #000));
+}
+.mdc-deprecated-chip-trailing-action:hover .mdc-deprecated-chip-trailing-action__ripple::before, .mdc-deprecated-chip-trailing-action.mdc-ripple-surface--hover .mdc-deprecated-chip-trailing-action__ripple::before {
+ opacity: 0.04;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.04);
+}
+.mdc-deprecated-chip-trailing-action.mdc-ripple-upgraded--background-focused .mdc-deprecated-chip-trailing-action__ripple::before, .mdc-deprecated-chip-trailing-action:not(.mdc-ripple-upgraded):focus .mdc-deprecated-chip-trailing-action__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.12);
+}
+.mdc-deprecated-chip-trailing-action:not(.mdc-ripple-upgraded) .mdc-deprecated-chip-trailing-action__ripple::after {
+ transition: opacity 150ms linear;
+}
+.mdc-deprecated-chip-trailing-action:not(.mdc-ripple-upgraded):active .mdc-deprecated-chip-trailing-action__ripple::after {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-deprecated-chip-trailing-action.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__ripple {
+ position: absolute;
+ box-sizing: content-box;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+}
+
+.mdc-chip__icon--leading {
+ color: rgba(0, 0, 0, 0.54);
+}
+
+.mdc-deprecated-chip-trailing-action {
+ color: #000;
+}
+
+.mdc-chip__icon--trailing {
+ color: rgba(0, 0, 0, 0.54);
+}
+.mdc-chip__icon--trailing:hover {
+ color: rgba(0, 0, 0, 0.62);
+}
+.mdc-chip__icon--trailing:focus {
+ color: rgba(0, 0, 0, 0.87);
+}
+
+.mdc-chip__icon.mdc-chip__icon--leading:not(.mdc-chip__icon--leading-hidden) {
+ width: 20px;
+ height: 20px;
+ font-size: 20px;
+}
+
+.mdc-deprecated-chip-trailing-action__icon {
+ height: 18px;
+ width: 18px;
+ font-size: 18px;
+}
+
+.mdc-chip__icon.mdc-chip__icon--trailing {
+ width: 18px;
+ height: 18px;
+ font-size: 18px;
+}
+
+.mdc-deprecated-chip-trailing-action {
+ /* @noflip */
+ margin-left: 4px;
+ /* @noflip */
+ margin-right: -4px;
+}
+[dir=rtl] .mdc-deprecated-chip-trailing-action, .mdc-deprecated-chip-trailing-action[dir=rtl] {
+ /* @noflip */
+ margin-left: -4px;
+ /* @noflip */
+ margin-right: 4px;
+}
+
+.mdc-chip__icon--trailing {
+ /* @noflip */
+ margin-left: 4px;
+ /* @noflip */
+ margin-right: -4px;
+}
+[dir=rtl] .mdc-chip__icon--trailing, .mdc-chip__icon--trailing[dir=rtl] {
+ /* @noflip */
+ margin-left: -4px;
+ /* @noflip */
+ margin-right: 4px;
+}
+
+.mdc-chip {
+ border-radius: 16px;
+ background-color: #e0e0e0;
+ color: rgba(0, 0, 0, 0.87);
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.875rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-body2-font-size, 0.875rem);
+ line-height: 1.25rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-body2-line-height, 1.25rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-body2-font-weight, 400);
+ letter-spacing: 0.0178571429em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-body2-letter-spacing, 0.0178571429em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-body2-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-body2-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-body2-text-transform, inherit);
+ height: 32px;
+ /* @alternate */
+ position: relative;
+ display: inline-flex;
+ align-items: center;
+ box-sizing: border-box;
+ padding: 0 12px;
+ border-width: 0;
+ outline: none;
+ cursor: pointer;
+ -webkit-appearance: none;
+}
+.mdc-chip .mdc-chip__ripple {
+ border-radius: 16px;
+}
+.mdc-chip:hover {
+ color: rgba(0, 0, 0, 0.87);
+}
+.mdc-chip.mdc-chip--selected .mdc-chip__checkmark,
+.mdc-chip .mdc-chip__icon--leading:not(.mdc-chip__icon--leading-hidden) {
+ /* @noflip */
+ margin-left: -4px;
+ /* @noflip */
+ margin-right: 4px;
+}
+[dir=rtl] .mdc-chip.mdc-chip--selected .mdc-chip__checkmark, [dir=rtl] .mdc-chip .mdc-chip__icon--leading:not(.mdc-chip__icon--leading-hidden), .mdc-chip.mdc-chip--selected .mdc-chip__checkmark[dir=rtl], .mdc-chip .mdc-chip__icon--leading:not(.mdc-chip__icon--leading-hidden)[dir=rtl] {
+ /* @noflip */
+ margin-left: 4px;
+ /* @noflip */
+ margin-right: -4px;
+}
+
+.mdc-chip .mdc-elevation-overlay {
+ width: 100%;
+ height: 100%;
+ top: 0;
+ /* @noflip */
+ left: 0;
+}
+.mdc-chip::-moz-focus-inner {
+ padding: 0;
+ border: 0;
+}
+.mdc-chip:hover {
+ color: #000;
+ /* @alternate */
+ color: var(--mdc-theme-on-surface, #000);
+}
+.mdc-chip .mdc-chip__touch {
+ position: absolute;
+ top: 50%;
+ height: 48px;
+ left: 0;
+ right: 0;
+ -webkit-transform: translateY(-50%);
+ transform: translateY(-50%);
+}
+
+.mdc-chip--exit {
+ transition: opacity 75ms cubic-bezier(0.4, 0, 0.2, 1), width 150ms cubic-bezier(0, 0, 0.2, 1), padding 100ms linear, margin 100ms linear;
+ opacity: 0;
+}
+
+.mdc-chip__overflow {
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+
+.mdc-chip__text {
+ white-space: nowrap;
+}
+
+.mdc-chip__icon {
+ border-radius: 50%;
+ outline: none;
+ vertical-align: middle;
+}
+
+.mdc-chip__checkmark {
+ height: 20px;
+}
+
+.mdc-chip__checkmark-path {
+ transition: stroke-dashoffset 150ms 50ms cubic-bezier(0.4, 0, 0.6, 1);
+ stroke-width: 2px;
+ stroke-dashoffset: 29.7833385;
+ stroke-dasharray: 29.7833385;
+}
+
+.mdc-chip__primary-action:focus {
+ outline: none;
+}
+
+.mdc-chip--selected .mdc-chip__checkmark-path {
+ stroke-dashoffset: 0;
+}
+
+.mdc-chip__icon--leading,
+.mdc-chip__icon--trailing {
+ position: relative;
+}
+
+.mdc-chip-set--choice .mdc-chip.mdc-chip--selected {
+ color: #6200ee;
+ /* @alternate */
+ color: var(--mdc-theme-primary, #6200ee);
+}
+.mdc-chip-set--choice .mdc-chip.mdc-chip--selected .mdc-chip__icon--leading {
+ color: rgba(98, 0, 238, 0.54);
+}
+.mdc-chip-set--choice .mdc-chip.mdc-chip--selected:hover {
+ color: #6200ee;
+ /* @alternate */
+ color: var(--mdc-theme-primary, #6200ee);
+}
+.mdc-chip-set--choice .mdc-chip .mdc-chip__checkmark-path {
+ stroke: #6200ee;
+ /* @alternate */
+ stroke: var(--mdc-theme-primary, #6200ee);
+}
+.mdc-chip-set--choice .mdc-chip--selected {
+ background-color: #fff;
+ /* @alternate */
+ background-color: var(--mdc-theme-surface, #fff);
+}
+
+.mdc-chip__checkmark-svg {
+ width: 0;
+ height: 20px;
+ transition: width 150ms cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.mdc-chip--selected .mdc-chip__checkmark-svg {
+ width: 20px;
+}
+
+.mdc-chip-set--filter .mdc-chip__icon--leading {
+ transition: opacity 75ms linear;
+ transition-delay: -50ms;
+ opacity: 1;
+}
+.mdc-chip-set--filter .mdc-chip__icon--leading + .mdc-chip__checkmark {
+ transition: opacity 75ms linear;
+ transition-delay: 80ms;
+ opacity: 0;
+}
+.mdc-chip-set--filter .mdc-chip__icon--leading + .mdc-chip__checkmark .mdc-chip__checkmark-svg {
+ transition: width 0ms;
+}
+.mdc-chip-set--filter .mdc-chip--selected .mdc-chip__icon--leading {
+ opacity: 0;
+}
+.mdc-chip-set--filter .mdc-chip--selected .mdc-chip__icon--leading + .mdc-chip__checkmark {
+ width: 0;
+ opacity: 1;
+}
+.mdc-chip-set--filter .mdc-chip__icon--leading-hidden.mdc-chip__icon--leading {
+ width: 0;
+ opacity: 0;
+}
+.mdc-chip-set--filter .mdc-chip__icon--leading-hidden.mdc-chip__icon--leading + .mdc-chip__checkmark {
+ width: 20px;
+}
+
+.mdc-chip {
+ --mdc-ripple-fg-size: 0;
+ --mdc-ripple-left: 0;
+ --mdc-ripple-top: 0;
+ --mdc-ripple-fg-scale: 1;
+ --mdc-ripple-fg-translate-end: 0;
+ --mdc-ripple-fg-translate-start: 0;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ will-change: transform, opacity;
+}
+.mdc-chip .mdc-chip__ripple::before,
+.mdc-chip .mdc-chip__ripple::after {
+ position: absolute;
+ border-radius: 50%;
+ opacity: 0;
+ pointer-events: none;
+ content: "";
+}
+.mdc-chip .mdc-chip__ripple::before {
+ transition: opacity 15ms linear, background-color 15ms linear;
+ z-index: 1;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 1);
+}
+.mdc-chip .mdc-chip__ripple::after {
+ z-index: 0;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 0);
+}
+.mdc-chip.mdc-ripple-upgraded .mdc-chip__ripple::before {
+ -webkit-transform: scale(var(--mdc-ripple-fg-scale, 1));
+ transform: scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-chip.mdc-ripple-upgraded .mdc-chip__ripple::after {
+ top: 0;
+ /* @noflip */
+ left: 0;
+ -webkit-transform: scale(0);
+ transform: scale(0);
+ -webkit-transform-origin: center center;
+ transform-origin: center center;
+}
+.mdc-chip.mdc-ripple-upgraded--unbounded .mdc-chip__ripple::after {
+ top: var(--mdc-ripple-top, 0);
+ /* @noflip */
+ left: var(--mdc-ripple-left, 0);
+}
+.mdc-chip.mdc-ripple-upgraded--foreground-activation .mdc-chip__ripple::after {
+ -webkit-animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+ animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+}
+.mdc-chip.mdc-ripple-upgraded--foreground-deactivation .mdc-chip__ripple::after {
+ -webkit-animation: mdc-ripple-fg-opacity-out 150ms;
+ animation: mdc-ripple-fg-opacity-out 150ms;
+ -webkit-transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+ transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-chip .mdc-chip__ripple::before,
+.mdc-chip .mdc-chip__ripple::after {
+ top: calc(50% - 100%);
+ /* @noflip */
+ left: calc(50% - 100%);
+ width: 200%;
+ height: 200%;
+}
+.mdc-chip.mdc-ripple-upgraded .mdc-chip__ripple::after {
+ width: var(--mdc-ripple-fg-size, 100%);
+ height: var(--mdc-ripple-fg-size, 100%);
+}
+.mdc-chip .mdc-chip__ripple::before, .mdc-chip .mdc-chip__ripple::after {
+ background-color: rgba(0, 0, 0, 0.87);
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, rgba(0, 0, 0, 0.87));
+}
+.mdc-chip:hover .mdc-chip__ripple::before, .mdc-chip.mdc-ripple-surface--hover .mdc-chip__ripple::before {
+ opacity: 0.04;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.04);
+}
+.mdc-chip.mdc-ripple-upgraded--background-focused .mdc-chip__ripple::before, .mdc-chip.mdc-ripple-upgraded:focus-within .mdc-chip__ripple::before, .mdc-chip:not(.mdc-ripple-upgraded):focus .mdc-chip__ripple::before, .mdc-chip:not(.mdc-ripple-upgraded):focus-within .mdc-chip__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.12);
+}
+.mdc-chip:not(.mdc-ripple-upgraded) .mdc-chip__ripple::after {
+ transition: opacity 150ms linear;
+}
+.mdc-chip:not(.mdc-ripple-upgraded):active .mdc-chip__ripple::after {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-chip.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-chip .mdc-chip__ripple {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ overflow: hidden;
+}
+
+.mdc-chip-set--choice .mdc-chip.mdc-chip--selected .mdc-chip__ripple::before {
+ opacity: 0.08;
+ /* @alternate */
+ opacity: var(--mdc-ripple-selected-opacity, 0.08);
+}
+.mdc-chip-set--choice .mdc-chip.mdc-chip--selected .mdc-chip__ripple::before, .mdc-chip-set--choice .mdc-chip.mdc-chip--selected .mdc-chip__ripple::after {
+ background-color: #6200ee;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee));
+}
+.mdc-chip-set--choice .mdc-chip.mdc-chip--selected:hover .mdc-chip__ripple::before, .mdc-chip-set--choice .mdc-chip.mdc-chip--selected.mdc-ripple-surface--hover .mdc-chip__ripple::before {
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.12);
+}
+.mdc-chip-set--choice .mdc-chip.mdc-chip--selected.mdc-ripple-upgraded--background-focused .mdc-chip__ripple::before, .mdc-chip-set--choice .mdc-chip.mdc-chip--selected.mdc-ripple-upgraded:focus-within .mdc-chip__ripple::before, .mdc-chip-set--choice .mdc-chip.mdc-chip--selected:not(.mdc-ripple-upgraded):focus .mdc-chip__ripple::before, .mdc-chip-set--choice .mdc-chip.mdc-chip--selected:not(.mdc-ripple-upgraded):focus-within .mdc-chip__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.2;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.2);
+}
+.mdc-chip-set--choice .mdc-chip.mdc-chip--selected:not(.mdc-ripple-upgraded) .mdc-chip__ripple::after {
+ transition: opacity 150ms linear;
+}
+.mdc-chip-set--choice .mdc-chip.mdc-chip--selected:not(.mdc-ripple-upgraded):active .mdc-chip__ripple::after {
+ transition-duration: 75ms;
+ opacity: 0.2;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.2);
+}
+.mdc-chip-set--choice .mdc-chip.mdc-chip--selected.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.2);
+}
+
+@-webkit-keyframes mdc-chip-entry {
+ from {
+ -webkit-transform: scale(0.8);
+ transform: scale(0.8);
+ opacity: 0.4;
+ }
+ to {
+ -webkit-transform: scale(1);
+ transform: scale(1);
+ opacity: 1;
+ }
+}
+
+@keyframes mdc-chip-entry {
+ from {
+ -webkit-transform: scale(0.8);
+ transform: scale(0.8);
+ opacity: 0.4;
+ }
+ to {
+ -webkit-transform: scale(1);
+ transform: scale(1);
+ opacity: 1;
+ }
+}
+.mdc-chip-set {
+ padding: 4px;
+ display: flex;
+ flex-wrap: wrap;
+ box-sizing: border-box;
+}
+.mdc-chip-set .mdc-chip {
+ margin: 4px;
+}
+.mdc-chip-set .mdc-chip--touch {
+ margin-top: 8px;
+ margin-bottom: 8px;
+}
+
+.mdc-chip-set--input .mdc-chip {
+ -webkit-animation: mdc-chip-entry 100ms cubic-bezier(0, 0, 0.2, 1);
+ animation: mdc-chip-entry 100ms cubic-bezier(0, 0, 0.2, 1);
+}
+
+.mdc-circular-progress__determinate-circle,
+.mdc-circular-progress__indeterminate-circle-graphic {
+ stroke: #6200ee;
+ /* @alternate */
+ stroke: var(--mdc-theme-primary, #6200ee);
+}
+
+.mdc-circular-progress__determinate-track {
+ stroke: transparent;
+}
+
+@-webkit-keyframes mdc-circular-progress-container-rotate {
+ to {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+}
+
+@keyframes mdc-circular-progress-container-rotate {
+ to {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+}
+@-webkit-keyframes mdc-circular-progress-spinner-layer-rotate {
+ 12.5% {
+ -webkit-transform: rotate(135deg);
+ transform: rotate(135deg);
+ }
+ 25% {
+ -webkit-transform: rotate(270deg);
+ transform: rotate(270deg);
+ }
+ 37.5% {
+ -webkit-transform: rotate(405deg);
+ transform: rotate(405deg);
+ }
+ 50% {
+ -webkit-transform: rotate(540deg);
+ transform: rotate(540deg);
+ }
+ 62.5% {
+ -webkit-transform: rotate(675deg);
+ transform: rotate(675deg);
+ }
+ 75% {
+ -webkit-transform: rotate(810deg);
+ transform: rotate(810deg);
+ }
+ 87.5% {
+ -webkit-transform: rotate(945deg);
+ transform: rotate(945deg);
+ }
+ 100% {
+ -webkit-transform: rotate(1080deg);
+ transform: rotate(1080deg);
+ }
+}
+@keyframes mdc-circular-progress-spinner-layer-rotate {
+ 12.5% {
+ -webkit-transform: rotate(135deg);
+ transform: rotate(135deg);
+ }
+ 25% {
+ -webkit-transform: rotate(270deg);
+ transform: rotate(270deg);
+ }
+ 37.5% {
+ -webkit-transform: rotate(405deg);
+ transform: rotate(405deg);
+ }
+ 50% {
+ -webkit-transform: rotate(540deg);
+ transform: rotate(540deg);
+ }
+ 62.5% {
+ -webkit-transform: rotate(675deg);
+ transform: rotate(675deg);
+ }
+ 75% {
+ -webkit-transform: rotate(810deg);
+ transform: rotate(810deg);
+ }
+ 87.5% {
+ -webkit-transform: rotate(945deg);
+ transform: rotate(945deg);
+ }
+ 100% {
+ -webkit-transform: rotate(1080deg);
+ transform: rotate(1080deg);
+ }
+}
+@-webkit-keyframes mdc-circular-progress-color-1-fade-in-out {
+ from {
+ opacity: 0.99;
+ }
+ 25% {
+ opacity: 0.99;
+ }
+ 26% {
+ opacity: 0;
+ }
+ 89% {
+ opacity: 0;
+ }
+ 90% {
+ opacity: 0.99;
+ }
+ to {
+ opacity: 0.99;
+ }
+}
+@keyframes mdc-circular-progress-color-1-fade-in-out {
+ from {
+ opacity: 0.99;
+ }
+ 25% {
+ opacity: 0.99;
+ }
+ 26% {
+ opacity: 0;
+ }
+ 89% {
+ opacity: 0;
+ }
+ 90% {
+ opacity: 0.99;
+ }
+ to {
+ opacity: 0.99;
+ }
+}
+@-webkit-keyframes mdc-circular-progress-color-2-fade-in-out {
+ from {
+ opacity: 0;
+ }
+ 15% {
+ opacity: 0;
+ }
+ 25% {
+ opacity: 0.99;
+ }
+ 50% {
+ opacity: 0.99;
+ }
+ 51% {
+ opacity: 0;
+ }
+ to {
+ opacity: 0;
+ }
+}
+@keyframes mdc-circular-progress-color-2-fade-in-out {
+ from {
+ opacity: 0;
+ }
+ 15% {
+ opacity: 0;
+ }
+ 25% {
+ opacity: 0.99;
+ }
+ 50% {
+ opacity: 0.99;
+ }
+ 51% {
+ opacity: 0;
+ }
+ to {
+ opacity: 0;
+ }
+}
+@-webkit-keyframes mdc-circular-progress-color-3-fade-in-out {
+ from {
+ opacity: 0;
+ }
+ 40% {
+ opacity: 0;
+ }
+ 50% {
+ opacity: 0.99;
+ }
+ 75% {
+ opacity: 0.99;
+ }
+ 76% {
+ opacity: 0;
+ }
+ to {
+ opacity: 0;
+ }
+}
+@keyframes mdc-circular-progress-color-3-fade-in-out {
+ from {
+ opacity: 0;
+ }
+ 40% {
+ opacity: 0;
+ }
+ 50% {
+ opacity: 0.99;
+ }
+ 75% {
+ opacity: 0.99;
+ }
+ 76% {
+ opacity: 0;
+ }
+ to {
+ opacity: 0;
+ }
+}
+@-webkit-keyframes mdc-circular-progress-color-4-fade-in-out {
+ from {
+ opacity: 0;
+ }
+ 65% {
+ opacity: 0;
+ }
+ 75% {
+ opacity: 0.99;
+ }
+ 90% {
+ opacity: 0.99;
+ }
+ to {
+ opacity: 0;
+ }
+}
+@keyframes mdc-circular-progress-color-4-fade-in-out {
+ from {
+ opacity: 0;
+ }
+ 65% {
+ opacity: 0;
+ }
+ 75% {
+ opacity: 0.99;
+ }
+ 90% {
+ opacity: 0.99;
+ }
+ to {
+ opacity: 0;
+ }
+}
+@-webkit-keyframes mdc-circular-progress-left-spin {
+ from {
+ -webkit-transform: rotate(265deg);
+ transform: rotate(265deg);
+ }
+ 50% {
+ -webkit-transform: rotate(130deg);
+ transform: rotate(130deg);
+ }
+ to {
+ -webkit-transform: rotate(265deg);
+ transform: rotate(265deg);
+ }
+}
+@keyframes mdc-circular-progress-left-spin {
+ from {
+ -webkit-transform: rotate(265deg);
+ transform: rotate(265deg);
+ }
+ 50% {
+ -webkit-transform: rotate(130deg);
+ transform: rotate(130deg);
+ }
+ to {
+ -webkit-transform: rotate(265deg);
+ transform: rotate(265deg);
+ }
+}
+@-webkit-keyframes mdc-circular-progress-right-spin {
+ from {
+ -webkit-transform: rotate(-265deg);
+ transform: rotate(-265deg);
+ }
+ 50% {
+ -webkit-transform: rotate(-130deg);
+ transform: rotate(-130deg);
+ }
+ to {
+ -webkit-transform: rotate(-265deg);
+ transform: rotate(-265deg);
+ }
+}
+@keyframes mdc-circular-progress-right-spin {
+ from {
+ -webkit-transform: rotate(-265deg);
+ transform: rotate(-265deg);
+ }
+ 50% {
+ -webkit-transform: rotate(-130deg);
+ transform: rotate(-130deg);
+ }
+ to {
+ -webkit-transform: rotate(-265deg);
+ transform: rotate(-265deg);
+ }
+}
+.mdc-circular-progress {
+ display: inline-flex;
+ position: relative;
+ /* @noflip */
+ direction: ltr;
+ line-height: 0;
+ transition: opacity 250ms 0ms cubic-bezier(0.4, 0, 0.6, 1);
+}
+
+.mdc-circular-progress__determinate-container,
+.mdc-circular-progress__indeterminate-circle-graphic,
+.mdc-circular-progress__indeterminate-container,
+.mdc-circular-progress__spinner-layer {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+}
+
+.mdc-circular-progress__determinate-container {
+ -webkit-transform: rotate(-90deg);
+ transform: rotate(-90deg);
+}
+
+.mdc-circular-progress__indeterminate-container {
+ font-size: 0;
+ letter-spacing: 0;
+ white-space: nowrap;
+ opacity: 0;
+}
+
+.mdc-circular-progress__determinate-circle-graphic,
+.mdc-circular-progress__indeterminate-circle-graphic {
+ fill: transparent;
+}
+
+.mdc-circular-progress__determinate-circle {
+ transition: stroke-dashoffset 500ms 0ms cubic-bezier(0, 0, 0.2, 1);
+}
+
+.mdc-circular-progress__gap-patch {
+ position: absolute;
+ top: 0;
+ /* @noflip */
+ left: 47.5%;
+ box-sizing: border-box;
+ width: 5%;
+ height: 100%;
+ overflow: hidden;
+}
+.mdc-circular-progress__gap-patch .mdc-circular-progress__indeterminate-circle-graphic {
+ /* @noflip */
+ left: -900%;
+ width: 2000%;
+ -webkit-transform: rotate(180deg);
+ transform: rotate(180deg);
+}
+
+.mdc-circular-progress__circle-clipper {
+ display: inline-flex;
+ position: relative;
+ width: 50%;
+ height: 100%;
+ overflow: hidden;
+}
+.mdc-circular-progress__circle-clipper .mdc-circular-progress__indeterminate-circle-graphic {
+ width: 200%;
+}
+
+.mdc-circular-progress__circle-right .mdc-circular-progress__indeterminate-circle-graphic {
+ /* @noflip */
+ left: -100%;
+}
+
+.mdc-circular-progress--indeterminate .mdc-circular-progress__determinate-container {
+ opacity: 0;
+}
+.mdc-circular-progress--indeterminate .mdc-circular-progress__indeterminate-container {
+ opacity: 1;
+}
+.mdc-circular-progress--indeterminate .mdc-circular-progress__indeterminate-container {
+ -webkit-animation: mdc-circular-progress-container-rotate 1568.2352941176ms linear infinite;
+ animation: mdc-circular-progress-container-rotate 1568.2352941176ms linear infinite;
+}
+.mdc-circular-progress--indeterminate .mdc-circular-progress__spinner-layer {
+ -webkit-animation: mdc-circular-progress-spinner-layer-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+ animation: mdc-circular-progress-spinner-layer-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+}
+.mdc-circular-progress--indeterminate .mdc-circular-progress__color-1 {
+ -webkit-animation: mdc-circular-progress-spinner-layer-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both, mdc-circular-progress-color-1-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+ animation: mdc-circular-progress-spinner-layer-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both, mdc-circular-progress-color-1-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+}
+.mdc-circular-progress--indeterminate .mdc-circular-progress__color-2 {
+ -webkit-animation: mdc-circular-progress-spinner-layer-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both, mdc-circular-progress-color-2-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+ animation: mdc-circular-progress-spinner-layer-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both, mdc-circular-progress-color-2-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+}
+.mdc-circular-progress--indeterminate .mdc-circular-progress__color-3 {
+ -webkit-animation: mdc-circular-progress-spinner-layer-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both, mdc-circular-progress-color-3-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+ animation: mdc-circular-progress-spinner-layer-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both, mdc-circular-progress-color-3-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+}
+.mdc-circular-progress--indeterminate .mdc-circular-progress__color-4 {
+ -webkit-animation: mdc-circular-progress-spinner-layer-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both, mdc-circular-progress-color-4-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+ animation: mdc-circular-progress-spinner-layer-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both, mdc-circular-progress-color-4-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+}
+.mdc-circular-progress--indeterminate .mdc-circular-progress__circle-left .mdc-circular-progress__indeterminate-circle-graphic {
+ /* @noflip */
+ -webkit-animation: mdc-circular-progress-left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+ animation: mdc-circular-progress-left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+}
+.mdc-circular-progress--indeterminate .mdc-circular-progress__circle-right .mdc-circular-progress__indeterminate-circle-graphic {
+ /* @noflip */
+ -webkit-animation: mdc-circular-progress-right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+ animation: mdc-circular-progress-right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
+}
+
+.mdc-circular-progress--closed {
+ opacity: 0;
+}
+
+.mdc-floating-label {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-subtitle1-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 1rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-subtitle1-font-size, 1rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-subtitle1-font-weight, 400);
+ letter-spacing: 0.009375em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-subtitle1-letter-spacing, 0.009375em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-subtitle1-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-subtitle1-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-subtitle1-text-transform, inherit);
+ position: absolute;
+ /* @noflip */
+ left: 0;
+ /* @noflip */
+ -webkit-transform-origin: left top;
+ /* @noflip */
+ transform-origin: left top;
+ line-height: 1.15rem;
+ text-align: left;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ cursor: text;
+ overflow: hidden;
+ /* @alternate */
+ will-change: transform;
+ transition: color 150ms cubic-bezier(0.4, 0, 0.2, 1), -webkit-transform 150ms cubic-bezier(0.4, 0, 0.2, 1);
+ transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1), color 150ms cubic-bezier(0.4, 0, 0.2, 1);
+ transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1), color 150ms cubic-bezier(0.4, 0, 0.2, 1), -webkit-transform 150ms cubic-bezier(0.4, 0, 0.2, 1);
+}
+[dir=rtl] .mdc-floating-label, .mdc-floating-label[dir=rtl] {
+ /* @noflip */
+ right: 0;
+ /* @noflip */
+ left: auto;
+ /* @noflip */
+ -webkit-transform-origin: right top;
+ /* @noflip */
+ transform-origin: right top;
+ /* @noflip */
+ text-align: right;
+}
+
+.mdc-floating-label--float-above {
+ cursor: auto;
+}
+
+.mdc-floating-label--required::after {
+ /* @noflip */
+ margin-left: 1px;
+ /* @noflip */
+ margin-right: 0px;
+ content: "*";
+}
+[dir=rtl] .mdc-floating-label--required::after, .mdc-floating-label--required[dir=rtl]::after {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 1px;
+}
+
+.mdc-floating-label--float-above {
+ -webkit-transform: translateY(-106%) scale(0.75);
+ transform: translateY(-106%) scale(0.75);
+}
+
+.mdc-floating-label--shake {
+ -webkit-animation: mdc-floating-label-shake-float-above-standard 250ms 1;
+ animation: mdc-floating-label-shake-float-above-standard 250ms 1;
+}
+
+@-webkit-keyframes mdc-floating-label-shake-float-above-standard {
+ 0% {
+ -webkit-transform: translateX(calc(0 - 0%)) translateY(-106%) scale(0.75);
+ transform: translateX(calc(0 - 0%)) translateY(-106%) scale(0.75);
+ }
+ 33% {
+ -webkit-animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ -webkit-transform: translateX(calc(4% - 0%)) translateY(-106%) scale(0.75);
+ transform: translateX(calc(4% - 0%)) translateY(-106%) scale(0.75);
+ }
+ 66% {
+ -webkit-animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ -webkit-transform: translateX(calc(-4% - 0%)) translateY(-106%) scale(0.75);
+ transform: translateX(calc(-4% - 0%)) translateY(-106%) scale(0.75);
+ }
+ 100% {
+ -webkit-transform: translateX(calc(0 - 0%)) translateY(-106%) scale(0.75);
+ transform: translateX(calc(0 - 0%)) translateY(-106%) scale(0.75);
+ }
+}
+
+@keyframes mdc-floating-label-shake-float-above-standard {
+ 0% {
+ -webkit-transform: translateX(calc(0 - 0%)) translateY(-106%) scale(0.75);
+ transform: translateX(calc(0 - 0%)) translateY(-106%) scale(0.75);
+ }
+ 33% {
+ -webkit-animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ -webkit-transform: translateX(calc(4% - 0%)) translateY(-106%) scale(0.75);
+ transform: translateX(calc(4% - 0%)) translateY(-106%) scale(0.75);
+ }
+ 66% {
+ -webkit-animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ -webkit-transform: translateX(calc(-4% - 0%)) translateY(-106%) scale(0.75);
+ transform: translateX(calc(-4% - 0%)) translateY(-106%) scale(0.75);
+ }
+ 100% {
+ -webkit-transform: translateX(calc(0 - 0%)) translateY(-106%) scale(0.75);
+ transform: translateX(calc(0 - 0%)) translateY(-106%) scale(0.75);
+ }
+}
+.mdc-line-ripple::before, .mdc-line-ripple::after {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ border-bottom-style: solid;
+ content: "";
+}
+.mdc-line-ripple::before {
+ border-bottom-width: 1px;
+ z-index: 1;
+}
+.mdc-line-ripple::after {
+ -webkit-transform: scaleX(0);
+ transform: scaleX(0);
+ border-bottom-width: 2px;
+ opacity: 0;
+ z-index: 2;
+}
+.mdc-line-ripple::after {
+ transition: opacity 180ms cubic-bezier(0.4, 0, 0.2, 1), -webkit-transform 180ms cubic-bezier(0.4, 0, 0.2, 1);
+ transition: transform 180ms cubic-bezier(0.4, 0, 0.2, 1), opacity 180ms cubic-bezier(0.4, 0, 0.2, 1);
+ transition: transform 180ms cubic-bezier(0.4, 0, 0.2, 1), opacity 180ms cubic-bezier(0.4, 0, 0.2, 1), -webkit-transform 180ms cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.mdc-line-ripple--active::after {
+ -webkit-transform: scaleX(1);
+ transform: scaleX(1);
+ opacity: 1;
+}
+
+.mdc-line-ripple--deactivating::after {
+ opacity: 0;
+}
+
+.mdc-notched-outline {
+ display: flex;
+ position: absolute;
+ top: 0;
+ right: 0;
+ left: 0;
+ box-sizing: border-box;
+ width: 100%;
+ max-width: 100%;
+ height: 100%;
+ /* @noflip */
+ text-align: left;
+ pointer-events: none;
+}
+[dir=rtl] .mdc-notched-outline, .mdc-notched-outline[dir=rtl] {
+ /* @noflip */
+ text-align: right;
+}
+
+.mdc-notched-outline__leading, .mdc-notched-outline__notch, .mdc-notched-outline__trailing {
+ box-sizing: border-box;
+ height: 100%;
+ border-top: 1px solid;
+ border-bottom: 1px solid;
+ pointer-events: none;
+}
+.mdc-notched-outline__leading {
+ /* @noflip */
+ border-left: 1px solid;
+ /* @noflip */
+ border-right: none;
+ width: 12px;
+}
+[dir=rtl] .mdc-notched-outline__leading, .mdc-notched-outline__leading[dir=rtl] {
+ /* @noflip */
+ border-left: none;
+ /* @noflip */
+ border-right: 1px solid;
+}
+
+.mdc-notched-outline__trailing {
+ /* @noflip */
+ border-left: none;
+ /* @noflip */
+ border-right: 1px solid;
+ flex-grow: 1;
+}
+[dir=rtl] .mdc-notched-outline__trailing, .mdc-notched-outline__trailing[dir=rtl] {
+ /* @noflip */
+ border-left: 1px solid;
+ /* @noflip */
+ border-right: none;
+}
+
+.mdc-notched-outline__notch {
+ flex: 0 0 auto;
+ width: auto;
+ max-width: calc(100% - 12px * 2);
+}
+.mdc-notched-outline .mdc-floating-label {
+ display: inline-block;
+ position: relative;
+ max-width: 100%;
+}
+.mdc-notched-outline .mdc-floating-label--float-above {
+ text-overflow: clip;
+}
+.mdc-notched-outline--upgraded .mdc-floating-label--float-above {
+ max-width: calc(100% / 0.75);
+}
+
+.mdc-notched-outline--notched .mdc-notched-outline__notch {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: 8px;
+ border-top: none;
+}
+[dir=rtl] .mdc-notched-outline--notched .mdc-notched-outline__notch, .mdc-notched-outline--notched .mdc-notched-outline__notch[dir=rtl] {
+ /* @noflip */
+ padding-left: 8px;
+ /* @noflip */
+ padding-right: 0;
+}
+
+.mdc-notched-outline--no-label .mdc-notched-outline__notch {
+ display: none;
+}
+
+.mdc-select {
+ display: inline-flex;
+ position: relative;
+}
+.mdc-select:not(.mdc-select--disabled) .mdc-select__selected-text {
+ color: rgba(0, 0, 0, 0.87);
+}
+.mdc-select.mdc-select--disabled .mdc-select__selected-text {
+ color: rgba(0, 0, 0, 0.38);
+}
+.mdc-select:not(.mdc-select--disabled) .mdc-floating-label {
+ color: rgba(0, 0, 0, 0.6);
+}
+.mdc-select:not(.mdc-select--disabled).mdc-select--focused .mdc-floating-label {
+ color: rgba(98, 0, 238, 0.87);
+}
+.mdc-select.mdc-select--disabled .mdc-floating-label {
+ color: rgba(0, 0, 0, 0.38);
+}
+.mdc-select:not(.mdc-select--disabled) .mdc-select__dropdown-icon {
+ fill: rgba(0, 0, 0, 0.54);
+}
+.mdc-select:not(.mdc-select--disabled).mdc-select--focused .mdc-select__dropdown-icon {
+ fill: #6200ee;
+ /* @alternate */
+ fill: var(--mdc-theme-primary, #6200ee);
+}
+.mdc-select.mdc-select--disabled .mdc-select__dropdown-icon {
+ fill: rgba(0, 0, 0, 0.38);
+}
+.mdc-select:not(.mdc-select--disabled) + .mdc-select-helper-text {
+ color: rgba(0, 0, 0, 0.6);
+}
+.mdc-select.mdc-select--disabled + .mdc-select-helper-text {
+ color: rgba(0, 0, 0, 0.38);
+}
+.mdc-select:not(.mdc-select--disabled) .mdc-select__icon {
+ color: rgba(0, 0, 0, 0.54);
+}
+.mdc-select.mdc-select--disabled .mdc-select__icon {
+ color: rgba(0, 0, 0, 0.38);
+}
+@media screen and (forced-colors: active), (-ms-high-contrast: active) {
+ .mdc-select.mdc-select--disabled .mdc-select__selected-text {
+ color: GrayText;
+ }
+ .mdc-select.mdc-select--disabled .mdc-select__dropdown-icon {
+ fill: red;
+ }
+ .mdc-select.mdc-select--disabled .mdc-floating-label {
+ color: GrayText;
+ }
+ .mdc-select.mdc-select--disabled .mdc-line-ripple::before {
+ border-bottom-color: GrayText;
+ }
+ .mdc-select.mdc-select--disabled .mdc-notched-outline__leading,
+.mdc-select.mdc-select--disabled .mdc-notched-outline__notch,
+.mdc-select.mdc-select--disabled .mdc-notched-outline__trailing {
+ border-color: GrayText;
+ }
+ .mdc-select.mdc-select--disabled .mdc-select__icon {
+ color: GrayText;
+ }
+ .mdc-select.mdc-select--disabled + .mdc-select-helper-text {
+ color: GrayText;
+ }
+}
+.mdc-select .mdc-floating-label {
+ top: 50%;
+ -webkit-transform: translateY(-50%);
+ transform: translateY(-50%);
+ pointer-events: none;
+}
+.mdc-select .mdc-select__anchor {
+ /* @noflip */
+ padding-left: 16px;
+ /* @noflip */
+ padding-right: 0;
+}
+[dir=rtl] .mdc-select .mdc-select__anchor, .mdc-select .mdc-select__anchor[dir=rtl] {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: 16px;
+}
+
+.mdc-select.mdc-select--with-leading-icon .mdc-select__anchor {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: 0;
+}
+[dir=rtl] .mdc-select.mdc-select--with-leading-icon .mdc-select__anchor, .mdc-select.mdc-select--with-leading-icon .mdc-select__anchor[dir=rtl] {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: 0;
+}
+
+.mdc-select .mdc-select__icon {
+ width: 24px;
+ height: 24px;
+ font-size: 24px;
+}
+.mdc-select .mdc-select__dropdown-icon {
+ width: 24px;
+ height: 24px;
+}
+.mdc-select .mdc-select__menu .mdc-deprecated-list-item {
+ /* @noflip */
+ padding-left: 16px;
+ /* @noflip */
+ padding-right: 16px;
+}
+[dir=rtl] .mdc-select .mdc-select__menu .mdc-deprecated-list-item, .mdc-select .mdc-select__menu .mdc-deprecated-list-item[dir=rtl] {
+ /* @noflip */
+ padding-left: 16px;
+ /* @noflip */
+ padding-right: 16px;
+}
+
+.mdc-select .mdc-select__menu .mdc-deprecated-list-item__graphic {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 12px;
+}
+[dir=rtl] .mdc-select .mdc-select__menu .mdc-deprecated-list-item__graphic, .mdc-select .mdc-select__menu .mdc-deprecated-list-item__graphic[dir=rtl] {
+ /* @noflip */
+ margin-left: 12px;
+ /* @noflip */
+ margin-right: 0;
+}
+
+.mdc-select__dropdown-icon {
+ /* @noflip */
+ margin-left: 12px;
+ /* @noflip */
+ margin-right: 12px;
+ display: inline-flex;
+ position: relative;
+ align-self: center;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ pointer-events: none;
+}
+.mdc-select__dropdown-icon .mdc-select__dropdown-icon-active,
+.mdc-select__dropdown-icon .mdc-select__dropdown-icon-inactive {
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+.mdc-select__dropdown-icon .mdc-select__dropdown-icon-graphic {
+ width: 41.6666666667%;
+ height: 20.8333333333%;
+}
+.mdc-select__dropdown-icon .mdc-select__dropdown-icon-inactive {
+ opacity: 1;
+ transition: opacity 75ms linear 75ms;
+}
+.mdc-select__dropdown-icon .mdc-select__dropdown-icon-active {
+ opacity: 0;
+ transition: opacity 75ms linear;
+}
+[dir=rtl] .mdc-select__dropdown-icon, .mdc-select__dropdown-icon[dir=rtl] {
+ /* @noflip */
+ margin-left: 12px;
+ /* @noflip */
+ margin-right: 12px;
+}
+
+.mdc-select--activated .mdc-select__dropdown-icon .mdc-select__dropdown-icon-inactive {
+ opacity: 0;
+ transition: opacity 49.5ms linear;
+}
+.mdc-select--activated .mdc-select__dropdown-icon .mdc-select__dropdown-icon-active {
+ opacity: 1;
+ transition: opacity 100.5ms linear 49.5ms;
+}
+
+.mdc-select__anchor {
+ width: 200px;
+ min-width: 0;
+ flex: 1 1 auto;
+ position: relative;
+ box-sizing: border-box;
+ overflow: hidden;
+ outline: none;
+ cursor: pointer;
+}
+.mdc-select__anchor .mdc-floating-label--float-above {
+ -webkit-transform: translateY(-106%) scale(0.75);
+ transform: translateY(-106%) scale(0.75);
+}
+
+.mdc-select__selected-text-container {
+ display: flex;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ pointer-events: none;
+ box-sizing: border-box;
+ width: auto;
+ min-width: 0;
+ flex-grow: 1;
+ height: 28px;
+ border: none;
+ outline: none;
+ padding: 0;
+ background-color: transparent;
+ color: inherit;
+}
+
+.mdc-select__selected-text {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-subtitle1-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 1rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-subtitle1-font-size, 1rem);
+ line-height: 1.75rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-subtitle1-line-height, 1.75rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-subtitle1-font-weight, 400);
+ letter-spacing: 0.009375em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-subtitle1-letter-spacing, 0.009375em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-subtitle1-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-subtitle1-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-subtitle1-text-transform, inherit);
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ display: block;
+ width: 100%;
+ /* @noflip */
+ text-align: left;
+}
+[dir=rtl] .mdc-select__selected-text, .mdc-select__selected-text[dir=rtl] {
+ /* @noflip */
+ text-align: right;
+}
+
+.mdc-select--invalid:not(.mdc-select--disabled) .mdc-floating-label {
+ color: #b00020;
+ /* @alternate */
+ color: var(--mdc-theme-error, #b00020);
+}
+.mdc-select--invalid:not(.mdc-select--disabled).mdc-select--focused .mdc-floating-label {
+ color: #b00020;
+ /* @alternate */
+ color: var(--mdc-theme-error, #b00020);
+}
+.mdc-select--invalid:not(.mdc-select--disabled).mdc-select--invalid + .mdc-select-helper-text--validation-msg {
+ color: #b00020;
+ /* @alternate */
+ color: var(--mdc-theme-error, #b00020);
+}
+.mdc-select--invalid:not(.mdc-select--disabled) .mdc-select__dropdown-icon {
+ fill: #b00020;
+ /* @alternate */
+ fill: var(--mdc-theme-error, #b00020);
+}
+.mdc-select--invalid:not(.mdc-select--disabled).mdc-select--focused .mdc-select__dropdown-icon {
+ fill: #b00020;
+ /* @alternate */
+ fill: var(--mdc-theme-error, #b00020);
+}
+.mdc-select--disabled {
+ cursor: default;
+ pointer-events: none;
+}
+
+.mdc-select--with-leading-icon .mdc-select__menu .mdc-deprecated-list-item {
+ /* @noflip */
+ padding-left: 12px;
+ /* @noflip */
+ padding-right: 12px;
+}
+[dir=rtl] .mdc-select--with-leading-icon .mdc-select__menu .mdc-deprecated-list-item, .mdc-select--with-leading-icon .mdc-select__menu .mdc-deprecated-list-item[dir=rtl] {
+ /* @noflip */
+ padding-left: 12px;
+ /* @noflip */
+ padding-right: 12px;
+}
+
+.mdc-select__menu .mdc-deprecated-list .mdc-select__icon {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 0;
+}
+[dir=rtl] .mdc-select__menu .mdc-deprecated-list .mdc-select__icon, .mdc-select__menu .mdc-deprecated-list .mdc-select__icon[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 0;
+}
+
+.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected,
+.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--activated {
+ color: #000;
+ /* @alternate */
+ color: var(--mdc-theme-on-surface, #000);
+}
+.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected .mdc-deprecated-list-item__graphic,
+.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--activated .mdc-deprecated-list-item__graphic {
+ color: #000;
+ /* @alternate */
+ color: var(--mdc-theme-on-surface, #000);
+}
+
+.mdc-select--filled .mdc-select__anchor {
+ height: 56px;
+ display: flex;
+ align-items: baseline;
+}
+.mdc-select--filled .mdc-select__anchor::before {
+ display: inline-block;
+ width: 0;
+ height: 40px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-select--filled.mdc-select--no-label .mdc-select__anchor .mdc-select__selected-text::before {
+ content: "";
+}
+.mdc-select--filled.mdc-select--no-label .mdc-select__anchor .mdc-select__selected-text-container {
+ height: 100%;
+ display: inline-flex;
+ align-items: center;
+}
+.mdc-select--filled.mdc-select--no-label .mdc-select__anchor::before {
+ display: none;
+}
+.mdc-select--filled .mdc-select__anchor {
+ border-top-left-radius: 4px;
+ /* @alternate */
+ border-top-left-radius: var(--mdc-shape-small, 4px);
+ border-top-right-radius: 4px;
+ /* @alternate */
+ border-top-right-radius: var(--mdc-shape-small, 4px);
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+}
+.mdc-select--filled:not(.mdc-select--disabled) .mdc-select__anchor {
+ background-color: whitesmoke;
+}
+.mdc-select--filled.mdc-select--disabled .mdc-select__anchor {
+ background-color: #fafafa;
+}
+.mdc-select--filled:not(.mdc-select--disabled) .mdc-line-ripple::before {
+ border-bottom-color: rgba(0, 0, 0, 0.42);
+}
+.mdc-select--filled:not(.mdc-select--disabled):hover .mdc-line-ripple::before {
+ border-bottom-color: rgba(0, 0, 0, 0.87);
+}
+.mdc-select--filled:not(.mdc-select--disabled) .mdc-line-ripple::after {
+ border-bottom-color: #6200ee;
+ /* @alternate */
+ border-bottom-color: var(--mdc-theme-primary, #6200ee);
+}
+.mdc-select--filled.mdc-select--disabled .mdc-line-ripple::before {
+ border-bottom-color: rgba(0, 0, 0, 0.06);
+}
+.mdc-select--filled .mdc-floating-label {
+ max-width: calc(100% - 64px);
+}
+.mdc-select--filled .mdc-floating-label--float-above {
+ max-width: calc(100% / 0.75 - 64px / 0.75);
+}
+.mdc-select--filled .mdc-menu-surface--is-open-below {
+ border-top-left-radius: 0px;
+ border-top-right-radius: 0px;
+}
+.mdc-select--filled.mdc-select--focused.mdc-line-ripple::after {
+ -webkit-transform: scale(1, 2);
+ transform: scale(1, 2);
+ opacity: 1;
+}
+.mdc-select--filled .mdc-floating-label {
+ /* @noflip */
+ left: 16px;
+ /* @noflip */
+ right: initial;
+}
+[dir=rtl] .mdc-select--filled .mdc-floating-label, .mdc-select--filled .mdc-floating-label[dir=rtl] {
+ /* @noflip */
+ left: initial;
+ /* @noflip */
+ right: 16px;
+}
+
+.mdc-select--filled.mdc-select--with-leading-icon .mdc-floating-label {
+ /* @noflip */
+ left: 48px;
+ /* @noflip */
+ right: initial;
+}
+[dir=rtl] .mdc-select--filled.mdc-select--with-leading-icon .mdc-floating-label, .mdc-select--filled.mdc-select--with-leading-icon .mdc-floating-label[dir=rtl] {
+ /* @noflip */
+ left: initial;
+ /* @noflip */
+ right: 48px;
+}
+
+.mdc-select--filled.mdc-select--with-leading-icon .mdc-floating-label {
+ max-width: calc(100% - 96px);
+}
+.mdc-select--filled.mdc-select--with-leading-icon .mdc-floating-label--float-above {
+ max-width: calc(100% / 0.75 - 96px / 0.75);
+}
+
+.mdc-select--invalid:not(.mdc-select--disabled) .mdc-line-ripple::before {
+ border-bottom-color: #b00020;
+ /* @alternate */
+ border-bottom-color: var(--mdc-theme-error, #b00020);
+}
+.mdc-select--invalid:not(.mdc-select--disabled):hover .mdc-line-ripple::before {
+ border-bottom-color: #b00020;
+ /* @alternate */
+ border-bottom-color: var(--mdc-theme-error, #b00020);
+}
+.mdc-select--invalid:not(.mdc-select--disabled) .mdc-line-ripple::after {
+ border-bottom-color: #b00020;
+ /* @alternate */
+ border-bottom-color: var(--mdc-theme-error, #b00020);
+}
+.mdc-select--outlined {
+ border: none;
+}
+.mdc-select--outlined .mdc-select__anchor {
+ height: 56px;
+}
+.mdc-select--outlined .mdc-select__anchor .mdc-floating-label--float-above {
+ -webkit-transform: translateY(-37.25px) scale(1);
+ transform: translateY(-37.25px) scale(1);
+}
+.mdc-select--outlined .mdc-select__anchor .mdc-floating-label--float-above {
+ font-size: 0.75rem;
+}
+.mdc-select--outlined .mdc-select__anchor.mdc-notched-outline--upgraded .mdc-floating-label--float-above,
+.mdc-select--outlined .mdc-select__anchor .mdc-notched-outline--upgraded .mdc-floating-label--float-above {
+ -webkit-transform: translateY(-34.75px) scale(0.75);
+ transform: translateY(-34.75px) scale(0.75);
+}
+.mdc-select--outlined .mdc-select__anchor.mdc-notched-outline--upgraded .mdc-floating-label--float-above,
+.mdc-select--outlined .mdc-select__anchor .mdc-notched-outline--upgraded .mdc-floating-label--float-above {
+ font-size: 1rem;
+}
+.mdc-select--outlined .mdc-select__anchor .mdc-floating-label--shake {
+ -webkit-animation: mdc-floating-label-shake-float-above-select-outlined-56px 250ms 1;
+ animation: mdc-floating-label-shake-float-above-select-outlined-56px 250ms 1;
+}
+@-webkit-keyframes mdc-floating-label-shake-float-above-select-outlined-56px {
+ 0% {
+ -webkit-transform: translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75);
+ }
+ 33% {
+ -webkit-animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ -webkit-transform: translateX(calc(4% - 0%)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(4% - 0%)) translateY(-34.75px) scale(0.75);
+ }
+ 66% {
+ -webkit-animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ -webkit-transform: translateX(calc(-4% - 0%)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(-4% - 0%)) translateY(-34.75px) scale(0.75);
+ }
+ 100% {
+ -webkit-transform: translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75);
+ }
+}
+@keyframes mdc-floating-label-shake-float-above-select-outlined-56px {
+ 0% {
+ -webkit-transform: translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75);
+ }
+ 33% {
+ -webkit-animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ -webkit-transform: translateX(calc(4% - 0%)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(4% - 0%)) translateY(-34.75px) scale(0.75);
+ }
+ 66% {
+ -webkit-animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ -webkit-transform: translateX(calc(-4% - 0%)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(-4% - 0%)) translateY(-34.75px) scale(0.75);
+ }
+ 100% {
+ -webkit-transform: translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75);
+ }
+}
+.mdc-select--outlined .mdc-notched-outline .mdc-notched-outline__leading {
+ /* @noflip */
+ border-top-left-radius: 4px;
+ /* @alternate */
+ /* @noflip */
+ border-top-left-radius: var(--mdc-shape-small, 4px);
+ /* @noflip */
+ border-top-right-radius: 0;
+ /* @noflip */
+ border-bottom-right-radius: 0;
+ /* @noflip */
+ border-bottom-left-radius: 4px;
+ /* @alternate */
+ /* @noflip */
+ border-bottom-left-radius: var(--mdc-shape-small, 4px);
+}
+[dir=rtl] .mdc-select--outlined .mdc-notched-outline .mdc-notched-outline__leading, .mdc-select--outlined .mdc-notched-outline .mdc-notched-outline__leading[dir=rtl] {
+ /* @noflip */
+ border-top-left-radius: 0;
+ /* @noflip */
+ border-top-right-radius: 4px;
+ /* @alternate */
+ /* @noflip */
+ border-top-right-radius: var(--mdc-shape-small, 4px);
+ /* @noflip */
+ border-bottom-right-radius: 4px;
+ /* @alternate */
+ /* @noflip */
+ border-bottom-right-radius: var(--mdc-shape-small, 4px);
+ /* @noflip */
+ border-bottom-left-radius: 0;
+}
+
+@supports (top: max(0%)) {
+ .mdc-select--outlined .mdc-notched-outline .mdc-notched-outline__leading {
+ width: max(12px, var(--mdc-shape-small, 4px));
+ }
+}
+@supports (top: max(0%)) {
+ .mdc-select--outlined .mdc-notched-outline .mdc-notched-outline__notch {
+ max-width: calc(100% - max(12px, var(--mdc-shape-small, 4px)) * 2);
+ }
+}
+.mdc-select--outlined .mdc-notched-outline .mdc-notched-outline__trailing {
+ /* @noflip */
+ border-top-left-radius: 0;
+ /* @noflip */
+ border-top-right-radius: 4px;
+ /* @alternate */
+ /* @noflip */
+ border-top-right-radius: var(--mdc-shape-small, 4px);
+ /* @noflip */
+ border-bottom-right-radius: 4px;
+ /* @alternate */
+ /* @noflip */
+ border-bottom-right-radius: var(--mdc-shape-small, 4px);
+ /* @noflip */
+ border-bottom-left-radius: 0;
+}
+[dir=rtl] .mdc-select--outlined .mdc-notched-outline .mdc-notched-outline__trailing, .mdc-select--outlined .mdc-notched-outline .mdc-notched-outline__trailing[dir=rtl] {
+ /* @noflip */
+ border-top-left-radius: 4px;
+ /* @alternate */
+ /* @noflip */
+ border-top-left-radius: var(--mdc-shape-small, 4px);
+ /* @noflip */
+ border-top-right-radius: 0;
+ /* @noflip */
+ border-bottom-right-radius: 0;
+ /* @noflip */
+ border-bottom-left-radius: 4px;
+ /* @alternate */
+ /* @noflip */
+ border-bottom-left-radius: var(--mdc-shape-small, 4px);
+}
+
+@supports (top: max(0%)) {
+ .mdc-select--outlined .mdc-select__anchor {
+ /* @noflip */
+ padding-left: max(16px, calc(var(--mdc-shape-small, 4px) + 4px));
+ }
+}
+[dir=rtl] .mdc-select--outlined .mdc-select__anchor, .mdc-select--outlined .mdc-select__anchor[dir=rtl] {
+ /* @noflip */
+ padding-left: 0;
+}
+@supports (top: max(0%)) {
+ [dir=rtl] .mdc-select--outlined .mdc-select__anchor, .mdc-select--outlined .mdc-select__anchor[dir=rtl] {
+ /* @noflip */
+ padding-right: max(16px, calc(var(--mdc-shape-small, 4px) + 4px));
+ }
+}
+
+@supports (top: max(0%)) {
+ .mdc-select--outlined + .mdc-select-helper-text {
+ /* @noflip */
+ margin-left: max(16px, calc(var(--mdc-shape-small, 4px) + 4px));
+ }
+}
+[dir=rtl] .mdc-select--outlined + .mdc-select-helper-text, .mdc-select--outlined + .mdc-select-helper-text[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+}
+@supports (top: max(0%)) {
+ [dir=rtl] .mdc-select--outlined + .mdc-select-helper-text, .mdc-select--outlined + .mdc-select-helper-text[dir=rtl] {
+ /* @noflip */
+ margin-right: max(16px, calc(var(--mdc-shape-small, 4px) + 4px));
+ }
+}
+
+.mdc-select--outlined:not(.mdc-select--disabled) .mdc-select__anchor {
+ background-color: transparent;
+}
+.mdc-select--outlined.mdc-select--disabled .mdc-select__anchor {
+ background-color: transparent;
+}
+.mdc-select--outlined:not(.mdc-select--disabled) .mdc-notched-outline__leading,
+.mdc-select--outlined:not(.mdc-select--disabled) .mdc-notched-outline__notch,
+.mdc-select--outlined:not(.mdc-select--disabled) .mdc-notched-outline__trailing {
+ border-color: rgba(0, 0, 0, 0.38);
+}
+.mdc-select--outlined:not(.mdc-select--disabled):not(.mdc-select--focused) .mdc-select__anchor:hover .mdc-notched-outline .mdc-notched-outline__leading,
+.mdc-select--outlined:not(.mdc-select--disabled):not(.mdc-select--focused) .mdc-select__anchor:hover .mdc-notched-outline .mdc-notched-outline__notch,
+.mdc-select--outlined:not(.mdc-select--disabled):not(.mdc-select--focused) .mdc-select__anchor:hover .mdc-notched-outline .mdc-notched-outline__trailing {
+ border-color: rgba(0, 0, 0, 0.87);
+}
+.mdc-select--outlined:not(.mdc-select--disabled).mdc-select--focused .mdc-notched-outline .mdc-notched-outline__leading,
+.mdc-select--outlined:not(.mdc-select--disabled).mdc-select--focused .mdc-notched-outline .mdc-notched-outline__notch,
+.mdc-select--outlined:not(.mdc-select--disabled).mdc-select--focused .mdc-notched-outline .mdc-notched-outline__trailing {
+ border-width: 2px;
+}
+.mdc-select--outlined:not(.mdc-select--disabled).mdc-select--focused .mdc-notched-outline .mdc-notched-outline__leading,
+.mdc-select--outlined:not(.mdc-select--disabled).mdc-select--focused .mdc-notched-outline .mdc-notched-outline__notch,
+.mdc-select--outlined:not(.mdc-select--disabled).mdc-select--focused .mdc-notched-outline .mdc-notched-outline__trailing {
+ border-color: #6200ee;
+ /* @alternate */
+ border-color: var(--mdc-theme-primary, #6200ee);
+}
+.mdc-select--outlined.mdc-select--disabled .mdc-notched-outline__leading,
+.mdc-select--outlined.mdc-select--disabled .mdc-notched-outline__notch,
+.mdc-select--outlined.mdc-select--disabled .mdc-notched-outline__trailing {
+ border-color: rgba(0, 0, 0, 0.06);
+}
+.mdc-select--outlined .mdc-select__anchor :not(.mdc-notched-outline--notched) .mdc-notched-outline__notch {
+ max-width: calc(100% - 60px);
+}
+.mdc-select--outlined .mdc-select__anchor {
+ display: flex;
+ align-items: baseline;
+ overflow: visible;
+}
+.mdc-select--outlined .mdc-select__anchor .mdc-floating-label--shake {
+ -webkit-animation: mdc-floating-label-shake-float-above-select-outlined 250ms 1;
+ animation: mdc-floating-label-shake-float-above-select-outlined 250ms 1;
+}
+.mdc-select--outlined .mdc-select__anchor .mdc-floating-label--float-above {
+ -webkit-transform: translateY(-37.25px) scale(1);
+ transform: translateY(-37.25px) scale(1);
+}
+.mdc-select--outlined .mdc-select__anchor .mdc-floating-label--float-above {
+ font-size: 0.75rem;
+}
+.mdc-select--outlined .mdc-select__anchor.mdc-notched-outline--upgraded .mdc-floating-label--float-above,
+.mdc-select--outlined .mdc-select__anchor .mdc-notched-outline--upgraded .mdc-floating-label--float-above {
+ -webkit-transform: translateY(-34.75px) scale(0.75);
+ transform: translateY(-34.75px) scale(0.75);
+}
+.mdc-select--outlined .mdc-select__anchor.mdc-notched-outline--upgraded .mdc-floating-label--float-above,
+.mdc-select--outlined .mdc-select__anchor .mdc-notched-outline--upgraded .mdc-floating-label--float-above {
+ font-size: 1rem;
+}
+.mdc-select--outlined .mdc-select__anchor .mdc-notched-outline--notched .mdc-notched-outline__notch {
+ padding-top: 1px;
+}
+.mdc-select--outlined .mdc-select__anchor .mdc-select__selected-text::before {
+ content: "";
+}
+.mdc-select--outlined .mdc-select__anchor .mdc-select__selected-text-container {
+ height: 100%;
+ display: inline-flex;
+ align-items: center;
+}
+.mdc-select--outlined .mdc-select__anchor::before {
+ display: none;
+}
+.mdc-select--outlined .mdc-select__selected-text-container {
+ display: flex;
+ border: none;
+ z-index: 1;
+ background-color: transparent;
+}
+.mdc-select--outlined .mdc-select__icon {
+ z-index: 2;
+}
+.mdc-select--outlined .mdc-floating-label {
+ line-height: 1.15rem;
+ /* @noflip */
+ left: 4px;
+ /* @noflip */
+ right: initial;
+}
+[dir=rtl] .mdc-select--outlined .mdc-floating-label, .mdc-select--outlined .mdc-floating-label[dir=rtl] {
+ /* @noflip */
+ left: initial;
+ /* @noflip */
+ right: 4px;
+}
+
+.mdc-select--outlined.mdc-select--focused .mdc-notched-outline--notched .mdc-notched-outline__notch {
+ padding-top: 2px;
+}
+.mdc-select--outlined.mdc-select--invalid:not(.mdc-select--disabled) .mdc-notched-outline__leading,
+.mdc-select--outlined.mdc-select--invalid:not(.mdc-select--disabled) .mdc-notched-outline__notch,
+.mdc-select--outlined.mdc-select--invalid:not(.mdc-select--disabled) .mdc-notched-outline__trailing {
+ border-color: #b00020;
+ /* @alternate */
+ border-color: var(--mdc-theme-error, #b00020);
+}
+.mdc-select--outlined.mdc-select--invalid:not(.mdc-select--disabled):not(.mdc-select--focused) .mdc-select__anchor:hover .mdc-notched-outline .mdc-notched-outline__leading,
+.mdc-select--outlined.mdc-select--invalid:not(.mdc-select--disabled):not(.mdc-select--focused) .mdc-select__anchor:hover .mdc-notched-outline .mdc-notched-outline__notch,
+.mdc-select--outlined.mdc-select--invalid:not(.mdc-select--disabled):not(.mdc-select--focused) .mdc-select__anchor:hover .mdc-notched-outline .mdc-notched-outline__trailing {
+ border-color: #b00020;
+ /* @alternate */
+ border-color: var(--mdc-theme-error, #b00020);
+}
+.mdc-select--outlined.mdc-select--invalid:not(.mdc-select--disabled).mdc-select--focused .mdc-notched-outline .mdc-notched-outline__leading,
+.mdc-select--outlined.mdc-select--invalid:not(.mdc-select--disabled).mdc-select--focused .mdc-notched-outline .mdc-notched-outline__notch,
+.mdc-select--outlined.mdc-select--invalid:not(.mdc-select--disabled).mdc-select--focused .mdc-notched-outline .mdc-notched-outline__trailing {
+ border-width: 2px;
+}
+.mdc-select--outlined.mdc-select--invalid:not(.mdc-select--disabled).mdc-select--focused .mdc-notched-outline .mdc-notched-outline__leading,
+.mdc-select--outlined.mdc-select--invalid:not(.mdc-select--disabled).mdc-select--focused .mdc-notched-outline .mdc-notched-outline__notch,
+.mdc-select--outlined.mdc-select--invalid:not(.mdc-select--disabled).mdc-select--focused .mdc-notched-outline .mdc-notched-outline__trailing {
+ border-color: #b00020;
+ /* @alternate */
+ border-color: var(--mdc-theme-error, #b00020);
+}
+.mdc-select--outlined.mdc-select--with-leading-icon .mdc-floating-label {
+ /* @noflip */
+ left: 36px;
+ /* @noflip */
+ right: initial;
+}
+[dir=rtl] .mdc-select--outlined.mdc-select--with-leading-icon .mdc-floating-label, .mdc-select--outlined.mdc-select--with-leading-icon .mdc-floating-label[dir=rtl] {
+ /* @noflip */
+ left: initial;
+ /* @noflip */
+ right: 36px;
+}
+
+.mdc-select--outlined.mdc-select--with-leading-icon .mdc-floating-label--float-above {
+ -webkit-transform: translateY(-37.25px) translateX(-32px) scale(1);
+ transform: translateY(-37.25px) translateX(-32px) scale(1);
+}
+[dir=rtl] .mdc-select--outlined.mdc-select--with-leading-icon .mdc-floating-label--float-above, .mdc-select--outlined.mdc-select--with-leading-icon .mdc-floating-label--float-above[dir=rtl] {
+ -webkit-transform: translateY(-37.25px) translateX(32px) scale(1);
+ transform: translateY(-37.25px) translateX(32px) scale(1);
+}
+
+.mdc-select--outlined.mdc-select--with-leading-icon .mdc-floating-label--float-above {
+ font-size: 0.75rem;
+}
+.mdc-select--outlined.mdc-select--with-leading-icon.mdc-notched-outline--upgraded .mdc-floating-label--float-above,
+.mdc-select--outlined.mdc-select--with-leading-icon .mdc-notched-outline--upgraded .mdc-floating-label--float-above {
+ -webkit-transform: translateY(-34.75px) translateX(-32px) scale(0.75);
+ transform: translateY(-34.75px) translateX(-32px) scale(0.75);
+}
+[dir=rtl] .mdc-select--outlined.mdc-select--with-leading-icon.mdc-notched-outline--upgraded .mdc-floating-label--float-above, [dir=rtl] .mdc-select--outlined.mdc-select--with-leading-icon .mdc-notched-outline--upgraded .mdc-floating-label--float-above, .mdc-select--outlined.mdc-select--with-leading-icon.mdc-notched-outline--upgraded .mdc-floating-label--float-above[dir=rtl], .mdc-select--outlined.mdc-select--with-leading-icon .mdc-notched-outline--upgraded .mdc-floating-label--float-above[dir=rtl] {
+ -webkit-transform: translateY(-34.75px) translateX(32px) scale(0.75);
+ transform: translateY(-34.75px) translateX(32px) scale(0.75);
+}
+
+.mdc-select--outlined.mdc-select--with-leading-icon.mdc-notched-outline--upgraded .mdc-floating-label--float-above,
+.mdc-select--outlined.mdc-select--with-leading-icon .mdc-notched-outline--upgraded .mdc-floating-label--float-above {
+ font-size: 1rem;
+}
+.mdc-select--outlined.mdc-select--with-leading-icon .mdc-floating-label--shake {
+ -webkit-animation: mdc-floating-label-shake-float-above-select-outlined-leading-icon-56px 250ms 1;
+ animation: mdc-floating-label-shake-float-above-select-outlined-leading-icon-56px 250ms 1;
+}
+@-webkit-keyframes mdc-floating-label-shake-float-above-select-outlined-leading-icon-56px {
+ 0% {
+ -webkit-transform: translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75);
+ }
+ 33% {
+ -webkit-animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ -webkit-transform: translateX(calc(4% - 32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(4% - 32px)) translateY(-34.75px) scale(0.75);
+ }
+ 66% {
+ -webkit-animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ -webkit-transform: translateX(calc(-4% - 32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(-4% - 32px)) translateY(-34.75px) scale(0.75);
+ }
+ 100% {
+ -webkit-transform: translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75);
+ }
+}
+@keyframes mdc-floating-label-shake-float-above-select-outlined-leading-icon-56px {
+ 0% {
+ -webkit-transform: translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75);
+ }
+ 33% {
+ -webkit-animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ -webkit-transform: translateX(calc(4% - 32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(4% - 32px)) translateY(-34.75px) scale(0.75);
+ }
+ 66% {
+ -webkit-animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ -webkit-transform: translateX(calc(-4% - 32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(-4% - 32px)) translateY(-34.75px) scale(0.75);
+ }
+ 100% {
+ -webkit-transform: translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75);
+ }
+}
+[dir=rtl] .mdc-select--outlined.mdc-select--with-leading-icon .mdc-floating-label--shake, .mdc-select--outlined.mdc-select--with-leading-icon[dir=rtl] .mdc-floating-label--shake {
+ -webkit-animation: mdc-floating-label-shake-float-above-select-outlined-leading-icon-56px 250ms 1;
+ animation: mdc-floating-label-shake-float-above-select-outlined-leading-icon-56px 250ms 1;
+}
+
+@-webkit-keyframes mdc-floating-label-shake-float-above-select-outlined-leading-icon-56px-rtl {
+ 0% {
+ -webkit-transform: translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75);
+ }
+ 33% {
+ -webkit-animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ -webkit-transform: translateX(calc(4% - -32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(4% - -32px)) translateY(-34.75px) scale(0.75);
+ }
+ 66% {
+ -webkit-animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ -webkit-transform: translateX(calc(-4% - -32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(-4% - -32px)) translateY(-34.75px) scale(0.75);
+ }
+ 100% {
+ -webkit-transform: translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75);
+ }
+}
+
+@keyframes mdc-floating-label-shake-float-above-select-outlined-leading-icon-56px-rtl {
+ 0% {
+ -webkit-transform: translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75);
+ }
+ 33% {
+ -webkit-animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ -webkit-transform: translateX(calc(4% - -32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(4% - -32px)) translateY(-34.75px) scale(0.75);
+ }
+ 66% {
+ -webkit-animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ -webkit-transform: translateX(calc(-4% - -32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(-4% - -32px)) translateY(-34.75px) scale(0.75);
+ }
+ 100% {
+ -webkit-transform: translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75);
+ }
+}
+.mdc-select--outlined.mdc-select--with-leading-icon .mdc-select__anchor :not(.mdc-notched-outline--notched) .mdc-notched-outline__notch {
+ max-width: calc(100% - 96px);
+}
+.mdc-select--outlined .mdc-menu-surface {
+ margin-bottom: 8px;
+}
+.mdc-select--outlined.mdc-select--no-label .mdc-menu-surface,
+.mdc-select--outlined .mdc-menu-surface--is-open-below {
+ margin-bottom: 0;
+}
+
+.mdc-select__anchor {
+ --mdc-ripple-fg-size: 0;
+ --mdc-ripple-left: 0;
+ --mdc-ripple-top: 0;
+ --mdc-ripple-fg-scale: 1;
+ --mdc-ripple-fg-translate-end: 0;
+ --mdc-ripple-fg-translate-start: 0;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ will-change: transform, opacity;
+}
+.mdc-select__anchor .mdc-select__ripple::before,
+.mdc-select__anchor .mdc-select__ripple::after {
+ position: absolute;
+ border-radius: 50%;
+ opacity: 0;
+ pointer-events: none;
+ content: "";
+}
+.mdc-select__anchor .mdc-select__ripple::before {
+ transition: opacity 15ms linear, background-color 15ms linear;
+ z-index: 1;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 1);
+}
+.mdc-select__anchor .mdc-select__ripple::after {
+ z-index: 0;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 0);
+}
+.mdc-select__anchor.mdc-ripple-upgraded .mdc-select__ripple::before {
+ -webkit-transform: scale(var(--mdc-ripple-fg-scale, 1));
+ transform: scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-select__anchor.mdc-ripple-upgraded .mdc-select__ripple::after {
+ top: 0;
+ /* @noflip */
+ left: 0;
+ -webkit-transform: scale(0);
+ transform: scale(0);
+ -webkit-transform-origin: center center;
+ transform-origin: center center;
+}
+.mdc-select__anchor.mdc-ripple-upgraded--unbounded .mdc-select__ripple::after {
+ top: var(--mdc-ripple-top, 0);
+ /* @noflip */
+ left: var(--mdc-ripple-left, 0);
+}
+.mdc-select__anchor.mdc-ripple-upgraded--foreground-activation .mdc-select__ripple::after {
+ -webkit-animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+ animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+}
+.mdc-select__anchor.mdc-ripple-upgraded--foreground-deactivation .mdc-select__ripple::after {
+ -webkit-animation: mdc-ripple-fg-opacity-out 150ms;
+ animation: mdc-ripple-fg-opacity-out 150ms;
+ -webkit-transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+ transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-select__anchor .mdc-select__ripple::before,
+.mdc-select__anchor .mdc-select__ripple::after {
+ top: calc(50% - 100%);
+ /* @noflip */
+ left: calc(50% - 100%);
+ width: 200%;
+ height: 200%;
+}
+.mdc-select__anchor.mdc-ripple-upgraded .mdc-select__ripple::after {
+ width: var(--mdc-ripple-fg-size, 100%);
+ height: var(--mdc-ripple-fg-size, 100%);
+}
+.mdc-select__anchor .mdc-select__ripple::before,
+.mdc-select__anchor .mdc-select__ripple::after {
+ background-color: rgba(0, 0, 0, 0.87);
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, rgba(0, 0, 0, 0.87));
+}
+.mdc-select__anchor:hover .mdc-select__ripple::before, .mdc-select__anchor.mdc-ripple-surface--hover .mdc-select__ripple::before {
+ opacity: 0.04;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.04);
+}
+.mdc-select__anchor.mdc-ripple-upgraded--background-focused .mdc-select__ripple::before, .mdc-select__anchor:not(.mdc-ripple-upgraded):focus .mdc-select__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.12);
+}
+.mdc-select__anchor .mdc-select__ripple {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+}
+
+.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected .mdc-deprecated-list-item__ripple::before, .mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected .mdc-deprecated-list-item__ripple::after {
+ background-color: #000;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, var(--mdc-theme-on-surface, #000));
+}
+.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected:hover .mdc-deprecated-list-item__ripple::before, .mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected.mdc-ripple-surface--hover .mdc-deprecated-list-item__ripple::before {
+ opacity: 0.04;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.04);
+}
+.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected.mdc-ripple-upgraded--background-focused .mdc-deprecated-list-item__ripple::before, .mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected:not(.mdc-ripple-upgraded):focus .mdc-deprecated-list-item__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.12);
+}
+.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected:not(.mdc-ripple-upgraded) .mdc-deprecated-list-item__ripple::after {
+ transition: opacity 150ms linear;
+}
+.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected:not(.mdc-ripple-upgraded):active .mdc-deprecated-list-item__ripple::after {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected .mdc-list-item__ripple::before, .mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected .mdc-list-item__ripple::after {
+ background-color: #000;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, var(--mdc-theme-on-surface, #000));
+}
+.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected:hover .mdc-list-item__ripple::before, .mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected.mdc-ripple-surface--hover .mdc-list-item__ripple::before {
+ opacity: 0.04;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.04);
+}
+.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected.mdc-ripple-upgraded--background-focused .mdc-list-item__ripple::before, .mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected:not(.mdc-ripple-upgraded):focus .mdc-list-item__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.12);
+}
+.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected:not(.mdc-ripple-upgraded) .mdc-list-item__ripple::after {
+ transition: opacity 150ms linear;
+}
+.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected:not(.mdc-ripple-upgraded):active .mdc-list-item__ripple::after {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+
+.mdc-select-helper-text {
+ margin: 0;
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 16px;
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-caption-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.75rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-caption-font-size, 0.75rem);
+ line-height: 1.25rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-caption-line-height, 1.25rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-caption-font-weight, 400);
+ letter-spacing: 0.0333333333em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-caption-letter-spacing, 0.0333333333em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-caption-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-caption-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-caption-text-transform, inherit);
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+}
+[dir=rtl] .mdc-select-helper-text, .mdc-select-helper-text[dir=rtl] {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 16px;
+}
+
+.mdc-select-helper-text::before {
+ display: inline-block;
+ width: 0;
+ height: 16px;
+ content: "";
+ vertical-align: 0;
+}
+
+.mdc-select-helper-text--validation-msg {
+ opacity: 0;
+ transition: opacity 180ms cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.mdc-select--invalid + .mdc-select-helper-text--validation-msg,
+.mdc-select-helper-text--validation-msg-persistent {
+ opacity: 1;
+}
+
+.mdc-select--with-leading-icon .mdc-select__icon {
+ display: inline-block;
+ box-sizing: border-box;
+ border: none;
+ text-decoration: none;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ flex-shrink: 0;
+ align-self: center;
+ background-color: transparent;
+ fill: currentColor;
+}
+.mdc-select--with-leading-icon .mdc-select__icon {
+ /* @noflip */
+ margin-left: 12px;
+ /* @noflip */
+ margin-right: 12px;
+}
+[dir=rtl] .mdc-select--with-leading-icon .mdc-select__icon, .mdc-select--with-leading-icon .mdc-select__icon[dir=rtl] {
+ /* @noflip */
+ margin-left: 12px;
+ /* @noflip */
+ margin-right: 12px;
+}
+
+.mdc-select__icon:not([tabindex]),
+.mdc-select__icon[tabindex="-1"] {
+ cursor: default;
+ pointer-events: none;
+}
+
+.mdc-data-table__content {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.875rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-body2-font-size, 0.875rem);
+ line-height: 1.25rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-body2-line-height, 1.25rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-body2-font-weight, 400);
+ letter-spacing: 0.0178571429em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-body2-letter-spacing, 0.0178571429em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-body2-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-body2-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-body2-text-transform, inherit);
+}
+
+.mdc-data-table {
+ background-color: #fff;
+ /* @alternate */
+ background-color: var(--mdc-theme-surface, #fff);
+ border-radius: 4px;
+ /* @alternate */
+ border-radius: var(--mdc-shape-medium, 4px);
+ border-width: 1px;
+ border-style: solid;
+ border-color: rgba(0, 0, 0, 0.12);
+ -webkit-overflow-scrolling: touch;
+ display: inline-flex;
+ flex-direction: column;
+ box-sizing: border-box;
+ position: relative;
+}
+.mdc-data-table .mdc-data-table__header-cell:first-child {
+ border-top-left-radius: 4px;
+ /* @alternate */
+ border-top-left-radius: var(--mdc-shape-medium, 4px);
+}
+[dir=rtl] .mdc-data-table .mdc-data-table__header-cell:first-child, .mdc-data-table .mdc-data-table__header-cell:first-child[dir=rtl] {
+ border-top-right-radius: 4px;
+ /* @alternate */
+ border-top-right-radius: var(--mdc-shape-medium, 4px);
+ border-top-left-radius: 0;
+}
+
+.mdc-data-table .mdc-data-table__header-cell:last-child {
+ border-top-right-radius: 4px;
+ /* @alternate */
+ border-top-right-radius: var(--mdc-shape-medium, 4px);
+}
+[dir=rtl] .mdc-data-table .mdc-data-table__header-cell:last-child, .mdc-data-table .mdc-data-table__header-cell:last-child[dir=rtl] {
+ border-top-left-radius: 4px;
+ /* @alternate */
+ border-top-left-radius: var(--mdc-shape-medium, 4px);
+ border-top-right-radius: 0;
+}
+
+.mdc-data-table__row {
+ background-color: inherit;
+}
+
+.mdc-data-table__header-cell {
+ background-color: #fff;
+ /* @alternate */
+ background-color: var(--mdc-theme-surface, #fff);
+}
+
+.mdc-data-table__row--selected {
+ background-color: rgba(98, 0, 238, 0.04);
+}
+
+.mdc-data-table__pagination-rows-per-page-select:not(.mdc-select--disabled) .mdc-notched-outline__leading,
+.mdc-data-table__pagination-rows-per-page-select:not(.mdc-select--disabled) .mdc-notched-outline__notch,
+.mdc-data-table__pagination-rows-per-page-select:not(.mdc-select--disabled) .mdc-notched-outline__trailing {
+ border-color: rgba(0, 0, 0, 0.12);
+}
+.mdc-data-table__cell,
+.mdc-data-table__header-cell {
+ border-bottom-color: rgba(0, 0, 0, 0.12);
+}
+
+.mdc-data-table__pagination {
+ border-top-color: rgba(0, 0, 0, 0.12);
+}
+
+.mdc-data-table__cell,
+.mdc-data-table__header-cell {
+ border-bottom-width: 1px;
+ border-bottom-style: solid;
+}
+
+.mdc-data-table__pagination {
+ border-top-width: 1px;
+ border-top-style: solid;
+}
+
+.mdc-data-table__row:last-child .mdc-data-table__cell {
+ border-bottom: none;
+}
+
+.mdc-data-table__row:not(.mdc-data-table__row--selected):hover {
+ background-color: rgba(0, 0, 0, 0.04);
+}
+
+.mdc-data-table__header-cell {
+ color: rgba(0, 0, 0, 0.87);
+}
+
+.mdc-data-table__pagination-total,
+.mdc-data-table__pagination-rows-per-page-label,
+.mdc-data-table__cell {
+ color: rgba(0, 0, 0, 0.87);
+}
+
+.mdc-data-table__row {
+ height: 52px;
+}
+
+.mdc-data-table__pagination {
+ min-height: 52px;
+}
+
+.mdc-data-table__header-row {
+ height: 56px;
+}
+
+.mdc-data-table__cell,
+.mdc-data-table__header-cell {
+ padding: 0 16px 0 16px;
+}
+
+.mdc-data-table__header-cell--checkbox,
+.mdc-data-table__cell--checkbox {
+ /* @noflip */
+ padding-left: 4px;
+ /* @noflip */
+ padding-right: 0;
+}
+[dir=rtl] .mdc-data-table__header-cell--checkbox, [dir=rtl] .mdc-data-table__cell--checkbox, .mdc-data-table__header-cell--checkbox[dir=rtl], .mdc-data-table__cell--checkbox[dir=rtl] {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: 4px;
+}
+
+.mdc-data-table__sort-icon-button {
+ color: rgba(0, 0, 0, 0.6);
+}
+.mdc-data-table__sort-icon-button .mdc-icon-button__ripple::before, .mdc-data-table__sort-icon-button .mdc-icon-button__ripple::after {
+ background-color: rgba(0, 0, 0, 0.6);
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, rgba(0, 0, 0, 0.6));
+}
+.mdc-data-table__sort-icon-button:hover .mdc-icon-button__ripple::before, .mdc-data-table__sort-icon-button.mdc-ripple-surface--hover .mdc-icon-button__ripple::before {
+ opacity: 0.04;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.04);
+}
+.mdc-data-table__sort-icon-button.mdc-ripple-upgraded--background-focused .mdc-icon-button__ripple::before, .mdc-data-table__sort-icon-button:not(.mdc-ripple-upgraded):focus .mdc-icon-button__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.12);
+}
+.mdc-data-table__sort-icon-button:not(.mdc-ripple-upgraded) .mdc-icon-button__ripple::after {
+ transition: opacity 150ms linear;
+}
+.mdc-data-table__sort-icon-button:not(.mdc-ripple-upgraded):active .mdc-icon-button__ripple::after {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-data-table__sort-icon-button.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+
+.mdc-data-table__header-cell--sorted .mdc-data-table__sort-icon-button {
+ color: rgba(0, 0, 0, 0.87);
+}
+.mdc-data-table__header-cell--sorted .mdc-data-table__sort-icon-button .mdc-icon-button__ripple::before, .mdc-data-table__header-cell--sorted .mdc-data-table__sort-icon-button .mdc-icon-button__ripple::after {
+ background-color: rgba(0, 0, 0, 0.87);
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, rgba(0, 0, 0, 0.87));
+}
+.mdc-data-table__header-cell--sorted .mdc-data-table__sort-icon-button:hover .mdc-icon-button__ripple::before, .mdc-data-table__header-cell--sorted .mdc-data-table__sort-icon-button.mdc-ripple-surface--hover .mdc-icon-button__ripple::before {
+ opacity: 0.04;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.04);
+}
+.mdc-data-table__header-cell--sorted .mdc-data-table__sort-icon-button.mdc-ripple-upgraded--background-focused .mdc-icon-button__ripple::before, .mdc-data-table__header-cell--sorted .mdc-data-table__sort-icon-button:not(.mdc-ripple-upgraded):focus .mdc-icon-button__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.12);
+}
+.mdc-data-table__header-cell--sorted .mdc-data-table__sort-icon-button:not(.mdc-ripple-upgraded) .mdc-icon-button__ripple::after {
+ transition: opacity 150ms linear;
+}
+.mdc-data-table__header-cell--sorted .mdc-data-table__sort-icon-button:not(.mdc-ripple-upgraded):active .mdc-icon-button__ripple::after {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-data-table__header-cell--sorted .mdc-data-table__sort-icon-button.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+
+.mdc-data-table__table-container {
+ -webkit-overflow-scrolling: touch;
+ overflow-x: auto;
+ width: 100%;
+}
+
+.mdc-data-table__table {
+ min-width: 100%;
+ border: 0;
+ white-space: nowrap;
+ border-spacing: 0;
+ /**
+ * With table-layout:fixed, table and column widths are defined by the width
+ * of the first row of cells. Cells in subsequent rows do not affect column
+ * widths. This results in a predictable table layout and may also speed up
+ * rendering.
+ */
+ table-layout: fixed;
+}
+
+.mdc-data-table__cell {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.875rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-body2-font-size, 0.875rem);
+ line-height: 1.25rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-body2-line-height, 1.25rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-body2-font-weight, 400);
+ letter-spacing: 0.0178571429em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-body2-letter-spacing, 0.0178571429em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-body2-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-body2-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-body2-text-transform, inherit);
+ box-sizing: border-box;
+ overflow: hidden;
+ text-align: left;
+ text-overflow: ellipsis;
+}
+[dir=rtl] .mdc-data-table__cell, .mdc-data-table__cell[dir=rtl] {
+ /* @noflip */
+ text-align: right;
+}
+
+.mdc-data-table__cell--numeric {
+ text-align: right;
+}
+[dir=rtl] .mdc-data-table__cell--numeric, .mdc-data-table__cell--numeric[dir=rtl] {
+ /* @noflip */
+ text-align: left;
+}
+
+.mdc-data-table__cell--checkbox {
+ width: 1px;
+}
+
+.mdc-data-table__header-cell {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-subtitle2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.875rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-subtitle2-font-size, 0.875rem);
+ line-height: 1.375rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-subtitle2-line-height, 1.375rem);
+ font-weight: 500;
+ /* @alternate */
+ font-weight: var(--mdc-typography-subtitle2-font-weight, 500);
+ letter-spacing: 0.0071428571em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-subtitle2-letter-spacing, 0.0071428571em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-subtitle2-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-subtitle2-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-subtitle2-text-transform, inherit);
+ box-sizing: border-box;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ outline: none;
+ /* @noflip */
+ text-align: left;
+}
+[dir=rtl] .mdc-data-table__header-cell, .mdc-data-table__header-cell[dir=rtl] {
+ /* @noflip */
+ text-align: right;
+}
+
+.mdc-data-table__header-cell--checkbox {
+ width: 1px;
+}
+
+.mdc-data-table__header-cell--numeric {
+ text-align: right;
+}
+[dir=rtl] .mdc-data-table__header-cell--numeric, .mdc-data-table__header-cell--numeric[dir=rtl] {
+ /* @noflip */
+ text-align: left;
+}
+
+.mdc-data-table__sort-icon-button {
+ width: 28px;
+ height: 28px;
+ padding: 2px;
+ -webkit-transform: rotate(0.0001deg);
+ transform: rotate(0.0001deg);
+ /* @noflip */
+ margin-left: 4px;
+ /* @noflip */
+ margin-right: 0;
+ transition: -webkit-transform 150ms 0ms cubic-bezier(0.4, 0, 0.2, 1);
+ transition: transform 150ms 0ms cubic-bezier(0.4, 0, 0.2, 1);
+ transition: transform 150ms 0ms cubic-bezier(0.4, 0, 0.2, 1), -webkit-transform 150ms 0ms cubic-bezier(0.4, 0, 0.2, 1);
+ opacity: 0;
+}
+.mdc-data-table__sort-icon-button.mdc-icon-button--touch {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+.mdc-data-table__sort-icon-button.mdc-icon-button--touch .mdc-icon-button__touch {
+ display: none;
+}
+[dir=rtl] .mdc-data-table__sort-icon-button, .mdc-data-table__sort-icon-button[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 4px;
+}
+
+.mdc-data-table__header-cell--numeric .mdc-data-table__sort-icon-button {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 4px;
+}
+[dir=rtl] .mdc-data-table__header-cell--numeric .mdc-data-table__sort-icon-button, .mdc-data-table__header-cell--numeric .mdc-data-table__sort-icon-button[dir=rtl] {
+ /* @noflip */
+ margin-left: 4px;
+ /* @noflip */
+ margin-right: 0;
+}
+
+.mdc-data-table__header-cell--sorted-descending .mdc-data-table__sort-icon-button {
+ -webkit-transform: rotate(-180deg);
+ transform: rotate(-180deg);
+}
+.mdc-data-table__sort-icon-button:focus, .mdc-data-table__header-cell:hover .mdc-data-table__sort-icon-button, .mdc-data-table__header-cell--sorted .mdc-data-table__sort-icon-button {
+ opacity: 1;
+}
+
+.mdc-data-table__header-cell-wrapper {
+ align-items: center;
+ display: inline-flex;
+ vertical-align: middle;
+}
+
+.mdc-data-table__header-cell--with-sort {
+ cursor: pointer;
+}
+
+.mdc-data-table__sort-status-label {
+ clip: rect(1px, 1px, 1px, 1px);
+ height: 1px;
+ overflow: hidden;
+ position: absolute;
+ white-space: nowrap;
+ /* added line */
+ width: 1px;
+}
+
+.mdc-data-table__progress-indicator {
+ display: none;
+ position: absolute;
+ width: 100%;
+}
+.mdc-data-table--in-progress .mdc-data-table__progress-indicator {
+ display: block;
+}
+
+.mdc-data-table__scrim {
+ background-color: #fff;
+ /* @alternate */
+ background-color: var(--mdc-theme-surface, #fff);
+ height: 100%;
+ opacity: 0.32;
+ position: absolute;
+ top: 0;
+ width: 100%;
+}
+
+.mdc-data-table--sticky-header .mdc-data-table__header-cell {
+ position: -webkit-sticky;
+ position: sticky;
+ top: 0;
+ z-index: 1;
+}
+
+.mdc-data-table__pagination {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.875rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-body2-font-size, 0.875rem);
+ line-height: 1.25rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-body2-line-height, 1.25rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-body2-font-weight, 400);
+ letter-spacing: 0.0178571429em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-body2-letter-spacing, 0.0178571429em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-body2-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-body2-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-body2-text-transform, inherit);
+ box-sizing: border-box;
+ display: flex;
+ justify-content: flex-end;
+}
+
+.mdc-data-table__pagination-trailing {
+ /* @noflip */
+ margin-left: 4px;
+ /* @noflip */
+ margin-right: 0;
+ align-items: center;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: flex-end;
+}
+[dir=rtl] .mdc-data-table__pagination-trailing, .mdc-data-table__pagination-trailing[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 4px;
+}
+
+.mdc-data-table__pagination-navigation {
+ align-items: center;
+ display: flex;
+}
+
+.mdc-data-table__pagination-button {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 4px;
+}
+[dir=rtl] .mdc-data-table__pagination-button .mdc-button__icon, .mdc-data-table__pagination-button .mdc-button__icon[dir=rtl] {
+ /* @noflip */
+ -webkit-transform: rotate(180deg);
+ transform: rotate(180deg);
+}
+
+[dir=rtl] .mdc-data-table__pagination-button, .mdc-data-table__pagination-button[dir=rtl] {
+ /* @noflip */
+ margin-left: 4px;
+ /* @noflip */
+ margin-right: 0;
+}
+
+.mdc-data-table__pagination-total {
+ /* @noflip */
+ margin-left: 14px;
+ /* @noflip */
+ margin-right: 36px;
+ white-space: nowrap;
+}
+[dir=rtl] .mdc-data-table__pagination-total, .mdc-data-table__pagination-total[dir=rtl] {
+ /* @noflip */
+ margin-left: 36px;
+ /* @noflip */
+ margin-right: 14px;
+}
+
+.mdc-data-table__pagination-rows-per-page {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 22px;
+ align-items: center;
+ display: inline-flex;
+}
+[dir=rtl] .mdc-data-table__pagination-rows-per-page, .mdc-data-table__pagination-rows-per-page[dir=rtl] {
+ /* @noflip */
+ margin-left: 22px;
+ /* @noflip */
+ margin-right: 0;
+}
+
+.mdc-data-table__pagination-rows-per-page-label {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 12px;
+ white-space: nowrap;
+}
+[dir=rtl] .mdc-data-table__pagination-rows-per-page-label, .mdc-data-table__pagination-rows-per-page-label[dir=rtl] {
+ /* @noflip */
+ margin-left: 12px;
+ /* @noflip */
+ margin-right: 0;
+}
+
+.mdc-data-table__pagination-rows-per-page-select {
+ min-width: 80px;
+ /* @alternate */
+ min-width: var(--mdc-menu-min-width, 80px);
+ margin: 8px 0;
+}
+.mdc-data-table__pagination-rows-per-page-select .mdc-select__anchor {
+ width: 100%;
+ min-width: 80px;
+}
+.mdc-data-table__pagination-rows-per-page-select .mdc-select__anchor {
+ height: 36px;
+}
+.mdc-data-table__pagination-rows-per-page-select .mdc-select__anchor .mdc-floating-label--float-above {
+ -webkit-transform: translateY(-27.25px) scale(1);
+ transform: translateY(-27.25px) scale(1);
+}
+.mdc-data-table__pagination-rows-per-page-select .mdc-select__anchor .mdc-floating-label--float-above {
+ font-size: 0.75rem;
+}
+.mdc-data-table__pagination-rows-per-page-select .mdc-select__anchor.mdc-notched-outline--upgraded .mdc-floating-label--float-above,
+.mdc-data-table__pagination-rows-per-page-select .mdc-select__anchor .mdc-notched-outline--upgraded .mdc-floating-label--float-above {
+ -webkit-transform: translateY(-24.75px) scale(0.75);
+ transform: translateY(-24.75px) scale(0.75);
+}
+.mdc-data-table__pagination-rows-per-page-select .mdc-select__anchor.mdc-notched-outline--upgraded .mdc-floating-label--float-above,
+.mdc-data-table__pagination-rows-per-page-select .mdc-select__anchor .mdc-notched-outline--upgraded .mdc-floating-label--float-above {
+ font-size: 1rem;
+}
+.mdc-data-table__pagination-rows-per-page-select .mdc-select__anchor .mdc-floating-label--shake {
+ -webkit-animation: mdc-floating-label-shake-float-above-select-outlined-36px 250ms 1;
+ animation: mdc-floating-label-shake-float-above-select-outlined-36px 250ms 1;
+}
+@-webkit-keyframes mdc-floating-label-shake-float-above-select-outlined-36px {
+ 0% {
+ -webkit-transform: translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75);
+ transform: translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75);
+ }
+ 33% {
+ -webkit-animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ -webkit-transform: translateX(calc(4% - 0%)) translateY(-24.75px) scale(0.75);
+ transform: translateX(calc(4% - 0%)) translateY(-24.75px) scale(0.75);
+ }
+ 66% {
+ -webkit-animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ -webkit-transform: translateX(calc(-4% - 0%)) translateY(-24.75px) scale(0.75);
+ transform: translateX(calc(-4% - 0%)) translateY(-24.75px) scale(0.75);
+ }
+ 100% {
+ -webkit-transform: translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75);
+ transform: translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75);
+ }
+}
+@keyframes mdc-floating-label-shake-float-above-select-outlined-36px {
+ 0% {
+ -webkit-transform: translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75);
+ transform: translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75);
+ }
+ 33% {
+ -webkit-animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ -webkit-transform: translateX(calc(4% - 0%)) translateY(-24.75px) scale(0.75);
+ transform: translateX(calc(4% - 0%)) translateY(-24.75px) scale(0.75);
+ }
+ 66% {
+ -webkit-animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ -webkit-transform: translateX(calc(-4% - 0%)) translateY(-24.75px) scale(0.75);
+ transform: translateX(calc(-4% - 0%)) translateY(-24.75px) scale(0.75);
+ }
+ 100% {
+ -webkit-transform: translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75);
+ transform: translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75);
+ }
+}
+.mdc-data-table__pagination-rows-per-page-select .mdc-select__dropdown-icon {
+ width: 20px;
+ height: 20px;
+}
+.mdc-data-table__pagination-rows-per-page-select.mdc-select--outlined .mdc-select__anchor :not(.mdc-notched-outline--notched) .mdc-notched-outline__notch {
+ max-width: calc(100% - 56px);
+}
+.mdc-data-table__pagination-rows-per-page-select .mdc-deprecated-list-item {
+ height: 36px;
+}
+
+.mdc-data-table__header-row-checkbox.mdc-checkbox--selected .mdc-checkbox__ripple::before, .mdc-data-table__header-row-checkbox.mdc-checkbox--selected .mdc-checkbox__ripple::after,
+.mdc-data-table__row-checkbox.mdc-checkbox--selected .mdc-checkbox__ripple::before,
+.mdc-data-table__row-checkbox.mdc-checkbox--selected .mdc-checkbox__ripple::after {
+ background-color: #6200ee;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee));
+}
+.mdc-data-table__header-row-checkbox.mdc-checkbox--selected:hover .mdc-checkbox__ripple::before, .mdc-data-table__header-row-checkbox.mdc-checkbox--selected.mdc-ripple-surface--hover .mdc-checkbox__ripple::before,
+.mdc-data-table__row-checkbox.mdc-checkbox--selected:hover .mdc-checkbox__ripple::before,
+.mdc-data-table__row-checkbox.mdc-checkbox--selected.mdc-ripple-surface--hover .mdc-checkbox__ripple::before {
+ opacity: 0.04;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.04);
+}
+.mdc-data-table__header-row-checkbox.mdc-checkbox--selected.mdc-ripple-upgraded--background-focused .mdc-checkbox__ripple::before, .mdc-data-table__header-row-checkbox.mdc-checkbox--selected:not(.mdc-ripple-upgraded):focus .mdc-checkbox__ripple::before,
+.mdc-data-table__row-checkbox.mdc-checkbox--selected.mdc-ripple-upgraded--background-focused .mdc-checkbox__ripple::before,
+.mdc-data-table__row-checkbox.mdc-checkbox--selected:not(.mdc-ripple-upgraded):focus .mdc-checkbox__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.12);
+}
+.mdc-data-table__header-row-checkbox.mdc-checkbox--selected:not(.mdc-ripple-upgraded) .mdc-checkbox__ripple::after,
+.mdc-data-table__row-checkbox.mdc-checkbox--selected:not(.mdc-ripple-upgraded) .mdc-checkbox__ripple::after {
+ transition: opacity 150ms linear;
+}
+.mdc-data-table__header-row-checkbox.mdc-checkbox--selected:not(.mdc-ripple-upgraded):active .mdc-checkbox__ripple::after,
+.mdc-data-table__row-checkbox.mdc-checkbox--selected:not(.mdc-ripple-upgraded):active .mdc-checkbox__ripple::after {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-data-table__header-row-checkbox.mdc-checkbox--selected.mdc-ripple-upgraded,
+.mdc-data-table__row-checkbox.mdc-checkbox--selected.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-data-table__header-row-checkbox.mdc-ripple-upgraded--background-focused.mdc-checkbox--selected .mdc-checkbox__ripple::before,
+.mdc-data-table__header-row-checkbox.mdc-ripple-upgraded--background-focused.mdc-checkbox--selected .mdc-checkbox__ripple::after,
+.mdc-data-table__row-checkbox.mdc-ripple-upgraded--background-focused.mdc-checkbox--selected .mdc-checkbox__ripple::before,
+.mdc-data-table__row-checkbox.mdc-ripple-upgraded--background-focused.mdc-checkbox--selected .mdc-checkbox__ripple::after {
+ background-color: #6200ee;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee));
+}
+.mdc-data-table__header-row-checkbox .mdc-checkbox__native-control:enabled:not(:checked):not(:indeterminate):not([data-indeterminate=true]) ~ .mdc-checkbox__background,
+.mdc-data-table__row-checkbox .mdc-checkbox__native-control:enabled:not(:checked):not(:indeterminate):not([data-indeterminate=true]) ~ .mdc-checkbox__background {
+ border-color: rgba(0, 0, 0, 0.54);
+ /* @alternate */
+ border-color: var(--mdc-checkbox-unchecked-color, rgba(0, 0, 0, 0.54));
+ background-color: transparent;
+}
+.mdc-data-table__header-row-checkbox .mdc-checkbox__native-control:enabled:checked ~ .mdc-checkbox__background,
+.mdc-data-table__header-row-checkbox .mdc-checkbox__native-control:enabled:indeterminate ~ .mdc-checkbox__background,
+.mdc-data-table__header-row-checkbox .mdc-checkbox__native-control[data-indeterminate=true]:enabled ~ .mdc-checkbox__background,
+.mdc-data-table__row-checkbox .mdc-checkbox__native-control:enabled:checked ~ .mdc-checkbox__background,
+.mdc-data-table__row-checkbox .mdc-checkbox__native-control:enabled:indeterminate ~ .mdc-checkbox__background,
+.mdc-data-table__row-checkbox .mdc-checkbox__native-control[data-indeterminate=true]:enabled ~ .mdc-checkbox__background {
+ border-color: #6200ee;
+ /* @alternate */
+ border-color: var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #6200ee));
+ background-color: #6200ee;
+ /* @alternate */
+ background-color: var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #6200ee));
+}
+@-webkit-keyframes mdc-checkbox-fade-in-background-8A000000FF6200EE00000000FF6200EE {
+ 0% {
+ border-color: rgba(0, 0, 0, 0.54);
+ /* @alternate */
+ border-color: var(--mdc-checkbox-unchecked-color, rgba(0, 0, 0, 0.54));
+ background-color: transparent;
+ }
+ 50% {
+ border-color: #6200ee;
+ /* @alternate */
+ border-color: var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #6200ee));
+ background-color: #6200ee;
+ /* @alternate */
+ background-color: var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #6200ee));
+ }
+}
+@keyframes mdc-checkbox-fade-in-background-8A000000FF6200EE00000000FF6200EE {
+ 0% {
+ border-color: rgba(0, 0, 0, 0.54);
+ /* @alternate */
+ border-color: var(--mdc-checkbox-unchecked-color, rgba(0, 0, 0, 0.54));
+ background-color: transparent;
+ }
+ 50% {
+ border-color: #6200ee;
+ /* @alternate */
+ border-color: var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #6200ee));
+ background-color: #6200ee;
+ /* @alternate */
+ background-color: var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #6200ee));
+ }
+}
+@-webkit-keyframes mdc-checkbox-fade-out-background-8A000000FF6200EE00000000FF6200EE {
+ 0%, 80% {
+ border-color: #6200ee;
+ /* @alternate */
+ border-color: var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #6200ee));
+ background-color: #6200ee;
+ /* @alternate */
+ background-color: var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #6200ee));
+ }
+ 100% {
+ border-color: rgba(0, 0, 0, 0.54);
+ /* @alternate */
+ border-color: var(--mdc-checkbox-unchecked-color, rgba(0, 0, 0, 0.54));
+ background-color: transparent;
+ }
+}
+@keyframes mdc-checkbox-fade-out-background-8A000000FF6200EE00000000FF6200EE {
+ 0%, 80% {
+ border-color: #6200ee;
+ /* @alternate */
+ border-color: var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #6200ee));
+ background-color: #6200ee;
+ /* @alternate */
+ background-color: var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #6200ee));
+ }
+ 100% {
+ border-color: rgba(0, 0, 0, 0.54);
+ /* @alternate */
+ border-color: var(--mdc-checkbox-unchecked-color, rgba(0, 0, 0, 0.54));
+ background-color: transparent;
+ }
+}
+.mdc-data-table__header-row-checkbox.mdc-checkbox--anim-unchecked-checked .mdc-checkbox__native-control:enabled ~ .mdc-checkbox__background, .mdc-data-table__header-row-checkbox.mdc-checkbox--anim-unchecked-indeterminate .mdc-checkbox__native-control:enabled ~ .mdc-checkbox__background,
+.mdc-data-table__row-checkbox.mdc-checkbox--anim-unchecked-checked .mdc-checkbox__native-control:enabled ~ .mdc-checkbox__background,
+.mdc-data-table__row-checkbox.mdc-checkbox--anim-unchecked-indeterminate .mdc-checkbox__native-control:enabled ~ .mdc-checkbox__background {
+ -webkit-animation-name: mdc-checkbox-fade-in-background-8A000000FF6200EE00000000FF6200EE;
+ animation-name: mdc-checkbox-fade-in-background-8A000000FF6200EE00000000FF6200EE;
+}
+.mdc-data-table__header-row-checkbox.mdc-checkbox--anim-checked-unchecked .mdc-checkbox__native-control:enabled ~ .mdc-checkbox__background, .mdc-data-table__header-row-checkbox.mdc-checkbox--anim-indeterminate-unchecked .mdc-checkbox__native-control:enabled ~ .mdc-checkbox__background,
+.mdc-data-table__row-checkbox.mdc-checkbox--anim-checked-unchecked .mdc-checkbox__native-control:enabled ~ .mdc-checkbox__background,
+.mdc-data-table__row-checkbox.mdc-checkbox--anim-indeterminate-unchecked .mdc-checkbox__native-control:enabled ~ .mdc-checkbox__background {
+ -webkit-animation-name: mdc-checkbox-fade-out-background-8A000000FF6200EE00000000FF6200EE;
+ animation-name: mdc-checkbox-fade-out-background-8A000000FF6200EE00000000FF6200EE;
+}
+
+.mdc-dialog,
+.mdc-dialog__scrim {
+ position: fixed;
+ top: 0;
+ left: 0;
+ align-items: center;
+ justify-content: center;
+ box-sizing: border-box;
+ width: 100%;
+ height: 100%;
+}
+
+.mdc-dialog {
+ display: none;
+ z-index: 7;
+ /* @alternate */
+ z-index: var(--mdc-dialog-z-index, 7);
+}
+.mdc-dialog .mdc-dialog__surface {
+ background-color: #fff;
+ /* @alternate */
+ background-color: var(--mdc-theme-surface, #fff);
+}
+.mdc-dialog .mdc-dialog__scrim {
+ background-color: rgba(0, 0, 0, 0.32);
+}
+.mdc-dialog .mdc-dialog__surface-scrim {
+ background-color: rgba(0, 0, 0, 0.32);
+}
+.mdc-dialog .mdc-dialog__title {
+ color: rgba(0, 0, 0, 0.87);
+}
+.mdc-dialog .mdc-dialog__content {
+ color: rgba(0, 0, 0, 0.6);
+}
+.mdc-dialog .mdc-dialog__close {
+ color: #000;
+ /* @alternate */
+ color: var(--mdc-theme-on-surface, #000);
+}
+.mdc-dialog .mdc-dialog__close .mdc-icon-button__ripple::before, .mdc-dialog .mdc-dialog__close .mdc-icon-button__ripple::after {
+ background-color: #000;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, var(--mdc-theme-on-surface, #000));
+}
+.mdc-dialog .mdc-dialog__close:hover .mdc-icon-button__ripple::before, .mdc-dialog .mdc-dialog__close.mdc-ripple-surface--hover .mdc-icon-button__ripple::before {
+ opacity: 0.04;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.04);
+}
+.mdc-dialog .mdc-dialog__close.mdc-ripple-upgraded--background-focused .mdc-icon-button__ripple::before, .mdc-dialog .mdc-dialog__close:not(.mdc-ripple-upgraded):focus .mdc-icon-button__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.12);
+}
+.mdc-dialog .mdc-dialog__close:not(.mdc-ripple-upgraded) .mdc-icon-button__ripple::after {
+ transition: opacity 150ms linear;
+}
+.mdc-dialog .mdc-dialog__close:not(.mdc-ripple-upgraded):active .mdc-icon-button__ripple::after {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-dialog .mdc-dialog__close.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-dialog.mdc-dialog--scrollable .mdc-dialog__title, .mdc-dialog.mdc-dialog--scrollable .mdc-dialog__actions, .mdc-dialog.mdc-dialog--scrollable.mdc-dialog-scroll-divider-footer .mdc-dialog__actions {
+ border-color: rgba(0, 0, 0, 0.12);
+}
+.mdc-dialog.mdc-dialog--scrollable .mdc-dialog__title {
+ border-bottom: 1px solid rgba(0, 0, 0, 0.12);
+ margin-bottom: 0;
+}
+.mdc-dialog.mdc-dialog-scroll-divider-header.mdc-dialog--fullscreen .mdc-dialog__header {
+ /* @alternate */
+ box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12);
+}
+.mdc-dialog .mdc-dialog__content {
+ padding: 20px 24px 20px 24px;
+}
+.mdc-dialog .mdc-dialog__surface {
+ min-width: 280px;
+}
+@media (max-width: 592px) {
+ .mdc-dialog .mdc-dialog__surface {
+ max-width: calc(100vw - 32px);
+ }
+}
+@media (min-width: 592px) {
+ .mdc-dialog .mdc-dialog__surface {
+ max-width: 560px;
+ }
+}
+.mdc-dialog .mdc-dialog__surface {
+ max-height: calc(100% - 32px);
+}
+@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
+ .mdc-dialog .mdc-dialog__container {
+ /* stylelint-disable */
+ /* stylelint-enable*/
+ }
+}
+.mdc-dialog .mdc-dialog__surface {
+ border-radius: 4px;
+ /* @alternate */
+ border-radius: var(--mdc-shape-medium, 4px);
+}
+@media (max-width: 960px) and (max-height: 1440px) {
+ .mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface {
+ max-height: 560px;
+ max-width: 560px;
+ }
+ .mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface .mdc-dialog__close {
+ right: -12px;
+ }
+}
+@media (max-width: 720px) and (max-height: 1023px) and (max-width: 672px) {
+ .mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface {
+ max-width: calc(100vw - 112px);
+ }
+}
+@media (max-width: 720px) and (max-height: 1023px) and (min-width: 672px) {
+ .mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface {
+ max-width: 560px;
+ }
+}
+@media (max-width: 720px) and (max-height: 1023px) and (max-height: 720px) {
+ .mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface {
+ max-height: calc(100vh - 160px);
+ }
+}
+@media (max-width: 720px) and (max-height: 1023px) and (min-height: 720px) {
+ .mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface {
+ max-height: 560px;
+ }
+}
+@media (max-width: 720px) and (max-height: 1023px) {
+ .mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface .mdc-dialog__close {
+ right: -12px;
+ }
+}
+@media (max-width: 720px) and (max-height: 400px) {
+ .mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface {
+ height: 100%;
+ max-height: 100vh;
+ max-width: 100vw;
+ width: 100%;
+ border-radius: 0;
+ }
+ .mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface .mdc-dialog__close {
+ order: -1;
+ left: -12px;
+ }
+ .mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface .mdc-dialog__header {
+ padding: 0 16px 9px;
+ justify-content: flex-start;
+ }
+ .mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface .mdc-dialog__title {
+ margin-left: calc(16px - 2 * 12px);
+ }
+}
+@media (max-width: 600px) and (max-height: 960px) {
+ .mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface {
+ height: 100%;
+ max-height: 100vh;
+ max-width: 100vw;
+ width: 100vw;
+ border-radius: 0;
+ }
+ .mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface .mdc-dialog__close {
+ order: -1;
+ left: -12px;
+ }
+ .mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface .mdc-dialog__header {
+ padding: 0 16px 9px;
+ justify-content: flex-start;
+ }
+ .mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface .mdc-dialog__title {
+ margin-left: calc(16px - 2 * 12px);
+ }
+}
+@media (min-width: 960px) and (min-height: 1440px) {
+ .mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface {
+ max-width: calc(100vw - 400px);
+ }
+ .mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface .mdc-dialog__close {
+ right: -12px;
+ }
+}
+.mdc-dialog.mdc-dialog__scrim--hidden .mdc-dialog__scrim {
+ opacity: 0;
+}
+
+.mdc-dialog__scrim {
+ opacity: 0;
+ z-index: -1;
+}
+
+.mdc-dialog__container {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-around;
+ box-sizing: border-box;
+ height: 100%;
+ -webkit-transform: scale(0.8);
+ transform: scale(0.8);
+ opacity: 0;
+ pointer-events: none;
+}
+
+.mdc-dialog__surface {
+ /* @alternate */
+ position: relative;
+ /* @alternate */
+ box-shadow: 0px 11px 15px -7px rgba(0, 0, 0, 0.2), 0px 24px 38px 3px rgba(0, 0, 0, 0.14), 0px 9px 46px 8px rgba(0, 0, 0, 0.12);
+ display: flex;
+ flex-direction: column;
+ flex-grow: 0;
+ flex-shrink: 0;
+ box-sizing: border-box;
+ max-width: 100%;
+ max-height: 100%;
+ pointer-events: auto;
+ overflow-y: auto;
+}
+.mdc-dialog__surface .mdc-elevation-overlay {
+ width: 100%;
+ height: 100%;
+ top: 0;
+ /* @noflip */
+ left: 0;
+}
+[dir=rtl] .mdc-dialog__surface, .mdc-dialog__surface[dir=rtl] {
+ /* @noflip */
+ text-align: right;
+}
+
+@media screen and (forced-colors: active), (-ms-high-contrast: active) {
+ .mdc-dialog__surface {
+ outline: 2px solid windowText;
+ }
+}
+.mdc-dialog__surface::before {
+ position: absolute;
+ box-sizing: border-box;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ border: 2px solid transparent;
+ border-radius: inherit;
+ content: "";
+ pointer-events: none;
+}
+@media screen and (-ms-high-contrast: active), screen and (-ms-high-contrast: none) {
+ .mdc-dialog__surface::before {
+ content: none;
+ }
+}
+
+.mdc-dialog__title {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-headline6-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 1.25rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-headline6-font-size, 1.25rem);
+ line-height: 2rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-headline6-line-height, 2rem);
+ font-weight: 500;
+ /* @alternate */
+ font-weight: var(--mdc-typography-headline6-font-weight, 500);
+ letter-spacing: 0.0125em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-headline6-letter-spacing, 0.0125em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-headline6-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-headline6-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-headline6-text-transform, inherit);
+ position: relative;
+ flex-shrink: 0;
+ box-sizing: border-box;
+ margin: 0 0 1px;
+ padding: 0 24px 9px;
+}
+.mdc-dialog__title::before {
+ display: inline-block;
+ width: 0;
+ height: 40px;
+ content: "";
+ vertical-align: 0;
+}
+[dir=rtl] .mdc-dialog__title, .mdc-dialog__title[dir=rtl] {
+ /* @noflip */
+ text-align: right;
+}
+
+.mdc-dialog--scrollable .mdc-dialog__title {
+ margin-bottom: 1px;
+ padding-bottom: 15px;
+}
+
+.mdc-dialog--fullscreen .mdc-dialog__header {
+ align-items: baseline;
+ border-bottom: 1px solid transparent;
+ display: inline-flex;
+ justify-content: space-between;
+ padding: 0 24px 9px;
+ z-index: 1;
+}
+.mdc-dialog--fullscreen .mdc-dialog__header .mdc-dialog__close {
+ right: -12px;
+}
+.mdc-dialog--fullscreen .mdc-dialog__title {
+ margin-bottom: 0;
+ padding: 0;
+ border-bottom: 0;
+}
+.mdc-dialog--fullscreen.mdc-dialog--scrollable .mdc-dialog__title {
+ border-bottom: 0;
+ margin-bottom: 0;
+}
+.mdc-dialog--fullscreen .mdc-dialog__close {
+ top: 5px;
+}
+.mdc-dialog--fullscreen.mdc-dialog--scrollable .mdc-dialog__actions {
+ border-top: 1px solid transparent;
+}
+
+.mdc-dialog__content {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-body1-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 1rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-body1-font-size, 1rem);
+ line-height: 1.5rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-body1-line-height, 1.5rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-body1-font-weight, 400);
+ letter-spacing: 0.03125em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-body1-letter-spacing, 0.03125em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-body1-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-body1-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-body1-text-transform, inherit);
+ flex-grow: 1;
+ box-sizing: border-box;
+ margin: 0;
+ overflow: auto;
+ -webkit-overflow-scrolling: touch;
+}
+.mdc-dialog__content > :first-child {
+ margin-top: 0;
+}
+.mdc-dialog__content > :last-child {
+ margin-bottom: 0;
+}
+
+.mdc-dialog__title + .mdc-dialog__content,
+.mdc-dialog__header + .mdc-dialog__content {
+ padding-top: 0;
+}
+
+.mdc-dialog--scrollable .mdc-dialog__title + .mdc-dialog__content {
+ padding-top: 8px;
+ padding-bottom: 8px;
+}
+
+.mdc-dialog__content .mdc-deprecated-list:first-child:last-child {
+ padding: 6px 0 0;
+}
+
+.mdc-dialog--scrollable .mdc-dialog__content .mdc-deprecated-list:first-child:last-child {
+ padding: 0;
+}
+
+.mdc-dialog__actions {
+ display: flex;
+ position: relative;
+ flex-shrink: 0;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: flex-end;
+ box-sizing: border-box;
+ min-height: 52px;
+ margin: 0;
+ padding: 8px;
+ border-top: 1px solid transparent;
+}
+.mdc-dialog--stacked .mdc-dialog__actions {
+ flex-direction: column;
+ align-items: flex-end;
+}
+
+.mdc-dialog__button {
+ /* @noflip */
+ margin-left: 8px;
+ /* @noflip */
+ margin-right: 0;
+ max-width: 100%;
+ /* @noflip */
+ text-align: right;
+}
+[dir=rtl] .mdc-dialog__button, .mdc-dialog__button[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 8px;
+}
+
+.mdc-dialog__button:first-child {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 0;
+}
+[dir=rtl] .mdc-dialog__button:first-child, .mdc-dialog__button:first-child[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 0;
+}
+
+[dir=rtl] .mdc-dialog__button, .mdc-dialog__button[dir=rtl] {
+ /* @noflip */
+ text-align: left;
+}
+
+.mdc-dialog--stacked .mdc-dialog__button:not(:first-child) {
+ margin-top: 12px;
+}
+
+.mdc-dialog--open,
+.mdc-dialog--opening,
+.mdc-dialog--closing {
+ display: flex;
+}
+
+.mdc-dialog--opening .mdc-dialog__scrim {
+ transition: opacity 150ms linear;
+}
+.mdc-dialog--opening .mdc-dialog__container {
+ transition: opacity 75ms linear, -webkit-transform 150ms 0ms cubic-bezier(0, 0, 0.2, 1);
+ transition: opacity 75ms linear, transform 150ms 0ms cubic-bezier(0, 0, 0.2, 1);
+ transition: opacity 75ms linear, transform 150ms 0ms cubic-bezier(0, 0, 0.2, 1), -webkit-transform 150ms 0ms cubic-bezier(0, 0, 0.2, 1);
+}
+
+.mdc-dialog--closing .mdc-dialog__scrim,
+.mdc-dialog--closing .mdc-dialog__container {
+ transition: opacity 75ms linear;
+}
+.mdc-dialog--closing .mdc-dialog__container {
+ -webkit-transform: none;
+ transform: none;
+}
+
+.mdc-dialog--open .mdc-dialog__scrim {
+ opacity: 1;
+}
+.mdc-dialog--open .mdc-dialog__container {
+ -webkit-transform: none;
+ transform: none;
+ opacity: 1;
+}
+.mdc-dialog--open.mdc-dialog__surface-scrim--shown .mdc-dialog__surface-scrim {
+ opacity: 1;
+ z-index: 1;
+}
+.mdc-dialog--open.mdc-dialog__surface-scrim--hiding .mdc-dialog__surface-scrim {
+ transition: opacity 75ms linear;
+}
+.mdc-dialog--open.mdc-dialog__surface-scrim--showing .mdc-dialog__surface-scrim {
+ transition: opacity 150ms linear;
+}
+
+.mdc-dialog__surface-scrim {
+ display: none;
+ opacity: 0;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+}
+.mdc-dialog__surface-scrim--shown .mdc-dialog__surface-scrim, .mdc-dialog__surface-scrim--showing .mdc-dialog__surface-scrim, .mdc-dialog__surface-scrim--hiding .mdc-dialog__surface-scrim {
+ display: block;
+}
+
+.mdc-dialog-scroll-lock {
+ overflow: hidden;
+}
+
+.mdc-drawer {
+ border-color: rgba(0, 0, 0, 0.12);
+ background-color: #fff;
+ /* @alternate */
+ background-color: var(--mdc-theme-surface, #fff);
+ /* @noflip */
+ border-top-left-radius: 0;
+ /* @noflip */
+ border-top-right-radius: 0;
+ /* @alternate */
+ /* @noflip */
+ border-top-right-radius: var(--mdc-shape-large, 0);
+ /* @noflip */
+ border-bottom-right-radius: 0;
+ /* @alternate */
+ /* @noflip */
+ border-bottom-right-radius: var(--mdc-shape-large, 0);
+ /* @noflip */
+ border-bottom-left-radius: 0;
+ z-index: 6;
+ width: 256px;
+ display: flex;
+ flex-direction: column;
+ flex-shrink: 0;
+ box-sizing: border-box;
+ height: 100%;
+ /* @noflip */
+ border-right-width: 1px;
+ /* @noflip */
+ border-right-style: solid;
+ overflow: hidden;
+ transition-property: -webkit-transform;
+ transition-property: transform;
+ transition-property: transform, -webkit-transform;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+}
+.mdc-drawer .mdc-drawer__title {
+ color: rgba(0, 0, 0, 0.87);
+}
+.mdc-drawer .mdc-deprecated-list-group__subheader {
+ color: rgba(0, 0, 0, 0.6);
+}
+.mdc-drawer .mdc-drawer__subtitle {
+ color: rgba(0, 0, 0, 0.6);
+}
+.mdc-drawer .mdc-deprecated-list-item__graphic {
+ color: rgba(0, 0, 0, 0.6);
+}
+.mdc-drawer .mdc-deprecated-list-item {
+ color: rgba(0, 0, 0, 0.87);
+}
+.mdc-drawer .mdc-deprecated-list-item--activated .mdc-deprecated-list-item__graphic {
+ color: #6200ee;
+}
+.mdc-drawer .mdc-deprecated-list-item--activated {
+ color: rgba(98, 0, 238, 0.87);
+}
+[dir=rtl] .mdc-drawer, .mdc-drawer[dir=rtl] {
+ /* @noflip */
+ border-top-left-radius: 0;
+ /* @alternate */
+ /* @noflip */
+ border-top-left-radius: var(--mdc-shape-large, 0);
+ /* @noflip */
+ border-top-right-radius: 0;
+ /* @noflip */
+ border-bottom-right-radius: 0;
+ /* @noflip */
+ border-bottom-left-radius: 0;
+ /* @alternate */
+ /* @noflip */
+ border-bottom-left-radius: var(--mdc-shape-large, 0);
+}
+
+.mdc-drawer .mdc-deprecated-list-item {
+ border-radius: 4px;
+ /* @alternate */
+ border-radius: var(--mdc-shape-small, 4px);
+}
+.mdc-drawer.mdc-drawer--open:not(.mdc-drawer--closing) + .mdc-drawer-app-content {
+ /* @noflip */
+ margin-left: 256px;
+ /* @noflip */
+ margin-right: 0;
+}
+[dir=rtl] .mdc-drawer.mdc-drawer--open:not(.mdc-drawer--closing) + .mdc-drawer-app-content, .mdc-drawer.mdc-drawer--open:not(.mdc-drawer--closing) + .mdc-drawer-app-content[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 256px;
+}
+
+[dir=rtl] .mdc-drawer, .mdc-drawer[dir=rtl] {
+ /* @noflip */
+ border-right-width: 0;
+ /* @noflip */
+ border-left-width: 1px;
+ /* @noflip */
+ border-right-style: none;
+ /* @noflip */
+ border-left-style: solid;
+}
+
+.mdc-drawer .mdc-deprecated-list-item {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-subtitle2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.875rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-subtitle2-font-size, 0.875rem);
+ line-height: 1.375rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-subtitle2-line-height, 1.375rem);
+ font-weight: 500;
+ /* @alternate */
+ font-weight: var(--mdc-typography-subtitle2-font-weight, 500);
+ letter-spacing: 0.0071428571em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-subtitle2-letter-spacing, 0.0071428571em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-subtitle2-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-subtitle2-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-subtitle2-text-transform, inherit);
+ height: calc(48px - 2 * 4px);
+ margin: 8px 8px;
+ padding: 0 8px;
+}
+.mdc-drawer .mdc-deprecated-list-item:nth-child(1) {
+ margin-top: 2px;
+}
+.mdc-drawer .mdc-deprecated-list-item:nth-last-child(1) {
+ margin-bottom: 0;
+}
+.mdc-drawer .mdc-deprecated-list-group__subheader {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.875rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-body2-font-size, 0.875rem);
+ line-height: 1.25rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-body2-line-height, 1.25rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-body2-font-weight, 400);
+ letter-spacing: 0.0178571429em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-body2-letter-spacing, 0.0178571429em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-body2-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-body2-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-body2-text-transform, inherit);
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+ margin: 0;
+ padding: 0 16px;
+}
+.mdc-drawer .mdc-deprecated-list-group__subheader::before {
+ display: inline-block;
+ width: 0;
+ height: 24px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-drawer .mdc-deprecated-list-divider {
+ margin: 3px 0 4px;
+}
+.mdc-drawer .mdc-deprecated-list-item__text,
+.mdc-drawer .mdc-deprecated-list-item__graphic {
+ pointer-events: none;
+}
+
+.mdc-drawer--animate {
+ -webkit-transform: translateX(-100%);
+ transform: translateX(-100%);
+}
+[dir=rtl] .mdc-drawer--animate, .mdc-drawer--animate[dir=rtl] {
+ -webkit-transform: translateX(100%);
+ transform: translateX(100%);
+}
+
+.mdc-drawer--opening {
+ -webkit-transform: translateX(0);
+ transform: translateX(0);
+ transition-duration: 250ms;
+}
+[dir=rtl] .mdc-drawer--opening, .mdc-drawer--opening[dir=rtl] {
+ -webkit-transform: translateX(0);
+ transform: translateX(0);
+}
+
+.mdc-drawer--closing {
+ -webkit-transform: translateX(-100%);
+ transform: translateX(-100%);
+ transition-duration: 200ms;
+}
+[dir=rtl] .mdc-drawer--closing, .mdc-drawer--closing[dir=rtl] {
+ -webkit-transform: translateX(100%);
+ transform: translateX(100%);
+}
+
+.mdc-drawer__header {
+ flex-shrink: 0;
+ box-sizing: border-box;
+ min-height: 64px;
+ padding: 0 16px 4px;
+}
+
+.mdc-drawer__title {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-headline6-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 1.25rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-headline6-font-size, 1.25rem);
+ line-height: 2rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-headline6-line-height, 2rem);
+ font-weight: 500;
+ /* @alternate */
+ font-weight: var(--mdc-typography-headline6-font-weight, 500);
+ letter-spacing: 0.0125em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-headline6-letter-spacing, 0.0125em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-headline6-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-headline6-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-headline6-text-transform, inherit);
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+ margin-bottom: -20px;
+}
+.mdc-drawer__title::before {
+ display: inline-block;
+ width: 0;
+ height: 36px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-drawer__title::after {
+ display: inline-block;
+ width: 0;
+ height: 20px;
+ content: "";
+ vertical-align: -20px;
+}
+
+.mdc-drawer__subtitle {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.875rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-body2-font-size, 0.875rem);
+ line-height: 1.25rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-body2-line-height, 1.25rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-body2-font-weight, 400);
+ letter-spacing: 0.0178571429em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-body2-letter-spacing, 0.0178571429em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-body2-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-body2-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-body2-text-transform, inherit);
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+ margin-bottom: 0;
+}
+.mdc-drawer__subtitle::before {
+ display: inline-block;
+ width: 0;
+ height: 20px;
+ content: "";
+ vertical-align: 0;
+}
+
+.mdc-drawer__content {
+ height: 100%;
+ overflow-y: auto;
+ -webkit-overflow-scrolling: touch;
+}
+
+.mdc-drawer--dismissible {
+ /* @noflip */
+ left: 0;
+ /* @noflip */
+ right: initial;
+ display: none;
+ position: absolute;
+}
+[dir=rtl] .mdc-drawer--dismissible, .mdc-drawer--dismissible[dir=rtl] {
+ /* @noflip */
+ left: initial;
+ /* @noflip */
+ right: 0;
+}
+
+.mdc-drawer--dismissible.mdc-drawer--open {
+ display: flex;
+}
+
+.mdc-drawer-app-content {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 0;
+ position: relative;
+}
+[dir=rtl] .mdc-drawer-app-content, .mdc-drawer-app-content[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 0;
+}
+
+.mdc-drawer--modal {
+ /* @alternate */
+ box-shadow: 0px 8px 10px -5px rgba(0, 0, 0, 0.2), 0px 16px 24px 2px rgba(0, 0, 0, 0.14), 0px 6px 30px 5px rgba(0, 0, 0, 0.12);
+ /* @noflip */
+ left: 0;
+ /* @noflip */
+ right: initial;
+ display: none;
+ position: fixed;
+}
+.mdc-drawer--modal + .mdc-drawer-scrim {
+ background-color: rgba(0, 0, 0, 0.32);
+}
+[dir=rtl] .mdc-drawer--modal, .mdc-drawer--modal[dir=rtl] {
+ /* @noflip */
+ left: initial;
+ /* @noflip */
+ right: 0;
+}
+
+.mdc-drawer--modal.mdc-drawer--open {
+ display: flex;
+}
+
+.mdc-drawer-scrim {
+ display: none;
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 5;
+ transition-property: opacity;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+}
+.mdc-drawer--open + .mdc-drawer-scrim {
+ display: block;
+}
+.mdc-drawer--animate + .mdc-drawer-scrim {
+ opacity: 0;
+}
+.mdc-drawer--opening + .mdc-drawer-scrim {
+ transition-duration: 250ms;
+ opacity: 1;
+}
+.mdc-drawer--closing + .mdc-drawer-scrim {
+ transition-duration: 200ms;
+ opacity: 0;
+}
+
+.mdc-elevation--z0 {
+ /* @alternate */
+ box-shadow: 0px 0px 0px 0px rgba(0, 0, 0, 0.2), 0px 0px 0px 0px rgba(0, 0, 0, 0.14), 0px 0px 0px 0px rgba(0, 0, 0, 0.12);
+}
+
+.mdc-elevation--z1 {
+ /* @alternate */
+ box-shadow: 0px 2px 1px -1px rgba(0, 0, 0, 0.2), 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 1px 3px 0px rgba(0, 0, 0, 0.12);
+}
+
+.mdc-elevation--z2 {
+ /* @alternate */
+ box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12);
+}
+
+.mdc-elevation--z3 {
+ /* @alternate */
+ box-shadow: 0px 3px 3px -2px rgba(0, 0, 0, 0.2), 0px 3px 4px 0px rgba(0, 0, 0, 0.14), 0px 1px 8px 0px rgba(0, 0, 0, 0.12);
+}
+
+.mdc-elevation--z4 {
+ /* @alternate */
+ box-shadow: 0px 2px 4px -1px rgba(0, 0, 0, 0.2), 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12);
+}
+
+.mdc-elevation--z5 {
+ /* @alternate */
+ box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2), 0px 5px 8px 0px rgba(0, 0, 0, 0.14), 0px 1px 14px 0px rgba(0, 0, 0, 0.12);
+}
+
+.mdc-elevation--z6 {
+ /* @alternate */
+ box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2), 0px 6px 10px 0px rgba(0, 0, 0, 0.14), 0px 1px 18px 0px rgba(0, 0, 0, 0.12);
+}
+
+.mdc-elevation--z7 {
+ /* @alternate */
+ box-shadow: 0px 4px 5px -2px rgba(0, 0, 0, 0.2), 0px 7px 10px 1px rgba(0, 0, 0, 0.14), 0px 2px 16px 1px rgba(0, 0, 0, 0.12);
+}
+
+.mdc-elevation--z8 {
+ /* @alternate */
+ box-shadow: 0px 5px 5px -3px rgba(0, 0, 0, 0.2), 0px 8px 10px 1px rgba(0, 0, 0, 0.14), 0px 3px 14px 2px rgba(0, 0, 0, 0.12);
+}
+
+.mdc-elevation--z9 {
+ /* @alternate */
+ box-shadow: 0px 5px 6px -3px rgba(0, 0, 0, 0.2), 0px 9px 12px 1px rgba(0, 0, 0, 0.14), 0px 3px 16px 2px rgba(0, 0, 0, 0.12);
+}
+
+.mdc-elevation--z10 {
+ /* @alternate */
+ box-shadow: 0px 6px 6px -3px rgba(0, 0, 0, 0.2), 0px 10px 14px 1px rgba(0, 0, 0, 0.14), 0px 4px 18px 3px rgba(0, 0, 0, 0.12);
+}
+
+.mdc-elevation--z11 {
+ /* @alternate */
+ box-shadow: 0px 6px 7px -4px rgba(0, 0, 0, 0.2), 0px 11px 15px 1px rgba(0, 0, 0, 0.14), 0px 4px 20px 3px rgba(0, 0, 0, 0.12);
+}
+
+.mdc-elevation--z12 {
+ /* @alternate */
+ box-shadow: 0px 7px 8px -4px rgba(0, 0, 0, 0.2), 0px 12px 17px 2px rgba(0, 0, 0, 0.14), 0px 5px 22px 4px rgba(0, 0, 0, 0.12);
+}
+
+.mdc-elevation--z13 {
+ /* @alternate */
+ box-shadow: 0px 7px 8px -4px rgba(0, 0, 0, 0.2), 0px 13px 19px 2px rgba(0, 0, 0, 0.14), 0px 5px 24px 4px rgba(0, 0, 0, 0.12);
+}
+
+.mdc-elevation--z14 {
+ /* @alternate */
+ box-shadow: 0px 7px 9px -4px rgba(0, 0, 0, 0.2), 0px 14px 21px 2px rgba(0, 0, 0, 0.14), 0px 5px 26px 4px rgba(0, 0, 0, 0.12);
+}
+
+.mdc-elevation--z15 {
+ /* @alternate */
+ box-shadow: 0px 8px 9px -5px rgba(0, 0, 0, 0.2), 0px 15px 22px 2px rgba(0, 0, 0, 0.14), 0px 6px 28px 5px rgba(0, 0, 0, 0.12);
+}
+
+.mdc-elevation--z16 {
+ /* @alternate */
+ box-shadow: 0px 8px 10px -5px rgba(0, 0, 0, 0.2), 0px 16px 24px 2px rgba(0, 0, 0, 0.14), 0px 6px 30px 5px rgba(0, 0, 0, 0.12);
+}
+
+.mdc-elevation--z17 {
+ /* @alternate */
+ box-shadow: 0px 8px 11px -5px rgba(0, 0, 0, 0.2), 0px 17px 26px 2px rgba(0, 0, 0, 0.14), 0px 6px 32px 5px rgba(0, 0, 0, 0.12);
+}
+
+.mdc-elevation--z18 {
+ /* @alternate */
+ box-shadow: 0px 9px 11px -5px rgba(0, 0, 0, 0.2), 0px 18px 28px 2px rgba(0, 0, 0, 0.14), 0px 7px 34px 6px rgba(0, 0, 0, 0.12);
+}
+
+.mdc-elevation--z19 {
+ /* @alternate */
+ box-shadow: 0px 9px 12px -6px rgba(0, 0, 0, 0.2), 0px 19px 29px 2px rgba(0, 0, 0, 0.14), 0px 7px 36px 6px rgba(0, 0, 0, 0.12);
+}
+
+.mdc-elevation--z20 {
+ /* @alternate */
+ box-shadow: 0px 10px 13px -6px rgba(0, 0, 0, 0.2), 0px 20px 31px 3px rgba(0, 0, 0, 0.14), 0px 8px 38px 7px rgba(0, 0, 0, 0.12);
+}
+
+.mdc-elevation--z21 {
+ /* @alternate */
+ box-shadow: 0px 10px 13px -6px rgba(0, 0, 0, 0.2), 0px 21px 33px 3px rgba(0, 0, 0, 0.14), 0px 8px 40px 7px rgba(0, 0, 0, 0.12);
+}
+
+.mdc-elevation--z22 {
+ /* @alternate */
+ box-shadow: 0px 10px 14px -6px rgba(0, 0, 0, 0.2), 0px 22px 35px 3px rgba(0, 0, 0, 0.14), 0px 8px 42px 7px rgba(0, 0, 0, 0.12);
+}
+
+.mdc-elevation--z23 {
+ /* @alternate */
+ box-shadow: 0px 11px 14px -7px rgba(0, 0, 0, 0.2), 0px 23px 36px 3px rgba(0, 0, 0, 0.14), 0px 9px 44px 8px rgba(0, 0, 0, 0.12);
+}
+
+.mdc-elevation--z24 {
+ /* @alternate */
+ box-shadow: 0px 11px 15px -7px rgba(0, 0, 0, 0.2), 0px 24px 38px 3px rgba(0, 0, 0, 0.14), 0px 9px 46px 8px rgba(0, 0, 0, 0.12);
+}
+
+.mdc-elevation-transition {
+ transition: box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1);
+ will-change: box-shadow;
+}
+
+.mdc-fab {
+ /* @alternate */
+ position: relative;
+ display: inline-flex;
+ position: relative;
+ align-items: center;
+ justify-content: center;
+ box-sizing: border-box;
+ width: 56px;
+ height: 56px;
+ padding: 0;
+ border: none;
+ fill: currentColor;
+ text-decoration: none;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ -moz-appearance: none;
+ -webkit-appearance: none;
+ overflow: visible;
+ transition: box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1), opacity 15ms linear 30ms, -webkit-transform 270ms 0ms cubic-bezier(0, 0, 0.2, 1);
+ transition: box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1), opacity 15ms linear 30ms, transform 270ms 0ms cubic-bezier(0, 0, 0.2, 1);
+ transition: box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1), opacity 15ms linear 30ms, transform 270ms 0ms cubic-bezier(0, 0, 0.2, 1), -webkit-transform 270ms 0ms cubic-bezier(0, 0, 0.2, 1);
+}
+.mdc-fab .mdc-elevation-overlay {
+ width: 100%;
+ height: 100%;
+ top: 0;
+ /* @noflip */
+ left: 0;
+}
+.mdc-fab::-moz-focus-inner {
+ padding: 0;
+ border: 0;
+}
+.mdc-fab:hover {
+ /* @alternate */
+ box-shadow: 0px 5px 5px -3px rgba(0, 0, 0, 0.2), 0px 8px 10px 1px rgba(0, 0, 0, 0.14), 0px 3px 14px 2px rgba(0, 0, 0, 0.12);
+}
+.mdc-fab.mdc-ripple-upgraded--background-focused, .mdc-fab:not(.mdc-ripple-upgraded):focus {
+ /* @alternate */
+ box-shadow: 0px 5px 5px -3px rgba(0, 0, 0, 0.2), 0px 8px 10px 1px rgba(0, 0, 0, 0.14), 0px 3px 14px 2px rgba(0, 0, 0, 0.12);
+}
+.mdc-fab:active, .mdc-fab:focus:active {
+ /* @alternate */
+ box-shadow: 0px 7px 8px -4px rgba(0, 0, 0, 0.2), 0px 12px 17px 2px rgba(0, 0, 0, 0.14), 0px 5px 22px 4px rgba(0, 0, 0, 0.12);
+}
+.mdc-fab:active, .mdc-fab:focus {
+ outline: none;
+}
+.mdc-fab:hover {
+ cursor: pointer;
+}
+.mdc-fab > svg {
+ width: 100%;
+}
+
+.mdc-fab--mini {
+ width: 40px;
+ height: 40px;
+}
+
+.mdc-fab--extended {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-button-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.875rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-button-font-size, 0.875rem);
+ line-height: 2.25rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-button-line-height, 2.25rem);
+ font-weight: 500;
+ /* @alternate */
+ font-weight: var(--mdc-typography-button-font-weight, 500);
+ letter-spacing: 0.0892857143em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-button-letter-spacing, 0.0892857143em);
+ text-decoration: none;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-button-text-decoration, none);
+ text-decoration: var(--mdc-typography-button-text-decoration, none);
+ text-transform: uppercase;
+ /* @alternate */
+ text-transform: var(--mdc-typography-button-text-transform, uppercase);
+ border-radius: 24px;
+ /* @noflip */
+ padding-left: 20px;
+ /* @noflip */
+ padding-right: 20px;
+ width: auto;
+ max-width: 100%;
+ height: 48px;
+ /* @alternate */
+ line-height: normal;
+}
+.mdc-fab--extended .mdc-fab__ripple {
+ border-radius: 24px;
+}
+.mdc-fab--extended .mdc-fab__icon {
+ /* @noflip */
+ margin-left: calc(12px - 20px);
+ /* @noflip */
+ margin-right: 12px;
+}
+[dir=rtl] .mdc-fab--extended .mdc-fab__icon, .mdc-fab--extended .mdc-fab__icon[dir=rtl] {
+ /* @noflip */
+ margin-left: 12px;
+ /* @noflip */
+ margin-right: calc(12px - 20px);
+}
+
+.mdc-fab--extended .mdc-fab__label + .mdc-fab__icon {
+ /* @noflip */
+ margin-left: 12px;
+ /* @noflip */
+ margin-right: calc(12px - 20px);
+}
+[dir=rtl] .mdc-fab--extended .mdc-fab__label + .mdc-fab__icon, .mdc-fab--extended .mdc-fab__label + .mdc-fab__icon[dir=rtl] {
+ /* @noflip */
+ margin-left: calc(12px - 20px);
+ /* @noflip */
+ margin-right: 12px;
+}
+
+.mdc-fab--touch {
+ margin-top: 4px;
+ margin-bottom: 4px;
+ margin-right: 4px;
+ margin-left: 4px;
+}
+.mdc-fab--touch .mdc-fab__touch {
+ position: absolute;
+ top: 50%;
+ height: 48px;
+ /* @noflip */
+ left: 50%;
+ width: 48px;
+ -webkit-transform: translate(-50%, -50%);
+ transform: translate(-50%, -50%);
+}
+
+.mdc-fab::before {
+ position: absolute;
+ box-sizing: border-box;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ border: 1px solid transparent;
+ border-radius: inherit;
+ content: "";
+ pointer-events: none;
+}
+
+.mdc-fab__label {
+ justify-content: flex-start;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow-x: hidden;
+ overflow-y: visible;
+}
+
+.mdc-fab__icon {
+ transition: -webkit-transform 180ms 90ms cubic-bezier(0, 0, 0.2, 1);
+ transition: transform 180ms 90ms cubic-bezier(0, 0, 0.2, 1);
+ transition: transform 180ms 90ms cubic-bezier(0, 0, 0.2, 1), -webkit-transform 180ms 90ms cubic-bezier(0, 0, 0.2, 1);
+ fill: currentColor;
+ will-change: transform;
+}
+
+.mdc-fab .mdc-fab__icon {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.mdc-fab--exited {
+ -webkit-transform: scale(0);
+ transform: scale(0);
+ opacity: 0;
+ transition: opacity 15ms linear 150ms, -webkit-transform 180ms 0ms cubic-bezier(0.4, 0, 1, 1);
+ transition: opacity 15ms linear 150ms, transform 180ms 0ms cubic-bezier(0.4, 0, 1, 1);
+ transition: opacity 15ms linear 150ms, transform 180ms 0ms cubic-bezier(0.4, 0, 1, 1), -webkit-transform 180ms 0ms cubic-bezier(0.4, 0, 1, 1);
+}
+.mdc-fab--exited .mdc-fab__icon {
+ -webkit-transform: scale(0);
+ transform: scale(0);
+ transition: -webkit-transform 135ms 0ms cubic-bezier(0.4, 0, 1, 1);
+ transition: transform 135ms 0ms cubic-bezier(0.4, 0, 1, 1);
+ transition: transform 135ms 0ms cubic-bezier(0.4, 0, 1, 1), -webkit-transform 135ms 0ms cubic-bezier(0.4, 0, 1, 1);
+}
+
+.mdc-fab {
+ background-color: #018786;
+ /* @alternate */
+ background-color: var(--mdc-theme-secondary, #018786);
+ /* @alternate */
+ box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2), 0px 6px 10px 0px rgba(0, 0, 0, 0.14), 0px 1px 18px 0px rgba(0, 0, 0, 0.12);
+}
+.mdc-fab .mdc-fab__icon {
+ width: 24px;
+ height: 24px;
+ font-size: 24px;
+}
+.mdc-fab, .mdc-fab:not(:disabled) .mdc-fab__icon, .mdc-fab:not(:disabled) .mdc-fab__label, .mdc-fab:disabled .mdc-fab__icon, .mdc-fab:disabled .mdc-fab__label {
+ color: #fff;
+ /* @alternate */
+ color: var(--mdc-theme-on-secondary, #fff);
+}
+.mdc-fab:not(.mdc-fab--extended) {
+ border-radius: 50%;
+}
+.mdc-fab:not(.mdc-fab--extended) .mdc-fab__ripple {
+ border-radius: 50%;
+}
+
+.mdc-fab {
+ --mdc-ripple-fg-size: 0;
+ --mdc-ripple-left: 0;
+ --mdc-ripple-top: 0;
+ --mdc-ripple-fg-scale: 1;
+ --mdc-ripple-fg-translate-end: 0;
+ --mdc-ripple-fg-translate-start: 0;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ will-change: transform, opacity;
+}
+.mdc-fab .mdc-fab__ripple::before,
+.mdc-fab .mdc-fab__ripple::after {
+ position: absolute;
+ border-radius: 50%;
+ opacity: 0;
+ pointer-events: none;
+ content: "";
+}
+.mdc-fab .mdc-fab__ripple::before {
+ transition: opacity 15ms linear, background-color 15ms linear;
+ z-index: 1;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 1);
+}
+.mdc-fab .mdc-fab__ripple::after {
+ z-index: 0;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 0);
+}
+.mdc-fab.mdc-ripple-upgraded .mdc-fab__ripple::before {
+ -webkit-transform: scale(var(--mdc-ripple-fg-scale, 1));
+ transform: scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-fab.mdc-ripple-upgraded .mdc-fab__ripple::after {
+ top: 0;
+ /* @noflip */
+ left: 0;
+ -webkit-transform: scale(0);
+ transform: scale(0);
+ -webkit-transform-origin: center center;
+ transform-origin: center center;
+}
+.mdc-fab.mdc-ripple-upgraded--unbounded .mdc-fab__ripple::after {
+ top: var(--mdc-ripple-top, 0);
+ /* @noflip */
+ left: var(--mdc-ripple-left, 0);
+}
+.mdc-fab.mdc-ripple-upgraded--foreground-activation .mdc-fab__ripple::after {
+ -webkit-animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+ animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+}
+.mdc-fab.mdc-ripple-upgraded--foreground-deactivation .mdc-fab__ripple::after {
+ -webkit-animation: mdc-ripple-fg-opacity-out 150ms;
+ animation: mdc-ripple-fg-opacity-out 150ms;
+ -webkit-transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+ transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-fab .mdc-fab__ripple::before,
+.mdc-fab .mdc-fab__ripple::after {
+ top: calc(50% - 100%);
+ /* @noflip */
+ left: calc(50% - 100%);
+ width: 200%;
+ height: 200%;
+}
+.mdc-fab.mdc-ripple-upgraded .mdc-fab__ripple::after {
+ width: var(--mdc-ripple-fg-size, 100%);
+ height: var(--mdc-ripple-fg-size, 100%);
+}
+.mdc-fab .mdc-fab__ripple::before, .mdc-fab .mdc-fab__ripple::after {
+ background-color: #fff;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, var(--mdc-theme-on-secondary, #fff));
+}
+.mdc-fab:hover .mdc-fab__ripple::before, .mdc-fab.mdc-ripple-surface--hover .mdc-fab__ripple::before {
+ opacity: 0.08;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.08);
+}
+.mdc-fab.mdc-ripple-upgraded--background-focused .mdc-fab__ripple::before, .mdc-fab:not(.mdc-ripple-upgraded):focus .mdc-fab__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.24;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.24);
+}
+.mdc-fab:not(.mdc-ripple-upgraded) .mdc-fab__ripple::after {
+ transition: opacity 150ms linear;
+}
+.mdc-fab:not(.mdc-ripple-upgraded):active .mdc-fab__ripple::after {
+ transition-duration: 75ms;
+ opacity: 0.24;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.24);
+}
+.mdc-fab.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.24);
+}
+.mdc-fab .mdc-fab__ripple {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ overflow: hidden;
+}
+.mdc-fab {
+ z-index: 0;
+}
+.mdc-fab .mdc-fab__ripple::before,
+.mdc-fab .mdc-fab__ripple::after {
+ z-index: -1;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, -1);
+}
+
+.mdc-form-field {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.875rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-body2-font-size, 0.875rem);
+ line-height: 1.25rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-body2-line-height, 1.25rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-body2-font-weight, 400);
+ letter-spacing: 0.0178571429em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-body2-letter-spacing, 0.0178571429em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-body2-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-body2-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-body2-text-transform, inherit);
+ color: rgba(0, 0, 0, 0.87);
+ /* @alternate */
+ color: var(--mdc-theme-text-primary-on-background, rgba(0, 0, 0, 0.87));
+ display: inline-flex;
+ align-items: center;
+ vertical-align: middle;
+}
+.mdc-form-field > label {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: auto;
+ /* @noflip */
+ padding-left: 4px;
+ /* @noflip */
+ padding-right: 0;
+ order: 0;
+}
+[dir=rtl] .mdc-form-field > label, .mdc-form-field > label[dir=rtl] {
+ /* @noflip */
+ margin-left: auto;
+ /* @noflip */
+ margin-right: 0;
+}
+
+[dir=rtl] .mdc-form-field > label, .mdc-form-field > label[dir=rtl] {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: 4px;
+}
+
+.mdc-form-field--nowrap > label {
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+}
+
+.mdc-form-field--align-end > label {
+ /* @noflip */
+ margin-left: auto;
+ /* @noflip */
+ margin-right: 0;
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: 4px;
+ order: -1;
+}
+[dir=rtl] .mdc-form-field--align-end > label, .mdc-form-field--align-end > label[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: auto;
+}
+
+[dir=rtl] .mdc-form-field--align-end > label, .mdc-form-field--align-end > label[dir=rtl] {
+ /* @noflip */
+ padding-left: 4px;
+ /* @noflip */
+ padding-right: 0;
+}
+
+.mdc-form-field--space-between {
+ justify-content: space-between;
+}
+.mdc-form-field--space-between > label {
+ margin: 0;
+}
+[dir=rtl] .mdc-form-field--space-between > label, .mdc-form-field--space-between > label[dir=rtl] {
+ margin: 0;
+}
+
+.mdc-icon-button {
+ display: inline-block;
+ position: relative;
+ box-sizing: border-box;
+ border: none;
+ outline: none;
+ background-color: transparent;
+ fill: currentColor;
+ color: inherit;
+ font-size: 24px;
+ text-decoration: none;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ width: 48px;
+ height: 48px;
+ padding: 12px;
+}
+.mdc-icon-button svg,
+.mdc-icon-button img {
+ width: 24px;
+ height: 24px;
+}
+.mdc-icon-button:disabled {
+ color: rgba(0, 0, 0, 0.38);
+ /* @alternate */
+ color: var(--mdc-theme-text-disabled-on-light, rgba(0, 0, 0, 0.38));
+}
+.mdc-icon-button:disabled {
+ cursor: default;
+ pointer-events: none;
+}
+.mdc-icon-button .mdc-icon-button__touch {
+ position: absolute;
+ top: 50%;
+ height: 48px;
+ /* @noflip */
+ left: 50%;
+ width: 48px;
+ -webkit-transform: translate(-50%, -50%);
+ transform: translate(-50%, -50%);
+}
+
+.mdc-icon-button__icon {
+ display: inline-block;
+}
+.mdc-icon-button__icon.mdc-icon-button__icon--on {
+ display: none;
+}
+
+.mdc-icon-button--on .mdc-icon-button__icon {
+ display: none;
+}
+.mdc-icon-button--on .mdc-icon-button__icon.mdc-icon-button__icon--on {
+ display: inline-block;
+}
+
+.mdc-icon-button--touch {
+ margin-top: 0px;
+ margin-bottom: 0px;
+}
+
+.mdc-icon-button {
+ --mdc-ripple-fg-size: 0;
+ --mdc-ripple-left: 0;
+ --mdc-ripple-top: 0;
+ --mdc-ripple-fg-scale: 1;
+ --mdc-ripple-fg-translate-end: 0;
+ --mdc-ripple-fg-translate-start: 0;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ will-change: transform, opacity;
+}
+.mdc-icon-button .mdc-icon-button__ripple::before,
+.mdc-icon-button .mdc-icon-button__ripple::after {
+ position: absolute;
+ border-radius: 50%;
+ opacity: 0;
+ pointer-events: none;
+ content: "";
+}
+.mdc-icon-button .mdc-icon-button__ripple::before {
+ transition: opacity 15ms linear, background-color 15ms linear;
+ z-index: 1;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 1);
+}
+.mdc-icon-button .mdc-icon-button__ripple::after {
+ z-index: 0;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 0);
+}
+.mdc-icon-button.mdc-ripple-upgraded .mdc-icon-button__ripple::before {
+ -webkit-transform: scale(var(--mdc-ripple-fg-scale, 1));
+ transform: scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-icon-button.mdc-ripple-upgraded .mdc-icon-button__ripple::after {
+ top: 0;
+ /* @noflip */
+ left: 0;
+ -webkit-transform: scale(0);
+ transform: scale(0);
+ -webkit-transform-origin: center center;
+ transform-origin: center center;
+}
+.mdc-icon-button.mdc-ripple-upgraded--unbounded .mdc-icon-button__ripple::after {
+ top: var(--mdc-ripple-top, 0);
+ /* @noflip */
+ left: var(--mdc-ripple-left, 0);
+}
+.mdc-icon-button.mdc-ripple-upgraded--foreground-activation .mdc-icon-button__ripple::after {
+ -webkit-animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+ animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+}
+.mdc-icon-button.mdc-ripple-upgraded--foreground-deactivation .mdc-icon-button__ripple::after {
+ -webkit-animation: mdc-ripple-fg-opacity-out 150ms;
+ animation: mdc-ripple-fg-opacity-out 150ms;
+ -webkit-transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+ transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-icon-button .mdc-icon-button__ripple::before,
+.mdc-icon-button .mdc-icon-button__ripple::after {
+ top: calc(50% - 50%);
+ /* @noflip */
+ left: calc(50% - 50%);
+ width: 100%;
+ height: 100%;
+}
+.mdc-icon-button.mdc-ripple-upgraded .mdc-icon-button__ripple::before,
+.mdc-icon-button.mdc-ripple-upgraded .mdc-icon-button__ripple::after {
+ top: var(--mdc-ripple-top, calc(50% - 50%));
+ /* @noflip */
+ left: var(--mdc-ripple-left, calc(50% - 50%));
+ width: var(--mdc-ripple-fg-size, 100%);
+ height: var(--mdc-ripple-fg-size, 100%);
+}
+.mdc-icon-button.mdc-ripple-upgraded .mdc-icon-button__ripple::after {
+ width: var(--mdc-ripple-fg-size, 100%);
+ height: var(--mdc-ripple-fg-size, 100%);
+}
+.mdc-icon-button .mdc-icon-button__ripple::before, .mdc-icon-button .mdc-icon-button__ripple::after {
+ background-color: #000;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, #000);
+}
+.mdc-icon-button:hover .mdc-icon-button__ripple::before, .mdc-icon-button.mdc-ripple-surface--hover .mdc-icon-button__ripple::before {
+ opacity: 0.04;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.04);
+}
+.mdc-icon-button.mdc-ripple-upgraded--background-focused .mdc-icon-button__ripple::before, .mdc-icon-button:not(.mdc-ripple-upgraded):focus .mdc-icon-button__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.12);
+}
+.mdc-icon-button:not(.mdc-ripple-upgraded) .mdc-icon-button__ripple::after {
+ transition: opacity 150ms linear;
+}
+.mdc-icon-button:not(.mdc-ripple-upgraded):active .mdc-icon-button__ripple::after {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-icon-button.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-icon-button .mdc-icon-button__ripple {
+ pointer-events: none;
+ z-index: 1;
+}
+
+.mdc-image-list {
+ display: flex;
+ flex-wrap: wrap;
+ margin: 0 auto;
+ padding: 0;
+}
+
+.mdc-image-list__item,
+.mdc-image-list__image-aspect-container {
+ position: relative;
+ box-sizing: border-box;
+}
+
+.mdc-image-list__item {
+ list-style-type: none;
+}
+
+.mdc-image-list__image {
+ width: 100%;
+}
+
+.mdc-image-list__image-aspect-container .mdc-image-list__image {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ height: 100%;
+ background-repeat: no-repeat;
+ background-position: center;
+ background-size: cover;
+}
+
+.mdc-image-list__image-aspect-container {
+ padding-bottom: calc(100% / 1);
+}
+
+.mdc-image-list__image {
+ border-radius: 0;
+}
+
+.mdc-image-list--with-text-protection .mdc-image-list__supporting {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+}
+
+.mdc-image-list__supporting {
+ color: rgba(0, 0, 0, 0.87);
+ /* @alternate */
+ color: var(--mdc-theme-text-primary-on-background, rgba(0, 0, 0, 0.87));
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ box-sizing: border-box;
+ padding: 8px 0;
+ line-height: 24px;
+}
+
+.mdc-image-list__label {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-subtitle1-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 1rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-subtitle1-font-size, 1rem);
+ line-height: 1.75rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-subtitle1-line-height, 1.75rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-subtitle1-font-weight, 400);
+ letter-spacing: 0.009375em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-subtitle1-letter-spacing, 0.009375em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-subtitle1-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-subtitle1-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-subtitle1-text-transform, inherit);
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+}
+
+.mdc-image-list--with-text-protection .mdc-image-list__supporting {
+ position: absolute;
+ bottom: 0;
+ width: 100%;
+ height: 48px;
+ padding: 0 16px;
+ background: rgba(0, 0, 0, 0.6);
+ color: #fff;
+}
+
+.mdc-image-list--masonry {
+ display: block;
+}
+.mdc-image-list--masonry .mdc-image-list__item {
+ -webkit-column-break-inside: avoid;
+ break-inside: avoid-column;
+}
+.mdc-image-list--masonry .mdc-image-list__image {
+ display: block;
+ height: auto;
+}
+
+:root {
+ --mdc-layout-grid-margin-desktop: 24px;
+ --mdc-layout-grid-gutter-desktop: 24px;
+ --mdc-layout-grid-column-width-desktop: 72px;
+ --mdc-layout-grid-margin-tablet: 16px;
+ --mdc-layout-grid-gutter-tablet: 16px;
+ --mdc-layout-grid-column-width-tablet: 72px;
+ --mdc-layout-grid-margin-phone: 16px;
+ --mdc-layout-grid-gutter-phone: 16px;
+ --mdc-layout-grid-column-width-phone: 72px;
+}
+
+@media (min-width: 840px) {
+ .mdc-layout-grid {
+ box-sizing: border-box;
+ margin: 0 auto;
+ padding: 24px;
+ padding: var(--mdc-layout-grid-margin-desktop, 24px);
+ }
+}
+@media (min-width: 600px) and (max-width: 839px) {
+ .mdc-layout-grid {
+ box-sizing: border-box;
+ margin: 0 auto;
+ padding: 16px;
+ padding: var(--mdc-layout-grid-margin-tablet, 16px);
+ }
+}
+@media (max-width: 599px) {
+ .mdc-layout-grid {
+ box-sizing: border-box;
+ margin: 0 auto;
+ padding: 16px;
+ padding: var(--mdc-layout-grid-margin-phone, 16px);
+ }
+}
+
+@media (min-width: 840px) {
+ .mdc-layout-grid__inner {
+ display: flex;
+ flex-flow: row wrap;
+ align-items: stretch;
+ margin: -12px;
+ margin: calc(var(--mdc-layout-grid-gutter-desktop, 24px) / 2 * -1);
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__inner {
+ display: grid;
+ margin: 0;
+ grid-gap: 24px;
+ grid-gap: var(--mdc-layout-grid-gutter-desktop, 24px);
+ grid-template-columns: repeat(12, minmax(0, 1fr));
+ }
+ }
+}
+@media (min-width: 600px) and (max-width: 839px) {
+ .mdc-layout-grid__inner {
+ display: flex;
+ flex-flow: row wrap;
+ align-items: stretch;
+ margin: -8px;
+ margin: calc(var(--mdc-layout-grid-gutter-tablet, 16px) / 2 * -1);
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__inner {
+ display: grid;
+ margin: 0;
+ grid-gap: 16px;
+ grid-gap: var(--mdc-layout-grid-gutter-tablet, 16px);
+ grid-template-columns: repeat(8, minmax(0, 1fr));
+ }
+ }
+}
+@media (max-width: 599px) {
+ .mdc-layout-grid__inner {
+ display: flex;
+ flex-flow: row wrap;
+ align-items: stretch;
+ margin: -8px;
+ margin: calc(var(--mdc-layout-grid-gutter-phone, 16px) / 2 * -1);
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__inner {
+ display: grid;
+ margin: 0;
+ grid-gap: 16px;
+ grid-gap: var(--mdc-layout-grid-gutter-phone, 16px);
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ }
+ }
+}
+
+@media (min-width: 840px) {
+ .mdc-layout-grid__cell {
+ width: calc(33.3333333333% - 24px);
+ width: calc(33.3333333333% - var(--mdc-layout-grid-gutter-desktop, 24px));
+ box-sizing: border-box;
+ margin: 12px;
+ margin: calc(var(--mdc-layout-grid-gutter-desktop, 24px) / 2);
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell {
+ width: auto;
+ grid-column-end: span 4;
+ }
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell {
+ margin: 0;
+ }
+ }
+ .mdc-layout-grid__cell--span-1,
+.mdc-layout-grid__cell--span-1-desktop {
+ width: calc(8.3333333333% - 24px);
+ width: calc(8.3333333333% - var(--mdc-layout-grid-gutter-desktop, 24px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-1,
+.mdc-layout-grid__cell--span-1-desktop {
+ width: auto;
+ grid-column-end: span 1;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-2,
+.mdc-layout-grid__cell--span-2-desktop {
+ width: calc(16.6666666667% - 24px);
+ width: calc(16.6666666667% - var(--mdc-layout-grid-gutter-desktop, 24px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-2,
+.mdc-layout-grid__cell--span-2-desktop {
+ width: auto;
+ grid-column-end: span 2;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-3,
+.mdc-layout-grid__cell--span-3-desktop {
+ width: calc(25% - 24px);
+ width: calc(25% - var(--mdc-layout-grid-gutter-desktop, 24px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-3,
+.mdc-layout-grid__cell--span-3-desktop {
+ width: auto;
+ grid-column-end: span 3;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-4,
+.mdc-layout-grid__cell--span-4-desktop {
+ width: calc(33.3333333333% - 24px);
+ width: calc(33.3333333333% - var(--mdc-layout-grid-gutter-desktop, 24px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-4,
+.mdc-layout-grid__cell--span-4-desktop {
+ width: auto;
+ grid-column-end: span 4;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-5,
+.mdc-layout-grid__cell--span-5-desktop {
+ width: calc(41.6666666667% - 24px);
+ width: calc(41.6666666667% - var(--mdc-layout-grid-gutter-desktop, 24px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-5,
+.mdc-layout-grid__cell--span-5-desktop {
+ width: auto;
+ grid-column-end: span 5;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-6,
+.mdc-layout-grid__cell--span-6-desktop {
+ width: calc(50% - 24px);
+ width: calc(50% - var(--mdc-layout-grid-gutter-desktop, 24px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-6,
+.mdc-layout-grid__cell--span-6-desktop {
+ width: auto;
+ grid-column-end: span 6;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-7,
+.mdc-layout-grid__cell--span-7-desktop {
+ width: calc(58.3333333333% - 24px);
+ width: calc(58.3333333333% - var(--mdc-layout-grid-gutter-desktop, 24px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-7,
+.mdc-layout-grid__cell--span-7-desktop {
+ width: auto;
+ grid-column-end: span 7;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-8,
+.mdc-layout-grid__cell--span-8-desktop {
+ width: calc(66.6666666667% - 24px);
+ width: calc(66.6666666667% - var(--mdc-layout-grid-gutter-desktop, 24px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-8,
+.mdc-layout-grid__cell--span-8-desktop {
+ width: auto;
+ grid-column-end: span 8;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-9,
+.mdc-layout-grid__cell--span-9-desktop {
+ width: calc(75% - 24px);
+ width: calc(75% - var(--mdc-layout-grid-gutter-desktop, 24px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-9,
+.mdc-layout-grid__cell--span-9-desktop {
+ width: auto;
+ grid-column-end: span 9;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-10,
+.mdc-layout-grid__cell--span-10-desktop {
+ width: calc(83.3333333333% - 24px);
+ width: calc(83.3333333333% - var(--mdc-layout-grid-gutter-desktop, 24px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-10,
+.mdc-layout-grid__cell--span-10-desktop {
+ width: auto;
+ grid-column-end: span 10;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-11,
+.mdc-layout-grid__cell--span-11-desktop {
+ width: calc(91.6666666667% - 24px);
+ width: calc(91.6666666667% - var(--mdc-layout-grid-gutter-desktop, 24px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-11,
+.mdc-layout-grid__cell--span-11-desktop {
+ width: auto;
+ grid-column-end: span 11;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-12,
+.mdc-layout-grid__cell--span-12-desktop {
+ width: calc(100% - 24px);
+ width: calc(100% - var(--mdc-layout-grid-gutter-desktop, 24px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-12,
+.mdc-layout-grid__cell--span-12-desktop {
+ width: auto;
+ grid-column-end: span 12;
+ }
+ }
+}
+@media (min-width: 600px) and (max-width: 839px) {
+ .mdc-layout-grid__cell {
+ width: calc(50% - 16px);
+ width: calc(50% - var(--mdc-layout-grid-gutter-tablet, 16px));
+ box-sizing: border-box;
+ margin: 8px;
+ margin: calc(var(--mdc-layout-grid-gutter-tablet, 16px) / 2);
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell {
+ width: auto;
+ grid-column-end: span 4;
+ }
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell {
+ margin: 0;
+ }
+ }
+ .mdc-layout-grid__cell--span-1,
+.mdc-layout-grid__cell--span-1-tablet {
+ width: calc(12.5% - 16px);
+ width: calc(12.5% - var(--mdc-layout-grid-gutter-tablet, 16px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-1,
+.mdc-layout-grid__cell--span-1-tablet {
+ width: auto;
+ grid-column-end: span 1;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-2,
+.mdc-layout-grid__cell--span-2-tablet {
+ width: calc(25% - 16px);
+ width: calc(25% - var(--mdc-layout-grid-gutter-tablet, 16px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-2,
+.mdc-layout-grid__cell--span-2-tablet {
+ width: auto;
+ grid-column-end: span 2;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-3,
+.mdc-layout-grid__cell--span-3-tablet {
+ width: calc(37.5% - 16px);
+ width: calc(37.5% - var(--mdc-layout-grid-gutter-tablet, 16px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-3,
+.mdc-layout-grid__cell--span-3-tablet {
+ width: auto;
+ grid-column-end: span 3;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-4,
+.mdc-layout-grid__cell--span-4-tablet {
+ width: calc(50% - 16px);
+ width: calc(50% - var(--mdc-layout-grid-gutter-tablet, 16px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-4,
+.mdc-layout-grid__cell--span-4-tablet {
+ width: auto;
+ grid-column-end: span 4;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-5,
+.mdc-layout-grid__cell--span-5-tablet {
+ width: calc(62.5% - 16px);
+ width: calc(62.5% - var(--mdc-layout-grid-gutter-tablet, 16px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-5,
+.mdc-layout-grid__cell--span-5-tablet {
+ width: auto;
+ grid-column-end: span 5;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-6,
+.mdc-layout-grid__cell--span-6-tablet {
+ width: calc(75% - 16px);
+ width: calc(75% - var(--mdc-layout-grid-gutter-tablet, 16px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-6,
+.mdc-layout-grid__cell--span-6-tablet {
+ width: auto;
+ grid-column-end: span 6;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-7,
+.mdc-layout-grid__cell--span-7-tablet {
+ width: calc(87.5% - 16px);
+ width: calc(87.5% - var(--mdc-layout-grid-gutter-tablet, 16px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-7,
+.mdc-layout-grid__cell--span-7-tablet {
+ width: auto;
+ grid-column-end: span 7;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-8,
+.mdc-layout-grid__cell--span-8-tablet {
+ width: calc(100% - 16px);
+ width: calc(100% - var(--mdc-layout-grid-gutter-tablet, 16px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-8,
+.mdc-layout-grid__cell--span-8-tablet {
+ width: auto;
+ grid-column-end: span 8;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-9,
+.mdc-layout-grid__cell--span-9-tablet {
+ width: calc(100% - 16px);
+ width: calc(100% - var(--mdc-layout-grid-gutter-tablet, 16px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-9,
+.mdc-layout-grid__cell--span-9-tablet {
+ width: auto;
+ grid-column-end: span 8;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-10,
+.mdc-layout-grid__cell--span-10-tablet {
+ width: calc(100% - 16px);
+ width: calc(100% - var(--mdc-layout-grid-gutter-tablet, 16px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-10,
+.mdc-layout-grid__cell--span-10-tablet {
+ width: auto;
+ grid-column-end: span 8;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-11,
+.mdc-layout-grid__cell--span-11-tablet {
+ width: calc(100% - 16px);
+ width: calc(100% - var(--mdc-layout-grid-gutter-tablet, 16px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-11,
+.mdc-layout-grid__cell--span-11-tablet {
+ width: auto;
+ grid-column-end: span 8;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-12,
+.mdc-layout-grid__cell--span-12-tablet {
+ width: calc(100% - 16px);
+ width: calc(100% - var(--mdc-layout-grid-gutter-tablet, 16px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-12,
+.mdc-layout-grid__cell--span-12-tablet {
+ width: auto;
+ grid-column-end: span 8;
+ }
+ }
+}
+@media (max-width: 599px) {
+ .mdc-layout-grid__cell {
+ width: calc(100% - 16px);
+ width: calc(100% - var(--mdc-layout-grid-gutter-phone, 16px));
+ box-sizing: border-box;
+ margin: 8px;
+ margin: calc(var(--mdc-layout-grid-gutter-phone, 16px) / 2);
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell {
+ width: auto;
+ grid-column-end: span 4;
+ }
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell {
+ margin: 0;
+ }
+ }
+ .mdc-layout-grid__cell--span-1,
+.mdc-layout-grid__cell--span-1-phone {
+ width: calc(25% - 16px);
+ width: calc(25% - var(--mdc-layout-grid-gutter-phone, 16px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-1,
+.mdc-layout-grid__cell--span-1-phone {
+ width: auto;
+ grid-column-end: span 1;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-2,
+.mdc-layout-grid__cell--span-2-phone {
+ width: calc(50% - 16px);
+ width: calc(50% - var(--mdc-layout-grid-gutter-phone, 16px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-2,
+.mdc-layout-grid__cell--span-2-phone {
+ width: auto;
+ grid-column-end: span 2;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-3,
+.mdc-layout-grid__cell--span-3-phone {
+ width: calc(75% - 16px);
+ width: calc(75% - var(--mdc-layout-grid-gutter-phone, 16px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-3,
+.mdc-layout-grid__cell--span-3-phone {
+ width: auto;
+ grid-column-end: span 3;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-4,
+.mdc-layout-grid__cell--span-4-phone {
+ width: calc(100% - 16px);
+ width: calc(100% - var(--mdc-layout-grid-gutter-phone, 16px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-4,
+.mdc-layout-grid__cell--span-4-phone {
+ width: auto;
+ grid-column-end: span 4;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-5,
+.mdc-layout-grid__cell--span-5-phone {
+ width: calc(100% - 16px);
+ width: calc(100% - var(--mdc-layout-grid-gutter-phone, 16px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-5,
+.mdc-layout-grid__cell--span-5-phone {
+ width: auto;
+ grid-column-end: span 4;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-6,
+.mdc-layout-grid__cell--span-6-phone {
+ width: calc(100% - 16px);
+ width: calc(100% - var(--mdc-layout-grid-gutter-phone, 16px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-6,
+.mdc-layout-grid__cell--span-6-phone {
+ width: auto;
+ grid-column-end: span 4;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-7,
+.mdc-layout-grid__cell--span-7-phone {
+ width: calc(100% - 16px);
+ width: calc(100% - var(--mdc-layout-grid-gutter-phone, 16px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-7,
+.mdc-layout-grid__cell--span-7-phone {
+ width: auto;
+ grid-column-end: span 4;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-8,
+.mdc-layout-grid__cell--span-8-phone {
+ width: calc(100% - 16px);
+ width: calc(100% - var(--mdc-layout-grid-gutter-phone, 16px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-8,
+.mdc-layout-grid__cell--span-8-phone {
+ width: auto;
+ grid-column-end: span 4;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-9,
+.mdc-layout-grid__cell--span-9-phone {
+ width: calc(100% - 16px);
+ width: calc(100% - var(--mdc-layout-grid-gutter-phone, 16px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-9,
+.mdc-layout-grid__cell--span-9-phone {
+ width: auto;
+ grid-column-end: span 4;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-10,
+.mdc-layout-grid__cell--span-10-phone {
+ width: calc(100% - 16px);
+ width: calc(100% - var(--mdc-layout-grid-gutter-phone, 16px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-10,
+.mdc-layout-grid__cell--span-10-phone {
+ width: auto;
+ grid-column-end: span 4;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-11,
+.mdc-layout-grid__cell--span-11-phone {
+ width: calc(100% - 16px);
+ width: calc(100% - var(--mdc-layout-grid-gutter-phone, 16px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-11,
+.mdc-layout-grid__cell--span-11-phone {
+ width: auto;
+ grid-column-end: span 4;
+ }
+ }
+
+ .mdc-layout-grid__cell--span-12,
+.mdc-layout-grid__cell--span-12-phone {
+ width: calc(100% - 16px);
+ width: calc(100% - var(--mdc-layout-grid-gutter-phone, 16px));
+ }
+ @supports (display: grid) {
+ .mdc-layout-grid__cell--span-12,
+.mdc-layout-grid__cell--span-12-phone {
+ width: auto;
+ grid-column-end: span 4;
+ }
+ }
+}
+.mdc-layout-grid__cell--order-1 {
+ order: 1;
+}
+.mdc-layout-grid__cell--order-2 {
+ order: 2;
+}
+.mdc-layout-grid__cell--order-3 {
+ order: 3;
+}
+.mdc-layout-grid__cell--order-4 {
+ order: 4;
+}
+.mdc-layout-grid__cell--order-5 {
+ order: 5;
+}
+.mdc-layout-grid__cell--order-6 {
+ order: 6;
+}
+.mdc-layout-grid__cell--order-7 {
+ order: 7;
+}
+.mdc-layout-grid__cell--order-8 {
+ order: 8;
+}
+.mdc-layout-grid__cell--order-9 {
+ order: 9;
+}
+.mdc-layout-grid__cell--order-10 {
+ order: 10;
+}
+.mdc-layout-grid__cell--order-11 {
+ order: 11;
+}
+.mdc-layout-grid__cell--order-12 {
+ order: 12;
+}
+.mdc-layout-grid__cell--align-top {
+ align-self: flex-start;
+}
+@supports (display: grid) {
+ .mdc-layout-grid__cell--align-top {
+ align-self: start;
+ }
+}
+.mdc-layout-grid__cell--align-middle {
+ align-self: center;
+}
+.mdc-layout-grid__cell--align-bottom {
+ align-self: flex-end;
+}
+@supports (display: grid) {
+ .mdc-layout-grid__cell--align-bottom {
+ align-self: end;
+ }
+}
+
+@media (min-width: 840px) {
+ .mdc-layout-grid--fixed-column-width {
+ width: 1176px;
+ width: calc( var(--mdc-layout-grid-column-width-desktop, 72px) * 12 + var(--mdc-layout-grid-gutter-desktop, 24px) * 11 + var(--mdc-layout-grid-margin-desktop, 24px) * 2 );
+ }
+}
+@media (min-width: 600px) and (max-width: 839px) {
+ .mdc-layout-grid--fixed-column-width {
+ width: 720px;
+ width: calc( var(--mdc-layout-grid-column-width-tablet, 72px) * 8 + var(--mdc-layout-grid-gutter-tablet, 16px) * 7 + var(--mdc-layout-grid-margin-tablet, 16px) * 2 );
+ }
+}
+@media (max-width: 599px) {
+ .mdc-layout-grid--fixed-column-width {
+ width: 368px;
+ width: calc( var(--mdc-layout-grid-column-width-phone, 72px) * 4 + var(--mdc-layout-grid-gutter-phone, 16px) * 3 + var(--mdc-layout-grid-margin-phone, 16px) * 2 );
+ }
+}
+
+.mdc-layout-grid--align-left {
+ margin-right: auto;
+ margin-left: 0;
+}
+
+.mdc-layout-grid--align-right {
+ margin-right: 0;
+ margin-left: auto;
+}
+
+@-webkit-keyframes mdc-linear-progress-primary-indeterminate-translate {
+ 0% {
+ -webkit-transform: translateX(0);
+ transform: translateX(0);
+ }
+ 20% {
+ -webkit-animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ -webkit-transform: translateX(0);
+ transform: translateX(0);
+ }
+ 59.15% {
+ -webkit-animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ -webkit-transform: translateX(83.67142%);
+ transform: translateX(83.67142%);
+ /* @alternate */
+ -webkit-transform: translateX(var(--mdc-linear-progress-primary-half, 83.67142%));
+ transform: translateX(var(--mdc-linear-progress-primary-half, 83.67142%));
+ }
+ 100% {
+ -webkit-transform: translateX(200.611057%);
+ transform: translateX(200.611057%);
+ /* @alternate */
+ -webkit-transform: translateX(var(--mdc-linear-progress-primary-full, 200.611057%));
+ transform: translateX(var(--mdc-linear-progress-primary-full, 200.611057%));
+ }
+}
+
+@keyframes mdc-linear-progress-primary-indeterminate-translate {
+ 0% {
+ -webkit-transform: translateX(0);
+ transform: translateX(0);
+ }
+ 20% {
+ -webkit-animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ -webkit-transform: translateX(0);
+ transform: translateX(0);
+ }
+ 59.15% {
+ -webkit-animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ -webkit-transform: translateX(83.67142%);
+ transform: translateX(83.67142%);
+ /* @alternate */
+ -webkit-transform: translateX(var(--mdc-linear-progress-primary-half, 83.67142%));
+ transform: translateX(var(--mdc-linear-progress-primary-half, 83.67142%));
+ }
+ 100% {
+ -webkit-transform: translateX(200.611057%);
+ transform: translateX(200.611057%);
+ /* @alternate */
+ -webkit-transform: translateX(var(--mdc-linear-progress-primary-full, 200.611057%));
+ transform: translateX(var(--mdc-linear-progress-primary-full, 200.611057%));
+ }
+}
+@-webkit-keyframes mdc-linear-progress-primary-indeterminate-scale {
+ 0% {
+ -webkit-transform: scaleX(0.08);
+ transform: scaleX(0.08);
+ }
+ 36.65% {
+ -webkit-animation-timing-function: cubic-bezier(0.334731, 0.12482, 0.785844, 1);
+ animation-timing-function: cubic-bezier(0.334731, 0.12482, 0.785844, 1);
+ -webkit-transform: scaleX(0.08);
+ transform: scaleX(0.08);
+ }
+ 69.15% {
+ -webkit-animation-timing-function: cubic-bezier(0.06, 0.11, 0.6, 1);
+ animation-timing-function: cubic-bezier(0.06, 0.11, 0.6, 1);
+ -webkit-transform: scaleX(0.661479);
+ transform: scaleX(0.661479);
+ }
+ 100% {
+ -webkit-transform: scaleX(0.08);
+ transform: scaleX(0.08);
+ }
+}
+@keyframes mdc-linear-progress-primary-indeterminate-scale {
+ 0% {
+ -webkit-transform: scaleX(0.08);
+ transform: scaleX(0.08);
+ }
+ 36.65% {
+ -webkit-animation-timing-function: cubic-bezier(0.334731, 0.12482, 0.785844, 1);
+ animation-timing-function: cubic-bezier(0.334731, 0.12482, 0.785844, 1);
+ -webkit-transform: scaleX(0.08);
+ transform: scaleX(0.08);
+ }
+ 69.15% {
+ -webkit-animation-timing-function: cubic-bezier(0.06, 0.11, 0.6, 1);
+ animation-timing-function: cubic-bezier(0.06, 0.11, 0.6, 1);
+ -webkit-transform: scaleX(0.661479);
+ transform: scaleX(0.661479);
+ }
+ 100% {
+ -webkit-transform: scaleX(0.08);
+ transform: scaleX(0.08);
+ }
+}
+@-webkit-keyframes mdc-linear-progress-secondary-indeterminate-translate {
+ 0% {
+ -webkit-animation-timing-function: cubic-bezier(0.15, 0, 0.515058, 0.409685);
+ animation-timing-function: cubic-bezier(0.15, 0, 0.515058, 0.409685);
+ -webkit-transform: translateX(0);
+ transform: translateX(0);
+ }
+ 25% {
+ -webkit-animation-timing-function: cubic-bezier(0.31033, 0.284058, 0.8, 0.733712);
+ animation-timing-function: cubic-bezier(0.31033, 0.284058, 0.8, 0.733712);
+ -webkit-transform: translateX(37.651913%);
+ transform: translateX(37.651913%);
+ /* @alternate */
+ -webkit-transform: translateX(var(--mdc-linear-progress-secondary-quarter, 37.651913%));
+ transform: translateX(var(--mdc-linear-progress-secondary-quarter, 37.651913%));
+ }
+ 48.35% {
+ -webkit-animation-timing-function: cubic-bezier(0.4, 0.627035, 0.6, 0.902026);
+ animation-timing-function: cubic-bezier(0.4, 0.627035, 0.6, 0.902026);
+ -webkit-transform: translateX(84.386165%);
+ transform: translateX(84.386165%);
+ /* @alternate */
+ -webkit-transform: translateX(var(--mdc-linear-progress-secondary-half, 84.386165%));
+ transform: translateX(var(--mdc-linear-progress-secondary-half, 84.386165%));
+ }
+ 100% {
+ -webkit-transform: translateX(160.277782%);
+ transform: translateX(160.277782%);
+ /* @alternate */
+ -webkit-transform: translateX(var(--mdc-linear-progress-secondary-full, 160.277782%));
+ transform: translateX(var(--mdc-linear-progress-secondary-full, 160.277782%));
+ }
+}
+@keyframes mdc-linear-progress-secondary-indeterminate-translate {
+ 0% {
+ -webkit-animation-timing-function: cubic-bezier(0.15, 0, 0.515058, 0.409685);
+ animation-timing-function: cubic-bezier(0.15, 0, 0.515058, 0.409685);
+ -webkit-transform: translateX(0);
+ transform: translateX(0);
+ }
+ 25% {
+ -webkit-animation-timing-function: cubic-bezier(0.31033, 0.284058, 0.8, 0.733712);
+ animation-timing-function: cubic-bezier(0.31033, 0.284058, 0.8, 0.733712);
+ -webkit-transform: translateX(37.651913%);
+ transform: translateX(37.651913%);
+ /* @alternate */
+ -webkit-transform: translateX(var(--mdc-linear-progress-secondary-quarter, 37.651913%));
+ transform: translateX(var(--mdc-linear-progress-secondary-quarter, 37.651913%));
+ }
+ 48.35% {
+ -webkit-animation-timing-function: cubic-bezier(0.4, 0.627035, 0.6, 0.902026);
+ animation-timing-function: cubic-bezier(0.4, 0.627035, 0.6, 0.902026);
+ -webkit-transform: translateX(84.386165%);
+ transform: translateX(84.386165%);
+ /* @alternate */
+ -webkit-transform: translateX(var(--mdc-linear-progress-secondary-half, 84.386165%));
+ transform: translateX(var(--mdc-linear-progress-secondary-half, 84.386165%));
+ }
+ 100% {
+ -webkit-transform: translateX(160.277782%);
+ transform: translateX(160.277782%);
+ /* @alternate */
+ -webkit-transform: translateX(var(--mdc-linear-progress-secondary-full, 160.277782%));
+ transform: translateX(var(--mdc-linear-progress-secondary-full, 160.277782%));
+ }
+}
+@-webkit-keyframes mdc-linear-progress-secondary-indeterminate-scale {
+ 0% {
+ -webkit-animation-timing-function: cubic-bezier(0.205028, 0.057051, 0.57661, 0.453971);
+ animation-timing-function: cubic-bezier(0.205028, 0.057051, 0.57661, 0.453971);
+ -webkit-transform: scaleX(0.08);
+ transform: scaleX(0.08);
+ }
+ 19.15% {
+ -webkit-animation-timing-function: cubic-bezier(0.152313, 0.196432, 0.648374, 1.004315);
+ animation-timing-function: cubic-bezier(0.152313, 0.196432, 0.648374, 1.004315);
+ -webkit-transform: scaleX(0.457104);
+ transform: scaleX(0.457104);
+ }
+ 44.15% {
+ -webkit-animation-timing-function: cubic-bezier(0.257759, -0.003163, 0.211762, 1.38179);
+ animation-timing-function: cubic-bezier(0.257759, -0.003163, 0.211762, 1.38179);
+ -webkit-transform: scaleX(0.72796);
+ transform: scaleX(0.72796);
+ }
+ 100% {
+ -webkit-transform: scaleX(0.08);
+ transform: scaleX(0.08);
+ }
+}
+@keyframes mdc-linear-progress-secondary-indeterminate-scale {
+ 0% {
+ -webkit-animation-timing-function: cubic-bezier(0.205028, 0.057051, 0.57661, 0.453971);
+ animation-timing-function: cubic-bezier(0.205028, 0.057051, 0.57661, 0.453971);
+ -webkit-transform: scaleX(0.08);
+ transform: scaleX(0.08);
+ }
+ 19.15% {
+ -webkit-animation-timing-function: cubic-bezier(0.152313, 0.196432, 0.648374, 1.004315);
+ animation-timing-function: cubic-bezier(0.152313, 0.196432, 0.648374, 1.004315);
+ -webkit-transform: scaleX(0.457104);
+ transform: scaleX(0.457104);
+ }
+ 44.15% {
+ -webkit-animation-timing-function: cubic-bezier(0.257759, -0.003163, 0.211762, 1.38179);
+ animation-timing-function: cubic-bezier(0.257759, -0.003163, 0.211762, 1.38179);
+ -webkit-transform: scaleX(0.72796);
+ transform: scaleX(0.72796);
+ }
+ 100% {
+ -webkit-transform: scaleX(0.08);
+ transform: scaleX(0.08);
+ }
+}
+@-webkit-keyframes mdc-linear-progress-buffering {
+ from {
+ -webkit-transform: rotate(180deg) translateX(-10px);
+ transform: rotate(180deg) translateX(-10px);
+ }
+}
+@keyframes mdc-linear-progress-buffering {
+ from {
+ -webkit-transform: rotate(180deg) translateX(-10px);
+ transform: rotate(180deg) translateX(-10px);
+ }
+}
+@-webkit-keyframes mdc-linear-progress-primary-indeterminate-translate-reverse {
+ 0% {
+ -webkit-transform: translateX(0);
+ transform: translateX(0);
+ }
+ 20% {
+ -webkit-animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ -webkit-transform: translateX(0);
+ transform: translateX(0);
+ }
+ 59.15% {
+ -webkit-animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ -webkit-transform: translateX(-83.67142%);
+ transform: translateX(-83.67142%);
+ /* @alternate */
+ -webkit-transform: translateX(var(--mdc-linear-progress-primary-half-neg, -83.67142%));
+ transform: translateX(var(--mdc-linear-progress-primary-half-neg, -83.67142%));
+ }
+ 100% {
+ -webkit-transform: translateX(-200.611057%);
+ transform: translateX(-200.611057%);
+ /* @alternate */
+ -webkit-transform: translateX(var(--mdc-linear-progress-primary-full-neg, -200.611057%));
+ transform: translateX(var(--mdc-linear-progress-primary-full-neg, -200.611057%));
+ }
+}
+@keyframes mdc-linear-progress-primary-indeterminate-translate-reverse {
+ 0% {
+ -webkit-transform: translateX(0);
+ transform: translateX(0);
+ }
+ 20% {
+ -webkit-animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ -webkit-transform: translateX(0);
+ transform: translateX(0);
+ }
+ 59.15% {
+ -webkit-animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ -webkit-transform: translateX(-83.67142%);
+ transform: translateX(-83.67142%);
+ /* @alternate */
+ -webkit-transform: translateX(var(--mdc-linear-progress-primary-half-neg, -83.67142%));
+ transform: translateX(var(--mdc-linear-progress-primary-half-neg, -83.67142%));
+ }
+ 100% {
+ -webkit-transform: translateX(-200.611057%);
+ transform: translateX(-200.611057%);
+ /* @alternate */
+ -webkit-transform: translateX(var(--mdc-linear-progress-primary-full-neg, -200.611057%));
+ transform: translateX(var(--mdc-linear-progress-primary-full-neg, -200.611057%));
+ }
+}
+@-webkit-keyframes mdc-linear-progress-secondary-indeterminate-translate-reverse {
+ 0% {
+ -webkit-animation-timing-function: cubic-bezier(0.15, 0, 0.515058, 0.409685);
+ animation-timing-function: cubic-bezier(0.15, 0, 0.515058, 0.409685);
+ -webkit-transform: translateX(0);
+ transform: translateX(0);
+ }
+ 25% {
+ -webkit-animation-timing-function: cubic-bezier(0.31033, 0.284058, 0.8, 0.733712);
+ animation-timing-function: cubic-bezier(0.31033, 0.284058, 0.8, 0.733712);
+ -webkit-transform: translateX(-37.651913%);
+ transform: translateX(-37.651913%);
+ /* @alternate */
+ -webkit-transform: translateX(var(--mdc-linear-progress-secondary-quarter-neg, -37.651913%));
+ transform: translateX(var(--mdc-linear-progress-secondary-quarter-neg, -37.651913%));
+ }
+ 48.35% {
+ -webkit-animation-timing-function: cubic-bezier(0.4, 0.627035, 0.6, 0.902026);
+ animation-timing-function: cubic-bezier(0.4, 0.627035, 0.6, 0.902026);
+ -webkit-transform: translateX(-84.386165%);
+ transform: translateX(-84.386165%);
+ /* @alternate */
+ -webkit-transform: translateX(var(--mdc-linear-progress-secondary-half-neg, -84.386165%));
+ transform: translateX(var(--mdc-linear-progress-secondary-half-neg, -84.386165%));
+ }
+ 100% {
+ -webkit-transform: translateX(-160.277782%);
+ transform: translateX(-160.277782%);
+ /* @alternate */
+ -webkit-transform: translateX(var(--mdc-linear-progress-secondary-full-neg, -160.277782%));
+ transform: translateX(var(--mdc-linear-progress-secondary-full-neg, -160.277782%));
+ }
+}
+@keyframes mdc-linear-progress-secondary-indeterminate-translate-reverse {
+ 0% {
+ -webkit-animation-timing-function: cubic-bezier(0.15, 0, 0.515058, 0.409685);
+ animation-timing-function: cubic-bezier(0.15, 0, 0.515058, 0.409685);
+ -webkit-transform: translateX(0);
+ transform: translateX(0);
+ }
+ 25% {
+ -webkit-animation-timing-function: cubic-bezier(0.31033, 0.284058, 0.8, 0.733712);
+ animation-timing-function: cubic-bezier(0.31033, 0.284058, 0.8, 0.733712);
+ -webkit-transform: translateX(-37.651913%);
+ transform: translateX(-37.651913%);
+ /* @alternate */
+ -webkit-transform: translateX(var(--mdc-linear-progress-secondary-quarter-neg, -37.651913%));
+ transform: translateX(var(--mdc-linear-progress-secondary-quarter-neg, -37.651913%));
+ }
+ 48.35% {
+ -webkit-animation-timing-function: cubic-bezier(0.4, 0.627035, 0.6, 0.902026);
+ animation-timing-function: cubic-bezier(0.4, 0.627035, 0.6, 0.902026);
+ -webkit-transform: translateX(-84.386165%);
+ transform: translateX(-84.386165%);
+ /* @alternate */
+ -webkit-transform: translateX(var(--mdc-linear-progress-secondary-half-neg, -84.386165%));
+ transform: translateX(var(--mdc-linear-progress-secondary-half-neg, -84.386165%));
+ }
+ 100% {
+ -webkit-transform: translateX(-160.277782%);
+ transform: translateX(-160.277782%);
+ /* @alternate */
+ -webkit-transform: translateX(var(--mdc-linear-progress-secondary-full-neg, -160.277782%));
+ transform: translateX(var(--mdc-linear-progress-secondary-full-neg, -160.277782%));
+ }
+}
+@-webkit-keyframes mdc-linear-progress-buffering-reverse {
+ from {
+ -webkit-transform: translateX(-10px);
+ transform: translateX(-10px);
+ }
+}
+@keyframes mdc-linear-progress-buffering-reverse {
+ from {
+ -webkit-transform: translateX(-10px);
+ transform: translateX(-10px);
+ }
+}
+.mdc-linear-progress {
+ position: relative;
+ width: 100%;
+ height: 4px;
+ -webkit-transform: translateZ(0);
+ transform: translateZ(0);
+ outline: 1px solid transparent;
+ overflow: hidden;
+ transition: opacity 250ms 0ms cubic-bezier(0.4, 0, 0.6, 1);
+}
+.mdc-linear-progress__bar {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ -webkit-animation: none;
+ animation: none;
+ -webkit-transform-origin: top left;
+ transform-origin: top left;
+ transition: -webkit-transform 250ms 0ms cubic-bezier(0.4, 0, 0.6, 1);
+ transition: transform 250ms 0ms cubic-bezier(0.4, 0, 0.6, 1);
+ transition: transform 250ms 0ms cubic-bezier(0.4, 0, 0.6, 1), -webkit-transform 250ms 0ms cubic-bezier(0.4, 0, 0.6, 1);
+}
+.mdc-linear-progress__bar-inner {
+ display: inline-block;
+ position: absolute;
+ width: 100%;
+ -webkit-animation: none;
+ animation: none;
+ border-top: 4px solid;
+}
+.mdc-linear-progress__buffer {
+ display: flex;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+}
+.mdc-linear-progress__buffer-dots {
+ background-repeat: repeat-x;
+ background-size: 10px 4px;
+ flex: auto;
+ -webkit-transform: rotate(180deg);
+ transform: rotate(180deg);
+ -webkit-animation: mdc-linear-progress-buffering 250ms infinite linear;
+ animation: mdc-linear-progress-buffering 250ms infinite linear;
+}
+.mdc-linear-progress__buffer-bar {
+ flex: 0 1 100%;
+ transition: flex-basis 250ms 0ms cubic-bezier(0.4, 0, 0.6, 1);
+}
+.mdc-linear-progress__primary-bar {
+ -webkit-transform: scaleX(0);
+ transform: scaleX(0);
+}
+.mdc-linear-progress__secondary-bar {
+ display: none;
+}
+.mdc-linear-progress--indeterminate .mdc-linear-progress__bar {
+ transition: none;
+}
+.mdc-linear-progress--indeterminate .mdc-linear-progress__primary-bar {
+ left: -145.166611%;
+}
+.mdc-linear-progress--indeterminate .mdc-linear-progress__secondary-bar {
+ left: -54.888891%;
+ display: block;
+}
+.mdc-linear-progress--indeterminate.mdc-linear-progress--animation-ready .mdc-linear-progress__primary-bar {
+ -webkit-animation: mdc-linear-progress-primary-indeterminate-translate 2s infinite linear;
+ animation: mdc-linear-progress-primary-indeterminate-translate 2s infinite linear;
+}
+.mdc-linear-progress--indeterminate.mdc-linear-progress--animation-ready .mdc-linear-progress__primary-bar > .mdc-linear-progress__bar-inner {
+ -webkit-animation: mdc-linear-progress-primary-indeterminate-scale 2s infinite linear;
+ animation: mdc-linear-progress-primary-indeterminate-scale 2s infinite linear;
+}
+.mdc-linear-progress--indeterminate.mdc-linear-progress--animation-ready .mdc-linear-progress__secondary-bar {
+ -webkit-animation: mdc-linear-progress-secondary-indeterminate-translate 2s infinite linear;
+ animation: mdc-linear-progress-secondary-indeterminate-translate 2s infinite linear;
+}
+.mdc-linear-progress--indeterminate.mdc-linear-progress--animation-ready .mdc-linear-progress__secondary-bar > .mdc-linear-progress__bar-inner {
+ -webkit-animation: mdc-linear-progress-secondary-indeterminate-scale 2s infinite linear;
+ animation: mdc-linear-progress-secondary-indeterminate-scale 2s infinite linear;
+}
+[dir=rtl] .mdc-linear-progress:not([dir=ltr]) .mdc-linear-progress__bar, .mdc-linear-progress[dir=rtl]:not([dir=ltr]) .mdc-linear-progress__bar {
+ /* @noflip */
+ right: 0;
+ /* @noflip */
+ -webkit-transform-origin: center right;
+ /* @noflip */
+ transform-origin: center right;
+}
+[dir=rtl] .mdc-linear-progress:not([dir=ltr]).mdc-linear-progress--animation-ready .mdc-linear-progress__primary-bar, .mdc-linear-progress[dir=rtl]:not([dir=ltr]).mdc-linear-progress--animation-ready .mdc-linear-progress__primary-bar {
+ -webkit-animation-name: mdc-linear-progress-primary-indeterminate-translate-reverse;
+ animation-name: mdc-linear-progress-primary-indeterminate-translate-reverse;
+}
+[dir=rtl] .mdc-linear-progress:not([dir=ltr]).mdc-linear-progress--animation-ready .mdc-linear-progress__secondary-bar, .mdc-linear-progress[dir=rtl]:not([dir=ltr]).mdc-linear-progress--animation-ready .mdc-linear-progress__secondary-bar {
+ -webkit-animation-name: mdc-linear-progress-secondary-indeterminate-translate-reverse;
+ animation-name: mdc-linear-progress-secondary-indeterminate-translate-reverse;
+}
+[dir=rtl] .mdc-linear-progress:not([dir=ltr]) .mdc-linear-progress__buffer-dots, .mdc-linear-progress[dir=rtl]:not([dir=ltr]) .mdc-linear-progress__buffer-dots {
+ -webkit-animation: mdc-linear-progress-buffering-reverse 250ms infinite linear;
+ animation: mdc-linear-progress-buffering-reverse 250ms infinite linear;
+ -webkit-transform: rotate(0);
+ transform: rotate(0);
+}
+[dir=rtl] .mdc-linear-progress:not([dir=ltr]).mdc-linear-progress--indeterminate .mdc-linear-progress__primary-bar, .mdc-linear-progress[dir=rtl]:not([dir=ltr]).mdc-linear-progress--indeterminate .mdc-linear-progress__primary-bar {
+ /* @noflip */
+ right: -145.166611%;
+ /* @noflip */
+ left: auto;
+}
+[dir=rtl] .mdc-linear-progress:not([dir=ltr]).mdc-linear-progress--indeterminate .mdc-linear-progress__secondary-bar, .mdc-linear-progress[dir=rtl]:not([dir=ltr]).mdc-linear-progress--indeterminate .mdc-linear-progress__secondary-bar {
+ /* @noflip */
+ right: -54.888891%;
+ /* @noflip */
+ left: auto;
+}
+
+.mdc-linear-progress--closed {
+ opacity: 0;
+}
+.mdc-linear-progress--closed-animation-off .mdc-linear-progress__buffer-dots {
+ -webkit-animation: none;
+ animation: none;
+}
+.mdc-linear-progress--closed-animation-off.mdc-linear-progress--indeterminate .mdc-linear-progress__bar,
+.mdc-linear-progress--closed-animation-off.mdc-linear-progress--indeterminate .mdc-linear-progress__bar .mdc-linear-progress__bar-inner {
+ -webkit-animation: none;
+ animation: none;
+}
+
+.mdc-linear-progress__bar-inner {
+ border-color: #6200ee;
+ /* @alternate */
+ border-color: var(--mdc-theme-primary, #6200ee);
+}
+
+.mdc-linear-progress__buffer-dots {
+ background-image: url("data:image/svg+xml,%3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' enable-background='new 0 0 5 2' xml:space='preserve' viewBox='0 0 5 2' preserveAspectRatio='none slice'%3E%3Ccircle cx='1' cy='1' r='1' fill='%23e6e6e6'/%3E%3C/svg%3E");
+}
+
+.mdc-linear-progress__buffer-bar {
+ background-color: #e6e6e6;
+}
+
+.mdc-deprecated-list {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-subtitle1-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 1rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-subtitle1-font-size, 1rem);
+ line-height: 1.75rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-subtitle1-line-height, 1.75rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-subtitle1-font-weight, 400);
+ letter-spacing: 0.009375em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-subtitle1-letter-spacing, 0.009375em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-subtitle1-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-subtitle1-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-subtitle1-text-transform, inherit);
+ /* @alternate */
+ line-height: 1.5rem;
+ margin: 0;
+ padding: 8px 0;
+ list-style-type: none;
+ color: rgba(0, 0, 0, 0.87);
+ /* @alternate */
+ color: var(--mdc-theme-text-primary-on-background, rgba(0, 0, 0, 0.87));
+}
+.mdc-deprecated-list:focus {
+ outline: none;
+}
+
+.mdc-deprecated-list-item {
+ height: 48px;
+}
+
+.mdc-deprecated-list-item__secondary-text {
+ color: rgba(0, 0, 0, 0.54);
+ /* @alternate */
+ color: var(--mdc-theme-text-secondary-on-background, rgba(0, 0, 0, 0.54));
+}
+
+.mdc-deprecated-list-item__graphic {
+ background-color: transparent;
+}
+
+.mdc-deprecated-list-item__graphic {
+ color: rgba(0, 0, 0, 0.38);
+ /* @alternate */
+ color: var(--mdc-theme-text-icon-on-background, rgba(0, 0, 0, 0.38));
+}
+
+.mdc-deprecated-list-item__meta {
+ color: rgba(0, 0, 0, 0.38);
+ /* @alternate */
+ color: var(--mdc-theme-text-hint-on-background, rgba(0, 0, 0, 0.38));
+}
+
+.mdc-deprecated-list-group__subheader {
+ color: rgba(0, 0, 0, 0.87);
+ /* @alternate */
+ color: var(--mdc-theme-text-primary-on-background, rgba(0, 0, 0, 0.87));
+}
+
+.mdc-deprecated-list-item--disabled .mdc-deprecated-list-item__text {
+ opacity: 0.38;
+}
+
+.mdc-deprecated-list-item--disabled .mdc-deprecated-list-item__text,
+.mdc-deprecated-list-item--disabled .mdc-deprecated-list-item__primary-text,
+.mdc-deprecated-list-item--disabled .mdc-deprecated-list-item__secondary-text {
+ color: #000;
+ /* @alternate */
+ color: var(--mdc-theme-on-surface, #000);
+}
+
+.mdc-deprecated-list-item--selected,
+.mdc-deprecated-list-item--activated {
+ color: #6200ee;
+ /* @alternate */
+ color: var(--mdc-theme-primary, #6200ee);
+}
+.mdc-deprecated-list-item--selected .mdc-deprecated-list-item__graphic,
+.mdc-deprecated-list-item--activated .mdc-deprecated-list-item__graphic {
+ color: #6200ee;
+ /* @alternate */
+ color: var(--mdc-theme-primary, #6200ee);
+}
+
+.mdc-deprecated-list--dense {
+ padding-top: 4px;
+ padding-bottom: 4px;
+ font-size: 0.812rem;
+}
+
+.mdc-deprecated-list-item {
+ display: flex;
+ position: relative;
+ align-items: center;
+ justify-content: flex-start;
+ overflow: hidden;
+ padding: 0;
+ /* @noflip */
+ padding-left: 16px;
+ /* @noflip */
+ padding-right: 16px;
+ height: 48px;
+}
+.mdc-deprecated-list-item:focus {
+ outline: none;
+}
+.mdc-deprecated-list-item:not(.mdc-deprecated-list-item--selected):focus::before, .mdc-deprecated-list-item.mdc-ripple-upgraded--background-focused::before {
+ position: absolute;
+ box-sizing: border-box;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ border: 1px solid transparent;
+ border-radius: inherit;
+ content: "";
+ pointer-events: none;
+}
+.mdc-deprecated-list-item.mdc-deprecated-list-item--selected::before {
+ position: absolute;
+ box-sizing: border-box;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ border: 3px double transparent;
+ border-radius: inherit;
+ content: "";
+ pointer-events: none;
+}
+[dir=rtl] .mdc-deprecated-list-item, .mdc-deprecated-list-item[dir=rtl] {
+ /* @noflip */
+ padding-left: 16px;
+ /* @noflip */
+ padding-right: 16px;
+}
+
+.mdc-deprecated-list--icon-list .mdc-deprecated-list-item {
+ /* @noflip */
+ padding-left: 16px;
+ /* @noflip */
+ padding-right: 16px;
+ height: 56px;
+}
+[dir=rtl] .mdc-deprecated-list--icon-list .mdc-deprecated-list-item, .mdc-deprecated-list--icon-list .mdc-deprecated-list-item[dir=rtl] {
+ /* @noflip */
+ padding-left: 16px;
+ /* @noflip */
+ padding-right: 16px;
+}
+
+.mdc-deprecated-list--avatar-list .mdc-deprecated-list-item {
+ /* @noflip */
+ padding-left: 16px;
+ /* @noflip */
+ padding-right: 16px;
+ height: 56px;
+}
+[dir=rtl] .mdc-deprecated-list--avatar-list .mdc-deprecated-list-item, .mdc-deprecated-list--avatar-list .mdc-deprecated-list-item[dir=rtl] {
+ /* @noflip */
+ padding-left: 16px;
+ /* @noflip */
+ padding-right: 16px;
+}
+
+.mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-item {
+ /* @noflip */
+ padding-left: 16px;
+ /* @noflip */
+ padding-right: 16px;
+ height: 56px;
+}
+[dir=rtl] .mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-item, .mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-item[dir=rtl] {
+ /* @noflip */
+ padding-left: 16px;
+ /* @noflip */
+ padding-right: 16px;
+}
+
+.mdc-deprecated-list--image-list .mdc-deprecated-list-item {
+ /* @noflip */
+ padding-left: 16px;
+ /* @noflip */
+ padding-right: 16px;
+ height: 72px;
+}
+[dir=rtl] .mdc-deprecated-list--image-list .mdc-deprecated-list-item, .mdc-deprecated-list--image-list .mdc-deprecated-list-item[dir=rtl] {
+ /* @noflip */
+ padding-left: 16px;
+ /* @noflip */
+ padding-right: 16px;
+}
+
+.mdc-deprecated-list--video-list .mdc-deprecated-list-item {
+ /* @noflip */
+ padding-left: 0px;
+ /* @noflip */
+ padding-right: 16px;
+ height: 72px;
+}
+[dir=rtl] .mdc-deprecated-list--video-list .mdc-deprecated-list-item, .mdc-deprecated-list--video-list .mdc-deprecated-list-item[dir=rtl] {
+ /* @noflip */
+ padding-left: 16px;
+ /* @noflip */
+ padding-right: 0px;
+}
+
+.mdc-deprecated-list--dense .mdc-deprecated-list-item__graphic {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 16px;
+ width: 20px;
+ height: 20px;
+}
+[dir=rtl] .mdc-deprecated-list--dense .mdc-deprecated-list-item__graphic, .mdc-deprecated-list--dense .mdc-deprecated-list-item__graphic[dir=rtl] {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 0;
+}
+
+.mdc-deprecated-list-item__graphic {
+ flex-shrink: 0;
+ align-items: center;
+ justify-content: center;
+ fill: currentColor;
+ -o-object-fit: cover;
+ object-fit: cover;
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 32px;
+ width: 24px;
+ height: 24px;
+}
+[dir=rtl] .mdc-deprecated-list-item__graphic, .mdc-deprecated-list-item__graphic[dir=rtl] {
+ /* @noflip */
+ margin-left: 32px;
+ /* @noflip */
+ margin-right: 0;
+}
+
+.mdc-deprecated-list--icon-list .mdc-deprecated-list-item__graphic {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 32px;
+ width: 24px;
+ height: 24px;
+}
+[dir=rtl] .mdc-deprecated-list--icon-list .mdc-deprecated-list-item__graphic, .mdc-deprecated-list--icon-list .mdc-deprecated-list-item__graphic[dir=rtl] {
+ /* @noflip */
+ margin-left: 32px;
+ /* @noflip */
+ margin-right: 0;
+}
+
+.mdc-deprecated-list--avatar-list .mdc-deprecated-list-item__graphic {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 16px;
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+}
+[dir=rtl] .mdc-deprecated-list--avatar-list .mdc-deprecated-list-item__graphic, .mdc-deprecated-list--avatar-list .mdc-deprecated-list-item__graphic[dir=rtl] {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 0;
+}
+
+.mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-item__graphic {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 16px;
+ width: 40px;
+ height: 40px;
+}
+[dir=rtl] .mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-item__graphic, .mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-item__graphic[dir=rtl] {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 0;
+}
+
+.mdc-deprecated-list--image-list .mdc-deprecated-list-item__graphic {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 16px;
+ width: 56px;
+ height: 56px;
+}
+[dir=rtl] .mdc-deprecated-list--image-list .mdc-deprecated-list-item__graphic, .mdc-deprecated-list--image-list .mdc-deprecated-list-item__graphic[dir=rtl] {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 0;
+}
+
+.mdc-deprecated-list--video-list .mdc-deprecated-list-item__graphic {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 16px;
+ width: 100px;
+ height: 56px;
+}
+[dir=rtl] .mdc-deprecated-list--video-list .mdc-deprecated-list-item__graphic, .mdc-deprecated-list--video-list .mdc-deprecated-list-item__graphic[dir=rtl] {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 0;
+}
+
+.mdc-deprecated-list .mdc-deprecated-list-item__graphic {
+ display: inline-flex;
+}
+
+.mdc-deprecated-list-item__meta {
+ /* @noflip */
+ margin-left: auto;
+ /* @noflip */
+ margin-right: 0;
+}
+.mdc-deprecated-list-item__meta:not(.material-icons) {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-caption-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.75rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-caption-font-size, 0.75rem);
+ line-height: 1.25rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-caption-line-height, 1.25rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-caption-font-weight, 400);
+ letter-spacing: 0.0333333333em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-caption-letter-spacing, 0.0333333333em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-caption-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-caption-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-caption-text-transform, inherit);
+}
+.mdc-deprecated-list-item[dir=rtl] .mdc-deprecated-list-item__meta, [dir=rtl] .mdc-deprecated-list-item .mdc-deprecated-list-item__meta {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: auto;
+}
+
+.mdc-deprecated-list-item__text {
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+}
+
+.mdc-deprecated-list-item__text[for] {
+ pointer-events: none;
+}
+
+.mdc-deprecated-list-item__primary-text {
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+ margin-bottom: -20px;
+}
+.mdc-deprecated-list-item__primary-text::before {
+ display: inline-block;
+ width: 0;
+ height: 28px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-deprecated-list-item__primary-text::after {
+ display: inline-block;
+ width: 0;
+ height: 20px;
+ content: "";
+ vertical-align: -20px;
+}
+.mdc-deprecated-list--video-list .mdc-deprecated-list-item__primary-text, .mdc-deprecated-list--image-list .mdc-deprecated-list-item__primary-text, .mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-item__primary-text, .mdc-deprecated-list--avatar-list .mdc-deprecated-list-item__primary-text, .mdc-deprecated-list--icon-list .mdc-deprecated-list-item__primary-text {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+ margin-bottom: -20px;
+}
+.mdc-deprecated-list--video-list .mdc-deprecated-list-item__primary-text::before, .mdc-deprecated-list--image-list .mdc-deprecated-list-item__primary-text::before, .mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-item__primary-text::before, .mdc-deprecated-list--avatar-list .mdc-deprecated-list-item__primary-text::before, .mdc-deprecated-list--icon-list .mdc-deprecated-list-item__primary-text::before {
+ display: inline-block;
+ width: 0;
+ height: 32px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-deprecated-list--video-list .mdc-deprecated-list-item__primary-text::after, .mdc-deprecated-list--image-list .mdc-deprecated-list-item__primary-text::after, .mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-item__primary-text::after, .mdc-deprecated-list--avatar-list .mdc-deprecated-list-item__primary-text::after, .mdc-deprecated-list--icon-list .mdc-deprecated-list-item__primary-text::after {
+ display: inline-block;
+ width: 0;
+ height: 20px;
+ content: "";
+ vertical-align: -20px;
+}
+.mdc-deprecated-list--dense .mdc-deprecated-list-item__primary-text {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+ margin-bottom: -20px;
+}
+.mdc-deprecated-list--dense .mdc-deprecated-list-item__primary-text::before {
+ display: inline-block;
+ width: 0;
+ height: 24px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-deprecated-list--dense .mdc-deprecated-list-item__primary-text::after {
+ display: inline-block;
+ width: 0;
+ height: 20px;
+ content: "";
+ vertical-align: -20px;
+}
+
+.mdc-deprecated-list-item__secondary-text {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.875rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-body2-font-size, 0.875rem);
+ line-height: 1.25rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-body2-line-height, 1.25rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-body2-font-weight, 400);
+ letter-spacing: 0.0178571429em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-body2-letter-spacing, 0.0178571429em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-body2-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-body2-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-body2-text-transform, inherit);
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+}
+.mdc-deprecated-list-item__secondary-text::before {
+ display: inline-block;
+ width: 0;
+ height: 20px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-deprecated-list--dense .mdc-deprecated-list-item__secondary-text {
+ font-size: inherit;
+}
+
+.mdc-deprecated-list--dense .mdc-deprecated-list-item {
+ height: 40px;
+}
+
+.mdc-deprecated-list--two-line .mdc-deprecated-list-item__text {
+ align-self: flex-start;
+}
+
+.mdc-deprecated-list--two-line .mdc-deprecated-list-item {
+ height: 64px;
+}
+.mdc-deprecated-list--two-line.mdc-deprecated-list--video-list .mdc-deprecated-list-item, .mdc-deprecated-list--two-line.mdc-deprecated-list--image-list .mdc-deprecated-list-item, .mdc-deprecated-list--two-line.mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-item, .mdc-deprecated-list--two-line.mdc-deprecated-list--avatar-list .mdc-deprecated-list-item, .mdc-deprecated-list--two-line.mdc-deprecated-list--icon-list .mdc-deprecated-list-item {
+ height: 72px;
+}
+.mdc-deprecated-list--two-line.mdc-deprecated-list--icon-list .mdc-deprecated-list-item__graphic {
+ align-self: flex-start;
+ margin-top: 16px;
+}
+
+.mdc-deprecated-list--two-line.mdc-deprecated-list--dense .mdc-deprecated-list-item,
+.mdc-deprecated-list--avatar-list.mdc-deprecated-list--dense .mdc-deprecated-list-item {
+ height: 60px;
+}
+
+.mdc-deprecated-list--avatar-list.mdc-deprecated-list--dense .mdc-deprecated-list-item__graphic {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 16px;
+ width: 36px;
+ height: 36px;
+}
+[dir=rtl] .mdc-deprecated-list--avatar-list.mdc-deprecated-list--dense .mdc-deprecated-list-item__graphic, .mdc-deprecated-list--avatar-list.mdc-deprecated-list--dense .mdc-deprecated-list-item__graphic[dir=rtl] {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 0;
+}
+
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item {
+ cursor: pointer;
+}
+
+a.mdc-deprecated-list-item {
+ color: inherit;
+ text-decoration: none;
+}
+
+.mdc-deprecated-list-divider {
+ height: 0;
+ margin: 0;
+ border: none;
+ border-bottom-width: 1px;
+ border-bottom-style: solid;
+}
+
+.mdc-deprecated-list-divider {
+ border-bottom-color: rgba(0, 0, 0, 0.12);
+}
+
+.mdc-deprecated-list-divider--padded {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 0;
+ width: calc(100% - 32px);
+}
+[dir=rtl] .mdc-deprecated-list-divider--padded, .mdc-deprecated-list-divider--padded[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 16px;
+}
+
+.mdc-deprecated-list-divider--inset {
+ /* @noflip */
+ margin-left: 72px;
+ /* @noflip */
+ margin-right: 0;
+ width: calc(100% - 72px);
+}
+[dir=rtl] .mdc-deprecated-list-divider--inset, .mdc-deprecated-list-divider--inset[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 72px;
+}
+
+.mdc-deprecated-list-divider--inset.mdc-deprecated-list-divider--padded {
+ /* @noflip */
+ margin-left: 72px;
+ /* @noflip */
+ margin-right: 0;
+ width: calc(100% - 88px);
+}
+[dir=rtl] .mdc-deprecated-list-divider--inset.mdc-deprecated-list-divider--padded, .mdc-deprecated-list-divider--inset.mdc-deprecated-list-divider--padded[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 72px;
+}
+
+.mdc-deprecated-list .mdc-deprecated-list-divider--inset-leading {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 0;
+ width: calc(100% - 16px);
+}
+[dir=rtl] .mdc-deprecated-list .mdc-deprecated-list-divider--inset-leading, .mdc-deprecated-list .mdc-deprecated-list-divider--inset-leading[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 16px;
+}
+
+.mdc-deprecated-list .mdc-deprecated-list-divider--inset-trailing {
+ width: calc(100% - 16px);
+}
+.mdc-deprecated-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 0;
+ width: calc(100% - 32px);
+}
+[dir=rtl] .mdc-deprecated-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing, .mdc-deprecated-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 16px;
+}
+
+.mdc-deprecated-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 0;
+ width: calc(100% - 16px);
+}
+[dir=rtl] .mdc-deprecated-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding, .mdc-deprecated-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 16px;
+}
+
+.mdc-deprecated-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 0;
+ width: calc(100% - 32px);
+}
+[dir=rtl] .mdc-deprecated-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding, .mdc-deprecated-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 16px;
+}
+
+.mdc-deprecated-list--icon-list .mdc-deprecated-list-divider--inset-leading {
+ /* @noflip */
+ margin-left: 72px;
+ /* @noflip */
+ margin-right: 0;
+ width: calc(100% - 72px);
+}
+[dir=rtl] .mdc-deprecated-list--icon-list .mdc-deprecated-list-divider--inset-leading, .mdc-deprecated-list--icon-list .mdc-deprecated-list-divider--inset-leading[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 72px;
+}
+
+.mdc-deprecated-list--icon-list .mdc-deprecated-list-divider--inset-trailing {
+ width: calc(100% - 16px);
+}
+.mdc-deprecated-list--icon-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing {
+ /* @noflip */
+ margin-left: 72px;
+ /* @noflip */
+ margin-right: 0;
+ width: calc(100% - 88px);
+}
+[dir=rtl] .mdc-deprecated-list--icon-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing, .mdc-deprecated-list--icon-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 72px;
+}
+
+.mdc-deprecated-list--icon-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 0;
+ width: calc(100% - 16px);
+}
+[dir=rtl] .mdc-deprecated-list--icon-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding, .mdc-deprecated-list--icon-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 16px;
+}
+
+.mdc-deprecated-list--icon-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 0;
+ width: calc(100% - 32px);
+}
+[dir=rtl] .mdc-deprecated-list--icon-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding, .mdc-deprecated-list--icon-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 16px;
+}
+
+.mdc-deprecated-list--avatar-list .mdc-deprecated-list-divider--inset-leading {
+ /* @noflip */
+ margin-left: 72px;
+ /* @noflip */
+ margin-right: 0;
+ width: calc(100% - 72px);
+}
+[dir=rtl] .mdc-deprecated-list--avatar-list .mdc-deprecated-list-divider--inset-leading, .mdc-deprecated-list--avatar-list .mdc-deprecated-list-divider--inset-leading[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 72px;
+}
+
+.mdc-deprecated-list--avatar-list .mdc-deprecated-list-divider--inset-trailing {
+ width: calc(100% - 16px);
+}
+.mdc-deprecated-list--avatar-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing {
+ /* @noflip */
+ margin-left: 72px;
+ /* @noflip */
+ margin-right: 0;
+ width: calc(100% - 88px);
+}
+[dir=rtl] .mdc-deprecated-list--avatar-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing, .mdc-deprecated-list--avatar-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 72px;
+}
+
+.mdc-deprecated-list--avatar-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 0;
+ width: calc(100% - 16px);
+}
+[dir=rtl] .mdc-deprecated-list--avatar-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding, .mdc-deprecated-list--avatar-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 16px;
+}
+
+.mdc-deprecated-list--avatar-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 0;
+ width: calc(100% - 32px);
+}
+[dir=rtl] .mdc-deprecated-list--avatar-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding, .mdc-deprecated-list--avatar-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 16px;
+}
+
+.mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-divider--inset-leading {
+ /* @noflip */
+ margin-left: 72px;
+ /* @noflip */
+ margin-right: 0;
+ width: calc(100% - 72px);
+}
+[dir=rtl] .mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-divider--inset-leading, .mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-divider--inset-leading[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 72px;
+}
+
+.mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-divider--inset-trailing {
+ width: calc(100% - 16px);
+}
+.mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing {
+ /* @noflip */
+ margin-left: 72px;
+ /* @noflip */
+ margin-right: 0;
+ width: calc(100% - 88px);
+}
+[dir=rtl] .mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing, .mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 72px;
+}
+
+.mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 0;
+ width: calc(100% - 16px);
+}
+[dir=rtl] .mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding, .mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 16px;
+}
+
+.mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 0;
+ width: calc(100% - 32px);
+}
+[dir=rtl] .mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding, .mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 16px;
+}
+
+.mdc-deprecated-list--image-list .mdc-deprecated-list-divider--inset-leading {
+ /* @noflip */
+ margin-left: 88px;
+ /* @noflip */
+ margin-right: 0;
+ width: calc(100% - 88px);
+}
+[dir=rtl] .mdc-deprecated-list--image-list .mdc-deprecated-list-divider--inset-leading, .mdc-deprecated-list--image-list .mdc-deprecated-list-divider--inset-leading[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 88px;
+}
+
+.mdc-deprecated-list--image-list .mdc-deprecated-list-divider--inset-trailing {
+ width: calc(100% - 16px);
+}
+.mdc-deprecated-list--image-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing {
+ /* @noflip */
+ margin-left: 88px;
+ /* @noflip */
+ margin-right: 0;
+ width: calc(100% - 104px);
+}
+[dir=rtl] .mdc-deprecated-list--image-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing, .mdc-deprecated-list--image-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 88px;
+}
+
+.mdc-deprecated-list--image-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 0;
+ width: calc(100% - 16px);
+}
+[dir=rtl] .mdc-deprecated-list--image-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding, .mdc-deprecated-list--image-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 16px;
+}
+
+.mdc-deprecated-list--image-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 0;
+ width: calc(100% - 32px);
+}
+[dir=rtl] .mdc-deprecated-list--image-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding, .mdc-deprecated-list--image-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 16px;
+}
+
+.mdc-deprecated-list--video-list .mdc-deprecated-list-divider--inset-leading {
+ /* @noflip */
+ margin-left: 116px;
+ /* @noflip */
+ margin-right: 0;
+ width: calc(100% - 116px);
+}
+[dir=rtl] .mdc-deprecated-list--video-list .mdc-deprecated-list-divider--inset-leading, .mdc-deprecated-list--video-list .mdc-deprecated-list-divider--inset-leading[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 116px;
+}
+
+.mdc-deprecated-list--video-list .mdc-deprecated-list-divider--inset-trailing {
+ width: calc(100% - 16px);
+}
+.mdc-deprecated-list--video-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing {
+ /* @noflip */
+ margin-left: 116px;
+ /* @noflip */
+ margin-right: 0;
+ width: calc(100% - 132px);
+}
+[dir=rtl] .mdc-deprecated-list--video-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing, .mdc-deprecated-list--video-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 116px;
+}
+
+.mdc-deprecated-list--video-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding {
+ /* @noflip */
+ margin-left: 0px;
+ /* @noflip */
+ margin-right: 0;
+ width: calc(100% - 0px);
+}
+[dir=rtl] .mdc-deprecated-list--video-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding, .mdc-deprecated-list--video-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 0px;
+}
+
+.mdc-deprecated-list--video-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding {
+ /* @noflip */
+ margin-left: 0px;
+ /* @noflip */
+ margin-right: 0;
+ width: calc(100% - 16px);
+}
+[dir=rtl] .mdc-deprecated-list--video-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding, .mdc-deprecated-list--video-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 0px;
+}
+
+.mdc-deprecated-list-group .mdc-deprecated-list {
+ padding: 0;
+}
+
+.mdc-deprecated-list-group__subheader {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-subtitle1-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 1rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-subtitle1-font-size, 1rem);
+ line-height: 1.75rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-subtitle1-line-height, 1.75rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-subtitle1-font-weight, 400);
+ letter-spacing: 0.009375em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-subtitle1-letter-spacing, 0.009375em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-subtitle1-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-subtitle1-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-subtitle1-text-transform, inherit);
+ margin: calc( (3rem - 1.5rem) / 2 ) 16px;
+}
+
+.mdc-list-item__primary-text {
+ color: rgba(0, 0, 0, 0.87);
+ /* @alternate */
+ color: var(--mdc-theme-text-primary-on-background, rgba(0, 0, 0, 0.87));
+}
+
+.mdc-list-item__secondary-text {
+ color: rgba(0, 0, 0, 0.54);
+ /* @alternate */
+ color: var(--mdc-theme-text-secondary-on-background, rgba(0, 0, 0, 0.54));
+}
+
+.mdc-list-item__overline-text {
+ color: rgba(0, 0, 0, 0.38);
+ /* @alternate */
+ color: var(--mdc-theme-text-hint-on-background, rgba(0, 0, 0, 0.38));
+}
+
+.mdc-list-item--with-leading-icon .mdc-list-item__start,
+.mdc-list-item--with-trailing-icon .mdc-list-item__end {
+ background-color: transparent;
+}
+
+.mdc-list-item--with-leading-icon .mdc-list-item__start,
+.mdc-list-item--with-trailing-icon .mdc-list-item__end {
+ color: rgba(0, 0, 0, 0.38);
+ /* @alternate */
+ color: var(--mdc-theme-text-icon-on-background, rgba(0, 0, 0, 0.38));
+}
+
+.mdc-list-item__end {
+ color: rgba(0, 0, 0, 0.38);
+ /* @alternate */
+ color: var(--mdc-theme-text-hint-on-background, rgba(0, 0, 0, 0.38));
+}
+
+.mdc-list-item--disabled .mdc-list-item__start,
+.mdc-list-item--disabled .mdc-list-item__content,
+.mdc-list-item--disabled .mdc-list-item__end {
+ opacity: 0.38;
+}
+
+.mdc-list-item--disabled .mdc-list-item__primary-text {
+ color: #000;
+ /* @alternate */
+ color: var(--mdc-theme-on-surface, #000);
+}
+.mdc-list-item--disabled .mdc-list-item__secondary-text {
+ color: #000;
+ /* @alternate */
+ color: var(--mdc-theme-on-surface, #000);
+}
+.mdc-list-item--disabled .mdc-list-item__overline-text {
+ color: #000;
+ /* @alternate */
+ color: var(--mdc-theme-on-surface, #000);
+}
+.mdc-list-item--disabled.mdc-list-item--with-leading-icon .mdc-list-item__start {
+ color: #000;
+ /* @alternate */
+ color: var(--mdc-theme-on-surface, #000);
+}
+.mdc-list-item--disabled.mdc-list-item--with-trailing-icon .mdc-list-item__end {
+ color: #000;
+ /* @alternate */
+ color: var(--mdc-theme-on-surface, #000);
+}
+.mdc-list-item--disabled.mdc-list-item--with-trailing-meta .mdc-list-item__end {
+ color: #000;
+ /* @alternate */
+ color: var(--mdc-theme-on-surface, #000);
+}
+
+.mdc-list-item--selected .mdc-list-item__primary-text,
+.mdc-list-item--activated .mdc-list-item__primary-text {
+ color: #6200ee;
+ /* @alternate */
+ color: var(--mdc-theme-primary, #6200ee);
+}
+.mdc-list-item--selected.mdc-list-item--with-leading-icon .mdc-list-item__start,
+.mdc-list-item--activated.mdc-list-item--with-leading-icon .mdc-list-item__start {
+ color: #6200ee;
+ /* @alternate */
+ color: var(--mdc-theme-primary, #6200ee);
+}
+
+.mdc-deprecated-list-group__subheader {
+ color: rgba(0, 0, 0, 0.87);
+ /* @alternate */
+ color: var(--mdc-theme-text-primary-on-background, rgba(0, 0, 0, 0.87));
+}
+
+@media screen and (forced-colors: active), (-ms-high-contrast: active) {
+ .mdc-list-divider::after {
+ content: "";
+ display: block;
+ border-bottom-width: 1px;
+ border-bottom-style: solid;
+ border-bottom-color: white;
+ }
+}
+.mdc-list {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-subtitle1-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 1rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-subtitle1-font-size, 1rem);
+ line-height: 1.75rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-subtitle1-line-height, 1.75rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-subtitle1-font-weight, 400);
+ letter-spacing: 0.009375em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-subtitle1-letter-spacing, 0.009375em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-subtitle1-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-subtitle1-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-subtitle1-text-transform, inherit);
+ /* @alternate */
+ line-height: 1.5rem;
+ margin: 0;
+ padding: 8px 0;
+ list-style-type: none;
+}
+.mdc-list:focus {
+ outline: none;
+}
+
+.mdc-list-item {
+ display: flex;
+ position: relative;
+ align-items: center;
+ justify-content: flex-start;
+ overflow: hidden;
+ padding: 0;
+ align-items: stretch;
+ cursor: pointer;
+}
+.mdc-list-item:focus {
+ outline: none;
+}
+.mdc-list-item.mdc-list-item--with-one-line {
+ height: 48px;
+}
+.mdc-list-item.mdc-list-item--with-two-lines {
+ height: 64px;
+}
+.mdc-list-item.mdc-list-item--with-three-lines {
+ height: 88px;
+}
+.mdc-list-item.mdc-list-item--with-one-line .mdc-list-item__start {
+ align-self: center;
+ margin-top: 0;
+}
+.mdc-list-item.mdc-list-item--with-two-lines .mdc-list-item__start {
+ align-self: flex-start;
+ margin-top: 16px;
+}
+.mdc-list-item.mdc-list-item--with-three-lines .mdc-list-item__start {
+ align-self: flex-start;
+ margin-top: 16px;
+}
+.mdc-list-item.mdc-list-item--with-one-line .mdc-list-item__end {
+ align-self: center;
+ margin-top: 0;
+}
+.mdc-list-item.mdc-list-item--with-two-lines .mdc-list-item__end {
+ align-self: center;
+ margin-top: 0;
+}
+.mdc-list-item.mdc-list-item--with-three-lines .mdc-list-item__end {
+ align-self: flex-start;
+ margin-top: 16px;
+}
+.mdc-list-item.mdc-list-item--disabled, .mdc-list-item.mdc-list-item--non-interactive {
+ cursor: auto;
+}
+.mdc-list-item:not(.mdc-list-item--selected):focus::before, .mdc-list-item.mdc-ripple-upgraded--background-focused::before {
+ position: absolute;
+ box-sizing: border-box;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ border: 1px solid transparent;
+ border-radius: inherit;
+ content: "";
+ pointer-events: none;
+}
+.mdc-list-item.mdc-list-item--selected::before {
+ position: absolute;
+ box-sizing: border-box;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ border: 3px double transparent;
+ border-radius: inherit;
+ content: "";
+ pointer-events: none;
+}
+.mdc-list-item.mdc-list-item--selected:focus::before {
+ position: absolute;
+ box-sizing: border-box;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ border: 3px solid transparent;
+ border-radius: inherit;
+ content: "";
+ pointer-events: none;
+}
+
+a.mdc-list-item {
+ color: inherit;
+ text-decoration: none;
+}
+
+.mdc-list-item__start {
+ fill: currentColor;
+ flex-shrink: 0;
+ pointer-events: none;
+}
+
+.mdc-list-item__end {
+ flex-shrink: 0;
+ pointer-events: none;
+}
+
+.mdc-list-item__content {
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ align-self: center;
+ flex: 1;
+ pointer-events: none;
+}
+.mdc-list-item--with-two-lines .mdc-list-item__content, .mdc-list-item--with-three-lines .mdc-list-item__content {
+ align-self: stretch;
+}
+.mdc-list-item__content[for] {
+ pointer-events: none;
+}
+
+.mdc-list-item__primary-text {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-subtitle1-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 1rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-subtitle1-font-size, 1rem);
+ line-height: 1.75rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-subtitle1-line-height, 1.75rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-subtitle1-font-weight, 400);
+ letter-spacing: 0.009375em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-subtitle1-letter-spacing, 0.009375em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-subtitle1-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-subtitle1-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-subtitle1-text-transform, inherit);
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+}
+.mdc-list-item--with-two-lines .mdc-list-item__primary-text, .mdc-list-item--with-three-lines .mdc-list-item__primary-text {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+ margin-bottom: -20px;
+}
+.mdc-list-item--with-two-lines .mdc-list-item__primary-text::before, .mdc-list-item--with-three-lines .mdc-list-item__primary-text::before {
+ display: inline-block;
+ width: 0;
+ height: 28px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-list-item--with-two-lines .mdc-list-item__primary-text::after, .mdc-list-item--with-three-lines .mdc-list-item__primary-text::after {
+ display: inline-block;
+ width: 0;
+ height: 20px;
+ content: "";
+ vertical-align: -20px;
+}
+
+.mdc-list-item__secondary-text {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.875rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-body2-font-size, 0.875rem);
+ line-height: 1.25rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-body2-line-height, 1.25rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-body2-font-weight, 400);
+ letter-spacing: 0.0178571429em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-body2-letter-spacing, 0.0178571429em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-body2-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-body2-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-body2-text-transform, inherit);
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+}
+.mdc-list-item__secondary-text::before {
+ display: inline-block;
+ width: 0;
+ height: 20px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-list-item--with-three-lines .mdc-list-item__secondary-text {
+ white-space: normal;
+ line-height: 20px;
+}
+.mdc-list-item--with-overline .mdc-list-item__secondary-text {
+ white-space: nowrap;
+ line-height: auto;
+}
+
+.mdc-list-item__overline-text {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-overline-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.75rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-overline-font-size, 0.75rem);
+ line-height: 2rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-overline-line-height, 2rem);
+ font-weight: 500;
+ /* @alternate */
+ font-weight: var(--mdc-typography-overline-font-weight, 500);
+ letter-spacing: 0.1666666667em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-overline-letter-spacing, 0.1666666667em);
+ text-decoration: none;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-overline-text-decoration, none);
+ text-decoration: var(--mdc-typography-overline-text-decoration, none);
+ text-transform: uppercase;
+ /* @alternate */
+ text-transform: var(--mdc-typography-overline-text-transform, uppercase);
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+}
+.mdc-list-item--with-two-lines .mdc-list-item__overline-text {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+ margin-bottom: -20px;
+}
+.mdc-list-item--with-two-lines .mdc-list-item__overline-text::before {
+ display: inline-block;
+ width: 0;
+ height: 24px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-list-item--with-two-lines .mdc-list-item__overline-text::after {
+ display: inline-block;
+ width: 0;
+ height: 20px;
+ content: "";
+ vertical-align: -20px;
+}
+.mdc-list-item--with-three-lines .mdc-list-item__overline-text {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+ margin-bottom: -20px;
+}
+.mdc-list-item--with-three-lines .mdc-list-item__overline-text::before {
+ display: inline-block;
+ width: 0;
+ height: 28px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-list-item--with-three-lines .mdc-list-item__overline-text::after {
+ display: inline-block;
+ width: 0;
+ height: 20px;
+ content: "";
+ vertical-align: -20px;
+}
+
+.mdc-list-item--with-leading-avatar.mdc-list-item {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: auto;
+}
+[dir=rtl] .mdc-list-item--with-leading-avatar.mdc-list-item, .mdc-list-item--with-leading-avatar.mdc-list-item[dir=rtl] {
+ /* @noflip */
+ padding-left: auto;
+ /* @noflip */
+ padding-right: 0;
+}
+
+.mdc-list-item--with-leading-avatar .mdc-list-item__start {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 16px;
+}
+[dir=rtl] .mdc-list-item--with-leading-avatar .mdc-list-item__start, .mdc-list-item--with-leading-avatar .mdc-list-item__start[dir=rtl] {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 16px;
+}
+
+.mdc-list-item--with-leading-avatar .mdc-list-item__start {
+ width: 40px;
+ height: 40px;
+}
+.mdc-list-item--with-leading-avatar.mdc-list-item--with-two-lines .mdc-list-item__primary-text {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+ margin-bottom: -20px;
+}
+.mdc-list-item--with-leading-avatar.mdc-list-item--with-two-lines .mdc-list-item__primary-text::before {
+ display: inline-block;
+ width: 0;
+ height: 32px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-list-item--with-leading-avatar.mdc-list-item--with-two-lines .mdc-list-item__primary-text::after {
+ display: inline-block;
+ width: 0;
+ height: 20px;
+ content: "";
+ vertical-align: -20px;
+}
+.mdc-list-item--with-leading-avatar.mdc-list-item--with-two-lines .mdc-list-item__overline-text {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+ margin-bottom: -20px;
+}
+.mdc-list-item--with-leading-avatar.mdc-list-item--with-two-lines .mdc-list-item__overline-text::before {
+ display: inline-block;
+ width: 0;
+ height: 28px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-list-item--with-leading-avatar.mdc-list-item--with-two-lines .mdc-list-item__overline-text::after {
+ display: inline-block;
+ width: 0;
+ height: 20px;
+ content: "";
+ vertical-align: -20px;
+}
+.mdc-list-item--with-leading-avatar.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+}
+.mdc-list-item--with-leading-avatar.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end::before {
+ display: inline-block;
+ width: 0;
+ height: 32px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-list-item--with-leading-avatar.mdc-list-item--with-one-line {
+ height: 56px;
+}
+.mdc-list-item--with-leading-avatar.mdc-list-item--with-two-lines {
+ height: 72px;
+}
+.mdc-list-item--with-leading-avatar .mdc-list-item__start {
+ border-radius: 50%;
+}
+
+.mdc-list-item--with-leading-icon .mdc-list-item__start {
+ width: 24px;
+ height: 24px;
+}
+.mdc-list-item--with-leading-icon.mdc-list-item {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: auto;
+}
+[dir=rtl] .mdc-list-item--with-leading-icon.mdc-list-item, .mdc-list-item--with-leading-icon.mdc-list-item[dir=rtl] {
+ /* @noflip */
+ padding-left: auto;
+ /* @noflip */
+ padding-right: 0;
+}
+
+.mdc-list-item--with-leading-icon .mdc-list-item__start {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 32px;
+}
+[dir=rtl] .mdc-list-item--with-leading-icon .mdc-list-item__start, .mdc-list-item--with-leading-icon .mdc-list-item__start[dir=rtl] {
+ /* @noflip */
+ margin-left: 32px;
+ /* @noflip */
+ margin-right: 16px;
+}
+
+.mdc-list-item--with-leading-icon.mdc-list-item--with-two-lines .mdc-list-item__primary-text {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+ margin-bottom: -20px;
+}
+.mdc-list-item--with-leading-icon.mdc-list-item--with-two-lines .mdc-list-item__primary-text::before {
+ display: inline-block;
+ width: 0;
+ height: 32px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-list-item--with-leading-icon.mdc-list-item--with-two-lines .mdc-list-item__primary-text::after {
+ display: inline-block;
+ width: 0;
+ height: 20px;
+ content: "";
+ vertical-align: -20px;
+}
+.mdc-list-item--with-leading-icon.mdc-list-item--with-two-lines .mdc-list-item__overline-text {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+ margin-bottom: -20px;
+}
+.mdc-list-item--with-leading-icon.mdc-list-item--with-two-lines .mdc-list-item__overline-text::before {
+ display: inline-block;
+ width: 0;
+ height: 28px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-list-item--with-leading-icon.mdc-list-item--with-two-lines .mdc-list-item__overline-text::after {
+ display: inline-block;
+ width: 0;
+ height: 20px;
+ content: "";
+ vertical-align: -20px;
+}
+.mdc-list-item--with-leading-icon.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+}
+.mdc-list-item--with-leading-icon.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end::before {
+ display: inline-block;
+ width: 0;
+ height: 32px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-list-item--with-leading-icon.mdc-list-item--with-one-line {
+ height: 56px;
+}
+.mdc-list-item--with-leading-icon.mdc-list-item--with-two-lines {
+ height: 72px;
+}
+
+.mdc-list-item--with-leading-thumbnail.mdc-list-item {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: auto;
+}
+[dir=rtl] .mdc-list-item--with-leading-thumbnail.mdc-list-item, .mdc-list-item--with-leading-thumbnail.mdc-list-item[dir=rtl] {
+ /* @noflip */
+ padding-left: auto;
+ /* @noflip */
+ padding-right: 0;
+}
+
+.mdc-list-item--with-leading-thumbnail .mdc-list-item__start {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 16px;
+}
+[dir=rtl] .mdc-list-item--with-leading-thumbnail .mdc-list-item__start, .mdc-list-item--with-leading-thumbnail .mdc-list-item__start[dir=rtl] {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 16px;
+}
+
+.mdc-list-item--with-leading-thumbnail .mdc-list-item__start {
+ width: 40px;
+ height: 40px;
+}
+.mdc-list-item--with-leading-thumbnail.mdc-list-item--with-two-lines .mdc-list-item__primary-text {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+ margin-bottom: -20px;
+}
+.mdc-list-item--with-leading-thumbnail.mdc-list-item--with-two-lines .mdc-list-item__primary-text::before {
+ display: inline-block;
+ width: 0;
+ height: 32px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-list-item--with-leading-thumbnail.mdc-list-item--with-two-lines .mdc-list-item__primary-text::after {
+ display: inline-block;
+ width: 0;
+ height: 20px;
+ content: "";
+ vertical-align: -20px;
+}
+.mdc-list-item--with-leading-thumbnail.mdc-list-item--with-two-lines .mdc-list-item__overline-text {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+ margin-bottom: -20px;
+}
+.mdc-list-item--with-leading-thumbnail.mdc-list-item--with-two-lines .mdc-list-item__overline-text::before {
+ display: inline-block;
+ width: 0;
+ height: 28px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-list-item--with-leading-thumbnail.mdc-list-item--with-two-lines .mdc-list-item__overline-text::after {
+ display: inline-block;
+ width: 0;
+ height: 20px;
+ content: "";
+ vertical-align: -20px;
+}
+.mdc-list-item--with-leading-thumbnail.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+}
+.mdc-list-item--with-leading-thumbnail.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end::before {
+ display: inline-block;
+ width: 0;
+ height: 32px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-list-item--with-leading-thumbnail.mdc-list-item--with-one-line {
+ height: 56px;
+}
+.mdc-list-item--with-leading-thumbnail.mdc-list-item--with-two-lines {
+ height: 72px;
+}
+
+.mdc-list-item--with-leading-image.mdc-list-item {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: auto;
+}
+[dir=rtl] .mdc-list-item--with-leading-image.mdc-list-item, .mdc-list-item--with-leading-image.mdc-list-item[dir=rtl] {
+ /* @noflip */
+ padding-left: auto;
+ /* @noflip */
+ padding-right: 0;
+}
+
+.mdc-list-item--with-leading-image .mdc-list-item__start {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 16px;
+}
+[dir=rtl] .mdc-list-item--with-leading-image .mdc-list-item__start, .mdc-list-item--with-leading-image .mdc-list-item__start[dir=rtl] {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 16px;
+}
+
+.mdc-list-item--with-leading-image .mdc-list-item__start {
+ width: 56px;
+ height: 56px;
+}
+.mdc-list-item--with-leading-image.mdc-list-item--with-two-lines .mdc-list-item__primary-text {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+ margin-bottom: -20px;
+}
+.mdc-list-item--with-leading-image.mdc-list-item--with-two-lines .mdc-list-item__primary-text::before {
+ display: inline-block;
+ width: 0;
+ height: 32px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-list-item--with-leading-image.mdc-list-item--with-two-lines .mdc-list-item__primary-text::after {
+ display: inline-block;
+ width: 0;
+ height: 20px;
+ content: "";
+ vertical-align: -20px;
+}
+.mdc-list-item--with-leading-image.mdc-list-item--with-two-lines .mdc-list-item__overline-text {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+ margin-bottom: -20px;
+}
+.mdc-list-item--with-leading-image.mdc-list-item--with-two-lines .mdc-list-item__overline-text::before {
+ display: inline-block;
+ width: 0;
+ height: 28px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-list-item--with-leading-image.mdc-list-item--with-two-lines .mdc-list-item__overline-text::after {
+ display: inline-block;
+ width: 0;
+ height: 20px;
+ content: "";
+ vertical-align: -20px;
+}
+.mdc-list-item--with-leading-image.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+}
+.mdc-list-item--with-leading-image.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end::before {
+ display: inline-block;
+ width: 0;
+ height: 32px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-list-item--with-leading-image.mdc-list-item--with-one-line {
+ height: 72px;
+}
+.mdc-list-item--with-leading-image.mdc-list-item--with-two-lines {
+ height: 72px;
+}
+
+.mdc-list-item--with-leading-video.mdc-list-item--with-two-lines .mdc-list-item__start {
+ align-self: flex-start;
+ margin-top: 8px;
+}
+.mdc-list-item--with-leading-video.mdc-list-item {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: auto;
+}
+[dir=rtl] .mdc-list-item--with-leading-video.mdc-list-item, .mdc-list-item--with-leading-video.mdc-list-item[dir=rtl] {
+ /* @noflip */
+ padding-left: auto;
+ /* @noflip */
+ padding-right: 0;
+}
+
+.mdc-list-item--with-leading-video .mdc-list-item__start {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 16px;
+}
+[dir=rtl] .mdc-list-item--with-leading-video .mdc-list-item__start, .mdc-list-item--with-leading-video .mdc-list-item__start[dir=rtl] {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 0;
+}
+
+.mdc-list-item--with-leading-video .mdc-list-item__start {
+ width: 100px;
+ height: 56px;
+}
+.mdc-list-item--with-leading-video.mdc-list-item--with-two-lines .mdc-list-item__primary-text {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+ margin-bottom: -20px;
+}
+.mdc-list-item--with-leading-video.mdc-list-item--with-two-lines .mdc-list-item__primary-text::before {
+ display: inline-block;
+ width: 0;
+ height: 32px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-list-item--with-leading-video.mdc-list-item--with-two-lines .mdc-list-item__primary-text::after {
+ display: inline-block;
+ width: 0;
+ height: 20px;
+ content: "";
+ vertical-align: -20px;
+}
+.mdc-list-item--with-leading-video.mdc-list-item--with-two-lines .mdc-list-item__overline-text {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+ margin-bottom: -20px;
+}
+.mdc-list-item--with-leading-video.mdc-list-item--with-two-lines .mdc-list-item__overline-text::before {
+ display: inline-block;
+ width: 0;
+ height: 28px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-list-item--with-leading-video.mdc-list-item--with-two-lines .mdc-list-item__overline-text::after {
+ display: inline-block;
+ width: 0;
+ height: 20px;
+ content: "";
+ vertical-align: -20px;
+}
+.mdc-list-item--with-leading-video.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+}
+.mdc-list-item--with-leading-video.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end::before {
+ display: inline-block;
+ width: 0;
+ height: 32px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-list-item--with-leading-video.mdc-list-item--with-one-line {
+ height: 72px;
+}
+.mdc-list-item--with-leading-video.mdc-list-item--with-two-lines {
+ height: 72px;
+}
+
+.mdc-list-item--with-leading-checkbox.mdc-list-item {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: auto;
+}
+[dir=rtl] .mdc-list-item--with-leading-checkbox.mdc-list-item, .mdc-list-item--with-leading-checkbox.mdc-list-item[dir=rtl] {
+ /* @noflip */
+ padding-left: auto;
+ /* @noflip */
+ padding-right: 0;
+}
+
+.mdc-list-item--with-leading-checkbox .mdc-list-item__start {
+ /* @noflip */
+ margin-left: 8px;
+ /* @noflip */
+ margin-right: 24px;
+}
+[dir=rtl] .mdc-list-item--with-leading-checkbox .mdc-list-item__start, .mdc-list-item--with-leading-checkbox .mdc-list-item__start[dir=rtl] {
+ /* @noflip */
+ margin-left: 24px;
+ /* @noflip */
+ margin-right: 8px;
+}
+
+.mdc-list-item--with-leading-checkbox .mdc-list-item__start {
+ width: 40px;
+ height: 40px;
+}
+.mdc-list-item--with-leading-checkbox.mdc-list-item--with-two-lines .mdc-list-item__start {
+ align-self: flex-start;
+ margin-top: 8px;
+}
+.mdc-list-item--with-leading-checkbox.mdc-list-item--with-two-lines .mdc-list-item__primary-text {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+ margin-bottom: -20px;
+}
+.mdc-list-item--with-leading-checkbox.mdc-list-item--with-two-lines .mdc-list-item__primary-text::before {
+ display: inline-block;
+ width: 0;
+ height: 32px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-list-item--with-leading-checkbox.mdc-list-item--with-two-lines .mdc-list-item__primary-text::after {
+ display: inline-block;
+ width: 0;
+ height: 20px;
+ content: "";
+ vertical-align: -20px;
+}
+.mdc-list-item--with-leading-checkbox.mdc-list-item--with-two-lines .mdc-list-item__overline-text {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+ margin-bottom: -20px;
+}
+.mdc-list-item--with-leading-checkbox.mdc-list-item--with-two-lines .mdc-list-item__overline-text::before {
+ display: inline-block;
+ width: 0;
+ height: 28px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-list-item--with-leading-checkbox.mdc-list-item--with-two-lines .mdc-list-item__overline-text::after {
+ display: inline-block;
+ width: 0;
+ height: 20px;
+ content: "";
+ vertical-align: -20px;
+}
+.mdc-list-item--with-leading-checkbox.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+}
+.mdc-list-item--with-leading-checkbox.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end::before {
+ display: inline-block;
+ width: 0;
+ height: 32px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-list-item--with-leading-checkbox.mdc-list-item--with-one-line {
+ height: 56px;
+}
+.mdc-list-item--with-leading-checkbox.mdc-list-item--with-two-lines {
+ height: 72px;
+}
+
+.mdc-list-item--with-leading-radio.mdc-list-item {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: auto;
+}
+[dir=rtl] .mdc-list-item--with-leading-radio.mdc-list-item, .mdc-list-item--with-leading-radio.mdc-list-item[dir=rtl] {
+ /* @noflip */
+ padding-left: auto;
+ /* @noflip */
+ padding-right: 0;
+}
+
+.mdc-list-item--with-leading-radio .mdc-list-item__start {
+ /* @noflip */
+ margin-left: 8px;
+ /* @noflip */
+ margin-right: 24px;
+}
+[dir=rtl] .mdc-list-item--with-leading-radio .mdc-list-item__start, .mdc-list-item--with-leading-radio .mdc-list-item__start[dir=rtl] {
+ /* @noflip */
+ margin-left: 24px;
+ /* @noflip */
+ margin-right: 8px;
+}
+
+.mdc-list-item--with-leading-radio .mdc-list-item__start {
+ width: 40px;
+ height: 40px;
+}
+.mdc-list-item--with-leading-radio.mdc-list-item--with-two-lines .mdc-list-item__start {
+ align-self: flex-start;
+ margin-top: 8px;
+}
+.mdc-list-item--with-leading-radio.mdc-list-item--with-two-lines .mdc-list-item__primary-text {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+ margin-bottom: -20px;
+}
+.mdc-list-item--with-leading-radio.mdc-list-item--with-two-lines .mdc-list-item__primary-text::before {
+ display: inline-block;
+ width: 0;
+ height: 32px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-list-item--with-leading-radio.mdc-list-item--with-two-lines .mdc-list-item__primary-text::after {
+ display: inline-block;
+ width: 0;
+ height: 20px;
+ content: "";
+ vertical-align: -20px;
+}
+.mdc-list-item--with-leading-radio.mdc-list-item--with-two-lines .mdc-list-item__overline-text {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+ margin-bottom: -20px;
+}
+.mdc-list-item--with-leading-radio.mdc-list-item--with-two-lines .mdc-list-item__overline-text::before {
+ display: inline-block;
+ width: 0;
+ height: 28px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-list-item--with-leading-radio.mdc-list-item--with-two-lines .mdc-list-item__overline-text::after {
+ display: inline-block;
+ width: 0;
+ height: 20px;
+ content: "";
+ vertical-align: -20px;
+}
+.mdc-list-item--with-leading-radio.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+}
+.mdc-list-item--with-leading-radio.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end::before {
+ display: inline-block;
+ width: 0;
+ height: 32px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-list-item--with-leading-radio.mdc-list-item--with-one-line {
+ height: 56px;
+}
+.mdc-list-item--with-leading-radio.mdc-list-item--with-two-lines {
+ height: 72px;
+}
+
+.mdc-list-item--with-leading-switch.mdc-list-item {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: auto;
+}
+[dir=rtl] .mdc-list-item--with-leading-switch.mdc-list-item, .mdc-list-item--with-leading-switch.mdc-list-item[dir=rtl] {
+ /* @noflip */
+ padding-left: auto;
+ /* @noflip */
+ padding-right: 0;
+}
+
+.mdc-list-item--with-leading-switch .mdc-list-item__start {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 16px;
+}
+[dir=rtl] .mdc-list-item--with-leading-switch .mdc-list-item__start, .mdc-list-item--with-leading-switch .mdc-list-item__start[dir=rtl] {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 16px;
+}
+
+.mdc-list-item--with-leading-switch .mdc-list-item__start {
+ width: 36px;
+ height: 20px;
+}
+.mdc-list-item--with-leading-switch.mdc-list-item--with-two-lines .mdc-list-item__start {
+ align-self: flex-start;
+ margin-top: 16px;
+}
+.mdc-list-item--with-leading-switch.mdc-list-item--with-two-lines .mdc-list-item__primary-text {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+ margin-bottom: -20px;
+}
+.mdc-list-item--with-leading-switch.mdc-list-item--with-two-lines .mdc-list-item__primary-text::before {
+ display: inline-block;
+ width: 0;
+ height: 32px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-list-item--with-leading-switch.mdc-list-item--with-two-lines .mdc-list-item__primary-text::after {
+ display: inline-block;
+ width: 0;
+ height: 20px;
+ content: "";
+ vertical-align: -20px;
+}
+.mdc-list-item--with-leading-switch.mdc-list-item--with-two-lines .mdc-list-item__overline-text {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+ margin-bottom: -20px;
+}
+.mdc-list-item--with-leading-switch.mdc-list-item--with-two-lines .mdc-list-item__overline-text::before {
+ display: inline-block;
+ width: 0;
+ height: 28px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-list-item--with-leading-switch.mdc-list-item--with-two-lines .mdc-list-item__overline-text::after {
+ display: inline-block;
+ width: 0;
+ height: 20px;
+ content: "";
+ vertical-align: -20px;
+}
+.mdc-list-item--with-leading-switch.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+}
+.mdc-list-item--with-leading-switch.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end::before {
+ display: inline-block;
+ width: 0;
+ height: 32px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-list-item--with-leading-switch.mdc-list-item--with-one-line {
+ height: 56px;
+}
+.mdc-list-item--with-leading-switch.mdc-list-item--with-two-lines {
+ height: 72px;
+}
+
+.mdc-list-item--with-trailing-icon.mdc-list-item {
+ /* @noflip */
+ padding-left: auto;
+ /* @noflip */
+ padding-right: 0;
+}
+[dir=rtl] .mdc-list-item--with-trailing-icon.mdc-list-item, .mdc-list-item--with-trailing-icon.mdc-list-item[dir=rtl] {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: auto;
+}
+
+.mdc-list-item--with-trailing-icon .mdc-list-item__end {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 16px;
+}
+[dir=rtl] .mdc-list-item--with-trailing-icon .mdc-list-item__end, .mdc-list-item--with-trailing-icon .mdc-list-item__end[dir=rtl] {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 16px;
+}
+
+.mdc-list-item--with-trailing-icon .mdc-list-item__end {
+ width: 24px;
+ height: 24px;
+}
+
+.mdc-list-item--with-trailing-meta.mdc-list-item--with-two-lines .mdc-list-item__end {
+ align-self: flex-start;
+ margin-top: 0;
+}
+.mdc-list-item--with-trailing-meta.mdc-list-item--with-three-lines .mdc-list-item__end {
+ align-self: flex-start;
+ margin-top: 0;
+}
+.mdc-list-item--with-trailing-meta.mdc-list-item {
+ /* @noflip */
+ padding-left: auto;
+ /* @noflip */
+ padding-right: 0;
+}
+[dir=rtl] .mdc-list-item--with-trailing-meta.mdc-list-item, .mdc-list-item--with-trailing-meta.mdc-list-item[dir=rtl] {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: auto;
+}
+
+.mdc-list-item--with-trailing-meta .mdc-list-item__end {
+ /* @noflip */
+ margin-left: 28px;
+ /* @noflip */
+ margin-right: 16px;
+}
+[dir=rtl] .mdc-list-item--with-trailing-meta .mdc-list-item__end, .mdc-list-item--with-trailing-meta .mdc-list-item__end[dir=rtl] {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 28px;
+}
+
+.mdc-list-item--with-trailing-meta.mdc-list-item--with-two-lines .mdc-list-item__end {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+}
+.mdc-list-item--with-trailing-meta.mdc-list-item--with-two-lines .mdc-list-item__end::before {
+ display: inline-block;
+ width: 0;
+ height: 28px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-list-item--with-trailing-meta.mdc-list-item--with-three-lines .mdc-list-item__end {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+}
+.mdc-list-item--with-trailing-meta.mdc-list-item--with-three-lines .mdc-list-item__end::before {
+ display: inline-block;
+ width: 0;
+ height: 28px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-list-item--with-trailing-meta .mdc-list-item__end {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-caption-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.75rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-caption-font-size, 0.75rem);
+ line-height: 1.25rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-caption-line-height, 1.25rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-caption-font-weight, 400);
+ letter-spacing: 0.0333333333em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-caption-letter-spacing, 0.0333333333em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-caption-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-caption-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-caption-text-transform, inherit);
+}
+
+.mdc-list-item--with-trailing-checkbox.mdc-list-item {
+ /* @noflip */
+ padding-left: auto;
+ /* @noflip */
+ padding-right: 0;
+}
+[dir=rtl] .mdc-list-item--with-trailing-checkbox.mdc-list-item, .mdc-list-item--with-trailing-checkbox.mdc-list-item[dir=rtl] {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: auto;
+}
+
+.mdc-list-item--with-trailing-checkbox .mdc-list-item__end {
+ /* @noflip */
+ margin-left: 24px;
+ /* @noflip */
+ margin-right: 8px;
+}
+[dir=rtl] .mdc-list-item--with-trailing-checkbox .mdc-list-item__end, .mdc-list-item--with-trailing-checkbox .mdc-list-item__end[dir=rtl] {
+ /* @noflip */
+ margin-left: 8px;
+ /* @noflip */
+ margin-right: 24px;
+}
+
+.mdc-list-item--with-trailing-checkbox .mdc-list-item__end {
+ width: 40px;
+ height: 40px;
+}
+.mdc-list-item--with-trailing-checkbox.mdc-list-item--with-three-lines .mdc-list-item__end {
+ align-self: flex-start;
+ margin-top: 8px;
+}
+
+.mdc-list-item--with-trailing-radio.mdc-list-item {
+ /* @noflip */
+ padding-left: auto;
+ /* @noflip */
+ padding-right: 0;
+}
+[dir=rtl] .mdc-list-item--with-trailing-radio.mdc-list-item, .mdc-list-item--with-trailing-radio.mdc-list-item[dir=rtl] {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: auto;
+}
+
+.mdc-list-item--with-trailing-radio .mdc-list-item__end {
+ /* @noflip */
+ margin-left: 24px;
+ /* @noflip */
+ margin-right: 8px;
+}
+[dir=rtl] .mdc-list-item--with-trailing-radio .mdc-list-item__end, .mdc-list-item--with-trailing-radio .mdc-list-item__end[dir=rtl] {
+ /* @noflip */
+ margin-left: 8px;
+ /* @noflip */
+ margin-right: 24px;
+}
+
+.mdc-list-item--with-trailing-radio .mdc-list-item__end {
+ width: 40px;
+ height: 40px;
+}
+.mdc-list-item--with-trailing-radio.mdc-list-item--with-three-lines .mdc-list-item__end {
+ align-self: flex-start;
+ margin-top: 8px;
+}
+
+.mdc-list-item--with-trailing-switch.mdc-list-item {
+ /* @noflip */
+ padding-left: auto;
+ /* @noflip */
+ padding-right: 0;
+}
+[dir=rtl] .mdc-list-item--with-trailing-switch.mdc-list-item, .mdc-list-item--with-trailing-switch.mdc-list-item[dir=rtl] {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: auto;
+}
+
+.mdc-list-item--with-trailing-switch .mdc-list-item__end {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 16px;
+}
+[dir=rtl] .mdc-list-item--with-trailing-switch .mdc-list-item__end, .mdc-list-item--with-trailing-switch .mdc-list-item__end[dir=rtl] {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 16px;
+}
+
+.mdc-list-item--with-trailing-switch .mdc-list-item__end {
+ width: 36px;
+ height: 20px;
+}
+.mdc-list-item--with-trailing-switch.mdc-list-item--with-three-lines .mdc-list-item__end {
+ align-self: flex-start;
+ margin-top: 16px;
+}
+
+.mdc-list-item--with-overline.mdc-list-item--with-two-lines .mdc-list-item__primary-text {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+}
+.mdc-list-item--with-overline.mdc-list-item--with-two-lines .mdc-list-item__primary-text::before {
+ display: inline-block;
+ width: 0;
+ height: 20px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-list-item--with-overline.mdc-list-item--with-three-lines .mdc-list-item__primary-text {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+}
+.mdc-list-item--with-overline.mdc-list-item--with-three-lines .mdc-list-item__primary-text::before {
+ display: inline-block;
+ width: 0;
+ height: 20px;
+ content: "";
+ vertical-align: 0;
+}
+
+.mdc-list-item {
+ /* @noflip */
+ padding-left: 16px;
+ /* @noflip */
+ padding-right: 16px;
+}
+[dir=rtl] .mdc-list-item, .mdc-list-item[dir=rtl] {
+ /* @noflip */
+ padding-left: 16px;
+ /* @noflip */
+ padding-right: 16px;
+}
+
+.mdc-list-group .mdc-deprecated-list {
+ padding: 0;
+}
+
+.mdc-list-group__subheader {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-subtitle1-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 1rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-subtitle1-font-size, 1rem);
+ line-height: 1.75rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-subtitle1-line-height, 1.75rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-subtitle1-font-weight, 400);
+ letter-spacing: 0.009375em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-subtitle1-letter-spacing, 0.009375em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-subtitle1-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-subtitle1-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-subtitle1-text-transform, inherit);
+ margin: calc( (3rem - 1.5rem) / 2 ) 16px;
+}
+
+.mdc-list-divider {
+ background-color: rgba(0, 0, 0, 0.12);
+}
+
+.mdc-list-divider {
+ height: 1px;
+ padding: 0;
+ background-clip: content-box;
+}
+
+.mdc-list-divider.mdc-list-divider--with-leading-inset,
+.mdc-list-divider--with-leading-text.mdc-list-divider--with-leading-inset,
+.mdc-list-divider--with-leading-icon.mdc-list-divider--with-leading-inset,
+.mdc-list-divider--with-leading-image.mdc-list-divider--with-leading-inset,
+.mdc-list-divider--with-leading-thumbnail.mdc-list-divider--with-leading-inset,
+.mdc-list-divider--with-leading-avatar.mdc-list-divider--with-leading-inset,
+.mdc-list-divider--with-leading-checkbox.mdc-list-divider--with-leading-inset,
+.mdc-list-divider--with-leading-switch.mdc-list-divider--with-leading-inset,
+.mdc-list-divider--with-leading-radio.mdc-list-divider--with-leading-inset {
+ /* @noflip */
+ padding-left: 16px;
+ /* @noflip */
+ padding-right: auto;
+}
+[dir=rtl] .mdc-list-divider.mdc-list-divider--with-leading-inset, [dir=rtl] .mdc-list-divider--with-leading-text.mdc-list-divider--with-leading-inset, [dir=rtl] .mdc-list-divider--with-leading-icon.mdc-list-divider--with-leading-inset, [dir=rtl] .mdc-list-divider--with-leading-image.mdc-list-divider--with-leading-inset, [dir=rtl] .mdc-list-divider--with-leading-thumbnail.mdc-list-divider--with-leading-inset, [dir=rtl] .mdc-list-divider--with-leading-avatar.mdc-list-divider--with-leading-inset, [dir=rtl] .mdc-list-divider--with-leading-checkbox.mdc-list-divider--with-leading-inset, [dir=rtl] .mdc-list-divider--with-leading-switch.mdc-list-divider--with-leading-inset, [dir=rtl] .mdc-list-divider--with-leading-radio.mdc-list-divider--with-leading-inset, .mdc-list-divider.mdc-list-divider--with-leading-inset[dir=rtl], .mdc-list-divider--with-leading-text.mdc-list-divider--with-leading-inset[dir=rtl], .mdc-list-divider--with-leading-icon.mdc-list-divider--with-leading-inset[dir=rtl], .mdc-list-divider--with-leading-image.mdc-list-divider--with-leading-inset[dir=rtl], .mdc-list-divider--with-leading-thumbnail.mdc-list-divider--with-leading-inset[dir=rtl], .mdc-list-divider--with-leading-avatar.mdc-list-divider--with-leading-inset[dir=rtl], .mdc-list-divider--with-leading-checkbox.mdc-list-divider--with-leading-inset[dir=rtl], .mdc-list-divider--with-leading-switch.mdc-list-divider--with-leading-inset[dir=rtl], .mdc-list-divider--with-leading-radio.mdc-list-divider--with-leading-inset[dir=rtl] {
+ /* @noflip */
+ padding-left: auto;
+ /* @noflip */
+ padding-right: 16px;
+}
+
+.mdc-list-divider.mdc-list-divider--with-trailing-inset,
+.mdc-list-divider--with-leading-text.mdc-list-divider--with-trailing-inset,
+.mdc-list-divider--with-leading-icon.mdc-list-divider--with-trailing-inset,
+.mdc-list-divider--with-leading-image.mdc-list-divider--with-trailing-inset,
+.mdc-list-divider--with-leading-thumbnail.mdc-list-divider--with-trailing-inset,
+.mdc-list-divider--with-leading-avatar.mdc-list-divider--with-trailing-inset,
+.mdc-list-divider--with-leading-checkbox.mdc-list-divider--with-trailing-inset,
+.mdc-list-divider--with-leading-switch.mdc-list-divider--with-trailing-inset,
+.mdc-list-divider--with-leading-radio.mdc-list-divider--with-trailing-inset {
+ /* @noflip */
+ padding-left: auto;
+ /* @noflip */
+ padding-right: 16px;
+}
+[dir=rtl] .mdc-list-divider.mdc-list-divider--with-trailing-inset, [dir=rtl] .mdc-list-divider--with-leading-text.mdc-list-divider--with-trailing-inset, [dir=rtl] .mdc-list-divider--with-leading-icon.mdc-list-divider--with-trailing-inset, [dir=rtl] .mdc-list-divider--with-leading-image.mdc-list-divider--with-trailing-inset, [dir=rtl] .mdc-list-divider--with-leading-thumbnail.mdc-list-divider--with-trailing-inset, [dir=rtl] .mdc-list-divider--with-leading-avatar.mdc-list-divider--with-trailing-inset, [dir=rtl] .mdc-list-divider--with-leading-checkbox.mdc-list-divider--with-trailing-inset, [dir=rtl] .mdc-list-divider--with-leading-switch.mdc-list-divider--with-trailing-inset, [dir=rtl] .mdc-list-divider--with-leading-radio.mdc-list-divider--with-trailing-inset, .mdc-list-divider.mdc-list-divider--with-trailing-inset[dir=rtl], .mdc-list-divider--with-leading-text.mdc-list-divider--with-trailing-inset[dir=rtl], .mdc-list-divider--with-leading-icon.mdc-list-divider--with-trailing-inset[dir=rtl], .mdc-list-divider--with-leading-image.mdc-list-divider--with-trailing-inset[dir=rtl], .mdc-list-divider--with-leading-thumbnail.mdc-list-divider--with-trailing-inset[dir=rtl], .mdc-list-divider--with-leading-avatar.mdc-list-divider--with-trailing-inset[dir=rtl], .mdc-list-divider--with-leading-checkbox.mdc-list-divider--with-trailing-inset[dir=rtl], .mdc-list-divider--with-leading-switch.mdc-list-divider--with-trailing-inset[dir=rtl], .mdc-list-divider--with-leading-radio.mdc-list-divider--with-trailing-inset[dir=rtl] {
+ /* @noflip */
+ padding-left: 16px;
+ /* @noflip */
+ padding-right: auto;
+}
+
+.mdc-list-divider--with-leading-video.mdc-list-divider--with-leading-inset {
+ /* @noflip */
+ padding-left: 0px;
+ /* @noflip */
+ padding-right: auto;
+}
+[dir=rtl] .mdc-list-divider--with-leading-video.mdc-list-divider--with-leading-inset, .mdc-list-divider--with-leading-video.mdc-list-divider--with-leading-inset[dir=rtl] {
+ /* @noflip */
+ padding-left: auto;
+ /* @noflip */
+ padding-right: 0px;
+}
+
+[dir=rtl] .mdc-list-divider, .mdc-list-divider[dir=rtl] {
+ padding: 0;
+}
+
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item {
+ --mdc-ripple-fg-size: 0;
+ --mdc-ripple-left: 0;
+ --mdc-ripple-top: 0;
+ --mdc-ripple-fg-scale: 1;
+ --mdc-ripple-fg-translate-end: 0;
+ --mdc-ripple-fg-translate-start: 0;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ will-change: transform, opacity;
+ --mdc-ripple-fg-size: 0;
+ --mdc-ripple-left: 0;
+ --mdc-ripple-top: 0;
+ --mdc-ripple-fg-scale: 1;
+ --mdc-ripple-fg-translate-end: 0;
+ --mdc-ripple-fg-translate-start: 0;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ will-change: transform, opacity;
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-deprecated-list-item__ripple::before,
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-deprecated-list-item__ripple::after {
+ position: absolute;
+ border-radius: 50%;
+ opacity: 0;
+ pointer-events: none;
+ content: "";
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-deprecated-list-item__ripple::before {
+ transition: opacity 15ms linear, background-color 15ms linear;
+ z-index: 1;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 1);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-deprecated-list-item__ripple::after {
+ z-index: 0;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 0);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded .mdc-deprecated-list-item__ripple::before {
+ -webkit-transform: scale(var(--mdc-ripple-fg-scale, 1));
+ transform: scale(var(--mdc-ripple-fg-scale, 1));
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded .mdc-deprecated-list-item__ripple::after {
+ top: 0;
+ /* @noflip */
+ left: 0;
+ -webkit-transform: scale(0);
+ transform: scale(0);
+ -webkit-transform-origin: center center;
+ transform-origin: center center;
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded--unbounded .mdc-deprecated-list-item__ripple::after {
+ top: var(--mdc-ripple-top, 0);
+ /* @noflip */
+ left: var(--mdc-ripple-left, 0);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded--foreground-activation .mdc-deprecated-list-item__ripple::after {
+ -webkit-animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+ animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded--foreground-deactivation .mdc-deprecated-list-item__ripple::after {
+ -webkit-animation: mdc-ripple-fg-opacity-out 150ms;
+ animation: mdc-ripple-fg-opacity-out 150ms;
+ -webkit-transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+ transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-list-item__ripple::before,
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-list-item__ripple::after {
+ position: absolute;
+ border-radius: 50%;
+ opacity: 0;
+ pointer-events: none;
+ content: "";
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-list-item__ripple::before {
+ transition: opacity 15ms linear, background-color 15ms linear;
+ z-index: 1;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 1);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-list-item__ripple::after {
+ z-index: 0;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 0);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded .mdc-list-item__ripple::before {
+ -webkit-transform: scale(var(--mdc-ripple-fg-scale, 1));
+ transform: scale(var(--mdc-ripple-fg-scale, 1));
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded .mdc-list-item__ripple::after {
+ top: 0;
+ /* @noflip */
+ left: 0;
+ -webkit-transform: scale(0);
+ transform: scale(0);
+ -webkit-transform-origin: center center;
+ transform-origin: center center;
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded--unbounded .mdc-list-item__ripple::after {
+ top: var(--mdc-ripple-top, 0);
+ /* @noflip */
+ left: var(--mdc-ripple-left, 0);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded--foreground-activation .mdc-list-item__ripple::after {
+ -webkit-animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+ animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded--foreground-deactivation .mdc-list-item__ripple::after {
+ -webkit-animation: mdc-ripple-fg-opacity-out 150ms;
+ animation: mdc-ripple-fg-opacity-out 150ms;
+ -webkit-transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+ transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-deprecated-list-item__ripple::before,
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-deprecated-list-item__ripple::after {
+ top: calc(50% - 100%);
+ /* @noflip */
+ left: calc(50% - 100%);
+ width: 200%;
+ height: 200%;
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded .mdc-deprecated-list-item__ripple::after {
+ width: var(--mdc-ripple-fg-size, 100%);
+ height: var(--mdc-ripple-fg-size, 100%);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-list-item__ripple::before,
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-list-item__ripple::after {
+ top: calc(50% - 100%);
+ /* @noflip */
+ left: calc(50% - 100%);
+ width: 200%;
+ height: 200%;
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded .mdc-list-item__ripple::after {
+ width: var(--mdc-ripple-fg-size, 100%);
+ height: var(--mdc-ripple-fg-size, 100%);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-deprecated-list-item__ripple::before, :not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-deprecated-list-item__ripple::after {
+ background-color: #000;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, #000);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item:hover .mdc-deprecated-list-item__ripple::before, :not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-surface--hover .mdc-deprecated-list-item__ripple::before {
+ opacity: 0.04;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.04);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded--background-focused .mdc-deprecated-list-item__ripple::before, :not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item:not(.mdc-ripple-upgraded):focus .mdc-deprecated-list-item__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.12);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item:not(.mdc-ripple-upgraded) .mdc-deprecated-list-item__ripple::after {
+ transition: opacity 150ms linear;
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item:not(.mdc-ripple-upgraded):active .mdc-deprecated-list-item__ripple::after {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-list-item__ripple::before, :not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-list-item__ripple::after {
+ background-color: #000;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, #000);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item:hover .mdc-list-item__ripple::before, :not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-surface--hover .mdc-list-item__ripple::before {
+ opacity: 0.04;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.04);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded--background-focused .mdc-list-item__ripple::before, :not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item:not(.mdc-ripple-upgraded):focus .mdc-list-item__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.12);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item:not(.mdc-ripple-upgraded) .mdc-list-item__ripple::after {
+ transition: opacity 150ms linear;
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item:not(.mdc-ripple-upgraded):active .mdc-list-item__ripple::after {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated .mdc-deprecated-list-item__ripple::before {
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-activated-opacity, 0.12);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated .mdc-deprecated-list-item__ripple::before, :not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated .mdc-deprecated-list-item__ripple::after {
+ background-color: #6200ee;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee));
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated:hover .mdc-deprecated-list-item__ripple::before, :not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated.mdc-ripple-surface--hover .mdc-deprecated-list-item__ripple::before {
+ opacity: 0.16;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.16);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated.mdc-ripple-upgraded--background-focused .mdc-deprecated-list-item__ripple::before, :not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated:not(.mdc-ripple-upgraded):focus .mdc-deprecated-list-item__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.24;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.24);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated:not(.mdc-ripple-upgraded) .mdc-deprecated-list-item__ripple::after {
+ transition: opacity 150ms linear;
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated:not(.mdc-ripple-upgraded):active .mdc-deprecated-list-item__ripple::after {
+ transition-duration: 75ms;
+ opacity: 0.24;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.24);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.24);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated .mdc-list-item__ripple::before {
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-activated-opacity, 0.12);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated .mdc-list-item__ripple::before, :not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated .mdc-list-item__ripple::after {
+ background-color: #6200ee;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee));
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated:hover .mdc-list-item__ripple::before, :not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated.mdc-ripple-surface--hover .mdc-list-item__ripple::before {
+ opacity: 0.16;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.16);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated.mdc-ripple-upgraded--background-focused .mdc-list-item__ripple::before, :not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated:not(.mdc-ripple-upgraded):focus .mdc-list-item__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.24;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.24);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated:not(.mdc-ripple-upgraded) .mdc-list-item__ripple::after {
+ transition: opacity 150ms linear;
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated:not(.mdc-ripple-upgraded):active .mdc-list-item__ripple::after {
+ transition-duration: 75ms;
+ opacity: 0.24;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.24);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.24);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected .mdc-deprecated-list-item__ripple::before {
+ opacity: 0.08;
+ /* @alternate */
+ opacity: var(--mdc-ripple-selected-opacity, 0.08);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected .mdc-deprecated-list-item__ripple::before, :not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected .mdc-deprecated-list-item__ripple::after {
+ background-color: #6200ee;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee));
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected:hover .mdc-deprecated-list-item__ripple::before, :not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected.mdc-ripple-surface--hover .mdc-deprecated-list-item__ripple::before {
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.12);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected.mdc-ripple-upgraded--background-focused .mdc-deprecated-list-item__ripple::before, :not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected:not(.mdc-ripple-upgraded):focus .mdc-deprecated-list-item__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.2;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.2);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected:not(.mdc-ripple-upgraded) .mdc-deprecated-list-item__ripple::after {
+ transition: opacity 150ms linear;
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected:not(.mdc-ripple-upgraded):active .mdc-deprecated-list-item__ripple::after {
+ transition-duration: 75ms;
+ opacity: 0.2;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.2);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.2);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected .mdc-list-item__ripple::before {
+ opacity: 0.08;
+ /* @alternate */
+ opacity: var(--mdc-ripple-selected-opacity, 0.08);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected .mdc-list-item__ripple::before, :not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected .mdc-list-item__ripple::after {
+ background-color: #6200ee;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee));
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected:hover .mdc-list-item__ripple::before, :not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected.mdc-ripple-surface--hover .mdc-list-item__ripple::before {
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.12);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected.mdc-ripple-upgraded--background-focused .mdc-list-item__ripple::before, :not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected:not(.mdc-ripple-upgraded):focus .mdc-list-item__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.2;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.2);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected:not(.mdc-ripple-upgraded) .mdc-list-item__ripple::after {
+ transition: opacity 150ms linear;
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected:not(.mdc-ripple-upgraded):active .mdc-list-item__ripple::after {
+ transition-duration: 75ms;
+ opacity: 0.2;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.2);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.2);
+}
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-deprecated-list-item__ripple,
+:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-list-item__ripple {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+}
+
+.mdc-deprecated-list-item--disabled {
+ --mdc-ripple-fg-size: 0;
+ --mdc-ripple-left: 0;
+ --mdc-ripple-top: 0;
+ --mdc-ripple-fg-scale: 1;
+ --mdc-ripple-fg-translate-end: 0;
+ --mdc-ripple-fg-translate-start: 0;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ will-change: transform, opacity;
+ --mdc-ripple-fg-size: 0;
+ --mdc-ripple-left: 0;
+ --mdc-ripple-top: 0;
+ --mdc-ripple-fg-scale: 1;
+ --mdc-ripple-fg-translate-end: 0;
+ --mdc-ripple-fg-translate-start: 0;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ will-change: transform, opacity;
+}
+.mdc-deprecated-list-item--disabled .mdc-deprecated-list-item__ripple::before,
+.mdc-deprecated-list-item--disabled .mdc-deprecated-list-item__ripple::after {
+ position: absolute;
+ border-radius: 50%;
+ opacity: 0;
+ pointer-events: none;
+ content: "";
+}
+.mdc-deprecated-list-item--disabled .mdc-deprecated-list-item__ripple::before {
+ transition: opacity 15ms linear, background-color 15ms linear;
+ z-index: 1;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 1);
+}
+.mdc-deprecated-list-item--disabled .mdc-deprecated-list-item__ripple::after {
+ z-index: 0;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 0);
+}
+.mdc-deprecated-list-item--disabled.mdc-ripple-upgraded .mdc-deprecated-list-item__ripple::before {
+ -webkit-transform: scale(var(--mdc-ripple-fg-scale, 1));
+ transform: scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-deprecated-list-item--disabled.mdc-ripple-upgraded .mdc-deprecated-list-item__ripple::after {
+ top: 0;
+ /* @noflip */
+ left: 0;
+ -webkit-transform: scale(0);
+ transform: scale(0);
+ -webkit-transform-origin: center center;
+ transform-origin: center center;
+}
+.mdc-deprecated-list-item--disabled.mdc-ripple-upgraded--unbounded .mdc-deprecated-list-item__ripple::after {
+ top: var(--mdc-ripple-top, 0);
+ /* @noflip */
+ left: var(--mdc-ripple-left, 0);
+}
+.mdc-deprecated-list-item--disabled.mdc-ripple-upgraded--foreground-activation .mdc-deprecated-list-item__ripple::after {
+ -webkit-animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+ animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+}
+.mdc-deprecated-list-item--disabled.mdc-ripple-upgraded--foreground-deactivation .mdc-deprecated-list-item__ripple::after {
+ -webkit-animation: mdc-ripple-fg-opacity-out 150ms;
+ animation: mdc-ripple-fg-opacity-out 150ms;
+ -webkit-transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+ transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-deprecated-list-item--disabled .mdc-list-item__ripple::before,
+.mdc-deprecated-list-item--disabled .mdc-list-item__ripple::after {
+ position: absolute;
+ border-radius: 50%;
+ opacity: 0;
+ pointer-events: none;
+ content: "";
+}
+.mdc-deprecated-list-item--disabled .mdc-list-item__ripple::before {
+ transition: opacity 15ms linear, background-color 15ms linear;
+ z-index: 1;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 1);
+}
+.mdc-deprecated-list-item--disabled .mdc-list-item__ripple::after {
+ z-index: 0;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 0);
+}
+.mdc-deprecated-list-item--disabled.mdc-ripple-upgraded .mdc-list-item__ripple::before {
+ -webkit-transform: scale(var(--mdc-ripple-fg-scale, 1));
+ transform: scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-deprecated-list-item--disabled.mdc-ripple-upgraded .mdc-list-item__ripple::after {
+ top: 0;
+ /* @noflip */
+ left: 0;
+ -webkit-transform: scale(0);
+ transform: scale(0);
+ -webkit-transform-origin: center center;
+ transform-origin: center center;
+}
+.mdc-deprecated-list-item--disabled.mdc-ripple-upgraded--unbounded .mdc-list-item__ripple::after {
+ top: var(--mdc-ripple-top, 0);
+ /* @noflip */
+ left: var(--mdc-ripple-left, 0);
+}
+.mdc-deprecated-list-item--disabled.mdc-ripple-upgraded--foreground-activation .mdc-list-item__ripple::after {
+ -webkit-animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+ animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+}
+.mdc-deprecated-list-item--disabled.mdc-ripple-upgraded--foreground-deactivation .mdc-list-item__ripple::after {
+ -webkit-animation: mdc-ripple-fg-opacity-out 150ms;
+ animation: mdc-ripple-fg-opacity-out 150ms;
+ -webkit-transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+ transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-deprecated-list-item--disabled .mdc-deprecated-list-item__ripple::before,
+.mdc-deprecated-list-item--disabled .mdc-deprecated-list-item__ripple::after {
+ top: calc(50% - 100%);
+ /* @noflip */
+ left: calc(50% - 100%);
+ width: 200%;
+ height: 200%;
+}
+.mdc-deprecated-list-item--disabled.mdc-ripple-upgraded .mdc-deprecated-list-item__ripple::after {
+ width: var(--mdc-ripple-fg-size, 100%);
+ height: var(--mdc-ripple-fg-size, 100%);
+}
+.mdc-deprecated-list-item--disabled .mdc-list-item__ripple::before,
+.mdc-deprecated-list-item--disabled .mdc-list-item__ripple::after {
+ top: calc(50% - 100%);
+ /* @noflip */
+ left: calc(50% - 100%);
+ width: 200%;
+ height: 200%;
+}
+.mdc-deprecated-list-item--disabled.mdc-ripple-upgraded .mdc-list-item__ripple::after {
+ width: var(--mdc-ripple-fg-size, 100%);
+ height: var(--mdc-ripple-fg-size, 100%);
+}
+.mdc-deprecated-list-item--disabled .mdc-deprecated-list-item__ripple::before,
+.mdc-deprecated-list-item--disabled .mdc-deprecated-list-item__ripple::after {
+ background-color: #000;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, #000);
+}
+.mdc-deprecated-list-item--disabled .mdc-list-item__ripple::before,
+.mdc-deprecated-list-item--disabled .mdc-list-item__ripple::after {
+ background-color: #000;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, #000);
+}
+.mdc-deprecated-list-item--disabled.mdc-ripple-upgraded--background-focused .mdc-deprecated-list-item__ripple::before, .mdc-deprecated-list-item--disabled:not(.mdc-ripple-upgraded):focus .mdc-deprecated-list-item__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.12);
+}
+.mdc-deprecated-list-item--disabled.mdc-ripple-upgraded--background-focused .mdc-list-item__ripple::before, .mdc-deprecated-list-item--disabled:not(.mdc-ripple-upgraded):focus .mdc-list-item__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.12);
+}
+.mdc-deprecated-list-item--disabled .mdc-deprecated-list-item__ripple,
+.mdc-deprecated-list-item--disabled .mdc-list-item__ripple {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+}
+
+:not(.mdc-list-item--disabled).mdc-list-item {
+ --mdc-ripple-fg-size: 0;
+ --mdc-ripple-left: 0;
+ --mdc-ripple-top: 0;
+ --mdc-ripple-fg-scale: 1;
+ --mdc-ripple-fg-translate-end: 0;
+ --mdc-ripple-fg-translate-start: 0;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ will-change: transform, opacity;
+}
+:not(.mdc-list-item--disabled).mdc-list-item .mdc-list-item__ripple::before,
+:not(.mdc-list-item--disabled).mdc-list-item .mdc-list-item__ripple::after {
+ position: absolute;
+ border-radius: 50%;
+ opacity: 0;
+ pointer-events: none;
+ content: "";
+}
+:not(.mdc-list-item--disabled).mdc-list-item .mdc-list-item__ripple::before {
+ transition: opacity 15ms linear, background-color 15ms linear;
+ z-index: 1;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 1);
+}
+:not(.mdc-list-item--disabled).mdc-list-item .mdc-list-item__ripple::after {
+ z-index: 0;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 0);
+}
+:not(.mdc-list-item--disabled).mdc-list-item.mdc-ripple-upgraded .mdc-list-item__ripple::before {
+ -webkit-transform: scale(var(--mdc-ripple-fg-scale, 1));
+ transform: scale(var(--mdc-ripple-fg-scale, 1));
+}
+:not(.mdc-list-item--disabled).mdc-list-item.mdc-ripple-upgraded .mdc-list-item__ripple::after {
+ top: 0;
+ /* @noflip */
+ left: 0;
+ -webkit-transform: scale(0);
+ transform: scale(0);
+ -webkit-transform-origin: center center;
+ transform-origin: center center;
+}
+:not(.mdc-list-item--disabled).mdc-list-item.mdc-ripple-upgraded--unbounded .mdc-list-item__ripple::after {
+ top: var(--mdc-ripple-top, 0);
+ /* @noflip */
+ left: var(--mdc-ripple-left, 0);
+}
+:not(.mdc-list-item--disabled).mdc-list-item.mdc-ripple-upgraded--foreground-activation .mdc-list-item__ripple::after {
+ -webkit-animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+ animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+}
+:not(.mdc-list-item--disabled).mdc-list-item.mdc-ripple-upgraded--foreground-deactivation .mdc-list-item__ripple::after {
+ -webkit-animation: mdc-ripple-fg-opacity-out 150ms;
+ animation: mdc-ripple-fg-opacity-out 150ms;
+ -webkit-transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+ transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+}
+:not(.mdc-list-item--disabled).mdc-list-item .mdc-list-item__ripple::before,
+:not(.mdc-list-item--disabled).mdc-list-item .mdc-list-item__ripple::after {
+ top: calc(50% - 100%);
+ /* @noflip */
+ left: calc(50% - 100%);
+ width: 200%;
+ height: 200%;
+}
+:not(.mdc-list-item--disabled).mdc-list-item.mdc-ripple-upgraded .mdc-list-item__ripple::after {
+ width: var(--mdc-ripple-fg-size, 100%);
+ height: var(--mdc-ripple-fg-size, 100%);
+}
+:not(.mdc-list-item--disabled).mdc-list-item .mdc-list-item__ripple::before, :not(.mdc-list-item--disabled).mdc-list-item .mdc-list-item__ripple::after {
+ background-color: #000;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, #000);
+}
+:not(.mdc-list-item--disabled).mdc-list-item:hover .mdc-list-item__ripple::before, :not(.mdc-list-item--disabled).mdc-list-item.mdc-ripple-surface--hover .mdc-list-item__ripple::before {
+ opacity: 0.04;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.04);
+}
+:not(.mdc-list-item--disabled).mdc-list-item.mdc-ripple-upgraded--background-focused .mdc-list-item__ripple::before, :not(.mdc-list-item--disabled).mdc-list-item:not(.mdc-ripple-upgraded):focus .mdc-list-item__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.12);
+}
+:not(.mdc-list-item--disabled).mdc-list-item:not(.mdc-ripple-upgraded) .mdc-list-item__ripple::after {
+ transition: opacity 150ms linear;
+}
+:not(.mdc-list-item--disabled).mdc-list-item:not(.mdc-ripple-upgraded):active .mdc-list-item__ripple::after {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+:not(.mdc-list-item--disabled).mdc-list-item.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+:not(.mdc-list-item--disabled).mdc-list-item--activated .mdc-list-item__ripple::before {
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-activated-opacity, 0.12);
+}
+:not(.mdc-list-item--disabled).mdc-list-item--activated .mdc-list-item__ripple::before, :not(.mdc-list-item--disabled).mdc-list-item--activated .mdc-list-item__ripple::after {
+ background-color: #6200ee;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee));
+}
+:not(.mdc-list-item--disabled).mdc-list-item--activated:hover .mdc-list-item__ripple::before, :not(.mdc-list-item--disabled).mdc-list-item--activated.mdc-ripple-surface--hover .mdc-list-item__ripple::before {
+ opacity: 0.16;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.16);
+}
+:not(.mdc-list-item--disabled).mdc-list-item--activated.mdc-ripple-upgraded--background-focused .mdc-list-item__ripple::before, :not(.mdc-list-item--disabled).mdc-list-item--activated:not(.mdc-ripple-upgraded):focus .mdc-list-item__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.24;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.24);
+}
+:not(.mdc-list-item--disabled).mdc-list-item--activated:not(.mdc-ripple-upgraded) .mdc-list-item__ripple::after {
+ transition: opacity 150ms linear;
+}
+:not(.mdc-list-item--disabled).mdc-list-item--activated:not(.mdc-ripple-upgraded):active .mdc-list-item__ripple::after {
+ transition-duration: 75ms;
+ opacity: 0.24;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.24);
+}
+:not(.mdc-list-item--disabled).mdc-list-item--activated.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.24);
+}
+:not(.mdc-list-item--disabled).mdc-list-item--selected .mdc-list-item__ripple::before {
+ opacity: 0.08;
+ /* @alternate */
+ opacity: var(--mdc-ripple-selected-opacity, 0.08);
+}
+:not(.mdc-list-item--disabled).mdc-list-item--selected .mdc-list-item__ripple::before, :not(.mdc-list-item--disabled).mdc-list-item--selected .mdc-list-item__ripple::after {
+ background-color: #6200ee;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee));
+}
+:not(.mdc-list-item--disabled).mdc-list-item--selected:hover .mdc-list-item__ripple::before, :not(.mdc-list-item--disabled).mdc-list-item--selected.mdc-ripple-surface--hover .mdc-list-item__ripple::before {
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.12);
+}
+:not(.mdc-list-item--disabled).mdc-list-item--selected.mdc-ripple-upgraded--background-focused .mdc-list-item__ripple::before, :not(.mdc-list-item--disabled).mdc-list-item--selected:not(.mdc-ripple-upgraded):focus .mdc-list-item__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.2;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.2);
+}
+:not(.mdc-list-item--disabled).mdc-list-item--selected:not(.mdc-ripple-upgraded) .mdc-list-item__ripple::after {
+ transition: opacity 150ms linear;
+}
+:not(.mdc-list-item--disabled).mdc-list-item--selected:not(.mdc-ripple-upgraded):active .mdc-list-item__ripple::after {
+ transition-duration: 75ms;
+ opacity: 0.2;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.2);
+}
+:not(.mdc-list-item--disabled).mdc-list-item--selected.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.2);
+}
+:not(.mdc-list-item--disabled).mdc-list-item .mdc-list-item__ripple {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+}
+
+.mdc-list-item--disabled {
+ --mdc-ripple-fg-size: 0;
+ --mdc-ripple-left: 0;
+ --mdc-ripple-top: 0;
+ --mdc-ripple-fg-scale: 1;
+ --mdc-ripple-fg-translate-end: 0;
+ --mdc-ripple-fg-translate-start: 0;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ will-change: transform, opacity;
+}
+.mdc-list-item--disabled .mdc-list-item__ripple::before,
+.mdc-list-item--disabled .mdc-list-item__ripple::after {
+ position: absolute;
+ border-radius: 50%;
+ opacity: 0;
+ pointer-events: none;
+ content: "";
+}
+.mdc-list-item--disabled .mdc-list-item__ripple::before {
+ transition: opacity 15ms linear, background-color 15ms linear;
+ z-index: 1;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 1);
+}
+.mdc-list-item--disabled .mdc-list-item__ripple::after {
+ z-index: 0;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 0);
+}
+.mdc-list-item--disabled.mdc-ripple-upgraded .mdc-list-item__ripple::before {
+ -webkit-transform: scale(var(--mdc-ripple-fg-scale, 1));
+ transform: scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-list-item--disabled.mdc-ripple-upgraded .mdc-list-item__ripple::after {
+ top: 0;
+ /* @noflip */
+ left: 0;
+ -webkit-transform: scale(0);
+ transform: scale(0);
+ -webkit-transform-origin: center center;
+ transform-origin: center center;
+}
+.mdc-list-item--disabled.mdc-ripple-upgraded--unbounded .mdc-list-item__ripple::after {
+ top: var(--mdc-ripple-top, 0);
+ /* @noflip */
+ left: var(--mdc-ripple-left, 0);
+}
+.mdc-list-item--disabled.mdc-ripple-upgraded--foreground-activation .mdc-list-item__ripple::after {
+ -webkit-animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+ animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+}
+.mdc-list-item--disabled.mdc-ripple-upgraded--foreground-deactivation .mdc-list-item__ripple::after {
+ -webkit-animation: mdc-ripple-fg-opacity-out 150ms;
+ animation: mdc-ripple-fg-opacity-out 150ms;
+ -webkit-transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+ transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-list-item--disabled .mdc-list-item__ripple::before,
+.mdc-list-item--disabled .mdc-list-item__ripple::after {
+ top: calc(50% - 100%);
+ /* @noflip */
+ left: calc(50% - 100%);
+ width: 200%;
+ height: 200%;
+}
+.mdc-list-item--disabled.mdc-ripple-upgraded .mdc-list-item__ripple::after {
+ width: var(--mdc-ripple-fg-size, 100%);
+ height: var(--mdc-ripple-fg-size, 100%);
+}
+.mdc-list-item--disabled .mdc-list-item__ripple::before,
+.mdc-list-item--disabled .mdc-list-item__ripple::after {
+ background-color: #000;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, #000);
+}
+.mdc-list-item--disabled.mdc-ripple-upgraded--background-focused .mdc-list-item__ripple::before, .mdc-list-item--disabled:not(.mdc-ripple-upgraded):focus .mdc-list-item__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.12);
+}
+.mdc-list-item--disabled .mdc-list-item__ripple {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+}
+
+.mdc-menu {
+ min-width: 112px;
+ /* @alternate */
+ min-width: var(--mdc-menu-min-width, 112px);
+}
+.mdc-menu .mdc-deprecated-list-item__meta {
+ color: rgba(0, 0, 0, 0.87);
+}
+.mdc-menu .mdc-deprecated-list-item__graphic {
+ color: rgba(0, 0, 0, 0.87);
+}
+.mdc-menu .mdc-deprecated-list {
+ color: rgba(0, 0, 0, 0.87);
+ /* @alternate */
+ position: relative;
+}
+.mdc-menu .mdc-deprecated-list .mdc-elevation-overlay {
+ width: 100%;
+ height: 100%;
+ top: 0;
+ /* @noflip */
+ left: 0;
+}
+.mdc-menu .mdc-deprecated-list-divider {
+ margin: 8px 0;
+}
+.mdc-menu .mdc-deprecated-list-item {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+.mdc-menu .mdc-deprecated-list-item--disabled {
+ cursor: auto;
+}
+.mdc-menu a.mdc-deprecated-list-item .mdc-deprecated-list-item__text,
+.mdc-menu a.mdc-deprecated-list-item .mdc-deprecated-list-item__graphic {
+ pointer-events: none;
+}
+
+.mdc-menu__selection-group {
+ padding: 0;
+ fill: currentColor;
+}
+.mdc-menu__selection-group .mdc-deprecated-list-item {
+ /* @noflip */
+ padding-left: 56px;
+ /* @noflip */
+ padding-right: 16px;
+}
+[dir=rtl] .mdc-menu__selection-group .mdc-deprecated-list-item, .mdc-menu__selection-group .mdc-deprecated-list-item[dir=rtl] {
+ /* @noflip */
+ padding-left: 16px;
+ /* @noflip */
+ padding-right: 56px;
+}
+
+.mdc-menu__selection-group .mdc-menu__selection-group-icon {
+ /* @noflip */
+ left: 16px;
+ /* @noflip */
+ right: initial;
+ display: none;
+ position: absolute;
+ top: 50%;
+ -webkit-transform: translateY(-50%);
+ transform: translateY(-50%);
+}
+[dir=rtl] .mdc-menu__selection-group .mdc-menu__selection-group-icon, .mdc-menu__selection-group .mdc-menu__selection-group-icon[dir=rtl] {
+ /* @noflip */
+ left: initial;
+ /* @noflip */
+ right: 16px;
+}
+
+.mdc-menu-item--selected .mdc-menu__selection-group-icon {
+ display: inline;
+}
+
+.mdc-menu-surface {
+ display: none;
+ position: absolute;
+ box-sizing: border-box;
+ max-width: calc(100vw - 32px);
+ /* @alternate */
+ max-width: var(--mdc-menu-max-width, calc(100vw - 32px));
+ max-height: calc(100vh - 32px);
+ /* @alternate */
+ max-height: var(--mdc-menu-max-height, calc(100vh - 32px));
+ margin: 0;
+ padding: 0;
+ -webkit-transform: scale(1);
+ transform: scale(1);
+ -webkit-transform-origin: top left;
+ transform-origin: top left;
+ opacity: 0;
+ overflow: auto;
+ will-change: transform, opacity;
+ z-index: 8;
+ transition: opacity 0.03s linear, height 250ms cubic-bezier(0, 0, 0.2, 1), -webkit-transform 0.12s cubic-bezier(0, 0, 0.2, 1);
+ transition: opacity 0.03s linear, transform 0.12s cubic-bezier(0, 0, 0.2, 1), height 250ms cubic-bezier(0, 0, 0.2, 1);
+ transition: opacity 0.03s linear, transform 0.12s cubic-bezier(0, 0, 0.2, 1), height 250ms cubic-bezier(0, 0, 0.2, 1), -webkit-transform 0.12s cubic-bezier(0, 0, 0.2, 1);
+ /* @alternate */
+ box-shadow: 0px 5px 5px -3px rgba(0, 0, 0, 0.2), 0px 8px 10px 1px rgba(0, 0, 0, 0.14), 0px 3px 14px 2px rgba(0, 0, 0, 0.12);
+ background-color: #fff;
+ /* @alternate */
+ background-color: var(--mdc-theme-surface, #fff);
+ color: #000;
+ /* @alternate */
+ color: var(--mdc-theme-on-surface, #000);
+ border-radius: 4px;
+ /* @alternate */
+ border-radius: var(--mdc-shape-medium, 4px);
+ /* @noflip */
+ transform-origin-left: top left;
+ /* @noflip */
+ transform-origin-right: top right;
+}
+.mdc-menu-surface:focus {
+ outline: none;
+}
+.mdc-menu-surface--animating-open {
+ display: inline-block;
+ -webkit-transform: scale(0.8);
+ transform: scale(0.8);
+ opacity: 0;
+}
+.mdc-menu-surface--open {
+ display: inline-block;
+ -webkit-transform: scale(1);
+ transform: scale(1);
+ opacity: 1;
+}
+.mdc-menu-surface--animating-closed {
+ display: inline-block;
+ opacity: 0;
+ transition: opacity 0.075s linear;
+}
+[dir=rtl] .mdc-menu-surface, .mdc-menu-surface[dir=rtl] {
+ /* @noflip */
+ transform-origin-left: top right;
+ /* @noflip */
+ transform-origin-right: top left;
+}
+
+.mdc-menu-surface--anchor {
+ position: relative;
+ overflow: visible;
+}
+
+.mdc-menu-surface--fixed {
+ position: fixed;
+}
+
+.mdc-menu-surface--fullwidth {
+ width: 100%;
+}
+
+.mdc-radio {
+ padding: calc((40px - 20px) / 2);
+}
+.mdc-radio .mdc-radio__native-control:enabled:not(:checked) + .mdc-radio__background .mdc-radio__outer-circle {
+ border-color: rgba(0, 0, 0, 0.54);
+}
+.mdc-radio .mdc-radio__native-control:enabled:checked + .mdc-radio__background .mdc-radio__outer-circle {
+ border-color: #018786;
+ /* @alternate */
+ border-color: var(--mdc-theme-secondary, #018786);
+}
+.mdc-radio .mdc-radio__native-control:enabled + .mdc-radio__background .mdc-radio__inner-circle {
+ border-color: #018786;
+ /* @alternate */
+ border-color: var(--mdc-theme-secondary, #018786);
+}
+.mdc-radio [aria-disabled=true] .mdc-radio__native-control:not(:checked) + .mdc-radio__background .mdc-radio__outer-circle,
+.mdc-radio .mdc-radio__native-control:disabled:not(:checked) + .mdc-radio__background .mdc-radio__outer-circle {
+ border-color: rgba(0, 0, 0, 0.38);
+}
+.mdc-radio [aria-disabled=true] .mdc-radio__native-control:checked + .mdc-radio__background .mdc-radio__outer-circle,
+.mdc-radio .mdc-radio__native-control:disabled:checked + .mdc-radio__background .mdc-radio__outer-circle {
+ border-color: rgba(0, 0, 0, 0.38);
+}
+.mdc-radio [aria-disabled=true] .mdc-radio__native-control + .mdc-radio__background .mdc-radio__inner-circle,
+.mdc-radio .mdc-radio__native-control:disabled + .mdc-radio__background .mdc-radio__inner-circle {
+ border-color: rgba(0, 0, 0, 0.38);
+}
+.mdc-radio .mdc-radio__background::before {
+ background-color: #018786;
+ /* @alternate */
+ background-color: var(--mdc-theme-secondary, #018786);
+}
+.mdc-radio .mdc-radio__background::before {
+ top: calc(-1 * (40px - 20px) / 2);
+ left: calc(-1 * (40px - 20px) / 2);
+ width: 40px;
+ height: 40px;
+}
+.mdc-radio .mdc-radio__native-control {
+ top: calc((40px - 40px) / 2);
+ right: calc((40px - 40px) / 2);
+ left: calc((40px - 40px) / 2);
+ width: 40px;
+ height: 40px;
+}
+@media screen and (forced-colors: active), (-ms-high-contrast: active) {
+ .mdc-radio [aria-disabled=true] .mdc-radio__native-control:not(:checked) + .mdc-radio__background .mdc-radio__outer-circle,
+.mdc-radio .mdc-radio__native-control:disabled:not(:checked) + .mdc-radio__background .mdc-radio__outer-circle {
+ border-color: GrayText;
+ }
+ .mdc-radio [aria-disabled=true] .mdc-radio__native-control:checked + .mdc-radio__background .mdc-radio__outer-circle,
+.mdc-radio .mdc-radio__native-control:disabled:checked + .mdc-radio__background .mdc-radio__outer-circle {
+ border-color: GrayText;
+ }
+ .mdc-radio [aria-disabled=true] .mdc-radio__native-control + .mdc-radio__background .mdc-radio__inner-circle,
+.mdc-radio .mdc-radio__native-control:disabled + .mdc-radio__background .mdc-radio__inner-circle {
+ border-color: GrayText;
+ }
+}
+
+.mdc-radio {
+ display: inline-block;
+ position: relative;
+ flex: 0 0 auto;
+ box-sizing: content-box;
+ width: 20px;
+ height: 20px;
+ cursor: pointer;
+ /* @alternate */
+ will-change: opacity, transform, border-color, color;
+}
+.mdc-radio__background {
+ display: inline-block;
+ position: relative;
+ box-sizing: border-box;
+ width: 20px;
+ height: 20px;
+}
+.mdc-radio__background::before {
+ position: absolute;
+ -webkit-transform: scale(0, 0);
+ transform: scale(0, 0);
+ border-radius: 50%;
+ opacity: 0;
+ pointer-events: none;
+ content: "";
+ transition: opacity 120ms 0ms cubic-bezier(0.4, 0, 0.6, 1), -webkit-transform 120ms 0ms cubic-bezier(0.4, 0, 0.6, 1);
+ transition: opacity 120ms 0ms cubic-bezier(0.4, 0, 0.6, 1), transform 120ms 0ms cubic-bezier(0.4, 0, 0.6, 1);
+ transition: opacity 120ms 0ms cubic-bezier(0.4, 0, 0.6, 1), transform 120ms 0ms cubic-bezier(0.4, 0, 0.6, 1), -webkit-transform 120ms 0ms cubic-bezier(0.4, 0, 0.6, 1);
+}
+.mdc-radio__outer-circle {
+ position: absolute;
+ top: 0;
+ left: 0;
+ box-sizing: border-box;
+ width: 100%;
+ height: 100%;
+ border-width: 2px;
+ border-style: solid;
+ border-radius: 50%;
+ transition: border-color 120ms 0ms cubic-bezier(0.4, 0, 0.6, 1);
+}
+.mdc-radio__inner-circle {
+ position: absolute;
+ top: 0;
+ left: 0;
+ box-sizing: border-box;
+ width: 100%;
+ height: 100%;
+ -webkit-transform: scale(0, 0);
+ transform: scale(0, 0);
+ border-width: 10px;
+ border-style: solid;
+ border-radius: 50%;
+ transition: border-color 120ms 0ms cubic-bezier(0.4, 0, 0.6, 1), -webkit-transform 120ms 0ms cubic-bezier(0.4, 0, 0.6, 1);
+ transition: transform 120ms 0ms cubic-bezier(0.4, 0, 0.6, 1), border-color 120ms 0ms cubic-bezier(0.4, 0, 0.6, 1);
+ transition: transform 120ms 0ms cubic-bezier(0.4, 0, 0.6, 1), border-color 120ms 0ms cubic-bezier(0.4, 0, 0.6, 1), -webkit-transform 120ms 0ms cubic-bezier(0.4, 0, 0.6, 1);
+}
+.mdc-radio__native-control {
+ position: absolute;
+ margin: 0;
+ padding: 0;
+ opacity: 0;
+ cursor: inherit;
+ z-index: 1;
+}
+.mdc-radio--touch {
+ margin-top: 4px;
+ margin-bottom: 4px;
+ margin-right: 4px;
+ margin-left: 4px;
+}
+.mdc-radio--touch .mdc-radio__native-control {
+ top: calc((40px - 48px) / 2);
+ right: calc((40px - 48px) / 2);
+ left: calc((40px - 48px) / 2);
+ width: 48px;
+ height: 48px;
+}
+
+.mdc-radio__native-control:checked + .mdc-radio__background,
+.mdc-radio__native-control:disabled + .mdc-radio__background {
+ transition: opacity 120ms 0ms cubic-bezier(0, 0, 0.2, 1), -webkit-transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1);
+ transition: opacity 120ms 0ms cubic-bezier(0, 0, 0.2, 1), transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1);
+ transition: opacity 120ms 0ms cubic-bezier(0, 0, 0.2, 1), transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1), -webkit-transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1);
+}
+.mdc-radio__native-control:checked + .mdc-radio__background .mdc-radio__outer-circle,
+.mdc-radio__native-control:disabled + .mdc-radio__background .mdc-radio__outer-circle {
+ transition: border-color 120ms 0ms cubic-bezier(0, 0, 0.2, 1);
+}
+.mdc-radio__native-control:checked + .mdc-radio__background .mdc-radio__inner-circle,
+.mdc-radio__native-control:disabled + .mdc-radio__background .mdc-radio__inner-circle {
+ transition: border-color 120ms 0ms cubic-bezier(0, 0, 0.2, 1), -webkit-transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1);
+ transition: transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1), border-color 120ms 0ms cubic-bezier(0, 0, 0.2, 1);
+ transition: transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1), border-color 120ms 0ms cubic-bezier(0, 0, 0.2, 1), -webkit-transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1);
+}
+
+.mdc-radio--disabled {
+ cursor: default;
+ pointer-events: none;
+}
+
+.mdc-radio__native-control:checked + .mdc-radio__background .mdc-radio__inner-circle {
+ -webkit-transform: scale(0.5);
+ transform: scale(0.5);
+ transition: border-color 120ms 0ms cubic-bezier(0, 0, 0.2, 1), -webkit-transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1);
+ transition: transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1), border-color 120ms 0ms cubic-bezier(0, 0, 0.2, 1);
+ transition: transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1), border-color 120ms 0ms cubic-bezier(0, 0, 0.2, 1), -webkit-transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1);
+}
+
+.mdc-radio__native-control:disabled + .mdc-radio__background,
+[aria-disabled=true] .mdc-radio__native-control + .mdc-radio__background {
+ cursor: default;
+}
+
+.mdc-radio__native-control:focus + .mdc-radio__background::before {
+ -webkit-transform: scale(1);
+ transform: scale(1);
+ opacity: 0.12;
+ transition: opacity 120ms 0ms cubic-bezier(0, 0, 0.2, 1), -webkit-transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1);
+ transition: opacity 120ms 0ms cubic-bezier(0, 0, 0.2, 1), transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1);
+ transition: opacity 120ms 0ms cubic-bezier(0, 0, 0.2, 1), transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1), -webkit-transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1);
+}
+
+.mdc-radio {
+ --mdc-ripple-fg-size: 0;
+ --mdc-ripple-left: 0;
+ --mdc-ripple-top: 0;
+ --mdc-ripple-fg-scale: 1;
+ --mdc-ripple-fg-translate-end: 0;
+ --mdc-ripple-fg-translate-start: 0;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ will-change: transform, opacity;
+}
+.mdc-radio .mdc-radio__ripple::before,
+.mdc-radio .mdc-radio__ripple::after {
+ position: absolute;
+ border-radius: 50%;
+ opacity: 0;
+ pointer-events: none;
+ content: "";
+}
+.mdc-radio .mdc-radio__ripple::before {
+ transition: opacity 15ms linear, background-color 15ms linear;
+ z-index: 1;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 1);
+}
+.mdc-radio .mdc-radio__ripple::after {
+ z-index: 0;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 0);
+}
+.mdc-radio.mdc-ripple-upgraded .mdc-radio__ripple::before {
+ -webkit-transform: scale(var(--mdc-ripple-fg-scale, 1));
+ transform: scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-radio.mdc-ripple-upgraded .mdc-radio__ripple::after {
+ top: 0;
+ /* @noflip */
+ left: 0;
+ -webkit-transform: scale(0);
+ transform: scale(0);
+ -webkit-transform-origin: center center;
+ transform-origin: center center;
+}
+.mdc-radio.mdc-ripple-upgraded--unbounded .mdc-radio__ripple::after {
+ top: var(--mdc-ripple-top, 0);
+ /* @noflip */
+ left: var(--mdc-ripple-left, 0);
+}
+.mdc-radio.mdc-ripple-upgraded--foreground-activation .mdc-radio__ripple::after {
+ -webkit-animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+ animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+}
+.mdc-radio.mdc-ripple-upgraded--foreground-deactivation .mdc-radio__ripple::after {
+ -webkit-animation: mdc-ripple-fg-opacity-out 150ms;
+ animation: mdc-ripple-fg-opacity-out 150ms;
+ -webkit-transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+ transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-radio .mdc-radio__ripple::before,
+.mdc-radio .mdc-radio__ripple::after {
+ top: calc(50% - 50%);
+ /* @noflip */
+ left: calc(50% - 50%);
+ width: 100%;
+ height: 100%;
+}
+.mdc-radio.mdc-ripple-upgraded .mdc-radio__ripple::before,
+.mdc-radio.mdc-ripple-upgraded .mdc-radio__ripple::after {
+ top: var(--mdc-ripple-top, calc(50% - 50%));
+ /* @noflip */
+ left: var(--mdc-ripple-left, calc(50% - 50%));
+ width: var(--mdc-ripple-fg-size, 100%);
+ height: var(--mdc-ripple-fg-size, 100%);
+}
+.mdc-radio.mdc-ripple-upgraded .mdc-radio__ripple::after {
+ width: var(--mdc-ripple-fg-size, 100%);
+ height: var(--mdc-ripple-fg-size, 100%);
+}
+.mdc-radio .mdc-radio__ripple::before, .mdc-radio .mdc-radio__ripple::after {
+ background-color: #018786;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, var(--mdc-theme-secondary, #018786));
+}
+.mdc-radio:hover .mdc-radio__ripple::before, .mdc-radio.mdc-ripple-surface--hover .mdc-radio__ripple::before {
+ opacity: 0.04;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.04);
+}
+.mdc-radio.mdc-ripple-upgraded--background-focused .mdc-radio__ripple::before, .mdc-radio:not(.mdc-ripple-upgraded):focus .mdc-radio__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.12);
+}
+.mdc-radio:not(.mdc-ripple-upgraded) .mdc-radio__ripple::after {
+ transition: opacity 150ms linear;
+}
+.mdc-radio:not(.mdc-ripple-upgraded):active .mdc-radio__ripple::after {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-radio.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-radio.mdc-ripple-upgraded .mdc-radio__background::before, .mdc-radio.mdc-ripple-upgraded--background-focused .mdc-radio__background::before {
+ content: none;
+}
+
+.mdc-radio__ripple {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+}
+
+.mdc-ripple-surface {
+ --mdc-ripple-fg-size: 0;
+ --mdc-ripple-left: 0;
+ --mdc-ripple-top: 0;
+ --mdc-ripple-fg-scale: 1;
+ --mdc-ripple-fg-translate-end: 0;
+ --mdc-ripple-fg-translate-start: 0;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ will-change: transform, opacity;
+ position: relative;
+ outline: none;
+ overflow: hidden;
+}
+.mdc-ripple-surface::before, .mdc-ripple-surface::after {
+ position: absolute;
+ border-radius: 50%;
+ opacity: 0;
+ pointer-events: none;
+ content: "";
+}
+.mdc-ripple-surface::before {
+ transition: opacity 15ms linear, background-color 15ms linear;
+ z-index: 1;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 1);
+}
+.mdc-ripple-surface::after {
+ z-index: 0;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 0);
+}
+.mdc-ripple-surface.mdc-ripple-upgraded::before {
+ -webkit-transform: scale(var(--mdc-ripple-fg-scale, 1));
+ transform: scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-ripple-surface.mdc-ripple-upgraded::after {
+ top: 0;
+ /* @noflip */
+ left: 0;
+ -webkit-transform: scale(0);
+ transform: scale(0);
+ -webkit-transform-origin: center center;
+ transform-origin: center center;
+}
+.mdc-ripple-surface.mdc-ripple-upgraded--unbounded::after {
+ top: var(--mdc-ripple-top, 0);
+ /* @noflip */
+ left: var(--mdc-ripple-left, 0);
+}
+.mdc-ripple-surface.mdc-ripple-upgraded--foreground-activation::after {
+ -webkit-animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+ animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+}
+.mdc-ripple-surface.mdc-ripple-upgraded--foreground-deactivation::after {
+ -webkit-animation: mdc-ripple-fg-opacity-out 150ms;
+ animation: mdc-ripple-fg-opacity-out 150ms;
+ -webkit-transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+ transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-ripple-surface::before, .mdc-ripple-surface::after {
+ top: calc(50% - 100%);
+ /* @noflip */
+ left: calc(50% - 100%);
+ width: 200%;
+ height: 200%;
+}
+.mdc-ripple-surface.mdc-ripple-upgraded::after {
+ width: var(--mdc-ripple-fg-size, 100%);
+ height: var(--mdc-ripple-fg-size, 100%);
+}
+
+.mdc-ripple-surface[data-mdc-ripple-is-unbounded],
+.mdc-ripple-upgraded--unbounded {
+ overflow: visible;
+}
+.mdc-ripple-surface[data-mdc-ripple-is-unbounded]::before, .mdc-ripple-surface[data-mdc-ripple-is-unbounded]::after,
+.mdc-ripple-upgraded--unbounded::before,
+.mdc-ripple-upgraded--unbounded::after {
+ top: calc(50% - 50%);
+ /* @noflip */
+ left: calc(50% - 50%);
+ width: 100%;
+ height: 100%;
+}
+.mdc-ripple-surface[data-mdc-ripple-is-unbounded].mdc-ripple-upgraded::before, .mdc-ripple-surface[data-mdc-ripple-is-unbounded].mdc-ripple-upgraded::after,
+.mdc-ripple-upgraded--unbounded.mdc-ripple-upgraded::before,
+.mdc-ripple-upgraded--unbounded.mdc-ripple-upgraded::after {
+ top: var(--mdc-ripple-top, calc(50% - 50%));
+ /* @noflip */
+ left: var(--mdc-ripple-left, calc(50% - 50%));
+ width: var(--mdc-ripple-fg-size, 100%);
+ height: var(--mdc-ripple-fg-size, 100%);
+}
+.mdc-ripple-surface[data-mdc-ripple-is-unbounded].mdc-ripple-upgraded::after,
+.mdc-ripple-upgraded--unbounded.mdc-ripple-upgraded::after {
+ width: var(--mdc-ripple-fg-size, 100%);
+ height: var(--mdc-ripple-fg-size, 100%);
+}
+
+.mdc-ripple-surface::before, .mdc-ripple-surface::after {
+ background-color: #000;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, #000);
+}
+.mdc-ripple-surface:hover::before, .mdc-ripple-surface.mdc-ripple-surface--hover::before {
+ opacity: 0.04;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.04);
+}
+.mdc-ripple-surface.mdc-ripple-upgraded--background-focused::before, .mdc-ripple-surface:not(.mdc-ripple-upgraded):focus::before {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.12);
+}
+.mdc-ripple-surface:not(.mdc-ripple-upgraded)::after {
+ transition: opacity 150ms linear;
+}
+.mdc-ripple-surface:not(.mdc-ripple-upgraded):active::after {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-ripple-surface.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+
+.mdc-segmented-button {
+ display: inline-block;
+ font-size: 0;
+}
+
+.mdc-segmented-button__segment {
+ border-color: rgba(0, 0, 0, 0.12);
+ /* @alternate */
+ border-color: var(--mdc-segmented-button-outline-color, rgba(0, 0, 0, 0.12));
+}
+
+.mdc-segmented-button__segment {
+ color: rgba(0, 0, 0, 0.6);
+ /* @alternate */
+ color: var(--mdc-segmented-button-unselected-ink-color, rgba(0, 0, 0, 0.6));
+}
+
+.mdc-segmented-button__segment {
+ background-color: white;
+ /* @alternate */
+ background-color: var(--mdc-segmented-button-unselected-container-fill-color, white);
+}
+
+.mdc-segmented-button__segment--selected {
+ color: #6200ee;
+ /* @alternate */
+ color: var(--mdc-segmented-button-selected-ink-color, #6200ee);
+}
+
+.mdc-segmented-button__segment--selected {
+ background-color: rgba(98, 0, 238, 0.08);
+ /* @alternate */
+ background-color: var(--mdc-segmented-button-selected-container-fill-color, rgba(98, 0, 238, 0.08));
+}
+
+.mdc-segmented-button__segment {
+ /* @alternate */
+ position: relative;
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-button-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.875rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-button-font-size, 0.875rem);
+ line-height: 2.25rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-button-line-height, 2.25rem);
+ font-weight: 500;
+ /* @alternate */
+ font-weight: var(--mdc-typography-button-font-weight, 500);
+ letter-spacing: 0.0892857143em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-button-letter-spacing, 0.0892857143em);
+ text-decoration: none;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-button-text-decoration, none);
+ text-decoration: var(--mdc-typography-button-text-decoration, none);
+ text-transform: uppercase;
+ /* @alternate */
+ text-transform: var(--mdc-typography-button-text-transform, uppercase);
+ display: inline-flex;
+ vertical-align: top;
+ align-items: center;
+ height: 36px;
+ min-width: 48px;
+ padding: 0 12px;
+ border-width: 1px 0 1px 1px;
+}
+.mdc-segmented-button__segment .mdc-elevation-overlay {
+ width: 100%;
+ height: 100%;
+ top: 0;
+ /* @noflip */
+ left: 0;
+}
+.mdc-segmented-button__segment:hover {
+ cursor: pointer;
+}
+.mdc-segmented-button__segment:focus {
+ outline-width: 0;
+}
+.mdc-segmented-button__segment:first-child {
+ border-radius: 4px 0 0 4px;
+}
+.mdc-segmented-button__segment:last-child {
+ border-right-width: 1px;
+ border-radius: 0 4px 4px 0;
+}
+.mdc-segmented-button__segment .mdc-segmented-button__segment__touch {
+ position: absolute;
+ top: 50%;
+ height: 48px;
+ left: 0;
+ right: 0;
+ -webkit-transform: translateY(-50%);
+ transform: translateY(-50%);
+}
+.mdc-segmented-button__segment .mdc-segmented-button__segment--touch {
+ margin-top: 0px;
+ margin-bottom: 0px;
+}
+
+.mdc-touch-target-wrapper .mdc-segmented-button__segment {
+ border-radius: 0;
+ border-right-width: 0;
+}
+.mdc-touch-target-wrapper:first-child .mdc-segmented-button__segment {
+ border-radius: 4px 0 0 4px;
+}
+.mdc-touch-target-wrapper:last-child .mdc-segmented-button__segment {
+ border-right-width: 1px;
+ border-radius: 0 4px 4px 0;
+}
+
+.mdc-segmented-button__icon {
+ width: 24px;
+ font-size: 18px;
+}
+
+.mdc-segmented-button__icon + .mdc-segmented-button__label {
+ padding-left: 6px;
+}
+
+.mdc-segmented-button__segment {
+ --mdc-ripple-fg-size: 0;
+ --mdc-ripple-left: 0;
+ --mdc-ripple-top: 0;
+ --mdc-ripple-fg-scale: 1;
+ --mdc-ripple-fg-translate-end: 0;
+ --mdc-ripple-fg-translate-start: 0;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ will-change: transform, opacity;
+ overflow: hidden;
+}
+.mdc-segmented-button__segment .mdc-segmented-button__ripple::before,
+.mdc-segmented-button__segment .mdc-segmented-button__ripple::after {
+ position: absolute;
+ border-radius: 50%;
+ opacity: 0;
+ pointer-events: none;
+ content: "";
+}
+.mdc-segmented-button__segment .mdc-segmented-button__ripple::before {
+ transition: opacity 15ms linear, background-color 15ms linear;
+ z-index: 1;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 1);
+}
+.mdc-segmented-button__segment .mdc-segmented-button__ripple::after {
+ z-index: 0;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 0);
+}
+.mdc-segmented-button__segment.mdc-ripple-upgraded .mdc-segmented-button__ripple::before {
+ -webkit-transform: scale(var(--mdc-ripple-fg-scale, 1));
+ transform: scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-segmented-button__segment.mdc-ripple-upgraded .mdc-segmented-button__ripple::after {
+ top: 0;
+ /* @noflip */
+ left: 0;
+ -webkit-transform: scale(0);
+ transform: scale(0);
+ -webkit-transform-origin: center center;
+ transform-origin: center center;
+}
+.mdc-segmented-button__segment.mdc-ripple-upgraded--unbounded .mdc-segmented-button__ripple::after {
+ top: var(--mdc-ripple-top, 0);
+ /* @noflip */
+ left: var(--mdc-ripple-left, 0);
+}
+.mdc-segmented-button__segment.mdc-ripple-upgraded--foreground-activation .mdc-segmented-button__ripple::after {
+ -webkit-animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+ animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+}
+.mdc-segmented-button__segment.mdc-ripple-upgraded--foreground-deactivation .mdc-segmented-button__ripple::after {
+ -webkit-animation: mdc-ripple-fg-opacity-out 150ms;
+ animation: mdc-ripple-fg-opacity-out 150ms;
+ -webkit-transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+ transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-segmented-button__segment .mdc-segmented-button__ripple::before,
+.mdc-segmented-button__segment .mdc-segmented-button__ripple::after {
+ top: calc(50% - 100%);
+ /* @noflip */
+ left: calc(50% - 100%);
+ width: 200%;
+ height: 200%;
+}
+.mdc-segmented-button__segment.mdc-ripple-upgraded .mdc-segmented-button__ripple::after {
+ width: var(--mdc-ripple-fg-size, 100%);
+ height: var(--mdc-ripple-fg-size, 100%);
+}
+.mdc-segmented-button__segment .mdc-segmented-button__ripple::before, .mdc-segmented-button__segment .mdc-segmented-button__ripple::after {
+ background-color: #6200ee;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, #6200ee);
+}
+.mdc-segmented-button__segment:hover .mdc-segmented-button__ripple::before, .mdc-segmented-button__segment.mdc-ripple-surface--hover .mdc-segmented-button__ripple::before {
+ opacity: 0.04;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.04);
+}
+.mdc-segmented-button__segment.mdc-ripple-upgraded--background-focused .mdc-segmented-button__ripple::before, .mdc-segmented-button__segment.mdc-ripple-upgraded:focus-within .mdc-segmented-button__ripple::before, .mdc-segmented-button__segment:not(.mdc-ripple-upgraded):focus .mdc-segmented-button__ripple::before, .mdc-segmented-button__segment:not(.mdc-ripple-upgraded):focus-within .mdc-segmented-button__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.12);
+}
+.mdc-segmented-button__segment:not(.mdc-ripple-upgraded) .mdc-segmented-button__ripple::after {
+ transition: opacity 150ms linear;
+}
+.mdc-segmented-button__segment:not(.mdc-ripple-upgraded):active .mdc-segmented-button__ripple::after {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-segmented-button__segment.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-segmented-button__segment .mdc-segmented-button__ripple {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+}
+
+.mdc-slider__thumb {
+ --mdc-ripple-fg-size: 0;
+ --mdc-ripple-left: 0;
+ --mdc-ripple-top: 0;
+ --mdc-ripple-fg-scale: 1;
+ --mdc-ripple-fg-translate-end: 0;
+ --mdc-ripple-fg-translate-start: 0;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ will-change: transform, opacity;
+}
+.mdc-slider__thumb::before, .mdc-slider__thumb::after {
+ position: absolute;
+ border-radius: 50%;
+ opacity: 0;
+ pointer-events: none;
+ content: "";
+}
+.mdc-slider__thumb::before {
+ transition: opacity 15ms linear, background-color 15ms linear;
+ z-index: 1;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 1);
+}
+.mdc-slider__thumb::after {
+ z-index: 0;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 0);
+}
+.mdc-slider__thumb.mdc-ripple-upgraded::before {
+ -webkit-transform: scale(var(--mdc-ripple-fg-scale, 1));
+ transform: scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-slider__thumb.mdc-ripple-upgraded::after {
+ top: 0;
+ /* @noflip */
+ left: 0;
+ -webkit-transform: scale(0);
+ transform: scale(0);
+ -webkit-transform-origin: center center;
+ transform-origin: center center;
+}
+.mdc-slider__thumb.mdc-ripple-upgraded--unbounded::after {
+ top: var(--mdc-ripple-top, 0);
+ /* @noflip */
+ left: var(--mdc-ripple-left, 0);
+}
+.mdc-slider__thumb.mdc-ripple-upgraded--foreground-activation::after {
+ -webkit-animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+ animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+}
+.mdc-slider__thumb.mdc-ripple-upgraded--foreground-deactivation::after {
+ -webkit-animation: mdc-ripple-fg-opacity-out 150ms;
+ animation: mdc-ripple-fg-opacity-out 150ms;
+ -webkit-transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+ transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-slider__thumb::before, .mdc-slider__thumb::after {
+ top: calc(50% - 50%);
+ /* @noflip */
+ left: calc(50% - 50%);
+ width: 100%;
+ height: 100%;
+}
+.mdc-slider__thumb.mdc-ripple-upgraded::before, .mdc-slider__thumb.mdc-ripple-upgraded::after {
+ top: var(--mdc-ripple-top, calc(50% - 50%));
+ /* @noflip */
+ left: var(--mdc-ripple-left, calc(50% - 50%));
+ width: var(--mdc-ripple-fg-size, 100%);
+ height: var(--mdc-ripple-fg-size, 100%);
+}
+.mdc-slider__thumb.mdc-ripple-upgraded::after {
+ width: var(--mdc-ripple-fg-size, 100%);
+ height: var(--mdc-ripple-fg-size, 100%);
+}
+.mdc-slider__thumb::before, .mdc-slider__thumb::after {
+ background-color: #6200ee;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee));
+}
+.mdc-slider__thumb:hover::before, .mdc-slider__thumb.mdc-ripple-surface--hover::before {
+ opacity: 0.04;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.04);
+}
+.mdc-slider__thumb.mdc-ripple-upgraded--background-focused::before, .mdc-slider__thumb:not(.mdc-ripple-upgraded):focus::before {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.12);
+}
+.mdc-slider__thumb:not(.mdc-ripple-upgraded)::after {
+ transition: opacity 150ms linear;
+}
+.mdc-slider__thumb:not(.mdc-ripple-upgraded):active::after {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-slider__thumb.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+
+.mdc-slider {
+ cursor: pointer;
+ height: 48px;
+ margin: 0 24px;
+ position: relative;
+ touch-action: pan-y;
+}
+.mdc-slider .mdc-slider__track {
+ height: 4px;
+ position: absolute;
+ top: 50%;
+ -webkit-transform: translateY(-50%);
+ transform: translateY(-50%);
+ width: 100%;
+}
+.mdc-slider .mdc-slider__track--active,
+.mdc-slider .mdc-slider__track--inactive {
+ display: flex;
+ height: 100%;
+ position: absolute;
+ width: 100%;
+}
+.mdc-slider .mdc-slider__track--active {
+ border-radius: 3px;
+ height: 6px;
+ overflow: hidden;
+ top: -1px;
+}
+.mdc-slider .mdc-slider__track--active_fill {
+ border-top: 6px solid;
+ box-sizing: border-box;
+ height: 100%;
+ width: 100%;
+ position: relative;
+ /* @noflip */
+ -webkit-transform-origin: left;
+ /* @noflip */
+ transform-origin: left;
+}
+[dir=rtl] .mdc-slider .mdc-slider__track--active_fill, .mdc-slider .mdc-slider__track--active_fill[dir=rtl] {
+ /* @noflip */
+ -webkit-transform-origin: right;
+ /* @noflip */
+ transform-origin: right;
+}
+
+.mdc-slider .mdc-slider__track--inactive {
+ border-radius: 2px;
+ height: 4px;
+ left: 0;
+ top: 0;
+}
+.mdc-slider .mdc-slider__track--inactive::before {
+ position: absolute;
+ box-sizing: border-box;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ border: 1px solid transparent;
+ border-radius: inherit;
+ content: "";
+ pointer-events: none;
+}
+.mdc-slider .mdc-slider__track--active_fill {
+ border-color: #6200ee;
+ /* @alternate */
+ border-color: var(--mdc-theme-primary, #6200ee);
+}
+.mdc-slider.mdc-slider--disabled .mdc-slider__track--active_fill {
+ border-color: #000;
+ /* @alternate */
+ border-color: var(--mdc-theme-on-surface, #000);
+}
+.mdc-slider .mdc-slider__track--inactive {
+ background-color: #6200ee;
+ /* @alternate */
+ background-color: var(--mdc-theme-primary, #6200ee);
+ opacity: 0.24;
+}
+.mdc-slider.mdc-slider--disabled .mdc-slider__track--inactive {
+ background-color: #000;
+ /* @alternate */
+ background-color: var(--mdc-theme-on-surface, #000);
+ opacity: 0.24;
+}
+.mdc-slider .mdc-slider__value-indicator-container {
+ bottom: 44px;
+ /* @noflip */
+ left: 50%;
+ pointer-events: none;
+ position: absolute;
+ -webkit-transform: translateX(-50%);
+ transform: translateX(-50%);
+}
+.mdc-slider .mdc-slider__value-indicator {
+ transition: -webkit-transform 100ms 0ms cubic-bezier(0.4, 0, 1, 1);
+ transition: transform 100ms 0ms cubic-bezier(0.4, 0, 1, 1);
+ transition: transform 100ms 0ms cubic-bezier(0.4, 0, 1, 1), -webkit-transform 100ms 0ms cubic-bezier(0.4, 0, 1, 1);
+ align-items: center;
+ border-radius: 4px;
+ display: flex;
+ height: 32px;
+ padding: 0 12px;
+ -webkit-transform: scale(0);
+ transform: scale(0);
+ -webkit-transform-origin: bottom;
+ transform-origin: bottom;
+}
+.mdc-slider .mdc-slider__value-indicator::before {
+ border-left: 6px solid transparent;
+ border-right: 6px solid transparent;
+ border-top: 6px solid;
+ bottom: -5px;
+ content: "";
+ height: 0;
+ /* @noflip */
+ left: 50%;
+ position: absolute;
+ -webkit-transform: translateX(-50%);
+ transform: translateX(-50%);
+ width: 0;
+}
+.mdc-slider .mdc-slider__value-indicator::after {
+ position: absolute;
+ box-sizing: border-box;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ border: 1px solid transparent;
+ border-radius: inherit;
+ content: "";
+ pointer-events: none;
+}
+.mdc-slider .mdc-slider__thumb--with-indicator .mdc-slider__value-indicator-container {
+ pointer-events: auto;
+}
+.mdc-slider .mdc-slider__thumb--with-indicator .mdc-slider__value-indicator {
+ transition: -webkit-transform 100ms 0ms cubic-bezier(0, 0, 0.2, 1);
+ transition: transform 100ms 0ms cubic-bezier(0, 0, 0.2, 1);
+ transition: transform 100ms 0ms cubic-bezier(0, 0, 0.2, 1), -webkit-transform 100ms 0ms cubic-bezier(0, 0, 0.2, 1);
+ -webkit-transform: scale(1);
+ transform: scale(1);
+}
+@media (prefers-reduced-motion) {
+ .mdc-slider .mdc-slider__value-indicator,
+.mdc-slider .mdc-slider__thumb--with-indicator .mdc-slider__value-indicator {
+ transition: none;
+ }
+}
+.mdc-slider .mdc-slider__value-indicator-text {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-subtitle2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.875rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-subtitle2-font-size, 0.875rem);
+ line-height: 1.375rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-subtitle2-line-height, 1.375rem);
+ font-weight: 500;
+ /* @alternate */
+ font-weight: var(--mdc-typography-subtitle2-font-weight, 500);
+ letter-spacing: 0.0071428571em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-subtitle2-letter-spacing, 0.0071428571em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-subtitle2-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-subtitle2-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-subtitle2-text-transform, inherit);
+}
+.mdc-slider .mdc-slider__value-indicator {
+ background-color: #000;
+ opacity: 0.6;
+}
+.mdc-slider .mdc-slider__value-indicator::before {
+ border-top-color: #000;
+}
+.mdc-slider .mdc-slider__value-indicator {
+ color: #fff;
+ /* @alternate */
+ color: var(--mdc-theme-on-primary, #fff);
+}
+.mdc-slider .mdc-slider__thumb {
+ display: flex;
+ height: 48px;
+ /* @noflip */
+ left: -24px;
+ outline: none;
+ position: absolute;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ width: 48px;
+}
+.mdc-slider .mdc-slider__thumb--top {
+ z-index: 1;
+}
+.mdc-slider .mdc-slider__thumb--top .mdc-slider__thumb-knob, .mdc-slider .mdc-slider__thumb--top.mdc-slider__thumb:hover .mdc-slider__thumb-knob, .mdc-slider .mdc-slider__thumb--top.mdc-slider__thumb--focused .mdc-slider__thumb-knob {
+ border-style: solid;
+ border-width: 1px;
+ box-sizing: content-box;
+}
+.mdc-slider .mdc-slider__thumb-knob {
+ /* @alternate */
+ box-shadow: 0px 2px 1px -1px rgba(0, 0, 0, 0.2), 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 1px 3px 0px rgba(0, 0, 0, 0.12);
+ border: 10px solid;
+ border-radius: 50%;
+ box-sizing: border-box;
+ height: 20px;
+ /* @noflip */
+ left: 50%;
+ position: absolute;
+ top: 50%;
+ -webkit-transform: translate(-50%, -50%);
+ transform: translate(-50%, -50%);
+ width: 20px;
+}
+.mdc-slider .mdc-slider__thumb-knob {
+ background-color: #6200ee;
+ /* @alternate */
+ background-color: var(--mdc-theme-primary, #6200ee);
+ border-color: #6200ee;
+ /* @alternate */
+ border-color: var(--mdc-theme-primary, #6200ee);
+}
+.mdc-slider .mdc-slider__thumb--top .mdc-slider__thumb-knob, .mdc-slider .mdc-slider__thumb--top.mdc-slider__thumb:hover .mdc-slider__thumb-knob, .mdc-slider .mdc-slider__thumb--top.mdc-slider__thumb--focused .mdc-slider__thumb-knob {
+ border-color: #fff;
+}
+.mdc-slider.mdc-slider--disabled .mdc-slider__thumb-knob {
+ background-color: #000;
+ /* @alternate */
+ background-color: var(--mdc-theme-on-surface, #000);
+ border-color: #000;
+ /* @alternate */
+ border-color: var(--mdc-theme-on-surface, #000);
+}
+.mdc-slider.mdc-slider--disabled .mdc-slider__thumb--top .mdc-slider__thumb-knob, .mdc-slider.mdc-slider--disabled .mdc-slider__thumb--top.mdc-slider__thumb:hover .mdc-slider__thumb-knob, .mdc-slider.mdc-slider--disabled .mdc-slider__thumb--top.mdc-slider__thumb--focused .mdc-slider__thumb-knob {
+ border-color: #fff;
+}
+.mdc-slider .mdc-slider__thumb::before, .mdc-slider .mdc-slider__thumb::after {
+ background-color: #6200ee;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee));
+}
+.mdc-slider .mdc-slider__thumb:hover::before, .mdc-slider .mdc-slider__thumb.mdc-ripple-surface--hover::before {
+ opacity: 0.04;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.04);
+}
+.mdc-slider .mdc-slider__thumb.mdc-ripple-upgraded--background-focused::before, .mdc-slider .mdc-slider__thumb:not(.mdc-ripple-upgraded):focus::before {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.12);
+}
+.mdc-slider .mdc-slider__thumb:not(.mdc-ripple-upgraded)::after {
+ transition: opacity 150ms linear;
+}
+.mdc-slider .mdc-slider__thumb:not(.mdc-ripple-upgraded):active::after {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-slider .mdc-slider__thumb.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-slider .mdc-slider__tick-marks {
+ align-items: center;
+ box-sizing: border-box;
+ display: flex;
+ height: 100%;
+ justify-content: space-between;
+ padding: 0 1px;
+ position: absolute;
+ width: 100%;
+}
+.mdc-slider .mdc-slider__tick-mark--active,
+.mdc-slider .mdc-slider__tick-mark--inactive {
+ border-radius: 50%;
+ height: 2px;
+ width: 2px;
+}
+.mdc-slider .mdc-slider__tick-mark--active {
+ background-color: #fff;
+ /* @alternate */
+ background-color: var(--mdc-theme-on-primary, #fff);
+ opacity: 0.6;
+}
+.mdc-slider.mdc-slider--disabled .mdc-slider__tick-mark--active {
+ background-color: #fff;
+ /* @alternate */
+ background-color: var(--mdc-theme-on-primary, #fff);
+ opacity: 0.6;
+}
+.mdc-slider .mdc-slider__tick-mark--inactive {
+ background-color: #6200ee;
+ /* @alternate */
+ background-color: var(--mdc-theme-primary, #6200ee);
+ opacity: 0.6;
+}
+.mdc-slider.mdc-slider--disabled .mdc-slider__tick-mark--inactive {
+ background-color: #000;
+ /* @alternate */
+ background-color: var(--mdc-theme-on-surface, #000);
+ opacity: 0.6;
+}
+.mdc-slider.mdc-slider--disabled {
+ opacity: 0.38;
+ cursor: auto;
+}
+.mdc-slider.mdc-slider--disabled .mdc-slider__thumb {
+ pointer-events: none;
+}
+.mdc-slider--discrete .mdc-slider__thumb,
+.mdc-slider--discrete .mdc-slider__track--active_fill {
+ transition: -webkit-transform 80ms ease;
+ transition: transform 80ms ease;
+ transition: transform 80ms ease, -webkit-transform 80ms ease;
+}
+@media (prefers-reduced-motion) {
+ .mdc-slider--discrete .mdc-slider__thumb,
+.mdc-slider--discrete .mdc-slider__track--active_fill {
+ transition: none;
+ }
+}
+
+.mdc-slider__input {
+ cursor: pointer;
+ left: 0;
+ margin: 0;
+ height: 100%;
+ opacity: 0;
+ pointer-events: none;
+ position: absolute;
+ top: 0;
+ width: 100%;
+}
+
+.mdc-snackbar {
+ z-index: 8;
+ margin: 8px;
+ display: none;
+ position: fixed;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ align-items: center;
+ justify-content: center;
+ box-sizing: border-box;
+ pointer-events: none;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+}
+
+.mdc-snackbar__surface {
+ background-color: #333333;
+}
+
+.mdc-snackbar__label {
+ color: rgba(255, 255, 255, 0.87);
+}
+
+.mdc-snackbar__surface {
+ min-width: 344px;
+}
+@media (max-width: 480px), (max-width: 344px) {
+ .mdc-snackbar__surface {
+ min-width: 100%;
+ }
+}
+
+.mdc-snackbar__surface {
+ max-width: 672px;
+}
+
+.mdc-snackbar__surface {
+ /* @alternate */
+ box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2), 0px 6px 10px 0px rgba(0, 0, 0, 0.14), 0px 1px 18px 0px rgba(0, 0, 0, 0.12);
+}
+
+.mdc-snackbar__surface {
+ border-radius: 4px;
+ /* @alternate */
+ border-radius: var(--mdc-shape-small, 4px);
+}
+
+.mdc-snackbar--opening,
+.mdc-snackbar--open,
+.mdc-snackbar--closing {
+ display: flex;
+}
+
+.mdc-snackbar--open .mdc-snackbar__label,
+.mdc-snackbar--open .mdc-snackbar__actions {
+ visibility: visible;
+}
+
+.mdc-snackbar--leading {
+ justify-content: flex-start;
+}
+
+.mdc-snackbar--stacked .mdc-snackbar__label {
+ /* @noflip */
+ padding-left: 16px;
+ /* @noflip */
+ padding-right: 8px;
+ padding-bottom: 12px;
+}
+[dir=rtl] .mdc-snackbar--stacked .mdc-snackbar__label, .mdc-snackbar--stacked .mdc-snackbar__label[dir=rtl] {
+ /* @noflip */
+ padding-left: 8px;
+ /* @noflip */
+ padding-right: 16px;
+}
+
+.mdc-snackbar--stacked .mdc-snackbar__surface {
+ flex-direction: column;
+ align-items: flex-start;
+}
+.mdc-snackbar--stacked .mdc-snackbar__actions {
+ align-self: flex-end;
+ margin-bottom: 8px;
+}
+
+.mdc-snackbar__surface {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: 8px;
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ box-sizing: border-box;
+ -webkit-transform: scale(0.8);
+ transform: scale(0.8);
+ opacity: 0;
+}
+.mdc-snackbar__surface::before {
+ position: absolute;
+ box-sizing: border-box;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ border: 1px solid transparent;
+ border-radius: inherit;
+ content: "";
+ pointer-events: none;
+}
+[dir=rtl] .mdc-snackbar__surface, .mdc-snackbar__surface[dir=rtl] {
+ /* @noflip */
+ padding-left: 8px;
+ /* @noflip */
+ padding-right: 0;
+}
+
+.mdc-snackbar--open .mdc-snackbar__surface {
+ -webkit-transform: scale(1);
+ transform: scale(1);
+ opacity: 1;
+ pointer-events: auto;
+ transition: opacity 150ms 0ms cubic-bezier(0, 0, 0.2, 1), -webkit-transform 150ms 0ms cubic-bezier(0, 0, 0.2, 1);
+ transition: opacity 150ms 0ms cubic-bezier(0, 0, 0.2, 1), transform 150ms 0ms cubic-bezier(0, 0, 0.2, 1);
+ transition: opacity 150ms 0ms cubic-bezier(0, 0, 0.2, 1), transform 150ms 0ms cubic-bezier(0, 0, 0.2, 1), -webkit-transform 150ms 0ms cubic-bezier(0, 0, 0.2, 1);
+}
+.mdc-snackbar--closing .mdc-snackbar__surface {
+ -webkit-transform: scale(1);
+ transform: scale(1);
+ transition: opacity 75ms 0ms cubic-bezier(0.4, 0, 1, 1);
+}
+
+.mdc-snackbar__label {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.875rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-body2-font-size, 0.875rem);
+ line-height: 1.25rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-body2-line-height, 1.25rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-body2-font-weight, 400);
+ letter-spacing: 0.0178571429em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-body2-letter-spacing, 0.0178571429em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-body2-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-body2-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-body2-text-transform, inherit);
+ /* @noflip */
+ padding-left: 16px;
+ /* @noflip */
+ padding-right: 8px;
+ width: 100%;
+ flex-grow: 1;
+ box-sizing: border-box;
+ margin: 0;
+ visibility: hidden;
+ padding-top: 14px;
+ padding-bottom: 14px;
+}
+[dir=rtl] .mdc-snackbar__label, .mdc-snackbar__label[dir=rtl] {
+ /* @noflip */
+ padding-left: 8px;
+ /* @noflip */
+ padding-right: 16px;
+}
+
+.mdc-snackbar__label::before {
+ display: inline;
+ content: attr(data-mdc-snackbar-label-text);
+}
+
+.mdc-snackbar__actions {
+ display: flex;
+ flex-shrink: 0;
+ align-items: center;
+ box-sizing: border-box;
+ visibility: hidden;
+}
+
+.mdc-snackbar__action:not(:disabled) {
+ color: #bb86fc;
+}
+.mdc-snackbar__action::before, .mdc-snackbar__action::after {
+ background-color: #bb86fc;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, #bb86fc);
+}
+.mdc-snackbar__action:hover::before, .mdc-snackbar__action.mdc-ripple-surface--hover::before {
+ opacity: 0.08;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.08);
+}
+.mdc-snackbar__action.mdc-ripple-upgraded--background-focused::before, .mdc-snackbar__action:not(.mdc-ripple-upgraded):focus::before {
+ transition-duration: 75ms;
+ opacity: 0.24;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.24);
+}
+.mdc-snackbar__action:not(.mdc-ripple-upgraded)::after {
+ transition: opacity 150ms linear;
+}
+.mdc-snackbar__action:not(.mdc-ripple-upgraded):active::after {
+ transition-duration: 75ms;
+ opacity: 0.24;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.24);
+}
+.mdc-snackbar__action.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.24);
+}
+
+.mdc-snackbar__dismiss {
+ color: rgba(255, 255, 255, 0.87);
+}
+.mdc-snackbar__dismiss .mdc-icon-button__ripple::before, .mdc-snackbar__dismiss .mdc-icon-button__ripple::after {
+ background-color: rgba(255, 255, 255, 0.87);
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, rgba(255, 255, 255, 0.87));
+}
+.mdc-snackbar__dismiss:hover .mdc-icon-button__ripple::before, .mdc-snackbar__dismiss.mdc-ripple-surface--hover .mdc-icon-button__ripple::before {
+ opacity: 0.08;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.08);
+}
+.mdc-snackbar__dismiss.mdc-ripple-upgraded--background-focused .mdc-icon-button__ripple::before, .mdc-snackbar__dismiss:not(.mdc-ripple-upgraded):focus .mdc-icon-button__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.24;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.24);
+}
+.mdc-snackbar__dismiss:not(.mdc-ripple-upgraded) .mdc-icon-button__ripple::after {
+ transition: opacity 150ms linear;
+}
+.mdc-snackbar__dismiss:not(.mdc-ripple-upgraded):active .mdc-icon-button__ripple::after {
+ transition-duration: 75ms;
+ opacity: 0.24;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.24);
+}
+.mdc-snackbar__dismiss.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.24);
+}
+
+.mdc-snackbar__dismiss.mdc-snackbar__dismiss {
+ width: 36px;
+ height: 36px;
+ padding: 6px;
+ font-size: 18px;
+}
+
+.mdc-snackbar__action + .mdc-snackbar__dismiss {
+ /* @noflip */
+ margin-left: 8px;
+ /* @noflip */
+ margin-right: 0;
+}
+[dir=rtl] .mdc-snackbar__action + .mdc-snackbar__dismiss, .mdc-snackbar__action + .mdc-snackbar__dismiss[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: 8px;
+}
+
+.mdc-switch__thumb-underlay {
+ /* @noflip */
+ left: -14px;
+ /* @noflip */
+ right: initial;
+ top: -17px;
+ width: 48px;
+ height: 48px;
+}
+[dir=rtl] .mdc-switch__thumb-underlay, .mdc-switch__thumb-underlay[dir=rtl] {
+ /* @noflip */
+ left: initial;
+ /* @noflip */
+ right: -14px;
+}
+
+.mdc-switch__native-control {
+ width: 64px;
+ height: 48px;
+}
+
+.mdc-switch {
+ display: inline-block;
+ position: relative;
+ outline: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+.mdc-switch.mdc-switch--checked .mdc-switch__track {
+ background-color: #018786;
+ /* @alternate */
+ background-color: var(--mdc-theme-secondary, #018786);
+}
+.mdc-switch.mdc-switch--checked .mdc-switch__thumb {
+ background-color: #018786;
+ /* @alternate */
+ background-color: var(--mdc-theme-secondary, #018786);
+ border-color: #018786;
+ /* @alternate */
+ border-color: var(--mdc-theme-secondary, #018786);
+}
+.mdc-switch:not(.mdc-switch--checked) .mdc-switch__track {
+ background-color: #000;
+ /* @alternate */
+ background-color: var(--mdc-theme-on-surface, #000);
+}
+.mdc-switch:not(.mdc-switch--checked) .mdc-switch__thumb {
+ background-color: #fff;
+ /* @alternate */
+ background-color: var(--mdc-theme-surface, #fff);
+ border-color: #fff;
+ /* @alternate */
+ border-color: var(--mdc-theme-surface, #fff);
+}
+
+.mdc-switch__native-control {
+ /* @noflip */
+ left: 0;
+ /* @noflip */
+ right: initial;
+ position: absolute;
+ top: 0;
+ margin: 0;
+ opacity: 0;
+ cursor: pointer;
+ pointer-events: auto;
+ transition: -webkit-transform 90ms cubic-bezier(0.4, 0, 0.2, 1);
+ transition: transform 90ms cubic-bezier(0.4, 0, 0.2, 1);
+ transition: transform 90ms cubic-bezier(0.4, 0, 0.2, 1), -webkit-transform 90ms cubic-bezier(0.4, 0, 0.2, 1);
+}
+[dir=rtl] .mdc-switch__native-control, .mdc-switch__native-control[dir=rtl] {
+ /* @noflip */
+ left: initial;
+ /* @noflip */
+ right: 0;
+}
+
+.mdc-switch__track {
+ box-sizing: border-box;
+ width: 36px;
+ height: 14px;
+ border: 1px solid transparent;
+ border-radius: 7px;
+ opacity: 0.38;
+ transition: opacity 90ms cubic-bezier(0.4, 0, 0.2, 1), background-color 90ms cubic-bezier(0.4, 0, 0.2, 1), border-color 90ms cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.mdc-switch__thumb-underlay {
+ display: flex;
+ position: absolute;
+ align-items: center;
+ justify-content: center;
+ -webkit-transform: translateX(0);
+ transform: translateX(0);
+ transition: background-color 90ms cubic-bezier(0.4, 0, 0.2, 1), border-color 90ms cubic-bezier(0.4, 0, 0.2, 1), -webkit-transform 90ms cubic-bezier(0.4, 0, 0.2, 1);
+ transition: transform 90ms cubic-bezier(0.4, 0, 0.2, 1), background-color 90ms cubic-bezier(0.4, 0, 0.2, 1), border-color 90ms cubic-bezier(0.4, 0, 0.2, 1);
+ transition: transform 90ms cubic-bezier(0.4, 0, 0.2, 1), background-color 90ms cubic-bezier(0.4, 0, 0.2, 1), border-color 90ms cubic-bezier(0.4, 0, 0.2, 1), -webkit-transform 90ms cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.mdc-switch__thumb {
+ /* @alternate */
+ box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12);
+ box-sizing: border-box;
+ width: 20px;
+ height: 20px;
+ border: 10px solid;
+ border-radius: 50%;
+ pointer-events: none;
+ z-index: 1;
+}
+
+.mdc-switch--checked .mdc-switch__track {
+ opacity: 0.54;
+}
+.mdc-switch--checked .mdc-switch__thumb-underlay {
+ -webkit-transform: translateX(16px);
+ transform: translateX(16px);
+}
+[dir=rtl] .mdc-switch--checked .mdc-switch__thumb-underlay, .mdc-switch--checked .mdc-switch__thumb-underlay[dir=rtl] {
+ -webkit-transform: translateX(-16px);
+ transform: translateX(-16px);
+}
+
+.mdc-switch--checked .mdc-switch__native-control {
+ -webkit-transform: translateX(-16px);
+ transform: translateX(-16px);
+}
+[dir=rtl] .mdc-switch--checked .mdc-switch__native-control, .mdc-switch--checked .mdc-switch__native-control[dir=rtl] {
+ -webkit-transform: translateX(16px);
+ transform: translateX(16px);
+}
+
+.mdc-switch--disabled {
+ opacity: 0.38;
+ pointer-events: none;
+}
+.mdc-switch--disabled .mdc-switch__thumb {
+ border-width: 1px;
+}
+.mdc-switch--disabled .mdc-switch__native-control {
+ cursor: default;
+ pointer-events: none;
+}
+
+.mdc-switch:not(.mdc-switch--checked) .mdc-switch__thumb-underlay::before, .mdc-switch:not(.mdc-switch--checked) .mdc-switch__thumb-underlay::after {
+ background-color: #9e9e9e;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, #9e9e9e);
+}
+.mdc-switch:not(.mdc-switch--checked) .mdc-switch__thumb-underlay:hover::before, .mdc-switch:not(.mdc-switch--checked) .mdc-switch__thumb-underlay.mdc-ripple-surface--hover::before {
+ opacity: 0.08;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.08);
+}
+.mdc-switch:not(.mdc-switch--checked) .mdc-switch__thumb-underlay.mdc-ripple-upgraded--background-focused::before, .mdc-switch:not(.mdc-switch--checked) .mdc-switch__thumb-underlay:not(.mdc-ripple-upgraded):focus::before {
+ transition-duration: 75ms;
+ opacity: 0.24;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.24);
+}
+.mdc-switch:not(.mdc-switch--checked) .mdc-switch__thumb-underlay:not(.mdc-ripple-upgraded)::after {
+ transition: opacity 150ms linear;
+}
+.mdc-switch:not(.mdc-switch--checked) .mdc-switch__thumb-underlay:not(.mdc-ripple-upgraded):active::after {
+ transition-duration: 75ms;
+ opacity: 0.24;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.24);
+}
+.mdc-switch:not(.mdc-switch--checked) .mdc-switch__thumb-underlay.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.24);
+}
+
+.mdc-switch__thumb-underlay {
+ --mdc-ripple-fg-size: 0;
+ --mdc-ripple-left: 0;
+ --mdc-ripple-top: 0;
+ --mdc-ripple-fg-scale: 1;
+ --mdc-ripple-fg-translate-end: 0;
+ --mdc-ripple-fg-translate-start: 0;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ will-change: transform, opacity;
+}
+.mdc-switch__thumb-underlay::before, .mdc-switch__thumb-underlay::after {
+ position: absolute;
+ border-radius: 50%;
+ opacity: 0;
+ pointer-events: none;
+ content: "";
+}
+.mdc-switch__thumb-underlay::before {
+ transition: opacity 15ms linear, background-color 15ms linear;
+ z-index: 1;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 1);
+}
+.mdc-switch__thumb-underlay::after {
+ z-index: 0;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 0);
+}
+.mdc-switch__thumb-underlay.mdc-ripple-upgraded::before {
+ -webkit-transform: scale(var(--mdc-ripple-fg-scale, 1));
+ transform: scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-switch__thumb-underlay.mdc-ripple-upgraded::after {
+ top: 0;
+ /* @noflip */
+ left: 0;
+ -webkit-transform: scale(0);
+ transform: scale(0);
+ -webkit-transform-origin: center center;
+ transform-origin: center center;
+}
+.mdc-switch__thumb-underlay.mdc-ripple-upgraded--unbounded::after {
+ top: var(--mdc-ripple-top, 0);
+ /* @noflip */
+ left: var(--mdc-ripple-left, 0);
+}
+.mdc-switch__thumb-underlay.mdc-ripple-upgraded--foreground-activation::after {
+ -webkit-animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+ animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+}
+.mdc-switch__thumb-underlay.mdc-ripple-upgraded--foreground-deactivation::after {
+ -webkit-animation: mdc-ripple-fg-opacity-out 150ms;
+ animation: mdc-ripple-fg-opacity-out 150ms;
+ -webkit-transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+ transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-switch__thumb-underlay::before, .mdc-switch__thumb-underlay::after {
+ top: calc(50% - 50%);
+ /* @noflip */
+ left: calc(50% - 50%);
+ width: 100%;
+ height: 100%;
+}
+.mdc-switch__thumb-underlay.mdc-ripple-upgraded::before, .mdc-switch__thumb-underlay.mdc-ripple-upgraded::after {
+ top: var(--mdc-ripple-top, calc(50% - 50%));
+ /* @noflip */
+ left: var(--mdc-ripple-left, calc(50% - 50%));
+ width: var(--mdc-ripple-fg-size, 100%);
+ height: var(--mdc-ripple-fg-size, 100%);
+}
+.mdc-switch__thumb-underlay.mdc-ripple-upgraded::after {
+ width: var(--mdc-ripple-fg-size, 100%);
+ height: var(--mdc-ripple-fg-size, 100%);
+}
+.mdc-switch__thumb-underlay::before, .mdc-switch__thumb-underlay::after {
+ background-color: #018786;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, var(--mdc-theme-secondary, #018786));
+}
+.mdc-switch__thumb-underlay:hover::before, .mdc-switch__thumb-underlay.mdc-ripple-surface--hover::before {
+ opacity: 0.04;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.04);
+}
+.mdc-switch__thumb-underlay.mdc-ripple-upgraded--background-focused::before, .mdc-switch__thumb-underlay:not(.mdc-ripple-upgraded):focus::before {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.12);
+}
+.mdc-switch__thumb-underlay:not(.mdc-ripple-upgraded)::after {
+ transition: opacity 150ms linear;
+}
+.mdc-switch__thumb-underlay:not(.mdc-ripple-upgraded):active::after {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-switch__thumb-underlay.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+
+.mdc-tab {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-button-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.875rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-button-font-size, 0.875rem);
+ line-height: 2.25rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-button-line-height, 2.25rem);
+ font-weight: 500;
+ /* @alternate */
+ font-weight: var(--mdc-typography-button-font-weight, 500);
+ letter-spacing: 0.0892857143em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-button-letter-spacing, 0.0892857143em);
+ text-decoration: none;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-button-text-decoration, none);
+ text-decoration: var(--mdc-typography-button-text-decoration, none);
+ text-transform: uppercase;
+ /* @alternate */
+ text-transform: var(--mdc-typography-button-text-transform, uppercase);
+ position: relative;
+}
+.mdc-tab .mdc-tab__text-label {
+ color: rgba(0, 0, 0, 0.6);
+}
+.mdc-tab .mdc-tab__icon {
+ color: rgba(0, 0, 0, 0.54);
+ fill: currentColor;
+}
+
+.mdc-tab__content {
+ position: relative;
+}
+
+.mdc-tab__icon {
+ width: 24px;
+ height: 24px;
+ font-size: 24px;
+}
+
+.mdc-tab--active .mdc-tab__text-label {
+ color: #6200ee;
+ /* @alternate */
+ color: var(--mdc-theme-primary, #6200ee);
+}
+.mdc-tab--active .mdc-tab__icon {
+ color: #6200ee;
+ /* @alternate */
+ color: var(--mdc-theme-primary, #6200ee);
+ fill: currentColor;
+}
+
+.mdc-tab {
+ background: none;
+}
+
+.mdc-tab {
+ min-width: 90px;
+ padding-right: 24px;
+ padding-left: 24px;
+ display: flex;
+ flex: 1 0 auto;
+ justify-content: center;
+ box-sizing: border-box;
+ margin: 0;
+ padding-top: 0;
+ padding-bottom: 0;
+ border: none;
+ outline: none;
+ text-align: center;
+ white-space: nowrap;
+ cursor: pointer;
+ -webkit-appearance: none;
+ z-index: 1;
+}
+.mdc-tab::-moz-focus-inner {
+ padding: 0;
+ border: 0;
+}
+
+.mdc-tab--min-width {
+ flex: 0 1 auto;
+}
+
+.mdc-tab__content {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: inherit;
+ pointer-events: none;
+}
+
+.mdc-tab__text-label {
+ transition: 150ms color linear;
+ display: inline-block;
+ line-height: 1;
+ z-index: 2;
+}
+
+.mdc-tab__icon {
+ transition: 150ms color linear;
+ z-index: 2;
+}
+
+.mdc-tab--stacked .mdc-tab__content {
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+.mdc-tab--stacked .mdc-tab__text-label {
+ padding-top: 6px;
+ padding-bottom: 4px;
+}
+
+.mdc-tab--active .mdc-tab__text-label,
+.mdc-tab--active .mdc-tab__icon {
+ transition-delay: 100ms;
+}
+
+.mdc-tab:not(.mdc-tab--stacked) .mdc-tab__icon + .mdc-tab__text-label {
+ /* @noflip */
+ padding-left: 8px;
+ /* @noflip */
+ padding-right: 0;
+}
+[dir=rtl] .mdc-tab:not(.mdc-tab--stacked) .mdc-tab__icon + .mdc-tab__text-label, .mdc-tab:not(.mdc-tab--stacked) .mdc-tab__icon + .mdc-tab__text-label[dir=rtl] {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: 8px;
+}
+
+.mdc-tab {
+ --mdc-ripple-fg-size: 0;
+ --mdc-ripple-left: 0;
+ --mdc-ripple-top: 0;
+ --mdc-ripple-fg-scale: 1;
+ --mdc-ripple-fg-translate-end: 0;
+ --mdc-ripple-fg-translate-start: 0;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+}
+.mdc-tab .mdc-tab__ripple::before,
+.mdc-tab .mdc-tab__ripple::after {
+ position: absolute;
+ border-radius: 50%;
+ opacity: 0;
+ pointer-events: none;
+ content: "";
+}
+.mdc-tab .mdc-tab__ripple::before {
+ transition: opacity 15ms linear, background-color 15ms linear;
+ z-index: 1;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 1);
+}
+.mdc-tab .mdc-tab__ripple::after {
+ z-index: 0;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 0);
+}
+.mdc-tab.mdc-ripple-upgraded .mdc-tab__ripple::before {
+ -webkit-transform: scale(var(--mdc-ripple-fg-scale, 1));
+ transform: scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-tab.mdc-ripple-upgraded .mdc-tab__ripple::after {
+ top: 0;
+ /* @noflip */
+ left: 0;
+ -webkit-transform: scale(0);
+ transform: scale(0);
+ -webkit-transform-origin: center center;
+ transform-origin: center center;
+}
+.mdc-tab.mdc-ripple-upgraded--unbounded .mdc-tab__ripple::after {
+ top: var(--mdc-ripple-top, 0);
+ /* @noflip */
+ left: var(--mdc-ripple-left, 0);
+}
+.mdc-tab.mdc-ripple-upgraded--foreground-activation .mdc-tab__ripple::after {
+ -webkit-animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+ animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+}
+.mdc-tab.mdc-ripple-upgraded--foreground-deactivation .mdc-tab__ripple::after {
+ -webkit-animation: mdc-ripple-fg-opacity-out 150ms;
+ animation: mdc-ripple-fg-opacity-out 150ms;
+ -webkit-transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+ transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-tab .mdc-tab__ripple::before,
+.mdc-tab .mdc-tab__ripple::after {
+ top: calc(50% - 100%);
+ /* @noflip */
+ left: calc(50% - 100%);
+ width: 200%;
+ height: 200%;
+}
+.mdc-tab.mdc-ripple-upgraded .mdc-tab__ripple::after {
+ width: var(--mdc-ripple-fg-size, 100%);
+ height: var(--mdc-ripple-fg-size, 100%);
+}
+.mdc-tab .mdc-tab__ripple::before, .mdc-tab .mdc-tab__ripple::after {
+ background-color: #6200ee;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee));
+}
+.mdc-tab:hover .mdc-tab__ripple::before, .mdc-tab.mdc-ripple-surface--hover .mdc-tab__ripple::before {
+ opacity: 0.04;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.04);
+}
+.mdc-tab.mdc-ripple-upgraded--background-focused .mdc-tab__ripple::before, .mdc-tab:not(.mdc-ripple-upgraded):focus .mdc-tab__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.12);
+}
+.mdc-tab:not(.mdc-ripple-upgraded) .mdc-tab__ripple::after {
+ transition: opacity 150ms linear;
+}
+.mdc-tab:not(.mdc-ripple-upgraded):active .mdc-tab__ripple::after {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+.mdc-tab.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.12);
+}
+
+.mdc-tab__ripple {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ will-change: transform, opacity;
+}
+
+/**
+ * @license
+ * Copyright 2018 Google Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+.mdc-tab-bar {
+ width: 100%;
+}
+
+.mdc-tab {
+ height: 48px;
+}
+
+.mdc-tab--stacked {
+ height: 72px;
+}
+
+/**
+ * @license
+ * Copyright 2018 Google Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+.mdc-tab-indicator .mdc-tab-indicator__content--underline {
+ border-color: #6200ee;
+ /* @alternate */
+ border-color: var(--mdc-theme-primary, #6200ee);
+}
+.mdc-tab-indicator .mdc-tab-indicator__content--icon {
+ color: #018786;
+ /* @alternate */
+ color: var(--mdc-theme-secondary, #018786);
+}
+.mdc-tab-indicator .mdc-tab-indicator__content--underline {
+ border-top-width: 2px;
+}
+.mdc-tab-indicator .mdc-tab-indicator__content--icon {
+ height: 34px;
+ font-size: 34px;
+}
+
+.mdc-tab-indicator {
+ display: flex;
+ position: absolute;
+ top: 0;
+ left: 0;
+ justify-content: center;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ z-index: 1;
+}
+
+.mdc-tab-indicator__content {
+ -webkit-transform-origin: left;
+ transform-origin: left;
+ opacity: 0;
+}
+
+.mdc-tab-indicator__content--underline {
+ align-self: flex-end;
+ box-sizing: border-box;
+ width: 100%;
+ border-top-style: solid;
+}
+
+.mdc-tab-indicator__content--icon {
+ align-self: center;
+ margin: 0 auto;
+}
+
+.mdc-tab-indicator--active .mdc-tab-indicator__content {
+ opacity: 1;
+}
+
+.mdc-tab-indicator .mdc-tab-indicator__content {
+ transition: 250ms -webkit-transform cubic-bezier(0.4, 0, 0.2, 1);
+ transition: 250ms transform cubic-bezier(0.4, 0, 0.2, 1);
+ transition: 250ms transform cubic-bezier(0.4, 0, 0.2, 1), 250ms -webkit-transform cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.mdc-tab-indicator--no-transition .mdc-tab-indicator__content {
+ transition: none;
+}
+
+.mdc-tab-indicator--fade .mdc-tab-indicator__content {
+ transition: 150ms opacity linear;
+}
+
+.mdc-tab-indicator--active.mdc-tab-indicator--fade .mdc-tab-indicator__content {
+ transition-delay: 100ms;
+}
+
+/**
+ * @license
+ * Copyright 2018 Google Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+.mdc-tab-scroller {
+ overflow-y: hidden;
+}
+.mdc-tab-scroller.mdc-tab-scroller--animating .mdc-tab-scroller__scroll-content {
+ transition: 250ms -webkit-transform cubic-bezier(0.4, 0, 0.2, 1);
+ transition: 250ms transform cubic-bezier(0.4, 0, 0.2, 1);
+ transition: 250ms transform cubic-bezier(0.4, 0, 0.2, 1), 250ms -webkit-transform cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.mdc-tab-scroller__test {
+ position: absolute;
+ top: -9999px;
+ width: 100px;
+ height: 100px;
+ overflow-x: scroll;
+}
+
+.mdc-tab-scroller__scroll-area {
+ -webkit-overflow-scrolling: touch;
+ display: flex;
+ overflow-x: hidden;
+}
+
+.mdc-tab-scroller__scroll-area::-webkit-scrollbar,
+.mdc-tab-scroller__test::-webkit-scrollbar {
+ display: none;
+}
+
+.mdc-tab-scroller__scroll-area--scroll {
+ overflow-x: scroll;
+}
+
+.mdc-tab-scroller__scroll-content {
+ position: relative;
+ display: flex;
+ flex: 1 0 auto;
+ -webkit-transform: none;
+ transform: none;
+ will-change: transform;
+}
+
+.mdc-tab-scroller--align-start .mdc-tab-scroller__scroll-content {
+ justify-content: flex-start;
+}
+
+.mdc-tab-scroller--align-end .mdc-tab-scroller__scroll-content {
+ justify-content: flex-end;
+}
+
+.mdc-tab-scroller--align-center .mdc-tab-scroller__scroll-content {
+ justify-content: center;
+}
+
+.mdc-tab-scroller--animating .mdc-tab-scroller__scroll-area {
+ -webkit-overflow-scrolling: auto;
+}
+
+.mdc-text-field--filled {
+ --mdc-ripple-fg-size: 0;
+ --mdc-ripple-left: 0;
+ --mdc-ripple-top: 0;
+ --mdc-ripple-fg-scale: 1;
+ --mdc-ripple-fg-translate-end: 0;
+ --mdc-ripple-fg-translate-start: 0;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ will-change: transform, opacity;
+}
+.mdc-text-field--filled .mdc-text-field__ripple::before,
+.mdc-text-field--filled .mdc-text-field__ripple::after {
+ position: absolute;
+ border-radius: 50%;
+ opacity: 0;
+ pointer-events: none;
+ content: "";
+}
+.mdc-text-field--filled .mdc-text-field__ripple::before {
+ transition: opacity 15ms linear, background-color 15ms linear;
+ z-index: 1;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 1);
+}
+.mdc-text-field--filled .mdc-text-field__ripple::after {
+ z-index: 0;
+ /* @alternate */
+ z-index: var(--mdc-ripple-z-index, 0);
+}
+.mdc-text-field--filled.mdc-ripple-upgraded .mdc-text-field__ripple::before {
+ -webkit-transform: scale(var(--mdc-ripple-fg-scale, 1));
+ transform: scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-text-field--filled.mdc-ripple-upgraded .mdc-text-field__ripple::after {
+ top: 0;
+ /* @noflip */
+ left: 0;
+ -webkit-transform: scale(0);
+ transform: scale(0);
+ -webkit-transform-origin: center center;
+ transform-origin: center center;
+}
+.mdc-text-field--filled.mdc-ripple-upgraded--unbounded .mdc-text-field__ripple::after {
+ top: var(--mdc-ripple-top, 0);
+ /* @noflip */
+ left: var(--mdc-ripple-left, 0);
+}
+.mdc-text-field--filled.mdc-ripple-upgraded--foreground-activation .mdc-text-field__ripple::after {
+ -webkit-animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+ animation: mdc-ripple-fg-radius-in 225ms forwards, mdc-ripple-fg-opacity-in 75ms forwards;
+}
+.mdc-text-field--filled.mdc-ripple-upgraded--foreground-deactivation .mdc-text-field__ripple::after {
+ -webkit-animation: mdc-ripple-fg-opacity-out 150ms;
+ animation: mdc-ripple-fg-opacity-out 150ms;
+ -webkit-transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+ transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
+}
+.mdc-text-field--filled .mdc-text-field__ripple::before,
+.mdc-text-field--filled .mdc-text-field__ripple::after {
+ top: calc(50% - 100%);
+ /* @noflip */
+ left: calc(50% - 100%);
+ width: 200%;
+ height: 200%;
+}
+.mdc-text-field--filled.mdc-ripple-upgraded .mdc-text-field__ripple::after {
+ width: var(--mdc-ripple-fg-size, 100%);
+ height: var(--mdc-ripple-fg-size, 100%);
+}
+
+.mdc-text-field__ripple {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+}
+
+.mdc-text-field {
+ border-top-left-radius: 4px;
+ /* @alternate */
+ border-top-left-radius: var(--mdc-shape-small, 4px);
+ border-top-right-radius: 4px;
+ /* @alternate */
+ border-top-right-radius: var(--mdc-shape-small, 4px);
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+ display: inline-flex;
+ align-items: baseline;
+ padding: 0 16px;
+ position: relative;
+ box-sizing: border-box;
+ overflow: hidden;
+ /* @alternate */
+ will-change: opacity, transform, color;
+}
+.mdc-text-field:not(.mdc-text-field--disabled) .mdc-floating-label {
+ color: rgba(0, 0, 0, 0.6);
+}
+.mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field__input {
+ color: rgba(0, 0, 0, 0.87);
+}
+@media all {
+ .mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field__input::-webkit-input-placeholder {
+ color: rgba(0, 0, 0, 0.54);
+ }
+ .mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field__input:-ms-input-placeholder {
+ color: rgba(0, 0, 0, 0.54);
+ }
+ .mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field__input::-ms-input-placeholder {
+ color: rgba(0, 0, 0, 0.54);
+ }
+ .mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field__input::placeholder {
+ color: rgba(0, 0, 0, 0.54);
+ }
+}
+@media all {
+ .mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field__input:-ms-input-placeholder {
+ color: rgba(0, 0, 0, 0.54);
+ }
+}
+.mdc-text-field .mdc-text-field__input {
+ caret-color: #6200ee;
+ /* @alternate */
+ caret-color: var(--mdc-theme-primary, #6200ee);
+}
+.mdc-text-field:not(.mdc-text-field--disabled) + .mdc-text-field-helper-line .mdc-text-field-helper-text {
+ color: rgba(0, 0, 0, 0.6);
+}
+.mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field-character-counter,
+.mdc-text-field:not(.mdc-text-field--disabled) + .mdc-text-field-helper-line .mdc-text-field-character-counter {
+ color: rgba(0, 0, 0, 0.6);
+}
+.mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field__icon--leading {
+ color: rgba(0, 0, 0, 0.54);
+}
+.mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field__icon--trailing {
+ color: rgba(0, 0, 0, 0.54);
+}
+.mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field__affix--prefix {
+ color: rgba(0, 0, 0, 0.6);
+}
+.mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field__affix--suffix {
+ color: rgba(0, 0, 0, 0.6);
+}
+.mdc-text-field .mdc-floating-label {
+ top: 50%;
+ -webkit-transform: translateY(-50%);
+ transform: translateY(-50%);
+ pointer-events: none;
+}
+
+.mdc-text-field__input {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-subtitle1-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 1rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-subtitle1-font-size, 1rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-subtitle1-font-weight, 400);
+ letter-spacing: 0.009375em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-subtitle1-letter-spacing, 0.009375em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-subtitle1-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-subtitle1-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-subtitle1-text-transform, inherit);
+ height: 28px;
+ transition: opacity 150ms 0ms cubic-bezier(0.4, 0, 0.2, 1);
+ width: 100%;
+ min-width: 0;
+ border: none;
+ border-radius: 0;
+ background: none;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ padding: 0;
+}
+.mdc-text-field__input::-ms-clear {
+ display: none;
+}
+.mdc-text-field__input::-webkit-calendar-picker-indicator {
+ display: none;
+}
+.mdc-text-field__input:focus {
+ outline: none;
+}
+.mdc-text-field__input:invalid {
+ box-shadow: none;
+}
+@media all {
+ .mdc-text-field__input::-webkit-input-placeholder {
+ transition: opacity 67ms 0ms cubic-bezier(0.4, 0, 0.2, 1);
+ opacity: 0;
+ }
+ .mdc-text-field__input:-ms-input-placeholder {
+ transition: opacity 67ms 0ms cubic-bezier(0.4, 0, 0.2, 1);
+ opacity: 0;
+ }
+ .mdc-text-field__input::-ms-input-placeholder {
+ transition: opacity 67ms 0ms cubic-bezier(0.4, 0, 0.2, 1);
+ opacity: 0;
+ }
+ .mdc-text-field__input::placeholder {
+ transition: opacity 67ms 0ms cubic-bezier(0.4, 0, 0.2, 1);
+ opacity: 0;
+ }
+}
+@media all {
+ .mdc-text-field__input:-ms-input-placeholder {
+ transition: opacity 67ms 0ms cubic-bezier(0.4, 0, 0.2, 1);
+ opacity: 0;
+ }
+}
+@media all {
+ .mdc-text-field--no-label .mdc-text-field__input::-webkit-input-placeholder, .mdc-text-field--focused .mdc-text-field__input::-webkit-input-placeholder {
+ transition-delay: 40ms;
+ transition-duration: 110ms;
+ opacity: 1;
+ }
+ .mdc-text-field--no-label .mdc-text-field__input:-ms-input-placeholder, .mdc-text-field--focused .mdc-text-field__input:-ms-input-placeholder {
+ transition-delay: 40ms;
+ transition-duration: 110ms;
+ opacity: 1;
+ }
+ .mdc-text-field--no-label .mdc-text-field__input::-ms-input-placeholder, .mdc-text-field--focused .mdc-text-field__input::-ms-input-placeholder {
+ transition-delay: 40ms;
+ transition-duration: 110ms;
+ opacity: 1;
+ }
+ .mdc-text-field--no-label .mdc-text-field__input::placeholder, .mdc-text-field--focused .mdc-text-field__input::placeholder {
+ transition-delay: 40ms;
+ transition-duration: 110ms;
+ opacity: 1;
+ }
+}
+@media all {
+ .mdc-text-field--no-label .mdc-text-field__input:-ms-input-placeholder, .mdc-text-field--focused .mdc-text-field__input:-ms-input-placeholder {
+ transition-delay: 40ms;
+ transition-duration: 110ms;
+ opacity: 1;
+ }
+}
+
+.mdc-text-field__affix {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-subtitle1-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 1rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-subtitle1-font-size, 1rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-subtitle1-font-weight, 400);
+ letter-spacing: 0.009375em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-subtitle1-letter-spacing, 0.009375em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-subtitle1-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-subtitle1-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-subtitle1-text-transform, inherit);
+ height: 28px;
+ transition: opacity 150ms 0ms cubic-bezier(0.4, 0, 0.2, 1);
+ opacity: 0;
+ white-space: nowrap;
+}
+.mdc-text-field--label-floating .mdc-text-field__affix, .mdc-text-field--no-label .mdc-text-field__affix {
+ opacity: 1;
+}
+@supports (-webkit-hyphens: none) {
+ .mdc-text-field--outlined .mdc-text-field__affix {
+ align-items: center;
+ align-self: center;
+ display: inline-flex;
+ height: 100%;
+ }
+}
+
+.mdc-text-field__affix--prefix {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: 2px;
+}
+[dir=rtl] .mdc-text-field__affix--prefix, .mdc-text-field__affix--prefix[dir=rtl] {
+ /* @noflip */
+ padding-left: 2px;
+ /* @noflip */
+ padding-right: 0;
+}
+
+.mdc-text-field--end-aligned .mdc-text-field__affix--prefix {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: 12px;
+}
+[dir=rtl] .mdc-text-field--end-aligned .mdc-text-field__affix--prefix, .mdc-text-field--end-aligned .mdc-text-field__affix--prefix[dir=rtl] {
+ /* @noflip */
+ padding-left: 12px;
+ /* @noflip */
+ padding-right: 0;
+}
+
+.mdc-text-field__affix--suffix {
+ /* @noflip */
+ padding-left: 12px;
+ /* @noflip */
+ padding-right: 0;
+}
+[dir=rtl] .mdc-text-field__affix--suffix, .mdc-text-field__affix--suffix[dir=rtl] {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: 12px;
+}
+
+.mdc-text-field--end-aligned .mdc-text-field__affix--suffix {
+ /* @noflip */
+ padding-left: 2px;
+ /* @noflip */
+ padding-right: 0;
+}
+[dir=rtl] .mdc-text-field--end-aligned .mdc-text-field__affix--suffix, .mdc-text-field--end-aligned .mdc-text-field__affix--suffix[dir=rtl] {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: 2px;
+}
+
+.mdc-text-field--filled {
+ height: 56px;
+}
+.mdc-text-field--filled .mdc-text-field__ripple::before,
+.mdc-text-field--filled .mdc-text-field__ripple::after {
+ background-color: rgba(0, 0, 0, 0.87);
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, rgba(0, 0, 0, 0.87));
+}
+.mdc-text-field--filled:hover .mdc-text-field__ripple::before, .mdc-text-field--filled.mdc-ripple-surface--hover .mdc-text-field__ripple::before {
+ opacity: 0.04;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.04);
+}
+.mdc-text-field--filled.mdc-ripple-upgraded--background-focused .mdc-text-field__ripple::before, .mdc-text-field--filled:not(.mdc-ripple-upgraded):focus .mdc-text-field__ripple::before {
+ transition-duration: 75ms;
+ opacity: 0.12;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.12);
+}
+.mdc-text-field--filled::before {
+ display: inline-block;
+ width: 0;
+ height: 40px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-text-field--filled:not(.mdc-text-field--disabled) {
+ background-color: whitesmoke;
+}
+.mdc-text-field--filled:not(.mdc-text-field--disabled) .mdc-line-ripple::before {
+ border-bottom-color: rgba(0, 0, 0, 0.42);
+}
+.mdc-text-field--filled:not(.mdc-text-field--disabled):hover .mdc-line-ripple::before {
+ border-bottom-color: rgba(0, 0, 0, 0.87);
+}
+.mdc-text-field--filled .mdc-line-ripple::after {
+ border-bottom-color: #6200ee;
+ /* @alternate */
+ border-bottom-color: var(--mdc-theme-primary, #6200ee);
+}
+.mdc-text-field--filled .mdc-floating-label {
+ /* @noflip */
+ left: 16px;
+ /* @noflip */
+ right: initial;
+}
+[dir=rtl] .mdc-text-field--filled .mdc-floating-label, .mdc-text-field--filled .mdc-floating-label[dir=rtl] {
+ /* @noflip */
+ left: initial;
+ /* @noflip */
+ right: 16px;
+}
+
+.mdc-text-field--filled .mdc-floating-label--float-above {
+ -webkit-transform: translateY(-106%) scale(0.75);
+ transform: translateY(-106%) scale(0.75);
+}
+.mdc-text-field--filled.mdc-text-field--no-label .mdc-text-field__input {
+ height: 100%;
+}
+.mdc-text-field--filled.mdc-text-field--no-label .mdc-floating-label {
+ display: none;
+}
+.mdc-text-field--filled.mdc-text-field--no-label::before {
+ display: none;
+}
+@supports (-webkit-hyphens: none) {
+ .mdc-text-field--filled.mdc-text-field--no-label .mdc-text-field__affix {
+ align-items: center;
+ align-self: center;
+ display: inline-flex;
+ height: 100%;
+ }
+}
+
+.mdc-text-field--outlined {
+ height: 56px;
+ overflow: visible;
+}
+.mdc-text-field--outlined .mdc-floating-label--float-above {
+ -webkit-transform: translateY(-37.25px) scale(1);
+ transform: translateY(-37.25px) scale(1);
+}
+.mdc-text-field--outlined .mdc-floating-label--float-above {
+ font-size: 0.75rem;
+}
+.mdc-text-field--outlined.mdc-notched-outline--upgraded .mdc-floating-label--float-above,
+.mdc-text-field--outlined .mdc-notched-outline--upgraded .mdc-floating-label--float-above {
+ -webkit-transform: translateY(-34.75px) scale(0.75);
+ transform: translateY(-34.75px) scale(0.75);
+}
+.mdc-text-field--outlined.mdc-notched-outline--upgraded .mdc-floating-label--float-above,
+.mdc-text-field--outlined .mdc-notched-outline--upgraded .mdc-floating-label--float-above {
+ font-size: 1rem;
+}
+.mdc-text-field--outlined .mdc-floating-label--shake {
+ -webkit-animation: mdc-floating-label-shake-float-above-text-field-outlined 250ms 1;
+ animation: mdc-floating-label-shake-float-above-text-field-outlined 250ms 1;
+}
+@-webkit-keyframes mdc-floating-label-shake-float-above-text-field-outlined {
+ 0% {
+ -webkit-transform: translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75);
+ }
+ 33% {
+ -webkit-animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ -webkit-transform: translateX(calc(4% - 0%)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(4% - 0%)) translateY(-34.75px) scale(0.75);
+ }
+ 66% {
+ -webkit-animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ -webkit-transform: translateX(calc(-4% - 0%)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(-4% - 0%)) translateY(-34.75px) scale(0.75);
+ }
+ 100% {
+ -webkit-transform: translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75);
+ }
+}
+@keyframes mdc-floating-label-shake-float-above-text-field-outlined {
+ 0% {
+ -webkit-transform: translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75);
+ }
+ 33% {
+ -webkit-animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ -webkit-transform: translateX(calc(4% - 0%)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(4% - 0%)) translateY(-34.75px) scale(0.75);
+ }
+ 66% {
+ -webkit-animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ -webkit-transform: translateX(calc(-4% - 0%)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(-4% - 0%)) translateY(-34.75px) scale(0.75);
+ }
+ 100% {
+ -webkit-transform: translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75);
+ }
+}
+.mdc-text-field--outlined .mdc-text-field__input {
+ height: 100%;
+}
+.mdc-text-field--outlined:not(.mdc-text-field--disabled) .mdc-notched-outline__leading,
+.mdc-text-field--outlined:not(.mdc-text-field--disabled) .mdc-notched-outline__notch,
+.mdc-text-field--outlined:not(.mdc-text-field--disabled) .mdc-notched-outline__trailing {
+ border-color: rgba(0, 0, 0, 0.38);
+}
+.mdc-text-field--outlined:not(.mdc-text-field--disabled):not(.mdc-text-field--focused):hover .mdc-notched-outline .mdc-notched-outline__leading,
+.mdc-text-field--outlined:not(.mdc-text-field--disabled):not(.mdc-text-field--focused):hover .mdc-notched-outline .mdc-notched-outline__notch,
+.mdc-text-field--outlined:not(.mdc-text-field--disabled):not(.mdc-text-field--focused):hover .mdc-notched-outline .mdc-notched-outline__trailing {
+ border-color: rgba(0, 0, 0, 0.87);
+}
+.mdc-text-field--outlined:not(.mdc-text-field--disabled).mdc-text-field--focused .mdc-notched-outline__leading,
+.mdc-text-field--outlined:not(.mdc-text-field--disabled).mdc-text-field--focused .mdc-notched-outline__notch,
+.mdc-text-field--outlined:not(.mdc-text-field--disabled).mdc-text-field--focused .mdc-notched-outline__trailing {
+ border-color: #6200ee;
+ /* @alternate */
+ border-color: var(--mdc-theme-primary, #6200ee);
+}
+.mdc-text-field--outlined .mdc-notched-outline .mdc-notched-outline__leading {
+ /* @noflip */
+ border-top-left-radius: 4px;
+ /* @alternate */
+ /* @noflip */
+ border-top-left-radius: var(--mdc-shape-small, 4px);
+ /* @noflip */
+ border-top-right-radius: 0;
+ /* @noflip */
+ border-bottom-right-radius: 0;
+ /* @noflip */
+ border-bottom-left-radius: 4px;
+ /* @alternate */
+ /* @noflip */
+ border-bottom-left-radius: var(--mdc-shape-small, 4px);
+}
+[dir=rtl] .mdc-text-field--outlined .mdc-notched-outline .mdc-notched-outline__leading, .mdc-text-field--outlined .mdc-notched-outline .mdc-notched-outline__leading[dir=rtl] {
+ /* @noflip */
+ border-top-left-radius: 0;
+ /* @noflip */
+ border-top-right-radius: 4px;
+ /* @alternate */
+ /* @noflip */
+ border-top-right-radius: var(--mdc-shape-small, 4px);
+ /* @noflip */
+ border-bottom-right-radius: 4px;
+ /* @alternate */
+ /* @noflip */
+ border-bottom-right-radius: var(--mdc-shape-small, 4px);
+ /* @noflip */
+ border-bottom-left-radius: 0;
+}
+
+@supports (top: max(0%)) {
+ .mdc-text-field--outlined .mdc-notched-outline .mdc-notched-outline__leading {
+ width: max(12px, var(--mdc-shape-small, 4px));
+ }
+}
+@supports (top: max(0%)) {
+ .mdc-text-field--outlined .mdc-notched-outline .mdc-notched-outline__notch {
+ max-width: calc(100% - max(12px, var(--mdc-shape-small, 4px)) * 2);
+ }
+}
+.mdc-text-field--outlined .mdc-notched-outline .mdc-notched-outline__trailing {
+ /* @noflip */
+ border-top-left-radius: 0;
+ /* @noflip */
+ border-top-right-radius: 4px;
+ /* @alternate */
+ /* @noflip */
+ border-top-right-radius: var(--mdc-shape-small, 4px);
+ /* @noflip */
+ border-bottom-right-radius: 4px;
+ /* @alternate */
+ /* @noflip */
+ border-bottom-right-radius: var(--mdc-shape-small, 4px);
+ /* @noflip */
+ border-bottom-left-radius: 0;
+}
+[dir=rtl] .mdc-text-field--outlined .mdc-notched-outline .mdc-notched-outline__trailing, .mdc-text-field--outlined .mdc-notched-outline .mdc-notched-outline__trailing[dir=rtl] {
+ /* @noflip */
+ border-top-left-radius: 4px;
+ /* @alternate */
+ /* @noflip */
+ border-top-left-radius: var(--mdc-shape-small, 4px);
+ /* @noflip */
+ border-top-right-radius: 0;
+ /* @noflip */
+ border-bottom-right-radius: 0;
+ /* @noflip */
+ border-bottom-left-radius: 4px;
+ /* @alternate */
+ /* @noflip */
+ border-bottom-left-radius: var(--mdc-shape-small, 4px);
+}
+
+@supports (top: max(0%)) {
+ .mdc-text-field--outlined {
+ /* @noflip */
+ padding-left: max(16px, calc(var(--mdc-shape-small, 4px) + 4px));
+ }
+}
+@supports (top: max(0%)) {
+ .mdc-text-field--outlined {
+ /* @noflip */
+ padding-right: max(16px, var(--mdc-shape-small, 4px));
+ }
+}
+@supports (top: max(0%)) {
+ .mdc-text-field--outlined + .mdc-text-field-helper-line {
+ /* @noflip */
+ padding-left: max(16px, calc(var(--mdc-shape-small, 4px) + 4px));
+ }
+}
+@supports (top: max(0%)) {
+ .mdc-text-field--outlined + .mdc-text-field-helper-line {
+ /* @noflip */
+ padding-right: max(16px, var(--mdc-shape-small, 4px));
+ }
+}
+.mdc-text-field--outlined.mdc-text-field--with-leading-icon {
+ /* @noflip */
+ padding-left: 0;
+}
+@supports (top: max(0%)) {
+ .mdc-text-field--outlined.mdc-text-field--with-leading-icon {
+ /* @noflip */
+ padding-right: max(16px, var(--mdc-shape-small, 4px));
+ }
+}
+[dir=rtl] .mdc-text-field--outlined.mdc-text-field--with-leading-icon, .mdc-text-field--outlined.mdc-text-field--with-leading-icon[dir=rtl] {
+ /* @noflip */
+ padding-right: 0;
+}
+@supports (top: max(0%)) {
+ [dir=rtl] .mdc-text-field--outlined.mdc-text-field--with-leading-icon, .mdc-text-field--outlined.mdc-text-field--with-leading-icon[dir=rtl] {
+ /* @noflip */
+ padding-left: max(16px, var(--mdc-shape-small, 4px));
+ }
+}
+
+.mdc-text-field--outlined.mdc-text-field--with-trailing-icon {
+ /* @noflip */
+ padding-right: 0;
+}
+@supports (top: max(0%)) {
+ .mdc-text-field--outlined.mdc-text-field--with-trailing-icon {
+ /* @noflip */
+ padding-left: max(16px, calc(var(--mdc-shape-small, 4px) + 4px));
+ }
+}
+[dir=rtl] .mdc-text-field--outlined.mdc-text-field--with-trailing-icon, .mdc-text-field--outlined.mdc-text-field--with-trailing-icon[dir=rtl] {
+ /* @noflip */
+ padding-left: 0;
+}
+@supports (top: max(0%)) {
+ [dir=rtl] .mdc-text-field--outlined.mdc-text-field--with-trailing-icon, .mdc-text-field--outlined.mdc-text-field--with-trailing-icon[dir=rtl] {
+ /* @noflip */
+ padding-right: max(16px, calc(var(--mdc-shape-small, 4px) + 4px));
+ }
+}
+
+.mdc-text-field--outlined.mdc-text-field--with-leading-icon.mdc-text-field--with-trailing-icon {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: 0;
+}
+.mdc-text-field--outlined .mdc-notched-outline--notched .mdc-notched-outline__notch {
+ padding-top: 1px;
+}
+.mdc-text-field--outlined .mdc-text-field__ripple::before,
+.mdc-text-field--outlined .mdc-text-field__ripple::after {
+ content: none;
+}
+.mdc-text-field--outlined .mdc-floating-label {
+ /* @noflip */
+ left: 4px;
+ /* @noflip */
+ right: initial;
+}
+[dir=rtl] .mdc-text-field--outlined .mdc-floating-label, .mdc-text-field--outlined .mdc-floating-label[dir=rtl] {
+ /* @noflip */
+ left: initial;
+ /* @noflip */
+ right: 4px;
+}
+
+.mdc-text-field--outlined .mdc-text-field__input {
+ display: flex;
+ border: none !important;
+ background-color: transparent;
+}
+.mdc-text-field--outlined .mdc-notched-outline {
+ z-index: 1;
+}
+
+.mdc-text-field--textarea {
+ flex-direction: column;
+ align-items: center;
+ width: auto;
+ height: auto;
+ padding: 0;
+ transition: none;
+}
+.mdc-text-field--textarea .mdc-floating-label {
+ top: 19px;
+}
+.mdc-text-field--textarea .mdc-floating-label:not(.mdc-floating-label--float-above) {
+ -webkit-transform: none;
+ transform: none;
+}
+.mdc-text-field--textarea .mdc-text-field__input {
+ flex-grow: 1;
+ height: auto;
+ min-height: 1.5rem;
+ overflow-x: hidden;
+ overflow-y: auto;
+ box-sizing: border-box;
+ resize: none;
+ padding: 0 16px;
+ line-height: 1.5rem;
+}
+.mdc-text-field--textarea.mdc-text-field--filled::before {
+ display: none;
+}
+.mdc-text-field--textarea.mdc-text-field--filled .mdc-floating-label--float-above {
+ -webkit-transform: translateY(-10.25px) scale(0.75);
+ transform: translateY(-10.25px) scale(0.75);
+}
+.mdc-text-field--textarea.mdc-text-field--filled .mdc-floating-label--shake {
+ -webkit-animation: mdc-floating-label-shake-float-above-textarea-filled 250ms 1;
+ animation: mdc-floating-label-shake-float-above-textarea-filled 250ms 1;
+}
+@-webkit-keyframes mdc-floating-label-shake-float-above-textarea-filled {
+ 0% {
+ -webkit-transform: translateX(calc(0 - 0%)) translateY(-10.25px) scale(0.75);
+ transform: translateX(calc(0 - 0%)) translateY(-10.25px) scale(0.75);
+ }
+ 33% {
+ -webkit-animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ -webkit-transform: translateX(calc(4% - 0%)) translateY(-10.25px) scale(0.75);
+ transform: translateX(calc(4% - 0%)) translateY(-10.25px) scale(0.75);
+ }
+ 66% {
+ -webkit-animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ -webkit-transform: translateX(calc(-4% - 0%)) translateY(-10.25px) scale(0.75);
+ transform: translateX(calc(-4% - 0%)) translateY(-10.25px) scale(0.75);
+ }
+ 100% {
+ -webkit-transform: translateX(calc(0 - 0%)) translateY(-10.25px) scale(0.75);
+ transform: translateX(calc(0 - 0%)) translateY(-10.25px) scale(0.75);
+ }
+}
+@keyframes mdc-floating-label-shake-float-above-textarea-filled {
+ 0% {
+ -webkit-transform: translateX(calc(0 - 0%)) translateY(-10.25px) scale(0.75);
+ transform: translateX(calc(0 - 0%)) translateY(-10.25px) scale(0.75);
+ }
+ 33% {
+ -webkit-animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ -webkit-transform: translateX(calc(4% - 0%)) translateY(-10.25px) scale(0.75);
+ transform: translateX(calc(4% - 0%)) translateY(-10.25px) scale(0.75);
+ }
+ 66% {
+ -webkit-animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ -webkit-transform: translateX(calc(-4% - 0%)) translateY(-10.25px) scale(0.75);
+ transform: translateX(calc(-4% - 0%)) translateY(-10.25px) scale(0.75);
+ }
+ 100% {
+ -webkit-transform: translateX(calc(0 - 0%)) translateY(-10.25px) scale(0.75);
+ transform: translateX(calc(0 - 0%)) translateY(-10.25px) scale(0.75);
+ }
+}
+.mdc-text-field--textarea.mdc-text-field--filled .mdc-text-field__input {
+ margin-top: 23px;
+ margin-bottom: 9px;
+}
+.mdc-text-field--textarea.mdc-text-field--filled.mdc-text-field--no-label .mdc-text-field__input {
+ margin-top: 16px;
+ margin-bottom: 16px;
+}
+.mdc-text-field--textarea.mdc-text-field--outlined .mdc-notched-outline--notched .mdc-notched-outline__notch {
+ padding-top: 0;
+}
+.mdc-text-field--textarea.mdc-text-field--outlined .mdc-floating-label--float-above {
+ -webkit-transform: translateY(-27.25px) scale(1);
+ transform: translateY(-27.25px) scale(1);
+}
+.mdc-text-field--textarea.mdc-text-field--outlined .mdc-floating-label--float-above {
+ font-size: 0.75rem;
+}
+.mdc-text-field--textarea.mdc-text-field--outlined.mdc-notched-outline--upgraded .mdc-floating-label--float-above,
+.mdc-text-field--textarea.mdc-text-field--outlined .mdc-notched-outline--upgraded .mdc-floating-label--float-above {
+ -webkit-transform: translateY(-24.75px) scale(0.75);
+ transform: translateY(-24.75px) scale(0.75);
+}
+.mdc-text-field--textarea.mdc-text-field--outlined.mdc-notched-outline--upgraded .mdc-floating-label--float-above,
+.mdc-text-field--textarea.mdc-text-field--outlined .mdc-notched-outline--upgraded .mdc-floating-label--float-above {
+ font-size: 1rem;
+}
+.mdc-text-field--textarea.mdc-text-field--outlined .mdc-floating-label--shake {
+ -webkit-animation: mdc-floating-label-shake-float-above-textarea-outlined 250ms 1;
+ animation: mdc-floating-label-shake-float-above-textarea-outlined 250ms 1;
+}
+@-webkit-keyframes mdc-floating-label-shake-float-above-textarea-outlined {
+ 0% {
+ -webkit-transform: translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75);
+ transform: translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75);
+ }
+ 33% {
+ -webkit-animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ -webkit-transform: translateX(calc(4% - 0%)) translateY(-24.75px) scale(0.75);
+ transform: translateX(calc(4% - 0%)) translateY(-24.75px) scale(0.75);
+ }
+ 66% {
+ -webkit-animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ -webkit-transform: translateX(calc(-4% - 0%)) translateY(-24.75px) scale(0.75);
+ transform: translateX(calc(-4% - 0%)) translateY(-24.75px) scale(0.75);
+ }
+ 100% {
+ -webkit-transform: translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75);
+ transform: translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75);
+ }
+}
+@keyframes mdc-floating-label-shake-float-above-textarea-outlined {
+ 0% {
+ -webkit-transform: translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75);
+ transform: translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75);
+ }
+ 33% {
+ -webkit-animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ -webkit-transform: translateX(calc(4% - 0%)) translateY(-24.75px) scale(0.75);
+ transform: translateX(calc(4% - 0%)) translateY(-24.75px) scale(0.75);
+ }
+ 66% {
+ -webkit-animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ -webkit-transform: translateX(calc(-4% - 0%)) translateY(-24.75px) scale(0.75);
+ transform: translateX(calc(-4% - 0%)) translateY(-24.75px) scale(0.75);
+ }
+ 100% {
+ -webkit-transform: translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75);
+ transform: translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75);
+ }
+}
+.mdc-text-field--textarea.mdc-text-field--outlined .mdc-text-field__input {
+ margin-top: 16px;
+ margin-bottom: 16px;
+}
+.mdc-text-field--textarea.mdc-text-field--outlined .mdc-floating-label {
+ top: 18px;
+}
+.mdc-text-field--textarea.mdc-text-field--with-internal-counter .mdc-text-field__input {
+ margin-bottom: 2px;
+}
+.mdc-text-field--textarea.mdc-text-field--with-internal-counter .mdc-text-field-character-counter {
+ align-self: flex-end;
+ padding: 0 16px;
+}
+.mdc-text-field--textarea.mdc-text-field--with-internal-counter .mdc-text-field-character-counter::after {
+ display: inline-block;
+ width: 0;
+ height: 16px;
+ content: "";
+ vertical-align: -16px;
+}
+.mdc-text-field--textarea.mdc-text-field--with-internal-counter .mdc-text-field-character-counter::before {
+ display: none;
+}
+
+.mdc-text-field__resizer {
+ align-self: stretch;
+ display: inline-flex;
+ flex-direction: column;
+ flex-grow: 1;
+ max-height: 100%;
+ max-width: 100%;
+ min-height: 56px;
+ min-width: -webkit-fit-content;
+ min-width: -moz-fit-content;
+ min-width: fit-content;
+ /* @alternate */
+ min-width: -moz-available;
+ /* @alternate */
+ min-width: -webkit-fill-available;
+ overflow: hidden;
+ resize: both;
+}
+.mdc-text-field--filled .mdc-text-field__resizer {
+ -webkit-transform: translateY(-1px);
+ transform: translateY(-1px);
+}
+.mdc-text-field--filled .mdc-text-field__resizer .mdc-text-field__input,
+.mdc-text-field--filled .mdc-text-field__resizer .mdc-text-field-character-counter {
+ -webkit-transform: translateY(1px);
+ transform: translateY(1px);
+}
+.mdc-text-field--outlined .mdc-text-field__resizer {
+ -webkit-transform: translateX(-1px) translateY(-1px);
+ transform: translateX(-1px) translateY(-1px);
+}
+[dir=rtl] .mdc-text-field--outlined .mdc-text-field__resizer, .mdc-text-field--outlined .mdc-text-field__resizer[dir=rtl] {
+ -webkit-transform: translateX(1px) translateY(-1px);
+ transform: translateX(1px) translateY(-1px);
+}
+
+.mdc-text-field--outlined .mdc-text-field__resizer .mdc-text-field__input,
+.mdc-text-field--outlined .mdc-text-field__resizer .mdc-text-field-character-counter {
+ -webkit-transform: translateX(1px) translateY(1px);
+ transform: translateX(1px) translateY(1px);
+}
+[dir=rtl] .mdc-text-field--outlined .mdc-text-field__resizer .mdc-text-field__input, [dir=rtl] .mdc-text-field--outlined .mdc-text-field__resizer .mdc-text-field-character-counter, .mdc-text-field--outlined .mdc-text-field__resizer .mdc-text-field__input[dir=rtl], .mdc-text-field--outlined .mdc-text-field__resizer .mdc-text-field-character-counter[dir=rtl] {
+ -webkit-transform: translateX(-1px) translateY(1px);
+ transform: translateX(-1px) translateY(1px);
+}
+
+.mdc-text-field--with-leading-icon {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: 16px;
+}
+[dir=rtl] .mdc-text-field--with-leading-icon, .mdc-text-field--with-leading-icon[dir=rtl] {
+ /* @noflip */
+ padding-left: 16px;
+ /* @noflip */
+ padding-right: 0;
+}
+
+.mdc-text-field--with-leading-icon.mdc-text-field--filled .mdc-floating-label {
+ max-width: calc(100% - 48px);
+ /* @noflip */
+ left: 48px;
+ /* @noflip */
+ right: initial;
+}
+[dir=rtl] .mdc-text-field--with-leading-icon.mdc-text-field--filled .mdc-floating-label, .mdc-text-field--with-leading-icon.mdc-text-field--filled .mdc-floating-label[dir=rtl] {
+ /* @noflip */
+ left: initial;
+ /* @noflip */
+ right: 48px;
+}
+
+.mdc-text-field--with-leading-icon.mdc-text-field--filled .mdc-floating-label--float-above {
+ max-width: calc(100% / 0.75 - 64px / 0.75);
+}
+.mdc-text-field--with-leading-icon.mdc-text-field--outlined .mdc-floating-label {
+ /* @noflip */
+ left: 36px;
+ /* @noflip */
+ right: initial;
+}
+[dir=rtl] .mdc-text-field--with-leading-icon.mdc-text-field--outlined .mdc-floating-label, .mdc-text-field--with-leading-icon.mdc-text-field--outlined .mdc-floating-label[dir=rtl] {
+ /* @noflip */
+ left: initial;
+ /* @noflip */
+ right: 36px;
+}
+
+.mdc-text-field--with-leading-icon.mdc-text-field--outlined :not(.mdc-notched-outline--notched) .mdc-notched-outline__notch {
+ max-width: calc(100% - 60px);
+}
+.mdc-text-field--with-leading-icon.mdc-text-field--outlined .mdc-floating-label--float-above {
+ -webkit-transform: translateY(-37.25px) translateX(-32px) scale(1);
+ transform: translateY(-37.25px) translateX(-32px) scale(1);
+}
+[dir=rtl] .mdc-text-field--with-leading-icon.mdc-text-field--outlined .mdc-floating-label--float-above, .mdc-text-field--with-leading-icon.mdc-text-field--outlined .mdc-floating-label--float-above[dir=rtl] {
+ -webkit-transform: translateY(-37.25px) translateX(32px) scale(1);
+ transform: translateY(-37.25px) translateX(32px) scale(1);
+}
+
+.mdc-text-field--with-leading-icon.mdc-text-field--outlined .mdc-floating-label--float-above {
+ font-size: 0.75rem;
+}
+.mdc-text-field--with-leading-icon.mdc-text-field--outlined.mdc-notched-outline--upgraded .mdc-floating-label--float-above,
+.mdc-text-field--with-leading-icon.mdc-text-field--outlined .mdc-notched-outline--upgraded .mdc-floating-label--float-above {
+ -webkit-transform: translateY(-34.75px) translateX(-32px) scale(0.75);
+ transform: translateY(-34.75px) translateX(-32px) scale(0.75);
+}
+[dir=rtl] .mdc-text-field--with-leading-icon.mdc-text-field--outlined.mdc-notched-outline--upgraded .mdc-floating-label--float-above, [dir=rtl] .mdc-text-field--with-leading-icon.mdc-text-field--outlined .mdc-notched-outline--upgraded .mdc-floating-label--float-above, .mdc-text-field--with-leading-icon.mdc-text-field--outlined.mdc-notched-outline--upgraded .mdc-floating-label--float-above[dir=rtl], .mdc-text-field--with-leading-icon.mdc-text-field--outlined .mdc-notched-outline--upgraded .mdc-floating-label--float-above[dir=rtl] {
+ -webkit-transform: translateY(-34.75px) translateX(32px) scale(0.75);
+ transform: translateY(-34.75px) translateX(32px) scale(0.75);
+}
+
+.mdc-text-field--with-leading-icon.mdc-text-field--outlined.mdc-notched-outline--upgraded .mdc-floating-label--float-above,
+.mdc-text-field--with-leading-icon.mdc-text-field--outlined .mdc-notched-outline--upgraded .mdc-floating-label--float-above {
+ font-size: 1rem;
+}
+.mdc-text-field--with-leading-icon.mdc-text-field--outlined .mdc-floating-label--shake {
+ -webkit-animation: mdc-floating-label-shake-float-above-text-field-outlined-leading-icon 250ms 1;
+ animation: mdc-floating-label-shake-float-above-text-field-outlined-leading-icon 250ms 1;
+}
+@-webkit-keyframes mdc-floating-label-shake-float-above-text-field-outlined-leading-icon {
+ 0% {
+ -webkit-transform: translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75);
+ }
+ 33% {
+ -webkit-animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ -webkit-transform: translateX(calc(4% - 32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(4% - 32px)) translateY(-34.75px) scale(0.75);
+ }
+ 66% {
+ -webkit-animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ -webkit-transform: translateX(calc(-4% - 32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(-4% - 32px)) translateY(-34.75px) scale(0.75);
+ }
+ 100% {
+ -webkit-transform: translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75);
+ }
+}
+@keyframes mdc-floating-label-shake-float-above-text-field-outlined-leading-icon {
+ 0% {
+ -webkit-transform: translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75);
+ }
+ 33% {
+ -webkit-animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ -webkit-transform: translateX(calc(4% - 32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(4% - 32px)) translateY(-34.75px) scale(0.75);
+ }
+ 66% {
+ -webkit-animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ -webkit-transform: translateX(calc(-4% - 32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(-4% - 32px)) translateY(-34.75px) scale(0.75);
+ }
+ 100% {
+ -webkit-transform: translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75);
+ }
+}
+[dir=rtl] .mdc-text-field--with-leading-icon.mdc-text-field--outlined .mdc-floating-label--shake, .mdc-text-field--with-leading-icon.mdc-text-field--outlined[dir=rtl] .mdc-floating-label--shake {
+ -webkit-animation: mdc-floating-label-shake-float-above-text-field-outlined-leading-icon 250ms 1;
+ animation: mdc-floating-label-shake-float-above-text-field-outlined-leading-icon 250ms 1;
+}
+
+@-webkit-keyframes mdc-floating-label-shake-float-above-text-field-outlined-leading-icon-rtl {
+ 0% {
+ -webkit-transform: translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75);
+ }
+ 33% {
+ -webkit-animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ -webkit-transform: translateX(calc(4% - -32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(4% - -32px)) translateY(-34.75px) scale(0.75);
+ }
+ 66% {
+ -webkit-animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ -webkit-transform: translateX(calc(-4% - -32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(-4% - -32px)) translateY(-34.75px) scale(0.75);
+ }
+ 100% {
+ -webkit-transform: translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75);
+ }
+}
+
+@keyframes mdc-floating-label-shake-float-above-text-field-outlined-leading-icon-rtl {
+ 0% {
+ -webkit-transform: translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75);
+ }
+ 33% {
+ -webkit-animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
+ -webkit-transform: translateX(calc(4% - -32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(4% - -32px)) translateY(-34.75px) scale(0.75);
+ }
+ 66% {
+ -webkit-animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
+ -webkit-transform: translateX(calc(-4% - -32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(-4% - -32px)) translateY(-34.75px) scale(0.75);
+ }
+ 100% {
+ -webkit-transform: translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75);
+ transform: translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75);
+ }
+}
+
+.mdc-text-field--with-trailing-icon {
+ /* @noflip */
+ padding-left: 16px;
+ /* @noflip */
+ padding-right: 0;
+}
+[dir=rtl] .mdc-text-field--with-trailing-icon, .mdc-text-field--with-trailing-icon[dir=rtl] {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: 16px;
+}
+
+.mdc-text-field--with-trailing-icon.mdc-text-field--filled .mdc-floating-label {
+ max-width: calc(100% - 64px);
+}
+.mdc-text-field--with-trailing-icon.mdc-text-field--filled .mdc-floating-label--float-above {
+ max-width: calc(100% / 0.75 - 64px / 0.75);
+}
+.mdc-text-field--with-trailing-icon.mdc-text-field--outlined :not(.mdc-notched-outline--notched) .mdc-notched-outline__notch {
+ max-width: calc(100% - 60px);
+}
+
+.mdc-text-field--with-leading-icon.mdc-text-field--with-trailing-icon {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: 0;
+}
+.mdc-text-field--with-leading-icon.mdc-text-field--with-trailing-icon.mdc-text-field--filled .mdc-floating-label {
+ max-width: calc(100% - 96px);
+}
+.mdc-text-field--with-leading-icon.mdc-text-field--with-trailing-icon.mdc-text-field--filled .mdc-floating-label--float-above {
+ max-width: calc(100% / 0.75 - 96px / 0.75);
+}
+
+.mdc-text-field-helper-line {
+ display: flex;
+ justify-content: space-between;
+ box-sizing: border-box;
+}
+.mdc-text-field + .mdc-text-field-helper-line {
+ padding-right: 16px;
+ padding-left: 16px;
+}
+
+.mdc-form-field > .mdc-text-field + label {
+ align-self: flex-start;
+}
+
+.mdc-text-field--focused:not(.mdc-text-field--disabled) .mdc-floating-label {
+ color: rgba(98, 0, 238, 0.87);
+}
+.mdc-text-field--focused .mdc-notched-outline__leading,
+.mdc-text-field--focused .mdc-notched-outline__notch,
+.mdc-text-field--focused .mdc-notched-outline__trailing {
+ border-width: 2px;
+}
+.mdc-text-field--focused + .mdc-text-field-helper-line .mdc-text-field-helper-text:not(.mdc-text-field-helper-text--validation-msg) {
+ opacity: 1;
+}
+.mdc-text-field--focused.mdc-text-field--outlined .mdc-notched-outline--notched .mdc-notched-outline__notch {
+ padding-top: 2px;
+}
+.mdc-text-field--focused.mdc-text-field--outlined.mdc-text-field--textarea .mdc-notched-outline--notched .mdc-notched-outline__notch {
+ padding-top: 0;
+}
+
+.mdc-text-field--invalid:not(.mdc-text-field--disabled):hover .mdc-line-ripple::before {
+ border-bottom-color: #b00020;
+ /* @alternate */
+ border-bottom-color: var(--mdc-theme-error, #b00020);
+}
+.mdc-text-field--invalid:not(.mdc-text-field--disabled) .mdc-line-ripple::after {
+ border-bottom-color: #b00020;
+ /* @alternate */
+ border-bottom-color: var(--mdc-theme-error, #b00020);
+}
+.mdc-text-field--invalid:not(.mdc-text-field--disabled) .mdc-floating-label {
+ color: #b00020;
+ /* @alternate */
+ color: var(--mdc-theme-error, #b00020);
+}
+.mdc-text-field--invalid:not(.mdc-text-field--disabled).mdc-text-field--invalid + .mdc-text-field-helper-line .mdc-text-field-helper-text--validation-msg {
+ color: #b00020;
+ /* @alternate */
+ color: var(--mdc-theme-error, #b00020);
+}
+.mdc-text-field--invalid .mdc-text-field__input {
+ caret-color: #b00020;
+ /* @alternate */
+ caret-color: var(--mdc-theme-error, #b00020);
+}
+.mdc-text-field--invalid:not(.mdc-text-field--disabled) .mdc-text-field__icon--trailing {
+ color: #b00020;
+ /* @alternate */
+ color: var(--mdc-theme-error, #b00020);
+}
+.mdc-text-field--invalid:not(.mdc-text-field--disabled) .mdc-line-ripple::before {
+ border-bottom-color: #b00020;
+ /* @alternate */
+ border-bottom-color: var(--mdc-theme-error, #b00020);
+}
+.mdc-text-field--invalid:not(.mdc-text-field--disabled) .mdc-notched-outline__leading,
+.mdc-text-field--invalid:not(.mdc-text-field--disabled) .mdc-notched-outline__notch,
+.mdc-text-field--invalid:not(.mdc-text-field--disabled) .mdc-notched-outline__trailing {
+ border-color: #b00020;
+ /* @alternate */
+ border-color: var(--mdc-theme-error, #b00020);
+}
+.mdc-text-field--invalid:not(.mdc-text-field--disabled):not(.mdc-text-field--focused):hover .mdc-notched-outline .mdc-notched-outline__leading,
+.mdc-text-field--invalid:not(.mdc-text-field--disabled):not(.mdc-text-field--focused):hover .mdc-notched-outline .mdc-notched-outline__notch,
+.mdc-text-field--invalid:not(.mdc-text-field--disabled):not(.mdc-text-field--focused):hover .mdc-notched-outline .mdc-notched-outline__trailing {
+ border-color: #b00020;
+ /* @alternate */
+ border-color: var(--mdc-theme-error, #b00020);
+}
+.mdc-text-field--invalid:not(.mdc-text-field--disabled).mdc-text-field--focused .mdc-notched-outline__leading,
+.mdc-text-field--invalid:not(.mdc-text-field--disabled).mdc-text-field--focused .mdc-notched-outline__notch,
+.mdc-text-field--invalid:not(.mdc-text-field--disabled).mdc-text-field--focused .mdc-notched-outline__trailing {
+ border-color: #b00020;
+ /* @alternate */
+ border-color: var(--mdc-theme-error, #b00020);
+}
+.mdc-text-field--invalid + .mdc-text-field-helper-line .mdc-text-field-helper-text--validation-msg {
+ opacity: 1;
+}
+
+.mdc-text-field--disabled {
+ pointer-events: none;
+}
+.mdc-text-field--disabled .mdc-text-field__input {
+ color: rgba(0, 0, 0, 0.38);
+}
+@media all {
+ .mdc-text-field--disabled .mdc-text-field__input::-webkit-input-placeholder {
+ color: rgba(0, 0, 0, 0.38);
+ }
+ .mdc-text-field--disabled .mdc-text-field__input:-ms-input-placeholder {
+ color: rgba(0, 0, 0, 0.38);
+ }
+ .mdc-text-field--disabled .mdc-text-field__input::-ms-input-placeholder {
+ color: rgba(0, 0, 0, 0.38);
+ }
+ .mdc-text-field--disabled .mdc-text-field__input::placeholder {
+ color: rgba(0, 0, 0, 0.38);
+ }
+}
+@media all {
+ .mdc-text-field--disabled .mdc-text-field__input:-ms-input-placeholder {
+ color: rgba(0, 0, 0, 0.38);
+ }
+}
+.mdc-text-field--disabled .mdc-floating-label {
+ color: rgba(0, 0, 0, 0.38);
+}
+.mdc-text-field--disabled + .mdc-text-field-helper-line .mdc-text-field-helper-text {
+ color: rgba(0, 0, 0, 0.38);
+}
+.mdc-text-field--disabled .mdc-text-field-character-counter,
+.mdc-text-field--disabled + .mdc-text-field-helper-line .mdc-text-field-character-counter {
+ color: rgba(0, 0, 0, 0.38);
+}
+.mdc-text-field--disabled .mdc-text-field__icon--leading {
+ color: rgba(0, 0, 0, 0.3);
+}
+.mdc-text-field--disabled .mdc-text-field__icon--trailing {
+ color: rgba(0, 0, 0, 0.3);
+}
+.mdc-text-field--disabled .mdc-text-field__affix--prefix {
+ color: rgba(0, 0, 0, 0.38);
+}
+.mdc-text-field--disabled .mdc-text-field__affix--suffix {
+ color: rgba(0, 0, 0, 0.38);
+}
+.mdc-text-field--disabled .mdc-line-ripple::before {
+ border-bottom-color: rgba(0, 0, 0, 0.06);
+}
+.mdc-text-field--disabled .mdc-notched-outline__leading,
+.mdc-text-field--disabled .mdc-notched-outline__notch,
+.mdc-text-field--disabled .mdc-notched-outline__trailing {
+ border-color: rgba(0, 0, 0, 0.06);
+}
+@media screen and (forced-colors: active), (-ms-high-contrast: active) {
+ .mdc-text-field--disabled .mdc-text-field__input::-webkit-input-placeholder {
+ color: GrayText;
+ }
+ .mdc-text-field--disabled .mdc-text-field__input:-ms-input-placeholder {
+ color: GrayText;
+ }
+ .mdc-text-field--disabled .mdc-text-field__input::-ms-input-placeholder {
+ color: GrayText;
+ }
+ .mdc-text-field--disabled .mdc-text-field__input::placeholder {
+ color: GrayText;
+ }
+}
+@media screen and (forced-colors: active), (-ms-high-contrast: active) {
+ .mdc-text-field--disabled .mdc-text-field__input:-ms-input-placeholder {
+ color: GrayText;
+ }
+}
+@media screen and (forced-colors: active), (-ms-high-contrast: active) {
+ .mdc-text-field--disabled .mdc-floating-label {
+ color: GrayText;
+ }
+}
+@media screen and (forced-colors: active), (-ms-high-contrast: active) {
+ .mdc-text-field--disabled + .mdc-text-field-helper-line .mdc-text-field-helper-text {
+ color: GrayText;
+ }
+}
+@media screen and (forced-colors: active), (-ms-high-contrast: active) {
+ .mdc-text-field--disabled .mdc-text-field-character-counter,
+.mdc-text-field--disabled + .mdc-text-field-helper-line .mdc-text-field-character-counter {
+ color: GrayText;
+ }
+}
+@media screen and (forced-colors: active), (-ms-high-contrast: active) {
+ .mdc-text-field--disabled .mdc-text-field__icon--leading {
+ color: GrayText;
+ }
+}
+@media screen and (forced-colors: active), (-ms-high-contrast: active) {
+ .mdc-text-field--disabled .mdc-text-field__icon--trailing {
+ color: GrayText;
+ }
+}
+@media screen and (forced-colors: active), (-ms-high-contrast: active) {
+ .mdc-text-field--disabled .mdc-text-field__affix--prefix {
+ color: GrayText;
+ }
+}
+@media screen and (forced-colors: active), (-ms-high-contrast: active) {
+ .mdc-text-field--disabled .mdc-text-field__affix--suffix {
+ color: GrayText;
+ }
+}
+@media screen and (forced-colors: active), (-ms-high-contrast: active) {
+ .mdc-text-field--disabled .mdc-line-ripple::before {
+ border-bottom-color: GrayText;
+ }
+}
+@media screen and (forced-colors: active), (-ms-high-contrast: active) {
+ .mdc-text-field--disabled .mdc-notched-outline__leading,
+.mdc-text-field--disabled .mdc-notched-outline__notch,
+.mdc-text-field--disabled .mdc-notched-outline__trailing {
+ border-color: GrayText;
+ }
+}
+@media screen and (forced-colors: active) {
+ .mdc-text-field--disabled .mdc-text-field__input {
+ background-color: Window;
+ }
+ .mdc-text-field--disabled .mdc-floating-label {
+ z-index: 1;
+ }
+}
+.mdc-text-field--disabled .mdc-floating-label {
+ cursor: default;
+}
+.mdc-text-field--disabled.mdc-text-field--filled {
+ background-color: #fafafa;
+}
+.mdc-text-field--disabled.mdc-text-field--filled .mdc-text-field__ripple {
+ display: none;
+}
+.mdc-text-field--disabled .mdc-text-field__input {
+ pointer-events: auto;
+}
+
+.mdc-text-field--end-aligned .mdc-text-field__input {
+ /* @noflip */
+ text-align: right;
+}
+[dir=rtl] .mdc-text-field--end-aligned .mdc-text-field__input, .mdc-text-field--end-aligned .mdc-text-field__input[dir=rtl] {
+ /* @noflip */
+ text-align: left;
+}
+
+[dir=rtl] .mdc-text-field--ltr-text .mdc-text-field__input,
+[dir=rtl] .mdc-text-field--ltr-text .mdc-text-field__affix, .mdc-text-field--ltr-text[dir=rtl] .mdc-text-field__input,
+.mdc-text-field--ltr-text[dir=rtl] .mdc-text-field__affix {
+ /* @noflip */
+ direction: ltr;
+}
+[dir=rtl] .mdc-text-field--ltr-text .mdc-text-field__affix--prefix, .mdc-text-field--ltr-text[dir=rtl] .mdc-text-field__affix--prefix {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: 2px;
+}
+[dir=rtl] .mdc-text-field--ltr-text .mdc-text-field__affix--suffix, .mdc-text-field--ltr-text[dir=rtl] .mdc-text-field__affix--suffix {
+ /* @noflip */
+ padding-left: 12px;
+ /* @noflip */
+ padding-right: 0;
+}
+[dir=rtl] .mdc-text-field--ltr-text .mdc-text-field__icon--leading, .mdc-text-field--ltr-text[dir=rtl] .mdc-text-field__icon--leading {
+ order: 1;
+}
+[dir=rtl] .mdc-text-field--ltr-text .mdc-text-field__affix--suffix, .mdc-text-field--ltr-text[dir=rtl] .mdc-text-field__affix--suffix {
+ order: 2;
+}
+[dir=rtl] .mdc-text-field--ltr-text .mdc-text-field__input, .mdc-text-field--ltr-text[dir=rtl] .mdc-text-field__input {
+ order: 3;
+}
+[dir=rtl] .mdc-text-field--ltr-text .mdc-text-field__affix--prefix, .mdc-text-field--ltr-text[dir=rtl] .mdc-text-field__affix--prefix {
+ order: 4;
+}
+[dir=rtl] .mdc-text-field--ltr-text .mdc-text-field__icon--trailing, .mdc-text-field--ltr-text[dir=rtl] .mdc-text-field__icon--trailing {
+ order: 5;
+}
+
+[dir=rtl] .mdc-text-field--ltr-text.mdc-text-field--end-aligned .mdc-text-field__input, .mdc-text-field--ltr-text.mdc-text-field--end-aligned[dir=rtl] .mdc-text-field__input {
+ /* @noflip */
+ text-align: right;
+}
+[dir=rtl] .mdc-text-field--ltr-text.mdc-text-field--end-aligned .mdc-text-field__affix--prefix, .mdc-text-field--ltr-text.mdc-text-field--end-aligned[dir=rtl] .mdc-text-field__affix--prefix {
+ /* @noflip */
+ padding-right: 12px;
+}
+[dir=rtl] .mdc-text-field--ltr-text.mdc-text-field--end-aligned .mdc-text-field__affix--suffix, .mdc-text-field--ltr-text.mdc-text-field--end-aligned[dir=rtl] .mdc-text-field__affix--suffix {
+ /* @noflip */
+ padding-left: 2px;
+}
+
+.mdc-text-field-helper-text {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-caption-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.75rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-caption-font-size, 0.75rem);
+ line-height: 1.25rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-caption-line-height, 1.25rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-caption-font-weight, 400);
+ letter-spacing: 0.0333333333em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-caption-letter-spacing, 0.0333333333em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-caption-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-caption-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-caption-text-transform, inherit);
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+ margin: 0;
+ opacity: 0;
+ will-change: opacity;
+ transition: opacity 150ms 0ms cubic-bezier(0.4, 0, 0.2, 1);
+}
+.mdc-text-field-helper-text::before {
+ display: inline-block;
+ width: 0;
+ height: 16px;
+ content: "";
+ vertical-align: 0;
+}
+
+.mdc-text-field-helper-text--persistent {
+ transition: none;
+ opacity: 1;
+ will-change: initial;
+}
+
+.mdc-text-field-character-counter {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-caption-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.75rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-caption-font-size, 0.75rem);
+ line-height: 1.25rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-caption-line-height, 1.25rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-caption-font-weight, 400);
+ letter-spacing: 0.0333333333em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-caption-letter-spacing, 0.0333333333em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-caption-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-caption-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-caption-text-transform, inherit);
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+ /* @noflip */
+ margin-left: auto;
+ /* @noflip */
+ margin-right: 0;
+ /* @noflip */
+ padding-left: 16px;
+ /* @noflip */
+ padding-right: 0;
+ white-space: nowrap;
+}
+.mdc-text-field-character-counter::before {
+ display: inline-block;
+ width: 0;
+ height: 16px;
+ content: "";
+ vertical-align: 0;
+}
+[dir=rtl] .mdc-text-field-character-counter, .mdc-text-field-character-counter[dir=rtl] {
+ /* @noflip */
+ margin-left: 0;
+ /* @noflip */
+ margin-right: auto;
+}
+
+[dir=rtl] .mdc-text-field-character-counter, .mdc-text-field-character-counter[dir=rtl] {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: 16px;
+}
+
+.mdc-text-field__icon {
+ align-self: center;
+ cursor: pointer;
+}
+.mdc-text-field__icon:not([tabindex]), .mdc-text-field__icon[tabindex="-1"] {
+ cursor: default;
+ pointer-events: none;
+}
+.mdc-text-field__icon svg {
+ display: block;
+}
+
+.mdc-text-field__icon--leading {
+ /* @noflip */
+ margin-left: 16px;
+ /* @noflip */
+ margin-right: 8px;
+}
+[dir=rtl] .mdc-text-field__icon--leading, .mdc-text-field__icon--leading[dir=rtl] {
+ /* @noflip */
+ margin-left: 8px;
+ /* @noflip */
+ margin-right: 16px;
+}
+
+.mdc-text-field__icon--trailing {
+ padding: 12px;
+ /* @noflip */
+ margin-left: 0px;
+ /* @noflip */
+ margin-right: 0px;
+}
+[dir=rtl] .mdc-text-field__icon--trailing, .mdc-text-field__icon--trailing[dir=rtl] {
+ /* @noflip */
+ margin-left: 0px;
+ /* @noflip */
+ margin-right: 0px;
+}
+
+:root {
+ --mdc-theme-primary: #6200ee;
+ --mdc-theme-secondary: #018786;
+ --mdc-theme-background: #fff;
+ --mdc-theme-surface: #fff;
+ --mdc-theme-error: #b00020;
+ --mdc-theme-on-primary: #fff;
+ --mdc-theme-on-secondary: #fff;
+ --mdc-theme-on-surface: #000;
+ --mdc-theme-on-error: #fff;
+ --mdc-theme-text-primary-on-background: rgba(0, 0, 0, 0.87);
+ --mdc-theme-text-secondary-on-background: rgba(0, 0, 0, 0.54);
+ --mdc-theme-text-hint-on-background: rgba(0, 0, 0, 0.38);
+ --mdc-theme-text-disabled-on-background: rgba(0, 0, 0, 0.38);
+ --mdc-theme-text-icon-on-background: rgba(0, 0, 0, 0.38);
+ --mdc-theme-text-primary-on-light: rgba(0, 0, 0, 0.87);
+ --mdc-theme-text-secondary-on-light: rgba(0, 0, 0, 0.54);
+ --mdc-theme-text-hint-on-light: rgba(0, 0, 0, 0.38);
+ --mdc-theme-text-disabled-on-light: rgba(0, 0, 0, 0.38);
+ --mdc-theme-text-icon-on-light: rgba(0, 0, 0, 0.38);
+ --mdc-theme-text-primary-on-dark: white;
+ --mdc-theme-text-secondary-on-dark: rgba(255, 255, 255, 0.7);
+ --mdc-theme-text-hint-on-dark: rgba(255, 255, 255, 0.5);
+ --mdc-theme-text-disabled-on-dark: rgba(255, 255, 255, 0.5);
+ --mdc-theme-text-icon-on-dark: rgba(255, 255, 255, 0.5);
+}
+
+.mdc-theme--primary {
+ color: #6200ee !important;
+ /* @alternate */
+ color: var(--mdc-theme-primary, #6200ee) !important;
+}
+
+.mdc-theme--secondary {
+ color: #018786 !important;
+ /* @alternate */
+ color: var(--mdc-theme-secondary, #018786) !important;
+}
+
+.mdc-theme--background {
+ background-color: #fff;
+ /* @alternate */
+ background-color: var(--mdc-theme-background, #fff);
+}
+
+.mdc-theme--surface {
+ background-color: #fff;
+ /* @alternate */
+ background-color: var(--mdc-theme-surface, #fff);
+}
+
+.mdc-theme--error {
+ color: #b00020 !important;
+ /* @alternate */
+ color: var(--mdc-theme-error, #b00020) !important;
+}
+
+.mdc-theme--on-primary {
+ color: #fff !important;
+ /* @alternate */
+ color: var(--mdc-theme-on-primary, #fff) !important;
+}
+
+.mdc-theme--on-secondary {
+ color: #fff !important;
+ /* @alternate */
+ color: var(--mdc-theme-on-secondary, #fff) !important;
+}
+
+.mdc-theme--on-surface {
+ color: #000 !important;
+ /* @alternate */
+ color: var(--mdc-theme-on-surface, #000) !important;
+}
+
+.mdc-theme--on-error {
+ color: #fff !important;
+ /* @alternate */
+ color: var(--mdc-theme-on-error, #fff) !important;
+}
+
+.mdc-theme--text-primary-on-background {
+ color: rgba(0, 0, 0, 0.87) !important;
+ /* @alternate */
+ color: var(--mdc-theme-text-primary-on-background, rgba(0, 0, 0, 0.87)) !important;
+}
+
+.mdc-theme--text-secondary-on-background {
+ color: rgba(0, 0, 0, 0.54) !important;
+ /* @alternate */
+ color: var(--mdc-theme-text-secondary-on-background, rgba(0, 0, 0, 0.54)) !important;
+}
+
+.mdc-theme--text-hint-on-background {
+ color: rgba(0, 0, 0, 0.38) !important;
+ /* @alternate */
+ color: var(--mdc-theme-text-hint-on-background, rgba(0, 0, 0, 0.38)) !important;
+}
+
+.mdc-theme--text-disabled-on-background {
+ color: rgba(0, 0, 0, 0.38) !important;
+ /* @alternate */
+ color: var(--mdc-theme-text-disabled-on-background, rgba(0, 0, 0, 0.38)) !important;
+}
+
+.mdc-theme--text-icon-on-background {
+ color: rgba(0, 0, 0, 0.38) !important;
+ /* @alternate */
+ color: var(--mdc-theme-text-icon-on-background, rgba(0, 0, 0, 0.38)) !important;
+}
+
+.mdc-theme--text-primary-on-light {
+ color: rgba(0, 0, 0, 0.87) !important;
+ /* @alternate */
+ color: var(--mdc-theme-text-primary-on-light, rgba(0, 0, 0, 0.87)) !important;
+}
+
+.mdc-theme--text-secondary-on-light {
+ color: rgba(0, 0, 0, 0.54) !important;
+ /* @alternate */
+ color: var(--mdc-theme-text-secondary-on-light, rgba(0, 0, 0, 0.54)) !important;
+}
+
+.mdc-theme--text-hint-on-light {
+ color: rgba(0, 0, 0, 0.38) !important;
+ /* @alternate */
+ color: var(--mdc-theme-text-hint-on-light, rgba(0, 0, 0, 0.38)) !important;
+}
+
+.mdc-theme--text-disabled-on-light {
+ color: rgba(0, 0, 0, 0.38) !important;
+ /* @alternate */
+ color: var(--mdc-theme-text-disabled-on-light, rgba(0, 0, 0, 0.38)) !important;
+}
+
+.mdc-theme--text-icon-on-light {
+ color: rgba(0, 0, 0, 0.38) !important;
+ /* @alternate */
+ color: var(--mdc-theme-text-icon-on-light, rgba(0, 0, 0, 0.38)) !important;
+}
+
+.mdc-theme--text-primary-on-dark {
+ color: white !important;
+ /* @alternate */
+ color: var(--mdc-theme-text-primary-on-dark, white) !important;
+}
+
+.mdc-theme--text-secondary-on-dark {
+ color: rgba(255, 255, 255, 0.7) !important;
+ /* @alternate */
+ color: var(--mdc-theme-text-secondary-on-dark, rgba(255, 255, 255, 0.7)) !important;
+}
+
+.mdc-theme--text-hint-on-dark {
+ color: rgba(255, 255, 255, 0.5) !important;
+ /* @alternate */
+ color: var(--mdc-theme-text-hint-on-dark, rgba(255, 255, 255, 0.5)) !important;
+}
+
+.mdc-theme--text-disabled-on-dark {
+ color: rgba(255, 255, 255, 0.5) !important;
+ /* @alternate */
+ color: var(--mdc-theme-text-disabled-on-dark, rgba(255, 255, 255, 0.5)) !important;
+}
+
+.mdc-theme--text-icon-on-dark {
+ color: rgba(255, 255, 255, 0.5) !important;
+ /* @alternate */
+ color: var(--mdc-theme-text-icon-on-dark, rgba(255, 255, 255, 0.5)) !important;
+}
+
+.mdc-theme--primary-bg {
+ background-color: #6200ee !important;
+ /* @alternate */
+ background-color: var(--mdc-theme-primary, #6200ee) !important;
+}
+
+.mdc-theme--secondary-bg {
+ background-color: #018786 !important;
+ /* @alternate */
+ background-color: var(--mdc-theme-secondary, #018786) !important;
+}
+
+.mdc-tooltip__surface {
+ border-radius: 4px;
+ /* @alternate */
+ border-radius: var(--mdc-shape-small, 4px);
+}
+
+.mdc-tooltip__caret-surface-top,
+.mdc-tooltip__caret-surface-bottom {
+ border-radius: 4px;
+ /* @alternate */
+ border-radius: var(--mdc-shape-small, 4px);
+}
+
+.mdc-tooltip__surface {
+ color: white;
+ /* @alternate */
+ color: var(--mdc-theme-text-primary-on-dark, white);
+}
+
+.mdc-tooltip__surface {
+ background-color: rgba(0, 0, 0, 0.6);
+}
+
+.mdc-tooltip__surface {
+ word-break: break-all;
+ /* @alternate */
+ word-break: var(--mdc-tooltip-word-break, normal);
+ overflow-wrap: anywhere;
+}
+
+.mdc-tooltip {
+ z-index: 9;
+}
+
+.mdc-tooltip--showing-transition .mdc-tooltip__surface-animation {
+ transition: opacity 150ms 0ms cubic-bezier(0, 0, 0.2, 1), -webkit-transform 150ms 0ms cubic-bezier(0, 0, 0.2, 1);
+ transition: opacity 150ms 0ms cubic-bezier(0, 0, 0.2, 1), transform 150ms 0ms cubic-bezier(0, 0, 0.2, 1);
+ transition: opacity 150ms 0ms cubic-bezier(0, 0, 0.2, 1), transform 150ms 0ms cubic-bezier(0, 0, 0.2, 1), -webkit-transform 150ms 0ms cubic-bezier(0, 0, 0.2, 1);
+}
+
+.mdc-tooltip--hide-transition .mdc-tooltip__surface-animation {
+ transition: opacity 75ms 0ms cubic-bezier(0.4, 0, 1, 1);
+}
+
+.mdc-tooltip__title {
+ color: rgba(0, 0, 0, 0.87);
+ /* @alternate */
+ color: var(--mdc-theme-text-primary-on-light, rgba(0, 0, 0, 0.87));
+}
+
+.mdc-tooltip__content {
+ color: rgba(0, 0, 0, 0.6);
+}
+
+.mdc-tooltip__content-link {
+ color: #6200ee;
+ /* @alternate */
+ color: var(--mdc-theme-primary, #6200ee);
+}
+
+.mdc-tooltip {
+ position: fixed;
+ display: none;
+}
+.mdc-tooltip.mdc-tooltip--rich .mdc-tooltip__surface {
+ background-color: #fff;
+}
+.mdc-tooltip.mdc-tooltip--rich .mdc-tooltip__caret-surface-top,
+.mdc-tooltip.mdc-tooltip--rich .mdc-tooltip__caret-surface-bottom {
+ background-color: #fff;
+}
+
+.mdc-tooltip-wrapper--rich {
+ position: relative;
+}
+
+.mdc-tooltip--shown,
+.mdc-tooltip--showing,
+.mdc-tooltip--hide {
+ display: inline-flex;
+}
+.mdc-tooltip--shown.mdc-tooltip--rich,
+.mdc-tooltip--showing.mdc-tooltip--rich,
+.mdc-tooltip--hide.mdc-tooltip--rich {
+ display: inline-block;
+ /* @noflip */
+ left: -320px;
+ position: absolute;
+}
+
+.mdc-tooltip__surface {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-caption-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.75rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-caption-font-size, 0.75rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-caption-font-weight, 400);
+ letter-spacing: 0.0333333333em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-caption-letter-spacing, 0.0333333333em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-caption-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-caption-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-caption-text-transform, inherit);
+ line-height: 16px;
+ padding: 4px 8px;
+ min-width: 40px;
+ max-width: 200px;
+ min-height: 24px;
+ max-height: 40vh;
+ box-sizing: border-box;
+ overflow: hidden;
+ text-align: center;
+}
+.mdc-tooltip__surface::before {
+ position: absolute;
+ box-sizing: border-box;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ border: 1px solid transparent;
+ border-radius: inherit;
+ content: "";
+ pointer-events: none;
+}
+.mdc-tooltip--rich .mdc-tooltip__surface {
+ /* @alternate */
+ box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12);
+ align-items: flex-start;
+ border-radius: 4px;
+ display: flex;
+ flex-direction: column;
+ line-height: 20px;
+ min-height: 24px;
+ min-width: 40px;
+ max-width: 320px;
+ position: relative;
+}
+.mdc-tooltip--rich .mdc-tooltip__surface .mdc-elevation-overlay {
+ width: 100%;
+ height: 100%;
+ top: 0;
+ /* @noflip */
+ left: 0;
+}
+.mdc-tooltip--multiline .mdc-tooltip__surface {
+ /* @noflip */
+ text-align: left;
+}
+[dir=rtl] .mdc-tooltip--multiline .mdc-tooltip__surface, .mdc-tooltip--multiline .mdc-tooltip__surface[dir=rtl] {
+ /* @noflip */
+ text-align: right;
+}
+
+.mdc-tooltip__surface .mdc-tooltip__title {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-subtitle2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.875rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-subtitle2-font-size, 0.875rem);
+ font-weight: 500;
+ /* @alternate */
+ font-weight: var(--mdc-typography-subtitle2-font-weight, 500);
+ letter-spacing: 0.0071428571em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-subtitle2-letter-spacing, 0.0071428571em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-subtitle2-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-subtitle2-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-subtitle2-text-transform, inherit);
+ margin: 0 8px;
+}
+.mdc-tooltip__surface .mdc-tooltip__title::before {
+ display: inline-block;
+ width: 0;
+ height: 28px;
+ content: "";
+ vertical-align: 0;
+}
+.mdc-tooltip__surface .mdc-tooltip__content {
+ display: block;
+ margin-top: 0;
+ /* @alternate */
+ line-height: normal;
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.875rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-body2-font-size, 0.875rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-body2-font-weight, 400);
+ letter-spacing: 0.0178571429em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-body2-letter-spacing, 0.0178571429em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-body2-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-body2-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-body2-text-transform, inherit);
+ max-width: calc(100% - 2 * 8px);
+ margin: 0 8px 16px 8px;
+ /* @noflip */
+ text-align: left;
+}
+.mdc-tooltip__surface .mdc-tooltip__content::before {
+ display: inline-block;
+ width: 0;
+ height: 24px;
+ content: "";
+ vertical-align: 0;
+}
+[dir=rtl] .mdc-tooltip__surface .mdc-tooltip__content, .mdc-tooltip__surface .mdc-tooltip__content[dir=rtl] {
+ /* @noflip */
+ text-align: right;
+}
+
+.mdc-tooltip__surface .mdc-tooltip__content-link {
+ text-decoration: none;
+}
+
+.mdc-tooltip__surface-animation {
+ opacity: 0;
+ -webkit-transform: scale(0.8);
+ transform: scale(0.8);
+ will-change: transform, opacity;
+}
+.mdc-tooltip--shown .mdc-tooltip__surface-animation {
+ -webkit-transform: scale(1);
+ transform: scale(1);
+ opacity: 1;
+}
+.mdc-tooltip--hide .mdc-tooltip__surface-animation {
+ -webkit-transform: scale(1);
+ transform: scale(1);
+}
+
+.mdc-tooltip__caret-surface-top,
+.mdc-tooltip__caret-surface-bottom {
+ position: absolute;
+ height: 24px;
+ width: 24px;
+ -webkit-transform: rotate(35deg) skewY(20deg) scaleX(0.9396926208);
+ transform: rotate(35deg) skewY(20deg) scaleX(0.9396926208);
+}
+.mdc-tooltip__caret-surface-top .mdc-elevation-overlay,
+.mdc-tooltip__caret-surface-bottom .mdc-elevation-overlay {
+ width: 100%;
+ height: 100%;
+ top: 0;
+ /* @noflip */
+ left: 0;
+}
+
+.mdc-tooltip__caret-surface-bottom {
+ /* @alternate */
+ box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12);
+ outline: 1px solid transparent;
+ z-index: -1;
+}
+
+.mdc-top-app-bar {
+ background-color: #6200ee;
+ /* @alternate */
+ background-color: var(--mdc-theme-primary, #6200ee);
+ color: white;
+ display: flex;
+ position: fixed;
+ flex-direction: column;
+ justify-content: space-between;
+ box-sizing: border-box;
+ width: 100%;
+ z-index: 4;
+}
+.mdc-top-app-bar .mdc-top-app-bar__action-item,
+.mdc-top-app-bar .mdc-top-app-bar__navigation-icon {
+ color: #fff;
+ /* @alternate */
+ color: var(--mdc-theme-on-primary, #fff);
+}
+.mdc-top-app-bar .mdc-top-app-bar__action-item::before, .mdc-top-app-bar .mdc-top-app-bar__action-item::after,
+.mdc-top-app-bar .mdc-top-app-bar__navigation-icon::before,
+.mdc-top-app-bar .mdc-top-app-bar__navigation-icon::after {
+ background-color: #fff;
+ /* @alternate */
+ background-color: var(--mdc-ripple-color, var(--mdc-theme-on-primary, #fff));
+}
+.mdc-top-app-bar .mdc-top-app-bar__action-item:hover::before, .mdc-top-app-bar .mdc-top-app-bar__action-item.mdc-ripple-surface--hover::before,
+.mdc-top-app-bar .mdc-top-app-bar__navigation-icon:hover::before,
+.mdc-top-app-bar .mdc-top-app-bar__navigation-icon.mdc-ripple-surface--hover::before {
+ opacity: 0.08;
+ /* @alternate */
+ opacity: var(--mdc-ripple-hover-opacity, 0.08);
+}
+.mdc-top-app-bar .mdc-top-app-bar__action-item.mdc-ripple-upgraded--background-focused::before, .mdc-top-app-bar .mdc-top-app-bar__action-item:not(.mdc-ripple-upgraded):focus::before,
+.mdc-top-app-bar .mdc-top-app-bar__navigation-icon.mdc-ripple-upgraded--background-focused::before,
+.mdc-top-app-bar .mdc-top-app-bar__navigation-icon:not(.mdc-ripple-upgraded):focus::before {
+ transition-duration: 75ms;
+ opacity: 0.24;
+ /* @alternate */
+ opacity: var(--mdc-ripple-focus-opacity, 0.24);
+}
+.mdc-top-app-bar .mdc-top-app-bar__action-item:not(.mdc-ripple-upgraded)::after,
+.mdc-top-app-bar .mdc-top-app-bar__navigation-icon:not(.mdc-ripple-upgraded)::after {
+ transition: opacity 150ms linear;
+}
+.mdc-top-app-bar .mdc-top-app-bar__action-item:not(.mdc-ripple-upgraded):active::after,
+.mdc-top-app-bar .mdc-top-app-bar__navigation-icon:not(.mdc-ripple-upgraded):active::after {
+ transition-duration: 75ms;
+ opacity: 0.24;
+ /* @alternate */
+ opacity: var(--mdc-ripple-press-opacity, 0.24);
+}
+.mdc-top-app-bar .mdc-top-app-bar__action-item.mdc-ripple-upgraded,
+.mdc-top-app-bar .mdc-top-app-bar__navigation-icon.mdc-ripple-upgraded {
+ --mdc-ripple-fg-opacity: var(--mdc-ripple-press-opacity, 0.24);
+}
+.mdc-top-app-bar__row {
+ display: flex;
+ position: relative;
+ box-sizing: border-box;
+ width: 100%;
+ height: 64px;
+}
+.mdc-top-app-bar__section {
+ display: inline-flex;
+ flex: 1 1 auto;
+ align-items: center;
+ min-width: 0;
+ padding: 8px 12px;
+ z-index: 1;
+}
+.mdc-top-app-bar__section--align-start {
+ justify-content: flex-start;
+ order: -1;
+}
+.mdc-top-app-bar__section--align-end {
+ justify-content: flex-end;
+ order: 1;
+}
+.mdc-top-app-bar__title {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-headline6-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 1.25rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-headline6-font-size, 1.25rem);
+ line-height: 2rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-headline6-line-height, 2rem);
+ font-weight: 500;
+ /* @alternate */
+ font-weight: var(--mdc-typography-headline6-font-weight, 500);
+ letter-spacing: 0.0125em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-headline6-letter-spacing, 0.0125em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-headline6-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-headline6-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-headline6-text-transform, inherit);
+ /* @noflip */
+ padding-left: 20px;
+ /* @noflip */
+ padding-right: 0;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ z-index: 1;
+}
+[dir=rtl] .mdc-top-app-bar__title, .mdc-top-app-bar__title[dir=rtl] {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: 20px;
+}
+
+.mdc-top-app-bar--short-collapsed {
+ /* @noflip */
+ border-top-left-radius: 0;
+ /* @noflip */
+ border-top-right-radius: 0;
+ /* @noflip */
+ border-bottom-right-radius: 24px;
+ /* @noflip */
+ border-bottom-left-radius: 0;
+}
+[dir=rtl] .mdc-top-app-bar--short-collapsed, .mdc-top-app-bar--short-collapsed[dir=rtl] {
+ /* @noflip */
+ border-top-left-radius: 0;
+ /* @noflip */
+ border-top-right-radius: 0;
+ /* @noflip */
+ border-bottom-right-radius: 0;
+ /* @noflip */
+ border-bottom-left-radius: 24px;
+}
+
+.mdc-top-app-bar--short {
+ top: 0;
+ /* @noflip */
+ right: auto;
+ /* @noflip */
+ left: 0;
+ width: 100%;
+ transition: width 250ms cubic-bezier(0.4, 0, 0.2, 1);
+}
+[dir=rtl] .mdc-top-app-bar--short, .mdc-top-app-bar--short[dir=rtl] {
+ /* @noflip */
+ right: 0;
+ /* @noflip */
+ left: auto;
+}
+
+.mdc-top-app-bar--short .mdc-top-app-bar__row {
+ height: 56px;
+}
+.mdc-top-app-bar--short .mdc-top-app-bar__section {
+ padding: 4px;
+}
+.mdc-top-app-bar--short .mdc-top-app-bar__title {
+ transition: opacity 200ms cubic-bezier(0.4, 0, 0.2, 1);
+ opacity: 1;
+}
+
+.mdc-top-app-bar--short-collapsed {
+ /* @alternate */
+ box-shadow: 0px 2px 4px -1px rgba(0, 0, 0, 0.2), 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12);
+ width: 56px;
+ transition: width 300ms cubic-bezier(0.4, 0, 0.2, 1);
+}
+.mdc-top-app-bar--short-collapsed .mdc-top-app-bar__title {
+ display: none;
+}
+.mdc-top-app-bar--short-collapsed .mdc-top-app-bar__action-item {
+ transition: padding 150ms cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.mdc-top-app-bar--short-collapsed.mdc-top-app-bar--short-has-action-item {
+ width: 112px;
+}
+.mdc-top-app-bar--short-collapsed.mdc-top-app-bar--short-has-action-item .mdc-top-app-bar__section--align-end {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: 12px;
+}
+[dir=rtl] .mdc-top-app-bar--short-collapsed.mdc-top-app-bar--short-has-action-item .mdc-top-app-bar__section--align-end, .mdc-top-app-bar--short-collapsed.mdc-top-app-bar--short-has-action-item .mdc-top-app-bar__section--align-end[dir=rtl] {
+ /* @noflip */
+ padding-left: 12px;
+ /* @noflip */
+ padding-right: 0;
+}
+
+.mdc-top-app-bar--dense .mdc-top-app-bar__row {
+ height: 48px;
+}
+.mdc-top-app-bar--dense .mdc-top-app-bar__section {
+ padding: 0 4px;
+}
+.mdc-top-app-bar--dense .mdc-top-app-bar__title {
+ /* @noflip */
+ padding-left: 12px;
+ /* @noflip */
+ padding-right: 0;
+}
+[dir=rtl] .mdc-top-app-bar--dense .mdc-top-app-bar__title, .mdc-top-app-bar--dense .mdc-top-app-bar__title[dir=rtl] {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: 12px;
+}
+
+.mdc-top-app-bar--prominent .mdc-top-app-bar__row {
+ height: 128px;
+}
+.mdc-top-app-bar--prominent .mdc-top-app-bar__title {
+ align-self: flex-end;
+ padding-bottom: 2px;
+}
+.mdc-top-app-bar--prominent .mdc-top-app-bar__action-item,
+.mdc-top-app-bar--prominent .mdc-top-app-bar__navigation-icon {
+ align-self: flex-start;
+}
+
+.mdc-top-app-bar--fixed {
+ transition: box-shadow 200ms linear;
+}
+
+.mdc-top-app-bar--fixed-scrolled {
+ /* @alternate */
+ box-shadow: 0px 2px 4px -1px rgba(0, 0, 0, 0.2), 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12);
+ transition: box-shadow 200ms linear;
+}
+
+.mdc-top-app-bar--dense.mdc-top-app-bar--prominent .mdc-top-app-bar__row {
+ height: 96px;
+}
+.mdc-top-app-bar--dense.mdc-top-app-bar--prominent .mdc-top-app-bar__section {
+ padding: 0 12px;
+}
+.mdc-top-app-bar--dense.mdc-top-app-bar--prominent .mdc-top-app-bar__title {
+ /* @noflip */
+ padding-left: 20px;
+ /* @noflip */
+ padding-right: 0;
+ padding-bottom: 9px;
+}
+[dir=rtl] .mdc-top-app-bar--dense.mdc-top-app-bar--prominent .mdc-top-app-bar__title, .mdc-top-app-bar--dense.mdc-top-app-bar--prominent .mdc-top-app-bar__title[dir=rtl] {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: 20px;
+}
+
+.mdc-top-app-bar--fixed-adjust {
+ padding-top: 64px;
+}
+
+.mdc-top-app-bar--dense-fixed-adjust {
+ padding-top: 48px;
+}
+
+.mdc-top-app-bar--short-fixed-adjust {
+ padding-top: 56px;
+}
+
+.mdc-top-app-bar--prominent-fixed-adjust {
+ padding-top: 128px;
+}
+
+.mdc-top-app-bar--dense-prominent-fixed-adjust {
+ padding-top: 96px;
+}
+
+@media (max-width: 599px) {
+ .mdc-top-app-bar__row {
+ height: 56px;
+ }
+
+ .mdc-top-app-bar__section {
+ padding: 4px;
+ }
+
+ .mdc-top-app-bar--short {
+ transition: width 200ms cubic-bezier(0.4, 0, 0.2, 1);
+ }
+
+ .mdc-top-app-bar--short-collapsed {
+ transition: width 250ms cubic-bezier(0.4, 0, 0.2, 1);
+ }
+ .mdc-top-app-bar--short-collapsed .mdc-top-app-bar__section--align-end {
+ /* @noflip */
+ padding-left: 0;
+ /* @noflip */
+ padding-right: 12px;
+ }
+ [dir=rtl] .mdc-top-app-bar--short-collapsed .mdc-top-app-bar__section--align-end, .mdc-top-app-bar--short-collapsed .mdc-top-app-bar__section--align-end[dir=rtl] {
+ /* @noflip */
+ padding-left: 12px;
+ /* @noflip */
+ padding-right: 0;
+ }
+
+ .mdc-top-app-bar--prominent .mdc-top-app-bar__title {
+ padding-bottom: 6px;
+ }
+
+ .mdc-top-app-bar--fixed-adjust {
+ padding-top: 56px;
+ }
+}
+.mdc-typography {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-font-family, Roboto, sans-serif);
+}
+
+.mdc-typography--headline1 {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-headline1-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 6rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-headline1-font-size, 6rem);
+ line-height: 6rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-headline1-line-height, 6rem);
+ font-weight: 300;
+ /* @alternate */
+ font-weight: var(--mdc-typography-headline1-font-weight, 300);
+ letter-spacing: -0.015625em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-headline1-letter-spacing, -0.015625em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-headline1-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-headline1-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-headline1-text-transform, inherit);
+}
+
+.mdc-typography--headline2 {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-headline2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 3.75rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-headline2-font-size, 3.75rem);
+ line-height: 3.75rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-headline2-line-height, 3.75rem);
+ font-weight: 300;
+ /* @alternate */
+ font-weight: var(--mdc-typography-headline2-font-weight, 300);
+ letter-spacing: -0.0083333333em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-headline2-letter-spacing, -0.0083333333em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-headline2-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-headline2-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-headline2-text-transform, inherit);
+}
+
+.mdc-typography--headline3 {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-headline3-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 3rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-headline3-font-size, 3rem);
+ line-height: 3.125rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-headline3-line-height, 3.125rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-headline3-font-weight, 400);
+ letter-spacing: normal;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-headline3-letter-spacing, normal);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-headline3-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-headline3-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-headline3-text-transform, inherit);
+}
+
+.mdc-typography--headline4 {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-headline4-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 2.125rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-headline4-font-size, 2.125rem);
+ line-height: 2.5rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-headline4-line-height, 2.5rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-headline4-font-weight, 400);
+ letter-spacing: 0.0073529412em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-headline4-letter-spacing, 0.0073529412em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-headline4-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-headline4-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-headline4-text-transform, inherit);
+}
+
+.mdc-typography--headline5 {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-headline5-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 1.5rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-headline5-font-size, 1.5rem);
+ line-height: 2rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-headline5-line-height, 2rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-headline5-font-weight, 400);
+ letter-spacing: normal;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-headline5-letter-spacing, normal);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-headline5-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-headline5-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-headline5-text-transform, inherit);
+}
+
+.mdc-typography--headline6 {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-headline6-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 1.25rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-headline6-font-size, 1.25rem);
+ line-height: 2rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-headline6-line-height, 2rem);
+ font-weight: 500;
+ /* @alternate */
+ font-weight: var(--mdc-typography-headline6-font-weight, 500);
+ letter-spacing: 0.0125em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-headline6-letter-spacing, 0.0125em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-headline6-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-headline6-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-headline6-text-transform, inherit);
+}
+
+.mdc-typography--subtitle1 {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-subtitle1-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 1rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-subtitle1-font-size, 1rem);
+ line-height: 1.75rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-subtitle1-line-height, 1.75rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-subtitle1-font-weight, 400);
+ letter-spacing: 0.009375em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-subtitle1-letter-spacing, 0.009375em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-subtitle1-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-subtitle1-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-subtitle1-text-transform, inherit);
+}
+
+.mdc-typography--subtitle2 {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-subtitle2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.875rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-subtitle2-font-size, 0.875rem);
+ line-height: 1.375rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-subtitle2-line-height, 1.375rem);
+ font-weight: 500;
+ /* @alternate */
+ font-weight: var(--mdc-typography-subtitle2-font-weight, 500);
+ letter-spacing: 0.0071428571em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-subtitle2-letter-spacing, 0.0071428571em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-subtitle2-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-subtitle2-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-subtitle2-text-transform, inherit);
+}
+
+.mdc-typography--body1 {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-body1-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 1rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-body1-font-size, 1rem);
+ line-height: 1.5rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-body1-line-height, 1.5rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-body1-font-weight, 400);
+ letter-spacing: 0.03125em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-body1-letter-spacing, 0.03125em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-body1-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-body1-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-body1-text-transform, inherit);
+}
+
+.mdc-typography--body2 {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.875rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-body2-font-size, 0.875rem);
+ line-height: 1.25rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-body2-line-height, 1.25rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-body2-font-weight, 400);
+ letter-spacing: 0.0178571429em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-body2-letter-spacing, 0.0178571429em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-body2-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-body2-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-body2-text-transform, inherit);
+}
+
+.mdc-typography--caption {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-caption-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.75rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-caption-font-size, 0.75rem);
+ line-height: 1.25rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-caption-line-height, 1.25rem);
+ font-weight: 400;
+ /* @alternate */
+ font-weight: var(--mdc-typography-caption-font-weight, 400);
+ letter-spacing: 0.0333333333em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-caption-letter-spacing, 0.0333333333em);
+ text-decoration: inherit;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-caption-text-decoration, inherit);
+ text-decoration: var(--mdc-typography-caption-text-decoration, inherit);
+ text-transform: inherit;
+ /* @alternate */
+ text-transform: var(--mdc-typography-caption-text-transform, inherit);
+}
+
+.mdc-typography--button {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-button-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.875rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-button-font-size, 0.875rem);
+ line-height: 2.25rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-button-line-height, 2.25rem);
+ font-weight: 500;
+ /* @alternate */
+ font-weight: var(--mdc-typography-button-font-weight, 500);
+ letter-spacing: 0.0892857143em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-button-letter-spacing, 0.0892857143em);
+ text-decoration: none;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-button-text-decoration, none);
+ text-decoration: var(--mdc-typography-button-text-decoration, none);
+ text-transform: uppercase;
+ /* @alternate */
+ text-transform: var(--mdc-typography-button-text-transform, uppercase);
+}
+
+.mdc-typography--overline {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ font-family: Roboto, sans-serif;
+ /* @alternate */
+ font-family: var(--mdc-typography-overline-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));
+ font-size: 0.75rem;
+ /* @alternate */
+ font-size: var(--mdc-typography-overline-font-size, 0.75rem);
+ line-height: 2rem;
+ /* @alternate */
+ line-height: var(--mdc-typography-overline-line-height, 2rem);
+ font-weight: 500;
+ /* @alternate */
+ font-weight: var(--mdc-typography-overline-font-weight, 500);
+ letter-spacing: 0.1666666667em;
+ /* @alternate */
+ letter-spacing: var(--mdc-typography-overline-letter-spacing, 0.1666666667em);
+ text-decoration: none;
+ /* @alternate */
+ -webkit-text-decoration: var(--mdc-typography-overline-text-decoration, none);
+ text-decoration: var(--mdc-typography-overline-text-decoration, none);
+ text-transform: uppercase;
+ /* @alternate */
+ text-transform: var(--mdc-typography-overline-text-transform, uppercase);
+}
+
+/*# sourceMappingURL=material-components-web.css.map*/
\ No newline at end of file
diff --git a/pkgs/csslib/third_party/mdc/material-components-web.min.css b/pkgs/csslib/third_party/mdc/material-components-web.min.css
new file mode 100644
index 0000000..7194839
--- /dev/null
+++ b/pkgs/csslib/third_party/mdc/material-components-web.min.css
@@ -0,0 +1,10 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://github.com/material-components/material-components-web/blob/master/LICENSE
+ */
+ .mdc-banner__text{color:#000;color:var(--mdc-theme-on-surface, #000)}.mdc-banner__graphic{color:#fff;color:var(--mdc-theme-surface, #fff)}.mdc-banner__graphic{background-color:#6200ee;background-color:var(--mdc-theme-primary, #6200ee)}.mdc-banner__graphic{border-radius:50%}.mdc-banner__content,.mdc-banner__fixed{min-width:344px}@media(max-width: 480px),(max-width: 344px){.mdc-banner__content,.mdc-banner__fixed{min-width:100%}}.mdc-banner__content{max-width:720px}.mdc-banner{z-index:1;border-bottom-style:solid;border-bottom-width:1px;box-sizing:border-box;display:none;flex-shrink:0;height:0;position:relative;width:100%}@media(max-width: 480px){.mdc-banner .mdc-banner__fixed{left:0;right:0}.mdc-banner .mdc-banner__text{margin-left:16px;margin-right:36px}[dir=rtl] .mdc-banner .mdc-banner__text,.mdc-banner .mdc-banner__text[dir=rtl]{margin-left:36px;margin-right:16px}}@media(max-width: 480px){.mdc-banner.mdc-banner--mobile-stacked .mdc-banner__content{flex-wrap:wrap}.mdc-banner.mdc-banner--mobile-stacked .mdc-banner__graphic{margin-bottom:12px}.mdc-banner.mdc-banner--mobile-stacked .mdc-banner__text{margin-left:16px;margin-right:8px;padding-bottom:4px}[dir=rtl] .mdc-banner.mdc-banner--mobile-stacked .mdc-banner__text,.mdc-banner.mdc-banner--mobile-stacked .mdc-banner__text[dir=rtl]{margin-left:8px;margin-right:16px}.mdc-banner.mdc-banner--mobile-stacked .mdc-banner__actions{margin-left:auto}}.mdc-banner--opening,.mdc-banner--open,.mdc-banner--closing{display:flex}.mdc-banner--open{transition:height 300ms ease}.mdc-banner--open .mdc-banner__content{transition:-webkit-transform 300ms ease;transition:transform 300ms ease;transition:transform 300ms ease, -webkit-transform 300ms ease;-webkit-transform:translateY(0);transform:translateY(0)}.mdc-banner--closing{transition:height 250ms ease}.mdc-banner--closing .mdc-banner__content{transition:-webkit-transform 250ms ease;transition:transform 250ms ease;transition:transform 250ms ease, -webkit-transform 250ms ease}.mdc-banner--centered .mdc-banner__content{left:0;margin-left:auto;margin-right:auto;right:0}.mdc-banner__fixed{border-bottom-style:solid;border-bottom-width:1px;box-sizing:border-box;height:inherit;position:fixed;width:100%}.mdc-banner__content{display:flex;min-height:52px;position:absolute;-webkit-transform:translateY(-100%);transform:translateY(-100%);width:100%}.mdc-banner__graphic-text-wrapper{display:flex;width:100%}.mdc-banner__graphic{margin-left:16px;margin-right:0;flex-shrink:0;height:40px;margin-top:16px;margin-bottom:16px;text-align:center;width:40px}[dir=rtl] .mdc-banner__graphic,.mdc-banner__graphic[dir=rtl]{margin-left:0;margin-right:16px}.mdc-banner__icon{position:relative;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.mdc-banner__text{margin-left:24px;margin-right:90px;align-self:center;flex-grow:1;padding-top:16px;padding-bottom:16px}[dir=rtl] .mdc-banner__text,.mdc-banner__text[dir=rtl]{margin-left:90px;margin-right:24px}.mdc-banner__actions{padding-left:0;padding-right:8px;align-self:flex-end;display:flex;flex-shrink:0;padding-bottom:8px;padding-top:8px}[dir=rtl] .mdc-banner__actions,.mdc-banner__actions[dir=rtl]{padding-left:8px;padding-right:0}.mdc-banner__secondary-action{margin-left:0;margin-right:8px}[dir=rtl] .mdc-banner__secondary-action,.mdc-banner__secondary-action[dir=rtl]{margin-left:8px;margin-right:0}.mdc-banner{background-color:#fff;background-color:var(--mdc-theme-surface, #fff);border-bottom-color:rgba(0, 0, 0, 0.12)}.mdc-banner .mdc-banner__fixed{background-color:#fff;background-color:var(--mdc-theme-surface, #fff)}.mdc-banner .mdc-banner__fixed{border-bottom-color:rgba(0, 0, 0, 0.12)}.mdc-banner__text{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.875rem;font-size:var(--mdc-typography-body2-font-size, 0.875rem);line-height:1.25rem;line-height:var(--mdc-typography-body2-line-height, 1.25rem);font-weight:400;font-weight:var(--mdc-typography-body2-font-weight, 400);letter-spacing:0.0178571429em;letter-spacing:var(--mdc-typography-body2-letter-spacing, 0.0178571429em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-body2-text-transform, inherit)}.mdc-banner__primary-action:not(:disabled){color:#6200ee;color:var(--mdc-theme-primary, #6200ee)}.mdc-banner__primary-action::before,.mdc-banner__primary-action::after{background-color:#6200ee;background-color:var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee))}.mdc-banner__primary-action:hover::before,.mdc-banner__primary-action.mdc-ripple-surface--hover::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}.mdc-banner__primary-action.mdc-ripple-upgraded--background-focused::before,.mdc-banner__primary-action:not(.mdc-ripple-upgraded):focus::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-banner__primary-action:not(.mdc-ripple-upgraded)::after{transition:opacity 150ms linear}.mdc-banner__primary-action:not(.mdc-ripple-upgraded):active::after{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-banner__primary-action.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-banner__secondary-action{margin-left:0;margin-right:8px}.mdc-banner__secondary-action:not(:disabled){color:#6200ee;color:var(--mdc-theme-primary, #6200ee)}.mdc-banner__secondary-action::before,.mdc-banner__secondary-action::after{background-color:#6200ee;background-color:var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee))}.mdc-banner__secondary-action:hover::before,.mdc-banner__secondary-action.mdc-ripple-surface--hover::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}.mdc-banner__secondary-action.mdc-ripple-upgraded--background-focused::before,.mdc-banner__secondary-action:not(.mdc-ripple-upgraded):focus::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-banner__secondary-action:not(.mdc-ripple-upgraded)::after{transition:opacity 150ms linear}.mdc-banner__secondary-action:not(.mdc-ripple-upgraded):active::after{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-banner__secondary-action.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.12)}[dir=rtl] .mdc-banner__secondary-action,.mdc-banner__secondary-action[dir=rtl]{margin-left:8px;margin-right:0}.mdc-touch-target-wrapper{display:inline}.mdc-elevation-overlay{position:absolute;border-radius:inherit;pointer-events:none;opacity:0;opacity:var(--mdc-elevation-overlay-opacity, 0);transition:opacity 280ms cubic-bezier(0.4, 0, 0.2, 1);background-color:#fff;background-color:var(--mdc-elevation-overlay-color, #fff)}.mdc-button{position:relative;display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;min-width:64px;border:none;outline:none;line-height:inherit;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-appearance:none;overflow:visible;vertical-align:middle;background:transparent}.mdc-button .mdc-elevation-overlay{width:100%;height:100%;top:0;left:0}.mdc-button::-moz-focus-inner{padding:0;border:0}.mdc-button:active{outline:none}.mdc-button:hover{cursor:pointer}.mdc-button:disabled{cursor:default;pointer-events:none}.mdc-button .mdc-button__icon{margin-left:0;margin-right:8px;display:inline-block;position:relative;vertical-align:top}[dir=rtl] .mdc-button .mdc-button__icon,.mdc-button .mdc-button__icon[dir=rtl]{margin-left:8px;margin-right:0}.mdc-button .mdc-button__touch{position:absolute;top:50%;height:48px;left:0;right:0;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.mdc-button__label+.mdc-button__icon{margin-left:8px;margin-right:0}[dir=rtl] .mdc-button__label+.mdc-button__icon,.mdc-button__label+.mdc-button__icon[dir=rtl]{margin-left:0;margin-right:8px}svg.mdc-button__icon{fill:currentColor}.mdc-button--raised .mdc-button__icon,.mdc-button--unelevated .mdc-button__icon,.mdc-button--outlined .mdc-button__icon{margin-left:-4px;margin-right:8px}[dir=rtl] .mdc-button--raised .mdc-button__icon,[dir=rtl] .mdc-button--unelevated .mdc-button__icon,[dir=rtl] .mdc-button--outlined .mdc-button__icon,.mdc-button--raised .mdc-button__icon[dir=rtl],.mdc-button--unelevated .mdc-button__icon[dir=rtl],.mdc-button--outlined .mdc-button__icon[dir=rtl]{margin-left:8px;margin-right:-4px}.mdc-button--raised .mdc-button__label+.mdc-button__icon,.mdc-button--unelevated .mdc-button__label+.mdc-button__icon,.mdc-button--outlined .mdc-button__label+.mdc-button__icon{margin-left:8px;margin-right:-4px}[dir=rtl] .mdc-button--raised .mdc-button__label+.mdc-button__icon,[dir=rtl] .mdc-button--unelevated .mdc-button__label+.mdc-button__icon,[dir=rtl] .mdc-button--outlined .mdc-button__label+.mdc-button__icon,.mdc-button--raised .mdc-button__label+.mdc-button__icon[dir=rtl],.mdc-button--unelevated .mdc-button__label+.mdc-button__icon[dir=rtl],.mdc-button--outlined .mdc-button__label+.mdc-button__icon[dir=rtl]{margin-left:-4px;margin-right:8px}.mdc-button--touch{margin-top:6px;margin-bottom:6px}.mdc-button{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-button-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));text-decoration:none;-webkit-text-decoration:var(--mdc-typography-button-text-decoration, none);text-decoration:var(--mdc-typography-button-text-decoration, none)}@-webkit-keyframes mdc-ripple-fg-radius-in{from{-webkit-animation-timing-function:cubic-bezier(0.4, 0, 0.2, 1);animation-timing-function:cubic-bezier(0.4, 0, 0.2, 1);-webkit-transform:translate(var(--mdc-ripple-fg-translate-start, 0)) scale(1);transform:translate(var(--mdc-ripple-fg-translate-start, 0)) scale(1)}to{-webkit-transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1))}}@keyframes mdc-ripple-fg-radius-in{from{-webkit-animation-timing-function:cubic-bezier(0.4, 0, 0.2, 1);animation-timing-function:cubic-bezier(0.4, 0, 0.2, 1);-webkit-transform:translate(var(--mdc-ripple-fg-translate-start, 0)) scale(1);transform:translate(var(--mdc-ripple-fg-translate-start, 0)) scale(1)}to{-webkit-transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1))}}@-webkit-keyframes mdc-ripple-fg-opacity-in{from{-webkit-animation-timing-function:linear;animation-timing-function:linear;opacity:0}to{opacity:var(--mdc-ripple-fg-opacity, 0)}}@keyframes mdc-ripple-fg-opacity-in{from{-webkit-animation-timing-function:linear;animation-timing-function:linear;opacity:0}to{opacity:var(--mdc-ripple-fg-opacity, 0)}}@-webkit-keyframes mdc-ripple-fg-opacity-out{from{-webkit-animation-timing-function:linear;animation-timing-function:linear;opacity:var(--mdc-ripple-fg-opacity, 0)}to{opacity:0}}@keyframes mdc-ripple-fg-opacity-out{from{-webkit-animation-timing-function:linear;animation-timing-function:linear;opacity:var(--mdc-ripple-fg-opacity, 0)}to{opacity:0}}.mdc-button{--mdc-ripple-fg-size: 0;--mdc-ripple-left: 0;--mdc-ripple-top: 0;--mdc-ripple-fg-scale: 1;--mdc-ripple-fg-translate-end: 0;--mdc-ripple-fg-translate-start: 0;-webkit-tap-highlight-color:rgba(0,0,0,0);will-change:transform,opacity}.mdc-button .mdc-button__ripple::before,.mdc-button .mdc-button__ripple::after{position:absolute;border-radius:50%;opacity:0;pointer-events:none;content:""}.mdc-button .mdc-button__ripple::before{transition:opacity 15ms linear,background-color 15ms linear;z-index:1;z-index:var(--mdc-ripple-z-index, 1)}.mdc-button .mdc-button__ripple::after{z-index:0;z-index:var(--mdc-ripple-z-index, 0)}.mdc-button.mdc-ripple-upgraded .mdc-button__ripple::before{-webkit-transform:scale(var(--mdc-ripple-fg-scale, 1));transform:scale(var(--mdc-ripple-fg-scale, 1))}.mdc-button.mdc-ripple-upgraded .mdc-button__ripple::after{top:0;left:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:center center;transform-origin:center center}.mdc-button.mdc-ripple-upgraded--unbounded .mdc-button__ripple::after{top:var(--mdc-ripple-top, 0);left:var(--mdc-ripple-left, 0)}.mdc-button.mdc-ripple-upgraded--foreground-activation .mdc-button__ripple::after{-webkit-animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards;animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards}.mdc-button.mdc-ripple-upgraded--foreground-deactivation .mdc-button__ripple::after{-webkit-animation:mdc-ripple-fg-opacity-out 150ms;animation:mdc-ripple-fg-opacity-out 150ms;-webkit-transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1))}.mdc-button .mdc-button__ripple::before,.mdc-button .mdc-button__ripple::after{top:calc(50% - 100%);left:calc(50% - 100%);width:200%;height:200%}.mdc-button.mdc-ripple-upgraded .mdc-button__ripple::after{width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}.mdc-button .mdc-button__ripple{position:absolute;box-sizing:content-box;width:100%;height:100%;overflow:hidden}.mdc-button:not(.mdc-button--outlined) .mdc-button__ripple{top:0;left:0}.mdc-button--raised{transition:box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1)}.mdc-button--outlined{border-style:solid}.mdc-button{font-family:Roboto, sans-serif;font-family:var(--mdc-typography-button-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.875rem;font-size:var(--mdc-typography-button-font-size, 0.875rem);letter-spacing:0.0892857143em;letter-spacing:var(--mdc-typography-button-letter-spacing, 0.0892857143em);font-weight:500;font-weight:var(--mdc-typography-button-font-weight, 500);text-transform:uppercase;text-transform:var(--mdc-typography-button-text-transform, uppercase);height:36px;border-radius:4px;border-radius:var(--mdc-shape-small, 4px);padding:0 8px 0 8px}.mdc-button:not(:disabled){color:#6200ee;color:var(--mdc-theme-primary, #6200ee)}.mdc-button:disabled{color:rgba(0, 0, 0, 0.38)}.mdc-button .mdc-button__icon{font-size:1.125rem;height:1.125rem;width:1.125rem}.mdc-button .mdc-button__ripple::before,.mdc-button .mdc-button__ripple::after{background-color:#6200ee;background-color:var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee))}.mdc-button:hover .mdc-button__ripple::before,.mdc-button.mdc-ripple-surface--hover .mdc-button__ripple::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}.mdc-button.mdc-ripple-upgraded--background-focused .mdc-button__ripple::before,.mdc-button:not(.mdc-ripple-upgraded):focus .mdc-button__ripple::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-button:not(.mdc-ripple-upgraded) .mdc-button__ripple::after{transition:opacity 150ms linear}.mdc-button:not(.mdc-ripple-upgraded):active .mdc-button__ripple::after{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-button.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-button .mdc-button__ripple{border-radius:4px;border-radius:var(--mdc-shape-small, 4px)}.mdc-button--unelevated{padding:0 16px 0 16px;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-button-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.875rem;font-size:var(--mdc-typography-button-font-size, 0.875rem);letter-spacing:0.0892857143em;letter-spacing:var(--mdc-typography-button-letter-spacing, 0.0892857143em);font-weight:500;font-weight:var(--mdc-typography-button-font-weight, 500);text-transform:uppercase;text-transform:var(--mdc-typography-button-text-transform, uppercase);height:36px;border-radius:4px;border-radius:var(--mdc-shape-small, 4px)}.mdc-button--unelevated.mdc-button--icon-trailing{padding:0 12px 0 16px}.mdc-button--unelevated.mdc-button--icon-leading{padding:0 16px 0 12px}.mdc-button--unelevated:not(:disabled){background-color:#6200ee;background-color:var(--mdc-theme-primary, #6200ee)}.mdc-button--unelevated:disabled{background-color:rgba(0, 0, 0, 0.12)}.mdc-button--unelevated:not(:disabled){color:#fff;color:var(--mdc-theme-on-primary, #fff)}.mdc-button--unelevated:disabled{color:rgba(0, 0, 0, 0.38)}.mdc-button--unelevated .mdc-button__icon{font-size:1.125rem;height:1.125rem;width:1.125rem}.mdc-button--unelevated .mdc-button__ripple::before,.mdc-button--unelevated .mdc-button__ripple::after{background-color:#fff;background-color:var(--mdc-ripple-color, var(--mdc-theme-on-primary, #fff))}.mdc-button--unelevated:hover .mdc-button__ripple::before,.mdc-button--unelevated.mdc-ripple-surface--hover .mdc-button__ripple::before{opacity:0.08;opacity:var(--mdc-ripple-hover-opacity, 0.08)}.mdc-button--unelevated.mdc-ripple-upgraded--background-focused .mdc-button__ripple::before,.mdc-button--unelevated:not(.mdc-ripple-upgraded):focus .mdc-button__ripple::before{transition-duration:75ms;opacity:0.24;opacity:var(--mdc-ripple-focus-opacity, 0.24)}.mdc-button--unelevated:not(.mdc-ripple-upgraded) .mdc-button__ripple::after{transition:opacity 150ms linear}.mdc-button--unelevated:not(.mdc-ripple-upgraded):active .mdc-button__ripple::after{transition-duration:75ms;opacity:0.24;opacity:var(--mdc-ripple-press-opacity, 0.24)}.mdc-button--unelevated.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.24)}.mdc-button--unelevated .mdc-button__ripple{border-radius:4px;border-radius:var(--mdc-shape-small, 4px)}.mdc-button--raised{padding:0 16px 0 16px;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-button-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.875rem;font-size:var(--mdc-typography-button-font-size, 0.875rem);letter-spacing:0.0892857143em;letter-spacing:var(--mdc-typography-button-letter-spacing, 0.0892857143em);font-weight:500;font-weight:var(--mdc-typography-button-font-weight, 500);text-transform:uppercase;text-transform:var(--mdc-typography-button-text-transform, uppercase);height:36px;border-radius:4px;border-radius:var(--mdc-shape-small, 4px);box-shadow:0px 3px 1px -2px rgba(0, 0, 0, 0.2),0px 2px 2px 0px rgba(0, 0, 0, 0.14),0px 1px 5px 0px rgba(0,0,0,.12)}.mdc-button--raised.mdc-button--icon-trailing{padding:0 12px 0 16px}.mdc-button--raised.mdc-button--icon-leading{padding:0 16px 0 12px}.mdc-button--raised:not(:disabled){background-color:#6200ee;background-color:var(--mdc-theme-primary, #6200ee)}.mdc-button--raised:disabled{background-color:rgba(0, 0, 0, 0.12)}.mdc-button--raised:not(:disabled){color:#fff;color:var(--mdc-theme-on-primary, #fff)}.mdc-button--raised:disabled{color:rgba(0, 0, 0, 0.38)}.mdc-button--raised .mdc-button__icon{font-size:1.125rem;height:1.125rem;width:1.125rem}.mdc-button--raised .mdc-button__ripple::before,.mdc-button--raised .mdc-button__ripple::after{background-color:#fff;background-color:var(--mdc-ripple-color, var(--mdc-theme-on-primary, #fff))}.mdc-button--raised:hover .mdc-button__ripple::before,.mdc-button--raised.mdc-ripple-surface--hover .mdc-button__ripple::before{opacity:0.08;opacity:var(--mdc-ripple-hover-opacity, 0.08)}.mdc-button--raised.mdc-ripple-upgraded--background-focused .mdc-button__ripple::before,.mdc-button--raised:not(.mdc-ripple-upgraded):focus .mdc-button__ripple::before{transition-duration:75ms;opacity:0.24;opacity:var(--mdc-ripple-focus-opacity, 0.24)}.mdc-button--raised:not(.mdc-ripple-upgraded) .mdc-button__ripple::after{transition:opacity 150ms linear}.mdc-button--raised:not(.mdc-ripple-upgraded):active .mdc-button__ripple::after{transition-duration:75ms;opacity:0.24;opacity:var(--mdc-ripple-press-opacity, 0.24)}.mdc-button--raised.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.24)}.mdc-button--raised .mdc-button__ripple{border-radius:4px;border-radius:var(--mdc-shape-small, 4px)}.mdc-button--raised.mdc-ripple-upgraded--background-focused,.mdc-button--raised:not(.mdc-ripple-upgraded):focus{box-shadow:0px 2px 4px -1px rgba(0, 0, 0, 0.2),0px 4px 5px 0px rgba(0, 0, 0, 0.14),0px 1px 10px 0px rgba(0,0,0,.12)}.mdc-button--raised:hover{box-shadow:0px 2px 4px -1px rgba(0, 0, 0, 0.2),0px 4px 5px 0px rgba(0, 0, 0, 0.14),0px 1px 10px 0px rgba(0,0,0,.12)}.mdc-button--raised:not(:disabled):active{box-shadow:0px 5px 5px -3px rgba(0, 0, 0, 0.2),0px 8px 10px 1px rgba(0, 0, 0, 0.14),0px 3px 14px 2px rgba(0,0,0,.12)}.mdc-button--raised:disabled{box-shadow:0px 0px 0px 0px rgba(0, 0, 0, 0.2),0px 0px 0px 0px rgba(0, 0, 0, 0.14),0px 0px 0px 0px rgba(0,0,0,.12)}.mdc-button--outlined{font-family:Roboto, sans-serif;font-family:var(--mdc-typography-button-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.875rem;font-size:var(--mdc-typography-button-font-size, 0.875rem);letter-spacing:0.0892857143em;letter-spacing:var(--mdc-typography-button-letter-spacing, 0.0892857143em);font-weight:500;font-weight:var(--mdc-typography-button-font-weight, 500);text-transform:uppercase;text-transform:var(--mdc-typography-button-text-transform, uppercase);height:36px;border-radius:4px;border-radius:var(--mdc-shape-small, 4px);padding:0 15px 0 15px;border-width:1px}.mdc-button--outlined:not(:disabled){color:#6200ee;color:var(--mdc-theme-primary, #6200ee)}.mdc-button--outlined:disabled{color:rgba(0, 0, 0, 0.38)}.mdc-button--outlined .mdc-button__icon{font-size:1.125rem;height:1.125rem;width:1.125rem}.mdc-button--outlined .mdc-button__ripple::before,.mdc-button--outlined .mdc-button__ripple::after{background-color:#6200ee;background-color:var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee))}.mdc-button--outlined:hover .mdc-button__ripple::before,.mdc-button--outlined.mdc-ripple-surface--hover .mdc-button__ripple::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}.mdc-button--outlined.mdc-ripple-upgraded--background-focused .mdc-button__ripple::before,.mdc-button--outlined:not(.mdc-ripple-upgraded):focus .mdc-button__ripple::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-button--outlined:not(.mdc-ripple-upgraded) .mdc-button__ripple::after{transition:opacity 150ms linear}.mdc-button--outlined:not(.mdc-ripple-upgraded):active .mdc-button__ripple::after{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-button--outlined.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-button--outlined .mdc-button__ripple{border-radius:4px;border-radius:var(--mdc-shape-small, 4px)}.mdc-button--outlined:not(:disabled){border-color:rgba(0, 0, 0, 0.12)}.mdc-button--outlined:disabled{border-color:rgba(0, 0, 0, 0.12)}.mdc-button--outlined.mdc-button--icon-trailing{padding:0 11px 0 15px}.mdc-button--outlined.mdc-button--icon-leading{padding:0 15px 0 11px}.mdc-button--outlined .mdc-button__ripple{top:-1px;left:-1px;border:1px solid transparent}.mdc-button--outlined .mdc-button__touch{left:-1px;width:calc(100% + 2 * 1px)}.mdc-card{border-radius:4px;border-radius:var(--mdc-shape-medium, 4px);background-color:#fff;background-color:var(--mdc-theme-surface, #fff);position:relative;box-shadow:0px 2px 1px -1px rgba(0, 0, 0, 0.2),0px 1px 1px 0px rgba(0, 0, 0, 0.14),0px 1px 3px 0px rgba(0,0,0,.12);display:flex;flex-direction:column;box-sizing:border-box}.mdc-card .mdc-elevation-overlay{width:100%;height:100%;top:0;left:0}.mdc-card::after{border-radius:4px;border-radius:var(--mdc-shape-medium, 4px);position:absolute;box-sizing:border-box;width:100%;height:100%;top:0;left:0;border:1px solid transparent;border-radius:inherit;content:"";pointer-events:none;pointer-events:none}.mdc-card--outlined{box-shadow:0px 0px 0px 0px rgba(0, 0, 0, 0.2),0px 0px 0px 0px rgba(0, 0, 0, 0.14),0px 0px 0px 0px rgba(0,0,0,.12);border-width:1px;border-style:solid;border-color:#e0e0e0}.mdc-card--outlined::after{border:none}.mdc-card__content{border-radius:inherit;height:100%}.mdc-card__media{position:relative;box-sizing:border-box;background-repeat:no-repeat;background-position:center;background-size:cover}.mdc-card__media::before{display:block;content:""}.mdc-card__media:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.mdc-card__media:last-child{border-bottom-left-radius:inherit;border-bottom-right-radius:inherit}.mdc-card__media--square::before{margin-top:100%}.mdc-card__media--16-9::before{margin-top:56.25%}.mdc-card__media-content{position:absolute;top:0;right:0;bottom:0;left:0;box-sizing:border-box}.mdc-card__primary-action{display:flex;flex-direction:column;box-sizing:border-box;position:relative;outline:none;color:inherit;text-decoration:none;cursor:pointer;overflow:hidden}.mdc-card__primary-action:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.mdc-card__primary-action:last-child{border-bottom-left-radius:inherit;border-bottom-right-radius:inherit}.mdc-card__actions{display:flex;flex-direction:row;align-items:center;box-sizing:border-box;min-height:52px;padding:8px}.mdc-card__actions--full-bleed{padding:0}.mdc-card__action-buttons,.mdc-card__action-icons{display:flex;flex-direction:row;align-items:center;box-sizing:border-box}.mdc-card__action-icons{color:rgba(0, 0, 0, 0.6);flex-grow:1;justify-content:flex-end}.mdc-card__action-buttons+.mdc-card__action-icons{margin-left:16px;margin-right:0}[dir=rtl] .mdc-card__action-buttons+.mdc-card__action-icons,.mdc-card__action-buttons+.mdc-card__action-icons[dir=rtl]{margin-left:0;margin-right:16px}.mdc-card__action{display:inline-flex;flex-direction:row;align-items:center;box-sizing:border-box;justify-content:center;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mdc-card__action:focus{outline:none}.mdc-card__action--button{margin-left:0;margin-right:8px;padding:0 8px}[dir=rtl] .mdc-card__action--button,.mdc-card__action--button[dir=rtl]{margin-left:8px;margin-right:0}.mdc-card__action--button:last-child{margin-left:0;margin-right:0}[dir=rtl] .mdc-card__action--button:last-child,.mdc-card__action--button:last-child[dir=rtl]{margin-left:0;margin-right:0}.mdc-card__actions--full-bleed .mdc-card__action--button{justify-content:space-between;width:100%;height:auto;max-height:none;margin:0;padding:8px 16px;text-align:left}[dir=rtl] .mdc-card__actions--full-bleed .mdc-card__action--button,.mdc-card__actions--full-bleed .mdc-card__action--button[dir=rtl]{text-align:right}.mdc-card__action--icon{margin:-6px 0;padding:12px}.mdc-card__action--icon:not(:disabled){color:rgba(0, 0, 0, 0.6)}.mdc-card__primary-action{--mdc-ripple-fg-size: 0;--mdc-ripple-left: 0;--mdc-ripple-top: 0;--mdc-ripple-fg-scale: 1;--mdc-ripple-fg-translate-end: 0;--mdc-ripple-fg-translate-start: 0;-webkit-tap-highlight-color:rgba(0,0,0,0);will-change:transform,opacity}.mdc-card__primary-action .mdc-card__ripple::before,.mdc-card__primary-action .mdc-card__ripple::after{position:absolute;border-radius:50%;opacity:0;pointer-events:none;content:""}.mdc-card__primary-action .mdc-card__ripple::before{transition:opacity 15ms linear,background-color 15ms linear;z-index:1;z-index:var(--mdc-ripple-z-index, 1)}.mdc-card__primary-action .mdc-card__ripple::after{z-index:0;z-index:var(--mdc-ripple-z-index, 0)}.mdc-card__primary-action.mdc-ripple-upgraded .mdc-card__ripple::before{-webkit-transform:scale(var(--mdc-ripple-fg-scale, 1));transform:scale(var(--mdc-ripple-fg-scale, 1))}.mdc-card__primary-action.mdc-ripple-upgraded .mdc-card__ripple::after{top:0;left:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:center center;transform-origin:center center}.mdc-card__primary-action.mdc-ripple-upgraded--unbounded .mdc-card__ripple::after{top:var(--mdc-ripple-top, 0);left:var(--mdc-ripple-left, 0)}.mdc-card__primary-action.mdc-ripple-upgraded--foreground-activation .mdc-card__ripple::after{-webkit-animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards;animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards}.mdc-card__primary-action.mdc-ripple-upgraded--foreground-deactivation .mdc-card__ripple::after{-webkit-animation:mdc-ripple-fg-opacity-out 150ms;animation:mdc-ripple-fg-opacity-out 150ms;-webkit-transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1))}.mdc-card__primary-action .mdc-card__ripple::before,.mdc-card__primary-action .mdc-card__ripple::after{top:calc(50% - 100%);left:calc(50% - 100%);width:200%;height:200%}.mdc-card__primary-action.mdc-ripple-upgraded .mdc-card__ripple::after{width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}.mdc-card__primary-action .mdc-card__ripple::before,.mdc-card__primary-action .mdc-card__ripple::after{background-color:#000;background-color:var(--mdc-ripple-color, #000)}.mdc-card__primary-action:hover .mdc-card__ripple::before,.mdc-card__primary-action.mdc-ripple-surface--hover .mdc-card__ripple::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}.mdc-card__primary-action.mdc-ripple-upgraded--background-focused .mdc-card__ripple::before,.mdc-card__primary-action:not(.mdc-ripple-upgraded):focus .mdc-card__ripple::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-card__primary-action:not(.mdc-ripple-upgraded) .mdc-card__ripple::after{transition:opacity 150ms linear}.mdc-card__primary-action:not(.mdc-ripple-upgraded):active .mdc-card__ripple::after{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-card__primary-action.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-card__primary-action .mdc-card__ripple{box-sizing:content-box;height:100%;overflow:hidden;left:0;pointer-events:none;position:absolute;top:0;width:100%}.mdc-card__primary-action.mdc-ripple-upgraded--background-focused::after,.mdc-card__primary-action:not(.mdc-ripple-upgraded):focus::after{position:absolute;box-sizing:border-box;width:100%;height:100%;top:0;left:0;border:5px double transparent;border-radius:inherit;content:"";pointer-events:none}.mdc-checkbox{padding:calc((40px - 18px) / 2);padding:calc((var(--mdc-checkbox-ripple-size, 40px) - 18px) / 2);margin:calc((40px - 40px) / 2);margin:calc((var(--mdc-checkbox-touch-target-size, 40px) - 40px) / 2)}.mdc-checkbox .mdc-checkbox__ripple::before,.mdc-checkbox .mdc-checkbox__ripple::after{background-color:#000;background-color:var(--mdc-ripple-color, #000)}.mdc-checkbox:hover .mdc-checkbox__ripple::before,.mdc-checkbox.mdc-ripple-surface--hover .mdc-checkbox__ripple::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}.mdc-checkbox.mdc-ripple-upgraded--background-focused .mdc-checkbox__ripple::before,.mdc-checkbox:not(.mdc-ripple-upgraded):focus .mdc-checkbox__ripple::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-checkbox:not(.mdc-ripple-upgraded) .mdc-checkbox__ripple::after{transition:opacity 150ms linear}.mdc-checkbox:not(.mdc-ripple-upgraded):active .mdc-checkbox__ripple::after{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-checkbox.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-checkbox.mdc-checkbox--selected .mdc-checkbox__ripple::before,.mdc-checkbox.mdc-checkbox--selected .mdc-checkbox__ripple::after{background-color:#018786;background-color:var(--mdc-ripple-color, var(--mdc-theme-secondary, #018786))}.mdc-checkbox.mdc-checkbox--selected:hover .mdc-checkbox__ripple::before,.mdc-checkbox.mdc-checkbox--selected.mdc-ripple-surface--hover .mdc-checkbox__ripple::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}.mdc-checkbox.mdc-checkbox--selected.mdc-ripple-upgraded--background-focused .mdc-checkbox__ripple::before,.mdc-checkbox.mdc-checkbox--selected:not(.mdc-ripple-upgraded):focus .mdc-checkbox__ripple::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-checkbox.mdc-checkbox--selected:not(.mdc-ripple-upgraded) .mdc-checkbox__ripple::after{transition:opacity 150ms linear}.mdc-checkbox.mdc-checkbox--selected:not(.mdc-ripple-upgraded):active .mdc-checkbox__ripple::after{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-checkbox.mdc-checkbox--selected.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-checkbox.mdc-ripple-upgraded--background-focused.mdc-checkbox--selected .mdc-checkbox__ripple::before,.mdc-checkbox.mdc-ripple-upgraded--background-focused.mdc-checkbox--selected .mdc-checkbox__ripple::after{background-color:#018786;background-color:var(--mdc-ripple-color, var(--mdc-theme-secondary, #018786))}.mdc-checkbox .mdc-checkbox__background{top:calc((40px - 18px) / 2);top:calc((var(--mdc-checkbox-ripple-size, 40px) - 18px) / 2);left:calc((40px - 18px) / 2);left:calc((var(--mdc-checkbox-ripple-size, 40px) - 18px) / 2)}.mdc-checkbox .mdc-checkbox__native-control{top:calc((40px - 40px) / 2);top:calc((40px - var(--mdc-checkbox-touch-target-size, 40px)) / 2);right:calc((40px - 40px) / 2);right:calc((40px - var(--mdc-checkbox-touch-target-size, 40px)) / 2);left:calc((40px - 40px) / 2);left:calc((40px - var(--mdc-checkbox-touch-target-size, 40px)) / 2);width:40px;width:var(--mdc-checkbox-touch-target-size, 40px);height:40px;height:var(--mdc-checkbox-touch-target-size, 40px)}.mdc-checkbox .mdc-checkbox__native-control:enabled:not(:checked):not(:indeterminate):not([data-indeterminate=true])~.mdc-checkbox__background{border-color:rgba(0, 0, 0, 0.54);border-color:var(--mdc-checkbox-unchecked-color, rgba(0, 0, 0, 0.54));background-color:transparent}.mdc-checkbox .mdc-checkbox__native-control:enabled:checked~.mdc-checkbox__background,.mdc-checkbox .mdc-checkbox__native-control:enabled:indeterminate~.mdc-checkbox__background,.mdc-checkbox .mdc-checkbox__native-control[data-indeterminate=true]:enabled~.mdc-checkbox__background{border-color:#018786;border-color:var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #018786));background-color:#018786;background-color:var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #018786))}@-webkit-keyframes mdc-checkbox-fade-in-background-8A000000FF01878600000000FF018786{0%{border-color:rgba(0, 0, 0, 0.54);border-color:var(--mdc-checkbox-unchecked-color, rgba(0, 0, 0, 0.54));background-color:transparent}50%{border-color:#018786;border-color:var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #018786));background-color:#018786;background-color:var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #018786))}}@keyframes mdc-checkbox-fade-in-background-8A000000FF01878600000000FF018786{0%{border-color:rgba(0, 0, 0, 0.54);border-color:var(--mdc-checkbox-unchecked-color, rgba(0, 0, 0, 0.54));background-color:transparent}50%{border-color:#018786;border-color:var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #018786));background-color:#018786;background-color:var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #018786))}}@-webkit-keyframes mdc-checkbox-fade-out-background-8A000000FF01878600000000FF018786{0%,80%{border-color:#018786;border-color:var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #018786));background-color:#018786;background-color:var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #018786))}100%{border-color:rgba(0, 0, 0, 0.54);border-color:var(--mdc-checkbox-unchecked-color, rgba(0, 0, 0, 0.54));background-color:transparent}}@keyframes mdc-checkbox-fade-out-background-8A000000FF01878600000000FF018786{0%,80%{border-color:#018786;border-color:var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #018786));background-color:#018786;background-color:var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #018786))}100%{border-color:rgba(0, 0, 0, 0.54);border-color:var(--mdc-checkbox-unchecked-color, rgba(0, 0, 0, 0.54));background-color:transparent}}.mdc-checkbox.mdc-checkbox--anim-unchecked-checked .mdc-checkbox__native-control:enabled~.mdc-checkbox__background,.mdc-checkbox.mdc-checkbox--anim-unchecked-indeterminate .mdc-checkbox__native-control:enabled~.mdc-checkbox__background{-webkit-animation-name:mdc-checkbox-fade-in-background-8A000000FF01878600000000FF018786;animation-name:mdc-checkbox-fade-in-background-8A000000FF01878600000000FF018786}.mdc-checkbox.mdc-checkbox--anim-checked-unchecked .mdc-checkbox__native-control:enabled~.mdc-checkbox__background,.mdc-checkbox.mdc-checkbox--anim-indeterminate-unchecked .mdc-checkbox__native-control:enabled~.mdc-checkbox__background{-webkit-animation-name:mdc-checkbox-fade-out-background-8A000000FF01878600000000FF018786;animation-name:mdc-checkbox-fade-out-background-8A000000FF01878600000000FF018786}.mdc-checkbox .mdc-checkbox__native-control[disabled]:not(:checked):not(:indeterminate):not([data-indeterminate=true])~.mdc-checkbox__background{border-color:rgba(0, 0, 0, 0.38);border-color:var(--mdc-checkbox-disabled-color, rgba(0, 0, 0, 0.38));background-color:transparent}.mdc-checkbox .mdc-checkbox__native-control[disabled]:checked~.mdc-checkbox__background,.mdc-checkbox .mdc-checkbox__native-control[disabled]:indeterminate~.mdc-checkbox__background,.mdc-checkbox .mdc-checkbox__native-control[data-indeterminate=true][disabled]~.mdc-checkbox__background{border-color:transparent;background-color:rgba(0, 0, 0, 0.38);background-color:var(--mdc-checkbox-disabled-color, rgba(0, 0, 0, 0.38))}.mdc-checkbox .mdc-checkbox__native-control:enabled~.mdc-checkbox__background .mdc-checkbox__checkmark{color:#fff;color:var(--mdc-checkbox-ink-color, #fff)}.mdc-checkbox .mdc-checkbox__native-control:enabled~.mdc-checkbox__background .mdc-checkbox__mixedmark{border-color:#fff;border-color:var(--mdc-checkbox-ink-color, #fff)}.mdc-checkbox .mdc-checkbox__native-control:disabled~.mdc-checkbox__background .mdc-checkbox__checkmark{color:#fff;color:var(--mdc-checkbox-ink-color, #fff)}.mdc-checkbox .mdc-checkbox__native-control:disabled~.mdc-checkbox__background .mdc-checkbox__mixedmark{border-color:#fff;border-color:var(--mdc-checkbox-ink-color, #fff)}@-webkit-keyframes mdc-checkbox-unchecked-checked-checkmark-path{0%,50%{stroke-dashoffset:29.7833385}50%{-webkit-animation-timing-function:cubic-bezier(0, 0, 0.2, 1);animation-timing-function:cubic-bezier(0, 0, 0.2, 1)}100%{stroke-dashoffset:0}}@keyframes mdc-checkbox-unchecked-checked-checkmark-path{0%,50%{stroke-dashoffset:29.7833385}50%{-webkit-animation-timing-function:cubic-bezier(0, 0, 0.2, 1);animation-timing-function:cubic-bezier(0, 0, 0.2, 1)}100%{stroke-dashoffset:0}}@-webkit-keyframes mdc-checkbox-unchecked-indeterminate-mixedmark{0%,68.2%{-webkit-transform:scaleX(0);transform:scaleX(0)}68.2%{-webkit-animation-timing-function:cubic-bezier(0, 0, 0, 1);animation-timing-function:cubic-bezier(0, 0, 0, 1)}100%{-webkit-transform:scaleX(1);transform:scaleX(1)}}@keyframes mdc-checkbox-unchecked-indeterminate-mixedmark{0%,68.2%{-webkit-transform:scaleX(0);transform:scaleX(0)}68.2%{-webkit-animation-timing-function:cubic-bezier(0, 0, 0, 1);animation-timing-function:cubic-bezier(0, 0, 0, 1)}100%{-webkit-transform:scaleX(1);transform:scaleX(1)}}@-webkit-keyframes mdc-checkbox-checked-unchecked-checkmark-path{from{-webkit-animation-timing-function:cubic-bezier(0.4, 0, 1, 1);animation-timing-function:cubic-bezier(0.4, 0, 1, 1);opacity:1;stroke-dashoffset:0}to{opacity:0;stroke-dashoffset:-29.7833385}}@keyframes mdc-checkbox-checked-unchecked-checkmark-path{from{-webkit-animation-timing-function:cubic-bezier(0.4, 0, 1, 1);animation-timing-function:cubic-bezier(0.4, 0, 1, 1);opacity:1;stroke-dashoffset:0}to{opacity:0;stroke-dashoffset:-29.7833385}}@-webkit-keyframes mdc-checkbox-checked-indeterminate-checkmark{from{-webkit-animation-timing-function:cubic-bezier(0, 0, 0.2, 1);animation-timing-function:cubic-bezier(0, 0, 0.2, 1);-webkit-transform:rotate(0deg);transform:rotate(0deg);opacity:1}to{-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}}@keyframes mdc-checkbox-checked-indeterminate-checkmark{from{-webkit-animation-timing-function:cubic-bezier(0, 0, 0.2, 1);animation-timing-function:cubic-bezier(0, 0, 0.2, 1);-webkit-transform:rotate(0deg);transform:rotate(0deg);opacity:1}to{-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}}@-webkit-keyframes mdc-checkbox-indeterminate-checked-checkmark{from{-webkit-animation-timing-function:cubic-bezier(0.14, 0, 0, 1);animation-timing-function:cubic-bezier(0.14, 0, 0, 1);-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}to{-webkit-transform:rotate(360deg);transform:rotate(360deg);opacity:1}}@keyframes mdc-checkbox-indeterminate-checked-checkmark{from{-webkit-animation-timing-function:cubic-bezier(0.14, 0, 0, 1);animation-timing-function:cubic-bezier(0.14, 0, 0, 1);-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}to{-webkit-transform:rotate(360deg);transform:rotate(360deg);opacity:1}}@-webkit-keyframes mdc-checkbox-checked-indeterminate-mixedmark{from{-webkit-animation-timing-function:mdc-animation-deceleration-curve-timing-function;animation-timing-function:mdc-animation-deceleration-curve-timing-function;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}to{-webkit-transform:rotate(0deg);transform:rotate(0deg);opacity:1}}@keyframes mdc-checkbox-checked-indeterminate-mixedmark{from{-webkit-animation-timing-function:mdc-animation-deceleration-curve-timing-function;animation-timing-function:mdc-animation-deceleration-curve-timing-function;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}to{-webkit-transform:rotate(0deg);transform:rotate(0deg);opacity:1}}@-webkit-keyframes mdc-checkbox-indeterminate-checked-mixedmark{from{-webkit-animation-timing-function:cubic-bezier(0.14, 0, 0, 1);animation-timing-function:cubic-bezier(0.14, 0, 0, 1);-webkit-transform:rotate(0deg);transform:rotate(0deg);opacity:1}to{-webkit-transform:rotate(315deg);transform:rotate(315deg);opacity:0}}@keyframes mdc-checkbox-indeterminate-checked-mixedmark{from{-webkit-animation-timing-function:cubic-bezier(0.14, 0, 0, 1);animation-timing-function:cubic-bezier(0.14, 0, 0, 1);-webkit-transform:rotate(0deg);transform:rotate(0deg);opacity:1}to{-webkit-transform:rotate(315deg);transform:rotate(315deg);opacity:0}}@-webkit-keyframes mdc-checkbox-indeterminate-unchecked-mixedmark{0%{-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-transform:scaleX(1);transform:scaleX(1);opacity:1}32.8%,100%{-webkit-transform:scaleX(0);transform:scaleX(0);opacity:0}}@keyframes mdc-checkbox-indeterminate-unchecked-mixedmark{0%{-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-transform:scaleX(1);transform:scaleX(1);opacity:1}32.8%,100%{-webkit-transform:scaleX(0);transform:scaleX(0);opacity:0}}.mdc-checkbox{display:inline-block;position:relative;flex:0 0 18px;box-sizing:content-box;width:18px;height:18px;line-height:0;white-space:nowrap;cursor:pointer;vertical-align:bottom}@media screen and (forced-colors: active),(-ms-high-contrast: active){.mdc-checkbox__native-control[disabled]:not(:checked):not(:indeterminate):not([data-indeterminate=true])~.mdc-checkbox__background{border-color:GrayText;border-color:var(--mdc-checkbox-disabled-color, GrayText);background-color:transparent}.mdc-checkbox__native-control[disabled]:checked~.mdc-checkbox__background,.mdc-checkbox__native-control[disabled]:indeterminate~.mdc-checkbox__background,.mdc-checkbox__native-control[data-indeterminate=true][disabled]~.mdc-checkbox__background{border-color:GrayText;background-color:transparent;background-color:var(--mdc-checkbox-disabled-color, transparent)}.mdc-checkbox__native-control:disabled~.mdc-checkbox__background .mdc-checkbox__checkmark{color:GrayText;color:var(--mdc-checkbox-ink-color, GrayText)}.mdc-checkbox__native-control:disabled~.mdc-checkbox__background .mdc-checkbox__mixedmark{border-color:GrayText;border-color:var(--mdc-checkbox-ink-color, GrayText)}.mdc-checkbox__mixedmark{margin:0 1px}}.mdc-checkbox--disabled{cursor:default;pointer-events:none}.mdc-checkbox__background{display:inline-flex;position:absolute;align-items:center;justify-content:center;box-sizing:border-box;width:18px;height:18px;border:2px solid currentColor;border-radius:2px;background-color:transparent;pointer-events:none;will-change:background-color,border-color;transition:background-color 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1),border-color 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1)}.mdc-checkbox__checkmark{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;opacity:0;transition:opacity 180ms 0ms cubic-bezier(0.4, 0, 0.6, 1)}.mdc-checkbox--upgraded .mdc-checkbox__checkmark{opacity:1}.mdc-checkbox__checkmark-path{transition:stroke-dashoffset 180ms 0ms cubic-bezier(0.4, 0, 0.6, 1);stroke:currentColor;stroke-width:3.12px;stroke-dashoffset:29.7833385;stroke-dasharray:29.7833385}.mdc-checkbox__mixedmark{width:100%;height:0;-webkit-transform:scaleX(0) rotate(0deg);transform:scaleX(0) rotate(0deg);border-width:1px;border-style:solid;opacity:0;transition:opacity 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1),-webkit-transform 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1);transition:opacity 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1),transform 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1);transition:opacity 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1),transform 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1),-webkit-transform 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1)}.mdc-checkbox--anim-unchecked-checked .mdc-checkbox__background,.mdc-checkbox--anim-unchecked-indeterminate .mdc-checkbox__background,.mdc-checkbox--anim-checked-unchecked .mdc-checkbox__background,.mdc-checkbox--anim-indeterminate-unchecked .mdc-checkbox__background{-webkit-animation-duration:180ms;animation-duration:180ms;-webkit-animation-timing-function:linear;animation-timing-function:linear}.mdc-checkbox--anim-unchecked-checked .mdc-checkbox__checkmark-path{-webkit-animation:mdc-checkbox-unchecked-checked-checkmark-path 180ms linear 0s;animation:mdc-checkbox-unchecked-checked-checkmark-path 180ms linear 0s;transition:none}.mdc-checkbox--anim-unchecked-indeterminate .mdc-checkbox__mixedmark{-webkit-animation:mdc-checkbox-unchecked-indeterminate-mixedmark 90ms linear 0s;animation:mdc-checkbox-unchecked-indeterminate-mixedmark 90ms linear 0s;transition:none}.mdc-checkbox--anim-checked-unchecked .mdc-checkbox__checkmark-path{-webkit-animation:mdc-checkbox-checked-unchecked-checkmark-path 90ms linear 0s;animation:mdc-checkbox-checked-unchecked-checkmark-path 90ms linear 0s;transition:none}.mdc-checkbox--anim-checked-indeterminate .mdc-checkbox__checkmark{-webkit-animation:mdc-checkbox-checked-indeterminate-checkmark 90ms linear 0s;animation:mdc-checkbox-checked-indeterminate-checkmark 90ms linear 0s;transition:none}.mdc-checkbox--anim-checked-indeterminate .mdc-checkbox__mixedmark{-webkit-animation:mdc-checkbox-checked-indeterminate-mixedmark 90ms linear 0s;animation:mdc-checkbox-checked-indeterminate-mixedmark 90ms linear 0s;transition:none}.mdc-checkbox--anim-indeterminate-checked .mdc-checkbox__checkmark{-webkit-animation:mdc-checkbox-indeterminate-checked-checkmark 500ms linear 0s;animation:mdc-checkbox-indeterminate-checked-checkmark 500ms linear 0s;transition:none}.mdc-checkbox--anim-indeterminate-checked .mdc-checkbox__mixedmark{-webkit-animation:mdc-checkbox-indeterminate-checked-mixedmark 500ms linear 0s;animation:mdc-checkbox-indeterminate-checked-mixedmark 500ms linear 0s;transition:none}.mdc-checkbox--anim-indeterminate-unchecked .mdc-checkbox__mixedmark{-webkit-animation:mdc-checkbox-indeterminate-unchecked-mixedmark 300ms linear 0s;animation:mdc-checkbox-indeterminate-unchecked-mixedmark 300ms linear 0s;transition:none}.mdc-checkbox__native-control:checked~.mdc-checkbox__background,.mdc-checkbox__native-control:indeterminate~.mdc-checkbox__background,.mdc-checkbox__native-control[data-indeterminate=true]~.mdc-checkbox__background{transition:border-color 90ms 0ms cubic-bezier(0, 0, 0.2, 1),background-color 90ms 0ms cubic-bezier(0, 0, 0.2, 1)}.mdc-checkbox__native-control:checked~.mdc-checkbox__background .mdc-checkbox__checkmark-path,.mdc-checkbox__native-control:indeterminate~.mdc-checkbox__background .mdc-checkbox__checkmark-path,.mdc-checkbox__native-control[data-indeterminate=true]~.mdc-checkbox__background .mdc-checkbox__checkmark-path{stroke-dashoffset:0}.mdc-checkbox__native-control{position:absolute;margin:0;padding:0;opacity:0;cursor:inherit}.mdc-checkbox__native-control:disabled{cursor:default;pointer-events:none}.mdc-checkbox--touch{margin:calc((48px - 40px) / 2);margin:calc((var(--mdc-checkbox-state-layer-size, 48px) - var(--mdc-checkbox-state-layer-size, 40px)) / 2)}.mdc-checkbox--touch .mdc-checkbox__native-control{top:calc((40px - 48px) / 2);top:calc((var(--mdc-checkbox-state-layer-size, 40px) - var(--mdc-checkbox-state-layer-size, 48px)) / 2);right:calc((40px - 48px) / 2);right:calc((var(--mdc-checkbox-state-layer-size, 40px) - var(--mdc-checkbox-state-layer-size, 48px)) / 2);left:calc((40px - 48px) / 2);left:calc((var(--mdc-checkbox-state-layer-size, 40px) - var(--mdc-checkbox-state-layer-size, 48px)) / 2);width:48px;width:var(--mdc-checkbox-state-layer-size, 48px);height:48px;height:var(--mdc-checkbox-state-layer-size, 48px)}.mdc-checkbox__native-control:checked~.mdc-checkbox__background .mdc-checkbox__checkmark{transition:opacity 180ms 0ms cubic-bezier(0, 0, 0.2, 1),-webkit-transform 180ms 0ms cubic-bezier(0, 0, 0.2, 1);transition:opacity 180ms 0ms cubic-bezier(0, 0, 0.2, 1),transform 180ms 0ms cubic-bezier(0, 0, 0.2, 1);transition:opacity 180ms 0ms cubic-bezier(0, 0, 0.2, 1),transform 180ms 0ms cubic-bezier(0, 0, 0.2, 1),-webkit-transform 180ms 0ms cubic-bezier(0, 0, 0.2, 1);opacity:1}.mdc-checkbox__native-control:checked~.mdc-checkbox__background .mdc-checkbox__mixedmark{-webkit-transform:scaleX(1) rotate(-45deg);transform:scaleX(1) rotate(-45deg)}.mdc-checkbox__native-control:indeterminate~.mdc-checkbox__background .mdc-checkbox__checkmark,.mdc-checkbox__native-control[data-indeterminate=true]~.mdc-checkbox__background .mdc-checkbox__checkmark{-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0;transition:opacity 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1),-webkit-transform 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1);transition:opacity 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1),transform 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1);transition:opacity 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1),transform 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1),-webkit-transform 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1)}.mdc-checkbox__native-control:indeterminate~.mdc-checkbox__background .mdc-checkbox__mixedmark,.mdc-checkbox__native-control[data-indeterminate=true]~.mdc-checkbox__background .mdc-checkbox__mixedmark{-webkit-transform:scaleX(1) rotate(0deg);transform:scaleX(1) rotate(0deg);opacity:1}.mdc-checkbox.mdc-checkbox--upgraded .mdc-checkbox__background,.mdc-checkbox.mdc-checkbox--upgraded .mdc-checkbox__checkmark,.mdc-checkbox.mdc-checkbox--upgraded .mdc-checkbox__checkmark-path,.mdc-checkbox.mdc-checkbox--upgraded .mdc-checkbox__mixedmark{transition:none}.mdc-checkbox{--mdc-ripple-fg-size: 0;--mdc-ripple-left: 0;--mdc-ripple-top: 0;--mdc-ripple-fg-scale: 1;--mdc-ripple-fg-translate-end: 0;--mdc-ripple-fg-translate-start: 0;-webkit-tap-highlight-color:rgba(0,0,0,0);will-change:transform,opacity}.mdc-checkbox .mdc-checkbox__ripple::before,.mdc-checkbox .mdc-checkbox__ripple::after{position:absolute;border-radius:50%;opacity:0;pointer-events:none;content:""}.mdc-checkbox .mdc-checkbox__ripple::before{transition:opacity 15ms linear,background-color 15ms linear;z-index:1;z-index:var(--mdc-ripple-z-index, 1)}.mdc-checkbox .mdc-checkbox__ripple::after{z-index:0;z-index:var(--mdc-ripple-z-index, 0)}.mdc-checkbox.mdc-ripple-upgraded .mdc-checkbox__ripple::before{-webkit-transform:scale(var(--mdc-ripple-fg-scale, 1));transform:scale(var(--mdc-ripple-fg-scale, 1))}.mdc-checkbox.mdc-ripple-upgraded .mdc-checkbox__ripple::after{top:0;left:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:center center;transform-origin:center center}.mdc-checkbox.mdc-ripple-upgraded--unbounded .mdc-checkbox__ripple::after{top:var(--mdc-ripple-top, 0);left:var(--mdc-ripple-left, 0)}.mdc-checkbox.mdc-ripple-upgraded--foreground-activation .mdc-checkbox__ripple::after{-webkit-animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards;animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards}.mdc-checkbox.mdc-ripple-upgraded--foreground-deactivation .mdc-checkbox__ripple::after{-webkit-animation:mdc-ripple-fg-opacity-out 150ms;animation:mdc-ripple-fg-opacity-out 150ms;-webkit-transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1))}.mdc-checkbox .mdc-checkbox__ripple::before,.mdc-checkbox .mdc-checkbox__ripple::after{top:calc(50% - 50%);left:calc(50% - 50%);width:100%;height:100%}.mdc-checkbox.mdc-ripple-upgraded .mdc-checkbox__ripple::before,.mdc-checkbox.mdc-ripple-upgraded .mdc-checkbox__ripple::after{top:var(--mdc-ripple-top, calc(50% - 50%));left:var(--mdc-ripple-left, calc(50% - 50%));width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}.mdc-checkbox.mdc-ripple-upgraded .mdc-checkbox__ripple::after{width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}.mdc-checkbox{z-index:0}.mdc-checkbox .mdc-checkbox__ripple::before,.mdc-checkbox .mdc-checkbox__ripple::after{z-index:-1;z-index:var(--mdc-ripple-z-index, -1)}.mdc-checkbox__ripple{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none}.mdc-deprecated-chip-trailing-action__touch{position:absolute;top:50%;height:48px;left:50%;width:48px;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%)}.mdc-deprecated-chip-trailing-action{border:none;display:inline-flex;position:relative;align-items:center;justify-content:center;box-sizing:border-box;padding:0;outline:none;cursor:pointer;-webkit-appearance:none;background:none}.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__icon{height:18px;width:18px;font-size:18px}.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action{color:#000;color:var(--mdc-theme-on-surface, #000)}.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__touch{width:26px}.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__icon{fill:currentColor;color:inherit}.mdc-deprecated-chip-trailing-action{--mdc-ripple-fg-size: 0;--mdc-ripple-left: 0;--mdc-ripple-top: 0;--mdc-ripple-fg-scale: 1;--mdc-ripple-fg-translate-end: 0;--mdc-ripple-fg-translate-start: 0;-webkit-tap-highlight-color:rgba(0,0,0,0);will-change:transform,opacity}.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__ripple::before,.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__ripple::after{position:absolute;border-radius:50%;opacity:0;pointer-events:none;content:""}.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__ripple::before{transition:opacity 15ms linear,background-color 15ms linear;z-index:1;z-index:var(--mdc-ripple-z-index, 1)}.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__ripple::after{z-index:0;z-index:var(--mdc-ripple-z-index, 0)}.mdc-deprecated-chip-trailing-action.mdc-ripple-upgraded .mdc-deprecated-chip-trailing-action__ripple::before{-webkit-transform:scale(var(--mdc-ripple-fg-scale, 1));transform:scale(var(--mdc-ripple-fg-scale, 1))}.mdc-deprecated-chip-trailing-action.mdc-ripple-upgraded .mdc-deprecated-chip-trailing-action__ripple::after{top:0;left:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:center center;transform-origin:center center}.mdc-deprecated-chip-trailing-action.mdc-ripple-upgraded--unbounded .mdc-deprecated-chip-trailing-action__ripple::after{top:var(--mdc-ripple-top, 0);left:var(--mdc-ripple-left, 0)}.mdc-deprecated-chip-trailing-action.mdc-ripple-upgraded--foreground-activation .mdc-deprecated-chip-trailing-action__ripple::after{-webkit-animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards;animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards}.mdc-deprecated-chip-trailing-action.mdc-ripple-upgraded--foreground-deactivation .mdc-deprecated-chip-trailing-action__ripple::after{-webkit-animation:mdc-ripple-fg-opacity-out 150ms;animation:mdc-ripple-fg-opacity-out 150ms;-webkit-transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1))}.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__ripple::before,.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__ripple::after{top:calc(50% - 50%);left:calc(50% - 50%);width:100%;height:100%}.mdc-deprecated-chip-trailing-action.mdc-ripple-upgraded .mdc-deprecated-chip-trailing-action__ripple::before,.mdc-deprecated-chip-trailing-action.mdc-ripple-upgraded .mdc-deprecated-chip-trailing-action__ripple::after{top:var(--mdc-ripple-top, calc(50% - 50%));left:var(--mdc-ripple-left, calc(50% - 50%));width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}.mdc-deprecated-chip-trailing-action.mdc-ripple-upgraded .mdc-deprecated-chip-trailing-action__ripple::after{width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__ripple::before,.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__ripple::after{background-color:#000;background-color:var(--mdc-ripple-color, var(--mdc-theme-on-surface, #000))}.mdc-deprecated-chip-trailing-action:hover .mdc-deprecated-chip-trailing-action__ripple::before,.mdc-deprecated-chip-trailing-action.mdc-ripple-surface--hover .mdc-deprecated-chip-trailing-action__ripple::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}.mdc-deprecated-chip-trailing-action.mdc-ripple-upgraded--background-focused .mdc-deprecated-chip-trailing-action__ripple::before,.mdc-deprecated-chip-trailing-action:not(.mdc-ripple-upgraded):focus .mdc-deprecated-chip-trailing-action__ripple::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-deprecated-chip-trailing-action:not(.mdc-ripple-upgraded) .mdc-deprecated-chip-trailing-action__ripple::after{transition:opacity 150ms linear}.mdc-deprecated-chip-trailing-action:not(.mdc-ripple-upgraded):active .mdc-deprecated-chip-trailing-action__ripple::after{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-deprecated-chip-trailing-action.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__ripple{position:absolute;box-sizing:content-box;width:100%;height:100%;overflow:hidden}.mdc-chip__icon--leading{color:rgba(0,0,0,.54)}.mdc-deprecated-chip-trailing-action{color:#000}.mdc-chip__icon--trailing{color:rgba(0,0,0,.54)}.mdc-chip__icon--trailing:hover{color:rgba(0,0,0,.62)}.mdc-chip__icon--trailing:focus{color:rgba(0,0,0,.87)}.mdc-chip__icon.mdc-chip__icon--leading:not(.mdc-chip__icon--leading-hidden){width:20px;height:20px;font-size:20px}.mdc-deprecated-chip-trailing-action__icon{height:18px;width:18px;font-size:18px}.mdc-chip__icon.mdc-chip__icon--trailing{width:18px;height:18px;font-size:18px}.mdc-deprecated-chip-trailing-action{margin-left:4px;margin-right:-4px}[dir=rtl] .mdc-deprecated-chip-trailing-action,.mdc-deprecated-chip-trailing-action[dir=rtl]{margin-left:-4px;margin-right:4px}.mdc-chip__icon--trailing{margin-left:4px;margin-right:-4px}[dir=rtl] .mdc-chip__icon--trailing,.mdc-chip__icon--trailing[dir=rtl]{margin-left:-4px;margin-right:4px}.mdc-chip{border-radius:16px;background-color:#e0e0e0;color:rgba(0, 0, 0, 0.87);-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.875rem;font-size:var(--mdc-typography-body2-font-size, 0.875rem);line-height:1.25rem;line-height:var(--mdc-typography-body2-line-height, 1.25rem);font-weight:400;font-weight:var(--mdc-typography-body2-font-weight, 400);letter-spacing:0.0178571429em;letter-spacing:var(--mdc-typography-body2-letter-spacing, 0.0178571429em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-body2-text-transform, inherit);height:32px;position:relative;display:inline-flex;align-items:center;box-sizing:border-box;padding:0 12px;border-width:0;outline:none;cursor:pointer;-webkit-appearance:none}.mdc-chip .mdc-chip__ripple{border-radius:16px}.mdc-chip:hover{color:rgba(0, 0, 0, 0.87)}.mdc-chip.mdc-chip--selected .mdc-chip__checkmark,.mdc-chip .mdc-chip__icon--leading:not(.mdc-chip__icon--leading-hidden){margin-left:-4px;margin-right:4px}[dir=rtl] .mdc-chip.mdc-chip--selected .mdc-chip__checkmark,[dir=rtl] .mdc-chip .mdc-chip__icon--leading:not(.mdc-chip__icon--leading-hidden),.mdc-chip.mdc-chip--selected .mdc-chip__checkmark[dir=rtl],.mdc-chip .mdc-chip__icon--leading:not(.mdc-chip__icon--leading-hidden)[dir=rtl]{margin-left:4px;margin-right:-4px}.mdc-chip .mdc-elevation-overlay{width:100%;height:100%;top:0;left:0}.mdc-chip::-moz-focus-inner{padding:0;border:0}.mdc-chip:hover{color:#000;color:var(--mdc-theme-on-surface, #000)}.mdc-chip .mdc-chip__touch{position:absolute;top:50%;height:48px;left:0;right:0;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.mdc-chip--exit{transition:opacity 75ms cubic-bezier(0.4, 0, 0.2, 1),width 150ms cubic-bezier(0, 0, 0.2, 1),padding 100ms linear,margin 100ms linear;opacity:0}.mdc-chip__overflow{text-overflow:ellipsis;overflow:hidden}.mdc-chip__text{white-space:nowrap}.mdc-chip__icon{border-radius:50%;outline:none;vertical-align:middle}.mdc-chip__checkmark{height:20px}.mdc-chip__checkmark-path{transition:stroke-dashoffset 150ms 50ms cubic-bezier(0.4, 0, 0.6, 1);stroke-width:2px;stroke-dashoffset:29.7833385;stroke-dasharray:29.7833385}.mdc-chip__primary-action:focus{outline:none}.mdc-chip--selected .mdc-chip__checkmark-path{stroke-dashoffset:0}.mdc-chip__icon--leading,.mdc-chip__icon--trailing{position:relative}.mdc-chip-set--choice .mdc-chip.mdc-chip--selected{color:#6200ee;color:var(--mdc-theme-primary, #6200ee)}.mdc-chip-set--choice .mdc-chip.mdc-chip--selected .mdc-chip__icon--leading{color:rgba(98,0,238,.54)}.mdc-chip-set--choice .mdc-chip.mdc-chip--selected:hover{color:#6200ee;color:var(--mdc-theme-primary, #6200ee)}.mdc-chip-set--choice .mdc-chip .mdc-chip__checkmark-path{stroke:#6200ee;stroke:var(--mdc-theme-primary, #6200ee)}.mdc-chip-set--choice .mdc-chip--selected{background-color:#fff;background-color:var(--mdc-theme-surface, #fff)}.mdc-chip__checkmark-svg{width:0;height:20px;transition:width 150ms cubic-bezier(0.4, 0, 0.2, 1)}.mdc-chip--selected .mdc-chip__checkmark-svg{width:20px}.mdc-chip-set--filter .mdc-chip__icon--leading{transition:opacity 75ms linear;transition-delay:-50ms;opacity:1}.mdc-chip-set--filter .mdc-chip__icon--leading+.mdc-chip__checkmark{transition:opacity 75ms linear;transition-delay:80ms;opacity:0}.mdc-chip-set--filter .mdc-chip__icon--leading+.mdc-chip__checkmark .mdc-chip__checkmark-svg{transition:width 0ms}.mdc-chip-set--filter .mdc-chip--selected .mdc-chip__icon--leading{opacity:0}.mdc-chip-set--filter .mdc-chip--selected .mdc-chip__icon--leading+.mdc-chip__checkmark{width:0;opacity:1}.mdc-chip-set--filter .mdc-chip__icon--leading-hidden.mdc-chip__icon--leading{width:0;opacity:0}.mdc-chip-set--filter .mdc-chip__icon--leading-hidden.mdc-chip__icon--leading+.mdc-chip__checkmark{width:20px}.mdc-chip{--mdc-ripple-fg-size: 0;--mdc-ripple-left: 0;--mdc-ripple-top: 0;--mdc-ripple-fg-scale: 1;--mdc-ripple-fg-translate-end: 0;--mdc-ripple-fg-translate-start: 0;-webkit-tap-highlight-color:rgba(0,0,0,0);will-change:transform,opacity}.mdc-chip .mdc-chip__ripple::before,.mdc-chip .mdc-chip__ripple::after{position:absolute;border-radius:50%;opacity:0;pointer-events:none;content:""}.mdc-chip .mdc-chip__ripple::before{transition:opacity 15ms linear,background-color 15ms linear;z-index:1;z-index:var(--mdc-ripple-z-index, 1)}.mdc-chip .mdc-chip__ripple::after{z-index:0;z-index:var(--mdc-ripple-z-index, 0)}.mdc-chip.mdc-ripple-upgraded .mdc-chip__ripple::before{-webkit-transform:scale(var(--mdc-ripple-fg-scale, 1));transform:scale(var(--mdc-ripple-fg-scale, 1))}.mdc-chip.mdc-ripple-upgraded .mdc-chip__ripple::after{top:0;left:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:center center;transform-origin:center center}.mdc-chip.mdc-ripple-upgraded--unbounded .mdc-chip__ripple::after{top:var(--mdc-ripple-top, 0);left:var(--mdc-ripple-left, 0)}.mdc-chip.mdc-ripple-upgraded--foreground-activation .mdc-chip__ripple::after{-webkit-animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards;animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards}.mdc-chip.mdc-ripple-upgraded--foreground-deactivation .mdc-chip__ripple::after{-webkit-animation:mdc-ripple-fg-opacity-out 150ms;animation:mdc-ripple-fg-opacity-out 150ms;-webkit-transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1))}.mdc-chip .mdc-chip__ripple::before,.mdc-chip .mdc-chip__ripple::after{top:calc(50% - 100%);left:calc(50% - 100%);width:200%;height:200%}.mdc-chip.mdc-ripple-upgraded .mdc-chip__ripple::after{width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}.mdc-chip .mdc-chip__ripple::before,.mdc-chip .mdc-chip__ripple::after{background-color:rgba(0, 0, 0, 0.87);background-color:var(--mdc-ripple-color, rgba(0, 0, 0, 0.87))}.mdc-chip:hover .mdc-chip__ripple::before,.mdc-chip.mdc-ripple-surface--hover .mdc-chip__ripple::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}.mdc-chip.mdc-ripple-upgraded--background-focused .mdc-chip__ripple::before,.mdc-chip.mdc-ripple-upgraded:focus-within .mdc-chip__ripple::before,.mdc-chip:not(.mdc-ripple-upgraded):focus .mdc-chip__ripple::before,.mdc-chip:not(.mdc-ripple-upgraded):focus-within .mdc-chip__ripple::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-chip:not(.mdc-ripple-upgraded) .mdc-chip__ripple::after{transition:opacity 150ms linear}.mdc-chip:not(.mdc-ripple-upgraded):active .mdc-chip__ripple::after{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-chip.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-chip .mdc-chip__ripple{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;overflow:hidden}.mdc-chip-set--choice .mdc-chip.mdc-chip--selected .mdc-chip__ripple::before{opacity:0.08;opacity:var(--mdc-ripple-selected-opacity, 0.08)}.mdc-chip-set--choice .mdc-chip.mdc-chip--selected .mdc-chip__ripple::before,.mdc-chip-set--choice .mdc-chip.mdc-chip--selected .mdc-chip__ripple::after{background-color:#6200ee;background-color:var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee))}.mdc-chip-set--choice .mdc-chip.mdc-chip--selected:hover .mdc-chip__ripple::before,.mdc-chip-set--choice .mdc-chip.mdc-chip--selected.mdc-ripple-surface--hover .mdc-chip__ripple::before{opacity:0.12;opacity:var(--mdc-ripple-hover-opacity, 0.12)}.mdc-chip-set--choice .mdc-chip.mdc-chip--selected.mdc-ripple-upgraded--background-focused .mdc-chip__ripple::before,.mdc-chip-set--choice .mdc-chip.mdc-chip--selected.mdc-ripple-upgraded:focus-within .mdc-chip__ripple::before,.mdc-chip-set--choice .mdc-chip.mdc-chip--selected:not(.mdc-ripple-upgraded):focus .mdc-chip__ripple::before,.mdc-chip-set--choice .mdc-chip.mdc-chip--selected:not(.mdc-ripple-upgraded):focus-within .mdc-chip__ripple::before{transition-duration:75ms;opacity:0.2;opacity:var(--mdc-ripple-focus-opacity, 0.2)}.mdc-chip-set--choice .mdc-chip.mdc-chip--selected:not(.mdc-ripple-upgraded) .mdc-chip__ripple::after{transition:opacity 150ms linear}.mdc-chip-set--choice .mdc-chip.mdc-chip--selected:not(.mdc-ripple-upgraded):active .mdc-chip__ripple::after{transition-duration:75ms;opacity:0.2;opacity:var(--mdc-ripple-press-opacity, 0.2)}.mdc-chip-set--choice .mdc-chip.mdc-chip--selected.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.2)}@-webkit-keyframes mdc-chip-entry{from{-webkit-transform:scale(0.8);transform:scale(0.8);opacity:.4}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}@keyframes mdc-chip-entry{from{-webkit-transform:scale(0.8);transform:scale(0.8);opacity:.4}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}.mdc-chip-set{padding:4px;display:flex;flex-wrap:wrap;box-sizing:border-box}.mdc-chip-set .mdc-chip{margin:4px}.mdc-chip-set .mdc-chip--touch{margin-top:8px;margin-bottom:8px}.mdc-chip-set--input .mdc-chip{-webkit-animation:mdc-chip-entry 100ms cubic-bezier(0, 0, 0.2, 1);animation:mdc-chip-entry 100ms cubic-bezier(0, 0, 0.2, 1)}.mdc-circular-progress__determinate-circle,.mdc-circular-progress__indeterminate-circle-graphic{stroke:#6200ee;stroke:var(--mdc-theme-primary, #6200ee)}.mdc-circular-progress__determinate-track{stroke:transparent}@-webkit-keyframes mdc-circular-progress-container-rotate{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes mdc-circular-progress-container-rotate{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes mdc-circular-progress-spinner-layer-rotate{12.5%{-webkit-transform:rotate(135deg);transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg);transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg);transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg);transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg);transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg);transform:rotate(945deg)}100%{-webkit-transform:rotate(1080deg);transform:rotate(1080deg)}}@keyframes mdc-circular-progress-spinner-layer-rotate{12.5%{-webkit-transform:rotate(135deg);transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg);transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg);transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg);transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg);transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg);transform:rotate(945deg)}100%{-webkit-transform:rotate(1080deg);transform:rotate(1080deg)}}@-webkit-keyframes mdc-circular-progress-color-1-fade-in-out{from{opacity:.99}25%{opacity:.99}26%{opacity:0}89%{opacity:0}90%{opacity:.99}to{opacity:.99}}@keyframes mdc-circular-progress-color-1-fade-in-out{from{opacity:.99}25%{opacity:.99}26%{opacity:0}89%{opacity:0}90%{opacity:.99}to{opacity:.99}}@-webkit-keyframes mdc-circular-progress-color-2-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:.99}50%{opacity:.99}51%{opacity:0}to{opacity:0}}@keyframes mdc-circular-progress-color-2-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:.99}50%{opacity:.99}51%{opacity:0}to{opacity:0}}@-webkit-keyframes mdc-circular-progress-color-3-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:.99}75%{opacity:.99}76%{opacity:0}to{opacity:0}}@keyframes mdc-circular-progress-color-3-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:.99}75%{opacity:.99}76%{opacity:0}to{opacity:0}}@-webkit-keyframes mdc-circular-progress-color-4-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:.99}90%{opacity:.99}to{opacity:0}}@keyframes mdc-circular-progress-color-4-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:.99}90%{opacity:.99}to{opacity:0}}@-webkit-keyframes mdc-circular-progress-left-spin{from{-webkit-transform:rotate(265deg);transform:rotate(265deg)}50%{-webkit-transform:rotate(130deg);transform:rotate(130deg)}to{-webkit-transform:rotate(265deg);transform:rotate(265deg)}}@keyframes mdc-circular-progress-left-spin{from{-webkit-transform:rotate(265deg);transform:rotate(265deg)}50%{-webkit-transform:rotate(130deg);transform:rotate(130deg)}to{-webkit-transform:rotate(265deg);transform:rotate(265deg)}}@-webkit-keyframes mdc-circular-progress-right-spin{from{-webkit-transform:rotate(-265deg);transform:rotate(-265deg)}50%{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}to{-webkit-transform:rotate(-265deg);transform:rotate(-265deg)}}@keyframes mdc-circular-progress-right-spin{from{-webkit-transform:rotate(-265deg);transform:rotate(-265deg)}50%{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}to{-webkit-transform:rotate(-265deg);transform:rotate(-265deg)}}.mdc-circular-progress{display:inline-flex;position:relative;direction:ltr;line-height:0;transition:opacity 250ms 0ms cubic-bezier(0.4, 0, 0.6, 1)}.mdc-circular-progress__determinate-container,.mdc-circular-progress__indeterminate-circle-graphic,.mdc-circular-progress__indeterminate-container,.mdc-circular-progress__spinner-layer{position:absolute;width:100%;height:100%}.mdc-circular-progress__determinate-container{-webkit-transform:rotate(-90deg);transform:rotate(-90deg)}.mdc-circular-progress__indeterminate-container{font-size:0;letter-spacing:0;white-space:nowrap;opacity:0}.mdc-circular-progress__determinate-circle-graphic,.mdc-circular-progress__indeterminate-circle-graphic{fill:transparent}.mdc-circular-progress__determinate-circle{transition:stroke-dashoffset 500ms 0ms cubic-bezier(0, 0, 0.2, 1)}.mdc-circular-progress__gap-patch{position:absolute;top:0;left:47.5%;box-sizing:border-box;width:5%;height:100%;overflow:hidden}.mdc-circular-progress__gap-patch .mdc-circular-progress__indeterminate-circle-graphic{left:-900%;width:2000%;-webkit-transform:rotate(180deg);transform:rotate(180deg)}.mdc-circular-progress__circle-clipper{display:inline-flex;position:relative;width:50%;height:100%;overflow:hidden}.mdc-circular-progress__circle-clipper .mdc-circular-progress__indeterminate-circle-graphic{width:200%}.mdc-circular-progress__circle-right .mdc-circular-progress__indeterminate-circle-graphic{left:-100%}.mdc-circular-progress--indeterminate .mdc-circular-progress__determinate-container{opacity:0}.mdc-circular-progress--indeterminate .mdc-circular-progress__indeterminate-container{opacity:1}.mdc-circular-progress--indeterminate .mdc-circular-progress__indeterminate-container{-webkit-animation:mdc-circular-progress-container-rotate 1568.2352941176ms linear infinite;animation:mdc-circular-progress-container-rotate 1568.2352941176ms linear infinite}.mdc-circular-progress--indeterminate .mdc-circular-progress__spinner-layer{-webkit-animation:mdc-circular-progress-spinner-layer-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:mdc-circular-progress-spinner-layer-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.mdc-circular-progress--indeterminate .mdc-circular-progress__color-1{-webkit-animation:mdc-circular-progress-spinner-layer-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,mdc-circular-progress-color-1-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:mdc-circular-progress-spinner-layer-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,mdc-circular-progress-color-1-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.mdc-circular-progress--indeterminate .mdc-circular-progress__color-2{-webkit-animation:mdc-circular-progress-spinner-layer-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,mdc-circular-progress-color-2-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:mdc-circular-progress-spinner-layer-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,mdc-circular-progress-color-2-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.mdc-circular-progress--indeterminate .mdc-circular-progress__color-3{-webkit-animation:mdc-circular-progress-spinner-layer-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,mdc-circular-progress-color-3-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:mdc-circular-progress-spinner-layer-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,mdc-circular-progress-color-3-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.mdc-circular-progress--indeterminate .mdc-circular-progress__color-4{-webkit-animation:mdc-circular-progress-spinner-layer-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,mdc-circular-progress-color-4-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:mdc-circular-progress-spinner-layer-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,mdc-circular-progress-color-4-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.mdc-circular-progress--indeterminate .mdc-circular-progress__circle-left .mdc-circular-progress__indeterminate-circle-graphic{-webkit-animation:mdc-circular-progress-left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:mdc-circular-progress-left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.mdc-circular-progress--indeterminate .mdc-circular-progress__circle-right .mdc-circular-progress__indeterminate-circle-graphic{-webkit-animation:mdc-circular-progress-right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:mdc-circular-progress-right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.mdc-circular-progress--closed{opacity:0}.mdc-floating-label{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-subtitle1-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:1rem;font-size:var(--mdc-typography-subtitle1-font-size, 1rem);font-weight:400;font-weight:var(--mdc-typography-subtitle1-font-weight, 400);letter-spacing:0.009375em;letter-spacing:var(--mdc-typography-subtitle1-letter-spacing, 0.009375em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-subtitle1-text-decoration, inherit);text-decoration:var(--mdc-typography-subtitle1-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-subtitle1-text-transform, inherit);position:absolute;left:0;-webkit-transform-origin:left top;transform-origin:left top;line-height:1.15rem;text-align:left;text-overflow:ellipsis;white-space:nowrap;cursor:text;overflow:hidden;will-change:transform;transition:color 150ms cubic-bezier(0.4, 0, 0.2, 1),-webkit-transform 150ms cubic-bezier(0.4, 0, 0.2, 1);transition:transform 150ms cubic-bezier(0.4, 0, 0.2, 1),color 150ms cubic-bezier(0.4, 0, 0.2, 1);transition:transform 150ms cubic-bezier(0.4, 0, 0.2, 1),color 150ms cubic-bezier(0.4, 0, 0.2, 1),-webkit-transform 150ms cubic-bezier(0.4, 0, 0.2, 1)}[dir=rtl] .mdc-floating-label,.mdc-floating-label[dir=rtl]{right:0;left:auto;-webkit-transform-origin:right top;transform-origin:right top;text-align:right}.mdc-floating-label--float-above{cursor:auto}.mdc-floating-label--required::after{margin-left:1px;margin-right:0px;content:"*"}[dir=rtl] .mdc-floating-label--required::after,.mdc-floating-label--required[dir=rtl]::after{margin-left:0;margin-right:1px}.mdc-floating-label--float-above{-webkit-transform:translateY(-106%) scale(0.75);transform:translateY(-106%) scale(0.75)}.mdc-floating-label--shake{-webkit-animation:mdc-floating-label-shake-float-above-standard 250ms 1;animation:mdc-floating-label-shake-float-above-standard 250ms 1}@-webkit-keyframes mdc-floating-label-shake-float-above-standard{0%{-webkit-transform:translateX(calc(0 - 0%)) translateY(-106%) scale(0.75);transform:translateX(calc(0 - 0%)) translateY(-106%) scale(0.75)}33%{-webkit-animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);-webkit-transform:translateX(calc(4% - 0%)) translateY(-106%) scale(0.75);transform:translateX(calc(4% - 0%)) translateY(-106%) scale(0.75)}66%{-webkit-animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);-webkit-transform:translateX(calc(-4% - 0%)) translateY(-106%) scale(0.75);transform:translateX(calc(-4% - 0%)) translateY(-106%) scale(0.75)}100%{-webkit-transform:translateX(calc(0 - 0%)) translateY(-106%) scale(0.75);transform:translateX(calc(0 - 0%)) translateY(-106%) scale(0.75)}}@keyframes mdc-floating-label-shake-float-above-standard{0%{-webkit-transform:translateX(calc(0 - 0%)) translateY(-106%) scale(0.75);transform:translateX(calc(0 - 0%)) translateY(-106%) scale(0.75)}33%{-webkit-animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);-webkit-transform:translateX(calc(4% - 0%)) translateY(-106%) scale(0.75);transform:translateX(calc(4% - 0%)) translateY(-106%) scale(0.75)}66%{-webkit-animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);-webkit-transform:translateX(calc(-4% - 0%)) translateY(-106%) scale(0.75);transform:translateX(calc(-4% - 0%)) translateY(-106%) scale(0.75)}100%{-webkit-transform:translateX(calc(0 - 0%)) translateY(-106%) scale(0.75);transform:translateX(calc(0 - 0%)) translateY(-106%) scale(0.75)}}.mdc-line-ripple::before,.mdc-line-ripple::after{position:absolute;bottom:0;left:0;width:100%;border-bottom-style:solid;content:""}.mdc-line-ripple::before{border-bottom-width:1px;z-index:1}.mdc-line-ripple::after{-webkit-transform:scaleX(0);transform:scaleX(0);border-bottom-width:2px;opacity:0;z-index:2}.mdc-line-ripple::after{transition:opacity 180ms cubic-bezier(0.4, 0, 0.2, 1),-webkit-transform 180ms cubic-bezier(0.4, 0, 0.2, 1);transition:transform 180ms cubic-bezier(0.4, 0, 0.2, 1),opacity 180ms cubic-bezier(0.4, 0, 0.2, 1);transition:transform 180ms cubic-bezier(0.4, 0, 0.2, 1),opacity 180ms cubic-bezier(0.4, 0, 0.2, 1),-webkit-transform 180ms cubic-bezier(0.4, 0, 0.2, 1)}.mdc-line-ripple--active::after{-webkit-transform:scaleX(1);transform:scaleX(1);opacity:1}.mdc-line-ripple--deactivating::after{opacity:0}.mdc-notched-outline{display:flex;position:absolute;top:0;right:0;left:0;box-sizing:border-box;width:100%;max-width:100%;height:100%;text-align:left;pointer-events:none}[dir=rtl] .mdc-notched-outline,.mdc-notched-outline[dir=rtl]{text-align:right}.mdc-notched-outline__leading,.mdc-notched-outline__notch,.mdc-notched-outline__trailing{box-sizing:border-box;height:100%;border-top:1px solid;border-bottom:1px solid;pointer-events:none}.mdc-notched-outline__leading{border-left:1px solid;border-right:none;width:12px}[dir=rtl] .mdc-notched-outline__leading,.mdc-notched-outline__leading[dir=rtl]{border-left:none;border-right:1px solid}.mdc-notched-outline__trailing{border-left:none;border-right:1px solid;flex-grow:1}[dir=rtl] .mdc-notched-outline__trailing,.mdc-notched-outline__trailing[dir=rtl]{border-left:1px solid;border-right:none}.mdc-notched-outline__notch{flex:0 0 auto;width:auto;max-width:calc(100% - 12px * 2)}.mdc-notched-outline .mdc-floating-label{display:inline-block;position:relative;max-width:100%}.mdc-notched-outline .mdc-floating-label--float-above{text-overflow:clip}.mdc-notched-outline--upgraded .mdc-floating-label--float-above{max-width:calc(100% / 0.75)}.mdc-notched-outline--notched .mdc-notched-outline__notch{padding-left:0;padding-right:8px;border-top:none}[dir=rtl] .mdc-notched-outline--notched .mdc-notched-outline__notch,.mdc-notched-outline--notched .mdc-notched-outline__notch[dir=rtl]{padding-left:8px;padding-right:0}.mdc-notched-outline--no-label .mdc-notched-outline__notch{display:none}.mdc-select{display:inline-flex;position:relative}.mdc-select:not(.mdc-select--disabled) .mdc-select__selected-text{color:rgba(0, 0, 0, 0.87)}.mdc-select.mdc-select--disabled .mdc-select__selected-text{color:rgba(0, 0, 0, 0.38)}.mdc-select:not(.mdc-select--disabled) .mdc-floating-label{color:rgba(0, 0, 0, 0.6)}.mdc-select:not(.mdc-select--disabled).mdc-select--focused .mdc-floating-label{color:rgba(98, 0, 238, 0.87)}.mdc-select.mdc-select--disabled .mdc-floating-label{color:rgba(0, 0, 0, 0.38)}.mdc-select:not(.mdc-select--disabled) .mdc-select__dropdown-icon{fill:rgba(0, 0, 0, 0.54)}.mdc-select:not(.mdc-select--disabled).mdc-select--focused .mdc-select__dropdown-icon{fill:#6200ee;fill:var(--mdc-theme-primary, #6200ee)}.mdc-select.mdc-select--disabled .mdc-select__dropdown-icon{fill:rgba(0, 0, 0, 0.38)}.mdc-select:not(.mdc-select--disabled)+.mdc-select-helper-text{color:rgba(0, 0, 0, 0.6)}.mdc-select.mdc-select--disabled+.mdc-select-helper-text{color:rgba(0, 0, 0, 0.38)}.mdc-select:not(.mdc-select--disabled) .mdc-select__icon{color:rgba(0, 0, 0, 0.54)}.mdc-select.mdc-select--disabled .mdc-select__icon{color:rgba(0, 0, 0, 0.38)}@media screen and (forced-colors: active),(-ms-high-contrast: active){.mdc-select.mdc-select--disabled .mdc-select__selected-text{color:GrayText}.mdc-select.mdc-select--disabled .mdc-select__dropdown-icon{fill:red}.mdc-select.mdc-select--disabled .mdc-floating-label{color:GrayText}.mdc-select.mdc-select--disabled .mdc-line-ripple::before{border-bottom-color:GrayText}.mdc-select.mdc-select--disabled .mdc-notched-outline__leading,.mdc-select.mdc-select--disabled .mdc-notched-outline__notch,.mdc-select.mdc-select--disabled .mdc-notched-outline__trailing{border-color:GrayText}.mdc-select.mdc-select--disabled .mdc-select__icon{color:GrayText}.mdc-select.mdc-select--disabled+.mdc-select-helper-text{color:GrayText}}.mdc-select .mdc-floating-label{top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);pointer-events:none}.mdc-select .mdc-select__anchor{padding-left:16px;padding-right:0}[dir=rtl] .mdc-select .mdc-select__anchor,.mdc-select .mdc-select__anchor[dir=rtl]{padding-left:0;padding-right:16px}.mdc-select.mdc-select--with-leading-icon .mdc-select__anchor{padding-left:0;padding-right:0}[dir=rtl] .mdc-select.mdc-select--with-leading-icon .mdc-select__anchor,.mdc-select.mdc-select--with-leading-icon .mdc-select__anchor[dir=rtl]{padding-left:0;padding-right:0}.mdc-select .mdc-select__icon{width:24px;height:24px;font-size:24px}.mdc-select .mdc-select__dropdown-icon{width:24px;height:24px}.mdc-select .mdc-select__menu .mdc-deprecated-list-item{padding-left:16px;padding-right:16px}[dir=rtl] .mdc-select .mdc-select__menu .mdc-deprecated-list-item,.mdc-select .mdc-select__menu .mdc-deprecated-list-item[dir=rtl]{padding-left:16px;padding-right:16px}.mdc-select .mdc-select__menu .mdc-deprecated-list-item__graphic{margin-left:0;margin-right:12px}[dir=rtl] .mdc-select .mdc-select__menu .mdc-deprecated-list-item__graphic,.mdc-select .mdc-select__menu .mdc-deprecated-list-item__graphic[dir=rtl]{margin-left:12px;margin-right:0}.mdc-select__dropdown-icon{margin-left:12px;margin-right:12px;display:inline-flex;position:relative;align-self:center;align-items:center;justify-content:center;flex-shrink:0;pointer-events:none}.mdc-select__dropdown-icon .mdc-select__dropdown-icon-active,.mdc-select__dropdown-icon .mdc-select__dropdown-icon-inactive{position:absolute;top:0;left:0}.mdc-select__dropdown-icon .mdc-select__dropdown-icon-graphic{width:41.6666666667%;height:20.8333333333%}.mdc-select__dropdown-icon .mdc-select__dropdown-icon-inactive{opacity:1;transition:opacity 75ms linear 75ms}.mdc-select__dropdown-icon .mdc-select__dropdown-icon-active{opacity:0;transition:opacity 75ms linear}[dir=rtl] .mdc-select__dropdown-icon,.mdc-select__dropdown-icon[dir=rtl]{margin-left:12px;margin-right:12px}.mdc-select--activated .mdc-select__dropdown-icon .mdc-select__dropdown-icon-inactive{opacity:0;transition:opacity 49.5ms linear}.mdc-select--activated .mdc-select__dropdown-icon .mdc-select__dropdown-icon-active{opacity:1;transition:opacity 100.5ms linear 49.5ms}.mdc-select__anchor{width:200px;min-width:0;flex:1 1 auto;position:relative;box-sizing:border-box;overflow:hidden;outline:none;cursor:pointer}.mdc-select__anchor .mdc-floating-label--float-above{-webkit-transform:translateY(-106%) scale(0.75);transform:translateY(-106%) scale(0.75)}.mdc-select__selected-text-container{display:flex;-webkit-appearance:none;-moz-appearance:none;appearance:none;pointer-events:none;box-sizing:border-box;width:auto;min-width:0;flex-grow:1;height:28px;border:none;outline:none;padding:0;background-color:transparent;color:inherit}.mdc-select__selected-text{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-subtitle1-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:1rem;font-size:var(--mdc-typography-subtitle1-font-size, 1rem);line-height:1.75rem;line-height:var(--mdc-typography-subtitle1-line-height, 1.75rem);font-weight:400;font-weight:var(--mdc-typography-subtitle1-font-weight, 400);letter-spacing:0.009375em;letter-spacing:var(--mdc-typography-subtitle1-letter-spacing, 0.009375em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-subtitle1-text-decoration, inherit);text-decoration:var(--mdc-typography-subtitle1-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-subtitle1-text-transform, inherit);text-overflow:ellipsis;white-space:nowrap;overflow:hidden;display:block;width:100%;text-align:left}[dir=rtl] .mdc-select__selected-text,.mdc-select__selected-text[dir=rtl]{text-align:right}.mdc-select--invalid:not(.mdc-select--disabled) .mdc-floating-label{color:#b00020;color:var(--mdc-theme-error, #b00020)}.mdc-select--invalid:not(.mdc-select--disabled).mdc-select--focused .mdc-floating-label{color:#b00020;color:var(--mdc-theme-error, #b00020)}.mdc-select--invalid:not(.mdc-select--disabled).mdc-select--invalid+.mdc-select-helper-text--validation-msg{color:#b00020;color:var(--mdc-theme-error, #b00020)}.mdc-select--invalid:not(.mdc-select--disabled) .mdc-select__dropdown-icon{fill:#b00020;fill:var(--mdc-theme-error, #b00020)}.mdc-select--invalid:not(.mdc-select--disabled).mdc-select--focused .mdc-select__dropdown-icon{fill:#b00020;fill:var(--mdc-theme-error, #b00020)}.mdc-select--disabled{cursor:default;pointer-events:none}.mdc-select--with-leading-icon .mdc-select__menu .mdc-deprecated-list-item{padding-left:12px;padding-right:12px}[dir=rtl] .mdc-select--with-leading-icon .mdc-select__menu .mdc-deprecated-list-item,.mdc-select--with-leading-icon .mdc-select__menu .mdc-deprecated-list-item[dir=rtl]{padding-left:12px;padding-right:12px}.mdc-select__menu .mdc-deprecated-list .mdc-select__icon{margin-left:0;margin-right:0}[dir=rtl] .mdc-select__menu .mdc-deprecated-list .mdc-select__icon,.mdc-select__menu .mdc-deprecated-list .mdc-select__icon[dir=rtl]{margin-left:0;margin-right:0}.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected,.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--activated{color:#000;color:var(--mdc-theme-on-surface, #000)}.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected .mdc-deprecated-list-item__graphic,.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--activated .mdc-deprecated-list-item__graphic{color:#000;color:var(--mdc-theme-on-surface, #000)}.mdc-select--filled .mdc-select__anchor{height:56px;display:flex;align-items:baseline}.mdc-select--filled .mdc-select__anchor::before{display:inline-block;width:0;height:40px;content:"";vertical-align:0}.mdc-select--filled.mdc-select--no-label .mdc-select__anchor .mdc-select__selected-text::before{content:""}.mdc-select--filled.mdc-select--no-label .mdc-select__anchor .mdc-select__selected-text-container{height:100%;display:inline-flex;align-items:center}.mdc-select--filled.mdc-select--no-label .mdc-select__anchor::before{display:none}.mdc-select--filled .mdc-select__anchor{border-top-left-radius:4px;border-top-left-radius:var(--mdc-shape-small, 4px);border-top-right-radius:4px;border-top-right-radius:var(--mdc-shape-small, 4px);border-bottom-right-radius:0;border-bottom-left-radius:0}.mdc-select--filled:not(.mdc-select--disabled) .mdc-select__anchor{background-color:whitesmoke}.mdc-select--filled.mdc-select--disabled .mdc-select__anchor{background-color:#fafafa}.mdc-select--filled:not(.mdc-select--disabled) .mdc-line-ripple::before{border-bottom-color:rgba(0, 0, 0, 0.42)}.mdc-select--filled:not(.mdc-select--disabled):hover .mdc-line-ripple::before{border-bottom-color:rgba(0, 0, 0, 0.87)}.mdc-select--filled:not(.mdc-select--disabled) .mdc-line-ripple::after{border-bottom-color:#6200ee;border-bottom-color:var(--mdc-theme-primary, #6200ee)}.mdc-select--filled.mdc-select--disabled .mdc-line-ripple::before{border-bottom-color:rgba(0, 0, 0, 0.06)}.mdc-select--filled .mdc-floating-label{max-width:calc(100% - 64px)}.mdc-select--filled .mdc-floating-label--float-above{max-width:calc(100% / 0.75 - 64px / 0.75)}.mdc-select--filled .mdc-menu-surface--is-open-below{border-top-left-radius:0px;border-top-right-radius:0px}.mdc-select--filled.mdc-select--focused.mdc-line-ripple::after{-webkit-transform:scale(1, 2);transform:scale(1, 2);opacity:1}.mdc-select--filled .mdc-floating-label{left:16px;right:initial}[dir=rtl] .mdc-select--filled .mdc-floating-label,.mdc-select--filled .mdc-floating-label[dir=rtl]{left:initial;right:16px}.mdc-select--filled.mdc-select--with-leading-icon .mdc-floating-label{left:48px;right:initial}[dir=rtl] .mdc-select--filled.mdc-select--with-leading-icon .mdc-floating-label,.mdc-select--filled.mdc-select--with-leading-icon .mdc-floating-label[dir=rtl]{left:initial;right:48px}.mdc-select--filled.mdc-select--with-leading-icon .mdc-floating-label{max-width:calc(100% - 96px)}.mdc-select--filled.mdc-select--with-leading-icon .mdc-floating-label--float-above{max-width:calc(100% / 0.75 - 96px / 0.75)}.mdc-select--invalid:not(.mdc-select--disabled) .mdc-line-ripple::before{border-bottom-color:#b00020;border-bottom-color:var(--mdc-theme-error, #b00020)}.mdc-select--invalid:not(.mdc-select--disabled):hover .mdc-line-ripple::before{border-bottom-color:#b00020;border-bottom-color:var(--mdc-theme-error, #b00020)}.mdc-select--invalid:not(.mdc-select--disabled) .mdc-line-ripple::after{border-bottom-color:#b00020;border-bottom-color:var(--mdc-theme-error, #b00020)}.mdc-select--outlined{border:none}.mdc-select--outlined .mdc-select__anchor{height:56px}.mdc-select--outlined .mdc-select__anchor .mdc-floating-label--float-above{-webkit-transform:translateY(-37.25px) scale(1);transform:translateY(-37.25px) scale(1)}.mdc-select--outlined .mdc-select__anchor .mdc-floating-label--float-above{font-size:.75rem}.mdc-select--outlined .mdc-select__anchor.mdc-notched-outline--upgraded .mdc-floating-label--float-above,.mdc-select--outlined .mdc-select__anchor .mdc-notched-outline--upgraded .mdc-floating-label--float-above{-webkit-transform:translateY(-34.75px) scale(0.75);transform:translateY(-34.75px) scale(0.75)}.mdc-select--outlined .mdc-select__anchor.mdc-notched-outline--upgraded .mdc-floating-label--float-above,.mdc-select--outlined .mdc-select__anchor .mdc-notched-outline--upgraded .mdc-floating-label--float-above{font-size:1rem}.mdc-select--outlined .mdc-select__anchor .mdc-floating-label--shake{-webkit-animation:mdc-floating-label-shake-float-above-select-outlined-56px 250ms 1;animation:mdc-floating-label-shake-float-above-select-outlined-56px 250ms 1}@-webkit-keyframes mdc-floating-label-shake-float-above-select-outlined-56px{0%{-webkit-transform:translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75);transform:translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75)}33%{-webkit-animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);-webkit-transform:translateX(calc(4% - 0%)) translateY(-34.75px) scale(0.75);transform:translateX(calc(4% - 0%)) translateY(-34.75px) scale(0.75)}66%{-webkit-animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);-webkit-transform:translateX(calc(-4% - 0%)) translateY(-34.75px) scale(0.75);transform:translateX(calc(-4% - 0%)) translateY(-34.75px) scale(0.75)}100%{-webkit-transform:translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75);transform:translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75)}}@keyframes mdc-floating-label-shake-float-above-select-outlined-56px{0%{-webkit-transform:translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75);transform:translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75)}33%{-webkit-animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);-webkit-transform:translateX(calc(4% - 0%)) translateY(-34.75px) scale(0.75);transform:translateX(calc(4% - 0%)) translateY(-34.75px) scale(0.75)}66%{-webkit-animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);-webkit-transform:translateX(calc(-4% - 0%)) translateY(-34.75px) scale(0.75);transform:translateX(calc(-4% - 0%)) translateY(-34.75px) scale(0.75)}100%{-webkit-transform:translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75);transform:translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75)}}.mdc-select--outlined .mdc-notched-outline .mdc-notched-outline__leading{border-top-left-radius:4px;border-top-left-radius:var(--mdc-shape-small, 4px);border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:4px;border-bottom-left-radius:var(--mdc-shape-small, 4px)}[dir=rtl] .mdc-select--outlined .mdc-notched-outline .mdc-notched-outline__leading,.mdc-select--outlined .mdc-notched-outline .mdc-notched-outline__leading[dir=rtl]{border-top-left-radius:0;border-top-right-radius:4px;border-top-right-radius:var(--mdc-shape-small, 4px);border-bottom-right-radius:4px;border-bottom-right-radius:var(--mdc-shape-small, 4px);border-bottom-left-radius:0}@supports(top: max(0%)){.mdc-select--outlined .mdc-notched-outline .mdc-notched-outline__leading{width:max(12px, var(--mdc-shape-small, 4px))}}@supports(top: max(0%)){.mdc-select--outlined .mdc-notched-outline .mdc-notched-outline__notch{max-width:calc(100% - max(12px, var(--mdc-shape-small, 4px)) * 2)}}.mdc-select--outlined .mdc-notched-outline .mdc-notched-outline__trailing{border-top-left-radius:0;border-top-right-radius:4px;border-top-right-radius:var(--mdc-shape-small, 4px);border-bottom-right-radius:4px;border-bottom-right-radius:var(--mdc-shape-small, 4px);border-bottom-left-radius:0}[dir=rtl] .mdc-select--outlined .mdc-notched-outline .mdc-notched-outline__trailing,.mdc-select--outlined .mdc-notched-outline .mdc-notched-outline__trailing[dir=rtl]{border-top-left-radius:4px;border-top-left-radius:var(--mdc-shape-small, 4px);border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:4px;border-bottom-left-radius:var(--mdc-shape-small, 4px)}@supports(top: max(0%)){.mdc-select--outlined .mdc-select__anchor{padding-left:max(16px, calc(var(--mdc-shape-small, 4px) + 4px))}}[dir=rtl] .mdc-select--outlined .mdc-select__anchor,.mdc-select--outlined .mdc-select__anchor[dir=rtl]{padding-left:0}@supports(top: max(0%)){[dir=rtl] .mdc-select--outlined .mdc-select__anchor,.mdc-select--outlined .mdc-select__anchor[dir=rtl]{padding-right:max(16px, calc(var(--mdc-shape-small, 4px) + 4px))}}@supports(top: max(0%)){.mdc-select--outlined+.mdc-select-helper-text{margin-left:max(16px, calc(var(--mdc-shape-small, 4px) + 4px))}}[dir=rtl] .mdc-select--outlined+.mdc-select-helper-text,.mdc-select--outlined+.mdc-select-helper-text[dir=rtl]{margin-left:0}@supports(top: max(0%)){[dir=rtl] .mdc-select--outlined+.mdc-select-helper-text,.mdc-select--outlined+.mdc-select-helper-text[dir=rtl]{margin-right:max(16px, calc(var(--mdc-shape-small, 4px) + 4px))}}.mdc-select--outlined:not(.mdc-select--disabled) .mdc-select__anchor{background-color:transparent}.mdc-select--outlined.mdc-select--disabled .mdc-select__anchor{background-color:transparent}.mdc-select--outlined:not(.mdc-select--disabled) .mdc-notched-outline__leading,.mdc-select--outlined:not(.mdc-select--disabled) .mdc-notched-outline__notch,.mdc-select--outlined:not(.mdc-select--disabled) .mdc-notched-outline__trailing{border-color:rgba(0, 0, 0, 0.38)}.mdc-select--outlined:not(.mdc-select--disabled):not(.mdc-select--focused) .mdc-select__anchor:hover .mdc-notched-outline .mdc-notched-outline__leading,.mdc-select--outlined:not(.mdc-select--disabled):not(.mdc-select--focused) .mdc-select__anchor:hover .mdc-notched-outline .mdc-notched-outline__notch,.mdc-select--outlined:not(.mdc-select--disabled):not(.mdc-select--focused) .mdc-select__anchor:hover .mdc-notched-outline .mdc-notched-outline__trailing{border-color:rgba(0, 0, 0, 0.87)}.mdc-select--outlined:not(.mdc-select--disabled).mdc-select--focused .mdc-notched-outline .mdc-notched-outline__leading,.mdc-select--outlined:not(.mdc-select--disabled).mdc-select--focused .mdc-notched-outline .mdc-notched-outline__notch,.mdc-select--outlined:not(.mdc-select--disabled).mdc-select--focused .mdc-notched-outline .mdc-notched-outline__trailing{border-width:2px}.mdc-select--outlined:not(.mdc-select--disabled).mdc-select--focused .mdc-notched-outline .mdc-notched-outline__leading,.mdc-select--outlined:not(.mdc-select--disabled).mdc-select--focused .mdc-notched-outline .mdc-notched-outline__notch,.mdc-select--outlined:not(.mdc-select--disabled).mdc-select--focused .mdc-notched-outline .mdc-notched-outline__trailing{border-color:#6200ee;border-color:var(--mdc-theme-primary, #6200ee)}.mdc-select--outlined.mdc-select--disabled .mdc-notched-outline__leading,.mdc-select--outlined.mdc-select--disabled .mdc-notched-outline__notch,.mdc-select--outlined.mdc-select--disabled .mdc-notched-outline__trailing{border-color:rgba(0, 0, 0, 0.06)}.mdc-select--outlined .mdc-select__anchor :not(.mdc-notched-outline--notched) .mdc-notched-outline__notch{max-width:calc(100% - 60px)}.mdc-select--outlined .mdc-select__anchor{display:flex;align-items:baseline;overflow:visible}.mdc-select--outlined .mdc-select__anchor .mdc-floating-label--shake{-webkit-animation:mdc-floating-label-shake-float-above-select-outlined 250ms 1;animation:mdc-floating-label-shake-float-above-select-outlined 250ms 1}.mdc-select--outlined .mdc-select__anchor .mdc-floating-label--float-above{-webkit-transform:translateY(-37.25px) scale(1);transform:translateY(-37.25px) scale(1)}.mdc-select--outlined .mdc-select__anchor .mdc-floating-label--float-above{font-size:.75rem}.mdc-select--outlined .mdc-select__anchor.mdc-notched-outline--upgraded .mdc-floating-label--float-above,.mdc-select--outlined .mdc-select__anchor .mdc-notched-outline--upgraded .mdc-floating-label--float-above{-webkit-transform:translateY(-34.75px) scale(0.75);transform:translateY(-34.75px) scale(0.75)}.mdc-select--outlined .mdc-select__anchor.mdc-notched-outline--upgraded .mdc-floating-label--float-above,.mdc-select--outlined .mdc-select__anchor .mdc-notched-outline--upgraded .mdc-floating-label--float-above{font-size:1rem}.mdc-select--outlined .mdc-select__anchor .mdc-notched-outline--notched .mdc-notched-outline__notch{padding-top:1px}.mdc-select--outlined .mdc-select__anchor .mdc-select__selected-text::before{content:""}.mdc-select--outlined .mdc-select__anchor .mdc-select__selected-text-container{height:100%;display:inline-flex;align-items:center}.mdc-select--outlined .mdc-select__anchor::before{display:none}.mdc-select--outlined .mdc-select__selected-text-container{display:flex;border:none;z-index:1;background-color:transparent}.mdc-select--outlined .mdc-select__icon{z-index:2}.mdc-select--outlined .mdc-floating-label{line-height:1.15rem;left:4px;right:initial}[dir=rtl] .mdc-select--outlined .mdc-floating-label,.mdc-select--outlined .mdc-floating-label[dir=rtl]{left:initial;right:4px}.mdc-select--outlined.mdc-select--focused .mdc-notched-outline--notched .mdc-notched-outline__notch{padding-top:2px}.mdc-select--outlined.mdc-select--invalid:not(.mdc-select--disabled) .mdc-notched-outline__leading,.mdc-select--outlined.mdc-select--invalid:not(.mdc-select--disabled) .mdc-notched-outline__notch,.mdc-select--outlined.mdc-select--invalid:not(.mdc-select--disabled) .mdc-notched-outline__trailing{border-color:#b00020;border-color:var(--mdc-theme-error, #b00020)}.mdc-select--outlined.mdc-select--invalid:not(.mdc-select--disabled):not(.mdc-select--focused) .mdc-select__anchor:hover .mdc-notched-outline .mdc-notched-outline__leading,.mdc-select--outlined.mdc-select--invalid:not(.mdc-select--disabled):not(.mdc-select--focused) .mdc-select__anchor:hover .mdc-notched-outline .mdc-notched-outline__notch,.mdc-select--outlined.mdc-select--invalid:not(.mdc-select--disabled):not(.mdc-select--focused) .mdc-select__anchor:hover .mdc-notched-outline .mdc-notched-outline__trailing{border-color:#b00020;border-color:var(--mdc-theme-error, #b00020)}.mdc-select--outlined.mdc-select--invalid:not(.mdc-select--disabled).mdc-select--focused .mdc-notched-outline .mdc-notched-outline__leading,.mdc-select--outlined.mdc-select--invalid:not(.mdc-select--disabled).mdc-select--focused .mdc-notched-outline .mdc-notched-outline__notch,.mdc-select--outlined.mdc-select--invalid:not(.mdc-select--disabled).mdc-select--focused .mdc-notched-outline .mdc-notched-outline__trailing{border-width:2px}.mdc-select--outlined.mdc-select--invalid:not(.mdc-select--disabled).mdc-select--focused .mdc-notched-outline .mdc-notched-outline__leading,.mdc-select--outlined.mdc-select--invalid:not(.mdc-select--disabled).mdc-select--focused .mdc-notched-outline .mdc-notched-outline__notch,.mdc-select--outlined.mdc-select--invalid:not(.mdc-select--disabled).mdc-select--focused .mdc-notched-outline .mdc-notched-outline__trailing{border-color:#b00020;border-color:var(--mdc-theme-error, #b00020)}.mdc-select--outlined.mdc-select--with-leading-icon .mdc-floating-label{left:36px;right:initial}[dir=rtl] .mdc-select--outlined.mdc-select--with-leading-icon .mdc-floating-label,.mdc-select--outlined.mdc-select--with-leading-icon .mdc-floating-label[dir=rtl]{left:initial;right:36px}.mdc-select--outlined.mdc-select--with-leading-icon .mdc-floating-label--float-above{-webkit-transform:translateY(-37.25px) translateX(-32px) scale(1);transform:translateY(-37.25px) translateX(-32px) scale(1)}[dir=rtl] .mdc-select--outlined.mdc-select--with-leading-icon .mdc-floating-label--float-above,.mdc-select--outlined.mdc-select--with-leading-icon .mdc-floating-label--float-above[dir=rtl]{-webkit-transform:translateY(-37.25px) translateX(32px) scale(1);transform:translateY(-37.25px) translateX(32px) scale(1)}.mdc-select--outlined.mdc-select--with-leading-icon .mdc-floating-label--float-above{font-size:.75rem}.mdc-select--outlined.mdc-select--with-leading-icon.mdc-notched-outline--upgraded .mdc-floating-label--float-above,.mdc-select--outlined.mdc-select--with-leading-icon .mdc-notched-outline--upgraded .mdc-floating-label--float-above{-webkit-transform:translateY(-34.75px) translateX(-32px) scale(0.75);transform:translateY(-34.75px) translateX(-32px) scale(0.75)}[dir=rtl] .mdc-select--outlined.mdc-select--with-leading-icon.mdc-notched-outline--upgraded .mdc-floating-label--float-above,[dir=rtl] .mdc-select--outlined.mdc-select--with-leading-icon .mdc-notched-outline--upgraded .mdc-floating-label--float-above,.mdc-select--outlined.mdc-select--with-leading-icon.mdc-notched-outline--upgraded .mdc-floating-label--float-above[dir=rtl],.mdc-select--outlined.mdc-select--with-leading-icon .mdc-notched-outline--upgraded .mdc-floating-label--float-above[dir=rtl]{-webkit-transform:translateY(-34.75px) translateX(32px) scale(0.75);transform:translateY(-34.75px) translateX(32px) scale(0.75)}.mdc-select--outlined.mdc-select--with-leading-icon.mdc-notched-outline--upgraded .mdc-floating-label--float-above,.mdc-select--outlined.mdc-select--with-leading-icon .mdc-notched-outline--upgraded .mdc-floating-label--float-above{font-size:1rem}.mdc-select--outlined.mdc-select--with-leading-icon .mdc-floating-label--shake{-webkit-animation:mdc-floating-label-shake-float-above-select-outlined-leading-icon-56px 250ms 1;animation:mdc-floating-label-shake-float-above-select-outlined-leading-icon-56px 250ms 1}@-webkit-keyframes mdc-floating-label-shake-float-above-select-outlined-leading-icon-56px{0%{-webkit-transform:translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75)}33%{-webkit-animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);-webkit-transform:translateX(calc(4% - 32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(4% - 32px)) translateY(-34.75px) scale(0.75)}66%{-webkit-animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);-webkit-transform:translateX(calc(-4% - 32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(-4% - 32px)) translateY(-34.75px) scale(0.75)}100%{-webkit-transform:translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75)}}@keyframes mdc-floating-label-shake-float-above-select-outlined-leading-icon-56px{0%{-webkit-transform:translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75)}33%{-webkit-animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);-webkit-transform:translateX(calc(4% - 32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(4% - 32px)) translateY(-34.75px) scale(0.75)}66%{-webkit-animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);-webkit-transform:translateX(calc(-4% - 32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(-4% - 32px)) translateY(-34.75px) scale(0.75)}100%{-webkit-transform:translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75)}}[dir=rtl] .mdc-select--outlined.mdc-select--with-leading-icon .mdc-floating-label--shake,.mdc-select--outlined.mdc-select--with-leading-icon[dir=rtl] .mdc-floating-label--shake{-webkit-animation:mdc-floating-label-shake-float-above-select-outlined-leading-icon-56px 250ms 1;animation:mdc-floating-label-shake-float-above-select-outlined-leading-icon-56px 250ms 1}@-webkit-keyframes mdc-floating-label-shake-float-above-select-outlined-leading-icon-56px-rtl{0%{-webkit-transform:translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75)}33%{-webkit-animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);-webkit-transform:translateX(calc(4% - -32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(4% - -32px)) translateY(-34.75px) scale(0.75)}66%{-webkit-animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);-webkit-transform:translateX(calc(-4% - -32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(-4% - -32px)) translateY(-34.75px) scale(0.75)}100%{-webkit-transform:translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75)}}@keyframes mdc-floating-label-shake-float-above-select-outlined-leading-icon-56px-rtl{0%{-webkit-transform:translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75)}33%{-webkit-animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);-webkit-transform:translateX(calc(4% - -32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(4% - -32px)) translateY(-34.75px) scale(0.75)}66%{-webkit-animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);-webkit-transform:translateX(calc(-4% - -32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(-4% - -32px)) translateY(-34.75px) scale(0.75)}100%{-webkit-transform:translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75)}}.mdc-select--outlined.mdc-select--with-leading-icon .mdc-select__anchor :not(.mdc-notched-outline--notched) .mdc-notched-outline__notch{max-width:calc(100% - 96px)}.mdc-select--outlined .mdc-menu-surface{margin-bottom:8px}.mdc-select--outlined.mdc-select--no-label .mdc-menu-surface,.mdc-select--outlined .mdc-menu-surface--is-open-below{margin-bottom:0}.mdc-select__anchor{--mdc-ripple-fg-size: 0;--mdc-ripple-left: 0;--mdc-ripple-top: 0;--mdc-ripple-fg-scale: 1;--mdc-ripple-fg-translate-end: 0;--mdc-ripple-fg-translate-start: 0;-webkit-tap-highlight-color:rgba(0,0,0,0);will-change:transform,opacity}.mdc-select__anchor .mdc-select__ripple::before,.mdc-select__anchor .mdc-select__ripple::after{position:absolute;border-radius:50%;opacity:0;pointer-events:none;content:""}.mdc-select__anchor .mdc-select__ripple::before{transition:opacity 15ms linear,background-color 15ms linear;z-index:1;z-index:var(--mdc-ripple-z-index, 1)}.mdc-select__anchor .mdc-select__ripple::after{z-index:0;z-index:var(--mdc-ripple-z-index, 0)}.mdc-select__anchor.mdc-ripple-upgraded .mdc-select__ripple::before{-webkit-transform:scale(var(--mdc-ripple-fg-scale, 1));transform:scale(var(--mdc-ripple-fg-scale, 1))}.mdc-select__anchor.mdc-ripple-upgraded .mdc-select__ripple::after{top:0;left:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:center center;transform-origin:center center}.mdc-select__anchor.mdc-ripple-upgraded--unbounded .mdc-select__ripple::after{top:var(--mdc-ripple-top, 0);left:var(--mdc-ripple-left, 0)}.mdc-select__anchor.mdc-ripple-upgraded--foreground-activation .mdc-select__ripple::after{-webkit-animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards;animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards}.mdc-select__anchor.mdc-ripple-upgraded--foreground-deactivation .mdc-select__ripple::after{-webkit-animation:mdc-ripple-fg-opacity-out 150ms;animation:mdc-ripple-fg-opacity-out 150ms;-webkit-transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1))}.mdc-select__anchor .mdc-select__ripple::before,.mdc-select__anchor .mdc-select__ripple::after{top:calc(50% - 100%);left:calc(50% - 100%);width:200%;height:200%}.mdc-select__anchor.mdc-ripple-upgraded .mdc-select__ripple::after{width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}.mdc-select__anchor .mdc-select__ripple::before,.mdc-select__anchor .mdc-select__ripple::after{background-color:rgba(0, 0, 0, 0.87);background-color:var(--mdc-ripple-color, rgba(0, 0, 0, 0.87))}.mdc-select__anchor:hover .mdc-select__ripple::before,.mdc-select__anchor.mdc-ripple-surface--hover .mdc-select__ripple::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}.mdc-select__anchor.mdc-ripple-upgraded--background-focused .mdc-select__ripple::before,.mdc-select__anchor:not(.mdc-ripple-upgraded):focus .mdc-select__ripple::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-select__anchor .mdc-select__ripple{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none}.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected .mdc-deprecated-list-item__ripple::before,.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected .mdc-deprecated-list-item__ripple::after{background-color:#000;background-color:var(--mdc-ripple-color, var(--mdc-theme-on-surface, #000))}.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected:hover .mdc-deprecated-list-item__ripple::before,.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected.mdc-ripple-surface--hover .mdc-deprecated-list-item__ripple::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected.mdc-ripple-upgraded--background-focused .mdc-deprecated-list-item__ripple::before,.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected:not(.mdc-ripple-upgraded):focus .mdc-deprecated-list-item__ripple::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected:not(.mdc-ripple-upgraded) .mdc-deprecated-list-item__ripple::after{transition:opacity 150ms linear}.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected:not(.mdc-ripple-upgraded):active .mdc-deprecated-list-item__ripple::after{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected .mdc-list-item__ripple::before,.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected .mdc-list-item__ripple::after{background-color:#000;background-color:var(--mdc-ripple-color, var(--mdc-theme-on-surface, #000))}.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected:hover .mdc-list-item__ripple::before,.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected.mdc-ripple-surface--hover .mdc-list-item__ripple::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected.mdc-ripple-upgraded--background-focused .mdc-list-item__ripple::before,.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected:not(.mdc-ripple-upgraded):focus .mdc-list-item__ripple::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected:not(.mdc-ripple-upgraded) .mdc-list-item__ripple::after{transition:opacity 150ms linear}.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected:not(.mdc-ripple-upgraded):active .mdc-list-item__ripple::after{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-select__menu .mdc-deprecated-list .mdc-deprecated-list-item--selected.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-select-helper-text{margin:0;margin-left:16px;margin-right:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-caption-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.75rem;font-size:var(--mdc-typography-caption-font-size, 0.75rem);line-height:1.25rem;line-height:var(--mdc-typography-caption-line-height, 1.25rem);font-weight:400;font-weight:var(--mdc-typography-caption-font-weight, 400);letter-spacing:0.0333333333em;letter-spacing:var(--mdc-typography-caption-letter-spacing, 0.0333333333em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-caption-text-decoration, inherit);text-decoration:var(--mdc-typography-caption-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-caption-text-transform, inherit);display:block;margin-top:0;line-height:normal}[dir=rtl] .mdc-select-helper-text,.mdc-select-helper-text[dir=rtl]{margin-left:16px;margin-right:16px}.mdc-select-helper-text::before{display:inline-block;width:0;height:16px;content:"";vertical-align:0}.mdc-select-helper-text--validation-msg{opacity:0;transition:opacity 180ms cubic-bezier(0.4, 0, 0.2, 1)}.mdc-select--invalid+.mdc-select-helper-text--validation-msg,.mdc-select-helper-text--validation-msg-persistent{opacity:1}.mdc-select--with-leading-icon .mdc-select__icon{display:inline-block;box-sizing:border-box;border:none;text-decoration:none;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;flex-shrink:0;align-self:center;background-color:transparent;fill:currentColor}.mdc-select--with-leading-icon .mdc-select__icon{margin-left:12px;margin-right:12px}[dir=rtl] .mdc-select--with-leading-icon .mdc-select__icon,.mdc-select--with-leading-icon .mdc-select__icon[dir=rtl]{margin-left:12px;margin-right:12px}.mdc-select__icon:not([tabindex]),.mdc-select__icon[tabindex="-1"]{cursor:default;pointer-events:none}.mdc-data-table__content{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.875rem;font-size:var(--mdc-typography-body2-font-size, 0.875rem);line-height:1.25rem;line-height:var(--mdc-typography-body2-line-height, 1.25rem);font-weight:400;font-weight:var(--mdc-typography-body2-font-weight, 400);letter-spacing:0.0178571429em;letter-spacing:var(--mdc-typography-body2-letter-spacing, 0.0178571429em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-body2-text-transform, inherit)}.mdc-data-table{background-color:#fff;background-color:var(--mdc-theme-surface, #fff);border-radius:4px;border-radius:var(--mdc-shape-medium, 4px);border-width:1px;border-style:solid;border-color:rgba(0,0,0,.12);-webkit-overflow-scrolling:touch;display:inline-flex;flex-direction:column;box-sizing:border-box;position:relative}.mdc-data-table .mdc-data-table__header-cell:first-child{border-top-left-radius:4px;border-top-left-radius:var(--mdc-shape-medium, 4px)}[dir=rtl] .mdc-data-table .mdc-data-table__header-cell:first-child,.mdc-data-table .mdc-data-table__header-cell:first-child[dir=rtl]{border-top-right-radius:4px;border-top-right-radius:var(--mdc-shape-medium, 4px);border-top-left-radius:0}.mdc-data-table .mdc-data-table__header-cell:last-child{border-top-right-radius:4px;border-top-right-radius:var(--mdc-shape-medium, 4px)}[dir=rtl] .mdc-data-table .mdc-data-table__header-cell:last-child,.mdc-data-table .mdc-data-table__header-cell:last-child[dir=rtl]{border-top-left-radius:4px;border-top-left-radius:var(--mdc-shape-medium, 4px);border-top-right-radius:0}.mdc-data-table__row{background-color:inherit}.mdc-data-table__header-cell{background-color:#fff;background-color:var(--mdc-theme-surface, #fff)}.mdc-data-table__row--selected{background-color:rgba(98, 0, 238, 0.04)}.mdc-data-table__pagination-rows-per-page-select:not(.mdc-select--disabled) .mdc-notched-outline__leading,.mdc-data-table__pagination-rows-per-page-select:not(.mdc-select--disabled) .mdc-notched-outline__notch,.mdc-data-table__pagination-rows-per-page-select:not(.mdc-select--disabled) .mdc-notched-outline__trailing{border-color:rgba(0, 0, 0, 0.12)}.mdc-data-table__cell,.mdc-data-table__header-cell{border-bottom-color:rgba(0,0,0,.12)}.mdc-data-table__pagination{border-top-color:rgba(0,0,0,.12)}.mdc-data-table__cell,.mdc-data-table__header-cell{border-bottom-width:1px;border-bottom-style:solid}.mdc-data-table__pagination{border-top-width:1px;border-top-style:solid}.mdc-data-table__row:last-child .mdc-data-table__cell{border-bottom:none}.mdc-data-table__row:not(.mdc-data-table__row--selected):hover{background-color:rgba(0, 0, 0, 0.04)}.mdc-data-table__header-cell{color:rgba(0, 0, 0, 0.87)}.mdc-data-table__pagination-total,.mdc-data-table__pagination-rows-per-page-label,.mdc-data-table__cell{color:rgba(0, 0, 0, 0.87)}.mdc-data-table__row{height:52px}.mdc-data-table__pagination{min-height:52px}.mdc-data-table__header-row{height:56px}.mdc-data-table__cell,.mdc-data-table__header-cell{padding:0 16px 0 16px}.mdc-data-table__header-cell--checkbox,.mdc-data-table__cell--checkbox{padding-left:4px;padding-right:0}[dir=rtl] .mdc-data-table__header-cell--checkbox,[dir=rtl] .mdc-data-table__cell--checkbox,.mdc-data-table__header-cell--checkbox[dir=rtl],.mdc-data-table__cell--checkbox[dir=rtl]{padding-left:0;padding-right:4px}.mdc-data-table__sort-icon-button{color:rgba(0, 0, 0, 0.6)}.mdc-data-table__sort-icon-button .mdc-icon-button__ripple::before,.mdc-data-table__sort-icon-button .mdc-icon-button__ripple::after{background-color:rgba(0, 0, 0, 0.6);background-color:var(--mdc-ripple-color, rgba(0, 0, 0, 0.6))}.mdc-data-table__sort-icon-button:hover .mdc-icon-button__ripple::before,.mdc-data-table__sort-icon-button.mdc-ripple-surface--hover .mdc-icon-button__ripple::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}.mdc-data-table__sort-icon-button.mdc-ripple-upgraded--background-focused .mdc-icon-button__ripple::before,.mdc-data-table__sort-icon-button:not(.mdc-ripple-upgraded):focus .mdc-icon-button__ripple::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-data-table__sort-icon-button:not(.mdc-ripple-upgraded) .mdc-icon-button__ripple::after{transition:opacity 150ms linear}.mdc-data-table__sort-icon-button:not(.mdc-ripple-upgraded):active .mdc-icon-button__ripple::after{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-data-table__sort-icon-button.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-data-table__header-cell--sorted .mdc-data-table__sort-icon-button{color:rgba(0, 0, 0, 0.87)}.mdc-data-table__header-cell--sorted .mdc-data-table__sort-icon-button .mdc-icon-button__ripple::before,.mdc-data-table__header-cell--sorted .mdc-data-table__sort-icon-button .mdc-icon-button__ripple::after{background-color:rgba(0, 0, 0, 0.87);background-color:var(--mdc-ripple-color, rgba(0, 0, 0, 0.87))}.mdc-data-table__header-cell--sorted .mdc-data-table__sort-icon-button:hover .mdc-icon-button__ripple::before,.mdc-data-table__header-cell--sorted .mdc-data-table__sort-icon-button.mdc-ripple-surface--hover .mdc-icon-button__ripple::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}.mdc-data-table__header-cell--sorted .mdc-data-table__sort-icon-button.mdc-ripple-upgraded--background-focused .mdc-icon-button__ripple::before,.mdc-data-table__header-cell--sorted .mdc-data-table__sort-icon-button:not(.mdc-ripple-upgraded):focus .mdc-icon-button__ripple::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-data-table__header-cell--sorted .mdc-data-table__sort-icon-button:not(.mdc-ripple-upgraded) .mdc-icon-button__ripple::after{transition:opacity 150ms linear}.mdc-data-table__header-cell--sorted .mdc-data-table__sort-icon-button:not(.mdc-ripple-upgraded):active .mdc-icon-button__ripple::after{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-data-table__header-cell--sorted .mdc-data-table__sort-icon-button.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-data-table__table-container{-webkit-overflow-scrolling:touch;overflow-x:auto;width:100%}.mdc-data-table__table{min-width:100%;border:0;white-space:nowrap;border-spacing:0;table-layout:fixed}.mdc-data-table__cell{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.875rem;font-size:var(--mdc-typography-body2-font-size, 0.875rem);line-height:1.25rem;line-height:var(--mdc-typography-body2-line-height, 1.25rem);font-weight:400;font-weight:var(--mdc-typography-body2-font-weight, 400);letter-spacing:0.0178571429em;letter-spacing:var(--mdc-typography-body2-letter-spacing, 0.0178571429em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-body2-text-transform, inherit);box-sizing:border-box;overflow:hidden;text-align:left;text-overflow:ellipsis}[dir=rtl] .mdc-data-table__cell,.mdc-data-table__cell[dir=rtl]{text-align:right}.mdc-data-table__cell--numeric{text-align:right}[dir=rtl] .mdc-data-table__cell--numeric,.mdc-data-table__cell--numeric[dir=rtl]{text-align:left}.mdc-data-table__cell--checkbox{width:1px}.mdc-data-table__header-cell{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-subtitle2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.875rem;font-size:var(--mdc-typography-subtitle2-font-size, 0.875rem);line-height:1.375rem;line-height:var(--mdc-typography-subtitle2-line-height, 1.375rem);font-weight:500;font-weight:var(--mdc-typography-subtitle2-font-weight, 500);letter-spacing:0.0071428571em;letter-spacing:var(--mdc-typography-subtitle2-letter-spacing, 0.0071428571em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-subtitle2-text-decoration, inherit);text-decoration:var(--mdc-typography-subtitle2-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-subtitle2-text-transform, inherit);box-sizing:border-box;text-overflow:ellipsis;overflow:hidden;outline:none;text-align:left}[dir=rtl] .mdc-data-table__header-cell,.mdc-data-table__header-cell[dir=rtl]{text-align:right}.mdc-data-table__header-cell--checkbox{width:1px}.mdc-data-table__header-cell--numeric{text-align:right}[dir=rtl] .mdc-data-table__header-cell--numeric,.mdc-data-table__header-cell--numeric[dir=rtl]{text-align:left}.mdc-data-table__sort-icon-button{width:28px;height:28px;padding:2px;-webkit-transform:rotate(0.0001deg);transform:rotate(0.0001deg);margin-left:4px;margin-right:0;transition:-webkit-transform 150ms 0ms cubic-bezier(0.4, 0, 0.2, 1);transition:transform 150ms 0ms cubic-bezier(0.4, 0, 0.2, 1);transition:transform 150ms 0ms cubic-bezier(0.4, 0, 0.2, 1), -webkit-transform 150ms 0ms cubic-bezier(0.4, 0, 0.2, 1);opacity:0}.mdc-data-table__sort-icon-button.mdc-icon-button--touch{margin-top:0;margin-bottom:0}.mdc-data-table__sort-icon-button.mdc-icon-button--touch .mdc-icon-button__touch{display:none}[dir=rtl] .mdc-data-table__sort-icon-button,.mdc-data-table__sort-icon-button[dir=rtl]{margin-left:0;margin-right:4px}.mdc-data-table__header-cell--numeric .mdc-data-table__sort-icon-button{margin-left:0;margin-right:4px}[dir=rtl] .mdc-data-table__header-cell--numeric .mdc-data-table__sort-icon-button,.mdc-data-table__header-cell--numeric .mdc-data-table__sort-icon-button[dir=rtl]{margin-left:4px;margin-right:0}.mdc-data-table__header-cell--sorted-descending .mdc-data-table__sort-icon-button{-webkit-transform:rotate(-180deg);transform:rotate(-180deg)}.mdc-data-table__sort-icon-button:focus,.mdc-data-table__header-cell:hover .mdc-data-table__sort-icon-button,.mdc-data-table__header-cell--sorted .mdc-data-table__sort-icon-button{opacity:1}.mdc-data-table__header-cell-wrapper{align-items:center;display:inline-flex;vertical-align:middle}.mdc-data-table__header-cell--with-sort{cursor:pointer}.mdc-data-table__sort-status-label{clip:rect(1px, 1px, 1px, 1px);height:1px;overflow:hidden;position:absolute;white-space:nowrap;width:1px}.mdc-data-table__progress-indicator{display:none;position:absolute;width:100%}.mdc-data-table--in-progress .mdc-data-table__progress-indicator{display:block}.mdc-data-table__scrim{background-color:#fff;background-color:var(--mdc-theme-surface, #fff);height:100%;opacity:.32;position:absolute;top:0;width:100%}.mdc-data-table--sticky-header .mdc-data-table__header-cell{position:-webkit-sticky;position:sticky;top:0;z-index:1}.mdc-data-table__pagination{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.875rem;font-size:var(--mdc-typography-body2-font-size, 0.875rem);line-height:1.25rem;line-height:var(--mdc-typography-body2-line-height, 1.25rem);font-weight:400;font-weight:var(--mdc-typography-body2-font-weight, 400);letter-spacing:0.0178571429em;letter-spacing:var(--mdc-typography-body2-letter-spacing, 0.0178571429em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-body2-text-transform, inherit);box-sizing:border-box;display:flex;justify-content:flex-end}.mdc-data-table__pagination-trailing{margin-left:4px;margin-right:0;align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-end}[dir=rtl] .mdc-data-table__pagination-trailing,.mdc-data-table__pagination-trailing[dir=rtl]{margin-left:0;margin-right:4px}.mdc-data-table__pagination-navigation{align-items:center;display:flex}.mdc-data-table__pagination-button{margin-left:0;margin-right:4px}[dir=rtl] .mdc-data-table__pagination-button .mdc-button__icon,.mdc-data-table__pagination-button .mdc-button__icon[dir=rtl]{-webkit-transform:rotate(180deg);transform:rotate(180deg)}[dir=rtl] .mdc-data-table__pagination-button,.mdc-data-table__pagination-button[dir=rtl]{margin-left:4px;margin-right:0}.mdc-data-table__pagination-total{margin-left:14px;margin-right:36px;white-space:nowrap}[dir=rtl] .mdc-data-table__pagination-total,.mdc-data-table__pagination-total[dir=rtl]{margin-left:36px;margin-right:14px}.mdc-data-table__pagination-rows-per-page{margin-left:0;margin-right:22px;align-items:center;display:inline-flex}[dir=rtl] .mdc-data-table__pagination-rows-per-page,.mdc-data-table__pagination-rows-per-page[dir=rtl]{margin-left:22px;margin-right:0}.mdc-data-table__pagination-rows-per-page-label{margin-left:0;margin-right:12px;white-space:nowrap}[dir=rtl] .mdc-data-table__pagination-rows-per-page-label,.mdc-data-table__pagination-rows-per-page-label[dir=rtl]{margin-left:12px;margin-right:0}.mdc-data-table__pagination-rows-per-page-select{min-width:80px;min-width:var(--mdc-menu-min-width, 80px);margin:8px 0}.mdc-data-table__pagination-rows-per-page-select .mdc-select__anchor{width:100%;min-width:80px}.mdc-data-table__pagination-rows-per-page-select .mdc-select__anchor{height:36px}.mdc-data-table__pagination-rows-per-page-select .mdc-select__anchor .mdc-floating-label--float-above{-webkit-transform:translateY(-27.25px) scale(1);transform:translateY(-27.25px) scale(1)}.mdc-data-table__pagination-rows-per-page-select .mdc-select__anchor .mdc-floating-label--float-above{font-size:.75rem}.mdc-data-table__pagination-rows-per-page-select .mdc-select__anchor.mdc-notched-outline--upgraded .mdc-floating-label--float-above,.mdc-data-table__pagination-rows-per-page-select .mdc-select__anchor .mdc-notched-outline--upgraded .mdc-floating-label--float-above{-webkit-transform:translateY(-24.75px) scale(0.75);transform:translateY(-24.75px) scale(0.75)}.mdc-data-table__pagination-rows-per-page-select .mdc-select__anchor.mdc-notched-outline--upgraded .mdc-floating-label--float-above,.mdc-data-table__pagination-rows-per-page-select .mdc-select__anchor .mdc-notched-outline--upgraded .mdc-floating-label--float-above{font-size:1rem}.mdc-data-table__pagination-rows-per-page-select .mdc-select__anchor .mdc-floating-label--shake{-webkit-animation:mdc-floating-label-shake-float-above-select-outlined-36px 250ms 1;animation:mdc-floating-label-shake-float-above-select-outlined-36px 250ms 1}@-webkit-keyframes mdc-floating-label-shake-float-above-select-outlined-36px{0%{-webkit-transform:translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75);transform:translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75)}33%{-webkit-animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);-webkit-transform:translateX(calc(4% - 0%)) translateY(-24.75px) scale(0.75);transform:translateX(calc(4% - 0%)) translateY(-24.75px) scale(0.75)}66%{-webkit-animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);-webkit-transform:translateX(calc(-4% - 0%)) translateY(-24.75px) scale(0.75);transform:translateX(calc(-4% - 0%)) translateY(-24.75px) scale(0.75)}100%{-webkit-transform:translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75);transform:translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75)}}@keyframes mdc-floating-label-shake-float-above-select-outlined-36px{0%{-webkit-transform:translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75);transform:translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75)}33%{-webkit-animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);-webkit-transform:translateX(calc(4% - 0%)) translateY(-24.75px) scale(0.75);transform:translateX(calc(4% - 0%)) translateY(-24.75px) scale(0.75)}66%{-webkit-animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);-webkit-transform:translateX(calc(-4% - 0%)) translateY(-24.75px) scale(0.75);transform:translateX(calc(-4% - 0%)) translateY(-24.75px) scale(0.75)}100%{-webkit-transform:translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75);transform:translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75)}}.mdc-data-table__pagination-rows-per-page-select .mdc-select__dropdown-icon{width:20px;height:20px}.mdc-data-table__pagination-rows-per-page-select.mdc-select--outlined .mdc-select__anchor :not(.mdc-notched-outline--notched) .mdc-notched-outline__notch{max-width:calc(100% - 56px)}.mdc-data-table__pagination-rows-per-page-select .mdc-deprecated-list-item{height:36px}.mdc-data-table__header-row-checkbox.mdc-checkbox--selected .mdc-checkbox__ripple::before,.mdc-data-table__header-row-checkbox.mdc-checkbox--selected .mdc-checkbox__ripple::after,.mdc-data-table__row-checkbox.mdc-checkbox--selected .mdc-checkbox__ripple::before,.mdc-data-table__row-checkbox.mdc-checkbox--selected .mdc-checkbox__ripple::after{background-color:#6200ee;background-color:var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee))}.mdc-data-table__header-row-checkbox.mdc-checkbox--selected:hover .mdc-checkbox__ripple::before,.mdc-data-table__header-row-checkbox.mdc-checkbox--selected.mdc-ripple-surface--hover .mdc-checkbox__ripple::before,.mdc-data-table__row-checkbox.mdc-checkbox--selected:hover .mdc-checkbox__ripple::before,.mdc-data-table__row-checkbox.mdc-checkbox--selected.mdc-ripple-surface--hover .mdc-checkbox__ripple::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}.mdc-data-table__header-row-checkbox.mdc-checkbox--selected.mdc-ripple-upgraded--background-focused .mdc-checkbox__ripple::before,.mdc-data-table__header-row-checkbox.mdc-checkbox--selected:not(.mdc-ripple-upgraded):focus .mdc-checkbox__ripple::before,.mdc-data-table__row-checkbox.mdc-checkbox--selected.mdc-ripple-upgraded--background-focused .mdc-checkbox__ripple::before,.mdc-data-table__row-checkbox.mdc-checkbox--selected:not(.mdc-ripple-upgraded):focus .mdc-checkbox__ripple::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-data-table__header-row-checkbox.mdc-checkbox--selected:not(.mdc-ripple-upgraded) .mdc-checkbox__ripple::after,.mdc-data-table__row-checkbox.mdc-checkbox--selected:not(.mdc-ripple-upgraded) .mdc-checkbox__ripple::after{transition:opacity 150ms linear}.mdc-data-table__header-row-checkbox.mdc-checkbox--selected:not(.mdc-ripple-upgraded):active .mdc-checkbox__ripple::after,.mdc-data-table__row-checkbox.mdc-checkbox--selected:not(.mdc-ripple-upgraded):active .mdc-checkbox__ripple::after{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-data-table__header-row-checkbox.mdc-checkbox--selected.mdc-ripple-upgraded,.mdc-data-table__row-checkbox.mdc-checkbox--selected.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-data-table__header-row-checkbox.mdc-ripple-upgraded--background-focused.mdc-checkbox--selected .mdc-checkbox__ripple::before,.mdc-data-table__header-row-checkbox.mdc-ripple-upgraded--background-focused.mdc-checkbox--selected .mdc-checkbox__ripple::after,.mdc-data-table__row-checkbox.mdc-ripple-upgraded--background-focused.mdc-checkbox--selected .mdc-checkbox__ripple::before,.mdc-data-table__row-checkbox.mdc-ripple-upgraded--background-focused.mdc-checkbox--selected .mdc-checkbox__ripple::after{background-color:#6200ee;background-color:var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee))}.mdc-data-table__header-row-checkbox .mdc-checkbox__native-control:enabled:not(:checked):not(:indeterminate):not([data-indeterminate=true])~.mdc-checkbox__background,.mdc-data-table__row-checkbox .mdc-checkbox__native-control:enabled:not(:checked):not(:indeterminate):not([data-indeterminate=true])~.mdc-checkbox__background{border-color:rgba(0, 0, 0, 0.54);border-color:var(--mdc-checkbox-unchecked-color, rgba(0, 0, 0, 0.54));background-color:transparent}.mdc-data-table__header-row-checkbox .mdc-checkbox__native-control:enabled:checked~.mdc-checkbox__background,.mdc-data-table__header-row-checkbox .mdc-checkbox__native-control:enabled:indeterminate~.mdc-checkbox__background,.mdc-data-table__header-row-checkbox .mdc-checkbox__native-control[data-indeterminate=true]:enabled~.mdc-checkbox__background,.mdc-data-table__row-checkbox .mdc-checkbox__native-control:enabled:checked~.mdc-checkbox__background,.mdc-data-table__row-checkbox .mdc-checkbox__native-control:enabled:indeterminate~.mdc-checkbox__background,.mdc-data-table__row-checkbox .mdc-checkbox__native-control[data-indeterminate=true]:enabled~.mdc-checkbox__background{border-color:#6200ee;border-color:var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #6200ee));background-color:#6200ee;background-color:var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #6200ee))}@-webkit-keyframes mdc-checkbox-fade-in-background-8A000000FF6200EE00000000FF6200EE{0%{border-color:rgba(0, 0, 0, 0.54);border-color:var(--mdc-checkbox-unchecked-color, rgba(0, 0, 0, 0.54));background-color:transparent}50%{border-color:#6200ee;border-color:var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #6200ee));background-color:#6200ee;background-color:var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #6200ee))}}@keyframes mdc-checkbox-fade-in-background-8A000000FF6200EE00000000FF6200EE{0%{border-color:rgba(0, 0, 0, 0.54);border-color:var(--mdc-checkbox-unchecked-color, rgba(0, 0, 0, 0.54));background-color:transparent}50%{border-color:#6200ee;border-color:var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #6200ee));background-color:#6200ee;background-color:var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #6200ee))}}@-webkit-keyframes mdc-checkbox-fade-out-background-8A000000FF6200EE00000000FF6200EE{0%,80%{border-color:#6200ee;border-color:var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #6200ee));background-color:#6200ee;background-color:var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #6200ee))}100%{border-color:rgba(0, 0, 0, 0.54);border-color:var(--mdc-checkbox-unchecked-color, rgba(0, 0, 0, 0.54));background-color:transparent}}@keyframes mdc-checkbox-fade-out-background-8A000000FF6200EE00000000FF6200EE{0%,80%{border-color:#6200ee;border-color:var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #6200ee));background-color:#6200ee;background-color:var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #6200ee))}100%{border-color:rgba(0, 0, 0, 0.54);border-color:var(--mdc-checkbox-unchecked-color, rgba(0, 0, 0, 0.54));background-color:transparent}}.mdc-data-table__header-row-checkbox.mdc-checkbox--anim-unchecked-checked .mdc-checkbox__native-control:enabled~.mdc-checkbox__background,.mdc-data-table__header-row-checkbox.mdc-checkbox--anim-unchecked-indeterminate .mdc-checkbox__native-control:enabled~.mdc-checkbox__background,.mdc-data-table__row-checkbox.mdc-checkbox--anim-unchecked-checked .mdc-checkbox__native-control:enabled~.mdc-checkbox__background,.mdc-data-table__row-checkbox.mdc-checkbox--anim-unchecked-indeterminate .mdc-checkbox__native-control:enabled~.mdc-checkbox__background{-webkit-animation-name:mdc-checkbox-fade-in-background-8A000000FF6200EE00000000FF6200EE;animation-name:mdc-checkbox-fade-in-background-8A000000FF6200EE00000000FF6200EE}.mdc-data-table__header-row-checkbox.mdc-checkbox--anim-checked-unchecked .mdc-checkbox__native-control:enabled~.mdc-checkbox__background,.mdc-data-table__header-row-checkbox.mdc-checkbox--anim-indeterminate-unchecked .mdc-checkbox__native-control:enabled~.mdc-checkbox__background,.mdc-data-table__row-checkbox.mdc-checkbox--anim-checked-unchecked .mdc-checkbox__native-control:enabled~.mdc-checkbox__background,.mdc-data-table__row-checkbox.mdc-checkbox--anim-indeterminate-unchecked .mdc-checkbox__native-control:enabled~.mdc-checkbox__background{-webkit-animation-name:mdc-checkbox-fade-out-background-8A000000FF6200EE00000000FF6200EE;animation-name:mdc-checkbox-fade-out-background-8A000000FF6200EE00000000FF6200EE}.mdc-dialog,.mdc-dialog__scrim{position:fixed;top:0;left:0;align-items:center;justify-content:center;box-sizing:border-box;width:100%;height:100%}.mdc-dialog{display:none;z-index:7;z-index:var(--mdc-dialog-z-index, 7)}.mdc-dialog .mdc-dialog__surface{background-color:#fff;background-color:var(--mdc-theme-surface, #fff)}.mdc-dialog .mdc-dialog__scrim{background-color:rgba(0,0,0,.32)}.mdc-dialog .mdc-dialog__surface-scrim{background-color:rgba(0,0,0,.32)}.mdc-dialog .mdc-dialog__title{color:rgba(0,0,0,.87)}.mdc-dialog .mdc-dialog__content{color:rgba(0,0,0,.6)}.mdc-dialog .mdc-dialog__close{color:#000;color:var(--mdc-theme-on-surface, #000)}.mdc-dialog .mdc-dialog__close .mdc-icon-button__ripple::before,.mdc-dialog .mdc-dialog__close .mdc-icon-button__ripple::after{background-color:#000;background-color:var(--mdc-ripple-color, var(--mdc-theme-on-surface, #000))}.mdc-dialog .mdc-dialog__close:hover .mdc-icon-button__ripple::before,.mdc-dialog .mdc-dialog__close.mdc-ripple-surface--hover .mdc-icon-button__ripple::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}.mdc-dialog .mdc-dialog__close.mdc-ripple-upgraded--background-focused .mdc-icon-button__ripple::before,.mdc-dialog .mdc-dialog__close:not(.mdc-ripple-upgraded):focus .mdc-icon-button__ripple::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-dialog .mdc-dialog__close:not(.mdc-ripple-upgraded) .mdc-icon-button__ripple::after{transition:opacity 150ms linear}.mdc-dialog .mdc-dialog__close:not(.mdc-ripple-upgraded):active .mdc-icon-button__ripple::after{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-dialog .mdc-dialog__close.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-dialog.mdc-dialog--scrollable .mdc-dialog__title,.mdc-dialog.mdc-dialog--scrollable .mdc-dialog__actions,.mdc-dialog.mdc-dialog--scrollable.mdc-dialog-scroll-divider-footer .mdc-dialog__actions{border-color:rgba(0,0,0,.12)}.mdc-dialog.mdc-dialog--scrollable .mdc-dialog__title{border-bottom:1px solid rgba(0,0,0,.12);margin-bottom:0}.mdc-dialog.mdc-dialog-scroll-divider-header.mdc-dialog--fullscreen .mdc-dialog__header{box-shadow:0px 3px 1px -2px rgba(0, 0, 0, 0.2),0px 2px 2px 0px rgba(0, 0, 0, 0.14),0px 1px 5px 0px rgba(0,0,0,.12)}.mdc-dialog .mdc-dialog__content{padding:20px 24px 20px 24px}.mdc-dialog .mdc-dialog__surface{min-width:280px}@media(max-width: 592px){.mdc-dialog .mdc-dialog__surface{max-width:calc(100vw - 32px)}}@media(min-width: 592px){.mdc-dialog .mdc-dialog__surface{max-width:560px}}.mdc-dialog .mdc-dialog__surface{max-height:calc(100% - 32px)}.mdc-dialog .mdc-dialog__surface{border-radius:4px;border-radius:var(--mdc-shape-medium, 4px)}@media(max-width: 960px)and (max-height: 1440px){.mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface{max-height:560px;max-width:560px}.mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface .mdc-dialog__close{right:-12px}}@media(max-width: 720px)and (max-height: 1023px)and (max-width: 672px){.mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface{max-width:calc(100vw - 112px)}}@media(max-width: 720px)and (max-height: 1023px)and (min-width: 672px){.mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface{max-width:560px}}@media(max-width: 720px)and (max-height: 1023px)and (max-height: 720px){.mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface{max-height:calc(100vh - 160px)}}@media(max-width: 720px)and (max-height: 1023px)and (min-height: 720px){.mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface{max-height:560px}}@media(max-width: 720px)and (max-height: 1023px){.mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface .mdc-dialog__close{right:-12px}}@media(max-width: 720px)and (max-height: 400px){.mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface{height:100%;max-height:100vh;max-width:100vw;width:100%;border-radius:0}.mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface .mdc-dialog__close{order:-1;left:-12px}.mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface .mdc-dialog__header{padding:0 16px 9px;justify-content:flex-start}.mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface .mdc-dialog__title{margin-left:calc(16px - 2 * 12px)}}@media(max-width: 600px)and (max-height: 960px){.mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface{height:100%;max-height:100vh;max-width:100vw;width:100vw;border-radius:0}.mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface .mdc-dialog__close{order:-1;left:-12px}.mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface .mdc-dialog__header{padding:0 16px 9px;justify-content:flex-start}.mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface .mdc-dialog__title{margin-left:calc(16px - 2 * 12px)}}@media(min-width: 960px)and (min-height: 1440px){.mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface{max-width:calc(100vw - 400px)}.mdc-dialog.mdc-dialog--fullscreen .mdc-dialog__surface .mdc-dialog__close{right:-12px}}.mdc-dialog.mdc-dialog__scrim--hidden .mdc-dialog__scrim{opacity:0}.mdc-dialog__scrim{opacity:0;z-index:-1}.mdc-dialog__container{display:flex;flex-direction:row;align-items:center;justify-content:space-around;box-sizing:border-box;height:100%;-webkit-transform:scale(0.8);transform:scale(0.8);opacity:0;pointer-events:none}.mdc-dialog__surface{position:relative;box-shadow:0px 11px 15px -7px rgba(0, 0, 0, 0.2),0px 24px 38px 3px rgba(0, 0, 0, 0.14),0px 9px 46px 8px rgba(0,0,0,.12);display:flex;flex-direction:column;flex-grow:0;flex-shrink:0;box-sizing:border-box;max-width:100%;max-height:100%;pointer-events:auto;overflow-y:auto}.mdc-dialog__surface .mdc-elevation-overlay{width:100%;height:100%;top:0;left:0}[dir=rtl] .mdc-dialog__surface,.mdc-dialog__surface[dir=rtl]{text-align:right}@media screen and (forced-colors: active),(-ms-high-contrast: active){.mdc-dialog__surface{outline:2px solid windowText}}.mdc-dialog__surface::before{position:absolute;box-sizing:border-box;width:100%;height:100%;top:0;left:0;border:2px solid transparent;border-radius:inherit;content:"";pointer-events:none}@media screen and (-ms-high-contrast: active),screen and (-ms-high-contrast: none){.mdc-dialog__surface::before{content:none}}.mdc-dialog__title{display:block;margin-top:0;line-height:normal;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-headline6-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:1.25rem;font-size:var(--mdc-typography-headline6-font-size, 1.25rem);line-height:2rem;line-height:var(--mdc-typography-headline6-line-height, 2rem);font-weight:500;font-weight:var(--mdc-typography-headline6-font-weight, 500);letter-spacing:0.0125em;letter-spacing:var(--mdc-typography-headline6-letter-spacing, 0.0125em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-headline6-text-decoration, inherit);text-decoration:var(--mdc-typography-headline6-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-headline6-text-transform, inherit);position:relative;flex-shrink:0;box-sizing:border-box;margin:0 0 1px;padding:0 24px 9px}.mdc-dialog__title::before{display:inline-block;width:0;height:40px;content:"";vertical-align:0}[dir=rtl] .mdc-dialog__title,.mdc-dialog__title[dir=rtl]{text-align:right}.mdc-dialog--scrollable .mdc-dialog__title{margin-bottom:1px;padding-bottom:15px}.mdc-dialog--fullscreen .mdc-dialog__header{align-items:baseline;border-bottom:1px solid transparent;display:inline-flex;justify-content:space-between;padding:0 24px 9px;z-index:1}.mdc-dialog--fullscreen .mdc-dialog__header .mdc-dialog__close{right:-12px}.mdc-dialog--fullscreen .mdc-dialog__title{margin-bottom:0;padding:0;border-bottom:0}.mdc-dialog--fullscreen.mdc-dialog--scrollable .mdc-dialog__title{border-bottom:0;margin-bottom:0}.mdc-dialog--fullscreen .mdc-dialog__close{top:5px}.mdc-dialog--fullscreen.mdc-dialog--scrollable .mdc-dialog__actions{border-top:1px solid transparent}.mdc-dialog__content{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-body1-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:1rem;font-size:var(--mdc-typography-body1-font-size, 1rem);line-height:1.5rem;line-height:var(--mdc-typography-body1-line-height, 1.5rem);font-weight:400;font-weight:var(--mdc-typography-body1-font-weight, 400);letter-spacing:0.03125em;letter-spacing:var(--mdc-typography-body1-letter-spacing, 0.03125em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-body1-text-decoration, inherit);text-decoration:var(--mdc-typography-body1-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-body1-text-transform, inherit);flex-grow:1;box-sizing:border-box;margin:0;overflow:auto;-webkit-overflow-scrolling:touch}.mdc-dialog__content>:first-child{margin-top:0}.mdc-dialog__content>:last-child{margin-bottom:0}.mdc-dialog__title+.mdc-dialog__content,.mdc-dialog__header+.mdc-dialog__content{padding-top:0}.mdc-dialog--scrollable .mdc-dialog__title+.mdc-dialog__content{padding-top:8px;padding-bottom:8px}.mdc-dialog__content .mdc-deprecated-list:first-child:last-child{padding:6px 0 0}.mdc-dialog--scrollable .mdc-dialog__content .mdc-deprecated-list:first-child:last-child{padding:0}.mdc-dialog__actions{display:flex;position:relative;flex-shrink:0;flex-wrap:wrap;align-items:center;justify-content:flex-end;box-sizing:border-box;min-height:52px;margin:0;padding:8px;border-top:1px solid transparent}.mdc-dialog--stacked .mdc-dialog__actions{flex-direction:column;align-items:flex-end}.mdc-dialog__button{margin-left:8px;margin-right:0;max-width:100%;text-align:right}[dir=rtl] .mdc-dialog__button,.mdc-dialog__button[dir=rtl]{margin-left:0;margin-right:8px}.mdc-dialog__button:first-child{margin-left:0;margin-right:0}[dir=rtl] .mdc-dialog__button:first-child,.mdc-dialog__button:first-child[dir=rtl]{margin-left:0;margin-right:0}[dir=rtl] .mdc-dialog__button,.mdc-dialog__button[dir=rtl]{text-align:left}.mdc-dialog--stacked .mdc-dialog__button:not(:first-child){margin-top:12px}.mdc-dialog--open,.mdc-dialog--opening,.mdc-dialog--closing{display:flex}.mdc-dialog--opening .mdc-dialog__scrim{transition:opacity 150ms linear}.mdc-dialog--opening .mdc-dialog__container{transition:opacity 75ms linear,-webkit-transform 150ms 0ms cubic-bezier(0, 0, 0.2, 1);transition:opacity 75ms linear,transform 150ms 0ms cubic-bezier(0, 0, 0.2, 1);transition:opacity 75ms linear,transform 150ms 0ms cubic-bezier(0, 0, 0.2, 1),-webkit-transform 150ms 0ms cubic-bezier(0, 0, 0.2, 1)}.mdc-dialog--closing .mdc-dialog__scrim,.mdc-dialog--closing .mdc-dialog__container{transition:opacity 75ms linear}.mdc-dialog--closing .mdc-dialog__container{-webkit-transform:none;transform:none}.mdc-dialog--open .mdc-dialog__scrim{opacity:1}.mdc-dialog--open .mdc-dialog__container{-webkit-transform:none;transform:none;opacity:1}.mdc-dialog--open.mdc-dialog__surface-scrim--shown .mdc-dialog__surface-scrim{opacity:1;z-index:1}.mdc-dialog--open.mdc-dialog__surface-scrim--hiding .mdc-dialog__surface-scrim{transition:opacity 75ms linear}.mdc-dialog--open.mdc-dialog__surface-scrim--showing .mdc-dialog__surface-scrim{transition:opacity 150ms linear}.mdc-dialog__surface-scrim{display:none;opacity:0;position:absolute;width:100%;height:100%}.mdc-dialog__surface-scrim--shown .mdc-dialog__surface-scrim,.mdc-dialog__surface-scrim--showing .mdc-dialog__surface-scrim,.mdc-dialog__surface-scrim--hiding .mdc-dialog__surface-scrim{display:block}.mdc-dialog-scroll-lock{overflow:hidden}.mdc-drawer{border-color:rgba(0, 0, 0, 0.12);background-color:#fff;background-color:var(--mdc-theme-surface, #fff);border-top-left-radius:0;border-top-right-radius:0;border-top-right-radius:var(--mdc-shape-large, 0);border-bottom-right-radius:0;border-bottom-right-radius:var(--mdc-shape-large, 0);border-bottom-left-radius:0;z-index:6;width:256px;display:flex;flex-direction:column;flex-shrink:0;box-sizing:border-box;height:100%;border-right-width:1px;border-right-style:solid;overflow:hidden;transition-property:-webkit-transform;transition-property:transform;transition-property:transform, -webkit-transform;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1)}.mdc-drawer .mdc-drawer__title{color:rgba(0, 0, 0, 0.87)}.mdc-drawer .mdc-deprecated-list-group__subheader{color:rgba(0, 0, 0, 0.6)}.mdc-drawer .mdc-drawer__subtitle{color:rgba(0, 0, 0, 0.6)}.mdc-drawer .mdc-deprecated-list-item__graphic{color:rgba(0, 0, 0, 0.6)}.mdc-drawer .mdc-deprecated-list-item{color:rgba(0, 0, 0, 0.87)}.mdc-drawer .mdc-deprecated-list-item--activated .mdc-deprecated-list-item__graphic{color:#6200ee}.mdc-drawer .mdc-deprecated-list-item--activated{color:rgba(98, 0, 238, 0.87)}[dir=rtl] .mdc-drawer,.mdc-drawer[dir=rtl]{border-top-left-radius:0;border-top-left-radius:var(--mdc-shape-large, 0);border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:0;border-bottom-left-radius:var(--mdc-shape-large, 0)}.mdc-drawer .mdc-deprecated-list-item{border-radius:4px;border-radius:var(--mdc-shape-small, 4px)}.mdc-drawer.mdc-drawer--open:not(.mdc-drawer--closing)+.mdc-drawer-app-content{margin-left:256px;margin-right:0}[dir=rtl] .mdc-drawer.mdc-drawer--open:not(.mdc-drawer--closing)+.mdc-drawer-app-content,.mdc-drawer.mdc-drawer--open:not(.mdc-drawer--closing)+.mdc-drawer-app-content[dir=rtl]{margin-left:0;margin-right:256px}[dir=rtl] .mdc-drawer,.mdc-drawer[dir=rtl]{border-right-width:0;border-left-width:1px;border-right-style:none;border-left-style:solid}.mdc-drawer .mdc-deprecated-list-item{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-subtitle2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.875rem;font-size:var(--mdc-typography-subtitle2-font-size, 0.875rem);line-height:1.375rem;line-height:var(--mdc-typography-subtitle2-line-height, 1.375rem);font-weight:500;font-weight:var(--mdc-typography-subtitle2-font-weight, 500);letter-spacing:0.0071428571em;letter-spacing:var(--mdc-typography-subtitle2-letter-spacing, 0.0071428571em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-subtitle2-text-decoration, inherit);text-decoration:var(--mdc-typography-subtitle2-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-subtitle2-text-transform, inherit);height:calc(48px - 2 * 4px);margin:8px 8px;padding:0 8px}.mdc-drawer .mdc-deprecated-list-item:nth-child(1){margin-top:2px}.mdc-drawer .mdc-deprecated-list-item:nth-last-child(1){margin-bottom:0}.mdc-drawer .mdc-deprecated-list-group__subheader{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.875rem;font-size:var(--mdc-typography-body2-font-size, 0.875rem);line-height:1.25rem;line-height:var(--mdc-typography-body2-line-height, 1.25rem);font-weight:400;font-weight:var(--mdc-typography-body2-font-weight, 400);letter-spacing:0.0178571429em;letter-spacing:var(--mdc-typography-body2-letter-spacing, 0.0178571429em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-body2-text-transform, inherit);display:block;margin-top:0;line-height:normal;margin:0;padding:0 16px}.mdc-drawer .mdc-deprecated-list-group__subheader::before{display:inline-block;width:0;height:24px;content:"";vertical-align:0}.mdc-drawer .mdc-deprecated-list-divider{margin:3px 0 4px}.mdc-drawer .mdc-deprecated-list-item__text,.mdc-drawer .mdc-deprecated-list-item__graphic{pointer-events:none}.mdc-drawer--animate{-webkit-transform:translateX(-100%);transform:translateX(-100%)}[dir=rtl] .mdc-drawer--animate,.mdc-drawer--animate[dir=rtl]{-webkit-transform:translateX(100%);transform:translateX(100%)}.mdc-drawer--opening{-webkit-transform:translateX(0);transform:translateX(0);transition-duration:250ms}[dir=rtl] .mdc-drawer--opening,.mdc-drawer--opening[dir=rtl]{-webkit-transform:translateX(0);transform:translateX(0)}.mdc-drawer--closing{-webkit-transform:translateX(-100%);transform:translateX(-100%);transition-duration:200ms}[dir=rtl] .mdc-drawer--closing,.mdc-drawer--closing[dir=rtl]{-webkit-transform:translateX(100%);transform:translateX(100%)}.mdc-drawer__header{flex-shrink:0;box-sizing:border-box;min-height:64px;padding:0 16px 4px}.mdc-drawer__title{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-headline6-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:1.25rem;font-size:var(--mdc-typography-headline6-font-size, 1.25rem);line-height:2rem;line-height:var(--mdc-typography-headline6-line-height, 2rem);font-weight:500;font-weight:var(--mdc-typography-headline6-font-weight, 500);letter-spacing:0.0125em;letter-spacing:var(--mdc-typography-headline6-letter-spacing, 0.0125em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-headline6-text-decoration, inherit);text-decoration:var(--mdc-typography-headline6-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-headline6-text-transform, inherit);display:block;margin-top:0;line-height:normal;margin-bottom:-20px}.mdc-drawer__title::before{display:inline-block;width:0;height:36px;content:"";vertical-align:0}.mdc-drawer__title::after{display:inline-block;width:0;height:20px;content:"";vertical-align:-20px}.mdc-drawer__subtitle{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.875rem;font-size:var(--mdc-typography-body2-font-size, 0.875rem);line-height:1.25rem;line-height:var(--mdc-typography-body2-line-height, 1.25rem);font-weight:400;font-weight:var(--mdc-typography-body2-font-weight, 400);letter-spacing:0.0178571429em;letter-spacing:var(--mdc-typography-body2-letter-spacing, 0.0178571429em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-body2-text-transform, inherit);display:block;margin-top:0;line-height:normal;margin-bottom:0}.mdc-drawer__subtitle::before{display:inline-block;width:0;height:20px;content:"";vertical-align:0}.mdc-drawer__content{height:100%;overflow-y:auto;-webkit-overflow-scrolling:touch}.mdc-drawer--dismissible{left:0;right:initial;display:none;position:absolute}[dir=rtl] .mdc-drawer--dismissible,.mdc-drawer--dismissible[dir=rtl]{left:initial;right:0}.mdc-drawer--dismissible.mdc-drawer--open{display:flex}.mdc-drawer-app-content{margin-left:0;margin-right:0;position:relative}[dir=rtl] .mdc-drawer-app-content,.mdc-drawer-app-content[dir=rtl]{margin-left:0;margin-right:0}.mdc-drawer--modal{box-shadow:0px 8px 10px -5px rgba(0, 0, 0, 0.2),0px 16px 24px 2px rgba(0, 0, 0, 0.14),0px 6px 30px 5px rgba(0,0,0,.12);left:0;right:initial;display:none;position:fixed}.mdc-drawer--modal+.mdc-drawer-scrim{background-color:rgba(0, 0, 0, 0.32)}[dir=rtl] .mdc-drawer--modal,.mdc-drawer--modal[dir=rtl]{left:initial;right:0}.mdc-drawer--modal.mdc-drawer--open{display:flex}.mdc-drawer-scrim{display:none;position:fixed;top:0;left:0;width:100%;height:100%;z-index:5;transition-property:opacity;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1)}.mdc-drawer--open+.mdc-drawer-scrim{display:block}.mdc-drawer--animate+.mdc-drawer-scrim{opacity:0}.mdc-drawer--opening+.mdc-drawer-scrim{transition-duration:250ms;opacity:1}.mdc-drawer--closing+.mdc-drawer-scrim{transition-duration:200ms;opacity:0}.mdc-elevation--z0{box-shadow:0px 0px 0px 0px rgba(0, 0, 0, 0.2),0px 0px 0px 0px rgba(0, 0, 0, 0.14),0px 0px 0px 0px rgba(0,0,0,.12)}.mdc-elevation--z1{box-shadow:0px 2px 1px -1px rgba(0, 0, 0, 0.2),0px 1px 1px 0px rgba(0, 0, 0, 0.14),0px 1px 3px 0px rgba(0,0,0,.12)}.mdc-elevation--z2{box-shadow:0px 3px 1px -2px rgba(0, 0, 0, 0.2),0px 2px 2px 0px rgba(0, 0, 0, 0.14),0px 1px 5px 0px rgba(0,0,0,.12)}.mdc-elevation--z3{box-shadow:0px 3px 3px -2px rgba(0, 0, 0, 0.2),0px 3px 4px 0px rgba(0, 0, 0, 0.14),0px 1px 8px 0px rgba(0,0,0,.12)}.mdc-elevation--z4{box-shadow:0px 2px 4px -1px rgba(0, 0, 0, 0.2),0px 4px 5px 0px rgba(0, 0, 0, 0.14),0px 1px 10px 0px rgba(0,0,0,.12)}.mdc-elevation--z5{box-shadow:0px 3px 5px -1px rgba(0, 0, 0, 0.2),0px 5px 8px 0px rgba(0, 0, 0, 0.14),0px 1px 14px 0px rgba(0,0,0,.12)}.mdc-elevation--z6{box-shadow:0px 3px 5px -1px rgba(0, 0, 0, 0.2),0px 6px 10px 0px rgba(0, 0, 0, 0.14),0px 1px 18px 0px rgba(0,0,0,.12)}.mdc-elevation--z7{box-shadow:0px 4px 5px -2px rgba(0, 0, 0, 0.2),0px 7px 10px 1px rgba(0, 0, 0, 0.14),0px 2px 16px 1px rgba(0,0,0,.12)}.mdc-elevation--z8{box-shadow:0px 5px 5px -3px rgba(0, 0, 0, 0.2),0px 8px 10px 1px rgba(0, 0, 0, 0.14),0px 3px 14px 2px rgba(0,0,0,.12)}.mdc-elevation--z9{box-shadow:0px 5px 6px -3px rgba(0, 0, 0, 0.2),0px 9px 12px 1px rgba(0, 0, 0, 0.14),0px 3px 16px 2px rgba(0,0,0,.12)}.mdc-elevation--z10{box-shadow:0px 6px 6px -3px rgba(0, 0, 0, 0.2),0px 10px 14px 1px rgba(0, 0, 0, 0.14),0px 4px 18px 3px rgba(0,0,0,.12)}.mdc-elevation--z11{box-shadow:0px 6px 7px -4px rgba(0, 0, 0, 0.2),0px 11px 15px 1px rgba(0, 0, 0, 0.14),0px 4px 20px 3px rgba(0,0,0,.12)}.mdc-elevation--z12{box-shadow:0px 7px 8px -4px rgba(0, 0, 0, 0.2),0px 12px 17px 2px rgba(0, 0, 0, 0.14),0px 5px 22px 4px rgba(0,0,0,.12)}.mdc-elevation--z13{box-shadow:0px 7px 8px -4px rgba(0, 0, 0, 0.2),0px 13px 19px 2px rgba(0, 0, 0, 0.14),0px 5px 24px 4px rgba(0,0,0,.12)}.mdc-elevation--z14{box-shadow:0px 7px 9px -4px rgba(0, 0, 0, 0.2),0px 14px 21px 2px rgba(0, 0, 0, 0.14),0px 5px 26px 4px rgba(0,0,0,.12)}.mdc-elevation--z15{box-shadow:0px 8px 9px -5px rgba(0, 0, 0, 0.2),0px 15px 22px 2px rgba(0, 0, 0, 0.14),0px 6px 28px 5px rgba(0,0,0,.12)}.mdc-elevation--z16{box-shadow:0px 8px 10px -5px rgba(0, 0, 0, 0.2),0px 16px 24px 2px rgba(0, 0, 0, 0.14),0px 6px 30px 5px rgba(0,0,0,.12)}.mdc-elevation--z17{box-shadow:0px 8px 11px -5px rgba(0, 0, 0, 0.2),0px 17px 26px 2px rgba(0, 0, 0, 0.14),0px 6px 32px 5px rgba(0,0,0,.12)}.mdc-elevation--z18{box-shadow:0px 9px 11px -5px rgba(0, 0, 0, 0.2),0px 18px 28px 2px rgba(0, 0, 0, 0.14),0px 7px 34px 6px rgba(0,0,0,.12)}.mdc-elevation--z19{box-shadow:0px 9px 12px -6px rgba(0, 0, 0, 0.2),0px 19px 29px 2px rgba(0, 0, 0, 0.14),0px 7px 36px 6px rgba(0,0,0,.12)}.mdc-elevation--z20{box-shadow:0px 10px 13px -6px rgba(0, 0, 0, 0.2),0px 20px 31px 3px rgba(0, 0, 0, 0.14),0px 8px 38px 7px rgba(0,0,0,.12)}.mdc-elevation--z21{box-shadow:0px 10px 13px -6px rgba(0, 0, 0, 0.2),0px 21px 33px 3px rgba(0, 0, 0, 0.14),0px 8px 40px 7px rgba(0,0,0,.12)}.mdc-elevation--z22{box-shadow:0px 10px 14px -6px rgba(0, 0, 0, 0.2),0px 22px 35px 3px rgba(0, 0, 0, 0.14),0px 8px 42px 7px rgba(0,0,0,.12)}.mdc-elevation--z23{box-shadow:0px 11px 14px -7px rgba(0, 0, 0, 0.2),0px 23px 36px 3px rgba(0, 0, 0, 0.14),0px 9px 44px 8px rgba(0,0,0,.12)}.mdc-elevation--z24{box-shadow:0px 11px 15px -7px rgba(0, 0, 0, 0.2),0px 24px 38px 3px rgba(0, 0, 0, 0.14),0px 9px 46px 8px rgba(0,0,0,.12)}.mdc-elevation-transition{transition:box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1);will-change:box-shadow}.mdc-fab{position:relative;display:inline-flex;position:relative;align-items:center;justify-content:center;box-sizing:border-box;width:56px;height:56px;padding:0;border:none;fill:currentColor;text-decoration:none;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-moz-appearance:none;-webkit-appearance:none;overflow:visible;transition:box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1),opacity 15ms linear 30ms,-webkit-transform 270ms 0ms cubic-bezier(0, 0, 0.2, 1);transition:box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1),opacity 15ms linear 30ms,transform 270ms 0ms cubic-bezier(0, 0, 0.2, 1);transition:box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1),opacity 15ms linear 30ms,transform 270ms 0ms cubic-bezier(0, 0, 0.2, 1),-webkit-transform 270ms 0ms cubic-bezier(0, 0, 0.2, 1)}.mdc-fab .mdc-elevation-overlay{width:100%;height:100%;top:0;left:0}.mdc-fab::-moz-focus-inner{padding:0;border:0}.mdc-fab:hover{box-shadow:0px 5px 5px -3px rgba(0, 0, 0, 0.2),0px 8px 10px 1px rgba(0, 0, 0, 0.14),0px 3px 14px 2px rgba(0,0,0,.12)}.mdc-fab.mdc-ripple-upgraded--background-focused,.mdc-fab:not(.mdc-ripple-upgraded):focus{box-shadow:0px 5px 5px -3px rgba(0, 0, 0, 0.2),0px 8px 10px 1px rgba(0, 0, 0, 0.14),0px 3px 14px 2px rgba(0,0,0,.12)}.mdc-fab:active,.mdc-fab:focus:active{box-shadow:0px 7px 8px -4px rgba(0, 0, 0, 0.2),0px 12px 17px 2px rgba(0, 0, 0, 0.14),0px 5px 22px 4px rgba(0,0,0,.12)}.mdc-fab:active,.mdc-fab:focus{outline:none}.mdc-fab:hover{cursor:pointer}.mdc-fab>svg{width:100%}.mdc-fab--mini{width:40px;height:40px}.mdc-fab--extended{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-button-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.875rem;font-size:var(--mdc-typography-button-font-size, 0.875rem);line-height:2.25rem;line-height:var(--mdc-typography-button-line-height, 2.25rem);font-weight:500;font-weight:var(--mdc-typography-button-font-weight, 500);letter-spacing:0.0892857143em;letter-spacing:var(--mdc-typography-button-letter-spacing, 0.0892857143em);text-decoration:none;-webkit-text-decoration:var(--mdc-typography-button-text-decoration, none);text-decoration:var(--mdc-typography-button-text-decoration, none);text-transform:uppercase;text-transform:var(--mdc-typography-button-text-transform, uppercase);border-radius:24px;padding-left:20px;padding-right:20px;width:auto;max-width:100%;height:48px;line-height:normal}.mdc-fab--extended .mdc-fab__ripple{border-radius:24px}.mdc-fab--extended .mdc-fab__icon{margin-left:calc(12px - 20px);margin-right:12px}[dir=rtl] .mdc-fab--extended .mdc-fab__icon,.mdc-fab--extended .mdc-fab__icon[dir=rtl]{margin-left:12px;margin-right:calc(12px - 20px)}.mdc-fab--extended .mdc-fab__label+.mdc-fab__icon{margin-left:12px;margin-right:calc(12px - 20px)}[dir=rtl] .mdc-fab--extended .mdc-fab__label+.mdc-fab__icon,.mdc-fab--extended .mdc-fab__label+.mdc-fab__icon[dir=rtl]{margin-left:calc(12px - 20px);margin-right:12px}.mdc-fab--touch{margin-top:4px;margin-bottom:4px;margin-right:4px;margin-left:4px}.mdc-fab--touch .mdc-fab__touch{position:absolute;top:50%;height:48px;left:50%;width:48px;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%)}.mdc-fab::before{position:absolute;box-sizing:border-box;width:100%;height:100%;top:0;left:0;border:1px solid transparent;border-radius:inherit;content:"";pointer-events:none}.mdc-fab__label{justify-content:flex-start;text-overflow:ellipsis;white-space:nowrap;overflow-x:hidden;overflow-y:visible}.mdc-fab__icon{transition:-webkit-transform 180ms 90ms cubic-bezier(0, 0, 0.2, 1);transition:transform 180ms 90ms cubic-bezier(0, 0, 0.2, 1);transition:transform 180ms 90ms cubic-bezier(0, 0, 0.2, 1), -webkit-transform 180ms 90ms cubic-bezier(0, 0, 0.2, 1);fill:currentColor;will-change:transform}.mdc-fab .mdc-fab__icon{display:inline-flex;align-items:center;justify-content:center}.mdc-fab--exited{-webkit-transform:scale(0);transform:scale(0);opacity:0;transition:opacity 15ms linear 150ms,-webkit-transform 180ms 0ms cubic-bezier(0.4, 0, 1, 1);transition:opacity 15ms linear 150ms,transform 180ms 0ms cubic-bezier(0.4, 0, 1, 1);transition:opacity 15ms linear 150ms,transform 180ms 0ms cubic-bezier(0.4, 0, 1, 1),-webkit-transform 180ms 0ms cubic-bezier(0.4, 0, 1, 1)}.mdc-fab--exited .mdc-fab__icon{-webkit-transform:scale(0);transform:scale(0);transition:-webkit-transform 135ms 0ms cubic-bezier(0.4, 0, 1, 1);transition:transform 135ms 0ms cubic-bezier(0.4, 0, 1, 1);transition:transform 135ms 0ms cubic-bezier(0.4, 0, 1, 1), -webkit-transform 135ms 0ms cubic-bezier(0.4, 0, 1, 1)}.mdc-fab{background-color:#018786;background-color:var(--mdc-theme-secondary, #018786);box-shadow:0px 3px 5px -1px rgba(0, 0, 0, 0.2),0px 6px 10px 0px rgba(0, 0, 0, 0.14),0px 1px 18px 0px rgba(0,0,0,.12)}.mdc-fab .mdc-fab__icon{width:24px;height:24px;font-size:24px}.mdc-fab,.mdc-fab:not(:disabled) .mdc-fab__icon,.mdc-fab:not(:disabled) .mdc-fab__label,.mdc-fab:disabled .mdc-fab__icon,.mdc-fab:disabled .mdc-fab__label{color:#fff;color:var(--mdc-theme-on-secondary, #fff)}.mdc-fab:not(.mdc-fab--extended){border-radius:50%}.mdc-fab:not(.mdc-fab--extended) .mdc-fab__ripple{border-radius:50%}.mdc-fab{--mdc-ripple-fg-size: 0;--mdc-ripple-left: 0;--mdc-ripple-top: 0;--mdc-ripple-fg-scale: 1;--mdc-ripple-fg-translate-end: 0;--mdc-ripple-fg-translate-start: 0;-webkit-tap-highlight-color:rgba(0,0,0,0);will-change:transform,opacity}.mdc-fab .mdc-fab__ripple::before,.mdc-fab .mdc-fab__ripple::after{position:absolute;border-radius:50%;opacity:0;pointer-events:none;content:""}.mdc-fab .mdc-fab__ripple::before{transition:opacity 15ms linear,background-color 15ms linear;z-index:1;z-index:var(--mdc-ripple-z-index, 1)}.mdc-fab .mdc-fab__ripple::after{z-index:0;z-index:var(--mdc-ripple-z-index, 0)}.mdc-fab.mdc-ripple-upgraded .mdc-fab__ripple::before{-webkit-transform:scale(var(--mdc-ripple-fg-scale, 1));transform:scale(var(--mdc-ripple-fg-scale, 1))}.mdc-fab.mdc-ripple-upgraded .mdc-fab__ripple::after{top:0;left:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:center center;transform-origin:center center}.mdc-fab.mdc-ripple-upgraded--unbounded .mdc-fab__ripple::after{top:var(--mdc-ripple-top, 0);left:var(--mdc-ripple-left, 0)}.mdc-fab.mdc-ripple-upgraded--foreground-activation .mdc-fab__ripple::after{-webkit-animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards;animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards}.mdc-fab.mdc-ripple-upgraded--foreground-deactivation .mdc-fab__ripple::after{-webkit-animation:mdc-ripple-fg-opacity-out 150ms;animation:mdc-ripple-fg-opacity-out 150ms;-webkit-transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1))}.mdc-fab .mdc-fab__ripple::before,.mdc-fab .mdc-fab__ripple::after{top:calc(50% - 100%);left:calc(50% - 100%);width:200%;height:200%}.mdc-fab.mdc-ripple-upgraded .mdc-fab__ripple::after{width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}.mdc-fab .mdc-fab__ripple::before,.mdc-fab .mdc-fab__ripple::after{background-color:#fff;background-color:var(--mdc-ripple-color, var(--mdc-theme-on-secondary, #fff))}.mdc-fab:hover .mdc-fab__ripple::before,.mdc-fab.mdc-ripple-surface--hover .mdc-fab__ripple::before{opacity:0.08;opacity:var(--mdc-ripple-hover-opacity, 0.08)}.mdc-fab.mdc-ripple-upgraded--background-focused .mdc-fab__ripple::before,.mdc-fab:not(.mdc-ripple-upgraded):focus .mdc-fab__ripple::before{transition-duration:75ms;opacity:0.24;opacity:var(--mdc-ripple-focus-opacity, 0.24)}.mdc-fab:not(.mdc-ripple-upgraded) .mdc-fab__ripple::after{transition:opacity 150ms linear}.mdc-fab:not(.mdc-ripple-upgraded):active .mdc-fab__ripple::after{transition-duration:75ms;opacity:0.24;opacity:var(--mdc-ripple-press-opacity, 0.24)}.mdc-fab.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.24)}.mdc-fab .mdc-fab__ripple{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;overflow:hidden}.mdc-fab{z-index:0}.mdc-fab .mdc-fab__ripple::before,.mdc-fab .mdc-fab__ripple::after{z-index:-1;z-index:var(--mdc-ripple-z-index, -1)}.mdc-form-field{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.875rem;font-size:var(--mdc-typography-body2-font-size, 0.875rem);line-height:1.25rem;line-height:var(--mdc-typography-body2-line-height, 1.25rem);font-weight:400;font-weight:var(--mdc-typography-body2-font-weight, 400);letter-spacing:0.0178571429em;letter-spacing:var(--mdc-typography-body2-letter-spacing, 0.0178571429em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-body2-text-transform, inherit);color:rgba(0, 0, 0, 0.87);color:var(--mdc-theme-text-primary-on-background, rgba(0, 0, 0, 0.87));display:inline-flex;align-items:center;vertical-align:middle}.mdc-form-field>label{margin-left:0;margin-right:auto;padding-left:4px;padding-right:0;order:0}[dir=rtl] .mdc-form-field>label,.mdc-form-field>label[dir=rtl]{margin-left:auto;margin-right:0}[dir=rtl] .mdc-form-field>label,.mdc-form-field>label[dir=rtl]{padding-left:0;padding-right:4px}.mdc-form-field--nowrap>label{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.mdc-form-field--align-end>label{margin-left:auto;margin-right:0;padding-left:0;padding-right:4px;order:-1}[dir=rtl] .mdc-form-field--align-end>label,.mdc-form-field--align-end>label[dir=rtl]{margin-left:0;margin-right:auto}[dir=rtl] .mdc-form-field--align-end>label,.mdc-form-field--align-end>label[dir=rtl]{padding-left:4px;padding-right:0}.mdc-form-field--space-between{justify-content:space-between}.mdc-form-field--space-between>label{margin:0}[dir=rtl] .mdc-form-field--space-between>label,.mdc-form-field--space-between>label[dir=rtl]{margin:0}.mdc-icon-button{display:inline-block;position:relative;box-sizing:border-box;border:none;outline:none;background-color:transparent;fill:currentColor;color:inherit;font-size:24px;text-decoration:none;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;width:48px;height:48px;padding:12px}.mdc-icon-button svg,.mdc-icon-button img{width:24px;height:24px}.mdc-icon-button:disabled{color:rgba(0, 0, 0, 0.38);color:var(--mdc-theme-text-disabled-on-light, rgba(0, 0, 0, 0.38))}.mdc-icon-button:disabled{cursor:default;pointer-events:none}.mdc-icon-button .mdc-icon-button__touch{position:absolute;top:50%;height:48px;left:50%;width:48px;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%)}.mdc-icon-button__icon{display:inline-block}.mdc-icon-button__icon.mdc-icon-button__icon--on{display:none}.mdc-icon-button--on .mdc-icon-button__icon{display:none}.mdc-icon-button--on .mdc-icon-button__icon.mdc-icon-button__icon--on{display:inline-block}.mdc-icon-button--touch{margin-top:0px;margin-bottom:0px}.mdc-icon-button{--mdc-ripple-fg-size: 0;--mdc-ripple-left: 0;--mdc-ripple-top: 0;--mdc-ripple-fg-scale: 1;--mdc-ripple-fg-translate-end: 0;--mdc-ripple-fg-translate-start: 0;-webkit-tap-highlight-color:rgba(0,0,0,0);will-change:transform,opacity}.mdc-icon-button .mdc-icon-button__ripple::before,.mdc-icon-button .mdc-icon-button__ripple::after{position:absolute;border-radius:50%;opacity:0;pointer-events:none;content:""}.mdc-icon-button .mdc-icon-button__ripple::before{transition:opacity 15ms linear,background-color 15ms linear;z-index:1;z-index:var(--mdc-ripple-z-index, 1)}.mdc-icon-button .mdc-icon-button__ripple::after{z-index:0;z-index:var(--mdc-ripple-z-index, 0)}.mdc-icon-button.mdc-ripple-upgraded .mdc-icon-button__ripple::before{-webkit-transform:scale(var(--mdc-ripple-fg-scale, 1));transform:scale(var(--mdc-ripple-fg-scale, 1))}.mdc-icon-button.mdc-ripple-upgraded .mdc-icon-button__ripple::after{top:0;left:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:center center;transform-origin:center center}.mdc-icon-button.mdc-ripple-upgraded--unbounded .mdc-icon-button__ripple::after{top:var(--mdc-ripple-top, 0);left:var(--mdc-ripple-left, 0)}.mdc-icon-button.mdc-ripple-upgraded--foreground-activation .mdc-icon-button__ripple::after{-webkit-animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards;animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards}.mdc-icon-button.mdc-ripple-upgraded--foreground-deactivation .mdc-icon-button__ripple::after{-webkit-animation:mdc-ripple-fg-opacity-out 150ms;animation:mdc-ripple-fg-opacity-out 150ms;-webkit-transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1))}.mdc-icon-button .mdc-icon-button__ripple::before,.mdc-icon-button .mdc-icon-button__ripple::after{top:calc(50% - 50%);left:calc(50% - 50%);width:100%;height:100%}.mdc-icon-button.mdc-ripple-upgraded .mdc-icon-button__ripple::before,.mdc-icon-button.mdc-ripple-upgraded .mdc-icon-button__ripple::after{top:var(--mdc-ripple-top, calc(50% - 50%));left:var(--mdc-ripple-left, calc(50% - 50%));width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}.mdc-icon-button.mdc-ripple-upgraded .mdc-icon-button__ripple::after{width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}.mdc-icon-button .mdc-icon-button__ripple::before,.mdc-icon-button .mdc-icon-button__ripple::after{background-color:#000;background-color:var(--mdc-ripple-color, #000)}.mdc-icon-button:hover .mdc-icon-button__ripple::before,.mdc-icon-button.mdc-ripple-surface--hover .mdc-icon-button__ripple::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}.mdc-icon-button.mdc-ripple-upgraded--background-focused .mdc-icon-button__ripple::before,.mdc-icon-button:not(.mdc-ripple-upgraded):focus .mdc-icon-button__ripple::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-icon-button:not(.mdc-ripple-upgraded) .mdc-icon-button__ripple::after{transition:opacity 150ms linear}.mdc-icon-button:not(.mdc-ripple-upgraded):active .mdc-icon-button__ripple::after{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-icon-button.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-icon-button .mdc-icon-button__ripple{pointer-events:none;z-index:1}.mdc-image-list{display:flex;flex-wrap:wrap;margin:0 auto;padding:0}.mdc-image-list__item,.mdc-image-list__image-aspect-container{position:relative;box-sizing:border-box}.mdc-image-list__item{list-style-type:none}.mdc-image-list__image{width:100%}.mdc-image-list__image-aspect-container .mdc-image-list__image{position:absolute;top:0;right:0;bottom:0;left:0;height:100%;background-repeat:no-repeat;background-position:center;background-size:cover}.mdc-image-list__image-aspect-container{padding-bottom:calc(100% / 1)}.mdc-image-list__image{border-radius:0}.mdc-image-list--with-text-protection .mdc-image-list__supporting{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.mdc-image-list__supporting{color:rgba(0, 0, 0, 0.87);color:var(--mdc-theme-text-primary-on-background, rgba(0, 0, 0, 0.87));display:flex;align-items:center;justify-content:space-between;box-sizing:border-box;padding:8px 0;line-height:24px}.mdc-image-list__label{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-subtitle1-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:1rem;font-size:var(--mdc-typography-subtitle1-font-size, 1rem);line-height:1.75rem;line-height:var(--mdc-typography-subtitle1-line-height, 1.75rem);font-weight:400;font-weight:var(--mdc-typography-subtitle1-font-weight, 400);letter-spacing:0.009375em;letter-spacing:var(--mdc-typography-subtitle1-letter-spacing, 0.009375em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-subtitle1-text-decoration, inherit);text-decoration:var(--mdc-typography-subtitle1-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-subtitle1-text-transform, inherit);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.mdc-image-list--with-text-protection .mdc-image-list__supporting{position:absolute;bottom:0;width:100%;height:48px;padding:0 16px;background:rgba(0,0,0,.6);color:#fff}.mdc-image-list--masonry{display:block}.mdc-image-list--masonry .mdc-image-list__item{-webkit-column-break-inside:avoid;break-inside:avoid-column}.mdc-image-list--masonry .mdc-image-list__image{display:block;height:auto}:root{--mdc-layout-grid-margin-desktop: 24px;--mdc-layout-grid-gutter-desktop: 24px;--mdc-layout-grid-column-width-desktop: 72px;--mdc-layout-grid-margin-tablet: 16px;--mdc-layout-grid-gutter-tablet: 16px;--mdc-layout-grid-column-width-tablet: 72px;--mdc-layout-grid-margin-phone: 16px;--mdc-layout-grid-gutter-phone: 16px;--mdc-layout-grid-column-width-phone: 72px}@media(min-width: 840px){.mdc-layout-grid{box-sizing:border-box;margin:0 auto;padding:24px;padding:var(--mdc-layout-grid-margin-desktop, 24px)}}@media(min-width: 600px)and (max-width: 839px){.mdc-layout-grid{box-sizing:border-box;margin:0 auto;padding:16px;padding:var(--mdc-layout-grid-margin-tablet, 16px)}}@media(max-width: 599px){.mdc-layout-grid{box-sizing:border-box;margin:0 auto;padding:16px;padding:var(--mdc-layout-grid-margin-phone, 16px)}}@media(min-width: 840px){.mdc-layout-grid__inner{display:flex;flex-flow:row wrap;align-items:stretch;margin:-12px;margin:calc(var(--mdc-layout-grid-gutter-desktop, 24px) / 2 * -1)}@supports(display: grid){.mdc-layout-grid__inner{display:grid;margin:0;grid-gap:24px;grid-gap:var(--mdc-layout-grid-gutter-desktop, 24px);grid-template-columns:repeat(12, minmax(0, 1fr))}}}@media(min-width: 600px)and (max-width: 839px){.mdc-layout-grid__inner{display:flex;flex-flow:row wrap;align-items:stretch;margin:-8px;margin:calc(var(--mdc-layout-grid-gutter-tablet, 16px) / 2 * -1)}@supports(display: grid){.mdc-layout-grid__inner{display:grid;margin:0;grid-gap:16px;grid-gap:var(--mdc-layout-grid-gutter-tablet, 16px);grid-template-columns:repeat(8, minmax(0, 1fr))}}}@media(max-width: 599px){.mdc-layout-grid__inner{display:flex;flex-flow:row wrap;align-items:stretch;margin:-8px;margin:calc(var(--mdc-layout-grid-gutter-phone, 16px) / 2 * -1)}@supports(display: grid){.mdc-layout-grid__inner{display:grid;margin:0;grid-gap:16px;grid-gap:var(--mdc-layout-grid-gutter-phone, 16px);grid-template-columns:repeat(4, minmax(0, 1fr))}}}@media(min-width: 840px){.mdc-layout-grid__cell{width:calc(33.3333333333% - 24px);width:calc(33.3333333333% - var(--mdc-layout-grid-gutter-desktop, 24px));box-sizing:border-box;margin:12px;margin:calc(var(--mdc-layout-grid-gutter-desktop, 24px) / 2)}@supports(display: grid){.mdc-layout-grid__cell{width:auto;grid-column-end:span 4}}@supports(display: grid){.mdc-layout-grid__cell{margin:0}}.mdc-layout-grid__cell--span-1,.mdc-layout-grid__cell--span-1-desktop{width:calc(8.3333333333% - 24px);width:calc(8.3333333333% - var(--mdc-layout-grid-gutter-desktop, 24px))}@supports(display: grid){.mdc-layout-grid__cell--span-1,.mdc-layout-grid__cell--span-1-desktop{width:auto;grid-column-end:span 1}}.mdc-layout-grid__cell--span-2,.mdc-layout-grid__cell--span-2-desktop{width:calc(16.6666666667% - 24px);width:calc(16.6666666667% - var(--mdc-layout-grid-gutter-desktop, 24px))}@supports(display: grid){.mdc-layout-grid__cell--span-2,.mdc-layout-grid__cell--span-2-desktop{width:auto;grid-column-end:span 2}}.mdc-layout-grid__cell--span-3,.mdc-layout-grid__cell--span-3-desktop{width:calc(25% - 24px);width:calc(25% - var(--mdc-layout-grid-gutter-desktop, 24px))}@supports(display: grid){.mdc-layout-grid__cell--span-3,.mdc-layout-grid__cell--span-3-desktop{width:auto;grid-column-end:span 3}}.mdc-layout-grid__cell--span-4,.mdc-layout-grid__cell--span-4-desktop{width:calc(33.3333333333% - 24px);width:calc(33.3333333333% - var(--mdc-layout-grid-gutter-desktop, 24px))}@supports(display: grid){.mdc-layout-grid__cell--span-4,.mdc-layout-grid__cell--span-4-desktop{width:auto;grid-column-end:span 4}}.mdc-layout-grid__cell--span-5,.mdc-layout-grid__cell--span-5-desktop{width:calc(41.6666666667% - 24px);width:calc(41.6666666667% - var(--mdc-layout-grid-gutter-desktop, 24px))}@supports(display: grid){.mdc-layout-grid__cell--span-5,.mdc-layout-grid__cell--span-5-desktop{width:auto;grid-column-end:span 5}}.mdc-layout-grid__cell--span-6,.mdc-layout-grid__cell--span-6-desktop{width:calc(50% - 24px);width:calc(50% - var(--mdc-layout-grid-gutter-desktop, 24px))}@supports(display: grid){.mdc-layout-grid__cell--span-6,.mdc-layout-grid__cell--span-6-desktop{width:auto;grid-column-end:span 6}}.mdc-layout-grid__cell--span-7,.mdc-layout-grid__cell--span-7-desktop{width:calc(58.3333333333% - 24px);width:calc(58.3333333333% - var(--mdc-layout-grid-gutter-desktop, 24px))}@supports(display: grid){.mdc-layout-grid__cell--span-7,.mdc-layout-grid__cell--span-7-desktop{width:auto;grid-column-end:span 7}}.mdc-layout-grid__cell--span-8,.mdc-layout-grid__cell--span-8-desktop{width:calc(66.6666666667% - 24px);width:calc(66.6666666667% - var(--mdc-layout-grid-gutter-desktop, 24px))}@supports(display: grid){.mdc-layout-grid__cell--span-8,.mdc-layout-grid__cell--span-8-desktop{width:auto;grid-column-end:span 8}}.mdc-layout-grid__cell--span-9,.mdc-layout-grid__cell--span-9-desktop{width:calc(75% - 24px);width:calc(75% - var(--mdc-layout-grid-gutter-desktop, 24px))}@supports(display: grid){.mdc-layout-grid__cell--span-9,.mdc-layout-grid__cell--span-9-desktop{width:auto;grid-column-end:span 9}}.mdc-layout-grid__cell--span-10,.mdc-layout-grid__cell--span-10-desktop{width:calc(83.3333333333% - 24px);width:calc(83.3333333333% - var(--mdc-layout-grid-gutter-desktop, 24px))}@supports(display: grid){.mdc-layout-grid__cell--span-10,.mdc-layout-grid__cell--span-10-desktop{width:auto;grid-column-end:span 10}}.mdc-layout-grid__cell--span-11,.mdc-layout-grid__cell--span-11-desktop{width:calc(91.6666666667% - 24px);width:calc(91.6666666667% - var(--mdc-layout-grid-gutter-desktop, 24px))}@supports(display: grid){.mdc-layout-grid__cell--span-11,.mdc-layout-grid__cell--span-11-desktop{width:auto;grid-column-end:span 11}}.mdc-layout-grid__cell--span-12,.mdc-layout-grid__cell--span-12-desktop{width:calc(100% - 24px);width:calc(100% - var(--mdc-layout-grid-gutter-desktop, 24px))}@supports(display: grid){.mdc-layout-grid__cell--span-12,.mdc-layout-grid__cell--span-12-desktop{width:auto;grid-column-end:span 12}}}@media(min-width: 600px)and (max-width: 839px){.mdc-layout-grid__cell{width:calc(50% - 16px);width:calc(50% - var(--mdc-layout-grid-gutter-tablet, 16px));box-sizing:border-box;margin:8px;margin:calc(var(--mdc-layout-grid-gutter-tablet, 16px) / 2)}@supports(display: grid){.mdc-layout-grid__cell{width:auto;grid-column-end:span 4}}@supports(display: grid){.mdc-layout-grid__cell{margin:0}}.mdc-layout-grid__cell--span-1,.mdc-layout-grid__cell--span-1-tablet{width:calc(12.5% - 16px);width:calc(12.5% - var(--mdc-layout-grid-gutter-tablet, 16px))}@supports(display: grid){.mdc-layout-grid__cell--span-1,.mdc-layout-grid__cell--span-1-tablet{width:auto;grid-column-end:span 1}}.mdc-layout-grid__cell--span-2,.mdc-layout-grid__cell--span-2-tablet{width:calc(25% - 16px);width:calc(25% - var(--mdc-layout-grid-gutter-tablet, 16px))}@supports(display: grid){.mdc-layout-grid__cell--span-2,.mdc-layout-grid__cell--span-2-tablet{width:auto;grid-column-end:span 2}}.mdc-layout-grid__cell--span-3,.mdc-layout-grid__cell--span-3-tablet{width:calc(37.5% - 16px);width:calc(37.5% - var(--mdc-layout-grid-gutter-tablet, 16px))}@supports(display: grid){.mdc-layout-grid__cell--span-3,.mdc-layout-grid__cell--span-3-tablet{width:auto;grid-column-end:span 3}}.mdc-layout-grid__cell--span-4,.mdc-layout-grid__cell--span-4-tablet{width:calc(50% - 16px);width:calc(50% - var(--mdc-layout-grid-gutter-tablet, 16px))}@supports(display: grid){.mdc-layout-grid__cell--span-4,.mdc-layout-grid__cell--span-4-tablet{width:auto;grid-column-end:span 4}}.mdc-layout-grid__cell--span-5,.mdc-layout-grid__cell--span-5-tablet{width:calc(62.5% - 16px);width:calc(62.5% - var(--mdc-layout-grid-gutter-tablet, 16px))}@supports(display: grid){.mdc-layout-grid__cell--span-5,.mdc-layout-grid__cell--span-5-tablet{width:auto;grid-column-end:span 5}}.mdc-layout-grid__cell--span-6,.mdc-layout-grid__cell--span-6-tablet{width:calc(75% - 16px);width:calc(75% - var(--mdc-layout-grid-gutter-tablet, 16px))}@supports(display: grid){.mdc-layout-grid__cell--span-6,.mdc-layout-grid__cell--span-6-tablet{width:auto;grid-column-end:span 6}}.mdc-layout-grid__cell--span-7,.mdc-layout-grid__cell--span-7-tablet{width:calc(87.5% - 16px);width:calc(87.5% - var(--mdc-layout-grid-gutter-tablet, 16px))}@supports(display: grid){.mdc-layout-grid__cell--span-7,.mdc-layout-grid__cell--span-7-tablet{width:auto;grid-column-end:span 7}}.mdc-layout-grid__cell--span-8,.mdc-layout-grid__cell--span-8-tablet{width:calc(100% - 16px);width:calc(100% - var(--mdc-layout-grid-gutter-tablet, 16px))}@supports(display: grid){.mdc-layout-grid__cell--span-8,.mdc-layout-grid__cell--span-8-tablet{width:auto;grid-column-end:span 8}}.mdc-layout-grid__cell--span-9,.mdc-layout-grid__cell--span-9-tablet{width:calc(100% - 16px);width:calc(100% - var(--mdc-layout-grid-gutter-tablet, 16px))}@supports(display: grid){.mdc-layout-grid__cell--span-9,.mdc-layout-grid__cell--span-9-tablet{width:auto;grid-column-end:span 8}}.mdc-layout-grid__cell--span-10,.mdc-layout-grid__cell--span-10-tablet{width:calc(100% - 16px);width:calc(100% - var(--mdc-layout-grid-gutter-tablet, 16px))}@supports(display: grid){.mdc-layout-grid__cell--span-10,.mdc-layout-grid__cell--span-10-tablet{width:auto;grid-column-end:span 8}}.mdc-layout-grid__cell--span-11,.mdc-layout-grid__cell--span-11-tablet{width:calc(100% - 16px);width:calc(100% - var(--mdc-layout-grid-gutter-tablet, 16px))}@supports(display: grid){.mdc-layout-grid__cell--span-11,.mdc-layout-grid__cell--span-11-tablet{width:auto;grid-column-end:span 8}}.mdc-layout-grid__cell--span-12,.mdc-layout-grid__cell--span-12-tablet{width:calc(100% - 16px);width:calc(100% - var(--mdc-layout-grid-gutter-tablet, 16px))}@supports(display: grid){.mdc-layout-grid__cell--span-12,.mdc-layout-grid__cell--span-12-tablet{width:auto;grid-column-end:span 8}}}@media(max-width: 599px){.mdc-layout-grid__cell{width:calc(100% - 16px);width:calc(100% - var(--mdc-layout-grid-gutter-phone, 16px));box-sizing:border-box;margin:8px;margin:calc(var(--mdc-layout-grid-gutter-phone, 16px) / 2)}@supports(display: grid){.mdc-layout-grid__cell{width:auto;grid-column-end:span 4}}@supports(display: grid){.mdc-layout-grid__cell{margin:0}}.mdc-layout-grid__cell--span-1,.mdc-layout-grid__cell--span-1-phone{width:calc(25% - 16px);width:calc(25% - var(--mdc-layout-grid-gutter-phone, 16px))}@supports(display: grid){.mdc-layout-grid__cell--span-1,.mdc-layout-grid__cell--span-1-phone{width:auto;grid-column-end:span 1}}.mdc-layout-grid__cell--span-2,.mdc-layout-grid__cell--span-2-phone{width:calc(50% - 16px);width:calc(50% - var(--mdc-layout-grid-gutter-phone, 16px))}@supports(display: grid){.mdc-layout-grid__cell--span-2,.mdc-layout-grid__cell--span-2-phone{width:auto;grid-column-end:span 2}}.mdc-layout-grid__cell--span-3,.mdc-layout-grid__cell--span-3-phone{width:calc(75% - 16px);width:calc(75% - var(--mdc-layout-grid-gutter-phone, 16px))}@supports(display: grid){.mdc-layout-grid__cell--span-3,.mdc-layout-grid__cell--span-3-phone{width:auto;grid-column-end:span 3}}.mdc-layout-grid__cell--span-4,.mdc-layout-grid__cell--span-4-phone{width:calc(100% - 16px);width:calc(100% - var(--mdc-layout-grid-gutter-phone, 16px))}@supports(display: grid){.mdc-layout-grid__cell--span-4,.mdc-layout-grid__cell--span-4-phone{width:auto;grid-column-end:span 4}}.mdc-layout-grid__cell--span-5,.mdc-layout-grid__cell--span-5-phone{width:calc(100% - 16px);width:calc(100% - var(--mdc-layout-grid-gutter-phone, 16px))}@supports(display: grid){.mdc-layout-grid__cell--span-5,.mdc-layout-grid__cell--span-5-phone{width:auto;grid-column-end:span 4}}.mdc-layout-grid__cell--span-6,.mdc-layout-grid__cell--span-6-phone{width:calc(100% - 16px);width:calc(100% - var(--mdc-layout-grid-gutter-phone, 16px))}@supports(display: grid){.mdc-layout-grid__cell--span-6,.mdc-layout-grid__cell--span-6-phone{width:auto;grid-column-end:span 4}}.mdc-layout-grid__cell--span-7,.mdc-layout-grid__cell--span-7-phone{width:calc(100% - 16px);width:calc(100% - var(--mdc-layout-grid-gutter-phone, 16px))}@supports(display: grid){.mdc-layout-grid__cell--span-7,.mdc-layout-grid__cell--span-7-phone{width:auto;grid-column-end:span 4}}.mdc-layout-grid__cell--span-8,.mdc-layout-grid__cell--span-8-phone{width:calc(100% - 16px);width:calc(100% - var(--mdc-layout-grid-gutter-phone, 16px))}@supports(display: grid){.mdc-layout-grid__cell--span-8,.mdc-layout-grid__cell--span-8-phone{width:auto;grid-column-end:span 4}}.mdc-layout-grid__cell--span-9,.mdc-layout-grid__cell--span-9-phone{width:calc(100% - 16px);width:calc(100% - var(--mdc-layout-grid-gutter-phone, 16px))}@supports(display: grid){.mdc-layout-grid__cell--span-9,.mdc-layout-grid__cell--span-9-phone{width:auto;grid-column-end:span 4}}.mdc-layout-grid__cell--span-10,.mdc-layout-grid__cell--span-10-phone{width:calc(100% - 16px);width:calc(100% - var(--mdc-layout-grid-gutter-phone, 16px))}@supports(display: grid){.mdc-layout-grid__cell--span-10,.mdc-layout-grid__cell--span-10-phone{width:auto;grid-column-end:span 4}}.mdc-layout-grid__cell--span-11,.mdc-layout-grid__cell--span-11-phone{width:calc(100% - 16px);width:calc(100% - var(--mdc-layout-grid-gutter-phone, 16px))}@supports(display: grid){.mdc-layout-grid__cell--span-11,.mdc-layout-grid__cell--span-11-phone{width:auto;grid-column-end:span 4}}.mdc-layout-grid__cell--span-12,.mdc-layout-grid__cell--span-12-phone{width:calc(100% - 16px);width:calc(100% - var(--mdc-layout-grid-gutter-phone, 16px))}@supports(display: grid){.mdc-layout-grid__cell--span-12,.mdc-layout-grid__cell--span-12-phone{width:auto;grid-column-end:span 4}}}.mdc-layout-grid__cell--order-1{order:1}.mdc-layout-grid__cell--order-2{order:2}.mdc-layout-grid__cell--order-3{order:3}.mdc-layout-grid__cell--order-4{order:4}.mdc-layout-grid__cell--order-5{order:5}.mdc-layout-grid__cell--order-6{order:6}.mdc-layout-grid__cell--order-7{order:7}.mdc-layout-grid__cell--order-8{order:8}.mdc-layout-grid__cell--order-9{order:9}.mdc-layout-grid__cell--order-10{order:10}.mdc-layout-grid__cell--order-11{order:11}.mdc-layout-grid__cell--order-12{order:12}.mdc-layout-grid__cell--align-top{align-self:flex-start}@supports(display: grid){.mdc-layout-grid__cell--align-top{align-self:start}}.mdc-layout-grid__cell--align-middle{align-self:center}.mdc-layout-grid__cell--align-bottom{align-self:flex-end}@supports(display: grid){.mdc-layout-grid__cell--align-bottom{align-self:end}}@media(min-width: 840px){.mdc-layout-grid--fixed-column-width{width:1176px;width:calc( var(--mdc-layout-grid-column-width-desktop, 72px) * 12 + var(--mdc-layout-grid-gutter-desktop, 24px) * 11 + var(--mdc-layout-grid-margin-desktop, 24px) * 2 )}}@media(min-width: 600px)and (max-width: 839px){.mdc-layout-grid--fixed-column-width{width:720px;width:calc( var(--mdc-layout-grid-column-width-tablet, 72px) * 8 + var(--mdc-layout-grid-gutter-tablet, 16px) * 7 + var(--mdc-layout-grid-margin-tablet, 16px) * 2 )}}@media(max-width: 599px){.mdc-layout-grid--fixed-column-width{width:368px;width:calc( var(--mdc-layout-grid-column-width-phone, 72px) * 4 + var(--mdc-layout-grid-gutter-phone, 16px) * 3 + var(--mdc-layout-grid-margin-phone, 16px) * 2 )}}.mdc-layout-grid--align-left{margin-right:auto;margin-left:0}.mdc-layout-grid--align-right{margin-right:0;margin-left:auto}@-webkit-keyframes mdc-linear-progress-primary-indeterminate-translate{0%{-webkit-transform:translateX(0);transform:translateX(0)}20%{-webkit-animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);-webkit-transform:translateX(0);transform:translateX(0)}59.15%{-webkit-animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);-webkit-transform:translateX(83.67142%);transform:translateX(83.67142%);-webkit-transform:translateX(var(--mdc-linear-progress-primary-half, 83.67142%));transform:translateX(var(--mdc-linear-progress-primary-half, 83.67142%))}100%{-webkit-transform:translateX(200.611057%);transform:translateX(200.611057%);-webkit-transform:translateX(var(--mdc-linear-progress-primary-full, 200.611057%));transform:translateX(var(--mdc-linear-progress-primary-full, 200.611057%))}}@keyframes mdc-linear-progress-primary-indeterminate-translate{0%{-webkit-transform:translateX(0);transform:translateX(0)}20%{-webkit-animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);-webkit-transform:translateX(0);transform:translateX(0)}59.15%{-webkit-animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);-webkit-transform:translateX(83.67142%);transform:translateX(83.67142%);-webkit-transform:translateX(var(--mdc-linear-progress-primary-half, 83.67142%));transform:translateX(var(--mdc-linear-progress-primary-half, 83.67142%))}100%{-webkit-transform:translateX(200.611057%);transform:translateX(200.611057%);-webkit-transform:translateX(var(--mdc-linear-progress-primary-full, 200.611057%));transform:translateX(var(--mdc-linear-progress-primary-full, 200.611057%))}}@-webkit-keyframes mdc-linear-progress-primary-indeterminate-scale{0%{-webkit-transform:scaleX(0.08);transform:scaleX(0.08)}36.65%{-webkit-animation-timing-function:cubic-bezier(0.334731, 0.12482, 0.785844, 1);animation-timing-function:cubic-bezier(0.334731, 0.12482, 0.785844, 1);-webkit-transform:scaleX(0.08);transform:scaleX(0.08)}69.15%{-webkit-animation-timing-function:cubic-bezier(0.06, 0.11, 0.6, 1);animation-timing-function:cubic-bezier(0.06, 0.11, 0.6, 1);-webkit-transform:scaleX(0.661479);transform:scaleX(0.661479)}100%{-webkit-transform:scaleX(0.08);transform:scaleX(0.08)}}@keyframes mdc-linear-progress-primary-indeterminate-scale{0%{-webkit-transform:scaleX(0.08);transform:scaleX(0.08)}36.65%{-webkit-animation-timing-function:cubic-bezier(0.334731, 0.12482, 0.785844, 1);animation-timing-function:cubic-bezier(0.334731, 0.12482, 0.785844, 1);-webkit-transform:scaleX(0.08);transform:scaleX(0.08)}69.15%{-webkit-animation-timing-function:cubic-bezier(0.06, 0.11, 0.6, 1);animation-timing-function:cubic-bezier(0.06, 0.11, 0.6, 1);-webkit-transform:scaleX(0.661479);transform:scaleX(0.661479)}100%{-webkit-transform:scaleX(0.08);transform:scaleX(0.08)}}@-webkit-keyframes mdc-linear-progress-secondary-indeterminate-translate{0%{-webkit-animation-timing-function:cubic-bezier(0.15, 0, 0.515058, 0.409685);animation-timing-function:cubic-bezier(0.15, 0, 0.515058, 0.409685);-webkit-transform:translateX(0);transform:translateX(0)}25%{-webkit-animation-timing-function:cubic-bezier(0.31033, 0.284058, 0.8, 0.733712);animation-timing-function:cubic-bezier(0.31033, 0.284058, 0.8, 0.733712);-webkit-transform:translateX(37.651913%);transform:translateX(37.651913%);-webkit-transform:translateX(var(--mdc-linear-progress-secondary-quarter, 37.651913%));transform:translateX(var(--mdc-linear-progress-secondary-quarter, 37.651913%))}48.35%{-webkit-animation-timing-function:cubic-bezier(0.4, 0.627035, 0.6, 0.902026);animation-timing-function:cubic-bezier(0.4, 0.627035, 0.6, 0.902026);-webkit-transform:translateX(84.386165%);transform:translateX(84.386165%);-webkit-transform:translateX(var(--mdc-linear-progress-secondary-half, 84.386165%));transform:translateX(var(--mdc-linear-progress-secondary-half, 84.386165%))}100%{-webkit-transform:translateX(160.277782%);transform:translateX(160.277782%);-webkit-transform:translateX(var(--mdc-linear-progress-secondary-full, 160.277782%));transform:translateX(var(--mdc-linear-progress-secondary-full, 160.277782%))}}@keyframes mdc-linear-progress-secondary-indeterminate-translate{0%{-webkit-animation-timing-function:cubic-bezier(0.15, 0, 0.515058, 0.409685);animation-timing-function:cubic-bezier(0.15, 0, 0.515058, 0.409685);-webkit-transform:translateX(0);transform:translateX(0)}25%{-webkit-animation-timing-function:cubic-bezier(0.31033, 0.284058, 0.8, 0.733712);animation-timing-function:cubic-bezier(0.31033, 0.284058, 0.8, 0.733712);-webkit-transform:translateX(37.651913%);transform:translateX(37.651913%);-webkit-transform:translateX(var(--mdc-linear-progress-secondary-quarter, 37.651913%));transform:translateX(var(--mdc-linear-progress-secondary-quarter, 37.651913%))}48.35%{-webkit-animation-timing-function:cubic-bezier(0.4, 0.627035, 0.6, 0.902026);animation-timing-function:cubic-bezier(0.4, 0.627035, 0.6, 0.902026);-webkit-transform:translateX(84.386165%);transform:translateX(84.386165%);-webkit-transform:translateX(var(--mdc-linear-progress-secondary-half, 84.386165%));transform:translateX(var(--mdc-linear-progress-secondary-half, 84.386165%))}100%{-webkit-transform:translateX(160.277782%);transform:translateX(160.277782%);-webkit-transform:translateX(var(--mdc-linear-progress-secondary-full, 160.277782%));transform:translateX(var(--mdc-linear-progress-secondary-full, 160.277782%))}}@-webkit-keyframes mdc-linear-progress-secondary-indeterminate-scale{0%{-webkit-animation-timing-function:cubic-bezier(0.205028, 0.057051, 0.57661, 0.453971);animation-timing-function:cubic-bezier(0.205028, 0.057051, 0.57661, 0.453971);-webkit-transform:scaleX(0.08);transform:scaleX(0.08)}19.15%{-webkit-animation-timing-function:cubic-bezier(0.152313, 0.196432, 0.648374, 1.004315);animation-timing-function:cubic-bezier(0.152313, 0.196432, 0.648374, 1.004315);-webkit-transform:scaleX(0.457104);transform:scaleX(0.457104)}44.15%{-webkit-animation-timing-function:cubic-bezier(0.257759, -0.003163, 0.211762, 1.38179);animation-timing-function:cubic-bezier(0.257759, -0.003163, 0.211762, 1.38179);-webkit-transform:scaleX(0.72796);transform:scaleX(0.72796)}100%{-webkit-transform:scaleX(0.08);transform:scaleX(0.08)}}@keyframes mdc-linear-progress-secondary-indeterminate-scale{0%{-webkit-animation-timing-function:cubic-bezier(0.205028, 0.057051, 0.57661, 0.453971);animation-timing-function:cubic-bezier(0.205028, 0.057051, 0.57661, 0.453971);-webkit-transform:scaleX(0.08);transform:scaleX(0.08)}19.15%{-webkit-animation-timing-function:cubic-bezier(0.152313, 0.196432, 0.648374, 1.004315);animation-timing-function:cubic-bezier(0.152313, 0.196432, 0.648374, 1.004315);-webkit-transform:scaleX(0.457104);transform:scaleX(0.457104)}44.15%{-webkit-animation-timing-function:cubic-bezier(0.257759, -0.003163, 0.211762, 1.38179);animation-timing-function:cubic-bezier(0.257759, -0.003163, 0.211762, 1.38179);-webkit-transform:scaleX(0.72796);transform:scaleX(0.72796)}100%{-webkit-transform:scaleX(0.08);transform:scaleX(0.08)}}@-webkit-keyframes mdc-linear-progress-buffering{from{-webkit-transform:rotate(180deg) translateX(-10px);transform:rotate(180deg) translateX(-10px)}}@keyframes mdc-linear-progress-buffering{from{-webkit-transform:rotate(180deg) translateX(-10px);transform:rotate(180deg) translateX(-10px)}}@-webkit-keyframes mdc-linear-progress-primary-indeterminate-translate-reverse{0%{-webkit-transform:translateX(0);transform:translateX(0)}20%{-webkit-animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);-webkit-transform:translateX(0);transform:translateX(0)}59.15%{-webkit-animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);-webkit-transform:translateX(-83.67142%);transform:translateX(-83.67142%);-webkit-transform:translateX(var(--mdc-linear-progress-primary-half-neg, -83.67142%));transform:translateX(var(--mdc-linear-progress-primary-half-neg, -83.67142%))}100%{-webkit-transform:translateX(-200.611057%);transform:translateX(-200.611057%);-webkit-transform:translateX(var(--mdc-linear-progress-primary-full-neg, -200.611057%));transform:translateX(var(--mdc-linear-progress-primary-full-neg, -200.611057%))}}@keyframes mdc-linear-progress-primary-indeterminate-translate-reverse{0%{-webkit-transform:translateX(0);transform:translateX(0)}20%{-webkit-animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);-webkit-transform:translateX(0);transform:translateX(0)}59.15%{-webkit-animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);-webkit-transform:translateX(-83.67142%);transform:translateX(-83.67142%);-webkit-transform:translateX(var(--mdc-linear-progress-primary-half-neg, -83.67142%));transform:translateX(var(--mdc-linear-progress-primary-half-neg, -83.67142%))}100%{-webkit-transform:translateX(-200.611057%);transform:translateX(-200.611057%);-webkit-transform:translateX(var(--mdc-linear-progress-primary-full-neg, -200.611057%));transform:translateX(var(--mdc-linear-progress-primary-full-neg, -200.611057%))}}@-webkit-keyframes mdc-linear-progress-secondary-indeterminate-translate-reverse{0%{-webkit-animation-timing-function:cubic-bezier(0.15, 0, 0.515058, 0.409685);animation-timing-function:cubic-bezier(0.15, 0, 0.515058, 0.409685);-webkit-transform:translateX(0);transform:translateX(0)}25%{-webkit-animation-timing-function:cubic-bezier(0.31033, 0.284058, 0.8, 0.733712);animation-timing-function:cubic-bezier(0.31033, 0.284058, 0.8, 0.733712);-webkit-transform:translateX(-37.651913%);transform:translateX(-37.651913%);-webkit-transform:translateX(var(--mdc-linear-progress-secondary-quarter-neg, -37.651913%));transform:translateX(var(--mdc-linear-progress-secondary-quarter-neg, -37.651913%))}48.35%{-webkit-animation-timing-function:cubic-bezier(0.4, 0.627035, 0.6, 0.902026);animation-timing-function:cubic-bezier(0.4, 0.627035, 0.6, 0.902026);-webkit-transform:translateX(-84.386165%);transform:translateX(-84.386165%);-webkit-transform:translateX(var(--mdc-linear-progress-secondary-half-neg, -84.386165%));transform:translateX(var(--mdc-linear-progress-secondary-half-neg, -84.386165%))}100%{-webkit-transform:translateX(-160.277782%);transform:translateX(-160.277782%);-webkit-transform:translateX(var(--mdc-linear-progress-secondary-full-neg, -160.277782%));transform:translateX(var(--mdc-linear-progress-secondary-full-neg, -160.277782%))}}@keyframes mdc-linear-progress-secondary-indeterminate-translate-reverse{0%{-webkit-animation-timing-function:cubic-bezier(0.15, 0, 0.515058, 0.409685);animation-timing-function:cubic-bezier(0.15, 0, 0.515058, 0.409685);-webkit-transform:translateX(0);transform:translateX(0)}25%{-webkit-animation-timing-function:cubic-bezier(0.31033, 0.284058, 0.8, 0.733712);animation-timing-function:cubic-bezier(0.31033, 0.284058, 0.8, 0.733712);-webkit-transform:translateX(-37.651913%);transform:translateX(-37.651913%);-webkit-transform:translateX(var(--mdc-linear-progress-secondary-quarter-neg, -37.651913%));transform:translateX(var(--mdc-linear-progress-secondary-quarter-neg, -37.651913%))}48.35%{-webkit-animation-timing-function:cubic-bezier(0.4, 0.627035, 0.6, 0.902026);animation-timing-function:cubic-bezier(0.4, 0.627035, 0.6, 0.902026);-webkit-transform:translateX(-84.386165%);transform:translateX(-84.386165%);-webkit-transform:translateX(var(--mdc-linear-progress-secondary-half-neg, -84.386165%));transform:translateX(var(--mdc-linear-progress-secondary-half-neg, -84.386165%))}100%{-webkit-transform:translateX(-160.277782%);transform:translateX(-160.277782%);-webkit-transform:translateX(var(--mdc-linear-progress-secondary-full-neg, -160.277782%));transform:translateX(var(--mdc-linear-progress-secondary-full-neg, -160.277782%))}}@-webkit-keyframes mdc-linear-progress-buffering-reverse{from{-webkit-transform:translateX(-10px);transform:translateX(-10px)}}@keyframes mdc-linear-progress-buffering-reverse{from{-webkit-transform:translateX(-10px);transform:translateX(-10px)}}.mdc-linear-progress{position:relative;width:100%;height:4px;-webkit-transform:translateZ(0);transform:translateZ(0);outline:1px solid transparent;overflow:hidden;transition:opacity 250ms 0ms cubic-bezier(0.4, 0, 0.6, 1)}.mdc-linear-progress__bar{position:absolute;width:100%;height:100%;-webkit-animation:none;animation:none;-webkit-transform-origin:top left;transform-origin:top left;transition:-webkit-transform 250ms 0ms cubic-bezier(0.4, 0, 0.6, 1);transition:transform 250ms 0ms cubic-bezier(0.4, 0, 0.6, 1);transition:transform 250ms 0ms cubic-bezier(0.4, 0, 0.6, 1), -webkit-transform 250ms 0ms cubic-bezier(0.4, 0, 0.6, 1)}.mdc-linear-progress__bar-inner{display:inline-block;position:absolute;width:100%;-webkit-animation:none;animation:none;border-top:4px solid}.mdc-linear-progress__buffer{display:flex;position:absolute;width:100%;height:100%}.mdc-linear-progress__buffer-dots{background-repeat:repeat-x;background-size:10px 4px;flex:auto;-webkit-transform:rotate(180deg);transform:rotate(180deg);-webkit-animation:mdc-linear-progress-buffering 250ms infinite linear;animation:mdc-linear-progress-buffering 250ms infinite linear}.mdc-linear-progress__buffer-bar{flex:0 1 100%;transition:flex-basis 250ms 0ms cubic-bezier(0.4, 0, 0.6, 1)}.mdc-linear-progress__primary-bar{-webkit-transform:scaleX(0);transform:scaleX(0)}.mdc-linear-progress__secondary-bar{display:none}.mdc-linear-progress--indeterminate .mdc-linear-progress__bar{transition:none}.mdc-linear-progress--indeterminate .mdc-linear-progress__primary-bar{left:-145.166611%}.mdc-linear-progress--indeterminate .mdc-linear-progress__secondary-bar{left:-54.888891%;display:block}.mdc-linear-progress--indeterminate.mdc-linear-progress--animation-ready .mdc-linear-progress__primary-bar{-webkit-animation:mdc-linear-progress-primary-indeterminate-translate 2s infinite linear;animation:mdc-linear-progress-primary-indeterminate-translate 2s infinite linear}.mdc-linear-progress--indeterminate.mdc-linear-progress--animation-ready .mdc-linear-progress__primary-bar>.mdc-linear-progress__bar-inner{-webkit-animation:mdc-linear-progress-primary-indeterminate-scale 2s infinite linear;animation:mdc-linear-progress-primary-indeterminate-scale 2s infinite linear}.mdc-linear-progress--indeterminate.mdc-linear-progress--animation-ready .mdc-linear-progress__secondary-bar{-webkit-animation:mdc-linear-progress-secondary-indeterminate-translate 2s infinite linear;animation:mdc-linear-progress-secondary-indeterminate-translate 2s infinite linear}.mdc-linear-progress--indeterminate.mdc-linear-progress--animation-ready .mdc-linear-progress__secondary-bar>.mdc-linear-progress__bar-inner{-webkit-animation:mdc-linear-progress-secondary-indeterminate-scale 2s infinite linear;animation:mdc-linear-progress-secondary-indeterminate-scale 2s infinite linear}[dir=rtl] .mdc-linear-progress:not([dir=ltr]) .mdc-linear-progress__bar,.mdc-linear-progress[dir=rtl]:not([dir=ltr]) .mdc-linear-progress__bar{right:0;-webkit-transform-origin:center right;transform-origin:center right}[dir=rtl] .mdc-linear-progress:not([dir=ltr]).mdc-linear-progress--animation-ready .mdc-linear-progress__primary-bar,.mdc-linear-progress[dir=rtl]:not([dir=ltr]).mdc-linear-progress--animation-ready .mdc-linear-progress__primary-bar{-webkit-animation-name:mdc-linear-progress-primary-indeterminate-translate-reverse;animation-name:mdc-linear-progress-primary-indeterminate-translate-reverse}[dir=rtl] .mdc-linear-progress:not([dir=ltr]).mdc-linear-progress--animation-ready .mdc-linear-progress__secondary-bar,.mdc-linear-progress[dir=rtl]:not([dir=ltr]).mdc-linear-progress--animation-ready .mdc-linear-progress__secondary-bar{-webkit-animation-name:mdc-linear-progress-secondary-indeterminate-translate-reverse;animation-name:mdc-linear-progress-secondary-indeterminate-translate-reverse}[dir=rtl] .mdc-linear-progress:not([dir=ltr]) .mdc-linear-progress__buffer-dots,.mdc-linear-progress[dir=rtl]:not([dir=ltr]) .mdc-linear-progress__buffer-dots{-webkit-animation:mdc-linear-progress-buffering-reverse 250ms infinite linear;animation:mdc-linear-progress-buffering-reverse 250ms infinite linear;-webkit-transform:rotate(0);transform:rotate(0)}[dir=rtl] .mdc-linear-progress:not([dir=ltr]).mdc-linear-progress--indeterminate .mdc-linear-progress__primary-bar,.mdc-linear-progress[dir=rtl]:not([dir=ltr]).mdc-linear-progress--indeterminate .mdc-linear-progress__primary-bar{right:-145.166611%;left:auto}[dir=rtl] .mdc-linear-progress:not([dir=ltr]).mdc-linear-progress--indeterminate .mdc-linear-progress__secondary-bar,.mdc-linear-progress[dir=rtl]:not([dir=ltr]).mdc-linear-progress--indeterminate .mdc-linear-progress__secondary-bar{right:-54.888891%;left:auto}.mdc-linear-progress--closed{opacity:0}.mdc-linear-progress--closed-animation-off .mdc-linear-progress__buffer-dots{-webkit-animation:none;animation:none}.mdc-linear-progress--closed-animation-off.mdc-linear-progress--indeterminate .mdc-linear-progress__bar,.mdc-linear-progress--closed-animation-off.mdc-linear-progress--indeterminate .mdc-linear-progress__bar .mdc-linear-progress__bar-inner{-webkit-animation:none;animation:none}.mdc-linear-progress__bar-inner{border-color:#6200ee;border-color:var(--mdc-theme-primary, #6200ee)}.mdc-linear-progress__buffer-dots{background-image:url("data:image/svg+xml,%3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' enable-background='new 0 0 5 2' xml:space='preserve' viewBox='0 0 5 2' preserveAspectRatio='none slice'%3E%3Ccircle cx='1' cy='1' r='1' fill='%23e6e6e6'/%3E%3C/svg%3E")}.mdc-linear-progress__buffer-bar{background-color:#e6e6e6}.mdc-deprecated-list{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-subtitle1-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:1rem;font-size:var(--mdc-typography-subtitle1-font-size, 1rem);line-height:1.75rem;line-height:var(--mdc-typography-subtitle1-line-height, 1.75rem);font-weight:400;font-weight:var(--mdc-typography-subtitle1-font-weight, 400);letter-spacing:0.009375em;letter-spacing:var(--mdc-typography-subtitle1-letter-spacing, 0.009375em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-subtitle1-text-decoration, inherit);text-decoration:var(--mdc-typography-subtitle1-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-subtitle1-text-transform, inherit);line-height:1.5rem;margin:0;padding:8px 0;list-style-type:none;color:rgba(0, 0, 0, 0.87);color:var(--mdc-theme-text-primary-on-background, rgba(0, 0, 0, 0.87))}.mdc-deprecated-list:focus{outline:none}.mdc-deprecated-list-item{height:48px}.mdc-deprecated-list-item__secondary-text{color:rgba(0, 0, 0, 0.54);color:var(--mdc-theme-text-secondary-on-background, rgba(0, 0, 0, 0.54))}.mdc-deprecated-list-item__graphic{background-color:transparent}.mdc-deprecated-list-item__graphic{color:rgba(0, 0, 0, 0.38);color:var(--mdc-theme-text-icon-on-background, rgba(0, 0, 0, 0.38))}.mdc-deprecated-list-item__meta{color:rgba(0, 0, 0, 0.38);color:var(--mdc-theme-text-hint-on-background, rgba(0, 0, 0, 0.38))}.mdc-deprecated-list-group__subheader{color:rgba(0, 0, 0, 0.87);color:var(--mdc-theme-text-primary-on-background, rgba(0, 0, 0, 0.87))}.mdc-deprecated-list-item--disabled .mdc-deprecated-list-item__text{opacity:.38}.mdc-deprecated-list-item--disabled .mdc-deprecated-list-item__text,.mdc-deprecated-list-item--disabled .mdc-deprecated-list-item__primary-text,.mdc-deprecated-list-item--disabled .mdc-deprecated-list-item__secondary-text{color:#000;color:var(--mdc-theme-on-surface, #000)}.mdc-deprecated-list-item--selected,.mdc-deprecated-list-item--activated{color:#6200ee;color:var(--mdc-theme-primary, #6200ee)}.mdc-deprecated-list-item--selected .mdc-deprecated-list-item__graphic,.mdc-deprecated-list-item--activated .mdc-deprecated-list-item__graphic{color:#6200ee;color:var(--mdc-theme-primary, #6200ee)}.mdc-deprecated-list--dense{padding-top:4px;padding-bottom:4px;font-size:.812rem}.mdc-deprecated-list-item{display:flex;position:relative;align-items:center;justify-content:flex-start;overflow:hidden;padding:0;padding-left:16px;padding-right:16px;height:48px}.mdc-deprecated-list-item:focus{outline:none}.mdc-deprecated-list-item:not(.mdc-deprecated-list-item--selected):focus::before,.mdc-deprecated-list-item.mdc-ripple-upgraded--background-focused::before{position:absolute;box-sizing:border-box;width:100%;height:100%;top:0;left:0;border:1px solid transparent;border-radius:inherit;content:"";pointer-events:none}.mdc-deprecated-list-item.mdc-deprecated-list-item--selected::before{position:absolute;box-sizing:border-box;width:100%;height:100%;top:0;left:0;border:3px double transparent;border-radius:inherit;content:"";pointer-events:none}[dir=rtl] .mdc-deprecated-list-item,.mdc-deprecated-list-item[dir=rtl]{padding-left:16px;padding-right:16px}.mdc-deprecated-list--icon-list .mdc-deprecated-list-item{padding-left:16px;padding-right:16px;height:56px}[dir=rtl] .mdc-deprecated-list--icon-list .mdc-deprecated-list-item,.mdc-deprecated-list--icon-list .mdc-deprecated-list-item[dir=rtl]{padding-left:16px;padding-right:16px}.mdc-deprecated-list--avatar-list .mdc-deprecated-list-item{padding-left:16px;padding-right:16px;height:56px}[dir=rtl] .mdc-deprecated-list--avatar-list .mdc-deprecated-list-item,.mdc-deprecated-list--avatar-list .mdc-deprecated-list-item[dir=rtl]{padding-left:16px;padding-right:16px}.mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-item{padding-left:16px;padding-right:16px;height:56px}[dir=rtl] .mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-item,.mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-item[dir=rtl]{padding-left:16px;padding-right:16px}.mdc-deprecated-list--image-list .mdc-deprecated-list-item{padding-left:16px;padding-right:16px;height:72px}[dir=rtl] .mdc-deprecated-list--image-list .mdc-deprecated-list-item,.mdc-deprecated-list--image-list .mdc-deprecated-list-item[dir=rtl]{padding-left:16px;padding-right:16px}.mdc-deprecated-list--video-list .mdc-deprecated-list-item{padding-left:0px;padding-right:16px;height:72px}[dir=rtl] .mdc-deprecated-list--video-list .mdc-deprecated-list-item,.mdc-deprecated-list--video-list .mdc-deprecated-list-item[dir=rtl]{padding-left:16px;padding-right:0px}.mdc-deprecated-list--dense .mdc-deprecated-list-item__graphic{margin-left:0;margin-right:16px;width:20px;height:20px}[dir=rtl] .mdc-deprecated-list--dense .mdc-deprecated-list-item__graphic,.mdc-deprecated-list--dense .mdc-deprecated-list-item__graphic[dir=rtl]{margin-left:16px;margin-right:0}.mdc-deprecated-list-item__graphic{flex-shrink:0;align-items:center;justify-content:center;fill:currentColor;-o-object-fit:cover;object-fit:cover;margin-left:0;margin-right:32px;width:24px;height:24px}[dir=rtl] .mdc-deprecated-list-item__graphic,.mdc-deprecated-list-item__graphic[dir=rtl]{margin-left:32px;margin-right:0}.mdc-deprecated-list--icon-list .mdc-deprecated-list-item__graphic{margin-left:0;margin-right:32px;width:24px;height:24px}[dir=rtl] .mdc-deprecated-list--icon-list .mdc-deprecated-list-item__graphic,.mdc-deprecated-list--icon-list .mdc-deprecated-list-item__graphic[dir=rtl]{margin-left:32px;margin-right:0}.mdc-deprecated-list--avatar-list .mdc-deprecated-list-item__graphic{margin-left:0;margin-right:16px;width:40px;height:40px;border-radius:50%}[dir=rtl] .mdc-deprecated-list--avatar-list .mdc-deprecated-list-item__graphic,.mdc-deprecated-list--avatar-list .mdc-deprecated-list-item__graphic[dir=rtl]{margin-left:16px;margin-right:0}.mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-item__graphic{margin-left:0;margin-right:16px;width:40px;height:40px}[dir=rtl] .mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-item__graphic,.mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-item__graphic[dir=rtl]{margin-left:16px;margin-right:0}.mdc-deprecated-list--image-list .mdc-deprecated-list-item__graphic{margin-left:0;margin-right:16px;width:56px;height:56px}[dir=rtl] .mdc-deprecated-list--image-list .mdc-deprecated-list-item__graphic,.mdc-deprecated-list--image-list .mdc-deprecated-list-item__graphic[dir=rtl]{margin-left:16px;margin-right:0}.mdc-deprecated-list--video-list .mdc-deprecated-list-item__graphic{margin-left:0;margin-right:16px;width:100px;height:56px}[dir=rtl] .mdc-deprecated-list--video-list .mdc-deprecated-list-item__graphic,.mdc-deprecated-list--video-list .mdc-deprecated-list-item__graphic[dir=rtl]{margin-left:16px;margin-right:0}.mdc-deprecated-list .mdc-deprecated-list-item__graphic{display:inline-flex}.mdc-deprecated-list-item__meta{margin-left:auto;margin-right:0}.mdc-deprecated-list-item__meta:not(.material-icons){-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-caption-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.75rem;font-size:var(--mdc-typography-caption-font-size, 0.75rem);line-height:1.25rem;line-height:var(--mdc-typography-caption-line-height, 1.25rem);font-weight:400;font-weight:var(--mdc-typography-caption-font-weight, 400);letter-spacing:0.0333333333em;letter-spacing:var(--mdc-typography-caption-letter-spacing, 0.0333333333em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-caption-text-decoration, inherit);text-decoration:var(--mdc-typography-caption-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-caption-text-transform, inherit)}.mdc-deprecated-list-item[dir=rtl] .mdc-deprecated-list-item__meta,[dir=rtl] .mdc-deprecated-list-item .mdc-deprecated-list-item__meta{margin-left:0;margin-right:auto}.mdc-deprecated-list-item__text{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.mdc-deprecated-list-item__text[for]{pointer-events:none}.mdc-deprecated-list-item__primary-text{text-overflow:ellipsis;white-space:nowrap;overflow:hidden;display:block;margin-top:0;line-height:normal;margin-bottom:-20px}.mdc-deprecated-list-item__primary-text::before{display:inline-block;width:0;height:28px;content:"";vertical-align:0}.mdc-deprecated-list-item__primary-text::after{display:inline-block;width:0;height:20px;content:"";vertical-align:-20px}.mdc-deprecated-list--video-list .mdc-deprecated-list-item__primary-text,.mdc-deprecated-list--image-list .mdc-deprecated-list-item__primary-text,.mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-item__primary-text,.mdc-deprecated-list--avatar-list .mdc-deprecated-list-item__primary-text,.mdc-deprecated-list--icon-list .mdc-deprecated-list-item__primary-text{display:block;margin-top:0;line-height:normal;margin-bottom:-20px}.mdc-deprecated-list--video-list .mdc-deprecated-list-item__primary-text::before,.mdc-deprecated-list--image-list .mdc-deprecated-list-item__primary-text::before,.mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-item__primary-text::before,.mdc-deprecated-list--avatar-list .mdc-deprecated-list-item__primary-text::before,.mdc-deprecated-list--icon-list .mdc-deprecated-list-item__primary-text::before{display:inline-block;width:0;height:32px;content:"";vertical-align:0}.mdc-deprecated-list--video-list .mdc-deprecated-list-item__primary-text::after,.mdc-deprecated-list--image-list .mdc-deprecated-list-item__primary-text::after,.mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-item__primary-text::after,.mdc-deprecated-list--avatar-list .mdc-deprecated-list-item__primary-text::after,.mdc-deprecated-list--icon-list .mdc-deprecated-list-item__primary-text::after{display:inline-block;width:0;height:20px;content:"";vertical-align:-20px}.mdc-deprecated-list--dense .mdc-deprecated-list-item__primary-text{display:block;margin-top:0;line-height:normal;margin-bottom:-20px}.mdc-deprecated-list--dense .mdc-deprecated-list-item__primary-text::before{display:inline-block;width:0;height:24px;content:"";vertical-align:0}.mdc-deprecated-list--dense .mdc-deprecated-list-item__primary-text::after{display:inline-block;width:0;height:20px;content:"";vertical-align:-20px}.mdc-deprecated-list-item__secondary-text{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.875rem;font-size:var(--mdc-typography-body2-font-size, 0.875rem);line-height:1.25rem;line-height:var(--mdc-typography-body2-line-height, 1.25rem);font-weight:400;font-weight:var(--mdc-typography-body2-font-weight, 400);letter-spacing:0.0178571429em;letter-spacing:var(--mdc-typography-body2-letter-spacing, 0.0178571429em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-body2-text-transform, inherit);text-overflow:ellipsis;white-space:nowrap;overflow:hidden;display:block;margin-top:0;line-height:normal}.mdc-deprecated-list-item__secondary-text::before{display:inline-block;width:0;height:20px;content:"";vertical-align:0}.mdc-deprecated-list--dense .mdc-deprecated-list-item__secondary-text{font-size:inherit}.mdc-deprecated-list--dense .mdc-deprecated-list-item{height:40px}.mdc-deprecated-list--two-line .mdc-deprecated-list-item__text{align-self:flex-start}.mdc-deprecated-list--two-line .mdc-deprecated-list-item{height:64px}.mdc-deprecated-list--two-line.mdc-deprecated-list--video-list .mdc-deprecated-list-item,.mdc-deprecated-list--two-line.mdc-deprecated-list--image-list .mdc-deprecated-list-item,.mdc-deprecated-list--two-line.mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-item,.mdc-deprecated-list--two-line.mdc-deprecated-list--avatar-list .mdc-deprecated-list-item,.mdc-deprecated-list--two-line.mdc-deprecated-list--icon-list .mdc-deprecated-list-item{height:72px}.mdc-deprecated-list--two-line.mdc-deprecated-list--icon-list .mdc-deprecated-list-item__graphic{align-self:flex-start;margin-top:16px}.mdc-deprecated-list--two-line.mdc-deprecated-list--dense .mdc-deprecated-list-item,.mdc-deprecated-list--avatar-list.mdc-deprecated-list--dense .mdc-deprecated-list-item{height:60px}.mdc-deprecated-list--avatar-list.mdc-deprecated-list--dense .mdc-deprecated-list-item__graphic{margin-left:0;margin-right:16px;width:36px;height:36px}[dir=rtl] .mdc-deprecated-list--avatar-list.mdc-deprecated-list--dense .mdc-deprecated-list-item__graphic,.mdc-deprecated-list--avatar-list.mdc-deprecated-list--dense .mdc-deprecated-list-item__graphic[dir=rtl]{margin-left:16px;margin-right:0}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item{cursor:pointer}a.mdc-deprecated-list-item{color:inherit;text-decoration:none}.mdc-deprecated-list-divider{height:0;margin:0;border:none;border-bottom-width:1px;border-bottom-style:solid}.mdc-deprecated-list-divider{border-bottom-color:rgba(0, 0, 0, 0.12)}.mdc-deprecated-list-divider--padded{margin-left:16px;margin-right:0;width:calc(100% - 32px)}[dir=rtl] .mdc-deprecated-list-divider--padded,.mdc-deprecated-list-divider--padded[dir=rtl]{margin-left:0;margin-right:16px}.mdc-deprecated-list-divider--inset{margin-left:72px;margin-right:0;width:calc(100% - 72px)}[dir=rtl] .mdc-deprecated-list-divider--inset,.mdc-deprecated-list-divider--inset[dir=rtl]{margin-left:0;margin-right:72px}.mdc-deprecated-list-divider--inset.mdc-deprecated-list-divider--padded{margin-left:72px;margin-right:0;width:calc(100% - 88px)}[dir=rtl] .mdc-deprecated-list-divider--inset.mdc-deprecated-list-divider--padded,.mdc-deprecated-list-divider--inset.mdc-deprecated-list-divider--padded[dir=rtl]{margin-left:0;margin-right:72px}.mdc-deprecated-list .mdc-deprecated-list-divider--inset-leading{margin-left:16px;margin-right:0;width:calc(100% - 16px)}[dir=rtl] .mdc-deprecated-list .mdc-deprecated-list-divider--inset-leading,.mdc-deprecated-list .mdc-deprecated-list-divider--inset-leading[dir=rtl]{margin-left:0;margin-right:16px}.mdc-deprecated-list .mdc-deprecated-list-divider--inset-trailing{width:calc(100% - 16px)}.mdc-deprecated-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing{margin-left:16px;margin-right:0;width:calc(100% - 32px)}[dir=rtl] .mdc-deprecated-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing,.mdc-deprecated-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing[dir=rtl]{margin-left:0;margin-right:16px}.mdc-deprecated-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding{margin-left:16px;margin-right:0;width:calc(100% - 16px)}[dir=rtl] .mdc-deprecated-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding,.mdc-deprecated-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding[dir=rtl]{margin-left:0;margin-right:16px}.mdc-deprecated-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding{margin-left:16px;margin-right:0;width:calc(100% - 32px)}[dir=rtl] .mdc-deprecated-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding,.mdc-deprecated-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding[dir=rtl]{margin-left:0;margin-right:16px}.mdc-deprecated-list--icon-list .mdc-deprecated-list-divider--inset-leading{margin-left:72px;margin-right:0;width:calc(100% - 72px)}[dir=rtl] .mdc-deprecated-list--icon-list .mdc-deprecated-list-divider--inset-leading,.mdc-deprecated-list--icon-list .mdc-deprecated-list-divider--inset-leading[dir=rtl]{margin-left:0;margin-right:72px}.mdc-deprecated-list--icon-list .mdc-deprecated-list-divider--inset-trailing{width:calc(100% - 16px)}.mdc-deprecated-list--icon-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing{margin-left:72px;margin-right:0;width:calc(100% - 88px)}[dir=rtl] .mdc-deprecated-list--icon-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing,.mdc-deprecated-list--icon-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing[dir=rtl]{margin-left:0;margin-right:72px}.mdc-deprecated-list--icon-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding{margin-left:16px;margin-right:0;width:calc(100% - 16px)}[dir=rtl] .mdc-deprecated-list--icon-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding,.mdc-deprecated-list--icon-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding[dir=rtl]{margin-left:0;margin-right:16px}.mdc-deprecated-list--icon-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding{margin-left:16px;margin-right:0;width:calc(100% - 32px)}[dir=rtl] .mdc-deprecated-list--icon-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding,.mdc-deprecated-list--icon-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding[dir=rtl]{margin-left:0;margin-right:16px}.mdc-deprecated-list--avatar-list .mdc-deprecated-list-divider--inset-leading{margin-left:72px;margin-right:0;width:calc(100% - 72px)}[dir=rtl] .mdc-deprecated-list--avatar-list .mdc-deprecated-list-divider--inset-leading,.mdc-deprecated-list--avatar-list .mdc-deprecated-list-divider--inset-leading[dir=rtl]{margin-left:0;margin-right:72px}.mdc-deprecated-list--avatar-list .mdc-deprecated-list-divider--inset-trailing{width:calc(100% - 16px)}.mdc-deprecated-list--avatar-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing{margin-left:72px;margin-right:0;width:calc(100% - 88px)}[dir=rtl] .mdc-deprecated-list--avatar-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing,.mdc-deprecated-list--avatar-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing[dir=rtl]{margin-left:0;margin-right:72px}.mdc-deprecated-list--avatar-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding{margin-left:16px;margin-right:0;width:calc(100% - 16px)}[dir=rtl] .mdc-deprecated-list--avatar-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding,.mdc-deprecated-list--avatar-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding[dir=rtl]{margin-left:0;margin-right:16px}.mdc-deprecated-list--avatar-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding{margin-left:16px;margin-right:0;width:calc(100% - 32px)}[dir=rtl] .mdc-deprecated-list--avatar-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding,.mdc-deprecated-list--avatar-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding[dir=rtl]{margin-left:0;margin-right:16px}.mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-divider--inset-leading{margin-left:72px;margin-right:0;width:calc(100% - 72px)}[dir=rtl] .mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-divider--inset-leading,.mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-divider--inset-leading[dir=rtl]{margin-left:0;margin-right:72px}.mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-divider--inset-trailing{width:calc(100% - 16px)}.mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing{margin-left:72px;margin-right:0;width:calc(100% - 88px)}[dir=rtl] .mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing,.mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing[dir=rtl]{margin-left:0;margin-right:72px}.mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding{margin-left:16px;margin-right:0;width:calc(100% - 16px)}[dir=rtl] .mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding,.mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding[dir=rtl]{margin-left:0;margin-right:16px}.mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding{margin-left:16px;margin-right:0;width:calc(100% - 32px)}[dir=rtl] .mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding,.mdc-deprecated-list--thumbnail-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding[dir=rtl]{margin-left:0;margin-right:16px}.mdc-deprecated-list--image-list .mdc-deprecated-list-divider--inset-leading{margin-left:88px;margin-right:0;width:calc(100% - 88px)}[dir=rtl] .mdc-deprecated-list--image-list .mdc-deprecated-list-divider--inset-leading,.mdc-deprecated-list--image-list .mdc-deprecated-list-divider--inset-leading[dir=rtl]{margin-left:0;margin-right:88px}.mdc-deprecated-list--image-list .mdc-deprecated-list-divider--inset-trailing{width:calc(100% - 16px)}.mdc-deprecated-list--image-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing{margin-left:88px;margin-right:0;width:calc(100% - 104px)}[dir=rtl] .mdc-deprecated-list--image-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing,.mdc-deprecated-list--image-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing[dir=rtl]{margin-left:0;margin-right:88px}.mdc-deprecated-list--image-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding{margin-left:16px;margin-right:0;width:calc(100% - 16px)}[dir=rtl] .mdc-deprecated-list--image-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding,.mdc-deprecated-list--image-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding[dir=rtl]{margin-left:0;margin-right:16px}.mdc-deprecated-list--image-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding{margin-left:16px;margin-right:0;width:calc(100% - 32px)}[dir=rtl] .mdc-deprecated-list--image-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding,.mdc-deprecated-list--image-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding[dir=rtl]{margin-left:0;margin-right:16px}.mdc-deprecated-list--video-list .mdc-deprecated-list-divider--inset-leading{margin-left:116px;margin-right:0;width:calc(100% - 116px)}[dir=rtl] .mdc-deprecated-list--video-list .mdc-deprecated-list-divider--inset-leading,.mdc-deprecated-list--video-list .mdc-deprecated-list-divider--inset-leading[dir=rtl]{margin-left:0;margin-right:116px}.mdc-deprecated-list--video-list .mdc-deprecated-list-divider--inset-trailing{width:calc(100% - 16px)}.mdc-deprecated-list--video-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing{margin-left:116px;margin-right:0;width:calc(100% - 132px)}[dir=rtl] .mdc-deprecated-list--video-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing,.mdc-deprecated-list--video-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing[dir=rtl]{margin-left:0;margin-right:116px}.mdc-deprecated-list--video-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding{margin-left:0px;margin-right:0;width:calc(100% - 0px)}[dir=rtl] .mdc-deprecated-list--video-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding,.mdc-deprecated-list--video-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--padding[dir=rtl]{margin-left:0;margin-right:0px}.mdc-deprecated-list--video-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding{margin-left:0px;margin-right:0;width:calc(100% - 16px)}[dir=rtl] .mdc-deprecated-list--video-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding,.mdc-deprecated-list--video-list .mdc-deprecated-list-divider--inset-leading.mdc-deprecated-list-divider--inset-trailing.mdc-deprecated-list-divider--inset-padding[dir=rtl]{margin-left:0;margin-right:0px}.mdc-deprecated-list-group .mdc-deprecated-list{padding:0}.mdc-deprecated-list-group__subheader{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-subtitle1-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:1rem;font-size:var(--mdc-typography-subtitle1-font-size, 1rem);line-height:1.75rem;line-height:var(--mdc-typography-subtitle1-line-height, 1.75rem);font-weight:400;font-weight:var(--mdc-typography-subtitle1-font-weight, 400);letter-spacing:0.009375em;letter-spacing:var(--mdc-typography-subtitle1-letter-spacing, 0.009375em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-subtitle1-text-decoration, inherit);text-decoration:var(--mdc-typography-subtitle1-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-subtitle1-text-transform, inherit);margin:calc( (3rem - 1.5rem) / 2 ) 16px}.mdc-list-item__primary-text{color:rgba(0, 0, 0, 0.87);color:var(--mdc-theme-text-primary-on-background, rgba(0, 0, 0, 0.87))}.mdc-list-item__secondary-text{color:rgba(0, 0, 0, 0.54);color:var(--mdc-theme-text-secondary-on-background, rgba(0, 0, 0, 0.54))}.mdc-list-item__overline-text{color:rgba(0, 0, 0, 0.38);color:var(--mdc-theme-text-hint-on-background, rgba(0, 0, 0, 0.38))}.mdc-list-item--with-leading-icon .mdc-list-item__start,.mdc-list-item--with-trailing-icon .mdc-list-item__end{background-color:transparent}.mdc-list-item--with-leading-icon .mdc-list-item__start,.mdc-list-item--with-trailing-icon .mdc-list-item__end{color:rgba(0, 0, 0, 0.38);color:var(--mdc-theme-text-icon-on-background, rgba(0, 0, 0, 0.38))}.mdc-list-item__end{color:rgba(0, 0, 0, 0.38);color:var(--mdc-theme-text-hint-on-background, rgba(0, 0, 0, 0.38))}.mdc-list-item--disabled .mdc-list-item__start,.mdc-list-item--disabled .mdc-list-item__content,.mdc-list-item--disabled .mdc-list-item__end{opacity:.38}.mdc-list-item--disabled .mdc-list-item__primary-text{color:#000;color:var(--mdc-theme-on-surface, #000)}.mdc-list-item--disabled .mdc-list-item__secondary-text{color:#000;color:var(--mdc-theme-on-surface, #000)}.mdc-list-item--disabled .mdc-list-item__overline-text{color:#000;color:var(--mdc-theme-on-surface, #000)}.mdc-list-item--disabled.mdc-list-item--with-leading-icon .mdc-list-item__start{color:#000;color:var(--mdc-theme-on-surface, #000)}.mdc-list-item--disabled.mdc-list-item--with-trailing-icon .mdc-list-item__end{color:#000;color:var(--mdc-theme-on-surface, #000)}.mdc-list-item--disabled.mdc-list-item--with-trailing-meta .mdc-list-item__end{color:#000;color:var(--mdc-theme-on-surface, #000)}.mdc-list-item--selected .mdc-list-item__primary-text,.mdc-list-item--activated .mdc-list-item__primary-text{color:#6200ee;color:var(--mdc-theme-primary, #6200ee)}.mdc-list-item--selected.mdc-list-item--with-leading-icon .mdc-list-item__start,.mdc-list-item--activated.mdc-list-item--with-leading-icon .mdc-list-item__start{color:#6200ee;color:var(--mdc-theme-primary, #6200ee)}.mdc-deprecated-list-group__subheader{color:rgba(0, 0, 0, 0.87);color:var(--mdc-theme-text-primary-on-background, rgba(0, 0, 0, 0.87))}@media screen and (forced-colors: active),(-ms-high-contrast: active){.mdc-list-divider::after{content:"";display:block;border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:white}}.mdc-list{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-subtitle1-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:1rem;font-size:var(--mdc-typography-subtitle1-font-size, 1rem);line-height:1.75rem;line-height:var(--mdc-typography-subtitle1-line-height, 1.75rem);font-weight:400;font-weight:var(--mdc-typography-subtitle1-font-weight, 400);letter-spacing:0.009375em;letter-spacing:var(--mdc-typography-subtitle1-letter-spacing, 0.009375em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-subtitle1-text-decoration, inherit);text-decoration:var(--mdc-typography-subtitle1-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-subtitle1-text-transform, inherit);line-height:1.5rem;margin:0;padding:8px 0;list-style-type:none}.mdc-list:focus{outline:none}.mdc-list-item{display:flex;position:relative;align-items:center;justify-content:flex-start;overflow:hidden;padding:0;align-items:stretch;cursor:pointer}.mdc-list-item:focus{outline:none}.mdc-list-item.mdc-list-item--with-one-line{height:48px}.mdc-list-item.mdc-list-item--with-two-lines{height:64px}.mdc-list-item.mdc-list-item--with-three-lines{height:88px}.mdc-list-item.mdc-list-item--with-one-line .mdc-list-item__start{align-self:center;margin-top:0}.mdc-list-item.mdc-list-item--with-two-lines .mdc-list-item__start{align-self:flex-start;margin-top:16px}.mdc-list-item.mdc-list-item--with-three-lines .mdc-list-item__start{align-self:flex-start;margin-top:16px}.mdc-list-item.mdc-list-item--with-one-line .mdc-list-item__end{align-self:center;margin-top:0}.mdc-list-item.mdc-list-item--with-two-lines .mdc-list-item__end{align-self:center;margin-top:0}.mdc-list-item.mdc-list-item--with-three-lines .mdc-list-item__end{align-self:flex-start;margin-top:16px}.mdc-list-item.mdc-list-item--disabled,.mdc-list-item.mdc-list-item--non-interactive{cursor:auto}.mdc-list-item:not(.mdc-list-item--selected):focus::before,.mdc-list-item.mdc-ripple-upgraded--background-focused::before{position:absolute;box-sizing:border-box;width:100%;height:100%;top:0;left:0;border:1px solid transparent;border-radius:inherit;content:"";pointer-events:none}.mdc-list-item.mdc-list-item--selected::before{position:absolute;box-sizing:border-box;width:100%;height:100%;top:0;left:0;border:3px double transparent;border-radius:inherit;content:"";pointer-events:none}.mdc-list-item.mdc-list-item--selected:focus::before{position:absolute;box-sizing:border-box;width:100%;height:100%;top:0;left:0;border:3px solid transparent;border-radius:inherit;content:"";pointer-events:none}a.mdc-list-item{color:inherit;text-decoration:none}.mdc-list-item__start{fill:currentColor;flex-shrink:0;pointer-events:none}.mdc-list-item__end{flex-shrink:0;pointer-events:none}.mdc-list-item__content{text-overflow:ellipsis;white-space:nowrap;overflow:hidden;align-self:center;flex:1;pointer-events:none}.mdc-list-item--with-two-lines .mdc-list-item__content,.mdc-list-item--with-three-lines .mdc-list-item__content{align-self:stretch}.mdc-list-item__content[for]{pointer-events:none}.mdc-list-item__primary-text{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-subtitle1-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:1rem;font-size:var(--mdc-typography-subtitle1-font-size, 1rem);line-height:1.75rem;line-height:var(--mdc-typography-subtitle1-line-height, 1.75rem);font-weight:400;font-weight:var(--mdc-typography-subtitle1-font-weight, 400);letter-spacing:0.009375em;letter-spacing:var(--mdc-typography-subtitle1-letter-spacing, 0.009375em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-subtitle1-text-decoration, inherit);text-decoration:var(--mdc-typography-subtitle1-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-subtitle1-text-transform, inherit);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.mdc-list-item--with-two-lines .mdc-list-item__primary-text,.mdc-list-item--with-three-lines .mdc-list-item__primary-text{display:block;margin-top:0;line-height:normal;margin-bottom:-20px}.mdc-list-item--with-two-lines .mdc-list-item__primary-text::before,.mdc-list-item--with-three-lines .mdc-list-item__primary-text::before{display:inline-block;width:0;height:28px;content:"";vertical-align:0}.mdc-list-item--with-two-lines .mdc-list-item__primary-text::after,.mdc-list-item--with-three-lines .mdc-list-item__primary-text::after{display:inline-block;width:0;height:20px;content:"";vertical-align:-20px}.mdc-list-item__secondary-text{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.875rem;font-size:var(--mdc-typography-body2-font-size, 0.875rem);line-height:1.25rem;line-height:var(--mdc-typography-body2-line-height, 1.25rem);font-weight:400;font-weight:var(--mdc-typography-body2-font-weight, 400);letter-spacing:0.0178571429em;letter-spacing:var(--mdc-typography-body2-letter-spacing, 0.0178571429em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-body2-text-transform, inherit);text-overflow:ellipsis;white-space:nowrap;overflow:hidden;display:block;margin-top:0;line-height:normal}.mdc-list-item__secondary-text::before{display:inline-block;width:0;height:20px;content:"";vertical-align:0}.mdc-list-item--with-three-lines .mdc-list-item__secondary-text{white-space:normal;line-height:20px}.mdc-list-item--with-overline .mdc-list-item__secondary-text{white-space:nowrap;line-height:auto}.mdc-list-item__overline-text{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-overline-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.75rem;font-size:var(--mdc-typography-overline-font-size, 0.75rem);line-height:2rem;line-height:var(--mdc-typography-overline-line-height, 2rem);font-weight:500;font-weight:var(--mdc-typography-overline-font-weight, 500);letter-spacing:0.1666666667em;letter-spacing:var(--mdc-typography-overline-letter-spacing, 0.1666666667em);text-decoration:none;-webkit-text-decoration:var(--mdc-typography-overline-text-decoration, none);text-decoration:var(--mdc-typography-overline-text-decoration, none);text-transform:uppercase;text-transform:var(--mdc-typography-overline-text-transform, uppercase);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.mdc-list-item--with-two-lines .mdc-list-item__overline-text{display:block;margin-top:0;line-height:normal;margin-bottom:-20px}.mdc-list-item--with-two-lines .mdc-list-item__overline-text::before{display:inline-block;width:0;height:24px;content:"";vertical-align:0}.mdc-list-item--with-two-lines .mdc-list-item__overline-text::after{display:inline-block;width:0;height:20px;content:"";vertical-align:-20px}.mdc-list-item--with-three-lines .mdc-list-item__overline-text{display:block;margin-top:0;line-height:normal;margin-bottom:-20px}.mdc-list-item--with-three-lines .mdc-list-item__overline-text::before{display:inline-block;width:0;height:28px;content:"";vertical-align:0}.mdc-list-item--with-three-lines .mdc-list-item__overline-text::after{display:inline-block;width:0;height:20px;content:"";vertical-align:-20px}.mdc-list-item--with-leading-avatar.mdc-list-item{padding-left:0;padding-right:auto}[dir=rtl] .mdc-list-item--with-leading-avatar.mdc-list-item,.mdc-list-item--with-leading-avatar.mdc-list-item[dir=rtl]{padding-left:auto;padding-right:0}.mdc-list-item--with-leading-avatar .mdc-list-item__start{margin-left:16px;margin-right:16px}[dir=rtl] .mdc-list-item--with-leading-avatar .mdc-list-item__start,.mdc-list-item--with-leading-avatar .mdc-list-item__start[dir=rtl]{margin-left:16px;margin-right:16px}.mdc-list-item--with-leading-avatar .mdc-list-item__start{width:40px;height:40px}.mdc-list-item--with-leading-avatar.mdc-list-item--with-two-lines .mdc-list-item__primary-text{display:block;margin-top:0;line-height:normal;margin-bottom:-20px}.mdc-list-item--with-leading-avatar.mdc-list-item--with-two-lines .mdc-list-item__primary-text::before{display:inline-block;width:0;height:32px;content:"";vertical-align:0}.mdc-list-item--with-leading-avatar.mdc-list-item--with-two-lines .mdc-list-item__primary-text::after{display:inline-block;width:0;height:20px;content:"";vertical-align:-20px}.mdc-list-item--with-leading-avatar.mdc-list-item--with-two-lines .mdc-list-item__overline-text{display:block;margin-top:0;line-height:normal;margin-bottom:-20px}.mdc-list-item--with-leading-avatar.mdc-list-item--with-two-lines .mdc-list-item__overline-text::before{display:inline-block;width:0;height:28px;content:"";vertical-align:0}.mdc-list-item--with-leading-avatar.mdc-list-item--with-two-lines .mdc-list-item__overline-text::after{display:inline-block;width:0;height:20px;content:"";vertical-align:-20px}.mdc-list-item--with-leading-avatar.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end{display:block;margin-top:0;line-height:normal}.mdc-list-item--with-leading-avatar.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end::before{display:inline-block;width:0;height:32px;content:"";vertical-align:0}.mdc-list-item--with-leading-avatar.mdc-list-item--with-one-line{height:56px}.mdc-list-item--with-leading-avatar.mdc-list-item--with-two-lines{height:72px}.mdc-list-item--with-leading-avatar .mdc-list-item__start{border-radius:50%}.mdc-list-item--with-leading-icon .mdc-list-item__start{width:24px;height:24px}.mdc-list-item--with-leading-icon.mdc-list-item{padding-left:0;padding-right:auto}[dir=rtl] .mdc-list-item--with-leading-icon.mdc-list-item,.mdc-list-item--with-leading-icon.mdc-list-item[dir=rtl]{padding-left:auto;padding-right:0}.mdc-list-item--with-leading-icon .mdc-list-item__start{margin-left:16px;margin-right:32px}[dir=rtl] .mdc-list-item--with-leading-icon .mdc-list-item__start,.mdc-list-item--with-leading-icon .mdc-list-item__start[dir=rtl]{margin-left:32px;margin-right:16px}.mdc-list-item--with-leading-icon.mdc-list-item--with-two-lines .mdc-list-item__primary-text{display:block;margin-top:0;line-height:normal;margin-bottom:-20px}.mdc-list-item--with-leading-icon.mdc-list-item--with-two-lines .mdc-list-item__primary-text::before{display:inline-block;width:0;height:32px;content:"";vertical-align:0}.mdc-list-item--with-leading-icon.mdc-list-item--with-two-lines .mdc-list-item__primary-text::after{display:inline-block;width:0;height:20px;content:"";vertical-align:-20px}.mdc-list-item--with-leading-icon.mdc-list-item--with-two-lines .mdc-list-item__overline-text{display:block;margin-top:0;line-height:normal;margin-bottom:-20px}.mdc-list-item--with-leading-icon.mdc-list-item--with-two-lines .mdc-list-item__overline-text::before{display:inline-block;width:0;height:28px;content:"";vertical-align:0}.mdc-list-item--with-leading-icon.mdc-list-item--with-two-lines .mdc-list-item__overline-text::after{display:inline-block;width:0;height:20px;content:"";vertical-align:-20px}.mdc-list-item--with-leading-icon.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end{display:block;margin-top:0;line-height:normal}.mdc-list-item--with-leading-icon.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end::before{display:inline-block;width:0;height:32px;content:"";vertical-align:0}.mdc-list-item--with-leading-icon.mdc-list-item--with-one-line{height:56px}.mdc-list-item--with-leading-icon.mdc-list-item--with-two-lines{height:72px}.mdc-list-item--with-leading-thumbnail.mdc-list-item{padding-left:0;padding-right:auto}[dir=rtl] .mdc-list-item--with-leading-thumbnail.mdc-list-item,.mdc-list-item--with-leading-thumbnail.mdc-list-item[dir=rtl]{padding-left:auto;padding-right:0}.mdc-list-item--with-leading-thumbnail .mdc-list-item__start{margin-left:16px;margin-right:16px}[dir=rtl] .mdc-list-item--with-leading-thumbnail .mdc-list-item__start,.mdc-list-item--with-leading-thumbnail .mdc-list-item__start[dir=rtl]{margin-left:16px;margin-right:16px}.mdc-list-item--with-leading-thumbnail .mdc-list-item__start{width:40px;height:40px}.mdc-list-item--with-leading-thumbnail.mdc-list-item--with-two-lines .mdc-list-item__primary-text{display:block;margin-top:0;line-height:normal;margin-bottom:-20px}.mdc-list-item--with-leading-thumbnail.mdc-list-item--with-two-lines .mdc-list-item__primary-text::before{display:inline-block;width:0;height:32px;content:"";vertical-align:0}.mdc-list-item--with-leading-thumbnail.mdc-list-item--with-two-lines .mdc-list-item__primary-text::after{display:inline-block;width:0;height:20px;content:"";vertical-align:-20px}.mdc-list-item--with-leading-thumbnail.mdc-list-item--with-two-lines .mdc-list-item__overline-text{display:block;margin-top:0;line-height:normal;margin-bottom:-20px}.mdc-list-item--with-leading-thumbnail.mdc-list-item--with-two-lines .mdc-list-item__overline-text::before{display:inline-block;width:0;height:28px;content:"";vertical-align:0}.mdc-list-item--with-leading-thumbnail.mdc-list-item--with-two-lines .mdc-list-item__overline-text::after{display:inline-block;width:0;height:20px;content:"";vertical-align:-20px}.mdc-list-item--with-leading-thumbnail.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end{display:block;margin-top:0;line-height:normal}.mdc-list-item--with-leading-thumbnail.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end::before{display:inline-block;width:0;height:32px;content:"";vertical-align:0}.mdc-list-item--with-leading-thumbnail.mdc-list-item--with-one-line{height:56px}.mdc-list-item--with-leading-thumbnail.mdc-list-item--with-two-lines{height:72px}.mdc-list-item--with-leading-image.mdc-list-item{padding-left:0;padding-right:auto}[dir=rtl] .mdc-list-item--with-leading-image.mdc-list-item,.mdc-list-item--with-leading-image.mdc-list-item[dir=rtl]{padding-left:auto;padding-right:0}.mdc-list-item--with-leading-image .mdc-list-item__start{margin-left:16px;margin-right:16px}[dir=rtl] .mdc-list-item--with-leading-image .mdc-list-item__start,.mdc-list-item--with-leading-image .mdc-list-item__start[dir=rtl]{margin-left:16px;margin-right:16px}.mdc-list-item--with-leading-image .mdc-list-item__start{width:56px;height:56px}.mdc-list-item--with-leading-image.mdc-list-item--with-two-lines .mdc-list-item__primary-text{display:block;margin-top:0;line-height:normal;margin-bottom:-20px}.mdc-list-item--with-leading-image.mdc-list-item--with-two-lines .mdc-list-item__primary-text::before{display:inline-block;width:0;height:32px;content:"";vertical-align:0}.mdc-list-item--with-leading-image.mdc-list-item--with-two-lines .mdc-list-item__primary-text::after{display:inline-block;width:0;height:20px;content:"";vertical-align:-20px}.mdc-list-item--with-leading-image.mdc-list-item--with-two-lines .mdc-list-item__overline-text{display:block;margin-top:0;line-height:normal;margin-bottom:-20px}.mdc-list-item--with-leading-image.mdc-list-item--with-two-lines .mdc-list-item__overline-text::before{display:inline-block;width:0;height:28px;content:"";vertical-align:0}.mdc-list-item--with-leading-image.mdc-list-item--with-two-lines .mdc-list-item__overline-text::after{display:inline-block;width:0;height:20px;content:"";vertical-align:-20px}.mdc-list-item--with-leading-image.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end{display:block;margin-top:0;line-height:normal}.mdc-list-item--with-leading-image.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end::before{display:inline-block;width:0;height:32px;content:"";vertical-align:0}.mdc-list-item--with-leading-image.mdc-list-item--with-one-line{height:72px}.mdc-list-item--with-leading-image.mdc-list-item--with-two-lines{height:72px}.mdc-list-item--with-leading-video.mdc-list-item--with-two-lines .mdc-list-item__start{align-self:flex-start;margin-top:8px}.mdc-list-item--with-leading-video.mdc-list-item{padding-left:0;padding-right:auto}[dir=rtl] .mdc-list-item--with-leading-video.mdc-list-item,.mdc-list-item--with-leading-video.mdc-list-item[dir=rtl]{padding-left:auto;padding-right:0}.mdc-list-item--with-leading-video .mdc-list-item__start{margin-left:0;margin-right:16px}[dir=rtl] .mdc-list-item--with-leading-video .mdc-list-item__start,.mdc-list-item--with-leading-video .mdc-list-item__start[dir=rtl]{margin-left:16px;margin-right:0}.mdc-list-item--with-leading-video .mdc-list-item__start{width:100px;height:56px}.mdc-list-item--with-leading-video.mdc-list-item--with-two-lines .mdc-list-item__primary-text{display:block;margin-top:0;line-height:normal;margin-bottom:-20px}.mdc-list-item--with-leading-video.mdc-list-item--with-two-lines .mdc-list-item__primary-text::before{display:inline-block;width:0;height:32px;content:"";vertical-align:0}.mdc-list-item--with-leading-video.mdc-list-item--with-two-lines .mdc-list-item__primary-text::after{display:inline-block;width:0;height:20px;content:"";vertical-align:-20px}.mdc-list-item--with-leading-video.mdc-list-item--with-two-lines .mdc-list-item__overline-text{display:block;margin-top:0;line-height:normal;margin-bottom:-20px}.mdc-list-item--with-leading-video.mdc-list-item--with-two-lines .mdc-list-item__overline-text::before{display:inline-block;width:0;height:28px;content:"";vertical-align:0}.mdc-list-item--with-leading-video.mdc-list-item--with-two-lines .mdc-list-item__overline-text::after{display:inline-block;width:0;height:20px;content:"";vertical-align:-20px}.mdc-list-item--with-leading-video.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end{display:block;margin-top:0;line-height:normal}.mdc-list-item--with-leading-video.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end::before{display:inline-block;width:0;height:32px;content:"";vertical-align:0}.mdc-list-item--with-leading-video.mdc-list-item--with-one-line{height:72px}.mdc-list-item--with-leading-video.mdc-list-item--with-two-lines{height:72px}.mdc-list-item--with-leading-checkbox.mdc-list-item{padding-left:0;padding-right:auto}[dir=rtl] .mdc-list-item--with-leading-checkbox.mdc-list-item,.mdc-list-item--with-leading-checkbox.mdc-list-item[dir=rtl]{padding-left:auto;padding-right:0}.mdc-list-item--with-leading-checkbox .mdc-list-item__start{margin-left:8px;margin-right:24px}[dir=rtl] .mdc-list-item--with-leading-checkbox .mdc-list-item__start,.mdc-list-item--with-leading-checkbox .mdc-list-item__start[dir=rtl]{margin-left:24px;margin-right:8px}.mdc-list-item--with-leading-checkbox .mdc-list-item__start{width:40px;height:40px}.mdc-list-item--with-leading-checkbox.mdc-list-item--with-two-lines .mdc-list-item__start{align-self:flex-start;margin-top:8px}.mdc-list-item--with-leading-checkbox.mdc-list-item--with-two-lines .mdc-list-item__primary-text{display:block;margin-top:0;line-height:normal;margin-bottom:-20px}.mdc-list-item--with-leading-checkbox.mdc-list-item--with-two-lines .mdc-list-item__primary-text::before{display:inline-block;width:0;height:32px;content:"";vertical-align:0}.mdc-list-item--with-leading-checkbox.mdc-list-item--with-two-lines .mdc-list-item__primary-text::after{display:inline-block;width:0;height:20px;content:"";vertical-align:-20px}.mdc-list-item--with-leading-checkbox.mdc-list-item--with-two-lines .mdc-list-item__overline-text{display:block;margin-top:0;line-height:normal;margin-bottom:-20px}.mdc-list-item--with-leading-checkbox.mdc-list-item--with-two-lines .mdc-list-item__overline-text::before{display:inline-block;width:0;height:28px;content:"";vertical-align:0}.mdc-list-item--with-leading-checkbox.mdc-list-item--with-two-lines .mdc-list-item__overline-text::after{display:inline-block;width:0;height:20px;content:"";vertical-align:-20px}.mdc-list-item--with-leading-checkbox.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end{display:block;margin-top:0;line-height:normal}.mdc-list-item--with-leading-checkbox.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end::before{display:inline-block;width:0;height:32px;content:"";vertical-align:0}.mdc-list-item--with-leading-checkbox.mdc-list-item--with-one-line{height:56px}.mdc-list-item--with-leading-checkbox.mdc-list-item--with-two-lines{height:72px}.mdc-list-item--with-leading-radio.mdc-list-item{padding-left:0;padding-right:auto}[dir=rtl] .mdc-list-item--with-leading-radio.mdc-list-item,.mdc-list-item--with-leading-radio.mdc-list-item[dir=rtl]{padding-left:auto;padding-right:0}.mdc-list-item--with-leading-radio .mdc-list-item__start{margin-left:8px;margin-right:24px}[dir=rtl] .mdc-list-item--with-leading-radio .mdc-list-item__start,.mdc-list-item--with-leading-radio .mdc-list-item__start[dir=rtl]{margin-left:24px;margin-right:8px}.mdc-list-item--with-leading-radio .mdc-list-item__start{width:40px;height:40px}.mdc-list-item--with-leading-radio.mdc-list-item--with-two-lines .mdc-list-item__start{align-self:flex-start;margin-top:8px}.mdc-list-item--with-leading-radio.mdc-list-item--with-two-lines .mdc-list-item__primary-text{display:block;margin-top:0;line-height:normal;margin-bottom:-20px}.mdc-list-item--with-leading-radio.mdc-list-item--with-two-lines .mdc-list-item__primary-text::before{display:inline-block;width:0;height:32px;content:"";vertical-align:0}.mdc-list-item--with-leading-radio.mdc-list-item--with-two-lines .mdc-list-item__primary-text::after{display:inline-block;width:0;height:20px;content:"";vertical-align:-20px}.mdc-list-item--with-leading-radio.mdc-list-item--with-two-lines .mdc-list-item__overline-text{display:block;margin-top:0;line-height:normal;margin-bottom:-20px}.mdc-list-item--with-leading-radio.mdc-list-item--with-two-lines .mdc-list-item__overline-text::before{display:inline-block;width:0;height:28px;content:"";vertical-align:0}.mdc-list-item--with-leading-radio.mdc-list-item--with-two-lines .mdc-list-item__overline-text::after{display:inline-block;width:0;height:20px;content:"";vertical-align:-20px}.mdc-list-item--with-leading-radio.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end{display:block;margin-top:0;line-height:normal}.mdc-list-item--with-leading-radio.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end::before{display:inline-block;width:0;height:32px;content:"";vertical-align:0}.mdc-list-item--with-leading-radio.mdc-list-item--with-one-line{height:56px}.mdc-list-item--with-leading-radio.mdc-list-item--with-two-lines{height:72px}.mdc-list-item--with-leading-switch.mdc-list-item{padding-left:0;padding-right:auto}[dir=rtl] .mdc-list-item--with-leading-switch.mdc-list-item,.mdc-list-item--with-leading-switch.mdc-list-item[dir=rtl]{padding-left:auto;padding-right:0}.mdc-list-item--with-leading-switch .mdc-list-item__start{margin-left:16px;margin-right:16px}[dir=rtl] .mdc-list-item--with-leading-switch .mdc-list-item__start,.mdc-list-item--with-leading-switch .mdc-list-item__start[dir=rtl]{margin-left:16px;margin-right:16px}.mdc-list-item--with-leading-switch .mdc-list-item__start{width:36px;height:20px}.mdc-list-item--with-leading-switch.mdc-list-item--with-two-lines .mdc-list-item__start{align-self:flex-start;margin-top:16px}.mdc-list-item--with-leading-switch.mdc-list-item--with-two-lines .mdc-list-item__primary-text{display:block;margin-top:0;line-height:normal;margin-bottom:-20px}.mdc-list-item--with-leading-switch.mdc-list-item--with-two-lines .mdc-list-item__primary-text::before{display:inline-block;width:0;height:32px;content:"";vertical-align:0}.mdc-list-item--with-leading-switch.mdc-list-item--with-two-lines .mdc-list-item__primary-text::after{display:inline-block;width:0;height:20px;content:"";vertical-align:-20px}.mdc-list-item--with-leading-switch.mdc-list-item--with-two-lines .mdc-list-item__overline-text{display:block;margin-top:0;line-height:normal;margin-bottom:-20px}.mdc-list-item--with-leading-switch.mdc-list-item--with-two-lines .mdc-list-item__overline-text::before{display:inline-block;width:0;height:28px;content:"";vertical-align:0}.mdc-list-item--with-leading-switch.mdc-list-item--with-two-lines .mdc-list-item__overline-text::after{display:inline-block;width:0;height:20px;content:"";vertical-align:-20px}.mdc-list-item--with-leading-switch.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end{display:block;margin-top:0;line-height:normal}.mdc-list-item--with-leading-switch.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end::before{display:inline-block;width:0;height:32px;content:"";vertical-align:0}.mdc-list-item--with-leading-switch.mdc-list-item--with-one-line{height:56px}.mdc-list-item--with-leading-switch.mdc-list-item--with-two-lines{height:72px}.mdc-list-item--with-trailing-icon.mdc-list-item{padding-left:auto;padding-right:0}[dir=rtl] .mdc-list-item--with-trailing-icon.mdc-list-item,.mdc-list-item--with-trailing-icon.mdc-list-item[dir=rtl]{padding-left:0;padding-right:auto}.mdc-list-item--with-trailing-icon .mdc-list-item__end{margin-left:16px;margin-right:16px}[dir=rtl] .mdc-list-item--with-trailing-icon .mdc-list-item__end,.mdc-list-item--with-trailing-icon .mdc-list-item__end[dir=rtl]{margin-left:16px;margin-right:16px}.mdc-list-item--with-trailing-icon .mdc-list-item__end{width:24px;height:24px}.mdc-list-item--with-trailing-meta.mdc-list-item--with-two-lines .mdc-list-item__end{align-self:flex-start;margin-top:0}.mdc-list-item--with-trailing-meta.mdc-list-item--with-three-lines .mdc-list-item__end{align-self:flex-start;margin-top:0}.mdc-list-item--with-trailing-meta.mdc-list-item{padding-left:auto;padding-right:0}[dir=rtl] .mdc-list-item--with-trailing-meta.mdc-list-item,.mdc-list-item--with-trailing-meta.mdc-list-item[dir=rtl]{padding-left:0;padding-right:auto}.mdc-list-item--with-trailing-meta .mdc-list-item__end{margin-left:28px;margin-right:16px}[dir=rtl] .mdc-list-item--with-trailing-meta .mdc-list-item__end,.mdc-list-item--with-trailing-meta .mdc-list-item__end[dir=rtl]{margin-left:16px;margin-right:28px}.mdc-list-item--with-trailing-meta.mdc-list-item--with-two-lines .mdc-list-item__end{display:block;margin-top:0;line-height:normal}.mdc-list-item--with-trailing-meta.mdc-list-item--with-two-lines .mdc-list-item__end::before{display:inline-block;width:0;height:28px;content:"";vertical-align:0}.mdc-list-item--with-trailing-meta.mdc-list-item--with-three-lines .mdc-list-item__end{display:block;margin-top:0;line-height:normal}.mdc-list-item--with-trailing-meta.mdc-list-item--with-three-lines .mdc-list-item__end::before{display:inline-block;width:0;height:28px;content:"";vertical-align:0}.mdc-list-item--with-trailing-meta .mdc-list-item__end{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-caption-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.75rem;font-size:var(--mdc-typography-caption-font-size, 0.75rem);line-height:1.25rem;line-height:var(--mdc-typography-caption-line-height, 1.25rem);font-weight:400;font-weight:var(--mdc-typography-caption-font-weight, 400);letter-spacing:0.0333333333em;letter-spacing:var(--mdc-typography-caption-letter-spacing, 0.0333333333em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-caption-text-decoration, inherit);text-decoration:var(--mdc-typography-caption-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-caption-text-transform, inherit)}.mdc-list-item--with-trailing-checkbox.mdc-list-item{padding-left:auto;padding-right:0}[dir=rtl] .mdc-list-item--with-trailing-checkbox.mdc-list-item,.mdc-list-item--with-trailing-checkbox.mdc-list-item[dir=rtl]{padding-left:0;padding-right:auto}.mdc-list-item--with-trailing-checkbox .mdc-list-item__end{margin-left:24px;margin-right:8px}[dir=rtl] .mdc-list-item--with-trailing-checkbox .mdc-list-item__end,.mdc-list-item--with-trailing-checkbox .mdc-list-item__end[dir=rtl]{margin-left:8px;margin-right:24px}.mdc-list-item--with-trailing-checkbox .mdc-list-item__end{width:40px;height:40px}.mdc-list-item--with-trailing-checkbox.mdc-list-item--with-three-lines .mdc-list-item__end{align-self:flex-start;margin-top:8px}.mdc-list-item--with-trailing-radio.mdc-list-item{padding-left:auto;padding-right:0}[dir=rtl] .mdc-list-item--with-trailing-radio.mdc-list-item,.mdc-list-item--with-trailing-radio.mdc-list-item[dir=rtl]{padding-left:0;padding-right:auto}.mdc-list-item--with-trailing-radio .mdc-list-item__end{margin-left:24px;margin-right:8px}[dir=rtl] .mdc-list-item--with-trailing-radio .mdc-list-item__end,.mdc-list-item--with-trailing-radio .mdc-list-item__end[dir=rtl]{margin-left:8px;margin-right:24px}.mdc-list-item--with-trailing-radio .mdc-list-item__end{width:40px;height:40px}.mdc-list-item--with-trailing-radio.mdc-list-item--with-three-lines .mdc-list-item__end{align-self:flex-start;margin-top:8px}.mdc-list-item--with-trailing-switch.mdc-list-item{padding-left:auto;padding-right:0}[dir=rtl] .mdc-list-item--with-trailing-switch.mdc-list-item,.mdc-list-item--with-trailing-switch.mdc-list-item[dir=rtl]{padding-left:0;padding-right:auto}.mdc-list-item--with-trailing-switch .mdc-list-item__end{margin-left:16px;margin-right:16px}[dir=rtl] .mdc-list-item--with-trailing-switch .mdc-list-item__end,.mdc-list-item--with-trailing-switch .mdc-list-item__end[dir=rtl]{margin-left:16px;margin-right:16px}.mdc-list-item--with-trailing-switch .mdc-list-item__end{width:36px;height:20px}.mdc-list-item--with-trailing-switch.mdc-list-item--with-three-lines .mdc-list-item__end{align-self:flex-start;margin-top:16px}.mdc-list-item--with-overline.mdc-list-item--with-two-lines .mdc-list-item__primary-text{display:block;margin-top:0;line-height:normal}.mdc-list-item--with-overline.mdc-list-item--with-two-lines .mdc-list-item__primary-text::before{display:inline-block;width:0;height:20px;content:"";vertical-align:0}.mdc-list-item--with-overline.mdc-list-item--with-three-lines .mdc-list-item__primary-text{display:block;margin-top:0;line-height:normal}.mdc-list-item--with-overline.mdc-list-item--with-three-lines .mdc-list-item__primary-text::before{display:inline-block;width:0;height:20px;content:"";vertical-align:0}.mdc-list-item{padding-left:16px;padding-right:16px}[dir=rtl] .mdc-list-item,.mdc-list-item[dir=rtl]{padding-left:16px;padding-right:16px}.mdc-list-group .mdc-deprecated-list{padding:0}.mdc-list-group__subheader{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-subtitle1-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:1rem;font-size:var(--mdc-typography-subtitle1-font-size, 1rem);line-height:1.75rem;line-height:var(--mdc-typography-subtitle1-line-height, 1.75rem);font-weight:400;font-weight:var(--mdc-typography-subtitle1-font-weight, 400);letter-spacing:0.009375em;letter-spacing:var(--mdc-typography-subtitle1-letter-spacing, 0.009375em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-subtitle1-text-decoration, inherit);text-decoration:var(--mdc-typography-subtitle1-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-subtitle1-text-transform, inherit);margin:calc( (3rem - 1.5rem) / 2 ) 16px}.mdc-list-divider{background-color:rgba(0, 0, 0, 0.12)}.mdc-list-divider{height:1px;padding:0;background-clip:content-box}.mdc-list-divider.mdc-list-divider--with-leading-inset,.mdc-list-divider--with-leading-text.mdc-list-divider--with-leading-inset,.mdc-list-divider--with-leading-icon.mdc-list-divider--with-leading-inset,.mdc-list-divider--with-leading-image.mdc-list-divider--with-leading-inset,.mdc-list-divider--with-leading-thumbnail.mdc-list-divider--with-leading-inset,.mdc-list-divider--with-leading-avatar.mdc-list-divider--with-leading-inset,.mdc-list-divider--with-leading-checkbox.mdc-list-divider--with-leading-inset,.mdc-list-divider--with-leading-switch.mdc-list-divider--with-leading-inset,.mdc-list-divider--with-leading-radio.mdc-list-divider--with-leading-inset{padding-left:16px;padding-right:auto}[dir=rtl] .mdc-list-divider.mdc-list-divider--with-leading-inset,[dir=rtl] .mdc-list-divider--with-leading-text.mdc-list-divider--with-leading-inset,[dir=rtl] .mdc-list-divider--with-leading-icon.mdc-list-divider--with-leading-inset,[dir=rtl] .mdc-list-divider--with-leading-image.mdc-list-divider--with-leading-inset,[dir=rtl] .mdc-list-divider--with-leading-thumbnail.mdc-list-divider--with-leading-inset,[dir=rtl] .mdc-list-divider--with-leading-avatar.mdc-list-divider--with-leading-inset,[dir=rtl] .mdc-list-divider--with-leading-checkbox.mdc-list-divider--with-leading-inset,[dir=rtl] .mdc-list-divider--with-leading-switch.mdc-list-divider--with-leading-inset,[dir=rtl] .mdc-list-divider--with-leading-radio.mdc-list-divider--with-leading-inset,.mdc-list-divider.mdc-list-divider--with-leading-inset[dir=rtl],.mdc-list-divider--with-leading-text.mdc-list-divider--with-leading-inset[dir=rtl],.mdc-list-divider--with-leading-icon.mdc-list-divider--with-leading-inset[dir=rtl],.mdc-list-divider--with-leading-image.mdc-list-divider--with-leading-inset[dir=rtl],.mdc-list-divider--with-leading-thumbnail.mdc-list-divider--with-leading-inset[dir=rtl],.mdc-list-divider--with-leading-avatar.mdc-list-divider--with-leading-inset[dir=rtl],.mdc-list-divider--with-leading-checkbox.mdc-list-divider--with-leading-inset[dir=rtl],.mdc-list-divider--with-leading-switch.mdc-list-divider--with-leading-inset[dir=rtl],.mdc-list-divider--with-leading-radio.mdc-list-divider--with-leading-inset[dir=rtl]{padding-left:auto;padding-right:16px}.mdc-list-divider.mdc-list-divider--with-trailing-inset,.mdc-list-divider--with-leading-text.mdc-list-divider--with-trailing-inset,.mdc-list-divider--with-leading-icon.mdc-list-divider--with-trailing-inset,.mdc-list-divider--with-leading-image.mdc-list-divider--with-trailing-inset,.mdc-list-divider--with-leading-thumbnail.mdc-list-divider--with-trailing-inset,.mdc-list-divider--with-leading-avatar.mdc-list-divider--with-trailing-inset,.mdc-list-divider--with-leading-checkbox.mdc-list-divider--with-trailing-inset,.mdc-list-divider--with-leading-switch.mdc-list-divider--with-trailing-inset,.mdc-list-divider--with-leading-radio.mdc-list-divider--with-trailing-inset{padding-left:auto;padding-right:16px}[dir=rtl] .mdc-list-divider.mdc-list-divider--with-trailing-inset,[dir=rtl] .mdc-list-divider--with-leading-text.mdc-list-divider--with-trailing-inset,[dir=rtl] .mdc-list-divider--with-leading-icon.mdc-list-divider--with-trailing-inset,[dir=rtl] .mdc-list-divider--with-leading-image.mdc-list-divider--with-trailing-inset,[dir=rtl] .mdc-list-divider--with-leading-thumbnail.mdc-list-divider--with-trailing-inset,[dir=rtl] .mdc-list-divider--with-leading-avatar.mdc-list-divider--with-trailing-inset,[dir=rtl] .mdc-list-divider--with-leading-checkbox.mdc-list-divider--with-trailing-inset,[dir=rtl] .mdc-list-divider--with-leading-switch.mdc-list-divider--with-trailing-inset,[dir=rtl] .mdc-list-divider--with-leading-radio.mdc-list-divider--with-trailing-inset,.mdc-list-divider.mdc-list-divider--with-trailing-inset[dir=rtl],.mdc-list-divider--with-leading-text.mdc-list-divider--with-trailing-inset[dir=rtl],.mdc-list-divider--with-leading-icon.mdc-list-divider--with-trailing-inset[dir=rtl],.mdc-list-divider--with-leading-image.mdc-list-divider--with-trailing-inset[dir=rtl],.mdc-list-divider--with-leading-thumbnail.mdc-list-divider--with-trailing-inset[dir=rtl],.mdc-list-divider--with-leading-avatar.mdc-list-divider--with-trailing-inset[dir=rtl],.mdc-list-divider--with-leading-checkbox.mdc-list-divider--with-trailing-inset[dir=rtl],.mdc-list-divider--with-leading-switch.mdc-list-divider--with-trailing-inset[dir=rtl],.mdc-list-divider--with-leading-radio.mdc-list-divider--with-trailing-inset[dir=rtl]{padding-left:16px;padding-right:auto}.mdc-list-divider--with-leading-video.mdc-list-divider--with-leading-inset{padding-left:0px;padding-right:auto}[dir=rtl] .mdc-list-divider--with-leading-video.mdc-list-divider--with-leading-inset,.mdc-list-divider--with-leading-video.mdc-list-divider--with-leading-inset[dir=rtl]{padding-left:auto;padding-right:0px}[dir=rtl] .mdc-list-divider,.mdc-list-divider[dir=rtl]{padding:0}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item{--mdc-ripple-fg-size: 0;--mdc-ripple-left: 0;--mdc-ripple-top: 0;--mdc-ripple-fg-scale: 1;--mdc-ripple-fg-translate-end: 0;--mdc-ripple-fg-translate-start: 0;-webkit-tap-highlight-color:rgba(0,0,0,0);will-change:transform,opacity;--mdc-ripple-fg-size: 0;--mdc-ripple-left: 0;--mdc-ripple-top: 0;--mdc-ripple-fg-scale: 1;--mdc-ripple-fg-translate-end: 0;--mdc-ripple-fg-translate-start: 0;-webkit-tap-highlight-color:rgba(0,0,0,0);will-change:transform,opacity}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-deprecated-list-item__ripple::before,:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-deprecated-list-item__ripple::after{position:absolute;border-radius:50%;opacity:0;pointer-events:none;content:""}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-deprecated-list-item__ripple::before{transition:opacity 15ms linear,background-color 15ms linear;z-index:1;z-index:var(--mdc-ripple-z-index, 1)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-deprecated-list-item__ripple::after{z-index:0;z-index:var(--mdc-ripple-z-index, 0)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded .mdc-deprecated-list-item__ripple::before{-webkit-transform:scale(var(--mdc-ripple-fg-scale, 1));transform:scale(var(--mdc-ripple-fg-scale, 1))}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded .mdc-deprecated-list-item__ripple::after{top:0;left:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:center center;transform-origin:center center}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded--unbounded .mdc-deprecated-list-item__ripple::after{top:var(--mdc-ripple-top, 0);left:var(--mdc-ripple-left, 0)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded--foreground-activation .mdc-deprecated-list-item__ripple::after{-webkit-animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards;animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded--foreground-deactivation .mdc-deprecated-list-item__ripple::after{-webkit-animation:mdc-ripple-fg-opacity-out 150ms;animation:mdc-ripple-fg-opacity-out 150ms;-webkit-transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1))}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-list-item__ripple::before,:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-list-item__ripple::after{position:absolute;border-radius:50%;opacity:0;pointer-events:none;content:""}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-list-item__ripple::before{transition:opacity 15ms linear,background-color 15ms linear;z-index:1;z-index:var(--mdc-ripple-z-index, 1)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-list-item__ripple::after{z-index:0;z-index:var(--mdc-ripple-z-index, 0)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded .mdc-list-item__ripple::before{-webkit-transform:scale(var(--mdc-ripple-fg-scale, 1));transform:scale(var(--mdc-ripple-fg-scale, 1))}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded .mdc-list-item__ripple::after{top:0;left:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:center center;transform-origin:center center}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded--unbounded .mdc-list-item__ripple::after{top:var(--mdc-ripple-top, 0);left:var(--mdc-ripple-left, 0)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded--foreground-activation .mdc-list-item__ripple::after{-webkit-animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards;animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded--foreground-deactivation .mdc-list-item__ripple::after{-webkit-animation:mdc-ripple-fg-opacity-out 150ms;animation:mdc-ripple-fg-opacity-out 150ms;-webkit-transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1))}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-deprecated-list-item__ripple::before,:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-deprecated-list-item__ripple::after{top:calc(50% - 100%);left:calc(50% - 100%);width:200%;height:200%}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded .mdc-deprecated-list-item__ripple::after{width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-list-item__ripple::before,:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-list-item__ripple::after{top:calc(50% - 100%);left:calc(50% - 100%);width:200%;height:200%}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded .mdc-list-item__ripple::after{width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-deprecated-list-item__ripple::before,:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-deprecated-list-item__ripple::after{background-color:#000;background-color:var(--mdc-ripple-color, #000)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item:hover .mdc-deprecated-list-item__ripple::before,:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-surface--hover .mdc-deprecated-list-item__ripple::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded--background-focused .mdc-deprecated-list-item__ripple::before,:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item:not(.mdc-ripple-upgraded):focus .mdc-deprecated-list-item__ripple::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item:not(.mdc-ripple-upgraded) .mdc-deprecated-list-item__ripple::after{transition:opacity 150ms linear}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item:not(.mdc-ripple-upgraded):active .mdc-deprecated-list-item__ripple::after{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-press-opacity, 0.12)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.12)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-list-item__ripple::before,:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-list-item__ripple::after{background-color:#000;background-color:var(--mdc-ripple-color, #000)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item:hover .mdc-list-item__ripple::before,:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-surface--hover .mdc-list-item__ripple::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded--background-focused .mdc-list-item__ripple::before,:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item:not(.mdc-ripple-upgraded):focus .mdc-list-item__ripple::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item:not(.mdc-ripple-upgraded) .mdc-list-item__ripple::after{transition:opacity 150ms linear}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item:not(.mdc-ripple-upgraded):active .mdc-list-item__ripple::after{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-press-opacity, 0.12)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.12)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated .mdc-deprecated-list-item__ripple::before{opacity:0.12;opacity:var(--mdc-ripple-activated-opacity, 0.12)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated .mdc-deprecated-list-item__ripple::before,:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated .mdc-deprecated-list-item__ripple::after{background-color:#6200ee;background-color:var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee))}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated:hover .mdc-deprecated-list-item__ripple::before,:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated.mdc-ripple-surface--hover .mdc-deprecated-list-item__ripple::before{opacity:0.16;opacity:var(--mdc-ripple-hover-opacity, 0.16)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated.mdc-ripple-upgraded--background-focused .mdc-deprecated-list-item__ripple::before,:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated:not(.mdc-ripple-upgraded):focus .mdc-deprecated-list-item__ripple::before{transition-duration:75ms;opacity:0.24;opacity:var(--mdc-ripple-focus-opacity, 0.24)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated:not(.mdc-ripple-upgraded) .mdc-deprecated-list-item__ripple::after{transition:opacity 150ms linear}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated:not(.mdc-ripple-upgraded):active .mdc-deprecated-list-item__ripple::after{transition-duration:75ms;opacity:0.24;opacity:var(--mdc-ripple-press-opacity, 0.24)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.24)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated .mdc-list-item__ripple::before{opacity:0.12;opacity:var(--mdc-ripple-activated-opacity, 0.12)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated .mdc-list-item__ripple::before,:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated .mdc-list-item__ripple::after{background-color:#6200ee;background-color:var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee))}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated:hover .mdc-list-item__ripple::before,:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated.mdc-ripple-surface--hover .mdc-list-item__ripple::before{opacity:0.16;opacity:var(--mdc-ripple-hover-opacity, 0.16)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated.mdc-ripple-upgraded--background-focused .mdc-list-item__ripple::before,:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated:not(.mdc-ripple-upgraded):focus .mdc-list-item__ripple::before{transition-duration:75ms;opacity:0.24;opacity:var(--mdc-ripple-focus-opacity, 0.24)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated:not(.mdc-ripple-upgraded) .mdc-list-item__ripple::after{transition:opacity 150ms linear}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated:not(.mdc-ripple-upgraded):active .mdc-list-item__ripple::after{transition-duration:75ms;opacity:0.24;opacity:var(--mdc-ripple-press-opacity, 0.24)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--activated.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.24)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected .mdc-deprecated-list-item__ripple::before{opacity:0.08;opacity:var(--mdc-ripple-selected-opacity, 0.08)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected .mdc-deprecated-list-item__ripple::before,:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected .mdc-deprecated-list-item__ripple::after{background-color:#6200ee;background-color:var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee))}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected:hover .mdc-deprecated-list-item__ripple::before,:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected.mdc-ripple-surface--hover .mdc-deprecated-list-item__ripple::before{opacity:0.12;opacity:var(--mdc-ripple-hover-opacity, 0.12)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected.mdc-ripple-upgraded--background-focused .mdc-deprecated-list-item__ripple::before,:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected:not(.mdc-ripple-upgraded):focus .mdc-deprecated-list-item__ripple::before{transition-duration:75ms;opacity:0.2;opacity:var(--mdc-ripple-focus-opacity, 0.2)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected:not(.mdc-ripple-upgraded) .mdc-deprecated-list-item__ripple::after{transition:opacity 150ms linear}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected:not(.mdc-ripple-upgraded):active .mdc-deprecated-list-item__ripple::after{transition-duration:75ms;opacity:0.2;opacity:var(--mdc-ripple-press-opacity, 0.2)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.2)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected .mdc-list-item__ripple::before{opacity:0.08;opacity:var(--mdc-ripple-selected-opacity, 0.08)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected .mdc-list-item__ripple::before,:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected .mdc-list-item__ripple::after{background-color:#6200ee;background-color:var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee))}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected:hover .mdc-list-item__ripple::before,:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected.mdc-ripple-surface--hover .mdc-list-item__ripple::before{opacity:0.12;opacity:var(--mdc-ripple-hover-opacity, 0.12)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected.mdc-ripple-upgraded--background-focused .mdc-list-item__ripple::before,:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected:not(.mdc-ripple-upgraded):focus .mdc-list-item__ripple::before{transition-duration:75ms;opacity:0.2;opacity:var(--mdc-ripple-focus-opacity, 0.2)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected:not(.mdc-ripple-upgraded) .mdc-list-item__ripple::after{transition:opacity 150ms linear}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected:not(.mdc-ripple-upgraded):active .mdc-list-item__ripple::after{transition-duration:75ms;opacity:0.2;opacity:var(--mdc-ripple-press-opacity, 0.2)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item--selected.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.2)}:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-deprecated-list-item__ripple,:not(.mdc-deprecated-list-item--disabled).mdc-deprecated-list-item .mdc-list-item__ripple{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none}.mdc-deprecated-list-item--disabled{--mdc-ripple-fg-size: 0;--mdc-ripple-left: 0;--mdc-ripple-top: 0;--mdc-ripple-fg-scale: 1;--mdc-ripple-fg-translate-end: 0;--mdc-ripple-fg-translate-start: 0;-webkit-tap-highlight-color:rgba(0,0,0,0);will-change:transform,opacity;--mdc-ripple-fg-size: 0;--mdc-ripple-left: 0;--mdc-ripple-top: 0;--mdc-ripple-fg-scale: 1;--mdc-ripple-fg-translate-end: 0;--mdc-ripple-fg-translate-start: 0;-webkit-tap-highlight-color:rgba(0,0,0,0);will-change:transform,opacity}.mdc-deprecated-list-item--disabled .mdc-deprecated-list-item__ripple::before,.mdc-deprecated-list-item--disabled .mdc-deprecated-list-item__ripple::after{position:absolute;border-radius:50%;opacity:0;pointer-events:none;content:""}.mdc-deprecated-list-item--disabled .mdc-deprecated-list-item__ripple::before{transition:opacity 15ms linear,background-color 15ms linear;z-index:1;z-index:var(--mdc-ripple-z-index, 1)}.mdc-deprecated-list-item--disabled .mdc-deprecated-list-item__ripple::after{z-index:0;z-index:var(--mdc-ripple-z-index, 0)}.mdc-deprecated-list-item--disabled.mdc-ripple-upgraded .mdc-deprecated-list-item__ripple::before{-webkit-transform:scale(var(--mdc-ripple-fg-scale, 1));transform:scale(var(--mdc-ripple-fg-scale, 1))}.mdc-deprecated-list-item--disabled.mdc-ripple-upgraded .mdc-deprecated-list-item__ripple::after{top:0;left:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:center center;transform-origin:center center}.mdc-deprecated-list-item--disabled.mdc-ripple-upgraded--unbounded .mdc-deprecated-list-item__ripple::after{top:var(--mdc-ripple-top, 0);left:var(--mdc-ripple-left, 0)}.mdc-deprecated-list-item--disabled.mdc-ripple-upgraded--foreground-activation .mdc-deprecated-list-item__ripple::after{-webkit-animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards;animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards}.mdc-deprecated-list-item--disabled.mdc-ripple-upgraded--foreground-deactivation .mdc-deprecated-list-item__ripple::after{-webkit-animation:mdc-ripple-fg-opacity-out 150ms;animation:mdc-ripple-fg-opacity-out 150ms;-webkit-transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1))}.mdc-deprecated-list-item--disabled .mdc-list-item__ripple::before,.mdc-deprecated-list-item--disabled .mdc-list-item__ripple::after{position:absolute;border-radius:50%;opacity:0;pointer-events:none;content:""}.mdc-deprecated-list-item--disabled .mdc-list-item__ripple::before{transition:opacity 15ms linear,background-color 15ms linear;z-index:1;z-index:var(--mdc-ripple-z-index, 1)}.mdc-deprecated-list-item--disabled .mdc-list-item__ripple::after{z-index:0;z-index:var(--mdc-ripple-z-index, 0)}.mdc-deprecated-list-item--disabled.mdc-ripple-upgraded .mdc-list-item__ripple::before{-webkit-transform:scale(var(--mdc-ripple-fg-scale, 1));transform:scale(var(--mdc-ripple-fg-scale, 1))}.mdc-deprecated-list-item--disabled.mdc-ripple-upgraded .mdc-list-item__ripple::after{top:0;left:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:center center;transform-origin:center center}.mdc-deprecated-list-item--disabled.mdc-ripple-upgraded--unbounded .mdc-list-item__ripple::after{top:var(--mdc-ripple-top, 0);left:var(--mdc-ripple-left, 0)}.mdc-deprecated-list-item--disabled.mdc-ripple-upgraded--foreground-activation .mdc-list-item__ripple::after{-webkit-animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards;animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards}.mdc-deprecated-list-item--disabled.mdc-ripple-upgraded--foreground-deactivation .mdc-list-item__ripple::after{-webkit-animation:mdc-ripple-fg-opacity-out 150ms;animation:mdc-ripple-fg-opacity-out 150ms;-webkit-transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1))}.mdc-deprecated-list-item--disabled .mdc-deprecated-list-item__ripple::before,.mdc-deprecated-list-item--disabled .mdc-deprecated-list-item__ripple::after{top:calc(50% - 100%);left:calc(50% - 100%);width:200%;height:200%}.mdc-deprecated-list-item--disabled.mdc-ripple-upgraded .mdc-deprecated-list-item__ripple::after{width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}.mdc-deprecated-list-item--disabled .mdc-list-item__ripple::before,.mdc-deprecated-list-item--disabled .mdc-list-item__ripple::after{top:calc(50% - 100%);left:calc(50% - 100%);width:200%;height:200%}.mdc-deprecated-list-item--disabled.mdc-ripple-upgraded .mdc-list-item__ripple::after{width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}.mdc-deprecated-list-item--disabled .mdc-deprecated-list-item__ripple::before,.mdc-deprecated-list-item--disabled .mdc-deprecated-list-item__ripple::after{background-color:#000;background-color:var(--mdc-ripple-color, #000)}.mdc-deprecated-list-item--disabled .mdc-list-item__ripple::before,.mdc-deprecated-list-item--disabled .mdc-list-item__ripple::after{background-color:#000;background-color:var(--mdc-ripple-color, #000)}.mdc-deprecated-list-item--disabled.mdc-ripple-upgraded--background-focused .mdc-deprecated-list-item__ripple::before,.mdc-deprecated-list-item--disabled:not(.mdc-ripple-upgraded):focus .mdc-deprecated-list-item__ripple::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-deprecated-list-item--disabled.mdc-ripple-upgraded--background-focused .mdc-list-item__ripple::before,.mdc-deprecated-list-item--disabled:not(.mdc-ripple-upgraded):focus .mdc-list-item__ripple::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-deprecated-list-item--disabled .mdc-deprecated-list-item__ripple,.mdc-deprecated-list-item--disabled .mdc-list-item__ripple{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none}:not(.mdc-list-item--disabled).mdc-list-item{--mdc-ripple-fg-size: 0;--mdc-ripple-left: 0;--mdc-ripple-top: 0;--mdc-ripple-fg-scale: 1;--mdc-ripple-fg-translate-end: 0;--mdc-ripple-fg-translate-start: 0;-webkit-tap-highlight-color:rgba(0,0,0,0);will-change:transform,opacity}:not(.mdc-list-item--disabled).mdc-list-item .mdc-list-item__ripple::before,:not(.mdc-list-item--disabled).mdc-list-item .mdc-list-item__ripple::after{position:absolute;border-radius:50%;opacity:0;pointer-events:none;content:""}:not(.mdc-list-item--disabled).mdc-list-item .mdc-list-item__ripple::before{transition:opacity 15ms linear,background-color 15ms linear;z-index:1;z-index:var(--mdc-ripple-z-index, 1)}:not(.mdc-list-item--disabled).mdc-list-item .mdc-list-item__ripple::after{z-index:0;z-index:var(--mdc-ripple-z-index, 0)}:not(.mdc-list-item--disabled).mdc-list-item.mdc-ripple-upgraded .mdc-list-item__ripple::before{-webkit-transform:scale(var(--mdc-ripple-fg-scale, 1));transform:scale(var(--mdc-ripple-fg-scale, 1))}:not(.mdc-list-item--disabled).mdc-list-item.mdc-ripple-upgraded .mdc-list-item__ripple::after{top:0;left:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:center center;transform-origin:center center}:not(.mdc-list-item--disabled).mdc-list-item.mdc-ripple-upgraded--unbounded .mdc-list-item__ripple::after{top:var(--mdc-ripple-top, 0);left:var(--mdc-ripple-left, 0)}:not(.mdc-list-item--disabled).mdc-list-item.mdc-ripple-upgraded--foreground-activation .mdc-list-item__ripple::after{-webkit-animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards;animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards}:not(.mdc-list-item--disabled).mdc-list-item.mdc-ripple-upgraded--foreground-deactivation .mdc-list-item__ripple::after{-webkit-animation:mdc-ripple-fg-opacity-out 150ms;animation:mdc-ripple-fg-opacity-out 150ms;-webkit-transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1))}:not(.mdc-list-item--disabled).mdc-list-item .mdc-list-item__ripple::before,:not(.mdc-list-item--disabled).mdc-list-item .mdc-list-item__ripple::after{top:calc(50% - 100%);left:calc(50% - 100%);width:200%;height:200%}:not(.mdc-list-item--disabled).mdc-list-item.mdc-ripple-upgraded .mdc-list-item__ripple::after{width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}:not(.mdc-list-item--disabled).mdc-list-item .mdc-list-item__ripple::before,:not(.mdc-list-item--disabled).mdc-list-item .mdc-list-item__ripple::after{background-color:#000;background-color:var(--mdc-ripple-color, #000)}:not(.mdc-list-item--disabled).mdc-list-item:hover .mdc-list-item__ripple::before,:not(.mdc-list-item--disabled).mdc-list-item.mdc-ripple-surface--hover .mdc-list-item__ripple::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}:not(.mdc-list-item--disabled).mdc-list-item.mdc-ripple-upgraded--background-focused .mdc-list-item__ripple::before,:not(.mdc-list-item--disabled).mdc-list-item:not(.mdc-ripple-upgraded):focus .mdc-list-item__ripple::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}:not(.mdc-list-item--disabled).mdc-list-item:not(.mdc-ripple-upgraded) .mdc-list-item__ripple::after{transition:opacity 150ms linear}:not(.mdc-list-item--disabled).mdc-list-item:not(.mdc-ripple-upgraded):active .mdc-list-item__ripple::after{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-press-opacity, 0.12)}:not(.mdc-list-item--disabled).mdc-list-item.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.12)}:not(.mdc-list-item--disabled).mdc-list-item--activated .mdc-list-item__ripple::before{opacity:0.12;opacity:var(--mdc-ripple-activated-opacity, 0.12)}:not(.mdc-list-item--disabled).mdc-list-item--activated .mdc-list-item__ripple::before,:not(.mdc-list-item--disabled).mdc-list-item--activated .mdc-list-item__ripple::after{background-color:#6200ee;background-color:var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee))}:not(.mdc-list-item--disabled).mdc-list-item--activated:hover .mdc-list-item__ripple::before,:not(.mdc-list-item--disabled).mdc-list-item--activated.mdc-ripple-surface--hover .mdc-list-item__ripple::before{opacity:0.16;opacity:var(--mdc-ripple-hover-opacity, 0.16)}:not(.mdc-list-item--disabled).mdc-list-item--activated.mdc-ripple-upgraded--background-focused .mdc-list-item__ripple::before,:not(.mdc-list-item--disabled).mdc-list-item--activated:not(.mdc-ripple-upgraded):focus .mdc-list-item__ripple::before{transition-duration:75ms;opacity:0.24;opacity:var(--mdc-ripple-focus-opacity, 0.24)}:not(.mdc-list-item--disabled).mdc-list-item--activated:not(.mdc-ripple-upgraded) .mdc-list-item__ripple::after{transition:opacity 150ms linear}:not(.mdc-list-item--disabled).mdc-list-item--activated:not(.mdc-ripple-upgraded):active .mdc-list-item__ripple::after{transition-duration:75ms;opacity:0.24;opacity:var(--mdc-ripple-press-opacity, 0.24)}:not(.mdc-list-item--disabled).mdc-list-item--activated.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.24)}:not(.mdc-list-item--disabled).mdc-list-item--selected .mdc-list-item__ripple::before{opacity:0.08;opacity:var(--mdc-ripple-selected-opacity, 0.08)}:not(.mdc-list-item--disabled).mdc-list-item--selected .mdc-list-item__ripple::before,:not(.mdc-list-item--disabled).mdc-list-item--selected .mdc-list-item__ripple::after{background-color:#6200ee;background-color:var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee))}:not(.mdc-list-item--disabled).mdc-list-item--selected:hover .mdc-list-item__ripple::before,:not(.mdc-list-item--disabled).mdc-list-item--selected.mdc-ripple-surface--hover .mdc-list-item__ripple::before{opacity:0.12;opacity:var(--mdc-ripple-hover-opacity, 0.12)}:not(.mdc-list-item--disabled).mdc-list-item--selected.mdc-ripple-upgraded--background-focused .mdc-list-item__ripple::before,:not(.mdc-list-item--disabled).mdc-list-item--selected:not(.mdc-ripple-upgraded):focus .mdc-list-item__ripple::before{transition-duration:75ms;opacity:0.2;opacity:var(--mdc-ripple-focus-opacity, 0.2)}:not(.mdc-list-item--disabled).mdc-list-item--selected:not(.mdc-ripple-upgraded) .mdc-list-item__ripple::after{transition:opacity 150ms linear}:not(.mdc-list-item--disabled).mdc-list-item--selected:not(.mdc-ripple-upgraded):active .mdc-list-item__ripple::after{transition-duration:75ms;opacity:0.2;opacity:var(--mdc-ripple-press-opacity, 0.2)}:not(.mdc-list-item--disabled).mdc-list-item--selected.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.2)}:not(.mdc-list-item--disabled).mdc-list-item .mdc-list-item__ripple{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none}.mdc-list-item--disabled{--mdc-ripple-fg-size: 0;--mdc-ripple-left: 0;--mdc-ripple-top: 0;--mdc-ripple-fg-scale: 1;--mdc-ripple-fg-translate-end: 0;--mdc-ripple-fg-translate-start: 0;-webkit-tap-highlight-color:rgba(0,0,0,0);will-change:transform,opacity}.mdc-list-item--disabled .mdc-list-item__ripple::before,.mdc-list-item--disabled .mdc-list-item__ripple::after{position:absolute;border-radius:50%;opacity:0;pointer-events:none;content:""}.mdc-list-item--disabled .mdc-list-item__ripple::before{transition:opacity 15ms linear,background-color 15ms linear;z-index:1;z-index:var(--mdc-ripple-z-index, 1)}.mdc-list-item--disabled .mdc-list-item__ripple::after{z-index:0;z-index:var(--mdc-ripple-z-index, 0)}.mdc-list-item--disabled.mdc-ripple-upgraded .mdc-list-item__ripple::before{-webkit-transform:scale(var(--mdc-ripple-fg-scale, 1));transform:scale(var(--mdc-ripple-fg-scale, 1))}.mdc-list-item--disabled.mdc-ripple-upgraded .mdc-list-item__ripple::after{top:0;left:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:center center;transform-origin:center center}.mdc-list-item--disabled.mdc-ripple-upgraded--unbounded .mdc-list-item__ripple::after{top:var(--mdc-ripple-top, 0);left:var(--mdc-ripple-left, 0)}.mdc-list-item--disabled.mdc-ripple-upgraded--foreground-activation .mdc-list-item__ripple::after{-webkit-animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards;animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards}.mdc-list-item--disabled.mdc-ripple-upgraded--foreground-deactivation .mdc-list-item__ripple::after{-webkit-animation:mdc-ripple-fg-opacity-out 150ms;animation:mdc-ripple-fg-opacity-out 150ms;-webkit-transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1))}.mdc-list-item--disabled .mdc-list-item__ripple::before,.mdc-list-item--disabled .mdc-list-item__ripple::after{top:calc(50% - 100%);left:calc(50% - 100%);width:200%;height:200%}.mdc-list-item--disabled.mdc-ripple-upgraded .mdc-list-item__ripple::after{width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}.mdc-list-item--disabled .mdc-list-item__ripple::before,.mdc-list-item--disabled .mdc-list-item__ripple::after{background-color:#000;background-color:var(--mdc-ripple-color, #000)}.mdc-list-item--disabled.mdc-ripple-upgraded--background-focused .mdc-list-item__ripple::before,.mdc-list-item--disabled:not(.mdc-ripple-upgraded):focus .mdc-list-item__ripple::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-list-item--disabled .mdc-list-item__ripple{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none}.mdc-menu{min-width:112px;min-width:var(--mdc-menu-min-width, 112px)}.mdc-menu .mdc-deprecated-list-item__meta{color:rgba(0, 0, 0, 0.87)}.mdc-menu .mdc-deprecated-list-item__graphic{color:rgba(0, 0, 0, 0.87)}.mdc-menu .mdc-deprecated-list{color:rgba(0, 0, 0, 0.87);position:relative}.mdc-menu .mdc-deprecated-list .mdc-elevation-overlay{width:100%;height:100%;top:0;left:0}.mdc-menu .mdc-deprecated-list-divider{margin:8px 0}.mdc-menu .mdc-deprecated-list-item{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mdc-menu .mdc-deprecated-list-item--disabled{cursor:auto}.mdc-menu a.mdc-deprecated-list-item .mdc-deprecated-list-item__text,.mdc-menu a.mdc-deprecated-list-item .mdc-deprecated-list-item__graphic{pointer-events:none}.mdc-menu__selection-group{padding:0;fill:currentColor}.mdc-menu__selection-group .mdc-deprecated-list-item{padding-left:56px;padding-right:16px}[dir=rtl] .mdc-menu__selection-group .mdc-deprecated-list-item,.mdc-menu__selection-group .mdc-deprecated-list-item[dir=rtl]{padding-left:16px;padding-right:56px}.mdc-menu__selection-group .mdc-menu__selection-group-icon{left:16px;right:initial;display:none;position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}[dir=rtl] .mdc-menu__selection-group .mdc-menu__selection-group-icon,.mdc-menu__selection-group .mdc-menu__selection-group-icon[dir=rtl]{left:initial;right:16px}.mdc-menu-item--selected .mdc-menu__selection-group-icon{display:inline}.mdc-menu-surface{display:none;position:absolute;box-sizing:border-box;max-width:calc(100vw - 32px);max-width:var(--mdc-menu-max-width, calc(100vw - 32px));max-height:calc(100vh - 32px);max-height:var(--mdc-menu-max-height, calc(100vh - 32px));margin:0;padding:0;-webkit-transform:scale(1);transform:scale(1);-webkit-transform-origin:top left;transform-origin:top left;opacity:0;overflow:auto;will-change:transform,opacity;z-index:8;transition:opacity .03s linear,height 250ms cubic-bezier(0, 0, 0.2, 1),-webkit-transform .12s cubic-bezier(0, 0, 0.2, 1);transition:opacity .03s linear,transform .12s cubic-bezier(0, 0, 0.2, 1),height 250ms cubic-bezier(0, 0, 0.2, 1);transition:opacity .03s linear,transform .12s cubic-bezier(0, 0, 0.2, 1),height 250ms cubic-bezier(0, 0, 0.2, 1),-webkit-transform .12s cubic-bezier(0, 0, 0.2, 1);box-shadow:0px 5px 5px -3px rgba(0, 0, 0, 0.2),0px 8px 10px 1px rgba(0, 0, 0, 0.14),0px 3px 14px 2px rgba(0,0,0,.12);background-color:#fff;background-color:var(--mdc-theme-surface, #fff);color:#000;color:var(--mdc-theme-on-surface, #000);border-radius:4px;border-radius:var(--mdc-shape-medium, 4px);transform-origin-left:top left;transform-origin-right:top right}.mdc-menu-surface:focus{outline:none}.mdc-menu-surface--animating-open{display:inline-block;-webkit-transform:scale(0.8);transform:scale(0.8);opacity:0}.mdc-menu-surface--open{display:inline-block;-webkit-transform:scale(1);transform:scale(1);opacity:1}.mdc-menu-surface--animating-closed{display:inline-block;opacity:0;transition:opacity .075s linear}[dir=rtl] .mdc-menu-surface,.mdc-menu-surface[dir=rtl]{transform-origin-left:top right;transform-origin-right:top left}.mdc-menu-surface--anchor{position:relative;overflow:visible}.mdc-menu-surface--fixed{position:fixed}.mdc-menu-surface--fullwidth{width:100%}.mdc-radio{padding:calc((40px - 20px) / 2)}.mdc-radio .mdc-radio__native-control:enabled:not(:checked)+.mdc-radio__background .mdc-radio__outer-circle{border-color:rgba(0, 0, 0, 0.54)}.mdc-radio .mdc-radio__native-control:enabled:checked+.mdc-radio__background .mdc-radio__outer-circle{border-color:#018786;border-color:var(--mdc-theme-secondary, #018786)}.mdc-radio .mdc-radio__native-control:enabled+.mdc-radio__background .mdc-radio__inner-circle{border-color:#018786;border-color:var(--mdc-theme-secondary, #018786)}.mdc-radio [aria-disabled=true] .mdc-radio__native-control:not(:checked)+.mdc-radio__background .mdc-radio__outer-circle,.mdc-radio .mdc-radio__native-control:disabled:not(:checked)+.mdc-radio__background .mdc-radio__outer-circle{border-color:rgba(0, 0, 0, 0.38)}.mdc-radio [aria-disabled=true] .mdc-radio__native-control:checked+.mdc-radio__background .mdc-radio__outer-circle,.mdc-radio .mdc-radio__native-control:disabled:checked+.mdc-radio__background .mdc-radio__outer-circle{border-color:rgba(0, 0, 0, 0.38)}.mdc-radio [aria-disabled=true] .mdc-radio__native-control+.mdc-radio__background .mdc-radio__inner-circle,.mdc-radio .mdc-radio__native-control:disabled+.mdc-radio__background .mdc-radio__inner-circle{border-color:rgba(0, 0, 0, 0.38)}.mdc-radio .mdc-radio__background::before{background-color:#018786;background-color:var(--mdc-theme-secondary, #018786)}.mdc-radio .mdc-radio__background::before{top:calc(-1 * (40px - 20px) / 2);left:calc(-1 * (40px - 20px) / 2);width:40px;height:40px}.mdc-radio .mdc-radio__native-control{top:calc((40px - 40px) / 2);right:calc((40px - 40px) / 2);left:calc((40px - 40px) / 2);width:40px;height:40px}@media screen and (forced-colors: active),(-ms-high-contrast: active){.mdc-radio [aria-disabled=true] .mdc-radio__native-control:not(:checked)+.mdc-radio__background .mdc-radio__outer-circle,.mdc-radio .mdc-radio__native-control:disabled:not(:checked)+.mdc-radio__background .mdc-radio__outer-circle{border-color:GrayText}.mdc-radio [aria-disabled=true] .mdc-radio__native-control:checked+.mdc-radio__background .mdc-radio__outer-circle,.mdc-radio .mdc-radio__native-control:disabled:checked+.mdc-radio__background .mdc-radio__outer-circle{border-color:GrayText}.mdc-radio [aria-disabled=true] .mdc-radio__native-control+.mdc-radio__background .mdc-radio__inner-circle,.mdc-radio .mdc-radio__native-control:disabled+.mdc-radio__background .mdc-radio__inner-circle{border-color:GrayText}}.mdc-radio{display:inline-block;position:relative;flex:0 0 auto;box-sizing:content-box;width:20px;height:20px;cursor:pointer;will-change:opacity,transform,border-color,color}.mdc-radio__background{display:inline-block;position:relative;box-sizing:border-box;width:20px;height:20px}.mdc-radio__background::before{position:absolute;-webkit-transform:scale(0, 0);transform:scale(0, 0);border-radius:50%;opacity:0;pointer-events:none;content:"";transition:opacity 120ms 0ms cubic-bezier(0.4, 0, 0.6, 1),-webkit-transform 120ms 0ms cubic-bezier(0.4, 0, 0.6, 1);transition:opacity 120ms 0ms cubic-bezier(0.4, 0, 0.6, 1),transform 120ms 0ms cubic-bezier(0.4, 0, 0.6, 1);transition:opacity 120ms 0ms cubic-bezier(0.4, 0, 0.6, 1),transform 120ms 0ms cubic-bezier(0.4, 0, 0.6, 1),-webkit-transform 120ms 0ms cubic-bezier(0.4, 0, 0.6, 1)}.mdc-radio__outer-circle{position:absolute;top:0;left:0;box-sizing:border-box;width:100%;height:100%;border-width:2px;border-style:solid;border-radius:50%;transition:border-color 120ms 0ms cubic-bezier(0.4, 0, 0.6, 1)}.mdc-radio__inner-circle{position:absolute;top:0;left:0;box-sizing:border-box;width:100%;height:100%;-webkit-transform:scale(0, 0);transform:scale(0, 0);border-width:10px;border-style:solid;border-radius:50%;transition:border-color 120ms 0ms cubic-bezier(0.4, 0, 0.6, 1),-webkit-transform 120ms 0ms cubic-bezier(0.4, 0, 0.6, 1);transition:transform 120ms 0ms cubic-bezier(0.4, 0, 0.6, 1),border-color 120ms 0ms cubic-bezier(0.4, 0, 0.6, 1);transition:transform 120ms 0ms cubic-bezier(0.4, 0, 0.6, 1),border-color 120ms 0ms cubic-bezier(0.4, 0, 0.6, 1),-webkit-transform 120ms 0ms cubic-bezier(0.4, 0, 0.6, 1)}.mdc-radio__native-control{position:absolute;margin:0;padding:0;opacity:0;cursor:inherit;z-index:1}.mdc-radio--touch{margin-top:4px;margin-bottom:4px;margin-right:4px;margin-left:4px}.mdc-radio--touch .mdc-radio__native-control{top:calc((40px - 48px) / 2);right:calc((40px - 48px) / 2);left:calc((40px - 48px) / 2);width:48px;height:48px}.mdc-radio__native-control:checked+.mdc-radio__background,.mdc-radio__native-control:disabled+.mdc-radio__background{transition:opacity 120ms 0ms cubic-bezier(0, 0, 0.2, 1),-webkit-transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1);transition:opacity 120ms 0ms cubic-bezier(0, 0, 0.2, 1),transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1);transition:opacity 120ms 0ms cubic-bezier(0, 0, 0.2, 1),transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1),-webkit-transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1)}.mdc-radio__native-control:checked+.mdc-radio__background .mdc-radio__outer-circle,.mdc-radio__native-control:disabled+.mdc-radio__background .mdc-radio__outer-circle{transition:border-color 120ms 0ms cubic-bezier(0, 0, 0.2, 1)}.mdc-radio__native-control:checked+.mdc-radio__background .mdc-radio__inner-circle,.mdc-radio__native-control:disabled+.mdc-radio__background .mdc-radio__inner-circle{transition:border-color 120ms 0ms cubic-bezier(0, 0, 0.2, 1),-webkit-transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1);transition:transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1),border-color 120ms 0ms cubic-bezier(0, 0, 0.2, 1);transition:transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1),border-color 120ms 0ms cubic-bezier(0, 0, 0.2, 1),-webkit-transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1)}.mdc-radio--disabled{cursor:default;pointer-events:none}.mdc-radio__native-control:checked+.mdc-radio__background .mdc-radio__inner-circle{-webkit-transform:scale(0.5);transform:scale(0.5);transition:border-color 120ms 0ms cubic-bezier(0, 0, 0.2, 1),-webkit-transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1);transition:transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1),border-color 120ms 0ms cubic-bezier(0, 0, 0.2, 1);transition:transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1),border-color 120ms 0ms cubic-bezier(0, 0, 0.2, 1),-webkit-transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1)}.mdc-radio__native-control:disabled+.mdc-radio__background,[aria-disabled=true] .mdc-radio__native-control+.mdc-radio__background{cursor:default}.mdc-radio__native-control:focus+.mdc-radio__background::before{-webkit-transform:scale(1);transform:scale(1);opacity:.12;transition:opacity 120ms 0ms cubic-bezier(0, 0, 0.2, 1),-webkit-transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1);transition:opacity 120ms 0ms cubic-bezier(0, 0, 0.2, 1),transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1);transition:opacity 120ms 0ms cubic-bezier(0, 0, 0.2, 1),transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1),-webkit-transform 120ms 0ms cubic-bezier(0, 0, 0.2, 1)}.mdc-radio{--mdc-ripple-fg-size: 0;--mdc-ripple-left: 0;--mdc-ripple-top: 0;--mdc-ripple-fg-scale: 1;--mdc-ripple-fg-translate-end: 0;--mdc-ripple-fg-translate-start: 0;-webkit-tap-highlight-color:rgba(0,0,0,0);will-change:transform,opacity}.mdc-radio .mdc-radio__ripple::before,.mdc-radio .mdc-radio__ripple::after{position:absolute;border-radius:50%;opacity:0;pointer-events:none;content:""}.mdc-radio .mdc-radio__ripple::before{transition:opacity 15ms linear,background-color 15ms linear;z-index:1;z-index:var(--mdc-ripple-z-index, 1)}.mdc-radio .mdc-radio__ripple::after{z-index:0;z-index:var(--mdc-ripple-z-index, 0)}.mdc-radio.mdc-ripple-upgraded .mdc-radio__ripple::before{-webkit-transform:scale(var(--mdc-ripple-fg-scale, 1));transform:scale(var(--mdc-ripple-fg-scale, 1))}.mdc-radio.mdc-ripple-upgraded .mdc-radio__ripple::after{top:0;left:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:center center;transform-origin:center center}.mdc-radio.mdc-ripple-upgraded--unbounded .mdc-radio__ripple::after{top:var(--mdc-ripple-top, 0);left:var(--mdc-ripple-left, 0)}.mdc-radio.mdc-ripple-upgraded--foreground-activation .mdc-radio__ripple::after{-webkit-animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards;animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards}.mdc-radio.mdc-ripple-upgraded--foreground-deactivation .mdc-radio__ripple::after{-webkit-animation:mdc-ripple-fg-opacity-out 150ms;animation:mdc-ripple-fg-opacity-out 150ms;-webkit-transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1))}.mdc-radio .mdc-radio__ripple::before,.mdc-radio .mdc-radio__ripple::after{top:calc(50% - 50%);left:calc(50% - 50%);width:100%;height:100%}.mdc-radio.mdc-ripple-upgraded .mdc-radio__ripple::before,.mdc-radio.mdc-ripple-upgraded .mdc-radio__ripple::after{top:var(--mdc-ripple-top, calc(50% - 50%));left:var(--mdc-ripple-left, calc(50% - 50%));width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}.mdc-radio.mdc-ripple-upgraded .mdc-radio__ripple::after{width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}.mdc-radio .mdc-radio__ripple::before,.mdc-radio .mdc-radio__ripple::after{background-color:#018786;background-color:var(--mdc-ripple-color, var(--mdc-theme-secondary, #018786))}.mdc-radio:hover .mdc-radio__ripple::before,.mdc-radio.mdc-ripple-surface--hover .mdc-radio__ripple::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}.mdc-radio.mdc-ripple-upgraded--background-focused .mdc-radio__ripple::before,.mdc-radio:not(.mdc-ripple-upgraded):focus .mdc-radio__ripple::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-radio:not(.mdc-ripple-upgraded) .mdc-radio__ripple::after{transition:opacity 150ms linear}.mdc-radio:not(.mdc-ripple-upgraded):active .mdc-radio__ripple::after{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-radio.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-radio.mdc-ripple-upgraded .mdc-radio__background::before,.mdc-radio.mdc-ripple-upgraded--background-focused .mdc-radio__background::before{content:none}.mdc-radio__ripple{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none}.mdc-ripple-surface{--mdc-ripple-fg-size: 0;--mdc-ripple-left: 0;--mdc-ripple-top: 0;--mdc-ripple-fg-scale: 1;--mdc-ripple-fg-translate-end: 0;--mdc-ripple-fg-translate-start: 0;-webkit-tap-highlight-color:rgba(0,0,0,0);will-change:transform,opacity;position:relative;outline:none;overflow:hidden}.mdc-ripple-surface::before,.mdc-ripple-surface::after{position:absolute;border-radius:50%;opacity:0;pointer-events:none;content:""}.mdc-ripple-surface::before{transition:opacity 15ms linear,background-color 15ms linear;z-index:1;z-index:var(--mdc-ripple-z-index, 1)}.mdc-ripple-surface::after{z-index:0;z-index:var(--mdc-ripple-z-index, 0)}.mdc-ripple-surface.mdc-ripple-upgraded::before{-webkit-transform:scale(var(--mdc-ripple-fg-scale, 1));transform:scale(var(--mdc-ripple-fg-scale, 1))}.mdc-ripple-surface.mdc-ripple-upgraded::after{top:0;left:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:center center;transform-origin:center center}.mdc-ripple-surface.mdc-ripple-upgraded--unbounded::after{top:var(--mdc-ripple-top, 0);left:var(--mdc-ripple-left, 0)}.mdc-ripple-surface.mdc-ripple-upgraded--foreground-activation::after{-webkit-animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards;animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards}.mdc-ripple-surface.mdc-ripple-upgraded--foreground-deactivation::after{-webkit-animation:mdc-ripple-fg-opacity-out 150ms;animation:mdc-ripple-fg-opacity-out 150ms;-webkit-transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1))}.mdc-ripple-surface::before,.mdc-ripple-surface::after{top:calc(50% - 100%);left:calc(50% - 100%);width:200%;height:200%}.mdc-ripple-surface.mdc-ripple-upgraded::after{width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}.mdc-ripple-surface[data-mdc-ripple-is-unbounded],.mdc-ripple-upgraded--unbounded{overflow:visible}.mdc-ripple-surface[data-mdc-ripple-is-unbounded]::before,.mdc-ripple-surface[data-mdc-ripple-is-unbounded]::after,.mdc-ripple-upgraded--unbounded::before,.mdc-ripple-upgraded--unbounded::after{top:calc(50% - 50%);left:calc(50% - 50%);width:100%;height:100%}.mdc-ripple-surface[data-mdc-ripple-is-unbounded].mdc-ripple-upgraded::before,.mdc-ripple-surface[data-mdc-ripple-is-unbounded].mdc-ripple-upgraded::after,.mdc-ripple-upgraded--unbounded.mdc-ripple-upgraded::before,.mdc-ripple-upgraded--unbounded.mdc-ripple-upgraded::after{top:var(--mdc-ripple-top, calc(50% - 50%));left:var(--mdc-ripple-left, calc(50% - 50%));width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}.mdc-ripple-surface[data-mdc-ripple-is-unbounded].mdc-ripple-upgraded::after,.mdc-ripple-upgraded--unbounded.mdc-ripple-upgraded::after{width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}.mdc-ripple-surface::before,.mdc-ripple-surface::after{background-color:#000;background-color:var(--mdc-ripple-color, #000)}.mdc-ripple-surface:hover::before,.mdc-ripple-surface.mdc-ripple-surface--hover::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}.mdc-ripple-surface.mdc-ripple-upgraded--background-focused::before,.mdc-ripple-surface:not(.mdc-ripple-upgraded):focus::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-ripple-surface:not(.mdc-ripple-upgraded)::after{transition:opacity 150ms linear}.mdc-ripple-surface:not(.mdc-ripple-upgraded):active::after{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-ripple-surface.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-segmented-button{display:inline-block;font-size:0}.mdc-segmented-button__segment{border-color:rgba(0, 0, 0, 0.12);border-color:var(--mdc-segmented-button-outline-color, rgba(0, 0, 0, 0.12))}.mdc-segmented-button__segment{color:rgba(0, 0, 0, 0.6);color:var(--mdc-segmented-button-unselected-ink-color, rgba(0, 0, 0, 0.6))}.mdc-segmented-button__segment{background-color:white;background-color:var(--mdc-segmented-button-unselected-container-fill-color, white)}.mdc-segmented-button__segment--selected{color:#6200ee;color:var(--mdc-segmented-button-selected-ink-color, #6200ee)}.mdc-segmented-button__segment--selected{background-color:rgba(98, 0, 238, 0.08);background-color:var(--mdc-segmented-button-selected-container-fill-color, rgba(98, 0, 238, 0.08))}.mdc-segmented-button__segment{position:relative;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-button-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.875rem;font-size:var(--mdc-typography-button-font-size, 0.875rem);line-height:2.25rem;line-height:var(--mdc-typography-button-line-height, 2.25rem);font-weight:500;font-weight:var(--mdc-typography-button-font-weight, 500);letter-spacing:0.0892857143em;letter-spacing:var(--mdc-typography-button-letter-spacing, 0.0892857143em);text-decoration:none;-webkit-text-decoration:var(--mdc-typography-button-text-decoration, none);text-decoration:var(--mdc-typography-button-text-decoration, none);text-transform:uppercase;text-transform:var(--mdc-typography-button-text-transform, uppercase);display:inline-flex;vertical-align:top;align-items:center;height:36px;min-width:48px;padding:0 12px;border-width:1px 0 1px 1px}.mdc-segmented-button__segment .mdc-elevation-overlay{width:100%;height:100%;top:0;left:0}.mdc-segmented-button__segment:hover{cursor:pointer}.mdc-segmented-button__segment:focus{outline-width:0}.mdc-segmented-button__segment:first-child{border-radius:4px 0 0 4px}.mdc-segmented-button__segment:last-child{border-right-width:1px;border-radius:0 4px 4px 0}.mdc-segmented-button__segment .mdc-segmented-button__segment__touch{position:absolute;top:50%;height:48px;left:0;right:0;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.mdc-segmented-button__segment .mdc-segmented-button__segment--touch{margin-top:0px;margin-bottom:0px}.mdc-touch-target-wrapper .mdc-segmented-button__segment{border-radius:0;border-right-width:0}.mdc-touch-target-wrapper:first-child .mdc-segmented-button__segment{border-radius:4px 0 0 4px}.mdc-touch-target-wrapper:last-child .mdc-segmented-button__segment{border-right-width:1px;border-radius:0 4px 4px 0}.mdc-segmented-button__icon{width:24px;font-size:18px}.mdc-segmented-button__icon+.mdc-segmented-button__label{padding-left:6px}.mdc-segmented-button__segment{--mdc-ripple-fg-size: 0;--mdc-ripple-left: 0;--mdc-ripple-top: 0;--mdc-ripple-fg-scale: 1;--mdc-ripple-fg-translate-end: 0;--mdc-ripple-fg-translate-start: 0;-webkit-tap-highlight-color:rgba(0,0,0,0);will-change:transform,opacity;overflow:hidden}.mdc-segmented-button__segment .mdc-segmented-button__ripple::before,.mdc-segmented-button__segment .mdc-segmented-button__ripple::after{position:absolute;border-radius:50%;opacity:0;pointer-events:none;content:""}.mdc-segmented-button__segment .mdc-segmented-button__ripple::before{transition:opacity 15ms linear,background-color 15ms linear;z-index:1;z-index:var(--mdc-ripple-z-index, 1)}.mdc-segmented-button__segment .mdc-segmented-button__ripple::after{z-index:0;z-index:var(--mdc-ripple-z-index, 0)}.mdc-segmented-button__segment.mdc-ripple-upgraded .mdc-segmented-button__ripple::before{-webkit-transform:scale(var(--mdc-ripple-fg-scale, 1));transform:scale(var(--mdc-ripple-fg-scale, 1))}.mdc-segmented-button__segment.mdc-ripple-upgraded .mdc-segmented-button__ripple::after{top:0;left:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:center center;transform-origin:center center}.mdc-segmented-button__segment.mdc-ripple-upgraded--unbounded .mdc-segmented-button__ripple::after{top:var(--mdc-ripple-top, 0);left:var(--mdc-ripple-left, 0)}.mdc-segmented-button__segment.mdc-ripple-upgraded--foreground-activation .mdc-segmented-button__ripple::after{-webkit-animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards;animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards}.mdc-segmented-button__segment.mdc-ripple-upgraded--foreground-deactivation .mdc-segmented-button__ripple::after{-webkit-animation:mdc-ripple-fg-opacity-out 150ms;animation:mdc-ripple-fg-opacity-out 150ms;-webkit-transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1))}.mdc-segmented-button__segment .mdc-segmented-button__ripple::before,.mdc-segmented-button__segment .mdc-segmented-button__ripple::after{top:calc(50% - 100%);left:calc(50% - 100%);width:200%;height:200%}.mdc-segmented-button__segment.mdc-ripple-upgraded .mdc-segmented-button__ripple::after{width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}.mdc-segmented-button__segment .mdc-segmented-button__ripple::before,.mdc-segmented-button__segment .mdc-segmented-button__ripple::after{background-color:#6200ee;background-color:var(--mdc-ripple-color, #6200ee)}.mdc-segmented-button__segment:hover .mdc-segmented-button__ripple::before,.mdc-segmented-button__segment.mdc-ripple-surface--hover .mdc-segmented-button__ripple::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}.mdc-segmented-button__segment.mdc-ripple-upgraded--background-focused .mdc-segmented-button__ripple::before,.mdc-segmented-button__segment.mdc-ripple-upgraded:focus-within .mdc-segmented-button__ripple::before,.mdc-segmented-button__segment:not(.mdc-ripple-upgraded):focus .mdc-segmented-button__ripple::before,.mdc-segmented-button__segment:not(.mdc-ripple-upgraded):focus-within .mdc-segmented-button__ripple::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-segmented-button__segment:not(.mdc-ripple-upgraded) .mdc-segmented-button__ripple::after{transition:opacity 150ms linear}.mdc-segmented-button__segment:not(.mdc-ripple-upgraded):active .mdc-segmented-button__ripple::after{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-segmented-button__segment.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-segmented-button__segment .mdc-segmented-button__ripple{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none}.mdc-slider__thumb{--mdc-ripple-fg-size: 0;--mdc-ripple-left: 0;--mdc-ripple-top: 0;--mdc-ripple-fg-scale: 1;--mdc-ripple-fg-translate-end: 0;--mdc-ripple-fg-translate-start: 0;-webkit-tap-highlight-color:rgba(0,0,0,0);will-change:transform,opacity}.mdc-slider__thumb::before,.mdc-slider__thumb::after{position:absolute;border-radius:50%;opacity:0;pointer-events:none;content:""}.mdc-slider__thumb::before{transition:opacity 15ms linear,background-color 15ms linear;z-index:1;z-index:var(--mdc-ripple-z-index, 1)}.mdc-slider__thumb::after{z-index:0;z-index:var(--mdc-ripple-z-index, 0)}.mdc-slider__thumb.mdc-ripple-upgraded::before{-webkit-transform:scale(var(--mdc-ripple-fg-scale, 1));transform:scale(var(--mdc-ripple-fg-scale, 1))}.mdc-slider__thumb.mdc-ripple-upgraded::after{top:0;left:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:center center;transform-origin:center center}.mdc-slider__thumb.mdc-ripple-upgraded--unbounded::after{top:var(--mdc-ripple-top, 0);left:var(--mdc-ripple-left, 0)}.mdc-slider__thumb.mdc-ripple-upgraded--foreground-activation::after{-webkit-animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards;animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards}.mdc-slider__thumb.mdc-ripple-upgraded--foreground-deactivation::after{-webkit-animation:mdc-ripple-fg-opacity-out 150ms;animation:mdc-ripple-fg-opacity-out 150ms;-webkit-transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1))}.mdc-slider__thumb::before,.mdc-slider__thumb::after{top:calc(50% - 50%);left:calc(50% - 50%);width:100%;height:100%}.mdc-slider__thumb.mdc-ripple-upgraded::before,.mdc-slider__thumb.mdc-ripple-upgraded::after{top:var(--mdc-ripple-top, calc(50% - 50%));left:var(--mdc-ripple-left, calc(50% - 50%));width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}.mdc-slider__thumb.mdc-ripple-upgraded::after{width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}.mdc-slider__thumb::before,.mdc-slider__thumb::after{background-color:#6200ee;background-color:var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee))}.mdc-slider__thumb:hover::before,.mdc-slider__thumb.mdc-ripple-surface--hover::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}.mdc-slider__thumb.mdc-ripple-upgraded--background-focused::before,.mdc-slider__thumb:not(.mdc-ripple-upgraded):focus::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-slider__thumb:not(.mdc-ripple-upgraded)::after{transition:opacity 150ms linear}.mdc-slider__thumb:not(.mdc-ripple-upgraded):active::after{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-slider__thumb.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-slider{cursor:pointer;height:48px;margin:0 24px;position:relative;touch-action:pan-y}.mdc-slider .mdc-slider__track{height:4px;position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);width:100%}.mdc-slider .mdc-slider__track--active,.mdc-slider .mdc-slider__track--inactive{display:flex;height:100%;position:absolute;width:100%}.mdc-slider .mdc-slider__track--active{border-radius:3px;height:6px;overflow:hidden;top:-1px}.mdc-slider .mdc-slider__track--active_fill{border-top:6px solid;box-sizing:border-box;height:100%;width:100%;position:relative;-webkit-transform-origin:left;transform-origin:left}[dir=rtl] .mdc-slider .mdc-slider__track--active_fill,.mdc-slider .mdc-slider__track--active_fill[dir=rtl]{-webkit-transform-origin:right;transform-origin:right}.mdc-slider .mdc-slider__track--inactive{border-radius:2px;height:4px;left:0;top:0}.mdc-slider .mdc-slider__track--inactive::before{position:absolute;box-sizing:border-box;width:100%;height:100%;top:0;left:0;border:1px solid transparent;border-radius:inherit;content:"";pointer-events:none}.mdc-slider .mdc-slider__track--active_fill{border-color:#6200ee;border-color:var(--mdc-theme-primary, #6200ee)}.mdc-slider.mdc-slider--disabled .mdc-slider__track--active_fill{border-color:#000;border-color:var(--mdc-theme-on-surface, #000)}.mdc-slider .mdc-slider__track--inactive{background-color:#6200ee;background-color:var(--mdc-theme-primary, #6200ee);opacity:.24}.mdc-slider.mdc-slider--disabled .mdc-slider__track--inactive{background-color:#000;background-color:var(--mdc-theme-on-surface, #000);opacity:.24}.mdc-slider .mdc-slider__value-indicator-container{bottom:44px;left:50%;pointer-events:none;position:absolute;-webkit-transform:translateX(-50%);transform:translateX(-50%)}.mdc-slider .mdc-slider__value-indicator{transition:-webkit-transform 100ms 0ms cubic-bezier(0.4, 0, 1, 1);transition:transform 100ms 0ms cubic-bezier(0.4, 0, 1, 1);transition:transform 100ms 0ms cubic-bezier(0.4, 0, 1, 1), -webkit-transform 100ms 0ms cubic-bezier(0.4, 0, 1, 1);align-items:center;border-radius:4px;display:flex;height:32px;padding:0 12px;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:bottom;transform-origin:bottom}.mdc-slider .mdc-slider__value-indicator::before{border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid;bottom:-5px;content:"";height:0;left:50%;position:absolute;-webkit-transform:translateX(-50%);transform:translateX(-50%);width:0}.mdc-slider .mdc-slider__value-indicator::after{position:absolute;box-sizing:border-box;width:100%;height:100%;top:0;left:0;border:1px solid transparent;border-radius:inherit;content:"";pointer-events:none}.mdc-slider .mdc-slider__thumb--with-indicator .mdc-slider__value-indicator-container{pointer-events:auto}.mdc-slider .mdc-slider__thumb--with-indicator .mdc-slider__value-indicator{transition:-webkit-transform 100ms 0ms cubic-bezier(0, 0, 0.2, 1);transition:transform 100ms 0ms cubic-bezier(0, 0, 0.2, 1);transition:transform 100ms 0ms cubic-bezier(0, 0, 0.2, 1), -webkit-transform 100ms 0ms cubic-bezier(0, 0, 0.2, 1);-webkit-transform:scale(1);transform:scale(1)}@media(prefers-reduced-motion){.mdc-slider .mdc-slider__value-indicator,.mdc-slider .mdc-slider__thumb--with-indicator .mdc-slider__value-indicator{transition:none}}.mdc-slider .mdc-slider__value-indicator-text{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-subtitle2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.875rem;font-size:var(--mdc-typography-subtitle2-font-size, 0.875rem);line-height:1.375rem;line-height:var(--mdc-typography-subtitle2-line-height, 1.375rem);font-weight:500;font-weight:var(--mdc-typography-subtitle2-font-weight, 500);letter-spacing:0.0071428571em;letter-spacing:var(--mdc-typography-subtitle2-letter-spacing, 0.0071428571em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-subtitle2-text-decoration, inherit);text-decoration:var(--mdc-typography-subtitle2-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-subtitle2-text-transform, inherit)}.mdc-slider .mdc-slider__value-indicator{background-color:#000;opacity:.6}.mdc-slider .mdc-slider__value-indicator::before{border-top-color:#000}.mdc-slider .mdc-slider__value-indicator{color:#fff;color:var(--mdc-theme-on-primary, #fff)}.mdc-slider .mdc-slider__thumb{display:flex;height:48px;left:-24px;outline:none;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;width:48px}.mdc-slider .mdc-slider__thumb--top{z-index:1}.mdc-slider .mdc-slider__thumb--top .mdc-slider__thumb-knob,.mdc-slider .mdc-slider__thumb--top.mdc-slider__thumb:hover .mdc-slider__thumb-knob,.mdc-slider .mdc-slider__thumb--top.mdc-slider__thumb--focused .mdc-slider__thumb-knob{border-style:solid;border-width:1px;box-sizing:content-box}.mdc-slider .mdc-slider__thumb-knob{box-shadow:0px 2px 1px -1px rgba(0, 0, 0, 0.2),0px 1px 1px 0px rgba(0, 0, 0, 0.14),0px 1px 3px 0px rgba(0,0,0,.12);border:10px solid;border-radius:50%;box-sizing:border-box;height:20px;left:50%;position:absolute;top:50%;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%);width:20px}.mdc-slider .mdc-slider__thumb-knob{background-color:#6200ee;background-color:var(--mdc-theme-primary, #6200ee);border-color:#6200ee;border-color:var(--mdc-theme-primary, #6200ee)}.mdc-slider .mdc-slider__thumb--top .mdc-slider__thumb-knob,.mdc-slider .mdc-slider__thumb--top.mdc-slider__thumb:hover .mdc-slider__thumb-knob,.mdc-slider .mdc-slider__thumb--top.mdc-slider__thumb--focused .mdc-slider__thumb-knob{border-color:#fff}.mdc-slider.mdc-slider--disabled .mdc-slider__thumb-knob{background-color:#000;background-color:var(--mdc-theme-on-surface, #000);border-color:#000;border-color:var(--mdc-theme-on-surface, #000)}.mdc-slider.mdc-slider--disabled .mdc-slider__thumb--top .mdc-slider__thumb-knob,.mdc-slider.mdc-slider--disabled .mdc-slider__thumb--top.mdc-slider__thumb:hover .mdc-slider__thumb-knob,.mdc-slider.mdc-slider--disabled .mdc-slider__thumb--top.mdc-slider__thumb--focused .mdc-slider__thumb-knob{border-color:#fff}.mdc-slider .mdc-slider__thumb::before,.mdc-slider .mdc-slider__thumb::after{background-color:#6200ee;background-color:var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee))}.mdc-slider .mdc-slider__thumb:hover::before,.mdc-slider .mdc-slider__thumb.mdc-ripple-surface--hover::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}.mdc-slider .mdc-slider__thumb.mdc-ripple-upgraded--background-focused::before,.mdc-slider .mdc-slider__thumb:not(.mdc-ripple-upgraded):focus::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-slider .mdc-slider__thumb:not(.mdc-ripple-upgraded)::after{transition:opacity 150ms linear}.mdc-slider .mdc-slider__thumb:not(.mdc-ripple-upgraded):active::after{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-slider .mdc-slider__thumb.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-slider .mdc-slider__tick-marks{align-items:center;box-sizing:border-box;display:flex;height:100%;justify-content:space-between;padding:0 1px;position:absolute;width:100%}.mdc-slider .mdc-slider__tick-mark--active,.mdc-slider .mdc-slider__tick-mark--inactive{border-radius:50%;height:2px;width:2px}.mdc-slider .mdc-slider__tick-mark--active{background-color:#fff;background-color:var(--mdc-theme-on-primary, #fff);opacity:.6}.mdc-slider.mdc-slider--disabled .mdc-slider__tick-mark--active{background-color:#fff;background-color:var(--mdc-theme-on-primary, #fff);opacity:.6}.mdc-slider .mdc-slider__tick-mark--inactive{background-color:#6200ee;background-color:var(--mdc-theme-primary, #6200ee);opacity:.6}.mdc-slider.mdc-slider--disabled .mdc-slider__tick-mark--inactive{background-color:#000;background-color:var(--mdc-theme-on-surface, #000);opacity:.6}.mdc-slider.mdc-slider--disabled{opacity:.38;cursor:auto}.mdc-slider.mdc-slider--disabled .mdc-slider__thumb{pointer-events:none}.mdc-slider--discrete .mdc-slider__thumb,.mdc-slider--discrete .mdc-slider__track--active_fill{transition:-webkit-transform 80ms ease;transition:transform 80ms ease;transition:transform 80ms ease, -webkit-transform 80ms ease}@media(prefers-reduced-motion){.mdc-slider--discrete .mdc-slider__thumb,.mdc-slider--discrete .mdc-slider__track--active_fill{transition:none}}.mdc-slider__input{cursor:pointer;left:0;margin:0;height:100%;opacity:0;pointer-events:none;position:absolute;top:0;width:100%}.mdc-snackbar{z-index:8;margin:8px;display:none;position:fixed;right:0;bottom:0;left:0;align-items:center;justify-content:center;box-sizing:border-box;pointer-events:none;-webkit-tap-highlight-color:rgba(0,0,0,0)}.mdc-snackbar__surface{background-color:#333333}.mdc-snackbar__label{color:rgba(255, 255, 255, 0.87)}.mdc-snackbar__surface{min-width:344px}@media(max-width: 480px),(max-width: 344px){.mdc-snackbar__surface{min-width:100%}}.mdc-snackbar__surface{max-width:672px}.mdc-snackbar__surface{box-shadow:0px 3px 5px -1px rgba(0, 0, 0, 0.2),0px 6px 10px 0px rgba(0, 0, 0, 0.14),0px 1px 18px 0px rgba(0,0,0,.12)}.mdc-snackbar__surface{border-radius:4px;border-radius:var(--mdc-shape-small, 4px)}.mdc-snackbar--opening,.mdc-snackbar--open,.mdc-snackbar--closing{display:flex}.mdc-snackbar--open .mdc-snackbar__label,.mdc-snackbar--open .mdc-snackbar__actions{visibility:visible}.mdc-snackbar--leading{justify-content:flex-start}.mdc-snackbar--stacked .mdc-snackbar__label{padding-left:16px;padding-right:8px;padding-bottom:12px}[dir=rtl] .mdc-snackbar--stacked .mdc-snackbar__label,.mdc-snackbar--stacked .mdc-snackbar__label[dir=rtl]{padding-left:8px;padding-right:16px}.mdc-snackbar--stacked .mdc-snackbar__surface{flex-direction:column;align-items:flex-start}.mdc-snackbar--stacked .mdc-snackbar__actions{align-self:flex-end;margin-bottom:8px}.mdc-snackbar__surface{padding-left:0;padding-right:8px;display:flex;align-items:center;justify-content:flex-start;box-sizing:border-box;-webkit-transform:scale(0.8);transform:scale(0.8);opacity:0}.mdc-snackbar__surface::before{position:absolute;box-sizing:border-box;width:100%;height:100%;top:0;left:0;border:1px solid transparent;border-radius:inherit;content:"";pointer-events:none}[dir=rtl] .mdc-snackbar__surface,.mdc-snackbar__surface[dir=rtl]{padding-left:8px;padding-right:0}.mdc-snackbar--open .mdc-snackbar__surface{-webkit-transform:scale(1);transform:scale(1);opacity:1;pointer-events:auto;transition:opacity 150ms 0ms cubic-bezier(0, 0, 0.2, 1),-webkit-transform 150ms 0ms cubic-bezier(0, 0, 0.2, 1);transition:opacity 150ms 0ms cubic-bezier(0, 0, 0.2, 1),transform 150ms 0ms cubic-bezier(0, 0, 0.2, 1);transition:opacity 150ms 0ms cubic-bezier(0, 0, 0.2, 1),transform 150ms 0ms cubic-bezier(0, 0, 0.2, 1),-webkit-transform 150ms 0ms cubic-bezier(0, 0, 0.2, 1)}.mdc-snackbar--closing .mdc-snackbar__surface{-webkit-transform:scale(1);transform:scale(1);transition:opacity 75ms 0ms cubic-bezier(0.4, 0, 1, 1)}.mdc-snackbar__label{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.875rem;font-size:var(--mdc-typography-body2-font-size, 0.875rem);line-height:1.25rem;line-height:var(--mdc-typography-body2-line-height, 1.25rem);font-weight:400;font-weight:var(--mdc-typography-body2-font-weight, 400);letter-spacing:0.0178571429em;letter-spacing:var(--mdc-typography-body2-letter-spacing, 0.0178571429em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-body2-text-transform, inherit);padding-left:16px;padding-right:8px;width:100%;flex-grow:1;box-sizing:border-box;margin:0;visibility:hidden;padding-top:14px;padding-bottom:14px}[dir=rtl] .mdc-snackbar__label,.mdc-snackbar__label[dir=rtl]{padding-left:8px;padding-right:16px}.mdc-snackbar__label::before{display:inline;content:attr(data-mdc-snackbar-label-text)}.mdc-snackbar__actions{display:flex;flex-shrink:0;align-items:center;box-sizing:border-box;visibility:hidden}.mdc-snackbar__action:not(:disabled){color:#bb86fc}.mdc-snackbar__action::before,.mdc-snackbar__action::after{background-color:#bb86fc;background-color:var(--mdc-ripple-color, #bb86fc)}.mdc-snackbar__action:hover::before,.mdc-snackbar__action.mdc-ripple-surface--hover::before{opacity:0.08;opacity:var(--mdc-ripple-hover-opacity, 0.08)}.mdc-snackbar__action.mdc-ripple-upgraded--background-focused::before,.mdc-snackbar__action:not(.mdc-ripple-upgraded):focus::before{transition-duration:75ms;opacity:0.24;opacity:var(--mdc-ripple-focus-opacity, 0.24)}.mdc-snackbar__action:not(.mdc-ripple-upgraded)::after{transition:opacity 150ms linear}.mdc-snackbar__action:not(.mdc-ripple-upgraded):active::after{transition-duration:75ms;opacity:0.24;opacity:var(--mdc-ripple-press-opacity, 0.24)}.mdc-snackbar__action.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.24)}.mdc-snackbar__dismiss{color:rgba(255, 255, 255, 0.87)}.mdc-snackbar__dismiss .mdc-icon-button__ripple::before,.mdc-snackbar__dismiss .mdc-icon-button__ripple::after{background-color:rgba(255, 255, 255, 0.87);background-color:var(--mdc-ripple-color, rgba(255, 255, 255, 0.87))}.mdc-snackbar__dismiss:hover .mdc-icon-button__ripple::before,.mdc-snackbar__dismiss.mdc-ripple-surface--hover .mdc-icon-button__ripple::before{opacity:0.08;opacity:var(--mdc-ripple-hover-opacity, 0.08)}.mdc-snackbar__dismiss.mdc-ripple-upgraded--background-focused .mdc-icon-button__ripple::before,.mdc-snackbar__dismiss:not(.mdc-ripple-upgraded):focus .mdc-icon-button__ripple::before{transition-duration:75ms;opacity:0.24;opacity:var(--mdc-ripple-focus-opacity, 0.24)}.mdc-snackbar__dismiss:not(.mdc-ripple-upgraded) .mdc-icon-button__ripple::after{transition:opacity 150ms linear}.mdc-snackbar__dismiss:not(.mdc-ripple-upgraded):active .mdc-icon-button__ripple::after{transition-duration:75ms;opacity:0.24;opacity:var(--mdc-ripple-press-opacity, 0.24)}.mdc-snackbar__dismiss.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.24)}.mdc-snackbar__dismiss.mdc-snackbar__dismiss{width:36px;height:36px;padding:6px;font-size:18px}.mdc-snackbar__action+.mdc-snackbar__dismiss{margin-left:8px;margin-right:0}[dir=rtl] .mdc-snackbar__action+.mdc-snackbar__dismiss,.mdc-snackbar__action+.mdc-snackbar__dismiss[dir=rtl]{margin-left:0;margin-right:8px}.mdc-switch__thumb-underlay{left:-14px;right:initial;top:-17px;width:48px;height:48px}[dir=rtl] .mdc-switch__thumb-underlay,.mdc-switch__thumb-underlay[dir=rtl]{left:initial;right:-14px}.mdc-switch__native-control{width:64px;height:48px}.mdc-switch{display:inline-block;position:relative;outline:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mdc-switch.mdc-switch--checked .mdc-switch__track{background-color:#018786;background-color:var(--mdc-theme-secondary, #018786)}.mdc-switch.mdc-switch--checked .mdc-switch__thumb{background-color:#018786;background-color:var(--mdc-theme-secondary, #018786);border-color:#018786;border-color:var(--mdc-theme-secondary, #018786)}.mdc-switch:not(.mdc-switch--checked) .mdc-switch__track{background-color:#000;background-color:var(--mdc-theme-on-surface, #000)}.mdc-switch:not(.mdc-switch--checked) .mdc-switch__thumb{background-color:#fff;background-color:var(--mdc-theme-surface, #fff);border-color:#fff;border-color:var(--mdc-theme-surface, #fff)}.mdc-switch__native-control{left:0;right:initial;position:absolute;top:0;margin:0;opacity:0;cursor:pointer;pointer-events:auto;transition:-webkit-transform 90ms cubic-bezier(0.4, 0, 0.2, 1);transition:transform 90ms cubic-bezier(0.4, 0, 0.2, 1);transition:transform 90ms cubic-bezier(0.4, 0, 0.2, 1), -webkit-transform 90ms cubic-bezier(0.4, 0, 0.2, 1)}[dir=rtl] .mdc-switch__native-control,.mdc-switch__native-control[dir=rtl]{left:initial;right:0}.mdc-switch__track{box-sizing:border-box;width:36px;height:14px;border:1px solid transparent;border-radius:7px;opacity:.38;transition:opacity 90ms cubic-bezier(0.4, 0, 0.2, 1),background-color 90ms cubic-bezier(0.4, 0, 0.2, 1),border-color 90ms cubic-bezier(0.4, 0, 0.2, 1)}.mdc-switch__thumb-underlay{display:flex;position:absolute;align-items:center;justify-content:center;-webkit-transform:translateX(0);transform:translateX(0);transition:background-color 90ms cubic-bezier(0.4, 0, 0.2, 1),border-color 90ms cubic-bezier(0.4, 0, 0.2, 1),-webkit-transform 90ms cubic-bezier(0.4, 0, 0.2, 1);transition:transform 90ms cubic-bezier(0.4, 0, 0.2, 1),background-color 90ms cubic-bezier(0.4, 0, 0.2, 1),border-color 90ms cubic-bezier(0.4, 0, 0.2, 1);transition:transform 90ms cubic-bezier(0.4, 0, 0.2, 1),background-color 90ms cubic-bezier(0.4, 0, 0.2, 1),border-color 90ms cubic-bezier(0.4, 0, 0.2, 1),-webkit-transform 90ms cubic-bezier(0.4, 0, 0.2, 1)}.mdc-switch__thumb{box-shadow:0px 3px 1px -2px rgba(0, 0, 0, 0.2),0px 2px 2px 0px rgba(0, 0, 0, 0.14),0px 1px 5px 0px rgba(0,0,0,.12);box-sizing:border-box;width:20px;height:20px;border:10px solid;border-radius:50%;pointer-events:none;z-index:1}.mdc-switch--checked .mdc-switch__track{opacity:.54}.mdc-switch--checked .mdc-switch__thumb-underlay{-webkit-transform:translateX(16px);transform:translateX(16px)}[dir=rtl] .mdc-switch--checked .mdc-switch__thumb-underlay,.mdc-switch--checked .mdc-switch__thumb-underlay[dir=rtl]{-webkit-transform:translateX(-16px);transform:translateX(-16px)}.mdc-switch--checked .mdc-switch__native-control{-webkit-transform:translateX(-16px);transform:translateX(-16px)}[dir=rtl] .mdc-switch--checked .mdc-switch__native-control,.mdc-switch--checked .mdc-switch__native-control[dir=rtl]{-webkit-transform:translateX(16px);transform:translateX(16px)}.mdc-switch--disabled{opacity:.38;pointer-events:none}.mdc-switch--disabled .mdc-switch__thumb{border-width:1px}.mdc-switch--disabled .mdc-switch__native-control{cursor:default;pointer-events:none}.mdc-switch:not(.mdc-switch--checked) .mdc-switch__thumb-underlay::before,.mdc-switch:not(.mdc-switch--checked) .mdc-switch__thumb-underlay::after{background-color:#9e9e9e;background-color:var(--mdc-ripple-color, #9e9e9e)}.mdc-switch:not(.mdc-switch--checked) .mdc-switch__thumb-underlay:hover::before,.mdc-switch:not(.mdc-switch--checked) .mdc-switch__thumb-underlay.mdc-ripple-surface--hover::before{opacity:0.08;opacity:var(--mdc-ripple-hover-opacity, 0.08)}.mdc-switch:not(.mdc-switch--checked) .mdc-switch__thumb-underlay.mdc-ripple-upgraded--background-focused::before,.mdc-switch:not(.mdc-switch--checked) .mdc-switch__thumb-underlay:not(.mdc-ripple-upgraded):focus::before{transition-duration:75ms;opacity:0.24;opacity:var(--mdc-ripple-focus-opacity, 0.24)}.mdc-switch:not(.mdc-switch--checked) .mdc-switch__thumb-underlay:not(.mdc-ripple-upgraded)::after{transition:opacity 150ms linear}.mdc-switch:not(.mdc-switch--checked) .mdc-switch__thumb-underlay:not(.mdc-ripple-upgraded):active::after{transition-duration:75ms;opacity:0.24;opacity:var(--mdc-ripple-press-opacity, 0.24)}.mdc-switch:not(.mdc-switch--checked) .mdc-switch__thumb-underlay.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.24)}.mdc-switch__thumb-underlay{--mdc-ripple-fg-size: 0;--mdc-ripple-left: 0;--mdc-ripple-top: 0;--mdc-ripple-fg-scale: 1;--mdc-ripple-fg-translate-end: 0;--mdc-ripple-fg-translate-start: 0;-webkit-tap-highlight-color:rgba(0,0,0,0);will-change:transform,opacity}.mdc-switch__thumb-underlay::before,.mdc-switch__thumb-underlay::after{position:absolute;border-radius:50%;opacity:0;pointer-events:none;content:""}.mdc-switch__thumb-underlay::before{transition:opacity 15ms linear,background-color 15ms linear;z-index:1;z-index:var(--mdc-ripple-z-index, 1)}.mdc-switch__thumb-underlay::after{z-index:0;z-index:var(--mdc-ripple-z-index, 0)}.mdc-switch__thumb-underlay.mdc-ripple-upgraded::before{-webkit-transform:scale(var(--mdc-ripple-fg-scale, 1));transform:scale(var(--mdc-ripple-fg-scale, 1))}.mdc-switch__thumb-underlay.mdc-ripple-upgraded::after{top:0;left:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:center center;transform-origin:center center}.mdc-switch__thumb-underlay.mdc-ripple-upgraded--unbounded::after{top:var(--mdc-ripple-top, 0);left:var(--mdc-ripple-left, 0)}.mdc-switch__thumb-underlay.mdc-ripple-upgraded--foreground-activation::after{-webkit-animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards;animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards}.mdc-switch__thumb-underlay.mdc-ripple-upgraded--foreground-deactivation::after{-webkit-animation:mdc-ripple-fg-opacity-out 150ms;animation:mdc-ripple-fg-opacity-out 150ms;-webkit-transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1))}.mdc-switch__thumb-underlay::before,.mdc-switch__thumb-underlay::after{top:calc(50% - 50%);left:calc(50% - 50%);width:100%;height:100%}.mdc-switch__thumb-underlay.mdc-ripple-upgraded::before,.mdc-switch__thumb-underlay.mdc-ripple-upgraded::after{top:var(--mdc-ripple-top, calc(50% - 50%));left:var(--mdc-ripple-left, calc(50% - 50%));width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}.mdc-switch__thumb-underlay.mdc-ripple-upgraded::after{width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}.mdc-switch__thumb-underlay::before,.mdc-switch__thumb-underlay::after{background-color:#018786;background-color:var(--mdc-ripple-color, var(--mdc-theme-secondary, #018786))}.mdc-switch__thumb-underlay:hover::before,.mdc-switch__thumb-underlay.mdc-ripple-surface--hover::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}.mdc-switch__thumb-underlay.mdc-ripple-upgraded--background-focused::before,.mdc-switch__thumb-underlay:not(.mdc-ripple-upgraded):focus::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-switch__thumb-underlay:not(.mdc-ripple-upgraded)::after{transition:opacity 150ms linear}.mdc-switch__thumb-underlay:not(.mdc-ripple-upgraded):active::after{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-switch__thumb-underlay.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-tab{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-button-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.875rem;font-size:var(--mdc-typography-button-font-size, 0.875rem);line-height:2.25rem;line-height:var(--mdc-typography-button-line-height, 2.25rem);font-weight:500;font-weight:var(--mdc-typography-button-font-weight, 500);letter-spacing:0.0892857143em;letter-spacing:var(--mdc-typography-button-letter-spacing, 0.0892857143em);text-decoration:none;-webkit-text-decoration:var(--mdc-typography-button-text-decoration, none);text-decoration:var(--mdc-typography-button-text-decoration, none);text-transform:uppercase;text-transform:var(--mdc-typography-button-text-transform, uppercase);position:relative}.mdc-tab .mdc-tab__text-label{color:rgba(0, 0, 0, 0.6)}.mdc-tab .mdc-tab__icon{color:rgba(0, 0, 0, 0.54);fill:currentColor}.mdc-tab__content{position:relative}.mdc-tab__icon{width:24px;height:24px;font-size:24px}.mdc-tab--active .mdc-tab__text-label{color:#6200ee;color:var(--mdc-theme-primary, #6200ee)}.mdc-tab--active .mdc-tab__icon{color:#6200ee;color:var(--mdc-theme-primary, #6200ee);fill:currentColor}.mdc-tab{background:none}.mdc-tab{min-width:90px;padding-right:24px;padding-left:24px;display:flex;flex:1 0 auto;justify-content:center;box-sizing:border-box;margin:0;padding-top:0;padding-bottom:0;border:none;outline:none;text-align:center;white-space:nowrap;cursor:pointer;-webkit-appearance:none;z-index:1}.mdc-tab::-moz-focus-inner{padding:0;border:0}.mdc-tab--min-width{flex:0 1 auto}.mdc-tab__content{display:flex;align-items:center;justify-content:center;height:inherit;pointer-events:none}.mdc-tab__text-label{transition:150ms color linear;display:inline-block;line-height:1;z-index:2}.mdc-tab__icon{transition:150ms color linear;z-index:2}.mdc-tab--stacked .mdc-tab__content{flex-direction:column;align-items:center;justify-content:center}.mdc-tab--stacked .mdc-tab__text-label{padding-top:6px;padding-bottom:4px}.mdc-tab--active .mdc-tab__text-label,.mdc-tab--active .mdc-tab__icon{transition-delay:100ms}.mdc-tab:not(.mdc-tab--stacked) .mdc-tab__icon+.mdc-tab__text-label{padding-left:8px;padding-right:0}[dir=rtl] .mdc-tab:not(.mdc-tab--stacked) .mdc-tab__icon+.mdc-tab__text-label,.mdc-tab:not(.mdc-tab--stacked) .mdc-tab__icon+.mdc-tab__text-label[dir=rtl]{padding-left:0;padding-right:8px}.mdc-tab{--mdc-ripple-fg-size: 0;--mdc-ripple-left: 0;--mdc-ripple-top: 0;--mdc-ripple-fg-scale: 1;--mdc-ripple-fg-translate-end: 0;--mdc-ripple-fg-translate-start: 0;-webkit-tap-highlight-color:rgba(0,0,0,0)}.mdc-tab .mdc-tab__ripple::before,.mdc-tab .mdc-tab__ripple::after{position:absolute;border-radius:50%;opacity:0;pointer-events:none;content:""}.mdc-tab .mdc-tab__ripple::before{transition:opacity 15ms linear,background-color 15ms linear;z-index:1;z-index:var(--mdc-ripple-z-index, 1)}.mdc-tab .mdc-tab__ripple::after{z-index:0;z-index:var(--mdc-ripple-z-index, 0)}.mdc-tab.mdc-ripple-upgraded .mdc-tab__ripple::before{-webkit-transform:scale(var(--mdc-ripple-fg-scale, 1));transform:scale(var(--mdc-ripple-fg-scale, 1))}.mdc-tab.mdc-ripple-upgraded .mdc-tab__ripple::after{top:0;left:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:center center;transform-origin:center center}.mdc-tab.mdc-ripple-upgraded--unbounded .mdc-tab__ripple::after{top:var(--mdc-ripple-top, 0);left:var(--mdc-ripple-left, 0)}.mdc-tab.mdc-ripple-upgraded--foreground-activation .mdc-tab__ripple::after{-webkit-animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards;animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards}.mdc-tab.mdc-ripple-upgraded--foreground-deactivation .mdc-tab__ripple::after{-webkit-animation:mdc-ripple-fg-opacity-out 150ms;animation:mdc-ripple-fg-opacity-out 150ms;-webkit-transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1))}.mdc-tab .mdc-tab__ripple::before,.mdc-tab .mdc-tab__ripple::after{top:calc(50% - 100%);left:calc(50% - 100%);width:200%;height:200%}.mdc-tab.mdc-ripple-upgraded .mdc-tab__ripple::after{width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}.mdc-tab .mdc-tab__ripple::before,.mdc-tab .mdc-tab__ripple::after{background-color:#6200ee;background-color:var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee))}.mdc-tab:hover .mdc-tab__ripple::before,.mdc-tab.mdc-ripple-surface--hover .mdc-tab__ripple::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}.mdc-tab.mdc-ripple-upgraded--background-focused .mdc-tab__ripple::before,.mdc-tab:not(.mdc-ripple-upgraded):focus .mdc-tab__ripple::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-tab:not(.mdc-ripple-upgraded) .mdc-tab__ripple::after{transition:opacity 150ms linear}.mdc-tab:not(.mdc-ripple-upgraded):active .mdc-tab__ripple::after{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-tab.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-tab__ripple{position:absolute;top:0;left:0;width:100%;height:100%;overflow:hidden;will-change:transform,opacity}.mdc-tab-bar{width:100%}.mdc-tab{height:48px}.mdc-tab--stacked{height:72px}.mdc-tab-indicator .mdc-tab-indicator__content--underline{border-color:#6200ee;border-color:var(--mdc-theme-primary, #6200ee)}.mdc-tab-indicator .mdc-tab-indicator__content--icon{color:#018786;color:var(--mdc-theme-secondary, #018786)}.mdc-tab-indicator .mdc-tab-indicator__content--underline{border-top-width:2px}.mdc-tab-indicator .mdc-tab-indicator__content--icon{height:34px;font-size:34px}.mdc-tab-indicator{display:flex;position:absolute;top:0;left:0;justify-content:center;width:100%;height:100%;pointer-events:none;z-index:1}.mdc-tab-indicator__content{-webkit-transform-origin:left;transform-origin:left;opacity:0}.mdc-tab-indicator__content--underline{align-self:flex-end;box-sizing:border-box;width:100%;border-top-style:solid}.mdc-tab-indicator__content--icon{align-self:center;margin:0 auto}.mdc-tab-indicator--active .mdc-tab-indicator__content{opacity:1}.mdc-tab-indicator .mdc-tab-indicator__content{transition:250ms -webkit-transform cubic-bezier(0.4, 0, 0.2, 1);transition:250ms transform cubic-bezier(0.4, 0, 0.2, 1);transition:250ms transform cubic-bezier(0.4, 0, 0.2, 1), 250ms -webkit-transform cubic-bezier(0.4, 0, 0.2, 1)}.mdc-tab-indicator--no-transition .mdc-tab-indicator__content{transition:none}.mdc-tab-indicator--fade .mdc-tab-indicator__content{transition:150ms opacity linear}.mdc-tab-indicator--active.mdc-tab-indicator--fade .mdc-tab-indicator__content{transition-delay:100ms}.mdc-tab-scroller{overflow-y:hidden}.mdc-tab-scroller.mdc-tab-scroller--animating .mdc-tab-scroller__scroll-content{transition:250ms -webkit-transform cubic-bezier(0.4, 0, 0.2, 1);transition:250ms transform cubic-bezier(0.4, 0, 0.2, 1);transition:250ms transform cubic-bezier(0.4, 0, 0.2, 1), 250ms -webkit-transform cubic-bezier(0.4, 0, 0.2, 1)}.mdc-tab-scroller__test{position:absolute;top:-9999px;width:100px;height:100px;overflow-x:scroll}.mdc-tab-scroller__scroll-area{-webkit-overflow-scrolling:touch;display:flex;overflow-x:hidden}.mdc-tab-scroller__scroll-area::-webkit-scrollbar,.mdc-tab-scroller__test::-webkit-scrollbar{display:none}.mdc-tab-scroller__scroll-area--scroll{overflow-x:scroll}.mdc-tab-scroller__scroll-content{position:relative;display:flex;flex:1 0 auto;-webkit-transform:none;transform:none;will-change:transform}.mdc-tab-scroller--align-start .mdc-tab-scroller__scroll-content{justify-content:flex-start}.mdc-tab-scroller--align-end .mdc-tab-scroller__scroll-content{justify-content:flex-end}.mdc-tab-scroller--align-center .mdc-tab-scroller__scroll-content{justify-content:center}.mdc-tab-scroller--animating .mdc-tab-scroller__scroll-area{-webkit-overflow-scrolling:auto}.mdc-text-field--filled{--mdc-ripple-fg-size: 0;--mdc-ripple-left: 0;--mdc-ripple-top: 0;--mdc-ripple-fg-scale: 1;--mdc-ripple-fg-translate-end: 0;--mdc-ripple-fg-translate-start: 0;-webkit-tap-highlight-color:rgba(0,0,0,0);will-change:transform,opacity}.mdc-text-field--filled .mdc-text-field__ripple::before,.mdc-text-field--filled .mdc-text-field__ripple::after{position:absolute;border-radius:50%;opacity:0;pointer-events:none;content:""}.mdc-text-field--filled .mdc-text-field__ripple::before{transition:opacity 15ms linear,background-color 15ms linear;z-index:1;z-index:var(--mdc-ripple-z-index, 1)}.mdc-text-field--filled .mdc-text-field__ripple::after{z-index:0;z-index:var(--mdc-ripple-z-index, 0)}.mdc-text-field--filled.mdc-ripple-upgraded .mdc-text-field__ripple::before{-webkit-transform:scale(var(--mdc-ripple-fg-scale, 1));transform:scale(var(--mdc-ripple-fg-scale, 1))}.mdc-text-field--filled.mdc-ripple-upgraded .mdc-text-field__ripple::after{top:0;left:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:center center;transform-origin:center center}.mdc-text-field--filled.mdc-ripple-upgraded--unbounded .mdc-text-field__ripple::after{top:var(--mdc-ripple-top, 0);left:var(--mdc-ripple-left, 0)}.mdc-text-field--filled.mdc-ripple-upgraded--foreground-activation .mdc-text-field__ripple::after{-webkit-animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards;animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards}.mdc-text-field--filled.mdc-ripple-upgraded--foreground-deactivation .mdc-text-field__ripple::after{-webkit-animation:mdc-ripple-fg-opacity-out 150ms;animation:mdc-ripple-fg-opacity-out 150ms;-webkit-transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1))}.mdc-text-field--filled .mdc-text-field__ripple::before,.mdc-text-field--filled .mdc-text-field__ripple::after{top:calc(50% - 100%);left:calc(50% - 100%);width:200%;height:200%}.mdc-text-field--filled.mdc-ripple-upgraded .mdc-text-field__ripple::after{width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}.mdc-text-field__ripple{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none}.mdc-text-field{border-top-left-radius:4px;border-top-left-radius:var(--mdc-shape-small, 4px);border-top-right-radius:4px;border-top-right-radius:var(--mdc-shape-small, 4px);border-bottom-right-radius:0;border-bottom-left-radius:0;display:inline-flex;align-items:baseline;padding:0 16px;position:relative;box-sizing:border-box;overflow:hidden;will-change:opacity,transform,color}.mdc-text-field:not(.mdc-text-field--disabled) .mdc-floating-label{color:rgba(0, 0, 0, 0.6)}.mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field__input{color:rgba(0, 0, 0, 0.87)}@media all{.mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field__input::-webkit-input-placeholder{color:rgba(0, 0, 0, 0.54)}.mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field__input:-ms-input-placeholder{color:rgba(0, 0, 0, 0.54)}.mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field__input::-ms-input-placeholder{color:rgba(0, 0, 0, 0.54)}.mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field__input::placeholder{color:rgba(0, 0, 0, 0.54)}}@media all{.mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field__input:-ms-input-placeholder{color:rgba(0, 0, 0, 0.54)}}.mdc-text-field .mdc-text-field__input{caret-color:#6200ee;caret-color:var(--mdc-theme-primary, #6200ee)}.mdc-text-field:not(.mdc-text-field--disabled)+.mdc-text-field-helper-line .mdc-text-field-helper-text{color:rgba(0, 0, 0, 0.6)}.mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field-character-counter,.mdc-text-field:not(.mdc-text-field--disabled)+.mdc-text-field-helper-line .mdc-text-field-character-counter{color:rgba(0, 0, 0, 0.6)}.mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field__icon--leading{color:rgba(0, 0, 0, 0.54)}.mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field__icon--trailing{color:rgba(0, 0, 0, 0.54)}.mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field__affix--prefix{color:rgba(0, 0, 0, 0.6)}.mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field__affix--suffix{color:rgba(0, 0, 0, 0.6)}.mdc-text-field .mdc-floating-label{top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);pointer-events:none}.mdc-text-field__input{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-subtitle1-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:1rem;font-size:var(--mdc-typography-subtitle1-font-size, 1rem);font-weight:400;font-weight:var(--mdc-typography-subtitle1-font-weight, 400);letter-spacing:0.009375em;letter-spacing:var(--mdc-typography-subtitle1-letter-spacing, 0.009375em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-subtitle1-text-decoration, inherit);text-decoration:var(--mdc-typography-subtitle1-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-subtitle1-text-transform, inherit);height:28px;transition:opacity 150ms 0ms cubic-bezier(0.4, 0, 0.2, 1);width:100%;min-width:0;border:none;border-radius:0;background:none;-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:0}.mdc-text-field__input::-ms-clear{display:none}.mdc-text-field__input::-webkit-calendar-picker-indicator{display:none}.mdc-text-field__input:focus{outline:none}.mdc-text-field__input:invalid{box-shadow:none}@media all{.mdc-text-field__input::-webkit-input-placeholder{transition:opacity 67ms 0ms cubic-bezier(0.4, 0, 0.2, 1);opacity:0}.mdc-text-field__input:-ms-input-placeholder{transition:opacity 67ms 0ms cubic-bezier(0.4, 0, 0.2, 1);opacity:0}.mdc-text-field__input::-ms-input-placeholder{transition:opacity 67ms 0ms cubic-bezier(0.4, 0, 0.2, 1);opacity:0}.mdc-text-field__input::placeholder{transition:opacity 67ms 0ms cubic-bezier(0.4, 0, 0.2, 1);opacity:0}}@media all{.mdc-text-field__input:-ms-input-placeholder{transition:opacity 67ms 0ms cubic-bezier(0.4, 0, 0.2, 1);opacity:0}}@media all{.mdc-text-field--no-label .mdc-text-field__input::-webkit-input-placeholder,.mdc-text-field--focused .mdc-text-field__input::-webkit-input-placeholder{transition-delay:40ms;transition-duration:110ms;opacity:1}.mdc-text-field--no-label .mdc-text-field__input:-ms-input-placeholder,.mdc-text-field--focused .mdc-text-field__input:-ms-input-placeholder{transition-delay:40ms;transition-duration:110ms;opacity:1}.mdc-text-field--no-label .mdc-text-field__input::-ms-input-placeholder,.mdc-text-field--focused .mdc-text-field__input::-ms-input-placeholder{transition-delay:40ms;transition-duration:110ms;opacity:1}.mdc-text-field--no-label .mdc-text-field__input::placeholder,.mdc-text-field--focused .mdc-text-field__input::placeholder{transition-delay:40ms;transition-duration:110ms;opacity:1}}@media all{.mdc-text-field--no-label .mdc-text-field__input:-ms-input-placeholder,.mdc-text-field--focused .mdc-text-field__input:-ms-input-placeholder{transition-delay:40ms;transition-duration:110ms;opacity:1}}.mdc-text-field__affix{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-subtitle1-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:1rem;font-size:var(--mdc-typography-subtitle1-font-size, 1rem);font-weight:400;font-weight:var(--mdc-typography-subtitle1-font-weight, 400);letter-spacing:0.009375em;letter-spacing:var(--mdc-typography-subtitle1-letter-spacing, 0.009375em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-subtitle1-text-decoration, inherit);text-decoration:var(--mdc-typography-subtitle1-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-subtitle1-text-transform, inherit);height:28px;transition:opacity 150ms 0ms cubic-bezier(0.4, 0, 0.2, 1);opacity:0;white-space:nowrap}.mdc-text-field--label-floating .mdc-text-field__affix,.mdc-text-field--no-label .mdc-text-field__affix{opacity:1}@supports(-webkit-hyphens: none){.mdc-text-field--outlined .mdc-text-field__affix{align-items:center;align-self:center;display:inline-flex;height:100%}}.mdc-text-field__affix--prefix{padding-left:0;padding-right:2px}[dir=rtl] .mdc-text-field__affix--prefix,.mdc-text-field__affix--prefix[dir=rtl]{padding-left:2px;padding-right:0}.mdc-text-field--end-aligned .mdc-text-field__affix--prefix{padding-left:0;padding-right:12px}[dir=rtl] .mdc-text-field--end-aligned .mdc-text-field__affix--prefix,.mdc-text-field--end-aligned .mdc-text-field__affix--prefix[dir=rtl]{padding-left:12px;padding-right:0}.mdc-text-field__affix--suffix{padding-left:12px;padding-right:0}[dir=rtl] .mdc-text-field__affix--suffix,.mdc-text-field__affix--suffix[dir=rtl]{padding-left:0;padding-right:12px}.mdc-text-field--end-aligned .mdc-text-field__affix--suffix{padding-left:2px;padding-right:0}[dir=rtl] .mdc-text-field--end-aligned .mdc-text-field__affix--suffix,.mdc-text-field--end-aligned .mdc-text-field__affix--suffix[dir=rtl]{padding-left:0;padding-right:2px}.mdc-text-field--filled{height:56px}.mdc-text-field--filled .mdc-text-field__ripple::before,.mdc-text-field--filled .mdc-text-field__ripple::after{background-color:rgba(0, 0, 0, 0.87);background-color:var(--mdc-ripple-color, rgba(0, 0, 0, 0.87))}.mdc-text-field--filled:hover .mdc-text-field__ripple::before,.mdc-text-field--filled.mdc-ripple-surface--hover .mdc-text-field__ripple::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}.mdc-text-field--filled.mdc-ripple-upgraded--background-focused .mdc-text-field__ripple::before,.mdc-text-field--filled:not(.mdc-ripple-upgraded):focus .mdc-text-field__ripple::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-text-field--filled::before{display:inline-block;width:0;height:40px;content:"";vertical-align:0}.mdc-text-field--filled:not(.mdc-text-field--disabled){background-color:whitesmoke}.mdc-text-field--filled:not(.mdc-text-field--disabled) .mdc-line-ripple::before{border-bottom-color:rgba(0, 0, 0, 0.42)}.mdc-text-field--filled:not(.mdc-text-field--disabled):hover .mdc-line-ripple::before{border-bottom-color:rgba(0, 0, 0, 0.87)}.mdc-text-field--filled .mdc-line-ripple::after{border-bottom-color:#6200ee;border-bottom-color:var(--mdc-theme-primary, #6200ee)}.mdc-text-field--filled .mdc-floating-label{left:16px;right:initial}[dir=rtl] .mdc-text-field--filled .mdc-floating-label,.mdc-text-field--filled .mdc-floating-label[dir=rtl]{left:initial;right:16px}.mdc-text-field--filled .mdc-floating-label--float-above{-webkit-transform:translateY(-106%) scale(0.75);transform:translateY(-106%) scale(0.75)}.mdc-text-field--filled.mdc-text-field--no-label .mdc-text-field__input{height:100%}.mdc-text-field--filled.mdc-text-field--no-label .mdc-floating-label{display:none}.mdc-text-field--filled.mdc-text-field--no-label::before{display:none}@supports(-webkit-hyphens: none){.mdc-text-field--filled.mdc-text-field--no-label .mdc-text-field__affix{align-items:center;align-self:center;display:inline-flex;height:100%}}.mdc-text-field--outlined{height:56px;overflow:visible}.mdc-text-field--outlined .mdc-floating-label--float-above{-webkit-transform:translateY(-37.25px) scale(1);transform:translateY(-37.25px) scale(1)}.mdc-text-field--outlined .mdc-floating-label--float-above{font-size:.75rem}.mdc-text-field--outlined.mdc-notched-outline--upgraded .mdc-floating-label--float-above,.mdc-text-field--outlined .mdc-notched-outline--upgraded .mdc-floating-label--float-above{-webkit-transform:translateY(-34.75px) scale(0.75);transform:translateY(-34.75px) scale(0.75)}.mdc-text-field--outlined.mdc-notched-outline--upgraded .mdc-floating-label--float-above,.mdc-text-field--outlined .mdc-notched-outline--upgraded .mdc-floating-label--float-above{font-size:1rem}.mdc-text-field--outlined .mdc-floating-label--shake{-webkit-animation:mdc-floating-label-shake-float-above-text-field-outlined 250ms 1;animation:mdc-floating-label-shake-float-above-text-field-outlined 250ms 1}@-webkit-keyframes mdc-floating-label-shake-float-above-text-field-outlined{0%{-webkit-transform:translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75);transform:translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75)}33%{-webkit-animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);-webkit-transform:translateX(calc(4% - 0%)) translateY(-34.75px) scale(0.75);transform:translateX(calc(4% - 0%)) translateY(-34.75px) scale(0.75)}66%{-webkit-animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);-webkit-transform:translateX(calc(-4% - 0%)) translateY(-34.75px) scale(0.75);transform:translateX(calc(-4% - 0%)) translateY(-34.75px) scale(0.75)}100%{-webkit-transform:translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75);transform:translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75)}}@keyframes mdc-floating-label-shake-float-above-text-field-outlined{0%{-webkit-transform:translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75);transform:translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75)}33%{-webkit-animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);-webkit-transform:translateX(calc(4% - 0%)) translateY(-34.75px) scale(0.75);transform:translateX(calc(4% - 0%)) translateY(-34.75px) scale(0.75)}66%{-webkit-animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);-webkit-transform:translateX(calc(-4% - 0%)) translateY(-34.75px) scale(0.75);transform:translateX(calc(-4% - 0%)) translateY(-34.75px) scale(0.75)}100%{-webkit-transform:translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75);transform:translateX(calc(0 - 0%)) translateY(-34.75px) scale(0.75)}}.mdc-text-field--outlined .mdc-text-field__input{height:100%}.mdc-text-field--outlined:not(.mdc-text-field--disabled) .mdc-notched-outline__leading,.mdc-text-field--outlined:not(.mdc-text-field--disabled) .mdc-notched-outline__notch,.mdc-text-field--outlined:not(.mdc-text-field--disabled) .mdc-notched-outline__trailing{border-color:rgba(0, 0, 0, 0.38)}.mdc-text-field--outlined:not(.mdc-text-field--disabled):not(.mdc-text-field--focused):hover .mdc-notched-outline .mdc-notched-outline__leading,.mdc-text-field--outlined:not(.mdc-text-field--disabled):not(.mdc-text-field--focused):hover .mdc-notched-outline .mdc-notched-outline__notch,.mdc-text-field--outlined:not(.mdc-text-field--disabled):not(.mdc-text-field--focused):hover .mdc-notched-outline .mdc-notched-outline__trailing{border-color:rgba(0, 0, 0, 0.87)}.mdc-text-field--outlined:not(.mdc-text-field--disabled).mdc-text-field--focused .mdc-notched-outline__leading,.mdc-text-field--outlined:not(.mdc-text-field--disabled).mdc-text-field--focused .mdc-notched-outline__notch,.mdc-text-field--outlined:not(.mdc-text-field--disabled).mdc-text-field--focused .mdc-notched-outline__trailing{border-color:#6200ee;border-color:var(--mdc-theme-primary, #6200ee)}.mdc-text-field--outlined .mdc-notched-outline .mdc-notched-outline__leading{border-top-left-radius:4px;border-top-left-radius:var(--mdc-shape-small, 4px);border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:4px;border-bottom-left-radius:var(--mdc-shape-small, 4px)}[dir=rtl] .mdc-text-field--outlined .mdc-notched-outline .mdc-notched-outline__leading,.mdc-text-field--outlined .mdc-notched-outline .mdc-notched-outline__leading[dir=rtl]{border-top-left-radius:0;border-top-right-radius:4px;border-top-right-radius:var(--mdc-shape-small, 4px);border-bottom-right-radius:4px;border-bottom-right-radius:var(--mdc-shape-small, 4px);border-bottom-left-radius:0}@supports(top: max(0%)){.mdc-text-field--outlined .mdc-notched-outline .mdc-notched-outline__leading{width:max(12px, var(--mdc-shape-small, 4px))}}@supports(top: max(0%)){.mdc-text-field--outlined .mdc-notched-outline .mdc-notched-outline__notch{max-width:calc(100% - max(12px, var(--mdc-shape-small, 4px)) * 2)}}.mdc-text-field--outlined .mdc-notched-outline .mdc-notched-outline__trailing{border-top-left-radius:0;border-top-right-radius:4px;border-top-right-radius:var(--mdc-shape-small, 4px);border-bottom-right-radius:4px;border-bottom-right-radius:var(--mdc-shape-small, 4px);border-bottom-left-radius:0}[dir=rtl] .mdc-text-field--outlined .mdc-notched-outline .mdc-notched-outline__trailing,.mdc-text-field--outlined .mdc-notched-outline .mdc-notched-outline__trailing[dir=rtl]{border-top-left-radius:4px;border-top-left-radius:var(--mdc-shape-small, 4px);border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:4px;border-bottom-left-radius:var(--mdc-shape-small, 4px)}@supports(top: max(0%)){.mdc-text-field--outlined{padding-left:max(16px, calc(var(--mdc-shape-small, 4px) + 4px))}}@supports(top: max(0%)){.mdc-text-field--outlined{padding-right:max(16px, var(--mdc-shape-small, 4px))}}@supports(top: max(0%)){.mdc-text-field--outlined+.mdc-text-field-helper-line{padding-left:max(16px, calc(var(--mdc-shape-small, 4px) + 4px))}}@supports(top: max(0%)){.mdc-text-field--outlined+.mdc-text-field-helper-line{padding-right:max(16px, var(--mdc-shape-small, 4px))}}.mdc-text-field--outlined.mdc-text-field--with-leading-icon{padding-left:0}@supports(top: max(0%)){.mdc-text-field--outlined.mdc-text-field--with-leading-icon{padding-right:max(16px, var(--mdc-shape-small, 4px))}}[dir=rtl] .mdc-text-field--outlined.mdc-text-field--with-leading-icon,.mdc-text-field--outlined.mdc-text-field--with-leading-icon[dir=rtl]{padding-right:0}@supports(top: max(0%)){[dir=rtl] .mdc-text-field--outlined.mdc-text-field--with-leading-icon,.mdc-text-field--outlined.mdc-text-field--with-leading-icon[dir=rtl]{padding-left:max(16px, var(--mdc-shape-small, 4px))}}.mdc-text-field--outlined.mdc-text-field--with-trailing-icon{padding-right:0}@supports(top: max(0%)){.mdc-text-field--outlined.mdc-text-field--with-trailing-icon{padding-left:max(16px, calc(var(--mdc-shape-small, 4px) + 4px))}}[dir=rtl] .mdc-text-field--outlined.mdc-text-field--with-trailing-icon,.mdc-text-field--outlined.mdc-text-field--with-trailing-icon[dir=rtl]{padding-left:0}@supports(top: max(0%)){[dir=rtl] .mdc-text-field--outlined.mdc-text-field--with-trailing-icon,.mdc-text-field--outlined.mdc-text-field--with-trailing-icon[dir=rtl]{padding-right:max(16px, calc(var(--mdc-shape-small, 4px) + 4px))}}.mdc-text-field--outlined.mdc-text-field--with-leading-icon.mdc-text-field--with-trailing-icon{padding-left:0;padding-right:0}.mdc-text-field--outlined .mdc-notched-outline--notched .mdc-notched-outline__notch{padding-top:1px}.mdc-text-field--outlined .mdc-text-field__ripple::before,.mdc-text-field--outlined .mdc-text-field__ripple::after{content:none}.mdc-text-field--outlined .mdc-floating-label{left:4px;right:initial}[dir=rtl] .mdc-text-field--outlined .mdc-floating-label,.mdc-text-field--outlined .mdc-floating-label[dir=rtl]{left:initial;right:4px}.mdc-text-field--outlined .mdc-text-field__input{display:flex;border:none !important;background-color:transparent}.mdc-text-field--outlined .mdc-notched-outline{z-index:1}.mdc-text-field--textarea{flex-direction:column;align-items:center;width:auto;height:auto;padding:0;transition:none}.mdc-text-field--textarea .mdc-floating-label{top:19px}.mdc-text-field--textarea .mdc-floating-label:not(.mdc-floating-label--float-above){-webkit-transform:none;transform:none}.mdc-text-field--textarea .mdc-text-field__input{flex-grow:1;height:auto;min-height:1.5rem;overflow-x:hidden;overflow-y:auto;box-sizing:border-box;resize:none;padding:0 16px;line-height:1.5rem}.mdc-text-field--textarea.mdc-text-field--filled::before{display:none}.mdc-text-field--textarea.mdc-text-field--filled .mdc-floating-label--float-above{-webkit-transform:translateY(-10.25px) scale(0.75);transform:translateY(-10.25px) scale(0.75)}.mdc-text-field--textarea.mdc-text-field--filled .mdc-floating-label--shake{-webkit-animation:mdc-floating-label-shake-float-above-textarea-filled 250ms 1;animation:mdc-floating-label-shake-float-above-textarea-filled 250ms 1}@-webkit-keyframes mdc-floating-label-shake-float-above-textarea-filled{0%{-webkit-transform:translateX(calc(0 - 0%)) translateY(-10.25px) scale(0.75);transform:translateX(calc(0 - 0%)) translateY(-10.25px) scale(0.75)}33%{-webkit-animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);-webkit-transform:translateX(calc(4% - 0%)) translateY(-10.25px) scale(0.75);transform:translateX(calc(4% - 0%)) translateY(-10.25px) scale(0.75)}66%{-webkit-animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);-webkit-transform:translateX(calc(-4% - 0%)) translateY(-10.25px) scale(0.75);transform:translateX(calc(-4% - 0%)) translateY(-10.25px) scale(0.75)}100%{-webkit-transform:translateX(calc(0 - 0%)) translateY(-10.25px) scale(0.75);transform:translateX(calc(0 - 0%)) translateY(-10.25px) scale(0.75)}}@keyframes mdc-floating-label-shake-float-above-textarea-filled{0%{-webkit-transform:translateX(calc(0 - 0%)) translateY(-10.25px) scale(0.75);transform:translateX(calc(0 - 0%)) translateY(-10.25px) scale(0.75)}33%{-webkit-animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);-webkit-transform:translateX(calc(4% - 0%)) translateY(-10.25px) scale(0.75);transform:translateX(calc(4% - 0%)) translateY(-10.25px) scale(0.75)}66%{-webkit-animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);-webkit-transform:translateX(calc(-4% - 0%)) translateY(-10.25px) scale(0.75);transform:translateX(calc(-4% - 0%)) translateY(-10.25px) scale(0.75)}100%{-webkit-transform:translateX(calc(0 - 0%)) translateY(-10.25px) scale(0.75);transform:translateX(calc(0 - 0%)) translateY(-10.25px) scale(0.75)}}.mdc-text-field--textarea.mdc-text-field--filled .mdc-text-field__input{margin-top:23px;margin-bottom:9px}.mdc-text-field--textarea.mdc-text-field--filled.mdc-text-field--no-label .mdc-text-field__input{margin-top:16px;margin-bottom:16px}.mdc-text-field--textarea.mdc-text-field--outlined .mdc-notched-outline--notched .mdc-notched-outline__notch{padding-top:0}.mdc-text-field--textarea.mdc-text-field--outlined .mdc-floating-label--float-above{-webkit-transform:translateY(-27.25px) scale(1);transform:translateY(-27.25px) scale(1)}.mdc-text-field--textarea.mdc-text-field--outlined .mdc-floating-label--float-above{font-size:.75rem}.mdc-text-field--textarea.mdc-text-field--outlined.mdc-notched-outline--upgraded .mdc-floating-label--float-above,.mdc-text-field--textarea.mdc-text-field--outlined .mdc-notched-outline--upgraded .mdc-floating-label--float-above{-webkit-transform:translateY(-24.75px) scale(0.75);transform:translateY(-24.75px) scale(0.75)}.mdc-text-field--textarea.mdc-text-field--outlined.mdc-notched-outline--upgraded .mdc-floating-label--float-above,.mdc-text-field--textarea.mdc-text-field--outlined .mdc-notched-outline--upgraded .mdc-floating-label--float-above{font-size:1rem}.mdc-text-field--textarea.mdc-text-field--outlined .mdc-floating-label--shake{-webkit-animation:mdc-floating-label-shake-float-above-textarea-outlined 250ms 1;animation:mdc-floating-label-shake-float-above-textarea-outlined 250ms 1}@-webkit-keyframes mdc-floating-label-shake-float-above-textarea-outlined{0%{-webkit-transform:translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75);transform:translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75)}33%{-webkit-animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);-webkit-transform:translateX(calc(4% - 0%)) translateY(-24.75px) scale(0.75);transform:translateX(calc(4% - 0%)) translateY(-24.75px) scale(0.75)}66%{-webkit-animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);-webkit-transform:translateX(calc(-4% - 0%)) translateY(-24.75px) scale(0.75);transform:translateX(calc(-4% - 0%)) translateY(-24.75px) scale(0.75)}100%{-webkit-transform:translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75);transform:translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75)}}@keyframes mdc-floating-label-shake-float-above-textarea-outlined{0%{-webkit-transform:translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75);transform:translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75)}33%{-webkit-animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);-webkit-transform:translateX(calc(4% - 0%)) translateY(-24.75px) scale(0.75);transform:translateX(calc(4% - 0%)) translateY(-24.75px) scale(0.75)}66%{-webkit-animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);-webkit-transform:translateX(calc(-4% - 0%)) translateY(-24.75px) scale(0.75);transform:translateX(calc(-4% - 0%)) translateY(-24.75px) scale(0.75)}100%{-webkit-transform:translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75);transform:translateX(calc(0 - 0%)) translateY(-24.75px) scale(0.75)}}.mdc-text-field--textarea.mdc-text-field--outlined .mdc-text-field__input{margin-top:16px;margin-bottom:16px}.mdc-text-field--textarea.mdc-text-field--outlined .mdc-floating-label{top:18px}.mdc-text-field--textarea.mdc-text-field--with-internal-counter .mdc-text-field__input{margin-bottom:2px}.mdc-text-field--textarea.mdc-text-field--with-internal-counter .mdc-text-field-character-counter{align-self:flex-end;padding:0 16px}.mdc-text-field--textarea.mdc-text-field--with-internal-counter .mdc-text-field-character-counter::after{display:inline-block;width:0;height:16px;content:"";vertical-align:-16px}.mdc-text-field--textarea.mdc-text-field--with-internal-counter .mdc-text-field-character-counter::before{display:none}.mdc-text-field__resizer{align-self:stretch;display:inline-flex;flex-direction:column;flex-grow:1;max-height:100%;max-width:100%;min-height:56px;min-width:-webkit-fit-content;min-width:-moz-fit-content;min-width:fit-content;min-width:-moz-available;min-width:-webkit-fill-available;overflow:hidden;resize:both}.mdc-text-field--filled .mdc-text-field__resizer{-webkit-transform:translateY(-1px);transform:translateY(-1px)}.mdc-text-field--filled .mdc-text-field__resizer .mdc-text-field__input,.mdc-text-field--filled .mdc-text-field__resizer .mdc-text-field-character-counter{-webkit-transform:translateY(1px);transform:translateY(1px)}.mdc-text-field--outlined .mdc-text-field__resizer{-webkit-transform:translateX(-1px) translateY(-1px);transform:translateX(-1px) translateY(-1px)}[dir=rtl] .mdc-text-field--outlined .mdc-text-field__resizer,.mdc-text-field--outlined .mdc-text-field__resizer[dir=rtl]{-webkit-transform:translateX(1px) translateY(-1px);transform:translateX(1px) translateY(-1px)}.mdc-text-field--outlined .mdc-text-field__resizer .mdc-text-field__input,.mdc-text-field--outlined .mdc-text-field__resizer .mdc-text-field-character-counter{-webkit-transform:translateX(1px) translateY(1px);transform:translateX(1px) translateY(1px)}[dir=rtl] .mdc-text-field--outlined .mdc-text-field__resizer .mdc-text-field__input,[dir=rtl] .mdc-text-field--outlined .mdc-text-field__resizer .mdc-text-field-character-counter,.mdc-text-field--outlined .mdc-text-field__resizer .mdc-text-field__input[dir=rtl],.mdc-text-field--outlined .mdc-text-field__resizer .mdc-text-field-character-counter[dir=rtl]{-webkit-transform:translateX(-1px) translateY(1px);transform:translateX(-1px) translateY(1px)}.mdc-text-field--with-leading-icon{padding-left:0;padding-right:16px}[dir=rtl] .mdc-text-field--with-leading-icon,.mdc-text-field--with-leading-icon[dir=rtl]{padding-left:16px;padding-right:0}.mdc-text-field--with-leading-icon.mdc-text-field--filled .mdc-floating-label{max-width:calc(100% - 48px);left:48px;right:initial}[dir=rtl] .mdc-text-field--with-leading-icon.mdc-text-field--filled .mdc-floating-label,.mdc-text-field--with-leading-icon.mdc-text-field--filled .mdc-floating-label[dir=rtl]{left:initial;right:48px}.mdc-text-field--with-leading-icon.mdc-text-field--filled .mdc-floating-label--float-above{max-width:calc(100% / 0.75 - 64px / 0.75)}.mdc-text-field--with-leading-icon.mdc-text-field--outlined .mdc-floating-label{left:36px;right:initial}[dir=rtl] .mdc-text-field--with-leading-icon.mdc-text-field--outlined .mdc-floating-label,.mdc-text-field--with-leading-icon.mdc-text-field--outlined .mdc-floating-label[dir=rtl]{left:initial;right:36px}.mdc-text-field--with-leading-icon.mdc-text-field--outlined :not(.mdc-notched-outline--notched) .mdc-notched-outline__notch{max-width:calc(100% - 60px)}.mdc-text-field--with-leading-icon.mdc-text-field--outlined .mdc-floating-label--float-above{-webkit-transform:translateY(-37.25px) translateX(-32px) scale(1);transform:translateY(-37.25px) translateX(-32px) scale(1)}[dir=rtl] .mdc-text-field--with-leading-icon.mdc-text-field--outlined .mdc-floating-label--float-above,.mdc-text-field--with-leading-icon.mdc-text-field--outlined .mdc-floating-label--float-above[dir=rtl]{-webkit-transform:translateY(-37.25px) translateX(32px) scale(1);transform:translateY(-37.25px) translateX(32px) scale(1)}.mdc-text-field--with-leading-icon.mdc-text-field--outlined .mdc-floating-label--float-above{font-size:.75rem}.mdc-text-field--with-leading-icon.mdc-text-field--outlined.mdc-notched-outline--upgraded .mdc-floating-label--float-above,.mdc-text-field--with-leading-icon.mdc-text-field--outlined .mdc-notched-outline--upgraded .mdc-floating-label--float-above{-webkit-transform:translateY(-34.75px) translateX(-32px) scale(0.75);transform:translateY(-34.75px) translateX(-32px) scale(0.75)}[dir=rtl] .mdc-text-field--with-leading-icon.mdc-text-field--outlined.mdc-notched-outline--upgraded .mdc-floating-label--float-above,[dir=rtl] .mdc-text-field--with-leading-icon.mdc-text-field--outlined .mdc-notched-outline--upgraded .mdc-floating-label--float-above,.mdc-text-field--with-leading-icon.mdc-text-field--outlined.mdc-notched-outline--upgraded .mdc-floating-label--float-above[dir=rtl],.mdc-text-field--with-leading-icon.mdc-text-field--outlined .mdc-notched-outline--upgraded .mdc-floating-label--float-above[dir=rtl]{-webkit-transform:translateY(-34.75px) translateX(32px) scale(0.75);transform:translateY(-34.75px) translateX(32px) scale(0.75)}.mdc-text-field--with-leading-icon.mdc-text-field--outlined.mdc-notched-outline--upgraded .mdc-floating-label--float-above,.mdc-text-field--with-leading-icon.mdc-text-field--outlined .mdc-notched-outline--upgraded .mdc-floating-label--float-above{font-size:1rem}.mdc-text-field--with-leading-icon.mdc-text-field--outlined .mdc-floating-label--shake{-webkit-animation:mdc-floating-label-shake-float-above-text-field-outlined-leading-icon 250ms 1;animation:mdc-floating-label-shake-float-above-text-field-outlined-leading-icon 250ms 1}@-webkit-keyframes mdc-floating-label-shake-float-above-text-field-outlined-leading-icon{0%{-webkit-transform:translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75)}33%{-webkit-animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);-webkit-transform:translateX(calc(4% - 32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(4% - 32px)) translateY(-34.75px) scale(0.75)}66%{-webkit-animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);-webkit-transform:translateX(calc(-4% - 32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(-4% - 32px)) translateY(-34.75px) scale(0.75)}100%{-webkit-transform:translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75)}}@keyframes mdc-floating-label-shake-float-above-text-field-outlined-leading-icon{0%{-webkit-transform:translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75)}33%{-webkit-animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);-webkit-transform:translateX(calc(4% - 32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(4% - 32px)) translateY(-34.75px) scale(0.75)}66%{-webkit-animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);-webkit-transform:translateX(calc(-4% - 32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(-4% - 32px)) translateY(-34.75px) scale(0.75)}100%{-webkit-transform:translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(0 - 32px)) translateY(-34.75px) scale(0.75)}}[dir=rtl] .mdc-text-field--with-leading-icon.mdc-text-field--outlined .mdc-floating-label--shake,.mdc-text-field--with-leading-icon.mdc-text-field--outlined[dir=rtl] .mdc-floating-label--shake{-webkit-animation:mdc-floating-label-shake-float-above-text-field-outlined-leading-icon 250ms 1;animation:mdc-floating-label-shake-float-above-text-field-outlined-leading-icon 250ms 1}@-webkit-keyframes mdc-floating-label-shake-float-above-text-field-outlined-leading-icon-rtl{0%{-webkit-transform:translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75)}33%{-webkit-animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);-webkit-transform:translateX(calc(4% - -32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(4% - -32px)) translateY(-34.75px) scale(0.75)}66%{-webkit-animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);-webkit-transform:translateX(calc(-4% - -32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(-4% - -32px)) translateY(-34.75px) scale(0.75)}100%{-webkit-transform:translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75)}}@keyframes mdc-floating-label-shake-float-above-text-field-outlined-leading-icon-rtl{0%{-webkit-transform:translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75)}33%{-webkit-animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);animation-timing-function:cubic-bezier(0.5, 0, 0.701732, 0.495819);-webkit-transform:translateX(calc(4% - -32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(4% - -32px)) translateY(-34.75px) scale(0.75)}66%{-webkit-animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);animation-timing-function:cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);-webkit-transform:translateX(calc(-4% - -32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(-4% - -32px)) translateY(-34.75px) scale(0.75)}100%{-webkit-transform:translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75);transform:translateX(calc(0 - -32px)) translateY(-34.75px) scale(0.75)}}.mdc-text-field--with-trailing-icon{padding-left:16px;padding-right:0}[dir=rtl] .mdc-text-field--with-trailing-icon,.mdc-text-field--with-trailing-icon[dir=rtl]{padding-left:0;padding-right:16px}.mdc-text-field--with-trailing-icon.mdc-text-field--filled .mdc-floating-label{max-width:calc(100% - 64px)}.mdc-text-field--with-trailing-icon.mdc-text-field--filled .mdc-floating-label--float-above{max-width:calc(100% / 0.75 - 64px / 0.75)}.mdc-text-field--with-trailing-icon.mdc-text-field--outlined :not(.mdc-notched-outline--notched) .mdc-notched-outline__notch{max-width:calc(100% - 60px)}.mdc-text-field--with-leading-icon.mdc-text-field--with-trailing-icon{padding-left:0;padding-right:0}.mdc-text-field--with-leading-icon.mdc-text-field--with-trailing-icon.mdc-text-field--filled .mdc-floating-label{max-width:calc(100% - 96px)}.mdc-text-field--with-leading-icon.mdc-text-field--with-trailing-icon.mdc-text-field--filled .mdc-floating-label--float-above{max-width:calc(100% / 0.75 - 96px / 0.75)}.mdc-text-field-helper-line{display:flex;justify-content:space-between;box-sizing:border-box}.mdc-text-field+.mdc-text-field-helper-line{padding-right:16px;padding-left:16px}.mdc-form-field>.mdc-text-field+label{align-self:flex-start}.mdc-text-field--focused:not(.mdc-text-field--disabled) .mdc-floating-label{color:rgba(98, 0, 238, 0.87)}.mdc-text-field--focused .mdc-notched-outline__leading,.mdc-text-field--focused .mdc-notched-outline__notch,.mdc-text-field--focused .mdc-notched-outline__trailing{border-width:2px}.mdc-text-field--focused+.mdc-text-field-helper-line .mdc-text-field-helper-text:not(.mdc-text-field-helper-text--validation-msg){opacity:1}.mdc-text-field--focused.mdc-text-field--outlined .mdc-notched-outline--notched .mdc-notched-outline__notch{padding-top:2px}.mdc-text-field--focused.mdc-text-field--outlined.mdc-text-field--textarea .mdc-notched-outline--notched .mdc-notched-outline__notch{padding-top:0}.mdc-text-field--invalid:not(.mdc-text-field--disabled):hover .mdc-line-ripple::before{border-bottom-color:#b00020;border-bottom-color:var(--mdc-theme-error, #b00020)}.mdc-text-field--invalid:not(.mdc-text-field--disabled) .mdc-line-ripple::after{border-bottom-color:#b00020;border-bottom-color:var(--mdc-theme-error, #b00020)}.mdc-text-field--invalid:not(.mdc-text-field--disabled) .mdc-floating-label{color:#b00020;color:var(--mdc-theme-error, #b00020)}.mdc-text-field--invalid:not(.mdc-text-field--disabled).mdc-text-field--invalid+.mdc-text-field-helper-line .mdc-text-field-helper-text--validation-msg{color:#b00020;color:var(--mdc-theme-error, #b00020)}.mdc-text-field--invalid .mdc-text-field__input{caret-color:#b00020;caret-color:var(--mdc-theme-error, #b00020)}.mdc-text-field--invalid:not(.mdc-text-field--disabled) .mdc-text-field__icon--trailing{color:#b00020;color:var(--mdc-theme-error, #b00020)}.mdc-text-field--invalid:not(.mdc-text-field--disabled) .mdc-line-ripple::before{border-bottom-color:#b00020;border-bottom-color:var(--mdc-theme-error, #b00020)}.mdc-text-field--invalid:not(.mdc-text-field--disabled) .mdc-notched-outline__leading,.mdc-text-field--invalid:not(.mdc-text-field--disabled) .mdc-notched-outline__notch,.mdc-text-field--invalid:not(.mdc-text-field--disabled) .mdc-notched-outline__trailing{border-color:#b00020;border-color:var(--mdc-theme-error, #b00020)}.mdc-text-field--invalid:not(.mdc-text-field--disabled):not(.mdc-text-field--focused):hover .mdc-notched-outline .mdc-notched-outline__leading,.mdc-text-field--invalid:not(.mdc-text-field--disabled):not(.mdc-text-field--focused):hover .mdc-notched-outline .mdc-notched-outline__notch,.mdc-text-field--invalid:not(.mdc-text-field--disabled):not(.mdc-text-field--focused):hover .mdc-notched-outline .mdc-notched-outline__trailing{border-color:#b00020;border-color:var(--mdc-theme-error, #b00020)}.mdc-text-field--invalid:not(.mdc-text-field--disabled).mdc-text-field--focused .mdc-notched-outline__leading,.mdc-text-field--invalid:not(.mdc-text-field--disabled).mdc-text-field--focused .mdc-notched-outline__notch,.mdc-text-field--invalid:not(.mdc-text-field--disabled).mdc-text-field--focused .mdc-notched-outline__trailing{border-color:#b00020;border-color:var(--mdc-theme-error, #b00020)}.mdc-text-field--invalid+.mdc-text-field-helper-line .mdc-text-field-helper-text--validation-msg{opacity:1}.mdc-text-field--disabled{pointer-events:none}.mdc-text-field--disabled .mdc-text-field__input{color:rgba(0, 0, 0, 0.38)}@media all{.mdc-text-field--disabled .mdc-text-field__input::-webkit-input-placeholder{color:rgba(0, 0, 0, 0.38)}.mdc-text-field--disabled .mdc-text-field__input:-ms-input-placeholder{color:rgba(0, 0, 0, 0.38)}.mdc-text-field--disabled .mdc-text-field__input::-ms-input-placeholder{color:rgba(0, 0, 0, 0.38)}.mdc-text-field--disabled .mdc-text-field__input::placeholder{color:rgba(0, 0, 0, 0.38)}}@media all{.mdc-text-field--disabled .mdc-text-field__input:-ms-input-placeholder{color:rgba(0, 0, 0, 0.38)}}.mdc-text-field--disabled .mdc-floating-label{color:rgba(0, 0, 0, 0.38)}.mdc-text-field--disabled+.mdc-text-field-helper-line .mdc-text-field-helper-text{color:rgba(0, 0, 0, 0.38)}.mdc-text-field--disabled .mdc-text-field-character-counter,.mdc-text-field--disabled+.mdc-text-field-helper-line .mdc-text-field-character-counter{color:rgba(0, 0, 0, 0.38)}.mdc-text-field--disabled .mdc-text-field__icon--leading{color:rgba(0, 0, 0, 0.3)}.mdc-text-field--disabled .mdc-text-field__icon--trailing{color:rgba(0, 0, 0, 0.3)}.mdc-text-field--disabled .mdc-text-field__affix--prefix{color:rgba(0, 0, 0, 0.38)}.mdc-text-field--disabled .mdc-text-field__affix--suffix{color:rgba(0, 0, 0, 0.38)}.mdc-text-field--disabled .mdc-line-ripple::before{border-bottom-color:rgba(0, 0, 0, 0.06)}.mdc-text-field--disabled .mdc-notched-outline__leading,.mdc-text-field--disabled .mdc-notched-outline__notch,.mdc-text-field--disabled .mdc-notched-outline__trailing{border-color:rgba(0, 0, 0, 0.06)}@media screen and (forced-colors: active),(-ms-high-contrast: active){.mdc-text-field--disabled .mdc-text-field__input::-webkit-input-placeholder{color:GrayText}.mdc-text-field--disabled .mdc-text-field__input:-ms-input-placeholder{color:GrayText}.mdc-text-field--disabled .mdc-text-field__input::-ms-input-placeholder{color:GrayText}.mdc-text-field--disabled .mdc-text-field__input::placeholder{color:GrayText}}@media screen and (forced-colors: active),(-ms-high-contrast: active){.mdc-text-field--disabled .mdc-text-field__input:-ms-input-placeholder{color:GrayText}}@media screen and (forced-colors: active),(-ms-high-contrast: active){.mdc-text-field--disabled .mdc-floating-label{color:GrayText}}@media screen and (forced-colors: active),(-ms-high-contrast: active){.mdc-text-field--disabled+.mdc-text-field-helper-line .mdc-text-field-helper-text{color:GrayText}}@media screen and (forced-colors: active),(-ms-high-contrast: active){.mdc-text-field--disabled .mdc-text-field-character-counter,.mdc-text-field--disabled+.mdc-text-field-helper-line .mdc-text-field-character-counter{color:GrayText}}@media screen and (forced-colors: active),(-ms-high-contrast: active){.mdc-text-field--disabled .mdc-text-field__icon--leading{color:GrayText}}@media screen and (forced-colors: active),(-ms-high-contrast: active){.mdc-text-field--disabled .mdc-text-field__icon--trailing{color:GrayText}}@media screen and (forced-colors: active),(-ms-high-contrast: active){.mdc-text-field--disabled .mdc-text-field__affix--prefix{color:GrayText}}@media screen and (forced-colors: active),(-ms-high-contrast: active){.mdc-text-field--disabled .mdc-text-field__affix--suffix{color:GrayText}}@media screen and (forced-colors: active),(-ms-high-contrast: active){.mdc-text-field--disabled .mdc-line-ripple::before{border-bottom-color:GrayText}}@media screen and (forced-colors: active),(-ms-high-contrast: active){.mdc-text-field--disabled .mdc-notched-outline__leading,.mdc-text-field--disabled .mdc-notched-outline__notch,.mdc-text-field--disabled .mdc-notched-outline__trailing{border-color:GrayText}}@media screen and (forced-colors: active){.mdc-text-field--disabled .mdc-text-field__input{background-color:Window}.mdc-text-field--disabled .mdc-floating-label{z-index:1}}.mdc-text-field--disabled .mdc-floating-label{cursor:default}.mdc-text-field--disabled.mdc-text-field--filled{background-color:#fafafa}.mdc-text-field--disabled.mdc-text-field--filled .mdc-text-field__ripple{display:none}.mdc-text-field--disabled .mdc-text-field__input{pointer-events:auto}.mdc-text-field--end-aligned .mdc-text-field__input{text-align:right}[dir=rtl] .mdc-text-field--end-aligned .mdc-text-field__input,.mdc-text-field--end-aligned .mdc-text-field__input[dir=rtl]{text-align:left}[dir=rtl] .mdc-text-field--ltr-text .mdc-text-field__input,[dir=rtl] .mdc-text-field--ltr-text .mdc-text-field__affix,.mdc-text-field--ltr-text[dir=rtl] .mdc-text-field__input,.mdc-text-field--ltr-text[dir=rtl] .mdc-text-field__affix{direction:ltr}[dir=rtl] .mdc-text-field--ltr-text .mdc-text-field__affix--prefix,.mdc-text-field--ltr-text[dir=rtl] .mdc-text-field__affix--prefix{padding-left:0;padding-right:2px}[dir=rtl] .mdc-text-field--ltr-text .mdc-text-field__affix--suffix,.mdc-text-field--ltr-text[dir=rtl] .mdc-text-field__affix--suffix{padding-left:12px;padding-right:0}[dir=rtl] .mdc-text-field--ltr-text .mdc-text-field__icon--leading,.mdc-text-field--ltr-text[dir=rtl] .mdc-text-field__icon--leading{order:1}[dir=rtl] .mdc-text-field--ltr-text .mdc-text-field__affix--suffix,.mdc-text-field--ltr-text[dir=rtl] .mdc-text-field__affix--suffix{order:2}[dir=rtl] .mdc-text-field--ltr-text .mdc-text-field__input,.mdc-text-field--ltr-text[dir=rtl] .mdc-text-field__input{order:3}[dir=rtl] .mdc-text-field--ltr-text .mdc-text-field__affix--prefix,.mdc-text-field--ltr-text[dir=rtl] .mdc-text-field__affix--prefix{order:4}[dir=rtl] .mdc-text-field--ltr-text .mdc-text-field__icon--trailing,.mdc-text-field--ltr-text[dir=rtl] .mdc-text-field__icon--trailing{order:5}[dir=rtl] .mdc-text-field--ltr-text.mdc-text-field--end-aligned .mdc-text-field__input,.mdc-text-field--ltr-text.mdc-text-field--end-aligned[dir=rtl] .mdc-text-field__input{text-align:right}[dir=rtl] .mdc-text-field--ltr-text.mdc-text-field--end-aligned .mdc-text-field__affix--prefix,.mdc-text-field--ltr-text.mdc-text-field--end-aligned[dir=rtl] .mdc-text-field__affix--prefix{padding-right:12px}[dir=rtl] .mdc-text-field--ltr-text.mdc-text-field--end-aligned .mdc-text-field__affix--suffix,.mdc-text-field--ltr-text.mdc-text-field--end-aligned[dir=rtl] .mdc-text-field__affix--suffix{padding-left:2px}.mdc-text-field-helper-text{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-caption-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.75rem;font-size:var(--mdc-typography-caption-font-size, 0.75rem);line-height:1.25rem;line-height:var(--mdc-typography-caption-line-height, 1.25rem);font-weight:400;font-weight:var(--mdc-typography-caption-font-weight, 400);letter-spacing:0.0333333333em;letter-spacing:var(--mdc-typography-caption-letter-spacing, 0.0333333333em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-caption-text-decoration, inherit);text-decoration:var(--mdc-typography-caption-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-caption-text-transform, inherit);display:block;margin-top:0;line-height:normal;margin:0;opacity:0;will-change:opacity;transition:opacity 150ms 0ms cubic-bezier(0.4, 0, 0.2, 1)}.mdc-text-field-helper-text::before{display:inline-block;width:0;height:16px;content:"";vertical-align:0}.mdc-text-field-helper-text--persistent{transition:none;opacity:1;will-change:initial}.mdc-text-field-character-counter{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-caption-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.75rem;font-size:var(--mdc-typography-caption-font-size, 0.75rem);line-height:1.25rem;line-height:var(--mdc-typography-caption-line-height, 1.25rem);font-weight:400;font-weight:var(--mdc-typography-caption-font-weight, 400);letter-spacing:0.0333333333em;letter-spacing:var(--mdc-typography-caption-letter-spacing, 0.0333333333em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-caption-text-decoration, inherit);text-decoration:var(--mdc-typography-caption-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-caption-text-transform, inherit);display:block;margin-top:0;line-height:normal;margin-left:auto;margin-right:0;padding-left:16px;padding-right:0;white-space:nowrap}.mdc-text-field-character-counter::before{display:inline-block;width:0;height:16px;content:"";vertical-align:0}[dir=rtl] .mdc-text-field-character-counter,.mdc-text-field-character-counter[dir=rtl]{margin-left:0;margin-right:auto}[dir=rtl] .mdc-text-field-character-counter,.mdc-text-field-character-counter[dir=rtl]{padding-left:0;padding-right:16px}.mdc-text-field__icon{align-self:center;cursor:pointer}.mdc-text-field__icon:not([tabindex]),.mdc-text-field__icon[tabindex="-1"]{cursor:default;pointer-events:none}.mdc-text-field__icon svg{display:block}.mdc-text-field__icon--leading{margin-left:16px;margin-right:8px}[dir=rtl] .mdc-text-field__icon--leading,.mdc-text-field__icon--leading[dir=rtl]{margin-left:8px;margin-right:16px}.mdc-text-field__icon--trailing{padding:12px;margin-left:0px;margin-right:0px}[dir=rtl] .mdc-text-field__icon--trailing,.mdc-text-field__icon--trailing[dir=rtl]{margin-left:0px;margin-right:0px}:root{--mdc-theme-primary:#6200ee;--mdc-theme-secondary:#018786;--mdc-theme-background:#fff;--mdc-theme-surface:#fff;--mdc-theme-error:#b00020;--mdc-theme-on-primary:#fff;--mdc-theme-on-secondary:#fff;--mdc-theme-on-surface:#000;--mdc-theme-on-error:#fff;--mdc-theme-text-primary-on-background:rgba(0, 0, 0, 0.87);--mdc-theme-text-secondary-on-background:rgba(0, 0, 0, 0.54);--mdc-theme-text-hint-on-background:rgba(0, 0, 0, 0.38);--mdc-theme-text-disabled-on-background:rgba(0, 0, 0, 0.38);--mdc-theme-text-icon-on-background:rgba(0, 0, 0, 0.38);--mdc-theme-text-primary-on-light:rgba(0, 0, 0, 0.87);--mdc-theme-text-secondary-on-light:rgba(0, 0, 0, 0.54);--mdc-theme-text-hint-on-light:rgba(0, 0, 0, 0.38);--mdc-theme-text-disabled-on-light:rgba(0, 0, 0, 0.38);--mdc-theme-text-icon-on-light:rgba(0, 0, 0, 0.38);--mdc-theme-text-primary-on-dark:white;--mdc-theme-text-secondary-on-dark:rgba(255, 255, 255, 0.7);--mdc-theme-text-hint-on-dark:rgba(255, 255, 255, 0.5);--mdc-theme-text-disabled-on-dark:rgba(255, 255, 255, 0.5);--mdc-theme-text-icon-on-dark:rgba(255, 255, 255, 0.5)}.mdc-theme--primary{color:#6200ee !important;color:var(--mdc-theme-primary, #6200ee) !important}.mdc-theme--secondary{color:#018786 !important;color:var(--mdc-theme-secondary, #018786) !important}.mdc-theme--background{background-color:#fff;background-color:var(--mdc-theme-background, #fff)}.mdc-theme--surface{background-color:#fff;background-color:var(--mdc-theme-surface, #fff)}.mdc-theme--error{color:#b00020 !important;color:var(--mdc-theme-error, #b00020) !important}.mdc-theme--on-primary{color:#fff !important;color:var(--mdc-theme-on-primary, #fff) !important}.mdc-theme--on-secondary{color:#fff !important;color:var(--mdc-theme-on-secondary, #fff) !important}.mdc-theme--on-surface{color:#000 !important;color:var(--mdc-theme-on-surface, #000) !important}.mdc-theme--on-error{color:#fff !important;color:var(--mdc-theme-on-error, #fff) !important}.mdc-theme--text-primary-on-background{color:rgba(0, 0, 0, 0.87) !important;color:var(--mdc-theme-text-primary-on-background, rgba(0, 0, 0, 0.87)) !important}.mdc-theme--text-secondary-on-background{color:rgba(0, 0, 0, 0.54) !important;color:var(--mdc-theme-text-secondary-on-background, rgba(0, 0, 0, 0.54)) !important}.mdc-theme--text-hint-on-background{color:rgba(0, 0, 0, 0.38) !important;color:var(--mdc-theme-text-hint-on-background, rgba(0, 0, 0, 0.38)) !important}.mdc-theme--text-disabled-on-background{color:rgba(0, 0, 0, 0.38) !important;color:var(--mdc-theme-text-disabled-on-background, rgba(0, 0, 0, 0.38)) !important}.mdc-theme--text-icon-on-background{color:rgba(0, 0, 0, 0.38) !important;color:var(--mdc-theme-text-icon-on-background, rgba(0, 0, 0, 0.38)) !important}.mdc-theme--text-primary-on-light{color:rgba(0, 0, 0, 0.87) !important;color:var(--mdc-theme-text-primary-on-light, rgba(0, 0, 0, 0.87)) !important}.mdc-theme--text-secondary-on-light{color:rgba(0, 0, 0, 0.54) !important;color:var(--mdc-theme-text-secondary-on-light, rgba(0, 0, 0, 0.54)) !important}.mdc-theme--text-hint-on-light{color:rgba(0, 0, 0, 0.38) !important;color:var(--mdc-theme-text-hint-on-light, rgba(0, 0, 0, 0.38)) !important}.mdc-theme--text-disabled-on-light{color:rgba(0, 0, 0, 0.38) !important;color:var(--mdc-theme-text-disabled-on-light, rgba(0, 0, 0, 0.38)) !important}.mdc-theme--text-icon-on-light{color:rgba(0, 0, 0, 0.38) !important;color:var(--mdc-theme-text-icon-on-light, rgba(0, 0, 0, 0.38)) !important}.mdc-theme--text-primary-on-dark{color:white !important;color:var(--mdc-theme-text-primary-on-dark, white) !important}.mdc-theme--text-secondary-on-dark{color:rgba(255, 255, 255, 0.7) !important;color:var(--mdc-theme-text-secondary-on-dark, rgba(255, 255, 255, 0.7)) !important}.mdc-theme--text-hint-on-dark{color:rgba(255, 255, 255, 0.5) !important;color:var(--mdc-theme-text-hint-on-dark, rgba(255, 255, 255, 0.5)) !important}.mdc-theme--text-disabled-on-dark{color:rgba(255, 255, 255, 0.5) !important;color:var(--mdc-theme-text-disabled-on-dark, rgba(255, 255, 255, 0.5)) !important}.mdc-theme--text-icon-on-dark{color:rgba(255, 255, 255, 0.5) !important;color:var(--mdc-theme-text-icon-on-dark, rgba(255, 255, 255, 0.5)) !important}.mdc-theme--primary-bg{background-color:#6200ee !important;background-color:var(--mdc-theme-primary, #6200ee) !important}.mdc-theme--secondary-bg{background-color:#018786 !important;background-color:var(--mdc-theme-secondary, #018786) !important}.mdc-tooltip__surface{border-radius:4px;border-radius:var(--mdc-shape-small, 4px)}.mdc-tooltip__caret-surface-top,.mdc-tooltip__caret-surface-bottom{border-radius:4px;border-radius:var(--mdc-shape-small, 4px)}.mdc-tooltip__surface{color:white;color:var(--mdc-theme-text-primary-on-dark, white)}.mdc-tooltip__surface{background-color:rgba(0, 0, 0, 0.6)}.mdc-tooltip__surface{word-break:break-all;word-break:var(--mdc-tooltip-word-break, normal);overflow-wrap:anywhere}.mdc-tooltip{z-index:9}.mdc-tooltip--showing-transition .mdc-tooltip__surface-animation{transition:opacity 150ms 0ms cubic-bezier(0, 0, 0.2, 1),-webkit-transform 150ms 0ms cubic-bezier(0, 0, 0.2, 1);transition:opacity 150ms 0ms cubic-bezier(0, 0, 0.2, 1),transform 150ms 0ms cubic-bezier(0, 0, 0.2, 1);transition:opacity 150ms 0ms cubic-bezier(0, 0, 0.2, 1),transform 150ms 0ms cubic-bezier(0, 0, 0.2, 1),-webkit-transform 150ms 0ms cubic-bezier(0, 0, 0.2, 1)}.mdc-tooltip--hide-transition .mdc-tooltip__surface-animation{transition:opacity 75ms 0ms cubic-bezier(0.4, 0, 1, 1)}.mdc-tooltip__title{color:rgba(0, 0, 0, 0.87);color:var(--mdc-theme-text-primary-on-light, rgba(0, 0, 0, 0.87))}.mdc-tooltip__content{color:rgba(0, 0, 0, 0.6)}.mdc-tooltip__content-link{color:#6200ee;color:var(--mdc-theme-primary, #6200ee)}.mdc-tooltip{position:fixed;display:none}.mdc-tooltip.mdc-tooltip--rich .mdc-tooltip__surface{background-color:#fff}.mdc-tooltip.mdc-tooltip--rich .mdc-tooltip__caret-surface-top,.mdc-tooltip.mdc-tooltip--rich .mdc-tooltip__caret-surface-bottom{background-color:#fff}.mdc-tooltip-wrapper--rich{position:relative}.mdc-tooltip--shown,.mdc-tooltip--showing,.mdc-tooltip--hide{display:inline-flex}.mdc-tooltip--shown.mdc-tooltip--rich,.mdc-tooltip--showing.mdc-tooltip--rich,.mdc-tooltip--hide.mdc-tooltip--rich{display:inline-block;left:-320px;position:absolute}.mdc-tooltip__surface{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-caption-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.75rem;font-size:var(--mdc-typography-caption-font-size, 0.75rem);font-weight:400;font-weight:var(--mdc-typography-caption-font-weight, 400);letter-spacing:0.0333333333em;letter-spacing:var(--mdc-typography-caption-letter-spacing, 0.0333333333em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-caption-text-decoration, inherit);text-decoration:var(--mdc-typography-caption-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-caption-text-transform, inherit);line-height:16px;padding:4px 8px;min-width:40px;max-width:200px;min-height:24px;max-height:40vh;box-sizing:border-box;overflow:hidden;text-align:center}.mdc-tooltip__surface::before{position:absolute;box-sizing:border-box;width:100%;height:100%;top:0;left:0;border:1px solid transparent;border-radius:inherit;content:"";pointer-events:none}.mdc-tooltip--rich .mdc-tooltip__surface{box-shadow:0px 3px 1px -2px rgba(0, 0, 0, 0.2),0px 2px 2px 0px rgba(0, 0, 0, 0.14),0px 1px 5px 0px rgba(0,0,0,.12);align-items:flex-start;border-radius:4px;display:flex;flex-direction:column;line-height:20px;min-height:24px;min-width:40px;max-width:320px;position:relative}.mdc-tooltip--rich .mdc-tooltip__surface .mdc-elevation-overlay{width:100%;height:100%;top:0;left:0}.mdc-tooltip--multiline .mdc-tooltip__surface{text-align:left}[dir=rtl] .mdc-tooltip--multiline .mdc-tooltip__surface,.mdc-tooltip--multiline .mdc-tooltip__surface[dir=rtl]{text-align:right}.mdc-tooltip__surface .mdc-tooltip__title{display:block;margin-top:0;line-height:normal;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-subtitle2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.875rem;font-size:var(--mdc-typography-subtitle2-font-size, 0.875rem);font-weight:500;font-weight:var(--mdc-typography-subtitle2-font-weight, 500);letter-spacing:0.0071428571em;letter-spacing:var(--mdc-typography-subtitle2-letter-spacing, 0.0071428571em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-subtitle2-text-decoration, inherit);text-decoration:var(--mdc-typography-subtitle2-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-subtitle2-text-transform, inherit);margin:0 8px}.mdc-tooltip__surface .mdc-tooltip__title::before{display:inline-block;width:0;height:28px;content:"";vertical-align:0}.mdc-tooltip__surface .mdc-tooltip__content{display:block;margin-top:0;line-height:normal;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.875rem;font-size:var(--mdc-typography-body2-font-size, 0.875rem);font-weight:400;font-weight:var(--mdc-typography-body2-font-weight, 400);letter-spacing:0.0178571429em;letter-spacing:var(--mdc-typography-body2-letter-spacing, 0.0178571429em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-body2-text-transform, inherit);max-width:calc(100% - 2 * 8px);margin:0 8px 16px 8px;text-align:left}.mdc-tooltip__surface .mdc-tooltip__content::before{display:inline-block;width:0;height:24px;content:"";vertical-align:0}[dir=rtl] .mdc-tooltip__surface .mdc-tooltip__content,.mdc-tooltip__surface .mdc-tooltip__content[dir=rtl]{text-align:right}.mdc-tooltip__surface .mdc-tooltip__content-link{text-decoration:none}.mdc-tooltip__surface-animation{opacity:0;-webkit-transform:scale(0.8);transform:scale(0.8);will-change:transform,opacity}.mdc-tooltip--shown .mdc-tooltip__surface-animation{-webkit-transform:scale(1);transform:scale(1);opacity:1}.mdc-tooltip--hide .mdc-tooltip__surface-animation{-webkit-transform:scale(1);transform:scale(1)}.mdc-tooltip__caret-surface-top,.mdc-tooltip__caret-surface-bottom{position:absolute;height:24px;width:24px;-webkit-transform:rotate(35deg) skewY(20deg) scaleX(0.9396926208);transform:rotate(35deg) skewY(20deg) scaleX(0.9396926208)}.mdc-tooltip__caret-surface-top .mdc-elevation-overlay,.mdc-tooltip__caret-surface-bottom .mdc-elevation-overlay{width:100%;height:100%;top:0;left:0}.mdc-tooltip__caret-surface-bottom{box-shadow:0px 3px 1px -2px rgba(0, 0, 0, 0.2),0px 2px 2px 0px rgba(0, 0, 0, 0.14),0px 1px 5px 0px rgba(0,0,0,.12);outline:1px solid transparent;z-index:-1}.mdc-top-app-bar{background-color:#6200ee;background-color:var(--mdc-theme-primary, #6200ee);color:white;display:flex;position:fixed;flex-direction:column;justify-content:space-between;box-sizing:border-box;width:100%;z-index:4}.mdc-top-app-bar .mdc-top-app-bar__action-item,.mdc-top-app-bar .mdc-top-app-bar__navigation-icon{color:#fff;color:var(--mdc-theme-on-primary, #fff)}.mdc-top-app-bar .mdc-top-app-bar__action-item::before,.mdc-top-app-bar .mdc-top-app-bar__action-item::after,.mdc-top-app-bar .mdc-top-app-bar__navigation-icon::before,.mdc-top-app-bar .mdc-top-app-bar__navigation-icon::after{background-color:#fff;background-color:var(--mdc-ripple-color, var(--mdc-theme-on-primary, #fff))}.mdc-top-app-bar .mdc-top-app-bar__action-item:hover::before,.mdc-top-app-bar .mdc-top-app-bar__action-item.mdc-ripple-surface--hover::before,.mdc-top-app-bar .mdc-top-app-bar__navigation-icon:hover::before,.mdc-top-app-bar .mdc-top-app-bar__navigation-icon.mdc-ripple-surface--hover::before{opacity:0.08;opacity:var(--mdc-ripple-hover-opacity, 0.08)}.mdc-top-app-bar .mdc-top-app-bar__action-item.mdc-ripple-upgraded--background-focused::before,.mdc-top-app-bar .mdc-top-app-bar__action-item:not(.mdc-ripple-upgraded):focus::before,.mdc-top-app-bar .mdc-top-app-bar__navigation-icon.mdc-ripple-upgraded--background-focused::before,.mdc-top-app-bar .mdc-top-app-bar__navigation-icon:not(.mdc-ripple-upgraded):focus::before{transition-duration:75ms;opacity:0.24;opacity:var(--mdc-ripple-focus-opacity, 0.24)}.mdc-top-app-bar .mdc-top-app-bar__action-item:not(.mdc-ripple-upgraded)::after,.mdc-top-app-bar .mdc-top-app-bar__navigation-icon:not(.mdc-ripple-upgraded)::after{transition:opacity 150ms linear}.mdc-top-app-bar .mdc-top-app-bar__action-item:not(.mdc-ripple-upgraded):active::after,.mdc-top-app-bar .mdc-top-app-bar__navigation-icon:not(.mdc-ripple-upgraded):active::after{transition-duration:75ms;opacity:0.24;opacity:var(--mdc-ripple-press-opacity, 0.24)}.mdc-top-app-bar .mdc-top-app-bar__action-item.mdc-ripple-upgraded,.mdc-top-app-bar .mdc-top-app-bar__navigation-icon.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.24)}.mdc-top-app-bar__row{display:flex;position:relative;box-sizing:border-box;width:100%;height:64px}.mdc-top-app-bar__section{display:inline-flex;flex:1 1 auto;align-items:center;min-width:0;padding:8px 12px;z-index:1}.mdc-top-app-bar__section--align-start{justify-content:flex-start;order:-1}.mdc-top-app-bar__section--align-end{justify-content:flex-end;order:1}.mdc-top-app-bar__title{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-headline6-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:1.25rem;font-size:var(--mdc-typography-headline6-font-size, 1.25rem);line-height:2rem;line-height:var(--mdc-typography-headline6-line-height, 2rem);font-weight:500;font-weight:var(--mdc-typography-headline6-font-weight, 500);letter-spacing:0.0125em;letter-spacing:var(--mdc-typography-headline6-letter-spacing, 0.0125em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-headline6-text-decoration, inherit);text-decoration:var(--mdc-typography-headline6-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-headline6-text-transform, inherit);padding-left:20px;padding-right:0;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;z-index:1}[dir=rtl] .mdc-top-app-bar__title,.mdc-top-app-bar__title[dir=rtl]{padding-left:0;padding-right:20px}.mdc-top-app-bar--short-collapsed{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:24px;border-bottom-left-radius:0}[dir=rtl] .mdc-top-app-bar--short-collapsed,.mdc-top-app-bar--short-collapsed[dir=rtl]{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:24px}.mdc-top-app-bar--short{top:0;right:auto;left:0;width:100%;transition:width 250ms cubic-bezier(0.4, 0, 0.2, 1)}[dir=rtl] .mdc-top-app-bar--short,.mdc-top-app-bar--short[dir=rtl]{right:0;left:auto}.mdc-top-app-bar--short .mdc-top-app-bar__row{height:56px}.mdc-top-app-bar--short .mdc-top-app-bar__section{padding:4px}.mdc-top-app-bar--short .mdc-top-app-bar__title{transition:opacity 200ms cubic-bezier(0.4, 0, 0.2, 1);opacity:1}.mdc-top-app-bar--short-collapsed{box-shadow:0px 2px 4px -1px rgba(0, 0, 0, 0.2),0px 4px 5px 0px rgba(0, 0, 0, 0.14),0px 1px 10px 0px rgba(0,0,0,.12);width:56px;transition:width 300ms cubic-bezier(0.4, 0, 0.2, 1)}.mdc-top-app-bar--short-collapsed .mdc-top-app-bar__title{display:none}.mdc-top-app-bar--short-collapsed .mdc-top-app-bar__action-item{transition:padding 150ms cubic-bezier(0.4, 0, 0.2, 1)}.mdc-top-app-bar--short-collapsed.mdc-top-app-bar--short-has-action-item{width:112px}.mdc-top-app-bar--short-collapsed.mdc-top-app-bar--short-has-action-item .mdc-top-app-bar__section--align-end{padding-left:0;padding-right:12px}[dir=rtl] .mdc-top-app-bar--short-collapsed.mdc-top-app-bar--short-has-action-item .mdc-top-app-bar__section--align-end,.mdc-top-app-bar--short-collapsed.mdc-top-app-bar--short-has-action-item .mdc-top-app-bar__section--align-end[dir=rtl]{padding-left:12px;padding-right:0}.mdc-top-app-bar--dense .mdc-top-app-bar__row{height:48px}.mdc-top-app-bar--dense .mdc-top-app-bar__section{padding:0 4px}.mdc-top-app-bar--dense .mdc-top-app-bar__title{padding-left:12px;padding-right:0}[dir=rtl] .mdc-top-app-bar--dense .mdc-top-app-bar__title,.mdc-top-app-bar--dense .mdc-top-app-bar__title[dir=rtl]{padding-left:0;padding-right:12px}.mdc-top-app-bar--prominent .mdc-top-app-bar__row{height:128px}.mdc-top-app-bar--prominent .mdc-top-app-bar__title{align-self:flex-end;padding-bottom:2px}.mdc-top-app-bar--prominent .mdc-top-app-bar__action-item,.mdc-top-app-bar--prominent .mdc-top-app-bar__navigation-icon{align-self:flex-start}.mdc-top-app-bar--fixed{transition:box-shadow 200ms linear}.mdc-top-app-bar--fixed-scrolled{box-shadow:0px 2px 4px -1px rgba(0, 0, 0, 0.2),0px 4px 5px 0px rgba(0, 0, 0, 0.14),0px 1px 10px 0px rgba(0,0,0,.12);transition:box-shadow 200ms linear}.mdc-top-app-bar--dense.mdc-top-app-bar--prominent .mdc-top-app-bar__row{height:96px}.mdc-top-app-bar--dense.mdc-top-app-bar--prominent .mdc-top-app-bar__section{padding:0 12px}.mdc-top-app-bar--dense.mdc-top-app-bar--prominent .mdc-top-app-bar__title{padding-left:20px;padding-right:0;padding-bottom:9px}[dir=rtl] .mdc-top-app-bar--dense.mdc-top-app-bar--prominent .mdc-top-app-bar__title,.mdc-top-app-bar--dense.mdc-top-app-bar--prominent .mdc-top-app-bar__title[dir=rtl]{padding-left:0;padding-right:20px}.mdc-top-app-bar--fixed-adjust{padding-top:64px}.mdc-top-app-bar--dense-fixed-adjust{padding-top:48px}.mdc-top-app-bar--short-fixed-adjust{padding-top:56px}.mdc-top-app-bar--prominent-fixed-adjust{padding-top:128px}.mdc-top-app-bar--dense-prominent-fixed-adjust{padding-top:96px}@media(max-width: 599px){.mdc-top-app-bar__row{height:56px}.mdc-top-app-bar__section{padding:4px}.mdc-top-app-bar--short{transition:width 200ms cubic-bezier(0.4, 0, 0.2, 1)}.mdc-top-app-bar--short-collapsed{transition:width 250ms cubic-bezier(0.4, 0, 0.2, 1)}.mdc-top-app-bar--short-collapsed .mdc-top-app-bar__section--align-end{padding-left:0;padding-right:12px}[dir=rtl] .mdc-top-app-bar--short-collapsed .mdc-top-app-bar__section--align-end,.mdc-top-app-bar--short-collapsed .mdc-top-app-bar__section--align-end[dir=rtl]{padding-left:12px;padding-right:0}.mdc-top-app-bar--prominent .mdc-top-app-bar__title{padding-bottom:6px}.mdc-top-app-bar--fixed-adjust{padding-top:56px}}.mdc-typography{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-font-family, Roboto, sans-serif)}.mdc-typography--headline1{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-headline1-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:6rem;font-size:var(--mdc-typography-headline1-font-size, 6rem);line-height:6rem;line-height:var(--mdc-typography-headline1-line-height, 6rem);font-weight:300;font-weight:var(--mdc-typography-headline1-font-weight, 300);letter-spacing:-0.015625em;letter-spacing:var(--mdc-typography-headline1-letter-spacing, -0.015625em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-headline1-text-decoration, inherit);text-decoration:var(--mdc-typography-headline1-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-headline1-text-transform, inherit)}.mdc-typography--headline2{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-headline2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:3.75rem;font-size:var(--mdc-typography-headline2-font-size, 3.75rem);line-height:3.75rem;line-height:var(--mdc-typography-headline2-line-height, 3.75rem);font-weight:300;font-weight:var(--mdc-typography-headline2-font-weight, 300);letter-spacing:-0.0083333333em;letter-spacing:var(--mdc-typography-headline2-letter-spacing, -0.0083333333em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-headline2-text-decoration, inherit);text-decoration:var(--mdc-typography-headline2-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-headline2-text-transform, inherit)}.mdc-typography--headline3{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-headline3-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:3rem;font-size:var(--mdc-typography-headline3-font-size, 3rem);line-height:3.125rem;line-height:var(--mdc-typography-headline3-line-height, 3.125rem);font-weight:400;font-weight:var(--mdc-typography-headline3-font-weight, 400);letter-spacing:normal;letter-spacing:var(--mdc-typography-headline3-letter-spacing, normal);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-headline3-text-decoration, inherit);text-decoration:var(--mdc-typography-headline3-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-headline3-text-transform, inherit)}.mdc-typography--headline4{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-headline4-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:2.125rem;font-size:var(--mdc-typography-headline4-font-size, 2.125rem);line-height:2.5rem;line-height:var(--mdc-typography-headline4-line-height, 2.5rem);font-weight:400;font-weight:var(--mdc-typography-headline4-font-weight, 400);letter-spacing:0.0073529412em;letter-spacing:var(--mdc-typography-headline4-letter-spacing, 0.0073529412em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-headline4-text-decoration, inherit);text-decoration:var(--mdc-typography-headline4-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-headline4-text-transform, inherit)}.mdc-typography--headline5{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-headline5-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:1.5rem;font-size:var(--mdc-typography-headline5-font-size, 1.5rem);line-height:2rem;line-height:var(--mdc-typography-headline5-line-height, 2rem);font-weight:400;font-weight:var(--mdc-typography-headline5-font-weight, 400);letter-spacing:normal;letter-spacing:var(--mdc-typography-headline5-letter-spacing, normal);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-headline5-text-decoration, inherit);text-decoration:var(--mdc-typography-headline5-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-headline5-text-transform, inherit)}.mdc-typography--headline6{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-headline6-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:1.25rem;font-size:var(--mdc-typography-headline6-font-size, 1.25rem);line-height:2rem;line-height:var(--mdc-typography-headline6-line-height, 2rem);font-weight:500;font-weight:var(--mdc-typography-headline6-font-weight, 500);letter-spacing:0.0125em;letter-spacing:var(--mdc-typography-headline6-letter-spacing, 0.0125em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-headline6-text-decoration, inherit);text-decoration:var(--mdc-typography-headline6-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-headline6-text-transform, inherit)}.mdc-typography--subtitle1{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-subtitle1-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:1rem;font-size:var(--mdc-typography-subtitle1-font-size, 1rem);line-height:1.75rem;line-height:var(--mdc-typography-subtitle1-line-height, 1.75rem);font-weight:400;font-weight:var(--mdc-typography-subtitle1-font-weight, 400);letter-spacing:0.009375em;letter-spacing:var(--mdc-typography-subtitle1-letter-spacing, 0.009375em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-subtitle1-text-decoration, inherit);text-decoration:var(--mdc-typography-subtitle1-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-subtitle1-text-transform, inherit)}.mdc-typography--subtitle2{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-subtitle2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.875rem;font-size:var(--mdc-typography-subtitle2-font-size, 0.875rem);line-height:1.375rem;line-height:var(--mdc-typography-subtitle2-line-height, 1.375rem);font-weight:500;font-weight:var(--mdc-typography-subtitle2-font-weight, 500);letter-spacing:0.0071428571em;letter-spacing:var(--mdc-typography-subtitle2-letter-spacing, 0.0071428571em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-subtitle2-text-decoration, inherit);text-decoration:var(--mdc-typography-subtitle2-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-subtitle2-text-transform, inherit)}.mdc-typography--body1{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-body1-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:1rem;font-size:var(--mdc-typography-body1-font-size, 1rem);line-height:1.5rem;line-height:var(--mdc-typography-body1-line-height, 1.5rem);font-weight:400;font-weight:var(--mdc-typography-body1-font-weight, 400);letter-spacing:0.03125em;letter-spacing:var(--mdc-typography-body1-letter-spacing, 0.03125em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-body1-text-decoration, inherit);text-decoration:var(--mdc-typography-body1-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-body1-text-transform, inherit)}.mdc-typography--body2{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.875rem;font-size:var(--mdc-typography-body2-font-size, 0.875rem);line-height:1.25rem;line-height:var(--mdc-typography-body2-line-height, 1.25rem);font-weight:400;font-weight:var(--mdc-typography-body2-font-weight, 400);letter-spacing:0.0178571429em;letter-spacing:var(--mdc-typography-body2-letter-spacing, 0.0178571429em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-body2-text-transform, inherit)}.mdc-typography--caption{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-caption-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.75rem;font-size:var(--mdc-typography-caption-font-size, 0.75rem);line-height:1.25rem;line-height:var(--mdc-typography-caption-line-height, 1.25rem);font-weight:400;font-weight:var(--mdc-typography-caption-font-weight, 400);letter-spacing:0.0333333333em;letter-spacing:var(--mdc-typography-caption-letter-spacing, 0.0333333333em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-caption-text-decoration, inherit);text-decoration:var(--mdc-typography-caption-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-caption-text-transform, inherit)}.mdc-typography--button{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-button-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.875rem;font-size:var(--mdc-typography-button-font-size, 0.875rem);line-height:2.25rem;line-height:var(--mdc-typography-button-line-height, 2.25rem);font-weight:500;font-weight:var(--mdc-typography-button-font-weight, 500);letter-spacing:0.0892857143em;letter-spacing:var(--mdc-typography-button-letter-spacing, 0.0892857143em);text-decoration:none;-webkit-text-decoration:var(--mdc-typography-button-text-decoration, none);text-decoration:var(--mdc-typography-button-text-decoration, none);text-transform:uppercase;text-transform:var(--mdc-typography-button-text-transform, uppercase)}.mdc-typography--overline{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-overline-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.75rem;font-size:var(--mdc-typography-overline-font-size, 0.75rem);line-height:2rem;line-height:var(--mdc-typography-overline-line-height, 2rem);font-weight:500;font-weight:var(--mdc-typography-overline-font-weight, 500);letter-spacing:0.1666666667em;letter-spacing:var(--mdc-typography-overline-letter-spacing, 0.1666666667em);text-decoration:none;-webkit-text-decoration:var(--mdc-typography-overline-text-decoration, none);text-decoration:var(--mdc-typography-overline-text-decoration, none);text-transform:uppercase;text-transform:var(--mdc-typography-overline-text-transform, uppercase)}
+
+ /*# sourceMappingURL=material-components-web.min.css.map*/
\ No newline at end of file
diff --git a/pkgs/csslib/third_party/pure/LICENSE b/pkgs/csslib/third_party/pure/LICENSE
new file mode 100644
index 0000000..aae45d8
--- /dev/null
+++ b/pkgs/csslib/third_party/pure/LICENSE
@@ -0,0 +1,29 @@
+Software License Agreement (BSD License)
+========================================
+
+Copyright 2013 Yahoo! Inc.
+
+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 the Yahoo! Inc. 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 YAHOO! INC. 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.
diff --git a/pkgs/csslib/third_party/pure/README.md b/pkgs/csslib/third_party/pure/README.md
new file mode 100644
index 0000000..7c005a6
--- /dev/null
+++ b/pkgs/csslib/third_party/pure/README.md
@@ -0,0 +1,4 @@
+This folder contains sample css files from the open-source project
+https://github.com/pure-css/pure/
+
+This code was included under the terms in the `LICENSE` file.
\ No newline at end of file
diff --git a/pkgs/csslib/third_party/pure/main-grid.css b/pkgs/csslib/third_party/pure/main-grid.css
new file mode 100644
index 0000000..0598dfe
--- /dev/null
+++ b/pkgs/csslib/third_party/pure/main-grid.css
@@ -0,0 +1,855 @@
+@media screen and (min-width: 35.5em) {
+ .u-sm-1,
+ .u-sm-1-1,
+ .u-sm-1-2,
+ .u-sm-1-3,
+ .u-sm-2-3,
+ .u-sm-1-4,
+ .u-sm-3-4,
+ .u-sm-1-5,
+ .u-sm-2-5,
+ .u-sm-3-5,
+ .u-sm-4-5,
+ .u-sm-5-5,
+ .u-sm-1-6,
+ .u-sm-5-6,
+ .u-sm-1-8,
+ .u-sm-3-8,
+ .u-sm-5-8,
+ .u-sm-7-8,
+ .u-sm-1-12,
+ .u-sm-5-12,
+ .u-sm-7-12,
+ .u-sm-11-12,
+ .u-sm-1-24,
+ .u-sm-2-24,
+ .u-sm-3-24,
+ .u-sm-4-24,
+ .u-sm-5-24,
+ .u-sm-6-24,
+ .u-sm-7-24,
+ .u-sm-8-24,
+ .u-sm-9-24,
+ .u-sm-10-24,
+ .u-sm-11-24,
+ .u-sm-12-24,
+ .u-sm-13-24,
+ .u-sm-14-24,
+ .u-sm-15-24,
+ .u-sm-16-24,
+ .u-sm-17-24,
+ .u-sm-18-24,
+ .u-sm-19-24,
+ .u-sm-20-24,
+ .u-sm-21-24,
+ .u-sm-22-24,
+ .u-sm-23-24,
+ .u-sm-24-24 {
+ display: inline-block;
+ *display: inline;
+ zoom: 1;
+ letter-spacing: normal;
+ word-spacing: normal;
+ vertical-align: top;
+ text-rendering: auto;
+ }
+
+ .u-sm-1-24 {
+ width: 4.1667%;
+ *width: 4.1357%;
+ }
+
+ .u-sm-1-12,
+ .u-sm-2-24 {
+ width: 8.3333%;
+ *width: 8.3023%;
+ }
+
+ .u-sm-1-8,
+ .u-sm-3-24 {
+ width: 12.5000%;
+ *width: 12.4690%;
+ }
+
+ .u-sm-1-6,
+ .u-sm-4-24 {
+ width: 16.6667%;
+ *width: 16.6357%;
+ }
+
+ .u-sm-1-5 {
+ width: 20%;
+ *width: 19.9690%;
+ }
+
+ .u-sm-5-24 {
+ width: 20.8333%;
+ *width: 20.8023%;
+ }
+
+ .u-sm-1-4,
+ .u-sm-6-24 {
+ width: 25%;
+ *width: 24.9690%;
+ }
+
+ .u-sm-7-24 {
+ width: 29.1667%;
+ *width: 29.1357%;
+ }
+
+ .u-sm-1-3,
+ .u-sm-8-24 {
+ width: 33.3333%;
+ *width: 33.3023%;
+ }
+
+ .u-sm-3-8,
+ .u-sm-9-24 {
+ width: 37.5000%;
+ *width: 37.4690%;
+ }
+
+ .u-sm-2-5 {
+ width: 40%;
+ *width: 39.9690%;
+ }
+
+ .u-sm-5-12,
+ .u-sm-10-24 {
+ width: 41.6667%;
+ *width: 41.6357%;
+ }
+
+ .u-sm-11-24 {
+ width: 45.8333%;
+ *width: 45.8023%;
+ }
+
+ .u-sm-1-2,
+ .u-sm-12-24 {
+ width: 50%;
+ *width: 49.9690%;
+ }
+
+ .u-sm-13-24 {
+ width: 54.1667%;
+ *width: 54.1357%;
+ }
+
+ .u-sm-7-12,
+ .u-sm-14-24 {
+ width: 58.3333%;
+ *width: 58.3023%;
+ }
+
+ .u-sm-3-5 {
+ width: 60%;
+ *width: 59.9690%;
+ }
+
+ .u-sm-5-8,
+ .u-sm-15-24 {
+ width: 62.5000%;
+ *width: 62.4690%;
+ }
+
+ .u-sm-2-3,
+ .u-sm-16-24 {
+ width: 66.6667%;
+ *width: 66.6357%;
+ }
+
+ .u-sm-17-24 {
+ width: 70.8333%;
+ *width: 70.8023%;
+ }
+
+ .u-sm-3-4,
+ .u-sm-18-24 {
+ width: 75%;
+ *width: 74.9690%;
+ }
+
+ .u-sm-19-24 {
+ width: 79.1667%;
+ *width: 79.1357%;
+ }
+
+ .u-sm-4-5 {
+ width: 80%;
+ *width: 79.9690%;
+ }
+
+ .u-sm-5-6,
+ .u-sm-20-24 {
+ width: 83.3333%;
+ *width: 83.3023%;
+ }
+
+ .u-sm-7-8,
+ .u-sm-21-24 {
+ width: 87.5000%;
+ *width: 87.4690%;
+ }
+
+ .u-sm-11-12,
+ .u-sm-22-24 {
+ width: 91.6667%;
+ *width: 91.6357%;
+ }
+
+ .u-sm-23-24 {
+ width: 95.8333%;
+ *width: 95.8023%;
+ }
+
+ .u-sm-1,
+ .u-sm-1-1,
+ .u-sm-5-5,
+ .u-sm-24-24 {
+ width: 100%;
+ }
+}
+
+@media screen and (min-width: 48em) {
+ .u-md-1,
+ .u-md-1-1,
+ .u-md-1-2,
+ .u-md-1-3,
+ .u-md-2-3,
+ .u-md-1-4,
+ .u-md-3-4,
+ .u-md-1-5,
+ .u-md-2-5,
+ .u-md-3-5,
+ .u-md-4-5,
+ .u-md-5-5,
+ .u-md-1-6,
+ .u-md-5-6,
+ .u-md-1-8,
+ .u-md-3-8,
+ .u-md-5-8,
+ .u-md-7-8,
+ .u-md-1-12,
+ .u-md-5-12,
+ .u-md-7-12,
+ .u-md-11-12,
+ .u-md-1-24,
+ .u-md-2-24,
+ .u-md-3-24,
+ .u-md-4-24,
+ .u-md-5-24,
+ .u-md-6-24,
+ .u-md-7-24,
+ .u-md-8-24,
+ .u-md-9-24,
+ .u-md-10-24,
+ .u-md-11-24,
+ .u-md-12-24,
+ .u-md-13-24,
+ .u-md-14-24,
+ .u-md-15-24,
+ .u-md-16-24,
+ .u-md-17-24,
+ .u-md-18-24,
+ .u-md-19-24,
+ .u-md-20-24,
+ .u-md-21-24,
+ .u-md-22-24,
+ .u-md-23-24,
+ .u-md-24-24 {
+ display: inline-block;
+ *display: inline;
+ zoom: 1;
+ letter-spacing: normal;
+ word-spacing: normal;
+ vertical-align: top;
+ text-rendering: auto;
+ }
+
+ .u-md-1-24 {
+ width: 4.1667%;
+ *width: 4.1357%;
+ }
+
+ .u-md-1-12,
+ .u-md-2-24 {
+ width: 8.3333%;
+ *width: 8.3023%;
+ }
+
+ .u-md-1-8,
+ .u-md-3-24 {
+ width: 12.5000%;
+ *width: 12.4690%;
+ }
+
+ .u-md-1-6,
+ .u-md-4-24 {
+ width: 16.6667%;
+ *width: 16.6357%;
+ }
+
+ .u-md-1-5 {
+ width: 20%;
+ *width: 19.9690%;
+ }
+
+ .u-md-5-24 {
+ width: 20.8333%;
+ *width: 20.8023%;
+ }
+
+ .u-md-1-4,
+ .u-md-6-24 {
+ width: 25%;
+ *width: 24.9690%;
+ }
+
+ .u-md-7-24 {
+ width: 29.1667%;
+ *width: 29.1357%;
+ }
+
+ .u-md-1-3,
+ .u-md-8-24 {
+ width: 33.3333%;
+ *width: 33.3023%;
+ }
+
+ .u-md-3-8,
+ .u-md-9-24 {
+ width: 37.5000%;
+ *width: 37.4690%;
+ }
+
+ .u-md-2-5 {
+ width: 40%;
+ *width: 39.9690%;
+ }
+
+ .u-md-5-12,
+ .u-md-10-24 {
+ width: 41.6667%;
+ *width: 41.6357%;
+ }
+
+ .u-md-11-24 {
+ width: 45.8333%;
+ *width: 45.8023%;
+ }
+
+ .u-md-1-2,
+ .u-md-12-24 {
+ width: 50%;
+ *width: 49.9690%;
+ }
+
+ .u-md-13-24 {
+ width: 54.1667%;
+ *width: 54.1357%;
+ }
+
+ .u-md-7-12,
+ .u-md-14-24 {
+ width: 58.3333%;
+ *width: 58.3023%;
+ }
+
+ .u-md-3-5 {
+ width: 60%;
+ *width: 59.9690%;
+ }
+
+ .u-md-5-8,
+ .u-md-15-24 {
+ width: 62.5000%;
+ *width: 62.4690%;
+ }
+
+ .u-md-2-3,
+ .u-md-16-24 {
+ width: 66.6667%;
+ *width: 66.6357%;
+ }
+
+ .u-md-17-24 {
+ width: 70.8333%;
+ *width: 70.8023%;
+ }
+
+ .u-md-3-4,
+ .u-md-18-24 {
+ width: 75%;
+ *width: 74.9690%;
+ }
+
+ .u-md-19-24 {
+ width: 79.1667%;
+ *width: 79.1357%;
+ }
+
+ .u-md-4-5 {
+ width: 80%;
+ *width: 79.9690%;
+ }
+
+ .u-md-5-6,
+ .u-md-20-24 {
+ width: 83.3333%;
+ *width: 83.3023%;
+ }
+
+ .u-md-7-8,
+ .u-md-21-24 {
+ width: 87.5000%;
+ *width: 87.4690%;
+ }
+
+ .u-md-11-12,
+ .u-md-22-24 {
+ width: 91.6667%;
+ *width: 91.6357%;
+ }
+
+ .u-md-23-24 {
+ width: 95.8333%;
+ *width: 95.8023%;
+ }
+
+ .u-md-1,
+ .u-md-1-1,
+ .u-md-5-5,
+ .u-md-24-24 {
+ width: 100%;
+ }
+}
+
+@media screen and (min-width: 58em) {
+ .u-lg-1,
+ .u-lg-1-1,
+ .u-lg-1-2,
+ .u-lg-1-3,
+ .u-lg-2-3,
+ .u-lg-1-4,
+ .u-lg-3-4,
+ .u-lg-1-5,
+ .u-lg-2-5,
+ .u-lg-3-5,
+ .u-lg-4-5,
+ .u-lg-5-5,
+ .u-lg-1-6,
+ .u-lg-5-6,
+ .u-lg-1-8,
+ .u-lg-3-8,
+ .u-lg-5-8,
+ .u-lg-7-8,
+ .u-lg-1-12,
+ .u-lg-5-12,
+ .u-lg-7-12,
+ .u-lg-11-12,
+ .u-lg-1-24,
+ .u-lg-2-24,
+ .u-lg-3-24,
+ .u-lg-4-24,
+ .u-lg-5-24,
+ .u-lg-6-24,
+ .u-lg-7-24,
+ .u-lg-8-24,
+ .u-lg-9-24,
+ .u-lg-10-24,
+ .u-lg-11-24,
+ .u-lg-12-24,
+ .u-lg-13-24,
+ .u-lg-14-24,
+ .u-lg-15-24,
+ .u-lg-16-24,
+ .u-lg-17-24,
+ .u-lg-18-24,
+ .u-lg-19-24,
+ .u-lg-20-24,
+ .u-lg-21-24,
+ .u-lg-22-24,
+ .u-lg-23-24,
+ .u-lg-24-24 {
+ display: inline-block;
+ *display: inline;
+ zoom: 1;
+ letter-spacing: normal;
+ word-spacing: normal;
+ vertical-align: top;
+ text-rendering: auto;
+ }
+
+ .u-lg-1-24 {
+ width: 4.1667%;
+ *width: 4.1357%;
+ }
+
+ .u-lg-1-12,
+ .u-lg-2-24 {
+ width: 8.3333%;
+ *width: 8.3023%;
+ }
+
+ .u-lg-1-8,
+ .u-lg-3-24 {
+ width: 12.5000%;
+ *width: 12.4690%;
+ }
+
+ .u-lg-1-6,
+ .u-lg-4-24 {
+ width: 16.6667%;
+ *width: 16.6357%;
+ }
+
+ .u-lg-1-5 {
+ width: 20%;
+ *width: 19.9690%;
+ }
+
+ .u-lg-5-24 {
+ width: 20.8333%;
+ *width: 20.8023%;
+ }
+
+ .u-lg-1-4,
+ .u-lg-6-24 {
+ width: 25%;
+ *width: 24.9690%;
+ }
+
+ .u-lg-7-24 {
+ width: 29.1667%;
+ *width: 29.1357%;
+ }
+
+ .u-lg-1-3,
+ .u-lg-8-24 {
+ width: 33.3333%;
+ *width: 33.3023%;
+ }
+
+ .u-lg-3-8,
+ .u-lg-9-24 {
+ width: 37.5000%;
+ *width: 37.4690%;
+ }
+
+ .u-lg-2-5 {
+ width: 40%;
+ *width: 39.9690%;
+ }
+
+ .u-lg-5-12,
+ .u-lg-10-24 {
+ width: 41.6667%;
+ *width: 41.6357%;
+ }
+
+ .u-lg-11-24 {
+ width: 45.8333%;
+ *width: 45.8023%;
+ }
+
+ .u-lg-1-2,
+ .u-lg-12-24 {
+ width: 50%;
+ *width: 49.9690%;
+ }
+
+ .u-lg-13-24 {
+ width: 54.1667%;
+ *width: 54.1357%;
+ }
+
+ .u-lg-7-12,
+ .u-lg-14-24 {
+ width: 58.3333%;
+ *width: 58.3023%;
+ }
+
+ .u-lg-3-5 {
+ width: 60%;
+ *width: 59.9690%;
+ }
+
+ .u-lg-5-8,
+ .u-lg-15-24 {
+ width: 62.5000%;
+ *width: 62.4690%;
+ }
+
+ .u-lg-2-3,
+ .u-lg-16-24 {
+ width: 66.6667%;
+ *width: 66.6357%;
+ }
+
+ .u-lg-17-24 {
+ width: 70.8333%;
+ *width: 70.8023%;
+ }
+
+ .u-lg-3-4,
+ .u-lg-18-24 {
+ width: 75%;
+ *width: 74.9690%;
+ }
+
+ .u-lg-19-24 {
+ width: 79.1667%;
+ *width: 79.1357%;
+ }
+
+ .u-lg-4-5 {
+ width: 80%;
+ *width: 79.9690%;
+ }
+
+ .u-lg-5-6,
+ .u-lg-20-24 {
+ width: 83.3333%;
+ *width: 83.3023%;
+ }
+
+ .u-lg-7-8,
+ .u-lg-21-24 {
+ width: 87.5000%;
+ *width: 87.4690%;
+ }
+
+ .u-lg-11-12,
+ .u-lg-22-24 {
+ width: 91.6667%;
+ *width: 91.6357%;
+ }
+
+ .u-lg-23-24 {
+ width: 95.8333%;
+ *width: 95.8023%;
+ }
+
+ .u-lg-1,
+ .u-lg-1-1,
+ .u-lg-5-5,
+ .u-lg-24-24 {
+ width: 100%;
+ }
+}
+
+@media screen and (min-width: 75em) {
+ .u-xl-1,
+ .u-xl-1-1,
+ .u-xl-1-2,
+ .u-xl-1-3,
+ .u-xl-2-3,
+ .u-xl-1-4,
+ .u-xl-3-4,
+ .u-xl-1-5,
+ .u-xl-2-5,
+ .u-xl-3-5,
+ .u-xl-4-5,
+ .u-xl-5-5,
+ .u-xl-1-6,
+ .u-xl-5-6,
+ .u-xl-1-8,
+ .u-xl-3-8,
+ .u-xl-5-8,
+ .u-xl-7-8,
+ .u-xl-1-12,
+ .u-xl-5-12,
+ .u-xl-7-12,
+ .u-xl-11-12,
+ .u-xl-1-24,
+ .u-xl-2-24,
+ .u-xl-3-24,
+ .u-xl-4-24,
+ .u-xl-5-24,
+ .u-xl-6-24,
+ .u-xl-7-24,
+ .u-xl-8-24,
+ .u-xl-9-24,
+ .u-xl-10-24,
+ .u-xl-11-24,
+ .u-xl-12-24,
+ .u-xl-13-24,
+ .u-xl-14-24,
+ .u-xl-15-24,
+ .u-xl-16-24,
+ .u-xl-17-24,
+ .u-xl-18-24,
+ .u-xl-19-24,
+ .u-xl-20-24,
+ .u-xl-21-24,
+ .u-xl-22-24,
+ .u-xl-23-24,
+ .u-xl-24-24 {
+ display: inline-block;
+ *display: inline;
+ zoom: 1;
+ letter-spacing: normal;
+ word-spacing: normal;
+ vertical-align: top;
+ text-rendering: auto;
+ }
+
+ .u-xl-1-24 {
+ width: 4.1667%;
+ *width: 4.1357%;
+ }
+
+ .u-xl-1-12,
+ .u-xl-2-24 {
+ width: 8.3333%;
+ *width: 8.3023%;
+ }
+
+ .u-xl-1-8,
+ .u-xl-3-24 {
+ width: 12.5000%;
+ *width: 12.4690%;
+ }
+
+ .u-xl-1-6,
+ .u-xl-4-24 {
+ width: 16.6667%;
+ *width: 16.6357%;
+ }
+
+ .u-xl-1-5 {
+ width: 20%;
+ *width: 19.9690%;
+ }
+
+ .u-xl-5-24 {
+ width: 20.8333%;
+ *width: 20.8023%;
+ }
+
+ .u-xl-1-4,
+ .u-xl-6-24 {
+ width: 25%;
+ *width: 24.9690%;
+ }
+
+ .u-xl-7-24 {
+ width: 29.1667%;
+ *width: 29.1357%;
+ }
+
+ .u-xl-1-3,
+ .u-xl-8-24 {
+ width: 33.3333%;
+ *width: 33.3023%;
+ }
+
+ .u-xl-3-8,
+ .u-xl-9-24 {
+ width: 37.5000%;
+ *width: 37.4690%;
+ }
+
+ .u-xl-2-5 {
+ width: 40%;
+ *width: 39.9690%;
+ }
+
+ .u-xl-5-12,
+ .u-xl-10-24 {
+ width: 41.6667%;
+ *width: 41.6357%;
+ }
+
+ .u-xl-11-24 {
+ width: 45.8333%;
+ *width: 45.8023%;
+ }
+
+ .u-xl-1-2,
+ .u-xl-12-24 {
+ width: 50%;
+ *width: 49.9690%;
+ }
+
+ .u-xl-13-24 {
+ width: 54.1667%;
+ *width: 54.1357%;
+ }
+
+ .u-xl-7-12,
+ .u-xl-14-24 {
+ width: 58.3333%;
+ *width: 58.3023%;
+ }
+
+ .u-xl-3-5 {
+ width: 60%;
+ *width: 59.9690%;
+ }
+
+ .u-xl-5-8,
+ .u-xl-15-24 {
+ width: 62.5000%;
+ *width: 62.4690%;
+ }
+
+ .u-xl-2-3,
+ .u-xl-16-24 {
+ width: 66.6667%;
+ *width: 66.6357%;
+ }
+
+ .u-xl-17-24 {
+ width: 70.8333%;
+ *width: 70.8023%;
+ }
+
+ .u-xl-3-4,
+ .u-xl-18-24 {
+ width: 75%;
+ *width: 74.9690%;
+ }
+
+ .u-xl-19-24 {
+ width: 79.1667%;
+ *width: 79.1357%;
+ }
+
+ .u-xl-4-5 {
+ width: 80%;
+ *width: 79.9690%;
+ }
+
+ .u-xl-5-6,
+ .u-xl-20-24 {
+ width: 83.3333%;
+ *width: 83.3023%;
+ }
+
+ .u-xl-7-8,
+ .u-xl-21-24 {
+ width: 87.5000%;
+ *width: 87.4690%;
+ }
+
+ .u-xl-11-12,
+ .u-xl-22-24 {
+ width: 91.6667%;
+ *width: 91.6357%;
+ }
+
+ .u-xl-23-24 {
+ width: 95.8333%;
+ *width: 95.8023%;
+ }
+
+ .u-xl-1,
+ .u-xl-1-1,
+ .u-xl-5-5,
+ .u-xl-24-24 {
+ width: 100%;
+ }
+}
\ No newline at end of file
diff --git a/pkgs/csslib/third_party/pure/main.css b/pkgs/csslib/third_party/pure/main.css
new file mode 100644
index 0000000..ed5f613
--- /dev/null
+++ b/pkgs/csslib/third_party/pure/main.css
@@ -0,0 +1,624 @@
+* {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+*:before,
+*:after {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+html, button, input, select, textarea,
+.pure-g [class *= "pure-u"] {
+ font-family: Helvetica, Arial, sans-serif;
+ letter-spacing: 0.01em;
+}
+
+
+/* --------------------------
+ * Element Styles
+ * --------------------------
+*/
+
+body {
+ min-width: 320px;
+ background-color: #fff;
+ color: #777;
+ line-height: 1.6;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ font-weight: bold;
+ color: rgb(75, 75, 75);
+}
+h3 {
+ font-size: 1.25em;
+}
+h4 {
+ font-size: 1.125em;
+}
+
+a {
+ color: #3b8bba; /* block-background-text-normal */
+ text-decoration: none;
+}
+
+a:visited {
+ color: #265778; /* block-normal-text-normal */
+}
+
+dt {
+ font-weight: bold;
+}
+dd {
+ margin: 0 0 10px 0;
+}
+
+aside {
+ background: #1f8dd6; /* same color as selected state on site menu */
+ margin: 1em 0;
+ padding: 0.3em 1em;
+ border-radius: 3px;
+ color: #fff;
+}
+ aside a, aside a:visited {
+ color: rgb(169, 226, 255);
+ }
+
+
+/* --------------------------
+ * Layout Styles
+ * --------------------------
+*/
+
+/* Navigation Push Styles */
+#layout {
+ position: relative;
+ padding-left: 0;
+}
+ #layout.active #menu {
+ left: 160px;
+ width: 160px;
+ }
+
+/* Apply the .box class on the immediate parent of any grid element (pure-u-*) to apply some padding. */
+.l-box {
+ padding: 1em;
+}
+
+.l-wrap {
+ margin-left: auto;
+ margin-right: auto;
+}
+.content .l-wrap {
+ margin-left: -1em;
+ margin-right: -1em;
+}
+
+
+/* --------------------------
+ * Header Module Styles
+ * --------------------------
+*/
+
+.header {
+ font-family: "Raleway", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ max-width: 768px;
+ margin: 0 auto;
+ padding: 1em;
+ text-align: center;
+ border-bottom: 1px solid #eee;
+ background: #fff;
+ letter-spacing: 0.05em;
+}
+ .header h1 {
+ font-size: 300%;
+ font-weight: 100;
+ margin: 0;
+ }
+ .header h2 {
+ font-size: 125%;
+ font-weight: 100;
+ line-height: 1.5;
+ margin: 0;
+ color: #666;
+ letter-spacing: -0.02em;
+ }
+
+
+ /* --------------------------
+ * Content Module Styles
+ * --------------------------
+ */
+
+/* The content div is placed as a wrapper around all the docs */
+.content {
+ margin-left: auto;
+ margin-right: auto;
+ padding-left: 1em;
+ padding-right: 1em;
+ max-width: 768px;
+}
+
+ .content .content-subhead {
+ margin: 2em 0 1em 0;
+ font-weight: 300;
+ color: #888;
+ position: relative;
+ }
+
+ .content .content-spaced {
+ line-height: 1.8;
+ }
+
+ .content .content-quote {
+ font-family: "Georgia", serif;
+ color: #666;
+ font-style: italic;
+ line-height: 1.8;
+ border-left: 5px solid #ddd;
+ padding-left: 1.5em;
+ }
+
+ .content-link {
+ position: absolute;
+ top: 0;
+ right: 0;
+ display: block;
+ height: 100%;
+ width: 20px;
+ background: transparent url('/img/link-icon.png') no-repeat center center;
+ background-size: 20px 20px;
+ }
+
+ @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 2dppx) {
+ .content-link {
+ background-image: url('/img/link-icon@2x.png');
+ }
+ }
+
+
+/* --------------------------
+ * Code Styles
+ * --------------------------
+*/
+
+pre,
+code {
+ font-family: Consolas, Courier, monospace;
+ color: #333;
+ background: rgb(250, 250, 250);
+}
+
+code {
+ padding: 0.2em 0.4em;
+ white-space: nowrap;
+}
+.content p code {
+ font-size: 90%;
+}
+
+.code {
+ margin-left: -1em;
+ margin-right: -1em;
+ border: 1px solid #eee;
+ border-left-width: 0;
+ border-right-width: 0;
+ overflow-x: auto;
+}
+.code pre {
+ margin: 0;
+}
+.code code {
+ font-size: 95%;
+ white-space: pre;
+ word-wrap: normal;
+ padding: 0;
+ background: none;
+}
+.code-wrap code {
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+.example .code {
+ margin-top: 1em;
+}
+
+/* --------------------------
+ * Footer Module Styles
+ * --------------------------
+*/
+
+.footer {
+ font-size: 87.5%;
+ border-top: 1px solid #eee;
+ margin-top: 3.4286em;
+ padding: 1.1429em;
+ background: rgb(250, 250, 250);
+}
+
+.legal {
+ line-height: 1.6;
+ text-align: center;
+ margin: 0 auto;
+}
+
+ .legal-license {
+ margin-top: 0;
+ }
+ .legal-links {
+ list-style: none;
+ padding: 0;
+ margin-bottom: 0;
+ }
+ .legal-copyright {
+ margin-top: 0;
+ margin-bottom: 0;
+ }
+
+
+/* --------------------------
+ * Main Navigation Bar Styles
+ * --------------------------
+*/
+
+/* Add transition to containers so they can push in and out */
+#layout,
+#menu,
+.menu-link {
+ -webkit-transition: all 0.2s ease-out;
+ -moz-transition: all 0.2s ease-out;
+ -ms-transition: all 0.2s ease-out;
+ -o-transition: all 0.2s ease-out;
+ transition: all 0.2s ease-out;
+}
+
+#layout.active .menu-link {
+ left: 160px;
+}
+
+#menu {
+ margin-left: -160px; /* "#menu" width */
+ width: 160px;
+ position: fixed;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ z-index: 1000; /* so the menu or its navicon stays above all content */
+ background: #191818;
+ overflow-y: auto;
+}
+ #menu a {
+ color: #999;
+ border: none;
+ white-space: normal;
+ padding: 0.625em 1em;
+ }
+
+ #menu .pure-menu-open {
+ background: transparent;
+ border: 0;
+ }
+
+ #menu .pure-menu ul {
+ border: none;
+ background: transparent;
+ display: block;
+ }
+
+ #menu .pure-menu ul,
+ #menu .pure-menu .menu-item-divided {
+ border-top: 1px solid #333;
+ }
+
+ #menu .pure-menu-list li .pure-menu-link:hover,
+ #menu .pure-menu-list li .pure-menu-link:focus {
+ background: #333;
+ }
+
+ .menu-link {
+ position: fixed;
+ display: block; /* show this only on small screens */
+ top: 0;
+ left: 0; /* "#menu width" */
+ background: #000;
+ background: rgba(0,0,0,0.7);
+ font-size: 11px; /* change this value to increase/decrease button size */
+ z-index: 10;
+ width: 4em;
+ height: 4em;
+ padding: 1em;
+ }
+
+ .menu-link:hover,
+ .menu-link:focus {
+ background: #000;
+ }
+
+ .menu-link span {
+ position: relative;
+ display: block;
+ margin-top: 0.9em;
+ }
+
+ .menu-link span,
+ .menu-link span:before,
+ .menu-link span:after {
+ background-color: #fff;
+ pointer-events: none;
+ width: 100%;
+ height: .2em;
+ -webkit-transition: all 0.4s;
+ -moz-transition: all 0.4s;
+ -ms-transition: all 0.4s;
+ -o-transition: all 0.4s;
+ transition: all 0.4s;
+ }
+
+ .menu-link span:before,
+ .menu-link span:after {
+ position: absolute;
+ top: -.55em;
+ content: " ";
+ }
+
+ .menu-link span:after {
+ top: .55em;
+ }
+
+ .menu-link.active span {
+ background: transparent;
+ }
+
+ .menu-link.active span:before {
+ -webkit-transform: rotate(45deg) translate(.5em, .4em);
+ -moz-transform: rotate(45deg) translate(.5em, .4em);
+ -ms-transform: rotate(45deg) translate(.5em, .4em);
+ -o-transform: rotate(45deg) translate(.5em, .4em);
+ transform: rotate(45deg) translate(.5em, .4em);
+ }
+
+ .menu-link.active span:after {
+ -webkit-transform: rotate(-45deg) translate(.4em, -.3em);
+ -moz-transform: rotate(-45deg) translate(.4em, -.3em);
+ -ms-transform: rotate(-45deg) translate(.4em, -.3em);
+ -o-transform: rotate(-45deg) translate(.4em, -.3em);
+ transform: rotate(-45deg) translate(.4em, -.3em);
+ }
+
+ #menu .pure-menu-heading {
+ font-size: 125%;
+ font-weight: 300;
+ letter-spacing: 0.1em;
+ color: #fff;
+ margin-top: 0;
+ padding: 0.5em 0.8em;
+ text-transform: uppercase;
+ }
+ #menu .pure-menu-heading:hover,
+ #menu .pure-menu-heading:focus {
+ color: #999;
+ }
+
+ #menu .pure-menu-item .active {
+ background: #1f8dd6;
+ color: #fff;
+ }
+
+ #menu li.pure-menu-item .active:hover,
+ #menu li.pure-menu-item .active:focus {
+ background: #1f8dd6;
+ }
+
+
+/* ---------------------
+ * Smaller Module Styles
+ * ---------------------
+*/
+
+.pure-img-responsive {
+ max-width: 100%;
+ height: auto;
+}
+
+.pure-paginator .pure-button {
+ -webkit-box-sizing: content-box;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+}
+
+.pure-button {
+ font-family: inherit;
+}
+a.pure-button-primary {
+ color: white;
+}
+
+
+/* green call to action button class */
+.notice {
+ background-color: #61B842;
+ color: white;
+}
+
+.muted {
+ color: #ccc;
+}
+
+
+
+/* -------------
+ * Table Styles
+ * -------------
+*/
+.pure-table th,
+.pure-table td {
+ padding: 0.5em 1em;
+}
+
+.table-responsive {
+ margin-left: -1em;
+ margin-right: -1em;
+ overflow-x: auto;
+ margin-bottom: 1em;
+}
+.table-responsive table {
+ width: 100%;
+ min-width: 35.5em;
+ border-left-width: 0;
+ border-right-width: 0;
+}
+
+.table-responsive .mq-table {
+ width: 100%;
+ min-width: 44em;
+}
+.mq-table th.highlight {
+ background-color: rgb(255, 234, 133);
+}
+.mq-table td.highlight {
+ background-color: rgb(255, 250, 229);
+}
+.mq-table th.highlight code,
+.mq-table td.highlight code {
+ background: rgb(255, 255, 243);
+}
+.mq-table-mq code {
+ font-size: 0.875em;
+}
+
+/* ----------------------------
+ * Example for full-width Grids
+ * ----------------------------
+*/
+
+.grids-example {
+ background: rgb(250, 250, 250);
+ margin: 2em auto;
+ border-top: 1px solid #ddd;
+ border-bottom: 1px solid #ddd;
+}
+
+/* --------------------------
+ * State Rules
+ * --------------------------
+*/
+
+
+.is-code-full {
+ text-align: center;
+}
+.is-code-full .code {
+ margin-left: auto;
+ margin-right: auto;
+}
+.is-code-full code {
+ display: inline-block;
+ max-width: 768px;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+
+/* --------------------------
+ * Responsive Styles
+ * --------------------------
+*/
+
+@media screen and (min-width: 35.5em) {
+
+ .legal-license {
+ text-align: left;
+ margin: 0;
+ }
+ .legal-copyright,
+ .legal-links,
+ .legal-links li {
+ text-align: right;
+ margin: 0;
+ }
+
+}
+
+@media screen and (min-width: 48em) {
+
+ .l-wrap,
+ .l-wrap .content {
+ padding-left: 1em;
+ padding-right: 1em;
+ }
+ .content .l-wrap {
+ margin-left: -2em;
+ margin-right: -2em;
+ }
+
+ .header,
+ .content {
+ padding-left: 2em;
+ padding-right: 2em;
+ }
+
+ .header h1 {
+ font-size: 320%;
+ }
+ .header h2 {
+ font-size: 128%;
+ }
+
+ .content p {
+ font-size: 1.125em;
+ }
+
+ .code {
+ margin-left: auto;
+ margin-right: auto;
+ border-left-width: 1px;
+ border-right-width: 1px;
+ }
+
+ .table-responsive {
+ margin-left: auto;
+ margin-right: auto;
+ }
+ .table-responsive table {
+ border-left-width: 1px;
+ border-right-width: 1px;
+ }
+
+}
+
+@media (max-width: 58em) {
+ /* Only apply this when the window is smaller. Otherwise, the following
+ case results in extra padding on the left:
+ * Make the window small. (Rotate to portrait on a mobile.)
+ * Tap the menu to trigger the active state.
+ * Make the window large again. (Rotate to landscape on mobile.)
+ */
+ #layout.active {
+ position: relative;
+ left: 160px;
+ }
+}
+
+@media (min-width: 58em) {
+
+ #layout {
+ padding-left: 160px; /* left col width "#menu" */
+ left: 0;
+ }
+ #menu {
+ left: 160px;
+ }
+ .menu-link {
+ position: fixed;
+ left: 160px;
+ display: none;
+ }
+ #layout.active .menu-link {
+ left: 160px;
+ }
+
+}
diff --git a/pkgs/csslib/third_party/skeleton/LICENSE.md b/pkgs/csslib/third_party/skeleton/LICENSE.md
new file mode 100644
index 0000000..32a62b3
--- /dev/null
+++ b/pkgs/csslib/third_party/skeleton/LICENSE.md
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2011-2014 Dave Gamache
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/pkgs/csslib/third_party/skeleton/README.me b/pkgs/csslib/third_party/skeleton/README.me
new file mode 100644
index 0000000..aa8c136
--- /dev/null
+++ b/pkgs/csslib/third_party/skeleton/README.me
@@ -0,0 +1,4 @@
+This folder contains sample css files from the open-source project
+https://github.com/dhg/Skeleton.
+
+This code was included under the terms in the `LICENSE.md file.
\ No newline at end of file
diff --git a/pkgs/csslib/third_party/skeleton/normalize.css b/pkgs/csslib/third_party/skeleton/normalize.css
new file mode 100644
index 0000000..81c6f31
--- /dev/null
+++ b/pkgs/csslib/third_party/skeleton/normalize.css
@@ -0,0 +1,427 @@
+/*! normalize.css v3.0.2 | MIT License | git.io/normalize */
+
+/**
+ * 1. Set default font family to sans-serif.
+ * 2. Prevent iOS text size adjust after orientation change, without disabling
+ * user zoom.
+ */
+
+html {
+ font-family: sans-serif; /* 1 */
+ -ms-text-size-adjust: 100%; /* 2 */
+ -webkit-text-size-adjust: 100%; /* 2 */
+}
+
+/**
+ * Remove default margin.
+ */
+
+body {
+ margin: 0;
+}
+
+/* HTML5 display definitions
+ ========================================================================== */
+
+/**
+ * Correct `block` display not defined for any HTML5 element in IE 8/9.
+ * Correct `block` display not defined for `details` or `summary` in IE 10/11
+ * and Firefox.
+ * Correct `block` display not defined for `main` in IE 11.
+ */
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+menu,
+nav,
+section,
+summary {
+ display: block;
+}
+
+/**
+ * 1. Correct `inline-block` display not defined in IE 8/9.
+ * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
+ */
+
+audio,
+canvas,
+progress,
+video {
+ display: inline-block; /* 1 */
+ vertical-align: baseline; /* 2 */
+}
+
+/**
+ * Prevent modern browsers from displaying `audio` without controls.
+ * Remove excess height in iOS 5 devices.
+ */
+
+audio:not([controls]) {
+ display: none;
+ height: 0;
+}
+
+/**
+ * Address `[hidden]` styling not present in IE 8/9/10.
+ * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
+ */
+
+[hidden],
+template {
+ display: none;
+}
+
+/* Links
+ ========================================================================== */
+
+/**
+ * Remove the gray background color from active links in IE 10.
+ */
+
+a {
+ background-color: transparent;
+}
+
+/**
+ * Improve readability when focused and also mouse hovered in all browsers.
+ */
+
+a:active,
+a:hover {
+ outline: 0;
+}
+
+/* Text-level semantics
+ ========================================================================== */
+
+/**
+ * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
+ */
+
+abbr[title] {
+ border-bottom: 1px dotted;
+}
+
+/**
+ * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
+ */
+
+b,
+strong {
+ font-weight: bold;
+}
+
+/**
+ * Address styling not present in Safari and Chrome.
+ */
+
+dfn {
+ font-style: italic;
+}
+
+/**
+ * Address variable `h1` font-size and margin within `section` and `article`
+ * contexts in Firefox 4+, Safari, and Chrome.
+ */
+
+h1 {
+ font-size: 2em;
+ margin: 0.67em 0;
+}
+
+/**
+ * Address styling not present in IE 8/9.
+ */
+
+mark {
+ background: #ff0;
+ color: #000;
+}
+
+/**
+ * Address inconsistent and variable font size in all browsers.
+ */
+
+small {
+ font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` affecting `line-height` in all browsers.
+ */
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sup {
+ top: -0.5em;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+/* Embedded content
+ ========================================================================== */
+
+/**
+ * Remove border when inside `a` element in IE 8/9/10.
+ */
+
+img {
+ border: 0;
+}
+
+/**
+ * Correct overflow not hidden in IE 9/10/11.
+ */
+
+svg:not(:root) {
+ overflow: hidden;
+}
+
+/* Grouping content
+ ========================================================================== */
+
+/**
+ * Address margin not present in IE 8/9 and Safari.
+ */
+
+figure {
+ margin: 1em 40px;
+}
+
+/**
+ * Address differences between Firefox and other browsers.
+ */
+
+hr {
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+ height: 0;
+}
+
+/**
+ * Contain overflow in all browsers.
+ */
+
+pre {
+ overflow: auto;
+}
+
+/**
+ * Address odd `em`-unit font size rendering in all browsers.
+ */
+
+code,
+kbd,
+pre,
+samp {
+ font-family: monospace, monospace;
+ font-size: 1em;
+}
+
+/* Forms
+ ========================================================================== */
+
+/**
+ * Known limitation: by default, Chrome and Safari on OS X allow very limited
+ * styling of `select`, unless a `border` property is set.
+ */
+
+/**
+ * 1. Correct color not being inherited.
+ * Known issue: affects color of disabled elements.
+ * 2. Correct font properties not being inherited.
+ * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
+ */
+
+button,
+input,
+optgroup,
+select,
+textarea {
+ color: inherit; /* 1 */
+ font: inherit; /* 2 */
+ margin: 0; /* 3 */
+}
+
+/**
+ * Address `overflow` set to `hidden` in IE 8/9/10/11.
+ */
+
+button {
+ overflow: visible;
+}
+
+/**
+ * Address inconsistent `text-transform` inheritance for `button` and `select`.
+ * All other form control elements do not inherit `text-transform` values.
+ * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
+ * Correct `select` style inheritance in Firefox.
+ */
+
+button,
+select {
+ text-transform: none;
+}
+
+/**
+ * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
+ * and `video` controls.
+ * 2. Correct inability to style clickable `input` types in iOS.
+ * 3. Improve usability and consistency of cursor style between image-type
+ * `input` and others.
+ */
+
+button,
+html input[type="button"], /* 1 */
+input[type="reset"],
+input[type="submit"] {
+ -webkit-appearance: button; /* 2 */
+ cursor: pointer; /* 3 */
+}
+
+/**
+ * Re-set default cursor for disabled elements.
+ */
+
+button[disabled],
+html input[disabled] {
+ cursor: default;
+}
+
+/**
+ * Remove inner padding and border in Firefox 4+.
+ */
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+ border: 0;
+ padding: 0;
+}
+
+/**
+ * Address Firefox 4+ setting `line-height` on `input` using `!important` in
+ * the UA stylesheet.
+ */
+
+input {
+ line-height: normal;
+}
+
+/**
+ * It's recommended that you don't attempt to style these elements.
+ * Firefox's implementation doesn't respect box-sizing, padding, or width.
+ *
+ * 1. Address box sizing set to `content-box` in IE 8/9/10.
+ * 2. Remove excess padding in IE 8/9/10.
+ */
+
+input[type="checkbox"],
+input[type="radio"] {
+ box-sizing: border-box; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * Fix the cursor style for Chrome's increment/decrement buttons. For certain
+ * `font-size` values of the `input`, it causes the cursor style of the
+ * decrement button to change from `default` to `text`.
+ */
+
+input[type="number"]::-webkit-inner-spin-button,
+input[type="number"]::-webkit-outer-spin-button {
+ height: auto;
+}
+
+/**
+ * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
+ * 2. Address `box-sizing` set to `border-box` in Safari and Chrome
+ * (include `-moz` to future-proof).
+ */
+
+input[type="search"] {
+ -webkit-appearance: textfield; /* 1 */
+ -moz-box-sizing: content-box;
+ -webkit-box-sizing: content-box; /* 2 */
+ box-sizing: content-box;
+}
+
+/**
+ * Remove inner padding and search cancel button in Safari and Chrome on OS X.
+ * Safari (but not Chrome) clips the cancel button when the search input has
+ * padding (and `textfield` appearance).
+ */
+
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/**
+ * Define consistent border, margin, and padding.
+ */
+
+fieldset {
+ border: 1px solid #c0c0c0;
+ margin: 0 2px;
+ padding: 0.35em 0.625em 0.75em;
+}
+
+/**
+ * 1. Correct `color` not being inherited in IE 8/9/10/11.
+ * 2. Remove padding so people aren't caught out if they zero out fieldsets.
+ */
+
+legend {
+ border: 0; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * Remove default vertical scrollbar in IE 8/9/10/11.
+ */
+
+textarea {
+ overflow: auto;
+}
+
+/**
+ * Don't inherit the `font-weight` (applied by a rule above).
+ * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
+ */
+
+optgroup {
+ font-weight: bold;
+}
+
+/* Tables
+ ========================================================================== */
+
+/**
+ * Remove most spacing between table cells.
+ */
+
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+td,
+th {
+ padding: 0;
+}
\ No newline at end of file
diff --git a/pkgs/csslib/third_party/skeleton/skeleton.css b/pkgs/csslib/third_party/skeleton/skeleton.css
new file mode 100644
index 0000000..f28bf6c
--- /dev/null
+++ b/pkgs/csslib/third_party/skeleton/skeleton.css
@@ -0,0 +1,418 @@
+/*
+* Skeleton V2.0.4
+* Copyright 2014, Dave Gamache
+* www.getskeleton.com
+* Free to use under the MIT license.
+* http://www.opensource.org/licenses/mit-license.php
+* 12/29/2014
+*/
+
+
+/* Table of contents
+––––––––––––––––––––––––––––––––––––––––––––––––––
+- Grid
+- Base Styles
+- Typography
+- Links
+- Buttons
+- Forms
+- Lists
+- Code
+- Tables
+- Spacing
+- Utilities
+- Clearing
+- Media Queries
+*/
+
+
+/* Grid
+–––––––––––––––––––––––––––––––––––––––––––––––––– */
+.container {
+ position: relative;
+ width: 100%;
+ max-width: 960px;
+ margin: 0 auto;
+ padding: 0 20px;
+ box-sizing: border-box; }
+.column,
+.columns {
+ width: 100%;
+ float: left;
+ box-sizing: border-box; }
+
+/* For devices larger than 400px */
+@media (min-width: 400px) {
+ .container {
+ width: 85%;
+ padding: 0; }
+}
+
+/* For devices larger than 550px */
+@media (min-width: 550px) {
+ .container {
+ width: 80%; }
+ .column,
+ .columns {
+ margin-left: 4%; }
+ .column:first-child,
+ .columns:first-child {
+ margin-left: 0; }
+
+ .one.column,
+ .one.columns { width: 4.66666666667%; }
+ .two.columns { width: 13.3333333333%; }
+ .three.columns { width: 22%; }
+ .four.columns { width: 30.6666666667%; }
+ .five.columns { width: 39.3333333333%; }
+ .six.columns { width: 48%; }
+ .seven.columns { width: 56.6666666667%; }
+ .eight.columns { width: 65.3333333333%; }
+ .nine.columns { width: 74.0%; }
+ .ten.columns { width: 82.6666666667%; }
+ .eleven.columns { width: 91.3333333333%; }
+ .twelve.columns { width: 100%; margin-left: 0; }
+
+ .one-third.column { width: 30.6666666667%; }
+ .two-thirds.column { width: 65.3333333333%; }
+
+ .one-half.column { width: 48%; }
+
+ /* Offsets */
+ .offset-by-one.column,
+ .offset-by-one.columns { margin-left: 8.66666666667%; }
+ .offset-by-two.column,
+ .offset-by-two.columns { margin-left: 17.3333333333%; }
+ .offset-by-three.column,
+ .offset-by-three.columns { margin-left: 26%; }
+ .offset-by-four.column,
+ .offset-by-four.columns { margin-left: 34.6666666667%; }
+ .offset-by-five.column,
+ .offset-by-five.columns { margin-left: 43.3333333333%; }
+ .offset-by-six.column,
+ .offset-by-six.columns { margin-left: 52%; }
+ .offset-by-seven.column,
+ .offset-by-seven.columns { margin-left: 60.6666666667%; }
+ .offset-by-eight.column,
+ .offset-by-eight.columns { margin-left: 69.3333333333%; }
+ .offset-by-nine.column,
+ .offset-by-nine.columns { margin-left: 78.0%; }
+ .offset-by-ten.column,
+ .offset-by-ten.columns { margin-left: 86.6666666667%; }
+ .offset-by-eleven.column,
+ .offset-by-eleven.columns { margin-left: 95.3333333333%; }
+
+ .offset-by-one-third.column,
+ .offset-by-one-third.columns { margin-left: 34.6666666667%; }
+ .offset-by-two-thirds.column,
+ .offset-by-two-thirds.columns { margin-left: 69.3333333333%; }
+
+ .offset-by-one-half.column,
+ .offset-by-one-half.columns { margin-left: 52%; }
+
+}
+
+
+/* Base Styles
+–––––––––––––––––––––––––––––––––––––––––––––––––– */
+/* NOTE
+html is set to 62.5% so that all the REM measurements throughout Skeleton
+are based on 10px sizing. So basically 1.5rem = 15px :) */
+html {
+ font-size: 62.5%; }
+body {
+ font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */
+ line-height: 1.6;
+ font-weight: 400;
+ font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ color: #222; }
+
+
+/* Typography
+–––––––––––––––––––––––––––––––––––––––––––––––––– */
+h1, h2, h3, h4, h5, h6 {
+ margin-top: 0;
+ margin-bottom: 2rem;
+ font-weight: 300; }
+h1 { font-size: 4.0rem; line-height: 1.2; letter-spacing: -.1rem;}
+h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; }
+h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; }
+h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; }
+h5 { font-size: 1.8rem; line-height: 1.5; letter-spacing: -.05rem; }
+h6 { font-size: 1.5rem; line-height: 1.6; letter-spacing: 0; }
+
+/* Larger than phablet */
+@media (min-width: 550px) {
+ h1 { font-size: 5.0rem; }
+ h2 { font-size: 4.2rem; }
+ h3 { font-size: 3.6rem; }
+ h4 { font-size: 3.0rem; }
+ h5 { font-size: 2.4rem; }
+ h6 { font-size: 1.5rem; }
+}
+
+p {
+ margin-top: 0; }
+
+
+/* Links
+–––––––––––––––––––––––––––––––––––––––––––––––––– */
+a {
+ color: #1EAEDB; }
+a:hover {
+ color: #0FA0CE; }
+
+
+/* Buttons
+–––––––––––––––––––––––––––––––––––––––––––––––––– */
+.button,
+button,
+input[type="submit"],
+input[type="reset"],
+input[type="button"] {
+ display: inline-block;
+ height: 38px;
+ padding: 0 30px;
+ color: #555;
+ text-align: center;
+ font-size: 11px;
+ font-weight: 600;
+ line-height: 38px;
+ letter-spacing: .1rem;
+ text-transform: uppercase;
+ text-decoration: none;
+ white-space: nowrap;
+ background-color: transparent;
+ border-radius: 4px;
+ border: 1px solid #bbb;
+ cursor: pointer;
+ box-sizing: border-box; }
+.button:hover,
+button:hover,
+input[type="submit"]:hover,
+input[type="reset"]:hover,
+input[type="button"]:hover,
+.button:focus,
+button:focus,
+input[type="submit"]:focus,
+input[type="reset"]:focus,
+input[type="button"]:focus {
+ color: #333;
+ border-color: #888;
+ outline: 0; }
+.button.button-primary,
+button.button-primary,
+input[type="submit"].button-primary,
+input[type="reset"].button-primary,
+input[type="button"].button-primary {
+ color: #FFF;
+ background-color: #33C3F0;
+ border-color: #33C3F0; }
+.button.button-primary:hover,
+button.button-primary:hover,
+input[type="submit"].button-primary:hover,
+input[type="reset"].button-primary:hover,
+input[type="button"].button-primary:hover,
+.button.button-primary:focus,
+button.button-primary:focus,
+input[type="submit"].button-primary:focus,
+input[type="reset"].button-primary:focus,
+input[type="button"].button-primary:focus {
+ color: #FFF;
+ background-color: #1EAEDB;
+ border-color: #1EAEDB; }
+
+
+/* Forms
+–––––––––––––––––––––––––––––––––––––––––––––––––– */
+input[type="email"],
+input[type="number"],
+input[type="search"],
+input[type="text"],
+input[type="tel"],
+input[type="url"],
+input[type="password"],
+textarea,
+select {
+ height: 38px;
+ padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */
+ background-color: #fff;
+ border: 1px solid #D1D1D1;
+ border-radius: 4px;
+ box-shadow: none;
+ box-sizing: border-box; }
+/* Removes awkward default styles on some inputs for iOS */
+input[type="email"],
+input[type="number"],
+input[type="search"],
+input[type="text"],
+input[type="tel"],
+input[type="url"],
+input[type="password"],
+textarea {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none; }
+textarea {
+ min-height: 65px;
+ padding-top: 6px;
+ padding-bottom: 6px; }
+input[type="email"]:focus,
+input[type="number"]:focus,
+input[type="search"]:focus,
+input[type="text"]:focus,
+input[type="tel"]:focus,
+input[type="url"]:focus,
+input[type="password"]:focus,
+textarea:focus,
+select:focus {
+ border: 1px solid #33C3F0;
+ outline: 0; }
+label,
+legend {
+ display: block;
+ margin-bottom: .5rem;
+ font-weight: 600; }
+fieldset {
+ padding: 0;
+ border-width: 0; }
+input[type="checkbox"],
+input[type="radio"] {
+ display: inline; }
+label > .label-body {
+ display: inline-block;
+ margin-left: .5rem;
+ font-weight: normal; }
+
+
+/* Lists
+–––––––––––––––––––––––––––––––––––––––––––––––––– */
+ul {
+ list-style: circle inside; }
+ol {
+ list-style: decimal inside; }
+ol, ul {
+ padding-left: 0;
+ margin-top: 0; }
+ul ul,
+ul ol,
+ol ol,
+ol ul {
+ margin: 1.5rem 0 1.5rem 3rem;
+ font-size: 90%; }
+li {
+ margin-bottom: 1rem; }
+
+
+/* Code
+–––––––––––––––––––––––––––––––––––––––––––––––––– */
+code {
+ padding: .2rem .5rem;
+ margin: 0 .2rem;
+ font-size: 90%;
+ white-space: nowrap;
+ background: #F1F1F1;
+ border: 1px solid #E1E1E1;
+ border-radius: 4px; }
+pre > code {
+ display: block;
+ padding: 1rem 1.5rem;
+ white-space: pre; }
+
+
+/* Tables
+–––––––––––––––––––––––––––––––––––––––––––––––––– */
+th,
+td {
+ padding: 12px 15px;
+ text-align: left;
+ border-bottom: 1px solid #E1E1E1; }
+th:first-child,
+td:first-child {
+ padding-left: 0; }
+th:last-child,
+td:last-child {
+ padding-right: 0; }
+
+
+/* Spacing
+–––––––––––––––––––––––––––––––––––––––––––––––––– */
+button,
+.button {
+ margin-bottom: 1rem; }
+input,
+textarea,
+select,
+fieldset {
+ margin-bottom: 1.5rem; }
+pre,
+blockquote,
+dl,
+figure,
+table,
+p,
+ul,
+ol,
+form {
+ margin-bottom: 2.5rem; }
+
+
+/* Utilities
+–––––––––––––––––––––––––––––––––––––––––––––––––– */
+.u-full-width {
+ width: 100%;
+ box-sizing: border-box; }
+.u-max-full-width {
+ max-width: 100%;
+ box-sizing: border-box; }
+.u-pull-right {
+ float: right; }
+.u-pull-left {
+ float: left; }
+
+
+/* Misc
+–––––––––––––––––––––––––––––––––––––––––––––––––– */
+hr {
+ margin-top: 3rem;
+ margin-bottom: 3.5rem;
+ border-width: 0;
+ border-top: 1px solid #E1E1E1; }
+
+
+/* Clearing
+–––––––––––––––––––––––––––––––––––––––––––––––––– */
+
+/* Self Clearing Goodness */
+.container:after,
+.row:after,
+.u-cf {
+ content: "";
+ display: table;
+ clear: both; }
+
+
+/* Media Queries
+–––––––––––––––––––––––––––––––––––––––––––––––––– */
+/*
+Note: The best way to structure the use of media queries is to create the queries
+near the relevant code. For example, if you wanted to change the styles for buttons
+on small devices, paste the mobile query code up in the buttons section and style it
+there.
+*/
+
+
+/* Larger than mobile */
+@media (min-width: 400px) {}
+
+/* Larger than phablet (also point when grid becomes active) */
+@media (min-width: 550px) {}
+
+/* Larger than tablet */
+@media (min-width: 750px) {}
+
+/* Larger than desktop */
+@media (min-width: 1000px) {}
+
+/* Larger than Desktop HD */
+@media (min-width: 1200px) {}
diff --git a/pkgs/extension_discovery/.gitignore b/pkgs/extension_discovery/.gitignore
new file mode 100644
index 0000000..ddfdca1
--- /dev/null
+++ b/pkgs/extension_discovery/.gitignore
@@ -0,0 +1,5 @@
+.dart_tool/
+.packages
+.pub/
+build/
+pubspec.lock
diff --git a/pkgs/extension_discovery/CHANGELOG.md b/pkgs/extension_discovery/CHANGELOG.md
new file mode 100644
index 0000000..f35371e
--- /dev/null
+++ b/pkgs/extension_discovery/CHANGELOG.md
@@ -0,0 +1,24 @@
+## 2.1.0
+
+- Require Dart 3.4.
+- Update to the latest version of `package:dart_flutter_team_lints`.
+- Add `findPackageConfig()` allowing for automatic discovery of the
+ packageConfig based on a package directory.
+
+## 2.0.0
+
+- Use `extension/<package>/config.yaml` instead of
+ `extension/<package>/config.json` for better consistency with other tooling.
+- Require that the top-level value in `extension/<package>/config.yaml` is
+ always a `Map`, and that the structure can be converted to a JSON equivalent
+ structure.
+
+## 1.0.1
+
+- Support optional `packageUri`.
+- Improve error messaging for `package_config.json` parsing.
+- Update the package description.
+
+## 1.0.0
+
+- Initial version.
diff --git a/pkgs/extension_discovery/LICENSE b/pkgs/extension_discovery/LICENSE
new file mode 100644
index 0000000..ac90031
--- /dev/null
+++ b/pkgs/extension_discovery/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2023, 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/extension_discovery/README.md b/pkgs/extension_discovery/README.md
new file mode 100644
index 0000000..0214ae9
--- /dev/null
+++ b/pkgs/extension_discovery/README.md
@@ -0,0 +1,237 @@
+[](https://github.com/dart-lang/tools/actions/workflows/extension_discovery.yml)
+[](https://pub.dev/packages/extension_discovery)
+[](https://pub.dev/packages/extension_discovery/publisher)
+
+A convention and utilities for package extension discovery.
+
+## What's this?
+
+A convention to allow other packages to provide extensions for your package
+(or tool). Including logic for finding extensions that honor this convention.
+
+The convention implemented in this package is that if `foo` provides an
+extension for `<targetPackage>`.
+Then `foo` must contain a config file `extension/<targetPackage>/config.yaml`.
+This file indicates that `foo` provides an extension for `<targetPackage>`.
+
+If `<targetPackage>` accepts extensions from other packages it must:
+ * Find extensions using `findExtensions('<targetPackage>')` from this package.
+ * Document how extensions are implemented:
+ * What should the contents of the `extension/<targetPackage>/config.yaml` file be?
+ * Should packages providing extensions have a dependency constraint on `<targetPackage>`?
+ * What libraries/assets should packages that provide extensions include?
+ * Should packages providing extensions specify a [topic in `pubspec.yaml`][1]
+ for easy discovery on pub.dev.
+
+The `findExtensions(targetPackage, packageConfig: ...)` function will:
+ * Load `.dart_tool/package_config.json` and find all packages that contain a
+ valid YAML file: `extension/<targetPackage>/config.yaml`.
+ * Provide the package name, location and contents of the `config.yaml` file,
+ for all detected extensions (aimed at `targetPackage`).
+ * Cache the results for fast loading, comparing modification timestamps to
+ ensure consistent results.
+
+It is the responsibility package that can be extended to validate extensions,
+decide when they should be enabled, and documenting how such extensions are
+created.
+
+
+## Packages that extend tools
+
+You can also use this package (and associated convention), if you are developing
+a tool that can be extended by packages. In this case, you would call
+`findExtensions(<my_tool_package_name>, packageConfig: ...)` where
+`packageConfig` points to the `.dart_tool/package_config.json` in the workspace
+the tool is operating on.
+
+If you tool is not distributed through pub.dev, you might consider publishing
+a placeholder package in order to reserve a unique name (and avoid collisions).
+Using a placeholder package to reserve a unique is also recommended for tools
+that wish to cache files in `.dart_tool/<my_tool_package_name>/`.
+See [package layout documentation][2] for details.
+
+
+## Example: Hello World
+
+Imagine that we have a `hello_world` package that defines a single method
+`sayHello(String language)`, along the lines of:
+
+```dart
+void sayHello(String language) {
+ if (language == 'danish') {
+ print('Hej verden');
+ } else {
+ print('Hello world!');
+ }
+}
+```
+
+### Enabling packages to extend `hello_world`
+
+If we wanted to allow other packages to provide additional languages by
+extending the `hello_world` package. Then we could do:
+
+```dart
+import 'package:extension_discovery/extension_discovery.dart';
+
+Future<void> sayHello(String language) async {
+ // Find extensions for the "hello_world" package.
+ // WARNING: This only works when running in JIT-mode, if running in AOT-mode
+ // you must supply the `packageConfig` argument, and have a local
+ // `.dart_tool/package_config.json` and `$PUB_CACHE`.
+ // See "Runtime limitations" section further down.
+ final extensions = await findExtensions('hello_world');
+
+ // Search extensions to see if one provides a message for language
+ for (final ext in extensions) {
+ final config = ext.config;
+ if (config is! Map<String, Object?>) {
+ continue; // ignore extensions with invalid configation
+ }
+ if (config['language'] == language) {
+ print(config['message']);
+ return; // Don't print more messages!
+ }
+ }
+
+ if (language == 'danish') {
+ print('Hej verden');
+ } else {
+ print('Hello world!');
+ }
+}
+```
+
+The `findExtensions` function will search other packages for
+`extension/hello_world/config.yaml`, and provide the contents of this file as
+well as provide the location of the extending packages.
+As authors of the `hello_world` package we should also document how other
+packages can extend `hello_world`. This is typically done by adding a segment
+to the `README.md`.
+
+
+### Extending `hello_world` from another package
+
+If in another package `hello_world_german` we wanted to extend `hello_world`
+and provide a translation for German, then we would create a
+`hello_world_german` package containing an
+**`extension/hello_world/config.yaml`**:
+
+```yaml
+language: german
+message: "Hello Welt!"
+```
+
+Obviously, this is a contrived example. The authors of the `hello_world` package
+could specify all sorts configuration options that extension authors can
+specify in `extension/hello_world/config.yaml`.
+
+The authors of `hello_world` could also specify that extensions must provide
+certain assets in `extension/hello_world/` or that they must provide certain
+Dart libraries implementing a specified interface somewhere in `lib/src/...`.
+
+It is up to the authors of `hello_world` to specify what extension authors must
+provide. The `extension_discovery` package only provides a utility for finding
+extensions.
+
+
+### Using `hello_world` and `hello_world_german`
+
+If writing `my_hello_world_app` I can now take advantage of `hello_world` and
+`hello_world_german`. Simply write a `pubspec.yaml` as follows:
+
+```yaml
+# pubspec.yaml
+name: my_hello_world_app
+dependencies:
+ hello_world: ^1.0.0
+ hello_world_german: ^1.0.0
+environment:
+ sdk: ^3.4.0
+```
+
+Then I can write a `bin/hello.dart` as follows:
+
+```dart
+// bin/hello.dart
+import 'package:hello_world/hello_world.dart';
+
+Future<void> main() async {
+ await sayHello('german');
+}
+```
+
+
+## What can an extension provide?
+
+As far as the `extension_discovery` package is concerned an extension can
+provide anything. Naturally, it is the authors of the extendable package that
+decides what extensions can be provide.
+
+In the example above it is the authors of the `hello_world` package that decides
+what extension packages can provide. For this reason it is important that the
+authors of `hello_world` very explicitly document how an extension is written.
+
+Obviously, authors of `hello_world` should document what should be specified in
+`extension/hello_world/config.yaml`. They could also specify that other files
+should be provided in `extension/hello_world/`, or that certain Dart libraries
+should be provided in `lib/src/hello_world/...` or something like that.
+
+When authors of `hello_world` consumes the extensions discovered through
+`findExtensions` they would naturally also be wise to validate that the
+extension provides the required configuration and files.
+
+
+## Compatibility considerations
+
+When writing an extension it is strongly encouraged to have a dependency
+constraint on the package being extended. This ensures that the extending
+package will be incompatibility with new major versions of the extended package.
+
+In the example above, it is strongly encouraged for `hello_world_german` to
+have a dependency constraint `hello_world: ^1.0.0`. Even if `hello_world_german`
+doesn't import libraries from `package:hello_world`.
+
+Because the next major version of `hello_world` (version `2.0.0`) might change
+what is required of an extension. Thus, it's fair to assume that
+`hello_world_german` might not be compatible with newer versions of
+`hello_world`. Hence, adding a dependency constraint `hello_world: ^1.0.0` saves
+users from resolving dependencies that aren't compatible.
+
+Naturally, after a new major version of `hello_world` is published a new
+version of `hello_world_german` can then also be published, addressing any
+breaking changes and bumping the dependency constraint.
+
+**Tip:** Authors of packages that can be extended might want to force extension
+authors take dependency on their package, to ensure that they have the ability
+to do breaking changes in the future.
+
+
+## Runtime limitations
+
+The `findExtensions` function only works when running in JIT-mode, otherwise the
+`packageConfig` parameter must be used to provide the location of
+`.dart_tool/package_config.json`. Obviously, the `package_config.json` must be
+present, as must the pub-cache locations referenced here.
+
+Hence, `findExtensions` effectively **only works from project workspace!**.
+
+You can't use `findExtensions` in a compiled Flutter application or an
+AOT-compiled executable distributed to end-users. Because in these environments
+you don't have a `package_config.json` nor do you have a pub-cache. You don't
+even have access to your own source files.
+
+If your deployment target a compiled Flutter application or AOT-compiled
+executable, then you will have to create some code/asset-generation.
+You code/asset-generation scripts can use `findExtensions` to find extensions,
+and then use the assets or Dart libraries from here to generate assets or code
+that is embedded in the final Flutter application (or AOT-compiled executable).
+
+This makes `findExtensions` immediately useful, if you are writing development
+tools that users will install into their project workspace. But if you're
+writing a package for use in deployed applications, you'll likely need to figure
+out how to embed the extensions, `findExtensions` only helps
+you find the extensions during code-gen.
+
+[1]: https://dart.dev/tools/pub/pubspec#topics
+[2]: https://dart.dev/tools/pub/package-layout#project-specific-caching-for-tools
diff --git a/pkgs/extension_discovery/analysis_options.yaml b/pkgs/extension_discovery/analysis_options.yaml
new file mode 100644
index 0000000..aadd26e
--- /dev/null
+++ b/pkgs/extension_discovery/analysis_options.yaml
@@ -0,0 +1,5 @@
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+linter:
+ rules:
+ - avoid_catches_without_on_clauses
diff --git a/pkgs/extension_discovery/example/hello_world/README.md b/pkgs/extension_discovery/example/hello_world/README.md
new file mode 100644
index 0000000..39ec96d
--- /dev/null
+++ b/pkgs/extension_discovery/example/hello_world/README.md
@@ -0,0 +1,26 @@
+# `hello_world` package
+
+Example of a package that can be extended, and uses `extension_discovery` to
+find extensions.
+
+## Limitations
+
+The `sayHello` function in this package can only be used in a project workspace,
+where dependencies are resolved and you're running in JIT-mode.
+
+The `sayHello` function uses `findExtensions` and, thus, cannot be called in a
+compiled Flutter application or AOT-compiled executable. For this to work, we'd
+need to augment this package with code-generation, such that the code-generation
+uses `findExtensions` and compiles a `sayHello` function into your application.
+
+## Extending the `hello_world` package
+
+Other packages can extend this package by providing an `extension/hello_world/config.json` file the following form:
+
+```js
+// extension/hello_world/config.json
+{
+ "language": "<language>",
+ "message": "<message>"
+}
+```
diff --git a/pkgs/extension_discovery/example/hello_world/lib/hello_world.dart b/pkgs/extension_discovery/example/hello_world/lib/hello_world.dart
new file mode 100644
index 0000000..9e4a848
--- /dev/null
+++ b/pkgs/extension_discovery/example/hello_world/lib/hello_world.dart
@@ -0,0 +1,28 @@
+// Copyright (c) 2023, 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:extension_discovery/extension_discovery.dart';
+
+Future<void> sayHello(String language) async {
+ // Find extensions for the "hello_world" package.
+ // WARNING: This only works when running in JIT-mode, if running in AOT-mode
+ // you must supply the `packageConfig` argument, and have a local
+ // `.dart_tool/package_config.json` and `$PUB_CACHE`.
+ final extensions = await findExtensions('hello_world');
+
+ // Search extensions to see if one provides a message for language
+ for (final ext in extensions) {
+ final config = ext.config;
+ if (config['language'] == language) {
+ print(config['message']);
+ return; // Don't print more messages!
+ }
+ }
+
+ if (language == 'danish') {
+ print('Hej verden');
+ } else {
+ print('Hello world!');
+ }
+}
diff --git a/pkgs/extension_discovery/example/hello_world/pubspec.yaml b/pkgs/extension_discovery/example/hello_world/pubspec.yaml
new file mode 100644
index 0000000..40c68a3
--- /dev/null
+++ b/pkgs/extension_discovery/example/hello_world/pubspec.yaml
@@ -0,0 +1,10 @@
+name: hello_world
+version: 1.0.0
+publish_to: none # This is an example, don't publish!
+
+dependencies:
+ extension_discovery: # ^1.0.0
+ path: ../../
+
+environment:
+ sdk: ^3.4.0
diff --git a/pkgs/extension_discovery/example/hello_world_app/README.md b/pkgs/extension_discovery/example/hello_world_app/README.md
new file mode 100644
index 0000000..1d45229
--- /dev/null
+++ b/pkgs/extension_discovery/example/hello_world_app/README.md
@@ -0,0 +1,8 @@
+# `hello_world_app`
+
+An application that uses the `hello_world` package and has a dependency on
+`hello_world_german` such that when `hello_world` calls `findExtensions` it will
+find the extension in `hello_world_german`.
+
+**Notice**: This application only works when running from a project workspace.
+See "runtime limitation" in the README for `package:extension_discovery`.
diff --git a/pkgs/extension_discovery/example/hello_world_app/bin/hello.dart b/pkgs/extension_discovery/example/hello_world_app/bin/hello.dart
new file mode 100644
index 0000000..a4715ef
--- /dev/null
+++ b/pkgs/extension_discovery/example/hello_world_app/bin/hello.dart
@@ -0,0 +1,9 @@
+// Copyright (c) 2023, 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:hello_world/hello_world.dart';
+
+Future<void> main() async {
+ await sayHello('german');
+}
diff --git a/pkgs/extension_discovery/example/hello_world_app/pubspec.yaml b/pkgs/extension_discovery/example/hello_world_app/pubspec.yaml
new file mode 100644
index 0000000..30338d2
--- /dev/null
+++ b/pkgs/extension_discovery/example/hello_world_app/pubspec.yaml
@@ -0,0 +1,11 @@
+name: my_hello_world_app
+publish_to: none # applications should never be published!
+
+dependencies:
+ hello_world: # ^1.0.0
+ path: ../hello_world
+ hello_world_german: # ^1.0.0
+ path: ../hello_world_german
+
+environment:
+ sdk: ^3.4.0
diff --git a/pkgs/extension_discovery/example/hello_world_german/README.md b/pkgs/extension_discovery/example/hello_world_german/README.md
new file mode 100644
index 0000000..4a58703
--- /dev/null
+++ b/pkgs/extension_discovery/example/hello_world_german/README.md
@@ -0,0 +1,4 @@
+# `hello_world_german`
+
+Example of a simple package that provides an extension for the `hello_world`
+package.
diff --git a/pkgs/extension_discovery/example/hello_world_german/extension/hello_world/config.yaml b/pkgs/extension_discovery/example/hello_world_german/extension/hello_world/config.yaml
new file mode 100644
index 0000000..ef57087
--- /dev/null
+++ b/pkgs/extension_discovery/example/hello_world_german/extension/hello_world/config.yaml
@@ -0,0 +1,9 @@
+# This config file let's `package:extension_discovery` find out that
+# `package:hello_world_german` has an extension for `package:hello_world`.
+#
+# The root object must be a map, and the data structure must be representable
+# as JSON, but those are the only constraints. Beyond that it is the
+# responsibility of `package:hello_world` to define what this file should
+# contain, to validate the contents, and do something useful with it.
+language: german
+message: "Hello Welt!"
diff --git a/pkgs/extension_discovery/example/hello_world_german/pubspec.yaml b/pkgs/extension_discovery/example/hello_world_german/pubspec.yaml
new file mode 100644
index 0000000..39e80be
--- /dev/null
+++ b/pkgs/extension_discovery/example/hello_world_german/pubspec.yaml
@@ -0,0 +1,15 @@
+name: hello_world_german
+version: 1.0.0
+publish_to: none # This is an example, don't publish!
+
+dependencies:
+ # We keep a dependency on package `hello_world` because this package provides
+ # an extension for this package. Hence, a newer major version of hello_world
+ # might be incompatible with the extension options we've provided in
+ # extension/hello_world/config.json
+ # It is `hello_world` that specifies what is required in `config.json`.
+ hello_world: # ^1.0.0
+ path: ../hello_world
+
+environment:
+ sdk: ^3.4.0
diff --git a/pkgs/extension_discovery/lib/extension_discovery.dart b/pkgs/extension_discovery/lib/extension_discovery.dart
new file mode 100644
index 0000000..662435a
--- /dev/null
+++ b/pkgs/extension_discovery/lib/extension_discovery.dart
@@ -0,0 +1,285 @@
+// Copyright (c) 2023, 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.
+
+/// Find extensions with [findExtensions].
+library;
+
+import 'dart:collection' show UnmodifiableListView;
+import 'dart:io' show File, IOException;
+import 'dart:isolate' show Isolate;
+
+import 'src/io.dart';
+import 'src/package_config.dart';
+import 'src/registry.dart';
+import 'src/yaml_config_format.dart';
+
+export 'src/package_config.dart' show PackageConfigException, findPackageConfig;
+
+/// Information about an extension for target package.
+final class Extension {
+ /// Name of the package providing an extension.
+ final String package;
+
+ /// Absolute path to the package root.
+ ///
+ /// This folder usually contains a `lib/` folder and a `pubspec.yaml`
+ /// (assuming dependencies are fetched using the pub package manager).
+ ///
+ /// **Examples:** If `foo` is installed in pub-cache this would be:
+ /// * `/home/my_user/.pub-cache/hosted/pub.dev/foo-1.0.0/`
+ ///
+ /// See `rootUri` in the [specification for `package_config.json`][1],
+ /// for details.
+ ///
+ /// [1]: https://github.com/dart-lang/language/blob/main/accepted/2.8/language-versioning/package-config-file-v2.md
+ final Uri rootUri;
+
+ /// Path to the library import path relative to [rootUri].
+ ///
+ /// In Dart code the `package:<package>/<path>` will be resolved as
+ /// `<rootUri>/<packageUri>/<path>`.
+ ///
+ /// If dependencies are installed using `dart pub`, then this is
+ /// **always** `lib/`.
+ ///
+ /// See `packageUri` in the [specification for `package_config.json`][1],
+ /// for details.
+ ///
+ /// [1]: https://github.com/dart-lang/language/blob/main/accepted/2.8/language-versioning/package-config-file-v2.md
+ final Uri packageUri;
+
+ /// Contents of `extension/<targetPackage>/config.yaml` parsed as YAML and
+ /// converted to JSON compatible types.
+ ///
+ /// If parsing YAML from this file failed, then no [Extension] entry
+ /// will exist.
+ ///
+ /// This field is always a structure consisting of the following types:
+ /// * `null`,
+ /// * [bool] (`true` or `false`),
+ /// * [String],
+ /// * [num] ([int] or [double]),
+ /// * `List<Object?>`, and,
+ /// * `Map<String, Object?>`.
+ final Map<String, Object?> config;
+
+ Extension._({
+ required this.package,
+ required this.rootUri,
+ required this.packageUri,
+ required this.config,
+ });
+}
+
+/// Find extensions for [targetPackage] provided by packages in
+/// `.dart_tool/package_config.json`.
+///
+/// ## Locating `.dart_tool/package_config.json`
+///
+/// This method requires the location of the [packageConfig], unless the current
+/// isolate has been setup for package resolution.
+/// Notably, Dart programs compiled for AOT cannot find their own
+/// `package_config.json`.
+///
+/// If operating on a project that isn't the current project, for example, if
+/// you are developing a tool that users are globally activating and then
+/// running against their own projects, and you wish to detect extensions within
+/// their projects, then you must specify the path the
+/// `.dart_tool/package_config.json` for the users project as [packageConfig].
+///
+/// The [packageConfig] parameter must reference a file, absolute or
+/// relative-path, may use the `file://` scheme. This method throws, if
+/// [packageConfig] is not a valid [Uri] for a file-path.
+///
+/// ## Detection of extensions
+///
+/// An extension for [targetPackage] is detected in `package:foo` if `foo`
+/// contains `extension/<targetPackage>/config.yaml`, and the contents of this
+/// file is valid YAML, that can be represented as JSON.
+///
+/// ### Caching results
+///
+/// When [useCache] is `true` then the detected extensions will be cached
+/// in `.dart_tool/extension_discovery/<targetPackage>.json`.
+/// This function will compare modification timestamps of
+/// `.dart_tool/package_config.json` with the cache file, before reusing cached
+/// results.
+/// This function will also treat relative path-dependencies as mutable
+/// packages, and check such packages for extensions every time [findExtensions]
+/// is called. Notably, it'll compare the modification time of the
+/// `extension/<targetPackage>/config.yaml` file, to ensure that it's older than
+/// the extension cache file. Otherwise, it'll reload the extension
+/// configuration.
+///
+/// ## Exceptions
+///
+/// This method will throw [PackageConfigException], if the
+/// `.dart_tool/package_config.json` file specified in [packageConfig] could not
+/// be loaded or is invalid. This usually happens if dependencies are not
+/// resolved, and users can probably address it by running `dart pub get`.
+///
+/// But, **do consider catch** [PackageConfigException] and handling the failure
+/// to load extensions appropriately.
+///
+/// This method will throw an [Error] if [packageConfig] is not specified, and
+/// the current isolate isn't configured for package resolution.
+Future<List<Extension>> findExtensions(
+ String targetPackage, {
+ bool useCache = true,
+ Uri? packageConfig,
+}) async {
+ packageConfig ??= await Isolate.packageConfig;
+ if (packageConfig == null) {
+ throw UnsupportedError(
+ 'packageConfigUri must be provided, if not running in JIT mode',
+ );
+ }
+ if ((packageConfig.hasScheme && !packageConfig.isScheme('file')) ||
+ packageConfig.hasEmptyPath ||
+ packageConfig.hasFragment ||
+ packageConfig.hasPort ||
+ packageConfig.hasQuery) {
+ throw ArgumentError.value(
+ packageConfig,
+ 'packageConfig',
+ 'must be a file:// URI',
+ );
+ }
+ // Always normalize to an absolute URI
+ final packageConfigUri = File.fromUri(packageConfig).absolute.uri;
+
+ return await _findExtensions(
+ targetPackage: targetPackage,
+ useCache: useCache,
+ packageConfigUri: packageConfigUri,
+ );
+}
+
+/// Find extensions with normalized arguments.
+Future<List<Extension>> _findExtensions({
+ required String targetPackage,
+ required bool useCache,
+ required Uri packageConfigUri,
+}) async {
+ final packageConfigFile = File.fromUri(packageConfigUri);
+ final registryFile = File.fromUri(packageConfigFile.parent.uri.resolve(
+ 'extension_discovery/$targetPackage.json',
+ ));
+
+ Registry? registry;
+ final registryStat = registryFile.statSync();
+ if (registryStat.isFileOrLink && useCache) {
+ final packageConfigStat = packageConfigFile.statSync();
+ if (!packageConfigStat.isFileOrLink) {
+ throw packageConfigNotFound(packageConfigUri);
+ }
+ if (packageConfigStat.isPossiblyModifiedAfter(registryStat.modified)) {
+ await registryFile.tryDelete();
+ } else {
+ registry = await loadRegistry(registryFile);
+ }
+ }
+
+ final configFileName = 'extension/$targetPackage/config.yaml';
+ var registryUpdated = false;
+ if (registry != null) {
+ // Update mutable entries in registry
+ for (var i = 0; i < registry.length; i++) {
+ final p = registry[i];
+ if (p.rootUri.hasAbsolutePath) continue;
+
+ final rootUri = packageConfigUri.resolveUri(p.rootUri);
+ final configFile = File.fromUri(rootUri.resolve(configFileName));
+ final configStat = configFile.statSync();
+ if (configStat.isFileOrLink) {
+ if (configStat.isPossiblyModifiedAfter(registryStat.modified)) {
+ try {
+ registryUpdated = true;
+ registry[i] = (
+ package: p.package,
+ rootUri: p.rootUri,
+ packageUri: p.packageUri,
+ config: parseYamlFromConfigFile(await configFile.readAsString()),
+ );
+ continue;
+ } on FormatException {
+ // pass
+ } on IOException {
+ // pass
+ }
+ registryUpdated = true;
+ registry[i] = (
+ package: p.package,
+ rootUri: p.rootUri,
+ packageUri: p.packageUri,
+ config: null,
+ );
+ }
+ } else {
+ // If there is no file present, but registry says there is then we need
+ // to update the registry.
+ if (p.config != null) {
+ registryUpdated = true;
+ registry[i] = (
+ package: p.package,
+ rootUri: p.rootUri,
+ packageUri: p.packageUri,
+ config: null,
+ );
+ }
+ }
+ }
+ } else {
+ // Load packages from package_config.json
+ final packages = await loadPackageConfig(packageConfigFile);
+ registryUpdated = true;
+ registry = (await Future.wait(packages.map((p) async {
+ try {
+ final rootUri = packageConfigUri.resolveUri(p.rootUri);
+ final configFile = File.fromUri(rootUri.resolve(configFileName));
+ final configStat = configFile.statSync();
+ if (configStat.isFileOrLink) {
+ return (
+ package: p.name,
+ rootUri: p.rootUri,
+ packageUri: p.packageUri,
+ config: parseYamlFromConfigFile(await configFile.readAsString()),
+ );
+ }
+ } on FormatException {
+ // pass
+ } on IOException {
+ // pass
+ }
+ if (!p.rootUri.hasAbsolutePath) {
+ return (
+ package: p.name,
+ rootUri: p.rootUri,
+ packageUri: p.packageUri,
+ config: null,
+ );
+ }
+ return null;
+ })))
+ .whereType<RegistryEntry>()
+ .toList(growable: false);
+ }
+
+ // Save registry
+ if (registryUpdated && useCache) {
+ await saveRegistry(registryFile, registry);
+ }
+
+ return UnmodifiableListView(
+ registry
+ .where((e) => e.config != null)
+ .map((e) => Extension._(
+ package: e.package,
+ rootUri: packageConfigUri.resolveUri(e.rootUri),
+ packageUri: e.packageUri,
+ config: e.config!,
+ ))
+ .toList(growable: false),
+ );
+}
diff --git a/pkgs/extension_discovery/lib/src/expect_json.dart b/pkgs/extension_discovery/lib/src/expect_json.dart
new file mode 100644
index 0000000..c2784bc
--- /dev/null
+++ b/pkgs/extension_discovery/lib/src/expect_json.dart
@@ -0,0 +1,73 @@
+// Copyright (c) 2023, 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' show jsonDecode;
+
+Map<String, Object?> decodeJsonMap(String json) {
+ final root = jsonDecode(json);
+ if (root case Map<String, Object?> v) return v;
+ throw const FormatException('root must be a map');
+}
+
+extension ExpectJson on Map<String, Object?> {
+ bool expectBool(String key) {
+ if (this[key] case bool v) return v;
+ throw FormatException('The value at "$key" must be a bool:\n$toString()');
+ }
+
+ num expectNumber(String key) {
+ if (this[key] case num v) return v;
+ throw FormatException('The value at "$key" must be a number:\n$toString()');
+ }
+
+ String expectString(String key) {
+ if (this[key] case String v) return v;
+ throw FormatException('The value at "$key" must be a string:\n$toString()');
+ }
+
+ Uri expectUri(String key) {
+ try {
+ return Uri.parse(expectString(key));
+ } on FormatException {
+ throw FormatException('The value at "$key" must be a URI:\n$toString()');
+ }
+ }
+
+ Map<String, Object?> expectMap(String key) {
+ if (this[key] case Map<String, Object?> v) return v;
+ throw FormatException('The value at "$key" must be a map:\n$toString()');
+ }
+
+ Map<String, Object?>? optionalMap(String key) {
+ if (containsKey(key)) {
+ return expectMap(key);
+ }
+ return null;
+ }
+
+ Uri? optionalUri(String key) {
+ if (containsKey(key)) {
+ return expectUri(key);
+ }
+ return null;
+ }
+
+ List<Object?> expectList(String key) {
+ if (this[key] case List<Object?> v) return v;
+ throw FormatException('The value at "$key" must be a list:\n$toString()');
+ }
+
+ Iterable<Map<String, Object?>> expectListObjects(String key) sync* {
+ final list = expectList(key);
+ for (final entry in list) {
+ if (entry case Map<String, Object?> v) {
+ yield v;
+ } else {
+ throw FormatException(
+ 'The value at "$key" must be a list of map:\n$toString()',
+ );
+ }
+ }
+ }
+}
diff --git a/pkgs/extension_discovery/lib/src/io.dart b/pkgs/extension_discovery/lib/src/io.dart
new file mode 100644
index 0000000..9343ced
--- /dev/null
+++ b/pkgs/extension_discovery/lib/src/io.dart
@@ -0,0 +1,84 @@
+// Copyright (c) 2023, 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'
+ show
+ File,
+ FileStat,
+ FileSystemEntityType,
+ FileSystemException,
+ IOException,
+ Platform;
+
+const _maxAttempts = 20;
+
+/// When comparing modification timestamps of files to see if A is newer than B,
+/// such that data cached in A about B does not need to be recomputed, we
+/// required that A is at-least [modificationMaturityDelay] older than B.
+///
+/// This ensures that if `pub get` is racing with cache writing then we won't
+/// rely on the result.
+Duration modificationMaturityDelay = const Duration(seconds: 5);
+
+/// On windows some renaming files/folders recently created can be problematic
+/// as they may be locked by security scanning systems.
+///
+/// This function will retry [fn] a few times with a short delay, if running on
+/// Windows and the error appears to be related to permissions or file locking.
+Future<T> _attempt<T>(FutureOr<T> Function() fn) async {
+ if (!Platform.isWindows) {
+ return await fn();
+ }
+ var attempts = 0;
+ while (true) {
+ try {
+ await fn();
+ } on FileSystemException catch (e) {
+ attempts += 1;
+ if (attempts >= _maxAttempts) rethrow;
+
+ // Cargo culting error codes from:
+ // https://github.com/dart-lang/pub/blob/98565c3e5defcd80c59910795ba3548552102f2c/lib/src/io.dart#L419-L427
+ final code = e.osError?.errorCode;
+ if (code != 5 && code != 32) rethrow;
+
+ // Sleep a bit a try again.
+ await Future<void>.delayed(const Duration(milliseconds: 5));
+ }
+ }
+}
+
+extension FileExt on File {
+ /// Try to delete this file, ignore errors.
+ Future<void> tryDelete() async {
+ try {
+ await delete();
+ } on IOException {
+ // pass
+ }
+ }
+
+ Future<void> reliablyRename(String newPath) =>
+ _attempt(() => rename(newPath));
+}
+
+extension FileStatExt on FileStat {
+ /// Returns `false` if we're pretty certain that the current file was NOT
+ /// modified after [other].
+ ///
+ /// We do this by subtracting 5 seconds from [other], thus, making it more
+ /// likely that the current file was _possibly modified_ after [other].
+ ///
+ /// This, aims to ensure that file where modified within 5 seconds of
+ /// eachother then we don't trust the file modification stamps. This mostly
+ /// makes sense if someone is updating a file while we're writing the registry
+ /// cache file.
+ bool isPossiblyModifiedAfter(DateTime other) =>
+ modified.isAfter(other.subtract(modificationMaturityDelay));
+
+ /// True, if current [FileStat] is possibly a file (link or file)
+ bool get isFileOrLink =>
+ type == FileSystemEntityType.file || type == FileSystemEntityType.link;
+}
diff --git a/pkgs/extension_discovery/lib/src/package_config.dart b/pkgs/extension_discovery/lib/src/package_config.dart
new file mode 100644
index 0000000..f2eaffc
--- /dev/null
+++ b/pkgs/extension_discovery/lib/src/package_config.dart
@@ -0,0 +1,107 @@
+// Copyright (c) 2023, 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 File, IOException;
+import 'expect_json.dart';
+
+typedef PackageConfigEntry = ({String name, Uri rootUri, Uri packageUri});
+typedef PackageConfig = List<PackageConfigEntry>;
+
+/// Searches in [packageDir] and all directories above for a
+/// `.dart_tool/package_config.json` file. Returns the Uri of that file if
+/// found.
+///
+/// Returns `null` if no file was found.
+Uri? findPackageConfig(Uri packageDir) {
+ if (!packageDir.isScheme('file')) {
+ throw ArgumentError(
+ 'Expected [packageDir] to be a file URI, got $packageDir',
+ );
+ }
+ if (!packageDir.path.endsWith('/')) {
+ packageDir = packageDir.replace(path: '${packageDir.path}/');
+ }
+ while (true) {
+ final packageConfigCandidate =
+ packageDir.resolve('.dart_tool/package_config.json');
+ try {
+ if (File.fromUri(packageConfigCandidate).existsSync()) {
+ return packageConfigCandidate;
+ }
+ } on IOException {
+ return null; // if we get a permission error, etc, we return null
+ }
+ final next = packageDir.resolve('..');
+ if (next == packageDir) return null;
+ packageDir = next;
+ }
+}
+
+/// Load list of packages and associated URIs from the
+/// `.dart_tool/package_config.json` file in [packageConfigFile].
+Future<PackageConfig> loadPackageConfig(
+ File packageConfigFile,
+) async {
+ try {
+ final packageConfig = decodeJsonMap(await packageConfigFile.readAsString());
+ if (packageConfig.expectNumber('configVersion') != 2) {
+ throw const FormatException('"configVersion" must be 2');
+ }
+ return packageConfig.expectListObjects('packages').map((p) {
+ final rootUri = p.expectUri('rootUri').asDirectory();
+ return (
+ name: p.expectString('name'),
+ rootUri: rootUri,
+ packageUri: p.optionalUri('packageUri')?.asDirectory() ?? rootUri,
+ );
+ }).toList();
+ } on IOException catch (e) {
+ if (!packageConfigFile.existsSync()) {
+ throw packageConfigNotFound(packageConfigFile.uri);
+ }
+ throw packageConfigIOException(e);
+ } on FormatException catch (e) {
+ throw packageConfigInvalid(packageConfigFile.uri, e);
+ }
+}
+
+/// Thrown, if the `.dart_tool/package_config.json` cannot be found or parsing
+/// it fails.
+///
+/// This could be because the `package_config.json` file is missing or simply
+/// invalid.
+///
+/// Mostly, this will happen if dependencies are not resolved, the solution is
+/// call `dart pub get`.
+final class PackageConfigException implements Exception {
+ final String message;
+ PackageConfigException._(this.message);
+
+ @override
+ String toString() => message;
+}
+
+PackageConfigException packageConfigNotFound(Uri packageConfigUri) =>
+ PackageConfigException._(
+ 'package_config.json not found at: "$packageConfigUri"',
+ );
+
+PackageConfigException packageConfigInvalid(
+ Uri packageConfigUri, FormatException e) =>
+ PackageConfigException._(
+ 'Invalid package_config.json found at "$packageConfigUri": $e',
+ );
+
+PackageConfigException packageConfigIOException(IOException e) =>
+ PackageConfigException._(
+ 'Failed to read package_config.json: $e',
+ );
+
+extension on Uri {
+ Uri asDirectory() => replace(
+ pathSegments: pathSegments.lastOrNull != ''
+ ? pathSegments.followedBy([''])
+ : pathSegments,
+ );
+}
diff --git a/pkgs/extension_discovery/lib/src/registry.dart b/pkgs/extension_discovery/lib/src/registry.dart
new file mode 100644
index 0000000..60efc75
--- /dev/null
+++ b/pkgs/extension_discovery/lib/src/registry.dart
@@ -0,0 +1,144 @@
+// Copyright (c) 2023, 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' show jsonEncode;
+import 'dart:io' show File, FileSystemEntityType, IOException;
+
+import 'expect_json.dart';
+import 'io.dart';
+
+// TODO: Convert the 'rootUri' reference below to a doc comment reference once
+// https://github.com/dart-lang/linter/issues/4645 is addressed.
+
+/// Entry in the `.dart_tool/extension_discovery/<package>.json` file.
+///
+/// If the `rootUri` is not an absolute path, then we will assume that the
+/// package is mutable (either it's the root package or a path dependency).
+/// If there is no extension config file for a mutable package, then we will
+/// still store a [RegistryEntry] with `config = null`. Because everytime we
+/// load the registry, we still need to check if a configuration file has been
+/// added to the mutable package.
+typedef RegistryEntry = ({
+ String package,
+ Uri rootUri,
+ Uri packageUri,
+ Map<String, Object?>? config,
+});
+
+typedef Registry = List<RegistryEntry>;
+
+Future<Registry?> loadRegistry(File registryFile) async {
+ try {
+ final registryJson = decodeJsonMap(await registryFile.readAsString());
+ if (registryJson.expectNumber('version') != 2) {
+ throw const FormatException('"version" must be 2');
+ }
+ return registryJson
+ .expectListObjects('entries')
+ .map((e) => (
+ package: e.expectString('package'),
+ rootUri: e.expectUri('rootUri'),
+ packageUri: e.expectUri('packageUri'),
+ config: e.optionalMap('config'),
+ ))
+ .toList(growable: false);
+ } on IOException {
+ return null; // pass
+ } on FormatException {
+ await registryFile.tryDelete();
+ return null;
+ }
+}
+
+Future<void> saveRegistry(
+ File registryFile,
+ Registry registry,
+) async {
+ try {
+ if (!registryFile.parent.existsSync()) {
+ await registryFile.parent.create();
+ }
+ final tmpFile = File('${registryFile.path}.tmp');
+ final tmpFileStat = tmpFile.statSync();
+ if (tmpFileStat.type != FileSystemEntityType.notFound) {
+ final tmpAge = DateTime.now().difference(tmpFileStat.modified);
+ if (tmpAge.inSeconds < 5) {
+ // Don't try to write registry, if there is an abandoned temporary file
+ // no older than 5 seconds. Otherwise, we could have race conditions!
+ // Note: That saving the registry is a performance improvement, not a
+ // strict necessity!
+ return;
+ } else {
+ await tmpFile.delete();
+ }
+ }
+
+ await tmpFile.writeAsString(jsonEncode({
+ 'version': 2,
+ 'entries': registry
+ .map((e) => {
+ 'package': e.package,
+ 'rootUri': e.rootUri.toString(),
+ 'packageUri': e.packageUri.toString(),
+ if (e.config != null) 'config': e.config,
+ })
+ .toList(),
+ }));
+ await tmpFile.reliablyRename(registryFile.path);
+ await _ensureReadme(
+ File.fromUri(registryFile.parent.uri.resolve('README.md')),
+ );
+ } on IOException {
+ // pass
+ }
+}
+
+Future<void> _ensureReadme(File readmeFile) async {
+ try {
+ final stat = readmeFile.statSync();
+ if (stat.type != FileSystemEntityType.notFound) {
+ final age = DateTime.now().difference(stat.modified);
+ if (age.inDays < 5) {
+ return; // don't update README.md, if it's less than 5 days old
+ }
+ }
+ await readmeFile.writeAsString(_readmeContents);
+ } on IOException {
+ // pass
+ }
+}
+
+const _readmeContents = '''
+Extension Discovery Cache
+=========================
+
+This folder is used by `package:extension_discovery` to cache lists of
+packages that contains extensions for other packages.
+
+DO NOT USE THIS FOLDER
+----------------------
+
+ * Do not read (or rely) the contents of this folder.
+ * Do write to this folder.
+
+If you're interested in the lists of extensions stored in this folder use the
+API offered by package `extension_discovery` to get this information.
+
+If this package doesn't work for your use-case, then don't try to read the
+contents of this folder. It may change, and will not remain stable.
+
+Use package `extension_discovery`
+---------------------------------
+
+If you want to access information from this folder.
+
+Feel free to delete this folder
+-------------------------------
+
+Files in this folder act as a cache, and the cache is discarded if the files
+are older than the modification time of `.dart_tool/package_config.json`.
+
+Hence, it should never be necessary to clear this cache manually, if you find a
+need to do please file a bug.
+''';
diff --git a/pkgs/extension_discovery/lib/src/yaml_config_format.dart b/pkgs/extension_discovery/lib/src/yaml_config_format.dart
new file mode 100644
index 0000000..a28b31b
--- /dev/null
+++ b/pkgs/extension_discovery/lib/src/yaml_config_format.dart
@@ -0,0 +1,85 @@
+// Copyright (c) 2023, 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.
+
+/// Utilities for loading and validating YAML from a config file.
+///
+/// When writing YAML config files it's often preferable to force a few
+/// constraints on the YAML files. Specifically:
+/// * Root value must be a map.
+/// * No anchors / aliases, this can't be represented in JSON and
+/// can be used to created cycles, which might crash someone trying to
+/// serialize the data as JSON.
+/// * Maps can only have string keys.
+///
+/// Basically, only allow value that can't be represented in JSON.
+///
+/// This library aims to provide a [parseYamlFromConfigFile] that throws
+/// a [FormatException] when parsing YAML files that doesn't satisfy this
+/// constraint.
+library;
+
+import 'package:yaml/yaml.dart'
+ show YamlList, YamlMap, YamlNode, YamlScalar, loadYamlNode;
+
+/// Parse YAML from a config file.
+///
+/// This will validate that:
+/// * Maps only have string keys.
+/// * Anchor and aliases are not used (to avoid cycles).
+/// * Values can be represented as JSON.
+/// * Root node is a map.
+///
+/// Throw will throw [FormatException], if the [yamlString] does not satisfy
+/// constraints above.
+///
+/// Returns a structure consisting of the following types:
+/// * `null`,
+/// * [bool] (`true` or `false`),
+/// * [String],
+/// * [num] ([int] or [double]),
+/// * `List<Object?>`, and,
+/// * `Map<String, Object?>`.
+Map<String, Object?> parseYamlFromConfigFile(String yamlString) {
+ final visited = <YamlNode>{};
+ Object? toPlainType(YamlNode n) {
+ if (!visited.add(n)) {
+ throw const FormatException(
+ 'Anchors/aliases are not supported in YAML config files',
+ );
+ }
+ if (n is YamlScalar) {
+ return switch (n.value) {
+ null => null,
+ String s => s,
+ num n => n,
+ bool b => b,
+ _ => throw const FormatException(
+ 'Only null, string, number, bool, map and lists are supported '
+ 'in YAML config files',
+ ),
+ };
+ }
+ if (n is YamlList) {
+ return n.nodes.map(toPlainType).toList();
+ }
+ if (n is YamlMap) {
+ return n.nodes.map((key, value) {
+ final k = toPlainType(key as YamlNode);
+ if (k is! String) {
+ throw const FormatException(
+ 'Only string keys are allowed in YAML config files',
+ );
+ }
+ return MapEntry(k, toPlainType(value));
+ });
+ }
+ throw UnsupportedError('Unknown YamlNode: $n');
+ }
+
+ final value = toPlainType(loadYamlNode(yamlString));
+ if (value is! Map<String, Object?>) {
+ throw const FormatException('The root of a YAML config file must be a map');
+ }
+ return value;
+}
diff --git a/pkgs/extension_discovery/pubspec.yaml b/pkgs/extension_discovery/pubspec.yaml
new file mode 100644
index 0000000..095be3f
--- /dev/null
+++ b/pkgs/extension_discovery/pubspec.yaml
@@ -0,0 +1,18 @@
+name: extension_discovery
+description: >-
+ A convention and utilities for package extension discovery.
+version: 2.1.0
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/extension_discovery
+issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aextension_discovery
+
+environment:
+ sdk: ^3.4.0
+
+dependencies:
+ yaml: ^3.0.0
+
+dev_dependencies:
+ collection: ^1.18.0
+ dart_flutter_team_lints: ^3.0.0
+ test: ^1.21.0
+ test_descriptor: ^2.0.1
diff --git a/pkgs/extension_discovery/test/find_extensions_test.dart b/pkgs/extension_discovery/test/find_extensions_test.dart
new file mode 100644
index 0000000..33e8bf7
--- /dev/null
+++ b/pkgs/extension_discovery/test/find_extensions_test.dart
@@ -0,0 +1,350 @@
+// Copyright (c) 2023, 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:isolate' show Isolate;
+
+import 'package:extension_discovery/extension_discovery.dart';
+import 'package:extension_discovery/src/io.dart';
+import 'package:test/test.dart';
+
+import 'test_descriptor.dart' as d;
+
+void main() {
+ test('findExtensions', () async {
+ // Override the maturity delay for reliable testing of caching logic.
+ modificationMaturityDelay = const Duration(milliseconds: 500);
+
+ final pkgLibDir = await Isolate.resolvePackageUri(
+ Uri.parse('package:extension_discovery/'),
+ );
+ final pkgDir = pkgLibDir!.resolve('..');
+
+ await d.dir('myapp', [
+ d.pubspec({
+ 'name': 'myapp',
+ 'dependencies': {
+ 'extension_discovery': {'path': pkgDir.toFilePath()},
+ },
+ 'environment': {'sdk': '^3.0.0'},
+ }),
+ ]).create();
+
+ // Get dependencies
+ await d.dartPubGet(d.path('myapp'));
+
+ // Check that we don't find any extensions
+ expect(
+ await findExtensions(
+ 'myapp',
+ packageConfig: d.fileUri('myapp/.dart_tool/package_config.json'),
+ ),
+ isEmpty);
+
+ // Check that there is a myapp.json cache file
+ await d
+ .file(
+ 'myapp/.dart_tool/extension_discovery/myapp.json',
+ isNotEmpty,
+ )
+ .validate();
+
+ // ################################## Create foo that provides extension
+ await d.dir('foo', [
+ d.pubspec({
+ 'name': 'foo',
+ 'environment': {'sdk': '^3.0.0'},
+ }),
+ // It has a config.yaml for myapp
+ d.dir('extension/myapp', [
+ d.json('config.yaml', {'fromFoo': true}),
+ ]),
+ ]).create();
+
+ // Update the pubspec.yaml with a dependency on foo
+ await d.dir('myapp', [
+ d.pubspec({
+ 'name': 'myapp',
+ 'dependencies': {
+ 'extension_discovery': {'path': pkgDir.toFilePath()},
+ 'foo': {'path': '../foo'},
+ },
+ 'environment': {'sdk': '^3.0.0'},
+ }),
+ ]).create();
+
+ // Get dependencies
+ await d.dartPubGet(d.path('myapp'));
+
+ // Check that we do find an extension
+ {
+ final ext = await findExtensions(
+ 'myapp',
+ packageConfig: d.fileUri('myapp/.dart_tool/package_config.json'),
+ );
+ expect(ext, isNotEmpty);
+ expect(
+ ext.any(
+ (e) =>
+ e.package == 'foo' &&
+ e.rootUri == d.directoryUri('foo/') &&
+ e.packageUri == Uri.parse('lib/') &&
+ e.config['fromFoo'] == true,
+ ),
+ isTrue,
+ );
+ }
+
+ // ################################## Modify the config
+ await d.json('foo/extension/myapp/config.yaml', {'fromFoo': 42}).create();
+
+ // Check that we do find an extension
+ {
+ final ext = await findExtensions(
+ 'myapp',
+ packageConfig: d.fileUri('myapp/.dart_tool/package_config.json'),
+ );
+ expect(ext, isNotEmpty);
+ expect(
+ ext.any(
+ (e) =>
+ e.package == 'foo' &&
+ e.rootUri == d.directoryUri('foo/') &&
+ e.packageUri == Uri.parse('lib/') &&
+ e.config['fromFoo'] == 42,
+ ),
+ isTrue,
+ );
+ }
+
+ // ################################## Make config invalid
+ await d.file('foo/extension/myapp/config.yaml', 'invalid YAML').create();
+
+ // Check that we do not find extensions
+ {
+ final ext = await findExtensions(
+ 'myapp',
+ packageConfig: d.fileUri('myapp/.dart_tool/package_config.json'),
+ );
+ expect(ext, isEmpty);
+ }
+
+ // ################################## Make config valid again
+ await d.json('foo/extension/myapp/config.yaml', {'fromFoo': true}).create();
+
+ // Check that we do find extensions
+ {
+ final ext = await findExtensions(
+ 'myapp',
+ packageConfig: d.fileUri('myapp/.dart_tool/package_config.json'),
+ );
+ expect(ext, isNotEmpty);
+ }
+
+ // ################################## Remove config file
+ await d.file('foo/extension/myapp/config.yaml').io.delete();
+ // Check that we do not find extensions
+ {
+ final ext = await findExtensions(
+ 'myapp',
+ packageConfig: d.fileUri('myapp/.dart_tool/package_config.json'),
+ );
+ expect(ext, isEmpty);
+ }
+
+ // ################################## Make config valid again
+ await d.json('foo/extension/myapp/config.yaml', {'fromFoo': true}).create();
+
+ // Check that we do find extensions
+ {
+ final ext = await findExtensions(
+ 'myapp',
+ packageConfig: d.fileUri('myapp/.dart_tool/package_config.json'),
+ );
+ expect(ext, isNotEmpty);
+ }
+
+ // ################################## Create bar as absolute path-dep
+ await d.dir('bar', [
+ d.pubspec({
+ 'name': 'bar',
+ 'environment': {'sdk': '^3.0.0'},
+ }),
+ // It has a config.yaml for myapp
+ d.dir('extension/myapp', [
+ d.json('config.yaml', {'fromFoo': false}),
+ ]),
+ ]).create();
+
+ // Update the pubspec.yaml with a dependency on foo
+ await d.dir('myapp', [
+ d.pubspec({
+ 'name': 'myapp',
+ 'dependencies': {
+ 'extension_discovery': {'path': pkgDir.toFilePath()},
+ 'foo': {'path': '../foo'},
+ 'bar': {'path': d.path('bar')},
+ },
+ 'environment': {'sdk': '^3.0.0'},
+ }),
+ ]).create();
+
+ // We won't find bar without running `dart pub get`
+ {
+ final ext = await findExtensions(
+ 'myapp',
+ packageConfig: d.fileUri('myapp/.dart_tool/package_config.json'),
+ );
+ expect(ext.any((e) => e.package == 'bar'), isFalse);
+ }
+
+ // Get dependencies
+ await d.dartPubGet(d.path('myapp'));
+
+ // Check that we do find both extensions
+ {
+ final ext = await findExtensions(
+ 'myapp',
+ packageConfig: d.fileUri('myapp/.dart_tool/package_config.json'),
+ );
+ expect(ext, isNotEmpty);
+ expect(
+ ext.any(
+ (e) =>
+ e.package == 'foo' &&
+ e.rootUri == d.directoryUri('foo/') &&
+ e.packageUri == Uri.parse('lib/') &&
+ e.config['fromFoo'] == true,
+ ),
+ isTrue,
+ );
+ expect(
+ ext.any(
+ (e) =>
+ e.package == 'bar' &&
+ e.rootUri == d.directoryUri('bar/') &&
+ e.packageUri == Uri.parse('lib/') &&
+ e.config['fromFoo'] == false,
+ ),
+ isTrue,
+ );
+ }
+
+ // ################################## Changes to bar are not detected
+
+ // Ensure that we wait long enough for the cache to be trusted,
+ // this delay is to make sure that the logic we have handling races isn't
+ // triggered. Think of it as waiting...
+ // If the modification time of package_config.json and the cache is no more
+ // than [modificationMaturityDelay] time apart, we'll ignore the cache.
+ // Under the assumption that there was a tiny risk of a race condition.
+ await Future<void>.delayed(modificationMaturityDelay * 2);
+ // Ensure that we have a cache value!
+ await findExtensions(
+ 'myapp',
+ packageConfig: d.fileUri('myapp/.dart_tool/package_config.json'),
+ );
+
+ // Delete config from package that has an absolute path, hence, a package
+ // we assume to be immutable.
+ await d.file('bar/extension/myapp/config.yaml').io.delete();
+
+ // Check that we do find both extensions
+ {
+ final ext = await findExtensions(
+ 'myapp',
+ packageConfig: d.fileUri('myapp/.dart_tool/package_config.json'),
+ );
+ expect(ext.any((e) => e.package == 'foo'), isTrue);
+ // This is the wrong result, but it is expected. Essentially, if we expect
+ // that if you have packages in package_config.json using an absolute path
+ // then these won't be modified. If you do modify the
+ // extension/<targetPackage>/config.yaml
+ // file, then the cache will give the wrong result. The workaround is to
+ // touch `package_config.json`, or run `dart pub get` (essentially).
+ expect(ext.any((e) => e.package == 'bar'), isTrue);
+ }
+
+ // Get dependencies
+ await d.dartPubGet(d.path('myapp'));
+
+ // We won't find bar after `dart pub get`, because we removed the config
+ {
+ final ext = await findExtensions(
+ 'myapp',
+ packageConfig: d.fileUri('myapp/.dart_tool/package_config.json'),
+ );
+ expect(ext.any((e) => e.package == 'foo'), isTrue);
+ expect(ext.any((e) => e.package == 'bar'), isFalse);
+ }
+
+ // ################################## Sanity check bar and foo
+ expect(
+ await findExtensions(
+ 'foo',
+ packageConfig: d.fileUri('myapp/.dart_tool/package_config.json'),
+ ),
+ isEmpty,
+ );
+ expect(
+ await findExtensions(
+ 'bar',
+ packageConfig: d.fileUri('myapp/.dart_tool/package_config.json'),
+ ),
+ isEmpty,
+ );
+
+ // ################################## What if foo has extensions for bar
+ await d.dir('foo/extension/bar', [
+ d.json('config.yaml', {'fromFoo': true}),
+ ]).create();
+
+ {
+ final ext = await findExtensions(
+ 'bar',
+ packageConfig: d.fileUri('myapp/.dart_tool/package_config.json'),
+ );
+ expect(ext.any((e) => e.package == 'foo'), isTrue);
+ expect(ext.any((e) => e.package == 'bar'), isFalse);
+ expect(ext.any((e) => e.package == 'myapp'), isFalse);
+ }
+
+ // ################################## myapp could also extend bar
+ await d.dir('myapp/extension/bar', [
+ d.json('config.yaml', {'fromFoo': false}),
+ ]).create();
+
+ {
+ final ext = await findExtensions(
+ 'bar',
+ packageConfig: d.fileUri('myapp/.dart_tool/package_config.json'),
+ );
+ expect(ext.any((e) => e.package == 'foo'), isTrue);
+ expect(ext.any((e) => e.package == 'bar'), isFalse);
+ expect(ext.any((e) => e.package == 'myapp'), isTrue);
+ }
+
+ // ################################## config could also be written in YAML
+ await d.dir('myapp/extension/bar', [
+ d.file('config.yaml', '''
+writtenAsYaml: true
+'''),
+ ]).create();
+
+ {
+ final ext = await findExtensions(
+ 'bar',
+ packageConfig: d.fileUri('myapp/.dart_tool/package_config.json'),
+ );
+ expect(ext.any((e) => e.package == 'foo'), isTrue);
+ expect(ext.any((e) => e.package == 'bar'), isFalse);
+ expect(ext.any((e) => e.package == 'myapp'), isTrue);
+ expect(
+ ext.any(
+ (e) => e.package == 'myapp' && e.config['writtenAsYaml'] == true,
+ ),
+ isTrue,
+ );
+ }
+ });
+}
diff --git a/pkgs/extension_discovery/test/integration_test.dart b/pkgs/extension_discovery/test/integration_test.dart
new file mode 100644
index 0000000..ec52265
--- /dev/null
+++ b/pkgs/extension_discovery/test/integration_test.dart
@@ -0,0 +1,134 @@
+// Copyright (c) 2023, 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' show jsonDecode;
+import 'dart:isolate' show Isolate;
+
+import 'package:test/test.dart';
+
+import 'test_descriptor.dart' as d;
+
+typedef Ext = ({
+ String package,
+ String rootUri,
+ String packageUri,
+ dynamic config
+});
+
+void main() {
+ test('findExtensions', () async {
+ final pkgLibDir = await Isolate.resolvePackageUri(
+ Uri.parse('package:extension_discovery/'),
+ );
+ final pkgDir = pkgLibDir!.resolve('..');
+
+ await d.dir('myapp', [
+ d.pubspec({
+ 'name': 'myapp',
+ 'dependencies': {
+ 'extension_discovery': {'path': pkgDir.toFilePath()},
+ },
+ 'environment': {'sdk': '^3.0.0'},
+ }),
+ d.dir('bin', [
+ d.file('find_extensions.dart', '''
+import 'dart:convert' show JsonEncoder;
+import 'package:extension_discovery/extension_discovery.dart';
+
+Future<void> main(List<String> args) async {
+ final extensions = await findExtensions(args.first);
+ print(JsonEncoder.withIndent(' ').convert([
+ for (final e in extensions)
+ {
+ 'package': e.package,
+ 'rootUri': e.rootUri.toString(),
+ 'packageUri': e.packageUri.toString(),
+ 'config': e.config,
+ },
+ ]));
+}
+ '''),
+ ])
+ ]).create();
+
+ Future<List<Ext>> findExtensionsInSandbox(String target) async {
+ final out = await d.dart(
+ d.path('myapp/bin/find_extensions.dart'),
+ target,
+ );
+ return (jsonDecode(out) as List<Object?>)
+ .cast<Map>()
+ .map((e) => (
+ package: e['package'] as String,
+ rootUri: e['rootUri'] as String,
+ packageUri: e['packageUri'] as String,
+ config: e['config'],
+ ))
+ .toList();
+ }
+
+ // Get dependencies
+ await d.dartPubGet(d.path('myapp'));
+
+ // Check that we don't find any extensions
+ expect(await findExtensionsInSandbox('myapp'), isEmpty);
+
+ // Check that there is a myapp.json cache file
+ await d
+ .file(
+ 'myapp/.dart_tool/extension_discovery/myapp.json',
+ isNotEmpty,
+ )
+ .validate();
+
+ // Check that there is README.md
+ await d
+ .file(
+ 'myapp/.dart_tool/extension_discovery/README.md',
+ isNotEmpty,
+ )
+ .validate();
+
+ // Create a foo package
+ await d.dir('foo', [
+ d.pubspec({
+ 'name': 'foo',
+ 'environment': {'sdk': '^3.0.0'},
+ }),
+ // It has a config.yaml for myapp
+ d.dir('extension/myapp', [
+ d.json('config.yaml', {'fromFoo': true}),
+ ]),
+ ]).create();
+
+ // Update the pubspec.yaml with a dependency on foo
+ await d.dir('myapp', [
+ d.pubspec({
+ 'name': 'myapp',
+ 'dependencies': {
+ 'extension_discovery': {'path': pkgDir.toFilePath()},
+ 'foo': {'path': '../foo'},
+ },
+ 'environment': {'sdk': '^3.0.0'},
+ }),
+ ]).create();
+
+ // Get dependencies
+ await d.dartPubGet(d.path('myapp'));
+
+ // Check that we do find an extension
+ final ext = await findExtensionsInSandbox('myapp');
+ expect(ext, isNotEmpty);
+ expect(
+ ext.any(
+ (e) =>
+ e.package == 'foo' &&
+ e.rootUri == d.directoryUri('foo/').toString() &&
+ e.packageUri == Uri.parse('lib/').toString() &&
+ (e.config as Map)['fromFoo'] == true,
+ ),
+ isTrue,
+ );
+ });
+}
diff --git a/pkgs/extension_discovery/test/package_config_test.dart b/pkgs/extension_discovery/test/package_config_test.dart
new file mode 100644
index 0000000..83c6c4d
--- /dev/null
+++ b/pkgs/extension_discovery/test/package_config_test.dart
@@ -0,0 +1,192 @@
+// Copyright (c) 2023, 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' show jsonDecode, jsonEncode;
+
+import 'package:extension_discovery/src/package_config.dart';
+import 'package:test/test.dart';
+
+import 'test_descriptor.dart' as d;
+
+void main() {
+ test('loadPackageConfig', () async {
+ await d.dir('myapp', [
+ d.pubspec({
+ 'name': 'myapp',
+ 'dependencies': {},
+ 'environment': {'sdk': '^3.0.0'},
+ }),
+ ]).create();
+
+ final packageConfigFile = d.file('myapp/.dart_tool/package_config.json').io;
+
+ // Loading before `dart pub get` throws PackageConfigException
+ expect(
+ loadPackageConfig(packageConfigFile),
+ throwsA(isA<PackageConfigException>()),
+ );
+
+ // Get dependencies
+ await d.dartPubGet(d.path('myapp'));
+
+ // Parse package_config
+ {
+ final packages = await loadPackageConfig(packageConfigFile);
+ expect(packages, isNotEmpty);
+ expect(packages.any((p) => p.name == 'myapp'), isTrue);
+ expect(packages.any((p) => p.name == 'foo'), isFalse);
+ expect(packages.any((p) => p.name == 'bar'), isFalse);
+ }
+
+ // ################################## Create foo as relative path-dep
+ await d.dir('foo', [
+ d.pubspec({
+ 'name': 'foo',
+ 'environment': {'sdk': '^3.0.0'},
+ }),
+ ]).create();
+
+ // Update the pubspec.yaml with a dependency on foo
+ await d.dir('myapp', [
+ d.pubspec({
+ 'name': 'myapp',
+ 'dependencies': {
+ 'foo': {'path': '../foo'},
+ },
+ 'environment': {'sdk': '^3.0.0'},
+ }),
+ ]).create();
+
+ // Get dependencies
+ await d.dartPubGet(d.path('myapp'));
+
+ // Parse package_config
+ {
+ final packages = await loadPackageConfig(packageConfigFile);
+ expect(packages, isNotEmpty);
+ expect(packages.any((p) => p.name == 'myapp'), isTrue);
+ expect(packages.any((p) => p.name == 'foo'), isTrue);
+ expect(packages.any((p) => p.name == 'bar'), isFalse);
+ }
+
+ // ################################## Create bar as absolute path-dep
+
+ await d.dir('bar', [
+ d.pubspec({
+ 'name': 'bar',
+ 'environment': {'sdk': '^3.0.0'},
+ }),
+ ]).create();
+
+ // Update the pubspec.yaml with a dependency on foo
+ await d.dir('myapp', [
+ d.pubspec({
+ 'name': 'myapp',
+ 'dependencies': {
+ 'foo': {'path': '../foo'},
+ 'bar': {'path': d.path('bar')},
+ },
+ 'environment': {'sdk': '^3.0.0'},
+ }),
+ ]).create();
+
+ // Get dependencies
+ await d.dartPubGet(d.path('myapp'));
+
+ // Parse package_config
+ {
+ final packages = await loadPackageConfig(packageConfigFile);
+ expect(packages, isNotEmpty);
+ expect(packages.any((p) => p.name == 'myapp'), isTrue);
+ expect(packages.any((p) => p.name == 'foo'), isTrue);
+ expect(packages.any((p) => p.name == 'bar'), isTrue);
+ }
+
+ // ################################## Cannot read version 99
+
+ {
+ final pkgcfg = jsonDecode(packageConfigFile.readAsStringSync()) as Map;
+ pkgcfg['configVersion'] = 99;
+ packageConfigFile.writeAsStringSync(jsonEncode(pkgcfg));
+ }
+
+ // Loading before `dart pub get` throws PackageConfigException
+ expect(
+ loadPackageConfig(packageConfigFile),
+ throwsA(isA<PackageConfigException>()),
+ );
+
+ // ################################## packageUri is optional
+
+ // Get dependencies
+ await d.dartPubGet(d.path('myapp'));
+
+ {
+ final pkgcfg = jsonDecode(packageConfigFile.readAsStringSync()) as Map;
+ for (final p in pkgcfg['packages'] as List) {
+ (p as Map).remove('packageUri');
+ }
+ packageConfigFile.writeAsStringSync(jsonEncode(pkgcfg));
+ }
+
+ // Parse package_config
+ {
+ final packages = await loadPackageConfig(packageConfigFile);
+ expect(packages, isNotEmpty);
+ expect(packages.any((p) => p.name == 'myapp'), isTrue);
+ expect(packages.any((p) => p.name == 'foo'), isTrue);
+ expect(packages.any((p) => p.name == 'bar'), isTrue);
+ expect(packages.every((p) => p.rootUri == p.packageUri), isTrue);
+ }
+
+ // ################################## packageUri is optional
+
+ // Get dependencies
+ await d.dartPubGet(d.path('myapp'));
+
+ {
+ final pkgcfg = jsonDecode(packageConfigFile.readAsStringSync()) as Map;
+ for (final p in pkgcfg['packages'] as List) {
+ (p as Map).remove('rootUri');
+ }
+ packageConfigFile.writeAsStringSync(jsonEncode(pkgcfg));
+ }
+
+ // Loading before `dart pub get` throws PackageConfigException
+ expect(
+ loadPackageConfig(packageConfigFile),
+ throwsA(isA<PackageConfigException>()),
+ );
+ });
+
+ test('`findPackageConfig()`', () async {
+ await d.dir('workspace', [
+ d.dir('.dart_tool', [
+ d.file('package_config.json'),
+ ]),
+ d.dir('myapp', [])
+ ]).create();
+
+ expect(
+ findPackageConfig(d.fileUri('workspace')),
+ d.fileUri('workspace/.dart_tool/package_config.json'),
+ );
+ expect(
+ findPackageConfig(d.fileUri('workspace/')),
+ d.fileUri('workspace/.dart_tool/package_config.json'),
+ );
+ expect(
+ findPackageConfig(d.fileUri('workspace/myapp')),
+ d.fileUri('workspace/.dart_tool/package_config.json'),
+ );
+ expect(
+ findPackageConfig(d.fileUri('.')),
+ isNull,
+ );
+ expect(
+ findPackageConfig(d.fileUri('foo')),
+ isNull,
+ );
+ });
+}
diff --git a/pkgs/extension_discovery/test/test_descriptor.dart b/pkgs/extension_discovery/test/test_descriptor.dart
new file mode 100644
index 0000000..0bd4a63
--- /dev/null
+++ b/pkgs/extension_discovery/test/test_descriptor.dart
@@ -0,0 +1,56 @@
+// Copyright (c) 2023, 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' show JsonEncoder;
+import 'dart:io' show Platform, Process;
+
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+export 'package:test_descriptor/test_descriptor.dart';
+
+d.FileDescriptor json(String fileName, Object? json) =>
+ d.file(fileName, const JsonEncoder.withIndent(' ').convert(json));
+
+d.FileDescriptor pubspec(Map<String, Object?> pubspec) =>
+ json('pubspec.yaml', pubspec);
+
+Uri fileUri(String path) => Uri.file(d.path(path));
+Uri directoryUri(String path) => Uri.directory(d.path(path));
+
+Future<String> dart([
+ String? arg1,
+ String? arg2,
+ String? arg3,
+ String? arg4,
+ String? arg5,
+ String? arg6,
+ String? arg7,
+ String? arg8,
+]) async {
+ final arguments = [
+ if (arg1 != null) arg1,
+ if (arg2 != null) arg2,
+ if (arg3 != null) arg3,
+ if (arg4 != null) arg4,
+ if (arg5 != null) arg5,
+ if (arg6 != null) arg6,
+ if (arg7 != null) arg7,
+ if (arg8 != null) arg8,
+ ];
+ final result = await Process.run(
+ Platform.executable,
+ arguments,
+ workingDirectory: d.sandbox,
+ );
+ if (result.exitCode != 0) {
+ print(result.stderr);
+ print(result.stdout);
+ }
+ expect(result.exitCode, 0,
+ reason: 'failed executing "dart ${arguments.join(' ')}"');
+ return result.stdout as String;
+}
+
+Future<String> dartPubGet(String folder) async =>
+ await dart('pub', 'get', '-C', folder);
diff --git a/pkgs/extension_discovery/test/yaml_config_format_test.dart b/pkgs/extension_discovery/test/yaml_config_format_test.dart
new file mode 100644
index 0000000..0e3f568
--- /dev/null
+++ b/pkgs/extension_discovery/test/yaml_config_format_test.dart
@@ -0,0 +1,149 @@
+// Copyright (c) 2023, 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:collection/collection.dart' show DeepCollectionEquality;
+
+import 'package:extension_discovery/src/yaml_config_format.dart';
+import 'package:test/test.dart';
+
+void _testInvalid(String name, String yaml, {String match = ''}) {
+ test(name, () {
+ expect(
+ () => parseYamlFromConfigFile(yaml),
+ throwsA(isFormatException.having(
+ (e) => e.message,
+ 'message',
+ contains(match),
+ )),
+ );
+ });
+}
+
+void _testValid(String name, String yaml) {
+ test(name, () {
+ parseYamlFromConfigFile(yaml);
+ });
+}
+
+void main() {
+ group('parseYamlFromConfigFile', () {
+ group('root must be a map', () {
+ _testInvalid('empty string', '', match: 'map');
+ _testInvalid('null', 'null', match: 'map');
+ _testInvalid('list', '[]', match: 'map');
+ _testInvalid('number (1)', '42', match: 'map');
+ _testInvalid('number (2)', '42.2', match: 'map');
+ _testInvalid('bool (1)', 'true', match: 'map');
+ _testInvalid('bool (2)', 'false', match: 'map');
+ _testInvalid('string', '"hello world"', match: 'map');
+ });
+
+ _testInvalid('must not have alias', match: 'alias', '''
+ keyA: &myref
+ - value 1
+ - value 2
+ KeyB: *myref
+ ''');
+
+ // it works without aliases
+ _testValid('listed inside a map is fine', '''
+ keyA:
+ - value 1
+ - value 2
+ KeyB:
+ - value 1
+ - value 2
+ ''');
+
+ _testValid('comments are cool', '''
+ keyA:
+ - value 1 # this is fine
+ - value 2
+ ''');
+
+ _testInvalid(
+ 'Top-level map with integer keys is not allowed', match: 'key', '''
+ 42: "value" # this is NOT fine
+ 43: true
+ ''');
+
+ _testValid('Map with quoted integer keys is fine', '''
+ "42": "value" # this is fine
+ '43': true
+ ''');
+
+ _testInvalid('Map with integer keys is not allowed', match: 'key', '''
+ keyA:
+ - value 1 # this is fine
+ - value 2
+ KeyB:
+ 42: value 3 # this is NOT fine
+ ''');
+
+ _testInvalid('Map with list keys is not allowed', match: 'key', '''
+ keyA:
+ - [1, 2, 3] # this is fine, crazy but fine!
+ keyB:
+ [1, 2, 3]: value 3 # this is NOT fine
+ ''');
+
+ _testInvalid('Map with boolean keys is not allowed (1)', match: 'key', '''
+ keyA: true
+ keyB:
+ true: value 3 # this is NOT fine
+ ''');
+
+ _testInvalid('Map with boolean keys is not allowed (2)', match: 'key', '''
+ keyA: true
+ keyB:
+ false: value 3 # this is NOT fine
+ ''');
+
+ _testInvalid('Map with null keys is not allowed', match: 'key', '''
+ keyA: null # fine
+ keyB:
+ null: value 3 # this is NOT fine
+ ''');
+
+ _testValid('complex structures is fine', '''
+ keyA: |
+ multi-line
+ string
+ keyB: 42 # number
+ keyC: 42.2 # double
+ keyD: [1, 2]
+ keyE:
+ - 1
+ - 2
+ keyF:
+ k: "v"
+ ''');
+
+ test('complex structures matches the same JSON', () {
+ final yaml = '''
+ keyA: "hello"
+ keyB: 42 # number
+ keyC: 42.2 # double
+ keyD: [1, 2]
+ keyE:
+ - 1
+ - 2
+ keyF:
+ k: "v"
+ ''';
+ final data = parseYamlFromConfigFile(yaml);
+ expect(
+ const DeepCollectionEquality().equals(data, {
+ 'keyA': 'hello',
+ 'keyB': 42,
+ 'keyC': 42.2,
+ 'keyD': [1, 2],
+ 'keyE': [1, 2],
+ 'keyF': {'k': 'v'},
+ }),
+ isTrue,
+ );
+ });
+ });
+}
diff --git a/pkgs/file/.gitignore b/pkgs/file/.gitignore
new file mode 100644
index 0000000..ddfdca1
--- /dev/null
+++ b/pkgs/file/.gitignore
@@ -0,0 +1,5 @@
+.dart_tool/
+.packages
+.pub/
+build/
+pubspec.lock
diff --git a/pkgs/file/CHANGELOG.md b/pkgs/file/CHANGELOG.md
new file mode 100644
index 0000000..3a3969c
--- /dev/null
+++ b/pkgs/file/CHANGELOG.md
@@ -0,0 +1,244 @@
+## 7.0.2-wip
+
+## 7.0.1
+
+* Update the pubspec repository field to reflect the new package repository.
+
+## 7.0.0
+
+* Dart 3 fixes for class modifiers.
+* Require Dart 3.0.
+* `MemoryFileSystem` now treats empty paths as non-existent.
+* Fix `FileSystem.isLink`/`FileSystem.isLinkSync` to not follow symbolic links.
+* Make the return type of `MemoryFile.openRead` and `_ChrootFile.openRead` again
+ match the return type from `dart:io`.
+
+## 6.1.4
+
+* Populate the pubspec `repository` field.
+
+## 6.1.3
+
+* In classes that implement `File` methods `create`, `createSync` now take `bool exclusive = true` parameter. No functional changes.
+
+## 6.1.2
+
+* `MemoryFileSystem` now provides `opHandle`s for exists operations.
+
+## 6.1.1
+
+* `MemoryFile` now provides `opHandle`s for copy and open operations.
+
+## 6.1.0
+
+* Reading invalid UTF8 with the `MemoryFileSystem` now correctly throws a `FileSystemException` instead of a `FormatError`.
+* `MemoryFileSystem` now provides an `opHandle` to inspect read/write operations.
+* `MemoryFileSystem` now creates the temporary directory before returning in `createTemp`/`createTempSync`.
+
+## 6.0.1
+
+* Fix sound type error in memory backend when reading non-existent `MemoryDirectory`.
+
+## 6.0.0
+
+* First stable null safe release.
+
+## 6.0.0-nullsafety.4
+
+* Update upper bound of SDK constraint.
+
+## 6.0.0-nullsafety.3
+
+* Update upper bound of SDK constraint.
+
+## 6.0.0-nullsafety.2
+
+* Make `ForwardingFile.openRead`'s return type again match the return type from
+ `dart:io`.
+* Remove some unnecessary `Uint8List` conversions in `ForwardingFile`.
+
+## 6.0.0-nullsafety.1
+
+* Update to null safety.
+* Remove record/replay functionality.
+* Made `MemoryRandomAccessFile` and `MemoryFile.openWrite` handle the file
+ being removed or renamed while open.
+* Fixed incorrect formatting in `NoMatchingInvocationError.toString()`.
+* Fixed more test flakiness.
+* Enabled more tests.
+* Internal cleanup.
+* Remove implicit dynamic in preparation for null safety.
+* Remove dependency on Intl.
+
+## 5.2.1
+
+* systemTemp directories created by `MemoryFileSystem` will allot names
+ based on the file system instance instead of globally.
+* `MemoryFile.readAsLines()`/`readAsLinesSync()` no longer treat a final newline
+ in the file as the start of a new, empty line.
+* `RecordingFile.readAsLine()`/`readAsLinesSync()` now always record a final
+ newline.
+* `MemoryFile.flush()` now returns `Future<void>` instead of `Future<dynamic>`.
+* Fixed some test flakiness.
+* Enabled more tests.
+* Internal cleanup.
+
+## 5.2.0
+
+* Added a `MemoryRandomAccessFile` class and implemented
+ `MemoryFile.open()`/`openSync()`.
+
+## 5.1.0
+
+* Added a new `MemoryFileSystem` constructor to use a test clock
+
+## 5.0.10
+
+* Added example
+
+## 5.0.9
+
+* Fix lints for project health
+
+## 5.0.8
+
+* Return `Uint8List` rather than `List<int>`.
+
+## 5.0.7
+
+* Dart 2 fixes for `RecordingProxyMixin` and `ReplayProxyMixin`.
+
+## 5.0.6
+
+* Dart 2 fixes for `RecordingFile.open()`
+
+## 5.0.5
+
+* Dart 2 fixes
+
+## 5.0.4
+
+* Update SDK constraint to 2.0.0-dev.67.0, remove workaround in
+ recording_proxy_mixin.dart.
+* Fix usage within Dart 2 runtime mode in Dart 2.0.0-dev.61.0 and later.
+* Relax constraints on `package:test`
+
+## 5.0.3
+
+* Update `package:test` dependency to 1.0
+
+## 5.0.2
+
+* Declare compatibility with Dart 2 stable
+
+## 5.0.1
+
+* Remove upper case constants
+* Update SDK constraint to 2.0.0-dev.54.0.
+
+## 5.0.0
+
+* Moved `testing` library into a dedicated `package:file_testing` so that
+ libraries don't need to take on a transitive dependency on `package:test`
+ in order to use `package:file`.
+
+## 4.0.1
+
+* General library cleanup
+* Add `style` support in `MemoryFileSystem`, so that callers can choose to
+ have a memory file system with windows-like paths. [#68]
+ (https://github.com/google/file.dart/issues/68)
+
+## 4.0.0
+
+* Change method signature for `RecordingRandomAccessFile._close` to return a
+ `Future<void>` instead of `Future<RandomAccessFile>`. This follows a change in
+ dart:io, Dart SDK `2.0.0-dev.40`.
+
+## 3.0.0
+
+* Import `dart:io` unconditionally. More recent Dart SDK revisions allow
+ `dart:io` to be imported in a browser context, though if methods are actually
+ invoked, they will fail. This matches well with `package:file`, where users
+ can use the `memory` library and get in-memory implementations of the
+ `dart:io` interfaces.
+* Bump minimum Dart SDK to `1.24.0`
+
+## 2.3.7
+
+* Fix Dart 2 error.
+
+## 2.3.6
+
+* Relax sdk upper bound constraint to '<2.0.0' to allow 'edge' dart sdk use.
+
+## 2.3.5
+
+* Fix internal use of a cast which fails on Dart 2.0 .
+
+## 2.3.4
+
+* Bumped maximum Dart SDK version to 2.0.0-dev.infinity
+
+## 2.3.3
+
+* Relaxes version requirements on `package:intl`
+
+## 2.3.2
+
+* Fixed `FileSystem.directory(Uri)`, `FileSystem.file(Uri)`, and
+ `FileSystem.link(Uri)` to consult the file system's path context when
+ converting the URI to a file path rather than using `Uri.toFilePath()`.
+
+## 2.3.1
+
+* Fixed `MemoryFileSystem` to make `File.writeAs...()` update the last modified
+ time of the file.
+
+## 2.3.0
+
+* Added the following convenience methods in `Directory`:
+ * `Directory.childDirectory(String basename)`
+ * `Directory.childFile(String basename)`
+ * `Directory.childLink(String basename)`
+
+## 2.2.0
+
+* Added `ErrorCodes` class, which holds errno values.
+
+## 2.1.0
+
+* Add support for new `dart:io` API methods added in Dart SDK 1.23
+
+## 2.0.1
+
+* Minor doc updates
+
+## 2.0.0
+
+* Improved `toString` implementations in file system entity classes
+* Added `ForwardingFileSystem` and associated forwarding classes to the
+ main `file` library
+* Removed `FileSystem.pathSeparator`, and added a more comprehensive
+ `FileSystem.path` property
+* Added `FileSystemEntity.basename` and `FileSystemEntity.dirname`
+* Added the `record_replay` library
+* Added the `testing` library
+
+## 1.0.1
+
+* Added `FileSystem.systemTempDirectory`
+* Added the ability to pass `Uri` and `FileSystemEntity` types to
+ `FileSystem.directory()`, `FileSystem.file()`, and `FileSystem.link()`
+* Added `FileSystem.pathSeparator`
+
+## 1.0.0
+
+* Unified interface to match dart:io API
+* Local file system implementation
+* In-memory file system implementation
+* Chroot file system implementation
+
+## 0.1.0
+
+* Initial version
diff --git a/pkgs/file/LICENSE b/pkgs/file/LICENSE
new file mode 100644
index 0000000..076334f
--- /dev/null
+++ b/pkgs/file/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2017, the Dart project authors. All rights reserved.
+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 Inc. 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/file/README.md b/pkgs/file/README.md
new file mode 100644
index 0000000..a63d6e6
--- /dev/null
+++ b/pkgs/file/README.md
@@ -0,0 +1,44 @@
+[](https://github.com/dart-lang/tools/actions/workflows/file.yml)
+[](https://pub.dev/packages/file)
+[](https://pub.dev/packages/file/publisher)
+
+A generic file system abstraction for Dart.
+
+## Features
+
+Like `dart:io`, `package:file` supplies a rich Dart-idiomatic API for accessing
+a file system.
+
+Unlike `dart:io`, `package:file`:
+
+- Can be used to implement custom file systems.
+- Comes with an in-memory implementation out-of-the-box, making it super-easy to
+ test code that works with the file system.
+- Allows using multiple file systems simultaneously. A file system is a
+ first-class object. Instantiate however many you want and use them all.
+
+## Usage
+
+Implement your own custom file system:
+
+```dart
+import 'package:file/file.dart';
+
+class FooBarFileSystem implements FileSystem { ... }
+```
+
+Use the in-memory file system:
+
+```dart
+import 'package:file/memory.dart';
+
+var fs = MemoryFileSystem();
+```
+
+Use the local file system (requires dart:io access):
+
+```dart
+import 'package:file/local.dart';
+
+var fs = const LocalFileSystem();
+```
diff --git a/pkgs/file/analysis_options.yaml b/pkgs/file/analysis_options.yaml
new file mode 100644
index 0000000..d978f81
--- /dev/null
+++ b/pkgs/file/analysis_options.yaml
@@ -0,0 +1 @@
+include: package:dart_flutter_team_lints/analysis_options.yaml
diff --git a/pkgs/file/example/main.dart b/pkgs/file/example/main.dart
new file mode 100644
index 0000000..b03b363
--- /dev/null
+++ b/pkgs/file/example/main.dart
@@ -0,0 +1,14 @@
+// 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 'package:file/file.dart';
+import 'package:file/memory.dart';
+
+Future<void> main() async {
+ final FileSystem fs = MemoryFileSystem();
+ final tmp = await fs.systemTempDirectory.createTemp('example_');
+ final outputFile = tmp.childFile('output');
+ await outputFile.writeAsString('Hello world!');
+ print(outputFile.readAsStringSync());
+}
diff --git a/pkgs/file/lib/chroot.dart b/pkgs/file/lib/chroot.dart
new file mode 100644
index 0000000..6992ad0
--- /dev/null
+++ b/pkgs/file/lib/chroot.dart
@@ -0,0 +1,8 @@
+// Copyright (c) 2017, 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.
+
+/// A file system that provides a view into _another_ `FileSystem` via a path.
+library;
+
+export 'src/backends/chroot.dart';
diff --git a/pkgs/file/lib/file.dart b/pkgs/file/lib/file.dart
new file mode 100644
index 0000000..c2e97b2
--- /dev/null
+++ b/pkgs/file/lib/file.dart
@@ -0,0 +1,10 @@
+// Copyright (c) 2017, 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.
+
+/// Core interfaces containing the abstract `FileSystem` interface definition
+/// and all associated types used by `FileSystem`.
+library;
+
+export 'src/forwarding.dart';
+export 'src/interface.dart';
diff --git a/pkgs/file/lib/local.dart b/pkgs/file/lib/local.dart
new file mode 100644
index 0000000..5b1e3cd
--- /dev/null
+++ b/pkgs/file/lib/local.dart
@@ -0,0 +1,9 @@
+// Copyright (c) 2017, 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.
+
+/// A local file system implementation. This relies on the use of `dart:io`
+/// and is thus not suitable for use in the browser.
+library;
+
+export 'src/backends/local.dart';
diff --git a/pkgs/file/lib/memory.dart b/pkgs/file/lib/memory.dart
new file mode 100644
index 0000000..690b65f
--- /dev/null
+++ b/pkgs/file/lib/memory.dart
@@ -0,0 +1,10 @@
+// Copyright (c) 2017, 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.
+
+/// An implementation of `FileSystem` that exists entirely in memory with an
+/// internal representation loosely based on the Filesystem Hierarchy Standard.
+library;
+
+export 'src/backends/memory.dart';
+export 'src/backends/memory/operations.dart';
diff --git a/pkgs/file/lib/src/backends/chroot.dart b/pkgs/file/lib/src/backends/chroot.dart
new file mode 100644
index 0000000..402dbec
--- /dev/null
+++ b/pkgs/file/lib/src/backends/chroot.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2017, 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:typed_data';
+
+import 'package:path/path.dart' as p;
+
+import '../common.dart' as common;
+import '../forwarding.dart';
+import '../interface.dart';
+import '../io.dart' as io;
+
+part 'chroot/chroot_directory.dart';
+part 'chroot/chroot_file.dart';
+part 'chroot/chroot_file_system.dart';
+part 'chroot/chroot_file_system_entity.dart';
+part 'chroot/chroot_link.dart';
+part 'chroot/chroot_random_access_file.dart';
diff --git a/pkgs/file/lib/src/backends/chroot/chroot_directory.dart b/pkgs/file/lib/src/backends/chroot/chroot_directory.dart
new file mode 100644
index 0000000..e094193
--- /dev/null
+++ b/pkgs/file/lib/src/backends/chroot/chroot_directory.dart
@@ -0,0 +1,174 @@
+// Copyright (c) 2017, 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.
+
+part of '../chroot.dart';
+
+class _ChrootDirectory extends _ChrootFileSystemEntity<Directory, io.Directory>
+ with ForwardingDirectory<Directory>, common.DirectoryAddOnsMixin {
+ _ChrootDirectory(super.fs, super.path);
+
+ factory _ChrootDirectory.wrapped(
+ ChrootFileSystem fs,
+ Directory delegate, {
+ bool relative = false,
+ }) {
+ var localPath = fs._local(delegate.path, relative: relative);
+ return _ChrootDirectory(fs, localPath);
+ }
+
+ @override
+ FileSystemEntityType get expectedType => FileSystemEntityType.directory;
+
+ @override
+ io.Directory _rawDelegate(String path) => fileSystem.delegate.directory(path);
+
+ @override
+ Uri get uri => Uri.directory(path);
+
+ @override
+ Future<Directory> rename(String newPath) async {
+ if (_isLink) {
+ if (await fileSystem.type(path) != expectedType) {
+ throw common.notADirectory(path);
+ }
+ var type = await fileSystem.type(newPath);
+ if (type != FileSystemEntityType.notFound) {
+ if (type != expectedType) {
+ throw common.notADirectory(newPath);
+ }
+ if (!(await fileSystem
+ .directory(newPath)
+ .list(followLinks: false)
+ .isEmpty)) {
+ throw common.directoryNotEmpty(newPath);
+ }
+ }
+ var target = await fileSystem.link(path).target();
+ await fileSystem.link(path).delete();
+ await fileSystem.link(newPath).create(target);
+ return fileSystem.directory(newPath);
+ } else {
+ return wrap(await getDelegate(followLinks: true)
+ .rename(fileSystem._real(newPath)));
+ }
+ }
+
+ @override
+ Directory renameSync(String newPath) {
+ if (_isLink) {
+ if (fileSystem.typeSync(path) != expectedType) {
+ throw common.notADirectory(path);
+ }
+ var type = fileSystem.typeSync(newPath);
+ if (type != FileSystemEntityType.notFound) {
+ if (type != expectedType) {
+ throw common.notADirectory(newPath);
+ }
+ if (fileSystem
+ .directory(newPath)
+ .listSync(followLinks: false)
+ .isNotEmpty) {
+ throw common.directoryNotEmpty(newPath);
+ }
+ }
+ var target = fileSystem.link(path).targetSync();
+ fileSystem.link(path).deleteSync();
+ fileSystem.link(newPath).createSync(target);
+ return fileSystem.directory(newPath);
+ } else {
+ return wrap(
+ getDelegate(followLinks: true).renameSync(fileSystem._real(newPath)));
+ }
+ }
+
+ @override
+ Directory get absolute => _ChrootDirectory(fileSystem, _absolutePath);
+
+ @override
+ Directory get parent {
+ try {
+ return wrapDirectory(delegate.parent);
+ } on _ChrootJailException {
+ return this;
+ }
+ }
+
+ @override
+ Future<Directory> create({bool recursive = false}) async {
+ if (_isLink) {
+ return switch (await fileSystem.type(path)) {
+ FileSystemEntityType.notFound =>
+ throw common.noSuchFileOrDirectory(path),
+ FileSystemEntityType.file => throw common.fileExists(path),
+ FileSystemEntityType.directory =>
+ // Nothing to do.
+ this,
+ _ => throw AssertionError()
+ };
+ } else {
+ return wrap(await delegate.create(recursive: recursive));
+ }
+ }
+
+ @override
+ void createSync({bool recursive = false}) {
+ if (_isLink) {
+ switch (fileSystem.typeSync(path)) {
+ case FileSystemEntityType.notFound:
+ throw common.noSuchFileOrDirectory(path);
+ case FileSystemEntityType.file:
+ throw common.fileExists(path);
+ case FileSystemEntityType.directory:
+ // Nothing to do.
+ return;
+ default:
+ throw AssertionError();
+ }
+ } else {
+ delegate.createSync(recursive: recursive);
+ }
+ }
+
+ @override
+ Stream<FileSystemEntity> list({
+ bool recursive = false,
+ bool followLinks = true,
+ }) {
+ var delegate = this.delegate as Directory;
+ var dirname = delegate.path;
+ return delegate
+ .list(recursive: recursive, followLinks: followLinks)
+ .map((io.FileSystemEntity entity) => _denormalize(entity, dirname));
+ }
+
+ @override
+ List<FileSystemEntity> listSync({
+ bool recursive = false,
+ bool followLinks = true,
+ }) {
+ var delegate = this.delegate as Directory;
+ var dirname = delegate.path;
+ return delegate
+ .listSync(recursive: recursive, followLinks: followLinks)
+ .map((io.FileSystemEntity entity) => _denormalize(entity, dirname))
+ .toList();
+ }
+
+ FileSystemEntity _denormalize(io.FileSystemEntity entity, String dirname) {
+ var ctx = fileSystem.path;
+ var relativePart = ctx.relative(entity.path, from: dirname);
+ var entityPath = ctx.join(path, relativePart);
+ if (entity is io.File) {
+ return _ChrootFile(fileSystem, entityPath);
+ } else if (entity is io.Directory) {
+ return _ChrootDirectory(fileSystem, entityPath);
+ } else if (entity is io.Link) {
+ return _ChrootLink(fileSystem, entityPath);
+ }
+ throw FileSystemException('Unsupported type: $entity', entity.path);
+ }
+
+ @override
+ String toString() => "ChrootDirectory: '$path'";
+}
diff --git a/pkgs/file/lib/src/backends/chroot/chroot_file.dart b/pkgs/file/lib/src/backends/chroot/chroot_file.dart
new file mode 100644
index 0000000..d6c29fc
--- /dev/null
+++ b/pkgs/file/lib/src/backends/chroot/chroot_file.dart
@@ -0,0 +1,339 @@
+// Copyright (c) 2017, 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.
+
+part of '../chroot.dart';
+
+typedef _SetupCallback = dynamic Function();
+
+class _ChrootFile extends _ChrootFileSystemEntity<File, io.File>
+ with ForwardingFile {
+ _ChrootFile(super.fs, super.path);
+
+ factory _ChrootFile.wrapped(
+ ChrootFileSystem fs,
+ io.File delegate, {
+ bool relative = false,
+ }) {
+ var localPath = fs._local(delegate.path, relative: relative);
+ return _ChrootFile(fs, localPath);
+ }
+
+ @override
+ FileSystemEntityType get expectedType => FileSystemEntityType.file;
+
+ @override
+ io.File _rawDelegate(String path) => fileSystem.delegate.file(path);
+
+ @override
+ Future<File> rename(String newPath) async {
+ _SetupCallback setUp = () async {};
+
+ if (await fileSystem.type(newPath, followLinks: false) ==
+ FileSystemEntityType.link) {
+ // The delegate file system will ensure that the link target references
+ // an actual file before allowing the rename, but we want the link target
+ // to be resolved with respect to this file system. Thus, we perform that
+ // validation here instead.
+ switch (await fileSystem.type(newPath)) {
+ case FileSystemEntityType.file:
+ case FileSystemEntityType.notFound:
+ // Validation passed; delete the link to keep the delegate file
+ // system's validation from getting in the way.
+ setUp = () async {
+ await fileSystem.link(newPath).delete();
+ };
+ break;
+ case FileSystemEntityType.directory:
+ throw common.isADirectory(newPath);
+ default:
+ // Should never happen.
+ throw AssertionError();
+ }
+ }
+
+ if (_isLink) {
+ switch (await fileSystem.type(path)) {
+ case FileSystemEntityType.notFound:
+ throw common.noSuchFileOrDirectory(path);
+ case FileSystemEntityType.directory:
+ throw common.isADirectory(path);
+ case FileSystemEntityType.file:
+ await setUp();
+ await fileSystem.delegate
+ .link(fileSystem._real(path))
+ .rename(fileSystem._real(newPath));
+ return _ChrootFile(fileSystem, newPath);
+ default:
+ throw AssertionError();
+ }
+ } else {
+ await setUp();
+ return wrap(await delegate.rename(fileSystem._real(newPath)));
+ }
+ }
+
+ @override
+ File renameSync(String newPath) {
+ _SetupCallback setUp = () {};
+
+ if (fileSystem.typeSync(newPath, followLinks: false) ==
+ FileSystemEntityType.link) {
+ // The delegate file system will ensure that the link target references
+ // an actual file before allowing the rename, but we want the link target
+ // to be resolved with respect to this file system. Thus, we perform that
+ // validation here instead.
+ switch (fileSystem.typeSync(newPath)) {
+ case FileSystemEntityType.file:
+ case FileSystemEntityType.notFound:
+ // Validation passed; delete the link to keep the delegate file
+ // system's validation from getting in the way.
+ setUp = () {
+ fileSystem.link(newPath).deleteSync();
+ };
+ break;
+ case FileSystemEntityType.directory:
+ throw common.isADirectory(newPath);
+ default:
+ // Should never happen.
+ throw AssertionError();
+ }
+ }
+
+ if (_isLink) {
+ switch (fileSystem.typeSync(path)) {
+ case FileSystemEntityType.notFound:
+ throw common.noSuchFileOrDirectory(path);
+ case FileSystemEntityType.directory:
+ throw common.isADirectory(path);
+ case FileSystemEntityType.file:
+ setUp();
+ fileSystem.delegate
+ .link(fileSystem._real(path))
+ .renameSync(fileSystem._real(newPath));
+ return _ChrootFile(fileSystem, newPath);
+ default:
+ throw AssertionError();
+ }
+ } else {
+ setUp();
+ return wrap(delegate.renameSync(fileSystem._real(newPath)));
+ }
+ }
+
+ @override
+ File get absolute => _ChrootFile(fileSystem, _absolutePath);
+
+ @override
+ Future<File> create({bool recursive = false, bool exclusive = false}) async {
+ var path = fileSystem._resolve(
+ this.path,
+ followLinks: false,
+ notFound: recursive ? _NotFoundBehavior.mkdir : _NotFoundBehavior.allow,
+ );
+
+ String real() => fileSystem._real(path, resolve: false);
+ Future<FileSystemEntityType> type() =>
+ fileSystem.delegate.type(real(), followLinks: false);
+
+ if (await type() == FileSystemEntityType.link) {
+ path = fileSystem._resolve(p.basename(path),
+ from: p.dirname(path), notFound: _NotFoundBehavior.allowAtTail);
+ switch (await type()) {
+ case FileSystemEntityType.notFound:
+ await _rawDelegate(real()).create();
+ return this;
+ case FileSystemEntityType.file:
+ // Nothing to do.
+ return this;
+ case FileSystemEntityType.directory:
+ throw common.isADirectory(path);
+ default:
+ throw AssertionError();
+ }
+ } else {
+ return wrap(await _rawDelegate(real()).create());
+ }
+ }
+
+ @override
+ void createSync({bool recursive = false, bool exclusive = false}) {
+ var path = fileSystem._resolve(
+ this.path,
+ followLinks: false,
+ notFound: recursive ? _NotFoundBehavior.mkdir : _NotFoundBehavior.allow,
+ );
+
+ String real() => fileSystem._real(path, resolve: false);
+ FileSystemEntityType type() =>
+ fileSystem.delegate.typeSync(real(), followLinks: false);
+
+ if (type() == FileSystemEntityType.link) {
+ path = fileSystem._resolve(p.basename(path),
+ from: p.dirname(path), notFound: _NotFoundBehavior.allowAtTail);
+ switch (type()) {
+ case FileSystemEntityType.notFound:
+ _rawDelegate(real()).createSync();
+ return;
+ case FileSystemEntityType.file:
+ // Nothing to do.
+ return;
+ case FileSystemEntityType.directory:
+ throw common.isADirectory(path);
+ default:
+ throw AssertionError();
+ }
+ } else {
+ _rawDelegate(real()).createSync();
+ }
+ }
+
+ @override
+ Future<File> copy(String newPath) async {
+ return wrap(await getDelegate(followLinks: true)
+ .copy(fileSystem._real(newPath, followLinks: true)));
+ }
+
+ @override
+ File copySync(String newPath) {
+ return wrap(getDelegate(followLinks: true)
+ .copySync(fileSystem._real(newPath, followLinks: true)));
+ }
+
+ @override
+ Future<int> length() => getDelegate(followLinks: true).length();
+
+ @override
+ int lengthSync() => getDelegate(followLinks: true).lengthSync();
+
+ @override
+ Future<DateTime> lastAccessed() =>
+ getDelegate(followLinks: true).lastAccessed();
+
+ @override
+ DateTime lastAccessedSync() =>
+ getDelegate(followLinks: true).lastAccessedSync();
+
+ @override
+ Future<dynamic> setLastAccessed(DateTime time) =>
+ getDelegate(followLinks: true).setLastAccessed(time);
+
+ @override
+ void setLastAccessedSync(DateTime time) =>
+ getDelegate(followLinks: true).setLastAccessedSync(time);
+
+ @override
+ Future<DateTime> lastModified() =>
+ getDelegate(followLinks: true).lastModified();
+
+ @override
+ DateTime lastModifiedSync() =>
+ getDelegate(followLinks: true).lastModifiedSync();
+
+ @override
+ Future<dynamic> setLastModified(DateTime time) =>
+ getDelegate(followLinks: true).setLastModified(time);
+
+ @override
+ void setLastModifiedSync(DateTime time) =>
+ getDelegate(followLinks: true).setLastModifiedSync(time);
+
+ @override
+ Future<RandomAccessFile> open({
+ FileMode mode = FileMode.read,
+ }) async =>
+ _ChrootRandomAccessFile(
+ path, await getDelegate(followLinks: true).open(mode: mode));
+
+ @override
+ RandomAccessFile openSync({FileMode mode = FileMode.read}) =>
+ _ChrootRandomAccessFile(
+ path, getDelegate(followLinks: true).openSync(mode: mode));
+
+ @override
+ Stream<List<int>> openRead([int? start, int? end]) =>
+ getDelegate(followLinks: true).openRead(start, end);
+
+ @override
+ IOSink openWrite({
+ FileMode mode = FileMode.write,
+ Encoding encoding = utf8,
+ }) =>
+ getDelegate(followLinks: true).openWrite(mode: mode, encoding: encoding);
+
+ @override
+ Future<Uint8List> readAsBytes() =>
+ getDelegate(followLinks: true).readAsBytes();
+
+ @override
+ Uint8List readAsBytesSync() =>
+ getDelegate(followLinks: true).readAsBytesSync();
+
+ @override
+ Future<String> readAsString({Encoding encoding = utf8}) =>
+ getDelegate(followLinks: true).readAsString(encoding: encoding);
+
+ @override
+ String readAsStringSync({Encoding encoding = utf8}) =>
+ getDelegate(followLinks: true).readAsStringSync(encoding: encoding);
+
+ @override
+ Future<List<String>> readAsLines({Encoding encoding = utf8}) =>
+ getDelegate(followLinks: true).readAsLines(encoding: encoding);
+
+ @override
+ List<String> readAsLinesSync({Encoding encoding = utf8}) =>
+ getDelegate(followLinks: true).readAsLinesSync(encoding: encoding);
+
+ @override
+ Future<File> writeAsBytes(
+ List<int> bytes, {
+ FileMode mode = FileMode.write,
+ bool flush = false,
+ }) async =>
+ wrap(await getDelegate(followLinks: true).writeAsBytes(
+ bytes,
+ mode: mode,
+ flush: flush,
+ ));
+
+ @override
+ void writeAsBytesSync(
+ List<int> bytes, {
+ FileMode mode = FileMode.write,
+ bool flush = false,
+ }) =>
+ getDelegate(followLinks: true)
+ .writeAsBytesSync(bytes, mode: mode, flush: flush);
+
+ @override
+ Future<File> writeAsString(
+ String contents, {
+ FileMode mode = FileMode.write,
+ Encoding encoding = utf8,
+ bool flush = false,
+ }) async =>
+ wrap(await getDelegate(followLinks: true).writeAsString(
+ contents,
+ mode: mode,
+ encoding: encoding,
+ flush: flush,
+ ));
+
+ @override
+ void writeAsStringSync(
+ String contents, {
+ FileMode mode = FileMode.write,
+ Encoding encoding = utf8,
+ bool flush = false,
+ }) =>
+ getDelegate(followLinks: true).writeAsStringSync(
+ contents,
+ mode: mode,
+ encoding: encoding,
+ flush: flush,
+ );
+
+ @override
+ String toString() => "ChrootFile: '$path'";
+}
diff --git a/pkgs/file/lib/src/backends/chroot/chroot_file_system.dart b/pkgs/file/lib/src/backends/chroot/chroot_file_system.dart
new file mode 100644
index 0000000..503821f
--- /dev/null
+++ b/pkgs/file/lib/src/backends/chroot/chroot_file_system.dart
@@ -0,0 +1,391 @@
+// Copyright (c) 2017, 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.
+
+part of '../chroot.dart';
+
+const String _thisDir = '.';
+const String _parentDir = '..';
+
+/// File system that provides a view into _another_ [FileSystem] via a path.
+///
+/// This is similar in concept to the `chroot` operation in Linux operating
+/// systems. Such a modified file system cannot name or access files outside of
+/// the designated directory tree.
+///
+/// ## Example use:
+/// ```dart
+/// // Create a "file system" where the root directory is /tmp/some-dir.
+/// var fs = ChrootFileSystem(existingFileSystem, '/tmp/some-dir');
+/// ```
+///
+/// **Notes on usage**:
+///
+/// * This file system maintains its _own_ [currentDirectory], distinct from
+/// that of the underlying file system, and new instances automatically start
+/// at the root (i.e. `/`).
+///
+/// * This file system does _not_ leverage any underlying OS system calls (such
+/// as `chroot` itself), so the developer needs to take care to not assume any
+/// more of a secure environment than is actually provided. For instance, the
+/// underlying system is available via the [delegate] - which underscores this
+/// file system is intended to be a convenient abstraction, not a security
+/// measure.
+///
+/// * This file system _necessarily_ carries certain performance overhead due
+/// to the fact that symbolic links are resolved manually (not delegated).
+class ChrootFileSystem extends FileSystem {
+ /// Creates a new file system backed by [root] path in [delegate] file system.
+ ///
+ /// **NOTE**: [root] must be a _canonicalized_ path; see [p.canonicalize].
+ ChrootFileSystem(this.delegate, this.root) {
+ if (root != delegate.path.canonicalize(root)) {
+ throw ArgumentError.value(root, 'root', 'Must be canonical path');
+ }
+ _cwd = _localRoot;
+ }
+
+ /// Underlying file system.
+ final FileSystem delegate;
+
+ /// Directory in [delegate] file system that is treated as the root here.
+ final String root;
+
+ String? _systemTemp;
+
+ /// Path to the synthetic current working directory in this file system.
+ late String _cwd;
+
+ /// Gets the root path, as seen by entities in this file system.
+ String get _localRoot => delegate.path.rootPrefix(root);
+
+ @override
+ Directory directory(dynamic path) => _ChrootDirectory(this, getPath(path));
+
+ @override
+ File file(dynamic path) => _ChrootFile(this, getPath(path));
+
+ @override
+ Link link(dynamic path) => _ChrootLink(this, getPath(path));
+
+ @override
+ p.Context get path => p.Context(style: delegate.path.style, current: _cwd);
+
+ /// Gets the system temp directory. This directory will be created on-demand
+ /// in the local root of the file system. Once created, its location is fixed
+ /// for the life of the process.
+ @override
+ Directory get systemTempDirectory {
+ _systemTemp ??= directory(_localRoot).createTempSync('.tmp_').path;
+ return directory(_systemTemp)..createSync();
+ }
+
+ /// Creates a directory object pointing to the current working directory.
+ ///
+ /// **NOTE** This does _not_ proxy to the underlying file system's current
+ /// directory in any way; the state of this file system's current directory
+ /// is local to this file system.
+ @override
+ Directory get currentDirectory => directory(_cwd);
+
+ /// Sets the current working directory to the specified [path].
+ ///
+ /// **NOTE** This does _not_ proxy to the underlying file system's current
+ /// directory in any way; the state of this file system's current directory
+ /// is local to this file system.
+ /// Gets the path context for this file system given the current working dir.
+
+ @override
+ set currentDirectory(dynamic path) {
+ String value;
+ if (path is io.Directory) {
+ value = path.path;
+ } else if (path is String) {
+ value = path;
+ } else {
+ throw ArgumentError('Invalid type for "path": ${path?.runtimeType}');
+ }
+
+ value = _resolve(value, notFound: _NotFoundBehavior.throwError);
+ var realPath = _real(value, resolve: false);
+ switch (delegate.typeSync(realPath, followLinks: false)) {
+ case FileSystemEntityType.directory:
+ break;
+ case FileSystemEntityType.notFound:
+ throw common.noSuchFileOrDirectory(path as String);
+ default:
+ throw common.notADirectory(path as String);
+ }
+ assert(() {
+ var ctx = delegate.path;
+ return ctx.isAbsolute(value) && value == ctx.canonicalize(value);
+ }());
+ _cwd = value;
+ }
+
+ @override
+ Future<FileStat> stat(String path) {
+ try {
+ path = _resolve(path);
+ } on FileSystemException {
+ return Future<FileStat>.value(const _NotFoundFileStat());
+ }
+ return delegate.stat(_real(path, resolve: false));
+ }
+
+ @override
+ FileStat statSync(String path) {
+ try {
+ path = _resolve(path);
+ } on FileSystemException {
+ return const _NotFoundFileStat();
+ }
+ return delegate.statSync(_real(path, resolve: false));
+ }
+
+ @override
+ Future<bool> identical(String path1, String path2) => delegate.identical(
+ _real(_resolve(path1, followLinks: false)),
+ _real(_resolve(path2, followLinks: false)),
+ );
+
+ @override
+ bool identicalSync(String path1, String path2) => delegate.identicalSync(
+ _real(_resolve(path1, followLinks: false)),
+ _real(_resolve(path2, followLinks: false)),
+ );
+
+ @override
+ bool get isWatchSupported => false;
+
+ @override
+ Future<FileSystemEntityType> type(String path, {bool followLinks = true}) {
+ String realPath;
+ try {
+ realPath = _real(path, followLinks: followLinks);
+ } on FileSystemException {
+ return Future<FileSystemEntityType>.value(FileSystemEntityType.notFound);
+ }
+ return delegate.type(realPath, followLinks: false);
+ }
+
+ @override
+ FileSystemEntityType typeSync(String path, {bool followLinks = true}) {
+ String realPath;
+ try {
+ realPath = _real(path, followLinks: followLinks);
+ } on FileSystemException {
+ return FileSystemEntityType.notFound;
+ }
+ return delegate.typeSync(realPath, followLinks: false);
+ }
+
+ /// Converts a [realPath] in the underlying file system to a local path here.
+ ///
+ /// If [relative] is set to `true`, then the resulting path will be relative
+ /// to [currentDirectory], otherwise the resulting path will be absolute.
+ ///
+ /// An exception is thrown if the path is outside of this file system's root
+ /// directory unless [keepInJail] is true, in which case this will instead
+ /// return the path of the root of this file system.
+ String _local(
+ String realPath, {
+ bool relative = false,
+ bool keepInJail = false,
+ }) {
+ assert(path.isAbsolute(realPath));
+ if (!realPath.startsWith(root)) {
+ if (keepInJail) {
+ return _localRoot;
+ }
+ throw _ChrootJailException();
+ }
+ // TODO(tvolkert): See if _context.relative() works here
+ var result = realPath.substring(root.length);
+ if (result.isEmpty) {
+ result = _localRoot;
+ }
+ if (relative) {
+ assert(result.startsWith(_cwd));
+ result = path.relative(result, from: _cwd);
+ }
+ return result;
+ }
+
+ /// Converts [localPath] in this file system to the real path in the delegate.
+ ///
+ /// The returned path will always be absolute.
+ ///
+ /// If [resolve] is true, symbolic links will be resolved in the local file
+ /// system _before_ converting the path to the delegate file system's
+ /// namespace, and if the tail element of the path is a symbolic link, it will
+ /// only be resolved if [followLinks] is true (where-as symbolic links found
+ /// in the middle of the path will always be resolved).
+ String _real(
+ String localPath, {
+ bool resolve = true,
+ bool followLinks = false,
+ }) {
+ if (resolve) {
+ localPath = _resolve(localPath, followLinks: followLinks);
+ } else {
+ assert(path.isAbsolute(localPath));
+ }
+ return '$root$localPath';
+ }
+
+ /// Resolves symbolic links on [path] and returns the resulting resolved path.
+ ///
+ /// The return value will always be an absolute path; if [path] is relative
+ /// it will be interpreted relative to [from] (or [currentDirectory] if
+ /// `null`).
+ ///
+ /// If the tail element is a symbolic link, then the link will be resolved
+ /// only if [followLinks] is `true`. Symbolic links found in the middle of
+ /// the path will always be resolved.
+ ///
+ /// If the path cannot be resolved, and [notFound] is:
+ /// - [_NotFoundBehavior.throwError]: a [FileSystemException] is thrown.
+ /// - [_NotFoundBehavior.mkdir]: the path will be created as needed.
+ /// - [_NotFoundBehavior.allowAtTail]: a [FileSystemException] is thrown,
+ /// unless only the *tail* path element cannot be resolved, in which case
+ /// the resolution will halt at the tail element, and the partially
+ /// resolved path will be returned.
+ /// - [_NotFoundBehavior.allow] (the default), the resolution will halt and
+ /// the partially resolved path will be returned.
+ String _resolve(
+ String path, {
+ String? from,
+ bool followLinks = true,
+ _NotFoundBehavior notFound = _NotFoundBehavior.allow,
+ }) {
+ if (path.isEmpty) {
+ throw common.noSuchFileOrDirectory(path);
+ }
+
+ var ctx = this.path;
+ var root = _localRoot;
+ List<String> parts, ledger;
+ if (ctx.isAbsolute(path)) {
+ parts = ctx.split(path).sublist(1);
+ ledger = <String>[];
+ } else {
+ from ??= _cwd;
+ assert(ctx.isAbsolute(from));
+ parts = ctx.split(path);
+ ledger = ctx.split(from).sublist(1);
+ }
+
+ String getCurrentPath() => root + ctx.joinAll(ledger);
+ var breadcrumbs = <String>{};
+ while (parts.isNotEmpty) {
+ var segment = parts.removeAt(0);
+ if (segment == _thisDir) {
+ continue;
+ } else if (segment == _parentDir) {
+ if (ledger.isNotEmpty) {
+ ledger.removeLast();
+ }
+ continue;
+ }
+
+ ledger.add(segment);
+ var currentPath = getCurrentPath();
+ var realPath = _real(currentPath, resolve: false);
+
+ switch (delegate.typeSync(realPath, followLinks: false)) {
+ case FileSystemEntityType.directory:
+ breadcrumbs.clear();
+ break;
+ case FileSystemEntityType.file:
+ breadcrumbs.clear();
+ if (parts.isNotEmpty) {
+ throw common.notADirectory(currentPath);
+ }
+ break;
+ case FileSystemEntityType.notFound:
+ String returnEarly() {
+ ledger.addAll(parts);
+ return getCurrentPath();
+ }
+
+ switch (notFound) {
+ case _NotFoundBehavior.mkdir:
+ if (parts.isNotEmpty) {
+ delegate.directory(realPath).createSync();
+ }
+ break;
+ case _NotFoundBehavior.allow:
+ return returnEarly();
+ case _NotFoundBehavior.allowAtTail:
+ if (parts.isEmpty) {
+ return returnEarly();
+ }
+ throw common.noSuchFileOrDirectory(path);
+ case _NotFoundBehavior.throwError:
+ throw common.noSuchFileOrDirectory(path);
+ }
+ break;
+ case FileSystemEntityType.link:
+ if (parts.isEmpty && !followLinks) {
+ break;
+ }
+ if (!breadcrumbs.add(currentPath)) {
+ throw common.tooManyLevelsOfSymbolicLinks(path);
+ }
+ var target = delegate.link(realPath).targetSync();
+ if (ctx.isAbsolute(target)) {
+ ledger.clear();
+ parts.insertAll(0, ctx.split(target).sublist(1));
+ } else {
+ ledger.removeLast();
+ parts.insertAll(0, ctx.split(target));
+ }
+ break;
+ default:
+ throw AssertionError();
+ }
+ }
+
+ return getCurrentPath();
+ }
+}
+
+/// Thrown when a path is encountered that exists outside of the root path.
+class _ChrootJailException implements IOException {}
+
+/// What to do when `NOT_FOUND` paths are encountered while resolving.
+enum _NotFoundBehavior {
+ allow,
+ allowAtTail,
+ throwError,
+ mkdir,
+}
+
+/// A [FileStat] representing a `NOT_FOUND` entity.
+class _NotFoundFileStat implements FileStat {
+ const _NotFoundFileStat();
+
+ static final DateTime _empty = DateTime(0);
+
+ @override
+ DateTime get changed => _empty;
+
+ @override
+ DateTime get modified => _empty;
+
+ @override
+ DateTime get accessed => _empty;
+
+ @override
+ FileSystemEntityType get type => FileSystemEntityType.notFound;
+
+ @override
+ int get mode => 0;
+
+ @override
+ int get size => -1;
+
+ @override
+ String modeString() => '---------';
+}
diff --git a/pkgs/file/lib/src/backends/chroot/chroot_file_system_entity.dart b/pkgs/file/lib/src/backends/chroot/chroot_file_system_entity.dart
new file mode 100644
index 0000000..18e37cd
--- /dev/null
+++ b/pkgs/file/lib/src/backends/chroot/chroot_file_system_entity.dart
@@ -0,0 +1,169 @@
+// Copyright (c) 2017, 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.
+
+part of '../chroot.dart';
+
+abstract class _ChrootFileSystemEntity<T extends FileSystemEntity,
+ D extends io.FileSystemEntity> extends ForwardingFileSystemEntity<T, D> {
+ _ChrootFileSystemEntity(this.fileSystem, this.path);
+
+ @override
+ final ChrootFileSystem fileSystem;
+
+ @override
+ final String path;
+
+ @override
+ String get dirname => fileSystem.path.dirname(path);
+
+ @override
+ String get basename => fileSystem.path.basename(path);
+
+ @override
+ D get delegate => getDelegate();
+
+ /// Gets the delegate file system entity in the underlying file system that
+ /// corresponds to this entity's local file system path.
+ ///
+ /// If [followLinks] is true and this entity's path references a symbolic
+ /// link, then the path of the delegate entity will reference the ultimate
+ /// target of that symbolic link. Symbolic links in the middle of the path
+ /// will always be resolved in the delegate entity's path.
+ D getDelegate({bool followLinks = false}) =>
+ _rawDelegate(fileSystem._real(path, followLinks: followLinks));
+
+ /// Returns the expected type of this entity, which may differ from the type
+ /// of the entity that's found at the path specified by this entity.
+ FileSystemEntityType get expectedType;
+
+ /// Returns a delegate entity at the specified [realPath] (the path in the
+ /// underlying file system).
+ D _rawDelegate(String realPath);
+
+ /// Gets the path of this entity as an absolute path (unchanged if the
+ /// entity already specifies an absolute path).
+ String get _absolutePath => fileSystem.path.absolute(path);
+
+ /// Tells whether this entity's path references a symbolic link.
+ bool get _isLink =>
+ fileSystem.typeSync(path, followLinks: false) ==
+ FileSystemEntityType.link;
+
+ @override
+ Directory wrapDirectory(io.Directory delegate) =>
+ _ChrootDirectory.wrapped(fileSystem, delegate as Directory,
+ relative: !isAbsolute);
+
+ @override
+ File wrapFile(io.File delegate) =>
+ _ChrootFile.wrapped(fileSystem, delegate, relative: !isAbsolute);
+
+ @override
+ Link wrapLink(io.Link delegate) =>
+ _ChrootLink.wrapped(fileSystem, delegate, relative: !isAbsolute);
+
+ @override
+ Uri get uri => Uri.file(path);
+
+ @override
+ Future<bool> exists() => getDelegate(followLinks: true).exists();
+
+ @override
+ bool existsSync() => getDelegate(followLinks: true).existsSync();
+
+ @override
+ Future<String> resolveSymbolicLinks() async => resolveSymbolicLinksSync();
+
+ @override
+ String resolveSymbolicLinksSync() =>
+ fileSystem._resolve(path, notFound: _NotFoundBehavior.throwError);
+
+ @override
+ Future<FileStat> stat() {
+ D delegate;
+ try {
+ delegate = getDelegate(followLinks: true);
+ } on FileSystemException {
+ return Future<FileStat>.value(const _NotFoundFileStat());
+ }
+ return delegate.stat();
+ }
+
+ @override
+ FileStat statSync() {
+ D delegate;
+ try {
+ delegate = getDelegate(followLinks: true);
+ } on FileSystemException {
+ return const _NotFoundFileStat();
+ }
+ return delegate.statSync();
+ }
+
+ @override
+ Future<T> delete({bool recursive = false}) async {
+ var path = fileSystem._resolve(this.path,
+ followLinks: false, notFound: _NotFoundBehavior.throwError);
+
+ String real(String path) => fileSystem._real(path, resolve: false);
+ Future<FileSystemEntityType> type(String path) =>
+ fileSystem.delegate.type(real(path), followLinks: false);
+
+ if (await type(path) == FileSystemEntityType.link) {
+ if (expectedType == FileSystemEntityType.link) {
+ await fileSystem.delegate.link(real(path)).delete();
+ } else {
+ var resolvedPath = fileSystem._resolve(p.basename(path),
+ from: p.dirname(path), notFound: _NotFoundBehavior.allowAtTail);
+ if (!recursive && await type(resolvedPath) != expectedType) {
+ throw expectedType == FileSystemEntityType.file
+ ? common.isADirectory(path)
+ : common.notADirectory(path);
+ }
+ await fileSystem.delegate.link(real(path)).delete();
+ }
+ return this as T;
+ } else {
+ return wrap(
+ await _rawDelegate(real(path)).delete(recursive: recursive) as D);
+ }
+ }
+
+ @override
+ void deleteSync({bool recursive = false}) {
+ var path = fileSystem._resolve(this.path,
+ followLinks: false, notFound: _NotFoundBehavior.throwError);
+
+ String real(String path) => fileSystem._real(path, resolve: false);
+ FileSystemEntityType type(String path) =>
+ fileSystem.delegate.typeSync(real(path), followLinks: false);
+
+ if (type(path) == FileSystemEntityType.link) {
+ if (expectedType == FileSystemEntityType.link) {
+ fileSystem.delegate.link(real(path)).deleteSync();
+ } else {
+ var resolvedPath = fileSystem._resolve(p.basename(path),
+ from: p.dirname(path), notFound: _NotFoundBehavior.allowAtTail);
+ if (!recursive && type(resolvedPath) != expectedType) {
+ throw expectedType == FileSystemEntityType.file
+ ? common.isADirectory(path)
+ : common.notADirectory(path);
+ }
+ fileSystem.delegate.link(real(path)).deleteSync();
+ }
+ } else {
+ _rawDelegate(real(path)).deleteSync(recursive: recursive);
+ }
+ }
+
+ @override
+ Stream<FileSystemEvent> watch({
+ int events = FileSystemEvent.all,
+ bool recursive = false,
+ }) =>
+ throw UnsupportedError('watch is not supported on ChrootFileSystem');
+
+ @override
+ bool get isAbsolute => fileSystem.path.isAbsolute(path);
+}
diff --git a/pkgs/file/lib/src/backends/chroot/chroot_link.dart b/pkgs/file/lib/src/backends/chroot/chroot_link.dart
new file mode 100644
index 0000000..1620df9
--- /dev/null
+++ b/pkgs/file/lib/src/backends/chroot/chroot_link.dart
@@ -0,0 +1,47 @@
+// Copyright (c) 2017, 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.
+
+part of '../chroot.dart';
+
+class _ChrootLink extends _ChrootFileSystemEntity<Link, io.Link>
+ with ForwardingLink {
+ _ChrootLink(super.fs, super.path);
+
+ factory _ChrootLink.wrapped(
+ ChrootFileSystem fs,
+ io.Link delegate, {
+ bool relative = false,
+ }) {
+ var localPath = fs._local(delegate.path, relative: relative);
+ return _ChrootLink(fs, localPath);
+ }
+
+ @override
+ Future<bool> exists() => delegate.exists();
+
+ @override
+ bool existsSync() => delegate.existsSync();
+
+ @override
+ Future<Link> rename(String newPath) async {
+ return wrap(await delegate.rename(fileSystem._real(newPath)));
+ }
+
+ @override
+ Link renameSync(String newPath) {
+ return wrap(delegate.renameSync(fileSystem._real(newPath)));
+ }
+
+ @override
+ FileSystemEntityType get expectedType => FileSystemEntityType.link;
+
+ @override
+ io.Link _rawDelegate(String path) => fileSystem.delegate.link(path);
+
+ @override
+ Link get absolute => _ChrootLink(fileSystem, _absolutePath);
+
+ @override
+ String toString() => "ChrootLink: '$path'";
+}
diff --git a/pkgs/file/lib/src/backends/chroot/chroot_random_access_file.dart b/pkgs/file/lib/src/backends/chroot/chroot_random_access_file.dart
new file mode 100644
index 0000000..10bbd70
--- /dev/null
+++ b/pkgs/file/lib/src/backends/chroot/chroot_random_access_file.dart
@@ -0,0 +1,15 @@
+// Copyright (c) 2020, 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.
+
+part of '../chroot.dart';
+
+class _ChrootRandomAccessFile with ForwardingRandomAccessFile {
+ _ChrootRandomAccessFile(this.path, this.delegate);
+
+ @override
+ final io.RandomAccessFile delegate;
+
+ @override
+ final String path;
+}
diff --git a/pkgs/file/lib/src/backends/local.dart b/pkgs/file/lib/src/backends/local.dart
new file mode 100644
index 0000000..1e92241
--- /dev/null
+++ b/pkgs/file/lib/src/backends/local.dart
@@ -0,0 +1,5 @@
+// Copyright (c) 2017, 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 'local/local_file_system.dart' show LocalFileSystem;
diff --git a/pkgs/file/lib/src/backends/local/local_directory.dart b/pkgs/file/lib/src/backends/local/local_directory.dart
new file mode 100644
index 0000000..3e1db61
--- /dev/null
+++ b/pkgs/file/lib/src/backends/local/local_directory.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2017, 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 '../../common.dart' as common;
+import '../../forwarding.dart';
+import '../../interface.dart';
+import '../../io.dart' as io;
+import 'local_file_system_entity.dart';
+
+/// [Directory] implementation that forwards all calls to `dart:io`.
+class LocalDirectory extends LocalFileSystemEntity<LocalDirectory, io.Directory>
+ with ForwardingDirectory<LocalDirectory>, common.DirectoryAddOnsMixin {
+ /// Instantiates a new [LocalDirectory] tied to the specified file system
+ /// and delegating to the specified [delegate].
+ LocalDirectory(super.fs, super.delegate);
+
+ @override
+ String toString() => "LocalDirectory: '$path'";
+}
diff --git a/pkgs/file/lib/src/backends/local/local_file.dart b/pkgs/file/lib/src/backends/local/local_file.dart
new file mode 100644
index 0000000..a4bc106
--- /dev/null
+++ b/pkgs/file/lib/src/backends/local/local_file.dart
@@ -0,0 +1,19 @@
+// Copyright (c) 2017, 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 '../../forwarding.dart';
+import '../../interface.dart';
+import '../../io.dart' as io;
+import 'local_file_system_entity.dart';
+
+/// [File] implementation that forwards all calls to `dart:io`.
+class LocalFile extends LocalFileSystemEntity<File, io.File>
+ with ForwardingFile {
+ /// Instantiates a new [LocalFile] tied to the specified file system
+ /// and delegating to the specified [delegate].
+ LocalFile(super.fs, super.delegate);
+
+ @override
+ String toString() => "LocalFile: '$path'";
+}
diff --git a/pkgs/file/lib/src/backends/local/local_file_system.dart b/pkgs/file/lib/src/backends/local/local_file_system.dart
new file mode 100644
index 0000000..7541c37
--- /dev/null
+++ b/pkgs/file/lib/src/backends/local/local_file_system.dart
@@ -0,0 +1,72 @@
+// Copyright (c) 2017, 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:path/path.dart' as p;
+
+import '../../interface.dart';
+import '../../io.dart' as io;
+import 'local_directory.dart';
+import 'local_file.dart';
+import 'local_link.dart';
+
+/// A wrapper implementation around `dart:io`'s implementation.
+///
+/// Since this implementation of the [FileSystem] interface delegates to
+/// `dart:io`, is is not suitable for use in the browser.
+class LocalFileSystem extends FileSystem {
+ /// Creates a new `LocalFileSystem`.
+ const LocalFileSystem();
+
+ @override
+ Directory directory(dynamic path) =>
+ LocalDirectory(this, io.Directory(getPath(path)));
+
+ @override
+ File file(dynamic path) => LocalFile(this, io.File(getPath(path)));
+
+ @override
+ Link link(dynamic path) => LocalLink(this, io.Link(getPath(path)));
+
+ @override
+ p.Context get path => p.Context();
+
+ /// Gets the directory provided by the operating system for creating temporary
+ /// files and directories in. The location of the system temp directory is
+ /// platform-dependent, and may be set by an environment variable.
+ @override
+ Directory get systemTempDirectory =>
+ LocalDirectory(this, io.Directory.systemTemp);
+
+ @override
+ Directory get currentDirectory => directory(io.Directory.current.path);
+
+ @override
+ set currentDirectory(dynamic path) => io.Directory.current = path;
+
+ @override
+ Future<io.FileStat> stat(String path) => io.FileStat.stat(path);
+
+ @override
+ io.FileStat statSync(String path) => io.FileStat.statSync(path);
+
+ @override
+ Future<bool> identical(String path1, String path2) =>
+ io.FileSystemEntity.identical(path1, path2);
+
+ @override
+ bool identicalSync(String path1, String path2) =>
+ io.FileSystemEntity.identicalSync(path1, path2);
+
+ @override
+ bool get isWatchSupported => io.FileSystemEntity.isWatchSupported;
+
+ @override
+ Future<io.FileSystemEntityType> type(String path,
+ {bool followLinks = true}) =>
+ io.FileSystemEntity.type(path, followLinks: followLinks);
+
+ @override
+ io.FileSystemEntityType typeSync(String path, {bool followLinks = true}) =>
+ io.FileSystemEntity.typeSync(path, followLinks: followLinks);
+}
diff --git a/pkgs/file/lib/src/backends/local/local_file_system_entity.dart b/pkgs/file/lib/src/backends/local/local_file_system_entity.dart
new file mode 100644
index 0000000..d0da559
--- /dev/null
+++ b/pkgs/file/lib/src/backends/local/local_file_system_entity.dart
@@ -0,0 +1,40 @@
+// Copyright (c) 2017, 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 '../../forwarding.dart';
+import '../../interface.dart';
+import '../../io.dart' as io;
+import 'local_directory.dart';
+import 'local_file.dart';
+import 'local_link.dart';
+
+/// [FileSystemEntity] implementation that forwards all calls to `dart:io`.
+abstract class LocalFileSystemEntity<T extends FileSystemEntity,
+ D extends io.FileSystemEntity> extends ForwardingFileSystemEntity<T, D> {
+ /// Instantiates a new [LocalFileSystemEntity] tied to the specified file
+ /// system and delegating to the specified [delegate].
+ LocalFileSystemEntity(this.fileSystem, this.delegate);
+
+ @override
+ final FileSystem fileSystem;
+
+ @override
+ final D delegate;
+
+ @override
+ String get dirname => fileSystem.path.dirname(path);
+
+ @override
+ String get basename => fileSystem.path.basename(path);
+
+ @override
+ Directory wrapDirectory(io.Directory delegate) =>
+ LocalDirectory(fileSystem, delegate);
+
+ @override
+ File wrapFile(io.File delegate) => LocalFile(fileSystem, delegate);
+
+ @override
+ Link wrapLink(io.Link delegate) => LocalLink(fileSystem, delegate);
+}
diff --git a/pkgs/file/lib/src/backends/local/local_link.dart b/pkgs/file/lib/src/backends/local/local_link.dart
new file mode 100644
index 0000000..2ce4791
--- /dev/null
+++ b/pkgs/file/lib/src/backends/local/local_link.dart
@@ -0,0 +1,19 @@
+// Copyright (c) 2017, 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 '../../forwarding.dart';
+import '../../interface.dart';
+import '../../io.dart' as io;
+import 'local_file_system_entity.dart';
+
+/// [Link] implementation that forwards all calls to `dart:io`.
+class LocalLink extends LocalFileSystemEntity<Link, io.Link>
+ with ForwardingLink {
+ /// Instantiates a new [LocalLink] tied to the specified file system
+ /// and delegating to the specified [delegate].
+ LocalLink(super.fs, super.delegate);
+
+ @override
+ String toString() => "LocalLink: '$path'";
+}
diff --git a/pkgs/file/lib/src/backends/memory.dart b/pkgs/file/lib/src/backends/memory.dart
new file mode 100644
index 0000000..428bc54
--- /dev/null
+++ b/pkgs/file/lib/src/backends/memory.dart
@@ -0,0 +1,6 @@
+// Copyright (c) 2017, 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 'memory/memory_file_system.dart' show MemoryFileSystem;
+export 'memory/style.dart' show FileSystemStyle, StyleableFileSystem;
diff --git a/pkgs/file/lib/src/backends/memory/clock.dart b/pkgs/file/lib/src/backends/memory/clock.dart
new file mode 100644
index 0000000..57c1b72
--- /dev/null
+++ b/pkgs/file/lib/src/backends/memory/clock.dart
@@ -0,0 +1,54 @@
+// 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: comment_references
+
+/// Interface describing clocks used by the [MemoryFileSystem].
+///
+/// The [MemoryFileSystem] uses a clock to determine the modification times of
+/// files that are created in that file system.
+abstract class Clock {
+ /// Abstract const constructor. This constructor enables subclasses to provide
+ /// const constructors so that they can be used in const expressions.
+ const Clock();
+
+ /// A real-time clock.
+ ///
+ /// Uses [DateTime.now] to reflect the actual time reported by the operating
+ /// system.
+ const factory Clock.realTime() = _RealtimeClock;
+
+ /// A monotonically-increasing test clock.
+ ///
+ /// Each time [now] is called, the time increases by one minute.
+ ///
+ /// The `start` argument can be used to set the seed time for the clock.
+ /// The first value will be that time plus one minute.
+ /// By default, `start` is midnight on the first of January, 2000.
+ factory Clock.monotonicTest() = _MonotonicTestClock;
+
+ /// Returns the value of the clock.
+ DateTime get now;
+}
+
+class _RealtimeClock extends Clock {
+ const _RealtimeClock();
+
+ @override
+ DateTime get now => DateTime.now();
+}
+
+class _MonotonicTestClock extends Clock {
+ _MonotonicTestClock({
+ DateTime? start,
+ }) : _current = start ?? DateTime(2000);
+
+ DateTime _current;
+
+ @override
+ DateTime get now {
+ _current = _current.add(const Duration(minutes: 1));
+ return _current;
+ }
+}
diff --git a/pkgs/file/lib/src/backends/memory/common.dart b/pkgs/file/lib/src/backends/memory/common.dart
new file mode 100644
index 0000000..eb4ca43
--- /dev/null
+++ b/pkgs/file/lib/src/backends/memory/common.dart
@@ -0,0 +1,15 @@
+// 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.
+
+import '../../common.dart' as common;
+
+/// Generates a path to use in error messages.
+typedef PathGenerator = dynamic Function();
+
+/// Throws a `FileSystemException` if [object] is null.
+void checkExists(Object? object, PathGenerator path) {
+ if (object == null) {
+ throw common.noSuchFileOrDirectory(path() as String);
+ }
+}
diff --git a/pkgs/file/lib/src/backends/memory/memory_directory.dart b/pkgs/file/lib/src/backends/memory/memory_directory.dart
new file mode 100644
index 0000000..e73b967
--- /dev/null
+++ b/pkgs/file/lib/src/backends/memory/memory_directory.dart
@@ -0,0 +1,183 @@
+// Copyright (c) 2017, 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:meta/meta.dart';
+
+import '../../common.dart' as common;
+import '../../interface.dart';
+import '../../io.dart' as io;
+import 'common.dart';
+import 'memory_file.dart';
+import 'memory_file_system_entity.dart';
+import 'memory_link.dart';
+import 'node.dart';
+import 'operations.dart';
+import 'style.dart';
+import 'utils.dart' as utils;
+
+// Tracks a unique name for system temp directories, per filesystem
+// instance.
+final Expando<int> _systemTempCounter = Expando<int>();
+
+/// Internal implementation of [Directory].
+class MemoryDirectory extends MemoryFileSystemEntity
+ with common.DirectoryAddOnsMixin
+ implements Directory {
+ /// Instantiates a new [MemoryDirectory].
+ MemoryDirectory(super.fileSystem, super.path);
+
+ @override
+ io.FileSystemEntityType get expectedType => io.FileSystemEntityType.directory;
+
+ @override
+ Uri get uri {
+ return Uri.directory(path,
+ windows: fileSystem.style == FileSystemStyle.windows);
+ }
+
+ @override
+ bool existsSync() {
+ fileSystem.opHandle.call(path, FileSystemOp.exists);
+ return backingOrNull?.stat.type == expectedType;
+ }
+
+ @override
+ Future<Directory> create({bool recursive = false}) async {
+ createSync(recursive: recursive);
+ return this;
+ }
+
+ @override
+ void createSync({bool recursive = false}) {
+ fileSystem.opHandle(path, FileSystemOp.create);
+ var node = internalCreateSync(
+ followTailLink: true,
+ visitLinks: true,
+ createChild: (DirectoryNode parent, bool isFinalSegment) {
+ if (recursive || isFinalSegment) {
+ return DirectoryNode(parent);
+ }
+ return null;
+ },
+ );
+ if (node?.type != expectedType) {
+ // There was an existing non-directory node at this object's path
+ throw common.notADirectory(path);
+ }
+ }
+
+ @override
+ Future<Directory> createTemp([String? prefix]) async =>
+ createTempSync(prefix);
+
+ @override
+ Directory createTempSync([String? prefix]) {
+ prefix = '${prefix ?? ''}rand';
+ var fullPath = fileSystem.path.join(path, prefix);
+ var dirname = fileSystem.path.dirname(fullPath);
+ var basename = fileSystem.path.basename(fullPath);
+ var node = fileSystem.findNode(dirname) as DirectoryNode?;
+ checkExists(node, () => dirname);
+ utils.checkIsDir(node!, () => dirname);
+ var tempCounter = _systemTempCounter[fileSystem] ?? 0;
+ String name() => '$basename$tempCounter';
+ while (node.children.containsKey(name())) {
+ tempCounter++;
+ }
+ _systemTempCounter[fileSystem] = tempCounter;
+ var tempDir = DirectoryNode(node);
+ node.children[name()] = tempDir;
+ return MemoryDirectory(fileSystem, fileSystem.path.join(dirname, name()))
+ ..createSync();
+ }
+
+ @override
+ Future<Directory> rename(String newPath) async => renameSync(newPath);
+
+ @override
+ Directory renameSync(String newPath) => internalRenameSync<DirectoryNode>(
+ newPath,
+ validateOverwriteExistingEntity: (DirectoryNode existingNode) {
+ if (existingNode.children.isNotEmpty) {
+ throw common.directoryNotEmpty(newPath);
+ }
+ },
+ ) as Directory;
+
+ @override
+ Directory get parent =>
+ (backingOrNull?.isRoot ?? false) ? this : super.parent;
+
+ @override
+ Directory get absolute => super.absolute as Directory;
+
+ @override
+ Stream<FileSystemEntity> list({
+ bool recursive = false,
+ bool followLinks = true,
+ }) =>
+ Stream<FileSystemEntity>.fromIterable(listSync(
+ recursive: recursive,
+ followLinks: followLinks,
+ ));
+
+ @override
+ List<FileSystemEntity> listSync({
+ bool recursive = false,
+ bool followLinks = true,
+ }) {
+ var node = backing as DirectoryNode;
+ var listing = <FileSystemEntity>[];
+ var tasks = <_PendingListTask>[
+ _PendingListTask(
+ node,
+ path.endsWith(fileSystem.path.separator)
+ ? path.substring(0, path.length - 1)
+ : path,
+ <LinkNode>{},
+ ),
+ ];
+ while (tasks.isNotEmpty) {
+ var task = tasks.removeLast();
+ task.dir.children.forEach((String name, Node child) {
+ var breadcrumbs = Set<LinkNode>.from(task.breadcrumbs);
+ var childPath = fileSystem.path.join(task.path, name);
+ while (followLinks &&
+ utils.isLink(child) &&
+ breadcrumbs.add(child as LinkNode)) {
+ var referent = child.referentOrNull;
+ if (referent != null) {
+ child = referent;
+ }
+ }
+ if (utils.isDirectory(child)) {
+ listing.add(MemoryDirectory(fileSystem, childPath));
+ if (recursive) {
+ tasks.add(_PendingListTask(
+ child as DirectoryNode, childPath, breadcrumbs));
+ }
+ } else if (utils.isLink(child)) {
+ listing.add(MemoryLink(fileSystem, childPath));
+ } else if (utils.isFile(child)) {
+ listing.add(MemoryFile(fileSystem, childPath));
+ }
+ });
+ }
+ return listing;
+ }
+
+ @override
+ @protected
+ Directory clone(String path) => MemoryDirectory(fileSystem, path);
+
+ @override
+ String toString() => "MemoryDirectory: '$path'";
+}
+
+class _PendingListTask {
+ _PendingListTask(this.dir, this.path, this.breadcrumbs);
+ final DirectoryNode dir;
+ final String path;
+ final Set<LinkNode> breadcrumbs;
+}
diff --git a/pkgs/file/lib/src/backends/memory/memory_file.dart b/pkgs/file/lib/src/backends/memory/memory_file.dart
new file mode 100644
index 0000000..1a8f5f9
--- /dev/null
+++ b/pkgs/file/lib/src/backends/memory/memory_file.dart
@@ -0,0 +1,468 @@
+// Copyright (c) 2017, 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:math' as math show min;
+import 'dart:typed_data';
+
+import 'package:meta/meta.dart';
+
+import '../../common.dart' as common;
+import '../../interface.dart';
+import '../../io.dart' as io;
+import 'common.dart';
+import 'memory_file_system_entity.dart';
+import 'memory_random_access_file.dart';
+import 'node.dart';
+import 'operations.dart';
+import 'utils.dart' as utils;
+
+/// Internal implementation of [File].
+class MemoryFile extends MemoryFileSystemEntity implements File {
+ /// Instantiates a new [MemoryFile].
+ const MemoryFile(super.fileSystem, super.path);
+
+ FileNode get _resolvedBackingOrCreate {
+ var node = backingOrNull;
+ if (node == null) {
+ node = _doCreate();
+ } else {
+ node = utils.isLink(node)
+ ? utils.resolveLinks(node as LinkNode, () => path)
+ : node;
+ utils.checkType(expectedType, node.type, () => path);
+ }
+ return node as FileNode;
+ }
+
+ @override
+ io.FileSystemEntityType get expectedType => io.FileSystemEntityType.file;
+
+ @override
+ bool existsSync() {
+ fileSystem.opHandle.call(path, FileSystemOp.exists);
+ return backingOrNull?.stat.type == expectedType;
+ }
+
+ @override
+ Future<File> create({bool recursive = false, bool exclusive = false}) async {
+ createSync(recursive: recursive, exclusive: exclusive);
+ return this;
+ }
+
+ // TODO(dartbug.com/49647): Pass `exclusive` through after it lands.
+ @override
+ void createSync({bool recursive = false, bool exclusive = false}) {
+ fileSystem.opHandle(path, FileSystemOp.create);
+ _doCreate(recursive: recursive /*, exclusive: exclusive*/);
+ }
+
+ Node? _doCreate({bool recursive = false}) {
+ var node = internalCreateSync(
+ followTailLink: true,
+ createChild: (DirectoryNode parent, bool isFinalSegment) {
+ if (isFinalSegment) {
+ return FileNode(parent);
+ } else if (recursive) {
+ return DirectoryNode(parent);
+ }
+ return null;
+ },
+ );
+ if (node?.type != expectedType) {
+ // There was an existing non-file entity at this object's path
+ assert(node?.type == FileSystemEntityType.directory);
+ throw common.isADirectory(path);
+ }
+ return node;
+ }
+
+ @override
+ Future<File> rename(String newPath) async => renameSync(newPath);
+
+ @override
+ File renameSync(String newPath) => internalRenameSync(
+ newPath,
+ followTailLink: true,
+ checkType: (Node node) {
+ var actualType = node.stat.type;
+ if (actualType != expectedType) {
+ throw actualType == FileSystemEntityType.notFound
+ ? common.noSuchFileOrDirectory(path)
+ : common.isADirectory(path);
+ }
+ },
+ ) as File;
+
+ @override
+ Future<File> copy(String newPath) async => copySync(newPath);
+
+ @override
+ File copySync(String newPath) {
+ fileSystem.opHandle(path, FileSystemOp.copy);
+ var sourceNode = resolvedBacking as FileNode;
+ fileSystem.findNode(
+ newPath,
+ segmentVisitor: (
+ DirectoryNode parent,
+ String childName,
+ Node? child,
+ int currentSegment,
+ int finalSegment,
+ ) {
+ if (currentSegment == finalSegment) {
+ if (child != null) {
+ if (utils.isLink(child)) {
+ var ledger = <String>[];
+ child = utils.resolveLinks(child as LinkNode, () => newPath,
+ ledger: ledger);
+ checkExists(child, () => newPath);
+ parent = child.parent;
+ childName = ledger.last;
+ assert(parent.children.containsKey(childName));
+ }
+ utils.checkType(expectedType, child.type, () => newPath);
+ parent.children.remove(childName);
+ }
+ var newNode = FileNode(parent);
+ newNode.copyFrom(sourceNode);
+ parent.children[childName] = newNode;
+ }
+ return child;
+ },
+ );
+ return clone(newPath);
+ }
+
+ @override
+ Future<int> length() async => lengthSync();
+
+ @override
+ int lengthSync() => (resolvedBacking as FileNode).size;
+
+ @override
+ File get absolute => super.absolute as File;
+
+ @override
+ Future<DateTime> lastAccessed() async => lastAccessedSync();
+
+ @override
+ DateTime lastAccessedSync() => (resolvedBacking as FileNode).stat.accessed;
+
+ @override
+ Future<dynamic> setLastAccessed(DateTime time) async =>
+ setLastAccessedSync(time);
+
+ @override
+ void setLastAccessedSync(DateTime time) {
+ var node = resolvedBacking as FileNode;
+ node.accessed = time.millisecondsSinceEpoch;
+ }
+
+ @override
+ Future<DateTime> lastModified() async => lastModifiedSync();
+
+ @override
+ DateTime lastModifiedSync() => (resolvedBacking as FileNode).stat.modified;
+
+ @override
+ Future<dynamic> setLastModified(DateTime time) async =>
+ setLastModifiedSync(time);
+
+ @override
+ void setLastModifiedSync(DateTime time) {
+ var node = resolvedBacking as FileNode;
+ node.modified = time.millisecondsSinceEpoch;
+ }
+
+ @override
+ Future<io.RandomAccessFile> open(
+ {io.FileMode mode = io.FileMode.read}) async =>
+ openSync(mode: mode);
+
+ @override
+ io.RandomAccessFile openSync({io.FileMode mode = io.FileMode.read}) {
+ fileSystem.opHandle(path, FileSystemOp.open);
+ if (utils.isWriteMode(mode) && !existsSync()) {
+ // [resolvedBacking] requires that the file already exists, so we must
+ // create it here first.
+ createSync();
+ }
+
+ return MemoryRandomAccessFile(path, resolvedBacking as FileNode, mode);
+ }
+
+ @override
+ Stream<List<int>> openRead([int? start, int? end]) {
+ fileSystem.opHandle(path, FileSystemOp.open);
+ try {
+ var node = resolvedBacking as FileNode;
+ var content = node.content;
+ if (start != null) {
+ content = end == null
+ ? content.sublist(start)
+ : content.sublist(start, math.min(end, content.length));
+ }
+ return Stream.value(content);
+ } catch (e) {
+ return Stream.error(e);
+ }
+ }
+
+ @override
+ io.IOSink openWrite({
+ io.FileMode mode = io.FileMode.write,
+ Encoding encoding = utf8,
+ }) {
+ fileSystem.opHandle(path, FileSystemOp.open);
+ if (!utils.isWriteMode(mode)) {
+ throw ArgumentError.value(mode, 'mode',
+ 'Must be either WRITE, APPEND, WRITE_ONLY, or WRITE_ONLY_APPEND');
+ }
+ return _FileSink.fromFile(this, mode, encoding);
+ }
+
+ @override
+ Future<Uint8List> readAsBytes() async => readAsBytesSync();
+
+ @override
+ Uint8List readAsBytesSync() {
+ fileSystem.opHandle(path, FileSystemOp.read);
+ return Uint8List.fromList((resolvedBacking as FileNode).content);
+ }
+
+ @override
+ Future<String> readAsString({Encoding encoding = utf8}) async =>
+ readAsStringSync(encoding: encoding);
+
+ @override
+ String readAsStringSync({Encoding encoding = utf8}) {
+ try {
+ return encoding.decode(readAsBytesSync());
+ } on FormatException catch (err) {
+ throw FileSystemException(err.message, path);
+ }
+ }
+
+ @override
+ Future<List<String>> readAsLines({Encoding encoding = utf8}) async =>
+ readAsLinesSync(encoding: encoding);
+
+ @override
+ List<String> readAsLinesSync({Encoding encoding = utf8}) {
+ var str = readAsStringSync(encoding: encoding);
+
+ if (str.isEmpty) {
+ return <String>[];
+ }
+
+ final lines = str.split('\n');
+ if (str.endsWith('\n')) {
+ // A final newline should not create an additional line.
+ lines.removeLast();
+ }
+
+ return lines;
+ }
+
+ @override
+ Future<File> writeAsBytes(
+ List<int> bytes, {
+ io.FileMode mode = io.FileMode.write,
+ bool flush = false,
+ }) async {
+ writeAsBytesSync(bytes, mode: mode, flush: flush);
+ return this;
+ }
+
+ @override
+ void writeAsBytesSync(
+ List<int> bytes, {
+ io.FileMode mode = io.FileMode.write,
+ bool flush = false,
+ }) {
+ if (!utils.isWriteMode(mode)) {
+ throw common.badFileDescriptor(path);
+ }
+ var node = _resolvedBackingOrCreate;
+ _truncateIfNecessary(node, mode);
+ fileSystem.opHandle(path, FileSystemOp.write);
+ node.write(bytes);
+ node.touch();
+ }
+
+ @override
+ Future<File> writeAsString(
+ String contents, {
+ io.FileMode mode = io.FileMode.write,
+ Encoding encoding = utf8,
+ bool flush = false,
+ }) async {
+ writeAsStringSync(contents, mode: mode, encoding: encoding, flush: flush);
+ return this;
+ }
+
+ @override
+ void writeAsStringSync(
+ String contents, {
+ io.FileMode mode = io.FileMode.write,
+ Encoding encoding = utf8,
+ bool flush = false,
+ }) =>
+ writeAsBytesSync(encoding.encode(contents), mode: mode, flush: flush);
+
+ @override
+ @protected
+ File clone(String path) => MemoryFile(fileSystem, path);
+
+ void _truncateIfNecessary(FileNode node, io.FileMode mode) {
+ if (mode == io.FileMode.write || mode == io.FileMode.writeOnly) {
+ node.clear();
+ }
+ }
+
+ @override
+ String toString() => "MemoryFile: '$path'";
+}
+
+/// Implementation of an [io.IOSink] that's backed by a [FileNode].
+class _FileSink implements io.IOSink {
+ factory _FileSink.fromFile(
+ MemoryFile file,
+ io.FileMode mode,
+ Encoding encoding,
+ ) {
+ late FileNode node;
+ Exception? deferredException;
+
+ // Resolve the backing immediately to ensure that the [FileNode] we write
+ // to is the same as when [openWrite] was called. This can matter if the
+ // file is moved or removed while open.
+ try {
+ node = file._resolvedBackingOrCreate;
+ } on Exception catch (e) {
+ // For behavioral consistency with [LocalFile], do not report failures
+ // immediately.
+ deferredException = e;
+ }
+
+ var future = Future<FileNode>.microtask(() {
+ if (deferredException != null) {
+ throw deferredException;
+ }
+ file._truncateIfNecessary(node, mode);
+ return node;
+ });
+ return _FileSink._(future, encoding);
+ }
+
+ _FileSink._(Future<FileNode> node, this.encoding) : _pendingWrites = node;
+
+ final Completer<void> _completer = Completer<void>();
+
+ Future<FileNode> _pendingWrites;
+ Completer<void>? _streamCompleter;
+ bool _isClosed = false;
+
+ @override
+ Encoding encoding;
+
+ bool get isStreaming => !(_streamCompleter?.isCompleted ?? true);
+
+ @override
+ void add(List<int> data) {
+ _checkNotStreaming();
+ if (_isClosed) {
+ throw StateError('StreamSink is closed');
+ }
+
+ _addData(data);
+ }
+
+ @override
+ void write(Object? obj) => add(encoding.encode(obj?.toString() ?? 'null'));
+
+ @override
+ void writeAll(Iterable<dynamic> objects, [String separator = '']) {
+ var firstIter = true;
+ for (dynamic obj in objects) {
+ if (!firstIter) {
+ write(separator);
+ }
+ firstIter = false;
+ write(obj);
+ }
+ }
+
+ @override
+ void writeln([Object? obj = '']) {
+ write(obj);
+ write('\n');
+ }
+
+ @override
+ void writeCharCode(int charCode) => write(String.fromCharCode(charCode));
+
+ @override
+ void addError(Object error, [StackTrace? stackTrace]) {
+ _checkNotStreaming();
+ _completer.completeError(error, stackTrace);
+ }
+
+ @override
+ Future<void> addStream(Stream<List<int>> stream) {
+ _checkNotStreaming();
+ _streamCompleter = Completer<void>();
+
+ stream.listen(
+ _addData,
+ cancelOnError: true,
+ onError: (Object error, StackTrace stackTrace) {
+ _streamCompleter!.completeError(error, stackTrace);
+ _streamCompleter = null;
+ },
+ onDone: () {
+ _streamCompleter!.complete();
+ _streamCompleter = null;
+ },
+ );
+ return _streamCompleter!.future;
+ }
+
+ @override
+ Future<void> flush() {
+ _checkNotStreaming();
+ return _pendingWrites;
+ }
+
+ @override
+ Future<void> close() {
+ _checkNotStreaming();
+ if (!_isClosed) {
+ _isClosed = true;
+ _pendingWrites.then(
+ (_) => _completer.complete(),
+ onError: _completer.completeError,
+ );
+ }
+ return _completer.future;
+ }
+
+ @override
+ Future<void> get done => _completer.future;
+
+ void _addData(List<int> data) {
+ _pendingWrites = _pendingWrites.then((FileNode node) {
+ node.write(data);
+ return node;
+ });
+ }
+
+ void _checkNotStreaming() {
+ if (isStreaming) {
+ throw StateError('StreamSink is bound to a stream');
+ }
+ }
+}
diff --git a/pkgs/file/lib/src/backends/memory/memory_file_stat.dart b/pkgs/file/lib/src/backends/memory/memory_file_stat.dart
new file mode 100644
index 0000000..ce6beda
--- /dev/null
+++ b/pkgs/file/lib/src/backends/memory/memory_file_stat.dart
@@ -0,0 +1,68 @@
+// Copyright (c) 2017, 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 '../../io.dart' as io;
+
+/// Internal implementation of [io.FileStat].
+class MemoryFileStat implements io.FileStat {
+ /// Creates a new [MemoryFileStat] with the specified properties.
+ const MemoryFileStat(
+ this.changed,
+ this.modified,
+ this.accessed,
+ this.type,
+ this.mode,
+ this.size,
+ );
+
+ MemoryFileStat._internalNotFound()
+ : changed = DateTime(0),
+ modified = DateTime(0),
+ accessed = DateTime(0),
+ type = io.FileSystemEntityType.notFound,
+ mode = 0,
+ size = -1;
+
+ /// Shared instance representing a non-existent entity.
+ static final MemoryFileStat notFound = MemoryFileStat._internalNotFound();
+
+ @override
+ final DateTime changed;
+
+ @override
+ final DateTime modified;
+
+ @override
+ final DateTime accessed;
+
+ @override
+ final io.FileSystemEntityType type;
+
+ @override
+ final int mode;
+
+ @override
+ final int size;
+
+ @override
+ String modeString() {
+ var permissions = mode & 0xFFF;
+ var codes = const <String>[
+ '---',
+ '--x',
+ '-w-',
+ '-wx',
+ 'r--',
+ 'r-x',
+ 'rw-',
+ 'rwx',
+ ];
+ var result = <String>[];
+ result
+ ..add(codes[(permissions >> 6) & 0x7])
+ ..add(codes[(permissions >> 3) & 0x7])
+ ..add(codes[permissions & 0x7]);
+ return result.join();
+ }
+}
diff --git a/pkgs/file/lib/src/backends/memory/memory_file_system.dart b/pkgs/file/lib/src/backends/memory/memory_file_system.dart
new file mode 100644
index 0000000..dd359f0
--- /dev/null
+++ b/pkgs/file/lib/src/backends/memory/memory_file_system.dart
@@ -0,0 +1,281 @@
+// Copyright (c) 2017, 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:path/path.dart' as p;
+
+import '../../interface.dart';
+import '../../io.dart' as io;
+import 'clock.dart';
+import 'common.dart';
+import 'memory_directory.dart';
+import 'memory_file.dart';
+import 'memory_file_stat.dart';
+import 'memory_link.dart';
+import 'node.dart';
+import 'operations.dart';
+import 'style.dart';
+import 'utils.dart' as utils;
+
+const String _thisDir = '.';
+const String _parentDir = '..';
+
+void _defaultOpHandle(String context, FileSystemOp operation) {}
+
+/// An implementation of [FileSystem] that exists entirely in memory with an
+/// internal representation loosely based on the Filesystem Hierarchy Standard.
+///
+/// [MemoryFileSystem] is suitable for mocking and tests, as well as for
+/// caching or staging before writing or reading to a live system.
+///
+/// This implementation of the [FileSystem] interface does not directly use
+/// any `dart:io` APIs; it merely uses the library's enum values and interfaces.
+/// As such, it is suitable for use in the browser.
+abstract class MemoryFileSystem implements StyleableFileSystem {
+ /// Creates a new `MemoryFileSystem`.
+ ///
+ /// The file system will be empty, and the current directory will be the
+ /// root directory.
+ ///
+ /// The clock will be a real-time clock; file modification times will
+ /// reflect the real time as reported by the operating system.
+ ///
+ /// If [style] is specified, the file system will use the specified path
+ /// style. The default is [FileSystemStyle.posix].
+ factory MemoryFileSystem({
+ FileSystemStyle style = FileSystemStyle.posix,
+ void Function(String context, FileSystemOp operation) opHandle =
+ _defaultOpHandle,
+ }) =>
+ _MemoryFileSystem(
+ style: style,
+ clock: const Clock.realTime(),
+ opHandle: opHandle,
+ );
+
+ /// Creates a new `MemoryFileSystem` that has a fake clock.
+ ///
+ /// The file system will be empty, and the current directory will be the
+ /// root directory.
+ ///
+ /// The clock will increase monotonically each time it is used, disconnected
+ /// from any real-world clock.
+ ///
+ /// If [style] is specified, the file system will use the specified path
+ /// style. The default is [FileSystemStyle.posix].
+ factory MemoryFileSystem.test({
+ FileSystemStyle style = FileSystemStyle.posix,
+ void Function(String context, FileSystemOp operation) opHandle =
+ _defaultOpHandle,
+ }) =>
+ _MemoryFileSystem(
+ style: style,
+ clock: Clock.monotonicTest(),
+ opHandle: opHandle,
+ );
+}
+
+/// Internal implementation of [MemoryFileSystem].
+class _MemoryFileSystem extends FileSystem
+ implements MemoryFileSystem, NodeBasedFileSystem {
+ _MemoryFileSystem({
+ this.style = FileSystemStyle.posix,
+ required this.clock,
+ this.opHandle = _defaultOpHandle,
+ }) : _context = style.contextFor(style.root) {
+ _root = RootNode(this);
+ }
+
+ RootNode? _root;
+ String? _systemTemp;
+ p.Context _context;
+
+ @override
+ final void Function(String context, FileSystemOp operation) opHandle;
+
+ @override
+ final Clock clock;
+
+ @override
+ final FileSystemStyle style;
+
+ @override
+ RootNode? get root => _root;
+
+ @override
+ String get cwd => _context.current;
+
+ @override
+ Directory directory(dynamic path) => MemoryDirectory(this, getPath(path));
+
+ @override
+ File file(dynamic path) => MemoryFile(this, getPath(path));
+
+ @override
+ Link link(dynamic path) => MemoryLink(this, getPath(path));
+
+ @override
+ p.Context get path => _context;
+
+ /// Gets the system temp directory. This directory will be created on-demand
+ /// in the root of the file system. Once created, its location is fixed for
+ /// the life of the process.
+ @override
+ Directory get systemTempDirectory {
+ _systemTemp ??= directory(style.root).createTempSync('.tmp_').path;
+ return directory(_systemTemp)..createSync();
+ }
+
+ @override
+ Directory get currentDirectory => directory(cwd);
+
+ @override
+ set currentDirectory(dynamic path) {
+ String value;
+ if (path is io.Directory) {
+ value = path.path;
+ } else if (path is String) {
+ value = path;
+ } else {
+ throw ArgumentError('Invalid type for "path": ${path?.runtimeType}');
+ }
+
+ value = directory(value).resolveSymbolicLinksSync();
+ var node = findNode(value);
+ checkExists(node, () => value);
+ utils.checkIsDir(node!, () => value);
+ assert(_context.isAbsolute(value));
+ _context = style.contextFor(value);
+ }
+
+ @override
+ Future<io.FileStat> stat(String path) async => statSync(path);
+
+ @override
+ io.FileStat statSync(String path) {
+ try {
+ return findNode(path)?.stat ?? MemoryFileStat.notFound;
+ } on io.FileSystemException {
+ return MemoryFileStat.notFound;
+ }
+ }
+
+ @override
+ Future<bool> identical(String path1, String path2) async =>
+ identicalSync(path1, path2);
+
+ @override
+ bool identicalSync(String path1, String path2) {
+ var node1 = findNode(path1);
+ checkExists(node1, () => path1);
+ var node2 = findNode(path2);
+ checkExists(node2, () => path2);
+ return node1 != null && node1 == node2;
+ }
+
+ @override
+ bool get isWatchSupported => false;
+
+ @override
+ Future<io.FileSystemEntityType> type(
+ String path, {
+ bool followLinks = true,
+ }) async =>
+ typeSync(path, followLinks: followLinks);
+
+ @override
+ io.FileSystemEntityType typeSync(String path, {bool followLinks = true}) {
+ Node? node;
+ try {
+ node = findNode(path, followTailLink: followLinks);
+ } on io.FileSystemException {
+ node = null;
+ }
+ if (node == null) {
+ return io.FileSystemEntityType.notFound;
+ }
+ return node.type;
+ }
+
+ /// Gets the node backing for the current working directory. Note that this
+ /// can return null if the directory has been deleted or moved from under our
+ /// feet.
+ DirectoryNode? get _current => findNode(cwd) as DirectoryNode?;
+
+ @override
+ Node? findNode(
+ String path, {
+ Node? reference,
+ SegmentVisitor? segmentVisitor,
+ bool visitLinks = false,
+ List<String>? pathWithSymlinks,
+ bool followTailLink = false,
+ }) {
+ if (path.isEmpty) {
+ return null;
+ } else if (_context.isAbsolute(path)) {
+ reference = _root;
+ path = path.substring(style.drive.length);
+ } else {
+ reference ??= _current;
+ }
+
+ var parts = path.split(style.separator)..removeWhere(utils.isEmpty);
+ var directory = reference?.directory;
+ Node? child = directory;
+
+ var finalSegment = parts.length - 1;
+ for (var i = 0; i <= finalSegment; i++) {
+ var basename = parts[i];
+ assert(basename.isNotEmpty);
+
+ switch (basename) {
+ case _thisDir:
+ child = directory;
+ break;
+ case _parentDir:
+ child = directory?.parent;
+ directory = directory?.parent;
+ break;
+ default:
+ child = directory?.children[basename];
+ }
+
+ if (pathWithSymlinks != null) {
+ pathWithSymlinks.add(basename);
+ }
+
+ // Generates a subpath for the current segment.
+ String subpath() => parts.sublist(0, i + 1).join(_context.separator);
+
+ if (utils.isLink(child) && (i < finalSegment || followTailLink)) {
+ if (visitLinks || segmentVisitor == null) {
+ if (segmentVisitor != null) {
+ child =
+ segmentVisitor(directory!, basename, child, i, finalSegment);
+ }
+ child = utils.resolveLinks(child as LinkNode, subpath,
+ ledger: pathWithSymlinks);
+ } else {
+ child = utils.resolveLinks(
+ child as LinkNode,
+ subpath,
+ ledger: pathWithSymlinks,
+ tailVisitor: (DirectoryNode parent, String childName, Node? child) {
+ return segmentVisitor(parent, childName, child, i, finalSegment);
+ },
+ );
+ }
+ } else if (segmentVisitor != null) {
+ child = segmentVisitor(directory!, basename, child, i, finalSegment);
+ }
+
+ if (i < finalSegment) {
+ checkExists(child, subpath);
+ utils.checkIsDir(child!, subpath);
+ directory = child as DirectoryNode;
+ }
+ }
+ return child;
+ }
+}
diff --git a/pkgs/file/lib/src/backends/memory/memory_file_system_entity.dart b/pkgs/file/lib/src/backends/memory/memory_file_system_entity.dart
new file mode 100644
index 0000000..1990abc
--- /dev/null
+++ b/pkgs/file/lib/src/backends/memory/memory_file_system_entity.dart
@@ -0,0 +1,309 @@
+// Copyright (c) 2017, 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:meta/meta.dart';
+
+import '../../common.dart' as common;
+import '../../interface.dart';
+import '../../io.dart' as io;
+import 'common.dart';
+import 'memory_directory.dart';
+import 'node.dart';
+import 'operations.dart';
+import 'style.dart';
+import 'utils.dart' as utils;
+
+/// Validator function for use with `_renameSync`. This will be invoked if the
+/// rename would overwrite an existing entity at the new path. If this operation
+/// should not be allowed, this function is expected to throw a
+/// [io.FileSystemException]. The lack of such an exception will be interpreted
+/// as the overwrite being permissible.
+typedef RenameOverwriteValidator<T extends Node> = void Function(
+ T existingNode);
+
+/// Base class for all in-memory file system entity types.
+abstract class MemoryFileSystemEntity implements FileSystemEntity {
+ /// Constructor for subclasses.
+ const MemoryFileSystemEntity(this.fileSystem, this.path);
+
+ @override
+ final NodeBasedFileSystem fileSystem;
+
+ @override
+ final String path;
+
+ @override
+ String get dirname => fileSystem.path.dirname(path);
+
+ @override
+ String get basename => fileSystem.path.basename(path);
+
+ /// Returns the expected type of this entity, which may differ from the type
+ /// of the node that's found at the path specified by this entity.
+ io.FileSystemEntityType get expectedType;
+
+ /// Gets the node that backs this file system entity, or null if this
+ /// entity does not exist.
+ @protected
+ Node? get backingOrNull {
+ try {
+ return fileSystem.findNode(path);
+ } on io.FileSystemException {
+ return null;
+ }
+ }
+
+ /// Gets the node that backs this file system entity. Throws a
+ /// [io.FileSystemException] if this entity doesn't exist.
+ ///
+ /// The type of the node is not guaranteed to match [expectedType].
+ @protected
+ Node get backing {
+ var node = fileSystem.findNode(path);
+ checkExists(node, () => path);
+ return node!;
+ }
+
+ /// Gets the node that backs this file system entity, or if that node is
+ /// a symbolic link, the target node. This also will check that the type of
+ /// the node (after symlink resolution) matches [expectedType]. If the type
+ /// doesn't match, this will throw a [io.FileSystemException].
+ @protected
+ Node get resolvedBacking {
+ var node = backing;
+ node = utils.isLink(node)
+ ? utils.resolveLinks(node as LinkNode, () => path)
+ : node;
+ utils.checkType(expectedType, node.type, () => path);
+ return node;
+ }
+
+ /// Checks the expected type of this file system entity against the specified
+ /// node's `stat` type, throwing a [FileSystemException] if the types don't
+ /// match. Note that since this checks the node's `stat` type, symbolic links
+ /// will be resolved to their target type for the purpose of this validation.
+ ///
+ /// Protected methods that accept a `checkType` argument will default to this
+ /// method if the `checkType` argument is unspecified.
+ @protected
+ void defaultCheckType(Node node) {
+ utils.checkType(expectedType, node.stat.type, () => path);
+ }
+
+ @override
+ Uri get uri {
+ return Uri.file(path, windows: fileSystem.style == FileSystemStyle.windows);
+ }
+
+ @override
+ Future<bool> exists() async => existsSync();
+
+ @override
+ Future<String> resolveSymbolicLinks() async => resolveSymbolicLinksSync();
+
+ @override
+ String resolveSymbolicLinksSync() {
+ if (path.isEmpty) {
+ throw common.noSuchFileOrDirectory(path);
+ }
+ var ledger = <String>[];
+ if (isAbsolute) {
+ ledger.add(fileSystem.style.drive);
+ }
+ var node = fileSystem.findNode(path,
+ pathWithSymlinks: ledger, followTailLink: true);
+ checkExists(node, () => path);
+ var resolved = ledger.join(fileSystem.path.separator);
+ if (resolved == fileSystem.style.drive) {
+ resolved = fileSystem.style.root;
+ } else if (!fileSystem.path.isAbsolute(resolved)) {
+ resolved = fileSystem.cwd + fileSystem.path.separator + resolved;
+ }
+ return fileSystem.path.normalize(resolved);
+ }
+
+ @override
+ Future<io.FileStat> stat() => fileSystem.stat(path);
+
+ @override
+ io.FileStat statSync() => fileSystem.statSync(path);
+
+ @override
+ Future<FileSystemEntity> delete({bool recursive = false}) async {
+ deleteSync(recursive: recursive);
+ return this;
+ }
+
+ @override
+ void deleteSync({bool recursive = false}) =>
+ internalDeleteSync(recursive: recursive);
+
+ @override
+ Stream<io.FileSystemEvent> watch({
+ int events = io.FileSystemEvent.all,
+ bool recursive = false,
+ }) =>
+ throw UnsupportedError('Watching not supported in MemoryFileSystem');
+
+ @override
+ bool get isAbsolute => fileSystem.path.isAbsolute(path);
+
+ @override
+ FileSystemEntity get absolute {
+ var absolutePath = path;
+ if (!fileSystem.path.isAbsolute(absolutePath)) {
+ absolutePath = fileSystem.path.join(fileSystem.cwd, absolutePath);
+ }
+ return clone(absolutePath);
+ }
+
+ @override
+ Directory get parent => MemoryDirectory(fileSystem, dirname);
+
+ /// Helper method for subclasses wishing to synchronously create this entity.
+ /// This method will traverse the path to this entity one segment at a time,
+ /// calling [createChild] for each segment whose child does not already exist.
+ ///
+ /// When [createChild] is invoked:
+ /// - `parent` will be the parent node for the current segment and is
+ /// guaranteed to be non-null.
+ /// - `isFinalSegment` will indicate whether the current segment is the tail
+ /// segment, which in turn indicates that this is the segment into which to
+ /// create the node for this entity.
+ ///
+ /// This method returns with the backing node for the entity at this [path].
+ /// If an entity already existed at this path, [createChild] will not be
+ /// invoked at all, and this method will return with the backing node for the
+ /// existing entity (whose type may differ from this entity's type).
+ ///
+ /// If [followTailLink] is true and the result node is a link, this will
+ /// resolve it to its target prior to returning it.
+ @protected
+ Node? internalCreateSync({
+ required Node? Function(DirectoryNode parent, bool isFinalSegment)
+ createChild,
+ bool followTailLink = false,
+ bool visitLinks = false,
+ }) {
+ return fileSystem.findNode(
+ path,
+ followTailLink: followTailLink,
+ visitLinks: visitLinks,
+ segmentVisitor: (
+ DirectoryNode parent,
+ String childName,
+ Node? child,
+ int currentSegment,
+ int finalSegment,
+ ) {
+ if (child == null) {
+ assert(!parent.children.containsKey(childName));
+ child = createChild(parent, currentSegment == finalSegment);
+ if (child != null) {
+ parent.children[childName] = child;
+ }
+ }
+ return child;
+ },
+ );
+ }
+
+ /// Helper method for subclasses wishing to synchronously rename this entity.
+ /// This method will look for an existing file system entity at the location
+ /// identified by [newPath], and if it finds an existing entity, it will check
+ /// the following:
+ ///
+ /// - If the entity is of a different type than this entity, the operation
+ /// will fail, and a [io.FileSystemException] will be thrown.
+ /// - If the caller has specified [validateOverwriteExistingEntity], then that
+ /// method will be invoked and passed the node backing of the existing
+ /// entity that would overwritten by the rename action. That callback is
+ /// expected to throw a [io.FileSystemException] if overwriting the existing
+ /// entity is not allowed.
+ ///
+ /// If the previous two checks pass, or if there was no existing entity at
+ /// the specified location, this will perform the rename.
+ ///
+ /// If [newPath] cannot be traversed to because its directory does not exist,
+ /// a [io.FileSystemException] will be thrown.
+ ///
+ /// If [followTailLink] is true and there is an existing link at the location
+ /// identified by [newPath], this will resolve the link to its target prior
+ /// to running the validation checks above.
+ ///
+ /// If [checkType] is specified, it will be used to validate that the file
+ /// system entity that exists at [path] is of the expected type. By default,
+ /// [defaultCheckType] is used to perform this validation.
+ @protected
+ FileSystemEntity internalRenameSync<T extends Node>(
+ String newPath, {
+ RenameOverwriteValidator<T>? validateOverwriteExistingEntity,
+ bool followTailLink = false,
+ utils.TypeChecker? checkType,
+ }) {
+ var node = backing;
+ (checkType ?? defaultCheckType)(node);
+ fileSystem.findNode(
+ newPath,
+ segmentVisitor: (
+ DirectoryNode parent,
+ String childName,
+ Node? child,
+ int currentSegment,
+ int finalSegment,
+ ) {
+ if (currentSegment == finalSegment) {
+ if (child != null) {
+ if (followTailLink) {
+ var childType = child.stat.type;
+ if (childType != FileSystemEntityType.notFound) {
+ utils.checkType(expectedType, child.stat.type, () => newPath);
+ }
+ } else {
+ utils.checkType(expectedType, child.type, () => newPath);
+ }
+ if (validateOverwriteExistingEntity != null) {
+ validateOverwriteExistingEntity(child as T);
+ }
+ parent.children.remove(childName);
+ }
+ node.parent.children.remove(basename);
+ parent.children[childName] = node;
+ node.parent = parent;
+ }
+ return child;
+ },
+ );
+ return clone(newPath);
+ }
+
+ /// Deletes this entity from the node tree.
+ ///
+ /// If [checkType] is specified, it will be used to validate that the file
+ /// system entity that exists at [path] is of the expected type. By default,
+ /// [defaultCheckType] is used to perform this validation.
+ @protected
+ void internalDeleteSync({
+ bool recursive = false,
+ utils.TypeChecker? checkType,
+ }) {
+ fileSystem.opHandle(path, FileSystemOp.delete);
+ var node = backing;
+ if (!recursive) {
+ if (node is DirectoryNode && node.children.isNotEmpty) {
+ throw common.directoryNotEmpty(path);
+ }
+ (checkType ?? defaultCheckType)(node);
+ }
+ // Once we remove this reference, the node and all its children will be
+ // garbage collected; we don't need to explicitly delete all children in
+ // the recursive:true case.
+ node.parent.children.remove(basename);
+ }
+
+ /// Creates a new entity with the same type as this entity but with the
+ /// specified path.
+ @protected
+ FileSystemEntity clone(String path);
+}
diff --git a/pkgs/file/lib/src/backends/memory/memory_link.dart b/pkgs/file/lib/src/backends/memory/memory_link.dart
new file mode 100644
index 0000000..a599fe8
--- /dev/null
+++ b/pkgs/file/lib/src/backends/memory/memory_link.dart
@@ -0,0 +1,112 @@
+// Copyright (c) 2017, 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:meta/meta.dart';
+
+import '../../common.dart' as common;
+import '../../interface.dart';
+import '../../io.dart' as io;
+import 'memory_file_system_entity.dart';
+import 'node.dart';
+import 'operations.dart';
+import 'utils.dart' as utils;
+
+/// Internal implementation of [Link].
+class MemoryLink extends MemoryFileSystemEntity implements Link {
+ /// Instantiates a new [MemoryLink].
+ const MemoryLink(super.fileSystem, super.path);
+
+ @override
+ io.FileSystemEntityType get expectedType => io.FileSystemEntityType.link;
+
+ @override
+ bool existsSync() {
+ fileSystem.opHandle.call(path, FileSystemOp.exists);
+ return backingOrNull?.type == expectedType;
+ }
+
+ @override
+ Future<Link> rename(String newPath) async => renameSync(newPath);
+
+ @override
+ Link renameSync(String newPath) => internalRenameSync(
+ newPath,
+ checkType: (Node node) {
+ if (node.type != expectedType) {
+ throw node.type == FileSystemEntityType.directory
+ ? common.isADirectory(newPath)
+ : common.invalidArgument(newPath);
+ }
+ },
+ ) as Link;
+
+ @override
+ Future<Link> create(String target, {bool recursive = false}) async {
+ createSync(target, recursive: recursive);
+ return this;
+ }
+
+ @override
+ void createSync(String target, {bool recursive = false}) {
+ var preexisting = true;
+ fileSystem.opHandle(path, FileSystemOp.create);
+ internalCreateSync(
+ createChild: (DirectoryNode parent, bool isFinalSegment) {
+ if (isFinalSegment) {
+ preexisting = false;
+ return LinkNode(parent, target);
+ } else if (recursive) {
+ return DirectoryNode(parent);
+ }
+ return null;
+ });
+ if (preexisting) {
+ // Per the spec, this is an error.
+ throw common.fileExists(path);
+ }
+ }
+
+ @override
+ Future<Link> update(String target) async {
+ updateSync(target);
+ return this;
+ }
+
+ @override
+ void updateSync(String target) {
+ var node = backing;
+ utils.checkType(expectedType, node.type, () => path);
+ (node as LinkNode).target = target;
+ }
+
+ @override
+ void deleteSync({bool recursive = false}) => internalDeleteSync(
+ recursive: recursive,
+ checkType: (Node node) =>
+ utils.checkType(expectedType, node.type, () => path),
+ );
+
+ @override
+ Future<String> target() async => targetSync();
+
+ @override
+ String targetSync() {
+ var node = backing;
+ if (node.type != expectedType) {
+ // Note: this may change; https://github.com/dart-lang/sdk/issues/28204
+ throw common.noSuchFileOrDirectory(path);
+ }
+ return (node as LinkNode).target;
+ }
+
+ @override
+ Link get absolute => super.absolute as Link;
+
+ @override
+ @protected
+ Link clone(String path) => MemoryLink(fileSystem, path);
+
+ @override
+ String toString() => "MemoryLink: '$path'";
+}
diff --git a/pkgs/file/lib/src/backends/memory/memory_random_access_file.dart b/pkgs/file/lib/src/backends/memory/memory_random_access_file.dart
new file mode 100644
index 0000000..190f0a1
--- /dev/null
+++ b/pkgs/file/lib/src/backends/memory/memory_random_access_file.dart
@@ -0,0 +1,391 @@
+// Copyright (c) 2020, 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:math' as math show min;
+import 'dart:typed_data';
+
+import '../../common.dart' as common;
+import '../../io.dart' as io;
+import '../memory.dart' show MemoryFileSystem;
+import 'memory_file.dart';
+import 'memory_file_system.dart' show MemoryFileSystem;
+import 'node.dart';
+import 'utils.dart' as utils;
+
+/// A [MemoryFileSystem]-backed implementation of [io.RandomAccessFile].
+class MemoryRandomAccessFile implements io.RandomAccessFile {
+ /// Constructs a [MemoryRandomAccessFile].
+ ///
+ /// This should be used only by [MemoryFile.open] or [MemoryFile.openSync].
+ MemoryRandomAccessFile(this.path, this._node, this._mode) {
+ switch (_mode) {
+ case io.FileMode.read:
+ break;
+ case io.FileMode.write:
+ case io.FileMode.writeOnly:
+ truncateSync(0);
+ break;
+ case io.FileMode.append:
+ case io.FileMode.writeOnlyAppend:
+ _position = lengthSync();
+ break;
+ default:
+ // [FileMode] provides no way of retrieving its value or name.
+ throw UnimplementedError('Unsupported FileMode');
+ }
+ }
+
+ @override
+ final String path;
+
+ final FileNode _node;
+ final io.FileMode _mode;
+
+ bool _isOpen = true;
+ int _position = 0;
+
+ /// Whether an asynchronous operation is pending.
+ ///
+ /// See [_asyncWrapper] for details.
+ bool get _asyncOperationPending => __asyncOperationPending;
+
+ set _asyncOperationPending(bool value) {
+ assert(__asyncOperationPending != value);
+ __asyncOperationPending = value;
+ }
+
+ bool __asyncOperationPending = false;
+
+ /// Throws a [io.FileSystemException] if an operation is attempted on a file
+ /// that is not open.
+ void _checkOpen() {
+ if (!_isOpen) {
+ throw io.FileSystemException('File closed', path);
+ }
+ }
+
+ /// Throws a [io.FileSystemException] if attempting to read from a file that
+ /// has not been opened for reading.
+ void _checkReadable(String operation) {
+ switch (_mode) {
+ case io.FileMode.read:
+ case io.FileMode.write:
+ case io.FileMode.append:
+ return;
+ case io.FileMode.writeOnly:
+ case io.FileMode.writeOnlyAppend:
+ default:
+ throw io.FileSystemException(
+ '$operation failed', path, common.badFileDescriptor(path).osError);
+ }
+ }
+
+ /// Throws a [io.FileSystemException] if attempting to read from a file that
+ /// has not been opened for writing.
+ void _checkWritable(String operation) {
+ if (utils.isWriteMode(_mode)) {
+ return;
+ }
+
+ throw io.FileSystemException(
+ '$operation failed', path, common.badFileDescriptor(path).osError);
+ }
+
+ /// Throws a [io.FileSystemException] if attempting to perform an operation
+ /// while an asynchronous operation is already in progress.
+ ///
+ /// See [_asyncWrapper] for details.
+ void _checkAsync() {
+ if (_asyncOperationPending) {
+ throw io.FileSystemException(
+ 'An async operation is currently pending', path);
+ }
+ }
+
+ /// Wraps a synchronous function to make it appear asynchronous.
+ ///
+ /// [_asyncOperationPending], [_checkAsync], and [_asyncWrapper] are used to
+ /// mimic [io.RandomAccessFile]'s enforcement that only one asynchronous
+ /// operation is pending for a [io.RandomAccessFile] instance. Since
+ /// [MemoryFileSystem]-based classes are likely to be used in tests, fidelity
+ /// is important to catch errors that might occur in production.
+ ///
+ /// [_asyncWrapper] does not call [f] directly since setting and unsetting
+ /// [_asyncOperationPending] synchronously would not be meaningful. We
+ /// instead execute [f] through a [Future.delayed] callback to better simulate
+ /// asynchrony.
+ Future<R> _asyncWrapper<R>(R Function() f) async {
+ _checkAsync();
+
+ _asyncOperationPending = true;
+ try {
+ return await Future<R>.delayed(
+ Duration.zero,
+ () {
+ // Temporarily reset [_asyncOpPending] in case [f]'s has its own
+ // checks for pending asynchronous operations.
+ _asyncOperationPending = false;
+ try {
+ return f();
+ } finally {
+ _asyncOperationPending = true;
+ }
+ },
+ );
+ } finally {
+ _asyncOperationPending = false;
+ }
+ }
+
+ @override
+ Future<void> close() async => _asyncWrapper(closeSync);
+
+ @override
+ void closeSync() {
+ _checkOpen();
+ _isOpen = false;
+ }
+
+ @override
+ Future<io.RandomAccessFile> flush() async {
+ await _asyncWrapper(flushSync);
+ return this;
+ }
+
+ @override
+ void flushSync() {
+ _checkOpen();
+ _checkAsync();
+ }
+
+ @override
+ Future<int> length() => _asyncWrapper(lengthSync);
+
+ @override
+ int lengthSync() {
+ _checkOpen();
+ _checkAsync();
+ return _node.size;
+ }
+
+ @override
+ Future<io.RandomAccessFile> lock([
+ io.FileLock mode = io.FileLock.exclusive,
+ int start = 0,
+ int end = -1,
+ ]) async {
+ await _asyncWrapper(() => lockSync(mode, start, end));
+ return this;
+ }
+
+ @override
+ void lockSync([
+ io.FileLock mode = io.FileLock.exclusive,
+ int start = 0,
+ int end = -1,
+ ]) {
+ _checkOpen();
+ _checkAsync();
+ // TODO(jamesderlin): Implement, https://github.com/google/file.dart/issues/140
+ throw UnimplementedError('TODO');
+ }
+
+ @override
+ Future<int> position() => _asyncWrapper(positionSync);
+
+ @override
+ int positionSync() {
+ _checkOpen();
+ _checkAsync();
+ return _position;
+ }
+
+ @override
+ Future<Uint8List> read(int bytes) => _asyncWrapper(() => readSync(bytes));
+
+ @override
+ Uint8List readSync(int bytes) {
+ _checkOpen();
+ _checkAsync();
+ _checkReadable('read');
+ // TODO(jamesderlin): Check for integer overflow.
+ final int end = math.min(_position + bytes, lengthSync());
+ final copy = _node.content.sublist(_position, end);
+ _position = end;
+ return copy;
+ }
+
+ @override
+ Future<int> readByte() => _asyncWrapper(readByteSync);
+
+ @override
+ int readByteSync() {
+ _checkOpen();
+ _checkAsync();
+ _checkReadable('readByte');
+
+ if (_position >= lengthSync()) {
+ return -1;
+ }
+ return _node.content[_position++];
+ }
+
+ @override
+ Future<int> readInto(List<int> buffer, [int start = 0, int? end]) =>
+ _asyncWrapper(() => readIntoSync(buffer, start, end));
+
+ @override
+ int readIntoSync(List<int> buffer, [int start = 0, int? end]) {
+ _checkOpen();
+ _checkAsync();
+ _checkReadable('readInto');
+
+ end = RangeError.checkValidRange(start, end, buffer.length);
+
+ final length = lengthSync();
+ int i;
+ for (i = start; i < end && _position < length; i += 1, _position += 1) {
+ buffer[i] = _node.content[_position];
+ }
+ return i - start;
+ }
+
+ @override
+ Future<io.RandomAccessFile> setPosition(int position) async {
+ await _asyncWrapper(() => setPositionSync(position));
+ return this;
+ }
+
+ @override
+ void setPositionSync(int position) {
+ _checkOpen();
+ _checkAsync();
+
+ if (position < 0) {
+ throw io.FileSystemException(
+ 'setPosition failed', path, common.invalidArgument(path).osError);
+ }
+
+ // Empirical testing indicates that setting the position to be beyond the
+ // end of the file is legal and will zero-fill upon the next write.
+ _position = position;
+ }
+
+ @override
+ Future<io.RandomAccessFile> truncate(int length) async {
+ await _asyncWrapper(() => truncateSync(length));
+ return this;
+ }
+
+ @override
+ void truncateSync(int length) {
+ _checkOpen();
+ _checkAsync();
+
+ if (length < 0 || !utils.isWriteMode(_mode)) {
+ throw io.FileSystemException(
+ 'truncate failed', path, common.invalidArgument(path).osError);
+ }
+
+ final oldLength = lengthSync();
+ if (length < oldLength) {
+ _node.truncate(length);
+
+ // [_position] is intentionally left untouched to match the observed
+ // behavior of [RandomAccessFile].
+ } else if (length > oldLength) {
+ _node.write(Uint8List(length - oldLength));
+ }
+ assert(lengthSync() == length);
+ }
+
+ @override
+ Future<io.RandomAccessFile> unlock([int start = 0, int end = -1]) async {
+ await _asyncWrapper(() => unlockSync(start, end));
+ return this;
+ }
+
+ @override
+ void unlockSync([int start = 0, int end = -1]) {
+ _checkOpen();
+ _checkAsync();
+ // TODO(jamesderlin): Implement, https://github.com/google/file.dart/issues/140
+ throw UnimplementedError('TODO');
+ }
+
+ @override
+ Future<io.RandomAccessFile> writeByte(int value) async {
+ await _asyncWrapper(() => writeByteSync(value));
+ return this;
+ }
+
+ @override
+ int writeByteSync(int value) {
+ _checkOpen();
+ _checkAsync();
+ _checkWritable('writeByte');
+
+ // [Uint8List] will truncate values to 8-bits automatically, so we don't
+ // need to check [value].
+
+ var length = lengthSync();
+ if (_position >= length) {
+ // If [_position] is out of bounds, [RandomAccessFile] zero-fills the
+ // file.
+ truncateSync(_position + 1);
+ length = lengthSync();
+ }
+ assert(_position < length);
+ _node.content[_position++] = value;
+
+ // Despite what the documentation states, [RandomAccessFile.writeByteSync]
+ // always seems to return 1, even if we had to extend the file for an out of
+ // bounds write. See https://github.com/dart-lang/sdk/issues/42298.
+ return 1;
+ }
+
+ @override
+ Future<io.RandomAccessFile> writeFrom(
+ List<int> buffer, [
+ int start = 0,
+ int? end,
+ ]) async {
+ await _asyncWrapper(() => writeFromSync(buffer, start, end));
+ return this;
+ }
+
+ @override
+ void writeFromSync(List<int> buffer, [int start = 0, int? end]) {
+ _checkOpen();
+ _checkAsync();
+ _checkWritable('writeFrom');
+
+ end = RangeError.checkValidRange(start, end, buffer.length);
+
+ final writeByteCount = end - start;
+ final endPosition = _position + writeByteCount;
+
+ if (endPosition > lengthSync()) {
+ truncateSync(endPosition);
+ }
+
+ _node.content.setRange(_position, endPosition, buffer, start);
+ _position = endPosition;
+ }
+
+ @override
+ Future<io.RandomAccessFile> writeString(
+ String string, {
+ Encoding encoding = utf8,
+ }) async {
+ await _asyncWrapper(() => writeStringSync(string, encoding: encoding));
+ return this;
+ }
+
+ @override
+ void writeStringSync(String string, {Encoding encoding = utf8}) {
+ writeFromSync(encoding.encode(string));
+ }
+}
diff --git a/pkgs/file/lib/src/backends/memory/node.dart b/pkgs/file/lib/src/backends/memory/node.dart
new file mode 100644
index 0000000..eea72b5
--- /dev/null
+++ b/pkgs/file/lib/src/backends/memory/node.dart
@@ -0,0 +1,355 @@
+// Copyright (c) 2017, 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:typed_data';
+
+import '../../interface.dart';
+import '../../io.dart' as io;
+import 'clock.dart';
+import 'common.dart';
+import 'memory_file_stat.dart';
+import 'operations.dart';
+import 'style.dart';
+
+/// Visitor callback for use with [NodeBasedFileSystem.findNode].
+///
+/// [parent] is the parent node of the current path segment and is guaranteed
+/// to be non-null.
+///
+/// [childName] is the basename of the entity at the current path segment. It
+/// is guaranteed to be non-null.
+///
+/// [childNode] is the node at the current path segment. It will be
+/// non-null only if such an entity exists. The return value of this callback
+/// will be used as the value of this node, which allows this callback to
+/// do things like recursively create or delete folders.
+///
+/// [currentSegment] is the index of the current segment within the overall
+/// path that's being walked by [NodeBasedFileSystem.findNode].
+///
+/// [finalSegment] is the index of the final segment that will be walked by
+/// [NodeBasedFileSystem.findNode].
+typedef SegmentVisitor = Node? Function(
+ DirectoryNode parent,
+ String childName,
+ Node? childNode,
+ int currentSegment,
+ int finalSegment,
+);
+
+/// A [FileSystem] whose internal structure is made up of a tree of [Node]
+/// instances, rooted at a single node.
+abstract class NodeBasedFileSystem implements StyleableFileSystem {
+ /// An optional handle to hook into common file system operations.
+ void Function(String context, FileSystemOp operation) get opHandle;
+
+ /// The root node.
+ RootNode? get root;
+
+ /// The path of the current working directory.
+ String get cwd;
+
+ /// The clock to use when finding the current time (e.g. to set the creation
+ /// time of a new node).
+ Clock get clock;
+
+ /// Gets the backing node of the entity at the specified path. If the tail
+ /// element of the path does not exist, this will return null. If the tail
+ /// element cannot be reached because its directory does not exist, a
+ /// [io.FileSystemException] will be thrown.
+ ///
+ /// If [path] is a relative path, it will be resolved relative to
+ /// [reference], or the current working directory ([cwd]) if [reference] is
+ /// null. If [path] is an absolute path, [reference] will be ignored.
+ ///
+ /// If the last element in [path] represents a symbolic link, this will
+ /// return the [LinkNode] node for the link (it will not return the
+ /// node to which the link points), unless [followTailLink] is true.
+ /// Directory links in the _middle_ of the path will be followed in order to
+ /// find the node regardless of the value of [followTailLink].
+ ///
+ /// If [segmentVisitor] is specified, it will be invoked for every path
+ /// segment visited along the way starting where the reference (root folder
+ /// if the path is absolute) is the parent. For each segment, the return value
+ /// of [segmentVisitor] will be used as the backing node of that path
+ /// segment, thus allowing callers to create nodes on demand in the
+ /// specified path. Note that `..` and `.` segments may cause the visitor to
+ /// get invoked with the same node multiple times. When [segmentVisitor] is
+ /// invoked, for each path segment that resolves to a link node, the visitor
+ /// will visit the actual link node if [visitLinks] is true; otherwise it
+ /// will visit the target of the link node.
+ ///
+ /// If [pathWithSymlinks] is specified, the path to the node with symbolic
+ /// links explicitly broken out will be appended to the buffer. `..` and `.`
+ /// path segments will *not* be resolved and are left to the caller.
+ Node? findNode(
+ String path, {
+ Node reference,
+ SegmentVisitor segmentVisitor,
+ bool visitLinks = false,
+ List<String> pathWithSymlinks,
+ bool followTailLink = false,
+ });
+}
+
+/// A class that represents the actual storage of an existent file system
+/// entity (whereas classes [File], [Directory], and [Link] represent less
+/// concrete entities that may or may not yet exist).
+///
+/// This data structure is loosely based on a Unix-style file system inode
+/// (hence the name).
+abstract class Node {
+ /// Constructs a new [Node] as a child of the specified parent.
+ Node(this._parent) {
+ if (_parent == null && !isRoot) {
+ throw const io.FileSystemException('All nodes must have a parent.');
+ }
+ }
+
+ DirectoryNode? _parent;
+
+ /// Gets the directory that holds this node.
+ DirectoryNode get parent => _parent!;
+
+ /// Reparents this node to live in the specified directory.
+ set parent(DirectoryNode parent) {
+ var ancestor = parent;
+ while (!ancestor.isRoot) {
+ if (ancestor == this) {
+ throw const io.FileSystemException(
+ 'A directory cannot be its own ancestor.');
+ }
+ ancestor = ancestor.parent;
+ }
+ _parent = parent;
+ }
+
+ /// Returns the type of the file system entity that this node represents.
+ io.FileSystemEntityType get type;
+
+ /// Returns the POSIX stat information for this file system object.
+ io.FileStat get stat;
+
+ /// Returns the closest directory in the ancestry hierarchy starting with
+ /// this node. For directory nodes, it returns the node itself; for other
+ /// nodes, it returns the parent node.
+ DirectoryNode get directory => _parent!;
+
+ /// Tells if this node is a root node.
+ bool get isRoot => false;
+
+ /// Returns the file system responsible for this node.
+ NodeBasedFileSystem get fs => _parent!.fs;
+}
+
+/// Base class that represents the backing for those nodes that have
+/// substance (namely, node types that will not redirect to other types when
+/// you call [stat] on them).
+abstract class RealNode extends Node {
+ /// Constructs a new [RealNode] as a child of the specified [parent].
+ RealNode(super.parent) {
+ var now = clock.now.millisecondsSinceEpoch;
+ changed = now;
+ modified = now;
+ accessed = now;
+ }
+
+ /// See [NodeBasedFileSystem.clock].
+ Clock get clock => parent.clock;
+
+ /// Last changed time in milliseconds since the Epoch.
+ late int changed;
+
+ /// Last modified time in milliseconds since the Epoch.
+ late int modified;
+
+ /// Last accessed time in milliseconds since the Epoch.
+ late int accessed;
+
+ /// Bitmask representing the file read/write/execute mode.
+ int mode = 0x777;
+
+ @override
+ io.FileStat get stat {
+ return MemoryFileStat(
+ DateTime.fromMillisecondsSinceEpoch(changed),
+ DateTime.fromMillisecondsSinceEpoch(modified),
+ DateTime.fromMillisecondsSinceEpoch(accessed),
+ type,
+ mode,
+ size,
+ );
+ }
+
+ /// The size of the file system entity in bytes.
+ int get size;
+
+ /// Updates the last modified time of the node.
+ void touch() {
+ modified = clock.now.millisecondsSinceEpoch;
+ }
+}
+
+/// Class that represents the backing for an in-memory directory.
+class DirectoryNode extends RealNode {
+ /// Constructs a new [DirectoryNode] as a child of the specified [parent].
+ DirectoryNode(super.parent);
+
+ /// Child nodes, indexed by their basename.
+ final Map<String, Node> children = <String, Node>{};
+
+ @override
+ io.FileSystemEntityType get type => io.FileSystemEntityType.directory;
+
+ @override
+ DirectoryNode get directory => this;
+
+ @override
+ int get size => 0;
+}
+
+/// Class that represents the backing for the root of the in-memory file system.
+class RootNode extends DirectoryNode {
+ /// Constructs a new [RootNode] tied to the specified file system.
+ RootNode(this.fs)
+ : assert(fs.root == null),
+ super(null);
+
+ @override
+ final NodeBasedFileSystem fs;
+
+ @override
+ Clock get clock => fs.clock;
+
+ @override
+ DirectoryNode get parent => this;
+
+ @override
+ bool get isRoot => true;
+
+ @override
+ set parent(DirectoryNode parent) =>
+ throw UnsupportedError('Cannot set the parent of the root directory.');
+}
+
+/// Class that represents the backing for an in-memory regular file.
+class FileNode extends RealNode {
+ /// Constructs a new [FileNode] as a child of the specified [parent].
+ FileNode(DirectoryNode super.parent);
+
+ /// File contents in bytes.
+ Uint8List get content => _content;
+ Uint8List _content = Uint8List(0);
+
+ @override
+ io.FileSystemEntityType get type => io.FileSystemEntityType.file;
+
+ @override
+ int get size => _content.length;
+
+ /// Appends the specified bytes to the end of this node's [content].
+ void write(List<int> bytes) {
+ var existing = _content;
+ _content = Uint8List(existing.length + bytes.length);
+ _content.setRange(0, existing.length, existing);
+ _content.setRange(existing.length, _content.length, bytes);
+ }
+
+ /// Truncates this node's [content] to the specified length.
+ ///
+ /// [length] must be in the range \[0, [size]\].
+ void truncate(int length) {
+ assert(length >= 0);
+ assert(length <= _content.length);
+ _content = _content.sublist(0, length);
+ }
+
+ /// Clears the [content] of the node.
+ void clear() {
+ _content = Uint8List(0);
+ }
+
+ /// Copies data from [source] into this node. The [modified] and [changed]
+ /// fields will be reset as opposed to copied to indicate that this file
+ /// has been modified and changed.
+ void copyFrom(FileNode source) {
+ modified = changed = clock.now.millisecondsSinceEpoch;
+ accessed = source.accessed;
+ mode = source.mode;
+ _content = Uint8List.fromList(source.content);
+ }
+}
+
+/// Class that represents the backing for an in-memory symbolic link.
+class LinkNode extends Node {
+ /// Constructs a new [LinkNode] as a child of the specified [parent] and
+ /// linking to the specified [target] path.
+ LinkNode(DirectoryNode super.parent, this.target) : assert(target.isNotEmpty);
+
+ /// The path to which this link points.
+ String target;
+
+ /// A marker used to detect circular link references.
+ bool _reentrant = false;
+
+ /// Gets the node backing for this link's target. Throws a
+ /// [FileSystemException] if this link references a non-existent file
+ /// system entity.
+ ///
+ /// If [tailVisitor] is specified, it will be invoked for the tail path
+ /// segment of this link's target, and its return value will be used as the
+ /// return value of this method. If the tail path segment of this link's
+ /// target cannot be traversed into, a [FileSystemException] will be thrown,
+ /// and [tailVisitor] will not be invoked.
+ Node getReferent({
+ Node? Function(DirectoryNode parent, String childName, Node? child)?
+ tailVisitor,
+ }) {
+ var referent = fs.findNode(
+ target,
+ reference: this,
+ segmentVisitor: (
+ DirectoryNode parent,
+ String childName,
+ Node? child,
+ int currentSegment,
+ int finalSegment,
+ ) {
+ if (tailVisitor != null && currentSegment == finalSegment) {
+ child = tailVisitor(parent, childName, child);
+ }
+ return child;
+ },
+ );
+ checkExists(referent, () => target);
+ return referent!;
+ }
+
+ /// Gets the node backing for this link's target, or null if this link
+ /// references a non-existent file system entity.
+ Node? get referentOrNull {
+ try {
+ return getReferent();
+ } on io.FileSystemException {
+ return null;
+ }
+ }
+
+ @override
+ io.FileSystemEntityType get type => io.FileSystemEntityType.link;
+
+ @override
+ io.FileStat get stat {
+ if (_reentrant) {
+ return MemoryFileStat.notFound;
+ }
+ _reentrant = true;
+ try {
+ var node = referentOrNull;
+ return node == null ? MemoryFileStat.notFound : node.stat;
+ } finally {
+ _reentrant = false;
+ }
+ }
+}
diff --git a/pkgs/file/lib/src/backends/memory/operations.dart b/pkgs/file/lib/src/backends/memory/operations.dart
new file mode 100644
index 0000000..57d118b
--- /dev/null
+++ b/pkgs/file/lib/src/backends/memory/operations.dart
@@ -0,0 +1,80 @@
+// Copyright (c) 2017, 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: comment_references
+
+/// A file system operation used by the [MemoryFileSytem] to allow
+/// tests to insert errors for certain operations.
+///
+/// This is not implemented as an enum to allow new values to be added in a
+/// backwards compatible manner.
+class FileSystemOp {
+ const FileSystemOp._(this._value);
+
+ // This field added to ensure const values can be different.
+ // ignore: unused_field
+ final int _value;
+
+ /// A file system operation used for all read methods.
+ ///
+ /// * [FileSystemEntity.readAsString]
+ /// * [FileSystemEntity.readAsStringSync]
+ /// * [FileSystemEntity.readAsBytes]
+ /// * [FileSystemEntity.readAsBytesSync]
+ static const FileSystemOp read = FileSystemOp._(0);
+
+ /// A file system operation used for all write methods.
+ ///
+ /// * [FileSystemEntity.writeAsString]
+ /// * [FileSystemEntity.writeAsStringSync]
+ /// * [FileSystemEntity.writeAsBytes]
+ /// * [FileSystemEntity.writeAsBytesSync]
+ static const FileSystemOp write = FileSystemOp._(1);
+
+ /// A file system operation used for all delete methods.
+ ///
+ /// * [FileSystemEntity.delete]
+ /// * [FileSystemEntity.deleteSync]
+ static const FileSystemOp delete = FileSystemOp._(2);
+
+ /// A file system operation used for all create methods.
+ ///
+ /// * [FileSystemEntity.create]
+ /// * [FileSystemEntity.createSync]
+ static const FileSystemOp create = FileSystemOp._(3);
+
+ /// A file operation used for all open methods.
+ ///
+ /// * [File.open]
+ /// * [File.openSync]
+ /// * [File.openRead]
+ /// * [File.openWrite]
+ static const FileSystemOp open = FileSystemOp._(4);
+
+ /// A file operation used for all copy methods.
+ ///
+ /// * [File.copy]
+ /// * [File.copySync]
+ static const FileSystemOp copy = FileSystemOp._(5);
+
+ /// A file system operation used for all exists methods.
+ ///
+ /// * [FileSystemEntity.exists]
+ /// * [FileSystemEntity.existsSync]
+ static const FileSystemOp exists = FileSystemOp._(6);
+
+ @override
+ String toString() {
+ return switch (_value) {
+ 0 => 'FileSystemOp.read',
+ 1 => 'FileSystemOp.write',
+ 2 => 'FileSystemOp.delete',
+ 3 => 'FileSystemOp.create',
+ 4 => 'FileSystemOp.open',
+ 5 => 'FileSystemOp.copy',
+ 6 => 'FileSystemOp.exists',
+ _ => throw StateError('Invalid FileSytemOp type: $this')
+ };
+ }
+}
diff --git a/pkgs/file/lib/src/backends/memory/style.dart b/pkgs/file/lib/src/backends/memory/style.dart
new file mode 100644
index 0000000..f4bd33f
--- /dev/null
+++ b/pkgs/file/lib/src/backends/memory/style.dart
@@ -0,0 +1,99 @@
+// 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.
+
+import 'package:path/path.dart' as p;
+
+import '../../interface.dart';
+
+/// Class that represents the path style that a memory file system should
+/// adopt.
+///
+/// This is primarily useful if you want to test how your code will behave
+/// when faced with particular paths or particular path separator characters.
+/// For instance, you may want to test that your code will work on Windows,
+/// while still using a memory file system in order to gain hermeticity in your
+/// tests.
+abstract class FileSystemStyle {
+ const FileSystemStyle._();
+
+ /// Mimics the Unix file system style.
+ ///
+ /// * This style does not have the notion of drives
+ /// * All file system paths are rooted at `/`
+ /// * The path separator is `/`
+ ///
+ /// An example path in this style is `/path/to/file`.
+ static const FileSystemStyle posix = _Posix();
+
+ /// Mimics the Windows file system style.
+ ///
+ /// * This style mounts its root folder on a single root drive (`C:`)
+ /// * All file system paths are rooted at `C:\`
+ /// * The path separator is `\`
+ ///
+ /// An example path in this style is `C:\path\to\file`.
+ static const FileSystemStyle windows = _Windows();
+
+ /// The drive upon which the root directory is mounted.
+ ///
+ /// While real-world file systems that have the notion of drives will support
+ /// multiple drives per system, memory file system will only support one
+ /// root drive.
+ ///
+ /// This will be the empty string for styles that don't have the notion of
+ /// drives (e.g. [posix]).
+ String get drive;
+
+ /// The String that represents the delineation between a directory and its
+ /// children.
+ String get separator;
+
+ /// The string that represents the root of the file system.
+ ///
+ /// Memory file system is always single-rooted.
+ String get root => '$drive$separator';
+
+ /// Gets an object useful for manipulating paths in this style.
+ ///
+ /// Relative path manipulations will be relative to the specified [path].
+ p.Context contextFor(String path);
+}
+
+class _Posix extends FileSystemStyle {
+ const _Posix() : super._();
+
+ @override
+ String get drive => '';
+
+ @override
+ String get separator {
+ return p.Style.posix.separator; // ignore: deprecated_member_use
+ }
+
+ @override
+ p.Context contextFor(String path) =>
+ p.Context(style: p.Style.posix, current: path);
+}
+
+class _Windows extends FileSystemStyle {
+ const _Windows() : super._();
+
+ @override
+ String get drive => 'C:';
+
+ @override
+ String get separator {
+ return p.Style.windows.separator; // ignore: deprecated_member_use
+ }
+
+ @override
+ p.Context contextFor(String path) =>
+ p.Context(style: p.Style.windows, current: path);
+}
+
+/// A file system that supports different styles.
+abstract class StyleableFileSystem implements FileSystem {
+ /// The style used by this file system.
+ FileSystemStyle get style;
+}
diff --git a/pkgs/file/lib/src/backends/memory/utils.dart b/pkgs/file/lib/src/backends/memory/utils.dart
new file mode 100644
index 0000000..aa24cfb
--- /dev/null
+++ b/pkgs/file/lib/src/backends/memory/utils.dart
@@ -0,0 +1,116 @@
+// Copyright (c) 2017, 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 '../../common.dart' as common;
+import '../../interface.dart';
+import '../../io.dart' as io;
+import 'common.dart';
+import 'node.dart';
+
+/// Checks if `node.type` returns [io.FileSystemEntityType.file].
+bool isFile(Node? node) => node?.type == io.FileSystemEntityType.file;
+
+/// Checks if `node.type` returns [io.FileSystemEntityType.directory].
+bool isDirectory(Node? node) => node?.type == io.FileSystemEntityType.directory;
+
+/// Checks if `node.type` returns [io.FileSystemEntityType.link].
+bool isLink(Node? node) => node?.type == io.FileSystemEntityType.link;
+
+/// Validator function that is expected to throw a [FileSystemException] if
+/// the node does not represent the type that is expected in any given context.
+typedef TypeChecker = void Function(Node node);
+
+/// Throws a [io.FileSystemException] if [node] is not a directory.
+void checkIsDir(Node node, PathGenerator path) {
+ if (!isDirectory(node)) {
+ throw common.notADirectory(path() as String);
+ }
+}
+
+/// Throws a [io.FileSystemException] if [expectedType] doesn't match
+/// [actualType].
+void checkType(
+ FileSystemEntityType expectedType,
+ FileSystemEntityType actualType,
+ PathGenerator path,
+) {
+ if (expectedType != actualType) {
+ switch (expectedType) {
+ case FileSystemEntityType.directory:
+ throw common.notADirectory(path() as String);
+ case FileSystemEntityType.file:
+ assert(actualType == FileSystemEntityType.directory);
+ throw common.isADirectory(path() as String);
+ case FileSystemEntityType.link:
+ throw common.invalidArgument(path() as String);
+ default:
+ // Should not happen
+ throw AssertionError();
+ }
+ }
+}
+
+/// Tells if the specified file mode represents a write mode.
+bool isWriteMode(io.FileMode mode) =>
+ mode == io.FileMode.write ||
+ mode == io.FileMode.append ||
+ mode == io.FileMode.writeOnly ||
+ mode == io.FileMode.writeOnlyAppend;
+
+/// Tells whether the given string is empty.
+bool isEmpty(String str) => str.isEmpty;
+
+/// Returns the node ultimately referred to by [link]. This will resolve
+/// the link references (following chains of links as necessary) and return
+/// the node at the end of the link chain.
+///
+/// If a loop in the link chain is found, this will throw a
+/// [FileSystemException], calling [path] to generate the path.
+///
+/// If [ledger] is specified, the resolved path to the terminal node will be
+/// appended to the ledger (or overwritten in the ledger if a link target
+/// specified an absolute path). The path will not be normalized, meaning
+/// `..` and `.` path segments may be present.
+///
+/// If [tailVisitor] is specified, it will be invoked for the tail element of
+/// the last link in the symbolic link chain, and its return value will be the
+/// return value of this method (thus allowing callers to create the entity
+/// at the end of the chain on demand).
+Node resolveLinks(
+ LinkNode link,
+ PathGenerator path, {
+ List<String>? ledger,
+ Node? Function(DirectoryNode parent, String childName, Node? child)?
+ tailVisitor,
+}) {
+ // Record a breadcrumb trail to guard against symlink loops.
+ var breadcrumbs = <LinkNode>{};
+
+ Node node = link;
+ while (isLink(node)) {
+ link = node as LinkNode;
+ if (!breadcrumbs.add(link)) {
+ throw common.tooManyLevelsOfSymbolicLinks(path() as String);
+ }
+ if (ledger != null) {
+ if (link.fs.path.isAbsolute(link.target)) {
+ ledger.clear();
+ } else if (ledger.isNotEmpty) {
+ ledger.removeLast();
+ }
+ ledger.addAll(link.target.split(link.fs.path.separator));
+ }
+ node = link.getReferent(
+ tailVisitor: (DirectoryNode parent, String childName, Node? child) {
+ if (tailVisitor != null && !isLink(child)) {
+ // Only invoke [tailListener] on the final resolution pass.
+ child = tailVisitor(parent, childName, child);
+ }
+ return child;
+ },
+ );
+ }
+
+ return node;
+}
diff --git a/pkgs/file/lib/src/common.dart b/pkgs/file/lib/src/common.dart
new file mode 100644
index 0000000..6706ec9
--- /dev/null
+++ b/pkgs/file/lib/src/common.dart
@@ -0,0 +1,70 @@
+// Copyright (c) 2017, 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 'interface.dart';
+
+/// Returns a 'No such file or directory' [FileSystemException].
+FileSystemException noSuchFileOrDirectory(String path) {
+ return _fsException(path, 'No such file or directory', ErrorCodes.ENOENT);
+}
+
+/// Returns a 'Not a directory' [FileSystemException].
+FileSystemException notADirectory(String path) {
+ return _fsException(path, 'Not a directory', ErrorCodes.ENOTDIR);
+}
+
+/// Returns a 'Is a directory' [FileSystemException].
+FileSystemException isADirectory(String path) {
+ return _fsException(path, 'Is a directory', ErrorCodes.EISDIR);
+}
+
+/// Returns a 'Directory not empty' [FileSystemException].
+FileSystemException directoryNotEmpty(String path) {
+ return _fsException(path, 'Directory not empty', ErrorCodes.ENOTEMPTY);
+}
+
+/// Returns a 'File exists' [FileSystemException].
+FileSystemException fileExists(String path) {
+ return _fsException(path, 'File exists', ErrorCodes.EEXIST);
+}
+
+/// Returns a 'Invalid argument' [FileSystemException].
+FileSystemException invalidArgument(String path) {
+ return _fsException(path, 'Invalid argument', ErrorCodes.EINVAL);
+}
+
+/// Returns a 'Too many levels of symbolic links' [FileSystemException].
+FileSystemException tooManyLevelsOfSymbolicLinks(String path) {
+ // TODO(tvolkert): Switch to ErrorCodes.EMLINK
+ return _fsException(
+ path, 'Too many levels of symbolic links', ErrorCodes.ELOOP);
+}
+
+/// Returns a 'Bad file descriptor' [FileSystemException].
+FileSystemException badFileDescriptor(String path) {
+ return _fsException(path, 'Bad file descriptor', ErrorCodes.EBADF);
+}
+
+FileSystemException _fsException(String path, String msg, int errorCode) {
+ return FileSystemException(msg, path, OSError(msg, errorCode));
+}
+
+/// Mixin containing implementations of [Directory] methods that are common
+/// to all implementations.
+mixin DirectoryAddOnsMixin implements Directory {
+ @override
+ Directory childDirectory(String basename) {
+ return fileSystem.directory(fileSystem.path.join(path, basename));
+ }
+
+ @override
+ File childFile(String basename) {
+ return fileSystem.file(fileSystem.path.join(path, basename));
+ }
+
+ @override
+ Link childLink(String basename) {
+ return fileSystem.link(fileSystem.path.join(path, basename));
+ }
+}
diff --git a/pkgs/file/lib/src/forwarding.dart b/pkgs/file/lib/src/forwarding.dart
new file mode 100644
index 0000000..9566a30
--- /dev/null
+++ b/pkgs/file/lib/src/forwarding.dart
@@ -0,0 +1,10 @@
+// Copyright (c) 2017, 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 'forwarding/forwarding_directory.dart';
+export 'forwarding/forwarding_file.dart';
+export 'forwarding/forwarding_file_system.dart';
+export 'forwarding/forwarding_file_system_entity.dart';
+export 'forwarding/forwarding_link.dart';
+export 'forwarding/forwarding_random_access_file.dart';
diff --git a/pkgs/file/lib/src/forwarding/forwarding_directory.dart b/pkgs/file/lib/src/forwarding/forwarding_directory.dart
new file mode 100644
index 0000000..ad1c548
--- /dev/null
+++ b/pkgs/file/lib/src/forwarding/forwarding_directory.dart
@@ -0,0 +1,58 @@
+// Copyright (c) 2017, 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 '../forwarding.dart';
+import '../interface.dart';
+import '../io.dart' as io;
+
+/// A directory that forwards all methods and properties to a delegate.
+mixin ForwardingDirectory<T extends Directory>
+ implements ForwardingFileSystemEntity<T, io.Directory>, Directory {
+ @override
+ T wrap(io.Directory delegate) => wrapDirectory(delegate) as T;
+
+ @override
+ Future<Directory> create({bool recursive = false}) async =>
+ wrap(await delegate.create(recursive: recursive));
+
+ @override
+ void createSync({bool recursive = false}) =>
+ delegate.createSync(recursive: recursive);
+
+ @override
+ Future<Directory> createTemp([String? prefix]) async =>
+ wrap(await delegate.createTemp(prefix));
+
+ @override
+ Directory createTempSync([String? prefix]) =>
+ wrap(delegate.createTempSync(prefix));
+
+ @override
+ Stream<FileSystemEntity> list({
+ bool recursive = false,
+ bool followLinks = true,
+ }) =>
+ delegate.list(recursive: recursive, followLinks: followLinks).map(_wrap);
+
+ @override
+ List<FileSystemEntity> listSync({
+ bool recursive = false,
+ bool followLinks = true,
+ }) =>
+ delegate
+ .listSync(recursive: recursive, followLinks: followLinks)
+ .map(_wrap)
+ .toList();
+
+ FileSystemEntity _wrap(io.FileSystemEntity entity) {
+ if (entity is io.File) {
+ return wrapFile(entity);
+ } else if (entity is io.Directory) {
+ return wrapDirectory(entity);
+ } else if (entity is io.Link) {
+ return wrapLink(entity);
+ }
+ throw FileSystemException('Unsupported type: $entity', entity.path);
+ }
+}
diff --git a/pkgs/file/lib/src/forwarding/forwarding_file.dart b/pkgs/file/lib/src/forwarding/forwarding_file.dart
new file mode 100644
index 0000000..d6cfe3b
--- /dev/null
+++ b/pkgs/file/lib/src/forwarding/forwarding_file.dart
@@ -0,0 +1,157 @@
+// Copyright (c) 2017, 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:typed_data';
+
+import '../forwarding.dart';
+import '../interface.dart';
+import '../io.dart' as io;
+
+/// A file that forwards all methods and properties to a delegate.
+mixin ForwardingFile
+ implements ForwardingFileSystemEntity<File, io.File>, File {
+ @override
+ ForwardingFile wrap(io.File delegate) => wrapFile(delegate) as ForwardingFile;
+
+ // TODO(dartbug.com/49647): Pass `exclusive` through after it lands.
+ @override
+ Future<File> create({bool recursive = false, bool exclusive = false}) async =>
+ wrap(await delegate.create(
+ recursive: recursive /*, exclusive: exclusive*/));
+
+ // TODO(dartbug.com/49647): Pass `exclusive` through after it lands.
+ @override
+ void createSync({bool recursive = false, bool exclusive = false}) =>
+ delegate.createSync(recursive: recursive /*, exclusive: exclusive*/);
+
+ @override
+ Future<File> copy(String newPath) async => wrap(await delegate.copy(newPath));
+
+ @override
+ File copySync(String newPath) => wrap(delegate.copySync(newPath));
+
+ @override
+ Future<int> length() => delegate.length();
+
+ @override
+ int lengthSync() => delegate.lengthSync();
+
+ @override
+ Future<DateTime> lastAccessed() => delegate.lastAccessed();
+
+ @override
+ DateTime lastAccessedSync() => delegate.lastAccessedSync();
+
+ @override
+ Future<dynamic> setLastAccessed(DateTime time) =>
+ delegate.setLastAccessed(time);
+
+ @override
+ void setLastAccessedSync(DateTime time) => delegate.setLastAccessedSync(time);
+
+ @override
+ Future<DateTime> lastModified() => delegate.lastModified();
+
+ @override
+ DateTime lastModifiedSync() => delegate.lastModifiedSync();
+
+ @override
+ Future<dynamic> setLastModified(DateTime time) =>
+ delegate.setLastModified(time);
+
+ @override
+ void setLastModifiedSync(DateTime time) => delegate.setLastModifiedSync(time);
+
+ @override
+ Future<RandomAccessFile> open({
+ FileMode mode = FileMode.read,
+ }) =>
+ delegate.open(mode: mode);
+
+ @override
+ RandomAccessFile openSync({FileMode mode = FileMode.read}) =>
+ delegate.openSync(mode: mode);
+
+ @override
+ Stream<List<int>> openRead([int? start, int? end]) =>
+ delegate.openRead(start, end);
+
+ @override
+ IOSink openWrite({
+ FileMode mode = FileMode.write,
+ Encoding encoding = utf8,
+ }) =>
+ delegate.openWrite(mode: mode, encoding: encoding);
+
+ @override
+ Future<Uint8List> readAsBytes() => delegate.readAsBytes();
+
+ @override
+ Uint8List readAsBytesSync() => delegate.readAsBytesSync();
+
+ @override
+ Future<String> readAsString({Encoding encoding = utf8}) =>
+ delegate.readAsString(encoding: encoding);
+
+ @override
+ String readAsStringSync({Encoding encoding = utf8}) =>
+ delegate.readAsStringSync(encoding: encoding);
+
+ @override
+ Future<List<String>> readAsLines({Encoding encoding = utf8}) =>
+ delegate.readAsLines(encoding: encoding);
+
+ @override
+ List<String> readAsLinesSync({Encoding encoding = utf8}) =>
+ delegate.readAsLinesSync(encoding: encoding);
+
+ @override
+ Future<File> writeAsBytes(
+ List<int> bytes, {
+ FileMode mode = FileMode.write,
+ bool flush = false,
+ }) async =>
+ wrap(await delegate.writeAsBytes(
+ bytes,
+ mode: mode,
+ flush: flush,
+ ));
+
+ @override
+ void writeAsBytesSync(
+ List<int> bytes, {
+ FileMode mode = FileMode.write,
+ bool flush = false,
+ }) =>
+ delegate.writeAsBytesSync(bytes, mode: mode, flush: flush);
+
+ @override
+ Future<File> writeAsString(
+ String contents, {
+ FileMode mode = FileMode.write,
+ Encoding encoding = utf8,
+ bool flush = false,
+ }) async =>
+ wrap(await delegate.writeAsString(
+ contents,
+ mode: mode,
+ encoding: encoding,
+ flush: flush,
+ ));
+
+ @override
+ void writeAsStringSync(
+ String contents, {
+ FileMode mode = FileMode.write,
+ Encoding encoding = utf8,
+ bool flush = false,
+ }) =>
+ delegate.writeAsStringSync(
+ contents,
+ mode: mode,
+ encoding: encoding,
+ flush: flush,
+ );
+}
diff --git a/pkgs/file/lib/src/forwarding/forwarding_file_system.dart b/pkgs/file/lib/src/forwarding/forwarding_file_system.dart
new file mode 100644
index 0000000..885fdb6
--- /dev/null
+++ b/pkgs/file/lib/src/forwarding/forwarding_file_system.dart
@@ -0,0 +1,67 @@
+// Copyright (c) 2017, 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:meta/meta.dart';
+import 'package:path/path.dart' as p;
+
+import '../interface.dart';
+import '../io.dart' as io;
+
+/// A file system that forwards all methods and properties to a delegate.
+abstract class ForwardingFileSystem extends FileSystem {
+ /// Creates a new [ForwardingFileSystem] that forwards all methods and
+ /// properties to the specified [delegate].
+ ForwardingFileSystem(this.delegate);
+
+ /// The file system to which this file system will forward all activity.
+ @protected
+ final FileSystem delegate;
+
+ @override
+ Directory directory(dynamic path) => delegate.directory(path);
+
+ @override
+ File file(dynamic path) => delegate.file(path);
+
+ @override
+ Link link(dynamic path) => delegate.link(path);
+
+ @override
+ p.Context get path => delegate.path;
+
+ @override
+ Directory get systemTempDirectory => delegate.systemTempDirectory;
+
+ @override
+ Directory get currentDirectory => delegate.currentDirectory;
+
+ @override
+ set currentDirectory(dynamic path) => delegate.currentDirectory = path;
+
+ @override
+ Future<io.FileStat> stat(String path) => delegate.stat(path);
+
+ @override
+ io.FileStat statSync(String path) => delegate.statSync(path);
+
+ @override
+ Future<bool> identical(String path1, String path2) =>
+ delegate.identical(path1, path2);
+
+ @override
+ bool identicalSync(String path1, String path2) =>
+ delegate.identicalSync(path1, path2);
+
+ @override
+ bool get isWatchSupported => delegate.isWatchSupported;
+
+ @override
+ Future<io.FileSystemEntityType> type(String path,
+ {bool followLinks = true}) =>
+ delegate.type(path, followLinks: followLinks);
+
+ @override
+ io.FileSystemEntityType typeSync(String path, {bool followLinks = true}) =>
+ delegate.typeSync(path, followLinks: followLinks);
+}
diff --git a/pkgs/file/lib/src/forwarding/forwarding_file_system_entity.dart b/pkgs/file/lib/src/forwarding/forwarding_file_system_entity.dart
new file mode 100644
index 0000000..1c0628e
--- /dev/null
+++ b/pkgs/file/lib/src/forwarding/forwarding_file_system_entity.dart
@@ -0,0 +1,97 @@
+// Copyright (c) 2017, 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:meta/meta.dart';
+
+import '../interface.dart';
+import '../io.dart' as io;
+
+/// A file system entity that forwards all methods and properties to a delegate.
+abstract class ForwardingFileSystemEntity<T extends FileSystemEntity,
+ D extends io.FileSystemEntity> implements FileSystemEntity {
+ /// The entity to which this entity will forward all methods and properties.
+ @protected
+ D get delegate;
+
+ /// Creates a new entity with the same file system and same type as this
+ /// entity but backed by the specified delegate.
+ @protected
+ T wrap(D delegate);
+
+ /// Creates a new directory with the same file system as this entity and
+ /// backed by the specified delegate.
+ @protected
+ Directory wrapDirectory(io.Directory delegate);
+
+ /// Creates a new file with the same file system as this entity and
+ /// backed by the specified delegate.
+ @protected
+ File wrapFile(io.File delegate);
+
+ /// Creates a new link with the same file system as this entity and
+ /// backed by the specified delegate.
+ @protected
+ Link wrapLink(io.Link delegate);
+
+ @override
+ Uri get uri => delegate.uri;
+
+ @override
+ Future<bool> exists() => delegate.exists();
+
+ @override
+ bool existsSync() => delegate.existsSync();
+
+ @override
+ Future<T> rename(String newPath) async =>
+ wrap(await delegate.rename(newPath) as D);
+
+ @override
+ T renameSync(String newPath) => wrap(delegate.renameSync(newPath) as D);
+
+ @override
+ Future<String> resolveSymbolicLinks() => delegate.resolveSymbolicLinks();
+
+ @override
+ String resolveSymbolicLinksSync() => delegate.resolveSymbolicLinksSync();
+
+ @override
+ Future<io.FileStat> stat() => delegate.stat();
+
+ @override
+ io.FileStat statSync() => delegate.statSync();
+
+ @override
+ Future<T> delete({bool recursive = false}) async =>
+ wrap(await delegate.delete(recursive: recursive) as D);
+
+ @override
+ void deleteSync({bool recursive = false}) =>
+ delegate.deleteSync(recursive: recursive);
+
+ @override
+ Stream<FileSystemEvent> watch({
+ int events = FileSystemEvent.all,
+ bool recursive = false,
+ }) =>
+ delegate.watch(events: events, recursive: recursive);
+
+ @override
+ bool get isAbsolute => delegate.isAbsolute;
+
+ @override
+ T get absolute => wrap(delegate.absolute as D);
+
+ @override
+ Directory get parent => wrapDirectory(delegate.parent);
+
+ @override
+ String get path => delegate.path;
+
+ @override
+ String get basename => fileSystem.path.basename(path);
+
+ @override
+ String get dirname => fileSystem.path.dirname(path);
+}
diff --git a/pkgs/file/lib/src/forwarding/forwarding_link.dart b/pkgs/file/lib/src/forwarding/forwarding_link.dart
new file mode 100644
index 0000000..915e710
--- /dev/null
+++ b/pkgs/file/lib/src/forwarding/forwarding_link.dart
@@ -0,0 +1,35 @@
+// Copyright (c) 2017, 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 '../forwarding.dart';
+import '../interface.dart';
+import '../io.dart' as io;
+
+/// A link that forwards all methods and properties to a delegate.
+mixin ForwardingLink
+ implements ForwardingFileSystemEntity<Link, io.Link>, Link {
+ @override
+ ForwardingLink wrap(io.Link delegate) => wrapLink(delegate) as ForwardingLink;
+
+ @override
+ Future<Link> create(String target, {bool recursive = false}) async =>
+ wrap(await delegate.create(target, recursive: recursive));
+
+ @override
+ void createSync(String target, {bool recursive = false}) =>
+ delegate.createSync(target, recursive: recursive);
+
+ @override
+ Future<Link> update(String target) async =>
+ wrap(await delegate.update(target));
+
+ @override
+ void updateSync(String target) => delegate.updateSync(target);
+
+ @override
+ Future<String> target() => delegate.target();
+
+ @override
+ String targetSync() => delegate.targetSync();
+}
diff --git a/pkgs/file/lib/src/forwarding/forwarding_random_access_file.dart b/pkgs/file/lib/src/forwarding/forwarding_random_access_file.dart
new file mode 100644
index 0000000..3847b91
--- /dev/null
+++ b/pkgs/file/lib/src/forwarding/forwarding_random_access_file.dart
@@ -0,0 +1,150 @@
+// Copyright (c) 2020, 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:typed_data';
+
+import 'package:meta/meta.dart';
+
+import '../io.dart' as io;
+
+/// A [io.RandomAccessFile] implementation that forwards all methods and
+/// properties to a delegate.
+mixin ForwardingRandomAccessFile implements io.RandomAccessFile {
+ /// The entity to which this entity will forward all methods and properties.
+ @protected
+ io.RandomAccessFile get delegate;
+
+ @override
+ String get path => delegate.path;
+
+ @override
+ Future<void> close() => delegate.close();
+
+ @override
+ void closeSync() => delegate.closeSync();
+
+ @override
+ Future<io.RandomAccessFile> flush() async {
+ await delegate.flush();
+ return this;
+ }
+
+ @override
+ void flushSync() => delegate.flushSync();
+
+ @override
+ Future<int> length() => delegate.length();
+
+ @override
+ int lengthSync() => delegate.lengthSync();
+
+ @override
+ Future<io.RandomAccessFile> lock([
+ io.FileLock mode = io.FileLock.exclusive,
+ int start = 0,
+ int end = -1,
+ ]) async {
+ await delegate.lock(mode, start, end);
+ return this;
+ }
+
+ @override
+ void lockSync([
+ io.FileLock mode = io.FileLock.exclusive,
+ int start = 0,
+ int end = -1,
+ ]) =>
+ delegate.lockSync(mode, start, end);
+
+ @override
+ Future<int> position() => delegate.position();
+
+ @override
+ int positionSync() => delegate.positionSync();
+
+ @override
+ Future<Uint8List> read(int bytes) => delegate.read(bytes);
+
+ @override
+ Uint8List readSync(int bytes) => delegate.readSync(bytes);
+
+ @override
+ Future<int> readByte() => delegate.readByte();
+
+ @override
+ int readByteSync() => delegate.readByteSync();
+
+ @override
+ Future<int> readInto(List<int> buffer, [int start = 0, int? end]) =>
+ delegate.readInto(buffer, start, end);
+
+ @override
+ int readIntoSync(List<int> buffer, [int start = 0, int? end]) =>
+ delegate.readIntoSync(buffer, start, end);
+
+ @override
+ Future<io.RandomAccessFile> setPosition(int position) async {
+ await delegate.setPosition(position);
+ return this;
+ }
+
+ @override
+ void setPositionSync(int position) => delegate.setPositionSync(position);
+
+ @override
+ Future<io.RandomAccessFile> truncate(int length) async {
+ await delegate.truncate(length);
+ return this;
+ }
+
+ @override
+ void truncateSync(int length) => delegate.truncateSync(length);
+
+ @override
+ Future<io.RandomAccessFile> unlock([int start = 0, int end = -1]) async {
+ await delegate.unlock(start, end);
+ return this;
+ }
+
+ @override
+ void unlockSync([int start = 0, int end = -1]) =>
+ delegate.unlockSync(start, end);
+
+ @override
+ Future<io.RandomAccessFile> writeByte(int value) async {
+ await delegate.writeByte(value);
+ return this;
+ }
+
+ @override
+ int writeByteSync(int value) => delegate.writeByteSync(value);
+
+ @override
+ Future<io.RandomAccessFile> writeFrom(
+ List<int> buffer, [
+ int start = 0,
+ int? end,
+ ]) async {
+ await delegate.writeFrom(buffer, start, end);
+ return this;
+ }
+
+ @override
+ void writeFromSync(List<int> buffer, [int start = 0, int? end]) =>
+ delegate.writeFromSync(buffer, start, end);
+
+ @override
+ Future<io.RandomAccessFile> writeString(
+ String string, {
+ Encoding encoding = utf8,
+ }) async {
+ await delegate.writeString(string, encoding: encoding);
+ return this;
+ }
+
+ @override
+ void writeStringSync(String string, {Encoding encoding = utf8}) =>
+ delegate.writeStringSync(string, encoding: encoding);
+}
diff --git a/pkgs/file/lib/src/interface.dart b/pkgs/file/lib/src/interface.dart
new file mode 100644
index 0000000..d9b7ed5
--- /dev/null
+++ b/pkgs/file/lib/src/interface.dart
@@ -0,0 +1,11 @@
+// Copyright (c) 2017, 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 'interface/directory.dart';
+export 'interface/error_codes.dart';
+export 'interface/file.dart';
+export 'interface/file_system.dart';
+export 'interface/file_system_entity.dart';
+export 'interface/link.dart';
+export 'io.dart' hide Directory, File, FileSystemEntity, Link;
diff --git a/pkgs/file/lib/src/interface/directory.dart b/pkgs/file/lib/src/interface/directory.dart
new file mode 100644
index 0000000..e62ad8c
--- /dev/null
+++ b/pkgs/file/lib/src/interface/directory.dart
@@ -0,0 +1,51 @@
+// Copyright (c) 2017, 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 '../io.dart' as io;
+
+import 'file.dart';
+import 'file_system_entity.dart';
+import 'link.dart';
+
+/// A reference to a directory on the file system.
+abstract class Directory implements FileSystemEntity, io.Directory {
+ // Override method definitions to codify the return type covariance.
+ @override
+ Future<Directory> create({bool recursive = false});
+
+ @override
+ Future<Directory> createTemp([String? prefix]);
+
+ @override
+ Directory createTempSync([String? prefix]);
+
+ @override
+ Future<Directory> rename(String newPath);
+
+ @override
+ Directory renameSync(String newPath);
+
+ @override
+ Directory get absolute;
+
+ @override
+ Stream<FileSystemEntity> list(
+ {bool recursive = false, bool followLinks = true});
+
+ @override
+ List<FileSystemEntity> listSync(
+ {bool recursive = false, bool followLinks = true});
+
+ /// Returns a reference to a [Directory] that exists as a child of this
+ /// directory and has the specified [basename].
+ Directory childDirectory(String basename);
+
+ /// Returns a reference to a [File] that exists as a child of this directory
+ /// and has the specified [basename].
+ File childFile(String basename);
+
+ /// Returns a reference to a [Link] that exists as a child of this directory
+ /// and has the specified [basename].
+ Link childLink(String basename);
+}
diff --git a/pkgs/file/lib/src/interface/error_codes.dart b/pkgs/file/lib/src/interface/error_codes.dart
new file mode 100644
index 0000000..4836b56
--- /dev/null
+++ b/pkgs/file/lib/src/interface/error_codes.dart
@@ -0,0 +1,585 @@
+// Copyright (c) 2017, 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 'error_codes_internal.dart'
+ if (dart.library.io) 'error_codes_dart_io.dart';
+
+/// Operating system error codes.
+// TODO(tvolkert): Remove (https://github.com/dart-lang/sdk/issues/28860)
+class ErrorCodes {
+ ErrorCodes._();
+
+ /// Argument list too long
+ // ignore: non_constant_identifier_names
+ static int get E2BIG => _platform((_Codes codes) => codes.e2big);
+
+ /// Permission denied
+ // ignore: non_constant_identifier_names
+ static int get EACCES => _platform((_Codes codes) => codes.eacces);
+
+ /// Try again
+ // ignore: non_constant_identifier_names
+ static int get EAGAIN => _platform((_Codes codes) => codes.eagain);
+
+ /// Bad file number
+ // ignore: non_constant_identifier_names
+ static int get EBADF => _platform((_Codes codes) => codes.ebadf);
+
+ /// Device or resource busy
+ // ignore: non_constant_identifier_names
+ static int get EBUSY => _platform((_Codes codes) => codes.ebusy);
+
+ /// No child processes
+ // ignore: non_constant_identifier_names
+ static int get ECHILD => _platform((_Codes codes) => codes.echild);
+
+ /// Resource deadlock would occur
+ // ignore: non_constant_identifier_names
+ static int get EDEADLK => _platform((_Codes codes) => codes.edeadlk);
+
+ /// Math argument out of domain of func
+ // ignore: non_constant_identifier_names
+ static int get EDOM => _platform((_Codes codes) => codes.edom);
+
+ /// File exists
+ // ignore: non_constant_identifier_names
+ static int get EEXIST => _platform((_Codes codes) => codes.eexist);
+
+ /// Bad address
+ // ignore: non_constant_identifier_names
+ static int get EFAULT => _platform((_Codes codes) => codes.efault);
+
+ /// File too large
+ // ignore: non_constant_identifier_names
+ static int get EFBIG => _platform((_Codes codes) => codes.efbig);
+
+ /// Illegal byte sequence
+ // ignore: non_constant_identifier_names
+ static int get EILSEQ => _platform((_Codes codes) => codes.eilseq);
+
+ /// Interrupted system call
+ // ignore: non_constant_identifier_names
+ static int get EINTR => _platform((_Codes codes) => codes.eintr);
+
+ /// Invalid argument
+ // ignore: non_constant_identifier_names
+ static int get EINVAL => _platform((_Codes codes) => codes.einval);
+
+ /// I/O error
+ // ignore: non_constant_identifier_names
+ static int get EIO => _platform((_Codes codes) => codes.eio);
+
+ /// Is a directory
+ // ignore: non_constant_identifier_names
+ static int get EISDIR => _platform((_Codes codes) => codes.eisdir);
+
+ /// Too many levels of symbolic links
+ // ignore: non_constant_identifier_names
+ static int get ELOOP => _platform((_Codes codes) => codes.eloop);
+
+ /// Too many open files
+ // ignore: non_constant_identifier_names
+ static int get EMFILE => _platform((_Codes codes) => codes.emfile);
+
+ /// Too many links
+ // ignore: non_constant_identifier_names
+ static int get EMLINK => _platform((_Codes codes) => codes.emlink);
+
+ /// File name too long
+ // ignore: non_constant_identifier_names
+ static int get ENAMETOOLONG =>
+ _platform((_Codes codes) => codes.enametoolong);
+
+ /// File table overflow
+ // ignore: non_constant_identifier_names
+ static int get ENFILE => _platform((_Codes codes) => codes.enfile);
+
+ /// No such device
+ // ignore: non_constant_identifier_names
+ static int get ENODEV => _platform((_Codes codes) => codes.enodev);
+
+ /// No such file or directory
+ // ignore: non_constant_identifier_names
+ static int get ENOENT => _platform((_Codes codes) => codes.enoent);
+
+ /// Exec format error
+ // ignore: non_constant_identifier_names
+ static int get ENOEXEC => _platform((_Codes codes) => codes.enoexec);
+
+ /// No record locks available
+ // ignore: non_constant_identifier_names
+ static int get ENOLCK => _platform((_Codes codes) => codes.enolck);
+
+ /// Out of memory
+ // ignore: non_constant_identifier_names
+ static int get ENOMEM => _platform((_Codes codes) => codes.enomem);
+
+ /// No space left on device
+ // ignore: non_constant_identifier_names
+ static int get ENOSPC => _platform((_Codes codes) => codes.enospc);
+
+ /// Function not implemented
+ // ignore: non_constant_identifier_names
+ static int get ENOSYS => _platform((_Codes codes) => codes.enosys);
+
+ /// Not a directory
+ // ignore: non_constant_identifier_names
+ static int get ENOTDIR => _platform((_Codes codes) => codes.enotdir);
+
+ /// Directory not empty
+ // ignore: non_constant_identifier_names
+ static int get ENOTEMPTY => _platform((_Codes codes) => codes.enotempty);
+
+ /// Not a typewriter
+ // ignore: non_constant_identifier_names
+ static int get ENOTTY => _platform((_Codes codes) => codes.enotty);
+
+ /// No such device or address
+ // ignore: non_constant_identifier_names
+ static int get ENXIO => _platform((_Codes codes) => codes.enxio);
+
+ /// Operation not permitted
+ // ignore: non_constant_identifier_names
+ static int get EPERM => _platform((_Codes codes) => codes.eperm);
+
+ /// Broken pipe
+ // ignore: non_constant_identifier_names
+ static int get EPIPE => _platform((_Codes codes) => codes.epipe);
+
+ /// Math result not representable
+ // ignore: non_constant_identifier_names
+ static int get ERANGE => _platform((_Codes codes) => codes.erange);
+
+ /// Read-only file system
+ // ignore: non_constant_identifier_names
+ static int get EROFS => _platform((_Codes codes) => codes.erofs);
+
+ /// Illegal seek
+ // ignore: non_constant_identifier_names
+ static int get ESPIPE => _platform((_Codes codes) => codes.espipe);
+
+ /// No such process
+ // ignore: non_constant_identifier_names
+ static int get ESRCH => _platform((_Codes codes) => codes.esrch);
+
+ /// Cross-device link
+ // ignore: non_constant_identifier_names
+ static int get EXDEV => _platform((_Codes codes) => codes.exdev);
+
+ static int _platform(int Function(_Codes codes) getCode) {
+ var codes = (_platforms[operatingSystem] ?? _platforms['linux'])!;
+ return getCode(codes);
+ }
+}
+
+const Map<String, _Codes> _platforms = <String, _Codes>{
+ 'linux': _LinuxCodes(),
+ 'macos': _MacOSCodes(),
+ 'windows': _WindowsCodes(),
+};
+
+abstract class _Codes {
+ int get e2big;
+ int get eacces;
+ int get eagain;
+ int get ebadf;
+ int get ebusy;
+ int get echild;
+ int get edeadlk;
+ int get edom;
+ int get eexist;
+ int get efault;
+ int get efbig;
+ int get eilseq;
+ int get eintr;
+ int get einval;
+ int get eio;
+ int get eisdir;
+ int get eloop;
+ int get emfile;
+ int get emlink;
+ int get enametoolong;
+ int get enfile;
+ int get enodev;
+ int get enoent;
+ int get enoexec;
+ int get enolck;
+ int get enomem;
+ int get enospc;
+ int get enosys;
+ int get enotdir;
+ int get enotempty;
+ int get enotty;
+ int get enxio;
+ int get eperm;
+ int get epipe;
+ int get erange;
+ int get erofs;
+ int get espipe;
+ int get esrch;
+ int get exdev;
+}
+
+class _LinuxCodes implements _Codes {
+ const _LinuxCodes();
+
+ @override
+ int get e2big => 7;
+
+ @override
+ int get eacces => 13;
+
+ @override
+ int get eagain => 11;
+
+ @override
+ int get ebadf => 9;
+
+ @override
+ int get ebusy => 16;
+
+ @override
+ int get echild => 10;
+
+ @override
+ int get edeadlk => 35;
+
+ @override
+ int get edom => 33;
+
+ @override
+ int get eexist => 17;
+
+ @override
+ int get efault => 14;
+
+ @override
+ int get efbig => 27;
+
+ @override
+ int get eilseq => 84;
+
+ @override
+ int get eintr => 4;
+
+ @override
+ int get einval => 22;
+
+ @override
+ int get eio => 5;
+
+ @override
+ int get eisdir => 21;
+
+ @override
+ int get eloop => 40;
+
+ @override
+ int get emfile => 24;
+
+ @override
+ int get emlink => 31;
+
+ @override
+ int get enametoolong => 36;
+
+ @override
+ int get enfile => 23;
+
+ @override
+ int get enodev => 19;
+
+ @override
+ int get enoent => 2;
+
+ @override
+ int get enoexec => 8;
+
+ @override
+ int get enolck => 37;
+
+ @override
+ int get enomem => 12;
+
+ @override
+ int get enospc => 28;
+
+ @override
+ int get enosys => 38;
+
+ @override
+ int get enotdir => 20;
+
+ @override
+ int get enotempty => 39;
+
+ @override
+ int get enotty => 25;
+
+ @override
+ int get enxio => 6;
+
+ @override
+ int get eperm => 1;
+
+ @override
+ int get epipe => 32;
+
+ @override
+ int get erange => 34;
+
+ @override
+ int get erofs => 30;
+
+ @override
+ int get espipe => 29;
+
+ @override
+ int get esrch => 3;
+
+ @override
+ int get exdev => 18;
+}
+
+class _MacOSCodes implements _Codes {
+ const _MacOSCodes();
+
+ @override
+ int get e2big => 7;
+
+ @override
+ int get eacces => 13;
+
+ @override
+ int get eagain => 35;
+
+ @override
+ int get ebadf => 9;
+
+ @override
+ int get ebusy => 16;
+
+ @override
+ int get echild => 10;
+
+ @override
+ int get edeadlk => 11;
+
+ @override
+ int get edom => 33;
+
+ @override
+ int get eexist => 17;
+
+ @override
+ int get efault => 14;
+
+ @override
+ int get efbig => 27;
+
+ @override
+ int get eilseq => 92;
+
+ @override
+ int get eintr => 4;
+
+ @override
+ int get einval => 22;
+
+ @override
+ int get eio => 5;
+
+ @override
+ int get eisdir => 21;
+
+ @override
+ int get eloop => 62;
+
+ @override
+ int get emfile => 24;
+
+ @override
+ int get emlink => 31;
+
+ @override
+ int get enametoolong => 63;
+
+ @override
+ int get enfile => 23;
+
+ @override
+ int get enodev => 19;
+
+ @override
+ int get enoent => 2;
+
+ @override
+ int get enoexec => 8;
+
+ @override
+ int get enolck => 77;
+
+ @override
+ int get enomem => 12;
+
+ @override
+ int get enospc => 28;
+
+ @override
+ int get enosys => 78;
+
+ @override
+ int get enotdir => 20;
+
+ @override
+ int get enotempty => 66;
+
+ @override
+ int get enotty => 25;
+
+ @override
+ int get enxio => 6;
+
+ @override
+ int get eperm => 1;
+
+ @override
+ int get epipe => 32;
+
+ @override
+ int get erange => 34;
+
+ @override
+ int get erofs => 30;
+
+ @override
+ int get espipe => 29;
+
+ @override
+ int get esrch => 3;
+
+ @override
+ int get exdev => 18;
+}
+
+class _WindowsCodes implements _Codes {
+ const _WindowsCodes();
+
+ @override
+ int get e2big => 7;
+
+ @override
+ int get eacces => 13;
+
+ @override
+ int get eagain => 11;
+
+ @override
+ int get ebadf => 9;
+
+ @override
+ int get ebusy => 16;
+
+ @override
+ int get echild => 10;
+
+ @override
+ int get edeadlk => 36;
+
+ @override
+ int get edom => 33;
+
+ @override
+ int get eexist => 17;
+
+ @override
+ int get efault => 14;
+
+ @override
+ int get efbig => 27;
+
+ @override
+ int get eilseq => 42;
+
+ @override
+ int get eintr => 4;
+
+ @override
+ int get einval => 22;
+
+ @override
+ int get eio => 5;
+
+ @override
+ int get eisdir => 21;
+
+ @override
+ int get eloop => -1;
+
+ @override
+ int get emfile => 24;
+
+ @override
+ int get emlink => 31;
+
+ @override
+ int get enametoolong => 38;
+
+ @override
+ int get enfile => 23;
+
+ @override
+ int get enodev => 19;
+
+ @override
+ int get enoent => 2;
+
+ @override
+ int get enoexec => 8;
+
+ @override
+ int get enolck => 39;
+
+ @override
+ int get enomem => 12;
+
+ @override
+ int get enospc => 28;
+
+ @override
+ int get enosys => 40;
+
+ @override
+ int get enotdir => 20;
+
+ @override
+ int get enotempty => 41;
+
+ @override
+ int get enotty => 25;
+
+ @override
+ int get enxio => 6;
+
+ @override
+ int get eperm => 1;
+
+ @override
+ int get epipe => 32;
+
+ @override
+ int get erange => 34;
+
+ @override
+ int get erofs => 30;
+
+ @override
+ int get espipe => 29;
+
+ @override
+ int get esrch => 3;
+
+ @override
+ int get exdev => 18;
+}
diff --git a/pkgs/file/lib/src/interface/error_codes_dart_io.dart b/pkgs/file/lib/src/interface/error_codes_dart_io.dart
new file mode 100644
index 0000000..3f0a97f
--- /dev/null
+++ b/pkgs/file/lib/src/interface/error_codes_dart_io.dart
@@ -0,0 +1,10 @@
+// Copyright (c) 2017, 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;
+
+/// If we have `dart:io` available, we pull the current operating system from
+/// the [Platform] class, so we'll get errno values that match our current
+/// operating system.
+final String operatingSystem = Platform.operatingSystem;
diff --git a/pkgs/file/lib/src/interface/error_codes_internal.dart b/pkgs/file/lib/src/interface/error_codes_internal.dart
new file mode 100644
index 0000000..0a9d7dc
--- /dev/null
+++ b/pkgs/file/lib/src/interface/error_codes_internal.dart
@@ -0,0 +1,8 @@
+// Copyright (c) 2017, 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.
+
+/// In environments that don't have `dart:io`, we can't access the Platform
+/// class to determine what platform we're on, so we just pretend we're on
+/// Linux, meaning we'll get errno values that match Linux's errno.h.
+const String operatingSystem = 'linux';
diff --git a/pkgs/file/lib/src/interface/file.dart b/pkgs/file/lib/src/interface/file.dart
new file mode 100644
index 0000000..87417c6
--- /dev/null
+++ b/pkgs/file/lib/src/interface/file.dart
@@ -0,0 +1,41 @@
+// Copyright (c) 2017, 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 '../io.dart' as io;
+
+import 'file_system_entity.dart';
+
+/// A reference to a file on the file system.
+abstract class File implements FileSystemEntity, io.File {
+ // Override method definitions to codify the return type covariance.
+ @override
+ Future<File> create({bool recursive = false, bool exclusive = false});
+
+ @override
+ Future<File> rename(String newPath);
+
+ @override
+ File renameSync(String newPath);
+
+ @override
+ Future<File> copy(String newPath);
+
+ @override
+ File copySync(String newPath);
+
+ @override
+ File get absolute;
+
+ @override
+ Future<File> writeAsBytes(List<int> bytes,
+ {io.FileMode mode = io.FileMode.write, bool flush = false});
+
+ @override
+ Future<File> writeAsString(String contents,
+ {io.FileMode mode = io.FileMode.write,
+ Encoding encoding = utf8,
+ bool flush = false});
+}
diff --git a/pkgs/file/lib/src/interface/file_system.dart b/pkgs/file/lib/src/interface/file_system.dart
new file mode 100644
index 0000000..2d4e4aa
--- /dev/null
+++ b/pkgs/file/lib/src/interface/file_system.dart
@@ -0,0 +1,163 @@
+// Copyright (c) 2017, 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:meta/meta.dart';
+import 'package:path/path.dart' as p;
+
+import '../io.dart' as io;
+import 'directory.dart';
+import 'file.dart';
+import 'file_system_entity.dart';
+import 'link.dart';
+
+/// A generic representation of a file system.
+///
+/// Note that this class uses `dart:io` only inasmuch as it deals in the types
+/// exposed by the `dart:io` library. Subclasses should document their level of
+/// dependence on the library (and the associated implications of using that
+/// implementation in the browser).
+abstract class FileSystem {
+ /// Creates a new `FileSystem`.
+ const FileSystem();
+
+ /// Returns a reference to a [Directory] at [path].
+ ///
+ /// [path] can be either a [String], a [Uri], or a [FileSystemEntity].
+ Directory directory(dynamic path);
+
+ /// Returns a reference to a [File] at [path].
+ ///
+ /// [path] can be either a [String], a [Uri], or a [FileSystemEntity].
+ File file(dynamic path);
+
+ /// Returns a reference to a [Link] at [path].
+ ///
+ /// [path] can be either a [String], a [Uri], or a [FileSystemEntity].
+ Link link(dynamic path);
+
+ /// An object for manipulating paths in this file system.
+ p.Context get path;
+
+ /// Gets the system temp directory.
+ ///
+ /// It is left to file system implementations to decide how to define the
+ /// "system temp directory".
+ Directory get systemTempDirectory;
+
+ /// Creates a directory object pointing to the current working directory.
+ Directory get currentDirectory;
+
+ /// Sets the current working directory to the specified [path].
+ ///
+ /// The new value set can be either a [Directory] or a [String].
+ ///
+ /// Relative paths will be resolved by the underlying file system
+ /// implementation (meaning it is up to the underlying implementation to
+ /// decide whether to support relative paths).
+ set currentDirectory(dynamic path);
+
+ /// Asynchronously calls the operating system's stat() function on [path].
+ /// Returns a Future which completes with a [io.FileStat] object containing
+ /// the data returned by stat().
+ /// If the call fails, completes the future with a [io.FileStat] object with
+ /// .type set to FileSystemEntityType.NOT_FOUND and the other fields invalid.
+ Future<io.FileStat> stat(String path);
+
+ /// Calls the operating system's stat() function on [path].
+ /// Returns a [io.FileStat] object containing the data returned by stat().
+ /// If the call fails, returns a [io.FileStat] object with .type set to
+ /// FileSystemEntityType.NOT_FOUND and the other fields invalid.
+ io.FileStat statSync(String path);
+
+ /// Checks whether two paths refer to the same object in the
+ /// file system. Returns a [Future<bool>] that completes with the result.
+ ///
+ /// Comparing a link to its target returns false, as does comparing two links
+ /// that point to the same target. To check the target of a link, use
+ /// Link.target explicitly to fetch it. Directory links appearing
+ /// inside a path are followed, though, to find the file system object.
+ ///
+ /// Completes the returned Future with an error if one of the paths points
+ /// to an object that does not exist.
+ Future<bool> identical(String path1, String path2);
+
+ /// Synchronously checks whether two paths refer to the same object in the
+ /// file system.
+ ///
+ /// Comparing a link to its target returns false, as does comparing two links
+ /// that point to the same target. To check the target of a link, use
+ /// Link.target explicitly to fetch it. Directory links appearing
+ /// inside a path are followed, though, to find the file system object.
+ ///
+ /// Throws an error if one of the paths points to an object that does not
+ /// exist.
+ bool identicalSync(String path1, String path2);
+
+ /// Tests if [FileSystemEntity.watch] is supported on the current system.
+ bool get isWatchSupported;
+
+ /// Finds the type of file system object that a [path] points to. Returns
+ /// a `Future<FileSystemEntityType>` that completes with the result.
+ ///
+ /// [io.FileSystemEntityType.link] will only be returned if [followLinks] is
+ /// `false`, and [path] points to a link
+ ///
+ /// If the [path] does not point to a file system object or an error occurs
+ /// then [io.FileSystemEntityType.notFound] is returned.
+ Future<io.FileSystemEntityType> type(String path, {bool followLinks = true});
+
+ /// Syncronously finds the type of file system object that a [path] points
+ /// to. Returns a [io.FileSystemEntityType].
+ ///
+ /// [io.FileSystemEntityType.link] will only be returned if [followLinks] is
+ /// `false`, and [path] points to a link
+ ///
+ /// If the [path] does not point to a file system object or an error occurs
+ /// then [io.FileSystemEntityType.notFound] is returned.
+ io.FileSystemEntityType typeSync(String path, {bool followLinks = true});
+
+ /// Checks if [`type(path)`](type) returns [io.FileSystemEntityType.file].
+ Future<bool> isFile(String path) async =>
+ await type(path) == io.FileSystemEntityType.file;
+
+ /// Synchronously checks if [`type(path)`](type) returns
+ /// [io.FileSystemEntityType.file].
+ bool isFileSync(String path) =>
+ typeSync(path) == io.FileSystemEntityType.file;
+
+ /// Checks if [`type(path)`](type) returns
+ /// [io.FileSystemEntityType.directory].
+ Future<bool> isDirectory(String path) async =>
+ await type(path) == io.FileSystemEntityType.directory;
+
+ /// Synchronously checks if [`type(path)`](type) returns
+ /// [io.FileSystemEntityType.directory].
+ bool isDirectorySync(String path) =>
+ typeSync(path) == io.FileSystemEntityType.directory;
+
+ /// Checks if [`type(path)`](type) returns [io.FileSystemEntityType.link].
+ Future<bool> isLink(String path) async =>
+ await type(path, followLinks: false) == io.FileSystemEntityType.link;
+
+ /// Synchronously checks if [`type(path)`](type) returns
+ /// [io.FileSystemEntityType.link].
+ bool isLinkSync(String path) =>
+ typeSync(path, followLinks: false) == io.FileSystemEntityType.link;
+
+ /// Gets the string path represented by the specified generic [path].
+ ///
+ /// [path] may be a [io.FileSystemEntity], a [String], or a [Uri].
+ @protected
+ String getPath(dynamic path) {
+ if (path is io.FileSystemEntity) {
+ return path.path;
+ } else if (path is String) {
+ return path;
+ } else if (path is Uri) {
+ return this.path.fromUri(path);
+ } else {
+ throw ArgumentError('Invalid type for "path": ${path?.runtimeType}');
+ }
+ }
+}
diff --git a/pkgs/file/lib/src/interface/file_system_entity.dart b/pkgs/file/lib/src/interface/file_system_entity.dart
new file mode 100644
index 0000000..a377397
--- /dev/null
+++ b/pkgs/file/lib/src/interface/file_system_entity.dart
@@ -0,0 +1,42 @@
+// Copyright (c) 2017, 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 '../io.dart' as io;
+
+import 'directory.dart';
+import 'file_system.dart';
+
+/// The common super class for [io.File], [io.Directory], and [io.Link] objects.
+abstract class FileSystemEntity implements io.FileSystemEntity {
+ /// Returns the file system responsible for this entity.
+ FileSystem get fileSystem;
+
+ /// Gets the part of this entity's path after the last separator.
+ ///
+ /// context.basename('path/to/foo.dart'); // -> 'foo.dart'
+ /// context.basename('path/to'); // -> 'to'
+ ///
+ /// Trailing separators are ignored.
+ ///
+ /// context.basename('path/to/'); // -> 'to'
+ String get basename;
+
+ /// Gets the part of this entity's path before the last separator.
+ ///
+ /// context.dirname('path/to/foo.dart'); // -> 'path/to'
+ /// context.dirname('path/to'); // -> 'path'
+ /// context.dirname('foo.dart'); // -> '.'
+ ///
+ /// Trailing separators are ignored.
+ ///
+ /// context.dirname('path/to/'); // -> 'path'
+ String get dirname;
+
+ // Override method definitions to codify the return type covariance.
+ @override
+ Future<FileSystemEntity> delete({bool recursive = false});
+
+ @override
+ Directory get parent;
+}
diff --git a/pkgs/file/lib/src/interface/link.dart b/pkgs/file/lib/src/interface/link.dart
new file mode 100644
index 0000000..27874b3
--- /dev/null
+++ b/pkgs/file/lib/src/interface/link.dart
@@ -0,0 +1,26 @@
+// Copyright (c) 2017, 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 '../io.dart' as io;
+
+import 'file_system_entity.dart';
+
+/// A reference to a symbolic link on the file system.
+abstract class Link implements FileSystemEntity, io.Link {
+ // Override method definitions to codify the return type covariance.
+ @override
+ Future<Link> create(String target, {bool recursive = false});
+
+ @override
+ Future<Link> update(String target);
+
+ @override
+ Future<Link> rename(String newPath);
+
+ @override
+ Link renameSync(String newPath);
+
+ @override
+ Link get absolute;
+}
diff --git a/pkgs/file/lib/src/io.dart b/pkgs/file/lib/src/io.dart
new file mode 100644
index 0000000..28c1d6d
--- /dev/null
+++ b/pkgs/file/lib/src/io.dart
@@ -0,0 +1,28 @@
+// Copyright (c) 2017, 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.
+
+/// For internal use only!
+///
+/// This exposes the subset of the `dart:io` interfaces that are required by
+/// the `file` package. The `file` package re-exports these interfaces (or in
+/// some cases, implementations of these interfaces by the same name), so this
+/// file need not be exposes publicly and exists for internal use only.
+library;
+
+export 'dart:io'
+ show
+ Directory,
+ File,
+ FileLock,
+ FileMode,
+ FileStat,
+ FileSystemEntity,
+ FileSystemEntityType,
+ FileSystemEvent,
+ FileSystemException,
+ IOException,
+ IOSink,
+ Link,
+ OSError,
+ RandomAccessFile;
diff --git a/pkgs/file/pubspec.yaml b/pkgs/file/pubspec.yaml
new file mode 100644
index 0000000..0ad65b0
--- /dev/null
+++ b/pkgs/file/pubspec.yaml
@@ -0,0 +1,21 @@
+name: file
+version: 7.0.2-wip
+description: A pluggable, mockable file system abstraction for Dart.
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/file
+issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Afile
+
+environment:
+ sdk: ^3.0.0
+
+dependencies:
+ meta: ^1.9.1
+ path: ^1.8.3
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ file_testing: ^3.0.0
+ test: ^1.23.1
+
+dependency_overrides:
+ file_testing:
+ path: ../file_testing
diff --git a/pkgs/file/test/chroot_test.dart b/pkgs/file/test/chroot_test.dart
new file mode 100644
index 0000000..cf23f47
--- /dev/null
+++ b/pkgs/file/test/chroot_test.dart
@@ -0,0 +1,181 @@
+// Copyright (c) 2017, 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:io' as io;
+
+import 'package:file/chroot.dart';
+import 'package:file/file.dart';
+import 'package:file/local.dart';
+import 'package:file/memory.dart';
+import 'package:file_testing/file_testing.dart';
+import 'package:test/test.dart';
+
+import 'common_tests.dart';
+
+void main() {
+ group('ChrootFileSystem', () {
+ ChrootFileSystem createMemoryBackedChrootFileSystem() {
+ var fs = MemoryFileSystem();
+ fs.directory('/tmp').createSync();
+ return ChrootFileSystem(fs, '/tmp');
+ }
+
+ // TODO(jamesderlin): Make ChrootFile.openSync return a delegating
+ // RandomAccessFile that uses the chroot'd path.
+ var skipCommon = <String>[
+ // ignore: lines_longer_than_80_chars
+ 'File > open > .* > RandomAccessFile > read > openReadHandleDoesNotChange',
+ 'File > open > .* > RandomAccessFile > openWriteHandleDoesNotChange',
+ ];
+
+ group('memoryBacked', () {
+ runCommonTests(createMemoryBackedChrootFileSystem, skip: skipCommon);
+ });
+
+ group('localBacked', () {
+ late ChrootFileSystem fs;
+ late io.Directory tmp;
+
+ setUp(() {
+ tmp = io.Directory.systemTemp.createTempSync('file_test_');
+ tmp = io.Directory(tmp.resolveSymbolicLinksSync());
+ fs = ChrootFileSystem(const LocalFileSystem(), tmp.path);
+ });
+
+ tearDown(() {
+ tmp.deleteSync(recursive: true);
+ });
+
+ runCommonTests(
+ () => fs,
+ skip: <String>[
+ // https://github.com/dart-lang/sdk/issues/28275
+ 'Link > rename > throwsIfDestinationExistsAsDirectory',
+
+ // https://github.com/dart-lang/sdk/issues/28277
+ 'Link > rename > throwsIfDestinationExistsAsFile',
+
+ ...skipCommon,
+ ],
+ );
+ }, skip: io.Platform.isWindows);
+
+ group('chrootSpecific', () {
+ late ChrootFileSystem fs;
+ late MemoryFileSystem mem;
+
+ setUp(() {
+ fs = createMemoryBackedChrootFileSystem();
+ mem = fs.delegate as MemoryFileSystem;
+ });
+
+ group('FileSystem', () {
+ group('currentDirectory', () {
+ test('staysInJailIfSetToParentOfRoot', () {
+ fs.currentDirectory = '../../../..';
+ fs.file('foo').createSync();
+ expect(mem.file('/tmp/foo'), exists);
+ });
+
+ test('throwsIfSetToSymlinkToDirectoryOutsideJail', () {
+ mem.directory('/bar').createSync();
+ mem.link('/tmp/foo').createSync('/bar');
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.currentDirectory = '/foo';
+ });
+ });
+ });
+
+ group('stat', () {
+ test('isNotFoundForJailbreakPath', () {
+ mem.file('/foo').createSync();
+ expect(fs.statSync('../foo').type, FileSystemEntityType.notFound);
+ });
+
+ test('isNotFoundForSymlinkWithJailbreakTarget', () {
+ mem.file('/foo').createSync();
+ mem.link('/tmp/bar').createSync('/foo');
+ expect(mem.statSync('/tmp/bar').type, FileSystemEntityType.file);
+ expect(fs.statSync('/bar').type, FileSystemEntityType.notFound);
+ });
+
+ test('isNotFoundForSymlinkToOutsideAndBackInsideJail', () {
+ mem.file('/tmp/bar').createSync();
+ mem.link('/foo').createSync('/tmp/bar');
+ mem.link('/tmp/baz').createSync('/foo');
+ expect(mem.statSync('/tmp/baz').type, FileSystemEntityType.file);
+ expect(fs.statSync('/baz').type, FileSystemEntityType.notFound);
+ });
+ });
+
+ group('type', () {
+ test('isNotFoundForJailbreakPath', () {
+ mem.file('/foo').createSync();
+ expect(fs.typeSync('../foo'), FileSystemEntityType.notFound);
+ });
+
+ test('isNotFoundForSymlinkWithJailbreakTarget', () {
+ mem.file('/foo').createSync();
+ mem.link('/tmp/bar').createSync('/foo');
+ expect(mem.typeSync('/tmp/bar'), FileSystemEntityType.file);
+ expect(fs.typeSync('/bar'), FileSystemEntityType.notFound);
+ });
+
+ test('isNotFoundForSymlinkToOutsideAndBackInsideJail', () {
+ mem.file('/tmp/bar').createSync();
+ mem.link('/foo').createSync('/tmp/bar');
+ mem.link('/tmp/baz').createSync('/foo');
+ expect(mem.typeSync('/tmp/baz'), FileSystemEntityType.file);
+ expect(fs.typeSync('/baz'), FileSystemEntityType.notFound);
+ });
+ });
+ });
+
+ group('File', () {
+ group('delegate', () {
+ test('referencesRootEntityForJailbreakPath', () {
+ mem.file('/foo').createSync();
+ dynamic f = fs.file('../foo');
+ // ignore: avoid_dynamic_calls
+ expect(f.delegate.path, '/tmp/foo');
+ });
+ });
+
+ group('create', () {
+ test('createsAtRootIfPathReferencesJailbreakFile', () {
+ fs.file('../foo').createSync();
+ expect(mem.file('/foo'), isNot(exists));
+ expect(mem.file('/tmp/foo'), exists);
+ });
+ });
+
+ group('copy', () {
+ test('copiesToRootDirectoryIfDestinationIsJailbreakPath', () {
+ var f = fs.file('/foo')..createSync();
+ f.copySync('../bar');
+ expect(mem.file('/bar'), isNot(exists));
+ expect(mem.file('/tmp/bar'), exists);
+ });
+ });
+ });
+
+ group('Link', () {
+ group('target', () {
+ test('chrootAndDelegateFileSystemsReturnSameValue', () {
+ mem.file('/foo').createSync();
+ mem.link('/tmp/bar').createSync('/foo');
+ mem.link('/tmp/baz').createSync('../foo');
+ expect(mem.link('/tmp/bar').targetSync(), '/foo');
+ expect(fs.link('/bar').targetSync(), '/foo');
+ expect(mem.link('/tmp/baz').targetSync(), '../foo');
+ expect(fs.link('/baz').targetSync(), '../foo');
+ });
+ });
+ });
+ });
+ });
+}
diff --git a/pkgs/file/test/common_tests.dart b/pkgs/file/test/common_tests.dart
new file mode 100644
index 0000000..491d4f9
--- /dev/null
+++ b/pkgs/file/test/common_tests.dart
@@ -0,0 +1,3516 @@
+// Copyright (c) 2017, 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:async';
+import 'dart:convert';
+import 'dart:io' as io;
+
+import 'package:file/file.dart';
+import 'package:file_testing/file_testing.dart';
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart' as testpkg show group, setUp, tearDown, test;
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+/// Callback used in [runCommonTests] to produce the root folder in which all
+/// file system entities will be created.
+typedef RootPathGenerator = String Function();
+
+/// Callback used in [runCommonTests] to create the file system under test.
+/// It must return either a [FileSystem] or a [Future] that completes with a
+/// [FileSystem].
+typedef FileSystemGenerator = dynamic Function();
+
+/// A function to run before tests (passed to [setUp]) or after tests
+/// (passed to [tearDown]).
+typedef SetUpTearDown = dynamic Function();
+
+/// Runs a suite of tests common to all file system implementations. All file
+/// system implementations should run *at least* these tests to ensure
+/// compliance with file system API.
+///
+/// If [root] is specified, its return value will be used as the root folder
+/// in which all file system entities will be created. If not specified, the
+/// tests will attempt to create entities in the file system root.
+///
+/// [skip] may be used to skip certain tests (or entire groups of tests) in
+/// this suite (to be used, for instance, if a file system implementation is
+/// not yet fully complete). The format of each entry in the list is:
+/// `$group1Description > $group2Description > ... > $testDescription`.
+/// Entries may use regular expression syntax.
+///
+/// If [replay] is specified, each test (and its setup callbacks) will run
+/// twice - once as a "setup" pass with the file system returned by
+/// [createFileSystem], and again as the "test" pass with the file system
+/// returned by [replay]. This is intended for use with `ReplayFileSystem`,
+/// where in order for the file system to behave as expected, a recording of
+/// the invocation(s) must first be made.
+void runCommonTests(
+ FileSystemGenerator createFileSystem, {
+ RootPathGenerator? root,
+ List<String> skip = const <String>[],
+ FileSystemGenerator? replay,
+}) {
+ var rootfn = root;
+
+ group('common', () {
+ late FileSystemGenerator createFs;
+ late List<SetUpTearDown> setUps;
+ late List<SetUpTearDown> tearDowns;
+ late FileSystem fs;
+ late String root;
+ var stack = <String>[];
+
+ void skipIfNecessary(String description, void Function() callback) {
+ stack.add(description);
+ bool matchesCurrentFrame(String input) =>
+ RegExp('^$input\$').hasMatch(stack.join(' > '));
+ if (skip.where(matchesCurrentFrame).isEmpty) {
+ callback();
+ }
+ stack.removeLast();
+ }
+
+ testpkg.setUp(() async {
+ createFs = createFileSystem;
+ setUps = <SetUpTearDown>[];
+ tearDowns = <SetUpTearDown>[];
+ });
+
+ void setUp(FutureOr<void> Function() callback) {
+ testpkg.setUp(replay == null ? callback : () => setUps.add(callback));
+ }
+
+ void tearDown(FutureOr<void> Function() callback) {
+ if (replay == null) {
+ testpkg.tearDown(callback);
+ } else {
+ testpkg.setUp(() => tearDowns.insert(0, callback));
+ }
+ }
+
+ void group(String description, void Function() body) =>
+ skipIfNecessary(description, () => testpkg.group(description, body));
+
+ void test(String description, FutureOr<void> Function() body,
+ {dynamic skip}) =>
+ skipIfNecessary(description, () {
+ if (replay == null) {
+ testpkg.test(description, body, skip: skip);
+ } else {
+ group('rerun', () {
+ testpkg.setUp(() async {
+ await Future.forEach(setUps, (SetUpTearDown setUp) => setUp());
+ await body();
+ for (var tearDown in tearDowns) {
+ await tearDown();
+ }
+ createFs = replay;
+ await Future.forEach(setUps, (SetUpTearDown setUp) => setUp());
+ });
+
+ testpkg.test(description, body, skip: skip);
+
+ testpkg.tearDown(() async {
+ for (var tearDown in tearDowns) {
+ await tearDown();
+ }
+ });
+ });
+ }
+ });
+
+ /// Returns [path] prefixed by the [root] namespace.
+ /// This is only intended for absolute paths.
+ String ns(String path) {
+ var posix = p.Context(style: p.Style.posix);
+ var parts = posix.split(path);
+ parts[0] = root;
+ path = fs.path.joinAll(parts);
+ var rootPrefix = fs.path.rootPrefix(path);
+ assert(rootPrefix.isNotEmpty);
+ var result = root == rootPrefix
+ ? path
+ : (path == rootPrefix
+ ? root
+ : fs.path.join(root, fs.path.joinAll(parts.sublist(1))));
+ return result;
+ }
+
+ setUp(() async {
+ root = rootfn != null ? rootfn() : '/';
+ fs = await createFs() as FileSystem;
+ assert(fs.path.isAbsolute(root));
+ assert(!root.endsWith(fs.path.separator) ||
+ fs.path.rootPrefix(root) == root);
+ });
+
+ group('FileSystem', () {
+ group('directory', () {
+ test('allowsStringArgument', () {
+ expect(fs.directory(ns('/foo')), isDirectory);
+ });
+
+ test('allowsUriArgument', () {
+ expect(fs.directory(Uri.parse('file:///')), isDirectory);
+ });
+
+ test('succeedsWithUriArgument', () {
+ fs.directory(ns('/foo')).createSync();
+ var uri = fs.path.toUri(ns('/foo'));
+ expect(fs.directory(uri), exists);
+ });
+
+ test('allowsDirectoryArgument', () {
+ expect(fs.directory(io.Directory(ns('/foo'))), isDirectory);
+ });
+
+ test('disallowsOtherArgumentType', () {
+ expect(() => fs.directory(123), throwsArgumentError);
+ });
+
+ // Fails due to
+ // https://github.com/dart-lang/tools/issues/632
+ test('considersBothSlashesEquivalent', () {
+ fs.directory(r'foo\bar_dir').createSync(recursive: true);
+ expect(fs.directory(r'foo/bar_dir'), exists);
+ }, skip: 'Fails due to https://github.com/dart-lang/tools/issues/632');
+ });
+
+ group('file', () {
+ test('allowsStringArgument', () {
+ expect(fs.file(ns('/foo')), isFile);
+ });
+
+ test('allowsUriArgument', () {
+ expect(fs.file(Uri.parse('file:///')), isFile);
+ });
+
+ test('succeedsWithUriArgument', () {
+ fs.file(ns('/foo')).createSync();
+ var uri = fs.path.toUri(ns('/foo'));
+ expect(fs.file(uri), exists);
+ });
+
+ test('allowsDirectoryArgument', () {
+ expect(fs.file(io.File(ns('/foo'))), isFile);
+ });
+
+ test('disallowsOtherArgumentType', () {
+ expect(() => fs.file(123), throwsArgumentError);
+ });
+
+ // Fails due to
+ // https://github.com/dart-lang/tools/issues/632
+ test('considersBothSlashesEquivalent', () {
+ fs.file(r'foo\bar_file').createSync(recursive: true);
+ expect(fs.file(r'foo/bar_file'), exists);
+ }, skip: 'Fails due to https://github.com/dart-lang/tools/issues/632');
+ });
+
+ group('link', () {
+ test('allowsStringArgument', () {
+ expect(fs.link(ns('/foo')), isLink);
+ });
+
+ test('allowsUriArgument', () {
+ expect(fs.link(Uri.parse('file:///')), isLink);
+ });
+
+ test('succeedsWithUriArgument', () {
+ fs.file(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ var uri = fs.path.toUri(ns('/bar'));
+ expect(fs.link(uri), exists);
+ });
+
+ test('allowsDirectoryArgument', () {
+ expect(fs.link(io.File(ns('/foo'))), isLink);
+ });
+
+ test('disallowsOtherArgumentType', () {
+ expect(() => fs.link(123), throwsArgumentError);
+ });
+ });
+
+ group('path', () {
+ test('hasCorrectCurrentWorkingDirectory', () {
+ expect(fs.path.current, fs.currentDirectory.path);
+ });
+
+ test('separatorIsAmongExpectedValues', () {
+ expect(fs.path.separator, anyOf('/', r'\'));
+ });
+ });
+
+ group('systemTempDirectory', () {
+ test('existsAsDirectory', () {
+ var tmp = fs.systemTempDirectory;
+ expect(tmp, isDirectory);
+ expect(tmp, exists);
+ });
+ });
+
+ group('currentDirectory', () {
+ test('defaultsToRoot', () {
+ expect(fs.currentDirectory.path, root);
+ });
+
+ test('throwsIfSetToNonExistentPath', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.currentDirectory = ns('/foo');
+ });
+ });
+
+ test('throwsIfHasNonExistentPathInComplexChain', () {
+ fs.directory(ns('/foo')).createSync();
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.currentDirectory = ns('/bar/../foo');
+ });
+ });
+
+ test('succeedsIfSetToValidStringPath', () {
+ fs.directory(ns('/foo')).createSync();
+ fs.currentDirectory = ns('/foo');
+ expect(fs.currentDirectory.path, ns('/foo'));
+ });
+
+ test('succeedsIfSetToValidDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ fs.currentDirectory = io.Directory(ns('/foo'));
+ expect(fs.currentDirectory.path, ns('/foo'));
+ });
+
+ test('throwsIfArgumentIsNotStringOrDirectory', () {
+ expect(() {
+ fs.currentDirectory = 123;
+ }, throwsArgumentError);
+ });
+
+ test('succeedsIfSetToRelativePath', () {
+ fs.directory(ns('/foo/bar')).createSync(recursive: true);
+ fs.currentDirectory = 'foo';
+ expect(fs.currentDirectory.path, ns('/foo'));
+ fs.currentDirectory = 'bar';
+ expect(fs.currentDirectory.path, ns('/foo/bar'));
+ });
+
+ test('succeedsIfSetToAbsolutePathWhenCwdIsNotRoot', () {
+ fs.directory(ns('/foo/bar')).createSync(recursive: true);
+ fs.directory(ns('/baz/qux')).createSync(recursive: true);
+ fs.currentDirectory = ns('/foo/bar');
+ expect(fs.currentDirectory.path, ns('/foo/bar'));
+ fs.currentDirectory = fs.directory(ns('/baz/qux'));
+ expect(fs.currentDirectory.path, ns('/baz/qux'));
+ });
+
+ test('succeedsIfSetToParentDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ fs.currentDirectory = 'foo';
+ expect(fs.currentDirectory.path, ns('/foo'));
+ fs.currentDirectory = '..';
+ expect(fs.currentDirectory.path, ns('/'));
+ });
+
+ test('staysAtRootIfSetToParentOfRoot', () {
+ fs.currentDirectory =
+ List<String>.filled(20, '..').join(fs.path.separator);
+ var cwd = fs.currentDirectory.path;
+ expect(cwd, fs.path.rootPrefix(cwd));
+ });
+
+ test('removesTrailingSlashIfSet', () {
+ fs.directory(ns('/foo')).createSync();
+ fs.currentDirectory = ns('/foo/');
+ expect(fs.currentDirectory.path, ns('/foo'));
+ });
+
+ test('throwsIfSetToFilePathSegmentAtTail', () {
+ fs.file(ns('/foo')).createSync();
+ expectFileSystemException(ErrorCodes.ENOTDIR, () {
+ fs.currentDirectory = ns('/foo');
+ });
+ });
+
+ test('throwsIfSetToFilePathSegmentViaTraversal', () {
+ fs.file(ns('/foo')).createSync();
+ expectFileSystemException(ErrorCodes.ENOTDIR, () {
+ fs.currentDirectory = ns('/foo/bar/baz');
+ });
+ });
+
+ test('resolvesLinksIfEncountered', () {
+ fs.link(ns('/foo/bar/baz')).createSync(ns('/qux'), recursive: true);
+ fs.directory(ns('/qux')).createSync();
+ fs.directory(ns('/quux')).createSync();
+ fs.currentDirectory = ns('/foo/bar/baz/../quux/');
+ expect(fs.currentDirectory.path, ns('/quux'));
+ });
+
+ test('succeedsIfSetToDirectoryLinkAtTail', () {
+ fs.directory(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ fs.currentDirectory = ns('/bar');
+ expect(fs.currentDirectory.path, ns('/foo'));
+ });
+
+ test('throwsIfSetToLinkLoop', () {
+ fs.link(ns('/foo')).createSync(ns('/bar'));
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ expectFileSystemException(
+ anyOf(ErrorCodes.EMLINK, ErrorCodes.ELOOP),
+ () {
+ fs.currentDirectory = ns('/foo');
+ },
+ );
+ });
+ });
+
+ group('stat', () {
+ test('isNotFoundForEmptyPath', () {
+ var stat = fs.statSync('');
+ expect(stat.type, FileSystemEntityType.notFound);
+ });
+
+ test('isNotFoundForPathToNonExistentEntityAtTail', () {
+ var stat = fs.statSync(ns('/foo'));
+ expect(stat.type, FileSystemEntityType.notFound);
+ });
+
+ test('isNotFoundForPathToNonExistentEntityInTraversal', () {
+ var stat = fs.statSync(ns('/foo/bar'));
+ expect(stat.type, FileSystemEntityType.notFound);
+ });
+
+ test('isDirectoryForDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ var stat = fs.statSync(ns('/foo'));
+ expect(stat.type, FileSystemEntityType.directory);
+ });
+
+ test('isFileForFile', () {
+ fs.file(ns('/foo')).createSync();
+ var stat = fs.statSync(ns('/foo'));
+ expect(stat.type, FileSystemEntityType.file);
+ });
+
+ test('isFileForLinkToFile', () {
+ fs.file(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ var stat = fs.statSync(ns('/bar'));
+ expect(stat.type, FileSystemEntityType.file);
+ });
+
+ test('isNotFoundForLinkWithCircularReference', () {
+ fs.link(ns('/foo')).createSync(ns('/bar'));
+ fs.link(ns('/bar')).createSync(ns('/baz'));
+ fs.link(ns('/baz')).createSync(ns('/foo'));
+ var stat = fs.statSync(ns('/foo'));
+ expect(stat.type, FileSystemEntityType.notFound);
+ });
+ });
+
+ group('identical', () {
+ test('isTrueForIdenticalPathsToExistentFile', () {
+ fs.file(ns('/foo')).createSync();
+ expect(fs.identicalSync(ns('/foo'), ns('/foo')), true);
+ });
+
+ test('isFalseForDifferentPathsToDifferentFiles', () {
+ fs.file(ns('/foo')).createSync();
+ fs.file(ns('/bar')).createSync();
+ expect(fs.identicalSync(ns('/foo'), ns('/bar')), false);
+ });
+
+ test('isTrueForDifferentPathsToSameFileViaLinkInTraversal', () {
+ fs.file(ns('/foo/file')).createSync(recursive: true);
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ expect(fs.identicalSync(ns('/foo/file'), ns('/bar/file')), true);
+ });
+
+ test('isFalseForDifferentPathsToSameFileViaLinkAtTail', () {
+ fs.file(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ expect(fs.identicalSync(ns('/foo'), ns('/bar')), false);
+ });
+
+ test('throwsForDifferentPathsToNonExistentEntities', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.identicalSync(ns('/foo'), ns('/bar'));
+ });
+ });
+
+ test('throwsForDifferentPathsToOneFileOneNonExistentEntity', () {
+ fs.file(ns('/foo')).createSync();
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.identicalSync(ns('/foo'), ns('/bar'));
+ });
+ });
+ });
+
+ group('type', () {
+ test('isFileForFile', () {
+ fs.file(ns('/foo')).createSync();
+ var type = fs.typeSync(ns('/foo'));
+ expect(type, FileSystemEntityType.file);
+ });
+
+ test('isDirectoryForDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ var type = fs.typeSync(ns('/foo'));
+ expect(type, FileSystemEntityType.directory);
+ });
+
+ test('isDirectoryForAncestorOfRoot', () {
+ var type = fs
+ .typeSync(List<String>.filled(20, '..').join(fs.path.separator));
+ expect(type, FileSystemEntityType.directory);
+ });
+
+ test('isFileForLinkToFileAndFollowLinksTrue', () {
+ fs.file(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ var type = fs.typeSync(ns('/bar'));
+ expect(type, FileSystemEntityType.file);
+ });
+
+ test('isLinkForLinkToFileAndFollowLinksFalse', () {
+ fs.file(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ var type = fs.typeSync(ns('/bar'), followLinks: false);
+ expect(type, FileSystemEntityType.link);
+ });
+
+ test('isNotFoundForLinkWithCircularReferenceAndFollowLinksTrue', () {
+ fs.link(ns('/foo')).createSync(ns('/bar'));
+ fs.link(ns('/bar')).createSync(ns('/baz'));
+ fs.link(ns('/baz')).createSync(ns('/foo'));
+ var type = fs.typeSync(ns('/foo'));
+ expect(type, FileSystemEntityType.notFound);
+ });
+
+ test('isNotFoundForNoEntityAtTail', () {
+ var type = fs.typeSync(ns('/foo'));
+ expect(type, FileSystemEntityType.notFound);
+ });
+
+ test('isNotFoundForNoDirectoryInTraversal', () {
+ var type = fs.typeSync(ns('/foo/bar/baz'));
+ expect(type, FileSystemEntityType.notFound);
+ });
+ });
+
+ group('isFile/isDirectory/isLink', () {
+ late String filePath;
+ late String directoryPath;
+ late String fileLinkPath;
+ late String directoryLinkPath;
+
+ setUp(() {
+ filePath = ns('/file');
+ directoryPath = ns('/directory');
+ fileLinkPath = ns('/file-link');
+ directoryLinkPath = ns('/directory-link');
+
+ fs.file(filePath).createSync();
+ fs.directory(directoryPath).createSync();
+ fs.link(fileLinkPath).createSync(filePath);
+ fs.link(directoryLinkPath).createSync(directoryPath);
+ });
+
+ test('isFile', () {
+ expect(fs.isFileSync(filePath), true);
+ expect(fs.isFileSync(directoryPath), false);
+ expect(fs.isFileSync(fileLinkPath), true);
+ expect(fs.isFileSync(directoryLinkPath), false);
+ });
+
+ test('isDirectory', () {
+ expect(fs.isDirectorySync(filePath), false);
+ expect(fs.isDirectorySync(directoryPath), true);
+ expect(fs.isDirectorySync(fileLinkPath), false);
+ expect(fs.isDirectorySync(directoryLinkPath), true);
+ });
+
+ test('isLink', () {
+ expect(fs.isLinkSync(filePath), false);
+ expect(fs.isLinkSync(directoryPath), false);
+ expect(fs.isLinkSync(fileLinkPath), true);
+ expect(fs.isLinkSync(directoryLinkPath), true);
+ });
+ });
+ });
+
+ group('Directory', () {
+ test('uri', () {
+ expect(fs.directory(ns('/foo')).uri, fs.path.toUri('${ns('/foo')}/'));
+ expect(fs.directory('foo').uri.toString(), 'foo/');
+ });
+
+ group('exists', () {
+ test('falseIfNotExists', () {
+ expect(fs.directory(ns('/foo')), isNot(exists));
+ expect(fs.directory('foo'), isNot(exists));
+ expect(fs.directory(ns('/foo/bar')), isNot(exists));
+ });
+
+ test('trueIfExistsAsDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ expect(fs.directory(ns('/foo')), exists);
+ expect(fs.directory('foo'), exists);
+ });
+
+ test('falseIfExistsAsFile', () {
+ fs.file(ns('/foo')).createSync();
+ expect(fs.directory(ns('/foo')), isNot(exists));
+ expect(fs.directory('foo'), isNot(exists));
+ });
+
+ test('trueIfExistsAsLinkToDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ expect(fs.directory(ns('/bar')), exists);
+ expect(fs.directory('bar'), exists);
+ });
+
+ test('falseIfExistsAsLinkToFile', () {
+ fs.file(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ expect(fs.directory(ns('/bar')), isNot(exists));
+ expect(fs.directory('bar'), isNot(exists));
+ });
+
+ test('falseIfNotFoundSegmentExistsThenIsBackedOut', () {
+ fs.directory(ns('/foo')).createSync();
+ expect(fs.directory(ns('/bar/../foo')), isNot(exists));
+ });
+ });
+
+ group('create', () {
+ test('returnsCovariantType', () async {
+ expect(await fs.directory(ns('/foo')).create(), isDirectory);
+ });
+
+ test('succeedsIfAlreadyExistsAsDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ fs.directory(ns('/foo')).createSync();
+ });
+
+ test('throwsIfAlreadyExistsAsFile', () {
+ fs.file(ns('/foo')).createSync();
+ expectFileSystemException(ErrorCodes.ENOTDIR, () {
+ fs.directory(ns('/foo')).createSync();
+ });
+ });
+
+ test('succeedsIfAlreadyExistsAsLinkToDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ fs.directory(ns('/bar')).createSync();
+ });
+
+ test('throwsIfAlreadyExistsAsLinkToFile', () {
+ fs.file(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ // TODO(tvolkert): Change this to just be 'Not a directory'
+ // once Dart 1.22 is stable.
+ expectFileSystemException(
+ anyOf(ErrorCodes.EEXIST, ErrorCodes.ENOTDIR),
+ () {
+ fs.directory(ns('/bar')).createSync();
+ },
+ );
+ });
+
+ test('throwsIfAlreadyExistsAsLinkToNotFoundAtTail', () {
+ fs.link(ns('/foo')).createSync(ns('/bar'));
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.directory(ns('/foo')).createSync();
+ });
+ });
+
+ test('throwsIfAlreadyExistsAsLinkToNotFoundViaTraversal', () {
+ fs.link(ns('/foo')).createSync(ns('/bar/baz'));
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.directory(ns('/foo')).createSync();
+ });
+ });
+
+ test('throwsIfAlreadyExistsAsLinkToNotFoundInDifferentDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ fs.directory(ns('/bar')).createSync();
+ fs.link(ns('/bar/baz')).createSync(ns('/foo/qux'));
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.directory(ns('/bar/baz')).createSync();
+ });
+ });
+
+ test('succeedsIfTailDoesntExist', () {
+ expect(fs.directory(ns('/')), exists);
+ fs.directory(ns('/foo')).createSync();
+ expect(fs.directory(ns('/foo')), exists);
+ });
+
+ test('throwsIfAncestorDoesntExistRecursiveFalse', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.directory(ns('/foo/bar')).createSync();
+ });
+ });
+
+ test('succeedsIfAncestorDoesntExistRecursiveTrue', () {
+ fs.directory(ns('/foo/bar')).createSync(recursive: true);
+ expect(fs.directory(ns('/foo')), exists);
+ expect(fs.directory(ns('/foo/bar')), exists);
+ });
+ });
+
+ group('rename', () {
+ test('returnsCovariantType', () async {
+ Directory src() => fs.directory(ns('/foo'))..createSync();
+ expect(src().renameSync(ns('/bar')), isDirectory);
+ expect(await src().rename(ns('/baz')), isDirectory);
+ });
+
+ test('succeedsIfDestinationDoesntExist', () {
+ var src = fs.directory(ns('/foo'))..createSync();
+ var dest = src.renameSync(ns('/bar'));
+ expect(dest.path, ns('/bar'));
+ expect(dest, exists);
+ });
+
+ test(
+ 'succeedsIfDestinationIsEmptyDirectory',
+ () {
+ fs.directory(ns('/bar')).createSync();
+ var src = fs.directory(ns('/foo'))..createSync();
+ var dest = src.renameSync(ns('/bar'));
+ expect(src, isNot(exists));
+ expect(dest, exists);
+ },
+ // See https://github.com/google/file.dart/issues/197.
+ skip: io.Platform.isWindows,
+ );
+
+ test('throwsIfDestinationIsFile', () {
+ fs.file(ns('/bar')).createSync();
+ var src = fs.directory(ns('/foo'))..createSync();
+ expectFileSystemException(ErrorCodes.ENOTDIR, () {
+ src.renameSync(ns('/bar'));
+ });
+ });
+
+ test('throwsIfDestinationParentFolderDoesntExist', () {
+ var src = fs.directory(ns('/foo'))..createSync();
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ src.renameSync(ns('/bar/baz'));
+ });
+ });
+
+ test('throwsIfDestinationIsNonEmptyDirectory', () {
+ fs.file(ns('/bar/baz')).createSync(recursive: true);
+ var src = fs.directory(ns('/foo'))..createSync();
+ // The error will be 'Directory not empty' on OS X, but it will be
+ // 'File exists' on Linux.
+ expectFileSystemException(
+ anyOf(ErrorCodes.ENOTEMPTY, ErrorCodes.EEXIST),
+ () {
+ src.renameSync(ns('/bar'));
+ },
+ );
+ });
+
+ test('throwsIfSourceDoesntExist', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.directory(ns('/foo')).renameSync(ns('/bar'));
+ });
+ });
+
+ test('throwsIfSourceIsFile', () {
+ fs.file(ns('/foo')).createSync();
+ expectFileSystemException(ErrorCodes.ENOTDIR, () {
+ fs.directory(ns('/foo')).renameSync(ns('/bar'));
+ });
+ });
+
+ test('succeedsIfSourceIsLinkToDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ fs.directory(ns('/bar')).renameSync(ns('/baz'));
+ expect(fs.typeSync(ns('/foo'), followLinks: false),
+ FileSystemEntityType.directory);
+ expect(fs.typeSync(ns('/bar')), FileSystemEntityType.notFound);
+ expect(fs.typeSync(ns('/baz'), followLinks: false),
+ FileSystemEntityType.link);
+ expect(fs.link(ns('/baz')).targetSync(), ns('/foo'));
+ });
+
+ test('throwsIfDestinationIsLinkToNotFound', () {
+ var src = fs.directory(ns('/foo'))..createSync();
+ fs.link(ns('/bar')).createSync(ns('/baz'));
+ expectFileSystemException(ErrorCodes.ENOTDIR, () {
+ src.renameSync(ns('/bar'));
+ });
+ });
+
+ test('throwsIfDestinationIsLinkToEmptyDirectory', () {
+ var src = fs.directory(ns('/foo'))..createSync();
+ fs.directory(ns('/bar')).createSync();
+ fs.link(ns('/baz')).createSync(ns('/bar'));
+ expectFileSystemException(ErrorCodes.ENOTDIR, () {
+ src.renameSync(ns('/baz'));
+ });
+ });
+
+ test('succeedsIfDestinationIsInDifferentDirectory', () {
+ var src = fs.directory(ns('/foo'))..createSync();
+ fs.directory(ns('/bar')).createSync();
+ src.renameSync(ns('/bar/baz'));
+ expect(fs.typeSync(ns('/foo')), FileSystemEntityType.notFound);
+ expect(fs.typeSync(ns('/bar/baz')), FileSystemEntityType.directory);
+ });
+
+ test('succeedsIfSourceIsLinkToDifferentDirectory', () {
+ fs.directory(ns('/foo/subfoo')).createSync(recursive: true);
+ fs.directory(ns('/bar/subbar')).createSync(recursive: true);
+ fs.directory(ns('/baz/subbaz')).createSync(recursive: true);
+ fs.link(ns('/foo/subfoo/lnk')).createSync(ns('/bar/subbar'));
+ fs.directory(ns('/foo/subfoo/lnk')).renameSync(ns('/baz/subbaz/dst'));
+ expect(fs.typeSync(ns('/foo/subfoo/lnk')),
+ FileSystemEntityType.notFound);
+ expect(fs.typeSync(ns('/baz/subbaz/dst'), followLinks: false),
+ FileSystemEntityType.link);
+ expect(fs.typeSync(ns('/baz/subbaz/dst'), followLinks: true),
+ FileSystemEntityType.directory);
+ });
+ });
+
+ group('delete', () {
+ test('returnsCovariantType', () async {
+ var dir = fs.directory(ns('/foo'))..createSync();
+ expect(await dir.delete(), isDirectory);
+ });
+
+ test('succeedsIfEmptyDirectoryExistsAndRecursiveFalse', () {
+ var dir = fs.directory(ns('/foo'))..createSync();
+ dir.deleteSync();
+ expect(dir, isNot(exists));
+ });
+
+ test('succeedsIfEmptyDirectoryExistsAndRecursiveTrue', () {
+ var dir = fs.directory(ns('/foo'))..createSync();
+ dir.deleteSync(recursive: true);
+ expect(dir, isNot(exists));
+ });
+
+ test('throwsIfNonEmptyDirectoryExistsAndRecursiveFalse', () {
+ var dir = fs.directory(ns('/foo'))..createSync();
+ fs.file(ns('/foo/bar')).createSync();
+ expectFileSystemException(ErrorCodes.ENOTEMPTY, () {
+ dir.deleteSync();
+ });
+ });
+
+ test('succeedsIfNonEmptyDirectoryExistsAndRecursiveTrue', () {
+ var dir = fs.directory(ns('/foo'))..createSync();
+ fs.file(ns('/foo/bar')).createSync();
+ dir.deleteSync(recursive: true);
+ expect(fs.directory(ns('/foo')), isNot(exists));
+ expect(fs.file(ns('/foo/bar')), isNot(exists));
+ });
+
+ test('throwsIfDirectoryDoesntExistAndRecursiveFalse', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.directory(ns('/foo')).deleteSync();
+ });
+ });
+
+ test('throwsIfDirectoryDoesntExistAndRecursiveTrue', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.directory(ns('/foo')).deleteSync(recursive: true);
+ });
+ });
+
+ test('succeedsIfPathReferencesFileAndRecursiveTrue', () {
+ fs.file(ns('/foo')).createSync();
+ fs.directory(ns('/foo')).deleteSync(recursive: true);
+ expect(fs.typeSync(ns('/foo')), FileSystemEntityType.notFound);
+ });
+
+ test('throwsIfPathReferencesFileAndRecursiveFalse', () {
+ fs.file(ns('/foo')).createSync();
+ expectFileSystemException(ErrorCodes.ENOTDIR, () {
+ fs.directory(ns('/foo')).deleteSync();
+ });
+ });
+
+ test('succeedsIfPathReferencesLinkToDirectoryAndRecursiveTrue', () {
+ fs.directory(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ fs.directory(ns('/bar')).deleteSync(recursive: true);
+ expect(fs.typeSync(ns('/foo'), followLinks: false),
+ FileSystemEntityType.directory);
+ expect(fs.typeSync(ns('/bar'), followLinks: false),
+ FileSystemEntityType.notFound);
+ });
+
+ test('succeedsIfPathReferencesLinkToDirectoryAndRecursiveFalse', () {
+ fs.directory(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ fs.directory(ns('/bar')).deleteSync();
+ expect(fs.typeSync(ns('/foo'), followLinks: false),
+ FileSystemEntityType.directory);
+ expect(fs.typeSync(ns('/bar'), followLinks: false),
+ FileSystemEntityType.notFound);
+ });
+
+ test('succeedsIfExistsAsLinkToDirectoryInDifferentDirectory', () {
+ fs.directory(ns('/foo/bar')).createSync(recursive: true);
+ fs.link(ns('/baz/qux')).createSync(ns('/foo/bar'), recursive: true);
+ fs.directory(ns('/baz/qux')).deleteSync();
+ expect(fs.typeSync(ns('/foo/bar'), followLinks: false),
+ FileSystemEntityType.directory);
+ expect(fs.typeSync(ns('/baz/qux'), followLinks: false),
+ FileSystemEntityType.notFound);
+ });
+
+ test('succeedsIfPathReferencesLinkToFileAndRecursiveTrue', () {
+ fs.file(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ fs.directory(ns('/bar')).deleteSync(recursive: true);
+ expect(fs.typeSync(ns('/foo'), followLinks: false),
+ FileSystemEntityType.file);
+ expect(fs.typeSync(ns('/bar'), followLinks: false),
+ FileSystemEntityType.notFound);
+ });
+
+ test('throwsIfPathReferencesLinkToFileAndRecursiveFalse', () {
+ fs.file(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ expectFileSystemException(ErrorCodes.ENOTDIR, () {
+ fs.directory(ns('/bar')).deleteSync();
+ });
+ });
+
+ test('throwsIfPathReferencesLinkToNotFoundAndRecursiveFalse', () {
+ fs.link(ns('/foo')).createSync(ns('/bar'));
+ expectFileSystemException(ErrorCodes.ENOTDIR, () {
+ fs.directory(ns('/foo')).deleteSync();
+ });
+ });
+ });
+
+ group('resolveSymbolicLinks', () {
+ test('succeedsForRootDirectory', () {
+ expect(fs.directory(ns('/')).resolveSymbolicLinksSync(), ns('/'));
+ });
+
+ test('throwsIfPathIsEmpty', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.directory('').resolveSymbolicLinksSync();
+ });
+ });
+
+ test('throwsIfLoopInLinkChain', () {
+ fs.link(ns('/foo')).createSync(ns('/bar'));
+ fs.link(ns('/bar')).createSync(ns('/baz'));
+ fs.link(ns('/baz')).createSync(ns('/foo'));
+ expectFileSystemException(
+ anyOf(ErrorCodes.EMLINK, ErrorCodes.ELOOP),
+ () {
+ fs.directory(ns('/foo')).resolveSymbolicLinksSync();
+ },
+ );
+ });
+
+ test('throwsIfPathNotFoundInTraversal', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.directory(ns('/foo/bar')).resolveSymbolicLinksSync();
+ });
+ });
+
+ test('throwsIfPathNotFoundAtTail', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.directory(ns('/foo')).resolveSymbolicLinksSync();
+ });
+ });
+
+ test('throwsIfPathNotFoundInMiddleThenBackedOut', () {
+ fs.directory(ns('/foo/bar')).createSync(recursive: true);
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.directory(ns('/foo/baz/../bar')).resolveSymbolicLinksSync();
+ });
+ });
+
+ test('resolvesRelativePathToCurrentDirectory', () {
+ fs.directory(ns('/foo/bar')).createSync(recursive: true);
+ fs.link(ns('/foo/baz')).createSync(ns('/foo/bar'));
+ fs.currentDirectory = ns('/foo');
+ expect(
+ fs.directory('baz').resolveSymbolicLinksSync(), ns('/foo/bar'));
+ });
+
+ test('resolvesAbsolutePathsAbsolutely', () {
+ fs.directory(ns('/foo/bar')).createSync(recursive: true);
+ fs.currentDirectory = ns('/foo');
+ expect(fs.directory(ns('/foo/bar')).resolveSymbolicLinksSync(),
+ ns('/foo/bar'));
+ });
+
+ test('handlesRelativeLinks', () {
+ fs.directory(ns('/foo/bar/baz')).createSync(recursive: true);
+ fs.link(ns('/foo/qux')).createSync(fs.path.join('bar', 'baz'));
+ expect(
+ fs.directory(ns('/foo/qux')).resolveSymbolicLinksSync(),
+ ns('/foo/bar/baz'),
+ );
+ expect(
+ fs.directory(fs.path.join('foo', 'qux')).resolveSymbolicLinksSync(),
+ ns('/foo/bar/baz'),
+ );
+ });
+
+ test('handlesAbsoluteLinks', () {
+ fs.directory(ns('/foo')).createSync();
+ fs.directory(ns('/bar/baz/qux')).createSync(recursive: true);
+ fs.link(ns('/foo/quux')).createSync(ns('/bar/baz/qux'));
+ expect(fs.directory(ns('/foo/quux')).resolveSymbolicLinksSync(),
+ ns('/bar/baz/qux'));
+ });
+
+ test('handlesLinksWhoseTargetsHaveNestedLinks', () {
+ fs.directory(ns('/foo')).createSync();
+ fs.link(ns('/foo/quuz')).createSync(ns('/bar'));
+ fs.link(ns('/foo/grault')).createSync(ns('/baz/quux'));
+ fs.directory(ns('/bar')).createSync();
+ fs.link(ns('/bar/qux')).createSync(ns('/baz'));
+ fs.link(ns('/bar/garply')).createSync(ns('/foo'));
+ fs.directory(ns('/baz')).createSync();
+ fs.link(ns('/baz/quux')).createSync(ns('/bar/garply/quuz'));
+ expect(fs.directory(ns('/foo/grault/qux')).resolveSymbolicLinksSync(),
+ ns('/baz'));
+ });
+
+ test('handlesParentAndThisFolderReferences', () {
+ fs.directory(ns('/foo/bar/baz')).createSync(recursive: true);
+ fs.link(ns('/foo/bar/baz/qux')).createSync(fs.path.join('..', '..'));
+ var resolved = fs
+ .directory(ns('/foo/./bar/baz/../baz/qux/bar'))
+ .resolveSymbolicLinksSync();
+ expect(resolved, ns('/foo/bar'));
+ });
+
+ test('handlesBackToBackSlashesInPath', () {
+ fs.directory(ns('/foo/bar/baz')).createSync(recursive: true);
+ expect(fs.directory(ns('//foo/bar///baz')).resolveSymbolicLinksSync(),
+ ns('/foo/bar/baz'));
+ });
+
+ test('handlesComplexPathWithMultipleLinks', () {
+ fs
+ .link(ns('/foo/bar/baz'))
+ .createSync(fs.path.join('..', '..', 'qux'), recursive: true);
+ fs.link(ns('/qux')).createSync('quux');
+ fs.link(ns('/quux/quuz')).createSync(ns('/foo'), recursive: true);
+ var resolved = fs
+ .directory(ns('/foo//bar/./baz/quuz/bar/..///bar/baz/'))
+ .resolveSymbolicLinksSync();
+ expect(resolved, ns('/quux'));
+ });
+ });
+
+ group('absolute', () {
+ test('returnsCovariantType', () {
+ expect(fs.directory('foo').absolute, isDirectory);
+ });
+
+ test('returnsSamePathIfAlreadyAbsolute', () {
+ expect(fs.directory(ns('/foo')).absolute.path, ns('/foo'));
+ });
+
+ test('succeedsForRelativePaths', () {
+ expect(fs.directory('foo').absolute.path, ns('/foo'));
+ });
+ });
+
+ group('parent', () {
+ late String root;
+
+ setUp(() {
+ root = fs.path.style.name == 'windows' ? r'C:\' : '/';
+ });
+
+ test('returnsCovariantType', () {
+ expect(fs.directory(root).parent, isDirectory);
+ });
+
+ test('returnsRootForRoot', () {
+ expect(fs.directory(root).parent.path, root);
+ });
+
+ test('succeedsForNonRoot', () {
+ expect(fs.directory(ns('/foo/bar')).parent.path, ns('/foo'));
+ });
+ });
+
+ group('createTemp', () {
+ test('returnsCovariantType', () {
+ expect(fs.directory(ns('/')).createTempSync(), isDirectory);
+ });
+
+ test('throwsIfDirectoryDoesntExist', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.directory(ns('/foo')).createTempSync();
+ });
+ });
+
+ test('resolvesNameCollisions', () {
+ fs.directory(ns('/foo/bar')).createSync(recursive: true);
+ var tmp = fs.directory(ns('/foo')).createTempSync('bar');
+ expect(tmp.path,
+ allOf(isNot(ns('/foo/bar')), startsWith(ns('/foo/bar'))));
+ });
+
+ test('succeedsWithoutPrefix', () {
+ var dir = fs.directory(ns('/foo'))..createSync();
+ expect(dir.createTempSync().path, startsWith(ns('/foo/')));
+ });
+
+ test('succeedsWithPrefix', () {
+ var dir = fs.directory(ns('/foo'))..createSync();
+ expect(dir.createTempSync('bar').path, startsWith(ns('/foo/bar')));
+ });
+
+ test('succeedsWithNestedPathPrefixThatExists', () {
+ fs.directory(ns('/foo/bar')).createSync(recursive: true);
+ var tmp = fs.directory(ns('/foo')).createTempSync('bar/baz');
+ expect(tmp.path, startsWith(ns('/foo/bar/baz')));
+ });
+
+ test('throwsWithNestedPathPrefixThatDoesntExist', () {
+ var dir = fs.directory(ns('/foo'))..createSync();
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ dir.createTempSync('bar/baz');
+ });
+ });
+ });
+
+ group('list', () {
+ late Directory dir;
+
+ setUp(() {
+ dir = fs.currentDirectory = fs.directory(ns('/foo'))..createSync();
+ fs.file('bar').createSync();
+ fs.file(fs.path.join('baz', 'qux')).createSync(recursive: true);
+ fs.link('quux').createSync(fs.path.join('baz', 'qux'));
+ fs
+ .link(fs.path.join('baz', 'quuz'))
+ .createSync(fs.path.join('..', 'quux'));
+ fs.link(fs.path.join('baz', 'grault')).createSync('.');
+ fs.currentDirectory = ns('/');
+ });
+
+ test('returnsCovariantType', () async {
+ void expectIsFileSystemEntity(dynamic entity) {
+ expect(entity, isFileSystemEntity);
+ }
+
+ dir.listSync().forEach(expectIsFileSystemEntity);
+ (await dir.list().toList()).forEach(expectIsFileSystemEntity);
+ });
+
+ test('returnsEmptyListForEmptyDirectory', () {
+ var empty = fs.directory(ns('/bar'))..createSync();
+ expect(empty.listSync(), isEmpty);
+ });
+
+ test('throwsIfDirectoryDoesntExist', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.directory(ns('/bar')).listSync();
+ });
+ });
+
+ test('returnsLinkObjectsIfFollowLinksFalse', () {
+ var list = dir.listSync(followLinks: false);
+ expect(list, hasLength(3));
+ expect(list, contains(allOf(isFile, hasPath(ns('/foo/bar')))));
+ expect(list, contains(allOf(isDirectory, hasPath(ns('/foo/baz')))));
+ expect(list, contains(allOf(isLink, hasPath(ns('/foo/quux')))));
+ });
+
+ test('followsLinksIfFollowLinksTrue', () {
+ var list = dir.listSync();
+ expect(list, hasLength(3));
+ expect(list, contains(allOf(isFile, hasPath(ns('/foo/bar')))));
+ expect(list, contains(allOf(isDirectory, hasPath(ns('/foo/baz')))));
+ expect(list, contains(allOf(isFile, hasPath(ns('/foo/quux')))));
+ });
+
+ test('returnsLinkObjectsForRecursiveLinkIfFollowLinksTrue', () {
+ expect(
+ dir.listSync(recursive: true),
+ allOf(
+ hasLength(9),
+ allOf(
+ contains(allOf(isFile, hasPath(ns('/foo/bar')))),
+ contains(allOf(isFile, hasPath(ns('/foo/quux')))),
+ contains(allOf(isFile, hasPath(ns('/foo/baz/qux')))),
+ contains(allOf(isFile, hasPath(ns('/foo/baz/quuz')))),
+ contains(allOf(isFile, hasPath(ns('/foo/baz/grault/qux')))),
+ contains(allOf(isFile, hasPath(ns('/foo/baz/grault/quuz')))),
+ ),
+ allOf(
+ contains(allOf(isDirectory, hasPath(ns('/foo/baz')))),
+ contains(allOf(isDirectory, hasPath(ns('/foo/baz/grault')))),
+ ),
+ contains(allOf(isLink, hasPath(ns('/foo/baz/grault/grault')))),
+ ),
+ );
+ });
+
+ test('recurseIntoDirectoriesIfRecursiveTrueFollowLinksFalse', () {
+ expect(
+ dir.listSync(recursive: true, followLinks: false),
+ allOf(
+ hasLength(6),
+ contains(allOf(isFile, hasPath(ns('/foo/bar')))),
+ contains(allOf(isFile, hasPath(ns('/foo/baz/qux')))),
+ contains(allOf(isLink, hasPath(ns('/foo/quux')))),
+ contains(allOf(isLink, hasPath(ns('/foo/baz/quuz')))),
+ contains(allOf(isLink, hasPath(ns('/foo/baz/grault')))),
+ contains(allOf(isDirectory, hasPath(ns('/foo/baz')))),
+ ),
+ );
+ });
+
+ test('childEntriesNotNormalized', () {
+ dir = fs.directory(ns('/bar/baz'))..createSync(recursive: true);
+ fs.file(ns('/bar/baz/qux')).createSync();
+ var list = fs.directory(ns('/bar//../bar/./baz')).listSync();
+ expect(list, hasLength(1));
+ expect(list[0], allOf(isFile, hasPath(ns('/bar//../bar/./baz/qux'))));
+ });
+
+ test('symlinksToNotFoundAlwaysReturnedAsLinks', () {
+ dir = fs.directory(ns('/bar'))..createSync();
+ fs.link(ns('/bar/baz')).createSync('qux');
+ for (var followLinks in const <bool>[true, false]) {
+ var list = dir.listSync(followLinks: followLinks);
+ expect(list, hasLength(1));
+ expect(list[0], allOf(isLink, hasPath(ns('/bar/baz'))));
+ }
+ });
+ });
+
+ test('childEntities', () {
+ var dir = fs.directory(ns('/foo'))..createSync();
+ dir.childDirectory('bar').createSync();
+ dir.childFile('baz').createSync();
+ dir.childLink('qux').createSync('bar');
+ expect(fs.directory(ns('/foo/bar')), exists);
+ expect(fs.file(ns('/foo/baz')), exists);
+ expect(fs.link(ns('/foo/qux')), exists);
+ });
+ });
+
+ group('File', () {
+ test('uri', () {
+ expect(fs.file(ns('/foo')).uri, fs.path.toUri(ns('/foo')));
+ expect(fs.file('foo').uri.toString(), 'foo');
+ });
+
+ group('create', () {
+ test('returnsCovariantType', () async {
+ expect(await fs.file(ns('/foo')).create(), isFile);
+ });
+
+ test('succeedsIfTailDoesntAlreadyExist', () {
+ fs.file(ns('/foo')).createSync();
+ expect(fs.file(ns('/foo')), exists);
+ });
+
+ test('succeedsIfAlreadyExistsAsFile', () {
+ fs.file(ns('/foo')).createSync();
+ fs.file(ns('/foo')).createSync();
+ expect(fs.file(ns('/foo')), exists);
+ });
+
+ test('throwsIfAncestorDoesntExistRecursiveFalse', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.file(ns('/foo/bar')).createSync();
+ });
+ });
+
+ test('succeedsIfAncestorDoesntExistRecursiveTrue', () {
+ fs.file(ns('/foo/bar')).createSync(recursive: true);
+ expect(fs.file(ns('/foo/bar')), exists);
+ });
+
+ test('throwsIfAlreadyExistsAsDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ expectFileSystemException(ErrorCodes.EISDIR, () {
+ fs.file(ns('/foo')).createSync();
+ });
+ });
+
+ test('throwsIfAlreadyExistsAsLinkToDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ expectFileSystemException(ErrorCodes.EISDIR, () {
+ fs.file(ns('/bar')).createSync();
+ });
+ });
+
+ test('succeedsIfAlreadyExistsAsLinkToFile', () {
+ fs.file(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ fs.file(ns('/bar')).createSync();
+ expect(fs.file(ns('/bar')), exists);
+ });
+
+ test('succeedsIfAlreadyExistsAsLinkToNotFoundAtTail', () {
+ fs.link(ns('/foo')).createSync(ns('/bar'));
+ fs.file(ns('/foo')).createSync();
+ expect(fs.typeSync(ns('/foo'), followLinks: false),
+ FileSystemEntityType.link);
+ expect(fs.typeSync(ns('/bar'), followLinks: false),
+ FileSystemEntityType.file);
+ });
+
+ test('throwsIfAlreadyExistsAsLinkToNotFoundViaTraversal', () {
+ fs.link(ns('/foo')).createSync(ns('/bar/baz'));
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.file(ns('/foo')).createSync();
+ });
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.file(ns('/foo')).createSync(recursive: true);
+ });
+ });
+
+ /*
+ test('throwsIfPathSegmentIsLinkToNotFoundAndRecursiveTrue', () {
+ fs.link(ns('/foo')).createSync(ns('/bar'));
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.file(ns('/foo/baz')).createSync(recursive: true);
+ });
+ });
+ */
+
+ test('succeedsIfAlreadyExistsAsLinkToNotFoundInDifferentDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ fs.directory(ns('/bar')).createSync();
+ fs.link(ns('/bar/baz')).createSync(ns('/foo/qux'));
+ fs.file(ns('/bar/baz')).createSync();
+ expect(fs.typeSync(ns('/bar/baz'), followLinks: false),
+ FileSystemEntityType.link);
+ expect(fs.typeSync(ns('/foo/qux'), followLinks: false),
+ FileSystemEntityType.file);
+ });
+ });
+
+ group('rename', () {
+ test('returnsCovariantType', () async {
+ File f() => fs.file(ns('/foo'))..createSync();
+ expect(await f().rename(ns('/bar')), isFile);
+ expect(f().renameSync(ns('/baz')), isFile);
+ });
+
+ test('succeedsIfDestinationDoesntExistAtTail', () {
+ var src = fs.file(ns('/foo'))..createSync();
+ var dest = src.renameSync(ns('/bar'));
+ expect(fs.file(ns('/foo')), isNot(exists));
+ expect(fs.file(ns('/bar')), exists);
+ expect(dest.path, ns('/bar'));
+ });
+
+ test('throwsIfDestinationDoesntExistViaTraversal', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ f.renameSync(ns('/bar/baz'));
+ });
+ });
+
+ test('succeedsIfDestinationExistsAsFile', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ fs.file(ns('/bar')).createSync();
+ f.renameSync(ns('/bar'));
+ expect(fs.file(ns('/foo')), isNot(exists));
+ expect(fs.file(ns('/bar')), exists);
+ });
+
+ test('throwsIfDestinationExistsAsDirectory', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ fs.directory(ns('/bar')).createSync();
+ expectFileSystemException(ErrorCodes.EISDIR, () {
+ f.renameSync(ns('/bar'));
+ });
+ });
+
+ test('succeedsIfDestinationExistsAsLinkToFile', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ fs.file(ns('/bar')).createSync();
+ fs.link(ns('/baz')).createSync(ns('/bar'));
+ f.renameSync(ns('/baz'));
+ expect(fs.typeSync(ns('/foo')), FileSystemEntityType.notFound);
+ expect(fs.typeSync(ns('/bar'), followLinks: false),
+ FileSystemEntityType.file);
+ expect(fs.typeSync(ns('/baz'), followLinks: false),
+ FileSystemEntityType.file);
+ });
+
+ test('throwsIfDestinationExistsAsLinkToDirectory', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ fs.directory(ns('/bar')).createSync();
+ fs.link(ns('/baz')).createSync(ns('/bar'));
+ expectFileSystemException(ErrorCodes.EISDIR, () {
+ f.renameSync(ns('/baz'));
+ });
+ });
+
+ test('succeedsIfDestinationExistsAsLinkToNotFound', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ fs.link(ns('/bar')).createSync(ns('/baz'));
+ f.renameSync(ns('/bar'));
+ expect(fs.typeSync(ns('/foo')), FileSystemEntityType.notFound);
+ expect(fs.typeSync(ns('/bar'), followLinks: false),
+ FileSystemEntityType.file);
+ });
+
+ test('throwsIfSourceDoesntExist', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.file(ns('/foo')).renameSync(ns('/bar'));
+ });
+ });
+
+ test('throwsIfSourceExistsAsDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ expectFileSystemException(ErrorCodes.EISDIR, () {
+ fs.file(ns('/foo')).renameSync(ns('/bar'));
+ });
+ });
+
+ test('succeedsIfSourceExistsAsLinkToFile', () {
+ fs.file(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ fs.file(ns('/bar')).renameSync(ns('/baz'));
+ expect(fs.typeSync(ns('/bar')), FileSystemEntityType.notFound);
+ expect(fs.typeSync(ns('/baz'), followLinks: false),
+ FileSystemEntityType.link);
+ expect(fs.typeSync(ns('/baz'), followLinks: true),
+ FileSystemEntityType.file);
+ });
+
+ test('throwsIfSourceExistsAsLinkToDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ expectFileSystemException(ErrorCodes.EISDIR, () {
+ fs.file(ns('/bar')).renameSync(ns('/baz'));
+ });
+ });
+
+ test('throwsIfSourceExistsAsLinkToNotFound', () {
+ fs.link(ns('/foo')).createSync(ns('/bar'));
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.file(ns('/foo')).renameSync(ns('/baz'));
+ });
+ });
+ });
+
+ group('copy', () {
+ test('returnsCovariantType', () async {
+ File f() => fs.file(ns('/foo'))..createSync();
+ expect(await f().copy(ns('/bar')), isFile);
+ expect(f().copySync(ns('/baz')), isFile);
+ });
+
+ test('succeedsIfDestinationDoesntExistAtTail', () {
+ var f = fs.file(ns('/foo'))
+ ..createSync()
+ ..writeAsStringSync('foo');
+ f.copySync(ns('/bar'));
+ expect(fs.file(ns('/foo')), exists);
+ expect(fs.file(ns('/bar')), exists);
+ expect(fs.file(ns('/foo')).readAsStringSync(), 'foo');
+ });
+
+ test('throwsIfDestinationDoesntExistViaTraversal', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ f.copySync(ns('/bar/baz'));
+ });
+ });
+
+ test('succeedsIfDestinationExistsAsFile', () {
+ var f = fs.file(ns('/foo'))
+ ..createSync()
+ ..writeAsStringSync('foo');
+ fs.file(ns('/bar'))
+ ..createSync()
+ ..writeAsStringSync('bar');
+ f.copySync(ns('/bar'));
+ expect(fs.file(ns('/foo')), exists);
+ expect(fs.file(ns('/bar')), exists);
+ expect(fs.file(ns('/foo')).readAsStringSync(), 'foo');
+ expect(fs.file(ns('/bar')).readAsStringSync(), 'foo');
+ });
+
+ test('throwsIfDestinationExistsAsDirectory', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ fs.directory(ns('/bar')).createSync();
+ expectFileSystemException(ErrorCodes.EISDIR, () {
+ f.copySync(ns('/bar'));
+ });
+ });
+
+ test('succeedsIfDestinationExistsAsLinkToFile', () {
+ var f = fs.file(ns('/foo'))
+ ..createSync()
+ ..writeAsStringSync('foo');
+ fs.file(ns('/bar'))
+ ..createSync()
+ ..writeAsStringSync('bar');
+ fs.link(ns('/baz')).createSync(ns('/bar'));
+ f.copySync(ns('/baz'));
+ expect(fs.typeSync(ns('/foo'), followLinks: false),
+ FileSystemEntityType.file);
+ expect(fs.typeSync(ns('/bar'), followLinks: false),
+ FileSystemEntityType.file);
+ expect(fs.typeSync(ns('/baz'), followLinks: false),
+ FileSystemEntityType.link);
+ expect(fs.file(ns('/foo')).readAsStringSync(), 'foo');
+ expect(fs.file(ns('/bar')).readAsStringSync(), 'foo');
+ }, skip: io.Platform.isWindows /* No links on Windows */);
+
+ test('throwsIfDestinationExistsAsLinkToDirectory', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ fs.directory(ns('/bar')).createSync();
+ fs.link(ns('/baz')).createSync(ns('/bar'));
+ expectFileSystemException(ErrorCodes.EISDIR, () {
+ f.copySync(ns('/baz'));
+ });
+ });
+
+ test('throwsIfSourceDoesntExist', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.file(ns('/foo')).copySync(ns('/bar'));
+ });
+ });
+
+ test('throwsIfSourceExistsAsDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ expectFileSystemException(ErrorCodes.EISDIR, () {
+ fs.file(ns('/foo')).copySync(ns('/bar'));
+ });
+ });
+
+ test('succeedsIfSourceExistsAsLinkToFile', () {
+ fs.file(ns('/foo'))
+ ..createSync()
+ ..writeAsStringSync('foo');
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ fs.file(ns('/bar')).copySync(ns('/baz'));
+ expect(fs.typeSync(ns('/foo'), followLinks: false),
+ FileSystemEntityType.file);
+ expect(fs.typeSync(ns('/bar'), followLinks: false),
+ FileSystemEntityType.link);
+ expect(fs.typeSync(ns('/baz'), followLinks: false),
+ FileSystemEntityType.file);
+ expect(fs.file(ns('/foo')).readAsStringSync(), 'foo');
+ expect(fs.file(ns('/baz')).readAsStringSync(), 'foo');
+ });
+
+ test('succeedsIfDestinationIsInDifferentDirectoryThanSource', () {
+ var f = fs.file(ns('/foo/bar'))
+ ..createSync(recursive: true)
+ ..writeAsStringSync('foo');
+ fs.directory(ns('/baz')).createSync();
+ f.copySync(ns('/baz/qux'));
+ expect(fs.file(ns('/foo/bar')), exists);
+ expect(fs.file(ns('/baz/qux')), exists);
+ expect(fs.file(ns('/foo/bar')).readAsStringSync(), 'foo');
+ expect(fs.file(ns('/baz/qux')).readAsStringSync(), 'foo');
+ });
+
+ test('succeedsIfSourceIsLinkToFileInDifferentDirectory', () {
+ fs.file(ns('/foo/bar'))
+ ..createSync(recursive: true)
+ ..writeAsStringSync('foo');
+ fs.link(ns('/baz/qux')).createSync(ns('/foo/bar'), recursive: true);
+ fs.file(ns('/baz/qux')).copySync(ns('/baz/quux'));
+ expect(fs.typeSync(ns('/foo/bar'), followLinks: false),
+ FileSystemEntityType.file);
+ expect(fs.typeSync(ns('/baz/qux'), followLinks: false),
+ FileSystemEntityType.link);
+ expect(fs.typeSync(ns('/baz/quux'), followLinks: false),
+ FileSystemEntityType.file);
+ expect(fs.file(ns('/foo/bar')).readAsStringSync(), 'foo');
+ expect(fs.file(ns('/baz/quux')).readAsStringSync(), 'foo');
+ });
+
+ test('succeedsIfDestinationIsLinkToFileInDifferentDirectory', () {
+ fs.file(ns('/foo/bar'))
+ ..createSync(recursive: true)
+ ..writeAsStringSync('bar');
+ fs.file(ns('/baz/qux'))
+ ..createSync(recursive: true)
+ ..writeAsStringSync('qux');
+ fs.link(ns('/baz/quux')).createSync(ns('/foo/bar'));
+ fs.file(ns('/baz/qux')).copySync(ns('/baz/quux'));
+ expect(fs.typeSync(ns('/foo/bar'), followLinks: false),
+ FileSystemEntityType.file);
+ expect(fs.typeSync(ns('/baz/qux'), followLinks: false),
+ FileSystemEntityType.file);
+ expect(fs.typeSync(ns('/baz/quux'), followLinks: false),
+ FileSystemEntityType.link);
+ expect(fs.file(ns('/foo/bar')).readAsStringSync(), 'qux');
+ expect(fs.file(ns('/baz/qux')).readAsStringSync(), 'qux');
+ }, skip: io.Platform.isWindows /* No links on Windows */);
+ });
+
+ group('length', () {
+ test('throwsIfDoesntExist', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.file(ns('/foo')).lengthSync();
+ });
+ });
+
+ test('throwsIfExistsAsDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ expectFileSystemException(ErrorCodes.EISDIR, () {
+ fs.file(ns('/foo')).lengthSync();
+ });
+ });
+
+ test('returnsZeroForNewlyCreatedFile', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ expect(f.lengthSync(), 0);
+ });
+
+ test('writeNBytesReturnsLengthN', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ f.writeAsBytesSync(<int>[1, 2, 3, 4], flush: true);
+ expect(f.lengthSync(), 4);
+ });
+
+ test('succeedsIfExistsAsLinkToFile', () {
+ fs.file(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ expect(fs.file(ns('/bar')).lengthSync(), 0);
+ });
+ });
+
+ group('absolute', () {
+ test('returnsSamePathIfAlreadyAbsolute', () {
+ expect(fs.file(ns('/foo')).absolute.path, ns('/foo'));
+ });
+
+ test('succeedsForRelativePaths', () {
+ expect(fs.file('foo').absolute.path, ns('/foo'));
+ });
+ });
+
+ group('lastAccessed', () {
+ test('isNowForNewlyCreatedFile', () {
+ var before = downstairs();
+ var f = fs.file(ns('/foo'))..createSync();
+ var after = ceil();
+ var accessed = f.lastAccessedSync();
+ expect(accessed, isSameOrAfter(before));
+ expect(accessed, isSameOrBefore(after));
+ });
+
+ test('throwsIfDoesntExist', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.file(ns('/foo')).lastAccessedSync();
+ });
+ });
+
+ test('throwsIfExistsAsDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ expectFileSystemException(ErrorCodes.EISDIR, () {
+ fs.file(ns('/foo')).lastAccessedSync();
+ });
+ });
+
+ test('succeedsIfExistsAsLinkToFile', () {
+ var before = downstairs();
+ fs.file(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ var after = ceil();
+ var accessed = fs.file(ns('/bar')).lastAccessedSync();
+ expect(accessed, isSameOrAfter(before));
+ expect(accessed, isSameOrBefore(after));
+ });
+ });
+
+ group('setLastAccessed', () {
+ final time = DateTime(1999);
+
+ test('throwsIfDoesntExist', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.file(ns('/foo')).setLastAccessedSync(time);
+ });
+ });
+
+ test('throwsIfExistsAsDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ expectFileSystemException(ErrorCodes.EISDIR, () {
+ fs.file(ns('/foo')).setLastAccessedSync(time);
+ });
+ });
+
+ test('succeedsIfExistsAsFile', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ f.setLastAccessedSync(time);
+ expect(fs.file(ns('/foo')).lastAccessedSync(), time);
+ });
+
+ test('succeedsIfExistsAsLinkToFile', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ f.setLastAccessedSync(time);
+ expect(fs.file(ns('/bar')).lastAccessedSync(), time);
+ });
+ });
+
+ group('lastModified', () {
+ test('isNowForNewlyCreatedFile', () {
+ var before = downstairs();
+ var f = fs.file(ns('/foo'))..createSync();
+ var after = ceil();
+ var modified = f.lastModifiedSync();
+ expect(modified, isSameOrAfter(before));
+ expect(modified, isSameOrBefore(after));
+ });
+
+ test('throwsIfDoesntExist', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.file(ns('/foo')).lastModifiedSync();
+ });
+ });
+
+ test('throwsIfExistsAsDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ expectFileSystemException(ErrorCodes.EISDIR, () {
+ fs.file(ns('/foo')).lastModifiedSync();
+ });
+ });
+
+ test('succeedsIfExistsAsLinkToFile', () {
+ var before = downstairs();
+ fs.file(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ var after = ceil();
+ var modified = fs.file(ns('/bar')).lastModifiedSync();
+ expect(modified, isSameOrAfter(before));
+ expect(modified, isSameOrBefore(after));
+ });
+ });
+
+ group('setLastModified', () {
+ final time = DateTime(1999);
+
+ test('throwsIfDoesntExist', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.file(ns('/foo')).setLastModifiedSync(time);
+ });
+ });
+
+ test('throwsIfExistsAsDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ expectFileSystemException(ErrorCodes.EISDIR, () {
+ fs.file(ns('/foo')).setLastModifiedSync(time);
+ });
+ });
+
+ test('succeedsIfExistsAsFile', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ f.setLastModifiedSync(time);
+ expect(fs.file(ns('/foo')).lastModifiedSync(), time);
+ });
+
+ test('succeedsIfExistsAsLinkToFile', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ f.setLastModifiedSync(time);
+ expect(fs.file(ns('/bar')).lastModifiedSync(), time);
+ });
+ });
+
+ group('open', () {
+ void testIfDoesntExistAtTail(FileMode mode) {
+ if (mode == FileMode.read) {
+ test('throwsIfDoesntExistAtTail', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.file(ns('/bar')).openSync(mode: mode);
+ });
+ });
+ } else {
+ test('createsFileIfDoesntExistAtTail', () {
+ var raf = fs.file(ns('/bar')).openSync(mode: mode);
+ raf.closeSync();
+ expect(fs.file(ns('/bar')), exists);
+ });
+ }
+ }
+
+ void testThrowsIfDoesntExistViaTraversal(FileMode mode) {
+ test('throwsIfDoesntExistViaTraversal', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.file(ns('/bar/baz')).openSync(mode: mode);
+ });
+ });
+ }
+
+ void testRandomAccessFileOperations(FileMode mode) {
+ group('RandomAccessFile', () {
+ late File f;
+ late RandomAccessFile raf;
+
+ setUp(() {
+ f = fs.file(ns('/foo'))..createSync();
+ f.writeAsStringSync('pre-existing content\n', flush: true);
+ raf = f.openSync(mode: mode);
+ });
+
+ tearDown(() {
+ try {
+ raf.closeSync();
+ } on FileSystemException {
+ // Ignore; a test may have already closed it.
+ }
+ });
+
+ test('succeedsIfClosedAfterClosed', () {
+ raf.closeSync();
+ expectFileSystemException(null, () {
+ raf.closeSync();
+ });
+ });
+
+ test('throwsIfReadAfterClose', () {
+ raf.closeSync();
+ expectFileSystemException(null, () {
+ raf.readByteSync();
+ });
+ });
+
+ test('throwsIfWriteAfterClose', () {
+ raf.closeSync();
+ expectFileSystemException(null, () {
+ raf.writeByteSync(0xBAD);
+ });
+ });
+
+ test('throwsIfTruncateAfterClose', () {
+ raf.closeSync();
+ expectFileSystemException(null, () {
+ raf.truncateSync(0);
+ });
+ });
+
+ if (mode == FileMode.write || mode == FileMode.writeOnly) {
+ test('lengthIsResetToZeroIfOpened', () {
+ expect(raf.lengthSync(), equals(0));
+ });
+
+ test('throwsIfAsyncUnawaited', () async {
+ try {
+ final Future<void> future = raf.flush();
+ expectFileSystemException(null, () => raf.flush());
+ expectFileSystemException(null, () => raf.flushSync());
+ await expectLater(future, completes);
+ raf.flushSync();
+ } finally {
+ raf.closeSync();
+ }
+ });
+ } else {
+ test('lengthIsNotModifiedIfOpened', () {
+ expect(raf.lengthSync(), isNot(equals(0)));
+ });
+ }
+
+ if (mode == FileMode.writeOnly ||
+ mode == FileMode.writeOnlyAppend) {
+ test('throwsIfReadByte', () {
+ expectFileSystemException(ErrorCodes.EBADF, () {
+ raf.readByteSync();
+ });
+ });
+
+ test('throwsIfRead', () {
+ expectFileSystemException(ErrorCodes.EBADF, () {
+ raf.readSync(2);
+ });
+ });
+
+ test('throwsIfReadInto', () {
+ expectFileSystemException(ErrorCodes.EBADF, () {
+ raf.readIntoSync(List<int>.filled(5, 0));
+ });
+ });
+ } else {
+ group('read', () {
+ setUp(() {
+ if (mode == FileMode.write) {
+ // Write data back that we truncated when opening the file.
+ raf.writeStringSync('pre-existing content\n');
+ }
+ // Reset the position to zero so we can read the content.
+ raf.setPositionSync(0);
+ });
+
+ test('readByte', () {
+ expect(utf8.decode(<int>[raf.readByteSync()]), 'p');
+ });
+
+ test('read', () {
+ List<int> bytes = raf.readSync(1024);
+ expect(bytes.length, 21);
+ expect(utf8.decode(bytes), 'pre-existing content\n');
+ });
+
+ test('readIntoWithBufferLargerThanContent', () {
+ var buffer = List<int>.filled(1024, 0);
+ var numRead = raf.readIntoSync(buffer);
+ expect(numRead, 21);
+ expect(utf8.decode(buffer.sublist(0, 21)),
+ 'pre-existing content\n');
+ });
+
+ test('readIntoWithBufferSmallerThanContent', () {
+ var buffer = List<int>.filled(10, 0);
+ var numRead = raf.readIntoSync(buffer);
+ expect(numRead, 10);
+ expect(utf8.decode(buffer), 'pre-existi');
+ });
+
+ test('readIntoWithStart', () {
+ var buffer = List<int>.filled(10, 0);
+ var numRead = raf.readIntoSync(buffer, 2);
+ expect(numRead, 8);
+ expect(utf8.decode(buffer.sublist(2)), 'pre-exis');
+ });
+
+ test('readIntoWithStartAndEnd', () {
+ var buffer = List<int>.filled(10, 0);
+ var numRead = raf.readIntoSync(buffer, 2, 5);
+ expect(numRead, 3);
+ expect(utf8.decode(buffer.sublist(2, 5)), 'pre');
+ });
+
+ test('openReadHandleDoesNotChange', () {
+ final initial = utf8.decode(raf.readSync(4));
+ expect(initial, 'pre-');
+ final newFile = f.renameSync(ns('/bar'));
+ var rest = utf8.decode(raf.readSync(1024));
+ expect(rest, 'existing content\n');
+
+ assert(newFile.path != f.path);
+ expect(f, isNot(exists));
+ expect(newFile, exists);
+
+ // [RandomAccessFile.path] always returns the original path.
+ expect(raf.path, f.path);
+ });
+ });
+ }
+
+ if (mode == FileMode.read) {
+ test('throwsIfWriteByte', () {
+ expectFileSystemException(ErrorCodes.EBADF, () {
+ raf.writeByteSync(0xBAD);
+ });
+ });
+
+ test('throwsIfWriteFrom', () {
+ expectFileSystemException(ErrorCodes.EBADF, () {
+ raf.writeFromSync(<int>[1, 2, 3, 4]);
+ });
+ });
+
+ test('throwsIfWriteString', () {
+ expectFileSystemException(ErrorCodes.EBADF, () {
+ raf.writeStringSync('This should throw.');
+ });
+ });
+ } else {
+ test('lengthGrowsAsDataIsWritten', () {
+ var lengthBefore = f.lengthSync();
+ raf.writeByteSync(0xFACE);
+ expect(raf.lengthSync(), lengthBefore + 1);
+ });
+
+ test('flush', () {
+ var lengthBefore = f.lengthSync();
+ raf.writeByteSync(0xFACE);
+ raf.flushSync();
+ expect(f.lengthSync(), lengthBefore + 1);
+ });
+
+ test('writeByte', () {
+ raf.writeByteSync(utf8.encode('A').first);
+ raf.flushSync();
+ if (mode == FileMode.write || mode == FileMode.writeOnly) {
+ expect(f.readAsStringSync(), 'A');
+ } else {
+ expect(f.readAsStringSync(), 'pre-existing content\nA');
+ }
+ });
+
+ test('writeFrom', () {
+ raf.writeFromSync(utf8.encode('Hello world'));
+ raf.flushSync();
+ if (mode == FileMode.write || mode == FileMode.writeOnly) {
+ expect(f.readAsStringSync(), 'Hello world');
+ } else {
+ expect(f.readAsStringSync(),
+ 'pre-existing content\nHello world');
+ }
+ });
+
+ test('writeFromWithStart', () {
+ raf.writeFromSync(utf8.encode('Hello world'), 2);
+ raf.flushSync();
+ if (mode == FileMode.write || mode == FileMode.writeOnly) {
+ expect(f.readAsStringSync(), 'llo world');
+ } else {
+ expect(
+ f.readAsStringSync(), 'pre-existing content\nllo world');
+ }
+ });
+
+ test('writeFromWithStartAndEnd', () {
+ raf.writeFromSync(utf8.encode('Hello world'), 2, 5);
+ raf.flushSync();
+ if (mode == FileMode.write || mode == FileMode.writeOnly) {
+ expect(f.readAsStringSync(), 'llo');
+ } else {
+ expect(f.readAsStringSync(), 'pre-existing content\nllo');
+ }
+ });
+
+ test('writeString', () {
+ raf.writeStringSync('Hello world');
+ raf.flushSync();
+ if (mode == FileMode.write || mode == FileMode.writeOnly) {
+ expect(f.readAsStringSync(), 'Hello world');
+ } else {
+ expect(f.readAsStringSync(),
+ 'pre-existing content\nHello world');
+ }
+ });
+
+ test('openWriteHandleDoesNotChange', () {
+ raf.writeStringSync('Hello ');
+ final newFile = f.renameSync(ns('/bar'));
+ raf.writeStringSync('world');
+
+ final contents = newFile.readAsStringSync();
+ if (mode == FileMode.write || mode == FileMode.writeOnly) {
+ expect(contents, 'Hello world');
+ } else {
+ expect(contents, 'pre-existing content\nHello world');
+ }
+
+ assert(newFile.path != f.path);
+ expect(f, isNot(exists));
+ expect(newFile, exists);
+
+ // [RandomAccessFile.path] always returns the original path.
+ expect(raf.path, f.path);
+ });
+ }
+
+ if (mode == FileMode.append || mode == FileMode.writeOnlyAppend) {
+ test('positionInitializedToEndOfFile', () {
+ expect(raf.positionSync(), 21);
+ });
+ } else {
+ test('positionInitializedToZero', () {
+ expect(raf.positionSync(), 0);
+ });
+ }
+
+ group('position', () {
+ setUp(() {
+ if (mode == FileMode.write || mode == FileMode.writeOnly) {
+ // Write data back that we truncated when opening the file.
+ raf.writeStringSync('pre-existing content\n');
+ }
+ });
+
+ if (mode != FileMode.writeOnly &&
+ mode != FileMode.writeOnlyAppend) {
+ test('growsAfterRead', () {
+ raf.setPositionSync(0);
+ raf.readSync(10);
+ expect(raf.positionSync(), 10);
+ });
+
+ test('affectsRead', () {
+ raf.setPositionSync(5);
+ expect(utf8.decode(raf.readSync(5)), 'xisti');
+ });
+ }
+
+ if (mode == FileMode.read) {
+ test('succeedsIfSetPastEndOfFile', () {
+ raf.setPositionSync(32);
+ expect(raf.positionSync(), 32);
+ });
+ } else {
+ test('growsAfterWrite', () {
+ var positionBefore = raf.positionSync();
+ raf.writeStringSync('Hello world');
+ expect(raf.positionSync(), positionBefore + 11);
+ });
+
+ test('affectsWrite', () {
+ raf.setPositionSync(5);
+ raf.writeStringSync('-yo-');
+ raf.flushSync();
+ expect(f.readAsStringSync(), 'pre-e-yo-ing content\n');
+ });
+
+ test('succeedsIfSetAndWrittenPastEndOfFile', () {
+ raf.setPositionSync(32);
+ expect(raf.positionSync(), 32);
+ raf.writeStringSync('here');
+ raf.flushSync();
+ List<int> bytes = f.readAsBytesSync();
+ expect(bytes.length, 36);
+ expect(utf8.decode(bytes.sublist(0, 21)),
+ 'pre-existing content\n');
+ expect(utf8.decode(bytes.sublist(32, 36)), 'here');
+ expect(bytes.sublist(21, 32), everyElement(0));
+ });
+ }
+
+ test('throwsIfSetToNegativeNumber', () {
+ expectFileSystemException(ErrorCodes.EINVAL, () {
+ raf.setPositionSync(-12);
+ });
+ });
+ });
+
+ if (mode == FileMode.read) {
+ test('throwsIfTruncate', () {
+ expectFileSystemException(ErrorCodes.EINVAL, () {
+ raf.truncateSync(5);
+ });
+ });
+ } else {
+ group('truncate', () {
+ setUp(() {
+ if (mode == FileMode.write || mode == FileMode.writeOnly) {
+ // Write data back that we truncated when opening the file.
+ raf.writeStringSync('pre-existing content\n');
+ }
+ });
+
+ test('succeedsIfSetWithinRangeOfContent', () {
+ raf.truncateSync(5);
+ raf.flushSync();
+ expect(f.lengthSync(), 5);
+ expect(f.readAsStringSync(), 'pre-e');
+ });
+
+ test('succeedsIfSetToZero', () {
+ raf.truncateSync(0);
+ raf.flushSync();
+ expect(f.lengthSync(), 0);
+ expect(f.readAsStringSync(), isEmpty);
+ });
+
+ test('throwsIfSetToNegativeNumber', () {
+ expectFileSystemException(ErrorCodes.EINVAL, () {
+ raf.truncateSync(-2);
+ });
+ });
+
+ test('extendsFileIfSetPastEndOfFile', () {
+ raf.truncateSync(32);
+ raf.flushSync();
+ List<int> bytes = f.readAsBytesSync();
+ expect(bytes.length, 32);
+ expect(utf8.decode(bytes.sublist(0, 21)),
+ 'pre-existing content\n');
+ expect(bytes.sublist(21, 32), everyElement(0));
+ });
+ });
+ }
+ });
+ }
+
+ void testOpenWithMode(FileMode mode) {
+ testIfDoesntExistAtTail(mode);
+ testThrowsIfDoesntExistViaTraversal(mode);
+ testRandomAccessFileOperations(mode);
+ }
+
+ group('READ', () => testOpenWithMode(FileMode.read));
+ group('WRITE', () => testOpenWithMode(FileMode.write));
+ group('APPEND', () => testOpenWithMode(FileMode.append));
+ group('WRITE_ONLY', () => testOpenWithMode(FileMode.writeOnly));
+ group('WRITE_ONLY_APPEND',
+ () => testOpenWithMode(FileMode.writeOnlyAppend));
+ });
+
+ group('openRead', () {
+ test('throwsIfDoesntExist', () {
+ var stream = fs.file(ns('/foo')).openRead();
+ expect(stream.drain<void>(),
+ throwsFileSystemException(ErrorCodes.ENOENT));
+ });
+
+ test('succeedsIfExistsAsFile', () async {
+ var f = fs.file(ns('/foo'))..createSync();
+ f.writeAsStringSync('Hello world', flush: true);
+ var stream = f.openRead();
+ var data = await stream.toList();
+ expect(data, hasLength(1));
+ expect(utf8.decode(data[0]), 'Hello world');
+ });
+
+ test('throwsIfExistsAsDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ var stream = fs.file(ns('/foo')).openRead();
+ expect(stream.drain<void>(),
+ throwsFileSystemException(ErrorCodes.EISDIR));
+ });
+
+ test('succeedsIfExistsAsLinkToFile', () async {
+ var f = fs.file(ns('/foo'))..createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ f.writeAsStringSync('Hello world', flush: true);
+ var stream = fs.file(ns('/bar')).openRead();
+ var data = await stream.toList();
+ expect(data, hasLength(1));
+ expect(utf8.decode(data[0]), 'Hello world');
+ });
+
+ test('respectsStartAndEndParameters', () async {
+ var f = fs.file(ns('/foo'))..createSync();
+ f.writeAsStringSync('Hello world', flush: true);
+ var stream = f.openRead(2);
+ var data = await stream.toList();
+ expect(data, hasLength(1));
+ expect(utf8.decode(data[0]), 'llo world');
+ stream = f.openRead(2, 5);
+ data = await stream.toList();
+ expect(data, hasLength(1));
+ expect(utf8.decode(data[0]), 'llo');
+ });
+
+ test('throwsIfStartParameterIsNegative', () async {
+ var f = fs.file(ns('/foo'))..createSync();
+ var stream = f.openRead(-2);
+ expect(stream.drain<void>(), throwsRangeError);
+ });
+
+ test('stopsAtEndOfFileIfEndParameterIsPastEndOfFile', () async {
+ var f = fs.file(ns('/foo'))..createSync();
+ f.writeAsStringSync('Hello world', flush: true);
+ var stream = f.openRead(2, 1024);
+ var data = await stream.toList();
+ expect(data, hasLength(1));
+ expect(utf8.decode(data[0]), 'llo world');
+ });
+
+ test('providesSingleSubscriptionStream', () async {
+ var f = fs.file(ns('/foo'))..createSync();
+ f.writeAsStringSync('Hello world', flush: true);
+ var stream = f.openRead();
+ expect(stream.isBroadcast, isFalse);
+ await stream.drain<void>();
+ });
+
+ test('openReadHandleDoesNotChange', () async {
+ // Ideally, `data` should be large enough so that its contents are
+ // split across multiple chunks in the [Stream]. However, there
+ // doesn't seem to be a good way to determine the chunk size used by
+ // [io.File].
+ final data = List<int>.generate(
+ 1024 * 256,
+ (int index) => index & 0xFF,
+ growable: false,
+ );
+
+ final f = fs.file(ns('/foo'))..createSync();
+
+ f.writeAsBytesSync(data, flush: true);
+ final stream = f.openRead();
+
+ File? newFile;
+ List<int>? initialChunk;
+ final remainingChunks = <int>[];
+
+ await for (List<int> chunk in stream) {
+ if (initialChunk == null) {
+ initialChunk = chunk;
+ assert(initialChunk.isNotEmpty);
+ expect(initialChunk, data.getRange(0, initialChunk.length));
+
+ newFile = f.renameSync(ns('/bar'));
+ } else {
+ remainingChunks.addAll(chunk);
+ }
+ }
+
+ expect(
+ remainingChunks,
+ data.getRange(initialChunk!.length, data.length),
+ );
+
+ assert(newFile?.path != f.path);
+ expect(f, isNot(exists));
+ expect(newFile, exists);
+ });
+
+ test('openReadCompatibleWithUtf8Decoder', () async {
+ const content = 'Hello world!';
+ var file = fs.file(ns('/foo'))
+ ..createSync()
+ ..writeAsStringSync(content);
+ expect(
+ await file
+ .openRead()
+ .transform(utf8.decoder)
+ .transform(const LineSplitter())
+ .first,
+ content,
+ );
+ });
+ });
+
+ group('openWrite', () {
+ test('createsFileIfDoesntExist', () async {
+ await fs.file(ns('/foo')).openWrite().close();
+ expect(fs.file(ns('/foo')), exists);
+ });
+
+ test('throwsIfExistsAsDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ expect(fs.file(ns('/foo')).openWrite().close(),
+ throwsFileSystemException(ErrorCodes.EISDIR));
+ });
+
+ test('throwsIfExistsAsLinkToDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ expect(fs.file(ns('/bar')).openWrite().close(),
+ throwsFileSystemException(ErrorCodes.EISDIR));
+ });
+
+ test('throwsIfModeIsRead', () {
+ expect(() => fs.file(ns('/foo')).openWrite(mode: FileMode.read),
+ throwsArgumentError);
+ });
+
+ test('succeedsIfExistsAsEmptyFile', () async {
+ var f = fs.file(ns('/foo'))..createSync();
+ var sink = f.openWrite();
+ sink.write('Hello world');
+ await sink.flush();
+ await sink.close();
+ expect(f.readAsStringSync(), 'Hello world');
+ });
+
+ test('succeedsIfExistsAsLinkToFile', () async {
+ fs.file(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ var sink = fs.file(ns('/bar')).openWrite();
+ sink.write('Hello world');
+ await sink.flush();
+ await sink.close();
+ expect(fs.file(ns('/foo')).readAsStringSync(), 'Hello world');
+ });
+
+ test('overwritesContentInWriteMode', () async {
+ var f = fs.file(ns('/foo'))..createSync();
+ f.writeAsStringSync('Hello');
+ var sink = f.openWrite();
+ sink.write('Goodbye');
+ await sink.flush();
+ await sink.close();
+ expect(fs.file(ns('/foo')).readAsStringSync(), 'Goodbye');
+ });
+
+ test('appendsContentInAppendMode', () async {
+ var f = fs.file(ns('/foo'))..createSync();
+ f.writeAsStringSync('Hello');
+ var sink = f.openWrite(mode: FileMode.append);
+ sink.write('Goodbye');
+ await sink.flush();
+ await sink.close();
+ expect(fs.file(ns('/foo')).readAsStringSync(), 'HelloGoodbye');
+ });
+
+ test('openWriteHandleDoesNotChange', () async {
+ var f = fs.file(ns('/foo'))..createSync();
+ var sink = f.openWrite();
+ sink.write('Hello');
+ await sink.flush();
+
+ final newFile = f.renameSync(ns('/bar'));
+ sink.write('Goodbye');
+ await sink.flush();
+ await sink.close();
+
+ expect(newFile.readAsStringSync(), 'HelloGoodbye');
+
+ assert(newFile.path != f.path);
+ expect(f, isNot(exists));
+ expect(newFile, exists);
+ });
+
+ group('ioSink', () {
+ late File f;
+ late IOSink sink;
+ late bool isSinkClosed;
+
+ Future<dynamic> closeSink() {
+ var future = sink.close();
+ isSinkClosed = true;
+ return future;
+ }
+
+ setUp(() {
+ f = fs.file(ns('/foo'));
+ sink = f.openWrite();
+ isSinkClosed = false;
+ });
+
+ tearDown(() async {
+ if (!isSinkClosed) {
+ await closeSink();
+ }
+ });
+
+ test('throwsIfAddError', () async {
+ sink.addError(ArgumentError());
+ expect(sink.done, throwsArgumentError);
+ isSinkClosed = true;
+ });
+
+ test('allowsChangingEncoding', () async {
+ sink.encoding = latin1;
+ sink.write('ÿ');
+ sink.encoding = utf8;
+ sink.write('ÿ');
+ await sink.flush();
+ expect(await f.readAsBytes(), <int>[255, 195, 191]);
+ });
+
+ test('succeedsIfAddRawData', () async {
+ sink.add(<int>[1, 2, 3, 4]);
+ await sink.flush();
+ expect(await f.readAsBytes(), <int>[1, 2, 3, 4]);
+ });
+
+ test('succeedsIfWrite', () async {
+ sink.write('Hello world');
+ await sink.flush();
+ expect(await f.readAsString(), 'Hello world');
+ });
+
+ test('succeedsIfWriteAll', () async {
+ sink.writeAll(<String>['foo', 'bar', 'baz'], ' ');
+ await sink.flush();
+ expect(await f.readAsString(), 'foo bar baz');
+ });
+
+ test('succeedsIfWriteCharCode', () async {
+ sink.writeCharCode(35);
+ await sink.flush();
+ expect(await f.readAsString(), '#');
+ });
+
+ test('succeedsIfWriteln', () async {
+ sink.writeln('Hello world');
+ await sink.flush();
+ expect(await f.readAsString(), 'Hello world\n');
+ });
+
+ test('ignoresDataWrittenAfterClose', () async {
+ sink.write('Before close');
+ await closeSink();
+ expect(() => sink.write('After close'), throwsStateError);
+ expect(await f.readAsString(), 'Before close');
+ });
+
+ test('ignoresCloseAfterAlreadyClosed', () async {
+ sink.write('Hello world');
+ var f1 = closeSink();
+ var f2 = closeSink();
+ await Future.wait<dynamic>(<Future<dynamic>>[f1, f2]);
+ });
+
+ test('returnsAccurateDoneFuture', () async {
+ var done = false;
+ // ignore: unawaited_futures
+ sink.done.then((dynamic _) => done = true);
+ expect(done, isFalse);
+ sink.write('foo');
+ expect(done, isFalse);
+ await sink.close();
+ expect(done, isTrue);
+ });
+
+ group('addStream', () {
+ late StreamController<List<int>> controller;
+ late bool isControllerClosed;
+
+ Future<dynamic> closeController() {
+ var future = controller.close();
+ isControllerClosed = true;
+ return future;
+ }
+
+ setUp(() {
+ controller = StreamController<List<int>>();
+ isControllerClosed = false;
+ sink.addStream(controller.stream);
+ });
+
+ tearDown(() async {
+ if (!isControllerClosed) {
+ await closeController();
+ }
+ });
+
+ test('succeedsIfStreamProducesData', () async {
+ controller.add(<int>[1, 2, 3, 4, 5]);
+ await closeController();
+ await sink.flush();
+ expect(await f.readAsBytes(), <int>[1, 2, 3, 4, 5]);
+ });
+
+ test('blocksCallToAddWhileStreamIsActive', () {
+ expect(() => sink.add(<int>[1, 2, 3]), throwsStateError);
+ });
+
+ test('blocksCallToWriteWhileStreamIsActive', () {
+ expect(() => sink.write('foo'), throwsStateError);
+ });
+
+ test('blocksCallToWriteAllWhileStreamIsActive', () {
+ expect(() => sink.writeAll(<String>['a', 'b']), throwsStateError);
+ });
+
+ test('blocksCallToWriteCharCodeWhileStreamIsActive', () {
+ expect(() => sink.writeCharCode(35), throwsStateError);
+ });
+
+ test('blocksCallToWritelnWhileStreamIsActive', () {
+ expect(() => sink.writeln('foo'), throwsStateError);
+ });
+
+ test('blocksCallToFlushWhileStreamIsActive', () {
+ expect(() => sink.flush(), throwsStateError);
+ });
+ });
+ });
+ });
+
+ group('readAsBytes', () {
+ test('throwsIfDoesntExist', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.file(ns('/foo')).readAsBytesSync();
+ });
+ });
+
+ test('throwsIfExistsAsDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ expectFileSystemException(ErrorCodes.EISDIR, () {
+ fs.file(ns('/foo')).readAsBytesSync();
+ });
+ });
+
+ test('throwsIfExistsAsLinkToDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ expectFileSystemException(ErrorCodes.EISDIR, () {
+ fs.file(ns('/bar')).readAsBytesSync();
+ });
+ });
+
+ test('succeedsIfExistsAsFile', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ f.writeAsBytesSync(<int>[1, 2, 3, 4]);
+ expect(f.readAsBytesSync(), <int>[1, 2, 3, 4]);
+ });
+
+ test('succeedsIfExistsAsLinkToFile', () {
+ fs.file(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ fs.file(ns('/foo')).writeAsBytesSync(<int>[1, 2, 3, 4]);
+ expect(fs.file(ns('/bar')).readAsBytesSync(), <int>[1, 2, 3, 4]);
+ });
+
+ test('returnsEmptyListForZeroByteFile', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ expect(f.readAsBytesSync(), isEmpty);
+ });
+
+ test('returns a copy, not a view, of the file content', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ f.writeAsBytesSync(<int>[1, 2, 3, 4]);
+ List<int> result = f.readAsBytesSync();
+ expect(result, <int>[1, 2, 3, 4]);
+ result[0] = 10;
+ expect(f.readAsBytesSync(), <int>[1, 2, 3, 4]);
+ });
+ });
+
+ group('readAsString', () {
+ test('throwsIfDoesntExist', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.file(ns('/foo')).readAsStringSync();
+ });
+ });
+
+ test('throwsIfExistsAsDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ expectFileSystemException(ErrorCodes.EISDIR, () {
+ fs.file(ns('/foo')).readAsStringSync();
+ });
+ });
+
+ test('throwsIfExistsAsLinkToDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ expectFileSystemException(ErrorCodes.EISDIR, () {
+ fs.file(ns('/bar')).readAsStringSync();
+ });
+ });
+
+ test('succeedsIfExistsAsFile', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ f.writeAsStringSync('Hello world');
+ expect(f.readAsStringSync(), 'Hello world');
+ });
+
+ test('succeedsIfExistsAsLinkToFile', () {
+ fs.file(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ fs.file(ns('/foo')).writeAsStringSync('Hello world');
+ expect(fs.file(ns('/bar')).readAsStringSync(), 'Hello world');
+ });
+
+ test('returnsEmptyStringForZeroByteFile', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ expect(f.readAsStringSync(), isEmpty);
+ });
+ });
+
+ group('readAsLines', () {
+ const testString = 'Hello world\nHow are you?\nI am fine';
+ final expectedLines = <String>[
+ 'Hello world',
+ 'How are you?',
+ 'I am fine',
+ ];
+
+ test('throwsIfDoesntExist', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.file(ns('/foo')).readAsLinesSync();
+ });
+ });
+
+ test('throwsIfExistsAsDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ expectFileSystemException(ErrorCodes.EISDIR, () {
+ fs.file(ns('/foo')).readAsLinesSync();
+ });
+ });
+
+ test('throwsIfExistsAsLinkToDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ expectFileSystemException(ErrorCodes.EISDIR, () {
+ fs.file(ns('/bar')).readAsLinesSync();
+ });
+ });
+
+ test('succeedsIfExistsAsFile', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ f.writeAsStringSync(testString);
+ expect(f.readAsLinesSync(), expectedLines);
+ });
+
+ test('succeedsIfExistsAsLinkToFile', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ f.writeAsStringSync(testString);
+ expect(f.readAsLinesSync(), expectedLines);
+ });
+
+ test('returnsEmptyListForZeroByteFile', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ expect(f.readAsLinesSync(), isEmpty);
+ });
+
+ test('isTrailingNewlineAgnostic', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ f.writeAsStringSync('$testString\n');
+ expect(f.readAsLinesSync(), expectedLines);
+
+ f.writeAsStringSync('\n');
+ expect(f.readAsLinesSync(), <String>['']);
+
+ f.writeAsStringSync('\n\n');
+ expect(f.readAsLinesSync(), <String>['', '']);
+ });
+ });
+
+ group('writeAsBytes', () {
+ test('returnsCovariantType', () async {
+ expect(await fs.file(ns('/foo')).writeAsBytes(<int>[]), isFile);
+ });
+
+ test('createsFileIfDoesntExist', () {
+ var f = fs.file(ns('/foo'));
+ expect(f, isNot(exists));
+ f.writeAsBytesSync(<int>[1, 2, 3, 4]);
+ expect(f, exists);
+ });
+
+ test('throwsIfExistsAsDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ expectFileSystemException(ErrorCodes.EISDIR, () {
+ fs.file(ns('/foo')).writeAsBytesSync(<int>[1, 2, 3, 4]);
+ });
+ });
+
+ test('throwsIfExistsAsLinkToDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ expectFileSystemException(ErrorCodes.EISDIR, () {
+ fs.file(ns('/foo')).writeAsBytesSync(<int>[1, 2, 3, 4]);
+ });
+ });
+
+ test('succeedsIfExistsAsLinkToFile', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ fs.file(ns('/bar')).writeAsBytesSync(<int>[1, 2, 3, 4]);
+ expect(f.readAsBytesSync(), <int>[1, 2, 3, 4]);
+ });
+
+ test('throwsIfFileModeRead', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ expectFileSystemException(ErrorCodes.EBADF, () {
+ f.writeAsBytesSync(<int>[1], mode: FileMode.read);
+ });
+ });
+
+ test('overwritesContentIfFileModeWrite', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ f.writeAsBytesSync(<int>[1, 2]);
+ expect(f.readAsBytesSync(), <int>[1, 2]);
+ f.writeAsBytesSync(<int>[3, 4]);
+ expect(f.readAsBytesSync(), <int>[3, 4]);
+ });
+
+ test('appendsContentIfFileModeAppend', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ f.writeAsBytesSync(<int>[1, 2], mode: FileMode.append);
+ expect(f.readAsBytesSync(), <int>[1, 2]);
+ f.writeAsBytesSync(<int>[3, 4], mode: FileMode.append);
+ expect(f.readAsBytesSync(), <int>[1, 2, 3, 4]);
+ });
+
+ test('acceptsEmptyBytesList', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ f.writeAsBytesSync(<int>[]);
+ expect(f.readAsBytesSync(), <int>[]);
+ });
+
+ test('updatesLastModifiedTime', () async {
+ var f = fs.file(ns('/foo'))..createSync();
+ var before = f.statSync().modified;
+ await Future<void>.delayed(const Duration(seconds: 2));
+ f.writeAsBytesSync(<int>[1, 2, 3]);
+ var after = f.statSync().modified;
+ expect(after, isAfter(before));
+ });
+ });
+
+ group('writeAsString', () {
+ test('returnsCovariantType', () async {
+ expect(await fs.file(ns('/foo')).writeAsString('foo'), isFile);
+ });
+
+ test('createsFileIfDoesntExist', () {
+ var f = fs.file(ns('/foo'));
+ expect(f, isNot(exists));
+ f.writeAsStringSync('Hello world');
+ expect(f, exists);
+ });
+
+ test('throwsIfExistsAsDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ expectFileSystemException(ErrorCodes.EISDIR, () {
+ fs.file(ns('/foo')).writeAsStringSync('Hello world');
+ });
+ });
+
+ test('throwsIfExistsAsLinkToDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ expectFileSystemException(ErrorCodes.EISDIR, () {
+ fs.file(ns('/foo')).writeAsStringSync('Hello world');
+ });
+ });
+
+ test('succeedsIfExistsAsLinkToFile', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ fs.file(ns('/bar')).writeAsStringSync('Hello world');
+ expect(f.readAsStringSync(), 'Hello world');
+ });
+
+ test('throwsIfFileModeRead', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ expectFileSystemException(ErrorCodes.EBADF, () {
+ f.writeAsStringSync('Hello world', mode: FileMode.read);
+ });
+ });
+
+ test('overwritesContentIfFileModeWrite', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ f.writeAsStringSync('Hello world');
+ expect(f.readAsStringSync(), 'Hello world');
+ f.writeAsStringSync('Goodbye cruel world');
+ expect(f.readAsStringSync(), 'Goodbye cruel world');
+ });
+
+ test('appendsContentIfFileModeAppend', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ f.writeAsStringSync('Hello', mode: FileMode.append);
+ expect(f.readAsStringSync(), 'Hello');
+ f.writeAsStringSync('Goodbye', mode: FileMode.append);
+ expect(f.readAsStringSync(), 'HelloGoodbye');
+ });
+
+ test('acceptsEmptyString', () {
+ var f = fs.file(ns('/foo'))..createSync();
+ f.writeAsStringSync('');
+ expect(f.readAsStringSync(), isEmpty);
+ });
+ });
+
+ group('exists', () {
+ test('trueIfExists', () {
+ fs.file(ns('/foo')).createSync();
+ expect(fs.file(ns('/foo')), exists);
+ });
+
+ test('falseIfDoesntExistAtTail', () {
+ expect(fs.file(ns('/foo')), isNot(exists));
+ });
+
+ test('falseIfDoesntExistViaTraversal', () {
+ expect(fs.file(ns('/foo/bar')), isNot(exists));
+ });
+
+ test('falseIfExistsAsDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ expect(fs.file(ns('/foo')), isNot(exists));
+ });
+
+ test('falseIfExistsAsLinkToDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ expect(fs.file(ns('/bar')), isNot(exists));
+ });
+
+ test('trueIfExistsAsLinkToFile', () {
+ fs.file(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ expect(fs.file(ns('/bar')), exists);
+ });
+
+ test('falseIfNotFoundSegmentExistsThenIsBackedOut', () {
+ fs.file(ns('/foo/bar')).createSync(recursive: true);
+ expect(fs.directory(ns('/baz/../foo/bar')), isNot(exists));
+ });
+ });
+
+ group('stat', () {
+ test('isNotFoundIfDoesntExistAtTail', () {
+ var stat = fs.file(ns('/foo')).statSync();
+ expect(stat.type, FileSystemEntityType.notFound);
+ });
+
+ test('isNotFoundIfDoesntExistViaTraversal', () {
+ var stat = fs.file(ns('/foo/bar')).statSync();
+ expect(stat.type, FileSystemEntityType.notFound);
+ });
+
+ test('isDirectoryIfExistsAsDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ var stat = fs.file(ns('/foo')).statSync();
+ expect(stat.type, FileSystemEntityType.directory);
+ });
+
+ test('isFileIfExistsAsFile', () {
+ fs.file(ns('/foo')).createSync();
+ var stat = fs.file(ns('/foo')).statSync();
+ expect(stat.type, FileSystemEntityType.file);
+ });
+
+ test('isFileIfExistsAsLinkToFile', () {
+ fs.file(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ var stat = fs.file(ns('/bar')).statSync();
+ expect(stat.type, FileSystemEntityType.file);
+ });
+ });
+
+ group('delete', () {
+ test('returnsCovariantType', () async {
+ var f = fs.file(ns('/foo'))..createSync();
+ expect(await f.delete(), isFile);
+ });
+
+ test('succeedsIfExistsAsFile', () {
+ fs.file(ns('/foo')).createSync();
+ expect(fs.file(ns('/foo')), exists);
+ fs.file(ns('/foo')).deleteSync();
+ expect(fs.file(ns('/foo')), isNot(exists));
+ });
+
+ test('throwsIfDoesntExistAndRecursiveFalse', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.file(ns('/foo')).deleteSync();
+ });
+ });
+
+ test('throwsIfDoesntExistAndRecursiveTrue', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.file(ns('/foo')).deleteSync(recursive: true);
+ });
+ });
+
+ test('succeedsIfExistsAsDirectoryAndRecursiveTrue', () {
+ fs.directory(ns('/foo')).createSync();
+ fs.file(ns('/foo')).deleteSync(recursive: true);
+ expect(fs.typeSync(ns('/foo')), FileSystemEntityType.notFound);
+ });
+
+ test('throwsIfExistsAsDirectoryAndRecursiveFalse', () {
+ fs.directory(ns('/foo')).createSync();
+ expectFileSystemException(ErrorCodes.EISDIR, () {
+ fs.file(ns('/foo')).deleteSync();
+ });
+ });
+
+ test('succeedsIfExistsAsLinkToFileAndRecursiveTrue', () {
+ fs.file(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ expect(fs.file(ns('/bar')), exists);
+ fs.file(ns('/bar')).deleteSync(recursive: true);
+ expect(fs.file(ns('/bar')), isNot(exists));
+ });
+
+ test('succeedsIfExistsAsLinkToFileAndRecursiveFalse', () {
+ fs.file(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ expect(fs.file(ns('/bar')), exists);
+ fs.file(ns('/bar')).deleteSync();
+ expect(fs.file(ns('/bar')), isNot(exists));
+ });
+
+ test('succeedsIfExistsAsLinkToDirectoryAndRecursiveTrue', () {
+ fs.directory(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ expect(fs.typeSync(ns('/bar')), FileSystemEntityType.directory);
+ fs.file(ns('/bar')).deleteSync(recursive: true);
+ expect(fs.typeSync(ns('/foo')), FileSystemEntityType.directory);
+ expect(fs.typeSync(ns('/bar')), FileSystemEntityType.notFound);
+ });
+
+ test('throwsIfExistsAsLinkToDirectoryAndRecursiveFalse', () {
+ fs.directory(ns('/foo')).createSync();
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ expectFileSystemException(ErrorCodes.EISDIR, () {
+ fs.file(ns('/bar')).deleteSync();
+ });
+ });
+ });
+ });
+
+ group('Link', () {
+ group('uri', () {
+ test('whenTargetIsDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ var l = fs.link(ns('/bar'))..createSync(ns('/foo'));
+ expect(l.uri, fs.path.toUri(ns('/bar')));
+ expect(fs.link('bar').uri.toString(), 'bar');
+ });
+
+ test('whenTargetIsFile', () {
+ fs.file(ns('/foo')).createSync();
+ var l = fs.link(ns('/bar'))..createSync(ns('/foo'));
+ expect(l.uri, fs.path.toUri(ns('/bar')));
+ expect(fs.link('bar').uri.toString(), 'bar');
+ });
+
+ test('whenLinkDoesntExist', () {
+ expect(fs.link(ns('/foo')).uri, fs.path.toUri(ns('/foo')));
+ expect(fs.link('foo').uri.toString(), 'foo');
+ });
+ });
+
+ group('exists', () {
+ test('isFalseIfLinkDoesntExistAtTail', () {
+ expect(fs.link(ns('/foo')), isNot(exists));
+ });
+
+ test('isFalseIfLinkDoesntExistViaTraversal', () {
+ expect(fs.link(ns('/foo/bar')), isNot(exists));
+ });
+
+ test('isFalseIfPathReferencesFile', () {
+ fs.file(ns('/foo')).createSync();
+ expect(fs.link(ns('/foo')), isNot(exists));
+ });
+
+ test('isFalseIfPathReferencesDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ expect(fs.link(ns('/foo')), isNot(exists));
+ });
+
+ test('isTrueIfTargetIsNotFound', () {
+ var l = fs.link(ns('/foo'))..createSync(ns('/bar'));
+ expect(l, exists);
+ });
+
+ test('isTrueIfTargetIsFile', () {
+ var l = fs.link(ns('/foo'))..createSync(ns('/bar'));
+ fs.file(ns('/bar')).createSync();
+ expect(l, exists);
+ });
+
+ test('isTrueIfTargetIsDirectory', () {
+ var l = fs.link(ns('/foo'))..createSync(ns('/bar'));
+ fs.directory(ns('/bar')).createSync();
+ expect(l, exists);
+ });
+
+ test('isTrueIfTargetIsLinkLoop', () {
+ var l = fs.link(ns('/foo'))..createSync(ns('/bar'));
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ expect(l, exists);
+ });
+ });
+
+ group('stat', () {
+ test('isNotFoundIfLinkDoesntExistAtTail', () {
+ expect(fs.link(ns('/foo')).statSync().type,
+ FileSystemEntityType.notFound);
+ });
+
+ test('isNotFoundIfLinkDoesntExistViaTraversal', () {
+ expect(fs.link(ns('/foo/bar')).statSync().type,
+ FileSystemEntityType.notFound);
+ });
+
+ test('isFileIfPathReferencesFile', () {
+ fs.file(ns('/foo')).createSync();
+ expect(
+ fs.link(ns('/foo')).statSync().type, FileSystemEntityType.file);
+ });
+
+ test('isDirectoryIfPathReferencesDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ expect(fs.link(ns('/foo')).statSync().type,
+ FileSystemEntityType.directory);
+ });
+
+ test('isNotFoundIfTargetNotFoundAtTail', () {
+ var l = fs.link(ns('/foo'))..createSync(ns('/bar'));
+ expect(l.statSync().type, FileSystemEntityType.notFound);
+ });
+
+ test('isNotFoundIfTargetNotFoundViaTraversal', () {
+ var l = fs.link(ns('/foo'))..createSync(ns('/bar/baz'));
+ expect(l.statSync().type, FileSystemEntityType.notFound);
+ });
+
+ test('isNotFoundIfTargetIsLinkLoop', () {
+ var l = fs.link(ns('/foo'))..createSync(ns('/bar'));
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ expect(l.statSync().type, FileSystemEntityType.notFound);
+ });
+
+ test('isFileIfTargetIsFile', () {
+ var l = fs.link(ns('/foo'))..createSync(ns('/bar'));
+ fs.file(ns('/bar')).createSync();
+ expect(l.statSync().type, FileSystemEntityType.file);
+ });
+
+ test('isDirectoryIfTargetIsDirectory', () {
+ var l = fs.link(ns('/foo'))..createSync(ns('/bar'));
+ fs.directory(ns('/bar')).createSync();
+ expect(l.statSync().type, FileSystemEntityType.directory);
+ });
+ });
+
+ group('delete', () {
+ test('returnsCovariantType', () async {
+ var link = fs.link(ns('/foo'))..createSync(ns('/bar'));
+ expect(await link.delete(), isLink);
+ });
+
+ test('throwsIfLinkDoesntExistAtTail', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.link(ns('/foo')).deleteSync();
+ });
+ });
+
+ test('throwsIfLinkDoesntExistViaTraversal', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.link(ns('/foo/bar')).deleteSync();
+ });
+ });
+
+ test('throwsIfPathReferencesFileAndRecursiveFalse', () {
+ fs.file(ns('/foo')).createSync();
+ expectFileSystemException(ErrorCodes.EINVAL, () {
+ fs.link(ns('/foo')).deleteSync();
+ });
+ });
+
+ test('succeedsIfPathReferencesFileAndRecursiveTrue', () {
+ fs.file(ns('/foo')).createSync();
+ fs.link(ns('/foo')).deleteSync(recursive: true);
+ expect(fs.typeSync(ns('/foo'), followLinks: false),
+ FileSystemEntityType.notFound);
+ });
+
+ test('throwsIfPathReferencesDirectoryAndRecursiveFalse', () {
+ fs.directory(ns('/foo')).createSync();
+ // TODO(tvolkert): Change this to just be 'Is a directory'
+ // once Dart 1.22 is stable.
+ expectFileSystemException(
+ anyOf(ErrorCodes.EINVAL, ErrorCodes.EISDIR),
+ () {
+ fs.link(ns('/foo')).deleteSync();
+ },
+ );
+ });
+
+ test('succeedsIfPathReferencesDirectoryAndRecursiveTrue', () {
+ fs.directory(ns('/foo')).createSync();
+ fs.link(ns('/foo')).deleteSync(recursive: true);
+ expect(fs.typeSync(ns('/foo'), followLinks: false),
+ FileSystemEntityType.notFound);
+ });
+
+ test('unlinksIfTargetIsFileAndRecursiveFalse', () {
+ var l = fs.link(ns('/foo'))..createSync(ns('/bar'));
+ fs.file(ns('/bar')).createSync();
+ l.deleteSync();
+ expect(fs.typeSync(ns('/foo'), followLinks: false),
+ FileSystemEntityType.notFound);
+ expect(fs.typeSync(ns('/bar'), followLinks: false),
+ FileSystemEntityType.file);
+ });
+
+ test('unlinksIfTargetIsFileAndRecursiveTrue', () {
+ var l = fs.link(ns('/foo'))..createSync(ns('/bar'));
+ fs.file(ns('/bar')).createSync();
+ l.deleteSync(recursive: true);
+ expect(fs.typeSync(ns('/foo'), followLinks: false),
+ FileSystemEntityType.notFound);
+ expect(fs.typeSync(ns('/bar'), followLinks: false),
+ FileSystemEntityType.file);
+ });
+
+ test('unlinksIfTargetIsDirectoryAndRecursiveFalse', () {
+ var l = fs.link(ns('/foo'))..createSync(ns('/bar'));
+ fs.directory(ns('/bar')).createSync();
+ l.deleteSync();
+ expect(fs.typeSync(ns('/foo'), followLinks: false),
+ FileSystemEntityType.notFound);
+ expect(fs.typeSync(ns('/bar'), followLinks: false),
+ FileSystemEntityType.directory);
+ });
+
+ test('unlinksIfTargetIsDirectoryAndRecursiveTrue', () {
+ var l = fs.link(ns('/foo'))..createSync(ns('/bar'));
+ fs.directory(ns('/bar')).createSync();
+ l.deleteSync(recursive: true);
+ expect(fs.typeSync(ns('/foo'), followLinks: false),
+ FileSystemEntityType.notFound);
+ expect(fs.typeSync(ns('/bar'), followLinks: false),
+ FileSystemEntityType.directory);
+ });
+
+ test('unlinksIfTargetIsLinkLoop', () {
+ var l = fs.link(ns('/foo'))..createSync(ns('/bar'));
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ l.deleteSync();
+ expect(fs.typeSync(ns('/foo'), followLinks: false),
+ FileSystemEntityType.notFound);
+ expect(fs.typeSync(ns('/bar'), followLinks: false),
+ FileSystemEntityType.link);
+ });
+ });
+
+ group('parent', () {
+ test('returnsCovariantType', () {
+ expect(fs.link(ns('/foo')).parent, isDirectory);
+ });
+
+ test('succeedsIfLinkDoesntExist', () {
+ expect(fs.link(ns('/foo')).parent.path, ns('/'));
+ });
+
+ test('ignoresLinkTarget', () {
+ var l = fs.link(ns('/foo/bar'))
+ ..createSync(ns('/baz/qux'), recursive: true);
+ expect(l.parent.path, ns('/foo'));
+ });
+ });
+
+ group('create', () {
+ test('returnsCovariantType', () async {
+ expect(await fs.link(ns('/foo')).create(ns('/bar')), isLink);
+ });
+
+ test('succeedsIfLinkDoesntExistAtTail', () {
+ var l = fs.link(ns('/foo'))..createSync(ns('/bar'));
+ expect(fs.typeSync(ns('/foo'), followLinks: false),
+ FileSystemEntityType.link);
+ expect(l.targetSync(), ns('/bar'));
+ });
+
+ test('throwsIfLinkDoesntExistViaTraversalAndRecursiveFalse', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.link(ns('/foo/bar')).createSync('baz');
+ });
+ });
+
+ test('succeedsIfLinkDoesntExistViaTraversalAndRecursiveTrue', () {
+ var l = fs.link(ns('/foo/bar'))..createSync('baz', recursive: true);
+ expect(fs.typeSync(ns('/foo'), followLinks: false),
+ FileSystemEntityType.directory);
+ expect(fs.typeSync(ns('/foo/bar'), followLinks: false),
+ FileSystemEntityType.link);
+ expect(l.targetSync(), 'baz');
+ });
+
+ test('throwsIfAlreadyExistsAsFile', () {
+ fs.file(ns('/foo')).createSync();
+ expectFileSystemException(ErrorCodes.EEXIST, () {
+ fs.link(ns('/foo')).createSync(ns('/bar'));
+ });
+ });
+
+ test('throwsIfAlreadyExistsAsDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ expectFileSystemException(ErrorCodes.EEXIST, () {
+ fs.link(ns('/foo')).createSync(ns('/bar'));
+ });
+ });
+
+ test('throwsIfAlreadyExistsWithSameTarget', () {
+ fs.link(ns('/foo')).createSync(ns('/bar'));
+ expectFileSystemException(ErrorCodes.EEXIST, () {
+ fs.link(ns('/foo')).createSync(ns('/bar'));
+ });
+ });
+
+ test('throwsIfAlreadyExistsWithDifferentTarget', () {
+ fs.link(ns('/foo')).createSync(ns('/bar'));
+ expectFileSystemException(ErrorCodes.EEXIST, () {
+ fs.link(ns('/foo')).createSync(ns('/baz'));
+ });
+ });
+ });
+
+ group('update', () {
+ test('returnsCovariantType', () async {
+ var l = fs.link(ns('/foo'))..createSync(ns('/bar'));
+ expect(await l.update(ns('/baz')), isLink);
+ });
+
+ test('throwsIfLinkDoesntExistAtTail', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.link(ns('/foo')).updateSync(ns('/bar'));
+ });
+ });
+
+ test('throwsIfLinkDoesntExistViaTraversal', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.link(ns('/foo/bar')).updateSync(ns('/baz'));
+ });
+ });
+
+ test('throwsIfPathReferencesFile', () {
+ fs.file(ns('/foo')).createSync();
+ expectFileSystemException(ErrorCodes.EINVAL, () {
+ fs.link(ns('/foo')).updateSync(ns('/bar'));
+ });
+ });
+
+ test('throwsIfPathReferencesDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ // TODO(tvolkert): Change this to just be 'Is a directory'
+ // once Dart 1.22 is stable.
+ expectFileSystemException(
+ anyOf(ErrorCodes.EINVAL, ErrorCodes.EISDIR),
+ () {
+ fs.link(ns('/foo')).updateSync(ns('/bar'));
+ },
+ );
+ });
+
+ test('succeedsIfNewTargetSameAsOldTarget', () {
+ fs.link(ns('/foo')).createSync(ns('/bar'));
+ fs.link(ns('/foo')).updateSync(ns('/bar'));
+ expect(fs.typeSync(ns('/foo'), followLinks: false),
+ FileSystemEntityType.link);
+ expect(fs.link(ns('/foo')).targetSync(), ns('/bar'));
+ });
+
+ test('succeedsIfNewTargetDifferentFromOldTarget', () {
+ fs.link(ns('/foo')).createSync(ns('/bar'));
+ fs.link(ns('/foo')).updateSync(ns('/baz'));
+ expect(fs.typeSync(ns('/foo'), followLinks: false),
+ FileSystemEntityType.link);
+ expect(fs.link(ns('/foo')).targetSync(), ns('/baz'));
+ });
+ });
+
+ group('absolute', () {
+ test('returnsCovariantType', () {
+ expect(fs.link('foo').absolute, isLink);
+ });
+
+ test('returnsSamePathIfAlreadyAbsolute', () {
+ expect(fs.link(ns('/foo')).absolute.path, ns('/foo'));
+ });
+
+ test('succeedsForRelativePaths', () {
+ expect(fs.link('foo').absolute.path, ns('/foo'));
+ });
+ });
+
+ group('target', () {
+ test('throwsIfLinkDoesntExistAtTail', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.link(ns('/foo')).targetSync();
+ });
+ });
+
+ test('throwsIfLinkDoesntExistViaTraversal', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.link(ns('/foo/bar')).targetSync();
+ });
+ });
+
+ test('throwsIfPathReferencesFile', () {
+ fs.file(ns('/foo')).createSync();
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.link(ns('/foo')).targetSync();
+ });
+ });
+
+ test('throwsIfPathReferencesDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.link(ns('/foo')).targetSync();
+ });
+ });
+
+ test('succeedsIfTargetIsNotFound', () {
+ var l = fs.link(ns('/foo'))..createSync(ns('/bar'));
+ expect(l.targetSync(), ns('/bar'));
+ });
+
+ test('succeedsIfTargetIsFile', () {
+ var l = fs.link(ns('/foo'))..createSync(ns('/bar'));
+ fs.file(ns('/bar')).createSync();
+ expect(l.targetSync(), ns('/bar'));
+ });
+
+ test('succeedsIfTargetIsDirectory', () {
+ var l = fs.link(ns('/foo'))..createSync(ns('/bar'));
+ fs.directory(ns('/bar')).createSync();
+ expect(l.targetSync(), ns('/bar'));
+ });
+
+ test('succeedsIfTargetIsLinkLoop', () {
+ var l = fs.link(ns('/foo'))..createSync(ns('/bar'));
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ expect(l.targetSync(), ns('/bar'));
+ });
+ });
+
+ group('rename', () {
+ test('returnsCovariantType', () async {
+ Link l() => fs.link(ns('/foo'))..createSync(ns('/bar'));
+ expect(l().renameSync(ns('/bar')), isLink);
+ expect(await l().rename(ns('/bar')), isLink);
+ });
+
+ test('throwsIfSourceDoesntExistAtTail', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.link(ns('/foo')).renameSync(ns('/bar'));
+ });
+ });
+
+ test('throwsIfSourceDoesntExistViaTraversal', () {
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ fs.link(ns('/foo/bar')).renameSync(ns('/bar'));
+ });
+ });
+
+ test('throwsIfSourceIsFile', () {
+ fs.file(ns('/foo')).createSync();
+ expectFileSystemException(ErrorCodes.EINVAL, () {
+ fs.link(ns('/foo')).renameSync(ns('/bar'));
+ });
+ });
+
+ test('throwsIfSourceIsDirectory', () {
+ fs.directory(ns('/foo')).createSync();
+ expectFileSystemException(ErrorCodes.EISDIR, () {
+ fs.link(ns('/foo')).renameSync(ns('/bar'));
+ });
+ });
+
+ test('succeedsIfSourceIsLinkToFile', () {
+ var l = fs.link(ns('/foo'))..createSync(ns('/bar'));
+ fs.file(ns('/bar')).createSync();
+ var renamed = l.renameSync(ns('/baz'));
+ expect(renamed.path, ns('/baz'));
+ expect(fs.typeSync(ns('/foo'), followLinks: false),
+ FileSystemEntityType.notFound);
+ expect(fs.typeSync(ns('/bar'), followLinks: false),
+ FileSystemEntityType.file);
+ expect(fs.typeSync(ns('/baz'), followLinks: false),
+ FileSystemEntityType.link);
+ expect(fs.link(ns('/baz')).targetSync(), ns('/bar'));
+ });
+
+ test('succeedsIfSourceIsLinkToNotFound', () {
+ var l = fs.link(ns('/foo'))..createSync(ns('/bar'));
+ var renamed = l.renameSync(ns('/baz'));
+ expect(renamed.path, ns('/baz'));
+ expect(fs.typeSync(ns('/foo'), followLinks: false),
+ FileSystemEntityType.notFound);
+ expect(fs.typeSync(ns('/baz'), followLinks: false),
+ FileSystemEntityType.link);
+ expect(fs.link(ns('/baz')).targetSync(), ns('/bar'));
+ });
+
+ test('succeedsIfSourceIsLinkToDirectory', () {
+ var l = fs.link(ns('/foo'))..createSync(ns('/bar'));
+ fs.directory(ns('/bar')).createSync();
+ var renamed = l.renameSync(ns('/baz'));
+ expect(renamed.path, ns('/baz'));
+ expect(fs.typeSync(ns('/foo'), followLinks: false),
+ FileSystemEntityType.notFound);
+ expect(fs.typeSync(ns('/bar'), followLinks: false),
+ FileSystemEntityType.directory);
+ expect(fs.typeSync(ns('/baz'), followLinks: false),
+ FileSystemEntityType.link);
+ expect(fs.link(ns('/baz')).targetSync(), ns('/bar'));
+ });
+
+ test('succeedsIfSourceIsLinkLoop', () {
+ var l = fs.link(ns('/foo'))..createSync(ns('/bar'));
+ fs.link(ns('/bar')).createSync(ns('/foo'));
+ var renamed = l.renameSync(ns('/baz'));
+ expect(renamed.path, ns('/baz'));
+ expect(fs.typeSync(ns('/foo'), followLinks: false),
+ FileSystemEntityType.notFound);
+ expect(fs.typeSync(ns('/bar'), followLinks: false),
+ FileSystemEntityType.link);
+ expect(fs.typeSync(ns('/baz'), followLinks: false),
+ FileSystemEntityType.link);
+ expect(fs.link(ns('/baz')).targetSync(), ns('/bar'));
+ });
+
+ test('succeedsIfDestinationDoesntExistAtTail', () {
+ var l = fs.link(ns('/foo'))..createSync(ns('/bar'));
+ var renamed = l.renameSync(ns('/baz'));
+ expect(renamed.path, ns('/baz'));
+ expect(fs.link(ns('/foo')), isNot(exists));
+ expect(fs.link(ns('/baz')), exists);
+ });
+
+ test('throwsIfDestinationDoesntExistViaTraversal', () {
+ var l = fs.link(ns('/foo'))..createSync(ns('/bar'));
+ expectFileSystemException(ErrorCodes.ENOENT, () {
+ l.renameSync(ns('/baz/qux'));
+ });
+ });
+
+ test('throwsIfDestinationExistsAsFile', () {
+ var l = fs.link(ns('/foo'))..createSync(ns('/bar'));
+ fs.file(ns('/baz')).createSync();
+ expectFileSystemException(ErrorCodes.EINVAL, () {
+ l.renameSync(ns('/baz'));
+ });
+ });
+
+ test('throwsIfDestinationExistsAsDirectory', () {
+ var l = fs.link(ns('/foo'))..createSync(ns('/bar'));
+ fs.directory(ns('/baz')).createSync();
+ expectFileSystemException(ErrorCodes.EINVAL, () {
+ l.renameSync(ns('/baz'));
+ });
+ });
+
+ test('succeedsIfDestinationExistsAsLinkToFile', () {
+ var l = fs.link(ns('/foo'))..createSync(ns('/bar'));
+ fs.file(ns('/baz')).createSync();
+ fs.link(ns('/qux')).createSync(ns('/baz'));
+ l.renameSync(ns('/qux'));
+ expect(fs.typeSync(ns('/foo')), FileSystemEntityType.notFound);
+ expect(fs.typeSync(ns('/baz'), followLinks: false),
+ FileSystemEntityType.file);
+ expect(fs.typeSync(ns('/qux'), followLinks: false),
+ FileSystemEntityType.link);
+ expect(fs.link(ns('/qux')).targetSync(), ns('/bar'));
+ });
+
+ test('throwsIfDestinationExistsAsLinkToDirectory', () {
+ var l = fs.link(ns('/foo'))..createSync(ns('/bar'));
+ fs.directory(ns('/baz')).createSync();
+ fs.link(ns('/qux')).createSync(ns('/baz'));
+ l.renameSync(ns('/qux'));
+ expect(fs.typeSync(ns('/foo')), FileSystemEntityType.notFound);
+ expect(fs.typeSync(ns('/baz'), followLinks: false),
+ FileSystemEntityType.directory);
+ expect(fs.typeSync(ns('/qux'), followLinks: false),
+ FileSystemEntityType.link);
+ expect(fs.link(ns('/qux')).targetSync(), ns('/bar'));
+ });
+
+ test('succeedsIfDestinationExistsAsLinkToNotFound', () {
+ var l = fs.link(ns('/foo'))..createSync(ns('/bar'));
+ fs.link(ns('/baz')).createSync(ns('/qux'));
+ l.renameSync(ns('/baz'));
+ expect(fs.typeSync(ns('/foo')), FileSystemEntityType.notFound);
+ expect(fs.typeSync(ns('/baz'), followLinks: false),
+ FileSystemEntityType.link);
+ expect(fs.link(ns('/baz')).targetSync(), ns('/bar'));
+ });
+ });
+ });
+ });
+}
diff --git a/pkgs/file/test/local_test.dart b/pkgs/file/test/local_test.dart
new file mode 100644
index 0000000..b794ccd
--- /dev/null
+++ b/pkgs/file/test/local_test.dart
@@ -0,0 +1,136 @@
+// Copyright (c) 2017, 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: lines_longer_than_80_chars
+
+@TestOn('vm')
+library;
+
+import 'dart:io' as io;
+
+import 'package:file/local.dart';
+import 'package:file_testing/src/testing/internal.dart';
+import 'package:test/test.dart';
+
+import 'common_tests.dart';
+
+void main() {
+ group('LocalFileSystem', () {
+ late LocalFileSystem fs;
+ late io.Directory tmp;
+ late String cwd;
+
+ setUp(() {
+ fs = const LocalFileSystem();
+ tmp = io.Directory.systemTemp.createTempSync('file_test_');
+ tmp = io.Directory(tmp.resolveSymbolicLinksSync());
+ cwd = io.Directory.current.path;
+ io.Directory.current = tmp;
+ });
+
+ tearDown(() {
+ io.Directory.current = cwd;
+ tmp.deleteSync(recursive: true);
+ });
+
+ setUpAll(() {
+ if (io.Platform.isWindows) {
+ // TODO(tvolkert): Remove once all more serious test failures are fixed
+ // https://github.com/dart-lang/tools/issues/618
+ ignoreOsErrorCodes = true;
+ }
+ });
+
+ tearDownAll(() {
+ ignoreOsErrorCodes = false;
+ });
+
+ var skipOnPlatform = <String, List<String>>{
+ 'windows': <String>[
+ 'FileSystem > currentDirectory > throwsIfHasNonExistentPathInComplexChain',
+ 'FileSystem > currentDirectory > resolvesLinksIfEncountered',
+ 'FileSystem > currentDirectory > succeedsIfSetToDirectoryLinkAtTail',
+ 'FileSystem > stat > isFileForLinkToFile',
+ 'FileSystem > type > isFileForLinkToFileAndFollowLinksTrue',
+ 'FileSystem > type > isNotFoundForLinkWithCircularReferenceAndFollowLinksTrue',
+ 'Directory > exists > falseIfNotFoundSegmentExistsThenIsBackedOut',
+ 'Directory > rename > throwsIfDestinationIsNonEmptyDirectory',
+ 'Directory > rename > throwsIfDestinationIsLinkToEmptyDirectory',
+ 'Directory > resolveSymbolicLinks > throwsIfPathNotFoundInMiddleThenBackedOut',
+ 'Directory > resolveSymbolicLinks > handlesRelativeLinks',
+ 'Directory > resolveSymbolicLinks > handlesLinksWhoseTargetsHaveNestedLinks',
+ 'Directory > resolveSymbolicLinks > handlesComplexPathWithMultipleLinks',
+ 'Directory > createTemp > succeedsWithNestedPathPrefixThatExists',
+ 'Directory > list > followsLinksIfFollowLinksTrue',
+ 'Directory > list > returnsCovariantType',
+ 'Directory > list > returnsLinkObjectsForRecursiveLinkIfFollowLinksTrue',
+ 'Directory > delete > succeedsIfPathReferencesLinkToFileAndRecursiveTrue',
+ 'File > rename > succeedsIfSourceExistsAsLinkToFile',
+ 'File > copy > succeedsIfSourceExistsAsLinkToFile',
+ 'File > copy > succeedsIfSourceIsLinkToFileInDifferentDirectory',
+ 'File > delete > succeedsIfExistsAsLinkToFileAndRecursiveTrue',
+ 'File > openWrite > ioSink > throwsIfEncodingIsNullAndWriteObject',
+ 'File > openWrite > ioSink > allowsChangingEncoding',
+ 'File > openWrite > ioSink > succeedsIfAddRawData',
+ 'File > openWrite > ioSink > succeedsIfWrite',
+ 'File > openWrite > ioSink > succeedsIfWriteAll',
+ 'File > openWrite > ioSink > succeedsIfWriteCharCode',
+ 'File > openWrite > ioSink > succeedsIfWriteln',
+ 'File > openWrite > ioSink > addStream > succeedsIfStreamProducesData',
+ 'File > openWrite > ioSink > addStream > blocksCallToAddWhileStreamIsActive',
+ 'File > openWrite > ioSink > addStream > blocksCallToWriteWhileStreamIsActive',
+ 'File > openWrite > ioSink > addStream > blocksCallToWriteAllWhileStreamIsActive',
+ 'File > openWrite > ioSink > addStream > blocksCallToWriteCharCodeWhileStreamIsActive',
+ 'File > openWrite > ioSink > addStream > blocksCallToWritelnWhileStreamIsActive',
+ 'File > openWrite > ioSink > addStream > blocksCallToFlushWhileStreamIsActive',
+ 'File > stat > isFileIfExistsAsLinkToFile',
+ 'Link > stat > isFileIfTargetIsFile',
+ 'Link > stat > isDirectoryIfTargetIsDirectory',
+ 'Link > delete > unlinksIfTargetIsDirectoryAndRecursiveTrue',
+ 'Link > delete > unlinksIfTargetIsFileAndRecursiveTrue',
+
+ // Fixed in SDK 1.23 (https://github.com/dart-lang/sdk/issues/28852)
+ 'File > open > WRITE > RandomAccessFile > truncate > throwsIfSetToNegativeNumber',
+ 'File > open > APPEND > RandomAccessFile > truncate > throwsIfSetToNegativeNumber',
+ 'File > open > WRITE_ONLY > RandomAccessFile > truncate > throwsIfSetToNegativeNumber',
+ 'File > open > WRITE_ONLY_APPEND > RandomAccessFile > truncate > throwsIfSetToNegativeNumber',
+
+ // Windows does not allow removing or renaming open files.
+ '.* > openReadHandleDoesNotChange',
+ '.* > openWriteHandleDoesNotChange',
+ ],
+ };
+
+ runCommonTests(
+ () => fs,
+ root: () => tmp.path,
+ skip: <String>[
+ // https://github.com/dart-lang/sdk/issues/28171
+ 'File > rename > throwsIfDestinationExistsAsLinkToDirectory',
+
+ // https://github.com/dart-lang/sdk/issues/28275
+ 'Link > rename > throwsIfDestinationExistsAsDirectory',
+
+ // https://github.com/dart-lang/sdk/issues/28277
+ 'Link > rename > throwsIfDestinationExistsAsFile',
+
+ ...skipOnPlatform[io.Platform.operatingSystem] ?? <String>[],
+ ],
+ );
+
+ group('toString', () {
+ test('File', () {
+ expect(fs.file('/foo').toString(), "LocalFile: '/foo'");
+ });
+
+ test('Directory', () {
+ expect(fs.directory('/foo').toString(), "LocalDirectory: '/foo'");
+ });
+
+ test('Link', () {
+ expect(fs.link('/foo').toString(), "LocalLink: '/foo'");
+ });
+ });
+ });
+}
diff --git a/pkgs/file/test/memory_operations_test.dart b/pkgs/file/test/memory_operations_test.dart
new file mode 100644
index 0000000..916707c
--- /dev/null
+++ b/pkgs/file/test/memory_operations_test.dart
@@ -0,0 +1,230 @@
+// Copyright (c) 2017, 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/memory.dart';
+import 'package:test/test.dart';
+
+void main() {
+ test('Read operations invoke opHandle', () async {
+ var contexts = <String>[];
+ var operations = <FileSystemOp>[];
+ var fs = MemoryFileSystem.test(
+ opHandle: (String context, FileSystemOp operation) {
+ if (operation == FileSystemOp.read) {
+ contexts.add(context);
+ operations.add(operation);
+ }
+ });
+ final file = fs.file('test')..createSync();
+
+ await file.readAsBytes();
+ file.readAsBytesSync();
+ await file.readAsString();
+ file.readAsStringSync();
+
+ expect(contexts, <String>['test', 'test', 'test', 'test']);
+ expect(operations, <FileSystemOp>[
+ FileSystemOp.read,
+ FileSystemOp.read,
+ FileSystemOp.read,
+ FileSystemOp.read
+ ]);
+ });
+
+ test('Write operations invoke opHandle', () async {
+ var contexts = <String>[];
+ var operations = <FileSystemOp>[];
+ var fs = MemoryFileSystem.test(
+ opHandle: (String context, FileSystemOp operation) {
+ if (operation == FileSystemOp.write) {
+ contexts.add(context);
+ operations.add(operation);
+ }
+ });
+ final file = fs.file('test')..createSync();
+
+ await file.writeAsBytes(<int>[]);
+ file.writeAsBytesSync(<int>[]);
+ await file.writeAsString('');
+ file.writeAsStringSync('');
+
+ expect(contexts, <String>['test', 'test', 'test', 'test']);
+ expect(operations, <FileSystemOp>[
+ FileSystemOp.write,
+ FileSystemOp.write,
+ FileSystemOp.write,
+ FileSystemOp.write
+ ]);
+ });
+
+ test('Delete operations invoke opHandle', () async {
+ var contexts = <String>[];
+ var operations = <FileSystemOp>[];
+ var fs = MemoryFileSystem.test(
+ opHandle: (String context, FileSystemOp operation) {
+ if (operation == FileSystemOp.delete) {
+ contexts.add(context);
+ operations.add(operation);
+ }
+ });
+ final file = fs.file('test')..createSync();
+ final directory = fs.directory('testDir')..createSync();
+ final link = fs.link('testLink')..createSync('foo');
+
+ await file.delete();
+ file.createSync();
+ file.deleteSync();
+
+ await directory.delete();
+ directory.createSync();
+ directory.deleteSync();
+
+ await link.delete();
+ link.createSync('foo');
+ link.deleteSync();
+
+ expect(contexts,
+ <String>['test', 'test', 'testDir', 'testDir', 'testLink', 'testLink']);
+ expect(operations, <FileSystemOp>[
+ FileSystemOp.delete,
+ FileSystemOp.delete,
+ FileSystemOp.delete,
+ FileSystemOp.delete,
+ FileSystemOp.delete,
+ FileSystemOp.delete,
+ ]);
+ });
+
+ test('Create operations invoke opHandle', () async {
+ var contexts = <String>[];
+ var operations = <FileSystemOp>[];
+ var fs = MemoryFileSystem.test(
+ opHandle: (String context, FileSystemOp operation) {
+ if (operation == FileSystemOp.create) {
+ contexts.add(context);
+ operations.add(operation);
+ }
+ });
+ fs.file('testA').createSync();
+ await fs.file('testB').create();
+ fs.directory('testDirA').createSync();
+ await fs.directory('testDirB').create();
+ fs.link('testLinkA').createSync('foo');
+ await fs.link('testLinkB').create('foo');
+ fs.currentDirectory.createTempSync('tmp.bar');
+ await fs.currentDirectory.createTemp('tmp.bar');
+
+ expect(contexts, <dynamic>[
+ 'testA',
+ 'testB',
+ 'testDirA',
+ 'testDirB',
+ 'testLinkA',
+ 'testLinkB',
+ startsWith('/tmp.bar'),
+ startsWith('/tmp.bar'),
+ ]);
+ expect(operations, <FileSystemOp>[
+ FileSystemOp.create,
+ FileSystemOp.create,
+ FileSystemOp.create,
+ FileSystemOp.create,
+ FileSystemOp.create,
+ FileSystemOp.create,
+ FileSystemOp.create,
+ FileSystemOp.create,
+ ]);
+ });
+
+ test('Open operations invoke opHandle', () async {
+ var contexts = <String>[];
+ var operations = <FileSystemOp>[];
+ var fs = MemoryFileSystem.test(
+ opHandle: (String context, FileSystemOp operation) {
+ if (operation == FileSystemOp.open) {
+ contexts.add(context);
+ operations.add(operation);
+ }
+ });
+ final file = fs.file('test')..createSync();
+
+ await file.open();
+ file.openSync();
+ file.openRead();
+ file.openWrite();
+
+ expect(contexts, <String>['test', 'test', 'test', 'test']);
+ expect(operations, <FileSystemOp>[
+ FileSystemOp.open,
+ FileSystemOp.open,
+ FileSystemOp.open,
+ FileSystemOp.open,
+ ]);
+ });
+
+ test('Copy operations invoke opHandle', () async {
+ var contexts = <String>[];
+ var operations = <FileSystemOp>[];
+ var fs = MemoryFileSystem.test(
+ opHandle: (String context, FileSystemOp operation) {
+ if (operation == FileSystemOp.copy) {
+ contexts.add(context);
+ operations.add(operation);
+ }
+ });
+ final file = fs.file('test')..createSync();
+
+ await file.copy('A');
+ file.copySync('B');
+
+ expect(contexts, <String>['test', 'test']);
+ expect(operations, <FileSystemOp>[
+ FileSystemOp.copy,
+ FileSystemOp.copy,
+ ]);
+ });
+
+ test('Exists operations invoke opHandle', () async {
+ var contexts = <String>[];
+ var operations = <FileSystemOp>[];
+ var fs = MemoryFileSystem.test(
+ opHandle: (String context, FileSystemOp operation) {
+ if (operation == FileSystemOp.exists) {
+ contexts.add(context);
+ operations.add(operation);
+ }
+ });
+ fs.file('testA').existsSync();
+ await fs.file('testB').exists();
+ fs.directory('testDirA').existsSync();
+ await fs.directory('testDirB').exists();
+ fs.link('testLinkA').existsSync();
+ await fs.link('testLinkB').exists();
+
+ expect(contexts, <dynamic>[
+ 'testA',
+ 'testB',
+ 'testDirA',
+ 'testDirB',
+ 'testLinkA',
+ 'testLinkB',
+ ]);
+ expect(operations, <FileSystemOp>[
+ FileSystemOp.exists,
+ FileSystemOp.exists,
+ FileSystemOp.exists,
+ FileSystemOp.exists,
+ FileSystemOp.exists,
+ FileSystemOp.exists,
+ ]);
+ });
+
+ test('FileSystemOp toString', () {
+ expect(FileSystemOp.create.toString(), 'FileSystemOp.create');
+ expect(FileSystemOp.delete.toString(), 'FileSystemOp.delete');
+ expect(FileSystemOp.read.toString(), 'FileSystemOp.read');
+ expect(FileSystemOp.write.toString(), 'FileSystemOp.write');
+ expect(FileSystemOp.exists.toString(), 'FileSystemOp.exists');
+ });
+}
diff --git a/pkgs/file/test/memory_test.dart b/pkgs/file/test/memory_test.dart
new file mode 100644
index 0000000..ce8675f
--- /dev/null
+++ b/pkgs/file/test/memory_test.dart
@@ -0,0 +1,172 @@
+// Copyright (c) 2017, 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;
+
+import 'package:file/file.dart';
+import 'package:file/memory.dart';
+import 'package:file/src/backends/memory/memory_random_access_file.dart';
+import 'package:test/test.dart';
+
+import 'common_tests.dart';
+
+void main() {
+ group('MemoryFileSystem unix style', () {
+ late MemoryFileSystem fs;
+
+ setUp(() {
+ fs = MemoryFileSystem();
+ });
+
+ runCommonTests(() => fs);
+
+ group('toString', () {
+ test('File', () {
+ expect(fs.file('/foo').toString(), "MemoryFile: '/foo'");
+ });
+
+ test('Directory', () {
+ expect(fs.directory('/foo').toString(), "MemoryDirectory: '/foo'");
+ });
+
+ test('Link', () {
+ expect(fs.link('/foo').toString(), "MemoryLink: '/foo'");
+ });
+ });
+ });
+
+ group('MemoryFileSystem windows style', () {
+ late MemoryFileSystem fs;
+
+ setUp(() {
+ fs = MemoryFileSystem(style: FileSystemStyle.windows);
+ });
+
+ runCommonTests(
+ () => fs,
+ root: () => fs.style.root,
+ );
+
+ group('toString', () {
+ test('File', () {
+ expect(fs.file('C:\\foo').toString(), "MemoryFile: 'C:\\foo'");
+ });
+
+ test('Directory', () {
+ expect(
+ fs.directory('C:\\foo').toString(), "MemoryDirectory: 'C:\\foo'");
+ });
+
+ test('Link', () {
+ expect(fs.link('C:\\foo').toString(), "MemoryLink: 'C:\\foo'");
+ });
+ });
+ });
+
+ test('MemoryFileSystem.test', () {
+ final fs = MemoryFileSystem.test(); // creates root directory
+ fs.file('/test1.txt').createSync(); // creates file
+ fs.file('/test2.txt').createSync(); // creates file
+ expect(fs.directory('/').statSync().modified, DateTime(2000, 1, 1, 0, 1));
+ expect(
+ fs.file('/test1.txt').statSync().modified, DateTime(2000, 1, 1, 0, 2));
+ expect(
+ fs.file('/test2.txt').statSync().modified, DateTime(2000, 1, 1, 0, 3));
+ fs.file('/test1.txt').createSync();
+ fs.file('/test2.txt').createSync();
+ expect(fs.file('/test1.txt').statSync().modified,
+ DateTime(2000, 1, 1, 0, 2)); // file already existed
+ expect(fs.file('/test2.txt').statSync().modified,
+ DateTime(2000, 1, 1, 0, 3)); // file already existed
+ fs.file('/test1.txt').writeAsStringSync('test'); // touches file
+ expect(
+ fs.file('/test1.txt').statSync().modified, DateTime(2000, 1, 1, 0, 4));
+ expect(fs.file('/test2.txt').statSync().modified,
+ DateTime(2000, 1, 1, 0, 3)); // didn't touch it
+ fs.file('/test1.txt').copySync(
+ '/test2.txt'); // creates file, then mutates file (so time changes twice)
+ expect(fs.file('/test1.txt').statSync().modified,
+ DateTime(2000, 1, 1, 0, 4)); // didn't touch it
+ expect(
+ fs.file('/test2.txt').statSync().modified, DateTime(2000, 1, 1, 0, 6));
+ });
+
+ test('MemoryFile.openSync returns a MemoryRandomAccessFile', () async {
+ final fs = MemoryFileSystem.test();
+ final io.File file = fs.file('/test1')..createSync();
+
+ var raf = file.openSync();
+ try {
+ expect(raf, isA<MemoryRandomAccessFile>());
+ } finally {
+ raf.closeSync();
+ }
+
+ raf = await file.open();
+ try {
+ expect(raf, isA<MemoryRandomAccessFile>());
+ } finally {
+ raf.closeSync();
+ }
+ });
+
+ test('MemoryFileSystem.systemTempDirectory test', () {
+ final fs = MemoryFileSystem.test();
+
+ final io.Directory fooA = fs.systemTempDirectory.createTempSync('foo');
+ final io.Directory fooB = fs.systemTempDirectory.createTempSync('foo');
+
+ expect(fooA.path, '/.tmp_rand0/foorand0');
+ expect(fooB.path, '/.tmp_rand0/foorand1');
+
+ final secondFs = MemoryFileSystem.test();
+
+ final io.Directory fooAA =
+ secondFs.systemTempDirectory.createTempSync('foo');
+ final io.Directory fooBB =
+ secondFs.systemTempDirectory.createTempSync('foo');
+
+ // Names are recycled with a new instance
+ expect(fooAA.path, '/.tmp_rand0/foorand0');
+ expect(fooBB.path, '/.tmp_rand0/foorand1');
+ });
+
+ test('Failed UTF8 decoding in MemoryFileSystem throws a FileSystemException',
+ () {
+ final fileSystem = MemoryFileSystem.test();
+ final file = fileSystem.file('foo')
+ ..writeAsBytesSync(<int>[0xFFFE]); // Invalid UTF8
+
+ expect(file.readAsStringSync, throwsA(isA<FileSystemException>()));
+ });
+
+ test('Creating a temporary directory actually creates the directory', () {
+ final fileSystem = MemoryFileSystem.test();
+ final tempDir = fileSystem.currentDirectory.createTempSync('foo');
+
+ expect(tempDir.existsSync(), true);
+ });
+
+ test(
+ 'addStream forwards error to returned future and file can still be '
+ 'closed', () async {
+ final file = MemoryFileSystem.test().file('foo').openWrite();
+ await expectLater(file.addStream(Stream.error('bar')), throwsA('bar'));
+ await file.close();
+ });
+
+ test(
+ 'addStream cancels on error and does not misbehave if the stream '
+ 'produces multiple errors and then closes', () async {
+ final file = MemoryFileSystem.test().file('foo').openWrite();
+ final controller = StreamController<List<int>>()
+ ..addError('bar')
+ ..addError('baz');
+ final close = controller.close();
+ await expectLater(file.addStream(controller.stream), throwsA('bar'));
+ await file.close();
+ await close;
+ });
+}
diff --git a/pkgs/file/test/utils.dart b/pkgs/file/test/utils.dart
new file mode 100644
index 0000000..797ec9d
--- /dev/null
+++ b/pkgs/file/test/utils.dart
@@ -0,0 +1,117 @@
+// Copyright (c) 2017, 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:meta/meta.dart';
+import 'package:test/test.dart';
+
+const Duration _oneSecond = Duration(seconds: 1);
+
+/// Returns a [DateTime] with an exact second-precision by removing the
+/// milliseconds and microseconds from the specified [time].
+///
+/// If [time] is not specified, it will default to the current time.
+DateTime floor([DateTime? time]) {
+ time ??= DateTime.now();
+ return time.subtract(Duration(
+ milliseconds: time.millisecond,
+ microseconds: time.microsecond,
+ ));
+}
+
+/// Returns a [DateTime] with an exact second precision, rounding up to the
+/// nearest second if necessary.
+///
+/// If [time] is not specified, it will default to the current time.
+DateTime ceil([DateTime? time]) {
+ time ??= DateTime.now();
+ var microseconds = (1000 * time.millisecond) + time.microsecond;
+ return (microseconds == 0)
+ ? time
+ // Add just enough milliseconds and microseconds to reach the next second.
+ : time.add(Duration(microseconds: 1000000 - microseconds));
+}
+
+/// Returns 1 second before the [floor] of the specified [DateTime].
+// TODO(jamesderlin): Remove this and use [floor], https://github.com/dart-lang/sdk/issues/42444
+DateTime downstairs([DateTime? time]) => floor(time).subtract(_oneSecond);
+
+/// Successfully matches against a [DateTime] that is the same moment or before
+/// the specified [time].
+Matcher isSameOrBefore(DateTime time) => _IsSameOrBefore(time);
+
+/// Successfully matches against a [DateTime] that is the same moment or after
+/// the specified [time].
+Matcher isSameOrAfter(DateTime time) => _IsSameOrAfter(time);
+
+/// Successfully matches against a [DateTime] that is after the specified
+/// [time].
+Matcher isAfter(DateTime time) => _IsAfter(time);
+
+abstract class _CompareDateTime extends Matcher {
+ const _CompareDateTime(this._time, this._matcher);
+
+ final DateTime _time;
+ final Matcher _matcher;
+
+ @override
+ bool matches(dynamic item, Map<dynamic, dynamic> matchState) {
+ return item is DateTime &&
+ _matcher.matches(item.compareTo(_time), <dynamic, dynamic>{});
+ }
+
+ @protected
+ String get descriptionOperator;
+
+ @override
+ Description describe(Description description) =>
+ description.add('a DateTime $descriptionOperator $_time');
+
+ @protected
+ String get mismatchAdjective;
+
+ @override
+ Description describeMismatch(
+ dynamic item,
+ Description description,
+ Map<dynamic, dynamic> matchState,
+ bool verbose,
+ ) {
+ if (item is DateTime) {
+ var diff = item.difference(_time).abs();
+ return description.add('is $mismatchAdjective $_time by $diff');
+ } else {
+ return description.add('is not a DateTime');
+ }
+ }
+}
+
+class _IsSameOrBefore extends _CompareDateTime {
+ const _IsSameOrBefore(DateTime time) : super(time, isNonPositive);
+
+ @override
+ String get descriptionOperator => '<=';
+
+ @override
+ String get mismatchAdjective => 'after';
+}
+
+class _IsSameOrAfter extends _CompareDateTime {
+ const _IsSameOrAfter(DateTime time) : super(time, isNonNegative);
+
+ @override
+ String get descriptionOperator => '>=';
+
+ @override
+ String get mismatchAdjective => 'before';
+}
+
+class _IsAfter extends _CompareDateTime {
+ const _IsAfter(DateTime time) : super(time, isPositive);
+
+ @override
+ String get descriptionOperator => '>';
+
+ @override
+ String get mismatchAdjective => 'before';
+}
diff --git a/pkgs/file/test/utils_test.dart b/pkgs/file/test/utils_test.dart
new file mode 100644
index 0000000..23788e9
--- /dev/null
+++ b/pkgs/file/test/utils_test.dart
@@ -0,0 +1,43 @@
+// Copyright (c) 2017, 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 main() {
+ test('floorAndCeilProduceExactSecondDateTime', () {
+ var time = DateTime.fromMicrosecondsSinceEpoch(1001);
+ var lower = floor(time);
+ var upper = ceil(time);
+ expect(lower.millisecond, 0);
+ expect(upper.millisecond, 0);
+ expect(lower.microsecond, 0);
+ expect(upper.microsecond, 0);
+ });
+
+ test('floorAndCeilWorkWithNow', () {
+ var time = DateTime.now();
+ var lower = time.difference(floor(time)).inMicroseconds;
+ var upper = ceil(time).difference(time).inMicroseconds;
+ expect(lower, lessThan(1000000));
+ expect(upper, lessThanOrEqualTo(1000000));
+ });
+
+ test('floorAndCeilWorkWithExactSecondDateTime', () {
+ var time = DateTime.parse('1999-12-31 23:59:59');
+ var lower = floor(time);
+ var upper = ceil(time);
+ expect(lower, time);
+ expect(upper, time);
+ });
+
+ test('floorAndCeilWorkWithInexactSecondDateTime', () {
+ var time = DateTime.parse('1999-12-31 23:59:59.500');
+ var lower = floor(time);
+ var upper = ceil(time);
+ var difference = upper.difference(lower);
+ expect(difference.inMicroseconds, 1000000);
+ });
+}
diff --git a/pkgs/file_testing/.gitignore b/pkgs/file_testing/.gitignore
new file mode 100644
index 0000000..ddfdca1
--- /dev/null
+++ b/pkgs/file_testing/.gitignore
@@ -0,0 +1,5 @@
+.dart_tool/
+.packages
+.pub/
+build/
+pubspec.lock
diff --git a/pkgs/file_testing/CHANGELOG.md b/pkgs/file_testing/CHANGELOG.md
new file mode 100644
index 0000000..17039ee
--- /dev/null
+++ b/pkgs/file_testing/CHANGELOG.md
@@ -0,0 +1,49 @@
+## 3.1.0-wip
+
+* Changed the type of several matchers to `TypeMatcher` which allows cascading
+ their usage with `.having` and similar.
+
+## 3.0.2
+
+* Require Dart 3.1.
+
+## 3.0.1
+
+* Update the pubspec repository field to reflect the new package repository.
+* Require Dart 3.0.
+
+## 3.0.0
+
+* Migrate to null safety.
+
+## 2.2.0
+
+* Change dependency on `package:test_api` back to `package:test`.
+
+## 2.1.0
+
+* Changed dependency on `package:test` to `package:test_api`
+* Bumped Dart SDK constraint to match `package:test_api`'s requirements
+* Updated style to match latest lint rules from Flutter repo.
+
+## 2.0.3
+
+* Relaxed constraints on `package:test`
+
+## 2.0.2
+
+* Bumped dependency on `package:test` to version 1.0
+
+## 2.0.1
+
+* Bumped Dart SDK constraint to allow for Dart 2 stable
+
+## 2.0.0
+
+* Removed `record_replay_matchers.dart` from API
+
+## 1.0.0
+
+* Moved `package:file/testing.dart` library into a dedicated package so that
+ libraries don't need to take on a transitive dependency on `package:test`
+ in order to use `package:file`.
diff --git a/pkgs/file_testing/LICENSE b/pkgs/file_testing/LICENSE
new file mode 100644
index 0000000..076334f
--- /dev/null
+++ b/pkgs/file_testing/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2017, the Dart project authors. All rights reserved.
+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 Inc. 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/file_testing/README.md b/pkgs/file_testing/README.md
new file mode 100644
index 0000000..7dda57e
--- /dev/null
+++ b/pkgs/file_testing/README.md
@@ -0,0 +1,39 @@
+[](https://pub.dev/packages/file_testing)
+[](https://pub.dev/packages/file_testing/publisher)
+
+Testing utilities intended to work with `package:file`.
+
+## Features
+
+This package provides a series of matchers to be used in tests that work with file
+system types.
+
+## Usage
+
+```dart
+import 'package:file/file.dart';
+import 'package:file/memory.dart';
+import 'package:file_testing/file_testing.dart';
+import 'package:test/test.dart';
+
+void main() {
+ MemoryFileSystem fs;
+
+ setUp(() {
+ fs = MemoryFileSystem();
+ fs.file('/foo').createSync();
+ });
+
+ test('some test', () {
+ expectFileSystemException(
+ ErrorCodes.ENOENT,
+ () {
+ fs.directory('').resolveSymbolicLinksSync();
+ },
+ );
+ expect(fs.file('/path/to/file'), isFile);
+ expect(fs.file('/path/to/directory'), isDirectory);
+ expect(fs.file('/foo'), exists);
+ });
+}
+```
diff --git a/pkgs/file_testing/analysis_options.yaml b/pkgs/file_testing/analysis_options.yaml
new file mode 100644
index 0000000..d978f81
--- /dev/null
+++ b/pkgs/file_testing/analysis_options.yaml
@@ -0,0 +1 @@
+include: package:dart_flutter_team_lints/analysis_options.yaml
diff --git a/pkgs/file_testing/lib/file_testing.dart b/pkgs/file_testing/lib/file_testing.dart
new file mode 100644
index 0000000..2ac2d0a
--- /dev/null
+++ b/pkgs/file_testing/lib/file_testing.dart
@@ -0,0 +1,8 @@
+// Copyright (c) 2017, 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.
+
+/// Matchers (from `package:test`) for use in tests that deal with file systems.
+library;
+
+export 'src/testing/core_matchers.dart';
diff --git a/pkgs/file_testing/lib/src/testing/core_matchers.dart b/pkgs/file_testing/lib/src/testing/core_matchers.dart
new file mode 100644
index 0000000..801209e
--- /dev/null
+++ b/pkgs/file_testing/lib/src/testing/core_matchers.dart
@@ -0,0 +1,80 @@
+// Copyright (c) 2017, 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: comment_references
+
+import 'dart:io';
+
+import 'package:test/test.dart';
+
+import 'internal.dart';
+
+/// Matcher that successfully matches against any instance of [Directory].
+const isDirectory = TypeMatcher<Directory>();
+
+/// Matcher that successfully matches against any instance of [File].
+const isFile = TypeMatcher<File>();
+
+/// Matcher that successfully matches against any instance of [Link].
+const isLink = TypeMatcher<Link>();
+
+/// Matcher that successfully matches against any instance of
+/// [FileSystemEntity].
+const isFileSystemEntity = TypeMatcher<FileSystemEntity>();
+
+/// Matcher that successfully matches against any instance of [FileStat].
+const isFileStat = TypeMatcher<FileStat>();
+
+/// Returns a [Matcher] that matches [path] against an entity's path.
+///
+/// [path] may be a String, a predicate function, or a [Matcher]. If it is
+/// a String, it will be wrapped in an equality matcher.
+TypeMatcher<FileSystemEntity> hasPath(dynamic path) =>
+ isFileSystemEntity.having((e) => e.path, 'path', path);
+
+/// Returns a [Matcher] that successfully matches against an instance of
+/// [FileSystemException].
+///
+/// If [osErrorCode] is specified, matches will be limited to exceptions whose
+/// `osError.errorCode` also match the specified matcher.
+///
+/// [osErrorCode] may be an `int`, a predicate function, or a [Matcher]. If it
+/// is an `int`, it will be wrapped in an equality matcher.
+Matcher isFileSystemException([dynamic osErrorCode]) =>
+ const TypeMatcher<FileSystemException>().having((e) => e.osError?.errorCode,
+ 'osError.errorCode', _fileExceptionWrapMatcher(osErrorCode));
+
+/// Returns a matcher that successfully matches against a future or function
+/// that throws a [FileSystemException].
+///
+/// If [osErrorCode] is specified, matches will be limited to exceptions whose
+/// `osError.errorCode` also match the specified matcher.
+///
+/// [osErrorCode] may be an `int`, a predicate function, or a [Matcher]. If it
+/// is an `int`, it will be wrapped in an equality matcher.
+Matcher throwsFileSystemException([dynamic osErrorCode]) =>
+ throwsA(isFileSystemException(osErrorCode));
+
+/// Expects the specified [callback] to throw a [FileSystemException] with the
+/// specified [osErrorCode] (matched against the exception's
+/// `osError.errorCode`).
+///
+/// [osErrorCode] may be an `int`, a predicate function, or a [Matcher]. If it
+/// is an `int`, it will be wrapped in an equality matcher.
+///
+/// See also:
+/// - [ErrorCodes]
+void expectFileSystemException(dynamic osErrorCode, void Function() callback) {
+ expect(callback, throwsFileSystemException(osErrorCode));
+}
+
+/// Matcher that successfully matches against a [FileSystemEntity] that
+/// exists ([FileSystemEntity.existsSync] returns true).
+final TypeMatcher<FileSystemEntity> exists =
+ isFileSystemEntity.having((e) => e.existsSync(), 'existsSync', true);
+
+Matcher? _fileExceptionWrapMatcher(dynamic osErrorCode) =>
+ (osErrorCode == null || ignoreOsErrorCodes)
+ ? anything
+ : wrapMatcher(osErrorCode);
diff --git a/pkgs/file_testing/lib/src/testing/internal.dart b/pkgs/file_testing/lib/src/testing/internal.dart
new file mode 100644
index 0000000..8f53a65
--- /dev/null
+++ b/pkgs/file_testing/lib/src/testing/internal.dart
@@ -0,0 +1,6 @@
+// Copyright (c) 2017, 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.
+
+/// True if we should ignore OS error codes in our matchers.
+bool ignoreOsErrorCodes = false;
diff --git a/pkgs/file_testing/pubspec.yaml b/pkgs/file_testing/pubspec.yaml
new file mode 100644
index 0000000..895826a
--- /dev/null
+++ b/pkgs/file_testing/pubspec.yaml
@@ -0,0 +1,14 @@
+name: file_testing
+version: 3.1.0-wip
+description: Testing utilities for package:file.
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/file_testing
+issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Afile_testing
+
+environment:
+ sdk: ^3.1.0
+
+dependencies:
+ test: ^1.23.1
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
diff --git a/pkgs/graphs/.gitignore b/pkgs/graphs/.gitignore
new file mode 100644
index 0000000..ddfdca1
--- /dev/null
+++ b/pkgs/graphs/.gitignore
@@ -0,0 +1,5 @@
+.dart_tool/
+.packages
+.pub/
+build/
+pubspec.lock
diff --git a/pkgs/graphs/CHANGELOG.md b/pkgs/graphs/CHANGELOG.md
new file mode 100644
index 0000000..7213b5a
--- /dev/null
+++ b/pkgs/graphs/CHANGELOG.md
@@ -0,0 +1,77 @@
+## 2.3.3-wip
+
+- Add an example usage to the README.
+
+## 2.3.2
+
+- Require Dart 3.4
+- Update to the latest version of `package:dart_flutter_team_lints`.
+
+## 2.3.1
+
+- Update package repository after move.
+
+## 2.3.0
+
+- Add a `transitiveClosure` function.
+- Make `stronglyConnectedComponents` and `topologicalSort` iterative rather than
+ recursive to avoid stack overflows on very large graphs.
+- Require Dart 2.18
+
+## 2.2.0
+
+- Add a `secondarySort` parameter to the `topologicalSort()` function which
+ applies an additional lexical sort where that doesn't break the topological
+ sort.
+
+## 2.1.0
+
+- Add a `topologicalSort()` function.
+
+## 2.0.0
+
+- **Breaking**: `crawlAsync` will no longer ignore a node from the graph if the
+ `readNode` callback returns null.
+
+## 1.0.0
+
+- Migrate to null safety.
+- **Breaking**: Paths from `shortestPath[s]` are now returned as iterables to
+ reduce memory consumption of the algorithm to O(n).
+
+## 0.2.0
+
+- **BREAKING** `shortestPath`, `shortestPaths` and `stronglyConnectedComponents`
+ now have one generic parameter and have replaced the `key` parameter with
+ optional params: `{bool equals(T key1, T key2), int hashCode(T key)}`.
+ This follows the pattern used in `dart:collection` classes `HashMap` and
+ `LinkedHashMap`. It improves the usability and performance of the case where
+ the source values are directly usable in a hash data structure.
+
+## 0.1.3+1
+
+- Fixed a bug with non-identity `key` in `shortestPath` and `shortestPaths`.
+
+## 0.1.3
+
+- Added `shortestPath` and `shortestPaths` functions.
+- Use `HashMap` and `HashSet` from `dart:collection` for
+ `stronglyConnectedComponents`. Improves runtime performance.
+
+## 0.1.2+1
+
+- Allow using non-dev Dart 2 SDK.
+
+## 0.1.2
+
+- `crawlAsync` surfaces exceptions while crawling through the result stream
+ rather than as uncaught asynchronous errors.
+
+## 0.1.1
+
+- `crawlAsync` will now ignore nodes that are resolved to `null`.
+
+## 0.1.0
+
+- Initial release with an implementation of `stronglyConnectedComponents` and
+ `crawlAsync`.
diff --git a/pkgs/graphs/LICENSE b/pkgs/graphs/LICENSE
new file mode 100644
index 0000000..03af64a
--- /dev/null
+++ b/pkgs/graphs/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2017, 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.
diff --git a/pkgs/graphs/README.md b/pkgs/graphs/README.md
new file mode 100644
index 0000000..09512b8
--- /dev/null
+++ b/pkgs/graphs/README.md
@@ -0,0 +1,57 @@
+[](https://github.com/dart-lang/tools/actions/workflows/graphs.yml)
+[](https://pub.dev/packages/graphs)
+[](https://pub.dev/packages/graphs/publisher)
+
+Graph algorithms that do not specify a particular approach for representing a
+Graph.
+
+Each algorithm is a top level function which takes callback arguments that
+provide the mechanism for traversing the graph. For example, two common
+approaches for representing a graph:
+
+```dart
+class AdjacencyListGraph<T> {
+ Map<T, List<T>> nodes;
+ // ...
+}
+```
+
+```dart
+class TreeGraph<T> {
+ Node<T> root;
+ // ...
+}
+class Node<T> {
+ List<Node<T>> edges;
+ T value;
+}
+```
+
+Any representation can be adapted to the callback arguments.
+
+- Algorithms which need to traverse the graph take an `edges` callback which
+ provides the immediate neighbors of a given node.
+- Algorithms which need to associate unique data with each node in the graph
+ allow passing `equals` and/or `hashCode` callbacks if the unique data type
+ does not correctly or efficiently implement `operator==` or `get hashCode`.
+
+
+Algorithms that support graphs which are resolved asynchronously will have
+similar callbacks which return `FutureOr`.
+
+```dart
+import 'package:graphs/graphs.dart';
+
+void sendMessage() {
+ final network = AdjacencyListGraph();
+ // ...
+ final route = shortestPath(
+ sender, receiver, (node) => network.nodes[node] ?? const []);
+}
+
+void resolveBuildOrder() {
+ final dependencies = TreeGraph();
+ // ...
+ final buildOrder = topologicalSort([dependencies.root], (node) => node.edges);
+}
+```
diff --git a/pkgs/graphs/analysis_options.yaml b/pkgs/graphs/analysis_options.yaml
new file mode 100644
index 0000000..fcbc888
--- /dev/null
+++ b/pkgs/graphs/analysis_options.yaml
@@ -0,0 +1,35 @@
+# https://dart.dev/guides/language/analysis-options
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-raw-types: true
+ errors:
+ sort_pub_dependencies: ignore
+ # https://github.com/dart-lang/sdk/issues/56465
+ unintended_html_in_doc_comment: ignore
+
+linter:
+ rules:
+ - avoid_bool_literals_in_conditional_expressions
+ - avoid_classes_with_only_static_members
+ - avoid_private_typedef_functions
+ - avoid_redundant_argument_values
+ - avoid_returning_this
+ - avoid_unused_constructor_parameters
+ - avoid_void_async
+ - cancel_subscriptions
+ - join_return_with_assignment
+ - literal_only_boolean_expressions
+ - missing_whitespace_between_adjacent_strings
+ - no_adjacent_strings_in_list
+ - no_runtimeType_toString
+ - prefer_const_declarations
+ - prefer_expression_function_bodies
+ - prefer_final_locals
+ - require_trailing_commas
+ - unnecessary_await_in_return
+ - unnecessary_raw_strings
+ - use_if_null_to_convert_nulls_to_bools
+ - use_raw_strings
+ - use_string_buffers
diff --git a/pkgs/graphs/benchmark/connected_components_benchmark.dart b/pkgs/graphs/benchmark/connected_components_benchmark.dart
new file mode 100644
index 0000000..1f71879
--- /dev/null
+++ b/pkgs/graphs/benchmark/connected_components_benchmark.dart
@@ -0,0 +1,50 @@
+// 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.
+
+import 'dart:collection';
+import 'dart:math' show Random;
+
+import 'package:graphs/graphs.dart';
+
+void main() {
+ final rnd = Random(0);
+ const size = 2000;
+ final graph = HashMap<int, List<int>>();
+
+ for (var i = 0; i < size * 3; i++) {
+ final toList = graph.putIfAbsent(rnd.nextInt(size), () => <int>[]);
+
+ final toValue = rnd.nextInt(size);
+ if (!toList.contains(toValue)) {
+ toList.add(toValue);
+ }
+ }
+
+ var maxCount = 0;
+ var maxIteration = 0;
+
+ const duration = Duration(milliseconds: 100);
+
+ for (var i = 1;; i++) {
+ var count = 0;
+ final watch = Stopwatch()..start();
+ while (watch.elapsed < duration) {
+ count++;
+ final length =
+ stronglyConnectedComponents(graph.keys, (e) => graph[e] ?? <Never>[])
+ .length;
+ assert(length == 244, '$length');
+ }
+
+ if (count > maxCount) {
+ maxCount = count;
+ maxIteration = i;
+ }
+
+ if (maxIteration == i || (i - maxIteration) % 20 == 0) {
+ print('max iterations in ${duration.inMilliseconds}ms: $maxCount\t'
+ 'after $maxIteration of $i iterations');
+ }
+ }
+}
diff --git a/pkgs/graphs/benchmark/shortest_path_benchmark.dart b/pkgs/graphs/benchmark/shortest_path_benchmark.dart
new file mode 100644
index 0000000..67e7367
--- /dev/null
+++ b/pkgs/graphs/benchmark/shortest_path_benchmark.dart
@@ -0,0 +1,54 @@
+// 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.
+
+import 'dart:collection';
+import 'dart:math' show Random;
+
+import 'package:graphs/graphs.dart';
+
+void main() {
+ final rnd = Random(1);
+ const size = 1000;
+ final graph = HashMap<int, List<int>>();
+
+ for (var i = 0; i < size * 5; i++) {
+ final toList = graph.putIfAbsent(rnd.nextInt(size), () => <int>[]);
+
+ final toValue = rnd.nextInt(size);
+ if (!toList.contains(toValue)) {
+ toList.add(toValue);
+ }
+ }
+
+ int? minTicks;
+ var maxIteration = 0;
+
+ final testOutput =
+ shortestPath(0, size - 1, (e) => graph[e] ?? <Never>[]).toString();
+ print(testOutput);
+ assert(testOutput == '(258, 252, 819, 999)', testOutput);
+
+ final watch = Stopwatch();
+ for (var i = 1;; i++) {
+ watch
+ ..reset()
+ ..start();
+ final result = shortestPath(0, size - 1, (e) => graph[e] ?? <Never>[])!;
+ final length = result.length;
+ final first = result.first;
+ watch.stop();
+ assert(length == 4, '$length');
+ assert(first == 258, '$first');
+
+ if (minTicks == null || watch.elapsedTicks < minTicks) {
+ minTicks = watch.elapsedTicks;
+ maxIteration = i;
+ }
+
+ if (maxIteration == i || (i - maxIteration) % 100000 == 0) {
+ print('min ticks for one run: $minTicks\t'
+ 'after $maxIteration of $i iterations');
+ }
+ }
+}
diff --git a/pkgs/graphs/benchmark/shortest_path_worst_case_benchmark.dart b/pkgs/graphs/benchmark/shortest_path_worst_case_benchmark.dart
new file mode 100644
index 0000000..d3117ac
--- /dev/null
+++ b/pkgs/graphs/benchmark/shortest_path_worst_case_benchmark.dart
@@ -0,0 +1,58 @@
+// Copyright (c) 2020, 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:collection';
+
+import 'package:graphs/graphs.dart';
+
+void main() {
+ const size = 1000;
+ final graph = HashMap<int, List<int>>();
+
+ // We create a graph where every subsequent node has an edge to every other
+ // node before it as well as the next node. This triggers worst case behavior
+ // in many algorithms as it requires visiting all nodes and edges before
+ // finding a solution, and there are a maximum number of edges.
+ for (var i = 0; i < size; i++) {
+ final toList = graph.putIfAbsent(i, () => <int>[]);
+ for (var t = 0; t < i + 2 && i < size; t++) {
+ if (i == t) continue;
+ toList.add(t);
+ }
+ }
+
+ int? minTicks;
+ var maxIteration = 0;
+
+ final testOutput =
+ shortestPath(0, size - 1, (e) => graph[e] ?? <Never>[]).toString();
+ print(testOutput);
+ assert(
+ testOutput == Iterable.generate(size - 1, (i) => i + 1).toString(),
+ testOutput,
+ );
+
+ final watch = Stopwatch();
+ for (var i = 1;; i++) {
+ watch
+ ..reset()
+ ..start();
+ final result = shortestPath(0, size - 1, (e) => graph[e] ?? <Never>[])!;
+ final length = result.length;
+ final first = result.first;
+ watch.stop();
+ assert(length == 999, '$length');
+ assert(first == 1, '$first');
+
+ if (minTicks == null || watch.elapsedTicks < minTicks) {
+ minTicks = watch.elapsedTicks;
+ maxIteration = i;
+ }
+
+ if (maxIteration == i || (i - maxIteration) % 100000 == 0) {
+ print('min ticks for one run: $minTicks\t'
+ 'after $maxIteration of $i iterations');
+ }
+ }
+}
diff --git a/pkgs/graphs/example/crawl_async_example.dart b/pkgs/graphs/example/crawl_async_example.dart
new file mode 100644
index 0000000..bd305c9
--- /dev/null
+++ b/pkgs/graphs/example/crawl_async_example.dart
@@ -0,0 +1,84 @@
+// Copyright (c) 2017, 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:isolate';
+
+import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
+import 'package:analyzer/dart/analysis/results.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:graphs/graphs.dart';
+import 'package:path/path.dart' as p;
+import 'package:pool/pool.dart';
+
+/// Print a transitive set of imported URIs where libraries are read
+/// asynchronously.
+Future<void> main() async {
+ // Limits calls to [findImports].
+ final pool = Pool(10);
+ final allImports = await crawlAsync<Uri, Source>(
+ [Uri.parse('package:graphs/graphs.dart')],
+ read,
+ (from, source) => pool.withResource(() => findImports(from, source)),
+ ).toList();
+ print(allImports.map((s) => s.uri).toList());
+}
+
+AnalysisContextCollection? _analysisContextCollection;
+
+Future<AnalysisContextCollection> get analysisContextCollection async {
+ var collection = _analysisContextCollection;
+ if (collection == null) {
+ final libUri = Uri.parse('package:graphs/');
+ final libPath = await pathForUri(libUri);
+ final packagePath = p.dirname(libPath);
+
+ collection = _analysisContextCollection = AnalysisContextCollection(
+ includedPaths: [packagePath],
+ );
+ }
+
+ return collection;
+}
+
+Future<Iterable<Uri>> findImports(Uri from, Source source) async =>
+ source.unit.directives
+ .whereType<UriBasedDirective>()
+ .map((d) => d.uri.stringValue!)
+ .where((uri) => !uri.startsWith('dart:'))
+ .map((import) => resolveImport(import, from));
+
+Future<CompilationUnit> parseUri(Uri uri) async {
+ final path = await pathForUri(uri);
+ final analysisContext = (await analysisContextCollection).contexts.single;
+ final analysisSession = analysisContext.currentSession;
+ final parseResult = analysisSession.getParsedUnit(path);
+ return (parseResult as ParsedUnitResult).unit;
+}
+
+Future<String> pathForUri(Uri uri) async {
+ final fileUri = await Isolate.resolvePackageUri(uri);
+ if (fileUri == null || !fileUri.isScheme('file')) {
+ throw StateError('Expected to resolve $uri to a file URI, got $fileUri');
+ }
+ return p.fromUri(fileUri);
+}
+
+Future<Source> read(Uri uri) async => Source(uri, await parseUri(uri));
+
+Uri resolveImport(String import, Uri from) {
+ if (import.startsWith('package:')) return Uri.parse(import);
+ assert(from.scheme == 'package');
+ final package = from.pathSegments.first;
+ final fromPath = p.joinAll(from.pathSegments.skip(1));
+ final path = p.normalize(p.join(p.dirname(fromPath), import));
+ return Uri.parse('package:${p.join(package, path)}');
+}
+
+class Source {
+ final Uri uri;
+ final CompilationUnit unit;
+
+ Source(this.uri, this.unit);
+}
diff --git a/pkgs/graphs/example/example.dart b/pkgs/graphs/example/example.dart
new file mode 100644
index 0000000..c9cf6dd
--- /dev/null
+++ b/pkgs/graphs/example/example.dart
@@ -0,0 +1,49 @@
+// Copyright (c) 2017, 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:graphs/graphs.dart';
+
+/// A representation of a directed graph.
+///
+/// Data is stored on the [Node] class.
+class Graph {
+ final Map<Node, List<Node>> nodes;
+
+ Graph(this.nodes);
+}
+
+class Node {
+ final String id;
+ final int data;
+
+ Node(this.id, this.data);
+
+ @override
+ bool operator ==(Object other) => other is Node && other.id == id;
+
+ @override
+ int get hashCode => id.hashCode;
+
+ @override
+ String toString() => '<$id -> $data>';
+}
+
+void main() {
+ final nodeA = Node('A', 1);
+ final nodeB = Node('B', 2);
+ final nodeC = Node('C', 3);
+ final nodeD = Node('D', 4);
+ final graph = Graph({
+ nodeA: [nodeB, nodeC],
+ nodeB: [nodeC, nodeD],
+ nodeC: [nodeB, nodeD],
+ });
+
+ final components = stronglyConnectedComponents<Node>(
+ graph.nodes.keys,
+ (node) => graph.nodes[node] ?? [],
+ );
+
+ print(components);
+}
diff --git a/pkgs/graphs/lib/graphs.dart b/pkgs/graphs/lib/graphs.dart
new file mode 100644
index 0000000..f10b2e4
--- /dev/null
+++ b/pkgs/graphs/lib/graphs.dart
@@ -0,0 +1,11 @@
+// Copyright (c) 2017, 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/crawl_async.dart' show crawlAsync;
+export 'src/cycle_exception.dart' show CycleException;
+export 'src/shortest_path.dart' show shortestPath, shortestPaths;
+export 'src/strongly_connected_components.dart'
+ show stronglyConnectedComponents;
+export 'src/topological_sort.dart' show topologicalSort;
+export 'src/transitive_closure.dart' show transitiveClosure;
diff --git a/pkgs/graphs/lib/src/crawl_async.dart b/pkgs/graphs/lib/src/crawl_async.dart
new file mode 100644
index 0000000..68c0a5b
--- /dev/null
+++ b/pkgs/graphs/lib/src/crawl_async.dart
@@ -0,0 +1,85 @@
+// Copyright (c) 2017, 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:collection';
+
+final _empty = Future<void>.value();
+
+/// Finds and returns every node in a graph who's nodes and edges are
+/// asynchronously resolved.
+///
+/// Cycles are allowed. If this is an undirected graph the [edges] function
+/// may be symmetric. In this case the [roots] may be any node in each connected
+/// graph.
+///
+/// [V] is the type of values in the graph nodes. [K] must be a type suitable
+/// for using as a Map or Set key. [edges] should return the next reachable
+/// nodes.
+///
+/// There are no ordering guarantees. This is useful for ensuring some work is
+/// performed at every node in an asynchronous graph, but does not give
+/// guarantees that the work is done in topological order.
+///
+/// If either [readNode] or [edges] throws the error will be forwarded
+/// through the result stream and no further nodes will be crawled, though some
+/// work may have already been started.
+///
+/// Crawling is eager, so calls to [edges] may overlap with other calls that
+/// have not completed. If the [edges] callback needs to be limited or throttled
+/// that must be done by wrapping it before calling [crawlAsync].
+Stream<V> crawlAsync<K extends Object, V>(
+ Iterable<K> roots,
+ FutureOr<V> Function(K) readNode,
+ FutureOr<Iterable<K>> Function(K, V) edges,
+) {
+ final crawl = _CrawlAsync(roots, readNode, edges)..run();
+ return crawl.result.stream;
+}
+
+class _CrawlAsync<K, V> {
+ final result = StreamController<V>();
+
+ final FutureOr<V> Function(K) readNode;
+ final FutureOr<Iterable<K>> Function(K, V) edges;
+ final Iterable<K> roots;
+
+ final _seen = HashSet<K>();
+
+ _CrawlAsync(this.roots, this.readNode, this.edges);
+
+ /// Add all nodes in the graph to [result] and return a Future which fires
+ /// after all nodes have been seen.
+ Future<void> run() async {
+ try {
+ await Future.wait(roots.map(_visit), eagerError: true);
+ await result.close();
+ } catch (e, st) {
+ result.addError(e, st);
+ await result.close();
+ }
+ }
+
+ /// Resolve the node at [key] and output it, then start crawling all of it's
+ /// edges.
+ Future<void> _crawlFrom(K key) async {
+ final value = await readNode(key);
+ if (result.isClosed) return;
+ result.add(value);
+ final next = await edges(key, value);
+ await Future.wait(next.map(_visit), eagerError: true);
+ }
+
+ /// Synchronously record that [key] is being handled then start work on the
+ /// node for [key].
+ ///
+ /// The returned Future will complete only after the work for [key] and all
+ /// transitively reachable nodes has either been finished, or will be finished
+ /// by some other Future in [_seen].
+ Future<void> _visit(K key) {
+ if (_seen.contains(key)) return _empty;
+ _seen.add(key);
+ return _crawlFrom(key);
+ }
+}
diff --git a/pkgs/graphs/lib/src/cycle_exception.dart b/pkgs/graphs/lib/src/cycle_exception.dart
new file mode 100644
index 0000000..eb9b433
--- /dev/null
+++ b/pkgs/graphs/lib/src/cycle_exception.dart
@@ -0,0 +1,19 @@
+// Copyright (c) 2021, 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.
+
+/// An exception indicating that a cycle was detected in a graph that was
+/// expected to be acyclic.
+class CycleException<T> implements Exception {
+ /// The list of nodes comprising the cycle.
+ ///
+ /// Each node in this list has an edge to the next node. The final node has an
+ /// edge to the first node.
+ final List<T> cycle;
+
+ CycleException(Iterable<T> cycle) : cycle = List.unmodifiable(cycle);
+
+ @override
+ String toString() => 'A cycle was detected in a graph that must be acyclic:\n'
+ '${cycle.map((node) => '* $node').join('\n')}';
+}
diff --git a/pkgs/graphs/lib/src/shortest_path.dart b/pkgs/graphs/lib/src/shortest_path.dart
new file mode 100644
index 0000000..28f7e51
--- /dev/null
+++ b/pkgs/graphs/lib/src/shortest_path.dart
@@ -0,0 +1,144 @@
+// 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.
+
+import 'dart:collection';
+
+/// Returns the shortest path from [start] to [target] given the directed
+/// edges of a graph provided by [edges].
+///
+/// If [start] `==` [target], an empty [List] is returned and [edges] is never
+/// called.
+///
+/// If [equals] is provided, it is used to compare nodes in the graph. If
+/// [equals] is omitted, the node's own [Object.==] is used instead.
+///
+/// Similarly, if [hashCode] is provided, it is used to produce a hash value
+/// for nodes to efficiently calculate the return value. If it is omitted, the
+/// key's own [Object.hashCode] is used.
+///
+/// If you supply one of [equals] or [hashCode], you should generally also to
+/// supply the other.
+Iterable<T>? shortestPath<T extends Object>(
+ T start,
+ T target,
+ Iterable<T> Function(T) edges, {
+ bool Function(T, T)? equals,
+ int Function(T)? hashCode,
+}) =>
+ _shortestPaths<T>(
+ start,
+ edges,
+ target: target,
+ equals: equals,
+ hashCode: hashCode,
+ )[target];
+
+/// Returns a [Map] of the shortest paths from [start] to all of the nodes in
+/// the directed graph defined by [edges].
+///
+/// All return values will contain the key [start] with an empty [List] value.
+///
+/// [start] and all values returned by [edges] must not be `null`.
+/// If asserts are enabled, an [AssertionError] is raised if these conditions
+/// are not met. If asserts are not enabled, violations result in undefined
+/// behavior.
+///
+/// If [equals] is provided, it is used to compare nodes in the graph. If
+/// [equals] is omitted, the node's own [Object.==] is used instead.
+///
+/// Similarly, if [hashCode] is provided, it is used to produce a hash value
+/// for nodes to efficiently calculate the return value. If it is omitted, the
+/// key's own [Object.hashCode] is used.
+///
+/// If you supply one of [equals] or [hashCode], you should generally also to
+/// supply the other.
+Map<T, Iterable<T>> shortestPaths<T extends Object>(
+ T start,
+ Iterable<T> Function(T) edges, {
+ bool Function(T, T)? equals,
+ int Function(T)? hashCode,
+}) =>
+ _shortestPaths<T>(
+ start,
+ edges,
+ equals: equals,
+ hashCode: hashCode,
+ );
+
+Map<T, Iterable<T>> _shortestPaths<T extends Object>(
+ T start,
+ Iterable<T> Function(T) edges, {
+ T? target,
+ bool Function(T, T)? equals,
+ int Function(T)? hashCode,
+}) {
+ final distances = HashMap<T, _Tail<T>>(equals: equals, hashCode: hashCode);
+ distances[start] = _Tail<T>();
+
+ final nonNullEquals = equals ??= _defaultEquals;
+ final isTarget =
+ target == null ? _neverTarget : (T node) => nonNullEquals(node, target);
+ if (isTarget(start)) {
+ return distances;
+ }
+
+ final toVisit = ListQueue<T>()..add(start);
+
+ while (toVisit.isNotEmpty) {
+ final current = toVisit.removeFirst();
+ final currentPath = distances[current]!;
+
+ for (var edge in edges(current)) {
+ final existingPath = distances[edge];
+
+ if (existingPath == null) {
+ distances[edge] = currentPath.append(edge);
+ if (isTarget(edge)) {
+ return distances;
+ }
+ toVisit.add(edge);
+ }
+ }
+ }
+
+ return distances;
+}
+
+bool _defaultEquals(Object a, Object b) => a == b;
+bool _neverTarget(Object _) => false;
+
+/// An immutable iterable that can efficiently return a copy with a value
+/// appended.
+///
+/// This implementation has an efficient [length] property.
+///
+/// Note that grabbing an [iterator] for the first time is O(n) in time and
+/// space because it copies all the values to a new list and uses that
+/// iterator in order to avoid stack overflows for large paths. This copy is
+/// cached for subsequent calls.
+class _Tail<T extends Object> extends Iterable<T> {
+ final T? tail;
+ final _Tail<T>? head;
+ @override
+ final int length;
+ _Tail()
+ : tail = null,
+ head = null,
+ length = 0;
+ _Tail._(this.tail, this.head, this.length);
+ _Tail<T> append(T value) => _Tail._(value, this, length + 1);
+
+ @override
+ Iterator<T> get iterator => _asIterable.iterator;
+
+ late final _asIterable = () {
+ _Tail<T>? next = this;
+ final reversed = List.generate(length, (_) {
+ final val = next!.tail;
+ next = next!.head;
+ return val as T;
+ });
+ return reversed.reversed;
+ }();
+}
diff --git a/pkgs/graphs/lib/src/strongly_connected_components.dart b/pkgs/graphs/lib/src/strongly_connected_components.dart
new file mode 100644
index 0000000..e8a775c
--- /dev/null
+++ b/pkgs/graphs/lib/src/strongly_connected_components.dart
@@ -0,0 +1,117 @@
+// Copyright (c) 2017, 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:collection';
+import 'dart:math' show min;
+
+/// Finds the strongly connected components of a directed graph using Tarjan's
+/// algorithm.
+///
+/// The result will be a valid reverse topological order ordering of the
+/// strongly connected components. Components further from a root will appear in
+/// the result before the components which they are connected to.
+///
+/// Nodes within a strongly connected component have no ordering guarantees,
+/// except that if the first value in [nodes] is a valid root, and is contained
+/// in a cycle, it will be the last element of that cycle.
+///
+/// [nodes] must contain at least a root of every tree in the graph if there are
+/// disjoint subgraphs but it may contain all nodes in the graph if the roots
+/// are not known.
+///
+/// If [equals] is provided, it is used to compare nodes in the graph. If
+/// [equals] is omitted, the node's own [Object.==] is used instead.
+///
+/// Similarly, if [hashCode] is provided, it is used to produce a hash value
+/// for nodes to efficiently calculate the return value. If it is omitted, the
+/// key's own [Object.hashCode] is used.
+///
+/// If you supply one of [equals] or [hashCode], you should generally also to
+/// supply the other.
+List<List<T>> stronglyConnectedComponents<T extends Object>(
+ Iterable<T> nodes,
+ Iterable<T> Function(T) edges, {
+ bool Function(T, T)? equals,
+ int Function(T)? hashCode,
+}) {
+ final result = <List<T>>[];
+ final lowLinks = HashMap<T, int>(equals: equals, hashCode: hashCode);
+ final indexes = HashMap<T, int>(equals: equals, hashCode: hashCode);
+ final onStack = HashSet<T>(equals: equals, hashCode: hashCode);
+
+ final nonNullEquals = equals ?? _defaultEquals;
+
+ var index = 0;
+ final lastVisited = Queue<T>();
+
+ final stack = [for (final node in nodes) _StackState(node)];
+ outer:
+ while (stack.isNotEmpty) {
+ final state = stack.removeLast();
+ final node = state.node;
+ var iterator = state.iterator;
+
+ int lowLink;
+ if (iterator == null) {
+ if (indexes.containsKey(node)) continue;
+ indexes[node] = index;
+ lowLink = lowLinks[node] = index;
+ index++;
+ iterator = edges(node).iterator;
+
+ // Nodes with no edges are always in their own component.
+ if (!iterator.moveNext()) {
+ result.add([node]);
+ continue;
+ }
+
+ lastVisited.addLast(node);
+ onStack.add(node);
+ } else {
+ lowLink = min(lowLinks[node]!, lowLinks[iterator.current]!);
+ }
+
+ do {
+ final next = iterator.current;
+ if (!indexes.containsKey(next)) {
+ stack.add(_StackState(node, iterator));
+ stack.add(_StackState(next));
+ continue outer;
+ } else if (onStack.contains(next)) {
+ lowLink = lowLinks[node] = min(lowLink, indexes[next]!);
+ }
+ } while (iterator.moveNext());
+
+ if (lowLink == indexes[node]) {
+ final component = <T>[];
+ T next;
+ do {
+ next = lastVisited.removeLast();
+ onStack.remove(next);
+ component.add(next);
+ } while (!nonNullEquals(next, node));
+ result.add(component);
+ }
+ }
+
+ return result;
+}
+
+/// The state of a pass on a single node in Tarjan's Algorithm.
+///
+/// This is used to perform the algorithm with an explicit stack rather than
+/// recursively, to avoid stack overflow errors for very large graphs.
+class _StackState<T> {
+ /// The node being inspected.
+ final T node;
+
+ /// The iterator traversing [node]'s edges.
+ ///
+ /// This is null if the node hasn't yet begun being traversed.
+ final Iterator<T>? iterator;
+
+ _StackState(this.node, [this.iterator]);
+}
+
+bool _defaultEquals(Object a, Object b) => a == b;
diff --git a/pkgs/graphs/lib/src/topological_sort.dart b/pkgs/graphs/lib/src/topological_sort.dart
new file mode 100644
index 0000000..948e696
--- /dev/null
+++ b/pkgs/graphs/lib/src/topological_sort.dart
@@ -0,0 +1,146 @@
+// Copyright (c) 2021, 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:collection';
+
+import 'package:collection/collection.dart' hide stronglyConnectedComponents;
+
+import 'cycle_exception.dart';
+
+/// Returns a topological sort of the nodes of the directed edges of a graph
+/// provided by [nodes] and [edges].
+///
+/// Each element of the returned iterable is guaranteed to appear after all
+/// nodes that have edges leading to that node. The result is not guaranteed to
+/// be unique, nor is it guaranteed to be stable across releases of this
+/// package; however, it will be stable for a given input within a given package
+/// version.
+///
+/// If [equals] is provided, it is used to compare nodes in the graph. If
+/// [equals] is omitted, the node's own [Object.==] is used instead.
+///
+/// Similarly, if [hashCode] is provided, it is used to produce a hash value
+/// for nodes to efficiently calculate the return value. If it is omitted, the
+/// key's own [Object.hashCode] is used.
+///
+/// If you supply one of [equals] or [hashCode], you should generally also to
+/// supply the other.
+///
+/// If you supply [secondarySort], the resulting list will be sorted by that
+/// comparison function as much as possible without violating the topological
+/// ordering. Note that even with a secondary sort, the result is _still_ not
+/// guaranteed to be unique or stable across releases of this package.
+///
+/// Note: this requires that [nodes] and each iterable returned by [edges]
+/// contain no duplicate entries.
+///
+/// Throws a [CycleException<T>] if the graph is cyclical.
+List<T> topologicalSort<T>(
+ Iterable<T> nodes,
+ Iterable<T> Function(T) edges, {
+ bool Function(T, T)? equals,
+ int Function(T)? hashCode,
+ Comparator<T>? secondarySort,
+}) {
+ if (secondarySort != null) {
+ return _topologicalSortWithSecondary(
+ [...nodes],
+ edges,
+ secondarySort,
+ equals,
+ hashCode,
+ );
+ }
+
+ // https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search
+ final result = QueueList<T>();
+ final permanentMark = HashSet<T>(equals: equals, hashCode: hashCode);
+ final temporaryMark = LinkedHashSet<T>(equals: equals, hashCode: hashCode);
+ final stack = [...nodes];
+ while (stack.isNotEmpty) {
+ final node = stack.removeLast();
+ if (permanentMark.contains(node)) continue;
+
+ // If we're visiting this node while it's already marked and not through a
+ // dependency, that must mean we've traversed all its dependencies and it's
+ // safe to add it to the result.
+ if (temporaryMark.contains(node)) {
+ temporaryMark.remove(node);
+ permanentMark.add(node);
+ result.addFirst(node);
+ } else {
+ temporaryMark.add(node);
+
+ // Revisit this node once we've visited all its children.
+ stack.add(node);
+ for (var child in edges(node)) {
+ if (temporaryMark.contains(child)) throw CycleException(temporaryMark);
+ stack.add(child);
+ }
+ }
+ }
+
+ return result;
+}
+
+/// An implementation of [topologicalSort] with a secondary comparison function.
+List<T> _topologicalSortWithSecondary<T>(
+ List<T> nodes,
+ Iterable<T> Function(T) edges,
+ Comparator<T> comparator,
+ bool Function(T, T)? equals,
+ int Function(T)? hashCode,
+) {
+ // https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm,
+ // modified to sort the nodes to traverse. Also documented in
+ // https://www.algotree.org/algorithms/tree_graph_traversal/lexical_topological_sort_c++/
+
+ // For each node, the number of incoming edges it has that we haven't yet
+ // traversed.
+ final incomingEdges = HashMap<T, int>(equals: equals, hashCode: hashCode);
+ for (var node in nodes) {
+ for (var child in edges(node)) {
+ incomingEdges[child] = (incomingEdges[child] ?? 0) + 1;
+ }
+ }
+
+ // A priority queue of nodes that have no remaining incoming edges.
+ final nodesToTraverse = PriorityQueue<T>(comparator);
+ for (var node in nodes) {
+ if (!incomingEdges.containsKey(node)) nodesToTraverse.add(node);
+ }
+
+ final result = <T>[];
+ while (nodesToTraverse.isNotEmpty) {
+ final node = nodesToTraverse.removeFirst();
+ result.add(node);
+ for (var child in edges(node)) {
+ var remainingEdges = incomingEdges[child]!;
+ remainingEdges--;
+ incomingEdges[child] = remainingEdges;
+ if (remainingEdges == 0) nodesToTraverse.add(child);
+ }
+ }
+
+ if (result.length < nodes.length) {
+ // This algorithm doesn't automatically produce a cycle list as a side
+ // effect of sorting, so to throw the appropriate [CycleException] we just
+ // call the normal [topologicalSort] with a view of this graph that only
+ // includes nodes that still have edges.
+ bool nodeIsInCycle(T node) {
+ final edges = incomingEdges[node];
+ return edges != null && edges > 0;
+ }
+
+ topologicalSort<T>(
+ nodes.where(nodeIsInCycle),
+ edges,
+ equals: equals,
+ hashCode: hashCode,
+ );
+ assert(false, 'topologicalSort() should throw if the graph has a cycle');
+ }
+
+ return result;
+}
diff --git a/pkgs/graphs/lib/src/transitive_closure.dart b/pkgs/graphs/lib/src/transitive_closure.dart
new file mode 100644
index 0000000..24791d6
--- /dev/null
+++ b/pkgs/graphs/lib/src/transitive_closure.dart
@@ -0,0 +1,143 @@
+// Copyright (c) 2023, 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:collection';
+
+import 'cycle_exception.dart';
+import 'strongly_connected_components.dart';
+import 'topological_sort.dart';
+
+/// Returns a transitive closure of a directed graph provided by [nodes] and
+/// [edges].
+///
+/// The result is a map from [nodes] to the sets of nodes that are transitively
+/// reachable through [edges]. No particular ordering is guaranteed.
+///
+/// If [equals] is provided, it is used to compare nodes in the graph. If
+/// [equals] is omitted, the node's own [Object.==] is used instead.
+///
+/// Similarly, if [hashCode] is provided, it is used to produce a hash value
+/// for nodes to efficiently calculate the return value. If it is omitted, the
+/// key's own [Object.hashCode] is used.
+///
+/// If you supply one of [equals] or [hashCode], you should generally also to
+/// supply the other.
+///
+/// Note: this requires that [nodes] and each iterable returned by [edges]
+/// contain no duplicate entries.
+///
+/// By default, this can handle either cyclic or acyclic graphs. If [acyclic] is
+/// true, this will run more efficiently but throw a [CycleException] if the
+/// graph is cyclical.
+Map<T, Set<T>> transitiveClosure<T extends Object>(
+ Iterable<T> nodes,
+ Iterable<T> Function(T) edges, {
+ bool Function(T, T)? equals,
+ int Function(T)? hashCode,
+ bool acyclic = false,
+}) {
+ if (!acyclic) {
+ return _cyclicTransitiveClosure(
+ nodes,
+ edges,
+ equals: equals,
+ hashCode: hashCode,
+ );
+ }
+
+ final topologicalOrder =
+ topologicalSort(nodes, edges, equals: equals, hashCode: hashCode);
+ final result = LinkedHashMap<T, Set<T>>(equals: equals, hashCode: hashCode);
+ for (final node in topologicalOrder.reversed) {
+ final closure = LinkedHashSet<T>(equals: equals, hashCode: hashCode);
+ for (var child in edges(node)) {
+ closure.add(child);
+ closure.addAll(result[child]!);
+ }
+
+ result[node] = closure;
+ }
+
+ return result;
+}
+
+/// Returns the transitive closure of a cyclic graph using [Purdom's algorithm].
+///
+/// [Purdom's algorithm]: https://algowiki-project.org/en/Purdom%27s_algorithm
+///
+/// This first computes the strongly connected components of the graph and finds
+/// the transitive closure of those before flattening it out into the transitive
+/// closure of the entire graph.
+Map<T, Set<T>> _cyclicTransitiveClosure<T extends Object>(
+ Iterable<T> nodes,
+ Iterable<T> Function(T) edges, {
+ bool Function(T, T)? equals,
+ int Function(T)? hashCode,
+}) {
+ final components = stronglyConnectedComponents<T>(
+ nodes,
+ edges,
+ equals: equals,
+ hashCode: hashCode,
+ );
+ final nodesToComponents =
+ HashMap<T, List<T>>(equals: equals, hashCode: hashCode);
+ for (final component in components) {
+ for (final node in component) {
+ nodesToComponents[node] = component;
+ }
+ }
+
+ // Because [stronglyConnectedComponents] returns the components in reverse
+ // topological order, we can avoid an additional topological sort here.
+ // Instead, we directly traverse the component list with the knowledge that
+ // once we reach a component, everything reachable from it has already been
+ // registered in [result].
+ final result = LinkedHashMap<T, Set<T>>(equals: equals, hashCode: hashCode);
+ for (final component in components) {
+ final closure = LinkedHashSet<T>(equals: equals, hashCode: hashCode);
+ if (_componentIncludesCycle(component, edges, equals)) {
+ closure.addAll(component);
+ }
+
+ // De-duplicate downstream components to avoid adding the same transitive
+ // children over and over.
+ final downstreamComponents = {
+ for (final node in component)
+ for (final child in edges(node)) nodesToComponents[child]!,
+ };
+ for (final childComponent in downstreamComponents) {
+ if (childComponent == component) continue;
+
+ // This if check is just for efficiency. If [childComponent] has multiple
+ // nodes, `result[childComponent.first]` will contain all the nodes in
+ // `childComponent` anyway since it's cyclical.
+ if (childComponent.length == 1) closure.addAll(childComponent);
+ closure.addAll(result[childComponent.first]!);
+ }
+
+ for (final node in component) {
+ result[node] = closure;
+ }
+ }
+ return result;
+}
+
+/// Returns whether the strongly-connected component [component] of a graph
+/// defined by [edges] includes a cycle.
+bool _componentIncludesCycle<T>(
+ List<T> component,
+ Iterable<T> Function(T) edges,
+ bool Function(T, T)? equals,
+) {
+ // A strongly-connected component with more than one node always contains a
+ // cycle, by definition.
+ if (component.length > 1) return true;
+
+ // A component with only a single node only contains a cycle if that node has
+ // an edge to itself.
+ final node = component.single;
+ return edges(node)
+ .any((edge) => equals == null ? edge == node : equals(edge, node));
+}
diff --git a/pkgs/graphs/pubspec.yaml b/pkgs/graphs/pubspec.yaml
new file mode 100644
index 0000000..e25ee85
--- /dev/null
+++ b/pkgs/graphs/pubspec.yaml
@@ -0,0 +1,20 @@
+name: graphs
+version: 2.3.3-wip
+description: Graph algorithms that operate on graphs in any representation.
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/graphs
+issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Agraphs
+
+environment:
+ sdk: ^3.4.0
+
+dependencies:
+ collection: ^1.15.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ test: ^1.21.6
+
+ # For examples
+ analyzer: '>=5.2.0 <8.0.0'
+ path: ^1.8.0
+ pool: ^1.5.0
diff --git a/pkgs/graphs/test/crawl_async_test.dart b/pkgs/graphs/test/crawl_async_test.dart
new file mode 100644
index 0000000..9e168e6
--- /dev/null
+++ b/pkgs/graphs/test/crawl_async_test.dart
@@ -0,0 +1,113 @@
+// Copyright (c) 2017, 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:graphs/graphs.dart';
+import 'package:test/test.dart';
+
+import 'utils/graph.dart';
+
+void main() {
+ group('asyncCrawl', () {
+ Future<List<String?>> crawl(
+ Map<String, List<String>?> g,
+ Iterable<String> roots,
+ ) {
+ final graph = AsyncGraph(g);
+ return crawlAsync(roots, graph.readNode, graph.edges).toList();
+ }
+
+ test('empty result for empty graph', () async {
+ final result = await crawl({}, []);
+ expect(result, isEmpty);
+ });
+
+ test('single item for a single node', () async {
+ final result = await crawl({'a': []}, ['a']);
+ expect(result, ['a']);
+ });
+
+ test('hits every node in a graph', () async {
+ final result = await crawl({
+ 'a': ['b', 'c'],
+ 'b': ['c'],
+ 'c': ['d'],
+ 'd': [],
+ }, [
+ 'a',
+ ]);
+ expect(result, hasLength(4));
+ expect(
+ result,
+ allOf(contains('a'), contains('b'), contains('c'), contains('d')),
+ );
+ });
+
+ test('handles cycles', () async {
+ final result = await crawl({
+ 'a': ['b'],
+ 'b': ['c'],
+ 'c': ['b'],
+ }, [
+ 'a',
+ ]);
+ expect(result, hasLength(3));
+ expect(result, allOf(contains('a'), contains('b'), contains('c')));
+ });
+
+ test('handles self cycles', () async {
+ final result = await crawl({
+ 'a': ['b'],
+ 'b': ['b'],
+ }, [
+ 'a',
+ ]);
+ expect(result, hasLength(2));
+ expect(result, allOf(contains('a'), contains('b')));
+ });
+
+ test('allows null edges', () async {
+ final result = await crawl({
+ 'a': ['b'],
+ 'b': null,
+ }, [
+ 'a',
+ ]);
+ expect(result, hasLength(2));
+ expect(result, allOf(contains('a'), contains('b')));
+ });
+
+ test('allows null nodes', () async {
+ final result = await crawl({
+ 'a': ['b'],
+ }, [
+ 'a',
+ ]);
+ expect(result, ['a', null]);
+ });
+
+ test('surfaces exceptions for crawling edges', () {
+ final graph = {
+ 'a': ['b'],
+ };
+ final nodes = crawlAsync(
+ ['a'],
+ (n) => n,
+ (k, n) => k == 'b' ? throw ArgumentError() : graph[k] ?? <String>[],
+ );
+ expect(nodes, emitsThrough(emitsError(isArgumentError)));
+ });
+
+ test('surfaces exceptions for resolving keys', () {
+ final graph = {
+ 'a': ['b'],
+ };
+ final nodes = crawlAsync(
+ ['a'],
+ (n) => n == 'b' ? throw ArgumentError() : n,
+ (k, n) => graph[k] ?? <Never>[],
+ );
+ expect(nodes, emitsThrough(emitsError(isArgumentError)));
+ });
+ });
+}
diff --git a/pkgs/graphs/test/shortest_path_test.dart b/pkgs/graphs/test/shortest_path_test.dart
new file mode 100644
index 0000000..88281d4
--- /dev/null
+++ b/pkgs/graphs/test/shortest_path_test.dart
@@ -0,0 +1,161 @@
+// 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.
+
+import 'dart:collection';
+import 'dart:math' show Random;
+
+import 'package:graphs/graphs.dart';
+import 'package:test/test.dart';
+
+import 'utils/utils.dart';
+
+void main() {
+ const graph = <String, List<String>>{
+ '1': ['2', '5'],
+ '2': ['3'],
+ '3': ['4', '5'],
+ '4': ['1'],
+ '5': ['8'],
+ '6': ['7'],
+ };
+
+ List<String> readEdges(String key) => graph[key] ?? [];
+
+ List<X> getXValues(X key) => graph[key.value]?.map(X.new).toList() ?? [];
+
+ void singlePathTest(String from, String to, List<String>? expected) {
+ test('$from -> $to should be $expected (mapped)', () {
+ expect(
+ shortestPath<X>(
+ X(from),
+ X(to),
+ getXValues,
+ equals: xEquals,
+ hashCode: xHashCode,
+ )?.map((x) => x.value),
+ expected,
+ );
+ });
+
+ test('$from -> $to should be $expected', () {
+ expect(shortestPath(from, to, readEdges), expected);
+ });
+ }
+
+ void pathsTest(
+ String from,
+ Map<String, List<String>> expected,
+ List<String> nullPaths,
+ ) {
+ test('paths from $from (mapped)', () {
+ final result = shortestPaths<X>(
+ X(from),
+ getXValues,
+ equals: xEquals,
+ hashCode: xHashCode,
+ ).map((k, v) => MapEntry(k.value, v.map((x) => x.value).toList()));
+ expect(result, expected);
+ });
+
+ test('paths from $from', () {
+ final result = shortestPaths(from, readEdges);
+ expect(result, expected);
+ });
+
+ for (var entry in expected.entries) {
+ singlePathTest(from, entry.key, entry.value);
+ }
+
+ for (var entry in nullPaths) {
+ singlePathTest(from, entry, null);
+ }
+ }
+
+ pathsTest('1', {
+ '5': ['5'],
+ '3': ['2', '3'],
+ '8': ['5', '8'],
+ '1': [],
+ '2': ['2'],
+ '4': ['2', '3', '4'],
+ }, [
+ '6',
+ '7',
+ ]);
+
+ pathsTest('6', {
+ '7': ['7'],
+ '6': [],
+ }, [
+ '1',
+ ]);
+ pathsTest('7', {'7': []}, ['1', '6']);
+
+ pathsTest('42', {'42': []}, ['1', '6']);
+
+ test('integration test', () {
+ // Be deterministic in the generated graph. This test may have to be updated
+ // if the behavior of `Random` changes for the provided seed.
+ final rnd = Random(1);
+ const size = 1000;
+ final graph = HashMap<int, List<int>>();
+
+ Iterable<int>? resultForGraph() =>
+ shortestPath(0, size - 1, (e) => graph[e] ?? const []);
+
+ void addRandomEdge() {
+ final toList = graph.putIfAbsent(rnd.nextInt(size), () => <int>[]);
+
+ final toValue = rnd.nextInt(size);
+ if (!toList.contains(toValue)) {
+ toList.add(toValue);
+ }
+ }
+
+ Iterable<int>? result;
+
+ // Add edges until there is a shortest path between `0` and `size - 1`
+ do {
+ addRandomEdge();
+ result = resultForGraph();
+ } while (result == null);
+
+ expect(result, [313, 547, 91, 481, 74, 64, 439, 388, 660, 275, 999]);
+
+ var count = 0;
+ // Add edges until the shortest path between `0` and `size - 1` is 2 items
+ // Adding edges should never increase the length of the shortest path.
+ // Adding enough edges should reduce the length of the shortest path.
+ do {
+ expect(++count, lessThan(size * 5), reason: 'This loop should finish.');
+ addRandomEdge();
+ final previousResultLength = result!.length;
+ result = resultForGraph();
+ expect(result, hasLength(lessThanOrEqualTo(previousResultLength)));
+ } while (result!.length > 2);
+
+ expect(result, [275, 999]);
+
+ count = 0;
+ // Remove edges until there is no shortest path.
+ // Removing edges should never reduce the length of the shortest path.
+ // Removing enough edges should increase the length of the shortest path and
+ // eventually eliminate any path.
+ do {
+ expect(++count, lessThan(size * 5), reason: 'This loop should finish.');
+ final randomKey = graph.keys.elementAt(rnd.nextInt(graph.length));
+ final list = graph[randomKey]!;
+ expect(list, isNotEmpty);
+ list.removeAt(rnd.nextInt(list.length));
+ if (list.isEmpty) {
+ graph.remove(randomKey);
+ }
+ final previousResultLength = result!.length;
+ result = resultForGraph();
+ if (result != null) {
+ expect(result, hasLength(greaterThanOrEqualTo(previousResultLength)));
+ }
+ } while (result != null);
+ });
+}
diff --git a/pkgs/graphs/test/strongly_connected_components_test.dart b/pkgs/graphs/test/strongly_connected_components_test.dart
new file mode 100644
index 0000000..2fa4d88
--- /dev/null
+++ b/pkgs/graphs/test/strongly_connected_components_test.dart
@@ -0,0 +1,304 @@
+// Copyright (c) 2017, 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:graphs/graphs.dart';
+import 'package:test/test.dart';
+
+import 'utils/graph.dart';
+import 'utils/utils.dart';
+
+void main() {
+ group('strongly connected components', () {
+ /// Run [stronglyConnectedComponents] on [g].
+ List<List<String>> components(
+ Map<String, List<String>?> g, {
+ Iterable<String>? startNodes,
+ }) {
+ final graph = Graph(g);
+ return stronglyConnectedComponents(
+ startNodes ?? graph.allNodes,
+ graph.edges,
+ );
+ }
+
+ test('empty result for empty graph', () {
+ final result = components({});
+ expect(result, isEmpty);
+ });
+
+ test('single item for single node', () {
+ final result = components({'a': []});
+ expect(result, [
+ ['a'],
+ ]);
+ });
+
+ test('handles non-cycles', () {
+ final result = components({
+ 'a': ['b'],
+ 'b': ['c'],
+ 'c': [],
+ });
+ expect(result, [
+ ['c'],
+ ['b'],
+ ['a'],
+ ]);
+ });
+
+ test('handles entire graph as cycle', () {
+ final result = components({
+ 'a': ['b'],
+ 'b': ['c'],
+ 'c': ['d'],
+ 'd': ['a'],
+ });
+ expect(
+ result,
+ [allOf(contains('a'), contains('b'), contains('c'), contains('d'))],
+ );
+ });
+
+ test('includes the first passed root last in a cycle', () {
+ // In cases where this is used to find a topological ordering the first
+ // value in nodes should always come last.
+ final graph = {
+ 'a': ['b'],
+ 'b': ['a'],
+ };
+ final resultFromA = components(graph, startNodes: ['a']);
+ final resultFromB = components(graph, startNodes: ['b']);
+ expect(resultFromA.single.last, 'a');
+ expect(resultFromB.single.last, 'b');
+ });
+
+ test('handles cycles in the middle', () {
+ final result = components({
+ 'a': ['b', 'c'],
+ 'b': ['c', 'd'],
+ 'c': ['b', 'd'],
+ 'd': [],
+ });
+ expect(result, [
+ ['d'],
+ allOf(contains('b'), contains('c')),
+ ['a'],
+ ]);
+ });
+
+ test('handles self cycles', () {
+ final result = components({
+ 'a': ['b'],
+ 'b': ['b'],
+ });
+ expect(result, [
+ ['b'],
+ ['a'],
+ ]);
+ });
+
+ test('valid topological ordering for disjoint subgraphs', () {
+ final result = components({
+ 'a': ['b', 'c'],
+ 'b': ['b1', 'b2'],
+ 'c': ['c1', 'c2'],
+ 'b1': [],
+ 'b2': [],
+ 'c1': [],
+ 'c2': [],
+ });
+
+ expect(
+ result,
+ containsAllInOrder([
+ ['c1'],
+ ['c'],
+ ['a'],
+ ]),
+ );
+ expect(
+ result,
+ containsAllInOrder([
+ ['c2'],
+ ['c'],
+ ['a'],
+ ]),
+ );
+ expect(
+ result,
+ containsAllInOrder([
+ ['b1'],
+ ['b'],
+ ['a'],
+ ]),
+ );
+ expect(
+ result,
+ containsAllInOrder([
+ ['b2'],
+ ['b'],
+ ['a'],
+ ]),
+ );
+ });
+
+ test('handles getting null for edges', () {
+ final result = components({
+ 'a': ['b'],
+ 'b': null,
+ });
+ expect(result, [
+ ['b'],
+ ['a'],
+ ]);
+ });
+ });
+
+ group('custom hashCode and equals', () {
+ /// Run [stronglyConnectedComponents] on [g].
+ List<List<String>> components(
+ Map<String, List<String>?> g, {
+ Iterable<String>? startNodes,
+ }) {
+ final graph = BadGraph(g);
+
+ startNodes ??= graph.allNodes.map((n) => n.value);
+
+ return stronglyConnectedComponents<X>(
+ startNodes.map(X.new),
+ graph.edges,
+ equals: xEquals,
+ hashCode: xHashCode,
+ ).map((list) => list.map((x) => x.value).toList()).toList();
+ }
+
+ test('empty result for empty graph', () {
+ final result = components({});
+ expect(result, isEmpty);
+ });
+
+ test('single item for single node', () {
+ final result = components({'a': []});
+ expect(result, [
+ ['a'],
+ ]);
+ });
+
+ test('handles non-cycles', () {
+ final result = components({
+ 'a': ['b'],
+ 'b': ['c'],
+ 'c': [],
+ });
+ expect(result, [
+ ['c'],
+ ['b'],
+ ['a'],
+ ]);
+ });
+
+ test('handles entire graph as cycle', () {
+ final result = components({
+ 'a': ['b'],
+ 'b': ['c'],
+ 'c': ['a'],
+ });
+ expect(result, [allOf(contains('a'), contains('b'), contains('c'))]);
+ });
+
+ test('includes the first passed root last in a cycle', () {
+ // In cases where this is used to find a topological ordering the first
+ // value in nodes should always come last.
+ final graph = {
+ 'a': ['b'],
+ 'b': ['a'],
+ };
+ final resultFromA = components(graph, startNodes: ['a']);
+ final resultFromB = components(graph, startNodes: ['b']);
+ expect(resultFromA.single.last, 'a');
+ expect(resultFromB.single.last, 'b');
+ });
+
+ test('handles cycles in the middle', () {
+ final result = components({
+ 'a': ['b', 'c'],
+ 'b': ['c', 'd'],
+ 'c': ['b', 'd'],
+ 'd': [],
+ });
+ expect(result, [
+ ['d'],
+ allOf(contains('b'), contains('c')),
+ ['a'],
+ ]);
+ });
+
+ test('handles self cycles', () {
+ final result = components({
+ 'a': ['b'],
+ 'b': ['b'],
+ });
+ expect(result, [
+ ['b'],
+ ['a'],
+ ]);
+ });
+
+ test('valid topological ordering for disjoint subgraphs', () {
+ final result = components({
+ 'a': ['b', 'c'],
+ 'b': ['b1', 'b2'],
+ 'c': ['c1', 'c2'],
+ 'b1': [],
+ 'b2': [],
+ 'c1': [],
+ 'c2': [],
+ });
+
+ expect(
+ result,
+ containsAllInOrder([
+ ['c1'],
+ ['c'],
+ ['a'],
+ ]),
+ );
+ expect(
+ result,
+ containsAllInOrder([
+ ['c2'],
+ ['c'],
+ ['a'],
+ ]),
+ );
+ expect(
+ result,
+ containsAllInOrder([
+ ['b1'],
+ ['b'],
+ ['a'],
+ ]),
+ );
+ expect(
+ result,
+ containsAllInOrder([
+ ['b2'],
+ ['b'],
+ ['a'],
+ ]),
+ );
+ });
+
+ test('handles getting null for edges', () {
+ final result = components({
+ 'a': ['b'],
+ 'b': null,
+ });
+ expect(result, [
+ ['b'],
+ ['a'],
+ ]);
+ });
+ });
+}
diff --git a/pkgs/graphs/test/topological_sort_test.dart b/pkgs/graphs/test/topological_sort_test.dart
new file mode 100644
index 0000000..831946b
--- /dev/null
+++ b/pkgs/graphs/test/topological_sort_test.dart
@@ -0,0 +1,362 @@
+// 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.
+
+import 'dart:collection';
+
+import 'package:graphs/graphs.dart';
+import 'package:test/test.dart';
+
+import 'utils/utils.dart';
+
+void main() {
+ group('without secondarySort', () {
+ group('topologically sorts a graph', () {
+ test('with no nodes', () {
+ expect(_topologicalSort({}), isEmpty);
+ });
+
+ test('with only one node', () {
+ expect(_topologicalSort({1: []}), equals([1]));
+ });
+
+ test('with no edges', () {
+ expect(
+ _topologicalSort({1: [], 2: [], 3: [], 4: []}),
+ unorderedEquals([1, 2, 3, 4]),
+ );
+ });
+
+ test('with single edges', () {
+ expect(
+ _topologicalSort({
+ 1: [2],
+ 2: [3],
+ 3: [4],
+ 4: [],
+ }),
+ equals([1, 2, 3, 4]),
+ );
+ });
+
+ test('with many edges from one node', () {
+ final result = _topologicalSort({
+ 1: [2, 3, 4],
+ 2: [],
+ 3: [],
+ 4: [],
+ });
+ expect(result.indexOf(1), lessThan(result.indexOf(2)));
+ expect(result.indexOf(1), lessThan(result.indexOf(3)));
+ expect(result.indexOf(1), lessThan(result.indexOf(4)));
+ });
+
+ test('with transitive edges', () {
+ final result = _topologicalSort({
+ 1: [2, 4],
+ 2: [],
+ 3: [],
+ 4: [3],
+ });
+ expect(result.indexOf(1), lessThan(result.indexOf(2)));
+ expect(result.indexOf(1), lessThan(result.indexOf(3)));
+ expect(result.indexOf(1), lessThan(result.indexOf(4)));
+ expect(result.indexOf(4), lessThan(result.indexOf(3)));
+ });
+
+ test('with diamond edges', () {
+ final result = _topologicalSort({
+ 1: [2, 3],
+ 2: [4],
+ 3: [4],
+ 4: [],
+ });
+ expect(result.indexOf(1), lessThan(result.indexOf(2)));
+ expect(result.indexOf(1), lessThan(result.indexOf(3)));
+ expect(result.indexOf(1), lessThan(result.indexOf(4)));
+ expect(result.indexOf(2), lessThan(result.indexOf(4)));
+ expect(result.indexOf(3), lessThan(result.indexOf(4)));
+ });
+ });
+
+ test('respects custom equality and hash functions', () {
+ expect(
+ _topologicalSort<int>(
+ {
+ 0: [2],
+ 3: [4],
+ 5: [6],
+ 7: [],
+ },
+ equals: (i, j) => (i ~/ 2) == (j ~/ 2),
+ hashCode: (i) => (i ~/ 2).hashCode,
+ ),
+ equals([
+ 0,
+ anyOf([2, 3]),
+ anyOf([4, 5]),
+ anyOf([6, 7]),
+ ]),
+ );
+ });
+
+ group('throws a CycleException for a graph with', () {
+ test('a one-node cycle', () {
+ expect(
+ () => _topologicalSort({
+ 1: [1],
+ }),
+ throwsCycleException([1]),
+ );
+ });
+
+ test('a multi-node cycle', () {
+ expect(
+ () => _topologicalSort({
+ 1: [2],
+ 2: [3],
+ 3: [4],
+ 4: [1],
+ }),
+ throwsCycleException([4, 1, 2, 3]),
+ );
+ });
+ });
+ });
+
+ group('with secondarySort', () {
+ group('topologically sorts a graph', () {
+ test('with no nodes', () {
+ expect(_topologicalSort({}, secondarySort: true), isEmpty);
+ });
+
+ test('with only one node', () {
+ expect(_topologicalSort({1: []}, secondarySort: true), equals([1]));
+ });
+
+ test('with no edges', () {
+ expect(
+ _topologicalSort({1: [], 2: [], 3: [], 4: []}, secondarySort: true),
+ unorderedEquals([1, 2, 3, 4]),
+ );
+ });
+
+ test('with single edges', () {
+ expect(
+ _topologicalSort(
+ {
+ 1: [2],
+ 2: [3],
+ 3: [4],
+ 4: [],
+ },
+ secondarySort: true,
+ ),
+ equals([1, 2, 3, 4]),
+ );
+ });
+
+ test('with many edges from one node', () {
+ final result = _topologicalSort(
+ {
+ 1: [2, 3, 4],
+ 2: [],
+ 3: [],
+ 4: [],
+ },
+ secondarySort: true,
+ );
+ expect(result.indexOf(1), lessThan(result.indexOf(2)));
+ expect(result.indexOf(1), lessThan(result.indexOf(3)));
+ expect(result.indexOf(1), lessThan(result.indexOf(4)));
+ });
+
+ test('with transitive edges', () {
+ final result = _topologicalSort(
+ {
+ 1: [2, 4],
+ 2: [],
+ 3: [],
+ 4: [3],
+ },
+ secondarySort: true,
+ );
+ expect(result.indexOf(1), lessThan(result.indexOf(2)));
+ expect(result.indexOf(1), lessThan(result.indexOf(3)));
+ expect(result.indexOf(1), lessThan(result.indexOf(4)));
+ expect(result.indexOf(4), lessThan(result.indexOf(3)));
+ });
+
+ test('with diamond edges', () {
+ final result = _topologicalSort(
+ {
+ 1: [2, 3],
+ 2: [4],
+ 3: [4],
+ 4: [],
+ },
+ secondarySort: true,
+ );
+ expect(result.indexOf(1), lessThan(result.indexOf(2)));
+ expect(result.indexOf(1), lessThan(result.indexOf(3)));
+ expect(result.indexOf(1), lessThan(result.indexOf(4)));
+ expect(result.indexOf(2), lessThan(result.indexOf(4)));
+ expect(result.indexOf(3), lessThan(result.indexOf(4)));
+ });
+ });
+
+ group('lexically sorts a graph where possible', () {
+ test('with no edges', () {
+ final result =
+ _topologicalSort({4: [], 3: [], 1: [], 2: []}, secondarySort: true);
+ expect(result, equals([1, 2, 3, 4]));
+ });
+
+ test('with one non-lexical edge', () {
+ final result = _topologicalSort(
+ {
+ 4: [],
+ 3: [1],
+ 1: [],
+ 2: [],
+ },
+ secondarySort: true,
+ );
+ expect(
+ result,
+ equals(
+ anyOf([
+ [2, 3, 1, 4],
+ [3, 1, 2, 4],
+ ]),
+ ),
+ );
+ });
+
+ test('with a non-lexical topolgical order', () {
+ final result = _topologicalSort(
+ {
+ 4: [3],
+ 3: [2],
+ 2: [1],
+ 1: [],
+ },
+ secondarySort: true,
+ );
+ expect(result, equals([4, 3, 2, 1]));
+ });
+
+ group('with multiple layers', () {
+ test('in lexical order', () {
+ final result = _topologicalSort(
+ {
+ 1: [2],
+ 2: [3],
+ 3: [],
+ 4: [5],
+ 5: [6],
+ 6: [],
+ },
+ secondarySort: true,
+ );
+ expect(result, equals([1, 2, 3, 4, 5, 6]));
+ });
+
+ test('in non-lexical order', () {
+ final result = _topologicalSort(
+ {
+ 1: [3],
+ 3: [5],
+ 4: [2],
+ 2: [6],
+ 5: [],
+ 6: [],
+ },
+ secondarySort: true,
+ );
+ expect(
+ result,
+ anyOf([
+ equals([1, 3, 4, 2, 5, 6]),
+ equals([1, 4, 2, 3, 5, 6]),
+ ]),
+ );
+ });
+ });
+ });
+
+ test('respects custom equality and hash functions', () {
+ expect(
+ _topologicalSort<int>(
+ {
+ 0: [2],
+ 3: [4],
+ 5: [6],
+ 7: [],
+ },
+ equals: (i, j) => (i ~/ 2) == (j ~/ 2),
+ hashCode: (i) => (i ~/ 2).hashCode,
+ secondarySort: true,
+ ),
+ equals([
+ 0,
+ anyOf([2, 3]),
+ anyOf([4, 5]),
+ anyOf([6, 7]),
+ ]),
+ );
+ });
+
+ group('throws a CycleException for a graph with', () {
+ test('a one-node cycle', () {
+ expect(
+ () => _topologicalSort(
+ {
+ 1: [1],
+ },
+ secondarySort: true,
+ ),
+ throwsCycleException([1]),
+ );
+ });
+
+ test('a multi-node cycle', () {
+ expect(
+ () => _topologicalSort(
+ {
+ 1: [2],
+ 2: [3],
+ 3: [4],
+ 4: [1],
+ },
+ secondarySort: true,
+ ),
+ throwsCycleException([4, 1, 2, 3]),
+ );
+ });
+ });
+ });
+}
+
+/// Runs a topological sort on a graph represented a map from keys to edges.
+List<T> _topologicalSort<T>(
+ Map<T, List<T>> graph, {
+ bool Function(T, T)? equals,
+ int Function(T)? hashCode,
+ bool secondarySort = false,
+}) {
+ if (equals != null) {
+ graph = LinkedHashMap(equals: equals, hashCode: hashCode)..addAll(graph);
+ }
+ return topologicalSort(
+ graph.keys,
+ (node) {
+ expect(graph, contains(node));
+ return graph[node]!;
+ },
+ equals: equals,
+ hashCode: hashCode,
+ secondarySort:
+ secondarySort ? (a, b) => (a as Comparable<T>).compareTo(b) : null,
+ );
+}
diff --git a/pkgs/graphs/test/transitive_closure_test.dart b/pkgs/graphs/test/transitive_closure_test.dart
new file mode 100644
index 0000000..f732573
--- /dev/null
+++ b/pkgs/graphs/test/transitive_closure_test.dart
@@ -0,0 +1,350 @@
+// Copyright (c) 2023, 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:collection';
+
+import 'package:graphs/graphs.dart';
+import 'package:test/test.dart';
+
+import 'utils/utils.dart';
+
+void main() {
+ group('for an acyclic graph', () {
+ for (final acyclic in [true, false]) {
+ group('with acyclic: $acyclic', () {
+ group('returns the transitive closure for a graph', () {
+ test('with no nodes', () {
+ expect(_transitiveClosure<int>({}, acyclic: acyclic), isEmpty);
+ });
+
+ test('with only one node', () {
+ expect(
+ _transitiveClosure<int>({1: []}, acyclic: acyclic),
+ equals({1: <int>{}}),
+ );
+ });
+
+ test('with no edges', () {
+ expect(
+ _transitiveClosure<int>(
+ {1: [], 2: [], 3: [], 4: []},
+ acyclic: acyclic,
+ ),
+ equals(<int, Set<int>>{1: {}, 2: {}, 3: {}, 4: {}}),
+ );
+ });
+
+ test('with single edges', () {
+ expect(
+ _transitiveClosure<int>(
+ {
+ 1: [2],
+ 2: [3],
+ 3: [4],
+ 4: [],
+ },
+ acyclic: acyclic,
+ ),
+ equals({
+ 1: {2, 3, 4},
+ 2: {3, 4},
+ 3: {4},
+ 4: <int>{},
+ }),
+ );
+ });
+
+ test('with many edges from one node', () {
+ expect(
+ _transitiveClosure<int>(
+ {
+ 1: [2, 3, 4],
+ 2: [],
+ 3: [],
+ 4: [],
+ },
+ acyclic: acyclic,
+ ),
+ equals(<int, Set<int>>{
+ 1: {2, 3, 4},
+ 2: {},
+ 3: {},
+ 4: {},
+ }),
+ );
+ });
+
+ test('with transitive edges', () {
+ expect(
+ _transitiveClosure<int>(
+ {
+ 1: [2, 4],
+ 2: [],
+ 3: [],
+ 4: [3],
+ },
+ acyclic: acyclic,
+ ),
+ equals(<int, Set<int>>{
+ 1: {2, 3, 4},
+ 2: {},
+ 3: {},
+ 4: {3},
+ }),
+ );
+ });
+
+ test('with diamond edges', () {
+ expect(
+ _transitiveClosure<int>(
+ {
+ 1: [2, 3],
+ 2: [4],
+ 3: [4],
+ 4: [],
+ },
+ acyclic: acyclic,
+ ),
+ equals(<int, Set<int>>{
+ 1: {2, 3, 4},
+ 2: {4},
+ 3: {4},
+ 4: {},
+ }),
+ );
+ });
+
+ test('with disjoint subgraphs', () {
+ expect(
+ _transitiveClosure<int>(
+ {
+ 1: [2],
+ 2: [3],
+ 3: [],
+ 4: [5],
+ 5: [6],
+ 6: [],
+ },
+ acyclic: acyclic,
+ ),
+ equals(<int, Set<int>>{
+ 1: {2, 3},
+ 2: {3},
+ 3: {},
+ 4: {5, 6},
+ 5: {6},
+ 6: {},
+ }),
+ );
+ });
+ });
+
+ test('respects custom equality and hash functions', () {
+ final result = _transitiveClosure<int>(
+ {
+ 0: [2],
+ 3: [4],
+ 5: [6],
+ 7: [],
+ },
+ equals: (i, j) => (i ~/ 2) == (j ~/ 2),
+ hashCode: (i) => (i ~/ 2).hashCode,
+ );
+
+ expect(
+ result.keys,
+ unorderedMatches([
+ 0,
+ anyOf([2, 3]),
+ anyOf([4, 5]),
+ anyOf([6, 7]),
+ ]),
+ );
+ expect(
+ result[0],
+ equals({
+ anyOf([2, 3]),
+ anyOf([4, 5]),
+ anyOf([6, 7]),
+ }),
+ );
+ expect(
+ result[2],
+ equals({
+ anyOf([4, 5]),
+ anyOf([6, 7]),
+ }),
+ );
+ expect(
+ result[4],
+ equals({
+ anyOf([6, 7]),
+ }),
+ );
+ expect(result[6], isEmpty);
+ });
+ });
+ }
+ });
+
+ group('for a cyclic graph', () {
+ group('with acyclic: true throws a CycleException for a graph with', () {
+ test('a one-node cycle', () {
+ expect(
+ () => _transitiveClosure<int>(
+ {
+ 1: [1],
+ },
+ acyclic: true,
+ ),
+ throwsCycleException([1]),
+ );
+ });
+
+ test('a multi-node cycle', () {
+ expect(
+ () => _transitiveClosure<int>(
+ {
+ 1: [2],
+ 2: [3],
+ 3: [4],
+ 4: [1],
+ },
+ acyclic: true,
+ ),
+ throwsCycleException([4, 1, 2, 3]),
+ );
+ });
+ });
+
+ group('returns the transitive closure for a graph', () {
+ test('with a single one-node component', () {
+ expect(
+ _transitiveClosure<int>({
+ 1: [1],
+ }),
+ equals({
+ 1: {1},
+ }),
+ );
+ });
+
+ test('with a single multi-node component', () {
+ expect(
+ _transitiveClosure<int>({
+ 1: [2],
+ 2: [3],
+ 3: [4],
+ 4: [1],
+ }),
+ equals({
+ 1: {1, 2, 3, 4},
+ 2: {1, 2, 3, 4},
+ 3: {1, 2, 3, 4},
+ 4: {1, 2, 3, 4},
+ }),
+ );
+ });
+
+ test('with a series of multi-node components', () {
+ expect(
+ _transitiveClosure<int>({
+ 1: [2],
+ 2: [1, 3],
+ 3: [4],
+ 4: [3, 5],
+ 5: [6],
+ 6: [5, 7],
+ 7: [8],
+ 8: [7],
+ }),
+ equals({
+ 1: {1, 2, 3, 4, 5, 6, 7, 8},
+ 2: {1, 2, 3, 4, 5, 6, 7, 8},
+ 3: {3, 4, 5, 6, 7, 8},
+ 4: {3, 4, 5, 6, 7, 8},
+ 5: {5, 6, 7, 8},
+ 6: {5, 6, 7, 8},
+ 7: {7, 8},
+ 8: {7, 8},
+ }),
+ );
+ });
+
+ test('with a diamond of multi-node components', () {
+ expect(
+ _transitiveClosure<int>({
+ 1: [2],
+ 2: [1, 3, 5],
+ 3: [4],
+ 4: [3, 7],
+ 5: [6],
+ 6: [5, 7],
+ 7: [8],
+ 8: [7],
+ }),
+ equals({
+ 1: {1, 2, 3, 4, 5, 6, 7, 8},
+ 2: {1, 2, 3, 4, 5, 6, 7, 8},
+ 3: {3, 4, 7, 8},
+ 4: {3, 4, 7, 8},
+ 5: {5, 6, 7, 8},
+ 6: {5, 6, 7, 8},
+ 7: {7, 8},
+ 8: {7, 8},
+ }),
+ );
+ });
+
+ test('mixed single- and multi-node components', () {
+ expect(
+ _transitiveClosure<int>({
+ 1: [2],
+ 2: [1, 3],
+ 3: [4],
+ 4: [5],
+ 5: [4, 6],
+ 6: [7],
+ 7: [8],
+ 8: [7],
+ }),
+ equals({
+ 1: {1, 2, 3, 4, 5, 6, 7, 8},
+ 2: {1, 2, 3, 4, 5, 6, 7, 8},
+ 3: {4, 5, 6, 7, 8},
+ 4: {4, 5, 6, 7, 8},
+ 5: {4, 5, 6, 7, 8},
+ 6: {7, 8},
+ 7: {7, 8},
+ 8: {7, 8},
+ }),
+ );
+ });
+ });
+ });
+}
+
+/// Returns the transitive closure of a graph represented a map from keys to
+/// edges.
+Map<T, Set<T>> _transitiveClosure<T extends Object>(
+ Map<T, List<T>> graph, {
+ bool Function(T, T)? equals,
+ int Function(T)? hashCode,
+ bool acyclic = false,
+}) {
+ assert((equals == null) == (hashCode == null));
+ if (equals != null) {
+ graph = LinkedHashMap(equals: equals, hashCode: hashCode)..addAll(graph);
+ }
+ return transitiveClosure(
+ graph.keys,
+ (node) {
+ expect(graph, contains(node));
+ return graph[node]!;
+ },
+ equals: equals,
+ hashCode: hashCode,
+ acyclic: acyclic,
+ );
+}
diff --git a/pkgs/graphs/test/utils/graph.dart b/pkgs/graphs/test/utils/graph.dart
new file mode 100644
index 0000000..ffa24b5
--- /dev/null
+++ b/pkgs/graphs/test/utils/graph.dart
@@ -0,0 +1,47 @@
+// Copyright (c) 2017, 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:collection';
+
+import 'utils.dart';
+
+/// A representation of a Graph since none is specified in `lib/`.
+class Graph {
+ final Map<String, List<String>?> _graph;
+
+ Graph(this._graph);
+
+ List<String> edges(String node) => _graph[node] ?? <Never>[];
+
+ Iterable<String> get allNodes => _graph.keys;
+}
+
+class BadGraph {
+ final Map<X, List<X>?> _graph;
+
+ BadGraph(Map<String, List<String>?> values)
+ : _graph = LinkedHashMap(equals: xEquals, hashCode: xHashCode)
+ ..addEntries(
+ values.entries
+ .map((e) => MapEntry(X(e.key), e.value?.map(X.new).toList())),
+ );
+
+ List<X> edges(X node) => _graph[node] ?? <Never>[];
+
+ Iterable<X> get allNodes => _graph.keys;
+}
+
+/// A representation of a Graph where keys can asynchronously be resolved to
+/// real values or to edges.
+class AsyncGraph {
+ final Map<String, List<String>?> graph;
+
+ AsyncGraph(this.graph);
+
+ Future<String?> readNode(String node) async =>
+ graph.containsKey(node) ? node : null;
+
+ Future<Iterable<String>> edges(String key, String? node) async =>
+ graph[key] ?? <Never>[];
+}
diff --git a/pkgs/graphs/test/utils/utils.dart b/pkgs/graphs/test/utils/utils.dart
new file mode 100644
index 0000000..45a42c6
--- /dev/null
+++ b/pkgs/graphs/test/utils/utils.dart
@@ -0,0 +1,37 @@
+// 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.
+
+import 'package:graphs/graphs.dart';
+import 'package:test/test.dart';
+
+bool xEquals(X a, X b) => a.value == b.value;
+
+int xHashCode(X a) => a.value.hashCode;
+
+/// Returns a matcher that verifies that a function throws a [CycleException<T>]
+/// with the given [cycle].
+Matcher throwsCycleException<T>(List<T> cycle) => throwsA(
+ allOf([
+ isA<CycleException<T>>(),
+ predicate((exception) {
+ expect((exception as CycleException<T>).cycle, equals(cycle));
+ return true;
+ }),
+ ]),
+ );
+
+class X {
+ final String value;
+
+ X(this.value);
+
+ @override
+ bool operator ==(Object other) => throw UnimplementedError();
+
+ @override
+ int get hashCode => 42;
+
+ @override
+ String toString() => '($value)';
+}
diff --git a/pkgs/html/.gitignore b/pkgs/html/.gitignore
new file mode 100644
index 0000000..3ab3a44
--- /dev/null
+++ b/pkgs/html/.gitignore
@@ -0,0 +1,19 @@
+# Don’t commit the following directories created by pub.
+.pub
+.dart_tool/
+build/
+packages
+.packages
+
+# Or the files created by dart2js.
+*.dart.js
+*.dart.precompiled.js
+*.js_
+*.js.deps
+*.js.map
+*.sw?
+.idea/
+.pub/
+
+# Include when developing application packages.
+pubspec.lock
diff --git a/pkgs/html/CHANGELOG.md b/pkgs/html/CHANGELOG.md
new file mode 100644
index 0000000..9a881e9
--- /dev/null
+++ b/pkgs/html/CHANGELOG.md
@@ -0,0 +1,149 @@
+## 0.15.5
+
+- Require Dart `3.2`.
+- Move to `dart-lang/tools` monorepo.
+
+## 0.15.4
+
+- Widen the dependency on `package:csslib`.
+- Require Dart `2.19`.
+
+## 0.15.3
+
+- Added package topics to the pubspec file.
+
+## 0.15.2
+
+- Add additional types at the API boundary (in `lib/parser.dart` and others).
+- Adopted the `package:dart_flutter_team_lints` linting rules.
+- Fixed an issue with `querySelector` where it would fail in some cases with
+ descendant or sibling combinators (#157).
+- Add an API example in `example/`.
+
+## 0.15.1
+
+- Move `htmlSerializeEscape` to its own library,
+ `package:html/html_escape.dart`, which is exported from
+ `package:html/dom_parsing.dart`.
+- Use more non-growable lists, and type annotations on List literals.
+- Switch analysis option `implicit-casts: false` to `strict-casts: true`.
+
+## 0.15.0
+
+- Migrate to null safety.
+- Drop `lastPhase`, `beforeRcDataPhase`, and `container` fields from
+ `HtmlParser` class. These fields never had a value other than `null`.
+
+## 0.14.0+4
+
+- Fix a bug parsing bad HTML where a 'button' end tag needs to close other
+ elements.
+
+## 0.14.0+3
+
+- Fix spans generated for HTML with higher-plane unicode characters
+ (eg. emojis).
+
+## 0.14.0+2
+
+- Support `package:css` `>=0.13.2 <0.17.0`.
+
+## 0.14.0+1
+
+- Support `package:css` `>=0.13.2 <0.16.0`.
+
+## 0.14.0
+
+*BREAKING CHANGES*
+
+- Drop support for encodings other than UTF-8 and ASCII.
+- Removed `parser_console.dart` library.
+
+## 0.13.4+1
+
+* Fixes to readme and pubspec.
+
+## 0.13.4
+
+* Require Dart 2.0 stable.
+
+## 0.13.3+3
+
+* Do not use this tag in our systems - there was an earlier version of it
+ pointing to a different commit, that is still in some caches.
+
+* Fix missing_return analyzer errors in `processStartTag` and `processEndTag`
+ methods.
+
+## 0.13.3+2
+
+* Set max SDK version to `<3.0.0`, and adjust other dependencies.
+
+## 0.13.3+1
+
+ * Updated SDK version to 2.0.0-dev.17.0
+
+## 0.13.3
+
+ * Update the signatures of `FilteredElementList.indexOf` and
+ `FilteredElementList.lastIndexOf` to include type annotations.
+
+## 0.13.2+2
+
+ * Update signature for implementations of `Iterable.singleWhere` to include
+ optional argument.
+
+## 0.13.2+1
+
+ * Changed the implementation of `Set` and `List` classes to use base classes
+ from `dart:collection`.
+
+## 0.13.2
+
+ * Support the latest release of `pkg/csslib`.
+
+## 0.13.1
+ * Update Set.difference to take a Set<Object>.
+
+## 0.13.0
+
+ * **BREAKING** Fix all [strong mode][] errors and warnings.
+ This involved adding more precise types on some public APIs, which is why it
+ may break users.
+
+[strong mode]: https://github.com/dart-lang/dev_compiler/blob/master/STRONG_MODE.md
+
+#### Pub version 0.12.2+2
+ * Support `csslib` versions `0.13.x`.
+
+#### Pub version 0.12.2+1
+ * Exclude `.packages` file from the published package.
+
+#### Pub version 0.12.2
+ * Added `Element.endSourceSpan`, containing the span of a closing tag.
+
+#### Pub version 0.12.0+1
+ * Support `csslib` version `0.12.0`.
+
+#### Rename to package:html 0.12.0
+ * package has been renamed to `html`
+
+#### Pub version 0.12.0
+ * switch from `source_maps`' `Span` class to `source_span`'s
+ `SourceSpan` class.
+
+#### Pub version 0.11.0+2
+ * expand the version constraint for csslib.
+
+#### Pub version 0.10.0+1
+ * use a more recent source_maps version.
+
+#### Pub version 0.10.0
+ * fix how document fragments are added in NodeList.add/addAll/insertAll.
+
+#### Pub version 0.9.2-dev
+ * add Node.text, Node.append, Document.documentElement
+ * add Text.data, deprecate Node.value and Text.value.
+ * deprecate Node.$dom_nodeType
+ * added querySelector/querySelectorAll, deprecated query/queryAll.
+ This matches the current APIs in dart:html.
diff --git a/pkgs/html/LICENSE b/pkgs/html/LICENSE
new file mode 100644
index 0000000..1d776b2
--- /dev/null
+++ b/pkgs/html/LICENSE
@@ -0,0 +1,23 @@
+Copyright (c) 2006-2012 The Authors
+
+Contributors:
+James Graham - jg307@cam.ac.uk
+Anne van Kesteren - annevankesteren@gmail.com
+Lachlan Hunt - lachlan.hunt@lachy.id.au
+Matt McDonald - kanashii@kanashii.ca
+Sam Ruby - rubys@intertwingly.net
+Ian Hickson (Google) - ian@hixie.ch
+Thomas Broyer - t.broyer@ltgt.net
+Jacques Distler - distler@golem.ph.utexas.edu
+Henri Sivonen - hsivonen@iki.fi
+Adam Barth - abarth@webkit.org
+Eric Seidel - eric@webkit.org
+The Mozilla Foundation (contributions from Henri Sivonen since 2008)
+David Flanagan (Mozilla) - dflanagan@mozilla.com
+Google LLC (contributed the Dart port) - misc@dartlang.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/pkgs/html/README.md b/pkgs/html/README.md
new file mode 100644
index 0000000..ed1d000
--- /dev/null
+++ b/pkgs/html/README.md
@@ -0,0 +1,28 @@
+[](https://github.com/dart-lang/tools/actions/workflows/html.yaml)
+[](https://pub.dev/packages/html)
+[](https://pub.dev/packages/html/publisher)
+
+A Dart implementation of an HTML5 parser.
+
+## Usage
+
+Parsing HTML is easy!
+
+```dart
+import 'package:html/parser.dart';
+
+void main() {
+ var document = parse(
+ '<body>Hello world! <a href="www.html5rocks.com">HTML5 rocks!');
+ print(document.outerHtml);
+}
+```
+
+You can pass a String or list of bytes to `parse`. There's also `parseFragment`
+for parsing a document fragment, and `HtmlParser` if you want more low level
+control.
+
+## Background
+
+This package was a port of the Python
+[html5lib](https://github.com/html5lib/html5lib-python) library.
diff --git a/pkgs/html/analysis_options.yaml b/pkgs/html/analysis_options.yaml
new file mode 100644
index 0000000..90d920e
--- /dev/null
+++ b/pkgs/html/analysis_options.yaml
@@ -0,0 +1,10 @@
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-casts: true
+ strict-raw-types: true
+ errors:
+ lines_longer_than_80_chars: ignore
+ # https://github.com/dart-lang/linter/issues/1649
+ prefer_collection_literals: ignore
diff --git a/pkgs/html/example/main.dart b/pkgs/html/example/main.dart
new file mode 100644
index 0000000..309ed7c
--- /dev/null
+++ b/pkgs/html/example/main.dart
@@ -0,0 +1,57 @@
+import 'package:html/dom.dart';
+import 'package:html/dom_parsing.dart';
+import 'package:html/parser.dart';
+
+void main(List<String> args) {
+ var document = parse('''
+<body>
+ <h2>Header 1</h2>
+ <p>Text.</p>
+ <h2>Header 2</h2>
+ More text.
+ <br/>
+</body>''');
+
+ // outerHtml output
+ print('outer html:');
+ print(document.outerHtml);
+
+ print('');
+
+ // visitor output
+ print('html visitor:');
+ _Visitor().visit(document);
+}
+
+// Note: this example visitor doesn't handle things like printing attributes and
+// such.
+class _Visitor extends TreeVisitor {
+ String indent = '';
+
+ @override
+ void visitText(Text node) {
+ if (node.data.trim().isNotEmpty) {
+ print('$indent${node.data.trim()}');
+ }
+ }
+
+ @override
+ void visitElement(Element node) {
+ if (isVoidElement(node.localName)) {
+ print('$indent<${node.localName}/>');
+ } else {
+ print('$indent<${node.localName}>');
+ indent += ' ';
+ visitChildren(node);
+ indent = indent.substring(0, indent.length - 2);
+ print('$indent</${node.localName}>');
+ }
+ }
+
+ @override
+ void visitChildren(Node node) {
+ for (var child in node.nodes) {
+ visit(child);
+ }
+ }
+}
diff --git a/pkgs/html/lib/dom.dart b/pkgs/html/lib/dom.dart
new file mode 100644
index 0000000..0c6b38e
--- /dev/null
+++ b/pkgs/html/lib/dom.dart
@@ -0,0 +1,1120 @@
+/// A simple tree API that results from parsing html. Intended to be compatible
+/// with dart:html, but it is missing many types and APIs.
+library;
+
+// ignore_for_file: constant_identifier_names
+
+// TODO(jmesserly): lots to do here. Originally I wanted to generate this using
+// our Blink IDL generator, but another idea is to directly use the excellent
+// http://dom.spec.whatwg.org/ and http://html.spec.whatwg.org/ and just
+// implement that.
+
+import 'dart:collection';
+
+import 'package:source_span/source_span.dart';
+
+import 'dom_parsing.dart';
+import 'parser.dart';
+import 'src/constants.dart';
+import 'src/css_class_set.dart';
+import 'src/list_proxy.dart';
+import 'src/query_selector.dart' as query;
+import 'src/token.dart';
+import 'src/tokenizer.dart';
+
+export 'src/css_class_set.dart' show CssClassSet;
+
+// TODO(jmesserly): this needs to be replaced by an AttributeMap for attributes
+// that exposes namespace info.
+class AttributeName implements Comparable<Object> {
+ /// The namespace prefix, e.g. `xlink`.
+ final String? prefix;
+
+ /// The attribute name, e.g. `title`.
+ final String name;
+
+ /// The namespace url, e.g. `http://www.w3.org/1999/xlink`
+ final String namespace;
+
+ const AttributeName(this.prefix, this.name, this.namespace);
+
+ @override
+ String toString() {
+ // Implement:
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#serializing-html-fragments
+ // If we get here we know we are xml, xmlns, or xlink, because of
+ // [HtmlParser.adjustForeignAttriubtes] is the only place we create
+ // an AttributeName.
+ return prefix != null ? '$prefix:$name' : name;
+ }
+
+ @override
+ int get hashCode {
+ var h = prefix.hashCode;
+ h = 37 * (h & 0x1FFFFF) + name.hashCode;
+ h = 37 * (h & 0x1FFFFF) + namespace.hashCode;
+ return h & 0x3FFFFFFF;
+ }
+
+ @override
+ int compareTo(Object other) {
+ // Not sure about this sort order
+ if (other is! AttributeName) return 1;
+ var cmp = (prefix ?? '').compareTo(other.prefix ?? '');
+ if (cmp != 0) return cmp;
+ cmp = name.compareTo(other.name);
+ if (cmp != 0) return cmp;
+ return namespace.compareTo(other.namespace);
+ }
+
+ @override
+ bool operator ==(Object other) =>
+ other is AttributeName &&
+ prefix == other.prefix &&
+ name == other.name &&
+ namespace == other.namespace;
+}
+
+// http://dom.spec.whatwg.org/#parentnode
+mixin _ParentNode implements Node {
+ // TODO(jmesserly): this is only a partial implementation
+
+ /// Seaches for the first descendant node matching the given selectors, using
+ /// a preorder traversal.
+ ///
+ /// NOTE: Not all selectors from
+ /// [selectors level 4](http://dev.w3.org/csswg/selectors-4/)
+ /// are implemented. For example, nth-child does not implement An+B syntax
+ /// and *-of-type is not implemented. If a selector is not implemented this
+ /// method will throw [UnimplementedError].
+ Element? querySelector(String selector) =>
+ query.querySelector(this, selector);
+
+ /// Returns all descendant nodes matching the given selectors, using a
+ /// preorder traversal.
+ ///
+ /// NOTE: Not all selectors from
+ /// [selectors level 4](http://dev.w3.org/csswg/selectors-4/)
+ /// are implemented. For example, nth-child does not implement An+B syntax
+ /// and *-of-type is not implemented. If a selector is not implemented this
+ /// method will throw [UnimplementedError].
+ List<Element> querySelectorAll(String selector) =>
+ query.querySelectorAll(this, selector);
+}
+
+// http://dom.spec.whatwg.org/#interface-nonelementparentnode
+mixin _NonElementParentNode implements _ParentNode {
+ // TODO(jmesserly): could be faster, should throw on invalid id.
+ Element? getElementById(String id) => querySelector('#$id');
+}
+
+// This doesn't exist as an interface in the spec, but it's useful to merge
+// common methods from these:
+// http://dom.spec.whatwg.org/#interface-document
+// http://dom.spec.whatwg.org/#element
+abstract mixin class _ElementAndDocument implements _ParentNode {
+ // TODO(jmesserly): could be faster, should throw on invalid tag/class names.
+
+ List<Element> getElementsByTagName(String localName) =>
+ querySelectorAll(localName);
+
+ List<Element> getElementsByClassName(String classNames) =>
+ querySelectorAll(classNames.splitMapJoin(' ',
+ onNonMatch: (m) => m.isNotEmpty ? '.$m' : m, onMatch: (m) => ''));
+}
+
+/// Really basic implementation of a DOM-core like Node.
+abstract class Node {
+ static const int ATTRIBUTE_NODE = 2;
+ static const int CDATA_SECTION_NODE = 4;
+ static const int COMMENT_NODE = 8;
+ static const int DOCUMENT_FRAGMENT_NODE = 11;
+ static const int DOCUMENT_NODE = 9;
+ static const int DOCUMENT_TYPE_NODE = 10;
+ static const int ELEMENT_NODE = 1;
+ static const int ENTITY_NODE = 6;
+ static const int ENTITY_REFERENCE_NODE = 5;
+ static const int NOTATION_NODE = 12;
+ static const int PROCESSING_INSTRUCTION_NODE = 7;
+ static const int TEXT_NODE = 3;
+
+ /// The parent of the current node (or null for the document node).
+ Node? parentNode;
+
+ /// The parent element of this node.
+ ///
+ /// Returns null if this node either does not have a parent or its parent is
+ /// not an element.
+ Element? get parent {
+ final parentNode = this.parentNode;
+ return parentNode is Element ? parentNode : null;
+ }
+
+ // TODO(jmesserly): should move to Element.
+ /// A map holding name, value pairs for attributes of the node.
+ ///
+ /// Note that attribute order needs to be stable for serialization, so we use
+ /// a LinkedHashMap. Each key is a [String] or [AttributeName].
+ LinkedHashMap<Object, String> attributes = LinkedHashMap();
+
+ /// A list of child nodes of the current node. This must
+ /// include all elements but not necessarily other node types.
+ late final nodes = NodeList._(this);
+
+ late final List<Element> children = FilteredElementList(this);
+
+ // TODO(jmesserly): consider using an Expando for this, and put it in
+ // dom_parsing. Need to check the performance affect.
+ /// The source span of this node, if it was created by the [HtmlParser].
+ FileSpan? sourceSpan;
+
+ /// The attribute spans if requested. Otherwise null.
+ LinkedHashMap<Object, FileSpan>? _attributeSpans;
+ LinkedHashMap<Object, FileSpan>? _attributeValueSpans;
+
+ Node._();
+
+ /// If [sourceSpan] is available, this contains the spans of each attribute.
+ /// The span of an attribute is the entire attribute, including the name and
+ /// quotes (if any). For example, the span of "attr" in `<a attr="value">`
+ /// would be the text `attr="value"`.
+ LinkedHashMap<Object, FileSpan>? get attributeSpans {
+ _ensureAttributeSpans();
+ return _attributeSpans;
+ }
+
+ /// If [sourceSpan] is available, this contains the spans of each attribute's
+ /// value. Unlike [attributeSpans], this span will include only the value.
+ /// For example, the value span of "attr" in `<a attr="value">` would be the
+ /// text `value`.
+ LinkedHashMap<Object, FileSpan>? get attributeValueSpans {
+ _ensureAttributeSpans();
+ return _attributeValueSpans;
+ }
+
+ /// Returns a copy of this node.
+ ///
+ /// If [deep] is `true`, then all of this node's children and decendents are
+ /// copied as well. If [deep] is `false`, then only this node is copied.
+ Node clone(bool deep);
+
+ int get nodeType;
+
+ // http://domparsing.spec.whatwg.org/#extensions-to-the-element-interface
+ String get _outerHtml {
+ final str = StringBuffer();
+ _addOuterHtml(str);
+ return str.toString();
+ }
+
+ String get _innerHtml {
+ final str = StringBuffer();
+ _addInnerHtml(str);
+ return str.toString();
+ }
+
+ // Implemented per: http://dom.spec.whatwg.org/#dom-node-textcontent
+ String? get text => null;
+
+ set text(String? value) {}
+
+ void append(Node node) => nodes.add(node);
+
+ Node? get firstChild => nodes.isNotEmpty ? nodes[0] : null;
+
+ void _addOuterHtml(StringBuffer str);
+
+ void _addInnerHtml(StringBuffer str) {
+ for (var child in nodes) {
+ child._addOuterHtml(str);
+ }
+ }
+
+ Node remove() {
+ // TODO(jmesserly): is parent == null an error?
+ parentNode?.nodes.remove(this);
+ return this;
+ }
+
+ /// Insert [node] as a child of the current node, before [refNode] in the
+ void insertBefore(Node node, Node? refNode) {
+ if (refNode == null) {
+ nodes.add(node);
+ } else {
+ nodes.insert(nodes.indexOf(refNode), node);
+ }
+ }
+
+ /// Replaces this node with another node.
+ Node replaceWith(Node otherNode) {
+ if (parentNode == null) {
+ throw UnsupportedError('Node must have a parent to replace it.');
+ }
+ parentNode!.nodes[parentNode!.nodes.indexOf(this)] = otherNode;
+ return this;
+ }
+
+ // TODO(jmesserly): should this be a property or remove?
+ /// Return true if the node has children or text.
+ bool hasContent() => nodes.isNotEmpty;
+
+ /// Move all the children of the current node to [newParent].
+ /// This is needed so that trees that don't store text as nodes move the
+ /// text in the correct way.
+ void reparentChildren(Node newParent) {
+ newParent.nodes.addAll(nodes);
+ nodes.clear();
+ }
+
+ bool hasChildNodes() => nodes.isNotEmpty;
+
+ bool contains(Node node) => nodes.contains(node);
+
+ /// Initialize [attributeSpans] using [sourceSpan].
+ void _ensureAttributeSpans() {
+ if (_attributeSpans != null) return;
+
+ final attributeSpans = _attributeSpans = LinkedHashMap<Object, FileSpan>();
+ final attributeValueSpans =
+ _attributeValueSpans = LinkedHashMap<Object, FileSpan>();
+
+ if (sourceSpan == null) return;
+
+ final tokenizer = HtmlTokenizer(sourceSpan!.text,
+ generateSpans: true, attributeSpans: true);
+
+ tokenizer.moveNext();
+ final token = tokenizer.current as StartTagToken;
+
+ if (token.attributeSpans == null) return; // no attributes
+
+ for (var attr in token.attributeSpans!) {
+ final offset = sourceSpan!.start.offset;
+ final name = attr.name!;
+ attributeSpans[name] =
+ sourceSpan!.file.span(offset + attr.start, offset + attr.end);
+ if (attr.startValue != null) {
+ attributeValueSpans[name] = sourceSpan!.file
+ .span(offset + attr.startValue!, offset + attr.endValue);
+ }
+ }
+ }
+
+ T _clone<T extends Node>(T shallowClone, bool deep) {
+ if (deep) {
+ for (var child in nodes) {
+ shallowClone.append(child.clone(true));
+ }
+ }
+ return shallowClone;
+ }
+}
+
+class Document extends Node
+ with _ParentNode, _NonElementParentNode, _ElementAndDocument {
+ Document() : super._();
+
+ factory Document.html(String html) => parse(html);
+
+ @override
+ int get nodeType => Node.DOCUMENT_NODE;
+
+ // TODO(jmesserly): optmize this if needed
+ Element? get documentElement => querySelector('html');
+
+ Element? get head => documentElement?.querySelector('head');
+
+ Element? get body => documentElement?.querySelector('body');
+
+ /// Returns a fragment of HTML or XML that represents the element and its
+ /// contents.
+ // TODO(jmesserly): this API is not specified in:
+ // <http://domparsing.spec.whatwg.org/> nor is it in dart:html, instead
+ // only Element has outerHtml. However it is quite useful. Should we move it
+ // to dom_parsing, where we keep other custom APIs?
+ String get outerHtml => _outerHtml;
+
+ @override
+ String toString() => '#document';
+
+ @override
+ void _addOuterHtml(StringBuffer str) => _addInnerHtml(str);
+
+ @override
+ Document clone(bool deep) => _clone(Document(), deep);
+
+ Element createElement(String tag) => Element.tag(tag);
+
+ // TODO(jmesserly): this is only a partial implementation of:
+ // http://dom.spec.whatwg.org/#dom-document-createelementns
+ Element createElementNS(String? namespaceUri, String? tag) {
+ if (namespaceUri == '') namespaceUri = null;
+ return Element._(tag, namespaceUri);
+ }
+
+ DocumentFragment createDocumentFragment() => DocumentFragment();
+}
+
+class DocumentFragment extends Node with _ParentNode, _NonElementParentNode {
+ DocumentFragment() : super._();
+
+ factory DocumentFragment.html(String html) => parseFragment(html);
+
+ @override
+ int get nodeType => Node.DOCUMENT_FRAGMENT_NODE;
+
+ /// Returns a fragment of HTML or XML that represents the element and its
+ /// contents.
+ // TODO(jmesserly): this API is not specified in:
+ // <http://domparsing.spec.whatwg.org/> nor is it in dart:html, instead
+ // only Element has outerHtml. However it is quite useful. Should we move it
+ // to dom_parsing, where we keep other custom APIs?
+ String get outerHtml => _outerHtml;
+
+ @override
+ String toString() => '#document-fragment';
+
+ @override
+ DocumentFragment clone(bool deep) => _clone(DocumentFragment(), deep);
+
+ @override
+ void _addOuterHtml(StringBuffer str) => _addInnerHtml(str);
+
+ @override
+ String? get text => _getText(this);
+
+ @override
+ set text(String? value) => _setText(this, value);
+}
+
+class DocumentType extends Node {
+ final String? name;
+ final String? publicId;
+ final String? systemId;
+
+ DocumentType(this.name, this.publicId, this.systemId) : super._();
+
+ @override
+ int get nodeType => Node.DOCUMENT_TYPE_NODE;
+
+ @override
+ String toString() {
+ if (publicId != null || systemId != null) {
+ // TODO(jmesserly): the html5 serialization spec does not add these. But
+ // it seems useful, and the parser can handle it, so for now keeping it.
+ final pid = publicId ?? '';
+ final sid = systemId ?? '';
+ return '<!DOCTYPE $name "$pid" "$sid">';
+ } else {
+ return '<!DOCTYPE $name>';
+ }
+ }
+
+ @override
+ void _addOuterHtml(StringBuffer str) {
+ str.write(toString());
+ }
+
+ @override
+ DocumentType clone(bool deep) => DocumentType(name, publicId, systemId);
+}
+
+class Text extends Node {
+ /// The text node's data, stored as either a String or StringBuffer.
+ /// We support storing a StringBuffer here to support fast [appendData].
+ /// It will flatten back to a String on read.
+ Object _data;
+
+ Text(String? data)
+ : _data = data ?? '',
+ super._();
+
+ @override
+ int get nodeType => Node.TEXT_NODE;
+
+ String get data => _data = _data.toString();
+
+ set data(String value) {
+ // Handle unsound null values.
+ _data = identical(value, null) ? '' : value;
+ }
+
+ @override
+ String toString() => '"$data"';
+
+ @override
+ void _addOuterHtml(StringBuffer str) => writeTextNodeAsHtml(str, this);
+
+ @override
+ Text clone(bool deep) => Text(data);
+
+ void appendData(String data) {
+ if (_data is! StringBuffer) _data = StringBuffer(_data);
+ final sb = _data as StringBuffer;
+ sb.write(data);
+ }
+
+ @override
+ String get text => data;
+
+ @override
+ // Text has a non-nullable `text` field, while Node has a nullable field.
+ set text(covariant String value) {
+ data = value;
+ }
+}
+
+// TODO(jmesserly): Elements should have a pointer back to their document
+class Element extends Node with _ParentNode, _ElementAndDocument {
+ final String? namespaceUri;
+
+ /// The [local name](http://dom.spec.whatwg.org/#concept-element-local-name)
+ /// of this element.
+ final String? localName;
+
+ // TODO(jmesserly): consider using an Expando for this, and put it in
+ // dom_parsing. Need to check the performance affect.
+ /// The source span of the end tag this element, if it was created by the
+ /// [HtmlParser]. May be `null` if does not have an implicit end tag.
+ FileSpan? endSourceSpan;
+
+ Element._(this.localName, [this.namespaceUri]) : super._();
+
+ Element.tag(this.localName)
+ : namespaceUri = Namespaces.html,
+ super._();
+
+ static final _startTagRegexp = RegExp('<(\\w+)');
+
+ static final _customParentTagMap = const {
+ 'body': 'html',
+ 'head': 'html',
+ 'caption': 'table',
+ 'td': 'tr',
+ 'colgroup': 'table',
+ 'col': 'colgroup',
+ 'tr': 'tbody',
+ 'tbody': 'table',
+ 'tfoot': 'table',
+ 'thead': 'table',
+ 'track': 'audio',
+ };
+
+ // TODO(jmesserly): this is from dart:html _ElementFactoryProvider...
+ // TODO(jmesserly): have a look at fixing some things in dart:html, in
+ // particular: is the parent tag map complete? Is it faster without regexp?
+ // TODO(jmesserly): for our version we can do something smarter in the parser.
+ // All we really need is to set the correct parse state.
+ factory Element.html(String html) {
+ // TODO(jacobr): this method can be made more robust and performant.
+ // 1) Cache the dummy parent elements required to use innerHTML rather than
+ // creating them every call.
+ // 2) Verify that the html does not contain leading or trailing text nodes.
+ // 3) Verify that the html does not contain both <head> and <body> tags.
+ // 4) Detach the created element from its dummy parent.
+ var parentTag = 'div';
+ String? tag;
+ final match = _startTagRegexp.firstMatch(html);
+ if (match != null) {
+ tag = match.group(1)!.toLowerCase();
+ if (_customParentTagMap.containsKey(tag)) {
+ parentTag = _customParentTagMap[tag]!;
+ }
+ }
+
+ final fragment = parseFragment(html, container: parentTag);
+ Element element;
+ if (fragment.children.length == 1) {
+ element = fragment.children[0];
+ } else if (parentTag == 'html' && fragment.children.length == 2) {
+ // You'll always get a head and a body when starting from html.
+ element = fragment.children[tag == 'head' ? 0 : 1];
+ } else {
+ throw ArgumentError('HTML had ${fragment.children.length} '
+ 'top level elements but 1 expected');
+ }
+ element.remove();
+ return element;
+ }
+
+ @override
+ int get nodeType => Node.ELEMENT_NODE;
+
+ // TODO(jmesserly): we can make this faster
+ Element? get previousElementSibling {
+ if (parentNode == null) return null;
+ final siblings = parentNode!.nodes;
+ for (var i = siblings.indexOf(this) - 1; i >= 0; i--) {
+ final s = siblings[i];
+ if (s is Element) return s;
+ }
+ return null;
+ }
+
+ Element? get nextElementSibling {
+ final parentNode = this.parentNode;
+ if (parentNode == null) return null;
+ final siblings = parentNode.nodes;
+ for (var i = siblings.indexOf(this) + 1; i < siblings.length; i++) {
+ final s = siblings[i];
+ if (s is Element) return s;
+ }
+ return null;
+ }
+
+ @override
+ String toString() {
+ final prefix = Namespaces.getPrefix(namespaceUri);
+ return "<${prefix == null ? '' : '$prefix '}$localName>";
+ }
+
+ @override
+ String get text => _getText(this);
+
+ @override
+ set text(String? value) => _setText(this, value);
+
+ /// Returns a fragment of HTML or XML that represents the element and its
+ /// contents.
+ String get outerHtml => _outerHtml;
+
+ /// Returns a fragment of HTML or XML that represents the element's contents.
+ /// Can be set, to replace the contents of the element with nodes parsed from
+ /// the given string.
+ String get innerHtml => _innerHtml;
+
+ // TODO(jmesserly): deprecate in favor of:
+ // <https://api.dartlang.org/apidocs/channels/stable/#dart-dom-html.Element@id_setInnerHtml>
+ set innerHtml(String value) {
+ nodes.clear();
+ // TODO(jmesserly): should be able to get the same effect by adding the
+ // fragment directly.
+ nodes.addAll(parseFragment(value, container: localName!).nodes);
+ }
+
+ @override
+ void _addOuterHtml(StringBuffer str) {
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#serializing-html-fragments
+ // Element is the most complicated one.
+ str.write('<');
+ str.write(_getSerializationPrefix(namespaceUri));
+ str.write(localName);
+
+ if (attributes.isNotEmpty) {
+ attributes.forEach((key, v) {
+ // Note: AttributeName.toString handles serialization of attribute
+ // namespace, if needed.
+ str.write(' ');
+ str.write(key);
+ str.write('="');
+ str.write(htmlSerializeEscape(v, attributeMode: true));
+ str.write('"');
+ });
+ }
+
+ str.write('>');
+
+ if (nodes.isNotEmpty) {
+ if (localName == 'pre' ||
+ localName == 'textarea' ||
+ localName == 'listing') {
+ final first = nodes[0];
+ if (first is Text && first.data.startsWith('\n')) {
+ // These nodes will remove a leading \n at parse time, so if we still
+ // have one, it means we started with two. Add it back.
+ str.write('\n');
+ }
+ }
+
+ _addInnerHtml(str);
+ }
+
+ // void elements must not have an end tag
+ // http://dev.w3.org/html5/markup/syntax.html#void-elements
+ if (!isVoidElement(localName)) str.write('</$localName>');
+ }
+
+ static String _getSerializationPrefix(String? uri) {
+ if (uri == null ||
+ uri == Namespaces.html ||
+ uri == Namespaces.mathml ||
+ uri == Namespaces.svg) {
+ return '';
+ }
+ final prefix = Namespaces.getPrefix(uri);
+ // TODO(jmesserly): the spec doesn't define "qualified name".
+ // I'm not sure if this is correct, but it should parse reasonably.
+ return prefix == null ? '' : '$prefix:';
+ }
+
+ @override
+ Element clone(bool deep) {
+ final result = Element._(localName, namespaceUri)
+ ..attributes = LinkedHashMap.from(attributes);
+ return _clone(result, deep);
+ }
+
+ // http://dom.spec.whatwg.org/#dom-element-id
+ String get id {
+ final result = attributes['id'];
+ return result ?? '';
+ }
+
+ set id(String value) {
+ attributes['id'] = value;
+ }
+
+ // http://dom.spec.whatwg.org/#dom-element-classname
+ String get className {
+ final result = attributes['class'];
+ return result ?? '';
+ }
+
+ set className(String value) {
+ attributes['class'] = value;
+ }
+
+ /// The set of CSS classes applied to this element.
+ ///
+ /// This set makes it easy to add, remove or toggle the classes applied to
+ /// this element.
+ ///
+ /// element.classes.add('selected');
+ /// element.classes.toggle('isOnline');
+ /// element.classes.remove('selected');
+ CssClassSet get classes => ElementCssClassSet(this);
+}
+
+class Comment extends Node {
+ String? data;
+
+ Comment(this.data) : super._();
+
+ @override
+ int get nodeType => Node.COMMENT_NODE;
+
+ @override
+ String toString() => '<!-- $data -->';
+
+ @override
+ void _addOuterHtml(StringBuffer str) {
+ str.write('<!--$data-->');
+ }
+
+ @override
+ Comment clone(bool deep) => Comment(data);
+
+ @override
+ String? get text => data;
+
+ @override
+ set text(String? value) {
+ data = value;
+ }
+}
+
+// TODO(jmesserly): fix this to extend one of the corelib classes if possible.
+// (The requirement to remove the node from the old node list makes it tricky.)
+// TODO(jmesserly): is there any way to share code with the _NodeListImpl?
+class NodeList extends ListProxy<Node> {
+ final Node _parent;
+
+ NodeList._(this._parent);
+
+ Node _setParent(Node node) {
+ // Note: we need to remove the node from its previous parent node, if any,
+ // before updating its parent pointer to point at our parent.
+ node.remove();
+ node.parentNode = _parent;
+ return node;
+ }
+
+ @override
+ void add(Node element) {
+ if (element is DocumentFragment) {
+ addAll(element.nodes);
+ } else {
+ super.add(_setParent(element));
+ }
+ }
+
+ void addLast(Node value) => add(value);
+
+ @override
+ void addAll(Iterable<Node> iterable) {
+ // Note: we need to be careful if collection is another NodeList.
+ // In particular:
+ // 1. we need to copy the items before updating their parent pointers,
+ // _flattenDocFragments does a copy internally.
+ // 2. we should update parent pointers in reverse order. That way they
+ // are removed from the original NodeList (if any) from the end, which
+ // is faster.
+ final list = _flattenDocFragments(iterable);
+ for (var node in list.reversed) {
+ _setParent(node);
+ }
+ super.addAll(list);
+ }
+
+ @override
+ void insert(int index, Node element) {
+ if (element is DocumentFragment) {
+ insertAll(index, element.nodes);
+ } else {
+ super.insert(index, _setParent(element));
+ }
+ }
+
+ @override
+ Node removeLast() => super.removeLast()..parentNode = null;
+
+ @override
+ Node removeAt(int index) => super.removeAt(index)..parentNode = null;
+
+ @override
+ void clear() {
+ for (var node in this) {
+ node.parentNode = null;
+ }
+ super.clear();
+ }
+
+ @override
+ void operator []=(int index, Node value) {
+ if (value is DocumentFragment) {
+ removeAt(index);
+ insertAll(index, value.nodes);
+ } else {
+ this[index].parentNode = null;
+ super[index] = _setParent(value);
+ }
+ }
+
+ // TODO(jmesserly): These aren't implemented in DOM _NodeListImpl, see
+ // http://code.google.com/p/dart/issues/detail?id=5371
+ @override
+ void setRange(int start, int end, Iterable<Node> iterable,
+ [int skipCount = 0]) {
+ var fromVar = iterable as List<Node>;
+ if (fromVar is NodeList) {
+ // Note: this is presumed to make a copy
+ fromVar = fromVar.sublist(skipCount, skipCount + end);
+ }
+ // Note: see comment in [addAll]. We need to be careful about the order of
+ // operations if [from] is also a NodeList.
+ for (var i = end - 1; i >= 0; i--) {
+ this[start + i] = fromVar[skipCount + i];
+ }
+ }
+
+ @override
+ void replaceRange(int start, int end, Iterable<Node> newContents) {
+ removeRange(start, end);
+ insertAll(start, newContents);
+ }
+
+ @override
+ void removeRange(int start, int end) {
+ for (var i = start; i < end; i++) {
+ this[i].parentNode = null;
+ }
+ super.removeRange(start, end);
+ }
+
+ @override
+ void removeWhere(bool Function(Node) test) {
+ for (var node in where(test)) {
+ node.parentNode = null;
+ }
+ super.removeWhere(test);
+ }
+
+ @override
+ void retainWhere(bool Function(Node) test) {
+ for (var node in where((n) => !test(n))) {
+ node.parentNode = null;
+ }
+ super.retainWhere(test);
+ }
+
+ @override
+ void insertAll(int index, Iterable<Node> iterable) {
+ // Note: we need to be careful how we copy nodes. See note in addAll.
+ final list = _flattenDocFragments(iterable);
+ for (var node in list.reversed) {
+ _setParent(node);
+ }
+ super.insertAll(index, list);
+ }
+
+ List<Node> _flattenDocFragments(Iterable<Node> collection) {
+ // Note: this function serves two purposes:
+ // * it flattens document fragments
+ // * it creates a copy of [collections] when `collection is NodeList`.
+ final result = <Node>[];
+ for (var node in collection) {
+ if (node is DocumentFragment) {
+ result.addAll(node.nodes);
+ } else {
+ result.add(node);
+ }
+ }
+ return result;
+ }
+}
+
+/// An indexable collection of a node's descendants in the document tree,
+/// filtered so that only elements are in the collection.
+// TODO(jmesserly): this was copied from dart:html
+// TODO(jmesserly): "implements List<Element>" is a workaround for analyzer bug.
+class FilteredElementList extends IterableBase<Element>
+ with ListMixin<Element>
+ implements List<Element> {
+ final List<Node> _childNodes;
+
+ /// Creates a collection of the elements that descend from a node.
+ ///
+ /// Example usage:
+ ///
+ /// var filteredElements = new FilteredElementList(query("#container"));
+ /// // filteredElements is [a, b, c].
+ FilteredElementList(Node node) : _childNodes = node.nodes;
+
+ // We can't memoize this, since it's possible that children will be messed
+ // with externally to this class.
+ //
+ // TODO(nweiz): we don't always need to create a new list. For example
+ // forEach, every, any, ... could directly work on the _childNodes.
+ List<Element> get _filtered =>
+ _childNodes.whereType<Element>().toList(growable: false);
+
+ @override
+ void forEach(void Function(Element) action) {
+ _filtered.forEach(action);
+ }
+
+ @override
+ void operator []=(int index, Element value) {
+ this[index].replaceWith(value);
+ }
+
+ @override
+ set length(int newLength) {
+ final len = length;
+ if (newLength >= len) {
+ return;
+ } else if (newLength < 0) {
+ throw ArgumentError('Invalid list length');
+ }
+
+ removeRange(newLength, len);
+ }
+
+ @override
+ String join([String separator = '']) => _filtered.join(separator);
+
+ @override
+ void add(Element element) {
+ _childNodes.add(element);
+ }
+
+ @override
+ void addAll(Iterable<Element> iterable) {
+ for (var element in iterable) {
+ add(element);
+ }
+ }
+
+ @override
+ bool contains(Object? element) {
+ return element is Element && _childNodes.contains(element);
+ }
+
+ @override
+ Iterable<Element> get reversed => _filtered.reversed;
+
+ @override
+ void sort([int Function(Element, Element)? compare]) {
+ throw UnsupportedError('TODO(jacobr): should we impl?');
+ }
+
+ @override
+ void setRange(int start, int end, Iterable<Element> iterable,
+ [int skipCount = 0]) {
+ throw UnimplementedError();
+ }
+
+ @override
+ void fillRange(int start, int end, [Element? fill]) {
+ throw UnimplementedError();
+ }
+
+ @override
+ void replaceRange(int start, int end, Iterable<Element> newContents) {
+ throw UnimplementedError();
+ }
+
+ @override
+ void removeRange(int start, int end) {
+ _filtered.sublist(start, end).forEach((el) => el.remove());
+ }
+
+ @override
+ void clear() {
+ // Currently, ElementList#clear clears even non-element nodes, so we follow
+ // that behavior.
+ _childNodes.clear();
+ }
+
+ @override
+ Element removeLast() {
+ return last..remove();
+ }
+
+ @override
+ Iterable<T> map<T>(T Function(Element) f) => _filtered.map(f);
+
+ @override
+ Iterable<Element> where(bool Function(Element) test) => _filtered.where(test);
+
+ @override
+ Iterable<T> expand<T>(Iterable<T> Function(Element) f) => _filtered.expand(f);
+
+ @override
+ void insert(int index, Element element) {
+ _childNodes.insert(index, element);
+ }
+
+ @override
+ void insertAll(int index, Iterable<Element> iterable) {
+ _childNodes.insertAll(index, iterable);
+ }
+
+ @override
+ Element removeAt(int index) {
+ final result = this[index];
+ result.remove();
+ return result;
+ }
+
+ @override
+ bool remove(Object? element) {
+ if (element is! Element) return false;
+ for (var i = 0; i < length; i++) {
+ final indexElement = this[i];
+ if (identical(indexElement, element)) {
+ indexElement.remove();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @override
+ Element reduce(Element Function(Element, Element) combine) {
+ return _filtered.reduce(combine);
+ }
+
+ @override
+ T fold<T>(
+ T initialValue, T Function(T previousValue, Element element) combine) {
+ return _filtered.fold(initialValue, combine);
+ }
+
+ @override
+ bool every(bool Function(Element) test) => _filtered.every(test);
+
+ @override
+ bool any(bool Function(Element) test) => _filtered.any(test);
+
+ @override
+ List<Element> toList({bool growable = true}) =>
+ List<Element>.of(this, growable: growable);
+
+ @override
+ Set<Element> toSet() => Set<Element>.from(this);
+
+ @override
+ Element firstWhere(bool Function(Element) test,
+ {Element Function()? orElse}) {
+ return _filtered.firstWhere(test, orElse: orElse);
+ }
+
+ @override
+ Element lastWhere(bool Function(Element) test, {Element Function()? orElse}) {
+ return _filtered.lastWhere(test, orElse: orElse);
+ }
+
+ @override
+ Element singleWhere(bool Function(Element) test,
+ {Element Function()? orElse}) {
+ if (orElse != null) throw UnimplementedError('orElse');
+ return _filtered.singleWhere(test);
+ }
+
+ @override
+ Element elementAt(int index) {
+ return this[index];
+ }
+
+ @override
+ bool get isEmpty => _filtered.isEmpty;
+
+ @override
+ int get length => _filtered.length;
+
+ @override
+ Element operator [](int index) => _filtered[index];
+
+ @override
+ Iterator<Element> get iterator => _filtered.iterator;
+
+ @override
+ List<Element> sublist(int start, [int? end]) => _filtered.sublist(start, end);
+
+ @override
+ Iterable<Element> getRange(int start, int end) =>
+ _filtered.getRange(start, end);
+
+ @override
+ int indexOf(Object? element, [int start = 0]) =>
+ // Cast forced by ListMixin https://github.com/dart-lang/sdk/issues/31311
+ _filtered.indexOf(element as Element, start);
+
+ @override
+ int lastIndexOf(Object? element, [int? start]) {
+ start ??= length - 1;
+ // Cast forced by ListMixin https://github.com/dart-lang/sdk/issues/31311
+ return _filtered.lastIndexOf(element as Element, start);
+ }
+
+ @override
+ Element get first => _filtered.first;
+
+ @override
+ Element get last => _filtered.last;
+
+ @override
+ Element get single => _filtered.single;
+}
+
+// http://dom.spec.whatwg.org/#dom-node-textcontent
+// For Element and DocumentFragment
+String _getText(Node node) => (_ConcatTextVisitor()..visit(node)).toString();
+
+void _setText(Node node, String? value) {
+ node.nodes.clear();
+ node.append(Text(value));
+}
+
+class _ConcatTextVisitor extends TreeVisitor {
+ final _str = StringBuffer();
+
+ @override
+ String toString() => _str.toString();
+
+ @override
+ void visitText(Text node) {
+ _str.write(node.data);
+ }
+}
diff --git a/pkgs/html/lib/dom_parsing.dart b/pkgs/html/lib/dom_parsing.dart
new file mode 100644
index 0000000..69b0bbd
--- /dev/null
+++ b/pkgs/html/lib/dom_parsing.dart
@@ -0,0 +1,157 @@
+/// This library contains extra APIs that aren't in the DOM, but are useful
+/// when interacting with the parse tree.
+library;
+
+import 'dom.dart';
+import 'html_escape.dart';
+import 'src/constants.dart' show rcdataElements;
+
+// Export a function which was previously declared here.
+export 'html_escape.dart';
+
+/// A simple tree visitor for the DOM nodes.
+class TreeVisitor {
+ void visit(Node node) {
+ return switch (node.nodeType) {
+ Node.ELEMENT_NODE => visitElement(node as Element),
+ Node.TEXT_NODE => visitText(node as Text),
+ Node.COMMENT_NODE => visitComment(node as Comment),
+ Node.DOCUMENT_FRAGMENT_NODE =>
+ visitDocumentFragment(node as DocumentFragment),
+ Node.DOCUMENT_NODE => visitDocument(node as Document),
+ Node.DOCUMENT_TYPE_NODE => visitDocumentType(node as DocumentType),
+ _ => throw UnsupportedError('DOM node type ${node.nodeType}')
+ };
+ }
+
+ void visitChildren(Node node) {
+ // Allow for mutations (remove works) while iterating.
+ for (var child in node.nodes.toList(growable: false)) {
+ visit(child);
+ }
+ }
+
+ /// The fallback handler if the more specific visit method hasn't been
+ /// overriden. Only use this from a subclass of [TreeVisitor], otherwise
+ /// call [visit] instead.
+ void visitNodeFallback(Node node) => visitChildren(node);
+
+ void visitDocument(Document node) => visitNodeFallback(node);
+
+ void visitDocumentType(DocumentType node) => visitNodeFallback(node);
+
+ void visitText(Text node) => visitNodeFallback(node);
+
+ // TODO(jmesserly): visit attributes.
+ void visitElement(Element node) => visitNodeFallback(node);
+
+ void visitComment(Comment node) => visitNodeFallback(node);
+
+ void visitDocumentFragment(DocumentFragment node) => visitNodeFallback(node);
+}
+
+/// Converts the DOM tree into an HTML string with code markup suitable for
+/// displaying the HTML's source code with CSS colors for different parts of the
+/// markup. See also [CodeMarkupVisitor].
+String htmlToCodeMarkup(Node node) {
+ return (CodeMarkupVisitor()..visit(node)).toString();
+}
+
+/// Converts the DOM tree into an HTML string with code markup suitable for
+/// displaying the HTML's source code with CSS colors for different parts of the
+/// markup. See also [htmlToCodeMarkup].
+class CodeMarkupVisitor extends TreeVisitor {
+ final StringBuffer _str;
+
+ CodeMarkupVisitor() : _str = StringBuffer();
+
+ @override
+ String toString() => _str.toString();
+
+ @override
+ void visitDocument(Document node) {
+ _str.write('<pre>');
+ visitChildren(node);
+ _str.write('</pre>');
+ }
+
+ @override
+ void visitDocumentType(DocumentType node) {
+ _str.write('<code class="markup doctype"><!DOCTYPE ${node.name}>'
+ '</code>');
+ }
+
+ @override
+ void visitText(Text node) {
+ writeTextNodeAsHtml(_str, node);
+ }
+
+ @override
+ void visitElement(Element node) {
+ final tag = node.localName;
+ _str.write('<<code class="markup element-name">$tag</code>');
+ if (node.attributes.isNotEmpty) {
+ node.attributes.forEach((key, v) {
+ v = htmlSerializeEscape(v, attributeMode: true);
+ _str.write(' <code class="markup attribute-name">$key</code>'
+ '=<code class="markup attribute-value">"$v"</code>');
+ });
+ }
+ if (node.nodes.isNotEmpty) {
+ _str.write('>');
+ visitChildren(node);
+ } else if (isVoidElement(tag)) {
+ _str.write('>');
+ return;
+ }
+ _str.write('</<code class="markup element-name">$tag</code>>');
+ }
+
+ @override
+ void visitComment(Comment node) {
+ final data = htmlSerializeEscape(node.data!);
+ _str.write('<code class="markup comment"><!--$data--></code>');
+ }
+}
+
+/// Returns true if this tag name is a void element.
+/// This method is useful to a pretty printer, because void elements must not
+/// have an end tag.
+/// See also: <http://dev.w3.org/html5/markup/syntax.html#void-elements>.
+bool isVoidElement(String? tagName) {
+ switch (tagName) {
+ case 'area':
+ case 'base':
+ case 'br':
+ case 'col':
+ case 'command':
+ case 'embed':
+ case 'hr':
+ case 'img':
+ case 'input':
+ case 'keygen':
+ case 'link':
+ case 'meta':
+ case 'param':
+ case 'source':
+ case 'track':
+ case 'wbr':
+ return true;
+ }
+ return false;
+}
+
+/// Serialize text node according to:
+/// <http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#html-fragment-serialization-algorithm>
+void writeTextNodeAsHtml(StringBuffer str, Text node) {
+ // Don't escape text for certain elements, notably <script>.
+ final parent = node.parentNode;
+ if (parent is Element) {
+ final tag = parent.localName;
+ if (rcdataElements.contains(tag) || tag == 'plaintext') {
+ str.write(node.data);
+ return;
+ }
+ }
+ str.write(htmlSerializeEscape(node.data));
+}
diff --git a/pkgs/html/lib/html_escape.dart b/pkgs/html/lib/html_escape.dart
new file mode 100644
index 0000000..b9c20c8
--- /dev/null
+++ b/pkgs/html/lib/html_escape.dart
@@ -0,0 +1,51 @@
+// TODO(jmesserly): reconcile this with dart:web htmlEscape.
+// This one might be more useful, as it is HTML5 spec compliant.
+/// Escapes [text] for use in the
+/// [HTML fragment serialization algorithm][1]. In particular, as described
+/// in the [specification][2]:
+///
+/// - Replace any occurrence of the `&` character by the string `&`.
+/// - Replace any occurrences of the U+00A0 NO-BREAK SPACE character by the
+/// string ` `.
+/// - If the algorithm was invoked in [attributeMode], replace any occurrences
+/// of the `"` character by the string `"`.
+/// - If the algorithm was not invoked in [attributeMode], replace any
+/// occurrences of the `<` character by the string `<`, and any occurrences
+/// of the `>` character by the string `>`.
+///
+/// [1]: http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#serializing-html-fragments
+/// [2]: http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#escapingString
+String htmlSerializeEscape(String text, {bool attributeMode = false}) {
+ // TODO(jmesserly): is it faster to build up a list of codepoints?
+ // StringBuffer seems cleaner assuming Dart can unbox 1-char strings.
+ StringBuffer? result;
+ for (var i = 0; i < text.length; i++) {
+ final ch = text[i];
+ String? replace;
+ switch (ch) {
+ case '&':
+ replace = '&';
+ break;
+ case '\u00A0' /*NO-BREAK SPACE*/ :
+ replace = ' ';
+ break;
+ case '"':
+ if (attributeMode) replace = '"';
+ break;
+ case '<':
+ if (!attributeMode) replace = '<';
+ break;
+ case '>':
+ if (!attributeMode) replace = '>';
+ break;
+ }
+ if (replace != null) {
+ result ??= StringBuffer(text.substring(0, i));
+ result.write(replace);
+ } else if (result != null) {
+ result.write(ch);
+ }
+ }
+
+ return result != null ? result.toString() : text;
+}
diff --git a/pkgs/html/lib/parser.dart b/pkgs/html/lib/parser.dart
new file mode 100644
index 0000000..0e89e66
--- /dev/null
+++ b/pkgs/html/lib/parser.dart
@@ -0,0 +1,3984 @@
+/// This library has a parser for HTML5 documents, that lets you parse HTML
+/// easily from a script or server side application:
+///
+/// import 'package:html/parser.dart' show parse;
+/// import 'package:html/dom.dart';
+/// main() {
+/// var document = parse(
+/// '<body>Hello world! <a href="www.html5rocks.com">HTML5 rocks!');
+/// print(document.outerHtml);
+/// }
+///
+/// The resulting document you get back has a DOM-like API for easy tree
+/// traversal and manipulation.
+library;
+
+import 'dart:collection';
+import 'dart:math';
+
+import 'package:source_span/source_span.dart';
+
+import 'dom.dart';
+import 'src/constants.dart';
+import 'src/encoding_parser.dart';
+import 'src/token.dart';
+import 'src/tokenizer.dart';
+import 'src/treebuilder.dart';
+import 'src/utils.dart';
+
+/// Parse an html5 document into a tree.
+///
+/// The [input] can be a `String`, a `List<int>` of bytes, or an
+/// [HtmlTokenizer].
+///
+/// If [input] is not a [HtmlTokenizer], you can optionally specify the file's
+/// [encoding], which must be a string. If specified that encoding will be
+/// used regardless of any BOM or later declaration (such as in a meta element).
+///
+/// Set [generateSpans] if you want to generate [SourceSpan]s, otherwise the
+/// [Node.sourceSpan] property will be `null`. When using [generateSpans] you
+/// can additionally pass [sourceUrl] to indicate where the [input] was
+/// extracted from.
+Document parse(dynamic input,
+ {String? encoding, bool generateSpans = false, String? sourceUrl}) {
+ final p = HtmlParser(input,
+ encoding: encoding, generateSpans: generateSpans, sourceUrl: sourceUrl);
+ return p.parse();
+}
+
+/// Parse an html5 document fragment into a tree.
+///
+/// The [input] can be a `String`, a `List<int>` of bytes, or an
+/// [HtmlTokenizer].
+/// The [container] element can optionally be specified, otherwise it defaults
+/// to "div".
+///
+/// If [input] is not a [HtmlTokenizer], you can optionally specify the file's
+/// [encoding], which must be a string. If specified, that encoding will be used,
+/// regardless of any BOM or later declaration (such as in a meta element).
+///
+/// Set [generateSpans] if you want to generate [SourceSpan]s, otherwise the
+/// [Node.sourceSpan] property will be `null`. When using [generateSpans] you can
+/// additionally pass [sourceUrl] to indicate where the [input] was extracted
+/// from.
+DocumentFragment parseFragment(dynamic input,
+ {String container = 'div',
+ String? encoding,
+ bool generateSpans = false,
+ String? sourceUrl}) {
+ final p = HtmlParser(input,
+ encoding: encoding, generateSpans: generateSpans, sourceUrl: sourceUrl);
+ return p.parseFragment(container);
+}
+
+/// Parser for HTML, which generates a tree structure from a stream of
+/// (possibly malformed) characters.
+class HtmlParser {
+ /// Raise an exception on the first error encountered.
+ final bool strict;
+
+ /// True to generate [SourceSpan]s for the [Node.sourceSpan] property.
+ final bool generateSpans;
+
+ final HtmlTokenizer tokenizer;
+
+ final TreeBuilder tree;
+
+ final List<ParseError> errors = <ParseError>[];
+
+ bool firstStartTag = false;
+
+ // TODO(jmesserly): use enum?
+ /// "quirks" / "limited quirks" / "no quirks"
+ String compatMode = 'no quirks';
+
+ /// innerHTML container when parsing document fragment.
+ String? innerHTML;
+
+ late Phase phase = _initialPhase;
+
+ Phase? originalPhase;
+
+ bool framesetOK = true;
+
+ // These fields hold the different phase singletons. At any given time one
+ // of them will be active.
+ late final _initialPhase = InitialPhase(this);
+ late final _beforeHtmlPhase = BeforeHtmlPhase(this);
+ late final _beforeHeadPhase = BeforeHeadPhase(this);
+ late final _inHeadPhase = InHeadPhase(this);
+ // TODO: html5lib did not implement the no script parsing mode
+ // More information here:
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#scripting-flag
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#parsing-main-inheadnoscript
+ // late final _inHeadNoscript = InHeadNoScriptPhase(this);
+ late final _afterHeadPhase = AfterHeadPhase(this);
+ late final _inBodyPhase = InBodyPhase(this);
+ late final _textPhase = TextPhase(this);
+ late final _inTablePhase = InTablePhase(this);
+ late final _inTableTextPhase = InTableTextPhase(this);
+ late final _inCaptionPhase = InCaptionPhase(this);
+ late final _inColumnGroupPhase = InColumnGroupPhase(this);
+ late final _inTableBodyPhase = InTableBodyPhase(this);
+ late final _inRowPhase = InRowPhase(this);
+ late final _inCellPhase = InCellPhase(this);
+ late final _inSelectPhase = InSelectPhase(this);
+ late final _inSelectInTablePhase = InSelectInTablePhase(this);
+ late final _inForeignContentPhase = InForeignContentPhase(this);
+ late final _afterBodyPhase = AfterBodyPhase(this);
+ late final _inFramesetPhase = InFramesetPhase(this);
+ late final _afterFramesetPhase = AfterFramesetPhase(this);
+ late final _afterAfterBodyPhase = AfterAfterBodyPhase(this);
+ late final _afterAfterFramesetPhase = AfterAfterFramesetPhase(this);
+
+ /// Create and configure an HtmlParser.
+ ///
+ /// The [input] can be a `String`, a `List<int>` of bytes, or an
+ /// [HtmlTokenizer].
+ ///
+ /// The [strict], [tree] builder, and [generateSpans] arguments configure
+ /// behavior for any type of input.
+ ///
+ /// If [input] is not a [HtmlTokenizer], you can specify a few more arguments.
+ ///
+ /// The [encoding] must be a string that indicates the encoding. If specified,
+ /// that encoding will be used, regardless of any BOM or later declaration
+ /// (such as in a meta element).
+ ///
+ /// Set [parseMeta] to false if you want to disable parsing the meta element.
+ ///
+ /// Set [lowercaseElementName] or [lowercaseAttrName] to false to disable the
+ /// automatic conversion of element and attribute names to lower case. Note
+ /// that standard way to parse HTML is to lowercase, which is what the browser
+ /// DOM will do if you request `Element.outerHTML`, for example.
+ HtmlParser(
+ dynamic input, {
+ TreeBuilder? tree,
+ this.strict = false,
+ this.generateSpans = false,
+ String? encoding,
+ bool parseMeta = true,
+ bool lowercaseElementName = true,
+ bool lowercaseAttrName = true,
+ String? sourceUrl,
+ }) : tree = tree ?? TreeBuilder(true),
+ tokenizer = input is HtmlTokenizer
+ ? input
+ : HtmlTokenizer(input,
+ encoding: encoding,
+ parseMeta: parseMeta,
+ lowercaseElementName: lowercaseElementName,
+ lowercaseAttrName: lowercaseAttrName,
+ generateSpans: generateSpans,
+ sourceUrl: sourceUrl) {
+ tokenizer.parser = this;
+ }
+
+ bool get innerHTMLMode => innerHTML != null;
+
+ /// Parse an html5 document into a tree.
+ ///
+ /// After parsing, [errors] will be populated with parse errors, if any.
+ Document parse() {
+ innerHTML = null;
+ _parse();
+ return tree.getDocument();
+ }
+
+ /// Parse an html5 document fragment into a tree.
+ ///
+ /// Pass a [container] to change the type of the containing element.
+ /// After parsing, [errors] will be populated with parse errors, if any.
+ DocumentFragment parseFragment([String container = 'div']) {
+ ArgumentError.checkNotNull(container, 'container');
+ innerHTML = container.toLowerCase();
+ _parse();
+ return tree.getFragment();
+ }
+
+ void _parse() {
+ reset();
+
+ while (true) {
+ try {
+ mainLoop();
+ break;
+ } on ReparseException catch (_) {
+ // Note: this happens if we start parsing but the character encoding
+ // changes. So we should only need to restart very early in the parse.
+ reset();
+ }
+ }
+ }
+
+ void reset() {
+ tokenizer.reset();
+
+ tree.reset();
+ firstStartTag = false;
+ errors.clear();
+ // "quirks" / "limited quirks" / "no quirks"
+ compatMode = 'no quirks';
+
+ if (innerHTMLMode) {
+ if (cdataElements.contains(innerHTML)) {
+ tokenizer.state = tokenizer.rcdataState;
+ } else if (rcdataElements.contains(innerHTML)) {
+ tokenizer.state = tokenizer.rawtextState;
+ } else if (innerHTML == 'plaintext') {
+ tokenizer.state = tokenizer.plaintextState;
+ } else {
+ // state already is data state
+ // tokenizer.state = tokenizer.dataState;
+ }
+ phase = _beforeHtmlPhase;
+ _beforeHtmlPhase.insertHtmlElement();
+ resetInsertionMode();
+ } else {
+ phase = _initialPhase;
+ }
+
+ framesetOK = true;
+ }
+
+ bool isHTMLIntegrationPoint(Element element) {
+ if (element.localName == 'annotation-xml' &&
+ element.namespaceUri == Namespaces.mathml) {
+ final enc = element.attributes['encoding']?.toAsciiLowerCase();
+ return enc == 'text/html' || enc == 'application/xhtml+xml';
+ } else {
+ return htmlIntegrationPointElements
+ .contains((element.namespaceUri, element.localName));
+ }
+ }
+
+ bool isMathMLTextIntegrationPoint(Element element) {
+ return mathmlTextIntegrationPointElements
+ .contains((element.namespaceUri, element.localName));
+ }
+
+ bool inForeignContent(Token token, int type) {
+ if (tree.openElements.isEmpty) return false;
+
+ final node = tree.openElements.last;
+ if (node.namespaceUri == tree.defaultNamespace) return false;
+
+ if (isMathMLTextIntegrationPoint(node)) {
+ if (type == TokenKind.startTag &&
+ (token as StartTagToken).name != 'mglyph' &&
+ token.name != 'malignmark') {
+ return false;
+ }
+ if (type == TokenKind.characters || type == TokenKind.spaceCharacters) {
+ return false;
+ }
+ }
+
+ if (node.localName == 'annotation-xml' &&
+ type == TokenKind.startTag &&
+ (token as StartTagToken).name == 'svg') {
+ return false;
+ }
+
+ if (isHTMLIntegrationPoint(node)) {
+ if (type == TokenKind.startTag ||
+ type == TokenKind.characters ||
+ type == TokenKind.spaceCharacters) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ void mainLoop() {
+ while (tokenizer.moveNext()) {
+ final token = tokenizer.current;
+ Token? newToken = token;
+ int type;
+ while (newToken != null) {
+ type = newToken.kind;
+
+ // Note: avoid "is" test here, see http://dartbug.com/4795
+ if (type == TokenKind.parseError) {
+ final error = newToken as ParseErrorToken;
+ parseError(error.span, error.data, error.messageParams);
+ newToken = null;
+ } else {
+ var localPhase = phase;
+ if (inForeignContent(token, type)) {
+ localPhase = _inForeignContentPhase;
+ }
+
+ switch (type) {
+ case TokenKind.characters:
+ newToken =
+ localPhase.processCharacters(newToken as CharactersToken);
+ break;
+ case TokenKind.spaceCharacters:
+ newToken = localPhase
+ .processSpaceCharacters(newToken as SpaceCharactersToken);
+ break;
+ case TokenKind.startTag:
+ newToken = localPhase.processStartTag(newToken as StartTagToken);
+ break;
+ case TokenKind.endTag:
+ newToken = localPhase.processEndTag(newToken as EndTagToken);
+ break;
+ case TokenKind.comment:
+ newToken = localPhase.processComment(newToken as CommentToken);
+ break;
+ case TokenKind.doctype:
+ newToken = localPhase.processDoctype(newToken as DoctypeToken);
+ break;
+ }
+ }
+ }
+
+ if (token is StartTagToken) {
+ if (token.selfClosing && !token.selfClosingAcknowledged) {
+ parseError(token.span, 'non-void-element-with-trailing-solidus',
+ {'name': token.name});
+ }
+ }
+ }
+
+ // When the loop finishes it's EOF
+ var reprocess = true;
+ final reprocessPhases = <Phase>[];
+ while (reprocess) {
+ reprocessPhases.add(phase);
+ reprocess = phase.processEOF();
+ if (reprocess) {
+ assert(!reprocessPhases.contains(phase));
+ }
+ }
+ }
+
+ /// The last span available. Used for EOF errors if we don't have something
+ /// better.
+ SourceSpan? get _lastSpan => tokenizer.stream.fileInfo
+ ?.location(tokenizer.stream.position)
+ .pointSpan();
+
+ void parseError(SourceSpan? span, String errorcode,
+ [Map<String, Object?>? datavars = const {}]) {
+ if (!generateSpans && span == null) {
+ span = _lastSpan;
+ }
+
+ final err = ParseError(errorcode, span, datavars);
+ errors.add(err);
+ if (strict) throw err;
+ }
+
+ void adjustMathMLAttributes(StartTagToken token) {
+ final orig = token.data.remove('definitionurl');
+ if (orig != null) {
+ token.data['definitionURL'] = orig;
+ }
+ }
+
+ void adjustSVGAttributes(StartTagToken token) {
+ final replacements = const {
+ 'attributename': 'attributeName',
+ 'attributetype': 'attributeType',
+ 'basefrequency': 'baseFrequency',
+ 'baseprofile': 'baseProfile',
+ 'calcmode': 'calcMode',
+ 'clippathunits': 'clipPathUnits',
+ 'contentscripttype': 'contentScriptType',
+ 'contentstyletype': 'contentStyleType',
+ 'diffuseconstant': 'diffuseConstant',
+ 'edgemode': 'edgeMode',
+ 'externalresourcesrequired': 'externalResourcesRequired',
+ 'filterres': 'filterRes',
+ 'filterunits': 'filterUnits',
+ 'glyphref': 'glyphRef',
+ 'gradienttransform': 'gradientTransform',
+ 'gradientunits': 'gradientUnits',
+ 'kernelmatrix': 'kernelMatrix',
+ 'kernelunitlength': 'kernelUnitLength',
+ 'keypoints': 'keyPoints',
+ 'keysplines': 'keySplines',
+ 'keytimes': 'keyTimes',
+ 'lengthadjust': 'lengthAdjust',
+ 'limitingconeangle': 'limitingConeAngle',
+ 'markerheight': 'markerHeight',
+ 'markerunits': 'markerUnits',
+ 'markerwidth': 'markerWidth',
+ 'maskcontentunits': 'maskContentUnits',
+ 'maskunits': 'maskUnits',
+ 'numoctaves': 'numOctaves',
+ 'pathlength': 'pathLength',
+ 'patterncontentunits': 'patternContentUnits',
+ 'patterntransform': 'patternTransform',
+ 'patternunits': 'patternUnits',
+ 'pointsatx': 'pointsAtX',
+ 'pointsaty': 'pointsAtY',
+ 'pointsatz': 'pointsAtZ',
+ 'preservealpha': 'preserveAlpha',
+ 'preserveaspectratio': 'preserveAspectRatio',
+ 'primitiveunits': 'primitiveUnits',
+ 'refx': 'refX',
+ 'refy': 'refY',
+ 'repeatcount': 'repeatCount',
+ 'repeatdur': 'repeatDur',
+ 'requiredextensions': 'requiredExtensions',
+ 'requiredfeatures': 'requiredFeatures',
+ 'specularconstant': 'specularConstant',
+ 'specularexponent': 'specularExponent',
+ 'spreadmethod': 'spreadMethod',
+ 'startoffset': 'startOffset',
+ 'stddeviation': 'stdDeviation',
+ 'stitchtiles': 'stitchTiles',
+ 'surfacescale': 'surfaceScale',
+ 'systemlanguage': 'systemLanguage',
+ 'tablevalues': 'tableValues',
+ 'targetx': 'targetX',
+ 'targety': 'targetY',
+ 'textlength': 'textLength',
+ 'viewbox': 'viewBox',
+ 'viewtarget': 'viewTarget',
+ 'xchannelselector': 'xChannelSelector',
+ 'ychannelselector': 'yChannelSelector',
+ 'zoomandpan': 'zoomAndPan'
+ };
+ for (var originalName in token.data.keys.toList(growable: false)) {
+ final svgName = replacements[originalName as String];
+ if (svgName != null) {
+ token.data[svgName] = token.data.remove(originalName)!;
+ }
+ }
+ }
+
+ void adjustForeignAttributes(StartTagToken token) {
+ // TODO(jmesserly): I don't like mixing non-string objects with strings in
+ // the Node.attributes Map. Is there another solution?
+ final replacements = const {
+ 'xlink:actuate': AttributeName('xlink', 'actuate', Namespaces.xlink),
+ 'xlink:arcrole': AttributeName('xlink', 'arcrole', Namespaces.xlink),
+ 'xlink:href': AttributeName('xlink', 'href', Namespaces.xlink),
+ 'xlink:role': AttributeName('xlink', 'role', Namespaces.xlink),
+ 'xlink:show': AttributeName('xlink', 'show', Namespaces.xlink),
+ 'xlink:title': AttributeName('xlink', 'title', Namespaces.xlink),
+ 'xlink:type': AttributeName('xlink', 'type', Namespaces.xlink),
+ 'xml:base': AttributeName('xml', 'base', Namespaces.xml),
+ 'xml:lang': AttributeName('xml', 'lang', Namespaces.xml),
+ 'xml:space': AttributeName('xml', 'space', Namespaces.xml),
+ 'xmlns': AttributeName(null, 'xmlns', Namespaces.xmlns),
+ 'xmlns:xlink': AttributeName('xmlns', 'xlink', Namespaces.xmlns)
+ };
+
+ for (var originalName in token.data.keys.toList(growable: false)) {
+ final foreignName = replacements[originalName as String];
+ if (foreignName != null) {
+ token.data[foreignName] = token.data.remove(originalName)!;
+ }
+ }
+ }
+
+ void resetInsertionMode() {
+ // The name of this method is mostly historical. (It's also used in the
+ // specification.)
+ for (var node in tree.openElements.reversed) {
+ var nodeName = node.localName;
+ final last = node == tree.openElements[0];
+ if (last) {
+ assert(innerHTMLMode);
+ nodeName = innerHTML;
+ }
+ // Check for conditions that should only happen in the innerHTML
+ // case
+ switch (nodeName) {
+ case 'select':
+ case 'colgroup':
+ case 'head':
+ case 'html':
+ assert(innerHTMLMode);
+ break;
+ }
+ if (!last && node.namespaceUri != tree.defaultNamespace) {
+ continue;
+ }
+ switch (nodeName) {
+ case 'select':
+ phase = _inSelectPhase;
+ return;
+ case 'td':
+ phase = _inCellPhase;
+ return;
+ case 'th':
+ phase = _inCellPhase;
+ return;
+ case 'tr':
+ phase = _inRowPhase;
+ return;
+ case 'tbody':
+ phase = _inTableBodyPhase;
+ return;
+ case 'thead':
+ phase = _inTableBodyPhase;
+ return;
+ case 'tfoot':
+ phase = _inTableBodyPhase;
+ return;
+ case 'caption':
+ phase = _inCaptionPhase;
+ return;
+ case 'colgroup':
+ phase = _inColumnGroupPhase;
+ return;
+ case 'table':
+ phase = _inTablePhase;
+ return;
+ case 'head':
+ phase = _inBodyPhase;
+ return;
+ case 'body':
+ phase = _inBodyPhase;
+ return;
+ case 'frameset':
+ phase = _inFramesetPhase;
+ return;
+ case 'html':
+ phase = _beforeHeadPhase;
+ return;
+ }
+ }
+ phase = _inBodyPhase;
+ }
+
+ /// Generic RCDATA/RAWTEXT Parsing algorithm
+ /// [contentType] - RCDATA or RAWTEXT
+ void parseRCDataRawtext(Token token, String contentType) {
+ assert(contentType == 'RAWTEXT' || contentType == 'RCDATA');
+
+ tree.insertElement(token as StartTagToken);
+
+ if (contentType == 'RAWTEXT') {
+ tokenizer.state = tokenizer.rawtextState;
+ } else {
+ tokenizer.state = tokenizer.rcdataState;
+ }
+
+ originalPhase = phase;
+ phase = _textPhase;
+ }
+}
+
+/// Base class for helper object that implements each phase of processing.
+class Phase {
+ // Order should be (they can be omitted):
+ // * EOF
+ // * Comment
+ // * Doctype
+ // * SpaceCharacters
+ // * Characters
+ // * StartTag
+ // - startTag* methods
+ // * EndTag
+ // - endTag* methods
+
+ final HtmlParser parser;
+
+ final TreeBuilder tree;
+
+ Phase(this.parser) : tree = parser.tree;
+
+ bool processEOF() {
+ throw UnimplementedError();
+ }
+
+ Token? processComment(CommentToken token) {
+ // For most phases the following is correct. Where it's not it will be
+ // overridden.
+ tree.insertComment(token, tree.openElements.last);
+ return null;
+ }
+
+ Token? processDoctype(DoctypeToken token) {
+ parser.parseError(token.span, 'unexpected-doctype');
+ return null;
+ }
+
+ Token? processCharacters(CharactersToken token) {
+ tree.insertText(token.data, token.span);
+ return null;
+ }
+
+ Token? processSpaceCharacters(SpaceCharactersToken token) {
+ tree.insertText(token.data, token.span);
+ return null;
+ }
+
+ Token? processStartTag(StartTagToken token) {
+ throw UnimplementedError();
+ }
+
+ Token? startTagHtml(StartTagToken token) {
+ if (parser.firstStartTag == false && token.name == 'html') {
+ parser.parseError(token.span, 'non-html-root');
+ }
+ // XXX Need a check here to see if the first start tag token emitted is
+ // this token... If it's not, invoke parser.parseError().
+ tree.openElements[0].sourceSpan = token.span;
+ token.data.forEach((attr, value) {
+ tree.openElements[0].attributes.putIfAbsent(attr, () => value);
+ });
+ parser.firstStartTag = false;
+ return null;
+ }
+
+ Token? processEndTag(EndTagToken token) {
+ throw UnimplementedError();
+ }
+
+ /// Helper method for popping openElements.
+ void popOpenElementsUntil(EndTagToken token) {
+ final name = token.name;
+ var node = tree.openElements.removeLast();
+ while (node.localName != name) {
+ node = tree.openElements.removeLast();
+ }
+ node.endSourceSpan = token.span;
+ }
+}
+
+class InitialPhase extends Phase {
+ InitialPhase(super.parser);
+
+ @override
+ Token? processSpaceCharacters(SpaceCharactersToken token) {
+ return null;
+ }
+
+ @override
+ Token? processComment(CommentToken token) {
+ tree.insertComment(token, tree.document);
+ return null;
+ }
+
+ @override
+ Token? processDoctype(DoctypeToken token) {
+ final name = token.name;
+ var publicId = token.publicId?.toAsciiLowerCase();
+ final systemId = token.systemId;
+ final correct = token.correct;
+
+ if (name != 'html' ||
+ publicId != null ||
+ systemId != null && systemId != 'about:legacy-compat') {
+ parser.parseError(token.span, 'unknown-doctype');
+ }
+
+ publicId ??= '';
+
+ tree.insertDoctype(token);
+
+ if (!correct ||
+ token.name != 'html' ||
+ startsWithAny(publicId, const [
+ '+//silmaril//dtd html pro v0r11 19970101//',
+ '-//advasoft ltd//dtd html 3.0 aswedit + extensions//',
+ '-//as//dtd html 3.0 aswedit + extensions//',
+ '-//ietf//dtd html 2.0 level 1//',
+ '-//ietf//dtd html 2.0 level 2//',
+ '-//ietf//dtd html 2.0 strict level 1//',
+ '-//ietf//dtd html 2.0 strict level 2//',
+ '-//ietf//dtd html 2.0 strict//',
+ '-//ietf//dtd html 2.0//',
+ '-//ietf//dtd html 2.1e//',
+ '-//ietf//dtd html 3.0//',
+ '-//ietf//dtd html 3.2 final//',
+ '-//ietf//dtd html 3.2//',
+ '-//ietf//dtd html 3//',
+ '-//ietf//dtd html level 0//',
+ '-//ietf//dtd html level 1//',
+ '-//ietf//dtd html level 2//',
+ '-//ietf//dtd html level 3//',
+ '-//ietf//dtd html strict level 0//',
+ '-//ietf//dtd html strict level 1//',
+ '-//ietf//dtd html strict level 2//',
+ '-//ietf//dtd html strict level 3//',
+ '-//ietf//dtd html strict//',
+ '-//ietf//dtd html//',
+ '-//metrius//dtd metrius presentational//',
+ '-//microsoft//dtd internet explorer 2.0 html strict//',
+ '-//microsoft//dtd internet explorer 2.0 html//',
+ '-//microsoft//dtd internet explorer 2.0 tables//',
+ '-//microsoft//dtd internet explorer 3.0 html strict//',
+ '-//microsoft//dtd internet explorer 3.0 html//',
+ '-//microsoft//dtd internet explorer 3.0 tables//',
+ '-//netscape comm. corp.//dtd html//',
+ '-//netscape comm. corp.//dtd strict html//',
+ "-//o'reilly and associates//dtd html 2.0//",
+ "-//o'reilly and associates//dtd html extended 1.0//",
+ "-//o'reilly and associates//dtd html extended relaxed 1.0//",
+ '-//softquad software//dtd hotmetal pro 6.0::19990601::extensions to html 4.0//',
+ '-//softquad//dtd hotmetal pro 4.0::19971010::extensions to html 4.0//',
+ '-//spyglass//dtd html 2.0 extended//',
+ '-//sq//dtd html 2.0 hotmetal + extensions//',
+ '-//sun microsystems corp.//dtd hotjava html//',
+ '-//sun microsystems corp.//dtd hotjava strict html//',
+ '-//w3c//dtd html 3 1995-03-24//',
+ '-//w3c//dtd html 3.2 draft//',
+ '-//w3c//dtd html 3.2 final//',
+ '-//w3c//dtd html 3.2//',
+ '-//w3c//dtd html 3.2s draft//',
+ '-//w3c//dtd html 4.0 frameset//',
+ '-//w3c//dtd html 4.0 transitional//',
+ '-//w3c//dtd html experimental 19960712//',
+ '-//w3c//dtd html experimental 970421//',
+ '-//w3c//dtd w3 html//',
+ '-//w3o//dtd w3 html 3.0//',
+ '-//webtechs//dtd mozilla html 2.0//',
+ '-//webtechs//dtd mozilla html//'
+ ]) ||
+ const [
+ '-//w3o//dtd w3 html strict 3.0//en//',
+ '-/w3c/dtd html 4.0 transitional/en',
+ 'html'
+ ].contains(publicId) ||
+ startsWithAny(publicId, const [
+ '-//w3c//dtd html 4.01 frameset//',
+ '-//w3c//dtd html 4.01 transitional//'
+ ]) &&
+ systemId == null ||
+ systemId != null &&
+ systemId.toLowerCase() ==
+ 'http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd') {
+ parser.compatMode = 'quirks';
+ } else if (startsWithAny(publicId, const [
+ '-//w3c//dtd xhtml 1.0 frameset//',
+ '-//w3c//dtd xhtml 1.0 transitional//'
+ ]) ||
+ startsWithAny(publicId, const [
+ '-//w3c//dtd html 4.01 frameset//',
+ '-//w3c//dtd html 4.01 transitional//'
+ ]) &&
+ systemId != null) {
+ parser.compatMode = 'limited quirks';
+ }
+ parser.phase = parser._beforeHtmlPhase;
+ return null;
+ }
+
+ void anythingElse() {
+ parser.compatMode = 'quirks';
+ parser.phase = parser._beforeHtmlPhase;
+ }
+
+ @override
+ Token processCharacters(CharactersToken token) {
+ parser.parseError(token.span, 'expected-doctype-but-got-chars');
+ anythingElse();
+ return token;
+ }
+
+ @override
+ Token processStartTag(StartTagToken token) {
+ parser.parseError(
+ token.span, 'expected-doctype-but-got-start-tag', {'name': token.name});
+ anythingElse();
+ return token;
+ }
+
+ @override
+ Token processEndTag(EndTagToken token) {
+ parser.parseError(
+ token.span, 'expected-doctype-but-got-end-tag', {'name': token.name});
+ anythingElse();
+ return token;
+ }
+
+ @override
+ bool processEOF() {
+ parser.parseError(parser._lastSpan, 'expected-doctype-but-got-eof');
+ anythingElse();
+ return true;
+ }
+}
+
+class BeforeHtmlPhase extends Phase {
+ BeforeHtmlPhase(super.parser);
+
+ // helper methods
+ void insertHtmlElement() {
+ tree.insertRoot(
+ StartTagToken('html', data: LinkedHashMap<Object, String>()));
+ parser.phase = parser._beforeHeadPhase;
+ }
+
+ // other
+ @override
+ bool processEOF() {
+ insertHtmlElement();
+ return true;
+ }
+
+ @override
+ Token? processComment(CommentToken token) {
+ tree.insertComment(token, tree.document);
+ return null;
+ }
+
+ @override
+ Token? processSpaceCharacters(SpaceCharactersToken token) {
+ return null;
+ }
+
+ @override
+ Token processCharacters(CharactersToken token) {
+ insertHtmlElement();
+ return token;
+ }
+
+ @override
+ @override
+ Token processStartTag(StartTagToken token) {
+ if (token.name == 'html') {
+ parser.firstStartTag = true;
+ }
+ insertHtmlElement();
+ return token;
+ }
+
+ @override
+ Token? processEndTag(EndTagToken token) {
+ switch (token.name) {
+ case 'head':
+ case 'body':
+ case 'html':
+ case 'br':
+ insertHtmlElement();
+ return token;
+ default:
+ parser.parseError(
+ token.span, 'unexpected-end-tag-before-html', {'name': token.name});
+ return null;
+ }
+ }
+}
+
+class BeforeHeadPhase extends Phase {
+ BeforeHeadPhase(super.parser);
+
+ @override
+ Token? processStartTag(StartTagToken token) {
+ switch (token.name) {
+ case 'html':
+ return startTagHtml(token);
+ case 'head':
+ startTagHead(token);
+ return null;
+ default:
+ return startTagOther(token);
+ }
+ }
+
+ @override
+ Token? processEndTag(EndTagToken token) {
+ switch (token.name) {
+ case 'head':
+ case 'body':
+ case 'html':
+ case 'br':
+ return endTagImplyHead(token);
+ default:
+ endTagOther(token);
+ return null;
+ }
+ }
+
+ @override
+ bool processEOF() {
+ startTagHead(StartTagToken('head', data: LinkedHashMap<Object, String>()));
+ return true;
+ }
+
+ @override
+ Token? processSpaceCharacters(SpaceCharactersToken token) {
+ return null;
+ }
+
+ @override
+ Token processCharacters(CharactersToken token) {
+ startTagHead(StartTagToken('head', data: LinkedHashMap<Object, String>()));
+ return token;
+ }
+
+ @override
+ Token? startTagHtml(StartTagToken token) {
+ return parser._inBodyPhase.processStartTag(token);
+ }
+
+ void startTagHead(StartTagToken token) {
+ tree.insertElement(token);
+ tree.headPointer = tree.openElements.last;
+ parser.phase = parser._inHeadPhase;
+ }
+
+ Token startTagOther(StartTagToken token) {
+ startTagHead(StartTagToken('head', data: LinkedHashMap<Object, String>()));
+ return token;
+ }
+
+ Token endTagImplyHead(EndTagToken token) {
+ startTagHead(StartTagToken('head', data: LinkedHashMap<Object, String>()));
+ return token;
+ }
+
+ void endTagOther(EndTagToken token) {
+ parser.parseError(
+ token.span, 'end-tag-after-implied-root', {'name': token.name});
+ }
+}
+
+class InHeadPhase extends Phase {
+ InHeadPhase(super.parser);
+
+ @override
+ Token? processStartTag(StartTagToken token) {
+ switch (token.name) {
+ case 'html':
+ return startTagHtml(token);
+ case 'title':
+ startTagTitle(token);
+ return null;
+ case 'noscript':
+ case 'noframes':
+ case 'style':
+ startTagNoScriptNoFramesStyle(token);
+ return null;
+ case 'script':
+ startTagScript(token);
+ return null;
+ case 'base':
+ case 'basefont':
+ case 'bgsound':
+ case 'command':
+ case 'link':
+ startTagBaseLinkCommand(token);
+ return null;
+ case 'meta':
+ startTagMeta(token);
+ return null;
+ case 'head':
+ startTagHead(token);
+ return null;
+ default:
+ return startTagOther(token);
+ }
+ }
+
+ @override
+ Token? processEndTag(EndTagToken token) {
+ switch (token.name) {
+ case 'head':
+ endTagHead(token);
+ return null;
+ case 'br':
+ case 'html':
+ case 'body':
+ return endTagHtmlBodyBr(token);
+ default:
+ endTagOther(token);
+ return null;
+ }
+ }
+
+ // the real thing
+ @override
+ bool processEOF() {
+ anythingElse();
+ return true;
+ }
+
+ @override
+ Token processCharacters(CharactersToken token) {
+ anythingElse();
+ return token;
+ }
+
+ @override
+ Token? startTagHtml(StartTagToken token) {
+ return parser._inBodyPhase.processStartTag(token);
+ }
+
+ void startTagHead(StartTagToken token) {
+ parser.parseError(token.span, 'two-heads-are-not-better-than-one');
+ }
+
+ void startTagBaseLinkCommand(StartTagToken token) {
+ tree.insertElement(token);
+ tree.openElements.removeLast();
+ token.selfClosingAcknowledged = true;
+ }
+
+ void startTagMeta(StartTagToken token) {
+ tree.insertElement(token);
+ tree.openElements.removeLast();
+ token.selfClosingAcknowledged = true;
+
+ final attributes = token.data;
+ if (!parser.tokenizer.stream.charEncodingCertain) {
+ final charset = attributes['charset'];
+ final content = attributes['content'];
+ if (charset != null) {
+ parser.tokenizer.stream.changeEncoding(charset);
+ } else if (content != null) {
+ final data = EncodingBytes(content);
+ final codec = ContentAttrParser(data).parse();
+ parser.tokenizer.stream.changeEncoding(codec);
+ }
+ }
+ }
+
+ void startTagTitle(StartTagToken token) {
+ parser.parseRCDataRawtext(token, 'RCDATA');
+ }
+
+ void startTagNoScriptNoFramesStyle(StartTagToken token) {
+ // Need to decide whether to implement the scripting-disabled case
+ parser.parseRCDataRawtext(token, 'RAWTEXT');
+ }
+
+ void startTagScript(StartTagToken token) {
+ tree.insertElement(token);
+ parser.tokenizer.state = parser.tokenizer.scriptDataState;
+ parser.originalPhase = parser.phase;
+ parser.phase = parser._textPhase;
+ }
+
+ Token startTagOther(StartTagToken token) {
+ anythingElse();
+ return token;
+ }
+
+ void endTagHead(EndTagToken token) {
+ final node = parser.tree.openElements.removeLast();
+ assert(node.localName == 'head');
+ node.endSourceSpan = token.span;
+ parser.phase = parser._afterHeadPhase;
+ }
+
+ Token endTagHtmlBodyBr(EndTagToken token) {
+ anythingElse();
+ return token;
+ }
+
+ void endTagOther(EndTagToken token) {
+ parser.parseError(token.span, 'unexpected-end-tag', {'name': token.name});
+ }
+
+ void anythingElse() {
+ endTagHead(EndTagToken('head'));
+ }
+}
+
+// XXX If we implement a parser for which scripting is disabled we need to
+// implement this phase.
+//
+// class InHeadNoScriptPhase extends Phase {
+
+class AfterHeadPhase extends Phase {
+ AfterHeadPhase(super.parser);
+
+ @override
+ Token? processStartTag(StartTagToken token) {
+ switch (token.name) {
+ case 'html':
+ return startTagHtml(token);
+ case 'body':
+ startTagBody(token);
+ return null;
+ case 'frameset':
+ startTagFrameset(token);
+ return null;
+ case 'base':
+ case 'basefont':
+ case 'bgsound':
+ case 'link':
+ case 'meta':
+ case 'noframes':
+ case 'script':
+ case 'style':
+ case 'title':
+ startTagFromHead(token);
+ return null;
+ case 'head':
+ startTagHead(token);
+ return null;
+ default:
+ return startTagOther(token);
+ }
+ }
+
+ @override
+ Token? processEndTag(EndTagToken token) {
+ switch (token.name) {
+ case 'body':
+ case 'html':
+ case 'br':
+ return endTagHtmlBodyBr(token);
+ default:
+ endTagOther(token);
+ return null;
+ }
+ }
+
+ @override
+ bool processEOF() {
+ anythingElse();
+ return true;
+ }
+
+ @override
+ Token processCharacters(CharactersToken token) {
+ anythingElse();
+ return token;
+ }
+
+ @override
+ Token? startTagHtml(StartTagToken token) {
+ return parser._inBodyPhase.processStartTag(token);
+ }
+
+ void startTagBody(StartTagToken token) {
+ parser.framesetOK = false;
+ tree.insertElement(token);
+ parser.phase = parser._inBodyPhase;
+ }
+
+ void startTagFrameset(StartTagToken token) {
+ tree.insertElement(token);
+ parser.phase = parser._inFramesetPhase;
+ }
+
+ void startTagFromHead(StartTagToken token) {
+ parser.parseError(token.span, 'unexpected-start-tag-out-of-my-head',
+ {'name': token.name});
+ tree.openElements.add(tree.headPointer as Element);
+ parser._inHeadPhase.processStartTag(token);
+ for (var node in tree.openElements.reversed) {
+ if (node.localName == 'head') {
+ tree.openElements.remove(node);
+ break;
+ }
+ }
+ }
+
+ void startTagHead(StartTagToken token) {
+ parser.parseError(token.span, 'unexpected-start-tag', {'name': token.name});
+ }
+
+ Token startTagOther(StartTagToken token) {
+ anythingElse();
+ return token;
+ }
+
+ Token endTagHtmlBodyBr(EndTagToken token) {
+ anythingElse();
+ return token;
+ }
+
+ void endTagOther(EndTagToken token) {
+ parser.parseError(token.span, 'unexpected-end-tag', {'name': token.name});
+ }
+
+ void anythingElse() {
+ tree.insertElement(
+ StartTagToken('body', data: LinkedHashMap<Object, String>()));
+ parser.phase = parser._inBodyPhase;
+ parser.framesetOK = true;
+ }
+}
+
+typedef TokenProccessor = Token Function(Token token);
+
+class InBodyPhase extends Phase {
+ bool dropNewline = false;
+
+ // http://www.whatwg.org/specs/web-apps/current-work///parsing-main-inbody
+ // the really-really-really-very crazy mode
+ InBodyPhase(super.parser);
+
+ @override
+ Token? processStartTag(StartTagToken token) {
+ switch (token.name) {
+ case 'html':
+ return startTagHtml(token);
+ case 'base':
+ case 'basefont':
+ case 'bgsound':
+ case 'command':
+ case 'link':
+ case 'meta':
+ case 'noframes':
+ case 'script':
+ case 'style':
+ case 'title':
+ return startTagProcessInHead(token);
+ case 'body':
+ startTagBody(token);
+ return null;
+ case 'frameset':
+ startTagFrameset(token);
+ return null;
+ case 'address':
+ case 'article':
+ case 'aside':
+ case 'blockquote':
+ case 'center':
+ case 'details':
+ case 'dir':
+ case 'div':
+ case 'dl':
+ case 'fieldset':
+ case 'figcaption':
+ case 'figure':
+ case 'footer':
+ case 'header':
+ case 'hgroup':
+ case 'menu':
+ case 'nav':
+ case 'ol':
+ case 'p':
+ case 'section':
+ case 'summary':
+ case 'ul':
+ startTagCloseP(token);
+ return null;
+ // headingElements
+ case 'h1':
+ case 'h2':
+ case 'h3':
+ case 'h4':
+ case 'h5':
+ case 'h6':
+ startTagHeading(token);
+ return null;
+ case 'pre':
+ case 'listing':
+ startTagPreListing(token);
+ return null;
+ case 'form':
+ startTagForm(token);
+ return null;
+ case 'li':
+ case 'dd':
+ case 'dt':
+ startTagListItem(token);
+ return null;
+ case 'plaintext':
+ startTagPlaintext(token);
+ return null;
+ case 'a':
+ startTagA(token);
+ return null;
+ case 'b':
+ case 'big':
+ case 'code':
+ case 'em':
+ case 'font':
+ case 'i':
+ case 's':
+ case 'small':
+ case 'strike':
+ case 'strong':
+ case 'tt':
+ case 'u':
+ startTagFormatting(token);
+ return null;
+ case 'nobr':
+ startTagNobr(token);
+ return null;
+ case 'button':
+ return startTagButton(token);
+ case 'applet':
+ case 'marquee':
+ case 'object':
+ startTagAppletMarqueeObject(token);
+ return null;
+ case 'xmp':
+ startTagXmp(token);
+ return null;
+ case 'table':
+ startTagTable(token);
+ return null;
+ case 'area':
+ case 'br':
+ case 'embed':
+ case 'img':
+ case 'keygen':
+ case 'wbr':
+ startTagVoidFormatting(token);
+ return null;
+ case 'param':
+ case 'source':
+ case 'track':
+ startTagParamSource(token);
+ return null;
+ case 'input':
+ startTagInput(token);
+ return null;
+ case 'hr':
+ startTagHr(token);
+ return null;
+ case 'image':
+ startTagImage(token);
+ return null;
+ case 'isindex':
+ startTagIsIndex(token);
+ return null;
+ case 'textarea':
+ startTagTextarea(token);
+ return null;
+ case 'iframe':
+ startTagIFrame(token);
+ return null;
+ case 'noembed':
+ case 'noscript':
+ startTagRawtext(token);
+ return null;
+ case 'select':
+ startTagSelect(token);
+ return null;
+ case 'rp':
+ case 'rt':
+ startTagRpRt(token);
+ return null;
+ case 'option':
+ case 'optgroup':
+ startTagOpt(token);
+ return null;
+ case 'math':
+ startTagMath(token);
+ return null;
+ case 'svg':
+ startTagSvg(token);
+ return null;
+ case 'caption':
+ case 'col':
+ case 'colgroup':
+ case 'frame':
+ case 'head':
+ case 'tbody':
+ case 'td':
+ case 'tfoot':
+ case 'th':
+ case 'thead':
+ case 'tr':
+ startTagMisplaced(token);
+ return null;
+ default:
+ return startTagOther(token);
+ }
+ }
+
+ @override
+ Token? processEndTag(EndTagToken token) {
+ switch (token.name) {
+ case 'body':
+ endTagBody(token);
+ return null;
+ case 'html':
+ return endTagHtml(token);
+ case 'address':
+ case 'article':
+ case 'aside':
+ case 'blockquote':
+ case 'button':
+ case 'center':
+ case 'details':
+ case 'dir':
+ case 'div':
+ case 'dl':
+ case 'fieldset':
+ case 'figcaption':
+ case 'figure':
+ case 'footer':
+ case 'header':
+ case 'hgroup':
+ case 'listing':
+ case 'menu':
+ case 'nav':
+ case 'ol':
+ case 'pre':
+ case 'section':
+ case 'summary':
+ case 'ul':
+ endTagBlock(token);
+ return null;
+ case 'form':
+ endTagForm(token);
+ return null;
+ case 'p':
+ endTagP(token);
+ return null;
+ case 'dd':
+ case 'dt':
+ case 'li':
+ endTagListItem(token);
+ return null;
+ // headingElements
+ case 'h1':
+ case 'h2':
+ case 'h3':
+ case 'h4':
+ case 'h5':
+ case 'h6':
+ endTagHeading(token);
+ return null;
+ case 'a':
+ case 'b':
+ case 'big':
+ case 'code':
+ case 'em':
+ case 'font':
+ case 'i':
+ case 'nobr':
+ case 's':
+ case 'small':
+ case 'strike':
+ case 'strong':
+ case 'tt':
+ case 'u':
+ endTagFormatting(token);
+ return null;
+ case 'applet':
+ case 'marquee':
+ case 'object':
+ endTagAppletMarqueeObject(token);
+ return null;
+ case 'br':
+ endTagBr(token);
+ return null;
+ default:
+ endTagOther(token);
+ return null;
+ }
+ }
+
+ bool isMatchingFormattingElement(Element node1, Element node2) {
+ if (node1.localName != node2.localName ||
+ node1.namespaceUri != node2.namespaceUri) {
+ return false;
+ } else if (node1.attributes.length != node2.attributes.length) {
+ return false;
+ } else {
+ for (var key in node1.attributes.keys) {
+ if (node1.attributes[key] != node2.attributes[key]) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ // helper
+ void addFormattingElement(StartTagToken token) {
+ tree.insertElement(token);
+ final element = tree.openElements.last;
+
+ final matchingElements = <Node?>[];
+ for (Node? node in tree.activeFormattingElements.reversed) {
+ if (node == null) {
+ break;
+ } else if (isMatchingFormattingElement(node as Element, element)) {
+ matchingElements.add(node);
+ }
+ }
+
+ assert(matchingElements.length <= 3);
+ if (matchingElements.length == 3) {
+ tree.activeFormattingElements.remove(matchingElements.last);
+ }
+ tree.activeFormattingElements.add(element);
+ }
+
+ // the real deal
+ @override
+ bool processEOF() {
+ for (var node in tree.openElements.reversed) {
+ switch (node.localName) {
+ case 'dd':
+ case 'dt':
+ case 'li':
+ case 'p':
+ case 'tbody':
+ case 'td':
+ case 'tfoot':
+ case 'th':
+ case 'thead':
+ case 'tr':
+ case 'body':
+ case 'html':
+ continue;
+ }
+ parser.parseError(node.sourceSpan, 'expected-closing-tag-but-got-eof');
+ break;
+ }
+ //Stop parsing
+ return false;
+ }
+
+ void processSpaceCharactersDropNewline(StringToken token) {
+ // Sometimes (start of <pre>, <listing>, and <textarea> blocks) we
+ // want to drop leading newlines
+ var data = token.data;
+ dropNewline = false;
+ if (data.startsWith('\n')) {
+ final lastOpen = tree.openElements.last;
+ if (const ['pre', 'listing', 'textarea'].contains(lastOpen.localName) &&
+ !lastOpen.hasContent()) {
+ data = data.substring(1);
+ }
+ }
+ if (data.isNotEmpty) {
+ tree.reconstructActiveFormattingElements();
+ tree.insertText(data, token.span);
+ }
+ }
+
+ @override
+ Token? processCharacters(CharactersToken token) {
+ if (token.data == '\u0000') {
+ //The tokenizer should always emit null on its own
+ return null;
+ }
+ tree.reconstructActiveFormattingElements();
+ tree.insertText(token.data, token.span);
+ if (parser.framesetOK && !allWhitespace(token.data)) {
+ parser.framesetOK = false;
+ }
+ return null;
+ }
+
+ @override
+ Token? processSpaceCharacters(SpaceCharactersToken token) {
+ if (dropNewline) {
+ processSpaceCharactersDropNewline(token);
+ } else {
+ tree.reconstructActiveFormattingElements();
+ tree.insertText(token.data, token.span);
+ }
+ return null;
+ }
+
+ Token? startTagProcessInHead(StartTagToken token) {
+ return parser._inHeadPhase.processStartTag(token);
+ }
+
+ void startTagBody(StartTagToken token) {
+ parser.parseError(token.span, 'unexpected-start-tag', {'name': 'body'});
+ if (tree.openElements.length == 1 ||
+ tree.openElements[1].localName != 'body') {
+ assert(parser.innerHTMLMode);
+ } else {
+ parser.framesetOK = false;
+ token.data.forEach((attr, value) {
+ tree.openElements[1].attributes.putIfAbsent(attr, () => value);
+ });
+ }
+ }
+
+ void startTagFrameset(StartTagToken token) {
+ parser.parseError(token.span, 'unexpected-start-tag', {'name': 'frameset'});
+ if (tree.openElements.length == 1 ||
+ tree.openElements[1].localName != 'body') {
+ assert(parser.innerHTMLMode);
+ } else if (parser.framesetOK) {
+ if (tree.openElements[1].parentNode != null) {
+ tree.openElements[1].parentNode!.nodes.remove(tree.openElements[1]);
+ }
+ while (tree.openElements.last.localName != 'html') {
+ tree.openElements.removeLast();
+ }
+ tree.insertElement(token);
+ parser.phase = parser._inFramesetPhase;
+ }
+ }
+
+ void startTagCloseP(StartTagToken token) {
+ if (tree.elementInScope('p', variant: 'button')) {
+ endTagP(EndTagToken('p'));
+ }
+ tree.insertElement(token);
+ }
+
+ void startTagPreListing(StartTagToken token) {
+ if (tree.elementInScope('p', variant: 'button')) {
+ endTagP(EndTagToken('p'));
+ }
+ tree.insertElement(token);
+ parser.framesetOK = false;
+ dropNewline = true;
+ }
+
+ void startTagForm(StartTagToken token) {
+ if (tree.formPointer != null) {
+ parser.parseError(token.span, 'unexpected-start-tag', {'name': 'form'});
+ } else {
+ if (tree.elementInScope('p', variant: 'button')) {
+ endTagP(EndTagToken('p'));
+ }
+ tree.insertElement(token);
+ tree.formPointer = tree.openElements.last;
+ }
+ }
+
+ void startTagListItem(StartTagToken token) {
+ parser.framesetOK = false;
+
+ final stopNamesMap = const {
+ 'li': ['li'],
+ 'dt': ['dt', 'dd'],
+ 'dd': ['dt', 'dd']
+ };
+ final stopNames = stopNamesMap[token.name!]!;
+ for (var node in tree.openElements.reversed) {
+ if (stopNames.contains(node.localName)) {
+ parser.phase.processEndTag(EndTagToken(node.localName));
+ break;
+ }
+ if (specialElements.contains(getElementNameTuple(node)) &&
+ !const ['address', 'div', 'p'].contains(node.localName)) {
+ break;
+ }
+ }
+
+ if (tree.elementInScope('p', variant: 'button')) {
+ parser.phase.processEndTag(EndTagToken('p'));
+ }
+
+ tree.insertElement(token);
+ }
+
+ void startTagPlaintext(StartTagToken token) {
+ if (tree.elementInScope('p', variant: 'button')) {
+ endTagP(EndTagToken('p'));
+ }
+ tree.insertElement(token);
+ parser.tokenizer.state = parser.tokenizer.plaintextState;
+ }
+
+ void startTagHeading(StartTagToken token) {
+ if (tree.elementInScope('p', variant: 'button')) {
+ endTagP(EndTagToken('p'));
+ }
+ if (headingElements.contains(tree.openElements.last.localName)) {
+ parser
+ .parseError(token.span, 'unexpected-start-tag', {'name': token.name});
+ tree.openElements.removeLast();
+ }
+ tree.insertElement(token);
+ }
+
+ void startTagA(StartTagToken token) {
+ final afeAElement = tree.elementInActiveFormattingElements('a');
+ if (afeAElement != null) {
+ parser.parseError(token.span, 'unexpected-start-tag-implies-end-tag',
+ {'startName': 'a', 'endName': 'a'});
+ endTagFormatting(EndTagToken('a'));
+ tree.openElements.remove(afeAElement);
+ tree.activeFormattingElements.remove(afeAElement);
+ }
+ tree.reconstructActiveFormattingElements();
+ addFormattingElement(token);
+ }
+
+ void startTagFormatting(StartTagToken token) {
+ tree.reconstructActiveFormattingElements();
+ addFormattingElement(token);
+ }
+
+ void startTagNobr(StartTagToken token) {
+ tree.reconstructActiveFormattingElements();
+ if (tree.elementInScope('nobr')) {
+ parser.parseError(token.span, 'unexpected-start-tag-implies-end-tag',
+ {'startName': 'nobr', 'endName': 'nobr'});
+ processEndTag(EndTagToken('nobr'));
+ // XXX Need tests that trigger the following
+ tree.reconstructActiveFormattingElements();
+ }
+ addFormattingElement(token);
+ }
+
+ Token? startTagButton(StartTagToken token) {
+ if (tree.elementInScope('button')) {
+ parser.parseError(token.span, 'unexpected-start-tag-implies-end-tag',
+ {'startName': 'button', 'endName': 'button'});
+ processEndTag(EndTagToken('button'));
+ return token;
+ } else {
+ tree.reconstructActiveFormattingElements();
+ tree.insertElement(token);
+ parser.framesetOK = false;
+ }
+ return null;
+ }
+
+ void startTagAppletMarqueeObject(StartTagToken token) {
+ tree.reconstructActiveFormattingElements();
+ tree.insertElement(token);
+ tree.activeFormattingElements.add(null);
+ parser.framesetOK = false;
+ }
+
+ void startTagXmp(StartTagToken token) {
+ if (tree.elementInScope('p', variant: 'button')) {
+ endTagP(EndTagToken('p'));
+ }
+ tree.reconstructActiveFormattingElements();
+ parser.framesetOK = false;
+ parser.parseRCDataRawtext(token, 'RAWTEXT');
+ }
+
+ void startTagTable(StartTagToken token) {
+ if (parser.compatMode != 'quirks') {
+ if (tree.elementInScope('p', variant: 'button')) {
+ processEndTag(EndTagToken('p'));
+ }
+ }
+ tree.insertElement(token);
+ parser.framesetOK = false;
+ parser.phase = parser._inTablePhase;
+ }
+
+ void startTagVoidFormatting(StartTagToken token) {
+ tree.reconstructActiveFormattingElements();
+ tree.insertElement(token);
+ tree.openElements.removeLast();
+ token.selfClosingAcknowledged = true;
+ parser.framesetOK = false;
+ }
+
+ void startTagInput(StartTagToken token) {
+ final savedFramesetOK = parser.framesetOK;
+ startTagVoidFormatting(token);
+ if (token.data['type']?.toAsciiLowerCase() == 'hidden') {
+ //input type=hidden doesn't change framesetOK
+ parser.framesetOK = savedFramesetOK;
+ }
+ }
+
+ void startTagParamSource(StartTagToken token) {
+ tree.insertElement(token);
+ tree.openElements.removeLast();
+ token.selfClosingAcknowledged = true;
+ }
+
+ void startTagHr(StartTagToken token) {
+ if (tree.elementInScope('p', variant: 'button')) {
+ endTagP(EndTagToken('p'));
+ }
+ tree.insertElement(token);
+ tree.openElements.removeLast();
+ token.selfClosingAcknowledged = true;
+ parser.framesetOK = false;
+ }
+
+ void startTagImage(StartTagToken token) {
+ // No really...
+ parser.parseError(token.span, 'unexpected-start-tag-treated-as',
+ {'originalName': 'image', 'newName': 'img'});
+ processStartTag(
+ StartTagToken('img', data: token.data, selfClosing: token.selfClosing));
+ }
+
+ void startTagIsIndex(StartTagToken token) {
+ parser.parseError(token.span, 'deprecated-tag', {'name': 'isindex'});
+ if (tree.formPointer != null) {
+ return;
+ }
+ final formAttrs = LinkedHashMap<Object, String>();
+ final dataAction = token.data['action'];
+ if (dataAction != null) {
+ formAttrs['action'] = dataAction;
+ }
+ processStartTag(StartTagToken('form', data: formAttrs));
+ processStartTag(StartTagToken('hr', data: LinkedHashMap<Object, String>()));
+ processStartTag(
+ StartTagToken('label', data: LinkedHashMap<Object, String>()));
+ // XXX Localization ...
+ var prompt = token.data['prompt'];
+ prompt ??= 'This is a searchable index. Enter search keywords: ';
+ processCharacters(CharactersToken(prompt));
+ final attributes = LinkedHashMap<Object, String>.from(token.data);
+ attributes.remove('action');
+ attributes.remove('prompt');
+ attributes['name'] = 'isindex';
+ processStartTag(StartTagToken('input',
+ data: attributes, selfClosing: token.selfClosing));
+ processEndTag(EndTagToken('label'));
+ processStartTag(StartTagToken('hr', data: LinkedHashMap<Object, String>()));
+ processEndTag(EndTagToken('form'));
+ }
+
+ void startTagTextarea(StartTagToken token) {
+ tree.insertElement(token);
+ parser.tokenizer.state = parser.tokenizer.rcdataState;
+ dropNewline = true;
+ parser.framesetOK = false;
+ }
+
+ void startTagIFrame(StartTagToken token) {
+ parser.framesetOK = false;
+ startTagRawtext(token);
+ }
+
+ /// iframe, noembed noframes, noscript(if scripting enabled).
+ void startTagRawtext(StartTagToken token) {
+ parser.parseRCDataRawtext(token, 'RAWTEXT');
+ }
+
+ void startTagOpt(StartTagToken token) {
+ if (tree.openElements.last.localName == 'option') {
+ parser.phase.processEndTag(EndTagToken('option'));
+ }
+ tree.reconstructActiveFormattingElements();
+ parser.tree.insertElement(token);
+ }
+
+ void startTagSelect(StartTagToken token) {
+ tree.reconstructActiveFormattingElements();
+ tree.insertElement(token);
+ parser.framesetOK = false;
+
+ if (parser._inTablePhase == parser.phase ||
+ parser._inCaptionPhase == parser.phase ||
+ parser._inColumnGroupPhase == parser.phase ||
+ parser._inTableBodyPhase == parser.phase ||
+ parser._inRowPhase == parser.phase ||
+ parser._inCellPhase == parser.phase) {
+ parser.phase = parser._inSelectInTablePhase;
+ } else {
+ parser.phase = parser._inSelectPhase;
+ }
+ }
+
+ void startTagRpRt(StartTagToken token) {
+ if (tree.elementInScope('ruby')) {
+ tree.generateImpliedEndTags();
+ final last = tree.openElements.last;
+ if (last.localName != 'ruby') {
+ parser.parseError(last.sourceSpan, 'undefined-error');
+ }
+ }
+ tree.insertElement(token);
+ }
+
+ void startTagMath(StartTagToken token) {
+ tree.reconstructActiveFormattingElements();
+ parser.adjustMathMLAttributes(token);
+ parser.adjustForeignAttributes(token);
+ token.namespace = Namespaces.mathml;
+ tree.insertElement(token);
+ //Need to get the parse error right for the case where the token
+ //has a namespace not equal to the xmlns attribute
+ if (token.selfClosing) {
+ tree.openElements.removeLast();
+ token.selfClosingAcknowledged = true;
+ }
+ }
+
+ void startTagSvg(StartTagToken token) {
+ tree.reconstructActiveFormattingElements();
+ parser.adjustSVGAttributes(token);
+ parser.adjustForeignAttributes(token);
+ token.namespace = Namespaces.svg;
+ tree.insertElement(token);
+ //Need to get the parse error right for the case where the token
+ //has a namespace not equal to the xmlns attribute
+ if (token.selfClosing) {
+ tree.openElements.removeLast();
+ token.selfClosingAcknowledged = true;
+ }
+ }
+
+ /// Elements that should be children of other elements that have a
+ /// different insertion mode; here they are ignored
+ /// "caption", "col", "colgroup", "frame", "frameset", "head",
+ /// "option", "optgroup", "tbody", "td", "tfoot", "th", "thead",
+ /// "tr", "noscript"
+ void startTagMisplaced(StartTagToken token) {
+ parser.parseError(
+ token.span, 'unexpected-start-tag-ignored', {'name': token.name});
+ }
+
+ Token? startTagOther(StartTagToken token) {
+ tree.reconstructActiveFormattingElements();
+ tree.insertElement(token);
+ return null;
+ }
+
+ void endTagP(EndTagToken token) {
+ if (!tree.elementInScope('p', variant: 'button')) {
+ startTagCloseP(StartTagToken('p', data: LinkedHashMap<Object, String>()));
+ parser.parseError(token.span, 'unexpected-end-tag', {'name': 'p'});
+ endTagP(EndTagToken('p'));
+ } else {
+ tree.generateImpliedEndTags('p');
+ if (tree.openElements.last.localName != 'p') {
+ parser.parseError(token.span, 'unexpected-end-tag', {'name': 'p'});
+ }
+ popOpenElementsUntil(token);
+ }
+ }
+
+ void endTagBody(EndTagToken token) {
+ if (!tree.elementInScope('body')) {
+ parser.parseError(token.span, 'undefined-error');
+ return;
+ } else if (tree.openElements.last.localName == 'body') {
+ tree.openElements.last.endSourceSpan = token.span;
+ } else {
+ for (var node in slice(tree.openElements, 2)) {
+ switch (node.localName) {
+ case 'dd':
+ case 'dt':
+ case 'li':
+ case 'optgroup':
+ case 'option':
+ case 'p':
+ case 'rp':
+ case 'rt':
+ case 'tbody':
+ case 'td':
+ case 'tfoot':
+ case 'th':
+ case 'thead':
+ case 'tr':
+ case 'body':
+ case 'html':
+ continue;
+ }
+ // Not sure this is the correct name for the parse error
+ parser.parseError(token.span, 'expected-one-end-tag-but-got-another',
+ {'gotName': 'body', 'expectedName': node.localName});
+ break;
+ }
+ }
+ parser.phase = parser._afterBodyPhase;
+ }
+
+ Token? endTagHtml(EndTagToken token) {
+ //We repeat the test for the body end tag token being ignored here
+ if (tree.elementInScope('body')) {
+ endTagBody(EndTagToken('body'));
+ return token;
+ }
+ return null;
+ }
+
+ void endTagBlock(EndTagToken token) {
+ //Put us back in the right whitespace handling mode
+ if (token.name == 'pre') {
+ dropNewline = false;
+ }
+ final inScope = tree.elementInScope(token.name);
+ if (inScope) {
+ tree.generateImpliedEndTags();
+ }
+ if (tree.openElements.last.localName != token.name) {
+ parser.parseError(token.span, 'end-tag-too-early', {'name': token.name});
+ }
+ if (inScope) {
+ popOpenElementsUntil(token);
+ }
+ }
+
+ void endTagForm(EndTagToken token) {
+ final node = tree.formPointer;
+ tree.formPointer = null;
+ if (node == null || !tree.elementInScope(node)) {
+ parser.parseError(token.span, 'unexpected-end-tag', {'name': 'form'});
+ } else {
+ tree.generateImpliedEndTags();
+ if (tree.openElements.last != node) {
+ parser.parseError(
+ token.span, 'end-tag-too-early-ignored', {'name': 'form'});
+ }
+ tree.openElements.remove(node);
+ node.endSourceSpan = token.span;
+ }
+ }
+
+ void endTagListItem(EndTagToken token) {
+ String? variant;
+ if (token.name == 'li') {
+ variant = 'list';
+ } else {
+ variant = null;
+ }
+ if (!tree.elementInScope(token.name, variant: variant)) {
+ parser.parseError(token.span, 'unexpected-end-tag', {'name': token.name});
+ } else {
+ tree.generateImpliedEndTags(token.name);
+ if (tree.openElements.last.localName != token.name) {
+ parser
+ .parseError(token.span, 'end-tag-too-early', {'name': token.name});
+ }
+ popOpenElementsUntil(token);
+ }
+ }
+
+ void endTagHeading(EndTagToken token) {
+ for (var item in headingElements) {
+ if (tree.elementInScope(item)) {
+ tree.generateImpliedEndTags();
+ break;
+ }
+ }
+ if (tree.openElements.last.localName != token.name) {
+ parser.parseError(token.span, 'end-tag-too-early', {'name': token.name});
+ }
+
+ for (var item in headingElements) {
+ if (tree.elementInScope(item)) {
+ var node = tree.openElements.removeLast();
+ while (!headingElements.contains(node.localName)) {
+ node = tree.openElements.removeLast();
+ }
+ node.endSourceSpan = token.span;
+ break;
+ }
+ }
+ }
+
+ /// The much-feared adoption agency algorithm.
+ void endTagFormatting(EndTagToken token) {
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#adoptionAgency
+ // TODO(jmesserly): the comments here don't match the numbered steps in the
+ // updated spec. This needs a pass over it to verify that it still matches.
+ // In particular the html5lib Python code skiped "step 4", I'm not sure why.
+ // XXX Better parseError messages appreciated.
+ var outerLoopCounter = 0;
+ while (outerLoopCounter < 8) {
+ outerLoopCounter += 1;
+
+ // Step 1 paragraph 1
+ final formattingElement =
+ tree.elementInActiveFormattingElements(token.name);
+ if (formattingElement == null ||
+ (tree.openElements.contains(formattingElement) &&
+ !tree.elementInScope(formattingElement.localName))) {
+ parser.parseError(
+ token.span, 'adoption-agency-1.1', {'name': token.name});
+ return;
+ // Step 1 paragraph 2
+ } else if (!tree.openElements.contains(formattingElement)) {
+ parser.parseError(
+ token.span, 'adoption-agency-1.2', {'name': token.name});
+ tree.activeFormattingElements.remove(formattingElement);
+ return;
+ }
+
+ // Step 1 paragraph 3
+ if (formattingElement != tree.openElements.last) {
+ parser.parseError(
+ token.span, 'adoption-agency-1.3', {'name': token.name});
+ }
+
+ // Step 2
+ // Start of the adoption agency algorithm proper
+ final afeIndex = tree.openElements.indexOf(formattingElement);
+ Element? furthestBlock;
+ for (var element in slice(tree.openElements, afeIndex)) {
+ if (specialElements.contains(getElementNameTuple(element))) {
+ furthestBlock = element;
+ break;
+ }
+ }
+ // Step 3
+ if (furthestBlock == null) {
+ var element = tree.openElements.removeLast();
+ while (element != formattingElement) {
+ element = tree.openElements.removeLast();
+ }
+ element.endSourceSpan = token.span;
+ tree.activeFormattingElements.remove(element);
+ return;
+ }
+
+ final commonAncestor = tree.openElements[afeIndex - 1];
+
+ // Step 5
+ // The bookmark is supposed to help us identify where to reinsert
+ // nodes in step 12. We have to ensure that we reinsert nodes after
+ // the node before the active formatting element. Note the bookmark
+ // can move in step 7.4
+ var bookmark = tree.activeFormattingElements.indexOf(formattingElement);
+
+ // Step 6
+ var lastNode = furthestBlock;
+ var node = furthestBlock;
+ var innerLoopCounter = 0;
+
+ var index = tree.openElements.indexOf(node);
+ while (innerLoopCounter < 3) {
+ innerLoopCounter += 1;
+
+ // Node is element before node in open elements
+ index -= 1;
+ node = tree.openElements[index];
+ if (!tree.activeFormattingElements.contains(node)) {
+ tree.openElements.remove(node);
+ continue;
+ }
+ // Step 6.3
+ if (node == formattingElement) {
+ break;
+ }
+ // Step 6.4
+ if (lastNode == furthestBlock) {
+ bookmark = tree.activeFormattingElements.indexOf(node) + 1;
+ }
+ // Step 6.5
+ //cite = node.parent
+ final clone = node.clone(false);
+ // Replace node with clone
+ tree.activeFormattingElements[
+ tree.activeFormattingElements.indexOf(node)] = clone;
+ tree.openElements[tree.openElements.indexOf(node)] = clone;
+ node = clone;
+
+ // Step 6.6
+ // Remove lastNode from its parents, if any
+ if (lastNode.parentNode != null) {
+ lastNode.parentNode!.nodes.remove(lastNode);
+ }
+ node.nodes.add(lastNode);
+ // Step 7.7
+ lastNode = node;
+ // End of inner loop
+ }
+
+ // Step 7
+ // Foster parent lastNode if commonAncestor is a
+ // table, tbody, tfoot, thead, or tr we need to foster parent the
+ // lastNode
+ if (lastNode.parentNode != null) {
+ lastNode.parentNode!.nodes.remove(lastNode);
+ }
+
+ if (const ['table', 'tbody', 'tfoot', 'thead', 'tr']
+ .contains(commonAncestor.localName)) {
+ final nodePos = tree.getTableMisnestedNodePosition();
+ nodePos[0]!.insertBefore(lastNode, nodePos[1]);
+ } else {
+ commonAncestor.nodes.add(lastNode);
+ }
+
+ // Step 8
+ final clone = formattingElement.clone(false);
+
+ // Step 9
+ furthestBlock.reparentChildren(clone);
+
+ // Step 10
+ furthestBlock.nodes.add(clone);
+
+ // Step 11
+ tree.activeFormattingElements.remove(formattingElement);
+ tree.activeFormattingElements
+ .insert(min(bookmark, tree.activeFormattingElements.length), clone);
+
+ // Step 12
+ tree.openElements.remove(formattingElement);
+ tree.openElements
+ .insert(tree.openElements.indexOf(furthestBlock) + 1, clone);
+ }
+ }
+
+ void endTagAppletMarqueeObject(EndTagToken token) {
+ if (tree.elementInScope(token.name)) {
+ tree.generateImpliedEndTags();
+ }
+ if (tree.openElements.last.localName != token.name) {
+ parser.parseError(token.span, 'end-tag-too-early', {'name': token.name});
+ }
+ if (tree.elementInScope(token.name)) {
+ popOpenElementsUntil(token);
+ tree.clearActiveFormattingElements();
+ }
+ }
+
+ void endTagBr(EndTagToken token) {
+ parser.parseError(token.span, 'unexpected-end-tag-treated-as',
+ {'originalName': 'br', 'newName': 'br element'});
+ tree.reconstructActiveFormattingElements();
+ tree.insertElement(
+ StartTagToken('br', data: LinkedHashMap<Object, String>()));
+ tree.openElements.removeLast();
+ }
+
+ void endTagOther(EndTagToken token) {
+ for (var node in tree.openElements.reversed) {
+ if (node.localName == token.name) {
+ tree.generateImpliedEndTags(token.name);
+ if (tree.openElements.last.localName != token.name) {
+ parser.parseError(
+ token.span, 'unexpected-end-tag', {'name': token.name});
+ }
+ while (tree.openElements.removeLast() != node) {
+ // noop
+ }
+ node.endSourceSpan = token.span;
+ break;
+ } else {
+ if (specialElements.contains(getElementNameTuple(node))) {
+ parser.parseError(
+ token.span, 'unexpected-end-tag', {'name': token.name});
+ break;
+ }
+ }
+ }
+ }
+}
+
+class TextPhase extends Phase {
+ TextPhase(super.parser);
+
+ // "Tried to process start tag %s in RCDATA/RAWTEXT mode"%token.name
+ @override
+ Token processStartTag(StartTagToken token) {
+ throw StateError('Cannot process start stag in text phase');
+ }
+
+ @override
+ Token? processEndTag(EndTagToken token) {
+ if (token.name == 'script') {
+ endTagScript(token);
+ return null;
+ }
+ endTagOther(token);
+ return null;
+ }
+
+ @override
+ Token? processCharacters(CharactersToken token) {
+ tree.insertText(token.data, token.span);
+ return null;
+ }
+
+ @override
+ bool processEOF() {
+ final last = tree.openElements.last;
+ parser.parseError(last.sourceSpan, 'expected-named-closing-tag-but-got-eof',
+ {'name': last.localName});
+ tree.openElements.removeLast();
+ parser.phase = parser.originalPhase!;
+ return true;
+ }
+
+ void endTagScript(EndTagToken token) {
+ final node = tree.openElements.removeLast();
+ assert(node.localName == 'script');
+ parser.phase = parser.originalPhase!;
+ //The rest of this method is all stuff that only happens if
+ //document.write works
+ }
+
+ void endTagOther(EndTagToken token) {
+ tree.openElements.removeLast();
+ parser.phase = parser.originalPhase!;
+ }
+}
+
+class InTablePhase extends Phase {
+ // http://www.whatwg.org/specs/web-apps/current-work///in-table
+ InTablePhase(super.parser);
+
+ @override
+ Token? processStartTag(StartTagToken token) {
+ switch (token.name) {
+ case 'html':
+ return startTagHtml(token);
+ case 'caption':
+ startTagCaption(token);
+ return null;
+ case 'colgroup':
+ startTagColgroup(token);
+ return null;
+ case 'col':
+ return startTagCol(token);
+ case 'tbody':
+ case 'tfoot':
+ case 'thead':
+ startTagRowGroup(token);
+ return null;
+ case 'td':
+ case 'th':
+ case 'tr':
+ return startTagImplyTbody(token);
+ case 'table':
+ return startTagTable(token);
+ case 'style':
+ case 'script':
+ return startTagStyleScript(token);
+ case 'input':
+ startTagInput(token);
+ return null;
+ case 'form':
+ startTagForm(token);
+ return null;
+ default:
+ startTagOther(token);
+ return null;
+ }
+ }
+
+ @override
+ Token? processEndTag(EndTagToken token) {
+ switch (token.name) {
+ case 'table':
+ endTagTable(token);
+ return null;
+ case 'body':
+ case 'caption':
+ case 'col':
+ case 'colgroup':
+ case 'html':
+ case 'tbody':
+ case 'td':
+ case 'tfoot':
+ case 'th':
+ case 'thead':
+ case 'tr':
+ endTagIgnore(token);
+ return null;
+ default:
+ endTagOther(token);
+ return null;
+ }
+ }
+
+ // helper methods
+ void clearStackToTableContext() {
+ // 'clear the stack back to a table context'
+ while (tree.openElements.last.localName != 'table' &&
+ tree.openElements.last.localName != 'html') {
+ //parser.parseError(token.span, "unexpected-implied-end-tag-in-table",
+ // {"name": tree.openElements.last.name})
+ tree.openElements.removeLast();
+ }
+ // When the current node is <html> it's an innerHTML case
+ }
+
+ // processing methods
+ @override
+ bool processEOF() {
+ final last = tree.openElements.last;
+ if (last.localName != 'html') {
+ parser.parseError(last.sourceSpan, 'eof-in-table');
+ } else {
+ assert(parser.innerHTMLMode);
+ }
+ //Stop parsing
+ return false;
+ }
+
+ @override
+ Token? processSpaceCharacters(SpaceCharactersToken token) {
+ final originalPhase = parser.phase;
+ parser.phase = parser._inTableTextPhase;
+ parser._inTableTextPhase.originalPhase = originalPhase;
+ parser.phase.processSpaceCharacters(token);
+ return null;
+ }
+
+ @override
+ Token? processCharacters(CharactersToken token) {
+ final originalPhase = parser.phase;
+ parser.phase = parser._inTableTextPhase;
+ parser._inTableTextPhase.originalPhase = originalPhase;
+ parser.phase.processCharacters(token);
+ return null;
+ }
+
+ void insertText(CharactersToken token) {
+ // If we get here there must be at least one non-whitespace character
+ // Do the table magic!
+ tree.insertFromTable = true;
+ parser._inBodyPhase.processCharacters(token);
+ tree.insertFromTable = false;
+ }
+
+ void startTagCaption(StartTagToken token) {
+ clearStackToTableContext();
+ tree.activeFormattingElements.add(null);
+ tree.insertElement(token);
+ parser.phase = parser._inCaptionPhase;
+ }
+
+ void startTagColgroup(StartTagToken token) {
+ clearStackToTableContext();
+ tree.insertElement(token);
+ parser.phase = parser._inColumnGroupPhase;
+ }
+
+ Token startTagCol(StartTagToken token) {
+ startTagColgroup(
+ StartTagToken('colgroup', data: LinkedHashMap<Object, String>()));
+ return token;
+ }
+
+ void startTagRowGroup(StartTagToken token) {
+ clearStackToTableContext();
+ tree.insertElement(token);
+ parser.phase = parser._inTableBodyPhase;
+ }
+
+ Token startTagImplyTbody(StartTagToken token) {
+ startTagRowGroup(
+ StartTagToken('tbody', data: LinkedHashMap<Object, String>()));
+ return token;
+ }
+
+ Token? startTagTable(StartTagToken token) {
+ parser.parseError(token.span, 'unexpected-start-tag-implies-end-tag',
+ {'startName': 'table', 'endName': 'table'});
+ parser.phase.processEndTag(EndTagToken('table'));
+ if (!parser.innerHTMLMode) {
+ return token;
+ }
+ return null;
+ }
+
+ Token? startTagStyleScript(StartTagToken token) {
+ return parser._inHeadPhase.processStartTag(token);
+ }
+
+ void startTagInput(StartTagToken token) {
+ if (token.data['type']?.toAsciiLowerCase() == 'hidden') {
+ parser.parseError(token.span, 'unexpected-hidden-input-in-table');
+ tree.insertElement(token);
+ // XXX associate with form
+ tree.openElements.removeLast();
+ } else {
+ startTagOther(token);
+ }
+ }
+
+ void startTagForm(StartTagToken token) {
+ parser.parseError(token.span, 'unexpected-form-in-table');
+ if (tree.formPointer == null) {
+ tree.insertElement(token);
+ tree.formPointer = tree.openElements.last;
+ tree.openElements.removeLast();
+ }
+ }
+
+ void startTagOther(StartTagToken token) {
+ parser.parseError(token.span, 'unexpected-start-tag-implies-table-voodoo',
+ {'name': token.name});
+ // Do the table magic!
+ tree.insertFromTable = true;
+ parser._inBodyPhase.processStartTag(token);
+ tree.insertFromTable = false;
+ }
+
+ void endTagTable(EndTagToken token) {
+ if (tree.elementInScope('table', variant: 'table')) {
+ tree.generateImpliedEndTags();
+ final last = tree.openElements.last;
+ if (last.localName != 'table') {
+ parser.parseError(token.span, 'end-tag-too-early-named',
+ {'gotName': 'table', 'expectedName': last.localName});
+ }
+ while (tree.openElements.last.localName != 'table') {
+ tree.openElements.removeLast();
+ }
+ final node = tree.openElements.removeLast();
+ node.endSourceSpan = token.span;
+ parser.resetInsertionMode();
+ } else {
+ // innerHTML case
+ assert(parser.innerHTMLMode);
+ parser.parseError(token.span, 'undefined-error');
+ }
+ }
+
+ void endTagIgnore(EndTagToken token) {
+ parser.parseError(token.span, 'unexpected-end-tag', {'name': token.name});
+ }
+
+ void endTagOther(EndTagToken token) {
+ parser.parseError(token.span, 'unexpected-end-tag-implies-table-voodoo',
+ {'name': token.name});
+ // Do the table magic!
+ tree.insertFromTable = true;
+ parser._inBodyPhase.processEndTag(token);
+ tree.insertFromTable = false;
+ }
+}
+
+class InTableTextPhase extends Phase {
+ Phase? originalPhase;
+ List<StringToken> characterTokens;
+
+ InTableTextPhase(super.parser) : characterTokens = <StringToken>[];
+
+ void flushCharacters() {
+ if (characterTokens.isEmpty) return;
+
+ // TODO(sigmund,jmesserly): remove '' (dartbug.com/8480)
+ final data = characterTokens.map((t) => t.data).join('');
+ FileSpan? span;
+
+ if (parser.generateSpans) {
+ span = characterTokens[0].span!.expand(characterTokens.last.span!);
+ }
+
+ if (!allWhitespace(data)) {
+ parser._inTablePhase.insertText(CharactersToken(data)..span = span);
+ } else if (data.isNotEmpty) {
+ tree.insertText(data, span);
+ }
+ characterTokens = <StringToken>[];
+ }
+
+ @override
+ Token processComment(CommentToken token) {
+ flushCharacters();
+ parser.phase = originalPhase!;
+ return token;
+ }
+
+ @override
+ bool processEOF() {
+ flushCharacters();
+ parser.phase = originalPhase!;
+ return true;
+ }
+
+ @override
+ Token? processCharacters(CharactersToken token) {
+ if (token.data == '\u0000') {
+ return null;
+ }
+ characterTokens.add(token);
+ return null;
+ }
+
+ @override
+ Token? processSpaceCharacters(SpaceCharactersToken token) {
+ //pretty sure we should never reach here
+ characterTokens.add(token);
+ // XXX assert(false);
+ return null;
+ }
+
+ @override
+ Token processStartTag(StartTagToken token) {
+ flushCharacters();
+ parser.phase = originalPhase!;
+ return token;
+ }
+
+ @override
+ Token processEndTag(EndTagToken token) {
+ flushCharacters();
+ parser.phase = originalPhase!;
+ return token;
+ }
+}
+
+class InCaptionPhase extends Phase {
+ // http://www.whatwg.org/specs/web-apps/current-work///in-caption
+ InCaptionPhase(super.parser);
+
+ @override
+ Token? processStartTag(StartTagToken token) {
+ switch (token.name) {
+ case 'html':
+ return startTagHtml(token);
+ case 'caption':
+ case 'col':
+ case 'colgroup':
+ case 'tbody':
+ case 'td':
+ case 'tfoot':
+ case 'th':
+ case 'thead':
+ case 'tr':
+ return startTagTableElement(token);
+ default:
+ return startTagOther(token);
+ }
+ }
+
+ @override
+ Token? processEndTag(EndTagToken token) {
+ switch (token.name) {
+ case 'caption':
+ endTagCaption(token);
+ return null;
+ case 'table':
+ return endTagTable(token);
+ case 'body':
+ case 'col':
+ case 'colgroup':
+ case 'html':
+ case 'tbody':
+ case 'td':
+ case 'tfoot':
+ case 'th':
+ case 'thead':
+ case 'tr':
+ endTagIgnore(token);
+ return null;
+ default:
+ return endTagOther(token);
+ }
+ }
+
+ bool ignoreEndTagCaption() {
+ return !tree.elementInScope('caption', variant: 'table');
+ }
+
+ @override
+ bool processEOF() {
+ parser._inBodyPhase.processEOF();
+ return false;
+ }
+
+ @override
+ Token? processCharacters(CharactersToken token) {
+ return parser._inBodyPhase.processCharacters(token);
+ }
+
+ Token? startTagTableElement(StartTagToken token) {
+ parser.parseError(token.span, 'undefined-error');
+ //XXX Have to duplicate logic here to find out if the tag is ignored
+ final ignoreEndTag = ignoreEndTagCaption();
+ parser.phase.processEndTag(EndTagToken('caption'));
+ if (!ignoreEndTag) {
+ return token;
+ }
+ return null;
+ }
+
+ Token? startTagOther(StartTagToken token) {
+ return parser._inBodyPhase.processStartTag(token);
+ }
+
+ void endTagCaption(EndTagToken token) {
+ if (!ignoreEndTagCaption()) {
+ // AT this code is quite similar to endTagTable in "InTable"
+ tree.generateImpliedEndTags();
+ if (tree.openElements.last.localName != 'caption') {
+ parser.parseError(token.span, 'expected-one-end-tag-but-got-another', {
+ 'gotName': 'caption',
+ 'expectedName': tree.openElements.last.localName
+ });
+ }
+ while (tree.openElements.last.localName != 'caption') {
+ tree.openElements.removeLast();
+ }
+ final node = tree.openElements.removeLast();
+ node.endSourceSpan = token.span;
+ tree.clearActiveFormattingElements();
+ parser.phase = parser._inTablePhase;
+ } else {
+ // innerHTML case
+ assert(parser.innerHTMLMode);
+ parser.parseError(token.span, 'undefined-error');
+ }
+ }
+
+ Token? endTagTable(EndTagToken token) {
+ parser.parseError(token.span, 'undefined-error');
+ final ignoreEndTag = ignoreEndTagCaption();
+ parser.phase.processEndTag(EndTagToken('caption'));
+ if (!ignoreEndTag) {
+ return token;
+ }
+ return null;
+ }
+
+ void endTagIgnore(EndTagToken token) {
+ parser.parseError(token.span, 'unexpected-end-tag', {'name': token.name});
+ }
+
+ Token? endTagOther(EndTagToken token) {
+ return parser._inBodyPhase.processEndTag(token);
+ }
+}
+
+class InColumnGroupPhase extends Phase {
+ // http://www.whatwg.org/specs/web-apps/current-work///in-column
+ InColumnGroupPhase(super.parser);
+
+ @override
+ Token? processStartTag(StartTagToken token) {
+ switch (token.name) {
+ case 'html':
+ return startTagHtml(token);
+ case 'col':
+ startTagCol(token);
+ return null;
+ default:
+ return startTagOther(token);
+ }
+ }
+
+ @override
+ Token? processEndTag(EndTagToken token) {
+ switch (token.name) {
+ case 'colgroup':
+ endTagColgroup(token);
+ return null;
+ case 'col':
+ endTagCol(token);
+ return null;
+ default:
+ return endTagOther(token);
+ }
+ }
+
+ bool ignoreEndTagColgroup() {
+ return tree.openElements.last.localName == 'html';
+ }
+
+ @override
+ bool processEOF() {
+ final ignoreEndTag = ignoreEndTagColgroup();
+ if (ignoreEndTag) {
+ assert(parser.innerHTMLMode);
+ return false;
+ } else {
+ endTagColgroup(EndTagToken('colgroup'));
+ return true;
+ }
+ }
+
+ @override
+ Token? processCharacters(CharactersToken token) {
+ final ignoreEndTag = ignoreEndTagColgroup();
+ endTagColgroup(EndTagToken('colgroup'));
+ return ignoreEndTag ? null : token;
+ }
+
+ void startTagCol(StartTagToken token) {
+ tree.insertElement(token);
+ tree.openElements.removeLast();
+ }
+
+ Token? startTagOther(StartTagToken token) {
+ final ignoreEndTag = ignoreEndTagColgroup();
+ endTagColgroup(EndTagToken('colgroup'));
+ return ignoreEndTag ? null : token;
+ }
+
+ void endTagColgroup(EndTagToken token) {
+ if (ignoreEndTagColgroup()) {
+ // innerHTML case
+ assert(parser.innerHTMLMode);
+ parser.parseError(token.span, 'undefined-error');
+ } else {
+ final node = tree.openElements.removeLast();
+ node.endSourceSpan = token.span;
+ parser.phase = parser._inTablePhase;
+ }
+ }
+
+ void endTagCol(EndTagToken token) {
+ parser.parseError(token.span, 'no-end-tag', {'name': 'col'});
+ }
+
+ Token? endTagOther(EndTagToken token) {
+ final ignoreEndTag = ignoreEndTagColgroup();
+ endTagColgroup(EndTagToken('colgroup'));
+ return ignoreEndTag ? null : token;
+ }
+}
+
+class InTableBodyPhase extends Phase {
+ // http://www.whatwg.org/specs/web-apps/current-work///in-table0
+ InTableBodyPhase(super.parser);
+
+ @override
+ Token? processStartTag(StartTagToken token) {
+ switch (token.name) {
+ case 'html':
+ return startTagHtml(token);
+ case 'tr':
+ startTagTr(token);
+ return null;
+ case 'td':
+ case 'th':
+ return startTagTableCell(token);
+ case 'caption':
+ case 'col':
+ case 'colgroup':
+ case 'tbody':
+ case 'tfoot':
+ case 'thead':
+ return startTagTableOther(token);
+ default:
+ return startTagOther(token);
+ }
+ }
+
+ @override
+ Token? processEndTag(EndTagToken token) {
+ switch (token.name) {
+ case 'tbody':
+ case 'tfoot':
+ case 'thead':
+ endTagTableRowGroup(token);
+ return null;
+ case 'table':
+ return endTagTable(token);
+ case 'body':
+ case 'caption':
+ case 'col':
+ case 'colgroup':
+ case 'html':
+ case 'td':
+ case 'th':
+ case 'tr':
+ endTagIgnore(token);
+ return null;
+ default:
+ return endTagOther(token);
+ }
+ }
+
+ // helper methods
+ void clearStackToTableBodyContext() {
+ final tableTags = const ['tbody', 'tfoot', 'thead', 'html'];
+ while (!tableTags.contains(tree.openElements.last.localName)) {
+ //XXX parser.parseError(token.span, "unexpected-implied-end-tag-in-table",
+ // {"name": tree.openElements.last.name})
+ tree.openElements.removeLast();
+ }
+ if (tree.openElements.last.localName == 'html') {
+ assert(parser.innerHTMLMode);
+ }
+ }
+
+ // the rest
+ @override
+ bool processEOF() {
+ parser._inTablePhase.processEOF();
+ return false;
+ }
+
+ @override
+ Token? processSpaceCharacters(SpaceCharactersToken token) {
+ return parser._inTablePhase.processSpaceCharacters(token);
+ }
+
+ @override
+ Token? processCharacters(CharactersToken token) {
+ return parser._inTablePhase.processCharacters(token);
+ }
+
+ void startTagTr(StartTagToken token) {
+ clearStackToTableBodyContext();
+ tree.insertElement(token);
+ parser.phase = parser._inRowPhase;
+ }
+
+ Token startTagTableCell(StartTagToken token) {
+ parser.parseError(
+ token.span, 'unexpected-cell-in-table-body', {'name': token.name});
+ startTagTr(StartTagToken('tr', data: LinkedHashMap<Object, String>()));
+ return token;
+ }
+
+ Token? startTagTableOther(TagToken token) => endTagTable(token);
+
+ Token? startTagOther(StartTagToken token) {
+ return parser._inTablePhase.processStartTag(token);
+ }
+
+ void endTagTableRowGroup(EndTagToken token) {
+ if (tree.elementInScope(token.name, variant: 'table')) {
+ clearStackToTableBodyContext();
+ final node = tree.openElements.removeLast();
+ node.endSourceSpan = token.span;
+ parser.phase = parser._inTablePhase;
+ } else {
+ parser.parseError(
+ token.span, 'unexpected-end-tag-in-table-body', {'name': token.name});
+ }
+ }
+
+ Token? endTagTable(TagToken token) {
+ // XXX AT Any ideas on how to share this with endTagTable?
+ if (tree.elementInScope('tbody', variant: 'table') ||
+ tree.elementInScope('thead', variant: 'table') ||
+ tree.elementInScope('tfoot', variant: 'table')) {
+ clearStackToTableBodyContext();
+ endTagTableRowGroup(EndTagToken(tree.openElements.last.localName));
+ return token;
+ } else {
+ // innerHTML case
+ assert(parser.innerHTMLMode);
+ parser.parseError(token.span, 'undefined-error');
+ }
+ return null;
+ }
+
+ void endTagIgnore(EndTagToken token) {
+ parser.parseError(
+ token.span, 'unexpected-end-tag-in-table-body', {'name': token.name});
+ }
+
+ Token? endTagOther(EndTagToken token) {
+ return parser._inTablePhase.processEndTag(token);
+ }
+}
+
+class InRowPhase extends Phase {
+ // http://www.whatwg.org/specs/web-apps/current-work///in-row
+ InRowPhase(super.parser);
+
+ @override
+ Token? processStartTag(StartTagToken token) {
+ switch (token.name) {
+ case 'html':
+ return startTagHtml(token);
+ case 'td':
+ case 'th':
+ startTagTableCell(token);
+ return null;
+ case 'caption':
+ case 'col':
+ case 'colgroup':
+ case 'tbody':
+ case 'tfoot':
+ case 'thead':
+ case 'tr':
+ return startTagTableOther(token);
+ default:
+ return startTagOther(token);
+ }
+ }
+
+ @override
+ Token? processEndTag(EndTagToken token) {
+ switch (token.name) {
+ case 'tr':
+ endTagTr(token);
+ return null;
+ case 'table':
+ return endTagTable(token);
+ case 'tbody':
+ case 'tfoot':
+ case 'thead':
+ return endTagTableRowGroup(token);
+ case 'body':
+ case 'caption':
+ case 'col':
+ case 'colgroup':
+ case 'html':
+ case 'td':
+ case 'th':
+ endTagIgnore(token);
+ return null;
+ default:
+ return endTagOther(token);
+ }
+ }
+
+ // helper methods (XXX unify this with other table helper methods)
+ void clearStackToTableRowContext() {
+ while (true) {
+ final last = tree.openElements.last;
+ if (last.localName == 'tr' || last.localName == 'html') break;
+
+ parser.parseError(
+ last.sourceSpan,
+ 'unexpected-implied-end-tag-in-table-row',
+ {'name': tree.openElements.last.localName});
+ tree.openElements.removeLast();
+ }
+ }
+
+ bool ignoreEndTagTr() {
+ return !tree.elementInScope('tr', variant: 'table');
+ }
+
+ // the rest
+ @override
+ bool processEOF() {
+ parser._inTablePhase.processEOF();
+ return false;
+ }
+
+ @override
+ Token? processSpaceCharacters(SpaceCharactersToken token) {
+ return parser._inTablePhase.processSpaceCharacters(token);
+ }
+
+ @override
+ Token? processCharacters(CharactersToken token) {
+ return parser._inTablePhase.processCharacters(token);
+ }
+
+ void startTagTableCell(StartTagToken token) {
+ clearStackToTableRowContext();
+ tree.insertElement(token);
+ parser.phase = parser._inCellPhase;
+ tree.activeFormattingElements.add(null);
+ }
+
+ Token? startTagTableOther(StartTagToken token) {
+ final ignoreEndTag = ignoreEndTagTr();
+ endTagTr(EndTagToken('tr'));
+ // XXX how are we sure it's always ignored in the innerHTML case?
+ return ignoreEndTag ? null : token;
+ }
+
+ Token? startTagOther(StartTagToken token) {
+ return parser._inTablePhase.processStartTag(token);
+ }
+
+ void endTagTr(EndTagToken token) {
+ if (!ignoreEndTagTr()) {
+ clearStackToTableRowContext();
+ final node = tree.openElements.removeLast();
+ node.endSourceSpan = token.span;
+ parser.phase = parser._inTableBodyPhase;
+ } else {
+ // innerHTML case
+ assert(parser.innerHTMLMode);
+ parser.parseError(token.span, 'undefined-error');
+ }
+ }
+
+ Token? endTagTable(EndTagToken token) {
+ final ignoreEndTag = ignoreEndTagTr();
+ endTagTr(EndTagToken('tr'));
+ // Reprocess the current tag if the tr end tag was not ignored
+ // XXX how are we sure it's always ignored in the innerHTML case?
+ return ignoreEndTag ? null : token;
+ }
+
+ Token? endTagTableRowGroup(EndTagToken token) {
+ if (tree.elementInScope(token.name, variant: 'table')) {
+ endTagTr(EndTagToken('tr'));
+ return token;
+ } else {
+ parser.parseError(token.span, 'undefined-error');
+ return null;
+ }
+ }
+
+ void endTagIgnore(EndTagToken token) {
+ parser.parseError(
+ token.span, 'unexpected-end-tag-in-table-row', {'name': token.name});
+ }
+
+ Token? endTagOther(EndTagToken token) {
+ return parser._inTablePhase.processEndTag(token);
+ }
+}
+
+class InCellPhase extends Phase {
+ // http://www.whatwg.org/specs/web-apps/current-work///in-cell
+ InCellPhase(super.parser);
+
+ @override
+ Token? processStartTag(StartTagToken token) {
+ switch (token.name) {
+ case 'html':
+ return startTagHtml(token);
+ case 'caption':
+ case 'col':
+ case 'colgroup':
+ case 'tbody':
+ case 'td':
+ case 'tfoot':
+ case 'th':
+ case 'thead':
+ case 'tr':
+ return startTagTableOther(token);
+ default:
+ return startTagOther(token);
+ }
+ }
+
+ @override
+ Token? processEndTag(EndTagToken token) {
+ switch (token.name) {
+ case 'td':
+ case 'th':
+ endTagTableCell(token);
+ return null;
+ case 'body':
+ case 'caption':
+ case 'col':
+ case 'colgroup':
+ case 'html':
+ endTagIgnore(token);
+ return null;
+ case 'table':
+ case 'tbody':
+ case 'tfoot':
+ case 'thead':
+ case 'tr':
+ return endTagImply(token);
+ default:
+ return endTagOther(token);
+ }
+ }
+
+ // helper
+ void closeCell() {
+ if (tree.elementInScope('td', variant: 'table')) {
+ endTagTableCell(EndTagToken('td'));
+ } else if (tree.elementInScope('th', variant: 'table')) {
+ endTagTableCell(EndTagToken('th'));
+ }
+ }
+
+ // the rest
+ @override
+ bool processEOF() {
+ parser._inBodyPhase.processEOF();
+ return false;
+ }
+
+ @override
+ Token? processCharacters(CharactersToken token) {
+ return parser._inBodyPhase.processCharacters(token);
+ }
+
+ Token? startTagTableOther(StartTagToken token) {
+ if (tree.elementInScope('td', variant: 'table') ||
+ tree.elementInScope('th', variant: 'table')) {
+ closeCell();
+ return token;
+ } else {
+ // innerHTML case
+ assert(parser.innerHTMLMode);
+ parser.parseError(token.span, 'undefined-error');
+ return null;
+ }
+ }
+
+ Token? startTagOther(StartTagToken token) {
+ return parser._inBodyPhase.processStartTag(token);
+ }
+
+ void endTagTableCell(EndTagToken token) {
+ if (tree.elementInScope(token.name, variant: 'table')) {
+ tree.generateImpliedEndTags(token.name);
+ if (tree.openElements.last.localName != token.name) {
+ parser.parseError(
+ token.span, 'unexpected-cell-end-tag', {'name': token.name});
+ popOpenElementsUntil(token);
+ } else {
+ final node = tree.openElements.removeLast();
+ node.endSourceSpan = token.span;
+ }
+ tree.clearActiveFormattingElements();
+ parser.phase = parser._inRowPhase;
+ } else {
+ parser.parseError(token.span, 'unexpected-end-tag', {'name': token.name});
+ }
+ }
+
+ void endTagIgnore(EndTagToken token) {
+ parser.parseError(token.span, 'unexpected-end-tag', {'name': token.name});
+ }
+
+ Token? endTagImply(EndTagToken token) {
+ if (tree.elementInScope(token.name, variant: 'table')) {
+ closeCell();
+ return token;
+ } else {
+ // sometimes innerHTML case
+ parser.parseError(token.span, 'undefined-error');
+ }
+ return null;
+ }
+
+ Token? endTagOther(EndTagToken token) {
+ return parser._inBodyPhase.processEndTag(token);
+ }
+}
+
+class InSelectPhase extends Phase {
+ InSelectPhase(super.parser);
+
+ @override
+ Token? processStartTag(StartTagToken token) {
+ switch (token.name) {
+ case 'html':
+ return startTagHtml(token);
+ case 'option':
+ startTagOption(token);
+ return null;
+ case 'optgroup':
+ startTagOptgroup(token);
+ return null;
+ case 'select':
+ startTagSelect(token);
+ return null;
+ case 'input':
+ case 'keygen':
+ case 'textarea':
+ return startTagInput(token);
+ case 'script':
+ return startTagScript(token);
+ default:
+ return startTagOther(token);
+ }
+ }
+
+ @override
+ Token? processEndTag(EndTagToken token) {
+ switch (token.name) {
+ case 'option':
+ endTagOption(token);
+ return null;
+ case 'optgroup':
+ endTagOptgroup(token);
+ return null;
+ case 'select':
+ endTagSelect(token);
+ return null;
+ default:
+ endTagOther(token);
+ return null;
+ }
+ }
+
+ // http://www.whatwg.org/specs/web-apps/current-work///in-select
+ @override
+ bool processEOF() {
+ final last = tree.openElements.last;
+ if (last.localName != 'html') {
+ parser.parseError(last.sourceSpan, 'eof-in-select');
+ } else {
+ assert(parser.innerHTMLMode);
+ }
+ return false;
+ }
+
+ @override
+ Token? processCharacters(CharactersToken token) {
+ if (token.data == '\u0000') {
+ return null;
+ }
+ tree.insertText(token.data, token.span);
+ return null;
+ }
+
+ void startTagOption(StartTagToken token) {
+ // We need to imply </option> if <option> is the current node.
+ if (tree.openElements.last.localName == 'option') {
+ tree.openElements.removeLast();
+ }
+ tree.insertElement(token);
+ }
+
+ void startTagOptgroup(StartTagToken token) {
+ if (tree.openElements.last.localName == 'option') {
+ tree.openElements.removeLast();
+ }
+ if (tree.openElements.last.localName == 'optgroup') {
+ tree.openElements.removeLast();
+ }
+ tree.insertElement(token);
+ }
+
+ void startTagSelect(StartTagToken token) {
+ parser.parseError(token.span, 'unexpected-select-in-select');
+ endTagSelect(EndTagToken('select'));
+ }
+
+ Token? startTagInput(StartTagToken token) {
+ parser.parseError(token.span, 'unexpected-input-in-select');
+ if (tree.elementInScope('select', variant: 'select')) {
+ endTagSelect(EndTagToken('select'));
+ return token;
+ } else {
+ assert(parser.innerHTMLMode);
+ }
+ return null;
+ }
+
+ Token? startTagScript(StartTagToken token) {
+ return parser._inHeadPhase.processStartTag(token);
+ }
+
+ Token? startTagOther(StartTagToken token) {
+ parser.parseError(
+ token.span, 'unexpected-start-tag-in-select', {'name': token.name});
+ return null;
+ }
+
+ void endTagOption(EndTagToken token) {
+ if (tree.openElements.last.localName == 'option') {
+ final node = tree.openElements.removeLast();
+ node.endSourceSpan = token.span;
+ } else {
+ parser.parseError(
+ token.span, 'unexpected-end-tag-in-select', {'name': 'option'});
+ }
+ }
+
+ void endTagOptgroup(EndTagToken token) {
+ // </optgroup> implicitly closes <option>
+ if (tree.openElements.last.localName == 'option' &&
+ tree.openElements[tree.openElements.length - 2].localName ==
+ 'optgroup') {
+ tree.openElements.removeLast();
+ }
+ // It also closes </optgroup>
+ if (tree.openElements.last.localName == 'optgroup') {
+ final node = tree.openElements.removeLast();
+ node.endSourceSpan = token.span;
+ // But nothing else
+ } else {
+ parser.parseError(
+ token.span, 'unexpected-end-tag-in-select', {'name': 'optgroup'});
+ }
+ }
+
+ void endTagSelect(EndTagToken token) {
+ if (tree.elementInScope('select', variant: 'select')) {
+ popOpenElementsUntil(token);
+ parser.resetInsertionMode();
+ } else {
+ // innerHTML case
+ assert(parser.innerHTMLMode);
+ parser.parseError(token.span, 'undefined-error');
+ }
+ }
+
+ void endTagOther(EndTagToken token) {
+ parser.parseError(
+ token.span, 'unexpected-end-tag-in-select', {'name': token.name});
+ }
+}
+
+class InSelectInTablePhase extends Phase {
+ InSelectInTablePhase(super.parser);
+
+ @override
+ Token? processStartTag(StartTagToken token) {
+ switch (token.name) {
+ case 'caption':
+ case 'table':
+ case 'tbody':
+ case 'tfoot':
+ case 'thead':
+ case 'tr':
+ case 'td':
+ case 'th':
+ return startTagTable(token);
+ default:
+ return startTagOther(token);
+ }
+ }
+
+ @override
+ Token? processEndTag(EndTagToken token) {
+ switch (token.name) {
+ case 'caption':
+ case 'table':
+ case 'tbody':
+ case 'tfoot':
+ case 'thead':
+ case 'tr':
+ case 'td':
+ case 'th':
+ return endTagTable(token);
+ default:
+ return endTagOther(token);
+ }
+ }
+
+ @override
+ bool processEOF() {
+ parser._inSelectPhase.processEOF();
+ return false;
+ }
+
+ @override
+ Token? processCharacters(CharactersToken token) {
+ return parser._inSelectPhase.processCharacters(token);
+ }
+
+ Token startTagTable(StartTagToken token) {
+ parser.parseError(
+ token.span,
+ 'unexpected-table-element-start-tag-in-select-in-table',
+ {'name': token.name});
+ endTagOther(EndTagToken('select'));
+ return token;
+ }
+
+ Token? startTagOther(StartTagToken token) {
+ return parser._inSelectPhase.processStartTag(token);
+ }
+
+ Token? endTagTable(EndTagToken token) {
+ parser.parseError(
+ token.span,
+ 'unexpected-table-element-end-tag-in-select-in-table',
+ {'name': token.name});
+ if (tree.elementInScope(token.name, variant: 'table')) {
+ endTagOther(EndTagToken('select'));
+ return token;
+ }
+ return null;
+ }
+
+ Token? endTagOther(EndTagToken token) {
+ return parser._inSelectPhase.processEndTag(token);
+ }
+}
+
+class InForeignContentPhase extends Phase {
+ // TODO(jmesserly): this is sorted so we could binary search.
+ static const breakoutElements = [
+ 'b',
+ 'big',
+ 'blockquote',
+ 'body',
+ 'br',
+ 'center',
+ 'code',
+ 'dd',
+ 'div',
+ 'dl',
+ 'dt',
+ 'em',
+ 'embed',
+ 'h1',
+ 'h2',
+ 'h3',
+ 'h4',
+ 'h5',
+ 'h6',
+ 'head',
+ 'hr',
+ 'i',
+ 'img',
+ 'li',
+ 'listing',
+ 'menu',
+ 'meta',
+ 'nobr',
+ 'ol',
+ 'p',
+ 'pre',
+ 'ruby',
+ 's',
+ 'small',
+ 'span',
+ 'strike',
+ 'strong',
+ 'sub',
+ 'sup',
+ 'table',
+ 'tt',
+ 'u',
+ 'ul',
+ 'var'
+ ];
+
+ InForeignContentPhase(super.parser);
+
+ void adjustSVGTagNames(StartTagToken token) {
+ final replacements = const {
+ 'altglyph': 'altGlyph',
+ 'altglyphdef': 'altGlyphDef',
+ 'altglyphitem': 'altGlyphItem',
+ 'animatecolor': 'animateColor',
+ 'animatemotion': 'animateMotion',
+ 'animatetransform': 'animateTransform',
+ 'clippath': 'clipPath',
+ 'feblend': 'feBlend',
+ 'fecolormatrix': 'feColorMatrix',
+ 'fecomponenttransfer': 'feComponentTransfer',
+ 'fecomposite': 'feComposite',
+ 'feconvolvematrix': 'feConvolveMatrix',
+ 'fediffuselighting': 'feDiffuseLighting',
+ 'fedisplacementmap': 'feDisplacementMap',
+ 'fedistantlight': 'feDistantLight',
+ 'feflood': 'feFlood',
+ 'fefunca': 'feFuncA',
+ 'fefuncb': 'feFuncB',
+ 'fefuncg': 'feFuncG',
+ 'fefuncr': 'feFuncR',
+ 'fegaussianblur': 'feGaussianBlur',
+ 'feimage': 'feImage',
+ 'femerge': 'feMerge',
+ 'femergenode': 'feMergeNode',
+ 'femorphology': 'feMorphology',
+ 'feoffset': 'feOffset',
+ 'fepointlight': 'fePointLight',
+ 'fespecularlighting': 'feSpecularLighting',
+ 'fespotlight': 'feSpotLight',
+ 'fetile': 'feTile',
+ 'feturbulence': 'feTurbulence',
+ 'foreignobject': 'foreignObject',
+ 'glyphref': 'glyphRef',
+ 'lineargradient': 'linearGradient',
+ 'radialgradient': 'radialGradient',
+ 'textpath': 'textPath'
+ };
+
+ final replace = replacements[token.name];
+ if (replace != null) {
+ token.name = replace;
+ }
+ }
+
+ @override
+ Token? processCharacters(CharactersToken token) {
+ if (token.data == '\u0000') {
+ token.replaceData('\uFFFD');
+ } else if (parser.framesetOK && !allWhitespace(token.data)) {
+ parser.framesetOK = false;
+ }
+ return super.processCharacters(token);
+ }
+
+ @override
+ Token? processStartTag(StartTagToken token) {
+ final currentNode = tree.openElements.last;
+ if (breakoutElements.contains(token.name) ||
+ (token.name == 'font' &&
+ (token.data.containsKey('color') ||
+ token.data.containsKey('face') ||
+ token.data.containsKey('size')))) {
+ parser.parseError(token.span,
+ 'unexpected-html-element-in-foreign-content', {'name': token.name});
+ while (tree.openElements.last.namespaceUri != tree.defaultNamespace &&
+ !parser.isHTMLIntegrationPoint(tree.openElements.last) &&
+ !parser.isMathMLTextIntegrationPoint(tree.openElements.last)) {
+ tree.openElements.removeLast();
+ }
+ return token;
+ } else {
+ if (currentNode.namespaceUri == Namespaces.mathml) {
+ parser.adjustMathMLAttributes(token);
+ } else if (currentNode.namespaceUri == Namespaces.svg) {
+ adjustSVGTagNames(token);
+ parser.adjustSVGAttributes(token);
+ }
+ parser.adjustForeignAttributes(token);
+ token.namespace = currentNode.namespaceUri;
+ tree.insertElement(token);
+ if (token.selfClosing) {
+ tree.openElements.removeLast();
+ token.selfClosingAcknowledged = true;
+ }
+ return null;
+ }
+ }
+
+ @override
+ Token? processEndTag(EndTagToken token) {
+ var nodeIndex = tree.openElements.length - 1;
+ var node = tree.openElements.last;
+ if (node.localName?.toAsciiLowerCase() != token.name) {
+ parser.parseError(token.span, 'unexpected-end-tag', {'name': token.name});
+ }
+
+ Token? newToken;
+ while (true) {
+ if (node.localName?.toAsciiLowerCase() == token.name) {
+ //XXX this isn't in the spec but it seems necessary
+ if (parser.phase == parser._inTableTextPhase) {
+ final inTableText = parser.phase as InTableTextPhase;
+ inTableText.flushCharacters();
+ parser.phase = inTableText.originalPhase!;
+ }
+ while (tree.openElements.removeLast() != node) {
+ assert(tree.openElements.isNotEmpty);
+ }
+ newToken = null;
+ break;
+ }
+ nodeIndex -= 1;
+
+ node = tree.openElements[nodeIndex];
+ if (node.namespaceUri != tree.defaultNamespace) {
+ continue;
+ } else {
+ newToken = parser.phase.processEndTag(token);
+ break;
+ }
+ }
+ return newToken;
+ }
+}
+
+class AfterBodyPhase extends Phase {
+ AfterBodyPhase(super.parser);
+
+ @override
+ Token? processStartTag(StartTagToken token) {
+ if (token.name == 'html') return startTagHtml(token);
+ return startTagOther(token);
+ }
+
+ @override
+ Token? processEndTag(EndTagToken token) {
+ if (token.name == 'html') {
+ endTagHtml(token);
+ return null;
+ }
+ return endTagOther(token);
+ }
+
+ //Stop parsing
+ @override
+ bool processEOF() => false;
+
+ @override
+ Token? processComment(CommentToken token) {
+ // This is needed because data is to be appended to the <html> element
+ // here and not to whatever is currently open.
+ tree.insertComment(token, tree.openElements[0]);
+ return null;
+ }
+
+ @override
+ Token processCharacters(CharactersToken token) {
+ parser.parseError(token.span, 'unexpected-char-after-body');
+ parser.phase = parser._inBodyPhase;
+ return token;
+ }
+
+ @override
+ Token? startTagHtml(StartTagToken token) {
+ return parser._inBodyPhase.processStartTag(token);
+ }
+
+ Token startTagOther(StartTagToken token) {
+ parser.parseError(
+ token.span, 'unexpected-start-tag-after-body', {'name': token.name});
+ parser.phase = parser._inBodyPhase;
+ return token;
+ }
+
+ void endTagHtml(Token token) {
+ for (var node in tree.openElements.reversed) {
+ if (node.localName == 'html') {
+ node.endSourceSpan = token.span;
+ break;
+ }
+ }
+ if (parser.innerHTMLMode) {
+ parser.parseError(token.span, 'unexpected-end-tag-after-body-innerhtml');
+ } else {
+ parser.phase = parser._afterAfterBodyPhase;
+ }
+ }
+
+ Token endTagOther(EndTagToken token) {
+ parser.parseError(
+ token.span, 'unexpected-end-tag-after-body', {'name': token.name});
+ parser.phase = parser._inBodyPhase;
+ return token;
+ }
+}
+
+class InFramesetPhase extends Phase {
+ // http://www.whatwg.org/specs/web-apps/current-work///in-frameset
+ InFramesetPhase(super.parser);
+
+ @override
+ Token? processStartTag(StartTagToken token) {
+ switch (token.name) {
+ case 'html':
+ return startTagHtml(token);
+ case 'frameset':
+ startTagFrameset(token);
+ return null;
+ case 'frame':
+ startTagFrame(token);
+ return null;
+ case 'noframes':
+ return startTagNoframes(token);
+ default:
+ return startTagOther(token);
+ }
+ }
+
+ @override
+ Token? processEndTag(EndTagToken token) {
+ switch (token.name) {
+ case 'frameset':
+ endTagFrameset(token);
+ return null;
+ default:
+ endTagOther(token);
+ return null;
+ }
+ }
+
+ @override
+ bool processEOF() {
+ final last = tree.openElements.last;
+ if (last.localName != 'html') {
+ parser.parseError(last.sourceSpan, 'eof-in-frameset');
+ } else {
+ assert(parser.innerHTMLMode);
+ }
+ return false;
+ }
+
+ @override
+ Token? processCharacters(CharactersToken token) {
+ parser.parseError(token.span, 'unexpected-char-in-frameset');
+ return null;
+ }
+
+ void startTagFrameset(StartTagToken token) {
+ tree.insertElement(token);
+ }
+
+ void startTagFrame(StartTagToken token) {
+ tree.insertElement(token);
+ tree.openElements.removeLast();
+ }
+
+ Token? startTagNoframes(StartTagToken token) {
+ return parser._inBodyPhase.processStartTag(token);
+ }
+
+ Token? startTagOther(StartTagToken token) {
+ parser.parseError(
+ token.span, 'unexpected-start-tag-in-frameset', {'name': token.name});
+ return null;
+ }
+
+ void endTagFrameset(EndTagToken token) {
+ if (tree.openElements.last.localName == 'html') {
+ // innerHTML case
+ parser.parseError(
+ token.span, 'unexpected-frameset-in-frameset-innerhtml');
+ } else {
+ final node = tree.openElements.removeLast();
+ node.endSourceSpan = token.span;
+ }
+ if (!parser.innerHTMLMode &&
+ tree.openElements.last.localName != 'frameset') {
+ // If we're not in innerHTML mode and the the current node is not a
+ // "frameset" element (anymore) then switch.
+ parser.phase = parser._afterFramesetPhase;
+ }
+ }
+
+ void endTagOther(EndTagToken token) {
+ parser.parseError(
+ token.span, 'unexpected-end-tag-in-frameset', {'name': token.name});
+ }
+}
+
+class AfterFramesetPhase extends Phase {
+ // http://www.whatwg.org/specs/web-apps/current-work///after3
+ AfterFramesetPhase(super.parser);
+
+ @override
+ Token? processStartTag(StartTagToken token) {
+ switch (token.name) {
+ case 'html':
+ return startTagHtml(token);
+ case 'noframes':
+ return startTagNoframes(token);
+ default:
+ startTagOther(token);
+ return null;
+ }
+ }
+
+ @override
+ Token? processEndTag(EndTagToken token) {
+ switch (token.name) {
+ case 'html':
+ endTagHtml(token);
+ return null;
+ default:
+ endTagOther(token);
+ return null;
+ }
+ }
+
+ // Stop parsing
+ @override
+ bool processEOF() => false;
+
+ @override
+ Token? processCharacters(CharactersToken token) {
+ parser.parseError(token.span, 'unexpected-char-after-frameset');
+ return null;
+ }
+
+ Token? startTagNoframes(StartTagToken token) {
+ return parser._inHeadPhase.processStartTag(token);
+ }
+
+ void startTagOther(StartTagToken token) {
+ parser.parseError(token.span, 'unexpected-start-tag-after-frameset',
+ {'name': token.name});
+ }
+
+ void endTagHtml(EndTagToken token) {
+ parser.phase = parser._afterAfterFramesetPhase;
+ }
+
+ void endTagOther(EndTagToken token) {
+ parser.parseError(
+ token.span, 'unexpected-end-tag-after-frameset', {'name': token.name});
+ }
+}
+
+class AfterAfterBodyPhase extends Phase {
+ AfterAfterBodyPhase(super.parser);
+
+ @override
+ Token? processStartTag(StartTagToken token) {
+ if (token.name == 'html') return startTagHtml(token);
+ return startTagOther(token);
+ }
+
+ @override
+ bool processEOF() => false;
+
+ @override
+ Token? processComment(CommentToken token) {
+ tree.insertComment(token, tree.document);
+ return null;
+ }
+
+ @override
+ Token? processSpaceCharacters(SpaceCharactersToken token) {
+ return parser._inBodyPhase.processSpaceCharacters(token);
+ }
+
+ @override
+ Token processCharacters(CharactersToken token) {
+ parser.parseError(token.span, 'expected-eof-but-got-char');
+ parser.phase = parser._inBodyPhase;
+ return token;
+ }
+
+ @override
+ Token? startTagHtml(StartTagToken token) {
+ return parser._inBodyPhase.processStartTag(token);
+ }
+
+ Token startTagOther(StartTagToken token) {
+ parser.parseError(
+ token.span, 'expected-eof-but-got-start-tag', {'name': token.name});
+ parser.phase = parser._inBodyPhase;
+ return token;
+ }
+
+ @override
+ Token processEndTag(EndTagToken token) {
+ parser.parseError(
+ token.span, 'expected-eof-but-got-end-tag', {'name': token.name});
+ parser.phase = parser._inBodyPhase;
+ return token;
+ }
+}
+
+class AfterAfterFramesetPhase extends Phase {
+ AfterAfterFramesetPhase(super.parser);
+
+ @override
+ Token? processStartTag(StartTagToken token) {
+ switch (token.name) {
+ case 'html':
+ return startTagHtml(token);
+ case 'noframes':
+ return startTagNoFrames(token);
+ default:
+ startTagOther(token);
+ return null;
+ }
+ }
+
+ @override
+ bool processEOF() => false;
+
+ @override
+ Token? processComment(CommentToken token) {
+ tree.insertComment(token, tree.document);
+ return null;
+ }
+
+ @override
+ Token? processSpaceCharacters(SpaceCharactersToken token) {
+ return parser._inBodyPhase.processSpaceCharacters(token);
+ }
+
+ @override
+ Token? processCharacters(CharactersToken token) {
+ parser.parseError(token.span, 'expected-eof-but-got-char');
+ return null;
+ }
+
+ @override
+ Token? startTagHtml(StartTagToken token) {
+ return parser._inBodyPhase.processStartTag(token);
+ }
+
+ Token? startTagNoFrames(StartTagToken token) {
+ return parser._inHeadPhase.processStartTag(token);
+ }
+
+ void startTagOther(StartTagToken token) {
+ parser.parseError(
+ token.span, 'expected-eof-but-got-start-tag', {'name': token.name});
+ }
+
+ @override
+ Token? processEndTag(EndTagToken token) {
+ parser.parseError(
+ token.span, 'expected-eof-but-got-end-tag', {'name': token.name});
+ return null;
+ }
+}
+
+/// Error in parsed document.
+class ParseError implements SourceSpanException {
+ final String errorCode;
+ @override
+ final SourceSpan? span;
+ final Map<String, Object?>? data;
+
+ ParseError(this.errorCode, this.span, this.data);
+
+ int get line => span!.start.line;
+
+ int get column => span!.start.column;
+
+ /// Returns the human readable error message for this error.
+ ///
+ /// Use [SourceSpan.message] or the [toString] from the [span] field to get a
+ /// message including span information
+ @override
+ String get message => formatStr(errorMessages[errorCode]!, data);
+
+ @override
+ String toString({dynamic color}) {
+ final res = span!.message(message, color: color);
+ return span!.sourceUrl == null ? 'ParserError on $res' : 'On $res';
+ }
+}
+
+/// Convenience function to get the pair of namespace and localName.
+(String, String?) getElementNameTuple(Element e) {
+ final ns = e.namespaceUri ?? Namespaces.html;
+ return (ns, e.localName);
+}
diff --git a/pkgs/html/lib/src/constants.dart b/pkgs/html/lib/src/constants.dart
new file mode 100644
index 0000000..e70b1e2
--- /dev/null
+++ b/pkgs/html/lib/src/constants.dart
@@ -0,0 +1,3046 @@
+import 'utils.dart';
+
+// TODO(jmesserly): fix up the const lists. For the bigger ones, we need faster
+// lookup than linear search "contains". In the Python code they were
+// frozensets.
+
+final String? eof = null;
+
+class ReparseException implements Exception {
+ final String message;
+ ReparseException(this.message);
+ @override
+ String toString() => 'ReparseException: $message';
+}
+
+// TODO(jmesserly): assuming the programmatic name is not important, it would be
+// good to make these "static const" fields on an ErrorMessage class.
+/// Error messages emitted by `HtmlParser`.
+///
+/// The values use Python style string formatting, as implemented by
+/// [formatStr]. That function only supports the subset of format functionality
+/// used here.
+const Map<String, String> errorMessages = {
+ 'null-character': 'Null character in input stream, replaced with U+FFFD.',
+ 'invalid-codepoint': 'Invalid codepoint in stream.',
+ 'incorrectly-placed-solidus': 'Solidus (/) incorrectly placed in tag.',
+ 'incorrect-cr-newline-entity':
+ 'Incorrect CR newline entity, replaced with LF.',
+ 'illegal-windows-1252-entity':
+ 'Entity used with illegal number (windows-1252 reference).',
+ 'cant-convert-numeric-entity':
+ "Numeric entity couldn't be converted to character "
+ '(codepoint U+%(charAsInt)08x).',
+ 'illegal-codepoint-for-numeric-entity':
+ 'Numeric entity represents an illegal codepoint: '
+ 'U+%(charAsInt)08x.',
+ 'numeric-entity-without-semicolon': "Numeric entity didn't end with ';'.",
+ 'expected-numeric-entity-but-got-eof':
+ 'Numeric entity expected. Got end of file instead.',
+ 'expected-numeric-entity': 'Numeric entity expected but none found.',
+ 'named-entity-without-semicolon': "Named entity didn't end with ';'.",
+ 'expected-named-entity': 'Named entity expected. Got none.',
+ 'attributes-in-end-tag': 'End tag contains unexpected attributes.',
+ 'self-closing-flag-on-end-tag':
+ 'End tag contains unexpected self-closing flag.',
+ 'expected-tag-name-but-got-right-bracket':
+ "Expected tag name. Got '>' instead.",
+ 'expected-tag-name-but-got-question-mark':
+ "Expected tag name. Got '?' instead. (HTML doesn't "
+ 'support processing instructions.)',
+ 'expected-tag-name': 'Expected tag name. Got something else instead',
+ 'expected-closing-tag-but-got-right-bracket':
+ "Expected closing tag. Got '>' instead. Ignoring '</>'.",
+ 'expected-closing-tag-but-got-eof':
+ 'Expected closing tag. Unexpected end of file.',
+ 'expected-closing-tag-but-got-char':
+ "Expected closing tag. Unexpected character '%(data)s' found.",
+ 'eof-in-tag-name': 'Unexpected end of file in the tag name.',
+ 'expected-attribute-name-but-got-eof':
+ 'Unexpected end of file. Expected attribute name instead.',
+ 'eof-in-attribute-name': 'Unexpected end of file in attribute name.',
+ 'invalid-character-in-attribute-name': 'Invalid character in attribute name',
+ 'duplicate-attribute': 'Dropped duplicate attribute on tag.',
+ 'expected-end-of-tag-name-but-got-eof':
+ 'Unexpected end of file. Expected = or end of tag.',
+ 'expected-attribute-value-but-got-eof':
+ 'Unexpected end of file. Expected attribute value.',
+ 'expected-attribute-value-but-got-right-bracket':
+ "Expected attribute value. Got '>' instead.",
+ 'equals-in-unquoted-attribute-value': 'Unexpected = in unquoted attribute',
+ 'unexpected-character-in-unquoted-attribute-value':
+ 'Unexpected character in unquoted attribute',
+ 'invalid-character-after-attribute-name':
+ 'Unexpected character after attribute name.',
+ 'unexpected-character-after-attribute-value':
+ 'Unexpected character after attribute value.',
+ 'eof-in-attribute-value-double-quote':
+ 'Unexpected end of file in attribute value (".',
+ 'eof-in-attribute-value-single-quote':
+ "Unexpected end of file in attribute value (').",
+ 'eof-in-attribute-value-no-quotes':
+ 'Unexpected end of file in attribute value.',
+ 'unexpected-EOF-after-solidus-in-tag':
+ 'Unexpected end of file in tag. Expected >',
+ 'unexpected-character-after-soldius-in-tag':
+ 'Unexpected character after / in tag. Expected >',
+ 'expected-dashes-or-doctype': "Expected '--' or 'DOCTYPE'. Not found.",
+ 'unexpected-bang-after-double-dash-in-comment':
+ 'Unexpected ! after -- in comment',
+ 'unexpected-space-after-double-dash-in-comment':
+ 'Unexpected space after -- in comment',
+ 'incorrect-comment': 'Incorrect comment.',
+ 'eof-in-comment': 'Unexpected end of file in comment.',
+ 'eof-in-comment-end-dash': 'Unexpected end of file in comment (-)',
+ 'unexpected-dash-after-double-dash-in-comment':
+ "Unexpected '-' after '--' found in comment.",
+ 'eof-in-comment-double-dash': 'Unexpected end of file in comment (--).',
+ 'eof-in-comment-end-space-state': 'Unexpected end of file in comment.',
+ 'eof-in-comment-end-bang-state': 'Unexpected end of file in comment.',
+ 'unexpected-char-in-comment': 'Unexpected character in comment found.',
+ 'need-space-after-doctype': "No space after literal string 'DOCTYPE'.",
+ 'expected-doctype-name-but-got-right-bracket':
+ 'Unexpected > character. Expected DOCTYPE name.',
+ 'expected-doctype-name-but-got-eof':
+ 'Unexpected end of file. Expected DOCTYPE name.',
+ 'eof-in-doctype-name': 'Unexpected end of file in DOCTYPE name.',
+ 'eof-in-doctype': 'Unexpected end of file in DOCTYPE.',
+ 'expected-space-or-right-bracket-in-doctype':
+ "Expected space or '>'. Got '%(data)s'",
+ 'unexpected-end-of-doctype': 'Unexpected end of DOCTYPE.',
+ 'unexpected-char-in-doctype': 'Unexpected character in DOCTYPE.',
+ 'eof-in-innerhtml': 'XXX innerHTML EOF',
+ 'unexpected-doctype': 'Unexpected DOCTYPE. Ignored.',
+ 'non-html-root': 'html needs to be the first start tag.',
+ 'expected-doctype-but-got-eof': 'Unexpected End of file. Expected DOCTYPE.',
+ 'unknown-doctype': 'Erroneous DOCTYPE.',
+ 'expected-doctype-but-got-chars':
+ 'Unexpected non-space characters. Expected DOCTYPE.',
+ 'expected-doctype-but-got-start-tag':
+ 'Unexpected start tag (%(name)s). Expected DOCTYPE.',
+ 'expected-doctype-but-got-end-tag':
+ 'Unexpected end tag (%(name)s). Expected DOCTYPE.',
+ 'end-tag-after-implied-root':
+ 'Unexpected end tag (%(name)s) after the (implied) root element.',
+ 'expected-named-closing-tag-but-got-eof':
+ 'Unexpected end of file. Expected end tag (%(name)s).',
+ 'two-heads-are-not-better-than-one':
+ 'Unexpected start tag head in existing head. Ignored.',
+ 'unexpected-end-tag': 'Unexpected end tag (%(name)s). Ignored.',
+ 'unexpected-start-tag-out-of-my-head':
+ 'Unexpected start tag (%(name)s) that can be in head. Moved.',
+ 'unexpected-start-tag': 'Unexpected start tag (%(name)s).',
+ 'missing-end-tag': 'Missing end tag (%(name)s).',
+ 'missing-end-tags': 'Missing end tags (%(name)s).',
+ 'unexpected-start-tag-implies-end-tag':
+ 'Unexpected start tag (%(startName)s) '
+ 'implies end tag (%(endName)s).',
+ 'unexpected-start-tag-treated-as':
+ 'Unexpected start tag (%(originalName)s). Treated as %(newName)s.',
+ 'deprecated-tag': "Unexpected start tag %(name)s. Don't use it!",
+ 'unexpected-start-tag-ignored': 'Unexpected start tag %(name)s. Ignored.',
+ 'expected-one-end-tag-but-got-another': 'Unexpected end tag (%(gotName)s). '
+ 'Missing end tag (%(expectedName)s).',
+ 'end-tag-too-early':
+ 'End tag (%(name)s) seen too early. Expected other end tag.',
+ 'end-tag-too-early-named':
+ 'Unexpected end tag (%(gotName)s). Expected end tag (%(expectedName)s).',
+ 'end-tag-too-early-ignored': 'End tag (%(name)s) seen too early. Ignored.',
+ 'adoption-agency-1.1': 'End tag (%(name)s) violates step 1, '
+ 'paragraph 1 of the adoption agency algorithm.',
+ 'adoption-agency-1.2': 'End tag (%(name)s) violates step 1, '
+ 'paragraph 2 of the adoption agency algorithm.',
+ 'adoption-agency-1.3': 'End tag (%(name)s) violates step 1, '
+ 'paragraph 3 of the adoption agency algorithm.',
+ 'unexpected-end-tag-treated-as':
+ 'Unexpected end tag (%(originalName)s). Treated as %(newName)s.',
+ 'no-end-tag': 'This element (%(name)s) has no end tag.',
+ 'unexpected-implied-end-tag-in-table':
+ 'Unexpected implied end tag (%(name)s) in the table phase.',
+ 'unexpected-implied-end-tag-in-table-body':
+ 'Unexpected implied end tag (%(name)s) in the table body phase.',
+ 'unexpected-char-implies-table-voodoo': 'Unexpected non-space characters in '
+ 'table context caused voodoo mode.',
+ 'unexpected-hidden-input-in-table':
+ 'Unexpected input with type hidden in table context.',
+ 'unexpected-form-in-table': 'Unexpected form in table context.',
+ 'unexpected-start-tag-implies-table-voodoo':
+ 'Unexpected start tag (%(name)s) in '
+ 'table context caused voodoo mode.',
+ 'unexpected-end-tag-implies-table-voodoo': 'Unexpected end tag (%(name)s) in '
+ 'table context caused voodoo mode.',
+ 'unexpected-cell-in-table-body': 'Unexpected table cell start tag (%(name)s) '
+ 'in the table body phase.',
+ 'unexpected-cell-end-tag': 'Got table cell end tag (%(name)s) '
+ 'while required end tags are missing.',
+ 'unexpected-end-tag-in-table-body':
+ 'Unexpected end tag (%(name)s) in the table body phase. Ignored.',
+ 'unexpected-implied-end-tag-in-table-row':
+ 'Unexpected implied end tag (%(name)s) in the table row phase.',
+ 'unexpected-end-tag-in-table-row':
+ 'Unexpected end tag (%(name)s) in the table row phase. Ignored.',
+ 'unexpected-select-in-select':
+ 'Unexpected select start tag in the select phase '
+ 'treated as select end tag.',
+ 'unexpected-input-in-select':
+ 'Unexpected input start tag in the select phase.',
+ 'unexpected-start-tag-in-select':
+ 'Unexpected start tag token (%(name)s in the select phase. '
+ 'Ignored.',
+ 'unexpected-end-tag-in-select':
+ 'Unexpected end tag (%(name)s) in the select phase. Ignored.',
+ 'unexpected-table-element-start-tag-in-select-in-table':
+ 'Unexpected table element start tag (%(name)s) in the select in table phase.',
+ 'unexpected-table-element-end-tag-in-select-in-table':
+ 'Unexpected table element end tag (%(name)s) in the select in table phase.',
+ 'unexpected-char-after-body':
+ 'Unexpected non-space characters in the after body phase.',
+ 'unexpected-start-tag-after-body': 'Unexpected start tag token (%(name)s)'
+ ' in the after body phase.',
+ 'unexpected-end-tag-after-body': 'Unexpected end tag token (%(name)s)'
+ ' in the after body phase.',
+ 'unexpected-char-in-frameset':
+ 'Unexpected characters in the frameset phase. Characters ignored.',
+ 'unexpected-start-tag-in-frameset': 'Unexpected start tag token (%(name)s)'
+ ' in the frameset phase. Ignored.',
+ 'unexpected-frameset-in-frameset-innerhtml':
+ 'Unexpected end tag token (frameset) '
+ 'in the frameset phase (innerHTML).',
+ 'unexpected-end-tag-in-frameset': 'Unexpected end tag token (%(name)s)'
+ ' in the frameset phase. Ignored.',
+ 'unexpected-char-after-frameset': 'Unexpected non-space characters in the '
+ 'after frameset phase. Ignored.',
+ 'unexpected-start-tag-after-frameset': 'Unexpected start tag (%(name)s)'
+ ' in the after frameset phase. Ignored.',
+ 'unexpected-end-tag-after-frameset': 'Unexpected end tag (%(name)s)'
+ ' in the after frameset phase. Ignored.',
+ 'unexpected-end-tag-after-body-innerhtml':
+ 'Unexpected end tag after body(innerHtml)',
+ 'expected-eof-but-got-char':
+ 'Unexpected non-space characters. Expected end of file.',
+ 'expected-eof-but-got-start-tag': 'Unexpected start tag (%(name)s)'
+ '. Expected end of file.',
+ 'expected-eof-but-got-end-tag': 'Unexpected end tag (%(name)s)'
+ '. Expected end of file.',
+ 'eof-in-table': 'Unexpected end of file. Expected table content.',
+ 'eof-in-select': 'Unexpected end of file. Expected select content.',
+ 'eof-in-frameset': 'Unexpected end of file. Expected frameset content.',
+ 'eof-in-script-in-script': 'Unexpected end of file. Expected script content.',
+ 'eof-in-foreign-lands': 'Unexpected end of file. Expected foreign content',
+ 'non-void-element-with-trailing-solidus':
+ 'Trailing solidus not allowed on element %(name)s',
+ 'unexpected-html-element-in-foreign-content':
+ 'Element %(name)s not allowed in a non-html context',
+ 'unexpected-end-tag-before-html':
+ 'Unexpected end tag (%(name)s) before html.',
+ 'undefined-error': 'Undefined error (this sucks and should be fixed)',
+};
+
+class Namespaces {
+ static const html = 'http://www.w3.org/1999/xhtml';
+ static const mathml = 'http://www.w3.org/1998/Math/MathML';
+ static const svg = 'http://www.w3.org/2000/svg';
+ static const xlink = 'http://www.w3.org/1999/xlink';
+ static const xml = 'http://www.w3.org/XML/1998/namespace';
+ static const xmlns = 'http://www.w3.org/2000/xmlns/';
+ Namespaces._();
+
+ static String? getPrefix(String? url) {
+ return switch (url) {
+ html => 'html',
+ mathml => 'math',
+ svg => 'svg',
+ xlink => 'xlink',
+ xml => 'xml',
+ xmlns => 'xmlns',
+ _ => null
+ };
+ }
+}
+
+const List<(String, String)> scopingElements = [
+ (Namespaces.html, 'applet'),
+ (Namespaces.html, 'caption'),
+ (Namespaces.html, 'html'),
+ (Namespaces.html, 'marquee'),
+ (Namespaces.html, 'object'),
+ (Namespaces.html, 'table'),
+ (Namespaces.html, 'td'),
+ (Namespaces.html, 'th'),
+ (Namespaces.mathml, 'mi'),
+ (Namespaces.mathml, 'mo'),
+ (Namespaces.mathml, 'mn'),
+ (Namespaces.mathml, 'ms'),
+ (Namespaces.mathml, 'mtext'),
+ (Namespaces.mathml, 'annotation-xml'),
+ (Namespaces.svg, 'foreignObject'),
+ (Namespaces.svg, 'desc'),
+ (Namespaces.svg, 'title')
+];
+
+const formattingElements = [
+ (Namespaces.html, 'a'),
+ (Namespaces.html, 'b'),
+ (Namespaces.html, 'big'),
+ (Namespaces.html, 'code'),
+ (Namespaces.html, 'em'),
+ (Namespaces.html, 'font'),
+ (Namespaces.html, 'i'),
+ (Namespaces.html, 'nobr'),
+ (Namespaces.html, 's'),
+ (Namespaces.html, 'small'),
+ (Namespaces.html, 'strike'),
+ (Namespaces.html, 'strong'),
+ (Namespaces.html, 'tt'),
+ (Namespaces.html, '')
+];
+
+const specialElements = [
+ (Namespaces.html, 'address'),
+ (Namespaces.html, 'applet'),
+ (Namespaces.html, 'area'),
+ (Namespaces.html, 'article'),
+ (Namespaces.html, 'aside'),
+ (Namespaces.html, 'base'),
+ (Namespaces.html, 'basefont'),
+ (Namespaces.html, 'bgsound'),
+ (Namespaces.html, 'blockquote'),
+ (Namespaces.html, 'body'),
+ (Namespaces.html, 'br'),
+ (Namespaces.html, 'button'),
+ (Namespaces.html, 'caption'),
+ (Namespaces.html, 'center'),
+ (Namespaces.html, 'col'),
+ (Namespaces.html, 'colgroup'),
+ (Namespaces.html, 'command'),
+ (Namespaces.html, 'dd'),
+ (Namespaces.html, 'details'),
+ (Namespaces.html, 'dir'),
+ (Namespaces.html, 'div'),
+ (Namespaces.html, 'dl'),
+ (Namespaces.html, 'dt'),
+ (Namespaces.html, 'embed'),
+ (Namespaces.html, 'fieldset'),
+ (Namespaces.html, 'figure'),
+ (Namespaces.html, 'footer'),
+ (Namespaces.html, 'form'),
+ (Namespaces.html, 'frame'),
+ (Namespaces.html, 'frameset'),
+ (Namespaces.html, 'h1'),
+ (Namespaces.html, 'h2'),
+ (Namespaces.html, 'h3'),
+ (Namespaces.html, 'h4'),
+ (Namespaces.html, 'h5'),
+ (Namespaces.html, 'h6'),
+ (Namespaces.html, 'head'),
+ (Namespaces.html, 'header'),
+ (Namespaces.html, 'hr'),
+ (Namespaces.html, 'html'),
+ (Namespaces.html, 'iframe'),
+ // Note that image is commented out in the spec as "this isn't an
+ // element that can end up on the stack, so it doesn't matter,"
+ (Namespaces.html, 'image'),
+ (Namespaces.html, 'img'),
+ (Namespaces.html, 'input'),
+ (Namespaces.html, 'isindex'),
+ (Namespaces.html, 'li'),
+ (Namespaces.html, 'link'),
+ (Namespaces.html, 'listing'),
+ (Namespaces.html, 'marquee'),
+ (Namespaces.html, 'men'),
+ (Namespaces.html, 'meta'),
+ (Namespaces.html, 'nav'),
+ (Namespaces.html, 'noembed'),
+ (Namespaces.html, 'noframes'),
+ (Namespaces.html, 'noscript'),
+ (Namespaces.html, 'object'),
+ (Namespaces.html, 'ol'),
+ (Namespaces.html, 'p'),
+ (Namespaces.html, 'param'),
+ (Namespaces.html, 'plaintext'),
+ (Namespaces.html, 'pre'),
+ (Namespaces.html, 'script'),
+ (Namespaces.html, 'section'),
+ (Namespaces.html, 'select'),
+ (Namespaces.html, 'style'),
+ (Namespaces.html, 'table'),
+ (Namespaces.html, 'tbody'),
+ (Namespaces.html, 'td'),
+ (Namespaces.html, 'textarea'),
+ (Namespaces.html, 'tfoot'),
+ (Namespaces.html, 'th'),
+ (Namespaces.html, 'thead'),
+ (Namespaces.html, 'title'),
+ (Namespaces.html, 'tr'),
+ (Namespaces.html, 'ul'),
+ (Namespaces.html, 'wbr'),
+ (Namespaces.html, 'xmp'),
+ (Namespaces.svg, 'foreignObject')
+];
+
+const htmlIntegrationPointElements = [
+ (Namespaces.mathml, 'annotaion-xml'),
+ (Namespaces.svg, 'foreignObject'),
+ (Namespaces.svg, 'desc'),
+ (Namespaces.svg, 'title')
+];
+
+const mathmlTextIntegrationPointElements = [
+ (Namespaces.mathml, 'mi'),
+ (Namespaces.mathml, 'mo'),
+ (Namespaces.mathml, 'mn'),
+ (Namespaces.mathml, 'ms'),
+ (Namespaces.mathml, 'mtext')
+];
+
+const spaceCharacters = ' \n\r\t\u000C';
+
+const int newLine = 10;
+const int returnCode = 13;
+
+bool isWhitespace(String? char) {
+ if (char == null) return false;
+ return isWhitespaceCC(char.codeUnitAt(0));
+}
+
+bool isWhitespaceCC(int charCode) {
+ switch (charCode) {
+ case 9: // '\t'
+ case newLine: // '\n'
+ case 12: // '\f'
+ case returnCode: // '\r'
+ case 32: // ' '
+ return true;
+ }
+ return false;
+}
+
+const List<String> 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;
+
+bool isLetterOrDigit(String? char) => isLetter(char) || isDigit(char);
+
+// Note: this is intentially ASCII only
+bool isLetter(String? char) {
+ if (char == null) return false;
+ final cc = char.codeUnitAt(0);
+ return cc >= _lowerACode && cc <= _lowerZCode ||
+ cc >= _upperACode && cc <= _upperZCode;
+}
+
+bool isDigit(String? char) {
+ if (char == null) return false;
+ final cc = char.codeUnitAt(0);
+ return cc >= _zeroCode && cc < _zeroCode + 10;
+}
+
+bool isHexDigit(String? char) {
+ if (char == null) return false;
+ switch (char.codeUnitAt(0)) {
+ case 48:
+ case 49:
+ case 50:
+ case 51:
+ case 52: // '0' - '4'
+ case 53:
+ case 54:
+ case 55:
+ case 56:
+ case 57: // '5' - '9'
+ case 65:
+ case 66:
+ case 67:
+ case 68:
+ case 69:
+ case 70: // 'A' - 'F'
+ case 97:
+ case 98:
+ case 99:
+ case 100:
+ case 101:
+ case 102: // 'a' - 'f'
+ return true;
+ }
+ return false;
+}
+
+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));
+
+ static int _asciiToLower(int c) => (c >= _upperACode && c <= _upperZCode)
+ ? c + _lowerACode - _upperACode
+ : c;
+}
+
+// Heading elements need to be ordered
+const headingElements = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
+
+const cdataElements = ['title', 'textarea'];
+
+const rcdataElements = [
+ 'style',
+ 'script',
+ 'xmp',
+ 'iframe',
+ 'noembed',
+ 'noframes',
+ 'noscript'
+];
+
+// entitiesWindows1252 has to be _ordered_ and needs to have an index. It
+// therefore can't be a frozenset.
+const List<int> entitiesWindows1252 = [
+ 8364, // 0x80 0x20AC EURO SIGN
+ 65533, // 0x81 UNDEFINED
+ 8218, // 0x82 0x201A SINGLE LOW-9 QUOTATION MARK
+ 402, // 0x83 0x0192 LATIN SMALL LETTER F WITH HOOK
+ 8222, // 0x84 0x201E DOUBLE LOW-9 QUOTATION MARK
+ 8230, // 0x85 0x2026 HORIZONTAL ELLIPSIS
+ 8224, // 0x86 0x2020 DAGGER
+ 8225, // 0x87 0x2021 DOUBLE DAGGER
+ 710, // 0x88 0x02C6 MODIFIER LETTER CIRCUMFLEX ACCENT
+ 8240, // 0x89 0x2030 PER MILLE SIGN
+ 352, // 0x8A 0x0160 LATIN CAPITAL LETTER S WITH CARON
+ 8249, // 0x8B 0x2039 SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+ 338, // 0x8C 0x0152 LATIN CAPITAL LIGATURE OE
+ 65533, // 0x8D UNDEFINED
+ 381, // 0x8E 0x017D LATIN CAPITAL LETTER Z WITH CARON
+ 65533, // 0x8F UNDEFINED
+ 65533, // 0x90 UNDEFINED
+ 8216, // 0x91 0x2018 LEFT SINGLE QUOTATION MARK
+ 8217, // 0x92 0x2019 RIGHT SINGLE QUOTATION MARK
+ 8220, // 0x93 0x201C LEFT DOUBLE QUOTATION MARK
+ 8221, // 0x94 0x201D RIGHT DOUBLE QUOTATION MARK
+ 8226, // 0x95 0x2022 BULLET
+ 8211, // 0x96 0x2013 EN DASH
+ 8212, // 0x97 0x2014 EM DASH
+ 732, // 0x98 0x02DC SMALL TILDE
+ 8482, // 0x99 0x2122 TRADE MARK SIGN
+ 353, // 0x9A 0x0161 LATIN SMALL LETTER S WITH CARON
+ 8250, // 0x9B 0x203A SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+ 339, // 0x9C 0x0153 LATIN SMALL LIGATURE OE
+ 65533, // 0x9D UNDEFINED
+ 382, // 0x9E 0x017E LATIN SMALL LETTER Z WITH CARON
+ 376 // 0x9F 0x0178 LATIN CAPITAL LETTER Y WITH DIAERESIS
+];
+
+const xmlEntities = ['lt;', 'gt;', 'amp;', 'apos;', 'quot;'];
+
+const Map<String, String> entities = {
+ 'AElig': '\xc6',
+ 'AElig;': '\xc6',
+ 'AMP': '&',
+ 'AMP;': '&',
+ 'Aacute': '\xc1',
+ 'Aacute;': '\xc1',
+ 'Abreve;': '\u0102',
+ 'Acirc': '\xc2',
+ 'Acirc;': '\xc2',
+ 'Acy;': '\u0410',
+ 'Afr;': '\u{01d504}',
+ 'Agrave': '\xc0',
+ 'Agrave;': '\xc0',
+ 'Alpha;': '\u0391',
+ 'Amacr;': '\u0100',
+ 'And;': '\u2a53',
+ 'Aogon;': '\u0104',
+ 'Aopf;': '\u{01d538}',
+ 'ApplyFunction;': '\u2061',
+ 'Aring': '\xc5',
+ 'Aring;': '\xc5',
+ 'Ascr;': '\u{01d49c}',
+ 'Assign;': '\u2254',
+ 'Atilde': '\xc3',
+ 'Atilde;': '\xc3',
+ 'Auml': '\xc4',
+ 'Auml;': '\xc4',
+ 'Backslash;': '\u2216',
+ 'Barv;': '\u2ae7',
+ 'Barwed;': '\u2306',
+ 'Bcy;': '\u0411',
+ 'Because;': '\u2235',
+ 'Bernoullis;': '\u212c',
+ 'Beta;': '\u0392',
+ 'Bfr;': '\u{01d505}',
+ 'Bopf;': '\u{01d539}',
+ 'Breve;': '\u02d8',
+ 'Bscr;': '\u212c',
+ 'Bumpeq;': '\u224e',
+ 'CHcy;': '\u0427',
+ 'COPY': '\xa9',
+ 'COPY;': '\xa9',
+ 'Cacute;': '\u0106',
+ 'Cap;': '\u22d2',
+ 'CapitalDifferentialD;': '\u2145',
+ 'Cayleys;': '\u212d',
+ 'Ccaron;': '\u010c',
+ 'Ccedil': '\xc7',
+ 'Ccedil;': '\xc7',
+ 'Ccirc;': '\u0108',
+ 'Cconint;': '\u2230',
+ 'Cdot;': '\u010a',
+ 'Cedilla;': '\xb8',
+ 'CenterDot;': '\xb7',
+ 'Cfr;': '\u212d',
+ 'Chi;': '\u03a7',
+ 'CircleDot;': '\u2299',
+ 'CircleMinus;': '\u2296',
+ 'CirclePlus;': '\u2295',
+ 'CircleTimes;': '\u2297',
+ 'ClockwiseContourIntegral;': '\u2232',
+ 'CloseCurlyDoubleQuote;': '\u201d',
+ 'CloseCurlyQuote;': '\u2019',
+ 'Colon;': '\u2237',
+ 'Colone;': '\u2a74',
+ 'Congruent;': '\u2261',
+ 'Conint;': '\u222f',
+ 'ContourIntegral;': '\u222e',
+ 'Copf;': '\u2102',
+ 'Coproduct;': '\u2210',
+ 'CounterClockwiseContourIntegral;': '\u2233',
+ 'Cross;': '\u2a2f',
+ 'Cscr;': '\u{01d49e}',
+ 'Cup;': '\u22d3',
+ 'CupCap;': '\u224d',
+ 'DD;': '\u2145',
+ 'DDotrahd;': '\u2911',
+ 'DJcy;': '\u0402',
+ 'DScy;': '\u0405',
+ 'DZcy;': '\u040f',
+ 'Dagger;': '\u2021',
+ 'Darr;': '\u21a1',
+ 'Dashv;': '\u2ae4',
+ 'Dcaron;': '\u010e',
+ 'Dcy;': '\u0414',
+ 'Del;': '\u2207',
+ 'Delta;': '\u0394',
+ 'Dfr;': '\u{01d507}',
+ 'DiacriticalAcute;': '\xb4',
+ 'DiacriticalDot;': '\u02d9',
+ 'DiacriticalDoubleAcute;': '\u02dd',
+ 'DiacriticalGrave;': '`',
+ 'DiacriticalTilde;': '\u02dc',
+ 'Diamond;': '\u22c4',
+ 'DifferentialD;': '\u2146',
+ 'Dopf;': '\u{01d53b}',
+ 'Dot;': '\xa8',
+ 'DotDot;': '\u20dc',
+ 'DotEqual;': '\u2250',
+ 'DoubleContourIntegral;': '\u222f',
+ 'DoubleDot;': '\xa8',
+ 'DoubleDownArrow;': '\u21d3',
+ 'DoubleLeftArrow;': '\u21d0',
+ 'DoubleLeftRightArrow;': '\u21d4',
+ 'DoubleLeftTee;': '\u2ae4',
+ 'DoubleLongLeftArrow;': '\u27f8',
+ 'DoubleLongLeftRightArrow;': '\u27fa',
+ 'DoubleLongRightArrow;': '\u27f9',
+ 'DoubleRightArrow;': '\u21d2',
+ 'DoubleRightTee;': '\u22a8',
+ 'DoubleUpArrow;': '\u21d1',
+ 'DoubleUpDownArrow;': '\u21d5',
+ 'DoubleVerticalBar;': '\u2225',
+ 'DownArrow;': '\u2193',
+ 'DownArrowBar;': '\u2913',
+ 'DownArrowUpArrow;': '\u21f5',
+ 'DownBreve;': '\u0311',
+ 'DownLeftRightVector;': '\u2950',
+ 'DownLeftTeeVector;': '\u295e',
+ 'DownLeftVector;': '\u21bd',
+ 'DownLeftVectorBar;': '\u2956',
+ 'DownRightTeeVector;': '\u295f',
+ 'DownRightVector;': '\u21c1',
+ 'DownRightVectorBar;': '\u2957',
+ 'DownTee;': '\u22a4',
+ 'DownTeeArrow;': '\u21a7',
+ 'Downarrow;': '\u21d3',
+ 'Dscr;': '\u{01d49f}',
+ 'Dstrok;': '\u0110',
+ 'ENG;': '\u014a',
+ 'ETH': '\xd0',
+ 'ETH;': '\xd0',
+ 'Eacute': '\xc9',
+ 'Eacute;': '\xc9',
+ 'Ecaron;': '\u011a',
+ 'Ecirc': '\xca',
+ 'Ecirc;': '\xca',
+ 'Ecy;': '\u042d',
+ 'Edot;': '\u0116',
+ 'Efr;': '\u{01d508}',
+ 'Egrave': '\xc8',
+ 'Egrave;': '\xc8',
+ 'Element;': '\u2208',
+ 'Emacr;': '\u0112',
+ 'EmptySmallSquare;': '\u25fb',
+ 'EmptyVerySmallSquare;': '\u25ab',
+ 'Eogon;': '\u0118',
+ 'Eopf;': '\u{01d53c}',
+ 'Epsilon;': '\u0395',
+ 'Equal;': '\u2a75',
+ 'EqualTilde;': '\u2242',
+ 'Equilibrium;': '\u21cc',
+ 'Escr;': '\u2130',
+ 'Esim;': '\u2a73',
+ 'Eta;': '\u0397',
+ 'Euml': '\xcb',
+ 'Euml;': '\xcb',
+ 'Exists;': '\u2203',
+ 'ExponentialE;': '\u2147',
+ 'Fcy;': '\u0424',
+ 'Ffr;': '\u{01d509}',
+ 'FilledSmallSquare;': '\u25fc',
+ 'FilledVerySmallSquare;': '\u25aa',
+ 'Fopf;': '\u{01d53d}',
+ 'ForAll;': '\u2200',
+ 'Fouriertrf;': '\u2131',
+ 'Fscr;': '\u2131',
+ 'GJcy;': '\u0403',
+ 'GT': '>',
+ 'GT;': '>',
+ 'Gamma;': '\u0393',
+ 'Gammad;': '\u03dc',
+ 'Gbreve;': '\u011e',
+ 'Gcedil;': '\u0122',
+ 'Gcirc;': '\u011c',
+ 'Gcy;': '\u0413',
+ 'Gdot;': '\u0120',
+ 'Gfr;': '\u{01d50a}',
+ 'Gg;': '\u22d9',
+ 'Gopf;': '\u{01d53e}',
+ 'GreaterEqual;': '\u2265',
+ 'GreaterEqualLess;': '\u22db',
+ 'GreaterFullEqual;': '\u2267',
+ 'GreaterGreater;': '\u2aa2',
+ 'GreaterLess;': '\u2277',
+ 'GreaterSlantEqual;': '\u2a7e',
+ 'GreaterTilde;': '\u2273',
+ 'Gscr;': '\u{01d4a2}',
+ 'Gt;': '\u226b',
+ 'HARDcy;': '\u042a',
+ 'Hacek;': '\u02c7',
+ 'Hat;': '^',
+ 'Hcirc;': '\u0124',
+ 'Hfr;': '\u210c',
+ 'HilbertSpace;': '\u210b',
+ 'Hopf;': '\u210d',
+ 'HorizontalLine;': '\u2500',
+ 'Hscr;': '\u210b',
+ 'Hstrok;': '\u0126',
+ 'HumpDownHump;': '\u224e',
+ 'HumpEqual;': '\u224f',
+ 'IEcy;': '\u0415',
+ 'IJlig;': '\u0132',
+ 'IOcy;': '\u0401',
+ 'Iacute': '\xcd',
+ 'Iacute;': '\xcd',
+ 'Icirc': '\xce',
+ 'Icirc;': '\xce',
+ 'Icy;': '\u0418',
+ 'Idot;': '\u0130',
+ 'Ifr;': '\u2111',
+ 'Igrave': '\xcc',
+ 'Igrave;': '\xcc',
+ 'Im;': '\u2111',
+ 'Imacr;': '\u012a',
+ 'ImaginaryI;': '\u2148',
+ 'Implies;': '\u21d2',
+ 'Int;': '\u222c',
+ 'Integral;': '\u222b',
+ 'Intersection;': '\u22c2',
+ 'InvisibleComma;': '\u2063',
+ 'InvisibleTimes;': '\u2062',
+ 'Iogon;': '\u012e',
+ 'Iopf;': '\u{01d540}',
+ 'Iota;': '\u0399',
+ 'Iscr;': '\u2110',
+ 'Itilde;': '\u0128',
+ 'Iukcy;': '\u0406',
+ 'Iuml': '\xcf',
+ 'Iuml;': '\xcf',
+ 'Jcirc;': '\u0134',
+ 'Jcy;': '\u0419',
+ 'Jfr;': '\u{01d50d}',
+ 'Jopf;': '\u{01d541}',
+ 'Jscr;': '\u{01d4a5}',
+ 'Jsercy;': '\u0408',
+ 'Jukcy;': '\u0404',
+ 'KHcy;': '\u0425',
+ 'KJcy;': '\u040c',
+ 'Kappa;': '\u039a',
+ 'Kcedil;': '\u0136',
+ 'Kcy;': '\u041a',
+ 'Kfr;': '\u{01d50e}',
+ 'Kopf;': '\u{01d542}',
+ 'Kscr;': '\u{01d4a6}',
+ 'LJcy;': '\u0409',
+ 'LT': '<',
+ 'LT;': '<',
+ 'Lacute;': '\u0139',
+ 'Lambda;': '\u039b',
+ 'Lang;': '\u27ea',
+ 'Laplacetrf;': '\u2112',
+ 'Larr;': '\u219e',
+ 'Lcaron;': '\u013d',
+ 'Lcedil;': '\u013b',
+ 'Lcy;': '\u041b',
+ 'LeftAngleBracket;': '\u27e8',
+ 'LeftArrow;': '\u2190',
+ 'LeftArrowBar;': '\u21e4',
+ 'LeftArrowRightArrow;': '\u21c6',
+ 'LeftCeiling;': '\u2308',
+ 'LeftDoubleBracket;': '\u27e6',
+ 'LeftDownTeeVector;': '\u2961',
+ 'LeftDownVector;': '\u21c3',
+ 'LeftDownVectorBar;': '\u2959',
+ 'LeftFloor;': '\u230a',
+ 'LeftRightArrow;': '\u2194',
+ 'LeftRightVector;': '\u294e',
+ 'LeftTee;': '\u22a3',
+ 'LeftTeeArrow;': '\u21a4',
+ 'LeftTeeVector;': '\u295a',
+ 'LeftTriangle;': '\u22b2',
+ 'LeftTriangleBar;': '\u29cf',
+ 'LeftTriangleEqual;': '\u22b4',
+ 'LeftUpDownVector;': '\u2951',
+ 'LeftUpTeeVector;': '\u2960',
+ 'LeftUpVector;': '\u21bf',
+ 'LeftUpVectorBar;': '\u2958',
+ 'LeftVector;': '\u21bc',
+ 'LeftVectorBar;': '\u2952',
+ 'Leftarrow;': '\u21d0',
+ 'Leftrightarrow;': '\u21d4',
+ 'LessEqualGreater;': '\u22da',
+ 'LessFullEqual;': '\u2266',
+ 'LessGreater;': '\u2276',
+ 'LessLess;': '\u2aa1',
+ 'LessSlantEqual;': '\u2a7d',
+ 'LessTilde;': '\u2272',
+ 'Lfr;': '\u{01d50f}',
+ 'Ll;': '\u22d8',
+ 'Lleftarrow;': '\u21da',
+ 'Lmidot;': '\u013f',
+ 'LongLeftArrow;': '\u27f5',
+ 'LongLeftRightArrow;': '\u27f7',
+ 'LongRightArrow;': '\u27f6',
+ 'Longleftarrow;': '\u27f8',
+ 'Longleftrightarrow;': '\u27fa',
+ 'Longrightarrow;': '\u27f9',
+ 'Lopf;': '\u{01d543}',
+ 'LowerLeftArrow;': '\u2199',
+ 'LowerRightArrow;': '\u2198',
+ 'Lscr;': '\u2112',
+ 'Lsh;': '\u21b0',
+ 'Lstrok;': '\u0141',
+ 'Lt;': '\u226a',
+ 'Map;': '\u2905',
+ 'Mcy;': '\u041c',
+ 'MediumSpace;': '\u205f',
+ 'Mellintrf;': '\u2133',
+ 'Mfr;': '\u{01d510}',
+ 'MinusPlus;': '\u2213',
+ 'Mopf;': '\u{01d544}',
+ 'Mscr;': '\u2133',
+ 'Mu;': '\u039c',
+ 'NJcy;': '\u040a',
+ 'Nacute;': '\u0143',
+ 'Ncaron;': '\u0147',
+ 'Ncedil;': '\u0145',
+ 'Ncy;': '\u041d',
+ 'NegativeMediumSpace;': '\u200b',
+ 'NegativeThickSpace;': '\u200b',
+ 'NegativeThinSpace;': '\u200b',
+ 'NegativeVeryThinSpace;': '\u200b',
+ 'NestedGreaterGreater;': '\u226b',
+ 'NestedLessLess;': '\u226a',
+ 'NewLine;': '\n',
+ 'Nfr;': '\u{01d511}',
+ 'NoBreak;': '\u2060',
+ 'NonBreakingSpace;': '\xa0',
+ 'Nopf;': '\u2115',
+ 'Not;': '\u2aec',
+ 'NotCongruent;': '\u2262',
+ 'NotCupCap;': '\u226d',
+ 'NotDoubleVerticalBar;': '\u2226',
+ 'NotElement;': '\u2209',
+ 'NotEqual;': '\u2260',
+ 'NotEqualTilde;': '\u2242\u0338',
+ 'NotExists;': '\u2204',
+ 'NotGreater;': '\u226f',
+ 'NotGreaterEqual;': '\u2271',
+ 'NotGreaterFullEqual;': '\u2267\u0338',
+ 'NotGreaterGreater;': '\u226b\u0338',
+ 'NotGreaterLess;': '\u2279',
+ 'NotGreaterSlantEqual;': '\u2a7e\u0338',
+ 'NotGreaterTilde;': '\u2275',
+ 'NotHumpDownHump;': '\u224e\u0338',
+ 'NotHumpEqual;': '\u224f\u0338',
+ 'NotLeftTriangle;': '\u22ea',
+ 'NotLeftTriangleBar;': '\u29cf\u0338',
+ 'NotLeftTriangleEqual;': '\u22ec',
+ 'NotLess;': '\u226e',
+ 'NotLessEqual;': '\u2270',
+ 'NotLessGreater;': '\u2278',
+ 'NotLessLess;': '\u226a\u0338',
+ 'NotLessSlantEqual;': '\u2a7d\u0338',
+ 'NotLessTilde;': '\u2274',
+ 'NotNestedGreaterGreater;': '\u2aa2\u0338',
+ 'NotNestedLessLess;': '\u2aa1\u0338',
+ 'NotPrecedes;': '\u2280',
+ 'NotPrecedesEqual;': '\u2aaf\u0338',
+ 'NotPrecedesSlantEqual;': '\u22e0',
+ 'NotReverseElement;': '\u220c',
+ 'NotRightTriangle;': '\u22eb',
+ 'NotRightTriangleBar;': '\u29d0\u0338',
+ 'NotRightTriangleEqual;': '\u22ed',
+ 'NotSquareSubset;': '\u228f\u0338',
+ 'NotSquareSubsetEqual;': '\u22e2',
+ 'NotSquareSuperset;': '\u2290\u0338',
+ 'NotSquareSupersetEqual;': '\u22e3',
+ 'NotSubset;': '\u2282\u20d2',
+ 'NotSubsetEqual;': '\u2288',
+ 'NotSucceeds;': '\u2281',
+ 'NotSucceedsEqual;': '\u2ab0\u0338',
+ 'NotSucceedsSlantEqual;': '\u22e1',
+ 'NotSucceedsTilde;': '\u227f\u0338',
+ 'NotSuperset;': '\u2283\u20d2',
+ 'NotSupersetEqual;': '\u2289',
+ 'NotTilde;': '\u2241',
+ 'NotTildeEqual;': '\u2244',
+ 'NotTildeFullEqual;': '\u2247',
+ 'NotTildeTilde;': '\u2249',
+ 'NotVerticalBar;': '\u2224',
+ 'Nscr;': '\u{01d4a9}',
+ 'Ntilde': '\xd1',
+ 'Ntilde;': '\xd1',
+ 'Nu;': '\u039d',
+ 'OElig;': '\u0152',
+ 'Oacute': '\xd3',
+ 'Oacute;': '\xd3',
+ 'Ocirc': '\xd4',
+ 'Ocirc;': '\xd4',
+ 'Ocy;': '\u041e',
+ 'Odblac;': '\u0150',
+ 'Ofr;': '\u{01d512}',
+ 'Ograve': '\xd2',
+ 'Ograve;': '\xd2',
+ 'Omacr;': '\u014c',
+ 'Omega;': '\u03a9',
+ 'Omicron;': '\u039f',
+ 'Oopf;': '\u{01d546}',
+ 'OpenCurlyDoubleQuote;': '\u201c',
+ 'OpenCurlyQuote;': '\u2018',
+ 'Or;': '\u2a54',
+ 'Oscr;': '\u{01d4aa}',
+ 'Oslash': '\xd8',
+ 'Oslash;': '\xd8',
+ 'Otilde': '\xd5',
+ 'Otilde;': '\xd5',
+ 'Otimes;': '\u2a37',
+ 'Ouml': '\xd6',
+ 'Ouml;': '\xd6',
+ 'OverBar;': '\u203e',
+ 'OverBrace;': '\u23de',
+ 'OverBracket;': '\u23b4',
+ 'OverParenthesis;': '\u23dc',
+ 'PartialD;': '\u2202',
+ 'Pcy;': '\u041f',
+ 'Pfr;': '\u{01d513}',
+ 'Phi;': '\u03a6',
+ 'Pi;': '\u03a0',
+ 'PlusMinus;': '\xb1',
+ 'Poincareplane;': '\u210c',
+ 'Popf;': '\u2119',
+ 'Pr;': '\u2abb',
+ 'Precedes;': '\u227a',
+ 'PrecedesEqual;': '\u2aaf',
+ 'PrecedesSlantEqual;': '\u227c',
+ 'PrecedesTilde;': '\u227e',
+ 'Prime;': '\u2033',
+ 'Product;': '\u220f',
+ 'Proportion;': '\u2237',
+ 'Proportional;': '\u221d',
+ 'Pscr;': '\u{01d4ab}',
+ 'Psi;': '\u03a8',
+ 'QUOT': '"',
+ 'QUOT;': '"',
+ 'Qfr;': '\u{01d514}',
+ 'Qopf;': '\u211a',
+ 'Qscr;': '\u{01d4ac}',
+ 'RBarr;': '\u2910',
+ 'REG': '\xae',
+ 'REG;': '\xae',
+ 'Racute;': '\u0154',
+ 'Rang;': '\u27eb',
+ 'Rarr;': '\u21a0',
+ 'Rarrtl;': '\u2916',
+ 'Rcaron;': '\u0158',
+ 'Rcedil;': '\u0156',
+ 'Rcy;': '\u0420',
+ 'Re;': '\u211c',
+ 'ReverseElement;': '\u220b',
+ 'ReverseEquilibrium;': '\u21cb',
+ 'ReverseUpEquilibrium;': '\u296f',
+ 'Rfr;': '\u211c',
+ 'Rho;': '\u03a1',
+ 'RightAngleBracket;': '\u27e9',
+ 'RightArrow;': '\u2192',
+ 'RightArrowBar;': '\u21e5',
+ 'RightArrowLeftArrow;': '\u21c4',
+ 'RightCeiling;': '\u2309',
+ 'RightDoubleBracket;': '\u27e7',
+ 'RightDownTeeVector;': '\u295d',
+ 'RightDownVector;': '\u21c2',
+ 'RightDownVectorBar;': '\u2955',
+ 'RightFloor;': '\u230b',
+ 'RightTee;': '\u22a2',
+ 'RightTeeArrow;': '\u21a6',
+ 'RightTeeVector;': '\u295b',
+ 'RightTriangle;': '\u22b3',
+ 'RightTriangleBar;': '\u29d0',
+ 'RightTriangleEqual;': '\u22b5',
+ 'RightUpDownVector;': '\u294f',
+ 'RightUpTeeVector;': '\u295c',
+ 'RightUpVector;': '\u21be',
+ 'RightUpVectorBar;': '\u2954',
+ 'RightVector;': '\u21c0',
+ 'RightVectorBar;': '\u2953',
+ 'Rightarrow;': '\u21d2',
+ 'Ropf;': '\u211d',
+ 'RoundImplies;': '\u2970',
+ 'Rrightarrow;': '\u21db',
+ 'Rscr;': '\u211b',
+ 'Rsh;': '\u21b1',
+ 'RuleDelayed;': '\u29f4',
+ 'SHCHcy;': '\u0429',
+ 'SHcy;': '\u0428',
+ 'SOFTcy;': '\u042c',
+ 'Sacute;': '\u015a',
+ 'Sc;': '\u2abc',
+ 'Scaron;': '\u0160',
+ 'Scedil;': '\u015e',
+ 'Scirc;': '\u015c',
+ 'Scy;': '\u0421',
+ 'Sfr;': '\u{01d516}',
+ 'ShortDownArrow;': '\u2193',
+ 'ShortLeftArrow;': '\u2190',
+ 'ShortRightArrow;': '\u2192',
+ 'ShortUpArrow;': '\u2191',
+ 'Sigma;': '\u03a3',
+ 'SmallCircle;': '\u2218',
+ 'Sopf;': '\u{01d54a}',
+ 'Sqrt;': '\u221a',
+ 'Square;': '\u25a1',
+ 'SquareIntersection;': '\u2293',
+ 'SquareSubset;': '\u228f',
+ 'SquareSubsetEqual;': '\u2291',
+ 'SquareSuperset;': '\u2290',
+ 'SquareSupersetEqual;': '\u2292',
+ 'SquareUnion;': '\u2294',
+ 'Sscr;': '\u{01d4ae}',
+ 'Star;': '\u22c6',
+ 'Sub;': '\u22d0',
+ 'Subset;': '\u22d0',
+ 'SubsetEqual;': '\u2286',
+ 'Succeeds;': '\u227b',
+ 'SucceedsEqual;': '\u2ab0',
+ 'SucceedsSlantEqual;': '\u227d',
+ 'SucceedsTilde;': '\u227f',
+ 'SuchThat;': '\u220b',
+ 'Sum;': '\u2211',
+ 'Sup;': '\u22d1',
+ 'Superset;': '\u2283',
+ 'SupersetEqual;': '\u2287',
+ 'Supset;': '\u22d1',
+ 'THORN': '\xde',
+ 'THORN;': '\xde',
+ 'TRADE;': '\u2122',
+ 'TSHcy;': '\u040b',
+ 'TScy;': '\u0426',
+ 'Tab;': '\t',
+ 'Tau;': '\u03a4',
+ 'Tcaron;': '\u0164',
+ 'Tcedil;': '\u0162',
+ 'Tcy;': '\u0422',
+ 'Tfr;': '\u{01d517}',
+ 'Therefore;': '\u2234',
+ 'Theta;': '\u0398',
+ 'ThickSpace;': '\u205f\u200a',
+ 'ThinSpace;': '\u2009',
+ 'Tilde;': '\u223c',
+ 'TildeEqual;': '\u2243',
+ 'TildeFullEqual;': '\u2245',
+ 'TildeTilde;': '\u2248',
+ 'Topf;': '\u{01d54b}',
+ 'TripleDot;': '\u20db',
+ 'Tscr;': '\u{01d4af}',
+ 'Tstrok;': '\u0166',
+ 'Uacute': '\xda',
+ 'Uacute;': '\xda',
+ 'Uarr;': '\u219f',
+ 'Uarrocir;': '\u2949',
+ 'Ubrcy;': '\u040e',
+ 'Ubreve;': '\u016c',
+ 'Ucirc': '\xdb',
+ 'Ucirc;': '\xdb',
+ 'Ucy;': '\u0423',
+ 'Udblac;': '\u0170',
+ 'Ufr;': '\u{01d518}',
+ 'Ugrave': '\xd9',
+ 'Ugrave;': '\xd9',
+ 'Umacr;': '\u016a',
+ 'UnderBar;': '_',
+ 'UnderBrace;': '\u23df',
+ 'UnderBracket;': '\u23b5',
+ 'UnderParenthesis;': '\u23dd',
+ 'Union;': '\u22c3',
+ 'UnionPlus;': '\u228e',
+ 'Uogon;': '\u0172',
+ 'Uopf;': '\u{01d54c}',
+ 'UpArrow;': '\u2191',
+ 'UpArrowBar;': '\u2912',
+ 'UpArrowDownArrow;': '\u21c5',
+ 'UpDownArrow;': '\u2195',
+ 'UpEquilibrium;': '\u296e',
+ 'UpTee;': '\u22a5',
+ 'UpTeeArrow;': '\u21a5',
+ 'Uparrow;': '\u21d1',
+ 'Updownarrow;': '\u21d5',
+ 'UpperLeftArrow;': '\u2196',
+ 'UpperRightArrow;': '\u2197',
+ 'Upsi;': '\u03d2',
+ 'Upsilon;': '\u03a5',
+ 'Uring;': '\u016e',
+ 'Uscr;': '\u{01d4b0}',
+ 'Utilde;': '\u0168',
+ 'Uuml': '\xdc',
+ 'Uuml;': '\xdc',
+ 'VDash;': '\u22ab',
+ 'Vbar;': '\u2aeb',
+ 'Vcy;': '\u0412',
+ 'Vdash;': '\u22a9',
+ 'Vdashl;': '\u2ae6',
+ 'Vee;': '\u22c1',
+ 'Verbar;': '\u2016',
+ 'Vert;': '\u2016',
+ 'VerticalBar;': '\u2223',
+ 'VerticalLine;': '|',
+ 'VerticalSeparator;': '\u2758',
+ 'VerticalTilde;': '\u2240',
+ 'VeryThinSpace;': '\u200a',
+ 'Vfr;': '\u{01d519}',
+ 'Vopf;': '\u{01d54d}',
+ 'Vscr;': '\u{01d4b1}',
+ 'Vvdash;': '\u22aa',
+ 'Wcirc;': '\u0174',
+ 'Wedge;': '\u22c0',
+ 'Wfr;': '\u{01d51a}',
+ 'Wopf;': '\u{01d54e}',
+ 'Wscr;': '\u{01d4b2}',
+ 'Xfr;': '\u{01d51b}',
+ 'Xi;': '\u039e',
+ 'Xopf;': '\u{01d54f}',
+ 'Xscr;': '\u{01d4b3}',
+ 'YAcy;': '\u042f',
+ 'YIcy;': '\u0407',
+ 'YUcy;': '\u042e',
+ 'Yacute': '\xdd',
+ 'Yacute;': '\xdd',
+ 'Ycirc;': '\u0176',
+ 'Ycy;': '\u042b',
+ 'Yfr;': '\u{01d51c}',
+ 'Yopf;': '\u{01d550}',
+ 'Yscr;': '\u{01d4b4}',
+ 'Yuml;': '\u0178',
+ 'ZHcy;': '\u0416',
+ 'Zacute;': '\u0179',
+ 'Zcaron;': '\u017d',
+ 'Zcy;': '\u0417',
+ 'Zdot;': '\u017b',
+ 'ZeroWidthSpace;': '\u200b',
+ 'Zeta;': '\u0396',
+ 'Zfr;': '\u2128',
+ 'Zopf;': '\u2124',
+ 'Zscr;': '\u{01d4b5}',
+ 'aacute': '\xe1',
+ 'aacute;': '\xe1',
+ 'abreve;': '\u0103',
+ 'ac;': '\u223e',
+ 'acE;': '\u223e\u0333',
+ 'acd;': '\u223f',
+ 'acirc': '\xe2',
+ 'acirc;': '\xe2',
+ 'acute': '\xb4',
+ 'acute;': '\xb4',
+ 'acy;': '\u0430',
+ 'aelig': '\xe6',
+ 'aelig;': '\xe6',
+ 'af;': '\u2061',
+ 'afr;': '\u{01d51e}',
+ 'agrave': '\xe0',
+ 'agrave;': '\xe0',
+ 'alefsym;': '\u2135',
+ 'aleph;': '\u2135',
+ 'alpha;': '\u03b1',
+ 'amacr;': '\u0101',
+ 'amalg;': '\u2a3f',
+ 'amp': '&',
+ 'amp;': '&',
+ 'and;': '\u2227',
+ 'andand;': '\u2a55',
+ 'andd;': '\u2a5c',
+ 'andslope;': '\u2a58',
+ 'andv;': '\u2a5a',
+ 'ang;': '\u2220',
+ 'ange;': '\u29a4',
+ 'angle;': '\u2220',
+ 'angmsd;': '\u2221',
+ 'angmsdaa;': '\u29a8',
+ 'angmsdab;': '\u29a9',
+ 'angmsdac;': '\u29aa',
+ 'angmsdad;': '\u29ab',
+ 'angmsdae;': '\u29ac',
+ 'angmsdaf;': '\u29ad',
+ 'angmsdag;': '\u29ae',
+ 'angmsdah;': '\u29af',
+ 'angrt;': '\u221f',
+ 'angrtvb;': '\u22be',
+ 'angrtvbd;': '\u299d',
+ 'angsph;': '\u2222',
+ 'angst;': '\xc5',
+ 'angzarr;': '\u237c',
+ 'aogon;': '\u0105',
+ 'aopf;': '\u{01d552}',
+ 'ap;': '\u2248',
+ 'apE;': '\u2a70',
+ 'apacir;': '\u2a6f',
+ 'ape;': '\u224a',
+ 'apid;': '\u224b',
+ 'apos;': "'",
+ 'approx;': '\u2248',
+ 'approxeq;': '\u224a',
+ 'aring': '\xe5',
+ 'aring;': '\xe5',
+ 'ascr;': '\u{01d4b6}',
+ 'ast;': '*',
+ 'asymp;': '\u2248',
+ 'asympeq;': '\u224d',
+ 'atilde': '\xe3',
+ 'atilde;': '\xe3',
+ 'auml': '\xe4',
+ 'auml;': '\xe4',
+ 'awconint;': '\u2233',
+ 'awint;': '\u2a11',
+ 'bNot;': '\u2aed',
+ 'backcong;': '\u224c',
+ 'backepsilon;': '\u03f6',
+ 'backprime;': '\u2035',
+ 'backsim;': '\u223d',
+ 'backsimeq;': '\u22cd',
+ 'barvee;': '\u22bd',
+ 'barwed;': '\u2305',
+ 'barwedge;': '\u2305',
+ 'bbrk;': '\u23b5',
+ 'bbrktbrk;': '\u23b6',
+ 'bcong;': '\u224c',
+ 'bcy;': '\u0431',
+ 'bdquo;': '\u201e',
+ 'becaus;': '\u2235',
+ 'because;': '\u2235',
+ 'bemptyv;': '\u29b0',
+ 'bepsi;': '\u03f6',
+ 'bernou;': '\u212c',
+ 'beta;': '\u03b2',
+ 'beth;': '\u2136',
+ 'between;': '\u226c',
+ 'bfr;': '\u{01d51f}',
+ 'bigcap;': '\u22c2',
+ 'bigcirc;': '\u25ef',
+ 'bigcup;': '\u22c3',
+ 'bigodot;': '\u2a00',
+ 'bigoplus;': '\u2a01',
+ 'bigotimes;': '\u2a02',
+ 'bigsqcup;': '\u2a06',
+ 'bigstar;': '\u2605',
+ 'bigtriangledown;': '\u25bd',
+ 'bigtriangleup;': '\u25b3',
+ 'biguplus;': '\u2a04',
+ 'bigvee;': '\u22c1',
+ 'bigwedge;': '\u22c0',
+ 'bkarow;': '\u290d',
+ 'blacklozenge;': '\u29eb',
+ 'blacksquare;': '\u25aa',
+ 'blacktriangle;': '\u25b4',
+ 'blacktriangledown;': '\u25be',
+ 'blacktriangleleft;': '\u25c2',
+ 'blacktriangleright;': '\u25b8',
+ 'blank;': '\u2423',
+ 'blk12;': '\u2592',
+ 'blk14;': '\u2591',
+ 'blk34;': '\u2593',
+ 'block;': '\u2588',
+ 'bne;': '=\u20e5',
+ 'bnequiv;': '\u2261\u20e5',
+ 'bnot;': '\u2310',
+ 'bopf;': '\u{01d553}',
+ 'bot;': '\u22a5',
+ 'bottom;': '\u22a5',
+ 'bowtie;': '\u22c8',
+ 'boxDL;': '\u2557',
+ 'boxDR;': '\u2554',
+ 'boxDl;': '\u2556',
+ 'boxDr;': '\u2553',
+ 'boxH;': '\u2550',
+ 'boxHD;': '\u2566',
+ 'boxHU;': '\u2569',
+ 'boxHd;': '\u2564',
+ 'boxHu;': '\u2567',
+ 'boxUL;': '\u255d',
+ 'boxUR;': '\u255a',
+ 'boxUl;': '\u255c',
+ 'boxUr;': '\u2559',
+ 'boxV;': '\u2551',
+ 'boxVH;': '\u256c',
+ 'boxVL;': '\u2563',
+ 'boxVR;': '\u2560',
+ 'boxVh;': '\u256b',
+ 'boxVl;': '\u2562',
+ 'boxVr;': '\u255f',
+ 'boxbox;': '\u29c9',
+ 'boxdL;': '\u2555',
+ 'boxdR;': '\u2552',
+ 'boxdl;': '\u2510',
+ 'boxdr;': '\u250c',
+ 'boxh;': '\u2500',
+ 'boxhD;': '\u2565',
+ 'boxhU;': '\u2568',
+ 'boxhd;': '\u252c',
+ 'boxhu;': '\u2534',
+ 'boxminus;': '\u229f',
+ 'boxplus;': '\u229e',
+ 'boxtimes;': '\u22a0',
+ 'boxuL;': '\u255b',
+ 'boxuR;': '\u2558',
+ 'boxul;': '\u2518',
+ 'boxur;': '\u2514',
+ 'boxv;': '\u2502',
+ 'boxvH;': '\u256a',
+ 'boxvL;': '\u2561',
+ 'boxvR;': '\u255e',
+ 'boxvh;': '\u253c',
+ 'boxvl;': '\u2524',
+ 'boxvr;': '\u251c',
+ 'bprime;': '\u2035',
+ 'breve;': '\u02d8',
+ 'brvbar': '\xa6',
+ 'brvbar;': '\xa6',
+ 'bscr;': '\u{01d4b7}',
+ 'bsemi;': '\u204f',
+ 'bsim;': '\u223d',
+ 'bsime;': '\u22cd',
+ 'bsol;': '\\',
+ 'bsolb;': '\u29c5',
+ 'bsolhsub;': '\u27c8',
+ 'bull;': '\u2022',
+ 'bullet;': '\u2022',
+ 'bump;': '\u224e',
+ 'bumpE;': '\u2aae',
+ 'bumpe;': '\u224f',
+ 'bumpeq;': '\u224f',
+ 'cacute;': '\u0107',
+ 'cap;': '\u2229',
+ 'capand;': '\u2a44',
+ 'capbrcup;': '\u2a49',
+ 'capcap;': '\u2a4b',
+ 'capcup;': '\u2a47',
+ 'capdot;': '\u2a40',
+ 'caps;': '\u2229\ufe00',
+ 'caret;': '\u2041',
+ 'caron;': '\u02c7',
+ 'ccaps;': '\u2a4d',
+ 'ccaron;': '\u010d',
+ 'ccedil': '\xe7',
+ 'ccedil;': '\xe7',
+ 'ccirc;': '\u0109',
+ 'ccups;': '\u2a4c',
+ 'ccupssm;': '\u2a50',
+ 'cdot;': '\u010b',
+ 'cedil': '\xb8',
+ 'cedil;': '\xb8',
+ 'cemptyv;': '\u29b2',
+ 'cent': '\xa2',
+ 'cent;': '\xa2',
+ 'centerdot;': '\xb7',
+ 'cfr;': '\u{01d520}',
+ 'chcy;': '\u0447',
+ 'check;': '\u2713',
+ 'checkmark;': '\u2713',
+ 'chi;': '\u03c7',
+ 'cir;': '\u25cb',
+ 'cirE;': '\u29c3',
+ 'circ;': '\u02c6',
+ 'circeq;': '\u2257',
+ 'circlearrowleft;': '\u21ba',
+ 'circlearrowright;': '\u21bb',
+ 'circledR;': '\xae',
+ 'circledS;': '\u24c8',
+ 'circledast;': '\u229b',
+ 'circledcirc;': '\u229a',
+ 'circleddash;': '\u229d',
+ 'cire;': '\u2257',
+ 'cirfnint;': '\u2a10',
+ 'cirmid;': '\u2aef',
+ 'cirscir;': '\u29c2',
+ 'clubs;': '\u2663',
+ 'clubsuit;': '\u2663',
+ 'colon;': ':',
+ 'colone;': '\u2254',
+ 'coloneq;': '\u2254',
+ 'comma;': ',',
+ 'commat;': '@',
+ 'comp;': '\u2201',
+ 'compfn;': '\u2218',
+ 'complement;': '\u2201',
+ 'complexes;': '\u2102',
+ 'cong;': '\u2245',
+ 'congdot;': '\u2a6d',
+ 'conint;': '\u222e',
+ 'copf;': '\u{01d554}',
+ 'coprod;': '\u2210',
+ 'copy': '\xa9',
+ 'copy;': '\xa9',
+ 'copysr;': '\u2117',
+ 'crarr;': '\u21b5',
+ 'cross;': '\u2717',
+ 'cscr;': '\u{01d4b8}',
+ 'csub;': '\u2acf',
+ 'csube;': '\u2ad1',
+ 'csup;': '\u2ad0',
+ 'csupe;': '\u2ad2',
+ 'ctdot;': '\u22ef',
+ 'cudarrl;': '\u2938',
+ 'cudarrr;': '\u2935',
+ 'cuepr;': '\u22de',
+ 'cuesc;': '\u22df',
+ 'cularr;': '\u21b6',
+ 'cularrp;': '\u293d',
+ 'cup;': '\u222a',
+ 'cupbrcap;': '\u2a48',
+ 'cupcap;': '\u2a46',
+ 'cupcup;': '\u2a4a',
+ 'cupdot;': '\u228d',
+ 'cupor;': '\u2a45',
+ 'cups;': '\u222a\ufe00',
+ 'curarr;': '\u21b7',
+ 'curarrm;': '\u293c',
+ 'curlyeqprec;': '\u22de',
+ 'curlyeqsucc;': '\u22df',
+ 'curlyvee;': '\u22ce',
+ 'curlywedge;': '\u22cf',
+ 'curren': '\xa4',
+ 'curren;': '\xa4',
+ 'curvearrowleft;': '\u21b6',
+ 'curvearrowright;': '\u21b7',
+ 'cuvee;': '\u22ce',
+ 'cuwed;': '\u22cf',
+ 'cwconint;': '\u2232',
+ 'cwint;': '\u2231',
+ 'cylcty;': '\u232d',
+ 'dArr;': '\u21d3',
+ 'dHar;': '\u2965',
+ 'dagger;': '\u2020',
+ 'daleth;': '\u2138',
+ 'darr;': '\u2193',
+ 'dash;': '\u2010',
+ 'dashv;': '\u22a3',
+ 'dbkarow;': '\u290f',
+ 'dblac;': '\u02dd',
+ 'dcaron;': '\u010f',
+ 'dcy;': '\u0434',
+ 'dd;': '\u2146',
+ 'ddagger;': '\u2021',
+ 'ddarr;': '\u21ca',
+ 'ddotseq;': '\u2a77',
+ 'deg': '\xb0',
+ 'deg;': '\xb0',
+ 'delta;': '\u03b4',
+ 'demptyv;': '\u29b1',
+ 'dfisht;': '\u297f',
+ 'dfr;': '\u{01d521}',
+ 'dharl;': '\u21c3',
+ 'dharr;': '\u21c2',
+ 'diam;': '\u22c4',
+ 'diamond;': '\u22c4',
+ 'diamondsuit;': '\u2666',
+ 'diams;': '\u2666',
+ 'die;': '\xa8',
+ 'digamma;': '\u03dd',
+ 'disin;': '\u22f2',
+ 'div;': '\xf7',
+ 'divide': '\xf7',
+ 'divide;': '\xf7',
+ 'divideontimes;': '\u22c7',
+ 'divonx;': '\u22c7',
+ 'djcy;': '\u0452',
+ 'dlcorn;': '\u231e',
+ 'dlcrop;': '\u230d',
+ 'dollar;': '\$',
+ 'dopf;': '\u{01d555}',
+ 'dot;': '\u02d9',
+ 'doteq;': '\u2250',
+ 'doteqdot;': '\u2251',
+ 'dotminus;': '\u2238',
+ 'dotplus;': '\u2214',
+ 'dotsquare;': '\u22a1',
+ 'doublebarwedge;': '\u2306',
+ 'downarrow;': '\u2193',
+ 'downdownarrows;': '\u21ca',
+ 'downharpoonleft;': '\u21c3',
+ 'downharpoonright;': '\u21c2',
+ 'drbkarow;': '\u2910',
+ 'drcorn;': '\u231f',
+ 'drcrop;': '\u230c',
+ 'dscr;': '\u{01d4b9}',
+ 'dscy;': '\u0455',
+ 'dsol;': '\u29f6',
+ 'dstrok;': '\u0111',
+ 'dtdot;': '\u22f1',
+ 'dtri;': '\u25bf',
+ 'dtrif;': '\u25be',
+ 'duarr;': '\u21f5',
+ 'duhar;': '\u296f',
+ 'dwangle;': '\u29a6',
+ 'dzcy;': '\u045f',
+ 'dzigrarr;': '\u27ff',
+ 'eDDot;': '\u2a77',
+ 'eDot;': '\u2251',
+ 'eacute': '\xe9',
+ 'eacute;': '\xe9',
+ 'easter;': '\u2a6e',
+ 'ecaron;': '\u011b',
+ 'ecir;': '\u2256',
+ 'ecirc': '\xea',
+ 'ecirc;': '\xea',
+ 'ecolon;': '\u2255',
+ 'ecy;': '\u044d',
+ 'edot;': '\u0117',
+ 'ee;': '\u2147',
+ 'efDot;': '\u2252',
+ 'efr;': '\u{01d522}',
+ 'eg;': '\u2a9a',
+ 'egrave': '\xe8',
+ 'egrave;': '\xe8',
+ 'egs;': '\u2a96',
+ 'egsdot;': '\u2a98',
+ 'el;': '\u2a99',
+ 'elinters;': '\u23e7',
+ 'ell;': '\u2113',
+ 'els;': '\u2a95',
+ 'elsdot;': '\u2a97',
+ 'emacr;': '\u0113',
+ 'empty;': '\u2205',
+ 'emptyset;': '\u2205',
+ 'emptyv;': '\u2205',
+ 'emsp13;': '\u2004',
+ 'emsp14;': '\u2005',
+ 'emsp;': '\u2003',
+ 'eng;': '\u014b',
+ 'ensp;': '\u2002',
+ 'eogon;': '\u0119',
+ 'eopf;': '\u{01d556}',
+ 'epar;': '\u22d5',
+ 'eparsl;': '\u29e3',
+ 'eplus;': '\u2a71',
+ 'epsi;': '\u03b5',
+ 'epsilon;': '\u03b5',
+ 'epsiv;': '\u03f5',
+ 'eqcirc;': '\u2256',
+ 'eqcolon;': '\u2255',
+ 'eqsim;': '\u2242',
+ 'eqslantgtr;': '\u2a96',
+ 'eqslantless;': '\u2a95',
+ 'equals;': '=',
+ 'equest;': '\u225f',
+ 'equiv;': '\u2261',
+ 'equivDD;': '\u2a78',
+ 'eqvparsl;': '\u29e5',
+ 'erDot;': '\u2253',
+ 'erarr;': '\u2971',
+ 'escr;': '\u212f',
+ 'esdot;': '\u2250',
+ 'esim;': '\u2242',
+ 'eta;': '\u03b7',
+ 'eth': '\xf0',
+ 'eth;': '\xf0',
+ 'euml': '\xeb',
+ 'euml;': '\xeb',
+ 'euro;': '\u20ac',
+ 'excl;': '!',
+ 'exist;': '\u2203',
+ 'expectation;': '\u2130',
+ 'exponentiale;': '\u2147',
+ 'fallingdotseq;': '\u2252',
+ 'fcy;': '\u0444',
+ 'female;': '\u2640',
+ 'ffilig;': '\ufb03',
+ 'fflig;': '\ufb00',
+ 'ffllig;': '\ufb04',
+ 'ffr;': '\u{01d523}',
+ 'filig;': '\ufb01',
+ 'fjlig;': 'fj',
+ 'flat;': '\u266d',
+ 'fllig;': '\ufb02',
+ 'fltns;': '\u25b1',
+ 'fnof;': '\u0192',
+ 'fopf;': '\u{01d557}',
+ 'forall;': '\u2200',
+ 'fork;': '\u22d4',
+ 'forkv;': '\u2ad9',
+ 'fpartint;': '\u2a0d',
+ 'frac12': '\xbd',
+ 'frac12;': '\xbd',
+ 'frac13;': '\u2153',
+ 'frac14': '\xbc',
+ 'frac14;': '\xbc',
+ 'frac15;': '\u2155',
+ 'frac16;': '\u2159',
+ 'frac18;': '\u215b',
+ 'frac23;': '\u2154',
+ 'frac25;': '\u2156',
+ 'frac34': '\xbe',
+ 'frac34;': '\xbe',
+ 'frac35;': '\u2157',
+ 'frac38;': '\u215c',
+ 'frac45;': '\u2158',
+ 'frac56;': '\u215a',
+ 'frac58;': '\u215d',
+ 'frac78;': '\u215e',
+ 'frasl;': '\u2044',
+ 'frown;': '\u2322',
+ 'fscr;': '\u{01d4bb}',
+ 'gE;': '\u2267',
+ 'gEl;': '\u2a8c',
+ 'gacute;': '\u01f5',
+ 'gamma;': '\u03b3',
+ 'gammad;': '\u03dd',
+ 'gap;': '\u2a86',
+ 'gbreve;': '\u011f',
+ 'gcirc;': '\u011d',
+ 'gcy;': '\u0433',
+ 'gdot;': '\u0121',
+ 'ge;': '\u2265',
+ 'gel;': '\u22db',
+ 'geq;': '\u2265',
+ 'geqq;': '\u2267',
+ 'geqslant;': '\u2a7e',
+ 'ges;': '\u2a7e',
+ 'gescc;': '\u2aa9',
+ 'gesdot;': '\u2a80',
+ 'gesdoto;': '\u2a82',
+ 'gesdotol;': '\u2a84',
+ 'gesl;': '\u22db\ufe00',
+ 'gesles;': '\u2a94',
+ 'gfr;': '\u{01d524}',
+ 'gg;': '\u226b',
+ 'ggg;': '\u22d9',
+ 'gimel;': '\u2137',
+ 'gjcy;': '\u0453',
+ 'gl;': '\u2277',
+ 'glE;': '\u2a92',
+ 'gla;': '\u2aa5',
+ 'glj;': '\u2aa4',
+ 'gnE;': '\u2269',
+ 'gnap;': '\u2a8a',
+ 'gnapprox;': '\u2a8a',
+ 'gne;': '\u2a88',
+ 'gneq;': '\u2a88',
+ 'gneqq;': '\u2269',
+ 'gnsim;': '\u22e7',
+ 'gopf;': '\u{01d558}',
+ 'grave;': '`',
+ 'gscr;': '\u210a',
+ 'gsim;': '\u2273',
+ 'gsime;': '\u2a8e',
+ 'gsiml;': '\u2a90',
+ 'gt': '>',
+ 'gt;': '>',
+ 'gtcc;': '\u2aa7',
+ 'gtcir;': '\u2a7a',
+ 'gtdot;': '\u22d7',
+ 'gtlPar;': '\u2995',
+ 'gtquest;': '\u2a7c',
+ 'gtrapprox;': '\u2a86',
+ 'gtrarr;': '\u2978',
+ 'gtrdot;': '\u22d7',
+ 'gtreqless;': '\u22db',
+ 'gtreqqless;': '\u2a8c',
+ 'gtrless;': '\u2277',
+ 'gtrsim;': '\u2273',
+ 'gvertneqq;': '\u2269\ufe00',
+ 'gvnE;': '\u2269\ufe00',
+ 'hArr;': '\u21d4',
+ 'hairsp;': '\u200a',
+ 'half;': '\xbd',
+ 'hamilt;': '\u210b',
+ 'hardcy;': '\u044a',
+ 'harr;': '\u2194',
+ 'harrcir;': '\u2948',
+ 'harrw;': '\u21ad',
+ 'hbar;': '\u210f',
+ 'hcirc;': '\u0125',
+ 'hearts;': '\u2665',
+ 'heartsuit;': '\u2665',
+ 'hellip;': '\u2026',
+ 'hercon;': '\u22b9',
+ 'hfr;': '\u{01d525}',
+ 'hksearow;': '\u2925',
+ 'hkswarow;': '\u2926',
+ 'hoarr;': '\u21ff',
+ 'homtht;': '\u223b',
+ 'hookleftarrow;': '\u21a9',
+ 'hookrightarrow;': '\u21aa',
+ 'hopf;': '\u{01d559}',
+ 'horbar;': '\u2015',
+ 'hscr;': '\u{01d4bd}',
+ 'hslash;': '\u210f',
+ 'hstrok;': '\u0127',
+ 'hybull;': '\u2043',
+ 'hyphen;': '\u2010',
+ 'iacute': '\xed',
+ 'iacute;': '\xed',
+ 'ic;': '\u2063',
+ 'icirc': '\xee',
+ 'icirc;': '\xee',
+ 'icy;': '\u0438',
+ 'iecy;': '\u0435',
+ 'iexcl': '\xa1',
+ 'iexcl;': '\xa1',
+ 'iff;': '\u21d4',
+ 'ifr;': '\u{01d526}',
+ 'igrave': '\xec',
+ 'igrave;': '\xec',
+ 'ii;': '\u2148',
+ 'iiiint;': '\u2a0c',
+ 'iiint;': '\u222d',
+ 'iinfin;': '\u29dc',
+ 'iiota;': '\u2129',
+ 'ijlig;': '\u0133',
+ 'imacr;': '\u012b',
+ 'image;': '\u2111',
+ 'imagline;': '\u2110',
+ 'imagpart;': '\u2111',
+ 'imath;': '\u0131',
+ 'imof;': '\u22b7',
+ 'imped;': '\u01b5',
+ 'in;': '\u2208',
+ 'incare;': '\u2105',
+ 'infin;': '\u221e',
+ 'infintie;': '\u29dd',
+ 'inodot;': '\u0131',
+ 'int;': '\u222b',
+ 'intcal;': '\u22ba',
+ 'integers;': '\u2124',
+ 'intercal;': '\u22ba',
+ 'intlarhk;': '\u2a17',
+ 'intprod;': '\u2a3c',
+ 'iocy;': '\u0451',
+ 'iogon;': '\u012f',
+ 'iopf;': '\u{01d55a}',
+ 'iota;': '\u03b9',
+ 'iprod;': '\u2a3c',
+ 'iquest': '\xbf',
+ 'iquest;': '\xbf',
+ 'iscr;': '\u{01d4be}',
+ 'isin;': '\u2208',
+ 'isinE;': '\u22f9',
+ 'isindot;': '\u22f5',
+ 'isins;': '\u22f4',
+ 'isinsv;': '\u22f3',
+ 'isinv;': '\u2208',
+ 'it;': '\u2062',
+ 'itilde;': '\u0129',
+ 'iukcy;': '\u0456',
+ 'iuml': '\xef',
+ 'iuml;': '\xef',
+ 'jcirc;': '\u0135',
+ 'jcy;': '\u0439',
+ 'jfr;': '\u{01d527}',
+ 'jmath;': '\u0237',
+ 'jopf;': '\u{01d55b}',
+ 'jscr;': '\u{01d4bf}',
+ 'jsercy;': '\u0458',
+ 'jukcy;': '\u0454',
+ 'kappa;': '\u03ba',
+ 'kappav;': '\u03f0',
+ 'kcedil;': '\u0137',
+ 'kcy;': '\u043a',
+ 'kfr;': '\u{01d528}',
+ 'kgreen;': '\u0138',
+ 'khcy;': '\u0445',
+ 'kjcy;': '\u045c',
+ 'kopf;': '\u{01d55c}',
+ 'kscr;': '\u{01d4c0}',
+ 'lAarr;': '\u21da',
+ 'lArr;': '\u21d0',
+ 'lAtail;': '\u291b',
+ 'lBarr;': '\u290e',
+ 'lE;': '\u2266',
+ 'lEg;': '\u2a8b',
+ 'lHar;': '\u2962',
+ 'lacute;': '\u013a',
+ 'laemptyv;': '\u29b4',
+ 'lagran;': '\u2112',
+ 'lambda;': '\u03bb',
+ 'lang;': '\u27e8',
+ 'langd;': '\u2991',
+ 'langle;': '\u27e8',
+ 'lap;': '\u2a85',
+ 'laquo': '\xab',
+ 'laquo;': '\xab',
+ 'larr;': '\u2190',
+ 'larrb;': '\u21e4',
+ 'larrbfs;': '\u291f',
+ 'larrfs;': '\u291d',
+ 'larrhk;': '\u21a9',
+ 'larrlp;': '\u21ab',
+ 'larrpl;': '\u2939',
+ 'larrsim;': '\u2973',
+ 'larrtl;': '\u21a2',
+ 'lat;': '\u2aab',
+ 'latail;': '\u2919',
+ 'late;': '\u2aad',
+ 'lates;': '\u2aad\ufe00',
+ 'lbarr;': '\u290c',
+ 'lbbrk;': '\u2772',
+ 'lbrace;': '{',
+ 'lbrack;': '[',
+ 'lbrke;': '\u298b',
+ 'lbrksld;': '\u298f',
+ 'lbrkslu;': '\u298d',
+ 'lcaron;': '\u013e',
+ 'lcedil;': '\u013c',
+ 'lceil;': '\u2308',
+ 'lcub;': '{',
+ 'lcy;': '\u043b',
+ 'ldca;': '\u2936',
+ 'ldquo;': '\u201c',
+ 'ldquor;': '\u201e',
+ 'ldrdhar;': '\u2967',
+ 'ldrushar;': '\u294b',
+ 'ldsh;': '\u21b2',
+ 'le;': '\u2264',
+ 'leftarrow;': '\u2190',
+ 'leftarrowtail;': '\u21a2',
+ 'leftharpoondown;': '\u21bd',
+ 'leftharpoonup;': '\u21bc',
+ 'leftleftarrows;': '\u21c7',
+ 'leftrightarrow;': '\u2194',
+ 'leftrightarrows;': '\u21c6',
+ 'leftrightharpoons;': '\u21cb',
+ 'leftrightsquigarrow;': '\u21ad',
+ 'leftthreetimes;': '\u22cb',
+ 'leg;': '\u22da',
+ 'leq;': '\u2264',
+ 'leqq;': '\u2266',
+ 'leqslant;': '\u2a7d',
+ 'les;': '\u2a7d',
+ 'lescc;': '\u2aa8',
+ 'lesdot;': '\u2a7f',
+ 'lesdoto;': '\u2a81',
+ 'lesdotor;': '\u2a83',
+ 'lesg;': '\u22da\ufe00',
+ 'lesges;': '\u2a93',
+ 'lessapprox;': '\u2a85',
+ 'lessdot;': '\u22d6',
+ 'lesseqgtr;': '\u22da',
+ 'lesseqqgtr;': '\u2a8b',
+ 'lessgtr;': '\u2276',
+ 'lesssim;': '\u2272',
+ 'lfisht;': '\u297c',
+ 'lfloor;': '\u230a',
+ 'lfr;': '\u{01d529}',
+ 'lg;': '\u2276',
+ 'lgE;': '\u2a91',
+ 'lhard;': '\u21bd',
+ 'lharu;': '\u21bc',
+ 'lharul;': '\u296a',
+ 'lhblk;': '\u2584',
+ 'ljcy;': '\u0459',
+ 'll;': '\u226a',
+ 'llarr;': '\u21c7',
+ 'llcorner;': '\u231e',
+ 'llhard;': '\u296b',
+ 'lltri;': '\u25fa',
+ 'lmidot;': '\u0140',
+ 'lmoust;': '\u23b0',
+ 'lmoustache;': '\u23b0',
+ 'lnE;': '\u2268',
+ 'lnap;': '\u2a89',
+ 'lnapprox;': '\u2a89',
+ 'lne;': '\u2a87',
+ 'lneq;': '\u2a87',
+ 'lneqq;': '\u2268',
+ 'lnsim;': '\u22e6',
+ 'loang;': '\u27ec',
+ 'loarr;': '\u21fd',
+ 'lobrk;': '\u27e6',
+ 'longleftarrow;': '\u27f5',
+ 'longleftrightarrow;': '\u27f7',
+ 'longmapsto;': '\u27fc',
+ 'longrightarrow;': '\u27f6',
+ 'looparrowleft;': '\u21ab',
+ 'looparrowright;': '\u21ac',
+ 'lopar;': '\u2985',
+ 'lopf;': '\u{01d55d}',
+ 'loplus;': '\u2a2d',
+ 'lotimes;': '\u2a34',
+ 'lowast;': '\u2217',
+ 'lowbar;': '_',
+ 'loz;': '\u25ca',
+ 'lozenge;': '\u25ca',
+ 'lozf;': '\u29eb',
+ 'lpar;': '(',
+ 'lparlt;': '\u2993',
+ 'lrarr;': '\u21c6',
+ 'lrcorner;': '\u231f',
+ 'lrhar;': '\u21cb',
+ 'lrhard;': '\u296d',
+ 'lrm;': '\u200e',
+ 'lrtri;': '\u22bf',
+ 'lsaquo;': '\u2039',
+ 'lscr;': '\u{01d4c1}',
+ 'lsh;': '\u21b0',
+ 'lsim;': '\u2272',
+ 'lsime;': '\u2a8d',
+ 'lsimg;': '\u2a8f',
+ 'lsqb;': '[',
+ 'lsquo;': '\u2018',
+ 'lsquor;': '\u201a',
+ 'lstrok;': '\u0142',
+ 'lt': '<',
+ 'lt;': '<',
+ 'ltcc;': '\u2aa6',
+ 'ltcir;': '\u2a79',
+ 'ltdot;': '\u22d6',
+ 'lthree;': '\u22cb',
+ 'ltimes;': '\u22c9',
+ 'ltlarr;': '\u2976',
+ 'ltquest;': '\u2a7b',
+ 'ltrPar;': '\u2996',
+ 'ltri;': '\u25c3',
+ 'ltrie;': '\u22b4',
+ 'ltrif;': '\u25c2',
+ 'lurdshar;': '\u294a',
+ 'luruhar;': '\u2966',
+ 'lvertneqq;': '\u2268\ufe00',
+ 'lvnE;': '\u2268\ufe00',
+ 'mDDot;': '\u223a',
+ 'macr': '\xaf',
+ 'macr;': '\xaf',
+ 'male;': '\u2642',
+ 'malt;': '\u2720',
+ 'maltese;': '\u2720',
+ 'map;': '\u21a6',
+ 'mapsto;': '\u21a6',
+ 'mapstodown;': '\u21a7',
+ 'mapstoleft;': '\u21a4',
+ 'mapstoup;': '\u21a5',
+ 'marker;': '\u25ae',
+ 'mcomma;': '\u2a29',
+ 'mcy;': '\u043c',
+ 'mdash;': '\u2014',
+ 'measuredangle;': '\u2221',
+ 'mfr;': '\u{01d52a}',
+ 'mho;': '\u2127',
+ 'micro': '\xb5',
+ 'micro;': '\xb5',
+ 'mid;': '\u2223',
+ 'midast;': '*',
+ 'midcir;': '\u2af0',
+ 'middot': '\xb7',
+ 'middot;': '\xb7',
+ 'minus;': '\u2212',
+ 'minusb;': '\u229f',
+ 'minusd;': '\u2238',
+ 'minusdu;': '\u2a2a',
+ 'mlcp;': '\u2adb',
+ 'mldr;': '\u2026',
+ 'mnplus;': '\u2213',
+ 'models;': '\u22a7',
+ 'mopf;': '\u{01d55e}',
+ 'mp;': '\u2213',
+ 'mscr;': '\u{01d4c2}',
+ 'mstpos;': '\u223e',
+ 'mu;': '\u03bc',
+ 'multimap;': '\u22b8',
+ 'mumap;': '\u22b8',
+ 'nGg;': '\u22d9\u0338',
+ 'nGt;': '\u226b\u20d2',
+ 'nGtv;': '\u226b\u0338',
+ 'nLeftarrow;': '\u21cd',
+ 'nLeftrightarrow;': '\u21ce',
+ 'nLl;': '\u22d8\u0338',
+ 'nLt;': '\u226a\u20d2',
+ 'nLtv;': '\u226a\u0338',
+ 'nRightarrow;': '\u21cf',
+ 'nVDash;': '\u22af',
+ 'nVdash;': '\u22ae',
+ 'nabla;': '\u2207',
+ 'nacute;': '\u0144',
+ 'nang;': '\u2220\u20d2',
+ 'nap;': '\u2249',
+ 'napE;': '\u2a70\u0338',
+ 'napid;': '\u224b\u0338',
+ 'napos;': '\u0149',
+ 'napprox;': '\u2249',
+ 'natur;': '\u266e',
+ 'natural;': '\u266e',
+ 'naturals;': '\u2115',
+ 'nbsp': '\xa0',
+ 'nbsp;': '\xa0',
+ 'nbump;': '\u224e\u0338',
+ 'nbumpe;': '\u224f\u0338',
+ 'ncap;': '\u2a43',
+ 'ncaron;': '\u0148',
+ 'ncedil;': '\u0146',
+ 'ncong;': '\u2247',
+ 'ncongdot;': '\u2a6d\u0338',
+ 'ncup;': '\u2a42',
+ 'ncy;': '\u043d',
+ 'ndash;': '\u2013',
+ 'ne;': '\u2260',
+ 'neArr;': '\u21d7',
+ 'nearhk;': '\u2924',
+ 'nearr;': '\u2197',
+ 'nearrow;': '\u2197',
+ 'nedot;': '\u2250\u0338',
+ 'nequiv;': '\u2262',
+ 'nesear;': '\u2928',
+ 'nesim;': '\u2242\u0338',
+ 'nexist;': '\u2204',
+ 'nexists;': '\u2204',
+ 'nfr;': '\u{01d52b}',
+ 'ngE;': '\u2267\u0338',
+ 'nge;': '\u2271',
+ 'ngeq;': '\u2271',
+ 'ngeqq;': '\u2267\u0338',
+ 'ngeqslant;': '\u2a7e\u0338',
+ 'nges;': '\u2a7e\u0338',
+ 'ngsim;': '\u2275',
+ 'ngt;': '\u226f',
+ 'ngtr;': '\u226f',
+ 'nhArr;': '\u21ce',
+ 'nharr;': '\u21ae',
+ 'nhpar;': '\u2af2',
+ 'ni;': '\u220b',
+ 'nis;': '\u22fc',
+ 'nisd;': '\u22fa',
+ 'niv;': '\u220b',
+ 'njcy;': '\u045a',
+ 'nlArr;': '\u21cd',
+ 'nlE;': '\u2266\u0338',
+ 'nlarr;': '\u219a',
+ 'nldr;': '\u2025',
+ 'nle;': '\u2270',
+ 'nleftarrow;': '\u219a',
+ 'nleftrightarrow;': '\u21ae',
+ 'nleq;': '\u2270',
+ 'nleqq;': '\u2266\u0338',
+ 'nleqslant;': '\u2a7d\u0338',
+ 'nles;': '\u2a7d\u0338',
+ 'nless;': '\u226e',
+ 'nlsim;': '\u2274',
+ 'nlt;': '\u226e',
+ 'nltri;': '\u22ea',
+ 'nltrie;': '\u22ec',
+ 'nmid;': '\u2224',
+ 'nopf;': '\u{01d55f}',
+ 'not': '\xac',
+ 'not;': '\xac',
+ 'notin;': '\u2209',
+ 'notinE;': '\u22f9\u0338',
+ 'notindot;': '\u22f5\u0338',
+ 'notinva;': '\u2209',
+ 'notinvb;': '\u22f7',
+ 'notinvc;': '\u22f6',
+ 'notni;': '\u220c',
+ 'notniva;': '\u220c',
+ 'notnivb;': '\u22fe',
+ 'notnivc;': '\u22fd',
+ 'npar;': '\u2226',
+ 'nparallel;': '\u2226',
+ 'nparsl;': '\u2afd\u20e5',
+ 'npart;': '\u2202\u0338',
+ 'npolint;': '\u2a14',
+ 'npr;': '\u2280',
+ 'nprcue;': '\u22e0',
+ 'npre;': '\u2aaf\u0338',
+ 'nprec;': '\u2280',
+ 'npreceq;': '\u2aaf\u0338',
+ 'nrArr;': '\u21cf',
+ 'nrarr;': '\u219b',
+ 'nrarrc;': '\u2933\u0338',
+ 'nrarrw;': '\u219d\u0338',
+ 'nrightarrow;': '\u219b',
+ 'nrtri;': '\u22eb',
+ 'nrtrie;': '\u22ed',
+ 'nsc;': '\u2281',
+ 'nsccue;': '\u22e1',
+ 'nsce;': '\u2ab0\u0338',
+ 'nscr;': '\u{01d4c3}',
+ 'nshortmid;': '\u2224',
+ 'nshortparallel;': '\u2226',
+ 'nsim;': '\u2241',
+ 'nsime;': '\u2244',
+ 'nsimeq;': '\u2244',
+ 'nsmid;': '\u2224',
+ 'nspar;': '\u2226',
+ 'nsqsube;': '\u22e2',
+ 'nsqsupe;': '\u22e3',
+ 'nsub;': '\u2284',
+ 'nsubE;': '\u2ac5\u0338',
+ 'nsube;': '\u2288',
+ 'nsubset;': '\u2282\u20d2',
+ 'nsubseteq;': '\u2288',
+ 'nsubseteqq;': '\u2ac5\u0338',
+ 'nsucc;': '\u2281',
+ 'nsucceq;': '\u2ab0\u0338',
+ 'nsup;': '\u2285',
+ 'nsupE;': '\u2ac6\u0338',
+ 'nsupe;': '\u2289',
+ 'nsupset;': '\u2283\u20d2',
+ 'nsupseteq;': '\u2289',
+ 'nsupseteqq;': '\u2ac6\u0338',
+ 'ntgl;': '\u2279',
+ 'ntilde': '\xf1',
+ 'ntilde;': '\xf1',
+ 'ntlg;': '\u2278',
+ 'ntriangleleft;': '\u22ea',
+ 'ntrianglelefteq;': '\u22ec',
+ 'ntriangleright;': '\u22eb',
+ 'ntrianglerighteq;': '\u22ed',
+ 'nu;': '\u03bd',
+ 'num;': '#',
+ 'numero;': '\u2116',
+ 'numsp;': '\u2007',
+ 'nvDash;': '\u22ad',
+ 'nvHarr;': '\u2904',
+ 'nvap;': '\u224d\u20d2',
+ 'nvdash;': '\u22ac',
+ 'nvge;': '\u2265\u20d2',
+ 'nvgt;': '>\u20d2',
+ 'nvinfin;': '\u29de',
+ 'nvlArr;': '\u2902',
+ 'nvle;': '\u2264\u20d2',
+ 'nvlt;': '<\u20d2',
+ 'nvltrie;': '\u22b4\u20d2',
+ 'nvrArr;': '\u2903',
+ 'nvrtrie;': '\u22b5\u20d2',
+ 'nvsim;': '\u223c\u20d2',
+ 'nwArr;': '\u21d6',
+ 'nwarhk;': '\u2923',
+ 'nwarr;': '\u2196',
+ 'nwarrow;': '\u2196',
+ 'nwnear;': '\u2927',
+ 'oS;': '\u24c8',
+ 'oacute': '\xf3',
+ 'oacute;': '\xf3',
+ 'oast;': '\u229b',
+ 'ocir;': '\u229a',
+ 'ocirc': '\xf4',
+ 'ocirc;': '\xf4',
+ 'ocy;': '\u043e',
+ 'odash;': '\u229d',
+ 'odblac;': '\u0151',
+ 'odiv;': '\u2a38',
+ 'odot;': '\u2299',
+ 'odsold;': '\u29bc',
+ 'oelig;': '\u0153',
+ 'ofcir;': '\u29bf',
+ 'ofr;': '\u{01d52c}',
+ 'ogon;': '\u02db',
+ 'ograve': '\xf2',
+ 'ograve;': '\xf2',
+ 'ogt;': '\u29c1',
+ 'ohbar;': '\u29b5',
+ 'ohm;': '\u03a9',
+ 'oint;': '\u222e',
+ 'olarr;': '\u21ba',
+ 'olcir;': '\u29be',
+ 'olcross;': '\u29bb',
+ 'oline;': '\u203e',
+ 'olt;': '\u29c0',
+ 'omacr;': '\u014d',
+ 'omega;': '\u03c9',
+ 'omicron;': '\u03bf',
+ 'omid;': '\u29b6',
+ 'ominus;': '\u2296',
+ 'oopf;': '\u{01d560}',
+ 'opar;': '\u29b7',
+ 'operp;': '\u29b9',
+ 'oplus;': '\u2295',
+ 'or;': '\u2228',
+ 'orarr;': '\u21bb',
+ 'ord;': '\u2a5d',
+ 'order;': '\u2134',
+ 'orderof;': '\u2134',
+ 'ordf': '\xaa',
+ 'ordf;': '\xaa',
+ 'ordm': '\xba',
+ 'ordm;': '\xba',
+ 'origof;': '\u22b6',
+ 'oror;': '\u2a56',
+ 'orslope;': '\u2a57',
+ 'orv;': '\u2a5b',
+ 'oscr;': '\u2134',
+ 'oslash': '\xf8',
+ 'oslash;': '\xf8',
+ 'osol;': '\u2298',
+ 'otilde': '\xf5',
+ 'otilde;': '\xf5',
+ 'otimes;': '\u2297',
+ 'otimesas;': '\u2a36',
+ 'ouml': '\xf6',
+ 'ouml;': '\xf6',
+ 'ovbar;': '\u233d',
+ 'par;': '\u2225',
+ 'para': '\xb6',
+ 'para;': '\xb6',
+ 'parallel;': '\u2225',
+ 'parsim;': '\u2af3',
+ 'parsl;': '\u2afd',
+ 'part;': '\u2202',
+ 'pcy;': '\u043f',
+ 'percnt;': '%',
+ 'period;': '.',
+ 'permil;': '\u2030',
+ 'perp;': '\u22a5',
+ 'pertenk;': '\u2031',
+ 'pfr;': '\u{01d52d}',
+ 'phi;': '\u03c6',
+ 'phiv;': '\u03d5',
+ 'phmmat;': '\u2133',
+ 'phone;': '\u260e',
+ 'pi;': '\u03c0',
+ 'pitchfork;': '\u22d4',
+ 'piv;': '\u03d6',
+ 'planck;': '\u210f',
+ 'planckh;': '\u210e',
+ 'plankv;': '\u210f',
+ 'plus;': '+',
+ 'plusacir;': '\u2a23',
+ 'plusb;': '\u229e',
+ 'pluscir;': '\u2a22',
+ 'plusdo;': '\u2214',
+ 'plusdu;': '\u2a25',
+ 'pluse;': '\u2a72',
+ 'plusmn': '\xb1',
+ 'plusmn;': '\xb1',
+ 'plussim;': '\u2a26',
+ 'plustwo;': '\u2a27',
+ 'pm;': '\xb1',
+ 'pointint;': '\u2a15',
+ 'popf;': '\u{01d561}',
+ 'pound': '\xa3',
+ 'pound;': '\xa3',
+ 'pr;': '\u227a',
+ 'prE;': '\u2ab3',
+ 'prap;': '\u2ab7',
+ 'prcue;': '\u227c',
+ 'pre;': '\u2aaf',
+ 'prec;': '\u227a',
+ 'precapprox;': '\u2ab7',
+ 'preccurlyeq;': '\u227c',
+ 'preceq;': '\u2aaf',
+ 'precnapprox;': '\u2ab9',
+ 'precneqq;': '\u2ab5',
+ 'precnsim;': '\u22e8',
+ 'precsim;': '\u227e',
+ 'prime;': '\u2032',
+ 'primes;': '\u2119',
+ 'prnE;': '\u2ab5',
+ 'prnap;': '\u2ab9',
+ 'prnsim;': '\u22e8',
+ 'prod;': '\u220f',
+ 'profalar;': '\u232e',
+ 'profline;': '\u2312',
+ 'profsurf;': '\u2313',
+ 'prop;': '\u221d',
+ 'propto;': '\u221d',
+ 'prsim;': '\u227e',
+ 'prurel;': '\u22b0',
+ 'pscr;': '\u{01d4c5}',
+ 'psi;': '\u03c8',
+ 'puncsp;': '\u2008',
+ 'qfr;': '\u{01d52e}',
+ 'qint;': '\u2a0c',
+ 'qopf;': '\u{01d562}',
+ 'qprime;': '\u2057',
+ 'qscr;': '\u{01d4c6}',
+ 'quaternions;': '\u210d',
+ 'quatint;': '\u2a16',
+ 'quest;': '?',
+ 'questeq;': '\u225f',
+ 'quot': '"',
+ 'quot;': '"',
+ 'rAarr;': '\u21db',
+ 'rArr;': '\u21d2',
+ 'rAtail;': '\u291c',
+ 'rBarr;': '\u290f',
+ 'rHar;': '\u2964',
+ 'race;': '\u223d\u0331',
+ 'racute;': '\u0155',
+ 'radic;': '\u221a',
+ 'raemptyv;': '\u29b3',
+ 'rang;': '\u27e9',
+ 'rangd;': '\u2992',
+ 'range;': '\u29a5',
+ 'rangle;': '\u27e9',
+ 'raquo': '\xbb',
+ 'raquo;': '\xbb',
+ 'rarr;': '\u2192',
+ 'rarrap;': '\u2975',
+ 'rarrb;': '\u21e5',
+ 'rarrbfs;': '\u2920',
+ 'rarrc;': '\u2933',
+ 'rarrfs;': '\u291e',
+ 'rarrhk;': '\u21aa',
+ 'rarrlp;': '\u21ac',
+ 'rarrpl;': '\u2945',
+ 'rarrsim;': '\u2974',
+ 'rarrtl;': '\u21a3',
+ 'rarrw;': '\u219d',
+ 'ratail;': '\u291a',
+ 'ratio;': '\u2236',
+ 'rationals;': '\u211a',
+ 'rbarr;': '\u290d',
+ 'rbbrk;': '\u2773',
+ 'rbrace;': '}',
+ 'rbrack;': ']',
+ 'rbrke;': '\u298c',
+ 'rbrksld;': '\u298e',
+ 'rbrkslu;': '\u2990',
+ 'rcaron;': '\u0159',
+ 'rcedil;': '\u0157',
+ 'rceil;': '\u2309',
+ 'rcub;': '}',
+ 'rcy;': '\u0440',
+ 'rdca;': '\u2937',
+ 'rdldhar;': '\u2969',
+ 'rdquo;': '\u201d',
+ 'rdquor;': '\u201d',
+ 'rdsh;': '\u21b3',
+ 'real;': '\u211c',
+ 'realine;': '\u211b',
+ 'realpart;': '\u211c',
+ 'reals;': '\u211d',
+ 'rect;': '\u25ad',
+ 'reg': '\xae',
+ 'reg;': '\xae',
+ 'rfisht;': '\u297d',
+ 'rfloor;': '\u230b',
+ 'rfr;': '\u{01d52f}',
+ 'rhard;': '\u21c1',
+ 'rharu;': '\u21c0',
+ 'rharul;': '\u296c',
+ 'rho;': '\u03c1',
+ 'rhov;': '\u03f1',
+ 'rightarrow;': '\u2192',
+ 'rightarrowtail;': '\u21a3',
+ 'rightharpoondown;': '\u21c1',
+ 'rightharpoonup;': '\u21c0',
+ 'rightleftarrows;': '\u21c4',
+ 'rightleftharpoons;': '\u21cc',
+ 'rightrightarrows;': '\u21c9',
+ 'rightsquigarrow;': '\u219d',
+ 'rightthreetimes;': '\u22cc',
+ 'ring;': '\u02da',
+ 'risingdotseq;': '\u2253',
+ 'rlarr;': '\u21c4',
+ 'rlhar;': '\u21cc',
+ 'rlm;': '\u200f',
+ 'rmoust;': '\u23b1',
+ 'rmoustache;': '\u23b1',
+ 'rnmid;': '\u2aee',
+ 'roang;': '\u27ed',
+ 'roarr;': '\u21fe',
+ 'robrk;': '\u27e7',
+ 'ropar;': '\u2986',
+ 'ropf;': '\u{01d563}',
+ 'roplus;': '\u2a2e',
+ 'rotimes;': '\u2a35',
+ 'rpar;': ')',
+ 'rpargt;': '\u2994',
+ 'rppolint;': '\u2a12',
+ 'rrarr;': '\u21c9',
+ 'rsaquo;': '\u203a',
+ 'rscr;': '\u{01d4c7}',
+ 'rsh;': '\u21b1',
+ 'rsqb;': ']',
+ 'rsquo;': '\u2019',
+ 'rsquor;': '\u2019',
+ 'rthree;': '\u22cc',
+ 'rtimes;': '\u22ca',
+ 'rtri;': '\u25b9',
+ 'rtrie;': '\u22b5',
+ 'rtrif;': '\u25b8',
+ 'rtriltri;': '\u29ce',
+ 'ruluhar;': '\u2968',
+ 'rx;': '\u211e',
+ 'sacute;': '\u015b',
+ 'sbquo;': '\u201a',
+ 'sc;': '\u227b',
+ 'scE;': '\u2ab4',
+ 'scap;': '\u2ab8',
+ 'scaron;': '\u0161',
+ 'sccue;': '\u227d',
+ 'sce;': '\u2ab0',
+ 'scedil;': '\u015f',
+ 'scirc;': '\u015d',
+ 'scnE;': '\u2ab6',
+ 'scnap;': '\u2aba',
+ 'scnsim;': '\u22e9',
+ 'scpolint;': '\u2a13',
+ 'scsim;': '\u227f',
+ 'scy;': '\u0441',
+ 'sdot;': '\u22c5',
+ 'sdotb;': '\u22a1',
+ 'sdote;': '\u2a66',
+ 'seArr;': '\u21d8',
+ 'searhk;': '\u2925',
+ 'searr;': '\u2198',
+ 'searrow;': '\u2198',
+ 'sect': '\xa7',
+ 'sect;': '\xa7',
+ 'semi;': ';',
+ 'seswar;': '\u2929',
+ 'setminus;': '\u2216',
+ 'setmn;': '\u2216',
+ 'sext;': '\u2736',
+ 'sfr;': '\u{01d530}',
+ 'sfrown;': '\u2322',
+ 'sharp;': '\u266f',
+ 'shchcy;': '\u0449',
+ 'shcy;': '\u0448',
+ 'shortmid;': '\u2223',
+ 'shortparallel;': '\u2225',
+ 'shy': '\xad',
+ 'shy;': '\xad',
+ 'sigma;': '\u03c3',
+ 'sigmaf;': '\u03c2',
+ 'sigmav;': '\u03c2',
+ 'sim;': '\u223c',
+ 'simdot;': '\u2a6a',
+ 'sime;': '\u2243',
+ 'simeq;': '\u2243',
+ 'simg;': '\u2a9e',
+ 'simgE;': '\u2aa0',
+ 'siml;': '\u2a9d',
+ 'simlE;': '\u2a9f',
+ 'simne;': '\u2246',
+ 'simplus;': '\u2a24',
+ 'simrarr;': '\u2972',
+ 'slarr;': '\u2190',
+ 'smallsetminus;': '\u2216',
+ 'smashp;': '\u2a33',
+ 'smeparsl;': '\u29e4',
+ 'smid;': '\u2223',
+ 'smile;': '\u2323',
+ 'smt;': '\u2aaa',
+ 'smte;': '\u2aac',
+ 'smtes;': '\u2aac\ufe00',
+ 'softcy;': '\u044c',
+ 'sol;': '/',
+ 'solb;': '\u29c4',
+ 'solbar;': '\u233f',
+ 'sopf;': '\u{01d564}',
+ 'spades;': '\u2660',
+ 'spadesuit;': '\u2660',
+ 'spar;': '\u2225',
+ 'sqcap;': '\u2293',
+ 'sqcaps;': '\u2293\ufe00',
+ 'sqcup;': '\u2294',
+ 'sqcups;': '\u2294\ufe00',
+ 'sqsub;': '\u228f',
+ 'sqsube;': '\u2291',
+ 'sqsubset;': '\u228f',
+ 'sqsubseteq;': '\u2291',
+ 'sqsup;': '\u2290',
+ 'sqsupe;': '\u2292',
+ 'sqsupset;': '\u2290',
+ 'sqsupseteq;': '\u2292',
+ 'squ;': '\u25a1',
+ 'square;': '\u25a1',
+ 'squarf;': '\u25aa',
+ 'squf;': '\u25aa',
+ 'srarr;': '\u2192',
+ 'sscr;': '\u{01d4c8}',
+ 'ssetmn;': '\u2216',
+ 'ssmile;': '\u2323',
+ 'sstarf;': '\u22c6',
+ 'star;': '\u2606',
+ 'starf;': '\u2605',
+ 'straightepsilon;': '\u03f5',
+ 'straightphi;': '\u03d5',
+ 'strns;': '\xaf',
+ 'sub;': '\u2282',
+ 'subE;': '\u2ac5',
+ 'subdot;': '\u2abd',
+ 'sube;': '\u2286',
+ 'subedot;': '\u2ac3',
+ 'submult;': '\u2ac1',
+ 'subnE;': '\u2acb',
+ 'subne;': '\u228a',
+ 'subplus;': '\u2abf',
+ 'subrarr;': '\u2979',
+ 'subset;': '\u2282',
+ 'subseteq;': '\u2286',
+ 'subseteqq;': '\u2ac5',
+ 'subsetneq;': '\u228a',
+ 'subsetneqq;': '\u2acb',
+ 'subsim;': '\u2ac7',
+ 'subsub;': '\u2ad5',
+ 'subsup;': '\u2ad3',
+ 'succ;': '\u227b',
+ 'succapprox;': '\u2ab8',
+ 'succcurlyeq;': '\u227d',
+ 'succeq;': '\u2ab0',
+ 'succnapprox;': '\u2aba',
+ 'succneqq;': '\u2ab6',
+ 'succnsim;': '\u22e9',
+ 'succsim;': '\u227f',
+ 'sum;': '\u2211',
+ 'sung;': '\u266a',
+ 'sup1': '\xb9',
+ 'sup1;': '\xb9',
+ 'sup2': '\xb2',
+ 'sup2;': '\xb2',
+ 'sup3': '\xb3',
+ 'sup3;': '\xb3',
+ 'sup;': '\u2283',
+ 'supE;': '\u2ac6',
+ 'supdot;': '\u2abe',
+ 'supdsub;': '\u2ad8',
+ 'supe;': '\u2287',
+ 'supedot;': '\u2ac4',
+ 'suphsol;': '\u27c9',
+ 'suphsub;': '\u2ad7',
+ 'suplarr;': '\u297b',
+ 'supmult;': '\u2ac2',
+ 'supnE;': '\u2acc',
+ 'supne;': '\u228b',
+ 'supplus;': '\u2ac0',
+ 'supset;': '\u2283',
+ 'supseteq;': '\u2287',
+ 'supseteqq;': '\u2ac6',
+ 'supsetneq;': '\u228b',
+ 'supsetneqq;': '\u2acc',
+ 'supsim;': '\u2ac8',
+ 'supsub;': '\u2ad4',
+ 'supsup;': '\u2ad6',
+ 'swArr;': '\u21d9',
+ 'swarhk;': '\u2926',
+ 'swarr;': '\u2199',
+ 'swarrow;': '\u2199',
+ 'swnwar;': '\u292a',
+ 'szlig': '\xdf',
+ 'szlig;': '\xdf',
+ 'target;': '\u2316',
+ 'tau;': '\u03c4',
+ 'tbrk;': '\u23b4',
+ 'tcaron;': '\u0165',
+ 'tcedil;': '\u0163',
+ 'tcy;': '\u0442',
+ 'tdot;': '\u20db',
+ 'telrec;': '\u2315',
+ 'tfr;': '\u{01d531}',
+ 'there4;': '\u2234',
+ 'therefore;': '\u2234',
+ 'theta;': '\u03b8',
+ 'thetasym;': '\u03d1',
+ 'thetav;': '\u03d1',
+ 'thickapprox;': '\u2248',
+ 'thicksim;': '\u223c',
+ 'thinsp;': '\u2009',
+ 'thkap;': '\u2248',
+ 'thksim;': '\u223c',
+ 'thorn': '\xfe',
+ 'thorn;': '\xfe',
+ 'tilde;': '\u02dc',
+ 'times': '\xd7',
+ 'times;': '\xd7',
+ 'timesb;': '\u22a0',
+ 'timesbar;': '\u2a31',
+ 'timesd;': '\u2a30',
+ 'tint;': '\u222d',
+ 'toea;': '\u2928',
+ 'top;': '\u22a4',
+ 'topbot;': '\u2336',
+ 'topcir;': '\u2af1',
+ 'topf;': '\u{01d565}',
+ 'topfork;': '\u2ada',
+ 'tosa;': '\u2929',
+ 'tprime;': '\u2034',
+ 'trade;': '\u2122',
+ 'triangle;': '\u25b5',
+ 'triangledown;': '\u25bf',
+ 'triangleleft;': '\u25c3',
+ 'trianglelefteq;': '\u22b4',
+ 'triangleq;': '\u225c',
+ 'triangleright;': '\u25b9',
+ 'trianglerighteq;': '\u22b5',
+ 'tridot;': '\u25ec',
+ 'trie;': '\u225c',
+ 'triminus;': '\u2a3a',
+ 'triplus;': '\u2a39',
+ 'trisb;': '\u29cd',
+ 'tritime;': '\u2a3b',
+ 'trpezium;': '\u23e2',
+ 'tscr;': '\u{01d4c9}',
+ 'tscy;': '\u0446',
+ 'tshcy;': '\u045b',
+ 'tstrok;': '\u0167',
+ 'twixt;': '\u226c',
+ 'twoheadleftarrow;': '\u219e',
+ 'twoheadrightarrow;': '\u21a0',
+ 'uArr;': '\u21d1',
+ 'uHar;': '\u2963',
+ 'uacute': '\xfa',
+ 'uacute;': '\xfa',
+ 'uarr;': '\u2191',
+ 'ubrcy;': '\u045e',
+ 'ubreve;': '\u016d',
+ 'ucirc': '\xfb',
+ 'ucirc;': '\xfb',
+ 'ucy;': '\u0443',
+ 'udarr;': '\u21c5',
+ 'udblac;': '\u0171',
+ 'udhar;': '\u296e',
+ 'ufisht;': '\u297e',
+ 'ufr;': '\u{01d532}',
+ 'ugrave': '\xf9',
+ 'ugrave;': '\xf9',
+ 'uharl;': '\u21bf',
+ 'uharr;': '\u21be',
+ 'uhblk;': '\u2580',
+ 'ulcorn;': '\u231c',
+ 'ulcorner;': '\u231c',
+ 'ulcrop;': '\u230f',
+ 'ultri;': '\u25f8',
+ 'umacr;': '\u016b',
+ 'uml': '\xa8',
+ 'uml;': '\xa8',
+ 'uogon;': '\u0173',
+ 'uopf;': '\u{01d566}',
+ 'uparrow;': '\u2191',
+ 'updownarrow;': '\u2195',
+ 'upharpoonleft;': '\u21bf',
+ 'upharpoonright;': '\u21be',
+ 'uplus;': '\u228e',
+ 'upsi;': '\u03c5',
+ 'upsih;': '\u03d2',
+ 'upsilon;': '\u03c5',
+ 'upuparrows;': '\u21c8',
+ 'urcorn;': '\u231d',
+ 'urcorner;': '\u231d',
+ 'urcrop;': '\u230e',
+ 'uring;': '\u016f',
+ 'urtri;': '\u25f9',
+ 'uscr;': '\u{01d4ca}',
+ 'utdot;': '\u22f0',
+ 'utilde;': '\u0169',
+ 'utri;': '\u25b5',
+ 'utrif;': '\u25b4',
+ 'uuarr;': '\u21c8',
+ 'uuml': '\xfc',
+ 'uuml;': '\xfc',
+ 'uwangle;': '\u29a7',
+ 'vArr;': '\u21d5',
+ 'vBar;': '\u2ae8',
+ 'vBarv;': '\u2ae9',
+ 'vDash;': '\u22a8',
+ 'vangrt;': '\u299c',
+ 'varepsilon;': '\u03f5',
+ 'varkappa;': '\u03f0',
+ 'varnothing;': '\u2205',
+ 'varphi;': '\u03d5',
+ 'varpi;': '\u03d6',
+ 'varpropto;': '\u221d',
+ 'varr;': '\u2195',
+ 'varrho;': '\u03f1',
+ 'varsigma;': '\u03c2',
+ 'varsubsetneq;': '\u228a\ufe00',
+ 'varsubsetneqq;': '\u2acb\ufe00',
+ 'varsupsetneq;': '\u228b\ufe00',
+ 'varsupsetneqq;': '\u2acc\ufe00',
+ 'vartheta;': '\u03d1',
+ 'vartriangleleft;': '\u22b2',
+ 'vartriangleright;': '\u22b3',
+ 'vcy;': '\u0432',
+ 'vdash;': '\u22a2',
+ 'vee;': '\u2228',
+ 'veebar;': '\u22bb',
+ 'veeeq;': '\u225a',
+ 'vellip;': '\u22ee',
+ 'verbar;': '|',
+ 'vert;': '|',
+ 'vfr;': '\u{01d533}',
+ 'vltri;': '\u22b2',
+ 'vnsub;': '\u2282\u20d2',
+ 'vnsup;': '\u2283\u20d2',
+ 'vopf;': '\u{01d567}',
+ 'vprop;': '\u221d',
+ 'vrtri;': '\u22b3',
+ 'vscr;': '\u{01d4cb}',
+ 'vsubnE;': '\u2acb\ufe00',
+ 'vsubne;': '\u228a\ufe00',
+ 'vsupnE;': '\u2acc\ufe00',
+ 'vsupne;': '\u228b\ufe00',
+ 'vzigzag;': '\u299a',
+ 'wcirc;': '\u0175',
+ 'wedbar;': '\u2a5f',
+ 'wedge;': '\u2227',
+ 'wedgeq;': '\u2259',
+ 'weierp;': '\u2118',
+ 'wfr;': '\u{01d534}',
+ 'wopf;': '\u{01d568}',
+ 'wp;': '\u2118',
+ 'wr;': '\u2240',
+ 'wreath;': '\u2240',
+ 'wscr;': '\u{01d4cc}',
+ 'xcap;': '\u22c2',
+ 'xcirc;': '\u25ef',
+ 'xcup;': '\u22c3',
+ 'xdtri;': '\u25bd',
+ 'xfr;': '\u{01d535}',
+ 'xhArr;': '\u27fa',
+ 'xharr;': '\u27f7',
+ 'xi;': '\u03be',
+ 'xlArr;': '\u27f8',
+ 'xlarr;': '\u27f5',
+ 'xmap;': '\u27fc',
+ 'xnis;': '\u22fb',
+ 'xodot;': '\u2a00',
+ 'xopf;': '\u{01d569}',
+ 'xoplus;': '\u2a01',
+ 'xotime;': '\u2a02',
+ 'xrArr;': '\u27f9',
+ 'xrarr;': '\u27f6',
+ 'xscr;': '\u{01d4cd}',
+ 'xsqcup;': '\u2a06',
+ 'xuplus;': '\u2a04',
+ 'xutri;': '\u25b3',
+ 'xvee;': '\u22c1',
+ 'xwedge;': '\u22c0',
+ 'yacute': '\xfd',
+ 'yacute;': '\xfd',
+ 'yacy;': '\u044f',
+ 'ycirc;': '\u0177',
+ 'ycy;': '\u044b',
+ 'yen': '\xa5',
+ 'yen;': '\xa5',
+ 'yfr;': '\u{01d536}',
+ 'yicy;': '\u0457',
+ 'yopf;': '\u{01d56a}',
+ 'yscr;': '\u{01d4ce}',
+ 'yucy;': '\u044e',
+ 'yuml': '\xff',
+ 'yuml;': '\xff',
+ 'zacute;': '\u017a',
+ 'zcaron;': '\u017e',
+ 'zcy;': '\u0437',
+ 'zdot;': '\u017c',
+ 'zeetrf;': '\u2128',
+ 'zeta;': '\u03b6',
+ 'zfr;': '\u{01d537}',
+ 'zhcy;': '\u0436',
+ 'zigrarr;': '\u21dd',
+ 'zopf;': '\u{01d56b}',
+ 'zscr;': '\u{01d4cf}',
+ 'zwj;': '\u200d',
+ 'zwnj;': '\u200c',
+};
+
+const Map<int, String> replacementCharacters = {
+ 0x00: '\uFFFD',
+ 0x0d: '\u000D',
+ 0x80: '\u20AC',
+ 0x81: '\u0081',
+ 0x82: '\u201A',
+ 0x83: '\u0192',
+ 0x84: '\u201E',
+ 0x85: '\u2026',
+ 0x86: '\u2020',
+ 0x87: '\u2021',
+ 0x88: '\u02C6',
+ 0x89: '\u2030',
+ 0x8A: '\u0160',
+ 0x8B: '\u2039',
+ 0x8C: '\u0152',
+ 0x8D: '\u008D',
+ 0x8E: '\u017D',
+ 0x8F: '\u008F',
+ 0x90: '\u0090',
+ 0x91: '\u2018',
+ 0x92: '\u2019',
+ 0x93: '\u201C',
+ 0x94: '\u201D',
+ 0x95: '\u2022',
+ 0x96: '\u2013',
+ 0x97: '\u2014',
+ 0x98: '\u02DC',
+ 0x99: '\u2122',
+ 0x9A: '\u0161',
+ 0x9B: '\u203A',
+ 0x9C: '\u0153',
+ 0x9D: '\u009D',
+ 0x9E: '\u017E',
+ 0x9F: '\u0178'
+};
+
+const Map<String, String> encodings = {
+ '437': 'cp437',
+ '850': 'cp850',
+ '852': 'cp852',
+ '855': 'cp855',
+ '857': 'cp857',
+ '860': 'cp860',
+ '861': 'cp861',
+ '862': 'cp862',
+ '863': 'cp863',
+ '865': 'cp865',
+ '866': 'cp866',
+ '869': 'cp869',
+ 'ansix341968': 'ascii',
+ 'ansix341986': 'ascii',
+ 'arabic': 'iso8859-6',
+ 'ascii': 'ascii',
+ 'asmo708': 'iso8859-6',
+ 'big5': 'big5',
+ 'big5hkscs': 'big5hkscs',
+ 'chinese': 'gbk',
+ 'cp037': 'cp037',
+ 'cp1026': 'cp1026',
+ 'cp154': 'ptcp154',
+ 'cp367': 'ascii',
+ 'cp424': 'cp424',
+ 'cp437': 'cp437',
+ 'cp500': 'cp500',
+ 'cp775': 'cp775',
+ 'cp819': 'windows-1252',
+ 'cp850': 'cp850',
+ 'cp852': 'cp852',
+ 'cp855': 'cp855',
+ 'cp857': 'cp857',
+ 'cp860': 'cp860',
+ 'cp861': 'cp861',
+ 'cp862': 'cp862',
+ 'cp863': 'cp863',
+ 'cp864': 'cp864',
+ 'cp865': 'cp865',
+ 'cp866': 'cp866',
+ 'cp869': 'cp869',
+ 'cp936': 'gbk',
+ 'cpgr': 'cp869',
+ 'cpis': 'cp861',
+ 'csascii': 'ascii',
+ 'csbig5': 'big5',
+ 'cseuckr': 'cp949',
+ 'cseucpkdfmtjapanese': 'euc_jp',
+ 'csgb2312': 'gbk',
+ 'cshproman8': 'hp-roman8',
+ 'csibm037': 'cp037',
+ 'csibm1026': 'cp1026',
+ 'csibm424': 'cp424',
+ 'csibm500': 'cp500',
+ 'csibm855': 'cp855',
+ 'csibm857': 'cp857',
+ 'csibm860': 'cp860',
+ 'csibm861': 'cp861',
+ 'csibm863': 'cp863',
+ 'csibm864': 'cp864',
+ 'csibm865': 'cp865',
+ 'csibm866': 'cp866',
+ 'csibm869': 'cp869',
+ 'csiso2022jp': 'iso2022_jp',
+ 'csiso2022jp2': 'iso2022_jp_2',
+ 'csiso2022kr': 'iso2022_kr',
+ 'csiso58gb231280': 'gbk',
+ 'csisolatin1': 'windows-1252',
+ 'csisolatin2': 'iso8859-2',
+ 'csisolatin3': 'iso8859-3',
+ 'csisolatin4': 'iso8859-4',
+ 'csisolatin5': 'windows-1254',
+ 'csisolatin6': 'iso8859-10',
+ 'csisolatinarabic': 'iso8859-6',
+ 'csisolatincyrillic': 'iso8859-5',
+ 'csisolatingreek': 'iso8859-7',
+ 'csisolatinhebrew': 'iso8859-8',
+ 'cskoi8r': 'koi8-r',
+ 'csksc56011987': 'cp949',
+ 'cspc775baltic': 'cp775',
+ 'cspc850multilingual': 'cp850',
+ 'cspc862latinhebrew': 'cp862',
+ 'cspc8codepage437': 'cp437',
+ 'cspcp852': 'cp852',
+ 'csptcp154': 'ptcp154',
+ 'csshiftjis': 'shift_jis',
+ 'csunicode11utf7': 'utf-7',
+ 'cyrillic': 'iso8859-5',
+ 'cyrillicasian': 'ptcp154',
+ 'ebcdiccpbe': 'cp500',
+ 'ebcdiccpca': 'cp037',
+ 'ebcdiccpch': 'cp500',
+ 'ebcdiccphe': 'cp424',
+ 'ebcdiccpnl': 'cp037',
+ 'ebcdiccpus': 'cp037',
+ 'ebcdiccpwt': 'cp037',
+ 'ecma114': 'iso8859-6',
+ 'ecma118': 'iso8859-7',
+ 'elot928': 'iso8859-7',
+ 'eucjp': 'euc_jp',
+ 'euckr': 'cp949',
+ 'extendedunixcodepackedformatforjapanese': 'euc_jp',
+ 'gb18030': 'gb18030',
+ 'gb2312': 'gbk',
+ 'gb231280': 'gbk',
+ 'gbk': 'gbk',
+ 'greek': 'iso8859-7',
+ 'greek8': 'iso8859-7',
+ 'hebrew': 'iso8859-8',
+ 'hproman8': 'hp-roman8',
+ 'hzgb2312': 'hz',
+ 'ibm037': 'cp037',
+ 'ibm1026': 'cp1026',
+ 'ibm367': 'ascii',
+ 'ibm424': 'cp424',
+ 'ibm437': 'cp437',
+ 'ibm500': 'cp500',
+ 'ibm775': 'cp775',
+ 'ibm819': 'windows-1252',
+ 'ibm850': 'cp850',
+ 'ibm852': 'cp852',
+ 'ibm855': 'cp855',
+ 'ibm857': 'cp857',
+ 'ibm860': 'cp860',
+ 'ibm861': 'cp861',
+ 'ibm862': 'cp862',
+ 'ibm863': 'cp863',
+ 'ibm864': 'cp864',
+ 'ibm865': 'cp865',
+ 'ibm866': 'cp866',
+ 'ibm869': 'cp869',
+ 'iso2022jp': 'iso2022_jp',
+ 'iso2022jp2': 'iso2022_jp_2',
+ 'iso2022kr': 'iso2022_kr',
+ 'iso646irv1991': 'ascii',
+ 'iso646us': 'ascii',
+ 'iso88591': 'windows-1252',
+ 'iso885910': 'iso8859-10',
+ 'iso8859101992': 'iso8859-10',
+ 'iso885911987': 'windows-1252',
+ 'iso885913': 'iso8859-13',
+ 'iso885914': 'iso8859-14',
+ 'iso8859141998': 'iso8859-14',
+ 'iso885915': 'iso8859-15',
+ 'iso885916': 'iso8859-16',
+ 'iso8859162001': 'iso8859-16',
+ 'iso88592': 'iso8859-2',
+ 'iso885921987': 'iso8859-2',
+ 'iso88593': 'iso8859-3',
+ 'iso885931988': 'iso8859-3',
+ 'iso88594': 'iso8859-4',
+ 'iso885941988': 'iso8859-4',
+ 'iso88595': 'iso8859-5',
+ 'iso885951988': 'iso8859-5',
+ 'iso88596': 'iso8859-6',
+ 'iso885961987': 'iso8859-6',
+ 'iso88597': 'iso8859-7',
+ 'iso885971987': 'iso8859-7',
+ 'iso88598': 'iso8859-8',
+ 'iso885981988': 'iso8859-8',
+ 'iso88599': 'windows-1254',
+ 'iso885991989': 'windows-1254',
+ 'isoceltic': 'iso8859-14',
+ 'isoir100': 'windows-1252',
+ 'isoir101': 'iso8859-2',
+ 'isoir109': 'iso8859-3',
+ 'isoir110': 'iso8859-4',
+ 'isoir126': 'iso8859-7',
+ 'isoir127': 'iso8859-6',
+ 'isoir138': 'iso8859-8',
+ 'isoir144': 'iso8859-5',
+ 'isoir148': 'windows-1254',
+ 'isoir149': 'cp949',
+ 'isoir157': 'iso8859-10',
+ 'isoir199': 'iso8859-14',
+ 'isoir226': 'iso8859-16',
+ 'isoir58': 'gbk',
+ 'isoir6': 'ascii',
+ 'koi8r': 'koi8-r',
+ 'koi8u': 'koi8-u',
+ 'korean': 'cp949',
+ 'ksc5601': 'cp949',
+ 'ksc56011987': 'cp949',
+ 'ksc56011989': 'cp949',
+ 'l1': 'windows-1252',
+ 'l10': 'iso8859-16',
+ 'l2': 'iso8859-2',
+ 'l3': 'iso8859-3',
+ 'l4': 'iso8859-4',
+ 'l5': 'windows-1254',
+ 'l6': 'iso8859-10',
+ 'l8': 'iso8859-14',
+ 'latin1': 'windows-1252',
+ 'latin10': 'iso8859-16',
+ 'latin2': 'iso8859-2',
+ 'latin3': 'iso8859-3',
+ 'latin4': 'iso8859-4',
+ 'latin5': 'windows-1254',
+ 'latin6': 'iso8859-10',
+ 'latin8': 'iso8859-14',
+ 'latin9': 'iso8859-15',
+ 'ms936': 'gbk',
+ 'mskanji': 'shift_jis',
+ 'pt154': 'ptcp154',
+ 'ptcp154': 'ptcp154',
+ 'r8': 'hp-roman8',
+ 'roman8': 'hp-roman8',
+ 'shiftjis': 'shift_jis',
+ 'tis620': 'cp874',
+ 'unicode11utf7': 'utf-7',
+ 'us': 'ascii',
+ 'usascii': 'ascii',
+ 'utf16': 'utf-16',
+ 'utf16be': 'utf-16-be',
+ 'utf16le': 'utf-16-le',
+ 'utf8': 'utf-8',
+ 'windows1250': 'cp1250',
+ 'windows1251': 'cp1251',
+ 'windows1252': 'cp1252',
+ 'windows1253': 'cp1253',
+ 'windows1254': 'cp1254',
+ 'windows1255': 'cp1255',
+ 'windows1256': 'cp1256',
+ 'windows1257': 'cp1257',
+ 'windows1258': 'cp1258',
+ 'windows936': 'gbk',
+ 'x-x-big5': 'big5'
+};
diff --git a/pkgs/html/lib/src/css_class_set.dart b/pkgs/html/lib/src/css_class_set.dart
new file mode 100644
index 0000000..deca7a5
--- /dev/null
+++ b/pkgs/html/lib/src/css_class_set.dart
@@ -0,0 +1,216 @@
+// Copyright (c) 2014, 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.
+
+// TODO(jmesserly): everything in this file is copied straight from "dart:html".
+
+import 'dart:collection';
+
+import '../dom.dart';
+
+class ElementCssClassSet extends _CssClassSetImpl {
+ final Element _element;
+
+ ElementCssClassSet(this._element);
+
+ @override
+ Set<String> readClasses() {
+ final s = LinkedHashSet<String>();
+ final classname = _element.className;
+
+ for (var name in classname.split(' ')) {
+ final trimmed = name.trim();
+ if (trimmed.isNotEmpty) {
+ s.add(trimmed);
+ }
+ }
+ return s;
+ }
+
+ @override
+ void writeClasses(Set<String> s) {
+ _element.className = s.join(' ');
+ }
+}
+
+/// A Set that stores the CSS class names for an element.
+abstract class CssClassSet implements Set<String> {
+ /// Adds the class [value] to the element if it is not on it, removes it if it
+ /// is.
+ ///
+ /// If [shouldAdd] is true, then we always add that [value] to the element. If
+ /// [shouldAdd] is false then we always remove [value] from the element.
+ bool toggle(String value, [bool? shouldAdd]);
+
+ /// Returns [:true:] if classes cannot be added or removed from this
+ /// [:CssClassSet:].
+ bool get frozen;
+
+ /// Determine if this element contains the class [value].
+ ///
+ /// This is the Dart equivalent of jQuery's
+ /// [hasClass](http://api.jquery.com/hasClass/).
+ @override
+ bool contains(Object? value);
+
+ /// Add the class [value] to element.
+ ///
+ /// This is the Dart equivalent of jQuery's
+ /// [addClass](http://api.jquery.com/addClass/).
+ ///
+ /// If this corresponds to one element. Returns true if [value] was added to
+ /// the set, otherwise false.
+ ///
+ /// If this corresponds to many elements, null is always returned.
+ @override
+ bool add(String value);
+
+ /// Remove the class [value] from element, and return true on successful
+ /// removal.
+ ///
+ /// This is the Dart equivalent of jQuery's
+ /// [removeClass](http://api.jquery.com/removeClass/).
+ @override
+ bool remove(Object? value);
+
+ /// Add all classes specified in [iterable] to element.
+ ///
+ /// This is the Dart equivalent of jQuery's
+ /// [addClass](http://api.jquery.com/addClass/).
+ @override
+ void addAll(Iterable<String> iterable);
+
+ /// Remove all classes specified in [iterable] from element.
+ ///
+ /// This is the Dart equivalent of jQuery's
+ /// [removeClass](http://api.jquery.com/removeClass/).
+ @override
+ void removeAll(Iterable<Object?> iterable);
+
+ /// Toggles all classes specified in [iterable] on element.
+ ///
+ /// Iterate through [iterable]'s items, and add it if it is not on it, or
+ /// remove it if it is. This is the Dart equivalent of jQuery's
+ /// [toggleClass](http://api.jquery.com/toggleClass/).
+ /// If [shouldAdd] is true, then we always add all the classes in [iterable]
+ /// element. If [shouldAdd] is false then we always remove all the classes in
+ /// [iterable] from the element.
+ void toggleAll(Iterable<String> iterable, [bool? shouldAdd]);
+}
+
+abstract class _CssClassSetImpl extends SetBase<String> implements CssClassSet {
+ @override
+ String toString() {
+ return readClasses().join(' ');
+ }
+
+ /// Adds the class [value] to the element if it is not on it, removes it if it
+ /// is.
+ ///
+ /// If [shouldAdd] is true, then we always add that [value] to the element. If
+ /// [shouldAdd] is false then we always remove [value] from the element.
+ @override
+ bool toggle(String value, [bool? shouldAdd]) {
+ final s = readClasses();
+ var result = false;
+ shouldAdd ??= !s.contains(value);
+ if (shouldAdd) {
+ s.add(value);
+ result = true;
+ } else {
+ s.remove(value);
+ }
+ writeClasses(s);
+ return result;
+ }
+
+ /// Returns [:true:] if classes cannot be added or removed from this
+ /// [:CssClassSet:].
+ @override
+ bool get frozen => false;
+
+ @override
+ Iterator<String> get iterator => readClasses().iterator;
+
+ @override
+ int get length => readClasses().length;
+
+ // interface Set - BEGIN
+ /// Determine if this element contains the class [value].
+ ///
+ /// This is the Dart equivalent of jQuery's
+ /// [hasClass](http://api.jquery.com/hasClass/).
+ @override
+ bool contains(Object? value) => readClasses().contains(value);
+
+ /// Lookup from the Set interface. Not interesting for a String set.
+ @override
+ String? lookup(Object? value) => contains(value) ? value as String? : null;
+
+ @override
+ Set<String> toSet() => readClasses().toSet();
+
+ /// Add the class [value] to element.
+ ///
+ /// This is the Dart equivalent of jQuery's
+ /// [addClass](http://api.jquery.com/addClass/).
+ @override
+ bool add(String value) {
+ // TODO - figure out if we need to do any validation here
+ // or if the browser natively does enough.
+ return _modify((s) => s.add(value));
+ }
+
+ /// Remove the class [value] from element, and return true on successful
+ /// removal.
+ ///
+ /// This is the Dart equivalent of jQuery's
+ /// [removeClass](http://api.jquery.com/removeClass/).
+ @override
+ bool remove(Object? value) {
+ if (value is! String) return false;
+ final s = readClasses();
+ final result = s.remove(value);
+ writeClasses(s);
+ return result;
+ }
+
+ /// Toggles all classes specified in [iterable] on element.
+ ///
+ /// Iterate through [iterable]'s items, and add it if it is not on it, or
+ /// remove it if it is. This is the Dart equivalent of jQuery's
+ /// [toggleClass](http://api.jquery.com/toggleClass/).
+ /// If [shouldAdd] is true, then we always add all the classes in [iterable]
+ /// element. If [shouldAdd] is false then we always remove all the classes in
+ /// [iterable] from the element.
+ @override
+ void toggleAll(Iterable<String> iterable, [bool? shouldAdd]) {
+ for (var e in iterable) {
+ toggle(e, shouldAdd);
+ }
+ }
+
+ /// Helper method used to modify the set of css classes on this element.
+ ///
+ /// f - callback with:
+ /// s - a Set of all the css class name currently on this element.
+ ///
+ /// After f returns, the modified set is written to the
+ /// className property of this element.
+ bool _modify(bool Function(Set<String>) f) {
+ final s = readClasses();
+ final ret = f(s);
+ writeClasses(s);
+ return ret;
+ }
+
+ /// Read the class names from the Element class property,
+ /// and put them into a set (duplicates are discarded).
+ /// This is intended to be overridden by specific implementations.
+ Set<String> readClasses();
+
+ /// Join all the elements of a set into one string and write
+ /// back to the element.
+ /// This is intended to be overridden by specific implementations.
+ void writeClasses(Set<String> s);
+}
diff --git a/pkgs/html/lib/src/encoding_parser.dart b/pkgs/html/lib/src/encoding_parser.dart
new file mode 100644
index 0000000..2ecd5c4
--- /dev/null
+++ b/pkgs/html/lib/src/encoding_parser.dart
@@ -0,0 +1,378 @@
+import 'constants.dart';
+import 'html_input_stream.dart';
+
+/// String-like object with an associated position and various extra methods.
+///
+/// If the position is ever greater than the string length then an exception is
+/// raised.
+class EncodingBytes {
+ final String _bytes;
+ int __position = -1;
+
+ EncodingBytes(this._bytes);
+
+ int get _length => _bytes.length;
+
+ String _next() {
+ final p = __position = __position + 1;
+ if (p >= _length) {
+ throw _EncodingRangeException('No more elements');
+ } else if (p < 0) {
+ throw RangeError(p);
+ }
+ return _bytes[p];
+ }
+
+ String _previous() {
+ var p = __position;
+ if (p >= _length) {
+ throw _EncodingRangeException('No more elements');
+ } else if (p < 0) {
+ throw RangeError(p);
+ }
+ __position = p = p - 1;
+ return _bytes[p];
+ }
+
+ set _position(int value) {
+ if (__position >= _length) {
+ throw _EncodingRangeException('No more elements');
+ }
+ __position = value;
+ }
+
+ int get _position {
+ if (__position >= _length) {
+ throw _EncodingRangeException('No more elements');
+ }
+ if (__position >= 0) {
+ return __position;
+ } else {
+ return 0;
+ }
+ }
+
+ String get _currentByte => _bytes[_position];
+
+ /// Skip past a list of characters. Defaults to skipping [isWhitespace].
+ String? _skipChars([_CharPredicate? skipChars]) {
+ skipChars ??= isWhitespace;
+ var p = _position; // use property for the error-checking
+ while (p < _length) {
+ final c = _bytes[p];
+ if (!skipChars(c)) {
+ __position = p;
+ return c;
+ }
+ p += 1;
+ }
+ __position = p;
+ return null;
+ }
+
+ String? _skipUntil(_CharPredicate untilChars) {
+ var p = _position;
+ while (p < _length) {
+ final c = _bytes[p];
+ if (untilChars(c)) {
+ __position = p;
+ return c;
+ }
+ p += 1;
+ }
+ return null;
+ }
+
+ /// Look for a sequence of bytes at the start of a string. If the bytes
+ /// are found return true and advance the position to the byte after the
+ /// match. Otherwise return false and leave the position alone.
+ bool _matchBytes(String bytes) {
+ final p = _position;
+ if (_bytes.length < p + bytes.length) {
+ return false;
+ }
+ final data = _bytes.substring(p, p + bytes.length);
+ if (data == bytes) {
+ _position += bytes.length;
+ return true;
+ }
+ return false;
+ }
+
+ /// Look for the next sequence of bytes matching a given sequence. If
+ /// a match is found advance the position to the last byte of the match
+ bool _jumpTo(String bytes) {
+ final newPosition = _bytes.indexOf(bytes, _position);
+ if (newPosition >= 0) {
+ __position = newPosition + bytes.length - 1;
+ return true;
+ } else {
+ throw _EncodingRangeException('No more elements');
+ }
+ }
+
+ String _slice(int start, [int? end]) {
+ end ??= _length;
+ if (end < 0) end += _length;
+ return _bytes.substring(start, end);
+ }
+}
+
+typedef _MethodHandler = bool Function();
+
+class _DispatchEntry {
+ final String pattern;
+ final _MethodHandler handler;
+
+ _DispatchEntry(this.pattern, this.handler);
+}
+
+/// Mini parser for detecting character encoding from meta elements.
+class EncodingParser {
+ final EncodingBytes _data;
+ String? _encoding;
+
+ /// [bytes] - the data to work on for encoding detection.
+ EncodingParser(List<int> bytes)
+ // Note: this is intentionally interpreting bytes as codepoints.
+ : _data = EncodingBytes(String.fromCharCodes(bytes).toLowerCase());
+
+ String? getEncoding() {
+ final methodDispatch = [
+ _DispatchEntry('<!--', _handleComment),
+ _DispatchEntry('<meta', _handleMeta),
+ _DispatchEntry('</', _handlePossibleEndTag),
+ _DispatchEntry('<!', _handleOther),
+ _DispatchEntry('<?', _handleOther),
+ _DispatchEntry('<', _handlePossibleStartTag),
+ ];
+
+ try {
+ for (;;) {
+ for (var dispatch in methodDispatch) {
+ if (_data._matchBytes(dispatch.pattern)) {
+ final keepParsing = dispatch.handler();
+ if (keepParsing) break;
+
+ // We found an encoding. Stop.
+ return _encoding;
+ }
+ }
+ _data._position += 1;
+ }
+ } on _EncodingRangeException catch (_) {
+ // Catch this here to match behavior of Python's StopIteration
+ // TODO(jmesserly): refactor to not use exceptions
+ }
+ return _encoding;
+ }
+
+ /// Skip over comments.
+ bool _handleComment() => _data._jumpTo('-->');
+
+ bool _handleMeta() {
+ if (!isWhitespace(_data._currentByte)) {
+ // if we have <meta not followed by a space so just keep going
+ return true;
+ }
+ // We have a valid meta element we want to search for attributes
+ while (true) {
+ // Try to find the next attribute after the current position
+ final attr = _getAttribute();
+ if (attr == null) return true;
+
+ if (attr[0] == 'charset') {
+ final tentativeEncoding = attr[1];
+ final codec = codecName(tentativeEncoding);
+ if (codec != null) {
+ _encoding = codec;
+ return false;
+ }
+ } else if (attr[0] == 'content') {
+ final contentParser = ContentAttrParser(EncodingBytes(attr[1]));
+ final tentativeEncoding = contentParser.parse();
+ final codec = codecName(tentativeEncoding);
+ if (codec != null) {
+ _encoding = codec;
+ return false;
+ }
+ }
+ }
+ }
+
+ bool _handlePossibleStartTag() => _handlePossibleTag(false);
+
+ bool _handlePossibleEndTag() {
+ _data._next();
+ return _handlePossibleTag(true);
+ }
+
+ bool _handlePossibleTag(bool endTag) {
+ if (!isLetter(_data._currentByte)) {
+ //If the next byte is not an ascii letter either ignore this
+ //fragment (possible start tag case) or treat it according to
+ //handleOther
+ if (endTag) {
+ _data._previous();
+ _handleOther();
+ }
+ return true;
+ }
+
+ final c = _data._skipUntil(_isSpaceOrAngleBracket);
+ if (c == '<') {
+ // return to the first step in the overall "two step" algorithm
+ // reprocessing the < byte
+ _data._previous();
+ } else {
+ //Read all attributes
+ var attr = _getAttribute();
+ while (attr != null) {
+ attr = _getAttribute();
+ }
+ }
+ return true;
+ }
+
+ bool _handleOther() => _data._jumpTo('>');
+
+ /// Return a name,value pair for the next attribute in the stream,
+ /// if one is found, or null
+ List<String>? _getAttribute() {
+ // Step 1 (skip chars)
+ var c = _data._skipChars((x) => x == '/' || isWhitespace(x));
+ // Step 2
+ if (c == '>' || c == null) {
+ return null;
+ }
+ // Step 3
+ final attrName = <String>[];
+ final attrValue = <String>[];
+ // Step 4 attribute name
+ while (true) {
+ if (c == null) {
+ return null;
+ } else if (c == '=' && attrName.isNotEmpty) {
+ break;
+ } else if (isWhitespace(c)) {
+ // Step 6!
+ c = _data._skipChars();
+ c = _data._next();
+ break;
+ } else if (c == '/' || c == '>') {
+ return [attrName.join(), ''];
+ } else if (isLetter(c)) {
+ attrName.add(c.toLowerCase());
+ } else {
+ attrName.add(c);
+ }
+ // Step 5
+ c = _data._next();
+ }
+ // Step 7
+ if (c != '=') {
+ _data._previous();
+ return [attrName.join(), ''];
+ }
+ // Step 8
+ _data._next();
+ // Step 9
+ c = _data._skipChars();
+ // Step 10
+ if (c == "'" || c == '"') {
+ // 10.1
+ final quoteChar = c;
+ while (true) {
+ // 10.2
+ c = _data._next();
+ if (c == quoteChar) {
+ // 10.3
+ _data._next();
+ return [attrName.join(), attrValue.join()];
+ } else if (isLetter(c)) {
+ // 10.4
+ attrValue.add(c.toLowerCase());
+ } else {
+ // 10.5
+ attrValue.add(c);
+ }
+ }
+ } else if (c == '>') {
+ return [attrName.join(), ''];
+ } else if (c == null) {
+ return null;
+ } else if (isLetter(c)) {
+ attrValue.add(c.toLowerCase());
+ } else {
+ attrValue.add(c);
+ }
+ // Step 11
+ while (true) {
+ c = _data._next();
+ if (_isSpaceOrAngleBracket(c)) {
+ return [attrName.join(), attrValue.join()];
+ } else if (isLetter(c)) {
+ attrValue.add(c.toLowerCase());
+ } else {
+ attrValue.add(c);
+ }
+ }
+ }
+}
+
+class ContentAttrParser {
+ final EncodingBytes data;
+
+ ContentAttrParser(this.data);
+
+ String? parse() {
+ try {
+ // Check if the attr name is charset
+ // otherwise return
+ data._jumpTo('charset');
+ data._position += 1;
+ data._skipChars();
+ if (data._currentByte != '=') {
+ // If there is no = sign keep looking for attrs
+ return null;
+ }
+ data._position += 1;
+ data._skipChars();
+ // Look for an encoding between matching quote marks
+ if (data._currentByte == '"' || data._currentByte == "'") {
+ final quoteMark = data._currentByte;
+ data._position += 1;
+ final oldPosition = data._position;
+ if (data._jumpTo(quoteMark)) {
+ return data._slice(oldPosition, data._position);
+ } else {
+ return null;
+ }
+ } else {
+ // Unquoted value
+ final oldPosition = data._position;
+ try {
+ data._skipUntil(isWhitespace);
+ return data._slice(oldPosition, data._position);
+ } on _EncodingRangeException catch (_) {
+ //Return the whole remaining value
+ return data._slice(oldPosition);
+ }
+ }
+ } on _EncodingRangeException catch (_) {
+ return null;
+ }
+ }
+}
+
+bool _isSpaceOrAngleBracket(String char) {
+ return char == '>' || char == '<' || isWhitespace(char);
+}
+
+typedef _CharPredicate = bool Function(String char);
+
+class _EncodingRangeException implements Exception {
+ final String message;
+
+ _EncodingRangeException(this.message);
+}
diff --git a/pkgs/html/lib/src/html_input_stream.dart b/pkgs/html/lib/src/html_input_stream.dart
new file mode 100644
index 0000000..b093d3c
--- /dev/null
+++ b/pkgs/html/lib/src/html_input_stream.dart
@@ -0,0 +1,340 @@
+import 'dart:collection';
+import 'dart:convert' show ascii, utf8;
+
+import 'package:source_span/source_span.dart';
+
+import 'constants.dart';
+import 'encoding_parser.dart';
+import 'utils.dart';
+
+/// Provides a unicode stream of characters to the HtmlTokenizer.
+///
+/// This class takes care of character encoding and removing or replacing
+/// incorrect byte-sequences and also provides column and line tracking.
+class HtmlInputStream {
+ /// Number of bytes to use when looking for a meta element with
+ /// encoding information.
+ static const int numBytesMeta = 512;
+
+ /// Encoding to use if no other information can be found.
+ static const String defaultEncoding = 'utf-8';
+
+ /// The name of the character encoding.
+ String? charEncodingName;
+
+ /// True if we are certain about [charEncodingName], false for tentative.
+ bool charEncodingCertain = true;
+
+ final bool generateSpans;
+
+ /// Location where the contents of the stream were found.
+ final String? sourceUrl;
+
+ List<int>? _rawBytes;
+
+ /// Raw UTF-16 codes, used if a Dart String is passed in.
+ List<int>? _rawChars;
+
+ Queue<String> errors = Queue<String>();
+
+ SourceFile? fileInfo;
+
+ var _chars = <int>[];
+
+ var _offset = 0;
+
+ /// Initialise an HtmlInputStream.
+ ///
+ /// HtmlInputStream(source, [encoding]) -> Normalized stream from source
+ /// for use by html5lib.
+ ///
+ /// [source] can be either a `String` or a `List<int>` containing the raw
+ /// bytes.
+ ///
+ /// The optional encoding parameter must be a string that indicates
+ /// the encoding. If specified, that encoding will be used,
+ /// regardless of any BOM or later declaration (such as in a meta
+ /// element)
+ ///
+ /// [parseMeta] - Look for a <meta> element containing encoding information
+ HtmlInputStream(dynamic source,
+ [String? encoding,
+ bool parseMeta = true,
+ this.generateSpans = false,
+ this.sourceUrl])
+ : charEncodingName = codecName(encoding) {
+ if (source is String) {
+ _rawChars = source.codeUnits;
+ charEncodingName = 'utf-8';
+ charEncodingCertain = true;
+ } else if (source is List<int>) {
+ _rawBytes = source;
+ } else {
+ throw ArgumentError.value(
+ source, 'source', 'Must be a String or List<int>.');
+ }
+
+ // Detect encoding iff no explicit "transport level" encoding is supplied
+ if (charEncodingName == null) {
+ detectEncoding(parseMeta);
+ }
+
+ reset();
+ }
+
+ void reset() {
+ errors = Queue<String>();
+
+ _offset = 0;
+ _chars = <int>[];
+
+ final rawChars = _rawChars ??= _decodeBytes(charEncodingName!, _rawBytes!);
+
+ var skipNewline = false;
+ var wasSurrogatePair = false;
+ for (var i = 0; i < rawChars.length; i++) {
+ var c = rawChars[i];
+ if (skipNewline) {
+ skipNewline = false;
+ if (c == newLine) continue;
+ }
+
+ final isSurrogatePair = _isSurrogatePair(rawChars, i);
+ if (!isSurrogatePair && !wasSurrogatePair) {
+ if (_invalidUnicode(c)) {
+ errors.add('invalid-codepoint');
+
+ if (0xD800 <= c && c <= 0xDFFF) {
+ c = 0xFFFD;
+ }
+ }
+ }
+ wasSurrogatePair = isSurrogatePair;
+
+ if (c == returnCode) {
+ skipNewline = true;
+ c = newLine;
+ }
+
+ _chars.add(c);
+ }
+
+ // 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);
+ }
+
+ void detectEncoding([bool parseMeta = true]) {
+ // First look for a BOM
+ // This will also read past the BOM if present
+ charEncodingName = detectBOM();
+ charEncodingCertain = true;
+
+ // If there is no BOM need to look for meta elements with encoding
+ // information
+ if (charEncodingName == null && parseMeta) {
+ charEncodingName = detectEncodingMeta();
+ charEncodingCertain = false;
+ }
+ // If all else fails use the default encoding
+ if (charEncodingName == null) {
+ charEncodingCertain = false;
+ charEncodingName = defaultEncoding;
+ }
+
+ // Substitute for equivalent encodings:
+ if (charEncodingName!.toLowerCase() == 'iso-8859-1') {
+ charEncodingName = 'windows-1252';
+ }
+ }
+
+ void changeEncoding(String? newEncoding) {
+ if (_rawBytes == null) {
+ // We should never get here -- if encoding is certain we won't try to
+ // change it.
+ throw StateError('cannot change encoding when parsing a String.');
+ }
+
+ newEncoding = codecName(newEncoding);
+ if (const ['utf-16', 'utf-16-be', 'utf-16-le'].contains(newEncoding)) {
+ newEncoding = 'utf-8';
+ }
+ if (newEncoding == null) {
+ return;
+ } else if (newEncoding == charEncodingName) {
+ charEncodingCertain = true;
+ } else {
+ charEncodingName = newEncoding;
+ charEncodingCertain = true;
+ _rawChars = null;
+ reset();
+ throw ReparseException(
+ 'Encoding changed from $charEncodingName to $newEncoding');
+ }
+ }
+
+ /// Attempts to detect at BOM at the start of the stream. If
+ /// an encoding can be determined from the BOM return the name of the
+ /// encoding otherwise return null.
+ String? detectBOM() {
+ // Try detecting the BOM using bytes from the string
+ if (_hasUtf8Bom(_rawBytes!)) {
+ return 'utf-8';
+ }
+ return null;
+ }
+
+ /// Report the encoding declared by the meta element.
+ String? detectEncodingMeta() {
+ final parser = EncodingParser(slice(_rawBytes!, 0, numBytesMeta));
+ var encoding = parser.getEncoding();
+
+ if (const ['utf-16', 'utf-16-be', 'utf-16-le'].contains(encoding)) {
+ encoding = 'utf-8';
+ }
+
+ return encoding;
+ }
+
+ /// Returns the current offset in the stream, i.e. the number of codepoints
+ /// since the start of the file.
+ int get position => _offset;
+
+ /// Read one character from the stream or queue if available. Return
+ /// 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++]);
+ }
+
+ String? peekChar() {
+ if (_offset >= _chars.length) return eof;
+ return _isSurrogatePair(_chars, _offset)
+ ? String.fromCharCodes([_chars[_offset], _chars[_offset + 1]])
+ : String.fromCharCode(_chars[_offset]);
+ }
+
+ // Whether the current and next chars indicate a surrogate pair.
+ bool _isSurrogatePair(List<int> chars, int i) {
+ return i + 1 < chars.length &&
+ _isLeadSurrogate(chars[i]) &&
+ _isTrailSurrogate(chars[i + 1]);
+ }
+
+ // Is then code (a 16-bit unsigned integer) a UTF-16 lead surrogate.
+ bool _isLeadSurrogate(int code) => (code & 0xFC00) == 0xD800;
+
+ // Is then code (a 16-bit unsigned integer) a UTF-16 trail surrogate.
+ 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]) {
+ final start = _offset;
+ String? c;
+ while ((c = peekChar()) != null && characters.contains(c!) == opposite) {
+ _offset += c.codeUnits.length;
+ }
+
+ return String.fromCharCodes(_chars.sublist(start, _offset));
+ }
+
+ void unget(String? ch) {
+ // Only one character is allowed to be ungotten at once - it must
+ // be consumed again before any further call to unget
+ if (ch != null) {
+ _offset -= ch.length;
+ assert(peekChar() == ch);
+ }
+ }
+}
+
+// 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) {
+ if (0x0001 <= c && c <= 0x0008) return true;
+ if (0x000E <= c && c <= 0x001F) return true;
+ if (0x007F <= c && c <= 0x009F) return true;
+ if (0xD800 <= c && c <= 0xDFFF) return true;
+ if (0xFDD0 <= c && c <= 0xFDEF) return true;
+ switch (c) {
+ case 0x000B:
+ case 0xFFFE:
+ case 0xFFFF:
+ case 0x01FFFE:
+ case 0x01FFFF:
+ case 0x02FFFE:
+ case 0x02FFFF:
+ case 0x03FFFE:
+ case 0x03FFFF:
+ case 0x04FFFE:
+ case 0x04FFFF:
+ case 0x05FFFE:
+ case 0x05FFFF:
+ case 0x06FFFE:
+ case 0x06FFFF:
+ case 0x07FFFE:
+ case 0x07FFFF:
+ case 0x08FFFE:
+ case 0x08FFFF:
+ case 0x09FFFE:
+ case 0x09FFFF:
+ case 0x0AFFFE:
+ case 0x0AFFFF:
+ case 0x0BFFFE:
+ case 0x0BFFFF:
+ case 0x0CFFFE:
+ case 0x0CFFFF:
+ case 0x0DFFFE:
+ case 0x0DFFFF:
+ case 0x0EFFFE:
+ case 0x0EFFFF:
+ case 0x0FFFFE:
+ case 0x0FFFFF:
+ case 0x10FFFE:
+ case 0x10FFFF:
+ return true;
+ }
+ return false;
+}
+
+/// Return the python codec name corresponding to an encoding or null if the
+/// string doesn't correspond to a valid encoding.
+String? codecName(String? encoding) {
+ final asciiPunctuation = RegExp(
+ '[\u0009-\u000D\u0020-\u002F\u003A-\u0040\u005B-\u0060\u007B-\u007E]');
+
+ if (encoding == null) return null;
+ final canonicalName = encoding.replaceAll(asciiPunctuation, '').toLowerCase();
+ return encodings[canonicalName];
+}
+
+/// Returns true if the [bytes] starts with a UTF-8 byte order mark.
+/// Since UTF-8 doesn't have byte order, it's somewhat of a misnomer, but it is
+/// used in HTML to detect the UTF-
+bool _hasUtf8Bom(List<int> bytes, [int offset = 0, int? length]) {
+ final end = length != null ? offset + length : bytes.length;
+ return (offset + 3) <= end &&
+ bytes[offset] == 0xEF &&
+ bytes[offset + 1] == 0xBB &&
+ bytes[offset + 2] == 0xBF;
+}
+
+/// Decodes the [bytes] with the provided [encoding] and returns a list for
+/// the codepoints. Supports the major unicode encodings as well as ascii and
+/// and windows-1252 encodings.
+List<int> _decodeBytes(String encoding, List<int> bytes) {
+ return switch (encoding) {
+ 'ascii' => ascii.decode(bytes).codeUnits,
+ 'utf-8' =>
+ // NOTE: To match the behavior of the other decode functions, we eat the
+ // UTF-8 BOM here. This is the default behavior of `utf8.decode`.
+ utf8.decode(bytes).codeUnits,
+ _ => throw ArgumentError('Encoding $encoding not supported')
+ };
+}
diff --git a/pkgs/html/lib/src/list_proxy.dart b/pkgs/html/lib/src/list_proxy.dart
new file mode 100644
index 0000000..f226d7f
--- /dev/null
+++ b/pkgs/html/lib/src/list_proxy.dart
@@ -0,0 +1,59 @@
+/// A [List] proxy that you can subclass.
+library;
+
+import 'dart:collection';
+
+abstract class ListProxy<E> extends ListBase<E> {
+ /// The proxied list with actual storage.
+ final List<E> _list = <E>[];
+
+ @override
+ bool remove(Object? element) => _list.remove(element);
+
+ @override
+ int get length => _list.length;
+
+ // From Iterable
+ @override
+ Iterator<E> get iterator => _list.iterator;
+
+ // From List
+ @override
+ E operator [](int index) => _list[index];
+
+ @override
+ void operator []=(int index, E value) {
+ _list[index] = value;
+ }
+
+ @override
+ set length(int value) {
+ _list.length = value;
+ }
+
+ @override
+ void add(E element) {
+ _list.add(element);
+ }
+
+ @override
+ void insert(int index, E element) => _list.insert(index, element);
+
+ @override
+ void addAll(Iterable<E> iterable) {
+ _list.addAll(iterable);
+ }
+
+ @override
+ void insertAll(int index, Iterable<E> iterable) {
+ _list.insertAll(index, iterable);
+ }
+
+ @override
+ E removeAt(int index) => _list.removeAt(index);
+
+ @override
+ void removeRange(int start, int end) {
+ _list.removeRange(start, end);
+ }
+}
diff --git a/pkgs/html/lib/src/query_selector.dart b/pkgs/html/lib/src/query_selector.dart
new file mode 100644
index 0000000..b57b811
--- /dev/null
+++ b/pkgs/html/lib/src/query_selector.dart
@@ -0,0 +1,300 @@
+/// Query selector implementation for our DOM.
+library;
+
+import 'package:csslib/parser.dart';
+import 'package:csslib/visitor.dart';
+
+import '../dom.dart';
+import 'constants.dart' show isWhitespaceCC;
+
+bool matches(Element node, String selector) =>
+ SelectorEvaluator().matches(node, _parseSelectorList(selector));
+
+Element? querySelector(Node node, String selector) =>
+ SelectorEvaluator().querySelector(node, _parseSelectorList(selector));
+
+List<Element> querySelectorAll(Node node, String selector) {
+ final results = <Element>[];
+ SelectorEvaluator()
+ .querySelectorAll(node, _parseSelectorList(selector), results);
+ return results;
+}
+
+// http://dev.w3.org/csswg/selectors-4/#grouping
+SelectorGroup _parseSelectorList(String selector) {
+ final errors = <Message>[];
+ final group = parseSelectorGroup(selector, errors: errors);
+ if (group == null || errors.isNotEmpty) {
+ throw FormatException("'$selector' is not a valid selector: $errors");
+ }
+ return group;
+}
+
+class SelectorEvaluator extends Visitor {
+ /// The current HTML element to match against.
+ Element? _element;
+
+ bool matches(Element element, SelectorGroup selector) {
+ _element = element;
+ return visitSelectorGroup(selector);
+ }
+
+ Element? querySelector(Node root, SelectorGroup selector) {
+ for (var element in root.nodes.whereType<Element>()) {
+ if (matches(element, selector)) return element;
+ final result = querySelector(element, selector);
+ if (result != null) return result;
+ }
+ return null;
+ }
+
+ void querySelectorAll(
+ Node root, SelectorGroup selector, List<Element> results) {
+ for (var element in root.nodes.whereType<Element>()) {
+ if (matches(element, selector)) results.add(element);
+ querySelectorAll(element, selector, results);
+ }
+ }
+
+ @override
+ bool visitSelectorGroup(SelectorGroup node) =>
+ node.selectors.any(visitSelector);
+
+ @override
+ bool visitSelector(Selector node) {
+ final old = _element;
+ var result = true;
+
+ // Note: evaluate selectors right-to-left as it's more efficient.
+ int? combinator;
+ for (var s in node.simpleSelectorSequences.reversed) {
+ if (combinator == null) {
+ result = s.simpleSelector.visit(this) as bool;
+ } else {
+ if (combinator == TokenKind.COMBINATOR_DESCENDANT) {
+ // descendant combinator
+ // http://dev.w3.org/csswg/selectors-4/#descendant-combinators
+ do {
+ _element = _element!.parent;
+ } while (_element != null && !(s.simpleSelector.visit(this) as bool));
+
+ if (_element == null) result = false;
+ } else if (combinator == TokenKind.COMBINATOR_TILDE) {
+ // Following-sibling combinator
+ // http://dev.w3.org/csswg/selectors-4/#general-sibling-combinators
+ do {
+ _element = _element!.previousElementSibling;
+ } while (_element != null && !(s.simpleSelector.visit(this) as bool));
+
+ if (_element == null) result = false;
+ }
+ combinator = null;
+ }
+
+ if (!result) break;
+
+ switch (s.combinator) {
+ case TokenKind.COMBINATOR_PLUS:
+ // Next-sibling combinator
+ // http://dev.w3.org/csswg/selectors-4/#adjacent-sibling-combinators
+ _element = _element!.previousElementSibling;
+ break;
+ case TokenKind.COMBINATOR_GREATER:
+ // Child combinator
+ // http://dev.w3.org/csswg/selectors-4/#child-combinators
+ _element = _element!.parent;
+ break;
+ case TokenKind.COMBINATOR_DESCENDANT:
+ case TokenKind.COMBINATOR_TILDE:
+ // We need to iterate through all siblings or parents.
+ // For now, just remember what the combinator was.
+ combinator = s.combinator;
+ break;
+ case TokenKind.COMBINATOR_NONE:
+ break;
+ default:
+ throw _unsupported(node);
+ }
+
+ if (_element == null) {
+ result = false;
+ break;
+ }
+ }
+
+ _element = old;
+ return result;
+ }
+
+ UnimplementedError _unimplemented(SimpleSelector selector) =>
+ UnimplementedError("'$selector' selector of type "
+ '${selector.runtimeType} is not implemented');
+
+ FormatException _unsupported(TreeNode selector) =>
+ FormatException("'$selector' is not a valid selector");
+
+ @override
+ bool visitPseudoClassSelector(PseudoClassSelector node) {
+ switch (node.name) {
+ // http://dev.w3.org/csswg/selectors-4/#structural-pseudos
+
+ // http://dev.w3.org/csswg/selectors-4/#the-root-pseudo
+ case 'root':
+ // TODO(jmesserly): fix when we have a .ownerDocument pointer
+ // return _element == _element.ownerDocument.rootElement;
+ return _element!.localName == 'html' && _element!.parentNode == null;
+
+ // http://dev.w3.org/csswg/selectors-4/#the-empty-pseudo
+ case 'empty':
+ return _element!.nodes
+ .any((n) => !(n is Element || n is Text && n.text.isNotEmpty));
+
+ // http://dev.w3.org/csswg/selectors-4/#the-blank-pseudo
+ case 'blank':
+ return _element!.nodes.any((n) => !(n is Element ||
+ n is Text && n.text.runes.any((r) => !isWhitespaceCC(r))));
+
+ // http://dev.w3.org/csswg/selectors-4/#the-first-child-pseudo
+ case 'first-child':
+ return _element!.previousElementSibling == null;
+
+ // http://dev.w3.org/csswg/selectors-4/#the-last-child-pseudo
+ case 'last-child':
+ return _element!.nextElementSibling == null;
+
+ // http://dev.w3.org/csswg/selectors-4/#the-only-child-pseudo
+ case 'only-child':
+ return _element!.previousElementSibling == null &&
+ _element!.nextElementSibling == null;
+
+ // http://dev.w3.org/csswg/selectors-4/#link
+ case 'link':
+ return _element!.attributes['href'] != null;
+
+ case 'visited':
+ // Always return false since we aren't a browser. This is allowed per:
+ // http://dev.w3.org/csswg/selectors-4/#visited-pseudo
+ return false;
+ }
+
+ // :before, :after, :first-letter/line can't match DOM elements.
+ if (_isLegacyPsuedoClass(node.name)) return false;
+
+ throw _unimplemented(node);
+ }
+
+ @override
+ bool visitPseudoElementSelector(PseudoElementSelector node) {
+ // :before, :after, :first-letter/line can't match DOM elements.
+ if (_isLegacyPsuedoClass(node.name)) return false;
+
+ throw _unimplemented(node);
+ }
+
+ static bool _isLegacyPsuedoClass(String name) {
+ switch (name) {
+ case 'before':
+ case 'after':
+ case 'first-line':
+ case 'first-letter':
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @override
+ bool visitPseudoElementFunctionSelector(PseudoElementFunctionSelector node) =>
+ throw _unimplemented(node);
+
+ @override
+ bool visitPseudoClassFunctionSelector(PseudoClassFunctionSelector node) {
+ switch (node.name) {
+ // http://dev.w3.org/csswg/selectors-4/#child-index
+
+ // http://dev.w3.org/csswg/selectors-4/#the-nth-child-pseudo
+ case 'nth-child':
+ // TODO(jmesserly): support An+B syntax too.
+ final exprs = node.expression.expressions;
+ if (exprs.length == 1 && exprs[0] is LiteralTerm) {
+ final literal = exprs[0] as LiteralTerm;
+ final parent = _element!.parentNode;
+ return parent != null &&
+ (literal.value as num) > 0 &&
+ parent.nodes.indexOf(_element) == literal.value;
+ }
+ break;
+
+ // http://dev.w3.org/csswg/selectors-4/#the-lang-pseudo
+ case 'lang':
+ // TODO(jmesserly): shouldn't need to get the raw text here, but csslib
+ // gets confused by the "-" in the expression, such as in "es-AR".
+ final toMatch = node.expression.span.text;
+ final lang = _getInheritedLanguage(_element);
+ // TODO(jmesserly): implement wildcards in level 4
+ return lang != null && lang.startsWith(toMatch);
+ }
+ throw _unimplemented(node);
+ }
+
+ static String? _getInheritedLanguage(Node? node) {
+ while (node != null) {
+ final lang = node.attributes['lang'];
+ if (lang != null) return lang;
+ node = node.parent;
+ }
+ return null;
+ }
+
+ @override
+ bool visitNamespaceSelector(NamespaceSelector node) {
+ // Match element tag name
+ if (!(node.nameAsSimpleSelector!.visit(this) as bool)) return false;
+
+ if (node.isNamespaceWildcard) return true;
+
+ if (node.namespace == '') return _element!.namespaceUri == null;
+
+ throw _unimplemented(node);
+ }
+
+ @override
+ bool visitElementSelector(ElementSelector node) =>
+ node.isWildcard || _element!.localName == node.name.toLowerCase();
+
+ @override
+ bool visitIdSelector(IdSelector node) => _element!.id == node.name;
+
+ @override
+ bool visitClassSelector(ClassSelector node) =>
+ _element!.classes.contains(node.name);
+
+ // TODO(jmesserly): negation should support any selectors in level 4,
+ // not just simple selectors.
+ // http://dev.w3.org/csswg/selectors-4/#negation
+ @override
+ bool visitNegationSelector(NegationSelector node) =>
+ !(node.negationArg!.visit(this) as bool);
+
+ @override
+ bool visitAttributeSelector(AttributeSelector node) {
+ // Match name first
+ final value = _element!.attributes[node.name.toLowerCase()];
+ if (value == null) return false;
+
+ if (node.operatorKind == TokenKind.NO_MATCH) return true;
+
+ final select = '${node.value}';
+ return switch (node.operatorKind) {
+ TokenKind.EQUALS => value == select,
+ TokenKind.INCLUDES =>
+ value.split(' ').any((v) => v.isNotEmpty && v == select),
+ TokenKind.DASH_MATCH => value.startsWith(select) &&
+ (value.length == select.length || value[select.length] == '-'),
+ TokenKind.PREFIX_MATCH => value.startsWith(select),
+ TokenKind.SUFFIX_MATCH => value.endsWith(select),
+ TokenKind.SUBSTRING_MATCH => value.contains(select),
+ _ => throw _unsupported(node)
+ };
+ }
+}
diff --git a/pkgs/html/lib/src/token.dart b/pkgs/html/lib/src/token.dart
new file mode 100644
index 0000000..1ade790
--- /dev/null
+++ b/pkgs/html/lib/src/token.dart
@@ -0,0 +1,151 @@
+/// This library contains token types used by the html5 tokenizer.
+library;
+
+import 'dart:collection';
+
+import 'package:source_span/source_span.dart';
+
+/// An html5 token.
+abstract class Token {
+ FileSpan? span;
+
+ int get kind;
+}
+
+abstract class TagToken extends Token {
+ String? name;
+
+ bool selfClosing;
+
+ TagToken(this.name, this.selfClosing);
+}
+
+class StartTagToken extends TagToken {
+ /// The tag's attributes. A map from the name to the value, where the name
+ /// can be a [String] or `AttributeName`.
+ LinkedHashMap<Object, String> data;
+
+ /// The attribute spans if requested. Otherwise null.
+ List<TagAttribute>? attributeSpans;
+
+ bool selfClosingAcknowledged;
+
+ /// The namespace. This is filled in later during tree building.
+ String? namespace;
+
+ StartTagToken(String? name,
+ {LinkedHashMap<Object, String>? data,
+ bool selfClosing = false,
+ this.selfClosingAcknowledged = false,
+ this.namespace})
+ : data = data ?? LinkedHashMap(),
+ super(name, selfClosing);
+
+ @override
+ int get kind => TokenKind.startTag;
+}
+
+class EndTagToken extends TagToken {
+ EndTagToken(String? name, {bool selfClosing = false})
+ : super(name, selfClosing);
+
+ @override
+ int get kind => TokenKind.endTag;
+}
+
+abstract class StringToken extends Token {
+ StringBuffer? _buffer;
+
+ String? _string;
+ String get data {
+ if (_string == null) {
+ _string = _buffer.toString();
+ _buffer = null;
+ }
+ return _string!;
+ }
+
+ StringToken(this._string) : _buffer = _string == null ? StringBuffer() : null;
+
+ StringToken add(String data) {
+ _buffer!.write(data);
+ return this;
+ }
+}
+
+class ParseErrorToken extends StringToken {
+ /// Extra information that goes along with the error message.
+ Map<String, Object?>? messageParams;
+
+ ParseErrorToken(String super.data, {this.messageParams});
+
+ @override
+ int get kind => TokenKind.parseError;
+}
+
+class CharactersToken extends StringToken {
+ CharactersToken([super.data]);
+
+ @override
+ int get kind => TokenKind.characters;
+
+ /// Replaces the token's [data]. This should only be used to wholly replace
+ /// data, not to append data.
+ void replaceData(String newData) {
+ _string = newData;
+ _buffer = null;
+ }
+}
+
+class SpaceCharactersToken extends StringToken {
+ SpaceCharactersToken([super.data]);
+
+ @override
+ int get kind => TokenKind.spaceCharacters;
+}
+
+class CommentToken extends StringToken {
+ CommentToken([super.data]);
+
+ @override
+ int get kind => TokenKind.comment;
+}
+
+class DoctypeToken extends Token {
+ String? publicId;
+ String? systemId;
+ String? name = '';
+ bool correct;
+
+ DoctypeToken({this.publicId, this.systemId, this.correct = false});
+
+ @override
+ int get kind => TokenKind.doctype;
+}
+
+/// These are used by the tokenizer to build up the attribute map.
+/// They're also used by [StartTagToken.attributeSpans] if attribute spans are
+/// requested.
+class TagAttribute {
+ String? name;
+ late String value;
+
+ // The spans of the attribute. This is not used unless we are computing an
+ // attribute span on demand.
+ late int start;
+ late int end;
+ int? startValue;
+ late int endValue;
+
+ TagAttribute();
+}
+
+class TokenKind {
+ static const int spaceCharacters = 0;
+ static const int characters = 1;
+ static const int startTag = 2;
+ static const int endTag = 3;
+ static const int comment = 4;
+ static const int doctype = 5;
+ static const int parseError = 6;
+}
diff --git a/pkgs/html/lib/src/tokenizer.dart b/pkgs/html/lib/src/tokenizer.dart
new file mode 100644
index 0000000..3bed0b0
--- /dev/null
+++ b/pkgs/html/lib/src/tokenizer.dart
@@ -0,0 +1,1907 @@
+import 'dart:collection';
+
+import '../parser.dart' show HtmlParser;
+
+import 'constants.dart';
+import 'html_input_stream.dart';
+import 'token.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<String, List<String>> entitiesByFirstChar = (() {
+ final result = <String, List<String>>{};
+ 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
+
+/// This class takes care of tokenizing HTML.
+class HtmlTokenizer implements Iterator<Token> {
+ // TODO(jmesserly): a lot of these could be made private
+
+ final HtmlInputStream stream;
+
+ final bool lowercaseElementName;
+
+ final bool lowercaseAttrName;
+
+ /// True to generate spans in for [Token.span].
+ final bool generateSpans;
+
+ /// True to generate spans for attributes.
+ final bool attributeSpans;
+
+ /// This reference to the parser is used for correct CDATA handling.
+ /// The [HtmlParser] will set this at construction time.
+ HtmlParser? parser;
+
+ final Queue<Token?> tokenQueue;
+
+ /// Holds the token that is currently being processed.
+ Token? currentToken;
+
+ /// Holds a reference to the method to be invoked for the next parser state.
+ late bool Function() state;
+
+ final StringBuffer _buffer = StringBuffer();
+
+ late int _lastOffset;
+
+ // TODO(jmesserly): ideally this would be a LinkedHashMap and we wouldn't add
+ // an item until it's ready. But the code doesn't have a clear notion of when
+ // it's "done" with the attribute.
+ List<TagAttribute>? _attributes;
+ Set<String>? _attributeNames;
+
+ HtmlTokenizer(dynamic doc,
+ {String? encoding,
+ bool parseMeta = true,
+ this.lowercaseElementName = true,
+ this.lowercaseAttrName = true,
+ this.generateSpans = false,
+ String? sourceUrl,
+ this.attributeSpans = false})
+ : stream =
+ HtmlInputStream(doc, encoding, parseMeta, generateSpans, sourceUrl),
+ tokenQueue = Queue() {
+ reset();
+ }
+
+ TagToken get currentTagToken => currentToken as TagToken;
+ DoctypeToken get currentDoctypeToken => currentToken as DoctypeToken;
+ StringToken get currentStringToken => currentToken as StringToken;
+
+ Token? _current;
+ @override
+ Token get current => _current!;
+
+ final StringBuffer _attributeName = StringBuffer();
+ final StringBuffer _attributeValue = StringBuffer();
+
+ void _markAttributeEnd(int offset) {
+ _attributes!.last.value = '$_attributeValue';
+ if (attributeSpans) _attributes!.last.end = stream.position + offset;
+ }
+
+ void _markAttributeValueStart(int offset) {
+ if (attributeSpans) _attributes!.last.startValue = stream.position + offset;
+ }
+
+ void _markAttributeValueEnd(int offset) {
+ if (attributeSpans) _attributes!.last.endValue = stream.position + offset;
+ _markAttributeEnd(offset);
+ }
+
+ // Note: we could track the name span here, if we need it.
+ void _markAttributeNameEnd(int offset) => _markAttributeEnd(offset);
+
+ void _addAttribute(String name) {
+ _attributes ??= [];
+ _attributeName.clear();
+ _attributeName.write(name);
+ _attributeValue.clear();
+ final attr = TagAttribute();
+ _attributes!.add(attr);
+ if (attributeSpans) attr.start = stream.position - name.length;
+ }
+
+ /// This is where the magic happens.
+ ///
+ /// We do our usually processing through the states and when we have a token
+ /// to return we yield the token which pauses processing until the next token
+ /// is requested.
+ @override
+ bool moveNext() {
+ // Start processing. When EOF is reached state will return false;
+ // instead of true and the loop will terminate.
+ while (stream.errors.isEmpty && tokenQueue.isEmpty) {
+ if (!state()) {
+ _current = null;
+ return false;
+ }
+ }
+ if (stream.errors.isNotEmpty) {
+ _current = ParseErrorToken(stream.errors.removeFirst());
+ } else {
+ assert(tokenQueue.isNotEmpty);
+ _current = tokenQueue.removeFirst();
+ }
+ return true;
+ }
+
+ /// Resets the tokenizer state. Calling this does not reset the [stream] or
+ /// the [parser].
+ void reset() {
+ _lastOffset = 0;
+ tokenQueue.clear();
+ currentToken = null;
+ _buffer.clear();
+ _attributes = null;
+ _attributeNames = null;
+ state = dataState;
+ }
+
+ /// Adds a token to the queue. Sets the span if needed.
+ void _addToken(Token token) {
+ if (generateSpans && token.span == null) {
+ final offset = stream.position;
+ token.span = stream.fileInfo!.span(_lastOffset, offset);
+ if (token is! ParseErrorToken) {
+ _lastOffset = offset;
+ }
+ }
+ tokenQueue.add(token);
+ }
+
+ /// This function returns either U+FFFD or the character based on the
+ /// decimal or hexadecimal representation. It also discards ";" if present.
+ /// If not present it will add a [ParseErrorToken].
+ String consumeNumberEntity(bool isHex) {
+ var allowed = isDigit;
+ var radix = 10;
+ if (isHex) {
+ allowed = isHexDigit;
+ radix = 16;
+ }
+
+ final charStack = <String?>[];
+
+ // Consume all the characters that are in range while making sure we
+ // don't hit an EOF.
+ var c = stream.char();
+ while (allowed(c) && c != eof) {
+ charStack.add(c);
+ c = stream.char();
+ }
+
+ // Convert the set of characters consumed to an int.
+ final charAsInt = int.parse(charStack.join(), radix: radix);
+
+ // Certain characters get replaced with others
+ var char = replacementCharacters[charAsInt];
+ if (char != null) {
+ _addToken(ParseErrorToken('illegal-codepoint-for-numeric-entity',
+ messageParams: {'charAsInt': charAsInt}));
+ } else if ((0xD800 <= charAsInt && charAsInt <= 0xDFFF) ||
+ (charAsInt > 0x10FFFF)) {
+ char = '\uFFFD';
+ _addToken(ParseErrorToken('illegal-codepoint-for-numeric-entity',
+ messageParams: {'charAsInt': charAsInt}));
+ } else {
+ // Should speed up this check somehow (e.g. move the set to a constant)
+ if ((0x0001 <= charAsInt && charAsInt <= 0x0008) ||
+ (0x000E <= charAsInt && charAsInt <= 0x001F) ||
+ (0x007F <= charAsInt && charAsInt <= 0x009F) ||
+ (0xFDD0 <= charAsInt && charAsInt <= 0xFDEF) ||
+ const [
+ 0x000B,
+ 0xFFFE,
+ 0xFFFF,
+ 0x1FFFE,
+ 0x1FFFF,
+ 0x2FFFE,
+ 0x2FFFF,
+ 0x3FFFE,
+ 0x3FFFF,
+ 0x4FFFE,
+ 0x4FFFF,
+ 0x5FFFE,
+ 0x5FFFF,
+ 0x6FFFE,
+ 0x6FFFF,
+ 0x7FFFE,
+ 0x7FFFF,
+ 0x8FFFE,
+ 0x8FFFF,
+ 0x9FFFE,
+ 0x9FFFF,
+ 0xAFFFE,
+ 0xAFFFF,
+ 0xBFFFE,
+ 0xBFFFF,
+ 0xCFFFE,
+ 0xCFFFF,
+ 0xDFFFE,
+ 0xDFFFF,
+ 0xEFFFE,
+ 0xEFFFF,
+ 0xFFFFE,
+ 0xFFFFF,
+ 0x10FFFE,
+ 0x10FFFF
+ ].contains(charAsInt)) {
+ _addToken(ParseErrorToken('illegal-codepoint-for-numeric-entity',
+ messageParams: {'charAsInt': charAsInt}));
+ }
+ char = String.fromCharCodes([charAsInt]);
+ }
+
+ // Discard the ; if present. Otherwise, put it back on the queue and
+ // invoke parseError on parser.
+ if (c != ';') {
+ _addToken(ParseErrorToken('numeric-entity-without-semicolon'));
+ stream.unget(c);
+ }
+ return char;
+ }
+
+ void consumeEntity({String? allowedChar, bool fromAttribute = false}) {
+ // Initialise to the default output for when no entity is matched
+ String? output = '&';
+
+ final charStack = [stream.char()];
+ if (isWhitespace(charStack[0]) ||
+ charStack[0] == '<' ||
+ charStack[0] == '&' ||
+ charStack[0] == eof ||
+ allowedChar == charStack[0]) {
+ stream.unget(charStack[0]);
+ } else if (charStack[0] == '#') {
+ // Read the next character to see if it's hex or decimal
+ var hex = false;
+ charStack.add(stream.char());
+ if (charStack.last == 'x' || charStack.last == 'X') {
+ hex = true;
+ charStack.add(stream.char());
+ }
+
+ // charStack.last should be the first digit
+ if (hex && isHexDigit(charStack.last) ||
+ (!hex && isDigit(charStack.last))) {
+ // At least one digit found, so consume the whole number
+ stream.unget(charStack.last);
+ output = consumeNumberEntity(hex);
+ } else {
+ // No digits found
+ _addToken(ParseErrorToken('expected-numeric-entity'));
+ stream.unget(charStack.removeLast());
+ output = '&${charStack.join()}';
+ }
+ } else {
+ // At this point in the process might have named entity. Entities
+ // are stored in the global variable "entities".
+ //
+ // 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);
+
+ if (filteredEntityList.isEmpty) {
+ break;
+ }
+ charStack.add(stream.char());
+ }
+
+ // At this point we have a string that starts with some characters
+ // that may match an entity
+ String? entityName;
+
+ // Try to find the longest entity the string will match to take care
+ // of ¬i for instance.
+
+ int entityLen;
+ for (entityLen = charStack.length - 1; entityLen > 1; entityLen--) {
+ final possibleEntityName = charStack.sublist(0, entityLen).join();
+ if (entities.containsKey(possibleEntityName)) {
+ entityName = possibleEntityName;
+ break;
+ }
+ }
+
+ if (entityName != null) {
+ final lastChar = entityName[entityName.length - 1];
+ if (lastChar != ';') {
+ _addToken(ParseErrorToken('named-entity-without-semicolon'));
+ }
+ if (lastChar != ';' &&
+ fromAttribute &&
+ (isLetterOrDigit(charStack[entityLen]) ||
+ charStack[entityLen] == '=')) {
+ stream.unget(charStack.removeLast());
+ output = '&${charStack.join()}';
+ } else {
+ output = entities[entityName];
+ stream.unget(charStack.removeLast());
+ output = '$output${slice(charStack, entityLen).join()}';
+ }
+ } else {
+ _addToken(ParseErrorToken('expected-named-entity'));
+ stream.unget(charStack.removeLast());
+ output = '&${charStack.join()}';
+ }
+ }
+ if (fromAttribute) {
+ _attributeValue.write(output);
+ } else {
+ Token token;
+ if (isWhitespace(output)) {
+ token = SpaceCharactersToken(output);
+ } else {
+ token = CharactersToken(output);
+ }
+ _addToken(token);
+ }
+ }
+
+ /// This method replaces the need for "entityInAttributeValueState".
+ void processEntityInAttribute(String allowedChar) {
+ consumeEntity(allowedChar: allowedChar, fromAttribute: true);
+ }
+
+ /// This method is a generic handler for emitting the tags. It also sets
+ /// the state to "data" because that's what's needed after a token has been
+ /// emitted.
+ void emitCurrentToken() {
+ final token = currentToken!;
+ // Add token to the queue to be yielded
+ if (token is TagToken) {
+ if (lowercaseElementName) {
+ token.name = token.name?.toAsciiLowerCase();
+ }
+ if (token is EndTagToken) {
+ if (_attributes != null) {
+ _addToken(ParseErrorToken('attributes-in-end-tag'));
+ }
+ if (token.selfClosing) {
+ _addToken(ParseErrorToken('this-closing-flag-on-end-tag'));
+ }
+ } else if (token is StartTagToken) {
+ // HTML5 specific normalizations to the token stream.
+ // Convert the list into a map where first key wins.
+ token.data = LinkedHashMap<Object, String>();
+ if (_attributes != null) {
+ for (var attr in _attributes!) {
+ token.data.putIfAbsent(attr.name!, () => attr.value);
+ }
+ if (attributeSpans) token.attributeSpans = _attributes;
+ }
+ }
+ _attributes = null;
+ _attributeNames = null;
+ }
+ _addToken(token);
+ state = dataState;
+ }
+
+ // Below are the various tokenizer states worked out.
+
+ bool dataState() {
+ final data = stream.char();
+ if (data == '&') {
+ state = entityDataState;
+ } else if (data == '<') {
+ state = tagOpenState;
+ } else if (data == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ _addToken(CharactersToken('\u0000'));
+ } else if (data == eof) {
+ // Tokenization ends.
+ return false;
+ } else if (isWhitespace(data)) {
+ // 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)}'));
+ // No need to update lastFourChars here, since the first space will
+ // have already been appended to lastFourChars and will have broken
+ // any <!-- or --> sequences
+ } else {
+ final chars = stream.charsUntil('&<\u0000');
+ _addToken(CharactersToken('$data$chars'));
+ }
+ return true;
+ }
+
+ bool entityDataState() {
+ consumeEntity();
+ state = dataState;
+ return true;
+ }
+
+ bool rcdataState() {
+ final data = stream.char();
+ if (data == '&') {
+ state = characterReferenceInRcdata;
+ } else if (data == '<') {
+ state = rcdataLessThanSignState;
+ } else if (data == eof) {
+ // Tokenization ends.
+ return false;
+ } else if (data == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ _addToken(CharactersToken('\uFFFD'));
+ } else if (isWhitespace(data)) {
+ // 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)}'));
+ } else {
+ final chars = stream.charsUntil('&<');
+ _addToken(CharactersToken('$data$chars'));
+ }
+ return true;
+ }
+
+ bool characterReferenceInRcdata() {
+ consumeEntity();
+ state = rcdataState;
+ return true;
+ }
+
+ bool rawtextState() {
+ final data = stream.char();
+ if (data == '<') {
+ state = rawtextLessThanSignState;
+ } else if (data == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ _addToken(CharactersToken('\uFFFD'));
+ } else if (data == eof) {
+ // Tokenization ends.
+ return false;
+ } else {
+ final chars = stream.charsUntil('<\u0000');
+ _addToken(CharactersToken('$data$chars'));
+ }
+ return true;
+ }
+
+ bool scriptDataState() {
+ final data = stream.char();
+ if (data == '<') {
+ state = scriptDataLessThanSignState;
+ } else if (data == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ _addToken(CharactersToken('\uFFFD'));
+ } else if (data == eof) {
+ // Tokenization ends.
+ return false;
+ } else {
+ final chars = stream.charsUntil('<\u0000');
+ _addToken(CharactersToken('$data$chars'));
+ }
+ return true;
+ }
+
+ bool plaintextState() {
+ final data = stream.char();
+ if (data == eof) {
+ // Tokenization ends.
+ return false;
+ } else if (data == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ _addToken(CharactersToken('\uFFFD'));
+ } else {
+ _addToken(CharactersToken('$data${stream.charsUntil("\u0000")}'));
+ }
+ return true;
+ }
+
+ bool tagOpenState() {
+ final data = stream.char();
+ if (data == '!') {
+ state = markupDeclarationOpenState;
+ } else if (data == '/') {
+ state = closeTagOpenState;
+ } else if (isLetter(data)) {
+ currentToken = StartTagToken(data);
+ state = tagNameState;
+ } else if (data == '>') {
+ // XXX In theory it could be something besides a tag name. But
+ // do we really care?
+ _addToken(ParseErrorToken('expected-tag-name-but-got-right-bracket'));
+ _addToken(CharactersToken('<>'));
+ state = dataState;
+ } else if (data == '?') {
+ // XXX In theory it could be something besides a tag name. But
+ // do we really care?
+ _addToken(ParseErrorToken('expected-tag-name-but-got-question-mark'));
+ stream.unget(data);
+ state = bogusCommentState;
+ } else {
+ // XXX
+ _addToken(ParseErrorToken('expected-tag-name'));
+ _addToken(CharactersToken('<'));
+ stream.unget(data);
+ state = dataState;
+ }
+ return true;
+ }
+
+ bool closeTagOpenState() {
+ final data = stream.char();
+ if (isLetter(data)) {
+ currentToken = EndTagToken(data);
+ state = tagNameState;
+ } else if (data == '>') {
+ _addToken(ParseErrorToken('expected-closing-tag-but-got-right-bracket'));
+ state = dataState;
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('expected-closing-tag-but-got-eof'));
+ _addToken(CharactersToken('</'));
+ state = dataState;
+ } else {
+ // XXX data can be _'_...
+ _addToken(ParseErrorToken('expected-closing-tag-but-got-char',
+ messageParams: {'data': data}));
+ stream.unget(data);
+ state = bogusCommentState;
+ }
+ return true;
+ }
+
+ bool tagNameState() {
+ final data = stream.char();
+ if (isWhitespace(data)) {
+ state = beforeAttributeNameState;
+ } else if (data == '>') {
+ emitCurrentToken();
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('eof-in-tag-name'));
+ state = dataState;
+ } else if (data == '/') {
+ state = selfClosingStartTagState;
+ } else if (data == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ currentTagToken.name = '${currentTagToken.name}\uFFFD';
+ } else {
+ currentTagToken.name = '${currentTagToken.name}$data';
+ // (Don't use charsUntil here, because tag names are
+ // very short and it's faster to not do anything fancy)
+ }
+ return true;
+ }
+
+ bool rcdataLessThanSignState() {
+ final data = stream.char();
+ if (data == '/') {
+ _buffer.clear();
+ state = rcdataEndTagOpenState;
+ } else {
+ _addToken(CharactersToken('<'));
+ stream.unget(data);
+ state = rcdataState;
+ }
+ return true;
+ }
+
+ bool rcdataEndTagOpenState() {
+ final data = stream.char();
+ if (isLetter(data)) {
+ _buffer.write(data);
+ state = rcdataEndTagNameState;
+ } else {
+ _addToken(CharactersToken('</'));
+ stream.unget(data);
+ state = rcdataState;
+ }
+ return true;
+ }
+
+ bool _tokenIsAppropriate() {
+ // TODO(jmesserly): this should use case insensitive compare instead.
+ return currentToken is TagToken &&
+ currentTagToken.name!.toLowerCase() == '$_buffer'.toLowerCase();
+ }
+
+ bool rcdataEndTagNameState() {
+ final appropriate = _tokenIsAppropriate();
+ final data = stream.char();
+ if (isWhitespace(data) && appropriate) {
+ currentToken = EndTagToken('$_buffer');
+ state = beforeAttributeNameState;
+ } else if (data == '/' && appropriate) {
+ currentToken = EndTagToken('$_buffer');
+ state = selfClosingStartTagState;
+ } else if (data == '>' && appropriate) {
+ currentToken = EndTagToken('$_buffer');
+ emitCurrentToken();
+ state = dataState;
+ } else if (isLetter(data)) {
+ _buffer.write(data);
+ } else {
+ _addToken(CharactersToken('</$_buffer'));
+ stream.unget(data);
+ state = rcdataState;
+ }
+ return true;
+ }
+
+ bool rawtextLessThanSignState() {
+ final data = stream.char();
+ if (data == '/') {
+ _buffer.clear();
+ state = rawtextEndTagOpenState;
+ } else {
+ _addToken(CharactersToken('<'));
+ stream.unget(data);
+ state = rawtextState;
+ }
+ return true;
+ }
+
+ bool rawtextEndTagOpenState() {
+ final data = stream.char();
+ if (isLetter(data)) {
+ _buffer.write(data);
+ state = rawtextEndTagNameState;
+ } else {
+ _addToken(CharactersToken('</'));
+ stream.unget(data);
+ state = rawtextState;
+ }
+ return true;
+ }
+
+ bool rawtextEndTagNameState() {
+ final appropriate = _tokenIsAppropriate();
+ final data = stream.char();
+ if (isWhitespace(data) && appropriate) {
+ currentToken = EndTagToken('$_buffer');
+ state = beforeAttributeNameState;
+ } else if (data == '/' && appropriate) {
+ currentToken = EndTagToken('$_buffer');
+ state = selfClosingStartTagState;
+ } else if (data == '>' && appropriate) {
+ currentToken = EndTagToken('$_buffer');
+ emitCurrentToken();
+ state = dataState;
+ } else if (isLetter(data)) {
+ _buffer.write(data);
+ } else {
+ _addToken(CharactersToken('</$_buffer'));
+ stream.unget(data);
+ state = rawtextState;
+ }
+ return true;
+ }
+
+ bool scriptDataLessThanSignState() {
+ final data = stream.char();
+ if (data == '/') {
+ _buffer.clear();
+ state = scriptDataEndTagOpenState;
+ } else if (data == '!') {
+ _addToken(CharactersToken('<!'));
+ state = scriptDataEscapeStartState;
+ } else {
+ _addToken(CharactersToken('<'));
+ stream.unget(data);
+ state = scriptDataState;
+ }
+ return true;
+ }
+
+ bool scriptDataEndTagOpenState() {
+ final data = stream.char();
+ if (isLetter(data)) {
+ _buffer.write(data);
+ state = scriptDataEndTagNameState;
+ } else {
+ _addToken(CharactersToken('</'));
+ stream.unget(data);
+ state = scriptDataState;
+ }
+ return true;
+ }
+
+ bool scriptDataEndTagNameState() {
+ final appropriate = _tokenIsAppropriate();
+ final data = stream.char();
+ if (isWhitespace(data) && appropriate) {
+ currentToken = EndTagToken('$_buffer');
+ state = beforeAttributeNameState;
+ } else if (data == '/' && appropriate) {
+ currentToken = EndTagToken('$_buffer');
+ state = selfClosingStartTagState;
+ } else if (data == '>' && appropriate) {
+ currentToken = EndTagToken('$_buffer');
+ emitCurrentToken();
+ state = dataState;
+ } else if (isLetter(data)) {
+ _buffer.write(data);
+ } else {
+ _addToken(CharactersToken('</$_buffer'));
+ stream.unget(data);
+ state = scriptDataState;
+ }
+ return true;
+ }
+
+ bool scriptDataEscapeStartState() {
+ final data = stream.char();
+ if (data == '-') {
+ _addToken(CharactersToken('-'));
+ state = scriptDataEscapeStartDashState;
+ } else {
+ stream.unget(data);
+ state = scriptDataState;
+ }
+ return true;
+ }
+
+ bool scriptDataEscapeStartDashState() {
+ final data = stream.char();
+ if (data == '-') {
+ _addToken(CharactersToken('-'));
+ state = scriptDataEscapedDashDashState;
+ } else {
+ stream.unget(data);
+ state = scriptDataState;
+ }
+ return true;
+ }
+
+ bool scriptDataEscapedState() {
+ final data = stream.char();
+ if (data == '-') {
+ _addToken(CharactersToken('-'));
+ state = scriptDataEscapedDashState;
+ } else if (data == '<') {
+ state = scriptDataEscapedLessThanSignState;
+ } else if (data == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ _addToken(CharactersToken('\uFFFD'));
+ } else if (data == eof) {
+ state = dataState;
+ } else {
+ final chars = stream.charsUntil('<-\u0000');
+ _addToken(CharactersToken('$data$chars'));
+ }
+ return true;
+ }
+
+ bool scriptDataEscapedDashState() {
+ final data = stream.char();
+ if (data == '-') {
+ _addToken(CharactersToken('-'));
+ state = scriptDataEscapedDashDashState;
+ } else if (data == '<') {
+ state = scriptDataEscapedLessThanSignState;
+ } else if (data == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ _addToken(CharactersToken('\uFFFD'));
+ state = scriptDataEscapedState;
+ } else if (data == eof) {
+ state = dataState;
+ } else {
+ _addToken(CharactersToken(data));
+ state = scriptDataEscapedState;
+ }
+ return true;
+ }
+
+ bool scriptDataEscapedDashDashState() {
+ final data = stream.char();
+ if (data == '-') {
+ _addToken(CharactersToken('-'));
+ } else if (data == '<') {
+ state = scriptDataEscapedLessThanSignState;
+ } else if (data == '>') {
+ _addToken(CharactersToken('>'));
+ state = scriptDataState;
+ } else if (data == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ _addToken(CharactersToken('\uFFFD'));
+ state = scriptDataEscapedState;
+ } else if (data == eof) {
+ state = dataState;
+ } else {
+ _addToken(CharactersToken(data));
+ state = scriptDataEscapedState;
+ }
+ return true;
+ }
+
+ bool scriptDataEscapedLessThanSignState() {
+ final data = stream.char();
+ if (data == '/') {
+ _buffer.clear();
+ state = scriptDataEscapedEndTagOpenState;
+ } else if (isLetter(data)) {
+ _addToken(CharactersToken('<$data'));
+ _buffer.clear();
+ _buffer.write(data);
+ state = scriptDataDoubleEscapeStartState;
+ } else {
+ _addToken(CharactersToken('<'));
+ stream.unget(data);
+ state = scriptDataEscapedState;
+ }
+ return true;
+ }
+
+ bool scriptDataEscapedEndTagOpenState() {
+ final data = stream.char();
+ if (isLetter(data)) {
+ _buffer.clear();
+ _buffer.write(data);
+ state = scriptDataEscapedEndTagNameState;
+ } else {
+ _addToken(CharactersToken('</'));
+ stream.unget(data);
+ state = scriptDataEscapedState;
+ }
+ return true;
+ }
+
+ bool scriptDataEscapedEndTagNameState() {
+ final appropriate = _tokenIsAppropriate();
+ final data = stream.char();
+ if (isWhitespace(data) && appropriate) {
+ currentToken = EndTagToken('$_buffer');
+ state = beforeAttributeNameState;
+ } else if (data == '/' && appropriate) {
+ currentToken = EndTagToken('$_buffer');
+ state = selfClosingStartTagState;
+ } else if (data == '>' && appropriate) {
+ currentToken = EndTagToken('$_buffer');
+ emitCurrentToken();
+ state = dataState;
+ } else if (isLetter(data)) {
+ _buffer.write(data);
+ } else {
+ _addToken(CharactersToken('</$_buffer'));
+ stream.unget(data);
+ state = scriptDataEscapedState;
+ }
+ return true;
+ }
+
+ bool scriptDataDoubleEscapeStartState() {
+ final data = stream.char();
+ if (isWhitespace(data) || data == '/' || data == '>') {
+ _addToken(CharactersToken(data));
+ if ('$_buffer'.toLowerCase() == 'script') {
+ state = scriptDataDoubleEscapedState;
+ } else {
+ state = scriptDataEscapedState;
+ }
+ } else if (isLetter(data)) {
+ _addToken(CharactersToken(data));
+ _buffer.write(data);
+ } else {
+ stream.unget(data);
+ state = scriptDataEscapedState;
+ }
+ return true;
+ }
+
+ bool scriptDataDoubleEscapedState() {
+ final data = stream.char();
+ if (data == '-') {
+ _addToken(CharactersToken('-'));
+ state = scriptDataDoubleEscapedDashState;
+ } else if (data == '<') {
+ _addToken(CharactersToken('<'));
+ state = scriptDataDoubleEscapedLessThanSignState;
+ } else if (data == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ _addToken(CharactersToken('\uFFFD'));
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('eof-in-script-in-script'));
+ state = dataState;
+ } else {
+ _addToken(CharactersToken(data));
+ }
+ return true;
+ }
+
+ bool scriptDataDoubleEscapedDashState() {
+ final data = stream.char();
+ if (data == '-') {
+ _addToken(CharactersToken('-'));
+ state = scriptDataDoubleEscapedDashDashState;
+ } else if (data == '<') {
+ _addToken(CharactersToken('<'));
+ state = scriptDataDoubleEscapedLessThanSignState;
+ } else if (data == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ _addToken(CharactersToken('\uFFFD'));
+ state = scriptDataDoubleEscapedState;
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('eof-in-script-in-script'));
+ state = dataState;
+ } else {
+ _addToken(CharactersToken(data));
+ state = scriptDataDoubleEscapedState;
+ }
+ return true;
+ }
+
+ // TODO(jmesserly): report bug in original code
+ // (was "Dash" instead of "DashDash")
+ bool scriptDataDoubleEscapedDashDashState() {
+ final data = stream.char();
+ if (data == '-') {
+ _addToken(CharactersToken('-'));
+ } else if (data == '<') {
+ _addToken(CharactersToken('<'));
+ state = scriptDataDoubleEscapedLessThanSignState;
+ } else if (data == '>') {
+ _addToken(CharactersToken('>'));
+ state = scriptDataState;
+ } else if (data == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ _addToken(CharactersToken('\uFFFD'));
+ state = scriptDataDoubleEscapedState;
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('eof-in-script-in-script'));
+ state = dataState;
+ } else {
+ _addToken(CharactersToken(data));
+ state = scriptDataDoubleEscapedState;
+ }
+ return true;
+ }
+
+ bool scriptDataDoubleEscapedLessThanSignState() {
+ final data = stream.char();
+ if (data == '/') {
+ _addToken(CharactersToken('/'));
+ _buffer.clear();
+ state = scriptDataDoubleEscapeEndState;
+ } else {
+ stream.unget(data);
+ state = scriptDataDoubleEscapedState;
+ }
+ return true;
+ }
+
+ bool scriptDataDoubleEscapeEndState() {
+ final data = stream.char();
+ if (isWhitespace(data) || data == '/' || data == '>') {
+ _addToken(CharactersToken(data));
+ if ('$_buffer'.toLowerCase() == 'script') {
+ state = scriptDataEscapedState;
+ } else {
+ state = scriptDataDoubleEscapedState;
+ }
+ } else if (isLetter(data)) {
+ _addToken(CharactersToken(data));
+ _buffer.write(data);
+ } else {
+ stream.unget(data);
+ state = scriptDataDoubleEscapedState;
+ }
+ return true;
+ }
+
+ bool beforeAttributeNameState() {
+ final data = stream.char();
+ if (isWhitespace(data)) {
+ stream.charsUntil(spaceCharacters, true);
+ } else if (data != null && isLetter(data)) {
+ _addAttribute(data);
+ state = attributeNameState;
+ } else if (data == '>') {
+ emitCurrentToken();
+ } else if (data == '/') {
+ state = selfClosingStartTagState;
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('expected-attribute-name-but-got-eof'));
+ state = dataState;
+ } else if ("'\"=<".contains(data!)) {
+ _addToken(ParseErrorToken('invalid-character-in-attribute-name'));
+ _addAttribute(data);
+ state = attributeNameState;
+ } else if (data == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ _addAttribute('\uFFFD');
+ state = attributeNameState;
+ } else {
+ _addAttribute(data);
+ state = attributeNameState;
+ }
+ return true;
+ }
+
+ bool attributeNameState() {
+ final data = stream.char();
+ var leavingThisState = true;
+ var emitToken = false;
+ if (data == '=') {
+ state = beforeAttributeValueState;
+ } else if (isLetter(data)) {
+ _attributeName.write(data);
+ _attributeName.write(stream.charsUntil(asciiLetters, true));
+ leavingThisState = false;
+ } else if (data == '>') {
+ // XXX If we emit here the attributes are converted to a dict
+ // without being checked and when the code below runs we error
+ // because data is a dict not a list
+ emitToken = true;
+ } else if (isWhitespace(data)) {
+ state = afterAttributeNameState;
+ } else if (data == '/') {
+ state = selfClosingStartTagState;
+ } else if (data == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ _attributeName.write('\uFFFD');
+ leavingThisState = false;
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('eof-in-attribute-name'));
+ state = dataState;
+ } else if ("'\"<".contains(data!)) {
+ _addToken(ParseErrorToken('invalid-character-in-attribute-name'));
+ _attributeName.write(data);
+ leavingThisState = false;
+ } else {
+ _attributeName.write(data);
+ leavingThisState = false;
+ }
+
+ if (leavingThisState) {
+ _markAttributeNameEnd(-1);
+
+ // Attributes are not dropped at this stage. That happens when the
+ // start tag token is emitted so values can still be safely appended
+ // to attributes, but we do want to report the parse error in time.
+ var attrName = _attributeName.toString();
+ if (lowercaseAttrName) {
+ attrName = attrName.toAsciiLowerCase();
+ }
+ _attributes!.last.name = attrName;
+ _attributeNames ??= {};
+ if (_attributeNames!.contains(attrName)) {
+ _addToken(ParseErrorToken('duplicate-attribute'));
+ }
+ _attributeNames!.add(attrName);
+
+ // XXX Fix for above XXX
+ if (emitToken) {
+ emitCurrentToken();
+ }
+ }
+ return true;
+ }
+
+ bool afterAttributeNameState() {
+ final data = stream.char();
+ if (isWhitespace(data)) {
+ stream.charsUntil(spaceCharacters, true);
+ } else if (data == '=') {
+ state = beforeAttributeValueState;
+ } else if (data == '>') {
+ emitCurrentToken();
+ } else if (data != null && isLetter(data)) {
+ _addAttribute(data);
+ state = attributeNameState;
+ } else if (data == '/') {
+ state = selfClosingStartTagState;
+ } else if (data == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ _addAttribute('\uFFFD');
+ state = attributeNameState;
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('expected-end-of-tag-but-got-eof'));
+ state = dataState;
+ } else if ("'\"<".contains(data!)) {
+ _addToken(ParseErrorToken('invalid-character-after-attribute-name'));
+ _addAttribute(data);
+ state = attributeNameState;
+ } else {
+ _addAttribute(data);
+ state = attributeNameState;
+ }
+ return true;
+ }
+
+ bool beforeAttributeValueState() {
+ final data = stream.char();
+ if (isWhitespace(data)) {
+ stream.charsUntil(spaceCharacters, true);
+ } else if (data == '"') {
+ _markAttributeValueStart(0);
+ state = attributeValueDoubleQuotedState;
+ } else if (data == '&') {
+ state = attributeValueUnQuotedState;
+ stream.unget(data);
+ _markAttributeValueStart(0);
+ } else if (data == "'") {
+ _markAttributeValueStart(0);
+ state = attributeValueSingleQuotedState;
+ } else if (data == '>') {
+ _addToken(
+ ParseErrorToken('expected-attribute-value-but-got-right-bracket'));
+ emitCurrentToken();
+ } else if (data == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ _markAttributeValueStart(-1);
+ _attributeValue.write('\uFFFD');
+ state = attributeValueUnQuotedState;
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('expected-attribute-value-but-got-eof'));
+ state = dataState;
+ } else if ('=<`'.contains(data!)) {
+ _addToken(ParseErrorToken('equals-in-unquoted-attribute-value'));
+ _markAttributeValueStart(-1);
+ _attributeValue.write(data);
+ state = attributeValueUnQuotedState;
+ } else {
+ _markAttributeValueStart(-1);
+ _attributeValue.write(data);
+ state = attributeValueUnQuotedState;
+ }
+ return true;
+ }
+
+ bool attributeValueDoubleQuotedState() {
+ final data = stream.char();
+ if (data == '"') {
+ _markAttributeValueEnd(-1);
+ _markAttributeEnd(0);
+ state = afterAttributeValueState;
+ } else if (data == '&') {
+ processEntityInAttribute('"');
+ } else if (data == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ _attributeValue.write('\uFFFD');
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('eof-in-attribute-value-double-quote'));
+ _markAttributeValueEnd(-1);
+ state = dataState;
+ } else {
+ _attributeValue.write(data);
+ _attributeValue.write(stream.charsUntil('"&'));
+ }
+ return true;
+ }
+
+ bool attributeValueSingleQuotedState() {
+ final data = stream.char();
+ if (data == "'") {
+ _markAttributeValueEnd(-1);
+ _markAttributeEnd(0);
+ state = afterAttributeValueState;
+ } else if (data == '&') {
+ processEntityInAttribute("'");
+ } else if (data == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ _attributeValue.write('\uFFFD');
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('eof-in-attribute-value-single-quote'));
+ _markAttributeValueEnd(-1);
+ state = dataState;
+ } else {
+ _attributeValue.write(data);
+ _attributeValue.write(stream.charsUntil("'&"));
+ }
+ return true;
+ }
+
+ bool attributeValueUnQuotedState() {
+ final data = stream.char();
+ if (isWhitespace(data)) {
+ _markAttributeValueEnd(-1);
+ state = beforeAttributeNameState;
+ } else if (data == '&') {
+ processEntityInAttribute('>');
+ } else if (data == '>') {
+ _markAttributeValueEnd(-1);
+ emitCurrentToken();
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('eof-in-attribute-value-no-quotes'));
+ _markAttributeValueEnd(-1);
+ state = dataState;
+ } else if ('"\'=<`'.contains(data!)) {
+ _addToken(
+ ParseErrorToken('unexpected-character-in-unquoted-attribute-value'));
+ _attributeValue.write(data);
+ } else if (data == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ _attributeValue.write('\uFFFD');
+ } else {
+ _attributeValue.write(data);
+ _attributeValue.write(stream.charsUntil("&>\"'=<`$spaceCharacters"));
+ }
+ return true;
+ }
+
+ bool afterAttributeValueState() {
+ final data = stream.char();
+ if (isWhitespace(data)) {
+ state = beforeAttributeNameState;
+ } else if (data == '>') {
+ emitCurrentToken();
+ } else if (data == '/') {
+ state = selfClosingStartTagState;
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('unexpected-EOF-after-attribute-value'));
+ stream.unget(data);
+ state = dataState;
+ } else {
+ _addToken(ParseErrorToken('unexpected-character-after-attribute-value'));
+ stream.unget(data);
+ state = beforeAttributeNameState;
+ }
+ return true;
+ }
+
+ bool selfClosingStartTagState() {
+ final data = stream.char();
+ if (data == '>') {
+ currentTagToken.selfClosing = true;
+ emitCurrentToken();
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('unexpected-EOF-after-solidus-in-tag'));
+ stream.unget(data);
+ state = dataState;
+ } else {
+ _addToken(ParseErrorToken('unexpected-character-after-soldius-in-tag'));
+ stream.unget(data);
+ state = beforeAttributeNameState;
+ }
+ return true;
+ }
+
+ bool bogusCommentState() {
+ // 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('>');
+ data = data.replaceAll('\u0000', '\uFFFD');
+ _addToken(CommentToken(data));
+
+ // Eat the character directly after the bogus comment which is either a
+ // ">" or an EOF.
+ stream.char();
+ state = dataState;
+ return true;
+ }
+
+ bool markupDeclarationOpenState() {
+ final charStack = [stream.char()];
+ if (charStack.last == '-') {
+ charStack.add(stream.char());
+ if (charStack.last == '-') {
+ currentToken = CommentToken();
+ state = commentStartState;
+ return true;
+ }
+ } else if (charStack.last == 'd' || charStack.last == 'D') {
+ var matched = true;
+ for (var expected in const ['oO', 'cC', 'tT', 'yY', 'pP', 'eE']) {
+ final char = stream.char();
+ charStack.add(char);
+ if (char == eof || !expected.contains(char!)) {
+ matched = false;
+ break;
+ }
+ }
+ if (matched) {
+ currentToken = DoctypeToken(correct: true);
+ state = doctypeState;
+ return true;
+ }
+ } else if (charStack.last == '[' &&
+ parser != null &&
+ parser!.tree.openElements.isNotEmpty &&
+ parser!.tree.openElements.last.namespaceUri !=
+ parser!.tree.defaultNamespace) {
+ var matched = true;
+ for (var expected in const ['C', 'D', 'A', 'T', 'A', '[']) {
+ charStack.add(stream.char());
+ if (charStack.last != expected) {
+ matched = false;
+ break;
+ }
+ }
+ if (matched) {
+ state = cdataSectionState;
+ return true;
+ }
+ }
+
+ _addToken(ParseErrorToken('expected-dashes-or-doctype'));
+
+ while (charStack.isNotEmpty) {
+ stream.unget(charStack.removeLast());
+ }
+ state = bogusCommentState;
+ return true;
+ }
+
+ bool commentStartState() {
+ final data = stream.char();
+ if (data == '-') {
+ state = commentStartDashState;
+ } else if (data == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ currentStringToken.add('\uFFFD');
+ } else if (data == '>') {
+ _addToken(ParseErrorToken('incorrect-comment'));
+ _addToken(currentToken!);
+ state = dataState;
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('eof-in-comment'));
+ _addToken(currentToken!);
+ state = dataState;
+ } else {
+ currentStringToken.add(data!);
+ state = commentState;
+ }
+ return true;
+ }
+
+ bool commentStartDashState() {
+ final data = stream.char();
+ if (data == '-') {
+ state = commentEndState;
+ } else if (data == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ currentStringToken.add('-\uFFFD');
+ } else if (data == '>') {
+ _addToken(ParseErrorToken('incorrect-comment'));
+ _addToken(currentToken!);
+ state = dataState;
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('eof-in-comment'));
+ _addToken(currentToken!);
+ state = dataState;
+ } else {
+ currentStringToken.add('-').add(data!);
+ state = commentState;
+ }
+ return true;
+ }
+
+ bool commentState() {
+ final data = stream.char();
+ if (data == '-') {
+ state = commentEndDashState;
+ } else if (data == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ currentStringToken.add('\uFFFD');
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('eof-in-comment'));
+ _addToken(currentToken!);
+ state = dataState;
+ } else {
+ currentStringToken.add(data!).add(stream.charsUntil('-\u0000'));
+ }
+ return true;
+ }
+
+ bool commentEndDashState() {
+ final data = stream.char();
+ if (data == '-') {
+ state = commentEndState;
+ } else if (data == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ currentStringToken.add('-\uFFFD');
+ state = commentState;
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('eof-in-comment-end-dash'));
+ _addToken(currentToken!);
+ state = dataState;
+ } else {
+ currentStringToken.add('-').add(data!);
+ state = commentState;
+ }
+ return true;
+ }
+
+ bool commentEndState() {
+ final data = stream.char();
+ if (data == '>') {
+ _addToken(currentToken!);
+ state = dataState;
+ } else if (data == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ currentStringToken.add('--\uFFFD');
+ state = commentState;
+ } else if (data == '!') {
+ _addToken(
+ ParseErrorToken('unexpected-bang-after-double-dash-in-comment'));
+ state = commentEndBangState;
+ } else if (data == '-') {
+ _addToken(
+ ParseErrorToken('unexpected-dash-after-double-dash-in-comment'));
+ currentStringToken.add(data!);
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('eof-in-comment-double-dash'));
+ _addToken(currentToken!);
+ state = dataState;
+ } else {
+ // XXX
+ _addToken(ParseErrorToken('unexpected-char-in-comment'));
+ currentStringToken.add('--').add(data!);
+ state = commentState;
+ }
+ return true;
+ }
+
+ bool commentEndBangState() {
+ final data = stream.char();
+ if (data == '>') {
+ _addToken(currentToken!);
+ state = dataState;
+ } else if (data == '-') {
+ currentStringToken.add('--!');
+ state = commentEndDashState;
+ } else if (data == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ currentStringToken.add('--!\uFFFD');
+ state = commentState;
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('eof-in-comment-end-bang-state'));
+ _addToken(currentToken!);
+ state = dataState;
+ } else {
+ currentStringToken.add('--!').add(data!);
+ state = commentState;
+ }
+ return true;
+ }
+
+ bool doctypeState() {
+ final data = stream.char();
+ if (isWhitespace(data)) {
+ state = beforeDoctypeNameState;
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('expected-doctype-name-but-got-eof'));
+ currentDoctypeToken.correct = false;
+ _addToken(currentToken!);
+ state = dataState;
+ } else {
+ _addToken(ParseErrorToken('need-space-after-doctype'));
+ stream.unget(data);
+ state = beforeDoctypeNameState;
+ }
+ return true;
+ }
+
+ bool beforeDoctypeNameState() {
+ final data = stream.char();
+ if (isWhitespace(data)) {
+ return true;
+ } else if (data == '>') {
+ _addToken(ParseErrorToken('expected-doctype-name-but-got-right-bracket'));
+ currentDoctypeToken.correct = false;
+ _addToken(currentToken!);
+ state = dataState;
+ } else if (data == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ currentDoctypeToken.name = '\uFFFD';
+ state = doctypeNameState;
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('expected-doctype-name-but-got-eof'));
+ currentDoctypeToken.correct = false;
+ _addToken(currentToken!);
+ state = dataState;
+ } else {
+ currentDoctypeToken.name = data;
+ state = doctypeNameState;
+ }
+ return true;
+ }
+
+ bool doctypeNameState() {
+ final data = stream.char();
+ if (isWhitespace(data)) {
+ currentDoctypeToken.name = currentDoctypeToken.name?.toAsciiLowerCase();
+ state = afterDoctypeNameState;
+ } else if (data == '>') {
+ currentDoctypeToken.name = currentDoctypeToken.name?.toAsciiLowerCase();
+ _addToken(currentToken!);
+ state = dataState;
+ } else if (data == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ currentDoctypeToken.name = '${currentDoctypeToken.name}\uFFFD';
+ state = doctypeNameState;
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('eof-in-doctype-name'));
+ currentDoctypeToken.correct = false;
+ currentDoctypeToken.name = currentDoctypeToken.name?.toAsciiLowerCase();
+ _addToken(currentToken!);
+ state = dataState;
+ } else {
+ currentDoctypeToken.name = '${currentDoctypeToken.name}$data';
+ }
+ return true;
+ }
+
+ bool afterDoctypeNameState() {
+ var data = stream.char();
+ if (isWhitespace(data)) {
+ return true;
+ } else if (data == '>') {
+ _addToken(currentToken!);
+ state = dataState;
+ } else if (data == eof) {
+ currentDoctypeToken.correct = false;
+ stream.unget(data);
+ _addToken(ParseErrorToken('eof-in-doctype'));
+ _addToken(currentToken!);
+ state = dataState;
+ } else {
+ if (data == 'p' || data == 'P') {
+ // TODO(jmesserly): would be nice to have a helper for this.
+ var matched = true;
+ for (var expected in const ['uU', 'bB', 'lL', 'iI', 'cC']) {
+ data = stream.char();
+ if (data == eof || !expected.contains(data!)) {
+ matched = false;
+ break;
+ }
+ }
+ if (matched) {
+ state = afterDoctypePublicKeywordState;
+ return true;
+ }
+ } else if (data == 's' || data == 'S') {
+ var matched = true;
+ for (var expected in const ['yY', 'sS', 'tT', 'eE', 'mM']) {
+ data = stream.char();
+ if (data == eof || !expected.contains(data!)) {
+ matched = false;
+ break;
+ }
+ }
+ if (matched) {
+ state = afterDoctypeSystemKeywordState;
+ return true;
+ }
+ }
+
+ // All the characters read before the current 'data' will be
+ // [a-zA-Z], so they're garbage in the bogus doctype and can be
+ // discarded; only the latest character might be '>' or EOF
+ // and needs to be ungetted
+ stream.unget(data);
+ _addToken(ParseErrorToken('expected-space-or-right-bracket-in-doctype',
+ messageParams: {'data': data}));
+ currentDoctypeToken.correct = false;
+ state = bogusDoctypeState;
+ }
+ return true;
+ }
+
+ bool afterDoctypePublicKeywordState() {
+ final data = stream.char();
+ if (isWhitespace(data)) {
+ state = beforeDoctypePublicIdentifierState;
+ } else if (data == "'" || data == '"') {
+ _addToken(ParseErrorToken('unexpected-char-in-doctype'));
+ stream.unget(data);
+ state = beforeDoctypePublicIdentifierState;
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('eof-in-doctype'));
+ currentDoctypeToken.correct = false;
+ _addToken(currentToken!);
+ state = dataState;
+ } else {
+ stream.unget(data);
+ state = beforeDoctypePublicIdentifierState;
+ }
+ return true;
+ }
+
+ bool beforeDoctypePublicIdentifierState() {
+ final data = stream.char();
+ if (isWhitespace(data)) {
+ return true;
+ } else if (data == '"') {
+ currentDoctypeToken.publicId = '';
+ state = doctypePublicIdentifierDoubleQuotedState;
+ } else if (data == "'") {
+ currentDoctypeToken.publicId = '';
+ state = doctypePublicIdentifierSingleQuotedState;
+ } else if (data == '>') {
+ _addToken(ParseErrorToken('unexpected-end-of-doctype'));
+ currentDoctypeToken.correct = false;
+ _addToken(currentToken!);
+ state = dataState;
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('eof-in-doctype'));
+ currentDoctypeToken.correct = false;
+ _addToken(currentToken!);
+ state = dataState;
+ } else {
+ _addToken(ParseErrorToken('unexpected-char-in-doctype'));
+ currentDoctypeToken.correct = false;
+ state = bogusDoctypeState;
+ }
+ return true;
+ }
+
+ bool doctypePublicIdentifierDoubleQuotedState() {
+ final data = stream.char();
+ if (data == '"') {
+ state = afterDoctypePublicIdentifierState;
+ } else if (data == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ currentDoctypeToken.publicId = '${currentDoctypeToken.publicId}\uFFFD';
+ } else if (data == '>') {
+ _addToken(ParseErrorToken('unexpected-end-of-doctype'));
+ currentDoctypeToken.correct = false;
+ _addToken(currentToken!);
+ state = dataState;
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('eof-in-doctype'));
+ currentDoctypeToken.correct = false;
+ _addToken(currentToken!);
+ state = dataState;
+ } else {
+ currentDoctypeToken.publicId = '${currentDoctypeToken.publicId}$data';
+ }
+ return true;
+ }
+
+ bool doctypePublicIdentifierSingleQuotedState() {
+ final data = stream.char();
+ if (data == "'") {
+ state = afterDoctypePublicIdentifierState;
+ } else if (data == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ currentDoctypeToken.publicId = '${currentDoctypeToken.publicId}\uFFFD';
+ } else if (data == '>') {
+ _addToken(ParseErrorToken('unexpected-end-of-doctype'));
+ currentDoctypeToken.correct = false;
+ _addToken(currentToken!);
+ state = dataState;
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('eof-in-doctype'));
+ currentDoctypeToken.correct = false;
+ _addToken(currentToken!);
+ state = dataState;
+ } else {
+ currentDoctypeToken.publicId = '${currentDoctypeToken.publicId}$data';
+ }
+ return true;
+ }
+
+ bool afterDoctypePublicIdentifierState() {
+ final data = stream.char();
+ if (isWhitespace(data)) {
+ state = betweenDoctypePublicAndSystemIdentifiersState;
+ } else if (data == '>') {
+ _addToken(currentToken!);
+ state = dataState;
+ } else if (data == '"') {
+ _addToken(ParseErrorToken('unexpected-char-in-doctype'));
+ currentDoctypeToken.systemId = '';
+ state = doctypeSystemIdentifierDoubleQuotedState;
+ } else if (data == "'") {
+ _addToken(ParseErrorToken('unexpected-char-in-doctype'));
+ currentDoctypeToken.systemId = '';
+ state = doctypeSystemIdentifierSingleQuotedState;
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('eof-in-doctype'));
+ currentDoctypeToken.correct = false;
+ _addToken(currentToken!);
+ state = dataState;
+ } else {
+ _addToken(ParseErrorToken('unexpected-char-in-doctype'));
+ currentDoctypeToken.correct = false;
+ state = bogusDoctypeState;
+ }
+ return true;
+ }
+
+ bool betweenDoctypePublicAndSystemIdentifiersState() {
+ final data = stream.char();
+ if (isWhitespace(data)) {
+ return true;
+ } else if (data == '>') {
+ _addToken(currentToken!);
+ state = dataState;
+ } else if (data == '"') {
+ currentDoctypeToken.systemId = '';
+ state = doctypeSystemIdentifierDoubleQuotedState;
+ } else if (data == "'") {
+ currentDoctypeToken.systemId = '';
+ state = doctypeSystemIdentifierSingleQuotedState;
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('eof-in-doctype'));
+ currentDoctypeToken.correct = false;
+ _addToken(currentToken!);
+ state = dataState;
+ } else {
+ _addToken(ParseErrorToken('unexpected-char-in-doctype'));
+ currentDoctypeToken.correct = false;
+ state = bogusDoctypeState;
+ }
+ return true;
+ }
+
+ bool afterDoctypeSystemKeywordState() {
+ final data = stream.char();
+ if (isWhitespace(data)) {
+ state = beforeDoctypeSystemIdentifierState;
+ } else if (data == "'" || data == '"') {
+ _addToken(ParseErrorToken('unexpected-char-in-doctype'));
+ stream.unget(data);
+ state = beforeDoctypeSystemIdentifierState;
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('eof-in-doctype'));
+ currentDoctypeToken.correct = false;
+ _addToken(currentToken!);
+ state = dataState;
+ } else {
+ stream.unget(data);
+ state = beforeDoctypeSystemIdentifierState;
+ }
+ return true;
+ }
+
+ bool beforeDoctypeSystemIdentifierState() {
+ final data = stream.char();
+ if (isWhitespace(data)) {
+ return true;
+ } else if (data == '"') {
+ currentDoctypeToken.systemId = '';
+ state = doctypeSystemIdentifierDoubleQuotedState;
+ } else if (data == "'") {
+ currentDoctypeToken.systemId = '';
+ state = doctypeSystemIdentifierSingleQuotedState;
+ } else if (data == '>') {
+ _addToken(ParseErrorToken('unexpected-char-in-doctype'));
+ currentDoctypeToken.correct = false;
+ _addToken(currentToken!);
+ state = dataState;
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('eof-in-doctype'));
+ currentDoctypeToken.correct = false;
+ _addToken(currentToken!);
+ state = dataState;
+ } else {
+ _addToken(ParseErrorToken('unexpected-char-in-doctype'));
+ currentDoctypeToken.correct = false;
+ state = bogusDoctypeState;
+ }
+ return true;
+ }
+
+ bool doctypeSystemIdentifierDoubleQuotedState() {
+ final data = stream.char();
+ if (data == '"') {
+ state = afterDoctypeSystemIdentifierState;
+ } else if (data == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ currentDoctypeToken.systemId = '${currentDoctypeToken.systemId}\uFFFD';
+ } else if (data == '>') {
+ _addToken(ParseErrorToken('unexpected-end-of-doctype'));
+ currentDoctypeToken.correct = false;
+ _addToken(currentToken!);
+ state = dataState;
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('eof-in-doctype'));
+ currentDoctypeToken.correct = false;
+ _addToken(currentToken!);
+ state = dataState;
+ } else {
+ currentDoctypeToken.systemId = '${currentDoctypeToken.systemId}$data';
+ }
+ return true;
+ }
+
+ bool doctypeSystemIdentifierSingleQuotedState() {
+ final data = stream.char();
+ if (data == "'") {
+ state = afterDoctypeSystemIdentifierState;
+ } else if (data == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ currentDoctypeToken.systemId = '${currentDoctypeToken.systemId}\uFFFD';
+ } else if (data == '>') {
+ _addToken(ParseErrorToken('unexpected-end-of-doctype'));
+ currentDoctypeToken.correct = false;
+ _addToken(currentToken!);
+ state = dataState;
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('eof-in-doctype'));
+ currentDoctypeToken.correct = false;
+ _addToken(currentToken!);
+ state = dataState;
+ } else {
+ currentDoctypeToken.systemId = '${currentDoctypeToken.systemId}$data';
+ }
+ return true;
+ }
+
+ bool afterDoctypeSystemIdentifierState() {
+ final data = stream.char();
+ if (isWhitespace(data)) {
+ return true;
+ } else if (data == '>') {
+ _addToken(currentToken!);
+ state = dataState;
+ } else if (data == eof) {
+ _addToken(ParseErrorToken('eof-in-doctype'));
+ currentDoctypeToken.correct = false;
+ _addToken(currentToken!);
+ state = dataState;
+ } else {
+ _addToken(ParseErrorToken('unexpected-char-in-doctype'));
+ state = bogusDoctypeState;
+ }
+ return true;
+ }
+
+ bool bogusDoctypeState() {
+ final data = stream.char();
+ if (data == '>') {
+ _addToken(currentToken!);
+ state = dataState;
+ } else if (data == eof) {
+ // XXX EMIT
+ stream.unget(data);
+ _addToken(currentToken!);
+ state = dataState;
+ }
+ return true;
+ }
+
+ bool cdataSectionState() {
+ final data = <String>[];
+ var matchedEnd = 0;
+ while (true) {
+ var ch = stream.char();
+ if (ch == null) {
+ break;
+ }
+ // Deal with null here rather than in the parser
+ if (ch == '\u0000') {
+ _addToken(ParseErrorToken('invalid-codepoint'));
+ ch = '\uFFFD';
+ }
+ data.add(ch);
+ // TODO(jmesserly): it'd be nice if we had an easier way to match the end,
+ // perhaps with a "peek" API.
+ if (ch == ']' && matchedEnd < 2) {
+ matchedEnd++;
+ } else if (ch == '>' && matchedEnd == 2) {
+ // Remove "]]>" from the end.
+ data.removeLast();
+ data.removeLast();
+ data.removeLast();
+ break;
+ } else {
+ matchedEnd = 0;
+ }
+ }
+
+ if (data.isNotEmpty) {
+ _addToken(CharactersToken(data.join()));
+ }
+ state = dataState;
+ return true;
+ }
+}
diff --git a/pkgs/html/lib/src/treebuilder.dart b/pkgs/html/lib/src/treebuilder.dart
new file mode 100644
index 0000000..781f3f6
--- /dev/null
+++ b/pkgs/html/lib/src/treebuilder.dart
@@ -0,0 +1,400 @@
+/// Internals to the tree builders.
+library;
+
+import 'dart:collection';
+
+import 'package:source_span/source_span.dart';
+
+import '../dom.dart';
+import '../parser.dart' show getElementNameTuple;
+import 'constants.dart';
+import 'list_proxy.dart';
+import 'token.dart';
+
+/// Open elements in the formatting category, most recent element last.
+///
+/// `null` is used as the "marker" entry to prevent style from leaking when
+/// entering some elements.
+///
+/// https://html.spec.whatwg.org/multipage/parsing.html#list-of-active-formatting-elements
+class ActiveFormattingElements extends ListProxy<Element?> {
+ /// Push an element into the active formatting elements.
+ ///
+ /// Prevents equivalent elements from appearing more than 3 times following
+ /// the last `null` marker. If adding [node] would cause there to be more than
+ /// 3 equivalent elements the earliest identical element is removed.
+ // TODO - Earliest equivalent following a marker, as opposed to earliest
+ // identical regardless of marker position, should be removed.
+ @override
+ void add(Element? node) {
+ var equalCount = 0;
+ if (node != null) {
+ for (var element in reversed) {
+ if (element == null) {
+ break;
+ }
+ if (_nodesEqual(element, node)) {
+ equalCount += 1;
+ }
+ if (equalCount == 3) {
+ // TODO - https://github.com/dart-lang/html/issues/135
+ remove(element);
+ break;
+ }
+ }
+ }
+ super.add(node);
+ }
+}
+
+// TODO(jmesserly): this should exist in corelib...
+bool _mapEquals(Map<Object, String> a, Map<Object, String> b) {
+ if (a.length != b.length) return false;
+ if (a.isEmpty) return true;
+
+ for (var keyA in a.keys) {
+ final valB = b[keyA];
+ if (valB == null && !b.containsKey(keyA)) {
+ return false;
+ }
+
+ if (a[keyA] != valB) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool _nodesEqual(Element node1, Element node2) {
+ return getElementNameTuple(node1) == getElementNameTuple(node2) &&
+ _mapEquals(node1.attributes, node2.attributes);
+}
+
+/// Basic treebuilder implementation.
+class TreeBuilder {
+ final String? defaultNamespace;
+
+ late Document document;
+
+ final List<Element> openElements = <Element>[];
+
+ final activeFormattingElements = ActiveFormattingElements();
+
+ Node? headPointer;
+
+ Element? formPointer;
+
+ /// Switch the function used to insert an element from the
+ /// normal one to the misnested table one and back again
+ bool insertFromTable = false;
+
+ TreeBuilder(bool namespaceHTMLElements)
+ : defaultNamespace = namespaceHTMLElements ? Namespaces.html : null {
+ reset();
+ }
+
+ void reset() {
+ openElements.clear();
+ activeFormattingElements.clear();
+
+ //XXX - rename these to headElement, formElement
+ headPointer = null;
+ formPointer = null;
+
+ insertFromTable = false;
+
+ document = Document();
+ }
+
+ bool elementInScope(dynamic target, {String? variant}) {
+ // If we pass a node in we match that. If we pass a string match any node
+ // with that name.
+ final exactNode = target is Node;
+
+ var listElements1 = scopingElements;
+ var listElements2 = const <(String, String)>[];
+ var invert = false;
+ if (variant != null) {
+ switch (variant) {
+ case 'button':
+ listElements2 = const [(Namespaces.html, 'button')];
+ break;
+ case 'list':
+ listElements2 = const [
+ (Namespaces.html, 'ol'),
+ (Namespaces.html, 'ul')
+ ];
+ break;
+ case 'table':
+ listElements1 = const [
+ (Namespaces.html, 'html'),
+ (Namespaces.html, 'table')
+ ];
+ break;
+ case 'select':
+ listElements1 = const [
+ (Namespaces.html, 'optgroup'),
+ (Namespaces.html, 'option')
+ ];
+ invert = true;
+ break;
+ default:
+ throw StateError('We should never reach this point');
+ }
+ }
+
+ for (var node in openElements.reversed) {
+ if (!exactNode && node.localName == target ||
+ exactNode && node == target) {
+ return true;
+ } else if (invert !=
+ (listElements1.contains(getElementNameTuple(node)) ||
+ listElements2.contains(getElementNameTuple(node)))) {
+ return false;
+ }
+ }
+
+ throw StateError('We should never reach this point');
+ }
+
+ void reconstructActiveFormattingElements() {
+ // Within this algorithm the order of steps described in the
+ // specification is not quite the same as the order of steps in the
+ // code. It should still do the same though.
+
+ // Step 1: stop the algorithm when there's nothing to do.
+ if (activeFormattingElements.isEmpty) {
+ return;
+ }
+
+ // Step 2 and step 3: we start with the last element. So i is -1.
+ var i = activeFormattingElements.length - 1;
+ var entry = activeFormattingElements[i];
+ if (entry == null || openElements.contains(entry)) {
+ return;
+ }
+
+ // Step 6
+ while (entry != null && !openElements.contains(entry)) {
+ if (i == 0) {
+ //This will be reset to 0 below
+ i = -1;
+ break;
+ }
+ i -= 1;
+ // Step 5: let entry be one earlier in the list.
+ entry = activeFormattingElements[i];
+ }
+
+ while (true) {
+ // Step 7
+ i += 1;
+
+ // Step 8
+ entry = activeFormattingElements[i];
+
+ // TODO(jmesserly): optimize this. No need to create a token.
+ final cloneToken = StartTagToken(entry!.localName,
+ namespace: entry.namespaceUri,
+ data: LinkedHashMap.from(entry.attributes))
+ ..span = entry.sourceSpan;
+
+ // Step 9
+ final element = insertElement(cloneToken);
+
+ // Step 10
+ activeFormattingElements[i] = element;
+
+ // Step 11
+ if (element == activeFormattingElements.last) {
+ break;
+ }
+ }
+ }
+
+ void clearActiveFormattingElements() {
+ var entry = activeFormattingElements.removeLast();
+ while (activeFormattingElements.isNotEmpty && entry != null) {
+ entry = activeFormattingElements.removeLast();
+ }
+ }
+
+ /// Check if an element exists between the end of the active
+ /// formatting elements and the last marker. If it does, return it, else
+ /// return null.
+ Element? elementInActiveFormattingElements(String? name) {
+ for (var item in activeFormattingElements.reversed) {
+ // Check for Marker first because if it's a Marker it doesn't have a
+ // name attribute.
+ if (item == null) {
+ break;
+ } else if (item.localName == name) {
+ return item;
+ }
+ }
+ return null;
+ }
+
+ void insertRoot(StartTagToken token) {
+ final element = createElement(token);
+ openElements.add(element);
+ document.nodes.add(element);
+ }
+
+ void insertDoctype(DoctypeToken token) {
+ final doctype = DocumentType(token.name, token.publicId, token.systemId)
+ ..sourceSpan = token.span;
+ document.nodes.add(doctype);
+ }
+
+ void insertComment(StringToken token, [Node? parent]) {
+ parent ??= openElements.last;
+ parent.nodes.add(Comment(token.data)..sourceSpan = token.span);
+ }
+
+ /// Create an element but don't insert it anywhere
+ Element createElement(StartTagToken token) {
+ final name = token.name;
+ final namespace = token.namespace ?? defaultNamespace;
+ final element = document.createElementNS(namespace, name)
+ ..attributes = token.data
+ ..sourceSpan = token.span;
+ return element;
+ }
+
+ Element insertElement(StartTagToken token) {
+ if (insertFromTable) return insertElementTable(token);
+ return insertElementNormal(token);
+ }
+
+ Element insertElementNormal(StartTagToken token) {
+ final name = token.name;
+ final namespace = token.namespace ?? defaultNamespace;
+ final element = document.createElementNS(namespace, name)
+ ..attributes = token.data
+ ..sourceSpan = token.span;
+ openElements.last.nodes.add(element);
+ openElements.add(element);
+ return element;
+ }
+
+ Element insertElementTable(StartTagToken token) {
+ /// Create an element and insert it into the tree
+ final element = createElement(token);
+ if (!tableInsertModeElements.contains(openElements.last.localName)) {
+ return insertElementNormal(token);
+ } else {
+ // We should be in the InTable mode. This means we want to do
+ // special magic element rearranging
+ final nodePos = getTableMisnestedNodePosition();
+ if (nodePos[1] == null) {
+ // TODO(jmesserly): I don't think this is reachable. If insertFromTable
+ // is true, there will be a <table> element open, and it always has a
+ // parent pointer.
+ nodePos[0]!.nodes.add(element);
+ } else {
+ nodePos[0]!.insertBefore(element, nodePos[1]);
+ }
+ openElements.add(element);
+ }
+ return element;
+ }
+
+ /// Insert text data.
+ void insertText(String data, FileSpan? span) {
+ final parent = openElements.last;
+
+ if (!insertFromTable ||
+ insertFromTable &&
+ !tableInsertModeElements.contains(openElements.last.localName)) {
+ _insertText(parent, data, span);
+ } else {
+ // We should be in the InTable mode. This means we want to do
+ // special magic element rearranging
+ final nodePos = getTableMisnestedNodePosition();
+ _insertText(nodePos[0]!, data, span, nodePos[1] as Element?);
+ }
+ }
+
+ /// Insert [data] as text in the current node, positioned before the
+ /// start of node [refNode] or to the end of the node's text.
+ static void _insertText(Node parent, String data, FileSpan? span,
+ [Element? refNode]) {
+ final nodes = parent.nodes;
+ if (refNode == null) {
+ if (nodes.isNotEmpty && nodes.last is Text) {
+ final last = nodes.last as Text;
+ last.appendData(data);
+
+ if (span != null) {
+ last.sourceSpan =
+ span.file.span(last.sourceSpan!.start.offset, span.end.offset);
+ }
+ } else {
+ nodes.add(Text(data)..sourceSpan = span);
+ }
+ } else {
+ final index = nodes.indexOf(refNode);
+ if (index > 0 && nodes[index - 1] is Text) {
+ final last = nodes[index - 1] as Text;
+ last.appendData(data);
+ } else {
+ nodes.insert(index, Text(data)..sourceSpan = span);
+ }
+ }
+ }
+
+ /// Get the foster parent element, and sibling to insert before
+ /// (or null) when inserting a misnested table node
+ List<Node?> getTableMisnestedNodePosition() {
+ // The foster parent element is the one which comes before the most
+ // recently opened table element
+ // XXX - this is really inelegant
+ Element? lastTable;
+ Node? fosterParent;
+ Node? insertBefore;
+ for (var elm in openElements.reversed) {
+ if (elm.localName == 'table') {
+ lastTable = elm;
+ break;
+ }
+ }
+ if (lastTable != null) {
+ // XXX - we should really check that this parent is actually a
+ // node here
+ if (lastTable.parentNode != null) {
+ fosterParent = lastTable.parentNode;
+ insertBefore = lastTable;
+ } else {
+ fosterParent = openElements[openElements.indexOf(lastTable) - 1];
+ }
+ } else {
+ fosterParent = openElements[0];
+ }
+ return [fosterParent, insertBefore];
+ }
+
+ void generateImpliedEndTags([String? exclude]) {
+ final name = openElements.last.localName;
+ // XXX td, th and tr are not actually needed
+ if (name != exclude &&
+ const ['dd', 'dt', 'li', 'option', 'optgroup', 'p', 'rp', 'rt']
+ .contains(name)) {
+ openElements.removeLast();
+ // XXX This is not entirely what the specification says. We should
+ // investigate it more closely.
+ generateImpliedEndTags(exclude);
+ }
+ }
+
+ /// Return the final tree.
+ Document getDocument() => document;
+
+ /// Return the final fragment.
+ DocumentFragment getFragment() {
+ //XXX assert innerHTML
+ final fragment = DocumentFragment();
+ openElements[0].reparentChildren(fragment);
+ return fragment;
+ }
+}
diff --git a/pkgs/html/lib/src/utils.dart b/pkgs/html/lib/src/utils.dart
new file mode 100644
index 0000000..4f4bd1a
--- /dev/null
+++ b/pkgs/html/lib/src/utils.dart
@@ -0,0 +1,85 @@
+import 'constants.dart';
+
+bool startsWithAny(String str, List<String> prefixes) =>
+ prefixes.any(str.startsWith);
+
+// Like the python [:] operator.
+List<T> slice<T>(List<T> list, int start, [int? end]) {
+ end ??= list.length;
+ if (end < 0) end += list.length;
+
+ // Ensure the indexes are in bounds.
+ if (end < start) end = start;
+ if (end > list.length) end = list.length;
+ return list.sublist(start, end);
+}
+
+bool allWhitespace(String str) {
+ for (var i = 0; i < str.length; i++) {
+ if (!isWhitespaceCC(str.codeUnitAt(i))) return false;
+ }
+ return true;
+}
+
+String padWithZeros(String str, int size) {
+ if (str.length == size) return str;
+ final result = StringBuffer();
+ size -= str.length;
+ for (var i = 0; i < size; i++) {
+ result.write('0');
+ }
+ result.write(str);
+ return result.toString();
+}
+
+// TODO(jmesserly): this implementation is pretty wrong, but I need something
+// quick until dartbug.com/1694 is fixed.
+/// Format a string like Python's % string format operator. Right now this only
+/// supports a [data] dictionary used with %s or %08x. Those were the only
+/// things needed for [errorMessages].
+String formatStr(String format, Map<String, Object?>? data) {
+ if (data == null) return format;
+ data.forEach((key, value) {
+ final result = StringBuffer();
+ final search = '%($key)';
+ int last = 0, match;
+ while ((match = format.indexOf(search, last)) >= 0) {
+ result.write(format.substring(last, match));
+ match += search.length;
+
+ var digits = match;
+ while (isDigit(format[digits])) {
+ digits++;
+ }
+ var numberSize = 0;
+ if (digits > match) {
+ numberSize = int.parse(format.substring(match, digits));
+ match = digits;
+ }
+
+ switch (format[match]) {
+ case 's':
+ result.write(value);
+ break;
+ case 'd':
+ final number = value.toString();
+ result.write(padWithZeros(number, numberSize));
+ break;
+ case 'x':
+ final number = (value as int).toRadixString(16);
+ result.write(padWithZeros(number, numberSize));
+ break;
+ default:
+ throw UnsupportedError('formatStr does not support format '
+ 'character ${format[match]}');
+ }
+
+ last = match + 1;
+ }
+
+ result.write(format.substring(last, format.length));
+ format = result.toString();
+ });
+
+ return format;
+}
diff --git a/pkgs/html/pubspec.yaml b/pkgs/html/pubspec.yaml
new file mode 100644
index 0000000..447b98e
--- /dev/null
+++ b/pkgs/html/pubspec.yaml
@@ -0,0 +1,21 @@
+name: html
+version: 0.15.5
+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
+
+topics:
+ - html
+ - web
+
+environment:
+ sdk: ^3.2.0
+
+dependencies:
+ csslib: ^1.0.0
+ source_span: ^1.8.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ path: ^1.8.0
+ test: ^1.16.6
diff --git a/pkgs/html/test/data/tokenizer/contentModelFlags.test b/pkgs/html/test/data/tokenizer/contentModelFlags.test
new file mode 100644
index 0000000..a8b1695
--- /dev/null
+++ b/pkgs/html/test/data/tokenizer/contentModelFlags.test
@@ -0,0 +1,75 @@
+{"tests": [
+
+{"description":"PLAINTEXT content model flag",
+"initialStates":["PLAINTEXT state"],
+"lastStartTag":"plaintext",
+"input":"<head>&body;",
+"output":[["Character", "<head>&body;"]]},
+
+{"description":"End tag closing RCDATA or RAWTEXT",
+"initialStates":["RCDATA state", "RAWTEXT state"],
+"lastStartTag":"xmp",
+"input":"foo</xmp>",
+"output":[["Character", "foo"], ["EndTag", "xmp"]]},
+
+{"description":"End tag closing RCDATA or RAWTEXT (case-insensitivity)",
+"initialStates":["RCDATA state", "RAWTEXT state"],
+"lastStartTag":"xmp",
+"input":"foo</xMp>",
+"output":[["Character", "foo"], ["EndTag", "xmp"]]},
+
+{"description":"End tag closing RCDATA or RAWTEXT (ending with space)",
+"initialStates":["RCDATA state", "RAWTEXT state"],
+"lastStartTag":"xmp",
+"input":"foo</xmp ",
+"output":[["Character", "foo"], "ParseError"]},
+
+{"description":"End tag closing RCDATA or RAWTEXT (ending with EOF)",
+"initialStates":["RCDATA state", "RAWTEXT state"],
+"lastStartTag":"xmp",
+"input":"foo</xmp",
+"output":[["Character", "foo</xmp"]]},
+
+{"description":"End tag closing RCDATA or RAWTEXT (ending with slash)",
+"initialStates":["RCDATA state", "RAWTEXT state"],
+"lastStartTag":"xmp",
+"input":"foo</xmp/",
+"output":[["Character", "foo"], "ParseError"]},
+
+{"description":"End tag not closing RCDATA or RAWTEXT (ending with left-angle-bracket)",
+"initialStates":["RCDATA state", "RAWTEXT state"],
+"lastStartTag":"xmp",
+"input":"foo</xmp<",
+"output":[["Character", "foo</xmp<"]]},
+
+{"description":"End tag with incorrect name in RCDATA or RAWTEXT",
+"initialStates":["RCDATA state", "RAWTEXT state"],
+"lastStartTag":"xmp",
+"input":"</foo>bar</xmp>",
+"output":[["Character", "</foo>bar"], ["EndTag", "xmp"]]},
+
+{"description":"End tag with incorrect name in RCDATA or RAWTEXT (starting like correct name)",
+"initialStates":["RCDATA state", "RAWTEXT state"],
+"lastStartTag":"xmp",
+"input":"</foo>bar</xmpaar>",
+"output":[["Character", "</foo>bar</xmpaar>"]]},
+
+{"description":"End tag closing RCDATA or RAWTEXT, switching back to PCDATA",
+"initialStates":["RCDATA state", "RAWTEXT state"],
+"lastStartTag":"xmp",
+"input":"foo</xmp></baz>",
+"output":[["Character", "foo"], ["EndTag", "xmp"], ["EndTag", "baz"]]},
+
+{"description":"RAWTEXT w/ something looking like an entity",
+"initialStates":["RAWTEXT state"],
+"lastStartTag":"xmp",
+"input":"&foo;",
+"output":[["Character", "&foo;"]]},
+
+{"description":"RCDATA w/ an entity",
+"initialStates":["RCDATA state"],
+"lastStartTag":"textarea",
+"input":"<",
+"output":[["Character", "<"]]}
+
+]}
diff --git a/pkgs/html/test/data/tokenizer/domjs.test b/pkgs/html/test/data/tokenizer/domjs.test
new file mode 100644
index 0000000..74771e2
--- /dev/null
+++ b/pkgs/html/test/data/tokenizer/domjs.test
@@ -0,0 +1,90 @@
+{
+ "tests": [
+ {
+ "description":"CR in bogus comment state",
+ "input":"<?\u000d",
+ "output":["ParseError", ["Comment", "?\u000a"]]
+ },
+ {
+ "description":"CRLF in bogus comment state",
+ "input":"<?\u000d\u000a",
+ "output":["ParseError", ["Comment", "?\u000a"]]
+ },
+ {
+ "description":"NUL in RCDATA and RAWTEXT",
+ "doubleEscaped":true,
+ "initialStates":["RCDATA state", "RAWTEXT state"],
+ "input":"\\u0000",
+ "output":["ParseError", ["Character", "\\uFFFD"]]
+ },
+ {
+ "description":"skip first BOM but not later ones",
+ "input":"\uFEFFfoo\uFEFFbar",
+ "output":[["Character", "foo\uFEFFbar"]]
+ },
+ {
+ "description":"Non BMP-charref in in RCDATA",
+ "initialStates":["RCDATA state"],
+ "input":"≂̸",
+ "output":[["Character", "\u2242\u0338"]]
+ },
+ {
+ "description":"Bad charref in in RCDATA",
+ "initialStates":["RCDATA state"],
+ "input":"&NotEqualTild;",
+ "output":["ParseError", ["Character", "&NotEqualTild;"]]
+ },
+ {
+ "description":"lowercase endtags in RCDATA and RAWTEXT",
+ "initialStates":["RCDATA state", "RAWTEXT state"],
+ "lastStartTag":"xmp",
+ "input":"</XMP>",
+ "output":[["EndTag","xmp"]]
+ },
+ {
+ "description":"bad endtag in RCDATA and RAWTEXT",
+ "initialStates":["RCDATA state", "RAWTEXT state"],
+ "lastStartTag":"xmp",
+ "input":"</ XMP>",
+ "output":[["Character","</ XMP>"]]
+ },
+ {
+ "description":"bad endtag in RCDATA and RAWTEXT",
+ "initialStates":["RCDATA state", "RAWTEXT state"],
+ "lastStartTag":"xmp",
+ "input":"</xm>",
+ "output":[["Character","</xm>"]]
+ },
+ {
+ "description":"bad endtag in RCDATA and RAWTEXT",
+ "initialStates":["RCDATA state", "RAWTEXT state"],
+ "lastStartTag":"xmp",
+ "input":"</xm ",
+ "output":[["Character","</xm "]]
+ },
+ {
+ "description":"bad endtag in RCDATA and RAWTEXT",
+ "initialStates":["RCDATA state", "RAWTEXT state"],
+ "lastStartTag":"xmp",
+ "input":"</xm/",
+ "output":[["Character","</xm/"]]
+ },
+ {
+ "description":"Non BMP-charref in attribute",
+ "input":"<p id=\"≂̸\">",
+ "output":[["StartTag", "p", {"id":"\u2242\u0338"}]]
+ },
+ {
+ "description":"--!NUL in comment ",
+ "doubleEscaped":true,
+ "input":"<!----!\\u0000-->",
+ "output":["ParseError", ["Comment", "--!\\uFFFD"]]
+ },
+ {
+ "description":"space EOF after doctype ",
+ "input":"<!DOCTYPE html ",
+ "output":["ParseError", ["DOCTYPE", "html", null, null , false]]
+ }
+
+ ]
+}
diff --git a/pkgs/html/test/data/tokenizer/entities.test b/pkgs/html/test/data/tokenizer/entities.test
new file mode 100644
index 0000000..1cb17a7
--- /dev/null
+++ b/pkgs/html/test/data/tokenizer/entities.test
@@ -0,0 +1,283 @@
+{"tests": [
+
+{"description": "Undefined named entity in attribute value ending in semicolon and whose name starts with a known entity name.",
+"input":"<h a='¬i;'>",
+"output": ["ParseError", ["StartTag", "h", {"a": "¬i;"}]]},
+
+{"description": "Entity name followed by the equals sign in an attribute value.",
+"input":"<h a='&lang='>",
+"output": ["ParseError", ["StartTag", "h", {"a": "&lang="}]]},
+
+{"description": "CR as numeric entity",
+"input":"
",
+"output": ["ParseError", ["Character", "\r"]]},
+
+{"description": "CR as hexadecimal numeric entity",
+"input":"
",
+"output": ["ParseError", ["Character", "\r"]]},
+
+{"description": "Windows-1252 EURO SIGN numeric entity.",
+"input":"€",
+"output": ["ParseError", ["Character", "\u20AC"]]},
+
+{"description": "Windows-1252 REPLACEMENT CHAR numeric entity.",
+"input":"",
+"output": ["ParseError", ["Character", "\u0081"]]},
+
+{"description": "Windows-1252 SINGLE LOW-9 QUOTATION MARK numeric entity.",
+"input":"‚",
+"output": ["ParseError", ["Character", "\u201A"]]},
+
+{"description": "Windows-1252 LATIN SMALL LETTER F WITH HOOK numeric entity.",
+"input":"ƒ",
+"output": ["ParseError", ["Character", "\u0192"]]},
+
+{"description": "Windows-1252 DOUBLE LOW-9 QUOTATION MARK numeric entity.",
+"input":"„",
+"output": ["ParseError", ["Character", "\u201E"]]},
+
+{"description": "Windows-1252 HORIZONTAL ELLIPSIS numeric entity.",
+"input":"…",
+"output": ["ParseError", ["Character", "\u2026"]]},
+
+{"description": "Windows-1252 DAGGER numeric entity.",
+"input":"†",
+"output": ["ParseError", ["Character", "\u2020"]]},
+
+{"description": "Windows-1252 DOUBLE DAGGER numeric entity.",
+"input":"‡",
+"output": ["ParseError", ["Character", "\u2021"]]},
+
+{"description": "Windows-1252 MODIFIER LETTER CIRCUMFLEX ACCENT numeric entity.",
+"input":"ˆ",
+"output": ["ParseError", ["Character", "\u02C6"]]},
+
+{"description": "Windows-1252 PER MILLE SIGN numeric entity.",
+"input":"‰",
+"output": ["ParseError", ["Character", "\u2030"]]},
+
+{"description": "Windows-1252 LATIN CAPITAL LETTER S WITH CARON numeric entity.",
+"input":"Š",
+"output": ["ParseError", ["Character", "\u0160"]]},
+
+{"description": "Windows-1252 SINGLE LEFT-POINTING ANGLE QUOTATION MARK numeric entity.",
+"input":"‹",
+"output": ["ParseError", ["Character", "\u2039"]]},
+
+{"description": "Windows-1252 LATIN CAPITAL LIGATURE OE numeric entity.",
+"input":"Œ",
+"output": ["ParseError", ["Character", "\u0152"]]},
+
+{"description": "Windows-1252 REPLACEMENT CHAR numeric entity.",
+"input":"",
+"output": ["ParseError", ["Character", "\u008D"]]},
+
+{"description": "Windows-1252 LATIN CAPITAL LETTER Z WITH CARON numeric entity.",
+"input":"Ž",
+"output": ["ParseError", ["Character", "\u017D"]]},
+
+{"description": "Windows-1252 REPLACEMENT CHAR numeric entity.",
+"input":"",
+"output": ["ParseError", ["Character", "\u008F"]]},
+
+{"description": "Windows-1252 REPLACEMENT CHAR numeric entity.",
+"input":"",
+"output": ["ParseError", ["Character", "\u0090"]]},
+
+{"description": "Windows-1252 LEFT SINGLE QUOTATION MARK numeric entity.",
+"input":"‘",
+"output": ["ParseError", ["Character", "\u2018"]]},
+
+{"description": "Windows-1252 RIGHT SINGLE QUOTATION MARK numeric entity.",
+"input":"’",
+"output": ["ParseError", ["Character", "\u2019"]]},
+
+{"description": "Windows-1252 LEFT DOUBLE QUOTATION MARK numeric entity.",
+"input":"“",
+"output": ["ParseError", ["Character", "\u201C"]]},
+
+{"description": "Windows-1252 RIGHT DOUBLE QUOTATION MARK numeric entity.",
+"input":"”",
+"output": ["ParseError", ["Character", "\u201D"]]},
+
+{"description": "Windows-1252 BULLET numeric entity.",
+"input":"•",
+"output": ["ParseError", ["Character", "\u2022"]]},
+
+{"description": "Windows-1252 EN DASH numeric entity.",
+"input":"–",
+"output": ["ParseError", ["Character", "\u2013"]]},
+
+{"description": "Windows-1252 EM DASH numeric entity.",
+"input":"—",
+"output": ["ParseError", ["Character", "\u2014"]]},
+
+{"description": "Windows-1252 SMALL TILDE numeric entity.",
+"input":"˜",
+"output": ["ParseError", ["Character", "\u02DC"]]},
+
+{"description": "Windows-1252 TRADE MARK SIGN numeric entity.",
+"input":"™",
+"output": ["ParseError", ["Character", "\u2122"]]},
+
+{"description": "Windows-1252 LATIN SMALL LETTER S WITH CARON numeric entity.",
+"input":"š",
+"output": ["ParseError", ["Character", "\u0161"]]},
+
+{"description": "Windows-1252 SINGLE RIGHT-POINTING ANGLE QUOTATION MARK numeric entity.",
+"input":"›",
+"output": ["ParseError", ["Character", "\u203A"]]},
+
+{"description": "Windows-1252 LATIN SMALL LIGATURE OE numeric entity.",
+"input":"œ",
+"output": ["ParseError", ["Character", "\u0153"]]},
+
+{"description": "Windows-1252 REPLACEMENT CHAR numeric entity.",
+"input":"",
+"output": ["ParseError", ["Character", "\u009D"]]},
+
+{"description": "Windows-1252 EURO SIGN hexadecimal numeric entity.",
+"input":"€",
+"output": ["ParseError", ["Character", "\u20AC"]]},
+
+{"description": "Windows-1252 REPLACEMENT CHAR hexadecimal numeric entity.",
+"input":"",
+"output": ["ParseError", ["Character", "\u0081"]]},
+
+{"description": "Windows-1252 SINGLE LOW-9 QUOTATION MARK hexadecimal numeric entity.",
+"input":"‚",
+"output": ["ParseError", ["Character", "\u201A"]]},
+
+{"description": "Windows-1252 LATIN SMALL LETTER F WITH HOOK hexadecimal numeric entity.",
+"input":"ƒ",
+"output": ["ParseError", ["Character", "\u0192"]]},
+
+{"description": "Windows-1252 DOUBLE LOW-9 QUOTATION MARK hexadecimal numeric entity.",
+"input":"„",
+"output": ["ParseError", ["Character", "\u201E"]]},
+
+{"description": "Windows-1252 HORIZONTAL ELLIPSIS hexadecimal numeric entity.",
+"input":"…",
+"output": ["ParseError", ["Character", "\u2026"]]},
+
+{"description": "Windows-1252 DAGGER hexadecimal numeric entity.",
+"input":"†",
+"output": ["ParseError", ["Character", "\u2020"]]},
+
+{"description": "Windows-1252 DOUBLE DAGGER hexadecimal numeric entity.",
+"input":"‡",
+"output": ["ParseError", ["Character", "\u2021"]]},
+
+{"description": "Windows-1252 MODIFIER LETTER CIRCUMFLEX ACCENT hexadecimal numeric entity.",
+"input":"ˆ",
+"output": ["ParseError", ["Character", "\u02C6"]]},
+
+{"description": "Windows-1252 PER MILLE SIGN hexadecimal numeric entity.",
+"input":"‰",
+"output": ["ParseError", ["Character", "\u2030"]]},
+
+{"description": "Windows-1252 LATIN CAPITAL LETTER S WITH CARON hexadecimal numeric entity.",
+"input":"Š",
+"output": ["ParseError", ["Character", "\u0160"]]},
+
+{"description": "Windows-1252 SINGLE LEFT-POINTING ANGLE QUOTATION MARK hexadecimal numeric entity.",
+"input":"‹",
+"output": ["ParseError", ["Character", "\u2039"]]},
+
+{"description": "Windows-1252 LATIN CAPITAL LIGATURE OE hexadecimal numeric entity.",
+"input":"Œ",
+"output": ["ParseError", ["Character", "\u0152"]]},
+
+{"description": "Windows-1252 REPLACEMENT CHAR hexadecimal numeric entity.",
+"input":"",
+"output": ["ParseError", ["Character", "\u008D"]]},
+
+{"description": "Windows-1252 LATIN CAPITAL LETTER Z WITH CARON hexadecimal numeric entity.",
+"input":"Ž",
+"output": ["ParseError", ["Character", "\u017D"]]},
+
+{"description": "Windows-1252 REPLACEMENT CHAR hexadecimal numeric entity.",
+"input":"",
+"output": ["ParseError", ["Character", "\u008F"]]},
+
+{"description": "Windows-1252 REPLACEMENT CHAR hexadecimal numeric entity.",
+"input":"",
+"output": ["ParseError", ["Character", "\u0090"]]},
+
+{"description": "Windows-1252 LEFT SINGLE QUOTATION MARK hexadecimal numeric entity.",
+"input":"‘",
+"output": ["ParseError", ["Character", "\u2018"]]},
+
+{"description": "Windows-1252 RIGHT SINGLE QUOTATION MARK hexadecimal numeric entity.",
+"input":"’",
+"output": ["ParseError", ["Character", "\u2019"]]},
+
+{"description": "Windows-1252 LEFT DOUBLE QUOTATION MARK hexadecimal numeric entity.",
+"input":"“",
+"output": ["ParseError", ["Character", "\u201C"]]},
+
+{"description": "Windows-1252 RIGHT DOUBLE QUOTATION MARK hexadecimal numeric entity.",
+"input":"”",
+"output": ["ParseError", ["Character", "\u201D"]]},
+
+{"description": "Windows-1252 BULLET hexadecimal numeric entity.",
+"input":"•",
+"output": ["ParseError", ["Character", "\u2022"]]},
+
+{"description": "Windows-1252 EN DASH hexadecimal numeric entity.",
+"input":"–",
+"output": ["ParseError", ["Character", "\u2013"]]},
+
+{"description": "Windows-1252 EM DASH hexadecimal numeric entity.",
+"input":"—",
+"output": ["ParseError", ["Character", "\u2014"]]},
+
+{"description": "Windows-1252 SMALL TILDE hexadecimal numeric entity.",
+"input":"˜",
+"output": ["ParseError", ["Character", "\u02DC"]]},
+
+{"description": "Windows-1252 TRADE MARK SIGN hexadecimal numeric entity.",
+"input":"™",
+"output": ["ParseError", ["Character", "\u2122"]]},
+
+{"description": "Windows-1252 LATIN SMALL LETTER S WITH CARON hexadecimal numeric entity.",
+"input":"š",
+"output": ["ParseError", ["Character", "\u0161"]]},
+
+{"description": "Windows-1252 SINGLE RIGHT-POINTING ANGLE QUOTATION MARK hexadecimal numeric entity.",
+"input":"›",
+"output": ["ParseError", ["Character", "\u203A"]]},
+
+{"description": "Windows-1252 LATIN SMALL LIGATURE OE hexadecimal numeric entity.",
+"input":"œ",
+"output": ["ParseError", ["Character", "\u0153"]]},
+
+{"description": "Windows-1252 REPLACEMENT CHAR hexadecimal numeric entity.",
+"input":"",
+"output": ["ParseError", ["Character", "\u009D"]]},
+
+{"description": "Windows-1252 LATIN SMALL LETTER Z WITH CARON hexadecimal numeric entity.",
+"input":"ž",
+"output": ["ParseError", ["Character", "\u017E"]]},
+
+{"description": "Windows-1252 LATIN CAPITAL LETTER Y WITH DIAERESIS hexadecimal numeric entity.",
+"input":"Ÿ",
+"output": ["ParseError", ["Character", "\u0178"]]},
+
+{"description": "Decimal numeric entity followed by hex character a.",
+"input":"aa",
+"output": ["ParseError", ["Character", "aa"]]},
+
+{"description": "Decimal numeric entity followed by hex character A.",
+"input":"aA",
+"output": ["ParseError", ["Character", "aA"]]},
+
+{"description": "Decimal numeric entity followed by hex character f.",
+"input":"af",
+"output": ["ParseError", ["Character", "af"]]},
+
+{"description": "Decimal numeric entity followed by hex character A.",
+"input":"aF",
+"output": ["ParseError", ["Character", "aF"]]}
+
+]}
diff --git a/pkgs/html/test/data/tokenizer/escapeFlag.test b/pkgs/html/test/data/tokenizer/escapeFlag.test
new file mode 100644
index 0000000..18cb430
--- /dev/null
+++ b/pkgs/html/test/data/tokenizer/escapeFlag.test
@@ -0,0 +1,33 @@
+{"tests": [
+
+{"description":"Commented close tag in RCDATA or RAWTEXT",
+"initialStates":["RCDATA state", "RAWTEXT state"],
+"lastStartTag":"xmp",
+"input":"foo<!--</xmp>--></xmp>",
+"output":[["Character", "foo<!--"], ["EndTag", "xmp"], ["Character", "-->"], ["EndTag", "xmp"]]},
+
+{"description":"Bogus comment in RCDATA or RAWTEXT",
+"initialStates":["RCDATA state", "RAWTEXT state"],
+"lastStartTag":"xmp",
+"input":"foo<!-->baz</xmp>",
+"output":[["Character", "foo<!-->baz"], ["EndTag", "xmp"]]},
+
+{"description":"End tag surrounded by bogus comment in RCDATA or RAWTEXT",
+"initialStates":["RCDATA state", "RAWTEXT state"],
+"lastStartTag":"xmp",
+"input":"foo<!--></xmp><!-->baz</xmp>",
+"output":[["Character", "foo<!-->"], ["EndTag", "xmp"], "ParseError", ["Comment", ""], ["Character", "baz"], ["EndTag", "xmp"]]},
+
+{"description":"Commented entities in RCDATA",
+"initialStates":["RCDATA state"],
+"lastStartTag":"xmp",
+"input":" & <!-- & --> & </xmp>",
+"output":[["Character", " & <!-- & --> & "], ["EndTag", "xmp"]]},
+
+{"description":"Incorrect comment ending sequences in RCDATA or RAWTEXT",
+"initialStates":["RCDATA state", "RAWTEXT state"],
+"lastStartTag":"xmp",
+"input":"foo<!-- x --x>x-- >x--!>x--<></xmp>",
+"output":[["Character", "foo<!-- x --x>x-- >x--!>x--<>"], ["EndTag", "xmp"]]}
+
+]}
diff --git a/pkgs/html/test/data/tokenizer/namedEntities.test b/pkgs/html/test/data/tokenizer/namedEntities.test
new file mode 100644
index 0000000..4a51c9c
--- /dev/null
+++ b/pkgs/html/test/data/tokenizer/namedEntities.test
@@ -0,0 +1,44189 @@
+{
+ "tests": [
+ {
+ "input": "Æ",
+ "description": "Named entity: AElig without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00c6"
+ ]
+ ]
+ },
+ {
+ "input": "Æ",
+ "description": "Named entity: AElig; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00c6"
+ ]
+ ]
+ },
+ {
+ "input": "&",
+ "description": "Named entity: AMP without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&"
+ ]
+ ]
+ },
+ {
+ "input": "&",
+ "description": "Named entity: AMP; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "&"
+ ]
+ ]
+ },
+ {
+ "input": "Á",
+ "description": "Named entity: Aacute without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00c1"
+ ]
+ ]
+ },
+ {
+ "input": "Á",
+ "description": "Named entity: Aacute; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00c1"
+ ]
+ ]
+ },
+ {
+ "input": "&Abreve",
+ "description": "Bad named entity: Abreve without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Abreve"
+ ]
+ ]
+ },
+ {
+ "input": "Ă",
+ "description": "Named entity: Abreve; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0102"
+ ]
+ ]
+ },
+ {
+ "input": "Â",
+ "description": "Named entity: Acirc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00c2"
+ ]
+ ]
+ },
+ {
+ "input": "Â",
+ "description": "Named entity: Acirc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00c2"
+ ]
+ ]
+ },
+ {
+ "input": "&Acy",
+ "description": "Bad named entity: Acy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Acy"
+ ]
+ ]
+ },
+ {
+ "input": "А",
+ "description": "Named entity: Acy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0410"
+ ]
+ ]
+ },
+ {
+ "input": "&Afr",
+ "description": "Bad named entity: Afr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Afr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔄",
+ "description": "Named entity: Afr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd04"
+ ]
+ ]
+ },
+ {
+ "input": "À",
+ "description": "Named entity: Agrave without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00c0"
+ ]
+ ]
+ },
+ {
+ "input": "À",
+ "description": "Named entity: Agrave; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00c0"
+ ]
+ ]
+ },
+ {
+ "input": "&Alpha",
+ "description": "Bad named entity: Alpha without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Alpha"
+ ]
+ ]
+ },
+ {
+ "input": "Α",
+ "description": "Named entity: Alpha; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0391"
+ ]
+ ]
+ },
+ {
+ "input": "&Amacr",
+ "description": "Bad named entity: Amacr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Amacr"
+ ]
+ ]
+ },
+ {
+ "input": "Ā",
+ "description": "Named entity: Amacr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0100"
+ ]
+ ]
+ },
+ {
+ "input": "&And",
+ "description": "Bad named entity: And without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&And"
+ ]
+ ]
+ },
+ {
+ "input": "⩓",
+ "description": "Named entity: And; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a53"
+ ]
+ ]
+ },
+ {
+ "input": "&Aogon",
+ "description": "Bad named entity: Aogon without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Aogon"
+ ]
+ ]
+ },
+ {
+ "input": "Ą",
+ "description": "Named entity: Aogon; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0104"
+ ]
+ ]
+ },
+ {
+ "input": "&Aopf",
+ "description": "Bad named entity: Aopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Aopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝔸",
+ "description": "Named entity: Aopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd38"
+ ]
+ ]
+ },
+ {
+ "input": "&ApplyFunction",
+ "description": "Bad named entity: ApplyFunction without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ApplyFunction"
+ ]
+ ]
+ },
+ {
+ "input": "⁡",
+ "description": "Named entity: ApplyFunction; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2061"
+ ]
+ ]
+ },
+ {
+ "input": "Å",
+ "description": "Named entity: Aring without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00c5"
+ ]
+ ]
+ },
+ {
+ "input": "Å",
+ "description": "Named entity: Aring; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00c5"
+ ]
+ ]
+ },
+ {
+ "input": "&Ascr",
+ "description": "Bad named entity: Ascr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Ascr"
+ ]
+ ]
+ },
+ {
+ "input": "𝒜",
+ "description": "Named entity: Ascr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udc9c"
+ ]
+ ]
+ },
+ {
+ "input": "&Assign",
+ "description": "Bad named entity: Assign without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Assign"
+ ]
+ ]
+ },
+ {
+ "input": "≔",
+ "description": "Named entity: Assign; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2254"
+ ]
+ ]
+ },
+ {
+ "input": "Ã",
+ "description": "Named entity: Atilde without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00c3"
+ ]
+ ]
+ },
+ {
+ "input": "Ã",
+ "description": "Named entity: Atilde; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00c3"
+ ]
+ ]
+ },
+ {
+ "input": "Ä",
+ "description": "Named entity: Auml without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00c4"
+ ]
+ ]
+ },
+ {
+ "input": "Ä",
+ "description": "Named entity: Auml; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00c4"
+ ]
+ ]
+ },
+ {
+ "input": "&Backslash",
+ "description": "Bad named entity: Backslash without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Backslash"
+ ]
+ ]
+ },
+ {
+ "input": "∖",
+ "description": "Named entity: Backslash; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2216"
+ ]
+ ]
+ },
+ {
+ "input": "&Barv",
+ "description": "Bad named entity: Barv without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Barv"
+ ]
+ ]
+ },
+ {
+ "input": "⫧",
+ "description": "Named entity: Barv; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ae7"
+ ]
+ ]
+ },
+ {
+ "input": "&Barwed",
+ "description": "Bad named entity: Barwed without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Barwed"
+ ]
+ ]
+ },
+ {
+ "input": "⌆",
+ "description": "Named entity: Barwed; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2306"
+ ]
+ ]
+ },
+ {
+ "input": "&Bcy",
+ "description": "Bad named entity: Bcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Bcy"
+ ]
+ ]
+ },
+ {
+ "input": "Б",
+ "description": "Named entity: Bcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0411"
+ ]
+ ]
+ },
+ {
+ "input": "&Because",
+ "description": "Bad named entity: Because without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Because"
+ ]
+ ]
+ },
+ {
+ "input": "∵",
+ "description": "Named entity: Because; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2235"
+ ]
+ ]
+ },
+ {
+ "input": "&Bernoullis",
+ "description": "Bad named entity: Bernoullis without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Bernoullis"
+ ]
+ ]
+ },
+ {
+ "input": "ℬ",
+ "description": "Named entity: Bernoullis; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u212c"
+ ]
+ ]
+ },
+ {
+ "input": "&Beta",
+ "description": "Bad named entity: Beta without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Beta"
+ ]
+ ]
+ },
+ {
+ "input": "Β",
+ "description": "Named entity: Beta; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0392"
+ ]
+ ]
+ },
+ {
+ "input": "&Bfr",
+ "description": "Bad named entity: Bfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Bfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔅",
+ "description": "Named entity: Bfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd05"
+ ]
+ ]
+ },
+ {
+ "input": "&Bopf",
+ "description": "Bad named entity: Bopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Bopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝔹",
+ "description": "Named entity: Bopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd39"
+ ]
+ ]
+ },
+ {
+ "input": "&Breve",
+ "description": "Bad named entity: Breve without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Breve"
+ ]
+ ]
+ },
+ {
+ "input": "˘",
+ "description": "Named entity: Breve; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u02d8"
+ ]
+ ]
+ },
+ {
+ "input": "&Bscr",
+ "description": "Bad named entity: Bscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Bscr"
+ ]
+ ]
+ },
+ {
+ "input": "ℬ",
+ "description": "Named entity: Bscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u212c"
+ ]
+ ]
+ },
+ {
+ "input": "&Bumpeq",
+ "description": "Bad named entity: Bumpeq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Bumpeq"
+ ]
+ ]
+ },
+ {
+ "input": "≎",
+ "description": "Named entity: Bumpeq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u224e"
+ ]
+ ]
+ },
+ {
+ "input": "&CHcy",
+ "description": "Bad named entity: CHcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&CHcy"
+ ]
+ ]
+ },
+ {
+ "input": "Ч",
+ "description": "Named entity: CHcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0427"
+ ]
+ ]
+ },
+ {
+ "input": "©",
+ "description": "Named entity: COPY without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00a9"
+ ]
+ ]
+ },
+ {
+ "input": "©",
+ "description": "Named entity: COPY; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00a9"
+ ]
+ ]
+ },
+ {
+ "input": "&Cacute",
+ "description": "Bad named entity: Cacute without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Cacute"
+ ]
+ ]
+ },
+ {
+ "input": "Ć",
+ "description": "Named entity: Cacute; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0106"
+ ]
+ ]
+ },
+ {
+ "input": "&Cap",
+ "description": "Bad named entity: Cap without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Cap"
+ ]
+ ]
+ },
+ {
+ "input": "⋒",
+ "description": "Named entity: Cap; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22d2"
+ ]
+ ]
+ },
+ {
+ "input": "&CapitalDifferentialD",
+ "description": "Bad named entity: CapitalDifferentialD without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&CapitalDifferentialD"
+ ]
+ ]
+ },
+ {
+ "input": "ⅅ",
+ "description": "Named entity: CapitalDifferentialD; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2145"
+ ]
+ ]
+ },
+ {
+ "input": "&Cayleys",
+ "description": "Bad named entity: Cayleys without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Cayleys"
+ ]
+ ]
+ },
+ {
+ "input": "ℭ",
+ "description": "Named entity: Cayleys; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u212d"
+ ]
+ ]
+ },
+ {
+ "input": "&Ccaron",
+ "description": "Bad named entity: Ccaron without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Ccaron"
+ ]
+ ]
+ },
+ {
+ "input": "Č",
+ "description": "Named entity: Ccaron; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u010c"
+ ]
+ ]
+ },
+ {
+ "input": "Ç",
+ "description": "Named entity: Ccedil without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00c7"
+ ]
+ ]
+ },
+ {
+ "input": "Ç",
+ "description": "Named entity: Ccedil; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00c7"
+ ]
+ ]
+ },
+ {
+ "input": "&Ccirc",
+ "description": "Bad named entity: Ccirc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Ccirc"
+ ]
+ ]
+ },
+ {
+ "input": "Ĉ",
+ "description": "Named entity: Ccirc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0108"
+ ]
+ ]
+ },
+ {
+ "input": "&Cconint",
+ "description": "Bad named entity: Cconint without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Cconint"
+ ]
+ ]
+ },
+ {
+ "input": "∰",
+ "description": "Named entity: Cconint; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2230"
+ ]
+ ]
+ },
+ {
+ "input": "&Cdot",
+ "description": "Bad named entity: Cdot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Cdot"
+ ]
+ ]
+ },
+ {
+ "input": "Ċ",
+ "description": "Named entity: Cdot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u010a"
+ ]
+ ]
+ },
+ {
+ "input": "&Cedilla",
+ "description": "Bad named entity: Cedilla without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Cedilla"
+ ]
+ ]
+ },
+ {
+ "input": "¸",
+ "description": "Named entity: Cedilla; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00b8"
+ ]
+ ]
+ },
+ {
+ "input": "&CenterDot",
+ "description": "Bad named entity: CenterDot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&CenterDot"
+ ]
+ ]
+ },
+ {
+ "input": "·",
+ "description": "Named entity: CenterDot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00b7"
+ ]
+ ]
+ },
+ {
+ "input": "&Cfr",
+ "description": "Bad named entity: Cfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Cfr"
+ ]
+ ]
+ },
+ {
+ "input": "ℭ",
+ "description": "Named entity: Cfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u212d"
+ ]
+ ]
+ },
+ {
+ "input": "&Chi",
+ "description": "Bad named entity: Chi without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Chi"
+ ]
+ ]
+ },
+ {
+ "input": "Χ",
+ "description": "Named entity: Chi; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03a7"
+ ]
+ ]
+ },
+ {
+ "input": "&CircleDot",
+ "description": "Bad named entity: CircleDot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&CircleDot"
+ ]
+ ]
+ },
+ {
+ "input": "⊙",
+ "description": "Named entity: CircleDot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2299"
+ ]
+ ]
+ },
+ {
+ "input": "&CircleMinus",
+ "description": "Bad named entity: CircleMinus without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&CircleMinus"
+ ]
+ ]
+ },
+ {
+ "input": "⊖",
+ "description": "Named entity: CircleMinus; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2296"
+ ]
+ ]
+ },
+ {
+ "input": "&CirclePlus",
+ "description": "Bad named entity: CirclePlus without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&CirclePlus"
+ ]
+ ]
+ },
+ {
+ "input": "⊕",
+ "description": "Named entity: CirclePlus; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2295"
+ ]
+ ]
+ },
+ {
+ "input": "&CircleTimes",
+ "description": "Bad named entity: CircleTimes without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&CircleTimes"
+ ]
+ ]
+ },
+ {
+ "input": "⊗",
+ "description": "Named entity: CircleTimes; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2297"
+ ]
+ ]
+ },
+ {
+ "input": "&ClockwiseContourIntegral",
+ "description": "Bad named entity: ClockwiseContourIntegral without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ClockwiseContourIntegral"
+ ]
+ ]
+ },
+ {
+ "input": "∲",
+ "description": "Named entity: ClockwiseContourIntegral; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2232"
+ ]
+ ]
+ },
+ {
+ "input": "&CloseCurlyDoubleQuote",
+ "description": "Bad named entity: CloseCurlyDoubleQuote without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&CloseCurlyDoubleQuote"
+ ]
+ ]
+ },
+ {
+ "input": "”",
+ "description": "Named entity: CloseCurlyDoubleQuote; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u201d"
+ ]
+ ]
+ },
+ {
+ "input": "&CloseCurlyQuote",
+ "description": "Bad named entity: CloseCurlyQuote without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&CloseCurlyQuote"
+ ]
+ ]
+ },
+ {
+ "input": "’",
+ "description": "Named entity: CloseCurlyQuote; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2019"
+ ]
+ ]
+ },
+ {
+ "input": "&Colon",
+ "description": "Bad named entity: Colon without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Colon"
+ ]
+ ]
+ },
+ {
+ "input": "∷",
+ "description": "Named entity: Colon; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2237"
+ ]
+ ]
+ },
+ {
+ "input": "&Colone",
+ "description": "Bad named entity: Colone without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Colone"
+ ]
+ ]
+ },
+ {
+ "input": "⩴",
+ "description": "Named entity: Colone; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a74"
+ ]
+ ]
+ },
+ {
+ "input": "&Congruent",
+ "description": "Bad named entity: Congruent without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Congruent"
+ ]
+ ]
+ },
+ {
+ "input": "≡",
+ "description": "Named entity: Congruent; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2261"
+ ]
+ ]
+ },
+ {
+ "input": "&Conint",
+ "description": "Bad named entity: Conint without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Conint"
+ ]
+ ]
+ },
+ {
+ "input": "∯",
+ "description": "Named entity: Conint; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u222f"
+ ]
+ ]
+ },
+ {
+ "input": "&ContourIntegral",
+ "description": "Bad named entity: ContourIntegral without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ContourIntegral"
+ ]
+ ]
+ },
+ {
+ "input": "∮",
+ "description": "Named entity: ContourIntegral; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u222e"
+ ]
+ ]
+ },
+ {
+ "input": "&Copf",
+ "description": "Bad named entity: Copf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Copf"
+ ]
+ ]
+ },
+ {
+ "input": "ℂ",
+ "description": "Named entity: Copf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2102"
+ ]
+ ]
+ },
+ {
+ "input": "&Coproduct",
+ "description": "Bad named entity: Coproduct without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Coproduct"
+ ]
+ ]
+ },
+ {
+ "input": "∐",
+ "description": "Named entity: Coproduct; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2210"
+ ]
+ ]
+ },
+ {
+ "input": "&CounterClockwiseContourIntegral",
+ "description": "Bad named entity: CounterClockwiseContourIntegral without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&CounterClockwiseContourIntegral"
+ ]
+ ]
+ },
+ {
+ "input": "∳",
+ "description": "Named entity: CounterClockwiseContourIntegral; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2233"
+ ]
+ ]
+ },
+ {
+ "input": "&Cross",
+ "description": "Bad named entity: Cross without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Cross"
+ ]
+ ]
+ },
+ {
+ "input": "⨯",
+ "description": "Named entity: Cross; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a2f"
+ ]
+ ]
+ },
+ {
+ "input": "&Cscr",
+ "description": "Bad named entity: Cscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Cscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝒞",
+ "description": "Named entity: Cscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udc9e"
+ ]
+ ]
+ },
+ {
+ "input": "&Cup",
+ "description": "Bad named entity: Cup without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Cup"
+ ]
+ ]
+ },
+ {
+ "input": "⋓",
+ "description": "Named entity: Cup; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22d3"
+ ]
+ ]
+ },
+ {
+ "input": "&CupCap",
+ "description": "Bad named entity: CupCap without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&CupCap"
+ ]
+ ]
+ },
+ {
+ "input": "≍",
+ "description": "Named entity: CupCap; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u224d"
+ ]
+ ]
+ },
+ {
+ "input": "&DD",
+ "description": "Bad named entity: DD without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DD"
+ ]
+ ]
+ },
+ {
+ "input": "ⅅ",
+ "description": "Named entity: DD; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2145"
+ ]
+ ]
+ },
+ {
+ "input": "&DDotrahd",
+ "description": "Bad named entity: DDotrahd without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DDotrahd"
+ ]
+ ]
+ },
+ {
+ "input": "⤑",
+ "description": "Named entity: DDotrahd; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2911"
+ ]
+ ]
+ },
+ {
+ "input": "&DJcy",
+ "description": "Bad named entity: DJcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DJcy"
+ ]
+ ]
+ },
+ {
+ "input": "Ђ",
+ "description": "Named entity: DJcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0402"
+ ]
+ ]
+ },
+ {
+ "input": "&DScy",
+ "description": "Bad named entity: DScy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DScy"
+ ]
+ ]
+ },
+ {
+ "input": "Ѕ",
+ "description": "Named entity: DScy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0405"
+ ]
+ ]
+ },
+ {
+ "input": "&DZcy",
+ "description": "Bad named entity: DZcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DZcy"
+ ]
+ ]
+ },
+ {
+ "input": "Џ",
+ "description": "Named entity: DZcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u040f"
+ ]
+ ]
+ },
+ {
+ "input": "&Dagger",
+ "description": "Bad named entity: Dagger without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Dagger"
+ ]
+ ]
+ },
+ {
+ "input": "‡",
+ "description": "Named entity: Dagger; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2021"
+ ]
+ ]
+ },
+ {
+ "input": "&Darr",
+ "description": "Bad named entity: Darr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Darr"
+ ]
+ ]
+ },
+ {
+ "input": "↡",
+ "description": "Named entity: Darr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21a1"
+ ]
+ ]
+ },
+ {
+ "input": "&Dashv",
+ "description": "Bad named entity: Dashv without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Dashv"
+ ]
+ ]
+ },
+ {
+ "input": "⫤",
+ "description": "Named entity: Dashv; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ae4"
+ ]
+ ]
+ },
+ {
+ "input": "&Dcaron",
+ "description": "Bad named entity: Dcaron without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Dcaron"
+ ]
+ ]
+ },
+ {
+ "input": "Ď",
+ "description": "Named entity: Dcaron; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u010e"
+ ]
+ ]
+ },
+ {
+ "input": "&Dcy",
+ "description": "Bad named entity: Dcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Dcy"
+ ]
+ ]
+ },
+ {
+ "input": "Д",
+ "description": "Named entity: Dcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0414"
+ ]
+ ]
+ },
+ {
+ "input": "&Del",
+ "description": "Bad named entity: Del without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Del"
+ ]
+ ]
+ },
+ {
+ "input": "∇",
+ "description": "Named entity: Del; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2207"
+ ]
+ ]
+ },
+ {
+ "input": "&Delta",
+ "description": "Bad named entity: Delta without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Delta"
+ ]
+ ]
+ },
+ {
+ "input": "Δ",
+ "description": "Named entity: Delta; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0394"
+ ]
+ ]
+ },
+ {
+ "input": "&Dfr",
+ "description": "Bad named entity: Dfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Dfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔇",
+ "description": "Named entity: Dfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd07"
+ ]
+ ]
+ },
+ {
+ "input": "&DiacriticalAcute",
+ "description": "Bad named entity: DiacriticalAcute without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DiacriticalAcute"
+ ]
+ ]
+ },
+ {
+ "input": "´",
+ "description": "Named entity: DiacriticalAcute; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00b4"
+ ]
+ ]
+ },
+ {
+ "input": "&DiacriticalDot",
+ "description": "Bad named entity: DiacriticalDot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DiacriticalDot"
+ ]
+ ]
+ },
+ {
+ "input": "˙",
+ "description": "Named entity: DiacriticalDot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u02d9"
+ ]
+ ]
+ },
+ {
+ "input": "&DiacriticalDoubleAcute",
+ "description": "Bad named entity: DiacriticalDoubleAcute without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DiacriticalDoubleAcute"
+ ]
+ ]
+ },
+ {
+ "input": "˝",
+ "description": "Named entity: DiacriticalDoubleAcute; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u02dd"
+ ]
+ ]
+ },
+ {
+ "input": "&DiacriticalGrave",
+ "description": "Bad named entity: DiacriticalGrave without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DiacriticalGrave"
+ ]
+ ]
+ },
+ {
+ "input": "`",
+ "description": "Named entity: DiacriticalGrave; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "`"
+ ]
+ ]
+ },
+ {
+ "input": "&DiacriticalTilde",
+ "description": "Bad named entity: DiacriticalTilde without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DiacriticalTilde"
+ ]
+ ]
+ },
+ {
+ "input": "˜",
+ "description": "Named entity: DiacriticalTilde; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u02dc"
+ ]
+ ]
+ },
+ {
+ "input": "&Diamond",
+ "description": "Bad named entity: Diamond without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Diamond"
+ ]
+ ]
+ },
+ {
+ "input": "⋄",
+ "description": "Named entity: Diamond; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22c4"
+ ]
+ ]
+ },
+ {
+ "input": "&DifferentialD",
+ "description": "Bad named entity: DifferentialD without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DifferentialD"
+ ]
+ ]
+ },
+ {
+ "input": "ⅆ",
+ "description": "Named entity: DifferentialD; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2146"
+ ]
+ ]
+ },
+ {
+ "input": "&Dopf",
+ "description": "Bad named entity: Dopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Dopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝔻",
+ "description": "Named entity: Dopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd3b"
+ ]
+ ]
+ },
+ {
+ "input": "&Dot",
+ "description": "Bad named entity: Dot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Dot"
+ ]
+ ]
+ },
+ {
+ "input": "¨",
+ "description": "Named entity: Dot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00a8"
+ ]
+ ]
+ },
+ {
+ "input": "&DotDot",
+ "description": "Bad named entity: DotDot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DotDot"
+ ]
+ ]
+ },
+ {
+ "input": "⃜",
+ "description": "Named entity: DotDot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u20dc"
+ ]
+ ]
+ },
+ {
+ "input": "&DotEqual",
+ "description": "Bad named entity: DotEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DotEqual"
+ ]
+ ]
+ },
+ {
+ "input": "≐",
+ "description": "Named entity: DotEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2250"
+ ]
+ ]
+ },
+ {
+ "input": "&DoubleContourIntegral",
+ "description": "Bad named entity: DoubleContourIntegral without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DoubleContourIntegral"
+ ]
+ ]
+ },
+ {
+ "input": "∯",
+ "description": "Named entity: DoubleContourIntegral; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u222f"
+ ]
+ ]
+ },
+ {
+ "input": "&DoubleDot",
+ "description": "Bad named entity: DoubleDot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DoubleDot"
+ ]
+ ]
+ },
+ {
+ "input": "¨",
+ "description": "Named entity: DoubleDot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00a8"
+ ]
+ ]
+ },
+ {
+ "input": "&DoubleDownArrow",
+ "description": "Bad named entity: DoubleDownArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DoubleDownArrow"
+ ]
+ ]
+ },
+ {
+ "input": "⇓",
+ "description": "Named entity: DoubleDownArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21d3"
+ ]
+ ]
+ },
+ {
+ "input": "&DoubleLeftArrow",
+ "description": "Bad named entity: DoubleLeftArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DoubleLeftArrow"
+ ]
+ ]
+ },
+ {
+ "input": "⇐",
+ "description": "Named entity: DoubleLeftArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21d0"
+ ]
+ ]
+ },
+ {
+ "input": "&DoubleLeftRightArrow",
+ "description": "Bad named entity: DoubleLeftRightArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DoubleLeftRightArrow"
+ ]
+ ]
+ },
+ {
+ "input": "⇔",
+ "description": "Named entity: DoubleLeftRightArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21d4"
+ ]
+ ]
+ },
+ {
+ "input": "&DoubleLeftTee",
+ "description": "Bad named entity: DoubleLeftTee without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DoubleLeftTee"
+ ]
+ ]
+ },
+ {
+ "input": "⫤",
+ "description": "Named entity: DoubleLeftTee; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ae4"
+ ]
+ ]
+ },
+ {
+ "input": "&DoubleLongLeftArrow",
+ "description": "Bad named entity: DoubleLongLeftArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DoubleLongLeftArrow"
+ ]
+ ]
+ },
+ {
+ "input": "⟸",
+ "description": "Named entity: DoubleLongLeftArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27f8"
+ ]
+ ]
+ },
+ {
+ "input": "&DoubleLongLeftRightArrow",
+ "description": "Bad named entity: DoubleLongLeftRightArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DoubleLongLeftRightArrow"
+ ]
+ ]
+ },
+ {
+ "input": "⟺",
+ "description": "Named entity: DoubleLongLeftRightArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27fa"
+ ]
+ ]
+ },
+ {
+ "input": "&DoubleLongRightArrow",
+ "description": "Bad named entity: DoubleLongRightArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DoubleLongRightArrow"
+ ]
+ ]
+ },
+ {
+ "input": "⟹",
+ "description": "Named entity: DoubleLongRightArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27f9"
+ ]
+ ]
+ },
+ {
+ "input": "&DoubleRightArrow",
+ "description": "Bad named entity: DoubleRightArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DoubleRightArrow"
+ ]
+ ]
+ },
+ {
+ "input": "⇒",
+ "description": "Named entity: DoubleRightArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21d2"
+ ]
+ ]
+ },
+ {
+ "input": "&DoubleRightTee",
+ "description": "Bad named entity: DoubleRightTee without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DoubleRightTee"
+ ]
+ ]
+ },
+ {
+ "input": "⊨",
+ "description": "Named entity: DoubleRightTee; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22a8"
+ ]
+ ]
+ },
+ {
+ "input": "&DoubleUpArrow",
+ "description": "Bad named entity: DoubleUpArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DoubleUpArrow"
+ ]
+ ]
+ },
+ {
+ "input": "⇑",
+ "description": "Named entity: DoubleUpArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21d1"
+ ]
+ ]
+ },
+ {
+ "input": "&DoubleUpDownArrow",
+ "description": "Bad named entity: DoubleUpDownArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DoubleUpDownArrow"
+ ]
+ ]
+ },
+ {
+ "input": "⇕",
+ "description": "Named entity: DoubleUpDownArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21d5"
+ ]
+ ]
+ },
+ {
+ "input": "&DoubleVerticalBar",
+ "description": "Bad named entity: DoubleVerticalBar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DoubleVerticalBar"
+ ]
+ ]
+ },
+ {
+ "input": "∥",
+ "description": "Named entity: DoubleVerticalBar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2225"
+ ]
+ ]
+ },
+ {
+ "input": "&DownArrow",
+ "description": "Bad named entity: DownArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DownArrow"
+ ]
+ ]
+ },
+ {
+ "input": "↓",
+ "description": "Named entity: DownArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2193"
+ ]
+ ]
+ },
+ {
+ "input": "&DownArrowBar",
+ "description": "Bad named entity: DownArrowBar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DownArrowBar"
+ ]
+ ]
+ },
+ {
+ "input": "⤓",
+ "description": "Named entity: DownArrowBar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2913"
+ ]
+ ]
+ },
+ {
+ "input": "&DownArrowUpArrow",
+ "description": "Bad named entity: DownArrowUpArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DownArrowUpArrow"
+ ]
+ ]
+ },
+ {
+ "input": "⇵",
+ "description": "Named entity: DownArrowUpArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21f5"
+ ]
+ ]
+ },
+ {
+ "input": "&DownBreve",
+ "description": "Bad named entity: DownBreve without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DownBreve"
+ ]
+ ]
+ },
+ {
+ "input": "̑",
+ "description": "Named entity: DownBreve; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0311"
+ ]
+ ]
+ },
+ {
+ "input": "&DownLeftRightVector",
+ "description": "Bad named entity: DownLeftRightVector without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DownLeftRightVector"
+ ]
+ ]
+ },
+ {
+ "input": "⥐",
+ "description": "Named entity: DownLeftRightVector; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2950"
+ ]
+ ]
+ },
+ {
+ "input": "&DownLeftTeeVector",
+ "description": "Bad named entity: DownLeftTeeVector without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DownLeftTeeVector"
+ ]
+ ]
+ },
+ {
+ "input": "⥞",
+ "description": "Named entity: DownLeftTeeVector; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u295e"
+ ]
+ ]
+ },
+ {
+ "input": "&DownLeftVector",
+ "description": "Bad named entity: DownLeftVector without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DownLeftVector"
+ ]
+ ]
+ },
+ {
+ "input": "↽",
+ "description": "Named entity: DownLeftVector; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21bd"
+ ]
+ ]
+ },
+ {
+ "input": "&DownLeftVectorBar",
+ "description": "Bad named entity: DownLeftVectorBar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DownLeftVectorBar"
+ ]
+ ]
+ },
+ {
+ "input": "⥖",
+ "description": "Named entity: DownLeftVectorBar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2956"
+ ]
+ ]
+ },
+ {
+ "input": "&DownRightTeeVector",
+ "description": "Bad named entity: DownRightTeeVector without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DownRightTeeVector"
+ ]
+ ]
+ },
+ {
+ "input": "⥟",
+ "description": "Named entity: DownRightTeeVector; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u295f"
+ ]
+ ]
+ },
+ {
+ "input": "&DownRightVector",
+ "description": "Bad named entity: DownRightVector without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DownRightVector"
+ ]
+ ]
+ },
+ {
+ "input": "⇁",
+ "description": "Named entity: DownRightVector; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21c1"
+ ]
+ ]
+ },
+ {
+ "input": "&DownRightVectorBar",
+ "description": "Bad named entity: DownRightVectorBar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DownRightVectorBar"
+ ]
+ ]
+ },
+ {
+ "input": "⥗",
+ "description": "Named entity: DownRightVectorBar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2957"
+ ]
+ ]
+ },
+ {
+ "input": "&DownTee",
+ "description": "Bad named entity: DownTee without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DownTee"
+ ]
+ ]
+ },
+ {
+ "input": "⊤",
+ "description": "Named entity: DownTee; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22a4"
+ ]
+ ]
+ },
+ {
+ "input": "&DownTeeArrow",
+ "description": "Bad named entity: DownTeeArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&DownTeeArrow"
+ ]
+ ]
+ },
+ {
+ "input": "↧",
+ "description": "Named entity: DownTeeArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21a7"
+ ]
+ ]
+ },
+ {
+ "input": "&Downarrow",
+ "description": "Bad named entity: Downarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Downarrow"
+ ]
+ ]
+ },
+ {
+ "input": "⇓",
+ "description": "Named entity: Downarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21d3"
+ ]
+ ]
+ },
+ {
+ "input": "&Dscr",
+ "description": "Bad named entity: Dscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Dscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝒟",
+ "description": "Named entity: Dscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udc9f"
+ ]
+ ]
+ },
+ {
+ "input": "&Dstrok",
+ "description": "Bad named entity: Dstrok without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Dstrok"
+ ]
+ ]
+ },
+ {
+ "input": "Đ",
+ "description": "Named entity: Dstrok; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0110"
+ ]
+ ]
+ },
+ {
+ "input": "&ENG",
+ "description": "Bad named entity: ENG without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ENG"
+ ]
+ ]
+ },
+ {
+ "input": "Ŋ",
+ "description": "Named entity: ENG; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u014a"
+ ]
+ ]
+ },
+ {
+ "input": "Ð",
+ "description": "Named entity: ETH without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00d0"
+ ]
+ ]
+ },
+ {
+ "input": "Ð",
+ "description": "Named entity: ETH; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00d0"
+ ]
+ ]
+ },
+ {
+ "input": "É",
+ "description": "Named entity: Eacute without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00c9"
+ ]
+ ]
+ },
+ {
+ "input": "É",
+ "description": "Named entity: Eacute; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00c9"
+ ]
+ ]
+ },
+ {
+ "input": "&Ecaron",
+ "description": "Bad named entity: Ecaron without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Ecaron"
+ ]
+ ]
+ },
+ {
+ "input": "Ě",
+ "description": "Named entity: Ecaron; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u011a"
+ ]
+ ]
+ },
+ {
+ "input": "Ê",
+ "description": "Named entity: Ecirc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00ca"
+ ]
+ ]
+ },
+ {
+ "input": "Ê",
+ "description": "Named entity: Ecirc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00ca"
+ ]
+ ]
+ },
+ {
+ "input": "&Ecy",
+ "description": "Bad named entity: Ecy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Ecy"
+ ]
+ ]
+ },
+ {
+ "input": "Э",
+ "description": "Named entity: Ecy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u042d"
+ ]
+ ]
+ },
+ {
+ "input": "&Edot",
+ "description": "Bad named entity: Edot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Edot"
+ ]
+ ]
+ },
+ {
+ "input": "Ė",
+ "description": "Named entity: Edot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0116"
+ ]
+ ]
+ },
+ {
+ "input": "&Efr",
+ "description": "Bad named entity: Efr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Efr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔈",
+ "description": "Named entity: Efr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd08"
+ ]
+ ]
+ },
+ {
+ "input": "È",
+ "description": "Named entity: Egrave without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00c8"
+ ]
+ ]
+ },
+ {
+ "input": "È",
+ "description": "Named entity: Egrave; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00c8"
+ ]
+ ]
+ },
+ {
+ "input": "&Element",
+ "description": "Bad named entity: Element without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Element"
+ ]
+ ]
+ },
+ {
+ "input": "∈",
+ "description": "Named entity: Element; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2208"
+ ]
+ ]
+ },
+ {
+ "input": "&Emacr",
+ "description": "Bad named entity: Emacr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Emacr"
+ ]
+ ]
+ },
+ {
+ "input": "Ē",
+ "description": "Named entity: Emacr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0112"
+ ]
+ ]
+ },
+ {
+ "input": "&EmptySmallSquare",
+ "description": "Bad named entity: EmptySmallSquare without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&EmptySmallSquare"
+ ]
+ ]
+ },
+ {
+ "input": "◻",
+ "description": "Named entity: EmptySmallSquare; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25fb"
+ ]
+ ]
+ },
+ {
+ "input": "&EmptyVerySmallSquare",
+ "description": "Bad named entity: EmptyVerySmallSquare without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&EmptyVerySmallSquare"
+ ]
+ ]
+ },
+ {
+ "input": "▫",
+ "description": "Named entity: EmptyVerySmallSquare; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25ab"
+ ]
+ ]
+ },
+ {
+ "input": "&Eogon",
+ "description": "Bad named entity: Eogon without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Eogon"
+ ]
+ ]
+ },
+ {
+ "input": "Ę",
+ "description": "Named entity: Eogon; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0118"
+ ]
+ ]
+ },
+ {
+ "input": "&Eopf",
+ "description": "Bad named entity: Eopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Eopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝔼",
+ "description": "Named entity: Eopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd3c"
+ ]
+ ]
+ },
+ {
+ "input": "&Epsilon",
+ "description": "Bad named entity: Epsilon without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Epsilon"
+ ]
+ ]
+ },
+ {
+ "input": "Ε",
+ "description": "Named entity: Epsilon; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0395"
+ ]
+ ]
+ },
+ {
+ "input": "&Equal",
+ "description": "Bad named entity: Equal without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Equal"
+ ]
+ ]
+ },
+ {
+ "input": "⩵",
+ "description": "Named entity: Equal; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a75"
+ ]
+ ]
+ },
+ {
+ "input": "&EqualTilde",
+ "description": "Bad named entity: EqualTilde without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&EqualTilde"
+ ]
+ ]
+ },
+ {
+ "input": "≂",
+ "description": "Named entity: EqualTilde; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2242"
+ ]
+ ]
+ },
+ {
+ "input": "&Equilibrium",
+ "description": "Bad named entity: Equilibrium without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Equilibrium"
+ ]
+ ]
+ },
+ {
+ "input": "⇌",
+ "description": "Named entity: Equilibrium; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21cc"
+ ]
+ ]
+ },
+ {
+ "input": "&Escr",
+ "description": "Bad named entity: Escr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Escr"
+ ]
+ ]
+ },
+ {
+ "input": "ℰ",
+ "description": "Named entity: Escr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2130"
+ ]
+ ]
+ },
+ {
+ "input": "&Esim",
+ "description": "Bad named entity: Esim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Esim"
+ ]
+ ]
+ },
+ {
+ "input": "⩳",
+ "description": "Named entity: Esim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a73"
+ ]
+ ]
+ },
+ {
+ "input": "&Eta",
+ "description": "Bad named entity: Eta without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Eta"
+ ]
+ ]
+ },
+ {
+ "input": "Η",
+ "description": "Named entity: Eta; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0397"
+ ]
+ ]
+ },
+ {
+ "input": "Ë",
+ "description": "Named entity: Euml without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00cb"
+ ]
+ ]
+ },
+ {
+ "input": "Ë",
+ "description": "Named entity: Euml; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00cb"
+ ]
+ ]
+ },
+ {
+ "input": "&Exists",
+ "description": "Bad named entity: Exists without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Exists"
+ ]
+ ]
+ },
+ {
+ "input": "∃",
+ "description": "Named entity: Exists; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2203"
+ ]
+ ]
+ },
+ {
+ "input": "&ExponentialE",
+ "description": "Bad named entity: ExponentialE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ExponentialE"
+ ]
+ ]
+ },
+ {
+ "input": "ⅇ",
+ "description": "Named entity: ExponentialE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2147"
+ ]
+ ]
+ },
+ {
+ "input": "&Fcy",
+ "description": "Bad named entity: Fcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Fcy"
+ ]
+ ]
+ },
+ {
+ "input": "Ф",
+ "description": "Named entity: Fcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0424"
+ ]
+ ]
+ },
+ {
+ "input": "&Ffr",
+ "description": "Bad named entity: Ffr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Ffr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔉",
+ "description": "Named entity: Ffr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd09"
+ ]
+ ]
+ },
+ {
+ "input": "&FilledSmallSquare",
+ "description": "Bad named entity: FilledSmallSquare without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&FilledSmallSquare"
+ ]
+ ]
+ },
+ {
+ "input": "◼",
+ "description": "Named entity: FilledSmallSquare; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25fc"
+ ]
+ ]
+ },
+ {
+ "input": "&FilledVerySmallSquare",
+ "description": "Bad named entity: FilledVerySmallSquare without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&FilledVerySmallSquare"
+ ]
+ ]
+ },
+ {
+ "input": "▪",
+ "description": "Named entity: FilledVerySmallSquare; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25aa"
+ ]
+ ]
+ },
+ {
+ "input": "&Fopf",
+ "description": "Bad named entity: Fopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Fopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝔽",
+ "description": "Named entity: Fopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd3d"
+ ]
+ ]
+ },
+ {
+ "input": "&ForAll",
+ "description": "Bad named entity: ForAll without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ForAll"
+ ]
+ ]
+ },
+ {
+ "input": "∀",
+ "description": "Named entity: ForAll; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2200"
+ ]
+ ]
+ },
+ {
+ "input": "&Fouriertrf",
+ "description": "Bad named entity: Fouriertrf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Fouriertrf"
+ ]
+ ]
+ },
+ {
+ "input": "ℱ",
+ "description": "Named entity: Fouriertrf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2131"
+ ]
+ ]
+ },
+ {
+ "input": "&Fscr",
+ "description": "Bad named entity: Fscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Fscr"
+ ]
+ ]
+ },
+ {
+ "input": "ℱ",
+ "description": "Named entity: Fscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2131"
+ ]
+ ]
+ },
+ {
+ "input": "&GJcy",
+ "description": "Bad named entity: GJcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&GJcy"
+ ]
+ ]
+ },
+ {
+ "input": "Ѓ",
+ "description": "Named entity: GJcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0403"
+ ]
+ ]
+ },
+ {
+ "input": ">",
+ "description": "Named entity: GT without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ ">"
+ ]
+ ]
+ },
+ {
+ "input": ">",
+ "description": "Named entity: GT; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ ">"
+ ]
+ ]
+ },
+ {
+ "input": "&Gamma",
+ "description": "Bad named entity: Gamma without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Gamma"
+ ]
+ ]
+ },
+ {
+ "input": "Γ",
+ "description": "Named entity: Gamma; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0393"
+ ]
+ ]
+ },
+ {
+ "input": "&Gammad",
+ "description": "Bad named entity: Gammad without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Gammad"
+ ]
+ ]
+ },
+ {
+ "input": "Ϝ",
+ "description": "Named entity: Gammad; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03dc"
+ ]
+ ]
+ },
+ {
+ "input": "&Gbreve",
+ "description": "Bad named entity: Gbreve without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Gbreve"
+ ]
+ ]
+ },
+ {
+ "input": "Ğ",
+ "description": "Named entity: Gbreve; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u011e"
+ ]
+ ]
+ },
+ {
+ "input": "&Gcedil",
+ "description": "Bad named entity: Gcedil without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Gcedil"
+ ]
+ ]
+ },
+ {
+ "input": "Ģ",
+ "description": "Named entity: Gcedil; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0122"
+ ]
+ ]
+ },
+ {
+ "input": "&Gcirc",
+ "description": "Bad named entity: Gcirc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Gcirc"
+ ]
+ ]
+ },
+ {
+ "input": "Ĝ",
+ "description": "Named entity: Gcirc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u011c"
+ ]
+ ]
+ },
+ {
+ "input": "&Gcy",
+ "description": "Bad named entity: Gcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Gcy"
+ ]
+ ]
+ },
+ {
+ "input": "Г",
+ "description": "Named entity: Gcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0413"
+ ]
+ ]
+ },
+ {
+ "input": "&Gdot",
+ "description": "Bad named entity: Gdot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Gdot"
+ ]
+ ]
+ },
+ {
+ "input": "Ġ",
+ "description": "Named entity: Gdot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0120"
+ ]
+ ]
+ },
+ {
+ "input": "&Gfr",
+ "description": "Bad named entity: Gfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Gfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔊",
+ "description": "Named entity: Gfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd0a"
+ ]
+ ]
+ },
+ {
+ "input": "&Gg",
+ "description": "Bad named entity: Gg without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Gg"
+ ]
+ ]
+ },
+ {
+ "input": "⋙",
+ "description": "Named entity: Gg; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22d9"
+ ]
+ ]
+ },
+ {
+ "input": "&Gopf",
+ "description": "Bad named entity: Gopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Gopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝔾",
+ "description": "Named entity: Gopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd3e"
+ ]
+ ]
+ },
+ {
+ "input": "&GreaterEqual",
+ "description": "Bad named entity: GreaterEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&GreaterEqual"
+ ]
+ ]
+ },
+ {
+ "input": "≥",
+ "description": "Named entity: GreaterEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2265"
+ ]
+ ]
+ },
+ {
+ "input": "&GreaterEqualLess",
+ "description": "Bad named entity: GreaterEqualLess without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&GreaterEqualLess"
+ ]
+ ]
+ },
+ {
+ "input": "⋛",
+ "description": "Named entity: GreaterEqualLess; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22db"
+ ]
+ ]
+ },
+ {
+ "input": "&GreaterFullEqual",
+ "description": "Bad named entity: GreaterFullEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&GreaterFullEqual"
+ ]
+ ]
+ },
+ {
+ "input": "≧",
+ "description": "Named entity: GreaterFullEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2267"
+ ]
+ ]
+ },
+ {
+ "input": "&GreaterGreater",
+ "description": "Bad named entity: GreaterGreater without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&GreaterGreater"
+ ]
+ ]
+ },
+ {
+ "input": "⪢",
+ "description": "Named entity: GreaterGreater; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2aa2"
+ ]
+ ]
+ },
+ {
+ "input": "&GreaterLess",
+ "description": "Bad named entity: GreaterLess without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&GreaterLess"
+ ]
+ ]
+ },
+ {
+ "input": "≷",
+ "description": "Named entity: GreaterLess; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2277"
+ ]
+ ]
+ },
+ {
+ "input": "&GreaterSlantEqual",
+ "description": "Bad named entity: GreaterSlantEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&GreaterSlantEqual"
+ ]
+ ]
+ },
+ {
+ "input": "⩾",
+ "description": "Named entity: GreaterSlantEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a7e"
+ ]
+ ]
+ },
+ {
+ "input": "&GreaterTilde",
+ "description": "Bad named entity: GreaterTilde without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&GreaterTilde"
+ ]
+ ]
+ },
+ {
+ "input": "≳",
+ "description": "Named entity: GreaterTilde; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2273"
+ ]
+ ]
+ },
+ {
+ "input": "&Gscr",
+ "description": "Bad named entity: Gscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Gscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝒢",
+ "description": "Named entity: Gscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udca2"
+ ]
+ ]
+ },
+ {
+ "input": "&Gt",
+ "description": "Bad named entity: Gt without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Gt"
+ ]
+ ]
+ },
+ {
+ "input": "≫",
+ "description": "Named entity: Gt; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u226b"
+ ]
+ ]
+ },
+ {
+ "input": "&HARDcy",
+ "description": "Bad named entity: HARDcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&HARDcy"
+ ]
+ ]
+ },
+ {
+ "input": "Ъ",
+ "description": "Named entity: HARDcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u042a"
+ ]
+ ]
+ },
+ {
+ "input": "&Hacek",
+ "description": "Bad named entity: Hacek without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Hacek"
+ ]
+ ]
+ },
+ {
+ "input": "ˇ",
+ "description": "Named entity: Hacek; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u02c7"
+ ]
+ ]
+ },
+ {
+ "input": "&Hat",
+ "description": "Bad named entity: Hat without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Hat"
+ ]
+ ]
+ },
+ {
+ "input": "^",
+ "description": "Named entity: Hat; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "^"
+ ]
+ ]
+ },
+ {
+ "input": "&Hcirc",
+ "description": "Bad named entity: Hcirc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Hcirc"
+ ]
+ ]
+ },
+ {
+ "input": "Ĥ",
+ "description": "Named entity: Hcirc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0124"
+ ]
+ ]
+ },
+ {
+ "input": "&Hfr",
+ "description": "Bad named entity: Hfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Hfr"
+ ]
+ ]
+ },
+ {
+ "input": "ℌ",
+ "description": "Named entity: Hfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u210c"
+ ]
+ ]
+ },
+ {
+ "input": "&HilbertSpace",
+ "description": "Bad named entity: HilbertSpace without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&HilbertSpace"
+ ]
+ ]
+ },
+ {
+ "input": "ℋ",
+ "description": "Named entity: HilbertSpace; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u210b"
+ ]
+ ]
+ },
+ {
+ "input": "&Hopf",
+ "description": "Bad named entity: Hopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Hopf"
+ ]
+ ]
+ },
+ {
+ "input": "ℍ",
+ "description": "Named entity: Hopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u210d"
+ ]
+ ]
+ },
+ {
+ "input": "&HorizontalLine",
+ "description": "Bad named entity: HorizontalLine without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&HorizontalLine"
+ ]
+ ]
+ },
+ {
+ "input": "─",
+ "description": "Named entity: HorizontalLine; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2500"
+ ]
+ ]
+ },
+ {
+ "input": "&Hscr",
+ "description": "Bad named entity: Hscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Hscr"
+ ]
+ ]
+ },
+ {
+ "input": "ℋ",
+ "description": "Named entity: Hscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u210b"
+ ]
+ ]
+ },
+ {
+ "input": "&Hstrok",
+ "description": "Bad named entity: Hstrok without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Hstrok"
+ ]
+ ]
+ },
+ {
+ "input": "Ħ",
+ "description": "Named entity: Hstrok; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0126"
+ ]
+ ]
+ },
+ {
+ "input": "&HumpDownHump",
+ "description": "Bad named entity: HumpDownHump without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&HumpDownHump"
+ ]
+ ]
+ },
+ {
+ "input": "≎",
+ "description": "Named entity: HumpDownHump; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u224e"
+ ]
+ ]
+ },
+ {
+ "input": "&HumpEqual",
+ "description": "Bad named entity: HumpEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&HumpEqual"
+ ]
+ ]
+ },
+ {
+ "input": "≏",
+ "description": "Named entity: HumpEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u224f"
+ ]
+ ]
+ },
+ {
+ "input": "&IEcy",
+ "description": "Bad named entity: IEcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&IEcy"
+ ]
+ ]
+ },
+ {
+ "input": "Е",
+ "description": "Named entity: IEcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0415"
+ ]
+ ]
+ },
+ {
+ "input": "&IJlig",
+ "description": "Bad named entity: IJlig without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&IJlig"
+ ]
+ ]
+ },
+ {
+ "input": "IJ",
+ "description": "Named entity: IJlig; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0132"
+ ]
+ ]
+ },
+ {
+ "input": "&IOcy",
+ "description": "Bad named entity: IOcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&IOcy"
+ ]
+ ]
+ },
+ {
+ "input": "Ё",
+ "description": "Named entity: IOcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0401"
+ ]
+ ]
+ },
+ {
+ "input": "Í",
+ "description": "Named entity: Iacute without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00cd"
+ ]
+ ]
+ },
+ {
+ "input": "Í",
+ "description": "Named entity: Iacute; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00cd"
+ ]
+ ]
+ },
+ {
+ "input": "Î",
+ "description": "Named entity: Icirc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00ce"
+ ]
+ ]
+ },
+ {
+ "input": "Î",
+ "description": "Named entity: Icirc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00ce"
+ ]
+ ]
+ },
+ {
+ "input": "&Icy",
+ "description": "Bad named entity: Icy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Icy"
+ ]
+ ]
+ },
+ {
+ "input": "И",
+ "description": "Named entity: Icy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0418"
+ ]
+ ]
+ },
+ {
+ "input": "&Idot",
+ "description": "Bad named entity: Idot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Idot"
+ ]
+ ]
+ },
+ {
+ "input": "İ",
+ "description": "Named entity: Idot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0130"
+ ]
+ ]
+ },
+ {
+ "input": "&Ifr",
+ "description": "Bad named entity: Ifr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Ifr"
+ ]
+ ]
+ },
+ {
+ "input": "ℑ",
+ "description": "Named entity: Ifr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2111"
+ ]
+ ]
+ },
+ {
+ "input": "Ì",
+ "description": "Named entity: Igrave without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00cc"
+ ]
+ ]
+ },
+ {
+ "input": "Ì",
+ "description": "Named entity: Igrave; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00cc"
+ ]
+ ]
+ },
+ {
+ "input": "&Im",
+ "description": "Bad named entity: Im without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Im"
+ ]
+ ]
+ },
+ {
+ "input": "ℑ",
+ "description": "Named entity: Im; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2111"
+ ]
+ ]
+ },
+ {
+ "input": "&Imacr",
+ "description": "Bad named entity: Imacr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Imacr"
+ ]
+ ]
+ },
+ {
+ "input": "Ī",
+ "description": "Named entity: Imacr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u012a"
+ ]
+ ]
+ },
+ {
+ "input": "&ImaginaryI",
+ "description": "Bad named entity: ImaginaryI without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ImaginaryI"
+ ]
+ ]
+ },
+ {
+ "input": "ⅈ",
+ "description": "Named entity: ImaginaryI; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2148"
+ ]
+ ]
+ },
+ {
+ "input": "&Implies",
+ "description": "Bad named entity: Implies without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Implies"
+ ]
+ ]
+ },
+ {
+ "input": "⇒",
+ "description": "Named entity: Implies; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21d2"
+ ]
+ ]
+ },
+ {
+ "input": "&Int",
+ "description": "Bad named entity: Int without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Int"
+ ]
+ ]
+ },
+ {
+ "input": "∬",
+ "description": "Named entity: Int; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u222c"
+ ]
+ ]
+ },
+ {
+ "input": "&Integral",
+ "description": "Bad named entity: Integral without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Integral"
+ ]
+ ]
+ },
+ {
+ "input": "∫",
+ "description": "Named entity: Integral; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u222b"
+ ]
+ ]
+ },
+ {
+ "input": "&Intersection",
+ "description": "Bad named entity: Intersection without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Intersection"
+ ]
+ ]
+ },
+ {
+ "input": "⋂",
+ "description": "Named entity: Intersection; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22c2"
+ ]
+ ]
+ },
+ {
+ "input": "&InvisibleComma",
+ "description": "Bad named entity: InvisibleComma without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&InvisibleComma"
+ ]
+ ]
+ },
+ {
+ "input": "⁣",
+ "description": "Named entity: InvisibleComma; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2063"
+ ]
+ ]
+ },
+ {
+ "input": "&InvisibleTimes",
+ "description": "Bad named entity: InvisibleTimes without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&InvisibleTimes"
+ ]
+ ]
+ },
+ {
+ "input": "⁢",
+ "description": "Named entity: InvisibleTimes; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2062"
+ ]
+ ]
+ },
+ {
+ "input": "&Iogon",
+ "description": "Bad named entity: Iogon without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Iogon"
+ ]
+ ]
+ },
+ {
+ "input": "Į",
+ "description": "Named entity: Iogon; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u012e"
+ ]
+ ]
+ },
+ {
+ "input": "&Iopf",
+ "description": "Bad named entity: Iopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Iopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕀",
+ "description": "Named entity: Iopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd40"
+ ]
+ ]
+ },
+ {
+ "input": "&Iota",
+ "description": "Bad named entity: Iota without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Iota"
+ ]
+ ]
+ },
+ {
+ "input": "Ι",
+ "description": "Named entity: Iota; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0399"
+ ]
+ ]
+ },
+ {
+ "input": "&Iscr",
+ "description": "Bad named entity: Iscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Iscr"
+ ]
+ ]
+ },
+ {
+ "input": "ℐ",
+ "description": "Named entity: Iscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2110"
+ ]
+ ]
+ },
+ {
+ "input": "&Itilde",
+ "description": "Bad named entity: Itilde without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Itilde"
+ ]
+ ]
+ },
+ {
+ "input": "Ĩ",
+ "description": "Named entity: Itilde; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0128"
+ ]
+ ]
+ },
+ {
+ "input": "&Iukcy",
+ "description": "Bad named entity: Iukcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Iukcy"
+ ]
+ ]
+ },
+ {
+ "input": "І",
+ "description": "Named entity: Iukcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0406"
+ ]
+ ]
+ },
+ {
+ "input": "Ï",
+ "description": "Named entity: Iuml without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00cf"
+ ]
+ ]
+ },
+ {
+ "input": "Ï",
+ "description": "Named entity: Iuml; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00cf"
+ ]
+ ]
+ },
+ {
+ "input": "&Jcirc",
+ "description": "Bad named entity: Jcirc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Jcirc"
+ ]
+ ]
+ },
+ {
+ "input": "Ĵ",
+ "description": "Named entity: Jcirc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0134"
+ ]
+ ]
+ },
+ {
+ "input": "&Jcy",
+ "description": "Bad named entity: Jcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Jcy"
+ ]
+ ]
+ },
+ {
+ "input": "Й",
+ "description": "Named entity: Jcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0419"
+ ]
+ ]
+ },
+ {
+ "input": "&Jfr",
+ "description": "Bad named entity: Jfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Jfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔍",
+ "description": "Named entity: Jfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd0d"
+ ]
+ ]
+ },
+ {
+ "input": "&Jopf",
+ "description": "Bad named entity: Jopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Jopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕁",
+ "description": "Named entity: Jopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd41"
+ ]
+ ]
+ },
+ {
+ "input": "&Jscr",
+ "description": "Bad named entity: Jscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Jscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝒥",
+ "description": "Named entity: Jscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udca5"
+ ]
+ ]
+ },
+ {
+ "input": "&Jsercy",
+ "description": "Bad named entity: Jsercy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Jsercy"
+ ]
+ ]
+ },
+ {
+ "input": "Ј",
+ "description": "Named entity: Jsercy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0408"
+ ]
+ ]
+ },
+ {
+ "input": "&Jukcy",
+ "description": "Bad named entity: Jukcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Jukcy"
+ ]
+ ]
+ },
+ {
+ "input": "Є",
+ "description": "Named entity: Jukcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0404"
+ ]
+ ]
+ },
+ {
+ "input": "&KHcy",
+ "description": "Bad named entity: KHcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&KHcy"
+ ]
+ ]
+ },
+ {
+ "input": "Х",
+ "description": "Named entity: KHcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0425"
+ ]
+ ]
+ },
+ {
+ "input": "&KJcy",
+ "description": "Bad named entity: KJcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&KJcy"
+ ]
+ ]
+ },
+ {
+ "input": "Ќ",
+ "description": "Named entity: KJcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u040c"
+ ]
+ ]
+ },
+ {
+ "input": "&Kappa",
+ "description": "Bad named entity: Kappa without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Kappa"
+ ]
+ ]
+ },
+ {
+ "input": "Κ",
+ "description": "Named entity: Kappa; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u039a"
+ ]
+ ]
+ },
+ {
+ "input": "&Kcedil",
+ "description": "Bad named entity: Kcedil without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Kcedil"
+ ]
+ ]
+ },
+ {
+ "input": "Ķ",
+ "description": "Named entity: Kcedil; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0136"
+ ]
+ ]
+ },
+ {
+ "input": "&Kcy",
+ "description": "Bad named entity: Kcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Kcy"
+ ]
+ ]
+ },
+ {
+ "input": "К",
+ "description": "Named entity: Kcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u041a"
+ ]
+ ]
+ },
+ {
+ "input": "&Kfr",
+ "description": "Bad named entity: Kfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Kfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔎",
+ "description": "Named entity: Kfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd0e"
+ ]
+ ]
+ },
+ {
+ "input": "&Kopf",
+ "description": "Bad named entity: Kopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Kopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕂",
+ "description": "Named entity: Kopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd42"
+ ]
+ ]
+ },
+ {
+ "input": "&Kscr",
+ "description": "Bad named entity: Kscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Kscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝒦",
+ "description": "Named entity: Kscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udca6"
+ ]
+ ]
+ },
+ {
+ "input": "&LJcy",
+ "description": "Bad named entity: LJcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LJcy"
+ ]
+ ]
+ },
+ {
+ "input": "Љ",
+ "description": "Named entity: LJcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0409"
+ ]
+ ]
+ },
+ {
+ "input": "<",
+ "description": "Named entity: LT without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "<"
+ ]
+ ]
+ },
+ {
+ "input": "<",
+ "description": "Named entity: LT; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "<"
+ ]
+ ]
+ },
+ {
+ "input": "&Lacute",
+ "description": "Bad named entity: Lacute without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Lacute"
+ ]
+ ]
+ },
+ {
+ "input": "Ĺ",
+ "description": "Named entity: Lacute; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0139"
+ ]
+ ]
+ },
+ {
+ "input": "&Lambda",
+ "description": "Bad named entity: Lambda without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Lambda"
+ ]
+ ]
+ },
+ {
+ "input": "Λ",
+ "description": "Named entity: Lambda; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u039b"
+ ]
+ ]
+ },
+ {
+ "input": "&Lang",
+ "description": "Bad named entity: Lang without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Lang"
+ ]
+ ]
+ },
+ {
+ "input": "⟪",
+ "description": "Named entity: Lang; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27ea"
+ ]
+ ]
+ },
+ {
+ "input": "&Laplacetrf",
+ "description": "Bad named entity: Laplacetrf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Laplacetrf"
+ ]
+ ]
+ },
+ {
+ "input": "ℒ",
+ "description": "Named entity: Laplacetrf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2112"
+ ]
+ ]
+ },
+ {
+ "input": "&Larr",
+ "description": "Bad named entity: Larr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Larr"
+ ]
+ ]
+ },
+ {
+ "input": "↞",
+ "description": "Named entity: Larr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u219e"
+ ]
+ ]
+ },
+ {
+ "input": "&Lcaron",
+ "description": "Bad named entity: Lcaron without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Lcaron"
+ ]
+ ]
+ },
+ {
+ "input": "Ľ",
+ "description": "Named entity: Lcaron; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u013d"
+ ]
+ ]
+ },
+ {
+ "input": "&Lcedil",
+ "description": "Bad named entity: Lcedil without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Lcedil"
+ ]
+ ]
+ },
+ {
+ "input": "Ļ",
+ "description": "Named entity: Lcedil; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u013b"
+ ]
+ ]
+ },
+ {
+ "input": "&Lcy",
+ "description": "Bad named entity: Lcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Lcy"
+ ]
+ ]
+ },
+ {
+ "input": "Л",
+ "description": "Named entity: Lcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u041b"
+ ]
+ ]
+ },
+ {
+ "input": "&LeftAngleBracket",
+ "description": "Bad named entity: LeftAngleBracket without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LeftAngleBracket"
+ ]
+ ]
+ },
+ {
+ "input": "⟨",
+ "description": "Named entity: LeftAngleBracket; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27e8"
+ ]
+ ]
+ },
+ {
+ "input": "&LeftArrow",
+ "description": "Bad named entity: LeftArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LeftArrow"
+ ]
+ ]
+ },
+ {
+ "input": "←",
+ "description": "Named entity: LeftArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2190"
+ ]
+ ]
+ },
+ {
+ "input": "&LeftArrowBar",
+ "description": "Bad named entity: LeftArrowBar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LeftArrowBar"
+ ]
+ ]
+ },
+ {
+ "input": "⇤",
+ "description": "Named entity: LeftArrowBar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21e4"
+ ]
+ ]
+ },
+ {
+ "input": "&LeftArrowRightArrow",
+ "description": "Bad named entity: LeftArrowRightArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LeftArrowRightArrow"
+ ]
+ ]
+ },
+ {
+ "input": "⇆",
+ "description": "Named entity: LeftArrowRightArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21c6"
+ ]
+ ]
+ },
+ {
+ "input": "&LeftCeiling",
+ "description": "Bad named entity: LeftCeiling without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LeftCeiling"
+ ]
+ ]
+ },
+ {
+ "input": "⌈",
+ "description": "Named entity: LeftCeiling; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2308"
+ ]
+ ]
+ },
+ {
+ "input": "&LeftDoubleBracket",
+ "description": "Bad named entity: LeftDoubleBracket without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LeftDoubleBracket"
+ ]
+ ]
+ },
+ {
+ "input": "⟦",
+ "description": "Named entity: LeftDoubleBracket; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27e6"
+ ]
+ ]
+ },
+ {
+ "input": "&LeftDownTeeVector",
+ "description": "Bad named entity: LeftDownTeeVector without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LeftDownTeeVector"
+ ]
+ ]
+ },
+ {
+ "input": "⥡",
+ "description": "Named entity: LeftDownTeeVector; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2961"
+ ]
+ ]
+ },
+ {
+ "input": "&LeftDownVector",
+ "description": "Bad named entity: LeftDownVector without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LeftDownVector"
+ ]
+ ]
+ },
+ {
+ "input": "⇃",
+ "description": "Named entity: LeftDownVector; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21c3"
+ ]
+ ]
+ },
+ {
+ "input": "&LeftDownVectorBar",
+ "description": "Bad named entity: LeftDownVectorBar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LeftDownVectorBar"
+ ]
+ ]
+ },
+ {
+ "input": "⥙",
+ "description": "Named entity: LeftDownVectorBar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2959"
+ ]
+ ]
+ },
+ {
+ "input": "&LeftFloor",
+ "description": "Bad named entity: LeftFloor without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LeftFloor"
+ ]
+ ]
+ },
+ {
+ "input": "⌊",
+ "description": "Named entity: LeftFloor; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u230a"
+ ]
+ ]
+ },
+ {
+ "input": "&LeftRightArrow",
+ "description": "Bad named entity: LeftRightArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LeftRightArrow"
+ ]
+ ]
+ },
+ {
+ "input": "↔",
+ "description": "Named entity: LeftRightArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2194"
+ ]
+ ]
+ },
+ {
+ "input": "&LeftRightVector",
+ "description": "Bad named entity: LeftRightVector without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LeftRightVector"
+ ]
+ ]
+ },
+ {
+ "input": "⥎",
+ "description": "Named entity: LeftRightVector; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u294e"
+ ]
+ ]
+ },
+ {
+ "input": "&LeftTee",
+ "description": "Bad named entity: LeftTee without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LeftTee"
+ ]
+ ]
+ },
+ {
+ "input": "⊣",
+ "description": "Named entity: LeftTee; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22a3"
+ ]
+ ]
+ },
+ {
+ "input": "&LeftTeeArrow",
+ "description": "Bad named entity: LeftTeeArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LeftTeeArrow"
+ ]
+ ]
+ },
+ {
+ "input": "↤",
+ "description": "Named entity: LeftTeeArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21a4"
+ ]
+ ]
+ },
+ {
+ "input": "&LeftTeeVector",
+ "description": "Bad named entity: LeftTeeVector without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LeftTeeVector"
+ ]
+ ]
+ },
+ {
+ "input": "⥚",
+ "description": "Named entity: LeftTeeVector; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u295a"
+ ]
+ ]
+ },
+ {
+ "input": "&LeftTriangle",
+ "description": "Bad named entity: LeftTriangle without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LeftTriangle"
+ ]
+ ]
+ },
+ {
+ "input": "⊲",
+ "description": "Named entity: LeftTriangle; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22b2"
+ ]
+ ]
+ },
+ {
+ "input": "&LeftTriangleBar",
+ "description": "Bad named entity: LeftTriangleBar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LeftTriangleBar"
+ ]
+ ]
+ },
+ {
+ "input": "⧏",
+ "description": "Named entity: LeftTriangleBar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29cf"
+ ]
+ ]
+ },
+ {
+ "input": "&LeftTriangleEqual",
+ "description": "Bad named entity: LeftTriangleEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LeftTriangleEqual"
+ ]
+ ]
+ },
+ {
+ "input": "⊴",
+ "description": "Named entity: LeftTriangleEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22b4"
+ ]
+ ]
+ },
+ {
+ "input": "&LeftUpDownVector",
+ "description": "Bad named entity: LeftUpDownVector without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LeftUpDownVector"
+ ]
+ ]
+ },
+ {
+ "input": "⥑",
+ "description": "Named entity: LeftUpDownVector; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2951"
+ ]
+ ]
+ },
+ {
+ "input": "&LeftUpTeeVector",
+ "description": "Bad named entity: LeftUpTeeVector without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LeftUpTeeVector"
+ ]
+ ]
+ },
+ {
+ "input": "⥠",
+ "description": "Named entity: LeftUpTeeVector; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2960"
+ ]
+ ]
+ },
+ {
+ "input": "&LeftUpVector",
+ "description": "Bad named entity: LeftUpVector without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LeftUpVector"
+ ]
+ ]
+ },
+ {
+ "input": "↿",
+ "description": "Named entity: LeftUpVector; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21bf"
+ ]
+ ]
+ },
+ {
+ "input": "&LeftUpVectorBar",
+ "description": "Bad named entity: LeftUpVectorBar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LeftUpVectorBar"
+ ]
+ ]
+ },
+ {
+ "input": "⥘",
+ "description": "Named entity: LeftUpVectorBar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2958"
+ ]
+ ]
+ },
+ {
+ "input": "&LeftVector",
+ "description": "Bad named entity: LeftVector without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LeftVector"
+ ]
+ ]
+ },
+ {
+ "input": "↼",
+ "description": "Named entity: LeftVector; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21bc"
+ ]
+ ]
+ },
+ {
+ "input": "&LeftVectorBar",
+ "description": "Bad named entity: LeftVectorBar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LeftVectorBar"
+ ]
+ ]
+ },
+ {
+ "input": "⥒",
+ "description": "Named entity: LeftVectorBar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2952"
+ ]
+ ]
+ },
+ {
+ "input": "&Leftarrow",
+ "description": "Bad named entity: Leftarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Leftarrow"
+ ]
+ ]
+ },
+ {
+ "input": "⇐",
+ "description": "Named entity: Leftarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21d0"
+ ]
+ ]
+ },
+ {
+ "input": "&Leftrightarrow",
+ "description": "Bad named entity: Leftrightarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Leftrightarrow"
+ ]
+ ]
+ },
+ {
+ "input": "⇔",
+ "description": "Named entity: Leftrightarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21d4"
+ ]
+ ]
+ },
+ {
+ "input": "&LessEqualGreater",
+ "description": "Bad named entity: LessEqualGreater without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LessEqualGreater"
+ ]
+ ]
+ },
+ {
+ "input": "⋚",
+ "description": "Named entity: LessEqualGreater; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22da"
+ ]
+ ]
+ },
+ {
+ "input": "&LessFullEqual",
+ "description": "Bad named entity: LessFullEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LessFullEqual"
+ ]
+ ]
+ },
+ {
+ "input": "≦",
+ "description": "Named entity: LessFullEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2266"
+ ]
+ ]
+ },
+ {
+ "input": "&LessGreater",
+ "description": "Bad named entity: LessGreater without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LessGreater"
+ ]
+ ]
+ },
+ {
+ "input": "≶",
+ "description": "Named entity: LessGreater; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2276"
+ ]
+ ]
+ },
+ {
+ "input": "&LessLess",
+ "description": "Bad named entity: LessLess without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LessLess"
+ ]
+ ]
+ },
+ {
+ "input": "⪡",
+ "description": "Named entity: LessLess; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2aa1"
+ ]
+ ]
+ },
+ {
+ "input": "&LessSlantEqual",
+ "description": "Bad named entity: LessSlantEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LessSlantEqual"
+ ]
+ ]
+ },
+ {
+ "input": "⩽",
+ "description": "Named entity: LessSlantEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a7d"
+ ]
+ ]
+ },
+ {
+ "input": "&LessTilde",
+ "description": "Bad named entity: LessTilde without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LessTilde"
+ ]
+ ]
+ },
+ {
+ "input": "≲",
+ "description": "Named entity: LessTilde; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2272"
+ ]
+ ]
+ },
+ {
+ "input": "&Lfr",
+ "description": "Bad named entity: Lfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Lfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔏",
+ "description": "Named entity: Lfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd0f"
+ ]
+ ]
+ },
+ {
+ "input": "&Ll",
+ "description": "Bad named entity: Ll without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Ll"
+ ]
+ ]
+ },
+ {
+ "input": "⋘",
+ "description": "Named entity: Ll; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22d8"
+ ]
+ ]
+ },
+ {
+ "input": "&Lleftarrow",
+ "description": "Bad named entity: Lleftarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Lleftarrow"
+ ]
+ ]
+ },
+ {
+ "input": "⇚",
+ "description": "Named entity: Lleftarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21da"
+ ]
+ ]
+ },
+ {
+ "input": "&Lmidot",
+ "description": "Bad named entity: Lmidot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Lmidot"
+ ]
+ ]
+ },
+ {
+ "input": "Ŀ",
+ "description": "Named entity: Lmidot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u013f"
+ ]
+ ]
+ },
+ {
+ "input": "&LongLeftArrow",
+ "description": "Bad named entity: LongLeftArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LongLeftArrow"
+ ]
+ ]
+ },
+ {
+ "input": "⟵",
+ "description": "Named entity: LongLeftArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27f5"
+ ]
+ ]
+ },
+ {
+ "input": "&LongLeftRightArrow",
+ "description": "Bad named entity: LongLeftRightArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LongLeftRightArrow"
+ ]
+ ]
+ },
+ {
+ "input": "⟷",
+ "description": "Named entity: LongLeftRightArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27f7"
+ ]
+ ]
+ },
+ {
+ "input": "&LongRightArrow",
+ "description": "Bad named entity: LongRightArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LongRightArrow"
+ ]
+ ]
+ },
+ {
+ "input": "⟶",
+ "description": "Named entity: LongRightArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27f6"
+ ]
+ ]
+ },
+ {
+ "input": "&Longleftarrow",
+ "description": "Bad named entity: Longleftarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Longleftarrow"
+ ]
+ ]
+ },
+ {
+ "input": "⟸",
+ "description": "Named entity: Longleftarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27f8"
+ ]
+ ]
+ },
+ {
+ "input": "&Longleftrightarrow",
+ "description": "Bad named entity: Longleftrightarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Longleftrightarrow"
+ ]
+ ]
+ },
+ {
+ "input": "⟺",
+ "description": "Named entity: Longleftrightarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27fa"
+ ]
+ ]
+ },
+ {
+ "input": "&Longrightarrow",
+ "description": "Bad named entity: Longrightarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Longrightarrow"
+ ]
+ ]
+ },
+ {
+ "input": "⟹",
+ "description": "Named entity: Longrightarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27f9"
+ ]
+ ]
+ },
+ {
+ "input": "&Lopf",
+ "description": "Bad named entity: Lopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Lopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕃",
+ "description": "Named entity: Lopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd43"
+ ]
+ ]
+ },
+ {
+ "input": "&LowerLeftArrow",
+ "description": "Bad named entity: LowerLeftArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LowerLeftArrow"
+ ]
+ ]
+ },
+ {
+ "input": "↙",
+ "description": "Named entity: LowerLeftArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2199"
+ ]
+ ]
+ },
+ {
+ "input": "&LowerRightArrow",
+ "description": "Bad named entity: LowerRightArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&LowerRightArrow"
+ ]
+ ]
+ },
+ {
+ "input": "↘",
+ "description": "Named entity: LowerRightArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2198"
+ ]
+ ]
+ },
+ {
+ "input": "&Lscr",
+ "description": "Bad named entity: Lscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Lscr"
+ ]
+ ]
+ },
+ {
+ "input": "ℒ",
+ "description": "Named entity: Lscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2112"
+ ]
+ ]
+ },
+ {
+ "input": "&Lsh",
+ "description": "Bad named entity: Lsh without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Lsh"
+ ]
+ ]
+ },
+ {
+ "input": "↰",
+ "description": "Named entity: Lsh; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21b0"
+ ]
+ ]
+ },
+ {
+ "input": "&Lstrok",
+ "description": "Bad named entity: Lstrok without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Lstrok"
+ ]
+ ]
+ },
+ {
+ "input": "Ł",
+ "description": "Named entity: Lstrok; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0141"
+ ]
+ ]
+ },
+ {
+ "input": "&Lt",
+ "description": "Bad named entity: Lt without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Lt"
+ ]
+ ]
+ },
+ {
+ "input": "≪",
+ "description": "Named entity: Lt; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u226a"
+ ]
+ ]
+ },
+ {
+ "input": "&Map",
+ "description": "Bad named entity: Map without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Map"
+ ]
+ ]
+ },
+ {
+ "input": "⤅",
+ "description": "Named entity: Map; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2905"
+ ]
+ ]
+ },
+ {
+ "input": "&Mcy",
+ "description": "Bad named entity: Mcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Mcy"
+ ]
+ ]
+ },
+ {
+ "input": "М",
+ "description": "Named entity: Mcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u041c"
+ ]
+ ]
+ },
+ {
+ "input": "&MediumSpace",
+ "description": "Bad named entity: MediumSpace without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&MediumSpace"
+ ]
+ ]
+ },
+ {
+ "input": " ",
+ "description": "Named entity: MediumSpace; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u205f"
+ ]
+ ]
+ },
+ {
+ "input": "&Mellintrf",
+ "description": "Bad named entity: Mellintrf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Mellintrf"
+ ]
+ ]
+ },
+ {
+ "input": "ℳ",
+ "description": "Named entity: Mellintrf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2133"
+ ]
+ ]
+ },
+ {
+ "input": "&Mfr",
+ "description": "Bad named entity: Mfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Mfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔐",
+ "description": "Named entity: Mfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd10"
+ ]
+ ]
+ },
+ {
+ "input": "&MinusPlus",
+ "description": "Bad named entity: MinusPlus without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&MinusPlus"
+ ]
+ ]
+ },
+ {
+ "input": "∓",
+ "description": "Named entity: MinusPlus; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2213"
+ ]
+ ]
+ },
+ {
+ "input": "&Mopf",
+ "description": "Bad named entity: Mopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Mopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕄",
+ "description": "Named entity: Mopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd44"
+ ]
+ ]
+ },
+ {
+ "input": "&Mscr",
+ "description": "Bad named entity: Mscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Mscr"
+ ]
+ ]
+ },
+ {
+ "input": "ℳ",
+ "description": "Named entity: Mscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2133"
+ ]
+ ]
+ },
+ {
+ "input": "&Mu",
+ "description": "Bad named entity: Mu without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Mu"
+ ]
+ ]
+ },
+ {
+ "input": "Μ",
+ "description": "Named entity: Mu; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u039c"
+ ]
+ ]
+ },
+ {
+ "input": "&NJcy",
+ "description": "Bad named entity: NJcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NJcy"
+ ]
+ ]
+ },
+ {
+ "input": "Њ",
+ "description": "Named entity: NJcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u040a"
+ ]
+ ]
+ },
+ {
+ "input": "&Nacute",
+ "description": "Bad named entity: Nacute without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Nacute"
+ ]
+ ]
+ },
+ {
+ "input": "Ń",
+ "description": "Named entity: Nacute; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0143"
+ ]
+ ]
+ },
+ {
+ "input": "&Ncaron",
+ "description": "Bad named entity: Ncaron without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Ncaron"
+ ]
+ ]
+ },
+ {
+ "input": "Ň",
+ "description": "Named entity: Ncaron; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0147"
+ ]
+ ]
+ },
+ {
+ "input": "&Ncedil",
+ "description": "Bad named entity: Ncedil without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Ncedil"
+ ]
+ ]
+ },
+ {
+ "input": "Ņ",
+ "description": "Named entity: Ncedil; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0145"
+ ]
+ ]
+ },
+ {
+ "input": "&Ncy",
+ "description": "Bad named entity: Ncy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Ncy"
+ ]
+ ]
+ },
+ {
+ "input": "Н",
+ "description": "Named entity: Ncy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u041d"
+ ]
+ ]
+ },
+ {
+ "input": "&NegativeMediumSpace",
+ "description": "Bad named entity: NegativeMediumSpace without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NegativeMediumSpace"
+ ]
+ ]
+ },
+ {
+ "input": "​",
+ "description": "Named entity: NegativeMediumSpace; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u200b"
+ ]
+ ]
+ },
+ {
+ "input": "&NegativeThickSpace",
+ "description": "Bad named entity: NegativeThickSpace without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NegativeThickSpace"
+ ]
+ ]
+ },
+ {
+ "input": "​",
+ "description": "Named entity: NegativeThickSpace; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u200b"
+ ]
+ ]
+ },
+ {
+ "input": "&NegativeThinSpace",
+ "description": "Bad named entity: NegativeThinSpace without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NegativeThinSpace"
+ ]
+ ]
+ },
+ {
+ "input": "​",
+ "description": "Named entity: NegativeThinSpace; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u200b"
+ ]
+ ]
+ },
+ {
+ "input": "&NegativeVeryThinSpace",
+ "description": "Bad named entity: NegativeVeryThinSpace without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NegativeVeryThinSpace"
+ ]
+ ]
+ },
+ {
+ "input": "​",
+ "description": "Named entity: NegativeVeryThinSpace; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u200b"
+ ]
+ ]
+ },
+ {
+ "input": "&NestedGreaterGreater",
+ "description": "Bad named entity: NestedGreaterGreater without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NestedGreaterGreater"
+ ]
+ ]
+ },
+ {
+ "input": "≫",
+ "description": "Named entity: NestedGreaterGreater; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u226b"
+ ]
+ ]
+ },
+ {
+ "input": "&NestedLessLess",
+ "description": "Bad named entity: NestedLessLess without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NestedLessLess"
+ ]
+ ]
+ },
+ {
+ "input": "≪",
+ "description": "Named entity: NestedLessLess; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u226a"
+ ]
+ ]
+ },
+ {
+ "input": "&NewLine",
+ "description": "Bad named entity: NewLine without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NewLine"
+ ]
+ ]
+ },
+ {
+ "input": "
",
+ "description": "Named entity: NewLine; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\n"
+ ]
+ ]
+ },
+ {
+ "input": "&Nfr",
+ "description": "Bad named entity: Nfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Nfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔑",
+ "description": "Named entity: Nfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd11"
+ ]
+ ]
+ },
+ {
+ "input": "&NoBreak",
+ "description": "Bad named entity: NoBreak without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NoBreak"
+ ]
+ ]
+ },
+ {
+ "input": "⁠",
+ "description": "Named entity: NoBreak; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2060"
+ ]
+ ]
+ },
+ {
+ "input": "&NonBreakingSpace",
+ "description": "Bad named entity: NonBreakingSpace without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NonBreakingSpace"
+ ]
+ ]
+ },
+ {
+ "input": " ",
+ "description": "Named entity: NonBreakingSpace; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00a0"
+ ]
+ ]
+ },
+ {
+ "input": "&Nopf",
+ "description": "Bad named entity: Nopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Nopf"
+ ]
+ ]
+ },
+ {
+ "input": "ℕ",
+ "description": "Named entity: Nopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2115"
+ ]
+ ]
+ },
+ {
+ "input": "&Not",
+ "description": "Bad named entity: Not without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Not"
+ ]
+ ]
+ },
+ {
+ "input": "⫬",
+ "description": "Named entity: Not; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2aec"
+ ]
+ ]
+ },
+ {
+ "input": "&NotCongruent",
+ "description": "Bad named entity: NotCongruent without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotCongruent"
+ ]
+ ]
+ },
+ {
+ "input": "≢",
+ "description": "Named entity: NotCongruent; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2262"
+ ]
+ ]
+ },
+ {
+ "input": "&NotCupCap",
+ "description": "Bad named entity: NotCupCap without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotCupCap"
+ ]
+ ]
+ },
+ {
+ "input": "≭",
+ "description": "Named entity: NotCupCap; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u226d"
+ ]
+ ]
+ },
+ {
+ "input": "&NotDoubleVerticalBar",
+ "description": "Bad named entity: NotDoubleVerticalBar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotDoubleVerticalBar"
+ ]
+ ]
+ },
+ {
+ "input": "∦",
+ "description": "Named entity: NotDoubleVerticalBar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2226"
+ ]
+ ]
+ },
+ {
+ "input": "&NotElement",
+ "description": "Bad named entity: NotElement without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotElement"
+ ]
+ ]
+ },
+ {
+ "input": "∉",
+ "description": "Named entity: NotElement; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2209"
+ ]
+ ]
+ },
+ {
+ "input": "&NotEqual",
+ "description": "Bad named entity: NotEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotEqual"
+ ]
+ ]
+ },
+ {
+ "input": "≠",
+ "description": "Named entity: NotEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2260"
+ ]
+ ]
+ },
+ {
+ "input": "&NotEqualTilde",
+ "description": "Bad named entity: NotEqualTilde without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotEqualTilde"
+ ]
+ ]
+ },
+ {
+ "input": "≂̸",
+ "description": "Named entity: NotEqualTilde; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2242\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&NotExists",
+ "description": "Bad named entity: NotExists without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotExists"
+ ]
+ ]
+ },
+ {
+ "input": "∄",
+ "description": "Named entity: NotExists; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2204"
+ ]
+ ]
+ },
+ {
+ "input": "&NotGreater",
+ "description": "Bad named entity: NotGreater without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotGreater"
+ ]
+ ]
+ },
+ {
+ "input": "≯",
+ "description": "Named entity: NotGreater; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u226f"
+ ]
+ ]
+ },
+ {
+ "input": "&NotGreaterEqual",
+ "description": "Bad named entity: NotGreaterEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotGreaterEqual"
+ ]
+ ]
+ },
+ {
+ "input": "≱",
+ "description": "Named entity: NotGreaterEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2271"
+ ]
+ ]
+ },
+ {
+ "input": "&NotGreaterFullEqual",
+ "description": "Bad named entity: NotGreaterFullEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotGreaterFullEqual"
+ ]
+ ]
+ },
+ {
+ "input": "≧̸",
+ "description": "Named entity: NotGreaterFullEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2267\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&NotGreaterGreater",
+ "description": "Bad named entity: NotGreaterGreater without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotGreaterGreater"
+ ]
+ ]
+ },
+ {
+ "input": "≫̸",
+ "description": "Named entity: NotGreaterGreater; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u226b\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&NotGreaterLess",
+ "description": "Bad named entity: NotGreaterLess without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotGreaterLess"
+ ]
+ ]
+ },
+ {
+ "input": "≹",
+ "description": "Named entity: NotGreaterLess; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2279"
+ ]
+ ]
+ },
+ {
+ "input": "&NotGreaterSlantEqual",
+ "description": "Bad named entity: NotGreaterSlantEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotGreaterSlantEqual"
+ ]
+ ]
+ },
+ {
+ "input": "⩾̸",
+ "description": "Named entity: NotGreaterSlantEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a7e\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&NotGreaterTilde",
+ "description": "Bad named entity: NotGreaterTilde without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotGreaterTilde"
+ ]
+ ]
+ },
+ {
+ "input": "≵",
+ "description": "Named entity: NotGreaterTilde; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2275"
+ ]
+ ]
+ },
+ {
+ "input": "&NotHumpDownHump",
+ "description": "Bad named entity: NotHumpDownHump without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotHumpDownHump"
+ ]
+ ]
+ },
+ {
+ "input": "≎̸",
+ "description": "Named entity: NotHumpDownHump; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u224e\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&NotHumpEqual",
+ "description": "Bad named entity: NotHumpEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotHumpEqual"
+ ]
+ ]
+ },
+ {
+ "input": "≏̸",
+ "description": "Named entity: NotHumpEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u224f\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&NotLeftTriangle",
+ "description": "Bad named entity: NotLeftTriangle without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotLeftTriangle"
+ ]
+ ]
+ },
+ {
+ "input": "⋪",
+ "description": "Named entity: NotLeftTriangle; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22ea"
+ ]
+ ]
+ },
+ {
+ "input": "&NotLeftTriangleBar",
+ "description": "Bad named entity: NotLeftTriangleBar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotLeftTriangleBar"
+ ]
+ ]
+ },
+ {
+ "input": "⧏̸",
+ "description": "Named entity: NotLeftTriangleBar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29cf\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&NotLeftTriangleEqual",
+ "description": "Bad named entity: NotLeftTriangleEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotLeftTriangleEqual"
+ ]
+ ]
+ },
+ {
+ "input": "⋬",
+ "description": "Named entity: NotLeftTriangleEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22ec"
+ ]
+ ]
+ },
+ {
+ "input": "&NotLess",
+ "description": "Bad named entity: NotLess without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotLess"
+ ]
+ ]
+ },
+ {
+ "input": "≮",
+ "description": "Named entity: NotLess; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u226e"
+ ]
+ ]
+ },
+ {
+ "input": "&NotLessEqual",
+ "description": "Bad named entity: NotLessEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotLessEqual"
+ ]
+ ]
+ },
+ {
+ "input": "≰",
+ "description": "Named entity: NotLessEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2270"
+ ]
+ ]
+ },
+ {
+ "input": "&NotLessGreater",
+ "description": "Bad named entity: NotLessGreater without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotLessGreater"
+ ]
+ ]
+ },
+ {
+ "input": "≸",
+ "description": "Named entity: NotLessGreater; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2278"
+ ]
+ ]
+ },
+ {
+ "input": "&NotLessLess",
+ "description": "Bad named entity: NotLessLess without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotLessLess"
+ ]
+ ]
+ },
+ {
+ "input": "≪̸",
+ "description": "Named entity: NotLessLess; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u226a\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&NotLessSlantEqual",
+ "description": "Bad named entity: NotLessSlantEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotLessSlantEqual"
+ ]
+ ]
+ },
+ {
+ "input": "⩽̸",
+ "description": "Named entity: NotLessSlantEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a7d\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&NotLessTilde",
+ "description": "Bad named entity: NotLessTilde without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotLessTilde"
+ ]
+ ]
+ },
+ {
+ "input": "≴",
+ "description": "Named entity: NotLessTilde; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2274"
+ ]
+ ]
+ },
+ {
+ "input": "&NotNestedGreaterGreater",
+ "description": "Bad named entity: NotNestedGreaterGreater without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotNestedGreaterGreater"
+ ]
+ ]
+ },
+ {
+ "input": "⪢̸",
+ "description": "Named entity: NotNestedGreaterGreater; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2aa2\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&NotNestedLessLess",
+ "description": "Bad named entity: NotNestedLessLess without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotNestedLessLess"
+ ]
+ ]
+ },
+ {
+ "input": "⪡̸",
+ "description": "Named entity: NotNestedLessLess; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2aa1\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&NotPrecedes",
+ "description": "Bad named entity: NotPrecedes without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotPrecedes"
+ ]
+ ]
+ },
+ {
+ "input": "⊀",
+ "description": "Named entity: NotPrecedes; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2280"
+ ]
+ ]
+ },
+ {
+ "input": "&NotPrecedesEqual",
+ "description": "Bad named entity: NotPrecedesEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotPrecedesEqual"
+ ]
+ ]
+ },
+ {
+ "input": "⪯̸",
+ "description": "Named entity: NotPrecedesEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2aaf\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&NotPrecedesSlantEqual",
+ "description": "Bad named entity: NotPrecedesSlantEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotPrecedesSlantEqual"
+ ]
+ ]
+ },
+ {
+ "input": "⋠",
+ "description": "Named entity: NotPrecedesSlantEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22e0"
+ ]
+ ]
+ },
+ {
+ "input": "&NotReverseElement",
+ "description": "Bad named entity: NotReverseElement without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotReverseElement"
+ ]
+ ]
+ },
+ {
+ "input": "∌",
+ "description": "Named entity: NotReverseElement; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u220c"
+ ]
+ ]
+ },
+ {
+ "input": "&NotRightTriangle",
+ "description": "Bad named entity: NotRightTriangle without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotRightTriangle"
+ ]
+ ]
+ },
+ {
+ "input": "⋫",
+ "description": "Named entity: NotRightTriangle; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22eb"
+ ]
+ ]
+ },
+ {
+ "input": "&NotRightTriangleBar",
+ "description": "Bad named entity: NotRightTriangleBar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotRightTriangleBar"
+ ]
+ ]
+ },
+ {
+ "input": "⧐̸",
+ "description": "Named entity: NotRightTriangleBar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29d0\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&NotRightTriangleEqual",
+ "description": "Bad named entity: NotRightTriangleEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotRightTriangleEqual"
+ ]
+ ]
+ },
+ {
+ "input": "⋭",
+ "description": "Named entity: NotRightTriangleEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22ed"
+ ]
+ ]
+ },
+ {
+ "input": "&NotSquareSubset",
+ "description": "Bad named entity: NotSquareSubset without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotSquareSubset"
+ ]
+ ]
+ },
+ {
+ "input": "⊏̸",
+ "description": "Named entity: NotSquareSubset; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u228f\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&NotSquareSubsetEqual",
+ "description": "Bad named entity: NotSquareSubsetEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotSquareSubsetEqual"
+ ]
+ ]
+ },
+ {
+ "input": "⋢",
+ "description": "Named entity: NotSquareSubsetEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22e2"
+ ]
+ ]
+ },
+ {
+ "input": "&NotSquareSuperset",
+ "description": "Bad named entity: NotSquareSuperset without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotSquareSuperset"
+ ]
+ ]
+ },
+ {
+ "input": "⊐̸",
+ "description": "Named entity: NotSquareSuperset; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2290\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&NotSquareSupersetEqual",
+ "description": "Bad named entity: NotSquareSupersetEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotSquareSupersetEqual"
+ ]
+ ]
+ },
+ {
+ "input": "⋣",
+ "description": "Named entity: NotSquareSupersetEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22e3"
+ ]
+ ]
+ },
+ {
+ "input": "&NotSubset",
+ "description": "Bad named entity: NotSubset without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotSubset"
+ ]
+ ]
+ },
+ {
+ "input": "⊂⃒",
+ "description": "Named entity: NotSubset; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2282\u20d2"
+ ]
+ ]
+ },
+ {
+ "input": "&NotSubsetEqual",
+ "description": "Bad named entity: NotSubsetEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotSubsetEqual"
+ ]
+ ]
+ },
+ {
+ "input": "⊈",
+ "description": "Named entity: NotSubsetEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2288"
+ ]
+ ]
+ },
+ {
+ "input": "&NotSucceeds",
+ "description": "Bad named entity: NotSucceeds without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotSucceeds"
+ ]
+ ]
+ },
+ {
+ "input": "⊁",
+ "description": "Named entity: NotSucceeds; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2281"
+ ]
+ ]
+ },
+ {
+ "input": "&NotSucceedsEqual",
+ "description": "Bad named entity: NotSucceedsEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotSucceedsEqual"
+ ]
+ ]
+ },
+ {
+ "input": "⪰̸",
+ "description": "Named entity: NotSucceedsEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ab0\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&NotSucceedsSlantEqual",
+ "description": "Bad named entity: NotSucceedsSlantEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotSucceedsSlantEqual"
+ ]
+ ]
+ },
+ {
+ "input": "⋡",
+ "description": "Named entity: NotSucceedsSlantEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22e1"
+ ]
+ ]
+ },
+ {
+ "input": "&NotSucceedsTilde",
+ "description": "Bad named entity: NotSucceedsTilde without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotSucceedsTilde"
+ ]
+ ]
+ },
+ {
+ "input": "≿̸",
+ "description": "Named entity: NotSucceedsTilde; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u227f\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&NotSuperset",
+ "description": "Bad named entity: NotSuperset without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotSuperset"
+ ]
+ ]
+ },
+ {
+ "input": "⊃⃒",
+ "description": "Named entity: NotSuperset; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2283\u20d2"
+ ]
+ ]
+ },
+ {
+ "input": "&NotSupersetEqual",
+ "description": "Bad named entity: NotSupersetEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotSupersetEqual"
+ ]
+ ]
+ },
+ {
+ "input": "⊉",
+ "description": "Named entity: NotSupersetEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2289"
+ ]
+ ]
+ },
+ {
+ "input": "&NotTilde",
+ "description": "Bad named entity: NotTilde without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotTilde"
+ ]
+ ]
+ },
+ {
+ "input": "≁",
+ "description": "Named entity: NotTilde; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2241"
+ ]
+ ]
+ },
+ {
+ "input": "&NotTildeEqual",
+ "description": "Bad named entity: NotTildeEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotTildeEqual"
+ ]
+ ]
+ },
+ {
+ "input": "≄",
+ "description": "Named entity: NotTildeEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2244"
+ ]
+ ]
+ },
+ {
+ "input": "&NotTildeFullEqual",
+ "description": "Bad named entity: NotTildeFullEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotTildeFullEqual"
+ ]
+ ]
+ },
+ {
+ "input": "≇",
+ "description": "Named entity: NotTildeFullEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2247"
+ ]
+ ]
+ },
+ {
+ "input": "&NotTildeTilde",
+ "description": "Bad named entity: NotTildeTilde without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotTildeTilde"
+ ]
+ ]
+ },
+ {
+ "input": "≉",
+ "description": "Named entity: NotTildeTilde; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2249"
+ ]
+ ]
+ },
+ {
+ "input": "&NotVerticalBar",
+ "description": "Bad named entity: NotVerticalBar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&NotVerticalBar"
+ ]
+ ]
+ },
+ {
+ "input": "∤",
+ "description": "Named entity: NotVerticalBar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2224"
+ ]
+ ]
+ },
+ {
+ "input": "&Nscr",
+ "description": "Bad named entity: Nscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Nscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝒩",
+ "description": "Named entity: Nscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udca9"
+ ]
+ ]
+ },
+ {
+ "input": "Ñ",
+ "description": "Named entity: Ntilde without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00d1"
+ ]
+ ]
+ },
+ {
+ "input": "Ñ",
+ "description": "Named entity: Ntilde; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00d1"
+ ]
+ ]
+ },
+ {
+ "input": "&Nu",
+ "description": "Bad named entity: Nu without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Nu"
+ ]
+ ]
+ },
+ {
+ "input": "Ν",
+ "description": "Named entity: Nu; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u039d"
+ ]
+ ]
+ },
+ {
+ "input": "&OElig",
+ "description": "Bad named entity: OElig without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&OElig"
+ ]
+ ]
+ },
+ {
+ "input": "Œ",
+ "description": "Named entity: OElig; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0152"
+ ]
+ ]
+ },
+ {
+ "input": "Ó",
+ "description": "Named entity: Oacute without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00d3"
+ ]
+ ]
+ },
+ {
+ "input": "Ó",
+ "description": "Named entity: Oacute; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00d3"
+ ]
+ ]
+ },
+ {
+ "input": "Ô",
+ "description": "Named entity: Ocirc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00d4"
+ ]
+ ]
+ },
+ {
+ "input": "Ô",
+ "description": "Named entity: Ocirc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00d4"
+ ]
+ ]
+ },
+ {
+ "input": "&Ocy",
+ "description": "Bad named entity: Ocy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Ocy"
+ ]
+ ]
+ },
+ {
+ "input": "О",
+ "description": "Named entity: Ocy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u041e"
+ ]
+ ]
+ },
+ {
+ "input": "&Odblac",
+ "description": "Bad named entity: Odblac without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Odblac"
+ ]
+ ]
+ },
+ {
+ "input": "Ő",
+ "description": "Named entity: Odblac; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0150"
+ ]
+ ]
+ },
+ {
+ "input": "&Ofr",
+ "description": "Bad named entity: Ofr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Ofr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔒",
+ "description": "Named entity: Ofr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd12"
+ ]
+ ]
+ },
+ {
+ "input": "Ò",
+ "description": "Named entity: Ograve without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00d2"
+ ]
+ ]
+ },
+ {
+ "input": "Ò",
+ "description": "Named entity: Ograve; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00d2"
+ ]
+ ]
+ },
+ {
+ "input": "&Omacr",
+ "description": "Bad named entity: Omacr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Omacr"
+ ]
+ ]
+ },
+ {
+ "input": "Ō",
+ "description": "Named entity: Omacr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u014c"
+ ]
+ ]
+ },
+ {
+ "input": "&Omega",
+ "description": "Bad named entity: Omega without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Omega"
+ ]
+ ]
+ },
+ {
+ "input": "Ω",
+ "description": "Named entity: Omega; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03a9"
+ ]
+ ]
+ },
+ {
+ "input": "&Omicron",
+ "description": "Bad named entity: Omicron without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Omicron"
+ ]
+ ]
+ },
+ {
+ "input": "Ο",
+ "description": "Named entity: Omicron; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u039f"
+ ]
+ ]
+ },
+ {
+ "input": "&Oopf",
+ "description": "Bad named entity: Oopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Oopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕆",
+ "description": "Named entity: Oopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd46"
+ ]
+ ]
+ },
+ {
+ "input": "&OpenCurlyDoubleQuote",
+ "description": "Bad named entity: OpenCurlyDoubleQuote without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&OpenCurlyDoubleQuote"
+ ]
+ ]
+ },
+ {
+ "input": "“",
+ "description": "Named entity: OpenCurlyDoubleQuote; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u201c"
+ ]
+ ]
+ },
+ {
+ "input": "&OpenCurlyQuote",
+ "description": "Bad named entity: OpenCurlyQuote without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&OpenCurlyQuote"
+ ]
+ ]
+ },
+ {
+ "input": "‘",
+ "description": "Named entity: OpenCurlyQuote; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2018"
+ ]
+ ]
+ },
+ {
+ "input": "&Or",
+ "description": "Bad named entity: Or without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Or"
+ ]
+ ]
+ },
+ {
+ "input": "⩔",
+ "description": "Named entity: Or; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a54"
+ ]
+ ]
+ },
+ {
+ "input": "&Oscr",
+ "description": "Bad named entity: Oscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Oscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝒪",
+ "description": "Named entity: Oscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udcaa"
+ ]
+ ]
+ },
+ {
+ "input": "Ø",
+ "description": "Named entity: Oslash without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00d8"
+ ]
+ ]
+ },
+ {
+ "input": "Ø",
+ "description": "Named entity: Oslash; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00d8"
+ ]
+ ]
+ },
+ {
+ "input": "Õ",
+ "description": "Named entity: Otilde without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00d5"
+ ]
+ ]
+ },
+ {
+ "input": "Õ",
+ "description": "Named entity: Otilde; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00d5"
+ ]
+ ]
+ },
+ {
+ "input": "&Otimes",
+ "description": "Bad named entity: Otimes without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Otimes"
+ ]
+ ]
+ },
+ {
+ "input": "⨷",
+ "description": "Named entity: Otimes; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a37"
+ ]
+ ]
+ },
+ {
+ "input": "Ö",
+ "description": "Named entity: Ouml without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00d6"
+ ]
+ ]
+ },
+ {
+ "input": "Ö",
+ "description": "Named entity: Ouml; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00d6"
+ ]
+ ]
+ },
+ {
+ "input": "&OverBar",
+ "description": "Bad named entity: OverBar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&OverBar"
+ ]
+ ]
+ },
+ {
+ "input": "‾",
+ "description": "Named entity: OverBar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u203e"
+ ]
+ ]
+ },
+ {
+ "input": "&OverBrace",
+ "description": "Bad named entity: OverBrace without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&OverBrace"
+ ]
+ ]
+ },
+ {
+ "input": "⏞",
+ "description": "Named entity: OverBrace; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u23de"
+ ]
+ ]
+ },
+ {
+ "input": "&OverBracket",
+ "description": "Bad named entity: OverBracket without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&OverBracket"
+ ]
+ ]
+ },
+ {
+ "input": "⎴",
+ "description": "Named entity: OverBracket; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u23b4"
+ ]
+ ]
+ },
+ {
+ "input": "&OverParenthesis",
+ "description": "Bad named entity: OverParenthesis without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&OverParenthesis"
+ ]
+ ]
+ },
+ {
+ "input": "⏜",
+ "description": "Named entity: OverParenthesis; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u23dc"
+ ]
+ ]
+ },
+ {
+ "input": "&PartialD",
+ "description": "Bad named entity: PartialD without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&PartialD"
+ ]
+ ]
+ },
+ {
+ "input": "∂",
+ "description": "Named entity: PartialD; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2202"
+ ]
+ ]
+ },
+ {
+ "input": "&Pcy",
+ "description": "Bad named entity: Pcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Pcy"
+ ]
+ ]
+ },
+ {
+ "input": "П",
+ "description": "Named entity: Pcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u041f"
+ ]
+ ]
+ },
+ {
+ "input": "&Pfr",
+ "description": "Bad named entity: Pfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Pfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔓",
+ "description": "Named entity: Pfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd13"
+ ]
+ ]
+ },
+ {
+ "input": "&Phi",
+ "description": "Bad named entity: Phi without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Phi"
+ ]
+ ]
+ },
+ {
+ "input": "Φ",
+ "description": "Named entity: Phi; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03a6"
+ ]
+ ]
+ },
+ {
+ "input": "&Pi",
+ "description": "Bad named entity: Pi without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Pi"
+ ]
+ ]
+ },
+ {
+ "input": "Π",
+ "description": "Named entity: Pi; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03a0"
+ ]
+ ]
+ },
+ {
+ "input": "&PlusMinus",
+ "description": "Bad named entity: PlusMinus without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&PlusMinus"
+ ]
+ ]
+ },
+ {
+ "input": "±",
+ "description": "Named entity: PlusMinus; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00b1"
+ ]
+ ]
+ },
+ {
+ "input": "&Poincareplane",
+ "description": "Bad named entity: Poincareplane without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Poincareplane"
+ ]
+ ]
+ },
+ {
+ "input": "ℌ",
+ "description": "Named entity: Poincareplane; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u210c"
+ ]
+ ]
+ },
+ {
+ "input": "&Popf",
+ "description": "Bad named entity: Popf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Popf"
+ ]
+ ]
+ },
+ {
+ "input": "ℙ",
+ "description": "Named entity: Popf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2119"
+ ]
+ ]
+ },
+ {
+ "input": "&Pr",
+ "description": "Bad named entity: Pr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Pr"
+ ]
+ ]
+ },
+ {
+ "input": "⪻",
+ "description": "Named entity: Pr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2abb"
+ ]
+ ]
+ },
+ {
+ "input": "&Precedes",
+ "description": "Bad named entity: Precedes without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Precedes"
+ ]
+ ]
+ },
+ {
+ "input": "≺",
+ "description": "Named entity: Precedes; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u227a"
+ ]
+ ]
+ },
+ {
+ "input": "&PrecedesEqual",
+ "description": "Bad named entity: PrecedesEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&PrecedesEqual"
+ ]
+ ]
+ },
+ {
+ "input": "⪯",
+ "description": "Named entity: PrecedesEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2aaf"
+ ]
+ ]
+ },
+ {
+ "input": "&PrecedesSlantEqual",
+ "description": "Bad named entity: PrecedesSlantEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&PrecedesSlantEqual"
+ ]
+ ]
+ },
+ {
+ "input": "≼",
+ "description": "Named entity: PrecedesSlantEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u227c"
+ ]
+ ]
+ },
+ {
+ "input": "&PrecedesTilde",
+ "description": "Bad named entity: PrecedesTilde without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&PrecedesTilde"
+ ]
+ ]
+ },
+ {
+ "input": "≾",
+ "description": "Named entity: PrecedesTilde; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u227e"
+ ]
+ ]
+ },
+ {
+ "input": "&Prime",
+ "description": "Bad named entity: Prime without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Prime"
+ ]
+ ]
+ },
+ {
+ "input": "″",
+ "description": "Named entity: Prime; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2033"
+ ]
+ ]
+ },
+ {
+ "input": "&Product",
+ "description": "Bad named entity: Product without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Product"
+ ]
+ ]
+ },
+ {
+ "input": "∏",
+ "description": "Named entity: Product; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u220f"
+ ]
+ ]
+ },
+ {
+ "input": "&Proportion",
+ "description": "Bad named entity: Proportion without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Proportion"
+ ]
+ ]
+ },
+ {
+ "input": "∷",
+ "description": "Named entity: Proportion; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2237"
+ ]
+ ]
+ },
+ {
+ "input": "&Proportional",
+ "description": "Bad named entity: Proportional without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Proportional"
+ ]
+ ]
+ },
+ {
+ "input": "∝",
+ "description": "Named entity: Proportional; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u221d"
+ ]
+ ]
+ },
+ {
+ "input": "&Pscr",
+ "description": "Bad named entity: Pscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Pscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝒫",
+ "description": "Named entity: Pscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udcab"
+ ]
+ ]
+ },
+ {
+ "input": "&Psi",
+ "description": "Bad named entity: Psi without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Psi"
+ ]
+ ]
+ },
+ {
+ "input": "Ψ",
+ "description": "Named entity: Psi; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03a8"
+ ]
+ ]
+ },
+ {
+ "input": """,
+ "description": "Named entity: QUOT without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\""
+ ]
+ ]
+ },
+ {
+ "input": """,
+ "description": "Named entity: QUOT; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\""
+ ]
+ ]
+ },
+ {
+ "input": "&Qfr",
+ "description": "Bad named entity: Qfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Qfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔔",
+ "description": "Named entity: Qfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd14"
+ ]
+ ]
+ },
+ {
+ "input": "&Qopf",
+ "description": "Bad named entity: Qopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Qopf"
+ ]
+ ]
+ },
+ {
+ "input": "ℚ",
+ "description": "Named entity: Qopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u211a"
+ ]
+ ]
+ },
+ {
+ "input": "&Qscr",
+ "description": "Bad named entity: Qscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Qscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝒬",
+ "description": "Named entity: Qscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udcac"
+ ]
+ ]
+ },
+ {
+ "input": "&RBarr",
+ "description": "Bad named entity: RBarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&RBarr"
+ ]
+ ]
+ },
+ {
+ "input": "⤐",
+ "description": "Named entity: RBarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2910"
+ ]
+ ]
+ },
+ {
+ "input": "®",
+ "description": "Named entity: REG without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00ae"
+ ]
+ ]
+ },
+ {
+ "input": "®",
+ "description": "Named entity: REG; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00ae"
+ ]
+ ]
+ },
+ {
+ "input": "&Racute",
+ "description": "Bad named entity: Racute without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Racute"
+ ]
+ ]
+ },
+ {
+ "input": "Ŕ",
+ "description": "Named entity: Racute; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0154"
+ ]
+ ]
+ },
+ {
+ "input": "&Rang",
+ "description": "Bad named entity: Rang without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Rang"
+ ]
+ ]
+ },
+ {
+ "input": "⟫",
+ "description": "Named entity: Rang; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27eb"
+ ]
+ ]
+ },
+ {
+ "input": "&Rarr",
+ "description": "Bad named entity: Rarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Rarr"
+ ]
+ ]
+ },
+ {
+ "input": "↠",
+ "description": "Named entity: Rarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21a0"
+ ]
+ ]
+ },
+ {
+ "input": "&Rarrtl",
+ "description": "Bad named entity: Rarrtl without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Rarrtl"
+ ]
+ ]
+ },
+ {
+ "input": "⤖",
+ "description": "Named entity: Rarrtl; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2916"
+ ]
+ ]
+ },
+ {
+ "input": "&Rcaron",
+ "description": "Bad named entity: Rcaron without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Rcaron"
+ ]
+ ]
+ },
+ {
+ "input": "Ř",
+ "description": "Named entity: Rcaron; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0158"
+ ]
+ ]
+ },
+ {
+ "input": "&Rcedil",
+ "description": "Bad named entity: Rcedil without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Rcedil"
+ ]
+ ]
+ },
+ {
+ "input": "Ŗ",
+ "description": "Named entity: Rcedil; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0156"
+ ]
+ ]
+ },
+ {
+ "input": "&Rcy",
+ "description": "Bad named entity: Rcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Rcy"
+ ]
+ ]
+ },
+ {
+ "input": "Р",
+ "description": "Named entity: Rcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0420"
+ ]
+ ]
+ },
+ {
+ "input": "&Re",
+ "description": "Bad named entity: Re without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Re"
+ ]
+ ]
+ },
+ {
+ "input": "ℜ",
+ "description": "Named entity: Re; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u211c"
+ ]
+ ]
+ },
+ {
+ "input": "&ReverseElement",
+ "description": "Bad named entity: ReverseElement without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ReverseElement"
+ ]
+ ]
+ },
+ {
+ "input": "∋",
+ "description": "Named entity: ReverseElement; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u220b"
+ ]
+ ]
+ },
+ {
+ "input": "&ReverseEquilibrium",
+ "description": "Bad named entity: ReverseEquilibrium without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ReverseEquilibrium"
+ ]
+ ]
+ },
+ {
+ "input": "⇋",
+ "description": "Named entity: ReverseEquilibrium; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21cb"
+ ]
+ ]
+ },
+ {
+ "input": "&ReverseUpEquilibrium",
+ "description": "Bad named entity: ReverseUpEquilibrium without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ReverseUpEquilibrium"
+ ]
+ ]
+ },
+ {
+ "input": "⥯",
+ "description": "Named entity: ReverseUpEquilibrium; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u296f"
+ ]
+ ]
+ },
+ {
+ "input": "&Rfr",
+ "description": "Bad named entity: Rfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Rfr"
+ ]
+ ]
+ },
+ {
+ "input": "ℜ",
+ "description": "Named entity: Rfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u211c"
+ ]
+ ]
+ },
+ {
+ "input": "&Rho",
+ "description": "Bad named entity: Rho without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Rho"
+ ]
+ ]
+ },
+ {
+ "input": "Ρ",
+ "description": "Named entity: Rho; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03a1"
+ ]
+ ]
+ },
+ {
+ "input": "&RightAngleBracket",
+ "description": "Bad named entity: RightAngleBracket without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&RightAngleBracket"
+ ]
+ ]
+ },
+ {
+ "input": "⟩",
+ "description": "Named entity: RightAngleBracket; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27e9"
+ ]
+ ]
+ },
+ {
+ "input": "&RightArrow",
+ "description": "Bad named entity: RightArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&RightArrow"
+ ]
+ ]
+ },
+ {
+ "input": "→",
+ "description": "Named entity: RightArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2192"
+ ]
+ ]
+ },
+ {
+ "input": "&RightArrowBar",
+ "description": "Bad named entity: RightArrowBar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&RightArrowBar"
+ ]
+ ]
+ },
+ {
+ "input": "⇥",
+ "description": "Named entity: RightArrowBar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21e5"
+ ]
+ ]
+ },
+ {
+ "input": "&RightArrowLeftArrow",
+ "description": "Bad named entity: RightArrowLeftArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&RightArrowLeftArrow"
+ ]
+ ]
+ },
+ {
+ "input": "⇄",
+ "description": "Named entity: RightArrowLeftArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21c4"
+ ]
+ ]
+ },
+ {
+ "input": "&RightCeiling",
+ "description": "Bad named entity: RightCeiling without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&RightCeiling"
+ ]
+ ]
+ },
+ {
+ "input": "⌉",
+ "description": "Named entity: RightCeiling; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2309"
+ ]
+ ]
+ },
+ {
+ "input": "&RightDoubleBracket",
+ "description": "Bad named entity: RightDoubleBracket without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&RightDoubleBracket"
+ ]
+ ]
+ },
+ {
+ "input": "⟧",
+ "description": "Named entity: RightDoubleBracket; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27e7"
+ ]
+ ]
+ },
+ {
+ "input": "&RightDownTeeVector",
+ "description": "Bad named entity: RightDownTeeVector without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&RightDownTeeVector"
+ ]
+ ]
+ },
+ {
+ "input": "⥝",
+ "description": "Named entity: RightDownTeeVector; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u295d"
+ ]
+ ]
+ },
+ {
+ "input": "&RightDownVector",
+ "description": "Bad named entity: RightDownVector without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&RightDownVector"
+ ]
+ ]
+ },
+ {
+ "input": "⇂",
+ "description": "Named entity: RightDownVector; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21c2"
+ ]
+ ]
+ },
+ {
+ "input": "&RightDownVectorBar",
+ "description": "Bad named entity: RightDownVectorBar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&RightDownVectorBar"
+ ]
+ ]
+ },
+ {
+ "input": "⥕",
+ "description": "Named entity: RightDownVectorBar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2955"
+ ]
+ ]
+ },
+ {
+ "input": "&RightFloor",
+ "description": "Bad named entity: RightFloor without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&RightFloor"
+ ]
+ ]
+ },
+ {
+ "input": "⌋",
+ "description": "Named entity: RightFloor; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u230b"
+ ]
+ ]
+ },
+ {
+ "input": "&RightTee",
+ "description": "Bad named entity: RightTee without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&RightTee"
+ ]
+ ]
+ },
+ {
+ "input": "⊢",
+ "description": "Named entity: RightTee; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22a2"
+ ]
+ ]
+ },
+ {
+ "input": "&RightTeeArrow",
+ "description": "Bad named entity: RightTeeArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&RightTeeArrow"
+ ]
+ ]
+ },
+ {
+ "input": "↦",
+ "description": "Named entity: RightTeeArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21a6"
+ ]
+ ]
+ },
+ {
+ "input": "&RightTeeVector",
+ "description": "Bad named entity: RightTeeVector without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&RightTeeVector"
+ ]
+ ]
+ },
+ {
+ "input": "⥛",
+ "description": "Named entity: RightTeeVector; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u295b"
+ ]
+ ]
+ },
+ {
+ "input": "&RightTriangle",
+ "description": "Bad named entity: RightTriangle without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&RightTriangle"
+ ]
+ ]
+ },
+ {
+ "input": "⊳",
+ "description": "Named entity: RightTriangle; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22b3"
+ ]
+ ]
+ },
+ {
+ "input": "&RightTriangleBar",
+ "description": "Bad named entity: RightTriangleBar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&RightTriangleBar"
+ ]
+ ]
+ },
+ {
+ "input": "⧐",
+ "description": "Named entity: RightTriangleBar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29d0"
+ ]
+ ]
+ },
+ {
+ "input": "&RightTriangleEqual",
+ "description": "Bad named entity: RightTriangleEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&RightTriangleEqual"
+ ]
+ ]
+ },
+ {
+ "input": "⊵",
+ "description": "Named entity: RightTriangleEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22b5"
+ ]
+ ]
+ },
+ {
+ "input": "&RightUpDownVector",
+ "description": "Bad named entity: RightUpDownVector without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&RightUpDownVector"
+ ]
+ ]
+ },
+ {
+ "input": "⥏",
+ "description": "Named entity: RightUpDownVector; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u294f"
+ ]
+ ]
+ },
+ {
+ "input": "&RightUpTeeVector",
+ "description": "Bad named entity: RightUpTeeVector without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&RightUpTeeVector"
+ ]
+ ]
+ },
+ {
+ "input": "⥜",
+ "description": "Named entity: RightUpTeeVector; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u295c"
+ ]
+ ]
+ },
+ {
+ "input": "&RightUpVector",
+ "description": "Bad named entity: RightUpVector without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&RightUpVector"
+ ]
+ ]
+ },
+ {
+ "input": "↾",
+ "description": "Named entity: RightUpVector; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21be"
+ ]
+ ]
+ },
+ {
+ "input": "&RightUpVectorBar",
+ "description": "Bad named entity: RightUpVectorBar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&RightUpVectorBar"
+ ]
+ ]
+ },
+ {
+ "input": "⥔",
+ "description": "Named entity: RightUpVectorBar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2954"
+ ]
+ ]
+ },
+ {
+ "input": "&RightVector",
+ "description": "Bad named entity: RightVector without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&RightVector"
+ ]
+ ]
+ },
+ {
+ "input": "⇀",
+ "description": "Named entity: RightVector; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21c0"
+ ]
+ ]
+ },
+ {
+ "input": "&RightVectorBar",
+ "description": "Bad named entity: RightVectorBar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&RightVectorBar"
+ ]
+ ]
+ },
+ {
+ "input": "⥓",
+ "description": "Named entity: RightVectorBar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2953"
+ ]
+ ]
+ },
+ {
+ "input": "&Rightarrow",
+ "description": "Bad named entity: Rightarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Rightarrow"
+ ]
+ ]
+ },
+ {
+ "input": "⇒",
+ "description": "Named entity: Rightarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21d2"
+ ]
+ ]
+ },
+ {
+ "input": "&Ropf",
+ "description": "Bad named entity: Ropf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Ropf"
+ ]
+ ]
+ },
+ {
+ "input": "ℝ",
+ "description": "Named entity: Ropf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u211d"
+ ]
+ ]
+ },
+ {
+ "input": "&RoundImplies",
+ "description": "Bad named entity: RoundImplies without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&RoundImplies"
+ ]
+ ]
+ },
+ {
+ "input": "⥰",
+ "description": "Named entity: RoundImplies; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2970"
+ ]
+ ]
+ },
+ {
+ "input": "&Rrightarrow",
+ "description": "Bad named entity: Rrightarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Rrightarrow"
+ ]
+ ]
+ },
+ {
+ "input": "⇛",
+ "description": "Named entity: Rrightarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21db"
+ ]
+ ]
+ },
+ {
+ "input": "&Rscr",
+ "description": "Bad named entity: Rscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Rscr"
+ ]
+ ]
+ },
+ {
+ "input": "ℛ",
+ "description": "Named entity: Rscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u211b"
+ ]
+ ]
+ },
+ {
+ "input": "&Rsh",
+ "description": "Bad named entity: Rsh without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Rsh"
+ ]
+ ]
+ },
+ {
+ "input": "↱",
+ "description": "Named entity: Rsh; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21b1"
+ ]
+ ]
+ },
+ {
+ "input": "&RuleDelayed",
+ "description": "Bad named entity: RuleDelayed without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&RuleDelayed"
+ ]
+ ]
+ },
+ {
+ "input": "⧴",
+ "description": "Named entity: RuleDelayed; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29f4"
+ ]
+ ]
+ },
+ {
+ "input": "&SHCHcy",
+ "description": "Bad named entity: SHCHcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&SHCHcy"
+ ]
+ ]
+ },
+ {
+ "input": "Щ",
+ "description": "Named entity: SHCHcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0429"
+ ]
+ ]
+ },
+ {
+ "input": "&SHcy",
+ "description": "Bad named entity: SHcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&SHcy"
+ ]
+ ]
+ },
+ {
+ "input": "Ш",
+ "description": "Named entity: SHcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0428"
+ ]
+ ]
+ },
+ {
+ "input": "&SOFTcy",
+ "description": "Bad named entity: SOFTcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&SOFTcy"
+ ]
+ ]
+ },
+ {
+ "input": "Ь",
+ "description": "Named entity: SOFTcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u042c"
+ ]
+ ]
+ },
+ {
+ "input": "&Sacute",
+ "description": "Bad named entity: Sacute without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Sacute"
+ ]
+ ]
+ },
+ {
+ "input": "Ś",
+ "description": "Named entity: Sacute; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u015a"
+ ]
+ ]
+ },
+ {
+ "input": "&Sc",
+ "description": "Bad named entity: Sc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Sc"
+ ]
+ ]
+ },
+ {
+ "input": "⪼",
+ "description": "Named entity: Sc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2abc"
+ ]
+ ]
+ },
+ {
+ "input": "&Scaron",
+ "description": "Bad named entity: Scaron without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Scaron"
+ ]
+ ]
+ },
+ {
+ "input": "Š",
+ "description": "Named entity: Scaron; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0160"
+ ]
+ ]
+ },
+ {
+ "input": "&Scedil",
+ "description": "Bad named entity: Scedil without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Scedil"
+ ]
+ ]
+ },
+ {
+ "input": "Ş",
+ "description": "Named entity: Scedil; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u015e"
+ ]
+ ]
+ },
+ {
+ "input": "&Scirc",
+ "description": "Bad named entity: Scirc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Scirc"
+ ]
+ ]
+ },
+ {
+ "input": "Ŝ",
+ "description": "Named entity: Scirc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u015c"
+ ]
+ ]
+ },
+ {
+ "input": "&Scy",
+ "description": "Bad named entity: Scy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Scy"
+ ]
+ ]
+ },
+ {
+ "input": "С",
+ "description": "Named entity: Scy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0421"
+ ]
+ ]
+ },
+ {
+ "input": "&Sfr",
+ "description": "Bad named entity: Sfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Sfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔖",
+ "description": "Named entity: Sfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd16"
+ ]
+ ]
+ },
+ {
+ "input": "&ShortDownArrow",
+ "description": "Bad named entity: ShortDownArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ShortDownArrow"
+ ]
+ ]
+ },
+ {
+ "input": "↓",
+ "description": "Named entity: ShortDownArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2193"
+ ]
+ ]
+ },
+ {
+ "input": "&ShortLeftArrow",
+ "description": "Bad named entity: ShortLeftArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ShortLeftArrow"
+ ]
+ ]
+ },
+ {
+ "input": "←",
+ "description": "Named entity: ShortLeftArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2190"
+ ]
+ ]
+ },
+ {
+ "input": "&ShortRightArrow",
+ "description": "Bad named entity: ShortRightArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ShortRightArrow"
+ ]
+ ]
+ },
+ {
+ "input": "→",
+ "description": "Named entity: ShortRightArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2192"
+ ]
+ ]
+ },
+ {
+ "input": "&ShortUpArrow",
+ "description": "Bad named entity: ShortUpArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ShortUpArrow"
+ ]
+ ]
+ },
+ {
+ "input": "↑",
+ "description": "Named entity: ShortUpArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2191"
+ ]
+ ]
+ },
+ {
+ "input": "&Sigma",
+ "description": "Bad named entity: Sigma without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Sigma"
+ ]
+ ]
+ },
+ {
+ "input": "Σ",
+ "description": "Named entity: Sigma; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03a3"
+ ]
+ ]
+ },
+ {
+ "input": "&SmallCircle",
+ "description": "Bad named entity: SmallCircle without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&SmallCircle"
+ ]
+ ]
+ },
+ {
+ "input": "∘",
+ "description": "Named entity: SmallCircle; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2218"
+ ]
+ ]
+ },
+ {
+ "input": "&Sopf",
+ "description": "Bad named entity: Sopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Sopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕊",
+ "description": "Named entity: Sopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd4a"
+ ]
+ ]
+ },
+ {
+ "input": "&Sqrt",
+ "description": "Bad named entity: Sqrt without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Sqrt"
+ ]
+ ]
+ },
+ {
+ "input": "√",
+ "description": "Named entity: Sqrt; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u221a"
+ ]
+ ]
+ },
+ {
+ "input": "&Square",
+ "description": "Bad named entity: Square without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Square"
+ ]
+ ]
+ },
+ {
+ "input": "□",
+ "description": "Named entity: Square; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25a1"
+ ]
+ ]
+ },
+ {
+ "input": "&SquareIntersection",
+ "description": "Bad named entity: SquareIntersection without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&SquareIntersection"
+ ]
+ ]
+ },
+ {
+ "input": "⊓",
+ "description": "Named entity: SquareIntersection; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2293"
+ ]
+ ]
+ },
+ {
+ "input": "&SquareSubset",
+ "description": "Bad named entity: SquareSubset without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&SquareSubset"
+ ]
+ ]
+ },
+ {
+ "input": "⊏",
+ "description": "Named entity: SquareSubset; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u228f"
+ ]
+ ]
+ },
+ {
+ "input": "&SquareSubsetEqual",
+ "description": "Bad named entity: SquareSubsetEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&SquareSubsetEqual"
+ ]
+ ]
+ },
+ {
+ "input": "⊑",
+ "description": "Named entity: SquareSubsetEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2291"
+ ]
+ ]
+ },
+ {
+ "input": "&SquareSuperset",
+ "description": "Bad named entity: SquareSuperset without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&SquareSuperset"
+ ]
+ ]
+ },
+ {
+ "input": "⊐",
+ "description": "Named entity: SquareSuperset; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2290"
+ ]
+ ]
+ },
+ {
+ "input": "&SquareSupersetEqual",
+ "description": "Bad named entity: SquareSupersetEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&SquareSupersetEqual"
+ ]
+ ]
+ },
+ {
+ "input": "⊒",
+ "description": "Named entity: SquareSupersetEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2292"
+ ]
+ ]
+ },
+ {
+ "input": "&SquareUnion",
+ "description": "Bad named entity: SquareUnion without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&SquareUnion"
+ ]
+ ]
+ },
+ {
+ "input": "⊔",
+ "description": "Named entity: SquareUnion; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2294"
+ ]
+ ]
+ },
+ {
+ "input": "&Sscr",
+ "description": "Bad named entity: Sscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Sscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝒮",
+ "description": "Named entity: Sscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udcae"
+ ]
+ ]
+ },
+ {
+ "input": "&Star",
+ "description": "Bad named entity: Star without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Star"
+ ]
+ ]
+ },
+ {
+ "input": "⋆",
+ "description": "Named entity: Star; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22c6"
+ ]
+ ]
+ },
+ {
+ "input": "&Sub",
+ "description": "Bad named entity: Sub without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Sub"
+ ]
+ ]
+ },
+ {
+ "input": "⋐",
+ "description": "Named entity: Sub; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22d0"
+ ]
+ ]
+ },
+ {
+ "input": "&Subset",
+ "description": "Bad named entity: Subset without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Subset"
+ ]
+ ]
+ },
+ {
+ "input": "⋐",
+ "description": "Named entity: Subset; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22d0"
+ ]
+ ]
+ },
+ {
+ "input": "&SubsetEqual",
+ "description": "Bad named entity: SubsetEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&SubsetEqual"
+ ]
+ ]
+ },
+ {
+ "input": "⊆",
+ "description": "Named entity: SubsetEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2286"
+ ]
+ ]
+ },
+ {
+ "input": "&Succeeds",
+ "description": "Bad named entity: Succeeds without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Succeeds"
+ ]
+ ]
+ },
+ {
+ "input": "≻",
+ "description": "Named entity: Succeeds; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u227b"
+ ]
+ ]
+ },
+ {
+ "input": "&SucceedsEqual",
+ "description": "Bad named entity: SucceedsEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&SucceedsEqual"
+ ]
+ ]
+ },
+ {
+ "input": "⪰",
+ "description": "Named entity: SucceedsEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ab0"
+ ]
+ ]
+ },
+ {
+ "input": "&SucceedsSlantEqual",
+ "description": "Bad named entity: SucceedsSlantEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&SucceedsSlantEqual"
+ ]
+ ]
+ },
+ {
+ "input": "≽",
+ "description": "Named entity: SucceedsSlantEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u227d"
+ ]
+ ]
+ },
+ {
+ "input": "&SucceedsTilde",
+ "description": "Bad named entity: SucceedsTilde without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&SucceedsTilde"
+ ]
+ ]
+ },
+ {
+ "input": "≿",
+ "description": "Named entity: SucceedsTilde; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u227f"
+ ]
+ ]
+ },
+ {
+ "input": "&SuchThat",
+ "description": "Bad named entity: SuchThat without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&SuchThat"
+ ]
+ ]
+ },
+ {
+ "input": "∋",
+ "description": "Named entity: SuchThat; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u220b"
+ ]
+ ]
+ },
+ {
+ "input": "&Sum",
+ "description": "Bad named entity: Sum without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Sum"
+ ]
+ ]
+ },
+ {
+ "input": "∑",
+ "description": "Named entity: Sum; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2211"
+ ]
+ ]
+ },
+ {
+ "input": "&Sup",
+ "description": "Bad named entity: Sup without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Sup"
+ ]
+ ]
+ },
+ {
+ "input": "⋑",
+ "description": "Named entity: Sup; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22d1"
+ ]
+ ]
+ },
+ {
+ "input": "&Superset",
+ "description": "Bad named entity: Superset without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Superset"
+ ]
+ ]
+ },
+ {
+ "input": "⊃",
+ "description": "Named entity: Superset; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2283"
+ ]
+ ]
+ },
+ {
+ "input": "&SupersetEqual",
+ "description": "Bad named entity: SupersetEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&SupersetEqual"
+ ]
+ ]
+ },
+ {
+ "input": "⊇",
+ "description": "Named entity: SupersetEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2287"
+ ]
+ ]
+ },
+ {
+ "input": "&Supset",
+ "description": "Bad named entity: Supset without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Supset"
+ ]
+ ]
+ },
+ {
+ "input": "⋑",
+ "description": "Named entity: Supset; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22d1"
+ ]
+ ]
+ },
+ {
+ "input": "Þ",
+ "description": "Named entity: THORN without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00de"
+ ]
+ ]
+ },
+ {
+ "input": "Þ",
+ "description": "Named entity: THORN; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00de"
+ ]
+ ]
+ },
+ {
+ "input": "&TRADE",
+ "description": "Bad named entity: TRADE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&TRADE"
+ ]
+ ]
+ },
+ {
+ "input": "™",
+ "description": "Named entity: TRADE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2122"
+ ]
+ ]
+ },
+ {
+ "input": "&TSHcy",
+ "description": "Bad named entity: TSHcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&TSHcy"
+ ]
+ ]
+ },
+ {
+ "input": "Ћ",
+ "description": "Named entity: TSHcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u040b"
+ ]
+ ]
+ },
+ {
+ "input": "&TScy",
+ "description": "Bad named entity: TScy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&TScy"
+ ]
+ ]
+ },
+ {
+ "input": "Ц",
+ "description": "Named entity: TScy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0426"
+ ]
+ ]
+ },
+ {
+ "input": "&Tab",
+ "description": "Bad named entity: Tab without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Tab"
+ ]
+ ]
+ },
+ {
+ "input": "	",
+ "description": "Named entity: Tab; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\t"
+ ]
+ ]
+ },
+ {
+ "input": "&Tau",
+ "description": "Bad named entity: Tau without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Tau"
+ ]
+ ]
+ },
+ {
+ "input": "Τ",
+ "description": "Named entity: Tau; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03a4"
+ ]
+ ]
+ },
+ {
+ "input": "&Tcaron",
+ "description": "Bad named entity: Tcaron without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Tcaron"
+ ]
+ ]
+ },
+ {
+ "input": "Ť",
+ "description": "Named entity: Tcaron; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0164"
+ ]
+ ]
+ },
+ {
+ "input": "&Tcedil",
+ "description": "Bad named entity: Tcedil without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Tcedil"
+ ]
+ ]
+ },
+ {
+ "input": "Ţ",
+ "description": "Named entity: Tcedil; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0162"
+ ]
+ ]
+ },
+ {
+ "input": "&Tcy",
+ "description": "Bad named entity: Tcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Tcy"
+ ]
+ ]
+ },
+ {
+ "input": "Т",
+ "description": "Named entity: Tcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0422"
+ ]
+ ]
+ },
+ {
+ "input": "&Tfr",
+ "description": "Bad named entity: Tfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Tfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔗",
+ "description": "Named entity: Tfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd17"
+ ]
+ ]
+ },
+ {
+ "input": "&Therefore",
+ "description": "Bad named entity: Therefore without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Therefore"
+ ]
+ ]
+ },
+ {
+ "input": "∴",
+ "description": "Named entity: Therefore; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2234"
+ ]
+ ]
+ },
+ {
+ "input": "&Theta",
+ "description": "Bad named entity: Theta without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Theta"
+ ]
+ ]
+ },
+ {
+ "input": "Θ",
+ "description": "Named entity: Theta; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0398"
+ ]
+ ]
+ },
+ {
+ "input": "&ThickSpace",
+ "description": "Bad named entity: ThickSpace without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ThickSpace"
+ ]
+ ]
+ },
+ {
+ "input": "  ",
+ "description": "Named entity: ThickSpace; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u205f\u200a"
+ ]
+ ]
+ },
+ {
+ "input": "&ThinSpace",
+ "description": "Bad named entity: ThinSpace without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ThinSpace"
+ ]
+ ]
+ },
+ {
+ "input": " ",
+ "description": "Named entity: ThinSpace; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2009"
+ ]
+ ]
+ },
+ {
+ "input": "&Tilde",
+ "description": "Bad named entity: Tilde without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Tilde"
+ ]
+ ]
+ },
+ {
+ "input": "∼",
+ "description": "Named entity: Tilde; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u223c"
+ ]
+ ]
+ },
+ {
+ "input": "&TildeEqual",
+ "description": "Bad named entity: TildeEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&TildeEqual"
+ ]
+ ]
+ },
+ {
+ "input": "≃",
+ "description": "Named entity: TildeEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2243"
+ ]
+ ]
+ },
+ {
+ "input": "&TildeFullEqual",
+ "description": "Bad named entity: TildeFullEqual without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&TildeFullEqual"
+ ]
+ ]
+ },
+ {
+ "input": "≅",
+ "description": "Named entity: TildeFullEqual; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2245"
+ ]
+ ]
+ },
+ {
+ "input": "&TildeTilde",
+ "description": "Bad named entity: TildeTilde without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&TildeTilde"
+ ]
+ ]
+ },
+ {
+ "input": "≈",
+ "description": "Named entity: TildeTilde; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2248"
+ ]
+ ]
+ },
+ {
+ "input": "&Topf",
+ "description": "Bad named entity: Topf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Topf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕋",
+ "description": "Named entity: Topf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd4b"
+ ]
+ ]
+ },
+ {
+ "input": "&TripleDot",
+ "description": "Bad named entity: TripleDot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&TripleDot"
+ ]
+ ]
+ },
+ {
+ "input": "⃛",
+ "description": "Named entity: TripleDot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u20db"
+ ]
+ ]
+ },
+ {
+ "input": "&Tscr",
+ "description": "Bad named entity: Tscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Tscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝒯",
+ "description": "Named entity: Tscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udcaf"
+ ]
+ ]
+ },
+ {
+ "input": "&Tstrok",
+ "description": "Bad named entity: Tstrok without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Tstrok"
+ ]
+ ]
+ },
+ {
+ "input": "Ŧ",
+ "description": "Named entity: Tstrok; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0166"
+ ]
+ ]
+ },
+ {
+ "input": "Ú",
+ "description": "Named entity: Uacute without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00da"
+ ]
+ ]
+ },
+ {
+ "input": "Ú",
+ "description": "Named entity: Uacute; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00da"
+ ]
+ ]
+ },
+ {
+ "input": "&Uarr",
+ "description": "Bad named entity: Uarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Uarr"
+ ]
+ ]
+ },
+ {
+ "input": "↟",
+ "description": "Named entity: Uarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u219f"
+ ]
+ ]
+ },
+ {
+ "input": "&Uarrocir",
+ "description": "Bad named entity: Uarrocir without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Uarrocir"
+ ]
+ ]
+ },
+ {
+ "input": "⥉",
+ "description": "Named entity: Uarrocir; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2949"
+ ]
+ ]
+ },
+ {
+ "input": "&Ubrcy",
+ "description": "Bad named entity: Ubrcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Ubrcy"
+ ]
+ ]
+ },
+ {
+ "input": "Ў",
+ "description": "Named entity: Ubrcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u040e"
+ ]
+ ]
+ },
+ {
+ "input": "&Ubreve",
+ "description": "Bad named entity: Ubreve without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Ubreve"
+ ]
+ ]
+ },
+ {
+ "input": "Ŭ",
+ "description": "Named entity: Ubreve; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u016c"
+ ]
+ ]
+ },
+ {
+ "input": "Û",
+ "description": "Named entity: Ucirc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00db"
+ ]
+ ]
+ },
+ {
+ "input": "Û",
+ "description": "Named entity: Ucirc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00db"
+ ]
+ ]
+ },
+ {
+ "input": "&Ucy",
+ "description": "Bad named entity: Ucy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Ucy"
+ ]
+ ]
+ },
+ {
+ "input": "У",
+ "description": "Named entity: Ucy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0423"
+ ]
+ ]
+ },
+ {
+ "input": "&Udblac",
+ "description": "Bad named entity: Udblac without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Udblac"
+ ]
+ ]
+ },
+ {
+ "input": "Ű",
+ "description": "Named entity: Udblac; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0170"
+ ]
+ ]
+ },
+ {
+ "input": "&Ufr",
+ "description": "Bad named entity: Ufr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Ufr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔘",
+ "description": "Named entity: Ufr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd18"
+ ]
+ ]
+ },
+ {
+ "input": "Ù",
+ "description": "Named entity: Ugrave without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00d9"
+ ]
+ ]
+ },
+ {
+ "input": "Ù",
+ "description": "Named entity: Ugrave; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00d9"
+ ]
+ ]
+ },
+ {
+ "input": "&Umacr",
+ "description": "Bad named entity: Umacr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Umacr"
+ ]
+ ]
+ },
+ {
+ "input": "Ū",
+ "description": "Named entity: Umacr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u016a"
+ ]
+ ]
+ },
+ {
+ "input": "&UnderBar",
+ "description": "Bad named entity: UnderBar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&UnderBar"
+ ]
+ ]
+ },
+ {
+ "input": "_",
+ "description": "Named entity: UnderBar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "_"
+ ]
+ ]
+ },
+ {
+ "input": "&UnderBrace",
+ "description": "Bad named entity: UnderBrace without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&UnderBrace"
+ ]
+ ]
+ },
+ {
+ "input": "⏟",
+ "description": "Named entity: UnderBrace; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u23df"
+ ]
+ ]
+ },
+ {
+ "input": "&UnderBracket",
+ "description": "Bad named entity: UnderBracket without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&UnderBracket"
+ ]
+ ]
+ },
+ {
+ "input": "⎵",
+ "description": "Named entity: UnderBracket; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u23b5"
+ ]
+ ]
+ },
+ {
+ "input": "&UnderParenthesis",
+ "description": "Bad named entity: UnderParenthesis without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&UnderParenthesis"
+ ]
+ ]
+ },
+ {
+ "input": "⏝",
+ "description": "Named entity: UnderParenthesis; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u23dd"
+ ]
+ ]
+ },
+ {
+ "input": "&Union",
+ "description": "Bad named entity: Union without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Union"
+ ]
+ ]
+ },
+ {
+ "input": "⋃",
+ "description": "Named entity: Union; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22c3"
+ ]
+ ]
+ },
+ {
+ "input": "&UnionPlus",
+ "description": "Bad named entity: UnionPlus without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&UnionPlus"
+ ]
+ ]
+ },
+ {
+ "input": "⊎",
+ "description": "Named entity: UnionPlus; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u228e"
+ ]
+ ]
+ },
+ {
+ "input": "&Uogon",
+ "description": "Bad named entity: Uogon without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Uogon"
+ ]
+ ]
+ },
+ {
+ "input": "Ų",
+ "description": "Named entity: Uogon; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0172"
+ ]
+ ]
+ },
+ {
+ "input": "&Uopf",
+ "description": "Bad named entity: Uopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Uopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕌",
+ "description": "Named entity: Uopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd4c"
+ ]
+ ]
+ },
+ {
+ "input": "&UpArrow",
+ "description": "Bad named entity: UpArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&UpArrow"
+ ]
+ ]
+ },
+ {
+ "input": "↑",
+ "description": "Named entity: UpArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2191"
+ ]
+ ]
+ },
+ {
+ "input": "&UpArrowBar",
+ "description": "Bad named entity: UpArrowBar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&UpArrowBar"
+ ]
+ ]
+ },
+ {
+ "input": "⤒",
+ "description": "Named entity: UpArrowBar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2912"
+ ]
+ ]
+ },
+ {
+ "input": "&UpArrowDownArrow",
+ "description": "Bad named entity: UpArrowDownArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&UpArrowDownArrow"
+ ]
+ ]
+ },
+ {
+ "input": "⇅",
+ "description": "Named entity: UpArrowDownArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21c5"
+ ]
+ ]
+ },
+ {
+ "input": "&UpDownArrow",
+ "description": "Bad named entity: UpDownArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&UpDownArrow"
+ ]
+ ]
+ },
+ {
+ "input": "↕",
+ "description": "Named entity: UpDownArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2195"
+ ]
+ ]
+ },
+ {
+ "input": "&UpEquilibrium",
+ "description": "Bad named entity: UpEquilibrium without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&UpEquilibrium"
+ ]
+ ]
+ },
+ {
+ "input": "⥮",
+ "description": "Named entity: UpEquilibrium; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u296e"
+ ]
+ ]
+ },
+ {
+ "input": "&UpTee",
+ "description": "Bad named entity: UpTee without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&UpTee"
+ ]
+ ]
+ },
+ {
+ "input": "⊥",
+ "description": "Named entity: UpTee; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22a5"
+ ]
+ ]
+ },
+ {
+ "input": "&UpTeeArrow",
+ "description": "Bad named entity: UpTeeArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&UpTeeArrow"
+ ]
+ ]
+ },
+ {
+ "input": "↥",
+ "description": "Named entity: UpTeeArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21a5"
+ ]
+ ]
+ },
+ {
+ "input": "&Uparrow",
+ "description": "Bad named entity: Uparrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Uparrow"
+ ]
+ ]
+ },
+ {
+ "input": "⇑",
+ "description": "Named entity: Uparrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21d1"
+ ]
+ ]
+ },
+ {
+ "input": "&Updownarrow",
+ "description": "Bad named entity: Updownarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Updownarrow"
+ ]
+ ]
+ },
+ {
+ "input": "⇕",
+ "description": "Named entity: Updownarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21d5"
+ ]
+ ]
+ },
+ {
+ "input": "&UpperLeftArrow",
+ "description": "Bad named entity: UpperLeftArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&UpperLeftArrow"
+ ]
+ ]
+ },
+ {
+ "input": "↖",
+ "description": "Named entity: UpperLeftArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2196"
+ ]
+ ]
+ },
+ {
+ "input": "&UpperRightArrow",
+ "description": "Bad named entity: UpperRightArrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&UpperRightArrow"
+ ]
+ ]
+ },
+ {
+ "input": "↗",
+ "description": "Named entity: UpperRightArrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2197"
+ ]
+ ]
+ },
+ {
+ "input": "&Upsi",
+ "description": "Bad named entity: Upsi without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Upsi"
+ ]
+ ]
+ },
+ {
+ "input": "ϒ",
+ "description": "Named entity: Upsi; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03d2"
+ ]
+ ]
+ },
+ {
+ "input": "&Upsilon",
+ "description": "Bad named entity: Upsilon without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Upsilon"
+ ]
+ ]
+ },
+ {
+ "input": "Υ",
+ "description": "Named entity: Upsilon; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03a5"
+ ]
+ ]
+ },
+ {
+ "input": "&Uring",
+ "description": "Bad named entity: Uring without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Uring"
+ ]
+ ]
+ },
+ {
+ "input": "Ů",
+ "description": "Named entity: Uring; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u016e"
+ ]
+ ]
+ },
+ {
+ "input": "&Uscr",
+ "description": "Bad named entity: Uscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Uscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝒰",
+ "description": "Named entity: Uscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udcb0"
+ ]
+ ]
+ },
+ {
+ "input": "&Utilde",
+ "description": "Bad named entity: Utilde without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Utilde"
+ ]
+ ]
+ },
+ {
+ "input": "Ũ",
+ "description": "Named entity: Utilde; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0168"
+ ]
+ ]
+ },
+ {
+ "input": "Ü",
+ "description": "Named entity: Uuml without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00dc"
+ ]
+ ]
+ },
+ {
+ "input": "Ü",
+ "description": "Named entity: Uuml; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00dc"
+ ]
+ ]
+ },
+ {
+ "input": "&VDash",
+ "description": "Bad named entity: VDash without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&VDash"
+ ]
+ ]
+ },
+ {
+ "input": "⊫",
+ "description": "Named entity: VDash; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22ab"
+ ]
+ ]
+ },
+ {
+ "input": "&Vbar",
+ "description": "Bad named entity: Vbar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Vbar"
+ ]
+ ]
+ },
+ {
+ "input": "⫫",
+ "description": "Named entity: Vbar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2aeb"
+ ]
+ ]
+ },
+ {
+ "input": "&Vcy",
+ "description": "Bad named entity: Vcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Vcy"
+ ]
+ ]
+ },
+ {
+ "input": "В",
+ "description": "Named entity: Vcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0412"
+ ]
+ ]
+ },
+ {
+ "input": "&Vdash",
+ "description": "Bad named entity: Vdash without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Vdash"
+ ]
+ ]
+ },
+ {
+ "input": "⊩",
+ "description": "Named entity: Vdash; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22a9"
+ ]
+ ]
+ },
+ {
+ "input": "&Vdashl",
+ "description": "Bad named entity: Vdashl without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Vdashl"
+ ]
+ ]
+ },
+ {
+ "input": "⫦",
+ "description": "Named entity: Vdashl; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ae6"
+ ]
+ ]
+ },
+ {
+ "input": "&Vee",
+ "description": "Bad named entity: Vee without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Vee"
+ ]
+ ]
+ },
+ {
+ "input": "⋁",
+ "description": "Named entity: Vee; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22c1"
+ ]
+ ]
+ },
+ {
+ "input": "&Verbar",
+ "description": "Bad named entity: Verbar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Verbar"
+ ]
+ ]
+ },
+ {
+ "input": "‖",
+ "description": "Named entity: Verbar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2016"
+ ]
+ ]
+ },
+ {
+ "input": "&Vert",
+ "description": "Bad named entity: Vert without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Vert"
+ ]
+ ]
+ },
+ {
+ "input": "‖",
+ "description": "Named entity: Vert; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2016"
+ ]
+ ]
+ },
+ {
+ "input": "&VerticalBar",
+ "description": "Bad named entity: VerticalBar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&VerticalBar"
+ ]
+ ]
+ },
+ {
+ "input": "∣",
+ "description": "Named entity: VerticalBar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2223"
+ ]
+ ]
+ },
+ {
+ "input": "&VerticalLine",
+ "description": "Bad named entity: VerticalLine without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&VerticalLine"
+ ]
+ ]
+ },
+ {
+ "input": "|",
+ "description": "Named entity: VerticalLine; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "|"
+ ]
+ ]
+ },
+ {
+ "input": "&VerticalSeparator",
+ "description": "Bad named entity: VerticalSeparator without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&VerticalSeparator"
+ ]
+ ]
+ },
+ {
+ "input": "❘",
+ "description": "Named entity: VerticalSeparator; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2758"
+ ]
+ ]
+ },
+ {
+ "input": "&VerticalTilde",
+ "description": "Bad named entity: VerticalTilde without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&VerticalTilde"
+ ]
+ ]
+ },
+ {
+ "input": "≀",
+ "description": "Named entity: VerticalTilde; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2240"
+ ]
+ ]
+ },
+ {
+ "input": "&VeryThinSpace",
+ "description": "Bad named entity: VeryThinSpace without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&VeryThinSpace"
+ ]
+ ]
+ },
+ {
+ "input": " ",
+ "description": "Named entity: VeryThinSpace; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u200a"
+ ]
+ ]
+ },
+ {
+ "input": "&Vfr",
+ "description": "Bad named entity: Vfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Vfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔙",
+ "description": "Named entity: Vfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd19"
+ ]
+ ]
+ },
+ {
+ "input": "&Vopf",
+ "description": "Bad named entity: Vopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Vopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕍",
+ "description": "Named entity: Vopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd4d"
+ ]
+ ]
+ },
+ {
+ "input": "&Vscr",
+ "description": "Bad named entity: Vscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Vscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝒱",
+ "description": "Named entity: Vscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udcb1"
+ ]
+ ]
+ },
+ {
+ "input": "&Vvdash",
+ "description": "Bad named entity: Vvdash without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Vvdash"
+ ]
+ ]
+ },
+ {
+ "input": "⊪",
+ "description": "Named entity: Vvdash; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22aa"
+ ]
+ ]
+ },
+ {
+ "input": "&Wcirc",
+ "description": "Bad named entity: Wcirc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Wcirc"
+ ]
+ ]
+ },
+ {
+ "input": "Ŵ",
+ "description": "Named entity: Wcirc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0174"
+ ]
+ ]
+ },
+ {
+ "input": "&Wedge",
+ "description": "Bad named entity: Wedge without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Wedge"
+ ]
+ ]
+ },
+ {
+ "input": "⋀",
+ "description": "Named entity: Wedge; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22c0"
+ ]
+ ]
+ },
+ {
+ "input": "&Wfr",
+ "description": "Bad named entity: Wfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Wfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔚",
+ "description": "Named entity: Wfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd1a"
+ ]
+ ]
+ },
+ {
+ "input": "&Wopf",
+ "description": "Bad named entity: Wopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Wopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕎",
+ "description": "Named entity: Wopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd4e"
+ ]
+ ]
+ },
+ {
+ "input": "&Wscr",
+ "description": "Bad named entity: Wscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Wscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝒲",
+ "description": "Named entity: Wscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udcb2"
+ ]
+ ]
+ },
+ {
+ "input": "&Xfr",
+ "description": "Bad named entity: Xfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Xfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔛",
+ "description": "Named entity: Xfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd1b"
+ ]
+ ]
+ },
+ {
+ "input": "&Xi",
+ "description": "Bad named entity: Xi without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Xi"
+ ]
+ ]
+ },
+ {
+ "input": "Ξ",
+ "description": "Named entity: Xi; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u039e"
+ ]
+ ]
+ },
+ {
+ "input": "&Xopf",
+ "description": "Bad named entity: Xopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Xopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕏",
+ "description": "Named entity: Xopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd4f"
+ ]
+ ]
+ },
+ {
+ "input": "&Xscr",
+ "description": "Bad named entity: Xscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Xscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝒳",
+ "description": "Named entity: Xscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udcb3"
+ ]
+ ]
+ },
+ {
+ "input": "&YAcy",
+ "description": "Bad named entity: YAcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&YAcy"
+ ]
+ ]
+ },
+ {
+ "input": "Я",
+ "description": "Named entity: YAcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u042f"
+ ]
+ ]
+ },
+ {
+ "input": "&YIcy",
+ "description": "Bad named entity: YIcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&YIcy"
+ ]
+ ]
+ },
+ {
+ "input": "Ї",
+ "description": "Named entity: YIcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0407"
+ ]
+ ]
+ },
+ {
+ "input": "&YUcy",
+ "description": "Bad named entity: YUcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&YUcy"
+ ]
+ ]
+ },
+ {
+ "input": "Ю",
+ "description": "Named entity: YUcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u042e"
+ ]
+ ]
+ },
+ {
+ "input": "Ý",
+ "description": "Named entity: Yacute without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00dd"
+ ]
+ ]
+ },
+ {
+ "input": "Ý",
+ "description": "Named entity: Yacute; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00dd"
+ ]
+ ]
+ },
+ {
+ "input": "&Ycirc",
+ "description": "Bad named entity: Ycirc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Ycirc"
+ ]
+ ]
+ },
+ {
+ "input": "Ŷ",
+ "description": "Named entity: Ycirc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0176"
+ ]
+ ]
+ },
+ {
+ "input": "&Ycy",
+ "description": "Bad named entity: Ycy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Ycy"
+ ]
+ ]
+ },
+ {
+ "input": "Ы",
+ "description": "Named entity: Ycy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u042b"
+ ]
+ ]
+ },
+ {
+ "input": "&Yfr",
+ "description": "Bad named entity: Yfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Yfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔜",
+ "description": "Named entity: Yfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd1c"
+ ]
+ ]
+ },
+ {
+ "input": "&Yopf",
+ "description": "Bad named entity: Yopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Yopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕐",
+ "description": "Named entity: Yopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd50"
+ ]
+ ]
+ },
+ {
+ "input": "&Yscr",
+ "description": "Bad named entity: Yscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Yscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝒴",
+ "description": "Named entity: Yscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udcb4"
+ ]
+ ]
+ },
+ {
+ "input": "&Yuml",
+ "description": "Bad named entity: Yuml without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Yuml"
+ ]
+ ]
+ },
+ {
+ "input": "Ÿ",
+ "description": "Named entity: Yuml; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0178"
+ ]
+ ]
+ },
+ {
+ "input": "&ZHcy",
+ "description": "Bad named entity: ZHcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ZHcy"
+ ]
+ ]
+ },
+ {
+ "input": "Ж",
+ "description": "Named entity: ZHcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0416"
+ ]
+ ]
+ },
+ {
+ "input": "&Zacute",
+ "description": "Bad named entity: Zacute without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Zacute"
+ ]
+ ]
+ },
+ {
+ "input": "Ź",
+ "description": "Named entity: Zacute; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0179"
+ ]
+ ]
+ },
+ {
+ "input": "&Zcaron",
+ "description": "Bad named entity: Zcaron without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Zcaron"
+ ]
+ ]
+ },
+ {
+ "input": "Ž",
+ "description": "Named entity: Zcaron; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u017d"
+ ]
+ ]
+ },
+ {
+ "input": "&Zcy",
+ "description": "Bad named entity: Zcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Zcy"
+ ]
+ ]
+ },
+ {
+ "input": "З",
+ "description": "Named entity: Zcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0417"
+ ]
+ ]
+ },
+ {
+ "input": "&Zdot",
+ "description": "Bad named entity: Zdot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Zdot"
+ ]
+ ]
+ },
+ {
+ "input": "Ż",
+ "description": "Named entity: Zdot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u017b"
+ ]
+ ]
+ },
+ {
+ "input": "&ZeroWidthSpace",
+ "description": "Bad named entity: ZeroWidthSpace without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ZeroWidthSpace"
+ ]
+ ]
+ },
+ {
+ "input": "​",
+ "description": "Named entity: ZeroWidthSpace; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u200b"
+ ]
+ ]
+ },
+ {
+ "input": "&Zeta",
+ "description": "Bad named entity: Zeta without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Zeta"
+ ]
+ ]
+ },
+ {
+ "input": "Ζ",
+ "description": "Named entity: Zeta; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0396"
+ ]
+ ]
+ },
+ {
+ "input": "&Zfr",
+ "description": "Bad named entity: Zfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Zfr"
+ ]
+ ]
+ },
+ {
+ "input": "ℨ",
+ "description": "Named entity: Zfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2128"
+ ]
+ ]
+ },
+ {
+ "input": "&Zopf",
+ "description": "Bad named entity: Zopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Zopf"
+ ]
+ ]
+ },
+ {
+ "input": "ℤ",
+ "description": "Named entity: Zopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2124"
+ ]
+ ]
+ },
+ {
+ "input": "&Zscr",
+ "description": "Bad named entity: Zscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&Zscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝒵",
+ "description": "Named entity: Zscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udcb5"
+ ]
+ ]
+ },
+ {
+ "input": "á",
+ "description": "Named entity: aacute without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00e1"
+ ]
+ ]
+ },
+ {
+ "input": "á",
+ "description": "Named entity: aacute; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00e1"
+ ]
+ ]
+ },
+ {
+ "input": "&abreve",
+ "description": "Bad named entity: abreve without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&abreve"
+ ]
+ ]
+ },
+ {
+ "input": "ă",
+ "description": "Named entity: abreve; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0103"
+ ]
+ ]
+ },
+ {
+ "input": "&ac",
+ "description": "Bad named entity: ac without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ac"
+ ]
+ ]
+ },
+ {
+ "input": "∾",
+ "description": "Named entity: ac; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u223e"
+ ]
+ ]
+ },
+ {
+ "input": "&acE",
+ "description": "Bad named entity: acE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&acE"
+ ]
+ ]
+ },
+ {
+ "input": "∾̳",
+ "description": "Named entity: acE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u223e\u0333"
+ ]
+ ]
+ },
+ {
+ "input": "&acd",
+ "description": "Bad named entity: acd without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&acd"
+ ]
+ ]
+ },
+ {
+ "input": "∿",
+ "description": "Named entity: acd; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u223f"
+ ]
+ ]
+ },
+ {
+ "input": "â",
+ "description": "Named entity: acirc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00e2"
+ ]
+ ]
+ },
+ {
+ "input": "â",
+ "description": "Named entity: acirc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00e2"
+ ]
+ ]
+ },
+ {
+ "input": "´",
+ "description": "Named entity: acute without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00b4"
+ ]
+ ]
+ },
+ {
+ "input": "´",
+ "description": "Named entity: acute; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00b4"
+ ]
+ ]
+ },
+ {
+ "input": "&acy",
+ "description": "Bad named entity: acy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&acy"
+ ]
+ ]
+ },
+ {
+ "input": "а",
+ "description": "Named entity: acy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0430"
+ ]
+ ]
+ },
+ {
+ "input": "æ",
+ "description": "Named entity: aelig without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00e6"
+ ]
+ ]
+ },
+ {
+ "input": "æ",
+ "description": "Named entity: aelig; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00e6"
+ ]
+ ]
+ },
+ {
+ "input": "&af",
+ "description": "Bad named entity: af without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&af"
+ ]
+ ]
+ },
+ {
+ "input": "⁡",
+ "description": "Named entity: af; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2061"
+ ]
+ ]
+ },
+ {
+ "input": "&afr",
+ "description": "Bad named entity: afr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&afr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔞",
+ "description": "Named entity: afr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd1e"
+ ]
+ ]
+ },
+ {
+ "input": "à",
+ "description": "Named entity: agrave without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00e0"
+ ]
+ ]
+ },
+ {
+ "input": "à",
+ "description": "Named entity: agrave; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00e0"
+ ]
+ ]
+ },
+ {
+ "input": "&alefsym",
+ "description": "Bad named entity: alefsym without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&alefsym"
+ ]
+ ]
+ },
+ {
+ "input": "ℵ",
+ "description": "Named entity: alefsym; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2135"
+ ]
+ ]
+ },
+ {
+ "input": "&aleph",
+ "description": "Bad named entity: aleph without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&aleph"
+ ]
+ ]
+ },
+ {
+ "input": "ℵ",
+ "description": "Named entity: aleph; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2135"
+ ]
+ ]
+ },
+ {
+ "input": "&alpha",
+ "description": "Bad named entity: alpha without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&alpha"
+ ]
+ ]
+ },
+ {
+ "input": "α",
+ "description": "Named entity: alpha; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03b1"
+ ]
+ ]
+ },
+ {
+ "input": "&amacr",
+ "description": "Bad named entity: amacr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&amacr"
+ ]
+ ]
+ },
+ {
+ "input": "ā",
+ "description": "Named entity: amacr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0101"
+ ]
+ ]
+ },
+ {
+ "input": "&amalg",
+ "description": "Bad named entity: amalg without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&amalg"
+ ]
+ ]
+ },
+ {
+ "input": "⨿",
+ "description": "Named entity: amalg; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a3f"
+ ]
+ ]
+ },
+ {
+ "input": "&",
+ "description": "Named entity: amp without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&"
+ ]
+ ]
+ },
+ {
+ "input": "&",
+ "description": "Named entity: amp; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "&"
+ ]
+ ]
+ },
+ {
+ "input": "&and",
+ "description": "Bad named entity: and without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&and"
+ ]
+ ]
+ },
+ {
+ "input": "∧",
+ "description": "Named entity: and; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2227"
+ ]
+ ]
+ },
+ {
+ "input": "&andand",
+ "description": "Bad named entity: andand without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&andand"
+ ]
+ ]
+ },
+ {
+ "input": "⩕",
+ "description": "Named entity: andand; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a55"
+ ]
+ ]
+ },
+ {
+ "input": "&andd",
+ "description": "Bad named entity: andd without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&andd"
+ ]
+ ]
+ },
+ {
+ "input": "⩜",
+ "description": "Named entity: andd; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a5c"
+ ]
+ ]
+ },
+ {
+ "input": "&andslope",
+ "description": "Bad named entity: andslope without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&andslope"
+ ]
+ ]
+ },
+ {
+ "input": "⩘",
+ "description": "Named entity: andslope; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a58"
+ ]
+ ]
+ },
+ {
+ "input": "&andv",
+ "description": "Bad named entity: andv without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&andv"
+ ]
+ ]
+ },
+ {
+ "input": "⩚",
+ "description": "Named entity: andv; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a5a"
+ ]
+ ]
+ },
+ {
+ "input": "&ang",
+ "description": "Bad named entity: ang without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ang"
+ ]
+ ]
+ },
+ {
+ "input": "∠",
+ "description": "Named entity: ang; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2220"
+ ]
+ ]
+ },
+ {
+ "input": "&ange",
+ "description": "Bad named entity: ange without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ange"
+ ]
+ ]
+ },
+ {
+ "input": "⦤",
+ "description": "Named entity: ange; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29a4"
+ ]
+ ]
+ },
+ {
+ "input": "&angle",
+ "description": "Bad named entity: angle without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&angle"
+ ]
+ ]
+ },
+ {
+ "input": "∠",
+ "description": "Named entity: angle; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2220"
+ ]
+ ]
+ },
+ {
+ "input": "&angmsd",
+ "description": "Bad named entity: angmsd without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&angmsd"
+ ]
+ ]
+ },
+ {
+ "input": "∡",
+ "description": "Named entity: angmsd; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2221"
+ ]
+ ]
+ },
+ {
+ "input": "&angmsdaa",
+ "description": "Bad named entity: angmsdaa without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&angmsdaa"
+ ]
+ ]
+ },
+ {
+ "input": "⦨",
+ "description": "Named entity: angmsdaa; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29a8"
+ ]
+ ]
+ },
+ {
+ "input": "&angmsdab",
+ "description": "Bad named entity: angmsdab without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&angmsdab"
+ ]
+ ]
+ },
+ {
+ "input": "⦩",
+ "description": "Named entity: angmsdab; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29a9"
+ ]
+ ]
+ },
+ {
+ "input": "&angmsdac",
+ "description": "Bad named entity: angmsdac without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&angmsdac"
+ ]
+ ]
+ },
+ {
+ "input": "⦪",
+ "description": "Named entity: angmsdac; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29aa"
+ ]
+ ]
+ },
+ {
+ "input": "&angmsdad",
+ "description": "Bad named entity: angmsdad without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&angmsdad"
+ ]
+ ]
+ },
+ {
+ "input": "⦫",
+ "description": "Named entity: angmsdad; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29ab"
+ ]
+ ]
+ },
+ {
+ "input": "&angmsdae",
+ "description": "Bad named entity: angmsdae without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&angmsdae"
+ ]
+ ]
+ },
+ {
+ "input": "⦬",
+ "description": "Named entity: angmsdae; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29ac"
+ ]
+ ]
+ },
+ {
+ "input": "&angmsdaf",
+ "description": "Bad named entity: angmsdaf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&angmsdaf"
+ ]
+ ]
+ },
+ {
+ "input": "⦭",
+ "description": "Named entity: angmsdaf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29ad"
+ ]
+ ]
+ },
+ {
+ "input": "&angmsdag",
+ "description": "Bad named entity: angmsdag without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&angmsdag"
+ ]
+ ]
+ },
+ {
+ "input": "⦮",
+ "description": "Named entity: angmsdag; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29ae"
+ ]
+ ]
+ },
+ {
+ "input": "&angmsdah",
+ "description": "Bad named entity: angmsdah without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&angmsdah"
+ ]
+ ]
+ },
+ {
+ "input": "⦯",
+ "description": "Named entity: angmsdah; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29af"
+ ]
+ ]
+ },
+ {
+ "input": "&angrt",
+ "description": "Bad named entity: angrt without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&angrt"
+ ]
+ ]
+ },
+ {
+ "input": "∟",
+ "description": "Named entity: angrt; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u221f"
+ ]
+ ]
+ },
+ {
+ "input": "&angrtvb",
+ "description": "Bad named entity: angrtvb without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&angrtvb"
+ ]
+ ]
+ },
+ {
+ "input": "⊾",
+ "description": "Named entity: angrtvb; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22be"
+ ]
+ ]
+ },
+ {
+ "input": "&angrtvbd",
+ "description": "Bad named entity: angrtvbd without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&angrtvbd"
+ ]
+ ]
+ },
+ {
+ "input": "⦝",
+ "description": "Named entity: angrtvbd; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u299d"
+ ]
+ ]
+ },
+ {
+ "input": "&angsph",
+ "description": "Bad named entity: angsph without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&angsph"
+ ]
+ ]
+ },
+ {
+ "input": "∢",
+ "description": "Named entity: angsph; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2222"
+ ]
+ ]
+ },
+ {
+ "input": "&angst",
+ "description": "Bad named entity: angst without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&angst"
+ ]
+ ]
+ },
+ {
+ "input": "Å",
+ "description": "Named entity: angst; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00c5"
+ ]
+ ]
+ },
+ {
+ "input": "&angzarr",
+ "description": "Bad named entity: angzarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&angzarr"
+ ]
+ ]
+ },
+ {
+ "input": "⍼",
+ "description": "Named entity: angzarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u237c"
+ ]
+ ]
+ },
+ {
+ "input": "&aogon",
+ "description": "Bad named entity: aogon without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&aogon"
+ ]
+ ]
+ },
+ {
+ "input": "ą",
+ "description": "Named entity: aogon; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0105"
+ ]
+ ]
+ },
+ {
+ "input": "&aopf",
+ "description": "Bad named entity: aopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&aopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕒",
+ "description": "Named entity: aopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd52"
+ ]
+ ]
+ },
+ {
+ "input": "&ap",
+ "description": "Bad named entity: ap without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ap"
+ ]
+ ]
+ },
+ {
+ "input": "≈",
+ "description": "Named entity: ap; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2248"
+ ]
+ ]
+ },
+ {
+ "input": "&apE",
+ "description": "Bad named entity: apE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&apE"
+ ]
+ ]
+ },
+ {
+ "input": "⩰",
+ "description": "Named entity: apE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a70"
+ ]
+ ]
+ },
+ {
+ "input": "&apacir",
+ "description": "Bad named entity: apacir without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&apacir"
+ ]
+ ]
+ },
+ {
+ "input": "⩯",
+ "description": "Named entity: apacir; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a6f"
+ ]
+ ]
+ },
+ {
+ "input": "&ape",
+ "description": "Bad named entity: ape without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ape"
+ ]
+ ]
+ },
+ {
+ "input": "≊",
+ "description": "Named entity: ape; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u224a"
+ ]
+ ]
+ },
+ {
+ "input": "&apid",
+ "description": "Bad named entity: apid without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&apid"
+ ]
+ ]
+ },
+ {
+ "input": "≋",
+ "description": "Named entity: apid; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u224b"
+ ]
+ ]
+ },
+ {
+ "input": "&apos",
+ "description": "Bad named entity: apos without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&apos"
+ ]
+ ]
+ },
+ {
+ "input": "'",
+ "description": "Named entity: apos; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "'"
+ ]
+ ]
+ },
+ {
+ "input": "&approx",
+ "description": "Bad named entity: approx without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&approx"
+ ]
+ ]
+ },
+ {
+ "input": "≈",
+ "description": "Named entity: approx; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2248"
+ ]
+ ]
+ },
+ {
+ "input": "&approxeq",
+ "description": "Bad named entity: approxeq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&approxeq"
+ ]
+ ]
+ },
+ {
+ "input": "≊",
+ "description": "Named entity: approxeq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u224a"
+ ]
+ ]
+ },
+ {
+ "input": "å",
+ "description": "Named entity: aring without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00e5"
+ ]
+ ]
+ },
+ {
+ "input": "å",
+ "description": "Named entity: aring; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00e5"
+ ]
+ ]
+ },
+ {
+ "input": "&ascr",
+ "description": "Bad named entity: ascr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ascr"
+ ]
+ ]
+ },
+ {
+ "input": "𝒶",
+ "description": "Named entity: ascr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udcb6"
+ ]
+ ]
+ },
+ {
+ "input": "&ast",
+ "description": "Bad named entity: ast without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ast"
+ ]
+ ]
+ },
+ {
+ "input": "*",
+ "description": "Named entity: ast; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "*"
+ ]
+ ]
+ },
+ {
+ "input": "&asymp",
+ "description": "Bad named entity: asymp without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&asymp"
+ ]
+ ]
+ },
+ {
+ "input": "≈",
+ "description": "Named entity: asymp; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2248"
+ ]
+ ]
+ },
+ {
+ "input": "&asympeq",
+ "description": "Bad named entity: asympeq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&asympeq"
+ ]
+ ]
+ },
+ {
+ "input": "≍",
+ "description": "Named entity: asympeq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u224d"
+ ]
+ ]
+ },
+ {
+ "input": "ã",
+ "description": "Named entity: atilde without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00e3"
+ ]
+ ]
+ },
+ {
+ "input": "ã",
+ "description": "Named entity: atilde; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00e3"
+ ]
+ ]
+ },
+ {
+ "input": "ä",
+ "description": "Named entity: auml without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00e4"
+ ]
+ ]
+ },
+ {
+ "input": "ä",
+ "description": "Named entity: auml; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00e4"
+ ]
+ ]
+ },
+ {
+ "input": "&awconint",
+ "description": "Bad named entity: awconint without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&awconint"
+ ]
+ ]
+ },
+ {
+ "input": "∳",
+ "description": "Named entity: awconint; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2233"
+ ]
+ ]
+ },
+ {
+ "input": "&awint",
+ "description": "Bad named entity: awint without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&awint"
+ ]
+ ]
+ },
+ {
+ "input": "⨑",
+ "description": "Named entity: awint; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a11"
+ ]
+ ]
+ },
+ {
+ "input": "&bNot",
+ "description": "Bad named entity: bNot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bNot"
+ ]
+ ]
+ },
+ {
+ "input": "⫭",
+ "description": "Named entity: bNot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2aed"
+ ]
+ ]
+ },
+ {
+ "input": "&backcong",
+ "description": "Bad named entity: backcong without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&backcong"
+ ]
+ ]
+ },
+ {
+ "input": "≌",
+ "description": "Named entity: backcong; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u224c"
+ ]
+ ]
+ },
+ {
+ "input": "&backepsilon",
+ "description": "Bad named entity: backepsilon without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&backepsilon"
+ ]
+ ]
+ },
+ {
+ "input": "϶",
+ "description": "Named entity: backepsilon; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03f6"
+ ]
+ ]
+ },
+ {
+ "input": "&backprime",
+ "description": "Bad named entity: backprime without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&backprime"
+ ]
+ ]
+ },
+ {
+ "input": "‵",
+ "description": "Named entity: backprime; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2035"
+ ]
+ ]
+ },
+ {
+ "input": "&backsim",
+ "description": "Bad named entity: backsim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&backsim"
+ ]
+ ]
+ },
+ {
+ "input": "∽",
+ "description": "Named entity: backsim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u223d"
+ ]
+ ]
+ },
+ {
+ "input": "&backsimeq",
+ "description": "Bad named entity: backsimeq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&backsimeq"
+ ]
+ ]
+ },
+ {
+ "input": "⋍",
+ "description": "Named entity: backsimeq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22cd"
+ ]
+ ]
+ },
+ {
+ "input": "&barvee",
+ "description": "Bad named entity: barvee without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&barvee"
+ ]
+ ]
+ },
+ {
+ "input": "⊽",
+ "description": "Named entity: barvee; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22bd"
+ ]
+ ]
+ },
+ {
+ "input": "&barwed",
+ "description": "Bad named entity: barwed without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&barwed"
+ ]
+ ]
+ },
+ {
+ "input": "⌅",
+ "description": "Named entity: barwed; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2305"
+ ]
+ ]
+ },
+ {
+ "input": "&barwedge",
+ "description": "Bad named entity: barwedge without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&barwedge"
+ ]
+ ]
+ },
+ {
+ "input": "⌅",
+ "description": "Named entity: barwedge; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2305"
+ ]
+ ]
+ },
+ {
+ "input": "&bbrk",
+ "description": "Bad named entity: bbrk without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bbrk"
+ ]
+ ]
+ },
+ {
+ "input": "⎵",
+ "description": "Named entity: bbrk; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u23b5"
+ ]
+ ]
+ },
+ {
+ "input": "&bbrktbrk",
+ "description": "Bad named entity: bbrktbrk without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bbrktbrk"
+ ]
+ ]
+ },
+ {
+ "input": "⎶",
+ "description": "Named entity: bbrktbrk; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u23b6"
+ ]
+ ]
+ },
+ {
+ "input": "&bcong",
+ "description": "Bad named entity: bcong without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bcong"
+ ]
+ ]
+ },
+ {
+ "input": "≌",
+ "description": "Named entity: bcong; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u224c"
+ ]
+ ]
+ },
+ {
+ "input": "&bcy",
+ "description": "Bad named entity: bcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bcy"
+ ]
+ ]
+ },
+ {
+ "input": "б",
+ "description": "Named entity: bcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0431"
+ ]
+ ]
+ },
+ {
+ "input": "&bdquo",
+ "description": "Bad named entity: bdquo without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bdquo"
+ ]
+ ]
+ },
+ {
+ "input": "„",
+ "description": "Named entity: bdquo; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u201e"
+ ]
+ ]
+ },
+ {
+ "input": "&becaus",
+ "description": "Bad named entity: becaus without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&becaus"
+ ]
+ ]
+ },
+ {
+ "input": "∵",
+ "description": "Named entity: becaus; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2235"
+ ]
+ ]
+ },
+ {
+ "input": "&because",
+ "description": "Bad named entity: because without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&because"
+ ]
+ ]
+ },
+ {
+ "input": "∵",
+ "description": "Named entity: because; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2235"
+ ]
+ ]
+ },
+ {
+ "input": "&bemptyv",
+ "description": "Bad named entity: bemptyv without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bemptyv"
+ ]
+ ]
+ },
+ {
+ "input": "⦰",
+ "description": "Named entity: bemptyv; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29b0"
+ ]
+ ]
+ },
+ {
+ "input": "&bepsi",
+ "description": "Bad named entity: bepsi without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bepsi"
+ ]
+ ]
+ },
+ {
+ "input": "϶",
+ "description": "Named entity: bepsi; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03f6"
+ ]
+ ]
+ },
+ {
+ "input": "&bernou",
+ "description": "Bad named entity: bernou without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bernou"
+ ]
+ ]
+ },
+ {
+ "input": "ℬ",
+ "description": "Named entity: bernou; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u212c"
+ ]
+ ]
+ },
+ {
+ "input": "&beta",
+ "description": "Bad named entity: beta without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&beta"
+ ]
+ ]
+ },
+ {
+ "input": "β",
+ "description": "Named entity: beta; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03b2"
+ ]
+ ]
+ },
+ {
+ "input": "&beth",
+ "description": "Bad named entity: beth without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&beth"
+ ]
+ ]
+ },
+ {
+ "input": "ℶ",
+ "description": "Named entity: beth; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2136"
+ ]
+ ]
+ },
+ {
+ "input": "&between",
+ "description": "Bad named entity: between without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&between"
+ ]
+ ]
+ },
+ {
+ "input": "≬",
+ "description": "Named entity: between; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u226c"
+ ]
+ ]
+ },
+ {
+ "input": "&bfr",
+ "description": "Bad named entity: bfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔟",
+ "description": "Named entity: bfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd1f"
+ ]
+ ]
+ },
+ {
+ "input": "&bigcap",
+ "description": "Bad named entity: bigcap without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bigcap"
+ ]
+ ]
+ },
+ {
+ "input": "⋂",
+ "description": "Named entity: bigcap; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22c2"
+ ]
+ ]
+ },
+ {
+ "input": "&bigcirc",
+ "description": "Bad named entity: bigcirc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bigcirc"
+ ]
+ ]
+ },
+ {
+ "input": "◯",
+ "description": "Named entity: bigcirc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25ef"
+ ]
+ ]
+ },
+ {
+ "input": "&bigcup",
+ "description": "Bad named entity: bigcup without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bigcup"
+ ]
+ ]
+ },
+ {
+ "input": "⋃",
+ "description": "Named entity: bigcup; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22c3"
+ ]
+ ]
+ },
+ {
+ "input": "&bigodot",
+ "description": "Bad named entity: bigodot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bigodot"
+ ]
+ ]
+ },
+ {
+ "input": "⨀",
+ "description": "Named entity: bigodot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a00"
+ ]
+ ]
+ },
+ {
+ "input": "&bigoplus",
+ "description": "Bad named entity: bigoplus without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bigoplus"
+ ]
+ ]
+ },
+ {
+ "input": "⨁",
+ "description": "Named entity: bigoplus; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a01"
+ ]
+ ]
+ },
+ {
+ "input": "&bigotimes",
+ "description": "Bad named entity: bigotimes without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bigotimes"
+ ]
+ ]
+ },
+ {
+ "input": "⨂",
+ "description": "Named entity: bigotimes; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a02"
+ ]
+ ]
+ },
+ {
+ "input": "&bigsqcup",
+ "description": "Bad named entity: bigsqcup without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bigsqcup"
+ ]
+ ]
+ },
+ {
+ "input": "⨆",
+ "description": "Named entity: bigsqcup; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a06"
+ ]
+ ]
+ },
+ {
+ "input": "&bigstar",
+ "description": "Bad named entity: bigstar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bigstar"
+ ]
+ ]
+ },
+ {
+ "input": "★",
+ "description": "Named entity: bigstar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2605"
+ ]
+ ]
+ },
+ {
+ "input": "&bigtriangledown",
+ "description": "Bad named entity: bigtriangledown without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bigtriangledown"
+ ]
+ ]
+ },
+ {
+ "input": "▽",
+ "description": "Named entity: bigtriangledown; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25bd"
+ ]
+ ]
+ },
+ {
+ "input": "&bigtriangleup",
+ "description": "Bad named entity: bigtriangleup without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bigtriangleup"
+ ]
+ ]
+ },
+ {
+ "input": "△",
+ "description": "Named entity: bigtriangleup; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25b3"
+ ]
+ ]
+ },
+ {
+ "input": "&biguplus",
+ "description": "Bad named entity: biguplus without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&biguplus"
+ ]
+ ]
+ },
+ {
+ "input": "⨄",
+ "description": "Named entity: biguplus; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a04"
+ ]
+ ]
+ },
+ {
+ "input": "&bigvee",
+ "description": "Bad named entity: bigvee without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bigvee"
+ ]
+ ]
+ },
+ {
+ "input": "⋁",
+ "description": "Named entity: bigvee; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22c1"
+ ]
+ ]
+ },
+ {
+ "input": "&bigwedge",
+ "description": "Bad named entity: bigwedge without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bigwedge"
+ ]
+ ]
+ },
+ {
+ "input": "⋀",
+ "description": "Named entity: bigwedge; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22c0"
+ ]
+ ]
+ },
+ {
+ "input": "&bkarow",
+ "description": "Bad named entity: bkarow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bkarow"
+ ]
+ ]
+ },
+ {
+ "input": "⤍",
+ "description": "Named entity: bkarow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u290d"
+ ]
+ ]
+ },
+ {
+ "input": "&blacklozenge",
+ "description": "Bad named entity: blacklozenge without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&blacklozenge"
+ ]
+ ]
+ },
+ {
+ "input": "⧫",
+ "description": "Named entity: blacklozenge; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29eb"
+ ]
+ ]
+ },
+ {
+ "input": "&blacksquare",
+ "description": "Bad named entity: blacksquare without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&blacksquare"
+ ]
+ ]
+ },
+ {
+ "input": "▪",
+ "description": "Named entity: blacksquare; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25aa"
+ ]
+ ]
+ },
+ {
+ "input": "&blacktriangle",
+ "description": "Bad named entity: blacktriangle without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&blacktriangle"
+ ]
+ ]
+ },
+ {
+ "input": "▴",
+ "description": "Named entity: blacktriangle; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25b4"
+ ]
+ ]
+ },
+ {
+ "input": "&blacktriangledown",
+ "description": "Bad named entity: blacktriangledown without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&blacktriangledown"
+ ]
+ ]
+ },
+ {
+ "input": "▾",
+ "description": "Named entity: blacktriangledown; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25be"
+ ]
+ ]
+ },
+ {
+ "input": "&blacktriangleleft",
+ "description": "Bad named entity: blacktriangleleft without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&blacktriangleleft"
+ ]
+ ]
+ },
+ {
+ "input": "◂",
+ "description": "Named entity: blacktriangleleft; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25c2"
+ ]
+ ]
+ },
+ {
+ "input": "&blacktriangleright",
+ "description": "Bad named entity: blacktriangleright without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&blacktriangleright"
+ ]
+ ]
+ },
+ {
+ "input": "▸",
+ "description": "Named entity: blacktriangleright; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25b8"
+ ]
+ ]
+ },
+ {
+ "input": "&blank",
+ "description": "Bad named entity: blank without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&blank"
+ ]
+ ]
+ },
+ {
+ "input": "␣",
+ "description": "Named entity: blank; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2423"
+ ]
+ ]
+ },
+ {
+ "input": "&blk12",
+ "description": "Bad named entity: blk12 without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&blk12"
+ ]
+ ]
+ },
+ {
+ "input": "▒",
+ "description": "Named entity: blk12; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2592"
+ ]
+ ]
+ },
+ {
+ "input": "&blk14",
+ "description": "Bad named entity: blk14 without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&blk14"
+ ]
+ ]
+ },
+ {
+ "input": "░",
+ "description": "Named entity: blk14; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2591"
+ ]
+ ]
+ },
+ {
+ "input": "&blk34",
+ "description": "Bad named entity: blk34 without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&blk34"
+ ]
+ ]
+ },
+ {
+ "input": "▓",
+ "description": "Named entity: blk34; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2593"
+ ]
+ ]
+ },
+ {
+ "input": "&block",
+ "description": "Bad named entity: block without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&block"
+ ]
+ ]
+ },
+ {
+ "input": "█",
+ "description": "Named entity: block; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2588"
+ ]
+ ]
+ },
+ {
+ "input": "&bne",
+ "description": "Bad named entity: bne without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bne"
+ ]
+ ]
+ },
+ {
+ "input": "=⃥",
+ "description": "Named entity: bne; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "=\u20e5"
+ ]
+ ]
+ },
+ {
+ "input": "&bnequiv",
+ "description": "Bad named entity: bnequiv without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bnequiv"
+ ]
+ ]
+ },
+ {
+ "input": "≡⃥",
+ "description": "Named entity: bnequiv; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2261\u20e5"
+ ]
+ ]
+ },
+ {
+ "input": "&bnot",
+ "description": "Bad named entity: bnot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bnot"
+ ]
+ ]
+ },
+ {
+ "input": "⌐",
+ "description": "Named entity: bnot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2310"
+ ]
+ ]
+ },
+ {
+ "input": "&bopf",
+ "description": "Bad named entity: bopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕓",
+ "description": "Named entity: bopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd53"
+ ]
+ ]
+ },
+ {
+ "input": "&bot",
+ "description": "Bad named entity: bot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bot"
+ ]
+ ]
+ },
+ {
+ "input": "⊥",
+ "description": "Named entity: bot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22a5"
+ ]
+ ]
+ },
+ {
+ "input": "&bottom",
+ "description": "Bad named entity: bottom without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bottom"
+ ]
+ ]
+ },
+ {
+ "input": "⊥",
+ "description": "Named entity: bottom; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22a5"
+ ]
+ ]
+ },
+ {
+ "input": "&bowtie",
+ "description": "Bad named entity: bowtie without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bowtie"
+ ]
+ ]
+ },
+ {
+ "input": "⋈",
+ "description": "Named entity: bowtie; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22c8"
+ ]
+ ]
+ },
+ {
+ "input": "&boxDL",
+ "description": "Bad named entity: boxDL without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxDL"
+ ]
+ ]
+ },
+ {
+ "input": "╗",
+ "description": "Named entity: boxDL; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2557"
+ ]
+ ]
+ },
+ {
+ "input": "&boxDR",
+ "description": "Bad named entity: boxDR without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxDR"
+ ]
+ ]
+ },
+ {
+ "input": "╔",
+ "description": "Named entity: boxDR; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2554"
+ ]
+ ]
+ },
+ {
+ "input": "&boxDl",
+ "description": "Bad named entity: boxDl without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxDl"
+ ]
+ ]
+ },
+ {
+ "input": "╖",
+ "description": "Named entity: boxDl; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2556"
+ ]
+ ]
+ },
+ {
+ "input": "&boxDr",
+ "description": "Bad named entity: boxDr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxDr"
+ ]
+ ]
+ },
+ {
+ "input": "╓",
+ "description": "Named entity: boxDr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2553"
+ ]
+ ]
+ },
+ {
+ "input": "&boxH",
+ "description": "Bad named entity: boxH without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxH"
+ ]
+ ]
+ },
+ {
+ "input": "═",
+ "description": "Named entity: boxH; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2550"
+ ]
+ ]
+ },
+ {
+ "input": "&boxHD",
+ "description": "Bad named entity: boxHD without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxHD"
+ ]
+ ]
+ },
+ {
+ "input": "╦",
+ "description": "Named entity: boxHD; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2566"
+ ]
+ ]
+ },
+ {
+ "input": "&boxHU",
+ "description": "Bad named entity: boxHU without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxHU"
+ ]
+ ]
+ },
+ {
+ "input": "╩",
+ "description": "Named entity: boxHU; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2569"
+ ]
+ ]
+ },
+ {
+ "input": "&boxHd",
+ "description": "Bad named entity: boxHd without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxHd"
+ ]
+ ]
+ },
+ {
+ "input": "╤",
+ "description": "Named entity: boxHd; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2564"
+ ]
+ ]
+ },
+ {
+ "input": "&boxHu",
+ "description": "Bad named entity: boxHu without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxHu"
+ ]
+ ]
+ },
+ {
+ "input": "╧",
+ "description": "Named entity: boxHu; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2567"
+ ]
+ ]
+ },
+ {
+ "input": "&boxUL",
+ "description": "Bad named entity: boxUL without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxUL"
+ ]
+ ]
+ },
+ {
+ "input": "╝",
+ "description": "Named entity: boxUL; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u255d"
+ ]
+ ]
+ },
+ {
+ "input": "&boxUR",
+ "description": "Bad named entity: boxUR without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxUR"
+ ]
+ ]
+ },
+ {
+ "input": "╚",
+ "description": "Named entity: boxUR; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u255a"
+ ]
+ ]
+ },
+ {
+ "input": "&boxUl",
+ "description": "Bad named entity: boxUl without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxUl"
+ ]
+ ]
+ },
+ {
+ "input": "╜",
+ "description": "Named entity: boxUl; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u255c"
+ ]
+ ]
+ },
+ {
+ "input": "&boxUr",
+ "description": "Bad named entity: boxUr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxUr"
+ ]
+ ]
+ },
+ {
+ "input": "╙",
+ "description": "Named entity: boxUr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2559"
+ ]
+ ]
+ },
+ {
+ "input": "&boxV",
+ "description": "Bad named entity: boxV without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxV"
+ ]
+ ]
+ },
+ {
+ "input": "║",
+ "description": "Named entity: boxV; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2551"
+ ]
+ ]
+ },
+ {
+ "input": "&boxVH",
+ "description": "Bad named entity: boxVH without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxVH"
+ ]
+ ]
+ },
+ {
+ "input": "╬",
+ "description": "Named entity: boxVH; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u256c"
+ ]
+ ]
+ },
+ {
+ "input": "&boxVL",
+ "description": "Bad named entity: boxVL without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxVL"
+ ]
+ ]
+ },
+ {
+ "input": "╣",
+ "description": "Named entity: boxVL; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2563"
+ ]
+ ]
+ },
+ {
+ "input": "&boxVR",
+ "description": "Bad named entity: boxVR without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxVR"
+ ]
+ ]
+ },
+ {
+ "input": "╠",
+ "description": "Named entity: boxVR; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2560"
+ ]
+ ]
+ },
+ {
+ "input": "&boxVh",
+ "description": "Bad named entity: boxVh without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxVh"
+ ]
+ ]
+ },
+ {
+ "input": "╫",
+ "description": "Named entity: boxVh; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u256b"
+ ]
+ ]
+ },
+ {
+ "input": "&boxVl",
+ "description": "Bad named entity: boxVl without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxVl"
+ ]
+ ]
+ },
+ {
+ "input": "╢",
+ "description": "Named entity: boxVl; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2562"
+ ]
+ ]
+ },
+ {
+ "input": "&boxVr",
+ "description": "Bad named entity: boxVr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxVr"
+ ]
+ ]
+ },
+ {
+ "input": "╟",
+ "description": "Named entity: boxVr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u255f"
+ ]
+ ]
+ },
+ {
+ "input": "&boxbox",
+ "description": "Bad named entity: boxbox without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxbox"
+ ]
+ ]
+ },
+ {
+ "input": "⧉",
+ "description": "Named entity: boxbox; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29c9"
+ ]
+ ]
+ },
+ {
+ "input": "&boxdL",
+ "description": "Bad named entity: boxdL without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxdL"
+ ]
+ ]
+ },
+ {
+ "input": "╕",
+ "description": "Named entity: boxdL; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2555"
+ ]
+ ]
+ },
+ {
+ "input": "&boxdR",
+ "description": "Bad named entity: boxdR without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxdR"
+ ]
+ ]
+ },
+ {
+ "input": "╒",
+ "description": "Named entity: boxdR; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2552"
+ ]
+ ]
+ },
+ {
+ "input": "&boxdl",
+ "description": "Bad named entity: boxdl without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxdl"
+ ]
+ ]
+ },
+ {
+ "input": "┐",
+ "description": "Named entity: boxdl; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2510"
+ ]
+ ]
+ },
+ {
+ "input": "&boxdr",
+ "description": "Bad named entity: boxdr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxdr"
+ ]
+ ]
+ },
+ {
+ "input": "┌",
+ "description": "Named entity: boxdr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u250c"
+ ]
+ ]
+ },
+ {
+ "input": "&boxh",
+ "description": "Bad named entity: boxh without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxh"
+ ]
+ ]
+ },
+ {
+ "input": "─",
+ "description": "Named entity: boxh; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2500"
+ ]
+ ]
+ },
+ {
+ "input": "&boxhD",
+ "description": "Bad named entity: boxhD without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxhD"
+ ]
+ ]
+ },
+ {
+ "input": "╥",
+ "description": "Named entity: boxhD; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2565"
+ ]
+ ]
+ },
+ {
+ "input": "&boxhU",
+ "description": "Bad named entity: boxhU without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxhU"
+ ]
+ ]
+ },
+ {
+ "input": "╨",
+ "description": "Named entity: boxhU; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2568"
+ ]
+ ]
+ },
+ {
+ "input": "&boxhd",
+ "description": "Bad named entity: boxhd without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxhd"
+ ]
+ ]
+ },
+ {
+ "input": "┬",
+ "description": "Named entity: boxhd; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u252c"
+ ]
+ ]
+ },
+ {
+ "input": "&boxhu",
+ "description": "Bad named entity: boxhu without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxhu"
+ ]
+ ]
+ },
+ {
+ "input": "┴",
+ "description": "Named entity: boxhu; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2534"
+ ]
+ ]
+ },
+ {
+ "input": "&boxminus",
+ "description": "Bad named entity: boxminus without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxminus"
+ ]
+ ]
+ },
+ {
+ "input": "⊟",
+ "description": "Named entity: boxminus; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u229f"
+ ]
+ ]
+ },
+ {
+ "input": "&boxplus",
+ "description": "Bad named entity: boxplus without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxplus"
+ ]
+ ]
+ },
+ {
+ "input": "⊞",
+ "description": "Named entity: boxplus; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u229e"
+ ]
+ ]
+ },
+ {
+ "input": "&boxtimes",
+ "description": "Bad named entity: boxtimes without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxtimes"
+ ]
+ ]
+ },
+ {
+ "input": "⊠",
+ "description": "Named entity: boxtimes; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22a0"
+ ]
+ ]
+ },
+ {
+ "input": "&boxuL",
+ "description": "Bad named entity: boxuL without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxuL"
+ ]
+ ]
+ },
+ {
+ "input": "╛",
+ "description": "Named entity: boxuL; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u255b"
+ ]
+ ]
+ },
+ {
+ "input": "&boxuR",
+ "description": "Bad named entity: boxuR without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxuR"
+ ]
+ ]
+ },
+ {
+ "input": "╘",
+ "description": "Named entity: boxuR; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2558"
+ ]
+ ]
+ },
+ {
+ "input": "&boxul",
+ "description": "Bad named entity: boxul without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxul"
+ ]
+ ]
+ },
+ {
+ "input": "┘",
+ "description": "Named entity: boxul; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2518"
+ ]
+ ]
+ },
+ {
+ "input": "&boxur",
+ "description": "Bad named entity: boxur without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxur"
+ ]
+ ]
+ },
+ {
+ "input": "└",
+ "description": "Named entity: boxur; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2514"
+ ]
+ ]
+ },
+ {
+ "input": "&boxv",
+ "description": "Bad named entity: boxv without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxv"
+ ]
+ ]
+ },
+ {
+ "input": "│",
+ "description": "Named entity: boxv; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2502"
+ ]
+ ]
+ },
+ {
+ "input": "&boxvH",
+ "description": "Bad named entity: boxvH without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxvH"
+ ]
+ ]
+ },
+ {
+ "input": "╪",
+ "description": "Named entity: boxvH; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u256a"
+ ]
+ ]
+ },
+ {
+ "input": "&boxvL",
+ "description": "Bad named entity: boxvL without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxvL"
+ ]
+ ]
+ },
+ {
+ "input": "╡",
+ "description": "Named entity: boxvL; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2561"
+ ]
+ ]
+ },
+ {
+ "input": "&boxvR",
+ "description": "Bad named entity: boxvR without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxvR"
+ ]
+ ]
+ },
+ {
+ "input": "╞",
+ "description": "Named entity: boxvR; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u255e"
+ ]
+ ]
+ },
+ {
+ "input": "&boxvh",
+ "description": "Bad named entity: boxvh without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxvh"
+ ]
+ ]
+ },
+ {
+ "input": "┼",
+ "description": "Named entity: boxvh; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u253c"
+ ]
+ ]
+ },
+ {
+ "input": "&boxvl",
+ "description": "Bad named entity: boxvl without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxvl"
+ ]
+ ]
+ },
+ {
+ "input": "┤",
+ "description": "Named entity: boxvl; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2524"
+ ]
+ ]
+ },
+ {
+ "input": "&boxvr",
+ "description": "Bad named entity: boxvr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&boxvr"
+ ]
+ ]
+ },
+ {
+ "input": "├",
+ "description": "Named entity: boxvr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u251c"
+ ]
+ ]
+ },
+ {
+ "input": "&bprime",
+ "description": "Bad named entity: bprime without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bprime"
+ ]
+ ]
+ },
+ {
+ "input": "‵",
+ "description": "Named entity: bprime; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2035"
+ ]
+ ]
+ },
+ {
+ "input": "&breve",
+ "description": "Bad named entity: breve without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&breve"
+ ]
+ ]
+ },
+ {
+ "input": "˘",
+ "description": "Named entity: breve; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u02d8"
+ ]
+ ]
+ },
+ {
+ "input": "¦",
+ "description": "Named entity: brvbar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00a6"
+ ]
+ ]
+ },
+ {
+ "input": "¦",
+ "description": "Named entity: brvbar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00a6"
+ ]
+ ]
+ },
+ {
+ "input": "&bscr",
+ "description": "Bad named entity: bscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝒷",
+ "description": "Named entity: bscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udcb7"
+ ]
+ ]
+ },
+ {
+ "input": "&bsemi",
+ "description": "Bad named entity: bsemi without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bsemi"
+ ]
+ ]
+ },
+ {
+ "input": "⁏",
+ "description": "Named entity: bsemi; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u204f"
+ ]
+ ]
+ },
+ {
+ "input": "&bsim",
+ "description": "Bad named entity: bsim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bsim"
+ ]
+ ]
+ },
+ {
+ "input": "∽",
+ "description": "Named entity: bsim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u223d"
+ ]
+ ]
+ },
+ {
+ "input": "&bsime",
+ "description": "Bad named entity: bsime without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bsime"
+ ]
+ ]
+ },
+ {
+ "input": "⋍",
+ "description": "Named entity: bsime; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22cd"
+ ]
+ ]
+ },
+ {
+ "input": "&bsol",
+ "description": "Bad named entity: bsol without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bsol"
+ ]
+ ]
+ },
+ {
+ "input": "\",
+ "description": "Named entity: bsol; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\\"
+ ]
+ ]
+ },
+ {
+ "input": "&bsolb",
+ "description": "Bad named entity: bsolb without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bsolb"
+ ]
+ ]
+ },
+ {
+ "input": "⧅",
+ "description": "Named entity: bsolb; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29c5"
+ ]
+ ]
+ },
+ {
+ "input": "&bsolhsub",
+ "description": "Bad named entity: bsolhsub without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bsolhsub"
+ ]
+ ]
+ },
+ {
+ "input": "⟈",
+ "description": "Named entity: bsolhsub; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27c8"
+ ]
+ ]
+ },
+ {
+ "input": "&bull",
+ "description": "Bad named entity: bull without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bull"
+ ]
+ ]
+ },
+ {
+ "input": "•",
+ "description": "Named entity: bull; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2022"
+ ]
+ ]
+ },
+ {
+ "input": "&bullet",
+ "description": "Bad named entity: bullet without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bullet"
+ ]
+ ]
+ },
+ {
+ "input": "•",
+ "description": "Named entity: bullet; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2022"
+ ]
+ ]
+ },
+ {
+ "input": "&bump",
+ "description": "Bad named entity: bump without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bump"
+ ]
+ ]
+ },
+ {
+ "input": "≎",
+ "description": "Named entity: bump; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u224e"
+ ]
+ ]
+ },
+ {
+ "input": "&bumpE",
+ "description": "Bad named entity: bumpE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bumpE"
+ ]
+ ]
+ },
+ {
+ "input": "⪮",
+ "description": "Named entity: bumpE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2aae"
+ ]
+ ]
+ },
+ {
+ "input": "&bumpe",
+ "description": "Bad named entity: bumpe without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bumpe"
+ ]
+ ]
+ },
+ {
+ "input": "≏",
+ "description": "Named entity: bumpe; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u224f"
+ ]
+ ]
+ },
+ {
+ "input": "&bumpeq",
+ "description": "Bad named entity: bumpeq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&bumpeq"
+ ]
+ ]
+ },
+ {
+ "input": "≏",
+ "description": "Named entity: bumpeq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u224f"
+ ]
+ ]
+ },
+ {
+ "input": "&cacute",
+ "description": "Bad named entity: cacute without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cacute"
+ ]
+ ]
+ },
+ {
+ "input": "ć",
+ "description": "Named entity: cacute; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0107"
+ ]
+ ]
+ },
+ {
+ "input": "&cap",
+ "description": "Bad named entity: cap without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cap"
+ ]
+ ]
+ },
+ {
+ "input": "∩",
+ "description": "Named entity: cap; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2229"
+ ]
+ ]
+ },
+ {
+ "input": "&capand",
+ "description": "Bad named entity: capand without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&capand"
+ ]
+ ]
+ },
+ {
+ "input": "⩄",
+ "description": "Named entity: capand; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a44"
+ ]
+ ]
+ },
+ {
+ "input": "&capbrcup",
+ "description": "Bad named entity: capbrcup without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&capbrcup"
+ ]
+ ]
+ },
+ {
+ "input": "⩉",
+ "description": "Named entity: capbrcup; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a49"
+ ]
+ ]
+ },
+ {
+ "input": "&capcap",
+ "description": "Bad named entity: capcap without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&capcap"
+ ]
+ ]
+ },
+ {
+ "input": "⩋",
+ "description": "Named entity: capcap; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a4b"
+ ]
+ ]
+ },
+ {
+ "input": "&capcup",
+ "description": "Bad named entity: capcup without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&capcup"
+ ]
+ ]
+ },
+ {
+ "input": "⩇",
+ "description": "Named entity: capcup; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a47"
+ ]
+ ]
+ },
+ {
+ "input": "&capdot",
+ "description": "Bad named entity: capdot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&capdot"
+ ]
+ ]
+ },
+ {
+ "input": "⩀",
+ "description": "Named entity: capdot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a40"
+ ]
+ ]
+ },
+ {
+ "input": "&caps",
+ "description": "Bad named entity: caps without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&caps"
+ ]
+ ]
+ },
+ {
+ "input": "∩︀",
+ "description": "Named entity: caps; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2229\ufe00"
+ ]
+ ]
+ },
+ {
+ "input": "&caret",
+ "description": "Bad named entity: caret without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&caret"
+ ]
+ ]
+ },
+ {
+ "input": "⁁",
+ "description": "Named entity: caret; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2041"
+ ]
+ ]
+ },
+ {
+ "input": "&caron",
+ "description": "Bad named entity: caron without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&caron"
+ ]
+ ]
+ },
+ {
+ "input": "ˇ",
+ "description": "Named entity: caron; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u02c7"
+ ]
+ ]
+ },
+ {
+ "input": "&ccaps",
+ "description": "Bad named entity: ccaps without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ccaps"
+ ]
+ ]
+ },
+ {
+ "input": "⩍",
+ "description": "Named entity: ccaps; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a4d"
+ ]
+ ]
+ },
+ {
+ "input": "&ccaron",
+ "description": "Bad named entity: ccaron without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ccaron"
+ ]
+ ]
+ },
+ {
+ "input": "č",
+ "description": "Named entity: ccaron; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u010d"
+ ]
+ ]
+ },
+ {
+ "input": "ç",
+ "description": "Named entity: ccedil without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00e7"
+ ]
+ ]
+ },
+ {
+ "input": "ç",
+ "description": "Named entity: ccedil; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00e7"
+ ]
+ ]
+ },
+ {
+ "input": "&ccirc",
+ "description": "Bad named entity: ccirc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ccirc"
+ ]
+ ]
+ },
+ {
+ "input": "ĉ",
+ "description": "Named entity: ccirc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0109"
+ ]
+ ]
+ },
+ {
+ "input": "&ccups",
+ "description": "Bad named entity: ccups without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ccups"
+ ]
+ ]
+ },
+ {
+ "input": "⩌",
+ "description": "Named entity: ccups; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a4c"
+ ]
+ ]
+ },
+ {
+ "input": "&ccupssm",
+ "description": "Bad named entity: ccupssm without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ccupssm"
+ ]
+ ]
+ },
+ {
+ "input": "⩐",
+ "description": "Named entity: ccupssm; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a50"
+ ]
+ ]
+ },
+ {
+ "input": "&cdot",
+ "description": "Bad named entity: cdot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cdot"
+ ]
+ ]
+ },
+ {
+ "input": "ċ",
+ "description": "Named entity: cdot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u010b"
+ ]
+ ]
+ },
+ {
+ "input": "¸",
+ "description": "Named entity: cedil without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00b8"
+ ]
+ ]
+ },
+ {
+ "input": "¸",
+ "description": "Named entity: cedil; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00b8"
+ ]
+ ]
+ },
+ {
+ "input": "&cemptyv",
+ "description": "Bad named entity: cemptyv without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cemptyv"
+ ]
+ ]
+ },
+ {
+ "input": "⦲",
+ "description": "Named entity: cemptyv; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29b2"
+ ]
+ ]
+ },
+ {
+ "input": "¢",
+ "description": "Named entity: cent without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00a2"
+ ]
+ ]
+ },
+ {
+ "input": "¢",
+ "description": "Named entity: cent; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00a2"
+ ]
+ ]
+ },
+ {
+ "input": "·",
+ "description": "Named entity: centerdot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00b7"
+ ]
+ ]
+ },
+ {
+ "input": "&cfr",
+ "description": "Bad named entity: cfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔠",
+ "description": "Named entity: cfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd20"
+ ]
+ ]
+ },
+ {
+ "input": "&chcy",
+ "description": "Bad named entity: chcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&chcy"
+ ]
+ ]
+ },
+ {
+ "input": "ч",
+ "description": "Named entity: chcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0447"
+ ]
+ ]
+ },
+ {
+ "input": "&check",
+ "description": "Bad named entity: check without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&check"
+ ]
+ ]
+ },
+ {
+ "input": "✓",
+ "description": "Named entity: check; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2713"
+ ]
+ ]
+ },
+ {
+ "input": "&checkmark",
+ "description": "Bad named entity: checkmark without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&checkmark"
+ ]
+ ]
+ },
+ {
+ "input": "✓",
+ "description": "Named entity: checkmark; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2713"
+ ]
+ ]
+ },
+ {
+ "input": "&chi",
+ "description": "Bad named entity: chi without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&chi"
+ ]
+ ]
+ },
+ {
+ "input": "χ",
+ "description": "Named entity: chi; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03c7"
+ ]
+ ]
+ },
+ {
+ "input": "&cir",
+ "description": "Bad named entity: cir without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cir"
+ ]
+ ]
+ },
+ {
+ "input": "○",
+ "description": "Named entity: cir; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25cb"
+ ]
+ ]
+ },
+ {
+ "input": "&cirE",
+ "description": "Bad named entity: cirE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cirE"
+ ]
+ ]
+ },
+ {
+ "input": "⧃",
+ "description": "Named entity: cirE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29c3"
+ ]
+ ]
+ },
+ {
+ "input": "&circ",
+ "description": "Bad named entity: circ without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&circ"
+ ]
+ ]
+ },
+ {
+ "input": "ˆ",
+ "description": "Named entity: circ; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u02c6"
+ ]
+ ]
+ },
+ {
+ "input": "&circeq",
+ "description": "Bad named entity: circeq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&circeq"
+ ]
+ ]
+ },
+ {
+ "input": "≗",
+ "description": "Named entity: circeq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2257"
+ ]
+ ]
+ },
+ {
+ "input": "&circlearrowleft",
+ "description": "Bad named entity: circlearrowleft without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&circlearrowleft"
+ ]
+ ]
+ },
+ {
+ "input": "↺",
+ "description": "Named entity: circlearrowleft; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21ba"
+ ]
+ ]
+ },
+ {
+ "input": "&circlearrowright",
+ "description": "Bad named entity: circlearrowright without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&circlearrowright"
+ ]
+ ]
+ },
+ {
+ "input": "↻",
+ "description": "Named entity: circlearrowright; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21bb"
+ ]
+ ]
+ },
+ {
+ "input": "&circledR",
+ "description": "Bad named entity: circledR without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&circledR"
+ ]
+ ]
+ },
+ {
+ "input": "®",
+ "description": "Named entity: circledR; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00ae"
+ ]
+ ]
+ },
+ {
+ "input": "&circledS",
+ "description": "Bad named entity: circledS without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&circledS"
+ ]
+ ]
+ },
+ {
+ "input": "Ⓢ",
+ "description": "Named entity: circledS; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u24c8"
+ ]
+ ]
+ },
+ {
+ "input": "&circledast",
+ "description": "Bad named entity: circledast without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&circledast"
+ ]
+ ]
+ },
+ {
+ "input": "⊛",
+ "description": "Named entity: circledast; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u229b"
+ ]
+ ]
+ },
+ {
+ "input": "&circledcirc",
+ "description": "Bad named entity: circledcirc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&circledcirc"
+ ]
+ ]
+ },
+ {
+ "input": "⊚",
+ "description": "Named entity: circledcirc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u229a"
+ ]
+ ]
+ },
+ {
+ "input": "&circleddash",
+ "description": "Bad named entity: circleddash without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&circleddash"
+ ]
+ ]
+ },
+ {
+ "input": "⊝",
+ "description": "Named entity: circleddash; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u229d"
+ ]
+ ]
+ },
+ {
+ "input": "&cire",
+ "description": "Bad named entity: cire without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cire"
+ ]
+ ]
+ },
+ {
+ "input": "≗",
+ "description": "Named entity: cire; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2257"
+ ]
+ ]
+ },
+ {
+ "input": "&cirfnint",
+ "description": "Bad named entity: cirfnint without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cirfnint"
+ ]
+ ]
+ },
+ {
+ "input": "⨐",
+ "description": "Named entity: cirfnint; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a10"
+ ]
+ ]
+ },
+ {
+ "input": "&cirmid",
+ "description": "Bad named entity: cirmid without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cirmid"
+ ]
+ ]
+ },
+ {
+ "input": "⫯",
+ "description": "Named entity: cirmid; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2aef"
+ ]
+ ]
+ },
+ {
+ "input": "&cirscir",
+ "description": "Bad named entity: cirscir without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cirscir"
+ ]
+ ]
+ },
+ {
+ "input": "⧂",
+ "description": "Named entity: cirscir; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29c2"
+ ]
+ ]
+ },
+ {
+ "input": "&clubs",
+ "description": "Bad named entity: clubs without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&clubs"
+ ]
+ ]
+ },
+ {
+ "input": "♣",
+ "description": "Named entity: clubs; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2663"
+ ]
+ ]
+ },
+ {
+ "input": "&clubsuit",
+ "description": "Bad named entity: clubsuit without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&clubsuit"
+ ]
+ ]
+ },
+ {
+ "input": "♣",
+ "description": "Named entity: clubsuit; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2663"
+ ]
+ ]
+ },
+ {
+ "input": "&colon",
+ "description": "Bad named entity: colon without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&colon"
+ ]
+ ]
+ },
+ {
+ "input": ":",
+ "description": "Named entity: colon; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ ":"
+ ]
+ ]
+ },
+ {
+ "input": "&colone",
+ "description": "Bad named entity: colone without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&colone"
+ ]
+ ]
+ },
+ {
+ "input": "≔",
+ "description": "Named entity: colone; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2254"
+ ]
+ ]
+ },
+ {
+ "input": "&coloneq",
+ "description": "Bad named entity: coloneq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&coloneq"
+ ]
+ ]
+ },
+ {
+ "input": "≔",
+ "description": "Named entity: coloneq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2254"
+ ]
+ ]
+ },
+ {
+ "input": "&comma",
+ "description": "Bad named entity: comma without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&comma"
+ ]
+ ]
+ },
+ {
+ "input": ",",
+ "description": "Named entity: comma; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ ","
+ ]
+ ]
+ },
+ {
+ "input": "&commat",
+ "description": "Bad named entity: commat without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&commat"
+ ]
+ ]
+ },
+ {
+ "input": "@",
+ "description": "Named entity: commat; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "@"
+ ]
+ ]
+ },
+ {
+ "input": "&comp",
+ "description": "Bad named entity: comp without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&comp"
+ ]
+ ]
+ },
+ {
+ "input": "∁",
+ "description": "Named entity: comp; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2201"
+ ]
+ ]
+ },
+ {
+ "input": "&compfn",
+ "description": "Bad named entity: compfn without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&compfn"
+ ]
+ ]
+ },
+ {
+ "input": "∘",
+ "description": "Named entity: compfn; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2218"
+ ]
+ ]
+ },
+ {
+ "input": "&complement",
+ "description": "Bad named entity: complement without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&complement"
+ ]
+ ]
+ },
+ {
+ "input": "∁",
+ "description": "Named entity: complement; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2201"
+ ]
+ ]
+ },
+ {
+ "input": "&complexes",
+ "description": "Bad named entity: complexes without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&complexes"
+ ]
+ ]
+ },
+ {
+ "input": "ℂ",
+ "description": "Named entity: complexes; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2102"
+ ]
+ ]
+ },
+ {
+ "input": "&cong",
+ "description": "Bad named entity: cong without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cong"
+ ]
+ ]
+ },
+ {
+ "input": "≅",
+ "description": "Named entity: cong; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2245"
+ ]
+ ]
+ },
+ {
+ "input": "&congdot",
+ "description": "Bad named entity: congdot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&congdot"
+ ]
+ ]
+ },
+ {
+ "input": "⩭",
+ "description": "Named entity: congdot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a6d"
+ ]
+ ]
+ },
+ {
+ "input": "&conint",
+ "description": "Bad named entity: conint without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&conint"
+ ]
+ ]
+ },
+ {
+ "input": "∮",
+ "description": "Named entity: conint; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u222e"
+ ]
+ ]
+ },
+ {
+ "input": "&copf",
+ "description": "Bad named entity: copf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&copf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕔",
+ "description": "Named entity: copf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd54"
+ ]
+ ]
+ },
+ {
+ "input": "&coprod",
+ "description": "Bad named entity: coprod without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&coprod"
+ ]
+ ]
+ },
+ {
+ "input": "∐",
+ "description": "Named entity: coprod; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2210"
+ ]
+ ]
+ },
+ {
+ "input": "©",
+ "description": "Named entity: copy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00a9"
+ ]
+ ]
+ },
+ {
+ "input": "©",
+ "description": "Named entity: copy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00a9"
+ ]
+ ]
+ },
+ {
+ "input": "℗",
+ "description": "Named entity: copysr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2117"
+ ]
+ ]
+ },
+ {
+ "input": "&crarr",
+ "description": "Bad named entity: crarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&crarr"
+ ]
+ ]
+ },
+ {
+ "input": "↵",
+ "description": "Named entity: crarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21b5"
+ ]
+ ]
+ },
+ {
+ "input": "&cross",
+ "description": "Bad named entity: cross without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cross"
+ ]
+ ]
+ },
+ {
+ "input": "✗",
+ "description": "Named entity: cross; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2717"
+ ]
+ ]
+ },
+ {
+ "input": "&cscr",
+ "description": "Bad named entity: cscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝒸",
+ "description": "Named entity: cscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udcb8"
+ ]
+ ]
+ },
+ {
+ "input": "&csub",
+ "description": "Bad named entity: csub without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&csub"
+ ]
+ ]
+ },
+ {
+ "input": "⫏",
+ "description": "Named entity: csub; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2acf"
+ ]
+ ]
+ },
+ {
+ "input": "&csube",
+ "description": "Bad named entity: csube without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&csube"
+ ]
+ ]
+ },
+ {
+ "input": "⫑",
+ "description": "Named entity: csube; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ad1"
+ ]
+ ]
+ },
+ {
+ "input": "&csup",
+ "description": "Bad named entity: csup without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&csup"
+ ]
+ ]
+ },
+ {
+ "input": "⫐",
+ "description": "Named entity: csup; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ad0"
+ ]
+ ]
+ },
+ {
+ "input": "&csupe",
+ "description": "Bad named entity: csupe without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&csupe"
+ ]
+ ]
+ },
+ {
+ "input": "⫒",
+ "description": "Named entity: csupe; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ad2"
+ ]
+ ]
+ },
+ {
+ "input": "&ctdot",
+ "description": "Bad named entity: ctdot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ctdot"
+ ]
+ ]
+ },
+ {
+ "input": "⋯",
+ "description": "Named entity: ctdot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22ef"
+ ]
+ ]
+ },
+ {
+ "input": "&cudarrl",
+ "description": "Bad named entity: cudarrl without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cudarrl"
+ ]
+ ]
+ },
+ {
+ "input": "⤸",
+ "description": "Named entity: cudarrl; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2938"
+ ]
+ ]
+ },
+ {
+ "input": "&cudarrr",
+ "description": "Bad named entity: cudarrr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cudarrr"
+ ]
+ ]
+ },
+ {
+ "input": "⤵",
+ "description": "Named entity: cudarrr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2935"
+ ]
+ ]
+ },
+ {
+ "input": "&cuepr",
+ "description": "Bad named entity: cuepr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cuepr"
+ ]
+ ]
+ },
+ {
+ "input": "⋞",
+ "description": "Named entity: cuepr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22de"
+ ]
+ ]
+ },
+ {
+ "input": "&cuesc",
+ "description": "Bad named entity: cuesc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cuesc"
+ ]
+ ]
+ },
+ {
+ "input": "⋟",
+ "description": "Named entity: cuesc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22df"
+ ]
+ ]
+ },
+ {
+ "input": "&cularr",
+ "description": "Bad named entity: cularr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cularr"
+ ]
+ ]
+ },
+ {
+ "input": "↶",
+ "description": "Named entity: cularr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21b6"
+ ]
+ ]
+ },
+ {
+ "input": "&cularrp",
+ "description": "Bad named entity: cularrp without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cularrp"
+ ]
+ ]
+ },
+ {
+ "input": "⤽",
+ "description": "Named entity: cularrp; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u293d"
+ ]
+ ]
+ },
+ {
+ "input": "&cup",
+ "description": "Bad named entity: cup without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cup"
+ ]
+ ]
+ },
+ {
+ "input": "∪",
+ "description": "Named entity: cup; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u222a"
+ ]
+ ]
+ },
+ {
+ "input": "&cupbrcap",
+ "description": "Bad named entity: cupbrcap without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cupbrcap"
+ ]
+ ]
+ },
+ {
+ "input": "⩈",
+ "description": "Named entity: cupbrcap; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a48"
+ ]
+ ]
+ },
+ {
+ "input": "&cupcap",
+ "description": "Bad named entity: cupcap without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cupcap"
+ ]
+ ]
+ },
+ {
+ "input": "⩆",
+ "description": "Named entity: cupcap; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a46"
+ ]
+ ]
+ },
+ {
+ "input": "&cupcup",
+ "description": "Bad named entity: cupcup without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cupcup"
+ ]
+ ]
+ },
+ {
+ "input": "⩊",
+ "description": "Named entity: cupcup; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a4a"
+ ]
+ ]
+ },
+ {
+ "input": "&cupdot",
+ "description": "Bad named entity: cupdot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cupdot"
+ ]
+ ]
+ },
+ {
+ "input": "⊍",
+ "description": "Named entity: cupdot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u228d"
+ ]
+ ]
+ },
+ {
+ "input": "&cupor",
+ "description": "Bad named entity: cupor without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cupor"
+ ]
+ ]
+ },
+ {
+ "input": "⩅",
+ "description": "Named entity: cupor; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a45"
+ ]
+ ]
+ },
+ {
+ "input": "&cups",
+ "description": "Bad named entity: cups without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cups"
+ ]
+ ]
+ },
+ {
+ "input": "∪︀",
+ "description": "Named entity: cups; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u222a\ufe00"
+ ]
+ ]
+ },
+ {
+ "input": "&curarr",
+ "description": "Bad named entity: curarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&curarr"
+ ]
+ ]
+ },
+ {
+ "input": "↷",
+ "description": "Named entity: curarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21b7"
+ ]
+ ]
+ },
+ {
+ "input": "&curarrm",
+ "description": "Bad named entity: curarrm without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&curarrm"
+ ]
+ ]
+ },
+ {
+ "input": "⤼",
+ "description": "Named entity: curarrm; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u293c"
+ ]
+ ]
+ },
+ {
+ "input": "&curlyeqprec",
+ "description": "Bad named entity: curlyeqprec without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&curlyeqprec"
+ ]
+ ]
+ },
+ {
+ "input": "⋞",
+ "description": "Named entity: curlyeqprec; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22de"
+ ]
+ ]
+ },
+ {
+ "input": "&curlyeqsucc",
+ "description": "Bad named entity: curlyeqsucc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&curlyeqsucc"
+ ]
+ ]
+ },
+ {
+ "input": "⋟",
+ "description": "Named entity: curlyeqsucc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22df"
+ ]
+ ]
+ },
+ {
+ "input": "&curlyvee",
+ "description": "Bad named entity: curlyvee without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&curlyvee"
+ ]
+ ]
+ },
+ {
+ "input": "⋎",
+ "description": "Named entity: curlyvee; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22ce"
+ ]
+ ]
+ },
+ {
+ "input": "&curlywedge",
+ "description": "Bad named entity: curlywedge without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&curlywedge"
+ ]
+ ]
+ },
+ {
+ "input": "⋏",
+ "description": "Named entity: curlywedge; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22cf"
+ ]
+ ]
+ },
+ {
+ "input": "¤",
+ "description": "Named entity: curren without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00a4"
+ ]
+ ]
+ },
+ {
+ "input": "¤",
+ "description": "Named entity: curren; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00a4"
+ ]
+ ]
+ },
+ {
+ "input": "&curvearrowleft",
+ "description": "Bad named entity: curvearrowleft without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&curvearrowleft"
+ ]
+ ]
+ },
+ {
+ "input": "↶",
+ "description": "Named entity: curvearrowleft; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21b6"
+ ]
+ ]
+ },
+ {
+ "input": "&curvearrowright",
+ "description": "Bad named entity: curvearrowright without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&curvearrowright"
+ ]
+ ]
+ },
+ {
+ "input": "↷",
+ "description": "Named entity: curvearrowright; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21b7"
+ ]
+ ]
+ },
+ {
+ "input": "&cuvee",
+ "description": "Bad named entity: cuvee without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cuvee"
+ ]
+ ]
+ },
+ {
+ "input": "⋎",
+ "description": "Named entity: cuvee; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22ce"
+ ]
+ ]
+ },
+ {
+ "input": "&cuwed",
+ "description": "Bad named entity: cuwed without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cuwed"
+ ]
+ ]
+ },
+ {
+ "input": "⋏",
+ "description": "Named entity: cuwed; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22cf"
+ ]
+ ]
+ },
+ {
+ "input": "&cwconint",
+ "description": "Bad named entity: cwconint without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cwconint"
+ ]
+ ]
+ },
+ {
+ "input": "∲",
+ "description": "Named entity: cwconint; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2232"
+ ]
+ ]
+ },
+ {
+ "input": "&cwint",
+ "description": "Bad named entity: cwint without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cwint"
+ ]
+ ]
+ },
+ {
+ "input": "∱",
+ "description": "Named entity: cwint; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2231"
+ ]
+ ]
+ },
+ {
+ "input": "&cylcty",
+ "description": "Bad named entity: cylcty without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&cylcty"
+ ]
+ ]
+ },
+ {
+ "input": "⌭",
+ "description": "Named entity: cylcty; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u232d"
+ ]
+ ]
+ },
+ {
+ "input": "&dArr",
+ "description": "Bad named entity: dArr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dArr"
+ ]
+ ]
+ },
+ {
+ "input": "⇓",
+ "description": "Named entity: dArr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21d3"
+ ]
+ ]
+ },
+ {
+ "input": "&dHar",
+ "description": "Bad named entity: dHar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dHar"
+ ]
+ ]
+ },
+ {
+ "input": "⥥",
+ "description": "Named entity: dHar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2965"
+ ]
+ ]
+ },
+ {
+ "input": "&dagger",
+ "description": "Bad named entity: dagger without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dagger"
+ ]
+ ]
+ },
+ {
+ "input": "†",
+ "description": "Named entity: dagger; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2020"
+ ]
+ ]
+ },
+ {
+ "input": "&daleth",
+ "description": "Bad named entity: daleth without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&daleth"
+ ]
+ ]
+ },
+ {
+ "input": "ℸ",
+ "description": "Named entity: daleth; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2138"
+ ]
+ ]
+ },
+ {
+ "input": "&darr",
+ "description": "Bad named entity: darr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&darr"
+ ]
+ ]
+ },
+ {
+ "input": "↓",
+ "description": "Named entity: darr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2193"
+ ]
+ ]
+ },
+ {
+ "input": "&dash",
+ "description": "Bad named entity: dash without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dash"
+ ]
+ ]
+ },
+ {
+ "input": "‐",
+ "description": "Named entity: dash; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2010"
+ ]
+ ]
+ },
+ {
+ "input": "&dashv",
+ "description": "Bad named entity: dashv without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dashv"
+ ]
+ ]
+ },
+ {
+ "input": "⊣",
+ "description": "Named entity: dashv; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22a3"
+ ]
+ ]
+ },
+ {
+ "input": "&dbkarow",
+ "description": "Bad named entity: dbkarow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dbkarow"
+ ]
+ ]
+ },
+ {
+ "input": "⤏",
+ "description": "Named entity: dbkarow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u290f"
+ ]
+ ]
+ },
+ {
+ "input": "&dblac",
+ "description": "Bad named entity: dblac without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dblac"
+ ]
+ ]
+ },
+ {
+ "input": "˝",
+ "description": "Named entity: dblac; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u02dd"
+ ]
+ ]
+ },
+ {
+ "input": "&dcaron",
+ "description": "Bad named entity: dcaron without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dcaron"
+ ]
+ ]
+ },
+ {
+ "input": "ď",
+ "description": "Named entity: dcaron; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u010f"
+ ]
+ ]
+ },
+ {
+ "input": "&dcy",
+ "description": "Bad named entity: dcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dcy"
+ ]
+ ]
+ },
+ {
+ "input": "д",
+ "description": "Named entity: dcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0434"
+ ]
+ ]
+ },
+ {
+ "input": "&dd",
+ "description": "Bad named entity: dd without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dd"
+ ]
+ ]
+ },
+ {
+ "input": "ⅆ",
+ "description": "Named entity: dd; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2146"
+ ]
+ ]
+ },
+ {
+ "input": "&ddagger",
+ "description": "Bad named entity: ddagger without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ddagger"
+ ]
+ ]
+ },
+ {
+ "input": "‡",
+ "description": "Named entity: ddagger; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2021"
+ ]
+ ]
+ },
+ {
+ "input": "&ddarr",
+ "description": "Bad named entity: ddarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ddarr"
+ ]
+ ]
+ },
+ {
+ "input": "⇊",
+ "description": "Named entity: ddarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21ca"
+ ]
+ ]
+ },
+ {
+ "input": "&ddotseq",
+ "description": "Bad named entity: ddotseq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ddotseq"
+ ]
+ ]
+ },
+ {
+ "input": "⩷",
+ "description": "Named entity: ddotseq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a77"
+ ]
+ ]
+ },
+ {
+ "input": "°",
+ "description": "Named entity: deg without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00b0"
+ ]
+ ]
+ },
+ {
+ "input": "°",
+ "description": "Named entity: deg; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00b0"
+ ]
+ ]
+ },
+ {
+ "input": "&delta",
+ "description": "Bad named entity: delta without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&delta"
+ ]
+ ]
+ },
+ {
+ "input": "δ",
+ "description": "Named entity: delta; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03b4"
+ ]
+ ]
+ },
+ {
+ "input": "&demptyv",
+ "description": "Bad named entity: demptyv without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&demptyv"
+ ]
+ ]
+ },
+ {
+ "input": "⦱",
+ "description": "Named entity: demptyv; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29b1"
+ ]
+ ]
+ },
+ {
+ "input": "&dfisht",
+ "description": "Bad named entity: dfisht without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dfisht"
+ ]
+ ]
+ },
+ {
+ "input": "⥿",
+ "description": "Named entity: dfisht; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u297f"
+ ]
+ ]
+ },
+ {
+ "input": "&dfr",
+ "description": "Bad named entity: dfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔡",
+ "description": "Named entity: dfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd21"
+ ]
+ ]
+ },
+ {
+ "input": "&dharl",
+ "description": "Bad named entity: dharl without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dharl"
+ ]
+ ]
+ },
+ {
+ "input": "⇃",
+ "description": "Named entity: dharl; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21c3"
+ ]
+ ]
+ },
+ {
+ "input": "&dharr",
+ "description": "Bad named entity: dharr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dharr"
+ ]
+ ]
+ },
+ {
+ "input": "⇂",
+ "description": "Named entity: dharr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21c2"
+ ]
+ ]
+ },
+ {
+ "input": "&diam",
+ "description": "Bad named entity: diam without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&diam"
+ ]
+ ]
+ },
+ {
+ "input": "⋄",
+ "description": "Named entity: diam; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22c4"
+ ]
+ ]
+ },
+ {
+ "input": "&diamond",
+ "description": "Bad named entity: diamond without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&diamond"
+ ]
+ ]
+ },
+ {
+ "input": "⋄",
+ "description": "Named entity: diamond; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22c4"
+ ]
+ ]
+ },
+ {
+ "input": "&diamondsuit",
+ "description": "Bad named entity: diamondsuit without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&diamondsuit"
+ ]
+ ]
+ },
+ {
+ "input": "♦",
+ "description": "Named entity: diamondsuit; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2666"
+ ]
+ ]
+ },
+ {
+ "input": "&diams",
+ "description": "Bad named entity: diams without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&diams"
+ ]
+ ]
+ },
+ {
+ "input": "♦",
+ "description": "Named entity: diams; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2666"
+ ]
+ ]
+ },
+ {
+ "input": "&die",
+ "description": "Bad named entity: die without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&die"
+ ]
+ ]
+ },
+ {
+ "input": "¨",
+ "description": "Named entity: die; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00a8"
+ ]
+ ]
+ },
+ {
+ "input": "&digamma",
+ "description": "Bad named entity: digamma without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&digamma"
+ ]
+ ]
+ },
+ {
+ "input": "ϝ",
+ "description": "Named entity: digamma; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03dd"
+ ]
+ ]
+ },
+ {
+ "input": "&disin",
+ "description": "Bad named entity: disin without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&disin"
+ ]
+ ]
+ },
+ {
+ "input": "⋲",
+ "description": "Named entity: disin; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22f2"
+ ]
+ ]
+ },
+ {
+ "input": "&div",
+ "description": "Bad named entity: div without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&div"
+ ]
+ ]
+ },
+ {
+ "input": "÷",
+ "description": "Named entity: div; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00f7"
+ ]
+ ]
+ },
+ {
+ "input": "÷",
+ "description": "Named entity: divide without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00f7"
+ ]
+ ]
+ },
+ {
+ "input": "÷",
+ "description": "Named entity: divide; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00f7"
+ ]
+ ]
+ },
+ {
+ "input": "⋇",
+ "description": "Named entity: divideontimes; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22c7"
+ ]
+ ]
+ },
+ {
+ "input": "&divonx",
+ "description": "Bad named entity: divonx without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&divonx"
+ ]
+ ]
+ },
+ {
+ "input": "⋇",
+ "description": "Named entity: divonx; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22c7"
+ ]
+ ]
+ },
+ {
+ "input": "&djcy",
+ "description": "Bad named entity: djcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&djcy"
+ ]
+ ]
+ },
+ {
+ "input": "ђ",
+ "description": "Named entity: djcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0452"
+ ]
+ ]
+ },
+ {
+ "input": "&dlcorn",
+ "description": "Bad named entity: dlcorn without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dlcorn"
+ ]
+ ]
+ },
+ {
+ "input": "⌞",
+ "description": "Named entity: dlcorn; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u231e"
+ ]
+ ]
+ },
+ {
+ "input": "&dlcrop",
+ "description": "Bad named entity: dlcrop without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dlcrop"
+ ]
+ ]
+ },
+ {
+ "input": "⌍",
+ "description": "Named entity: dlcrop; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u230d"
+ ]
+ ]
+ },
+ {
+ "input": "&dollar",
+ "description": "Bad named entity: dollar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dollar"
+ ]
+ ]
+ },
+ {
+ "input": "$",
+ "description": "Named entity: dollar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "$"
+ ]
+ ]
+ },
+ {
+ "input": "&dopf",
+ "description": "Bad named entity: dopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕕",
+ "description": "Named entity: dopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd55"
+ ]
+ ]
+ },
+ {
+ "input": "&dot",
+ "description": "Bad named entity: dot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dot"
+ ]
+ ]
+ },
+ {
+ "input": "˙",
+ "description": "Named entity: dot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u02d9"
+ ]
+ ]
+ },
+ {
+ "input": "&doteq",
+ "description": "Bad named entity: doteq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&doteq"
+ ]
+ ]
+ },
+ {
+ "input": "≐",
+ "description": "Named entity: doteq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2250"
+ ]
+ ]
+ },
+ {
+ "input": "&doteqdot",
+ "description": "Bad named entity: doteqdot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&doteqdot"
+ ]
+ ]
+ },
+ {
+ "input": "≑",
+ "description": "Named entity: doteqdot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2251"
+ ]
+ ]
+ },
+ {
+ "input": "&dotminus",
+ "description": "Bad named entity: dotminus without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dotminus"
+ ]
+ ]
+ },
+ {
+ "input": "∸",
+ "description": "Named entity: dotminus; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2238"
+ ]
+ ]
+ },
+ {
+ "input": "&dotplus",
+ "description": "Bad named entity: dotplus without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dotplus"
+ ]
+ ]
+ },
+ {
+ "input": "∔",
+ "description": "Named entity: dotplus; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2214"
+ ]
+ ]
+ },
+ {
+ "input": "&dotsquare",
+ "description": "Bad named entity: dotsquare without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dotsquare"
+ ]
+ ]
+ },
+ {
+ "input": "⊡",
+ "description": "Named entity: dotsquare; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22a1"
+ ]
+ ]
+ },
+ {
+ "input": "&doublebarwedge",
+ "description": "Bad named entity: doublebarwedge without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&doublebarwedge"
+ ]
+ ]
+ },
+ {
+ "input": "⌆",
+ "description": "Named entity: doublebarwedge; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2306"
+ ]
+ ]
+ },
+ {
+ "input": "&downarrow",
+ "description": "Bad named entity: downarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&downarrow"
+ ]
+ ]
+ },
+ {
+ "input": "↓",
+ "description": "Named entity: downarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2193"
+ ]
+ ]
+ },
+ {
+ "input": "&downdownarrows",
+ "description": "Bad named entity: downdownarrows without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&downdownarrows"
+ ]
+ ]
+ },
+ {
+ "input": "⇊",
+ "description": "Named entity: downdownarrows; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21ca"
+ ]
+ ]
+ },
+ {
+ "input": "&downharpoonleft",
+ "description": "Bad named entity: downharpoonleft without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&downharpoonleft"
+ ]
+ ]
+ },
+ {
+ "input": "⇃",
+ "description": "Named entity: downharpoonleft; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21c3"
+ ]
+ ]
+ },
+ {
+ "input": "&downharpoonright",
+ "description": "Bad named entity: downharpoonright without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&downharpoonright"
+ ]
+ ]
+ },
+ {
+ "input": "⇂",
+ "description": "Named entity: downharpoonright; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21c2"
+ ]
+ ]
+ },
+ {
+ "input": "&drbkarow",
+ "description": "Bad named entity: drbkarow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&drbkarow"
+ ]
+ ]
+ },
+ {
+ "input": "⤐",
+ "description": "Named entity: drbkarow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2910"
+ ]
+ ]
+ },
+ {
+ "input": "&drcorn",
+ "description": "Bad named entity: drcorn without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&drcorn"
+ ]
+ ]
+ },
+ {
+ "input": "⌟",
+ "description": "Named entity: drcorn; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u231f"
+ ]
+ ]
+ },
+ {
+ "input": "&drcrop",
+ "description": "Bad named entity: drcrop without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&drcrop"
+ ]
+ ]
+ },
+ {
+ "input": "⌌",
+ "description": "Named entity: drcrop; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u230c"
+ ]
+ ]
+ },
+ {
+ "input": "&dscr",
+ "description": "Bad named entity: dscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝒹",
+ "description": "Named entity: dscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udcb9"
+ ]
+ ]
+ },
+ {
+ "input": "&dscy",
+ "description": "Bad named entity: dscy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dscy"
+ ]
+ ]
+ },
+ {
+ "input": "ѕ",
+ "description": "Named entity: dscy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0455"
+ ]
+ ]
+ },
+ {
+ "input": "&dsol",
+ "description": "Bad named entity: dsol without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dsol"
+ ]
+ ]
+ },
+ {
+ "input": "⧶",
+ "description": "Named entity: dsol; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29f6"
+ ]
+ ]
+ },
+ {
+ "input": "&dstrok",
+ "description": "Bad named entity: dstrok without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dstrok"
+ ]
+ ]
+ },
+ {
+ "input": "đ",
+ "description": "Named entity: dstrok; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0111"
+ ]
+ ]
+ },
+ {
+ "input": "&dtdot",
+ "description": "Bad named entity: dtdot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dtdot"
+ ]
+ ]
+ },
+ {
+ "input": "⋱",
+ "description": "Named entity: dtdot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22f1"
+ ]
+ ]
+ },
+ {
+ "input": "&dtri",
+ "description": "Bad named entity: dtri without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dtri"
+ ]
+ ]
+ },
+ {
+ "input": "▿",
+ "description": "Named entity: dtri; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25bf"
+ ]
+ ]
+ },
+ {
+ "input": "&dtrif",
+ "description": "Bad named entity: dtrif without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dtrif"
+ ]
+ ]
+ },
+ {
+ "input": "▾",
+ "description": "Named entity: dtrif; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25be"
+ ]
+ ]
+ },
+ {
+ "input": "&duarr",
+ "description": "Bad named entity: duarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&duarr"
+ ]
+ ]
+ },
+ {
+ "input": "⇵",
+ "description": "Named entity: duarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21f5"
+ ]
+ ]
+ },
+ {
+ "input": "&duhar",
+ "description": "Bad named entity: duhar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&duhar"
+ ]
+ ]
+ },
+ {
+ "input": "⥯",
+ "description": "Named entity: duhar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u296f"
+ ]
+ ]
+ },
+ {
+ "input": "&dwangle",
+ "description": "Bad named entity: dwangle without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dwangle"
+ ]
+ ]
+ },
+ {
+ "input": "⦦",
+ "description": "Named entity: dwangle; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29a6"
+ ]
+ ]
+ },
+ {
+ "input": "&dzcy",
+ "description": "Bad named entity: dzcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dzcy"
+ ]
+ ]
+ },
+ {
+ "input": "џ",
+ "description": "Named entity: dzcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u045f"
+ ]
+ ]
+ },
+ {
+ "input": "&dzigrarr",
+ "description": "Bad named entity: dzigrarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&dzigrarr"
+ ]
+ ]
+ },
+ {
+ "input": "⟿",
+ "description": "Named entity: dzigrarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27ff"
+ ]
+ ]
+ },
+ {
+ "input": "&eDDot",
+ "description": "Bad named entity: eDDot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&eDDot"
+ ]
+ ]
+ },
+ {
+ "input": "⩷",
+ "description": "Named entity: eDDot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a77"
+ ]
+ ]
+ },
+ {
+ "input": "&eDot",
+ "description": "Bad named entity: eDot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&eDot"
+ ]
+ ]
+ },
+ {
+ "input": "≑",
+ "description": "Named entity: eDot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2251"
+ ]
+ ]
+ },
+ {
+ "input": "é",
+ "description": "Named entity: eacute without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00e9"
+ ]
+ ]
+ },
+ {
+ "input": "é",
+ "description": "Named entity: eacute; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00e9"
+ ]
+ ]
+ },
+ {
+ "input": "&easter",
+ "description": "Bad named entity: easter without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&easter"
+ ]
+ ]
+ },
+ {
+ "input": "⩮",
+ "description": "Named entity: easter; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a6e"
+ ]
+ ]
+ },
+ {
+ "input": "&ecaron",
+ "description": "Bad named entity: ecaron without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ecaron"
+ ]
+ ]
+ },
+ {
+ "input": "ě",
+ "description": "Named entity: ecaron; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u011b"
+ ]
+ ]
+ },
+ {
+ "input": "&ecir",
+ "description": "Bad named entity: ecir without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ecir"
+ ]
+ ]
+ },
+ {
+ "input": "≖",
+ "description": "Named entity: ecir; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2256"
+ ]
+ ]
+ },
+ {
+ "input": "ê",
+ "description": "Named entity: ecirc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00ea"
+ ]
+ ]
+ },
+ {
+ "input": "ê",
+ "description": "Named entity: ecirc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00ea"
+ ]
+ ]
+ },
+ {
+ "input": "&ecolon",
+ "description": "Bad named entity: ecolon without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ecolon"
+ ]
+ ]
+ },
+ {
+ "input": "≕",
+ "description": "Named entity: ecolon; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2255"
+ ]
+ ]
+ },
+ {
+ "input": "&ecy",
+ "description": "Bad named entity: ecy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ecy"
+ ]
+ ]
+ },
+ {
+ "input": "э",
+ "description": "Named entity: ecy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u044d"
+ ]
+ ]
+ },
+ {
+ "input": "&edot",
+ "description": "Bad named entity: edot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&edot"
+ ]
+ ]
+ },
+ {
+ "input": "ė",
+ "description": "Named entity: edot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0117"
+ ]
+ ]
+ },
+ {
+ "input": "&ee",
+ "description": "Bad named entity: ee without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ee"
+ ]
+ ]
+ },
+ {
+ "input": "ⅇ",
+ "description": "Named entity: ee; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2147"
+ ]
+ ]
+ },
+ {
+ "input": "&efDot",
+ "description": "Bad named entity: efDot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&efDot"
+ ]
+ ]
+ },
+ {
+ "input": "≒",
+ "description": "Named entity: efDot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2252"
+ ]
+ ]
+ },
+ {
+ "input": "&efr",
+ "description": "Bad named entity: efr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&efr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔢",
+ "description": "Named entity: efr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd22"
+ ]
+ ]
+ },
+ {
+ "input": "&eg",
+ "description": "Bad named entity: eg without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&eg"
+ ]
+ ]
+ },
+ {
+ "input": "⪚",
+ "description": "Named entity: eg; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a9a"
+ ]
+ ]
+ },
+ {
+ "input": "è",
+ "description": "Named entity: egrave without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00e8"
+ ]
+ ]
+ },
+ {
+ "input": "è",
+ "description": "Named entity: egrave; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00e8"
+ ]
+ ]
+ },
+ {
+ "input": "&egs",
+ "description": "Bad named entity: egs without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&egs"
+ ]
+ ]
+ },
+ {
+ "input": "⪖",
+ "description": "Named entity: egs; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a96"
+ ]
+ ]
+ },
+ {
+ "input": "&egsdot",
+ "description": "Bad named entity: egsdot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&egsdot"
+ ]
+ ]
+ },
+ {
+ "input": "⪘",
+ "description": "Named entity: egsdot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a98"
+ ]
+ ]
+ },
+ {
+ "input": "&el",
+ "description": "Bad named entity: el without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&el"
+ ]
+ ]
+ },
+ {
+ "input": "⪙",
+ "description": "Named entity: el; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a99"
+ ]
+ ]
+ },
+ {
+ "input": "&elinters",
+ "description": "Bad named entity: elinters without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&elinters"
+ ]
+ ]
+ },
+ {
+ "input": "⏧",
+ "description": "Named entity: elinters; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u23e7"
+ ]
+ ]
+ },
+ {
+ "input": "&ell",
+ "description": "Bad named entity: ell without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ell"
+ ]
+ ]
+ },
+ {
+ "input": "ℓ",
+ "description": "Named entity: ell; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2113"
+ ]
+ ]
+ },
+ {
+ "input": "&els",
+ "description": "Bad named entity: els without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&els"
+ ]
+ ]
+ },
+ {
+ "input": "⪕",
+ "description": "Named entity: els; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a95"
+ ]
+ ]
+ },
+ {
+ "input": "&elsdot",
+ "description": "Bad named entity: elsdot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&elsdot"
+ ]
+ ]
+ },
+ {
+ "input": "⪗",
+ "description": "Named entity: elsdot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a97"
+ ]
+ ]
+ },
+ {
+ "input": "&emacr",
+ "description": "Bad named entity: emacr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&emacr"
+ ]
+ ]
+ },
+ {
+ "input": "ē",
+ "description": "Named entity: emacr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0113"
+ ]
+ ]
+ },
+ {
+ "input": "&empty",
+ "description": "Bad named entity: empty without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&empty"
+ ]
+ ]
+ },
+ {
+ "input": "∅",
+ "description": "Named entity: empty; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2205"
+ ]
+ ]
+ },
+ {
+ "input": "&emptyset",
+ "description": "Bad named entity: emptyset without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&emptyset"
+ ]
+ ]
+ },
+ {
+ "input": "∅",
+ "description": "Named entity: emptyset; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2205"
+ ]
+ ]
+ },
+ {
+ "input": "&emptyv",
+ "description": "Bad named entity: emptyv without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&emptyv"
+ ]
+ ]
+ },
+ {
+ "input": "∅",
+ "description": "Named entity: emptyv; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2205"
+ ]
+ ]
+ },
+ {
+ "input": "&emsp",
+ "description": "Bad named entity: emsp without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&emsp"
+ ]
+ ]
+ },
+ {
+ "input": "&emsp13",
+ "description": "Bad named entity: emsp13 without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&emsp13"
+ ]
+ ]
+ },
+ {
+ "input": " ",
+ "description": "Named entity: emsp13; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2004"
+ ]
+ ]
+ },
+ {
+ "input": "&emsp14",
+ "description": "Bad named entity: emsp14 without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&emsp14"
+ ]
+ ]
+ },
+ {
+ "input": " ",
+ "description": "Named entity: emsp14; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2005"
+ ]
+ ]
+ },
+ {
+ "input": " ",
+ "description": "Named entity: emsp; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2003"
+ ]
+ ]
+ },
+ {
+ "input": "&eng",
+ "description": "Bad named entity: eng without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&eng"
+ ]
+ ]
+ },
+ {
+ "input": "ŋ",
+ "description": "Named entity: eng; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u014b"
+ ]
+ ]
+ },
+ {
+ "input": "&ensp",
+ "description": "Bad named entity: ensp without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ensp"
+ ]
+ ]
+ },
+ {
+ "input": " ",
+ "description": "Named entity: ensp; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2002"
+ ]
+ ]
+ },
+ {
+ "input": "&eogon",
+ "description": "Bad named entity: eogon without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&eogon"
+ ]
+ ]
+ },
+ {
+ "input": "ę",
+ "description": "Named entity: eogon; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0119"
+ ]
+ ]
+ },
+ {
+ "input": "&eopf",
+ "description": "Bad named entity: eopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&eopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕖",
+ "description": "Named entity: eopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd56"
+ ]
+ ]
+ },
+ {
+ "input": "&epar",
+ "description": "Bad named entity: epar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&epar"
+ ]
+ ]
+ },
+ {
+ "input": "⋕",
+ "description": "Named entity: epar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22d5"
+ ]
+ ]
+ },
+ {
+ "input": "&eparsl",
+ "description": "Bad named entity: eparsl without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&eparsl"
+ ]
+ ]
+ },
+ {
+ "input": "⧣",
+ "description": "Named entity: eparsl; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29e3"
+ ]
+ ]
+ },
+ {
+ "input": "&eplus",
+ "description": "Bad named entity: eplus without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&eplus"
+ ]
+ ]
+ },
+ {
+ "input": "⩱",
+ "description": "Named entity: eplus; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a71"
+ ]
+ ]
+ },
+ {
+ "input": "&epsi",
+ "description": "Bad named entity: epsi without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&epsi"
+ ]
+ ]
+ },
+ {
+ "input": "ε",
+ "description": "Named entity: epsi; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03b5"
+ ]
+ ]
+ },
+ {
+ "input": "&epsilon",
+ "description": "Bad named entity: epsilon without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&epsilon"
+ ]
+ ]
+ },
+ {
+ "input": "ε",
+ "description": "Named entity: epsilon; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03b5"
+ ]
+ ]
+ },
+ {
+ "input": "&epsiv",
+ "description": "Bad named entity: epsiv without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&epsiv"
+ ]
+ ]
+ },
+ {
+ "input": "ϵ",
+ "description": "Named entity: epsiv; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03f5"
+ ]
+ ]
+ },
+ {
+ "input": "&eqcirc",
+ "description": "Bad named entity: eqcirc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&eqcirc"
+ ]
+ ]
+ },
+ {
+ "input": "≖",
+ "description": "Named entity: eqcirc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2256"
+ ]
+ ]
+ },
+ {
+ "input": "&eqcolon",
+ "description": "Bad named entity: eqcolon without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&eqcolon"
+ ]
+ ]
+ },
+ {
+ "input": "≕",
+ "description": "Named entity: eqcolon; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2255"
+ ]
+ ]
+ },
+ {
+ "input": "&eqsim",
+ "description": "Bad named entity: eqsim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&eqsim"
+ ]
+ ]
+ },
+ {
+ "input": "≂",
+ "description": "Named entity: eqsim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2242"
+ ]
+ ]
+ },
+ {
+ "input": "&eqslantgtr",
+ "description": "Bad named entity: eqslantgtr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&eqslantgtr"
+ ]
+ ]
+ },
+ {
+ "input": "⪖",
+ "description": "Named entity: eqslantgtr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a96"
+ ]
+ ]
+ },
+ {
+ "input": "&eqslantless",
+ "description": "Bad named entity: eqslantless without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&eqslantless"
+ ]
+ ]
+ },
+ {
+ "input": "⪕",
+ "description": "Named entity: eqslantless; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a95"
+ ]
+ ]
+ },
+ {
+ "input": "&equals",
+ "description": "Bad named entity: equals without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&equals"
+ ]
+ ]
+ },
+ {
+ "input": "=",
+ "description": "Named entity: equals; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "="
+ ]
+ ]
+ },
+ {
+ "input": "&equest",
+ "description": "Bad named entity: equest without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&equest"
+ ]
+ ]
+ },
+ {
+ "input": "≟",
+ "description": "Named entity: equest; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u225f"
+ ]
+ ]
+ },
+ {
+ "input": "&equiv",
+ "description": "Bad named entity: equiv without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&equiv"
+ ]
+ ]
+ },
+ {
+ "input": "≡",
+ "description": "Named entity: equiv; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2261"
+ ]
+ ]
+ },
+ {
+ "input": "&equivDD",
+ "description": "Bad named entity: equivDD without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&equivDD"
+ ]
+ ]
+ },
+ {
+ "input": "⩸",
+ "description": "Named entity: equivDD; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a78"
+ ]
+ ]
+ },
+ {
+ "input": "&eqvparsl",
+ "description": "Bad named entity: eqvparsl without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&eqvparsl"
+ ]
+ ]
+ },
+ {
+ "input": "⧥",
+ "description": "Named entity: eqvparsl; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29e5"
+ ]
+ ]
+ },
+ {
+ "input": "&erDot",
+ "description": "Bad named entity: erDot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&erDot"
+ ]
+ ]
+ },
+ {
+ "input": "≓",
+ "description": "Named entity: erDot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2253"
+ ]
+ ]
+ },
+ {
+ "input": "&erarr",
+ "description": "Bad named entity: erarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&erarr"
+ ]
+ ]
+ },
+ {
+ "input": "⥱",
+ "description": "Named entity: erarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2971"
+ ]
+ ]
+ },
+ {
+ "input": "&escr",
+ "description": "Bad named entity: escr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&escr"
+ ]
+ ]
+ },
+ {
+ "input": "ℯ",
+ "description": "Named entity: escr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u212f"
+ ]
+ ]
+ },
+ {
+ "input": "&esdot",
+ "description": "Bad named entity: esdot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&esdot"
+ ]
+ ]
+ },
+ {
+ "input": "≐",
+ "description": "Named entity: esdot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2250"
+ ]
+ ]
+ },
+ {
+ "input": "&esim",
+ "description": "Bad named entity: esim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&esim"
+ ]
+ ]
+ },
+ {
+ "input": "≂",
+ "description": "Named entity: esim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2242"
+ ]
+ ]
+ },
+ {
+ "input": "&eta",
+ "description": "Bad named entity: eta without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&eta"
+ ]
+ ]
+ },
+ {
+ "input": "η",
+ "description": "Named entity: eta; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03b7"
+ ]
+ ]
+ },
+ {
+ "input": "ð",
+ "description": "Named entity: eth without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00f0"
+ ]
+ ]
+ },
+ {
+ "input": "ð",
+ "description": "Named entity: eth; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00f0"
+ ]
+ ]
+ },
+ {
+ "input": "ë",
+ "description": "Named entity: euml without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00eb"
+ ]
+ ]
+ },
+ {
+ "input": "ë",
+ "description": "Named entity: euml; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00eb"
+ ]
+ ]
+ },
+ {
+ "input": "&euro",
+ "description": "Bad named entity: euro without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&euro"
+ ]
+ ]
+ },
+ {
+ "input": "€",
+ "description": "Named entity: euro; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u20ac"
+ ]
+ ]
+ },
+ {
+ "input": "&excl",
+ "description": "Bad named entity: excl without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&excl"
+ ]
+ ]
+ },
+ {
+ "input": "!",
+ "description": "Named entity: excl; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "!"
+ ]
+ ]
+ },
+ {
+ "input": "&exist",
+ "description": "Bad named entity: exist without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&exist"
+ ]
+ ]
+ },
+ {
+ "input": "∃",
+ "description": "Named entity: exist; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2203"
+ ]
+ ]
+ },
+ {
+ "input": "&expectation",
+ "description": "Bad named entity: expectation without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&expectation"
+ ]
+ ]
+ },
+ {
+ "input": "ℰ",
+ "description": "Named entity: expectation; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2130"
+ ]
+ ]
+ },
+ {
+ "input": "&exponentiale",
+ "description": "Bad named entity: exponentiale without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&exponentiale"
+ ]
+ ]
+ },
+ {
+ "input": "ⅇ",
+ "description": "Named entity: exponentiale; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2147"
+ ]
+ ]
+ },
+ {
+ "input": "&fallingdotseq",
+ "description": "Bad named entity: fallingdotseq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&fallingdotseq"
+ ]
+ ]
+ },
+ {
+ "input": "≒",
+ "description": "Named entity: fallingdotseq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2252"
+ ]
+ ]
+ },
+ {
+ "input": "&fcy",
+ "description": "Bad named entity: fcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&fcy"
+ ]
+ ]
+ },
+ {
+ "input": "ф",
+ "description": "Named entity: fcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0444"
+ ]
+ ]
+ },
+ {
+ "input": "&female",
+ "description": "Bad named entity: female without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&female"
+ ]
+ ]
+ },
+ {
+ "input": "♀",
+ "description": "Named entity: female; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2640"
+ ]
+ ]
+ },
+ {
+ "input": "&ffilig",
+ "description": "Bad named entity: ffilig without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ffilig"
+ ]
+ ]
+ },
+ {
+ "input": "ffi",
+ "description": "Named entity: ffilig; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ufb03"
+ ]
+ ]
+ },
+ {
+ "input": "&fflig",
+ "description": "Bad named entity: fflig without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&fflig"
+ ]
+ ]
+ },
+ {
+ "input": "ff",
+ "description": "Named entity: fflig; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ufb00"
+ ]
+ ]
+ },
+ {
+ "input": "&ffllig",
+ "description": "Bad named entity: ffllig without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ffllig"
+ ]
+ ]
+ },
+ {
+ "input": "ffl",
+ "description": "Named entity: ffllig; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ufb04"
+ ]
+ ]
+ },
+ {
+ "input": "&ffr",
+ "description": "Bad named entity: ffr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ffr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔣",
+ "description": "Named entity: ffr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd23"
+ ]
+ ]
+ },
+ {
+ "input": "&filig",
+ "description": "Bad named entity: filig without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&filig"
+ ]
+ ]
+ },
+ {
+ "input": "fi",
+ "description": "Named entity: filig; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ufb01"
+ ]
+ ]
+ },
+ {
+ "input": "&fjlig",
+ "description": "Bad named entity: fjlig without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&fjlig"
+ ]
+ ]
+ },
+ {
+ "input": "fj",
+ "description": "Named entity: fjlig; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "fj"
+ ]
+ ]
+ },
+ {
+ "input": "&flat",
+ "description": "Bad named entity: flat without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&flat"
+ ]
+ ]
+ },
+ {
+ "input": "♭",
+ "description": "Named entity: flat; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u266d"
+ ]
+ ]
+ },
+ {
+ "input": "&fllig",
+ "description": "Bad named entity: fllig without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&fllig"
+ ]
+ ]
+ },
+ {
+ "input": "fl",
+ "description": "Named entity: fllig; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ufb02"
+ ]
+ ]
+ },
+ {
+ "input": "&fltns",
+ "description": "Bad named entity: fltns without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&fltns"
+ ]
+ ]
+ },
+ {
+ "input": "▱",
+ "description": "Named entity: fltns; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25b1"
+ ]
+ ]
+ },
+ {
+ "input": "&fnof",
+ "description": "Bad named entity: fnof without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&fnof"
+ ]
+ ]
+ },
+ {
+ "input": "ƒ",
+ "description": "Named entity: fnof; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0192"
+ ]
+ ]
+ },
+ {
+ "input": "&fopf",
+ "description": "Bad named entity: fopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&fopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕗",
+ "description": "Named entity: fopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd57"
+ ]
+ ]
+ },
+ {
+ "input": "&forall",
+ "description": "Bad named entity: forall without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&forall"
+ ]
+ ]
+ },
+ {
+ "input": "∀",
+ "description": "Named entity: forall; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2200"
+ ]
+ ]
+ },
+ {
+ "input": "&fork",
+ "description": "Bad named entity: fork without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&fork"
+ ]
+ ]
+ },
+ {
+ "input": "⋔",
+ "description": "Named entity: fork; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22d4"
+ ]
+ ]
+ },
+ {
+ "input": "&forkv",
+ "description": "Bad named entity: forkv without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&forkv"
+ ]
+ ]
+ },
+ {
+ "input": "⫙",
+ "description": "Named entity: forkv; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ad9"
+ ]
+ ]
+ },
+ {
+ "input": "&fpartint",
+ "description": "Bad named entity: fpartint without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&fpartint"
+ ]
+ ]
+ },
+ {
+ "input": "⨍",
+ "description": "Named entity: fpartint; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a0d"
+ ]
+ ]
+ },
+ {
+ "input": "½",
+ "description": "Named entity: frac12 without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00bd"
+ ]
+ ]
+ },
+ {
+ "input": "½",
+ "description": "Named entity: frac12; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00bd"
+ ]
+ ]
+ },
+ {
+ "input": "&frac13",
+ "description": "Bad named entity: frac13 without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&frac13"
+ ]
+ ]
+ },
+ {
+ "input": "⅓",
+ "description": "Named entity: frac13; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2153"
+ ]
+ ]
+ },
+ {
+ "input": "¼",
+ "description": "Named entity: frac14 without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00bc"
+ ]
+ ]
+ },
+ {
+ "input": "¼",
+ "description": "Named entity: frac14; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00bc"
+ ]
+ ]
+ },
+ {
+ "input": "&frac15",
+ "description": "Bad named entity: frac15 without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&frac15"
+ ]
+ ]
+ },
+ {
+ "input": "⅕",
+ "description": "Named entity: frac15; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2155"
+ ]
+ ]
+ },
+ {
+ "input": "&frac16",
+ "description": "Bad named entity: frac16 without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&frac16"
+ ]
+ ]
+ },
+ {
+ "input": "⅙",
+ "description": "Named entity: frac16; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2159"
+ ]
+ ]
+ },
+ {
+ "input": "&frac18",
+ "description": "Bad named entity: frac18 without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&frac18"
+ ]
+ ]
+ },
+ {
+ "input": "⅛",
+ "description": "Named entity: frac18; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u215b"
+ ]
+ ]
+ },
+ {
+ "input": "&frac23",
+ "description": "Bad named entity: frac23 without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&frac23"
+ ]
+ ]
+ },
+ {
+ "input": "⅔",
+ "description": "Named entity: frac23; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2154"
+ ]
+ ]
+ },
+ {
+ "input": "&frac25",
+ "description": "Bad named entity: frac25 without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&frac25"
+ ]
+ ]
+ },
+ {
+ "input": "⅖",
+ "description": "Named entity: frac25; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2156"
+ ]
+ ]
+ },
+ {
+ "input": "¾",
+ "description": "Named entity: frac34 without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00be"
+ ]
+ ]
+ },
+ {
+ "input": "¾",
+ "description": "Named entity: frac34; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00be"
+ ]
+ ]
+ },
+ {
+ "input": "&frac35",
+ "description": "Bad named entity: frac35 without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&frac35"
+ ]
+ ]
+ },
+ {
+ "input": "⅗",
+ "description": "Named entity: frac35; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2157"
+ ]
+ ]
+ },
+ {
+ "input": "&frac38",
+ "description": "Bad named entity: frac38 without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&frac38"
+ ]
+ ]
+ },
+ {
+ "input": "⅜",
+ "description": "Named entity: frac38; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u215c"
+ ]
+ ]
+ },
+ {
+ "input": "&frac45",
+ "description": "Bad named entity: frac45 without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&frac45"
+ ]
+ ]
+ },
+ {
+ "input": "⅘",
+ "description": "Named entity: frac45; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2158"
+ ]
+ ]
+ },
+ {
+ "input": "&frac56",
+ "description": "Bad named entity: frac56 without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&frac56"
+ ]
+ ]
+ },
+ {
+ "input": "⅚",
+ "description": "Named entity: frac56; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u215a"
+ ]
+ ]
+ },
+ {
+ "input": "&frac58",
+ "description": "Bad named entity: frac58 without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&frac58"
+ ]
+ ]
+ },
+ {
+ "input": "⅝",
+ "description": "Named entity: frac58; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u215d"
+ ]
+ ]
+ },
+ {
+ "input": "&frac78",
+ "description": "Bad named entity: frac78 without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&frac78"
+ ]
+ ]
+ },
+ {
+ "input": "⅞",
+ "description": "Named entity: frac78; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u215e"
+ ]
+ ]
+ },
+ {
+ "input": "&frasl",
+ "description": "Bad named entity: frasl without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&frasl"
+ ]
+ ]
+ },
+ {
+ "input": "⁄",
+ "description": "Named entity: frasl; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2044"
+ ]
+ ]
+ },
+ {
+ "input": "&frown",
+ "description": "Bad named entity: frown without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&frown"
+ ]
+ ]
+ },
+ {
+ "input": "⌢",
+ "description": "Named entity: frown; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2322"
+ ]
+ ]
+ },
+ {
+ "input": "&fscr",
+ "description": "Bad named entity: fscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&fscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝒻",
+ "description": "Named entity: fscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udcbb"
+ ]
+ ]
+ },
+ {
+ "input": "&gE",
+ "description": "Bad named entity: gE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gE"
+ ]
+ ]
+ },
+ {
+ "input": "≧",
+ "description": "Named entity: gE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2267"
+ ]
+ ]
+ },
+ {
+ "input": "&gEl",
+ "description": "Bad named entity: gEl without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gEl"
+ ]
+ ]
+ },
+ {
+ "input": "⪌",
+ "description": "Named entity: gEl; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a8c"
+ ]
+ ]
+ },
+ {
+ "input": "&gacute",
+ "description": "Bad named entity: gacute without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gacute"
+ ]
+ ]
+ },
+ {
+ "input": "ǵ",
+ "description": "Named entity: gacute; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u01f5"
+ ]
+ ]
+ },
+ {
+ "input": "&gamma",
+ "description": "Bad named entity: gamma without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gamma"
+ ]
+ ]
+ },
+ {
+ "input": "γ",
+ "description": "Named entity: gamma; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03b3"
+ ]
+ ]
+ },
+ {
+ "input": "&gammad",
+ "description": "Bad named entity: gammad without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gammad"
+ ]
+ ]
+ },
+ {
+ "input": "ϝ",
+ "description": "Named entity: gammad; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03dd"
+ ]
+ ]
+ },
+ {
+ "input": "&gap",
+ "description": "Bad named entity: gap without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gap"
+ ]
+ ]
+ },
+ {
+ "input": "⪆",
+ "description": "Named entity: gap; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a86"
+ ]
+ ]
+ },
+ {
+ "input": "&gbreve",
+ "description": "Bad named entity: gbreve without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gbreve"
+ ]
+ ]
+ },
+ {
+ "input": "ğ",
+ "description": "Named entity: gbreve; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u011f"
+ ]
+ ]
+ },
+ {
+ "input": "&gcirc",
+ "description": "Bad named entity: gcirc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gcirc"
+ ]
+ ]
+ },
+ {
+ "input": "ĝ",
+ "description": "Named entity: gcirc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u011d"
+ ]
+ ]
+ },
+ {
+ "input": "&gcy",
+ "description": "Bad named entity: gcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gcy"
+ ]
+ ]
+ },
+ {
+ "input": "г",
+ "description": "Named entity: gcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0433"
+ ]
+ ]
+ },
+ {
+ "input": "&gdot",
+ "description": "Bad named entity: gdot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gdot"
+ ]
+ ]
+ },
+ {
+ "input": "ġ",
+ "description": "Named entity: gdot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0121"
+ ]
+ ]
+ },
+ {
+ "input": "&ge",
+ "description": "Bad named entity: ge without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ge"
+ ]
+ ]
+ },
+ {
+ "input": "≥",
+ "description": "Named entity: ge; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2265"
+ ]
+ ]
+ },
+ {
+ "input": "&gel",
+ "description": "Bad named entity: gel without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gel"
+ ]
+ ]
+ },
+ {
+ "input": "⋛",
+ "description": "Named entity: gel; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22db"
+ ]
+ ]
+ },
+ {
+ "input": "&geq",
+ "description": "Bad named entity: geq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&geq"
+ ]
+ ]
+ },
+ {
+ "input": "≥",
+ "description": "Named entity: geq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2265"
+ ]
+ ]
+ },
+ {
+ "input": "&geqq",
+ "description": "Bad named entity: geqq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&geqq"
+ ]
+ ]
+ },
+ {
+ "input": "≧",
+ "description": "Named entity: geqq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2267"
+ ]
+ ]
+ },
+ {
+ "input": "&geqslant",
+ "description": "Bad named entity: geqslant without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&geqslant"
+ ]
+ ]
+ },
+ {
+ "input": "⩾",
+ "description": "Named entity: geqslant; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a7e"
+ ]
+ ]
+ },
+ {
+ "input": "&ges",
+ "description": "Bad named entity: ges without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ges"
+ ]
+ ]
+ },
+ {
+ "input": "⩾",
+ "description": "Named entity: ges; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a7e"
+ ]
+ ]
+ },
+ {
+ "input": "&gescc",
+ "description": "Bad named entity: gescc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gescc"
+ ]
+ ]
+ },
+ {
+ "input": "⪩",
+ "description": "Named entity: gescc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2aa9"
+ ]
+ ]
+ },
+ {
+ "input": "&gesdot",
+ "description": "Bad named entity: gesdot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gesdot"
+ ]
+ ]
+ },
+ {
+ "input": "⪀",
+ "description": "Named entity: gesdot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a80"
+ ]
+ ]
+ },
+ {
+ "input": "&gesdoto",
+ "description": "Bad named entity: gesdoto without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gesdoto"
+ ]
+ ]
+ },
+ {
+ "input": "⪂",
+ "description": "Named entity: gesdoto; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a82"
+ ]
+ ]
+ },
+ {
+ "input": "&gesdotol",
+ "description": "Bad named entity: gesdotol without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gesdotol"
+ ]
+ ]
+ },
+ {
+ "input": "⪄",
+ "description": "Named entity: gesdotol; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a84"
+ ]
+ ]
+ },
+ {
+ "input": "&gesl",
+ "description": "Bad named entity: gesl without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gesl"
+ ]
+ ]
+ },
+ {
+ "input": "⋛︀",
+ "description": "Named entity: gesl; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22db\ufe00"
+ ]
+ ]
+ },
+ {
+ "input": "&gesles",
+ "description": "Bad named entity: gesles without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gesles"
+ ]
+ ]
+ },
+ {
+ "input": "⪔",
+ "description": "Named entity: gesles; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a94"
+ ]
+ ]
+ },
+ {
+ "input": "&gfr",
+ "description": "Bad named entity: gfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔤",
+ "description": "Named entity: gfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd24"
+ ]
+ ]
+ },
+ {
+ "input": "&gg",
+ "description": "Bad named entity: gg without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gg"
+ ]
+ ]
+ },
+ {
+ "input": "≫",
+ "description": "Named entity: gg; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u226b"
+ ]
+ ]
+ },
+ {
+ "input": "&ggg",
+ "description": "Bad named entity: ggg without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ggg"
+ ]
+ ]
+ },
+ {
+ "input": "⋙",
+ "description": "Named entity: ggg; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22d9"
+ ]
+ ]
+ },
+ {
+ "input": "&gimel",
+ "description": "Bad named entity: gimel without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gimel"
+ ]
+ ]
+ },
+ {
+ "input": "ℷ",
+ "description": "Named entity: gimel; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2137"
+ ]
+ ]
+ },
+ {
+ "input": "&gjcy",
+ "description": "Bad named entity: gjcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gjcy"
+ ]
+ ]
+ },
+ {
+ "input": "ѓ",
+ "description": "Named entity: gjcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0453"
+ ]
+ ]
+ },
+ {
+ "input": "&gl",
+ "description": "Bad named entity: gl without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gl"
+ ]
+ ]
+ },
+ {
+ "input": "≷",
+ "description": "Named entity: gl; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2277"
+ ]
+ ]
+ },
+ {
+ "input": "&glE",
+ "description": "Bad named entity: glE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&glE"
+ ]
+ ]
+ },
+ {
+ "input": "⪒",
+ "description": "Named entity: glE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a92"
+ ]
+ ]
+ },
+ {
+ "input": "&gla",
+ "description": "Bad named entity: gla without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gla"
+ ]
+ ]
+ },
+ {
+ "input": "⪥",
+ "description": "Named entity: gla; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2aa5"
+ ]
+ ]
+ },
+ {
+ "input": "&glj",
+ "description": "Bad named entity: glj without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&glj"
+ ]
+ ]
+ },
+ {
+ "input": "⪤",
+ "description": "Named entity: glj; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2aa4"
+ ]
+ ]
+ },
+ {
+ "input": "&gnE",
+ "description": "Bad named entity: gnE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gnE"
+ ]
+ ]
+ },
+ {
+ "input": "≩",
+ "description": "Named entity: gnE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2269"
+ ]
+ ]
+ },
+ {
+ "input": "&gnap",
+ "description": "Bad named entity: gnap without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gnap"
+ ]
+ ]
+ },
+ {
+ "input": "⪊",
+ "description": "Named entity: gnap; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a8a"
+ ]
+ ]
+ },
+ {
+ "input": "&gnapprox",
+ "description": "Bad named entity: gnapprox without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gnapprox"
+ ]
+ ]
+ },
+ {
+ "input": "⪊",
+ "description": "Named entity: gnapprox; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a8a"
+ ]
+ ]
+ },
+ {
+ "input": "&gne",
+ "description": "Bad named entity: gne without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gne"
+ ]
+ ]
+ },
+ {
+ "input": "⪈",
+ "description": "Named entity: gne; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a88"
+ ]
+ ]
+ },
+ {
+ "input": "&gneq",
+ "description": "Bad named entity: gneq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gneq"
+ ]
+ ]
+ },
+ {
+ "input": "⪈",
+ "description": "Named entity: gneq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a88"
+ ]
+ ]
+ },
+ {
+ "input": "&gneqq",
+ "description": "Bad named entity: gneqq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gneqq"
+ ]
+ ]
+ },
+ {
+ "input": "≩",
+ "description": "Named entity: gneqq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2269"
+ ]
+ ]
+ },
+ {
+ "input": "&gnsim",
+ "description": "Bad named entity: gnsim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gnsim"
+ ]
+ ]
+ },
+ {
+ "input": "⋧",
+ "description": "Named entity: gnsim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22e7"
+ ]
+ ]
+ },
+ {
+ "input": "&gopf",
+ "description": "Bad named entity: gopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕘",
+ "description": "Named entity: gopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd58"
+ ]
+ ]
+ },
+ {
+ "input": "&grave",
+ "description": "Bad named entity: grave without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&grave"
+ ]
+ ]
+ },
+ {
+ "input": "`",
+ "description": "Named entity: grave; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "`"
+ ]
+ ]
+ },
+ {
+ "input": "&gscr",
+ "description": "Bad named entity: gscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gscr"
+ ]
+ ]
+ },
+ {
+ "input": "ℊ",
+ "description": "Named entity: gscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u210a"
+ ]
+ ]
+ },
+ {
+ "input": "&gsim",
+ "description": "Bad named entity: gsim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gsim"
+ ]
+ ]
+ },
+ {
+ "input": "≳",
+ "description": "Named entity: gsim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2273"
+ ]
+ ]
+ },
+ {
+ "input": "&gsime",
+ "description": "Bad named entity: gsime without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gsime"
+ ]
+ ]
+ },
+ {
+ "input": "⪎",
+ "description": "Named entity: gsime; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a8e"
+ ]
+ ]
+ },
+ {
+ "input": "&gsiml",
+ "description": "Bad named entity: gsiml without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gsiml"
+ ]
+ ]
+ },
+ {
+ "input": "⪐",
+ "description": "Named entity: gsiml; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a90"
+ ]
+ ]
+ },
+ {
+ "input": ">",
+ "description": "Named entity: gt without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ ">"
+ ]
+ ]
+ },
+ {
+ "input": ">",
+ "description": "Named entity: gt; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ ">"
+ ]
+ ]
+ },
+ {
+ "input": "⪧",
+ "description": "Named entity: gtcc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2aa7"
+ ]
+ ]
+ },
+ {
+ "input": "⩺",
+ "description": "Named entity: gtcir; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a7a"
+ ]
+ ]
+ },
+ {
+ "input": "⋗",
+ "description": "Named entity: gtdot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22d7"
+ ]
+ ]
+ },
+ {
+ "input": "⦕",
+ "description": "Named entity: gtlPar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2995"
+ ]
+ ]
+ },
+ {
+ "input": "⩼",
+ "description": "Named entity: gtquest; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a7c"
+ ]
+ ]
+ },
+ {
+ "input": "⪆",
+ "description": "Named entity: gtrapprox; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a86"
+ ]
+ ]
+ },
+ {
+ "input": "⥸",
+ "description": "Named entity: gtrarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2978"
+ ]
+ ]
+ },
+ {
+ "input": "⋗",
+ "description": "Named entity: gtrdot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22d7"
+ ]
+ ]
+ },
+ {
+ "input": "⋛",
+ "description": "Named entity: gtreqless; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22db"
+ ]
+ ]
+ },
+ {
+ "input": "⪌",
+ "description": "Named entity: gtreqqless; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a8c"
+ ]
+ ]
+ },
+ {
+ "input": "≷",
+ "description": "Named entity: gtrless; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2277"
+ ]
+ ]
+ },
+ {
+ "input": "≳",
+ "description": "Named entity: gtrsim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2273"
+ ]
+ ]
+ },
+ {
+ "input": "&gvertneqq",
+ "description": "Bad named entity: gvertneqq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gvertneqq"
+ ]
+ ]
+ },
+ {
+ "input": "≩︀",
+ "description": "Named entity: gvertneqq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2269\ufe00"
+ ]
+ ]
+ },
+ {
+ "input": "&gvnE",
+ "description": "Bad named entity: gvnE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&gvnE"
+ ]
+ ]
+ },
+ {
+ "input": "≩︀",
+ "description": "Named entity: gvnE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2269\ufe00"
+ ]
+ ]
+ },
+ {
+ "input": "&hArr",
+ "description": "Bad named entity: hArr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&hArr"
+ ]
+ ]
+ },
+ {
+ "input": "⇔",
+ "description": "Named entity: hArr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21d4"
+ ]
+ ]
+ },
+ {
+ "input": "&hairsp",
+ "description": "Bad named entity: hairsp without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&hairsp"
+ ]
+ ]
+ },
+ {
+ "input": " ",
+ "description": "Named entity: hairsp; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u200a"
+ ]
+ ]
+ },
+ {
+ "input": "&half",
+ "description": "Bad named entity: half without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&half"
+ ]
+ ]
+ },
+ {
+ "input": "½",
+ "description": "Named entity: half; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00bd"
+ ]
+ ]
+ },
+ {
+ "input": "&hamilt",
+ "description": "Bad named entity: hamilt without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&hamilt"
+ ]
+ ]
+ },
+ {
+ "input": "ℋ",
+ "description": "Named entity: hamilt; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u210b"
+ ]
+ ]
+ },
+ {
+ "input": "&hardcy",
+ "description": "Bad named entity: hardcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&hardcy"
+ ]
+ ]
+ },
+ {
+ "input": "ъ",
+ "description": "Named entity: hardcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u044a"
+ ]
+ ]
+ },
+ {
+ "input": "&harr",
+ "description": "Bad named entity: harr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&harr"
+ ]
+ ]
+ },
+ {
+ "input": "↔",
+ "description": "Named entity: harr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2194"
+ ]
+ ]
+ },
+ {
+ "input": "&harrcir",
+ "description": "Bad named entity: harrcir without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&harrcir"
+ ]
+ ]
+ },
+ {
+ "input": "⥈",
+ "description": "Named entity: harrcir; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2948"
+ ]
+ ]
+ },
+ {
+ "input": "&harrw",
+ "description": "Bad named entity: harrw without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&harrw"
+ ]
+ ]
+ },
+ {
+ "input": "↭",
+ "description": "Named entity: harrw; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21ad"
+ ]
+ ]
+ },
+ {
+ "input": "&hbar",
+ "description": "Bad named entity: hbar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&hbar"
+ ]
+ ]
+ },
+ {
+ "input": "ℏ",
+ "description": "Named entity: hbar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u210f"
+ ]
+ ]
+ },
+ {
+ "input": "&hcirc",
+ "description": "Bad named entity: hcirc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&hcirc"
+ ]
+ ]
+ },
+ {
+ "input": "ĥ",
+ "description": "Named entity: hcirc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0125"
+ ]
+ ]
+ },
+ {
+ "input": "&hearts",
+ "description": "Bad named entity: hearts without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&hearts"
+ ]
+ ]
+ },
+ {
+ "input": "♥",
+ "description": "Named entity: hearts; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2665"
+ ]
+ ]
+ },
+ {
+ "input": "&heartsuit",
+ "description": "Bad named entity: heartsuit without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&heartsuit"
+ ]
+ ]
+ },
+ {
+ "input": "♥",
+ "description": "Named entity: heartsuit; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2665"
+ ]
+ ]
+ },
+ {
+ "input": "&hellip",
+ "description": "Bad named entity: hellip without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&hellip"
+ ]
+ ]
+ },
+ {
+ "input": "…",
+ "description": "Named entity: hellip; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2026"
+ ]
+ ]
+ },
+ {
+ "input": "&hercon",
+ "description": "Bad named entity: hercon without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&hercon"
+ ]
+ ]
+ },
+ {
+ "input": "⊹",
+ "description": "Named entity: hercon; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22b9"
+ ]
+ ]
+ },
+ {
+ "input": "&hfr",
+ "description": "Bad named entity: hfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&hfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔥",
+ "description": "Named entity: hfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd25"
+ ]
+ ]
+ },
+ {
+ "input": "&hksearow",
+ "description": "Bad named entity: hksearow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&hksearow"
+ ]
+ ]
+ },
+ {
+ "input": "⤥",
+ "description": "Named entity: hksearow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2925"
+ ]
+ ]
+ },
+ {
+ "input": "&hkswarow",
+ "description": "Bad named entity: hkswarow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&hkswarow"
+ ]
+ ]
+ },
+ {
+ "input": "⤦",
+ "description": "Named entity: hkswarow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2926"
+ ]
+ ]
+ },
+ {
+ "input": "&hoarr",
+ "description": "Bad named entity: hoarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&hoarr"
+ ]
+ ]
+ },
+ {
+ "input": "⇿",
+ "description": "Named entity: hoarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21ff"
+ ]
+ ]
+ },
+ {
+ "input": "&homtht",
+ "description": "Bad named entity: homtht without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&homtht"
+ ]
+ ]
+ },
+ {
+ "input": "∻",
+ "description": "Named entity: homtht; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u223b"
+ ]
+ ]
+ },
+ {
+ "input": "&hookleftarrow",
+ "description": "Bad named entity: hookleftarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&hookleftarrow"
+ ]
+ ]
+ },
+ {
+ "input": "↩",
+ "description": "Named entity: hookleftarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21a9"
+ ]
+ ]
+ },
+ {
+ "input": "&hookrightarrow",
+ "description": "Bad named entity: hookrightarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&hookrightarrow"
+ ]
+ ]
+ },
+ {
+ "input": "↪",
+ "description": "Named entity: hookrightarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21aa"
+ ]
+ ]
+ },
+ {
+ "input": "&hopf",
+ "description": "Bad named entity: hopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&hopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕙",
+ "description": "Named entity: hopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd59"
+ ]
+ ]
+ },
+ {
+ "input": "&horbar",
+ "description": "Bad named entity: horbar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&horbar"
+ ]
+ ]
+ },
+ {
+ "input": "―",
+ "description": "Named entity: horbar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2015"
+ ]
+ ]
+ },
+ {
+ "input": "&hscr",
+ "description": "Bad named entity: hscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&hscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝒽",
+ "description": "Named entity: hscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udcbd"
+ ]
+ ]
+ },
+ {
+ "input": "&hslash",
+ "description": "Bad named entity: hslash without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&hslash"
+ ]
+ ]
+ },
+ {
+ "input": "ℏ",
+ "description": "Named entity: hslash; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u210f"
+ ]
+ ]
+ },
+ {
+ "input": "&hstrok",
+ "description": "Bad named entity: hstrok without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&hstrok"
+ ]
+ ]
+ },
+ {
+ "input": "ħ",
+ "description": "Named entity: hstrok; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0127"
+ ]
+ ]
+ },
+ {
+ "input": "&hybull",
+ "description": "Bad named entity: hybull without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&hybull"
+ ]
+ ]
+ },
+ {
+ "input": "⁃",
+ "description": "Named entity: hybull; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2043"
+ ]
+ ]
+ },
+ {
+ "input": "&hyphen",
+ "description": "Bad named entity: hyphen without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&hyphen"
+ ]
+ ]
+ },
+ {
+ "input": "‐",
+ "description": "Named entity: hyphen; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2010"
+ ]
+ ]
+ },
+ {
+ "input": "í",
+ "description": "Named entity: iacute without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00ed"
+ ]
+ ]
+ },
+ {
+ "input": "í",
+ "description": "Named entity: iacute; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00ed"
+ ]
+ ]
+ },
+ {
+ "input": "&ic",
+ "description": "Bad named entity: ic without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ic"
+ ]
+ ]
+ },
+ {
+ "input": "⁣",
+ "description": "Named entity: ic; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2063"
+ ]
+ ]
+ },
+ {
+ "input": "î",
+ "description": "Named entity: icirc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00ee"
+ ]
+ ]
+ },
+ {
+ "input": "î",
+ "description": "Named entity: icirc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00ee"
+ ]
+ ]
+ },
+ {
+ "input": "&icy",
+ "description": "Bad named entity: icy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&icy"
+ ]
+ ]
+ },
+ {
+ "input": "и",
+ "description": "Named entity: icy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0438"
+ ]
+ ]
+ },
+ {
+ "input": "&iecy",
+ "description": "Bad named entity: iecy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&iecy"
+ ]
+ ]
+ },
+ {
+ "input": "е",
+ "description": "Named entity: iecy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0435"
+ ]
+ ]
+ },
+ {
+ "input": "¡",
+ "description": "Named entity: iexcl without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00a1"
+ ]
+ ]
+ },
+ {
+ "input": "¡",
+ "description": "Named entity: iexcl; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00a1"
+ ]
+ ]
+ },
+ {
+ "input": "&iff",
+ "description": "Bad named entity: iff without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&iff"
+ ]
+ ]
+ },
+ {
+ "input": "⇔",
+ "description": "Named entity: iff; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21d4"
+ ]
+ ]
+ },
+ {
+ "input": "&ifr",
+ "description": "Bad named entity: ifr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ifr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔦",
+ "description": "Named entity: ifr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd26"
+ ]
+ ]
+ },
+ {
+ "input": "ì",
+ "description": "Named entity: igrave without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00ec"
+ ]
+ ]
+ },
+ {
+ "input": "ì",
+ "description": "Named entity: igrave; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00ec"
+ ]
+ ]
+ },
+ {
+ "input": "&ii",
+ "description": "Bad named entity: ii without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ii"
+ ]
+ ]
+ },
+ {
+ "input": "ⅈ",
+ "description": "Named entity: ii; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2148"
+ ]
+ ]
+ },
+ {
+ "input": "&iiiint",
+ "description": "Bad named entity: iiiint without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&iiiint"
+ ]
+ ]
+ },
+ {
+ "input": "⨌",
+ "description": "Named entity: iiiint; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a0c"
+ ]
+ ]
+ },
+ {
+ "input": "&iiint",
+ "description": "Bad named entity: iiint without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&iiint"
+ ]
+ ]
+ },
+ {
+ "input": "∭",
+ "description": "Named entity: iiint; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u222d"
+ ]
+ ]
+ },
+ {
+ "input": "&iinfin",
+ "description": "Bad named entity: iinfin without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&iinfin"
+ ]
+ ]
+ },
+ {
+ "input": "⧜",
+ "description": "Named entity: iinfin; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29dc"
+ ]
+ ]
+ },
+ {
+ "input": "&iiota",
+ "description": "Bad named entity: iiota without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&iiota"
+ ]
+ ]
+ },
+ {
+ "input": "℩",
+ "description": "Named entity: iiota; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2129"
+ ]
+ ]
+ },
+ {
+ "input": "&ijlig",
+ "description": "Bad named entity: ijlig without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ijlig"
+ ]
+ ]
+ },
+ {
+ "input": "ij",
+ "description": "Named entity: ijlig; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0133"
+ ]
+ ]
+ },
+ {
+ "input": "&imacr",
+ "description": "Bad named entity: imacr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&imacr"
+ ]
+ ]
+ },
+ {
+ "input": "ī",
+ "description": "Named entity: imacr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u012b"
+ ]
+ ]
+ },
+ {
+ "input": "&image",
+ "description": "Bad named entity: image without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&image"
+ ]
+ ]
+ },
+ {
+ "input": "ℑ",
+ "description": "Named entity: image; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2111"
+ ]
+ ]
+ },
+ {
+ "input": "&imagline",
+ "description": "Bad named entity: imagline without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&imagline"
+ ]
+ ]
+ },
+ {
+ "input": "ℐ",
+ "description": "Named entity: imagline; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2110"
+ ]
+ ]
+ },
+ {
+ "input": "&imagpart",
+ "description": "Bad named entity: imagpart without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&imagpart"
+ ]
+ ]
+ },
+ {
+ "input": "ℑ",
+ "description": "Named entity: imagpart; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2111"
+ ]
+ ]
+ },
+ {
+ "input": "&imath",
+ "description": "Bad named entity: imath without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&imath"
+ ]
+ ]
+ },
+ {
+ "input": "ı",
+ "description": "Named entity: imath; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0131"
+ ]
+ ]
+ },
+ {
+ "input": "&imof",
+ "description": "Bad named entity: imof without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&imof"
+ ]
+ ]
+ },
+ {
+ "input": "⊷",
+ "description": "Named entity: imof; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22b7"
+ ]
+ ]
+ },
+ {
+ "input": "&imped",
+ "description": "Bad named entity: imped without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&imped"
+ ]
+ ]
+ },
+ {
+ "input": "Ƶ",
+ "description": "Named entity: imped; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u01b5"
+ ]
+ ]
+ },
+ {
+ "input": "&in",
+ "description": "Bad named entity: in without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&in"
+ ]
+ ]
+ },
+ {
+ "input": "∈",
+ "description": "Named entity: in; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2208"
+ ]
+ ]
+ },
+ {
+ "input": "&incare",
+ "description": "Bad named entity: incare without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&incare"
+ ]
+ ]
+ },
+ {
+ "input": "℅",
+ "description": "Named entity: incare; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2105"
+ ]
+ ]
+ },
+ {
+ "input": "&infin",
+ "description": "Bad named entity: infin without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&infin"
+ ]
+ ]
+ },
+ {
+ "input": "∞",
+ "description": "Named entity: infin; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u221e"
+ ]
+ ]
+ },
+ {
+ "input": "&infintie",
+ "description": "Bad named entity: infintie without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&infintie"
+ ]
+ ]
+ },
+ {
+ "input": "⧝",
+ "description": "Named entity: infintie; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29dd"
+ ]
+ ]
+ },
+ {
+ "input": "&inodot",
+ "description": "Bad named entity: inodot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&inodot"
+ ]
+ ]
+ },
+ {
+ "input": "ı",
+ "description": "Named entity: inodot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0131"
+ ]
+ ]
+ },
+ {
+ "input": "&int",
+ "description": "Bad named entity: int without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&int"
+ ]
+ ]
+ },
+ {
+ "input": "∫",
+ "description": "Named entity: int; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u222b"
+ ]
+ ]
+ },
+ {
+ "input": "&intcal",
+ "description": "Bad named entity: intcal without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&intcal"
+ ]
+ ]
+ },
+ {
+ "input": "⊺",
+ "description": "Named entity: intcal; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22ba"
+ ]
+ ]
+ },
+ {
+ "input": "&integers",
+ "description": "Bad named entity: integers without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&integers"
+ ]
+ ]
+ },
+ {
+ "input": "ℤ",
+ "description": "Named entity: integers; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2124"
+ ]
+ ]
+ },
+ {
+ "input": "&intercal",
+ "description": "Bad named entity: intercal without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&intercal"
+ ]
+ ]
+ },
+ {
+ "input": "⊺",
+ "description": "Named entity: intercal; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22ba"
+ ]
+ ]
+ },
+ {
+ "input": "&intlarhk",
+ "description": "Bad named entity: intlarhk without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&intlarhk"
+ ]
+ ]
+ },
+ {
+ "input": "⨗",
+ "description": "Named entity: intlarhk; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a17"
+ ]
+ ]
+ },
+ {
+ "input": "&intprod",
+ "description": "Bad named entity: intprod without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&intprod"
+ ]
+ ]
+ },
+ {
+ "input": "⨼",
+ "description": "Named entity: intprod; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a3c"
+ ]
+ ]
+ },
+ {
+ "input": "&iocy",
+ "description": "Bad named entity: iocy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&iocy"
+ ]
+ ]
+ },
+ {
+ "input": "ё",
+ "description": "Named entity: iocy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0451"
+ ]
+ ]
+ },
+ {
+ "input": "&iogon",
+ "description": "Bad named entity: iogon without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&iogon"
+ ]
+ ]
+ },
+ {
+ "input": "į",
+ "description": "Named entity: iogon; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u012f"
+ ]
+ ]
+ },
+ {
+ "input": "&iopf",
+ "description": "Bad named entity: iopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&iopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕚",
+ "description": "Named entity: iopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd5a"
+ ]
+ ]
+ },
+ {
+ "input": "&iota",
+ "description": "Bad named entity: iota without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&iota"
+ ]
+ ]
+ },
+ {
+ "input": "ι",
+ "description": "Named entity: iota; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03b9"
+ ]
+ ]
+ },
+ {
+ "input": "&iprod",
+ "description": "Bad named entity: iprod without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&iprod"
+ ]
+ ]
+ },
+ {
+ "input": "⨼",
+ "description": "Named entity: iprod; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a3c"
+ ]
+ ]
+ },
+ {
+ "input": "¿",
+ "description": "Named entity: iquest without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00bf"
+ ]
+ ]
+ },
+ {
+ "input": "¿",
+ "description": "Named entity: iquest; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00bf"
+ ]
+ ]
+ },
+ {
+ "input": "&iscr",
+ "description": "Bad named entity: iscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&iscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝒾",
+ "description": "Named entity: iscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udcbe"
+ ]
+ ]
+ },
+ {
+ "input": "&isin",
+ "description": "Bad named entity: isin without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&isin"
+ ]
+ ]
+ },
+ {
+ "input": "∈",
+ "description": "Named entity: isin; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2208"
+ ]
+ ]
+ },
+ {
+ "input": "&isinE",
+ "description": "Bad named entity: isinE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&isinE"
+ ]
+ ]
+ },
+ {
+ "input": "⋹",
+ "description": "Named entity: isinE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22f9"
+ ]
+ ]
+ },
+ {
+ "input": "&isindot",
+ "description": "Bad named entity: isindot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&isindot"
+ ]
+ ]
+ },
+ {
+ "input": "⋵",
+ "description": "Named entity: isindot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22f5"
+ ]
+ ]
+ },
+ {
+ "input": "&isins",
+ "description": "Bad named entity: isins without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&isins"
+ ]
+ ]
+ },
+ {
+ "input": "⋴",
+ "description": "Named entity: isins; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22f4"
+ ]
+ ]
+ },
+ {
+ "input": "&isinsv",
+ "description": "Bad named entity: isinsv without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&isinsv"
+ ]
+ ]
+ },
+ {
+ "input": "⋳",
+ "description": "Named entity: isinsv; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22f3"
+ ]
+ ]
+ },
+ {
+ "input": "&isinv",
+ "description": "Bad named entity: isinv without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&isinv"
+ ]
+ ]
+ },
+ {
+ "input": "∈",
+ "description": "Named entity: isinv; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2208"
+ ]
+ ]
+ },
+ {
+ "input": "&it",
+ "description": "Bad named entity: it without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&it"
+ ]
+ ]
+ },
+ {
+ "input": "⁢",
+ "description": "Named entity: it; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2062"
+ ]
+ ]
+ },
+ {
+ "input": "&itilde",
+ "description": "Bad named entity: itilde without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&itilde"
+ ]
+ ]
+ },
+ {
+ "input": "ĩ",
+ "description": "Named entity: itilde; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0129"
+ ]
+ ]
+ },
+ {
+ "input": "&iukcy",
+ "description": "Bad named entity: iukcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&iukcy"
+ ]
+ ]
+ },
+ {
+ "input": "і",
+ "description": "Named entity: iukcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0456"
+ ]
+ ]
+ },
+ {
+ "input": "ï",
+ "description": "Named entity: iuml without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00ef"
+ ]
+ ]
+ },
+ {
+ "input": "ï",
+ "description": "Named entity: iuml; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00ef"
+ ]
+ ]
+ },
+ {
+ "input": "&jcirc",
+ "description": "Bad named entity: jcirc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&jcirc"
+ ]
+ ]
+ },
+ {
+ "input": "ĵ",
+ "description": "Named entity: jcirc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0135"
+ ]
+ ]
+ },
+ {
+ "input": "&jcy",
+ "description": "Bad named entity: jcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&jcy"
+ ]
+ ]
+ },
+ {
+ "input": "й",
+ "description": "Named entity: jcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0439"
+ ]
+ ]
+ },
+ {
+ "input": "&jfr",
+ "description": "Bad named entity: jfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&jfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔧",
+ "description": "Named entity: jfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd27"
+ ]
+ ]
+ },
+ {
+ "input": "&jmath",
+ "description": "Bad named entity: jmath without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&jmath"
+ ]
+ ]
+ },
+ {
+ "input": "ȷ",
+ "description": "Named entity: jmath; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0237"
+ ]
+ ]
+ },
+ {
+ "input": "&jopf",
+ "description": "Bad named entity: jopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&jopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕛",
+ "description": "Named entity: jopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd5b"
+ ]
+ ]
+ },
+ {
+ "input": "&jscr",
+ "description": "Bad named entity: jscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&jscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝒿",
+ "description": "Named entity: jscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udcbf"
+ ]
+ ]
+ },
+ {
+ "input": "&jsercy",
+ "description": "Bad named entity: jsercy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&jsercy"
+ ]
+ ]
+ },
+ {
+ "input": "ј",
+ "description": "Named entity: jsercy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0458"
+ ]
+ ]
+ },
+ {
+ "input": "&jukcy",
+ "description": "Bad named entity: jukcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&jukcy"
+ ]
+ ]
+ },
+ {
+ "input": "є",
+ "description": "Named entity: jukcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0454"
+ ]
+ ]
+ },
+ {
+ "input": "&kappa",
+ "description": "Bad named entity: kappa without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&kappa"
+ ]
+ ]
+ },
+ {
+ "input": "κ",
+ "description": "Named entity: kappa; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03ba"
+ ]
+ ]
+ },
+ {
+ "input": "&kappav",
+ "description": "Bad named entity: kappav without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&kappav"
+ ]
+ ]
+ },
+ {
+ "input": "ϰ",
+ "description": "Named entity: kappav; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03f0"
+ ]
+ ]
+ },
+ {
+ "input": "&kcedil",
+ "description": "Bad named entity: kcedil without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&kcedil"
+ ]
+ ]
+ },
+ {
+ "input": "ķ",
+ "description": "Named entity: kcedil; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0137"
+ ]
+ ]
+ },
+ {
+ "input": "&kcy",
+ "description": "Bad named entity: kcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&kcy"
+ ]
+ ]
+ },
+ {
+ "input": "к",
+ "description": "Named entity: kcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u043a"
+ ]
+ ]
+ },
+ {
+ "input": "&kfr",
+ "description": "Bad named entity: kfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&kfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔨",
+ "description": "Named entity: kfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd28"
+ ]
+ ]
+ },
+ {
+ "input": "&kgreen",
+ "description": "Bad named entity: kgreen without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&kgreen"
+ ]
+ ]
+ },
+ {
+ "input": "ĸ",
+ "description": "Named entity: kgreen; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0138"
+ ]
+ ]
+ },
+ {
+ "input": "&khcy",
+ "description": "Bad named entity: khcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&khcy"
+ ]
+ ]
+ },
+ {
+ "input": "х",
+ "description": "Named entity: khcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0445"
+ ]
+ ]
+ },
+ {
+ "input": "&kjcy",
+ "description": "Bad named entity: kjcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&kjcy"
+ ]
+ ]
+ },
+ {
+ "input": "ќ",
+ "description": "Named entity: kjcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u045c"
+ ]
+ ]
+ },
+ {
+ "input": "&kopf",
+ "description": "Bad named entity: kopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&kopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕜",
+ "description": "Named entity: kopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd5c"
+ ]
+ ]
+ },
+ {
+ "input": "&kscr",
+ "description": "Bad named entity: kscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&kscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝓀",
+ "description": "Named entity: kscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udcc0"
+ ]
+ ]
+ },
+ {
+ "input": "&lAarr",
+ "description": "Bad named entity: lAarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lAarr"
+ ]
+ ]
+ },
+ {
+ "input": "⇚",
+ "description": "Named entity: lAarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21da"
+ ]
+ ]
+ },
+ {
+ "input": "&lArr",
+ "description": "Bad named entity: lArr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lArr"
+ ]
+ ]
+ },
+ {
+ "input": "⇐",
+ "description": "Named entity: lArr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21d0"
+ ]
+ ]
+ },
+ {
+ "input": "&lAtail",
+ "description": "Bad named entity: lAtail without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lAtail"
+ ]
+ ]
+ },
+ {
+ "input": "⤛",
+ "description": "Named entity: lAtail; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u291b"
+ ]
+ ]
+ },
+ {
+ "input": "&lBarr",
+ "description": "Bad named entity: lBarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lBarr"
+ ]
+ ]
+ },
+ {
+ "input": "⤎",
+ "description": "Named entity: lBarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u290e"
+ ]
+ ]
+ },
+ {
+ "input": "&lE",
+ "description": "Bad named entity: lE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lE"
+ ]
+ ]
+ },
+ {
+ "input": "≦",
+ "description": "Named entity: lE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2266"
+ ]
+ ]
+ },
+ {
+ "input": "&lEg",
+ "description": "Bad named entity: lEg without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lEg"
+ ]
+ ]
+ },
+ {
+ "input": "⪋",
+ "description": "Named entity: lEg; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a8b"
+ ]
+ ]
+ },
+ {
+ "input": "&lHar",
+ "description": "Bad named entity: lHar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lHar"
+ ]
+ ]
+ },
+ {
+ "input": "⥢",
+ "description": "Named entity: lHar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2962"
+ ]
+ ]
+ },
+ {
+ "input": "&lacute",
+ "description": "Bad named entity: lacute without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lacute"
+ ]
+ ]
+ },
+ {
+ "input": "ĺ",
+ "description": "Named entity: lacute; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u013a"
+ ]
+ ]
+ },
+ {
+ "input": "&laemptyv",
+ "description": "Bad named entity: laemptyv without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&laemptyv"
+ ]
+ ]
+ },
+ {
+ "input": "⦴",
+ "description": "Named entity: laemptyv; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29b4"
+ ]
+ ]
+ },
+ {
+ "input": "&lagran",
+ "description": "Bad named entity: lagran without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lagran"
+ ]
+ ]
+ },
+ {
+ "input": "ℒ",
+ "description": "Named entity: lagran; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2112"
+ ]
+ ]
+ },
+ {
+ "input": "&lambda",
+ "description": "Bad named entity: lambda without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lambda"
+ ]
+ ]
+ },
+ {
+ "input": "λ",
+ "description": "Named entity: lambda; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03bb"
+ ]
+ ]
+ },
+ {
+ "input": "&lang",
+ "description": "Bad named entity: lang without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lang"
+ ]
+ ]
+ },
+ {
+ "input": "⟨",
+ "description": "Named entity: lang; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27e8"
+ ]
+ ]
+ },
+ {
+ "input": "&langd",
+ "description": "Bad named entity: langd without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&langd"
+ ]
+ ]
+ },
+ {
+ "input": "⦑",
+ "description": "Named entity: langd; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2991"
+ ]
+ ]
+ },
+ {
+ "input": "&langle",
+ "description": "Bad named entity: langle without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&langle"
+ ]
+ ]
+ },
+ {
+ "input": "⟨",
+ "description": "Named entity: langle; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27e8"
+ ]
+ ]
+ },
+ {
+ "input": "&lap",
+ "description": "Bad named entity: lap without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lap"
+ ]
+ ]
+ },
+ {
+ "input": "⪅",
+ "description": "Named entity: lap; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a85"
+ ]
+ ]
+ },
+ {
+ "input": "«",
+ "description": "Named entity: laquo without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00ab"
+ ]
+ ]
+ },
+ {
+ "input": "«",
+ "description": "Named entity: laquo; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00ab"
+ ]
+ ]
+ },
+ {
+ "input": "&larr",
+ "description": "Bad named entity: larr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&larr"
+ ]
+ ]
+ },
+ {
+ "input": "←",
+ "description": "Named entity: larr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2190"
+ ]
+ ]
+ },
+ {
+ "input": "&larrb",
+ "description": "Bad named entity: larrb without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&larrb"
+ ]
+ ]
+ },
+ {
+ "input": "⇤",
+ "description": "Named entity: larrb; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21e4"
+ ]
+ ]
+ },
+ {
+ "input": "&larrbfs",
+ "description": "Bad named entity: larrbfs without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&larrbfs"
+ ]
+ ]
+ },
+ {
+ "input": "⤟",
+ "description": "Named entity: larrbfs; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u291f"
+ ]
+ ]
+ },
+ {
+ "input": "&larrfs",
+ "description": "Bad named entity: larrfs without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&larrfs"
+ ]
+ ]
+ },
+ {
+ "input": "⤝",
+ "description": "Named entity: larrfs; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u291d"
+ ]
+ ]
+ },
+ {
+ "input": "&larrhk",
+ "description": "Bad named entity: larrhk without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&larrhk"
+ ]
+ ]
+ },
+ {
+ "input": "↩",
+ "description": "Named entity: larrhk; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21a9"
+ ]
+ ]
+ },
+ {
+ "input": "&larrlp",
+ "description": "Bad named entity: larrlp without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&larrlp"
+ ]
+ ]
+ },
+ {
+ "input": "↫",
+ "description": "Named entity: larrlp; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21ab"
+ ]
+ ]
+ },
+ {
+ "input": "&larrpl",
+ "description": "Bad named entity: larrpl without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&larrpl"
+ ]
+ ]
+ },
+ {
+ "input": "⤹",
+ "description": "Named entity: larrpl; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2939"
+ ]
+ ]
+ },
+ {
+ "input": "&larrsim",
+ "description": "Bad named entity: larrsim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&larrsim"
+ ]
+ ]
+ },
+ {
+ "input": "⥳",
+ "description": "Named entity: larrsim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2973"
+ ]
+ ]
+ },
+ {
+ "input": "&larrtl",
+ "description": "Bad named entity: larrtl without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&larrtl"
+ ]
+ ]
+ },
+ {
+ "input": "↢",
+ "description": "Named entity: larrtl; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21a2"
+ ]
+ ]
+ },
+ {
+ "input": "&lat",
+ "description": "Bad named entity: lat without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lat"
+ ]
+ ]
+ },
+ {
+ "input": "⪫",
+ "description": "Named entity: lat; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2aab"
+ ]
+ ]
+ },
+ {
+ "input": "&latail",
+ "description": "Bad named entity: latail without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&latail"
+ ]
+ ]
+ },
+ {
+ "input": "⤙",
+ "description": "Named entity: latail; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2919"
+ ]
+ ]
+ },
+ {
+ "input": "&late",
+ "description": "Bad named entity: late without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&late"
+ ]
+ ]
+ },
+ {
+ "input": "⪭",
+ "description": "Named entity: late; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2aad"
+ ]
+ ]
+ },
+ {
+ "input": "&lates",
+ "description": "Bad named entity: lates without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lates"
+ ]
+ ]
+ },
+ {
+ "input": "⪭︀",
+ "description": "Named entity: lates; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2aad\ufe00"
+ ]
+ ]
+ },
+ {
+ "input": "&lbarr",
+ "description": "Bad named entity: lbarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lbarr"
+ ]
+ ]
+ },
+ {
+ "input": "⤌",
+ "description": "Named entity: lbarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u290c"
+ ]
+ ]
+ },
+ {
+ "input": "&lbbrk",
+ "description": "Bad named entity: lbbrk without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lbbrk"
+ ]
+ ]
+ },
+ {
+ "input": "❲",
+ "description": "Named entity: lbbrk; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2772"
+ ]
+ ]
+ },
+ {
+ "input": "&lbrace",
+ "description": "Bad named entity: lbrace without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lbrace"
+ ]
+ ]
+ },
+ {
+ "input": "{",
+ "description": "Named entity: lbrace; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "{"
+ ]
+ ]
+ },
+ {
+ "input": "&lbrack",
+ "description": "Bad named entity: lbrack without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lbrack"
+ ]
+ ]
+ },
+ {
+ "input": "[",
+ "description": "Named entity: lbrack; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "["
+ ]
+ ]
+ },
+ {
+ "input": "&lbrke",
+ "description": "Bad named entity: lbrke without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lbrke"
+ ]
+ ]
+ },
+ {
+ "input": "⦋",
+ "description": "Named entity: lbrke; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u298b"
+ ]
+ ]
+ },
+ {
+ "input": "&lbrksld",
+ "description": "Bad named entity: lbrksld without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lbrksld"
+ ]
+ ]
+ },
+ {
+ "input": "⦏",
+ "description": "Named entity: lbrksld; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u298f"
+ ]
+ ]
+ },
+ {
+ "input": "&lbrkslu",
+ "description": "Bad named entity: lbrkslu without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lbrkslu"
+ ]
+ ]
+ },
+ {
+ "input": "⦍",
+ "description": "Named entity: lbrkslu; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u298d"
+ ]
+ ]
+ },
+ {
+ "input": "&lcaron",
+ "description": "Bad named entity: lcaron without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lcaron"
+ ]
+ ]
+ },
+ {
+ "input": "ľ",
+ "description": "Named entity: lcaron; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u013e"
+ ]
+ ]
+ },
+ {
+ "input": "&lcedil",
+ "description": "Bad named entity: lcedil without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lcedil"
+ ]
+ ]
+ },
+ {
+ "input": "ļ",
+ "description": "Named entity: lcedil; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u013c"
+ ]
+ ]
+ },
+ {
+ "input": "&lceil",
+ "description": "Bad named entity: lceil without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lceil"
+ ]
+ ]
+ },
+ {
+ "input": "⌈",
+ "description": "Named entity: lceil; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2308"
+ ]
+ ]
+ },
+ {
+ "input": "&lcub",
+ "description": "Bad named entity: lcub without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lcub"
+ ]
+ ]
+ },
+ {
+ "input": "{",
+ "description": "Named entity: lcub; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "{"
+ ]
+ ]
+ },
+ {
+ "input": "&lcy",
+ "description": "Bad named entity: lcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lcy"
+ ]
+ ]
+ },
+ {
+ "input": "л",
+ "description": "Named entity: lcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u043b"
+ ]
+ ]
+ },
+ {
+ "input": "&ldca",
+ "description": "Bad named entity: ldca without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ldca"
+ ]
+ ]
+ },
+ {
+ "input": "⤶",
+ "description": "Named entity: ldca; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2936"
+ ]
+ ]
+ },
+ {
+ "input": "&ldquo",
+ "description": "Bad named entity: ldquo without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ldquo"
+ ]
+ ]
+ },
+ {
+ "input": "“",
+ "description": "Named entity: ldquo; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u201c"
+ ]
+ ]
+ },
+ {
+ "input": "&ldquor",
+ "description": "Bad named entity: ldquor without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ldquor"
+ ]
+ ]
+ },
+ {
+ "input": "„",
+ "description": "Named entity: ldquor; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u201e"
+ ]
+ ]
+ },
+ {
+ "input": "&ldrdhar",
+ "description": "Bad named entity: ldrdhar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ldrdhar"
+ ]
+ ]
+ },
+ {
+ "input": "⥧",
+ "description": "Named entity: ldrdhar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2967"
+ ]
+ ]
+ },
+ {
+ "input": "&ldrushar",
+ "description": "Bad named entity: ldrushar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ldrushar"
+ ]
+ ]
+ },
+ {
+ "input": "⥋",
+ "description": "Named entity: ldrushar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u294b"
+ ]
+ ]
+ },
+ {
+ "input": "&ldsh",
+ "description": "Bad named entity: ldsh without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ldsh"
+ ]
+ ]
+ },
+ {
+ "input": "↲",
+ "description": "Named entity: ldsh; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21b2"
+ ]
+ ]
+ },
+ {
+ "input": "&le",
+ "description": "Bad named entity: le without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&le"
+ ]
+ ]
+ },
+ {
+ "input": "≤",
+ "description": "Named entity: le; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2264"
+ ]
+ ]
+ },
+ {
+ "input": "&leftarrow",
+ "description": "Bad named entity: leftarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&leftarrow"
+ ]
+ ]
+ },
+ {
+ "input": "←",
+ "description": "Named entity: leftarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2190"
+ ]
+ ]
+ },
+ {
+ "input": "&leftarrowtail",
+ "description": "Bad named entity: leftarrowtail without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&leftarrowtail"
+ ]
+ ]
+ },
+ {
+ "input": "↢",
+ "description": "Named entity: leftarrowtail; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21a2"
+ ]
+ ]
+ },
+ {
+ "input": "&leftharpoondown",
+ "description": "Bad named entity: leftharpoondown without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&leftharpoondown"
+ ]
+ ]
+ },
+ {
+ "input": "↽",
+ "description": "Named entity: leftharpoondown; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21bd"
+ ]
+ ]
+ },
+ {
+ "input": "&leftharpoonup",
+ "description": "Bad named entity: leftharpoonup without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&leftharpoonup"
+ ]
+ ]
+ },
+ {
+ "input": "↼",
+ "description": "Named entity: leftharpoonup; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21bc"
+ ]
+ ]
+ },
+ {
+ "input": "&leftleftarrows",
+ "description": "Bad named entity: leftleftarrows without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&leftleftarrows"
+ ]
+ ]
+ },
+ {
+ "input": "⇇",
+ "description": "Named entity: leftleftarrows; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21c7"
+ ]
+ ]
+ },
+ {
+ "input": "&leftrightarrow",
+ "description": "Bad named entity: leftrightarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&leftrightarrow"
+ ]
+ ]
+ },
+ {
+ "input": "↔",
+ "description": "Named entity: leftrightarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2194"
+ ]
+ ]
+ },
+ {
+ "input": "&leftrightarrows",
+ "description": "Bad named entity: leftrightarrows without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&leftrightarrows"
+ ]
+ ]
+ },
+ {
+ "input": "⇆",
+ "description": "Named entity: leftrightarrows; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21c6"
+ ]
+ ]
+ },
+ {
+ "input": "&leftrightharpoons",
+ "description": "Bad named entity: leftrightharpoons without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&leftrightharpoons"
+ ]
+ ]
+ },
+ {
+ "input": "⇋",
+ "description": "Named entity: leftrightharpoons; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21cb"
+ ]
+ ]
+ },
+ {
+ "input": "&leftrightsquigarrow",
+ "description": "Bad named entity: leftrightsquigarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&leftrightsquigarrow"
+ ]
+ ]
+ },
+ {
+ "input": "↭",
+ "description": "Named entity: leftrightsquigarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21ad"
+ ]
+ ]
+ },
+ {
+ "input": "&leftthreetimes",
+ "description": "Bad named entity: leftthreetimes without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&leftthreetimes"
+ ]
+ ]
+ },
+ {
+ "input": "⋋",
+ "description": "Named entity: leftthreetimes; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22cb"
+ ]
+ ]
+ },
+ {
+ "input": "&leg",
+ "description": "Bad named entity: leg without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&leg"
+ ]
+ ]
+ },
+ {
+ "input": "⋚",
+ "description": "Named entity: leg; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22da"
+ ]
+ ]
+ },
+ {
+ "input": "&leq",
+ "description": "Bad named entity: leq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&leq"
+ ]
+ ]
+ },
+ {
+ "input": "≤",
+ "description": "Named entity: leq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2264"
+ ]
+ ]
+ },
+ {
+ "input": "&leqq",
+ "description": "Bad named entity: leqq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&leqq"
+ ]
+ ]
+ },
+ {
+ "input": "≦",
+ "description": "Named entity: leqq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2266"
+ ]
+ ]
+ },
+ {
+ "input": "&leqslant",
+ "description": "Bad named entity: leqslant without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&leqslant"
+ ]
+ ]
+ },
+ {
+ "input": "⩽",
+ "description": "Named entity: leqslant; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a7d"
+ ]
+ ]
+ },
+ {
+ "input": "&les",
+ "description": "Bad named entity: les without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&les"
+ ]
+ ]
+ },
+ {
+ "input": "⩽",
+ "description": "Named entity: les; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a7d"
+ ]
+ ]
+ },
+ {
+ "input": "&lescc",
+ "description": "Bad named entity: lescc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lescc"
+ ]
+ ]
+ },
+ {
+ "input": "⪨",
+ "description": "Named entity: lescc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2aa8"
+ ]
+ ]
+ },
+ {
+ "input": "&lesdot",
+ "description": "Bad named entity: lesdot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lesdot"
+ ]
+ ]
+ },
+ {
+ "input": "⩿",
+ "description": "Named entity: lesdot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a7f"
+ ]
+ ]
+ },
+ {
+ "input": "&lesdoto",
+ "description": "Bad named entity: lesdoto without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lesdoto"
+ ]
+ ]
+ },
+ {
+ "input": "⪁",
+ "description": "Named entity: lesdoto; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a81"
+ ]
+ ]
+ },
+ {
+ "input": "&lesdotor",
+ "description": "Bad named entity: lesdotor without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lesdotor"
+ ]
+ ]
+ },
+ {
+ "input": "⪃",
+ "description": "Named entity: lesdotor; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a83"
+ ]
+ ]
+ },
+ {
+ "input": "&lesg",
+ "description": "Bad named entity: lesg without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lesg"
+ ]
+ ]
+ },
+ {
+ "input": "⋚︀",
+ "description": "Named entity: lesg; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22da\ufe00"
+ ]
+ ]
+ },
+ {
+ "input": "&lesges",
+ "description": "Bad named entity: lesges without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lesges"
+ ]
+ ]
+ },
+ {
+ "input": "⪓",
+ "description": "Named entity: lesges; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a93"
+ ]
+ ]
+ },
+ {
+ "input": "&lessapprox",
+ "description": "Bad named entity: lessapprox without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lessapprox"
+ ]
+ ]
+ },
+ {
+ "input": "⪅",
+ "description": "Named entity: lessapprox; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a85"
+ ]
+ ]
+ },
+ {
+ "input": "&lessdot",
+ "description": "Bad named entity: lessdot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lessdot"
+ ]
+ ]
+ },
+ {
+ "input": "⋖",
+ "description": "Named entity: lessdot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22d6"
+ ]
+ ]
+ },
+ {
+ "input": "&lesseqgtr",
+ "description": "Bad named entity: lesseqgtr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lesseqgtr"
+ ]
+ ]
+ },
+ {
+ "input": "⋚",
+ "description": "Named entity: lesseqgtr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22da"
+ ]
+ ]
+ },
+ {
+ "input": "&lesseqqgtr",
+ "description": "Bad named entity: lesseqqgtr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lesseqqgtr"
+ ]
+ ]
+ },
+ {
+ "input": "⪋",
+ "description": "Named entity: lesseqqgtr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a8b"
+ ]
+ ]
+ },
+ {
+ "input": "&lessgtr",
+ "description": "Bad named entity: lessgtr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lessgtr"
+ ]
+ ]
+ },
+ {
+ "input": "≶",
+ "description": "Named entity: lessgtr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2276"
+ ]
+ ]
+ },
+ {
+ "input": "&lesssim",
+ "description": "Bad named entity: lesssim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lesssim"
+ ]
+ ]
+ },
+ {
+ "input": "≲",
+ "description": "Named entity: lesssim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2272"
+ ]
+ ]
+ },
+ {
+ "input": "&lfisht",
+ "description": "Bad named entity: lfisht without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lfisht"
+ ]
+ ]
+ },
+ {
+ "input": "⥼",
+ "description": "Named entity: lfisht; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u297c"
+ ]
+ ]
+ },
+ {
+ "input": "&lfloor",
+ "description": "Bad named entity: lfloor without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lfloor"
+ ]
+ ]
+ },
+ {
+ "input": "⌊",
+ "description": "Named entity: lfloor; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u230a"
+ ]
+ ]
+ },
+ {
+ "input": "&lfr",
+ "description": "Bad named entity: lfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔩",
+ "description": "Named entity: lfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd29"
+ ]
+ ]
+ },
+ {
+ "input": "&lg",
+ "description": "Bad named entity: lg without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lg"
+ ]
+ ]
+ },
+ {
+ "input": "≶",
+ "description": "Named entity: lg; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2276"
+ ]
+ ]
+ },
+ {
+ "input": "&lgE",
+ "description": "Bad named entity: lgE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lgE"
+ ]
+ ]
+ },
+ {
+ "input": "⪑",
+ "description": "Named entity: lgE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a91"
+ ]
+ ]
+ },
+ {
+ "input": "&lhard",
+ "description": "Bad named entity: lhard without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lhard"
+ ]
+ ]
+ },
+ {
+ "input": "↽",
+ "description": "Named entity: lhard; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21bd"
+ ]
+ ]
+ },
+ {
+ "input": "&lharu",
+ "description": "Bad named entity: lharu without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lharu"
+ ]
+ ]
+ },
+ {
+ "input": "↼",
+ "description": "Named entity: lharu; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21bc"
+ ]
+ ]
+ },
+ {
+ "input": "&lharul",
+ "description": "Bad named entity: lharul without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lharul"
+ ]
+ ]
+ },
+ {
+ "input": "⥪",
+ "description": "Named entity: lharul; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u296a"
+ ]
+ ]
+ },
+ {
+ "input": "&lhblk",
+ "description": "Bad named entity: lhblk without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lhblk"
+ ]
+ ]
+ },
+ {
+ "input": "▄",
+ "description": "Named entity: lhblk; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2584"
+ ]
+ ]
+ },
+ {
+ "input": "&ljcy",
+ "description": "Bad named entity: ljcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ljcy"
+ ]
+ ]
+ },
+ {
+ "input": "љ",
+ "description": "Named entity: ljcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0459"
+ ]
+ ]
+ },
+ {
+ "input": "&ll",
+ "description": "Bad named entity: ll without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ll"
+ ]
+ ]
+ },
+ {
+ "input": "≪",
+ "description": "Named entity: ll; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u226a"
+ ]
+ ]
+ },
+ {
+ "input": "&llarr",
+ "description": "Bad named entity: llarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&llarr"
+ ]
+ ]
+ },
+ {
+ "input": "⇇",
+ "description": "Named entity: llarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21c7"
+ ]
+ ]
+ },
+ {
+ "input": "&llcorner",
+ "description": "Bad named entity: llcorner without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&llcorner"
+ ]
+ ]
+ },
+ {
+ "input": "⌞",
+ "description": "Named entity: llcorner; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u231e"
+ ]
+ ]
+ },
+ {
+ "input": "&llhard",
+ "description": "Bad named entity: llhard without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&llhard"
+ ]
+ ]
+ },
+ {
+ "input": "⥫",
+ "description": "Named entity: llhard; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u296b"
+ ]
+ ]
+ },
+ {
+ "input": "&lltri",
+ "description": "Bad named entity: lltri without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lltri"
+ ]
+ ]
+ },
+ {
+ "input": "◺",
+ "description": "Named entity: lltri; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25fa"
+ ]
+ ]
+ },
+ {
+ "input": "&lmidot",
+ "description": "Bad named entity: lmidot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lmidot"
+ ]
+ ]
+ },
+ {
+ "input": "ŀ",
+ "description": "Named entity: lmidot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0140"
+ ]
+ ]
+ },
+ {
+ "input": "&lmoust",
+ "description": "Bad named entity: lmoust without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lmoust"
+ ]
+ ]
+ },
+ {
+ "input": "⎰",
+ "description": "Named entity: lmoust; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u23b0"
+ ]
+ ]
+ },
+ {
+ "input": "&lmoustache",
+ "description": "Bad named entity: lmoustache without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lmoustache"
+ ]
+ ]
+ },
+ {
+ "input": "⎰",
+ "description": "Named entity: lmoustache; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u23b0"
+ ]
+ ]
+ },
+ {
+ "input": "&lnE",
+ "description": "Bad named entity: lnE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lnE"
+ ]
+ ]
+ },
+ {
+ "input": "≨",
+ "description": "Named entity: lnE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2268"
+ ]
+ ]
+ },
+ {
+ "input": "&lnap",
+ "description": "Bad named entity: lnap without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lnap"
+ ]
+ ]
+ },
+ {
+ "input": "⪉",
+ "description": "Named entity: lnap; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a89"
+ ]
+ ]
+ },
+ {
+ "input": "&lnapprox",
+ "description": "Bad named entity: lnapprox without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lnapprox"
+ ]
+ ]
+ },
+ {
+ "input": "⪉",
+ "description": "Named entity: lnapprox; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a89"
+ ]
+ ]
+ },
+ {
+ "input": "&lne",
+ "description": "Bad named entity: lne without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lne"
+ ]
+ ]
+ },
+ {
+ "input": "⪇",
+ "description": "Named entity: lne; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a87"
+ ]
+ ]
+ },
+ {
+ "input": "&lneq",
+ "description": "Bad named entity: lneq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lneq"
+ ]
+ ]
+ },
+ {
+ "input": "⪇",
+ "description": "Named entity: lneq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a87"
+ ]
+ ]
+ },
+ {
+ "input": "&lneqq",
+ "description": "Bad named entity: lneqq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lneqq"
+ ]
+ ]
+ },
+ {
+ "input": "≨",
+ "description": "Named entity: lneqq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2268"
+ ]
+ ]
+ },
+ {
+ "input": "&lnsim",
+ "description": "Bad named entity: lnsim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lnsim"
+ ]
+ ]
+ },
+ {
+ "input": "⋦",
+ "description": "Named entity: lnsim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22e6"
+ ]
+ ]
+ },
+ {
+ "input": "&loang",
+ "description": "Bad named entity: loang without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&loang"
+ ]
+ ]
+ },
+ {
+ "input": "⟬",
+ "description": "Named entity: loang; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27ec"
+ ]
+ ]
+ },
+ {
+ "input": "&loarr",
+ "description": "Bad named entity: loarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&loarr"
+ ]
+ ]
+ },
+ {
+ "input": "⇽",
+ "description": "Named entity: loarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21fd"
+ ]
+ ]
+ },
+ {
+ "input": "&lobrk",
+ "description": "Bad named entity: lobrk without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lobrk"
+ ]
+ ]
+ },
+ {
+ "input": "⟦",
+ "description": "Named entity: lobrk; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27e6"
+ ]
+ ]
+ },
+ {
+ "input": "&longleftarrow",
+ "description": "Bad named entity: longleftarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&longleftarrow"
+ ]
+ ]
+ },
+ {
+ "input": "⟵",
+ "description": "Named entity: longleftarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27f5"
+ ]
+ ]
+ },
+ {
+ "input": "&longleftrightarrow",
+ "description": "Bad named entity: longleftrightarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&longleftrightarrow"
+ ]
+ ]
+ },
+ {
+ "input": "⟷",
+ "description": "Named entity: longleftrightarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27f7"
+ ]
+ ]
+ },
+ {
+ "input": "&longmapsto",
+ "description": "Bad named entity: longmapsto without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&longmapsto"
+ ]
+ ]
+ },
+ {
+ "input": "⟼",
+ "description": "Named entity: longmapsto; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27fc"
+ ]
+ ]
+ },
+ {
+ "input": "&longrightarrow",
+ "description": "Bad named entity: longrightarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&longrightarrow"
+ ]
+ ]
+ },
+ {
+ "input": "⟶",
+ "description": "Named entity: longrightarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27f6"
+ ]
+ ]
+ },
+ {
+ "input": "&looparrowleft",
+ "description": "Bad named entity: looparrowleft without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&looparrowleft"
+ ]
+ ]
+ },
+ {
+ "input": "↫",
+ "description": "Named entity: looparrowleft; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21ab"
+ ]
+ ]
+ },
+ {
+ "input": "&looparrowright",
+ "description": "Bad named entity: looparrowright without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&looparrowright"
+ ]
+ ]
+ },
+ {
+ "input": "↬",
+ "description": "Named entity: looparrowright; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21ac"
+ ]
+ ]
+ },
+ {
+ "input": "&lopar",
+ "description": "Bad named entity: lopar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lopar"
+ ]
+ ]
+ },
+ {
+ "input": "⦅",
+ "description": "Named entity: lopar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2985"
+ ]
+ ]
+ },
+ {
+ "input": "&lopf",
+ "description": "Bad named entity: lopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕝",
+ "description": "Named entity: lopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd5d"
+ ]
+ ]
+ },
+ {
+ "input": "&loplus",
+ "description": "Bad named entity: loplus without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&loplus"
+ ]
+ ]
+ },
+ {
+ "input": "⨭",
+ "description": "Named entity: loplus; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a2d"
+ ]
+ ]
+ },
+ {
+ "input": "&lotimes",
+ "description": "Bad named entity: lotimes without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lotimes"
+ ]
+ ]
+ },
+ {
+ "input": "⨴",
+ "description": "Named entity: lotimes; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a34"
+ ]
+ ]
+ },
+ {
+ "input": "&lowast",
+ "description": "Bad named entity: lowast without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lowast"
+ ]
+ ]
+ },
+ {
+ "input": "∗",
+ "description": "Named entity: lowast; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2217"
+ ]
+ ]
+ },
+ {
+ "input": "&lowbar",
+ "description": "Bad named entity: lowbar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lowbar"
+ ]
+ ]
+ },
+ {
+ "input": "_",
+ "description": "Named entity: lowbar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "_"
+ ]
+ ]
+ },
+ {
+ "input": "&loz",
+ "description": "Bad named entity: loz without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&loz"
+ ]
+ ]
+ },
+ {
+ "input": "◊",
+ "description": "Named entity: loz; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25ca"
+ ]
+ ]
+ },
+ {
+ "input": "&lozenge",
+ "description": "Bad named entity: lozenge without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lozenge"
+ ]
+ ]
+ },
+ {
+ "input": "◊",
+ "description": "Named entity: lozenge; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25ca"
+ ]
+ ]
+ },
+ {
+ "input": "&lozf",
+ "description": "Bad named entity: lozf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lozf"
+ ]
+ ]
+ },
+ {
+ "input": "⧫",
+ "description": "Named entity: lozf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29eb"
+ ]
+ ]
+ },
+ {
+ "input": "&lpar",
+ "description": "Bad named entity: lpar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lpar"
+ ]
+ ]
+ },
+ {
+ "input": "(",
+ "description": "Named entity: lpar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "("
+ ]
+ ]
+ },
+ {
+ "input": "&lparlt",
+ "description": "Bad named entity: lparlt without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lparlt"
+ ]
+ ]
+ },
+ {
+ "input": "⦓",
+ "description": "Named entity: lparlt; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2993"
+ ]
+ ]
+ },
+ {
+ "input": "&lrarr",
+ "description": "Bad named entity: lrarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lrarr"
+ ]
+ ]
+ },
+ {
+ "input": "⇆",
+ "description": "Named entity: lrarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21c6"
+ ]
+ ]
+ },
+ {
+ "input": "&lrcorner",
+ "description": "Bad named entity: lrcorner without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lrcorner"
+ ]
+ ]
+ },
+ {
+ "input": "⌟",
+ "description": "Named entity: lrcorner; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u231f"
+ ]
+ ]
+ },
+ {
+ "input": "&lrhar",
+ "description": "Bad named entity: lrhar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lrhar"
+ ]
+ ]
+ },
+ {
+ "input": "⇋",
+ "description": "Named entity: lrhar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21cb"
+ ]
+ ]
+ },
+ {
+ "input": "&lrhard",
+ "description": "Bad named entity: lrhard without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lrhard"
+ ]
+ ]
+ },
+ {
+ "input": "⥭",
+ "description": "Named entity: lrhard; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u296d"
+ ]
+ ]
+ },
+ {
+ "input": "&lrm",
+ "description": "Bad named entity: lrm without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lrm"
+ ]
+ ]
+ },
+ {
+ "input": "‎",
+ "description": "Named entity: lrm; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u200e"
+ ]
+ ]
+ },
+ {
+ "input": "&lrtri",
+ "description": "Bad named entity: lrtri without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lrtri"
+ ]
+ ]
+ },
+ {
+ "input": "⊿",
+ "description": "Named entity: lrtri; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22bf"
+ ]
+ ]
+ },
+ {
+ "input": "&lsaquo",
+ "description": "Bad named entity: lsaquo without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lsaquo"
+ ]
+ ]
+ },
+ {
+ "input": "‹",
+ "description": "Named entity: lsaquo; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2039"
+ ]
+ ]
+ },
+ {
+ "input": "&lscr",
+ "description": "Bad named entity: lscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝓁",
+ "description": "Named entity: lscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udcc1"
+ ]
+ ]
+ },
+ {
+ "input": "&lsh",
+ "description": "Bad named entity: lsh without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lsh"
+ ]
+ ]
+ },
+ {
+ "input": "↰",
+ "description": "Named entity: lsh; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21b0"
+ ]
+ ]
+ },
+ {
+ "input": "&lsim",
+ "description": "Bad named entity: lsim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lsim"
+ ]
+ ]
+ },
+ {
+ "input": "≲",
+ "description": "Named entity: lsim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2272"
+ ]
+ ]
+ },
+ {
+ "input": "&lsime",
+ "description": "Bad named entity: lsime without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lsime"
+ ]
+ ]
+ },
+ {
+ "input": "⪍",
+ "description": "Named entity: lsime; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a8d"
+ ]
+ ]
+ },
+ {
+ "input": "&lsimg",
+ "description": "Bad named entity: lsimg without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lsimg"
+ ]
+ ]
+ },
+ {
+ "input": "⪏",
+ "description": "Named entity: lsimg; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a8f"
+ ]
+ ]
+ },
+ {
+ "input": "&lsqb",
+ "description": "Bad named entity: lsqb without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lsqb"
+ ]
+ ]
+ },
+ {
+ "input": "[",
+ "description": "Named entity: lsqb; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "["
+ ]
+ ]
+ },
+ {
+ "input": "&lsquo",
+ "description": "Bad named entity: lsquo without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lsquo"
+ ]
+ ]
+ },
+ {
+ "input": "‘",
+ "description": "Named entity: lsquo; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2018"
+ ]
+ ]
+ },
+ {
+ "input": "&lsquor",
+ "description": "Bad named entity: lsquor without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lsquor"
+ ]
+ ]
+ },
+ {
+ "input": "‚",
+ "description": "Named entity: lsquor; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u201a"
+ ]
+ ]
+ },
+ {
+ "input": "&lstrok",
+ "description": "Bad named entity: lstrok without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lstrok"
+ ]
+ ]
+ },
+ {
+ "input": "ł",
+ "description": "Named entity: lstrok; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0142"
+ ]
+ ]
+ },
+ {
+ "input": "<",
+ "description": "Named entity: lt without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "<"
+ ]
+ ]
+ },
+ {
+ "input": "<",
+ "description": "Named entity: lt; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "<"
+ ]
+ ]
+ },
+ {
+ "input": "⪦",
+ "description": "Named entity: ltcc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2aa6"
+ ]
+ ]
+ },
+ {
+ "input": "⩹",
+ "description": "Named entity: ltcir; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a79"
+ ]
+ ]
+ },
+ {
+ "input": "⋖",
+ "description": "Named entity: ltdot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22d6"
+ ]
+ ]
+ },
+ {
+ "input": "⋋",
+ "description": "Named entity: lthree; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22cb"
+ ]
+ ]
+ },
+ {
+ "input": "⋉",
+ "description": "Named entity: ltimes; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22c9"
+ ]
+ ]
+ },
+ {
+ "input": "⥶",
+ "description": "Named entity: ltlarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2976"
+ ]
+ ]
+ },
+ {
+ "input": "⩻",
+ "description": "Named entity: ltquest; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a7b"
+ ]
+ ]
+ },
+ {
+ "input": "⦖",
+ "description": "Named entity: ltrPar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2996"
+ ]
+ ]
+ },
+ {
+ "input": "◃",
+ "description": "Named entity: ltri; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25c3"
+ ]
+ ]
+ },
+ {
+ "input": "⊴",
+ "description": "Named entity: ltrie; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22b4"
+ ]
+ ]
+ },
+ {
+ "input": "◂",
+ "description": "Named entity: ltrif; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25c2"
+ ]
+ ]
+ },
+ {
+ "input": "&lurdshar",
+ "description": "Bad named entity: lurdshar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lurdshar"
+ ]
+ ]
+ },
+ {
+ "input": "⥊",
+ "description": "Named entity: lurdshar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u294a"
+ ]
+ ]
+ },
+ {
+ "input": "&luruhar",
+ "description": "Bad named entity: luruhar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&luruhar"
+ ]
+ ]
+ },
+ {
+ "input": "⥦",
+ "description": "Named entity: luruhar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2966"
+ ]
+ ]
+ },
+ {
+ "input": "&lvertneqq",
+ "description": "Bad named entity: lvertneqq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lvertneqq"
+ ]
+ ]
+ },
+ {
+ "input": "≨︀",
+ "description": "Named entity: lvertneqq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2268\ufe00"
+ ]
+ ]
+ },
+ {
+ "input": "&lvnE",
+ "description": "Bad named entity: lvnE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&lvnE"
+ ]
+ ]
+ },
+ {
+ "input": "≨︀",
+ "description": "Named entity: lvnE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2268\ufe00"
+ ]
+ ]
+ },
+ {
+ "input": "&mDDot",
+ "description": "Bad named entity: mDDot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&mDDot"
+ ]
+ ]
+ },
+ {
+ "input": "∺",
+ "description": "Named entity: mDDot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u223a"
+ ]
+ ]
+ },
+ {
+ "input": "¯",
+ "description": "Named entity: macr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00af"
+ ]
+ ]
+ },
+ {
+ "input": "¯",
+ "description": "Named entity: macr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00af"
+ ]
+ ]
+ },
+ {
+ "input": "&male",
+ "description": "Bad named entity: male without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&male"
+ ]
+ ]
+ },
+ {
+ "input": "♂",
+ "description": "Named entity: male; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2642"
+ ]
+ ]
+ },
+ {
+ "input": "&malt",
+ "description": "Bad named entity: malt without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&malt"
+ ]
+ ]
+ },
+ {
+ "input": "✠",
+ "description": "Named entity: malt; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2720"
+ ]
+ ]
+ },
+ {
+ "input": "&maltese",
+ "description": "Bad named entity: maltese without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&maltese"
+ ]
+ ]
+ },
+ {
+ "input": "✠",
+ "description": "Named entity: maltese; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2720"
+ ]
+ ]
+ },
+ {
+ "input": "&map",
+ "description": "Bad named entity: map without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&map"
+ ]
+ ]
+ },
+ {
+ "input": "↦",
+ "description": "Named entity: map; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21a6"
+ ]
+ ]
+ },
+ {
+ "input": "&mapsto",
+ "description": "Bad named entity: mapsto without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&mapsto"
+ ]
+ ]
+ },
+ {
+ "input": "↦",
+ "description": "Named entity: mapsto; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21a6"
+ ]
+ ]
+ },
+ {
+ "input": "&mapstodown",
+ "description": "Bad named entity: mapstodown without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&mapstodown"
+ ]
+ ]
+ },
+ {
+ "input": "↧",
+ "description": "Named entity: mapstodown; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21a7"
+ ]
+ ]
+ },
+ {
+ "input": "&mapstoleft",
+ "description": "Bad named entity: mapstoleft without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&mapstoleft"
+ ]
+ ]
+ },
+ {
+ "input": "↤",
+ "description": "Named entity: mapstoleft; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21a4"
+ ]
+ ]
+ },
+ {
+ "input": "&mapstoup",
+ "description": "Bad named entity: mapstoup without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&mapstoup"
+ ]
+ ]
+ },
+ {
+ "input": "↥",
+ "description": "Named entity: mapstoup; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21a5"
+ ]
+ ]
+ },
+ {
+ "input": "&marker",
+ "description": "Bad named entity: marker without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&marker"
+ ]
+ ]
+ },
+ {
+ "input": "▮",
+ "description": "Named entity: marker; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25ae"
+ ]
+ ]
+ },
+ {
+ "input": "&mcomma",
+ "description": "Bad named entity: mcomma without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&mcomma"
+ ]
+ ]
+ },
+ {
+ "input": "⨩",
+ "description": "Named entity: mcomma; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a29"
+ ]
+ ]
+ },
+ {
+ "input": "&mcy",
+ "description": "Bad named entity: mcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&mcy"
+ ]
+ ]
+ },
+ {
+ "input": "м",
+ "description": "Named entity: mcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u043c"
+ ]
+ ]
+ },
+ {
+ "input": "&mdash",
+ "description": "Bad named entity: mdash without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&mdash"
+ ]
+ ]
+ },
+ {
+ "input": "—",
+ "description": "Named entity: mdash; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2014"
+ ]
+ ]
+ },
+ {
+ "input": "&measuredangle",
+ "description": "Bad named entity: measuredangle without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&measuredangle"
+ ]
+ ]
+ },
+ {
+ "input": "∡",
+ "description": "Named entity: measuredangle; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2221"
+ ]
+ ]
+ },
+ {
+ "input": "&mfr",
+ "description": "Bad named entity: mfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&mfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔪",
+ "description": "Named entity: mfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd2a"
+ ]
+ ]
+ },
+ {
+ "input": "&mho",
+ "description": "Bad named entity: mho without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&mho"
+ ]
+ ]
+ },
+ {
+ "input": "℧",
+ "description": "Named entity: mho; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2127"
+ ]
+ ]
+ },
+ {
+ "input": "µ",
+ "description": "Named entity: micro without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00b5"
+ ]
+ ]
+ },
+ {
+ "input": "µ",
+ "description": "Named entity: micro; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00b5"
+ ]
+ ]
+ },
+ {
+ "input": "&mid",
+ "description": "Bad named entity: mid without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&mid"
+ ]
+ ]
+ },
+ {
+ "input": "∣",
+ "description": "Named entity: mid; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2223"
+ ]
+ ]
+ },
+ {
+ "input": "&midast",
+ "description": "Bad named entity: midast without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&midast"
+ ]
+ ]
+ },
+ {
+ "input": "*",
+ "description": "Named entity: midast; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "*"
+ ]
+ ]
+ },
+ {
+ "input": "&midcir",
+ "description": "Bad named entity: midcir without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&midcir"
+ ]
+ ]
+ },
+ {
+ "input": "⫰",
+ "description": "Named entity: midcir; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2af0"
+ ]
+ ]
+ },
+ {
+ "input": "·",
+ "description": "Named entity: middot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00b7"
+ ]
+ ]
+ },
+ {
+ "input": "·",
+ "description": "Named entity: middot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00b7"
+ ]
+ ]
+ },
+ {
+ "input": "&minus",
+ "description": "Bad named entity: minus without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&minus"
+ ]
+ ]
+ },
+ {
+ "input": "−",
+ "description": "Named entity: minus; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2212"
+ ]
+ ]
+ },
+ {
+ "input": "&minusb",
+ "description": "Bad named entity: minusb without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&minusb"
+ ]
+ ]
+ },
+ {
+ "input": "⊟",
+ "description": "Named entity: minusb; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u229f"
+ ]
+ ]
+ },
+ {
+ "input": "&minusd",
+ "description": "Bad named entity: minusd without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&minusd"
+ ]
+ ]
+ },
+ {
+ "input": "∸",
+ "description": "Named entity: minusd; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2238"
+ ]
+ ]
+ },
+ {
+ "input": "&minusdu",
+ "description": "Bad named entity: minusdu without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&minusdu"
+ ]
+ ]
+ },
+ {
+ "input": "⨪",
+ "description": "Named entity: minusdu; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a2a"
+ ]
+ ]
+ },
+ {
+ "input": "&mlcp",
+ "description": "Bad named entity: mlcp without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&mlcp"
+ ]
+ ]
+ },
+ {
+ "input": "⫛",
+ "description": "Named entity: mlcp; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2adb"
+ ]
+ ]
+ },
+ {
+ "input": "&mldr",
+ "description": "Bad named entity: mldr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&mldr"
+ ]
+ ]
+ },
+ {
+ "input": "…",
+ "description": "Named entity: mldr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2026"
+ ]
+ ]
+ },
+ {
+ "input": "&mnplus",
+ "description": "Bad named entity: mnplus without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&mnplus"
+ ]
+ ]
+ },
+ {
+ "input": "∓",
+ "description": "Named entity: mnplus; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2213"
+ ]
+ ]
+ },
+ {
+ "input": "&models",
+ "description": "Bad named entity: models without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&models"
+ ]
+ ]
+ },
+ {
+ "input": "⊧",
+ "description": "Named entity: models; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22a7"
+ ]
+ ]
+ },
+ {
+ "input": "&mopf",
+ "description": "Bad named entity: mopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&mopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕞",
+ "description": "Named entity: mopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd5e"
+ ]
+ ]
+ },
+ {
+ "input": "&mp",
+ "description": "Bad named entity: mp without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&mp"
+ ]
+ ]
+ },
+ {
+ "input": "∓",
+ "description": "Named entity: mp; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2213"
+ ]
+ ]
+ },
+ {
+ "input": "&mscr",
+ "description": "Bad named entity: mscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&mscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝓂",
+ "description": "Named entity: mscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udcc2"
+ ]
+ ]
+ },
+ {
+ "input": "&mstpos",
+ "description": "Bad named entity: mstpos without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&mstpos"
+ ]
+ ]
+ },
+ {
+ "input": "∾",
+ "description": "Named entity: mstpos; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u223e"
+ ]
+ ]
+ },
+ {
+ "input": "&mu",
+ "description": "Bad named entity: mu without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&mu"
+ ]
+ ]
+ },
+ {
+ "input": "μ",
+ "description": "Named entity: mu; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03bc"
+ ]
+ ]
+ },
+ {
+ "input": "&multimap",
+ "description": "Bad named entity: multimap without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&multimap"
+ ]
+ ]
+ },
+ {
+ "input": "⊸",
+ "description": "Named entity: multimap; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22b8"
+ ]
+ ]
+ },
+ {
+ "input": "&mumap",
+ "description": "Bad named entity: mumap without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&mumap"
+ ]
+ ]
+ },
+ {
+ "input": "⊸",
+ "description": "Named entity: mumap; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22b8"
+ ]
+ ]
+ },
+ {
+ "input": "&nGg",
+ "description": "Bad named entity: nGg without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nGg"
+ ]
+ ]
+ },
+ {
+ "input": "⋙̸",
+ "description": "Named entity: nGg; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22d9\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&nGt",
+ "description": "Bad named entity: nGt without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nGt"
+ ]
+ ]
+ },
+ {
+ "input": "≫⃒",
+ "description": "Named entity: nGt; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u226b\u20d2"
+ ]
+ ]
+ },
+ {
+ "input": "&nGtv",
+ "description": "Bad named entity: nGtv without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nGtv"
+ ]
+ ]
+ },
+ {
+ "input": "≫̸",
+ "description": "Named entity: nGtv; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u226b\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&nLeftarrow",
+ "description": "Bad named entity: nLeftarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nLeftarrow"
+ ]
+ ]
+ },
+ {
+ "input": "⇍",
+ "description": "Named entity: nLeftarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21cd"
+ ]
+ ]
+ },
+ {
+ "input": "&nLeftrightarrow",
+ "description": "Bad named entity: nLeftrightarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nLeftrightarrow"
+ ]
+ ]
+ },
+ {
+ "input": "⇎",
+ "description": "Named entity: nLeftrightarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21ce"
+ ]
+ ]
+ },
+ {
+ "input": "&nLl",
+ "description": "Bad named entity: nLl without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nLl"
+ ]
+ ]
+ },
+ {
+ "input": "⋘̸",
+ "description": "Named entity: nLl; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22d8\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&nLt",
+ "description": "Bad named entity: nLt without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nLt"
+ ]
+ ]
+ },
+ {
+ "input": "≪⃒",
+ "description": "Named entity: nLt; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u226a\u20d2"
+ ]
+ ]
+ },
+ {
+ "input": "&nLtv",
+ "description": "Bad named entity: nLtv without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nLtv"
+ ]
+ ]
+ },
+ {
+ "input": "≪̸",
+ "description": "Named entity: nLtv; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u226a\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&nRightarrow",
+ "description": "Bad named entity: nRightarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nRightarrow"
+ ]
+ ]
+ },
+ {
+ "input": "⇏",
+ "description": "Named entity: nRightarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21cf"
+ ]
+ ]
+ },
+ {
+ "input": "&nVDash",
+ "description": "Bad named entity: nVDash without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nVDash"
+ ]
+ ]
+ },
+ {
+ "input": "⊯",
+ "description": "Named entity: nVDash; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22af"
+ ]
+ ]
+ },
+ {
+ "input": "&nVdash",
+ "description": "Bad named entity: nVdash without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nVdash"
+ ]
+ ]
+ },
+ {
+ "input": "⊮",
+ "description": "Named entity: nVdash; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22ae"
+ ]
+ ]
+ },
+ {
+ "input": "&nabla",
+ "description": "Bad named entity: nabla without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nabla"
+ ]
+ ]
+ },
+ {
+ "input": "∇",
+ "description": "Named entity: nabla; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2207"
+ ]
+ ]
+ },
+ {
+ "input": "&nacute",
+ "description": "Bad named entity: nacute without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nacute"
+ ]
+ ]
+ },
+ {
+ "input": "ń",
+ "description": "Named entity: nacute; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0144"
+ ]
+ ]
+ },
+ {
+ "input": "&nang",
+ "description": "Bad named entity: nang without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nang"
+ ]
+ ]
+ },
+ {
+ "input": "∠⃒",
+ "description": "Named entity: nang; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2220\u20d2"
+ ]
+ ]
+ },
+ {
+ "input": "&nap",
+ "description": "Bad named entity: nap without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nap"
+ ]
+ ]
+ },
+ {
+ "input": "≉",
+ "description": "Named entity: nap; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2249"
+ ]
+ ]
+ },
+ {
+ "input": "&napE",
+ "description": "Bad named entity: napE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&napE"
+ ]
+ ]
+ },
+ {
+ "input": "⩰̸",
+ "description": "Named entity: napE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a70\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&napid",
+ "description": "Bad named entity: napid without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&napid"
+ ]
+ ]
+ },
+ {
+ "input": "≋̸",
+ "description": "Named entity: napid; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u224b\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&napos",
+ "description": "Bad named entity: napos without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&napos"
+ ]
+ ]
+ },
+ {
+ "input": "ʼn",
+ "description": "Named entity: napos; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0149"
+ ]
+ ]
+ },
+ {
+ "input": "&napprox",
+ "description": "Bad named entity: napprox without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&napprox"
+ ]
+ ]
+ },
+ {
+ "input": "≉",
+ "description": "Named entity: napprox; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2249"
+ ]
+ ]
+ },
+ {
+ "input": "&natur",
+ "description": "Bad named entity: natur without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&natur"
+ ]
+ ]
+ },
+ {
+ "input": "♮",
+ "description": "Named entity: natur; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u266e"
+ ]
+ ]
+ },
+ {
+ "input": "&natural",
+ "description": "Bad named entity: natural without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&natural"
+ ]
+ ]
+ },
+ {
+ "input": "♮",
+ "description": "Named entity: natural; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u266e"
+ ]
+ ]
+ },
+ {
+ "input": "&naturals",
+ "description": "Bad named entity: naturals without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&naturals"
+ ]
+ ]
+ },
+ {
+ "input": "ℕ",
+ "description": "Named entity: naturals; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2115"
+ ]
+ ]
+ },
+ {
+ "input": " ",
+ "description": "Named entity: nbsp without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00a0"
+ ]
+ ]
+ },
+ {
+ "input": " ",
+ "description": "Named entity: nbsp; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00a0"
+ ]
+ ]
+ },
+ {
+ "input": "&nbump",
+ "description": "Bad named entity: nbump without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nbump"
+ ]
+ ]
+ },
+ {
+ "input": "≎̸",
+ "description": "Named entity: nbump; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u224e\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&nbumpe",
+ "description": "Bad named entity: nbumpe without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nbumpe"
+ ]
+ ]
+ },
+ {
+ "input": "≏̸",
+ "description": "Named entity: nbumpe; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u224f\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&ncap",
+ "description": "Bad named entity: ncap without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ncap"
+ ]
+ ]
+ },
+ {
+ "input": "⩃",
+ "description": "Named entity: ncap; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a43"
+ ]
+ ]
+ },
+ {
+ "input": "&ncaron",
+ "description": "Bad named entity: ncaron without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ncaron"
+ ]
+ ]
+ },
+ {
+ "input": "ň",
+ "description": "Named entity: ncaron; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0148"
+ ]
+ ]
+ },
+ {
+ "input": "&ncedil",
+ "description": "Bad named entity: ncedil without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ncedil"
+ ]
+ ]
+ },
+ {
+ "input": "ņ",
+ "description": "Named entity: ncedil; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0146"
+ ]
+ ]
+ },
+ {
+ "input": "&ncong",
+ "description": "Bad named entity: ncong without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ncong"
+ ]
+ ]
+ },
+ {
+ "input": "≇",
+ "description": "Named entity: ncong; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2247"
+ ]
+ ]
+ },
+ {
+ "input": "&ncongdot",
+ "description": "Bad named entity: ncongdot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ncongdot"
+ ]
+ ]
+ },
+ {
+ "input": "⩭̸",
+ "description": "Named entity: ncongdot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a6d\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&ncup",
+ "description": "Bad named entity: ncup without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ncup"
+ ]
+ ]
+ },
+ {
+ "input": "⩂",
+ "description": "Named entity: ncup; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a42"
+ ]
+ ]
+ },
+ {
+ "input": "&ncy",
+ "description": "Bad named entity: ncy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ncy"
+ ]
+ ]
+ },
+ {
+ "input": "н",
+ "description": "Named entity: ncy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u043d"
+ ]
+ ]
+ },
+ {
+ "input": "&ndash",
+ "description": "Bad named entity: ndash without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ndash"
+ ]
+ ]
+ },
+ {
+ "input": "–",
+ "description": "Named entity: ndash; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2013"
+ ]
+ ]
+ },
+ {
+ "input": "&ne",
+ "description": "Bad named entity: ne without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ne"
+ ]
+ ]
+ },
+ {
+ "input": "≠",
+ "description": "Named entity: ne; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2260"
+ ]
+ ]
+ },
+ {
+ "input": "&neArr",
+ "description": "Bad named entity: neArr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&neArr"
+ ]
+ ]
+ },
+ {
+ "input": "⇗",
+ "description": "Named entity: neArr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21d7"
+ ]
+ ]
+ },
+ {
+ "input": "&nearhk",
+ "description": "Bad named entity: nearhk without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nearhk"
+ ]
+ ]
+ },
+ {
+ "input": "⤤",
+ "description": "Named entity: nearhk; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2924"
+ ]
+ ]
+ },
+ {
+ "input": "&nearr",
+ "description": "Bad named entity: nearr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nearr"
+ ]
+ ]
+ },
+ {
+ "input": "↗",
+ "description": "Named entity: nearr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2197"
+ ]
+ ]
+ },
+ {
+ "input": "&nearrow",
+ "description": "Bad named entity: nearrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nearrow"
+ ]
+ ]
+ },
+ {
+ "input": "↗",
+ "description": "Named entity: nearrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2197"
+ ]
+ ]
+ },
+ {
+ "input": "&nedot",
+ "description": "Bad named entity: nedot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nedot"
+ ]
+ ]
+ },
+ {
+ "input": "≐̸",
+ "description": "Named entity: nedot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2250\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&nequiv",
+ "description": "Bad named entity: nequiv without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nequiv"
+ ]
+ ]
+ },
+ {
+ "input": "≢",
+ "description": "Named entity: nequiv; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2262"
+ ]
+ ]
+ },
+ {
+ "input": "&nesear",
+ "description": "Bad named entity: nesear without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nesear"
+ ]
+ ]
+ },
+ {
+ "input": "⤨",
+ "description": "Named entity: nesear; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2928"
+ ]
+ ]
+ },
+ {
+ "input": "&nesim",
+ "description": "Bad named entity: nesim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nesim"
+ ]
+ ]
+ },
+ {
+ "input": "≂̸",
+ "description": "Named entity: nesim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2242\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&nexist",
+ "description": "Bad named entity: nexist without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nexist"
+ ]
+ ]
+ },
+ {
+ "input": "∄",
+ "description": "Named entity: nexist; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2204"
+ ]
+ ]
+ },
+ {
+ "input": "&nexists",
+ "description": "Bad named entity: nexists without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nexists"
+ ]
+ ]
+ },
+ {
+ "input": "∄",
+ "description": "Named entity: nexists; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2204"
+ ]
+ ]
+ },
+ {
+ "input": "&nfr",
+ "description": "Bad named entity: nfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔫",
+ "description": "Named entity: nfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd2b"
+ ]
+ ]
+ },
+ {
+ "input": "&ngE",
+ "description": "Bad named entity: ngE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ngE"
+ ]
+ ]
+ },
+ {
+ "input": "≧̸",
+ "description": "Named entity: ngE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2267\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&nge",
+ "description": "Bad named entity: nge without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nge"
+ ]
+ ]
+ },
+ {
+ "input": "≱",
+ "description": "Named entity: nge; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2271"
+ ]
+ ]
+ },
+ {
+ "input": "&ngeq",
+ "description": "Bad named entity: ngeq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ngeq"
+ ]
+ ]
+ },
+ {
+ "input": "≱",
+ "description": "Named entity: ngeq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2271"
+ ]
+ ]
+ },
+ {
+ "input": "&ngeqq",
+ "description": "Bad named entity: ngeqq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ngeqq"
+ ]
+ ]
+ },
+ {
+ "input": "≧̸",
+ "description": "Named entity: ngeqq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2267\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&ngeqslant",
+ "description": "Bad named entity: ngeqslant without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ngeqslant"
+ ]
+ ]
+ },
+ {
+ "input": "⩾̸",
+ "description": "Named entity: ngeqslant; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a7e\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&nges",
+ "description": "Bad named entity: nges without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nges"
+ ]
+ ]
+ },
+ {
+ "input": "⩾̸",
+ "description": "Named entity: nges; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a7e\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&ngsim",
+ "description": "Bad named entity: ngsim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ngsim"
+ ]
+ ]
+ },
+ {
+ "input": "≵",
+ "description": "Named entity: ngsim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2275"
+ ]
+ ]
+ },
+ {
+ "input": "&ngt",
+ "description": "Bad named entity: ngt without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ngt"
+ ]
+ ]
+ },
+ {
+ "input": "≯",
+ "description": "Named entity: ngt; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u226f"
+ ]
+ ]
+ },
+ {
+ "input": "&ngtr",
+ "description": "Bad named entity: ngtr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ngtr"
+ ]
+ ]
+ },
+ {
+ "input": "≯",
+ "description": "Named entity: ngtr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u226f"
+ ]
+ ]
+ },
+ {
+ "input": "&nhArr",
+ "description": "Bad named entity: nhArr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nhArr"
+ ]
+ ]
+ },
+ {
+ "input": "⇎",
+ "description": "Named entity: nhArr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21ce"
+ ]
+ ]
+ },
+ {
+ "input": "&nharr",
+ "description": "Bad named entity: nharr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nharr"
+ ]
+ ]
+ },
+ {
+ "input": "↮",
+ "description": "Named entity: nharr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21ae"
+ ]
+ ]
+ },
+ {
+ "input": "&nhpar",
+ "description": "Bad named entity: nhpar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nhpar"
+ ]
+ ]
+ },
+ {
+ "input": "⫲",
+ "description": "Named entity: nhpar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2af2"
+ ]
+ ]
+ },
+ {
+ "input": "&ni",
+ "description": "Bad named entity: ni without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ni"
+ ]
+ ]
+ },
+ {
+ "input": "∋",
+ "description": "Named entity: ni; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u220b"
+ ]
+ ]
+ },
+ {
+ "input": "&nis",
+ "description": "Bad named entity: nis without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nis"
+ ]
+ ]
+ },
+ {
+ "input": "⋼",
+ "description": "Named entity: nis; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22fc"
+ ]
+ ]
+ },
+ {
+ "input": "&nisd",
+ "description": "Bad named entity: nisd without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nisd"
+ ]
+ ]
+ },
+ {
+ "input": "⋺",
+ "description": "Named entity: nisd; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22fa"
+ ]
+ ]
+ },
+ {
+ "input": "&niv",
+ "description": "Bad named entity: niv without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&niv"
+ ]
+ ]
+ },
+ {
+ "input": "∋",
+ "description": "Named entity: niv; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u220b"
+ ]
+ ]
+ },
+ {
+ "input": "&njcy",
+ "description": "Bad named entity: njcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&njcy"
+ ]
+ ]
+ },
+ {
+ "input": "њ",
+ "description": "Named entity: njcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u045a"
+ ]
+ ]
+ },
+ {
+ "input": "&nlArr",
+ "description": "Bad named entity: nlArr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nlArr"
+ ]
+ ]
+ },
+ {
+ "input": "⇍",
+ "description": "Named entity: nlArr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21cd"
+ ]
+ ]
+ },
+ {
+ "input": "&nlE",
+ "description": "Bad named entity: nlE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nlE"
+ ]
+ ]
+ },
+ {
+ "input": "≦̸",
+ "description": "Named entity: nlE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2266\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&nlarr",
+ "description": "Bad named entity: nlarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nlarr"
+ ]
+ ]
+ },
+ {
+ "input": "↚",
+ "description": "Named entity: nlarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u219a"
+ ]
+ ]
+ },
+ {
+ "input": "&nldr",
+ "description": "Bad named entity: nldr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nldr"
+ ]
+ ]
+ },
+ {
+ "input": "‥",
+ "description": "Named entity: nldr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2025"
+ ]
+ ]
+ },
+ {
+ "input": "&nle",
+ "description": "Bad named entity: nle without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nle"
+ ]
+ ]
+ },
+ {
+ "input": "≰",
+ "description": "Named entity: nle; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2270"
+ ]
+ ]
+ },
+ {
+ "input": "&nleftarrow",
+ "description": "Bad named entity: nleftarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nleftarrow"
+ ]
+ ]
+ },
+ {
+ "input": "↚",
+ "description": "Named entity: nleftarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u219a"
+ ]
+ ]
+ },
+ {
+ "input": "&nleftrightarrow",
+ "description": "Bad named entity: nleftrightarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nleftrightarrow"
+ ]
+ ]
+ },
+ {
+ "input": "↮",
+ "description": "Named entity: nleftrightarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21ae"
+ ]
+ ]
+ },
+ {
+ "input": "&nleq",
+ "description": "Bad named entity: nleq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nleq"
+ ]
+ ]
+ },
+ {
+ "input": "≰",
+ "description": "Named entity: nleq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2270"
+ ]
+ ]
+ },
+ {
+ "input": "&nleqq",
+ "description": "Bad named entity: nleqq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nleqq"
+ ]
+ ]
+ },
+ {
+ "input": "≦̸",
+ "description": "Named entity: nleqq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2266\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&nleqslant",
+ "description": "Bad named entity: nleqslant without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nleqslant"
+ ]
+ ]
+ },
+ {
+ "input": "⩽̸",
+ "description": "Named entity: nleqslant; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a7d\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&nles",
+ "description": "Bad named entity: nles without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nles"
+ ]
+ ]
+ },
+ {
+ "input": "⩽̸",
+ "description": "Named entity: nles; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a7d\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&nless",
+ "description": "Bad named entity: nless without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nless"
+ ]
+ ]
+ },
+ {
+ "input": "≮",
+ "description": "Named entity: nless; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u226e"
+ ]
+ ]
+ },
+ {
+ "input": "&nlsim",
+ "description": "Bad named entity: nlsim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nlsim"
+ ]
+ ]
+ },
+ {
+ "input": "≴",
+ "description": "Named entity: nlsim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2274"
+ ]
+ ]
+ },
+ {
+ "input": "&nlt",
+ "description": "Bad named entity: nlt without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nlt"
+ ]
+ ]
+ },
+ {
+ "input": "≮",
+ "description": "Named entity: nlt; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u226e"
+ ]
+ ]
+ },
+ {
+ "input": "&nltri",
+ "description": "Bad named entity: nltri without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nltri"
+ ]
+ ]
+ },
+ {
+ "input": "⋪",
+ "description": "Named entity: nltri; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22ea"
+ ]
+ ]
+ },
+ {
+ "input": "&nltrie",
+ "description": "Bad named entity: nltrie without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nltrie"
+ ]
+ ]
+ },
+ {
+ "input": "⋬",
+ "description": "Named entity: nltrie; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22ec"
+ ]
+ ]
+ },
+ {
+ "input": "&nmid",
+ "description": "Bad named entity: nmid without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nmid"
+ ]
+ ]
+ },
+ {
+ "input": "∤",
+ "description": "Named entity: nmid; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2224"
+ ]
+ ]
+ },
+ {
+ "input": "&nopf",
+ "description": "Bad named entity: nopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕟",
+ "description": "Named entity: nopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd5f"
+ ]
+ ]
+ },
+ {
+ "input": "¬",
+ "description": "Named entity: not without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00ac"
+ ]
+ ]
+ },
+ {
+ "input": "¬",
+ "description": "Named entity: not; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00ac"
+ ]
+ ]
+ },
+ {
+ "input": "∉",
+ "description": "Named entity: notin; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2209"
+ ]
+ ]
+ },
+ {
+ "input": "⋹̸",
+ "description": "Named entity: notinE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22f9\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "⋵̸",
+ "description": "Named entity: notindot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22f5\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "∉",
+ "description": "Named entity: notinva; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2209"
+ ]
+ ]
+ },
+ {
+ "input": "⋷",
+ "description": "Named entity: notinvb; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22f7"
+ ]
+ ]
+ },
+ {
+ "input": "⋶",
+ "description": "Named entity: notinvc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22f6"
+ ]
+ ]
+ },
+ {
+ "input": "∌",
+ "description": "Named entity: notni; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u220c"
+ ]
+ ]
+ },
+ {
+ "input": "∌",
+ "description": "Named entity: notniva; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u220c"
+ ]
+ ]
+ },
+ {
+ "input": "⋾",
+ "description": "Named entity: notnivb; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22fe"
+ ]
+ ]
+ },
+ {
+ "input": "⋽",
+ "description": "Named entity: notnivc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22fd"
+ ]
+ ]
+ },
+ {
+ "input": "&npar",
+ "description": "Bad named entity: npar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&npar"
+ ]
+ ]
+ },
+ {
+ "input": "∦",
+ "description": "Named entity: npar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2226"
+ ]
+ ]
+ },
+ {
+ "input": "&nparallel",
+ "description": "Bad named entity: nparallel without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nparallel"
+ ]
+ ]
+ },
+ {
+ "input": "∦",
+ "description": "Named entity: nparallel; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2226"
+ ]
+ ]
+ },
+ {
+ "input": "&nparsl",
+ "description": "Bad named entity: nparsl without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nparsl"
+ ]
+ ]
+ },
+ {
+ "input": "⫽⃥",
+ "description": "Named entity: nparsl; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2afd\u20e5"
+ ]
+ ]
+ },
+ {
+ "input": "&npart",
+ "description": "Bad named entity: npart without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&npart"
+ ]
+ ]
+ },
+ {
+ "input": "∂̸",
+ "description": "Named entity: npart; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2202\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&npolint",
+ "description": "Bad named entity: npolint without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&npolint"
+ ]
+ ]
+ },
+ {
+ "input": "⨔",
+ "description": "Named entity: npolint; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a14"
+ ]
+ ]
+ },
+ {
+ "input": "&npr",
+ "description": "Bad named entity: npr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&npr"
+ ]
+ ]
+ },
+ {
+ "input": "⊀",
+ "description": "Named entity: npr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2280"
+ ]
+ ]
+ },
+ {
+ "input": "&nprcue",
+ "description": "Bad named entity: nprcue without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nprcue"
+ ]
+ ]
+ },
+ {
+ "input": "⋠",
+ "description": "Named entity: nprcue; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22e0"
+ ]
+ ]
+ },
+ {
+ "input": "&npre",
+ "description": "Bad named entity: npre without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&npre"
+ ]
+ ]
+ },
+ {
+ "input": "⪯̸",
+ "description": "Named entity: npre; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2aaf\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&nprec",
+ "description": "Bad named entity: nprec without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nprec"
+ ]
+ ]
+ },
+ {
+ "input": "⊀",
+ "description": "Named entity: nprec; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2280"
+ ]
+ ]
+ },
+ {
+ "input": "&npreceq",
+ "description": "Bad named entity: npreceq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&npreceq"
+ ]
+ ]
+ },
+ {
+ "input": "⪯̸",
+ "description": "Named entity: npreceq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2aaf\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&nrArr",
+ "description": "Bad named entity: nrArr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nrArr"
+ ]
+ ]
+ },
+ {
+ "input": "⇏",
+ "description": "Named entity: nrArr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21cf"
+ ]
+ ]
+ },
+ {
+ "input": "&nrarr",
+ "description": "Bad named entity: nrarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nrarr"
+ ]
+ ]
+ },
+ {
+ "input": "↛",
+ "description": "Named entity: nrarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u219b"
+ ]
+ ]
+ },
+ {
+ "input": "&nrarrc",
+ "description": "Bad named entity: nrarrc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nrarrc"
+ ]
+ ]
+ },
+ {
+ "input": "⤳̸",
+ "description": "Named entity: nrarrc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2933\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&nrarrw",
+ "description": "Bad named entity: nrarrw without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nrarrw"
+ ]
+ ]
+ },
+ {
+ "input": "↝̸",
+ "description": "Named entity: nrarrw; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u219d\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&nrightarrow",
+ "description": "Bad named entity: nrightarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nrightarrow"
+ ]
+ ]
+ },
+ {
+ "input": "↛",
+ "description": "Named entity: nrightarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u219b"
+ ]
+ ]
+ },
+ {
+ "input": "&nrtri",
+ "description": "Bad named entity: nrtri without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nrtri"
+ ]
+ ]
+ },
+ {
+ "input": "⋫",
+ "description": "Named entity: nrtri; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22eb"
+ ]
+ ]
+ },
+ {
+ "input": "&nrtrie",
+ "description": "Bad named entity: nrtrie without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nrtrie"
+ ]
+ ]
+ },
+ {
+ "input": "⋭",
+ "description": "Named entity: nrtrie; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22ed"
+ ]
+ ]
+ },
+ {
+ "input": "&nsc",
+ "description": "Bad named entity: nsc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nsc"
+ ]
+ ]
+ },
+ {
+ "input": "⊁",
+ "description": "Named entity: nsc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2281"
+ ]
+ ]
+ },
+ {
+ "input": "&nsccue",
+ "description": "Bad named entity: nsccue without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nsccue"
+ ]
+ ]
+ },
+ {
+ "input": "⋡",
+ "description": "Named entity: nsccue; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22e1"
+ ]
+ ]
+ },
+ {
+ "input": "&nsce",
+ "description": "Bad named entity: nsce without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nsce"
+ ]
+ ]
+ },
+ {
+ "input": "⪰̸",
+ "description": "Named entity: nsce; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ab0\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&nscr",
+ "description": "Bad named entity: nscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝓃",
+ "description": "Named entity: nscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udcc3"
+ ]
+ ]
+ },
+ {
+ "input": "&nshortmid",
+ "description": "Bad named entity: nshortmid without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nshortmid"
+ ]
+ ]
+ },
+ {
+ "input": "∤",
+ "description": "Named entity: nshortmid; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2224"
+ ]
+ ]
+ },
+ {
+ "input": "&nshortparallel",
+ "description": "Bad named entity: nshortparallel without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nshortparallel"
+ ]
+ ]
+ },
+ {
+ "input": "∦",
+ "description": "Named entity: nshortparallel; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2226"
+ ]
+ ]
+ },
+ {
+ "input": "&nsim",
+ "description": "Bad named entity: nsim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nsim"
+ ]
+ ]
+ },
+ {
+ "input": "≁",
+ "description": "Named entity: nsim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2241"
+ ]
+ ]
+ },
+ {
+ "input": "&nsime",
+ "description": "Bad named entity: nsime without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nsime"
+ ]
+ ]
+ },
+ {
+ "input": "≄",
+ "description": "Named entity: nsime; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2244"
+ ]
+ ]
+ },
+ {
+ "input": "&nsimeq",
+ "description": "Bad named entity: nsimeq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nsimeq"
+ ]
+ ]
+ },
+ {
+ "input": "≄",
+ "description": "Named entity: nsimeq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2244"
+ ]
+ ]
+ },
+ {
+ "input": "&nsmid",
+ "description": "Bad named entity: nsmid without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nsmid"
+ ]
+ ]
+ },
+ {
+ "input": "∤",
+ "description": "Named entity: nsmid; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2224"
+ ]
+ ]
+ },
+ {
+ "input": "&nspar",
+ "description": "Bad named entity: nspar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nspar"
+ ]
+ ]
+ },
+ {
+ "input": "∦",
+ "description": "Named entity: nspar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2226"
+ ]
+ ]
+ },
+ {
+ "input": "&nsqsube",
+ "description": "Bad named entity: nsqsube without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nsqsube"
+ ]
+ ]
+ },
+ {
+ "input": "⋢",
+ "description": "Named entity: nsqsube; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22e2"
+ ]
+ ]
+ },
+ {
+ "input": "&nsqsupe",
+ "description": "Bad named entity: nsqsupe without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nsqsupe"
+ ]
+ ]
+ },
+ {
+ "input": "⋣",
+ "description": "Named entity: nsqsupe; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22e3"
+ ]
+ ]
+ },
+ {
+ "input": "&nsub",
+ "description": "Bad named entity: nsub without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nsub"
+ ]
+ ]
+ },
+ {
+ "input": "⊄",
+ "description": "Named entity: nsub; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2284"
+ ]
+ ]
+ },
+ {
+ "input": "&nsubE",
+ "description": "Bad named entity: nsubE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nsubE"
+ ]
+ ]
+ },
+ {
+ "input": "⫅̸",
+ "description": "Named entity: nsubE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ac5\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&nsube",
+ "description": "Bad named entity: nsube without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nsube"
+ ]
+ ]
+ },
+ {
+ "input": "⊈",
+ "description": "Named entity: nsube; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2288"
+ ]
+ ]
+ },
+ {
+ "input": "&nsubset",
+ "description": "Bad named entity: nsubset without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nsubset"
+ ]
+ ]
+ },
+ {
+ "input": "⊂⃒",
+ "description": "Named entity: nsubset; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2282\u20d2"
+ ]
+ ]
+ },
+ {
+ "input": "&nsubseteq",
+ "description": "Bad named entity: nsubseteq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nsubseteq"
+ ]
+ ]
+ },
+ {
+ "input": "⊈",
+ "description": "Named entity: nsubseteq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2288"
+ ]
+ ]
+ },
+ {
+ "input": "&nsubseteqq",
+ "description": "Bad named entity: nsubseteqq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nsubseteqq"
+ ]
+ ]
+ },
+ {
+ "input": "⫅̸",
+ "description": "Named entity: nsubseteqq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ac5\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&nsucc",
+ "description": "Bad named entity: nsucc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nsucc"
+ ]
+ ]
+ },
+ {
+ "input": "⊁",
+ "description": "Named entity: nsucc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2281"
+ ]
+ ]
+ },
+ {
+ "input": "&nsucceq",
+ "description": "Bad named entity: nsucceq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nsucceq"
+ ]
+ ]
+ },
+ {
+ "input": "⪰̸",
+ "description": "Named entity: nsucceq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ab0\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&nsup",
+ "description": "Bad named entity: nsup without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nsup"
+ ]
+ ]
+ },
+ {
+ "input": "⊅",
+ "description": "Named entity: nsup; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2285"
+ ]
+ ]
+ },
+ {
+ "input": "&nsupE",
+ "description": "Bad named entity: nsupE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nsupE"
+ ]
+ ]
+ },
+ {
+ "input": "⫆̸",
+ "description": "Named entity: nsupE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ac6\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&nsupe",
+ "description": "Bad named entity: nsupe without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nsupe"
+ ]
+ ]
+ },
+ {
+ "input": "⊉",
+ "description": "Named entity: nsupe; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2289"
+ ]
+ ]
+ },
+ {
+ "input": "&nsupset",
+ "description": "Bad named entity: nsupset without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nsupset"
+ ]
+ ]
+ },
+ {
+ "input": "⊃⃒",
+ "description": "Named entity: nsupset; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2283\u20d2"
+ ]
+ ]
+ },
+ {
+ "input": "&nsupseteq",
+ "description": "Bad named entity: nsupseteq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nsupseteq"
+ ]
+ ]
+ },
+ {
+ "input": "⊉",
+ "description": "Named entity: nsupseteq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2289"
+ ]
+ ]
+ },
+ {
+ "input": "&nsupseteqq",
+ "description": "Bad named entity: nsupseteqq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nsupseteqq"
+ ]
+ ]
+ },
+ {
+ "input": "⫆̸",
+ "description": "Named entity: nsupseteqq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ac6\u0338"
+ ]
+ ]
+ },
+ {
+ "input": "&ntgl",
+ "description": "Bad named entity: ntgl without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ntgl"
+ ]
+ ]
+ },
+ {
+ "input": "≹",
+ "description": "Named entity: ntgl; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2279"
+ ]
+ ]
+ },
+ {
+ "input": "ñ",
+ "description": "Named entity: ntilde without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00f1"
+ ]
+ ]
+ },
+ {
+ "input": "ñ",
+ "description": "Named entity: ntilde; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00f1"
+ ]
+ ]
+ },
+ {
+ "input": "&ntlg",
+ "description": "Bad named entity: ntlg without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ntlg"
+ ]
+ ]
+ },
+ {
+ "input": "≸",
+ "description": "Named entity: ntlg; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2278"
+ ]
+ ]
+ },
+ {
+ "input": "&ntriangleleft",
+ "description": "Bad named entity: ntriangleleft without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ntriangleleft"
+ ]
+ ]
+ },
+ {
+ "input": "⋪",
+ "description": "Named entity: ntriangleleft; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22ea"
+ ]
+ ]
+ },
+ {
+ "input": "&ntrianglelefteq",
+ "description": "Bad named entity: ntrianglelefteq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ntrianglelefteq"
+ ]
+ ]
+ },
+ {
+ "input": "⋬",
+ "description": "Named entity: ntrianglelefteq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22ec"
+ ]
+ ]
+ },
+ {
+ "input": "&ntriangleright",
+ "description": "Bad named entity: ntriangleright without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ntriangleright"
+ ]
+ ]
+ },
+ {
+ "input": "⋫",
+ "description": "Named entity: ntriangleright; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22eb"
+ ]
+ ]
+ },
+ {
+ "input": "&ntrianglerighteq",
+ "description": "Bad named entity: ntrianglerighteq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ntrianglerighteq"
+ ]
+ ]
+ },
+ {
+ "input": "⋭",
+ "description": "Named entity: ntrianglerighteq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22ed"
+ ]
+ ]
+ },
+ {
+ "input": "&nu",
+ "description": "Bad named entity: nu without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nu"
+ ]
+ ]
+ },
+ {
+ "input": "ν",
+ "description": "Named entity: nu; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03bd"
+ ]
+ ]
+ },
+ {
+ "input": "&num",
+ "description": "Bad named entity: num without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&num"
+ ]
+ ]
+ },
+ {
+ "input": "#",
+ "description": "Named entity: num; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "#"
+ ]
+ ]
+ },
+ {
+ "input": "&numero",
+ "description": "Bad named entity: numero without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&numero"
+ ]
+ ]
+ },
+ {
+ "input": "№",
+ "description": "Named entity: numero; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2116"
+ ]
+ ]
+ },
+ {
+ "input": "&numsp",
+ "description": "Bad named entity: numsp without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&numsp"
+ ]
+ ]
+ },
+ {
+ "input": " ",
+ "description": "Named entity: numsp; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2007"
+ ]
+ ]
+ },
+ {
+ "input": "&nvDash",
+ "description": "Bad named entity: nvDash without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nvDash"
+ ]
+ ]
+ },
+ {
+ "input": "⊭",
+ "description": "Named entity: nvDash; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22ad"
+ ]
+ ]
+ },
+ {
+ "input": "&nvHarr",
+ "description": "Bad named entity: nvHarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nvHarr"
+ ]
+ ]
+ },
+ {
+ "input": "⤄",
+ "description": "Named entity: nvHarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2904"
+ ]
+ ]
+ },
+ {
+ "input": "&nvap",
+ "description": "Bad named entity: nvap without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nvap"
+ ]
+ ]
+ },
+ {
+ "input": "≍⃒",
+ "description": "Named entity: nvap; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u224d\u20d2"
+ ]
+ ]
+ },
+ {
+ "input": "&nvdash",
+ "description": "Bad named entity: nvdash without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nvdash"
+ ]
+ ]
+ },
+ {
+ "input": "⊬",
+ "description": "Named entity: nvdash; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22ac"
+ ]
+ ]
+ },
+ {
+ "input": "&nvge",
+ "description": "Bad named entity: nvge without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nvge"
+ ]
+ ]
+ },
+ {
+ "input": "≥⃒",
+ "description": "Named entity: nvge; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2265\u20d2"
+ ]
+ ]
+ },
+ {
+ "input": "&nvgt",
+ "description": "Bad named entity: nvgt without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nvgt"
+ ]
+ ]
+ },
+ {
+ "input": ">⃒",
+ "description": "Named entity: nvgt; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ ">\u20d2"
+ ]
+ ]
+ },
+ {
+ "input": "&nvinfin",
+ "description": "Bad named entity: nvinfin without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nvinfin"
+ ]
+ ]
+ },
+ {
+ "input": "⧞",
+ "description": "Named entity: nvinfin; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29de"
+ ]
+ ]
+ },
+ {
+ "input": "&nvlArr",
+ "description": "Bad named entity: nvlArr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nvlArr"
+ ]
+ ]
+ },
+ {
+ "input": "⤂",
+ "description": "Named entity: nvlArr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2902"
+ ]
+ ]
+ },
+ {
+ "input": "&nvle",
+ "description": "Bad named entity: nvle without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nvle"
+ ]
+ ]
+ },
+ {
+ "input": "≤⃒",
+ "description": "Named entity: nvle; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2264\u20d2"
+ ]
+ ]
+ },
+ {
+ "input": "&nvlt",
+ "description": "Bad named entity: nvlt without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nvlt"
+ ]
+ ]
+ },
+ {
+ "input": "<⃒",
+ "description": "Named entity: nvlt; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "<\u20d2"
+ ]
+ ]
+ },
+ {
+ "input": "&nvltrie",
+ "description": "Bad named entity: nvltrie without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nvltrie"
+ ]
+ ]
+ },
+ {
+ "input": "⊴⃒",
+ "description": "Named entity: nvltrie; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22b4\u20d2"
+ ]
+ ]
+ },
+ {
+ "input": "&nvrArr",
+ "description": "Bad named entity: nvrArr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nvrArr"
+ ]
+ ]
+ },
+ {
+ "input": "⤃",
+ "description": "Named entity: nvrArr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2903"
+ ]
+ ]
+ },
+ {
+ "input": "&nvrtrie",
+ "description": "Bad named entity: nvrtrie without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nvrtrie"
+ ]
+ ]
+ },
+ {
+ "input": "⊵⃒",
+ "description": "Named entity: nvrtrie; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22b5\u20d2"
+ ]
+ ]
+ },
+ {
+ "input": "&nvsim",
+ "description": "Bad named entity: nvsim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nvsim"
+ ]
+ ]
+ },
+ {
+ "input": "∼⃒",
+ "description": "Named entity: nvsim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u223c\u20d2"
+ ]
+ ]
+ },
+ {
+ "input": "&nwArr",
+ "description": "Bad named entity: nwArr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nwArr"
+ ]
+ ]
+ },
+ {
+ "input": "⇖",
+ "description": "Named entity: nwArr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21d6"
+ ]
+ ]
+ },
+ {
+ "input": "&nwarhk",
+ "description": "Bad named entity: nwarhk without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nwarhk"
+ ]
+ ]
+ },
+ {
+ "input": "⤣",
+ "description": "Named entity: nwarhk; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2923"
+ ]
+ ]
+ },
+ {
+ "input": "&nwarr",
+ "description": "Bad named entity: nwarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nwarr"
+ ]
+ ]
+ },
+ {
+ "input": "↖",
+ "description": "Named entity: nwarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2196"
+ ]
+ ]
+ },
+ {
+ "input": "&nwarrow",
+ "description": "Bad named entity: nwarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nwarrow"
+ ]
+ ]
+ },
+ {
+ "input": "↖",
+ "description": "Named entity: nwarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2196"
+ ]
+ ]
+ },
+ {
+ "input": "&nwnear",
+ "description": "Bad named entity: nwnear without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&nwnear"
+ ]
+ ]
+ },
+ {
+ "input": "⤧",
+ "description": "Named entity: nwnear; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2927"
+ ]
+ ]
+ },
+ {
+ "input": "&oS",
+ "description": "Bad named entity: oS without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&oS"
+ ]
+ ]
+ },
+ {
+ "input": "Ⓢ",
+ "description": "Named entity: oS; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u24c8"
+ ]
+ ]
+ },
+ {
+ "input": "ó",
+ "description": "Named entity: oacute without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00f3"
+ ]
+ ]
+ },
+ {
+ "input": "ó",
+ "description": "Named entity: oacute; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00f3"
+ ]
+ ]
+ },
+ {
+ "input": "&oast",
+ "description": "Bad named entity: oast without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&oast"
+ ]
+ ]
+ },
+ {
+ "input": "⊛",
+ "description": "Named entity: oast; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u229b"
+ ]
+ ]
+ },
+ {
+ "input": "&ocir",
+ "description": "Bad named entity: ocir without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ocir"
+ ]
+ ]
+ },
+ {
+ "input": "⊚",
+ "description": "Named entity: ocir; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u229a"
+ ]
+ ]
+ },
+ {
+ "input": "ô",
+ "description": "Named entity: ocirc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00f4"
+ ]
+ ]
+ },
+ {
+ "input": "ô",
+ "description": "Named entity: ocirc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00f4"
+ ]
+ ]
+ },
+ {
+ "input": "&ocy",
+ "description": "Bad named entity: ocy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ocy"
+ ]
+ ]
+ },
+ {
+ "input": "о",
+ "description": "Named entity: ocy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u043e"
+ ]
+ ]
+ },
+ {
+ "input": "&odash",
+ "description": "Bad named entity: odash without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&odash"
+ ]
+ ]
+ },
+ {
+ "input": "⊝",
+ "description": "Named entity: odash; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u229d"
+ ]
+ ]
+ },
+ {
+ "input": "&odblac",
+ "description": "Bad named entity: odblac without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&odblac"
+ ]
+ ]
+ },
+ {
+ "input": "ő",
+ "description": "Named entity: odblac; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0151"
+ ]
+ ]
+ },
+ {
+ "input": "&odiv",
+ "description": "Bad named entity: odiv without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&odiv"
+ ]
+ ]
+ },
+ {
+ "input": "⨸",
+ "description": "Named entity: odiv; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a38"
+ ]
+ ]
+ },
+ {
+ "input": "&odot",
+ "description": "Bad named entity: odot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&odot"
+ ]
+ ]
+ },
+ {
+ "input": "⊙",
+ "description": "Named entity: odot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2299"
+ ]
+ ]
+ },
+ {
+ "input": "&odsold",
+ "description": "Bad named entity: odsold without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&odsold"
+ ]
+ ]
+ },
+ {
+ "input": "⦼",
+ "description": "Named entity: odsold; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29bc"
+ ]
+ ]
+ },
+ {
+ "input": "&oelig",
+ "description": "Bad named entity: oelig without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&oelig"
+ ]
+ ]
+ },
+ {
+ "input": "œ",
+ "description": "Named entity: oelig; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0153"
+ ]
+ ]
+ },
+ {
+ "input": "&ofcir",
+ "description": "Bad named entity: ofcir without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ofcir"
+ ]
+ ]
+ },
+ {
+ "input": "⦿",
+ "description": "Named entity: ofcir; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29bf"
+ ]
+ ]
+ },
+ {
+ "input": "&ofr",
+ "description": "Bad named entity: ofr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ofr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔬",
+ "description": "Named entity: ofr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd2c"
+ ]
+ ]
+ },
+ {
+ "input": "&ogon",
+ "description": "Bad named entity: ogon without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ogon"
+ ]
+ ]
+ },
+ {
+ "input": "˛",
+ "description": "Named entity: ogon; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u02db"
+ ]
+ ]
+ },
+ {
+ "input": "ò",
+ "description": "Named entity: ograve without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00f2"
+ ]
+ ]
+ },
+ {
+ "input": "ò",
+ "description": "Named entity: ograve; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00f2"
+ ]
+ ]
+ },
+ {
+ "input": "&ogt",
+ "description": "Bad named entity: ogt without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ogt"
+ ]
+ ]
+ },
+ {
+ "input": "⧁",
+ "description": "Named entity: ogt; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29c1"
+ ]
+ ]
+ },
+ {
+ "input": "&ohbar",
+ "description": "Bad named entity: ohbar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ohbar"
+ ]
+ ]
+ },
+ {
+ "input": "⦵",
+ "description": "Named entity: ohbar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29b5"
+ ]
+ ]
+ },
+ {
+ "input": "&ohm",
+ "description": "Bad named entity: ohm without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ohm"
+ ]
+ ]
+ },
+ {
+ "input": "Ω",
+ "description": "Named entity: ohm; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03a9"
+ ]
+ ]
+ },
+ {
+ "input": "&oint",
+ "description": "Bad named entity: oint without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&oint"
+ ]
+ ]
+ },
+ {
+ "input": "∮",
+ "description": "Named entity: oint; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u222e"
+ ]
+ ]
+ },
+ {
+ "input": "&olarr",
+ "description": "Bad named entity: olarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&olarr"
+ ]
+ ]
+ },
+ {
+ "input": "↺",
+ "description": "Named entity: olarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21ba"
+ ]
+ ]
+ },
+ {
+ "input": "&olcir",
+ "description": "Bad named entity: olcir without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&olcir"
+ ]
+ ]
+ },
+ {
+ "input": "⦾",
+ "description": "Named entity: olcir; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29be"
+ ]
+ ]
+ },
+ {
+ "input": "&olcross",
+ "description": "Bad named entity: olcross without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&olcross"
+ ]
+ ]
+ },
+ {
+ "input": "⦻",
+ "description": "Named entity: olcross; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29bb"
+ ]
+ ]
+ },
+ {
+ "input": "&oline",
+ "description": "Bad named entity: oline without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&oline"
+ ]
+ ]
+ },
+ {
+ "input": "‾",
+ "description": "Named entity: oline; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u203e"
+ ]
+ ]
+ },
+ {
+ "input": "&olt",
+ "description": "Bad named entity: olt without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&olt"
+ ]
+ ]
+ },
+ {
+ "input": "⧀",
+ "description": "Named entity: olt; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29c0"
+ ]
+ ]
+ },
+ {
+ "input": "&omacr",
+ "description": "Bad named entity: omacr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&omacr"
+ ]
+ ]
+ },
+ {
+ "input": "ō",
+ "description": "Named entity: omacr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u014d"
+ ]
+ ]
+ },
+ {
+ "input": "&omega",
+ "description": "Bad named entity: omega without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&omega"
+ ]
+ ]
+ },
+ {
+ "input": "ω",
+ "description": "Named entity: omega; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03c9"
+ ]
+ ]
+ },
+ {
+ "input": "&omicron",
+ "description": "Bad named entity: omicron without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&omicron"
+ ]
+ ]
+ },
+ {
+ "input": "ο",
+ "description": "Named entity: omicron; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03bf"
+ ]
+ ]
+ },
+ {
+ "input": "&omid",
+ "description": "Bad named entity: omid without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&omid"
+ ]
+ ]
+ },
+ {
+ "input": "⦶",
+ "description": "Named entity: omid; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29b6"
+ ]
+ ]
+ },
+ {
+ "input": "&ominus",
+ "description": "Bad named entity: ominus without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ominus"
+ ]
+ ]
+ },
+ {
+ "input": "⊖",
+ "description": "Named entity: ominus; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2296"
+ ]
+ ]
+ },
+ {
+ "input": "&oopf",
+ "description": "Bad named entity: oopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&oopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕠",
+ "description": "Named entity: oopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd60"
+ ]
+ ]
+ },
+ {
+ "input": "&opar",
+ "description": "Bad named entity: opar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&opar"
+ ]
+ ]
+ },
+ {
+ "input": "⦷",
+ "description": "Named entity: opar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29b7"
+ ]
+ ]
+ },
+ {
+ "input": "&operp",
+ "description": "Bad named entity: operp without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&operp"
+ ]
+ ]
+ },
+ {
+ "input": "⦹",
+ "description": "Named entity: operp; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29b9"
+ ]
+ ]
+ },
+ {
+ "input": "&oplus",
+ "description": "Bad named entity: oplus without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&oplus"
+ ]
+ ]
+ },
+ {
+ "input": "⊕",
+ "description": "Named entity: oplus; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2295"
+ ]
+ ]
+ },
+ {
+ "input": "&or",
+ "description": "Bad named entity: or without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&or"
+ ]
+ ]
+ },
+ {
+ "input": "∨",
+ "description": "Named entity: or; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2228"
+ ]
+ ]
+ },
+ {
+ "input": "&orarr",
+ "description": "Bad named entity: orarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&orarr"
+ ]
+ ]
+ },
+ {
+ "input": "↻",
+ "description": "Named entity: orarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21bb"
+ ]
+ ]
+ },
+ {
+ "input": "&ord",
+ "description": "Bad named entity: ord without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ord"
+ ]
+ ]
+ },
+ {
+ "input": "⩝",
+ "description": "Named entity: ord; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a5d"
+ ]
+ ]
+ },
+ {
+ "input": "&order",
+ "description": "Bad named entity: order without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&order"
+ ]
+ ]
+ },
+ {
+ "input": "ℴ",
+ "description": "Named entity: order; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2134"
+ ]
+ ]
+ },
+ {
+ "input": "&orderof",
+ "description": "Bad named entity: orderof without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&orderof"
+ ]
+ ]
+ },
+ {
+ "input": "ℴ",
+ "description": "Named entity: orderof; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2134"
+ ]
+ ]
+ },
+ {
+ "input": "ª",
+ "description": "Named entity: ordf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00aa"
+ ]
+ ]
+ },
+ {
+ "input": "ª",
+ "description": "Named entity: ordf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00aa"
+ ]
+ ]
+ },
+ {
+ "input": "º",
+ "description": "Named entity: ordm without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00ba"
+ ]
+ ]
+ },
+ {
+ "input": "º",
+ "description": "Named entity: ordm; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00ba"
+ ]
+ ]
+ },
+ {
+ "input": "&origof",
+ "description": "Bad named entity: origof without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&origof"
+ ]
+ ]
+ },
+ {
+ "input": "⊶",
+ "description": "Named entity: origof; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22b6"
+ ]
+ ]
+ },
+ {
+ "input": "&oror",
+ "description": "Bad named entity: oror without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&oror"
+ ]
+ ]
+ },
+ {
+ "input": "⩖",
+ "description": "Named entity: oror; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a56"
+ ]
+ ]
+ },
+ {
+ "input": "&orslope",
+ "description": "Bad named entity: orslope without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&orslope"
+ ]
+ ]
+ },
+ {
+ "input": "⩗",
+ "description": "Named entity: orslope; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a57"
+ ]
+ ]
+ },
+ {
+ "input": "&orv",
+ "description": "Bad named entity: orv without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&orv"
+ ]
+ ]
+ },
+ {
+ "input": "⩛",
+ "description": "Named entity: orv; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a5b"
+ ]
+ ]
+ },
+ {
+ "input": "&oscr",
+ "description": "Bad named entity: oscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&oscr"
+ ]
+ ]
+ },
+ {
+ "input": "ℴ",
+ "description": "Named entity: oscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2134"
+ ]
+ ]
+ },
+ {
+ "input": "ø",
+ "description": "Named entity: oslash without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00f8"
+ ]
+ ]
+ },
+ {
+ "input": "ø",
+ "description": "Named entity: oslash; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00f8"
+ ]
+ ]
+ },
+ {
+ "input": "&osol",
+ "description": "Bad named entity: osol without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&osol"
+ ]
+ ]
+ },
+ {
+ "input": "⊘",
+ "description": "Named entity: osol; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2298"
+ ]
+ ]
+ },
+ {
+ "input": "õ",
+ "description": "Named entity: otilde without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00f5"
+ ]
+ ]
+ },
+ {
+ "input": "õ",
+ "description": "Named entity: otilde; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00f5"
+ ]
+ ]
+ },
+ {
+ "input": "&otimes",
+ "description": "Bad named entity: otimes without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&otimes"
+ ]
+ ]
+ },
+ {
+ "input": "⊗",
+ "description": "Named entity: otimes; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2297"
+ ]
+ ]
+ },
+ {
+ "input": "&otimesas",
+ "description": "Bad named entity: otimesas without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&otimesas"
+ ]
+ ]
+ },
+ {
+ "input": "⨶",
+ "description": "Named entity: otimesas; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a36"
+ ]
+ ]
+ },
+ {
+ "input": "ö",
+ "description": "Named entity: ouml without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00f6"
+ ]
+ ]
+ },
+ {
+ "input": "ö",
+ "description": "Named entity: ouml; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00f6"
+ ]
+ ]
+ },
+ {
+ "input": "&ovbar",
+ "description": "Bad named entity: ovbar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ovbar"
+ ]
+ ]
+ },
+ {
+ "input": "⌽",
+ "description": "Named entity: ovbar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u233d"
+ ]
+ ]
+ },
+ {
+ "input": "&par",
+ "description": "Bad named entity: par without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&par"
+ ]
+ ]
+ },
+ {
+ "input": "∥",
+ "description": "Named entity: par; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2225"
+ ]
+ ]
+ },
+ {
+ "input": "¶",
+ "description": "Named entity: para without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00b6"
+ ]
+ ]
+ },
+ {
+ "input": "¶",
+ "description": "Named entity: para; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00b6"
+ ]
+ ]
+ },
+ {
+ "input": "∥",
+ "description": "Named entity: parallel; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2225"
+ ]
+ ]
+ },
+ {
+ "input": "&parsim",
+ "description": "Bad named entity: parsim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&parsim"
+ ]
+ ]
+ },
+ {
+ "input": "⫳",
+ "description": "Named entity: parsim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2af3"
+ ]
+ ]
+ },
+ {
+ "input": "&parsl",
+ "description": "Bad named entity: parsl without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&parsl"
+ ]
+ ]
+ },
+ {
+ "input": "⫽",
+ "description": "Named entity: parsl; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2afd"
+ ]
+ ]
+ },
+ {
+ "input": "&part",
+ "description": "Bad named entity: part without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&part"
+ ]
+ ]
+ },
+ {
+ "input": "∂",
+ "description": "Named entity: part; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2202"
+ ]
+ ]
+ },
+ {
+ "input": "&pcy",
+ "description": "Bad named entity: pcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&pcy"
+ ]
+ ]
+ },
+ {
+ "input": "п",
+ "description": "Named entity: pcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u043f"
+ ]
+ ]
+ },
+ {
+ "input": "&percnt",
+ "description": "Bad named entity: percnt without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&percnt"
+ ]
+ ]
+ },
+ {
+ "input": "%",
+ "description": "Named entity: percnt; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "%"
+ ]
+ ]
+ },
+ {
+ "input": "&period",
+ "description": "Bad named entity: period without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&period"
+ ]
+ ]
+ },
+ {
+ "input": ".",
+ "description": "Named entity: period; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "."
+ ]
+ ]
+ },
+ {
+ "input": "&permil",
+ "description": "Bad named entity: permil without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&permil"
+ ]
+ ]
+ },
+ {
+ "input": "‰",
+ "description": "Named entity: permil; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2030"
+ ]
+ ]
+ },
+ {
+ "input": "&perp",
+ "description": "Bad named entity: perp without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&perp"
+ ]
+ ]
+ },
+ {
+ "input": "⊥",
+ "description": "Named entity: perp; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22a5"
+ ]
+ ]
+ },
+ {
+ "input": "&pertenk",
+ "description": "Bad named entity: pertenk without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&pertenk"
+ ]
+ ]
+ },
+ {
+ "input": "‱",
+ "description": "Named entity: pertenk; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2031"
+ ]
+ ]
+ },
+ {
+ "input": "&pfr",
+ "description": "Bad named entity: pfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&pfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔭",
+ "description": "Named entity: pfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd2d"
+ ]
+ ]
+ },
+ {
+ "input": "&phi",
+ "description": "Bad named entity: phi without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&phi"
+ ]
+ ]
+ },
+ {
+ "input": "φ",
+ "description": "Named entity: phi; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03c6"
+ ]
+ ]
+ },
+ {
+ "input": "&phiv",
+ "description": "Bad named entity: phiv without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&phiv"
+ ]
+ ]
+ },
+ {
+ "input": "ϕ",
+ "description": "Named entity: phiv; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03d5"
+ ]
+ ]
+ },
+ {
+ "input": "&phmmat",
+ "description": "Bad named entity: phmmat without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&phmmat"
+ ]
+ ]
+ },
+ {
+ "input": "ℳ",
+ "description": "Named entity: phmmat; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2133"
+ ]
+ ]
+ },
+ {
+ "input": "&phone",
+ "description": "Bad named entity: phone without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&phone"
+ ]
+ ]
+ },
+ {
+ "input": "☎",
+ "description": "Named entity: phone; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u260e"
+ ]
+ ]
+ },
+ {
+ "input": "&pi",
+ "description": "Bad named entity: pi without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&pi"
+ ]
+ ]
+ },
+ {
+ "input": "π",
+ "description": "Named entity: pi; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03c0"
+ ]
+ ]
+ },
+ {
+ "input": "&pitchfork",
+ "description": "Bad named entity: pitchfork without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&pitchfork"
+ ]
+ ]
+ },
+ {
+ "input": "⋔",
+ "description": "Named entity: pitchfork; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22d4"
+ ]
+ ]
+ },
+ {
+ "input": "&piv",
+ "description": "Bad named entity: piv without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&piv"
+ ]
+ ]
+ },
+ {
+ "input": "ϖ",
+ "description": "Named entity: piv; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03d6"
+ ]
+ ]
+ },
+ {
+ "input": "&planck",
+ "description": "Bad named entity: planck without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&planck"
+ ]
+ ]
+ },
+ {
+ "input": "ℏ",
+ "description": "Named entity: planck; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u210f"
+ ]
+ ]
+ },
+ {
+ "input": "&planckh",
+ "description": "Bad named entity: planckh without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&planckh"
+ ]
+ ]
+ },
+ {
+ "input": "ℎ",
+ "description": "Named entity: planckh; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u210e"
+ ]
+ ]
+ },
+ {
+ "input": "&plankv",
+ "description": "Bad named entity: plankv without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&plankv"
+ ]
+ ]
+ },
+ {
+ "input": "ℏ",
+ "description": "Named entity: plankv; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u210f"
+ ]
+ ]
+ },
+ {
+ "input": "&plus",
+ "description": "Bad named entity: plus without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&plus"
+ ]
+ ]
+ },
+ {
+ "input": "+",
+ "description": "Named entity: plus; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "+"
+ ]
+ ]
+ },
+ {
+ "input": "&plusacir",
+ "description": "Bad named entity: plusacir without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&plusacir"
+ ]
+ ]
+ },
+ {
+ "input": "⨣",
+ "description": "Named entity: plusacir; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a23"
+ ]
+ ]
+ },
+ {
+ "input": "&plusb",
+ "description": "Bad named entity: plusb without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&plusb"
+ ]
+ ]
+ },
+ {
+ "input": "⊞",
+ "description": "Named entity: plusb; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u229e"
+ ]
+ ]
+ },
+ {
+ "input": "&pluscir",
+ "description": "Bad named entity: pluscir without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&pluscir"
+ ]
+ ]
+ },
+ {
+ "input": "⨢",
+ "description": "Named entity: pluscir; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a22"
+ ]
+ ]
+ },
+ {
+ "input": "&plusdo",
+ "description": "Bad named entity: plusdo without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&plusdo"
+ ]
+ ]
+ },
+ {
+ "input": "∔",
+ "description": "Named entity: plusdo; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2214"
+ ]
+ ]
+ },
+ {
+ "input": "&plusdu",
+ "description": "Bad named entity: plusdu without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&plusdu"
+ ]
+ ]
+ },
+ {
+ "input": "⨥",
+ "description": "Named entity: plusdu; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a25"
+ ]
+ ]
+ },
+ {
+ "input": "&pluse",
+ "description": "Bad named entity: pluse without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&pluse"
+ ]
+ ]
+ },
+ {
+ "input": "⩲",
+ "description": "Named entity: pluse; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a72"
+ ]
+ ]
+ },
+ {
+ "input": "±",
+ "description": "Named entity: plusmn without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00b1"
+ ]
+ ]
+ },
+ {
+ "input": "±",
+ "description": "Named entity: plusmn; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00b1"
+ ]
+ ]
+ },
+ {
+ "input": "&plussim",
+ "description": "Bad named entity: plussim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&plussim"
+ ]
+ ]
+ },
+ {
+ "input": "⨦",
+ "description": "Named entity: plussim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a26"
+ ]
+ ]
+ },
+ {
+ "input": "&plustwo",
+ "description": "Bad named entity: plustwo without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&plustwo"
+ ]
+ ]
+ },
+ {
+ "input": "⨧",
+ "description": "Named entity: plustwo; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a27"
+ ]
+ ]
+ },
+ {
+ "input": "&pm",
+ "description": "Bad named entity: pm without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&pm"
+ ]
+ ]
+ },
+ {
+ "input": "±",
+ "description": "Named entity: pm; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00b1"
+ ]
+ ]
+ },
+ {
+ "input": "&pointint",
+ "description": "Bad named entity: pointint without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&pointint"
+ ]
+ ]
+ },
+ {
+ "input": "⨕",
+ "description": "Named entity: pointint; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a15"
+ ]
+ ]
+ },
+ {
+ "input": "&popf",
+ "description": "Bad named entity: popf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&popf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕡",
+ "description": "Named entity: popf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd61"
+ ]
+ ]
+ },
+ {
+ "input": "£",
+ "description": "Named entity: pound without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00a3"
+ ]
+ ]
+ },
+ {
+ "input": "£",
+ "description": "Named entity: pound; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00a3"
+ ]
+ ]
+ },
+ {
+ "input": "&pr",
+ "description": "Bad named entity: pr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&pr"
+ ]
+ ]
+ },
+ {
+ "input": "≺",
+ "description": "Named entity: pr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u227a"
+ ]
+ ]
+ },
+ {
+ "input": "&prE",
+ "description": "Bad named entity: prE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&prE"
+ ]
+ ]
+ },
+ {
+ "input": "⪳",
+ "description": "Named entity: prE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ab3"
+ ]
+ ]
+ },
+ {
+ "input": "&prap",
+ "description": "Bad named entity: prap without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&prap"
+ ]
+ ]
+ },
+ {
+ "input": "⪷",
+ "description": "Named entity: prap; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ab7"
+ ]
+ ]
+ },
+ {
+ "input": "&prcue",
+ "description": "Bad named entity: prcue without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&prcue"
+ ]
+ ]
+ },
+ {
+ "input": "≼",
+ "description": "Named entity: prcue; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u227c"
+ ]
+ ]
+ },
+ {
+ "input": "&pre",
+ "description": "Bad named entity: pre without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&pre"
+ ]
+ ]
+ },
+ {
+ "input": "⪯",
+ "description": "Named entity: pre; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2aaf"
+ ]
+ ]
+ },
+ {
+ "input": "&prec",
+ "description": "Bad named entity: prec without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&prec"
+ ]
+ ]
+ },
+ {
+ "input": "≺",
+ "description": "Named entity: prec; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u227a"
+ ]
+ ]
+ },
+ {
+ "input": "&precapprox",
+ "description": "Bad named entity: precapprox without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&precapprox"
+ ]
+ ]
+ },
+ {
+ "input": "⪷",
+ "description": "Named entity: precapprox; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ab7"
+ ]
+ ]
+ },
+ {
+ "input": "&preccurlyeq",
+ "description": "Bad named entity: preccurlyeq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&preccurlyeq"
+ ]
+ ]
+ },
+ {
+ "input": "≼",
+ "description": "Named entity: preccurlyeq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u227c"
+ ]
+ ]
+ },
+ {
+ "input": "&preceq",
+ "description": "Bad named entity: preceq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&preceq"
+ ]
+ ]
+ },
+ {
+ "input": "⪯",
+ "description": "Named entity: preceq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2aaf"
+ ]
+ ]
+ },
+ {
+ "input": "&precnapprox",
+ "description": "Bad named entity: precnapprox without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&precnapprox"
+ ]
+ ]
+ },
+ {
+ "input": "⪹",
+ "description": "Named entity: precnapprox; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ab9"
+ ]
+ ]
+ },
+ {
+ "input": "&precneqq",
+ "description": "Bad named entity: precneqq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&precneqq"
+ ]
+ ]
+ },
+ {
+ "input": "⪵",
+ "description": "Named entity: precneqq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ab5"
+ ]
+ ]
+ },
+ {
+ "input": "&precnsim",
+ "description": "Bad named entity: precnsim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&precnsim"
+ ]
+ ]
+ },
+ {
+ "input": "⋨",
+ "description": "Named entity: precnsim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22e8"
+ ]
+ ]
+ },
+ {
+ "input": "&precsim",
+ "description": "Bad named entity: precsim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&precsim"
+ ]
+ ]
+ },
+ {
+ "input": "≾",
+ "description": "Named entity: precsim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u227e"
+ ]
+ ]
+ },
+ {
+ "input": "&prime",
+ "description": "Bad named entity: prime without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&prime"
+ ]
+ ]
+ },
+ {
+ "input": "′",
+ "description": "Named entity: prime; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2032"
+ ]
+ ]
+ },
+ {
+ "input": "&primes",
+ "description": "Bad named entity: primes without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&primes"
+ ]
+ ]
+ },
+ {
+ "input": "ℙ",
+ "description": "Named entity: primes; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2119"
+ ]
+ ]
+ },
+ {
+ "input": "&prnE",
+ "description": "Bad named entity: prnE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&prnE"
+ ]
+ ]
+ },
+ {
+ "input": "⪵",
+ "description": "Named entity: prnE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ab5"
+ ]
+ ]
+ },
+ {
+ "input": "&prnap",
+ "description": "Bad named entity: prnap without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&prnap"
+ ]
+ ]
+ },
+ {
+ "input": "⪹",
+ "description": "Named entity: prnap; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ab9"
+ ]
+ ]
+ },
+ {
+ "input": "&prnsim",
+ "description": "Bad named entity: prnsim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&prnsim"
+ ]
+ ]
+ },
+ {
+ "input": "⋨",
+ "description": "Named entity: prnsim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22e8"
+ ]
+ ]
+ },
+ {
+ "input": "&prod",
+ "description": "Bad named entity: prod without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&prod"
+ ]
+ ]
+ },
+ {
+ "input": "∏",
+ "description": "Named entity: prod; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u220f"
+ ]
+ ]
+ },
+ {
+ "input": "&profalar",
+ "description": "Bad named entity: profalar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&profalar"
+ ]
+ ]
+ },
+ {
+ "input": "⌮",
+ "description": "Named entity: profalar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u232e"
+ ]
+ ]
+ },
+ {
+ "input": "&profline",
+ "description": "Bad named entity: profline without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&profline"
+ ]
+ ]
+ },
+ {
+ "input": "⌒",
+ "description": "Named entity: profline; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2312"
+ ]
+ ]
+ },
+ {
+ "input": "&profsurf",
+ "description": "Bad named entity: profsurf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&profsurf"
+ ]
+ ]
+ },
+ {
+ "input": "⌓",
+ "description": "Named entity: profsurf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2313"
+ ]
+ ]
+ },
+ {
+ "input": "&prop",
+ "description": "Bad named entity: prop without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&prop"
+ ]
+ ]
+ },
+ {
+ "input": "∝",
+ "description": "Named entity: prop; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u221d"
+ ]
+ ]
+ },
+ {
+ "input": "&propto",
+ "description": "Bad named entity: propto without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&propto"
+ ]
+ ]
+ },
+ {
+ "input": "∝",
+ "description": "Named entity: propto; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u221d"
+ ]
+ ]
+ },
+ {
+ "input": "&prsim",
+ "description": "Bad named entity: prsim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&prsim"
+ ]
+ ]
+ },
+ {
+ "input": "≾",
+ "description": "Named entity: prsim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u227e"
+ ]
+ ]
+ },
+ {
+ "input": "&prurel",
+ "description": "Bad named entity: prurel without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&prurel"
+ ]
+ ]
+ },
+ {
+ "input": "⊰",
+ "description": "Named entity: prurel; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22b0"
+ ]
+ ]
+ },
+ {
+ "input": "&pscr",
+ "description": "Bad named entity: pscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&pscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝓅",
+ "description": "Named entity: pscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udcc5"
+ ]
+ ]
+ },
+ {
+ "input": "&psi",
+ "description": "Bad named entity: psi without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&psi"
+ ]
+ ]
+ },
+ {
+ "input": "ψ",
+ "description": "Named entity: psi; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03c8"
+ ]
+ ]
+ },
+ {
+ "input": "&puncsp",
+ "description": "Bad named entity: puncsp without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&puncsp"
+ ]
+ ]
+ },
+ {
+ "input": " ",
+ "description": "Named entity: puncsp; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2008"
+ ]
+ ]
+ },
+ {
+ "input": "&qfr",
+ "description": "Bad named entity: qfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&qfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔮",
+ "description": "Named entity: qfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd2e"
+ ]
+ ]
+ },
+ {
+ "input": "&qint",
+ "description": "Bad named entity: qint without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&qint"
+ ]
+ ]
+ },
+ {
+ "input": "⨌",
+ "description": "Named entity: qint; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a0c"
+ ]
+ ]
+ },
+ {
+ "input": "&qopf",
+ "description": "Bad named entity: qopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&qopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕢",
+ "description": "Named entity: qopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd62"
+ ]
+ ]
+ },
+ {
+ "input": "&qprime",
+ "description": "Bad named entity: qprime without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&qprime"
+ ]
+ ]
+ },
+ {
+ "input": "⁗",
+ "description": "Named entity: qprime; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2057"
+ ]
+ ]
+ },
+ {
+ "input": "&qscr",
+ "description": "Bad named entity: qscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&qscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝓆",
+ "description": "Named entity: qscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udcc6"
+ ]
+ ]
+ },
+ {
+ "input": "&quaternions",
+ "description": "Bad named entity: quaternions without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&quaternions"
+ ]
+ ]
+ },
+ {
+ "input": "ℍ",
+ "description": "Named entity: quaternions; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u210d"
+ ]
+ ]
+ },
+ {
+ "input": "&quatint",
+ "description": "Bad named entity: quatint without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&quatint"
+ ]
+ ]
+ },
+ {
+ "input": "⨖",
+ "description": "Named entity: quatint; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a16"
+ ]
+ ]
+ },
+ {
+ "input": "&quest",
+ "description": "Bad named entity: quest without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&quest"
+ ]
+ ]
+ },
+ {
+ "input": "?",
+ "description": "Named entity: quest; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "?"
+ ]
+ ]
+ },
+ {
+ "input": "&questeq",
+ "description": "Bad named entity: questeq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&questeq"
+ ]
+ ]
+ },
+ {
+ "input": "≟",
+ "description": "Named entity: questeq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u225f"
+ ]
+ ]
+ },
+ {
+ "input": """,
+ "description": "Named entity: quot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\""
+ ]
+ ]
+ },
+ {
+ "input": """,
+ "description": "Named entity: quot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\""
+ ]
+ ]
+ },
+ {
+ "input": "&rAarr",
+ "description": "Bad named entity: rAarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rAarr"
+ ]
+ ]
+ },
+ {
+ "input": "⇛",
+ "description": "Named entity: rAarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21db"
+ ]
+ ]
+ },
+ {
+ "input": "&rArr",
+ "description": "Bad named entity: rArr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rArr"
+ ]
+ ]
+ },
+ {
+ "input": "⇒",
+ "description": "Named entity: rArr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21d2"
+ ]
+ ]
+ },
+ {
+ "input": "&rAtail",
+ "description": "Bad named entity: rAtail without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rAtail"
+ ]
+ ]
+ },
+ {
+ "input": "⤜",
+ "description": "Named entity: rAtail; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u291c"
+ ]
+ ]
+ },
+ {
+ "input": "&rBarr",
+ "description": "Bad named entity: rBarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rBarr"
+ ]
+ ]
+ },
+ {
+ "input": "⤏",
+ "description": "Named entity: rBarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u290f"
+ ]
+ ]
+ },
+ {
+ "input": "&rHar",
+ "description": "Bad named entity: rHar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rHar"
+ ]
+ ]
+ },
+ {
+ "input": "⥤",
+ "description": "Named entity: rHar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2964"
+ ]
+ ]
+ },
+ {
+ "input": "&race",
+ "description": "Bad named entity: race without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&race"
+ ]
+ ]
+ },
+ {
+ "input": "∽̱",
+ "description": "Named entity: race; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u223d\u0331"
+ ]
+ ]
+ },
+ {
+ "input": "&racute",
+ "description": "Bad named entity: racute without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&racute"
+ ]
+ ]
+ },
+ {
+ "input": "ŕ",
+ "description": "Named entity: racute; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0155"
+ ]
+ ]
+ },
+ {
+ "input": "&radic",
+ "description": "Bad named entity: radic without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&radic"
+ ]
+ ]
+ },
+ {
+ "input": "√",
+ "description": "Named entity: radic; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u221a"
+ ]
+ ]
+ },
+ {
+ "input": "&raemptyv",
+ "description": "Bad named entity: raemptyv without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&raemptyv"
+ ]
+ ]
+ },
+ {
+ "input": "⦳",
+ "description": "Named entity: raemptyv; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29b3"
+ ]
+ ]
+ },
+ {
+ "input": "&rang",
+ "description": "Bad named entity: rang without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rang"
+ ]
+ ]
+ },
+ {
+ "input": "⟩",
+ "description": "Named entity: rang; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27e9"
+ ]
+ ]
+ },
+ {
+ "input": "&rangd",
+ "description": "Bad named entity: rangd without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rangd"
+ ]
+ ]
+ },
+ {
+ "input": "⦒",
+ "description": "Named entity: rangd; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2992"
+ ]
+ ]
+ },
+ {
+ "input": "&range",
+ "description": "Bad named entity: range without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&range"
+ ]
+ ]
+ },
+ {
+ "input": "⦥",
+ "description": "Named entity: range; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29a5"
+ ]
+ ]
+ },
+ {
+ "input": "&rangle",
+ "description": "Bad named entity: rangle without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rangle"
+ ]
+ ]
+ },
+ {
+ "input": "⟩",
+ "description": "Named entity: rangle; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27e9"
+ ]
+ ]
+ },
+ {
+ "input": "»",
+ "description": "Named entity: raquo without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00bb"
+ ]
+ ]
+ },
+ {
+ "input": "»",
+ "description": "Named entity: raquo; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00bb"
+ ]
+ ]
+ },
+ {
+ "input": "&rarr",
+ "description": "Bad named entity: rarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rarr"
+ ]
+ ]
+ },
+ {
+ "input": "→",
+ "description": "Named entity: rarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2192"
+ ]
+ ]
+ },
+ {
+ "input": "&rarrap",
+ "description": "Bad named entity: rarrap without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rarrap"
+ ]
+ ]
+ },
+ {
+ "input": "⥵",
+ "description": "Named entity: rarrap; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2975"
+ ]
+ ]
+ },
+ {
+ "input": "&rarrb",
+ "description": "Bad named entity: rarrb without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rarrb"
+ ]
+ ]
+ },
+ {
+ "input": "⇥",
+ "description": "Named entity: rarrb; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21e5"
+ ]
+ ]
+ },
+ {
+ "input": "&rarrbfs",
+ "description": "Bad named entity: rarrbfs without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rarrbfs"
+ ]
+ ]
+ },
+ {
+ "input": "⤠",
+ "description": "Named entity: rarrbfs; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2920"
+ ]
+ ]
+ },
+ {
+ "input": "&rarrc",
+ "description": "Bad named entity: rarrc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rarrc"
+ ]
+ ]
+ },
+ {
+ "input": "⤳",
+ "description": "Named entity: rarrc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2933"
+ ]
+ ]
+ },
+ {
+ "input": "&rarrfs",
+ "description": "Bad named entity: rarrfs without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rarrfs"
+ ]
+ ]
+ },
+ {
+ "input": "⤞",
+ "description": "Named entity: rarrfs; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u291e"
+ ]
+ ]
+ },
+ {
+ "input": "&rarrhk",
+ "description": "Bad named entity: rarrhk without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rarrhk"
+ ]
+ ]
+ },
+ {
+ "input": "↪",
+ "description": "Named entity: rarrhk; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21aa"
+ ]
+ ]
+ },
+ {
+ "input": "&rarrlp",
+ "description": "Bad named entity: rarrlp without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rarrlp"
+ ]
+ ]
+ },
+ {
+ "input": "↬",
+ "description": "Named entity: rarrlp; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21ac"
+ ]
+ ]
+ },
+ {
+ "input": "&rarrpl",
+ "description": "Bad named entity: rarrpl without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rarrpl"
+ ]
+ ]
+ },
+ {
+ "input": "⥅",
+ "description": "Named entity: rarrpl; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2945"
+ ]
+ ]
+ },
+ {
+ "input": "&rarrsim",
+ "description": "Bad named entity: rarrsim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rarrsim"
+ ]
+ ]
+ },
+ {
+ "input": "⥴",
+ "description": "Named entity: rarrsim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2974"
+ ]
+ ]
+ },
+ {
+ "input": "&rarrtl",
+ "description": "Bad named entity: rarrtl without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rarrtl"
+ ]
+ ]
+ },
+ {
+ "input": "↣",
+ "description": "Named entity: rarrtl; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21a3"
+ ]
+ ]
+ },
+ {
+ "input": "&rarrw",
+ "description": "Bad named entity: rarrw without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rarrw"
+ ]
+ ]
+ },
+ {
+ "input": "↝",
+ "description": "Named entity: rarrw; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u219d"
+ ]
+ ]
+ },
+ {
+ "input": "&ratail",
+ "description": "Bad named entity: ratail without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ratail"
+ ]
+ ]
+ },
+ {
+ "input": "⤚",
+ "description": "Named entity: ratail; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u291a"
+ ]
+ ]
+ },
+ {
+ "input": "&ratio",
+ "description": "Bad named entity: ratio without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ratio"
+ ]
+ ]
+ },
+ {
+ "input": "∶",
+ "description": "Named entity: ratio; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2236"
+ ]
+ ]
+ },
+ {
+ "input": "&rationals",
+ "description": "Bad named entity: rationals without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rationals"
+ ]
+ ]
+ },
+ {
+ "input": "ℚ",
+ "description": "Named entity: rationals; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u211a"
+ ]
+ ]
+ },
+ {
+ "input": "&rbarr",
+ "description": "Bad named entity: rbarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rbarr"
+ ]
+ ]
+ },
+ {
+ "input": "⤍",
+ "description": "Named entity: rbarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u290d"
+ ]
+ ]
+ },
+ {
+ "input": "&rbbrk",
+ "description": "Bad named entity: rbbrk without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rbbrk"
+ ]
+ ]
+ },
+ {
+ "input": "❳",
+ "description": "Named entity: rbbrk; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2773"
+ ]
+ ]
+ },
+ {
+ "input": "&rbrace",
+ "description": "Bad named entity: rbrace without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rbrace"
+ ]
+ ]
+ },
+ {
+ "input": "}",
+ "description": "Named entity: rbrace; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "}"
+ ]
+ ]
+ },
+ {
+ "input": "&rbrack",
+ "description": "Bad named entity: rbrack without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rbrack"
+ ]
+ ]
+ },
+ {
+ "input": "]",
+ "description": "Named entity: rbrack; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "]"
+ ]
+ ]
+ },
+ {
+ "input": "&rbrke",
+ "description": "Bad named entity: rbrke without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rbrke"
+ ]
+ ]
+ },
+ {
+ "input": "⦌",
+ "description": "Named entity: rbrke; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u298c"
+ ]
+ ]
+ },
+ {
+ "input": "&rbrksld",
+ "description": "Bad named entity: rbrksld without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rbrksld"
+ ]
+ ]
+ },
+ {
+ "input": "⦎",
+ "description": "Named entity: rbrksld; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u298e"
+ ]
+ ]
+ },
+ {
+ "input": "&rbrkslu",
+ "description": "Bad named entity: rbrkslu without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rbrkslu"
+ ]
+ ]
+ },
+ {
+ "input": "⦐",
+ "description": "Named entity: rbrkslu; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2990"
+ ]
+ ]
+ },
+ {
+ "input": "&rcaron",
+ "description": "Bad named entity: rcaron without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rcaron"
+ ]
+ ]
+ },
+ {
+ "input": "ř",
+ "description": "Named entity: rcaron; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0159"
+ ]
+ ]
+ },
+ {
+ "input": "&rcedil",
+ "description": "Bad named entity: rcedil without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rcedil"
+ ]
+ ]
+ },
+ {
+ "input": "ŗ",
+ "description": "Named entity: rcedil; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0157"
+ ]
+ ]
+ },
+ {
+ "input": "&rceil",
+ "description": "Bad named entity: rceil without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rceil"
+ ]
+ ]
+ },
+ {
+ "input": "⌉",
+ "description": "Named entity: rceil; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2309"
+ ]
+ ]
+ },
+ {
+ "input": "&rcub",
+ "description": "Bad named entity: rcub without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rcub"
+ ]
+ ]
+ },
+ {
+ "input": "}",
+ "description": "Named entity: rcub; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "}"
+ ]
+ ]
+ },
+ {
+ "input": "&rcy",
+ "description": "Bad named entity: rcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rcy"
+ ]
+ ]
+ },
+ {
+ "input": "р",
+ "description": "Named entity: rcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0440"
+ ]
+ ]
+ },
+ {
+ "input": "&rdca",
+ "description": "Bad named entity: rdca without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rdca"
+ ]
+ ]
+ },
+ {
+ "input": "⤷",
+ "description": "Named entity: rdca; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2937"
+ ]
+ ]
+ },
+ {
+ "input": "&rdldhar",
+ "description": "Bad named entity: rdldhar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rdldhar"
+ ]
+ ]
+ },
+ {
+ "input": "⥩",
+ "description": "Named entity: rdldhar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2969"
+ ]
+ ]
+ },
+ {
+ "input": "&rdquo",
+ "description": "Bad named entity: rdquo without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rdquo"
+ ]
+ ]
+ },
+ {
+ "input": "”",
+ "description": "Named entity: rdquo; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u201d"
+ ]
+ ]
+ },
+ {
+ "input": "&rdquor",
+ "description": "Bad named entity: rdquor without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rdquor"
+ ]
+ ]
+ },
+ {
+ "input": "”",
+ "description": "Named entity: rdquor; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u201d"
+ ]
+ ]
+ },
+ {
+ "input": "&rdsh",
+ "description": "Bad named entity: rdsh without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rdsh"
+ ]
+ ]
+ },
+ {
+ "input": "↳",
+ "description": "Named entity: rdsh; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21b3"
+ ]
+ ]
+ },
+ {
+ "input": "&real",
+ "description": "Bad named entity: real without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&real"
+ ]
+ ]
+ },
+ {
+ "input": "ℜ",
+ "description": "Named entity: real; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u211c"
+ ]
+ ]
+ },
+ {
+ "input": "&realine",
+ "description": "Bad named entity: realine without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&realine"
+ ]
+ ]
+ },
+ {
+ "input": "ℛ",
+ "description": "Named entity: realine; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u211b"
+ ]
+ ]
+ },
+ {
+ "input": "&realpart",
+ "description": "Bad named entity: realpart without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&realpart"
+ ]
+ ]
+ },
+ {
+ "input": "ℜ",
+ "description": "Named entity: realpart; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u211c"
+ ]
+ ]
+ },
+ {
+ "input": "&reals",
+ "description": "Bad named entity: reals without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&reals"
+ ]
+ ]
+ },
+ {
+ "input": "ℝ",
+ "description": "Named entity: reals; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u211d"
+ ]
+ ]
+ },
+ {
+ "input": "&rect",
+ "description": "Bad named entity: rect without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rect"
+ ]
+ ]
+ },
+ {
+ "input": "▭",
+ "description": "Named entity: rect; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25ad"
+ ]
+ ]
+ },
+ {
+ "input": "®",
+ "description": "Named entity: reg without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00ae"
+ ]
+ ]
+ },
+ {
+ "input": "®",
+ "description": "Named entity: reg; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00ae"
+ ]
+ ]
+ },
+ {
+ "input": "&rfisht",
+ "description": "Bad named entity: rfisht without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rfisht"
+ ]
+ ]
+ },
+ {
+ "input": "⥽",
+ "description": "Named entity: rfisht; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u297d"
+ ]
+ ]
+ },
+ {
+ "input": "&rfloor",
+ "description": "Bad named entity: rfloor without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rfloor"
+ ]
+ ]
+ },
+ {
+ "input": "⌋",
+ "description": "Named entity: rfloor; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u230b"
+ ]
+ ]
+ },
+ {
+ "input": "&rfr",
+ "description": "Bad named entity: rfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔯",
+ "description": "Named entity: rfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd2f"
+ ]
+ ]
+ },
+ {
+ "input": "&rhard",
+ "description": "Bad named entity: rhard without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rhard"
+ ]
+ ]
+ },
+ {
+ "input": "⇁",
+ "description": "Named entity: rhard; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21c1"
+ ]
+ ]
+ },
+ {
+ "input": "&rharu",
+ "description": "Bad named entity: rharu without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rharu"
+ ]
+ ]
+ },
+ {
+ "input": "⇀",
+ "description": "Named entity: rharu; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21c0"
+ ]
+ ]
+ },
+ {
+ "input": "&rharul",
+ "description": "Bad named entity: rharul without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rharul"
+ ]
+ ]
+ },
+ {
+ "input": "⥬",
+ "description": "Named entity: rharul; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u296c"
+ ]
+ ]
+ },
+ {
+ "input": "&rho",
+ "description": "Bad named entity: rho without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rho"
+ ]
+ ]
+ },
+ {
+ "input": "ρ",
+ "description": "Named entity: rho; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03c1"
+ ]
+ ]
+ },
+ {
+ "input": "&rhov",
+ "description": "Bad named entity: rhov without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rhov"
+ ]
+ ]
+ },
+ {
+ "input": "ϱ",
+ "description": "Named entity: rhov; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03f1"
+ ]
+ ]
+ },
+ {
+ "input": "&rightarrow",
+ "description": "Bad named entity: rightarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rightarrow"
+ ]
+ ]
+ },
+ {
+ "input": "→",
+ "description": "Named entity: rightarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2192"
+ ]
+ ]
+ },
+ {
+ "input": "&rightarrowtail",
+ "description": "Bad named entity: rightarrowtail without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rightarrowtail"
+ ]
+ ]
+ },
+ {
+ "input": "↣",
+ "description": "Named entity: rightarrowtail; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21a3"
+ ]
+ ]
+ },
+ {
+ "input": "&rightharpoondown",
+ "description": "Bad named entity: rightharpoondown without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rightharpoondown"
+ ]
+ ]
+ },
+ {
+ "input": "⇁",
+ "description": "Named entity: rightharpoondown; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21c1"
+ ]
+ ]
+ },
+ {
+ "input": "&rightharpoonup",
+ "description": "Bad named entity: rightharpoonup without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rightharpoonup"
+ ]
+ ]
+ },
+ {
+ "input": "⇀",
+ "description": "Named entity: rightharpoonup; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21c0"
+ ]
+ ]
+ },
+ {
+ "input": "&rightleftarrows",
+ "description": "Bad named entity: rightleftarrows without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rightleftarrows"
+ ]
+ ]
+ },
+ {
+ "input": "⇄",
+ "description": "Named entity: rightleftarrows; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21c4"
+ ]
+ ]
+ },
+ {
+ "input": "&rightleftharpoons",
+ "description": "Bad named entity: rightleftharpoons without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rightleftharpoons"
+ ]
+ ]
+ },
+ {
+ "input": "⇌",
+ "description": "Named entity: rightleftharpoons; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21cc"
+ ]
+ ]
+ },
+ {
+ "input": "&rightrightarrows",
+ "description": "Bad named entity: rightrightarrows without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rightrightarrows"
+ ]
+ ]
+ },
+ {
+ "input": "⇉",
+ "description": "Named entity: rightrightarrows; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21c9"
+ ]
+ ]
+ },
+ {
+ "input": "&rightsquigarrow",
+ "description": "Bad named entity: rightsquigarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rightsquigarrow"
+ ]
+ ]
+ },
+ {
+ "input": "↝",
+ "description": "Named entity: rightsquigarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u219d"
+ ]
+ ]
+ },
+ {
+ "input": "&rightthreetimes",
+ "description": "Bad named entity: rightthreetimes without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rightthreetimes"
+ ]
+ ]
+ },
+ {
+ "input": "⋌",
+ "description": "Named entity: rightthreetimes; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22cc"
+ ]
+ ]
+ },
+ {
+ "input": "&ring",
+ "description": "Bad named entity: ring without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ring"
+ ]
+ ]
+ },
+ {
+ "input": "˚",
+ "description": "Named entity: ring; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u02da"
+ ]
+ ]
+ },
+ {
+ "input": "&risingdotseq",
+ "description": "Bad named entity: risingdotseq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&risingdotseq"
+ ]
+ ]
+ },
+ {
+ "input": "≓",
+ "description": "Named entity: risingdotseq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2253"
+ ]
+ ]
+ },
+ {
+ "input": "&rlarr",
+ "description": "Bad named entity: rlarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rlarr"
+ ]
+ ]
+ },
+ {
+ "input": "⇄",
+ "description": "Named entity: rlarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21c4"
+ ]
+ ]
+ },
+ {
+ "input": "&rlhar",
+ "description": "Bad named entity: rlhar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rlhar"
+ ]
+ ]
+ },
+ {
+ "input": "⇌",
+ "description": "Named entity: rlhar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21cc"
+ ]
+ ]
+ },
+ {
+ "input": "&rlm",
+ "description": "Bad named entity: rlm without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rlm"
+ ]
+ ]
+ },
+ {
+ "input": "‏",
+ "description": "Named entity: rlm; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u200f"
+ ]
+ ]
+ },
+ {
+ "input": "&rmoust",
+ "description": "Bad named entity: rmoust without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rmoust"
+ ]
+ ]
+ },
+ {
+ "input": "⎱",
+ "description": "Named entity: rmoust; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u23b1"
+ ]
+ ]
+ },
+ {
+ "input": "&rmoustache",
+ "description": "Bad named entity: rmoustache without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rmoustache"
+ ]
+ ]
+ },
+ {
+ "input": "⎱",
+ "description": "Named entity: rmoustache; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u23b1"
+ ]
+ ]
+ },
+ {
+ "input": "&rnmid",
+ "description": "Bad named entity: rnmid without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rnmid"
+ ]
+ ]
+ },
+ {
+ "input": "⫮",
+ "description": "Named entity: rnmid; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2aee"
+ ]
+ ]
+ },
+ {
+ "input": "&roang",
+ "description": "Bad named entity: roang without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&roang"
+ ]
+ ]
+ },
+ {
+ "input": "⟭",
+ "description": "Named entity: roang; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27ed"
+ ]
+ ]
+ },
+ {
+ "input": "&roarr",
+ "description": "Bad named entity: roarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&roarr"
+ ]
+ ]
+ },
+ {
+ "input": "⇾",
+ "description": "Named entity: roarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21fe"
+ ]
+ ]
+ },
+ {
+ "input": "&robrk",
+ "description": "Bad named entity: robrk without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&robrk"
+ ]
+ ]
+ },
+ {
+ "input": "⟧",
+ "description": "Named entity: robrk; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27e7"
+ ]
+ ]
+ },
+ {
+ "input": "&ropar",
+ "description": "Bad named entity: ropar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ropar"
+ ]
+ ]
+ },
+ {
+ "input": "⦆",
+ "description": "Named entity: ropar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2986"
+ ]
+ ]
+ },
+ {
+ "input": "&ropf",
+ "description": "Bad named entity: ropf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ropf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕣",
+ "description": "Named entity: ropf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd63"
+ ]
+ ]
+ },
+ {
+ "input": "&roplus",
+ "description": "Bad named entity: roplus without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&roplus"
+ ]
+ ]
+ },
+ {
+ "input": "⨮",
+ "description": "Named entity: roplus; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a2e"
+ ]
+ ]
+ },
+ {
+ "input": "&rotimes",
+ "description": "Bad named entity: rotimes without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rotimes"
+ ]
+ ]
+ },
+ {
+ "input": "⨵",
+ "description": "Named entity: rotimes; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a35"
+ ]
+ ]
+ },
+ {
+ "input": "&rpar",
+ "description": "Bad named entity: rpar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rpar"
+ ]
+ ]
+ },
+ {
+ "input": ")",
+ "description": "Named entity: rpar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ ")"
+ ]
+ ]
+ },
+ {
+ "input": "&rpargt",
+ "description": "Bad named entity: rpargt without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rpargt"
+ ]
+ ]
+ },
+ {
+ "input": "⦔",
+ "description": "Named entity: rpargt; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2994"
+ ]
+ ]
+ },
+ {
+ "input": "&rppolint",
+ "description": "Bad named entity: rppolint without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rppolint"
+ ]
+ ]
+ },
+ {
+ "input": "⨒",
+ "description": "Named entity: rppolint; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a12"
+ ]
+ ]
+ },
+ {
+ "input": "&rrarr",
+ "description": "Bad named entity: rrarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rrarr"
+ ]
+ ]
+ },
+ {
+ "input": "⇉",
+ "description": "Named entity: rrarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21c9"
+ ]
+ ]
+ },
+ {
+ "input": "&rsaquo",
+ "description": "Bad named entity: rsaquo without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rsaquo"
+ ]
+ ]
+ },
+ {
+ "input": "›",
+ "description": "Named entity: rsaquo; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u203a"
+ ]
+ ]
+ },
+ {
+ "input": "&rscr",
+ "description": "Bad named entity: rscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝓇",
+ "description": "Named entity: rscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udcc7"
+ ]
+ ]
+ },
+ {
+ "input": "&rsh",
+ "description": "Bad named entity: rsh without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rsh"
+ ]
+ ]
+ },
+ {
+ "input": "↱",
+ "description": "Named entity: rsh; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21b1"
+ ]
+ ]
+ },
+ {
+ "input": "&rsqb",
+ "description": "Bad named entity: rsqb without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rsqb"
+ ]
+ ]
+ },
+ {
+ "input": "]",
+ "description": "Named entity: rsqb; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "]"
+ ]
+ ]
+ },
+ {
+ "input": "&rsquo",
+ "description": "Bad named entity: rsquo without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rsquo"
+ ]
+ ]
+ },
+ {
+ "input": "’",
+ "description": "Named entity: rsquo; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2019"
+ ]
+ ]
+ },
+ {
+ "input": "&rsquor",
+ "description": "Bad named entity: rsquor without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rsquor"
+ ]
+ ]
+ },
+ {
+ "input": "’",
+ "description": "Named entity: rsquor; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2019"
+ ]
+ ]
+ },
+ {
+ "input": "&rthree",
+ "description": "Bad named entity: rthree without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rthree"
+ ]
+ ]
+ },
+ {
+ "input": "⋌",
+ "description": "Named entity: rthree; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22cc"
+ ]
+ ]
+ },
+ {
+ "input": "&rtimes",
+ "description": "Bad named entity: rtimes without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rtimes"
+ ]
+ ]
+ },
+ {
+ "input": "⋊",
+ "description": "Named entity: rtimes; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22ca"
+ ]
+ ]
+ },
+ {
+ "input": "&rtri",
+ "description": "Bad named entity: rtri without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rtri"
+ ]
+ ]
+ },
+ {
+ "input": "▹",
+ "description": "Named entity: rtri; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25b9"
+ ]
+ ]
+ },
+ {
+ "input": "&rtrie",
+ "description": "Bad named entity: rtrie without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rtrie"
+ ]
+ ]
+ },
+ {
+ "input": "⊵",
+ "description": "Named entity: rtrie; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22b5"
+ ]
+ ]
+ },
+ {
+ "input": "&rtrif",
+ "description": "Bad named entity: rtrif without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rtrif"
+ ]
+ ]
+ },
+ {
+ "input": "▸",
+ "description": "Named entity: rtrif; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25b8"
+ ]
+ ]
+ },
+ {
+ "input": "&rtriltri",
+ "description": "Bad named entity: rtriltri without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rtriltri"
+ ]
+ ]
+ },
+ {
+ "input": "⧎",
+ "description": "Named entity: rtriltri; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29ce"
+ ]
+ ]
+ },
+ {
+ "input": "&ruluhar",
+ "description": "Bad named entity: ruluhar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ruluhar"
+ ]
+ ]
+ },
+ {
+ "input": "⥨",
+ "description": "Named entity: ruluhar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2968"
+ ]
+ ]
+ },
+ {
+ "input": "&rx",
+ "description": "Bad named entity: rx without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&rx"
+ ]
+ ]
+ },
+ {
+ "input": "℞",
+ "description": "Named entity: rx; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u211e"
+ ]
+ ]
+ },
+ {
+ "input": "&sacute",
+ "description": "Bad named entity: sacute without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sacute"
+ ]
+ ]
+ },
+ {
+ "input": "ś",
+ "description": "Named entity: sacute; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u015b"
+ ]
+ ]
+ },
+ {
+ "input": "&sbquo",
+ "description": "Bad named entity: sbquo without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sbquo"
+ ]
+ ]
+ },
+ {
+ "input": "‚",
+ "description": "Named entity: sbquo; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u201a"
+ ]
+ ]
+ },
+ {
+ "input": "&sc",
+ "description": "Bad named entity: sc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sc"
+ ]
+ ]
+ },
+ {
+ "input": "≻",
+ "description": "Named entity: sc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u227b"
+ ]
+ ]
+ },
+ {
+ "input": "&scE",
+ "description": "Bad named entity: scE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&scE"
+ ]
+ ]
+ },
+ {
+ "input": "⪴",
+ "description": "Named entity: scE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ab4"
+ ]
+ ]
+ },
+ {
+ "input": "&scap",
+ "description": "Bad named entity: scap without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&scap"
+ ]
+ ]
+ },
+ {
+ "input": "⪸",
+ "description": "Named entity: scap; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ab8"
+ ]
+ ]
+ },
+ {
+ "input": "&scaron",
+ "description": "Bad named entity: scaron without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&scaron"
+ ]
+ ]
+ },
+ {
+ "input": "š",
+ "description": "Named entity: scaron; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0161"
+ ]
+ ]
+ },
+ {
+ "input": "&sccue",
+ "description": "Bad named entity: sccue without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sccue"
+ ]
+ ]
+ },
+ {
+ "input": "≽",
+ "description": "Named entity: sccue; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u227d"
+ ]
+ ]
+ },
+ {
+ "input": "&sce",
+ "description": "Bad named entity: sce without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sce"
+ ]
+ ]
+ },
+ {
+ "input": "⪰",
+ "description": "Named entity: sce; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ab0"
+ ]
+ ]
+ },
+ {
+ "input": "&scedil",
+ "description": "Bad named entity: scedil without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&scedil"
+ ]
+ ]
+ },
+ {
+ "input": "ş",
+ "description": "Named entity: scedil; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u015f"
+ ]
+ ]
+ },
+ {
+ "input": "&scirc",
+ "description": "Bad named entity: scirc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&scirc"
+ ]
+ ]
+ },
+ {
+ "input": "ŝ",
+ "description": "Named entity: scirc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u015d"
+ ]
+ ]
+ },
+ {
+ "input": "&scnE",
+ "description": "Bad named entity: scnE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&scnE"
+ ]
+ ]
+ },
+ {
+ "input": "⪶",
+ "description": "Named entity: scnE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ab6"
+ ]
+ ]
+ },
+ {
+ "input": "&scnap",
+ "description": "Bad named entity: scnap without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&scnap"
+ ]
+ ]
+ },
+ {
+ "input": "⪺",
+ "description": "Named entity: scnap; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2aba"
+ ]
+ ]
+ },
+ {
+ "input": "&scnsim",
+ "description": "Bad named entity: scnsim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&scnsim"
+ ]
+ ]
+ },
+ {
+ "input": "⋩",
+ "description": "Named entity: scnsim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22e9"
+ ]
+ ]
+ },
+ {
+ "input": "&scpolint",
+ "description": "Bad named entity: scpolint without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&scpolint"
+ ]
+ ]
+ },
+ {
+ "input": "⨓",
+ "description": "Named entity: scpolint; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a13"
+ ]
+ ]
+ },
+ {
+ "input": "&scsim",
+ "description": "Bad named entity: scsim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&scsim"
+ ]
+ ]
+ },
+ {
+ "input": "≿",
+ "description": "Named entity: scsim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u227f"
+ ]
+ ]
+ },
+ {
+ "input": "&scy",
+ "description": "Bad named entity: scy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&scy"
+ ]
+ ]
+ },
+ {
+ "input": "с",
+ "description": "Named entity: scy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0441"
+ ]
+ ]
+ },
+ {
+ "input": "&sdot",
+ "description": "Bad named entity: sdot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sdot"
+ ]
+ ]
+ },
+ {
+ "input": "⋅",
+ "description": "Named entity: sdot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22c5"
+ ]
+ ]
+ },
+ {
+ "input": "&sdotb",
+ "description": "Bad named entity: sdotb without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sdotb"
+ ]
+ ]
+ },
+ {
+ "input": "⊡",
+ "description": "Named entity: sdotb; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22a1"
+ ]
+ ]
+ },
+ {
+ "input": "&sdote",
+ "description": "Bad named entity: sdote without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sdote"
+ ]
+ ]
+ },
+ {
+ "input": "⩦",
+ "description": "Named entity: sdote; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a66"
+ ]
+ ]
+ },
+ {
+ "input": "&seArr",
+ "description": "Bad named entity: seArr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&seArr"
+ ]
+ ]
+ },
+ {
+ "input": "⇘",
+ "description": "Named entity: seArr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21d8"
+ ]
+ ]
+ },
+ {
+ "input": "&searhk",
+ "description": "Bad named entity: searhk without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&searhk"
+ ]
+ ]
+ },
+ {
+ "input": "⤥",
+ "description": "Named entity: searhk; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2925"
+ ]
+ ]
+ },
+ {
+ "input": "&searr",
+ "description": "Bad named entity: searr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&searr"
+ ]
+ ]
+ },
+ {
+ "input": "↘",
+ "description": "Named entity: searr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2198"
+ ]
+ ]
+ },
+ {
+ "input": "&searrow",
+ "description": "Bad named entity: searrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&searrow"
+ ]
+ ]
+ },
+ {
+ "input": "↘",
+ "description": "Named entity: searrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2198"
+ ]
+ ]
+ },
+ {
+ "input": "§",
+ "description": "Named entity: sect without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00a7"
+ ]
+ ]
+ },
+ {
+ "input": "§",
+ "description": "Named entity: sect; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00a7"
+ ]
+ ]
+ },
+ {
+ "input": "&semi",
+ "description": "Bad named entity: semi without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&semi"
+ ]
+ ]
+ },
+ {
+ "input": ";",
+ "description": "Named entity: semi; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ ";"
+ ]
+ ]
+ },
+ {
+ "input": "&seswar",
+ "description": "Bad named entity: seswar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&seswar"
+ ]
+ ]
+ },
+ {
+ "input": "⤩",
+ "description": "Named entity: seswar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2929"
+ ]
+ ]
+ },
+ {
+ "input": "&setminus",
+ "description": "Bad named entity: setminus without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&setminus"
+ ]
+ ]
+ },
+ {
+ "input": "∖",
+ "description": "Named entity: setminus; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2216"
+ ]
+ ]
+ },
+ {
+ "input": "&setmn",
+ "description": "Bad named entity: setmn without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&setmn"
+ ]
+ ]
+ },
+ {
+ "input": "∖",
+ "description": "Named entity: setmn; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2216"
+ ]
+ ]
+ },
+ {
+ "input": "&sext",
+ "description": "Bad named entity: sext without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sext"
+ ]
+ ]
+ },
+ {
+ "input": "✶",
+ "description": "Named entity: sext; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2736"
+ ]
+ ]
+ },
+ {
+ "input": "&sfr",
+ "description": "Bad named entity: sfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔰",
+ "description": "Named entity: sfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd30"
+ ]
+ ]
+ },
+ {
+ "input": "&sfrown",
+ "description": "Bad named entity: sfrown without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sfrown"
+ ]
+ ]
+ },
+ {
+ "input": "⌢",
+ "description": "Named entity: sfrown; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2322"
+ ]
+ ]
+ },
+ {
+ "input": "&sharp",
+ "description": "Bad named entity: sharp without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sharp"
+ ]
+ ]
+ },
+ {
+ "input": "♯",
+ "description": "Named entity: sharp; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u266f"
+ ]
+ ]
+ },
+ {
+ "input": "&shchcy",
+ "description": "Bad named entity: shchcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&shchcy"
+ ]
+ ]
+ },
+ {
+ "input": "щ",
+ "description": "Named entity: shchcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0449"
+ ]
+ ]
+ },
+ {
+ "input": "&shcy",
+ "description": "Bad named entity: shcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&shcy"
+ ]
+ ]
+ },
+ {
+ "input": "ш",
+ "description": "Named entity: shcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0448"
+ ]
+ ]
+ },
+ {
+ "input": "&shortmid",
+ "description": "Bad named entity: shortmid without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&shortmid"
+ ]
+ ]
+ },
+ {
+ "input": "∣",
+ "description": "Named entity: shortmid; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2223"
+ ]
+ ]
+ },
+ {
+ "input": "&shortparallel",
+ "description": "Bad named entity: shortparallel without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&shortparallel"
+ ]
+ ]
+ },
+ {
+ "input": "∥",
+ "description": "Named entity: shortparallel; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2225"
+ ]
+ ]
+ },
+ {
+ "input": "­",
+ "description": "Named entity: shy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00ad"
+ ]
+ ]
+ },
+ {
+ "input": "­",
+ "description": "Named entity: shy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00ad"
+ ]
+ ]
+ },
+ {
+ "input": "&sigma",
+ "description": "Bad named entity: sigma without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sigma"
+ ]
+ ]
+ },
+ {
+ "input": "σ",
+ "description": "Named entity: sigma; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03c3"
+ ]
+ ]
+ },
+ {
+ "input": "&sigmaf",
+ "description": "Bad named entity: sigmaf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sigmaf"
+ ]
+ ]
+ },
+ {
+ "input": "ς",
+ "description": "Named entity: sigmaf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03c2"
+ ]
+ ]
+ },
+ {
+ "input": "&sigmav",
+ "description": "Bad named entity: sigmav without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sigmav"
+ ]
+ ]
+ },
+ {
+ "input": "ς",
+ "description": "Named entity: sigmav; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03c2"
+ ]
+ ]
+ },
+ {
+ "input": "&sim",
+ "description": "Bad named entity: sim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sim"
+ ]
+ ]
+ },
+ {
+ "input": "∼",
+ "description": "Named entity: sim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u223c"
+ ]
+ ]
+ },
+ {
+ "input": "&simdot",
+ "description": "Bad named entity: simdot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&simdot"
+ ]
+ ]
+ },
+ {
+ "input": "⩪",
+ "description": "Named entity: simdot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a6a"
+ ]
+ ]
+ },
+ {
+ "input": "&sime",
+ "description": "Bad named entity: sime without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sime"
+ ]
+ ]
+ },
+ {
+ "input": "≃",
+ "description": "Named entity: sime; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2243"
+ ]
+ ]
+ },
+ {
+ "input": "&simeq",
+ "description": "Bad named entity: simeq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&simeq"
+ ]
+ ]
+ },
+ {
+ "input": "≃",
+ "description": "Named entity: simeq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2243"
+ ]
+ ]
+ },
+ {
+ "input": "&simg",
+ "description": "Bad named entity: simg without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&simg"
+ ]
+ ]
+ },
+ {
+ "input": "⪞",
+ "description": "Named entity: simg; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a9e"
+ ]
+ ]
+ },
+ {
+ "input": "&simgE",
+ "description": "Bad named entity: simgE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&simgE"
+ ]
+ ]
+ },
+ {
+ "input": "⪠",
+ "description": "Named entity: simgE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2aa0"
+ ]
+ ]
+ },
+ {
+ "input": "&siml",
+ "description": "Bad named entity: siml without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&siml"
+ ]
+ ]
+ },
+ {
+ "input": "⪝",
+ "description": "Named entity: siml; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a9d"
+ ]
+ ]
+ },
+ {
+ "input": "&simlE",
+ "description": "Bad named entity: simlE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&simlE"
+ ]
+ ]
+ },
+ {
+ "input": "⪟",
+ "description": "Named entity: simlE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a9f"
+ ]
+ ]
+ },
+ {
+ "input": "&simne",
+ "description": "Bad named entity: simne without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&simne"
+ ]
+ ]
+ },
+ {
+ "input": "≆",
+ "description": "Named entity: simne; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2246"
+ ]
+ ]
+ },
+ {
+ "input": "&simplus",
+ "description": "Bad named entity: simplus without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&simplus"
+ ]
+ ]
+ },
+ {
+ "input": "⨤",
+ "description": "Named entity: simplus; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a24"
+ ]
+ ]
+ },
+ {
+ "input": "&simrarr",
+ "description": "Bad named entity: simrarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&simrarr"
+ ]
+ ]
+ },
+ {
+ "input": "⥲",
+ "description": "Named entity: simrarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2972"
+ ]
+ ]
+ },
+ {
+ "input": "&slarr",
+ "description": "Bad named entity: slarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&slarr"
+ ]
+ ]
+ },
+ {
+ "input": "←",
+ "description": "Named entity: slarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2190"
+ ]
+ ]
+ },
+ {
+ "input": "&smallsetminus",
+ "description": "Bad named entity: smallsetminus without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&smallsetminus"
+ ]
+ ]
+ },
+ {
+ "input": "∖",
+ "description": "Named entity: smallsetminus; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2216"
+ ]
+ ]
+ },
+ {
+ "input": "&smashp",
+ "description": "Bad named entity: smashp without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&smashp"
+ ]
+ ]
+ },
+ {
+ "input": "⨳",
+ "description": "Named entity: smashp; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a33"
+ ]
+ ]
+ },
+ {
+ "input": "&smeparsl",
+ "description": "Bad named entity: smeparsl without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&smeparsl"
+ ]
+ ]
+ },
+ {
+ "input": "⧤",
+ "description": "Named entity: smeparsl; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29e4"
+ ]
+ ]
+ },
+ {
+ "input": "&smid",
+ "description": "Bad named entity: smid without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&smid"
+ ]
+ ]
+ },
+ {
+ "input": "∣",
+ "description": "Named entity: smid; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2223"
+ ]
+ ]
+ },
+ {
+ "input": "&smile",
+ "description": "Bad named entity: smile without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&smile"
+ ]
+ ]
+ },
+ {
+ "input": "⌣",
+ "description": "Named entity: smile; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2323"
+ ]
+ ]
+ },
+ {
+ "input": "&smt",
+ "description": "Bad named entity: smt without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&smt"
+ ]
+ ]
+ },
+ {
+ "input": "⪪",
+ "description": "Named entity: smt; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2aaa"
+ ]
+ ]
+ },
+ {
+ "input": "&smte",
+ "description": "Bad named entity: smte without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&smte"
+ ]
+ ]
+ },
+ {
+ "input": "⪬",
+ "description": "Named entity: smte; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2aac"
+ ]
+ ]
+ },
+ {
+ "input": "&smtes",
+ "description": "Bad named entity: smtes without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&smtes"
+ ]
+ ]
+ },
+ {
+ "input": "⪬︀",
+ "description": "Named entity: smtes; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2aac\ufe00"
+ ]
+ ]
+ },
+ {
+ "input": "&softcy",
+ "description": "Bad named entity: softcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&softcy"
+ ]
+ ]
+ },
+ {
+ "input": "ь",
+ "description": "Named entity: softcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u044c"
+ ]
+ ]
+ },
+ {
+ "input": "&sol",
+ "description": "Bad named entity: sol without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sol"
+ ]
+ ]
+ },
+ {
+ "input": "/",
+ "description": "Named entity: sol; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "/"
+ ]
+ ]
+ },
+ {
+ "input": "&solb",
+ "description": "Bad named entity: solb without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&solb"
+ ]
+ ]
+ },
+ {
+ "input": "⧄",
+ "description": "Named entity: solb; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29c4"
+ ]
+ ]
+ },
+ {
+ "input": "&solbar",
+ "description": "Bad named entity: solbar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&solbar"
+ ]
+ ]
+ },
+ {
+ "input": "⌿",
+ "description": "Named entity: solbar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u233f"
+ ]
+ ]
+ },
+ {
+ "input": "&sopf",
+ "description": "Bad named entity: sopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕤",
+ "description": "Named entity: sopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd64"
+ ]
+ ]
+ },
+ {
+ "input": "&spades",
+ "description": "Bad named entity: spades without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&spades"
+ ]
+ ]
+ },
+ {
+ "input": "♠",
+ "description": "Named entity: spades; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2660"
+ ]
+ ]
+ },
+ {
+ "input": "&spadesuit",
+ "description": "Bad named entity: spadesuit without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&spadesuit"
+ ]
+ ]
+ },
+ {
+ "input": "♠",
+ "description": "Named entity: spadesuit; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2660"
+ ]
+ ]
+ },
+ {
+ "input": "&spar",
+ "description": "Bad named entity: spar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&spar"
+ ]
+ ]
+ },
+ {
+ "input": "∥",
+ "description": "Named entity: spar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2225"
+ ]
+ ]
+ },
+ {
+ "input": "&sqcap",
+ "description": "Bad named entity: sqcap without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sqcap"
+ ]
+ ]
+ },
+ {
+ "input": "⊓",
+ "description": "Named entity: sqcap; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2293"
+ ]
+ ]
+ },
+ {
+ "input": "&sqcaps",
+ "description": "Bad named entity: sqcaps without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sqcaps"
+ ]
+ ]
+ },
+ {
+ "input": "⊓︀",
+ "description": "Named entity: sqcaps; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2293\ufe00"
+ ]
+ ]
+ },
+ {
+ "input": "&sqcup",
+ "description": "Bad named entity: sqcup without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sqcup"
+ ]
+ ]
+ },
+ {
+ "input": "⊔",
+ "description": "Named entity: sqcup; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2294"
+ ]
+ ]
+ },
+ {
+ "input": "&sqcups",
+ "description": "Bad named entity: sqcups without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sqcups"
+ ]
+ ]
+ },
+ {
+ "input": "⊔︀",
+ "description": "Named entity: sqcups; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2294\ufe00"
+ ]
+ ]
+ },
+ {
+ "input": "&sqsub",
+ "description": "Bad named entity: sqsub without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sqsub"
+ ]
+ ]
+ },
+ {
+ "input": "⊏",
+ "description": "Named entity: sqsub; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u228f"
+ ]
+ ]
+ },
+ {
+ "input": "&sqsube",
+ "description": "Bad named entity: sqsube without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sqsube"
+ ]
+ ]
+ },
+ {
+ "input": "⊑",
+ "description": "Named entity: sqsube; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2291"
+ ]
+ ]
+ },
+ {
+ "input": "&sqsubset",
+ "description": "Bad named entity: sqsubset without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sqsubset"
+ ]
+ ]
+ },
+ {
+ "input": "⊏",
+ "description": "Named entity: sqsubset; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u228f"
+ ]
+ ]
+ },
+ {
+ "input": "&sqsubseteq",
+ "description": "Bad named entity: sqsubseteq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sqsubseteq"
+ ]
+ ]
+ },
+ {
+ "input": "⊑",
+ "description": "Named entity: sqsubseteq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2291"
+ ]
+ ]
+ },
+ {
+ "input": "&sqsup",
+ "description": "Bad named entity: sqsup without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sqsup"
+ ]
+ ]
+ },
+ {
+ "input": "⊐",
+ "description": "Named entity: sqsup; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2290"
+ ]
+ ]
+ },
+ {
+ "input": "&sqsupe",
+ "description": "Bad named entity: sqsupe without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sqsupe"
+ ]
+ ]
+ },
+ {
+ "input": "⊒",
+ "description": "Named entity: sqsupe; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2292"
+ ]
+ ]
+ },
+ {
+ "input": "&sqsupset",
+ "description": "Bad named entity: sqsupset without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sqsupset"
+ ]
+ ]
+ },
+ {
+ "input": "⊐",
+ "description": "Named entity: sqsupset; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2290"
+ ]
+ ]
+ },
+ {
+ "input": "&sqsupseteq",
+ "description": "Bad named entity: sqsupseteq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sqsupseteq"
+ ]
+ ]
+ },
+ {
+ "input": "⊒",
+ "description": "Named entity: sqsupseteq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2292"
+ ]
+ ]
+ },
+ {
+ "input": "&squ",
+ "description": "Bad named entity: squ without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&squ"
+ ]
+ ]
+ },
+ {
+ "input": "□",
+ "description": "Named entity: squ; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25a1"
+ ]
+ ]
+ },
+ {
+ "input": "&square",
+ "description": "Bad named entity: square without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&square"
+ ]
+ ]
+ },
+ {
+ "input": "□",
+ "description": "Named entity: square; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25a1"
+ ]
+ ]
+ },
+ {
+ "input": "&squarf",
+ "description": "Bad named entity: squarf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&squarf"
+ ]
+ ]
+ },
+ {
+ "input": "▪",
+ "description": "Named entity: squarf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25aa"
+ ]
+ ]
+ },
+ {
+ "input": "&squf",
+ "description": "Bad named entity: squf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&squf"
+ ]
+ ]
+ },
+ {
+ "input": "▪",
+ "description": "Named entity: squf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25aa"
+ ]
+ ]
+ },
+ {
+ "input": "&srarr",
+ "description": "Bad named entity: srarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&srarr"
+ ]
+ ]
+ },
+ {
+ "input": "→",
+ "description": "Named entity: srarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2192"
+ ]
+ ]
+ },
+ {
+ "input": "&sscr",
+ "description": "Bad named entity: sscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝓈",
+ "description": "Named entity: sscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udcc8"
+ ]
+ ]
+ },
+ {
+ "input": "&ssetmn",
+ "description": "Bad named entity: ssetmn without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ssetmn"
+ ]
+ ]
+ },
+ {
+ "input": "∖",
+ "description": "Named entity: ssetmn; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2216"
+ ]
+ ]
+ },
+ {
+ "input": "&ssmile",
+ "description": "Bad named entity: ssmile without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ssmile"
+ ]
+ ]
+ },
+ {
+ "input": "⌣",
+ "description": "Named entity: ssmile; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2323"
+ ]
+ ]
+ },
+ {
+ "input": "&sstarf",
+ "description": "Bad named entity: sstarf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sstarf"
+ ]
+ ]
+ },
+ {
+ "input": "⋆",
+ "description": "Named entity: sstarf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22c6"
+ ]
+ ]
+ },
+ {
+ "input": "&star",
+ "description": "Bad named entity: star without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&star"
+ ]
+ ]
+ },
+ {
+ "input": "☆",
+ "description": "Named entity: star; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2606"
+ ]
+ ]
+ },
+ {
+ "input": "&starf",
+ "description": "Bad named entity: starf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&starf"
+ ]
+ ]
+ },
+ {
+ "input": "★",
+ "description": "Named entity: starf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2605"
+ ]
+ ]
+ },
+ {
+ "input": "&straightepsilon",
+ "description": "Bad named entity: straightepsilon without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&straightepsilon"
+ ]
+ ]
+ },
+ {
+ "input": "ϵ",
+ "description": "Named entity: straightepsilon; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03f5"
+ ]
+ ]
+ },
+ {
+ "input": "&straightphi",
+ "description": "Bad named entity: straightphi without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&straightphi"
+ ]
+ ]
+ },
+ {
+ "input": "ϕ",
+ "description": "Named entity: straightphi; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03d5"
+ ]
+ ]
+ },
+ {
+ "input": "&strns",
+ "description": "Bad named entity: strns without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&strns"
+ ]
+ ]
+ },
+ {
+ "input": "¯",
+ "description": "Named entity: strns; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00af"
+ ]
+ ]
+ },
+ {
+ "input": "&sub",
+ "description": "Bad named entity: sub without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sub"
+ ]
+ ]
+ },
+ {
+ "input": "⊂",
+ "description": "Named entity: sub; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2282"
+ ]
+ ]
+ },
+ {
+ "input": "&subE",
+ "description": "Bad named entity: subE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&subE"
+ ]
+ ]
+ },
+ {
+ "input": "⫅",
+ "description": "Named entity: subE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ac5"
+ ]
+ ]
+ },
+ {
+ "input": "&subdot",
+ "description": "Bad named entity: subdot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&subdot"
+ ]
+ ]
+ },
+ {
+ "input": "⪽",
+ "description": "Named entity: subdot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2abd"
+ ]
+ ]
+ },
+ {
+ "input": "&sube",
+ "description": "Bad named entity: sube without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sube"
+ ]
+ ]
+ },
+ {
+ "input": "⊆",
+ "description": "Named entity: sube; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2286"
+ ]
+ ]
+ },
+ {
+ "input": "&subedot",
+ "description": "Bad named entity: subedot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&subedot"
+ ]
+ ]
+ },
+ {
+ "input": "⫃",
+ "description": "Named entity: subedot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ac3"
+ ]
+ ]
+ },
+ {
+ "input": "&submult",
+ "description": "Bad named entity: submult without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&submult"
+ ]
+ ]
+ },
+ {
+ "input": "⫁",
+ "description": "Named entity: submult; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ac1"
+ ]
+ ]
+ },
+ {
+ "input": "&subnE",
+ "description": "Bad named entity: subnE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&subnE"
+ ]
+ ]
+ },
+ {
+ "input": "⫋",
+ "description": "Named entity: subnE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2acb"
+ ]
+ ]
+ },
+ {
+ "input": "&subne",
+ "description": "Bad named entity: subne without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&subne"
+ ]
+ ]
+ },
+ {
+ "input": "⊊",
+ "description": "Named entity: subne; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u228a"
+ ]
+ ]
+ },
+ {
+ "input": "&subplus",
+ "description": "Bad named entity: subplus without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&subplus"
+ ]
+ ]
+ },
+ {
+ "input": "⪿",
+ "description": "Named entity: subplus; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2abf"
+ ]
+ ]
+ },
+ {
+ "input": "&subrarr",
+ "description": "Bad named entity: subrarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&subrarr"
+ ]
+ ]
+ },
+ {
+ "input": "⥹",
+ "description": "Named entity: subrarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2979"
+ ]
+ ]
+ },
+ {
+ "input": "&subset",
+ "description": "Bad named entity: subset without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&subset"
+ ]
+ ]
+ },
+ {
+ "input": "⊂",
+ "description": "Named entity: subset; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2282"
+ ]
+ ]
+ },
+ {
+ "input": "&subseteq",
+ "description": "Bad named entity: subseteq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&subseteq"
+ ]
+ ]
+ },
+ {
+ "input": "⊆",
+ "description": "Named entity: subseteq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2286"
+ ]
+ ]
+ },
+ {
+ "input": "&subseteqq",
+ "description": "Bad named entity: subseteqq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&subseteqq"
+ ]
+ ]
+ },
+ {
+ "input": "⫅",
+ "description": "Named entity: subseteqq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ac5"
+ ]
+ ]
+ },
+ {
+ "input": "&subsetneq",
+ "description": "Bad named entity: subsetneq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&subsetneq"
+ ]
+ ]
+ },
+ {
+ "input": "⊊",
+ "description": "Named entity: subsetneq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u228a"
+ ]
+ ]
+ },
+ {
+ "input": "&subsetneqq",
+ "description": "Bad named entity: subsetneqq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&subsetneqq"
+ ]
+ ]
+ },
+ {
+ "input": "⫋",
+ "description": "Named entity: subsetneqq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2acb"
+ ]
+ ]
+ },
+ {
+ "input": "&subsim",
+ "description": "Bad named entity: subsim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&subsim"
+ ]
+ ]
+ },
+ {
+ "input": "⫇",
+ "description": "Named entity: subsim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ac7"
+ ]
+ ]
+ },
+ {
+ "input": "&subsub",
+ "description": "Bad named entity: subsub without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&subsub"
+ ]
+ ]
+ },
+ {
+ "input": "⫕",
+ "description": "Named entity: subsub; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ad5"
+ ]
+ ]
+ },
+ {
+ "input": "&subsup",
+ "description": "Bad named entity: subsup without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&subsup"
+ ]
+ ]
+ },
+ {
+ "input": "⫓",
+ "description": "Named entity: subsup; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ad3"
+ ]
+ ]
+ },
+ {
+ "input": "&succ",
+ "description": "Bad named entity: succ without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&succ"
+ ]
+ ]
+ },
+ {
+ "input": "≻",
+ "description": "Named entity: succ; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u227b"
+ ]
+ ]
+ },
+ {
+ "input": "&succapprox",
+ "description": "Bad named entity: succapprox without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&succapprox"
+ ]
+ ]
+ },
+ {
+ "input": "⪸",
+ "description": "Named entity: succapprox; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ab8"
+ ]
+ ]
+ },
+ {
+ "input": "&succcurlyeq",
+ "description": "Bad named entity: succcurlyeq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&succcurlyeq"
+ ]
+ ]
+ },
+ {
+ "input": "≽",
+ "description": "Named entity: succcurlyeq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u227d"
+ ]
+ ]
+ },
+ {
+ "input": "&succeq",
+ "description": "Bad named entity: succeq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&succeq"
+ ]
+ ]
+ },
+ {
+ "input": "⪰",
+ "description": "Named entity: succeq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ab0"
+ ]
+ ]
+ },
+ {
+ "input": "&succnapprox",
+ "description": "Bad named entity: succnapprox without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&succnapprox"
+ ]
+ ]
+ },
+ {
+ "input": "⪺",
+ "description": "Named entity: succnapprox; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2aba"
+ ]
+ ]
+ },
+ {
+ "input": "&succneqq",
+ "description": "Bad named entity: succneqq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&succneqq"
+ ]
+ ]
+ },
+ {
+ "input": "⪶",
+ "description": "Named entity: succneqq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ab6"
+ ]
+ ]
+ },
+ {
+ "input": "&succnsim",
+ "description": "Bad named entity: succnsim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&succnsim"
+ ]
+ ]
+ },
+ {
+ "input": "⋩",
+ "description": "Named entity: succnsim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22e9"
+ ]
+ ]
+ },
+ {
+ "input": "&succsim",
+ "description": "Bad named entity: succsim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&succsim"
+ ]
+ ]
+ },
+ {
+ "input": "≿",
+ "description": "Named entity: succsim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u227f"
+ ]
+ ]
+ },
+ {
+ "input": "&sum",
+ "description": "Bad named entity: sum without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sum"
+ ]
+ ]
+ },
+ {
+ "input": "∑",
+ "description": "Named entity: sum; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2211"
+ ]
+ ]
+ },
+ {
+ "input": "&sung",
+ "description": "Bad named entity: sung without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sung"
+ ]
+ ]
+ },
+ {
+ "input": "♪",
+ "description": "Named entity: sung; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u266a"
+ ]
+ ]
+ },
+ {
+ "input": "&sup",
+ "description": "Bad named entity: sup without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&sup"
+ ]
+ ]
+ },
+ {
+ "input": "¹",
+ "description": "Named entity: sup1 without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00b9"
+ ]
+ ]
+ },
+ {
+ "input": "¹",
+ "description": "Named entity: sup1; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00b9"
+ ]
+ ]
+ },
+ {
+ "input": "²",
+ "description": "Named entity: sup2 without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00b2"
+ ]
+ ]
+ },
+ {
+ "input": "²",
+ "description": "Named entity: sup2; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00b2"
+ ]
+ ]
+ },
+ {
+ "input": "³",
+ "description": "Named entity: sup3 without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00b3"
+ ]
+ ]
+ },
+ {
+ "input": "³",
+ "description": "Named entity: sup3; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00b3"
+ ]
+ ]
+ },
+ {
+ "input": "⊃",
+ "description": "Named entity: sup; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2283"
+ ]
+ ]
+ },
+ {
+ "input": "&supE",
+ "description": "Bad named entity: supE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&supE"
+ ]
+ ]
+ },
+ {
+ "input": "⫆",
+ "description": "Named entity: supE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ac6"
+ ]
+ ]
+ },
+ {
+ "input": "&supdot",
+ "description": "Bad named entity: supdot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&supdot"
+ ]
+ ]
+ },
+ {
+ "input": "⪾",
+ "description": "Named entity: supdot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2abe"
+ ]
+ ]
+ },
+ {
+ "input": "&supdsub",
+ "description": "Bad named entity: supdsub without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&supdsub"
+ ]
+ ]
+ },
+ {
+ "input": "⫘",
+ "description": "Named entity: supdsub; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ad8"
+ ]
+ ]
+ },
+ {
+ "input": "&supe",
+ "description": "Bad named entity: supe without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&supe"
+ ]
+ ]
+ },
+ {
+ "input": "⊇",
+ "description": "Named entity: supe; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2287"
+ ]
+ ]
+ },
+ {
+ "input": "&supedot",
+ "description": "Bad named entity: supedot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&supedot"
+ ]
+ ]
+ },
+ {
+ "input": "⫄",
+ "description": "Named entity: supedot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ac4"
+ ]
+ ]
+ },
+ {
+ "input": "&suphsol",
+ "description": "Bad named entity: suphsol without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&suphsol"
+ ]
+ ]
+ },
+ {
+ "input": "⟉",
+ "description": "Named entity: suphsol; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27c9"
+ ]
+ ]
+ },
+ {
+ "input": "&suphsub",
+ "description": "Bad named entity: suphsub without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&suphsub"
+ ]
+ ]
+ },
+ {
+ "input": "⫗",
+ "description": "Named entity: suphsub; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ad7"
+ ]
+ ]
+ },
+ {
+ "input": "&suplarr",
+ "description": "Bad named entity: suplarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&suplarr"
+ ]
+ ]
+ },
+ {
+ "input": "⥻",
+ "description": "Named entity: suplarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u297b"
+ ]
+ ]
+ },
+ {
+ "input": "&supmult",
+ "description": "Bad named entity: supmult without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&supmult"
+ ]
+ ]
+ },
+ {
+ "input": "⫂",
+ "description": "Named entity: supmult; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ac2"
+ ]
+ ]
+ },
+ {
+ "input": "&supnE",
+ "description": "Bad named entity: supnE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&supnE"
+ ]
+ ]
+ },
+ {
+ "input": "⫌",
+ "description": "Named entity: supnE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2acc"
+ ]
+ ]
+ },
+ {
+ "input": "&supne",
+ "description": "Bad named entity: supne without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&supne"
+ ]
+ ]
+ },
+ {
+ "input": "⊋",
+ "description": "Named entity: supne; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u228b"
+ ]
+ ]
+ },
+ {
+ "input": "&supplus",
+ "description": "Bad named entity: supplus without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&supplus"
+ ]
+ ]
+ },
+ {
+ "input": "⫀",
+ "description": "Named entity: supplus; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ac0"
+ ]
+ ]
+ },
+ {
+ "input": "&supset",
+ "description": "Bad named entity: supset without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&supset"
+ ]
+ ]
+ },
+ {
+ "input": "⊃",
+ "description": "Named entity: supset; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2283"
+ ]
+ ]
+ },
+ {
+ "input": "&supseteq",
+ "description": "Bad named entity: supseteq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&supseteq"
+ ]
+ ]
+ },
+ {
+ "input": "⊇",
+ "description": "Named entity: supseteq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2287"
+ ]
+ ]
+ },
+ {
+ "input": "&supseteqq",
+ "description": "Bad named entity: supseteqq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&supseteqq"
+ ]
+ ]
+ },
+ {
+ "input": "⫆",
+ "description": "Named entity: supseteqq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ac6"
+ ]
+ ]
+ },
+ {
+ "input": "&supsetneq",
+ "description": "Bad named entity: supsetneq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&supsetneq"
+ ]
+ ]
+ },
+ {
+ "input": "⊋",
+ "description": "Named entity: supsetneq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u228b"
+ ]
+ ]
+ },
+ {
+ "input": "&supsetneqq",
+ "description": "Bad named entity: supsetneqq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&supsetneqq"
+ ]
+ ]
+ },
+ {
+ "input": "⫌",
+ "description": "Named entity: supsetneqq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2acc"
+ ]
+ ]
+ },
+ {
+ "input": "&supsim",
+ "description": "Bad named entity: supsim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&supsim"
+ ]
+ ]
+ },
+ {
+ "input": "⫈",
+ "description": "Named entity: supsim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ac8"
+ ]
+ ]
+ },
+ {
+ "input": "&supsub",
+ "description": "Bad named entity: supsub without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&supsub"
+ ]
+ ]
+ },
+ {
+ "input": "⫔",
+ "description": "Named entity: supsub; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ad4"
+ ]
+ ]
+ },
+ {
+ "input": "&supsup",
+ "description": "Bad named entity: supsup without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&supsup"
+ ]
+ ]
+ },
+ {
+ "input": "⫖",
+ "description": "Named entity: supsup; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ad6"
+ ]
+ ]
+ },
+ {
+ "input": "&swArr",
+ "description": "Bad named entity: swArr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&swArr"
+ ]
+ ]
+ },
+ {
+ "input": "⇙",
+ "description": "Named entity: swArr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21d9"
+ ]
+ ]
+ },
+ {
+ "input": "&swarhk",
+ "description": "Bad named entity: swarhk without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&swarhk"
+ ]
+ ]
+ },
+ {
+ "input": "⤦",
+ "description": "Named entity: swarhk; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2926"
+ ]
+ ]
+ },
+ {
+ "input": "&swarr",
+ "description": "Bad named entity: swarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&swarr"
+ ]
+ ]
+ },
+ {
+ "input": "↙",
+ "description": "Named entity: swarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2199"
+ ]
+ ]
+ },
+ {
+ "input": "&swarrow",
+ "description": "Bad named entity: swarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&swarrow"
+ ]
+ ]
+ },
+ {
+ "input": "↙",
+ "description": "Named entity: swarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2199"
+ ]
+ ]
+ },
+ {
+ "input": "&swnwar",
+ "description": "Bad named entity: swnwar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&swnwar"
+ ]
+ ]
+ },
+ {
+ "input": "⤪",
+ "description": "Named entity: swnwar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u292a"
+ ]
+ ]
+ },
+ {
+ "input": "ß",
+ "description": "Named entity: szlig without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00df"
+ ]
+ ]
+ },
+ {
+ "input": "ß",
+ "description": "Named entity: szlig; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00df"
+ ]
+ ]
+ },
+ {
+ "input": "&target",
+ "description": "Bad named entity: target without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&target"
+ ]
+ ]
+ },
+ {
+ "input": "⌖",
+ "description": "Named entity: target; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2316"
+ ]
+ ]
+ },
+ {
+ "input": "&tau",
+ "description": "Bad named entity: tau without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&tau"
+ ]
+ ]
+ },
+ {
+ "input": "τ",
+ "description": "Named entity: tau; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03c4"
+ ]
+ ]
+ },
+ {
+ "input": "&tbrk",
+ "description": "Bad named entity: tbrk without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&tbrk"
+ ]
+ ]
+ },
+ {
+ "input": "⎴",
+ "description": "Named entity: tbrk; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u23b4"
+ ]
+ ]
+ },
+ {
+ "input": "&tcaron",
+ "description": "Bad named entity: tcaron without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&tcaron"
+ ]
+ ]
+ },
+ {
+ "input": "ť",
+ "description": "Named entity: tcaron; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0165"
+ ]
+ ]
+ },
+ {
+ "input": "&tcedil",
+ "description": "Bad named entity: tcedil without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&tcedil"
+ ]
+ ]
+ },
+ {
+ "input": "ţ",
+ "description": "Named entity: tcedil; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0163"
+ ]
+ ]
+ },
+ {
+ "input": "&tcy",
+ "description": "Bad named entity: tcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&tcy"
+ ]
+ ]
+ },
+ {
+ "input": "т",
+ "description": "Named entity: tcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0442"
+ ]
+ ]
+ },
+ {
+ "input": "&tdot",
+ "description": "Bad named entity: tdot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&tdot"
+ ]
+ ]
+ },
+ {
+ "input": "⃛",
+ "description": "Named entity: tdot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u20db"
+ ]
+ ]
+ },
+ {
+ "input": "&telrec",
+ "description": "Bad named entity: telrec without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&telrec"
+ ]
+ ]
+ },
+ {
+ "input": "⌕",
+ "description": "Named entity: telrec; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2315"
+ ]
+ ]
+ },
+ {
+ "input": "&tfr",
+ "description": "Bad named entity: tfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&tfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔱",
+ "description": "Named entity: tfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd31"
+ ]
+ ]
+ },
+ {
+ "input": "&there4",
+ "description": "Bad named entity: there4 without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&there4"
+ ]
+ ]
+ },
+ {
+ "input": "∴",
+ "description": "Named entity: there4; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2234"
+ ]
+ ]
+ },
+ {
+ "input": "&therefore",
+ "description": "Bad named entity: therefore without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&therefore"
+ ]
+ ]
+ },
+ {
+ "input": "∴",
+ "description": "Named entity: therefore; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2234"
+ ]
+ ]
+ },
+ {
+ "input": "&theta",
+ "description": "Bad named entity: theta without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&theta"
+ ]
+ ]
+ },
+ {
+ "input": "θ",
+ "description": "Named entity: theta; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03b8"
+ ]
+ ]
+ },
+ {
+ "input": "&thetasym",
+ "description": "Bad named entity: thetasym without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&thetasym"
+ ]
+ ]
+ },
+ {
+ "input": "ϑ",
+ "description": "Named entity: thetasym; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03d1"
+ ]
+ ]
+ },
+ {
+ "input": "&thetav",
+ "description": "Bad named entity: thetav without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&thetav"
+ ]
+ ]
+ },
+ {
+ "input": "ϑ",
+ "description": "Named entity: thetav; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03d1"
+ ]
+ ]
+ },
+ {
+ "input": "&thickapprox",
+ "description": "Bad named entity: thickapprox without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&thickapprox"
+ ]
+ ]
+ },
+ {
+ "input": "≈",
+ "description": "Named entity: thickapprox; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2248"
+ ]
+ ]
+ },
+ {
+ "input": "&thicksim",
+ "description": "Bad named entity: thicksim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&thicksim"
+ ]
+ ]
+ },
+ {
+ "input": "∼",
+ "description": "Named entity: thicksim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u223c"
+ ]
+ ]
+ },
+ {
+ "input": "&thinsp",
+ "description": "Bad named entity: thinsp without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&thinsp"
+ ]
+ ]
+ },
+ {
+ "input": " ",
+ "description": "Named entity: thinsp; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2009"
+ ]
+ ]
+ },
+ {
+ "input": "&thkap",
+ "description": "Bad named entity: thkap without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&thkap"
+ ]
+ ]
+ },
+ {
+ "input": "≈",
+ "description": "Named entity: thkap; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2248"
+ ]
+ ]
+ },
+ {
+ "input": "&thksim",
+ "description": "Bad named entity: thksim without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&thksim"
+ ]
+ ]
+ },
+ {
+ "input": "∼",
+ "description": "Named entity: thksim; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u223c"
+ ]
+ ]
+ },
+ {
+ "input": "þ",
+ "description": "Named entity: thorn without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00fe"
+ ]
+ ]
+ },
+ {
+ "input": "þ",
+ "description": "Named entity: thorn; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00fe"
+ ]
+ ]
+ },
+ {
+ "input": "&tilde",
+ "description": "Bad named entity: tilde without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&tilde"
+ ]
+ ]
+ },
+ {
+ "input": "˜",
+ "description": "Named entity: tilde; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u02dc"
+ ]
+ ]
+ },
+ {
+ "input": "×",
+ "description": "Named entity: times without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00d7"
+ ]
+ ]
+ },
+ {
+ "input": "×",
+ "description": "Named entity: times; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00d7"
+ ]
+ ]
+ },
+ {
+ "input": "⊠",
+ "description": "Named entity: timesb; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22a0"
+ ]
+ ]
+ },
+ {
+ "input": "⨱",
+ "description": "Named entity: timesbar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a31"
+ ]
+ ]
+ },
+ {
+ "input": "⨰",
+ "description": "Named entity: timesd; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a30"
+ ]
+ ]
+ },
+ {
+ "input": "&tint",
+ "description": "Bad named entity: tint without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&tint"
+ ]
+ ]
+ },
+ {
+ "input": "∭",
+ "description": "Named entity: tint; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u222d"
+ ]
+ ]
+ },
+ {
+ "input": "&toea",
+ "description": "Bad named entity: toea without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&toea"
+ ]
+ ]
+ },
+ {
+ "input": "⤨",
+ "description": "Named entity: toea; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2928"
+ ]
+ ]
+ },
+ {
+ "input": "&top",
+ "description": "Bad named entity: top without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&top"
+ ]
+ ]
+ },
+ {
+ "input": "⊤",
+ "description": "Named entity: top; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22a4"
+ ]
+ ]
+ },
+ {
+ "input": "&topbot",
+ "description": "Bad named entity: topbot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&topbot"
+ ]
+ ]
+ },
+ {
+ "input": "⌶",
+ "description": "Named entity: topbot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2336"
+ ]
+ ]
+ },
+ {
+ "input": "&topcir",
+ "description": "Bad named entity: topcir without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&topcir"
+ ]
+ ]
+ },
+ {
+ "input": "⫱",
+ "description": "Named entity: topcir; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2af1"
+ ]
+ ]
+ },
+ {
+ "input": "&topf",
+ "description": "Bad named entity: topf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&topf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕥",
+ "description": "Named entity: topf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd65"
+ ]
+ ]
+ },
+ {
+ "input": "&topfork",
+ "description": "Bad named entity: topfork without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&topfork"
+ ]
+ ]
+ },
+ {
+ "input": "⫚",
+ "description": "Named entity: topfork; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ada"
+ ]
+ ]
+ },
+ {
+ "input": "&tosa",
+ "description": "Bad named entity: tosa without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&tosa"
+ ]
+ ]
+ },
+ {
+ "input": "⤩",
+ "description": "Named entity: tosa; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2929"
+ ]
+ ]
+ },
+ {
+ "input": "&tprime",
+ "description": "Bad named entity: tprime without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&tprime"
+ ]
+ ]
+ },
+ {
+ "input": "‴",
+ "description": "Named entity: tprime; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2034"
+ ]
+ ]
+ },
+ {
+ "input": "&trade",
+ "description": "Bad named entity: trade without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&trade"
+ ]
+ ]
+ },
+ {
+ "input": "™",
+ "description": "Named entity: trade; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2122"
+ ]
+ ]
+ },
+ {
+ "input": "&triangle",
+ "description": "Bad named entity: triangle without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&triangle"
+ ]
+ ]
+ },
+ {
+ "input": "▵",
+ "description": "Named entity: triangle; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25b5"
+ ]
+ ]
+ },
+ {
+ "input": "&triangledown",
+ "description": "Bad named entity: triangledown without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&triangledown"
+ ]
+ ]
+ },
+ {
+ "input": "▿",
+ "description": "Named entity: triangledown; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25bf"
+ ]
+ ]
+ },
+ {
+ "input": "&triangleleft",
+ "description": "Bad named entity: triangleleft without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&triangleleft"
+ ]
+ ]
+ },
+ {
+ "input": "◃",
+ "description": "Named entity: triangleleft; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25c3"
+ ]
+ ]
+ },
+ {
+ "input": "&trianglelefteq",
+ "description": "Bad named entity: trianglelefteq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&trianglelefteq"
+ ]
+ ]
+ },
+ {
+ "input": "⊴",
+ "description": "Named entity: trianglelefteq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22b4"
+ ]
+ ]
+ },
+ {
+ "input": "&triangleq",
+ "description": "Bad named entity: triangleq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&triangleq"
+ ]
+ ]
+ },
+ {
+ "input": "≜",
+ "description": "Named entity: triangleq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u225c"
+ ]
+ ]
+ },
+ {
+ "input": "&triangleright",
+ "description": "Bad named entity: triangleright without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&triangleright"
+ ]
+ ]
+ },
+ {
+ "input": "▹",
+ "description": "Named entity: triangleright; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25b9"
+ ]
+ ]
+ },
+ {
+ "input": "&trianglerighteq",
+ "description": "Bad named entity: trianglerighteq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&trianglerighteq"
+ ]
+ ]
+ },
+ {
+ "input": "⊵",
+ "description": "Named entity: trianglerighteq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22b5"
+ ]
+ ]
+ },
+ {
+ "input": "&tridot",
+ "description": "Bad named entity: tridot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&tridot"
+ ]
+ ]
+ },
+ {
+ "input": "◬",
+ "description": "Named entity: tridot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25ec"
+ ]
+ ]
+ },
+ {
+ "input": "&trie",
+ "description": "Bad named entity: trie without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&trie"
+ ]
+ ]
+ },
+ {
+ "input": "≜",
+ "description": "Named entity: trie; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u225c"
+ ]
+ ]
+ },
+ {
+ "input": "&triminus",
+ "description": "Bad named entity: triminus without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&triminus"
+ ]
+ ]
+ },
+ {
+ "input": "⨺",
+ "description": "Named entity: triminus; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a3a"
+ ]
+ ]
+ },
+ {
+ "input": "&triplus",
+ "description": "Bad named entity: triplus without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&triplus"
+ ]
+ ]
+ },
+ {
+ "input": "⨹",
+ "description": "Named entity: triplus; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a39"
+ ]
+ ]
+ },
+ {
+ "input": "&trisb",
+ "description": "Bad named entity: trisb without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&trisb"
+ ]
+ ]
+ },
+ {
+ "input": "⧍",
+ "description": "Named entity: trisb; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29cd"
+ ]
+ ]
+ },
+ {
+ "input": "&tritime",
+ "description": "Bad named entity: tritime without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&tritime"
+ ]
+ ]
+ },
+ {
+ "input": "⨻",
+ "description": "Named entity: tritime; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a3b"
+ ]
+ ]
+ },
+ {
+ "input": "&trpezium",
+ "description": "Bad named entity: trpezium without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&trpezium"
+ ]
+ ]
+ },
+ {
+ "input": "⏢",
+ "description": "Named entity: trpezium; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u23e2"
+ ]
+ ]
+ },
+ {
+ "input": "&tscr",
+ "description": "Bad named entity: tscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&tscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝓉",
+ "description": "Named entity: tscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udcc9"
+ ]
+ ]
+ },
+ {
+ "input": "&tscy",
+ "description": "Bad named entity: tscy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&tscy"
+ ]
+ ]
+ },
+ {
+ "input": "ц",
+ "description": "Named entity: tscy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0446"
+ ]
+ ]
+ },
+ {
+ "input": "&tshcy",
+ "description": "Bad named entity: tshcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&tshcy"
+ ]
+ ]
+ },
+ {
+ "input": "ћ",
+ "description": "Named entity: tshcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u045b"
+ ]
+ ]
+ },
+ {
+ "input": "&tstrok",
+ "description": "Bad named entity: tstrok without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&tstrok"
+ ]
+ ]
+ },
+ {
+ "input": "ŧ",
+ "description": "Named entity: tstrok; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0167"
+ ]
+ ]
+ },
+ {
+ "input": "&twixt",
+ "description": "Bad named entity: twixt without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&twixt"
+ ]
+ ]
+ },
+ {
+ "input": "≬",
+ "description": "Named entity: twixt; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u226c"
+ ]
+ ]
+ },
+ {
+ "input": "&twoheadleftarrow",
+ "description": "Bad named entity: twoheadleftarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&twoheadleftarrow"
+ ]
+ ]
+ },
+ {
+ "input": "↞",
+ "description": "Named entity: twoheadleftarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u219e"
+ ]
+ ]
+ },
+ {
+ "input": "&twoheadrightarrow",
+ "description": "Bad named entity: twoheadrightarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&twoheadrightarrow"
+ ]
+ ]
+ },
+ {
+ "input": "↠",
+ "description": "Named entity: twoheadrightarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21a0"
+ ]
+ ]
+ },
+ {
+ "input": "&uArr",
+ "description": "Bad named entity: uArr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&uArr"
+ ]
+ ]
+ },
+ {
+ "input": "⇑",
+ "description": "Named entity: uArr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21d1"
+ ]
+ ]
+ },
+ {
+ "input": "&uHar",
+ "description": "Bad named entity: uHar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&uHar"
+ ]
+ ]
+ },
+ {
+ "input": "⥣",
+ "description": "Named entity: uHar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2963"
+ ]
+ ]
+ },
+ {
+ "input": "ú",
+ "description": "Named entity: uacute without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00fa"
+ ]
+ ]
+ },
+ {
+ "input": "ú",
+ "description": "Named entity: uacute; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00fa"
+ ]
+ ]
+ },
+ {
+ "input": "&uarr",
+ "description": "Bad named entity: uarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&uarr"
+ ]
+ ]
+ },
+ {
+ "input": "↑",
+ "description": "Named entity: uarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2191"
+ ]
+ ]
+ },
+ {
+ "input": "&ubrcy",
+ "description": "Bad named entity: ubrcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ubrcy"
+ ]
+ ]
+ },
+ {
+ "input": "ў",
+ "description": "Named entity: ubrcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u045e"
+ ]
+ ]
+ },
+ {
+ "input": "&ubreve",
+ "description": "Bad named entity: ubreve without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ubreve"
+ ]
+ ]
+ },
+ {
+ "input": "ŭ",
+ "description": "Named entity: ubreve; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u016d"
+ ]
+ ]
+ },
+ {
+ "input": "û",
+ "description": "Named entity: ucirc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00fb"
+ ]
+ ]
+ },
+ {
+ "input": "û",
+ "description": "Named entity: ucirc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00fb"
+ ]
+ ]
+ },
+ {
+ "input": "&ucy",
+ "description": "Bad named entity: ucy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ucy"
+ ]
+ ]
+ },
+ {
+ "input": "у",
+ "description": "Named entity: ucy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0443"
+ ]
+ ]
+ },
+ {
+ "input": "&udarr",
+ "description": "Bad named entity: udarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&udarr"
+ ]
+ ]
+ },
+ {
+ "input": "⇅",
+ "description": "Named entity: udarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21c5"
+ ]
+ ]
+ },
+ {
+ "input": "&udblac",
+ "description": "Bad named entity: udblac without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&udblac"
+ ]
+ ]
+ },
+ {
+ "input": "ű",
+ "description": "Named entity: udblac; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0171"
+ ]
+ ]
+ },
+ {
+ "input": "&udhar",
+ "description": "Bad named entity: udhar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&udhar"
+ ]
+ ]
+ },
+ {
+ "input": "⥮",
+ "description": "Named entity: udhar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u296e"
+ ]
+ ]
+ },
+ {
+ "input": "&ufisht",
+ "description": "Bad named entity: ufisht without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ufisht"
+ ]
+ ]
+ },
+ {
+ "input": "⥾",
+ "description": "Named entity: ufisht; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u297e"
+ ]
+ ]
+ },
+ {
+ "input": "&ufr",
+ "description": "Bad named entity: ufr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ufr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔲",
+ "description": "Named entity: ufr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd32"
+ ]
+ ]
+ },
+ {
+ "input": "ù",
+ "description": "Named entity: ugrave without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00f9"
+ ]
+ ]
+ },
+ {
+ "input": "ù",
+ "description": "Named entity: ugrave; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00f9"
+ ]
+ ]
+ },
+ {
+ "input": "&uharl",
+ "description": "Bad named entity: uharl without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&uharl"
+ ]
+ ]
+ },
+ {
+ "input": "↿",
+ "description": "Named entity: uharl; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21bf"
+ ]
+ ]
+ },
+ {
+ "input": "&uharr",
+ "description": "Bad named entity: uharr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&uharr"
+ ]
+ ]
+ },
+ {
+ "input": "↾",
+ "description": "Named entity: uharr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21be"
+ ]
+ ]
+ },
+ {
+ "input": "&uhblk",
+ "description": "Bad named entity: uhblk without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&uhblk"
+ ]
+ ]
+ },
+ {
+ "input": "▀",
+ "description": "Named entity: uhblk; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2580"
+ ]
+ ]
+ },
+ {
+ "input": "&ulcorn",
+ "description": "Bad named entity: ulcorn without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ulcorn"
+ ]
+ ]
+ },
+ {
+ "input": "⌜",
+ "description": "Named entity: ulcorn; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u231c"
+ ]
+ ]
+ },
+ {
+ "input": "&ulcorner",
+ "description": "Bad named entity: ulcorner without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ulcorner"
+ ]
+ ]
+ },
+ {
+ "input": "⌜",
+ "description": "Named entity: ulcorner; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u231c"
+ ]
+ ]
+ },
+ {
+ "input": "&ulcrop",
+ "description": "Bad named entity: ulcrop without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ulcrop"
+ ]
+ ]
+ },
+ {
+ "input": "⌏",
+ "description": "Named entity: ulcrop; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u230f"
+ ]
+ ]
+ },
+ {
+ "input": "&ultri",
+ "description": "Bad named entity: ultri without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ultri"
+ ]
+ ]
+ },
+ {
+ "input": "◸",
+ "description": "Named entity: ultri; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25f8"
+ ]
+ ]
+ },
+ {
+ "input": "&umacr",
+ "description": "Bad named entity: umacr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&umacr"
+ ]
+ ]
+ },
+ {
+ "input": "ū",
+ "description": "Named entity: umacr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u016b"
+ ]
+ ]
+ },
+ {
+ "input": "¨",
+ "description": "Named entity: uml without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00a8"
+ ]
+ ]
+ },
+ {
+ "input": "¨",
+ "description": "Named entity: uml; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00a8"
+ ]
+ ]
+ },
+ {
+ "input": "&uogon",
+ "description": "Bad named entity: uogon without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&uogon"
+ ]
+ ]
+ },
+ {
+ "input": "ų",
+ "description": "Named entity: uogon; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0173"
+ ]
+ ]
+ },
+ {
+ "input": "&uopf",
+ "description": "Bad named entity: uopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&uopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕦",
+ "description": "Named entity: uopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd66"
+ ]
+ ]
+ },
+ {
+ "input": "&uparrow",
+ "description": "Bad named entity: uparrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&uparrow"
+ ]
+ ]
+ },
+ {
+ "input": "↑",
+ "description": "Named entity: uparrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2191"
+ ]
+ ]
+ },
+ {
+ "input": "&updownarrow",
+ "description": "Bad named entity: updownarrow without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&updownarrow"
+ ]
+ ]
+ },
+ {
+ "input": "↕",
+ "description": "Named entity: updownarrow; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2195"
+ ]
+ ]
+ },
+ {
+ "input": "&upharpoonleft",
+ "description": "Bad named entity: upharpoonleft without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&upharpoonleft"
+ ]
+ ]
+ },
+ {
+ "input": "↿",
+ "description": "Named entity: upharpoonleft; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21bf"
+ ]
+ ]
+ },
+ {
+ "input": "&upharpoonright",
+ "description": "Bad named entity: upharpoonright without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&upharpoonright"
+ ]
+ ]
+ },
+ {
+ "input": "↾",
+ "description": "Named entity: upharpoonright; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21be"
+ ]
+ ]
+ },
+ {
+ "input": "&uplus",
+ "description": "Bad named entity: uplus without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&uplus"
+ ]
+ ]
+ },
+ {
+ "input": "⊎",
+ "description": "Named entity: uplus; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u228e"
+ ]
+ ]
+ },
+ {
+ "input": "&upsi",
+ "description": "Bad named entity: upsi without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&upsi"
+ ]
+ ]
+ },
+ {
+ "input": "υ",
+ "description": "Named entity: upsi; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03c5"
+ ]
+ ]
+ },
+ {
+ "input": "&upsih",
+ "description": "Bad named entity: upsih without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&upsih"
+ ]
+ ]
+ },
+ {
+ "input": "ϒ",
+ "description": "Named entity: upsih; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03d2"
+ ]
+ ]
+ },
+ {
+ "input": "&upsilon",
+ "description": "Bad named entity: upsilon without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&upsilon"
+ ]
+ ]
+ },
+ {
+ "input": "υ",
+ "description": "Named entity: upsilon; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03c5"
+ ]
+ ]
+ },
+ {
+ "input": "&upuparrows",
+ "description": "Bad named entity: upuparrows without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&upuparrows"
+ ]
+ ]
+ },
+ {
+ "input": "⇈",
+ "description": "Named entity: upuparrows; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21c8"
+ ]
+ ]
+ },
+ {
+ "input": "&urcorn",
+ "description": "Bad named entity: urcorn without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&urcorn"
+ ]
+ ]
+ },
+ {
+ "input": "⌝",
+ "description": "Named entity: urcorn; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u231d"
+ ]
+ ]
+ },
+ {
+ "input": "&urcorner",
+ "description": "Bad named entity: urcorner without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&urcorner"
+ ]
+ ]
+ },
+ {
+ "input": "⌝",
+ "description": "Named entity: urcorner; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u231d"
+ ]
+ ]
+ },
+ {
+ "input": "&urcrop",
+ "description": "Bad named entity: urcrop without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&urcrop"
+ ]
+ ]
+ },
+ {
+ "input": "⌎",
+ "description": "Named entity: urcrop; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u230e"
+ ]
+ ]
+ },
+ {
+ "input": "&uring",
+ "description": "Bad named entity: uring without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&uring"
+ ]
+ ]
+ },
+ {
+ "input": "ů",
+ "description": "Named entity: uring; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u016f"
+ ]
+ ]
+ },
+ {
+ "input": "&urtri",
+ "description": "Bad named entity: urtri without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&urtri"
+ ]
+ ]
+ },
+ {
+ "input": "◹",
+ "description": "Named entity: urtri; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25f9"
+ ]
+ ]
+ },
+ {
+ "input": "&uscr",
+ "description": "Bad named entity: uscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&uscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝓊",
+ "description": "Named entity: uscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udcca"
+ ]
+ ]
+ },
+ {
+ "input": "&utdot",
+ "description": "Bad named entity: utdot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&utdot"
+ ]
+ ]
+ },
+ {
+ "input": "⋰",
+ "description": "Named entity: utdot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22f0"
+ ]
+ ]
+ },
+ {
+ "input": "&utilde",
+ "description": "Bad named entity: utilde without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&utilde"
+ ]
+ ]
+ },
+ {
+ "input": "ũ",
+ "description": "Named entity: utilde; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0169"
+ ]
+ ]
+ },
+ {
+ "input": "&utri",
+ "description": "Bad named entity: utri without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&utri"
+ ]
+ ]
+ },
+ {
+ "input": "▵",
+ "description": "Named entity: utri; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25b5"
+ ]
+ ]
+ },
+ {
+ "input": "&utrif",
+ "description": "Bad named entity: utrif without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&utrif"
+ ]
+ ]
+ },
+ {
+ "input": "▴",
+ "description": "Named entity: utrif; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25b4"
+ ]
+ ]
+ },
+ {
+ "input": "&uuarr",
+ "description": "Bad named entity: uuarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&uuarr"
+ ]
+ ]
+ },
+ {
+ "input": "⇈",
+ "description": "Named entity: uuarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21c8"
+ ]
+ ]
+ },
+ {
+ "input": "ü",
+ "description": "Named entity: uuml without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00fc"
+ ]
+ ]
+ },
+ {
+ "input": "ü",
+ "description": "Named entity: uuml; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00fc"
+ ]
+ ]
+ },
+ {
+ "input": "&uwangle",
+ "description": "Bad named entity: uwangle without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&uwangle"
+ ]
+ ]
+ },
+ {
+ "input": "⦧",
+ "description": "Named entity: uwangle; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u29a7"
+ ]
+ ]
+ },
+ {
+ "input": "&vArr",
+ "description": "Bad named entity: vArr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&vArr"
+ ]
+ ]
+ },
+ {
+ "input": "⇕",
+ "description": "Named entity: vArr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21d5"
+ ]
+ ]
+ },
+ {
+ "input": "&vBar",
+ "description": "Bad named entity: vBar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&vBar"
+ ]
+ ]
+ },
+ {
+ "input": "⫨",
+ "description": "Named entity: vBar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ae8"
+ ]
+ ]
+ },
+ {
+ "input": "&vBarv",
+ "description": "Bad named entity: vBarv without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&vBarv"
+ ]
+ ]
+ },
+ {
+ "input": "⫩",
+ "description": "Named entity: vBarv; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2ae9"
+ ]
+ ]
+ },
+ {
+ "input": "&vDash",
+ "description": "Bad named entity: vDash without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&vDash"
+ ]
+ ]
+ },
+ {
+ "input": "⊨",
+ "description": "Named entity: vDash; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22a8"
+ ]
+ ]
+ },
+ {
+ "input": "&vangrt",
+ "description": "Bad named entity: vangrt without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&vangrt"
+ ]
+ ]
+ },
+ {
+ "input": "⦜",
+ "description": "Named entity: vangrt; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u299c"
+ ]
+ ]
+ },
+ {
+ "input": "&varepsilon",
+ "description": "Bad named entity: varepsilon without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&varepsilon"
+ ]
+ ]
+ },
+ {
+ "input": "ϵ",
+ "description": "Named entity: varepsilon; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03f5"
+ ]
+ ]
+ },
+ {
+ "input": "&varkappa",
+ "description": "Bad named entity: varkappa without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&varkappa"
+ ]
+ ]
+ },
+ {
+ "input": "ϰ",
+ "description": "Named entity: varkappa; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03f0"
+ ]
+ ]
+ },
+ {
+ "input": "&varnothing",
+ "description": "Bad named entity: varnothing without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&varnothing"
+ ]
+ ]
+ },
+ {
+ "input": "∅",
+ "description": "Named entity: varnothing; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2205"
+ ]
+ ]
+ },
+ {
+ "input": "&varphi",
+ "description": "Bad named entity: varphi without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&varphi"
+ ]
+ ]
+ },
+ {
+ "input": "ϕ",
+ "description": "Named entity: varphi; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03d5"
+ ]
+ ]
+ },
+ {
+ "input": "&varpi",
+ "description": "Bad named entity: varpi without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&varpi"
+ ]
+ ]
+ },
+ {
+ "input": "ϖ",
+ "description": "Named entity: varpi; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03d6"
+ ]
+ ]
+ },
+ {
+ "input": "&varpropto",
+ "description": "Bad named entity: varpropto without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&varpropto"
+ ]
+ ]
+ },
+ {
+ "input": "∝",
+ "description": "Named entity: varpropto; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u221d"
+ ]
+ ]
+ },
+ {
+ "input": "&varr",
+ "description": "Bad named entity: varr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&varr"
+ ]
+ ]
+ },
+ {
+ "input": "↕",
+ "description": "Named entity: varr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2195"
+ ]
+ ]
+ },
+ {
+ "input": "&varrho",
+ "description": "Bad named entity: varrho without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&varrho"
+ ]
+ ]
+ },
+ {
+ "input": "ϱ",
+ "description": "Named entity: varrho; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03f1"
+ ]
+ ]
+ },
+ {
+ "input": "&varsigma",
+ "description": "Bad named entity: varsigma without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&varsigma"
+ ]
+ ]
+ },
+ {
+ "input": "ς",
+ "description": "Named entity: varsigma; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03c2"
+ ]
+ ]
+ },
+ {
+ "input": "&varsubsetneq",
+ "description": "Bad named entity: varsubsetneq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&varsubsetneq"
+ ]
+ ]
+ },
+ {
+ "input": "⊊︀",
+ "description": "Named entity: varsubsetneq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u228a\ufe00"
+ ]
+ ]
+ },
+ {
+ "input": "&varsubsetneqq",
+ "description": "Bad named entity: varsubsetneqq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&varsubsetneqq"
+ ]
+ ]
+ },
+ {
+ "input": "⫋︀",
+ "description": "Named entity: varsubsetneqq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2acb\ufe00"
+ ]
+ ]
+ },
+ {
+ "input": "&varsupsetneq",
+ "description": "Bad named entity: varsupsetneq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&varsupsetneq"
+ ]
+ ]
+ },
+ {
+ "input": "⊋︀",
+ "description": "Named entity: varsupsetneq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u228b\ufe00"
+ ]
+ ]
+ },
+ {
+ "input": "&varsupsetneqq",
+ "description": "Bad named entity: varsupsetneqq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&varsupsetneqq"
+ ]
+ ]
+ },
+ {
+ "input": "⫌︀",
+ "description": "Named entity: varsupsetneqq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2acc\ufe00"
+ ]
+ ]
+ },
+ {
+ "input": "&vartheta",
+ "description": "Bad named entity: vartheta without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&vartheta"
+ ]
+ ]
+ },
+ {
+ "input": "ϑ",
+ "description": "Named entity: vartheta; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03d1"
+ ]
+ ]
+ },
+ {
+ "input": "&vartriangleleft",
+ "description": "Bad named entity: vartriangleleft without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&vartriangleleft"
+ ]
+ ]
+ },
+ {
+ "input": "⊲",
+ "description": "Named entity: vartriangleleft; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22b2"
+ ]
+ ]
+ },
+ {
+ "input": "&vartriangleright",
+ "description": "Bad named entity: vartriangleright without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&vartriangleright"
+ ]
+ ]
+ },
+ {
+ "input": "⊳",
+ "description": "Named entity: vartriangleright; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22b3"
+ ]
+ ]
+ },
+ {
+ "input": "&vcy",
+ "description": "Bad named entity: vcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&vcy"
+ ]
+ ]
+ },
+ {
+ "input": "в",
+ "description": "Named entity: vcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0432"
+ ]
+ ]
+ },
+ {
+ "input": "&vdash",
+ "description": "Bad named entity: vdash without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&vdash"
+ ]
+ ]
+ },
+ {
+ "input": "⊢",
+ "description": "Named entity: vdash; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22a2"
+ ]
+ ]
+ },
+ {
+ "input": "&vee",
+ "description": "Bad named entity: vee without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&vee"
+ ]
+ ]
+ },
+ {
+ "input": "∨",
+ "description": "Named entity: vee; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2228"
+ ]
+ ]
+ },
+ {
+ "input": "&veebar",
+ "description": "Bad named entity: veebar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&veebar"
+ ]
+ ]
+ },
+ {
+ "input": "⊻",
+ "description": "Named entity: veebar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22bb"
+ ]
+ ]
+ },
+ {
+ "input": "&veeeq",
+ "description": "Bad named entity: veeeq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&veeeq"
+ ]
+ ]
+ },
+ {
+ "input": "≚",
+ "description": "Named entity: veeeq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u225a"
+ ]
+ ]
+ },
+ {
+ "input": "&vellip",
+ "description": "Bad named entity: vellip without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&vellip"
+ ]
+ ]
+ },
+ {
+ "input": "⋮",
+ "description": "Named entity: vellip; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22ee"
+ ]
+ ]
+ },
+ {
+ "input": "&verbar",
+ "description": "Bad named entity: verbar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&verbar"
+ ]
+ ]
+ },
+ {
+ "input": "|",
+ "description": "Named entity: verbar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "|"
+ ]
+ ]
+ },
+ {
+ "input": "&vert",
+ "description": "Bad named entity: vert without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&vert"
+ ]
+ ]
+ },
+ {
+ "input": "|",
+ "description": "Named entity: vert; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "|"
+ ]
+ ]
+ },
+ {
+ "input": "&vfr",
+ "description": "Bad named entity: vfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&vfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔳",
+ "description": "Named entity: vfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd33"
+ ]
+ ]
+ },
+ {
+ "input": "&vltri",
+ "description": "Bad named entity: vltri without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&vltri"
+ ]
+ ]
+ },
+ {
+ "input": "⊲",
+ "description": "Named entity: vltri; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22b2"
+ ]
+ ]
+ },
+ {
+ "input": "&vnsub",
+ "description": "Bad named entity: vnsub without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&vnsub"
+ ]
+ ]
+ },
+ {
+ "input": "⊂⃒",
+ "description": "Named entity: vnsub; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2282\u20d2"
+ ]
+ ]
+ },
+ {
+ "input": "&vnsup",
+ "description": "Bad named entity: vnsup without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&vnsup"
+ ]
+ ]
+ },
+ {
+ "input": "⊃⃒",
+ "description": "Named entity: vnsup; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2283\u20d2"
+ ]
+ ]
+ },
+ {
+ "input": "&vopf",
+ "description": "Bad named entity: vopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&vopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕧",
+ "description": "Named entity: vopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd67"
+ ]
+ ]
+ },
+ {
+ "input": "&vprop",
+ "description": "Bad named entity: vprop without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&vprop"
+ ]
+ ]
+ },
+ {
+ "input": "∝",
+ "description": "Named entity: vprop; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u221d"
+ ]
+ ]
+ },
+ {
+ "input": "&vrtri",
+ "description": "Bad named entity: vrtri without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&vrtri"
+ ]
+ ]
+ },
+ {
+ "input": "⊳",
+ "description": "Named entity: vrtri; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22b3"
+ ]
+ ]
+ },
+ {
+ "input": "&vscr",
+ "description": "Bad named entity: vscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&vscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝓋",
+ "description": "Named entity: vscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udccb"
+ ]
+ ]
+ },
+ {
+ "input": "&vsubnE",
+ "description": "Bad named entity: vsubnE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&vsubnE"
+ ]
+ ]
+ },
+ {
+ "input": "⫋︀",
+ "description": "Named entity: vsubnE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2acb\ufe00"
+ ]
+ ]
+ },
+ {
+ "input": "&vsubne",
+ "description": "Bad named entity: vsubne without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&vsubne"
+ ]
+ ]
+ },
+ {
+ "input": "⊊︀",
+ "description": "Named entity: vsubne; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u228a\ufe00"
+ ]
+ ]
+ },
+ {
+ "input": "&vsupnE",
+ "description": "Bad named entity: vsupnE without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&vsupnE"
+ ]
+ ]
+ },
+ {
+ "input": "⫌︀",
+ "description": "Named entity: vsupnE; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2acc\ufe00"
+ ]
+ ]
+ },
+ {
+ "input": "&vsupne",
+ "description": "Bad named entity: vsupne without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&vsupne"
+ ]
+ ]
+ },
+ {
+ "input": "⊋︀",
+ "description": "Named entity: vsupne; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u228b\ufe00"
+ ]
+ ]
+ },
+ {
+ "input": "&vzigzag",
+ "description": "Bad named entity: vzigzag without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&vzigzag"
+ ]
+ ]
+ },
+ {
+ "input": "⦚",
+ "description": "Named entity: vzigzag; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u299a"
+ ]
+ ]
+ },
+ {
+ "input": "&wcirc",
+ "description": "Bad named entity: wcirc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&wcirc"
+ ]
+ ]
+ },
+ {
+ "input": "ŵ",
+ "description": "Named entity: wcirc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0175"
+ ]
+ ]
+ },
+ {
+ "input": "&wedbar",
+ "description": "Bad named entity: wedbar without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&wedbar"
+ ]
+ ]
+ },
+ {
+ "input": "⩟",
+ "description": "Named entity: wedbar; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a5f"
+ ]
+ ]
+ },
+ {
+ "input": "&wedge",
+ "description": "Bad named entity: wedge without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&wedge"
+ ]
+ ]
+ },
+ {
+ "input": "∧",
+ "description": "Named entity: wedge; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2227"
+ ]
+ ]
+ },
+ {
+ "input": "&wedgeq",
+ "description": "Bad named entity: wedgeq without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&wedgeq"
+ ]
+ ]
+ },
+ {
+ "input": "≙",
+ "description": "Named entity: wedgeq; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2259"
+ ]
+ ]
+ },
+ {
+ "input": "&weierp",
+ "description": "Bad named entity: weierp without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&weierp"
+ ]
+ ]
+ },
+ {
+ "input": "℘",
+ "description": "Named entity: weierp; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2118"
+ ]
+ ]
+ },
+ {
+ "input": "&wfr",
+ "description": "Bad named entity: wfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&wfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔴",
+ "description": "Named entity: wfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd34"
+ ]
+ ]
+ },
+ {
+ "input": "&wopf",
+ "description": "Bad named entity: wopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&wopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕨",
+ "description": "Named entity: wopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd68"
+ ]
+ ]
+ },
+ {
+ "input": "&wp",
+ "description": "Bad named entity: wp without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&wp"
+ ]
+ ]
+ },
+ {
+ "input": "℘",
+ "description": "Named entity: wp; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2118"
+ ]
+ ]
+ },
+ {
+ "input": "&wr",
+ "description": "Bad named entity: wr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&wr"
+ ]
+ ]
+ },
+ {
+ "input": "≀",
+ "description": "Named entity: wr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2240"
+ ]
+ ]
+ },
+ {
+ "input": "&wreath",
+ "description": "Bad named entity: wreath without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&wreath"
+ ]
+ ]
+ },
+ {
+ "input": "≀",
+ "description": "Named entity: wreath; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2240"
+ ]
+ ]
+ },
+ {
+ "input": "&wscr",
+ "description": "Bad named entity: wscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&wscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝓌",
+ "description": "Named entity: wscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udccc"
+ ]
+ ]
+ },
+ {
+ "input": "&xcap",
+ "description": "Bad named entity: xcap without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&xcap"
+ ]
+ ]
+ },
+ {
+ "input": "⋂",
+ "description": "Named entity: xcap; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22c2"
+ ]
+ ]
+ },
+ {
+ "input": "&xcirc",
+ "description": "Bad named entity: xcirc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&xcirc"
+ ]
+ ]
+ },
+ {
+ "input": "◯",
+ "description": "Named entity: xcirc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25ef"
+ ]
+ ]
+ },
+ {
+ "input": "&xcup",
+ "description": "Bad named entity: xcup without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&xcup"
+ ]
+ ]
+ },
+ {
+ "input": "⋃",
+ "description": "Named entity: xcup; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22c3"
+ ]
+ ]
+ },
+ {
+ "input": "&xdtri",
+ "description": "Bad named entity: xdtri without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&xdtri"
+ ]
+ ]
+ },
+ {
+ "input": "▽",
+ "description": "Named entity: xdtri; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25bd"
+ ]
+ ]
+ },
+ {
+ "input": "&xfr",
+ "description": "Bad named entity: xfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&xfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔵",
+ "description": "Named entity: xfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd35"
+ ]
+ ]
+ },
+ {
+ "input": "&xhArr",
+ "description": "Bad named entity: xhArr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&xhArr"
+ ]
+ ]
+ },
+ {
+ "input": "⟺",
+ "description": "Named entity: xhArr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27fa"
+ ]
+ ]
+ },
+ {
+ "input": "&xharr",
+ "description": "Bad named entity: xharr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&xharr"
+ ]
+ ]
+ },
+ {
+ "input": "⟷",
+ "description": "Named entity: xharr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27f7"
+ ]
+ ]
+ },
+ {
+ "input": "&xi",
+ "description": "Bad named entity: xi without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&xi"
+ ]
+ ]
+ },
+ {
+ "input": "ξ",
+ "description": "Named entity: xi; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03be"
+ ]
+ ]
+ },
+ {
+ "input": "&xlArr",
+ "description": "Bad named entity: xlArr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&xlArr"
+ ]
+ ]
+ },
+ {
+ "input": "⟸",
+ "description": "Named entity: xlArr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27f8"
+ ]
+ ]
+ },
+ {
+ "input": "&xlarr",
+ "description": "Bad named entity: xlarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&xlarr"
+ ]
+ ]
+ },
+ {
+ "input": "⟵",
+ "description": "Named entity: xlarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27f5"
+ ]
+ ]
+ },
+ {
+ "input": "&xmap",
+ "description": "Bad named entity: xmap without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&xmap"
+ ]
+ ]
+ },
+ {
+ "input": "⟼",
+ "description": "Named entity: xmap; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27fc"
+ ]
+ ]
+ },
+ {
+ "input": "&xnis",
+ "description": "Bad named entity: xnis without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&xnis"
+ ]
+ ]
+ },
+ {
+ "input": "⋻",
+ "description": "Named entity: xnis; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22fb"
+ ]
+ ]
+ },
+ {
+ "input": "&xodot",
+ "description": "Bad named entity: xodot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&xodot"
+ ]
+ ]
+ },
+ {
+ "input": "⨀",
+ "description": "Named entity: xodot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a00"
+ ]
+ ]
+ },
+ {
+ "input": "&xopf",
+ "description": "Bad named entity: xopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&xopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕩",
+ "description": "Named entity: xopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd69"
+ ]
+ ]
+ },
+ {
+ "input": "&xoplus",
+ "description": "Bad named entity: xoplus without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&xoplus"
+ ]
+ ]
+ },
+ {
+ "input": "⨁",
+ "description": "Named entity: xoplus; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a01"
+ ]
+ ]
+ },
+ {
+ "input": "&xotime",
+ "description": "Bad named entity: xotime without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&xotime"
+ ]
+ ]
+ },
+ {
+ "input": "⨂",
+ "description": "Named entity: xotime; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a02"
+ ]
+ ]
+ },
+ {
+ "input": "&xrArr",
+ "description": "Bad named entity: xrArr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&xrArr"
+ ]
+ ]
+ },
+ {
+ "input": "⟹",
+ "description": "Named entity: xrArr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27f9"
+ ]
+ ]
+ },
+ {
+ "input": "&xrarr",
+ "description": "Bad named entity: xrarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&xrarr"
+ ]
+ ]
+ },
+ {
+ "input": "⟶",
+ "description": "Named entity: xrarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u27f6"
+ ]
+ ]
+ },
+ {
+ "input": "&xscr",
+ "description": "Bad named entity: xscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&xscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝓍",
+ "description": "Named entity: xscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udccd"
+ ]
+ ]
+ },
+ {
+ "input": "&xsqcup",
+ "description": "Bad named entity: xsqcup without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&xsqcup"
+ ]
+ ]
+ },
+ {
+ "input": "⨆",
+ "description": "Named entity: xsqcup; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a06"
+ ]
+ ]
+ },
+ {
+ "input": "&xuplus",
+ "description": "Bad named entity: xuplus without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&xuplus"
+ ]
+ ]
+ },
+ {
+ "input": "⨄",
+ "description": "Named entity: xuplus; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2a04"
+ ]
+ ]
+ },
+ {
+ "input": "&xutri",
+ "description": "Bad named entity: xutri without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&xutri"
+ ]
+ ]
+ },
+ {
+ "input": "△",
+ "description": "Named entity: xutri; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u25b3"
+ ]
+ ]
+ },
+ {
+ "input": "&xvee",
+ "description": "Bad named entity: xvee without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&xvee"
+ ]
+ ]
+ },
+ {
+ "input": "⋁",
+ "description": "Named entity: xvee; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22c1"
+ ]
+ ]
+ },
+ {
+ "input": "&xwedge",
+ "description": "Bad named entity: xwedge without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&xwedge"
+ ]
+ ]
+ },
+ {
+ "input": "⋀",
+ "description": "Named entity: xwedge; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u22c0"
+ ]
+ ]
+ },
+ {
+ "input": "ý",
+ "description": "Named entity: yacute without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00fd"
+ ]
+ ]
+ },
+ {
+ "input": "ý",
+ "description": "Named entity: yacute; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00fd"
+ ]
+ ]
+ },
+ {
+ "input": "&yacy",
+ "description": "Bad named entity: yacy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&yacy"
+ ]
+ ]
+ },
+ {
+ "input": "я",
+ "description": "Named entity: yacy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u044f"
+ ]
+ ]
+ },
+ {
+ "input": "&ycirc",
+ "description": "Bad named entity: ycirc without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ycirc"
+ ]
+ ]
+ },
+ {
+ "input": "ŷ",
+ "description": "Named entity: ycirc; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0177"
+ ]
+ ]
+ },
+ {
+ "input": "&ycy",
+ "description": "Bad named entity: ycy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&ycy"
+ ]
+ ]
+ },
+ {
+ "input": "ы",
+ "description": "Named entity: ycy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u044b"
+ ]
+ ]
+ },
+ {
+ "input": "¥",
+ "description": "Named entity: yen without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00a5"
+ ]
+ ]
+ },
+ {
+ "input": "¥",
+ "description": "Named entity: yen; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00a5"
+ ]
+ ]
+ },
+ {
+ "input": "&yfr",
+ "description": "Bad named entity: yfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&yfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔶",
+ "description": "Named entity: yfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd36"
+ ]
+ ]
+ },
+ {
+ "input": "&yicy",
+ "description": "Bad named entity: yicy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&yicy"
+ ]
+ ]
+ },
+ {
+ "input": "ї",
+ "description": "Named entity: yicy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0457"
+ ]
+ ]
+ },
+ {
+ "input": "&yopf",
+ "description": "Bad named entity: yopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&yopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕪",
+ "description": "Named entity: yopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd6a"
+ ]
+ ]
+ },
+ {
+ "input": "&yscr",
+ "description": "Bad named entity: yscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&yscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝓎",
+ "description": "Named entity: yscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udcce"
+ ]
+ ]
+ },
+ {
+ "input": "&yucy",
+ "description": "Bad named entity: yucy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&yucy"
+ ]
+ ]
+ },
+ {
+ "input": "ю",
+ "description": "Named entity: yucy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u044e"
+ ]
+ ]
+ },
+ {
+ "input": "ÿ",
+ "description": "Named entity: yuml without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "\u00ff"
+ ]
+ ]
+ },
+ {
+ "input": "ÿ",
+ "description": "Named entity: yuml; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u00ff"
+ ]
+ ]
+ },
+ {
+ "input": "&zacute",
+ "description": "Bad named entity: zacute without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&zacute"
+ ]
+ ]
+ },
+ {
+ "input": "ź",
+ "description": "Named entity: zacute; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u017a"
+ ]
+ ]
+ },
+ {
+ "input": "&zcaron",
+ "description": "Bad named entity: zcaron without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&zcaron"
+ ]
+ ]
+ },
+ {
+ "input": "ž",
+ "description": "Named entity: zcaron; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u017e"
+ ]
+ ]
+ },
+ {
+ "input": "&zcy",
+ "description": "Bad named entity: zcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&zcy"
+ ]
+ ]
+ },
+ {
+ "input": "з",
+ "description": "Named entity: zcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0437"
+ ]
+ ]
+ },
+ {
+ "input": "&zdot",
+ "description": "Bad named entity: zdot without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&zdot"
+ ]
+ ]
+ },
+ {
+ "input": "ż",
+ "description": "Named entity: zdot; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u017c"
+ ]
+ ]
+ },
+ {
+ "input": "&zeetrf",
+ "description": "Bad named entity: zeetrf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&zeetrf"
+ ]
+ ]
+ },
+ {
+ "input": "ℨ",
+ "description": "Named entity: zeetrf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u2128"
+ ]
+ ]
+ },
+ {
+ "input": "&zeta",
+ "description": "Bad named entity: zeta without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&zeta"
+ ]
+ ]
+ },
+ {
+ "input": "ζ",
+ "description": "Named entity: zeta; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u03b6"
+ ]
+ ]
+ },
+ {
+ "input": "&zfr",
+ "description": "Bad named entity: zfr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&zfr"
+ ]
+ ]
+ },
+ {
+ "input": "𝔷",
+ "description": "Named entity: zfr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd37"
+ ]
+ ]
+ },
+ {
+ "input": "&zhcy",
+ "description": "Bad named entity: zhcy without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&zhcy"
+ ]
+ ]
+ },
+ {
+ "input": "ж",
+ "description": "Named entity: zhcy; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u0436"
+ ]
+ ]
+ },
+ {
+ "input": "&zigrarr",
+ "description": "Bad named entity: zigrarr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&zigrarr"
+ ]
+ ]
+ },
+ {
+ "input": "⇝",
+ "description": "Named entity: zigrarr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u21dd"
+ ]
+ ]
+ },
+ {
+ "input": "&zopf",
+ "description": "Bad named entity: zopf without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&zopf"
+ ]
+ ]
+ },
+ {
+ "input": "𝕫",
+ "description": "Named entity: zopf; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udd6b"
+ ]
+ ]
+ },
+ {
+ "input": "&zscr",
+ "description": "Bad named entity: zscr without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&zscr"
+ ]
+ ]
+ },
+ {
+ "input": "𝓏",
+ "description": "Named entity: zscr; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\ud835\udccf"
+ ]
+ ]
+ },
+ {
+ "input": "&zwj",
+ "description": "Bad named entity: zwj without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&zwj"
+ ]
+ ]
+ },
+ {
+ "input": "‍",
+ "description": "Named entity: zwj; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u200d"
+ ]
+ ]
+ },
+ {
+ "input": "&zwnj",
+ "description": "Bad named entity: zwnj without a semi-colon",
+ "output": [
+ "ParseError",
+ [
+ "Character",
+ "&zwnj"
+ ]
+ ]
+ },
+ {
+ "input": "‌",
+ "description": "Named entity: zwnj; with a semi-colon",
+ "output": [
+ [
+ "Character",
+ "\u200c"
+ ]
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/pkgs/html/test/data/tokenizer/numericEntities.test b/pkgs/html/test/data/tokenizer/numericEntities.test
new file mode 100644
index 0000000..36c8228
--- /dev/null
+++ b/pkgs/html/test/data/tokenizer/numericEntities.test
@@ -0,0 +1,1313 @@
+{"tests": [
+
+{"description": "Invalid numeric entity character U+0000",
+"input": "�",
+"output": ["ParseError", ["Character", "\uFFFD"]]},
+
+{"description": "Invalid numeric entity character U+0001",
+"input": "",
+"output": ["ParseError", ["Character", "\u0001"]]},
+
+{"description": "Invalid numeric entity character U+0002",
+"input": "",
+"output": ["ParseError", ["Character", "\u0002"]]},
+
+{"description": "Invalid numeric entity character U+0003",
+"input": "",
+"output": ["ParseError", ["Character", "\u0003"]]},
+
+{"description": "Invalid numeric entity character U+0004",
+"input": "",
+"output": ["ParseError", ["Character", "\u0004"]]},
+
+{"description": "Invalid numeric entity character U+0005",
+"input": "",
+"output": ["ParseError", ["Character", "\u0005"]]},
+
+{"description": "Invalid numeric entity character U+0006",
+"input": "",
+"output": ["ParseError", ["Character", "\u0006"]]},
+
+{"description": "Invalid numeric entity character U+0007",
+"input": "",
+"output": ["ParseError", ["Character", "\u0007"]]},
+
+{"description": "Invalid numeric entity character U+0008",
+"input": "",
+"output": ["ParseError", ["Character", "\u0008"]]},
+
+{"description": "Invalid numeric entity character U+000B",
+"input": "",
+"output": ["ParseError", ["Character", "\u000b"]]},
+
+{"description": "Invalid numeric entity character U+000E",
+"input": "",
+"output": ["ParseError", ["Character", "\u000e"]]},
+
+{"description": "Invalid numeric entity character U+000F",
+"input": "",
+"output": ["ParseError", ["Character", "\u000f"]]},
+
+{"description": "Invalid numeric entity character U+0010",
+"input": "",
+"output": ["ParseError", ["Character", "\u0010"]]},
+
+{"description": "Invalid numeric entity character U+0011",
+"input": "",
+"output": ["ParseError", ["Character", "\u0011"]]},
+
+{"description": "Invalid numeric entity character U+0012",
+"input": "",
+"output": ["ParseError", ["Character", "\u0012"]]},
+
+{"description": "Invalid numeric entity character U+0013",
+"input": "",
+"output": ["ParseError", ["Character", "\u0013"]]},
+
+{"description": "Invalid numeric entity character U+0014",
+"input": "",
+"output": ["ParseError", ["Character", "\u0014"]]},
+
+{"description": "Invalid numeric entity character U+0015",
+"input": "",
+"output": ["ParseError", ["Character", "\u0015"]]},
+
+{"description": "Invalid numeric entity character U+0016",
+"input": "",
+"output": ["ParseError", ["Character", "\u0016"]]},
+
+{"description": "Invalid numeric entity character U+0017",
+"input": "",
+"output": ["ParseError", ["Character", "\u0017"]]},
+
+{"description": "Invalid numeric entity character U+0018",
+"input": "",
+"output": ["ParseError", ["Character", "\u0018"]]},
+
+{"description": "Invalid numeric entity character U+0019",
+"input": "",
+"output": ["ParseError", ["Character", "\u0019"]]},
+
+{"description": "Invalid numeric entity character U+001A",
+"input": "",
+"output": ["ParseError", ["Character", "\u001a"]]},
+
+{"description": "Invalid numeric entity character U+001B",
+"input": "",
+"output": ["ParseError", ["Character", "\u001b"]]},
+
+{"description": "Invalid numeric entity character U+001C",
+"input": "",
+"output": ["ParseError", ["Character", "\u001c"]]},
+
+{"description": "Invalid numeric entity character U+001D",
+"input": "",
+"output": ["ParseError", ["Character", "\u001d"]]},
+
+{"description": "Invalid numeric entity character U+001E",
+"input": "",
+"output": ["ParseError", ["Character", "\u001e"]]},
+
+{"description": "Invalid numeric entity character U+001F",
+"input": "",
+"output": ["ParseError", ["Character", "\u001f"]]},
+
+{"description": "Invalid numeric entity character U+007F",
+"input": "",
+"output": ["ParseError", ["Character", "\u007f"]]},
+
+{"description": "Invalid numeric entity character U+D800",
+"input": "�",
+"output": ["ParseError", ["Character", "\uFFFD"]]},
+
+{"description": "Invalid numeric entity character U+DFFF",
+"input": "�",
+"output": ["ParseError", ["Character", "\uFFFD"]]},
+
+{"description": "Invalid numeric entity character U+FDD0",
+"input": "",
+"output": ["ParseError", ["Character", "\ufdd0"]]},
+
+{"description": "Invalid numeric entity character U+FDD1",
+"input": "",
+"output": ["ParseError", ["Character", "\ufdd1"]]},
+
+{"description": "Invalid numeric entity character U+FDD2",
+"input": "",
+"output": ["ParseError", ["Character", "\ufdd2"]]},
+
+{"description": "Invalid numeric entity character U+FDD3",
+"input": "",
+"output": ["ParseError", ["Character", "\ufdd3"]]},
+
+{"description": "Invalid numeric entity character U+FDD4",
+"input": "",
+"output": ["ParseError", ["Character", "\ufdd4"]]},
+
+{"description": "Invalid numeric entity character U+FDD5",
+"input": "",
+"output": ["ParseError", ["Character", "\ufdd5"]]},
+
+{"description": "Invalid numeric entity character U+FDD6",
+"input": "",
+"output": ["ParseError", ["Character", "\ufdd6"]]},
+
+{"description": "Invalid numeric entity character U+FDD7",
+"input": "",
+"output": ["ParseError", ["Character", "\ufdd7"]]},
+
+{"description": "Invalid numeric entity character U+FDD8",
+"input": "",
+"output": ["ParseError", ["Character", "\ufdd8"]]},
+
+{"description": "Invalid numeric entity character U+FDD9",
+"input": "",
+"output": ["ParseError", ["Character", "\ufdd9"]]},
+
+{"description": "Invalid numeric entity character U+FDDA",
+"input": "",
+"output": ["ParseError", ["Character", "\ufdda"]]},
+
+{"description": "Invalid numeric entity character U+FDDB",
+"input": "",
+"output": ["ParseError", ["Character", "\ufddb"]]},
+
+{"description": "Invalid numeric entity character U+FDDC",
+"input": "",
+"output": ["ParseError", ["Character", "\ufddc"]]},
+
+{"description": "Invalid numeric entity character U+FDDD",
+"input": "",
+"output": ["ParseError", ["Character", "\ufddd"]]},
+
+{"description": "Invalid numeric entity character U+FDDE",
+"input": "",
+"output": ["ParseError", ["Character", "\ufdde"]]},
+
+{"description": "Invalid numeric entity character U+FDDF",
+"input": "",
+"output": ["ParseError", ["Character", "\ufddf"]]},
+
+{"description": "Invalid numeric entity character U+FDE0",
+"input": "",
+"output": ["ParseError", ["Character", "\ufde0"]]},
+
+{"description": "Invalid numeric entity character U+FDE1",
+"input": "",
+"output": ["ParseError", ["Character", "\ufde1"]]},
+
+{"description": "Invalid numeric entity character U+FDE2",
+"input": "",
+"output": ["ParseError", ["Character", "\ufde2"]]},
+
+{"description": "Invalid numeric entity character U+FDE3",
+"input": "",
+"output": ["ParseError", ["Character", "\ufde3"]]},
+
+{"description": "Invalid numeric entity character U+FDE4",
+"input": "",
+"output": ["ParseError", ["Character", "\ufde4"]]},
+
+{"description": "Invalid numeric entity character U+FDE5",
+"input": "",
+"output": ["ParseError", ["Character", "\ufde5"]]},
+
+{"description": "Invalid numeric entity character U+FDE6",
+"input": "",
+"output": ["ParseError", ["Character", "\ufde6"]]},
+
+{"description": "Invalid numeric entity character U+FDE7",
+"input": "",
+"output": ["ParseError", ["Character", "\ufde7"]]},
+
+{"description": "Invalid numeric entity character U+FDE8",
+"input": "",
+"output": ["ParseError", ["Character", "\ufde8"]]},
+
+{"description": "Invalid numeric entity character U+FDE9",
+"input": "",
+"output": ["ParseError", ["Character", "\ufde9"]]},
+
+{"description": "Invalid numeric entity character U+FDEA",
+"input": "",
+"output": ["ParseError", ["Character", "\ufdea"]]},
+
+{"description": "Invalid numeric entity character U+FDEB",
+"input": "",
+"output": ["ParseError", ["Character", "\ufdeb"]]},
+
+{"description": "Invalid numeric entity character U+FDEC",
+"input": "",
+"output": ["ParseError", ["Character", "\ufdec"]]},
+
+{"description": "Invalid numeric entity character U+FDED",
+"input": "",
+"output": ["ParseError", ["Character", "\ufded"]]},
+
+{"description": "Invalid numeric entity character U+FDEE",
+"input": "",
+"output": ["ParseError", ["Character", "\ufdee"]]},
+
+{"description": "Invalid numeric entity character U+FDEF",
+"input": "",
+"output": ["ParseError", ["Character", "\ufdef"]]},
+
+{"description": "Invalid numeric entity character U+FFFE",
+"input": "",
+"output": ["ParseError", ["Character", "\ufffe"]]},
+
+{"description": "Invalid numeric entity character U+FFFF",
+"input": "",
+"output": ["ParseError", ["Character", "\uffff"]]},
+
+{"description": "Invalid numeric entity character U+1FFFE",
+"input": "",
+"output": ["ParseError", ["Character", "\uD83F\uDFFE"]]},
+
+{"description": "Invalid numeric entity character U+1FFFF",
+"input": "",
+"output": ["ParseError", ["Character", "\uD83F\uDFFF"]]},
+
+{"description": "Invalid numeric entity character U+2FFFE",
+"input": "",
+"output": ["ParseError", ["Character", "\uD87F\uDFFE"]]},
+
+{"description": "Invalid numeric entity character U+2FFFF",
+"input": "",
+"output": ["ParseError", ["Character", "\uD87F\uDFFF"]]},
+
+{"description": "Invalid numeric entity character U+3FFFE",
+"input": "",
+"output": ["ParseError", ["Character", "\uD8BF\uDFFE"]]},
+
+{"description": "Invalid numeric entity character U+3FFFF",
+"input": "",
+"output": ["ParseError", ["Character", "\uD8BF\uDFFF"]]},
+
+{"description": "Invalid numeric entity character U+4FFFE",
+"input": "",
+"output": ["ParseError", ["Character", "\uD8FF\uDFFE"]]},
+
+{"description": "Invalid numeric entity character U+4FFFF",
+"input": "",
+"output": ["ParseError", ["Character", "\uD8FF\uDFFF"]]},
+
+{"description": "Invalid numeric entity character U+5FFFE",
+"input": "",
+"output": ["ParseError", ["Character", "\uD93F\uDFFE"]]},
+
+{"description": "Invalid numeric entity character U+5FFFF",
+"input": "",
+"output": ["ParseError", ["Character", "\uD93F\uDFFF"]]},
+
+{"description": "Invalid numeric entity character U+6FFFE",
+"input": "",
+"output": ["ParseError", ["Character", "\uD97F\uDFFE"]]},
+
+{"description": "Invalid numeric entity character U+6FFFF",
+"input": "",
+"output": ["ParseError", ["Character", "\uD97F\uDFFF"]]},
+
+{"description": "Invalid numeric entity character U+7FFFE",
+"input": "",
+"output": ["ParseError", ["Character", "\uD9BF\uDFFE"]]},
+
+{"description": "Invalid numeric entity character U+7FFFF",
+"input": "",
+"output": ["ParseError", ["Character", "\uD9BF\uDFFF"]]},
+
+{"description": "Invalid numeric entity character U+8FFFE",
+"input": "",
+"output": ["ParseError", ["Character", "\uD9FF\uDFFE"]]},
+
+{"description": "Invalid numeric entity character U+8FFFF",
+"input": "",
+"output": ["ParseError", ["Character", "\uD9FF\uDFFF"]]},
+
+{"description": "Invalid numeric entity character U+9FFFE",
+"input": "",
+"output": ["ParseError", ["Character", "\uDA3F\uDFFE"]]},
+
+{"description": "Invalid numeric entity character U+9FFFF",
+"input": "",
+"output": ["ParseError", ["Character", "\uDA3F\uDFFF"]]},
+
+{"description": "Invalid numeric entity character U+AFFFE",
+"input": "",
+"output": ["ParseError", ["Character", "\uDA7F\uDFFE"]]},
+
+{"description": "Invalid numeric entity character U+AFFFF",
+"input": "",
+"output": ["ParseError", ["Character", "\uDA7F\uDFFF"]]},
+
+{"description": "Invalid numeric entity character U+BFFFE",
+"input": "",
+"output": ["ParseError", ["Character", "\uDABF\uDFFE"]]},
+
+{"description": "Invalid numeric entity character U+BFFFF",
+"input": "",
+"output": ["ParseError", ["Character", "\uDABF\uDFFF"]]},
+
+{"description": "Invalid numeric entity character U+CFFFE",
+"input": "",
+"output": ["ParseError", ["Character", "\uDAFF\uDFFE"]]},
+
+{"description": "Invalid numeric entity character U+CFFFF",
+"input": "",
+"output": ["ParseError", ["Character", "\uDAFF\uDFFF"]]},
+
+{"description": "Invalid numeric entity character U+DFFFE",
+"input": "",
+"output": ["ParseError", ["Character", "\uDB3F\uDFFE"]]},
+
+{"description": "Invalid numeric entity character U+DFFFF",
+"input": "",
+"output": ["ParseError", ["Character", "\uDB3F\uDFFF"]]},
+
+{"description": "Invalid numeric entity character U+EFFFE",
+"input": "",
+"output": ["ParseError", ["Character", "\uDB7F\uDFFE"]]},
+
+{"description": "Invalid numeric entity character U+EFFFF",
+"input": "",
+"output": ["ParseError", ["Character", "\uDB7F\uDFFF"]]},
+
+{"description": "Invalid numeric entity character U+FFFFE",
+"input": "",
+"output": ["ParseError", ["Character", "\uDBBF\uDFFE"]]},
+
+{"description": "Invalid numeric entity character U+FFFFF",
+"input": "",
+"output": ["ParseError", ["Character", "\uDBBF\uDFFF"]]},
+
+{"description": "Invalid numeric entity character U+10FFFE",
+"input": "",
+"output": ["ParseError", ["Character", "\uDBFF\uDFFE"]]},
+
+{"description": "Invalid numeric entity character U+10FFFF",
+"input": "",
+"output": ["ParseError", ["Character", "\uDBFF\uDFFF"]]},
+
+{"description": "Valid numeric entity character U+0009",
+"input": "	",
+"output": [["Character", "\u0009"]]},
+
+{"description": "Valid numeric entity character U+000A",
+"input": "
",
+"output": [["Character", "\u000A"]]},
+
+{"description": "Valid numeric entity character U+0020",
+"input": " ",
+"output": [["Character", "\u0020"]]},
+
+{"description": "Valid numeric entity character U+0021",
+"input": "!",
+"output": [["Character", "\u0021"]]},
+
+{"description": "Valid numeric entity character U+0022",
+"input": """,
+"output": [["Character", "\u0022"]]},
+
+{"description": "Valid numeric entity character U+0023",
+"input": "#",
+"output": [["Character", "\u0023"]]},
+
+{"description": "Valid numeric entity character U+0024",
+"input": "$",
+"output": [["Character", "\u0024"]]},
+
+{"description": "Valid numeric entity character U+0025",
+"input": "%",
+"output": [["Character", "\u0025"]]},
+
+{"description": "Valid numeric entity character U+0026",
+"input": "&",
+"output": [["Character", "\u0026"]]},
+
+{"description": "Valid numeric entity character U+0027",
+"input": "'",
+"output": [["Character", "\u0027"]]},
+
+{"description": "Valid numeric entity character U+0028",
+"input": "(",
+"output": [["Character", "\u0028"]]},
+
+{"description": "Valid numeric entity character U+0029",
+"input": ")",
+"output": [["Character", "\u0029"]]},
+
+{"description": "Valid numeric entity character U+002A",
+"input": "*",
+"output": [["Character", "\u002A"]]},
+
+{"description": "Valid numeric entity character U+002B",
+"input": "+",
+"output": [["Character", "\u002B"]]},
+
+{"description": "Valid numeric entity character U+002C",
+"input": ",",
+"output": [["Character", "\u002C"]]},
+
+{"description": "Valid numeric entity character U+002D",
+"input": "-",
+"output": [["Character", "\u002D"]]},
+
+{"description": "Valid numeric entity character U+002E",
+"input": ".",
+"output": [["Character", "\u002E"]]},
+
+{"description": "Valid numeric entity character U+002F",
+"input": "/",
+"output": [["Character", "\u002F"]]},
+
+{"description": "Valid numeric entity character U+0030",
+"input": "0",
+"output": [["Character", "\u0030"]]},
+
+{"description": "Valid numeric entity character U+0031",
+"input": "1",
+"output": [["Character", "\u0031"]]},
+
+{"description": "Valid numeric entity character U+0032",
+"input": "2",
+"output": [["Character", "\u0032"]]},
+
+{"description": "Valid numeric entity character U+0033",
+"input": "3",
+"output": [["Character", "\u0033"]]},
+
+{"description": "Valid numeric entity character U+0034",
+"input": "4",
+"output": [["Character", "\u0034"]]},
+
+{"description": "Valid numeric entity character U+0035",
+"input": "5",
+"output": [["Character", "\u0035"]]},
+
+{"description": "Valid numeric entity character U+0036",
+"input": "6",
+"output": [["Character", "\u0036"]]},
+
+{"description": "Valid numeric entity character U+0037",
+"input": "7",
+"output": [["Character", "\u0037"]]},
+
+{"description": "Valid numeric entity character U+0038",
+"input": "8",
+"output": [["Character", "\u0038"]]},
+
+{"description": "Valid numeric entity character U+0039",
+"input": "9",
+"output": [["Character", "\u0039"]]},
+
+{"description": "Valid numeric entity character U+003A",
+"input": ":",
+"output": [["Character", "\u003A"]]},
+
+{"description": "Valid numeric entity character U+003B",
+"input": ";",
+"output": [["Character", "\u003B"]]},
+
+{"description": "Valid numeric entity character U+003C",
+"input": "<",
+"output": [["Character", "\u003C"]]},
+
+{"description": "Valid numeric entity character U+003D",
+"input": "=",
+"output": [["Character", "\u003D"]]},
+
+{"description": "Valid numeric entity character U+003E",
+"input": ">",
+"output": [["Character", "\u003E"]]},
+
+{"description": "Valid numeric entity character U+003F",
+"input": "?",
+"output": [["Character", "\u003F"]]},
+
+{"description": "Valid numeric entity character U+0040",
+"input": "@",
+"output": [["Character", "\u0040"]]},
+
+{"description": "Valid numeric entity character U+0041",
+"input": "A",
+"output": [["Character", "\u0041"]]},
+
+{"description": "Valid numeric entity character U+0042",
+"input": "B",
+"output": [["Character", "\u0042"]]},
+
+{"description": "Valid numeric entity character U+0043",
+"input": "C",
+"output": [["Character", "\u0043"]]},
+
+{"description": "Valid numeric entity character U+0044",
+"input": "D",
+"output": [["Character", "\u0044"]]},
+
+{"description": "Valid numeric entity character U+0045",
+"input": "E",
+"output": [["Character", "\u0045"]]},
+
+{"description": "Valid numeric entity character U+0046",
+"input": "F",
+"output": [["Character", "\u0046"]]},
+
+{"description": "Valid numeric entity character U+0047",
+"input": "G",
+"output": [["Character", "\u0047"]]},
+
+{"description": "Valid numeric entity character U+0048",
+"input": "H",
+"output": [["Character", "\u0048"]]},
+
+{"description": "Valid numeric entity character U+0049",
+"input": "I",
+"output": [["Character", "\u0049"]]},
+
+{"description": "Valid numeric entity character U+004A",
+"input": "J",
+"output": [["Character", "\u004A"]]},
+
+{"description": "Valid numeric entity character U+004B",
+"input": "K",
+"output": [["Character", "\u004B"]]},
+
+{"description": "Valid numeric entity character U+004C",
+"input": "L",
+"output": [["Character", "\u004C"]]},
+
+{"description": "Valid numeric entity character U+004D",
+"input": "M",
+"output": [["Character", "\u004D"]]},
+
+{"description": "Valid numeric entity character U+004E",
+"input": "N",
+"output": [["Character", "\u004E"]]},
+
+{"description": "Valid numeric entity character U+004F",
+"input": "O",
+"output": [["Character", "\u004F"]]},
+
+{"description": "Valid numeric entity character U+0050",
+"input": "P",
+"output": [["Character", "\u0050"]]},
+
+{"description": "Valid numeric entity character U+0051",
+"input": "Q",
+"output": [["Character", "\u0051"]]},
+
+{"description": "Valid numeric entity character U+0052",
+"input": "R",
+"output": [["Character", "\u0052"]]},
+
+{"description": "Valid numeric entity character U+0053",
+"input": "S",
+"output": [["Character", "\u0053"]]},
+
+{"description": "Valid numeric entity character U+0054",
+"input": "T",
+"output": [["Character", "\u0054"]]},
+
+{"description": "Valid numeric entity character U+0055",
+"input": "U",
+"output": [["Character", "\u0055"]]},
+
+{"description": "Valid numeric entity character U+0056",
+"input": "V",
+"output": [["Character", "\u0056"]]},
+
+{"description": "Valid numeric entity character U+0057",
+"input": "W",
+"output": [["Character", "\u0057"]]},
+
+{"description": "Valid numeric entity character U+0058",
+"input": "X",
+"output": [["Character", "\u0058"]]},
+
+{"description": "Valid numeric entity character U+0059",
+"input": "Y",
+"output": [["Character", "\u0059"]]},
+
+{"description": "Valid numeric entity character U+005A",
+"input": "Z",
+"output": [["Character", "\u005A"]]},
+
+{"description": "Valid numeric entity character U+005B",
+"input": "[",
+"output": [["Character", "\u005B"]]},
+
+{"description": "Valid numeric entity character U+005C",
+"input": "\",
+"output": [["Character", "\u005C"]]},
+
+{"description": "Valid numeric entity character U+005D",
+"input": "]",
+"output": [["Character", "\u005D"]]},
+
+{"description": "Valid numeric entity character U+005E",
+"input": "^",
+"output": [["Character", "\u005E"]]},
+
+{"description": "Valid numeric entity character U+005F",
+"input": "_",
+"output": [["Character", "\u005F"]]},
+
+{"description": "Valid numeric entity character U+0060",
+"input": "`",
+"output": [["Character", "\u0060"]]},
+
+{"description": "Valid numeric entity character U+0061",
+"input": "a",
+"output": [["Character", "\u0061"]]},
+
+{"description": "Valid numeric entity character U+0062",
+"input": "b",
+"output": [["Character", "\u0062"]]},
+
+{"description": "Valid numeric entity character U+0063",
+"input": "c",
+"output": [["Character", "\u0063"]]},
+
+{"description": "Valid numeric entity character U+0064",
+"input": "d",
+"output": [["Character", "\u0064"]]},
+
+{"description": "Valid numeric entity character U+0065",
+"input": "e",
+"output": [["Character", "\u0065"]]},
+
+{"description": "Valid numeric entity character U+0066",
+"input": "f",
+"output": [["Character", "\u0066"]]},
+
+{"description": "Valid numeric entity character U+0067",
+"input": "g",
+"output": [["Character", "\u0067"]]},
+
+{"description": "Valid numeric entity character U+0068",
+"input": "h",
+"output": [["Character", "\u0068"]]},
+
+{"description": "Valid numeric entity character U+0069",
+"input": "i",
+"output": [["Character", "\u0069"]]},
+
+{"description": "Valid numeric entity character U+006A",
+"input": "j",
+"output": [["Character", "\u006A"]]},
+
+{"description": "Valid numeric entity character U+006B",
+"input": "k",
+"output": [["Character", "\u006B"]]},
+
+{"description": "Valid numeric entity character U+006C",
+"input": "l",
+"output": [["Character", "\u006C"]]},
+
+{"description": "Valid numeric entity character U+006D",
+"input": "m",
+"output": [["Character", "\u006D"]]},
+
+{"description": "Valid numeric entity character U+006E",
+"input": "n",
+"output": [["Character", "\u006E"]]},
+
+{"description": "Valid numeric entity character U+006F",
+"input": "o",
+"output": [["Character", "\u006F"]]},
+
+{"description": "Valid numeric entity character U+0070",
+"input": "p",
+"output": [["Character", "\u0070"]]},
+
+{"description": "Valid numeric entity character U+0071",
+"input": "q",
+"output": [["Character", "\u0071"]]},
+
+{"description": "Valid numeric entity character U+0072",
+"input": "r",
+"output": [["Character", "\u0072"]]},
+
+{"description": "Valid numeric entity character U+0073",
+"input": "s",
+"output": [["Character", "\u0073"]]},
+
+{"description": "Valid numeric entity character U+0074",
+"input": "t",
+"output": [["Character", "\u0074"]]},
+
+{"description": "Valid numeric entity character U+0075",
+"input": "u",
+"output": [["Character", "\u0075"]]},
+
+{"description": "Valid numeric entity character U+0076",
+"input": "v",
+"output": [["Character", "\u0076"]]},
+
+{"description": "Valid numeric entity character U+0077",
+"input": "w",
+"output": [["Character", "\u0077"]]},
+
+{"description": "Valid numeric entity character U+0078",
+"input": "x",
+"output": [["Character", "\u0078"]]},
+
+{"description": "Valid numeric entity character U+0079",
+"input": "y",
+"output": [["Character", "\u0079"]]},
+
+{"description": "Valid numeric entity character U+007A",
+"input": "z",
+"output": [["Character", "\u007A"]]},
+
+{"description": "Valid numeric entity character U+007B",
+"input": "{",
+"output": [["Character", "\u007B"]]},
+
+{"description": "Valid numeric entity character U+007C",
+"input": "|",
+"output": [["Character", "\u007C"]]},
+
+{"description": "Valid numeric entity character U+007D",
+"input": "}",
+"output": [["Character", "\u007D"]]},
+
+{"description": "Valid numeric entity character U+007E",
+"input": "~",
+"output": [["Character", "\u007E"]]},
+
+{"description": "Valid numeric entity character U+00A0",
+"input": " ",
+"output": [["Character", "\u00A0"]]},
+
+{"description": "Valid numeric entity character U+00A1",
+"input": "¡",
+"output": [["Character", "\u00A1"]]},
+
+{"description": "Valid numeric entity character U+00A2",
+"input": "¢",
+"output": [["Character", "\u00A2"]]},
+
+{"description": "Valid numeric entity character U+00A3",
+"input": "£",
+"output": [["Character", "\u00A3"]]},
+
+{"description": "Valid numeric entity character U+00A4",
+"input": "¤",
+"output": [["Character", "\u00A4"]]},
+
+{"description": "Valid numeric entity character U+00A5",
+"input": "¥",
+"output": [["Character", "\u00A5"]]},
+
+{"description": "Valid numeric entity character U+00A6",
+"input": "¦",
+"output": [["Character", "\u00A6"]]},
+
+{"description": "Valid numeric entity character U+00A7",
+"input": "§",
+"output": [["Character", "\u00A7"]]},
+
+{"description": "Valid numeric entity character U+00A8",
+"input": "¨",
+"output": [["Character", "\u00A8"]]},
+
+{"description": "Valid numeric entity character U+00A9",
+"input": "©",
+"output": [["Character", "\u00A9"]]},
+
+{"description": "Valid numeric entity character U+00AA",
+"input": "ª",
+"output": [["Character", "\u00AA"]]},
+
+{"description": "Valid numeric entity character U+00AB",
+"input": "«",
+"output": [["Character", "\u00AB"]]},
+
+{"description": "Valid numeric entity character U+00AC",
+"input": "¬",
+"output": [["Character", "\u00AC"]]},
+
+{"description": "Valid numeric entity character U+00AD",
+"input": "­",
+"output": [["Character", "\u00AD"]]},
+
+{"description": "Valid numeric entity character U+00AE",
+"input": "®",
+"output": [["Character", "\u00AE"]]},
+
+{"description": "Valid numeric entity character U+00AF",
+"input": "¯",
+"output": [["Character", "\u00AF"]]},
+
+{"description": "Valid numeric entity character U+00B0",
+"input": "°",
+"output": [["Character", "\u00B0"]]},
+
+{"description": "Valid numeric entity character U+00B1",
+"input": "±",
+"output": [["Character", "\u00B1"]]},
+
+{"description": "Valid numeric entity character U+00B2",
+"input": "²",
+"output": [["Character", "\u00B2"]]},
+
+{"description": "Valid numeric entity character U+00B3",
+"input": "³",
+"output": [["Character", "\u00B3"]]},
+
+{"description": "Valid numeric entity character U+00B4",
+"input": "´",
+"output": [["Character", "\u00B4"]]},
+
+{"description": "Valid numeric entity character U+00B5",
+"input": "µ",
+"output": [["Character", "\u00B5"]]},
+
+{"description": "Valid numeric entity character U+00B6",
+"input": "¶",
+"output": [["Character", "\u00B6"]]},
+
+{"description": "Valid numeric entity character U+00B7",
+"input": "·",
+"output": [["Character", "\u00B7"]]},
+
+{"description": "Valid numeric entity character U+00B8",
+"input": "¸",
+"output": [["Character", "\u00B8"]]},
+
+{"description": "Valid numeric entity character U+00B9",
+"input": "¹",
+"output": [["Character", "\u00B9"]]},
+
+{"description": "Valid numeric entity character U+00BA",
+"input": "º",
+"output": [["Character", "\u00BA"]]},
+
+{"description": "Valid numeric entity character U+00BB",
+"input": "»",
+"output": [["Character", "\u00BB"]]},
+
+{"description": "Valid numeric entity character U+00BC",
+"input": "¼",
+"output": [["Character", "\u00BC"]]},
+
+{"description": "Valid numeric entity character U+00BD",
+"input": "½",
+"output": [["Character", "\u00BD"]]},
+
+{"description": "Valid numeric entity character U+00BE",
+"input": "¾",
+"output": [["Character", "\u00BE"]]},
+
+{"description": "Valid numeric entity character U+00BF",
+"input": "¿",
+"output": [["Character", "\u00BF"]]},
+
+{"description": "Valid numeric entity character U+00C0",
+"input": "À",
+"output": [["Character", "\u00C0"]]},
+
+{"description": "Valid numeric entity character U+00C1",
+"input": "Á",
+"output": [["Character", "\u00C1"]]},
+
+{"description": "Valid numeric entity character U+00C2",
+"input": "Â",
+"output": [["Character", "\u00C2"]]},
+
+{"description": "Valid numeric entity character U+00C3",
+"input": "Ã",
+"output": [["Character", "\u00C3"]]},
+
+{"description": "Valid numeric entity character U+00C4",
+"input": "Ä",
+"output": [["Character", "\u00C4"]]},
+
+{"description": "Valid numeric entity character U+00C5",
+"input": "Å",
+"output": [["Character", "\u00C5"]]},
+
+{"description": "Valid numeric entity character U+00C6",
+"input": "Æ",
+"output": [["Character", "\u00C6"]]},
+
+{"description": "Valid numeric entity character U+00C7",
+"input": "Ç",
+"output": [["Character", "\u00C7"]]},
+
+{"description": "Valid numeric entity character U+00C8",
+"input": "È",
+"output": [["Character", "\u00C8"]]},
+
+{"description": "Valid numeric entity character U+00C9",
+"input": "É",
+"output": [["Character", "\u00C9"]]},
+
+{"description": "Valid numeric entity character U+00CA",
+"input": "Ê",
+"output": [["Character", "\u00CA"]]},
+
+{"description": "Valid numeric entity character U+00CB",
+"input": "Ë",
+"output": [["Character", "\u00CB"]]},
+
+{"description": "Valid numeric entity character U+00CC",
+"input": "Ì",
+"output": [["Character", "\u00CC"]]},
+
+{"description": "Valid numeric entity character U+00CD",
+"input": "Í",
+"output": [["Character", "\u00CD"]]},
+
+{"description": "Valid numeric entity character U+00CE",
+"input": "Î",
+"output": [["Character", "\u00CE"]]},
+
+{"description": "Valid numeric entity character U+00CF",
+"input": "Ï",
+"output": [["Character", "\u00CF"]]},
+
+{"description": "Valid numeric entity character U+00D0",
+"input": "Ð",
+"output": [["Character", "\u00D0"]]},
+
+{"description": "Valid numeric entity character U+00D1",
+"input": "Ñ",
+"output": [["Character", "\u00D1"]]},
+
+{"description": "Valid numeric entity character U+00D2",
+"input": "Ò",
+"output": [["Character", "\u00D2"]]},
+
+{"description": "Valid numeric entity character U+00D3",
+"input": "Ó",
+"output": [["Character", "\u00D3"]]},
+
+{"description": "Valid numeric entity character U+00D4",
+"input": "Ô",
+"output": [["Character", "\u00D4"]]},
+
+{"description": "Valid numeric entity character U+00D5",
+"input": "Õ",
+"output": [["Character", "\u00D5"]]},
+
+{"description": "Valid numeric entity character U+00D6",
+"input": "Ö",
+"output": [["Character", "\u00D6"]]},
+
+{"description": "Valid numeric entity character U+00D7",
+"input": "×",
+"output": [["Character", "\u00D7"]]},
+
+{"description": "Valid numeric entity character U+00D8",
+"input": "Ø",
+"output": [["Character", "\u00D8"]]},
+
+{"description": "Valid numeric entity character U+00D9",
+"input": "Ù",
+"output": [["Character", "\u00D9"]]},
+
+{"description": "Valid numeric entity character U+00DA",
+"input": "Ú",
+"output": [["Character", "\u00DA"]]},
+
+{"description": "Valid numeric entity character U+00DB",
+"input": "Û",
+"output": [["Character", "\u00DB"]]},
+
+{"description": "Valid numeric entity character U+00DC",
+"input": "Ü",
+"output": [["Character", "\u00DC"]]},
+
+{"description": "Valid numeric entity character U+00DD",
+"input": "Ý",
+"output": [["Character", "\u00DD"]]},
+
+{"description": "Valid numeric entity character U+00DE",
+"input": "Þ",
+"output": [["Character", "\u00DE"]]},
+
+{"description": "Valid numeric entity character U+00DF",
+"input": "ß",
+"output": [["Character", "\u00DF"]]},
+
+{"description": "Valid numeric entity character U+00E0",
+"input": "à",
+"output": [["Character", "\u00E0"]]},
+
+{"description": "Valid numeric entity character U+00E1",
+"input": "á",
+"output": [["Character", "\u00E1"]]},
+
+{"description": "Valid numeric entity character U+00E2",
+"input": "â",
+"output": [["Character", "\u00E2"]]},
+
+{"description": "Valid numeric entity character U+00E3",
+"input": "ã",
+"output": [["Character", "\u00E3"]]},
+
+{"description": "Valid numeric entity character U+00E4",
+"input": "ä",
+"output": [["Character", "\u00E4"]]},
+
+{"description": "Valid numeric entity character U+00E5",
+"input": "å",
+"output": [["Character", "\u00E5"]]},
+
+{"description": "Valid numeric entity character U+00E6",
+"input": "æ",
+"output": [["Character", "\u00E6"]]},
+
+{"description": "Valid numeric entity character U+00E7",
+"input": "ç",
+"output": [["Character", "\u00E7"]]},
+
+{"description": "Valid numeric entity character U+00E8",
+"input": "è",
+"output": [["Character", "\u00E8"]]},
+
+{"description": "Valid numeric entity character U+00E9",
+"input": "é",
+"output": [["Character", "\u00E9"]]},
+
+{"description": "Valid numeric entity character U+00EA",
+"input": "ê",
+"output": [["Character", "\u00EA"]]},
+
+{"description": "Valid numeric entity character U+00EB",
+"input": "ë",
+"output": [["Character", "\u00EB"]]},
+
+{"description": "Valid numeric entity character U+00EC",
+"input": "ì",
+"output": [["Character", "\u00EC"]]},
+
+{"description": "Valid numeric entity character U+00ED",
+"input": "í",
+"output": [["Character", "\u00ED"]]},
+
+{"description": "Valid numeric entity character U+00EE",
+"input": "î",
+"output": [["Character", "\u00EE"]]},
+
+{"description": "Valid numeric entity character U+00EF",
+"input": "ï",
+"output": [["Character", "\u00EF"]]},
+
+{"description": "Valid numeric entity character U+00F0",
+"input": "ð",
+"output": [["Character", "\u00F0"]]},
+
+{"description": "Valid numeric entity character U+00F1",
+"input": "ñ",
+"output": [["Character", "\u00F1"]]},
+
+{"description": "Valid numeric entity character U+00F2",
+"input": "ò",
+"output": [["Character", "\u00F2"]]},
+
+{"description": "Valid numeric entity character U+00F3",
+"input": "ó",
+"output": [["Character", "\u00F3"]]},
+
+{"description": "Valid numeric entity character U+00F4",
+"input": "ô",
+"output": [["Character", "\u00F4"]]},
+
+{"description": "Valid numeric entity character U+00F5",
+"input": "õ",
+"output": [["Character", "\u00F5"]]},
+
+{"description": "Valid numeric entity character U+00F6",
+"input": "ö",
+"output": [["Character", "\u00F6"]]},
+
+{"description": "Valid numeric entity character U+00F7",
+"input": "÷",
+"output": [["Character", "\u00F7"]]},
+
+{"description": "Valid numeric entity character U+00F8",
+"input": "ø",
+"output": [["Character", "\u00F8"]]},
+
+{"description": "Valid numeric entity character U+00F9",
+"input": "ù",
+"output": [["Character", "\u00F9"]]},
+
+{"description": "Valid numeric entity character U+00FA",
+"input": "ú",
+"output": [["Character", "\u00FA"]]},
+
+{"description": "Valid numeric entity character U+00FB",
+"input": "û",
+"output": [["Character", "\u00FB"]]},
+
+{"description": "Valid numeric entity character U+00FC",
+"input": "ü",
+"output": [["Character", "\u00FC"]]},
+
+{"description": "Valid numeric entity character U+00FD",
+"input": "ý",
+"output": [["Character", "\u00FD"]]},
+
+{"description": "Valid numeric entity character U+00FE",
+"input": "þ",
+"output": [["Character", "\u00FE"]]},
+
+{"description": "Valid numeric entity character U+00FF",
+"input": "ÿ",
+"output": [["Character", "\u00FF"]]},
+
+{"description": "Valid numeric entity character U+D7FF",
+"input": "퟿",
+"output": [["Character", "\uD7FF"]]},
+
+{"description": "Valid numeric entity character U+E000",
+"input": "",
+"output": [["Character", "\uE000"]]},
+
+{"description": "Valid numeric entity character U+FDCF",
+"input": "﷏",
+"output": [["Character", "\uFDCF"]]},
+
+{"description": "Valid numeric entity character U+FDF0",
+"input": "ﷰ",
+"output": [["Character", "\uFDF0"]]},
+
+{"description": "Valid numeric entity character U+FFFD",
+"input": "�",
+"output": [["Character", "\uFFFD"]]},
+
+{"description": "Valid numeric entity character U+10000",
+"input": "𐀀",
+"output": [["Character", "\uD800\uDC00"]]},
+
+{"description": "Valid numeric entity character U+1FFFD",
+"input": "🿽",
+"output": [["Character", "\uD83F\uDFFD"]]},
+
+{"description": "Valid numeric entity character U+20000",
+"input": "𠀀",
+"output": [["Character", "\uD840\uDC00"]]},
+
+{"description": "Valid numeric entity character U+2FFFD",
+"input": "𯿽",
+"output": [["Character", "\uD87F\uDFFD"]]},
+
+{"description": "Valid numeric entity character U+30000",
+"input": "𰀀",
+"output": [["Character", "\uD880\uDC00"]]},
+
+{"description": "Valid numeric entity character U+3FFFD",
+"input": "𿿽",
+"output": [["Character", "\uD8BF\uDFFD"]]},
+
+{"description": "Valid numeric entity character U+40000",
+"input": "񀀀",
+"output": [["Character", "\uD8C0\uDC00"]]},
+
+{"description": "Valid numeric entity character U+4FFFD",
+"input": "񏿽",
+"output": [["Character", "\uD8FF\uDFFD"]]},
+
+{"description": "Valid numeric entity character U+50000",
+"input": "񐀀",
+"output": [["Character", "\uD900\uDC00"]]},
+
+{"description": "Valid numeric entity character U+5FFFD",
+"input": "񟿽",
+"output": [["Character", "\uD93F\uDFFD"]]},
+
+{"description": "Valid numeric entity character U+60000",
+"input": "񠀀",
+"output": [["Character", "\uD940\uDC00"]]},
+
+{"description": "Valid numeric entity character U+6FFFD",
+"input": "񯿽",
+"output": [["Character", "\uD97F\uDFFD"]]},
+
+{"description": "Valid numeric entity character U+70000",
+"input": "񰀀",
+"output": [["Character", "\uD980\uDC00"]]},
+
+{"description": "Valid numeric entity character U+7FFFD",
+"input": "񿿽",
+"output": [["Character", "\uD9BF\uDFFD"]]},
+
+{"description": "Valid numeric entity character U+80000",
+"input": "򀀀",
+"output": [["Character", "\uD9C0\uDC00"]]},
+
+{"description": "Valid numeric entity character U+8FFFD",
+"input": "򏿽",
+"output": [["Character", "\uD9FF\uDFFD"]]},
+
+{"description": "Valid numeric entity character U+90000",
+"input": "򐀀",
+"output": [["Character", "\uDA00\uDC00"]]},
+
+{"description": "Valid numeric entity character U+9FFFD",
+"input": "򟿽",
+"output": [["Character", "\uDA3F\uDFFD"]]},
+
+{"description": "Valid numeric entity character U+A0000",
+"input": "򠀀",
+"output": [["Character", "\uDA40\uDC00"]]},
+
+{"description": "Valid numeric entity character U+AFFFD",
+"input": "򯿽",
+"output": [["Character", "\uDA7F\uDFFD"]]},
+
+{"description": "Valid numeric entity character U+B0000",
+"input": "򰀀",
+"output": [["Character", "\uDA80\uDC00"]]},
+
+{"description": "Valid numeric entity character U+BFFFD",
+"input": "򿿽",
+"output": [["Character", "\uDABF\uDFFD"]]},
+
+{"description": "Valid numeric entity character U+C0000",
+"input": "󀀀",
+"output": [["Character", "\uDAC0\uDC00"]]},
+
+{"description": "Valid numeric entity character U+CFFFD",
+"input": "󏿽",
+"output": [["Character", "\uDAFF\uDFFD"]]},
+
+{"description": "Valid numeric entity character U+D0000",
+"input": "󐀀",
+"output": [["Character", "\uDB00\uDC00"]]},
+
+{"description": "Valid numeric entity character U+DFFFD",
+"input": "󟿽",
+"output": [["Character", "\uDB3F\uDFFD"]]},
+
+{"description": "Valid numeric entity character U+E0000",
+"input": "󠀀",
+"output": [["Character", "\uDB40\uDC00"]]},
+
+{"description": "Valid numeric entity character U+EFFFD",
+"input": "󯿽",
+"output": [["Character", "\uDB7F\uDFFD"]]},
+
+{"description": "Valid numeric entity character U+F0000",
+"input": "󰀀",
+"output": [["Character", "\uDB80\uDC00"]]},
+
+{"description": "Valid numeric entity character U+FFFFD",
+"input": "󿿽",
+"output": [["Character", "\uDBBF\uDFFD"]]},
+
+{"description": "Valid numeric entity character U+100000",
+"input": "􀀀",
+"output": [["Character", "\uDBC0\uDC00"]]},
+
+{"description": "Valid numeric entity character U+10FFFD",
+"input": "􏿽",
+"output": [["Character", "\uDBFF\uDFFD"]]}
+
+]}
+
+
diff --git a/pkgs/html/test/data/tokenizer/pendingSpecChanges.test b/pkgs/html/test/data/tokenizer/pendingSpecChanges.test
new file mode 100644
index 0000000..1b7dc3c
--- /dev/null
+++ b/pkgs/html/test/data/tokenizer/pendingSpecChanges.test
@@ -0,0 +1,7 @@
+{"tests": [
+
+{"description":"<!---- >",
+"input":"<!---- >",
+"output":["ParseError", "ParseError", ["Comment","-- >"]]}
+
+]}
diff --git a/pkgs/html/test/data/tokenizer/test1.test b/pkgs/html/test/data/tokenizer/test1.test
new file mode 100644
index 0000000..5de66f5
--- /dev/null
+++ b/pkgs/html/test/data/tokenizer/test1.test
@@ -0,0 +1,196 @@
+{"tests": [
+
+{"description":"Correct Doctype lowercase",
+"input":"<!DOCTYPE html>",
+"output":[["DOCTYPE", "html", null, null, true]]},
+
+{"description":"Correct Doctype uppercase",
+"input":"<!DOCTYPE HTML>",
+"output":[["DOCTYPE", "html", null, null, true]]},
+
+{"description":"Correct Doctype mixed case",
+"input":"<!DOCTYPE HtMl>",
+"output":[["DOCTYPE", "html", null, null, true]]},
+
+{"description":"Correct Doctype case with EOF",
+"input":"<!DOCTYPE HtMl",
+"output":["ParseError", ["DOCTYPE", "html", null, null, false]]},
+
+{"description":"Truncated doctype start",
+"input":"<!DOC>",
+"output":["ParseError", ["Comment", "DOC"]]},
+
+{"description":"Doctype in error",
+"input":"<!DOCTYPE foo>",
+"output":[["DOCTYPE", "foo", null, null, true]]},
+
+{"description":"Single Start Tag",
+"input":"<h>",
+"output":[["StartTag", "h", {}]]},
+
+{"description":"Empty end tag",
+"input":"</>",
+"output":["ParseError"]},
+
+{"description":"Empty start tag",
+"input":"<>",
+"output":["ParseError", ["Character", "<>"]]},
+
+{"description":"Start Tag w/attribute",
+"input":"<h a='b'>",
+"output":[["StartTag", "h", {"a":"b"}]]},
+
+{"description":"Start Tag w/attribute no quotes",
+"input":"<h a=b>",
+"output":[["StartTag", "h", {"a":"b"}]]},
+
+{"description":"Start/End Tag",
+"input":"<h></h>",
+"output":[["StartTag", "h", {}], ["EndTag", "h"]]},
+
+{"description":"Two unclosed start tags",
+"input":"<p>One<p>Two",
+"output":[["StartTag", "p", {}], ["Character", "One"], ["StartTag", "p", {}], ["Character", "Two"]]},
+
+{"description":"End Tag w/attribute",
+"input":"<h></h a='b'>",
+"output":[["StartTag", "h", {}], "ParseError", ["EndTag", "h"]]},
+
+{"description":"Multiple atts",
+"input":"<h a='b' c='d'>",
+"output":[["StartTag", "h", {"a":"b", "c":"d"}]]},
+
+{"description":"Multiple atts no space",
+"input":"<h a='b'c='d'>",
+"output":["ParseError", ["StartTag", "h", {"a":"b", "c":"d"}]]},
+
+{"description":"Repeated attr",
+ "input":"<h a='b' a='d'>",
+ "output":["ParseError", ["StartTag", "h", {"a":"b"}]]},
+
+{"description":"Simple comment",
+ "input":"<!--comment-->",
+ "output":[["Comment", "comment"]]},
+
+{"description":"Comment, Central dash no space",
+ "input":"<!----->",
+ "output":["ParseError", ["Comment", "-"]]},
+
+{"description":"Comment, two central dashes",
+"input":"<!-- --comment -->",
+"output":["ParseError", ["Comment", " --comment "]]},
+
+{"description":"Unfinished comment",
+"input":"<!--comment",
+"output":["ParseError", ["Comment", "comment"]]},
+
+{"description":"Start of a comment",
+"input":"<!-",
+"output":["ParseError", ["Comment", "-"]]},
+
+{"description":"Short comment",
+ "input":"<!-->",
+ "output":["ParseError", ["Comment", ""]]},
+
+{"description":"Short comment two",
+ "input":"<!--->",
+ "output":["ParseError", ["Comment", ""]]},
+
+{"description":"Short comment three",
+ "input":"<!---->",
+ "output":[["Comment", ""]]},
+
+
+{"description":"Ampersand EOF",
+"input":"&",
+"output":[["Character", "&"]]},
+
+{"description":"Ampersand ampersand EOF",
+"input":"&&",
+"output":[["Character", "&&"]]},
+
+{"description":"Ampersand space EOF",
+"input":"& ",
+"output":[["Character", "& "]]},
+
+{"description":"Unfinished entity",
+"input":"&f",
+"output":["ParseError", ["Character", "&f"]]},
+
+{"description":"Ampersand, number sign",
+"input":"&#",
+"output":["ParseError", ["Character", "&#"]]},
+
+{"description":"Unfinished numeric entity",
+"input":"&#x",
+"output":["ParseError", ["Character", "&#x"]]},
+
+{"description":"Entity with trailing semicolon (1)",
+"input":"I'm ¬it",
+"output":[["Character","I'm \u00ACit"]]},
+
+{"description":"Entity with trailing semicolon (2)",
+"input":"I'm ∉",
+"output":[["Character","I'm \u2209"]]},
+
+{"description":"Entity without trailing semicolon (1)",
+"input":"I'm ¬it",
+"output":[["Character","I'm "], "ParseError", ["Character", "\u00ACit"]]},
+
+{"description":"Entity without trailing semicolon (2)",
+"input":"I'm ¬in",
+"output":[["Character","I'm "], "ParseError", ["Character", "\u00ACin"]]},
+
+{"description":"Partial entity match at end of file",
+"input":"I'm &no",
+"output":[["Character","I'm "], "ParseError", ["Character", "&no"]]},
+
+{"description":"Non-ASCII character reference name",
+"input":"&\u00AC;",
+"output":["ParseError", ["Character", "&\u00AC;"]]},
+
+{"description":"ASCII decimal entity",
+"input":"$",
+"output":[["Character","$"]]},
+
+{"description":"ASCII hexadecimal entity",
+"input":"?",
+"output":[["Character","?"]]},
+
+{"description":"Hexadecimal entity in attribute",
+"input":"<h a='?'></h>",
+"output":[["StartTag", "h", {"a":"?"}], ["EndTag", "h"]]},
+
+{"description":"Entity in attribute without semicolon ending in x",
+"input":"<h a='¬x'>",
+"output":["ParseError", ["StartTag", "h", {"a":"¬x"}]]},
+
+{"description":"Entity in attribute without semicolon ending in 1",
+"input":"<h a='¬1'>",
+"output":["ParseError", ["StartTag", "h", {"a":"¬1"}]]},
+
+{"description":"Entity in attribute without semicolon ending in i",
+"input":"<h a='¬i'>",
+"output":["ParseError", ["StartTag", "h", {"a":"¬i"}]]},
+
+{"description":"Entity in attribute without semicolon",
+"input":"<h a='©'>",
+"output":["ParseError", ["StartTag", "h", {"a":"\u00A9"}]]},
+
+{"description":"Unquoted attribute ending in ampersand",
+"input":"<s o=& t>",
+"output":[["StartTag","s",{"o":"&","t":""}]]},
+
+{"description":"Unquoted attribute at end of tag with final character of &, with tag followed by characters",
+"input":"<a a=a&>foo",
+"output":[["StartTag", "a", {"a":"a&"}], ["Character", "foo"]]},
+
+{"description":"plaintext element",
+ "input":"<plaintext>foobar",
+ "output":[["StartTag","plaintext",{}], ["Character","foobar"]]},
+
+{"description":"Open angled bracket in unquoted attribute value state",
+ "input":"<a a=f<>",
+ "output":["ParseError", ["StartTag", "a", {"a":"f<"}]]}
+
+]}
diff --git a/pkgs/html/test/data/tokenizer/test2.test b/pkgs/html/test/data/tokenizer/test2.test
new file mode 100644
index 0000000..e157514
--- /dev/null
+++ b/pkgs/html/test/data/tokenizer/test2.test
@@ -0,0 +1,179 @@
+{"tests": [
+
+{"description":"DOCTYPE without name",
+"input":"<!DOCTYPE>",
+"output":["ParseError", "ParseError", ["DOCTYPE", "", null, null, false]]},
+
+{"description":"DOCTYPE without space before name",
+"input":"<!DOCTYPEhtml>",
+"output":["ParseError", ["DOCTYPE", "html", null, null, true]]},
+
+{"description":"Incorrect DOCTYPE without a space before name",
+"input":"<!DOCTYPEfoo>",
+"output":["ParseError", ["DOCTYPE", "foo", null, null, true]]},
+
+{"description":"DOCTYPE with publicId",
+"input":"<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML Transitional 4.01//EN\">",
+"output":[["DOCTYPE", "html", "-//W3C//DTD HTML Transitional 4.01//EN", null, true]]},
+
+{"description":"DOCTYPE with EOF after PUBLIC",
+"input":"<!DOCTYPE html PUBLIC",
+"output":["ParseError", ["DOCTYPE", "html", null, null, false]]},
+
+{"description":"DOCTYPE with EOF after PUBLIC '",
+"input":"<!DOCTYPE html PUBLIC '",
+"output":["ParseError", ["DOCTYPE", "html", "", null, false]]},
+
+{"description":"DOCTYPE with EOF after PUBLIC 'x",
+"input":"<!DOCTYPE html PUBLIC 'x",
+"output":["ParseError", ["DOCTYPE", "html", "x", null, false]]},
+
+{"description":"DOCTYPE with systemId",
+"input":"<!DOCTYPE html SYSTEM \"-//W3C//DTD HTML Transitional 4.01//EN\">",
+"output":[["DOCTYPE", "html", null, "-//W3C//DTD HTML Transitional 4.01//EN", true]]},
+
+{"description":"DOCTYPE with publicId and systemId",
+"input":"<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML Transitional 4.01//EN\" \"-//W3C//DTD HTML Transitional 4.01//EN\">",
+"output":[["DOCTYPE", "html", "-//W3C//DTD HTML Transitional 4.01//EN", "-//W3C//DTD HTML Transitional 4.01//EN", true]]},
+
+{"description":"DOCTYPE with > in double-quoted publicId",
+"input":"<!DOCTYPE html PUBLIC \">x",
+"output":["ParseError", ["DOCTYPE", "html", "", null, false], ["Character", "x"]]},
+
+{"description":"DOCTYPE with > in single-quoted publicId",
+"input":"<!DOCTYPE html PUBLIC '>x",
+"output":["ParseError", ["DOCTYPE", "html", "", null, false], ["Character", "x"]]},
+
+{"description":"DOCTYPE with > in double-quoted systemId",
+"input":"<!DOCTYPE html PUBLIC \"foo\" \">x",
+"output":["ParseError", ["DOCTYPE", "html", "foo", "", false], ["Character", "x"]]},
+
+{"description":"DOCTYPE with > in single-quoted systemId",
+"input":"<!DOCTYPE html PUBLIC 'foo' '>x",
+"output":["ParseError", ["DOCTYPE", "html", "foo", "", false], ["Character", "x"]]},
+
+{"description":"Incomplete doctype",
+"input":"<!DOCTYPE html ",
+"output":["ParseError", ["DOCTYPE", "html", null, null, false]]},
+
+{"description":"Numeric entity representing the NUL character",
+"input":"�",
+"output":["ParseError", ["Character", "\uFFFD"]]},
+
+{"description":"Hexadecimal entity representing the NUL character",
+"input":"�",
+"output":["ParseError", ["Character", "\uFFFD"]]},
+
+{"description":"Numeric entity representing a codepoint after 1114111 (U+10FFFF)",
+"input":"�",
+"output":["ParseError", ["Character", "\uFFFD"]]},
+
+{"description":"Hexadecimal entity representing a codepoint after 1114111 (U+10FFFF)",
+"input":"�",
+"output":["ParseError", ["Character", "\uFFFD"]]},
+
+{"description":"Hexadecimal entity pair representing a surrogate pair",
+"input":"��",
+"output":["ParseError", ["Character", "\uFFFD"], "ParseError", ["Character", "\uFFFD"]]},
+
+{"description":"Hexadecimal entity with mixed uppercase and lowercase",
+"input":"ꯍ",
+"output":[["Character", "\uABCD"]]},
+
+{"description":"Entity without a name",
+"input":"&;",
+"output":["ParseError", ["Character", "&;"]]},
+
+{"description":"Unescaped ampersand in attribute value",
+"input":"<h a='&'>",
+"output":[["StartTag", "h", { "a":"&" }]]},
+
+{"description":"StartTag containing <",
+"input":"<a<b>",
+"output":[["StartTag", "a<b", { }]]},
+
+{"description":"Non-void element containing trailing /",
+"input":"<h/>",
+"output":[["StartTag","h",{},true]]},
+
+{"description":"Void element with permitted slash",
+"input":"<br/>",
+"output":[["StartTag","br",{},true]]},
+
+{"description":"Void element with permitted slash (with attribute)",
+"input":"<br foo='bar'/>",
+"output":[["StartTag","br",{"foo":"bar"},true]]},
+
+{"description":"StartTag containing /",
+"input":"<h/a='b'>",
+"output":["ParseError", ["StartTag", "h", { "a":"b" }]]},
+
+{"description":"Double-quoted attribute value",
+"input":"<h a=\"b\">",
+"output":[["StartTag", "h", { "a":"b" }]]},
+
+{"description":"Unescaped </",
+"input":"</",
+"output":["ParseError", ["Character", "</"]]},
+
+{"description":"Illegal end tag name",
+"input":"</1>",
+"output":["ParseError", ["Comment", "1"]]},
+
+{"description":"Simili processing instruction",
+"input":"<?namespace>",
+"output":["ParseError", ["Comment", "?namespace"]]},
+
+{"description":"A bogus comment stops at >, even if preceeded by two dashes",
+"input":"<?foo-->",
+"output":["ParseError", ["Comment", "?foo--"]]},
+
+{"description":"Unescaped <",
+"input":"foo < bar",
+"output":[["Character", "foo "], "ParseError", ["Character", "< bar"]]},
+
+{"description":"Null Byte Replacement",
+"input":"\u0000",
+"output":["ParseError", ["Character", "\u0000"]]},
+
+{"description":"Comment with dash",
+"input":"<!---x",
+"output":["ParseError", ["Comment", "-x"]]},
+
+{"description":"Entity + newline",
+"input":"\nx\n>\n",
+"output":[["Character","\nx\n>\n"]]},
+
+{"description":"Start tag with no attributes but space before the greater-than sign",
+"input":"<h >",
+"output":[["StartTag", "h", {}]]},
+
+{"description":"Empty attribute followed by uppercase attribute",
+"input":"<h a B=''>",
+"output":[["StartTag", "h", {"a":"", "b":""}]]},
+
+{"description":"Double-quote after attribute name",
+"input":"<h a \">",
+"output":["ParseError", ["StartTag", "h", {"a":"", "\"":""}]]},
+
+{"description":"Single-quote after attribute name",
+"input":"<h a '>",
+"output":["ParseError", ["StartTag", "h", {"a":"", "'":""}]]},
+
+{"description":"Empty end tag with following characters",
+"input":"a</>bc",
+"output":[["Character", "a"], "ParseError", ["Character", "bc"]]},
+
+{"description":"Empty end tag with following tag",
+"input":"a</><b>c",
+"output":[["Character", "a"], "ParseError", ["StartTag", "b", {}], ["Character", "c"]]},
+
+{"description":"Empty end tag with following comment",
+"input":"a</><!--b-->c",
+"output":[["Character", "a"], "ParseError", ["Comment", "b"], ["Character", "c"]]},
+
+{"description":"Empty end tag with following end tag",
+"input":"a</></b>c",
+"output":[["Character", "a"], "ParseError", ["EndTag", "b"], ["Character", "c"]]}
+
+]}
diff --git a/pkgs/html/test/data/tokenizer/test3.test b/pkgs/html/test/data/tokenizer/test3.test
new file mode 100644
index 0000000..58519e8
--- /dev/null
+++ b/pkgs/html/test/data/tokenizer/test3.test
@@ -0,0 +1,6047 @@
+{"tests": [
+
+{"description":"",
+"input":"",
+"output":[]},
+
+{"description":"\\u0009",
+"input":"\u0009",
+"output":[["Character", "\u0009"]]},
+
+{"description":"\\u000A",
+"input":"\u000A",
+"output":[["Character", "\u000A"]]},
+
+{"description":"\\u000B",
+"input":"\u000B",
+"output":["ParseError", ["Character", "\u000B"]]},
+
+{"description":"\\u000C",
+"input":"\u000C",
+"output":[["Character", "\u000C"]]},
+
+{"description":" ",
+"input":" ",
+"output":[["Character", " "]]},
+
+{"description":"!",
+"input":"!",
+"output":[["Character", "!"]]},
+
+{"description":"\"",
+"input":"\"",
+"output":[["Character", "\""]]},
+
+{"description":"%",
+"input":"%",
+"output":[["Character", "%"]]},
+
+{"description":"&",
+"input":"&",
+"output":[["Character", "&"]]},
+
+{"description":"'",
+"input":"'",
+"output":[["Character", "'"]]},
+
+{"description":",",
+"input":",",
+"output":[["Character", ","]]},
+
+{"description":"-",
+"input":"-",
+"output":[["Character", "-"]]},
+
+{"description":".",
+"input":".",
+"output":[["Character", "."]]},
+
+{"description":"/",
+"input":"/",
+"output":[["Character", "/"]]},
+
+{"description":"0",
+"input":"0",
+"output":[["Character", "0"]]},
+
+{"description":"1",
+"input":"1",
+"output":[["Character", "1"]]},
+
+{"description":"9",
+"input":"9",
+"output":[["Character", "9"]]},
+
+{"description":";",
+"input":";",
+"output":[["Character", ";"]]},
+
+{"description":"<",
+"input":"<",
+"output":["ParseError", ["Character", "<"]]},
+
+{"description":"<\\u0000",
+"input":"<\u0000",
+"output":["ParseError", ["Character", "<"], "ParseError", ["Character", "\u0000"]]},
+
+{"description":"<\\u0009",
+"input":"<\u0009",
+"output":["ParseError", ["Character", "<\u0009"]]},
+
+{"description":"<\\u000A",
+"input":"<\u000A",
+"output":["ParseError", ["Character", "<\u000A"]]},
+
+{"description":"<\\u000B",
+"input":"<\u000B",
+"output":["ParseError", "ParseError", ["Character", "<\u000B"]]},
+
+{"description":"<\\u000C",
+"input":"<\u000C",
+"output":["ParseError", ["Character", "<\u000C"]]},
+
+{"description":"< ",
+"input":"< ",
+"output":["ParseError", ["Character", "< "]]},
+
+{"description":"<!",
+"input":"<!",
+"output":["ParseError", ["Comment", ""]]},
+
+{"description":"<!\\u0000",
+"input":"<!\u0000",
+"output":["ParseError", ["Comment", "\uFFFD"]]},
+
+{"description":"<!\\u0009",
+"input":"<!\u0009",
+"output":["ParseError", ["Comment", "\u0009"]]},
+
+{"description":"<!\\u000A",
+"input":"<!\u000A",
+"output":["ParseError", ["Comment", "\u000A"]]},
+
+{"description":"<!\\u000B",
+"input":"<!\u000B",
+"output":["ParseError", "ParseError", ["Comment", "\u000B"]]},
+
+{"description":"<!\\u000C",
+"input":"<!\u000C",
+"output":["ParseError", ["Comment", "\u000C"]]},
+
+{"description":"<! ",
+"input":"<! ",
+"output":["ParseError", ["Comment", " "]]},
+
+{"description":"<!!",
+"input":"<!!",
+"output":["ParseError", ["Comment", "!"]]},
+
+{"description":"<!\"",
+"input":"<!\"",
+"output":["ParseError", ["Comment", "\""]]},
+
+{"description":"<!&",
+"input":"<!&",
+"output":["ParseError", ["Comment", "&"]]},
+
+{"description":"<!'",
+"input":"<!'",
+"output":["ParseError", ["Comment", "'"]]},
+
+{"description":"<!-",
+"input":"<!-",
+"output":["ParseError", ["Comment", "-"]]},
+
+{"description":"<!--",
+"input":"<!--",
+"output":["ParseError", ["Comment", ""]]},
+
+{"description":"<!--\\u0000",
+"input":"<!--\u0000",
+"output":["ParseError", "ParseError", ["Comment", "\uFFFD"]]},
+
+{"description":"<!--\\u0009",
+"input":"<!--\u0009",
+"output":["ParseError", ["Comment", "\u0009"]]},
+
+{"description":"<!--\\u000A",
+"input":"<!--\u000A",
+"output":["ParseError", ["Comment", "\u000A"]]},
+
+{"description":"<!--\\u000B",
+"input":"<!--\u000B",
+"output":["ParseError", "ParseError", ["Comment", "\u000B"]]},
+
+{"description":"<!--\\u000C",
+"input":"<!--\u000C",
+"output":["ParseError", ["Comment", "\u000C"]]},
+
+{"description":"<!-- ",
+"input":"<!-- ",
+"output":["ParseError", ["Comment", " "]]},
+
+{"description":"<!-- \\u0000",
+"input":"<!-- \u0000",
+"output":["ParseError", "ParseError", ["Comment", " \uFFFD"]]},
+
+{"description":"<!-- \\u0009",
+"input":"<!-- \u0009",
+"output":["ParseError", ["Comment", " \u0009"]]},
+
+{"description":"<!-- \\u000A",
+"input":"<!-- \u000A",
+"output":["ParseError", ["Comment", " \u000A"]]},
+
+{"description":"<!-- \\u000B",
+"input":"<!-- \u000B",
+"output":["ParseError", "ParseError", ["Comment", " \u000B"]]},
+
+{"description":"<!-- \\u000C",
+"input":"<!-- \u000C",
+"output":["ParseError", ["Comment", " \u000C"]]},
+
+{"description":"<!-- ",
+"input":"<!-- ",
+"output":["ParseError", ["Comment", " "]]},
+
+{"description":"<!-- !",
+"input":"<!-- !",
+"output":["ParseError", ["Comment", " !"]]},
+
+{"description":"<!-- \"",
+"input":"<!-- \"",
+"output":["ParseError", ["Comment", " \""]]},
+
+{"description":"<!-- &",
+"input":"<!-- &",
+"output":["ParseError", ["Comment", " &"]]},
+
+{"description":"<!-- '",
+"input":"<!-- '",
+"output":["ParseError", ["Comment", " '"]]},
+
+{"description":"<!-- ,",
+"input":"<!-- ,",
+"output":["ParseError", ["Comment", " ,"]]},
+
+{"description":"<!-- -",
+"input":"<!-- -",
+"output":["ParseError", ["Comment", " "]]},
+
+{"description":"<!-- -\\u0000",
+"input":"<!-- -\u0000",
+"output":["ParseError", "ParseError", ["Comment", " -\uFFFD"]]},
+
+{"description":"<!-- -\\u0009",
+"input":"<!-- -\u0009",
+"output":["ParseError", ["Comment", " -\u0009"]]},
+
+{"description":"<!-- -\\u000A",
+"input":"<!-- -\u000A",
+"output":["ParseError", ["Comment", " -\u000A"]]},
+
+{"description":"<!-- -\\u000B",
+"input":"<!-- -\u000B",
+"output":["ParseError", "ParseError", ["Comment", " -\u000B"]]},
+
+{"description":"<!-- -\\u000C",
+"input":"<!-- -\u000C",
+"output":["ParseError", ["Comment", " -\u000C"]]},
+
+{"description":"<!-- - ",
+"input":"<!-- - ",
+"output":["ParseError", ["Comment", " - "]]},
+
+{"description":"<!-- -!",
+"input":"<!-- -!",
+"output":["ParseError", ["Comment", " -!"]]},
+
+{"description":"<!-- -\"",
+"input":"<!-- -\"",
+"output":["ParseError", ["Comment", " -\""]]},
+
+{"description":"<!-- -&",
+"input":"<!-- -&",
+"output":["ParseError", ["Comment", " -&"]]},
+
+{"description":"<!-- -'",
+"input":"<!-- -'",
+"output":["ParseError", ["Comment", " -'"]]},
+
+{"description":"<!-- -,",
+"input":"<!-- -,",
+"output":["ParseError", ["Comment", " -,"]]},
+
+{"description":"<!-- --",
+"input":"<!-- --",
+"output":["ParseError", ["Comment", " "]]},
+
+{"description":"<!-- -.",
+"input":"<!-- -.",
+"output":["ParseError", ["Comment", " -."]]},
+
+{"description":"<!-- -/",
+"input":"<!-- -/",
+"output":["ParseError", ["Comment", " -/"]]},
+
+{"description":"<!-- -0",
+"input":"<!-- -0",
+"output":["ParseError", ["Comment", " -0"]]},
+
+{"description":"<!-- -1",
+"input":"<!-- -1",
+"output":["ParseError", ["Comment", " -1"]]},
+
+{"description":"<!-- -9",
+"input":"<!-- -9",
+"output":["ParseError", ["Comment", " -9"]]},
+
+{"description":"<!-- -<",
+"input":"<!-- -<",
+"output":["ParseError", ["Comment", " -<"]]},
+
+{"description":"<!-- -=",
+"input":"<!-- -=",
+"output":["ParseError", ["Comment", " -="]]},
+
+{"description":"<!-- ->",
+"input":"<!-- ->",
+"output":["ParseError", ["Comment", " ->"]]},
+
+{"description":"<!-- -?",
+"input":"<!-- -?",
+"output":["ParseError", ["Comment", " -?"]]},
+
+{"description":"<!-- -@",
+"input":"<!-- -@",
+"output":["ParseError", ["Comment", " -@"]]},
+
+{"description":"<!-- -A",
+"input":"<!-- -A",
+"output":["ParseError", ["Comment", " -A"]]},
+
+{"description":"<!-- -B",
+"input":"<!-- -B",
+"output":["ParseError", ["Comment", " -B"]]},
+
+{"description":"<!-- -Y",
+"input":"<!-- -Y",
+"output":["ParseError", ["Comment", " -Y"]]},
+
+{"description":"<!-- -Z",
+"input":"<!-- -Z",
+"output":["ParseError", ["Comment", " -Z"]]},
+
+{"description":"<!-- -`",
+"input":"<!-- -`",
+"output":["ParseError", ["Comment", " -`"]]},
+
+{"description":"<!-- -a",
+"input":"<!-- -a",
+"output":["ParseError", ["Comment", " -a"]]},
+
+{"description":"<!-- -b",
+"input":"<!-- -b",
+"output":["ParseError", ["Comment", " -b"]]},
+
+{"description":"<!-- -y",
+"input":"<!-- -y",
+"output":["ParseError", ["Comment", " -y"]]},
+
+{"description":"<!-- -z",
+"input":"<!-- -z",
+"output":["ParseError", ["Comment", " -z"]]},
+
+{"description":"<!-- -{",
+"input":"<!-- -{",
+"output":["ParseError", ["Comment", " -{"]]},
+
+{"description":"<!-- -\\uDBC0\\uDC00",
+"input":"<!-- -\uDBC0\uDC00",
+"output":["ParseError", ["Comment", " -\uDBC0\uDC00"]]},
+
+{"description":"<!-- .",
+"input":"<!-- .",
+"output":["ParseError", ["Comment", " ."]]},
+
+{"description":"<!-- /",
+"input":"<!-- /",
+"output":["ParseError", ["Comment", " /"]]},
+
+{"description":"<!-- 0",
+"input":"<!-- 0",
+"output":["ParseError", ["Comment", " 0"]]},
+
+{"description":"<!-- 1",
+"input":"<!-- 1",
+"output":["ParseError", ["Comment", " 1"]]},
+
+{"description":"<!-- 9",
+"input":"<!-- 9",
+"output":["ParseError", ["Comment", " 9"]]},
+
+{"description":"<!-- <",
+"input":"<!-- <",
+"output":["ParseError", ["Comment", " <"]]},
+
+{"description":"<!-- =",
+"input":"<!-- =",
+"output":["ParseError", ["Comment", " ="]]},
+
+{"description":"<!-- >",
+"input":"<!-- >",
+"output":["ParseError", ["Comment", " >"]]},
+
+{"description":"<!-- ?",
+"input":"<!-- ?",
+"output":["ParseError", ["Comment", " ?"]]},
+
+{"description":"<!-- @",
+"input":"<!-- @",
+"output":["ParseError", ["Comment", " @"]]},
+
+{"description":"<!-- A",
+"input":"<!-- A",
+"output":["ParseError", ["Comment", " A"]]},
+
+{"description":"<!-- B",
+"input":"<!-- B",
+"output":["ParseError", ["Comment", " B"]]},
+
+{"description":"<!-- Y",
+"input":"<!-- Y",
+"output":["ParseError", ["Comment", " Y"]]},
+
+{"description":"<!-- Z",
+"input":"<!-- Z",
+"output":["ParseError", ["Comment", " Z"]]},
+
+{"description":"<!-- `",
+"input":"<!-- `",
+"output":["ParseError", ["Comment", " `"]]},
+
+{"description":"<!-- a",
+"input":"<!-- a",
+"output":["ParseError", ["Comment", " a"]]},
+
+{"description":"<!-- b",
+"input":"<!-- b",
+"output":["ParseError", ["Comment", " b"]]},
+
+{"description":"<!-- y",
+"input":"<!-- y",
+"output":["ParseError", ["Comment", " y"]]},
+
+{"description":"<!-- z",
+"input":"<!-- z",
+"output":["ParseError", ["Comment", " z"]]},
+
+{"description":"<!-- {",
+"input":"<!-- {",
+"output":["ParseError", ["Comment", " {"]]},
+
+{"description":"<!-- \\uDBC0\\uDC00",
+"input":"<!-- \uDBC0\uDC00",
+"output":["ParseError", ["Comment", " \uDBC0\uDC00"]]},
+
+{"description":"<!--!",
+"input":"<!--!",
+"output":["ParseError", ["Comment", "!"]]},
+
+{"description":"<!--\"",
+"input":"<!--\"",
+"output":["ParseError", ["Comment", "\""]]},
+
+{"description":"<!--&",
+"input":"<!--&",
+"output":["ParseError", ["Comment", "&"]]},
+
+{"description":"<!--'",
+"input":"<!--'",
+"output":["ParseError", ["Comment", "'"]]},
+
+{"description":"<!--,",
+"input":"<!--,",
+"output":["ParseError", ["Comment", ","]]},
+
+{"description":"<!---",
+"input":"<!---",
+"output":["ParseError", ["Comment", ""]]},
+
+{"description":"<!---\\u0000",
+"input":"<!---\u0000",
+"output":["ParseError", "ParseError", ["Comment", "-\uFFFD"]]},
+
+{"description":"<!---\\u0009",
+"input":"<!---\u0009",
+"output":["ParseError", ["Comment", "-\u0009"]]},
+
+{"description":"<!---\\u000A",
+"input":"<!---\u000A",
+"output":["ParseError", ["Comment", "-\u000A"]]},
+
+{"description":"<!---\\u000B",
+"input":"<!---\u000B",
+"output":["ParseError", "ParseError", ["Comment", "-\u000B"]]},
+
+{"description":"<!---\\u000C",
+"input":"<!---\u000C",
+"output":["ParseError", ["Comment", "-\u000C"]]},
+
+{"description":"<!--- ",
+"input":"<!--- ",
+"output":["ParseError", ["Comment", "- "]]},
+
+{"description":"<!---!",
+"input":"<!---!",
+"output":["ParseError", ["Comment", "-!"]]},
+
+{"description":"<!---\"",
+"input":"<!---\"",
+"output":["ParseError", ["Comment", "-\""]]},
+
+{"description":"<!---&",
+"input":"<!---&",
+"output":["ParseError", ["Comment", "-&"]]},
+
+{"description":"<!---'",
+"input":"<!---'",
+"output":["ParseError", ["Comment", "-'"]]},
+
+{"description":"<!---,",
+"input":"<!---,",
+"output":["ParseError", ["Comment", "-,"]]},
+
+{"description":"<!----",
+"input":"<!----",
+"output":["ParseError", ["Comment", ""]]},
+
+{"description":"<!----\\u0000",
+"input":"<!----\u0000",
+"output":["ParseError", "ParseError", ["Comment", "--\uFFFD"]]},
+
+{"description":"<!----\\u0009",
+"input":"<!----\u0009",
+"output":["ParseError", "ParseError", ["Comment", "--\u0009"]]},
+
+{"description":"<!----\\u000A",
+"input":"<!----\u000A",
+"output":["ParseError", "ParseError", ["Comment", "--\u000A"]]},
+
+{"description":"<!----\\u000B",
+"input":"<!----\u000B",
+"output":["ParseError", "ParseError", "ParseError", ["Comment", "--\u000B"]]},
+
+{"description":"<!----\\u000C",
+"input":"<!----\u000C",
+"output":["ParseError", "ParseError", ["Comment", "--\u000C"]]},
+
+{"description":"<!---- ",
+"input":"<!---- ",
+"output":["ParseError", "ParseError", ["Comment", "-- "]]},
+
+{"description":"<!---- -",
+"input":"<!---- -",
+"output":["ParseError", "ParseError", ["Comment", "-- "]]},
+
+{"description":"<!---- --",
+"input":"<!---- --",
+"output":["ParseError", "ParseError", ["Comment", "-- "]]},
+
+{"description":"<!---- -->",
+"input":"<!---- -->",
+"output":["ParseError", ["Comment", "-- "]]},
+
+{"description":"<!---- -->",
+"input":"<!---- -->",
+"output":["ParseError", ["Comment", "-- "]]},
+
+{"description":"<!---- a-->",
+"input":"<!---- a-->",
+"output":["ParseError", ["Comment", "-- a"]]},
+
+{"description":"<!----!",
+"input":"<!----!",
+"output":["ParseError", "ParseError", ["Comment", ""]]},
+
+{"description":"<!----!>",
+"input":"<!----!>",
+"output":["ParseError", ["Comment", ""]]},
+
+{"description":"<!----!a",
+"input":"<!----!a",
+"output":["ParseError", "ParseError", ["Comment", "--!a"]]},
+
+{"description":"<!----!a-",
+"input":"<!----!a-",
+"output":["ParseError", "ParseError", ["Comment", "--!a"]]},
+
+{"description":"<!----!a--",
+"input":"<!----!a--",
+"output":["ParseError", "ParseError", ["Comment", "--!a"]]},
+
+{"description":"<!----!a-->",
+"input":"<!----!a-->",
+"output":["ParseError", ["Comment", "--!a"]]},
+
+{"description":"<!----!-",
+"input":"<!----!-",
+"output":["ParseError", "ParseError", ["Comment", "--!"]]},
+
+{"description":"<!----!--",
+"input":"<!----!--",
+"output":["ParseError", "ParseError", ["Comment", "--!"]]},
+
+{"description":"<!----!-->",
+"input":"<!----!-->",
+"output":["ParseError", ["Comment", "--!"]]},
+
+{"description":"<!----\"",
+"input":"<!----\"",
+"output":["ParseError", "ParseError", ["Comment", "--\""]]},
+
+{"description":"<!----&",
+"input":"<!----&",
+"output":["ParseError", "ParseError", ["Comment", "--&"]]},
+
+{"description":"<!----'",
+"input":"<!----'",
+"output":["ParseError", "ParseError", ["Comment", "--'"]]},
+
+{"description":"<!----,",
+"input":"<!----,",
+"output":["ParseError", "ParseError", ["Comment", "--,"]]},
+
+{"description":"<!-----",
+"input":"<!-----",
+"output":["ParseError", "ParseError", ["Comment", "-"]]},
+
+{"description":"<!----.",
+"input":"<!----.",
+"output":["ParseError", "ParseError", ["Comment", "--."]]},
+
+{"description":"<!----/",
+"input":"<!----/",
+"output":["ParseError", "ParseError", ["Comment", "--/"]]},
+
+{"description":"<!----0",
+"input":"<!----0",
+"output":["ParseError", "ParseError", ["Comment", "--0"]]},
+
+{"description":"<!----1",
+"input":"<!----1",
+"output":["ParseError", "ParseError", ["Comment", "--1"]]},
+
+{"description":"<!----9",
+"input":"<!----9",
+"output":["ParseError", "ParseError", ["Comment", "--9"]]},
+
+{"description":"<!----<",
+"input":"<!----<",
+"output":["ParseError", "ParseError", ["Comment", "--<"]]},
+
+{"description":"<!----=",
+"input":"<!----=",
+"output":["ParseError", "ParseError", ["Comment", "--="]]},
+
+{"description":"<!---->",
+"input":"<!---->",
+"output":[["Comment", ""]]},
+
+{"description":"<!----?",
+"input":"<!----?",
+"output":["ParseError", "ParseError", ["Comment", "--?"]]},
+
+{"description":"<!----@",
+"input":"<!----@",
+"output":["ParseError", "ParseError", ["Comment", "--@"]]},
+
+{"description":"<!----A",
+"input":"<!----A",
+"output":["ParseError", "ParseError", ["Comment", "--A"]]},
+
+{"description":"<!----B",
+"input":"<!----B",
+"output":["ParseError", "ParseError", ["Comment", "--B"]]},
+
+{"description":"<!----Y",
+"input":"<!----Y",
+"output":["ParseError", "ParseError", ["Comment", "--Y"]]},
+
+{"description":"<!----Z",
+"input":"<!----Z",
+"output":["ParseError", "ParseError", ["Comment", "--Z"]]},
+
+{"description":"<!----`",
+"input":"<!----`",
+"output":["ParseError", "ParseError", ["Comment", "--`"]]},
+
+{"description":"<!----a",
+"input":"<!----a",
+"output":["ParseError", "ParseError", ["Comment", "--a"]]},
+
+{"description":"<!----b",
+"input":"<!----b",
+"output":["ParseError", "ParseError", ["Comment", "--b"]]},
+
+{"description":"<!----y",
+"input":"<!----y",
+"output":["ParseError", "ParseError", ["Comment", "--y"]]},
+
+{"description":"<!----z",
+"input":"<!----z",
+"output":["ParseError", "ParseError", ["Comment", "--z"]]},
+
+{"description":"<!----{",
+"input":"<!----{",
+"output":["ParseError", "ParseError", ["Comment", "--{"]]},
+
+{"description":"<!----\\uDBC0\\uDC00",
+"input":"<!----\uDBC0\uDC00",
+"output":["ParseError", "ParseError", ["Comment", "--\uDBC0\uDC00"]]},
+
+{"description":"<!---.",
+"input":"<!---.",
+"output":["ParseError", ["Comment", "-."]]},
+
+{"description":"<!---/",
+"input":"<!---/",
+"output":["ParseError", ["Comment", "-/"]]},
+
+{"description":"<!---0",
+"input":"<!---0",
+"output":["ParseError", ["Comment", "-0"]]},
+
+{"description":"<!---1",
+"input":"<!---1",
+"output":["ParseError", ["Comment", "-1"]]},
+
+{"description":"<!---9",
+"input":"<!---9",
+"output":["ParseError", ["Comment", "-9"]]},
+
+{"description":"<!---<",
+"input":"<!---<",
+"output":["ParseError", ["Comment", "-<"]]},
+
+{"description":"<!---=",
+"input":"<!---=",
+"output":["ParseError", ["Comment", "-="]]},
+
+{"description":"<!--->",
+"input":"<!--->",
+"output":["ParseError", ["Comment", ""]]},
+
+{"description":"<!---?",
+"input":"<!---?",
+"output":["ParseError", ["Comment", "-?"]]},
+
+{"description":"<!---@",
+"input":"<!---@",
+"output":["ParseError", ["Comment", "-@"]]},
+
+{"description":"<!---A",
+"input":"<!---A",
+"output":["ParseError", ["Comment", "-A"]]},
+
+{"description":"<!---B",
+"input":"<!---B",
+"output":["ParseError", ["Comment", "-B"]]},
+
+{"description":"<!---Y",
+"input":"<!---Y",
+"output":["ParseError", ["Comment", "-Y"]]},
+
+{"description":"<!---Z",
+"input":"<!---Z",
+"output":["ParseError", ["Comment", "-Z"]]},
+
+{"description":"<!---`",
+"input":"<!---`",
+"output":["ParseError", ["Comment", "-`"]]},
+
+{"description":"<!---a",
+"input":"<!---a",
+"output":["ParseError", ["Comment", "-a"]]},
+
+{"description":"<!---b",
+"input":"<!---b",
+"output":["ParseError", ["Comment", "-b"]]},
+
+{"description":"<!---y",
+"input":"<!---y",
+"output":["ParseError", ["Comment", "-y"]]},
+
+{"description":"<!---z",
+"input":"<!---z",
+"output":["ParseError", ["Comment", "-z"]]},
+
+{"description":"<!---{",
+"input":"<!---{",
+"output":["ParseError", ["Comment", "-{"]]},
+
+{"description":"<!---\\uDBC0\\uDC00",
+"input":"<!---\uDBC0\uDC00",
+"output":["ParseError", ["Comment", "-\uDBC0\uDC00"]]},
+
+{"description":"<!--.",
+"input":"<!--.",
+"output":["ParseError", ["Comment", "."]]},
+
+{"description":"<!--/",
+"input":"<!--/",
+"output":["ParseError", ["Comment", "/"]]},
+
+{"description":"<!--0",
+"input":"<!--0",
+"output":["ParseError", ["Comment", "0"]]},
+
+{"description":"<!--1",
+"input":"<!--1",
+"output":["ParseError", ["Comment", "1"]]},
+
+{"description":"<!--9",
+"input":"<!--9",
+"output":["ParseError", ["Comment", "9"]]},
+
+{"description":"<!--<",
+"input":"<!--<",
+"output":["ParseError", ["Comment", "<"]]},
+
+{"description":"<!--=",
+"input":"<!--=",
+"output":["ParseError", ["Comment", "="]]},
+
+{"description":"<!-->",
+"input":"<!-->",
+"output":["ParseError", ["Comment", ""]]},
+
+{"description":"<!--?",
+"input":"<!--?",
+"output":["ParseError", ["Comment", "?"]]},
+
+{"description":"<!--@",
+"input":"<!--@",
+"output":["ParseError", ["Comment", "@"]]},
+
+{"description":"<!--A",
+"input":"<!--A",
+"output":["ParseError", ["Comment", "A"]]},
+
+{"description":"<!--B",
+"input":"<!--B",
+"output":["ParseError", ["Comment", "B"]]},
+
+{"description":"<!--Y",
+"input":"<!--Y",
+"output":["ParseError", ["Comment", "Y"]]},
+
+{"description":"<!--Z",
+"input":"<!--Z",
+"output":["ParseError", ["Comment", "Z"]]},
+
+{"description":"<!--`",
+"input":"<!--`",
+"output":["ParseError", ["Comment", "`"]]},
+
+{"description":"<!--a",
+"input":"<!--a",
+"output":["ParseError", ["Comment", "a"]]},
+
+{"description":"<!--b",
+"input":"<!--b",
+"output":["ParseError", ["Comment", "b"]]},
+
+{"description":"<!--y",
+"input":"<!--y",
+"output":["ParseError", ["Comment", "y"]]},
+
+{"description":"<!--z",
+"input":"<!--z",
+"output":["ParseError", ["Comment", "z"]]},
+
+{"description":"<!--{",
+"input":"<!--{",
+"output":["ParseError", ["Comment", "{"]]},
+
+{"description":"<!--\\uDBC0\\uDC00",
+"input":"<!--\uDBC0\uDC00",
+"output":["ParseError", ["Comment", "\uDBC0\uDC00"]]},
+
+{"description":"<!/",
+"input":"<!/",
+"output":["ParseError", ["Comment", "/"]]},
+
+{"description":"<!0",
+"input":"<!0",
+"output":["ParseError", ["Comment", "0"]]},
+
+{"description":"<!1",
+"input":"<!1",
+"output":["ParseError", ["Comment", "1"]]},
+
+{"description":"<!9",
+"input":"<!9",
+"output":["ParseError", ["Comment", "9"]]},
+
+{"description":"<!<",
+"input":"<!<",
+"output":["ParseError", ["Comment", "<"]]},
+
+{"description":"<!=",
+"input":"<!=",
+"output":["ParseError", ["Comment", "="]]},
+
+{"description":"<!>",
+"input":"<!>",
+"output":["ParseError", ["Comment", ""]]},
+
+{"description":"<!?",
+"input":"<!?",
+"output":["ParseError", ["Comment", "?"]]},
+
+{"description":"<!@",
+"input":"<!@",
+"output":["ParseError", ["Comment", "@"]]},
+
+{"description":"<!A",
+"input":"<!A",
+"output":["ParseError", ["Comment", "A"]]},
+
+{"description":"<!B",
+"input":"<!B",
+"output":["ParseError", ["Comment", "B"]]},
+
+{"description":"<!DOCTYPE",
+"input":"<!DOCTYPE",
+"output":["ParseError", ["DOCTYPE", "", null, null, false]]},
+
+{"description":"<!DOCTYPE\\u0000",
+"input":"<!DOCTYPE\u0000",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "\uFFFD", null, null, false]]},
+
+{"description":"<!DOCTYPE\\u0008",
+"input":"<!DOCTYPE\u0008",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "\u0008", null, null, false]]},
+
+{"description":"<!DOCTYPE\\u0009",
+"input":"<!DOCTYPE\u0009",
+"output":["ParseError", ["DOCTYPE", "", null, null, false]]},
+
+{"description":"<!DOCTYPE\\u000A",
+"input":"<!DOCTYPE\u000A",
+"output":["ParseError", ["DOCTYPE", "", null, null, false]]},
+
+{"description":"<!DOCTYPE\\u000B",
+"input":"<!DOCTYPE\u000B",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "\u000B", null, null, false]]},
+
+{"description":"<!DOCTYPE\\u000C",
+"input":"<!DOCTYPE\u000C",
+"output":["ParseError", ["DOCTYPE", "", null, null, false]]},
+
+{"description":"<!DOCTYPE\\u000D",
+"input":"<!DOCTYPE\u000D",
+"output":["ParseError", ["DOCTYPE", "", null, null, false]]},
+
+{"description":"<!DOCTYPE\\u001F",
+"input":"<!DOCTYPE\u001F",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "\u001F", null, null, false]]},
+
+{"description":"<!DOCTYPE ",
+"input":"<!DOCTYPE ",
+"output":["ParseError", ["DOCTYPE", "", null, null, false]]},
+
+{"description":"<!DOCTYPE \\u0000",
+"input":"<!DOCTYPE \u0000",
+"output":["ParseError", "ParseError", ["DOCTYPE", "\uFFFD", null, null, false]]},
+
+{"description":"<!DOCTYPE \\u0008",
+"input":"<!DOCTYPE \u0008",
+"output":["ParseError", "ParseError", ["DOCTYPE", "\u0008", null, null, false]]},
+
+{"description":"<!DOCTYPE \\u0009",
+"input":"<!DOCTYPE \u0009",
+"output":["ParseError", ["DOCTYPE", "", null, null, false]]},
+
+{"description":"<!DOCTYPE \\u000A",
+"input":"<!DOCTYPE \u000A",
+"output":["ParseError", ["DOCTYPE", "", null, null, false]]},
+
+{"description":"<!DOCTYPE \\u000B",
+"input":"<!DOCTYPE \u000B",
+"output":["ParseError", "ParseError", ["DOCTYPE", "\u000B", null, null, false]]},
+
+{"description":"<!DOCTYPE \\u000C",
+"input":"<!DOCTYPE \u000C",
+"output":["ParseError", ["DOCTYPE", "", null, null, false]]},
+
+{"description":"<!DOCTYPE \\u000D",
+"input":"<!DOCTYPE \u000D",
+"output":["ParseError", ["DOCTYPE", "", null, null, false]]},
+
+{"description":"<!DOCTYPE \\u001F",
+"input":"<!DOCTYPE \u001F",
+"output":["ParseError", "ParseError", ["DOCTYPE", "\u001F", null, null, false]]},
+
+{"description":"<!DOCTYPE ",
+"input":"<!DOCTYPE ",
+"output":["ParseError", ["DOCTYPE", "", null, null, false]]},
+
+{"description":"<!DOCTYPE !",
+"input":"<!DOCTYPE !",
+"output":["ParseError", ["DOCTYPE", "!", null, null, false]]},
+
+{"description":"<!DOCTYPE \"",
+"input":"<!DOCTYPE \"",
+"output":["ParseError", ["DOCTYPE", "\"", null, null, false]]},
+
+{"description":"<!DOCTYPE &",
+"input":"<!DOCTYPE &",
+"output":["ParseError", ["DOCTYPE", "&", null, null, false]]},
+
+{"description":"<!DOCTYPE '",
+"input":"<!DOCTYPE '",
+"output":["ParseError", ["DOCTYPE", "'", null, null, false]]},
+
+{"description":"<!DOCTYPE -",
+"input":"<!DOCTYPE -",
+"output":["ParseError", ["DOCTYPE", "-", null, null, false]]},
+
+{"description":"<!DOCTYPE /",
+"input":"<!DOCTYPE /",
+"output":["ParseError", ["DOCTYPE", "/", null, null, false]]},
+
+{"description":"<!DOCTYPE 0",
+"input":"<!DOCTYPE 0",
+"output":["ParseError", ["DOCTYPE", "0", null, null, false]]},
+
+{"description":"<!DOCTYPE 1",
+"input":"<!DOCTYPE 1",
+"output":["ParseError", ["DOCTYPE", "1", null, null, false]]},
+
+{"description":"<!DOCTYPE 9",
+"input":"<!DOCTYPE 9",
+"output":["ParseError", ["DOCTYPE", "9", null, null, false]]},
+
+{"description":"<!DOCTYPE <",
+"input":"<!DOCTYPE <",
+"output":["ParseError", ["DOCTYPE", "<", null, null, false]]},
+
+{"description":"<!DOCTYPE =",
+"input":"<!DOCTYPE =",
+"output":["ParseError", ["DOCTYPE", "=", null, null, false]]},
+
+{"description":"<!DOCTYPE >",
+"input":"<!DOCTYPE >",
+"output":["ParseError", ["DOCTYPE", "", null, null, false]]},
+
+{"description":"<!DOCTYPE ?",
+"input":"<!DOCTYPE ?",
+"output":["ParseError", ["DOCTYPE", "?", null, null, false]]},
+
+{"description":"<!DOCTYPE @",
+"input":"<!DOCTYPE @",
+"output":["ParseError", ["DOCTYPE", "@", null, null, false]]},
+
+{"description":"<!DOCTYPE A",
+"input":"<!DOCTYPE A",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE B",
+"input":"<!DOCTYPE B",
+"output":["ParseError", ["DOCTYPE", "b", null, null, false]]},
+
+{"description":"<!DOCTYPE Y",
+"input":"<!DOCTYPE Y",
+"output":["ParseError", ["DOCTYPE", "y", null, null, false]]},
+
+{"description":"<!DOCTYPE Z",
+"input":"<!DOCTYPE Z",
+"output":["ParseError", ["DOCTYPE", "z", null, null, false]]},
+
+{"description":"<!DOCTYPE [",
+"input":"<!DOCTYPE [",
+"output":["ParseError", ["DOCTYPE", "[", null, null, false]]},
+
+{"description":"<!DOCTYPE `",
+"input":"<!DOCTYPE `",
+"output":["ParseError", ["DOCTYPE", "`", null, null, false]]},
+
+{"description":"<!DOCTYPE a",
+"input":"<!DOCTYPE a",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a\\u0000",
+"input":"<!DOCTYPE a\u0000",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a\uFFFD", null, null, false]]},
+
+{"description":"<!DOCTYPE a\\u0008",
+"input":"<!DOCTYPE a\u0008",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a\u0008", null, null, false]]},
+
+{"description":"<!DOCTYPE a\\u0009",
+"input":"<!DOCTYPE a\u0009",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a\\u000A",
+"input":"<!DOCTYPE a\u000A",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a\\u000B",
+"input":"<!DOCTYPE a\u000B",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a\u000B", null, null, false]]},
+
+{"description":"<!DOCTYPE a\\u000C",
+"input":"<!DOCTYPE a\u000C",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a\\u000D",
+"input":"<!DOCTYPE a\u000D",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a\\u001F",
+"input":"<!DOCTYPE a\u001F",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a\u001F", null, null, false]]},
+
+{"description":"<!DOCTYPE a ",
+"input":"<!DOCTYPE a ",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a \\u0000",
+"input":"<!DOCTYPE a \u0000",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a \\u0008",
+"input":"<!DOCTYPE a \u0008",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a \\u0009",
+"input":"<!DOCTYPE a \u0009",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a \\u000A",
+"input":"<!DOCTYPE a \u000A",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a \\u000B",
+"input":"<!DOCTYPE a \u000B",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a \\u000C",
+"input":"<!DOCTYPE a \u000C",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a \\u000D",
+"input":"<!DOCTYPE a \u000D",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a \\u001F",
+"input":"<!DOCTYPE a \u001F",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a ",
+"input":"<!DOCTYPE a ",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a !",
+"input":"<!DOCTYPE a !",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a \"",
+"input":"<!DOCTYPE a \"",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a &",
+"input":"<!DOCTYPE a &",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a '",
+"input":"<!DOCTYPE a '",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a -",
+"input":"<!DOCTYPE a -",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a /",
+"input":"<!DOCTYPE a /",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a 0",
+"input":"<!DOCTYPE a 0",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a 1",
+"input":"<!DOCTYPE a 1",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a 9",
+"input":"<!DOCTYPE a 9",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a <",
+"input":"<!DOCTYPE a <",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a =",
+"input":"<!DOCTYPE a =",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a >",
+"input":"<!DOCTYPE a >",
+"output":[["DOCTYPE", "a", null, null, true]]},
+
+{"description":"<!DOCTYPE a ?",
+"input":"<!DOCTYPE a ?",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a @",
+"input":"<!DOCTYPE a @",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a A",
+"input":"<!DOCTYPE a A",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a B",
+"input":"<!DOCTYPE a B",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC",
+"input":"<!DOCTYPE a PUBLIC",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\\u0000",
+"input":"<!DOCTYPE a PUBLIC\u0000",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\\u0008",
+"input":"<!DOCTYPE a PUBLIC\u0008",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\\u0009",
+"input":"<!DOCTYPE a PUBLIC\u0009",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\\u000A",
+"input":"<!DOCTYPE a PUBLIC\u000A",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\\u000B",
+"input":"<!DOCTYPE a PUBLIC\u000B",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\\u000C",
+"input":"<!DOCTYPE a PUBLIC\u000C",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\\u000D",
+"input":"<!DOCTYPE a PUBLIC\u000D",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\\u001F",
+"input":"<!DOCTYPE a PUBLIC\u001F",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC ",
+"input":"<!DOCTYPE a PUBLIC ",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC!",
+"input":"<!DOCTYPE a PUBLIC!",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\"",
+"input":"<!DOCTYPE a PUBLIC\"",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\"\\u0000",
+"input":"<!DOCTYPE a PUBLIC\"\u0000",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "\uFFFD", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\"\\u0009",
+"input":"<!DOCTYPE a PUBLIC\"\u0009",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "\u0009", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\"\\u000A",
+"input":"<!DOCTYPE a PUBLIC\"\u000A",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "\u000A", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\"\\u000B",
+"input":"<!DOCTYPE a PUBLIC\"\u000B",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "\u000B", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\"\\u000C",
+"input":"<!DOCTYPE a PUBLIC\"\u000C",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "\u000C", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\" ",
+"input":"<!DOCTYPE a PUBLIC\" ",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", " ", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\"!",
+"input":"<!DOCTYPE a PUBLIC\"!",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "!", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\"\"",
+"input":"<!DOCTYPE a PUBLIC\"\"",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\"#",
+"input":"<!DOCTYPE a PUBLIC\"#",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "#", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\"&",
+"input":"<!DOCTYPE a PUBLIC\"&",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "&", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\"'",
+"input":"<!DOCTYPE a PUBLIC\"'",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "'", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\"-",
+"input":"<!DOCTYPE a PUBLIC\"-",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "-", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\"/",
+"input":"<!DOCTYPE a PUBLIC\"/",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "/", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\"0",
+"input":"<!DOCTYPE a PUBLIC\"0",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "0", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\"1",
+"input":"<!DOCTYPE a PUBLIC\"1",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "1", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\"9",
+"input":"<!DOCTYPE a PUBLIC\"9",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "9", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\"<",
+"input":"<!DOCTYPE a PUBLIC\"<",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "<", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\"=",
+"input":"<!DOCTYPE a PUBLIC\"=",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "=", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\">",
+"input":"<!DOCTYPE a PUBLIC\">",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\"?",
+"input":"<!DOCTYPE a PUBLIC\"?",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "?", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\"@",
+"input":"<!DOCTYPE a PUBLIC\"@",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "@", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\"A",
+"input":"<!DOCTYPE a PUBLIC\"A",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "A", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\"B",
+"input":"<!DOCTYPE a PUBLIC\"B",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "B", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\"Y",
+"input":"<!DOCTYPE a PUBLIC\"Y",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "Y", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\"Z",
+"input":"<!DOCTYPE a PUBLIC\"Z",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "Z", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\"`",
+"input":"<!DOCTYPE a PUBLIC\"`",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "`", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\"a",
+"input":"<!DOCTYPE a PUBLIC\"a",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "a", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\"b",
+"input":"<!DOCTYPE a PUBLIC\"b",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "b", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\"y",
+"input":"<!DOCTYPE a PUBLIC\"y",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "y", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\"z",
+"input":"<!DOCTYPE a PUBLIC\"z",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "z", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\"{",
+"input":"<!DOCTYPE a PUBLIC\"{",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "{", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\"\\uDBC0\\uDC00",
+"input":"<!DOCTYPE a PUBLIC\"\uDBC0\uDC00",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "\uDBC0\uDC00", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC#",
+"input":"<!DOCTYPE a PUBLIC#",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC&",
+"input":"<!DOCTYPE a PUBLIC&",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'",
+"input":"<!DOCTYPE a PUBLIC'",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'\\u0000",
+"input":"<!DOCTYPE a PUBLIC'\u0000",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "\uFFFD", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'\\u0009",
+"input":"<!DOCTYPE a PUBLIC'\u0009",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "\u0009", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'\\u000A",
+"input":"<!DOCTYPE a PUBLIC'\u000A",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "\u000A", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'\\u000B",
+"input":"<!DOCTYPE a PUBLIC'\u000B",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "\u000B", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'\\u000C",
+"input":"<!DOCTYPE a PUBLIC'\u000C",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "\u000C", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC' ",
+"input":"<!DOCTYPE a PUBLIC' ",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", " ", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'!",
+"input":"<!DOCTYPE a PUBLIC'!",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "!", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'\"",
+"input":"<!DOCTYPE a PUBLIC'\"",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "\"", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'&",
+"input":"<!DOCTYPE a PUBLIC'&",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "&", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''",
+"input":"<!DOCTYPE a PUBLIC''",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''\\u0000",
+"input":"<!DOCTYPE a PUBLIC''\u0000",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''\\u0008",
+"input":"<!DOCTYPE a PUBLIC''\u0008",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''\\u0009",
+"input":"<!DOCTYPE a PUBLIC''\u0009",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''\\u000A",
+"input":"<!DOCTYPE a PUBLIC''\u000A",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''\\u000B",
+"input":"<!DOCTYPE a PUBLIC''\u000B",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''\\u000C",
+"input":"<!DOCTYPE a PUBLIC''\u000C",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''\\u000D",
+"input":"<!DOCTYPE a PUBLIC''\u000D",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''\\u001F",
+"input":"<!DOCTYPE a PUBLIC''\u001F",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'' ",
+"input":"<!DOCTYPE a PUBLIC'' ",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''!",
+"input":"<!DOCTYPE a PUBLIC''!",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''\"",
+"input":"<!DOCTYPE a PUBLIC''\"",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", "", false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''#",
+"input":"<!DOCTYPE a PUBLIC''#",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''&",
+"input":"<!DOCTYPE a PUBLIC''&",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'''",
+"input":"<!DOCTYPE a PUBLIC'''",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", "", false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''(",
+"input":"<!DOCTYPE a PUBLIC''(",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''-",
+"input":"<!DOCTYPE a PUBLIC''-",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''/",
+"input":"<!DOCTYPE a PUBLIC''/",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''0",
+"input":"<!DOCTYPE a PUBLIC''0",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''1",
+"input":"<!DOCTYPE a PUBLIC''1",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''9",
+"input":"<!DOCTYPE a PUBLIC''9",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''<",
+"input":"<!DOCTYPE a PUBLIC''<",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''=",
+"input":"<!DOCTYPE a PUBLIC''=",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''>",
+"input":"<!DOCTYPE a PUBLIC''>",
+"output":["ParseError", ["DOCTYPE", "a", "", null, true]]},
+
+{"description":"<!DOCTYPE a PUBLIC''?",
+"input":"<!DOCTYPE a PUBLIC''?",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''@",
+"input":"<!DOCTYPE a PUBLIC''@",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''A",
+"input":"<!DOCTYPE a PUBLIC''A",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''B",
+"input":"<!DOCTYPE a PUBLIC''B",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''Y",
+"input":"<!DOCTYPE a PUBLIC''Y",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''Z",
+"input":"<!DOCTYPE a PUBLIC''Z",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''`",
+"input":"<!DOCTYPE a PUBLIC''`",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''a",
+"input":"<!DOCTYPE a PUBLIC''a",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''b",
+"input":"<!DOCTYPE a PUBLIC''b",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''y",
+"input":"<!DOCTYPE a PUBLIC''y",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''z",
+"input":"<!DOCTYPE a PUBLIC''z",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''{",
+"input":"<!DOCTYPE a PUBLIC''{",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC''\\uDBC0\\uDC00",
+"input":"<!DOCTYPE a PUBLIC''\uDBC0\uDC00",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'(",
+"input":"<!DOCTYPE a PUBLIC'(",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "(", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'-",
+"input":"<!DOCTYPE a PUBLIC'-",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "-", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'/",
+"input":"<!DOCTYPE a PUBLIC'/",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "/", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'0",
+"input":"<!DOCTYPE a PUBLIC'0",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "0", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'1",
+"input":"<!DOCTYPE a PUBLIC'1",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "1", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'9",
+"input":"<!DOCTYPE a PUBLIC'9",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "9", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'<",
+"input":"<!DOCTYPE a PUBLIC'<",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "<", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'=",
+"input":"<!DOCTYPE a PUBLIC'=",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "=", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'>",
+"input":"<!DOCTYPE a PUBLIC'>",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'?",
+"input":"<!DOCTYPE a PUBLIC'?",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "?", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'@",
+"input":"<!DOCTYPE a PUBLIC'@",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "@", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'A",
+"input":"<!DOCTYPE a PUBLIC'A",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "A", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'B",
+"input":"<!DOCTYPE a PUBLIC'B",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "B", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'Y",
+"input":"<!DOCTYPE a PUBLIC'Y",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "Y", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'Z",
+"input":"<!DOCTYPE a PUBLIC'Z",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "Z", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'`",
+"input":"<!DOCTYPE a PUBLIC'`",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "`", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'a",
+"input":"<!DOCTYPE a PUBLIC'a",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "a", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'b",
+"input":"<!DOCTYPE a PUBLIC'b",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "b", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'y",
+"input":"<!DOCTYPE a PUBLIC'y",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "y", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'z",
+"input":"<!DOCTYPE a PUBLIC'z",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "z", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'{",
+"input":"<!DOCTYPE a PUBLIC'{",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "{", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC'\\uDBC0\\uDC00",
+"input":"<!DOCTYPE a PUBLIC'\uDBC0\uDC00",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "\uDBC0\uDC00", null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC(",
+"input":"<!DOCTYPE a PUBLIC(",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC-",
+"input":"<!DOCTYPE a PUBLIC-",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC/",
+"input":"<!DOCTYPE a PUBLIC/",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC0",
+"input":"<!DOCTYPE a PUBLIC0",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC1",
+"input":"<!DOCTYPE a PUBLIC1",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC9",
+"input":"<!DOCTYPE a PUBLIC9",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC<",
+"input":"<!DOCTYPE a PUBLIC<",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC=",
+"input":"<!DOCTYPE a PUBLIC=",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC>",
+"input":"<!DOCTYPE a PUBLIC>",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC?",
+"input":"<!DOCTYPE a PUBLIC?",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC@",
+"input":"<!DOCTYPE a PUBLIC@",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLICA",
+"input":"<!DOCTYPE a PUBLICA",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLICB",
+"input":"<!DOCTYPE a PUBLICB",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLICY",
+"input":"<!DOCTYPE a PUBLICY",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLICZ",
+"input":"<!DOCTYPE a PUBLICZ",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC`",
+"input":"<!DOCTYPE a PUBLIC`",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLICa",
+"input":"<!DOCTYPE a PUBLICa",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLICb",
+"input":"<!DOCTYPE a PUBLICb",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLICy",
+"input":"<!DOCTYPE a PUBLICy",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLICz",
+"input":"<!DOCTYPE a PUBLICz",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC{",
+"input":"<!DOCTYPE a PUBLIC{",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a PUBLIC\\uDBC0\\uDC00",
+"input":"<!DOCTYPE a PUBLIC\uDBC0\uDC00",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEM",
+"input":"<!DOCTYPE a SYSTEM",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\\u0000",
+"input":"<!DOCTYPE a SYSTEM\u0000",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\\u0008",
+"input":"<!DOCTYPE a SYSTEM\u0008",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\\u0009",
+"input":"<!DOCTYPE a SYSTEM\u0009",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\\u000A",
+"input":"<!DOCTYPE a SYSTEM\u000A",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\\u000B",
+"input":"<!DOCTYPE a SYSTEM\u000B",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\\u000C",
+"input":"<!DOCTYPE a SYSTEM\u000C",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\\u000D",
+"input":"<!DOCTYPE a SYSTEM\u000D",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\\u001F",
+"input":"<!DOCTYPE a SYSTEM\u001F",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEM ",
+"input":"<!DOCTYPE a SYSTEM ",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEM!",
+"input":"<!DOCTYPE a SYSTEM!",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\"",
+"input":"<!DOCTYPE a SYSTEM\"",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\"\\u0000",
+"input":"<!DOCTYPE a SYSTEM\"\u0000",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "\uFFFD", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\"\\u0009",
+"input":"<!DOCTYPE a SYSTEM\"\u0009",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "\u0009", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\"\\u000A",
+"input":"<!DOCTYPE a SYSTEM\"\u000A",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "\u000A", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\"\\u000B",
+"input":"<!DOCTYPE a SYSTEM\"\u000B",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "\u000B", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\"\\u000C",
+"input":"<!DOCTYPE a SYSTEM\"\u000C",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "\u000C", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\" ",
+"input":"<!DOCTYPE a SYSTEM\" ",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, " ", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\"!",
+"input":"<!DOCTYPE a SYSTEM\"!",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "!", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\"\"",
+"input":"<!DOCTYPE a SYSTEM\"\"",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\"#",
+"input":"<!DOCTYPE a SYSTEM\"#",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "#", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\"&",
+"input":"<!DOCTYPE a SYSTEM\"&",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "&", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\"'",
+"input":"<!DOCTYPE a SYSTEM\"'",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "'", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\"-",
+"input":"<!DOCTYPE a SYSTEM\"-",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "-", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\"/",
+"input":"<!DOCTYPE a SYSTEM\"/",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "/", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\"0",
+"input":"<!DOCTYPE a SYSTEM\"0",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "0", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\"1",
+"input":"<!DOCTYPE a SYSTEM\"1",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "1", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\"9",
+"input":"<!DOCTYPE a SYSTEM\"9",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "9", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\"<",
+"input":"<!DOCTYPE a SYSTEM\"<",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "<", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\"=",
+"input":"<!DOCTYPE a SYSTEM\"=",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "=", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\">",
+"input":"<!DOCTYPE a SYSTEM\">",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\"?",
+"input":"<!DOCTYPE a SYSTEM\"?",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "?", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\"@",
+"input":"<!DOCTYPE a SYSTEM\"@",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "@", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\"A",
+"input":"<!DOCTYPE a SYSTEM\"A",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "A", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\"B",
+"input":"<!DOCTYPE a SYSTEM\"B",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "B", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\"Y",
+"input":"<!DOCTYPE a SYSTEM\"Y",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "Y", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\"Z",
+"input":"<!DOCTYPE a SYSTEM\"Z",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "Z", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\"`",
+"input":"<!DOCTYPE a SYSTEM\"`",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "`", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\"a",
+"input":"<!DOCTYPE a SYSTEM\"a",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "a", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\"b",
+"input":"<!DOCTYPE a SYSTEM\"b",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "b", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\"y",
+"input":"<!DOCTYPE a SYSTEM\"y",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "y", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\"z",
+"input":"<!DOCTYPE a SYSTEM\"z",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "z", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\"{",
+"input":"<!DOCTYPE a SYSTEM\"{",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "{", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\"\\uDBC0\\uDC00",
+"input":"<!DOCTYPE a SYSTEM\"\uDBC0\uDC00",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "\uDBC0\uDC00", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM#",
+"input":"<!DOCTYPE a SYSTEM#",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEM&",
+"input":"<!DOCTYPE a SYSTEM&",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEM'",
+"input":"<!DOCTYPE a SYSTEM'",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM'\\u0000",
+"input":"<!DOCTYPE a SYSTEM'\u0000",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "\uFFFD", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM'\\u0009",
+"input":"<!DOCTYPE a SYSTEM'\u0009",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "\u0009", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM'\\u000A",
+"input":"<!DOCTYPE a SYSTEM'\u000A",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "\u000A", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM'\\u000B",
+"input":"<!DOCTYPE a SYSTEM'\u000B",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "\u000B", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM'\\u000C",
+"input":"<!DOCTYPE a SYSTEM'\u000C",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "\u000C", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM' ",
+"input":"<!DOCTYPE a SYSTEM' ",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, " ", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM'!",
+"input":"<!DOCTYPE a SYSTEM'!",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "!", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM'\"",
+"input":"<!DOCTYPE a SYSTEM'\"",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "\"", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM'&",
+"input":"<!DOCTYPE a SYSTEM'&",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "&", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM''",
+"input":"<!DOCTYPE a SYSTEM''",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM''\\u0000",
+"input":"<!DOCTYPE a SYSTEM''\u0000",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPE a SYSTEM''\\u0008",
+"input":"<!DOCTYPE a SYSTEM''\u0008",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPE a SYSTEM''\\u0009",
+"input":"<!DOCTYPE a SYSTEM''\u0009",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM''\\u000A",
+"input":"<!DOCTYPE a SYSTEM''\u000A",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM''\\u000B",
+"input":"<!DOCTYPE a SYSTEM''\u000B",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPE a SYSTEM''\\u000C",
+"input":"<!DOCTYPE a SYSTEM''\u000C",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM''\\u000D",
+"input":"<!DOCTYPE a SYSTEM''\u000D",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM''\\u001F",
+"input":"<!DOCTYPE a SYSTEM''\u001F",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPE a SYSTEM'' ",
+"input":"<!DOCTYPE a SYSTEM'' ",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM''!",
+"input":"<!DOCTYPE a SYSTEM''!",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPE a SYSTEM''\"",
+"input":"<!DOCTYPE a SYSTEM''\"",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPE a SYSTEM''&",
+"input":"<!DOCTYPE a SYSTEM''&",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPE a SYSTEM'''",
+"input":"<!DOCTYPE a SYSTEM'''",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPE a SYSTEM''-",
+"input":"<!DOCTYPE a SYSTEM''-",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPE a SYSTEM''/",
+"input":"<!DOCTYPE a SYSTEM''/",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPE a SYSTEM''0",
+"input":"<!DOCTYPE a SYSTEM''0",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPE a SYSTEM''1",
+"input":"<!DOCTYPE a SYSTEM''1",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPE a SYSTEM''9",
+"input":"<!DOCTYPE a SYSTEM''9",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPE a SYSTEM''<",
+"input":"<!DOCTYPE a SYSTEM''<",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPE a SYSTEM''=",
+"input":"<!DOCTYPE a SYSTEM''=",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPE a SYSTEM''>",
+"input":"<!DOCTYPE a SYSTEM''>",
+"output":["ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPE a SYSTEM''?",
+"input":"<!DOCTYPE a SYSTEM''?",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPE a SYSTEM''@",
+"input":"<!DOCTYPE a SYSTEM''@",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPE a SYSTEM''A",
+"input":"<!DOCTYPE a SYSTEM''A",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPE a SYSTEM''B",
+"input":"<!DOCTYPE a SYSTEM''B",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPE a SYSTEM''Y",
+"input":"<!DOCTYPE a SYSTEM''Y",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPE a SYSTEM''Z",
+"input":"<!DOCTYPE a SYSTEM''Z",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPE a SYSTEM''`",
+"input":"<!DOCTYPE a SYSTEM''`",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPE a SYSTEM''a",
+"input":"<!DOCTYPE a SYSTEM''a",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPE a SYSTEM''b",
+"input":"<!DOCTYPE a SYSTEM''b",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPE a SYSTEM''y",
+"input":"<!DOCTYPE a SYSTEM''y",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPE a SYSTEM''z",
+"input":"<!DOCTYPE a SYSTEM''z",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPE a SYSTEM''{",
+"input":"<!DOCTYPE a SYSTEM''{",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPE a SYSTEM''\\uDBC0\\uDC00",
+"input":"<!DOCTYPE a SYSTEM''\uDBC0\uDC00",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPE a SYSTEM'(",
+"input":"<!DOCTYPE a SYSTEM'(",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "(", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM'-",
+"input":"<!DOCTYPE a SYSTEM'-",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "-", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM'/",
+"input":"<!DOCTYPE a SYSTEM'/",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "/", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM'0",
+"input":"<!DOCTYPE a SYSTEM'0",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "0", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM'1",
+"input":"<!DOCTYPE a SYSTEM'1",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "1", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM'9",
+"input":"<!DOCTYPE a SYSTEM'9",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "9", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM'<",
+"input":"<!DOCTYPE a SYSTEM'<",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "<", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM'=",
+"input":"<!DOCTYPE a SYSTEM'=",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "=", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM'>",
+"input":"<!DOCTYPE a SYSTEM'>",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM'?",
+"input":"<!DOCTYPE a SYSTEM'?",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "?", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM'@",
+"input":"<!DOCTYPE a SYSTEM'@",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "@", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM'A",
+"input":"<!DOCTYPE a SYSTEM'A",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "A", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM'B",
+"input":"<!DOCTYPE a SYSTEM'B",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "B", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM'Y",
+"input":"<!DOCTYPE a SYSTEM'Y",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "Y", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM'Z",
+"input":"<!DOCTYPE a SYSTEM'Z",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "Z", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM'`",
+"input":"<!DOCTYPE a SYSTEM'`",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "`", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM'a",
+"input":"<!DOCTYPE a SYSTEM'a",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "a", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM'b",
+"input":"<!DOCTYPE a SYSTEM'b",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "b", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM'y",
+"input":"<!DOCTYPE a SYSTEM'y",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "y", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM'z",
+"input":"<!DOCTYPE a SYSTEM'z",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "z", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM'{",
+"input":"<!DOCTYPE a SYSTEM'{",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "{", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM'\\uDBC0\\uDC00",
+"input":"<!DOCTYPE a SYSTEM'\uDBC0\uDC00",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "\uDBC0\uDC00", false]]},
+
+{"description":"<!DOCTYPE a SYSTEM(",
+"input":"<!DOCTYPE a SYSTEM(",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEM-",
+"input":"<!DOCTYPE a SYSTEM-",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEM/",
+"input":"<!DOCTYPE a SYSTEM/",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEM0",
+"input":"<!DOCTYPE a SYSTEM0",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEM1",
+"input":"<!DOCTYPE a SYSTEM1",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEM9",
+"input":"<!DOCTYPE a SYSTEM9",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEM<",
+"input":"<!DOCTYPE a SYSTEM<",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEM=",
+"input":"<!DOCTYPE a SYSTEM=",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEM>",
+"input":"<!DOCTYPE a SYSTEM>",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEM?",
+"input":"<!DOCTYPE a SYSTEM?",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEM@",
+"input":"<!DOCTYPE a SYSTEM@",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEMA",
+"input":"<!DOCTYPE a SYSTEMA",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEMB",
+"input":"<!DOCTYPE a SYSTEMB",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEMY",
+"input":"<!DOCTYPE a SYSTEMY",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEMZ",
+"input":"<!DOCTYPE a SYSTEMZ",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEM`",
+"input":"<!DOCTYPE a SYSTEM`",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEMa",
+"input":"<!DOCTYPE a SYSTEMa",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEMb",
+"input":"<!DOCTYPE a SYSTEMb",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEMy",
+"input":"<!DOCTYPE a SYSTEMy",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEMz",
+"input":"<!DOCTYPE a SYSTEMz",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEM{",
+"input":"<!DOCTYPE a SYSTEM{",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a SYSTEM\\uDBC0\\uDC00",
+"input":"<!DOCTYPE a SYSTEM\uDBC0\uDC00",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a Y",
+"input":"<!DOCTYPE a Y",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a Z",
+"input":"<!DOCTYPE a Z",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a `",
+"input":"<!DOCTYPE a `",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a a",
+"input":"<!DOCTYPE a a",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a a\\u0000",
+"input":"<!DOCTYPE a a\u0000",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a a\\u0009",
+"input":"<!DOCTYPE a a\u0009",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a a\\u000A",
+"input":"<!DOCTYPE a a\u000A",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a a\\u000B",
+"input":"<!DOCTYPE a a\u000B",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a a\\u000C",
+"input":"<!DOCTYPE a a\u000C",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a a ",
+"input":"<!DOCTYPE a a ",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a a!",
+"input":"<!DOCTYPE a a!",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a a\"",
+"input":"<!DOCTYPE a a\"",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a a&",
+"input":"<!DOCTYPE a a&",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a a'",
+"input":"<!DOCTYPE a a'",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a a-",
+"input":"<!DOCTYPE a a-",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a a/",
+"input":"<!DOCTYPE a a/",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a a0",
+"input":"<!DOCTYPE a a0",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a a1",
+"input":"<!DOCTYPE a a1",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a a9",
+"input":"<!DOCTYPE a a9",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a a<",
+"input":"<!DOCTYPE a a<",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a a=",
+"input":"<!DOCTYPE a a=",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a a>",
+"input":"<!DOCTYPE a a>",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a a?",
+"input":"<!DOCTYPE a a?",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a a@",
+"input":"<!DOCTYPE a a@",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a aA",
+"input":"<!DOCTYPE a aA",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a aB",
+"input":"<!DOCTYPE a aB",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a aY",
+"input":"<!DOCTYPE a aY",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a aZ",
+"input":"<!DOCTYPE a aZ",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a a`",
+"input":"<!DOCTYPE a a`",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a aa",
+"input":"<!DOCTYPE a aa",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a ab",
+"input":"<!DOCTYPE a ab",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a ay",
+"input":"<!DOCTYPE a ay",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a az",
+"input":"<!DOCTYPE a az",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a a{",
+"input":"<!DOCTYPE a a{",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a a\\uDBC0\\uDC00",
+"input":"<!DOCTYPE a a\uDBC0\uDC00",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a b",
+"input":"<!DOCTYPE a b",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a y",
+"input":"<!DOCTYPE a y",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a z",
+"input":"<!DOCTYPE a z",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a {",
+"input":"<!DOCTYPE a {",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a \\uDBC0\\uDC00",
+"input":"<!DOCTYPE a \uDBC0\uDC00",
+"output":["ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPE a!",
+"input":"<!DOCTYPE a!",
+"output":["ParseError", ["DOCTYPE", "a!", null, null, false]]},
+
+{"description":"<!DOCTYPE a\"",
+"input":"<!DOCTYPE a\"",
+"output":["ParseError", ["DOCTYPE", "a\"", null, null, false]]},
+
+{"description":"<!DOCTYPE a&",
+"input":"<!DOCTYPE a&",
+"output":["ParseError", ["DOCTYPE", "a&", null, null, false]]},
+
+{"description":"<!DOCTYPE a'",
+"input":"<!DOCTYPE a'",
+"output":["ParseError", ["DOCTYPE", "a'", null, null, false]]},
+
+{"description":"<!DOCTYPE a-",
+"input":"<!DOCTYPE a-",
+"output":["ParseError", ["DOCTYPE", "a-", null, null, false]]},
+
+{"description":"<!DOCTYPE a/",
+"input":"<!DOCTYPE a/",
+"output":["ParseError", ["DOCTYPE", "a/", null, null, false]]},
+
+{"description":"<!DOCTYPE a0",
+"input":"<!DOCTYPE a0",
+"output":["ParseError", ["DOCTYPE", "a0", null, null, false]]},
+
+{"description":"<!DOCTYPE a1",
+"input":"<!DOCTYPE a1",
+"output":["ParseError", ["DOCTYPE", "a1", null, null, false]]},
+
+{"description":"<!DOCTYPE a9",
+"input":"<!DOCTYPE a9",
+"output":["ParseError", ["DOCTYPE", "a9", null, null, false]]},
+
+{"description":"<!DOCTYPE a<",
+"input":"<!DOCTYPE a<",
+"output":["ParseError", ["DOCTYPE", "a<", null, null, false]]},
+
+{"description":"<!DOCTYPE a=",
+"input":"<!DOCTYPE a=",
+"output":["ParseError", ["DOCTYPE", "a=", null, null, false]]},
+
+{"description":"<!DOCTYPE a>",
+"input":"<!DOCTYPE a>",
+"output":[["DOCTYPE", "a", null, null, true]]},
+
+{"description":"<!DOCTYPE a?",
+"input":"<!DOCTYPE a?",
+"output":["ParseError", ["DOCTYPE", "a?", null, null, false]]},
+
+{"description":"<!DOCTYPE a@",
+"input":"<!DOCTYPE a@",
+"output":["ParseError", ["DOCTYPE", "a@", null, null, false]]},
+
+{"description":"<!DOCTYPE aA",
+"input":"<!DOCTYPE aA",
+"output":["ParseError", ["DOCTYPE", "aa", null, null, false]]},
+
+{"description":"<!DOCTYPE aB",
+"input":"<!DOCTYPE aB",
+"output":["ParseError", ["DOCTYPE", "ab", null, null, false]]},
+
+{"description":"<!DOCTYPE aY",
+"input":"<!DOCTYPE aY",
+"output":["ParseError", ["DOCTYPE", "ay", null, null, false]]},
+
+{"description":"<!DOCTYPE aZ",
+"input":"<!DOCTYPE aZ",
+"output":["ParseError", ["DOCTYPE", "az", null, null, false]]},
+
+{"description":"<!DOCTYPE a[",
+"input":"<!DOCTYPE a[",
+"output":["ParseError", ["DOCTYPE", "a[", null, null, false]]},
+
+{"description":"<!DOCTYPE a`",
+"input":"<!DOCTYPE a`",
+"output":["ParseError", ["DOCTYPE", "a`", null, null, false]]},
+
+{"description":"<!DOCTYPE aa",
+"input":"<!DOCTYPE aa",
+"output":["ParseError", ["DOCTYPE", "aa", null, null, false]]},
+
+{"description":"<!DOCTYPE ab",
+"input":"<!DOCTYPE ab",
+"output":["ParseError", ["DOCTYPE", "ab", null, null, false]]},
+
+{"description":"<!DOCTYPE ay",
+"input":"<!DOCTYPE ay",
+"output":["ParseError", ["DOCTYPE", "ay", null, null, false]]},
+
+{"description":"<!DOCTYPE az",
+"input":"<!DOCTYPE az",
+"output":["ParseError", ["DOCTYPE", "az", null, null, false]]},
+
+{"description":"<!DOCTYPE a{",
+"input":"<!DOCTYPE a{",
+"output":["ParseError", ["DOCTYPE", "a{", null, null, false]]},
+
+{"description":"<!DOCTYPE a\\uDBC0\\uDC00",
+"input":"<!DOCTYPE a\uDBC0\uDC00",
+"output":["ParseError", ["DOCTYPE", "a\uDBC0\uDC00", null, null, false]]},
+
+{"description":"<!DOCTYPE b",
+"input":"<!DOCTYPE b",
+"output":["ParseError", ["DOCTYPE", "b", null, null, false]]},
+
+{"description":"<!DOCTYPE y",
+"input":"<!DOCTYPE y",
+"output":["ParseError", ["DOCTYPE", "y", null, null, false]]},
+
+{"description":"<!DOCTYPE z",
+"input":"<!DOCTYPE z",
+"output":["ParseError", ["DOCTYPE", "z", null, null, false]]},
+
+{"description":"<!DOCTYPE {",
+"input":"<!DOCTYPE {",
+"output":["ParseError", ["DOCTYPE", "{", null, null, false]]},
+
+{"description":"<!DOCTYPE \\uDBC0\\uDC00",
+"input":"<!DOCTYPE \uDBC0\uDC00",
+"output":["ParseError", ["DOCTYPE", "\uDBC0\uDC00", null, null, false]]},
+
+{"description":"<!DOCTYPE!",
+"input":"<!DOCTYPE!",
+"output":["ParseError", "ParseError", ["DOCTYPE", "!", null, null, false]]},
+
+{"description":"<!DOCTYPE\"",
+"input":"<!DOCTYPE\"",
+"output":["ParseError", "ParseError", ["DOCTYPE", "\"", null, null, false]]},
+
+{"description":"<!DOCTYPE&",
+"input":"<!DOCTYPE&",
+"output":["ParseError", "ParseError", ["DOCTYPE", "&", null, null, false]]},
+
+{"description":"<!DOCTYPE'",
+"input":"<!DOCTYPE'",
+"output":["ParseError", "ParseError", ["DOCTYPE", "'", null, null, false]]},
+
+{"description":"<!DOCTYPE-",
+"input":"<!DOCTYPE-",
+"output":["ParseError", "ParseError", ["DOCTYPE", "-", null, null, false]]},
+
+{"description":"<!DOCTYPE/",
+"input":"<!DOCTYPE/",
+"output":["ParseError", "ParseError", ["DOCTYPE", "/", null, null, false]]},
+
+{"description":"<!DOCTYPE0",
+"input":"<!DOCTYPE0",
+"output":["ParseError", "ParseError", ["DOCTYPE", "0", null, null, false]]},
+
+{"description":"<!DOCTYPE1",
+"input":"<!DOCTYPE1",
+"output":["ParseError", "ParseError", ["DOCTYPE", "1", null, null, false]]},
+
+{"description":"<!DOCTYPE9",
+"input":"<!DOCTYPE9",
+"output":["ParseError", "ParseError", ["DOCTYPE", "9", null, null, false]]},
+
+{"description":"<!DOCTYPE<",
+"input":"<!DOCTYPE<",
+"output":["ParseError", "ParseError", ["DOCTYPE", "<", null, null, false]]},
+
+{"description":"<!DOCTYPE=",
+"input":"<!DOCTYPE=",
+"output":["ParseError", "ParseError", ["DOCTYPE", "=", null, null, false]]},
+
+{"description":"<!DOCTYPE>",
+"input":"<!DOCTYPE>",
+"output":["ParseError", "ParseError", ["DOCTYPE", "", null, null, false]]},
+
+{"description":"<!DOCTYPE?",
+"input":"<!DOCTYPE?",
+"output":["ParseError", "ParseError", ["DOCTYPE", "?", null, null, false]]},
+
+{"description":"<!DOCTYPE@",
+"input":"<!DOCTYPE@",
+"output":["ParseError", "ParseError", ["DOCTYPE", "@", null, null, false]]},
+
+{"description":"<!DOCTYPEA",
+"input":"<!DOCTYPEA",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEB",
+"input":"<!DOCTYPEB",
+"output":["ParseError", "ParseError", ["DOCTYPE", "b", null, null, false]]},
+
+{"description":"<!DOCTYPEY",
+"input":"<!DOCTYPEY",
+"output":["ParseError", "ParseError", ["DOCTYPE", "y", null, null, false]]},
+
+{"description":"<!DOCTYPEZ",
+"input":"<!DOCTYPEZ",
+"output":["ParseError", "ParseError", ["DOCTYPE", "z", null, null, false]]},
+
+{"description":"<!DOCTYPE`",
+"input":"<!DOCTYPE`",
+"output":["ParseError", "ParseError", ["DOCTYPE", "`", null, null, false]]},
+
+{"description":"<!DOCTYPEa",
+"input":"<!DOCTYPEa",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa\\u0000",
+"input":"<!DOCTYPEa\u0000",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a\uFFFD", null, null, false]]},
+
+{"description":"<!DOCTYPEa\\u0008",
+"input":"<!DOCTYPEa\u0008",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a\u0008", null, null, false]]},
+
+{"description":"<!DOCTYPEa\\u0009",
+"input":"<!DOCTYPEa\u0009",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa\\u000A",
+"input":"<!DOCTYPEa\u000A",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa\\u000B",
+"input":"<!DOCTYPEa\u000B",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a\u000B", null, null, false]]},
+
+{"description":"<!DOCTYPEa\\u000C",
+"input":"<!DOCTYPEa\u000C",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa\\u000D",
+"input":"<!DOCTYPEa\u000D",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa\\u001F",
+"input":"<!DOCTYPEa\u001F",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a\u001F", null, null, false]]},
+
+{"description":"<!DOCTYPEa ",
+"input":"<!DOCTYPEa ",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa \\u0000",
+"input":"<!DOCTYPEa \u0000",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa \\u0008",
+"input":"<!DOCTYPEa \u0008",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa \\u0009",
+"input":"<!DOCTYPEa \u0009",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa \\u000A",
+"input":"<!DOCTYPEa \u000A",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa \\u000B",
+"input":"<!DOCTYPEa \u000B",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa \\u000C",
+"input":"<!DOCTYPEa \u000C",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa \\u000D",
+"input":"<!DOCTYPEa \u000D",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa \\u001F",
+"input":"<!DOCTYPEa \u001F",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa ",
+"input":"<!DOCTYPEa ",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa !",
+"input":"<!DOCTYPEa !",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa \"",
+"input":"<!DOCTYPEa \"",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa &",
+"input":"<!DOCTYPEa &",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa '",
+"input":"<!DOCTYPEa '",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa -",
+"input":"<!DOCTYPEa -",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa /",
+"input":"<!DOCTYPEa /",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa 0",
+"input":"<!DOCTYPEa 0",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa 1",
+"input":"<!DOCTYPEa 1",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa 9",
+"input":"<!DOCTYPEa 9",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa <",
+"input":"<!DOCTYPEa <",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa =",
+"input":"<!DOCTYPEa =",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa >",
+"input":"<!DOCTYPEa >",
+"output":["ParseError", ["DOCTYPE", "a", null, null, true]]},
+
+{"description":"<!DOCTYPEa ?",
+"input":"<!DOCTYPEa ?",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa @",
+"input":"<!DOCTYPEa @",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa A",
+"input":"<!DOCTYPEa A",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa B",
+"input":"<!DOCTYPEa B",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC",
+"input":"<!DOCTYPEa PUBLIC",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\\u0000",
+"input":"<!DOCTYPEa PUBLIC\u0000",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\\u0008",
+"input":"<!DOCTYPEa PUBLIC\u0008",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\\u0009",
+"input":"<!DOCTYPEa PUBLIC\u0009",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\\u000A",
+"input":"<!DOCTYPEa PUBLIC\u000A",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\\u000B",
+"input":"<!DOCTYPEa PUBLIC\u000B",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\\u000C",
+"input":"<!DOCTYPEa PUBLIC\u000C",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\\u000D",
+"input":"<!DOCTYPEa PUBLIC\u000D",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\\u001F",
+"input":"<!DOCTYPEa PUBLIC\u001F",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC ",
+"input":"<!DOCTYPEa PUBLIC ",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC!",
+"input":"<!DOCTYPEa PUBLIC!",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\"",
+"input":"<!DOCTYPEa PUBLIC\"",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\"\\u0000",
+"input":"<!DOCTYPEa PUBLIC\"\u0000",
+"output":["ParseError", "ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "\uFFFD", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\"\\u0009",
+"input":"<!DOCTYPEa PUBLIC\"\u0009",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "\u0009", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\"\\u000A",
+"input":"<!DOCTYPEa PUBLIC\"\u000A",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "\u000A", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\"\\u000B",
+"input":"<!DOCTYPEa PUBLIC\"\u000B",
+"output":["ParseError", "ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "\u000B", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\"\\u000C",
+"input":"<!DOCTYPEa PUBLIC\"\u000C",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "\u000C", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\" ",
+"input":"<!DOCTYPEa PUBLIC\" ",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", " ", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\"!",
+"input":"<!DOCTYPEa PUBLIC\"!",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "!", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\"\"",
+"input":"<!DOCTYPEa PUBLIC\"\"",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\"#",
+"input":"<!DOCTYPEa PUBLIC\"#",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "#", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\"&",
+"input":"<!DOCTYPEa PUBLIC\"&",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "&", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\"'",
+"input":"<!DOCTYPEa PUBLIC\"'",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "'", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\"-",
+"input":"<!DOCTYPEa PUBLIC\"-",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "-", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\"/",
+"input":"<!DOCTYPEa PUBLIC\"/",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "/", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\"0",
+"input":"<!DOCTYPEa PUBLIC\"0",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "0", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\"1",
+"input":"<!DOCTYPEa PUBLIC\"1",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "1", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\"9",
+"input":"<!DOCTYPEa PUBLIC\"9",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "9", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\"<",
+"input":"<!DOCTYPEa PUBLIC\"<",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "<", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\"=",
+"input":"<!DOCTYPEa PUBLIC\"=",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "=", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\">",
+"input":"<!DOCTYPEa PUBLIC\">",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\"?",
+"input":"<!DOCTYPEa PUBLIC\"?",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "?", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\"@",
+"input":"<!DOCTYPEa PUBLIC\"@",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "@", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\"A",
+"input":"<!DOCTYPEa PUBLIC\"A",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "A", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\"B",
+"input":"<!DOCTYPEa PUBLIC\"B",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "B", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\"Y",
+"input":"<!DOCTYPEa PUBLIC\"Y",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "Y", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\"Z",
+"input":"<!DOCTYPEa PUBLIC\"Z",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "Z", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\"`",
+"input":"<!DOCTYPEa PUBLIC\"`",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "`", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\"a",
+"input":"<!DOCTYPEa PUBLIC\"a",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "a", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\"b",
+"input":"<!DOCTYPEa PUBLIC\"b",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "b", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\"y",
+"input":"<!DOCTYPEa PUBLIC\"y",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "y", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\"z",
+"input":"<!DOCTYPEa PUBLIC\"z",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "z", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\"{",
+"input":"<!DOCTYPEa PUBLIC\"{",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "{", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\"\\uDBC0\\uDC00",
+"input":"<!DOCTYPEa PUBLIC\"\uDBC0\uDC00",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "\uDBC0\uDC00", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC#",
+"input":"<!DOCTYPEa PUBLIC#",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC&",
+"input":"<!DOCTYPEa PUBLIC&",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'",
+"input":"<!DOCTYPEa PUBLIC'",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'\\u0000",
+"input":"<!DOCTYPEa PUBLIC'\u0000",
+"output":["ParseError", "ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "\uFFFD", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'\\u0009",
+"input":"<!DOCTYPEa PUBLIC'\u0009",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "\u0009", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'\\u000A",
+"input":"<!DOCTYPEa PUBLIC'\u000A",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "\u000A", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'\\u000B",
+"input":"<!DOCTYPEa PUBLIC'\u000B",
+"output":["ParseError", "ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "\u000B", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'\\u000C",
+"input":"<!DOCTYPEa PUBLIC'\u000C",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "\u000C", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC' ",
+"input":"<!DOCTYPEa PUBLIC' ",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", " ", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'!",
+"input":"<!DOCTYPEa PUBLIC'!",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "!", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'\"",
+"input":"<!DOCTYPEa PUBLIC'\"",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "\"", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'&",
+"input":"<!DOCTYPEa PUBLIC'&",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "&", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''",
+"input":"<!DOCTYPEa PUBLIC''",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''\\u0000",
+"input":"<!DOCTYPEa PUBLIC''\u0000",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''\\u0008",
+"input":"<!DOCTYPEa PUBLIC''\u0008",
+"output":["ParseError", "ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''\\u0009",
+"input":"<!DOCTYPEa PUBLIC''\u0009",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''\\u000A",
+"input":"<!DOCTYPEa PUBLIC''\u000A",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''\\u000B",
+"input":"<!DOCTYPEa PUBLIC''\u000B",
+"output":["ParseError", "ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''\\u000C",
+"input":"<!DOCTYPEa PUBLIC''\u000C",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''\\u000D",
+"input":"<!DOCTYPEa PUBLIC''\u000D",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''\\u001F",
+"input":"<!DOCTYPEa PUBLIC''\u001F",
+"output":["ParseError", "ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'' ",
+"input":"<!DOCTYPEa PUBLIC'' ",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''!",
+"input":"<!DOCTYPEa PUBLIC''!",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''\"",
+"input":"<!DOCTYPEa PUBLIC''\"",
+"output":["ParseError", "ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", "", false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''#",
+"input":"<!DOCTYPEa PUBLIC''#",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''&",
+"input":"<!DOCTYPEa PUBLIC''&",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'''",
+"input":"<!DOCTYPEa PUBLIC'''",
+"output":["ParseError", "ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", "", false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''(",
+"input":"<!DOCTYPEa PUBLIC''(",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''-",
+"input":"<!DOCTYPEa PUBLIC''-",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''/",
+"input":"<!DOCTYPEa PUBLIC''/",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''0",
+"input":"<!DOCTYPEa PUBLIC''0",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''1",
+"input":"<!DOCTYPEa PUBLIC''1",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''9",
+"input":"<!DOCTYPEa PUBLIC''9",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''<",
+"input":"<!DOCTYPEa PUBLIC''<",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''=",
+"input":"<!DOCTYPEa PUBLIC''=",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''>",
+"input":"<!DOCTYPEa PUBLIC''>",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", "", null, true]]},
+
+{"description":"<!DOCTYPEa PUBLIC''?",
+"input":"<!DOCTYPEa PUBLIC''?",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''@",
+"input":"<!DOCTYPEa PUBLIC''@",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''A",
+"input":"<!DOCTYPEa PUBLIC''A",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''B",
+"input":"<!DOCTYPEa PUBLIC''B",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''Y",
+"input":"<!DOCTYPEa PUBLIC''Y",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''Z",
+"input":"<!DOCTYPEa PUBLIC''Z",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''`",
+"input":"<!DOCTYPEa PUBLIC''`",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''a",
+"input":"<!DOCTYPEa PUBLIC''a",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''b",
+"input":"<!DOCTYPEa PUBLIC''b",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''y",
+"input":"<!DOCTYPEa PUBLIC''y",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''z",
+"input":"<!DOCTYPEa PUBLIC''z",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''{",
+"input":"<!DOCTYPEa PUBLIC''{",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC''\\uDBC0\\uDC00",
+"input":"<!DOCTYPEa PUBLIC''\uDBC0\uDC00",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'(",
+"input":"<!DOCTYPEa PUBLIC'(",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "(", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'-",
+"input":"<!DOCTYPEa PUBLIC'-",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "-", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'/",
+"input":"<!DOCTYPEa PUBLIC'/",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "/", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'0",
+"input":"<!DOCTYPEa PUBLIC'0",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "0", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'1",
+"input":"<!DOCTYPEa PUBLIC'1",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "1", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'9",
+"input":"<!DOCTYPEa PUBLIC'9",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "9", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'<",
+"input":"<!DOCTYPEa PUBLIC'<",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "<", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'=",
+"input":"<!DOCTYPEa PUBLIC'=",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "=", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'>",
+"input":"<!DOCTYPEa PUBLIC'>",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'?",
+"input":"<!DOCTYPEa PUBLIC'?",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "?", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'@",
+"input":"<!DOCTYPEa PUBLIC'@",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "@", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'A",
+"input":"<!DOCTYPEa PUBLIC'A",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "A", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'B",
+"input":"<!DOCTYPEa PUBLIC'B",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "B", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'Y",
+"input":"<!DOCTYPEa PUBLIC'Y",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "Y", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'Z",
+"input":"<!DOCTYPEa PUBLIC'Z",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "Z", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'`",
+"input":"<!DOCTYPEa PUBLIC'`",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "`", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'a",
+"input":"<!DOCTYPEa PUBLIC'a",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "a", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'b",
+"input":"<!DOCTYPEa PUBLIC'b",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "b", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'y",
+"input":"<!DOCTYPEa PUBLIC'y",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "y", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'z",
+"input":"<!DOCTYPEa PUBLIC'z",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "z", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'{",
+"input":"<!DOCTYPEa PUBLIC'{",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "{", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC'\\uDBC0\\uDC00",
+"input":"<!DOCTYPEa PUBLIC'\uDBC0\uDC00",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", "\uDBC0\uDC00", null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC(",
+"input":"<!DOCTYPEa PUBLIC(",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC-",
+"input":"<!DOCTYPEa PUBLIC-",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC/",
+"input":"<!DOCTYPEa PUBLIC/",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC0",
+"input":"<!DOCTYPEa PUBLIC0",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC1",
+"input":"<!DOCTYPEa PUBLIC1",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC9",
+"input":"<!DOCTYPEa PUBLIC9",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC<",
+"input":"<!DOCTYPEa PUBLIC<",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC=",
+"input":"<!DOCTYPEa PUBLIC=",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC>",
+"input":"<!DOCTYPEa PUBLIC>",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC?",
+"input":"<!DOCTYPEa PUBLIC?",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC@",
+"input":"<!DOCTYPEa PUBLIC@",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLICA",
+"input":"<!DOCTYPEa PUBLICA",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLICB",
+"input":"<!DOCTYPEa PUBLICB",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLICY",
+"input":"<!DOCTYPEa PUBLICY",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLICZ",
+"input":"<!DOCTYPEa PUBLICZ",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC`",
+"input":"<!DOCTYPEa PUBLIC`",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLICa",
+"input":"<!DOCTYPEa PUBLICa",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLICb",
+"input":"<!DOCTYPEa PUBLICb",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLICy",
+"input":"<!DOCTYPEa PUBLICy",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLICz",
+"input":"<!DOCTYPEa PUBLICz",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC{",
+"input":"<!DOCTYPEa PUBLIC{",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa PUBLIC\\uDBC0\\uDC00",
+"input":"<!DOCTYPEa PUBLIC\uDBC0\uDC00",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEM",
+"input":"<!DOCTYPEa SYSTEM",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\\u0000",
+"input":"<!DOCTYPEa SYSTEM\u0000",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\\u0008",
+"input":"<!DOCTYPEa SYSTEM\u0008",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\\u0009",
+"input":"<!DOCTYPEa SYSTEM\u0009",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\\u000A",
+"input":"<!DOCTYPEa SYSTEM\u000A",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\\u000B",
+"input":"<!DOCTYPEa SYSTEM\u000B",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\\u000C",
+"input":"<!DOCTYPEa SYSTEM\u000C",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\\u000D",
+"input":"<!DOCTYPEa SYSTEM\u000D",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\\u001F",
+"input":"<!DOCTYPEa SYSTEM\u001F",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEM ",
+"input":"<!DOCTYPEa SYSTEM ",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEM!",
+"input":"<!DOCTYPEa SYSTEM!",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\"",
+"input":"<!DOCTYPEa SYSTEM\"",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\"\\u0000",
+"input":"<!DOCTYPEa SYSTEM\"\u0000",
+"output":["ParseError", "ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "\uFFFD", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\"\\u0009",
+"input":"<!DOCTYPEa SYSTEM\"\u0009",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "\u0009", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\"\\u000A",
+"input":"<!DOCTYPEa SYSTEM\"\u000A",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "\u000A", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\"\\u000B",
+"input":"<!DOCTYPEa SYSTEM\"\u000B",
+"output":["ParseError", "ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "\u000B", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\"\\u000C",
+"input":"<!DOCTYPEa SYSTEM\"\u000C",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "\u000C", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\" ",
+"input":"<!DOCTYPEa SYSTEM\" ",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, " ", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\"!",
+"input":"<!DOCTYPEa SYSTEM\"!",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "!", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\"\"",
+"input":"<!DOCTYPEa SYSTEM\"\"",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\"#",
+"input":"<!DOCTYPEa SYSTEM\"#",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "#", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\"&",
+"input":"<!DOCTYPEa SYSTEM\"&",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "&", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\"'",
+"input":"<!DOCTYPEa SYSTEM\"'",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "'", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\"-",
+"input":"<!DOCTYPEa SYSTEM\"-",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "-", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\"/",
+"input":"<!DOCTYPEa SYSTEM\"/",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "/", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\"0",
+"input":"<!DOCTYPEa SYSTEM\"0",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "0", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\"1",
+"input":"<!DOCTYPEa SYSTEM\"1",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "1", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\"9",
+"input":"<!DOCTYPEa SYSTEM\"9",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "9", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\"<",
+"input":"<!DOCTYPEa SYSTEM\"<",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "<", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\"=",
+"input":"<!DOCTYPEa SYSTEM\"=",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "=", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\">",
+"input":"<!DOCTYPEa SYSTEM\">",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\"?",
+"input":"<!DOCTYPEa SYSTEM\"?",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "?", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\"@",
+"input":"<!DOCTYPEa SYSTEM\"@",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "@", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\"A",
+"input":"<!DOCTYPEa SYSTEM\"A",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "A", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\"B",
+"input":"<!DOCTYPEa SYSTEM\"B",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "B", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\"Y",
+"input":"<!DOCTYPEa SYSTEM\"Y",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "Y", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\"Z",
+"input":"<!DOCTYPEa SYSTEM\"Z",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "Z", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\"`",
+"input":"<!DOCTYPEa SYSTEM\"`",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "`", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\"a",
+"input":"<!DOCTYPEa SYSTEM\"a",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "a", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\"b",
+"input":"<!DOCTYPEa SYSTEM\"b",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "b", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\"y",
+"input":"<!DOCTYPEa SYSTEM\"y",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "y", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\"z",
+"input":"<!DOCTYPEa SYSTEM\"z",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "z", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\"{",
+"input":"<!DOCTYPEa SYSTEM\"{",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "{", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\"\\uDBC0\\uDC00",
+"input":"<!DOCTYPEa SYSTEM\"\uDBC0\uDC00",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "\uDBC0\uDC00", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM#",
+"input":"<!DOCTYPEa SYSTEM#",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEM&",
+"input":"<!DOCTYPEa SYSTEM&",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEM'",
+"input":"<!DOCTYPEa SYSTEM'",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM'\\u0000",
+"input":"<!DOCTYPEa SYSTEM'\u0000",
+"output":["ParseError", "ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "\uFFFD", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM'\\u0009",
+"input":"<!DOCTYPEa SYSTEM'\u0009",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "\u0009", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM'\\u000A",
+"input":"<!DOCTYPEa SYSTEM'\u000A",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "\u000A", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM'\\u000B",
+"input":"<!DOCTYPEa SYSTEM'\u000B",
+"output":["ParseError", "ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "\u000B", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM'\\u000C",
+"input":"<!DOCTYPEa SYSTEM'\u000C",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "\u000C", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM' ",
+"input":"<!DOCTYPEa SYSTEM' ",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, " ", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM'!",
+"input":"<!DOCTYPEa SYSTEM'!",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "!", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM'\"",
+"input":"<!DOCTYPEa SYSTEM'\"",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "\"", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM'&",
+"input":"<!DOCTYPEa SYSTEM'&",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "&", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM''",
+"input":"<!DOCTYPEa SYSTEM''",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM''\\u0000",
+"input":"<!DOCTYPEa SYSTEM''\u0000",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPEa SYSTEM''\\u0008",
+"input":"<!DOCTYPEa SYSTEM''\u0008",
+"output":["ParseError", "ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPEa SYSTEM''\\u0009",
+"input":"<!DOCTYPEa SYSTEM''\u0009",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM''\\u000A",
+"input":"<!DOCTYPEa SYSTEM''\u000A",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM''\\u000B",
+"input":"<!DOCTYPEa SYSTEM''\u000B",
+"output":["ParseError", "ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPEa SYSTEM''\\u000C",
+"input":"<!DOCTYPEa SYSTEM''\u000C",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM''\\u000D",
+"input":"<!DOCTYPEa SYSTEM''\u000D",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM''\\u001F",
+"input":"<!DOCTYPEa SYSTEM''\u001F",
+"output":["ParseError", "ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPEa SYSTEM'' ",
+"input":"<!DOCTYPEa SYSTEM'' ",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM''!",
+"input":"<!DOCTYPEa SYSTEM''!",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPEa SYSTEM''\"",
+"input":"<!DOCTYPEa SYSTEM''\"",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPEa SYSTEM''&",
+"input":"<!DOCTYPEa SYSTEM''&",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPEa SYSTEM'''",
+"input":"<!DOCTYPEa SYSTEM'''",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPEa SYSTEM''-",
+"input":"<!DOCTYPEa SYSTEM''-",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPEa SYSTEM''/",
+"input":"<!DOCTYPEa SYSTEM''/",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPEa SYSTEM''0",
+"input":"<!DOCTYPEa SYSTEM''0",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPEa SYSTEM''1",
+"input":"<!DOCTYPEa SYSTEM''1",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPEa SYSTEM''9",
+"input":"<!DOCTYPEa SYSTEM''9",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPEa SYSTEM''<",
+"input":"<!DOCTYPEa SYSTEM''<",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPEa SYSTEM''=",
+"input":"<!DOCTYPEa SYSTEM''=",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPEa SYSTEM''>",
+"input":"<!DOCTYPEa SYSTEM''>",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPEa SYSTEM''?",
+"input":"<!DOCTYPEa SYSTEM''?",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPEa SYSTEM''@",
+"input":"<!DOCTYPEa SYSTEM''@",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPEa SYSTEM''A",
+"input":"<!DOCTYPEa SYSTEM''A",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPEa SYSTEM''B",
+"input":"<!DOCTYPEa SYSTEM''B",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPEa SYSTEM''Y",
+"input":"<!DOCTYPEa SYSTEM''Y",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPEa SYSTEM''Z",
+"input":"<!DOCTYPEa SYSTEM''Z",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPEa SYSTEM''`",
+"input":"<!DOCTYPEa SYSTEM''`",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPEa SYSTEM''a",
+"input":"<!DOCTYPEa SYSTEM''a",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPEa SYSTEM''b",
+"input":"<!DOCTYPEa SYSTEM''b",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPEa SYSTEM''y",
+"input":"<!DOCTYPEa SYSTEM''y",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPEa SYSTEM''z",
+"input":"<!DOCTYPEa SYSTEM''z",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPEa SYSTEM''{",
+"input":"<!DOCTYPEa SYSTEM''{",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPEa SYSTEM''\\uDBC0\\uDC00",
+"input":"<!DOCTYPEa SYSTEM''\uDBC0\uDC00",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", true]]},
+
+{"description":"<!DOCTYPEa SYSTEM'(",
+"input":"<!DOCTYPEa SYSTEM'(",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "(", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM'-",
+"input":"<!DOCTYPEa SYSTEM'-",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "-", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM'/",
+"input":"<!DOCTYPEa SYSTEM'/",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "/", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM'0",
+"input":"<!DOCTYPEa SYSTEM'0",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "0", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM'1",
+"input":"<!DOCTYPEa SYSTEM'1",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "1", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM'9",
+"input":"<!DOCTYPEa SYSTEM'9",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "9", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM'<",
+"input":"<!DOCTYPEa SYSTEM'<",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "<", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM'=",
+"input":"<!DOCTYPEa SYSTEM'=",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "=", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM'>",
+"input":"<!DOCTYPEa SYSTEM'>",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM'?",
+"input":"<!DOCTYPEa SYSTEM'?",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "?", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM'@",
+"input":"<!DOCTYPEa SYSTEM'@",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "@", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM'A",
+"input":"<!DOCTYPEa SYSTEM'A",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "A", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM'B",
+"input":"<!DOCTYPEa SYSTEM'B",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "B", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM'Y",
+"input":"<!DOCTYPEa SYSTEM'Y",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "Y", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM'Z",
+"input":"<!DOCTYPEa SYSTEM'Z",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "Z", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM'`",
+"input":"<!DOCTYPEa SYSTEM'`",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "`", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM'a",
+"input":"<!DOCTYPEa SYSTEM'a",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "a", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM'b",
+"input":"<!DOCTYPEa SYSTEM'b",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "b", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM'y",
+"input":"<!DOCTYPEa SYSTEM'y",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "y", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM'z",
+"input":"<!DOCTYPEa SYSTEM'z",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "z", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM'{",
+"input":"<!DOCTYPEa SYSTEM'{",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "{", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM'\\uDBC0\\uDC00",
+"input":"<!DOCTYPEa SYSTEM'\uDBC0\uDC00",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, "\uDBC0\uDC00", false]]},
+
+{"description":"<!DOCTYPEa SYSTEM(",
+"input":"<!DOCTYPEa SYSTEM(",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEM-",
+"input":"<!DOCTYPEa SYSTEM-",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEM/",
+"input":"<!DOCTYPEa SYSTEM/",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEM0",
+"input":"<!DOCTYPEa SYSTEM0",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEM1",
+"input":"<!DOCTYPEa SYSTEM1",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEM9",
+"input":"<!DOCTYPEa SYSTEM9",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEM<",
+"input":"<!DOCTYPEa SYSTEM<",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEM=",
+"input":"<!DOCTYPEa SYSTEM=",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEM>",
+"input":"<!DOCTYPEa SYSTEM>",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEM?",
+"input":"<!DOCTYPEa SYSTEM?",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEM@",
+"input":"<!DOCTYPEa SYSTEM@",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEMA",
+"input":"<!DOCTYPEa SYSTEMA",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEMB",
+"input":"<!DOCTYPEa SYSTEMB",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEMY",
+"input":"<!DOCTYPEa SYSTEMY",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEMZ",
+"input":"<!DOCTYPEa SYSTEMZ",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEM`",
+"input":"<!DOCTYPEa SYSTEM`",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEMa",
+"input":"<!DOCTYPEa SYSTEMa",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEMb",
+"input":"<!DOCTYPEa SYSTEMb",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEMy",
+"input":"<!DOCTYPEa SYSTEMy",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEMz",
+"input":"<!DOCTYPEa SYSTEMz",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEM{",
+"input":"<!DOCTYPEa SYSTEM{",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa SYSTEM\\uDBC0\\uDC00",
+"input":"<!DOCTYPEa SYSTEM\uDBC0\uDC00",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa Y",
+"input":"<!DOCTYPEa Y",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa Z",
+"input":"<!DOCTYPEa Z",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa `",
+"input":"<!DOCTYPEa `",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa a",
+"input":"<!DOCTYPEa a",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa a\\u0000",
+"input":"<!DOCTYPEa a\u0000",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa a\\u0009",
+"input":"<!DOCTYPEa a\u0009",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa a\\u000A",
+"input":"<!DOCTYPEa a\u000A",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa a\\u000B",
+"input":"<!DOCTYPEa a\u000B",
+"output":["ParseError", "ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa a\\u000C",
+"input":"<!DOCTYPEa a\u000C",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa a ",
+"input":"<!DOCTYPEa a ",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa a!",
+"input":"<!DOCTYPEa a!",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa a\"",
+"input":"<!DOCTYPEa a\"",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa a&",
+"input":"<!DOCTYPEa a&",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa a'",
+"input":"<!DOCTYPEa a'",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa a-",
+"input":"<!DOCTYPEa a-",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa a/",
+"input":"<!DOCTYPEa a/",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa a0",
+"input":"<!DOCTYPEa a0",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa a1",
+"input":"<!DOCTYPEa a1",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa a9",
+"input":"<!DOCTYPEa a9",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa a<",
+"input":"<!DOCTYPEa a<",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa a=",
+"input":"<!DOCTYPEa a=",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa a>",
+"input":"<!DOCTYPEa a>",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa a?",
+"input":"<!DOCTYPEa a?",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa a@",
+"input":"<!DOCTYPEa a@",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa aA",
+"input":"<!DOCTYPEa aA",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa aB",
+"input":"<!DOCTYPEa aB",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa aY",
+"input":"<!DOCTYPEa aY",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa aZ",
+"input":"<!DOCTYPEa aZ",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa a`",
+"input":"<!DOCTYPEa a`",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa aa",
+"input":"<!DOCTYPEa aa",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa ab",
+"input":"<!DOCTYPEa ab",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa ay",
+"input":"<!DOCTYPEa ay",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa az",
+"input":"<!DOCTYPEa az",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa a{",
+"input":"<!DOCTYPEa a{",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa a\\uDBC0\\uDC00",
+"input":"<!DOCTYPEa a\uDBC0\uDC00",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa b",
+"input":"<!DOCTYPEa b",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa y",
+"input":"<!DOCTYPEa y",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa z",
+"input":"<!DOCTYPEa z",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa {",
+"input":"<!DOCTYPEa {",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa \\uDBC0\\uDC00",
+"input":"<!DOCTYPEa \uDBC0\uDC00",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a", null, null, false]]},
+
+{"description":"<!DOCTYPEa!",
+"input":"<!DOCTYPEa!",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a!", null, null, false]]},
+
+{"description":"<!DOCTYPEa\"",
+"input":"<!DOCTYPEa\"",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a\"", null, null, false]]},
+
+{"description":"<!DOCTYPEa&",
+"input":"<!DOCTYPEa&",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a&", null, null, false]]},
+
+{"description":"<!DOCTYPEa'",
+"input":"<!DOCTYPEa'",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a'", null, null, false]]},
+
+{"description":"<!DOCTYPEa-",
+"input":"<!DOCTYPEa-",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a-", null, null, false]]},
+
+{"description":"<!DOCTYPEa/",
+"input":"<!DOCTYPEa/",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a/", null, null, false]]},
+
+{"description":"<!DOCTYPEa0",
+"input":"<!DOCTYPEa0",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a0", null, null, false]]},
+
+{"description":"<!DOCTYPEa1",
+"input":"<!DOCTYPEa1",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a1", null, null, false]]},
+
+{"description":"<!DOCTYPEa9",
+"input":"<!DOCTYPEa9",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a9", null, null, false]]},
+
+{"description":"<!DOCTYPEa<",
+"input":"<!DOCTYPEa<",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a<", null, null, false]]},
+
+{"description":"<!DOCTYPEa=",
+"input":"<!DOCTYPEa=",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a=", null, null, false]]},
+
+{"description":"<!DOCTYPEa>",
+"input":"<!DOCTYPEa>",
+"output":["ParseError", ["DOCTYPE", "a", null, null, true]]},
+
+{"description":"<!DOCTYPEa?",
+"input":"<!DOCTYPEa?",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a?", null, null, false]]},
+
+{"description":"<!DOCTYPEa@",
+"input":"<!DOCTYPEa@",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a@", null, null, false]]},
+
+{"description":"<!DOCTYPEaA",
+"input":"<!DOCTYPEaA",
+"output":["ParseError", "ParseError", ["DOCTYPE", "aa", null, null, false]]},
+
+{"description":"<!DOCTYPEaB",
+"input":"<!DOCTYPEaB",
+"output":["ParseError", "ParseError", ["DOCTYPE", "ab", null, null, false]]},
+
+{"description":"<!DOCTYPEaY",
+"input":"<!DOCTYPEaY",
+"output":["ParseError", "ParseError", ["DOCTYPE", "ay", null, null, false]]},
+
+{"description":"<!DOCTYPEaZ",
+"input":"<!DOCTYPEaZ",
+"output":["ParseError", "ParseError", ["DOCTYPE", "az", null, null, false]]},
+
+{"description":"<!DOCTYPEa[",
+"input":"<!DOCTYPEa[",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a[", null, null, false]]},
+
+{"description":"<!DOCTYPEa`",
+"input":"<!DOCTYPEa`",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a`", null, null, false]]},
+
+{"description":"<!DOCTYPEaa",
+"input":"<!DOCTYPEaa",
+"output":["ParseError", "ParseError", ["DOCTYPE", "aa", null, null, false]]},
+
+{"description":"<!DOCTYPEab",
+"input":"<!DOCTYPEab",
+"output":["ParseError", "ParseError", ["DOCTYPE", "ab", null, null, false]]},
+
+{"description":"<!DOCTYPEay",
+"input":"<!DOCTYPEay",
+"output":["ParseError", "ParseError", ["DOCTYPE", "ay", null, null, false]]},
+
+{"description":"<!DOCTYPEaz",
+"input":"<!DOCTYPEaz",
+"output":["ParseError", "ParseError", ["DOCTYPE", "az", null, null, false]]},
+
+{"description":"<!DOCTYPEa{",
+"input":"<!DOCTYPEa{",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a{", null, null, false]]},
+
+{"description":"<!DOCTYPEa\\uDBC0\\uDC00",
+"input":"<!DOCTYPEa\uDBC0\uDC00",
+"output":["ParseError", "ParseError", ["DOCTYPE", "a\uDBC0\uDC00", null, null, false]]},
+
+{"description":"<!DOCTYPEb",
+"input":"<!DOCTYPEb",
+"output":["ParseError", "ParseError", ["DOCTYPE", "b", null, null, false]]},
+
+{"description":"<!DOCTYPEy",
+"input":"<!DOCTYPEy",
+"output":["ParseError", "ParseError", ["DOCTYPE", "y", null, null, false]]},
+
+{"description":"<!DOCTYPEz",
+"input":"<!DOCTYPEz",
+"output":["ParseError", "ParseError", ["DOCTYPE", "z", null, null, false]]},
+
+{"description":"<!DOCTYPE{",
+"input":"<!DOCTYPE{",
+"output":["ParseError", "ParseError", ["DOCTYPE", "{", null, null, false]]},
+
+{"description":"<!DOCTYPE\\uDBC0\\uDC00",
+"input":"<!DOCTYPE\uDBC0\uDC00",
+"output":["ParseError", "ParseError", ["DOCTYPE", "\uDBC0\uDC00", null, null, false]]},
+
+{"description":"<!Y",
+"input":"<!Y",
+"output":["ParseError", ["Comment", "Y"]]},
+
+{"description":"<!Z",
+"input":"<!Z",
+"output":["ParseError", ["Comment", "Z"]]},
+
+{"description":"<!`",
+"input":"<!`",
+"output":["ParseError", ["Comment", "`"]]},
+
+{"description":"<!a",
+"input":"<!a",
+"output":["ParseError", ["Comment", "a"]]},
+
+{"description":"<!b",
+"input":"<!b",
+"output":["ParseError", ["Comment", "b"]]},
+
+{"description":"<!y",
+"input":"<!y",
+"output":["ParseError", ["Comment", "y"]]},
+
+{"description":"<!z",
+"input":"<!z",
+"output":["ParseError", ["Comment", "z"]]},
+
+{"description":"<!{",
+"input":"<!{",
+"output":["ParseError", ["Comment", "{"]]},
+
+{"description":"<!\\uDBC0\\uDC00",
+"input":"<!\uDBC0\uDC00",
+"output":["ParseError", ["Comment", "\uDBC0\uDC00"]]},
+
+{"description":"<\"",
+"input":"<\"",
+"output":["ParseError", ["Character", "<\""]]},
+
+{"description":"<&",
+"input":"<&",
+"output":["ParseError", ["Character", "<&"]]},
+
+{"description":"<'",
+"input":"<'",
+"output":["ParseError", ["Character", "<'"]]},
+
+{"description":"<-",
+"input":"<-",
+"output":["ParseError", ["Character", "<-"]]},
+
+{"description":"<.",
+"input":"<.",
+"output":["ParseError", ["Character", "<."]]},
+
+{"description":"</",
+"input":"</",
+"output":["ParseError", ["Character", "</"]]},
+
+{"description":"</\\u0000",
+"input":"</\u0000",
+"output":["ParseError", ["Comment", "\uFFFD"]]},
+
+{"description":"</\\u0009",
+"input":"</\u0009",
+"output":["ParseError", ["Comment", "\u0009"]]},
+
+{"description":"</\\u000A",
+"input":"</\u000A",
+"output":["ParseError", ["Comment", "\u000A"]]},
+
+{"description":"</\\u000B",
+"input":"</\u000B",
+"output":["ParseError", "ParseError", ["Comment", "\u000B"]]},
+
+{"description":"</\\u000C",
+"input":"</\u000C",
+"output":["ParseError", ["Comment", "\u000C"]]},
+
+{"description":"</ ",
+"input":"</ ",
+"output":["ParseError", ["Comment", " "]]},
+
+{"description":"</!",
+"input":"</!",
+"output":["ParseError", ["Comment", "!"]]},
+
+{"description":"</\"",
+"input":"</\"",
+"output":["ParseError", ["Comment", "\""]]},
+
+{"description":"</&",
+"input":"</&",
+"output":["ParseError", ["Comment", "&"]]},
+
+{"description":"</'",
+"input":"</'",
+"output":["ParseError", ["Comment", "'"]]},
+
+{"description":"</-",
+"input":"</-",
+"output":["ParseError", ["Comment", "-"]]},
+
+{"description":"<//",
+"input":"<//",
+"output":["ParseError", ["Comment", "/"]]},
+
+{"description":"</0",
+"input":"</0",
+"output":["ParseError", ["Comment", "0"]]},
+
+{"description":"</1",
+"input":"</1",
+"output":["ParseError", ["Comment", "1"]]},
+
+{"description":"</9",
+"input":"</9",
+"output":["ParseError", ["Comment", "9"]]},
+
+{"description":"</<",
+"input":"</<",
+"output":["ParseError", ["Comment", "<"]]},
+
+{"description":"</=",
+"input":"</=",
+"output":["ParseError", ["Comment", "="]]},
+
+{"description":"</>",
+"input":"</>",
+"output":["ParseError"]},
+
+{"description":"</?",
+"input":"</?",
+"output":["ParseError", ["Comment", "?"]]},
+
+{"description":"</@",
+"input":"</@",
+"output":["ParseError", ["Comment", "@"]]},
+
+{"description":"</A>",
+"input":"</A>",
+"output":[["EndTag", "a"]]},
+
+{"description":"</B>",
+"input":"</B>",
+"output":[["EndTag", "b"]]},
+
+{"description":"</Y>",
+"input":"</Y>",
+"output":[["EndTag", "y"]]},
+
+{"description":"</Z>",
+"input":"</Z>",
+"output":[["EndTag", "z"]]},
+
+{"description":"</[",
+"input":"</[",
+"output":["ParseError", ["Comment", "["]]},
+
+{"description":"</`",
+"input":"</`",
+"output":["ParseError", ["Comment", "`"]]},
+
+{"description":"</a>",
+"input":"</a>",
+"output":[["EndTag", "a"]]},
+
+{"description":"</b>",
+"input":"</b>",
+"output":[["EndTag", "b"]]},
+
+{"description":"</y>",
+"input":"</y>",
+"output":[["EndTag", "y"]]},
+
+{"description":"</z>",
+"input":"</z>",
+"output":[["EndTag", "z"]]},
+
+{"description":"</{",
+"input":"</{",
+"output":["ParseError", ["Comment", "{"]]},
+
+{"description":"</\\uDBC0\\uDC00",
+"input":"</\uDBC0\uDC00",
+"output":["ParseError", ["Comment", "\uDBC0\uDC00"]]},
+
+{"description":"<0",
+"input":"<0",
+"output":["ParseError", ["Character", "<0"]]},
+
+{"description":"<1",
+"input":"<1",
+"output":["ParseError", ["Character", "<1"]]},
+
+{"description":"<9",
+"input":"<9",
+"output":["ParseError", ["Character", "<9"]]},
+
+{"description":"<<",
+"input":"<<",
+"output":["ParseError", ["Character", "<"], "ParseError", ["Character", "<"]]},
+
+{"description":"<=",
+"input":"<=",
+"output":["ParseError", ["Character", "<="]]},
+
+{"description":"<>",
+"input":"<>",
+"output":["ParseError", ["Character", "<>"]]},
+
+{"description":"<?",
+"input":"<?",
+"output":["ParseError", ["Comment", "?"]]},
+
+{"description":"<?\\u0000",
+"input":"<?\u0000",
+"output":["ParseError", ["Comment", "?\uFFFD"]]},
+
+{"description":"<?\\u0009",
+"input":"<?\u0009",
+"output":["ParseError", ["Comment", "?\u0009"]]},
+
+{"description":"<?\\u000A",
+"input":"<?\u000A",
+"output":["ParseError", ["Comment", "?\u000A"]]},
+
+{"description":"<?\\u000B",
+"input":"<?\u000B",
+"output":["ParseError", "ParseError", ["Comment", "?\u000B"]]},
+
+{"description":"<?\\u000C",
+"input":"<?\u000C",
+"output":["ParseError", ["Comment", "?\u000C"]]},
+
+{"description":"<? ",
+"input":"<? ",
+"output":["ParseError", ["Comment", "? "]]},
+
+{"description":"<?!",
+"input":"<?!",
+"output":["ParseError", ["Comment", "?!"]]},
+
+{"description":"<?\"",
+"input":"<?\"",
+"output":["ParseError", ["Comment", "?\""]]},
+
+{"description":"<?&",
+"input":"<?&",
+"output":["ParseError", ["Comment", "?&"]]},
+
+{"description":"<?'",
+"input":"<?'",
+"output":["ParseError", ["Comment", "?'"]]},
+
+{"description":"<?-",
+"input":"<?-",
+"output":["ParseError", ["Comment", "?-"]]},
+
+{"description":"<?/",
+"input":"<?/",
+"output":["ParseError", ["Comment", "?/"]]},
+
+{"description":"<?0",
+"input":"<?0",
+"output":["ParseError", ["Comment", "?0"]]},
+
+{"description":"<?1",
+"input":"<?1",
+"output":["ParseError", ["Comment", "?1"]]},
+
+{"description":"<?9",
+"input":"<?9",
+"output":["ParseError", ["Comment", "?9"]]},
+
+{"description":"<?<",
+"input":"<?<",
+"output":["ParseError", ["Comment", "?<"]]},
+
+{"description":"<?=",
+"input":"<?=",
+"output":["ParseError", ["Comment", "?="]]},
+
+{"description":"<?>",
+"input":"<?>",
+"output":["ParseError", ["Comment", "?"]]},
+
+{"description":"<??",
+"input":"<??",
+"output":["ParseError", ["Comment", "??"]]},
+
+{"description":"<?@",
+"input":"<?@",
+"output":["ParseError", ["Comment", "?@"]]},
+
+{"description":"<?A",
+"input":"<?A",
+"output":["ParseError", ["Comment", "?A"]]},
+
+{"description":"<?B",
+"input":"<?B",
+"output":["ParseError", ["Comment", "?B"]]},
+
+{"description":"<?Y",
+"input":"<?Y",
+"output":["ParseError", ["Comment", "?Y"]]},
+
+{"description":"<?Z",
+"input":"<?Z",
+"output":["ParseError", ["Comment", "?Z"]]},
+
+{"description":"<?`",
+"input":"<?`",
+"output":["ParseError", ["Comment", "?`"]]},
+
+{"description":"<?a",
+"input":"<?a",
+"output":["ParseError", ["Comment", "?a"]]},
+
+{"description":"<?b",
+"input":"<?b",
+"output":["ParseError", ["Comment", "?b"]]},
+
+{"description":"<?y",
+"input":"<?y",
+"output":["ParseError", ["Comment", "?y"]]},
+
+{"description":"<?z",
+"input":"<?z",
+"output":["ParseError", ["Comment", "?z"]]},
+
+{"description":"<?{",
+"input":"<?{",
+"output":["ParseError", ["Comment", "?{"]]},
+
+{"description":"<?\\uDBC0\\uDC00",
+"input":"<?\uDBC0\uDC00",
+"output":["ParseError", ["Comment", "?\uDBC0\uDC00"]]},
+
+{"description":"<@",
+"input":"<@",
+"output":["ParseError", ["Character", "<@"]]},
+
+{"description":"<A>",
+"input":"<A>",
+"output":[["StartTag", "a", {}]]},
+
+{"description":"<B>",
+"input":"<B>",
+"output":[["StartTag", "b", {}]]},
+
+{"description":"<Y>",
+"input":"<Y>",
+"output":[["StartTag", "y", {}]]},
+
+{"description":"<Z>",
+"input":"<Z>",
+"output":[["StartTag", "z", {}]]},
+
+{"description":"<[",
+"input":"<[",
+"output":["ParseError", ["Character", "<["]]},
+
+{"description":"<`",
+"input":"<`",
+"output":["ParseError", ["Character", "<`"]]},
+
+{"description":"<a>",
+"input":"<a>",
+"output":[["StartTag", "a", {}]]},
+
+{"description":"<a\\u0000>",
+"input":"<a\u0000>",
+"output":["ParseError", ["StartTag", "a\uFFFD", {}]]},
+
+{"description":"<a\\u0008>",
+"input":"<a\u0008>",
+"output":["ParseError", ["StartTag", "a\u0008", {}]]},
+
+{"description":"<a\\u0009>",
+"input":"<a\u0009>",
+"output":[["StartTag", "a", {}]]},
+
+{"description":"<a\\u000A>",
+"input":"<a\u000A>",
+"output":[["StartTag", "a", {}]]},
+
+{"description":"<a\\u000B>",
+"input":"<a\u000B>",
+"output":["ParseError", ["StartTag", "a\u000B", {}]]},
+
+{"description":"<a\\u000C>",
+"input":"<a\u000C>",
+"output":[["StartTag", "a", {}]]},
+
+{"description":"<a\\u000D>",
+"input":"<a\u000D>",
+"output":[["StartTag", "a", {}]]},
+
+{"description":"<a\\u001F>",
+"input":"<a\u001F>",
+"output":["ParseError", ["StartTag", "a\u001F", {}]]},
+
+{"description":"<a >",
+"input":"<a >",
+"output":[["StartTag", "a", {}]]},
+
+{"description":"<a \\u0000>",
+"input":"<a \u0000>",
+"output":["ParseError", ["StartTag", "a", {"\uFFFD":""}]]},
+
+{"description":"<a \\u0008>",
+"input":"<a \u0008>",
+"output":["ParseError", ["StartTag", "a", {"\u0008":""}]]},
+
+{"description":"<a \\u0009>",
+"input":"<a \u0009>",
+"output":[["StartTag", "a", {}]]},
+
+{"description":"<a \\u000A>",
+"input":"<a \u000A>",
+"output":[["StartTag", "a", {}]]},
+
+{"description":"<a \\u000B>",
+"input":"<a \u000B>",
+"output":["ParseError", ["StartTag", "a", {"\u000B":""}]]},
+
+{"description":"<a \\u000C>",
+"input":"<a \u000C>",
+"output":[["StartTag", "a", {}]]},
+
+{"description":"<a \\u000D>",
+"input":"<a \u000D>",
+"output":[["StartTag", "a", {}]]},
+
+{"description":"<a \\u001F>",
+"input":"<a \u001F>",
+"output":["ParseError", ["StartTag", "a", {"\u001F":""}]]},
+
+{"description":"<a >",
+"input":"<a >",
+"output":[["StartTag", "a", {}]]},
+
+{"description":"<a !>",
+"input":"<a !>",
+"output":[["StartTag", "a", {"!":""}]]},
+
+{"description":"<a \">",
+"input":"<a \">",
+"output":["ParseError", ["StartTag", "a", {"\"":""}]]},
+
+{"description":"<a #>",
+"input":"<a #>",
+"output":[["StartTag", "a", {"#":""}]]},
+
+{"description":"<a &>",
+"input":"<a &>",
+"output":[["StartTag", "a", {"&":""}]]},
+
+{"description":"<a '>",
+"input":"<a '>",
+"output":["ParseError", ["StartTag", "a", {"'":""}]]},
+
+{"description":"<a (>",
+"input":"<a (>",
+"output":[["StartTag", "a", {"(":""}]]},
+
+{"description":"<a ->",
+"input":"<a ->",
+"output":[["StartTag", "a", {"-":""}]]},
+
+{"description":"<a .>",
+"input":"<a .>",
+"output":[["StartTag", "a", {".":""}]]},
+
+{"description":"<a />",
+"input":"<a />",
+"output":[["StartTag", "a", {}, true]]},
+
+{"description":"<a 0>",
+"input":"<a 0>",
+"output":[["StartTag", "a", {"0":""}]]},
+
+{"description":"<a 1>",
+"input":"<a 1>",
+"output":[["StartTag", "a", {"1":""}]]},
+
+{"description":"<a 9>",
+"input":"<a 9>",
+"output":[["StartTag", "a", {"9":""}]]},
+
+{"description":"<a <>",
+"input":"<a <>",
+"output":["ParseError", ["StartTag", "a", {"<":""}]]},
+
+{"description":"<a =>",
+"input":"<a =>",
+"output":["ParseError", ["StartTag", "a", {"=":""}]]},
+
+{"description":"<a >",
+"input":"<a >",
+"output":[["StartTag", "a", {}]]},
+
+{"description":"<a ?>",
+"input":"<a ?>",
+"output":[["StartTag", "a", {"?":""}]]},
+
+{"description":"<a @>",
+"input":"<a @>",
+"output":[["StartTag", "a", {"@":""}]]},
+
+{"description":"<a A>",
+"input":"<a A>",
+"output":[["StartTag", "a", {"a":""}]]},
+
+{"description":"<a B>",
+"input":"<a B>",
+"output":[["StartTag", "a", {"b":""}]]},
+
+{"description":"<a Y>",
+"input":"<a Y>",
+"output":[["StartTag", "a", {"y":""}]]},
+
+{"description":"<a Z>",
+"input":"<a Z>",
+"output":[["StartTag", "a", {"z":""}]]},
+
+{"description":"<a [>",
+"input":"<a [>",
+"output":[["StartTag", "a", {"[":""}]]},
+
+{"description":"<a `>",
+"input":"<a `>",
+"output":[["StartTag", "a", {"`":""}]]},
+
+{"description":"<a a>",
+"input":"<a a>",
+"output":[["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a\\u0000>",
+"input":"<a a\u0000>",
+"output":["ParseError", ["StartTag", "a", {"a\uFFFD":""}]]},
+
+{"description":"<a a\\u0008>",
+"input":"<a a\u0008>",
+"output":["ParseError", ["StartTag", "a", {"a\u0008":""}]]},
+
+{"description":"<a a\\u0009>",
+"input":"<a a\u0009>",
+"output":[["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a\\u000A>",
+"input":"<a a\u000A>",
+"output":[["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a\\u000B>",
+"input":"<a a\u000B>",
+"output":["ParseError", ["StartTag", "a", {"a\u000B":""}]]},
+
+{"description":"<a a\\u000C>",
+"input":"<a a\u000C>",
+"output":[["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a\\u000D>",
+"input":"<a a\u000D>",
+"output":[["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a\\u001F>",
+"input":"<a a\u001F>",
+"output":["ParseError", ["StartTag", "a", {"a\u001F":""}]]},
+
+{"description":"<a a >",
+"input":"<a a >",
+"output":[["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a \\u0000>",
+"input":"<a a \u0000>",
+"output":["ParseError", ["StartTag", "a", {"a":"", "\uFFFD":""}]]},
+
+{"description":"<a a \\u0008>",
+"input":"<a a \u0008>",
+"output":["ParseError", ["StartTag", "a", {"a":"", "\u0008":""}]]},
+
+{"description":"<a a \\u0009>",
+"input":"<a a \u0009>",
+"output":[["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a \\u000A>",
+"input":"<a a \u000A>",
+"output":[["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a \\u000B>",
+"input":"<a a \u000B>",
+"output":["ParseError", ["StartTag", "a", {"a":"", "\u000B":""}]]},
+
+{"description":"<a a \\u000C>",
+"input":"<a a \u000C>",
+"output":[["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a \\u000D>",
+"input":"<a a \u000D>",
+"output":[["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a \\u001F>",
+"input":"<a a \u001F>",
+"output":["ParseError", ["StartTag", "a", {"a":"", "\u001F":""}]]},
+
+{"description":"<a a >",
+"input":"<a a >",
+"output":[["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a !>",
+"input":"<a a !>",
+"output":[["StartTag", "a", {"a":"", "!":""}]]},
+
+{"description":"<a a \">",
+"input":"<a a \">",
+"output":["ParseError", ["StartTag", "a", {"a":"", "\"":""}]]},
+
+{"description":"<a a #>",
+"input":"<a a #>",
+"output":[["StartTag", "a", {"a":"", "#":""}]]},
+
+{"description":"<a a &>",
+"input":"<a a &>",
+"output":[["StartTag", "a", {"a":"", "&":""}]]},
+
+{"description":"<a a '>",
+"input":"<a a '>",
+"output":["ParseError", ["StartTag", "a", {"a":"", "'":""}]]},
+
+{"description":"<a a (>",
+"input":"<a a (>",
+"output":[["StartTag", "a", {"a":"", "(":""}]]},
+
+{"description":"<a a ->",
+"input":"<a a ->",
+"output":[["StartTag", "a", {"a":"", "-":""}]]},
+
+{"description":"<a a .>",
+"input":"<a a .>",
+"output":[["StartTag", "a", {"a":"", ".":""}]]},
+
+{"description":"<a a />",
+"input":"<a a />",
+"output":[["StartTag", "a", {"a":""}, true]]},
+
+{"description":"<a a 0>",
+"input":"<a a 0>",
+"output":[["StartTag", "a", {"a":"", "0":""}]]},
+
+{"description":"<a a 1>",
+"input":"<a a 1>",
+"output":[["StartTag", "a", {"a":"", "1":""}]]},
+
+{"description":"<a a 9>",
+"input":"<a a 9>",
+"output":[["StartTag", "a", {"a":"", "9":""}]]},
+
+{"description":"<a a <>",
+"input":"<a a <>",
+"output":["ParseError", ["StartTag", "a", {"a":"", "<":""}]]},
+
+{"description":"<a a =>",
+"input":"<a a =>",
+"output":["ParseError", ["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a >",
+"input":"<a a >",
+"output":[["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a ?>",
+"input":"<a a ?>",
+"output":[["StartTag", "a", {"a":"", "?":""}]]},
+
+{"description":"<a a @>",
+"input":"<a a @>",
+"output":[["StartTag", "a", {"a":"", "@":""}]]},
+
+{"description":"<a a A>",
+"input":"<a a A>",
+"output":["ParseError", ["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a B>",
+"input":"<a a B>",
+"output":[["StartTag", "a", {"a":"", "b":""}]]},
+
+{"description":"<a a Y>",
+"input":"<a a Y>",
+"output":[["StartTag", "a", {"a":"", "y":""}]]},
+
+{"description":"<a a Z>",
+"input":"<a a Z>",
+"output":[["StartTag", "a", {"a":"", "z":""}]]},
+
+{"description":"<a a [>",
+"input":"<a a [>",
+"output":[["StartTag", "a", {"a":"", "[":""}]]},
+
+{"description":"<a a `>",
+"input":"<a a `>",
+"output":[["StartTag", "a", {"a":"", "`":""}]]},
+
+{"description":"<a a a>",
+"input":"<a a a>",
+"output":["ParseError", ["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a b>",
+"input":"<a a b>",
+"output":[["StartTag", "a", {"a":"", "b":""}]]},
+
+{"description":"<a a y>",
+"input":"<a a y>",
+"output":[["StartTag", "a", {"a":"", "y":""}]]},
+
+{"description":"<a a z>",
+"input":"<a a z>",
+"output":[["StartTag", "a", {"a":"", "z":""}]]},
+
+{"description":"<a a {>",
+"input":"<a a {>",
+"output":[["StartTag", "a", {"a":"", "{":""}]]},
+
+{"description":"<a a \\uDBC0\\uDC00>",
+"input":"<a a \uDBC0\uDC00>",
+"output":[["StartTag", "a", {"a":"", "\uDBC0\uDC00":""}]]},
+
+{"description":"<a a!>",
+"input":"<a a!>",
+"output":[["StartTag", "a", {"a!":""}]]},
+
+{"description":"<a a\">",
+"input":"<a a\">",
+"output":["ParseError", ["StartTag", "a", {"a\"":""}]]},
+
+{"description":"<a a#>",
+"input":"<a a#>",
+"output":[["StartTag", "a", {"a#":""}]]},
+
+{"description":"<a a&>",
+"input":"<a a&>",
+"output":[["StartTag", "a", {"a&":""}]]},
+
+{"description":"<a a'>",
+"input":"<a a'>",
+"output":["ParseError", ["StartTag", "a", {"a'":""}]]},
+
+{"description":"<a a(>",
+"input":"<a a(>",
+"output":[["StartTag", "a", {"a(":""}]]},
+
+{"description":"<a a->",
+"input":"<a a->",
+"output":[["StartTag", "a", {"a-":""}]]},
+
+{"description":"<a a.>",
+"input":"<a a.>",
+"output":[["StartTag", "a", {"a.":""}]]},
+
+{"description":"<a a/>",
+"input":"<a a/>",
+"output":[["StartTag", "a", {"a":""}, true]]},
+
+{"description":"<a a0>",
+"input":"<a a0>",
+"output":[["StartTag", "a", {"a0":""}]]},
+
+{"description":"<a a1>",
+"input":"<a a1>",
+"output":[["StartTag", "a", {"a1":""}]]},
+
+{"description":"<a a9>",
+"input":"<a a9>",
+"output":[["StartTag", "a", {"a9":""}]]},
+
+{"description":"<a a<>",
+"input":"<a a<>",
+"output":["ParseError", ["StartTag", "a", {"a<":""}]]},
+
+{"description":"<a a=>",
+"input":"<a a=>",
+"output":["ParseError", ["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a=\\u0000>",
+"input":"<a a=\u0000>",
+"output":["ParseError", ["StartTag", "a", {"a":"\uFFFD"}]]},
+
+{"description":"<a a=\\u0008>",
+"input":"<a a=\u0008>",
+"output":["ParseError", ["StartTag", "a", {"a":"\u0008"}]]},
+
+{"description":"<a a=\\u0009>",
+"input":"<a a=\u0009>",
+"output":["ParseError", ["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a=\\u000A>",
+"input":"<a a=\u000A>",
+"output":["ParseError", ["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a=\\u000B>",
+"input":"<a a=\u000B>",
+"output":["ParseError", ["StartTag", "a", {"a":"\u000B"}]]},
+
+{"description":"<a a=\\u000C>",
+"input":"<a a=\u000C>",
+"output":["ParseError", ["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a=\\u000D>",
+"input":"<a a=\u000D>",
+"output":["ParseError", ["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a=\\u001F>",
+"input":"<a a=\u001F>",
+"output":["ParseError", ["StartTag", "a", {"a":"\u001F"}]]},
+
+{"description":"<a a= >",
+"input":"<a a= >",
+"output":["ParseError", ["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a=!>",
+"input":"<a a=!>",
+"output":[["StartTag", "a", {"a":"!"}]]},
+
+{"description":"<a a=\"\">",
+"input":"<a a=\"\">",
+"output":[["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a=\"\\u0000\">",
+"input":"<a a=\"\u0000\">",
+"output":["ParseError", ["StartTag", "a", {"a":"\uFFFD"}]]},
+
+{"description":"<a a=\"\\u0009\">",
+"input":"<a a=\"\u0009\">",
+"output":[["StartTag", "a", {"a":"\u0009"}]]},
+
+{"description":"<a a=\"\\u000A\">",
+"input":"<a a=\"\u000A\">",
+"output":[["StartTag", "a", {"a":"\u000A"}]]},
+
+{"description":"<a a=\"\\u000B\">",
+"input":"<a a=\"\u000B\">",
+"output":["ParseError", ["StartTag", "a", {"a":"\u000B"}]]},
+
+{"description":"<a a=\"\\u000C\">",
+"input":"<a a=\"\u000C\">",
+"output":[["StartTag", "a", {"a":"\u000C"}]]},
+
+{"description":"<a a=\" \">",
+"input":"<a a=\" \">",
+"output":[["StartTag", "a", {"a":" "}]]},
+
+{"description":"<a a=\"!\">",
+"input":"<a a=\"!\">",
+"output":[["StartTag", "a", {"a":"!"}]]},
+
+{"description":"<a a=\"\">",
+"input":"<a a=\"\">",
+"output":[["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a=\"#\">",
+"input":"<a a=\"#\">",
+"output":[["StartTag", "a", {"a":"#"}]]},
+
+{"description":"<a a=\"%\">",
+"input":"<a a=\"%\">",
+"output":[["StartTag", "a", {"a":"%"}]]},
+
+{"description":"<a a=\"&\">",
+"input":"<a a=\"&\">",
+"output":[["StartTag", "a", {"a":"&"}]]},
+
+{"description":"<a a=\"'\">",
+"input":"<a a=\"'\">",
+"output":[["StartTag", "a", {"a":"'"}]]},
+
+{"description":"<a a=\"-\">",
+"input":"<a a=\"-\">",
+"output":[["StartTag", "a", {"a":"-"}]]},
+
+{"description":"<a a=\"/\">",
+"input":"<a a=\"/\">",
+"output":[["StartTag", "a", {"a":"/"}]]},
+
+{"description":"<a a=\"0\">",
+"input":"<a a=\"0\">",
+"output":[["StartTag", "a", {"a":"0"}]]},
+
+{"description":"<a a=\"1\">",
+"input":"<a a=\"1\">",
+"output":[["StartTag", "a", {"a":"1"}]]},
+
+{"description":"<a a=\"9\">",
+"input":"<a a=\"9\">",
+"output":[["StartTag", "a", {"a":"9"}]]},
+
+{"description":"<a a=\"<\">",
+"input":"<a a=\"<\">",
+"output":[["StartTag", "a", {"a":"<"}]]},
+
+{"description":"<a a=\"=\">",
+"input":"<a a=\"=\">",
+"output":[["StartTag", "a", {"a":"="}]]},
+
+{"description":"<a a=\">\">",
+"input":"<a a=\">\">",
+"output":[["StartTag", "a", {"a":">"}]]},
+
+{"description":"<a a=\"?\">",
+"input":"<a a=\"?\">",
+"output":[["StartTag", "a", {"a":"?"}]]},
+
+{"description":"<a a=\"@\">",
+"input":"<a a=\"@\">",
+"output":[["StartTag", "a", {"a":"@"}]]},
+
+{"description":"<a a=\"A\">",
+"input":"<a a=\"A\">",
+"output":[["StartTag", "a", {"a":"A"}]]},
+
+{"description":"<a a=\"B\">",
+"input":"<a a=\"B\">",
+"output":[["StartTag", "a", {"a":"B"}]]},
+
+{"description":"<a a=\"Y\">",
+"input":"<a a=\"Y\">",
+"output":[["StartTag", "a", {"a":"Y"}]]},
+
+{"description":"<a a=\"Z\">",
+"input":"<a a=\"Z\">",
+"output":[["StartTag", "a", {"a":"Z"}]]},
+
+{"description":"<a a=\"`\">",
+"input":"<a a=\"`\">",
+"output":[["StartTag", "a", {"a":"`"}]]},
+
+{"description":"<a a=\"a\">",
+"input":"<a a=\"a\">",
+"output":[["StartTag", "a", {"a":"a"}]]},
+
+{"description":"<a a=\"b\">",
+"input":"<a a=\"b\">",
+"output":[["StartTag", "a", {"a":"b"}]]},
+
+{"description":"<a a=\"y\">",
+"input":"<a a=\"y\">",
+"output":[["StartTag", "a", {"a":"y"}]]},
+
+{"description":"<a a=\"z\">",
+"input":"<a a=\"z\">",
+"output":[["StartTag", "a", {"a":"z"}]]},
+
+{"description":"<a a=\"{\">",
+"input":"<a a=\"{\">",
+"output":[["StartTag", "a", {"a":"{"}]]},
+
+{"description":"<a a=\"\\uDBC0\\uDC00\">",
+"input":"<a a=\"\uDBC0\uDC00\">",
+"output":[["StartTag", "a", {"a":"\uDBC0\uDC00"}]]},
+
+{"description":"<a a=#>",
+"input":"<a a=#>",
+"output":[["StartTag", "a", {"a":"#"}]]},
+
+{"description":"<a a=%>",
+"input":"<a a=%>",
+"output":[["StartTag", "a", {"a":"%"}]]},
+
+{"description":"<a a=&>",
+"input":"<a a=&>",
+"output":[["StartTag", "a", {"a":"&"}]]},
+
+{"description":"<a a=''>",
+"input":"<a a=''>",
+"output":[["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a='\\u0000'>",
+"input":"<a a='\u0000'>",
+"output":["ParseError", ["StartTag", "a", {"a":"\uFFFD"}]]},
+
+{"description":"<a a='\\u0009'>",
+"input":"<a a='\u0009'>",
+"output":[["StartTag", "a", {"a":"\u0009"}]]},
+
+{"description":"<a a='\\u000A'>",
+"input":"<a a='\u000A'>",
+"output":[["StartTag", "a", {"a":"\u000A"}]]},
+
+{"description":"<a a='\\u000B'>",
+"input":"<a a='\u000B'>",
+"output":["ParseError", ["StartTag", "a", {"a":"\u000B"}]]},
+
+{"description":"<a a='\\u000C'>",
+"input":"<a a='\u000C'>",
+"output":[["StartTag", "a", {"a":"\u000C"}]]},
+
+{"description":"<a a=' '>",
+"input":"<a a=' '>",
+"output":[["StartTag", "a", {"a":" "}]]},
+
+{"description":"<a a='!'>",
+"input":"<a a='!'>",
+"output":[["StartTag", "a", {"a":"!"}]]},
+
+{"description":"<a a='\"'>",
+"input":"<a a='\"'>",
+"output":[["StartTag", "a", {"a":"\""}]]},
+
+{"description":"<a a='%'>",
+"input":"<a a='%'>",
+"output":[["StartTag", "a", {"a":"%"}]]},
+
+{"description":"<a a='&'>",
+"input":"<a a='&'>",
+"output":[["StartTag", "a", {"a":"&"}]]},
+
+{"description":"<a a=''>",
+"input":"<a a=''>",
+"output":[["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a=''\\u0000>",
+"input":"<a a=''\u0000>",
+"output":["ParseError", "ParseError", ["StartTag", "a", {"a":"", "\uFFFD":""}]]},
+
+{"description":"<a a=''\\u0008>",
+"input":"<a a=''\u0008>",
+"output":["ParseError", "ParseError", ["StartTag", "a", {"a":"", "\u0008":""}]]},
+
+{"description":"<a a=''\\u0009>",
+"input":"<a a=''\u0009>",
+"output":[["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a=''\\u000A>",
+"input":"<a a=''\u000A>",
+"output":[["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a=''\\u000B>",
+"input":"<a a=''\u000B>",
+"output":["ParseError", "ParseError", ["StartTag", "a", {"a":"", "\u000B":""}]]},
+
+{"description":"<a a=''\\u000C>",
+"input":"<a a=''\u000C>",
+"output":[["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a=''\\u000D>",
+"input":"<a a=''\u000D>",
+"output":[["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a=''\\u001F>",
+"input":"<a a=''\u001F>",
+"output":["ParseError", "ParseError", ["StartTag", "a", {"a":"", "\u001F":""}]]},
+
+{"description":"<a a='' >",
+"input":"<a a='' >",
+"output":[["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a=''!>",
+"input":"<a a=''!>",
+"output":["ParseError", ["StartTag", "a", {"a":"", "!":""}]]},
+
+{"description":"<a a=''\">",
+"input":"<a a=''\">",
+"output":["ParseError", "ParseError", ["StartTag", "a", {"a":"", "\"":""}]]},
+
+{"description":"<a a=''&>",
+"input":"<a a=''&>",
+"output":["ParseError", ["StartTag", "a", {"a":"", "&":""}]]},
+
+{"description":"<a a='''>",
+"input":"<a a='''>",
+"output":["ParseError", "ParseError", ["StartTag", "a", {"a":"", "'":""}]]},
+
+{"description":"<a a=''->",
+"input":"<a a=''->",
+"output":["ParseError", ["StartTag", "a", {"a":"", "-":""}]]},
+
+{"description":"<a a=''.>",
+"input":"<a a=''.>",
+"output":["ParseError", ["StartTag", "a", {"a":"", ".":""}]]},
+
+{"description":"<a a=''/>",
+"input":"<a a=''/>",
+"output":[["StartTag", "a", {"a":""}, true]]},
+
+{"description":"<a a=''0>",
+"input":"<a a=''0>",
+"output":["ParseError", ["StartTag", "a", {"a":"", "0":""}]]},
+
+{"description":"<a a=''1>",
+"input":"<a a=''1>",
+"output":["ParseError", ["StartTag", "a", {"a":"", "1":""}]]},
+
+{"description":"<a a=''9>",
+"input":"<a a=''9>",
+"output":["ParseError", ["StartTag", "a", {"a":"", "9":""}]]},
+
+{"description":"<a a=''<>",
+"input":"<a a=''<>",
+"output":["ParseError", "ParseError", ["StartTag", "a", {"a":"", "<":""}]]},
+
+{"description":"<a a=''=>",
+"input":"<a a=''=>",
+"output":["ParseError", "ParseError", ["StartTag", "a", {"a":"", "=":""}]]},
+
+{"description":"<a a=''>",
+"input":"<a a=''>",
+"output":[["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a=''?>",
+"input":"<a a=''?>",
+"output":["ParseError", ["StartTag", "a", {"a":"", "?":""}]]},
+
+{"description":"<a a=''@>",
+"input":"<a a=''@>",
+"output":["ParseError", ["StartTag", "a", {"a":"", "@":""}]]},
+
+{"description":"<a a=''A>",
+"input":"<a a=''A>",
+"output":["ParseError", "ParseError", ["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a=''B>",
+"input":"<a a=''B>",
+"output":["ParseError", ["StartTag", "a", {"a":"", "b":""}]]},
+
+{"description":"<a a=''Y>",
+"input":"<a a=''Y>",
+"output":["ParseError", ["StartTag", "a", {"a":"", "y":""}]]},
+
+{"description":"<a a=''Z>",
+"input":"<a a=''Z>",
+"output":["ParseError", ["StartTag", "a", {"a":"", "z":""}]]},
+
+{"description":"<a a=''`>",
+"input":"<a a=''`>",
+"output":["ParseError", ["StartTag", "a", {"a":"", "`":""}]]},
+
+{"description":"<a a=''a>",
+"input":"<a a=''a>",
+"output":["ParseError", "ParseError", ["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a=''b>",
+"input":"<a a=''b>",
+"output":["ParseError", ["StartTag", "a", {"a":"", "b":""}]]},
+
+{"description":"<a a=''y>",
+"input":"<a a=''y>",
+"output":["ParseError", ["StartTag", "a", {"a":"", "y":""}]]},
+
+{"description":"<a a=''z>",
+"input":"<a a=''z>",
+"output":["ParseError", ["StartTag", "a", {"a":"", "z":""}]]},
+
+{"description":"<a a=''{>",
+"input":"<a a=''{>",
+"output":["ParseError", ["StartTag", "a", {"a":"", "{":""}]]},
+
+{"description":"<a a=''\\uDBC0\\uDC00>",
+"input":"<a a=''\uDBC0\uDC00>",
+"output":["ParseError", ["StartTag", "a", {"a":"", "\uDBC0\uDC00":""}]]},
+
+{"description":"<a a='('>",
+"input":"<a a='('>",
+"output":[["StartTag", "a", {"a":"("}]]},
+
+{"description":"<a a='-'>",
+"input":"<a a='-'>",
+"output":[["StartTag", "a", {"a":"-"}]]},
+
+{"description":"<a a='/'>",
+"input":"<a a='/'>",
+"output":[["StartTag", "a", {"a":"/"}]]},
+
+{"description":"<a a='0'>",
+"input":"<a a='0'>",
+"output":[["StartTag", "a", {"a":"0"}]]},
+
+{"description":"<a a='1'>",
+"input":"<a a='1'>",
+"output":[["StartTag", "a", {"a":"1"}]]},
+
+{"description":"<a a='9'>",
+"input":"<a a='9'>",
+"output":[["StartTag", "a", {"a":"9"}]]},
+
+{"description":"<a a='<'>",
+"input":"<a a='<'>",
+"output":[["StartTag", "a", {"a":"<"}]]},
+
+{"description":"<a a='='>",
+"input":"<a a='='>",
+"output":[["StartTag", "a", {"a":"="}]]},
+
+{"description":"<a a='>'>",
+"input":"<a a='>'>",
+"output":[["StartTag", "a", {"a":">"}]]},
+
+{"description":"<a a='?'>",
+"input":"<a a='?'>",
+"output":[["StartTag", "a", {"a":"?"}]]},
+
+{"description":"<a a='@'>",
+"input":"<a a='@'>",
+"output":[["StartTag", "a", {"a":"@"}]]},
+
+{"description":"<a a='A'>",
+"input":"<a a='A'>",
+"output":[["StartTag", "a", {"a":"A"}]]},
+
+{"description":"<a a='B'>",
+"input":"<a a='B'>",
+"output":[["StartTag", "a", {"a":"B"}]]},
+
+{"description":"<a a='Y'>",
+"input":"<a a='Y'>",
+"output":[["StartTag", "a", {"a":"Y"}]]},
+
+{"description":"<a a='Z'>",
+"input":"<a a='Z'>",
+"output":[["StartTag", "a", {"a":"Z"}]]},
+
+{"description":"<a a='`'>",
+"input":"<a a='`'>",
+"output":[["StartTag", "a", {"a":"`"}]]},
+
+{"description":"<a a='a'>",
+"input":"<a a='a'>",
+"output":[["StartTag", "a", {"a":"a"}]]},
+
+{"description":"<a a='b'>",
+"input":"<a a='b'>",
+"output":[["StartTag", "a", {"a":"b"}]]},
+
+{"description":"<a a='y'>",
+"input":"<a a='y'>",
+"output":[["StartTag", "a", {"a":"y"}]]},
+
+{"description":"<a a='z'>",
+"input":"<a a='z'>",
+"output":[["StartTag", "a", {"a":"z"}]]},
+
+{"description":"<a a='{'>",
+"input":"<a a='{'>",
+"output":[["StartTag", "a", {"a":"{"}]]},
+
+{"description":"<a a='\\uDBC0\\uDC00'>",
+"input":"<a a='\uDBC0\uDC00'>",
+"output":[["StartTag", "a", {"a":"\uDBC0\uDC00"}]]},
+
+{"description":"<a a=(>",
+"input":"<a a=(>",
+"output":[["StartTag", "a", {"a":"("}]]},
+
+{"description":"<a a=->",
+"input":"<a a=->",
+"output":[["StartTag", "a", {"a":"-"}]]},
+
+{"description":"<a a=/>",
+"input":"<a a=/>",
+"output":[["StartTag", "a", {"a":"/"}]]},
+
+{"description":"<a a=0>",
+"input":"<a a=0>",
+"output":[["StartTag", "a", {"a":"0"}]]},
+
+{"description":"<a a=1>",
+"input":"<a a=1>",
+"output":[["StartTag", "a", {"a":"1"}]]},
+
+{"description":"<a a=9>",
+"input":"<a a=9>",
+"output":[["StartTag", "a", {"a":"9"}]]},
+
+{"description":"<a a=<>",
+"input":"<a a=<>",
+"output":["ParseError", ["StartTag", "a", {"a":"<"}]]},
+
+{"description":"<a a==>",
+"input":"<a a==>",
+"output":["ParseError", ["StartTag", "a", {"a":"="}]]},
+
+{"description":"<a a=>",
+"input":"<a a=>",
+"output":["ParseError", ["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a=?>",
+"input":"<a a=?>",
+"output":[["StartTag", "a", {"a":"?"}]]},
+
+{"description":"<a a=@>",
+"input":"<a a=@>",
+"output":[["StartTag", "a", {"a":"@"}]]},
+
+{"description":"<a a=A>",
+"input":"<a a=A>",
+"output":[["StartTag", "a", {"a":"A"}]]},
+
+{"description":"<a a=B>",
+"input":"<a a=B>",
+"output":[["StartTag", "a", {"a":"B"}]]},
+
+{"description":"<a a=Y>",
+"input":"<a a=Y>",
+"output":[["StartTag", "a", {"a":"Y"}]]},
+
+{"description":"<a a=Z>",
+"input":"<a a=Z>",
+"output":[["StartTag", "a", {"a":"Z"}]]},
+
+{"description":"<a a=`>",
+"input":"<a a=`>",
+"output":["ParseError", ["StartTag", "a", {"a":"`"}]]},
+
+{"description":"<a a=a>",
+"input":"<a a=a>",
+"output":[["StartTag", "a", {"a":"a"}]]},
+
+{"description":"<a a=a\\u0000>",
+"input":"<a a=a\u0000>",
+"output":["ParseError", ["StartTag", "a", {"a":"a\uFFFD"}]]},
+
+{"description":"<a a=a\\u0008>",
+"input":"<a a=a\u0008>",
+"output":["ParseError", ["StartTag", "a", {"a":"a\u0008"}]]},
+
+{"description":"<a a=a\\u0009>",
+"input":"<a a=a\u0009>",
+"output":[["StartTag", "a", {"a":"a"}]]},
+
+{"description":"<a a=a\\u000A>",
+"input":"<a a=a\u000A>",
+"output":[["StartTag", "a", {"a":"a"}]]},
+
+{"description":"<a a=a\\u000B>",
+"input":"<a a=a\u000B>",
+"output":["ParseError", ["StartTag", "a", {"a":"a\u000B"}]]},
+
+{"description":"<a a=a\\u000C>",
+"input":"<a a=a\u000C>",
+"output":[["StartTag", "a", {"a":"a"}]]},
+
+{"description":"<a a=a\\u000D>",
+"input":"<a a=a\u000D>",
+"output":[["StartTag", "a", {"a":"a"}]]},
+
+{"description":"<a a=a\\u001F>",
+"input":"<a a=a\u001F>",
+"output":["ParseError", ["StartTag", "a", {"a":"a\u001F"}]]},
+
+{"description":"<a a=a >",
+"input":"<a a=a >",
+"output":[["StartTag", "a", {"a":"a"}]]},
+
+{"description":"<a a=a!>",
+"input":"<a a=a!>",
+"output":[["StartTag", "a", {"a":"a!"}]]},
+
+{"description":"<a a=a\">",
+"input":"<a a=a\">",
+"output":["ParseError", ["StartTag", "a", {"a":"a\""}]]},
+
+{"description":"<a a=a#>",
+"input":"<a a=a#>",
+"output":[["StartTag", "a", {"a":"a#"}]]},
+
+{"description":"<a a=a%>",
+"input":"<a a=a%>",
+"output":[["StartTag", "a", {"a":"a%"}]]},
+
+{"description":"<a a=a&>",
+"input":"<a a=a&>",
+"output":[["StartTag", "a", {"a":"a&"}]]},
+
+{"description":"<a a=a'>",
+"input":"<a a=a'>",
+"output":["ParseError", ["StartTag", "a", {"a":"a'"}]]},
+
+{"description":"<a a=a(>",
+"input":"<a a=a(>",
+"output":[["StartTag", "a", {"a":"a("}]]},
+
+{"description":"<a a=a->",
+"input":"<a a=a->",
+"output":[["StartTag", "a", {"a":"a-"}]]},
+
+{"description":"<a a=a/>",
+"input":"<a a=a/>",
+"output":[["StartTag", "a", {"a":"a/"}]]},
+
+{"description":"<a a=a0>",
+"input":"<a a=a0>",
+"output":[["StartTag", "a", {"a":"a0"}]]},
+
+{"description":"<a a=a1>",
+"input":"<a a=a1>",
+"output":[["StartTag", "a", {"a":"a1"}]]},
+
+{"description":"<a a=a9>",
+"input":"<a a=a9>",
+"output":[["StartTag", "a", {"a":"a9"}]]},
+
+{"description":"<a a=a<>",
+"input":"<a a=a<>",
+"output":["ParseError", ["StartTag", "a", {"a":"a<"}]]},
+
+{"description":"<a a=a=>",
+"input":"<a a=a=>",
+"output":["ParseError", ["StartTag", "a", {"a":"a="}]]},
+
+{"description":"<a a=a>",
+"input":"<a a=a>",
+"output":[["StartTag", "a", {"a":"a"}]]},
+
+{"description":"<a a=a?>",
+"input":"<a a=a?>",
+"output":[["StartTag", "a", {"a":"a?"}]]},
+
+{"description":"<a a=a@>",
+"input":"<a a=a@>",
+"output":[["StartTag", "a", {"a":"a@"}]]},
+
+{"description":"<a a=aA>",
+"input":"<a a=aA>",
+"output":[["StartTag", "a", {"a":"aA"}]]},
+
+{"description":"<a a=aB>",
+"input":"<a a=aB>",
+"output":[["StartTag", "a", {"a":"aB"}]]},
+
+{"description":"<a a=aY>",
+"input":"<a a=aY>",
+"output":[["StartTag", "a", {"a":"aY"}]]},
+
+{"description":"<a a=aZ>",
+"input":"<a a=aZ>",
+"output":[["StartTag", "a", {"a":"aZ"}]]},
+
+{"description":"<a a=a`>",
+"input":"<a a=a`>",
+"output":["ParseError", ["StartTag", "a", {"a":"a`"}]]},
+
+{"description":"<a a=aa>",
+"input":"<a a=aa>",
+"output":[["StartTag", "a", {"a":"aa"}]]},
+
+{"description":"<a a=ab>",
+"input":"<a a=ab>",
+"output":[["StartTag", "a", {"a":"ab"}]]},
+
+{"description":"<a a=ay>",
+"input":"<a a=ay>",
+"output":[["StartTag", "a", {"a":"ay"}]]},
+
+{"description":"<a a=az>",
+"input":"<a a=az>",
+"output":[["StartTag", "a", {"a":"az"}]]},
+
+{"description":"<a a=a{>",
+"input":"<a a=a{>",
+"output":[["StartTag", "a", {"a":"a{"}]]},
+
+{"description":"<a a=a\\uDBC0\\uDC00>",
+"input":"<a a=a\uDBC0\uDC00>",
+"output":[["StartTag", "a", {"a":"a\uDBC0\uDC00"}]]},
+
+{"description":"<a a=b>",
+"input":"<a a=b>",
+"output":[["StartTag", "a", {"a":"b"}]]},
+
+{"description":"<a a=y>",
+"input":"<a a=y>",
+"output":[["StartTag", "a", {"a":"y"}]]},
+
+{"description":"<a a=z>",
+"input":"<a a=z>",
+"output":[["StartTag", "a", {"a":"z"}]]},
+
+{"description":"<a a={>",
+"input":"<a a={>",
+"output":[["StartTag", "a", {"a":"{"}]]},
+
+{"description":"<a a=\\uDBC0\\uDC00>",
+"input":"<a a=\uDBC0\uDC00>",
+"output":[["StartTag", "a", {"a":"\uDBC0\uDC00"}]]},
+
+{"description":"<a a>",
+"input":"<a a>",
+"output":[["StartTag", "a", {"a":""}]]},
+
+{"description":"<a a?>",
+"input":"<a a?>",
+"output":[["StartTag", "a", {"a?":""}]]},
+
+{"description":"<a a@>",
+"input":"<a a@>",
+"output":[["StartTag", "a", {"a@":""}]]},
+
+{"description":"<a aA>",
+"input":"<a aA>",
+"output":[["StartTag", "a", {"aa":""}]]},
+
+{"description":"<a aB>",
+"input":"<a aB>",
+"output":[["StartTag", "a", {"ab":""}]]},
+
+{"description":"<a aY>",
+"input":"<a aY>",
+"output":[["StartTag", "a", {"ay":""}]]},
+
+{"description":"<a aZ>",
+"input":"<a aZ>",
+"output":[["StartTag", "a", {"az":""}]]},
+
+{"description":"<a a[>",
+"input":"<a a[>",
+"output":[["StartTag", "a", {"a[":""}]]},
+
+{"description":"<a a`>",
+"input":"<a a`>",
+"output":[["StartTag", "a", {"a`":""}]]},
+
+{"description":"<a aa>",
+"input":"<a aa>",
+"output":[["StartTag", "a", {"aa":""}]]},
+
+{"description":"<a ab>",
+"input":"<a ab>",
+"output":[["StartTag", "a", {"ab":""}]]},
+
+{"description":"<a ay>",
+"input":"<a ay>",
+"output":[["StartTag", "a", {"ay":""}]]},
+
+{"description":"<a az>",
+"input":"<a az>",
+"output":[["StartTag", "a", {"az":""}]]},
+
+{"description":"<a a{>",
+"input":"<a a{>",
+"output":[["StartTag", "a", {"a{":""}]]},
+
+{"description":"<a a\\uDBC0\\uDC00>",
+"input":"<a a\uDBC0\uDC00>",
+"output":[["StartTag", "a", {"a\uDBC0\uDC00":""}]]},
+
+{"description":"<a b>",
+"input":"<a b>",
+"output":[["StartTag", "a", {"b":""}]]},
+
+{"description":"<a y>",
+"input":"<a y>",
+"output":[["StartTag", "a", {"y":""}]]},
+
+{"description":"<a z>",
+"input":"<a z>",
+"output":[["StartTag", "a", {"z":""}]]},
+
+{"description":"<a {>",
+"input":"<a {>",
+"output":[["StartTag", "a", {"{":""}]]},
+
+{"description":"<a \\uDBC0\\uDC00>",
+"input":"<a \uDBC0\uDC00>",
+"output":[["StartTag", "a", {"\uDBC0\uDC00":""}]]},
+
+{"description":"<a!>",
+"input":"<a!>",
+"output":[["StartTag", "a!", {}]]},
+
+{"description":"<a\">",
+"input":"<a\">",
+"output":[["StartTag", "a\"", {}]]},
+
+{"description":"<a&>",
+"input":"<a&>",
+"output":[["StartTag", "a&", {}]]},
+
+{"description":"<a'>",
+"input":"<a'>",
+"output":[["StartTag", "a'", {}]]},
+
+{"description":"<a->",
+"input":"<a->",
+"output":[["StartTag", "a-", {}]]},
+
+{"description":"<a.>",
+"input":"<a.>",
+"output":[["StartTag", "a.", {}]]},
+
+{"description":"<a/>",
+"input":"<a/>",
+"output":[["StartTag", "a", {}, true]]},
+
+{"description":"<a/\\u0000>",
+"input":"<a/\u0000>",
+"output":["ParseError", "ParseError", ["StartTag", "a", {"\uFFFD":""}]]},
+
+{"description":"<a/\\u0009>",
+"input":"<a/\u0009>",
+"output":["ParseError", ["StartTag", "a", {}]]},
+
+{"description":"<a/\\u000A>",
+"input":"<a/\u000A>",
+"output":["ParseError", ["StartTag", "a", {}]]},
+
+{"description":"<a/\\u000B>",
+"input":"<a/\u000B>",
+"output":["ParseError", "ParseError", ["StartTag", "a", {"\u000B":""}]]},
+
+{"description":"<a/\\u000C>",
+"input":"<a/\u000C>",
+"output":["ParseError", ["StartTag", "a", {}]]},
+
+{"description":"<a/ >",
+"input":"<a/ >",
+"output":["ParseError", ["StartTag", "a", {}]]},
+
+{"description":"<a/!>",
+"input":"<a/!>",
+"output":["ParseError", ["StartTag", "a", {"!":""}]]},
+
+{"description":"<a/\">",
+"input":"<a/\">",
+"output":["ParseError", "ParseError", ["StartTag", "a", {"\"":""}]]},
+
+{"description":"<a/&>",
+"input":"<a/&>",
+"output":["ParseError", ["StartTag", "a", {"&":""}]]},
+
+{"description":"<a/'>",
+"input":"<a/'>",
+"output":["ParseError", "ParseError", ["StartTag", "a", {"'":""}]]},
+
+{"description":"<a/->",
+"input":"<a/->",
+"output":["ParseError", ["StartTag", "a", {"-":""}]]},
+
+{"description":"<a//>",
+"input":"<a//>",
+"output":["ParseError", ["StartTag", "a", {}, true]]},
+
+{"description":"<a/0>",
+"input":"<a/0>",
+"output":["ParseError", ["StartTag", "a", {"0":""}]]},
+
+{"description":"<a/1>",
+"input":"<a/1>",
+"output":["ParseError", ["StartTag", "a", {"1":""}]]},
+
+{"description":"<a/9>",
+"input":"<a/9>",
+"output":["ParseError", ["StartTag", "a", {"9":""}]]},
+
+{"description":"<a/<>",
+"input":"<a/<>",
+"output":["ParseError", "ParseError", ["StartTag", "a", {"<":""}]]},
+
+{"description":"<a/=>",
+"input":"<a/=>",
+"output":["ParseError", "ParseError", ["StartTag", "a", {"=":""}]]},
+
+{"description":"<a/>",
+"input":"<a/>",
+"output":[["StartTag", "a", {}, true]]},
+
+{"description":"<a/?>",
+"input":"<a/?>",
+"output":["ParseError", ["StartTag", "a", {"?":""}]]},
+
+{"description":"<a/@>",
+"input":"<a/@>",
+"output":["ParseError", ["StartTag", "a", {"@":""}]]},
+
+{"description":"<a/A>",
+"input":"<a/A>",
+"output":["ParseError", ["StartTag", "a", {"a":""}]]},
+
+{"description":"<a/B>",
+"input":"<a/B>",
+"output":["ParseError", ["StartTag", "a", {"b":""}]]},
+
+{"description":"<a/Y>",
+"input":"<a/Y>",
+"output":["ParseError", ["StartTag", "a", {"y":""}]]},
+
+{"description":"<a/Z>",
+"input":"<a/Z>",
+"output":["ParseError", ["StartTag", "a", {"z":""}]]},
+
+{"description":"<a/`>",
+"input":"<a/`>",
+"output":["ParseError", ["StartTag", "a", {"`":""}]]},
+
+{"description":"<a/a>",
+"input":"<a/a>",
+"output":["ParseError", ["StartTag", "a", {"a":""}]]},
+
+{"description":"<a/b>",
+"input":"<a/b>",
+"output":["ParseError", ["StartTag", "a", {"b":""}]]},
+
+{"description":"<a/y>",
+"input":"<a/y>",
+"output":["ParseError", ["StartTag", "a", {"y":""}]]},
+
+{"description":"<a/z>",
+"input":"<a/z>",
+"output":["ParseError", ["StartTag", "a", {"z":""}]]},
+
+{"description":"<a/{>",
+"input":"<a/{>",
+"output":["ParseError", ["StartTag", "a", {"{":""}]]},
+
+{"description":"<a/\\uDBC0\\uDC00>",
+"input":"<a/\uDBC0\uDC00>",
+"output":["ParseError", ["StartTag", "a", {"\uDBC0\uDC00":""}]]},
+
+{"description":"<a0>",
+"input":"<a0>",
+"output":[["StartTag", "a0", {}]]},
+
+{"description":"<a1>",
+"input":"<a1>",
+"output":[["StartTag", "a1", {}]]},
+
+{"description":"<a9>",
+"input":"<a9>",
+"output":[["StartTag", "a9", {}]]},
+
+{"description":"<a<>",
+"input":"<a<>",
+"output":[["StartTag", "a<", {}]]},
+
+{"description":"<a=>",
+"input":"<a=>",
+"output":[["StartTag", "a=", {}]]},
+
+{"description":"<a>",
+"input":"<a>",
+"output":[["StartTag", "a", {}]]},
+
+{"description":"<a?>",
+"input":"<a?>",
+"output":[["StartTag", "a?", {}]]},
+
+{"description":"<a@>",
+"input":"<a@>",
+"output":[["StartTag", "a@", {}]]},
+
+{"description":"<aA>",
+"input":"<aA>",
+"output":[["StartTag", "aa", {}]]},
+
+{"description":"<aB>",
+"input":"<aB>",
+"output":[["StartTag", "ab", {}]]},
+
+{"description":"<aY>",
+"input":"<aY>",
+"output":[["StartTag", "ay", {}]]},
+
+{"description":"<aZ>",
+"input":"<aZ>",
+"output":[["StartTag", "az", {}]]},
+
+{"description":"<a[>",
+"input":"<a[>",
+"output":[["StartTag", "a[", {}]]},
+
+{"description":"<a`>",
+"input":"<a`>",
+"output":[["StartTag", "a`", {}]]},
+
+{"description":"<aa>",
+"input":"<aa>",
+"output":[["StartTag", "aa", {}]]},
+
+{"description":"<ab>",
+"input":"<ab>",
+"output":[["StartTag", "ab", {}]]},
+
+{"description":"<ay>",
+"input":"<ay>",
+"output":[["StartTag", "ay", {}]]},
+
+{"description":"<az>",
+"input":"<az>",
+"output":[["StartTag", "az", {}]]},
+
+{"description":"<a{>",
+"input":"<a{>",
+"output":[["StartTag", "a{", {}]]},
+
+{"description":"<a\\uDBC0\\uDC00>",
+"input":"<a\uDBC0\uDC00>",
+"output":[["StartTag", "a\uDBC0\uDC00", {}]]},
+
+{"description":"<b>",
+"input":"<b>",
+"output":[["StartTag", "b", {}]]},
+
+{"description":"<y>",
+"input":"<y>",
+"output":[["StartTag", "y", {}]]},
+
+{"description":"<z>",
+"input":"<z>",
+"output":[["StartTag", "z", {}]]},
+
+{"description":"<{",
+"input":"<{",
+"output":["ParseError", ["Character", "<{"]]},
+
+{"description":"<\\uDBC0\\uDC00",
+"input":"<\uDBC0\uDC00",
+"output":["ParseError", ["Character", "<\uDBC0\uDC00"]]},
+
+{"description":"=",
+"input":"=",
+"output":[["Character", "="]]},
+
+{"description":">",
+"input":">",
+"output":[["Character", ">"]]},
+
+{"description":"?",
+"input":"?",
+"output":[["Character", "?"]]},
+
+{"description":"@",
+"input":"@",
+"output":[["Character", "@"]]},
+
+{"description":"A",
+"input":"A",
+"output":[["Character", "A"]]},
+
+{"description":"B",
+"input":"B",
+"output":[["Character", "B"]]},
+
+{"description":"Y",
+"input":"Y",
+"output":[["Character", "Y"]]},
+
+{"description":"Z",
+"input":"Z",
+"output":[["Character", "Z"]]},
+
+{"description":"`",
+"input":"`",
+"output":[["Character", "`"]]},
+
+{"description":"a",
+"input":"a",
+"output":[["Character", "a"]]},
+
+{"description":"b",
+"input":"b",
+"output":[["Character", "b"]]},
+
+{"description":"y",
+"input":"y",
+"output":[["Character", "y"]]},
+
+{"description":"z",
+"input":"z",
+"output":[["Character", "z"]]},
+
+{"description":"{",
+"input":"{",
+"output":[["Character", "{"]]},
+
+{"description":"\\uDBC0\\uDC00",
+"input":"\uDBC0\uDC00",
+"output":[["Character", "\uDBC0\uDC00"]]}
+
+]}
diff --git a/pkgs/html/test/data/tokenizer/test4.test b/pkgs/html/test/data/tokenizer/test4.test
new file mode 100644
index 0000000..c0f3b2b
--- /dev/null
+++ b/pkgs/html/test/data/tokenizer/test4.test
@@ -0,0 +1,336 @@
+{"tests": [
+
+{"description":"< in attribute name",
+"input":"<z/0 <>",
+"output":["ParseError", "ParseError", ["StartTag", "z", {"0": "", "<": ""}]]},
+
+{"description":"< in attribute value",
+"input":"<z x=<>",
+"output":["ParseError", ["StartTag", "z", {"x": "<"}]]},
+
+{"description":"= in unquoted attribute value",
+"input":"<z z=z=z>",
+"output":["ParseError", ["StartTag", "z", {"z": "z=z"}]]},
+
+{"description":"= attribute",
+"input":"<z =>",
+"output":["ParseError", ["StartTag", "z", {"=": ""}]]},
+
+{"description":"== attribute",
+"input":"<z ==>",
+"output":["ParseError", "ParseError", ["StartTag", "z", {"=": ""}]]},
+
+{"description":"=== attribute",
+"input":"<z ===>",
+"output":["ParseError", "ParseError", ["StartTag", "z", {"=": "="}]]},
+
+{"description":"==== attribute",
+"input":"<z ====>",
+"output":["ParseError", "ParseError", "ParseError", ["StartTag", "z", {"=": "=="}]]},
+
+{"description":"Allowed \" after ampersand in attribute value",
+"input":"<z z=\"&\">",
+"output":[["StartTag", "z", {"z": "&"}]]},
+
+{"description":"Non-allowed ' after ampersand in attribute value",
+"input":"<z z=\"&'\">",
+"output":["ParseError", ["StartTag", "z", {"z": "&'"}]]},
+
+{"description":"Allowed ' after ampersand in attribute value",
+"input":"<z z='&'>",
+"output":[["StartTag", "z", {"z": "&"}]]},
+
+{"description":"Non-allowed \" after ampersand in attribute value",
+"input":"<z z='&\"'>",
+"output":["ParseError", ["StartTag", "z", {"z": "&\""}]]},
+
+{"description":"Text after bogus character reference",
+"input":"<z z='&xlink_xmlns;'>bar<z>",
+"output":["ParseError",["StartTag","z",{"z":"&xlink_xmlns;"}],["Character","bar"],["StartTag","z",{}]]},
+
+{"description":"Text after hex character reference",
+"input":"<z z='  foo'>bar<z>",
+"output":[["StartTag","z",{"z":" foo"}],["Character","bar"],["StartTag","z",{}]]},
+
+{"description":"Attribute name starting with \"",
+"input":"<foo \"='bar'>",
+"output":["ParseError", ["StartTag", "foo", {"\"": "bar"}]]},
+
+{"description":"Attribute name starting with '",
+"input":"<foo '='bar'>",
+"output":["ParseError", ["StartTag", "foo", {"'": "bar"}]]},
+
+{"description":"Attribute name containing \"",
+"input":"<foo a\"b='bar'>",
+"output":["ParseError", ["StartTag", "foo", {"a\"b": "bar"}]]},
+
+{"description":"Attribute name containing '",
+"input":"<foo a'b='bar'>",
+"output":["ParseError", ["StartTag", "foo", {"a'b": "bar"}]]},
+
+{"description":"Unquoted attribute value containing '",
+"input":"<foo a=b'c>",
+"output":["ParseError", ["StartTag", "foo", {"a": "b'c"}]]},
+
+{"description":"Unquoted attribute value containing \"",
+"input":"<foo a=b\"c>",
+"output":["ParseError", ["StartTag", "foo", {"a": "b\"c"}]]},
+
+{"description":"Double-quoted attribute value not followed by whitespace",
+"input":"<foo a=\"b\"c>",
+"output":["ParseError", ["StartTag", "foo", {"a": "b", "c": ""}]]},
+
+{"description":"Single-quoted attribute value not followed by whitespace",
+"input":"<foo a='b'c>",
+"output":["ParseError", ["StartTag", "foo", {"a": "b", "c": ""}]]},
+
+{"description":"Quoted attribute followed by permitted /",
+"input":"<br a='b'/>",
+"output":[["StartTag","br",{"a":"b"},true]]},
+
+{"description":"Quoted attribute followed by non-permitted /",
+"input":"<bar a='b'/>",
+"output":[["StartTag","bar",{"a":"b"},true]]},
+
+{"description":"CR EOF after doctype name",
+"input":"<!doctype html \r",
+"output":["ParseError", ["DOCTYPE", "html", null, null, false]]},
+
+{"description":"CR EOF in tag name",
+"input":"<z\r",
+"output":["ParseError"]},
+
+{"description":"Slash EOF in tag name",
+"input":"<z/",
+"output":["ParseError"]},
+
+{"description":"Zero hex numeric entity",
+"input":"�",
+"output":["ParseError", "ParseError", ["Character", "\uFFFD"]]},
+
+{"description":"Zero decimal numeric entity",
+"input":"�",
+"output":["ParseError", "ParseError", ["Character", "\uFFFD"]]},
+
+{"description":"Zero-prefixed hex numeric entity",
+"input":"A",
+"output":[["Character", "A"]]},
+
+{"description":"Zero-prefixed decimal numeric entity",
+"input":"A",
+"output":[["Character", "A"]]},
+
+{"description":"Empty hex numeric entities",
+"input":"&#x &#X ",
+"output":["ParseError", ["Character", "&#x "], "ParseError", ["Character", "&#X "]]},
+
+{"description":"Empty decimal numeric entities",
+"input":"&# &#; ",
+"output":["ParseError", ["Character", "&# "], "ParseError", ["Character", "&#; "]]},
+
+{"description":"Non-BMP numeric entity",
+"input":"𐀀",
+"output":[["Character", "\uD800\uDC00"]]},
+
+{"description":"Maximum non-BMP numeric entity",
+"input":"",
+"output":["ParseError", ["Character", "\uDBFF\uDFFF"]]},
+
+{"description":"Above maximum numeric entity",
+"input":"�",
+"output":["ParseError", ["Character", "\uFFFD"]]},
+
+{"description":"32-bit hex numeric entity",
+"input":"�",
+"output":["ParseError", ["Character", "\uFFFD"]]},
+
+{"description":"33-bit hex numeric entity",
+"input":"�",
+"output":["ParseError", ["Character", "\uFFFD"]]},
+
+{"description":"33-bit decimal numeric entity",
+"input":"�",
+"output":["ParseError", ["Character", "\uFFFD"]]},
+
+{"description":"Surrogate code point edge cases",
+"input":"퟿����",
+"output":[["Character", "\uD7FF"], "ParseError", ["Character", "\uFFFD"], "ParseError", ["Character", "\uFFFD"], "ParseError", ["Character", "\uFFFD"], "ParseError", ["Character", "\uFFFD\uE000"]]},
+
+{"description":"Uppercase start tag name",
+"input":"<X>",
+"output":[["StartTag", "x", {}]]},
+
+{"description":"Uppercase end tag name",
+"input":"</X>",
+"output":[["EndTag", "x"]]},
+
+{"description":"Uppercase attribute name",
+"input":"<x X>",
+"output":[["StartTag", "x", { "x":"" }]]},
+
+{"description":"Tag/attribute name case edge values",
+"input":"<x@AZ[`az{ @AZ[`az{>",
+"output":[["StartTag", "x@az[`az{", { "@az[`az{":"" }]]},
+
+{"description":"Duplicate different-case attributes",
+"input":"<x x=1 x=2 X=3>",
+"output":["ParseError", "ParseError", ["StartTag", "x", { "x":"1" }]]},
+
+{"description":"Uppercase close tag attributes",
+"input":"</x X>",
+"output":["ParseError", ["EndTag", "x"]]},
+
+{"description":"Duplicate close tag attributes",
+"input":"</x x x>",
+"output":["ParseError", "ParseError", ["EndTag", "x"]]},
+
+{"description":"Permitted slash",
+"input":"<br/>",
+"output":[["StartTag","br",{},true]]},
+
+{"description":"Non-permitted slash",
+"input":"<xr/>",
+"output":[["StartTag","xr",{},true]]},
+
+{"description":"Permitted slash but in close tag",
+"input":"</br/>",
+"output":["ParseError", ["EndTag", "br"]]},
+
+{"description":"Doctype public case-sensitivity (1)",
+"input":"<!DoCtYpE HtMl PuBlIc \"AbC\" \"XyZ\">",
+"output":[["DOCTYPE", "html", "AbC", "XyZ", true]]},
+
+{"description":"Doctype public case-sensitivity (2)",
+"input":"<!dOcTyPe hTmL pUbLiC \"aBc\" \"xYz\">",
+"output":[["DOCTYPE", "html", "aBc", "xYz", true]]},
+
+{"description":"Doctype system case-sensitivity (1)",
+"input":"<!DoCtYpE HtMl SyStEm \"XyZ\">",
+"output":[["DOCTYPE", "html", null, "XyZ", true]]},
+
+{"description":"Doctype system case-sensitivity (2)",
+"input":"<!dOcTyPe hTmL sYsTeM \"xYz\">",
+"output":[["DOCTYPE", "html", null, "xYz", true]]},
+
+{"description":"U+0000 in lookahead region after non-matching character",
+"input":"<!doc>\u0000",
+"output":["ParseError", ["Comment", "doc"], "ParseError", ["Character", "\u0000"]],
+"ignoreErrorOrder":true},
+
+{"description":"U+0000 in lookahead region",
+"input":"<!doc\u0000",
+"output":["ParseError", ["Comment", "doc\uFFFD"]],
+"ignoreErrorOrder":true},
+
+{"description":"U+0080 in lookahead region",
+"input":"<!doc\u0080",
+"output":["ParseError", "ParseError", ["Comment", "doc\u0080"]],
+"ignoreErrorOrder":true},
+
+{"description":"U+FDD1 in lookahead region",
+"input":"<!doc\uFDD1",
+"output":["ParseError", "ParseError", ["Comment", "doc\uFDD1"]],
+"ignoreErrorOrder":true},
+
+{"description":"U+1FFFF in lookahead region",
+"input":"<!doc\uD83F\uDFFF",
+"output":["ParseError", "ParseError", ["Comment", "doc\uD83F\uDFFF"]],
+"ignoreErrorOrder":true},
+
+{"description":"CR followed by non-LF",
+"input":"\r?",
+"output":[["Character", "\n?"]]},
+
+{"description":"CR at EOF",
+"input":"\r",
+"output":[["Character", "\n"]]},
+
+{"description":"LF at EOF",
+"input":"\n",
+"output":[["Character", "\n"]]},
+
+{"description":"CR LF",
+"input":"\r\n",
+"output":[["Character", "\n"]]},
+
+{"description":"CR CR",
+"input":"\r\r",
+"output":[["Character", "\n\n"]]},
+
+{"description":"LF LF",
+"input":"\n\n",
+"output":[["Character", "\n\n"]]},
+
+{"description":"LF CR",
+"input":"\n\r",
+"output":[["Character", "\n\n"]]},
+
+{"description":"text CR CR CR text",
+"input":"text\r\r\rtext",
+"output":[["Character", "text\n\n\ntext"]]},
+
+{"description":"Doctype publik",
+"input":"<!DOCTYPE html PUBLIK \"AbC\" \"XyZ\">",
+"output":["ParseError", ["DOCTYPE", "html", null, null, false]]},
+
+{"description":"Doctype publi",
+"input":"<!DOCTYPE html PUBLI",
+"output":["ParseError", ["DOCTYPE", "html", null, null, false]]},
+
+{"description":"Doctype sistem",
+"input":"<!DOCTYPE html SISTEM \"AbC\">",
+"output":["ParseError", ["DOCTYPE", "html", null, null, false]]},
+
+{"description":"Doctype sys",
+"input":"<!DOCTYPE html SYS",
+"output":["ParseError", ["DOCTYPE", "html", null, null, false]]},
+
+{"description":"Doctype html x>text",
+"input":"<!DOCTYPE html x>text",
+"output":["ParseError", ["DOCTYPE", "html", null, null, false], ["Character", "text"]]},
+
+{"description":"Grave accent in unquoted attribute",
+"input":"<a a=aa`>",
+"output":["ParseError", ["StartTag", "a", {"a":"aa`"}]]},
+
+{"description":"EOF in tag name state ",
+"input":"<a",
+"output":["ParseError"]},
+
+{"description":"EOF in tag name state",
+"input":"<a",
+"output":["ParseError"]},
+
+{"description":"EOF in before attribute name state",
+"input":"<a ",
+"output":["ParseError"]},
+
+{"description":"EOF in attribute name state",
+"input":"<a a",
+"output":["ParseError"]},
+
+{"description":"EOF in after attribute name state",
+"input":"<a a ",
+"output":["ParseError"]},
+
+{"description":"EOF in before attribute value state",
+"input":"<a a =",
+"output":["ParseError"]},
+
+{"description":"EOF in attribute value (double quoted) state",
+"input":"<a a =\"a",
+"output":["ParseError"]},
+
+{"description":"EOF in attribute value (single quoted) state",
+"input":"<a a ='a",
+"output":["ParseError"]},
+
+{"description":"EOF in attribute value (unquoted) state",
+"input":"<a a =a",
+"output":["ParseError"]},
+
+{"description":"EOF in after attribute value state",
+"input":"<a a ='a'",
+"output":["ParseError"]}
+
+]}
diff --git a/pkgs/html/test/data/tokenizer/unicodeChars.test b/pkgs/html/test/data/tokenizer/unicodeChars.test
new file mode 100644
index 0000000..c778668
--- /dev/null
+++ b/pkgs/html/test/data/tokenizer/unicodeChars.test
@@ -0,0 +1,1295 @@
+{"tests": [
+
+{"description": "Invalid Unicode character U+0001",
+"input": "\u0001",
+"output": ["ParseError", ["Character", "\u0001"]]},
+
+{"description": "Invalid Unicode character U+0002",
+"input": "\u0002",
+"output": ["ParseError", ["Character", "\u0002"]]},
+
+{"description": "Invalid Unicode character U+0003",
+"input": "\u0003",
+"output": ["ParseError", ["Character", "\u0003"]]},
+
+{"description": "Invalid Unicode character U+0004",
+"input": "\u0004",
+"output": ["ParseError", ["Character", "\u0004"]]},
+
+{"description": "Invalid Unicode character U+0005",
+"input": "\u0005",
+"output": ["ParseError", ["Character", "\u0005"]]},
+
+{"description": "Invalid Unicode character U+0006",
+"input": "\u0006",
+"output": ["ParseError", ["Character", "\u0006"]]},
+
+{"description": "Invalid Unicode character U+0007",
+"input": "\u0007",
+"output": ["ParseError", ["Character", "\u0007"]]},
+
+{"description": "Invalid Unicode character U+0008",
+"input": "\u0008",
+"output": ["ParseError", ["Character", "\u0008"]]},
+
+{"description": "Invalid Unicode character U+000B",
+"input": "\u000B",
+"output": ["ParseError", ["Character", "\u000B"]]},
+
+{"description": "Invalid Unicode character U+000E",
+"input": "\u000E",
+"output": ["ParseError", ["Character", "\u000E"]]},
+
+{"description": "Invalid Unicode character U+000F",
+"input": "\u000F",
+"output": ["ParseError", ["Character", "\u000F"]]},
+
+{"description": "Invalid Unicode character U+0010",
+"input": "\u0010",
+"output": ["ParseError", ["Character", "\u0010"]]},
+
+{"description": "Invalid Unicode character U+0011",
+"input": "\u0011",
+"output": ["ParseError", ["Character", "\u0011"]]},
+
+{"description": "Invalid Unicode character U+0012",
+"input": "\u0012",
+"output": ["ParseError", ["Character", "\u0012"]]},
+
+{"description": "Invalid Unicode character U+0013",
+"input": "\u0013",
+"output": ["ParseError", ["Character", "\u0013"]]},
+
+{"description": "Invalid Unicode character U+0014",
+"input": "\u0014",
+"output": ["ParseError", ["Character", "\u0014"]]},
+
+{"description": "Invalid Unicode character U+0015",
+"input": "\u0015",
+"output": ["ParseError", ["Character", "\u0015"]]},
+
+{"description": "Invalid Unicode character U+0016",
+"input": "\u0016",
+"output": ["ParseError", ["Character", "\u0016"]]},
+
+{"description": "Invalid Unicode character U+0017",
+"input": "\u0017",
+"output": ["ParseError", ["Character", "\u0017"]]},
+
+{"description": "Invalid Unicode character U+0018",
+"input": "\u0018",
+"output": ["ParseError", ["Character", "\u0018"]]},
+
+{"description": "Invalid Unicode character U+0019",
+"input": "\u0019",
+"output": ["ParseError", ["Character", "\u0019"]]},
+
+{"description": "Invalid Unicode character U+001A",
+"input": "\u001A",
+"output": ["ParseError", ["Character", "\u001A"]]},
+
+{"description": "Invalid Unicode character U+001B",
+"input": "\u001B",
+"output": ["ParseError", ["Character", "\u001B"]]},
+
+{"description": "Invalid Unicode character U+001C",
+"input": "\u001C",
+"output": ["ParseError", ["Character", "\u001C"]]},
+
+{"description": "Invalid Unicode character U+001D",
+"input": "\u001D",
+"output": ["ParseError", ["Character", "\u001D"]]},
+
+{"description": "Invalid Unicode character U+001E",
+"input": "\u001E",
+"output": ["ParseError", ["Character", "\u001E"]]},
+
+{"description": "Invalid Unicode character U+001F",
+"input": "\u001F",
+"output": ["ParseError", ["Character", "\u001F"]]},
+
+{"description": "Invalid Unicode character U+007F",
+"input": "\u007F",
+"output": ["ParseError", ["Character", "\u007F"]]},
+
+{"description": "Invalid Unicode character U+FDD0",
+"input": "\uFDD0",
+"output": ["ParseError", ["Character", "\uFDD0"]]},
+
+{"description": "Invalid Unicode character U+FDD1",
+"input": "\uFDD1",
+"output": ["ParseError", ["Character", "\uFDD1"]]},
+
+{"description": "Invalid Unicode character U+FDD2",
+"input": "\uFDD2",
+"output": ["ParseError", ["Character", "\uFDD2"]]},
+
+{"description": "Invalid Unicode character U+FDD3",
+"input": "\uFDD3",
+"output": ["ParseError", ["Character", "\uFDD3"]]},
+
+{"description": "Invalid Unicode character U+FDD4",
+"input": "\uFDD4",
+"output": ["ParseError", ["Character", "\uFDD4"]]},
+
+{"description": "Invalid Unicode character U+FDD5",
+"input": "\uFDD5",
+"output": ["ParseError", ["Character", "\uFDD5"]]},
+
+{"description": "Invalid Unicode character U+FDD6",
+"input": "\uFDD6",
+"output": ["ParseError", ["Character", "\uFDD6"]]},
+
+{"description": "Invalid Unicode character U+FDD7",
+"input": "\uFDD7",
+"output": ["ParseError", ["Character", "\uFDD7"]]},
+
+{"description": "Invalid Unicode character U+FDD8",
+"input": "\uFDD8",
+"output": ["ParseError", ["Character", "\uFDD8"]]},
+
+{"description": "Invalid Unicode character U+FDD9",
+"input": "\uFDD9",
+"output": ["ParseError", ["Character", "\uFDD9"]]},
+
+{"description": "Invalid Unicode character U+FDDA",
+"input": "\uFDDA",
+"output": ["ParseError", ["Character", "\uFDDA"]]},
+
+{"description": "Invalid Unicode character U+FDDB",
+"input": "\uFDDB",
+"output": ["ParseError", ["Character", "\uFDDB"]]},
+
+{"description": "Invalid Unicode character U+FDDC",
+"input": "\uFDDC",
+"output": ["ParseError", ["Character", "\uFDDC"]]},
+
+{"description": "Invalid Unicode character U+FDDD",
+"input": "\uFDDD",
+"output": ["ParseError", ["Character", "\uFDDD"]]},
+
+{"description": "Invalid Unicode character U+FDDE",
+"input": "\uFDDE",
+"output": ["ParseError", ["Character", "\uFDDE"]]},
+
+{"description": "Invalid Unicode character U+FDDF",
+"input": "\uFDDF",
+"output": ["ParseError", ["Character", "\uFDDF"]]},
+
+{"description": "Invalid Unicode character U+FDE0",
+"input": "\uFDE0",
+"output": ["ParseError", ["Character", "\uFDE0"]]},
+
+{"description": "Invalid Unicode character U+FDE1",
+"input": "\uFDE1",
+"output": ["ParseError", ["Character", "\uFDE1"]]},
+
+{"description": "Invalid Unicode character U+FDE2",
+"input": "\uFDE2",
+"output": ["ParseError", ["Character", "\uFDE2"]]},
+
+{"description": "Invalid Unicode character U+FDE3",
+"input": "\uFDE3",
+"output": ["ParseError", ["Character", "\uFDE3"]]},
+
+{"description": "Invalid Unicode character U+FDE4",
+"input": "\uFDE4",
+"output": ["ParseError", ["Character", "\uFDE4"]]},
+
+{"description": "Invalid Unicode character U+FDE5",
+"input": "\uFDE5",
+"output": ["ParseError", ["Character", "\uFDE5"]]},
+
+{"description": "Invalid Unicode character U+FDE6",
+"input": "\uFDE6",
+"output": ["ParseError", ["Character", "\uFDE6"]]},
+
+{"description": "Invalid Unicode character U+FDE7",
+"input": "\uFDE7",
+"output": ["ParseError", ["Character", "\uFDE7"]]},
+
+{"description": "Invalid Unicode character U+FDE8",
+"input": "\uFDE8",
+"output": ["ParseError", ["Character", "\uFDE8"]]},
+
+{"description": "Invalid Unicode character U+FDE9",
+"input": "\uFDE9",
+"output": ["ParseError", ["Character", "\uFDE9"]]},
+
+{"description": "Invalid Unicode character U+FDEA",
+"input": "\uFDEA",
+"output": ["ParseError", ["Character", "\uFDEA"]]},
+
+{"description": "Invalid Unicode character U+FDEB",
+"input": "\uFDEB",
+"output": ["ParseError", ["Character", "\uFDEB"]]},
+
+{"description": "Invalid Unicode character U+FDEC",
+"input": "\uFDEC",
+"output": ["ParseError", ["Character", "\uFDEC"]]},
+
+{"description": "Invalid Unicode character U+FDED",
+"input": "\uFDED",
+"output": ["ParseError", ["Character", "\uFDED"]]},
+
+{"description": "Invalid Unicode character U+FDEE",
+"input": "\uFDEE",
+"output": ["ParseError", ["Character", "\uFDEE"]]},
+
+{"description": "Invalid Unicode character U+FDEF",
+"input": "\uFDEF",
+"output": ["ParseError", ["Character", "\uFDEF"]]},
+
+{"description": "Invalid Unicode character U+FFFE",
+"input": "\uFFFE",
+"output": ["ParseError", ["Character", "\uFFFE"]]},
+
+{"description": "Invalid Unicode character U+FFFF",
+"input": "\uFFFF",
+"output": ["ParseError", ["Character", "\uFFFF"]]},
+
+{"description": "Invalid Unicode character U+1FFFE",
+"input": "\uD83F\uDFFE",
+"output": ["ParseError", ["Character", "\uD83F\uDFFE"]]},
+
+{"description": "Invalid Unicode character U+1FFFF",
+"input": "\uD83F\uDFFF",
+"output": ["ParseError", ["Character", "\uD83F\uDFFF"]]},
+
+{"description": "Invalid Unicode character U+2FFFE",
+"input": "\uD87F\uDFFE",
+"output": ["ParseError", ["Character", "\uD87F\uDFFE"]]},
+
+{"description": "Invalid Unicode character U+2FFFF",
+"input": "\uD87F\uDFFF",
+"output": ["ParseError", ["Character", "\uD87F\uDFFF"]]},
+
+{"description": "Invalid Unicode character U+3FFFE",
+"input": "\uD8BF\uDFFE",
+"output": ["ParseError", ["Character", "\uD8BF\uDFFE"]]},
+
+{"description": "Invalid Unicode character U+3FFFF",
+"input": "\uD8BF\uDFFF",
+"output": ["ParseError", ["Character", "\uD8BF\uDFFF"]]},
+
+{"description": "Invalid Unicode character U+4FFFE",
+"input": "\uD8FF\uDFFE",
+"output": ["ParseError", ["Character", "\uD8FF\uDFFE"]]},
+
+{"description": "Invalid Unicode character U+4FFFF",
+"input": "\uD8FF\uDFFF",
+"output": ["ParseError", ["Character", "\uD8FF\uDFFF"]]},
+
+{"description": "Invalid Unicode character U+5FFFE",
+"input": "\uD93F\uDFFE",
+"output": ["ParseError", ["Character", "\uD93F\uDFFE"]]},
+
+{"description": "Invalid Unicode character U+5FFFF",
+"input": "\uD93F\uDFFF",
+"output": ["ParseError", ["Character", "\uD93F\uDFFF"]]},
+
+{"description": "Invalid Unicode character U+6FFFE",
+"input": "\uD97F\uDFFE",
+"output": ["ParseError", ["Character", "\uD97F\uDFFE"]]},
+
+{"description": "Invalid Unicode character U+6FFFF",
+"input": "\uD97F\uDFFF",
+"output": ["ParseError", ["Character", "\uD97F\uDFFF"]]},
+
+{"description": "Invalid Unicode character U+7FFFE",
+"input": "\uD9BF\uDFFE",
+"output": ["ParseError", ["Character", "\uD9BF\uDFFE"]]},
+
+{"description": "Invalid Unicode character U+7FFFF",
+"input": "\uD9BF\uDFFF",
+"output": ["ParseError", ["Character", "\uD9BF\uDFFF"]]},
+
+{"description": "Invalid Unicode character U+8FFFE",
+"input": "\uD9FF\uDFFE",
+"output": ["ParseError", ["Character", "\uD9FF\uDFFE"]]},
+
+{"description": "Invalid Unicode character U+8FFFF",
+"input": "\uD9FF\uDFFF",
+"output": ["ParseError", ["Character", "\uD9FF\uDFFF"]]},
+
+{"description": "Invalid Unicode character U+9FFFE",
+"input": "\uDA3F\uDFFE",
+"output": ["ParseError", ["Character", "\uDA3F\uDFFE"]]},
+
+{"description": "Invalid Unicode character U+9FFFF",
+"input": "\uDA3F\uDFFF",
+"output": ["ParseError", ["Character", "\uDA3F\uDFFF"]]},
+
+{"description": "Invalid Unicode character U+AFFFE",
+"input": "\uDA7F\uDFFE",
+"output": ["ParseError", ["Character", "\uDA7F\uDFFE"]]},
+
+{"description": "Invalid Unicode character U+AFFFF",
+"input": "\uDA7F\uDFFF",
+"output": ["ParseError", ["Character", "\uDA7F\uDFFF"]]},
+
+{"description": "Invalid Unicode character U+BFFFE",
+"input": "\uDABF\uDFFE",
+"output": ["ParseError", ["Character", "\uDABF\uDFFE"]]},
+
+{"description": "Invalid Unicode character U+BFFFF",
+"input": "\uDABF\uDFFF",
+"output": ["ParseError", ["Character", "\uDABF\uDFFF"]]},
+
+{"description": "Invalid Unicode character U+CFFFE",
+"input": "\uDAFF\uDFFE",
+"output": ["ParseError", ["Character", "\uDAFF\uDFFE"]]},
+
+{"description": "Invalid Unicode character U+CFFFF",
+"input": "\uDAFF\uDFFF",
+"output": ["ParseError", ["Character", "\uDAFF\uDFFF"]]},
+
+{"description": "Invalid Unicode character U+DFFFE",
+"input": "\uDB3F\uDFFE",
+"output": ["ParseError", ["Character", "\uDB3F\uDFFE"]]},
+
+{"description": "Invalid Unicode character U+DFFFF",
+"input": "\uDB3F\uDFFF",
+"output": ["ParseError", ["Character", "\uDB3F\uDFFF"]]},
+
+{"description": "Invalid Unicode character U+EFFFE",
+"input": "\uDB7F\uDFFE",
+"output": ["ParseError", ["Character", "\uDB7F\uDFFE"]]},
+
+{"description": "Invalid Unicode character U+EFFFF",
+"input": "\uDB7F\uDFFF",
+"output": ["ParseError", ["Character", "\uDB7F\uDFFF"]]},
+
+{"description": "Invalid Unicode character U+FFFFE",
+"input": "\uDBBF\uDFFE",
+"output": ["ParseError", ["Character", "\uDBBF\uDFFE"]]},
+
+{"description": "Invalid Unicode character U+FFFFF",
+"input": "\uDBBF\uDFFF",
+"output": ["ParseError", ["Character", "\uDBBF\uDFFF"]]},
+
+{"description": "Invalid Unicode character U+10FFFE",
+"input": "\uDBFF\uDFFE",
+"output": ["ParseError", ["Character", "\uDBFF\uDFFE"]]},
+
+{"description": "Invalid Unicode character U+10FFFF",
+"input": "\uDBFF\uDFFF",
+"output": ["ParseError", ["Character", "\uDBFF\uDFFF"]]},
+
+{"description": "Valid Unicode character U+0009",
+"input": "\u0009",
+"output": [["Character", "\u0009"]]},
+
+{"description": "Valid Unicode character U+000A",
+"input": "\u000A",
+"output": [["Character", "\u000A"]]},
+
+{"description": "Valid Unicode character U+0020",
+"input": "\u0020",
+"output": [["Character", "\u0020"]]},
+
+{"description": "Valid Unicode character U+0021",
+"input": "\u0021",
+"output": [["Character", "\u0021"]]},
+
+{"description": "Valid Unicode character U+0022",
+"input": "\u0022",
+"output": [["Character", "\u0022"]]},
+
+{"description": "Valid Unicode character U+0023",
+"input": "\u0023",
+"output": [["Character", "\u0023"]]},
+
+{"description": "Valid Unicode character U+0024",
+"input": "\u0024",
+"output": [["Character", "\u0024"]]},
+
+{"description": "Valid Unicode character U+0025",
+"input": "\u0025",
+"output": [["Character", "\u0025"]]},
+
+{"description": "Valid Unicode character U+0026",
+"input": "\u0026",
+"output": [["Character", "\u0026"]]},
+
+{"description": "Valid Unicode character U+0027",
+"input": "\u0027",
+"output": [["Character", "\u0027"]]},
+
+{"description": "Valid Unicode character U+0028",
+"input": "\u0028",
+"output": [["Character", "\u0028"]]},
+
+{"description": "Valid Unicode character U+0029",
+"input": "\u0029",
+"output": [["Character", "\u0029"]]},
+
+{"description": "Valid Unicode character U+002A",
+"input": "\u002A",
+"output": [["Character", "\u002A"]]},
+
+{"description": "Valid Unicode character U+002B",
+"input": "\u002B",
+"output": [["Character", "\u002B"]]},
+
+{"description": "Valid Unicode character U+002C",
+"input": "\u002C",
+"output": [["Character", "\u002C"]]},
+
+{"description": "Valid Unicode character U+002D",
+"input": "\u002D",
+"output": [["Character", "\u002D"]]},
+
+{"description": "Valid Unicode character U+002E",
+"input": "\u002E",
+"output": [["Character", "\u002E"]]},
+
+{"description": "Valid Unicode character U+002F",
+"input": "\u002F",
+"output": [["Character", "\u002F"]]},
+
+{"description": "Valid Unicode character U+0030",
+"input": "\u0030",
+"output": [["Character", "\u0030"]]},
+
+{"description": "Valid Unicode character U+0031",
+"input": "\u0031",
+"output": [["Character", "\u0031"]]},
+
+{"description": "Valid Unicode character U+0032",
+"input": "\u0032",
+"output": [["Character", "\u0032"]]},
+
+{"description": "Valid Unicode character U+0033",
+"input": "\u0033",
+"output": [["Character", "\u0033"]]},
+
+{"description": "Valid Unicode character U+0034",
+"input": "\u0034",
+"output": [["Character", "\u0034"]]},
+
+{"description": "Valid Unicode character U+0035",
+"input": "\u0035",
+"output": [["Character", "\u0035"]]},
+
+{"description": "Valid Unicode character U+0036",
+"input": "\u0036",
+"output": [["Character", "\u0036"]]},
+
+{"description": "Valid Unicode character U+0037",
+"input": "\u0037",
+"output": [["Character", "\u0037"]]},
+
+{"description": "Valid Unicode character U+0038",
+"input": "\u0038",
+"output": [["Character", "\u0038"]]},
+
+{"description": "Valid Unicode character U+0039",
+"input": "\u0039",
+"output": [["Character", "\u0039"]]},
+
+{"description": "Valid Unicode character U+003A",
+"input": "\u003A",
+"output": [["Character", "\u003A"]]},
+
+{"description": "Valid Unicode character U+003B",
+"input": "\u003B",
+"output": [["Character", "\u003B"]]},
+
+{"description": "Valid Unicode character U+003D",
+"input": "\u003D",
+"output": [["Character", "\u003D"]]},
+
+{"description": "Valid Unicode character U+003E",
+"input": "\u003E",
+"output": [["Character", "\u003E"]]},
+
+{"description": "Valid Unicode character U+003F",
+"input": "\u003F",
+"output": [["Character", "\u003F"]]},
+
+{"description": "Valid Unicode character U+0040",
+"input": "\u0040",
+"output": [["Character", "\u0040"]]},
+
+{"description": "Valid Unicode character U+0041",
+"input": "\u0041",
+"output": [["Character", "\u0041"]]},
+
+{"description": "Valid Unicode character U+0042",
+"input": "\u0042",
+"output": [["Character", "\u0042"]]},
+
+{"description": "Valid Unicode character U+0043",
+"input": "\u0043",
+"output": [["Character", "\u0043"]]},
+
+{"description": "Valid Unicode character U+0044",
+"input": "\u0044",
+"output": [["Character", "\u0044"]]},
+
+{"description": "Valid Unicode character U+0045",
+"input": "\u0045",
+"output": [["Character", "\u0045"]]},
+
+{"description": "Valid Unicode character U+0046",
+"input": "\u0046",
+"output": [["Character", "\u0046"]]},
+
+{"description": "Valid Unicode character U+0047",
+"input": "\u0047",
+"output": [["Character", "\u0047"]]},
+
+{"description": "Valid Unicode character U+0048",
+"input": "\u0048",
+"output": [["Character", "\u0048"]]},
+
+{"description": "Valid Unicode character U+0049",
+"input": "\u0049",
+"output": [["Character", "\u0049"]]},
+
+{"description": "Valid Unicode character U+004A",
+"input": "\u004A",
+"output": [["Character", "\u004A"]]},
+
+{"description": "Valid Unicode character U+004B",
+"input": "\u004B",
+"output": [["Character", "\u004B"]]},
+
+{"description": "Valid Unicode character U+004C",
+"input": "\u004C",
+"output": [["Character", "\u004C"]]},
+
+{"description": "Valid Unicode character U+004D",
+"input": "\u004D",
+"output": [["Character", "\u004D"]]},
+
+{"description": "Valid Unicode character U+004E",
+"input": "\u004E",
+"output": [["Character", "\u004E"]]},
+
+{"description": "Valid Unicode character U+004F",
+"input": "\u004F",
+"output": [["Character", "\u004F"]]},
+
+{"description": "Valid Unicode character U+0050",
+"input": "\u0050",
+"output": [["Character", "\u0050"]]},
+
+{"description": "Valid Unicode character U+0051",
+"input": "\u0051",
+"output": [["Character", "\u0051"]]},
+
+{"description": "Valid Unicode character U+0052",
+"input": "\u0052",
+"output": [["Character", "\u0052"]]},
+
+{"description": "Valid Unicode character U+0053",
+"input": "\u0053",
+"output": [["Character", "\u0053"]]},
+
+{"description": "Valid Unicode character U+0054",
+"input": "\u0054",
+"output": [["Character", "\u0054"]]},
+
+{"description": "Valid Unicode character U+0055",
+"input": "\u0055",
+"output": [["Character", "\u0055"]]},
+
+{"description": "Valid Unicode character U+0056",
+"input": "\u0056",
+"output": [["Character", "\u0056"]]},
+
+{"description": "Valid Unicode character U+0057",
+"input": "\u0057",
+"output": [["Character", "\u0057"]]},
+
+{"description": "Valid Unicode character U+0058",
+"input": "\u0058",
+"output": [["Character", "\u0058"]]},
+
+{"description": "Valid Unicode character U+0059",
+"input": "\u0059",
+"output": [["Character", "\u0059"]]},
+
+{"description": "Valid Unicode character U+005A",
+"input": "\u005A",
+"output": [["Character", "\u005A"]]},
+
+{"description": "Valid Unicode character U+005B",
+"input": "\u005B",
+"output": [["Character", "\u005B"]]},
+
+{"description": "Valid Unicode character U+005C",
+"input": "\u005C",
+"output": [["Character", "\u005C"]]},
+
+{"description": "Valid Unicode character U+005D",
+"input": "\u005D",
+"output": [["Character", "\u005D"]]},
+
+{"description": "Valid Unicode character U+005E",
+"input": "\u005E",
+"output": [["Character", "\u005E"]]},
+
+{"description": "Valid Unicode character U+005F",
+"input": "\u005F",
+"output": [["Character", "\u005F"]]},
+
+{"description": "Valid Unicode character U+0060",
+"input": "\u0060",
+"output": [["Character", "\u0060"]]},
+
+{"description": "Valid Unicode character U+0061",
+"input": "\u0061",
+"output": [["Character", "\u0061"]]},
+
+{"description": "Valid Unicode character U+0062",
+"input": "\u0062",
+"output": [["Character", "\u0062"]]},
+
+{"description": "Valid Unicode character U+0063",
+"input": "\u0063",
+"output": [["Character", "\u0063"]]},
+
+{"description": "Valid Unicode character U+0064",
+"input": "\u0064",
+"output": [["Character", "\u0064"]]},
+
+{"description": "Valid Unicode character U+0065",
+"input": "\u0065",
+"output": [["Character", "\u0065"]]},
+
+{"description": "Valid Unicode character U+0066",
+"input": "\u0066",
+"output": [["Character", "\u0066"]]},
+
+{"description": "Valid Unicode character U+0067",
+"input": "\u0067",
+"output": [["Character", "\u0067"]]},
+
+{"description": "Valid Unicode character U+0068",
+"input": "\u0068",
+"output": [["Character", "\u0068"]]},
+
+{"description": "Valid Unicode character U+0069",
+"input": "\u0069",
+"output": [["Character", "\u0069"]]},
+
+{"description": "Valid Unicode character U+006A",
+"input": "\u006A",
+"output": [["Character", "\u006A"]]},
+
+{"description": "Valid Unicode character U+006B",
+"input": "\u006B",
+"output": [["Character", "\u006B"]]},
+
+{"description": "Valid Unicode character U+006C",
+"input": "\u006C",
+"output": [["Character", "\u006C"]]},
+
+{"description": "Valid Unicode character U+006D",
+"input": "\u006D",
+"output": [["Character", "\u006D"]]},
+
+{"description": "Valid Unicode character U+006E",
+"input": "\u006E",
+"output": [["Character", "\u006E"]]},
+
+{"description": "Valid Unicode character U+006F",
+"input": "\u006F",
+"output": [["Character", "\u006F"]]},
+
+{"description": "Valid Unicode character U+0070",
+"input": "\u0070",
+"output": [["Character", "\u0070"]]},
+
+{"description": "Valid Unicode character U+0071",
+"input": "\u0071",
+"output": [["Character", "\u0071"]]},
+
+{"description": "Valid Unicode character U+0072",
+"input": "\u0072",
+"output": [["Character", "\u0072"]]},
+
+{"description": "Valid Unicode character U+0073",
+"input": "\u0073",
+"output": [["Character", "\u0073"]]},
+
+{"description": "Valid Unicode character U+0074",
+"input": "\u0074",
+"output": [["Character", "\u0074"]]},
+
+{"description": "Valid Unicode character U+0075",
+"input": "\u0075",
+"output": [["Character", "\u0075"]]},
+
+{"description": "Valid Unicode character U+0076",
+"input": "\u0076",
+"output": [["Character", "\u0076"]]},
+
+{"description": "Valid Unicode character U+0077",
+"input": "\u0077",
+"output": [["Character", "\u0077"]]},
+
+{"description": "Valid Unicode character U+0078",
+"input": "\u0078",
+"output": [["Character", "\u0078"]]},
+
+{"description": "Valid Unicode character U+0079",
+"input": "\u0079",
+"output": [["Character", "\u0079"]]},
+
+{"description": "Valid Unicode character U+007A",
+"input": "\u007A",
+"output": [["Character", "\u007A"]]},
+
+{"description": "Valid Unicode character U+007B",
+"input": "\u007B",
+"output": [["Character", "\u007B"]]},
+
+{"description": "Valid Unicode character U+007C",
+"input": "\u007C",
+"output": [["Character", "\u007C"]]},
+
+{"description": "Valid Unicode character U+007D",
+"input": "\u007D",
+"output": [["Character", "\u007D"]]},
+
+{"description": "Valid Unicode character U+007E",
+"input": "\u007E",
+"output": [["Character", "\u007E"]]},
+
+{"description": "Valid Unicode character U+00A0",
+"input": "\u00A0",
+"output": [["Character", "\u00A0"]]},
+
+{"description": "Valid Unicode character U+00A1",
+"input": "\u00A1",
+"output": [["Character", "\u00A1"]]},
+
+{"description": "Valid Unicode character U+00A2",
+"input": "\u00A2",
+"output": [["Character", "\u00A2"]]},
+
+{"description": "Valid Unicode character U+00A3",
+"input": "\u00A3",
+"output": [["Character", "\u00A3"]]},
+
+{"description": "Valid Unicode character U+00A4",
+"input": "\u00A4",
+"output": [["Character", "\u00A4"]]},
+
+{"description": "Valid Unicode character U+00A5",
+"input": "\u00A5",
+"output": [["Character", "\u00A5"]]},
+
+{"description": "Valid Unicode character U+00A6",
+"input": "\u00A6",
+"output": [["Character", "\u00A6"]]},
+
+{"description": "Valid Unicode character U+00A7",
+"input": "\u00A7",
+"output": [["Character", "\u00A7"]]},
+
+{"description": "Valid Unicode character U+00A8",
+"input": "\u00A8",
+"output": [["Character", "\u00A8"]]},
+
+{"description": "Valid Unicode character U+00A9",
+"input": "\u00A9",
+"output": [["Character", "\u00A9"]]},
+
+{"description": "Valid Unicode character U+00AA",
+"input": "\u00AA",
+"output": [["Character", "\u00AA"]]},
+
+{"description": "Valid Unicode character U+00AB",
+"input": "\u00AB",
+"output": [["Character", "\u00AB"]]},
+
+{"description": "Valid Unicode character U+00AC",
+"input": "\u00AC",
+"output": [["Character", "\u00AC"]]},
+
+{"description": "Valid Unicode character U+00AD",
+"input": "\u00AD",
+"output": [["Character", "\u00AD"]]},
+
+{"description": "Valid Unicode character U+00AE",
+"input": "\u00AE",
+"output": [["Character", "\u00AE"]]},
+
+{"description": "Valid Unicode character U+00AF",
+"input": "\u00AF",
+"output": [["Character", "\u00AF"]]},
+
+{"description": "Valid Unicode character U+00B0",
+"input": "\u00B0",
+"output": [["Character", "\u00B0"]]},
+
+{"description": "Valid Unicode character U+00B1",
+"input": "\u00B1",
+"output": [["Character", "\u00B1"]]},
+
+{"description": "Valid Unicode character U+00B2",
+"input": "\u00B2",
+"output": [["Character", "\u00B2"]]},
+
+{"description": "Valid Unicode character U+00B3",
+"input": "\u00B3",
+"output": [["Character", "\u00B3"]]},
+
+{"description": "Valid Unicode character U+00B4",
+"input": "\u00B4",
+"output": [["Character", "\u00B4"]]},
+
+{"description": "Valid Unicode character U+00B5",
+"input": "\u00B5",
+"output": [["Character", "\u00B5"]]},
+
+{"description": "Valid Unicode character U+00B6",
+"input": "\u00B6",
+"output": [["Character", "\u00B6"]]},
+
+{"description": "Valid Unicode character U+00B7",
+"input": "\u00B7",
+"output": [["Character", "\u00B7"]]},
+
+{"description": "Valid Unicode character U+00B8",
+"input": "\u00B8",
+"output": [["Character", "\u00B8"]]},
+
+{"description": "Valid Unicode character U+00B9",
+"input": "\u00B9",
+"output": [["Character", "\u00B9"]]},
+
+{"description": "Valid Unicode character U+00BA",
+"input": "\u00BA",
+"output": [["Character", "\u00BA"]]},
+
+{"description": "Valid Unicode character U+00BB",
+"input": "\u00BB",
+"output": [["Character", "\u00BB"]]},
+
+{"description": "Valid Unicode character U+00BC",
+"input": "\u00BC",
+"output": [["Character", "\u00BC"]]},
+
+{"description": "Valid Unicode character U+00BD",
+"input": "\u00BD",
+"output": [["Character", "\u00BD"]]},
+
+{"description": "Valid Unicode character U+00BE",
+"input": "\u00BE",
+"output": [["Character", "\u00BE"]]},
+
+{"description": "Valid Unicode character U+00BF",
+"input": "\u00BF",
+"output": [["Character", "\u00BF"]]},
+
+{"description": "Valid Unicode character U+00C0",
+"input": "\u00C0",
+"output": [["Character", "\u00C0"]]},
+
+{"description": "Valid Unicode character U+00C1",
+"input": "\u00C1",
+"output": [["Character", "\u00C1"]]},
+
+{"description": "Valid Unicode character U+00C2",
+"input": "\u00C2",
+"output": [["Character", "\u00C2"]]},
+
+{"description": "Valid Unicode character U+00C3",
+"input": "\u00C3",
+"output": [["Character", "\u00C3"]]},
+
+{"description": "Valid Unicode character U+00C4",
+"input": "\u00C4",
+"output": [["Character", "\u00C4"]]},
+
+{"description": "Valid Unicode character U+00C5",
+"input": "\u00C5",
+"output": [["Character", "\u00C5"]]},
+
+{"description": "Valid Unicode character U+00C6",
+"input": "\u00C6",
+"output": [["Character", "\u00C6"]]},
+
+{"description": "Valid Unicode character U+00C7",
+"input": "\u00C7",
+"output": [["Character", "\u00C7"]]},
+
+{"description": "Valid Unicode character U+00C8",
+"input": "\u00C8",
+"output": [["Character", "\u00C8"]]},
+
+{"description": "Valid Unicode character U+00C9",
+"input": "\u00C9",
+"output": [["Character", "\u00C9"]]},
+
+{"description": "Valid Unicode character U+00CA",
+"input": "\u00CA",
+"output": [["Character", "\u00CA"]]},
+
+{"description": "Valid Unicode character U+00CB",
+"input": "\u00CB",
+"output": [["Character", "\u00CB"]]},
+
+{"description": "Valid Unicode character U+00CC",
+"input": "\u00CC",
+"output": [["Character", "\u00CC"]]},
+
+{"description": "Valid Unicode character U+00CD",
+"input": "\u00CD",
+"output": [["Character", "\u00CD"]]},
+
+{"description": "Valid Unicode character U+00CE",
+"input": "\u00CE",
+"output": [["Character", "\u00CE"]]},
+
+{"description": "Valid Unicode character U+00CF",
+"input": "\u00CF",
+"output": [["Character", "\u00CF"]]},
+
+{"description": "Valid Unicode character U+00D0",
+"input": "\u00D0",
+"output": [["Character", "\u00D0"]]},
+
+{"description": "Valid Unicode character U+00D1",
+"input": "\u00D1",
+"output": [["Character", "\u00D1"]]},
+
+{"description": "Valid Unicode character U+00D2",
+"input": "\u00D2",
+"output": [["Character", "\u00D2"]]},
+
+{"description": "Valid Unicode character U+00D3",
+"input": "\u00D3",
+"output": [["Character", "\u00D3"]]},
+
+{"description": "Valid Unicode character U+00D4",
+"input": "\u00D4",
+"output": [["Character", "\u00D4"]]},
+
+{"description": "Valid Unicode character U+00D5",
+"input": "\u00D5",
+"output": [["Character", "\u00D5"]]},
+
+{"description": "Valid Unicode character U+00D6",
+"input": "\u00D6",
+"output": [["Character", "\u00D6"]]},
+
+{"description": "Valid Unicode character U+00D7",
+"input": "\u00D7",
+"output": [["Character", "\u00D7"]]},
+
+{"description": "Valid Unicode character U+00D8",
+"input": "\u00D8",
+"output": [["Character", "\u00D8"]]},
+
+{"description": "Valid Unicode character U+00D9",
+"input": "\u00D9",
+"output": [["Character", "\u00D9"]]},
+
+{"description": "Valid Unicode character U+00DA",
+"input": "\u00DA",
+"output": [["Character", "\u00DA"]]},
+
+{"description": "Valid Unicode character U+00DB",
+"input": "\u00DB",
+"output": [["Character", "\u00DB"]]},
+
+{"description": "Valid Unicode character U+00DC",
+"input": "\u00DC",
+"output": [["Character", "\u00DC"]]},
+
+{"description": "Valid Unicode character U+00DD",
+"input": "\u00DD",
+"output": [["Character", "\u00DD"]]},
+
+{"description": "Valid Unicode character U+00DE",
+"input": "\u00DE",
+"output": [["Character", "\u00DE"]]},
+
+{"description": "Valid Unicode character U+00DF",
+"input": "\u00DF",
+"output": [["Character", "\u00DF"]]},
+
+{"description": "Valid Unicode character U+00E0",
+"input": "\u00E0",
+"output": [["Character", "\u00E0"]]},
+
+{"description": "Valid Unicode character U+00E1",
+"input": "\u00E1",
+"output": [["Character", "\u00E1"]]},
+
+{"description": "Valid Unicode character U+00E2",
+"input": "\u00E2",
+"output": [["Character", "\u00E2"]]},
+
+{"description": "Valid Unicode character U+00E3",
+"input": "\u00E3",
+"output": [["Character", "\u00E3"]]},
+
+{"description": "Valid Unicode character U+00E4",
+"input": "\u00E4",
+"output": [["Character", "\u00E4"]]},
+
+{"description": "Valid Unicode character U+00E5",
+"input": "\u00E5",
+"output": [["Character", "\u00E5"]]},
+
+{"description": "Valid Unicode character U+00E6",
+"input": "\u00E6",
+"output": [["Character", "\u00E6"]]},
+
+{"description": "Valid Unicode character U+00E7",
+"input": "\u00E7",
+"output": [["Character", "\u00E7"]]},
+
+{"description": "Valid Unicode character U+00E8",
+"input": "\u00E8",
+"output": [["Character", "\u00E8"]]},
+
+{"description": "Valid Unicode character U+00E9",
+"input": "\u00E9",
+"output": [["Character", "\u00E9"]]},
+
+{"description": "Valid Unicode character U+00EA",
+"input": "\u00EA",
+"output": [["Character", "\u00EA"]]},
+
+{"description": "Valid Unicode character U+00EB",
+"input": "\u00EB",
+"output": [["Character", "\u00EB"]]},
+
+{"description": "Valid Unicode character U+00EC",
+"input": "\u00EC",
+"output": [["Character", "\u00EC"]]},
+
+{"description": "Valid Unicode character U+00ED",
+"input": "\u00ED",
+"output": [["Character", "\u00ED"]]},
+
+{"description": "Valid Unicode character U+00EE",
+"input": "\u00EE",
+"output": [["Character", "\u00EE"]]},
+
+{"description": "Valid Unicode character U+00EF",
+"input": "\u00EF",
+"output": [["Character", "\u00EF"]]},
+
+{"description": "Valid Unicode character U+00F0",
+"input": "\u00F0",
+"output": [["Character", "\u00F0"]]},
+
+{"description": "Valid Unicode character U+00F1",
+"input": "\u00F1",
+"output": [["Character", "\u00F1"]]},
+
+{"description": "Valid Unicode character U+00F2",
+"input": "\u00F2",
+"output": [["Character", "\u00F2"]]},
+
+{"description": "Valid Unicode character U+00F3",
+"input": "\u00F3",
+"output": [["Character", "\u00F3"]]},
+
+{"description": "Valid Unicode character U+00F4",
+"input": "\u00F4",
+"output": [["Character", "\u00F4"]]},
+
+{"description": "Valid Unicode character U+00F5",
+"input": "\u00F5",
+"output": [["Character", "\u00F5"]]},
+
+{"description": "Valid Unicode character U+00F6",
+"input": "\u00F6",
+"output": [["Character", "\u00F6"]]},
+
+{"description": "Valid Unicode character U+00F7",
+"input": "\u00F7",
+"output": [["Character", "\u00F7"]]},
+
+{"description": "Valid Unicode character U+00F8",
+"input": "\u00F8",
+"output": [["Character", "\u00F8"]]},
+
+{"description": "Valid Unicode character U+00F9",
+"input": "\u00F9",
+"output": [["Character", "\u00F9"]]},
+
+{"description": "Valid Unicode character U+00FA",
+"input": "\u00FA",
+"output": [["Character", "\u00FA"]]},
+
+{"description": "Valid Unicode character U+00FB",
+"input": "\u00FB",
+"output": [["Character", "\u00FB"]]},
+
+{"description": "Valid Unicode character U+00FC",
+"input": "\u00FC",
+"output": [["Character", "\u00FC"]]},
+
+{"description": "Valid Unicode character U+00FD",
+"input": "\u00FD",
+"output": [["Character", "\u00FD"]]},
+
+{"description": "Valid Unicode character U+00FE",
+"input": "\u00FE",
+"output": [["Character", "\u00FE"]]},
+
+{"description": "Valid Unicode character U+00FF",
+"input": "\u00FF",
+"output": [["Character", "\u00FF"]]},
+
+{"description": "Valid Unicode character U+D7FF",
+"input": "\uD7FF",
+"output": [["Character", "\uD7FF"]]},
+
+{"description": "Valid Unicode character U+E000",
+"input": "\uE000",
+"output": [["Character", "\uE000"]]},
+
+{"description": "Valid Unicode character U+FDCF",
+"input": "\uFDCF",
+"output": [["Character", "\uFDCF"]]},
+
+{"description": "Valid Unicode character U+FDF0",
+"input": "\uFDF0",
+"output": [["Character", "\uFDF0"]]},
+
+{"description": "Valid Unicode character U+FFFD",
+"input": "\uFFFD",
+"output": [["Character", "\uFFFD"]]},
+
+{"description": "Valid Unicode character U+10000",
+"input": "\uD800\uDC00",
+"output": [["Character", "\uD800\uDC00"]]},
+
+{"description": "Valid Unicode character U+1FFFD",
+"input": "\uD83F\uDFFD",
+"output": [["Character", "\uD83F\uDFFD"]]},
+
+{"description": "Valid Unicode character U+20000",
+"input": "\uD840\uDC00",
+"output": [["Character", "\uD840\uDC00"]]},
+
+{"description": "Valid Unicode character U+2FFFD",
+"input": "\uD87F\uDFFD",
+"output": [["Character", "\uD87F\uDFFD"]]},
+
+{"description": "Valid Unicode character U+30000",
+"input": "\uD880\uDC00",
+"output": [["Character", "\uD880\uDC00"]]},
+
+{"description": "Valid Unicode character U+3FFFD",
+"input": "\uD8BF\uDFFD",
+"output": [["Character", "\uD8BF\uDFFD"]]},
+
+{"description": "Valid Unicode character U+40000",
+"input": "\uD8C0\uDC00",
+"output": [["Character", "\uD8C0\uDC00"]]},
+
+{"description": "Valid Unicode character U+4FFFD",
+"input": "\uD8FF\uDFFD",
+"output": [["Character", "\uD8FF\uDFFD"]]},
+
+{"description": "Valid Unicode character U+50000",
+"input": "\uD900\uDC00",
+"output": [["Character", "\uD900\uDC00"]]},
+
+{"description": "Valid Unicode character U+5FFFD",
+"input": "\uD93F\uDFFD",
+"output": [["Character", "\uD93F\uDFFD"]]},
+
+{"description": "Valid Unicode character U+60000",
+"input": "\uD940\uDC00",
+"output": [["Character", "\uD940\uDC00"]]},
+
+{"description": "Valid Unicode character U+6FFFD",
+"input": "\uD97F\uDFFD",
+"output": [["Character", "\uD97F\uDFFD"]]},
+
+{"description": "Valid Unicode character U+70000",
+"input": "\uD980\uDC00",
+"output": [["Character", "\uD980\uDC00"]]},
+
+{"description": "Valid Unicode character U+7FFFD",
+"input": "\uD9BF\uDFFD",
+"output": [["Character", "\uD9BF\uDFFD"]]},
+
+{"description": "Valid Unicode character U+80000",
+"input": "\uD9C0\uDC00",
+"output": [["Character", "\uD9C0\uDC00"]]},
+
+{"description": "Valid Unicode character U+8FFFD",
+"input": "\uD9FF\uDFFD",
+"output": [["Character", "\uD9FF\uDFFD"]]},
+
+{"description": "Valid Unicode character U+90000",
+"input": "\uDA00\uDC00",
+"output": [["Character", "\uDA00\uDC00"]]},
+
+{"description": "Valid Unicode character U+9FFFD",
+"input": "\uDA3F\uDFFD",
+"output": [["Character", "\uDA3F\uDFFD"]]},
+
+{"description": "Valid Unicode character U+A0000",
+"input": "\uDA40\uDC00",
+"output": [["Character", "\uDA40\uDC00"]]},
+
+{"description": "Valid Unicode character U+AFFFD",
+"input": "\uDA7F\uDFFD",
+"output": [["Character", "\uDA7F\uDFFD"]]},
+
+{"description": "Valid Unicode character U+B0000",
+"input": "\uDA80\uDC00",
+"output": [["Character", "\uDA80\uDC00"]]},
+
+{"description": "Valid Unicode character U+BFFFD",
+"input": "\uDABF\uDFFD",
+"output": [["Character", "\uDABF\uDFFD"]]},
+
+{"description": "Valid Unicode character U+C0000",
+"input": "\uDAC0\uDC00",
+"output": [["Character", "\uDAC0\uDC00"]]},
+
+{"description": "Valid Unicode character U+CFFFD",
+"input": "\uDAFF\uDFFD",
+"output": [["Character", "\uDAFF\uDFFD"]]},
+
+{"description": "Valid Unicode character U+D0000",
+"input": "\uDB00\uDC00",
+"output": [["Character", "\uDB00\uDC00"]]},
+
+{"description": "Valid Unicode character U+DFFFD",
+"input": "\uDB3F\uDFFD",
+"output": [["Character", "\uDB3F\uDFFD"]]},
+
+{"description": "Valid Unicode character U+E0000",
+"input": "\uDB40\uDC00",
+"output": [["Character", "\uDB40\uDC00"]]},
+
+{"description": "Valid Unicode character U+EFFFD",
+"input": "\uDB7F\uDFFD",
+"output": [["Character", "\uDB7F\uDFFD"]]},
+
+{"description": "Valid Unicode character U+F0000",
+"input": "\uDB80\uDC00",
+"output": [["Character", "\uDB80\uDC00"]]},
+
+{"description": "Valid Unicode character U+FFFFD",
+"input": "\uDBBF\uDFFD",
+"output": [["Character", "\uDBBF\uDFFD"]]},
+
+{"description": "Valid Unicode character U+100000",
+"input": "\uDBC0\uDC00",
+"output": [["Character", "\uDBC0\uDC00"]]},
+
+{"description": "Valid Unicode character U+10FFFD",
+"input": "\uDBFF\uDFFD",
+"output": [["Character", "\uDBFF\uDFFD"]]}
+
+]}
diff --git a/pkgs/html/test/data/tokenizer/unicodeCharsProblematic.test b/pkgs/html/test/data/tokenizer/unicodeCharsProblematic.test
new file mode 100644
index 0000000..cf2fbe6
--- /dev/null
+++ b/pkgs/html/test/data/tokenizer/unicodeCharsProblematic.test
@@ -0,0 +1,27 @@
+{"tests" : [
+{"description": "Invalid Unicode character U+DFFF",
+"doubleEscaped":true,
+"input": "\\uDFFF",
+"output":["ParseError", ["Character", "\\uFFFD"]]},
+
+{"description": "Invalid Unicode character U+D800",
+"doubleEscaped":true,
+"input": "\\uD800",
+"output":["ParseError", ["Character", "\\uFFFD"]]},
+
+{"description": "Invalid Unicode character U+DFFF with valid preceding character",
+"doubleEscaped":true,
+"input": "a\\uDFFF",
+"output":["ParseError", ["Character", "a\\uFFFD"]]},
+
+{"description": "Invalid Unicode character U+D800 with valid following character",
+"doubleEscaped":true,
+"input": "\\uD800a",
+"output":["ParseError", ["Character", "\\uFFFDa"]]},
+
+{"description":"CR followed by U+0000",
+"input":"\r\u0000",
+"output":[["Character", "\n"], "ParseError", ["Character", "\u0000"]],
+"ignoreErrorOrder":true}
+]
+}
\ No newline at end of file
diff --git a/pkgs/html/test/data/tokenizer/unicodeCharsSurrogates.test b/pkgs/html/test/data/tokenizer/unicodeCharsSurrogates.test
new file mode 100644
index 0000000..9b56a98
--- /dev/null
+++ b/pkgs/html/test/data/tokenizer/unicodeCharsSurrogates.test
@@ -0,0 +1,24 @@
+{"tests" : [
+{"description": "Unicode surrogate (emoji)",
+"input": "\uD83D\uDC3C",
+"output":[["Character", "\uD83D\uDC3C"]]},
+
+{"description": "Unicode surrogate (emoji) prefixed by characters",
+"input": "before\uD83D\uDC3C",
+"output":[["Character", "before\uD83D\uDC3C"]]},
+
+{"description": "Unicode surrogate (emoji) suffixed by characters",
+"input": "\uD83D\uDC3Cafter",
+"output":[["Character", "\uD83D\uDC3Cafter"]]},
+
+{"description":"Quoted attribute with surrogate unicode content",
+"generateSpans": true,
+"input":"<a href='\uD83D\uDC3C'/>",
+"output":[["StartTag","a",{"href":"\uD83D\uDC3C"},true,0,14]]},
+
+{"description":"Surrogate unicode content followed by attribute",
+"generateSpans": true,
+"input":"\uD83D\uDC3C<a href='b'/>",
+"output":[["Character", "\uD83D\uDC3C", 0, 2],["StartTag","a",{"href":"b"},true,2,15]]}
+]
+}
\ No newline at end of file
diff --git a/pkgs/html/test/data/tokenizer/xmlViolation.test b/pkgs/html/test/data/tokenizer/xmlViolation.test
new file mode 100644
index 0000000..137d964
--- /dev/null
+++ b/pkgs/html/test/data/tokenizer/xmlViolation.test
@@ -0,0 +1,22 @@
+{"xmlViolationTests": [
+
+{"description":"Non-XML character",
+"input":"a\uFFFFb",
+"ignoreErrorOrder":true,
+"output":["ParseError",["Character","a\uFFFDb"]]},
+
+{"description":"Non-XML space",
+"input":"a\u000Cb",
+"ignoreErrorOrder":true,
+"output":[["Character","a b"]]},
+
+{"description":"Double hyphen in comment",
+"input":"<!-- foo -- bar -->",
+"output":["ParseError",["Comment"," foo - - bar "]]},
+
+{"description":"FF between attributes",
+"input":"<a b=''\u000Cc=''>",
+"output":[["StartTag","a",{"b":"","c":""}]]}
+]}
+
+
diff --git a/pkgs/html/test/data/tree-construction/adoption01.dat b/pkgs/html/test/data/tree-construction/adoption01.dat
new file mode 100644
index 0000000..787e1b0
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/adoption01.dat
@@ -0,0 +1,194 @@
+#data
+<a><p></a></p>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <a>
+| <p>
+| <a>
+
+#data
+<a>1<p>2</a>3</p>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <a>
+| "1"
+| <p>
+| <a>
+| "2"
+| "3"
+
+#data
+<a>1<button>2</a>3</button>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <a>
+| "1"
+| <button>
+| <a>
+| "2"
+| "3"
+
+#data
+<a>1<b>2</a>3</b>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <a>
+| "1"
+| <b>
+| "2"
+| <b>
+| "3"
+
+#data
+<a>1<div>2<div>3</a>4</div>5</div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <a>
+| "1"
+| <div>
+| <a>
+| "2"
+| <div>
+| <a>
+| "3"
+| "4"
+| "5"
+
+#data
+<table><a>1<p>2</a>3</p>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <a>
+| "1"
+| <p>
+| <a>
+| "2"
+| "3"
+| <table>
+
+#data
+<b><b><a><p></a>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <b>
+| <b>
+| <a>
+| <p>
+| <a>
+
+#data
+<b><a><b><p></a>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <b>
+| <a>
+| <b>
+| <b>
+| <p>
+| <a>
+
+#data
+<a><b><b><p></a>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <a>
+| <b>
+| <b>
+| <b>
+| <b>
+| <p>
+| <a>
+
+#data
+<p>1<s id="A">2<b id="B">3</p>4</s>5</b>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <p>
+| "1"
+| <s>
+| id="A"
+| "2"
+| <b>
+| id="B"
+| "3"
+| <s>
+| id="A"
+| <b>
+| id="B"
+| "4"
+| <b>
+| id="B"
+| "5"
+
+#data
+<table><a>1<td>2</td>3</table>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <a>
+| "1"
+| <a>
+| "3"
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| "2"
+
+#data
+<table>A<td>B</td>C</table>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "AC"
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| "B"
+
+#data
+<a><svg><tr><input></a>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <a>
+| <svg svg>
+| <svg tr>
+| <svg input>
diff --git a/pkgs/html/test/data/tree-construction/adoption02.dat b/pkgs/html/test/data/tree-construction/adoption02.dat
new file mode 100644
index 0000000..d18151b
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/adoption02.dat
@@ -0,0 +1,31 @@
+#data
+<b>1<i>2<p>3</b>4
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <b>
+| "1"
+| <i>
+| "2"
+| <i>
+| <p>
+| <b>
+| "3"
+| "4"
+
+#data
+<a><div><style></style><address><a>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <a>
+| <div>
+| <a>
+| <style>
+| <address>
+| <a>
+| <a>
diff --git a/pkgs/html/test/data/tree-construction/comments01.dat b/pkgs/html/test/data/tree-construction/comments01.dat
new file mode 100644
index 0000000..44f1876
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/comments01.dat
@@ -0,0 +1,135 @@
+#data
+FOO<!-- BAR -->BAZ
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <!-- BAR -->
+| "BAZ"
+
+#data
+FOO<!-- BAR --!>BAZ
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <!-- BAR -->
+| "BAZ"
+
+#data
+FOO<!-- BAR -- >BAZ
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <!-- BAR -- >BAZ -->
+
+#data
+FOO<!-- BAR -- <QUX> -- MUX -->BAZ
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <!-- BAR -- <QUX> -- MUX -->
+| "BAZ"
+
+#data
+FOO<!-- BAR -- <QUX> -- MUX --!>BAZ
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <!-- BAR -- <QUX> -- MUX -->
+| "BAZ"
+
+#data
+FOO<!-- BAR -- <QUX> -- MUX -- >BAZ
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <!-- BAR -- <QUX> -- MUX -- >BAZ -->
+
+#data
+FOO<!---->BAZ
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <!-- -->
+| "BAZ"
+
+#data
+FOO<!--->BAZ
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <!-- -->
+| "BAZ"
+
+#data
+FOO<!-->BAZ
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <!-- -->
+| "BAZ"
+
+#data
+<?xml version="1.0">Hi
+#errors
+#document
+| <!-- ?xml version="1.0" -->
+| <html>
+| <head>
+| <body>
+| "Hi"
+
+#data
+<?xml version="1.0">
+#errors
+#document
+| <!-- ?xml version="1.0" -->
+| <html>
+| <head>
+| <body>
+
+#data
+<?xml version
+#errors
+#document
+| <!-- ?xml version -->
+| <html>
+| <head>
+| <body>
+
+#data
+FOO<!----->BAZ
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <!-- - -->
+| "BAZ"
diff --git a/pkgs/html/test/data/tree-construction/doctype01.dat b/pkgs/html/test/data/tree-construction/doctype01.dat
new file mode 100644
index 0000000..ae45732
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/doctype01.dat
@@ -0,0 +1,370 @@
+#data
+<!DOCTYPE html>Hello
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| "Hello"
+
+#data
+<!dOctYpE HtMl>Hello
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| "Hello"
+
+#data
+<!DOCTYPEhtml>Hello
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| "Hello"
+
+#data
+<!DOCTYPE>Hello
+#errors
+#document
+| <!DOCTYPE >
+| <html>
+| <head>
+| <body>
+| "Hello"
+
+#data
+<!DOCTYPE >Hello
+#errors
+#document
+| <!DOCTYPE >
+| <html>
+| <head>
+| <body>
+| "Hello"
+
+#data
+<!DOCTYPE potato>Hello
+#errors
+#document
+| <!DOCTYPE potato>
+| <html>
+| <head>
+| <body>
+| "Hello"
+
+#data
+<!DOCTYPE potato >Hello
+#errors
+#document
+| <!DOCTYPE potato>
+| <html>
+| <head>
+| <body>
+| "Hello"
+
+#data
+<!DOCTYPE potato taco>Hello
+#errors
+#document
+| <!DOCTYPE potato>
+| <html>
+| <head>
+| <body>
+| "Hello"
+
+#data
+<!DOCTYPE potato taco "ddd>Hello
+#errors
+#document
+| <!DOCTYPE potato>
+| <html>
+| <head>
+| <body>
+| "Hello"
+
+#data
+<!DOCTYPE potato sYstEM>Hello
+#errors
+#document
+| <!DOCTYPE potato>
+| <html>
+| <head>
+| <body>
+| "Hello"
+
+#data
+<!DOCTYPE potato sYstEM >Hello
+#errors
+#document
+| <!DOCTYPE potato>
+| <html>
+| <head>
+| <body>
+| "Hello"
+
+#data
+<!DOCTYPE potato sYstEM ggg>Hello
+#errors
+#document
+| <!DOCTYPE potato>
+| <html>
+| <head>
+| <body>
+| "Hello"
+
+#data
+<!DOCTYPE potato SYSTEM taco >Hello
+#errors
+#document
+| <!DOCTYPE potato>
+| <html>
+| <head>
+| <body>
+| "Hello"
+
+#data
+<!DOCTYPE potato SYSTEM 'taco"'>Hello
+#errors
+#document
+| <!DOCTYPE potato "" "taco"">
+| <html>
+| <head>
+| <body>
+| "Hello"
+
+#data
+<!DOCTYPE potato SYSTEM "taco">Hello
+#errors
+#document
+| <!DOCTYPE potato "" "taco">
+| <html>
+| <head>
+| <body>
+| "Hello"
+
+#data
+<!DOCTYPE potato SYSTEM "tai'co">Hello
+#errors
+#document
+| <!DOCTYPE potato "" "tai'co">
+| <html>
+| <head>
+| <body>
+| "Hello"
+
+#data
+<!DOCTYPE potato SYSTEMtaco "ddd">Hello
+#errors
+#document
+| <!DOCTYPE potato>
+| <html>
+| <head>
+| <body>
+| "Hello"
+
+#data
+<!DOCTYPE potato grass SYSTEM taco>Hello
+#errors
+#document
+| <!DOCTYPE potato>
+| <html>
+| <head>
+| <body>
+| "Hello"
+
+#data
+<!DOCTYPE potato pUbLIc>Hello
+#errors
+#document
+| <!DOCTYPE potato>
+| <html>
+| <head>
+| <body>
+| "Hello"
+
+#data
+<!DOCTYPE potato pUbLIc >Hello
+#errors
+#document
+| <!DOCTYPE potato>
+| <html>
+| <head>
+| <body>
+| "Hello"
+
+#data
+<!DOCTYPE potato pUbLIcgoof>Hello
+#errors
+#document
+| <!DOCTYPE potato>
+| <html>
+| <head>
+| <body>
+| "Hello"
+
+#data
+<!DOCTYPE potato PUBLIC goof>Hello
+#errors
+#document
+| <!DOCTYPE potato>
+| <html>
+| <head>
+| <body>
+| "Hello"
+
+#data
+<!DOCTYPE potato PUBLIC "go'of">Hello
+#errors
+#document
+| <!DOCTYPE potato "go'of" "">
+| <html>
+| <head>
+| <body>
+| "Hello"
+
+#data
+<!DOCTYPE potato PUBLIC 'go'of'>Hello
+#errors
+#document
+| <!DOCTYPE potato "go" "">
+| <html>
+| <head>
+| <body>
+| "Hello"
+
+#data
+<!DOCTYPE potato PUBLIC 'go:hh of' >Hello
+#errors
+#document
+| <!DOCTYPE potato "go:hh of" "">
+| <html>
+| <head>
+| <body>
+| "Hello"
+
+#data
+<!DOCTYPE potato PUBLIC "W3C-//dfdf" SYSTEM ggg>Hello
+#errors
+#document
+| <!DOCTYPE potato "W3C-//dfdf" "">
+| <html>
+| <head>
+| <body>
+| "Hello"
+
+#data
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+ "http://www.w3.org/TR/html4/strict.dtd">Hello
+#errors
+#document
+| <!DOCTYPE html "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+| <html>
+| <head>
+| <body>
+| "Hello"
+
+#data
+<!DOCTYPE ...>Hello
+#errors
+#document
+| <!DOCTYPE ...>
+| <html>
+| <head>
+| <body>
+| "Hello"
+
+#data
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+#errors
+#document
+| <!DOCTYPE html "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+| <html>
+| <head>
+| <body>
+
+#data
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
+#errors
+#document
+| <!DOCTYPE html "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
+| <html>
+| <head>
+| <body>
+
+#data
+<!DOCTYPE root-element [SYSTEM OR PUBLIC FPI] "uri" [
+<!-- internal declarations -->
+]>
+#errors
+#document
+| <!DOCTYPE root-element>
+| <html>
+| <head>
+| <body>
+| "]>"
+
+#data
+<!DOCTYPE html PUBLIC
+ "-//WAPFORUM//DTD XHTML Mobile 1.0//EN"
+ "http://www.wapforum.org/DTD/xhtml-mobile10.dtd">
+#errors
+#document
+| <!DOCTYPE html "-//WAPFORUM//DTD XHTML Mobile 1.0//EN" "http://www.wapforum.org/DTD/xhtml-mobile10.dtd">
+| <html>
+| <head>
+| <body>
+
+#data
+<!DOCTYPE HTML SYSTEM "http://www.w3.org/DTD/HTML4-strict.dtd"><body><b>Mine!</b></body>
+#errors
+#document
+| <!DOCTYPE html "" "http://www.w3.org/DTD/HTML4-strict.dtd">
+| <html>
+| <head>
+| <body>
+| <b>
+| "Mine!"
+
+#data
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
+#errors
+#document
+| <!DOCTYPE html "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+| <html>
+| <head>
+| <body>
+
+#data
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"'http://www.w3.org/TR/html4/strict.dtd'>
+#errors
+#document
+| <!DOCTYPE html "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+| <html>
+| <head>
+| <body>
+
+#data
+<!DOCTYPE HTML PUBLIC"-//W3C//DTD HTML 4.01//EN"'http://www.w3.org/TR/html4/strict.dtd'>
+#errors
+#document
+| <!DOCTYPE html "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+| <html>
+| <head>
+| <body>
+
+#data
+<!DOCTYPE HTML PUBLIC'-//W3C//DTD HTML 4.01//EN''http://www.w3.org/TR/html4/strict.dtd'>
+#errors
+#document
+| <!DOCTYPE html "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+| <html>
+| <head>
+| <body>
diff --git a/pkgs/html/test/data/tree-construction/domjs-unsafe.dat b/pkgs/html/test/data/tree-construction/domjs-unsafe.dat
new file mode 100644
index 0000000..905b94e
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/domjs-unsafe.dat
Binary files differ
diff --git a/pkgs/html/test/data/tree-construction/entities01.dat b/pkgs/html/test/data/tree-construction/entities01.dat
new file mode 100644
index 0000000..c8073b7
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/entities01.dat
@@ -0,0 +1,603 @@
+#data
+FOO>BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO>BAR"
+
+#data
+FOO>BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO>BAR"
+
+#data
+FOO> BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO> BAR"
+
+#data
+FOO>;;BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO>;;BAR"
+
+#data
+I'm ¬it; I tell you
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "I'm ¬it; I tell you"
+
+#data
+I'm ∉ I tell you
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "I'm ∉ I tell you"
+
+#data
+FOO& BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO& BAR"
+
+#data
+FOO&<BAR>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO&"
+| <bar>
+
+#data
+FOO&&&>BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO&&&>BAR"
+
+#data
+FOO)BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO)BAR"
+
+#data
+FOOABAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOOABAR"
+
+#data
+FOOABAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOOABAR"
+
+#data
+FOO&#BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO&#BAR"
+
+#data
+FOO&#ZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO&#ZOO"
+
+#data
+FOOºR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOOºR"
+
+#data
+FOO&#xZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO&#xZOO"
+
+#data
+FOO&#XZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO&#XZOO"
+
+#data
+FOO)BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO)BAR"
+
+#data
+FOO䆺R
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO䆺R"
+
+#data
+FOOAZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOOAZOO"
+
+#data
+FOO�ZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO�ZOO"
+
+#data
+FOOxZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOOxZOO"
+
+#data
+FOOyZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOOyZOO"
+
+#data
+FOO€ZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO€ZOO"
+
+#data
+FOOZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOOZOO"
+
+#data
+FOO‚ZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO‚ZOO"
+
+#data
+FOOƒZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOOƒZOO"
+
+#data
+FOO„ZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO„ZOO"
+
+#data
+FOO…ZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO…ZOO"
+
+#data
+FOO†ZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO†ZOO"
+
+#data
+FOO‡ZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO‡ZOO"
+
+#data
+FOOˆZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOOˆZOO"
+
+#data
+FOO‰ZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO‰ZOO"
+
+#data
+FOOŠZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOOŠZOO"
+
+#data
+FOO‹ZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO‹ZOO"
+
+#data
+FOOŒZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOOŒZOO"
+
+#data
+FOOZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOOZOO"
+
+#data
+FOOŽZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOOŽZOO"
+
+#data
+FOOZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOOZOO"
+
+#data
+FOOZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOOZOO"
+
+#data
+FOO‘ZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO‘ZOO"
+
+#data
+FOO’ZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO’ZOO"
+
+#data
+FOO“ZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO“ZOO"
+
+#data
+FOO”ZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO”ZOO"
+
+#data
+FOO•ZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO•ZOO"
+
+#data
+FOO–ZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO–ZOO"
+
+#data
+FOO—ZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO—ZOO"
+
+#data
+FOO˜ZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO˜ZOO"
+
+#data
+FOO™ZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO™ZOO"
+
+#data
+FOOšZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOOšZOO"
+
+#data
+FOO›ZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO›ZOO"
+
+#data
+FOOœZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOOœZOO"
+
+#data
+FOOZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOOZOO"
+
+#data
+FOOžZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOOžZOO"
+
+#data
+FOOŸZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOOŸZOO"
+
+#data
+FOO ZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO ZOO"
+
+#data
+FOO퟿ZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOOZOO"
+
+#data
+FOO�ZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO�ZOO"
+
+#data
+FOO�ZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO�ZOO"
+
+#data
+FOO�ZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO�ZOO"
+
+#data
+FOO�ZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO�ZOO"
+
+#data
+FOOZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOOZOO"
+
+#data
+FOOZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOOZOO"
+
+#data
+FOO􈟔ZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOOZOO"
+
+#data
+FOOZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOOZOO"
+
+#data
+FOO�ZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO�ZOO"
+
+#data
+FOO�ZOO
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO�ZOO"
diff --git a/pkgs/html/test/data/tree-construction/entities02.dat b/pkgs/html/test/data/tree-construction/entities02.dat
new file mode 100644
index 0000000..e2fb42a
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/entities02.dat
@@ -0,0 +1,249 @@
+#data
+<div bar="ZZ>YY"></div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| bar="ZZ>YY"
+
+#data
+<div bar="ZZ&"></div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| bar="ZZ&"
+
+#data
+<div bar='ZZ&'></div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| bar="ZZ&"
+
+#data
+<div bar=ZZ&></div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| bar="ZZ&"
+
+#data
+<div bar="ZZ>=YY"></div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| bar="ZZ>=YY"
+
+#data
+<div bar="ZZ>0YY"></div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| bar="ZZ>0YY"
+
+#data
+<div bar="ZZ>9YY"></div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| bar="ZZ>9YY"
+
+#data
+<div bar="ZZ>aYY"></div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| bar="ZZ>aYY"
+
+#data
+<div bar="ZZ>ZYY"></div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| bar="ZZ>ZYY"
+
+#data
+<div bar="ZZ> YY"></div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| bar="ZZ> YY"
+
+#data
+<div bar="ZZ>"></div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| bar="ZZ>"
+
+#data
+<div bar='ZZ>'></div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| bar="ZZ>"
+
+#data
+<div bar=ZZ>></div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| bar="ZZ>"
+
+#data
+<div bar="ZZ£_id=23"></div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| bar="ZZ£_id=23"
+
+#data
+<div bar="ZZ&prod_id=23"></div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| bar="ZZ&prod_id=23"
+
+#data
+<div bar="ZZ£_id=23"></div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| bar="ZZ£_id=23"
+
+#data
+<div bar="ZZ∏_id=23"></div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| bar="ZZ∏_id=23"
+
+#data
+<div bar="ZZ£=23"></div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| bar="ZZ£=23"
+
+#data
+<div bar="ZZ&prod=23"></div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| bar="ZZ&prod=23"
+
+#data
+<div>ZZ£_id=23</div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| "ZZ£_id=23"
+
+#data
+<div>ZZ&prod_id=23</div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| "ZZ&prod_id=23"
+
+#data
+<div>ZZ£_id=23</div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| "ZZ£_id=23"
+
+#data
+<div>ZZ∏_id=23</div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| "ZZ∏_id=23"
+
+#data
+<div>ZZ£=23</div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| "ZZ£=23"
+
+#data
+<div>ZZ&prod=23</div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| "ZZ&prod=23"
diff --git a/pkgs/html/test/data/tree-construction/html5test-com.dat b/pkgs/html/test/data/tree-construction/html5test-com.dat
new file mode 100644
index 0000000..d7cb71d
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/html5test-com.dat
@@ -0,0 +1,246 @@
+#data
+<div<div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div<div>
+
+#data
+<div foo<bar=''>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| foo<bar=""
+
+#data
+<div foo=`bar`>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| foo="`bar`"
+
+#data
+<div \"foo=''>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| \"foo=""
+
+#data
+<a href='\nbar'></a>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <a>
+| href="\nbar"
+
+#data
+<!DOCTYPE html>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+
+#data
+⟨⟩
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "⟨⟩"
+
+#data
+'
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "'"
+
+#data
+ⅈ
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "ⅈ"
+
+#data
+𝕂
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "𝕂"
+
+#data
+∉
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "∉"
+
+#data
+<?import namespace="foo" implementation="#bar">
+#errors
+#document
+| <!-- ?import namespace="foo" implementation="#bar" -->
+| <html>
+| <head>
+| <body>
+
+#data
+<!--foo--bar-->
+#errors
+#document
+| <!-- foo--bar -->
+| <html>
+| <head>
+| <body>
+
+#data
+<![CDATA[x]]>
+#errors
+#document
+| <!-- [CDATA[x]] -->
+| <html>
+| <head>
+| <body>
+
+#data
+<textarea><!--</textarea>--></textarea>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <textarea>
+| "<!--"
+| "-->"
+
+#data
+<textarea><!--</textarea>-->
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <textarea>
+| "<!--"
+| "-->"
+
+#data
+<style><!--</style>--></style>
+#errors
+#document
+| <html>
+| <head>
+| <style>
+| "<!--"
+| <body>
+| "-->"
+
+#data
+<style><!--</style>-->
+#errors
+#document
+| <html>
+| <head>
+| <style>
+| "<!--"
+| <body>
+| "-->"
+
+#data
+<ul><li>A </li> <li>B</li></ul>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <ul>
+| <li>
+| "A "
+| " "
+| <li>
+| "B"
+
+#data
+<table><form><input type=hidden><input></form><div></div></table>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <input>
+| <div>
+| <table>
+| <form>
+| <input>
+| type="hidden"
+
+#data
+<i>A<b>B<p></i>C</b>D
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <i>
+| "A"
+| <b>
+| "B"
+| <b>
+| <p>
+| <b>
+| <i>
+| "C"
+| "D"
+
+#data
+<div></div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+
+#data
+<svg></svg>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <svg svg>
+
+#data
+<math></math>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <math math>
diff --git a/pkgs/html/test/data/tree-construction/inbody01.dat b/pkgs/html/test/data/tree-construction/inbody01.dat
new file mode 100644
index 0000000..3f2bd37
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/inbody01.dat
@@ -0,0 +1,43 @@
+#data
+<button>1</foo>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <button>
+| "1"
+
+#data
+<foo>1<p>2</foo>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <foo>
+| "1"
+| <p>
+| "2"
+
+#data
+<dd>1</foo>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <dd>
+| "1"
+
+#data
+<foo>1<dd>2</foo>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <foo>
+| "1"
+| <dd>
+| "2"
diff --git a/pkgs/html/test/data/tree-construction/isindex.dat b/pkgs/html/test/data/tree-construction/isindex.dat
new file mode 100644
index 0000000..88325ff
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/isindex.dat
@@ -0,0 +1,40 @@
+#data
+<isindex>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <form>
+| <hr>
+| <label>
+| "This is a searchable index. Enter search keywords: "
+| <input>
+| name="isindex"
+| <hr>
+
+#data
+<isindex name="A" action="B" prompt="C" foo="D">
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <form>
+| action="B"
+| <hr>
+| <label>
+| "C"
+| <input>
+| foo="D"
+| name="isindex"
+| <hr>
+
+#data
+<form><isindex>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <form>
diff --git a/pkgs/html/test/data/tree-construction/pending-spec-changes-plain-text-unsafe.dat b/pkgs/html/test/data/tree-construction/pending-spec-changes-plain-text-unsafe.dat
new file mode 100644
index 0000000..a5ebb1e
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/pending-spec-changes-plain-text-unsafe.dat
Binary files differ
diff --git a/pkgs/html/test/data/tree-construction/pending-spec-changes.dat b/pkgs/html/test/data/tree-construction/pending-spec-changes.dat
new file mode 100644
index 0000000..5a92084
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/pending-spec-changes.dat
@@ -0,0 +1,52 @@
+#data
+<input type="hidden"><frameset>
+#errors
+21: Start tag seen without seeing a doctype first. Expected “<!DOCTYPE html>”.
+31: “frameset” start tag seen.
+31: End of file seen and there were open elements.
+#document
+| <html>
+| <head>
+| <frameset>
+
+#data
+<!DOCTYPE html><table><caption><svg>foo</table>bar
+#errors
+47: End tag “table” did not match the name of the current open element (“svg”).
+47: “table” closed but “caption” was still open.
+47: End tag “table” seen, but there were open elements.
+36: Unclosed element “svg”.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <caption>
+| <svg svg>
+| "foo"
+| "bar"
+
+#data
+<table><tr><td><svg><desc><td></desc><circle>
+#errors
+7: Start tag seen without seeing a doctype first. Expected “<!DOCTYPE html>”.
+30: A table cell was implicitly closed, but there were open elements.
+26: Unclosed element “desc”.
+20: Unclosed element “svg”.
+37: Stray end tag “desc”.
+45: End of file seen and there were open elements.
+45: Unclosed element “circle”.
+7: Unclosed element “table”.
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <svg svg>
+| <svg desc>
+| <td>
+| <circle>
diff --git a/pkgs/html/test/data/tree-construction/plain-text-unsafe.dat b/pkgs/html/test/data/tree-construction/plain-text-unsafe.dat
new file mode 100644
index 0000000..04cc11f
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/plain-text-unsafe.dat
Binary files differ
diff --git a/pkgs/html/test/data/tree-construction/regression_tests.dat b/pkgs/html/test/data/tree-construction/regression_tests.dat
new file mode 100644
index 0000000..aedb804
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/regression_tests.dat
@@ -0,0 +1,13 @@
+#data
+<button><p></button><button></button>
+#info
+Regression test for https://github.com/dart-lang/html/issues/122
+#errors
+Errors are currently untested: https://github.com/dart-lang/html/issues/127
+#document
+| <html>
+| <head>
+| <body>
+| <button>
+| <p>
+| <button>
diff --git a/pkgs/html/test/data/tree-construction/scriptdata01.dat b/pkgs/html/test/data/tree-construction/scriptdata01.dat
new file mode 100644
index 0000000..76b67f4
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/scriptdata01.dat
@@ -0,0 +1,308 @@
+#data
+FOO<script>'Hello'</script>BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <script>
+| "'Hello'"
+| "BAR"
+
+#data
+FOO<script></script>BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <script>
+| "BAR"
+
+#data
+FOO<script></script >BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <script>
+| "BAR"
+
+#data
+FOO<script></script/>BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <script>
+| "BAR"
+
+#data
+FOO<script></script/ >BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <script>
+| "BAR"
+
+#data
+FOO<script type="text/plain"></scriptx>BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <script>
+| type="text/plain"
+| "</scriptx>BAR"
+
+#data
+FOO<script></script foo=">" dd>BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <script>
+| "BAR"
+
+#data
+FOO<script>'<'</script>BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <script>
+| "'<'"
+| "BAR"
+
+#data
+FOO<script>'<!'</script>BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <script>
+| "'<!'"
+| "BAR"
+
+#data
+FOO<script>'<!-'</script>BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <script>
+| "'<!-'"
+| "BAR"
+
+#data
+FOO<script>'<!--'</script>BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <script>
+| "'<!--'"
+| "BAR"
+
+#data
+FOO<script>'<!---'</script>BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <script>
+| "'<!---'"
+| "BAR"
+
+#data
+FOO<script>'<!-->'</script>BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <script>
+| "'<!-->'"
+| "BAR"
+
+#data
+FOO<script>'<!-->'</script>BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <script>
+| "'<!-->'"
+| "BAR"
+
+#data
+FOO<script>'<!-- potato'</script>BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <script>
+| "'<!-- potato'"
+| "BAR"
+
+#data
+FOO<script>'<!-- <sCrIpt'</script>BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <script>
+| "'<!-- <sCrIpt'"
+| "BAR"
+
+#data
+FOO<script type="text/plain">'<!-- <sCrIpt>'</script>BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <script>
+| type="text/plain"
+| "'<!-- <sCrIpt>'</script>BAR"
+
+#data
+FOO<script type="text/plain">'<!-- <sCrIpt> -'</script>BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <script>
+| type="text/plain"
+| "'<!-- <sCrIpt> -'</script>BAR"
+
+#data
+FOO<script type="text/plain">'<!-- <sCrIpt> --'</script>BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <script>
+| type="text/plain"
+| "'<!-- <sCrIpt> --'</script>BAR"
+
+#data
+FOO<script>'<!-- <sCrIpt> -->'</script>BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <script>
+| "'<!-- <sCrIpt> -->'"
+| "BAR"
+
+#data
+FOO<script type="text/plain">'<!-- <sCrIpt> --!>'</script>BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <script>
+| type="text/plain"
+| "'<!-- <sCrIpt> --!>'</script>BAR"
+
+#data
+FOO<script type="text/plain">'<!-- <sCrIpt> -- >'</script>BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <script>
+| type="text/plain"
+| "'<!-- <sCrIpt> -- >'</script>BAR"
+
+#data
+FOO<script type="text/plain">'<!-- <sCrIpt '</script>BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <script>
+| type="text/plain"
+| "'<!-- <sCrIpt '</script>BAR"
+
+#data
+FOO<script type="text/plain">'<!-- <sCrIpt/'</script>BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <script>
+| type="text/plain"
+| "'<!-- <sCrIpt/'</script>BAR"
+
+#data
+FOO<script type="text/plain">'<!-- <sCrIpt\'</script>BAR
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <script>
+| type="text/plain"
+| "'<!-- <sCrIpt\'"
+| "BAR"
+
+#data
+FOO<script type="text/plain">'<!-- <sCrIpt/'</script>BAR</script>QUX
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "FOO"
+| <script>
+| type="text/plain"
+| "'<!-- <sCrIpt/'</script>BAR"
+| "QUX"
diff --git a/pkgs/html/test/data/tree-construction/scripted/adoption01.dat b/pkgs/html/test/data/tree-construction/scripted/adoption01.dat
new file mode 100644
index 0000000..4e08d0e
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/scripted/adoption01.dat
@@ -0,0 +1,15 @@
+#data
+<p><b id="A"><script>document.getElementById("A").id = "B"</script></p>TEXT</b>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <p>
+| <b>
+| id="B"
+| <script>
+| "document.getElementById("A").id = "B""
+| <b>
+| id="A"
+| "TEXT"
diff --git a/pkgs/html/test/data/tree-construction/scripted/ark.dat b/pkgs/html/test/data/tree-construction/scripted/ark.dat
new file mode 100644
index 0000000..acbac41
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/scripted/ark.dat
@@ -0,0 +1,26 @@
+#data
+<p><font size=4><font size=4><font size=4><script>document.getElementsByTagName("font")[2].setAttribute("size", "5");</script><font size=4><p>X
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <p>
+| <font>
+| size="4"
+| <font>
+| size="4"
+| <font>
+| size="5"
+| <script>
+| "document.getElementsByTagName("font")[2].setAttribute("size", "5");"
+| <font>
+| size="4"
+| <p>
+| <font>
+| size="4"
+| <font>
+| size="4"
+| <font>
+| size="4"
+| "X"
diff --git a/pkgs/html/test/data/tree-construction/scripted/webkit01.dat b/pkgs/html/test/data/tree-construction/scripted/webkit01.dat
new file mode 100644
index 0000000..ef4a41c
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/scripted/webkit01.dat
@@ -0,0 +1,28 @@
+#data
+1<script>document.write("2")</script>3
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "1"
+| <script>
+| "document.write("2")"
+| "23"
+
+#data
+1<script>document.write("<script>document.write('2')</scr"+ "ipt><script>document.write('3')</scr" + "ipt>")</script>4
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "1"
+| <script>
+| "document.write("<script>document.write('2')</scr"+ "ipt><script>document.write('3')</scr" + "ipt>")"
+| <script>
+| "document.write('2')"
+| "2"
+| <script>
+| "document.write('3')"
+| "34"
diff --git a/pkgs/html/test/data/tree-construction/tables01.dat b/pkgs/html/test/data/tree-construction/tables01.dat
new file mode 100644
index 0000000..c4b47e4
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/tables01.dat
@@ -0,0 +1,212 @@
+#data
+<table><th>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <th>
+
+#data
+<table><td>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+
+#data
+<table><col foo='bar'>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <colgroup>
+| <col>
+| foo="bar"
+
+#data
+<table><colgroup></html>foo
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "foo"
+| <table>
+| <colgroup>
+
+#data
+<table></table><p>foo
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <p>
+| "foo"
+
+#data
+<table></body></caption></col></colgroup></html></tbody></td></tfoot></th></thead></tr><td>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+
+#data
+<table><select><option>3</select></table>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <select>
+| <option>
+| "3"
+| <table>
+
+#data
+<table><select><table></table></select></table>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <select>
+| <table>
+| <table>
+
+#data
+<table><select></table>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <select>
+| <table>
+
+#data
+<table><select><option>A<tr><td>B</td></tr></table>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <select>
+| <option>
+| "A"
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| "B"
+
+#data
+<table><td></body></caption></col></colgroup></html>foo
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| "foo"
+
+#data
+<table><td>A</table>B
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| "A"
+| "B"
+
+#data
+<table><tr><caption>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <caption>
+
+#data
+<table><tr></body></caption></col></colgroup></html></td></th><td>foo
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| "foo"
+
+#data
+<table><td><tr>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <tr>
+
+#data
+<table><td><button><td>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <button>
+| <td>
+
+#data
+<table><tr><td><svg><desc><td>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <svg svg>
+| <svg desc>
+| <td>
diff --git a/pkgs/html/test/data/tree-construction/tests1.dat b/pkgs/html/test/data/tree-construction/tests1.dat
new file mode 100644
index 0000000..cbf8bdd
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/tests1.dat
@@ -0,0 +1,1952 @@
+#data
+Test
+#errors
+Line: 1 Col: 4 Unexpected non-space characters. Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+| "Test"
+
+#data
+<p>One<p>Two
+#errors
+Line: 1 Col: 3 Unexpected start tag (p). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+| <p>
+| "One"
+| <p>
+| "Two"
+
+#data
+Line1<br>Line2<br>Line3<br>Line4
+#errors
+Line: 1 Col: 5 Unexpected non-space characters. Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+| "Line1"
+| <br>
+| "Line2"
+| <br>
+| "Line3"
+| <br>
+| "Line4"
+
+#data
+<html>
+#errors
+Line: 1 Col: 6 Unexpected start tag (html). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+
+#data
+<head>
+#errors
+Line: 1 Col: 6 Unexpected start tag (head). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+
+#data
+<body>
+#errors
+Line: 1 Col: 6 Unexpected start tag (body). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+
+#data
+<html><head>
+#errors
+Line: 1 Col: 6 Unexpected start tag (html). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+
+#data
+<html><head></head>
+#errors
+Line: 1 Col: 6 Unexpected start tag (html). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+
+#data
+<html><head></head><body>
+#errors
+Line: 1 Col: 6 Unexpected start tag (html). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+
+#data
+<html><head></head><body></body>
+#errors
+Line: 1 Col: 6 Unexpected start tag (html). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+
+#data
+<html><head><body></body></html>
+#errors
+Line: 1 Col: 6 Unexpected start tag (html). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+
+#data
+<html><head></body></html>
+#errors
+Line: 1 Col: 6 Unexpected start tag (html). Expected DOCTYPE.
+Line: 1 Col: 19 Unexpected end tag (body).
+Line: 1 Col: 26 Unexpected end tag (html).
+#document
+| <html>
+| <head>
+| <body>
+
+#data
+<html><head><body></html>
+#errors
+Line: 1 Col: 6 Unexpected start tag (html). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+
+#data
+<html><body></html>
+#errors
+Line: 1 Col: 6 Unexpected start tag (html). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+
+#data
+<body></html>
+#errors
+Line: 1 Col: 6 Unexpected start tag (body). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+
+#data
+<head></html>
+#errors
+Line: 1 Col: 6 Unexpected start tag (head). Expected DOCTYPE.
+Line: 1 Col: 13 Unexpected end tag (html). Ignored.
+#document
+| <html>
+| <head>
+| <body>
+
+#data
+</head>
+#errors
+Line: 1 Col: 7 Unexpected end tag (head). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+
+#data
+</body>
+#errors
+Line: 1 Col: 7 Unexpected end tag (body). Expected DOCTYPE.
+Line: 1 Col: 7 Unexpected end tag (body) after the (implied) root element.
+#document
+| <html>
+| <head>
+| <body>
+
+#data
+</html>
+#errors
+Line: 1 Col: 7 Unexpected end tag (html). Expected DOCTYPE.
+Line: 1 Col: 7 Unexpected end tag (html) after the (implied) root element.
+#document
+| <html>
+| <head>
+| <body>
+
+#data
+<b><table><td><i></table>
+#errors
+Line: 1 Col: 3 Unexpected start tag (b). Expected DOCTYPE.
+Line: 1 Col: 14 Unexpected table cell start tag (td) in the table body phase.
+Line: 1 Col: 25 Got table cell end tag (td) while required end tags are missing.
+Line: 1 Col: 25 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <b>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <i>
+
+#data
+<b><table><td></b><i></table>X
+#errors
+Line: 1 Col: 3 Unexpected start tag (b). Expected DOCTYPE.
+Line: 1 Col: 14 Unexpected table cell start tag (td) in the table body phase.
+Line: 1 Col: 18 End tag (b) violates step 1, paragraph 1 of the adoption agency algorithm.
+Line: 1 Col: 29 Got table cell end tag (td) while required end tags are missing.
+Line: 1 Col: 30 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <b>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <i>
+| "X"
+
+#data
+<h1>Hello<h2>World
+#errors
+4: Start tag seen without seeing a doctype first. Expected “<!DOCTYPE html>”.
+13: Heading cannot be a child of another heading.
+18: End of file seen and there were open elements.
+#document
+| <html>
+| <head>
+| <body>
+| <h1>
+| "Hello"
+| <h2>
+| "World"
+
+#data
+<a><p>X<a>Y</a>Z</p></a>
+#errors
+Line: 1 Col: 3 Unexpected start tag (a). Expected DOCTYPE.
+Line: 1 Col: 10 Unexpected start tag (a) implies end tag (a).
+Line: 1 Col: 10 End tag (a) violates step 1, paragraph 3 of the adoption agency algorithm.
+Line: 1 Col: 24 End tag (a) violates step 1, paragraph 1 of the adoption agency algorithm.
+#document
+| <html>
+| <head>
+| <body>
+| <a>
+| <p>
+| <a>
+| "X"
+| <a>
+| "Y"
+| "Z"
+
+#data
+<b><button>foo</b>bar
+#errors
+Line: 1 Col: 3 Unexpected start tag (b). Expected DOCTYPE.
+Line: 1 Col: 15 End tag (b) violates step 1, paragraph 1 of the adoption agency algorithm.
+#document
+| <html>
+| <head>
+| <body>
+| <b>
+| <button>
+| <b>
+| "foo"
+| "bar"
+
+#data
+<!DOCTYPE html><span><button>foo</span>bar
+#errors
+39: End tag “span” seen but there were unclosed elements.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <span>
+| <button>
+| "foobar"
+
+#data
+<p><b><div><marquee></p></b></div>X
+#errors
+Line: 1 Col: 3 Unexpected start tag (p). Expected DOCTYPE.
+Line: 1 Col: 11 Unexpected end tag (p). Ignored.
+Line: 1 Col: 24 Unexpected end tag (p). Ignored.
+Line: 1 Col: 28 End tag (b) violates step 1, paragraph 1 of the adoption agency algorithm.
+Line: 1 Col: 34 End tag (div) seen too early. Expected other end tag.
+Line: 1 Col: 35 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <p>
+| <b>
+| <div>
+| <b>
+| <marquee>
+| <p>
+| "X"
+
+#data
+<script><div></script></div><title><p></title><p><p>
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 28 Unexpected end tag (div). Ignored.
+#document
+| <html>
+| <head>
+| <script>
+| "<div>"
+| <title>
+| "<p>"
+| <body>
+| <p>
+| <p>
+
+#data
+<!--><div>--<!-->
+#errors
+Line: 1 Col: 5 Incorrect comment.
+Line: 1 Col: 10 Unexpected start tag (div). Expected DOCTYPE.
+Line: 1 Col: 17 Incorrect comment.
+Line: 1 Col: 17 Expected closing tag. Unexpected end of file.
+#document
+| <!-- -->
+| <html>
+| <head>
+| <body>
+| <div>
+| "--"
+| <!-- -->
+
+#data
+<p><hr></p>
+#errors
+Line: 1 Col: 3 Unexpected start tag (p). Expected DOCTYPE.
+Line: 1 Col: 11 Unexpected end tag (p). Ignored.
+#document
+| <html>
+| <head>
+| <body>
+| <p>
+| <hr>
+| <p>
+
+#data
+<select><b><option><select><option></b></select>X
+#errors
+Line: 1 Col: 8 Unexpected start tag (select). Expected DOCTYPE.
+Line: 1 Col: 11 Unexpected start tag token (b) in the select phase. Ignored.
+Line: 1 Col: 27 Unexpected select start tag in the select phase treated as select end tag.
+Line: 1 Col: 39 End tag (b) violates step 1, paragraph 1 of the adoption agency algorithm.
+Line: 1 Col: 48 Unexpected end tag (select). Ignored.
+Line: 1 Col: 49 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <select>
+| <option>
+| <option>
+| "X"
+
+#data
+<a><table><td><a><table></table><a></tr><a></table><b>X</b>C<a>Y
+#errors
+Line: 1 Col: 3 Unexpected start tag (a). Expected DOCTYPE.
+Line: 1 Col: 14 Unexpected table cell start tag (td) in the table body phase.
+Line: 1 Col: 35 Unexpected start tag (a) implies end tag (a).
+Line: 1 Col: 40 Got table cell end tag (td) while required end tags are missing.
+Line: 1 Col: 43 Unexpected start tag (a) in table context caused voodoo mode.
+Line: 1 Col: 43 Unexpected start tag (a) implies end tag (a).
+Line: 1 Col: 43 End tag (a) violates step 1, paragraph 1 of the adoption agency algorithm.
+Line: 1 Col: 51 Unexpected implied end tag (a) in the table phase.
+Line: 1 Col: 63 Unexpected start tag (a) implies end tag (a).
+Line: 1 Col: 64 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <a>
+| <a>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <a>
+| <table>
+| <a>
+| <a>
+| <b>
+| "X"
+| "C"
+| <a>
+| "Y"
+
+#data
+<a X>0<b>1<a Y>2
+#errors
+Line: 1 Col: 5 Unexpected start tag (a). Expected DOCTYPE.
+Line: 1 Col: 15 Unexpected start tag (a) implies end tag (a).
+Line: 1 Col: 15 End tag (a) violates step 1, paragraph 3 of the adoption agency algorithm.
+Line: 1 Col: 16 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <a>
+| x=""
+| "0"
+| <b>
+| "1"
+| <b>
+| <a>
+| y=""
+| "2"
+
+#data
+<!-----><font><div>hello<table>excite!<b>me!<th><i>please!</tr><!--X-->
+#errors
+Line: 1 Col: 7 Unexpected '-' after '--' found in comment.
+Line: 1 Col: 14 Unexpected start tag (font). Expected DOCTYPE.
+Line: 1 Col: 38 Unexpected non-space characters in table context caused voodoo mode.
+Line: 1 Col: 41 Unexpected start tag (b) in table context caused voodoo mode.
+Line: 1 Col: 48 Unexpected implied end tag (b) in the table phase.
+Line: 1 Col: 48 Unexpected table cell start tag (th) in the table body phase.
+Line: 1 Col: 63 Got table cell end tag (th) while required end tags are missing.
+Line: 1 Col: 71 Unexpected end of file. Expected table content.
+#document
+| <!-- - -->
+| <html>
+| <head>
+| <body>
+| <font>
+| <div>
+| "helloexcite!"
+| <b>
+| "me!"
+| <table>
+| <tbody>
+| <tr>
+| <th>
+| <i>
+| "please!"
+| <!-- X -->
+
+#data
+<!DOCTYPE html><li>hello<li>world<ul>how<li>do</ul>you</body><!--do-->
+#errors
+Line: 1 Col: 61 Unexpected end tag (li). Missing end tag (body).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <li>
+| "hello"
+| <li>
+| "world"
+| <ul>
+| "how"
+| <li>
+| "do"
+| "you"
+| <!-- do -->
+
+#data
+<!DOCTYPE html>A<option>B<optgroup>C<select>D</option>E
+#errors
+Line: 1 Col: 54 Unexpected end tag (option) in the select phase. Ignored.
+Line: 1 Col: 55 Expected closing tag. Unexpected end of file.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| "A"
+| <option>
+| "B"
+| <optgroup>
+| "C"
+| <select>
+| "DE"
+
+#data
+<
+#errors
+Line: 1 Col: 1 Expected tag name. Got something else instead
+Line: 1 Col: 1 Unexpected non-space characters. Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+| "<"
+
+#data
+<#
+#errors
+Line: 1 Col: 1 Expected tag name. Got something else instead
+Line: 1 Col: 1 Unexpected non-space characters. Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+| "<#"
+
+#data
+</
+#errors
+Line: 1 Col: 2 Expected closing tag. Unexpected end of file.
+Line: 1 Col: 2 Unexpected non-space characters. Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+| "</"
+
+#data
+</#
+#errors
+Line: 1 Col: 2 Expected closing tag. Unexpected character '#' found.
+Line: 1 Col: 3 Unexpected End of file. Expected DOCTYPE.
+#document
+| <!-- # -->
+| <html>
+| <head>
+| <body>
+
+#data
+<?
+#errors
+Line: 1 Col: 1 Expected tag name. Got '?' instead. (HTML doesn't support processing instructions.)
+Line: 1 Col: 2 Unexpected End of file. Expected DOCTYPE.
+#document
+| <!-- ? -->
+| <html>
+| <head>
+| <body>
+
+#data
+<?#
+#errors
+Line: 1 Col: 1 Expected tag name. Got '?' instead. (HTML doesn't support processing instructions.)
+Line: 1 Col: 3 Unexpected End of file. Expected DOCTYPE.
+#document
+| <!-- ?# -->
+| <html>
+| <head>
+| <body>
+
+#data
+<!
+#errors
+Line: 1 Col: 2 Expected '--' or 'DOCTYPE'. Not found.
+Line: 1 Col: 2 Unexpected End of file. Expected DOCTYPE.
+#document
+| <!-- -->
+| <html>
+| <head>
+| <body>
+
+#data
+<!#
+#errors
+Line: 1 Col: 3 Expected '--' or 'DOCTYPE'. Not found.
+Line: 1 Col: 3 Unexpected End of file. Expected DOCTYPE.
+#document
+| <!-- # -->
+| <html>
+| <head>
+| <body>
+
+#data
+<?COMMENT?>
+#errors
+Line: 1 Col: 1 Expected tag name. Got '?' instead. (HTML doesn't support processing instructions.)
+Line: 1 Col: 11 Unexpected End of file. Expected DOCTYPE.
+#document
+| <!-- ?COMMENT? -->
+| <html>
+| <head>
+| <body>
+
+#data
+<!COMMENT>
+#errors
+Line: 1 Col: 2 Expected '--' or 'DOCTYPE'. Not found.
+Line: 1 Col: 10 Unexpected End of file. Expected DOCTYPE.
+#document
+| <!-- COMMENT -->
+| <html>
+| <head>
+| <body>
+
+#data
+</ COMMENT >
+#errors
+Line: 1 Col: 2 Expected closing tag. Unexpected character ' ' found.
+Line: 1 Col: 12 Unexpected End of file. Expected DOCTYPE.
+#document
+| <!-- COMMENT -->
+| <html>
+| <head>
+| <body>
+
+#data
+<?COM--MENT?>
+#errors
+Line: 1 Col: 1 Expected tag name. Got '?' instead. (HTML doesn't support processing instructions.)
+Line: 1 Col: 13 Unexpected End of file. Expected DOCTYPE.
+#document
+| <!-- ?COM--MENT? -->
+| <html>
+| <head>
+| <body>
+
+#data
+<!COM--MENT>
+#errors
+Line: 1 Col: 2 Expected '--' or 'DOCTYPE'. Not found.
+Line: 1 Col: 12 Unexpected End of file. Expected DOCTYPE.
+#document
+| <!-- COM--MENT -->
+| <html>
+| <head>
+| <body>
+
+#data
+</ COM--MENT >
+#errors
+Line: 1 Col: 2 Expected closing tag. Unexpected character ' ' found.
+Line: 1 Col: 14 Unexpected End of file. Expected DOCTYPE.
+#document
+| <!-- COM--MENT -->
+| <html>
+| <head>
+| <body>
+
+#data
+<!DOCTYPE html><style> EOF
+#errors
+Line: 1 Col: 26 Unexpected end of file. Expected end tag (style).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <style>
+| " EOF"
+| <body>
+
+#data
+<!DOCTYPE html><script> <!-- </script> --> </script> EOF
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| " <!-- "
+| " "
+| <body>
+| "--> EOF"
+
+#data
+<b><p></b>TEST
+#errors
+Line: 1 Col: 3 Unexpected start tag (b). Expected DOCTYPE.
+Line: 1 Col: 10 End tag (b) violates step 1, paragraph 3 of the adoption agency algorithm.
+#document
+| <html>
+| <head>
+| <body>
+| <b>
+| <p>
+| <b>
+| "TEST"
+
+#data
+<p id=a><b><p id=b></b>TEST
+#errors
+Line: 1 Col: 8 Unexpected start tag (p). Expected DOCTYPE.
+Line: 1 Col: 19 Unexpected end tag (p). Ignored.
+Line: 1 Col: 23 End tag (b) violates step 1, paragraph 2 of the adoption agency algorithm.
+#document
+| <html>
+| <head>
+| <body>
+| <p>
+| id="a"
+| <b>
+| <p>
+| id="b"
+| "TEST"
+
+#data
+<b id=a><p><b id=b></p></b>TEST
+#errors
+Line: 1 Col: 8 Unexpected start tag (b). Expected DOCTYPE.
+Line: 1 Col: 23 Unexpected end tag (p). Ignored.
+Line: 1 Col: 27 End tag (b) violates step 1, paragraph 2 of the adoption agency algorithm.
+Line: 1 Col: 31 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <b>
+| id="a"
+| <p>
+| <b>
+| id="b"
+| "TEST"
+
+#data
+<!DOCTYPE html><title>U-test</title><body><div><p>Test<u></p></div></body>
+#errors
+Line: 1 Col: 61 Unexpected end tag (p). Ignored.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <title>
+| "U-test"
+| <body>
+| <div>
+| <p>
+| "Test"
+| <u>
+
+#data
+<!DOCTYPE html><font><table></font></table></font>
+#errors
+Line: 1 Col: 35 Unexpected end tag (font) in table context caused voodoo mode.
+Line: 1 Col: 35 End tag (font) violates step 1, paragraph 1 of the adoption agency algorithm.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <font>
+| <table>
+
+#data
+<font><p>hello<b>cruel</font>world
+#errors
+Line: 1 Col: 6 Unexpected start tag (font). Expected DOCTYPE.
+Line: 1 Col: 29 End tag (font) violates step 1, paragraph 3 of the adoption agency algorithm.
+Line: 1 Col: 29 End tag (font) violates step 1, paragraph 3 of the adoption agency algorithm.
+Line: 1 Col: 34 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <font>
+| <p>
+| <font>
+| "hello"
+| <b>
+| "cruel"
+| <b>
+| "world"
+
+#data
+<b>Test</i>Test
+#errors
+Line: 1 Col: 3 Unexpected start tag (b). Expected DOCTYPE.
+Line: 1 Col: 11 End tag (i) violates step 1, paragraph 1 of the adoption agency algorithm.
+Line: 1 Col: 15 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <b>
+| "TestTest"
+
+#data
+<b>A<cite>B<div>C
+#errors
+Line: 1 Col: 3 Unexpected start tag (b). Expected DOCTYPE.
+Line: 1 Col: 17 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <b>
+| "A"
+| <cite>
+| "B"
+| <div>
+| "C"
+
+#data
+<b>A<cite>B<div>C</cite>D
+#errors
+Line: 1 Col: 3 Unexpected start tag (b). Expected DOCTYPE.
+Line: 1 Col: 24 Unexpected end tag (cite). Ignored.
+Line: 1 Col: 25 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <b>
+| "A"
+| <cite>
+| "B"
+| <div>
+| "CD"
+
+#data
+<b>A<cite>B<div>C</b>D
+#errors
+Line: 1 Col: 3 Unexpected start tag (b). Expected DOCTYPE.
+Line: 1 Col: 21 End tag (b) violates step 1, paragraph 3 of the adoption agency algorithm.
+Line: 1 Col: 22 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <b>
+| "A"
+| <cite>
+| "B"
+| <div>
+| <b>
+| "C"
+| "D"
+
+#data
+
+#errors
+Line: 1 Col: 0 Unexpected End of file. Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+
+#data
+<DIV>
+#errors
+Line: 1 Col: 5 Unexpected start tag (div). Expected DOCTYPE.
+Line: 1 Col: 5 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+
+#data
+<DIV> abc
+#errors
+Line: 1 Col: 5 Unexpected start tag (div). Expected DOCTYPE.
+Line: 1 Col: 9 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| " abc"
+
+#data
+<DIV> abc <B>
+#errors
+Line: 1 Col: 5 Unexpected start tag (div). Expected DOCTYPE.
+Line: 1 Col: 13 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| " abc "
+| <b>
+
+#data
+<DIV> abc <B> def
+#errors
+Line: 1 Col: 5 Unexpected start tag (div). Expected DOCTYPE.
+Line: 1 Col: 17 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| " abc "
+| <b>
+| " def"
+
+#data
+<DIV> abc <B> def <I>
+#errors
+Line: 1 Col: 5 Unexpected start tag (div). Expected DOCTYPE.
+Line: 1 Col: 21 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| " abc "
+| <b>
+| " def "
+| <i>
+
+#data
+<DIV> abc <B> def <I> ghi
+#errors
+Line: 1 Col: 5 Unexpected start tag (div). Expected DOCTYPE.
+Line: 1 Col: 25 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| " abc "
+| <b>
+| " def "
+| <i>
+| " ghi"
+
+#data
+<DIV> abc <B> def <I> ghi <P>
+#errors
+Line: 1 Col: 5 Unexpected start tag (div). Expected DOCTYPE.
+Line: 1 Col: 29 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| " abc "
+| <b>
+| " def "
+| <i>
+| " ghi "
+| <p>
+
+#data
+<DIV> abc <B> def <I> ghi <P> jkl
+#errors
+Line: 1 Col: 5 Unexpected start tag (div). Expected DOCTYPE.
+Line: 1 Col: 33 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| " abc "
+| <b>
+| " def "
+| <i>
+| " ghi "
+| <p>
+| " jkl"
+
+#data
+<DIV> abc <B> def <I> ghi <P> jkl </B>
+#errors
+Line: 1 Col: 5 Unexpected start tag (div). Expected DOCTYPE.
+Line: 1 Col: 38 End tag (b) violates step 1, paragraph 3 of the adoption agency algorithm.
+Line: 1 Col: 38 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| " abc "
+| <b>
+| " def "
+| <i>
+| " ghi "
+| <i>
+| <p>
+| <b>
+| " jkl "
+
+#data
+<DIV> abc <B> def <I> ghi <P> jkl </B> mno
+#errors
+Line: 1 Col: 5 Unexpected start tag (div). Expected DOCTYPE.
+Line: 1 Col: 38 End tag (b) violates step 1, paragraph 3 of the adoption agency algorithm.
+Line: 1 Col: 42 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| " abc "
+| <b>
+| " def "
+| <i>
+| " ghi "
+| <i>
+| <p>
+| <b>
+| " jkl "
+| " mno"
+
+#data
+<DIV> abc <B> def <I> ghi <P> jkl </B> mno </I>
+#errors
+Line: 1 Col: 5 Unexpected start tag (div). Expected DOCTYPE.
+Line: 1 Col: 38 End tag (b) violates step 1, paragraph 3 of the adoption agency algorithm.
+Line: 1 Col: 47 End tag (i) violates step 1, paragraph 3 of the adoption agency algorithm.
+Line: 1 Col: 47 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| " abc "
+| <b>
+| " def "
+| <i>
+| " ghi "
+| <i>
+| <p>
+| <i>
+| <b>
+| " jkl "
+| " mno "
+
+#data
+<DIV> abc <B> def <I> ghi <P> jkl </B> mno </I> pqr
+#errors
+Line: 1 Col: 5 Unexpected start tag (div). Expected DOCTYPE.
+Line: 1 Col: 38 End tag (b) violates step 1, paragraph 3 of the adoption agency algorithm.
+Line: 1 Col: 47 End tag (i) violates step 1, paragraph 3 of the adoption agency algorithm.
+Line: 1 Col: 51 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| " abc "
+| <b>
+| " def "
+| <i>
+| " ghi "
+| <i>
+| <p>
+| <i>
+| <b>
+| " jkl "
+| " mno "
+| " pqr"
+
+#data
+<DIV> abc <B> def <I> ghi <P> jkl </B> mno </I> pqr </P>
+#errors
+Line: 1 Col: 5 Unexpected start tag (div). Expected DOCTYPE.
+Line: 1 Col: 38 End tag (b) violates step 1, paragraph 3 of the adoption agency algorithm.
+Line: 1 Col: 47 End tag (i) violates step 1, paragraph 3 of the adoption agency algorithm.
+Line: 1 Col: 56 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| " abc "
+| <b>
+| " def "
+| <i>
+| " ghi "
+| <i>
+| <p>
+| <i>
+| <b>
+| " jkl "
+| " mno "
+| " pqr "
+
+#data
+<DIV> abc <B> def <I> ghi <P> jkl </B> mno </I> pqr </P> stu
+#errors
+Line: 1 Col: 5 Unexpected start tag (div). Expected DOCTYPE.
+Line: 1 Col: 38 End tag (b) violates step 1, paragraph 3 of the adoption agency algorithm.
+Line: 1 Col: 47 End tag (i) violates step 1, paragraph 3 of the adoption agency algorithm.
+Line: 1 Col: 60 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| " abc "
+| <b>
+| " def "
+| <i>
+| " ghi "
+| <i>
+| <p>
+| <i>
+| <b>
+| " jkl "
+| " mno "
+| " pqr "
+| " stu"
+
+#data
+<test attribute---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------->
+#errors
+Line: 1 Col: 1040 Unexpected start tag (test). Expected DOCTYPE.
+Line: 1 Col: 1040 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <test>
+| attribute----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------=""
+
+#data
+<a href="blah">aba<table><a href="foo">br<tr><td></td></tr>x</table>aoe
+#errors
+Line: 1 Col: 15 Unexpected start tag (a). Expected DOCTYPE.
+Line: 1 Col: 39 Unexpected start tag (a) in table context caused voodoo mode.
+Line: 1 Col: 39 Unexpected start tag (a) implies end tag (a).
+Line: 1 Col: 39 End tag (a) violates step 1, paragraph 1 of the adoption agency algorithm.
+Line: 1 Col: 45 Unexpected implied end tag (a) in the table phase.
+Line: 1 Col: 68 Unexpected implied end tag (a) in the table phase.
+Line: 1 Col: 71 Expected closing tag. Unexpected end of file.
+
+#document
+| <html>
+| <head>
+| <body>
+| <a>
+| href="blah"
+| "aba"
+| <a>
+| href="foo"
+| "br"
+| <a>
+| href="foo"
+| "x"
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <a>
+| href="foo"
+| "aoe"
+
+#data
+<a href="blah">aba<table><tr><td><a href="foo">br</td></tr>x</table>aoe
+#errors
+Line: 1 Col: 15 Unexpected start tag (a). Expected DOCTYPE.
+Line: 1 Col: 54 Got table cell end tag (td) while required end tags are missing.
+Line: 1 Col: 60 Unexpected non-space characters in table context caused voodoo mode.
+Line: 1 Col: 71 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <a>
+| href="blah"
+| "abax"
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <a>
+| href="foo"
+| "br"
+| "aoe"
+
+#data
+<table><a href="blah">aba<tr><td><a href="foo">br</td></tr>x</table>aoe
+#errors
+Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE.
+Line: 1 Col: 22 Unexpected start tag (a) in table context caused voodoo mode.
+Line: 1 Col: 29 Unexpected implied end tag (a) in the table phase.
+Line: 1 Col: 54 Got table cell end tag (td) while required end tags are missing.
+Line: 1 Col: 68 Unexpected implied end tag (a) in the table phase.
+Line: 1 Col: 71 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <a>
+| href="blah"
+| "aba"
+| <a>
+| href="blah"
+| "x"
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <a>
+| href="foo"
+| "br"
+| <a>
+| href="blah"
+| "aoe"
+
+#data
+<a href=a>aa<marquee>aa<a href=b>bb</marquee>aa
+#errors
+Line: 1 Col: 10 Unexpected start tag (a). Expected DOCTYPE.
+Line: 1 Col: 45 End tag (marquee) seen too early. Expected other end tag.
+Line: 1 Col: 47 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <a>
+| href="a"
+| "aa"
+| <marquee>
+| "aa"
+| <a>
+| href="b"
+| "bb"
+| "aa"
+
+#data
+<wbr><strike><code></strike><code><strike></code>
+#errors
+Line: 1 Col: 5 Unexpected start tag (wbr). Expected DOCTYPE.
+Line: 1 Col: 28 End tag (strike) violates step 1, paragraph 3 of the adoption agency algorithm.
+Line: 1 Col: 49 Unexpected end tag (code). Ignored.
+#document
+| <html>
+| <head>
+| <body>
+| <wbr>
+| <strike>
+| <code>
+| <code>
+| <code>
+| <strike>
+
+#data
+<!DOCTYPE html><spacer>foo
+#errors
+26: End of file seen and there were open elements.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <spacer>
+| "foo"
+
+#data
+<title><meta></title><link><title><meta></title>
+#errors
+Line: 1 Col: 7 Unexpected start tag (title). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <title>
+| "<meta>"
+| <link>
+| <title>
+| "<meta>"
+| <body>
+
+#data
+<style><!--</style><meta><script>--><link></script>
+#errors
+Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE.
+Line: 1 Col: 51 Unexpected end of file. Expected end tag (style).
+#document
+| <html>
+| <head>
+| <style>
+| "<!--"
+| <meta>
+| <script>
+| "--><link>"
+| <body>
+
+#data
+<head><meta></head><link>
+#errors
+Line: 1 Col: 6 Unexpected start tag (head). Expected DOCTYPE.
+Line: 1 Col: 25 Unexpected start tag (link) that can be in head. Moved.
+#document
+| <html>
+| <head>
+| <meta>
+| <link>
+| <body>
+
+#data
+<table><tr><tr><td><td><span><th><span>X</table>
+#errors
+Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE.
+Line: 1 Col: 33 Got table cell end tag (td) while required end tags are missing.
+Line: 1 Col: 48 Got table cell end tag (th) while required end tags are missing.
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <tr>
+| <td>
+| <td>
+| <span>
+| <th>
+| <span>
+| "X"
+
+#data
+<body><body><base><link><meta><title><p></title><body><p></body>
+#errors
+Line: 1 Col: 6 Unexpected start tag (body). Expected DOCTYPE.
+Line: 1 Col: 12 Unexpected start tag (body).
+Line: 1 Col: 54 Unexpected start tag (body).
+Line: 1 Col: 64 Unexpected end tag (p). Missing end tag (body).
+#document
+| <html>
+| <head>
+| <body>
+| <base>
+| <link>
+| <meta>
+| <title>
+| "<p>"
+| <p>
+
+#data
+<textarea><p></textarea>
+#errors
+Line: 1 Col: 10 Unexpected start tag (textarea). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+| <textarea>
+| "<p>"
+
+#data
+<p><image></p>
+#errors
+Line: 1 Col: 3 Unexpected start tag (p). Expected DOCTYPE.
+Line: 1 Col: 10 Unexpected start tag (image). Treated as img.
+#document
+| <html>
+| <head>
+| <body>
+| <p>
+| <img>
+
+#data
+<a><table><a></table><p><a><div><a>
+#errors
+Line: 1 Col: 3 Unexpected start tag (a). Expected DOCTYPE.
+Line: 1 Col: 13 Unexpected start tag (a) in table context caused voodoo mode.
+Line: 1 Col: 13 Unexpected start tag (a) implies end tag (a).
+Line: 1 Col: 13 End tag (a) violates step 1, paragraph 1 of the adoption agency algorithm.
+Line: 1 Col: 21 Unexpected end tag (table). Expected end tag (a).
+Line: 1 Col: 27 Unexpected start tag (a) implies end tag (a).
+Line: 1 Col: 27 End tag (a) violates step 1, paragraph 2 of the adoption agency algorithm.
+Line: 1 Col: 32 Unexpected end tag (p). Ignored.
+Line: 1 Col: 35 Unexpected start tag (a) implies end tag (a).
+Line: 1 Col: 35 End tag (a) violates step 1, paragraph 2 of the adoption agency algorithm.
+Line: 1 Col: 35 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <a>
+| <a>
+| <table>
+| <p>
+| <a>
+| <div>
+| <a>
+
+#data
+<head></p><meta><p>
+#errors
+Line: 1 Col: 6 Unexpected start tag (head). Expected DOCTYPE.
+Line: 1 Col: 10 Unexpected end tag (p). Ignored.
+#document
+| <html>
+| <head>
+| <meta>
+| <body>
+| <p>
+
+#data
+<head></html><meta><p>
+#errors
+Line: 1 Col: 6 Unexpected start tag (head). Expected DOCTYPE.
+Line: 1 Col: 19 Unexpected start tag (meta).
+#document
+| <html>
+| <head>
+| <body>
+| <meta>
+| <p>
+
+#data
+<b><table><td><i></table>
+#errors
+Line: 1 Col: 3 Unexpected start tag (b). Expected DOCTYPE.
+Line: 1 Col: 14 Unexpected table cell start tag (td) in the table body phase.
+Line: 1 Col: 25 Got table cell end tag (td) while required end tags are missing.
+Line: 1 Col: 25 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <b>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <i>
+
+#data
+<b><table><td></b><i></table>
+#errors
+Line: 1 Col: 3 Unexpected start tag (b). Expected DOCTYPE.
+Line: 1 Col: 14 Unexpected table cell start tag (td) in the table body phase.
+Line: 1 Col: 18 End tag (b) violates step 1, paragraph 1 of the adoption agency algorithm.
+Line: 1 Col: 29 Got table cell end tag (td) while required end tags are missing.
+Line: 1 Col: 29 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <b>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <i>
+
+#data
+<h1><h2>
+#errors
+4: Start tag seen without seeing a doctype first. Expected “<!DOCTYPE html>”.
+8: Heading cannot be a child of another heading.
+8: End of file seen and there were open elements.
+#document
+| <html>
+| <head>
+| <body>
+| <h1>
+| <h2>
+
+#data
+<a><p><a></a></p></a>
+#errors
+Line: 1 Col: 3 Unexpected start tag (a). Expected DOCTYPE.
+Line: 1 Col: 9 Unexpected start tag (a) implies end tag (a).
+Line: 1 Col: 9 End tag (a) violates step 1, paragraph 3 of the adoption agency algorithm.
+Line: 1 Col: 21 End tag (a) violates step 1, paragraph 1 of the adoption agency algorithm.
+#document
+| <html>
+| <head>
+| <body>
+| <a>
+| <p>
+| <a>
+| <a>
+
+#data
+<b><button></b></button></b>
+#errors
+Line: 1 Col: 3 Unexpected start tag (b). Expected DOCTYPE.
+Line: 1 Col: 15 End tag (b) violates step 1, paragraph 1 of the adoption agency algorithm.
+#document
+| <html>
+| <head>
+| <body>
+| <b>
+| <button>
+| <b>
+
+#data
+<p><b><div><marquee></p></b></div>
+#errors
+Line: 1 Col: 3 Unexpected start tag (p). Expected DOCTYPE.
+Line: 1 Col: 11 Unexpected end tag (p). Ignored.
+Line: 1 Col: 24 Unexpected end tag (p). Ignored.
+Line: 1 Col: 28 End tag (b) violates step 1, paragraph 1 of the adoption agency algorithm.
+Line: 1 Col: 34 End tag (div) seen too early. Expected other end tag.
+Line: 1 Col: 34 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <p>
+| <b>
+| <div>
+| <b>
+| <marquee>
+| <p>
+
+#data
+<script></script></div><title></title><p><p>
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 23 Unexpected end tag (div). Ignored.
+#document
+| <html>
+| <head>
+| <script>
+| <title>
+| <body>
+| <p>
+| <p>
+
+#data
+<p><hr></p>
+#errors
+Line: 1 Col: 3 Unexpected start tag (p). Expected DOCTYPE.
+Line: 1 Col: 11 Unexpected end tag (p). Ignored.
+#document
+| <html>
+| <head>
+| <body>
+| <p>
+| <hr>
+| <p>
+
+#data
+<select><b><option><select><option></b></select>
+#errors
+Line: 1 Col: 8 Unexpected start tag (select). Expected DOCTYPE.
+Line: 1 Col: 11 Unexpected start tag token (b) in the select phase. Ignored.
+Line: 1 Col: 27 Unexpected select start tag in the select phase treated as select end tag.
+Line: 1 Col: 39 End tag (b) violates step 1, paragraph 1 of the adoption agency algorithm.
+Line: 1 Col: 48 Unexpected end tag (select). Ignored.
+Line: 1 Col: 48 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <select>
+| <option>
+| <option>
+
+#data
+<html><head><title></title><body></body></html>
+#errors
+Line: 1 Col: 6 Unexpected start tag (html). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <title>
+| <body>
+
+#data
+<a><table><td><a><table></table><a></tr><a></table><a>
+#errors
+Line: 1 Col: 3 Unexpected start tag (a). Expected DOCTYPE.
+Line: 1 Col: 14 Unexpected table cell start tag (td) in the table body phase.
+Line: 1 Col: 35 Unexpected start tag (a) implies end tag (a).
+Line: 1 Col: 40 Got table cell end tag (td) while required end tags are missing.
+Line: 1 Col: 43 Unexpected start tag (a) in table context caused voodoo mode.
+Line: 1 Col: 43 Unexpected start tag (a) implies end tag (a).
+Line: 1 Col: 43 End tag (a) violates step 1, paragraph 1 of the adoption agency algorithm.
+Line: 1 Col: 51 Unexpected implied end tag (a) in the table phase.
+Line: 1 Col: 54 Unexpected start tag (a) implies end tag (a).
+Line: 1 Col: 54 End tag (a) violates step 1, paragraph 2 of the adoption agency algorithm.
+Line: 1 Col: 54 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <a>
+| <a>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <a>
+| <table>
+| <a>
+| <a>
+
+#data
+<ul><li></li><div><li></div><li><li><div><li><address><li><b><em></b><li></ul>
+#errors
+Line: 1 Col: 4 Unexpected start tag (ul). Expected DOCTYPE.
+Line: 1 Col: 45 Missing end tag (div, li).
+Line: 1 Col: 58 Missing end tag (address, li).
+Line: 1 Col: 69 End tag (b) violates step 1, paragraph 3 of the adoption agency algorithm.
+#document
+| <html>
+| <head>
+| <body>
+| <ul>
+| <li>
+| <div>
+| <li>
+| <li>
+| <li>
+| <div>
+| <li>
+| <address>
+| <li>
+| <b>
+| <em>
+| <li>
+
+#data
+<ul><li><ul></li><li>a</li></ul></li></ul>
+#errors
+XXX: fix me
+#document
+| <html>
+| <head>
+| <body>
+| <ul>
+| <li>
+| <ul>
+| <li>
+| "a"
+
+#data
+<frameset><frame><frameset><frame></frameset><noframes></noframes></frameset>
+#errors
+Line: 1 Col: 10 Unexpected start tag (frameset). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <frameset>
+| <frame>
+| <frameset>
+| <frame>
+| <noframes>
+
+#data
+<h1><table><td><h3></table><h3></h1>
+#errors
+4: Start tag seen without seeing a doctype first. Expected “<!DOCTYPE html>”.
+15: “td” start tag in table body.
+27: Unclosed elements.
+31: Heading cannot be a child of another heading.
+36: End tag “h1” seen but there were unclosed elements.
+#document
+| <html>
+| <head>
+| <body>
+| <h1>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <h3>
+| <h3>
+
+#data
+<table><colgroup><col><colgroup><col><col><col><colgroup><col><col><thead><tr><td></table>
+#errors
+Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <colgroup>
+| <col>
+| <colgroup>
+| <col>
+| <col>
+| <col>
+| <colgroup>
+| <col>
+| <col>
+| <thead>
+| <tr>
+| <td>
+
+#data
+<table><col><tbody><col><tr><col><td><col></table><col>
+#errors
+Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE.
+Line: 1 Col: 37 Unexpected table cell start tag (td) in the table body phase.
+Line: 1 Col: 55 Unexpected start tag col. Ignored.
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <colgroup>
+| <col>
+| <tbody>
+| <colgroup>
+| <col>
+| <tbody>
+| <tr>
+| <colgroup>
+| <col>
+| <tbody>
+| <tr>
+| <td>
+| <colgroup>
+| <col>
+
+#data
+<table><colgroup><tbody><colgroup><tr><colgroup><td><colgroup></table><colgroup>
+#errors
+Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE.
+Line: 1 Col: 52 Unexpected table cell start tag (td) in the table body phase.
+Line: 1 Col: 80 Unexpected start tag colgroup. Ignored.
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <colgroup>
+| <tbody>
+| <colgroup>
+| <tbody>
+| <tr>
+| <colgroup>
+| <tbody>
+| <tr>
+| <td>
+| <colgroup>
+
+#data
+</strong></b></em></i></u></strike></s></blink></tt></pre></big></small></font></select></h1></h2></h3></h4></h5></h6></body></br></a></img></title></span></style></script></table></th></td></tr></frame></area></link></param></hr></input></col></base></meta></basefont></bgsound></embed></spacer></p></dd></dt></caption></colgroup></tbody></tfoot></thead></address></blockquote></center></dir></div></dl></fieldset></listing></menu></ol></ul></li></nobr></wbr></form></button></marquee></object></html></frameset></head></iframe></image></isindex></noembed></noframes></noscript></optgroup></option></plaintext></textarea>
+#errors
+Line: 1 Col: 9 Unexpected end tag (strong). Expected DOCTYPE.
+Line: 1 Col: 9 Unexpected end tag (strong) after the (implied) root element.
+Line: 1 Col: 13 Unexpected end tag (b) after the (implied) root element.
+Line: 1 Col: 18 Unexpected end tag (em) after the (implied) root element.
+Line: 1 Col: 22 Unexpected end tag (i) after the (implied) root element.
+Line: 1 Col: 26 Unexpected end tag (u) after the (implied) root element.
+Line: 1 Col: 35 Unexpected end tag (strike) after the (implied) root element.
+Line: 1 Col: 39 Unexpected end tag (s) after the (implied) root element.
+Line: 1 Col: 47 Unexpected end tag (blink) after the (implied) root element.
+Line: 1 Col: 52 Unexpected end tag (tt) after the (implied) root element.
+Line: 1 Col: 58 Unexpected end tag (pre) after the (implied) root element.
+Line: 1 Col: 64 Unexpected end tag (big) after the (implied) root element.
+Line: 1 Col: 72 Unexpected end tag (small) after the (implied) root element.
+Line: 1 Col: 79 Unexpected end tag (font) after the (implied) root element.
+Line: 1 Col: 88 Unexpected end tag (select) after the (implied) root element.
+Line: 1 Col: 93 Unexpected end tag (h1) after the (implied) root element.
+Line: 1 Col: 98 Unexpected end tag (h2) after the (implied) root element.
+Line: 1 Col: 103 Unexpected end tag (h3) after the (implied) root element.
+Line: 1 Col: 108 Unexpected end tag (h4) after the (implied) root element.
+Line: 1 Col: 113 Unexpected end tag (h5) after the (implied) root element.
+Line: 1 Col: 118 Unexpected end tag (h6) after the (implied) root element.
+Line: 1 Col: 125 Unexpected end tag (body) after the (implied) root element.
+Line: 1 Col: 130 Unexpected end tag (br). Treated as br element.
+Line: 1 Col: 134 End tag (a) violates step 1, paragraph 1 of the adoption agency algorithm.
+Line: 1 Col: 140 This element (img) has no end tag.
+Line: 1 Col: 148 Unexpected end tag (title). Ignored.
+Line: 1 Col: 155 Unexpected end tag (span). Ignored.
+Line: 1 Col: 163 Unexpected end tag (style). Ignored.
+Line: 1 Col: 172 Unexpected end tag (script). Ignored.
+Line: 1 Col: 180 Unexpected end tag (table). Ignored.
+Line: 1 Col: 185 Unexpected end tag (th). Ignored.
+Line: 1 Col: 190 Unexpected end tag (td). Ignored.
+Line: 1 Col: 195 Unexpected end tag (tr). Ignored.
+Line: 1 Col: 203 This element (frame) has no end tag.
+Line: 1 Col: 210 This element (area) has no end tag.
+Line: 1 Col: 217 Unexpected end tag (link). Ignored.
+Line: 1 Col: 225 This element (param) has no end tag.
+Line: 1 Col: 230 This element (hr) has no end tag.
+Line: 1 Col: 238 This element (input) has no end tag.
+Line: 1 Col: 244 Unexpected end tag (col). Ignored.
+Line: 1 Col: 251 Unexpected end tag (base). Ignored.
+Line: 1 Col: 258 Unexpected end tag (meta). Ignored.
+Line: 1 Col: 269 This element (basefont) has no end tag.
+Line: 1 Col: 279 This element (bgsound) has no end tag.
+Line: 1 Col: 287 This element (embed) has no end tag.
+Line: 1 Col: 296 This element (spacer) has no end tag.
+Line: 1 Col: 300 Unexpected end tag (p). Ignored.
+Line: 1 Col: 305 End tag (dd) seen too early. Expected other end tag.
+Line: 1 Col: 310 End tag (dt) seen too early. Expected other end tag.
+Line: 1 Col: 320 Unexpected end tag (caption). Ignored.
+Line: 1 Col: 331 Unexpected end tag (colgroup). Ignored.
+Line: 1 Col: 339 Unexpected end tag (tbody). Ignored.
+Line: 1 Col: 347 Unexpected end tag (tfoot). Ignored.
+Line: 1 Col: 355 Unexpected end tag (thead). Ignored.
+Line: 1 Col: 365 End tag (address) seen too early. Expected other end tag.
+Line: 1 Col: 378 End tag (blockquote) seen too early. Expected other end tag.
+Line: 1 Col: 387 End tag (center) seen too early. Expected other end tag.
+Line: 1 Col: 393 Unexpected end tag (dir). Ignored.
+Line: 1 Col: 399 End tag (div) seen too early. Expected other end tag.
+Line: 1 Col: 404 End tag (dl) seen too early. Expected other end tag.
+Line: 1 Col: 415 End tag (fieldset) seen too early. Expected other end tag.
+Line: 1 Col: 425 End tag (listing) seen too early. Expected other end tag.
+Line: 1 Col: 432 End tag (menu) seen too early. Expected other end tag.
+Line: 1 Col: 437 End tag (ol) seen too early. Expected other end tag.
+Line: 1 Col: 442 End tag (ul) seen too early. Expected other end tag.
+Line: 1 Col: 447 End tag (li) seen too early. Expected other end tag.
+Line: 1 Col: 454 End tag (nobr) violates step 1, paragraph 1 of the adoption agency algorithm.
+Line: 1 Col: 460 This element (wbr) has no end tag.
+Line: 1 Col: 476 End tag (button) seen too early. Expected other end tag.
+Line: 1 Col: 486 End tag (marquee) seen too early. Expected other end tag.
+Line: 1 Col: 495 End tag (object) seen too early. Expected other end tag.
+Line: 1 Col: 513 Unexpected end tag (html). Ignored.
+Line: 1 Col: 513 Unexpected end tag (frameset). Ignored.
+Line: 1 Col: 520 Unexpected end tag (head). Ignored.
+Line: 1 Col: 529 Unexpected end tag (iframe). Ignored.
+Line: 1 Col: 537 This element (image) has no end tag.
+Line: 1 Col: 547 This element (isindex) has no end tag.
+Line: 1 Col: 557 Unexpected end tag (noembed). Ignored.
+Line: 1 Col: 568 Unexpected end tag (noframes). Ignored.
+Line: 1 Col: 579 Unexpected end tag (noscript). Ignored.
+Line: 1 Col: 590 Unexpected end tag (optgroup). Ignored.
+Line: 1 Col: 599 Unexpected end tag (option). Ignored.
+Line: 1 Col: 611 Unexpected end tag (plaintext). Ignored.
+Line: 1 Col: 622 Unexpected end tag (textarea). Ignored.
+#document
+| <html>
+| <head>
+| <body>
+| <br>
+| <p>
+
+#data
+<table><tr></strong></b></em></i></u></strike></s></blink></tt></pre></big></small></font></select></h1></h2></h3></h4></h5></h6></body></br></a></img></title></span></style></script></table></th></td></tr></frame></area></link></param></hr></input></col></base></meta></basefont></bgsound></embed></spacer></p></dd></dt></caption></colgroup></tbody></tfoot></thead></address></blockquote></center></dir></div></dl></fieldset></listing></menu></ol></ul></li></nobr></wbr></form></button></marquee></object></html></frameset></head></iframe></image></isindex></noembed></noframes></noscript></optgroup></option></plaintext></textarea>
+#errors
+Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE.
+Line: 1 Col: 20 Unexpected end tag (strong) in table context caused voodoo mode.
+Line: 1 Col: 20 End tag (strong) violates step 1, paragraph 1 of the adoption agency algorithm.
+Line: 1 Col: 24 Unexpected end tag (b) in table context caused voodoo mode.
+Line: 1 Col: 24 End tag (b) violates step 1, paragraph 1 of the adoption agency algorithm.
+Line: 1 Col: 29 Unexpected end tag (em) in table context caused voodoo mode.
+Line: 1 Col: 29 End tag (em) violates step 1, paragraph 1 of the adoption agency algorithm.
+Line: 1 Col: 33 Unexpected end tag (i) in table context caused voodoo mode.
+Line: 1 Col: 33 End tag (i) violates step 1, paragraph 1 of the adoption agency algorithm.
+Line: 1 Col: 37 Unexpected end tag (u) in table context caused voodoo mode.
+Line: 1 Col: 37 End tag (u) violates step 1, paragraph 1 of the adoption agency algorithm.
+Line: 1 Col: 46 Unexpected end tag (strike) in table context caused voodoo mode.
+Line: 1 Col: 46 End tag (strike) violates step 1, paragraph 1 of the adoption agency algorithm.
+Line: 1 Col: 50 Unexpected end tag (s) in table context caused voodoo mode.
+Line: 1 Col: 50 End tag (s) violates step 1, paragraph 1 of the adoption agency algorithm.
+Line: 1 Col: 58 Unexpected end tag (blink) in table context caused voodoo mode.
+Line: 1 Col: 58 Unexpected end tag (blink). Ignored.
+Line: 1 Col: 63 Unexpected end tag (tt) in table context caused voodoo mode.
+Line: 1 Col: 63 End tag (tt) violates step 1, paragraph 1 of the adoption agency algorithm.
+Line: 1 Col: 69 Unexpected end tag (pre) in table context caused voodoo mode.
+Line: 1 Col: 69 End tag (pre) seen too early. Expected other end tag.
+Line: 1 Col: 75 Unexpected end tag (big) in table context caused voodoo mode.
+Line: 1 Col: 75 End tag (big) violates step 1, paragraph 1 of the adoption agency algorithm.
+Line: 1 Col: 83 Unexpected end tag (small) in table context caused voodoo mode.
+Line: 1 Col: 83 End tag (small) violates step 1, paragraph 1 of the adoption agency algorithm.
+Line: 1 Col: 90 Unexpected end tag (font) in table context caused voodoo mode.
+Line: 1 Col: 90 End tag (font) violates step 1, paragraph 1 of the adoption agency algorithm.
+Line: 1 Col: 99 Unexpected end tag (select) in table context caused voodoo mode.
+Line: 1 Col: 99 Unexpected end tag (select). Ignored.
+Line: 1 Col: 104 Unexpected end tag (h1) in table context caused voodoo mode.
+Line: 1 Col: 104 End tag (h1) seen too early. Expected other end tag.
+Line: 1 Col: 109 Unexpected end tag (h2) in table context caused voodoo mode.
+Line: 1 Col: 109 End tag (h2) seen too early. Expected other end tag.
+Line: 1 Col: 114 Unexpected end tag (h3) in table context caused voodoo mode.
+Line: 1 Col: 114 End tag (h3) seen too early. Expected other end tag.
+Line: 1 Col: 119 Unexpected end tag (h4) in table context caused voodoo mode.
+Line: 1 Col: 119 End tag (h4) seen too early. Expected other end tag.
+Line: 1 Col: 124 Unexpected end tag (h5) in table context caused voodoo mode.
+Line: 1 Col: 124 End tag (h5) seen too early. Expected other end tag.
+Line: 1 Col: 129 Unexpected end tag (h6) in table context caused voodoo mode.
+Line: 1 Col: 129 End tag (h6) seen too early. Expected other end tag.
+Line: 1 Col: 136 Unexpected end tag (body) in the table row phase. Ignored.
+Line: 1 Col: 141 Unexpected end tag (br) in table context caused voodoo mode.
+Line: 1 Col: 141 Unexpected end tag (br). Treated as br element.
+Line: 1 Col: 145 Unexpected end tag (a) in table context caused voodoo mode.
+Line: 1 Col: 145 End tag (a) violates step 1, paragraph 1 of the adoption agency algorithm.
+Line: 1 Col: 151 Unexpected end tag (img) in table context caused voodoo mode.
+Line: 1 Col: 151 This element (img) has no end tag.
+Line: 1 Col: 159 Unexpected end tag (title) in table context caused voodoo mode.
+Line: 1 Col: 159 Unexpected end tag (title). Ignored.
+Line: 1 Col: 166 Unexpected end tag (span) in table context caused voodoo mode.
+Line: 1 Col: 166 Unexpected end tag (span). Ignored.
+Line: 1 Col: 174 Unexpected end tag (style) in table context caused voodoo mode.
+Line: 1 Col: 174 Unexpected end tag (style). Ignored.
+Line: 1 Col: 183 Unexpected end tag (script) in table context caused voodoo mode.
+Line: 1 Col: 183 Unexpected end tag (script). Ignored.
+Line: 1 Col: 196 Unexpected end tag (th). Ignored.
+Line: 1 Col: 201 Unexpected end tag (td). Ignored.
+Line: 1 Col: 206 Unexpected end tag (tr). Ignored.
+Line: 1 Col: 214 This element (frame) has no end tag.
+Line: 1 Col: 221 This element (area) has no end tag.
+Line: 1 Col: 228 Unexpected end tag (link). Ignored.
+Line: 1 Col: 236 This element (param) has no end tag.
+Line: 1 Col: 241 This element (hr) has no end tag.
+Line: 1 Col: 249 This element (input) has no end tag.
+Line: 1 Col: 255 Unexpected end tag (col). Ignored.
+Line: 1 Col: 262 Unexpected end tag (base). Ignored.
+Line: 1 Col: 269 Unexpected end tag (meta). Ignored.
+Line: 1 Col: 280 This element (basefont) has no end tag.
+Line: 1 Col: 290 This element (bgsound) has no end tag.
+Line: 1 Col: 298 This element (embed) has no end tag.
+Line: 1 Col: 307 This element (spacer) has no end tag.
+Line: 1 Col: 311 Unexpected end tag (p). Ignored.
+Line: 1 Col: 316 End tag (dd) seen too early. Expected other end tag.
+Line: 1 Col: 321 End tag (dt) seen too early. Expected other end tag.
+Line: 1 Col: 331 Unexpected end tag (caption). Ignored.
+Line: 1 Col: 342 Unexpected end tag (colgroup). Ignored.
+Line: 1 Col: 350 Unexpected end tag (tbody). Ignored.
+Line: 1 Col: 358 Unexpected end tag (tfoot). Ignored.
+Line: 1 Col: 366 Unexpected end tag (thead). Ignored.
+Line: 1 Col: 376 End tag (address) seen too early. Expected other end tag.
+Line: 1 Col: 389 End tag (blockquote) seen too early. Expected other end tag.
+Line: 1 Col: 398 End tag (center) seen too early. Expected other end tag.
+Line: 1 Col: 404 Unexpected end tag (dir). Ignored.
+Line: 1 Col: 410 End tag (div) seen too early. Expected other end tag.
+Line: 1 Col: 415 End tag (dl) seen too early. Expected other end tag.
+Line: 1 Col: 426 End tag (fieldset) seen too early. Expected other end tag.
+Line: 1 Col: 436 End tag (listing) seen too early. Expected other end tag.
+Line: 1 Col: 443 End tag (menu) seen too early. Expected other end tag.
+Line: 1 Col: 448 End tag (ol) seen too early. Expected other end tag.
+Line: 1 Col: 453 End tag (ul) seen too early. Expected other end tag.
+Line: 1 Col: 458 End tag (li) seen too early. Expected other end tag.
+Line: 1 Col: 465 End tag (nobr) violates step 1, paragraph 1 of the adoption agency algorithm.
+Line: 1 Col: 471 This element (wbr) has no end tag.
+Line: 1 Col: 487 End tag (button) seen too early. Expected other end tag.
+Line: 1 Col: 497 End tag (marquee) seen too early. Expected other end tag.
+Line: 1 Col: 506 End tag (object) seen too early. Expected other end tag.
+Line: 1 Col: 524 Unexpected end tag (html). Ignored.
+Line: 1 Col: 524 Unexpected end tag (frameset). Ignored.
+Line: 1 Col: 531 Unexpected end tag (head). Ignored.
+Line: 1 Col: 540 Unexpected end tag (iframe). Ignored.
+Line: 1 Col: 548 This element (image) has no end tag.
+Line: 1 Col: 558 This element (isindex) has no end tag.
+Line: 1 Col: 568 Unexpected end tag (noembed). Ignored.
+Line: 1 Col: 579 Unexpected end tag (noframes). Ignored.
+Line: 1 Col: 590 Unexpected end tag (noscript). Ignored.
+Line: 1 Col: 601 Unexpected end tag (optgroup). Ignored.
+Line: 1 Col: 610 Unexpected end tag (option). Ignored.
+Line: 1 Col: 622 Unexpected end tag (plaintext). Ignored.
+Line: 1 Col: 633 Unexpected end tag (textarea). Ignored.
+#document
+| <html>
+| <head>
+| <body>
+| <br>
+| <table>
+| <tbody>
+| <tr>
+| <p>
+
+#data
+<frameset>
+#errors
+Line: 1 Col: 10 Unexpected start tag (frameset). Expected DOCTYPE.
+Line: 1 Col: 10 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <frameset>
diff --git a/pkgs/html/test/data/tree-construction/tests10.dat b/pkgs/html/test/data/tree-construction/tests10.dat
new file mode 100644
index 0000000..4f8df86
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/tests10.dat
@@ -0,0 +1,799 @@
+#data
+<!DOCTYPE html><svg></svg>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <svg svg>
+
+#data
+<!DOCTYPE html><svg></svg><![CDATA[a]]>
+#errors
+29: Bogus comment
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| <!-- [CDATA[a]] -->
+
+#data
+<!DOCTYPE html><body><svg></svg>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <svg svg>
+
+#data
+<!DOCTYPE html><body><select><svg></svg></select>
+#errors
+35: Stray “svg” start tag.
+42: Stray end tag “svg”
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <select>
+
+#data
+<!DOCTYPE html><body><select><option><svg></svg></option></select>
+#errors
+43: Stray “svg” start tag.
+50: Stray end tag “svg”
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <select>
+| <option>
+
+#data
+<!DOCTYPE html><body><table><svg></svg></table>
+#errors
+34: Start tag “svg” seen in “table”.
+41: Stray end tag “svg”.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| <table>
+
+#data
+<!DOCTYPE html><body><table><svg><g>foo</g></svg></table>
+#errors
+34: Start tag “svg” seen in “table”.
+46: Stray end tag “g”.
+53: Stray end tag “svg”.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| <svg g>
+| "foo"
+| <table>
+
+#data
+<!DOCTYPE html><body><table><svg><g>foo</g><g>bar</g></svg></table>
+#errors
+34: Start tag “svg” seen in “table”.
+46: Stray end tag “g”.
+58: Stray end tag “g”.
+65: Stray end tag “svg”.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| <svg g>
+| "foo"
+| <svg g>
+| "bar"
+| <table>
+
+#data
+<!DOCTYPE html><body><table><tbody><svg><g>foo</g><g>bar</g></svg></tbody></table>
+#errors
+41: Start tag “svg” seen in “table”.
+53: Stray end tag “g”.
+65: Stray end tag “g”.
+72: Stray end tag “svg”.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| <svg g>
+| "foo"
+| <svg g>
+| "bar"
+| <table>
+| <tbody>
+
+#data
+<!DOCTYPE html><body><table><tbody><tr><svg><g>foo</g><g>bar</g></svg></tr></tbody></table>
+#errors
+45: Start tag “svg” seen in “table”.
+57: Stray end tag “g”.
+69: Stray end tag “g”.
+76: Stray end tag “svg”.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| <svg g>
+| "foo"
+| <svg g>
+| "bar"
+| <table>
+| <tbody>
+| <tr>
+
+#data
+<!DOCTYPE html><body><table><tbody><tr><td><svg><g>foo</g><g>bar</g></svg></td></tr></tbody></table>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <svg svg>
+| <svg g>
+| "foo"
+| <svg g>
+| "bar"
+
+#data
+<!DOCTYPE html><body><table><tbody><tr><td><svg><g>foo</g><g>bar</g></svg><p>baz</td></tr></tbody></table>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <svg svg>
+| <svg g>
+| "foo"
+| <svg g>
+| "bar"
+| <p>
+| "baz"
+
+#data
+<!DOCTYPE html><body><table><caption><svg><g>foo</g><g>bar</g></svg><p>baz</caption></table>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <caption>
+| <svg svg>
+| <svg g>
+| "foo"
+| <svg g>
+| "bar"
+| <p>
+| "baz"
+
+#data
+<!DOCTYPE html><body><table><caption><svg><g>foo</g><g>bar</g><p>baz</table><p>quux
+#errors
+70: HTML start tag “p” in a foreign namespace context.
+81: “table” closed but “caption” was still open.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <caption>
+| <svg svg>
+| <svg g>
+| "foo"
+| <svg g>
+| "bar"
+| <p>
+| "baz"
+| <p>
+| "quux"
+
+#data
+<!DOCTYPE html><body><table><caption><svg><g>foo</g><g>bar</g>baz</table><p>quux
+#errors
+78: “table” closed but “caption” was still open.
+78: Unclosed elements on stack.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <caption>
+| <svg svg>
+| <svg g>
+| "foo"
+| <svg g>
+| "bar"
+| "baz"
+| <p>
+| "quux"
+
+#data
+<!DOCTYPE html><body><table><colgroup><svg><g>foo</g><g>bar</g><p>baz</table><p>quux
+#errors
+44: Start tag “svg” seen in “table”.
+56: Stray end tag “g”.
+68: Stray end tag “g”.
+71: HTML start tag “p” in a foreign namespace context.
+71: Start tag “p” seen in “table”.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| <svg g>
+| "foo"
+| <svg g>
+| "bar"
+| <p>
+| "baz"
+| <table>
+| <colgroup>
+| <p>
+| "quux"
+
+#data
+<!DOCTYPE html><body><table><tr><td><select><svg><g>foo</g><g>bar</g><p>baz</table><p>quux
+#errors
+50: Stray “svg” start tag.
+54: Stray “g” start tag.
+62: Stray end tag “g”
+66: Stray “g” start tag.
+74: Stray end tag “g”
+77: Stray “p” start tag.
+88: “table” end tag with “select” open.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <select>
+| "foobarbaz"
+| <p>
+| "quux"
+
+#data
+<!DOCTYPE html><body><table><select><svg><g>foo</g><g>bar</g><p>baz</table><p>quux
+#errors
+36: Start tag “select” seen in “table”.
+42: Stray “svg” start tag.
+46: Stray “g” start tag.
+54: Stray end tag “g”
+58: Stray “g” start tag.
+66: Stray end tag “g”
+69: Stray “p” start tag.
+80: “table” end tag with “select” open.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <select>
+| "foobarbaz"
+| <table>
+| <p>
+| "quux"
+
+#data
+<!DOCTYPE html><body></body></html><svg><g>foo</g><g>bar</g><p>baz
+#errors
+41: Stray “svg” start tag.
+68: HTML start tag “p” in a foreign namespace context.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| <svg g>
+| "foo"
+| <svg g>
+| "bar"
+| <p>
+| "baz"
+
+#data
+<!DOCTYPE html><body></body><svg><g>foo</g><g>bar</g><p>baz
+#errors
+34: Stray “svg” start tag.
+61: HTML start tag “p” in a foreign namespace context.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| <svg g>
+| "foo"
+| <svg g>
+| "bar"
+| <p>
+| "baz"
+
+#data
+<!DOCTYPE html><frameset><svg><g></g><g></g><p><span>
+#errors
+31: Stray “svg” start tag.
+35: Stray “g” start tag.
+40: Stray end tag “g”
+44: Stray “g” start tag.
+49: Stray end tag “g”
+52: Stray “p” start tag.
+58: Stray “span” start tag.
+58: End of file seen and there were open elements.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <frameset>
+
+#data
+<!DOCTYPE html><frameset></frameset><svg><g></g><g></g><p><span>
+#errors
+42: Stray “svg” start tag.
+46: Stray “g” start tag.
+51: Stray end tag “g”
+55: Stray “g” start tag.
+60: Stray end tag “g”
+63: Stray “p” start tag.
+69: Stray “span” start tag.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <frameset>
+
+#data
+<!DOCTYPE html><body xlink:href=foo><svg xlink:href=foo></svg>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| xlink:href="foo"
+| <svg svg>
+| xlink href="foo"
+
+#data
+<!DOCTYPE html><body xlink:href=foo xml:lang=en><svg><g xml:lang=en xlink:href=foo></g></svg>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| xlink:href="foo"
+| xml:lang="en"
+| <svg svg>
+| <svg g>
+| xlink href="foo"
+| xml lang="en"
+
+#data
+<!DOCTYPE html><body xlink:href=foo xml:lang=en><svg><g xml:lang=en xlink:href=foo /></svg>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| xlink:href="foo"
+| xml:lang="en"
+| <svg svg>
+| <svg g>
+| xlink href="foo"
+| xml lang="en"
+
+#data
+<!DOCTYPE html><body xlink:href=foo xml:lang=en><svg><g xml:lang=en xlink:href=foo />bar</svg>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| xlink:href="foo"
+| xml:lang="en"
+| <svg svg>
+| <svg g>
+| xlink href="foo"
+| xml lang="en"
+| "bar"
+
+#data
+<svg></path>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <svg svg>
+
+#data
+<div><svg></div>a
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| <svg svg>
+| "a"
+
+#data
+<div><svg><path></div>a
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| <svg svg>
+| <svg path>
+| "a"
+
+#data
+<div><svg><path></svg><path>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| <svg svg>
+| <svg path>
+| <path>
+
+#data
+<div><svg><path><foreignObject><math></div>a
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| <svg svg>
+| <svg path>
+| <svg foreignObject>
+| <math math>
+| "a"
+
+#data
+<div><svg><path><foreignObject><p></div>a
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| <svg svg>
+| <svg path>
+| <svg foreignObject>
+| <p>
+| "a"
+
+#data
+<!DOCTYPE html><svg><desc><div><svg><ul>a
+#errors
+40: HTML start tag “ul” in a foreign namespace context.
+41: End of file in a foreign namespace context.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| <svg desc>
+| <div>
+| <svg svg>
+| <ul>
+| "a"
+
+#data
+<!DOCTYPE html><svg><desc><svg><ul>a
+#errors
+35: HTML start tag “ul” in a foreign namespace context.
+36: End of file in a foreign namespace context.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| <svg desc>
+| <svg svg>
+| <ul>
+| "a"
+
+#data
+<!DOCTYPE html><p><svg><desc><p>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <svg svg>
+| <svg desc>
+| <p>
+
+#data
+<!DOCTYPE html><p><svg><title><p>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <svg svg>
+| <svg title>
+| <p>
+
+#data
+<div><svg><path><foreignObject><p></foreignObject><p>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| <svg svg>
+| <svg path>
+| <svg foreignObject>
+| <p>
+| <p>
+
+#data
+<math><mi><div><object><div><span></span></div></object></div></mi><mi>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math mi>
+| <div>
+| <object>
+| <div>
+| <span>
+| <math mi>
+
+#data
+<math><mi><svg><foreignObject><div><div></div></div></foreignObject></svg></mi><mi>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math mi>
+| <svg svg>
+| <svg foreignObject>
+| <div>
+| <div>
+| <math mi>
+
+#data
+<svg><script></script><path>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| <svg script>
+| <svg path>
+
+#data
+<table><svg></svg><tr>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| <table>
+| <tbody>
+| <tr>
+
+#data
+<math><mi><mglyph>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math mi>
+| <math mglyph>
+
+#data
+<math><mi><malignmark>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math mi>
+| <math malignmark>
+
+#data
+<math><mo><mglyph>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math mo>
+| <math mglyph>
+
+#data
+<math><mo><malignmark>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math mo>
+| <math malignmark>
+
+#data
+<math><mn><mglyph>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math mn>
+| <math mglyph>
+
+#data
+<math><mn><malignmark>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math mn>
+| <math malignmark>
+
+#data
+<math><ms><mglyph>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math ms>
+| <math mglyph>
+
+#data
+<math><ms><malignmark>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math ms>
+| <math malignmark>
+
+#data
+<math><mtext><mglyph>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math mtext>
+| <math mglyph>
+
+#data
+<math><mtext><malignmark>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math mtext>
+| <math malignmark>
+
+#data
+<math><annotation-xml><svg></svg></annotation-xml><mi>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math annotation-xml>
+| <svg svg>
+| <math mi>
+
+#data
+<math><annotation-xml><svg><foreignObject><div><math><mi></mi></math><span></span></div></foreignObject><path></path></svg></annotation-xml><mi>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math annotation-xml>
+| <svg svg>
+| <svg foreignObject>
+| <div>
+| <math math>
+| <math mi>
+| <span>
+| <svg path>
+| <math mi>
+
+#data
+<math><annotation-xml><svg><foreignObject><math><mi><svg></svg></mi><mo></mo></math><span></span></foreignObject><path></path></svg></annotation-xml><mi>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math annotation-xml>
+| <svg svg>
+| <svg foreignObject>
+| <math math>
+| <math mi>
+| <svg svg>
+| <math mo>
+| <span>
+| <svg path>
+| <math mi>
diff --git a/pkgs/html/test/data/tree-construction/tests11.dat b/pkgs/html/test/data/tree-construction/tests11.dat
new file mode 100644
index 0000000..638cde4
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/tests11.dat
@@ -0,0 +1,482 @@
+#data
+<!DOCTYPE html><body><svg attributeName='' attributeType='' baseFrequency='' baseProfile='' calcMode='' clipPathUnits='' contentScriptType='' contentStyleType='' diffuseConstant='' edgeMode='' externalResourcesRequired='' filterRes='' filterUnits='' glyphRef='' gradientTransform='' gradientUnits='' kernelMatrix='' kernelUnitLength='' keyPoints='' keySplines='' keyTimes='' lengthAdjust='' limitingConeAngle='' markerHeight='' markerUnits='' markerWidth='' maskContentUnits='' maskUnits='' numOctaves='' pathLength='' patternContentUnits='' patternTransform='' patternUnits='' pointsAtX='' pointsAtY='' pointsAtZ='' preserveAlpha='' preserveAspectRatio='' primitiveUnits='' refX='' refY='' repeatCount='' repeatDur='' requiredExtensions='' requiredFeatures='' specularConstant='' specularExponent='' spreadMethod='' startOffset='' stdDeviation='' stitchTiles='' surfaceScale='' systemLanguage='' tableValues='' targetX='' targetY='' textLength='' viewBox='' viewTarget='' xChannelSelector='' yChannelSelector='' zoomAndPan=''></svg>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| attributeName=""
+| attributeType=""
+| baseFrequency=""
+| baseProfile=""
+| calcMode=""
+| clipPathUnits=""
+| contentScriptType=""
+| contentStyleType=""
+| diffuseConstant=""
+| edgeMode=""
+| externalResourcesRequired=""
+| filterRes=""
+| filterUnits=""
+| glyphRef=""
+| gradientTransform=""
+| gradientUnits=""
+| kernelMatrix=""
+| kernelUnitLength=""
+| keyPoints=""
+| keySplines=""
+| keyTimes=""
+| lengthAdjust=""
+| limitingConeAngle=""
+| markerHeight=""
+| markerUnits=""
+| markerWidth=""
+| maskContentUnits=""
+| maskUnits=""
+| numOctaves=""
+| pathLength=""
+| patternContentUnits=""
+| patternTransform=""
+| patternUnits=""
+| pointsAtX=""
+| pointsAtY=""
+| pointsAtZ=""
+| preserveAlpha=""
+| preserveAspectRatio=""
+| primitiveUnits=""
+| refX=""
+| refY=""
+| repeatCount=""
+| repeatDur=""
+| requiredExtensions=""
+| requiredFeatures=""
+| specularConstant=""
+| specularExponent=""
+| spreadMethod=""
+| startOffset=""
+| stdDeviation=""
+| stitchTiles=""
+| surfaceScale=""
+| systemLanguage=""
+| tableValues=""
+| targetX=""
+| targetY=""
+| textLength=""
+| viewBox=""
+| viewTarget=""
+| xChannelSelector=""
+| yChannelSelector=""
+| zoomAndPan=""
+
+#data
+<!DOCTYPE html><BODY><SVG ATTRIBUTENAME='' ATTRIBUTETYPE='' BASEFREQUENCY='' BASEPROFILE='' CALCMODE='' CLIPPATHUNITS='' CONTENTSCRIPTTYPE='' CONTENTSTYLETYPE='' DIFFUSECONSTANT='' EDGEMODE='' EXTERNALRESOURCESREQUIRED='' FILTERRES='' FILTERUNITS='' GLYPHREF='' GRADIENTTRANSFORM='' GRADIENTUNITS='' KERNELMATRIX='' KERNELUNITLENGTH='' KEYPOINTS='' KEYSPLINES='' KEYTIMES='' LENGTHADJUST='' LIMITINGCONEANGLE='' MARKERHEIGHT='' MARKERUNITS='' MARKERWIDTH='' MASKCONTENTUNITS='' MASKUNITS='' NUMOCTAVES='' PATHLENGTH='' PATTERNCONTENTUNITS='' PATTERNTRANSFORM='' PATTERNUNITS='' POINTSATX='' POINTSATY='' POINTSATZ='' PRESERVEALPHA='' PRESERVEASPECTRATIO='' PRIMITIVEUNITS='' REFX='' REFY='' REPEATCOUNT='' REPEATDUR='' REQUIREDEXTENSIONS='' REQUIREDFEATURES='' SPECULARCONSTANT='' SPECULAREXPONENT='' SPREADMETHOD='' STARTOFFSET='' STDDEVIATION='' STITCHTILES='' SURFACESCALE='' SYSTEMLANGUAGE='' TABLEVALUES='' TARGETX='' TARGETY='' TEXTLENGTH='' VIEWBOX='' VIEWTARGET='' XCHANNELSELECTOR='' YCHANNELSELECTOR='' ZOOMANDPAN=''></SVG>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| attributeName=""
+| attributeType=""
+| baseFrequency=""
+| baseProfile=""
+| calcMode=""
+| clipPathUnits=""
+| contentScriptType=""
+| contentStyleType=""
+| diffuseConstant=""
+| edgeMode=""
+| externalResourcesRequired=""
+| filterRes=""
+| filterUnits=""
+| glyphRef=""
+| gradientTransform=""
+| gradientUnits=""
+| kernelMatrix=""
+| kernelUnitLength=""
+| keyPoints=""
+| keySplines=""
+| keyTimes=""
+| lengthAdjust=""
+| limitingConeAngle=""
+| markerHeight=""
+| markerUnits=""
+| markerWidth=""
+| maskContentUnits=""
+| maskUnits=""
+| numOctaves=""
+| pathLength=""
+| patternContentUnits=""
+| patternTransform=""
+| patternUnits=""
+| pointsAtX=""
+| pointsAtY=""
+| pointsAtZ=""
+| preserveAlpha=""
+| preserveAspectRatio=""
+| primitiveUnits=""
+| refX=""
+| refY=""
+| repeatCount=""
+| repeatDur=""
+| requiredExtensions=""
+| requiredFeatures=""
+| specularConstant=""
+| specularExponent=""
+| spreadMethod=""
+| startOffset=""
+| stdDeviation=""
+| stitchTiles=""
+| surfaceScale=""
+| systemLanguage=""
+| tableValues=""
+| targetX=""
+| targetY=""
+| textLength=""
+| viewBox=""
+| viewTarget=""
+| xChannelSelector=""
+| yChannelSelector=""
+| zoomAndPan=""
+
+#data
+<!DOCTYPE html><body><svg attributename='' attributetype='' basefrequency='' baseprofile='' calcmode='' clippathunits='' contentscripttype='' contentstyletype='' diffuseconstant='' edgemode='' externalresourcesrequired='' filterres='' filterunits='' glyphref='' gradienttransform='' gradientunits='' kernelmatrix='' kernelunitlength='' keypoints='' keysplines='' keytimes='' lengthadjust='' limitingconeangle='' markerheight='' markerunits='' markerwidth='' maskcontentunits='' maskunits='' numoctaves='' pathlength='' patterncontentunits='' patterntransform='' patternunits='' pointsatx='' pointsaty='' pointsatz='' preservealpha='' preserveaspectratio='' primitiveunits='' refx='' refy='' repeatcount='' repeatdur='' requiredextensions='' requiredfeatures='' specularconstant='' specularexponent='' spreadmethod='' startoffset='' stddeviation='' stitchtiles='' surfacescale='' systemlanguage='' tablevalues='' targetx='' targety='' textlength='' viewbox='' viewtarget='' xchannelselector='' ychannelselector='' zoomandpan=''></svg>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| attributeName=""
+| attributeType=""
+| baseFrequency=""
+| baseProfile=""
+| calcMode=""
+| clipPathUnits=""
+| contentScriptType=""
+| contentStyleType=""
+| diffuseConstant=""
+| edgeMode=""
+| externalResourcesRequired=""
+| filterRes=""
+| filterUnits=""
+| glyphRef=""
+| gradientTransform=""
+| gradientUnits=""
+| kernelMatrix=""
+| kernelUnitLength=""
+| keyPoints=""
+| keySplines=""
+| keyTimes=""
+| lengthAdjust=""
+| limitingConeAngle=""
+| markerHeight=""
+| markerUnits=""
+| markerWidth=""
+| maskContentUnits=""
+| maskUnits=""
+| numOctaves=""
+| pathLength=""
+| patternContentUnits=""
+| patternTransform=""
+| patternUnits=""
+| pointsAtX=""
+| pointsAtY=""
+| pointsAtZ=""
+| preserveAlpha=""
+| preserveAspectRatio=""
+| primitiveUnits=""
+| refX=""
+| refY=""
+| repeatCount=""
+| repeatDur=""
+| requiredExtensions=""
+| requiredFeatures=""
+| specularConstant=""
+| specularExponent=""
+| spreadMethod=""
+| startOffset=""
+| stdDeviation=""
+| stitchTiles=""
+| surfaceScale=""
+| systemLanguage=""
+| tableValues=""
+| targetX=""
+| targetY=""
+| textLength=""
+| viewBox=""
+| viewTarget=""
+| xChannelSelector=""
+| yChannelSelector=""
+| zoomAndPan=""
+
+#data
+<!DOCTYPE html><body><math attributeName='' attributeType='' baseFrequency='' baseProfile='' calcMode='' clipPathUnits='' contentScriptType='' contentStyleType='' diffuseConstant='' edgeMode='' externalResourcesRequired='' filterRes='' filterUnits='' glyphRef='' gradientTransform='' gradientUnits='' kernelMatrix='' kernelUnitLength='' keyPoints='' keySplines='' keyTimes='' lengthAdjust='' limitingConeAngle='' markerHeight='' markerUnits='' markerWidth='' maskContentUnits='' maskUnits='' numOctaves='' pathLength='' patternContentUnits='' patternTransform='' patternUnits='' pointsAtX='' pointsAtY='' pointsAtZ='' preserveAlpha='' preserveAspectRatio='' primitiveUnits='' refX='' refY='' repeatCount='' repeatDur='' requiredExtensions='' requiredFeatures='' specularConstant='' specularExponent='' spreadMethod='' startOffset='' stdDeviation='' stitchTiles='' surfaceScale='' systemLanguage='' tableValues='' targetX='' targetY='' textLength='' viewBox='' viewTarget='' xChannelSelector='' yChannelSelector='' zoomAndPan=''></math>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <math math>
+| attributename=""
+| attributetype=""
+| basefrequency=""
+| baseprofile=""
+| calcmode=""
+| clippathunits=""
+| contentscripttype=""
+| contentstyletype=""
+| diffuseconstant=""
+| edgemode=""
+| externalresourcesrequired=""
+| filterres=""
+| filterunits=""
+| glyphref=""
+| gradienttransform=""
+| gradientunits=""
+| kernelmatrix=""
+| kernelunitlength=""
+| keypoints=""
+| keysplines=""
+| keytimes=""
+| lengthadjust=""
+| limitingconeangle=""
+| markerheight=""
+| markerunits=""
+| markerwidth=""
+| maskcontentunits=""
+| maskunits=""
+| numoctaves=""
+| pathlength=""
+| patterncontentunits=""
+| patterntransform=""
+| patternunits=""
+| pointsatx=""
+| pointsaty=""
+| pointsatz=""
+| preservealpha=""
+| preserveaspectratio=""
+| primitiveunits=""
+| refx=""
+| refy=""
+| repeatcount=""
+| repeatdur=""
+| requiredextensions=""
+| requiredfeatures=""
+| specularconstant=""
+| specularexponent=""
+| spreadmethod=""
+| startoffset=""
+| stddeviation=""
+| stitchtiles=""
+| surfacescale=""
+| systemlanguage=""
+| tablevalues=""
+| targetx=""
+| targety=""
+| textlength=""
+| viewbox=""
+| viewtarget=""
+| xchannelselector=""
+| ychannelselector=""
+| zoomandpan=""
+
+#data
+<!DOCTYPE html><body><svg><altGlyph /><altGlyphDef /><altGlyphItem /><animateColor /><animateMotion /><animateTransform /><clipPath /><feBlend /><feColorMatrix /><feComponentTransfer /><feComposite /><feConvolveMatrix /><feDiffuseLighting /><feDisplacementMap /><feDistantLight /><feFlood /><feFuncA /><feFuncB /><feFuncG /><feFuncR /><feGaussianBlur /><feImage /><feMerge /><feMergeNode /><feMorphology /><feOffset /><fePointLight /><feSpecularLighting /><feSpotLight /><feTile /><feTurbulence /><foreignObject /><glyphRef /><linearGradient /><radialGradient /><textPath /></svg>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| <svg altGlyph>
+| <svg altGlyphDef>
+| <svg altGlyphItem>
+| <svg animateColor>
+| <svg animateMotion>
+| <svg animateTransform>
+| <svg clipPath>
+| <svg feBlend>
+| <svg feColorMatrix>
+| <svg feComponentTransfer>
+| <svg feComposite>
+| <svg feConvolveMatrix>
+| <svg feDiffuseLighting>
+| <svg feDisplacementMap>
+| <svg feDistantLight>
+| <svg feFlood>
+| <svg feFuncA>
+| <svg feFuncB>
+| <svg feFuncG>
+| <svg feFuncR>
+| <svg feGaussianBlur>
+| <svg feImage>
+| <svg feMerge>
+| <svg feMergeNode>
+| <svg feMorphology>
+| <svg feOffset>
+| <svg fePointLight>
+| <svg feSpecularLighting>
+| <svg feSpotLight>
+| <svg feTile>
+| <svg feTurbulence>
+| <svg foreignObject>
+| <svg glyphRef>
+| <svg linearGradient>
+| <svg radialGradient>
+| <svg textPath>
+
+#data
+<!DOCTYPE html><body><svg><altglyph /><altglyphdef /><altglyphitem /><animatecolor /><animatemotion /><animatetransform /><clippath /><feblend /><fecolormatrix /><fecomponenttransfer /><fecomposite /><feconvolvematrix /><fediffuselighting /><fedisplacementmap /><fedistantlight /><feflood /><fefunca /><fefuncb /><fefuncg /><fefuncr /><fegaussianblur /><feimage /><femerge /><femergenode /><femorphology /><feoffset /><fepointlight /><fespecularlighting /><fespotlight /><fetile /><feturbulence /><foreignobject /><glyphref /><lineargradient /><radialgradient /><textpath /></svg>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| <svg altGlyph>
+| <svg altGlyphDef>
+| <svg altGlyphItem>
+| <svg animateColor>
+| <svg animateMotion>
+| <svg animateTransform>
+| <svg clipPath>
+| <svg feBlend>
+| <svg feColorMatrix>
+| <svg feComponentTransfer>
+| <svg feComposite>
+| <svg feConvolveMatrix>
+| <svg feDiffuseLighting>
+| <svg feDisplacementMap>
+| <svg feDistantLight>
+| <svg feFlood>
+| <svg feFuncA>
+| <svg feFuncB>
+| <svg feFuncG>
+| <svg feFuncR>
+| <svg feGaussianBlur>
+| <svg feImage>
+| <svg feMerge>
+| <svg feMergeNode>
+| <svg feMorphology>
+| <svg feOffset>
+| <svg fePointLight>
+| <svg feSpecularLighting>
+| <svg feSpotLight>
+| <svg feTile>
+| <svg feTurbulence>
+| <svg foreignObject>
+| <svg glyphRef>
+| <svg linearGradient>
+| <svg radialGradient>
+| <svg textPath>
+
+#data
+<!DOCTYPE html><BODY><SVG><ALTGLYPH /><ALTGLYPHDEF /><ALTGLYPHITEM /><ANIMATECOLOR /><ANIMATEMOTION /><ANIMATETRANSFORM /><CLIPPATH /><FEBLEND /><FECOLORMATRIX /><FECOMPONENTTRANSFER /><FECOMPOSITE /><FECONVOLVEMATRIX /><FEDIFFUSELIGHTING /><FEDISPLACEMENTMAP /><FEDISTANTLIGHT /><FEFLOOD /><FEFUNCA /><FEFUNCB /><FEFUNCG /><FEFUNCR /><FEGAUSSIANBLUR /><FEIMAGE /><FEMERGE /><FEMERGENODE /><FEMORPHOLOGY /><FEOFFSET /><FEPOINTLIGHT /><FESPECULARLIGHTING /><FESPOTLIGHT /><FETILE /><FETURBULENCE /><FOREIGNOBJECT /><GLYPHREF /><LINEARGRADIENT /><RADIALGRADIENT /><TEXTPATH /></SVG>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| <svg altGlyph>
+| <svg altGlyphDef>
+| <svg altGlyphItem>
+| <svg animateColor>
+| <svg animateMotion>
+| <svg animateTransform>
+| <svg clipPath>
+| <svg feBlend>
+| <svg feColorMatrix>
+| <svg feComponentTransfer>
+| <svg feComposite>
+| <svg feConvolveMatrix>
+| <svg feDiffuseLighting>
+| <svg feDisplacementMap>
+| <svg feDistantLight>
+| <svg feFlood>
+| <svg feFuncA>
+| <svg feFuncB>
+| <svg feFuncG>
+| <svg feFuncR>
+| <svg feGaussianBlur>
+| <svg feImage>
+| <svg feMerge>
+| <svg feMergeNode>
+| <svg feMorphology>
+| <svg feOffset>
+| <svg fePointLight>
+| <svg feSpecularLighting>
+| <svg feSpotLight>
+| <svg feTile>
+| <svg feTurbulence>
+| <svg foreignObject>
+| <svg glyphRef>
+| <svg linearGradient>
+| <svg radialGradient>
+| <svg textPath>
+
+#data
+<!DOCTYPE html><body><math><altGlyph /><altGlyphDef /><altGlyphItem /><animateColor /><animateMotion /><animateTransform /><clipPath /><feBlend /><feColorMatrix /><feComponentTransfer /><feComposite /><feConvolveMatrix /><feDiffuseLighting /><feDisplacementMap /><feDistantLight /><feFlood /><feFuncA /><feFuncB /><feFuncG /><feFuncR /><feGaussianBlur /><feImage /><feMerge /><feMergeNode /><feMorphology /><feOffset /><fePointLight /><feSpecularLighting /><feSpotLight /><feTile /><feTurbulence /><foreignObject /><glyphRef /><linearGradient /><radialGradient /><textPath /></math>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math altglyph>
+| <math altglyphdef>
+| <math altglyphitem>
+| <math animatecolor>
+| <math animatemotion>
+| <math animatetransform>
+| <math clippath>
+| <math feblend>
+| <math fecolormatrix>
+| <math fecomponenttransfer>
+| <math fecomposite>
+| <math feconvolvematrix>
+| <math fediffuselighting>
+| <math fedisplacementmap>
+| <math fedistantlight>
+| <math feflood>
+| <math fefunca>
+| <math fefuncb>
+| <math fefuncg>
+| <math fefuncr>
+| <math fegaussianblur>
+| <math feimage>
+| <math femerge>
+| <math femergenode>
+| <math femorphology>
+| <math feoffset>
+| <math fepointlight>
+| <math fespecularlighting>
+| <math fespotlight>
+| <math fetile>
+| <math feturbulence>
+| <math foreignobject>
+| <math glyphref>
+| <math lineargradient>
+| <math radialgradient>
+| <math textpath>
+
+#data
+<!DOCTYPE html><body><svg><solidColor /></svg>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| <svg solidcolor>
diff --git a/pkgs/html/test/data/tree-construction/tests12.dat b/pkgs/html/test/data/tree-construction/tests12.dat
new file mode 100644
index 0000000..63107d2
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/tests12.dat
@@ -0,0 +1,62 @@
+#data
+<!DOCTYPE html><body><p>foo<math><mtext><i>baz</i></mtext><annotation-xml><svg><desc><b>eggs</b></desc><g><foreignObject><P>spam<TABLE><tr><td><img></td></table></foreignObject></g><g>quux</g></svg></annotation-xml></math>bar
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| "foo"
+| <math math>
+| <math mtext>
+| <i>
+| "baz"
+| <math annotation-xml>
+| <svg svg>
+| <svg desc>
+| <b>
+| "eggs"
+| <svg g>
+| <svg foreignObject>
+| <p>
+| "spam"
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <img>
+| <svg g>
+| "quux"
+| "bar"
+
+#data
+<!DOCTYPE html><body>foo<math><mtext><i>baz</i></mtext><annotation-xml><svg><desc><b>eggs</b></desc><g><foreignObject><P>spam<TABLE><tr><td><img></td></table></foreignObject></g><g>quux</g></svg></annotation-xml></math>bar
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| "foo"
+| <math math>
+| <math mtext>
+| <i>
+| "baz"
+| <math annotation-xml>
+| <svg svg>
+| <svg desc>
+| <b>
+| "eggs"
+| <svg g>
+| <svg foreignObject>
+| <p>
+| "spam"
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <img>
+| <svg g>
+| "quux"
+| "bar"
diff --git a/pkgs/html/test/data/tree-construction/tests14.dat b/pkgs/html/test/data/tree-construction/tests14.dat
new file mode 100644
index 0000000..b8713f8
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/tests14.dat
@@ -0,0 +1,74 @@
+#data
+<!DOCTYPE html><html><body><xyz:abc></xyz:abc>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <xyz:abc>
+
+#data
+<!DOCTYPE html><html><body><xyz:abc></xyz:abc><span></span>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <xyz:abc>
+| <span>
+
+#data
+<!DOCTYPE html><html><html abc:def=gh><xyz:abc></xyz:abc>
+#errors
+15: Unexpected start tag html
+#document
+| <!DOCTYPE html>
+| <html>
+| abc:def="gh"
+| <head>
+| <body>
+| <xyz:abc>
+
+#data
+<!DOCTYPE html><html xml:lang=bar><html xml:lang=foo>
+#errors
+15: Unexpected start tag html
+#document
+| <!DOCTYPE html>
+| <html>
+| xml:lang="bar"
+| <head>
+| <body>
+
+#data
+<!DOCTYPE html><html 123=456>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| 123="456"
+| <head>
+| <body>
+
+#data
+<!DOCTYPE html><html 123=456><html 789=012>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| 123="456"
+| 789="012"
+| <head>
+| <body>
+
+#data
+<!DOCTYPE html><html><body 789=012>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| 789="012"
diff --git a/pkgs/html/test/data/tree-construction/tests15.dat b/pkgs/html/test/data/tree-construction/tests15.dat
new file mode 100644
index 0000000..6ce1c0d
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/tests15.dat
@@ -0,0 +1,208 @@
+#data
+<!DOCTYPE html><p><b><i><u></p> <p>X
+#errors
+Line: 1 Col: 31 Unexpected end tag (p). Ignored.
+Line: 1 Col: 36 Expected closing tag. Unexpected end of file.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <b>
+| <i>
+| <u>
+| <b>
+| <i>
+| <u>
+| " "
+| <p>
+| "X"
+
+#data
+<p><b><i><u></p>
+<p>X
+#errors
+Line: 1 Col: 3 Unexpected start tag (p). Expected DOCTYPE.
+Line: 1 Col: 16 Unexpected end tag (p). Ignored.
+Line: 2 Col: 4 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <p>
+| <b>
+| <i>
+| <u>
+| <b>
+| <i>
+| <u>
+| "
+"
+| <p>
+| "X"
+
+#data
+<!doctype html></html> <head>
+#errors
+Line: 1 Col: 22 Unexpected end tag (html) after the (implied) root element.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| " "
+
+#data
+<!doctype html></body><meta>
+#errors
+Line: 1 Col: 22 Unexpected end tag (body) after the (implied) root element.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <meta>
+
+#data
+<html></html><!-- foo -->
+#errors
+Line: 1 Col: 6 Unexpected start tag (html). Expected DOCTYPE.
+Line: 1 Col: 13 Unexpected end tag (html) after the (implied) root element.
+#document
+| <html>
+| <head>
+| <body>
+| <!-- foo -->
+
+#data
+<!doctype html></body><title>X</title>
+#errors
+Line: 1 Col: 22 Unexpected end tag (body) after the (implied) root element.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <title>
+| "X"
+
+#data
+<!doctype html><table> X<meta></table>
+#errors
+Line: 1 Col: 24 Unexpected non-space characters in table context caused voodoo mode.
+Line: 1 Col: 30 Unexpected start tag (meta) in table context caused voodoo mode.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| " X"
+| <meta>
+| <table>
+
+#data
+<!doctype html><table> x</table>
+#errors
+Line: 1 Col: 24 Unexpected non-space characters in table context caused voodoo mode.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| " x"
+| <table>
+
+#data
+<!doctype html><table> x </table>
+#errors
+Line: 1 Col: 25 Unexpected non-space characters in table context caused voodoo mode.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| " x "
+| <table>
+
+#data
+<!doctype html><table><tr> x</table>
+#errors
+Line: 1 Col: 28 Unexpected non-space characters in table context caused voodoo mode.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| " x"
+| <table>
+| <tbody>
+| <tr>
+
+#data
+<!doctype html><table>X<style> <tr>x </style> </table>
+#errors
+Line: 1 Col: 23 Unexpected non-space characters in table context caused voodoo mode.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| "X"
+| <table>
+| <style>
+| " <tr>x "
+| " "
+
+#data
+<!doctype html><div><table><a>foo</a> <tr><td>bar</td> </tr></table></div>
+#errors
+Line: 1 Col: 30 Unexpected start tag (a) in table context caused voodoo mode.
+Line: 1 Col: 37 Unexpected end tag (a) in table context caused voodoo mode.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <div>
+| <a>
+| "foo"
+| <table>
+| " "
+| <tbody>
+| <tr>
+| <td>
+| "bar"
+| " "
+
+#data
+<frame></frame></frame><frameset><frame><frameset><frame></frameset><noframes></frameset><noframes>
+#errors
+6: Start tag seen without seeing a doctype first. Expected “<!DOCTYPE html>”.
+13: Stray start tag “frame”.
+21: Stray end tag “frame”.
+29: Stray end tag “frame”.
+39: “frameset” start tag after “body” already open.
+105: End of file seen inside an [R]CDATA element.
+105: End of file seen and there were open elements.
+XXX: These errors are wrong, please fix me!
+#document
+| <html>
+| <head>
+| <frameset>
+| <frame>
+| <frameset>
+| <frame>
+| <noframes>
+| "</frameset><noframes>"
+
+#data
+<!DOCTYPE html><object></html>
+#errors
+1: Expected closing tag. Unexpected end of file
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <object>
diff --git a/pkgs/html/test/data/tree-construction/tests16.dat b/pkgs/html/test/data/tree-construction/tests16.dat
new file mode 100644
index 0000000..c8ef66f
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/tests16.dat
@@ -0,0 +1,2299 @@
+#data
+<!doctype html><script>
+#errors
+Line: 1 Col: 23 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| <body>
+
+#data
+<!doctype html><script>a
+#errors
+Line: 1 Col: 24 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "a"
+| <body>
+
+#data
+<!doctype html><script><
+#errors
+Line: 1 Col: 24 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<"
+| <body>
+
+#data
+<!doctype html><script></
+#errors
+Line: 1 Col: 25 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "</"
+| <body>
+
+#data
+<!doctype html><script></S
+#errors
+Line: 1 Col: 26 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "</S"
+| <body>
+
+#data
+<!doctype html><script></SC
+#errors
+Line: 1 Col: 27 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "</SC"
+| <body>
+
+#data
+<!doctype html><script></SCR
+#errors
+Line: 1 Col: 28 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "</SCR"
+| <body>
+
+#data
+<!doctype html><script></SCRI
+#errors
+Line: 1 Col: 29 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "</SCRI"
+| <body>
+
+#data
+<!doctype html><script></SCRIP
+#errors
+Line: 1 Col: 30 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "</SCRIP"
+| <body>
+
+#data
+<!doctype html><script></SCRIPT
+#errors
+Line: 1 Col: 31 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "</SCRIPT"
+| <body>
+
+#data
+<!doctype html><script></SCRIPT
+#errors
+Line: 1 Col: 32 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| <body>
+
+#data
+<!doctype html><script></s
+#errors
+Line: 1 Col: 26 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "</s"
+| <body>
+
+#data
+<!doctype html><script></sc
+#errors
+Line: 1 Col: 27 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "</sc"
+| <body>
+
+#data
+<!doctype html><script></scr
+#errors
+Line: 1 Col: 28 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "</scr"
+| <body>
+
+#data
+<!doctype html><script></scri
+#errors
+Line: 1 Col: 29 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "</scri"
+| <body>
+
+#data
+<!doctype html><script></scrip
+#errors
+Line: 1 Col: 30 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "</scrip"
+| <body>
+
+#data
+<!doctype html><script></script
+#errors
+Line: 1 Col: 31 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "</script"
+| <body>
+
+#data
+<!doctype html><script></script
+#errors
+Line: 1 Col: 32 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| <body>
+
+#data
+<!doctype html><script><!
+#errors
+Line: 1 Col: 25 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!"
+| <body>
+
+#data
+<!doctype html><script><!a
+#errors
+Line: 1 Col: 26 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!a"
+| <body>
+
+#data
+<!doctype html><script><!-
+#errors
+Line: 1 Col: 26 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!-"
+| <body>
+
+#data
+<!doctype html><script><!-a
+#errors
+Line: 1 Col: 27 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!-a"
+| <body>
+
+#data
+<!doctype html><script><!--
+#errors
+Line: 1 Col: 27 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--"
+| <body>
+
+#data
+<!doctype html><script><!--a
+#errors
+Line: 1 Col: 28 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--a"
+| <body>
+
+#data
+<!doctype html><script><!--<
+#errors
+Line: 1 Col: 28 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<"
+| <body>
+
+#data
+<!doctype html><script><!--<a
+#errors
+Line: 1 Col: 29 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<a"
+| <body>
+
+#data
+<!doctype html><script><!--</
+#errors
+Line: 1 Col: 27 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--</"
+| <body>
+
+#data
+<!doctype html><script><!--</script
+#errors
+Line: 1 Col: 35 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--</script"
+| <body>
+
+#data
+<!doctype html><script><!--</script
+#errors
+Line: 1 Col: 36 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--"
+| <body>
+
+#data
+<!doctype html><script><!--<s
+#errors
+Line: 1 Col: 29 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<s"
+| <body>
+
+#data
+<!doctype html><script><!--<script
+#errors
+Line: 1 Col: 34 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script"
+| <body>
+
+#data
+<!doctype html><script><!--<script
+#errors
+Line: 1 Col: 35 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script "
+| <body>
+
+#data
+<!doctype html><script><!--<script <
+#errors
+Line: 1 Col: 36 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script <"
+| <body>
+
+#data
+<!doctype html><script><!--<script <a
+#errors
+Line: 1 Col: 37 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script <a"
+| <body>
+
+#data
+<!doctype html><script><!--<script </
+#errors
+Line: 1 Col: 37 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script </"
+| <body>
+
+#data
+<!doctype html><script><!--<script </s
+#errors
+Line: 1 Col: 38 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script </s"
+| <body>
+
+#data
+<!doctype html><script><!--<script </script
+#errors
+Line: 1 Col: 43 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script </script"
+| <body>
+
+#data
+<!doctype html><script><!--<script </scripta
+#errors
+Line: 1 Col: 44 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script </scripta"
+| <body>
+
+#data
+<!doctype html><script><!--<script </script
+#errors
+Line: 1 Col: 44 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script </script "
+| <body>
+
+#data
+<!doctype html><script><!--<script </script>
+#errors
+Line: 1 Col: 44 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script </script>"
+| <body>
+
+#data
+<!doctype html><script><!--<script </script/
+#errors
+Line: 1 Col: 44 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script </script/"
+| <body>
+
+#data
+<!doctype html><script><!--<script </script <
+#errors
+Line: 1 Col: 45 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script </script <"
+| <body>
+
+#data
+<!doctype html><script><!--<script </script <a
+#errors
+Line: 1 Col: 46 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script </script <a"
+| <body>
+
+#data
+<!doctype html><script><!--<script </script </
+#errors
+Line: 1 Col: 46 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script </script </"
+| <body>
+
+#data
+<!doctype html><script><!--<script </script </script
+#errors
+Line: 1 Col: 52 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script </script </script"
+| <body>
+
+#data
+<!doctype html><script><!--<script </script </script
+#errors
+Line: 1 Col: 53 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script </script "
+| <body>
+
+#data
+<!doctype html><script><!--<script </script </script/
+#errors
+Line: 1 Col: 53 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script </script "
+| <body>
+
+#data
+<!doctype html><script><!--<script </script </script>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script </script "
+| <body>
+
+#data
+<!doctype html><script><!--<script -
+#errors
+Line: 1 Col: 36 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script -"
+| <body>
+
+#data
+<!doctype html><script><!--<script -a
+#errors
+Line: 1 Col: 37 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script -a"
+| <body>
+
+#data
+<!doctype html><script><!--<script -<
+#errors
+Line: 1 Col: 37 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script -<"
+| <body>
+
+#data
+<!doctype html><script><!--<script --
+#errors
+Line: 1 Col: 37 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script --"
+| <body>
+
+#data
+<!doctype html><script><!--<script --a
+#errors
+Line: 1 Col: 38 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script --a"
+| <body>
+
+#data
+<!doctype html><script><!--<script --<
+#errors
+Line: 1 Col: 38 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script --<"
+| <body>
+
+#data
+<!doctype html><script><!--<script -->
+#errors
+Line: 1 Col: 38 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script -->"
+| <body>
+
+#data
+<!doctype html><script><!--<script --><
+#errors
+Line: 1 Col: 39 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script --><"
+| <body>
+
+#data
+<!doctype html><script><!--<script --></
+#errors
+Line: 1 Col: 40 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script --></"
+| <body>
+
+#data
+<!doctype html><script><!--<script --></script
+#errors
+Line: 1 Col: 46 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script --></script"
+| <body>
+
+#data
+<!doctype html><script><!--<script --></script
+#errors
+Line: 1 Col: 47 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script -->"
+| <body>
+
+#data
+<!doctype html><script><!--<script --></script/
+#errors
+Line: 1 Col: 47 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script -->"
+| <body>
+
+#data
+<!doctype html><script><!--<script --></script>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script -->"
+| <body>
+
+#data
+<!doctype html><script><!--<script><\/script>--></script>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script><\/script>-->"
+| <body>
+
+#data
+<!doctype html><script><!--<script></scr'+'ipt>--></script>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script></scr'+'ipt>-->"
+| <body>
+
+#data
+<!doctype html><script><!--<script></script><script></script></script>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script></script><script></script>"
+| <body>
+
+#data
+<!doctype html><script><!--<script></script><script></script>--><!--</script>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script></script><script></script>--><!--"
+| <body>
+
+#data
+<!doctype html><script><!--<script></script><script></script>-- ></script>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script></script><script></script>-- >"
+| <body>
+
+#data
+<!doctype html><script><!--<script></script><script></script>- -></script>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script></script><script></script>- ->"
+| <body>
+
+#data
+<!doctype html><script><!--<script></script><script></script>- - ></script>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script></script><script></script>- - >"
+| <body>
+
+#data
+<!doctype html><script><!--<script></script><script></script>-></script>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script></script><script></script>->"
+| <body>
+
+#data
+<!doctype html><script><!--<script>--!></script>X
+#errors
+Line: 1 Col: 49 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script>--!></script>X"
+| <body>
+
+#data
+<!doctype html><script><!--<scr'+'ipt></script>--></script>
+#errors
+Line: 1 Col: 59 Unexpected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<scr'+'ipt>"
+| <body>
+| "-->"
+
+#data
+<!doctype html><script><!--<script></scr'+'ipt></script>X
+#errors
+Line: 1 Col: 57 Unexpected end of file. Expected end tag (script).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "<!--<script></scr'+'ipt></script>X"
+| <body>
+
+#data
+<!doctype html><style><!--<style></style>--></style>
+#errors
+Line: 1 Col: 52 Unexpected end tag (style).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <style>
+| "<!--<style>"
+| <body>
+| "-->"
+
+#data
+<!doctype html><style><!--</style>X
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <style>
+| "<!--"
+| <body>
+| "X"
+
+#data
+<!doctype html><style><!--...</style>...--></style>
+#errors
+Line: 1 Col: 51 Unexpected end tag (style).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <style>
+| "<!--..."
+| <body>
+| "...-->"
+
+#data
+<!doctype html><style><!--<br><html xmlns:v="urn:schemas-microsoft-com:vml"><!--[if !mso]><style></style>X
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <style>
+| "<!--<br><html xmlns:v="urn:schemas-microsoft-com:vml"><!--[if !mso]><style>"
+| <body>
+| "X"
+
+#data
+<!doctype html><style><!--...<style><!--...--!></style>--></style>
+#errors
+Line: 1 Col: 66 Unexpected end tag (style).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <style>
+| "<!--...<style><!--...--!>"
+| <body>
+| "-->"
+
+#data
+<!doctype html><style><!--...</style><!-- --><style>@import ...</style>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <style>
+| "<!--..."
+| <!-- -->
+| <style>
+| "@import ..."
+| <body>
+
+#data
+<!doctype html><style>...<style><!--...</style><!-- --></style>
+#errors
+Line: 1 Col: 63 Unexpected end tag (style).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <style>
+| "...<style><!--..."
+| <!-- -->
+| <body>
+
+#data
+<!doctype html><style>...<!--[if IE]><style>...</style>X
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <style>
+| "...<!--[if IE]><style>..."
+| <body>
+| "X"
+
+#data
+<!doctype html><title><!--<title></title>--></title>
+#errors
+Line: 1 Col: 52 Unexpected end tag (title).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <title>
+| "<!--<title>"
+| <body>
+| "-->"
+
+#data
+<!doctype html><title></title></title>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <title>
+| "</title>"
+| <body>
+
+#data
+<!doctype html><title>foo/title><link></head><body>X
+#errors
+Line: 1 Col: 52 Unexpected end of file. Expected end tag (title).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <title>
+| "foo/title><link></head><body>X"
+| <body>
+
+#data
+<!doctype html><noscript><!--<noscript></noscript>--></noscript>
+#errors
+Line: 1 Col: 64 Unexpected end tag (noscript).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <noscript>
+| "<!--<noscript>"
+| <body>
+| "-->"
+
+#data
+<!doctype html><noscript><!--</noscript>X<noscript>--></noscript>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <noscript>
+| "<!--"
+| <body>
+| "X"
+| <noscript>
+| "-->"
+
+#data
+<!doctype html><noscript><iframe></noscript>X
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <noscript>
+| "<iframe>"
+| <body>
+| "X"
+
+#data
+<!doctype html><noframes><!--<noframes></noframes>--></noframes>
+#errors
+Line: 1 Col: 64 Unexpected end tag (noframes).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <noframes>
+| "<!--<noframes>"
+| <body>
+| "-->"
+
+#data
+<!doctype html><noframes><body><script><!--...</script></body></noframes></html>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <noframes>
+| "<body><script><!--...</script></body>"
+| <body>
+
+#data
+<!doctype html><textarea><!--<textarea></textarea>--></textarea>
+#errors
+Line: 1 Col: 64 Unexpected end tag (textarea).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <textarea>
+| "<!--<textarea>"
+| "-->"
+
+#data
+<!doctype html><textarea></textarea></textarea>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <textarea>
+| "</textarea>"
+
+#data
+<!doctype html><textarea><</textarea>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <textarea>
+| "<"
+
+#data
+<!doctype html><textarea>a<b</textarea>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <textarea>
+| "a<b"
+
+#data
+<!doctype html><iframe><!--<iframe></iframe>--></iframe>
+#errors
+Line: 1 Col: 56 Unexpected end tag (iframe).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <iframe>
+| "<!--<iframe>"
+| "-->"
+
+#data
+<!doctype html><iframe>...<!--X->...<!--/X->...</iframe>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <iframe>
+| "...<!--X->...<!--/X->..."
+
+#data
+<!doctype html><xmp><!--<xmp></xmp>--></xmp>
+#errors
+Line: 1 Col: 44 Unexpected end tag (xmp).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <xmp>
+| "<!--<xmp>"
+| "-->"
+
+#data
+<!doctype html><noembed><!--<noembed></noembed>--></noembed>
+#errors
+Line: 1 Col: 60 Unexpected end tag (noembed).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <noembed>
+| "<!--<noembed>"
+| "-->"
+
+#data
+<script>
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 8 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| <body>
+
+#data
+<script>a
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 9 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "a"
+| <body>
+
+#data
+<script><
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 9 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<"
+| <body>
+
+#data
+<script></
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 10 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "</"
+| <body>
+
+#data
+<script></S
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 11 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "</S"
+| <body>
+
+#data
+<script></SC
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 12 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "</SC"
+| <body>
+
+#data
+<script></SCR
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 13 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "</SCR"
+| <body>
+
+#data
+<script></SCRI
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 14 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "</SCRI"
+| <body>
+
+#data
+<script></SCRIP
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 15 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "</SCRIP"
+| <body>
+
+#data
+<script></SCRIPT
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 16 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "</SCRIPT"
+| <body>
+
+#data
+<script></SCRIPT
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 17 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| <body>
+
+#data
+<script></s
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 11 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "</s"
+| <body>
+
+#data
+<script></sc
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 12 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "</sc"
+| <body>
+
+#data
+<script></scr
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 13 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "</scr"
+| <body>
+
+#data
+<script></scri
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 14 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "</scri"
+| <body>
+
+#data
+<script></scrip
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 15 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "</scrip"
+| <body>
+
+#data
+<script></script
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 16 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "</script"
+| <body>
+
+#data
+<script></script
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 17 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| <body>
+
+#data
+<script><!
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 10 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!"
+| <body>
+
+#data
+<script><!a
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 11 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!a"
+| <body>
+
+#data
+<script><!-
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 11 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!-"
+| <body>
+
+#data
+<script><!-a
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 12 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!-a"
+| <body>
+
+#data
+<script><!--
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 12 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--"
+| <body>
+
+#data
+<script><!--a
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 13 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--a"
+| <body>
+
+#data
+<script><!--<
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 13 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<"
+| <body>
+
+#data
+<script><!--<a
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 14 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<a"
+| <body>
+
+#data
+<script><!--</
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 14 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--</"
+| <body>
+
+#data
+<script><!--</script
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 20 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--</script"
+| <body>
+
+#data
+<script><!--</script
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 21 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--"
+| <body>
+
+#data
+<script><!--<s
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 14 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<s"
+| <body>
+
+#data
+<script><!--<script
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 19 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script"
+| <body>
+
+#data
+<script><!--<script
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 20 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script "
+| <body>
+
+#data
+<script><!--<script <
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 21 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script <"
+| <body>
+
+#data
+<script><!--<script <a
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 22 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script <a"
+| <body>
+
+#data
+<script><!--<script </
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 22 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script </"
+| <body>
+
+#data
+<script><!--<script </s
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 23 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script </s"
+| <body>
+
+#data
+<script><!--<script </script
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 28 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script </script"
+| <body>
+
+#data
+<script><!--<script </scripta
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 29 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script </scripta"
+| <body>
+
+#data
+<script><!--<script </script
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 29 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script </script "
+| <body>
+
+#data
+<script><!--<script </script>
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 29 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script </script>"
+| <body>
+
+#data
+<script><!--<script </script/
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 29 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script </script/"
+| <body>
+
+#data
+<script><!--<script </script <
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 30 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script </script <"
+| <body>
+
+#data
+<script><!--<script </script <a
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 31 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script </script <a"
+| <body>
+
+#data
+<script><!--<script </script </
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 31 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script </script </"
+| <body>
+
+#data
+<script><!--<script </script </script
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 38 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script </script </script"
+| <body>
+
+#data
+<script><!--<script </script </script
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 38 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script </script "
+| <body>
+
+#data
+<script><!--<script </script </script/
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 38 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script </script "
+| <body>
+
+#data
+<script><!--<script </script </script>
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script </script "
+| <body>
+
+#data
+<script><!--<script -
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 21 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script -"
+| <body>
+
+#data
+<script><!--<script -a
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 22 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script -a"
+| <body>
+
+#data
+<script><!--<script --
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 22 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script --"
+| <body>
+
+#data
+<script><!--<script --a
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 23 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script --a"
+| <body>
+
+#data
+<script><!--<script -->
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 23 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script -->"
+| <body>
+
+#data
+<script><!--<script --><
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 24 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script --><"
+| <body>
+
+#data
+<script><!--<script --></
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 25 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script --></"
+| <body>
+
+#data
+<script><!--<script --></script
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 31 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script --></script"
+| <body>
+
+#data
+<script><!--<script --></script
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 32 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script -->"
+| <body>
+
+#data
+<script><!--<script --></script/
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 32 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script -->"
+| <body>
+
+#data
+<script><!--<script --></script>
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script -->"
+| <body>
+
+#data
+<script><!--<script><\/script>--></script>
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script><\/script>-->"
+| <body>
+
+#data
+<script><!--<script></scr'+'ipt>--></script>
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script></scr'+'ipt>-->"
+| <body>
+
+#data
+<script><!--<script></script><script></script></script>
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script></script><script></script>"
+| <body>
+
+#data
+<script><!--<script></script><script></script>--><!--</script>
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script></script><script></script>--><!--"
+| <body>
+
+#data
+<script><!--<script></script><script></script>-- ></script>
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script></script><script></script>-- >"
+| <body>
+
+#data
+<script><!--<script></script><script></script>- -></script>
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script></script><script></script>- ->"
+| <body>
+
+#data
+<script><!--<script></script><script></script>- - ></script>
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script></script><script></script>- - >"
+| <body>
+
+#data
+<script><!--<script></script><script></script>-></script>
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script></script><script></script>->"
+| <body>
+
+#data
+<script><!--<script>--!></script>X
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 34 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script>--!></script>X"
+| <body>
+
+#data
+<script><!--<scr'+'ipt></script>--></script>
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 44 Unexpected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<scr'+'ipt>"
+| <body>
+| "-->"
+
+#data
+<script><!--<script></scr'+'ipt></script>X
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 42 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "<!--<script></scr'+'ipt></script>X"
+| <body>
+
+#data
+<style><!--<style></style>--></style>
+#errors
+Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE.
+Line: 1 Col: 37 Unexpected end tag (style).
+#document
+| <html>
+| <head>
+| <style>
+| "<!--<style>"
+| <body>
+| "-->"
+
+#data
+<style><!--</style>X
+#errors
+Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <style>
+| "<!--"
+| <body>
+| "X"
+
+#data
+<style><!--...</style>...--></style>
+#errors
+Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE.
+Line: 1 Col: 36 Unexpected end tag (style).
+#document
+| <html>
+| <head>
+| <style>
+| "<!--..."
+| <body>
+| "...-->"
+
+#data
+<style><!--<br><html xmlns:v="urn:schemas-microsoft-com:vml"><!--[if !mso]><style></style>X
+#errors
+Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <style>
+| "<!--<br><html xmlns:v="urn:schemas-microsoft-com:vml"><!--[if !mso]><style>"
+| <body>
+| "X"
+
+#data
+<style><!--...<style><!--...--!></style>--></style>
+#errors
+Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE.
+Line: 1 Col: 51 Unexpected end tag (style).
+#document
+| <html>
+| <head>
+| <style>
+| "<!--...<style><!--...--!>"
+| <body>
+| "-->"
+
+#data
+<style><!--...</style><!-- --><style>@import ...</style>
+#errors
+Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <style>
+| "<!--..."
+| <!-- -->
+| <style>
+| "@import ..."
+| <body>
+
+#data
+<style>...<style><!--...</style><!-- --></style>
+#errors
+Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE.
+Line: 1 Col: 48 Unexpected end tag (style).
+#document
+| <html>
+| <head>
+| <style>
+| "...<style><!--..."
+| <!-- -->
+| <body>
+
+#data
+<style>...<!--[if IE]><style>...</style>X
+#errors
+Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <style>
+| "...<!--[if IE]><style>..."
+| <body>
+| "X"
+
+#data
+<title><!--<title></title>--></title>
+#errors
+Line: 1 Col: 7 Unexpected start tag (title). Expected DOCTYPE.
+Line: 1 Col: 37 Unexpected end tag (title).
+#document
+| <html>
+| <head>
+| <title>
+| "<!--<title>"
+| <body>
+| "-->"
+
+#data
+<title></title></title>
+#errors
+Line: 1 Col: 7 Unexpected start tag (title). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <title>
+| "</title>"
+| <body>
+
+#data
+<title>foo/title><link></head><body>X
+#errors
+Line: 1 Col: 7 Unexpected start tag (title). Expected DOCTYPE.
+Line: 1 Col: 37 Unexpected end of file. Expected end tag (title).
+#document
+| <html>
+| <head>
+| <title>
+| "foo/title><link></head><body>X"
+| <body>
+
+#data
+<noscript><!--<noscript></noscript>--></noscript>
+#errors
+Line: 1 Col: 10 Unexpected start tag (noscript). Expected DOCTYPE.
+Line: 1 Col: 49 Unexpected end tag (noscript).
+#document
+| <html>
+| <head>
+| <noscript>
+| "<!--<noscript>"
+| <body>
+| "-->"
+
+#data
+<noscript><!--</noscript>X<noscript>--></noscript>
+#errors
+Line: 1 Col: 10 Unexpected start tag (noscript). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <noscript>
+| "<!--"
+| <body>
+| "X"
+| <noscript>
+| "-->"
+
+#data
+<noscript><iframe></noscript>X
+#errors
+Line: 1 Col: 10 Unexpected start tag (noscript). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <noscript>
+| "<iframe>"
+| <body>
+| "X"
+
+#data
+<noframes><!--<noframes></noframes>--></noframes>
+#errors
+Line: 1 Col: 10 Unexpected start tag (noframes). Expected DOCTYPE.
+Line: 1 Col: 49 Unexpected end tag (noframes).
+#document
+| <html>
+| <head>
+| <noframes>
+| "<!--<noframes>"
+| <body>
+| "-->"
+
+#data
+<noframes><body><script><!--...</script></body></noframes></html>
+#errors
+Line: 1 Col: 10 Unexpected start tag (noframes). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <noframes>
+| "<body><script><!--...</script></body>"
+| <body>
+
+#data
+<textarea><!--<textarea></textarea>--></textarea>
+#errors
+Line: 1 Col: 10 Unexpected start tag (textarea). Expected DOCTYPE.
+Line: 1 Col: 49 Unexpected end tag (textarea).
+#document
+| <html>
+| <head>
+| <body>
+| <textarea>
+| "<!--<textarea>"
+| "-->"
+
+#data
+<textarea></textarea></textarea>
+#errors
+Line: 1 Col: 10 Unexpected start tag (textarea). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+| <textarea>
+| "</textarea>"
+
+#data
+<iframe><!--<iframe></iframe>--></iframe>
+#errors
+Line: 1 Col: 8 Unexpected start tag (iframe). Expected DOCTYPE.
+Line: 1 Col: 41 Unexpected end tag (iframe).
+#document
+| <html>
+| <head>
+| <body>
+| <iframe>
+| "<!--<iframe>"
+| "-->"
+
+#data
+<iframe>...<!--X->...<!--/X->...</iframe>
+#errors
+Line: 1 Col: 8 Unexpected start tag (iframe). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+| <iframe>
+| "...<!--X->...<!--/X->..."
+
+#data
+<xmp><!--<xmp></xmp>--></xmp>
+#errors
+Line: 1 Col: 5 Unexpected start tag (xmp). Expected DOCTYPE.
+Line: 1 Col: 29 Unexpected end tag (xmp).
+#document
+| <html>
+| <head>
+| <body>
+| <xmp>
+| "<!--<xmp>"
+| "-->"
+
+#data
+<noembed><!--<noembed></noembed>--></noembed>
+#errors
+Line: 1 Col: 9 Unexpected start tag (noembed). Expected DOCTYPE.
+Line: 1 Col: 45 Unexpected end tag (noembed).
+#document
+| <html>
+| <head>
+| <body>
+| <noembed>
+| "<!--<noembed>"
+| "-->"
+
+#data
+<!doctype html><table>
+
+#errors
+Line 2 Col 0 Unexpected end of file. Expected table content.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| "
+"
+
+#data
+<!doctype html><table><td><span><font></span><span>
+#errors
+Line 1 Col 26 Unexpected table cell start tag (td) in the table body phase.
+Line 1 Col 45 Unexpected end tag (span).
+Line 1 Col 51 Expected closing tag. Unexpected end of file.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <span>
+| <font>
+| <font>
+| <span>
+
+#data
+<!doctype html><form><table></form><form></table></form>
+#errors
+35: Stray end tag “form”.
+41: Start tag “form” seen in “table”.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <form>
+| <table>
+| <form>
diff --git a/pkgs/html/test/data/tree-construction/tests17.dat b/pkgs/html/test/data/tree-construction/tests17.dat
new file mode 100644
index 0000000..7b555f8
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/tests17.dat
@@ -0,0 +1,153 @@
+#data
+<!doctype html><table><tbody><select><tr>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <select>
+| <table>
+| <tbody>
+| <tr>
+
+#data
+<!doctype html><table><tr><select><td>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <select>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+
+#data
+<!doctype html><table><tr><td><select><td>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <select>
+| <td>
+
+#data
+<!doctype html><table><tr><th><select><td>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <th>
+| <select>
+| <td>
+
+#data
+<!doctype html><table><caption><select><tr>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <caption>
+| <select>
+| <tbody>
+| <tr>
+
+#data
+<!doctype html><select><tr>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <select>
+
+#data
+<!doctype html><select><td>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <select>
+
+#data
+<!doctype html><select><th>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <select>
+
+#data
+<!doctype html><select><tbody>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <select>
+
+#data
+<!doctype html><select><thead>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <select>
+
+#data
+<!doctype html><select><tfoot>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <select>
+
+#data
+<!doctype html><select><caption>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <select>
+
+#data
+<!doctype html><table><tr></table>a
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| "a"
diff --git a/pkgs/html/test/data/tree-construction/tests18.dat b/pkgs/html/test/data/tree-construction/tests18.dat
new file mode 100644
index 0000000..680e1f0
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/tests18.dat
@@ -0,0 +1,269 @@
+#data
+<!doctype html><plaintext></plaintext>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <plaintext>
+| "</plaintext>"
+
+#data
+<!doctype html><table><plaintext></plaintext>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <plaintext>
+| "</plaintext>"
+| <table>
+
+#data
+<!doctype html><table><tbody><plaintext></plaintext>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <plaintext>
+| "</plaintext>"
+| <table>
+| <tbody>
+
+#data
+<!doctype html><table><tbody><tr><plaintext></plaintext>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <plaintext>
+| "</plaintext>"
+| <table>
+| <tbody>
+| <tr>
+
+#data
+<!doctype html><table><tbody><tr><plaintext></plaintext>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <plaintext>
+| "</plaintext>"
+| <table>
+| <tbody>
+| <tr>
+
+#data
+<!doctype html><table><td><plaintext></plaintext>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <plaintext>
+| "</plaintext>"
+
+#data
+<!doctype html><table><caption><plaintext></plaintext>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <caption>
+| <plaintext>
+| "</plaintext>"
+
+#data
+<!doctype html><table><tr><style></script></style>abc
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| "abc"
+| <table>
+| <tbody>
+| <tr>
+| <style>
+| "</script>"
+
+#data
+<!doctype html><table><tr><script></style></script>abc
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| "abc"
+| <table>
+| <tbody>
+| <tr>
+| <script>
+| "</style>"
+
+#data
+<!doctype html><table><caption><style></script></style>abc
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <caption>
+| <style>
+| "</script>"
+| "abc"
+
+#data
+<!doctype html><table><td><style></script></style>abc
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <style>
+| "</script>"
+| "abc"
+
+#data
+<!doctype html><select><script></style></script>abc
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <select>
+| <script>
+| "</style>"
+| "abc"
+
+#data
+<!doctype html><table><select><script></style></script>abc
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <select>
+| <script>
+| "</style>"
+| "abc"
+| <table>
+
+#data
+<!doctype html><table><tr><select><script></style></script>abc
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <select>
+| <script>
+| "</style>"
+| "abc"
+| <table>
+| <tbody>
+| <tr>
+
+#data
+<!doctype html><frameset></frameset><noframes>abc
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <frameset>
+| <noframes>
+| "abc"
+
+#data
+<!doctype html><frameset></frameset><noframes>abc</noframes><!--abc-->
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <frameset>
+| <noframes>
+| "abc"
+| <!-- abc -->
+
+#data
+<!doctype html><frameset></frameset></html><noframes>abc
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <frameset>
+| <noframes>
+| "abc"
+
+#data
+<!doctype html><frameset></frameset></html><noframes>abc</noframes><!--abc-->
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <frameset>
+| <noframes>
+| "abc"
+| <!-- abc -->
+
+#data
+<!doctype html><table><tr></tbody><tfoot>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <tfoot>
+
+#data
+<!doctype html><table><td><svg></svg>abc<td>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <svg svg>
+| "abc"
+| <td>
diff --git a/pkgs/html/test/data/tree-construction/tests19.dat b/pkgs/html/test/data/tree-construction/tests19.dat
new file mode 100644
index 0000000..0d62f5a
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/tests19.dat
@@ -0,0 +1,1237 @@
+#data
+<!doctype html><math><mn DefinitionUrl="foo">
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math mn>
+| definitionURL="foo"
+
+#data
+<!doctype html><html></p><!--foo-->
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <!-- foo -->
+| <head>
+| <body>
+
+#data
+<!doctype html><head></head></p><!--foo-->
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <!-- foo -->
+| <body>
+
+#data
+<!doctype html><body><p><pre>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <pre>
+
+#data
+<!doctype html><body><p><listing>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <listing>
+
+#data
+<!doctype html><p><plaintext>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <plaintext>
+
+#data
+<!doctype html><p><h1>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <h1>
+
+#data
+<!doctype html><form><isindex>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <form>
+
+#data
+<!doctype html><isindex action="POST">
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <form>
+| action="POST"
+| <hr>
+| <label>
+| "This is a searchable index. Enter search keywords: "
+| <input>
+| name="isindex"
+| <hr>
+
+#data
+<!doctype html><isindex prompt="this is isindex">
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <form>
+| <hr>
+| <label>
+| "this is isindex"
+| <input>
+| name="isindex"
+| <hr>
+
+#data
+<!doctype html><isindex type="hidden">
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <form>
+| <hr>
+| <label>
+| "This is a searchable index. Enter search keywords: "
+| <input>
+| name="isindex"
+| type="hidden"
+| <hr>
+
+#data
+<!doctype html><isindex name="foo">
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <form>
+| <hr>
+| <label>
+| "This is a searchable index. Enter search keywords: "
+| <input>
+| name="isindex"
+| <hr>
+
+#data
+<!doctype html><ruby><p><rp>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <ruby>
+| <p>
+| <rp>
+
+#data
+<!doctype html><ruby><div><span><rp>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <ruby>
+| <div>
+| <span>
+| <rp>
+
+#data
+<!doctype html><ruby><div><p><rp>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <ruby>
+| <div>
+| <p>
+| <rp>
+
+#data
+<!doctype html><ruby><p><rt>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <ruby>
+| <p>
+| <rt>
+
+#data
+<!doctype html><ruby><div><span><rt>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <ruby>
+| <div>
+| <span>
+| <rt>
+
+#data
+<!doctype html><ruby><div><p><rt>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <ruby>
+| <div>
+| <p>
+| <rt>
+
+#data
+<!doctype html><math/><foo>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <math math>
+| <foo>
+
+#data
+<!doctype html><svg/><foo>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| <foo>
+
+#data
+<!doctype html><div></body><!--foo-->
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <div>
+| <!-- foo -->
+
+#data
+<!doctype html><h1><div><h3><span></h1>foo
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <h1>
+| <div>
+| <h3>
+| <span>
+| "foo"
+
+#data
+<!doctype html><p></h3>foo
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| "foo"
+
+#data
+<!doctype html><h3><li>abc</h2>foo
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <h3>
+| <li>
+| "abc"
+| "foo"
+
+#data
+<!doctype html><table>abc<!--foo-->
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| "abc"
+| <table>
+| <!-- foo -->
+
+#data
+<!doctype html><table> <!--foo-->
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| " "
+| <!-- foo -->
+
+#data
+<!doctype html><table> b <!--foo-->
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| " b "
+| <table>
+| <!-- foo -->
+
+#data
+<!doctype html><select><option><option>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <select>
+| <option>
+| <option>
+
+#data
+<!doctype html><select><option></optgroup>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <select>
+| <option>
+
+#data
+<!doctype html><select><option></optgroup>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <select>
+| <option>
+
+#data
+<!doctype html><p><math><mi><p><h1>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <math math>
+| <math mi>
+| <p>
+| <h1>
+
+#data
+<!doctype html><p><math><mo><p><h1>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <math math>
+| <math mo>
+| <p>
+| <h1>
+
+#data
+<!doctype html><p><math><mn><p><h1>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <math math>
+| <math mn>
+| <p>
+| <h1>
+
+#data
+<!doctype html><p><math><ms><p><h1>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <math math>
+| <math ms>
+| <p>
+| <h1>
+
+#data
+<!doctype html><p><math><mtext><p><h1>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <math math>
+| <math mtext>
+| <p>
+| <h1>
+
+#data
+<!doctype html><frameset></noframes>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <frameset>
+
+#data
+<!doctype html><html c=d><body></html><html a=b>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| a="b"
+| c="d"
+| <head>
+| <body>
+
+#data
+<!doctype html><html c=d><frameset></frameset></html><html a=b>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| a="b"
+| c="d"
+| <head>
+| <frameset>
+
+#data
+<!doctype html><html><frameset></frameset></html><!--foo-->
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <frameset>
+| <!-- foo -->
+
+#data
+<!doctype html><html><frameset></frameset></html>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <frameset>
+| " "
+
+#data
+<!doctype html><html><frameset></frameset></html>abc
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <frameset>
+
+#data
+<!doctype html><html><frameset></frameset></html><p>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <frameset>
+
+#data
+<!doctype html><html><frameset></frameset></html></p>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <frameset>
+
+#data
+<html><frameset></frameset></html><!doctype html>
+#errors
+#document
+| <html>
+| <head>
+| <frameset>
+
+#data
+<!doctype html><body><frameset>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+
+#data
+<!doctype html><p><frameset><frame>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <frameset>
+| <frame>
+
+#data
+<!doctype html><p>a<frameset>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| "a"
+
+#data
+<!doctype html><p> <frameset><frame>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <frameset>
+| <frame>
+
+#data
+<!doctype html><pre><frameset>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <pre>
+
+#data
+<!doctype html><listing><frameset>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <listing>
+
+#data
+<!doctype html><li><frameset>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <li>
+
+#data
+<!doctype html><dd><frameset>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <dd>
+
+#data
+<!doctype html><dt><frameset>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <dt>
+
+#data
+<!doctype html><button><frameset>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <button>
+
+#data
+<!doctype html><applet><frameset>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <applet>
+
+#data
+<!doctype html><marquee><frameset>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <marquee>
+
+#data
+<!doctype html><object><frameset>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <object>
+
+#data
+<!doctype html><table><frameset>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+
+#data
+<!doctype html><area><frameset>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <area>
+
+#data
+<!doctype html><basefont><frameset>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <basefont>
+| <frameset>
+
+#data
+<!doctype html><bgsound><frameset>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <bgsound>
+| <frameset>
+
+#data
+<!doctype html><br><frameset>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <br>
+
+#data
+<!doctype html><embed><frameset>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <embed>
+
+#data
+<!doctype html><img><frameset>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <img>
+
+#data
+<!doctype html><input><frameset>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <input>
+
+#data
+<!doctype html><keygen><frameset>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <keygen>
+
+#data
+<!doctype html><wbr><frameset>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <wbr>
+
+#data
+<!doctype html><hr><frameset>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <hr>
+
+#data
+<!doctype html><textarea></textarea><frameset>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <textarea>
+
+#data
+<!doctype html><xmp></xmp><frameset>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <xmp>
+
+#data
+<!doctype html><iframe></iframe><frameset>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <iframe>
+
+#data
+<!doctype html><select></select><frameset>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <select>
+
+#data
+<!doctype html><svg></svg><frameset><frame>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <frameset>
+| <frame>
+
+#data
+<!doctype html><math></math><frameset><frame>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <frameset>
+| <frame>
+
+#data
+<!doctype html><svg><foreignObject><div> <frameset><frame>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <frameset>
+| <frame>
+
+#data
+<!doctype html><svg>a</svg><frameset><frame>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| "a"
+
+#data
+<!doctype html><svg> </svg><frameset><frame>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <frameset>
+| <frame>
+
+#data
+<html>aaa<frameset></frameset>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "aaa"
+
+#data
+<html> a <frameset></frameset>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "a "
+
+#data
+<!doctype html><div><frameset>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <frameset>
+
+#data
+<!doctype html><div><body><frameset>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <div>
+
+#data
+<!doctype html><p><math></p>a
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <math math>
+| "a"
+
+#data
+<!doctype html><p><math><mn><span></p>a
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <math math>
+| <math mn>
+| <span>
+| <p>
+| "a"
+
+#data
+<!doctype html><math></html>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <math math>
+
+#data
+<!doctype html><meta charset="ascii">
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <meta>
+| charset="ascii"
+| <body>
+
+#data
+<!doctype html><meta http-equiv="content-type" content="text/html;charset=ascii">
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <meta>
+| content="text/html;charset=ascii"
+| http-equiv="content-type"
+| <body>
+
+#data
+<!doctype html><head><!--aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa--><meta charset="utf8">
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <!-- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -->
+| <meta>
+| charset="utf8"
+| <body>
+
+#data
+<!doctype html><html a=b><head></head><html c=d>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| a="b"
+| c="d"
+| <head>
+| <body>
+
+#data
+<!doctype html><image/>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <img>
+
+#data
+<!doctype html>a<i>b<table>c<b>d</i>e</b>f
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| "a"
+| <i>
+| "bc"
+| <b>
+| "de"
+| "f"
+| <table>
+
+#data
+<!doctype html><table><i>a<b>b<div>c<a>d</i>e</b>f
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <i>
+| "a"
+| <b>
+| "b"
+| <b>
+| <div>
+| <b>
+| <i>
+| "c"
+| <a>
+| "d"
+| <a>
+| "e"
+| <a>
+| "f"
+| <table>
+
+#data
+<!doctype html><i>a<b>b<div>c<a>d</i>e</b>f
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <i>
+| "a"
+| <b>
+| "b"
+| <b>
+| <div>
+| <b>
+| <i>
+| "c"
+| <a>
+| "d"
+| <a>
+| "e"
+| <a>
+| "f"
+
+#data
+<!doctype html><table><i>a<b>b<div>c</i>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <i>
+| "a"
+| <b>
+| "b"
+| <b>
+| <div>
+| <i>
+| "c"
+| <table>
+
+#data
+<!doctype html><table><i>a<b>b<div>c<a>d</i>e</b>f
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <i>
+| "a"
+| <b>
+| "b"
+| <b>
+| <div>
+| <b>
+| <i>
+| "c"
+| <a>
+| "d"
+| <a>
+| "e"
+| <a>
+| "f"
+| <table>
+
+#data
+<!doctype html><table><i>a<div>b<tr>c<b>d</i>e
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <i>
+| "a"
+| <div>
+| "b"
+| <i>
+| "c"
+| <b>
+| "d"
+| <b>
+| "e"
+| <table>
+| <tbody>
+| <tr>
+
+#data
+<!doctype html><table><td><table><i>a<div>b<b>c</i>d
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <i>
+| "a"
+| <div>
+| <i>
+| "b"
+| <b>
+| "c"
+| <b>
+| "d"
+| <table>
+
+#data
+<!doctype html><body><bgsound>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <bgsound>
+
+#data
+<!doctype html><body><basefont>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <basefont>
+
+#data
+<!doctype html><a><b></a><basefont>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <a>
+| <b>
+| <basefont>
+
+#data
+<!doctype html><a><b></a><bgsound>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <a>
+| <b>
+| <bgsound>
+
+#data
+<!doctype html><figcaption><article></figcaption>a
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <figcaption>
+| <article>
+| "a"
+
+#data
+<!doctype html><summary><article></summary>a
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <summary>
+| <article>
+| "a"
+
+#data
+<!doctype html><p><a><plaintext>b
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <a>
+| <plaintext>
+| <a>
+| "b"
+
+#data
+<!DOCTYPE html><div>a<a></div>b<p>c</p>d
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <div>
+| "a"
+| <a>
+| <a>
+| "b"
+| <p>
+| "c"
+| "d"
diff --git a/pkgs/html/test/data/tree-construction/tests2.dat b/pkgs/html/test/data/tree-construction/tests2.dat
new file mode 100644
index 0000000..60d8592
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/tests2.dat
@@ -0,0 +1,763 @@
+#data
+<!DOCTYPE html>Test
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| "Test"
+
+#data
+<textarea>test</div>test
+#errors
+Line: 1 Col: 10 Unexpected start tag (textarea). Expected DOCTYPE.
+Line: 1 Col: 24 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <textarea>
+| "test</div>test"
+
+#data
+<table><td>
+#errors
+Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE.
+Line: 1 Col: 11 Unexpected table cell start tag (td) in the table body phase.
+Line: 1 Col: 11 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+
+#data
+<table><td>test</tbody></table>
+#errors
+Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE.
+Line: 1 Col: 11 Unexpected table cell start tag (td) in the table body phase.
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| "test"
+
+#data
+<frame>test
+#errors
+Line: 1 Col: 7 Unexpected start tag (frame). Expected DOCTYPE.
+Line: 1 Col: 7 Unexpected start tag frame. Ignored.
+#document
+| <html>
+| <head>
+| <body>
+| "test"
+
+#data
+<!DOCTYPE html><frameset>test
+#errors
+Line: 1 Col: 29 Unepxected characters in the frameset phase. Characters ignored.
+Line: 1 Col: 29 Expected closing tag. Unexpected end of file.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <frameset>
+
+#data
+<!DOCTYPE html><frameset><!DOCTYPE html>
+#errors
+Line: 1 Col: 40 Unexpected DOCTYPE. Ignored.
+Line: 1 Col: 40 Expected closing tag. Unexpected end of file.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <frameset>
+
+#data
+<!DOCTYPE html><font><p><b>test</font>
+#errors
+Line: 1 Col: 38 End tag (font) violates step 1, paragraph 3 of the adoption agency algorithm.
+Line: 1 Col: 38 End tag (font) violates step 1, paragraph 3 of the adoption agency algorithm.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <font>
+| <p>
+| <font>
+| <b>
+| "test"
+
+#data
+<!DOCTYPE html><dt><div><dd>
+#errors
+Line: 1 Col: 28 Missing end tag (div, dt).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <dt>
+| <div>
+| <dd>
+
+#data
+<script></x
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+Line: 1 Col: 11 Unexpected end of file. Expected end tag (script).
+#document
+| <html>
+| <head>
+| <script>
+| "</x"
+| <body>
+
+#data
+<table><plaintext><td>
+#errors
+Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE.
+Line: 1 Col: 18 Unexpected start tag (plaintext) in table context caused voodoo mode.
+Line: 1 Col: 22 Unexpected end of file. Expected table content.
+#document
+| <html>
+| <head>
+| <body>
+| <plaintext>
+| "<td>"
+| <table>
+
+#data
+<plaintext></plaintext>
+#errors
+Line: 1 Col: 11 Unexpected start tag (plaintext). Expected DOCTYPE.
+Line: 1 Col: 23 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <plaintext>
+| "</plaintext>"
+
+#data
+<!DOCTYPE html><table><tr>TEST
+#errors
+Line: 1 Col: 30 Unexpected non-space characters in table context caused voodoo mode.
+Line: 1 Col: 30 Unexpected end of file. Expected table content.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| "TEST"
+| <table>
+| <tbody>
+| <tr>
+
+#data
+<!DOCTYPE html><body t1=1><body t2=2><body t3=3 t4=4>
+#errors
+Line: 1 Col: 37 Unexpected start tag (body).
+Line: 1 Col: 53 Unexpected start tag (body).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| t1="1"
+| t2="2"
+| t3="3"
+| t4="4"
+
+#data
+</b test
+#errors
+Line: 1 Col: 8 Unexpected end of file in attribute name.
+Line: 1 Col: 8 End tag contains unexpected attributes.
+Line: 1 Col: 8 Unexpected end tag (b). Expected DOCTYPE.
+Line: 1 Col: 8 Unexpected end tag (b) after the (implied) root element.
+#document
+| <html>
+| <head>
+| <body>
+
+#data
+<!DOCTYPE html></b test<b &=&>X
+#errors
+Line: 1 Col: 32 Named entity didn't end with ';'.
+Line: 1 Col: 33 End tag contains unexpected attributes.
+Line: 1 Col: 33 Unexpected end tag (b) after the (implied) root element.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| "X"
+
+#data
+<!doctypehtml><scrIPt type=text/x-foobar;baz>X</SCRipt
+#errors
+Line: 1 Col: 9 No space after literal string 'DOCTYPE'.
+Line: 1 Col: 54 Unexpected end of file in the tag name.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| type="text/x-foobar;baz"
+| "X</SCRipt"
+| <body>
+
+#data
+&
+#errors
+Line: 1 Col: 1 Unexpected non-space characters. Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+| "&"
+
+#data
+&#
+#errors
+Line: 1 Col: 1 Numeric entity expected. Got end of file instead.
+Line: 1 Col: 1 Unexpected non-space characters. Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+| "&#"
+
+#data
+&#X
+#errors
+Line: 1 Col: 3 Numeric entity expected but none found.
+Line: 1 Col: 3 Unexpected non-space characters. Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+| "&#X"
+
+#data
+&#x
+#errors
+Line: 1 Col: 3 Numeric entity expected but none found.
+Line: 1 Col: 3 Unexpected non-space characters. Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+| "&#x"
+
+#data
+-
+#errors
+Line: 1 Col: 4 Numeric entity didn't end with ';'.
+Line: 1 Col: 4 Unexpected non-space characters. Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+| "-"
+
+#data
+&x-test
+#errors
+Line: 1 Col: 1 Named entity expected. Got none.
+Line: 1 Col: 1 Unexpected non-space characters. Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+| "&x-test"
+
+#data
+<!doctypehtml><p><li>
+#errors
+Line: 1 Col: 9 No space after literal string 'DOCTYPE'.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <li>
+
+#data
+<!doctypehtml><p><dt>
+#errors
+Line: 1 Col: 9 No space after literal string 'DOCTYPE'.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <dt>
+
+#data
+<!doctypehtml><p><dd>
+#errors
+Line: 1 Col: 9 No space after literal string 'DOCTYPE'.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <dd>
+
+#data
+<!doctypehtml><p><form>
+#errors
+Line: 1 Col: 9 No space after literal string 'DOCTYPE'.
+Line: 1 Col: 23 Expected closing tag. Unexpected end of file.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <form>
+
+#data
+<!DOCTYPE html><p></P>X
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| "X"
+
+#data
+&
+#errors
+Line: 1 Col: 4 Named entity didn't end with ';'.
+Line: 1 Col: 4 Unexpected non-space characters. Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+| "&"
+
+#data
+&AMp;
+#errors
+Line: 1 Col: 1 Named entity expected. Got none.
+Line: 1 Col: 1 Unexpected non-space characters. Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+| "&AMp;"
+
+#data
+<!DOCTYPE html><html><head></head><body><thisISasillyTESTelementNameToMakeSureCrazyTagNamesArePARSEDcorrectLY>
+#errors
+Line: 1 Col: 110 Expected closing tag. Unexpected end of file.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <thisisasillytestelementnametomakesurecrazytagnamesareparsedcorrectly>
+
+#data
+<!DOCTYPE html>X</body>X
+#errors
+Line: 1 Col: 24 Unexpected non-space characters in the after body phase.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| "XX"
+
+#data
+<!DOCTYPE html><!-- X
+#errors
+Line: 1 Col: 21 Unexpected end of file in comment.
+#document
+| <!DOCTYPE html>
+| <!-- X -->
+| <html>
+| <head>
+| <body>
+
+#data
+<!DOCTYPE html><table><caption>test TEST</caption><td>test
+#errors
+Line: 1 Col: 54 Unexpected table cell start tag (td) in the table body phase.
+Line: 1 Col: 58 Expected closing tag. Unexpected end of file.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <caption>
+| "test TEST"
+| <tbody>
+| <tr>
+| <td>
+| "test"
+
+#data
+<!DOCTYPE html><select><option><optgroup>
+#errors
+Line: 1 Col: 41 Expected closing tag. Unexpected end of file.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <select>
+| <option>
+| <optgroup>
+
+#data
+<!DOCTYPE html><select><optgroup><option></optgroup><option><select><option>
+#errors
+Line: 1 Col: 68 Unexpected select start tag in the select phase treated as select end tag.
+Line: 1 Col: 76 Expected closing tag. Unexpected end of file.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <select>
+| <optgroup>
+| <option>
+| <option>
+| <option>
+
+#data
+<!DOCTYPE html><select><optgroup><option><optgroup>
+#errors
+Line: 1 Col: 51 Expected closing tag. Unexpected end of file.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <select>
+| <optgroup>
+| <option>
+| <optgroup>
+
+#data
+<!DOCTYPE html><datalist><option>foo</datalist>bar
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <datalist>
+| <option>
+| "foo"
+| "bar"
+
+#data
+<!DOCTYPE html><font><input><input></font>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <font>
+| <input>
+| <input>
+
+#data
+<!DOCTYPE html><!-- XXX - XXX -->
+#errors
+#document
+| <!DOCTYPE html>
+| <!-- XXX - XXX -->
+| <html>
+| <head>
+| <body>
+
+#data
+<!DOCTYPE html><!-- XXX - XXX
+#errors
+Line: 1 Col: 29 Unexpected end of file in comment (-)
+#document
+| <!DOCTYPE html>
+| <!-- XXX - XXX -->
+| <html>
+| <head>
+| <body>
+
+#data
+<!DOCTYPE html><!-- XXX - XXX - XXX -->
+#errors
+#document
+| <!DOCTYPE html>
+| <!-- XXX - XXX - XXX -->
+| <html>
+| <head>
+| <body>
+
+#data
+<isindex test=x name=x>
+#errors
+Line: 1 Col: 23 Unexpected start tag (isindex). Expected DOCTYPE.
+Line: 1 Col: 23 Unexpected start tag isindex. Don't use it!
+#document
+| <html>
+| <head>
+| <body>
+| <form>
+| <hr>
+| <label>
+| "This is a searchable index. Enter search keywords: "
+| <input>
+| name="isindex"
+| test="x"
+| <hr>
+
+#data
+test
+test
+#errors
+Line: 2 Col: 4 Unexpected non-space characters. Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+| "test
+test"
+
+#data
+<!DOCTYPE html><body><title>test</body></title>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <title>
+| "test</body>"
+
+#data
+<!DOCTYPE html><body><title>X</title><meta name=z><link rel=foo><style>
+x { content:"</style" } </style>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <title>
+| "X"
+| <meta>
+| name="z"
+| <link>
+| rel="foo"
+| <style>
+| "
+x { content:"</style" } "
+
+#data
+<!DOCTYPE html><select><optgroup></optgroup></select>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <select>
+| <optgroup>
+
+#data
+
+
+#errors
+Line: 2 Col: 1 Unexpected End of file. Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+
+#data
+<!DOCTYPE html> <html>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+
+#data
+<!DOCTYPE html><script>
+</script> <title>x</title> </head>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <script>
+| "
+"
+| " "
+| <title>
+| "x"
+| " "
+| <body>
+
+#data
+<!DOCTYPE html><html><body><html id=x>
+#errors
+Line: 1 Col: 38 html needs to be the first start tag.
+#document
+| <!DOCTYPE html>
+| <html>
+| id="x"
+| <head>
+| <body>
+
+#data
+<!DOCTYPE html>X</body><html id="x">
+#errors
+Line: 1 Col: 36 Unexpected start tag token (html) in the after body phase.
+Line: 1 Col: 36 html needs to be the first start tag.
+#document
+| <!DOCTYPE html>
+| <html>
+| id="x"
+| <head>
+| <body>
+| "X"
+
+#data
+<!DOCTYPE html><head><html id=x>
+#errors
+Line: 1 Col: 32 html needs to be the first start tag.
+#document
+| <!DOCTYPE html>
+| <html>
+| id="x"
+| <head>
+| <body>
+
+#data
+<!DOCTYPE html>X</html>X
+#errors
+Line: 1 Col: 24 Unexpected non-space characters in the after body phase.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| "XX"
+
+#data
+<!DOCTYPE html>X</html>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| "X "
+
+#data
+<!DOCTYPE html>X</html><p>X
+#errors
+Line: 1 Col: 26 Unexpected start tag (p).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| "X"
+| <p>
+| "X"
+
+#data
+<!DOCTYPE html>X<p/x/y/z>
+#errors
+Line: 1 Col: 19 Expected a > after the /.
+Line: 1 Col: 21 Solidus (/) incorrectly placed in tag.
+Line: 1 Col: 23 Solidus (/) incorrectly placed in tag.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| "X"
+| <p>
+| x=""
+| y=""
+| z=""
+
+#data
+<!DOCTYPE html><!--x--
+#errors
+Line: 1 Col: 22 Unexpected end of file in comment (--).
+#document
+| <!DOCTYPE html>
+| <!-- x -->
+| <html>
+| <head>
+| <body>
+
+#data
+<!DOCTYPE html><table><tr><td></p></table>
+#errors
+Line: 1 Col: 34 Unexpected end tag (p). Ignored.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <p>
+
+#data
+<!DOCTYPE <!DOCTYPE HTML>><!--<!--x-->-->
+#errors
+Line: 1 Col: 20 Expected space or '>'. Got ''
+Line: 1 Col: 25 Erroneous DOCTYPE.
+Line: 1 Col: 35 Unexpected character in comment found.
+#document
+| <!DOCTYPE <!doctype>
+| <html>
+| <head>
+| <body>
+| ">"
+| <!-- <!--x -->
+| "-->"
+
+#data
+<!doctype html><div><form></form><div></div></div>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <div>
+| <form>
+| <div>
diff --git a/pkgs/html/test/data/tree-construction/tests20.dat b/pkgs/html/test/data/tree-construction/tests20.dat
new file mode 100644
index 0000000..6bd8256
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/tests20.dat
@@ -0,0 +1,455 @@
+#data
+<!doctype html><p><button><button>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <button>
+| <button>
+
+#data
+<!doctype html><p><button><address>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <button>
+| <address>
+
+#data
+<!doctype html><p><button><blockquote>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <button>
+| <blockquote>
+
+#data
+<!doctype html><p><button><menu>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <button>
+| <menu>
+
+#data
+<!doctype html><p><button><p>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <button>
+| <p>
+
+#data
+<!doctype html><p><button><ul>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <button>
+| <ul>
+
+#data
+<!doctype html><p><button><h1>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <button>
+| <h1>
+
+#data
+<!doctype html><p><button><h6>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <button>
+| <h6>
+
+#data
+<!doctype html><p><button><listing>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <button>
+| <listing>
+
+#data
+<!doctype html><p><button><pre>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <button>
+| <pre>
+
+#data
+<!doctype html><p><button><form>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <button>
+| <form>
+
+#data
+<!doctype html><p><button><li>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <button>
+| <li>
+
+#data
+<!doctype html><p><button><dd>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <button>
+| <dd>
+
+#data
+<!doctype html><p><button><dt>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <button>
+| <dt>
+
+#data
+<!doctype html><p><button><plaintext>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <button>
+| <plaintext>
+
+#data
+<!doctype html><p><button><table>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <button>
+| <table>
+
+#data
+<!doctype html><p><button><hr>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <button>
+| <hr>
+
+#data
+<!doctype html><p><button><xmp>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <button>
+| <xmp>
+
+#data
+<!doctype html><p><button></p>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <button>
+| <p>
+
+#data
+<!doctype html><address><button></address>a
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <address>
+| <button>
+| "a"
+
+#data
+<!doctype html><address><button></address>a
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <address>
+| <button>
+| "a"
+
+#data
+<p><table></p>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <p>
+| <p>
+| <table>
+
+#data
+<!doctype html><svg>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <svg svg>
+
+#data
+<!doctype html><p><figcaption>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <figcaption>
+
+#data
+<!doctype html><p><summary>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <summary>
+
+#data
+<!doctype html><form><table><form>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <form>
+| <table>
+
+#data
+<!doctype html><table><form><form>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <form>
+
+#data
+<!doctype html><table><form></table><form>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <form>
+
+#data
+<!doctype html><svg><foreignObject><p>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| <svg foreignObject>
+| <p>
+
+#data
+<!doctype html><svg><title>abc
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| <svg title>
+| "abc"
+
+#data
+<option><span><option>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <option>
+| <span>
+| <option>
+
+#data
+<option><option>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <option>
+| <option>
+
+#data
+<math><annotation-xml><div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math annotation-xml>
+| <div>
+
+#data
+<math><annotation-xml encoding="application/svg+xml"><div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math annotation-xml>
+| encoding="application/svg+xml"
+| <div>
+
+#data
+<math><annotation-xml encoding="application/xhtml+xml"><div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math annotation-xml>
+| encoding="application/xhtml+xml"
+| <div>
+
+#data
+<math><annotation-xml encoding="aPPlication/xhtmL+xMl"><div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math annotation-xml>
+| encoding="aPPlication/xhtmL+xMl"
+| <div>
+
+#data
+<math><annotation-xml encoding="text/html"><div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math annotation-xml>
+| encoding="text/html"
+| <div>
+
+#data
+<math><annotation-xml encoding="Text/htmL"><div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math annotation-xml>
+| encoding="Text/htmL"
+| <div>
+
+#data
+<math><annotation-xml encoding=" text/html "><div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math annotation-xml>
+| encoding=" text/html "
+| <div>
diff --git a/pkgs/html/test/data/tree-construction/tests21.dat b/pkgs/html/test/data/tree-construction/tests21.dat
new file mode 100644
index 0000000..1260ec0
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/tests21.dat
@@ -0,0 +1,221 @@
+#data
+<svg><![CDATA[foo]]>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| "foo"
+
+#data
+<math><![CDATA[foo]]>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <math math>
+| "foo"
+
+#data
+<div><![CDATA[foo]]>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| <!-- [CDATA[foo]] -->
+
+#data
+<svg><![CDATA[foo
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| "foo"
+
+#data
+<svg><![CDATA[foo
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| "foo"
+
+#data
+<svg><![CDATA[
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <svg svg>
+
+#data
+<svg><![CDATA[]]>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <svg svg>
+
+#data
+<svg><![CDATA[]] >]]>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| "]] >"
+
+#data
+<svg><![CDATA[]] >]]>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| "]] >"
+
+#data
+<svg><![CDATA[]]
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| "]]"
+
+#data
+<svg><![CDATA[]
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| "]"
+
+#data
+<svg><![CDATA[]>a
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| "]>a"
+
+#data
+<svg><foreignObject><div><![CDATA[foo]]>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| <svg foreignObject>
+| <div>
+| <!-- [CDATA[foo]] -->
+
+#data
+<svg><![CDATA[<svg>]]>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| "<svg>"
+
+#data
+<svg><![CDATA[</svg>a]]>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| "</svg>a"
+
+#data
+<svg><![CDATA[<svg>a
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| "<svg>a"
+
+#data
+<svg><![CDATA[</svg>a
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| "</svg>a"
+
+#data
+<svg><![CDATA[<svg>]]><path>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| "<svg>"
+| <svg path>
+
+#data
+<svg><![CDATA[<svg>]]></path>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| "<svg>"
+
+#data
+<svg><![CDATA[<svg>]]><!--path-->
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| "<svg>"
+| <!-- path -->
+
+#data
+<svg><![CDATA[<svg>]]>path
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| "<svg>path"
+
+#data
+<svg><![CDATA[<!--svg-->]]>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| "<!--svg-->"
diff --git a/pkgs/html/test/data/tree-construction/tests22.dat b/pkgs/html/test/data/tree-construction/tests22.dat
new file mode 100644
index 0000000..aab27b2
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/tests22.dat
@@ -0,0 +1,157 @@
+#data
+<a><b><big><em><strong><div>X</a>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <a>
+| <b>
+| <big>
+| <em>
+| <strong>
+| <big>
+| <em>
+| <strong>
+| <div>
+| <a>
+| "X"
+
+#data
+<a><b><div id=1><div id=2><div id=3><div id=4><div id=5><div id=6><div id=7><div id=8>A</a>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <a>
+| <b>
+| <b>
+| <div>
+| id="1"
+| <a>
+| <div>
+| id="2"
+| <a>
+| <div>
+| id="3"
+| <a>
+| <div>
+| id="4"
+| <a>
+| <div>
+| id="5"
+| <a>
+| <div>
+| id="6"
+| <a>
+| <div>
+| id="7"
+| <a>
+| <div>
+| id="8"
+| <a>
+| "A"
+
+#data
+<a><b><div id=1><div id=2><div id=3><div id=4><div id=5><div id=6><div id=7><div id=8><div id=9>A</a>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <a>
+| <b>
+| <b>
+| <div>
+| id="1"
+| <a>
+| <div>
+| id="2"
+| <a>
+| <div>
+| id="3"
+| <a>
+| <div>
+| id="4"
+| <a>
+| <div>
+| id="5"
+| <a>
+| <div>
+| id="6"
+| <a>
+| <div>
+| id="7"
+| <a>
+| <div>
+| id="8"
+| <a>
+| <div>
+| id="9"
+| "A"
+
+#data
+<a><b><div id=1><div id=2><div id=3><div id=4><div id=5><div id=6><div id=7><div id=8><div id=9><div id=10>A</a>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <a>
+| <b>
+| <b>
+| <div>
+| id="1"
+| <a>
+| <div>
+| id="2"
+| <a>
+| <div>
+| id="3"
+| <a>
+| <div>
+| id="4"
+| <a>
+| <div>
+| id="5"
+| <a>
+| <div>
+| id="6"
+| <a>
+| <div>
+| id="7"
+| <a>
+| <div>
+| id="8"
+| <a>
+| <div>
+| id="9"
+| <div>
+| id="10"
+| "A"
+
+#data
+<cite><b><cite><i><cite><i><cite><i><div>X</b>TEST
+#errors
+Line: 1 Col: 6 Unexpected start tag (cite). Expected DOCTYPE.
+Line: 1 Col: 46 End tag (b) violates step 1, paragraph 3 of the adoption agency algorithm.
+Line: 1 Col: 50 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <cite>
+| <b>
+| <cite>
+| <i>
+| <cite>
+| <i>
+| <cite>
+| <i>
+| <i>
+| <i>
+| <div>
+| <b>
+| "X"
+| "TEST"
diff --git a/pkgs/html/test/data/tree-construction/tests23.dat b/pkgs/html/test/data/tree-construction/tests23.dat
new file mode 100644
index 0000000..34d2a73
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/tests23.dat
@@ -0,0 +1,155 @@
+#data
+<p><font size=4><font color=red><font size=4><font size=4><font size=4><font size=4><font size=4><font color=red><p>X
+#errors
+3: Start tag seen without seeing a doctype first. Expected “<!DOCTYPE html>”.
+116: Unclosed elements.
+117: End of file seen and there were open elements.
+#document
+| <html>
+| <head>
+| <body>
+| <p>
+| <font>
+| size="4"
+| <font>
+| color="red"
+| <font>
+| size="4"
+| <font>
+| size="4"
+| <font>
+| size="4"
+| <font>
+| size="4"
+| <font>
+| size="4"
+| <font>
+| color="red"
+| <p>
+| <font>
+| color="red"
+| <font>
+| size="4"
+| <font>
+| size="4"
+| <font>
+| size="4"
+| <font>
+| color="red"
+| "X"
+
+#data
+<p><font size=4><font size=4><font size=4><font size=4><p>X
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <p>
+| <font>
+| size="4"
+| <font>
+| size="4"
+| <font>
+| size="4"
+| <font>
+| size="4"
+| <p>
+| <font>
+| size="4"
+| <font>
+| size="4"
+| <font>
+| size="4"
+| "X"
+
+#data
+<p><font size=4><font size=4><font size=4><font size="5"><font size=4><p>X
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <p>
+| <font>
+| size="4"
+| <font>
+| size="4"
+| <font>
+| size="4"
+| <font>
+| size="5"
+| <font>
+| size="4"
+| <p>
+| <font>
+| size="4"
+| <font>
+| size="4"
+| <font>
+| size="5"
+| <font>
+| size="4"
+| "X"
+
+#data
+<p><font size=4 id=a><font size=4 id=b><font size=4><font size=4><p>X
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <p>
+| <font>
+| id="a"
+| size="4"
+| <font>
+| id="b"
+| size="4"
+| <font>
+| size="4"
+| <font>
+| size="4"
+| <p>
+| <font>
+| id="a"
+| size="4"
+| <font>
+| id="b"
+| size="4"
+| <font>
+| size="4"
+| <font>
+| size="4"
+| "X"
+
+#data
+<p><b id=a><b id=a><b id=a><b><object><b id=a><b id=a>X</object><p>Y
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <p>
+| <b>
+| id="a"
+| <b>
+| id="a"
+| <b>
+| id="a"
+| <b>
+| <object>
+| <b>
+| id="a"
+| <b>
+| id="a"
+| "X"
+| <p>
+| <b>
+| id="a"
+| <b>
+| id="a"
+| <b>
+| id="a"
+| <b>
+| "Y"
diff --git a/pkgs/html/test/data/tree-construction/tests24.dat b/pkgs/html/test/data/tree-construction/tests24.dat
new file mode 100644
index 0000000..f6dc7eb
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/tests24.dat
@@ -0,0 +1,79 @@
+#data
+<!DOCTYPE html>≂̸
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| "≂̸"
+
+#data
+<!DOCTYPE html>≂̸A
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| "≂̸A"
+
+#data
+<!DOCTYPE html>  
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| " "
+
+#data
+<!DOCTYPE html>  A
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| " A"
+
+#data
+<!DOCTYPE html>⊂⃒
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| "⊂⃒"
+
+#data
+<!DOCTYPE html>⊂⃒A
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| "⊂⃒A"
+
+#data
+<!DOCTYPE html>𝔾
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| "𝔾"
+
+#data
+<!DOCTYPE html>𝔾A
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| "𝔾A"
diff --git a/pkgs/html/test/data/tree-construction/tests25.dat b/pkgs/html/test/data/tree-construction/tests25.dat
new file mode 100644
index 0000000..00de729
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/tests25.dat
@@ -0,0 +1,219 @@
+#data
+<!DOCTYPE html><body><foo>A
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <foo>
+| "A"
+
+#data
+<!DOCTYPE html><body><area>A
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <area>
+| "A"
+
+#data
+<!DOCTYPE html><body><base>A
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <base>
+| "A"
+
+#data
+<!DOCTYPE html><body><basefont>A
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <basefont>
+| "A"
+
+#data
+<!DOCTYPE html><body><bgsound>A
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <bgsound>
+| "A"
+
+#data
+<!DOCTYPE html><body><br>A
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <br>
+| "A"
+
+#data
+<!DOCTYPE html><body><col>A
+#errors
+26: Stray start tag “col”.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| "A"
+
+#data
+<!DOCTYPE html><body><command>A
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <command>
+| "A"
+
+#data
+<!DOCTYPE html><body><embed>A
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <embed>
+| "A"
+
+#data
+<!DOCTYPE html><body><frame>A
+#errors
+26: Stray start tag “frame”.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| "A"
+
+#data
+<!DOCTYPE html><body><hr>A
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <hr>
+| "A"
+
+#data
+<!DOCTYPE html><body><img>A
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <img>
+| "A"
+
+#data
+<!DOCTYPE html><body><input>A
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <input>
+| "A"
+
+#data
+<!DOCTYPE html><body><keygen>A
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <keygen>
+| "A"
+
+#data
+<!DOCTYPE html><body><link>A
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <link>
+| "A"
+
+#data
+<!DOCTYPE html><body><meta>A
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <meta>
+| "A"
+
+#data
+<!DOCTYPE html><body><param>A
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <param>
+| "A"
+
+#data
+<!DOCTYPE html><body><source>A
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <source>
+| "A"
+
+#data
+<!DOCTYPE html><body><track>A
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <track>
+| "A"
+
+#data
+<!DOCTYPE html><body><wbr>A
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <wbr>
+| "A"
diff --git a/pkgs/html/test/data/tree-construction/tests26.dat b/pkgs/html/test/data/tree-construction/tests26.dat
new file mode 100644
index 0000000..fae11ff
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/tests26.dat
@@ -0,0 +1,313 @@
+#data
+<!DOCTYPE html><body><a href='#1'><nobr>1<nobr></a><br><a href='#2'><nobr>2<nobr></a><br><a href='#3'><nobr>3<nobr></a>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <a>
+| href="#1"
+| <nobr>
+| "1"
+| <nobr>
+| <nobr>
+| <br>
+| <a>
+| href="#2"
+| <a>
+| href="#2"
+| <nobr>
+| "2"
+| <nobr>
+| <nobr>
+| <br>
+| <a>
+| href="#3"
+| <a>
+| href="#3"
+| <nobr>
+| "3"
+| <nobr>
+
+#data
+<!DOCTYPE html><body><b><nobr>1<nobr></b><i><nobr>2<nobr></i>3
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <b>
+| <nobr>
+| "1"
+| <nobr>
+| <nobr>
+| <i>
+| <i>
+| <nobr>
+| "2"
+| <nobr>
+| <nobr>
+| "3"
+
+#data
+<!DOCTYPE html><body><b><nobr>1<table><nobr></b><i><nobr>2<nobr></i>3
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <b>
+| <nobr>
+| "1"
+| <nobr>
+| <i>
+| <i>
+| <nobr>
+| "2"
+| <nobr>
+| <nobr>
+| "3"
+| <table>
+
+#data
+<!DOCTYPE html><body><b><nobr>1<table><tr><td><nobr></b><i><nobr>2<nobr></i>3
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <b>
+| <nobr>
+| "1"
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <nobr>
+| <i>
+| <i>
+| <nobr>
+| "2"
+| <nobr>
+| <nobr>
+| "3"
+
+#data
+<!DOCTYPE html><body><b><nobr>1<div><nobr></b><i><nobr>2<nobr></i>3
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <b>
+| <nobr>
+| "1"
+| <div>
+| <b>
+| <nobr>
+| <nobr>
+| <nobr>
+| <i>
+| <i>
+| <nobr>
+| "2"
+| <nobr>
+| <nobr>
+| "3"
+
+#data
+<!DOCTYPE html><body><b><nobr>1<nobr></b><div><i><nobr>2<nobr></i>3
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <b>
+| <nobr>
+| "1"
+| <nobr>
+| <div>
+| <nobr>
+| <i>
+| <i>
+| <nobr>
+| "2"
+| <nobr>
+| <nobr>
+| "3"
+
+#data
+<!DOCTYPE html><body><b><nobr>1<nobr><ins></b><i><nobr>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <b>
+| <nobr>
+| "1"
+| <nobr>
+| <ins>
+| <nobr>
+| <i>
+| <i>
+| <nobr>
+
+#data
+<!DOCTYPE html><body><b><nobr>1<ins><nobr></b><i>2
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <b>
+| <nobr>
+| "1"
+| <ins>
+| <nobr>
+| <nobr>
+| <i>
+| "2"
+
+#data
+<!DOCTYPE html><body><b>1<nobr></b><i><nobr>2</i>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <b>
+| "1"
+| <nobr>
+| <nobr>
+| <i>
+| <i>
+| <nobr>
+| "2"
+
+#data
+<p><code x</code></p>
+
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <p>
+| <code>
+| code=""
+| x<=""
+| <code>
+| code=""
+| x<=""
+| "
+"
+
+#data
+<!DOCTYPE html><svg><foreignObject><p><i></p>a
+#errors
+45: End tag “p” seen, but there were open elements.
+41: Unclosed element “i”.
+46: End of file seen and there were open elements.
+35: Unclosed element “foreignObject”.
+20: Unclosed element “svg”.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| <svg foreignObject>
+| <p>
+| <i>
+| <i>
+| "a"
+
+#data
+<!DOCTYPE html><table><tr><td><svg><foreignObject><p><i></p>a
+#errors
+56: End tag “p” seen, but there were open elements.
+52: Unclosed element “i”.
+57: End of file seen and there were open elements.
+46: Unclosed element “foreignObject”.
+31: Unclosed element “svg”.
+22: Unclosed element “table”.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <svg svg>
+| <svg foreignObject>
+| <p>
+| <i>
+| <i>
+| "a"
+
+#data
+<!DOCTYPE html><math><mtext><p><i></p>a
+#errors
+38: End tag “p” seen, but there were open elements.
+34: Unclosed element “i”.
+39: End of file in a foreign namespace context.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math mtext>
+| <p>
+| <i>
+| <i>
+| "a"
+
+#data
+<!DOCTYPE html><table><tr><td><math><mtext><p><i></p>a
+#errors
+53: End tag “p” seen, but there were open elements.
+49: Unclosed element “i”.
+54: End of file in a foreign namespace context.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <math math>
+| <math mtext>
+| <p>
+| <i>
+| <i>
+| "a"
+
+#data
+<!DOCTYPE html><body><div><!/div>a
+#errors
+29: Bogus comment.
+34: End of file seen and there were open elements.
+26: Unclosed element “div”.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <div>
+| <!-- /div -->
+| "a"
diff --git a/pkgs/html/test/data/tree-construction/tests3.dat b/pkgs/html/test/data/tree-construction/tests3.dat
new file mode 100644
index 0000000..38dc501
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/tests3.dat
@@ -0,0 +1,305 @@
+#data
+<head></head><style></style>
+#errors
+Line: 1 Col: 6 Unexpected start tag (head). Expected DOCTYPE.
+Line: 1 Col: 20 Unexpected start tag (style) that can be in head. Moved.
+#document
+| <html>
+| <head>
+| <style>
+| <body>
+
+#data
+<head></head><script></script>
+#errors
+Line: 1 Col: 6 Unexpected start tag (head). Expected DOCTYPE.
+Line: 1 Col: 21 Unexpected start tag (script) that can be in head. Moved.
+#document
+| <html>
+| <head>
+| <script>
+| <body>
+
+#data
+<head></head><!-- --><style></style><!-- --><script></script>
+#errors
+Line: 1 Col: 6 Unexpected start tag (head). Expected DOCTYPE.
+Line: 1 Col: 28 Unexpected start tag (style) that can be in head. Moved.
+#document
+| <html>
+| <head>
+| <style>
+| <script>
+| <!-- -->
+| <!-- -->
+| <body>
+
+#data
+<head></head><!-- -->x<style></style><!-- --><script></script>
+#errors
+Line: 1 Col: 6 Unexpected start tag (head). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <!-- -->
+| <body>
+| "x"
+| <style>
+| <!-- -->
+| <script>
+
+#data
+<!DOCTYPE html><html><head></head><body><pre>
+</pre></body></html>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <pre>
+
+#data
+<!DOCTYPE html><html><head></head><body><pre>
+foo</pre></body></html>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <pre>
+| "foo"
+
+#data
+<!DOCTYPE html><html><head></head><body><pre>
+
+foo</pre></body></html>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <pre>
+| "
+foo"
+
+#data
+<!DOCTYPE html><html><head></head><body><pre>
+foo
+</pre></body></html>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <pre>
+| "foo
+"
+
+#data
+<!DOCTYPE html><html><head></head><body><pre>x</pre><span>
+</span></body></html>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <pre>
+| "x"
+| <span>
+| "
+"
+
+#data
+<!DOCTYPE html><html><head></head><body><pre>x
+y</pre></body></html>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <pre>
+| "x
+y"
+
+#data
+<!DOCTYPE html><html><head></head><body><pre>x<div>
+y</pre></body></html>
+#errors
+Line: 2 Col: 7 End tag (pre) seen too early. Expected other end tag.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <pre>
+| "x"
+| <div>
+| "
+y"
+
+#data
+<!DOCTYPE html><pre>

A</pre>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <pre>
+| "
+A"
+
+#data
+<!DOCTYPE html><HTML><META><HEAD></HEAD></HTML>
+#errors
+Line: 1 Col: 33 Unexpected start tag head in existing head. Ignored.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <meta>
+| <body>
+
+#data
+<!DOCTYPE html><HTML><HEAD><head></HEAD></HTML>
+#errors
+Line: 1 Col: 33 Unexpected start tag head in existing head. Ignored.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+
+#data
+<textarea>foo<span>bar</span><i>baz
+#errors
+Line: 1 Col: 10 Unexpected start tag (textarea). Expected DOCTYPE.
+Line: 1 Col: 35 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <textarea>
+| "foo<span>bar</span><i>baz"
+
+#data
+<title>foo<span>bar</em><i>baz
+#errors
+Line: 1 Col: 7 Unexpected start tag (title). Expected DOCTYPE.
+Line: 1 Col: 30 Unexpected end of file. Expected end tag (title).
+#document
+| <html>
+| <head>
+| <title>
+| "foo<span>bar</em><i>baz"
+| <body>
+
+#data
+<!DOCTYPE html><textarea>
+</textarea>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <textarea>
+
+#data
+<!DOCTYPE html><textarea>
+foo</textarea>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <textarea>
+| "foo"
+
+#data
+<!DOCTYPE html><textarea>
+
+foo</textarea>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <textarea>
+| "
+foo"
+
+#data
+<!DOCTYPE html><html><head></head><body><ul><li><div><p><li></ul></body></html>
+#errors
+Line: 1 Col: 60 Missing end tag (div, li).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <ul>
+| <li>
+| <div>
+| <p>
+| <li>
+
+#data
+<!doctype html><nobr><nobr><nobr>
+#errors
+Line: 1 Col: 27 Unexpected start tag (nobr) implies end tag (nobr).
+Line: 1 Col: 33 Unexpected start tag (nobr) implies end tag (nobr).
+Line: 1 Col: 33 Expected closing tag. Unexpected end of file.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <nobr>
+| <nobr>
+| <nobr>
+
+#data
+<!doctype html><nobr><nobr></nobr><nobr>
+#errors
+Line: 1 Col: 27 Unexpected start tag (nobr) implies end tag (nobr).
+Line: 1 Col: 40 Expected closing tag. Unexpected end of file.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <nobr>
+| <nobr>
+| <nobr>
+
+#data
+<!doctype html><html><body><p><table></table></body></html>
+#errors
+Not known
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <table>
+
+#data
+<p><table></table>
+#errors
+Not known
+#document
+| <html>
+| <head>
+| <body>
+| <p>
+| <table>
diff --git a/pkgs/html/test/data/tree-construction/tests4.dat b/pkgs/html/test/data/tree-construction/tests4.dat
new file mode 100644
index 0000000..3c50632
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/tests4.dat
@@ -0,0 +1,59 @@
+#data
+direct div content
+#errors
+#document-fragment
+div
+#document
+| "direct div content"
+
+#data
+direct textarea content
+#errors
+#document-fragment
+textarea
+#document
+| "direct textarea content"
+
+#data
+textarea content with <em>pseudo</em> <foo>markup
+#errors
+#document-fragment
+textarea
+#document
+| "textarea content with <em>pseudo</em> <foo>markup"
+
+#data
+this is CDATA inside a <style> element
+#errors
+#document-fragment
+style
+#document
+| "this is CDATA inside a <style> element"
+
+#data
+</plaintext>
+#errors
+#document-fragment
+plaintext
+#document
+| "</plaintext>"
+
+#data
+setting html's innerHTML
+#errors
+Line: 1 Col: 24 Unexpected EOF in inner html mode.
+#document-fragment
+html
+#document
+| <head>
+| <body>
+| "setting html's innerHTML"
+
+#data
+<title>setting head's innerHTML</title>
+#errors
+#document-fragment
+head
+#document
+| <title>
+| "setting head's innerHTML"
diff --git a/pkgs/html/test/data/tree-construction/tests5.dat b/pkgs/html/test/data/tree-construction/tests5.dat
new file mode 100644
index 0000000..d7b5128
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/tests5.dat
@@ -0,0 +1,191 @@
+#data
+<style> <!-- </style>x
+#errors
+Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE.
+Line: 1 Col: 22 Unexpected end of file. Expected end tag (style).
+#document
+| <html>
+| <head>
+| <style>
+| " <!-- "
+| <body>
+| "x"
+
+#data
+<style> <!-- </style> --> </style>x
+#errors
+Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <style>
+| " <!-- "
+| " "
+| <body>
+| "--> x"
+
+#data
+<style> <!--> </style>x
+#errors
+Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <style>
+| " <!--> "
+| <body>
+| "x"
+
+#data
+<style> <!---> </style>x
+#errors
+Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <style>
+| " <!---> "
+| <body>
+| "x"
+
+#data
+<iframe> <!---> </iframe>x
+#errors
+Line: 1 Col: 8 Unexpected start tag (iframe). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+| <iframe>
+| " <!---> "
+| "x"
+
+#data
+<iframe> <!--- </iframe>->x</iframe> --> </iframe>x
+#errors
+Line: 1 Col: 8 Unexpected start tag (iframe). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+| <iframe>
+| " <!--- "
+| "->x --> x"
+
+#data
+<script> <!-- </script> --> </script>x
+#errors
+Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <script>
+| " <!-- "
+| " "
+| <body>
+| "--> x"
+
+#data
+<title> <!-- </title> --> </title>x
+#errors
+Line: 1 Col: 7 Unexpected start tag (title). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <title>
+| " <!-- "
+| " "
+| <body>
+| "--> x"
+
+#data
+<textarea> <!--- </textarea>->x</textarea> --> </textarea>x
+#errors
+Line: 1 Col: 10 Unexpected start tag (textarea). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+| <textarea>
+| " <!--- "
+| "->x --> x"
+
+#data
+<style> <!</-- </style>x
+#errors
+Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <style>
+| " <!</-- "
+| <body>
+| "x"
+
+#data
+<p><xmp></xmp>
+#errors
+XXX: Unknown
+#document
+| <html>
+| <head>
+| <body>
+| <p>
+| <xmp>
+
+#data
+<xmp> <!-- > --> </xmp>
+#errors
+Line: 1 Col: 5 Unexpected start tag (xmp). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+| <xmp>
+| " <!-- > --> "
+
+#data
+<title>&</title>
+#errors
+Line: 1 Col: 7 Unexpected start tag (title). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <title>
+| "&"
+| <body>
+
+#data
+<title><!--&--></title>
+#errors
+Line: 1 Col: 7 Unexpected start tag (title). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <title>
+| "<!--&-->"
+| <body>
+
+#data
+<title><!--</title>
+#errors
+Line: 1 Col: 7 Unexpected start tag (title). Expected DOCTYPE.
+Line: 1 Col: 19 Unexpected end of file. Expected end tag (title).
+#document
+| <html>
+| <head>
+| <title>
+| "<!--"
+| <body>
+
+#data
+<noscript><!--</noscript>--></noscript>
+#errors
+Line: 1 Col: 10 Unexpected start tag (noscript). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <noscript>
+| "<!--"
+| <body>
+| "-->"
diff --git a/pkgs/html/test/data/tree-construction/tests6.dat b/pkgs/html/test/data/tree-construction/tests6.dat
new file mode 100644
index 0000000..f28ece4
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/tests6.dat
@@ -0,0 +1,663 @@
+#data
+<!doctype html></head> <head>
+#errors
+Line: 1 Col: 29 Unexpected start tag head. Ignored.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| " "
+| <body>
+
+#data
+<!doctype html><form><div></form><div>
+#errors
+33: End tag "form" seen but there were unclosed elements.
+38: End of file seen and there were open elements.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <form>
+| <div>
+| <div>
+
+#data
+<!doctype html><title>&</title>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <title>
+| "&"
+| <body>
+
+#data
+<!doctype html><title><!--&--></title>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <title>
+| "<!--&-->"
+| <body>
+
+#data
+<!doctype>
+#errors
+Line: 1 Col: 9 No space after literal string 'DOCTYPE'.
+Line: 1 Col: 10 Unexpected > character. Expected DOCTYPE name.
+Line: 1 Col: 10 Erroneous DOCTYPE.
+#document
+| <!DOCTYPE >
+| <html>
+| <head>
+| <body>
+
+#data
+<!---x
+#errors
+Line: 1 Col: 6 Unexpected end of file in comment.
+Line: 1 Col: 6 Unexpected End of file. Expected DOCTYPE.
+#document
+| <!-- -x -->
+| <html>
+| <head>
+| <body>
+
+#data
+<body>
+<div>
+#errors
+Line: 1 Col: 6 Unexpected start tag (body).
+Line: 2 Col: 5 Expected closing tag. Unexpected end of file.
+#document-fragment
+div
+#document
+| "
+"
+| <div>
+
+#data
+<frameset></frameset>
+foo
+#errors
+Line: 1 Col: 10 Unexpected start tag (frameset). Expected DOCTYPE.
+Line: 2 Col: 3 Unexpected non-space characters in the after frameset phase. Ignored.
+#document
+| <html>
+| <head>
+| <frameset>
+| "
+"
+
+#data
+<frameset></frameset>
+<noframes>
+#errors
+Line: 1 Col: 10 Unexpected start tag (frameset). Expected DOCTYPE.
+Line: 2 Col: 10 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <frameset>
+| "
+"
+| <noframes>
+
+#data
+<frameset></frameset>
+<div>
+#errors
+Line: 1 Col: 10 Unexpected start tag (frameset). Expected DOCTYPE.
+Line: 2 Col: 5 Unexpected start tag (div) in the after frameset phase. Ignored.
+#document
+| <html>
+| <head>
+| <frameset>
+| "
+"
+
+#data
+<frameset></frameset>
+</html>
+#errors
+Line: 1 Col: 10 Unexpected start tag (frameset). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <frameset>
+| "
+"
+
+#data
+<frameset></frameset>
+</div>
+#errors
+Line: 1 Col: 10 Unexpected start tag (frameset). Expected DOCTYPE.
+Line: 2 Col: 6 Unexpected end tag (div) in the after frameset phase. Ignored.
+#document
+| <html>
+| <head>
+| <frameset>
+| "
+"
+
+#data
+<form><form>
+#errors
+Line: 1 Col: 6 Unexpected start tag (form). Expected DOCTYPE.
+Line: 1 Col: 12 Unexpected start tag (form).
+Line: 1 Col: 12 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <form>
+
+#data
+<button><button>
+#errors
+Line: 1 Col: 8 Unexpected start tag (button). Expected DOCTYPE.
+Line: 1 Col: 16 Unexpected start tag (button) implies end tag (button).
+Line: 1 Col: 16 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <button>
+| <button>
+
+#data
+<table><tr><td></th>
+#errors
+Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE.
+Line: 1 Col: 20 Unexpected end tag (th). Ignored.
+Line: 1 Col: 20 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+
+#data
+<table><caption><td>
+#errors
+Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE.
+Line: 1 Col: 20 Unexpected end tag (td). Ignored.
+Line: 1 Col: 20 Unexpected table cell start tag (td) in the table body phase.
+Line: 1 Col: 20 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <caption>
+| <tbody>
+| <tr>
+| <td>
+
+#data
+<table><caption><div>
+#errors
+Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE.
+Line: 1 Col: 21 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <caption>
+| <div>
+
+#data
+</caption><div>
+#errors
+Line: 1 Col: 10 Unexpected end tag (caption). Ignored.
+Line: 1 Col: 15 Expected closing tag. Unexpected end of file.
+#document-fragment
+caption
+#document
+| <div>
+
+#data
+<table><caption><div></caption>
+#errors
+Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE.
+Line: 1 Col: 31 Unexpected end tag (caption). Missing end tag (div).
+Line: 1 Col: 31 Unexpected end of file. Expected table content.
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <caption>
+| <div>
+
+#data
+<table><caption></table>
+#errors
+Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE.
+Line: 1 Col: 24 Unexpected end table tag in caption. Generates implied end caption.
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <caption>
+
+#data
+</table><div>
+#errors
+Line: 1 Col: 8 Unexpected end table tag in caption. Generates implied end caption.
+Line: 1 Col: 8 Unexpected end tag (caption). Ignored.
+Line: 1 Col: 13 Expected closing tag. Unexpected end of file.
+#document-fragment
+caption
+#document
+| <div>
+
+#data
+<table><caption></body></col></colgroup></html></tbody></td></tfoot></th></thead></tr>
+#errors
+Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE.
+Line: 1 Col: 23 Unexpected end tag (body). Ignored.
+Line: 1 Col: 29 Unexpected end tag (col). Ignored.
+Line: 1 Col: 40 Unexpected end tag (colgroup). Ignored.
+Line: 1 Col: 47 Unexpected end tag (html). Ignored.
+Line: 1 Col: 55 Unexpected end tag (tbody). Ignored.
+Line: 1 Col: 60 Unexpected end tag (td). Ignored.
+Line: 1 Col: 68 Unexpected end tag (tfoot). Ignored.
+Line: 1 Col: 73 Unexpected end tag (th). Ignored.
+Line: 1 Col: 81 Unexpected end tag (thead). Ignored.
+Line: 1 Col: 86 Unexpected end tag (tr). Ignored.
+Line: 1 Col: 86 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <caption>
+
+#data
+<table><caption><div></div>
+#errors
+Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE.
+Line: 1 Col: 27 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <caption>
+| <div>
+
+#data
+<table><tr><td></body></caption></col></colgroup></html>
+#errors
+Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE.
+Line: 1 Col: 22 Unexpected end tag (body). Ignored.
+Line: 1 Col: 32 Unexpected end tag (caption). Ignored.
+Line: 1 Col: 38 Unexpected end tag (col). Ignored.
+Line: 1 Col: 49 Unexpected end tag (colgroup). Ignored.
+Line: 1 Col: 56 Unexpected end tag (html). Ignored.
+Line: 1 Col: 56 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+
+#data
+</table></tbody></tfoot></thead></tr><div>
+#errors
+Line: 1 Col: 8 Unexpected end tag (table). Ignored.
+Line: 1 Col: 16 Unexpected end tag (tbody). Ignored.
+Line: 1 Col: 24 Unexpected end tag (tfoot). Ignored.
+Line: 1 Col: 32 Unexpected end tag (thead). Ignored.
+Line: 1 Col: 37 Unexpected end tag (tr). Ignored.
+Line: 1 Col: 42 Expected closing tag. Unexpected end of file.
+#document-fragment
+td
+#document
+| <div>
+
+#data
+<table><colgroup>foo
+#errors
+Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE.
+Line: 1 Col: 20 Unexpected non-space characters in table context caused voodoo mode.
+Line: 1 Col: 20 Unexpected end of file. Expected table content.
+#document
+| <html>
+| <head>
+| <body>
+| "foo"
+| <table>
+| <colgroup>
+
+#data
+foo<col>
+#errors
+Line: 1 Col: 3 Unexpected end tag (colgroup). Ignored.
+#document-fragment
+colgroup
+#document
+| <col>
+
+#data
+<table><colgroup></col>
+#errors
+Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE.
+Line: 1 Col: 23 This element (col) has no end tag.
+Line: 1 Col: 23 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <colgroup>
+
+#data
+<frameset><div>
+#errors
+Line: 1 Col: 10 Unexpected start tag (frameset). Expected DOCTYPE.
+Line: 1 Col: 15 Unexpected start tag token (div) in the frameset phase. Ignored.
+Line: 1 Col: 15 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <frameset>
+
+#data
+</frameset><frame>
+#errors
+Line: 1 Col: 11 Unexpected end tag token (frameset) in the frameset phase (innerHTML).
+#document-fragment
+frameset
+#document
+| <frame>
+
+#data
+<frameset></div>
+#errors
+Line: 1 Col: 10 Unexpected start tag (frameset). Expected DOCTYPE.
+Line: 1 Col: 16 Unexpected end tag token (div) in the frameset phase. Ignored.
+Line: 1 Col: 16 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <frameset>
+
+#data
+</body><div>
+#errors
+Line: 1 Col: 7 Unexpected end tag (body). Ignored.
+Line: 1 Col: 12 Expected closing tag. Unexpected end of file.
+#document-fragment
+body
+#document
+| <div>
+
+#data
+<table><tr><div>
+#errors
+Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE.
+Line: 1 Col: 16 Unexpected start tag (div) in table context caused voodoo mode.
+Line: 1 Col: 16 Unexpected end of file. Expected table content.
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| <table>
+| <tbody>
+| <tr>
+
+#data
+</tr><td>
+#errors
+Line: 1 Col: 5 Unexpected end tag (tr). Ignored.
+#document-fragment
+tr
+#document
+| <td>
+
+#data
+</tbody></tfoot></thead><td>
+#errors
+Line: 1 Col: 8 Unexpected end tag (tbody). Ignored.
+Line: 1 Col: 16 Unexpected end tag (tfoot). Ignored.
+Line: 1 Col: 24 Unexpected end tag (thead). Ignored.
+#document-fragment
+tr
+#document
+| <td>
+
+#data
+<table><tr><div><td>
+#errors
+Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE.
+Line: 1 Col: 16 Unexpected start tag (div) in table context caused voodoo mode.
+Line: 1 Col: 20 Unexpected implied end tag (div) in the table row phase.
+Line: 1 Col: 20 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+
+#data
+<caption><col><colgroup><tbody><tfoot><thead><tr>
+#errors
+Line: 1 Col: 9 Unexpected start tag (caption).
+Line: 1 Col: 14 Unexpected start tag (col).
+Line: 1 Col: 24 Unexpected start tag (colgroup).
+Line: 1 Col: 31 Unexpected start tag (tbody).
+Line: 1 Col: 38 Unexpected start tag (tfoot).
+Line: 1 Col: 45 Unexpected start tag (thead).
+Line: 1 Col: 49 Unexpected end of file. Expected table content.
+#document-fragment
+tbody
+#document
+| <tr>
+
+#data
+<table><tbody></thead>
+#errors
+Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE.
+Line: 1 Col: 22 Unexpected end tag (thead) in the table body phase. Ignored.
+Line: 1 Col: 22 Unexpected end of file. Expected table content.
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+
+#data
+</table><tr>
+#errors
+Line: 1 Col: 8 Unexpected end tag (table). Ignored.
+Line: 1 Col: 12 Unexpected end of file. Expected table content.
+#document-fragment
+tbody
+#document
+| <tr>
+
+#data
+<table><tbody></body></caption></col></colgroup></html></td></th></tr>
+#errors
+Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE.
+Line: 1 Col: 21 Unexpected end tag (body) in the table body phase. Ignored.
+Line: 1 Col: 31 Unexpected end tag (caption) in the table body phase. Ignored.
+Line: 1 Col: 37 Unexpected end tag (col) in the table body phase. Ignored.
+Line: 1 Col: 48 Unexpected end tag (colgroup) in the table body phase. Ignored.
+Line: 1 Col: 55 Unexpected end tag (html) in the table body phase. Ignored.
+Line: 1 Col: 60 Unexpected end tag (td) in the table body phase. Ignored.
+Line: 1 Col: 65 Unexpected end tag (th) in the table body phase. Ignored.
+Line: 1 Col: 70 Unexpected end tag (tr) in the table body phase. Ignored.
+Line: 1 Col: 70 Unexpected end of file. Expected table content.
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+
+#data
+<table><tbody></div>
+#errors
+Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE.
+Line: 1 Col: 20 Unexpected end tag (div) in table context caused voodoo mode.
+Line: 1 Col: 20 End tag (div) seen too early. Expected other end tag.
+Line: 1 Col: 20 Unexpected end of file. Expected table content.
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+
+#data
+<table><table>
+#errors
+Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE.
+Line: 1 Col: 14 Unexpected start tag (table) implies end tag (table).
+Line: 1 Col: 14 Unexpected end of file. Expected table content.
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <table>
+
+#data
+<table></body></caption></col></colgroup></html></tbody></td></tfoot></th></thead></tr>
+#errors
+Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE.
+Line: 1 Col: 14 Unexpected end tag (body). Ignored.
+Line: 1 Col: 24 Unexpected end tag (caption). Ignored.
+Line: 1 Col: 30 Unexpected end tag (col). Ignored.
+Line: 1 Col: 41 Unexpected end tag (colgroup). Ignored.
+Line: 1 Col: 48 Unexpected end tag (html). Ignored.
+Line: 1 Col: 56 Unexpected end tag (tbody). Ignored.
+Line: 1 Col: 61 Unexpected end tag (td). Ignored.
+Line: 1 Col: 69 Unexpected end tag (tfoot). Ignored.
+Line: 1 Col: 74 Unexpected end tag (th). Ignored.
+Line: 1 Col: 82 Unexpected end tag (thead). Ignored.
+Line: 1 Col: 87 Unexpected end tag (tr). Ignored.
+Line: 1 Col: 87 Unexpected end of file. Expected table content.
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+
+#data
+</table><tr>
+#errors
+Line: 1 Col: 8 Unexpected end tag (table). Ignored.
+Line: 1 Col: 12 Unexpected end of file. Expected table content.
+#document-fragment
+table
+#document
+| <tbody>
+| <tr>
+
+#data
+<body></body></html>
+#errors
+Line: 1 Col: 20 Unexpected html end tag in inner html mode.
+Line: 1 Col: 20 Unexpected EOF in inner html mode.
+#document-fragment
+html
+#document
+| <head>
+| <body>
+
+#data
+<html><frameset></frameset></html>
+#errors
+Line: 1 Col: 6 Unexpected start tag (html). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <frameset>
+| " "
+
+#data
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"><html></html>
+#errors
+Line: 1 Col: 50 Erroneous DOCTYPE.
+Line: 1 Col: 63 Unexpected end tag (html) after the (implied) root element.
+#document
+| <!DOCTYPE html "-//W3C//DTD HTML 4.01//EN" "">
+| <html>
+| <head>
+| <body>
+
+#data
+<param><frameset></frameset>
+#errors
+Line: 1 Col: 7 Unexpected start tag (param). Expected DOCTYPE.
+Line: 1 Col: 17 Unexpected start tag (frameset).
+#document
+| <html>
+| <head>
+| <frameset>
+
+#data
+<source><frameset></frameset>
+#errors
+Line: 1 Col: 7 Unexpected start tag (source). Expected DOCTYPE.
+Line: 1 Col: 17 Unexpected start tag (frameset).
+#document
+| <html>
+| <head>
+| <frameset>
+
+#data
+<track><frameset></frameset>
+#errors
+Line: 1 Col: 7 Unexpected start tag (track). Expected DOCTYPE.
+Line: 1 Col: 17 Unexpected start tag (frameset).
+#document
+| <html>
+| <head>
+| <frameset>
+
+#data
+</html><frameset></frameset>
+#errors
+7: End tag seen without seeing a doctype first. Expected “<!DOCTYPE html>”.
+17: Stray “frameset” start tag.
+17: “frameset” start tag seen.
+#document
+| <html>
+| <head>
+| <frameset>
+
+#data
+</body><frameset></frameset>
+#errors
+7: End tag seen without seeing a doctype first. Expected “<!DOCTYPE html>”.
+17: Stray “frameset” start tag.
+17: “frameset” start tag seen.
+#document
+| <html>
+| <head>
+| <frameset>
diff --git a/pkgs/html/test/data/tree-construction/tests7.dat b/pkgs/html/test/data/tree-construction/tests7.dat
new file mode 100644
index 0000000..f5193c6
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/tests7.dat
@@ -0,0 +1,390 @@
+#data
+<!doctype html><body><title>X</title>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <title>
+| "X"
+
+#data
+<!doctype html><table><title>X</title></table>
+#errors
+Line: 1 Col: 29 Unexpected start tag (title) in table context caused voodoo mode.
+Line: 1 Col: 38 Unexpected end tag (title) in table context caused voodoo mode.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <title>
+| "X"
+| <table>
+
+#data
+<!doctype html><head></head><title>X</title>
+#errors
+Line: 1 Col: 35 Unexpected start tag (title) that can be in head. Moved.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <title>
+| "X"
+| <body>
+
+#data
+<!doctype html></head><title>X</title>
+#errors
+Line: 1 Col: 29 Unexpected start tag (title) that can be in head. Moved.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <title>
+| "X"
+| <body>
+
+#data
+<!doctype html><table><meta></table>
+#errors
+Line: 1 Col: 28 Unexpected start tag (meta) in table context caused voodoo mode.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <meta>
+| <table>
+
+#data
+<!doctype html><table>X<tr><td><table> <meta></table></table>
+#errors
+Line: 1 Col: 23 Unexpected non-space characters in table context caused voodoo mode.
+Line: 1 Col: 45 Unexpected start tag (meta) in table context caused voodoo mode.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| "X"
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <meta>
+| <table>
+| " "
+
+#data
+<!doctype html><html> <head>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+
+#data
+<!doctype html> <head>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+
+#data
+<!doctype html><table><style> <tr>x </style> </table>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <style>
+| " <tr>x "
+| " "
+
+#data
+<!doctype html><table><TBODY><script> <tr>x </script> </table>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <script>
+| " <tr>x "
+| " "
+
+#data
+<!doctype html><p><applet><p>X</p></applet>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <p>
+| <applet>
+| <p>
+| "X"
+
+#data
+<!doctype html><listing>
+X</listing>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <listing>
+| "X"
+
+#data
+<!doctype html><select><input>X
+#errors
+Line: 1 Col: 30 Unexpected input start tag in the select phase.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <select>
+| <input>
+| "X"
+
+#data
+<!doctype html><select><select>X
+#errors
+Line: 1 Col: 31 Unexpected select start tag in the select phase treated as select end tag.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <select>
+| "X"
+
+#data
+<!doctype html><table><input type=hidDEN></table>
+#errors
+Line: 1 Col: 41 Unexpected input with type hidden in table context.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <input>
+| type="hidDEN"
+
+#data
+<!doctype html><table>X<input type=hidDEN></table>
+#errors
+Line: 1 Col: 23 Unexpected non-space characters in table context caused voodoo mode.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| "X"
+| <table>
+| <input>
+| type="hidDEN"
+
+#data
+<!doctype html><table> <input type=hidDEN></table>
+#errors
+Line: 1 Col: 43 Unexpected input with type hidden in table context.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| " "
+| <input>
+| type="hidDEN"
+
+#data
+<!doctype html><table> <input type='hidDEN'></table>
+#errors
+Line: 1 Col: 45 Unexpected input with type hidden in table context.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| " "
+| <input>
+| type="hidDEN"
+
+#data
+<!doctype html><table><input type=" hidden"><input type=hidDEN></table>
+#errors
+Line: 1 Col: 44 Unexpected start tag (input) in table context caused voodoo mode.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <input>
+| type=" hidden"
+| <table>
+| <input>
+| type="hidDEN"
+
+#data
+<!doctype html><table><select>X<tr>
+#errors
+Line: 1 Col: 30 Unexpected start tag (select) in table context caused voodoo mode.
+Line: 1 Col: 35 Unexpected table element start tag (trs) in the select in table phase.
+Line: 1 Col: 35 Unexpected end of file. Expected table content.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <select>
+| "X"
+| <table>
+| <tbody>
+| <tr>
+
+#data
+<!doctype html><select>X</select>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <select>
+| "X"
+
+#data
+<!DOCTYPE hTmL><html></html>
+#errors
+Line: 1 Col: 28 Unexpected end tag (html) after the (implied) root element.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+
+#data
+<!DOCTYPE HTML><html></html>
+#errors
+Line: 1 Col: 28 Unexpected end tag (html) after the (implied) root element.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+
+#data
+<body>X</body></body>
+#errors
+Line: 1 Col: 21 Unexpected end tag token (body) in the after body phase.
+Line: 1 Col: 21 Unexpected EOF in inner html mode.
+#document-fragment
+html
+#document
+| <head>
+| <body>
+| "X"
+
+#data
+<div><p>a</x> b
+#errors
+Line: 1 Col: 5 Unexpected start tag (div). Expected DOCTYPE.
+Line: 1 Col: 13 Unexpected end tag (x). Ignored.
+Line: 1 Col: 15 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| <p>
+| "a b"
+
+#data
+<table><tr><td><code></code> </table>
+#errors
+Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <code>
+| " "
+
+#data
+<table><b><tr><td>aaa</td></tr>bbb</table>ccc
+#errors
+XXX: Fix me
+#document
+| <html>
+| <head>
+| <body>
+| <b>
+| <b>
+| "bbb"
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| "aaa"
+| <b>
+| "ccc"
+
+#data
+A<table><tr> B</tr> B</table>
+#errors
+XXX: Fix me
+#document
+| <html>
+| <head>
+| <body>
+| "A B B"
+| <table>
+| <tbody>
+| <tr>
+
+#data
+A<table><tr> B</tr> </em>C</table>
+#errors
+XXX: Fix me
+#document
+| <html>
+| <head>
+| <body>
+| "A BC"
+| <table>
+| <tbody>
+| <tr>
+| " "
+
+#data
+<select><keygen>
+#errors
+Not known
+#document
+| <html>
+| <head>
+| <body>
+| <select>
+| <keygen>
diff --git a/pkgs/html/test/data/tree-construction/tests8.dat b/pkgs/html/test/data/tree-construction/tests8.dat
new file mode 100644
index 0000000..90e6c91
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/tests8.dat
@@ -0,0 +1,148 @@
+#data
+<div>
+<div></div>
+</span>x
+#errors
+Line: 1 Col: 5 Unexpected start tag (div). Expected DOCTYPE.
+Line: 3 Col: 7 Unexpected end tag (span). Ignored.
+Line: 3 Col: 8 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| "
+"
+| <div>
+| "
+x"
+
+#data
+<div>x<div></div>
+</span>x
+#errors
+Line: 1 Col: 5 Unexpected start tag (div). Expected DOCTYPE.
+Line: 2 Col: 7 Unexpected end tag (span). Ignored.
+Line: 2 Col: 8 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| "x"
+| <div>
+| "
+x"
+
+#data
+<div>x<div></div>x</span>x
+#errors
+Line: 1 Col: 5 Unexpected start tag (div). Expected DOCTYPE.
+Line: 1 Col: 25 Unexpected end tag (span). Ignored.
+Line: 1 Col: 26 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| "x"
+| <div>
+| "xx"
+
+#data
+<div>x<div></div>y</span>z
+#errors
+Line: 1 Col: 5 Unexpected start tag (div). Expected DOCTYPE.
+Line: 1 Col: 25 Unexpected end tag (span). Ignored.
+Line: 1 Col: 26 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| "x"
+| <div>
+| "yz"
+
+#data
+<table><div>x<div></div>x</span>x
+#errors
+Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE.
+Line: 1 Col: 12 Unexpected start tag (div) in table context caused voodoo mode.
+Line: 1 Col: 18 Unexpected start tag (div) in table context caused voodoo mode.
+Line: 1 Col: 24 Unexpected end tag (div) in table context caused voodoo mode.
+Line: 1 Col: 32 Unexpected end tag (span) in table context caused voodoo mode.
+Line: 1 Col: 32 Unexpected end tag (span). Ignored.
+Line: 1 Col: 33 Unexpected end of file. Expected table content.
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| "x"
+| <div>
+| "xx"
+| <table>
+
+#data
+x<table>x
+#errors
+Line: 1 Col: 1 Unexpected non-space characters. Expected DOCTYPE.
+Line: 1 Col: 9 Unexpected non-space characters in table context caused voodoo mode.
+Line: 1 Col: 9 Unexpected end of file. Expected table content.
+#document
+| <html>
+| <head>
+| <body>
+| "xx"
+| <table>
+
+#data
+x<table><table>x
+#errors
+Line: 1 Col: 1 Unexpected non-space characters. Expected DOCTYPE.
+Line: 1 Col: 15 Unexpected start tag (table) implies end tag (table).
+Line: 1 Col: 16 Unexpected non-space characters in table context caused voodoo mode.
+Line: 1 Col: 16 Unexpected end of file. Expected table content.
+#document
+| <html>
+| <head>
+| <body>
+| "x"
+| <table>
+| "x"
+| <table>
+
+#data
+<b>a<div></div><div></b>y
+#errors
+Line: 1 Col: 3 Unexpected start tag (b). Expected DOCTYPE.
+Line: 1 Col: 24 End tag (b) violates step 1, paragraph 3 of the adoption agency algorithm.
+Line: 1 Col: 25 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <b>
+| "a"
+| <div>
+| <div>
+| <b>
+| "y"
+
+#data
+<a><div><p></a>
+#errors
+Line: 1 Col: 3 Unexpected start tag (a). Expected DOCTYPE.
+Line: 1 Col: 15 End tag (a) violates step 1, paragraph 3 of the adoption agency algorithm.
+Line: 1 Col: 15 End tag (a) violates step 1, paragraph 3 of the adoption agency algorithm.
+Line: 1 Col: 15 Expected closing tag. Unexpected end of file.
+#document
+| <html>
+| <head>
+| <body>
+| <a>
+| <div>
+| <a>
+| <p>
+| <a>
diff --git a/pkgs/html/test/data/tree-construction/tests9.dat b/pkgs/html/test/data/tree-construction/tests9.dat
new file mode 100644
index 0000000..554e27a
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/tests9.dat
@@ -0,0 +1,457 @@
+#data
+<!DOCTYPE html><math></math>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <math math>
+
+#data
+<!DOCTYPE html><body><math></math>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <math math>
+
+#data
+<!DOCTYPE html><math><mi>
+#errors
+25: End of file in a foreign namespace context.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math mi>
+
+#data
+<!DOCTYPE html><math><annotation-xml><svg><u>
+#errors
+45: HTML start tag “u” in a foreign namespace context.
+45: End of file seen and there were open elements.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math annotation-xml>
+| <svg svg>
+| <u>
+
+#data
+<!DOCTYPE html><body><select><math></math></select>
+#errors
+Line: 1 Col: 35 Unexpected start tag token (math) in the select phase. Ignored.
+Line: 1 Col: 42 Unexpected end tag (math) in the select phase. Ignored.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <select>
+
+#data
+<!DOCTYPE html><body><select><option><math></math></option></select>
+#errors
+Line: 1 Col: 43 Unexpected start tag token (math) in the select phase. Ignored.
+Line: 1 Col: 50 Unexpected end tag (math) in the select phase. Ignored.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <select>
+| <option>
+
+#data
+<!DOCTYPE html><body><table><math></math></table>
+#errors
+Line: 1 Col: 34 Unexpected start tag (math) in table context caused voodoo mode.
+Line: 1 Col: 41 Unexpected end tag (math) in table context caused voodoo mode.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <math math>
+| <table>
+
+#data
+<!DOCTYPE html><body><table><math><mi>foo</mi></math></table>
+#errors
+Line: 1 Col: 34 Unexpected start tag (math) in table context caused voodoo mode.
+Line: 1 Col: 46 Unexpected end tag (mi) in table context caused voodoo mode.
+Line: 1 Col: 53 Unexpected end tag (math) in table context caused voodoo mode.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math mi>
+| "foo"
+| <table>
+
+#data
+<!DOCTYPE html><body><table><math><mi>foo</mi><mi>bar</mi></math></table>
+#errors
+Line: 1 Col: 34 Unexpected start tag (math) in table context caused voodoo mode.
+Line: 1 Col: 46 Unexpected end tag (mi) in table context caused voodoo mode.
+Line: 1 Col: 58 Unexpected end tag (mi) in table context caused voodoo mode.
+Line: 1 Col: 65 Unexpected end tag (math) in table context caused voodoo mode.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math mi>
+| "foo"
+| <math mi>
+| "bar"
+| <table>
+
+#data
+<!DOCTYPE html><body><table><tbody><math><mi>foo</mi><mi>bar</mi></math></tbody></table>
+#errors
+Line: 1 Col: 41 Unexpected start tag (math) in table context caused voodoo mode.
+Line: 1 Col: 53 Unexpected end tag (mi) in table context caused voodoo mode.
+Line: 1 Col: 65 Unexpected end tag (mi) in table context caused voodoo mode.
+Line: 1 Col: 72 Unexpected end tag (math) in table context caused voodoo mode.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math mi>
+| "foo"
+| <math mi>
+| "bar"
+| <table>
+| <tbody>
+
+#data
+<!DOCTYPE html><body><table><tbody><tr><math><mi>foo</mi><mi>bar</mi></math></tr></tbody></table>
+#errors
+Line: 1 Col: 45 Unexpected start tag (math) in table context caused voodoo mode.
+Line: 1 Col: 57 Unexpected end tag (mi) in table context caused voodoo mode.
+Line: 1 Col: 69 Unexpected end tag (mi) in table context caused voodoo mode.
+Line: 1 Col: 76 Unexpected end tag (math) in table context caused voodoo mode.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math mi>
+| "foo"
+| <math mi>
+| "bar"
+| <table>
+| <tbody>
+| <tr>
+
+#data
+<!DOCTYPE html><body><table><tbody><tr><td><math><mi>foo</mi><mi>bar</mi></math></td></tr></tbody></table>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <math math>
+| <math mi>
+| "foo"
+| <math mi>
+| "bar"
+
+#data
+<!DOCTYPE html><body><table><tbody><tr><td><math><mi>foo</mi><mi>bar</mi></math><p>baz</td></tr></tbody></table>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <math math>
+| <math mi>
+| "foo"
+| <math mi>
+| "bar"
+| <p>
+| "baz"
+
+#data
+<!DOCTYPE html><body><table><caption><math><mi>foo</mi><mi>bar</mi></math><p>baz</caption></table>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <caption>
+| <math math>
+| <math mi>
+| "foo"
+| <math mi>
+| "bar"
+| <p>
+| "baz"
+
+#data
+<!DOCTYPE html><body><table><caption><math><mi>foo</mi><mi>bar</mi><p>baz</table><p>quux
+#errors
+Line: 1 Col: 70 HTML start tag "p" in a foreign namespace context.
+Line: 1 Col: 81 Unexpected end table tag in caption. Generates implied end caption.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <caption>
+| <math math>
+| <math mi>
+| "foo"
+| <math mi>
+| "bar"
+| <p>
+| "baz"
+| <p>
+| "quux"
+
+#data
+<!DOCTYPE html><body><table><caption><math><mi>foo</mi><mi>bar</mi>baz</table><p>quux
+#errors
+Line: 1 Col: 78 Unexpected end table tag in caption. Generates implied end caption.
+Line: 1 Col: 78 Unexpected end tag (caption). Missing end tag (math).
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <caption>
+| <math math>
+| <math mi>
+| "foo"
+| <math mi>
+| "bar"
+| "baz"
+| <p>
+| "quux"
+
+#data
+<!DOCTYPE html><body><table><colgroup><math><mi>foo</mi><mi>bar</mi><p>baz</table><p>quux
+#errors
+Line: 1 Col: 44 Unexpected start tag (math) in table context caused voodoo mode.
+Line: 1 Col: 56 Unexpected end tag (mi) in table context caused voodoo mode.
+Line: 1 Col: 68 Unexpected end tag (mi) in table context caused voodoo mode.
+Line: 1 Col: 71 HTML start tag "p" in a foreign namespace context.
+Line: 1 Col: 71 Unexpected start tag (p) in table context caused voodoo mode.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math mi>
+| "foo"
+| <math mi>
+| "bar"
+| <p>
+| "baz"
+| <table>
+| <colgroup>
+| <p>
+| "quux"
+
+#data
+<!DOCTYPE html><body><table><tr><td><select><math><mi>foo</mi><mi>bar</mi><p>baz</table><p>quux
+#errors
+Line: 1 Col: 50 Unexpected start tag token (math) in the select phase. Ignored.
+Line: 1 Col: 54 Unexpected start tag token (mi) in the select phase. Ignored.
+Line: 1 Col: 62 Unexpected end tag (mi) in the select phase. Ignored.
+Line: 1 Col: 66 Unexpected start tag token (mi) in the select phase. Ignored.
+Line: 1 Col: 74 Unexpected end tag (mi) in the select phase. Ignored.
+Line: 1 Col: 77 Unexpected start tag token (p) in the select phase. Ignored.
+Line: 1 Col: 88 Unexpected table element end tag (tables) in the select in table phase.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <select>
+| "foobarbaz"
+| <p>
+| "quux"
+
+#data
+<!DOCTYPE html><body><table><select><math><mi>foo</mi><mi>bar</mi><p>baz</table><p>quux
+#errors
+Line: 1 Col: 36 Unexpected start tag (select) in table context caused voodoo mode.
+Line: 1 Col: 42 Unexpected start tag token (math) in the select phase. Ignored.
+Line: 1 Col: 46 Unexpected start tag token (mi) in the select phase. Ignored.
+Line: 1 Col: 54 Unexpected end tag (mi) in the select phase. Ignored.
+Line: 1 Col: 58 Unexpected start tag token (mi) in the select phase. Ignored.
+Line: 1 Col: 66 Unexpected end tag (mi) in the select phase. Ignored.
+Line: 1 Col: 69 Unexpected start tag token (p) in the select phase. Ignored.
+Line: 1 Col: 80 Unexpected table element end tag (tables) in the select in table phase.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <select>
+| "foobarbaz"
+| <table>
+| <p>
+| "quux"
+
+#data
+<!DOCTYPE html><body></body></html><math><mi>foo</mi><mi>bar</mi><p>baz
+#errors
+Line: 1 Col: 41 Unexpected start tag (math).
+Line: 1 Col: 68 HTML start tag "p" in a foreign namespace context.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math mi>
+| "foo"
+| <math mi>
+| "bar"
+| <p>
+| "baz"
+
+#data
+<!DOCTYPE html><body></body><math><mi>foo</mi><mi>bar</mi><p>baz
+#errors
+Line: 1 Col: 34 Unexpected start tag token (math) in the after body phase.
+Line: 1 Col: 61 HTML start tag "p" in a foreign namespace context.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math mi>
+| "foo"
+| <math mi>
+| "bar"
+| <p>
+| "baz"
+
+#data
+<!DOCTYPE html><frameset><math><mi></mi><mi></mi><p><span>
+#errors
+Line: 1 Col: 31 Unexpected start tag token (math) in the frameset phase. Ignored.
+Line: 1 Col: 35 Unexpected start tag token (mi) in the frameset phase. Ignored.
+Line: 1 Col: 40 Unexpected end tag token (mi) in the frameset phase. Ignored.
+Line: 1 Col: 44 Unexpected start tag token (mi) in the frameset phase. Ignored.
+Line: 1 Col: 49 Unexpected end tag token (mi) in the frameset phase. Ignored.
+Line: 1 Col: 52 Unexpected start tag token (p) in the frameset phase. Ignored.
+Line: 1 Col: 58 Unexpected start tag token (span) in the frameset phase. Ignored.
+Line: 1 Col: 58 Expected closing tag. Unexpected end of file.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <frameset>
+
+#data
+<!DOCTYPE html><frameset></frameset><math><mi></mi><mi></mi><p><span>
+#errors
+Line: 1 Col: 42 Unexpected start tag (math) in the after frameset phase. Ignored.
+Line: 1 Col: 46 Unexpected start tag (mi) in the after frameset phase. Ignored.
+Line: 1 Col: 51 Unexpected end tag (mi) in the after frameset phase. Ignored.
+Line: 1 Col: 55 Unexpected start tag (mi) in the after frameset phase. Ignored.
+Line: 1 Col: 60 Unexpected end tag (mi) in the after frameset phase. Ignored.
+Line: 1 Col: 63 Unexpected start tag (p) in the after frameset phase. Ignored.
+Line: 1 Col: 69 Unexpected start tag (span) in the after frameset phase. Ignored.
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <frameset>
+
+#data
+<!DOCTYPE html><body xlink:href=foo><math xlink:href=foo></math>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| xlink:href="foo"
+| <math math>
+| xlink href="foo"
+
+#data
+<!DOCTYPE html><body xlink:href=foo xml:lang=en><math><mi xml:lang=en xlink:href=foo></mi></math>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| xlink:href="foo"
+| xml:lang="en"
+| <math math>
+| <math mi>
+| xlink href="foo"
+| xml lang="en"
+
+#data
+<!DOCTYPE html><body xlink:href=foo xml:lang=en><math><mi xml:lang=en xlink:href=foo /></math>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| xlink:href="foo"
+| xml:lang="en"
+| <math math>
+| <math mi>
+| xlink href="foo"
+| xml lang="en"
+
+#data
+<!DOCTYPE html><body xlink:href=foo xml:lang=en><math><mi xml:lang=en xlink:href=foo />bar</math>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| xlink:href="foo"
+| xml:lang="en"
+| <math math>
+| <math mi>
+| xlink href="foo"
+| xml lang="en"
+| "bar"
diff --git a/pkgs/html/test/data/tree-construction/tests_innerHTML_1.dat b/pkgs/html/test/data/tree-construction/tests_innerHTML_1.dat
new file mode 100644
index 0000000..6c78661
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/tests_innerHTML_1.dat
@@ -0,0 +1,741 @@
+#data
+<body><span>
+#errors
+#document-fragment
+body
+#document
+| <span>
+
+#data
+<span><body>
+#errors
+#document-fragment
+body
+#document
+| <span>
+
+#data
+<span><body>
+#errors
+#document-fragment
+div
+#document
+| <span>
+
+#data
+<body><span>
+#errors
+#document-fragment
+html
+#document
+| <head>
+| <body>
+| <span>
+
+#data
+<frameset><span>
+#errors
+#document-fragment
+body
+#document
+| <span>
+
+#data
+<span><frameset>
+#errors
+#document-fragment
+body
+#document
+| <span>
+
+#data
+<span><frameset>
+#errors
+#document-fragment
+div
+#document
+| <span>
+
+#data
+<frameset><span>
+#errors
+#document-fragment
+html
+#document
+| <head>
+| <frameset>
+
+#data
+<table><tr>
+#errors
+#document-fragment
+table
+#document
+| <tbody>
+| <tr>
+
+#data
+</table><tr>
+#errors
+#document-fragment
+table
+#document
+| <tbody>
+| <tr>
+
+#data
+<a>
+#errors
+#document-fragment
+table
+#document
+| <a>
+
+#data
+<a>
+#errors
+#document-fragment
+table
+#document
+| <a>
+
+#data
+<a><caption>a
+#errors
+#document-fragment
+table
+#document
+| <a>
+| <caption>
+| "a"
+
+#data
+<a><colgroup><col>
+#errors
+#document-fragment
+table
+#document
+| <a>
+| <colgroup>
+| <col>
+
+#data
+<a><tbody><tr>
+#errors
+#document-fragment
+table
+#document
+| <a>
+| <tbody>
+| <tr>
+
+#data
+<a><tfoot><tr>
+#errors
+#document-fragment
+table
+#document
+| <a>
+| <tfoot>
+| <tr>
+
+#data
+<a><thead><tr>
+#errors
+#document-fragment
+table
+#document
+| <a>
+| <thead>
+| <tr>
+
+#data
+<a><tr>
+#errors
+#document-fragment
+table
+#document
+| <a>
+| <tbody>
+| <tr>
+
+#data
+<a><th>
+#errors
+#document-fragment
+table
+#document
+| <a>
+| <tbody>
+| <tr>
+| <th>
+
+#data
+<a><td>
+#errors
+#document-fragment
+table
+#document
+| <a>
+| <tbody>
+| <tr>
+| <td>
+
+#data
+<table></table><tbody>
+#errors
+#document-fragment
+caption
+#document
+| <table>
+
+#data
+</table><span>
+#errors
+#document-fragment
+caption
+#document
+| <span>
+
+#data
+<span></table>
+#errors
+#document-fragment
+caption
+#document
+| <span>
+
+#data
+</caption><span>
+#errors
+#document-fragment
+caption
+#document
+| <span>
+
+#data
+<span></caption><span>
+#errors
+#document-fragment
+caption
+#document
+| <span>
+| <span>
+
+#data
+<span><caption><span>
+#errors
+#document-fragment
+caption
+#document
+| <span>
+| <span>
+
+#data
+<span><col><span>
+#errors
+#document-fragment
+caption
+#document
+| <span>
+| <span>
+
+#data
+<span><colgroup><span>
+#errors
+#document-fragment
+caption
+#document
+| <span>
+| <span>
+
+#data
+<span><html><span>
+#errors
+#document-fragment
+caption
+#document
+| <span>
+| <span>
+
+#data
+<span><tbody><span>
+#errors
+#document-fragment
+caption
+#document
+| <span>
+| <span>
+
+#data
+<span><td><span>
+#errors
+#document-fragment
+caption
+#document
+| <span>
+| <span>
+
+#data
+<span><tfoot><span>
+#errors
+#document-fragment
+caption
+#document
+| <span>
+| <span>
+
+#data
+<span><thead><span>
+#errors
+#document-fragment
+caption
+#document
+| <span>
+| <span>
+
+#data
+<span><th><span>
+#errors
+#document-fragment
+caption
+#document
+| <span>
+| <span>
+
+#data
+<span><tr><span>
+#errors
+#document-fragment
+caption
+#document
+| <span>
+| <span>
+
+#data
+<span></table><span>
+#errors
+#document-fragment
+caption
+#document
+| <span>
+| <span>
+
+#data
+</colgroup><col>
+#errors
+#document-fragment
+colgroup
+#document
+| <col>
+
+#data
+<a><col>
+#errors
+#document-fragment
+colgroup
+#document
+| <col>
+
+#data
+<caption><a>
+#errors
+#document-fragment
+tbody
+#document
+| <a>
+
+#data
+<col><a>
+#errors
+#document-fragment
+tbody
+#document
+| <a>
+
+#data
+<colgroup><a>
+#errors
+#document-fragment
+tbody
+#document
+| <a>
+
+#data
+<tbody><a>
+#errors
+#document-fragment
+tbody
+#document
+| <a>
+
+#data
+<tfoot><a>
+#errors
+#document-fragment
+tbody
+#document
+| <a>
+
+#data
+<thead><a>
+#errors
+#document-fragment
+tbody
+#document
+| <a>
+
+#data
+</table><a>
+#errors
+#document-fragment
+tbody
+#document
+| <a>
+
+#data
+<a><tr>
+#errors
+#document-fragment
+tbody
+#document
+| <a>
+| <tr>
+
+#data
+<a><td>
+#errors
+#document-fragment
+tbody
+#document
+| <a>
+| <tr>
+| <td>
+
+#data
+<a><td>
+#errors
+#document-fragment
+tbody
+#document
+| <a>
+| <tr>
+| <td>
+
+#data
+<a><td>
+#errors
+#document-fragment
+tbody
+#document
+| <a>
+| <tr>
+| <td>
+
+#data
+<td><table><tbody><a><tr>
+#errors
+#document-fragment
+tbody
+#document
+| <tr>
+| <td>
+| <a>
+| <table>
+| <tbody>
+| <tr>
+
+#data
+</tr><td>
+#errors
+#document-fragment
+tr
+#document
+| <td>
+
+#data
+<td><table><a><tr></tr><tr>
+#errors
+#document-fragment
+tr
+#document
+| <td>
+| <a>
+| <table>
+| <tbody>
+| <tr>
+| <tr>
+
+#data
+<caption><td>
+#errors
+#document-fragment
+tr
+#document
+| <td>
+
+#data
+<col><td>
+#errors
+#document-fragment
+tr
+#document
+| <td>
+
+#data
+<colgroup><td>
+#errors
+#document-fragment
+tr
+#document
+| <td>
+
+#data
+<tbody><td>
+#errors
+#document-fragment
+tr
+#document
+| <td>
+
+#data
+<tfoot><td>
+#errors
+#document-fragment
+tr
+#document
+| <td>
+
+#data
+<thead><td>
+#errors
+#document-fragment
+tr
+#document
+| <td>
+
+#data
+<tr><td>
+#errors
+#document-fragment
+tr
+#document
+| <td>
+
+#data
+</table><td>
+#errors
+#document-fragment
+tr
+#document
+| <td>
+
+#data
+<td><table></table><td>
+#errors
+#document-fragment
+tr
+#document
+| <td>
+| <table>
+| <td>
+
+#data
+<td><table></table><td>
+#errors
+#document-fragment
+tr
+#document
+| <td>
+| <table>
+| <td>
+
+#data
+<caption><a>
+#errors
+#document-fragment
+td
+#document
+| <a>
+
+#data
+<col><a>
+#errors
+#document-fragment
+td
+#document
+| <a>
+
+#data
+<colgroup><a>
+#errors
+#document-fragment
+td
+#document
+| <a>
+
+#data
+<tbody><a>
+#errors
+#document-fragment
+td
+#document
+| <a>
+
+#data
+<tfoot><a>
+#errors
+#document-fragment
+td
+#document
+| <a>
+
+#data
+<th><a>
+#errors
+#document-fragment
+td
+#document
+| <a>
+
+#data
+<thead><a>
+#errors
+#document-fragment
+td
+#document
+| <a>
+
+#data
+<tr><a>
+#errors
+#document-fragment
+td
+#document
+| <a>
+
+#data
+</table><a>
+#errors
+#document-fragment
+td
+#document
+| <a>
+
+#data
+</tbody><a>
+#errors
+#document-fragment
+td
+#document
+| <a>
+
+#data
+</td><a>
+#errors
+#document-fragment
+td
+#document
+| <a>
+
+#data
+</tfoot><a>
+#errors
+#document-fragment
+td
+#document
+| <a>
+
+#data
+</thead><a>
+#errors
+#document-fragment
+td
+#document
+| <a>
+
+#data
+</th><a>
+#errors
+#document-fragment
+td
+#document
+| <a>
+
+#data
+</tr><a>
+#errors
+#document-fragment
+td
+#document
+| <a>
+
+#data
+<table><td><td>
+#errors
+#document-fragment
+td
+#document
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| <td>
+
+#data
+</select><option>
+#errors
+#document-fragment
+select
+#document
+| <option>
+
+#data
+<input><option>
+#errors
+#document-fragment
+select
+#document
+| <option>
+
+#data
+<keygen><option>
+#errors
+#document-fragment
+select
+#document
+| <option>
+
+#data
+<textarea><option>
+#errors
+#document-fragment
+select
+#document
+| <option>
+
+#data
+</html><!--abc-->
+#errors
+#document-fragment
+html
+#document
+| <head>
+| <body>
+| <!-- abc -->
+
+#data
+</frameset><frame>
+#errors
+#document-fragment
+frameset
+#document
+| <frame>
+
+#data
+#errors
+#document-fragment
+html
+#document
+| <head>
+| <body>
diff --git a/pkgs/html/test/data/tree-construction/tricky01.dat b/pkgs/html/test/data/tree-construction/tricky01.dat
new file mode 100644
index 0000000..0841992
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/tricky01.dat
@@ -0,0 +1,261 @@
+#data
+<b><p>Bold </b> Not bold</p>
+Also not bold.
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <b>
+| <p>
+| <b>
+| "Bold "
+| " Not bold"
+| "
+Also not bold."
+
+#data
+<html>
+<font color=red><i>Italic and Red<p>Italic and Red </font> Just italic.</p> Italic only.</i> Plain
+<p>I should not be red. <font color=red>Red. <i>Italic and red.</p>
+<p>Italic and red. </i> Red.</font> I should not be red.</p>
+<b>Bold <i>Bold and italic</b> Only Italic </i> Plain
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <font>
+| color="red"
+| <i>
+| "Italic and Red"
+| <i>
+| <p>
+| <font>
+| color="red"
+| "Italic and Red "
+| " Just italic."
+| " Italic only."
+| " Plain
+"
+| <p>
+| "I should not be red. "
+| <font>
+| color="red"
+| "Red. "
+| <i>
+| "Italic and red."
+| <font>
+| color="red"
+| <i>
+| "
+"
+| <p>
+| <font>
+| color="red"
+| <i>
+| "Italic and red. "
+| " Red."
+| " I should not be red."
+| "
+"
+| <b>
+| "Bold "
+| <i>
+| "Bold and italic"
+| <i>
+| " Only Italic "
+| " Plain"
+
+#data
+<html><body>
+<p><font size="7">First paragraph.</p>
+<p>Second paragraph.</p></font>
+<b><p><i>Bold and Italic</b> Italic</p>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "
+"
+| <p>
+| <font>
+| size="7"
+| "First paragraph."
+| <font>
+| size="7"
+| "
+"
+| <p>
+| "Second paragraph."
+| "
+"
+| <b>
+| <p>
+| <b>
+| <i>
+| "Bold and Italic"
+| <i>
+| " Italic"
+
+#data
+<html>
+<dl>
+<dt><b>Boo
+<dd>Goo?
+</dl>
+</html>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <dl>
+| "
+"
+| <dt>
+| <b>
+| "Boo
+"
+| <dd>
+| <b>
+| "Goo?
+"
+| <b>
+| "
+"
+
+#data
+<html><body>
+<label><a><div>Hello<div>World</div></a></label>
+</body></html>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "
+"
+| <label>
+| <a>
+| <div>
+| <a>
+| "Hello"
+| <div>
+| "World"
+| "
+"
+
+#data
+<table><center> <font>a</center> <img> <tr><td> </td> </tr> </table>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <center>
+| " "
+| <font>
+| "a"
+| <font>
+| <img>
+| " "
+| <table>
+| " "
+| <tbody>
+| <tr>
+| <td>
+| " "
+| " "
+| " "
+
+#data
+<table><tr><p><a><p>You should see this text.
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <p>
+| <a>
+| <p>
+| <a>
+| "You should see this text."
+| <table>
+| <tbody>
+| <tr>
+
+#data
+<TABLE>
+<TR>
+<CENTER><CENTER><TD></TD></TR><TR>
+<FONT>
+<TABLE><tr></tr></TABLE>
+</P>
+<a></font><font></a>
+This page contains an insanely badly-nested tag sequence.
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <center>
+| <center>
+| <font>
+| "
+"
+| <table>
+| "
+"
+| <tbody>
+| <tr>
+| "
+"
+| <td>
+| <tr>
+| "
+"
+| <table>
+| <tbody>
+| <tr>
+| <font>
+| "
+"
+| <p>
+| "
+"
+| <a>
+| <a>
+| <font>
+| <font>
+| "
+This page contains an insanely badly-nested tag sequence."
+
+#data
+<html>
+<body>
+<b><nobr><div>This text is in a div inside a nobr</nobr>More text that should not be in the nobr, i.e., the
+nobr should have closed the div inside it implicitly. </b><pre>A pre tag outside everything else.</pre>
+</body>
+</html>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "
+"
+| <b>
+| <nobr>
+| <div>
+| <b>
+| <nobr>
+| "This text is in a div inside a nobr"
+| "More text that should not be in the nobr, i.e., the
+nobr should have closed the div inside it implicitly. "
+| <pre>
+| "A pre tag outside everything else."
+| "
+
+"
diff --git a/pkgs/html/test/data/tree-construction/webkit01.dat b/pkgs/html/test/data/tree-construction/webkit01.dat
new file mode 100644
index 0000000..06bc436
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/webkit01.dat
@@ -0,0 +1,594 @@
+#data
+Test
+#errors
+Line: 1 Col: 4 Unexpected non-space characters. Expected DOCTYPE.
+#document
+| <html>
+| <head>
+| <body>
+| "Test"
+
+#data
+<div></div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+
+#data
+<div>Test</div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| "Test"
+
+#data
+<di
+#errors
+#document
+| <html>
+| <head>
+| <body>
+
+#data
+<div>Hello</div>
+<script>
+console.log("PASS");
+</script>
+<div>Bye</div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| "Hello"
+| "
+"
+| <script>
+| "
+console.log("PASS");
+"
+| "
+"
+| <div>
+| "Bye"
+
+#data
+<div foo="bar">Hello</div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| foo="bar"
+| "Hello"
+
+#data
+<div>Hello</div>
+<script>
+console.log("FOO<span>BAR</span>BAZ");
+</script>
+<div>Bye</div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| "Hello"
+| "
+"
+| <script>
+| "
+console.log("FOO<span>BAR</span>BAZ");
+"
+| "
+"
+| <div>
+| "Bye"
+
+#data
+<foo bar="baz"></foo><potato quack="duck"></potato>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <foo>
+| bar="baz"
+| <potato>
+| quack="duck"
+
+#data
+<foo bar="baz"><potato quack="duck"></potato></foo>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <foo>
+| bar="baz"
+| <potato>
+| quack="duck"
+
+#data
+<foo></foo bar="baz"><potato></potato quack="duck">
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <foo>
+| <potato>
+
+#data
+</ tttt>
+#errors
+#document
+| <!-- tttt -->
+| <html>
+| <head>
+| <body>
+
+#data
+<div FOO ><img><img></div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| foo=""
+| <img>
+| <img>
+
+#data
+<p>Test</p<p>Test2</p>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <p>
+| "TestTest2"
+
+#data
+<rdar://problem/6869687>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <rdar:>
+| 6869687=""
+| problem=""
+
+#data
+<A>test< /A>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <a>
+| "test< /A>"
+
+#data
+<
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "<"
+
+#data
+<body foo='bar'><body foo='baz' yo='mama'>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| foo="bar"
+| yo="mama"
+
+#data
+<body></br foo="bar"></body>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <br>
+
+#data
+<bdy><br foo="bar"></body>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <bdy>
+| <br>
+| foo="bar"
+
+#data
+<body></body></br foo="bar">
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <br>
+
+#data
+<bdy></body><br foo="bar">
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <bdy>
+| <br>
+| foo="bar"
+
+#data
+<html><body></body></html><!-- Hi there -->
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <!-- Hi there -->
+
+#data
+<html><body></body></html>x<!-- Hi there -->
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "x"
+| <!-- Hi there -->
+
+#data
+<html><body></body></html>x<!-- Hi there --></html><!-- Again -->
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "x"
+| <!-- Hi there -->
+| <!-- Again -->
+
+#data
+<html><body></body></html>x<!-- Hi there --></body></html><!-- Again -->
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "x"
+| <!-- Hi there -->
+| <!-- Again -->
+
+#data
+<html><body><ruby><div><rp>xx</rp></div></ruby></body></html>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <ruby>
+| <div>
+| <rp>
+| "xx"
+
+#data
+<html><body><ruby><div><rt>xx</rt></div></ruby></body></html>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <ruby>
+| <div>
+| <rt>
+| "xx"
+
+#data
+<html><frameset><!--1--><noframes>A</noframes><!--2--></frameset><!--3--><noframes>B</noframes><!--4--></html><!--5--><noframes>C</noframes><!--6-->
+#errors
+#document
+| <html>
+| <head>
+| <frameset>
+| <!-- 1 -->
+| <noframes>
+| "A"
+| <!-- 2 -->
+| <!-- 3 -->
+| <noframes>
+| "B"
+| <!-- 4 -->
+| <noframes>
+| "C"
+| <!-- 5 -->
+| <!-- 6 -->
+
+#data
+<select><option>A<select><option>B<select><option>C<select><option>D<select><option>E<select><option>F<select><option>G<select>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <select>
+| <option>
+| "A"
+| <option>
+| "B"
+| <select>
+| <option>
+| "C"
+| <option>
+| "D"
+| <select>
+| <option>
+| "E"
+| <option>
+| "F"
+| <select>
+| <option>
+| "G"
+
+#data
+<dd><dd><dt><dt><dd><li><li>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <dd>
+| <dd>
+| <dt>
+| <dt>
+| <dd>
+| <li>
+| <li>
+
+#data
+<div><b></div><div><nobr>a<nobr>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| <b>
+| <div>
+| <b>
+| <nobr>
+| "a"
+| <nobr>
+
+#data
+<head></head>
+<body></body>
+#errors
+#document
+| <html>
+| <head>
+| "
+"
+| <body>
+
+#data
+<head></head> <style></style>ddd
+#errors
+#document
+| <html>
+| <head>
+| <style>
+| " "
+| <body>
+| "ddd"
+
+#data
+<kbd><table></kbd><col><select><tr>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <kbd>
+| <select>
+| <table>
+| <colgroup>
+| <col>
+| <tbody>
+| <tr>
+
+#data
+<kbd><table></kbd><col><select><tr></table><div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <kbd>
+| <select>
+| <table>
+| <colgroup>
+| <col>
+| <tbody>
+| <tr>
+| <div>
+
+#data
+<a><li><style></style><title></title></a>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <a>
+| <li>
+| <a>
+| <style>
+| <title>
+
+#data
+<font></p><p><meta><title></title></font>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <font>
+| <p>
+| <p>
+| <font>
+| <meta>
+| <title>
+
+#data
+<a><center><title></title><a>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <a>
+| <center>
+| <a>
+| <title>
+| <a>
+
+#data
+<svg><title><div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| <svg title>
+| <div>
+
+#data
+<svg><title><rect><div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| <svg title>
+| <rect>
+| <div>
+
+#data
+<svg><title><svg><div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| <svg title>
+| <svg svg>
+| <div>
+
+#data
+<img <="" FAIL>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <img>
+| <=""
+| fail=""
+
+#data
+<ul><li><div id='foo'/>A</li><li>B<div>C</div></li></ul>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <ul>
+| <li>
+| <div>
+| id="foo"
+| "A"
+| <li>
+| "B"
+| <div>
+| "C"
+
+#data
+<svg><em><desc></em>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| <em>
+| <desc>
+
+#data
+<svg><tfoot></mi><td>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <svg svg>
+| <svg tfoot>
+| <svg td>
+
+#data
+<math><mrow><mrow><mn>1</mn></mrow><mi>a</mi></mrow></math>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <math math>
+| <math mrow>
+| <math mrow>
+| <math mn>
+| "1"
+| <math mi>
+| "a"
+
+#data
+<!doctype html><input type="hidden"><frameset>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <frameset>
+
+#data
+<!doctype html><input type="button"><frameset>
+#errors
+#document
+| <!DOCTYPE html>
+| <html>
+| <head>
+| <body>
+| <input>
+| type="button"
diff --git a/pkgs/html/test/data/tree-construction/webkit02.dat b/pkgs/html/test/data/tree-construction/webkit02.dat
new file mode 100644
index 0000000..468879b
--- /dev/null
+++ b/pkgs/html/test/data/tree-construction/webkit02.dat
@@ -0,0 +1,94 @@
+#data
+<foo bar=qux/>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <foo>
+| bar="qux/"
+
+#data
+<p id="status"><noscript><strong>A</strong></noscript><span>B</span></p>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <p>
+| id="status"
+| <noscript>
+| "<strong>A</strong>"
+| <span>
+| "B"
+
+#data
+<div><sarcasm><div></div></sarcasm></div>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <div>
+| <sarcasm>
+| <div>
+
+#data
+<html><body><img src="" border="0" alt="><div>A</div></body></html>
+#errors
+#document
+| <html>
+| <head>
+| <body>
+
+#data
+<table><td></tbody>A
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| "A"
+| <table>
+| <tbody>
+| <tr>
+| <td>
+
+#data
+<table><td></thead>A
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| "A"
+
+#data
+<table><td></tfoot>A
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <tbody>
+| <tr>
+| <td>
+| "A"
+
+#data
+<table><thead><td></tbody>A
+#errors
+#document
+| <html>
+| <head>
+| <body>
+| <table>
+| <thead>
+| <tr>
+| <td>
+| "A"
diff --git a/pkgs/html/test/dom_test.dart b/pkgs/html/test/dom_test.dart
new file mode 100644
index 0000000..293443f
--- /dev/null
+++ b/pkgs/html/test/dom_test.dart
@@ -0,0 +1,190 @@
+/// Additional feature tests that aren't based on test data.
+library;
+
+import 'package:html/dom.dart';
+import 'package:html/parser.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('Element', () {
+ test('classes', () {
+ final barBaz = Element.html('<div class=" bar baz"></div>');
+ final quxBaz = Element.html('<div class="qux baz "></div>');
+ expect(barBaz.className, ' bar baz');
+ expect(quxBaz.className, 'qux baz ');
+ expect(barBaz.classes, ['bar', 'baz']);
+ expect(quxBaz.classes, ['qux', 'baz']);
+ });
+ });
+
+ group('Document', () {
+ final doc = parse('<div id=foo>'
+ '<div class=" bar baz"></div>'
+ '<div class="qux baz "></div>'
+ '<div id=Foo>');
+
+ test('getElementById', () {
+ final foo = doc.body!.nodes[0];
+ final fooVar = foo.nodes[2];
+ expect((foo as Element).id, 'foo');
+ expect((fooVar as Element).id, 'Foo');
+ expect(doc.getElementById('foo'), foo);
+ expect(doc.getElementById('Foo'), fooVar);
+ });
+
+ test('getElementsByClassName', () {
+ final foo = doc.body!.nodes[0];
+ final barBaz = foo.nodes[0];
+ final quxBaz = foo.nodes[1];
+ expect((barBaz as Element).className, ' bar baz');
+ expect((quxBaz as Element).className, 'qux baz ');
+ expect(doc.getElementsByClassName('baz'), [barBaz, quxBaz]);
+ expect(doc.getElementsByClassName('bar '), [barBaz]);
+ expect(doc.getElementsByClassName(' qux'), [quxBaz]);
+ expect(doc.getElementsByClassName(' baz qux'), [quxBaz]);
+ });
+
+ test('getElementsByTagName', () {
+ final foo = doc.body!.nodes[0];
+ final barBaz = foo.nodes[0];
+ final quxBaz = foo.nodes[1];
+ final fooVar = foo.nodes[2];
+ expect(doc.getElementsByTagName('div'), [foo, barBaz, quxBaz, fooVar]);
+ });
+ });
+
+ group('fragments are flattened', () {
+ test('add', () {
+ final doc = parse('<body>');
+ doc.body!.nodes.add(parseFragment('<x-foo>'));
+ expect((doc.body!.nodes[0] as Element).localName, 'x-foo');
+ doc.body!.nodes.add(parseFragment('<x-bar>'));
+ expect((doc.body!.nodes[1] as Element).localName, 'x-bar');
+ });
+
+ test('addLast', () {
+ final doc = parse('<body>');
+ doc.body!.nodes.addLast(parseFragment('<x-foo>'));
+ expect((doc.body!.nodes[0] as Element).localName, 'x-foo');
+ doc.body!.nodes.addLast(parseFragment('<x-bar>'));
+ expect((doc.body!.nodes[1] as Element).localName, 'x-bar');
+ });
+
+ test('addAll', () {
+ final doc = parse('<body><x-a></x-a>');
+ doc.body!.nodes.addAll([parseFragment('<x-b></x-b><x-c></x-c>')]);
+ expect((doc.body!.nodes[0] as Element).localName, 'x-a');
+ expect((doc.body!.nodes[1] as Element).localName, 'x-b');
+ expect((doc.body!.nodes[2] as Element).localName, 'x-c');
+ });
+
+ test('insert', () {
+ var doc = parse('<body><x-a></x-a>');
+ doc.body!.nodes.insert(0, parseFragment('<x-b></x-b><x-c></x-c>'));
+ expect((doc.body!.nodes[0] as Element).localName, 'x-b');
+ expect((doc.body!.nodes[1] as Element).localName, 'x-c');
+ expect((doc.body!.nodes[2] as Element).localName, 'x-a');
+
+ doc = parse('<body><x-a></x-a>');
+ doc.body!.nodes.insert(1, parseFragment('<x-b></x-b><x-c></x-c>'));
+ expect((doc.body!.nodes[0] as Element).localName, 'x-a');
+ expect((doc.body!.nodes[1] as Element).localName, 'x-b');
+ expect((doc.body!.nodes[2] as Element).localName, 'x-c');
+
+ doc = parse('<body><x-a></x-a>');
+ doc.body!.nodes.insert(0, parseFragment('<x-b></x-b>'));
+ doc.body!.nodes.insert(1, parseFragment('<x-c></x-c>'));
+ expect((doc.body!.nodes[0] as Element).localName, 'x-b');
+ expect((doc.body!.nodes[1] as Element).localName, 'x-c');
+ expect((doc.body!.nodes[2] as Element).localName, 'x-a');
+ });
+
+ test('insertAll', () {
+ var doc = parse('<body><x-a></x-a>');
+ doc.body!.nodes.insertAll(0, [parseFragment('<x-b></x-b><x-c></x-c>')]);
+ expect((doc.body!.nodes[0] as Element).localName, 'x-b');
+ expect((doc.body!.nodes[1] as Element).localName, 'x-c');
+ expect((doc.body!.nodes[2] as Element).localName, 'x-a');
+ expect(doc.body!.nodes.length, 3);
+
+ doc = parse('<body><x-a></x-a>');
+ doc.body!.nodes.insertAll(1, [parseFragment('<x-b></x-b><x-c></x-c>')]);
+ expect((doc.body!.nodes[0] as Element).localName, 'x-a');
+ expect((doc.body!.nodes[1] as Element).localName, 'x-b');
+ expect((doc.body!.nodes[2] as Element).localName, 'x-c');
+
+ doc = parse('<body><x-a></x-a>');
+ doc.body!.nodes.insertAll(0, [parseFragment('<x-b></x-b>')]);
+ doc.body!.nodes.insertAll(1, [parseFragment('<x-c></x-c>')]);
+ expect((doc.body!.nodes[0] as Element).localName, 'x-b');
+ expect((doc.body!.nodes[1] as Element).localName, 'x-c');
+ expect((doc.body!.nodes[2] as Element).localName, 'x-a');
+ });
+
+ test('operator []=', () {
+ var doc = parse('<body><x-a></x-a>');
+ doc.body!.nodes[0] = parseFragment('<x-b></x-b><x-c></x-c>');
+ expect((doc.body!.nodes[0] as Element).localName, 'x-b');
+ expect((doc.body!.nodes[1] as Element).localName, 'x-c');
+ expect(doc.body!.nodes.length, 2);
+
+ doc = parse('<body><x-a></x-a><x-b></x-b><x-c></x-c>');
+ doc.body!.nodes[1] = parseFragment('<y-b></y-b><y-c></y-c>');
+ expect((doc.body!.nodes[0] as Element).localName, 'x-a');
+ expect((doc.body!.nodes[1] as Element).localName, 'y-b');
+ expect((doc.body!.nodes[2] as Element).localName, 'y-c');
+ expect((doc.body!.nodes[3] as Element).localName, 'x-c');
+ expect(doc.body!.nodes.length, 4);
+ });
+
+ test('setRange', () {
+ var fragment = parseFragment('<y-b></y-b><y-c></y-c>');
+ var doc = parse('<body><x-a></x-a><x-b></x-b><x-c></x-c>');
+ doc.body!.nodes.setRange(1, 2, fragment.nodes, 0);
+ expect((doc.body!.nodes[0] as Element).localName, 'x-a');
+ expect((doc.body!.nodes[1] as Element).localName, 'y-b');
+ expect((doc.body!.nodes[2] as Element).localName, 'y-c');
+ expect(doc.body!.nodes.length, 3);
+
+ fragment = parseFragment('<y-b></y-b><y-c></y-c>');
+ doc = parse('<body><x-a></x-a><x-b></x-b><x-c></x-c>');
+ doc.body!.nodes.setRange(1, 1, [fragment], 0);
+ expect((doc.body!.nodes[0] as Element).localName, 'x-a');
+ expect((doc.body!.nodes[1] as Element).localName, 'y-b');
+ expect((doc.body!.nodes[2] as Element).localName, 'y-c');
+ expect((doc.body!.nodes[3] as Element).localName, 'x-c');
+ expect(doc.body!.nodes.length, 4);
+ });
+
+ test('replaceRange', () {
+ var fragment = parseFragment('<y-b></y-b><y-c></y-c>');
+ var doc = parse('<body><x-a></x-a><x-b></x-b><x-c></x-c>');
+ doc.body!.nodes.replaceRange(1, 2, fragment.nodes);
+ expect((doc.body!.nodes[0] as Element).localName, 'x-a');
+ expect((doc.body!.nodes[1] as Element).localName, 'y-b');
+ expect((doc.body!.nodes[2] as Element).localName, 'y-c');
+ expect((doc.body!.nodes[3] as Element).localName, 'x-c');
+ expect(doc.body!.nodes.length, 4);
+
+ fragment = parseFragment('<y-b></y-b><y-c></y-c>');
+ doc = parse('<body><x-a></x-a><x-b></x-b><x-c></x-c>');
+ doc.body!.nodes.replaceRange(1, 2, [fragment]);
+ expect((doc.body!.nodes[0] as Element).localName, 'x-a');
+ expect((doc.body!.nodes[1] as Element).localName, 'y-b');
+ expect((doc.body!.nodes[2] as Element).localName, 'y-c');
+ expect((doc.body!.nodes[3] as Element).localName, 'x-c');
+ expect(doc.body!.nodes.length, 4);
+ });
+
+ test('replaceWith', () {
+ final fragment = parseFragment('<y-b></y-b><y-c></y-c>');
+ final doc = parse('<body><x-a></x-a><x-b></x-b><x-c></x-c>');
+ doc.body!.nodes[1].replaceWith(fragment);
+ expect((doc.body!.nodes[0] as Element).localName, 'x-a');
+ expect((doc.body!.nodes[1] as Element).localName, 'y-b');
+ expect((doc.body!.nodes[2] as Element).localName, 'y-c');
+ expect((doc.body!.nodes[3] as Element).localName, 'x-c');
+ expect(doc.body!.nodes.length, 4);
+ });
+ });
+}
diff --git a/pkgs/html/test/parser_feature_test.dart b/pkgs/html/test/parser_feature_test.dart
new file mode 100644
index 0000000..7156146
--- /dev/null
+++ b/pkgs/html/test/parser_feature_test.dart
@@ -0,0 +1,493 @@
+/// Additional feature tests that aren't based on test data.
+library;
+
+import 'package:html/dom.dart';
+import 'package:html/parser.dart';
+import 'package:html/src/constants.dart';
+import 'package:html/src/encoding_parser.dart';
+import 'package:html/src/treebuilder.dart';
+import 'package:source_span/source_span.dart';
+import 'package:test/test.dart';
+
+void main() {
+ _testElementSpans();
+ test('doctype is cloneable', () {
+ final doc = parse('<!doctype HTML>');
+ final doctype = doc.nodes[0] as DocumentType;
+ expect(doctype.clone(false).toString(), '<!DOCTYPE html>');
+ });
+
+ test('line counter', () {
+ // http://groups.google.com/group/html5lib-discuss/browse_frm/thread/f4f00e4a2f26d5c0
+ final doc = parse('<pre>\nx\n>\n</pre>');
+ expect(doc.body!.innerHtml, '<pre>x\n>\n</pre>');
+ });
+
+ test('namespace html elements on', () {
+ final doc = HtmlParser('', tree: TreeBuilder(true)).parse();
+ expect((doc.nodes[0] as Element).namespaceUri, Namespaces.html);
+ });
+
+ test('namespace html elements off', () {
+ final doc = HtmlParser('', tree: TreeBuilder(false)).parse();
+ expect((doc.nodes[0] as Element).namespaceUri, null);
+ });
+
+ test('parse error spans - full', () {
+ final parser = HtmlParser('''
+<!DOCTYPE html>
+<html>
+ <body>
+ <!DOCTYPE html>
+ </body>
+</html>
+''', generateSpans: true, sourceUrl: 'ParseError');
+ final doc = parser.parse();
+ expect(doc.body!.outerHtml, '<body>\n \n \n\n</body>');
+ expect(parser.errors.length, 1);
+ final error = parser.errors[0];
+ expect(error.errorCode, 'unexpected-doctype');
+
+ // Note: these values are 0-based, but the printed format is 1-based.
+ expect(error.span!.start.line, 3);
+ expect(error.span!.end.line, 3);
+ expect(error.span!.start.column, 2);
+ expect(error.span!.end.column, 17);
+ expect(error.span!.text, '<!DOCTYPE html>');
+
+ expect(error.toString(), '''
+On line 4, column 3 of ParseError: Unexpected DOCTYPE. Ignored.
+ ╷
+4 │ <!DOCTYPE html>
+ │ ^^^^^^^^^^^^^^^
+ ╵''');
+ });
+
+ test('parse error spans - minimal', () {
+ final parser = HtmlParser('''
+<!DOCTYPE html>
+<html>
+ <body>
+ <!DOCTYPE html>
+ </body>
+</html>
+''');
+ final doc = parser.parse();
+ expect(doc.body!.outerHtml, '<body>\n \n \n\n</body>');
+ 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', () {
+ final textContent = '\n hello {{name}}';
+ final html = '<body><div>$textContent</div>';
+ final doc = parse(html, generateSpans: true);
+ final text = doc.body!.nodes[0].nodes[0] as Text;
+ expect(text, const TypeMatcher<Text>());
+ expect(text.data, textContent);
+ expect(text.sourceSpan!.start.offset, html.indexOf(textContent));
+ expect(text.sourceSpan!.length, textContent.length);
+ });
+
+ test('attribute spans', () {
+ final text = '<element name="x-foo" extends="x-bar" constructor="Foo">';
+ final doc = parse(text, generateSpans: true);
+ final elem = doc.querySelector('element')!;
+ expect(elem.sourceSpan!.start.offset, 0);
+ expect(elem.sourceSpan!.end.offset, text.length);
+ expect(elem.sourceSpan!.text, text);
+
+ expect(elem.attributeSpans!['quux'], null);
+
+ final span = elem.attributeSpans!['extends']!;
+ expect(span.start.offset, text.indexOf('extends'));
+ expect(span.text, 'extends="x-bar"');
+ });
+
+ test('attribute value spans', () {
+ final text = '<element name="x-foo" extends="x-bar" constructor="Foo">';
+ final doc = parse(text, generateSpans: true);
+ final elem = doc.querySelector('element')!;
+
+ expect(elem.attributeValueSpans!['quux'], null);
+
+ final span = elem.attributeValueSpans!['extends']!;
+ expect(span.start.offset, text.indexOf('x-bar'));
+ expect(span.text, 'x-bar');
+ });
+
+ test('attribute spans if no attributes', () {
+ final text = '<element>';
+ final doc = parse(text, generateSpans: true);
+ final elem = doc.querySelector('element')!;
+
+ expect(elem.attributeSpans!['quux'], null);
+ expect(elem.attributeValueSpans!['quux'], null);
+ });
+
+ test('attribute spans if no attribute value', () {
+ final text = '<foo template>';
+ final doc = parse(text, generateSpans: true);
+ final elem = doc.querySelector('foo')!;
+
+ expect(elem.attributeSpans!['template']!.start.offset,
+ text.indexOf('template'));
+ expect(elem.attributeValueSpans!.containsKey('template'), false);
+ });
+
+ test('attribute spans null if code parsed without spans', () {
+ final text = '<element name="x-foo" extends="x-bar" constructor="Foo">';
+ final doc = parse(text);
+ final elem = doc.querySelector('element')!;
+ expect(elem.sourceSpan, null);
+ expect(elem.attributeSpans!['quux'], null);
+ expect(elem.attributeSpans!['extends'], null);
+ });
+
+ test('void element innerHTML', () {
+ var doc = parse('<div></div>');
+ expect(doc.body!.innerHtml, '<div></div>');
+ doc = parse('<body><script></script></body>');
+ expect(doc.body!.innerHtml, '<script></script>');
+ doc = parse('<br>');
+ expect(doc.body!.innerHtml, '<br>');
+ doc = parse('<br><foo><bar>');
+ expect(doc.body!.innerHtml, '<br><foo><bar></bar></foo>');
+ });
+
+ test('empty document has html, body, and head', () {
+ final doc = parse('');
+ final html = '<html><head></head><body></body></html>';
+ expect(doc.outerHtml, html);
+ expect(doc.documentElement!.outerHtml, html);
+ expect(doc.head!.outerHtml, '<head></head>');
+ expect(doc.body!.outerHtml, '<body></body>');
+ });
+
+ test('strange table case', () {
+ final doc = parse('<table><tbody><foo>').body!;
+ expect(doc.innerHtml, '<foo></foo><table><tbody></tbody></table>');
+ });
+
+ group('html serialization', () {
+ test('attribute order', () {
+ // Note: the spec only requires a stable order.
+ // However, we preserve the input order via LinkedHashMap
+ final body = parse('<foo d=1 a=2 c=3 b=4>').body!;
+ expect(body.innerHtml, '<foo d="1" a="2" c="3" b="4"></foo>');
+ expect(body.querySelector('foo')!.attributes.remove('a'), '2');
+ expect(body.innerHtml, '<foo d="1" c="3" b="4"></foo>');
+ body.querySelector('foo')!.attributes['a'] = '0';
+ expect(body.innerHtml, '<foo d="1" c="3" b="4" a="0"></foo>');
+ });
+
+ test('escaping Text node in <script>', () {
+ final e = parseFragment('<script>a && b</script>').firstChild as Element;
+ expect(e.outerHtml, '<script>a && b</script>');
+ });
+
+ test('escaping Text node in <span>', () {
+ final e = parseFragment('<span>a && b</span>').firstChild as Element;
+ expect(e.outerHtml, '<span>a && b</span>');
+ });
+
+ test('Escaping attributes', () {
+ var e = parseFragment('<div class="a<b>">').firstChild as Element;
+ expect(e.outerHtml, '<div class="a<b>"></div>');
+ e = parseFragment('<div class=\'a"b\'>').firstChild as Element;
+ expect(e.outerHtml, '<div class="a"b"></div>');
+ });
+
+ test('Escaping non-breaking space', () {
+ final text = '<span>foO\u00A0bar</span>';
+ expect(text.codeUnitAt(text.indexOf('O') + 1), 0xA0);
+ final e = parseFragment(text).firstChild as Element;
+ expect(e.outerHtml, '<span>foO bar</span>');
+ });
+
+ test('Newline after <pre>', () {
+ var e = parseFragment('<pre>\n\nsome text</span>').firstChild as Element;
+ expect((e.firstChild as Text).data, '\nsome text');
+ expect(e.outerHtml, '<pre>\n\nsome text</pre>');
+
+ e = parseFragment('<pre>\nsome text</span>').firstChild as Element;
+ expect((e.firstChild as Text).data, 'some text');
+ expect(e.outerHtml, '<pre>some text</pre>');
+ });
+
+ test('xml namespaces', () {
+ // Note: this is a nonsensical example, but it triggers the behavior
+ // we're looking for with attribute names in foreign content.
+ final doc = parse('''
+ <body>
+ <svg>
+ <desc xlink:type="simple"
+ xlink:href="http://example.com/logo.png"
+ xlink:show="new"></desc>
+ ''');
+ final n = doc.querySelector('desc')!;
+ final keys = n.attributes.keys.toList();
+ expect(
+ keys.first,
+ isA<AttributeName>()
+ .having((n) => n.prefix, 'prefix', 'xlink')
+ .having((n) => n.namespace, 'namespace',
+ 'http://www.w3.org/1999/xlink')
+ .having((n) => n.name, 'name', 'type'));
+
+ expect(
+ n.outerHtml,
+ '<desc xlink:type="simple" '
+ 'xlink:href="http://example.com/logo.png" xlink:show="new"></desc>');
+ });
+ });
+
+ test('error printing without spans', () {
+ final parser = HtmlParser('foo');
+ final doc = parser.parse();
+ expect(doc.body!.innerHtml, 'foo');
+ expect(parser.errors.length, 1);
+ 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'
+ ' ╵');
+ });
+
+ test('Element.text', () {
+ final doc = parseFragment('<div>foo<div>bar</div>baz</div>');
+ final e = doc.firstChild!;
+ final text = e.firstChild!;
+ expect((text as Text).data, 'foo');
+ expect(e.text, 'foobarbaz');
+
+ e.text = 'FOO';
+ expect(e.nodes.length, 1);
+ expect(e.firstChild, isNot(text), reason: 'should create a new tree');
+ expect((e.firstChild as Text).data, 'FOO');
+ expect(e.text, 'FOO');
+ });
+
+ test('Text.text', () {
+ final doc = parseFragment('<div>foo<div>bar</div>baz</div>');
+ final e = doc.firstChild!;
+ final text = e.firstChild as Text;
+ expect(text.data, 'foo');
+ expect(text.text, 'foo');
+
+ text.text = 'FOO';
+ expect(text.data, 'FOO');
+ expect(e.text, 'FOObarbaz');
+ expect(text.text, 'FOO');
+ });
+
+ test('Comment.text', () {
+ final doc = parseFragment('<div><!--foo-->bar</div>');
+ final e = doc.firstChild!;
+ final c = e.firstChild!;
+ expect((c as Comment).data, 'foo');
+ expect(c.text, 'foo');
+ expect(e.text, 'bar');
+
+ c.text = 'qux';
+ expect(c.data, 'qux');
+ expect(c.text, 'qux');
+ expect(e.text, 'bar');
+ });
+
+ test('foreignObject end tag', () {
+ final p = HtmlParser('''
+<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg"
+ version="1.1">
+ <foreignObject width="320px" height="200px">
+ <x-flow></x-flow>
+ </foreignObject>
+</svg>''');
+ final doc = p.parseFragment();
+ expect(p.errors, isEmpty);
+ final svg = doc.querySelector('svg')!;
+ expect(svg.children[0].children[0].localName, 'x-flow');
+ });
+
+ group('Encoding pre-parser', () {
+ String? getEncoding(String s) => EncodingParser(s.codeUnits).getEncoding();
+
+ test('gets encoding from meta charset', () {
+ expect(getEncoding('<meta charset="utf-16">'), 'utf-16');
+ });
+
+ test('gets encoding from meta in head', () {
+ expect(getEncoding('<head><meta charset="utf-16">'), 'utf-16');
+ });
+
+ test('skips comments', () {
+ expect(getEncoding('<!--comment--><meta charset="utf-16">'), 'utf-16');
+ });
+
+ test('stops if no match', () {
+ // missing closing tag
+ expect(getEncoding('<meta charset="utf-16"'), null);
+ });
+
+ test('ignores whitespace', () {
+ expect(getEncoding(' <meta charset="utf-16">'), 'utf-16');
+ });
+
+ test('parses content attr', () {
+ expect(
+ getEncoding(
+ '<meta http-equiv="content-type" content="text/html; charset=UTF-8">'),
+ null);
+ });
+ });
+}
+
+void _testElementSpans() {
+ void assertSpan(SourceSpan span, int offset, int end, String text) {
+ expect(span, isNotNull);
+ expect(span.start.offset, offset);
+ expect(span.end.offset, end);
+ expect(span.text, text);
+ }
+
+ group('element spans', () {
+ test('html and body', () {
+ final text = '<html><body>123</body></html>';
+ final doc = parse(text, generateSpans: true);
+ {
+ final elem = doc.querySelector('html')!;
+ assertSpan(elem.sourceSpan!, 0, 6, '<html>');
+ assertSpan(elem.endSourceSpan!, 22, 29, '</html>');
+ }
+ {
+ final elem = doc.querySelector('body')!;
+ assertSpan(elem.sourceSpan!, 6, 12, '<body>');
+ assertSpan(elem.endSourceSpan!, 15, 22, '</body>');
+ }
+ });
+
+ test('normal', () {
+ final text = '<div><element><span></span></element></div>';
+ final doc = parse(text, generateSpans: true);
+ final elem = doc.querySelector('element')!;
+ assertSpan(elem.sourceSpan!, 5, 14, '<element>');
+ assertSpan(elem.endSourceSpan!, 27, 37, '</element>');
+ });
+
+ test('block', () {
+ final text = '<div>123</div>';
+ final doc = parse(text, generateSpans: true);
+ final elem = doc.querySelector('div')!;
+ assertSpan(elem.sourceSpan!, 0, 5, '<div>');
+ assertSpan(elem.endSourceSpan!, 8, 14, '</div>');
+ });
+
+ test('form', () {
+ final text = '<form>123</form>';
+ final doc = parse(text, generateSpans: true);
+ final elem = doc.querySelector('form')!;
+ assertSpan(elem.sourceSpan!, 0, 6, '<form>');
+ assertSpan(elem.endSourceSpan!, 9, 16, '</form>');
+ });
+
+ test('p explicit end', () {
+ final text = '<p>123</p>';
+ final doc = parse(text, generateSpans: true);
+ final elem = doc.querySelector('p')!;
+ assertSpan(elem.sourceSpan!, 0, 3, '<p>');
+ assertSpan(elem.endSourceSpan!, 6, 10, '</p>');
+ });
+
+ test('p implicit end', () {
+ final text = '<div><p>123<p>456</div>';
+ final doc = parse(text, generateSpans: true);
+ final elem = doc.querySelector('p')!;
+ assertSpan(elem.sourceSpan!, 5, 8, '<p>');
+ expect(elem.endSourceSpan, isNull);
+ });
+
+ test('li', () {
+ final text = '<li>123</li>';
+ final doc = parse(text, generateSpans: true);
+ final elem = doc.querySelector('li')!;
+ assertSpan(elem.sourceSpan!, 0, 4, '<li>');
+ assertSpan(elem.endSourceSpan!, 7, 12, '</li>');
+ });
+
+ test('heading', () {
+ final text = '<h1>123</h1>';
+ final doc = parse(text, generateSpans: true);
+ final elem = doc.querySelector('h1')!;
+ assertSpan(elem.sourceSpan!, 0, 4, '<h1>');
+ assertSpan(elem.endSourceSpan!, 7, 12, '</h1>');
+ });
+
+ test('formatting', () {
+ final text = '<b>123</b>';
+ final doc = parse(text, generateSpans: true);
+ final elem = doc.querySelector('b')!;
+ assertSpan(elem.sourceSpan!, 0, 3, '<b>');
+ assertSpan(elem.endSourceSpan!, 6, 10, '</b>');
+ });
+
+ test('table tbody', () {
+ final text = '<table><tbody> </tbody></table>';
+ final doc = parse(text, generateSpans: true);
+ {
+ final elem = doc.querySelector('tbody')!;
+ assertSpan(elem.sourceSpan!, 7, 14, '<tbody>');
+ assertSpan(elem.endSourceSpan!, 16, 24, '</tbody>');
+ }
+ });
+
+ test('table tr td', () {
+ final text = '<table><tr><td>123</td></tr></table>';
+ final doc = parse(text, generateSpans: true);
+ {
+ final elem = doc.querySelector('table')!;
+ assertSpan(elem.sourceSpan!, 0, 7, '<table>');
+ assertSpan(elem.endSourceSpan!, 28, 36, '</table>');
+ }
+ {
+ final elem = doc.querySelector('tr')!;
+ assertSpan(elem.sourceSpan!, 7, 11, '<tr>');
+ assertSpan(elem.endSourceSpan!, 23, 28, '</tr>');
+ }
+ {
+ final elem = doc.querySelector('td')!;
+ assertSpan(elem.sourceSpan!, 11, 15, '<td>');
+ assertSpan(elem.endSourceSpan!, 18, 23, '</td>');
+ }
+ });
+
+ test('select optgroup option', () {
+ final text = '<select><optgroup><option>123</option></optgroup></select>';
+ final doc = parse(text, generateSpans: true);
+ {
+ final elem = doc.querySelector('select')!;
+ assertSpan(elem.sourceSpan!, 0, 8, '<select>');
+ assertSpan(elem.endSourceSpan!, 49, 58, '</select>');
+ }
+ {
+ final elem = doc.querySelector('optgroup')!;
+ assertSpan(elem.sourceSpan!, 8, 18, '<optgroup>');
+ assertSpan(elem.endSourceSpan!, 38, 49, '</optgroup>');
+ }
+ {
+ final elem = doc.querySelector('option')!;
+ assertSpan(elem.sourceSpan!, 18, 26, '<option>');
+ assertSpan(elem.endSourceSpan!, 29, 38, '</option>');
+ }
+ });
+ });
+}
diff --git a/pkgs/html/test/parser_test.dart b/pkgs/html/test/parser_test.dart
new file mode 100644
index 0000000..952c1b2
--- /dev/null
+++ b/pkgs/html/test/parser_test.dart
@@ -0,0 +1,109 @@
+@TestOn('vm')
+library;
+
+import 'dart:convert';
+
+import 'package:html/dom.dart';
+import 'package:html/parser.dart';
+import 'package:path/path.dart' as pathos;
+import 'package:test/test.dart';
+
+import 'support.dart';
+
+// Run the parse error checks
+// TODO(jmesserly): presumably we want this on by default?
+final checkParseErrors = false;
+
+String namespaceHtml(String expected) {
+ // TODO(jmesserly): this is a workaround for http://dartbug.com/2979
+ // We can't do regex replace directly =\
+ // final namespaceExpected = new RegExp(@"^(\s*)<(\S+)>", multiLine: true);
+ // return expected.replaceAll(namespaceExpected, @"$1<html $2>");
+ final namespaceExpected = RegExp(r'^(\|\s*)<(\S+)>');
+ final lines = expected.split('\n');
+ for (var i = 0; i < lines.length; i++) {
+ final match = namespaceExpected.firstMatch(lines[i]);
+ if (match != null) {
+ lines[i] = '${match[1]}<html ${match[2]}>';
+ }
+ }
+ return lines.join('\n');
+}
+
+void runParserTest(
+ String groupName,
+ String? innerHTML,
+ String? input,
+ String? expected,
+ List<String>? errors,
+ TreeBuilderFactory treeCtor,
+ bool namespaceHTMLElements) {
+ // XXX - move this out into the setup function
+ // concatenate all consecutive character tokens into a single token
+ final builder = treeCtor(namespaceHTMLElements);
+ final parser = HtmlParser(input, tree: builder);
+
+ Node document;
+ if (innerHTML != null) {
+ document = parser.parseFragment(innerHTML);
+ } else {
+ document = parser.parse();
+ }
+
+ final output = testSerializer(document);
+
+ if (namespaceHTMLElements) {
+ expected = namespaceHtml(expected!);
+ }
+
+ expect(output, equals(expected),
+ reason:
+ '\n\nInput:\n$input\n\nExpected:\n$expected\n\nReceived:\n$output');
+
+ if (checkParseErrors) {
+ expect(parser.errors.length, equals(errors!.length),
+ reason: '\n\nInput:\n$input\n\nExpected errors (${errors.length}):\n'
+ "${errors.join('\n')}\n\n"
+ 'Actual errors (${parser.errors.length}):\n'
+ "${parser.errors.map((e) => '$e').join('\n')}");
+ }
+}
+
+void main() async {
+ await for (var path in dataFiles('tree-construction')) {
+ if (!path.endsWith('.dat')) continue;
+
+ final tests = TestData(path, 'data');
+ final testName = pathos.basenameWithoutExtension(path);
+
+ group(testName, () {
+ for (var testData in tests) {
+ final input = testData['data'];
+ final errorString = testData['errors'];
+ final errors = errorString?.split('\n');
+ final innerHTML = testData['document-fragment'];
+ final expected = testData['document'];
+
+ for (var treeCtor in treeTypes!.values) {
+ for (var namespaceHTMLElements in const [false, true]) {
+ test(_nameFor(input!), () {
+ runParserTest(testName, innerHTML, input, expected, errors,
+ treeCtor, namespaceHTMLElements);
+ });
+ }
+ }
+ }
+ });
+ }
+}
+
+/// Extract the name for the test based on the test input data.
+dynamic _nameFor(String input) {
+ // Using jsonDecode to unescape other unicode characters
+ final escapeQuote = input
+ .replaceAll(RegExp('\\\\.'), '_')
+ .replaceAll(RegExp('\u0000'), '_')
+ .replaceAll('"', '\\"')
+ .replaceAll(RegExp('[\n\r\t]'), '_');
+ return jsonDecode('"$escapeQuote"');
+}
diff --git a/pkgs/html/test/query_selector_test.dart b/pkgs/html/test/query_selector_test.dart
new file mode 100644
index 0000000..495b0f1
--- /dev/null
+++ b/pkgs/html/test/query_selector_test.dart
@@ -0,0 +1,24 @@
+import 'package:html/dom.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('querySelector descendant', () {
+ late Element el;
+
+ setUp(() {
+ el = Element.html('<div id="a" class="a"><div id="b"></div></div>');
+ });
+
+ test('descendant of type', () {
+ expect(el.querySelector('div div')?.id, 'b');
+ });
+
+ test('descendant of class', () {
+ expect(el.querySelector('.a div')?.id, 'b');
+ });
+
+ test('descendant of type and class', () {
+ expect(el.querySelector('div.a div')?.id, 'b');
+ });
+ });
+}
diff --git a/pkgs/html/test/selectors/LICENSE b/pkgs/html/test/selectors/LICENSE
new file mode 100644
index 0000000..0dc0193
--- /dev/null
+++ b/pkgs/html/test/selectors/LICENSE
@@ -0,0 +1,9 @@
+Contents of this folder are ported from
+https://github.com/w3c/web-platform-tests/tree/master/selectors-api
+
+It is based on commit 96c61ac7c21f7f37526d1c03c4c6087734524130
+
+The original code is covered by the dual-licensing approach described in:
+
+ http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html
+
\ No newline at end of file
diff --git a/pkgs/html/test/selectors/README b/pkgs/html/test/selectors/README
new file mode 100644
index 0000000..d6e3852
--- /dev/null
+++ b/pkgs/html/test/selectors/README
@@ -0,0 +1,9 @@
+Contents of this folder are ported from
+https://github.com/w3c/web-platform-tests/tree/master/selectors-api
+
+It is based on commit 96c61ac7c21f7f37526d1c03c4c6087734524130
+
+It is used to test the implementation of querySelector/querySelectorAll.
+
+Not all code was ported; as the current implementation doesn't yet support
+all features of those methods.
diff --git a/pkgs/html/test/selectors/level1-content.html b/pkgs/html/test/selectors/level1-content.html
new file mode 100644
index 0000000..b90d918
--- /dev/null
+++ b/pkgs/html/test/selectors/level1-content.html
@@ -0,0 +1,377 @@
+<!DOCTYPE html>
+<html id="html" lang="en">
+<head id="head">
+ <meta id="meta" charset="UTF-8">
+ <title id="title">Selectors-API Test Suite: HTML with Selectors Level 2 using TestHarness: Test Document</title>
+
+ <!-- Links for :link and :visited pseudo-class test -->
+ <link id="pseudo-link-link1" href="">
+ <link id="pseudo-link-link2" href="http://example.org/">
+ <link id="pseudo-link-link3">
+ <style>
+ @namespace ns "http://www.w3.org/1999/xhtml";
+ /* Declare the namespace prefix used in tests. This declaration should not be used by the API. */
+ </style>
+</head>
+<body id="body">
+<div id="root">
+ <div id="target"></div>
+
+ <div id="universal">
+ <p id="universal-p1">Universal selector tests inside element with <code id="universal-code1">id="universal"</code>.</p>
+ <hr id="universal-hr1">
+ <pre id="universal-pre1">Some preformatted text with some <span id="universal-span1">embedded code</span></pre>
+ <p id="universal-p2">This is a normal link: <a id="universal-a1" href="http://www.w3.org/">W3C</a></p>
+ <address id="universal-address1">Some more nested elements <code id="universal-code2"><a href="#" id="universal-a2">code hyperlink</a></code></address>
+ </div>
+
+ <div id="attr-presence">
+ <div class="attr-presence-div1" id="attr-presence-div1" align="center"></div>
+ <div class="attr-presence-div2" id="attr-presence-div2" align=""></div>
+ <div class="attr-presence-div3" id="attr-presence-div3" valign="center"></div>
+ <div class="attr-presence-div4" id="attr-presence-div4" alignv="center"></div>
+ <p id="attr-presence-p1"><a id="attr-presence-a1" tItLe=""></a><span id="attr-presence-span1" TITLE="attr-presence-span1"></span></p>
+ <pre id="attr-presence-pre1" data-attr-presence="pre1"></pre>
+ <blockquote id="attr-presence-blockquote1" data-attr-presence="blockquote1"></blockquote>
+ <ul id="attr-presence-ul1" data-中文=""></ul>
+
+ <select id="attr-presence-select1">
+ <option id="attr-presence-select1-option1">A</option>
+ <option id="attr-presence-select1-option2">B</option>
+ <option id="attr-presence-select1-option3">C</option>
+ <option id="attr-presence-select1-option4">D</option>
+ </select>
+ <select id="attr-presence-select2">
+ <option id="attr-presence-select2-option1">A</option>
+ <option id="attr-presence-select2-option2">B</option>
+ <option id="attr-presence-select2-option3">C</option>
+ <option id="attr-presence-select2-option4" selected="selected">D</option>
+ </select>
+ <select id="attr-presence-select3" multiple="multiple">
+ <option id="attr-presence-select3-option1">A</option>
+ <option id="attr-presence-select3-option2" selected="">B</option>
+ <option id="attr-presence-select3-option3" selected="selected">C</option>
+ <option id="attr-presence-select3-option4">D</option>
+ </select>
+ </div>
+
+ <div id="attr-value">
+ <div id="attr-value-div1" align="center"></div>
+ <div id="attr-value-div2" align=""></div>
+ <div id="attr-value-div3" data-attr-value="é"></div>
+ <div id="attr-value-div4" data-attr-value_foo="é"></div>
+
+ <form id="attr-value-form1">
+ <input id="attr-value-input1" type="text">
+ <input id="attr-value-input2" type="password">
+ <input id="attr-value-input3" type="hidden">
+ <input id="attr-value-input4" type="radio">
+ <input id="attr-value-input5" type="checkbox">
+ <input id="attr-value-input6" type="radio">
+ <input id="attr-value-input7" type="text">
+ <input id="attr-value-input8" type="hidden">
+ <input id="attr-value-input9" type="radio">
+ </form>
+
+ <div id="attr-value-div5" data-attr-value="中文"></div>
+ </div>
+
+ <div id="attr-whitespace">
+ <div id="attr-whitespace-div1" class="foo div1 bar"></div>
+ <div id="attr-whitespace-div2" class=""></div>
+ <div id="attr-whitespace-div3" class="foo div3 bar"></div>
+
+ <div id="attr-whitespace-div4" data-attr-whitespace="foo é bar"></div>
+ <div id="attr-whitespace-div5" data-attr-whitespace_foo="é foo"></div>
+
+ <a id="attr-whitespace-a1" rel="next bookmark"></a>
+ <a id="attr-whitespace-a2" rel="tag nofollow"></a>
+ <a id="attr-whitespace-a3" rel="tag bookmark"></a>
+ <a id="attr-whitespace-a4" rel="book mark"></a> <!-- Intentional space in "book mark" -->
+ <a id="attr-whitespace-a5" rel="nofollow"></a>
+ <a id="attr-whitespace-a6" rev="bookmark nofollow"></a>
+ <a id="attr-whitespace-a7" rel="prev next tag alternate nofollow author help icon noreferrer prefetch search stylesheet tag"></a>
+
+ <p id="attr-whitespace-p1" title="Chinese 中文 characters"></p>
+ </div>
+
+ <div id="attr-hyphen">
+ <div id="attr-hyphen-div1"></div>
+ <div id="attr-hyphen-div2" lang="fr"></div>
+ <div id="attr-hyphen-div3" lang="en-AU"></div>
+ <div id="attr-hyphen-div4" lang="es"></div>
+ </div>
+
+ <div id="attr-begins">
+ <a id="attr-begins-a1" href="http://www.example.org"></a>
+ <a id="attr-begins-a2" href="http://example.org/"></a>
+ <a id="attr-begins-a3" href="http://www.example.com/"></a>
+
+ <div id="attr-begins-div1" lang="fr"></div>
+ <div id="attr-begins-div2" lang="en-AU"></div>
+ <div id="attr-begins-div3" lang="es"></div>
+ <div id="attr-begins-div4" lang="en-US"></div>
+ <div id="attr-begins-div5" lang="en"></div>
+
+ <p id="attr-begins-p1" class=" apple"></p> <!-- Intentional space in class value " apple". -->
+ </div>
+
+ <div id="attr-ends">
+ <a id="attr-ends-a1" href="http://www.example.org"></a>
+ <a id="attr-ends-a2" href="http://example.org/"></a>
+ <a id="attr-ends-a3" href="http://www.example.org"></a>
+
+ <div id="attr-ends-div1" lang="fr"></div>
+ <div id="attr-ends-div2" lang="de-CH"></div>
+ <div id="attr-ends-div3" lang="es"></div>
+ <div id="attr-ends-div4" lang="fr-CH"></div>
+
+ <p id="attr-ends-p1" class="apple "></p> <!-- Intentional space in class value "apple ". -->
+ </div>
+
+ <div id="attr-contains">
+ <a id="attr-contains-a1" href="http://www.example.org"></a>
+ <a id="attr-contains-a2" href="http://example.org/"></a>
+ <a id="attr-contains-a3" href="http://www.example.com/"></a>
+
+ <div id="attr-contains-div1" lang="fr"></div>
+ <div id="attr-contains-div2" lang="en-AU"></div>
+ <div id="attr-contains-div3" lang="de-CH"></div>
+ <div id="attr-contains-div4" lang="es"></div>
+ <div id="attr-contains-div5" lang="fr-CH"></div>
+ <div id="attr-contains-div6" lang="en-US"></div>
+
+ <p id="attr-contains-p1" class=" apple banana orange "></p>
+ </div>
+
+ <div id="pseudo-nth">
+ <table id="pseudo-nth-table1">
+ <tr id="pseudo-nth-tr1"><td id="pseudo-nth-td1"></td><td id="pseudo-nth-td2"></td><td id="pseudo-nth-td3"></td><td id="pseudo-nth-td4"></td><td id="pseudo-nth--td5"></td><td id="pseudo-nth-td6"></td></tr>
+ <tr id="pseudo-nth-tr2"><td id="pseudo-nth-td7"></td><td id="pseudo-nth-td8"></td><td id="pseudo-nth-td9"></td><td id="pseudo-nth-td10"></td><td id="pseudo-nth-td11"></td><td id="pseudo-nth-td12"></td></tr>
+ <tr id="pseudo-nth-tr3"><td id="pseudo-nth-td13"></td><td id="pseudo-nth-td14"></td><td id="pseudo-nth-td15"></td><td id="pseudo-nth-td16"></td><td id="pseudo-nth-td17"></td><td id="pseudo-nth-td18"></td></tr>
+ </table>
+
+ <ol id="pseudo-nth-ol1">
+ <li id="pseudo-nth-li1"></li>
+ <li id="pseudo-nth-li2"></li>
+ <li id="pseudo-nth-li3"></li>
+ <li id="pseudo-nth-li4"></li>
+ <li id="pseudo-nth-li5"></li>
+ <li id="pseudo-nth-li6"></li>
+ <li id="pseudo-nth-li7"></li>
+ <li id="pseudo-nth-li8"></li>
+ <li id="pseudo-nth-li9"></li>
+ <li id="pseudo-nth-li10"></li>
+ <li id="pseudo-nth-li11"></li>
+ <li id="pseudo-nth-li12"></li>
+ </ol>
+
+ <p id="pseudo-nth-p1">
+ <span id="pseudo-nth-span1">span1</span>
+ <em id="pseudo-nth-em1">em1</em>
+ <!-- comment node-->
+ <em id="pseudo-nth-em2">em2</em>
+ <span id="pseudo-nth-span2">span2</span>
+ <strong id="pseudo-nth-strong1">strong1</strong>
+ <em id="pseudo-nth-em3">em3</em>
+ <span id="pseudo-nth-span3">span3</span>
+ <span id="pseudo-nth-span4">span4</span>
+ <strong id="pseudo-nth-strong2">strong2</strong>
+ <em id="pseudo-nth-em4">em4</em>
+ </p>
+ </div>
+
+ <div id="pseudo-first-child">
+ <div id="pseudo-first-child-div1"></div>
+ <div id="pseudo-first-child-div2"></div>
+ <div id="pseudo-first-child-div3"></div>
+
+ <p id="pseudo-first-child-p1"><span id="pseudo-first-child-span1"></span><span id="pseudo-first-child-span2"></span></p>
+ <p id="pseudo-first-child-p2"><span id="pseudo-first-child-span3"></span><span id="pseudo-first-child-span4"></span></p>
+ <p id="pseudo-first-child-p3"><span id="pseudo-first-child-span5"></span><span id="pseudo-first-child-span6"></span></p>
+ </div>
+
+ <div id="pseudo-last-child">
+ <p id="pseudo-last-child-p1"><span id="pseudo-last-child-span1"></span><span id="pseudo-last-child-span2"></span></p>
+ <p id="pseudo-last-child-p2"><span id="pseudo-last-child-span3"></span><span id="pseudo-last-child-span4"></span></p>
+ <p id="pseudo-last-child-p3"><span id="pseudo-last-child-span5"></span><span id="pseudo-last-child-span6"></span></p>
+
+ <div id="pseudo-last-child-div1"></div>
+ <div id="pseudo-last-child-div2"></div>
+ <div id="pseudo-last-child-div3"></div>
+ </div>
+
+ <div id="pseudo-only">
+ <p id="pseudo-only-p1">
+ <span id="pseudo-only-span1"></span>
+ </p>
+ <p id="pseudo-only-p2">
+ <span id="pseudo-only-span2"></span>
+ <span id="pseudo-only-span3"></span>
+ </p>
+ <p id="pseudo-only-p3">
+ <span id="pseudo-only-span4"></span>
+ <em id="pseudo-only-em1"></em>
+ <span id="pseudo-only-span5"></span>
+ </p>
+ </div>>
+
+ <div id="pseudo-empty">
+ <p id="pseudo-empty-p1"></p>
+ <p id="pseudo-empty-p2"><!-- comment node --></p>
+ <p id="pseudo-empty-p3"> </p>
+ <p id="pseudo-empty-p4">Text node</p>
+ <p id="pseudo-empty-p5"><span id="pseudo-empty-span1"></span></p>
+ </div>
+
+ <div id="pseudo-link">
+ <a id="pseudo-link-a1" href="">with href</a>
+ <a id="pseudo-link-a2" href="http://example.org/">with href</a>
+ <a id="pseudo-link-a3">without href</a>
+ <map name="pseudo-link-map1" id="pseudo-link-map1">
+ <area id="pseudo-link-area1" href="">
+ <area id="pseudo-link-area2">
+ </map>
+ </div>
+
+ <div id="pseudo-lang">
+ <div id="pseudo-lang-div1"></div>
+ <div id="pseudo-lang-div2" lang="fr"></div>
+ <div id="pseudo-lang-div3" lang="en-AU"></div>
+ <div id="pseudo-lang-div4" lang="es"></div>
+ </div>
+
+ <div id="pseudo-ui">
+ <input id="pseudo-ui-input1" type="text">
+ <input id="pseudo-ui-input2" type="password">
+ <input id="pseudo-ui-input3" type="radio">
+ <input id="pseudo-ui-input4" type="radio" checked="checked">
+ <input id="pseudo-ui-input5" type="checkbox">
+ <input id="pseudo-ui-input6" type="checkbox" checked="checked">
+ <input id="pseudo-ui-input7" type="submit">
+ <input id="pseudo-ui-input8" type="button">
+ <input id="pseudo-ui-input9" type="hidden">
+ <textarea id="pseudo-ui-textarea1"></textarea>
+ <button id="pseudo-ui-button1">Enabled</button>
+
+ <input id="pseudo-ui-input10" disabled="disabled" type="text">
+ <input id="pseudo-ui-input11" disabled="disabled" type="password">
+ <input id="pseudo-ui-input12" disabled="disabled" type="radio">
+ <input id="pseudo-ui-input13" disabled="disabled" type="radio" checked="checked">
+ <input id="pseudo-ui-input14" disabled="disabled" type="checkbox">
+ <input id="pseudo-ui-input15" disabled="disabled" type="checkbox" checked="checked">
+ <input id="pseudo-ui-input16" disabled="disabled" type="submit">
+ <input id="pseudo-ui-input17" disabled="disabled" type="button">
+ <input id="pseudo-ui-input18" disabled="disabled" type="hidden">
+ <textarea id="pseudo-ui-textarea2" disabled="disabled"></textarea>
+ <button id="pseudo-ui-button2" disabled="disabled">Disabled</button>
+ </div>
+
+ <div id="not">
+ <div id="not-div1"></div>
+ <div id="not-div2"></div>
+ <div id="not-div3"></div>
+
+ <p id="not-p1"><span id="not-span1"></span><em id="not-em1"></em></p>
+ <p id="not-p2"><span id="not-span2"></span><em id="not-em2"></em></p>
+ <p id="not-p3"><span id="not-span3"></span><em id="not-em3"></em></p>
+ </div>
+
+ <div id="pseudo-element">All pseudo-element tests</div>
+
+ <div id="class">
+ <p id="class-p1" class="foo class-p bar"></p>
+ <p id="class-p2" class="class-p foo bar"></p>
+ <p id="class-p3" class="foo bar class-p"></p>
+
+ <!-- All permutations of the classes should match -->
+ <div id="class-div1" class="apple orange banana"></div>
+ <div id="class-div2" class="apple banana orange"></div>
+ <p id="class-p4" class="orange apple banana"></p>
+ <div id="class-div3" class="orange banana apple"></div>
+ <p id="class-p6" class="banana apple orange"></p>
+ <div id="class-div4" class="banana orange apple"></div>
+ <div id="class-div5" class="apple orange"></div>
+ <div id="class-div6" class="apple banana"></div>
+ <div id="class-div7" class="orange banana"></div>
+
+ <span id="class-span1" class="台北Táiběi 台北"></span>
+ <span id="class-span2" class="台北"></span>
+
+ <span id="class-span3" class="foo:bar"></span>
+ <span id="class-span4" class="test.foo[5]bar"></span>
+ </div>
+
+ <div id="id">
+ <div id="id-div1"></div>
+ <div id="id-div2"></div>
+
+ <ul id="id-ul1">
+ <li id="id-li-duplicate"></li>
+ <li id="id-li-duplicate"></li>
+ <li id="id-li-duplicate"></li>
+ <li id="id-li-duplicate"></li>
+ </ul>
+
+ <span id="台北Táiběi"></span>
+ <span id="台北"></span>
+
+ <span id="#foo:bar"></span>
+ <span id="test.foo[5]bar"></span>
+ </div>
+
+ <div id="descendant">
+ <div id="descendant-div1" class="descendant-div1">
+ <div id="descendant-div2" class="descendant-div2">
+ <div id="descendant-div3" class="descendant-div3">
+ </div>
+ </div>
+ </div>
+ <div id="descendant-div4" class="descendant-div4"></div>
+ </div>
+
+ <div id="child">
+ <div id="child-div1" class="child-div1">
+ <div id="child-div2" class="child-div2">
+ <div id="child-div3" class="child-div3">
+ </div>
+ </div>
+ </div>
+ <div id="child-div4" class="child-div4"></div>
+ </div>
+
+ <div id="adjacent">
+ <div id="adjacent-div1" class="adjacent-div1"></div>
+ <div id="adjacent-div2" class="adjacent-div2">
+ <div id="adjacent-div3" class="adjacent-div3"></div>
+ </div>
+ <div id="adjacent-div4" class="adjacent-div4">
+ <p id="adjacent-p1" class="adjacent-p1"></p>
+ <div id="adjacent-div5" class="adjacent-div5"></div>
+ </div>
+ <div id="adjacent-div6" class="adjacent-div6"></div>
+ <p id="adjacent-p2" class="adjacent-p2"></p>
+ <p id="adjacent-p3" class="adjacent-p3"></p>
+ </div>
+
+ <div id="sibling">
+ <div id="sibling-div1" class="sibling-div"></div>
+ <div id="sibling-div2" class="sibling-div">
+ <div id="sibling-div3" class="sibling-div"></div>
+ </div>
+ <div id="sibling-div4" class="sibling-div">
+ <p id="sibling-p1" class="sibling-p"></p>
+ <div id="sibling-div5" class="sibling-div"></div>
+ </div>
+ <div id="sibling-div6" class="sibling-div"></div>
+ <p id="sibling-p2" class="sibling-p"></p>
+ <p id="sibling-p3" class="sibling-p"></p>
+ </div>
+
+ <div id="group">
+ <em id="group-em1"></em>
+ <strong id="group-strong1"></strong>
+ </div>
+</div>
+</body>
+</html>
diff --git a/pkgs/html/test/selectors/level1_baseline_test.dart b/pkgs/html/test/selectors/level1_baseline_test.dart
new file mode 100644
index 0000000..d5614e9
--- /dev/null
+++ b/pkgs/html/test/selectors/level1_baseline_test.dart
@@ -0,0 +1,126 @@
+/// Test for the Selectors API ported from
+/// <https://github.com/w3c/web-platform-tests/tree/master/selectors-api>
+///
+/// Note, unlike the original we don't operate in-browser on a DOM loaded into
+/// an iframe, but instead operate over a parsed DOM.
+
+@TestOn('vm')
+library;
+
+import 'dart:io';
+
+import 'package:html/dom.dart';
+import 'package:html/parser.dart';
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+
+import '../support.dart';
+import 'level1_lib.dart';
+import 'selectors.dart';
+
+Future<Document> testContentDocument() async {
+ final testPath =
+ p.join(await testDirectory, 'selectors', 'level1-content.html');
+ return parse(File(testPath).readAsStringSync());
+}
+
+int testType = testQsaBaseline; // Only run baseline tests.
+String docType = 'html'; // Only run tests suitable for HTML
+
+void main() async {
+ /*
+ * This test suite tests Selectors API methods in 4 different contexts:
+ * 1. Document node
+ * 2. In-document Element node
+ * 3. Detached Element node (an element with no parent, not in the document)
+ * 4. Document Fragment node
+ *
+ * For each context, the following tests are run:
+ *
+ * The interface check tests ensure that each type of node exposes the Selectors API methods
+ *
+ * The special selector tests verify the result of passing special values for the selector parameter,
+ * to ensure that the correct WebIDL processing is performed, such as stringification of null and
+ * undefined and missing parameter. The universal selector is also tested here, rather than with the
+ * rest of ordinary selectors for practical reasons.
+ *
+ * The static list verification tests ensure that the node lists returned by the method remain unchanged
+ * due to subsequent document modication, and that a new list is generated each time the method is
+ * invoked based on the current state of the document.
+ *
+ * The invalid selector tests ensure that SyntaxError is thrown for invalid forms of selectors
+ *
+ * The valid selector tests check the result from querying many different types of selectors, with a
+ * list of expected elements. This checks that querySelector() always returns the first result from
+ * querySelectorAll(), and that all matching elements are correctly returned in tree-order. The tests
+ * can be limited by specifying the test types to run, using the testType variable. The constants for this
+ * can be found in selectors.js.
+ *
+ * All the selectors tested for both the valid and invalid selector tests are found in selectors.js.
+ * See comments in that file for documentation of the format used.
+ *
+ * The level1-lib.js file contains all the common test functions for running each of the aforementioned tests
+ */
+
+ // Prepare the nodes for testing
+ //doc = frame.contentDocument; // Document Node tests
+ doc = await testContentDocument();
+
+ final element = doc.getElementById('root')!; // In-document Element Node tests
+
+ //Setup the namespace tests
+ setupSpecialElements(element);
+
+ final outOfScope = element
+ .clone(true); // Append this to the body before running the in-document
+ // Element tests, but after running the Document tests. This
+ // tests that no elements that are not descendants of element
+ // are selected.
+
+ traverse(outOfScope, (elem) {
+ // Annotate each element as being a clone; used for verifying
+ elem.attributes['data-clone'] =
+ ''; // that none of these elements ever match.
+ });
+
+ final detached = element.clone(true); // Detached Element Node tests
+
+ final fragment = doc.createDocumentFragment(); // Fragment Node tests
+ fragment.append(element.clone(true));
+
+ // Setup Tests
+ runSpecialSelectorTests('Document', SelectorAdaptor.document(doc));
+ runSpecialSelectorTests(
+ 'Detached Element', SelectorAdaptor.element(detached));
+ runSpecialSelectorTests('Fragment', SelectorAdaptor.fragment(fragment));
+ runSpecialSelectorTests(
+ 'In-document Element', SelectorAdaptor.element(element));
+
+ verifyStaticList('Document', SelectorAdaptor.document(doc));
+ verifyStaticList('Detached Element', SelectorAdaptor.element(detached));
+ verifyStaticList('Fragment', SelectorAdaptor.fragment(fragment));
+ verifyStaticList('In-document Element', SelectorAdaptor.element(element));
+
+ // TODO(jmesserly): fix negative tests
+ //runInvalidSelectorTest('Document', doc, invalidSelectors);
+ //runInvalidSelectorTest('Detached Element', detached, invalidSelectors);
+ //runInvalidSelectorTest('Fragment', fragment, invalidSelectors);
+ //runInvalidSelectorTest('In-document Element', element, invalidSelectors);
+
+ runValidSelectorTest('Document', SelectorAdaptor.document(doc),
+ validSelectors, testType, docType);
+ runValidSelectorTest('Detached Element', SelectorAdaptor.element(detached),
+ validSelectors, testType, docType);
+ runValidSelectorTest('Fragment', SelectorAdaptor.fragment(fragment),
+ validSelectors, testType, docType);
+
+ group('out of scope', () {
+ setUp(() {
+ doc.body!.append(outOfScope); // Append before in-document Element tests.
+ // None of these elements should match
+ });
+ tearDown(outOfScope.remove);
+ runValidSelectorTest('In-document Element',
+ SelectorAdaptor.element(element), validSelectors, testType, docType);
+ });
+}
diff --git a/pkgs/html/test/selectors/level1_lib.dart b/pkgs/html/test/selectors/level1_lib.dart
new file mode 100644
index 0000000..d77aac7
--- /dev/null
+++ b/pkgs/html/test/selectors/level1_lib.dart
@@ -0,0 +1,347 @@
+/// Test for the Selectors API ported from
+/// <https://github.com/w3c/web-platform-tests/tree/master/selectors-api>
+///
+/// Note: tried to make minimal changes possible here. Hence some oddities such
+/// as [runTest] arguments having a different order, long lines, etc.
+///
+/// As usual with ports: being faithful to the original style is more important
+/// than other style goals, as it reduces friction to integrating changes
+/// from upstream.
+library;
+
+import 'package:html/dom.dart';
+import 'package:test/test.dart' as unittest;
+
+late Document doc;
+
+// Create and append special elements that cannot be created correctly with HTML
+// markup alone.
+void setupSpecialElements(Element parent) {
+ // Setup null and undefined tests
+ parent.append(doc.createElement('null'));
+ parent.append(doc.createElement('undefined'));
+
+ // Setup namespace tests
+ final anyNS = doc.createElement('div');
+ final noNS = doc.createElement('div');
+ anyNS.id = 'any-namespace';
+ noNS.id = 'no-namespace';
+
+ var div = [
+ doc.createElement('div'),
+ doc.createElementNS('http://www.w3.org/1999/xhtml', 'div'),
+ doc.createElementNS('', 'div'),
+ doc.createElementNS('http://www.example.org/ns', 'div')
+ ];
+
+ div[0].id = 'any-namespace-div1';
+ div[1].id = 'any-namespace-div2';
+ div[2].attributes['id'] =
+ 'any-namespace-div3'; // Non-HTML elements can't use .id property
+ div[3].attributes['id'] = 'any-namespace-div4';
+
+ for (var i = 0; i < div.length; i++) {
+ anyNS.append(div[i]);
+ }
+
+ div = [
+ doc.createElement('div'),
+ doc.createElementNS('http://www.w3.org/1999/xhtml', 'div'),
+ doc.createElementNS('', 'div'),
+ doc.createElementNS('http://www.example.org/ns', 'div')
+ ];
+
+ div[0].id = 'no-namespace-div1';
+ div[1].id = 'no-namespace-div2';
+ div[2].attributes['id'] =
+ 'no-namespace-div3'; // Non-HTML elements can't use .id property
+ div[3].attributes['id'] = 'no-namespace-div4';
+
+ for (var i = 0; i < div.length; i++) {
+ noNS.append(div[i]);
+ }
+
+ parent.append(anyNS);
+ parent.append(noNS);
+}
+
+/// Verify that the NodeList returned by querySelectorAll is static and and that
+/// a new list is created after each call. A static list should not be affected
+/// by subsequent changes to the DOM.
+void verifyStaticList(String type, SelectorAdaptor root) {
+ List<Element> pre;
+ List<Element> post;
+ late int preLength;
+
+ runTest(() {
+ pre = root.querySelectorAll('div');
+ preLength = pre.length;
+
+ final div = doc.createElement('div');
+ root.isDocument ? root.body!.append(div) : root.append(div);
+
+ assertEquals(
+ pre.length, preLength, 'The length of the NodeList should not change.');
+ }, '$type: static NodeList');
+
+ runTest(() {
+ post = root.querySelectorAll('div');
+ assertEquals(post.length, preLength + 1,
+ 'The length of the new NodeList should be 1 more than the previous list.');
+ }, '$type: new NodeList');
+}
+
+/// Verify handling of special values for the selector parameter, including
+/// stringification of null and undefined, and the handling of the empty string.
+void runSpecialSelectorTests(String type, SelectorAdaptor root) {
+ // Dart note: changed these tests because we don't have auto conversion to
+ // String like JavaScript does.
+ runTest(() {
+ // 1
+ assertEquals(root.querySelectorAll('null').length, 1,
+ "This should find one element with the tag name 'NULL'.");
+ }, '$type.querySelectorAll null');
+
+ runTest(() {
+ // 2
+ assertEquals(root.querySelectorAll('undefined').length, 1,
+ "This should find one element with the tag name 'UNDEFINED'.");
+ }, '$type.querySelectorAll undefined');
+
+ // runTest(() {
+ // // 3
+ // assertThrows((e) => e is NoSuchMethodError, () {
+ // root.querySelectorAll();
+ // }, 'This should throw a TypeError.');
+ // }, '$type.querySelectorAll no parameter');
+
+ runTest(() {
+ // 4
+ final elm = root.querySelector('null');
+ assertNotEquals(elm, null, 'This should find an element.');
+ // TODO(jmesserly): change "localName" back to "tagName" once implemented.
+ assertEquals(elm!.localName?.toUpperCase(), 'NULL',
+ "The tag name should be 'NULL'.");
+ }, '$type.querySelector null');
+
+ runTest(() {
+ // 5
+ final elm = root.querySelector('undefined');
+ assertNotEquals(elm, 'undefined', 'This should find an element.');
+ // TODO(jmesserly): change "localName" back to "tagName" once implemented.
+ assertEquals(elm!.localName?.toUpperCase(), 'UNDEFINED',
+ "The tag name should be 'UNDEFINED'.");
+ }, '$type.querySelector undefined');
+
+ // runTest(() {
+ // // 6
+ // assertThrows((e) => e is NoSuchMethodError, () {
+ // root.querySelector();
+ // }, 'This should throw a TypeError.');
+ // }, '$type.querySelector no parameter');
+
+ runTest(() {
+ // 7
+ final result = root.querySelectorAll('*');
+ var i = 0;
+ traverse(root.asNode!, (elem) {
+ if (!identical(elem, root.asNode)) {
+ assertEquals(
+ elem, result[i], 'The result in index $i should be in tree order.');
+ i++;
+ }
+ });
+ }, '$type.querySelectorAll tree order');
+}
+
+/// Tests containing this string fail for an unknown reason
+final _failureName = 'matching custom data-* attribute with';
+
+String? _getSkip(String name) {
+ if (name.contains(_failureName)) {
+ return 'Tests related to `$_failureName` fail for an unknown reason.';
+ }
+ return null;
+}
+
+/*
+ * Execute queries with the specified valid selectors for both querySelector() and querySelectorAll()
+ * Only run these tests when results are expected. Don't run for syntax error tests.
+ */
+void runValidSelectorTest(String type, SelectorAdaptor root,
+ List<Map<String, dynamic>> selectors, int testType, String docType) {
+ var nodeType = '';
+ switch (root.nodeType) {
+ case Node.DOCUMENT_NODE:
+ nodeType = 'document';
+ break;
+ case Node.ELEMENT_NODE:
+ nodeType = root.parentNode != null ? 'element' : 'detached';
+ break;
+ case Node.DOCUMENT_FRAGMENT_NODE:
+ nodeType = 'fragment';
+ break;
+ default:
+ throw StateError('Reached unreachable code path.');
+ }
+
+ for (var i = 0; i < selectors.length; i++) {
+ final s = selectors[i];
+ final n = s['name'] as String;
+ final skip = _getSkip(n);
+ final q = s['selector'] as String;
+ final e = s['expect'] as List?;
+
+ final exclude = s['exclude'];
+
+ if ((exclude is! List ||
+ (!exclude.contains(nodeType) && !exclude.contains(docType))) &&
+ ((s['testType'] as int) & testType != 0)) {
+ //console.log("Running tests " + nodeType + ": " + s["testType"] + "&" + testType + "=" + (s["testType"] & testType) + ": " + JSON.stringify(s))
+ late List<Element> foundall;
+ Element? found;
+
+ runTest(() {
+ foundall = root.querySelectorAll(q);
+ assertNotEquals(foundall, null, 'The method should not return null.');
+ assertEquals(foundall.length, e!.length,
+ 'The method should return the expected number of matches.');
+
+ for (var i = 0; i < e.length; i++) {
+ assertNotEquals(
+ foundall[i], null, 'The item in index $i should not be null.');
+ assertEquals(foundall[i].attributes['id'], e[i],
+ 'The item in index $i should have the expected ID.');
+ assertFalse(foundall[i].attributes.containsKey('data-clone'),
+ 'This should not be a cloned element.');
+ }
+ }, '$type.querySelectorAll: $n:$q', skip: skip);
+
+ runTest(() {
+ found = root.querySelector(q);
+
+ if (e!.isNotEmpty) {
+ assertNotEquals(found, null, 'The method should return a match.');
+ assertEquals(found!.attributes['id'], e[0],
+ 'The method should return the first match.');
+ assertEquals(found, foundall[0],
+ 'The result should match the first item from querySelectorAll.');
+ assertFalse(found!.attributes.containsKey('data-clone'),
+ 'This should not be annotated as a cloned element.');
+ } else {
+ assertEquals(found, null, 'The method should not match anything.');
+ }
+ }, '$type.querySelector: $n : $q', skip: skip);
+ } else {
+ //console.log("Excluding for " + nodeType + ": " + s["testType"] + "&" + testType + "=" + (s["testType"] & testType) + ": " + JSON.stringify(s))
+ }
+ }
+}
+
+/*
+ * Execute queries with the specified invalid selectors for both querySelector() and querySelectorAll()
+ * Only run these tests when errors are expected. Don't run for valid selector tests.
+ */
+void runInvalidSelectorTest(
+ String type, SelectorAdaptor root, List<Map<String, dynamic>> selectors) {
+ for (var i = 0; i < selectors.length; i++) {
+ final s = selectors[i];
+ final n = s['name'] as String;
+ final q = s['selector'] as String;
+
+ // Dart note: FormatException seems a reasonable mapping of SyntaxError
+ runTest(() {
+ assertThrows((Object e) => e is FormatException, () {
+ root.querySelector(q);
+ });
+ }, '$type.querySelector: $n:$q');
+
+ runTest(() {
+ assertThrows((Object e) => e is FormatException, () {
+ root.querySelectorAll(q);
+ });
+ }, '$type.querySelectorAll: $n:$q');
+ }
+}
+
+void traverse(Node elem, void Function(Node) fn) {
+ if (elem.nodeType == Node.ELEMENT_NODE) {
+ fn(elem);
+ }
+
+ // Dart note: changed this since our DOM API doesn't support nextNode yet.
+ for (var node in elem.nodes) {
+ traverse(node, fn);
+ }
+}
+
+void runTest(dynamic Function() body, String name, {String? skip}) =>
+ unittest.test(name, body, skip: skip);
+
+void assertTrue(bool value, String reason) =>
+ unittest.expect(value, unittest.isTrue, reason: reason);
+
+void assertFalse(bool value, String reason) =>
+ unittest.expect(value, unittest.isFalse, reason: reason);
+
+void assertEquals(dynamic x, dynamic y, String reason) =>
+ unittest.expect(x, y, reason: reason);
+
+void assertNotEquals(dynamic x, dynamic y, String reason) =>
+ unittest.expect(x, unittest.isNot(y), reason: reason);
+
+void assertThrows(Object exception, void Function() body, [String? reason]) =>
+ unittest.expect(body, unittest.throwsA(exception), reason: reason);
+
+/// Used for testing.
+///
+/// This class delegates to one of three different kinds of objects. They share
+/// methods with similar signatures but do not share a type hierarchy.
+/// Previously these methods were invoked through `dynamic`.
+class SelectorAdaptor {
+ final Document? document;
+ final Element? element;
+ final DocumentFragment? fragment;
+
+ SelectorAdaptor.document(this.document)
+ : element = null,
+ fragment = null;
+
+ SelectorAdaptor.element(this.element)
+ : document = null,
+ fragment = null;
+
+ SelectorAdaptor.fragment(this.fragment)
+ : document = null,
+ element = null;
+
+ bool get isDocument => document != null;
+
+ Element? get body => document?.body;
+
+ Node? get asNode => document ?? element ?? fragment;
+
+ int get nodeType => asNode!.nodeType;
+
+ Node? get parentNode => asNode!.parentNode;
+
+ void append(Node node) {
+ asNode!.append(node);
+ }
+
+ Element? querySelector(String selector) {
+ if (document != null) return document!.querySelector(selector);
+ if (element != null) return element!.querySelector(selector);
+ if (fragment != null) return fragment!.querySelector(selector);
+
+ throw StateError('unsupported');
+ }
+
+ List<Element> querySelectorAll(String selector) {
+ if (document != null) return document!.querySelectorAll(selector);
+ if (element != null) return element!.querySelectorAll(selector);
+ if (fragment != null) return fragment!.querySelectorAll(selector);
+
+ throw StateError('unsupported');
+ }
+}
diff --git a/pkgs/html/test/selectors/selectors.dart b/pkgs/html/test/selectors/selectors.dart
new file mode 100644
index 0000000..81e69c6
--- /dev/null
+++ b/pkgs/html/test/selectors/selectors.dart
@@ -0,0 +1,1875 @@
+/// Test for the Selectors API ported from
+/// <https://github.com/w3c/web-platform-tests/tree/master/selectors-api>
+library;
+
+// Bit-mapped flags to indicate which tests the selector is suitable for
+final int testQsaBaseline =
+ 0x01; // querySelector() and querySelectorAll() baseline tests
+final int testQsaAdditional =
+ 0x02; // querySelector() and querySelectorAll() additional tests
+final int testFindBaseline =
+ 0x04; // find() and findAll() baseline tests, may be unsuitable for querySelector[All]
+final int testFindAdditional =
+ 0x08; // find() and findAll() additional tests, may be unsuitable for querySelector[All]
+final int testMatchBaseline = 0x10; // matches() baseline tests
+int testMatchAdditional = 0x20; // matches() additional tests
+
+/*
+ * All of these invalid selectors should result in a SyntaxError being thrown by the APIs.
+ *
+ * name: A descriptive name of the selector being tested
+ * selector: The selector to test
+ */
+final invalidSelectors = [
+ {'name': 'Empty String', 'selector': ''},
+ {'name': 'Invalid character', 'selector': '['},
+ {'name': 'Invalid character', 'selector': ']'},
+ {'name': 'Invalid character', 'selector': '('},
+ {'name': 'Invalid character', 'selector': ')'},
+ {'name': 'Invalid character', 'selector': '{'},
+ {'name': 'Invalid character', 'selector': '}'},
+ {'name': 'Invalid character', 'selector': '<'},
+ {'name': 'Invalid character', 'selector': '>'},
+ {'name': 'Invalid ID', 'selector': '#'},
+ {'name': 'Invalid group of selectors', 'selector': 'div,'},
+ {'name': 'Invalid class', 'selector': '.'},
+ {'name': 'Invalid class', 'selector': '.5cm'},
+ {'name': 'Invalid class', 'selector': '..test'},
+ {'name': 'Invalid class', 'selector': '.foo..quux'},
+ {'name': 'Invalid class', 'selector': '.bar.'},
+ {'name': 'Invalid combinator', 'selector': 'div & address, p'},
+ {'name': 'Invalid combinator', 'selector': 'div >> address, p'},
+ {'name': 'Invalid combinator', 'selector': 'div ++ address, p'},
+ {'name': 'Invalid combinator', 'selector': 'div ~~ address, p'},
+ {'name': 'Invalid [att=value] selector', 'selector': '[*=test]'},
+ {'name': 'Invalid [att=value] selector', 'selector': '[*|*=test]'},
+ {
+ 'name': 'Invalid [att=value] selector',
+ 'selector': '[class= space unquoted ]'
+ },
+ {'name': 'Unknown pseudo-class', 'selector': 'div:example'},
+ {'name': 'Unknown pseudo-class', 'selector': ':example'},
+ {'name': 'Unknown pseudo-element', 'selector': 'div::example'},
+ {'name': 'Unknown pseudo-element', 'selector': '::example'},
+ {'name': 'Invalid pseudo-element', 'selector': ':::before'},
+ {'name': 'Undeclared namespace', 'selector': 'ns|div'},
+ {'name': 'Undeclared namespace', 'selector': ':not(ns|div)'},
+ {'name': 'Invalid namespace', 'selector': '^|div'},
+ {'name': 'Invalid namespace', 'selector': '\$|div'}
+];
+
+/*
+ * All of these should be valid selectors, expected to match zero or more elements in the document.
+ * None should throw any errors.
+ *
+ * name: A descriptive name of the selector being tested
+ * selector: The selector to test
+ * 'expect': A list of IDs of the elements expected to be matched. List must be given in tree order.
+ * 'exclude': An array of contexts to exclude from testing. The valid values are:
+ * ["document", "element", "fragment", "detached", "html", "xhtml"]
+ * The "html" and "xhtml" values represent the type of document being queried. These are useful
+ * for tests that are affected by differences between HTML and XML, such as case sensitivity.
+ * 'level': An integer indicating the CSS or Selectors level in which the selector being tested was introduced.
+ * 'testType': A bit-mapped flag indicating the type of test.
+ *
+ * Note: Interactive pseudo-classes (:active :hover and :focus) have not been tested in this test suite.
+ */
+final List<Map<String, dynamic>> validSelectors = [
+ // Type Selector
+ {
+ 'name': 'Type selector, matching html element',
+ 'selector': 'html',
+ 'expect': ['html'],
+ 'exclude': ['element', 'fragment', 'detached'],
+ 'level': 1,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name': 'Type selector, matching html element',
+ 'selector': 'html',
+ 'expect': <String>[] /*no matches*/,
+ 'exclude': ['document'],
+ 'level': 1,
+ 'testType': testQsaBaseline
+ },
+ {
+ 'name': 'Type selector, matching body element',
+ 'selector': 'body',
+ 'expect': ['body'],
+ 'exclude': ['element', 'fragment', 'detached'],
+ 'level': 1,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name': 'Type selector, matching body element',
+ 'selector': 'body',
+ 'expect': <String>[] /*no matches*/,
+ 'exclude': ['document'],
+ 'level': 1,
+ 'testType': testQsaBaseline
+ },
+
+ // Universal Selector
+ // Testing "*" for entire an entire context node is handled separately.
+ {
+ 'name':
+ 'Universal selector, matching all children of element with specified ID',
+ 'selector': '#universal>*',
+ 'expect': [
+ 'universal-p1',
+ 'universal-hr1',
+ 'universal-pre1',
+ 'universal-p2',
+ 'universal-address1'
+ ],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Universal selector, matching all grandchildren of element with specified ID',
+ 'selector': '#universal>*>*',
+ 'expect': [
+ 'universal-code1',
+ 'universal-span1',
+ 'universal-a1',
+ 'universal-code2'
+ ],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Universal selector, matching all children of empty element with specified ID',
+ 'selector': '#empty>*',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 2,
+ 'testType': testQsaBaseline
+ },
+ {
+ 'name':
+ 'Universal selector, matching all descendants of element with specified ID',
+ 'selector': '#universal *',
+ 'expect': [
+ 'universal-p1',
+ 'universal-code1',
+ 'universal-hr1',
+ 'universal-pre1',
+ 'universal-span1',
+ 'universal-p2',
+ 'universal-a1',
+ 'universal-address1',
+ 'universal-code2',
+ 'universal-a2'
+ ],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+
+ // Attribute Selectors
+ // - presence [att]
+ {
+ 'name': 'Attribute presence selector, matching align attribute with value',
+ 'selector': '.attr-presence-div1[align]',
+ 'expect': ['attr-presence-div1'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute presence selector, matching align attribute with empty value',
+ 'selector': '.attr-presence-div2[align]',
+ 'expect': ['attr-presence-div2'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute presence selector, matching title attribute, case insensitivity',
+ 'selector': '#attr-presence [TiTlE]',
+ 'expect': ['attr-presence-a1', 'attr-presence-span1'],
+ 'exclude': ['xhtml'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute presence selector, not matching title attribute, case sensitivity',
+ 'selector': '#attr-presence [TiTlE]',
+ 'expect': <String>[],
+ 'exclude': ['html'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name': 'Attribute presence selector, matching custom data-* attribute',
+ 'selector': '[data-attr-presence]',
+ 'expect': ['attr-presence-pre1', 'attr-presence-blockquote1'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute presence selector, not matching attribute with similar name',
+ 'selector': '.attr-presence-div3[align], .attr-presence-div4[align]',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 2,
+ 'testType': testQsaBaseline
+ },
+ {
+ 'name':
+ 'Attribute presence selector, matching attribute with non-ASCII characters',
+ 'selector': 'ul[data-中文]',
+ 'expect': ['attr-presence-ul1'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute presence selector, not matching default option without selected attribute',
+ 'selector': '#attr-presence-select1 option[selected]',
+ 'expect': <String>[] /* no matches */,
+ 'level': 2,
+ 'testType': testQsaBaseline
+ },
+ {
+ 'name':
+ 'Attribute presence selector, matching option with selected attribute',
+ 'selector': '#attr-presence-select2 option[selected]',
+ 'expect': ['attr-presence-select2-option4'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute presence selector, matching multiple options with selected attributes',
+ 'selector': '#attr-presence-select3 option[selected]',
+ 'expect': [
+ 'attr-presence-select3-option2',
+ 'attr-presence-select3-option3'
+ ],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+
+ // - value [att=val]
+ {
+ 'name': 'Attribute value selector, matching align attribute with value',
+ 'selector': '#attr-value [align="center"]',
+ 'expect': ['attr-value-div1'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute value selector, matching align attribute with empty value',
+ 'selector': '#attr-value [align=""]',
+ 'expect': ['attr-value-div2'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute value selector, not matching align attribute with partial value',
+ 'selector': '#attr-value [align="c"]',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 2,
+ 'testType': testQsaBaseline
+ },
+ {
+ 'name':
+ 'Attribute value selector, not matching align attribute with incorrect value',
+ 'selector': '#attr-value [align="centera"]',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 2,
+ 'testType': testQsaBaseline
+ },
+ {
+ 'name':
+ 'Attribute value selector, matching custom data-* attribute with unicode escaped value',
+ 'selector': '[data-attr-value="\\e9"]',
+ 'expect': ['attr-value-div3'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute value selector, matching custom data-* attribute with escaped character',
+ 'selector': '[data-attr-value_foo="\\e9"]',
+ 'expect': ['attr-value-div4'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute value selector with single-quoted value, matching multiple inputs with type attributes',
+ 'selector':
+ "#attr-value input[type='hidden'],#attr-value input[type='radio']",
+ 'expect': [
+ 'attr-value-input3',
+ 'attr-value-input4',
+ 'attr-value-input6',
+ 'attr-value-input8',
+ 'attr-value-input9'
+ ],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute value selector with double-quoted value, matching multiple inputs with type attributes',
+ 'selector':
+ "#attr-value input[type=\"hidden\"],#attr-value input[type='radio']",
+ 'expect': [
+ 'attr-value-input3',
+ 'attr-value-input4',
+ 'attr-value-input6',
+ 'attr-value-input8',
+ 'attr-value-input9'
+ ],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute value selector with unquoted value, matching multiple inputs with type attributes',
+ 'selector': '#attr-value input[type=hidden],#attr-value input[type=radio]',
+ 'expect': [
+ 'attr-value-input3',
+ 'attr-value-input4',
+ 'attr-value-input6',
+ 'attr-value-input8',
+ 'attr-value-input9'
+ ],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute value selector, matching attribute with value using non-ASCII characters',
+ 'selector': '[data-attr-value=中文]',
+ 'expect': ['attr-value-div5'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+
+ // - whitespace-separated list [att~=val]
+ {
+ 'name':
+ 'Attribute whitespace-separated list selector, matching class attribute with value',
+ 'selector': '#attr-whitespace [class~="div1"]',
+ 'expect': ['attr-whitespace-div1'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute whitespace-separated list selector, not matching class attribute with empty value',
+ 'selector': '#attr-whitespace [class~=""]',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 2,
+ 'testType': testQsaBaseline
+ },
+ {
+ 'name':
+ 'Attribute whitespace-separated list selector, not matching class attribute with partial value',
+ 'selector': '[data-attr-whitespace~="div"]',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 2,
+ 'testType': testQsaBaseline
+ },
+ {
+ 'name':
+ 'Attribute whitespace-separated list selector, matching custom data-* attribute with unicode escaped value',
+ 'selector': '[data-attr-whitespace~="\\0000e9"]',
+ 'expect': ['attr-whitespace-div4'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute whitespace-separated list selector, matching custom data-* attribute with escaped character',
+ 'selector': '[data-attr-whitespace_foo~="\\e9"]',
+ 'expect': ['attr-whitespace-div5'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute whitespace-separated list selector with single-quoted value, matching multiple links with rel attributes',
+ 'selector':
+ "#attr-whitespace a[rel~='bookmark'], #attr-whitespace a[rel~='nofollow']",
+ 'expect': [
+ 'attr-whitespace-a1',
+ 'attr-whitespace-a2',
+ 'attr-whitespace-a3',
+ 'attr-whitespace-a5',
+ 'attr-whitespace-a7'
+ ],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute whitespace-separated list selector with double-quoted value, matching multiple links with rel attributes',
+ 'selector':
+ "#attr-whitespace a[rel~=\"bookmark\"],#attr-whitespace a[rel~='nofollow']",
+ 'expect': [
+ 'attr-whitespace-a1',
+ 'attr-whitespace-a2',
+ 'attr-whitespace-a3',
+ 'attr-whitespace-a5',
+ 'attr-whitespace-a7'
+ ],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute whitespace-separated list selector with unquoted value, matching multiple links with rel attributes',
+ 'selector':
+ '#attr-whitespace a[rel~=bookmark], #attr-whitespace a[rel~=nofollow]',
+ 'expect': [
+ 'attr-whitespace-a1',
+ 'attr-whitespace-a2',
+ 'attr-whitespace-a3',
+ 'attr-whitespace-a5',
+ 'attr-whitespace-a7'
+ ],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute whitespace-separated list selector with double-quoted value, not matching value with space',
+ 'selector': '#attr-whitespace a[rel~="book mark"]',
+ 'expect': <String>[] /* no matches */,
+ 'level': 2,
+ 'testType': testQsaBaseline
+ },
+ {
+ 'name':
+ 'Attribute whitespace-separated list selector, matching title attribute with value using non-ASCII characters',
+ 'selector': '#attr-whitespace [title~=中文]',
+ 'expect': ['attr-whitespace-p1'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+
+ // - hyphen-separated list [att|=val]
+ {
+ 'name':
+ 'Attribute hyphen-separated list selector, not matching unspecified lang attribute',
+ 'selector': '#attr-hyphen-div1[lang|="en"]',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 2,
+ 'testType': testQsaBaseline
+ },
+ {
+ 'name':
+ 'Attribute hyphen-separated list selector, matching lang attribute with exact value',
+ 'selector': '#attr-hyphen-div2[lang|="fr"]',
+ 'expect': ['attr-hyphen-div2'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute hyphen-separated list selector, matching lang attribute with partial value',
+ 'selector': '#attr-hyphen-div3[lang|="en"]',
+ 'expect': ['attr-hyphen-div3'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute hyphen-separated list selector, not matching incorrect value',
+ 'selector': '#attr-hyphen-div4[lang|="es-AR"]',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 2,
+ 'testType': testQsaBaseline
+ },
+
+ // - substring begins-with [att^=val] (Level 3)
+ {
+ 'name':
+ 'Attribute begins with selector, matching href attributes beginning with specified substring',
+ 'selector': '#attr-begins a[href^="http://www"]',
+ 'expect': ['attr-begins-a1', 'attr-begins-a3'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute begins with selector, matching lang attributes beginning with specified substring, ',
+ 'selector': '#attr-begins [lang^="en-"]',
+ 'expect': ['attr-begins-div2', 'attr-begins-div4'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute begins with selector, not matching class attribute not beginning with specified substring',
+ 'selector': '#attr-begins [class^=apple]',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 3,
+ 'testType': testQsaAdditional
+ },
+ {
+ 'name':
+ 'Attribute begins with selector with single-quoted value, matching class attribute beginning with specified substring',
+ 'selector': "#attr-begins [class^=' apple']",
+ 'expect': ['attr-begins-p1'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute begins with selector with double-quoted value, matching class attribute beginning with specified substring',
+ 'selector': '#attr-begins [class^=" apple"]',
+ 'expect': ['attr-begins-p1'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute begins with selector with unquoted value, not matching class attribute not beginning with specified substring',
+ 'selector': '#attr-begins [class^= apple]',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 3,
+ 'testType': testQsaAdditional
+ },
+
+ // - substring ends-with [att\$=val] (Level 3)
+ {
+ 'name':
+ 'Attribute ends with selector, matching href attributes ending with specified substring',
+ 'selector': '#attr-ends a[href\$=".org"]',
+ 'expect': ['attr-ends-a1', 'attr-ends-a3'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute ends with selector, matching lang attributes ending with specified substring, ',
+ 'selector': '#attr-ends [lang\$="-CH"]',
+ 'expect': ['attr-ends-div2', 'attr-ends-div4'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute ends with selector, not matching class attribute not ending with specified substring',
+ 'selector': '#attr-ends [class\$=apple]',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 3,
+ 'testType': testQsaAdditional
+ },
+ {
+ 'name':
+ 'Attribute ends with selector with single-quoted value, matching class attribute ending with specified substring',
+ 'selector': "#attr-ends [class\$='apple ']",
+ 'expect': ['attr-ends-p1'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute ends with selector with double-quoted value, matching class attribute ending with specified substring',
+ 'selector': '#attr-ends [class\$="apple "]',
+ 'expect': ['attr-ends-p1'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute ends with selector with unquoted value, not matching class attribute not ending with specified substring',
+ 'selector': '#attr-ends [class\$=apple ]',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 3,
+ 'testType': testQsaAdditional
+ },
+
+ // - substring contains [att*=val] (Level 3)
+ {
+ 'name':
+ 'Attribute contains selector, matching href attributes beginning with specified substring',
+ 'selector': '#attr-contains a[href*="http://www"]',
+ 'expect': ['attr-contains-a1', 'attr-contains-a3'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute contains selector, matching href attributes ending with specified substring',
+ 'selector': '#attr-contains a[href*=".org"]',
+ 'expect': ['attr-contains-a1', 'attr-contains-a2'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute contains selector, matching href attributes containing specified substring',
+ 'selector': '#attr-contains a[href*=".example."]',
+ 'expect': ['attr-contains-a1', 'attr-contains-a3'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute contains selector, matching lang attributes beginning with specified substring, ',
+ 'selector': '#attr-contains [lang*="en-"]',
+ 'expect': ['attr-contains-div2', 'attr-contains-div6'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute contains selector, matching lang attributes ending with specified substring, ',
+ 'selector': '#attr-contains [lang*="-CH"]',
+ 'expect': ['attr-contains-div3', 'attr-contains-div5'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute contains selector with single-quoted value, matching class attribute beginning with specified substring',
+ 'selector': "#attr-contains [class*=' apple']",
+ 'expect': ['attr-contains-p1'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute contains selector with single-quoted value, matching class attribute ending with specified substring',
+ 'selector': "#attr-contains [class*='orange ']",
+ 'expect': ['attr-contains-p1'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute contains selector with single-quoted value, matching class attribute containing specified substring',
+ 'selector': "#attr-contains [class*='ple banana ora']",
+ 'expect': ['attr-contains-p1'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute contains selector with double-quoted value, matching class attribute beginning with specified substring',
+ 'selector': '#attr-contains [class*=" apple"]',
+ 'expect': ['attr-contains-p1'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute contains selector with double-quoted value, matching class attribute ending with specified substring',
+ 'selector': '#attr-contains [class*="orange "]',
+ 'expect': ['attr-contains-p1'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute contains selector with double-quoted value, matching class attribute containing specified substring',
+ 'selector': '#attr-contains [class*="ple banana ora"]',
+ 'expect': ['attr-contains-p1'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute contains selector with unquoted value, matching class attribute beginning with specified substring',
+ 'selector': '#attr-contains [class*= apple]',
+ 'expect': ['attr-contains-p1'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute contains selector with unquoted value, matching class attribute ending with specified substring',
+ 'selector': '#attr-contains [class*=orange ]',
+ 'expect': ['attr-contains-p1'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Attribute contains selector with unquoted value, matching class attribute containing specified substring',
+ 'selector': '#attr-contains [class*= banana ]',
+ 'expect': ['attr-contains-p1'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+
+ // Pseudo-classes
+ // - :root (Level 3)
+ {
+ 'name': ':root pseudo-class selector, matching document root element',
+ 'selector': ':root',
+ 'expect': ['html'],
+ 'exclude': ['element', 'fragment', 'detached'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name': ':root pseudo-class selector, not matching document root element',
+ 'selector': ':root',
+ 'expect': <String>[] /*no matches*/,
+ 'exclude': ['document'],
+ 'level': 3,
+ 'testType': testQsaAdditional
+ },
+
+ // - :nth-child(n) (Level 3)
+ {
+ 'name': ':nth-child selector, matching the third child element',
+ 'selector': '#pseudo-nth-table1 :nth-child(3)',
+ 'expect': [
+ 'pseudo-nth-td3',
+ 'pseudo-nth-td9',
+ 'pseudo-nth-tr3',
+ 'pseudo-nth-td15'
+ ],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name': ':nth-child selector, matching every third child element',
+ 'selector': '#pseudo-nth li:nth-child(3n)',
+ 'expect': [
+ 'pseudo-nth-li3',
+ 'pseudo-nth-li6',
+ 'pseudo-nth-li9',
+ 'pseudo-nth-li12'
+ ],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ ':nth-child selector, matching every second child element, starting from the fourth',
+ 'selector': '#pseudo-nth li:nth-child(2n+4)',
+ 'expect': [
+ 'pseudo-nth-li4',
+ 'pseudo-nth-li6',
+ 'pseudo-nth-li8',
+ 'pseudo-nth-li10',
+ 'pseudo-nth-li12'
+ ],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ ':nth-child selector, matching every fourth child element, starting from the third',
+ 'selector': '#pseudo-nth-p1 :nth-child(4n-1)',
+ 'expect': ['pseudo-nth-em2', 'pseudo-nth-span3'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+
+ // - :nth-last-child (Level 3)
+ {
+ 'name': ':nth-last-child selector, matching the third last child element',
+ 'selector': '#pseudo-nth-table1 :nth-last-child(3)',
+ 'expect': [
+ 'pseudo-nth-tr1',
+ 'pseudo-nth-td4',
+ 'pseudo-nth-td10',
+ 'pseudo-nth-td16'
+ ],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ ':nth-last-child selector, matching every third child element from the end',
+ 'selector': '#pseudo-nth li:nth-last-child(3n)',
+ 'expect': [
+ 'pseudo-nth-li1',
+ 'pseudo-nth-li4',
+ 'pseudo-nth-li7',
+ 'pseudo-nth-li10'
+ ],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ ':nth-last-child selector, matching every second child element from the end, starting from the fourth last',
+ 'selector': '#pseudo-nth li:nth-last-child(2n+4)',
+ 'expect': [
+ 'pseudo-nth-li1',
+ 'pseudo-nth-li3',
+ 'pseudo-nth-li5',
+ 'pseudo-nth-li7',
+ 'pseudo-nth-li9'
+ ],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ ':nth-last-child selector, matching every fourth element from the end, starting from the third last',
+ 'selector': '#pseudo-nth-p1 :nth-last-child(4n-1)',
+ 'expect': ['pseudo-nth-span2', 'pseudo-nth-span4'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+
+ // - :nth-of-type(n) (Level 3)
+ {
+ 'name': ':nth-of-type selector, matching the third em element',
+ 'selector': '#pseudo-nth-p1 em:nth-of-type(3)',
+ 'expect': ['pseudo-nth-em3'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ ':nth-of-type selector, matching every second element of their type',
+ 'selector': '#pseudo-nth-p1 :nth-of-type(2n)',
+ 'expect': [
+ 'pseudo-nth-em2',
+ 'pseudo-nth-span2',
+ 'pseudo-nth-span4',
+ 'pseudo-nth-strong2',
+ 'pseudo-nth-em4'
+ ],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ ':nth-of-type selector, matching every second elemetn of their type, starting from the first',
+ 'selector': '#pseudo-nth-p1 span:nth-of-type(2n-1)',
+ 'expect': ['pseudo-nth-span1', 'pseudo-nth-span3'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+
+ // - :nth-last-of-type(n) (Level 3)
+ {
+ 'name': ':nth-last-of-type selector, matching the thrid last em element',
+ 'selector': '#pseudo-nth-p1 em:nth-last-of-type(3)',
+ 'expect': ['pseudo-nth-em2'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ ':nth-last-of-type selector, matching every second last element of their type',
+ 'selector': '#pseudo-nth-p1 :nth-last-of-type(2n)',
+ 'expect': [
+ 'pseudo-nth-span1',
+ 'pseudo-nth-em1',
+ 'pseudo-nth-strong1',
+ 'pseudo-nth-em3',
+ 'pseudo-nth-span3'
+ ],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ ':nth-last-of-type selector, matching every second last element of their type, starting from the last',
+ 'selector': '#pseudo-nth-p1 span:nth-last-of-type(2n-1)',
+ 'expect': ['pseudo-nth-span2', 'pseudo-nth-span4'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+
+ // - :first-of-type (Level 3)
+ {
+ 'name': ':first-of-type selector, matching the first em element',
+ 'selector': '#pseudo-nth-p1 em:first-of-type',
+ 'expect': ['pseudo-nth-em1'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ ':first-of-type selector, matching the first of every type of element',
+ 'selector': '#pseudo-nth-p1 :first-of-type',
+ 'expect': ['pseudo-nth-span1', 'pseudo-nth-em1', 'pseudo-nth-strong1'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ ':first-of-type selector, matching the first td element in each table row',
+ 'selector': '#pseudo-nth-table1 tr :first-of-type',
+ 'expect': ['pseudo-nth-td1', 'pseudo-nth-td7', 'pseudo-nth-td13'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+
+ // - :last-of-type (Level 3)
+ {
+ 'name': ':last-of-type selector, matching the last em elemnet',
+ 'selector': '#pseudo-nth-p1 em:last-of-type',
+ 'expect': ['pseudo-nth-em4'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ ':last-of-type selector, matching the last of every type of element',
+ 'selector': '#pseudo-nth-p1 :last-of-type',
+ 'expect': ['pseudo-nth-span4', 'pseudo-nth-strong2', 'pseudo-nth-em4'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ ':last-of-type selector, matching the last td element in each table row',
+ 'selector': '#pseudo-nth-table1 tr :last-of-type',
+ 'expect': ['pseudo-nth-td6', 'pseudo-nth-td12', 'pseudo-nth-td18'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+
+ // - :first-child
+ {
+ 'name':
+ ':first-child pseudo-class selector, matching first child div element',
+ 'selector': '#pseudo-first-child div:first-child',
+ 'expect': ['pseudo-first-child-div1'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ ":first-child pseudo-class selector, doesn't match non-first-child elements",
+ 'selector':
+ '.pseudo-first-child-div2:first-child, .pseudo-first-child-div3:first-child',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 2,
+ 'testType': testQsaBaseline
+ },
+ {
+ 'name':
+ ':first-child pseudo-class selector, matching first-child of multiple elements',
+ 'selector': '#pseudo-first-child span:first-child',
+ 'expect': [
+ 'pseudo-first-child-span1',
+ 'pseudo-first-child-span3',
+ 'pseudo-first-child-span5'
+ ],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+
+ // - :last-child (Level 3)
+ {
+ 'name':
+ ':last-child pseudo-class selector, matching last child div element',
+ 'selector': '#pseudo-last-child div:last-child',
+ 'expect': ['pseudo-last-child-div3'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ ":last-child pseudo-class selector, doesn't match non-last-child elements",
+ 'selector':
+ '.pseudo-last-child-div1:last-child, .pseudo-last-child-div2:first-child',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 3,
+ 'testType': testQsaAdditional
+ },
+ {
+ 'name':
+ ':last-child pseudo-class selector, matching first-child of multiple elements',
+ 'selector': '#pseudo-last-child span:last-child',
+ 'expect': [
+ 'pseudo-last-child-span2',
+ 'pseudo-last-child-span4',
+ 'pseudo-last-child-span6'
+ ],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+
+ // - :only-child (Level 3)
+ {
+ 'name':
+ ':pseudo-only-child pseudo-class selector, matching all only-child elements',
+ 'selector': '#pseudo-only :only-child',
+ 'expect': ['pseudo-only-span1'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ ':pseudo-only-child pseudo-class selector, matching only-child em elements',
+ 'selector': '#pseudo-only em:only-child',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 3,
+ 'testType': testQsaAdditional
+ },
+
+ // - :only-of-type (Level 3)
+ {
+ 'name':
+ ':pseudo-only-of-type pseudo-class selector, matching all elements with no siblings of the same type',
+ 'selector': '#pseudo-only :only-of-type',
+ 'expect': ['pseudo-only-span1', 'pseudo-only-em1'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ ':pseudo-only-of-type pseudo-class selector, matching em elements with no siblings of the same type',
+ 'selector': '#pseudo-only em:only-of-type',
+ 'expect': ['pseudo-only-em1'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+
+ // - :empty (Level 3)
+ {
+ 'name': ':empty pseudo-class selector, matching empty p elements',
+ 'selector': '#pseudo-empty p:empty',
+ 'expect': ['pseudo-empty-p1', 'pseudo-empty-p2'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name': ':empty pseudo-class selector, matching all empty elements',
+ 'selector': '#pseudo-empty :empty',
+ 'expect': ['pseudo-empty-p1', 'pseudo-empty-p2', 'pseudo-empty-span1'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+
+ // - :link and :visited
+ // Implementations may treat all visited links as unvisited, so these cannot be tested separately.
+ // The only guarantee is that ":link,:visited" matches the set of all visited and unvisited links and that they are individually mutually exclusive sets.
+ {
+ 'name':
+ ':link and :visited pseudo-class selectors, matching a and area elements with href attributes',
+ 'selector': '#pseudo-link :link, #pseudo-link :visited',
+ 'expect': ['pseudo-link-a1', 'pseudo-link-a2', 'pseudo-link-area1'],
+ 'level': 1,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ ':link and :visited pseudo-class selectors, matching link elements with href attributes',
+ 'selector': '#head :link, #head :visited',
+ 'expect': ['pseudo-link-link1', 'pseudo-link-link2'],
+ 'exclude': ['element', 'fragment', 'detached'],
+ 'level': 1,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ ':link and :visited pseudo-class selectors, not matching link elements with href attributes',
+ 'selector': '#head :link, #head :visited',
+ 'expect': <String>[] /*no matches*/,
+ 'exclude': ['document'],
+ 'level': 1,
+ 'testType': testQsaBaseline
+ },
+ {
+ 'name':
+ ':link and :visited pseudo-class selectors, chained, mutually exclusive pseudo-classes match nothing',
+ 'selector': ':link:visited',
+ 'expect': <String>[] /*no matches*/,
+ 'exclude': ['document'],
+ 'level': 1,
+ 'testType': testQsaBaseline
+ },
+
+ // - :target (Level 3)
+ {
+ 'name':
+ ':target pseudo-class selector, matching the element referenced by the URL fragment identifier',
+ 'selector': ':target',
+ 'expect': <String>[] /*no matches*/,
+ 'exclude': ['document', 'element'],
+ 'level': 3,
+ 'testType': testQsaAdditional
+ },
+ {
+ 'name':
+ ':target pseudo-class selector, matching the element referenced by the URL fragment identifier',
+ 'selector': ':target',
+ 'expect': ['target'],
+ 'exclude': ['fragment', 'detached'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+
+ // - :lang()
+ {
+ 'name': ':lang pseudo-class selector, matching inherited language',
+ 'selector': '#pseudo-lang-div1:lang(en)',
+ 'expect': ['pseudo-lang-div1'],
+ 'exclude': ['detached', 'fragment'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ ':lang pseudo-class selector, not matching element with no inherited language',
+ 'selector': '#pseudo-lang-div1:lang(en)',
+ 'expect': <String>[] /*no matches*/,
+ 'exclude': ['document', 'element'],
+ 'level': 2,
+ 'testType': testQsaBaseline
+ },
+ {
+ 'name':
+ ':lang pseudo-class selector, matching specified language with exact value',
+ 'selector': '#pseudo-lang-div2:lang(fr)',
+ 'expect': ['pseudo-lang-div2'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ ':lang pseudo-class selector, matching specified language with partial value',
+ 'selector': '#pseudo-lang-div3:lang(en)',
+ 'expect': ['pseudo-lang-div3'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name': ':lang pseudo-class selector, not matching incorrect language',
+ 'selector': '#pseudo-lang-div4:lang(es-AR)',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 2,
+ 'testType': testQsaBaseline
+ },
+
+ // - :enabled (Level 3)
+ {
+ 'name':
+ ':enabled pseudo-class selector, matching all enabled form controls',
+ 'selector': '#pseudo-ui :enabled',
+ 'expect': [
+ 'pseudo-ui-input1',
+ 'pseudo-ui-input2',
+ 'pseudo-ui-input3',
+ 'pseudo-ui-input4',
+ 'pseudo-ui-input5',
+ 'pseudo-ui-input6',
+ 'pseudo-ui-input7',
+ 'pseudo-ui-input8',
+ 'pseudo-ui-input9',
+ 'pseudo-ui-textarea1',
+ 'pseudo-ui-button1'
+ ],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+
+ // - :disabled (Level 3)
+ {
+ 'name':
+ ':enabled pseudo-class selector, matching all disabled form controls',
+ 'selector': '#pseudo-ui :disabled',
+ 'expect': [
+ 'pseudo-ui-input10',
+ 'pseudo-ui-input11',
+ 'pseudo-ui-input12',
+ 'pseudo-ui-input13',
+ 'pseudo-ui-input14',
+ 'pseudo-ui-input15',
+ 'pseudo-ui-input16',
+ 'pseudo-ui-input17',
+ 'pseudo-ui-input18',
+ 'pseudo-ui-textarea2',
+ 'pseudo-ui-button2'
+ ],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+
+ // - :checked (Level 3)
+ {
+ 'name':
+ ':checked pseudo-class selector, matching checked radio buttons and checkboxes',
+ 'selector': '#pseudo-ui :checked',
+ 'expect': [
+ 'pseudo-ui-input4',
+ 'pseudo-ui-input6',
+ 'pseudo-ui-input13',
+ 'pseudo-ui-input15'
+ ],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+
+ // - :not(s) (Level 3)
+ {
+ 'name': ':not pseudo-class selector, matching ',
+ 'selector': '#not>:not(div)',
+ 'expect': ['not-p1', 'not-p2', 'not-p3'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name': ':not pseudo-class selector, matching ',
+ 'selector': '#not * :not(:first-child)',
+ 'expect': ['not-em1', 'not-em2', 'not-em3'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name': ':not pseudo-class selector, matching nothing',
+ 'selector': ':not(*)',
+ 'expect': <String>[] /* no matches */,
+ 'level': 3,
+ 'testType': testQsaAdditional
+ },
+ {
+ 'name': ':not pseudo-class selector, matching nothing',
+ 'selector': ':not(*|*)',
+ 'expect': <String>[] /* no matches */,
+ 'level': 3,
+ 'testType': testQsaAdditional
+ },
+
+ // Pseudo-elements
+ // - ::first-line
+ {
+ 'name':
+ ':first-line pseudo-element (one-colon syntax) selector, not matching any elements',
+ 'selector': '#pseudo-element:first-line',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 2,
+ 'testType': testQsaBaseline
+ },
+ {
+ 'name':
+ '::first-line pseudo-element (two-colon syntax) selector, not matching any elements',
+ 'selector': '#pseudo-element::first-line',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 3,
+ 'testType': testQsaAdditional
+ },
+
+ // - ::first-letter
+ {
+ 'name':
+ ':first-letter pseudo-element (one-colon syntax) selector, not matching any elements',
+ 'selector': '#pseudo-element:first-letter',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 2,
+ 'testType': testQsaBaseline
+ },
+ {
+ 'name':
+ '::first-letter pseudo-element (two-colon syntax) selector, not matching any elements',
+ 'selector': '#pseudo-element::first-letter',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 3,
+ 'testType': testQsaAdditional
+ },
+
+ // - ::before
+ {
+ 'name':
+ ':before pseudo-element (one-colon syntax) selector, not matching any elements',
+ 'selector': '#pseudo-element:before',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 2,
+ 'testType': testQsaBaseline
+ },
+ {
+ 'name':
+ '::before pseudo-element (two-colon syntax) selector, not matching any elements',
+ 'selector': '#pseudo-element::before',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 3,
+ 'testType': testQsaAdditional
+ },
+
+ // - ::after
+ {
+ 'name':
+ ':after pseudo-element (one-colon syntax) selector, not matching any elements',
+ 'selector': '#pseudo-element:after',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 2,
+ 'testType': testQsaBaseline
+ },
+ {
+ 'name':
+ '::after pseudo-element (two-colon syntax) selector, not matching any elements',
+ 'selector': '#pseudo-element::after',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 3,
+ 'testType': testQsaAdditional
+ },
+
+ // Class Selectors
+ {
+ 'name': 'Class selector, matching element with specified class',
+ 'selector': '.class-p',
+ 'expect': ['class-p1', 'class-p2', 'class-p3'],
+ 'level': 1,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Class selector, chained, matching only elements with all specified classes',
+ 'selector': '#class .apple.orange.banana',
+ 'expect': [
+ 'class-div1',
+ 'class-div2',
+ 'class-p4',
+ 'class-div3',
+ 'class-p6',
+ 'class-div4'
+ ],
+ 'level': 1,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name': 'Class Selector, chained, with type selector',
+ 'selector': 'div.apple.banana.orange',
+ 'expect': ['class-div1', 'class-div2', 'class-div3', 'class-div4'],
+ 'level': 1,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ // Caution: If copying and pasting the folowing non-ASCII classes, ensure unicode normalisation is not performed in the process.
+ {
+ 'name':
+ 'Class selector, matching element with class value using non-ASCII characters',
+ 'selector': '.台北Táiběi',
+ 'expect': ['class-span1'],
+ 'level': 1,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Class selector, matching multiple elements with class value using non-ASCII characters',
+ 'selector': '.台北',
+ 'expect': ['class-span1', 'class-span2'],
+ 'level': 1,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Class selector, chained, matching element with multiple class values using non-ASCII characters',
+ 'selector': '.台北Táiběi.台北',
+ 'expect': ['class-span1'],
+ 'level': 1,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Class selector, matching element with class with escaped character',
+ 'selector': '.foo\\:bar',
+ 'expect': ['class-span3'],
+ 'level': 1,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Class selector, matching element with class with escaped character',
+ 'selector': '.test\\.foo\\[5\\]bar',
+ 'expect': ['class-span4'],
+ 'level': 1,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+
+ // ID Selectors
+ {
+ 'name': 'ID selector, matching element with specified id',
+ 'selector': '#id #id-div1',
+ 'expect': ['id-div1'],
+ 'level': 1,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name': 'ID selector, chained, matching element with specified id',
+ 'selector': '#id-div1, #id-div1',
+ 'expect': ['id-div1'],
+ 'level': 1,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name': 'ID selector, chained, matching element with specified id',
+ 'selector': '#id-div1, #id-div2',
+ 'expect': ['id-div1', 'id-div2'],
+ 'level': 1,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name': 'ID Selector, chained, with type selector',
+ 'selector': 'div#id-div1, div#id-div2',
+ 'expect': ['id-div1', 'id-div2'],
+ 'level': 1,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name': 'ID selector, not matching non-existent descendant',
+ 'selector': '#id #none',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 1,
+ 'testType': testQsaBaseline
+ },
+ {
+ 'name': 'ID selector, not matching non-existent ancestor',
+ 'selector': '#none #id-div1',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 1,
+ 'testType': testQsaBaseline
+ },
+ {
+ 'name': 'ID selector, matching multiple elements with duplicate id',
+ 'selector': '#id-li-duplicate',
+ 'expect': [
+ 'id-li-duplicate',
+ 'id-li-duplicate',
+ 'id-li-duplicate',
+ 'id-li-duplicate'
+ ],
+ 'level': 1,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+
+ // Caution: If copying and pasting the folowing non-ASCII IDs, ensure unicode normalisation is not performed in the process.
+ {
+ 'name': 'ID selector, matching id value using non-ASCII characters',
+ 'selector': '#台北Táiběi',
+ 'expect': ['台北Táiběi'],
+ 'level': 1,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name': 'ID selector, matching id value using non-ASCII characters',
+ 'selector': '#台北',
+ 'expect': ['台北'],
+ 'level': 1,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name': 'ID selector, matching id values using non-ASCII characters',
+ 'selector': '#台北Táiběi, #台北',
+ 'expect': ['台北Táiběi', '台北'],
+ 'level': 1,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+
+ // XXX runMatchesTest() in level2-lib.js can't handle this because obtaining the expected nodes requires escaping characters when generating the selector from 'expect' values
+ {
+ 'name': 'ID selector, matching element with id with escaped character',
+ 'selector': '#\\#foo\\:bar',
+ 'expect': ['#foo:bar'],
+ 'level': 1,
+ 'testType': testQsaBaseline
+ },
+ {
+ 'name': 'ID selector, matching element with id with escaped character',
+ 'selector': '#test\\.foo\\[5\\]bar',
+ 'expect': ['test.foo[5]bar'],
+ 'level': 1,
+ 'testType': testQsaBaseline
+ },
+
+ // Namespaces
+ // XXX runMatchesTest() in level2-lib.js can't handle these because non-HTML elements don't have a recognised id
+ {
+ 'name': 'Namespace selector, matching element with any namespace',
+ 'selector': '#any-namespace *|div',
+ 'expect': [
+ 'any-namespace-div1',
+ 'any-namespace-div2',
+ 'any-namespace-div3',
+ 'any-namespace-div4'
+ ],
+ 'level': 3,
+ 'testType': testQsaBaseline
+ },
+ {
+ 'name': 'Namespace selector, matching div elements in no namespace only',
+ 'selector': '#no-namespace |div',
+ 'expect': ['no-namespace-div3'],
+ 'level': 3,
+ 'testType': testQsaBaseline
+ },
+ {
+ 'name': 'Namespace selector, matching any elements in no namespace only',
+ 'selector': '#no-namespace |*',
+ 'expect': ['no-namespace-div3'],
+ 'level': 3,
+ 'testType': testQsaBaseline
+ },
+
+ // Combinators
+ // - Descendant combinator ' '
+ {
+ 'name':
+ 'Descendant combinator, matching element that is a descendant of an element with id',
+ 'selector': '#descendant div',
+ 'expect': [
+ 'descendant-div1',
+ 'descendant-div2',
+ 'descendant-div3',
+ 'descendant-div4'
+ ],
+ 'level': 1,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Descendant combinator, matching element with id that is a descendant of an element',
+ 'selector': 'body #descendant-div1',
+ 'expect': ['descendant-div1'],
+ 'exclude': ['detached', 'fragment'],
+ 'level': 1,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Descendant combinator, matching element with id that is a descendant of an element',
+ 'selector': 'div #descendant-div1',
+ 'expect': ['descendant-div1'],
+ 'level': 1,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Descendant combinator, matching element with id that is a descendant of an element with id',
+ 'selector': '#descendant #descendant-div2',
+ 'expect': ['descendant-div2'],
+ 'level': 1,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Descendant combinator, matching element with class that is a descendant of an element with id',
+ 'selector': '#descendant .descendant-div2',
+ 'expect': ['descendant-div2'],
+ 'level': 1,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Descendant combinator, matching element with class that is a descendant of an element with class',
+ 'selector': '.descendant-div1 .descendant-div3',
+ 'expect': ['descendant-div3'],
+ 'level': 1,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Descendant combinator, not matching element with id that is not a descendant of an element with id',
+ 'selector': '#descendant-div1 #descendant-div4',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 1,
+ 'testType': testQsaBaseline
+ },
+ {
+ 'name': 'Descendant combinator, whitespace characters',
+ 'selector': '#descendant\t\r\n#descendant-div2',
+ 'expect': ['descendant-div2'],
+ 'level': 1,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+
+ // - Child combinator '>'
+ {
+ 'name':
+ 'Child combinator, matching element that is a child of an element with id',
+ 'selector': '#child>div',
+ 'expect': ['child-div1', 'child-div4'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Child combinator, matching element with id that is a child of an element',
+ 'selector': 'div>#child-div1',
+ 'expect': ['child-div1'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Child combinator, matching element with id that is a child of an element with id',
+ 'selector': '#child>#child-div1',
+ 'expect': ['child-div1'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Child combinator, matching element with id that is a child of an element with class',
+ 'selector': '#child-div1>.child-div2',
+ 'expect': ['child-div2'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Child combinator, matching element with class that is a child of an element with class',
+ 'selector': '.child-div1>.child-div2',
+ 'expect': ['child-div2'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Child combinator, not matching element with id that is not a child of an element with id',
+ 'selector': '#child>#child-div3',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 2,
+ 'testType': testQsaBaseline
+ },
+ {
+ 'name':
+ 'Child combinator, not matching element with id that is not a child of an element with class',
+ 'selector': '#child-div1>.child-div3',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 2,
+ 'testType': testQsaBaseline
+ },
+ {
+ 'name':
+ 'Child combinator, not matching element with class that is not a child of an element with class',
+ 'selector': '.child-div1>.child-div3',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 2,
+ 'testType': testQsaBaseline
+ },
+ {
+ 'name': 'Child combinator, surrounded by whitespace',
+ 'selector': '#child-div1\t\r\n>\t\r\n#child-div2',
+ 'expect': ['child-div2'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name': 'Child combinator, whitespace after',
+ 'selector': '#child-div1>\t\r\n#child-div2',
+ 'expect': ['child-div2'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name': 'Child combinator, whitespace before',
+ 'selector': '#child-div1\t\r\n>#child-div2',
+ 'expect': ['child-div2'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name': 'Child combinator, no whitespace',
+ 'selector': '#child-div1>#child-div2',
+ 'expect': ['child-div2'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+
+ // - Adjacent sibling combinator '+'
+ {
+ 'name':
+ 'Adjacent sibling combinator, matching element that is an adjacent sibling of an element with id',
+ 'selector': '#adjacent-div2+div',
+ 'expect': ['adjacent-div4'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Adjacent sibling combinator, matching element with id that is an adjacent sibling of an element',
+ 'selector': 'div+#adjacent-div4',
+ 'expect': ['adjacent-div4'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Adjacent sibling combinator, matching element with id that is an adjacent sibling of an element with id',
+ 'selector': '#adjacent-div2+#adjacent-div4',
+ 'expect': ['adjacent-div4'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Adjacent sibling combinator, matching element with class that is an adjacent sibling of an element with id',
+ 'selector': '#adjacent-div2+.adjacent-div4',
+ 'expect': ['adjacent-div4'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Adjacent sibling combinator, matching element with class that is an adjacent sibling of an element with class',
+ 'selector': '.adjacent-div2+.adjacent-div4',
+ 'expect': ['adjacent-div4'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Adjacent sibling combinator, matching p element that is an adjacent sibling of a div element',
+ 'selector': '#adjacent div+p',
+ 'expect': ['adjacent-p2'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name':
+ 'Adjacent sibling combinator, not matching element with id that is not an adjacent sibling of an element with id',
+ 'selector': '#adjacent-div2+#adjacent-p2, #adjacent-div2+#adjacent-div1',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 2,
+ 'testType': testQsaBaseline
+ },
+ {
+ 'name': 'Adjacent sibling combinator, surrounded by whitespace',
+ 'selector': '#adjacent-p2\t\r\n+\t\r\n#adjacent-p3',
+ 'expect': ['adjacent-p3'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name': 'Adjacent sibling combinator, whitespace after',
+ 'selector': '#adjacent-p2+\t\r\n#adjacent-p3',
+ 'expect': ['adjacent-p3'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name': 'Adjacent sibling combinator, whitespace before',
+ 'selector': '#adjacent-p2\t\r\n+#adjacent-p3',
+ 'expect': ['adjacent-p3'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name': 'Adjacent sibling combinator, no whitespace',
+ 'selector': '#adjacent-p2+#adjacent-p3',
+ 'expect': ['adjacent-p3'],
+ 'level': 2,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+
+ // - General sibling combinator ~ (Level 3)
+ {
+ 'name':
+ 'General sibling combinator, matching element that is a sibling of an element with id',
+ 'selector': '#sibling-div2~div',
+ 'expect': ['sibling-div4', 'sibling-div6'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ 'General sibling combinator, matching element with id that is a sibling of an element',
+ 'selector': 'div~#sibling-div4',
+ 'expect': ['sibling-div4'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ 'General sibling combinator, matching element with id that is a sibling of an element with id',
+ 'selector': '#sibling-div2~#sibling-div4',
+ 'expect': ['sibling-div4'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ 'General sibling combinator, matching element with class that is a sibling of an element with id',
+ 'selector': '#sibling-div2~.sibling-div',
+ 'expect': ['sibling-div4', 'sibling-div6'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ 'General sibling combinator, matching p element that is a sibling of a div element',
+ 'selector': '#sibling div~p',
+ 'expect': ['sibling-p2', 'sibling-p3'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name':
+ 'General sibling combinator, not matching element with id that is not a sibling after a p element',
+ 'selector': '#sibling>p~div',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 3,
+ 'testType': testQsaAdditional
+ },
+ {
+ 'name':
+ 'General sibling combinator, not matching element with id that is not a sibling after an element with id',
+ 'selector': '#sibling-div2~#sibling-div3, #sibling-div2~#sibling-div1',
+ 'expect': <String>[] /*no matches*/,
+ 'level': 3,
+ 'testType': testQsaAdditional
+ },
+ {
+ 'name': 'General sibling combinator, surrounded by whitespace',
+ 'selector': '#sibling-p2\t\r\n~\t\r\n#sibling-p3',
+ 'expect': ['sibling-p3'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name': 'General sibling combinator, whitespace after',
+ 'selector': '#sibling-p2~\t\r\n#sibling-p3',
+ 'expect': ['sibling-p3'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name': 'General sibling combinator, whitespace before',
+ 'selector': '#sibling-p2\t\r\n~#sibling-p3',
+ 'expect': ['sibling-p3'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+ {
+ 'name': 'General sibling combinator, no whitespace',
+ 'selector': '#sibling-p2~#sibling-p3',
+ 'expect': ['sibling-p3'],
+ 'level': 3,
+ 'testType': testQsaAdditional | testMatchBaseline
+ },
+
+ // Group of selectors (comma)
+ {
+ 'name': 'Syntax, group of selectors separator, surrounded by whitespace',
+ 'selector': '#group em\t\r \n,\t\r \n#group strong',
+ 'expect': ['group-em1', 'group-strong1'],
+ 'level': 1,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name': 'Syntax, group of selectors separator, whitespace after',
+ 'selector': '#group em,\t\r\n#group strong',
+ 'expect': ['group-em1', 'group-strong1'],
+ 'level': 1,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name': 'Syntax, group of selectors separator, whitespace before',
+ 'selector': '#group em\t\r\n,#group strong',
+ 'expect': ['group-em1', 'group-strong1'],
+ 'level': 1,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+ {
+ 'name': 'Syntax, group of selectors separator, no whitespace',
+ 'selector': '#group em,#group strong',
+ 'expect': ['group-em1', 'group-strong1'],
+ 'level': 1,
+ 'testType': testQsaBaseline | testMatchBaseline
+ },
+];
diff --git a/pkgs/html/test/support.dart b/pkgs/html/test/support.dart
new file mode 100644
index 0000000..82f8e62
--- /dev/null
+++ b/pkgs/html/test/support.dart
@@ -0,0 +1,186 @@
+/// Support code for the tests in this directory.
+library;
+
+import 'dart:collection';
+import 'dart:io';
+import 'dart:isolate';
+
+import 'package:html/dom.dart';
+import 'package:html/dom_parsing.dart';
+import 'package:html/src/treebuilder.dart';
+import 'package:path/path.dart' as p;
+
+typedef TreeBuilderFactory = TreeBuilder Function(bool namespaceHTMLElements);
+
+Map<String, TreeBuilderFactory>? _treeTypes;
+Map<String, TreeBuilderFactory>? get treeTypes {
+ // TODO(jmesserly): add DOM here once it's implemented
+ _treeTypes ??= {'simpletree': TreeBuilder.new};
+ return _treeTypes;
+}
+
+Future<String> get testDirectory async {
+ final packageUriDir = p.dirname(p.fromUri(await Isolate.resolvePackageUri(
+ Uri(scheme: 'package', path: 'html/html.dart'))));
+ // Assume pub layout - root is parent directory to package URI (`lib/`).
+ final rootPackageDir = p.dirname(packageUriDir);
+ return p.join(rootPackageDir, 'test');
+}
+
+Stream<String> dataFiles(String subdirectory) async* {
+ final dir = Directory(p.join(await testDirectory, 'data', subdirectory));
+ await for (final file in dir.list()) {
+ if (file is! File) continue;
+ yield file.path;
+ }
+}
+
+// TODO(jmesserly): make this class simpler. We could probably split on
+// "\n#" instead of newline and remove a lot of code.
+class TestData extends IterableBase<Map<String?, String>> {
+ final String _text;
+ final String newTestHeading;
+
+ TestData(String filename, [this.newTestHeading = 'data'])
+ // Note: can't use readAsLinesSync here because it splits on \r
+ : _text = File(filename).readAsStringSync();
+
+ // Note: in Python this was a generator, but since we can't do that in Dart,
+ // it's easier to convert it into an upfront computation.
+ @override
+ Iterator<Map<String?, String>> get iterator => _getData().iterator;
+
+ List<Map<String?, String>> _getData() {
+ var data = <String, String>{};
+ String? key;
+ final List<Map<String?, String>> result = <Map<String, String>>[];
+ final lines = _text.split('\n');
+ // Remove trailing newline to match Python
+ if (lines.last == '') {
+ lines.removeLast();
+ }
+ for (var line in lines) {
+ final heading = sectionHeading(line);
+ if (heading != null) {
+ if (data.isNotEmpty && heading == newTestHeading) {
+ // Remove trailing newline
+ data[key!] = data[key]!.substring(0, data[key]!.length - 1);
+ result.add(normaliseOutput(data));
+ data = <String, String>{};
+ }
+ key = heading;
+ data[key] = '';
+ } else if (key != null) {
+ data[key] = '${data[key]}$line\n';
+ }
+ }
+
+ if (data.isNotEmpty) {
+ result.add(normaliseOutput(data));
+ }
+ return result;
+ }
+
+ /// If the current heading is a test section heading return the heading,
+ /// otherwise return null.
+ static String? sectionHeading(String line) {
+ return line.startsWith('#') ? line.substring(1).trim() : null;
+ }
+
+ static Map<String, String> normaliseOutput(Map<String, String> data) {
+ // Remove trailing newlines
+ data.forEach((key, value) {
+ if (value.endsWith('\n')) {
+ data[key] = value.substring(0, value.length - 1);
+ }
+ });
+ return data;
+ }
+}
+
+/// Serialize the [document] into the html5 test data format.
+String testSerializer(Node document) {
+ return (TestSerializer()..visit(document)).toString();
+}
+
+/// Serializes the DOM into test format. See [testSerializer].
+class TestSerializer extends TreeVisitor {
+ final StringBuffer _str;
+ int _indent = 0;
+ String _spaces = '';
+
+ TestSerializer() : _str = StringBuffer();
+
+ @override
+ String toString() => _str.toString();
+
+ int get indent => _indent;
+
+ set indent(int value) {
+ if (_indent == value) return;
+ _spaces = ' ' * value;
+ _indent = value;
+ }
+
+ void _newline() {
+ if (_str.length > 0) _str.write('\n');
+ _str.write('|$_spaces');
+ }
+
+ @override
+ void visitNodeFallback(Node node) {
+ _newline();
+ _str.write(node);
+ visitChildren(node);
+ }
+
+ @override
+ void visitChildren(Node node) {
+ indent += 2;
+ for (var child in node.nodes) {
+ visit(child);
+ }
+ indent -= 2;
+ }
+
+ @override
+ void visitDocument(Document node) => _visitDocumentOrFragment(node);
+
+ void _visitDocumentOrFragment(Node node) {
+ indent += 1;
+ for (var child in node.nodes) {
+ visit(child);
+ }
+ indent -= 1;
+ }
+
+ @override
+ void visitDocumentFragment(DocumentFragment node) =>
+ _visitDocumentOrFragment(node);
+
+ @override
+ void visitElement(Element node) {
+ _newline();
+ _str.write(node);
+ if (node.attributes.isNotEmpty) {
+ indent += 2;
+ final keys = node.attributes.keys.toList();
+ keys.sort((x, y) {
+ if (x is String) return x.compareTo(y as String);
+ if (x is AttributeName) return x.compareTo(y as AttributeName);
+ throw StateError('Cannot sort');
+ });
+ for (var key in keys) {
+ final v = node.attributes[key];
+ if (key is AttributeName) {
+ final attr = key;
+ key = '${attr.prefix} ${attr.name}';
+ }
+ _newline();
+ _str.write('$key="$v"');
+ }
+ indent -= 2;
+ }
+ visitChildren(node);
+ }
+}
diff --git a/pkgs/html/test/tokenizer_test.dart b/pkgs/html/test/tokenizer_test.dart
new file mode 100644
index 0000000..92103ef
--- /dev/null
+++ b/pkgs/html/test/tokenizer_test.dart
@@ -0,0 +1,291 @@
+@TestOn('vm')
+library;
+
+import 'dart:convert';
+import 'dart:io';
+// Note: mirrors used to match the getattr usage in the original test
+import 'dart:mirrors' show reflect;
+
+import 'package:html/src/token.dart';
+import 'package:html/src/tokenizer.dart';
+import 'package:path/path.dart' as pathos;
+import 'package:test/test.dart';
+
+import 'support.dart';
+
+void main() async {
+ await for (var path in dataFiles('tokenizer')) {
+ if (!path.endsWith('.test')) continue;
+
+ final text = File(path).readAsStringSync();
+ final tests = jsonDecode(text) as Map<String, dynamic>;
+ final testName = pathos.basenameWithoutExtension(path);
+ final testList = tests['tests'] as List?;
+ if (testList == null) continue;
+
+ group(testName, () {
+ for (var index = 0; index < testList.length; index++) {
+ final testInfo = testList[index] as Map<String, dynamic>;
+
+ testInfo.putIfAbsent('initialStates', () => ['Data state']);
+ for (var initialState in testInfo['initialStates'] as List) {
+ test(testInfo['description'], () {
+ testInfo['initialState'] = camelCase(initialState as String);
+ runTokenizerTest(testInfo);
+ });
+ }
+ }
+ });
+ }
+}
+
+class TokenizerTestParser {
+ final String? _state;
+ final String? _lastStartTag;
+ final bool _generateSpans;
+ List<List<Object?>>? outputTokens;
+
+ TokenizerTestParser(String? initialState,
+ [String? lastStartTag, bool generateSpans = false])
+ : _state = initialState,
+ _lastStartTag = lastStartTag,
+ _generateSpans = generateSpans;
+
+ List<dynamic>? parse(String str) {
+ // Note: we need to pass bytes to the tokenizer if we want it to handle BOM.
+ final bytes = utf8.encode(str);
+ final tokenizer =
+ HtmlTokenizer(bytes, encoding: 'utf-8', generateSpans: _generateSpans);
+ outputTokens = [];
+
+ // Note: we can't get a closure of the state method. However, we can
+ // create a new closure to invoke it via mirrors.
+ final mtok = reflect(tokenizer);
+ tokenizer.state =
+ () => mtok.invoke(Symbol(_state!), const []).reflectee as bool;
+
+ if (_lastStartTag != null) {
+ tokenizer.currentToken = StartTagToken(_lastStartTag);
+ }
+
+ while (tokenizer.moveNext()) {
+ final token = tokenizer.current;
+ switch (token.kind) {
+ case TokenKind.characters:
+ processCharacters(token as CharactersToken);
+ break;
+ case TokenKind.spaceCharacters:
+ processSpaceCharacters(token as SpaceCharactersToken);
+ break;
+ case TokenKind.startTag:
+ processStartTag(token as StartTagToken);
+ break;
+ case TokenKind.endTag:
+ processEndTag(token as EndTagToken);
+ break;
+ case TokenKind.comment:
+ processComment(token as CommentToken);
+ break;
+ case TokenKind.doctype:
+ processDoctype(token as DoctypeToken);
+ break;
+ case TokenKind.parseError:
+ processParseError(token as ParseErrorToken);
+ break;
+ }
+ }
+
+ return outputTokens;
+ }
+
+ void processDoctype(DoctypeToken token) {
+ addOutputToken(token,
+ ['DOCTYPE', token.name, token.publicId, token.systemId, token.correct]);
+ }
+
+ void processStartTag(StartTagToken token) {
+ addOutputToken(
+ token, ['StartTag', token.name, token.data, token.selfClosing]);
+ }
+
+ void processEndTag(EndTagToken token) {
+ addOutputToken(token, ['EndTag', token.name, token.selfClosing]);
+ }
+
+ void processComment(StringToken token) {
+ addOutputToken(token, ['Comment', token.data]);
+ }
+
+ void processSpaceCharacters(StringToken token) {
+ processCharacters(token);
+ }
+
+ void processCharacters(StringToken token) {
+ addOutputToken(token, ['Character', token.data]);
+ }
+
+ void processParseError(StringToken token) {
+ // TODO(jmesserly): when debugging test failures it can be useful to add
+ // logging here like `print('ParseError $token');`. It would be nice to
+ // use the actual logging library.
+ addOutputToken(token, ['ParseError', token.data]);
+ }
+
+ void addOutputToken(Token token, List<Object?> array) {
+ outputTokens!.add([
+ ...array,
+ if (token.span != null && _generateSpans) token.span!.start.offset,
+ if (token.span != null && _generateSpans) token.span!.end.offset,
+ ]);
+ }
+}
+
+/// [tokens] can contain strings, lists, and maps.
+List<dynamic> concatenateCharacterTokens(List<dynamic> tokens) {
+ final outputTokens = <dynamic>[];
+ for (var token in tokens) {
+ if (token != 'ParseError' && (token as List)[0] == 'Character') {
+ if (outputTokens.isNotEmpty &&
+ outputTokens.last != 'ParseError' &&
+ (outputTokens.last as List)[0] == 'Character') {
+ (outputTokens.last as List)[1] =
+ '${(outputTokens.last as List)[1]}${token[1]}';
+ } else {
+ outputTokens.add(token);
+ }
+ } else {
+ outputTokens.add(token);
+ }
+ }
+ return outputTokens;
+}
+
+List<dynamic> normalizeTokens(List<dynamic> tokens) {
+ // TODO: convert tests to reflect arrays
+ for (var i = 0; i < tokens.length; i++) {
+ final token = tokens[i] as List;
+ if (token[0] == 'ParseError') {
+ tokens[i] = token[0];
+ }
+ }
+ return tokens;
+}
+
+/// Test whether the test has passed or failed
+///
+/// If the ignoreErrorOrder flag is set to true we don't test the relative
+/// positions of parse errors and non parse errors.
+void expectTokensMatch(List<dynamic> expectedTokens,
+ List<dynamic> receivedTokens, bool ignoreErrorOrder,
+ [bool ignoreErrors = false, String? message]) {
+ // If the 'selfClosing' attribute is not included in the expected test tokens,
+ // remove it from the received token.
+ var removeSelfClosing = false;
+ for (var token in expectedTokens.whereType<List<dynamic>>()) {
+ if (token[0] == 'StartTag' && token.length == 3 ||
+ token[0] == 'EndTag' && token.length == 2) {
+ removeSelfClosing = true;
+ break;
+ }
+ }
+
+ if (removeSelfClosing) {
+ for (var token in receivedTokens.whereType<List<dynamic>>()) {
+ if (token[0] == 'StartTag' || token[0] == 'EndTag') {
+ token.removeLast();
+ }
+ }
+ }
+
+ if (!ignoreErrorOrder && !ignoreErrors) {
+ expect(receivedTokens, equals(expectedTokens), reason: message);
+ } else {
+ // Sort the tokens into two groups; non-parse errors and parse errors
+ final expectedNonErrors = expectedTokens.where((t) => t != 'ParseError');
+ final receivedNonErrors = receivedTokens.where((t) => t != 'ParseError');
+
+ expect(receivedNonErrors, equals(expectedNonErrors), reason: message);
+ if (!ignoreErrors) {
+ final expectedParseErrors =
+ expectedTokens.where((t) => t == 'ParseError');
+ final receivedParseErrors =
+ receivedTokens.where((t) => t == 'ParseError');
+ expect(receivedParseErrors, equals(expectedParseErrors), reason: message);
+ }
+ }
+}
+
+void runTokenizerTest(Map<String, dynamic> testInfo) {
+ // XXX - move this out into the setup function
+ // concatenate all consecutive character tokens into a single token
+ if (testInfo.containsKey('doubleEscaped')) {
+ testInfo = unescape(testInfo);
+ }
+
+ final expected = concatenateCharacterTokens(testInfo['output'] as List);
+ if (!testInfo.containsKey('lastStartTag')) {
+ testInfo['lastStartTag'] = null;
+ }
+ final parser = TokenizerTestParser(
+ testInfo['initialState'] as String?,
+ testInfo['lastStartTag'] as String?,
+ testInfo['generateSpans'] as bool? ?? false);
+ var tokens = parser.parse(testInfo['input'] as String)!;
+ tokens = concatenateCharacterTokens(tokens);
+ final received = normalizeTokens(tokens);
+ final errorMsg = [
+ '\n\nInitial state:',
+ testInfo['initialState'],
+ '\nInput:',
+ testInfo['input'],
+ '\nExpected:',
+ expected,
+ '\nreceived:',
+ tokens
+ ].map((s) => '$s').join('\n');
+ final ignoreErrorOrder = testInfo['ignoreErrorOrder'] as bool? ?? false;
+
+ expectTokensMatch(expected, received, ignoreErrorOrder, true, errorMsg);
+}
+
+Map<String, dynamic> unescape(Map<String, dynamic> testInfo) {
+ // TODO(sigmundch,jmesserly): we currently use jsonDecode to unescape the
+ // unicode characters in the string, we should use a decoding that works with
+ // any control characters.
+ dynamic decode(String inp) => inp == '\u0000' ? inp : jsonDecode('"$inp"');
+
+ testInfo['input'] = decode(testInfo['input'] as String);
+
+ for (var token in testInfo['output'] as List) {
+ if (token == 'ParseError') {
+ continue;
+ }
+
+ token as List;
+ token[1] = decode(token[1] as String);
+
+ if (token.length > 2) {
+ for (var pair in token[2] as List) {
+ pair as List;
+ final key = pair[0] as String;
+ final value = pair[1] as String;
+
+ (token[2] as Map).remove(key);
+ (token[2] as Map)[decode(key)] = decode(value);
+ }
+ }
+ }
+
+ return testInfo;
+}
+
+String camelCase(String s) {
+ s = s.toLowerCase();
+ final result = StringBuffer();
+ for (var match in RegExp(r'\W+(\w)(\w+)').allMatches(s)) {
+ if (result.length == 0) result.write(s.substring(0, match.start));
+ result.write(match.group(1)!.toUpperCase());
+ result.write(match.group(2));
+ }
+ return result.toString();
+}
diff --git a/pkgs/io/.gitignore b/pkgs/io/.gitignore
new file mode 100644
index 0000000..01d42c0
--- /dev/null
+++ b/pkgs/io/.gitignore
@@ -0,0 +1,4 @@
+.dart_tool/
+.pub/
+.packages
+pubspec.lock
diff --git a/pkgs/io/AUTHORS b/pkgs/io/AUTHORS
new file mode 100644
index 0000000..ff09364
--- /dev/null
+++ b/pkgs/io/AUTHORS
@@ -0,0 +1,7 @@
+# Below is a list of people and organizations that have contributed
+# to the project. Names should be added to the list like so:
+#
+# Name/Organization <email address>
+
+Google Inc.
+
diff --git a/pkgs/io/CHANGELOG.md b/pkgs/io/CHANGELOG.md
new file mode 100644
index 0000000..8c0057f
--- /dev/null
+++ b/pkgs/io/CHANGELOG.md
@@ -0,0 +1,123 @@
+## 1.1.0-wip
+
+* Add a `deepCopyLinks` argument to `copyPath` and `copyPathSync`.
+
+## 1.0.5
+
+* Require Dart 3.4.
+* Move to `dart-lang/tools` monorepo.
+
+## 1.0.4
+
+* Updates to the readme.
+
+## 1.0.3
+
+* Revert `meta` constraint to `^1.3.0`.
+
+## 1.0.2
+
+* Update `meta` constraint to `>=1.3.0 <3.0.0`.
+
+## 1.0.1
+
+* Update code examples to call the unified `dart` developer tool.
+
+## 1.0.0
+
+* Migrate this package to null-safety.
+* Require Dart >=2.12.
+
+## 0.3.5
+
+* Require Dart >=2.1.
+* Remove dependency on `package:charcode`.
+
+## 0.3.4
+
+* Fix a number of issues affecting the package score on `pub.dev`.
+
+## 0.3.3
+
+* Updates for Dart 2 constants. Require at least Dart `2.0.0-dev.54`.
+
+* Fix the type of `StartProcess` typedef to match `Process.start` from
+ `dart:io`.
+
+## 0.3.2+1
+
+* `ansi.dart`
+
+ * The "forScript" code paths now ignore the `ansiOutputEnabled` value. Affects
+ the `escapeForScript` property on `AnsiCode` and the `wrap` and `wrapWith`
+ functions when `forScript` is true.
+
+## 0.3.2
+
+* `ansi.dart`
+
+ * Added `forScript` named argument to top-level `wrapWith` function.
+
+ * `AnsiCode`
+
+ * Added `String get escapeForScript` property.
+
+ * Added `forScript` named argument to `wrap` function.
+
+## 0.3.1
+
+- Added `SharedStdIn.nextLine` (similar to `readLineSync`) and `lines`:
+
+```dart
+main() async {
+ // Prints the first line entered on stdin.
+ print(await sharedStdIn.nextLine());
+
+ // Prints all remaining lines.
+ await for (final line in sharedStdIn.lines) {
+ print(line);
+ }
+}
+```
+
+- Added a `copyPath` and `copyPathSync` function, similar to `cp -R`.
+
+- Added a dependency on `package:path`.
+
+- Added the remaining missing arguments to `ProcessManager.spawnX` which
+ forward to `Process.start`. It is now an interchangeable function for running
+ a process.
+
+## 0.3.0
+
+- **BREAKING CHANGE**: The `arguments` argument to `ProcessManager.spawn` is
+ now positional (not named) and required. This makes it more similar to the
+ built-in `Process.start`, and easier to use as a drop in replacement:
+
+```dart
+main() {
+ processManager.spawn('dart', ['--version']);
+}
+```
+
+- Fixed a bug where processes created from `ProcessManager.spawn` could not
+ have their `stdout`/`stderr` read through their respective getters (a runtime
+ error was always thrown).
+
+- Added `ProcessMangaer#spawnBackground`, which does not forward `stdin`.
+
+- Added `ProcessManager#spawnDetached`, which does not forward any I/O.
+
+- Added the `shellSplit()` function, which parses a list of arguments in the
+ same manner as [the POSIX shell][what_is_posix_shell].
+
+[what_is_posix_shell]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/contents.html
+
+## 0.2.0
+
+- Initial commit of...
+ - `FutureOr<bool> String isExecutable(path)`.
+ - `ExitCode`
+ - `ProcessManager` and `Spawn`
+ - `sharedStdIn` and `SharedStdIn`
+ - `ansi.dart` library with support for formatting terminal output
diff --git a/pkgs/io/LICENSE b/pkgs/io/LICENSE
new file mode 100644
index 0000000..03af64a
--- /dev/null
+++ b/pkgs/io/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2017, 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.
diff --git a/pkgs/io/README.md b/pkgs/io/README.md
new file mode 100644
index 0000000..adbc941
--- /dev/null
+++ b/pkgs/io/README.md
@@ -0,0 +1,104 @@
+[](https://github.com/dart-lang/tools/actions/workflows/io.yaml)
+[](https://pub.dev/packages/io)
+[](https://pub.dev/packages/io/publisher)
+
+Contains utilities for the Dart VM's `dart:io`.
+
+## Usage - `io.dart`
+
+### Files
+
+#### `isExecutable`
+
+Returns whether a provided file path is considered _executable_ on the host
+operating system.
+
+### Processes
+
+#### `ExitCode`
+
+An `enum`-like class that contains known exit codes.
+
+#### `ProcessManager`
+
+A higher-level service for spawning and communicating with processes.
+
+##### Use `spawn` to create a process with std[in|out|err] forwarded by default
+
+```dart
+Future<void> main() async {
+ final manager = ProcessManager();
+
+ // Print `dart` tool version to stdout.
+ print('** Running `dart --version`');
+ var spawn = await manager.spawn('dart', ['--version']);
+ await spawn.exitCode;
+
+ // Check formatting and print the result to stdout.
+ print('** Running `dart format --output=none .`');
+ spawn = await manager.spawn('dart', ['format', '--output=none', '.']);
+ await spawn.exitCode;
+
+ // Check if a package is ready for publishing.
+ // Upon hitting a blocking stdin state, you may directly
+ // output to the processes's stdin via your own, similar to how a bash or
+ // shell script would spawn a process.
+ print('** Running pub publish');
+ spawn = await manager.spawn('dart', ['pub', 'publish', '--dry-run']);
+ await spawn.exitCode;
+
+ // Closes stdin for the entire program.
+ await sharedStdIn.terminate();
+}
+```
+
+#### `sharedStdIn`
+
+A safer version of the default `stdin` stream from `dart:io` that allows a
+subscriber to cancel their subscription, and then allows a _new_ subscriber to
+start listening. This differs from the default behavior where only a single
+listener is ever allowed in the application lifecycle:
+
+```dart
+test('should allow multiple subscribers', () async {
+ final logs = <String>[];
+ final asUtf8 = sharedStdIn.transform(UTF8.decoder);
+ // Wait for input for the user.
+ logs.add(await asUtf8.first);
+ // Wait for more input for the user.
+ logs.add(await asUtf8.first);
+ expect(logs, ['Hello World', 'Goodbye World']);
+});
+```
+
+For testing, an instance of `SharedStdIn` may be created directly.
+
+## Usage - `ansi.dart`
+
+```dart
+import 'dart:io' as io;
+import 'package:io/ansi.dart';
+
+void main() {
+ // To use one style, call the `wrap` method on one of the provided top-level
+ // values.
+ io.stderr.writeln(red.wrap("Bad error!"));
+
+ // To use multiple styles, call `wrapWith`.
+ print(wrapWith('** Important **', [red, styleBold, styleUnderlined]));
+
+ // The wrap functions will simply return the provided value unchanged if
+ // `ansiOutputEnabled` is false.
+ //
+ // You can override the value `ansiOutputEnabled` by wrapping code in
+ // `overrideAnsiOutput`.
+ overrideAnsiOutput(false, () {
+ assert('Normal text' == green.wrap('Normal text'));
+ });
+}
+```
+
+## Publishing automation
+
+For information about our publishing automation and release process, see
+https://github.com/dart-lang/ecosystem/wiki/Publishing-automation.
diff --git a/pkgs/io/analysis_options.yaml b/pkgs/io/analysis_options.yaml
new file mode 100644
index 0000000..6d74ee9
--- /dev/null
+++ b/pkgs/io/analysis_options.yaml
@@ -0,0 +1,32 @@
+# https://dart.dev/guides/language/analysis-options
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-casts: true
+ strict-inference: true
+ strict-raw-types: true
+
+linter:
+ rules:
+ - avoid_bool_literals_in_conditional_expressions
+ - avoid_classes_with_only_static_members
+ - avoid_private_typedef_functions
+ - avoid_redundant_argument_values
+ - avoid_returning_this
+ - avoid_unused_constructor_parameters
+ - avoid_void_async
+ - cancel_subscriptions
+ - join_return_with_assignment
+ - literal_only_boolean_expressions
+ - missing_whitespace_between_adjacent_strings
+ - no_adjacent_strings_in_list
+ - no_runtimeType_toString
+ - prefer_const_declarations
+ - prefer_expression_function_bodies
+ - prefer_final_locals
+ - unnecessary_await_in_return
+ - unnecessary_breaks
+ - use_if_null_to_convert_nulls_to_bools
+ - use_raw_strings
+ - use_string_buffers
diff --git a/pkgs/io/example/example.dart b/pkgs/io/example/example.dart
new file mode 100644
index 0000000..8e358fd
--- /dev/null
+++ b/pkgs/io/example/example.dart
@@ -0,0 +1,33 @@
+// Copyright (c) 2017, 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';
+
+import 'package:io/ansi.dart';
+
+/// Prints a sample of all of the `AnsiCode` values.
+void main(List<String> args) {
+ final forScript = args.contains('--for-script');
+
+ if (!ansiOutputEnabled) {
+ print('`ansiOutputEnabled` is `false`.');
+ print("Don't expect pretty output.");
+ }
+ _preview('Foreground', foregroundColors, forScript);
+ _preview('Background', backgroundColors, forScript);
+ _preview('Styles', styles, forScript);
+}
+
+void _preview(String name, List<AnsiCode> values, bool forScript) {
+ print('');
+ final longest = values.map((ac) => ac.name.length).reduce(max);
+
+ print(wrapWith('** $name **', [styleBold, styleUnderlined]));
+ for (var code in values) {
+ final header =
+ '${code.name.padRight(longest)} ${code.code.toString().padLeft(3)}';
+
+ print("$header: ${code.wrap('Sample', forScript: forScript)}");
+ }
+}
diff --git a/pkgs/io/example/spawn_process_example.dart b/pkgs/io/example/spawn_process_example.dart
new file mode 100644
index 0000000..b7ba247
--- /dev/null
+++ b/pkgs/io/example/spawn_process_example.dart
@@ -0,0 +1,33 @@
+// Copyright (c) 2017, 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 'package:io/io.dart';
+
+/// Runs a few subcommands in the `dart` command.
+Future<void> main() async {
+ final manager = ProcessManager();
+
+ // Print `dart` tool version to stdout.
+ print('** Running `dart --version`');
+ var spawn = await manager.spawn('dart', ['--version']);
+ await spawn.exitCode;
+
+ // Check formatting and print the result to stdout.
+ print('** Running `dart format --output=none .`');
+ spawn = await manager.spawn('dart', ['format', '--output=none', '.']);
+ await spawn.exitCode;
+
+ // Check if a package is ready for publishing.
+ // Upon hitting a blocking stdin state, you may directly
+ // output to the processes's stdin via your own, similar to how a bash or
+ // shell script would spawn a process.
+ print('** Running pub publish');
+ spawn = await manager.spawn('dart', ['pub', 'publish', '--dry-run']);
+ await spawn.exitCode;
+
+ // Closes stdin for the entire program.
+ await sharedStdIn.terminate();
+}
diff --git a/pkgs/io/lib/ansi.dart b/pkgs/io/lib/ansi.dart
new file mode 100644
index 0000000..a2adbe7
--- /dev/null
+++ b/pkgs/io/lib/ansi.dart
@@ -0,0 +1,5 @@
+// Copyright (c) 2017, 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/ansi_code.dart';
diff --git a/pkgs/io/lib/io.dart b/pkgs/io/lib/io.dart
new file mode 100644
index 0000000..8ee0843
--- /dev/null
+++ b/pkgs/io/lib/io.dart
@@ -0,0 +1,10 @@
+// Copyright (c) 2017, 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/copy_path.dart' show copyPath, copyPathSync;
+export 'src/exit_code.dart' show ExitCode;
+export 'src/permissions.dart' show isExecutable;
+export 'src/process_manager.dart' show ProcessManager, Spawn, StartProcess;
+export 'src/shared_stdin.dart' show SharedStdIn, sharedStdIn;
+export 'src/shell_words.dart' show shellSplit;
diff --git a/pkgs/io/lib/src/ansi_code.dart b/pkgs/io/lib/src/ansi_code.dart
new file mode 100644
index 0000000..c9a22c5
--- /dev/null
+++ b/pkgs/io/lib/src/ansi_code.dart
@@ -0,0 +1,316 @@
+// Copyright (c) 2017, 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;
+
+const _ansiEscapeLiteral = '\x1B';
+const _ansiEscapeForScript = r'\033';
+
+/// Whether formatted ANSI output is enabled for [wrapWith] and [AnsiCode.wrap].
+///
+/// By default, returns `true` if both `stdout.supportsAnsiEscapes` and
+/// `stderr.supportsAnsiEscapes` from `dart:io` are `true`.
+///
+/// The default can be overridden by setting the [Zone] variable [AnsiCode] to
+/// either `true` or `false`.
+///
+/// [overrideAnsiOutput] is provided to make this easy.
+bool get ansiOutputEnabled =>
+ Zone.current[AnsiCode] as bool? ??
+ (io.stdout.supportsAnsiEscapes && io.stderr.supportsAnsiEscapes);
+
+/// Returns `true` no formatting is required for [input].
+bool _isNoop(bool skip, String? input, bool? forScript) =>
+ skip ||
+ input == null ||
+ input.isEmpty ||
+ !((forScript ?? false) || ansiOutputEnabled);
+
+/// Allows overriding [ansiOutputEnabled] to [enableAnsiOutput] for the code run
+/// within [body].
+T overrideAnsiOutput<T>(bool enableAnsiOutput, T Function() body) =>
+ runZoned(body, zoneValues: <Object, Object>{AnsiCode: enableAnsiOutput});
+
+/// The type of code represented by [AnsiCode].
+class AnsiCodeType {
+ final String _name;
+
+ /// A foreground color.
+ static const AnsiCodeType foreground = AnsiCodeType._('foreground');
+
+ /// A style.
+ static const AnsiCodeType style = AnsiCodeType._('style');
+
+ /// A background color.
+ static const AnsiCodeType background = AnsiCodeType._('background');
+
+ /// A reset value.
+ static const AnsiCodeType reset = AnsiCodeType._('reset');
+
+ const AnsiCodeType._(this._name);
+
+ @override
+ String toString() => 'AnsiType.$_name';
+}
+
+/// Standard ANSI escape code for customizing terminal text output.
+///
+/// [Source](https://en.wikipedia.org/wiki/ANSI_escape_code#Colors)
+class AnsiCode {
+ /// The numeric value associated with this code.
+ final int code;
+
+ /// The [AnsiCode] that resets this value, if one exists.
+ ///
+ /// Otherwise, `null`.
+ final AnsiCode? reset;
+
+ /// A description of this code.
+ final String name;
+
+ /// The type of code that is represented.
+ final AnsiCodeType type;
+
+ const AnsiCode._(this.name, this.type, this.code, this.reset);
+
+ /// Represents the value escaped for use in terminal output.
+ String get escape => '$_ansiEscapeLiteral[${code}m';
+
+ /// Represents the value as an unescaped literal suitable for scripts.
+ String get escapeForScript => '$_ansiEscapeForScript[${code}m';
+
+ String _escapeValue({bool forScript = false}) =>
+ forScript ? escapeForScript : escape;
+
+ /// Wraps [value] with the [escape] value for this code, followed by
+ /// [resetAll].
+ ///
+ /// If [forScript] is `true`, the return value is an unescaped literal. The
+ /// value of [ansiOutputEnabled] is also ignored.
+ ///
+ /// Returns `value` unchanged if
+ /// * [value] is `null` or empty
+ /// * both [ansiOutputEnabled] and [forScript] are `false`.
+ /// * [type] is [AnsiCodeType.reset]
+ String? wrap(String? value, {bool forScript = false}) =>
+ _isNoop(type == AnsiCodeType.reset, value, forScript)
+ ? value
+ : '${_escapeValue(forScript: forScript)}$value'
+ '${reset!._escapeValue(forScript: forScript)}';
+
+ @override
+ String toString() => '$name ${type._name} ($code)';
+}
+
+/// Returns a [String] formatted with [codes].
+///
+/// If [forScript] is `true`, the return value is an unescaped literal. The
+/// value of [ansiOutputEnabled] is also ignored.
+///
+/// Returns `value` unchanged if
+/// * [value] is `null` or empty.
+/// * both [ansiOutputEnabled] and [forScript] are `false`.
+/// * [codes] is empty.
+///
+/// Throws an [ArgumentError] if
+/// * [codes] contains more than one value of type [AnsiCodeType.foreground].
+/// * [codes] contains more than one value of type [AnsiCodeType.background].
+/// * [codes] contains any value of type [AnsiCodeType.reset].
+String? wrapWith(String? value, Iterable<AnsiCode> codes,
+ {bool forScript = false}) {
+ // Eliminate duplicates
+ final myCodes = codes.toSet();
+
+ if (_isNoop(myCodes.isEmpty, value, forScript)) {
+ return value;
+ }
+
+ var foreground = 0, background = 0;
+ for (var code in myCodes) {
+ switch (code.type) {
+ case AnsiCodeType.foreground:
+ foreground++;
+ if (foreground > 1) {
+ throw ArgumentError.value(codes, 'codes',
+ 'Cannot contain more than one foreground color code.');
+ }
+ case AnsiCodeType.background:
+ background++;
+ if (background > 1) {
+ throw ArgumentError.value(codes, 'codes',
+ 'Cannot contain more than one foreground color code.');
+ }
+ case AnsiCodeType.reset:
+ throw ArgumentError.value(
+ codes, 'codes', 'Cannot contain reset codes.');
+ case AnsiCodeType.style:
+ // Ignore.
+ break;
+ }
+ }
+
+ final sortedCodes = myCodes.map((ac) => ac.code).toList()..sort();
+ final escapeValue = forScript ? _ansiEscapeForScript : _ansiEscapeLiteral;
+
+ return "$escapeValue[${sortedCodes.join(';')}m$value"
+ '${resetAll._escapeValue(forScript: forScript)}';
+}
+
+//
+// Style values
+//
+
+const styleBold = AnsiCode._('bold', AnsiCodeType.style, 1, resetBold);
+const styleDim = AnsiCode._('dim', AnsiCodeType.style, 2, resetDim);
+const styleItalic = AnsiCode._('italic', AnsiCodeType.style, 3, resetItalic);
+const styleUnderlined =
+ AnsiCode._('underlined', AnsiCodeType.style, 4, resetUnderlined);
+const styleBlink = AnsiCode._('blink', AnsiCodeType.style, 5, resetBlink);
+const styleReverse = AnsiCode._('reverse', AnsiCodeType.style, 7, resetReverse);
+
+/// Not widely supported.
+const styleHidden = AnsiCode._('hidden', AnsiCodeType.style, 8, resetHidden);
+
+/// Not widely supported.
+const styleCrossedOut =
+ AnsiCode._('crossed out', AnsiCodeType.style, 9, resetCrossedOut);
+
+//
+// Reset values
+//
+
+const resetAll = AnsiCode._('all', AnsiCodeType.reset, 0, null);
+
+// NOTE: bold is weird. The reset code seems to be 22 sometimes – not 21
+// See https://gitlab.com/gnachman/iterm2/issues/3208
+const resetBold = AnsiCode._('bold', AnsiCodeType.reset, 22, null);
+const resetDim = AnsiCode._('dim', AnsiCodeType.reset, 22, null);
+const resetItalic = AnsiCode._('italic', AnsiCodeType.reset, 23, null);
+const resetUnderlined = AnsiCode._('underlined', AnsiCodeType.reset, 24, null);
+const resetBlink = AnsiCode._('blink', AnsiCodeType.reset, 25, null);
+const resetReverse = AnsiCode._('reverse', AnsiCodeType.reset, 27, null);
+const resetHidden = AnsiCode._('hidden', AnsiCodeType.reset, 28, null);
+const resetCrossedOut = AnsiCode._('crossed out', AnsiCodeType.reset, 29, null);
+
+//
+// Foreground values
+//
+
+const black = AnsiCode._('black', AnsiCodeType.foreground, 30, resetAll);
+const red = AnsiCode._('red', AnsiCodeType.foreground, 31, resetAll);
+const green = AnsiCode._('green', AnsiCodeType.foreground, 32, resetAll);
+const yellow = AnsiCode._('yellow', AnsiCodeType.foreground, 33, resetAll);
+const blue = AnsiCode._('blue', AnsiCodeType.foreground, 34, resetAll);
+const magenta = AnsiCode._('magenta', AnsiCodeType.foreground, 35, resetAll);
+const cyan = AnsiCode._('cyan', AnsiCodeType.foreground, 36, resetAll);
+const lightGray =
+ AnsiCode._('light gray', AnsiCodeType.foreground, 37, resetAll);
+const defaultForeground =
+ AnsiCode._('default', AnsiCodeType.foreground, 39, resetAll);
+const darkGray = AnsiCode._('dark gray', AnsiCodeType.foreground, 90, resetAll);
+const lightRed = AnsiCode._('light red', AnsiCodeType.foreground, 91, resetAll);
+const lightGreen =
+ AnsiCode._('light green', AnsiCodeType.foreground, 92, resetAll);
+const lightYellow =
+ AnsiCode._('light yellow', AnsiCodeType.foreground, 93, resetAll);
+const lightBlue =
+ AnsiCode._('light blue', AnsiCodeType.foreground, 94, resetAll);
+const lightMagenta =
+ AnsiCode._('light magenta', AnsiCodeType.foreground, 95, resetAll);
+const lightCyan =
+ AnsiCode._('light cyan', AnsiCodeType.foreground, 96, resetAll);
+const white = AnsiCode._('white', AnsiCodeType.foreground, 97, resetAll);
+
+//
+// Background values
+//
+
+const backgroundBlack =
+ AnsiCode._('black', AnsiCodeType.background, 40, resetAll);
+const backgroundRed = AnsiCode._('red', AnsiCodeType.background, 41, resetAll);
+const backgroundGreen =
+ AnsiCode._('green', AnsiCodeType.background, 42, resetAll);
+const backgroundYellow =
+ AnsiCode._('yellow', AnsiCodeType.background, 43, resetAll);
+const backgroundBlue =
+ AnsiCode._('blue', AnsiCodeType.background, 44, resetAll);
+const backgroundMagenta =
+ AnsiCode._('magenta', AnsiCodeType.background, 45, resetAll);
+const backgroundCyan =
+ AnsiCode._('cyan', AnsiCodeType.background, 46, resetAll);
+const backgroundLightGray =
+ AnsiCode._('light gray', AnsiCodeType.background, 47, resetAll);
+const backgroundDefault =
+ AnsiCode._('default', AnsiCodeType.background, 49, resetAll);
+const backgroundDarkGray =
+ AnsiCode._('dark gray', AnsiCodeType.background, 100, resetAll);
+const backgroundLightRed =
+ AnsiCode._('light red', AnsiCodeType.background, 101, resetAll);
+const backgroundLightGreen =
+ AnsiCode._('light green', AnsiCodeType.background, 102, resetAll);
+const backgroundLightYellow =
+ AnsiCode._('light yellow', AnsiCodeType.background, 103, resetAll);
+const backgroundLightBlue =
+ AnsiCode._('light blue', AnsiCodeType.background, 104, resetAll);
+const backgroundLightMagenta =
+ AnsiCode._('light magenta', AnsiCodeType.background, 105, resetAll);
+const backgroundLightCyan =
+ AnsiCode._('light cyan', AnsiCodeType.background, 106, resetAll);
+const backgroundWhite =
+ AnsiCode._('white', AnsiCodeType.background, 107, resetAll);
+
+/// All of the [AnsiCode] values that represent [AnsiCodeType.style].
+const List<AnsiCode> styles = [
+ styleBold,
+ styleDim,
+ styleItalic,
+ styleUnderlined,
+ styleBlink,
+ styleReverse,
+ styleHidden,
+ styleCrossedOut
+];
+
+/// All of the [AnsiCode] values that represent [AnsiCodeType.foreground].
+const List<AnsiCode> foregroundColors = [
+ black,
+ red,
+ green,
+ yellow,
+ blue,
+ magenta,
+ cyan,
+ lightGray,
+ defaultForeground,
+ darkGray,
+ lightRed,
+ lightGreen,
+ lightYellow,
+ lightBlue,
+ lightMagenta,
+ lightCyan,
+ white
+];
+
+/// All of the [AnsiCode] values that represent [AnsiCodeType.background].
+const List<AnsiCode> backgroundColors = [
+ backgroundBlack,
+ backgroundRed,
+ backgroundGreen,
+ backgroundYellow,
+ backgroundBlue,
+ backgroundMagenta,
+ backgroundCyan,
+ backgroundLightGray,
+ backgroundDefault,
+ backgroundDarkGray,
+ backgroundLightRed,
+ backgroundLightGreen,
+ backgroundLightYellow,
+ backgroundLightBlue,
+ backgroundLightMagenta,
+ backgroundLightCyan,
+ backgroundWhite
+];
diff --git a/pkgs/io/lib/src/charcodes.dart b/pkgs/io/lib/src/charcodes.dart
new file mode 100644
index 0000000..4acaf0a
--- /dev/null
+++ b/pkgs/io/lib/src/charcodes.dart
@@ -0,0 +1,34 @@
+// Copyright (c) 2021, 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.
+
+// Generated using:
+// pub global run charcode \$=dollar \'=single_quote \"=double_quote \
+// \' '\\\n"$`# \t'
+
+/// "Horizontal Tab" control character, common name.
+const int $tab = 0x09;
+
+/// "Line feed" control character.
+const int $lf = 0x0a;
+
+/// Space character.
+const int $space = 0x20;
+
+/// Character `"`, short name.
+const int $doubleQuote = 0x22;
+
+/// Character `#`.
+const int $hash = 0x23;
+
+/// Character `$`.
+const int $dollar = 0x24;
+
+/// Character "'".
+const int $singleQuote = 0x27;
+
+/// Character `\`.
+const int $backslash = 0x5c;
+
+/// Character `` ` ``.
+const int $backquote = 0x60;
diff --git a/pkgs/io/lib/src/copy_path.dart b/pkgs/io/lib/src/copy_path.dart
new file mode 100644
index 0000000..8a1c3ca
--- /dev/null
+++ b/pkgs/io/lib/src/copy_path.dart
@@ -0,0 +1,78 @@
+// Copyright (c) 2017, 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:path/path.dart' as p;
+
+bool _doNothing(String from, String to) {
+ if (p.canonicalize(from) == p.canonicalize(to)) {
+ return true;
+ }
+ if (p.isWithin(from, to)) {
+ throw ArgumentError('Cannot copy from $from to $to');
+ }
+ return false;
+}
+
+/// Copies all of the files in the [from] directory to [to].
+///
+/// This is similar to `cp -R <from> <to>`:
+/// * Existing files are over-written, if any.
+/// * If [to] is within [from], throws [ArgumentError] (an infinite operation).
+/// * If [from] and [to] are canonically the same, no operation occurs.
+/// * If [deepCopyLinks] is `true` (the default) then links are followed and
+/// the content of linked directories and files are copied entirely. If
+/// `false` then new [Link] file system entities are created linking to the
+/// same target the links under [from].
+///
+/// Returns a future that completes when complete.
+Future<void> copyPath(String from, String to,
+ {bool deepCopyLinks = true}) async {
+ if (_doNothing(from, to)) {
+ return;
+ }
+ await Directory(to).create(recursive: true);
+ await for (final file
+ in Directory(from).list(recursive: true, followLinks: deepCopyLinks)) {
+ final copyTo = p.join(to, p.relative(file.path, from: from));
+ if (file is Directory) {
+ await Directory(copyTo).create(recursive: true);
+ } else if (file is File) {
+ await File(file.path).copy(copyTo);
+ } else if (file is Link) {
+ await Link(copyTo).create(await file.target(), recursive: true);
+ }
+ }
+}
+
+/// Copies all of the files in the [from] directory to [to].
+///
+/// This is similar to `cp -R <from> <to>`:
+/// * Existing files are over-written, if any.
+/// * If [to] is within [from], throws [ArgumentError] (an infinite operation).
+/// * If [from] and [to] are canonically the same, no operation occurs.
+/// * If [deepCopyLinks] is `true` (the default) then links are followed and
+/// the content of linked directories and files are copied entirely. If
+/// `false` then new [Link] file system entities are created linking to the
+/// same target the links under [from].
+///
+/// This action is performed synchronously (blocking I/O).
+void copyPathSync(String from, String to, {bool deepCopyLinks = true}) {
+ if (_doNothing(from, to)) {
+ return;
+ }
+ Directory(to).createSync(recursive: true);
+ for (final file in Directory(from)
+ .listSync(recursive: true, followLinks: deepCopyLinks)) {
+ final copyTo = p.join(to, p.relative(file.path, from: from));
+ if (file is Directory) {
+ Directory(copyTo).createSync(recursive: true);
+ } else if (file is File) {
+ File(file.path).copySync(copyTo);
+ } else if (file is Link) {
+ Link(copyTo).createSync(file.targetSync(), recursive: true);
+ }
+ }
+}
diff --git a/pkgs/io/lib/src/exit_code.dart b/pkgs/io/lib/src/exit_code.dart
new file mode 100644
index 0000000..d405558
--- /dev/null
+++ b/pkgs/io/lib/src/exit_code.dart
@@ -0,0 +1,82 @@
+// Copyright (c) 2017, 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.
+
+/// Exit code constants.
+///
+/// [Source](https://www.freebsd.org/cgi/man.cgi?query=sysexits).
+class ExitCode {
+ /// Command completed successfully.
+ static const success = ExitCode._(0, 'success');
+
+ /// Command was used incorrectly.
+ ///
+ /// This may occur if the wrong number of arguments was used, a bad flag, or
+ /// bad syntax in a parameter.
+ static const usage = ExitCode._(64, 'usage');
+
+ /// Input data was used incorrectly.
+ ///
+ /// This should occur only for user data (not system files).
+ static const data = ExitCode._(65, 'data');
+
+ /// An input file (not a system file) did not exist or was not readable.
+ static const noInput = ExitCode._(66, 'noInput');
+
+ /// User specified did not exist.
+ static const noUser = ExitCode._(67, 'noUser');
+
+ /// Host specified did not exist.
+ static const noHost = ExitCode._(68, 'noHost');
+
+ /// A service is unavailable.
+ ///
+ /// This may occur if a support program or file does not exist. This may also
+ /// be used as a catch-all error when something you wanted to do does not
+ /// work, but you do not know why.
+ static const unavailable = ExitCode._(69, 'unavailable');
+
+ /// An internal software error has been detected.
+ ///
+ /// This should be limited to non-operating system related errors as possible.
+ static const software = ExitCode._(70, 'software');
+
+ /// An operating system error has been detected.
+ ///
+ /// This intended to be used for such thing as `cannot fork` or `cannot pipe`.
+ static const osError = ExitCode._(71, 'osError');
+
+ /// Some system file (e.g. `/etc/passwd`) does not exist or could not be read.
+ static const osFile = ExitCode._(72, 'osFile');
+
+ /// A (user specified) output file cannot be created.
+ static const cantCreate = ExitCode._(73, 'cantCreate');
+
+ /// An error occurred doing I/O on some file.
+ static const ioError = ExitCode._(74, 'ioError');
+
+ /// Temporary failure, indicating something is not really an error.
+ ///
+ /// In some cases, this can be re-attempted and will succeed later.
+ static const tempFail = ExitCode._(75, 'tempFail');
+
+ /// You did not have sufficient permissions to perform the operation.
+ ///
+ /// This is not intended for file system problems, which should use [noInput]
+ /// or [cantCreate], but rather for higher-level permissions.
+ static const noPerm = ExitCode._(77, 'noPerm');
+
+ /// Something was found in an unconfigured or misconfigured state.
+ static const config = ExitCode._(78, 'config');
+
+ /// Exit code value.
+ final int code;
+
+ /// Name of the exit code.
+ final String _name;
+
+ const ExitCode._(this.code, this._name);
+
+ @override
+ String toString() => '$_name: $code';
+}
diff --git a/pkgs/io/lib/src/permissions.dart b/pkgs/io/lib/src/permissions.dart
new file mode 100644
index 0000000..c516943
--- /dev/null
+++ b/pkgs/io/lib/src/permissions.dart
@@ -0,0 +1,69 @@
+// Copyright (c) 2017, 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';
+
+/// What type of permission is granted to a file based on file permission roles.
+enum _FilePermission {
+ execute,
+ // Although these two values are unused, their positions in the enum are
+ // meaningful.
+ write, // ignore: unused_field
+ read, // ignore: unused_field
+ setGid,
+ setUid,
+ sticky,
+}
+
+/// What type of role is assigned to a file.
+enum _FilePermissionRole {
+ world,
+ group,
+ user,
+}
+
+/// Returns whether file [stat] has [permission] for a [role] type.
+bool _hasPermission(
+ FileStat stat,
+ _FilePermission permission, {
+ _FilePermissionRole role = _FilePermissionRole.world,
+}) {
+ final index = _permissionBitIndex(permission, role);
+ return (stat.mode & (1 << index)) != 0;
+}
+
+int _permissionBitIndex(_FilePermission permission, _FilePermissionRole role) =>
+ switch (permission) {
+ _FilePermission.setUid => 11,
+ _FilePermission.setGid => 10,
+ _FilePermission.sticky => 9,
+ _ => (role.index * 3) + permission.index
+ };
+
+/// Returns whether [path] is considered an executable file on this OS.
+///
+/// May optionally define how to implement [getStat] or whether to execute based
+/// on whether this is the windows platform ([isWindows]) - if not set it is
+/// automatically extracted from `dart:io#Platform`.
+///
+/// **NOTE**: On windows this always returns `true`.
+FutureOr<bool> isExecutable(
+ String path, {
+ bool? isWindows,
+ FutureOr<FileStat> Function(String path) getStat = FileStat.stat,
+}) {
+ // Windows has no concept of executable.
+ if (isWindows ?? Platform.isWindows) return true;
+ final stat = getStat(path);
+ if (stat is FileStat) {
+ return _isExecutable(stat);
+ }
+ return stat.then(_isExecutable);
+}
+
+bool _isExecutable(FileStat stat) =>
+ stat.type == FileSystemEntityType.file &&
+ _FilePermissionRole.values.any(
+ (role) => _hasPermission(stat, _FilePermission.execute, role: role));
diff --git a/pkgs/io/lib/src/process_manager.dart b/pkgs/io/lib/src/process_manager.dart
new file mode 100644
index 0000000..84d22ec
--- /dev/null
+++ b/pkgs/io/lib/src/process_manager.dart
@@ -0,0 +1,255 @@
+// Copyright (c) 2017, 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: close_sinks,cancel_subscriptions
+
+import 'dart:async';
+import 'dart:io' as io;
+
+import 'package:meta/meta.dart';
+
+import 'shared_stdin.dart';
+
+/// Type definition for both [io.Process.start] and [ProcessManager.spawn].
+///
+/// Useful for taking different implementations of this base functionality.
+typedef StartProcess = Future<io.Process> Function(
+ String executable,
+ List<String> arguments, {
+ String workingDirectory,
+ Map<String, String> environment,
+ bool includeParentEnvironment,
+ bool runInShell,
+ io.ProcessStartMode mode,
+});
+
+/// A high-level abstraction around using and managing processes on the system.
+abstract class ProcessManager {
+ /// Terminates the global `stdin` listener, making future listens impossible.
+ ///
+ /// This method should be invoked only at the _end_ of a program's execution.
+ static Future<void> terminateStdIn() async {
+ await sharedStdIn.terminate();
+ }
+
+ /// Create a new instance of [ProcessManager] for the current platform.
+ ///
+ /// May manually specify whether the current platform [isWindows], otherwise
+ /// this is derived from the Dart runtime (i.e. [io.Platform.isWindows]).
+ factory ProcessManager({
+ Stream<List<int>>? stdin,
+ io.IOSink? stdout,
+ io.IOSink? stderr,
+ bool? isWindows,
+ }) {
+ stdin ??= sharedStdIn;
+ stdout ??= io.stdout;
+ stderr ??= io.stderr;
+ isWindows ??= io.Platform.isWindows;
+ if (isWindows) {
+ return _WindowsProcessManager(stdin, stdout, stderr);
+ }
+ return _UnixProcessManager(stdin, stdout, stderr);
+ }
+
+ final Stream<List<int>> _stdin;
+ final io.IOSink _stdout;
+ final io.IOSink _stderr;
+
+ const ProcessManager._(this._stdin, this._stdout, this._stderr);
+
+ /// Spawns a process by invoking [executable] with [arguments].
+ ///
+ /// This is _similar_ to [io.Process.start], but all standard input and output
+ /// is forwarded/routed between the process and the host, similar to how a
+ /// shell script works.
+ ///
+ /// Returns a future that completes with a handle to the spawned process.
+ Future<io.Process> spawn(
+ String executable,
+ Iterable<String> arguments, {
+ String? workingDirectory,
+ Map<String, String>? environment,
+ bool includeParentEnvironment = true,
+ bool runInShell = false,
+ io.ProcessStartMode mode = io.ProcessStartMode.normal,
+ }) async {
+ final process = io.Process.start(
+ executable,
+ arguments.toList(),
+ workingDirectory: workingDirectory,
+ environment: environment,
+ includeParentEnvironment: includeParentEnvironment,
+ runInShell: runInShell,
+ mode: mode,
+ );
+ return _ForwardingSpawn(await process, _stdin, _stdout, _stderr);
+ }
+
+ /// Spawns a process by invoking [executable] with [arguments].
+ ///
+ /// This is _similar_ to [io.Process.start], but `stdout` and `stderr` is
+ /// forwarded/routed between the process and host, similar to how a shell
+ /// script works.
+ ///
+ /// Returns a future that completes with a handle to the spawned process.
+ Future<io.Process> spawnBackground(
+ String executable,
+ Iterable<String> arguments, {
+ String? workingDirectory,
+ Map<String, String>? environment,
+ bool includeParentEnvironment = true,
+ bool runInShell = false,
+ io.ProcessStartMode mode = io.ProcessStartMode.normal,
+ }) async {
+ final process = io.Process.start(
+ executable,
+ arguments.toList(),
+ workingDirectory: workingDirectory,
+ environment: environment,
+ includeParentEnvironment: includeParentEnvironment,
+ runInShell: runInShell,
+ mode: mode,
+ );
+ return _ForwardingSpawn(
+ await process,
+ const Stream.empty(),
+ _stdout,
+ _stderr,
+ );
+ }
+
+ /// Spawns a process by invoking [executable] with [arguments].
+ ///
+ /// This is _identical to [io.Process.start] (no forwarding of I/O).
+ ///
+ /// Returns a future that completes with a handle to the spawned process.
+ Future<io.Process> spawnDetached(
+ String executable,
+ Iterable<String> arguments, {
+ String? workingDirectory,
+ Map<String, String>? environment,
+ bool includeParentEnvironment = true,
+ bool runInShell = false,
+ io.ProcessStartMode mode = io.ProcessStartMode.normal,
+ }) async =>
+ io.Process.start(
+ executable,
+ arguments.toList(),
+ workingDirectory: workingDirectory,
+ environment: environment,
+ includeParentEnvironment: includeParentEnvironment,
+ runInShell: runInShell,
+ mode: mode,
+ );
+}
+
+/// A process instance created and managed through [ProcessManager].
+///
+/// Unlike one created directly by [io.Process.start] or [io.Process.run], a
+/// spawned process works more like executing a command in a shell script.
+class Spawn implements io.Process {
+ final io.Process _delegate;
+
+ Spawn._(this._delegate) {
+ _delegate.exitCode.then((_) => _onClosed());
+ }
+
+ @mustCallSuper
+ void _onClosed() {}
+
+ @override
+ bool kill([io.ProcessSignal signal = io.ProcessSignal.sigterm]) =>
+ _delegate.kill(signal);
+
+ @override
+ Future<int> get exitCode => _delegate.exitCode;
+
+ @override
+ int get pid => _delegate.pid;
+
+ @override
+ Stream<List<int>> get stderr => _delegate.stderr;
+
+ @override
+ io.IOSink get stdin => _delegate.stdin;
+
+ @override
+ Stream<List<int>> get stdout => _delegate.stdout;
+}
+
+/// Forwards `stdin`/`stdout`/`stderr` to/from the host.
+class _ForwardingSpawn extends Spawn {
+ final StreamSubscription<List<int>> _stdInSub;
+ final StreamSubscription<List<int>> _stdOutSub;
+ final StreamSubscription<List<int>> _stdErrSub;
+ final StreamController<List<int>> _stdOut;
+ final StreamController<List<int>> _stdErr;
+
+ factory _ForwardingSpawn(
+ io.Process delegate,
+ Stream<List<int>> stdin,
+ io.IOSink stdout,
+ io.IOSink stderr,
+ ) {
+ final stdoutSelf = StreamController<List<int>>();
+ final stderrSelf = StreamController<List<int>>();
+ final stdInSub = stdin.listen(delegate.stdin.add);
+ final stdOutSub = delegate.stdout.listen((event) {
+ stdout.add(event);
+ stdoutSelf.add(event);
+ });
+ final stdErrSub = delegate.stderr.listen((event) {
+ stderr.add(event);
+ stderrSelf.add(event);
+ });
+ return _ForwardingSpawn._delegate(
+ delegate,
+ stdInSub,
+ stdOutSub,
+ stdErrSub,
+ stdoutSelf,
+ stderrSelf,
+ );
+ }
+
+ _ForwardingSpawn._delegate(
+ super.delegate,
+ this._stdInSub,
+ this._stdOutSub,
+ this._stdErrSub,
+ this._stdOut,
+ this._stdErr,
+ ) : super._();
+
+ @override
+ void _onClosed() {
+ _stdInSub.cancel();
+ _stdOutSub.cancel();
+ _stdErrSub.cancel();
+ super._onClosed();
+ }
+
+ @override
+ Stream<List<int>> get stdout => _stdOut.stream;
+
+ @override
+ Stream<List<int>> get stderr => _stdErr.stream;
+}
+
+class _UnixProcessManager extends ProcessManager {
+ const _UnixProcessManager(
+ super.stdin,
+ super.stdout,
+ super.stderr,
+ ) : super._();
+}
+
+class _WindowsProcessManager extends ProcessManager {
+ const _WindowsProcessManager(
+ super.stdin,
+ super.stdout,
+ super.stderr,
+ ) : super._();
+}
diff --git a/pkgs/io/lib/src/shared_stdin.dart b/pkgs/io/lib/src/shared_stdin.dart
new file mode 100644
index 0000000..72bb50c
--- /dev/null
+++ b/pkgs/io/lib/src/shared_stdin.dart
@@ -0,0 +1,99 @@
+// Copyright (c) 2017, 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';
+
+import 'package:meta/meta.dart';
+
+/// A shared singleton instance of `dart:io`'s [stdin] stream.
+///
+/// _Unlike_ the normal [stdin] stream, [sharedStdIn] may switch subscribers
+/// as long as the previous subscriber cancels before the new subscriber starts
+/// listening.
+///
+/// [SharedStdIn.terminate] *must* be invoked in order to close the underlying
+/// connection to [stdin], allowing your program to close automatically without
+/// hanging.
+final SharedStdIn sharedStdIn = SharedStdIn(stdin);
+
+/// A singleton wrapper around `stdin` that allows new subscribers.
+///
+/// This class is visible in order to be used as a test harness for mock
+/// implementations of `stdin`. In normal programs, [sharedStdIn] should be
+/// used directly.
+@visibleForTesting
+class SharedStdIn extends Stream<List<int>> {
+ StreamController<List<int>>? _current;
+ StreamSubscription<List<int>>? _sub;
+
+ SharedStdIn([Stream<List<int>>? stream]) {
+ _sub = (stream ??= stdin).listen(_onInput);
+ }
+
+ /// Returns a future that completes with the next line.
+ ///
+ /// This is similar to the standard [Stdin.readLineSync], but asynchronous.
+ Future<String> nextLine({Encoding encoding = systemEncoding}) =>
+ lines(encoding: encoding).first;
+
+ /// Returns the stream transformed as UTF8 strings separated by line breaks.
+ ///
+ /// This is similar to synchronous code using [Stdin.readLineSync]:
+ /// ```dart
+ /// while (true) {
+ /// var line = stdin.readLineSync();
+ /// // ...
+ /// }
+ /// ```
+ ///
+ /// ... but asynchronous.
+ Stream<String> lines({Encoding encoding = systemEncoding}) =>
+ transform(utf8.decoder).transform(const LineSplitter());
+
+ void _onInput(List<int> event) => _getCurrent().add(event);
+
+ StreamController<List<int>> _getCurrent() =>
+ _current ??= StreamController<List<int>>(
+ onCancel: () {
+ _current = null;
+ },
+ sync: true);
+
+ @override
+ StreamSubscription<List<int>> listen(
+ void Function(List<int> event)? onData, {
+ Function? onError,
+ void Function()? onDone,
+ bool? cancelOnError,
+ }) {
+ if (_sub == null) {
+ throw StateError('Stdin has already been terminated.');
+ }
+ // ignore: close_sinks
+ final controller = _getCurrent();
+ if (controller.hasListener) {
+ throw StateError(''
+ 'Subscriber already listening. The existing subscriber must cancel '
+ 'before another may be added.');
+ }
+ return controller.stream.listen(
+ onData,
+ onDone: onDone,
+ onError: onError,
+ cancelOnError: cancelOnError,
+ );
+ }
+
+ /// Terminates the connection to `stdin`, closing all subscription.
+ Future<void> terminate() async {
+ if (_sub == null) {
+ throw StateError('Stdin has already been terminated.');
+ }
+ await _sub?.cancel();
+ await _current?.close();
+ _sub = null;
+ }
+}
diff --git a/pkgs/io/lib/src/shell_words.dart b/pkgs/io/lib/src/shell_words.dart
new file mode 100644
index 0000000..5fca6d9
--- /dev/null
+++ b/pkgs/io/lib/src/shell_words.dart
@@ -0,0 +1,142 @@
+// Copyright (c) 2017, 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: comment_references
+
+import 'package:string_scanner/string_scanner.dart';
+
+import 'charcodes.dart';
+
+/// Splits [command] into tokens according to [the POSIX shell
+/// specification][spec].
+///
+/// [spec]: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/contents.html
+///
+/// This returns the unquoted values of quoted tokens. For example,
+/// `shellSplit('foo "bar baz"')` returns `["foo", "bar baz"]`. It does not
+/// currently support here-documents. It does *not* treat dynamic features such
+/// as parameter expansion specially. For example, `shellSplit("foo $(bar
+/// baz)")` returns `["foo", "$(bar", "baz)"]`.
+///
+/// This will discard any comments at the end of [command].
+///
+/// Throws a [FormatException] if [command] isn't a valid shell command.
+List<String> shellSplit(String command) {
+ final scanner = StringScanner(command);
+ final results = <String>[];
+ final token = StringBuffer();
+
+ // Whether a token is being parsed, as opposed to a separator character. This
+ // is different than just [token.isEmpty], because empty quoted tokens can
+ // exist.
+ var hasToken = false;
+
+ while (!scanner.isDone) {
+ final next = scanner.readChar();
+ switch (next) {
+ case $backslash:
+ // Section 2.2.1: A <backslash> that is not quoted shall preserve the
+ // literal value of the following character, with the exception of a
+ // <newline>. If a <newline> follows the <backslash>, the shell shall
+ // interpret this as line continuation. The <backslash> and <newline>
+ // shall be removed before splitting the input into tokens. Since the
+ // escaped <newline> is removed entirely from the input and is not
+ // replaced by any white space, it cannot serve as a token separator.
+ if (scanner.scanChar($lf)) break;
+
+ hasToken = true;
+ token.writeCharCode(scanner.readChar());
+
+ case $singleQuote:
+ hasToken = true;
+ // Section 2.2.2: Enclosing characters in single-quotes ( '' ) shall
+ // preserve the literal value of each character within the
+ // single-quotes. A single-quote cannot occur within single-quotes.
+ final firstQuote = scanner.position - 1;
+ while (!scanner.scanChar($singleQuote)) {
+ _checkUnmatchedQuote(scanner, firstQuote);
+ token.writeCharCode(scanner.readChar());
+ }
+
+ case $doubleQuote:
+ hasToken = true;
+ // Section 2.2.3: Enclosing characters in double-quotes ( "" ) shall
+ // preserve the literal value of all characters within the
+ // double-quotes, with the exception of the characters backquote,
+ // <dollar-sign>, and <backslash>.
+ //
+ // (Note that this code doesn't preserve special behavior of backquote
+ // or dollar sign within double quotes, since those are dynamic
+ // features.)
+ final firstQuote = scanner.position - 1;
+ while (!scanner.scanChar($doubleQuote)) {
+ _checkUnmatchedQuote(scanner, firstQuote);
+
+ if (scanner.scanChar($backslash)) {
+ _checkUnmatchedQuote(scanner, firstQuote);
+
+ // The <backslash> shall retain its special meaning as an escape
+ // character (see Escape Character (Backslash)) only when followed
+ // by one of the following characters when considered special:
+ //
+ // $ ` " \ <newline>
+ final next = scanner.readChar();
+ if (next == $lf) continue;
+ if (next == $dollar ||
+ next == $backquote ||
+ next == $doubleQuote ||
+ next == $backslash) {
+ token.writeCharCode(next);
+ } else {
+ token
+ ..writeCharCode($backslash)
+ ..writeCharCode(next);
+ }
+ } else {
+ token.writeCharCode(scanner.readChar());
+ }
+ }
+
+ case $hash:
+ // Section 2.3: If the current character is a '#' [and the previous
+ // characters was not part of a word], it and all subsequent characters
+ // up to, but excluding, the next <newline> shall be discarded as a
+ // comment. The <newline> that ends the line is not considered part of
+ // the comment.
+ if (hasToken) {
+ token.writeCharCode($hash);
+ break;
+ }
+
+ while (!scanner.isDone && scanner.peekChar() != $lf) {
+ scanner.readChar();
+ }
+
+ case $space:
+ case $tab:
+ case $lf:
+ // ignore: invariant_booleans
+ if (hasToken) results.add(token.toString());
+ hasToken = false;
+ token.clear();
+
+ default:
+ hasToken = true;
+ token.writeCharCode(next);
+ }
+ }
+
+ if (hasToken) results.add(token.toString());
+ return results;
+}
+
+/// Throws a [FormatException] if [scanner] is done indicating that a closing
+/// quote matching the one at position [openingQuote] is missing.
+void _checkUnmatchedQuote(StringScanner scanner, int openingQuote) {
+ if (!scanner.isDone) return;
+ final type = scanner.substring(openingQuote, openingQuote + 1) == '"'
+ ? 'double'
+ : 'single';
+ scanner.error('Unmatched $type quote.', position: openingQuote, length: 1);
+}
diff --git a/pkgs/io/pubspec.yaml b/pkgs/io/pubspec.yaml
new file mode 100644
index 0000000..46b76b3
--- /dev/null
+++ b/pkgs/io/pubspec.yaml
@@ -0,0 +1,19 @@
+name: io
+description: >-
+ Utilities for the Dart VM Runtime including support for ANSI colors, file
+ copying, and standard exit code values.
+version: 1.1.0-wip
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/io
+
+environment:
+ sdk: ^3.4.0
+
+dependencies:
+ meta: ^1.3.0
+ path: ^1.8.0
+ string_scanner: ^1.1.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ test: ^1.16.6
+ test_descriptor: ^2.0.0
diff --git a/pkgs/io/test/_files/is_executable.sh b/pkgs/io/test/_files/is_executable.sh
new file mode 100755
index 0000000..f1f641a
--- /dev/null
+++ b/pkgs/io/test/_files/is_executable.sh
@@ -0,0 +1 @@
+#!/usr/bin/env bash
diff --git a/pkgs/io/test/_files/is_not_executable.sh b/pkgs/io/test/_files/is_not_executable.sh
new file mode 100644
index 0000000..f1f641a
--- /dev/null
+++ b/pkgs/io/test/_files/is_not_executable.sh
@@ -0,0 +1 @@
+#!/usr/bin/env bash
diff --git a/pkgs/io/test/_files/stderr_hello.dart b/pkgs/io/test/_files/stderr_hello.dart
new file mode 100644
index 0000000..ac7a7d3
--- /dev/null
+++ b/pkgs/io/test/_files/stderr_hello.dart
@@ -0,0 +1,7 @@
+// Copyright (c) 2017, 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';
+
+void main() => stderr.write('Hello');
diff --git a/pkgs/io/test/_files/stdin_echo.dart b/pkgs/io/test/_files/stdin_echo.dart
new file mode 100644
index 0000000..256e0ee
--- /dev/null
+++ b/pkgs/io/test/_files/stdin_echo.dart
@@ -0,0 +1,7 @@
+// Copyright (c) 2017, 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';
+
+void main() => stdout.writeln('You said: ${stdin.readLineSync()}');
diff --git a/pkgs/io/test/_files/stdout_hello.dart b/pkgs/io/test/_files/stdout_hello.dart
new file mode 100644
index 0000000..af3bf51
--- /dev/null
+++ b/pkgs/io/test/_files/stdout_hello.dart
@@ -0,0 +1,7 @@
+// Copyright (c) 2017, 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';
+
+void main() => stdout.write('Hello');
diff --git a/pkgs/io/test/ansi_code_test.dart b/pkgs/io/test/ansi_code_test.dart
new file mode 100644
index 0000000..98ae68b
--- /dev/null
+++ b/pkgs/io/test/ansi_code_test.dart
@@ -0,0 +1,187 @@
+// Copyright (c) 2017, 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:io';
+
+import 'package:io/ansi.dart';
+import 'package:test/test.dart';
+
+const _ansiEscapeLiteral = '\x1B';
+const _ansiEscapeForScript = r'\033';
+const sampleInput = 'sample input';
+
+void main() {
+ group('ansiOutputEnabled', () {
+ test('default value matches dart:io', () {
+ expect(ansiOutputEnabled,
+ stdout.supportsAnsiEscapes && stderr.supportsAnsiEscapes);
+ });
+
+ test('override true', () {
+ overrideAnsiOutput(true, () {
+ expect(ansiOutputEnabled, isTrue);
+ });
+ });
+
+ test('override false', () {
+ overrideAnsiOutput(false, () {
+ expect(ansiOutputEnabled, isFalse);
+ });
+ });
+
+ test('forScript variaents ignore `ansiOutputEnabled`', () {
+ const expected =
+ '$_ansiEscapeForScript[34m$sampleInput$_ansiEscapeForScript[0m';
+
+ for (var override in [true, false]) {
+ overrideAnsiOutput(override, () {
+ expect(blue.escapeForScript, '$_ansiEscapeForScript[34m');
+ expect(blue.wrap(sampleInput, forScript: true), expected);
+ expect(wrapWith(sampleInput, [blue], forScript: true), expected);
+ });
+ }
+ });
+ });
+
+ test('foreground and background colors match', () {
+ expect(foregroundColors, hasLength(backgroundColors.length));
+
+ for (var i = 0; i < foregroundColors.length; i++) {
+ final foreground = foregroundColors[i];
+ expect(foreground.type, AnsiCodeType.foreground);
+ expect(foreground.name.toLowerCase(), foreground.name,
+ reason: 'All names should be lower case');
+ final background = backgroundColors[i];
+ expect(background.type, AnsiCodeType.background);
+ expect(background.name.toLowerCase(), background.name,
+ reason: 'All names should be lower case');
+
+ expect(foreground.name, background.name);
+
+ // The last base-10 digit also matches – good to sanity check
+ expect(foreground.code % 10, background.code % 10);
+ }
+ });
+
+ test('all styles are styles', () {
+ for (var style in styles) {
+ expect(style.type, AnsiCodeType.style);
+ expect(style.name.toLowerCase(), style.name,
+ reason: 'All names should be lower case');
+ if (style == styleBold) {
+ expect(style.reset, resetBold);
+ } else {
+ expect(style.reset!.code, equals(style.code + 20));
+ }
+ expect(style.name, equals(style.reset!.name));
+ }
+ });
+
+ for (var forScript in [true, false]) {
+ group(forScript ? 'forScript' : 'escaped', () {
+ final escapeLiteral =
+ forScript ? _ansiEscapeForScript : _ansiEscapeLiteral;
+
+ group('wrap', () {
+ _test('color', () {
+ final expected = '$escapeLiteral[34m$sampleInput$escapeLiteral[0m';
+
+ expect(blue.wrap(sampleInput, forScript: forScript), expected);
+ });
+
+ _test('style', () {
+ final expected = '$escapeLiteral[1m$sampleInput$escapeLiteral[22m';
+
+ expect(styleBold.wrap(sampleInput, forScript: forScript), expected);
+ });
+
+ _test('style', () {
+ final expected = '$escapeLiteral[34m$sampleInput$escapeLiteral[0m';
+
+ expect(blue.wrap(sampleInput, forScript: forScript), expected);
+ });
+
+ test('empty', () {
+ expect(blue.wrap('', forScript: forScript), '');
+ });
+
+ test(null, () {
+ expect(blue.wrap(null, forScript: forScript), isNull);
+ });
+ });
+
+ group('wrapWith', () {
+ _test('foreground', () {
+ final expected = '$escapeLiteral[34m$sampleInput$escapeLiteral[0m';
+
+ expect(wrapWith(sampleInput, [blue], forScript: forScript), expected);
+ });
+
+ _test('background', () {
+ final expected = '$escapeLiteral[44m$sampleInput$escapeLiteral[0m';
+
+ expect(wrapWith(sampleInput, [backgroundBlue], forScript: forScript),
+ expected);
+ });
+
+ _test('style', () {
+ final expected = '$escapeLiteral[1m$sampleInput$escapeLiteral[0m';
+
+ expect(wrapWith(sampleInput, [styleBold], forScript: forScript),
+ expected);
+ });
+
+ _test('2 styles', () {
+ final expected = '$escapeLiteral[1;3m$sampleInput$escapeLiteral[0m';
+
+ expect(
+ wrapWith(sampleInput, [styleBold, styleItalic],
+ forScript: forScript),
+ expected);
+ });
+
+ _test('2 foregrounds', () {
+ expect(
+ () => wrapWith(sampleInput, [blue, white], forScript: forScript),
+ throwsArgumentError);
+ });
+
+ _test('multi', () {
+ final expected =
+ '$escapeLiteral[1;4;34;107m$sampleInput$escapeLiteral[0m';
+
+ expect(
+ wrapWith(sampleInput,
+ [blue, backgroundWhite, styleBold, styleUnderlined],
+ forScript: forScript),
+ expected);
+ });
+
+ test('no codes', () {
+ expect(wrapWith(sampleInput, []), sampleInput);
+ });
+
+ _test('empty', () {
+ expect(
+ wrapWith('', [blue, backgroundWhite, styleBold],
+ forScript: forScript),
+ '');
+ });
+
+ _test('null', () {
+ expect(
+ wrapWith(null, [blue, backgroundWhite, styleBold],
+ forScript: forScript),
+ isNull);
+ });
+ });
+ });
+ }
+}
+
+void _test<T>(String name, T Function() body) =>
+ test(name, () => overrideAnsiOutput<T>(true, body));
diff --git a/pkgs/io/test/copy_path_test.dart b/pkgs/io/test/copy_path_test.dart
new file mode 100644
index 0000000..df10395
--- /dev/null
+++ b/pkgs/io/test/copy_path_test.dart
@@ -0,0 +1,126 @@
+// Copyright (c) 2017, 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:io';
+
+import 'package:io/io.dart';
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+void main() {
+ test('should copy a directory (async)', () async {
+ await _create();
+ await copyPath(p.join(d.sandbox, _parentDir), p.join(d.sandbox, _copyDir));
+ await _validate();
+ });
+
+ test('should copy a directory (sync)', () async {
+ await _create();
+ copyPathSync(p.join(d.sandbox, _parentDir), p.join(d.sandbox, _copyDir));
+ await _validate();
+ });
+
+ test('should catch an infinite operation', () async {
+ await _create();
+ expect(
+ copyPath(
+ p.join(d.sandbox, _parentDir),
+ p.join(d.sandbox, _parentDir, 'child'),
+ ),
+ throwsArgumentError,
+ );
+ });
+
+ group('links', () {
+ const linkTarget = 'link_target';
+ const linkSource = 'link_source';
+ const linkContent = 'link_content.txt';
+ late String targetPath;
+ setUp(() async {
+ await _create();
+ await d
+ .dir(linkTarget, [d.file(linkContent, 'original content')]).create();
+ targetPath = p.join(d.sandbox, linkTarget);
+ await Link(p.join(d.sandbox, _parentDir, linkSource)).create(targetPath);
+ });
+
+ test('are shallow copied with deepCopyLinks: false in copyPath', () async {
+ await copyPath(
+ deepCopyLinks: false,
+ p.join(d.sandbox, _parentDir),
+ p.join(d.sandbox, _copyDir));
+
+ final expectedLink = Link(p.join(d.sandbox, _copyDir, linkSource));
+ expect(await expectedLink.exists(), isTrue);
+ expect(await expectedLink.target(), targetPath);
+ });
+
+ test('are shallow copied with deepCopyLinks: false in copyPathSync',
+ () async {
+ copyPathSync(
+ deepCopyLinks: false,
+ p.join(d.sandbox, _parentDir),
+ p.join(d.sandbox, _copyDir));
+
+ final expectedLink = Link(p.join(d.sandbox, _copyDir, linkSource));
+ expect(await expectedLink.exists(), isTrue);
+ expect(await expectedLink.target(), targetPath);
+ });
+
+ test('are deep copied by default in copyPath', () async {
+ await copyPath(
+ p.join(d.sandbox, _parentDir), p.join(d.sandbox, _copyDir));
+
+ final expectedDir = Directory(p.join(d.sandbox, _copyDir, linkSource));
+ final expectedFile =
+ File(p.join(d.sandbox, _copyDir, linkSource, linkContent));
+ expect(await expectedDir.exists(), isTrue);
+ expect(await expectedFile.exists(), isTrue);
+
+ expect(await expectedFile.readAsString(), 'original content',
+ reason: 'The file behind the link was copied with invalid content');
+
+ await expectedFile.writeAsString('new content');
+ final originalFile =
+ File(p.join(d.sandbox, _parentDir, linkSource, linkContent));
+ expect(await originalFile.readAsString(), 'original content',
+ reason: 'The file behind the link should not change');
+ });
+
+ test('are deep copied by default in copyPathSync', () async {
+ copyPathSync(p.join(d.sandbox, _parentDir), p.join(d.sandbox, _copyDir));
+
+ final expectedDir = Directory(p.join(d.sandbox, _copyDir, linkSource));
+ final expectedFile =
+ File(p.join(d.sandbox, _copyDir, linkSource, linkContent));
+ expect(await expectedDir.exists(), isTrue);
+ expect(await expectedFile.exists(), isTrue);
+
+ expect(await expectedFile.readAsString(), 'original content',
+ reason: 'The file behind the link was copied with invalid content');
+
+ await expectedFile.writeAsString('new content');
+ final originalFile =
+ File(p.join(d.sandbox, _parentDir, linkSource, linkContent));
+ expect(await originalFile.readAsString(), 'original content',
+ reason: 'The file behind the link should not change');
+ });
+ });
+}
+
+const _parentDir = 'parent';
+const _copyDir = 'copy';
+
+d.DirectoryDescriptor _struct(String dirName) => d.dir(dirName, [
+ d.dir('child', [
+ d.file('foo.txt'),
+ ]),
+ ]);
+
+Future<void> _create() => _struct(_parentDir).create();
+Future<void> _validate() => _struct(_copyDir).validate();
diff --git a/pkgs/io/test/permissions_test.dart b/pkgs/io/test/permissions_test.dart
new file mode 100644
index 0000000..478e8df
--- /dev/null
+++ b/pkgs/io/test/permissions_test.dart
@@ -0,0 +1,37 @@
+// Copyright (c) 2017, 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 'package:io/io.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('isExecutable', () {
+ const files = 'test/_files';
+ const shellIsExec = '$files/is_executable.sh';
+ const shellNotExec = '$files/is_not_executable.sh';
+
+ group('on shell scripts', () {
+ test('should return true for "is_executable.sh"', () async {
+ expect(await isExecutable(shellIsExec), isTrue);
+ });
+
+ test('should return false for "is_not_executable.sh"', () async {
+ expect(await isExecutable(shellNotExec), isFalse);
+ });
+ }, testOn: '!windows');
+
+ group('on shell scripts [windows]', () {
+ test('should return true for "is_executable.sh"', () async {
+ expect(await isExecutable(shellIsExec, isWindows: true), isTrue);
+ });
+
+ test('should return true for "is_not_executable.sh"', () async {
+ expect(await isExecutable(shellNotExec, isWindows: true), isTrue);
+ });
+ });
+ });
+}
diff --git a/pkgs/io/test/process_manager_test.dart b/pkgs/io/test/process_manager_test.dart
new file mode 100644
index 0000000..9871a77
--- /dev/null
+++ b/pkgs/io/test/process_manager_test.dart
@@ -0,0 +1,100 @@
+// Copyright (c) 2017, 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: close_sinks
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:io/io.dart' hide sharedStdIn;
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+
+void main() {
+ StreamController<String> fakeStdIn;
+ late ProcessManager processManager;
+ SharedStdIn sharedStdIn;
+ late List<String> stdoutLog;
+ late List<String> stderrLog;
+
+ test('spawn functions should match the type definition of Process.start', () {
+ const isStartProcess = TypeMatcher<StartProcess>();
+ expect(Process.start, isStartProcess);
+ final manager = ProcessManager();
+ expect(manager.spawn, isStartProcess);
+ expect(manager.spawnBackground, isStartProcess);
+ expect(manager.spawnDetached, isStartProcess);
+ });
+
+ group('spawn', () {
+ setUp(() async {
+ fakeStdIn = StreamController<String>(sync: true);
+ sharedStdIn = SharedStdIn(fakeStdIn.stream.map((s) => s.codeUnits));
+ stdoutLog = <String>[];
+ stderrLog = <String>[];
+
+ final stdoutController = StreamController<List<int>>(sync: true);
+ stdoutController.stream.map(utf8.decode).listen(stdoutLog.add);
+ final stdout = IOSink(stdoutController);
+ final stderrController = StreamController<List<int>>(sync: true);
+ stderrController.stream.map(utf8.decode).listen(stderrLog.add);
+ final stderr = IOSink(stderrController);
+
+ processManager = ProcessManager(
+ stdin: sharedStdIn,
+ stdout: stdout,
+ stderr: stderr,
+ );
+ });
+
+ final dart = Platform.executable;
+
+ test('should output Hello from another process [via stdout]', () async {
+ final spawn = await processManager.spawn(
+ dart,
+ [p.join('test', '_files', 'stdout_hello.dart')],
+ );
+ await spawn.exitCode;
+ expect(stdoutLog, ['Hello']);
+ });
+
+ test('should output Hello from another process [via stderr]', () async {
+ final spawn = await processManager.spawn(
+ dart,
+ [p.join('test', '_files', 'stderr_hello.dart')],
+ );
+ await spawn.exitCode;
+ expect(stderrLog, ['Hello']);
+ });
+
+ test('should forward stdin to another process', () async {
+ final spawn = await processManager.spawn(
+ dart,
+ [p.join('test', '_files', 'stdin_echo.dart')],
+ );
+ spawn.stdin.writeln('Ping');
+ await spawn.exitCode;
+ expect(stdoutLog.join(), contains('You said: Ping'));
+ });
+
+ group('should return a Process where', () {
+ test('.stdout is readable', () async {
+ final spawn = await processManager.spawn(
+ dart,
+ [p.join('test', '_files', 'stdout_hello.dart')],
+ );
+ expect(await spawn.stdout.transform(utf8.decoder).first, 'Hello');
+ });
+
+ test('.stderr is readable', () async {
+ final spawn = await processManager.spawn(
+ dart,
+ [p.join('test', '_files', 'stderr_hello.dart')],
+ );
+ expect(await spawn.stderr.transform(utf8.decoder).first, 'Hello');
+ });
+ });
+ });
+}
diff --git a/pkgs/io/test/shared_stdin_test.dart b/pkgs/io/test/shared_stdin_test.dart
new file mode 100644
index 0000000..71629ec
--- /dev/null
+++ b/pkgs/io/test/shared_stdin_test.dart
@@ -0,0 +1,80 @@
+// Copyright (c) 2017, 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 'package:io/io.dart' hide sharedStdIn;
+import 'package:test/test.dart';
+
+void main() {
+ // ignore: close_sinks
+ late StreamController<String> fakeStdIn;
+ late SharedStdIn sharedStdIn;
+
+ setUp(() {
+ fakeStdIn = StreamController<String>(sync: true);
+ sharedStdIn = SharedStdIn(fakeStdIn.stream.map((s) => s.codeUnits));
+ });
+
+ test('should allow a single subscriber', () async {
+ final logs = <String>[];
+ final sub = sharedStdIn.transform(utf8.decoder).listen(logs.add);
+ fakeStdIn.add('Hello World');
+ await sub.cancel();
+ expect(logs, ['Hello World']);
+ });
+
+ test('should allow multiple subscribers', () async {
+ final logs = <String>[];
+ final asUtf8 = sharedStdIn.transform(utf8.decoder);
+ var sub = asUtf8.listen(logs.add);
+ fakeStdIn.add('Hello World');
+ await sub.cancel();
+ sub = asUtf8.listen(logs.add);
+ fakeStdIn.add('Goodbye World');
+ await sub.cancel();
+ expect(logs, ['Hello World', 'Goodbye World']);
+ });
+
+ test('should throw if a subscriber is still active', () async {
+ final active = sharedStdIn.listen((_) {});
+ expect(() => sharedStdIn.listen((_) {}), throwsStateError);
+ await active.cancel();
+ expect(() => sharedStdIn.listen((_) {}), returnsNormally);
+ });
+
+ test('should return a stream of lines', () async {
+ expect(
+ sharedStdIn.lines(),
+ emitsInOrder(<dynamic>[
+ 'I',
+ 'Think',
+ 'Therefore',
+ 'I',
+ 'Am',
+ ]),
+ );
+ [
+ 'I\nThink\n',
+ 'Therefore\n',
+ 'I\n',
+ 'Am\n',
+ ].forEach(fakeStdIn.add);
+ });
+
+ test('should return the next line', () {
+ expect(sharedStdIn.nextLine(), completion('Hello World'));
+ fakeStdIn.add('Hello World\n');
+ });
+
+ test('should allow listening for new lines multiple times', () async {
+ expect(sharedStdIn.nextLine(), completion('Hello World'));
+ fakeStdIn.add('Hello World\n');
+ await Future<void>.value();
+
+ expect(sharedStdIn.nextLine(), completion('Hello World'));
+ fakeStdIn.add('Hello World\n');
+ });
+}
diff --git a/pkgs/io/test/shell_words_test.dart b/pkgs/io/test/shell_words_test.dart
new file mode 100644
index 0000000..dc4441c
--- /dev/null
+++ b/pkgs/io/test/shell_words_test.dart
@@ -0,0 +1,185 @@
+// Copyright (c) 2017, 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:io/io.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('shellSplit()', () {
+ group('returns an empty list for', () {
+ test('an empty string', () {
+ expect(shellSplit(''), isEmpty);
+ });
+
+ test('spaces', () {
+ expect(shellSplit(' '), isEmpty);
+ });
+
+ test('tabs', () {
+ expect(shellSplit('\t\t\t'), isEmpty);
+ });
+
+ test('newlines', () {
+ expect(shellSplit('\n\n\n'), isEmpty);
+ });
+
+ test('a comment', () {
+ expect(shellSplit('#foo bar baz'), isEmpty);
+ });
+
+ test('a mix', () {
+ expect(shellSplit(' \t\n# foo'), isEmpty);
+ });
+ });
+
+ group('parses unquoted', () {
+ test('a single token', () {
+ expect(shellSplit('foo'), equals(['foo']));
+ });
+
+ test('multiple tokens', () {
+ expect(shellSplit('foo bar baz'), equals(['foo', 'bar', 'baz']));
+ });
+
+ test('tokens separated by tabs', () {
+ expect(shellSplit('foo\tbar\tbaz'), equals(['foo', 'bar', 'baz']));
+ });
+
+ test('tokens separated by newlines', () {
+ expect(shellSplit('foo\nbar\nbaz'), equals(['foo', 'bar', 'baz']));
+ });
+
+ test('a token after whitespace', () {
+ expect(shellSplit(' \t\nfoo'), equals(['foo']));
+ });
+
+ test('a token before whitespace', () {
+ expect(shellSplit('foo \t\n'), equals(['foo']));
+ });
+
+ test('a token with a hash', () {
+ expect(shellSplit('foo#bar'), equals(['foo#bar']));
+ });
+
+ test('a token before a comment', () {
+ expect(shellSplit('foo #bar'), equals(['foo']));
+ });
+
+ test('dynamic shell features', () {
+ expect(
+ shellSplit(r'foo $(bar baz)'), equals(['foo', r'$(bar', 'baz)']));
+ expect(shellSplit('foo `bar baz`'), equals(['foo', '`bar', 'baz`']));
+ expect(shellSplit(r'foo $bar | baz'),
+ equals(['foo', r'$bar', '|', 'baz']));
+ });
+ });
+
+ group('parses a backslash', () {
+ test('before a normal character', () {
+ expect(shellSplit(r'foo\bar'), equals(['foobar']));
+ });
+
+ test('before a dynamic shell feature', () {
+ expect(shellSplit(r'foo\$bar'), equals([r'foo$bar']));
+ });
+
+ test('before a single quote', () {
+ expect(shellSplit(r"foo\'bar"), equals(["foo'bar"]));
+ });
+
+ test('before a double quote', () {
+ expect(shellSplit(r'foo\"bar'), equals(['foo"bar']));
+ });
+
+ test('before a space', () {
+ expect(shellSplit(r'foo\ bar'), equals(['foo bar']));
+ });
+
+ test('at the beginning of a token', () {
+ expect(shellSplit(r'\ foo'), equals([' foo']));
+ });
+
+ test('before whitespace followed by a hash', () {
+ expect(shellSplit(r'\ #foo'), equals([' #foo']));
+ });
+
+ test('before a newline in a token', () {
+ expect(shellSplit('foo\\\nbar'), equals(['foobar']));
+ });
+
+ test('before a newline outside a token', () {
+ expect(shellSplit('foo \\\n bar'), equals(['foo', 'bar']));
+ });
+
+ test('before a backslash', () {
+ expect(shellSplit(r'foo\\bar'), equals([r'foo\bar']));
+ });
+ });
+
+ group('parses single quotes', () {
+ test('that are empty', () {
+ expect(shellSplit("''"), equals(['']));
+ });
+
+ test('that contain normal characters', () {
+ expect(shellSplit("'foo'"), equals(['foo']));
+ });
+
+ test('that contain active characters', () {
+ expect(shellSplit("'\" \\#'"), equals([r'" \#']));
+ });
+
+ test('before a hash', () {
+ expect(shellSplit("''#foo"), equals([r'#foo']));
+ });
+
+ test('inside a token', () {
+ expect(shellSplit("foo'bar baz'qux"), equals([r'foobar bazqux']));
+ });
+
+ test('without a closing quote', () {
+ expect(() => shellSplit("'foo bar"), throwsFormatException);
+ });
+ });
+
+ group('parses double quotes', () {
+ test('that are empty', () {
+ expect(shellSplit('""'), equals(['']));
+ });
+
+ test('that contain normal characters', () {
+ expect(shellSplit('"foo"'), equals(['foo']));
+ });
+
+ test('that contain otherwise-active characters', () {
+ expect(shellSplit('"\' #"'), equals(["' #"]));
+ });
+
+ test('that contain escaped characters', () {
+ expect(shellSplit(r'"\$\`\"\\"'), equals([r'$`"\']));
+ });
+
+ test('that contain an escaped newline', () {
+ expect(shellSplit('"\\\n"'), equals(['']));
+ });
+
+ test("that contain a backslash that's not an escape", () {
+ expect(shellSplit(r'"f\oo"'), equals([r'f\oo']));
+ });
+
+ test('before a hash', () {
+ expect(shellSplit('""#foo'), equals([r'#foo']));
+ });
+
+ test('inside a token', () {
+ expect(shellSplit('foo"bar baz"qux'), equals([r'foobar bazqux']));
+ });
+
+ test('without a closing quote', () {
+ expect(() => shellSplit('"foo bar'), throwsFormatException);
+ expect(() => shellSplit(r'"foo bar\'), throwsFormatException);
+ });
+ });
+ });
+}
diff --git a/pkgs/json_rpc_2/.gitignore b/pkgs/json_rpc_2/.gitignore
new file mode 100644
index 0000000..ab3cb76
--- /dev/null
+++ b/pkgs/json_rpc_2/.gitignore
@@ -0,0 +1,16 @@
+# Don’t commit the following directories created by pub.
+.buildlog
+.dart_tool/
+.pub/
+build/
+packages
+.packages
+
+# Or the files created by dart2js.
+*.dart.js
+*.js_
+*.js.deps
+*.js.map
+
+# Include when developing application packages.
+pubspec.lock
diff --git a/pkgs/json_rpc_2/.test_config b/pkgs/json_rpc_2/.test_config
new file mode 100644
index 0000000..412fc5c
--- /dev/null
+++ b/pkgs/json_rpc_2/.test_config
@@ -0,0 +1,3 @@
+{
+ "test_package": true
+}
\ No newline at end of file
diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md
new file mode 100644
index 0000000..1f2cf8e
--- /dev/null
+++ b/pkgs/json_rpc_2/CHANGELOG.md
@@ -0,0 +1,151 @@
+## 3.0.3
+
+* Require Dart 3.4
+* Move to `dart-lang/tools` monorepo.
+
+## 3.0.2
+
+* Switch to using `package:lints`.
+* Address a few analysis hint violations.
+* Populate the pubspec `repository` field.
+
+## 3.0.1
+
+* Fix a bug where a `null` result to a request caused an exception.
+
+## 3.0.0
+
+* Migrate to null safety.
+* Accept responses even if the server converts the ID to a String.
+
+## 2.2.2
+
+* Fix `Peer.close()` throwing `Bad state: Future already completed`.
+
+## 2.2.1
+
+* Fix `Peer` requests not terminating when the underlying channel is closed.
+
+## 2.2.0
+
+* Added `strictProtocolChecks` named parameter to `Server` and `Peer`
+ constructors. Setting this parameter to false will result in the server not
+ rejecting requests missing the `jsonrpc` parameter.
+
+## 2.1.1
+
+* Fixed issue where throwing `RpcException.methodNotFound` in an asynchronous
+ fallback handler would not result in the next fallback being executed.
+* Updated minimum SDK to Dart `2.2.0`.
+
+## 2.1.0
+
+* `Server` and related classes can now take an `onUnhandledError` callback to
+ notify callers of unhandled exceptions.
+
+## 2.0.10
+
+* Allow `stream_channel` version 2.x
+
+## 2.0.8
+
+* Updated SDK version to 2.0.0-dev.17.0
+
+## 2.0.7
+
+* When a `Client` is closed before a request completes, the error sent to that
+ request's `Future` now includes the request method to aid in debugging.
+
+## 2.0.6
+
+* Internal changes only.
+
+## 2.0.5
+
+* Internal changes only.
+
+## 2.0.4
+
+* `Client.sendRequest()` now throws a `StateError` if the client is closed while
+ the request is in-flight. This avoids dangling `Future`s that will never be
+ completed.
+
+* Both `Client.sendRequest()` and `Client.sendNotification()` now throw
+ `StateError`s if they're called after the client is closed.
+
+## 2.0.3
+
+* Fix new strong-mode warnings.
+
+## 2.0.2
+
+* Fix all strong-mode warnings.
+
+## 2.0.1
+
+* Fix a race condition in which a `StateError` could be top-leveled if
+ `Peer.close()` was called before the underlying channel closed.
+
+## 2.0.0
+
+* **Breaking change:** all constructors now take a `StreamChannel` rather than a
+ `Stream`/`StreamSink` pair.
+
+* `Client.sendRequest()` and `Client.sendNotification()` no longer throw
+ `StateError`s after the connection has been closed but before `Client.close()`
+ has been called.
+
+* The various `close()` methods may now be called before their corresponding
+ `listen()` methods.
+
+* The various `close()` methods now wait on the result of closing the underlying
+ `StreamSink`. Be aware that [in some circumstances][issue 19095]
+ `StreamController`s' `Sink.close()` futures may never complete.
+
+[issue 19095]: https://github.com/dart-lang/sdk/issues/19095
+
+## 1.2.0
+
+* Add `Client.isClosed` and `Server.isClosed`, which make it possible to
+ synchronously determine whether the connection is open. In particular, this
+ makes it possible to reliably tell whether it's safe to call
+ `Client.sendRequest`.
+
+* Fix a race condition in `Server` where a `StateError` could be thrown if the
+ connection was closed in the middle of handling a request.
+
+* Improve stack traces for error responses.
+
+## 1.1.1
+
+* Update the README to match the current API.
+
+## 1.1.0
+
+* Add a `done` getter to `Client`, `Server`, and `Peer`.
+
+## 1.0.0
+
+* Add a `Client` class for communicating with external JSON-RPC 2.0 servers.
+
+* Add a `Peer` class that's both a `Client` and a `Server`.
+
+## 0.1.0
+
+* Remove `Server.handleRequest()` and `Server.parseRequest()`. Instead, `new
+ Server()` takes a `Stream` and a `StreamSink` and uses those behind-the-scenes
+ for its communication.
+
+* Add `Server.listen()`, which causes the server to begin listening to the
+ underlying request stream.
+
+* Add `Server.close()`, which closes the underlying request stream and response
+ sink.
+
+## 0.0.2+3
+
+* Widen the version constraint for `stack_trace`.
+
+## 0.0.2+2
+
+* Fix error response to include data from `RpcException` when not a map.
diff --git a/pkgs/json_rpc_2/LICENSE b/pkgs/json_rpc_2/LICENSE
new file mode 100644
index 0000000..000cd7b
--- /dev/null
+++ b/pkgs/json_rpc_2/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2014, 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.
diff --git a/pkgs/json_rpc_2/README.md b/pkgs/json_rpc_2/README.md
new file mode 100644
index 0000000..a7dda4a
--- /dev/null
+++ b/pkgs/json_rpc_2/README.md
@@ -0,0 +1,150 @@
+[](https://github.com/dart-lang/tools/actions/workflows/json_rpc_2.yaml)
+[](https://pub.dev/packages/json_rpc_2)
+[](https://pub.dev/packages/json_rpc_2/publisher)
+
+A library that implements the [JSON-RPC 2.0 spec][spec].
+
+[spec]: https://www.jsonrpc.org/specification
+
+## Server
+
+A JSON-RPC 2.0 server exposes a set of methods that can be called by clients.
+These methods can be registered using `Server.registerMethod`:
+
+```dart
+import 'dart:io';
+
+import 'package:json_rpc_2/json_rpc_2.dart';
+import 'package:web_socket_channel/io.dart';
+import 'package:web_socket_channel/web_socket_channel.dart';
+
+void main() async {
+ var httpServer = await HttpServer.bind(InternetAddress.loopbackIPv4, 4321);
+ var connectedChannels =
+ httpServer.transform(WebSocketTransformer()).map(IOWebSocketChannel.new);
+ connectedChannels.listen(handleClient);
+}
+
+void handleClient(WebSocketChannel socket) {
+ // The socket is a `StreamChannel<dynamic>` because it might emit binary
+ // `List<int>`, but JSON RPC 2 only works with Strings so we assert it only
+ // emits those by casting it.
+ var server = Server(socket.cast<String>());
+
+ // Any string may be used as a method name. JSON-RPC 2.0 methods are
+ // case-sensitive.
+ var i = 0;
+ server.registerMethod('count', () {
+ // Just return the value to be sent as a response to the client. This can
+ // be anything JSON-serializable, or a Future that completes to something
+ // JSON-serializable.
+ return i++;
+ });
+
+ // Methods can take parameters. They're presented as a `Parameters` object
+ // which makes it easy to validate that the expected parameters exist.
+ server.registerMethod('echo', (Parameters params) {
+ // If the request doesn't have a "message" parameter this will
+ // automatically send a response notifying the client that the request
+ // was invalid.
+ return params['message'].value;
+ });
+
+ // `Parameters` has methods for verifying argument types.
+ server.registerMethod('subtract', (Parameters params) {
+ // If "minuend" or "subtrahend" aren't numbers, this will reject the
+ // request.
+ return params['minuend'].asNum - params['subtrahend'].asNum;
+ });
+
+ // [Parameters] also supports optional arguments.
+ server.registerMethod('sort', (Parameters params) {
+ var list = params['list'].asList;
+ list.sort();
+ if (params['descendint'].asBoolOr(false)) {
+ return list.reversed;
+ } else {
+ return list;
+ }
+ });
+
+ // A method can send an error response by throwing a `RpcException`.
+ // Any positive number may be used as an application- defined error code.
+ const dividByZero = 1;
+ server.registerMethod('divide', (Parameters params) {
+ var divisor = params['divisor'].asNum;
+ if (divisor == 0) {
+ throw RpcException(dividByZero, 'Cannot divide by zero.');
+ }
+
+ return params['dividend'].asNum / divisor;
+ });
+
+ // To give you time to register all your methods, the server won't start
+ // listening for requests until you call `listen`. Messages are buffered until
+ // listen is called. The returned Future won't complete until the connection
+ // is closed.
+ server.listen();
+}
+```
+
+## Client
+
+A JSON-RPC 2.0 client calls methods on a server and handles the server's
+responses to those method calls. These methods can be called using
+`Client.sendRequest`:
+
+```dart
+import 'package:json_rpc_2/json_rpc_2.dart';
+import 'package:pedantic/pedantic.dart';
+import 'package:web_socket_channel/web_socket_channel.dart';
+
+void main() async {
+ var socket = WebSocketChannel.connect(Uri.parse('ws://localhost:4321'));
+ var client = Client(socket.cast<String>());
+
+ // The client won't subscribe to the input stream until you call `listen`.
+ // The returned Future won't complete until the connection is closed.
+ unawaited(client.listen());
+
+ // This calls the "count" method on the server. A Future is returned that
+ // will complete to the value contained in the server's response.
+ var count = await client.sendRequest('count');
+ print('Count is $count');
+
+ // Parameters are passed as a simple Map or, for positional parameters, an
+ // Iterable. Make sure they're JSON-serializable!
+ var echo = await client.sendRequest('echo', {'message': 'hello'});
+ print('Echo says "$echo"!');
+
+ // A notification is a way to call a method that tells the server that no
+ // result is expected. Its return type is `void`; even if it causes an
+ // error, you won't hear back.
+ client.sendNotification('count');
+
+ // If the server sends an error response, the returned Future will complete
+ // with an RpcException. You can catch this error and inspect its error
+ // code, message, and any data that the server sent along with it.
+ try {
+ await client.sendRequest('divide', {'dividend': 2, 'divisor': 0});
+ } on RpcException catch (error) {
+ print('RPC error ${error.code}: ${error.message}');
+ }
+
+ await client.close();
+}
+```
+
+## Peer
+
+Although JSON-RPC 2.0 only explicitly describes clients and servers, it also
+mentions that two-way communication can be supported by making each endpoint
+both a client and a server. This package supports this directly using the `Peer`
+class, which implements both `Client` and `Server`. It supports the same methods
+as those classes, and automatically makes sure that every message from the other
+endpoint is routed and handled correctly.
+
+## Publishing automation
+
+For information about our publishing automation and release process, see
+https://github.com/dart-lang/ecosystem/wiki/Publishing-automation.
diff --git a/pkgs/json_rpc_2/analysis_options.yaml b/pkgs/json_rpc_2/analysis_options.yaml
new file mode 100644
index 0000000..295b7cc
--- /dev/null
+++ b/pkgs/json_rpc_2/analysis_options.yaml
@@ -0,0 +1,12 @@
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-casts: false
+ errors:
+ avoid_dynamic_calls: ignore
+
+linter:
+ rules:
+ - avoid_unused_constructor_parameters
+ - cancel_subscriptions
diff --git a/pkgs/json_rpc_2/example/client.dart b/pkgs/json_rpc_2/example/client.dart
new file mode 100644
index 0000000..aa8f7ed
--- /dev/null
+++ b/pkgs/json_rpc_2/example/client.dart
@@ -0,0 +1,43 @@
+// Copyright (c) 2020, 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 'package:json_rpc_2/json_rpc_2.dart';
+import 'package:web_socket_channel/web_socket_channel.dart';
+
+void main() async {
+ var socket = WebSocketChannel.connect(Uri.parse('ws://localhost:4321'));
+ var client = Client(socket.cast<String>());
+
+ // The client won't subscribe to the input stream until you call `listen`.
+ // The returned Future won't complete until the connection is closed.
+ unawaited(client.listen());
+
+ // This calls the "count" method on the server. A Future is returned that
+ // will complete to the value contained in the server's response.
+ var count = await client.sendRequest('count');
+ print('Count is $count');
+
+ // Parameters are passed as a simple Map or, for positional parameters, an
+ // Iterable. Make sure they're JSON-serializable!
+ var echo = await client.sendRequest('echo', {'message': 'hello'});
+ print('Echo says "$echo"!');
+
+ // A notification is a way to call a method that tells the server that no
+ // result is expected. Its return type is `void`; even if it causes an
+ // error, you won't hear back.
+ client.sendNotification('count');
+
+ // If the server sends an error response, the returned Future will complete
+ // with an RpcException. You can catch this error and inspect its error
+ // code, message, and any data that the server sent along with it.
+ try {
+ await client.sendRequest('divide', {'dividend': 2, 'divisor': 0});
+ } on RpcException catch (error) {
+ print('RPC error ${error.code}: ${error.message}');
+ }
+
+ await client.close();
+}
diff --git a/pkgs/json_rpc_2/example/main.dart b/pkgs/json_rpc_2/example/main.dart
new file mode 100644
index 0000000..7d5ab73
--- /dev/null
+++ b/pkgs/json_rpc_2/example/main.dart
@@ -0,0 +1,78 @@
+// Copyright (c) 2020, 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:json_rpc_2/json_rpc_2.dart';
+import 'package:web_socket_channel/io.dart';
+import 'package:web_socket_channel/web_socket_channel.dart';
+
+void main() async {
+ var httpServer = await HttpServer.bind(InternetAddress.loopbackIPv4, 4321);
+ var connectedChannels =
+ httpServer.transform(WebSocketTransformer()).map(IOWebSocketChannel.new);
+ connectedChannels.listen(handleClient);
+}
+
+void handleClient(WebSocketChannel socket) {
+ // The socket is a `StreamChannel<dynamic>` because it might emit binary
+ // `List<int>`, but JSON RPC 2 only works with Strings so we assert it only
+ // emits those by casting it.
+ var server = Server(socket.cast<String>());
+
+ // Any string may be used as a method name. JSON-RPC 2.0 methods are
+ // case-sensitive.
+ var i = 0;
+ server.registerMethod('count', () {
+ // Just return the value to be sent as a response to the client. This can
+ // be anything JSON-serializable, or a Future that completes to something
+ // JSON-serializable.
+ return i++;
+ });
+
+ // Methods can take parameters. They're presented as a `Parameters` object
+ // which makes it easy to validate that the expected parameters exist.
+ server.registerMethod('echo', (Parameters params) {
+ // If the request doesn't have a "message" parameter this will
+ // automatically send a response notifying the client that the request
+ // was invalid.
+ return params['message'].value;
+ });
+
+ // `Parameters` has methods for verifying argument types.
+ server.registerMethod('subtract', (Parameters params) {
+ // If "minuend" or "subtrahend" aren't numbers, this will reject the
+ // request.
+ return params['minuend'].asNum - params['subtrahend'].asNum;
+ });
+
+ // [Parameters] also supports optional arguments.
+ server.registerMethod('sort', (Parameters params) {
+ var list = params['list'].asList;
+ list.sort();
+ if (params['descendint'].asBoolOr(false)) {
+ return list.reversed;
+ } else {
+ return list;
+ }
+ });
+
+ // A method can send an error response by throwing a `RpcException`.
+ // Any positive number may be used as an application- defined error code.
+ const dividByZero = 1;
+ server.registerMethod('divide', (Parameters params) {
+ var divisor = params['divisor'].asNum;
+ if (divisor == 0) {
+ throw RpcException(dividByZero, 'Cannot divide by zero.');
+ }
+
+ return params['dividend'].asNum / divisor;
+ });
+
+ // To give you time to register all your methods, the server won't start
+ // listening for requests until you call `listen`. Messages are buffered until
+ // listen is called. The returned Future won't complete until the connection
+ // is closed.
+ server.listen();
+}
diff --git a/pkgs/json_rpc_2/lib/error_code.dart b/pkgs/json_rpc_2/lib/error_code.dart
new file mode 100644
index 0000000..5f90791
--- /dev/null
+++ b/pkgs/json_rpc_2/lib/error_code.dart
@@ -0,0 +1,51 @@
+// Copyright (c) 2014, 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: constant_identifier_names
+
+import 'src/exception.dart';
+
+/// Error codes defined in the [JSON-RPC 2.0 specification][spec].
+///
+/// These codes are generally used for protocol-level communication. Most of
+/// them shouldn't be used by the application. Those that should have
+/// convenience constructors in [RpcException].
+///
+/// [spec]: http://www.jsonrpc.org/specification#error_object
+/// An error code indicating that invalid JSON was received by the server.
+const PARSE_ERROR = -32700;
+
+/// An error code indicating that the request JSON was invalid according to the
+/// JSON-RPC 2.0 spec.
+const INVALID_REQUEST = -32600;
+
+/// An error code indicating that the requested method does not exist or is
+/// unavailable.
+const METHOD_NOT_FOUND = -32601;
+
+/// An error code indicating that the request parameters are invalid for the
+/// requested method.
+const INVALID_PARAMS = -32602;
+
+/// An internal JSON-RPC error.
+const INTERNAL_ERROR = -32603;
+
+/// An unexpected error occurred on the server.
+///
+/// The spec reserves the range from -32000 to -32099 for implementation-defined
+/// server exceptions, but for now we only use one of those values.
+const SERVER_ERROR = -32000;
+
+/// Returns a human-readable name for [errorCode] if it's one specified by the
+/// JSON-RPC 2.0 spec.
+///
+/// If [errorCode] isn't defined in the JSON-RPC 2.0 spec, returns `null`.
+String? name(int errorCode) => switch (errorCode) {
+ PARSE_ERROR => 'parse error',
+ INVALID_REQUEST => 'invalid request',
+ METHOD_NOT_FOUND => 'method not found',
+ INVALID_PARAMS => 'invalid parameters',
+ INTERNAL_ERROR => 'internal error',
+ _ => null
+ };
diff --git a/pkgs/json_rpc_2/lib/json_rpc_2.dart b/pkgs/json_rpc_2/lib/json_rpc_2.dart
new file mode 100644
index 0000000..33e5f49
--- /dev/null
+++ b/pkgs/json_rpc_2/lib/json_rpc_2.dart
@@ -0,0 +1,9 @@
+// Copyright (c) 2014, 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/client.dart';
+export 'src/exception.dart';
+export 'src/parameters.dart';
+export 'src/peer.dart';
+export 'src/server.dart';
diff --git a/pkgs/json_rpc_2/lib/src/client.dart b/pkgs/json_rpc_2/lib/src/client.dart
new file mode 100644
index 0000000..182f945
--- /dev/null
+++ b/pkgs/json_rpc_2/lib/src/client.dart
@@ -0,0 +1,246 @@
+// Copyright (c) 2014, 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 'package:stack_trace/stack_trace.dart';
+import 'package:stream_channel/stream_channel.dart';
+
+import 'exception.dart';
+import 'utils.dart';
+
+/// A JSON-RPC 2.0 client.
+///
+/// A client calls methods on a server and handles the server's responses to
+/// those method calls. Methods can be called with [sendRequest], or with
+/// [sendNotification] if no response is expected.
+class Client {
+ final StreamChannel<dynamic> _channel;
+
+ /// The next request id.
+ var _id = 0;
+
+ /// The current batch of requests to be sent together.
+ ///
+ /// Each element is a JSON RPC spec compliant message.
+ List<Map<String, dynamic>>? _batch;
+
+ /// The map of request ids to pending requests.
+ final _pendingRequests = <int, _Request>{};
+
+ final _done = Completer<void>();
+
+ /// Returns a [Future] that completes when the underlying connection is
+ /// closed.
+ ///
+ /// This is the same future that's returned by [listen] and [close]. It may
+ /// complete before [close] is called if the remote endpoint closes the
+ /// connection.
+ Future get done => _done.future;
+
+ /// Whether the underlying connection is closed.
+ ///
+ /// Note that this will be `true` before [close] is called if the remote
+ /// endpoint closes the connection.
+ bool get isClosed => _done.isCompleted;
+
+ /// Creates a [Client] that communicates over [channel].
+ ///
+ /// Note that the client won't begin listening to [channel] until
+ /// [Client.listen] is called.
+ Client(StreamChannel<String> channel)
+ : this.withoutJson(
+ jsonDocument.bind(channel).transformStream(ignoreFormatExceptions));
+
+ /// Creates a [Client] that communicates using decoded messages over
+ /// [_channel].
+ ///
+ /// Unlike [Client.new], this doesn't read or write JSON strings. Instead, it
+ /// reads and writes decoded maps or lists.
+ ///
+ /// Note that the client won't begin listening to [_channel] until
+ /// [Client.listen] is called.
+ Client.withoutJson(this._channel) {
+ done.whenComplete(() {
+ for (var request in _pendingRequests.values) {
+ request.completer.completeError(StateError(
+ 'The client closed with pending request "${request.method}".'));
+ }
+ _pendingRequests.clear();
+ }).catchError((_) {
+ // Avoid an unhandled error.
+ });
+ }
+
+ /// Starts listening to the underlying stream.
+ ///
+ /// Returns a [Future] that will complete when the connection is closed or
+ /// when it has an error. This is the same as [done].
+ ///
+ /// [listen] may only be called once.
+ Future listen() {
+ _channel.stream.listen(_handleResponse,
+ onError: (Object error, StackTrace stackTrace) {
+ _done.completeError(error, stackTrace);
+ _channel.sink.close();
+ }, onDone: () {
+ if (!_done.isCompleted) _done.complete();
+ close();
+ });
+ return done;
+ }
+
+ /// Closes the underlying connection.
+ ///
+ /// Returns a [Future] that completes when all resources have been released.
+ /// This is the same as [done].
+ Future close() {
+ _channel.sink.close();
+ if (!_done.isCompleted) _done.complete();
+ return done;
+ }
+
+ /// Sends a JSON-RPC 2 request to invoke the given [method].
+ ///
+ /// If passed, [parameters] is the parameters for the method. This must be
+ /// either an [Iterable] (to pass parameters by position) or a [Map] with
+ /// [String] keys (to pass parameters by name). Either way, it must be
+ /// JSON-serializable.
+ ///
+ /// If the request succeeds, this returns the response result as a decoded
+ /// JSON-serializable object. If it fails, it throws an [RpcException]
+ /// describing the failure.
+ ///
+ /// 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<Object?> sendRequest(String method, [Object? parameters]) {
+ var id = _id++;
+ _send(method, parameters, id);
+
+ var completer = Completer<Object?>.sync();
+ _pendingRequests[id] = _Request(method, completer, Chain.current());
+ return completer.future;
+ }
+
+ /// Sends a JSON-RPC 2 request to invoke the given [method] without expecting
+ /// a response.
+ ///
+ /// If passed, [parameters] is the parameters for the method. This must be
+ /// either an [Iterable] (to pass parameters by position) or a [Map] with
+ /// [String] keys (to pass parameters by name). Either way, it must be
+ /// JSON-serializable.
+ ///
+ /// Since this is just a notification to which the server isn't expected to
+ /// send a response, it has no return value.
+ ///
+ /// Throws a [StateError] if the client is closed when this method is called.
+ void sendNotification(String method, [Object? parameters]) =>
+ _send(method, parameters);
+
+ /// A helper method for [sendRequest] and [sendNotification].
+ ///
+ /// 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]) {
+ 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 '
+ 'parameters, was "$parameters".');
+ }
+ if (isClosed) throw StateError('The client is closed.');
+
+ var message = <String, dynamic>{'jsonrpc': '2.0', 'method': method};
+ if (id != null) message['id'] = id;
+ if (parameters != null) message['params'] = parameters;
+
+ if (_batch != null) {
+ _batch!.add(message);
+ } else {
+ _channel.sink.add(message);
+ }
+ }
+
+ /// Runs [callback] and batches any requests sent until it returns.
+ ///
+ /// A batch of requests is sent in a single message on the underlying stream,
+ /// and the responses are likewise sent back in a single message.
+ ///
+ /// [callback] may be synchronous or asynchronous. If it returns a [Future],
+ /// requests will be batched until that Future returns; otherwise, requests
+ /// will only be batched while synchronously executing [callback].
+ ///
+ /// If this is called in the context of another [withBatch] call, it just
+ /// invokes [callback] without creating another batch. This means that
+ /// responses are batched until the first batch ends.
+ void withBatch(FutureOr<void> Function() callback) {
+ if (_batch != null) {
+ callback();
+ return;
+ }
+
+ _batch = [];
+ return tryFinally(callback, () {
+ _channel.sink.add(_batch);
+ _batch = null;
+ });
+ }
+
+ /// Handles a decoded response from the server.
+ void _handleResponse(Object? response) {
+ if (response is List) {
+ response.forEach(_handleSingleResponse);
+ } else {
+ _handleSingleResponse(response);
+ }
+ }
+
+ /// Handles a decoded response from the server after batches have been
+ /// resolved.
+ void _handleSingleResponse(Object? response_) {
+ 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']);
+ } else {
+ request.completer.completeError(
+ RpcException(response['error']['code'], response['error']['message'],
+ data: response['error']['data']),
+ request.chain);
+ }
+ }
+
+ /// Determines whether the server's response is valid per the spec.
+ bool _isResponseValid(Object? response) {
+ 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;
+
+ if (!response.containsKey('error')) return false;
+ var error = response['error'];
+ if (error is! Map) return false;
+ if (error['code'] is! int) return false;
+ if (error['message'] is! String) return false;
+ return true;
+ }
+}
+
+/// A pending request to the server.
+class _Request {
+ /// THe method that was sent.
+ final String method;
+
+ /// The completer to use to complete the response future.
+ final Completer completer;
+
+ /// The stack chain from where the request was made.
+ final Chain chain;
+
+ _Request(this.method, this.completer, this.chain);
+}
diff --git a/pkgs/json_rpc_2/lib/src/exception.dart b/pkgs/json_rpc_2/lib/src/exception.dart
new file mode 100644
index 0000000..906a053
--- /dev/null
+++ b/pkgs/json_rpc_2/lib/src/exception.dart
@@ -0,0 +1,75 @@
+// Copyright (c) 2014, 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 '../error_code.dart' as error_code;
+
+/// An exception from a JSON-RPC server that can be translated into an error
+/// response.
+class RpcException implements Exception {
+ /// The error code.
+ ///
+ /// All non-negative error codes are available for use by application
+ /// developers.
+ final int code;
+
+ /// The error message.
+ ///
+ /// This should be limited to a concise single sentence. Further information
+ /// should be supplied via [data].
+ final String message;
+
+ /// Extra application-defined information about the error.
+ ///
+ /// This must be a JSON-serializable object. If it's a [Map] without a
+ /// `"request"` key, a copy of the request that caused the error will
+ /// automatically be injected.
+ final Object? data;
+
+ RpcException(this.code, this.message, {this.data});
+
+ /// An exception indicating that the method named [methodName] was not found.
+ ///
+ /// This should usually be used only by fallback handlers.
+ RpcException.methodNotFound(String methodName)
+ : this(error_code.METHOD_NOT_FOUND, 'Unknown method "$methodName".');
+
+ /// An exception indicating that the parameters for the requested method were
+ /// invalid.
+ ///
+ /// Methods can use this to reject requests with invalid parameters.
+ RpcException.invalidParams(String message)
+ : this(error_code.INVALID_PARAMS, message);
+
+ /// Converts this exception into a JSON-serializable object that's a valid
+ /// JSON-RPC 2.0 error response.
+ Map<String, dynamic> serialize(Object? request) {
+ dynamic modifiedData;
+ if (data is Map && !(data as Map).containsKey('request')) {
+ modifiedData = {
+ ...data as Map,
+ 'request': request,
+ };
+ } else if (data == null) {
+ modifiedData = {'request': request};
+ } else {
+ modifiedData = data;
+ }
+
+ var id = request is Map ? request['id'] : null;
+ if (id is! String && id is! num) id = null;
+ return {
+ 'jsonrpc': '2.0',
+ 'error': {'code': code, 'message': message, 'data': modifiedData},
+ 'id': id
+ };
+ }
+
+ @override
+ String toString() {
+ var prefix = 'JSON-RPC error $code';
+ var errorName = error_code.name(code);
+ if (errorName != null) prefix += ' ($errorName)';
+ return '$prefix: $message';
+ }
+}
diff --git a/pkgs/json_rpc_2/lib/src/parameters.dart b/pkgs/json_rpc_2/lib/src/parameters.dart
new file mode 100644
index 0000000..0a18882
--- /dev/null
+++ b/pkgs/json_rpc_2/lib/src/parameters.dart
@@ -0,0 +1,348 @@
+// Copyright (c) 2014, 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 'exception.dart';
+
+/// A wrapper for the parameters to a server method.
+///
+/// JSON-RPC 2.0 allows parameters that are either a list or a map. This class
+/// provides functions that not only assert that the parameters object is the
+/// correct type, but also that the expected arguments exist and are themselves
+/// the correct type.
+///
+/// Example usage:
+///
+/// server.registerMethod("subtract", (params) {
+/// return params["minuend"].asNum - params["subtrahend"].asNum;
+/// });
+class Parameters {
+ /// The name of the method that this request called.
+ final String method;
+
+ /// The underlying value of the parameters object.
+ ///
+ /// If this is accessed for a [Parameter] that was not passed, the request
+ /// will be automatically rejected. To avoid this, use [Parameter.valueOr].
+ final dynamic value;
+
+ Parameters(this.method, this.value);
+
+ /// Returns a single parameter.
+ ///
+ /// If [key] is a [String], the request is expected to provide named
+ /// parameters. If it's an [int], the request is expected to provide
+ /// positional parameters. Requests that don't do so will be rejected
+ /// automatically.
+ ///
+ /// Whether or not the given parameter exists, this returns a [Parameter]
+ /// object. If a parameter's value is accessed through a getter like [value]
+ /// or [Parameter.asNum], the request will be rejected if that parameter
+ /// doesn't exist. On the other hand, if it's accessed through a method with a
+ /// default value like [Parameter.valueOr] or [Parameter.asNumOr], the default
+ /// value will be returned.
+ Parameter operator [](Object? key) {
+ if (key is int) {
+ _assertPositional();
+ if (key < value.length) {
+ return Parameter._(method, value[key], this, key);
+ } else {
+ return _MissingParameter(method, this, key);
+ }
+ } else if (key is String) {
+ _assertNamed();
+ if (value.containsKey(key)) {
+ return Parameter._(method, value[key], this, key);
+ } else {
+ return _MissingParameter(method, this, key);
+ }
+ } else {
+ throw ArgumentError('Parameters[] only takes an int or a string, was '
+ '"$key".');
+ }
+ }
+
+ /// Asserts that [value] exists and is a [List] and returns it.
+ List get asList {
+ _assertPositional();
+ return value;
+ }
+
+ /// Asserts that [value] exists and is a [Map] and returns it.
+ Map get asMap {
+ _assertNamed();
+ return value;
+ }
+
+ /// Asserts that [value] is a positional argument list.
+ void _assertPositional() {
+ if (value is List) return;
+ throw RpcException.invalidParams('Parameters for method "$method" '
+ 'must be passed by position.');
+ }
+
+ /// Asserts that [value] is a named argument map.
+ void _assertNamed() {
+ if (value is Map) return;
+ throw RpcException.invalidParams('Parameters for method "$method" '
+ 'must be passed by name.');
+ }
+}
+
+/// A wrapper for a single parameter to a server method.
+///
+/// This provides numerous functions for asserting the type of the parameter in
+/// question. These functions each have a version that asserts that the
+/// parameter exists (for example, [asNum] and [asString]) and a version that
+/// returns a default value if the parameter doesn't exist (for example,
+/// [asNumOr] and [asStringOr]). If an assertion fails, the request is
+/// automatically rejected.
+///
+/// This extends [Parameters] to make it easy to access nested parameters. For
+/// example:
+///
+/// // "params.value" is "{'scores': {'home': [5, 10, 17]}}"
+/// params['scores']['home'][2].asInt // => 17
+class Parameter extends Parameters {
+ // The parent parameters, used to construct [_path].
+ final Parameters _parent;
+
+ /// The key used to access `this`, used to construct [_path].
+ final Object _key;
+
+ /// A human-readable representation of the path of getters used to get this.
+ ///
+ /// Named parameters are represented as `.name`, whereas positional parameters
+ /// are represented as `[index]`. For example: `"foo[0].bar.baz"`. Named
+ /// parameters that contain characters that are neither alphanumeric,
+ /// underscores, or hyphens will be JSON-encoded. For example: `"foo
+ /// bar"."baz.bang"`. If quotes are used for an individual component, they
+ /// won't be used for the entire string.
+ ///
+ /// An exception is made for single-level parameters. A single-level
+ /// positional parameter is just represented by the index plus one, because
+ /// "parameter 1" is clearer than "parameter [0]". A single-level named
+ /// parameter is represented by that name in quotes.
+ String get _path {
+ if (_parent is! Parameter) {
+ return _key is int ? (_key + 1).toString() : jsonEncode(_key);
+ }
+
+ String quoteKey(String key) {
+ if (key.contains(RegExp(r'[^a-zA-Z0-9_-]'))) return jsonEncode(key);
+ return key;
+ }
+
+ String computePath(Parameter params) {
+ if (params._parent is! Parameter) {
+ return params._key is int
+ ? '[${params._key}]'
+ : quoteKey(params._key as String);
+ }
+
+ var path = computePath(params._parent);
+ return params._key is int
+ ? '$path[${params._key}]'
+ : '$path.${quoteKey(params._key as String)}';
+ }
+
+ return computePath(this);
+ }
+
+ /// Whether this parameter exists.
+ bool get exists => true;
+
+ Parameter._(super.method, super.value, this._parent, this._key);
+
+ /// Returns [value], or [defaultValue] if this parameter wasn't passed.
+ dynamic valueOr(Object? defaultValue) => value;
+
+ /// Asserts that [value] exists and is a number and returns it.
+ ///
+ /// [asNumOr] may be used to provide a default value instead of rejecting the
+ /// request if [value] doesn't exist.
+ num get asNum => _getTyped('a number', (value) => value is num);
+
+ /// Asserts that [value] is a number and returns it.
+ ///
+ /// If [value] doesn't exist, this returns [defaultValue].
+ num asNumOr(num defaultValue) => asNum;
+
+ /// Asserts that [value] exists and is an integer and returns it.
+ ///
+ /// [asIntOr] may be used to provide a default value instead of rejecting the
+ /// request if [value] doesn't exist.
+ ///
+ /// Note that which values count as integers varies between the Dart VM and
+ /// dart2js. The value `1.0` will be considered an integer under dart2js but
+ /// not under the VM.
+ int get asInt => _getTyped('an integer', (value) => value is int);
+
+ /// Asserts that [value] is an integer and returns it.
+ ///
+ /// If [value] doesn't exist, this returns [defaultValue].
+ ///
+ /// Note that which values count as integers varies between the Dart VM and
+ /// dart2js. The value `1.0` will be considered an integer under dart2js but
+ /// not under the VM.
+ int asIntOr(int defaultValue) => asInt;
+
+ /// Asserts that [value] exists and is a boolean and returns it.
+ ///
+ /// [asBoolOr] may be used to provide a default value instead of rejecting the
+ /// request if [value] doesn't exist.
+ bool get asBool => _getTyped('a boolean', (value) => value is bool);
+
+ /// Asserts that [value] is a boolean and returns it.
+ ///
+ /// If [value] doesn't exist, this returns [defaultValue].
+ bool asBoolOr(bool defaultValue) => asBool;
+
+ /// Asserts that [value] exists and is a string and returns it.
+ ///
+ /// [asStringOr] may be used to provide a default value instead of rejecting
+ /// the request if [value] doesn't exist.
+ String get asString => _getTyped('a string', (value) => value is String);
+
+ /// Asserts that [value] is a string and returns it.
+ ///
+ /// If [value] doesn't exist, this returns [defaultValue].
+ String asStringOr(String defaultValue) => asString;
+
+ /// Asserts that [value] exists and is a [List] and returns it.
+ ///
+ /// [asListOr] may be used to provide a default value instead of rejecting the
+ /// request if [value] doesn't exist.
+ @override
+ List get asList => _getTyped('an Array', (value) => value is List);
+
+ /// Asserts that [value] is a [List] and returns it.
+ ///
+ /// If [value] doesn't exist, this returns [defaultValue].
+ List asListOr(List defaultValue) => asList;
+
+ /// Asserts that [value] exists and is a [Map] and returns it.
+ ///
+ /// [asMapOr] may be used to provide a default value instead of rejecting the
+ /// request if [value] doesn't exist.
+ @override
+ Map get asMap => _getTyped('an Object', (value) => value is Map);
+
+ /// Asserts that [value] is a [Map] and returns it.
+ ///
+ /// If [value] doesn't exist, this returns [defaultValue].
+ Map asMapOr(Map defaultValue) => asMap;
+
+ /// Asserts that [value] exists, is a string, and can be parsed as a
+ /// [DateTime] and returns it.
+ ///
+ /// [asDateTimeOr] may be used to provide a default value instead of rejecting
+ /// the request if [value] doesn't exist.
+ DateTime get asDateTime => _getParsed('date/time', DateTime.parse);
+
+ /// Asserts that [value] exists, is a string, and can be parsed as a
+ /// [DateTime] and returns it.
+ ///
+ /// If [value] doesn't exist, this returns [defaultValue].
+ DateTime asDateTimeOr(DateTime defaultValue) => asDateTime;
+
+ /// Asserts that [value] exists, is a string, and can be parsed as a
+ /// [Uri] and returns it.
+ ///
+ /// [asUriOr] may be used to provide a default value instead of rejecting the
+ /// request if [value] doesn't exist.
+ Uri get asUri => _getParsed('URI', Uri.parse);
+
+ /// Asserts that [value] exists, is a string, and can be parsed as a
+ /// [Uri] and returns it.
+ ///
+ /// If [value] doesn't exist, this returns [defaultValue].
+ Uri asUriOr(Uri defaultValue) => asUri;
+
+ /// Get a parameter named [type] that matches [test].
+ ///
+ /// [type] is used for the error message. It should begin with an indefinite
+ /// article.
+ dynamic _getTyped(String type, bool Function(dynamic) test) {
+ if (test(value)) return value;
+ throw RpcException.invalidParams('Parameter $_path for method '
+ '"$method" must be $type, but was ${jsonEncode(value)}.');
+ }
+
+ dynamic _getParsed(String description, void Function(String) parse) {
+ var string = asString;
+ try {
+ return parse(string);
+ } on FormatException catch (error) {
+ // DateTime.parse doesn't actually include any useful information in the
+ // FormatException, just the string that was being parsed. There's no use
+ // in including that in the RPC exception. See issue 17753.
+ var message = error.message;
+ if (message == string) {
+ message = '';
+ } else {
+ message = '\n$message';
+ }
+
+ throw RpcException.invalidParams('Parameter $_path for method '
+ '"$method" must be a valid $description, but was '
+ '${jsonEncode(string)}.$message');
+ }
+ }
+
+ @override
+ void _assertPositional() {
+ // Throw the standard exception for a mis-typed list.
+ asList;
+ }
+
+ @override
+ void _assertNamed() {
+ // Throw the standard exception for a mis-typed map.
+ asMap;
+ }
+}
+
+/// A subclass of [Parameter] representing a missing parameter.
+class _MissingParameter extends Parameter {
+ @override
+ dynamic get value {
+ throw RpcException.invalidParams('Request for method "$method" is '
+ 'missing required parameter $_path.');
+ }
+
+ @override
+ bool get exists => false;
+
+ _MissingParameter(String method, Parameters parent, Object key)
+ : super._(method, null, parent, key);
+
+ @override
+ dynamic valueOr(Object? defaultValue) => defaultValue;
+
+ @override
+ num asNumOr(num defaultValue) => defaultValue;
+
+ @override
+ int asIntOr(int defaultValue) => defaultValue;
+
+ @override
+ bool asBoolOr(bool defaultValue) => defaultValue;
+
+ @override
+ String asStringOr(String defaultValue) => defaultValue;
+
+ @override
+ List asListOr(List defaultValue) => defaultValue;
+
+ @override
+ Map asMapOr(Map defaultValue) => defaultValue;
+
+ @override
+ DateTime asDateTimeOr(DateTime defaultValue) => defaultValue;
+
+ @override
+ Uri asUriOr(Uri defaultValue) => defaultValue;
+}
diff --git a/pkgs/json_rpc_2/lib/src/peer.dart b/pkgs/json_rpc_2/lib/src/peer.dart
new file mode 100644
index 0000000..677b6e1
--- /dev/null
+++ b/pkgs/json_rpc_2/lib/src/peer.dart
@@ -0,0 +1,156 @@
+// Copyright (c) 2014, 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 'package:stream_channel/stream_channel.dart';
+
+import 'client.dart';
+import 'parameters.dart';
+import 'server.dart';
+import 'utils.dart';
+
+/// A JSON-RPC 2.0 client *and* server.
+///
+/// This supports bidirectional peer-to-peer communication with another JSON-RPC
+/// 2.0 endpoint. It sends both requests and responses across the same
+/// communication channel and expects to connect to a peer that does the same.
+class Peer implements Client, Server {
+ final StreamChannel<dynamic> _channel;
+
+ /// The underlying client that handles request-sending and response-receiving
+ /// logic.
+ late final Client _client;
+
+ /// The underlying server that handles request-receiving and response-sending
+ /// logic.
+ late final Server _server;
+
+ /// A stream controller that forwards incoming messages to [_server] if
+ /// they're requests.
+ final _serverIncomingForwarder = StreamController<Object?>(sync: true);
+
+ /// A stream controller that forwards incoming messages to [_client] if
+ /// they're responses.
+ final _clientIncomingForwarder = StreamController<Object?>(sync: true);
+
+ @override
+ late final Future done = Future.wait([_client.done, _server.done]);
+
+ @override
+ bool get isClosed => _client.isClosed || _server.isClosed;
+
+ @override
+ ErrorCallback? get onUnhandledError => _server.onUnhandledError;
+
+ @override
+ bool get strictProtocolChecks => _server.strictProtocolChecks;
+
+ /// Creates a [Peer] that communicates over [channel].
+ ///
+ /// Note that the peer won't begin listening to [channel] until [Peer.listen]
+ /// is called.
+ ///
+ /// Unhandled exceptions in callbacks will be forwarded to [onUnhandledError].
+ /// If this is not provided, unhandled exceptions will be swallowed.
+ ///
+ /// If [strictProtocolChecks] is false, the underlying [Server] will accept
+ /// 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<String> channel,
+ {ErrorCallback? onUnhandledError, bool strictProtocolChecks = true})
+ : this.withoutJson(
+ jsonDocument.bind(channel).transform(respondToFormatExceptions),
+ onUnhandledError: onUnhandledError,
+ strictProtocolChecks: strictProtocolChecks);
+
+ /// Creates a [Peer] that communicates using decoded messages over [_channel].
+ ///
+ /// Unlike [Peer.new], this doesn't read or write JSON strings. Instead, it
+ /// reads and writes decoded maps or lists.
+ ///
+ /// Note that the peer won't begin listening to [_channel] until
+ /// [Peer.listen] is called.
+ ///
+ /// Unhandled exceptions in callbacks will be forwarded to [onUnhandledError].
+ /// If this is not provided, unhandled exceptions will be swallowed.
+ ///
+ /// If [strictProtocolChecks] is false, the underlying [Server] will accept
+ /// some requests which are not conformant with the JSON-RPC 2.0
+ /// specification. In particular, requests missing the `jsonrpc` parameter
+ /// will be accepted.
+ Peer.withoutJson(this._channel,
+ {ErrorCallback? onUnhandledError, bool strictProtocolChecks = true}) {
+ _server = Server.withoutJson(
+ StreamChannel(_serverIncomingForwarder.stream, _channel.sink),
+ onUnhandledError: onUnhandledError,
+ strictProtocolChecks: strictProtocolChecks);
+ _client = Client.withoutJson(
+ StreamChannel(_clientIncomingForwarder.stream, _channel.sink));
+ }
+
+ // Client methods.
+
+ @override
+ Future sendRequest(String method, [Object? parameters]) =>
+ _client.sendRequest(method, parameters);
+
+ @override
+ void sendNotification(String method, [Object? parameters]) =>
+ _client.sendNotification(method, parameters);
+
+ @override
+ void withBatch(void Function() callback) => _client.withBatch(callback);
+
+ // Server methods.
+
+ @override
+ void registerMethod(String name, Function callback) =>
+ _server.registerMethod(name, callback);
+
+ @override
+ void registerFallback(void Function(Parameters parameters) callback) =>
+ _server.registerFallback(callback);
+
+ // Shared methods.
+
+ @override
+ Future listen() {
+ _client.listen();
+ _server.listen();
+ _channel.stream.listen((message) {
+ if (message is Map) {
+ if (message.containsKey('result') || message.containsKey('error')) {
+ _clientIncomingForwarder.add(message);
+ } else {
+ _serverIncomingForwarder.add(message);
+ }
+ } else if (message is List &&
+ message.isNotEmpty &&
+ message.first is Map) {
+ if (message.first.containsKey('result') ||
+ message.first.containsKey('error')) {
+ _clientIncomingForwarder.add(message);
+ } else {
+ _serverIncomingForwarder.add(message);
+ }
+ } else {
+ // Non-Map and -List messages are ill-formed, so we pass them to the
+ // server since it knows how to send error responses.
+ _serverIncomingForwarder.add(message);
+ }
+ }, onError: (Object error, StackTrace stackTrace) {
+ _serverIncomingForwarder.addError(error, stackTrace);
+ }, onDone: close);
+ return done;
+ }
+
+ @override
+ Future close() {
+ _client.close();
+ _server.close();
+ return done;
+ }
+}
diff --git a/pkgs/json_rpc_2/lib/src/server.dart b/pkgs/json_rpc_2/lib/src/server.dart
new file mode 100644
index 0000000..2c58b79
--- /dev/null
+++ b/pkgs/json_rpc_2/lib/src/server.dart
@@ -0,0 +1,319 @@
+// Copyright (c) 2014, 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:collection';
+import 'dart:convert';
+
+import 'package:stack_trace/stack_trace.dart';
+import 'package:stream_channel/stream_channel.dart';
+
+import '../error_code.dart' as error_code;
+import 'exception.dart';
+import 'parameters.dart';
+import 'utils.dart';
+
+/// A callback for unhandled exceptions.
+typedef ErrorCallback = void Function(dynamic error, dynamic stackTrace);
+
+/// A JSON-RPC 2.0 server.
+///
+/// A server exposes methods that are called by requests, to which it provides
+/// responses. Methods can be registered using [registerMethod] and
+/// [registerFallback].
+///
+/// Note that since requests can arrive asynchronously and methods can run
+/// asynchronously, it's possible for multiple methods to be invoked at the same
+/// time, or even for a single method to be invoked multiple times at once.
+class Server {
+ final StreamChannel<dynamic> _channel;
+
+ /// The methods registered for this server.
+ final _methods = <String, Function>{};
+
+ /// The fallback methods for this server.
+ ///
+ /// These are tried in order until one of them doesn't throw a
+ /// [RpcException.methodNotFound] exception.
+ final _fallbacks = Queue<Function>();
+
+ final _done = Completer<void>();
+
+ /// Returns a [Future] that completes when the underlying connection is
+ /// closed.
+ ///
+ /// This is the same future that's returned by [listen] and [close]. It may
+ /// complete before [close] is called if the remote endpoint closes the
+ /// connection.
+ Future get done => _done.future;
+
+ /// Whether the underlying connection is closed.
+ ///
+ /// Note that this will be `true` before [close] is called if the remote
+ /// endpoint closes the connection.
+ bool get isClosed => _done.isCompleted;
+
+ /// A callback that is fired on unhandled exceptions.
+ ///
+ /// In the case where a user provided callback results in an exception that
+ /// cannot be properly routed back to the client, this handler will be
+ /// invoked. If it is not set, the exception will be swallowed.
+ final ErrorCallback? onUnhandledError;
+
+ /// Whether to strictly enforce the JSON-RPC 2.0 specification for received
+ /// messages.
+ ///
+ /// If `false`, this [Server] will accept some requests which are not
+ /// conformant with the JSON-RPC 2.0 specification. In particular, requests
+ /// missing the `jsonrpc` parameter will be accepted.
+ final bool strictProtocolChecks;
+
+ /// Creates a [Server] that communicates over [channel].
+ ///
+ /// Note that the server won't begin listening to [channel] until
+ /// [Server.listen] is called.
+ ///
+ /// Unhandled exceptions in callbacks will be forwarded to [onUnhandledError].
+ /// If this is not provided, unhandled exceptions will be swallowed.
+ ///
+ /// If [strictProtocolChecks] is false, this [Server] will accept some
+ /// requests which are not conformant with the JSON-RPC 2.0 specification. In
+ /// particular, requests missing the `jsonrpc` parameter will be accepted.
+ Server(StreamChannel<String> channel,
+ {ErrorCallback? onUnhandledError, bool strictProtocolChecks = true})
+ : this.withoutJson(
+ jsonDocument.bind(channel).transform(respondToFormatExceptions),
+ onUnhandledError: onUnhandledError,
+ strictProtocolChecks: strictProtocolChecks);
+
+ /// Creates a [Server] that communicates using decoded messages over
+ /// [_channel].
+ ///
+ /// Unlike [Server.new], this doesn't read or write JSON strings. Instead, it
+ /// reads and writes decoded maps or lists.
+ ///
+ /// Note that the server won't begin listening to [_channel] until
+ /// [Server.listen] is called.
+ ///
+ /// Unhandled exceptions in callbacks will be forwarded to [onUnhandledError].
+ /// If this is not provided, unhandled exceptions will be swallowed.
+ ///
+ /// If [strictProtocolChecks] is false, this [Server] will accept some
+ /// requests which are not conformant with the JSON-RPC 2.0 specification. In
+ /// particular, requests missing the `jsonrpc` parameter will be accepted.
+ Server.withoutJson(this._channel,
+ {this.onUnhandledError, this.strictProtocolChecks = true});
+
+ /// Starts listening to the underlying stream.
+ ///
+ /// Returns a [Future] that will complete when the connection is closed or
+ /// when it has an error. This is the same as [done].
+ ///
+ /// [listen] may only be called once.
+ Future listen() {
+ _channel.stream.listen(_handleRequest,
+ onError: (Object error, StackTrace stackTrace) {
+ _done.completeError(error, stackTrace);
+ _channel.sink.close();
+ }, onDone: () {
+ if (!_done.isCompleted) _done.complete();
+ });
+ return done;
+ }
+
+ /// Closes the underlying connection.
+ ///
+ /// Returns a [Future] that completes when all resources have been released.
+ /// This is the same as [done].
+ Future close() {
+ _channel.sink.close();
+ if (!_done.isCompleted) _done.complete();
+ return done;
+ }
+
+ /// Registers a method named [name] on this server.
+ ///
+ /// [callback] can take either zero or one arguments. If it takes zero, any
+ /// requests for that method that include parameters will be rejected. If it
+ /// takes one, it will be passed a [Parameters] object.
+ ///
+ /// [callback] can return either a JSON-serializable object or a Future that
+ /// completes to a JSON-serializable object. Any errors in [callback] will be
+ /// reported to the client as JSON-RPC 2.0 errors.
+ void registerMethod(String name, Function callback) {
+ if (_methods.containsKey(name)) {
+ throw ArgumentError('There\'s already a method named "$name".');
+ }
+
+ _methods[name] = callback;
+ }
+
+ /// Registers a fallback method on this server.
+ ///
+ /// A server may have any number of fallback methods. When a request comes in
+ /// that doesn't match any named methods, each fallback is tried in order. A
+ /// fallback can pass on handling a request by throwing a
+ /// [RpcException.methodNotFound] exception.
+ ///
+ /// [callback] can return either a JSON-serializable object or a Future that
+ /// completes to a JSON-serializable object. Any errors in [callback] will be
+ /// reported to the client as JSON-RPC 2.0 errors. [callback] may send custom
+ /// errors by throwing an [RpcException].
+ void registerFallback(void Function(Parameters parameters) callback) {
+ _fallbacks.add(callback);
+ }
+
+ /// Handle a request.
+ ///
+ /// [request] is expected to be a JSON-serializable object representing a
+ /// request sent by a client. This calls the appropriate method or methods for
+ /// handling that request and returns a JSON-serializable response, or `null`
+ /// if no response should be sent.
+ Future _handleRequest(Object? request) async {
+ dynamic response;
+ if (request is List) {
+ if (request.isEmpty) {
+ response = RpcException(error_code.INVALID_REQUEST,
+ 'A batch must contain at least one request.')
+ .serialize(request);
+ } else {
+ var results = await Future.wait(request.map(_handleSingleRequest));
+ var nonNull = results.where((result) => result != null);
+ if (nonNull.isEmpty) return;
+ response = nonNull.toList();
+ }
+ } else {
+ response = await _handleSingleRequest(request);
+ if (response == null) return;
+ }
+
+ if (!isClosed) _channel.sink.add(response);
+ }
+
+ /// Handles an individual parsed request.
+ Future _handleSingleRequest(Object? request) async {
+ try {
+ _validateRequest(request);
+ request = request as Map;
+
+ var name = request['method'];
+ var method = _methods[name];
+ method ??= _tryFallbacks;
+
+ Object? result;
+ if (method is ZeroArgumentFunction) {
+ if (request.containsKey('params')) {
+ throw RpcException.invalidParams('No parameters are allowed for '
+ 'method "$name".');
+ }
+ result = await method();
+ } else {
+ result = await method(Parameters(name, request['params']));
+ }
+
+ // A request without an id is a notification, which should not be sent a
+ // response, even if one is generated on the server.
+ if (!request.containsKey('id')) return null;
+
+ return {'jsonrpc': '2.0', 'result': result, 'id': request['id']};
+ } catch (error, stackTrace) {
+ if (error is RpcException) {
+ if (error.code == error_code.INVALID_REQUEST ||
+ (request is Map && request.containsKey('id'))) {
+ return error.serialize(request);
+ } else {
+ onUnhandledError?.call(error, stackTrace);
+ return null;
+ }
+ } else if (request is Map && !request.containsKey('id')) {
+ onUnhandledError?.call(error, stackTrace);
+ return null;
+ }
+ final chain = Chain.forTrace(stackTrace);
+ return RpcException(error_code.SERVER_ERROR, getErrorMessage(error),
+ data: {
+ 'full': '$error',
+ 'stack': '$chain',
+ }).serialize(request);
+ }
+ }
+
+ /// Validates that [request] matches the JSON-RPC spec.
+ void _validateRequest(Object? request) {
+ if (request is! Map) {
+ throw RpcException(
+ error_code.INVALID_REQUEST,
+ 'Request must be '
+ 'an Array or an Object.');
+ }
+
+ if (strictProtocolChecks && !request.containsKey('jsonrpc')) {
+ throw RpcException(
+ error_code.INVALID_REQUEST,
+ 'Request must '
+ 'contain a "jsonrpc" key.');
+ }
+
+ if ((strictProtocolChecks || request.containsKey('jsonrpc')) &&
+ request['jsonrpc'] != '2.0') {
+ throw RpcException(
+ error_code.INVALID_REQUEST,
+ 'Invalid JSON-RPC '
+ 'version ${jsonEncode(request['jsonrpc'])}, expected "2.0".');
+ }
+
+ if (!request.containsKey('method')) {
+ throw RpcException(
+ error_code.INVALID_REQUEST,
+ 'Request must '
+ 'contain a "method" key.');
+ }
+
+ var method = request['method'];
+ if (request['method'] is! String) {
+ throw RpcException(
+ error_code.INVALID_REQUEST,
+ 'Request method must '
+ 'be a string, but was ${jsonEncode(method)}.');
+ }
+
+ if (request.containsKey('params')) {
+ var params = request['params'];
+ if (params is! List && params is! Map) {
+ throw RpcException(
+ error_code.INVALID_REQUEST,
+ 'Request params must '
+ 'be an Array or an Object, but was ${jsonEncode(params)}.');
+ }
+ }
+
+ var id = request['id'];
+ if (id != null && id is! String && id is! num) {
+ throw RpcException(
+ error_code.INVALID_REQUEST,
+ 'Request id must be a '
+ 'string, number, or null, but was ${jsonEncode(id)}.');
+ }
+ }
+
+ /// Try all the fallback methods in order.
+ Future _tryFallbacks(Parameters params) {
+ var iterator = _fallbacks.toList().iterator;
+
+ Future tryNext() async {
+ if (!iterator.moveNext()) {
+ throw RpcException.methodNotFound(params.method);
+ }
+
+ try {
+ return await iterator.current(params);
+ } on RpcException catch (error) {
+ if (error.code != error_code.METHOD_NOT_FOUND) rethrow;
+ return tryNext();
+ }
+ }
+
+ return tryNext();
+ }
+}
diff --git a/pkgs/json_rpc_2/lib/src/utils.dart b/pkgs/json_rpc_2/lib/src/utils.dart
new file mode 100644
index 0000000..28bbf21
--- /dev/null
+++ b/pkgs/json_rpc_2/lib/src/utils.dart
@@ -0,0 +1,70 @@
+// Copyright (c) 2014, 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 'package:stream_channel/stream_channel.dart';
+
+import '../error_code.dart' as error_code;
+import 'exception.dart';
+
+typedef ZeroArgumentFunction = FutureOr Function();
+
+/// A regular expression to match the exception prefix that some exceptions'
+/// [Object.toString] values contain.
+final _exceptionPrefix = RegExp(r'^([A-Z][a-zA-Z]*)?(Exception|Error): ');
+
+/// Get a string description of an exception.
+///
+/// Many exceptions include the exception class name at the beginning of their
+/// `toString`, so we remove that if it exists.
+String getErrorMessage(Object error) =>
+ error.toString().replaceFirst(_exceptionPrefix, '');
+
+/// Like `try`/`finally`, run [body] and ensure that [whenComplete] runs
+/// afterwards, regardless of whether [body] succeeded.
+///
+/// This is synchronicity-agnostic relative to [body]. If [body] returns a
+/// [Future], this wil run asynchronously; otherwise it will run synchronously.
+void tryFinally(dynamic Function() body, void Function() whenComplete) {
+ dynamic result;
+ try {
+ result = body();
+ } catch (_) {
+ whenComplete();
+ rethrow;
+ }
+
+ if (result is! Future) {
+ whenComplete();
+ } else {
+ result.whenComplete(whenComplete);
+ }
+}
+
+/// A transformer that silently drops [FormatException]s.
+final ignoreFormatExceptions = StreamTransformer<Object?, Object?>.fromHandlers(
+ handleError: (error, stackTrace, sink) {
+ if (error is FormatException) return;
+ sink.addError(error, stackTrace);
+});
+
+/// A transformer that sends error responses on [FormatException]s.
+final StreamChannelTransformer<Object?, Object?> respondToFormatExceptions =
+ _RespondToFormatExceptionsTransformer();
+
+class _RespondToFormatExceptionsTransformer
+ implements StreamChannelTransformer<Object?, Object?> {
+ @override
+ StreamChannel<Object?> bind(StreamChannel<Object?> channel) {
+ return channel.changeStream((stream) {
+ return stream.handleError((dynamic error) {
+ final formatException = error as FormatException;
+ var exception = RpcException(
+ error_code.PARSE_ERROR, 'Invalid JSON: ${formatException.message}');
+ channel.sink.add(exception.serialize(formatException.source));
+ }, test: (error) => error is FormatException);
+ });
+ }
+}
diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml
new file mode 100644
index 0000000..7b42278
--- /dev/null
+++ b/pkgs/json_rpc_2/pubspec.yaml
@@ -0,0 +1,18 @@
+name: json_rpc_2
+version: 3.0.3
+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
+issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Ajson_rpc_2
+
+environment:
+ sdk: ^3.4.0
+
+dependencies:
+ stack_trace: ^1.10.0
+ stream_channel: ^2.1.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ test: ^1.25.5
+ web_socket_channel: ^3.0.0
diff --git a/pkgs/json_rpc_2/test/client/client_test.dart b/pkgs/json_rpc_2/test/client/client_test.dart
new file mode 100644
index 0000000..1a4f65d
--- /dev/null
+++ b/pkgs/json_rpc_2/test/client/client_test.dart
@@ -0,0 +1,218 @@
+// Copyright (c) 2014, 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:json_rpc_2/error_code.dart' as error_code;
+import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
+import 'package:test/test.dart';
+
+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'})
+ ]));
+
+ return {'jsonrpc': '2.0', 'result': 'bar', 'id': request['id']};
+ });
+
+ expect(controller.client.sendRequest('foo', {'param': 'value'}),
+ completion(equals('bar')));
+ });
+
+ 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'})
+ ]));
+
+ return {
+ 'jsonrpc': '2.0',
+ 'result': 'bar',
+ 'id': request['id'].toString()
+ };
+ });
+
+ 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'}
+ }));
+ });
+
+ controller.client.sendNotification('foo', {'param': 'value'});
+ });
+
+ 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']);
+ });
+
+ test('sends a notification with no parameters', () {
+ controller.expectRequest((request) {
+ expect(request, equals({'jsonrpc': '2.0', 'method': 'foo'}));
+ });
+
+ controller.client.sendNotification('foo');
+ });
+
+ test('sends a synchronous batch of requests', () {
+ controller.expectRequest((request) {
+ expect(request, isA<List>());
+ 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.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('sends an asynchronous batch of requests', () {
+ controller.expectRequest((request) {
+ expect(request, isA<List>());
+ 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.withBatch(() {
+ return Future<void>.value().then<void>((_) {
+ controller.client.sendNotification('foo');
+ }).then<void>((_) {
+ expect(controller.client.sendRequest('bar', {'param': 'value'}),
+ completion(equals('bar response')));
+ }).then<void>((_) {
+ expect(controller.client.sendRequest('baz'),
+ completion(equals('baz response')));
+ });
+ });
+ });
+
+ 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']
+ };
+ });
+
+ expect(
+ controller.client.sendRequest('foo', {'param': 'value'}),
+ throwsA(isA<json_rpc.RpcException>()
+ .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']
+ });
+
+ return pumpEventQueue().then(
+ (_) => {'jsonrpc': '2.0', 'result': 'right', 'id': request['id']});
+ });
+
+ expect(controller.client.sendRequest('foo'), completion(equals('right')));
+ });
+}
diff --git a/pkgs/json_rpc_2/test/client/stream_test.dart b/pkgs/json_rpc_2/test/client/stream_test.dart
new file mode 100644
index 0000000..b33778e
--- /dev/null
+++ b/pkgs/json_rpc_2/test/client/stream_test.dart
@@ -0,0 +1,97 @@
+// Copyright (c) 2014, 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 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test/test.dart';
+
+void main() {
+ late StreamController responseController;
+ late StreamController requestController;
+ late json_rpc.Client client;
+
+ setUp(() {
+ responseController = StreamController();
+ requestController = StreamController();
+ client = json_rpc.Client.withoutJson(
+ StreamChannel(responseController.stream, requestController.sink));
+ });
+
+ test('.withoutJson supports decoded stream and sink', () {
+ client.listen();
+
+ expect(requestController.stream.first.then((request) {
+ expect(
+ request,
+ allOf(
+ [containsPair('jsonrpc', '2.0'), containsPair('method', 'foo')]));
+
+ responseController
+ .add({'jsonrpc': '2.0', 'result': 'bar', 'id': request['id']});
+ }), completes);
+
+ client.sendRequest('foo');
+ });
+
+ test('.listen returns when the controller is closed', () {
+ var hasListenCompeted = false;
+ expect(client.listen().then((_) => hasListenCompeted = true), completes);
+
+ return pumpEventQueue().then((_) {
+ expect(hasListenCompeted, isFalse);
+
+ // This should cause listen to complete.
+ return responseController.close();
+ });
+ });
+
+ test('.listen returns a stream error', () {
+ expect(client.listen(), throwsA('oh no'));
+ responseController.addError('oh no');
+ });
+
+ test('.listen can\'t be called twice', () {
+ client.listen();
+ expect(() => client.listen(), throwsStateError);
+ });
+
+ test('.close cancels the stream subscription and closes the sink', () {
+ // Work around sdk#19095.
+ requestController.stream.listen(null);
+
+ expect(client.listen(), completes);
+
+ expect(client.isClosed, isFalse);
+ expect(client.close(), completes);
+ expect(client.isClosed, isTrue);
+
+ expect(() => responseController.stream.listen((_) {}), throwsStateError);
+ expect(requestController.isClosed, isTrue);
+ });
+
+ group('a stream error', () {
+ test('is reported through .done', () {
+ expect(client.listen(), throwsA('oh no!'));
+ expect(client.done, throwsA('oh no!'));
+ responseController.addError('oh no!');
+ });
+
+ test('cause a pending request to throw a StateError', () {
+ expect(client.listen(), throwsA('oh no!'));
+ expect(client.sendRequest('foo'), throwsStateError);
+ responseController.addError('oh no!');
+ });
+
+ test('causes future requests to throw StateErrors', () async {
+ expect(client.listen(), throwsA('oh no!'));
+ responseController.addError('oh no!');
+ await pumpEventQueue();
+
+ expect(() => client.sendRequest('foo'), throwsStateError);
+ expect(() => client.sendNotification('foo'), throwsStateError);
+ });
+ });
+}
diff --git a/pkgs/json_rpc_2/test/client/utils.dart b/pkgs/json_rpc_2/test/client/utils.dart
new file mode 100644
index 0000000..38e187f
--- /dev/null
+++ b/pkgs/json_rpc_2/test/client/utils.dart
@@ -0,0 +1,56 @@
+// Copyright (c) 2014, 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 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test/test.dart';
+
+/// A controller used to test a [json_rpc.Client].
+class ClientController {
+ /// The controller for the client's response stream.
+ final _responseController = StreamController<String>();
+
+ /// The controller for the client's request sink.
+ final _requestController = StreamController<String>();
+
+ /// The client.
+ late final json_rpc.Client client;
+
+ ClientController() {
+ client = json_rpc.Client(
+ StreamChannel(_responseController.stream, _requestController.sink));
+ client.listen();
+ }
+
+ /// Expects that the client will send a request.
+ ///
+ /// The request is passed to [callback], which can return a response. If it
+ /// returns a String, that's sent as the response directly. If it returns
+ /// null, no response is sent. Otherwise, the return value is encoded and sent
+ /// as the response.
+ void expectRequest(FutureOr Function(dynamic) callback) {
+ expect(
+ _requestController.stream.first.then((request) {
+ return callback(jsonDecode(request));
+ }).then((response) {
+ if (response == null) return;
+ if (response is! String) response = jsonEncode(response);
+ _responseController.add(response);
+ }),
+ completes);
+ }
+
+ /// Sends [response], a decoded response, to [client].
+ void sendResponse(Object? response) {
+ sendJsonResponse(jsonEncode(response));
+ }
+
+ /// Sends [request], a JSON-encoded response, to [client].
+ void sendJsonResponse(String request) {
+ _responseController.add(request);
+ }
+}
diff --git a/pkgs/json_rpc_2/test/peer_test.dart b/pkgs/json_rpc_2/test/peer_test.dart
new file mode 100644
index 0000000..0df6056
--- /dev/null
+++ b/pkgs/json_rpc_2/test/peer_test.dart
@@ -0,0 +1,251 @@
+// Copyright (c) 2014, 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: inference_failure_on_instance_creation
+
+import 'dart:async';
+import 'dart:convert';
+
+import 'package:json_rpc_2/error_code.dart' as error_code;
+import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test/test.dart';
+
+void main() {
+ late StreamSink incoming;
+ late Stream outgoing;
+ late json_rpc.Peer peer;
+
+ setUp(() {
+ var incomingController = StreamController();
+ incoming = incomingController.sink;
+ var outgoingController = StreamController();
+ outgoing = outgoingController.stream;
+ peer = json_rpc.Peer.withoutJson(
+ StreamChannel(incomingController.stream, outgoingController));
+ });
+
+ group('like a client,', () {
+ test('can send a message and receive a response', () {
+ expect(outgoing.first.then((request) {
+ expect(
+ request,
+ equals({
+ 'jsonrpc': '2.0',
+ 'method': 'foo',
+ 'params': {'bar': 'baz'},
+ 'id': 0
+ }));
+ incoming.add({'jsonrpc': '2.0', 'result': 'qux', 'id': 0});
+ }), completes);
+
+ peer.listen();
+ expect(
+ peer.sendRequest('foo', {'bar': 'baz'}), completion(equals('qux')));
+ });
+
+ test('can send a batch of messages and receive a batch of responses', () {
+ expect(outgoing.first.then((request) {
+ expect(
+ request,
+ equals([
+ {
+ 'jsonrpc': '2.0',
+ 'method': 'foo',
+ 'params': {'bar': 'baz'},
+ 'id': 0
+ },
+ {
+ 'jsonrpc': '2.0',
+ 'method': 'a',
+ 'params': {'b': 'c'},
+ 'id': 1
+ },
+ {
+ 'jsonrpc': '2.0',
+ 'method': 'w',
+ 'params': {'x': 'y'},
+ 'id': 2
+ }
+ ]));
+
+ incoming.add([
+ {'jsonrpc': '2.0', 'result': 'qux', 'id': 0},
+ {'jsonrpc': '2.0', 'result': 'd', 'id': 1},
+ {'jsonrpc': '2.0', 'result': 'z', 'id': 2}
+ ]);
+ }), completes);
+
+ peer.listen();
+
+ peer.withBatch(() {
+ expect(
+ peer.sendRequest('foo', {'bar': 'baz'}), completion(equals('qux')));
+ expect(peer.sendRequest('a', {'b': 'c'}), completion(equals('d')));
+ expect(peer.sendRequest('w', {'x': 'y'}), completion(equals('z')));
+ });
+ });
+
+ test('requests terminates when the channel is closed', () async {
+ var incomingController = StreamController<void>();
+ var channel = StreamChannel.withGuarantees(
+ incomingController.stream,
+ StreamController<void>(),
+ );
+ var peer = json_rpc.Peer.withoutJson(channel);
+ unawaited(peer.listen());
+
+ var response = peer.sendRequest('foo');
+ await incomingController.close();
+
+ expect(response, throwsStateError);
+ });
+ });
+
+ test('can be closed', () async {
+ var incomingController = StreamController();
+ var channel = StreamChannel.withGuarantees(
+ incomingController.stream,
+ StreamController(),
+ );
+ var peer = json_rpc.Peer.withoutJson(channel);
+ unawaited(peer.listen());
+ await peer.close();
+ });
+
+ test('considered closed with misbehaving StreamChannel', () async {
+ // If a StreamChannel does not enforce the guarantees stated in it's
+ // contract - specifically that "Closing the sink causes the stream to close
+ // before it emits any more events." - The `Peer` should still understand
+ // when it has been closed manually.
+ var channel = StreamChannel(
+ StreamController().stream,
+ StreamController(),
+ );
+ var peer = json_rpc.Peer.withoutJson(channel);
+ unawaited(peer.listen());
+ unawaited(peer.close());
+ expect(peer.isClosed, true);
+ });
+
+ group('like a server,', () {
+ test('can receive a call and return a response', () {
+ expect(outgoing.first,
+ completion(equals({'jsonrpc': '2.0', 'result': 'qux', 'id': 0})));
+
+ peer.registerMethod('foo', (_) => 'qux');
+ peer.listen();
+
+ incoming.add({
+ 'jsonrpc': '2.0',
+ 'method': 'foo',
+ 'params': {'bar': 'baz'},
+ 'id': 0
+ });
+ });
+
+ test('can receive a batch of calls and return a batch of responses', () {
+ expect(
+ outgoing.first,
+ completion(equals([
+ {'jsonrpc': '2.0', 'result': 'qux', 'id': 0},
+ {'jsonrpc': '2.0', 'result': 'd', 'id': 1},
+ {'jsonrpc': '2.0', 'result': 'z', 'id': 2}
+ ])));
+
+ peer.registerMethod('foo', (_) => 'qux');
+ peer.registerMethod('a', (_) => 'd');
+ peer.registerMethod('w', (_) => 'z');
+ peer.listen();
+
+ incoming.add([
+ {
+ 'jsonrpc': '2.0',
+ 'method': 'foo',
+ 'params': {'bar': 'baz'},
+ 'id': 0
+ },
+ {
+ 'jsonrpc': '2.0',
+ 'method': 'a',
+ 'params': {'b': 'c'},
+ 'id': 1
+ },
+ {
+ 'jsonrpc': '2.0',
+ 'method': 'w',
+ 'params': {'x': 'y'},
+ 'id': 2
+ }
+ ]);
+ });
+
+ test('returns a response for malformed JSON', () {
+ var incomingController = StreamController<String>();
+ var outgoingController = StreamController<String>();
+ var jsonPeer = json_rpc.Peer(
+ StreamChannel(incomingController.stream, outgoingController));
+
+ expect(
+ outgoingController.stream.first.then(jsonDecode),
+ completion({
+ 'jsonrpc': '2.0',
+ 'error': {
+ 'code': error_code.PARSE_ERROR,
+ 'message': startsWith('Invalid JSON: '),
+ // TODO(nweiz): Always expect the source when sdk#25655 is fixed.
+ 'data': {
+ 'request': anyOf([isNull, '{invalid'])
+ }
+ },
+ 'id': null
+ }));
+
+ jsonPeer.listen();
+
+ incomingController.add('{invalid');
+ });
+
+ test('returns a response for incorrectly-structured JSON', () {
+ expect(
+ outgoing.first,
+ completion({
+ 'jsonrpc': '2.0',
+ 'error': {
+ 'code': error_code.INVALID_REQUEST,
+ 'message': 'Request must contain a "jsonrpc" key.',
+ 'data': {
+ 'request': {'completely': 'wrong'}
+ }
+ },
+ 'id': null
+ }));
+
+ peer.listen();
+
+ incoming.add({'completely': 'wrong'});
+ });
+ });
+
+ test('can notify on unhandled errors for if the method throws', () async {
+ var exception = Exception('test exception');
+ var incomingController = StreamController();
+ var outgoingController = StreamController();
+ final completer = Completer<Exception>();
+ peer = json_rpc.Peer.withoutJson(
+ StreamChannel(incomingController.stream, outgoingController),
+ onUnhandledError: (error, stack) {
+ completer.complete(error);
+ },
+ );
+ peer
+ ..registerMethod('foo', () => throw exception)
+ // ignore: unawaited_futures
+ ..listen();
+
+ incomingController.add({'jsonrpc': '2.0', 'method': 'foo'});
+ var receivedException = await completer.future;
+ expect(receivedException, equals(exception));
+ });
+}
diff --git a/pkgs/json_rpc_2/test/server/batch_test.dart b/pkgs/json_rpc_2/test/server/batch_test.dart
new file mode 100644
index 0000000..af883c4
--- /dev/null
+++ b/pkgs/json_rpc_2/test/server/batch_test.dart
@@ -0,0 +1,147 @@
+// Copyright (c) 2014, 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:json_rpc_2/error_code.dart' as error_code;
+import 'package:json_rpc_2/src/parameters.dart' show Parameters;
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ late ServerController controller;
+
+ setUp(() {
+ controller = ServerController();
+ controller.server
+ ..registerMethod('foo', () => 'foo')
+ ..registerMethod('id', (Parameters params) => params.value)
+ ..registerMethod('arg', (Parameters params) => params['arg'].value);
+ });
+
+ test('handles a batch of requests', () {
+ expect(
+ controller.handleRequest([
+ {'jsonrpc': '2.0', 'method': 'foo', 'id': 1},
+ {
+ 'jsonrpc': '2.0',
+ 'method': 'id',
+ 'params': ['value'],
+ 'id': 2
+ },
+ {
+ 'jsonrpc': '2.0',
+ 'method': 'arg',
+ 'params': {'arg': 'value'},
+ 'id': 3
+ }
+ ]),
+ completion(equals([
+ {'jsonrpc': '2.0', 'result': 'foo', 'id': 1},
+ {
+ 'jsonrpc': '2.0',
+ 'result': ['value'],
+ 'id': 2
+ },
+ {'jsonrpc': '2.0', 'result': 'value', 'id': 3}
+ ])));
+ });
+
+ test('handles errors individually', () {
+ expect(
+ controller.handleRequest([
+ {'jsonrpc': '2.0', 'method': 'foo', 'id': 1},
+ {'jsonrpc': '2.0', 'method': 'zap', 'id': 2},
+ {
+ 'jsonrpc': '2.0',
+ 'method': 'arg',
+ 'params': {'arg': 'value'},
+ 'id': 3
+ }
+ ]),
+ completion(equals([
+ {'jsonrpc': '2.0', 'result': 'foo', 'id': 1},
+ {
+ 'jsonrpc': '2.0',
+ 'id': 2,
+ 'error': {
+ 'code': error_code.METHOD_NOT_FOUND,
+ 'message': 'Unknown method "zap".',
+ 'data': {
+ 'request': {'jsonrpc': '2.0', 'method': 'zap', 'id': 2}
+ },
+ }
+ },
+ {'jsonrpc': '2.0', 'result': 'value', 'id': 3}
+ ])));
+ });
+
+ test('handles notifications individually', () {
+ expect(
+ controller.handleRequest([
+ {'jsonrpc': '2.0', 'method': 'foo', 'id': 1},
+ {
+ 'jsonrpc': '2.0',
+ 'method': 'id',
+ 'params': ['value']
+ },
+ {
+ 'jsonrpc': '2.0',
+ 'method': 'arg',
+ 'params': {'arg': 'value'},
+ 'id': 3
+ }
+ ]),
+ completion(equals([
+ {'jsonrpc': '2.0', 'result': 'foo', 'id': 1},
+ {'jsonrpc': '2.0', 'result': 'value', 'id': 3}
+ ])));
+ });
+
+ test('returns nothing if every request is a notification', () {
+ expect(
+ controller.handleRequest([
+ {'jsonrpc': '2.0', 'method': 'foo'},
+ {
+ 'jsonrpc': '2.0',
+ 'method': 'id',
+ 'params': ['value']
+ },
+ {
+ 'jsonrpc': '2.0',
+ 'method': 'arg',
+ 'params': {'arg': 'value'}
+ }
+ ]),
+ doesNotComplete);
+ });
+
+ test('returns an error if the batch is empty', () {
+ expectErrorResponse(controller, [], error_code.INVALID_REQUEST,
+ 'A batch must contain at least one request.');
+ });
+
+ test('disallows nested batches', () {
+ expect(
+ controller.handleRequest([
+ [
+ {'jsonrpc': '2.0', 'method': 'foo', 'id': 1}
+ ]
+ ]),
+ completion(equals([
+ {
+ 'jsonrpc': '2.0',
+ 'id': null,
+ 'error': {
+ 'code': error_code.INVALID_REQUEST,
+ 'message': 'Request must be an Array or an Object.',
+ 'data': {
+ 'request': [
+ {'jsonrpc': '2.0', 'method': 'foo', 'id': 1}
+ ]
+ }
+ }
+ }
+ ])));
+ });
+}
diff --git a/pkgs/json_rpc_2/test/server/invalid_request_test.dart b/pkgs/json_rpc_2/test/server/invalid_request_test.dart
new file mode 100644
index 0000000..4fa4de1
--- /dev/null
+++ b/pkgs/json_rpc_2/test/server/invalid_request_test.dart
@@ -0,0 +1,94 @@
+// Copyright (c) 2014, 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:json_rpc_2/error_code.dart' as error_code;
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ late ServerController controller;
+ setUp(() => controller = ServerController());
+
+ test('a non-Array/Object request is invalid', () {
+ expectErrorResponse(controller, 'foo', error_code.INVALID_REQUEST,
+ 'Request must be an Array or an Object.');
+ });
+
+ test('requests must have a jsonrpc key', () {
+ expectErrorResponse(controller, {'method': 'foo', 'id': 1234},
+ error_code.INVALID_REQUEST, 'Request must contain a "jsonrpc" key.');
+ });
+
+ test('the jsonrpc version must be 2.0', () {
+ expectErrorResponse(
+ controller,
+ {'jsonrpc': '1.0', 'method': 'foo', 'id': 1234},
+ error_code.INVALID_REQUEST,
+ 'Invalid JSON-RPC version "1.0", expected "2.0".');
+ });
+
+ test('requests must have a method key', () {
+ expectErrorResponse(controller, {'jsonrpc': '2.0', 'id': 1234},
+ error_code.INVALID_REQUEST, 'Request must contain a "method" key.');
+ });
+
+ test('request method must be a string', () {
+ expectErrorResponse(
+ controller,
+ {'jsonrpc': '2.0', 'method': 1234, 'id': 1234},
+ error_code.INVALID_REQUEST,
+ 'Request method must be a string, but was 1234.');
+ });
+
+ test('request params must be an Array or Object', () {
+ expectErrorResponse(
+ controller,
+ {'jsonrpc': '2.0', 'method': 'foo', 'params': 1234, 'id': 1234},
+ error_code.INVALID_REQUEST,
+ 'Request params must be an Array or an Object, but was 1234.');
+ });
+
+ test('request id may not be an Array or Object', () {
+ expect(
+ controller.handleRequest({
+ 'jsonrpc': '2.0',
+ 'method': 'foo',
+ 'id': {'bad': 'id'}
+ }),
+ completion(equals({
+ 'jsonrpc': '2.0',
+ 'id': null,
+ 'error': {
+ 'code': error_code.INVALID_REQUEST,
+ 'message': 'Request id must be a string, number, or null, but was '
+ '{"bad":"id"}.',
+ 'data': {
+ 'request': {
+ 'jsonrpc': '2.0',
+ 'method': 'foo',
+ 'id': {'bad': 'id'}
+ }
+ }
+ }
+ })));
+ });
+
+ group('strict protocol checks disabled', () {
+ setUp(() => controller = ServerController(strictProtocolChecks: false));
+
+ test('and no jsonrpc param', () {
+ expectErrorResponse(controller, {'method': 'foo', 'id': 1234},
+ error_code.METHOD_NOT_FOUND, 'Unknown method "foo".');
+ });
+
+ test('the jsonrpc version must be 2.0', () {
+ expectErrorResponse(
+ controller,
+ {'jsonrpc': '1.0', 'method': 'foo', 'id': 1234},
+ error_code.INVALID_REQUEST,
+ 'Invalid JSON-RPC version "1.0", expected "2.0".');
+ });
+ });
+}
diff --git a/pkgs/json_rpc_2/test/server/parameters_test.dart b/pkgs/json_rpc_2/test/server/parameters_test.dart
new file mode 100644
index 0000000..9ecfb1f
--- /dev/null
+++ b/pkgs/json_rpc_2/test/server/parameters_test.dart
@@ -0,0 +1,403 @@
+// Copyright (c) 2014, 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:json_rpc_2/json_rpc_2.dart' as json_rpc;
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ group('with named parameters', () {
+ late json_rpc.Parameters parameters;
+ setUp(() {
+ parameters = json_rpc.Parameters('foo', {
+ 'num': 1.5,
+ 'int': 1,
+ 'bool': true,
+ 'string': 'zap',
+ 'list': [1, 2, 3],
+ 'date-time': '1990-01-01 00:00:00.000',
+ 'uri': 'https://dart.dev',
+ 'invalid-uri': 'http://[::1',
+ 'map': {'num': 4.2, 'bool': false}
+ });
+ });
+
+ test('value returns the wrapped value', () {
+ expect(
+ parameters.value,
+ equals({
+ 'num': 1.5,
+ 'int': 1,
+ 'bool': true,
+ 'string': 'zap',
+ 'list': [1, 2, 3],
+ 'date-time': '1990-01-01 00:00:00.000',
+ 'uri': 'https://dart.dev',
+ 'invalid-uri': 'http://[::1',
+ 'map': {'num': 4.2, 'bool': false}
+ }));
+ });
+
+ test('[int] throws a parameter error', () {
+ expect(
+ () => parameters[0],
+ throwsInvalidParams('Parameters for method "foo" must be passed by '
+ 'position.'));
+ });
+
+ test('[].value returns existing parameters', () {
+ expect(parameters['num'].value, equals(1.5));
+ });
+
+ test('[].valueOr returns existing parameters', () {
+ expect(parameters['num'].valueOr(7), equals(1.5));
+ });
+
+ test('[].value fails for absent parameters', () {
+ expect(
+ () => parameters['fblthp'].value,
+ throwsInvalidParams('Request for method "foo" is missing required '
+ 'parameter "fblthp".'));
+ });
+
+ test('[].valueOr succeeds for absent parameters', () {
+ expect(parameters['fblthp'].valueOr(7), equals(7));
+ });
+
+ test('[].exists returns true for existing parameters', () {
+ expect(parameters['num'].exists, isTrue);
+ });
+
+ test('[].exists returns false for missing parameters', () {
+ expect(parameters['fblthp'].exists, isFalse);
+ });
+
+ test('[].asNum returns numeric parameters', () {
+ expect(parameters['num'].asNum, equals(1.5));
+ expect(parameters['int'].asNum, equals(1));
+ });
+
+ test('[].asNumOr returns numeric parameters', () {
+ expect(parameters['num'].asNumOr(7), equals(1.5));
+ });
+
+ test('[].asNum fails for non-numeric parameters', () {
+ expect(
+ () => parameters['bool'].asNum,
+ throwsInvalidParams('Parameter "bool" for method "foo" must be a '
+ 'number, but was true.'));
+ });
+
+ test('[].asNumOr fails for non-numeric parameters', () {
+ expect(
+ () => parameters['bool'].asNumOr(7),
+ throwsInvalidParams('Parameter "bool" for method "foo" must be a '
+ 'number, but was true.'));
+ });
+
+ test('[].asNum fails for absent parameters', () {
+ expect(
+ () => parameters['fblthp'].asNum,
+ throwsInvalidParams('Request for method "foo" is missing required '
+ 'parameter "fblthp".'));
+ });
+
+ test('[].asNumOr succeeds for absent parameters', () {
+ expect(parameters['fblthp'].asNumOr(7), equals(7));
+ });
+
+ test('[].asInt returns integer parameters', () {
+ expect(parameters['int'].asInt, equals(1));
+ });
+
+ test('[].asIntOr returns integer parameters', () {
+ expect(parameters['int'].asIntOr(7), equals(1));
+ });
+
+ test('[].asInt fails for non-integer parameters', () {
+ expect(
+ () => parameters['bool'].asInt,
+ throwsInvalidParams('Parameter "bool" for method "foo" must be an '
+ 'integer, but was true.'));
+ });
+
+ test('[].asIntOr succeeds for absent parameters', () {
+ expect(parameters['fblthp'].asIntOr(7), equals(7));
+ });
+
+ test('[].asBool returns boolean parameters', () {
+ expect(parameters['bool'].asBool, isTrue);
+ });
+
+ test('[].asBoolOr returns boolean parameters', () {
+ expect(parameters['bool'].asBoolOr(false), isTrue);
+ });
+
+ test('[].asBoolOr fails for non-boolean parameters', () {
+ expect(
+ () => parameters['int'].asBool,
+ throwsInvalidParams('Parameter "int" for method "foo" must be a '
+ 'boolean, but was 1.'));
+ });
+
+ test('[].asBoolOr succeeds for absent parameters', () {
+ expect(parameters['fblthp'].asBoolOr(false), isFalse);
+ });
+
+ test('[].asString returns string parameters', () {
+ expect(parameters['string'].asString, equals('zap'));
+ });
+
+ test('[].asStringOr returns string parameters', () {
+ expect(parameters['string'].asStringOr('bap'), equals('zap'));
+ });
+
+ test('[].asString fails for non-string parameters', () {
+ expect(
+ () => parameters['int'].asString,
+ throwsInvalidParams('Parameter "int" for method "foo" must be a '
+ 'string, but was 1.'));
+ });
+
+ test('[].asStringOr succeeds for absent parameters', () {
+ expect(parameters['fblthp'].asStringOr('bap'), equals('bap'));
+ });
+
+ test('[].asList returns list parameters', () {
+ expect(parameters['list'].asList, equals([1, 2, 3]));
+ });
+
+ test('[].asListOr returns list parameters', () {
+ expect(parameters['list'].asListOr([5, 6, 7]), equals([1, 2, 3]));
+ });
+
+ test('[].asList fails for non-list parameters', () {
+ expect(
+ () => parameters['int'].asList,
+ throwsInvalidParams('Parameter "int" for method "foo" must be an '
+ 'Array, but was 1.'));
+ });
+
+ test('[].asListOr succeeds for absent parameters', () {
+ expect(parameters['fblthp'].asListOr([5, 6, 7]), equals([5, 6, 7]));
+ });
+
+ test('[].asMap returns map parameters', () {
+ expect(parameters['map'].asMap, equals({'num': 4.2, 'bool': false}));
+ });
+
+ test('[].asMapOr returns map parameters', () {
+ expect(
+ parameters['map'].asMapOr({}), equals({'num': 4.2, 'bool': false}));
+ });
+
+ test('[].asMap fails for non-map parameters', () {
+ expect(
+ () => parameters['int'].asMap,
+ throwsInvalidParams('Parameter "int" for method "foo" must be an '
+ 'Object, but was 1.'));
+ });
+
+ test('[].asMapOr succeeds for absent parameters', () {
+ expect(parameters['fblthp'].asMapOr({}), equals({}));
+ });
+
+ test('[].asDateTime returns date/time parameters', () {
+ expect(parameters['date-time'].asDateTime, equals(DateTime(1990)));
+ });
+
+ test('[].asDateTimeOr returns date/time parameters', () {
+ expect(parameters['date-time'].asDateTimeOr(DateTime(2014)),
+ equals(DateTime(1990)));
+ });
+
+ test('[].asDateTime fails for non-date/time parameters', () {
+ expect(
+ () => parameters['int'].asDateTime,
+ throwsInvalidParams('Parameter "int" for method "foo" must be a '
+ 'string, but was 1.'));
+ });
+
+ test('[].asDateTimeOr succeeds for absent parameters', () {
+ expect(parameters['fblthp'].asDateTimeOr(DateTime(2014)),
+ equals(DateTime(2014)));
+ });
+
+ test('[].asDateTime fails for non-date/time parameters', () {
+ expect(
+ () => parameters['int'].asDateTime,
+ throwsInvalidParams('Parameter "int" for method "foo" must be a '
+ 'string, but was 1.'));
+ });
+
+ test('[].asDateTime fails for invalid date/times', () {
+ expect(
+ () => parameters['string'].asDateTime,
+ throwsInvalidParams('Parameter "string" for method "foo" must be a '
+ 'valid date/time, but was "zap".\n'
+ 'Invalid date format'));
+ });
+
+ test('[].asUri returns URI parameters', () {
+ expect(parameters['uri'].asUri, equals(Uri.parse('https://dart.dev')));
+ });
+
+ test('[].asUriOr returns URI parameters', () {
+ expect(parameters['uri'].asUriOr(Uri.parse('http://google.com')),
+ equals(Uri.parse('https://dart.dev')));
+ });
+
+ test('[].asUri fails for non-URI parameters', () {
+ expect(
+ () => parameters['int'].asUri,
+ throwsInvalidParams('Parameter "int" for method "foo" must be a '
+ 'string, but was 1.'));
+ });
+
+ test('[].asUriOr succeeds for absent parameters', () {
+ expect(parameters['fblthp'].asUriOr(Uri.parse('http://google.com')),
+ equals(Uri.parse('http://google.com')));
+ });
+
+ test('[].asUri fails for non-URI parameters', () {
+ expect(
+ () => parameters['int'].asUri,
+ throwsInvalidParams('Parameter "int" for method "foo" must be a '
+ 'string, but was 1.'));
+ });
+
+ test('[].asUri fails for invalid URIs', () {
+ expect(
+ () => parameters['invalid-uri'].asUri,
+ throwsInvalidParams('Parameter "invalid-uri" for method "foo" must '
+ 'be a valid URI, but was "http://[::1".\n'
+ 'Missing end `]` to match `[` in host'));
+ });
+
+ group('with a nested parameter map', () {
+ late json_rpc.Parameter nested;
+ setUp(() => nested = parameters['map']);
+
+ test('[int] fails with a type error', () {
+ expect(
+ () => nested[0],
+ throwsInvalidParams('Parameter "map" for method "foo" must be an '
+ 'Array, but was {"num":4.2,"bool":false}.'));
+ });
+
+ test('[].value returns existing parameters', () {
+ expect(nested['num'].value, equals(4.2));
+ expect(nested['bool'].value, isFalse);
+ });
+
+ test('[].value fails for absent parameters', () {
+ expect(
+ () => nested['fblthp'].value,
+ throwsInvalidParams('Request for method "foo" is missing required '
+ 'parameter map.fblthp.'));
+ });
+
+ test('typed getters return correctly-typed parameters', () {
+ expect(nested['num'].asNum, equals(4.2));
+ });
+
+ test('typed getters fail for incorrectly-typed parameters', () {
+ expect(
+ () => nested['bool'].asNum,
+ throwsInvalidParams('Parameter map.bool for method "foo" must be '
+ 'a number, but was false.'));
+ });
+ });
+
+ group('with a nested parameter list', () {
+ late json_rpc.Parameter nested;
+
+ setUp(() => nested = parameters['list']);
+
+ test('[string] fails with a type error', () {
+ expect(
+ () => nested['foo'],
+ throwsInvalidParams('Parameter "list" for method "foo" must be an '
+ 'Object, but was [1,2,3].'));
+ });
+
+ test('[].value returns existing parameters', () {
+ expect(nested[0].value, equals(1));
+ expect(nested[1].value, equals(2));
+ });
+
+ test('[].value fails for absent parameters', () {
+ expect(
+ () => nested[5].value,
+ throwsInvalidParams('Request for method "foo" is missing required '
+ 'parameter list[5].'));
+ });
+
+ test('typed getters return correctly-typed parameters', () {
+ expect(nested[0].asInt, equals(1));
+ });
+
+ test('typed getters fail for incorrectly-typed parameters', () {
+ expect(
+ () => nested[0].asBool,
+ throwsInvalidParams('Parameter list[0] for method "foo" must be '
+ 'a boolean, but was 1.'));
+ });
+ });
+ });
+
+ group('with positional parameters', () {
+ late json_rpc.Parameters parameters;
+ setUp(() => parameters = json_rpc.Parameters('foo', [1, 2, 3, 4, 5]));
+
+ test('value returns the wrapped value', () {
+ expect(parameters.value, equals([1, 2, 3, 4, 5]));
+ });
+
+ test('[string] throws a parameter error', () {
+ expect(
+ () => parameters['foo'],
+ throwsInvalidParams('Parameters for method "foo" must be passed by '
+ 'name.'));
+ });
+
+ test('[].value returns existing parameters', () {
+ expect(parameters[2].value, equals(3));
+ });
+
+ test('[].value fails for out-of-range parameters', () {
+ expect(
+ () => parameters[10].value,
+ throwsInvalidParams('Request for method "foo" is missing required '
+ 'parameter 11.'));
+ });
+
+ test('[].exists returns true for existing parameters', () {
+ expect(parameters[0].exists, isTrue);
+ });
+
+ test('[].exists returns false for missing parameters', () {
+ expect(parameters[10].exists, isFalse);
+ });
+ });
+
+ test('with a complex parameter path', () {
+ var parameters = json_rpc.Parameters('foo', {
+ 'bar baz': [
+ 0,
+ 1,
+ 2,
+ {
+ 'bang.zap': {'\n': 'qux'}
+ }
+ ]
+ });
+
+ expect(
+ () => parameters['bar baz'][3]['bang.zap']['\n']['bip'],
+ throwsInvalidParams('Parameter "bar baz"[3]."bang.zap"."\\n" for '
+ 'method "foo" must be an Object, but was "qux".'));
+ });
+}
diff --git a/pkgs/json_rpc_2/test/server/server_test.dart b/pkgs/json_rpc_2/test/server/server_test.dart
new file mode 100644
index 0000000..b3166ce
--- /dev/null
+++ b/pkgs/json_rpc_2/test/server/server_test.dart
@@ -0,0 +1,203 @@
+// Copyright (c) 2014, 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 'package:json_rpc_2/error_code.dart' as error_code;
+import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ late ServerController controller;
+
+ setUp(() => controller = ServerController());
+
+ test('calls a registered method with the given name', () {
+ controller.server.registerMethod('foo', (json_rpc.Parameters params) {
+ return {'params': params.value};
+ });
+
+ expect(
+ controller.handleRequest({
+ 'jsonrpc': '2.0',
+ 'method': 'foo',
+ 'params': {'param': 'value'},
+ 'id': 1234
+ }),
+ completion(equals({
+ 'jsonrpc': '2.0',
+ 'result': {
+ 'params': {'param': 'value'}
+ },
+ 'id': 1234
+ })));
+ });
+
+ test('calls a method that takes no parameters', () {
+ controller.server.registerMethod('foo', () => 'foo');
+
+ expect(
+ controller
+ .handleRequest({'jsonrpc': '2.0', 'method': 'foo', 'id': 1234}),
+ completion(equals({'jsonrpc': '2.0', 'result': 'foo', 'id': 1234})));
+ });
+
+ test('Allows a `null` result', () {
+ controller.server.registerMethod('foo', () => null);
+
+ expect(
+ controller
+ .handleRequest({'jsonrpc': '2.0', 'method': 'foo', 'id': 1234}),
+ completion(equals({'jsonrpc': '2.0', 'result': null, 'id': 1234})));
+ });
+
+ test('a method that takes no parameters rejects parameters', () {
+ controller.server.registerMethod('foo', () => 'foo');
+
+ expectErrorResponse(
+ controller,
+ {
+ 'jsonrpc': '2.0',
+ 'method': 'foo',
+ 'params': <String, dynamic>{},
+ 'id': 1234
+ },
+ error_code.INVALID_PARAMS,
+ 'No parameters are allowed for method "foo".');
+ });
+
+ test('an unexpected error in a method is captured', () {
+ controller.server
+ .registerMethod('foo', () => throw const FormatException('bad format'));
+
+ expect(
+ controller
+ .handleRequest({'jsonrpc': '2.0', 'method': 'foo', 'id': 1234}),
+ completion({
+ 'jsonrpc': '2.0',
+ 'id': 1234,
+ 'error': {
+ 'code': error_code.SERVER_ERROR,
+ 'message': 'bad format',
+ 'data': {
+ 'request': {'jsonrpc': '2.0', 'method': 'foo', 'id': 1234},
+ 'full': 'FormatException: bad format',
+ 'stack': isA<String>()
+ }
+ }
+ }));
+ });
+
+ test('doesn\'t return a result for a notification', () {
+ controller.server.registerMethod('foo', (args) => 'result');
+
+ expect(
+ controller.handleRequest(
+ {'jsonrpc': '2.0', 'method': 'foo', 'params': <String, dynamic>{}}),
+ doesNotComplete);
+ });
+
+ test('includes the error data in the response', () {
+ controller.server.registerMethod('foo', (params) {
+ throw json_rpc.RpcException(5, 'Error message.', data: 'data value');
+ });
+
+ expectErrorResponse(
+ controller,
+ {
+ 'jsonrpc': '2.0',
+ 'method': 'foo',
+ 'params': <String, dynamic>{},
+ 'id': 1234
+ },
+ 5,
+ 'Error message.',
+ data: 'data value');
+ });
+
+ test('a JSON parse error is rejected', () {
+ return controller.handleJsonRequest('invalid json {').then((result) {
+ expect(jsonDecode(result), {
+ 'jsonrpc': '2.0',
+ 'error': {
+ 'code': error_code.PARSE_ERROR,
+ 'message': startsWith('Invalid JSON: '),
+ // TODO(nweiz): Always expect the source when sdk#25655 is fixed.
+ 'data': {
+ 'request': anyOf([isNull, 'invalid json {'])
+ }
+ },
+ 'id': null
+ });
+ });
+ });
+
+ group('fallbacks', () {
+ test('calls a fallback if no method matches', () {
+ controller.server
+ ..registerMethod('foo', () => 'foo')
+ ..registerMethod('bar', () => 'foo')
+ ..registerFallback((params) => {'fallback': params.value});
+
+ expect(
+ controller.handleRequest({
+ 'jsonrpc': '2.0',
+ 'method': 'baz',
+ 'params': {'param': 'value'},
+ 'id': 1234
+ }),
+ completion(equals({
+ 'jsonrpc': '2.0',
+ 'result': {
+ 'fallback': {'param': 'value'}
+ },
+ 'id': 1234
+ })));
+ });
+
+ test('calls the first matching fallback', () {
+ controller.server
+ ..registerFallback((params) =>
+ throw json_rpc.RpcException.methodNotFound(params.method))
+ ..registerFallback((params) => 'fallback 2')
+ ..registerFallback((params) => 'fallback 3');
+
+ expect(
+ controller.handleRequest(
+ {'jsonrpc': '2.0', 'method': 'fallback 2', 'id': 1234}),
+ completion(
+ equals({'jsonrpc': '2.0', 'result': 'fallback 2', 'id': 1234})));
+ });
+
+ test('an unexpected error in a fallback is captured', () {
+ controller.server
+ .registerFallback((_) => throw const FormatException('bad format'));
+
+ expect(
+ controller
+ .handleRequest({'jsonrpc': '2.0', 'method': 'foo', 'id': 1234}),
+ completion({
+ 'jsonrpc': '2.0',
+ 'id': 1234,
+ 'error': {
+ 'code': error_code.SERVER_ERROR,
+ 'message': 'bad format',
+ 'data': {
+ 'request': {'jsonrpc': '2.0', 'method': 'foo', 'id': 1234},
+ 'full': 'FormatException: bad format',
+ 'stack': isA<String>()
+ }
+ }
+ }));
+ });
+ });
+
+ test('disallows multiple methods with the same name', () {
+ controller.server.registerMethod('foo', () => null);
+ expect(() => controller.server.registerMethod('foo', () => null),
+ throwsArgumentError);
+ });
+}
diff --git a/pkgs/json_rpc_2/test/server/stream_test.dart b/pkgs/json_rpc_2/test/server/stream_test.dart
new file mode 100644
index 0000000..832e13c
--- /dev/null
+++ b/pkgs/json_rpc_2/test/server/stream_test.dart
@@ -0,0 +1,84 @@
+// Copyright (c) 2014, 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 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test/test.dart';
+
+void main() {
+ late StreamController requestController;
+ late StreamController responseController;
+ late json_rpc.Server server;
+
+ setUp(() {
+ requestController = StreamController();
+ responseController = StreamController();
+ server = json_rpc.Server.withoutJson(
+ StreamChannel(requestController.stream, responseController.sink));
+ });
+
+ test('.withoutJson supports decoded stream and sink', () {
+ server.listen();
+
+ server.registerMethod('foo', (json_rpc.Parameters params) {
+ return {'params': params.value};
+ });
+
+ requestController.add({
+ 'jsonrpc': '2.0',
+ 'method': 'foo',
+ 'params': {'param': 'value'},
+ 'id': 1234
+ });
+
+ expect(
+ responseController.stream.first,
+ completion(equals({
+ 'jsonrpc': '2.0',
+ 'result': {
+ 'params': {'param': 'value'}
+ },
+ 'id': 1234
+ })));
+ });
+
+ test('.listen returns when the controller is closed', () {
+ var hasListenCompeted = false;
+ expect(server.listen().then((_) => hasListenCompeted = true), completes);
+
+ return pumpEventQueue().then((_) {
+ expect(hasListenCompeted, isFalse);
+
+ // This should cause listen to complete.
+ return requestController.close();
+ });
+ });
+
+ test('.listen returns a stream error', () {
+ expect(server.listen(), throwsA('oh no'));
+ requestController.addError('oh no');
+ });
+
+ test('.listen can\'t be called twice', () {
+ server.listen();
+
+ expect(() => server.listen(), throwsStateError);
+ });
+
+ test('.close cancels the stream subscription and closes the sink', () {
+ // Work around sdk#19095.
+ responseController.stream.listen(null);
+
+ expect(server.listen(), completes);
+
+ expect(server.isClosed, isFalse);
+ expect(server.close(), completes);
+ expect(server.isClosed, isTrue);
+
+ expect(() => requestController.stream.listen((_) {}), throwsStateError);
+ expect(responseController.isClosed, isTrue);
+ });
+}
diff --git a/pkgs/json_rpc_2/test/server/utils.dart b/pkgs/json_rpc_2/test/server/utils.dart
new file mode 100644
index 0000000..c94628e
--- /dev/null
+++ b/pkgs/json_rpc_2/test/server/utils.dart
@@ -0,0 +1,70 @@
+// Copyright (c) 2014, 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 'package:json_rpc_2/error_code.dart' as error_code;
+import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test/test.dart';
+
+/// A controller used to test a [json_rpc.Server].
+class ServerController {
+ /// The controller for the server's request stream.
+ final _requestController = StreamController<String>();
+
+ /// The controller for the server's response sink.
+ final _responseController = StreamController<String>();
+
+ /// The server.
+ late final json_rpc.Server server;
+
+ ServerController(
+ {json_rpc.ErrorCallback? onUnhandledError,
+ bool strictProtocolChecks = true}) {
+ server = json_rpc.Server(
+ StreamChannel(_requestController.stream, _responseController.sink),
+ onUnhandledError: onUnhandledError,
+ strictProtocolChecks: strictProtocolChecks);
+ server.listen();
+ }
+
+ /// Passes [request], a decoded request, to [server] and returns its decoded
+ /// response.
+ Future handleRequest(Object? request) =>
+ handleJsonRequest(jsonEncode(request)).then(jsonDecode);
+
+ /// Passes [request], a JSON-encoded request, to [server] and returns its
+ /// encoded response.
+ Future<String> handleJsonRequest(String request) {
+ _requestController.add(request);
+ return _responseController.stream.first;
+ }
+}
+
+/// Expects that [controller]'s server will return an error response to
+/// [request] with the given [errorCode], [message], and [data].
+void expectErrorResponse(
+ ServerController controller, Object? request, int errorCode, String message,
+ {Object? data}) {
+ dynamic id;
+ if (request is Map) id = request['id'];
+ data ??= {'request': request};
+
+ expect(
+ controller.handleRequest(request),
+ completion(equals({
+ 'jsonrpc': '2.0',
+ 'id': id,
+ 'error': {'code': errorCode, 'message': message, 'data': data}
+ })));
+}
+
+/// Returns a matcher that matches a [json_rpc.RpcException] with an
+/// `invalid_params` error code.
+Matcher throwsInvalidParams(String message) =>
+ throwsA(isA<json_rpc.RpcException>()
+ .having((e) => e.code, 'code', error_code.INVALID_PARAMS)
+ .having((e) => e.message, 'message', message));
diff --git a/pkgs/markdown/.gitignore b/pkgs/markdown/.gitignore
new file mode 100644
index 0000000..fcd8776
--- /dev/null
+++ b/pkgs/markdown/.gitignore
@@ -0,0 +1,4 @@
+# https://dart.dev/tools/pub/private-files
+.dart_tool
+build
+pubspec.lock
diff --git a/pkgs/markdown/AUTHORS b/pkgs/markdown/AUTHORS
new file mode 100644
index 0000000..ca5b46b
--- /dev/null
+++ b/pkgs/markdown/AUTHORS
@@ -0,0 +1,13 @@
+# Below is a list of people and organizations that have contributed
+# to the Dart project. Names should be added to the list like so:
+#
+# Name/Organization <email address>
+
+Google Inc.
+
+David Peek <ninjascript@gmail.com>
+Daniel Schubert <daniel.schubert+github.com@gmail.com>
+Jirka Daněk <dnk@mail.muni.cz>
+Seth Westphal <westy92@gmail.com>
+Tim Maffett <timmaffett@gmail.com>
+Alex Li <alexv.525.li@gmail.com>
diff --git a/pkgs/markdown/CHANGELOG.md b/pkgs/markdown/CHANGELOG.md
new file mode 100644
index 0000000..4b20f92
--- /dev/null
+++ b/pkgs/markdown/CHANGELOG.md
@@ -0,0 +1,414 @@
+## 7.3.1-wip
+
+* Update the README link to the markdown playground
+ (https://dart-lang.github.io/tools).
+
+## 7.3.0
+
+* Move to `dart-lang/tools` monorepo.
+* Fix an issue with checkbox list items separated with blank lines (#602).
+* Require package `web: '>=0.4.2 <2.0.0'`.
+* Fix several `RangeError` hazards in links (#623).
+* Export `LinkReferenceDefinitionSyntax` publicly (#626).
+
+## 7.2.2
+
+* Fix a crash parsing alert block syntax (#584).
+* Have alert block syntax support multiple paragraphs (#577).
+* Require Dart `^3.2.0`.
+
+## 7.2.1
+
+* Address a termination issue with GitHub alert syntax parsing.
+
+## 7.2.0
+
+* Require Dart `^3.1.0`.
+* Update all CommonMark specification links to 0.30.
+* Fix beginning of line detection in `AutolinkExtensionSyntax`.
+* Add a new syntax `AlertBlockSyntax` to parse GitHub Alerts.
+
+## 7.1.1
+
+* Fix delimiter row matching pattern for tables.
+* Tables are now able to interrupt other blocks.
+* Fix an obscure issue with HtmlBlockSyntax.
+
+## 7.1.0
+
+* Support for [footnotes](https://pandoc.org/MANUAL.html#footnotes).
+* Fixed bug causing infinite loop for links inside tables.
+
+## 7.0.2
+
+* Require Dart 2.19
+* Fix an issue in `HeaderWithIdSyntax`, do not generate heading IDs for headings
+ with no content.
+
+## 7.0.1
+
+* Remove RegExp lookarounds from autolink extension patterns. (Fixes issues when
+ running on Safari.)
+
+## 7.0.0
+
+* **Breaking change**: `close()` of `DelimiterSyntax` and `LinkSyntax`
+ returns multiple nodes instead of single one.
+* **Breaking change**: Remove deprecated APIs, including `TagSyntax`,
+ `indicatorForCheckedCheckBox`, and `indicatorForUncheckedCheckBox`.
+* **Breaking change**: Removed `BlockHtmlSyntax`, `BlockTagBlockHtmlSyntax`,
+ `LongBlockHtmlSyntax`, and `OtherTagBlockHtmlSyntax`.
+* **Breaking change**: Change the `line` properties of type `String` to `Line`.
+* **Breaking change**: Change the `lines` properties of type `List<String>` to
+ `List<Line>`.
+* Add a new syntax `HtmlBlockSyntax` to parse HTML blocks.
+* Add an `enableTagfilter` option to `HtmlRenderer` to eanble GFM `tagfilter`
+ extension.
+* Add a new syntax `DecodeHtmlSyntax` to decode HTML entity and numeric
+ character references.
+* Add a new syntax `SoftLineBreakSyntax` to remove the single space before the
+ line ending.
+* Add a new syntax `EscapeHtmlSyntax` to encode (`"`), (`<`), (`>`) and (`&`).
+* Add an option `caseSensitive` to `TextSyntax`.
+* Add a new public method `parse(String text)` for `Document`.
+* Add a new public method `parseLineList(List<Line> text)` for `Document`.
+* Add a new type: `Line`.
+* Add a new optional parameter `parentSyntax` for `parseLines()` of
+ `BlockParser`, which can be used when parsing nested blocks.
+* Add a new optional parameter `disabledSetextHeading` for `parseLines()` of
+ `BlockParser`, which is used to disable the `SetextHeaderSyntax`.
+* Add a new public property `previousSyntax` for `BlockParser`.
+
+## 6.0.1
+
+* Fix a crash in checkbox lists when mixing checkbox items with
+ non-checkbox items.
+
+## 6.0.0
+
+* Require Dart 2.17
+* Add support to GFM extension for GitHub task lists (aka checkboxes). These
+ are only active in the `gitHubFlavored` and `gitHubWeb` extension sets.
+* Add support for `#ff0000` color swatches.
+* Change emoji list do be derived from the GitHub API. The only two emoji that
+ visually change are `:cricket:` and `:beetle:`. There are alternate emoji
+ `:cricket_game:` and `:lady_beetle:` which can be used to access the previous
+ emoji. `update_github_emoji.dart` now pulls all emoji info directly from
+ GitHub API and as a result we have now support the entire GitHub emoji set
+ (excluding the 19 custom GitHub specific emoji which have no Unicode support).
+* **Breaking change**: The `TagSyntax` is _deprecated_.
+* Add new syntax `DelimiterSyntax`.
+* **Breaking change**: `StrikethroughSyntax` now extends `DelimiterSyntax`
+ instead of `TagSyntax`.
+* **Breaking change**: `LinkSyntax` now extends `DelimiterSyntax`
+ instead of `TagSyntax`.
+* Add two new emphasis syntaxes `EmphasisSyntax.underscore` and
+ `EmphasisSyntax.asterisk`.
+
+## 5.0.0
+
+* Breaking change: Change the type of `parseInline`'s parameter from `String?`
+ to `String`.
+* Fix table-rendering bug when table rows have trailing whitespace.
+ [#368](https://github.com/dart-lang/markdown/issues/368).
+* Do not allow reference link labels to contain left brackets. Thanks
+ @chenzhiguang.
+ [#335](https://github.com/dart-lang/markdown/issues/335).
+* Treat lines matching a code block syntax as continuations of paragraphs,
+ inside blockquotes. Thanks @chenzhiguang.
+ [#358](https://github.com/dart-lang/markdown/issues/358).
+* Add a syntax for GitLab-flavored fenced blockquotes. GitLab-flavored Markdown
+ will be evaluated into an ExtensionSet, in a future release. Thanks
+ @chenzhiguang.
+ [#359](https://github.com/dart-lang/markdown/issues/359).
+* Add `bool withDefaultInlineSyntaxes` and `bool withDefaultBlockSyntaxes`
+ parameters to `markdownToHtml` and `Document` to support the case of
+ specifying exactly the list of desired syntaxes. Thanks @chenzhiguang.
+ [#393](https://github.com/dart-lang/markdown/issues/393).
+
+## 4.0.1
+
+* Export `src/emojis.dart` in public API.
+* Update version of example page.
+* Internal: enforce lint rules found in the lints package.
+* Bump io dependency to `^1.0.0`.
+
+## 4.0.0
+
+* Stable null safety release.
+* Require the latest `args`, update the markdown executable to be opted in.
+
+## 4.0.0-nullsafety.0
+
+* Migrate package to Dart's null safety language feature, requiring Dart
+ 2.12 or higher.
+* **Breaking change:** The TagSyntax constructor no longer takes an `end`
+ parameter. TagSyntax no longer implements `onMatchEnd`. Instead, TagSyntax
+ implements a method called `close` which creates and returns a Node, if a
+ Node can be created and closed at the current position. If the TagSyntax
+ instance cannot create a Node at the current position, the method should
+ return `null`. Some TagSyntax subclasses will unconditionally create a tag in
+ `close`, while others may be unable to, such as LinkSyntax, if an inline or
+ reference link could not be resolved.
+* Improved parsing of nested links, images, and emphasis. CommonMark compliance
+ of emphasis-parsing improves to 99%, and link-parsing compliance rises to
+ 93%. Overall compliance improves to 94% and overall GitHub-flavored Markdown
+ improves to 93%.
+
+## 3.0.0
+
+* **Breaking change:** Remove `ListSyntax.removeLeadingEmptyLine`,
+ `ListSyntax.removeTrailingEmptyLines`, `TableSyntax.parseAlignments`,
+ `TableSyntax.parseRow`.
+* Allow intra-word strikethrough in GFM
+ ([#300](https://github.com/dart-lang/markdown/issues/300)).
+* **Breaking change:** Change `BlockSyntax.canEndBlock` from a getter to a
+ method accepting a BlockParser.
+
+## 2.1.8
+
+* Deprecate the _public_ methods `ListSyntax.removeLeadingEmptyLine`,
+ `ListSyntax.removeTrailingEmptyLines`, `TableSyntax.parseAlignments`,
+ `TableSyntax.parseRow`. These will be made private in a major version bump as
+ early as 3.0.0.
+
+## 2.1.7
+
+* Add dependency on the meta package
+
+## 2.1.6
+
+* Fix for custom link resolvers
+ ([#295](https://github.com/dart-lang/markdown/issues/295)).
+* Add missing HTML 5 block-level items
+ ([#294](https://github.com/dart-lang/markdown/pull/294)).
+
+## 2.1.5
+
+* Overhaul table row parsing. This does not have many consequences, except that
+ whitespace around escaped pipes is handled better.
+ ([#287](https://github.com/dart-lang/markdown/issues/287)).
+
+## 2.1.4
+
+* Correctly parse a reference link with a newline in the link reference part
+ ([#281](https://github.com/dart-lang/markdown/issues/281)).
+
+## 2.1.3
+
+* Do not encode HTML in link URLs. Also do not encode HTML in link text when
+ `encodeHtml` is false (e.g. when used in Flutter).
+
+## 2.1.2
+
+* Drop support for Dart 2.0.0 through 2.1.0.
+* Recognize Unicode ellipsis (…) and other Unicode punctuation as punctuation
+ when parsing potential emphasis.
+* Reduce time to parse a large HTML-block-free Markdown document (such as that
+ in #271) by more than half.
+* Add a new optional parameter for InlineSyntax(), `startCharacter`, where a
+ subclass can specify a single character to try to match, before matching with
+ more expensive regular expressions.
+
+## 2.1.1
+
+* Fix for encoding HTML for text string that contains `<pre>`
+ ([#263](https://github.com/dart-lang/markdown/issues/263)).
+
+## 2.1.0
+
+* Improve strict spec compliance of `>` handling by always encoding as `>`
+ – unless preceded by `/`.
+* Improve strict spec compliance for `blockquote` by always putting the closing
+ tag on a new line.
+* Improve strict spec compliance for `code` elements defined with "\`".
+* Properly encode `<`, `>`, and `"` as their respective HTML entities when
+ interpreted as text.
+* Improve inline code parsing when using multiple backticks.
+* Do not encode HTML in indented code blocks when `encodeHtml` is false (e.g.
+ when used in Flutter).
+
+## 2.0.3
+
+* Render element attributes in the order they were defined.
+ Aligns more closely with the strict spec definition.
+* Correctly render `&` within inline image titles.
+* Add 68 new GitHub emoji.
+* Escape HTML attribute for fenced code blocks, in the info string.
+
+## 2.0.2
+
+* Set max SDK version to `<3.0.0`, and adjust other dependencies.
+
+## 2.0.1
+
+* Require Dart 2.0.0-dev.
+
+## 2.0.0
+
+* **Breaking change:** The `Link` class has been renamed `LinkReference`, and
+ the `Document` field, `refLinks`, has been renamed `linkReferences`.
+* **Breaking change:** Remove the deprecated `ExtensionSet.gitHub` field.
+ Use `ExtensionSet.gitHubFlavored` instead.
+* **Breaking change:** Make all of the fields on `Document` read-only.
+* Overhaul support for emphasis (`*foo*` and `_foo_`) and strong emphasis
+ (`**foo**` and `__foo__`), dramatically improving CommonMark compliance.
+* Overhaul support for links and images, again dramatically improving CommonMark
+ compliance.
+* Improve support for tab characters, and horizontal rules.
+* Add support for GitHub Flavored Markdown's Strikethrough extension. See the
+ [GFM spec][strikethrough].
+* The above fixes raise compliance with the CommonMark specs to 93%, and
+ compliance with the GFM specs to 92%.
+* Add an `encodeHtml` parameter to `Document`, which defaults to true. When
+ false, HTML entities (such as `©` and the `<` character) will not be
+ escaped, useful when rendering Markdown in some output format other than HTML.
+* Allow the binary script to take a `--extension-set` option.
+
+ A reminder: You can [run `bin/markdown.dart` from anywhere][pub-global] via:
+
+ ```shell
+ $ pub global activate markdown
+ $ markdown
+ ```
+
+[strikethrough]: https://github.github.com/gfm/#strikethrough-extension-
+[pub-global]: https://dart.dev/tools/pub/cmd/pub-global#running-a-script-from-your-path
+
+## 1.1.1
+
+* Add support for GitHub's colon-based Emoji syntax. :tada:! This is available
+ in the `gitHubWeb` extension set.
+
+## 1.1.0
+
+* Make the constructor for ExtensionSet public, for tools like dartdoc.
+* Split the `gitHub` ExtensionSet into two sets: `gitHubFlavored`, which
+ represents the GitHub Flavored Markdown spec, and `gitHubWeb`, which
+ represents what GitHub actually renders Markdown.
+
+## 1.0.0
+
+* Fix issue where `accept` could cause an exception.
+* Remove deprecated `escapeHtml` function.
+* Fix compliance with auto-links, including support for email addresses.
+* Updated `ExtensionSet.gitHub` to more closely align with GitHub markdown.
+
+## 0.11.4
+
+* Fix bug with lazy blockquote continuations (#162)
+* Fix bug with list item continuations (#156)
+
+## 0.11.3
+
+* Deprecate `escapeHtml`. This code exists in `dart:convert`.
+
+## 0.11.2
+
+* Fix reference code links inside blockquotes.
+* Add src/util.dart to exports.
+
+## 0.11.1
+
+* Add version information:
+ * `dart bin/markdown.dart --version` now shows the package version number.
+ * The playground app now shows the version number.
+* Improve autolink parsing.
+* Add new table syntax: `TableSyntax`.
+* Add new ExtensionSet that includes the table syntax: `ExtensionSet.gitHub`.
+* For development: added `tool/travis.sh`.
+* Support multiline Setext headers.
+* Handle loose-vs-strict list items better.
+* Support ordered lists that start with a number other than 1.
+
+## 0.11.0+1
+
+* Add playground app at https://dart-lang.github.io/markdown.
+
+## 0.11.0
+
+* Parse HTML blocks more accurately, according to
+ [CommonMark](https://spec.commonmark.org/0.24/#html-blocks).
+* Support [shortcut reference
+ links](https://spec.commonmark.org/0.24/#reference-link).
+* Don't allow an indented code block to interrupt a paragraph.
+* Change definition of "loose" and "strict" lists (items wrapped in
+ paragraph tags vs not) to CommonMark's. The primary difference is that any
+ single list item can trigger the entire list to be marked as "loose", rather
+ than defining "looseness" on each specific item.
+* Fix paragraph continuations in blockquotes and list items.
+* Fix silly typing bug with `tool/common_mark_stats.dart`, which resulted in
+ a dramatic overestimate of our CommonMark compliance.
+* There are now 427/613 (69%) passing CommonMark v0.25 specs.
+
+## 0.10.1
+
+* Parse [hard line breaks](https://spec.commonmark.org/0.24/#hard-line-breaks)
+ properly (#86). Thanks @mehaase!
+* Fix processing of `[ ... ]` syntax when no resolver is specified (#92).
+* There are now 401/613 (65%) passing CommonMark v0.24 specs.
+ (_Actually: after 0f64c8f the actual number of passing tests was 352/613
+ (57%)._)
+
+## 0.10.0
+
+* BREAKING: Now following the CommonMark spec for fenced code blocks.
+ If a language (info string) is provided, it is added as a class to the `code`
+ element with a `language-` prefix.
+* BREAKING: Now following the CommonMark spec for images. Previously,
+ `` would compile too
+ `<a href="img.prg"><img src="img.prg" alt="text"></img></a>`. That same code
+ will now compile to `<img src="img.png" alt="text" />`.
+* Fix all [strong mode][] errors.
+
+[strong mode]: https://github.com/dart-lang/dev_compiler/blob/master/STRONG_MODE.md
+
+## 0.9.0
+
+* BREAKING: The text `[foo] (bar)` no longer renders as an inline link (#53).
+* BREAKING: Change list parsing to allow lists to begin immediately after a
+ preceding block element, without a blank line in between.
+* Formalize an API for Markdown extensions (#43).
+* Introduce ExtensionSets. FencedCodeBlock is considered an extension, but
+ existing usage of `markdownToHtml()` and `new Document()` will use the
+ default extension set, which is `ExtensionSet.commonMark`, which includes
+ FencedCodeBlock.
+* Inline HTML syntax support; This is also considered an extension (#18).
+* The text `[foo]()` now renders as an inline link.
+* Whitespace now allowed between a link's destination and title (#65).
+* Header identifier support in the HeaderWithIdSyntax and
+ SetextHeaderWithIdSyntax extensions.
+* Implement backslash-escaping so that Markdown syntax can be escaped, such as
+ `[foo]\(bar) ==> <p>[foo](bar)</p>`.
+* Support for hard line breaks with either `\\\n` or <code> \n</code> (#30,
+ #60).
+* New public method for BlockParser: `peek(int linesAhead)`, meant for use in
+ subclasses.
+* New public members for ListSyntax: `blocksInList` and `determineBlockItems()`,
+ meant for use in subclasses.
+* Improve public docs (better, and more of them).
+
+## 0.8.0
+
+* **Breaking:** Remove (probably unused) fields: `LinkSyntax.resolved`,
+ `InlineParser.currentSource`.
+* Switch tests to use [test][] instead of [unittest][].
+* Fix a few bugs in inline code syntax.
+* Ignore underscores inside words (#41).
+
+[test]: https://pub.dev/packages/test
+[unittest]: https://pub.dev/packages/unittest
+
+## 0.7.2
+
+* Allow resolving links that contain inline syntax (#42).
+
+## 0.7.1+3
+
+* Updated homepage.
+
+## 0.7.1+2
+
+* Formatted code.
+
+* Updated readme.
diff --git a/pkgs/markdown/LICENSE b/pkgs/markdown/LICENSE
new file mode 100644
index 0000000..c2730c9
--- /dev/null
+++ b/pkgs/markdown/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2012, 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.
diff --git a/pkgs/markdown/README.md b/pkgs/markdown/README.md
new file mode 100644
index 0000000..5642563
--- /dev/null
+++ b/pkgs/markdown/README.md
@@ -0,0 +1,173 @@
+[](https://github.com/dart-lang/tools/actions/workflows/markdown.yaml)
+[](https://pub.dev/packages/markdown)
+[](https://pub.dev/packages/markdown/publisher)
+
+A portable Markdown library written in Dart. It can parse Markdown into HTML on
+both the client and server.
+
+Play with it at
+[dart-lang.github.io/tools/markdown](https://dart-lang.github.io/tools/markdown).
+
+### Usage
+
+```dart
+import 'package:markdown/markdown.dart';
+
+void main() {
+ print(markdownToHtml('Hello *Markdown*'));
+ //=> <p>Hello <em>Markdown</em></p>
+}
+```
+
+### Syntax extensions
+
+A few Markdown extensions, beyond what was specified in the original
+[Perl Markdown][] implementation, are supported. By default, the ones supported
+in [CommonMark] are enabled. Any individual extension can be enabled by
+specifying an Array of extension syntaxes in the `blockSyntaxes` or
+`inlineSyntaxes` argument of `markdownToHtml`.
+
+The currently supported inline extension syntaxes are:
+
+* `InlineHtmlSyntax()` - approximately CommonMark's
+ [definition][commonmark-raw-html] of "Raw HTML".
+
+The currently supported block extension syntaxes are:
+
+* `const FencedCodeBlockSyntax()` - Code blocks familiar to Pandoc and PHP
+ Markdown Extra users.
+* `const HeaderWithIdSyntax()` - ATX-style headers have generated IDs, for link
+ anchors (akin to Pandoc's [`auto_identifiers`][pandoc-auto_identifiers]).
+* `const SetextHeaderWithIdSyntax()` - Setext-style headers have generated IDs
+ for link anchors (akin to Pandoc's
+ [`auto_identifiers`][pandoc-auto_identifiers]).
+* `const TableSyntax()` - Table syntax familiar to GitHub, PHP Markdown Extra,
+ and Pandoc users.
+
+For example:
+
+```dart
+import 'package:markdown/markdown.dart';
+
+void main() {
+ print(markdownToHtml('Hello <span class="green">Markdown</span>',
+ inlineSyntaxes: [InlineHtmlSyntax()]));
+ //=> <p>Hello <span class="green">Markdown</span></p>
+}
+```
+
+### Extension sets
+
+To make extension management easy, you can also just specify an extension set.
+Both `markdownToHtml()` and `Document()` accept an `extensionSet` named
+parameter. Currently, there are four pre-defined extension sets:
+
+* `ExtensionSet.none` includes no extensions. With no extensions, Markdown
+ documents will be parsed with a default set of block and inline syntax
+ parsers that closely match how the document might be parsed by the original
+ [Perl Markdown][] implementation.
+
+* `ExtensionSet.commonMark` includes two extensions in addition to the default
+ parsers to bring the parsed output closer to the [CommonMark] specification:
+
+ * Block Syntax Parser
+ * `const FencedCodeBlockSyntax()`
+
+ * Inline Syntax Parser
+ * `InlineHtmlSyntax()`
+
+* `ExtensionSet.gitHubFlavored` includes five extensions in addition to the default
+ parsers to bring the parsed output close to the [GitHub Flavored] Markdown
+ specification:
+
+ * Block Syntax Parser
+ * `const FencedCodeBlockSyntax()`
+ * `const TableSyntax()`
+
+ * Inline Syntax Parser
+ * `InlineHtmlSyntax()`
+ * `StrikethroughSyntax()`
+ * `AutolinkExtensionSyntax()`
+
+* `ExtensionSet.gitHubWeb` includes eight extensions. The same set of parsers use
+ in the `gitHubFlavored` extension set with the addition of the block syntax parsers,
+ HeaderWithIdSyntax and SetextHeaderWithIdSyntax, which add `id` attributes to
+ headers and inline syntax parser, EmojiSyntax, for parsing GitHub style emoji
+ characters:
+
+ * Block Syntax Parser
+ * `const FencedCodeBlockSyntax()`
+ * `const HeaderWithIdSyntax()`, which adds `id` attributes to ATX-style
+ headers, for easy intra-document linking.
+ * `const SetextHeaderWithIdSyntax()`, which adds `id` attributes to
+ Setext-style headers, for easy intra-document linking.
+ * `const TableSyntax()`
+
+ * Inline Syntax Parser
+ * `InlineHtmlSyntax()`
+ * `StrikethroughSyntax()`
+ * `EmojiSyntax()`
+ * `AutolinkExtensionSyntax()`
+
+### Custom syntax extensions
+
+You can create and use your own syntaxes.
+
+```dart
+import 'package:markdown/markdown.dart';
+
+void main() {
+ var syntaxes = [TextSyntax('nyan', sub: '~=[,,_,,]:3')];
+ print(markdownToHtml('nyan', inlineSyntaxes: syntaxes));
+ //=> <p>~=[,,_,,]:3</p>
+}
+```
+
+### HTML sanitization
+
+This package offers no features in the way of HTML sanitization. Read Estevão
+Soares dos Santos's great article, ["Markdown's XSS Vulnerability (and how to
+mitigate it)"], to learn more.
+
+The authors recommend that you perform any necessary sanitization on the
+resulting HTML, for example via `dart:html`'s [NodeValidator].
+
+### CommonMark compliance
+
+This package contains a number of files in the `tool` directory for tracking
+compliance with [CommonMark].
+
+#### Updating CommonMark stats when changing the implementation
+
+ 1. Update the library and test code, making sure that tests still pass.
+ 2. Run `dart run tool/stats.dart --update-files` to update the
+ per-test results `tool/common_mark_stats.json` and the test summary
+ `tool/common_mark_stats.txt`.
+ 3. Verify that more tests now pass – or at least, no more tests fail.
+ 4. Make sure you include the updated stats files in your commit.
+
+#### Updating the CommonMark test file for a spec update
+
+ 1. Check out the [CommonMark source]. Make sure you checkout a *major* release.
+ 2. Dump the test output overwriting the existing tests file.
+
+ ```console
+ > cd /path/to/common_mark_dir
+ > python3 test/spec_tests.py --dump-tests > \
+ /path/to/markdown.dart/tool/common_mark_tests.json
+ ```
+
+ 3. Update the stats files as described above. Note any changes in the results.
+ 4. Update any references to the existing spec by search for
+ `https://spec.commonmark.org/0.30/` in the repository. (Including this one.)
+ Verify the updated links are still valid.
+ 5. Commit changes, including a corresponding note in `CHANGELOG.md`.
+
+[Perl Markdown]: https://daringfireball.net/projects/markdown/
+[CommonMark]: https://commonmark.org/
+[commonMark-raw-html]: https://spec.commonmark.org/0.30/#raw-html
+[CommonMark source]: https://github.com/commonmark/commonmark-spec
+[GitHub Flavored]: https://github.github.io/gfm/
+[pandoc-auto_identifiers]: https://pandoc.org/MANUAL.html#extension-auto_identifiers
+["Markdown's XSS Vulnerability (and how to mitigate it)"]: https://github.com/showdownjs/showdown/wiki/Markdown%27s-XSS-Vulnerability-(and-how-to-mitigate-it)
+[NodeValidator]: https://api.dart.dev/stable/dart-html/NodeValidator-class.html
diff --git a/pkgs/markdown/analysis_options.yaml b/pkgs/markdown/analysis_options.yaml
new file mode 100644
index 0000000..e7748f5
--- /dev/null
+++ b/pkgs/markdown/analysis_options.yaml
@@ -0,0 +1,36 @@
+# https://dart.dev/tools/analysis
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-casts: true
+ strict-inference: true
+ strict-raw-types: true
+
+ errors:
+ # The example app explicitly takes a String of user-generated HTML and
+ # inserts it straight into a <div> using innerHtml.
+ unsafe_html: ignore
+ # Waiting on a couple of bug fixes and new features before this should be enabled
+ comment_references: ignore
+
+linter:
+ rules:
+ # https://github.com/dart-lang/linter/issues/574
+ #- comment_references
+ - avoid_private_typedef_functions
+ - avoid_redundant_argument_values
+ - avoid_unused_constructor_parameters
+ - avoid_void_async
+ - cancel_subscriptions
+ - literal_only_boolean_expressions
+ - missing_whitespace_between_adjacent_strings
+ - no_adjacent_strings_in_list
+ - prefer_const_declarations
+ - prefer_final_locals
+ - prefer_final_in_for_each
+ - unnecessary_await_in_return
+ - unnecessary_raw_strings
+ - use_if_null_to_convert_nulls_to_bools
+ - use_raw_strings
+ - use_string_buffers
diff --git a/pkgs/markdown/benchmark/benchmark.dart b/pkgs/markdown/benchmark/benchmark.dart
new file mode 100644
index 0000000..aaf8112
--- /dev/null
+++ b/pkgs/markdown/benchmark/benchmark.dart
@@ -0,0 +1,64 @@
+// Copyright (c) 2015, 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:markdown/markdown.dart';
+import 'package:path/path.dart' as p;
+
+const numTrials = 100;
+const runsPerTrial = 50;
+
+final source = _loadFile('input.md');
+final expected = _loadFile('output.html');
+
+void main() {
+ var best = double.infinity;
+
+ // Run the benchmark several times. This ensures the VM is warmed up and lets
+ // us see how much variance there is.
+ for (var i = 0; i <= numTrials; i++) {
+ final stopwatch = Stopwatch()..start();
+
+ // For a single benchmark, convert the source multiple times.
+ late String result;
+ for (var j = 0; j < runsPerTrial; j++) {
+ result = markdownToHtml(source);
+ }
+
+ stopwatch.stop();
+ final elapsed = stopwatch.elapsedMilliseconds / runsPerTrial;
+
+ // Keep track of the best run so far.
+ if (elapsed >= best) continue;
+ best = elapsed;
+
+ // Sanity check to make sure the output is what we expect and to make sure
+ // the VM doesn't optimize "dead" code away.
+ if (result != expected) {
+ print('Incorrect output:\n$result');
+ exitCode = 1;
+ return;
+ }
+
+ // Don't print the first run. It's always terrible since the VM hasn't
+ // warmed up yet.
+ if (i == 0) continue;
+ _printResult("Run ${'#$i'.padLeft(3)}", elapsed);
+ }
+
+ _printResult('Best ', best);
+}
+
+String _loadFile(String name) {
+ final path = p.join(p.dirname(p.fromUri(Platform.script)), name);
+ return File(path).readAsStringSync();
+}
+
+void _printResult(String label, double time) {
+ print(
+ '$label: ${time.toStringAsFixed(2).padLeft(4)}ms '
+ "${'=' * ((time * 20).toInt())}",
+ );
+}
diff --git a/pkgs/markdown/benchmark/input.md b/pkgs/markdown/benchmark/input.md
new file mode 100644
index 0000000..2dd9df5
--- /dev/null
+++ b/pkgs/markdown/benchmark/input.md
@@ -0,0 +1,421 @@
+**TODO: Add more examples to cover all of the syntax.**
+
+# Regressions
+
+Bad backtracking in the HR parser:
+
+-------------------------- | -------------------------------------------------
+
+# Real-world sample
+
+This input was taken from the test package's README to get a representative
+sample of real-world markdown:
+
+Tests are specified using the top-level [`test()`][test] function, and test
+assertions are made using [`expect()`][expect]:
+
+[test]: https://pub.dev/documentation/test_core/latest/test_core/test.html
+[expect]: https://pub.dev/documentation/test_api/latest/test_api/expect.html
+
+```dart
+import "package:test/test.dart";
+
+void main() {
+ test("String.split() splits the string on the delimiter", () {
+ var string = "foo,bar,baz";
+ expect(string.split(","), equals(["foo", "bar", "baz"]));
+ });
+
+ test("String.trim() removes surrounding whitespace", () {
+ var string = " foo ";
+ expect(string.trim(), equals("foo"));
+ });
+}
+```
+
+Tests can be grouped together using the [`group()`] function. Each group's
+description is added to the beginning of its test's descriptions.
+
+```dart
+import "package:test/test.dart";
+
+void main() {
+ group("String", () {
+ test(".split() splits the string on the delimiter", () {
+ var string = "foo,bar,baz";
+ expect(string.split(","), equals(["foo", "bar", "baz"]));
+ });
+
+ test(".trim() removes surrounding whitespace", () {
+ var string = " foo ";
+ expect(string.trim(), equals("foo"));
+ });
+ });
+
+ group("int", () {
+ test(".remainder() returns the remainder of division", () {
+ expect(11.remainder(3), equals(2));
+ });
+
+ test(".toRadixString() returns a hex string", () {
+ expect(11.toRadixString(16), equals("b"));
+ });
+ });
+}
+```
+
+Any matchers from the [`matcher`][matcher] package can be used with `expect()`
+to do complex validations:
+
+[matcher]: https://pub.dev/documentation/matcher/latest/matcher/matcher-library.html
+
+```dart
+import "package:test/test.dart";
+
+void main() {
+ test(".split() splits the string on the delimiter", () {
+ expect("foo,bar,baz", allOf([
+ contains("foo"),
+ isNot(startsWith("bar")),
+ endsWith("baz")
+ ]));
+ });
+}
+```
+
+## Running Tests
+
+A single test file can be run just using `dart run test path/to/test.dart`.
+
+Many tests can be run at a time using `dart run test path/to/dir`.
+
+It's also possible to run a test on the Dart VM only by invoking it using `dart
+path/to/test.dart`, but this doesn't load the full test runner and will be
+missing some features.
+
+The test runner considers any file that ends with `_test.dart` to be a test
+file. If you don't pass any paths, it will run all the test files in your
+`test/` directory, making it easy to test your entire application at once.
+
+By default, tests are run in the Dart VM, but you can run them in the browser as
+well by passing `dart run test -p chrome path/to/test.dart`.
+`test` will take care of starting the browser and loading the tests, and all
+the results will be reported on the command line just like for VM tests. In
+fact, you can even run tests on both platforms with a single command: `dart run
+test -p chrome,vm path/to/test.dart`.
+
+### Restricting Tests to Certain Platforms
+
+Some test files only make sense to run on particular platforms. They may use
+`dart:html` or `dart:io`, they might test Windows' particular filesystem
+behavior, or they might use a feature that's only available in Chrome. The
+[`@TestOn`][TestOn] annotation makes it easy to declare exactly which platforms
+a test file should run on. Just put it at the top of your file, before any
+`library` or `import` declarations:
+
+```dart
+@TestOn("vm")
+
+import "dart:io";
+
+import "package:test/test.dart";
+
+void main() {
+ // ...
+}
+```
+
+[TestOn]: https://pub.dev/documentation/test_api/latest/test_api/TestOn-class.html
+
+The string you pass to `@TestOn` is what's called a "platform selector", and it
+specifies exactly which platforms a test can run on. It can be as simple as the
+name of a platform, or a more complex Dart-like boolean expression involving
+these platform names.
+
+### Platform Selector Syntax
+
+Platform selectors can contain identifiers, parentheses, and operators. When
+loading a test, each identifier is set to `true` or `false` based on the current
+platform, and the test is only loaded if the platform selector returns `true`.
+The operators `||`, `&&`, `!`, and `? :` all work just like they do in Dart. The
+valid identifiers are:
+
+* `vm`: Whether the test is running on the command-line Dart VM.
+
+* `dartium`: Whether the test is running on Dartium.
+
+* `content-shell`: Whether the test is running on the headless Dartium content
+ shell.
+
+* `chrome`: Whether the test is running on Google Chrome.
+
+* `phantomjs`: Whether the test is running on
+ [PhantomJS](http://phantomjs.org/).
+
+* `firefox`: Whether the test is running on Mozilla Firefox.
+
+* `safari`: Whether the test is running on Apple Safari.
+
+* `ie`: Whether the test is running on Microsoft Internet Explorer.
+
+* `dart-vm`: Whether the test is running on the Dart VM in any context,
+ including Dartium. It's identical to `!js`.
+
+* `browser`: Whether the test is running in any browser.
+
+* `js`: Whether the test has been compiled to JS. This is identical to
+ `!dart-vm`.
+
+* `blink`: Whether the test is running in a browser that uses the Blink
+ rendering engine.
+
+* `windows`: Whether the test is running on Windows. If `vm` is false, this will
+ be `false` as well.
+
+* `mac-os`: Whether the test is running on Mac OS. If `vm` is false, this will
+ be `false` as well.
+
+* `linux`: Whether the test is running on Linux. If `vm` is false, this will be
+ `false` as well.
+
+* `android`: Whether the test is running on Android. If `vm` is false, this will
+ be `false` as well, which means that this *won't* be true if the test is
+ running on an Android browser.
+
+* `posix`: Whether the test is running on a POSIX operating system. This is
+ equivalent to `!windows`.
+
+For example, if you wanted to run a test on every browser but Chrome, you would
+write `@TestOn("browser && !chrome")`.
+
+## Asynchronous Tests
+
+Tests written with `async`/`await` will work automatically. The test runner
+won't consider the test finished until the returned `Future` completes.
+
+```dart
+import "dart:async";
+
+import "package:test/test.dart";
+
+void main() {
+ test("new Future.value() returns the value", () async {
+ var value = await new Future.value(10);
+ expect(value, equals(10));
+ });
+}
+```
+
+There are also a number of useful functions and matchers for more advanced
+asynchrony. The [`completion()`][completion] matcher can be used to test
+`Futures`; it ensures that the test doesn't finish until the `Future` completes,
+and runs a matcher against that `Future`'s value.
+
+[completion]: https://pub.dev/documentation/test_api/latest/test_api/completion.html
+
+```dart
+import "dart:async";
+
+import "package:test/test.dart";
+
+void main() {
+ test("new Future.value() returns the value", () {
+ expect(new Future.value(10), completion(equals(10)));
+ });
+}
+```
+
+The [`throwsA()`][throwsA] matcher and the various `throwsExceptionType`
+matchers work with both synchronous callbacks and asynchronous `Future`s. They
+ensure that a particular type of exception is thrown:
+
+[throwsA]: https://pub.dev/documentation/test_api/latest/test_api/throwsA.html
+
+```dart
+import "dart:async";
+
+import "package:test/test.dart";
+
+void main() {
+ test("new Future.error() throws the error", () {
+ expect(new Future.error("oh no"), throwsA(equals("oh no")));
+ expect(new Future.error(new StateError("bad state")), throwsStateError);
+ });
+}
+```
+
+The [`expectAsync()`][expectAsync] function wraps another function and has two
+jobs. First, it asserts that the wrapped function is called a certain number of
+times, and will cause the test to fail if it's called too often; second, it
+keeps the test from finishing until the function is called the requisite number
+of times.
+
+```dart
+import "dart:async";
+
+import "package:test/test.dart";
+
+void main() {
+ test("Stream.fromIterable() emits the values in the iterable", () {
+ var stream = new Stream.fromIterable([1, 2, 3]);
+
+ stream.listen(expectAsync((number) {
+ expect(number, inInclusiveRange(1, 3));
+ }, count: 3));
+ });
+}
+```
+
+[expectAsync]: https://pub.dev/documentation/test_api/latest/test_api/expectAsync.html
+
+## Running Tests with Custom HTML
+
+By default, the test runner will generate its own empty HTML file for browser
+tests. However, tests that need custom HTML can create their own files. These
+files have three requirements:
+
+* They must have the same name as the test, with `.dart` replaced by `.html`.
+
+* They must contain a `link` tag with `rel="x-dart-test"` and an `href`
+ attribute pointing to the test script.
+
+* They must contain `<script src="packages/test/dart.js"></script>`.
+
+For example, if you had a test called `custom_html_test.dart`, you might write
+the following HTML file:
+
+```html
+<!doctype html>
+<!-- custom_html_test.html -->
+<html>
+ <head>
+ <title>Custom HTML Test</title>
+ <link rel="x-dart-test" href="custom_html_test.dart">
+ <script src="packages/test/dart.js"></script>
+ </head>
+ <body>
+ // ...
+ </body>
+</html>
+```
+
+## Configuring Tests
+
+### Skipping Tests
+
+If a test, group, or entire suite isn't working yet and you just want it to stop
+complaining, you can mark it as "skipped". The test or tests won't be run, and,
+if you supply a reason why, that reason will be printed. In general, skipping
+tests indicates that they should run but is temporarily not working. If they're
+is fundamentally incompatible with a platform, [`@TestOn`/`testOn`][TestOn]
+should be used instead.
+
+[TestOn]: #restricting-tests-to-certain-platforms
+
+To skip a test suite, put a `@Skip` annotation at the top of the file:
+
+```dart
+@Skip("currently failing (see issue 1234)")
+
+import "package:test/test.dart";
+
+void main() {
+ // ...
+}
+```
+
+The string you pass should describe why the test is skipped. You don't have to
+include it, but it's a good idea to document why the test isn't running.
+
+Groups and individual tests can be skipped by passing the `skip` parameter. This
+can be either `true` or a String describing why the test is skipped. For example:
+
+```dart
+import "package:test/test.dart";
+
+void main() {
+ group("complicated algorithm tests", () {
+ // ...
+ }, skip: "the algorithm isn't quite right");
+
+ test("error-checking test", () {
+ // ...
+ }, skip: "TODO: add error-checking.");
+}
+```
+
+### Timeouts
+
+By default, tests will time out after 30 seconds of inactivity. However, this
+can be configured on a per-test, -group, or -suite basis. To change the timeout
+for a test suite, put a `@Timeout` annotation at the top of the file:
+
+```dart
+@Timeout(const Duration(seconds: 45))
+
+import "package:test/test.dart";
+
+void main() {
+ // ...
+}
+```
+
+In addition to setting an absolute timeout, you can set the timeout relative to
+the default using `@Timeout.factor`. For example, `@Timeout.factor(1.5)` will
+set the timeout to one and a half times as long as the default—45 seconds.
+
+Timeouts can be set for tests and groups using the `timeout` parameter. This
+parameter takes a `Timeout` object just like the annotation. For example:
+
+```dart
+import "package:test/test.dart";
+
+void main() {
+ group("slow tests", () {
+ // ...
+
+ test("even slower test", () {
+ // ...
+ }, timeout: new Timeout.factor(2))
+ }, timeout: new Timeout(new Duration(minutes: 1)));
+}
+```
+
+Nested timeouts apply in order from outermost to innermost. That means that
+"even slower test" will take two minutes to time out, since it multiplies the
+group's timeout by 2.
+
+### Platform-Specific Configuration
+
+Sometimes a test may need to be configured differently for different platforms.
+Windows might run your code slower than other platforms, or your DOM
+manipulation might not work right on Safari yet. For these cases, you can use
+the `@OnPlatform` annotation and the `onPlatform` named parameter to `test()`
+and `group()`. For example:
+
+```dart
+@OnPlatform(const {
+ // Give Windows some extra wiggle-room before timing out.
+ "windows": const Timeout.factor(2)
+})
+
+import "package:test/test.dart";
+
+void main() {
+ test("do a thing", () {
+ // ...
+ }, onPlatform: {
+ "safari": new Skip("Safari is currently broken (see #1234)")
+ });
+}
+```
+
+Both the annotation and the parameter take a map. The map's keys are [platform
+selectors](#platform-selector-syntax) which describe the platforms for which the
+specialized configuration applies. Its values are instances of some of the same
+annotation classes that can be used for a suite: `Skip` and `Timeout`. A value
+can also be a list of these values.
+
+If multiple platforms match, the configuration is applied in order from first to
+last, just as they would in nested groups. This means that for configuration
+like duration-based timeouts, the last matching value wins.
diff --git a/pkgs/markdown/benchmark/output.html b/pkgs/markdown/benchmark/output.html
new file mode 100644
index 0000000..200e4bd
--- /dev/null
+++ b/pkgs/markdown/benchmark/output.html
@@ -0,0 +1,360 @@
+<p><strong>TODO: Add more examples to cover all of the syntax.</strong></p>
+<h1>Regressions</h1>
+<p>Bad backtracking in the HR parser:</p>
+<p>-------------------------- | -------------------------------------------------</p>
+<h1>Real-world sample</h1>
+<p>This input was taken from the test package's README to get a representative
+sample of real-world markdown:</p>
+<p>Tests are specified using the top-level <a href="https://pub.dev/documentation/test_core/latest/test_core/test.html"><code>test()</code></a> function, and test
+assertions are made using <a href="https://pub.dev/documentation/test_api/latest/test_api/expect.html"><code>expect()</code></a>:</p>
+<pre><code class="language-dart">import "package:test/test.dart";
+
+void main() {
+ test("String.split() splits the string on the delimiter", () {
+ var string = "foo,bar,baz";
+ expect(string.split(","), equals(["foo", "bar", "baz"]));
+ });
+
+ test("String.trim() removes surrounding whitespace", () {
+ var string = " foo ";
+ expect(string.trim(), equals("foo"));
+ });
+}
+</code></pre>
+<p>Tests can be grouped together using the [<code>group()</code>] function. Each group's
+description is added to the beginning of its test's descriptions.</p>
+<pre><code class="language-dart">import "package:test/test.dart";
+
+void main() {
+ group("String", () {
+ test(".split() splits the string on the delimiter", () {
+ var string = "foo,bar,baz";
+ expect(string.split(","), equals(["foo", "bar", "baz"]));
+ });
+
+ test(".trim() removes surrounding whitespace", () {
+ var string = " foo ";
+ expect(string.trim(), equals("foo"));
+ });
+ });
+
+ group("int", () {
+ test(".remainder() returns the remainder of division", () {
+ expect(11.remainder(3), equals(2));
+ });
+
+ test(".toRadixString() returns a hex string", () {
+ expect(11.toRadixString(16), equals("b"));
+ });
+ });
+}
+</code></pre>
+<p>Any matchers from the <a href="https://pub.dev/documentation/matcher/latest/matcher/matcher-library.html"><code>matcher</code></a> package can be used with <code>expect()</code>
+to do complex validations:</p>
+<pre><code class="language-dart">import "package:test/test.dart";
+
+void main() {
+ test(".split() splits the string on the delimiter", () {
+ expect("foo,bar,baz", allOf([
+ contains("foo"),
+ isNot(startsWith("bar")),
+ endsWith("baz")
+ ]));
+ });
+}
+</code></pre>
+<h2>Running Tests</h2>
+<p>A single test file can be run just using <code>pub run test:test path/to/test.dart</code>
+(on Dart 1.10, this can be shortened to <code>pub run test path/to/test.dart</code>).</p>
+<p><img src="https://raw.githubusercontent.com/dart-lang/test/master/image/test1.gif" alt="Single file being run via pub run"" /></p>
+<p>Many tests can be run at a time using <code>pub run test:test path/to/dir</code>.</p>
+<p><img src="https://raw.githubusercontent.com/dart-lang/test/master/image/test2.gif" alt="Directory being run via "pub run"." /></p>
+<p>It's also possible to run a test on the Dart VM only by invoking it using <code>dart path/to/test.dart</code>, but this doesn't load the full test runner and will be
+missing some features.</p>
+<p>The test runner considers any file that ends with <code>_test.dart</code> to be a test
+file. If you don't pass any paths, it will run all the test files in your
+<code>test/</code> directory, making it easy to test your entire application at once.</p>
+<p>By default, tests are run in the Dart VM, but you can run them in the browser as
+well by passing <code>pub run test:test -p chrome path/to/test.dart</code>.
+<code>test</code> will take care of starting the browser and loading the tests, and all
+the results will be reported on the command line just like for VM tests. In
+fact, you can even run tests on both platforms with a single command: <code>pub run test:test -p "chrome,vm" path/to/test.dart</code>.</p>
+<h3>Restricting Tests to Certain Platforms</h3>
+<p>Some test files only make sense to run on particular platforms. They may use
+<code>dart:html</code> or <code>dart:io</code>, they might test Windows' particular filesystem
+behavior, or they might use a feature that's only available in Chrome. The
+<a href="https://pub.dev/documentation/test_api/latest/test_api/TestOn-class.html"><code>@TestOn</code></a> annotation makes it easy to declare exactly which platforms
+a test file should run on. Just put it at the top of your file, before any
+<code>library</code> or <code>import</code> declarations:</p>
+<pre><code class="language-dart">@TestOn("vm")
+
+import "dart:io";
+
+import "package:test/test.dart";
+
+void main() {
+ // ...
+}
+</code></pre>
+<p>The string you pass to <code>@TestOn</code> is what's called a "platform selector", and it
+specifies exactly which platforms a test can run on. It can be as simple as the
+name of a platform, or a more complex Dart-like boolean expression involving
+these platform names.</p>
+<h3>Platform Selector Syntax</h3>
+<p>Platform selectors can contain identifiers, parentheses, and operators. When
+loading a test, each identifier is set to <code>true</code> or <code>false</code> based on the current
+platform, and the test is only loaded if the platform selector returns <code>true</code>.
+The operators <code>||</code>, <code>&&</code>, <code>!</code>, and <code>? :</code> all work just like they do in Dart. The
+valid identifiers are:</p>
+<ul>
+<li>
+<p><code>vm</code>: Whether the test is running on the command-line Dart VM.</p>
+</li>
+<li>
+<p><code>dartium</code>: Whether the test is running on Dartium.</p>
+</li>
+<li>
+<p><code>content-shell</code>: Whether the test is running on the headless Dartium content
+shell.</p>
+</li>
+<li>
+<p><code>chrome</code>: Whether the test is running on Google Chrome.</p>
+</li>
+<li>
+<p><code>phantomjs</code>: Whether the test is running on
+<a href="http://phantomjs.org/">PhantomJS</a>.</p>
+</li>
+<li>
+<p><code>firefox</code>: Whether the test is running on Mozilla Firefox.</p>
+</li>
+<li>
+<p><code>safari</code>: Whether the test is running on Apple Safari.</p>
+</li>
+<li>
+<p><code>ie</code>: Whether the test is running on Microsoft Internet Explorer.</p>
+</li>
+<li>
+<p><code>dart-vm</code>: Whether the test is running on the Dart VM in any context,
+including Dartium. It's identical to <code>!js</code>.</p>
+</li>
+<li>
+<p><code>browser</code>: Whether the test is running in any browser.</p>
+</li>
+<li>
+<p><code>js</code>: Whether the test has been compiled to JS. This is identical to
+<code>!dart-vm</code>.</p>
+</li>
+<li>
+<p><code>blink</code>: Whether the test is running in a browser that uses the Blink
+rendering engine.</p>
+</li>
+<li>
+<p><code>windows</code>: Whether the test is running on Windows. If <code>vm</code> is false, this will
+be <code>false</code> as well.</p>
+</li>
+<li>
+<p><code>mac-os</code>: Whether the test is running on Mac OS. If <code>vm</code> is false, this will
+be <code>false</code> as well.</p>
+</li>
+<li>
+<p><code>linux</code>: Whether the test is running on Linux. If <code>vm</code> is false, this will be
+<code>false</code> as well.</p>
+</li>
+<li>
+<p><code>android</code>: Whether the test is running on Android. If <code>vm</code> is false, this will
+be <code>false</code> as well, which means that this <em>won't</em> be true if the test is
+running on an Android browser.</p>
+</li>
+<li>
+<p><code>posix</code>: Whether the test is running on a POSIX operating system. This is
+equivalent to <code>!windows</code>.</p>
+</li>
+</ul>
+<p>For example, if you wanted to run a test on every browser but Chrome, you would
+write <code>@TestOn("browser && !chrome")</code>.</p>
+<h2>Asynchronous Tests</h2>
+<p>Tests written with <code>async</code>/<code>await</code> will work automatically. The test runner
+won't consider the test finished until the returned <code>Future</code> completes.</p>
+<pre><code class="language-dart">import "dart:async";
+
+import "package:test/test.dart";
+
+void main() {
+ test("new Future.value() returns the value", () async {
+ var value = await new Future.value(10);
+ expect(value, equals(10));
+ });
+}
+</code></pre>
+<p>There are also a number of useful functions and matchers for more advanced
+asynchrony. The <a href="https://pub.dev/documentation/test_api/latest/test_api/completion.html"><code>completion()</code></a> matcher can be used to test
+<code>Futures</code>; it ensures that the test doesn't finish until the <code>Future</code> completes,
+and runs a matcher against that <code>Future</code>'s value.</p>
+<pre><code class="language-dart">import "dart:async";
+
+import "package:test/test.dart";
+
+void main() {
+ test("new Future.value() returns the value", () {
+ expect(new Future.value(10), completion(equals(10)));
+ });
+}
+</code></pre>
+<p>The <a href="https://pub.dev/documentation/test_api/latest/test_api/throwsA.html"><code>throwsA()</code></a> matcher and the various <code>throwsExceptionType</code>
+matchers work with both synchronous callbacks and asynchronous <code>Future</code>s. They
+ensure that a particular type of exception is thrown:</p>
+<pre><code class="language-dart">import "dart:async";
+
+import "package:test/test.dart";
+
+void main() {
+ test("new Future.error() throws the error", () {
+ expect(new Future.error("oh no"), throwsA(equals("oh no")));
+ expect(new Future.error(new StateError("bad state")), throwsStateError);
+ });
+}
+</code></pre>
+<p>The <a href="https://pub.dev/documentation/test_api/latest/test_api/expectAsync.html"><code>expectAsync()</code></a> function wraps another function and has two
+jobs. First, it asserts that the wrapped function is called a certain number of
+times, and will cause the test to fail if it's called too often; second, it
+keeps the test from finishing until the function is called the requisite number
+of times.</p>
+<pre><code class="language-dart">import "dart:async";
+
+import "package:test/test.dart";
+
+void main() {
+ test("Stream.fromIterable() emits the values in the iterable", () {
+ var stream = new Stream.fromIterable([1, 2, 3]);
+
+ stream.listen(expectAsync((number) {
+ expect(number, inInclusiveRange(1, 3));
+ }, count: 3));
+ });
+}
+</code></pre>
+<h2>Running Tests with Custom HTML</h2>
+<p>By default, the test runner will generate its own empty HTML file for browser
+tests. However, tests that need custom HTML can create their own files. These
+files have three requirements:</p>
+<ul>
+<li>
+<p>They must have the same name as the test, with <code>.dart</code> replaced by <code>.html</code>.</p>
+</li>
+<li>
+<p>They must contain a <code>link</code> tag with <code>rel="x-dart-test"</code> and an <code>href</code>
+attribute pointing to the test script.</p>
+</li>
+<li>
+<p>They must contain <code><script src="packages/test/dart.js"></script></code>.</p>
+</li>
+</ul>
+<p>For example, if you had a test called <code>custom_html_test.dart</code>, you might write
+the following HTML file:</p>
+<pre><code class="language-html"><!doctype html>
+<!-- custom_html_test.html -->
+<html>
+ <head>
+ <title>Custom HTML Test</title>
+ <link rel="x-dart-test" href="custom_html_test.dart">
+ <script src="packages/test/dart.js"></script>
+ </head>
+ <body>
+ // ...
+ </body>
+</html>
+</code></pre>
+<h2>Configuring Tests</h2>
+<h3>Skipping Tests</h3>
+<p>If a test, group, or entire suite isn't working yet and you just want it to stop
+complaining, you can mark it as "skipped". The test or tests won't be run, and,
+if you supply a reason why, that reason will be printed. In general, skipping
+tests indicates that they should run but is temporarily not working. If they're
+is fundamentally incompatible with a platform, <a href="https://pub.dev/documentation/test_api/latest/test_api/TestOn-class.html"><code>@TestOn</code>/<code>testOn</code></a>
+should be used instead.</p>
+<p>To skip a test suite, put a <code>@Skip</code> annotation at the top of the file:</p>
+<pre><code class="language-dart">@Skip("currently failing (see issue 1234)")
+
+import "package:test/test.dart";
+
+void main() {
+ // ...
+}
+</code></pre>
+<p>The string you pass should describe why the test is skipped. You don't have to
+include it, but it's a good idea to document why the test isn't running.</p>
+<p>Groups and individual tests can be skipped by passing the <code>skip</code> parameter. This
+can be either <code>true</code> or a String describing why the test is skipped. For example:</p>
+<pre><code class="language-dart">import "package:test/test.dart";
+
+void main() {
+ group("complicated algorithm tests", () {
+ // ...
+ }, skip: "the algorithm isn't quite right");
+
+ test("error-checking test", () {
+ // ...
+ }, skip: "TODO: add error-checking.");
+}
+</code></pre>
+<h3>Timeouts</h3>
+<p>By default, tests will time out after 30 seconds of inactivity. However, this
+can be configured on a per-test, -group, or -suite basis. To change the timeout
+for a test suite, put a <code>@Timeout</code> annotation at the top of the file:</p>
+<pre><code class="language-dart">@Timeout(const Duration(seconds: 45))
+
+import "package:test/test.dart";
+
+void main() {
+ // ...
+}
+</code></pre>
+<p>In addition to setting an absolute timeout, you can set the timeout relative to
+the default using <code>@Timeout.factor</code>. For example, <code>@Timeout.factor(1.5)</code> will
+set the timeout to one and a half times as long as the default—45 seconds.</p>
+<p>Timeouts can be set for tests and groups using the <code>timeout</code> parameter. This
+parameter takes a <code>Timeout</code> object just like the annotation. For example:</p>
+<pre><code class="language-dart">import "package:test/test.dart";
+
+void main() {
+ group("slow tests", () {
+ // ...
+
+ test("even slower test", () {
+ // ...
+ }, timeout: new Timeout.factor(2))
+ }, timeout: new Timeout(new Duration(minutes: 1)));
+}
+</code></pre>
+<p>Nested timeouts apply in order from outermost to innermost. That means that
+"even slower test" will take two minutes to time out, since it multiplies the
+group's timeout by 2.</p>
+<h3>Platform-Specific Configuration</h3>
+<p>Sometimes a test may need to be configured differently for different platforms.
+Windows might run your code slower than other platforms, or your DOM
+manipulation might not work right on Safari yet. For these cases, you can use
+the <code>@OnPlatform</code> annotation and the <code>onPlatform</code> named parameter to <code>test()</code>
+and <code>group()</code>. For example:</p>
+<pre><code class="language-dart">@OnPlatform(const {
+ // Give Windows some extra wiggle-room before timing out.
+ "windows": const Timeout.factor(2)
+})
+
+import "package:test/test.dart";
+
+void main() {
+ test("do a thing", () {
+ // ...
+ }, onPlatform: {
+ "safari": new Skip("Safari is currently broken (see #1234)")
+ });
+}
+</code></pre>
+<p>Both the annotation and the parameter take a map. The map's keys are <a href="#platform-selector-syntax">platform
+selectors</a> which describe the platforms for which the
+specialized configuration applies. Its values are instances of some of the same
+annotation classes that can be used for a suite: <code>Skip</code> and <code>Timeout</code>. A value
+can also be a list of these values.</p>
+<p>If multiple platforms match, the configuration is applied in order from first to
+last, just as they would in nested groups. This means that for configuration
+like duration-based timeouts, the last matching value wins.</p>
diff --git a/pkgs/markdown/bin/markdown.dart b/pkgs/markdown/bin/markdown.dart
new file mode 100644
index 0000000..76926c6
--- /dev/null
+++ b/pkgs/markdown/bin/markdown.dart
@@ -0,0 +1,81 @@
+// Copyright (c) 2020, 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';
+
+import 'package:args/args.dart';
+import 'package:markdown/markdown.dart';
+
+final extensionSets = <String, ExtensionSet>{
+ 'none': ExtensionSet.none,
+ 'CommonMark': ExtensionSet.commonMark,
+ 'GitHubFlavored': ExtensionSet.gitHubFlavored,
+ 'GitHubWeb': ExtensionSet.gitHubWeb,
+};
+
+Future<void> main(List<String> args) async {
+ final parser = ArgParser()
+ ..addFlag('help', negatable: false, help: 'Print help text and exit')
+ ..addFlag('version', negatable: false, help: 'Print version and exit')
+ ..addOption(
+ 'extension-set',
+ allowed: ['none', 'CommonMark', 'GitHubFlavored', 'GitHubWeb'],
+ defaultsTo: 'CommonMark',
+ help: 'Specify a set of extensions',
+ allowedHelp: {
+ 'none': 'No extensions; similar to Markdown.pl',
+ 'CommonMark': 'Parse like CommonMark Markdown (default)',
+ 'GitHubFlavored': 'Parse like GitHub Flavored Markdown',
+ 'GitHubWeb': 'Parse like GitHub\'s Markdown-enabled web input fields',
+ },
+ );
+ final results = parser.parse(args);
+
+ if (results['help'] as bool) {
+ printUsage(parser);
+ return;
+ }
+
+ if (results['version'] as bool) {
+ print(version);
+ return;
+ }
+
+ final extensionSet = extensionSets[results['extension-set']];
+
+ if (results.rest.length > 1) {
+ printUsage(parser);
+ exitCode = 1;
+ return;
+ }
+
+ if (results.rest.length == 1) {
+ // Read argument as a file path.
+ final input = File(results.rest.first).readAsStringSync();
+ print(markdownToHtml(input, extensionSet: extensionSet));
+ return;
+ }
+
+ // Read from stdin.
+ final buffer = StringBuffer();
+ String? line;
+ while ((line = stdin.readLineSync()) != null) {
+ buffer.writeln(line);
+ }
+ print(markdownToHtml(buffer.toString(), extensionSet: extensionSet));
+}
+
+void printUsage(ArgParser parser) {
+ print('''Usage: markdown.dart [options] [file]
+
+Parse [file] as Markdown and print resulting HTML. If [file] is omitted,
+use stdin as input.
+
+By default, CommonMark Markdown will be parsed. This can be changed with
+the --extensionSet flag.
+
+${parser.usage}
+''');
+}
diff --git a/pkgs/markdown/build.yaml b/pkgs/markdown/build.yaml
new file mode 100644
index 0000000..3f26d30
--- /dev/null
+++ b/pkgs/markdown/build.yaml
@@ -0,0 +1,16 @@
+# See https://github.com/dart-lang/build/tree/master/build_web_compilers#configuration
+targets:
+ $default:
+ builders:
+ build_web_compilers|entrypoint:
+ generate_for:
+ - example/**.dart
+ options:
+ compilers:
+ dart2wasm:
+ args:
+ - -O4
+ - --no-strip-wasm
+ dart2js:
+ args:
+ - -O4
diff --git a/pkgs/markdown/dart_test.yaml b/pkgs/markdown/dart_test.yaml
new file mode 100644
index 0000000..588b409
--- /dev/null
+++ b/pkgs/markdown/dart_test.yaml
@@ -0,0 +1,6 @@
+tags:
+ crash_test:
+ skip: 'Only run crash_test tests manually with `dart test -P crash_test`'
+ presets:
+ crash_test:
+ skip: false # Don't skip when running in -P crash_test
diff --git a/pkgs/markdown/example/README.md b/pkgs/markdown/example/README.md
new file mode 100644
index 0000000..b5633aa
--- /dev/null
+++ b/pkgs/markdown/example/README.md
@@ -0,0 +1,13 @@
+Commands must be executed from the `/markdown` directory.
+
+Run locally with JavaScript development compiler:
+
+```console
+dart run build_runner serve example
+```
+
+Build production JS and WebAssembly:
+
+```console
+dart run build_runner build -o example:build --release
+```
diff --git a/pkgs/markdown/example/app.dart b/pkgs/markdown/example/app.dart
new file mode 100644
index 0000000..14b8f19
--- /dev/null
+++ b/pkgs/markdown/example/app.dart
@@ -0,0 +1,148 @@
+// Copyright (c) 2020, 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:js_interop';
+
+import 'package:markdown/markdown.dart' as md;
+import 'package:web/web.dart';
+
+import 'highlight.dart';
+
+final markdownInput =
+ document.querySelector('#markdown') as HTMLTextAreaElement;
+final htmlDiv = document.querySelector('#html') as HTMLDivElement;
+final versionSpan = document.querySelector('.version') as HTMLSpanElement;
+
+const typing = Duration(milliseconds: 150);
+const introText = '''Markdown is the **best**!
+
+* It has lists.
+* It has [links](https://dart.dev).
+* It has...
+ ```dart
+ void sourceCode() {}
+ ```
+* ...and _so much more_...''';
+
+// Flavor support.
+final basicRadio = document.querySelector('#basic-radio') as HTMLElement;
+final commonmarkRadio =
+ document.querySelector('#commonmark-radio') as HTMLElement;
+final gfmRadio = document.querySelector('#gfm-radio') as HTMLElement;
+md.ExtensionSet? extensionSet;
+
+final extensionSets = {
+ 'basic-radio': md.ExtensionSet.none,
+ 'commonmark-radio': md.ExtensionSet.commonMark,
+ 'gfm-radio': md.ExtensionSet.gitHubWeb,
+};
+
+void main() {
+ versionSpan.text = 'v${md.version}';
+ markdownInput.onKeyUp.listen(_renderMarkdown);
+
+ final savedMarkdown = window.localStorage['markdown'];
+
+ if (savedMarkdown != null &&
+ savedMarkdown.isNotEmpty &&
+ savedMarkdown != introText) {
+ markdownInput.value = savedMarkdown;
+ markdownInput.focus();
+ _renderMarkdown();
+ } else {
+ _typeItOut(introText, 82);
+ }
+
+ // GitHub is the default extension set.
+ gfmRadio.attributes.getNamedItem('checked')?.value = '';
+ gfmRadio.querySelector('.glyph')!.text = 'radio_button_checked';
+ extensionSet = extensionSets[gfmRadio.id];
+ _renderMarkdown();
+
+ basicRadio.onClick.listen(_switchFlavor);
+ commonmarkRadio.onClick.listen(_switchFlavor);
+ gfmRadio.onClick.listen(_switchFlavor);
+}
+
+void _renderMarkdown([Event? event]) {
+ final markdown = markdownInput.value;
+
+ htmlDiv.innerHtml = md.markdownToHtml(markdown, extensionSet: extensionSet);
+
+ for (final block in htmlDiv.querySelectorAll('pre code').items) {
+ try {
+ highlightElement(block);
+ } catch (e) {
+ console.error('Error highlighting markdown:'.toJS);
+ console.error(e.toString().toJS);
+ }
+ }
+
+ if (event != null) {
+ // Not simulated typing. Store it.
+ window.localStorage['markdown'] = markdown;
+ }
+}
+
+void _typeItOut(String msg, int pos) {
+ late Timer timer;
+ markdownInput.onKeyUp.listen((_) {
+ timer.cancel();
+ });
+ void addCharacter() {
+ if (pos > msg.length) {
+ return;
+ }
+ markdownInput.value = msg.substring(0, pos);
+ markdownInput.focus();
+ _renderMarkdown();
+ pos++;
+ timer = Timer(typing, addCharacter);
+ }
+
+ timer = Timer(typing, addCharacter);
+}
+
+void _switchFlavor(Event e) {
+ final target = e.currentTarget as HTMLElement;
+ if (target.attributes.getNamedItem('checked') == null) {
+ if (basicRadio != target) {
+ basicRadio.attributes.safeRemove('checked');
+ basicRadio.querySelector('.glyph')!.text = 'radio_button_unchecked';
+ }
+ if (commonmarkRadio != target) {
+ commonmarkRadio.attributes.safeRemove('checked');
+ commonmarkRadio.querySelector('.glyph')!.text = 'radio_button_unchecked';
+ }
+ if (gfmRadio != target) {
+ gfmRadio.attributes.safeRemove('checked');
+ gfmRadio.querySelector('.glyph')!.text = 'radio_button_unchecked';
+ }
+
+ target.attributes.getNamedItem('checked')?.value = '';
+ target.querySelector('.glyph')!.text = 'radio_button_checked';
+ extensionSet = extensionSets[target.id];
+ _renderMarkdown();
+ }
+}
+
+extension on NodeList {
+ List<Node> get items => [
+ for (var i = 0; i < length; i++) item(i)!,
+ ];
+}
+
+extension on NamedNodeMap {
+ void safeRemove(String qualifiedName) {
+ if (getNamedItem(qualifiedName) != null) removeNamedItem(qualifiedName);
+ }
+}
+
+extension on HTMLDivElement {
+ // The default implementation allows `JSAny` to support trusted types. We only
+ // use `String`s, so prefer this to avoid manual conversions.
+ @JS('innerHTML')
+ external set innerHtml(String value);
+}
diff --git a/pkgs/markdown/example/favicon.png b/pkgs/markdown/example/favicon.png
new file mode 100644
index 0000000..43d2ffa
--- /dev/null
+++ b/pkgs/markdown/example/favicon.png
Binary files differ
diff --git a/pkgs/markdown/example/highlight.dart b/pkgs/markdown/example/highlight.dart
new file mode 100644
index 0000000..b54c447
--- /dev/null
+++ b/pkgs/markdown/example/highlight.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2017, 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.
+
+@JS('hljs')
+library;
+
+import 'dart:js_interop';
+
+import 'package:web/web.dart';
+
+@JS()
+external void highlightElement(Node block);
diff --git a/pkgs/markdown/example/index.html b/pkgs/markdown/example/index.html
new file mode 100644
index 0000000..7dbeae2
--- /dev/null
+++ b/pkgs/markdown/example/index.html
@@ -0,0 +1,67 @@
+<html>
+ <head>
+ <link rel="icon" sizes="64x64" href="favicon.png">
+ <link rel="stylesheet" href="style.css">
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto+Mono|Roboto">
+ <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons+Extended">
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/default.min.css" integrity="sha512-hasIneQUHlh06VNBe7f6ZcHmeRTLIaQWFd43YriJ0UND19bvYRauxthDg8E4eVNPm9bRUhr5JGeqH7FRFXQu5g==" crossorigin="anonymous" referrerpolicy="no-referrer" />
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.8.1/github-markdown-light.min.css" integrity="sha512-X175XRJAO6PHAUi8AA7GP8uUF5Wiv+w9bOi64i02CHKDQBsO1yy0jLSKaUKg/NhRCDYBmOLQCfKaTaXiyZlLrw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js" integrity="sha512-EBLzUL8XLl+va/zAsmXwS7Z2B1F9HUHkZwyS/VKwh3S7T/U0nF4BaU29EP/ZSf6zgiIxYAnKLu6bJ8dqpmX5uw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/dart.min.js" integrity="sha512-4NIdIujw2gXoLU7x+versij0q7JzrtjDETxeDaBIb2gM7EctoudrUUtWm+aTx9ODExJTTCLzq06Gjs642x/DwA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
+ <script defer src="app.dart.js"></script>
+ <title>Dart Markdown Live Editor</title>
+ </head>
+ <body>
+ <div class="container">
+ <header>
+ <div class="toolbar">
+ <h2>Dart Markdown Live Editor</h2>
+ <span style="display: flex; flex: 1;"></span>
+ <span class="version"></span>
+ <a href="https://github.com/dart-lang/tools/tree/main/pkgs/markdown" title="Open Source on GitHub">
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="32" height="32">
+ <path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
+ </svg>
+ </a>
+ </div>
+ </header>
+ <div class="main">
+ <div class="card">
+ <div class="toolbar">
+ <h2>Markdown</h2>
+ </div>
+ <div style="padding: 8px; display: flex; height: 100%;">
+ <textarea id="markdown"></textarea>
+ </div>
+ </div>
+ <div class="card">
+ <div class="toolbar">
+ <h2>HTML</h2>
+ </div>
+ <div id="html" class="markdown-body"></div>
+ </div>
+ </div>
+ <footer>
+ <span style="margin-right: 16px;">Markdown Flavor:</span>
+ <div class="radio" id="basic-radio">
+ <i class="glyph">radio_button_unchecked</i>
+ Basic Markdown
+ </div>
+ <div class="radio" id="commonmark-radio">
+ <i class="glyph">radio_button_unchecked</i>
+ CommonMark
+ </div>
+ <div class="radio" id="gfm-radio">
+ <i class="glyph">radio_button_unchecked</i>
+ GitHub Flavored Markdown
+ </div>
+ <span style="display: flex; flex: 1;"></span>
+ <a href="https://github.com/dart-lang/tools/tree/main/pkgs/markdown#extension-sets"
+ target="_blank"
+ title="More info">
+ <i class="big glyph">help</i>
+ </a>
+ </footer>
+ </div>
+ </body>
+</html>
diff --git a/pkgs/markdown/example/style.css b/pkgs/markdown/example/style.css
new file mode 100644
index 0000000..bfacfb0
--- /dev/null
+++ b/pkgs/markdown/example/style.css
@@ -0,0 +1,152 @@
+html, body {
+ background-color: rgb(250, 250, 250);
+ font-family: Roboto,"Helvetica Neue",sans-serif;
+ font-size: 16px;
+ margin: 0;
+}
+
+.container {
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ width: 100%;
+}
+
+.main {
+ align-items: stretch;
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: row;
+ height: 100%;
+ margin: 32px;
+}
+
+.version {
+ color: rgba(0, 0, 0, 0.54);
+ margin-right: 16px;
+}
+
+.card {
+ /* Styled like Material Cards. */
+ color: rgba(0, 0, 0, 0.87);
+ background-color: white;
+ border-radius: 2px;
+ box-sizing: border-box;
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .2),
+ 0 1px 1px 0 rgba(0, 0, 0, .14),
+ 0 2px 1px -1px rgba(0, 0, 0, .12);
+ margin: 8px;
+
+ /* Positioned next to each other. */
+ display: flex;
+ flex: 1 1 50%;
+ flex-direction: column;
+ max-height: 100%;
+ max-width: 50%;
+ min-height: 400px;
+}
+
+.toolbar {
+ /* Styled like Material Toolbar. */
+ align-items: center;
+ background-color: #22d3c5;
+ box-sizing: border-box;
+ color: rgba(0, 0, 0, 0.87);
+ display: flex;
+ font-size: 20px;
+ line-height: 1.4;
+ margin: 0;
+ min-height: 64px;
+ padding: 0 16px;
+ position: relative;
+ width: 100%;
+}
+
+.toolbar h2 {
+ font-size: inherit;
+ font-weight: inherit;
+ margin: inherit;
+}
+
+.toolbar a:link,
+.toolbar a:visited {
+ color: rgba(0, 0, 0, 0.87);
+}
+
+.toolbar svg {
+ fill: currentColor;
+}
+
+.card .toolbar {
+ border-radius: 3px 3px 0 0;
+}
+
+.textarea-container {
+ display: flex;
+ height: 100%;
+ padding: 8px;
+}
+
+textarea {
+ border-color: rgba(0, 0, 0, 0.12);
+ border-width: 0 0 1px;
+ box-sizing: border-box;
+ color: rgba(0, 0, 0, 0.87);
+ font-family: "Roboto Mono",monospace;
+ font-size: 100%;
+ line-height: 26px;
+ overflow: auto;
+ padding: 2px 2px 1px;
+ width: 100%;
+}
+
+textarea:focus {
+ border-color: #22d3c5;
+ border-width: 0 0 2px;
+ outline: 0;
+ padding-bottom: 0;
+}
+
+#html {
+ overflow: auto;
+ padding: 8px;
+}
+
+footer {
+ box-sizing: border-box;
+ display: flex;
+ font-size: 20px;
+ height: 64px;
+ padding: 0 16px;
+}
+
+footer a:link,
+footer a:visited {
+ color: rgba(0, 0, 0, 0.87);
+}
+
+.radio {
+ cursor: pointer;
+ display: inline-block;
+ margin-right: 16px;
+}
+
+i.glyph {
+ display: inline-block;
+ font-family: 'Material Icons Extended';
+ font-size: 16px;
+ font-style: normal;
+ font-weight: normal;
+ line-height: 1;
+}
+
+.radio[checked] i.glyph {
+ color: #22d3c5;
+}
+
+i.glyph.big {
+ font-size: 32px;
+ height: 32px;
+ width: 32px;
+}
diff --git a/pkgs/markdown/lib/markdown.dart b/pkgs/markdown/lib/markdown.dart
new file mode 100644
index 0000000..9fac732
--- /dev/null
+++ b/pkgs/markdown/lib/markdown.dart
@@ -0,0 +1,94 @@
+// 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.
+
+/// Parses text in a Markdown-like format building an
+/// [AST tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree)
+/// that can then be rendered to HTML.
+///
+/// If you are only interested in rendering Markdown to HTML please refer
+/// to the [README](../index.html) which explains the use of [markdownToHtml].
+///
+/// The main entrypoint to the library is the [Document] which
+/// encapsulates the parsing process converting a Markdown text into
+/// a tree of [Node] (`List<Node>`).
+///
+/// The two main parsing mechanics used are:
+///
+/// - Blocks, representing top-level elements
+/// implemented via [BlockSyntax] subclasses,
+/// such as headers, paragraphs, blockquotes, and code blocks.
+/// - Inlines, representing chunks of text within a block with special meaning,
+/// implemented via [InlineSyntax] subclasses,
+/// such as links, emphasis, and inlined code.
+///
+/// Looking closely at [Document.new] a few other concepts merit a mention:
+///
+/// - [ExtensionSet] that provide configurations for common Markdown flavors
+/// - [Resolver] which aid in resolving links and images
+///
+/// If you are looking at extending the library to support custom formatting
+/// what you might want is to:
+///
+/// - Implement your own [InlineSyntax] subclasses
+/// - Implement your own [BlockSyntax] subclasses
+/// - Instruct the library to use those by:
+/// - Creating a new [ExtensionSet] from one of the existing flavors
+/// and adding your syntaxes.
+/// - Passing your syntaxes to [Document] or [markdownToHtml] as parameters.
+library;
+
+import 'src/version.dart';
+
+export 'src/ast.dart';
+export 'src/block_parser.dart';
+export 'src/block_syntaxes/alert_block_syntax.dart';
+export 'src/block_syntaxes/block_syntax.dart';
+export 'src/block_syntaxes/blockquote_syntax.dart';
+export 'src/block_syntaxes/code_block_syntax.dart';
+export 'src/block_syntaxes/dummy_block_syntax.dart';
+export 'src/block_syntaxes/empty_block_syntax.dart';
+export 'src/block_syntaxes/fenced_blockquote_syntax.dart';
+export 'src/block_syntaxes/fenced_code_block_syntax.dart';
+export 'src/block_syntaxes/footnote_def_syntax.dart';
+export 'src/block_syntaxes/header_syntax.dart';
+export 'src/block_syntaxes/header_with_id_syntax.dart';
+export 'src/block_syntaxes/horizontal_rule_syntax.dart';
+export 'src/block_syntaxes/html_block_syntax.dart';
+export 'src/block_syntaxes/link_reference_definition_syntax.dart';
+export 'src/block_syntaxes/list_syntax.dart';
+export 'src/block_syntaxes/ordered_list_syntax.dart';
+export 'src/block_syntaxes/ordered_list_with_checkbox_syntax.dart';
+export 'src/block_syntaxes/paragraph_syntax.dart';
+export 'src/block_syntaxes/setext_header_syntax.dart';
+export 'src/block_syntaxes/setext_header_with_id_syntax.dart';
+export 'src/block_syntaxes/table_syntax.dart';
+export 'src/block_syntaxes/unordered_list_syntax.dart';
+export 'src/block_syntaxes/unordered_list_with_checkbox_syntax.dart';
+export 'src/document.dart';
+export 'src/emojis.dart';
+export 'src/extension_set.dart';
+export 'src/html_renderer.dart';
+export 'src/inline_parser.dart';
+export 'src/inline_syntaxes/autolink_extension_syntax.dart';
+export 'src/inline_syntaxes/autolink_syntax.dart';
+export 'src/inline_syntaxes/code_syntax.dart';
+export 'src/inline_syntaxes/color_swatch_syntax.dart';
+export 'src/inline_syntaxes/decode_html_syntax.dart';
+export 'src/inline_syntaxes/delimiter_syntax.dart';
+export 'src/inline_syntaxes/email_autolink_syntax.dart';
+export 'src/inline_syntaxes/emoji_syntax.dart';
+export 'src/inline_syntaxes/emphasis_syntax.dart';
+export 'src/inline_syntaxes/escape_html_syntax.dart';
+export 'src/inline_syntaxes/escape_syntax.dart';
+export 'src/inline_syntaxes/image_syntax.dart';
+export 'src/inline_syntaxes/inline_html_syntax.dart';
+export 'src/inline_syntaxes/inline_syntax.dart';
+export 'src/inline_syntaxes/line_break_syntax.dart';
+export 'src/inline_syntaxes/link_syntax.dart';
+export 'src/inline_syntaxes/soft_line_break_syntax.dart';
+export 'src/inline_syntaxes/strikethrough_syntax.dart';
+export 'src/inline_syntaxes/text_syntax.dart';
+export 'src/line.dart';
+
+const version = packageVersion;
diff --git a/pkgs/markdown/lib/src/assets/case_folding.dart b/pkgs/markdown/lib/src/assets/case_folding.dart
new file mode 100644
index 0000000..09081aa
--- /dev/null
+++ b/pkgs/markdown/lib/src/assets/case_folding.dart
@@ -0,0 +1,1316 @@
+// Generated file. do not edit.
+//
+// Source: tool/case_folding.txt
+// Script: tool/update_case_folding.dart
+// ignore_for_file: prefer_single_quotes
+
+const caseFoldingMap = {
+ "A": "a",
+ "B": "b",
+ "C": "c",
+ "D": "d",
+ "E": "e",
+ "F": "f",
+ "G": "g",
+ "H": "h",
+ "I": "i",
+ "J": "j",
+ "K": "k",
+ "L": "l",
+ "M": "m",
+ "N": "n",
+ "O": "o",
+ "P": "p",
+ "Q": "q",
+ "R": "r",
+ "S": "s",
+ "T": "t",
+ "U": "u",
+ "V": "v",
+ "W": "w",
+ "X": "x",
+ "Y": "y",
+ "Z": "z",
+ "À": "à",
+ "Á": "á",
+ "Â": "â",
+ "Ã": "ã",
+ "Ä": "ä",
+ "Å": "å",
+ "Æ": "æ",
+ "Ç": "ç",
+ "È": "è",
+ "É": "é",
+ "Ê": "ê",
+ "Ë": "ë",
+ "Ì": "ì",
+ "Í": "í",
+ "Î": "î",
+ "Ï": "ï",
+ "Ð": "ð",
+ "Ñ": "ñ",
+ "Ò": "ò",
+ "Ó": "ó",
+ "Ô": "ô",
+ "Õ": "õ",
+ "Ö": "ö",
+ "Ø": "ø",
+ "Ù": "ù",
+ "Ú": "ú",
+ "Û": "û",
+ "Ü": "ü",
+ "Ý": "ý",
+ "Þ": "þ",
+ "Ā": "ā",
+ "Ă": "ă",
+ "Ą": "ą",
+ "Ć": "ć",
+ "Ĉ": "ĉ",
+ "Ċ": "ċ",
+ "Č": "č",
+ "Ď": "ď",
+ "Đ": "đ",
+ "Ē": "ē",
+ "Ĕ": "ĕ",
+ "Ė": "ė",
+ "Ę": "ę",
+ "Ě": "ě",
+ "Ĝ": "ĝ",
+ "Ğ": "ğ",
+ "Ġ": "ġ",
+ "Ģ": "ģ",
+ "Ĥ": "ĥ",
+ "Ħ": "ħ",
+ "Ĩ": "ĩ",
+ "Ī": "ī",
+ "Ĭ": "ĭ",
+ "Į": "į",
+ "İ": "i̇",
+ "Ĵ": "ĵ",
+ "Ķ": "ķ",
+ "Ĺ": "ĺ",
+ "Ļ": "ļ",
+ "Ľ": "ľ",
+ "Ŀ": "ŀ",
+ "Ł": "ł",
+ "Ń": "ń",
+ "Ņ": "ņ",
+ "Ň": "ň",
+ "Ŋ": "ŋ",
+ "Ō": "ō",
+ "Ŏ": "ŏ",
+ "Ő": "ő",
+ "Ŕ": "ŕ",
+ "Ŗ": "ŗ",
+ "Ř": "ř",
+ "Ś": "ś",
+ "Ŝ": "ŝ",
+ "Ş": "ş",
+ "Š": "š",
+ "Ţ": "ţ",
+ "Ť": "ť",
+ "Ŧ": "ŧ",
+ "Ũ": "ũ",
+ "Ū": "ū",
+ "Ŭ": "ŭ",
+ "Ů": "ů",
+ "Ű": "ű",
+ "Ų": "ų",
+ "Ŵ": "ŵ",
+ "Ŷ": "ŷ",
+ "Ÿ": "ÿ",
+ "Ź": "ź",
+ "Ż": "ż",
+ "Ž": "ž",
+ "Ɓ": "ɓ",
+ "Ƃ": "ƃ",
+ "Ƅ": "ƅ",
+ "Ɔ": "ɔ",
+ "Ƈ": "ƈ",
+ "Ɖ": "ɖ",
+ "Ɗ": "ɗ",
+ "Ƌ": "ƌ",
+ "Ǝ": "ǝ",
+ "Ə": "ə",
+ "Ɛ": "ɛ",
+ "Ƒ": "ƒ",
+ "Ɠ": "ɠ",
+ "Ɣ": "ɣ",
+ "Ɩ": "ɩ",
+ "Ɨ": "ɨ",
+ "Ƙ": "ƙ",
+ "Ɯ": "ɯ",
+ "Ɲ": "ɲ",
+ "Ɵ": "ɵ",
+ "Ơ": "ơ",
+ "Ƣ": "ƣ",
+ "Ƥ": "ƥ",
+ "Ƨ": "ƨ",
+ "Ʃ": "ʃ",
+ "Ƭ": "ƭ",
+ "Ʈ": "ʈ",
+ "Ư": "ư",
+ "Ʊ": "ʊ",
+ "Ʋ": "ʋ",
+ "Ƴ": "ƴ",
+ "Ƶ": "ƶ",
+ "Ʒ": "ʒ",
+ "Ƹ": "ƹ",
+ "Ƽ": "ƽ",
+ "DŽ": "dž",
+ "Dž": "dž",
+ "LJ": "lj",
+ "Lj": "lj",
+ "NJ": "nj",
+ "Nj": "nj",
+ "Ǎ": "ǎ",
+ "Ǐ": "ǐ",
+ "Ǒ": "ǒ",
+ "Ǔ": "ǔ",
+ "Ǖ": "ǖ",
+ "Ǘ": "ǘ",
+ "Ǚ": "ǚ",
+ "Ǜ": "ǜ",
+ "Ǟ": "ǟ",
+ "Ǡ": "ǡ",
+ "Ǣ": "ǣ",
+ "Ǥ": "ǥ",
+ "Ǧ": "ǧ",
+ "Ǩ": "ǩ",
+ "Ǫ": "ǫ",
+ "Ǭ": "ǭ",
+ "Ǯ": "ǯ",
+ "DZ": "dz",
+ "Dz": "dz",
+ "Ǵ": "ǵ",
+ "Ƕ": "ƕ",
+ "Ƿ": "ƿ",
+ "Ǹ": "ǹ",
+ "Ǻ": "ǻ",
+ "Ǽ": "ǽ",
+ "Ǿ": "ǿ",
+ "Ȁ": "ȁ",
+ "Ȃ": "ȃ",
+ "Ȅ": "ȅ",
+ "Ȇ": "ȇ",
+ "Ȉ": "ȉ",
+ "Ȋ": "ȋ",
+ "Ȍ": "ȍ",
+ "Ȏ": "ȏ",
+ "Ȑ": "ȑ",
+ "Ȓ": "ȓ",
+ "Ȕ": "ȕ",
+ "Ȗ": "ȗ",
+ "Ș": "ș",
+ "Ț": "ț",
+ "Ȝ": "ȝ",
+ "Ȟ": "ȟ",
+ "Ƞ": "ƞ",
+ "Ȣ": "ȣ",
+ "Ȥ": "ȥ",
+ "Ȧ": "ȧ",
+ "Ȩ": "ȩ",
+ "Ȫ": "ȫ",
+ "Ȭ": "ȭ",
+ "Ȯ": "ȯ",
+ "Ȱ": "ȱ",
+ "Ȳ": "ȳ",
+ "Ⱥ": "ⱥ",
+ "Ȼ": "ȼ",
+ "Ƚ": "ƚ",
+ "Ⱦ": "ⱦ",
+ "Ɂ": "ɂ",
+ "Ƀ": "ƀ",
+ "Ʉ": "ʉ",
+ "Ʌ": "ʌ",
+ "Ɇ": "ɇ",
+ "Ɉ": "ɉ",
+ "Ɋ": "ɋ",
+ "Ɍ": "ɍ",
+ "Ɏ": "ɏ",
+ "Ͱ": "ͱ",
+ "Ͳ": "ͳ",
+ "Ͷ": "ͷ",
+ "Ϳ": "ϳ",
+ "Ά": "ά",
+ "Έ": "έ",
+ "Ή": "ή",
+ "Ί": "ί",
+ "Ό": "ό",
+ "Ύ": "ύ",
+ "Ώ": "ώ",
+ "Α": "α",
+ "Β": "β",
+ "Γ": "γ",
+ "Δ": "δ",
+ "Ε": "ε",
+ "Ζ": "ζ",
+ "Η": "η",
+ "Θ": "θ",
+ "Ι": "ι",
+ "Κ": "κ",
+ "Λ": "λ",
+ "Μ": "μ",
+ "Ν": "ν",
+ "Ξ": "ξ",
+ "Ο": "ο",
+ "Π": "π",
+ "Ρ": "ρ",
+ "Σ": "σ",
+ "Τ": "τ",
+ "Υ": "υ",
+ "Φ": "φ",
+ "Χ": "χ",
+ "Ψ": "ψ",
+ "Ω": "ω",
+ "Ϊ": "ϊ",
+ "Ϋ": "ϋ",
+ "Ϣ": "ϣ",
+ "Ϥ": "ϥ",
+ "Ϧ": "ϧ",
+ "Ϩ": "ϩ",
+ "Ϫ": "ϫ",
+ "Ϭ": "ϭ",
+ "Ϯ": "ϯ",
+ "Ϸ": "ϸ",
+ "Ϻ": "ϻ",
+ "Ѐ": "ѐ",
+ "Ё": "ё",
+ "Ђ": "ђ",
+ "Ѓ": "ѓ",
+ "Є": "є",
+ "Ѕ": "ѕ",
+ "І": "і",
+ "Ї": "ї",
+ "Ј": "ј",
+ "Љ": "љ",
+ "Њ": "њ",
+ "Ћ": "ћ",
+ "Ќ": "ќ",
+ "Ѝ": "ѝ",
+ "Ў": "ў",
+ "Џ": "џ",
+ "А": "а",
+ "Б": "б",
+ "В": "в",
+ "Г": "г",
+ "Д": "д",
+ "Е": "е",
+ "Ж": "ж",
+ "З": "з",
+ "И": "и",
+ "Й": "й",
+ "К": "к",
+ "Л": "л",
+ "М": "м",
+ "Н": "н",
+ "О": "о",
+ "П": "п",
+ "Р": "р",
+ "С": "с",
+ "Т": "т",
+ "У": "у",
+ "Ф": "ф",
+ "Х": "х",
+ "Ц": "ц",
+ "Ч": "ч",
+ "Ш": "ш",
+ "Щ": "щ",
+ "Ъ": "ъ",
+ "Ы": "ы",
+ "Ь": "ь",
+ "Э": "э",
+ "Ю": "ю",
+ "Я": "я",
+ "Ѡ": "ѡ",
+ "Ѣ": "ѣ",
+ "Ѥ": "ѥ",
+ "Ѧ": "ѧ",
+ "Ѩ": "ѩ",
+ "Ѫ": "ѫ",
+ "Ѭ": "ѭ",
+ "Ѯ": "ѯ",
+ "Ѱ": "ѱ",
+ "Ѳ": "ѳ",
+ "Ѵ": "ѵ",
+ "Ѷ": "ѷ",
+ "Ѹ": "ѹ",
+ "Ѻ": "ѻ",
+ "Ѽ": "ѽ",
+ "Ѿ": "ѿ",
+ "Ҁ": "ҁ",
+ "Ҋ": "ҋ",
+ "Ҍ": "ҍ",
+ "Ҏ": "ҏ",
+ "Ґ": "ґ",
+ "Ғ": "ғ",
+ "Ҕ": "ҕ",
+ "Җ": "җ",
+ "Ҙ": "ҙ",
+ "Қ": "қ",
+ "Ҝ": "ҝ",
+ "Ҟ": "ҟ",
+ "Ҡ": "ҡ",
+ "Ң": "ң",
+ "Ҧ": "ҧ",
+ "Ҩ": "ҩ",
+ "Ҫ": "ҫ",
+ "Ҭ": "ҭ",
+ "Ү": "ү",
+ "Ұ": "ұ",
+ "Ҳ": "ҳ",
+ "Ҷ": "ҷ",
+ "Ҹ": "ҹ",
+ "Һ": "һ",
+ "Ҽ": "ҽ",
+ "Ҿ": "ҿ",
+ "Ӂ": "ӂ",
+ "Ӄ": "ӄ",
+ "Ӆ": "ӆ",
+ "Ӈ": "ӈ",
+ "Ӊ": "ӊ",
+ "Ӌ": "ӌ",
+ "Ӎ": "ӎ",
+ "Ӑ": "ӑ",
+ "Ӓ": "ӓ",
+ "Ӗ": "ӗ",
+ "Ә": "ә",
+ "Ӛ": "ӛ",
+ "Ӝ": "ӝ",
+ "Ӟ": "ӟ",
+ "Ӡ": "ӡ",
+ "Ӣ": "ӣ",
+ "Ӥ": "ӥ",
+ "Ӧ": "ӧ",
+ "Ө": "ө",
+ "Ӫ": "ӫ",
+ "Ӭ": "ӭ",
+ "Ӯ": "ӯ",
+ "Ӱ": "ӱ",
+ "Ӳ": "ӳ",
+ "Ӵ": "ӵ",
+ "Ӷ": "ӷ",
+ "Ӹ": "ӹ",
+ "Ӻ": "ӻ",
+ "Ӽ": "ӽ",
+ "Ӿ": "ӿ",
+ "Ԁ": "ԁ",
+ "Ԃ": "ԃ",
+ "Ԅ": "ԅ",
+ "Ԇ": "ԇ",
+ "Ԉ": "ԉ",
+ "Ԋ": "ԋ",
+ "Ԍ": "ԍ",
+ "Ԏ": "ԏ",
+ "Ԑ": "ԑ",
+ "Ԓ": "ԓ",
+ "Ԕ": "ԕ",
+ "Ԗ": "ԗ",
+ "Ԙ": "ԙ",
+ "Ԛ": "ԛ",
+ "Ԝ": "ԝ",
+ "Ԟ": "ԟ",
+ "Ԡ": "ԡ",
+ "Ԣ": "ԣ",
+ "Ԥ": "ԥ",
+ "Ԧ": "ԧ",
+ "Ԩ": "ԩ",
+ "Ԫ": "ԫ",
+ "Ԭ": "ԭ",
+ "Ԯ": "ԯ",
+ "Ա": "ա",
+ "Բ": "բ",
+ "Գ": "գ",
+ "Դ": "դ",
+ "Ե": "ե",
+ "Զ": "զ",
+ "Է": "է",
+ "Ը": "ը",
+ "Թ": "թ",
+ "Ժ": "ժ",
+ "Ի": "ի",
+ "Լ": "լ",
+ "Խ": "խ",
+ "Ծ": "ծ",
+ "Կ": "կ",
+ "Հ": "հ",
+ "Ձ": "ձ",
+ "Ղ": "ղ",
+ "Ճ": "ճ",
+ "Մ": "մ",
+ "Յ": "յ",
+ "Ն": "ն",
+ "Շ": "շ",
+ "Ո": "ո",
+ "Չ": "չ",
+ "Պ": "պ",
+ "Ջ": "ջ",
+ "Ռ": "ռ",
+ "Ս": "ս",
+ "Վ": "վ",
+ "Տ": "տ",
+ "Ր": "ր",
+ "Ց": "ց",
+ "Ւ": "ւ",
+ "Փ": "փ",
+ "Ք": "ք",
+ "Օ": "օ",
+ "Ֆ": "ֆ",
+ "Ⴀ": "ⴀ",
+ "Ⴁ": "ⴁ",
+ "Ⴂ": "ⴂ",
+ "Ⴃ": "ⴃ",
+ "Ⴄ": "ⴄ",
+ "Ⴅ": "ⴅ",
+ "Ⴆ": "ⴆ",
+ "Ⴇ": "ⴇ",
+ "Ⴈ": "ⴈ",
+ "Ⴉ": "ⴉ",
+ "Ⴊ": "ⴊ",
+ "Ⴋ": "ⴋ",
+ "Ⴌ": "ⴌ",
+ "Ⴍ": "ⴍ",
+ "Ⴎ": "ⴎ",
+ "Ⴏ": "ⴏ",
+ "Ⴐ": "ⴐ",
+ "Ⴑ": "ⴑ",
+ "Ⴒ": "ⴒ",
+ "Ⴓ": "ⴓ",
+ "Ⴔ": "ⴔ",
+ "Ⴕ": "ⴕ",
+ "Ⴖ": "ⴖ",
+ "Ⴗ": "ⴗ",
+ "Ⴘ": "ⴘ",
+ "Ⴙ": "ⴙ",
+ "Ⴚ": "ⴚ",
+ "Ⴛ": "ⴛ",
+ "Ⴜ": "ⴜ",
+ "Ⴝ": "ⴝ",
+ "Ⴞ": "ⴞ",
+ "Ⴟ": "ⴟ",
+ "Ⴠ": "ⴠ",
+ "Ⴡ": "ⴡ",
+ "Ⴢ": "ⴢ",
+ "Ⴣ": "ⴣ",
+ "Ⴤ": "ⴤ",
+ "Ⴥ": "ⴥ",
+ "Ⴧ": "ⴧ",
+ "Ⴭ": "ⴭ",
+ "Ა": "ა",
+ "Ბ": "ბ",
+ "Გ": "გ",
+ "Დ": "დ",
+ "Ე": "ე",
+ "Ვ": "ვ",
+ "Ზ": "ზ",
+ "Თ": "თ",
+ "Ი": "ი",
+ "Კ": "კ",
+ "Ლ": "ლ",
+ "Მ": "მ",
+ "Ნ": "ნ",
+ "Ო": "ო",
+ "Პ": "პ",
+ "Ჟ": "ჟ",
+ "Რ": "რ",
+ "Ს": "ს",
+ "Ტ": "ტ",
+ "Უ": "უ",
+ "Ფ": "ფ",
+ "Ქ": "ქ",
+ "Ღ": "ღ",
+ "Ყ": "ყ",
+ "Შ": "შ",
+ "Ჩ": "ჩ",
+ "Ც": "ც",
+ "Ძ": "ძ",
+ "Წ": "წ",
+ "Ჭ": "ჭ",
+ "Ხ": "ხ",
+ "Ჯ": "ჯ",
+ "Ჰ": "ჰ",
+ "Ჱ": "ჱ",
+ "Ჲ": "ჲ",
+ "Ჳ": "ჳ",
+ "Ჴ": "ჴ",
+ "Ჵ": "ჵ",
+ "Ჶ": "ჶ",
+ "Ჷ": "ჷ",
+ "Ჸ": "ჸ",
+ "Ჹ": "ჹ",
+ "Ჺ": "ჺ",
+ "Ჽ": "ჽ",
+ "Ჾ": "ჾ",
+ "Ჿ": "ჿ",
+ "Ḁ": "ḁ",
+ "Ḃ": "ḃ",
+ "Ḅ": "ḅ",
+ "Ḇ": "ḇ",
+ "Ḉ": "ḉ",
+ "Ḋ": "ḋ",
+ "Ḍ": "ḍ",
+ "Ḏ": "ḏ",
+ "Ḑ": "ḑ",
+ "Ḓ": "ḓ",
+ "Ḕ": "ḕ",
+ "Ḗ": "ḗ",
+ "Ḙ": "ḙ",
+ "Ḛ": "ḛ",
+ "Ḝ": "ḝ",
+ "Ḟ": "ḟ",
+ "Ḡ": "ḡ",
+ "Ḣ": "ḣ",
+ "Ḥ": "ḥ",
+ "Ḧ": "ḧ",
+ "Ḩ": "ḩ",
+ "Ḫ": "ḫ",
+ "Ḭ": "ḭ",
+ "Ḯ": "ḯ",
+ "Ḱ": "ḱ",
+ "Ḳ": "ḳ",
+ "Ḵ": "ḵ",
+ "Ḷ": "ḷ",
+ "Ḹ": "ḹ",
+ "Ḻ": "ḻ",
+ "Ḽ": "ḽ",
+ "Ḿ": "ḿ",
+ "Ṁ": "ṁ",
+ "Ṃ": "ṃ",
+ "Ṅ": "ṅ",
+ "Ṇ": "ṇ",
+ "Ṉ": "ṉ",
+ "Ṋ": "ṋ",
+ "Ṍ": "ṍ",
+ "Ṏ": "ṏ",
+ "Ṑ": "ṑ",
+ "Ṓ": "ṓ",
+ "Ṕ": "ṕ",
+ "Ṗ": "ṗ",
+ "Ṙ": "ṙ",
+ "Ṛ": "ṛ",
+ "Ṝ": "ṝ",
+ "Ṟ": "ṟ",
+ "Ṡ": "ṡ",
+ "Ṣ": "ṣ",
+ "Ṥ": "ṥ",
+ "Ṧ": "ṧ",
+ "Ṩ": "ṩ",
+ "Ṫ": "ṫ",
+ "Ṭ": "ṭ",
+ "Ṯ": "ṯ",
+ "Ṱ": "ṱ",
+ "Ṳ": "ṳ",
+ "Ṵ": "ṵ",
+ "Ṷ": "ṷ",
+ "Ṹ": "ṹ",
+ "Ṻ": "ṻ",
+ "Ṽ": "ṽ",
+ "Ṿ": "ṿ",
+ "Ẁ": "ẁ",
+ "Ẃ": "ẃ",
+ "Ẅ": "ẅ",
+ "Ẇ": "ẇ",
+ "Ẉ": "ẉ",
+ "Ẋ": "ẋ",
+ "Ẍ": "ẍ",
+ "Ẏ": "ẏ",
+ "Ẑ": "ẑ",
+ "Ẓ": "ẓ",
+ "Ẕ": "ẕ",
+ "ẞ": "ss",
+ "Ạ": "ạ",
+ "Ả": "ả",
+ "Ấ": "ấ",
+ "Ầ": "ầ",
+ "Ẩ": "ẩ",
+ "Ẫ": "ẫ",
+ "Ậ": "ậ",
+ "Ắ": "ắ",
+ "Ằ": "ằ",
+ "Ẳ": "ẳ",
+ "Ẵ": "ẵ",
+ "Ặ": "ặ",
+ "Ẹ": "ẹ",
+ "Ẻ": "ẻ",
+ "Ẽ": "ẽ",
+ "Ế": "ế",
+ "Ề": "ề",
+ "Ể": "ể",
+ "Ễ": "ễ",
+ "Ệ": "ệ",
+ "Ỉ": "ỉ",
+ "Ị": "ị",
+ "Ọ": "ọ",
+ "Ỏ": "ỏ",
+ "Ố": "ố",
+ "Ồ": "ồ",
+ "Ổ": "ổ",
+ "Ỗ": "ỗ",
+ "Ộ": "ộ",
+ "Ớ": "ớ",
+ "Ờ": "ờ",
+ "Ở": "ở",
+ "Ỡ": "ỡ",
+ "Ợ": "ợ",
+ "Ụ": "ụ",
+ "Ủ": "ủ",
+ "Ứ": "ứ",
+ "Ừ": "ừ",
+ "Ử": "ử",
+ "Ữ": "ữ",
+ "Ự": "ự",
+ "Ỳ": "ỳ",
+ "Ỵ": "ỵ",
+ "Ỷ": "ỷ",
+ "Ỹ": "ỹ",
+ "Ỻ": "ỻ",
+ "Ỽ": "ỽ",
+ "Ỿ": "ỿ",
+ "Ἀ": "ἀ",
+ "Ἁ": "ἁ",
+ "Ἂ": "ἂ",
+ "Ἃ": "ἃ",
+ "Ἄ": "ἄ",
+ "Ἅ": "ἅ",
+ "Ἆ": "ἆ",
+ "Ἇ": "ἇ",
+ "Ἐ": "ἐ",
+ "Ἑ": "ἑ",
+ "Ἒ": "ἒ",
+ "Ἓ": "ἓ",
+ "Ἔ": "ἔ",
+ "Ἕ": "ἕ",
+ "Ἠ": "ἠ",
+ "Ἡ": "ἡ",
+ "Ἢ": "ἢ",
+ "Ἣ": "ἣ",
+ "Ἤ": "ἤ",
+ "Ἥ": "ἥ",
+ "Ἦ": "ἦ",
+ "Ἧ": "ἧ",
+ "Ἰ": "ἰ",
+ "Ἱ": "ἱ",
+ "Ἲ": "ἲ",
+ "Ἳ": "ἳ",
+ "Ἴ": "ἴ",
+ "Ἵ": "ἵ",
+ "Ἶ": "ἶ",
+ "Ἷ": "ἷ",
+ "Ὀ": "ὀ",
+ "Ὁ": "ὁ",
+ "Ὂ": "ὂ",
+ "Ὃ": "ὃ",
+ "Ὄ": "ὄ",
+ "Ὅ": "ὅ",
+ "Ὑ": "ὑ",
+ "Ὓ": "ὓ",
+ "Ὕ": "ὕ",
+ "Ὗ": "ὗ",
+ "Ὠ": "ὠ",
+ "Ὡ": "ὡ",
+ "Ὢ": "ὢ",
+ "Ὣ": "ὣ",
+ "Ὤ": "ὤ",
+ "Ὥ": "ὥ",
+ "Ὦ": "ὦ",
+ "Ὧ": "ὧ",
+ "ᾈ": "ἀι",
+ "ᾉ": "ἁι",
+ "ᾊ": "ἂι",
+ "ᾋ": "ἃι",
+ "ᾌ": "ἄι",
+ "ᾍ": "ἅι",
+ "ᾎ": "ἆι",
+ "ᾏ": "ἇι",
+ "ᾘ": "ἠι",
+ "ᾙ": "ἡι",
+ "ᾚ": "ἢι",
+ "ᾛ": "ἣι",
+ "ᾜ": "ἤι",
+ "ᾝ": "ἥι",
+ "ᾞ": "ἦι",
+ "ᾟ": "ἧι",
+ "ᾨ": "ὠι",
+ "ᾩ": "ὡι",
+ "ᾪ": "ὢι",
+ "ᾫ": "ὣι",
+ "ᾬ": "ὤι",
+ "ᾭ": "ὥι",
+ "ᾮ": "ὦι",
+ "ᾯ": "ὧι",
+ "Ᾰ": "ᾰ",
+ "Ᾱ": "ᾱ",
+ "Ὰ": "ὰ",
+ "Ά": "ά",
+ "ᾼ": "αι",
+ "Ὲ": "ὲ",
+ "Έ": "έ",
+ "Ὴ": "ὴ",
+ "Ή": "ή",
+ "ῌ": "ηι",
+ "Ῐ": "ῐ",
+ "Ῑ": "ῑ",
+ "Ὶ": "ὶ",
+ "Ί": "ί",
+ "Ῠ": "ῠ",
+ "Ῡ": "ῡ",
+ "Ὺ": "ὺ",
+ "Ύ": "ύ",
+ "Ῥ": "ῥ",
+ "Ὸ": "ὸ",
+ "Ό": "ό",
+ "Ὼ": "ὼ",
+ "Ώ": "ώ",
+ "ῼ": "ωι",
+ "Ⓐ": "ⓐ",
+ "Ⓑ": "ⓑ",
+ "Ⓒ": "ⓒ",
+ "Ⓓ": "ⓓ",
+ "Ⓔ": "ⓔ",
+ "Ⓕ": "ⓕ",
+ "Ⓖ": "ⓖ",
+ "Ⓗ": "ⓗ",
+ "Ⓘ": "ⓘ",
+ "Ⓙ": "ⓙ",
+ "Ⓚ": "ⓚ",
+ "Ⓛ": "ⓛ",
+ "Ⓜ": "ⓜ",
+ "Ⓝ": "ⓝ",
+ "Ⓞ": "ⓞ",
+ "Ⓟ": "ⓟ",
+ "Ⓠ": "ⓠ",
+ "Ⓡ": "ⓡ",
+ "Ⓢ": "ⓢ",
+ "Ⓣ": "ⓣ",
+ "Ⓤ": "ⓤ",
+ "Ⓥ": "ⓥ",
+ "Ⓦ": "ⓦ",
+ "Ⓧ": "ⓧ",
+ "Ⓨ": "ⓨ",
+ "Ⓩ": "ⓩ",
+ "Ⰰ": "ⰰ",
+ "Ⰱ": "ⰱ",
+ "Ⰲ": "ⰲ",
+ "Ⰳ": "ⰳ",
+ "Ⰴ": "ⰴ",
+ "Ⰵ": "ⰵ",
+ "Ⰶ": "ⰶ",
+ "Ⰷ": "ⰷ",
+ "Ⰸ": "ⰸ",
+ "Ⰹ": "ⰹ",
+ "Ⰺ": "ⰺ",
+ "Ⰻ": "ⰻ",
+ "Ⰼ": "ⰼ",
+ "Ⰽ": "ⰽ",
+ "Ⰾ": "ⰾ",
+ "Ⰿ": "ⰿ",
+ "Ⱀ": "ⱀ",
+ "Ⱁ": "ⱁ",
+ "Ⱂ": "ⱂ",
+ "Ⱃ": "ⱃ",
+ "Ⱄ": "ⱄ",
+ "Ⱅ": "ⱅ",
+ "Ⱆ": "ⱆ",
+ "Ⱇ": "ⱇ",
+ "Ⱈ": "ⱈ",
+ "Ⱉ": "ⱉ",
+ "Ⱊ": "ⱊ",
+ "Ⱋ": "ⱋ",
+ "Ⱌ": "ⱌ",
+ "Ⱍ": "ⱍ",
+ "Ⱎ": "ⱎ",
+ "Ⱏ": "ⱏ",
+ "Ⱐ": "ⱐ",
+ "Ⱑ": "ⱑ",
+ "Ⱒ": "ⱒ",
+ "Ⱓ": "ⱓ",
+ "Ⱔ": "ⱔ",
+ "Ⱕ": "ⱕ",
+ "Ⱖ": "ⱖ",
+ "Ⱗ": "ⱗ",
+ "Ⱘ": "ⱘ",
+ "Ⱙ": "ⱙ",
+ "Ⱚ": "ⱚ",
+ "Ⱛ": "ⱛ",
+ "Ⱜ": "ⱜ",
+ "Ⱝ": "ⱝ",
+ "Ⱞ": "ⱞ",
+ "Ⱟ": "ⱟ",
+ "Ⱡ": "ⱡ",
+ "Ɫ": "ɫ",
+ "Ᵽ": "ᵽ",
+ "Ɽ": "ɽ",
+ "Ⱨ": "ⱨ",
+ "Ⱪ": "ⱪ",
+ "Ⱬ": "ⱬ",
+ "Ɑ": "ɑ",
+ "Ɱ": "ɱ",
+ "Ɐ": "ɐ",
+ "Ɒ": "ɒ",
+ "Ⱳ": "ⱳ",
+ "Ⱶ": "ⱶ",
+ "Ȿ": "ȿ",
+ "Ɀ": "ɀ",
+ "Ⲁ": "ⲁ",
+ "Ⲃ": "ⲃ",
+ "Ⲅ": "ⲅ",
+ "Ⲇ": "ⲇ",
+ "Ⲉ": "ⲉ",
+ "Ⲋ": "ⲋ",
+ "Ⲍ": "ⲍ",
+ "Ⲏ": "ⲏ",
+ "Ⲑ": "ⲑ",
+ "Ⲓ": "ⲓ",
+ "Ⲕ": "ⲕ",
+ "Ⲗ": "ⲗ",
+ "Ⲙ": "ⲙ",
+ "Ⲛ": "ⲛ",
+ "Ⲝ": "ⲝ",
+ "Ⲟ": "ⲟ",
+ "Ⲡ": "ⲡ",
+ "Ⲣ": "ⲣ",
+ "Ⲥ": "ⲥ",
+ "Ⲧ": "ⲧ",
+ "Ⲩ": "ⲩ",
+ "Ⲫ": "ⲫ",
+ "Ⲭ": "ⲭ",
+ "Ⲯ": "ⲯ",
+ "Ⲱ": "ⲱ",
+ "Ⲳ": "ⲳ",
+ "Ⲵ": "ⲵ",
+ "Ⲷ": "ⲷ",
+ "Ⲹ": "ⲹ",
+ "Ⲻ": "ⲻ",
+ "Ⲽ": "ⲽ",
+ "Ⲿ": "ⲿ",
+ "Ⳁ": "ⳁ",
+ "Ⳃ": "ⳃ",
+ "Ⳅ": "ⳅ",
+ "Ⳇ": "ⳇ",
+ "Ⳉ": "ⳉ",
+ "Ⳋ": "ⳋ",
+ "Ⳍ": "ⳍ",
+ "Ⳏ": "ⳏ",
+ "Ⳑ": "ⳑ",
+ "Ⳓ": "ⳓ",
+ "Ⳕ": "ⳕ",
+ "Ⳗ": "ⳗ",
+ "Ⳙ": "ⳙ",
+ "Ⳛ": "ⳛ",
+ "Ⳝ": "ⳝ",
+ "Ⳟ": "ⳟ",
+ "Ⳡ": "ⳡ",
+ "Ⳣ": "ⳣ",
+ "Ⳬ": "ⳬ",
+ "Ⳮ": "ⳮ",
+ "Ⳳ": "ⳳ",
+ "Ꙁ": "ꙁ",
+ "Ꙃ": "ꙃ",
+ "Ꙅ": "ꙅ",
+ "Ꙇ": "ꙇ",
+ "Ꙉ": "ꙉ",
+ "Ꙋ": "ꙋ",
+ "Ꙍ": "ꙍ",
+ "Ꙏ": "ꙏ",
+ "Ꙑ": "ꙑ",
+ "Ꙓ": "ꙓ",
+ "Ꙕ": "ꙕ",
+ "Ꙗ": "ꙗ",
+ "Ꙙ": "ꙙ",
+ "Ꙛ": "ꙛ",
+ "Ꙝ": "ꙝ",
+ "Ꙟ": "ꙟ",
+ "Ꙡ": "ꙡ",
+ "Ꙣ": "ꙣ",
+ "Ꙥ": "ꙥ",
+ "Ꙧ": "ꙧ",
+ "Ꙩ": "ꙩ",
+ "Ꙫ": "ꙫ",
+ "Ꙭ": "ꙭ",
+ "Ꚁ": "ꚁ",
+ "Ꚃ": "ꚃ",
+ "Ꚅ": "ꚅ",
+ "Ꚇ": "ꚇ",
+ "Ꚉ": "ꚉ",
+ "Ꚋ": "ꚋ",
+ "Ꚍ": "ꚍ",
+ "Ꚏ": "ꚏ",
+ "Ꚑ": "ꚑ",
+ "Ꚓ": "ꚓ",
+ "Ꚕ": "ꚕ",
+ "Ꚗ": "ꚗ",
+ "Ꚙ": "ꚙ",
+ "Ꚛ": "ꚛ",
+ "Ꜣ": "ꜣ",
+ "Ꜥ": "ꜥ",
+ "Ꜧ": "ꜧ",
+ "Ꜩ": "ꜩ",
+ "Ꜫ": "ꜫ",
+ "Ꜭ": "ꜭ",
+ "Ꜯ": "ꜯ",
+ "Ꜳ": "ꜳ",
+ "Ꜵ": "ꜵ",
+ "Ꜷ": "ꜷ",
+ "Ꜹ": "ꜹ",
+ "Ꜻ": "ꜻ",
+ "Ꜽ": "ꜽ",
+ "Ꜿ": "ꜿ",
+ "Ꝁ": "ꝁ",
+ "Ꝃ": "ꝃ",
+ "Ꝅ": "ꝅ",
+ "Ꝇ": "ꝇ",
+ "Ꝉ": "ꝉ",
+ "Ꝋ": "ꝋ",
+ "Ꝍ": "ꝍ",
+ "Ꝏ": "ꝏ",
+ "Ꝑ": "ꝑ",
+ "Ꝓ": "ꝓ",
+ "Ꝕ": "ꝕ",
+ "Ꝗ": "ꝗ",
+ "Ꝙ": "ꝙ",
+ "Ꝛ": "ꝛ",
+ "Ꝝ": "ꝝ",
+ "Ꝟ": "ꝟ",
+ "Ꝡ": "ꝡ",
+ "Ꝣ": "ꝣ",
+ "Ꝥ": "ꝥ",
+ "Ꝧ": "ꝧ",
+ "Ꝩ": "ꝩ",
+ "Ꝫ": "ꝫ",
+ "Ꝭ": "ꝭ",
+ "Ꝯ": "ꝯ",
+ "Ꝺ": "ꝺ",
+ "Ꝼ": "ꝼ",
+ "Ᵹ": "ᵹ",
+ "Ꝿ": "ꝿ",
+ "Ꞁ": "ꞁ",
+ "Ꞃ": "ꞃ",
+ "Ꞅ": "ꞅ",
+ "Ꞇ": "ꞇ",
+ "Ꞌ": "ꞌ",
+ "Ɥ": "ɥ",
+ "Ꞑ": "ꞑ",
+ "Ꞓ": "ꞓ",
+ "Ꞗ": "ꞗ",
+ "Ꞙ": "ꞙ",
+ "Ꞛ": "ꞛ",
+ "Ꞝ": "ꞝ",
+ "Ꞟ": "ꞟ",
+ "Ꞡ": "ꞡ",
+ "Ꞣ": "ꞣ",
+ "Ꞥ": "ꞥ",
+ "Ꞧ": "ꞧ",
+ "Ꞩ": "ꞩ",
+ "Ɦ": "ɦ",
+ "Ɜ": "ɜ",
+ "Ɡ": "ɡ",
+ "Ɬ": "ɬ",
+ "Ɪ": "ɪ",
+ "Ʞ": "ʞ",
+ "Ʇ": "ʇ",
+ "Ʝ": "ʝ",
+ "Ꭓ": "ꭓ",
+ "Ꞵ": "ꞵ",
+ "Ꞷ": "ꞷ",
+ "Ꞹ": "ꞹ",
+ "Ꞻ": "ꞻ",
+ "Ꞽ": "ꞽ",
+ "Ꞿ": "ꞿ",
+ "Ꟁ": "ꟁ",
+ "Ꟃ": "ꟃ",
+ "Ꞔ": "ꞔ",
+ "Ʂ": "ʂ",
+ "Ᶎ": "ᶎ",
+ "Ꟈ": "ꟈ",
+ "Ꟊ": "ꟊ",
+ "Ꟑ": "ꟑ",
+ "Ꟗ": "ꟗ",
+ "Ꟙ": "ꟙ",
+ "Ꟶ": "ꟶ",
+ "A": "a",
+ "B": "b",
+ "C": "c",
+ "D": "d",
+ "E": "e",
+ "F": "f",
+ "G": "g",
+ "H": "h",
+ "I": "i",
+ "J": "j",
+ "K": "k",
+ "L": "l",
+ "M": "m",
+ "N": "n",
+ "O": "o",
+ "P": "p",
+ "Q": "q",
+ "R": "r",
+ "S": "s",
+ "T": "t",
+ "U": "u",
+ "V": "v",
+ "W": "w",
+ "X": "x",
+ "Y": "y",
+ "Z": "z",
+ "𐐀": "𐐨",
+ "𐐁": "𐐩",
+ "𐐂": "𐐪",
+ "𐐃": "𐐫",
+ "𐐄": "𐐬",
+ "𐐅": "𐐭",
+ "𐐆": "𐐮",
+ "𐐇": "𐐯",
+ "𐐈": "𐐰",
+ "𐐉": "𐐱",
+ "𐐊": "𐐲",
+ "𐐋": "𐐳",
+ "𐐌": "𐐴",
+ "𐐍": "𐐵",
+ "𐐎": "𐐶",
+ "𐐏": "𐐷",
+ "𐐐": "𐐸",
+ "𐐑": "𐐹",
+ "𐐒": "𐐺",
+ "𐐓": "𐐻",
+ "𐐔": "𐐼",
+ "𐐕": "𐐽",
+ "𐐖": "𐐾",
+ "𐐗": "𐐿",
+ "𐐘": "𐑀",
+ "𐐙": "𐑁",
+ "𐐚": "𐑂",
+ "𐐛": "𐑃",
+ "𐐜": "𐑄",
+ "𐐝": "𐑅",
+ "𐐞": "𐑆",
+ "𐐟": "𐑇",
+ "𐐠": "𐑈",
+ "𐐡": "𐑉",
+ "𐐢": "𐑊",
+ "𐐣": "𐑋",
+ "𐐤": "𐑌",
+ "𐐥": "𐑍",
+ "𐐦": "𐑎",
+ "𐐧": "𐑏",
+ "𐒰": "𐓘",
+ "𐒱": "𐓙",
+ "𐒲": "𐓚",
+ "𐒳": "𐓛",
+ "𐒴": "𐓜",
+ "𐒵": "𐓝",
+ "𐒶": "𐓞",
+ "𐒷": "𐓟",
+ "𐒸": "𐓠",
+ "𐒹": "𐓡",
+ "𐒺": "𐓢",
+ "𐒻": "𐓣",
+ "𐒼": "𐓤",
+ "𐒽": "𐓥",
+ "𐒾": "𐓦",
+ "𐒿": "𐓧",
+ "𐓀": "𐓨",
+ "𐓁": "𐓩",
+ "𐓂": "𐓪",
+ "𐓃": "𐓫",
+ "𐓄": "𐓬",
+ "𐓅": "𐓭",
+ "𐓆": "𐓮",
+ "𐓇": "𐓯",
+ "𐓈": "𐓰",
+ "𐓉": "𐓱",
+ "𐓊": "𐓲",
+ "𐓋": "𐓳",
+ "𐓌": "𐓴",
+ "𐓍": "𐓵",
+ "𐓎": "𐓶",
+ "𐓏": "𐓷",
+ "𐓐": "𐓸",
+ "𐓑": "𐓹",
+ "𐓒": "𐓺",
+ "𐓓": "𐓻",
+ "𐕰": "𐖗",
+ "𐕱": "𐖘",
+ "𐕲": "𐖙",
+ "𐕳": "𐖚",
+ "𐕴": "𐖛",
+ "𐕵": "𐖜",
+ "𐕶": "𐖝",
+ "𐕷": "𐖞",
+ "𐕸": "𐖟",
+ "𐕹": "𐖠",
+ "𐕺": "𐖡",
+ "𐕼": "𐖣",
+ "𐕽": "𐖤",
+ "𐕾": "𐖥",
+ "𐕿": "𐖦",
+ "𐖀": "𐖧",
+ "𐖁": "𐖨",
+ "𐖂": "𐖩",
+ "𐖃": "𐖪",
+ "𐖄": "𐖫",
+ "𐖅": "𐖬",
+ "𐖆": "𐖭",
+ "𐖇": "𐖮",
+ "𐖈": "𐖯",
+ "𐖉": "𐖰",
+ "𐖊": "𐖱",
+ "𐖌": "𐖳",
+ "𐖍": "𐖴",
+ "𐖎": "𐖵",
+ "𐖏": "𐖶",
+ "𐖐": "𐖷",
+ "𐖑": "𐖸",
+ "𐖒": "𐖹",
+ "𐖔": "𐖻",
+ "𐖕": "𐖼",
+ "𐲀": "𐳀",
+ "𐲁": "𐳁",
+ "𐲂": "𐳂",
+ "𐲃": "𐳃",
+ "𐲄": "𐳄",
+ "𐲅": "𐳅",
+ "𐲆": "𐳆",
+ "𐲇": "𐳇",
+ "𐲈": "𐳈",
+ "𐲉": "𐳉",
+ "𐲊": "𐳊",
+ "𐲋": "𐳋",
+ "𐲌": "𐳌",
+ "𐲍": "𐳍",
+ "𐲎": "𐳎",
+ "𐲏": "𐳏",
+ "𐲐": "𐳐",
+ "𐲑": "𐳑",
+ "𐲒": "𐳒",
+ "𐲓": "𐳓",
+ "𐲔": "𐳔",
+ "𐲕": "𐳕",
+ "𐲖": "𐳖",
+ "𐲗": "𐳗",
+ "𐲘": "𐳘",
+ "𐲙": "𐳙",
+ "𐲚": "𐳚",
+ "𐲛": "𐳛",
+ "𐲜": "𐳜",
+ "𐲝": "𐳝",
+ "𐲞": "𐳞",
+ "𐲟": "𐳟",
+ "𐲠": "𐳠",
+ "𐲡": "𐳡",
+ "𐲢": "𐳢",
+ "𐲣": "𐳣",
+ "𐲤": "𐳤",
+ "𐲥": "𐳥",
+ "𐲦": "𐳦",
+ "𐲧": "𐳧",
+ "𐲨": "𐳨",
+ "𐲩": "𐳩",
+ "𐲪": "𐳪",
+ "𐲫": "𐳫",
+ "𐲬": "𐳬",
+ "𐲭": "𐳭",
+ "𐲮": "𐳮",
+ "𐲯": "𐳯",
+ "𐲰": "𐳰",
+ "𐲱": "𐳱",
+ "𐲲": "𐳲",
+ "𑢠": "𑣀",
+ "𑢡": "𑣁",
+ "𑢢": "𑣂",
+ "𑢣": "𑣃",
+ "𑢤": "𑣄",
+ "𑢥": "𑣅",
+ "𑢦": "𑣆",
+ "𑢧": "𑣇",
+ "𑢨": "𑣈",
+ "𑢩": "𑣉",
+ "𑢪": "𑣊",
+ "𑢫": "𑣋",
+ "𑢬": "𑣌",
+ "𑢭": "𑣍",
+ "𑢮": "𑣎",
+ "𑢯": "𑣏",
+ "𑢰": "𑣐",
+ "𑢱": "𑣑",
+ "𑢲": "𑣒",
+ "𑢳": "𑣓",
+ "𑢴": "𑣔",
+ "𑢵": "𑣕",
+ "𑢶": "𑣖",
+ "𑢷": "𑣗",
+ "𑢸": "𑣘",
+ "𑢹": "𑣙",
+ "𑢺": "𑣚",
+ "𑢻": "𑣛",
+ "𑢼": "𑣜",
+ "𑢽": "𑣝",
+ "𑢾": "𑣞",
+ "𑢿": "𑣟",
+ "𖹀": "𖹠",
+ "𖹁": "𖹡",
+ "𖹂": "𖹢",
+ "𖹃": "𖹣",
+ "𖹄": "𖹤",
+ "𖹅": "𖹥",
+ "𖹆": "𖹦",
+ "𖹇": "𖹧",
+ "𖹈": "𖹨",
+ "𖹉": "𖹩",
+ "𖹊": "𖹪",
+ "𖹋": "𖹫",
+ "𖹌": "𖹬",
+ "𖹍": "𖹭",
+ "𖹎": "𖹮",
+ "𖹏": "𖹯",
+ "𖹐": "𖹰",
+ "𖹑": "𖹱",
+ "𖹒": "𖹲",
+ "𖹓": "𖹳",
+ "𖹔": "𖹴",
+ "𖹕": "𖹵",
+ "𖹖": "𖹶",
+ "𖹗": "𖹷",
+ "𖹘": "𖹸",
+ "𖹙": "𖹹",
+ "𖹚": "𖹺",
+ "𖹛": "𖹻",
+ "𖹜": "𖹼",
+ "𖹝": "𖹽",
+ "𖹞": "𖹾",
+ "𖹟": "𖹿",
+ "𞤀": "𞤢",
+ "𞤁": "𞤣",
+ "𞤂": "𞤤",
+ "𞤃": "𞤥",
+ "𞤄": "𞤦",
+ "𞤅": "𞤧",
+ "𞤆": "𞤨",
+ "𞤇": "𞤩",
+ "𞤈": "𞤪",
+ "𞤉": "𞤫",
+ "𞤊": "𞤬",
+ "𞤋": "𞤭",
+ "𞤌": "𞤮",
+ "𞤍": "𞤯",
+ "𞤎": "𞤰",
+ "𞤏": "𞤱",
+ "𞤐": "𞤲",
+ "𞤑": "𞤳",
+ "𞤒": "𞤴",
+ "𞤓": "𞤵",
+ "𞤔": "𞤶",
+ "𞤕": "𞤷",
+ "𞤖": "𞤸",
+ "𞤗": "𞤹",
+ "𞤘": "𞤺",
+ "𞤙": "𞤻",
+ "𞤚": "𞤼",
+ "𞤛": "𞤽",
+ "𞤜": "𞤾",
+ "𞤝": "𞤿",
+ "𞤞": "𞥀",
+ "𞤟": "𞥁",
+ "𞤠": "𞥂",
+ "𞤡": "𞥃"
+};
diff --git a/pkgs/markdown/lib/src/assets/html_entities.dart b/pkgs/markdown/lib/src/assets/html_entities.dart
new file mode 100644
index 0000000..2599762
--- /dev/null
+++ b/pkgs/markdown/lib/src/assets/html_entities.dart
@@ -0,0 +1,2133 @@
+// Generated file. do not edit.
+//
+// Source: tool/entities.json
+// Script: tool/update_entities.dart
+// ignore_for_file: prefer_single_quotes
+
+const htmlEntitiesMap = {
+ "Æ": "Æ",
+ "&": "&",
+ "Á": "Á",
+ "Ă": "Ă",
+ "Â": "Â",
+ "А": "А",
+ "𝔄": "𝔄",
+ "À": "À",
+ "Α": "Α",
+ "Ā": "Ā",
+ "⩓": "⩓",
+ "Ą": "Ą",
+ "𝔸": "𝔸",
+ "⁡": "",
+ "Å": "Å",
+ "𝒜": "𝒜",
+ "≔": "≔",
+ "Ã": "Ã",
+ "Ä": "Ä",
+ "∖": "∖",
+ "⫧": "⫧",
+ "⌆": "⌆",
+ "Б": "Б",
+ "∵": "∵",
+ "ℬ": "ℬ",
+ "Β": "Β",
+ "𝔅": "𝔅",
+ "𝔹": "𝔹",
+ "˘": "˘",
+ "ℬ": "ℬ",
+ "≎": "≎",
+ "Ч": "Ч",
+ "©": "©",
+ "Ć": "Ć",
+ "⋒": "⋒",
+ "ⅅ": "ⅅ",
+ "ℭ": "ℭ",
+ "Č": "Č",
+ "Ç": "Ç",
+ "Ĉ": "Ĉ",
+ "∰": "∰",
+ "Ċ": "Ċ",
+ "¸": "¸",
+ "·": "·",
+ "ℭ": "ℭ",
+ "Χ": "Χ",
+ "⊙": "⊙",
+ "⊖": "⊖",
+ "⊕": "⊕",
+ "⊗": "⊗",
+ "∲": "∲",
+ "”": "”",
+ "’": "’",
+ "∷": "∷",
+ "⩴": "⩴",
+ "≡": "≡",
+ "∯": "∯",
+ "∮": "∮",
+ "ℂ": "ℂ",
+ "∐": "∐",
+ "∳": "∳",
+ "⨯": "⨯",
+ "𝒞": "𝒞",
+ "⋓": "⋓",
+ "≍": "≍",
+ "ⅅ": "ⅅ",
+ "⤑": "⤑",
+ "Ђ": "Ђ",
+ "Ѕ": "Ѕ",
+ "Џ": "Џ",
+ "‡": "‡",
+ "↡": "↡",
+ "⫤": "⫤",
+ "Ď": "Ď",
+ "Д": "Д",
+ "∇": "∇",
+ "Δ": "Δ",
+ "𝔇": "𝔇",
+ "´": "´",
+ "˙": "˙",
+ "˝": "˝",
+ "`": "`",
+ "˜": "˜",
+ "⋄": "⋄",
+ "ⅆ": "ⅆ",
+ "𝔻": "𝔻",
+ "¨": "¨",
+ "⃜": "⃜",
+ "≐": "≐",
+ "∯": "∯",
+ "¨": "¨",
+ "⇓": "⇓",
+ "⇐": "⇐",
+ "⇔": "⇔",
+ "⫤": "⫤",
+ "⟸": "⟸",
+ "⟺": "⟺",
+ "⟹": "⟹",
+ "⇒": "⇒",
+ "⊨": "⊨",
+ "⇑": "⇑",
+ "⇕": "⇕",
+ "∥": "∥",
+ "↓": "↓",
+ "⤓": "⤓",
+ "⇵": "⇵",
+ "̑": "̑",
+ "⥐": "⥐",
+ "⥞": "⥞",
+ "↽": "↽",
+ "⥖": "⥖",
+ "⥟": "⥟",
+ "⇁": "⇁",
+ "⥗": "⥗",
+ "⊤": "⊤",
+ "↧": "↧",
+ "⇓": "⇓",
+ "𝒟": "𝒟",
+ "Đ": "Đ",
+ "Ŋ": "Ŋ",
+ "Ð": "Ð",
+ "É": "É",
+ "Ě": "Ě",
+ "Ê": "Ê",
+ "Э": "Э",
+ "Ė": "Ė",
+ "𝔈": "𝔈",
+ "È": "È",
+ "∈": "∈",
+ "Ē": "Ē",
+ "◻": "◻",
+ "▫": "▫",
+ "Ę": "Ę",
+ "𝔼": "𝔼",
+ "Ε": "Ε",
+ "⩵": "⩵",
+ "≂": "≂",
+ "⇌": "⇌",
+ "ℰ": "ℰ",
+ "⩳": "⩳",
+ "Η": "Η",
+ "Ë": "Ë",
+ "∃": "∃",
+ "ⅇ": "ⅇ",
+ "Ф": "Ф",
+ "𝔉": "𝔉",
+ "◼": "◼",
+ "▪": "▪",
+ "𝔽": "𝔽",
+ "∀": "∀",
+ "ℱ": "ℱ",
+ "ℱ": "ℱ",
+ "Ѓ": "Ѓ",
+ ">": ">",
+ "Γ": "Γ",
+ "Ϝ": "Ϝ",
+ "Ğ": "Ğ",
+ "Ģ": "Ģ",
+ "Ĝ": "Ĝ",
+ "Г": "Г",
+ "Ġ": "Ġ",
+ "𝔊": "𝔊",
+ "⋙": "⋙",
+ "𝔾": "𝔾",
+ "≥": "≥",
+ "⋛": "⋛",
+ "≧": "≧",
+ "⪢": "⪢",
+ "≷": "≷",
+ "⩾": "⩾",
+ "≳": "≳",
+ "𝒢": "𝒢",
+ "≫": "≫",
+ "Ъ": "Ъ",
+ "ˇ": "ˇ",
+ "^": "^",
+ "Ĥ": "Ĥ",
+ "ℌ": "ℌ",
+ "ℋ": "ℋ",
+ "ℍ": "ℍ",
+ "─": "─",
+ "ℋ": "ℋ",
+ "Ħ": "Ħ",
+ "≎": "≎",
+ "≏": "≏",
+ "Е": "Е",
+ "IJ": "IJ",
+ "Ё": "Ё",
+ "Í": "Í",
+ "Î": "Î",
+ "И": "И",
+ "İ": "İ",
+ "ℑ": "ℑ",
+ "Ì": "Ì",
+ "ℑ": "ℑ",
+ "Ī": "Ī",
+ "ⅈ": "ⅈ",
+ "⇒": "⇒",
+ "∬": "∬",
+ "∫": "∫",
+ "⋂": "⋂",
+ "⁣": "",
+ "⁢": "",
+ "Į": "Į",
+ "𝕀": "𝕀",
+ "Ι": "Ι",
+ "ℐ": "ℐ",
+ "Ĩ": "Ĩ",
+ "І": "І",
+ "Ï": "Ï",
+ "Ĵ": "Ĵ",
+ "Й": "Й",
+ "𝔍": "𝔍",
+ "𝕁": "𝕁",
+ "𝒥": "𝒥",
+ "Ј": "Ј",
+ "Є": "Є",
+ "Х": "Х",
+ "Ќ": "Ќ",
+ "Κ": "Κ",
+ "Ķ": "Ķ",
+ "К": "К",
+ "𝔎": "𝔎",
+ "𝕂": "𝕂",
+ "𝒦": "𝒦",
+ "Љ": "Љ",
+ "<": "<",
+ "Ĺ": "Ĺ",
+ "Λ": "Λ",
+ "⟪": "⟪",
+ "ℒ": "ℒ",
+ "↞": "↞",
+ "Ľ": "Ľ",
+ "Ļ": "Ļ",
+ "Л": "Л",
+ "⟨": "⟨",
+ "←": "←",
+ "⇤": "⇤",
+ "⇆": "⇆",
+ "⌈": "⌈",
+ "⟦": "⟦",
+ "⥡": "⥡",
+ "⇃": "⇃",
+ "⥙": "⥙",
+ "⌊": "⌊",
+ "↔": "↔",
+ "⥎": "⥎",
+ "⊣": "⊣",
+ "↤": "↤",
+ "⥚": "⥚",
+ "⊲": "⊲",
+ "⧏": "⧏",
+ "⊴": "⊴",
+ "⥑": "⥑",
+ "⥠": "⥠",
+ "↿": "↿",
+ "⥘": "⥘",
+ "↼": "↼",
+ "⥒": "⥒",
+ "⇐": "⇐",
+ "⇔": "⇔",
+ "⋚": "⋚",
+ "≦": "≦",
+ "≶": "≶",
+ "⪡": "⪡",
+ "⩽": "⩽",
+ "≲": "≲",
+ "𝔏": "𝔏",
+ "⋘": "⋘",
+ "⇚": "⇚",
+ "Ŀ": "Ŀ",
+ "⟵": "⟵",
+ "⟷": "⟷",
+ "⟶": "⟶",
+ "⟸": "⟸",
+ "⟺": "⟺",
+ "⟹": "⟹",
+ "𝕃": "𝕃",
+ "↙": "↙",
+ "↘": "↘",
+ "ℒ": "ℒ",
+ "↰": "↰",
+ "Ł": "Ł",
+ "≪": "≪",
+ "⤅": "⤅",
+ "М": "М",
+ " ": " ",
+ "ℳ": "ℳ",
+ "𝔐": "𝔐",
+ "∓": "∓",
+ "𝕄": "𝕄",
+ "ℳ": "ℳ",
+ "Μ": "Μ",
+ "Њ": "Њ",
+ "Ń": "Ń",
+ "Ň": "Ň",
+ "Ņ": "Ņ",
+ "Н": "Н",
+ "​": "",
+ "​": "",
+ "​": "",
+ "​": "",
+ "≫": "≫",
+ "≪": "≪",
+ "
": "\n",
+ "𝔑": "𝔑",
+ "⁠": "",
+ " ": " ",
+ "ℕ": "ℕ",
+ "⫬": "⫬",
+ "≢": "≢",
+ "≭": "≭",
+ "∦": "∦",
+ "∉": "∉",
+ "≠": "≠",
+ "≂̸": "≂̸",
+ "∄": "∄",
+ "≯": "≯",
+ "≱": "≱",
+ "≧̸": "≧̸",
+ "≫̸": "≫̸",
+ "≹": "≹",
+ "⩾̸": "⩾̸",
+ "≵": "≵",
+ "≎̸": "≎̸",
+ "≏̸": "≏̸",
+ "⋪": "⋪",
+ "⧏̸": "⧏̸",
+ "⋬": "⋬",
+ "≮": "≮",
+ "≰": "≰",
+ "≸": "≸",
+ "≪̸": "≪̸",
+ "⩽̸": "⩽̸",
+ "≴": "≴",
+ "⪢̸": "⪢̸",
+ "⪡̸": "⪡̸",
+ "⊀": "⊀",
+ "⪯̸": "⪯̸",
+ "⋠": "⋠",
+ "∌": "∌",
+ "⋫": "⋫",
+ "⧐̸": "⧐̸",
+ "⋭": "⋭",
+ "⊏̸": "⊏̸",
+ "⋢": "⋢",
+ "⊐̸": "⊐̸",
+ "⋣": "⋣",
+ "⊂⃒": "⊂⃒",
+ "⊈": "⊈",
+ "⊁": "⊁",
+ "⪰̸": "⪰̸",
+ "⋡": "⋡",
+ "≿̸": "≿̸",
+ "⊃⃒": "⊃⃒",
+ "⊉": "⊉",
+ "≁": "≁",
+ "≄": "≄",
+ "≇": "≇",
+ "≉": "≉",
+ "∤": "∤",
+ "𝒩": "𝒩",
+ "Ñ": "Ñ",
+ "Ν": "Ν",
+ "Œ": "Œ",
+ "Ó": "Ó",
+ "Ô": "Ô",
+ "О": "О",
+ "Ő": "Ő",
+ "𝔒": "𝔒",
+ "Ò": "Ò",
+ "Ō": "Ō",
+ "Ω": "Ω",
+ "Ο": "Ο",
+ "𝕆": "𝕆",
+ "“": "“",
+ "‘": "‘",
+ "⩔": "⩔",
+ "𝒪": "𝒪",
+ "Ø": "Ø",
+ "Õ": "Õ",
+ "⨷": "⨷",
+ "Ö": "Ö",
+ "‾": "‾",
+ "⏞": "⏞",
+ "⎴": "⎴",
+ "⏜": "⏜",
+ "∂": "∂",
+ "П": "П",
+ "𝔓": "𝔓",
+ "Φ": "Φ",
+ "Π": "Π",
+ "±": "±",
+ "ℌ": "ℌ",
+ "ℙ": "ℙ",
+ "⪻": "⪻",
+ "≺": "≺",
+ "⪯": "⪯",
+ "≼": "≼",
+ "≾": "≾",
+ "″": "″",
+ "∏": "∏",
+ "∷": "∷",
+ "∝": "∝",
+ "𝒫": "𝒫",
+ "Ψ": "Ψ",
+ """: "\"",
+ "𝔔": "𝔔",
+ "ℚ": "ℚ",
+ "𝒬": "𝒬",
+ "⤐": "⤐",
+ "®": "®",
+ "Ŕ": "Ŕ",
+ "⟫": "⟫",
+ "↠": "↠",
+ "⤖": "⤖",
+ "Ř": "Ř",
+ "Ŗ": "Ŗ",
+ "Р": "Р",
+ "ℜ": "ℜ",
+ "∋": "∋",
+ "⇋": "⇋",
+ "⥯": "⥯",
+ "ℜ": "ℜ",
+ "Ρ": "Ρ",
+ "⟩": "⟩",
+ "→": "→",
+ "⇥": "⇥",
+ "⇄": "⇄",
+ "⌉": "⌉",
+ "⟧": "⟧",
+ "⥝": "⥝",
+ "⇂": "⇂",
+ "⥕": "⥕",
+ "⌋": "⌋",
+ "⊢": "⊢",
+ "↦": "↦",
+ "⥛": "⥛",
+ "⊳": "⊳",
+ "⧐": "⧐",
+ "⊵": "⊵",
+ "⥏": "⥏",
+ "⥜": "⥜",
+ "↾": "↾",
+ "⥔": "⥔",
+ "⇀": "⇀",
+ "⥓": "⥓",
+ "⇒": "⇒",
+ "ℝ": "ℝ",
+ "⥰": "⥰",
+ "⇛": "⇛",
+ "ℛ": "ℛ",
+ "↱": "↱",
+ "⧴": "⧴",
+ "Щ": "Щ",
+ "Ш": "Ш",
+ "Ь": "Ь",
+ "Ś": "Ś",
+ "⪼": "⪼",
+ "Š": "Š",
+ "Ş": "Ş",
+ "Ŝ": "Ŝ",
+ "С": "С",
+ "𝔖": "𝔖",
+ "↓": "↓",
+ "←": "←",
+ "→": "→",
+ "↑": "↑",
+ "Σ": "Σ",
+ "∘": "∘",
+ "𝕊": "𝕊",
+ "√": "√",
+ "□": "□",
+ "⊓": "⊓",
+ "⊏": "⊏",
+ "⊑": "⊑",
+ "⊐": "⊐",
+ "⊒": "⊒",
+ "⊔": "⊔",
+ "𝒮": "𝒮",
+ "⋆": "⋆",
+ "⋐": "⋐",
+ "⋐": "⋐",
+ "⊆": "⊆",
+ "≻": "≻",
+ "⪰": "⪰",
+ "≽": "≽",
+ "≿": "≿",
+ "∋": "∋",
+ "∑": "∑",
+ "⋑": "⋑",
+ "⊃": "⊃",
+ "⊇": "⊇",
+ "⋑": "⋑",
+ "Þ": "Þ",
+ "™": "™",
+ "Ћ": "Ћ",
+ "Ц": "Ц",
+ "	": "\t",
+ "Τ": "Τ",
+ "Ť": "Ť",
+ "Ţ": "Ţ",
+ "Т": "Т",
+ "𝔗": "𝔗",
+ "∴": "∴",
+ "Θ": "Θ",
+ "  ": " ",
+ " ": " ",
+ "∼": "∼",
+ "≃": "≃",
+ "≅": "≅",
+ "≈": "≈",
+ "𝕋": "𝕋",
+ "⃛": "⃛",
+ "𝒯": "𝒯",
+ "Ŧ": "Ŧ",
+ "Ú": "Ú",
+ "↟": "↟",
+ "⥉": "⥉",
+ "Ў": "Ў",
+ "Ŭ": "Ŭ",
+ "Û": "Û",
+ "У": "У",
+ "Ű": "Ű",
+ "𝔘": "𝔘",
+ "Ù": "Ù",
+ "Ū": "Ū",
+ "_": "_",
+ "⏟": "⏟",
+ "⎵": "⎵",
+ "⏝": "⏝",
+ "⋃": "⋃",
+ "⊎": "⊎",
+ "Ų": "Ų",
+ "𝕌": "𝕌",
+ "↑": "↑",
+ "⤒": "⤒",
+ "⇅": "⇅",
+ "↕": "↕",
+ "⥮": "⥮",
+ "⊥": "⊥",
+ "↥": "↥",
+ "⇑": "⇑",
+ "⇕": "⇕",
+ "↖": "↖",
+ "↗": "↗",
+ "ϒ": "ϒ",
+ "Υ": "Υ",
+ "Ů": "Ů",
+ "𝒰": "𝒰",
+ "Ũ": "Ũ",
+ "Ü": "Ü",
+ "⊫": "⊫",
+ "⫫": "⫫",
+ "В": "В",
+ "⊩": "⊩",
+ "⫦": "⫦",
+ "⋁": "⋁",
+ "‖": "‖",
+ "‖": "‖",
+ "∣": "∣",
+ "|": "|",
+ "❘": "❘",
+ "≀": "≀",
+ " ": " ",
+ "𝔙": "𝔙",
+ "𝕍": "𝕍",
+ "𝒱": "𝒱",
+ "⊪": "⊪",
+ "Ŵ": "Ŵ",
+ "⋀": "⋀",
+ "𝔚": "𝔚",
+ "𝕎": "𝕎",
+ "𝒲": "𝒲",
+ "𝔛": "𝔛",
+ "Ξ": "Ξ",
+ "𝕏": "𝕏",
+ "𝒳": "𝒳",
+ "Я": "Я",
+ "Ї": "Ї",
+ "Ю": "Ю",
+ "Ý": "Ý",
+ "Ŷ": "Ŷ",
+ "Ы": "Ы",
+ "𝔜": "𝔜",
+ "𝕐": "𝕐",
+ "𝒴": "𝒴",
+ "Ÿ": "Ÿ",
+ "Ж": "Ж",
+ "Ź": "Ź",
+ "Ž": "Ž",
+ "З": "З",
+ "Ż": "Ż",
+ "​": "",
+ "Ζ": "Ζ",
+ "ℨ": "ℨ",
+ "ℤ": "ℤ",
+ "𝒵": "𝒵",
+ "á": "á",
+ "ă": "ă",
+ "∾": "∾",
+ "∾̳": "∾̳",
+ "∿": "∿",
+ "â": "â",
+ "´": "´",
+ "а": "а",
+ "æ": "æ",
+ "⁡": "",
+ "𝔞": "𝔞",
+ "à": "à",
+ "ℵ": "ℵ",
+ "ℵ": "ℵ",
+ "α": "α",
+ "ā": "ā",
+ "⨿": "⨿",
+ "&": "&",
+ "∧": "∧",
+ "⩕": "⩕",
+ "⩜": "⩜",
+ "⩘": "⩘",
+ "⩚": "⩚",
+ "∠": "∠",
+ "⦤": "⦤",
+ "∠": "∠",
+ "∡": "∡",
+ "⦨": "⦨",
+ "⦩": "⦩",
+ "⦪": "⦪",
+ "⦫": "⦫",
+ "⦬": "⦬",
+ "⦭": "⦭",
+ "⦮": "⦮",
+ "⦯": "⦯",
+ "∟": "∟",
+ "⊾": "⊾",
+ "⦝": "⦝",
+ "∢": "∢",
+ "Å": "Å",
+ "⍼": "⍼",
+ "ą": "ą",
+ "𝕒": "𝕒",
+ "≈": "≈",
+ "⩰": "⩰",
+ "⩯": "⩯",
+ "≊": "≊",
+ "≋": "≋",
+ "'": "'",
+ "≈": "≈",
+ "≊": "≊",
+ "å": "å",
+ "𝒶": "𝒶",
+ "*": "*",
+ "≈": "≈",
+ "≍": "≍",
+ "ã": "ã",
+ "ä": "ä",
+ "∳": "∳",
+ "⨑": "⨑",
+ "⫭": "⫭",
+ "≌": "≌",
+ "϶": "϶",
+ "‵": "‵",
+ "∽": "∽",
+ "⋍": "⋍",
+ "⊽": "⊽",
+ "⌅": "⌅",
+ "⌅": "⌅",
+ "⎵": "⎵",
+ "⎶": "⎶",
+ "≌": "≌",
+ "б": "б",
+ "„": "„",
+ "∵": "∵",
+ "∵": "∵",
+ "⦰": "⦰",
+ "϶": "϶",
+ "ℬ": "ℬ",
+ "β": "β",
+ "ℶ": "ℶ",
+ "≬": "≬",
+ "𝔟": "𝔟",
+ "⋂": "⋂",
+ "◯": "◯",
+ "⋃": "⋃",
+ "⨀": "⨀",
+ "⨁": "⨁",
+ "⨂": "⨂",
+ "⨆": "⨆",
+ "★": "★",
+ "▽": "▽",
+ "△": "△",
+ "⨄": "⨄",
+ "⋁": "⋁",
+ "⋀": "⋀",
+ "⤍": "⤍",
+ "⧫": "⧫",
+ "▪": "▪",
+ "▴": "▴",
+ "▾": "▾",
+ "◂": "◂",
+ "▸": "▸",
+ "␣": "␣",
+ "▒": "▒",
+ "░": "░",
+ "▓": "▓",
+ "█": "█",
+ "=⃥": "=⃥",
+ "≡⃥": "≡⃥",
+ "⌐": "⌐",
+ "𝕓": "𝕓",
+ "⊥": "⊥",
+ "⊥": "⊥",
+ "⋈": "⋈",
+ "╗": "╗",
+ "╔": "╔",
+ "╖": "╖",
+ "╓": "╓",
+ "═": "═",
+ "╦": "╦",
+ "╩": "╩",
+ "╤": "╤",
+ "╧": "╧",
+ "╝": "╝",
+ "╚": "╚",
+ "╜": "╜",
+ "╙": "╙",
+ "║": "║",
+ "╬": "╬",
+ "╣": "╣",
+ "╠": "╠",
+ "╫": "╫",
+ "╢": "╢",
+ "╟": "╟",
+ "⧉": "⧉",
+ "╕": "╕",
+ "╒": "╒",
+ "┐": "┐",
+ "┌": "┌",
+ "─": "─",
+ "╥": "╥",
+ "╨": "╨",
+ "┬": "┬",
+ "┴": "┴",
+ "⊟": "⊟",
+ "⊞": "⊞",
+ "⊠": "⊠",
+ "╛": "╛",
+ "╘": "╘",
+ "┘": "┘",
+ "└": "└",
+ "│": "│",
+ "╪": "╪",
+ "╡": "╡",
+ "╞": "╞",
+ "┼": "┼",
+ "┤": "┤",
+ "├": "├",
+ "‵": "‵",
+ "˘": "˘",
+ "¦": "¦",
+ "𝒷": "𝒷",
+ "⁏": "⁏",
+ "∽": "∽",
+ "⋍": "⋍",
+ "\": r"\",
+ "⧅": "⧅",
+ "⟈": "⟈",
+ "•": "•",
+ "•": "•",
+ "≎": "≎",
+ "⪮": "⪮",
+ "≏": "≏",
+ "≏": "≏",
+ "ć": "ć",
+ "∩": "∩",
+ "⩄": "⩄",
+ "⩉": "⩉",
+ "⩋": "⩋",
+ "⩇": "⩇",
+ "⩀": "⩀",
+ "∩︀": "∩︀",
+ "⁁": "⁁",
+ "ˇ": "ˇ",
+ "⩍": "⩍",
+ "č": "č",
+ "ç": "ç",
+ "ĉ": "ĉ",
+ "⩌": "⩌",
+ "⩐": "⩐",
+ "ċ": "ċ",
+ "¸": "¸",
+ "⦲": "⦲",
+ "¢": "¢",
+ "·": "·",
+ "𝔠": "𝔠",
+ "ч": "ч",
+ "✓": "✓",
+ "✓": "✓",
+ "χ": "χ",
+ "○": "○",
+ "⧃": "⧃",
+ "ˆ": "ˆ",
+ "≗": "≗",
+ "↺": "↺",
+ "↻": "↻",
+ "®": "®",
+ "Ⓢ": "Ⓢ",
+ "⊛": "⊛",
+ "⊚": "⊚",
+ "⊝": "⊝",
+ "≗": "≗",
+ "⨐": "⨐",
+ "⫯": "⫯",
+ "⧂": "⧂",
+ "♣": "♣",
+ "♣": "♣",
+ ":": ":",
+ "≔": "≔",
+ "≔": "≔",
+ ",": ",",
+ "@": "@",
+ "∁": "∁",
+ "∘": "∘",
+ "∁": "∁",
+ "ℂ": "ℂ",
+ "≅": "≅",
+ "⩭": "⩭",
+ "∮": "∮",
+ "𝕔": "𝕔",
+ "∐": "∐",
+ "©": "©",
+ "℗": "℗",
+ "↵": "↵",
+ "✗": "✗",
+ "𝒸": "𝒸",
+ "⫏": "⫏",
+ "⫑": "⫑",
+ "⫐": "⫐",
+ "⫒": "⫒",
+ "⋯": "⋯",
+ "⤸": "⤸",
+ "⤵": "⤵",
+ "⋞": "⋞",
+ "⋟": "⋟",
+ "↶": "↶",
+ "⤽": "⤽",
+ "∪": "∪",
+ "⩈": "⩈",
+ "⩆": "⩆",
+ "⩊": "⩊",
+ "⊍": "⊍",
+ "⩅": "⩅",
+ "∪︀": "∪︀",
+ "↷": "↷",
+ "⤼": "⤼",
+ "⋞": "⋞",
+ "⋟": "⋟",
+ "⋎": "⋎",
+ "⋏": "⋏",
+ "¤": "¤",
+ "↶": "↶",
+ "↷": "↷",
+ "⋎": "⋎",
+ "⋏": "⋏",
+ "∲": "∲",
+ "∱": "∱",
+ "⌭": "⌭",
+ "⇓": "⇓",
+ "⥥": "⥥",
+ "†": "†",
+ "ℸ": "ℸ",
+ "↓": "↓",
+ "‐": "‐",
+ "⊣": "⊣",
+ "⤏": "⤏",
+ "˝": "˝",
+ "ď": "ď",
+ "д": "д",
+ "ⅆ": "ⅆ",
+ "‡": "‡",
+ "⇊": "⇊",
+ "⩷": "⩷",
+ "°": "°",
+ "δ": "δ",
+ "⦱": "⦱",
+ "⥿": "⥿",
+ "𝔡": "𝔡",
+ "⇃": "⇃",
+ "⇂": "⇂",
+ "⋄": "⋄",
+ "⋄": "⋄",
+ "♦": "♦",
+ "♦": "♦",
+ "¨": "¨",
+ "ϝ": "ϝ",
+ "⋲": "⋲",
+ "÷": "÷",
+ "÷": "÷",
+ "⋇": "⋇",
+ "⋇": "⋇",
+ "ђ": "ђ",
+ "⌞": "⌞",
+ "⌍": "⌍",
+ "$": r"$",
+ "𝕕": "𝕕",
+ "˙": "˙",
+ "≐": "≐",
+ "≑": "≑",
+ "∸": "∸",
+ "∔": "∔",
+ "⊡": "⊡",
+ "⌆": "⌆",
+ "↓": "↓",
+ "⇊": "⇊",
+ "⇃": "⇃",
+ "⇂": "⇂",
+ "⤐": "⤐",
+ "⌟": "⌟",
+ "⌌": "⌌",
+ "𝒹": "𝒹",
+ "ѕ": "ѕ",
+ "⧶": "⧶",
+ "đ": "đ",
+ "⋱": "⋱",
+ "▿": "▿",
+ "▾": "▾",
+ "⇵": "⇵",
+ "⥯": "⥯",
+ "⦦": "⦦",
+ "џ": "џ",
+ "⟿": "⟿",
+ "⩷": "⩷",
+ "≑": "≑",
+ "é": "é",
+ "⩮": "⩮",
+ "ě": "ě",
+ "≖": "≖",
+ "ê": "ê",
+ "≕": "≕",
+ "э": "э",
+ "ė": "ė",
+ "ⅇ": "ⅇ",
+ "≒": "≒",
+ "𝔢": "𝔢",
+ "⪚": "⪚",
+ "è": "è",
+ "⪖": "⪖",
+ "⪘": "⪘",
+ "⪙": "⪙",
+ "⏧": "⏧",
+ "ℓ": "ℓ",
+ "⪕": "⪕",
+ "⪗": "⪗",
+ "ē": "ē",
+ "∅": "∅",
+ "∅": "∅",
+ "∅": "∅",
+ " ": " ",
+ " ": " ",
+ " ": " ",
+ "ŋ": "ŋ",
+ " ": " ",
+ "ę": "ę",
+ "𝕖": "𝕖",
+ "⋕": "⋕",
+ "⧣": "⧣",
+ "⩱": "⩱",
+ "ε": "ε",
+ "ε": "ε",
+ "ϵ": "ϵ",
+ "≖": "≖",
+ "≕": "≕",
+ "≂": "≂",
+ "⪖": "⪖",
+ "⪕": "⪕",
+ "=": "=",
+ "≟": "≟",
+ "≡": "≡",
+ "⩸": "⩸",
+ "⧥": "⧥",
+ "≓": "≓",
+ "⥱": "⥱",
+ "ℯ": "ℯ",
+ "≐": "≐",
+ "≂": "≂",
+ "η": "η",
+ "ð": "ð",
+ "ë": "ë",
+ "€": "€",
+ "!": "!",
+ "∃": "∃",
+ "ℰ": "ℰ",
+ "ⅇ": "ⅇ",
+ "≒": "≒",
+ "ф": "ф",
+ "♀": "♀",
+ "ffi": "ffi",
+ "ff": "ff",
+ "ffl": "ffl",
+ "𝔣": "𝔣",
+ "fi": "fi",
+ "fj": "fj",
+ "♭": "♭",
+ "fl": "fl",
+ "▱": "▱",
+ "ƒ": "ƒ",
+ "𝕗": "𝕗",
+ "∀": "∀",
+ "⋔": "⋔",
+ "⫙": "⫙",
+ "⨍": "⨍",
+ "½": "½",
+ "⅓": "⅓",
+ "¼": "¼",
+ "⅕": "⅕",
+ "⅙": "⅙",
+ "⅛": "⅛",
+ "⅔": "⅔",
+ "⅖": "⅖",
+ "¾": "¾",
+ "⅗": "⅗",
+ "⅜": "⅜",
+ "⅘": "⅘",
+ "⅚": "⅚",
+ "⅝": "⅝",
+ "⅞": "⅞",
+ "⁄": "⁄",
+ "⌢": "⌢",
+ "𝒻": "𝒻",
+ "≧": "≧",
+ "⪌": "⪌",
+ "ǵ": "ǵ",
+ "γ": "γ",
+ "ϝ": "ϝ",
+ "⪆": "⪆",
+ "ğ": "ğ",
+ "ĝ": "ĝ",
+ "г": "г",
+ "ġ": "ġ",
+ "≥": "≥",
+ "⋛": "⋛",
+ "≥": "≥",
+ "≧": "≧",
+ "⩾": "⩾",
+ "⩾": "⩾",
+ "⪩": "⪩",
+ "⪀": "⪀",
+ "⪂": "⪂",
+ "⪄": "⪄",
+ "⋛︀": "⋛︀",
+ "⪔": "⪔",
+ "𝔤": "𝔤",
+ "≫": "≫",
+ "⋙": "⋙",
+ "ℷ": "ℷ",
+ "ѓ": "ѓ",
+ "≷": "≷",
+ "⪒": "⪒",
+ "⪥": "⪥",
+ "⪤": "⪤",
+ "≩": "≩",
+ "⪊": "⪊",
+ "⪊": "⪊",
+ "⪈": "⪈",
+ "⪈": "⪈",
+ "≩": "≩",
+ "⋧": "⋧",
+ "𝕘": "𝕘",
+ "`": "`",
+ "ℊ": "ℊ",
+ "≳": "≳",
+ "⪎": "⪎",
+ "⪐": "⪐",
+ ">": ">",
+ "⪧": "⪧",
+ "⩺": "⩺",
+ "⋗": "⋗",
+ "⦕": "⦕",
+ "⩼": "⩼",
+ "⪆": "⪆",
+ "⥸": "⥸",
+ "⋗": "⋗",
+ "⋛": "⋛",
+ "⪌": "⪌",
+ "≷": "≷",
+ "≳": "≳",
+ "≩︀": "≩︀",
+ "≩︀": "≩︀",
+ "⇔": "⇔",
+ " ": " ",
+ "½": "½",
+ "ℋ": "ℋ",
+ "ъ": "ъ",
+ "↔": "↔",
+ "⥈": "⥈",
+ "↭": "↭",
+ "ℏ": "ℏ",
+ "ĥ": "ĥ",
+ "♥": "♥",
+ "♥": "♥",
+ "…": "…",
+ "⊹": "⊹",
+ "𝔥": "𝔥",
+ "⤥": "⤥",
+ "⤦": "⤦",
+ "⇿": "⇿",
+ "∻": "∻",
+ "↩": "↩",
+ "↪": "↪",
+ "𝕙": "𝕙",
+ "―": "―",
+ "𝒽": "𝒽",
+ "ℏ": "ℏ",
+ "ħ": "ħ",
+ "⁃": "⁃",
+ "‐": "‐",
+ "í": "í",
+ "⁣": "",
+ "î": "î",
+ "и": "и",
+ "е": "е",
+ "¡": "¡",
+ "⇔": "⇔",
+ "𝔦": "𝔦",
+ "ì": "ì",
+ "ⅈ": "ⅈ",
+ "⨌": "⨌",
+ "∭": "∭",
+ "⧜": "⧜",
+ "℩": "℩",
+ "ij": "ij",
+ "ī": "ī",
+ "ℑ": "ℑ",
+ "ℐ": "ℐ",
+ "ℑ": "ℑ",
+ "ı": "ı",
+ "⊷": "⊷",
+ "Ƶ": "Ƶ",
+ "∈": "∈",
+ "℅": "℅",
+ "∞": "∞",
+ "⧝": "⧝",
+ "ı": "ı",
+ "∫": "∫",
+ "⊺": "⊺",
+ "ℤ": "ℤ",
+ "⊺": "⊺",
+ "⨗": "⨗",
+ "⨼": "⨼",
+ "ё": "ё",
+ "į": "į",
+ "𝕚": "𝕚",
+ "ι": "ι",
+ "⨼": "⨼",
+ "¿": "¿",
+ "𝒾": "𝒾",
+ "∈": "∈",
+ "⋹": "⋹",
+ "⋵": "⋵",
+ "⋴": "⋴",
+ "⋳": "⋳",
+ "∈": "∈",
+ "⁢": "",
+ "ĩ": "ĩ",
+ "і": "і",
+ "ï": "ï",
+ "ĵ": "ĵ",
+ "й": "й",
+ "𝔧": "𝔧",
+ "ȷ": "ȷ",
+ "𝕛": "𝕛",
+ "𝒿": "𝒿",
+ "ј": "ј",
+ "є": "є",
+ "κ": "κ",
+ "ϰ": "ϰ",
+ "ķ": "ķ",
+ "к": "к",
+ "𝔨": "𝔨",
+ "ĸ": "ĸ",
+ "х": "х",
+ "ќ": "ќ",
+ "𝕜": "𝕜",
+ "𝓀": "𝓀",
+ "⇚": "⇚",
+ "⇐": "⇐",
+ "⤛": "⤛",
+ "⤎": "⤎",
+ "≦": "≦",
+ "⪋": "⪋",
+ "⥢": "⥢",
+ "ĺ": "ĺ",
+ "⦴": "⦴",
+ "ℒ": "ℒ",
+ "λ": "λ",
+ "⟨": "⟨",
+ "⦑": "⦑",
+ "⟨": "⟨",
+ "⪅": "⪅",
+ "«": "«",
+ "←": "←",
+ "⇤": "⇤",
+ "⤟": "⤟",
+ "⤝": "⤝",
+ "↩": "↩",
+ "↫": "↫",
+ "⤹": "⤹",
+ "⥳": "⥳",
+ "↢": "↢",
+ "⪫": "⪫",
+ "⤙": "⤙",
+ "⪭": "⪭",
+ "⪭︀": "⪭︀",
+ "⤌": "⤌",
+ "❲": "❲",
+ "{": "{",
+ "[": "[",
+ "⦋": "⦋",
+ "⦏": "⦏",
+ "⦍": "⦍",
+ "ľ": "ľ",
+ "ļ": "ļ",
+ "⌈": "⌈",
+ "{": "{",
+ "л": "л",
+ "⤶": "⤶",
+ "“": "“",
+ "„": "„",
+ "⥧": "⥧",
+ "⥋": "⥋",
+ "↲": "↲",
+ "≤": "≤",
+ "←": "←",
+ "↢": "↢",
+ "↽": "↽",
+ "↼": "↼",
+ "⇇": "⇇",
+ "↔": "↔",
+ "⇆": "⇆",
+ "⇋": "⇋",
+ "↭": "↭",
+ "⋋": "⋋",
+ "⋚": "⋚",
+ "≤": "≤",
+ "≦": "≦",
+ "⩽": "⩽",
+ "⩽": "⩽",
+ "⪨": "⪨",
+ "⩿": "⩿",
+ "⪁": "⪁",
+ "⪃": "⪃",
+ "⋚︀": "⋚︀",
+ "⪓": "⪓",
+ "⪅": "⪅",
+ "⋖": "⋖",
+ "⋚": "⋚",
+ "⪋": "⪋",
+ "≶": "≶",
+ "≲": "≲",
+ "⥼": "⥼",
+ "⌊": "⌊",
+ "𝔩": "𝔩",
+ "≶": "≶",
+ "⪑": "⪑",
+ "↽": "↽",
+ "↼": "↼",
+ "⥪": "⥪",
+ "▄": "▄",
+ "љ": "љ",
+ "≪": "≪",
+ "⇇": "⇇",
+ "⌞": "⌞",
+ "⥫": "⥫",
+ "◺": "◺",
+ "ŀ": "ŀ",
+ "⎰": "⎰",
+ "⎰": "⎰",
+ "≨": "≨",
+ "⪉": "⪉",
+ "⪉": "⪉",
+ "⪇": "⪇",
+ "⪇": "⪇",
+ "≨": "≨",
+ "⋦": "⋦",
+ "⟬": "⟬",
+ "⇽": "⇽",
+ "⟦": "⟦",
+ "⟵": "⟵",
+ "⟷": "⟷",
+ "⟼": "⟼",
+ "⟶": "⟶",
+ "↫": "↫",
+ "↬": "↬",
+ "⦅": "⦅",
+ "𝕝": "𝕝",
+ "⨭": "⨭",
+ "⨴": "⨴",
+ "∗": "∗",
+ "_": "_",
+ "◊": "◊",
+ "◊": "◊",
+ "⧫": "⧫",
+ "(": "(",
+ "⦓": "⦓",
+ "⇆": "⇆",
+ "⌟": "⌟",
+ "⇋": "⇋",
+ "⥭": "⥭",
+ "‎": "",
+ "⊿": "⊿",
+ "‹": "‹",
+ "𝓁": "𝓁",
+ "↰": "↰",
+ "≲": "≲",
+ "⪍": "⪍",
+ "⪏": "⪏",
+ "[": "[",
+ "‘": "‘",
+ "‚": "‚",
+ "ł": "ł",
+ "<": "<",
+ "⪦": "⪦",
+ "⩹": "⩹",
+ "⋖": "⋖",
+ "⋋": "⋋",
+ "⋉": "⋉",
+ "⥶": "⥶",
+ "⩻": "⩻",
+ "⦖": "⦖",
+ "◃": "◃",
+ "⊴": "⊴",
+ "◂": "◂",
+ "⥊": "⥊",
+ "⥦": "⥦",
+ "≨︀": "≨︀",
+ "≨︀": "≨︀",
+ "∺": "∺",
+ "¯": "¯",
+ "♂": "♂",
+ "✠": "✠",
+ "✠": "✠",
+ "↦": "↦",
+ "↦": "↦",
+ "↧": "↧",
+ "↤": "↤",
+ "↥": "↥",
+ "▮": "▮",
+ "⨩": "⨩",
+ "м": "м",
+ "—": "—",
+ "∡": "∡",
+ "𝔪": "𝔪",
+ "℧": "℧",
+ "µ": "µ",
+ "∣": "∣",
+ "*": "*",
+ "⫰": "⫰",
+ "·": "·",
+ "−": "−",
+ "⊟": "⊟",
+ "∸": "∸",
+ "⨪": "⨪",
+ "⫛": "⫛",
+ "…": "…",
+ "∓": "∓",
+ "⊧": "⊧",
+ "𝕞": "𝕞",
+ "∓": "∓",
+ "𝓂": "𝓂",
+ "∾": "∾",
+ "μ": "μ",
+ "⊸": "⊸",
+ "⊸": "⊸",
+ "⋙̸": "⋙̸",
+ "≫⃒": "≫⃒",
+ "≫̸": "≫̸",
+ "⇍": "⇍",
+ "⇎": "⇎",
+ "⋘̸": "⋘̸",
+ "≪⃒": "≪⃒",
+ "≪̸": "≪̸",
+ "⇏": "⇏",
+ "⊯": "⊯",
+ "⊮": "⊮",
+ "∇": "∇",
+ "ń": "ń",
+ "∠⃒": "∠⃒",
+ "≉": "≉",
+ "⩰̸": "⩰̸",
+ "≋̸": "≋̸",
+ "ʼn": "ʼn",
+ "≉": "≉",
+ "♮": "♮",
+ "♮": "♮",
+ "ℕ": "ℕ",
+ " ": " ",
+ "≎̸": "≎̸",
+ "≏̸": "≏̸",
+ "⩃": "⩃",
+ "ň": "ň",
+ "ņ": "ņ",
+ "≇": "≇",
+ "⩭̸": "⩭̸",
+ "⩂": "⩂",
+ "н": "н",
+ "–": "–",
+ "≠": "≠",
+ "⇗": "⇗",
+ "⤤": "⤤",
+ "↗": "↗",
+ "↗": "↗",
+ "≐̸": "≐̸",
+ "≢": "≢",
+ "⤨": "⤨",
+ "≂̸": "≂̸",
+ "∄": "∄",
+ "∄": "∄",
+ "𝔫": "𝔫",
+ "≧̸": "≧̸",
+ "≱": "≱",
+ "≱": "≱",
+ "≧̸": "≧̸",
+ "⩾̸": "⩾̸",
+ "⩾̸": "⩾̸",
+ "≵": "≵",
+ "≯": "≯",
+ "≯": "≯",
+ "⇎": "⇎",
+ "↮": "↮",
+ "⫲": "⫲",
+ "∋": "∋",
+ "⋼": "⋼",
+ "⋺": "⋺",
+ "∋": "∋",
+ "њ": "њ",
+ "⇍": "⇍",
+ "≦̸": "≦̸",
+ "↚": "↚",
+ "‥": "‥",
+ "≰": "≰",
+ "↚": "↚",
+ "↮": "↮",
+ "≰": "≰",
+ "≦̸": "≦̸",
+ "⩽̸": "⩽̸",
+ "⩽̸": "⩽̸",
+ "≮": "≮",
+ "≴": "≴",
+ "≮": "≮",
+ "⋪": "⋪",
+ "⋬": "⋬",
+ "∤": "∤",
+ "𝕟": "𝕟",
+ "¬": "¬",
+ "∉": "∉",
+ "⋹̸": "⋹̸",
+ "⋵̸": "⋵̸",
+ "∉": "∉",
+ "⋷": "⋷",
+ "⋶": "⋶",
+ "∌": "∌",
+ "∌": "∌",
+ "⋾": "⋾",
+ "⋽": "⋽",
+ "∦": "∦",
+ "∦": "∦",
+ "⫽⃥": "⫽⃥",
+ "∂̸": "∂̸",
+ "⨔": "⨔",
+ "⊀": "⊀",
+ "⋠": "⋠",
+ "⪯̸": "⪯̸",
+ "⊀": "⊀",
+ "⪯̸": "⪯̸",
+ "⇏": "⇏",
+ "↛": "↛",
+ "⤳̸": "⤳̸",
+ "↝̸": "↝̸",
+ "↛": "↛",
+ "⋫": "⋫",
+ "⋭": "⋭",
+ "⊁": "⊁",
+ "⋡": "⋡",
+ "⪰̸": "⪰̸",
+ "𝓃": "𝓃",
+ "∤": "∤",
+ "∦": "∦",
+ "≁": "≁",
+ "≄": "≄",
+ "≄": "≄",
+ "∤": "∤",
+ "∦": "∦",
+ "⋢": "⋢",
+ "⋣": "⋣",
+ "⊄": "⊄",
+ "⫅̸": "⫅̸",
+ "⊈": "⊈",
+ "⊂⃒": "⊂⃒",
+ "⊈": "⊈",
+ "⫅̸": "⫅̸",
+ "⊁": "⊁",
+ "⪰̸": "⪰̸",
+ "⊅": "⊅",
+ "⫆̸": "⫆̸",
+ "⊉": "⊉",
+ "⊃⃒": "⊃⃒",
+ "⊉": "⊉",
+ "⫆̸": "⫆̸",
+ "≹": "≹",
+ "ñ": "ñ",
+ "≸": "≸",
+ "⋪": "⋪",
+ "⋬": "⋬",
+ "⋫": "⋫",
+ "⋭": "⋭",
+ "ν": "ν",
+ "#": "#",
+ "№": "№",
+ " ": " ",
+ "⊭": "⊭",
+ "⤄": "⤄",
+ "≍⃒": "≍⃒",
+ "⊬": "⊬",
+ "≥⃒": "≥⃒",
+ ">⃒": ">⃒",
+ "⧞": "⧞",
+ "⤂": "⤂",
+ "≤⃒": "≤⃒",
+ "<⃒": "<⃒",
+ "⊴⃒": "⊴⃒",
+ "⤃": "⤃",
+ "⊵⃒": "⊵⃒",
+ "∼⃒": "∼⃒",
+ "⇖": "⇖",
+ "⤣": "⤣",
+ "↖": "↖",
+ "↖": "↖",
+ "⤧": "⤧",
+ "Ⓢ": "Ⓢ",
+ "ó": "ó",
+ "⊛": "⊛",
+ "⊚": "⊚",
+ "ô": "ô",
+ "о": "о",
+ "⊝": "⊝",
+ "ő": "ő",
+ "⨸": "⨸",
+ "⊙": "⊙",
+ "⦼": "⦼",
+ "œ": "œ",
+ "⦿": "⦿",
+ "𝔬": "𝔬",
+ "˛": "˛",
+ "ò": "ò",
+ "⧁": "⧁",
+ "⦵": "⦵",
+ "Ω": "Ω",
+ "∮": "∮",
+ "↺": "↺",
+ "⦾": "⦾",
+ "⦻": "⦻",
+ "‾": "‾",
+ "⧀": "⧀",
+ "ō": "ō",
+ "ω": "ω",
+ "ο": "ο",
+ "⦶": "⦶",
+ "⊖": "⊖",
+ "𝕠": "𝕠",
+ "⦷": "⦷",
+ "⦹": "⦹",
+ "⊕": "⊕",
+ "∨": "∨",
+ "↻": "↻",
+ "⩝": "⩝",
+ "ℴ": "ℴ",
+ "ℴ": "ℴ",
+ "ª": "ª",
+ "º": "º",
+ "⊶": "⊶",
+ "⩖": "⩖",
+ "⩗": "⩗",
+ "⩛": "⩛",
+ "ℴ": "ℴ",
+ "ø": "ø",
+ "⊘": "⊘",
+ "õ": "õ",
+ "⊗": "⊗",
+ "⨶": "⨶",
+ "ö": "ö",
+ "⌽": "⌽",
+ "∥": "∥",
+ "¶": "¶",
+ "∥": "∥",
+ "⫳": "⫳",
+ "⫽": "⫽",
+ "∂": "∂",
+ "п": "п",
+ "%": "%",
+ ".": ".",
+ "‰": "‰",
+ "⊥": "⊥",
+ "‱": "‱",
+ "𝔭": "𝔭",
+ "φ": "φ",
+ "ϕ": "ϕ",
+ "ℳ": "ℳ",
+ "☎": "☎",
+ "π": "π",
+ "⋔": "⋔",
+ "ϖ": "ϖ",
+ "ℏ": "ℏ",
+ "ℎ": "ℎ",
+ "ℏ": "ℏ",
+ "+": "+",
+ "⨣": "⨣",
+ "⊞": "⊞",
+ "⨢": "⨢",
+ "∔": "∔",
+ "⨥": "⨥",
+ "⩲": "⩲",
+ "±": "±",
+ "⨦": "⨦",
+ "⨧": "⨧",
+ "±": "±",
+ "⨕": "⨕",
+ "𝕡": "𝕡",
+ "£": "£",
+ "≺": "≺",
+ "⪳": "⪳",
+ "⪷": "⪷",
+ "≼": "≼",
+ "⪯": "⪯",
+ "≺": "≺",
+ "⪷": "⪷",
+ "≼": "≼",
+ "⪯": "⪯",
+ "⪹": "⪹",
+ "⪵": "⪵",
+ "⋨": "⋨",
+ "≾": "≾",
+ "′": "′",
+ "ℙ": "ℙ",
+ "⪵": "⪵",
+ "⪹": "⪹",
+ "⋨": "⋨",
+ "∏": "∏",
+ "⌮": "⌮",
+ "⌒": "⌒",
+ "⌓": "⌓",
+ "∝": "∝",
+ "∝": "∝",
+ "≾": "≾",
+ "⊰": "⊰",
+ "𝓅": "𝓅",
+ "ψ": "ψ",
+ " ": " ",
+ "𝔮": "𝔮",
+ "⨌": "⨌",
+ "𝕢": "𝕢",
+ "⁗": "⁗",
+ "𝓆": "𝓆",
+ "ℍ": "ℍ",
+ "⨖": "⨖",
+ "?": "?",
+ "≟": "≟",
+ """: "\"",
+ "⇛": "⇛",
+ "⇒": "⇒",
+ "⤜": "⤜",
+ "⤏": "⤏",
+ "⥤": "⥤",
+ "∽̱": "∽̱",
+ "ŕ": "ŕ",
+ "√": "√",
+ "⦳": "⦳",
+ "⟩": "⟩",
+ "⦒": "⦒",
+ "⦥": "⦥",
+ "⟩": "⟩",
+ "»": "»",
+ "→": "→",
+ "⥵": "⥵",
+ "⇥": "⇥",
+ "⤠": "⤠",
+ "⤳": "⤳",
+ "⤞": "⤞",
+ "↪": "↪",
+ "↬": "↬",
+ "⥅": "⥅",
+ "⥴": "⥴",
+ "↣": "↣",
+ "↝": "↝",
+ "⤚": "⤚",
+ "∶": "∶",
+ "ℚ": "ℚ",
+ "⤍": "⤍",
+ "❳": "❳",
+ "}": "}",
+ "]": "]",
+ "⦌": "⦌",
+ "⦎": "⦎",
+ "⦐": "⦐",
+ "ř": "ř",
+ "ŗ": "ŗ",
+ "⌉": "⌉",
+ "}": "}",
+ "р": "р",
+ "⤷": "⤷",
+ "⥩": "⥩",
+ "”": "”",
+ "”": "”",
+ "↳": "↳",
+ "ℜ": "ℜ",
+ "ℛ": "ℛ",
+ "ℜ": "ℜ",
+ "ℝ": "ℝ",
+ "▭": "▭",
+ "®": "®",
+ "⥽": "⥽",
+ "⌋": "⌋",
+ "𝔯": "𝔯",
+ "⇁": "⇁",
+ "⇀": "⇀",
+ "⥬": "⥬",
+ "ρ": "ρ",
+ "ϱ": "ϱ",
+ "→": "→",
+ "↣": "↣",
+ "⇁": "⇁",
+ "⇀": "⇀",
+ "⇄": "⇄",
+ "⇌": "⇌",
+ "⇉": "⇉",
+ "↝": "↝",
+ "⋌": "⋌",
+ "˚": "˚",
+ "≓": "≓",
+ "⇄": "⇄",
+ "⇌": "⇌",
+ "‏": "",
+ "⎱": "⎱",
+ "⎱": "⎱",
+ "⫮": "⫮",
+ "⟭": "⟭",
+ "⇾": "⇾",
+ "⟧": "⟧",
+ "⦆": "⦆",
+ "𝕣": "𝕣",
+ "⨮": "⨮",
+ "⨵": "⨵",
+ ")": ")",
+ "⦔": "⦔",
+ "⨒": "⨒",
+ "⇉": "⇉",
+ "›": "›",
+ "𝓇": "𝓇",
+ "↱": "↱",
+ "]": "]",
+ "’": "’",
+ "’": "’",
+ "⋌": "⋌",
+ "⋊": "⋊",
+ "▹": "▹",
+ "⊵": "⊵",
+ "▸": "▸",
+ "⧎": "⧎",
+ "⥨": "⥨",
+ "℞": "℞",
+ "ś": "ś",
+ "‚": "‚",
+ "≻": "≻",
+ "⪴": "⪴",
+ "⪸": "⪸",
+ "š": "š",
+ "≽": "≽",
+ "⪰": "⪰",
+ "ş": "ş",
+ "ŝ": "ŝ",
+ "⪶": "⪶",
+ "⪺": "⪺",
+ "⋩": "⋩",
+ "⨓": "⨓",
+ "≿": "≿",
+ "с": "с",
+ "⋅": "⋅",
+ "⊡": "⊡",
+ "⩦": "⩦",
+ "⇘": "⇘",
+ "⤥": "⤥",
+ "↘": "↘",
+ "↘": "↘",
+ "§": "§",
+ ";": ";",
+ "⤩": "⤩",
+ "∖": "∖",
+ "∖": "∖",
+ "✶": "✶",
+ "𝔰": "𝔰",
+ "⌢": "⌢",
+ "♯": "♯",
+ "щ": "щ",
+ "ш": "ш",
+ "∣": "∣",
+ "∥": "∥",
+ "­": "",
+ "σ": "σ",
+ "ς": "ς",
+ "ς": "ς",
+ "∼": "∼",
+ "⩪": "⩪",
+ "≃": "≃",
+ "≃": "≃",
+ "⪞": "⪞",
+ "⪠": "⪠",
+ "⪝": "⪝",
+ "⪟": "⪟",
+ "≆": "≆",
+ "⨤": "⨤",
+ "⥲": "⥲",
+ "←": "←",
+ "∖": "∖",
+ "⨳": "⨳",
+ "⧤": "⧤",
+ "∣": "∣",
+ "⌣": "⌣",
+ "⪪": "⪪",
+ "⪬": "⪬",
+ "⪬︀": "⪬︀",
+ "ь": "ь",
+ "/": "/",
+ "⧄": "⧄",
+ "⌿": "⌿",
+ "𝕤": "𝕤",
+ "♠": "♠",
+ "♠": "♠",
+ "∥": "∥",
+ "⊓": "⊓",
+ "⊓︀": "⊓︀",
+ "⊔": "⊔",
+ "⊔︀": "⊔︀",
+ "⊏": "⊏",
+ "⊑": "⊑",
+ "⊏": "⊏",
+ "⊑": "⊑",
+ "⊐": "⊐",
+ "⊒": "⊒",
+ "⊐": "⊐",
+ "⊒": "⊒",
+ "□": "□",
+ "□": "□",
+ "▪": "▪",
+ "▪": "▪",
+ "→": "→",
+ "𝓈": "𝓈",
+ "∖": "∖",
+ "⌣": "⌣",
+ "⋆": "⋆",
+ "☆": "☆",
+ "★": "★",
+ "ϵ": "ϵ",
+ "ϕ": "ϕ",
+ "¯": "¯",
+ "⊂": "⊂",
+ "⫅": "⫅",
+ "⪽": "⪽",
+ "⊆": "⊆",
+ "⫃": "⫃",
+ "⫁": "⫁",
+ "⫋": "⫋",
+ "⊊": "⊊",
+ "⪿": "⪿",
+ "⥹": "⥹",
+ "⊂": "⊂",
+ "⊆": "⊆",
+ "⫅": "⫅",
+ "⊊": "⊊",
+ "⫋": "⫋",
+ "⫇": "⫇",
+ "⫕": "⫕",
+ "⫓": "⫓",
+ "≻": "≻",
+ "⪸": "⪸",
+ "≽": "≽",
+ "⪰": "⪰",
+ "⪺": "⪺",
+ "⪶": "⪶",
+ "⋩": "⋩",
+ "≿": "≿",
+ "∑": "∑",
+ "♪": "♪",
+ "¹": "¹",
+ "²": "²",
+ "³": "³",
+ "⊃": "⊃",
+ "⫆": "⫆",
+ "⪾": "⪾",
+ "⫘": "⫘",
+ "⊇": "⊇",
+ "⫄": "⫄",
+ "⟉": "⟉",
+ "⫗": "⫗",
+ "⥻": "⥻",
+ "⫂": "⫂",
+ "⫌": "⫌",
+ "⊋": "⊋",
+ "⫀": "⫀",
+ "⊃": "⊃",
+ "⊇": "⊇",
+ "⫆": "⫆",
+ "⊋": "⊋",
+ "⫌": "⫌",
+ "⫈": "⫈",
+ "⫔": "⫔",
+ "⫖": "⫖",
+ "⇙": "⇙",
+ "⤦": "⤦",
+ "↙": "↙",
+ "↙": "↙",
+ "⤪": "⤪",
+ "ß": "ß",
+ "⌖": "⌖",
+ "τ": "τ",
+ "⎴": "⎴",
+ "ť": "ť",
+ "ţ": "ţ",
+ "т": "т",
+ "⃛": "⃛",
+ "⌕": "⌕",
+ "𝔱": "𝔱",
+ "∴": "∴",
+ "∴": "∴",
+ "θ": "θ",
+ "ϑ": "ϑ",
+ "ϑ": "ϑ",
+ "≈": "≈",
+ "∼": "∼",
+ " ": " ",
+ "≈": "≈",
+ "∼": "∼",
+ "þ": "þ",
+ "˜": "˜",
+ "×": "×",
+ "⊠": "⊠",
+ "⨱": "⨱",
+ "⨰": "⨰",
+ "∭": "∭",
+ "⤨": "⤨",
+ "⊤": "⊤",
+ "⌶": "⌶",
+ "⫱": "⫱",
+ "𝕥": "𝕥",
+ "⫚": "⫚",
+ "⤩": "⤩",
+ "‴": "‴",
+ "™": "™",
+ "▵": "▵",
+ "▿": "▿",
+ "◃": "◃",
+ "⊴": "⊴",
+ "≜": "≜",
+ "▹": "▹",
+ "⊵": "⊵",
+ "◬": "◬",
+ "≜": "≜",
+ "⨺": "⨺",
+ "⨹": "⨹",
+ "⧍": "⧍",
+ "⨻": "⨻",
+ "⏢": "⏢",
+ "𝓉": "𝓉",
+ "ц": "ц",
+ "ћ": "ћ",
+ "ŧ": "ŧ",
+ "≬": "≬",
+ "↞": "↞",
+ "↠": "↠",
+ "⇑": "⇑",
+ "⥣": "⥣",
+ "ú": "ú",
+ "↑": "↑",
+ "ў": "ў",
+ "ŭ": "ŭ",
+ "û": "û",
+ "у": "у",
+ "⇅": "⇅",
+ "ű": "ű",
+ "⥮": "⥮",
+ "⥾": "⥾",
+ "𝔲": "𝔲",
+ "ù": "ù",
+ "↿": "↿",
+ "↾": "↾",
+ "▀": "▀",
+ "⌜": "⌜",
+ "⌜": "⌜",
+ "⌏": "⌏",
+ "◸": "◸",
+ "ū": "ū",
+ "¨": "¨",
+ "ų": "ų",
+ "𝕦": "𝕦",
+ "↑": "↑",
+ "↕": "↕",
+ "↿": "↿",
+ "↾": "↾",
+ "⊎": "⊎",
+ "υ": "υ",
+ "ϒ": "ϒ",
+ "υ": "υ",
+ "⇈": "⇈",
+ "⌝": "⌝",
+ "⌝": "⌝",
+ "⌎": "⌎",
+ "ů": "ů",
+ "◹": "◹",
+ "𝓊": "𝓊",
+ "⋰": "⋰",
+ "ũ": "ũ",
+ "▵": "▵",
+ "▴": "▴",
+ "⇈": "⇈",
+ "ü": "ü",
+ "⦧": "⦧",
+ "⇕": "⇕",
+ "⫨": "⫨",
+ "⫩": "⫩",
+ "⊨": "⊨",
+ "⦜": "⦜",
+ "ϵ": "ϵ",
+ "ϰ": "ϰ",
+ "∅": "∅",
+ "ϕ": "ϕ",
+ "ϖ": "ϖ",
+ "∝": "∝",
+ "↕": "↕",
+ "ϱ": "ϱ",
+ "ς": "ς",
+ "⊊︀": "⊊︀",
+ "⫋︀": "⫋︀",
+ "⊋︀": "⊋︀",
+ "⫌︀": "⫌︀",
+ "ϑ": "ϑ",
+ "⊲": "⊲",
+ "⊳": "⊳",
+ "в": "в",
+ "⊢": "⊢",
+ "∨": "∨",
+ "⊻": "⊻",
+ "≚": "≚",
+ "⋮": "⋮",
+ "|": "|",
+ "|": "|",
+ "𝔳": "𝔳",
+ "⊲": "⊲",
+ "⊂⃒": "⊂⃒",
+ "⊃⃒": "⊃⃒",
+ "𝕧": "𝕧",
+ "∝": "∝",
+ "⊳": "⊳",
+ "𝓋": "𝓋",
+ "⫋︀": "⫋︀",
+ "⊊︀": "⊊︀",
+ "⫌︀": "⫌︀",
+ "⊋︀": "⊋︀",
+ "⦚": "⦚",
+ "ŵ": "ŵ",
+ "⩟": "⩟",
+ "∧": "∧",
+ "≙": "≙",
+ "℘": "℘",
+ "𝔴": "𝔴",
+ "𝕨": "𝕨",
+ "℘": "℘",
+ "≀": "≀",
+ "≀": "≀",
+ "𝓌": "𝓌",
+ "⋂": "⋂",
+ "◯": "◯",
+ "⋃": "⋃",
+ "▽": "▽",
+ "𝔵": "𝔵",
+ "⟺": "⟺",
+ "⟷": "⟷",
+ "ξ": "ξ",
+ "⟸": "⟸",
+ "⟵": "⟵",
+ "⟼": "⟼",
+ "⋻": "⋻",
+ "⨀": "⨀",
+ "𝕩": "𝕩",
+ "⨁": "⨁",
+ "⨂": "⨂",
+ "⟹": "⟹",
+ "⟶": "⟶",
+ "𝓍": "𝓍",
+ "⨆": "⨆",
+ "⨄": "⨄",
+ "△": "△",
+ "⋁": "⋁",
+ "⋀": "⋀",
+ "ý": "ý",
+ "я": "я",
+ "ŷ": "ŷ",
+ "ы": "ы",
+ "¥": "¥",
+ "𝔶": "𝔶",
+ "ї": "ї",
+ "𝕪": "𝕪",
+ "𝓎": "𝓎",
+ "ю": "ю",
+ "ÿ": "ÿ",
+ "ź": "ź",
+ "ž": "ž",
+ "з": "з",
+ "ż": "ż",
+ "ℨ": "ℨ",
+ "ζ": "ζ",
+ "𝔷": "𝔷",
+ "ж": "ж",
+ "⇝": "⇝",
+ "𝕫": "𝕫",
+ "𝓏": "𝓏",
+ "‍": "",
+ "‌": ""
+};
diff --git a/pkgs/markdown/lib/src/ast.dart b/pkgs/markdown/lib/src/ast.dart
new file mode 100644
index 0000000..e5530cb
--- /dev/null
+++ b/pkgs/markdown/lib/src/ast.dart
@@ -0,0 +1,113 @@
+// 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.
+
+typedef Resolver = Node? Function(String name, [String? title]);
+
+/// Base class for any AST item.
+///
+/// Roughly corresponds to Node in the DOM. Will be either an Element or Text.
+abstract class Node {
+ void accept(NodeVisitor visitor);
+
+ String get textContent;
+}
+
+/// A named tag that can contain other nodes.
+class Element implements Node {
+ final String tag;
+ final List<Node>? children;
+ final Map<String, String> attributes;
+ String? generatedId;
+ String? footnoteLabel;
+
+ /// Instantiates a [tag] Element with [children].
+ Element(this.tag, this.children) : attributes = {};
+
+ /// Instantiates an empty, self-closing [tag] Element.
+ Element.empty(this.tag)
+ : children = null,
+ attributes = {};
+
+ /// Instantiates a [tag] Element with no [children].
+ Element.withTag(this.tag)
+ : children = const [],
+ attributes = {};
+
+ /// Instantiates a [tag] Element with a single Text child.
+ Element.text(this.tag, String text)
+ : children = [Text(text)],
+ attributes = {};
+
+ /// Whether this element is self-closing.
+ bool get isEmpty => children == null;
+
+ @override
+ void accept(NodeVisitor visitor) {
+ if (visitor.visitElementBefore(this)) {
+ if (children != null) {
+ for (final child in children!) {
+ child.accept(visitor);
+ }
+ }
+ visitor.visitElementAfter(this);
+ }
+ }
+
+ @override
+ String get textContent {
+ final children = this.children;
+ return children == null
+ ? ''
+ : children.map((child) => child.textContent).join();
+ }
+}
+
+/// A plain text element.
+class Text implements Node {
+ final String text;
+
+ Text(this.text);
+
+ @override
+ void accept(NodeVisitor visitor) => visitor.visitText(this);
+
+ @override
+ String get textContent => text;
+}
+
+/// Inline content that has not been parsed into inline nodes (strong, links,
+/// etc).
+///
+/// These placeholder nodes should only remain in place while the block nodes
+/// of a document are still being parsed, in order to gather all reference link
+/// definitions.
+class UnparsedContent implements Node {
+ @override
+ final String textContent;
+
+ UnparsedContent(this.textContent);
+
+ @override
+ void accept(NodeVisitor visitor) {}
+}
+
+/// Visitor pattern for the AST.
+///
+/// Renderers or other AST transformers should implement this.
+abstract class NodeVisitor {
+ /// Called when a Text node has been reached.
+ void visitText(Text text);
+
+ /// Called when an Element has been reached, before its children have been
+ /// visited.
+ ///
+ /// Returns `false` to skip its children.
+ bool visitElementBefore(Element element);
+
+ /// Called when an Element has been reached, after its children have been
+ /// visited.
+ ///
+ /// Will not be called if [visitElementBefore] returns `false`.
+ void visitElementAfter(Element element);
+}
diff --git a/pkgs/markdown/lib/src/block_parser.dart b/pkgs/markdown/lib/src/block_parser.dart
new file mode 100644
index 0000000..efe2352
--- /dev/null
+++ b/pkgs/markdown/lib/src/block_parser.dart
@@ -0,0 +1,216 @@
+// 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 'ast.dart';
+import 'block_syntaxes/block_syntax.dart';
+import 'block_syntaxes/blockquote_syntax.dart';
+import 'block_syntaxes/code_block_syntax.dart';
+import 'block_syntaxes/dummy_block_syntax.dart';
+import 'block_syntaxes/empty_block_syntax.dart';
+import 'block_syntaxes/header_syntax.dart';
+import 'block_syntaxes/horizontal_rule_syntax.dart';
+import 'block_syntaxes/html_block_syntax.dart';
+import 'block_syntaxes/link_reference_definition_syntax.dart';
+import 'block_syntaxes/ordered_list_syntax.dart';
+import 'block_syntaxes/paragraph_syntax.dart';
+import 'block_syntaxes/setext_header_syntax.dart';
+import 'block_syntaxes/unordered_list_syntax.dart';
+import 'document.dart';
+import 'line.dart';
+
+/// Maintains the internal state needed to parse a series of lines into blocks
+/// of Markdown suitable for further inline parsing.
+class BlockParser {
+ final List<Line> lines;
+
+ /// The Markdown document this parser is parsing.
+ final Document document;
+
+ /// The enabled block syntaxes.
+ ///
+ /// To turn a series of lines into blocks, each of these will be tried in
+ /// turn. Order matters here.
+ final List<BlockSyntax> blockSyntaxes = [];
+
+ /// Index of the current line.
+ int _pos = 0;
+
+ /// Starting line of the last unconsumed content.
+ int _start = 0;
+
+ /// The lines from [_start] to [_pos] (inclusive), it works as a buffer for
+ /// some blocks, for example:
+ /// When the [ParagraphSyntax] parsing process is interrupted by the
+ /// [SetextHeaderSyntax], so this structure is not a paragraph but a setext
+ /// heading, then the [ParagraphSyntax.parse] does not have to retreat the
+ /// reading position, it only needs to return `null`, the [SetextHeaderSyntax]
+ /// will pick up the lines in [linesToConsume].
+ List<Line> get linesToConsume => lines.getRange(_start, _pos + 1).toList();
+
+ /// Whether the parser has encountered a blank line between two block-level
+ /// elements.
+ bool encounteredBlankLine = false;
+
+ /// The collection of built-in block parsers.
+ final List<BlockSyntax> standardBlockSyntaxes = [
+ const EmptyBlockSyntax(),
+ const HtmlBlockSyntax(),
+ const SetextHeaderSyntax(),
+ const HeaderSyntax(),
+ const CodeBlockSyntax(),
+ const BlockquoteSyntax(),
+ const HorizontalRuleSyntax(),
+ const UnorderedListSyntax(),
+ const OrderedListSyntax(),
+ const LinkReferenceDefinitionSyntax(),
+ const ParagraphSyntax()
+ ];
+
+ BlockParser(this.lines, this.document) {
+ blockSyntaxes.addAll(document.blockSyntaxes);
+
+ if (document.withDefaultBlockSyntaxes) {
+ blockSyntaxes.addAll(standardBlockSyntaxes);
+ } else {
+ blockSyntaxes.add(const DummyBlockSyntax());
+ }
+ }
+
+ /// Gets the current line.
+ Line get current => lines[_pos];
+
+ /// Gets the line after the current one or `null` if there is none.
+ Line? get next {
+ // Don't read past the end.
+ if (_pos >= lines.length - 1) return null;
+ return lines[_pos + 1];
+ }
+
+ /// Gets the line that is [linesAhead] lines ahead of the current one, or
+ /// `null` if there is none.
+ ///
+ /// `peek(0)` is equivalent to [current].
+ ///
+ /// `peek(1)` is equivalent to [next].
+ Line? peek(int linesAhead) {
+ if (linesAhead < 0) {
+ throw ArgumentError('Invalid linesAhead: $linesAhead; must be >= 0.');
+ }
+ // Don't read past the end.
+ if (_pos >= lines.length - linesAhead) return null;
+ return lines[_pos + linesAhead];
+ }
+
+ /// Advances the reading position by one line.
+ void advance() {
+ _pos++;
+ }
+
+ /// Retreats the reading position by one line.
+ void retreat() {
+ _pos--;
+ }
+
+ /// Retreats the reading position by [count] lines.
+ void retreatBy(int count) {
+ _pos -= count;
+ }
+
+ bool get isDone => _pos >= lines.length;
+
+ /// Gets whether or not the current line matches the given pattern.
+ bool matches(RegExp regex) {
+ if (isDone) return false;
+ return regex.hasMatch(current.content);
+ }
+
+ /// Gets whether or not the next line matches the given pattern.
+ bool matchesNext(RegExp regex) {
+ if (next == null) return false;
+ return regex.hasMatch(next!.content);
+ }
+
+ /// The parent [BlockSyntax] when it is running inside a nested syntax.
+ BlockSyntax? get parentSyntax => _parentSyntax;
+ BlockSyntax? _parentSyntax;
+
+ /// Whether the [SetextHeadingSyntax] is disabled temporarily.
+ bool get setextHeadingDisabled => _setextHeadingDisabled;
+ bool _setextHeadingDisabled = false;
+
+ /// The [BlockSyntax] which is running now.
+ /// The value is `null` until we found the first matched [BlockSyntax].
+ BlockSyntax? get currentSyntax => _currentSyntax;
+ BlockSyntax? _currentSyntax;
+
+ /// The [BlockSyntax] which is running before the [currentSyntax].
+ BlockSyntax? get previousSyntax => _previousSyntax;
+ BlockSyntax? _previousSyntax;
+
+ List<Node> parseLines({
+ BlockSyntax? parentSyntax,
+ bool disabledSetextHeading = false,
+ }) {
+ _parentSyntax = parentSyntax;
+ _setextHeadingDisabled = disabledSetextHeading;
+
+ final blocks = <Node>[];
+
+ // If the `_pos` does not change before and after `parse()`, never try to
+ // parse the line at `_pos` with the same syntax again.
+ // For example the `TableSyntax` might not advance the `_pos` in `parse`
+ // method, beause of the header row does not match the delimiter row in the
+ // number of cells, which makes a table like structure not be recognized.
+ BlockSyntax? neverMatch;
+
+ var iterationsWithoutProgress = 0;
+ while (!isDone) {
+ final positionBefore = _pos;
+ for (final syntax in blockSyntaxes) {
+ if (neverMatch == syntax) {
+ continue;
+ }
+
+ if (syntax.canParse(this)) {
+ _previousSyntax = _currentSyntax;
+ _currentSyntax = syntax;
+ final block = syntax.parse(this);
+ if (block != null) {
+ blocks.add(block);
+ }
+ neverMatch = _pos != positionBefore ? null : syntax;
+
+ if (block != null ||
+ syntax is EmptyBlockSyntax ||
+ syntax is LinkReferenceDefinitionSyntax) {
+ _start = _pos;
+ }
+
+ break;
+ }
+ }
+ // Count the number of iterations without progress.
+ // This ensures that we don't have an infinite loop. And if we have an
+ // infinite loop, it's easier to gracefully recover from an error, than
+ // it is to discover an kill an isolate that's stuck in an infinite loop.
+ // Technically, it should be perfectly safe to remove this check
+ // But as it's possible to inject custom BlockSyntax implementations and
+ // combine existing ones, it is hard to promise that no combination can't
+ // trigger an infinite loop
+ if (positionBefore == _pos) {
+ iterationsWithoutProgress++;
+ if (iterationsWithoutProgress > 2) {
+ // If this happens we throw an error to avoid having the parser
+ // running in an infinite loop. An error is easier to handle.
+ // If you see this error in production please file a bug!
+ throw AssertionError('BlockParser.parseLines is not advancing');
+ }
+ } else {
+ iterationsWithoutProgress = 0;
+ }
+ }
+
+ return blocks;
+ }
+}
diff --git a/pkgs/markdown/lib/src/block_syntaxes/alert_block_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/alert_block_syntax.dart
new file mode 100644
index 0000000..0f4f5b1
--- /dev/null
+++ b/pkgs/markdown/lib/src/block_syntaxes/alert_block_syntax.dart
@@ -0,0 +1,109 @@
+// Copyright (c) 2023, 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 '../ast.dart';
+import '../block_parser.dart';
+import '../line.dart';
+import '../patterns.dart';
+import 'block_syntax.dart';
+import 'code_block_syntax.dart';
+import 'paragraph_syntax.dart';
+
+/// Parses GitHub Alerts blocks.
+///
+/// See also: https://docs.github.com/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts
+class AlertBlockSyntax extends BlockSyntax {
+ const AlertBlockSyntax();
+
+ @override
+ RegExp get pattern => alertPattern;
+
+ @override
+ bool canParse(BlockParser parser) {
+ return alertPattern.hasMatch(parser.current.content);
+ }
+
+ /// Whether this alert ends with a lazy continuation line.
+ ///
+ /// The definition of lazy continuation lines:
+ /// https://spec.commonmark.org/0.30/#lazy-continuation-line
+ static bool _lazyContinuation = false;
+ static final _contentLineRegExp = RegExp(r'>?\s?(.*)*');
+
+ @override
+ List<Line> parseChildLines(BlockParser parser) {
+ // Grab all of the lines that form the alert, stripping off the ">".
+ final childLines = <Line>[];
+ _lazyContinuation = false;
+
+ while (!parser.isDone) {
+ final lineContent = parser.current.content.trimLeft();
+ final strippedContent = lineContent.replaceFirst(RegExp(r'^>?\s*'), '');
+ final match = strippedContent.isEmpty && !lineContent.startsWith('>')
+ ? null
+ : _contentLineRegExp.firstMatch(strippedContent);
+ if (match != null) {
+ childLines.add(Line(strippedContent));
+ parser.advance();
+ _lazyContinuation = false;
+ continue;
+ }
+
+ final lastLine = childLines.isEmpty ? Line('') : childLines.last;
+
+ // A paragraph continuation is OK. This is content that cannot be parsed
+ // as any other syntax except Paragraph, and it doesn't match the bar in
+ // a Setext header.
+ // Because indented code blocks cannot interrupt paragraphs, a line
+ // matched CodeBlockSyntax is also paragraph continuation text.
+ final otherMatched =
+ parser.blockSyntaxes.firstWhere((s) => s.canParse(parser));
+ if ((otherMatched is ParagraphSyntax &&
+ !lastLine.isBlankLine &&
+ !codeFencePattern.hasMatch(lastLine.content)) ||
+ (otherMatched is CodeBlockSyntax &&
+ !indentPattern.hasMatch(lastLine.content))) {
+ childLines.add(parser.current);
+ _lazyContinuation = true;
+ parser.advance();
+ } else {
+ break;
+ }
+ }
+
+ return childLines;
+ }
+
+ @override
+ Node parse(BlockParser parser) {
+ // Parse the alert type from the first line.
+ final type =
+ pattern.firstMatch(parser.current.content)!.group(1)!.toLowerCase();
+ parser.advance();
+ final childLines = parseChildLines(parser);
+ // Recursively parse the contents of the alert.
+ final children = BlockParser(childLines, parser.document).parseLines(
+ // The setext heading underline cannot be a lazy continuation line in a
+ // block quote.
+ // https://spec.commonmark.org/0.30/#example-93
+ disabledSetextHeading: _lazyContinuation,
+ parentSyntax: this,
+ );
+
+ // Mapping the alert title text.
+ const typeTextMap = {
+ 'note': 'Note',
+ 'tip': 'Tip',
+ 'important': 'Important',
+ 'caution': 'Caution',
+ 'warning': 'Warning',
+ };
+ final titleText = typeTextMap[type]!;
+ final titleElement = Element('p', [Text(titleText)])
+ ..attributes['class'] = 'markdown-alert-title';
+ final elementClass = 'markdown-alert markdown-alert-$type';
+ return Element('div', [titleElement, ...children])
+ ..attributes['class'] = elementClass;
+ }
+}
diff --git a/pkgs/markdown/lib/src/block_syntaxes/block_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/block_syntax.dart
new file mode 100644
index 0000000..947824a
--- /dev/null
+++ b/pkgs/markdown/lib/src/block_syntaxes/block_syntax.dart
@@ -0,0 +1,64 @@
+// 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 '../ast.dart';
+import '../block_parser.dart';
+import '../line.dart';
+
+abstract class BlockSyntax {
+ const BlockSyntax();
+
+ /// Gets the regex used to identify the beginning of this block, if any.
+ RegExp get pattern;
+
+ bool canEndBlock(BlockParser parser) => true;
+
+ bool canParse(BlockParser parser) {
+ return pattern.hasMatch(parser.current.content);
+ }
+
+ Node? parse(BlockParser parser);
+
+ List<Line?> parseChildLines(BlockParser parser) {
+ // Grab all of the lines that form the block element.
+ final childLines = <Line?>[];
+
+ while (!parser.isDone) {
+ final match = pattern.firstMatch(parser.current.content);
+ if (match == null) break;
+ childLines.add(parser.current);
+ parser.advance();
+ }
+
+ return childLines;
+ }
+
+ /// Returns the block which interrupts current syntax parsing if there is one,
+ /// otherwise returns `null`.
+ ///
+ /// Make sure to check if [parser] `isDone` is `false` first.
+ BlockSyntax? interruptedBy(BlockParser parser) {
+ for (final syntax in parser.blockSyntaxes) {
+ if (syntax.canParse(parser) && syntax.canEndBlock(parser)) {
+ return syntax;
+ }
+ }
+ return null;
+ }
+
+ /// Gets whether or not [parser]'s current line should end the previous block.
+ static bool isAtBlockEnd(BlockParser parser) {
+ if (parser.isDone) return true;
+ return parser.blockSyntaxes
+ .any((s) => s.canParse(parser) && s.canEndBlock(parser));
+ }
+
+ /// Generates a valid HTML anchor from the inner text of [element].
+ static String generateAnchorHash(Element element) =>
+ element.children!.first.textContent
+ .toLowerCase()
+ .trim()
+ .replaceAll(RegExp('[^a-z0-9 _-]'), '')
+ .replaceAll(RegExp(r'\s'), '-');
+}
diff --git a/pkgs/markdown/lib/src/block_syntaxes/blockquote_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/blockquote_syntax.dart
new file mode 100644
index 0000000..afbe23a
--- /dev/null
+++ b/pkgs/markdown/lib/src/block_syntaxes/blockquote_syntax.dart
@@ -0,0 +1,99 @@
+// 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 '../ast.dart';
+import '../block_parser.dart';
+import '../charcode.dart';
+import '../line.dart';
+import '../patterns.dart';
+import '../util.dart';
+import 'block_syntax.dart';
+import 'code_block_syntax.dart';
+import 'paragraph_syntax.dart';
+
+/// Parses email-style blockquotes: `> quote`.
+class BlockquoteSyntax extends BlockSyntax {
+ @override
+ RegExp get pattern => blockquotePattern;
+
+ const BlockquoteSyntax();
+
+ /// Whether this blockquote ends with a lazy continuation line.
+ // The definition of lazy continuation lines:
+ // https://spec.commonmark.org/0.30/#lazy-continuation-line
+ static var _lazyContinuation = false;
+ @override
+ List<Line> parseChildLines(BlockParser parser) {
+ // Grab all of the lines that form the blockquote, stripping off the ">".
+ final childLines = <Line>[];
+ _lazyContinuation = false;
+
+ while (!parser.isDone) {
+ final currentLine = parser.current;
+ final match = pattern.firstMatch(parser.current.content);
+ if (match != null) {
+ // A block quote marker consists of a `>` together with an optional
+ // following space of indentation, see
+ // https://spec.commonmark.org/0.30/#block-quote-marker.
+ final markerStart = match.match.indexOf('>');
+ int markerEnd;
+ if (currentLine.content.length > 1) {
+ var hasSpace = false;
+ // Check if there is a following space if the marker is not at the end
+ // of this line.
+ if (markerStart < currentLine.content.length - 1) {
+ final nextChar = currentLine.content.codeUnitAt(markerStart + 1);
+ hasSpace = nextChar == $tab || nextChar == $space;
+ }
+ markerEnd = markerStart + (hasSpace ? 2 : 1);
+ } else {
+ markerEnd = markerStart + 1;
+ }
+ childLines.add(Line(currentLine.content.substring(markerEnd)));
+ parser.advance();
+ _lazyContinuation = false;
+ continue;
+ }
+
+ final lastLine = childLines.last;
+
+ // A paragraph continuation is OK. This is content that cannot be parsed
+ // as any other syntax except Paragraph, and it doesn't match the bar in
+ // a Setext header.
+ // Because indented code blocks cannot interrupt paragraphs, a line
+ // matched CodeBlockSyntax is also paragraph continuation text.
+ final otherMatched =
+ parser.blockSyntaxes.firstWhere((s) => s.canParse(parser));
+ if ((otherMatched is ParagraphSyntax &&
+ !lastLine.isBlankLine &&
+ !codeFencePattern.hasMatch(lastLine.content)) ||
+ (otherMatched is CodeBlockSyntax &&
+ !indentPattern.hasMatch(lastLine.content))) {
+ childLines.add(parser.current);
+ _lazyContinuation = true;
+ parser.advance();
+ } else {
+ break;
+ }
+ }
+
+ return childLines;
+ }
+
+ @override
+ Node parse(BlockParser parser) {
+ final childLines = parseChildLines(parser);
+
+ // Recursively parse the contents of the blockquote.
+ final children = BlockParser(childLines, parser.document).parseLines(
+ // The setext heading underline cannot be a lazy continuation line in a
+ // block quote.
+ // https://spec.commonmark.org/0.30/#example-93
+ disabledSetextHeading: _lazyContinuation,
+ parentSyntax: this,
+ );
+
+ return Element('blockquote', children);
+ }
+}
diff --git a/pkgs/markdown/lib/src/block_syntaxes/code_block_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/code_block_syntax.dart
new file mode 100644
index 0000000..1d30667
--- /dev/null
+++ b/pkgs/markdown/lib/src/block_syntaxes/code_block_syntax.dart
@@ -0,0 +1,85 @@
+// 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 '../ast.dart';
+import '../block_parser.dart';
+import '../line.dart';
+import '../patterns.dart';
+import '../util.dart';
+import 'block_syntax.dart';
+
+/// Parses preformatted code blocks that are indented four spaces.
+class CodeBlockSyntax extends BlockSyntax {
+ @override
+ RegExp get pattern => indentPattern;
+
+ @override
+ bool canEndBlock(BlockParser parser) => false;
+
+ const CodeBlockSyntax();
+
+ @override
+ List<Line> parseChildLines(BlockParser parser) {
+ final childLines = <Line>[];
+
+ while (!parser.isDone) {
+ final isBlankLine = parser.current.isBlankLine;
+ if (isBlankLine && _shouldEnd(parser)) {
+ break;
+ }
+
+ if (!isBlankLine &&
+ childLines.isNotEmpty &&
+ !pattern.hasMatch(parser.current.content)) {
+ break;
+ }
+
+ childLines.add(Line(
+ parser.current.content.dedent().text,
+ tabRemaining: parser.current.tabRemaining,
+ ));
+
+ parser.advance();
+ }
+
+ return childLines;
+ }
+
+ @override
+ Node parse(BlockParser parser) {
+ final childLines = parseChildLines(parser);
+
+ // The Markdown tests expect a trailing newline.
+ childLines.add(Line(''));
+
+ var content = childLines
+ .map((e) => e.content.prependSpace(e.tabRemaining ?? 0))
+ .join('\n');
+ if (parser.document.encodeHtml) {
+ content = escapeHtml(content, escapeApos: false);
+ }
+
+ return Element('pre', [Element.text('code', content)]);
+ }
+
+ bool _shouldEnd(BlockParser parser) {
+ var i = 1;
+ while (true) {
+ final nextLine = parser.peek(i);
+ // EOF
+ if (nextLine == null) {
+ return true;
+ }
+
+ // It does not matter how many blank lines between chunks:
+ // https://spec.commonmark.org/0.30/#example-111
+ if (nextLine.isBlankLine) {
+ i++;
+ continue;
+ }
+
+ return !pattern.hasMatch(nextLine.content);
+ }
+ }
+}
diff --git a/pkgs/markdown/lib/src/block_syntaxes/dummy_block_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/dummy_block_syntax.dart
new file mode 100644
index 0000000..c08c8a8
--- /dev/null
+++ b/pkgs/markdown/lib/src/block_syntaxes/dummy_block_syntax.dart
@@ -0,0 +1,36 @@
+// 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 '../ast.dart';
+import '../block_parser.dart';
+import '../patterns.dart';
+import 'block_syntax.dart';
+
+/// Walks the parser forward through the lines does not match any [BlockSyntax].
+///
+/// Returns a [UnparsedContent] with the unmatched lines as `textContent`.
+class DummyBlockSyntax extends BlockSyntax {
+ const DummyBlockSyntax();
+
+ @override
+ RegExp get pattern => dummyPattern;
+
+ @override
+ bool canEndBlock(BlockParser parser) => false;
+
+ @override
+ bool canParse(BlockParser parser) => true;
+
+ @override
+ Node parse(BlockParser parser) {
+ final childLines = <String>[];
+
+ while (!BlockSyntax.isAtBlockEnd(parser)) {
+ childLines.add(parser.current.content);
+ parser.advance();
+ }
+
+ return UnparsedContent(childLines.join('\n'));
+ }
+}
diff --git a/pkgs/markdown/lib/src/block_syntaxes/empty_block_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/empty_block_syntax.dart
new file mode 100644
index 0000000..54cc865
--- /dev/null
+++ b/pkgs/markdown/lib/src/block_syntaxes/empty_block_syntax.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 '../ast.dart';
+import '../block_parser.dart';
+import '../patterns.dart';
+import 'block_syntax.dart';
+
+class EmptyBlockSyntax extends BlockSyntax {
+ @override
+ RegExp get pattern => emptyPattern;
+
+ const EmptyBlockSyntax();
+
+ @override
+ Node? parse(BlockParser parser) {
+ parser.encounteredBlankLine = true;
+ parser.advance();
+
+ // Don't actually emit anything.
+ return null;
+ }
+}
diff --git a/pkgs/markdown/lib/src/block_syntaxes/fenced_blockquote_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/fenced_blockquote_syntax.dart
new file mode 100644
index 0000000..9e18d42
--- /dev/null
+++ b/pkgs/markdown/lib/src/block_syntaxes/fenced_blockquote_syntax.dart
@@ -0,0 +1,45 @@
+// 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 '../ast.dart';
+import '../block_parser.dart';
+import '../line.dart';
+import '../patterns.dart';
+import 'block_syntax.dart';
+
+/// Parses lines fenced by `>>>` to blockquotes
+class FencedBlockquoteSyntax extends BlockSyntax {
+ const FencedBlockquoteSyntax();
+
+ @override
+ RegExp get pattern => blockquoteFencePattern;
+
+ @override
+ List<Line> parseChildLines(BlockParser parser) {
+ final childLines = <Line>[];
+ parser.advance();
+
+ while (!parser.isDone) {
+ final match = pattern.hasMatch(parser.current.content);
+ if (!match) {
+ childLines.add(parser.current);
+ parser.advance();
+ } else {
+ parser.advance();
+ break;
+ }
+ }
+
+ return childLines;
+ }
+
+ @override
+ Node? parse(BlockParser parser) {
+ final childLines = parseChildLines(parser);
+
+ // Recursively parse the contents of the blockquote.
+ final children = BlockParser(childLines, parser.document).parseLines();
+ return Element('blockquote', children);
+ }
+}
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
new file mode 100644
index 0000000..cea1106
--- /dev/null
+++ b/pkgs/markdown/lib/src/block_syntaxes/fenced_code_block_syntax.dart
@@ -0,0 +1,141 @@
+// 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 '../ast.dart';
+import '../block_parser.dart';
+import '../line.dart';
+import '../patterns.dart';
+import '../util.dart';
+import 'block_syntax.dart';
+
+/// Parses preformatted code blocks between two ~~~ or ``` sequences.
+///
+/// See the CommonMark spec:
+/// https://spec.commonmark.org/0.30/#fenced-code-blocks
+class FencedCodeBlockSyntax extends BlockSyntax {
+ @override
+ RegExp get pattern => codeFencePattern;
+
+ const FencedCodeBlockSyntax();
+
+ @override
+ Node parse(BlockParser parser) {
+ final openingFence = _FenceMatch.fromMatch(pattern.firstMatch(
+ escapePunctuation(parser.current.content),
+ )!);
+
+ var text = parseChildLines(
+ parser,
+ openingFence.marker,
+ openingFence.indent,
+ ).map((e) => e.content).join('\n');
+
+ if (parser.document.encodeHtml) {
+ text = escapeHtml(text, escapeApos: false);
+ }
+ if (text.isNotEmpty) {
+ text = '$text\n';
+ }
+
+ 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';
+ }
+
+ 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<Line> parseChildLines(
+ BlockParser parser, [
+ String openingMarker = '',
+ int indent = 0,
+ ]) {
+ final childLines = <Line>[];
+
+ parser.advance();
+
+ _FenceMatch? closingFence;
+ while (!parser.isDone) {
+ final match = pattern.firstMatch(parser.current.content);
+ closingFence = match == null ? null : _FenceMatch.fromMatch(match);
+
+ // Closing code fences cannot have info strings:
+ // https://spec.commonmark.org/0.30/#example-147
+ if (closingFence == null ||
+ !closingFence.marker.startsWith(openingMarker) ||
+ closingFence.hasInfo) {
+ childLines.add(
+ Line(_removeIndentation(parser.current.content, indent)),
+ );
+ parser.advance();
+ } else {
+ parser.advance();
+ break;
+ }
+ }
+
+ // https://spec.commonmark.org/0.30/#example-127
+ // https://spec.commonmark.org/0.30/#example-128
+ if (closingFence == null &&
+ childLines.isNotEmpty &&
+ childLines.last.isBlankLine) {
+ childLines.removeLast();
+ }
+
+ return childLines;
+ }
+}
+
+class _FenceMatch {
+ _FenceMatch._({
+ required this.indent,
+ required this.marker,
+ required this.info,
+ });
+
+ factory _FenceMatch.fromMatch(RegExpMatch match) {
+ String marker;
+ String info;
+
+ if (match.namedGroup('backtick') != null) {
+ marker = match.namedGroup('backtick')!;
+ info = match.namedGroup('backtickInfo')!;
+ } else {
+ marker = match.namedGroup('tilde')!;
+ info = match.namedGroup('tildeInfo')!;
+ }
+
+ return _FenceMatch._(
+ indent: match[1]!.length,
+ marker: marker,
+ info: info.trim(),
+ );
+ }
+
+ final int indent;
+ final String marker;
+
+ // The info-string should be trimmed,
+ // 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;
+
+ bool get hasInfo => info.isNotEmpty;
+
+ bool get hasLanguage => language.isNotEmpty;
+}
diff --git a/pkgs/markdown/lib/src/block_syntaxes/footnote_def_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/footnote_def_syntax.dart
new file mode 100644
index 0000000..3e59dcf
--- /dev/null
+++ b/pkgs/markdown/lib/src/block_syntaxes/footnote_def_syntax.dart
@@ -0,0 +1,82 @@
+import '../ast.dart' show Element, Node;
+import '../block_parser.dart' show BlockParser;
+import '../line.dart';
+import '../patterns.dart' show dummyPattern, emptyPattern, footnotePattern;
+import 'block_syntax.dart' show BlockSyntax;
+
+/// The spec of GFM about footnotes is [missing](https://github.com/github/cmark-gfm/issues/283#issuecomment-1378868725).
+/// For online source code of cmark-gfm, see [master@c32ef78](https://github.com/github/cmark-gfm/blob/c32ef78/src/blocks.c#L1212).
+/// A Rust implementation is also [available](https://github.com/wooorm/markdown-rs/blob/2498e31eecead798efc649502bbf5f86feaa94be/src/construct/gfm_footnote_definition.rs).
+/// Footnote definition could contain multiple line-children and children could
+/// be separated by one empty line.
+/// Its first child-line would be the remaining part of the first line after
+/// taking definition leading, combining with other child lines parsed by
+/// [parseChildLines], is fed into [BlockParser].
+class FootnoteDefSyntax extends BlockSyntax {
+ const FootnoteDefSyntax();
+
+ @override
+ RegExp get pattern => footnotePattern;
+
+ @override
+ Node? parse(BlockParser parser) {
+ final current = parser.current.content;
+ final match = pattern.firstMatch(current)!;
+ final label = match[2]!;
+ final refs = parser.document.footnoteReferences;
+ refs[label] = 0;
+
+ final id = Uri.encodeComponent(label);
+ parser.advance();
+ final lines = [
+ Line(current.substring(match[0]!.length)),
+ ...parseChildLines(parser),
+ ];
+ final children = BlockParser(lines, parser.document).parseLines();
+ return Element('li', children)
+ ..attributes['id'] = 'fn-$id'
+ ..footnoteLabel = label;
+ }
+
+ @override
+ List<Line> parseChildLines(BlockParser parser) {
+ final children = <String>[];
+ // As one empty line should not split footnote definition, use this flag.
+ var shouldBeBlock = false;
+ late final syntaxList = parser.blockSyntaxes
+ .where((s) => !_excludingPattern.contains(s.pattern));
+
+ // Every line is footnote's children util two blank lines or a block.
+ while (!parser.isDone) {
+ final line = parser.current.content;
+ if (line.trim().isEmpty) {
+ children.add(line);
+ parser.advance();
+ shouldBeBlock = true;
+ continue;
+ } else if (line.startsWith(' ')) {
+ children.add(line.substring(4));
+ parser.advance();
+ shouldBeBlock = false;
+ } else if (shouldBeBlock || _isBlock(syntaxList, line)) {
+ break;
+ } else {
+ children.add(line);
+ parser.advance();
+ }
+ }
+ return children.map(Line.new).toList(growable: false);
+ }
+
+ /// Patterns that would be used to decide if one line is a block.
+ static final _excludingPattern = {
+ emptyPattern,
+ dummyPattern,
+ };
+
+ /// Whether this line is any kind of block.
+ /// If `true`, the footnote block should end.
+ static bool _isBlock(Iterable<BlockSyntax> syntaxList, String line) {
+ return syntaxList.any((s) => s.pattern.hasMatch(line));
+ }
+}
diff --git a/pkgs/markdown/lib/src/block_syntaxes/header_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/header_syntax.dart
new file mode 100644
index 0000000..197b417
--- /dev/null
+++ b/pkgs/markdown/lib/src/block_syntaxes/header_syntax.dart
@@ -0,0 +1,47 @@
+// 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 '../ast.dart';
+import '../block_parser.dart';
+import '../patterns.dart';
+import 'block_syntax.dart';
+
+/// Parses atx-style headers: `## Header ##`.
+class HeaderSyntax extends BlockSyntax {
+ @override
+ RegExp get pattern => headerPattern;
+
+ const HeaderSyntax();
+
+ @override
+ Node parse(BlockParser parser) {
+ final match = pattern.firstMatch(parser.current.content)!;
+ final matchedText = match[0]!;
+ final openMarker = match[1]!;
+ final closeMarker = match[2];
+ final level = openMarker.length;
+ final openMarkerStart = matchedText.indexOf(openMarker);
+ final openMarkerEnd = openMarkerStart + level;
+
+ String? content;
+ if (closeMarker == null) {
+ content = parser.current.content.substring(openMarkerEnd);
+ } else {
+ final closeMarkerStart = matchedText.lastIndexOf(closeMarker);
+ content = parser.current.content.substring(
+ openMarkerEnd,
+ closeMarkerStart,
+ );
+ }
+ content = content.trim();
+
+ // https://spec.commonmark.org/0.30/#example-79
+ if (closeMarker == null && RegExp(r'^#+$').hasMatch(content)) {
+ content = null;
+ }
+
+ parser.advance();
+ return Element('h$level', [if (content != null) UnparsedContent(content)]);
+ }
+}
diff --git a/pkgs/markdown/lib/src/block_syntaxes/header_with_id_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/header_with_id_syntax.dart
new file mode 100644
index 0000000..8e2c50b
--- /dev/null
+++ b/pkgs/markdown/lib/src/block_syntaxes/header_with_id_syntax.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 '../ast.dart';
+import '../block_parser.dart';
+import 'block_syntax.dart';
+import 'header_syntax.dart';
+
+/// Parses atx-style headers, and adds generated IDs to the generated elements.
+class HeaderWithIdSyntax extends HeaderSyntax {
+ const HeaderWithIdSyntax();
+
+ @override
+ Node parse(BlockParser parser) {
+ final element = super.parse(parser) as Element;
+
+ if (element.children?.isNotEmpty ?? false) {
+ element.generatedId = BlockSyntax.generateAnchorHash(element);
+ }
+
+ return element;
+ }
+}
diff --git a/pkgs/markdown/lib/src/block_syntaxes/horizontal_rule_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/horizontal_rule_syntax.dart
new file mode 100644
index 0000000..12e7839
--- /dev/null
+++ b/pkgs/markdown/lib/src/block_syntaxes/horizontal_rule_syntax.dart
@@ -0,0 +1,22 @@
+// 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 '../ast.dart';
+import '../block_parser.dart';
+import '../patterns.dart';
+import 'block_syntax.dart';
+
+/// Parses horizontal rules like `---`, `_ _ _`, `* * *`, etc.
+class HorizontalRuleSyntax extends BlockSyntax {
+ @override
+ RegExp get pattern => hrPattern;
+
+ const HorizontalRuleSyntax();
+
+ @override
+ Node parse(BlockParser parser) {
+ parser.advance();
+ return Element.empty('hr');
+ }
+}
diff --git a/pkgs/markdown/lib/src/block_syntaxes/html_block_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/html_block_syntax.dart
new file mode 100644
index 0000000..c1aa85c
--- /dev/null
+++ b/pkgs/markdown/lib/src/block_syntaxes/html_block_syntax.dart
@@ -0,0 +1,99 @@
+// 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 '../ast.dart';
+import '../block_parser.dart';
+import '../line.dart';
+import '../patterns.dart';
+import 'block_syntax.dart';
+import 'list_syntax.dart';
+
+/// Parse HTML blocks.
+// There are seven kinds of HTML block defined in the CommonMark spec:
+// https://spec.commonmark.org/0.30/#html-blocks.
+// These matching conditions and HTML block types mentioned in this syntax
+// correspond to these ones in the CommonMark spec.
+class HtmlBlockSyntax extends BlockSyntax {
+ @override
+ RegExp get pattern => htmlBlockPattern;
+
+ // All types of HTML blocks except type 7 may interrupt a paragraph, see the
+ // second paragraph after https://spec.commonmark.org/0.30/#example-148 for
+ // more detail.
+ @override
+ bool canEndBlock(BlockParser parser) =>
+ pattern.firstMatch(parser.current.content)!.namedGroup('condition_7') ==
+ null;
+
+ static final _endConditions = [
+ // For condition 1, it does not need to match the start tag, see
+ // https://spec.commonmark.org/0.30/#end-condition
+ RegExp('</(?:pre|script|style|textarea)>', caseSensitive: false),
+ RegExp('-->'),
+ RegExp(r'\?>'),
+ RegExp('>'),
+ RegExp(']]>'),
+ emptyPattern,
+ emptyPattern,
+ ];
+
+ const HtmlBlockSyntax();
+
+ @override
+ List<Line> parseChildLines(BlockParser parser) {
+ final lines = <Line>[];
+
+ final match = pattern.firstMatch(parser.current.content);
+ var matchedCondition = 0;
+ for (var i = 0; i < match!.groupCount; i++) {
+ if (match.group(i + 1) != null) {
+ matchedCondition = i;
+ break;
+ }
+ }
+
+ final endCondition = _endConditions[matchedCondition];
+ if (endCondition == emptyPattern) {
+ lines.add(parser.current);
+ parser.advance();
+
+ while (!parser.isDone && !endCondition.hasMatch(parser.current.content)) {
+ lines.add(parser.current);
+ parser.advance();
+ }
+ } else {
+ while (!parser.isDone) {
+ lines.add(parser.current);
+ if (endCondition.hasMatch(parser.current.content)) {
+ break;
+ }
+ parser.advance();
+ }
+ parser.advance();
+ }
+
+ // If the current line start an HTML block again, put them together with
+ // the previous HTML block.
+ if (!parser.isDone && pattern.hasMatch(parser.current.content)) {
+ lines.addAll(parseChildLines(parser));
+ }
+
+ return lines;
+ }
+
+ @override
+ Node parse(BlockParser parser) {
+ final childLines = parseChildLines(parser);
+
+ var text = childLines.map((e) => e.content).join('\n').trimRight();
+ if (parser.previousSyntax != null || parser.parentSyntax != null) {
+ text = '\n$text';
+ if (parser.parentSyntax is ListSyntax) {
+ text = '$text\n';
+ }
+ }
+
+ return Text(text);
+ }
+}
diff --git a/pkgs/markdown/lib/src/block_syntaxes/link_reference_definition_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/link_reference_definition_syntax.dart
new file mode 100644
index 0000000..5b2b1b5
--- /dev/null
+++ b/pkgs/markdown/lib/src/block_syntaxes/link_reference_definition_syntax.dart
@@ -0,0 +1,65 @@
+// Copyright (c) 2023, 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 '../ast.dart';
+import '../block_parser.dart';
+import '../document.dart';
+import '../line.dart';
+import '../link_parser.dart';
+import '../patterns.dart';
+import '../util.dart';
+import 'block_syntax.dart';
+
+class LinkReferenceDefinitionSyntax extends BlockSyntax {
+ @override
+ RegExp get pattern => linkReferenceDefinitionPattern;
+
+ @override
+ bool canEndBlock(BlockParser parser) => false;
+
+ const LinkReferenceDefinitionSyntax();
+
+ @override
+ Node? parse(BlockParser parser) {
+ final lines = <Line>[parser.current];
+ parser.advance();
+
+ while (!BlockSyntax.isAtBlockEnd(parser)) {
+ lines.add(parser.current);
+ parser.advance();
+ }
+
+ if (!_parseLinkReferenceDefinition(lines, parser)) {
+ parser.retreatBy(lines.length);
+ }
+
+ return null;
+ }
+
+ bool _parseLinkReferenceDefinition(List<Line> lines, BlockParser parser) {
+ final linkParser = LinkParser(lines.map((e) => e.content).join('\n'))
+ ..parseDefinition();
+
+ if (!linkParser.valid) {
+ return false;
+ }
+
+ // Retreat the parsing position back to where the link reference definition
+ // ends, so that the next syntax can continue parsing from there.
+ parser.retreatBy(linkParser.unconsumedLines);
+
+ final labelString = normalizeLinkLabel(linkParser.label!);
+
+ parser.document.linkReferences.putIfAbsent(
+ labelString,
+ () => LinkReference(
+ labelString,
+ linkParser.destination!,
+ linkParser.title,
+ ),
+ );
+
+ return true;
+ }
+}
diff --git a/pkgs/markdown/lib/src/block_syntaxes/list_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/list_syntax.dart
new file mode 100644
index 0000000..534ca3e
--- /dev/null
+++ b/pkgs/markdown/lib/src/block_syntaxes/list_syntax.dart
@@ -0,0 +1,353 @@
+// 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 '../ast.dart';
+import '../block_parser.dart';
+import '../charcode.dart';
+import '../line.dart';
+import '../patterns.dart';
+import '../text_parser.dart';
+import '../util.dart';
+import 'block_syntax.dart';
+import 'ordered_list_with_checkbox_syntax.dart';
+import 'unordered_list_with_checkbox_syntax.dart';
+
+class ListItem {
+ const ListItem(
+ this.lines, {
+ this.taskListItemState,
+ });
+
+ final List<Line> lines;
+ final TaskListItemState? taskListItemState;
+}
+
+enum TaskListItemState { checked, unchecked }
+
+/// Base class for both ordered and unordered lists.
+abstract class ListSyntax extends BlockSyntax {
+ @override
+ bool canParse(BlockParser parser) =>
+ pattern.hasMatch(parser.current.content) &&
+ !hrPattern.hasMatch(parser.current.content);
+
+ @override
+ bool canEndBlock(BlockParser parser) {
+ // An empty list cannot interrupt a paragraph. See
+ // https://spec.commonmark.org/0.30/#example-285.
+ // Ideally, [BlockSyntax.canEndBlock] should be changed to be a method
+ // which accepts a [BlockParser], but this would be a breaking change,
+ // so we're going with this temporarily.
+ final match = pattern.firstMatch(parser.current.content)!;
+
+ // Allow only lists starting with 1 to interrupt paragraphs, if it is an
+ // ordered list. See https://spec.commonmark.org/0.30/#example-304.
+ // But there should be an exception for nested ordered lists, for example:
+ // ```
+ // 1. one
+ // 2. two
+ // 3. three
+ // 4. four
+ // 5. five
+ // ```
+ if (parser.parentSyntax is! ListSyntax &&
+ match[1] != null &&
+ match[1] != '1') {
+ return false;
+ }
+
+ // An empty list item cannot interrupt a paragraph. See
+ // https://spec.commonmark.org/0.30/#example-285
+ return match[2]?.isNotEmpty ?? false;
+ }
+
+ const ListSyntax();
+
+ /// A list of patterns that can start a valid block within a list item.
+ static final blocksInList = [
+ blockquotePattern,
+ headerPattern,
+ hrPattern,
+ indentPattern,
+ listPattern,
+ ];
+
+ @override
+ Node parse(BlockParser parser) {
+ final match = pattern.firstMatch(parser.current.content);
+ final ordered = match![1] != null;
+
+ final taskListParserEnabled = this is UnorderedListWithCheckboxSyntax ||
+ this is OrderedListWithCheckboxSyntax;
+ final items = <ListItem>[];
+ var childLines = <Line>[];
+ TaskListItemState? taskListItemState;
+
+ void endItem() {
+ if (childLines.isNotEmpty) {
+ items.add(ListItem(childLines, taskListItemState: taskListItemState));
+ childLines = <Line>[];
+ }
+ }
+
+ String parseTaskListItem(String text) {
+ final pattern = RegExp(r'^ {0,3}\[([ xX])\][ \t]');
+
+ if (taskListParserEnabled && pattern.hasMatch(text)) {
+ return text.replaceFirstMapped(pattern, (match) {
+ taskListItemState = match[1] == ' '
+ ? TaskListItemState.unchecked
+ : TaskListItemState.checked;
+
+ return '';
+ });
+ } else {
+ taskListItemState = null;
+ return text;
+ }
+ }
+
+ late Match? possibleMatch;
+ bool tryMatch(RegExp pattern) {
+ possibleMatch = pattern.firstMatch(parser.current.content);
+ return possibleMatch != null;
+ }
+
+ String? listMarker;
+ int? indent;
+ // In case the first number in an ordered list is not 1, use it as the
+ // "start".
+ int? startNumber;
+
+ int? blankLines;
+
+ while (!parser.isDone) {
+ final currentIndent = parser.current.content.indentation() +
+ (parser.current.tabRemaining ?? 0);
+
+ if (parser.current.isBlankLine) {
+ childLines.add(parser.current);
+
+ if (blankLines != null) {
+ blankLines++;
+ }
+ } else if (indent != null && indent <= currentIndent) {
+ // A list item can begin with at most one blank line. See:
+ // https://spec.commonmark.org/0.30/#example-280
+ if (blankLines != null && blankLines > 1) {
+ break;
+ }
+
+ final indentedLine = parser.current.content.dedent(indent);
+
+ childLines.add(Line(
+ blankLines == null
+ ? indentedLine.text
+ : parseTaskListItem(indentedLine.text),
+ tabRemaining: indentedLine.tabRemaining,
+ ));
+ } else if (tryMatch(hrPattern)) {
+ // Horizontal rule takes precedence to a new list item.
+ break;
+ } else if (tryMatch(listPattern)) {
+ blankLines = null;
+ final match = possibleMatch!;
+ final textParser = TextParser(parser.current.content);
+ var precedingWhitespaces = textParser.moveThroughWhitespace();
+ final markerStart = textParser.pos;
+ final digits = match[1] ?? '';
+ if (digits.isNotEmpty) {
+ startNumber ??= int.parse(digits);
+ textParser.advanceBy(digits.length);
+ }
+ textParser.advance();
+
+ // See https://spec.commonmark.org/0.30/#ordered-list-marker
+ final marker = textParser.substring(
+ markerStart,
+ textParser.pos,
+ );
+
+ var isBlank = true;
+ var contentWhitespances = 0;
+ var containsTab = false;
+ int? contentBlockStart;
+
+ if (!textParser.isDone) {
+ containsTab = textParser.charAt() == $tab;
+ // Skip the first whitespace.
+ textParser.advance();
+ contentBlockStart = textParser.pos;
+ if (!textParser.isDone) {
+ contentWhitespances = textParser.moveThroughWhitespace();
+
+ if (!textParser.isDone) {
+ isBlank = false;
+ }
+ }
+ }
+
+ // Changing the bullet or ordered list delimiter starts a new list.
+ if (listMarker != null && listMarker.last() != marker.last()) {
+ break;
+ }
+
+ // End the current list item and start a new one.
+ endItem();
+
+ // Start a new list item, the last item will be ended up outside of the
+ // `while` loop.
+ listMarker = marker;
+ precedingWhitespaces += digits.length + 2;
+ if (isBlank) {
+ // See https://spec.commonmark.org/0.30/#example-278.
+ blankLines = 1;
+ indent = precedingWhitespaces;
+ } else if (contentWhitespances >= 4) {
+ // See https://spec.commonmark.org/0.30/#example-270.
+ //
+ // If the list item starts with indented code, we need to _not_ count
+ // any indentation past the required whitespace character.
+ indent = precedingWhitespaces;
+ } else {
+ indent = precedingWhitespaces + contentWhitespances;
+ }
+
+ taskListItemState = null;
+ var content = contentBlockStart != null && !isBlank
+ ? parseTaskListItem(textParser.substring(contentBlockStart))
+ : '';
+
+ if (content.isEmpty && containsTab) {
+ content = content.prependSpace(2);
+ }
+
+ childLines.add(Line(
+ content,
+ tabRemaining: containsTab ? 2 : null,
+ ));
+ } else if (BlockSyntax.isAtBlockEnd(parser)) {
+ // Done with the list.
+ break;
+ } else {
+ // If the previous item is a blank line, this means we're done with the
+ // list and are starting a new top-level paragraph.
+ if (childLines.isNotEmpty && childLines.last.isBlankLine) {
+ parser.encounteredBlankLine = true;
+ break;
+ }
+
+ // Anything else is paragraph continuation text.
+ childLines.add(parser.current);
+ }
+ parser.advance();
+ }
+
+ endItem();
+ final itemNodes = <Element>[];
+
+ items.forEach(_removeLeadingEmptyLine);
+ final anyEmptyLines = _removeTrailingEmptyLines(items);
+ var anyEmptyLinesBetweenBlocks = false;
+ var containsTaskList = false;
+ const taskListClass = 'task-list-item';
+
+ for (final item in items) {
+ Element? checkboxToInsert;
+ if (item.taskListItemState != null) {
+ containsTaskList = true;
+ checkboxToInsert = Element.withTag('input')
+ ..attributes['type'] = 'checkbox';
+ if (item.taskListItemState == TaskListItemState.checked) {
+ checkboxToInsert.attributes['checked'] = 'true';
+ }
+ }
+
+ final itemParser = BlockParser(item.lines, parser.document);
+ final children = itemParser.parseLines(parentSyntax: this);
+ final itemElement = checkboxToInsert == null
+ ? Element('li', children)
+ : (Element('li', _addCheckbox(children, checkboxToInsert))
+ ..attributes['class'] = taskListClass);
+
+ itemNodes.add(itemElement);
+ anyEmptyLinesBetweenBlocks =
+ anyEmptyLinesBetweenBlocks || itemParser.encounteredBlankLine;
+ }
+
+ // Must strip paragraph tags if the list is "tight".
+ // https://spec.commonmark.org/0.30/#lists
+ final listIsTight = !anyEmptyLines && !anyEmptyLinesBetweenBlocks;
+
+ if (listIsTight) {
+ // We must post-process the list items, converting any top-level paragraph
+ // elements to just text elements.
+ for (final item in itemNodes) {
+ final isTaskList = item.attributes['class'] == taskListClass;
+ final children = item.children;
+ if (children != null) {
+ Node? lastNode;
+ for (var i = 0; i < children.length; i++) {
+ final child = children[i];
+ if (child is Element && child.tag == 'p') {
+ final childContent = child.children!;
+ if (lastNode is Element && !isTaskList) {
+ childContent.insert(0, Text('\n'));
+ }
+
+ children
+ ..removeAt(i)
+ ..insertAll(i, childContent);
+ }
+
+ lastNode = child;
+ }
+ }
+ }
+ }
+
+ final listElement = Element(ordered ? 'ol' : 'ul', itemNodes);
+ if (ordered && startNumber != 1) {
+ listElement.attributes['start'] = '$startNumber';
+ }
+
+ if (containsTaskList) {
+ listElement.attributes['class'] = 'contains-task-list';
+ }
+ return listElement;
+ }
+
+ List<Node> _addCheckbox(List<Node> children, Element checkbox) {
+ if (children.isNotEmpty) {
+ final firstChild = children.first;
+ if (firstChild is Element && firstChild.tag == 'p') {
+ firstChild.children!.insert(0, checkbox);
+ return children;
+ }
+ }
+ return [checkbox, ...children];
+ }
+
+ void _removeLeadingEmptyLine(ListItem item) {
+ if (item.lines.isNotEmpty && item.lines.first.isBlankLine) {
+ item.lines.removeAt(0);
+ }
+ }
+
+ /// Removes any trailing empty lines and notes whether any items are separated
+ /// by such lines.
+ bool _removeTrailingEmptyLines(List<ListItem> items) {
+ var anyEmpty = false;
+ for (var i = 0; i < items.length; i++) {
+ if (items[i].lines.length == 1) continue;
+ while (items[i].lines.isNotEmpty && items[i].lines.last.isBlankLine) {
+ if (i < items.length - 1) {
+ anyEmpty = true;
+ }
+ items[i].lines.removeLast();
+ }
+ }
+ return anyEmpty;
+ }
+}
diff --git a/pkgs/markdown/lib/src/block_syntaxes/ordered_list_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/ordered_list_syntax.dart
new file mode 100644
index 0000000..53c4730
--- /dev/null
+++ b/pkgs/markdown/lib/src/block_syntaxes/ordered_list_syntax.dart
@@ -0,0 +1,14 @@
+// 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 '../patterns.dart';
+import 'list_syntax.dart';
+
+/// Parses ordered lists.
+class OrderedListSyntax extends ListSyntax {
+ @override
+ RegExp get pattern => listPattern;
+
+ const OrderedListSyntax();
+}
diff --git a/pkgs/markdown/lib/src/block_syntaxes/ordered_list_with_checkbox_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/ordered_list_with_checkbox_syntax.dart
new file mode 100644
index 0000000..8d865e8
--- /dev/null
+++ b/pkgs/markdown/lib/src/block_syntaxes/ordered_list_with_checkbox_syntax.dart
@@ -0,0 +1,10 @@
+// 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 'ordered_list_syntax.dart';
+
+/// Parses ordered lists with checkboxes.
+class OrderedListWithCheckboxSyntax extends OrderedListSyntax {
+ const OrderedListWithCheckboxSyntax();
+}
diff --git a/pkgs/markdown/lib/src/block_syntaxes/paragraph_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/paragraph_syntax.dart
new file mode 100644
index 0000000..53d496d
--- /dev/null
+++ b/pkgs/markdown/lib/src/block_syntaxes/paragraph_syntax.dart
@@ -0,0 +1,49 @@
+// 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 '../ast.dart';
+import '../block_parser.dart';
+import '../patterns.dart';
+import 'block_syntax.dart';
+import 'setext_header_syntax.dart';
+
+/// Parses paragraphs of regular text.
+class ParagraphSyntax extends BlockSyntax {
+ @override
+ RegExp get pattern => dummyPattern;
+
+ @override
+ bool canEndBlock(BlockParser parser) => false;
+
+ const ParagraphSyntax();
+
+ @override
+ bool canParse(BlockParser parser) => true;
+
+ @override
+ Node? parse(BlockParser parser) {
+ final childLines = <String>[parser.current.content];
+
+ parser.advance();
+ var interruptedBySetextHeading = false;
+ // Eat until we hit something that ends a paragraph.
+ while (!parser.isDone) {
+ final syntax = interruptedBy(parser);
+ if (syntax != null) {
+ interruptedBySetextHeading = syntax is SetextHeaderSyntax;
+ break;
+ }
+ childLines.add(parser.current.content);
+ parser.advance();
+ }
+
+ // It is not a paragraph, but a setext heading.
+ if (interruptedBySetextHeading) {
+ return null;
+ }
+
+ final contents = UnparsedContent(childLines.join('\n').trimRight());
+ return Element('p', [contents]);
+ }
+}
diff --git a/pkgs/markdown/lib/src/block_syntaxes/setext_header_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/setext_header_syntax.dart
new file mode 100644
index 0000000..49f4eda
--- /dev/null
+++ b/pkgs/markdown/lib/src/block_syntaxes/setext_header_syntax.dart
@@ -0,0 +1,44 @@
+// 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 '../ast.dart';
+import '../block_parser.dart';
+import '../patterns.dart';
+import 'block_syntax.dart';
+import 'paragraph_syntax.dart';
+
+/// Parses setext-style headers.
+class SetextHeaderSyntax extends BlockSyntax {
+ @override
+ RegExp get pattern => setextPattern;
+
+ const SetextHeaderSyntax();
+
+ @override
+ bool canParse(BlockParser parser) {
+ final lastSyntax = parser.currentSyntax;
+ if (parser.setextHeadingDisabled || lastSyntax is! ParagraphSyntax) {
+ return false;
+ }
+ return pattern.hasMatch(parser.current.content);
+ }
+
+ @override
+ Node? parse(BlockParser parser) {
+ final lines = parser.linesToConsume;
+ if (lines.length < 2) {
+ return null;
+ }
+
+ // Remove the last line which is a marker.
+ lines.removeLast();
+
+ final marker = parser.current.content.trim();
+ final level = (marker[0] == '=') ? '1' : '2';
+ final content = lines.map((e) => e.content).join('\n').trimRight();
+
+ parser.advance();
+ return Element('h$level', [UnparsedContent(content)]);
+ }
+}
diff --git a/pkgs/markdown/lib/src/block_syntaxes/setext_header_with_id_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/setext_header_with_id_syntax.dart
new file mode 100644
index 0000000..fffe992
--- /dev/null
+++ b/pkgs/markdown/lib/src/block_syntaxes/setext_header_with_id_syntax.dart
@@ -0,0 +1,21 @@
+// 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 '../ast.dart';
+import '../block_parser.dart';
+import 'block_syntax.dart';
+import 'setext_header_syntax.dart';
+
+/// Parses setext-style headers, and adds generated IDs to the generated
+/// elements.
+class SetextHeaderWithIdSyntax extends SetextHeaderSyntax {
+ const SetextHeaderWithIdSyntax();
+
+ @override
+ Node parse(BlockParser parser) {
+ final element = super.parse(parser) as Element;
+ element.generatedId = BlockSyntax.generateAnchorHash(element);
+ return element;
+ }
+}
diff --git a/pkgs/markdown/lib/src/block_syntaxes/table_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/table_syntax.dart
new file mode 100644
index 0000000..89cbd90
--- /dev/null
+++ b/pkgs/markdown/lib/src/block_syntaxes/table_syntax.dart
@@ -0,0 +1,225 @@
+// 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 '../ast.dart';
+import '../block_parser.dart';
+import '../charcode.dart';
+import '../patterns.dart';
+import 'block_syntax.dart';
+
+/// Parses tables.
+class TableSyntax extends BlockSyntax {
+ @override
+ bool canEndBlock(BlockParser parser) => true;
+
+ @override
+ RegExp get pattern => dummyPattern;
+
+ const TableSyntax();
+
+ @override
+ bool canParse(BlockParser parser) {
+ // Note: matches *next* line, not the current one. We're looking for the
+ // bar separating the head row from the body rows.
+ return parser.matchesNext(tablePattern);
+ }
+
+ /// Parses a table into its three parts:
+ ///
+ /// * a head row of head cells (`<th>` cells)
+ /// * a divider of hyphens and pipes (not rendered)
+ /// * many body rows of body cells (`<td>` cells)
+ @override
+ Node? parse(BlockParser parser) {
+ final alignments = _parseAlignments(parser.next!.content);
+ final columnCount = alignments.length;
+ final headRow = _parseRow(parser, alignments, 'th');
+ if (headRow.children!.length != columnCount) {
+ parser.retreat();
+ return null;
+ }
+ final head = Element('thead', [headRow]);
+
+ // Advance past the divider of hyphens.
+ parser.advance();
+
+ final rows = <Element>[];
+ while (!parser.isDone && !BlockSyntax.isAtBlockEnd(parser)) {
+ final row = _parseRow(parser, alignments, 'td');
+ final children = row.children;
+ if (children != null) {
+ while (children.length < columnCount) {
+ // Insert synthetic empty cells.
+ children.add(Element('td', []));
+ }
+ while (children.length > columnCount) {
+ children.removeLast();
+ }
+ }
+ while (row.children!.length > columnCount) {
+ row.children!.removeLast();
+ }
+ rows.add(row);
+ }
+ if (rows.isEmpty) {
+ return Element('table', [head]);
+ } else {
+ final body = Element('tbody', rows);
+
+ return Element('table', [head, body]);
+ }
+ }
+
+ List<String?> _parseAlignments(String line) {
+ final columns = <String?>[];
+ // Set the value to `true` when hitting a non whitespace character other
+ // than the first pipe character.
+ var started = false;
+ var hitDash = false;
+ String? alignment;
+
+ for (var i = 0; i < line.length; i++) {
+ final char = line.codeUnitAt(i);
+ if (char == $space || char == $tab || (!started && char == $pipe)) {
+ continue;
+ }
+ started = true;
+
+ if (char == $colon) {
+ if (hitDash) {
+ alignment = alignment == 'left' ? 'center' : 'right';
+ } else {
+ alignment = 'left';
+ }
+ }
+
+ if (char == $pipe) {
+ columns.add(alignment);
+ hitDash = false;
+ alignment = null;
+ } else {
+ hitDash = true;
+ }
+ }
+
+ if (hitDash) {
+ columns.add(alignment);
+ }
+
+ return columns;
+ }
+
+ /// Parses a table row at the current line into a table row element, with
+ /// parsed table cells.
+ ///
+ /// [alignments] is used to annotate an alignment on each cell, and
+ /// [cellType] is used to declare either "td" or "th" cells.
+ Element _parseRow(
+ BlockParser parser,
+ List<String?> alignments,
+ String cellType,
+ ) {
+ final line = parser.current;
+ final cells = <String>[];
+ var index = _walkPastOpeningPipe(line.content);
+ final cellBuffer = StringBuffer();
+
+ while (true) {
+ if (index >= line.content.length) {
+ // This row ended without a trailing pipe, which is fine.
+ cells.add(cellBuffer.toString().trimRight());
+ cellBuffer.clear();
+ break;
+ }
+ final ch = line.content.codeUnitAt(index);
+ if (ch == $backslash) {
+ if (index == line.content.length - 1) {
+ // A table row ending in a backslash is not well-specified, but it
+ // looks like GitHub just allows the character as part of the text of
+ // the last cell.
+ cellBuffer.writeCharCode(ch);
+ cells.add(cellBuffer.toString().trimRight());
+ cellBuffer.clear();
+ break;
+ }
+ final escaped = line.content.codeUnitAt(index + 1);
+ if (escaped == $pipe) {
+ // GitHub Flavored Markdown has a strange bit here; the pipe is to be
+ // escaped before any other inline processing. One consequence, for
+ // example, is that "| `\|` |" should be parsed as a cell with a code
+ // element with text "|", rather than "\|". Most parsers are not
+ // compliant with this corner, but this is what is specified, and what
+ // GitHub does in practice.
+ cellBuffer.writeCharCode(escaped);
+ } else {
+ // The [InlineParser] will handle the escaping.
+ cellBuffer.writeCharCode(ch);
+ cellBuffer.writeCharCode(escaped);
+ }
+ index += 2;
+ } else if (ch == $pipe) {
+ cells.add(cellBuffer.toString().trimRight());
+ cellBuffer.clear();
+ // Walk forward past any whitespace which leads the next cell.
+ index++;
+ index = _walkPastWhitespace(line.content, index);
+ if (index >= line.content.length) {
+ // This row ended with a trailing pipe.
+ break;
+ }
+ } else {
+ cellBuffer.writeCharCode(ch);
+ index++;
+ }
+ }
+ parser.advance();
+ final row = [
+ for (final cell in cells) Element(cellType, [UnparsedContent(cell)])
+ ];
+
+ for (var i = 0; i < row.length && i < alignments.length; i++) {
+ if (alignments[i] == null) continue;
+ row[i].attributes['align'] = '${alignments[i]}';
+ }
+
+ return Element('tr', row);
+ }
+
+ /// Walks past whitespace in [line] starting at [index].
+ ///
+ /// Returns the index of the first non-whitespace character.
+ int _walkPastWhitespace(String line, int index) {
+ while (index < line.length) {
+ final ch = line.codeUnitAt(index);
+ if (ch != $space && ch != $tab) {
+ break;
+ }
+ index++;
+ }
+ return index;
+ }
+
+ /// Walks past the opening pipe (and any whitespace that surrounds it) in
+ /// [line].
+ ///
+ /// Returns the index of the first non-whitespace character after the pipe.
+ /// If no opening pipe is found, this just returns the index of the first
+ /// non-whitespace character.
+ int _walkPastOpeningPipe(String line) {
+ var index = 0;
+ while (index < line.length) {
+ final ch = line.codeUnitAt(index);
+ if (ch == $pipe) {
+ index++;
+ index = _walkPastWhitespace(line, index);
+ }
+ if (ch != $space && ch != $tab) {
+ // No leading pipe.
+ break;
+ }
+ index++;
+ }
+ return index;
+ }
+}
diff --git a/pkgs/markdown/lib/src/block_syntaxes/unordered_list_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/unordered_list_syntax.dart
new file mode 100644
index 0000000..35dd670
--- /dev/null
+++ b/pkgs/markdown/lib/src/block_syntaxes/unordered_list_syntax.dart
@@ -0,0 +1,30 @@
+// 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 '../block_parser.dart';
+import '../patterns.dart';
+import 'list_syntax.dart';
+
+/// Parses unordered lists.
+class UnorderedListSyntax extends ListSyntax {
+ @override
+ RegExp get pattern => listPattern;
+
+ @override
+ bool canParse(BlockParser parser) {
+ // Check if it matches `hrPattern`, otherwise it will produce an infinite
+ // loop if put `UnorderedListSyntax` or `UnorderedListWithCheckboxSyntax`
+ // bofore `HorizontalRuleSyntax` and parse:
+ // ```
+ // * * *
+ // ```
+ if (hrPattern.hasMatch(parser.current.content)) {
+ return false;
+ }
+
+ return pattern.hasMatch(parser.current.content);
+ }
+
+ const UnorderedListSyntax();
+}
diff --git a/pkgs/markdown/lib/src/block_syntaxes/unordered_list_with_checkbox_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/unordered_list_with_checkbox_syntax.dart
new file mode 100644
index 0000000..5f3ddf0
--- /dev/null
+++ b/pkgs/markdown/lib/src/block_syntaxes/unordered_list_with_checkbox_syntax.dart
@@ -0,0 +1,10 @@
+// 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 'unordered_list_syntax.dart';
+
+/// Parses unordered lists with checkboxes.
+class UnorderedListWithCheckboxSyntax extends UnorderedListSyntax {
+ const UnorderedListWithCheckboxSyntax();
+}
diff --git a/pkgs/markdown/lib/src/charcode.dart b/pkgs/markdown/lib/src/charcode.dart
new file mode 100644
index 0000000..693fcc5
--- /dev/null
+++ b/pkgs/markdown/lib/src/charcode.dart
@@ -0,0 +1,123 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source is governed by a
+// BSD-style license that can be found in the LICENSE file
+
+/// "Horizontal Tab" control character, common name.
+const int $tab = 0x09;
+
+/// "Line feed" control character.
+const int $lf = 0x0A;
+
+/// "Vertical Tab" control character.
+const int $vt = 0x0B;
+
+/// "Form feed" control character.
+const int $ff = 0x0C;
+
+/// "Carriage return" control character.
+const int $cr = 0x0D;
+
+/// Space character.
+const int $space = 0x20;
+
+/// Character `!`.
+const int $exclamation = 0x21;
+
+/// Character `"`.
+const int $quote = 0x22;
+
+/// Character `"`.
+const int $double_quote = 0x22; // ignore: constant_identifier_names
+
+/// Character `#`.
+const int $hash = 0x23;
+
+/// Character `$`.
+const int $dollar = 0x24;
+
+/// Character `%`.
+const int $percent = 0x25;
+
+/// Character `&`.
+const int $ampersand = 0x26;
+
+/// Character `'`.
+const int $apostrophe = 0x27;
+
+/// Character `(`.
+const int $lparen = 0x28;
+
+/// Character `)`.
+const int $rparen = 0x29;
+
+/// Character `*`.
+const int $asterisk = 0x2A;
+
+/// Character `+`.
+const int $plus = 0x2B;
+
+/// Character `,`.
+const int $comma = 0x2C;
+
+/// Character `-`.
+const int $dash = 0x2D;
+
+/// Character `.`.
+const int $dot = 0x2E;
+
+/// Character `/`.
+const int $slash = 0x2F;
+
+/// Character `:`.
+const int $colon = 0x3A;
+
+/// Character `;`.
+const int $semicolon = 0x3B;
+
+/// Character `<`.
+const int $lt = 0x3C;
+
+/// Character `=`.
+const int $equal = 0x3D;
+
+/// Character `>`.
+const int $gt = 0x3E;
+
+/// Character `?`.
+const int $question = 0x3F;
+
+/// Character `@`.
+const int $at = 0x40;
+
+/// Character `[`.
+const int $lbracket = 0x5B;
+
+/// Character `\`.
+const int $backslash = 0x5C;
+
+/// Character `]`.
+const int $rbracket = 0x5D;
+
+/// Character `^`.
+const int $caret = 0x5E;
+
+/// Character `_`.
+const int $underscore = 0x5F;
+
+/// Character `` ` ``.
+const int $backquote = 0x60;
+
+/// Character `{`.
+const int $lbrace = 0x7B;
+
+/// Character `|`.
+const int $pipe = 0x7C;
+
+/// Character `|`.
+const int $bar = 0x7C;
+
+/// Character `}`.
+const int $rbrace = 0x7D;
+
+/// Character `~`.
+const int $tilde = 0x7E;
diff --git a/pkgs/markdown/lib/src/document.dart b/pkgs/markdown/lib/src/document.dart
new file mode 100644
index 0000000..f33ff33
--- /dev/null
+++ b/pkgs/markdown/lib/src/document.dart
@@ -0,0 +1,217 @@
+// Copyright (c) 2017, 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 'ast.dart';
+import 'block_parser.dart';
+import 'block_syntaxes/block_syntax.dart';
+import 'extension_set.dart';
+import 'inline_parser.dart';
+import 'inline_syntaxes/inline_syntax.dart';
+import 'line.dart';
+import 'util.dart';
+
+/// Maintains the context needed to parse a Markdown document.
+class Document {
+ final Map<String, LinkReference> linkReferences = {};
+
+ /// Footnote ref count, keys are case-sensitive and added by define syntax.
+ final footnoteReferences = <String, int>{};
+
+ /// Footnote labels by appearing order.
+ ///
+ /// They are case-insensitive and added by ref syntax.
+ final footnoteLabels = <String>[];
+ final Resolver? linkResolver;
+ final Resolver? imageLinkResolver;
+ final bool encodeHtml;
+
+ /// Whether to use default block syntaxes.
+ final bool withDefaultBlockSyntaxes;
+
+ /// Whether to use default inline syntaxes.
+ ///
+ /// Need to set both [withDefaultInlineSyntaxes] and [encodeHtml] to
+ /// `false` to disable all inline syntaxes including html encoding syntaxes.
+ final bool withDefaultInlineSyntaxes;
+
+ final _blockSyntaxes = <BlockSyntax>{};
+ final _inlineSyntaxes = <InlineSyntax>{};
+ final bool hasCustomInlineSyntaxes;
+
+ Iterable<BlockSyntax> get blockSyntaxes => _blockSyntaxes;
+
+ Iterable<InlineSyntax> get inlineSyntaxes => _inlineSyntaxes;
+
+ Document({
+ Iterable<BlockSyntax>? blockSyntaxes,
+ Iterable<InlineSyntax>? inlineSyntaxes,
+ ExtensionSet? extensionSet,
+ this.linkResolver,
+ this.imageLinkResolver,
+ this.encodeHtml = true,
+ this.withDefaultBlockSyntaxes = true,
+ this.withDefaultInlineSyntaxes = true,
+ }) : hasCustomInlineSyntaxes = (inlineSyntaxes?.isNotEmpty ?? false) ||
+ (extensionSet?.inlineSyntaxes.isNotEmpty ?? false) {
+ if (blockSyntaxes != null) {
+ _blockSyntaxes.addAll(blockSyntaxes);
+ }
+ if (inlineSyntaxes != null) {
+ _inlineSyntaxes.addAll(inlineSyntaxes);
+ }
+
+ if (extensionSet == null) {
+ if (withDefaultBlockSyntaxes) {
+ _blockSyntaxes.addAll(ExtensionSet.commonMark.blockSyntaxes);
+ }
+
+ if (withDefaultInlineSyntaxes) {
+ _inlineSyntaxes.addAll(ExtensionSet.commonMark.inlineSyntaxes);
+ }
+ } else {
+ _blockSyntaxes.addAll(extensionSet.blockSyntaxes);
+ _inlineSyntaxes.addAll(extensionSet.inlineSyntaxes);
+ }
+ }
+
+ /// Parses the given [lines] of Markdown to a series of AST nodes.
+ List<Node> parseLines(List<String> lines) =>
+ parseLineList(lines.map(Line.new).toList());
+
+ /// Parses the given [text] to a series of AST nodes.
+ List<Node> parse(String text) => parseLineList(text.toLines());
+
+ /// Parses the given [lines] of [Line] to a series of AST nodes.
+ List<Node> parseLineList(List<Line> lines) {
+ final nodes = BlockParser(lines, this).parseLines();
+ _parseInlineContent(nodes);
+ // Do filter after parsing inline as we need ref count.
+ return _filterFootnotes(nodes);
+ }
+
+ /// Parses the given inline Markdown [text] to a series of AST nodes.
+ List<Node> parseInline(String text) => InlineParser(text, this).parse();
+
+ void _parseInlineContent(List<Node> nodes) {
+ for (var i = 0; i < nodes.length; i++) {
+ final node = nodes[i];
+ if (node is UnparsedContent) {
+ final inlineNodes = parseInline(node.textContent);
+ nodes.removeAt(i);
+ nodes.insertAll(i, inlineNodes);
+ i += inlineNodes.length - 1;
+ } else if (node is Element && node.children != null) {
+ _parseInlineContent(node.children!);
+ }
+ }
+ }
+
+ /// Footnotes could be defined in arbitrary positions of a document, we need
+ /// to distinguish them and put them behind; and every footnote definition
+ /// may have multiple backrefs, we need to append backrefs for it.
+ List<Node> _filterFootnotes(List<Node> nodes) {
+ final footnotes = <Element>[];
+ final blocks = <Node>[];
+ for (final node in nodes) {
+ if (node is Element &&
+ node.tag == 'li' &&
+ footnoteReferences.containsKey(node.footnoteLabel)) {
+ final label = node.footnoteLabel;
+ var count = 0;
+ if (label != null && (count = footnoteReferences[label] ?? 0) > 0) {
+ footnotes.add(node);
+ final children = node.children;
+ if (children != null) {
+ _appendBackref(children, Uri.encodeComponent(label), count);
+ }
+ }
+ } else {
+ blocks.add(node);
+ }
+ }
+
+ if (footnotes.isNotEmpty) {
+ // Sort footnotes by appearing order.
+ final ordinal = {
+ for (var i = 0; i < footnoteLabels.length; i++)
+ 'fn-${footnoteLabels[i]}': i,
+ };
+ footnotes.sort((l, r) {
+ final idl = l.attributes['id']?.toLowerCase() ?? '';
+ final idr = r.attributes['id']?.toLowerCase() ?? '';
+ return (ordinal[idl] ?? 0) - (ordinal[idr] ?? 0);
+ });
+ final list = Element('ol', footnotes);
+
+ // Ignore GFM attribute: <data-footnotes>.
+ final section = Element('section', [list])
+ ..attributes['class'] = 'footnotes';
+ blocks.add(section);
+ }
+ return blocks;
+ }
+
+ /// Generate backref nodes, append them to footnote definition's last child.
+ void _appendBackref(List<Node> children, String ref, int count) {
+ final refs = [
+ for (var i = 0; i < count; i++) ...[
+ Text(' '),
+ _ElementExt.footnoteAnchor(ref, i)
+ ]
+ ];
+ if (children.isEmpty) {
+ children.addAll(refs);
+ } else {
+ final last = children.last;
+ if (last is Element) {
+ last.children?.addAll(refs);
+ } else {
+ children.last = Element('p', [last, ...refs]);
+ }
+ }
+ }
+}
+
+extension _ElementExt on Element {
+ static Element footnoteAnchor(String ref, int i) {
+ final num = '${i + 1}';
+ final suffix = i > 0 ? '-$num' : '';
+ final e = Element.empty('tag');
+ e.match;
+ return Element('a', [
+ Text('\u21a9'),
+ if (i > 0)
+ Element('sup', [Text(num)])..attributes['class'] = 'footnote-ref',
+ ])
+ // Ignore GFM's attributes:
+ // <data-footnote-backref aria-label="Back to content">.
+ ..attributes['href'] = '#fnref-$ref$suffix'
+ ..attributes['class'] = 'footnote-backref';
+ }
+
+ String get match => tag;
+}
+
+/// A [link reference
+/// definition](https://spec.commonmark.org/0.30/#link-reference-definitions).
+class LinkReference {
+ /// The [link label](https://spec.commonmark.org/0.30/#link-label).
+ ///
+ /// Temporarily, this class is also being used to represent the link data for
+ /// an inline link (the destination and title), but this should change before
+ /// the package is released.
+ final String label;
+
+ /// The [link destination](https://spec.commonmark.org/0.30/#link-destination).
+ final String destination;
+
+ /// The [link title](https://spec.commonmark.org/0.30/#link-title).
+ final String? title;
+
+ /// Construct a new [LinkReference], with all necessary fields.
+ ///
+ /// If the parsed link reference definition does not include a title, use
+ /// `null` for the [title] parameter.
+ LinkReference(this.label, this.destination, this.title);
+}
diff --git a/pkgs/markdown/lib/src/emojis.dart b/pkgs/markdown/lib/src/emojis.dart
new file mode 100644
index 0000000..cca1a28
--- /dev/null
+++ b/pkgs/markdown/lib/src/emojis.dart
@@ -0,0 +1,1904 @@
+// GENERATED FILE. DO NOT EDIT.
+//
+// This file was generated from GitHub's emoji API list endpoint:
+// https://api.github.com/emojis
+// at 2022-05-03 01:45:16.587812 by the script, tool/update_github_emojis.dart.
+
+const emojis = <String, String>{
+ '+1': '👍',
+ '-1': '👎',
+ '100': '💯',
+ '1234': '🔢',
+ '1st_place_medal': '🥇',
+ '2nd_place_medal': '🥈',
+ '3rd_place_medal': '🥉',
+ '8ball': '🎱',
+ 'a': '🅰️',
+ 'ab': '🆎',
+ 'abacus': '🧮',
+ 'abc': '🔤',
+ 'abcd': '🔡',
+ 'accept': '🉑',
+ 'accordion': '🪗',
+ 'adhesive_bandage': '🩹',
+ 'adult': '🧑',
+ 'aerial_tramway': '🚡',
+ 'afghanistan': '🇦🇫',
+ 'airplane': '✈️',
+ 'aland_islands': '🇦🇽',
+ 'alarm_clock': '⏰',
+ 'albania': '🇦🇱',
+ 'alembic': '⚗',
+ 'algeria': '🇩🇿',
+ 'alien': '👽',
+ 'ambulance': '🚑',
+ 'american_samoa': '🇦🇸',
+ 'amphora': '🏺',
+ 'anatomical_heart': '🫀',
+ 'anchor': '⚓',
+ 'andorra': '🇦🇩',
+ 'angel': '👼',
+ 'anger': '💢',
+ 'angola': '🇦🇴',
+ 'angry': '😠',
+ 'anguilla': '🇦🇮',
+ 'anguished': '😧',
+ 'ant': '🐜',
+ 'antarctica': '🇦🇶',
+ 'antigua_barbuda': '🇦🇬',
+ 'apple': '🍎',
+ 'aquarius': '♒',
+ 'argentina': '🇦🇷',
+ 'aries': '♈',
+ 'armenia': '🇦🇲',
+ 'arrow_backward': '◀️',
+ 'arrow_double_down': '⏬',
+ 'arrow_double_up': '⏫',
+ 'arrow_down': '⬇️',
+ 'arrow_down_small': '🔽',
+ 'arrow_forward': '▶️',
+ 'arrow_heading_down': '⤵️',
+ 'arrow_heading_up': '⤴️',
+ 'arrow_left': '⬅️',
+ 'arrow_lower_left': '↙️',
+ 'arrow_lower_right': '↘️',
+ 'arrow_right': '➡️',
+ 'arrow_right_hook': '↪️',
+ 'arrow_up': '⬆️',
+ 'arrow_up_down': '↕️',
+ 'arrow_up_small': '🔼',
+ 'arrow_upper_left': '↖️',
+ 'arrow_upper_right': '↗️',
+ 'arrows_clockwise': '🔃',
+ 'arrows_counterclockwise': '🔄',
+ 'art': '🎨',
+ 'articulated_lorry': '🚛',
+ 'artificial_satellite': '🛰',
+ 'artist': '🧑️🎨',
+ 'aruba': '🇦🇼',
+ 'ascension_island': '🇦️🇨',
+ 'asterisk': '*⃣',
+ 'astonished': '😲',
+ 'astronaut': '🧑️🚀',
+ 'athletic_shoe': '👟',
+ 'atm': '🏧',
+ 'atom_symbol': '⚛',
+ 'australia': '🇦🇺',
+ 'austria': '🇦🇹',
+ 'auto_rickshaw': '🛺',
+ 'avocado': '🥑',
+ 'axe': '🪓',
+ 'azerbaijan': '🇦🇿',
+ 'b': '🅱️',
+ 'baby': '👶',
+ 'baby_bottle': '🍼',
+ 'baby_chick': '🐤',
+ 'baby_symbol': '🚼',
+ 'back': '🔙',
+ 'bacon': '🥓',
+ 'badger': '🦡',
+ 'badminton': '🏸',
+ 'bagel': '🥯',
+ 'baggage_claim': '🛄',
+ 'baguette_bread': '🥖',
+ 'bahamas': '🇧🇸',
+ 'bahrain': '🇧🇭',
+ 'balance_scale': '⚖',
+ 'bald_man': '👨️🦲',
+ 'bald_woman': '👩️🦲',
+ 'ballet_shoes': '🩰',
+ 'balloon': '🎈',
+ 'ballot_box': '🗳',
+ 'ballot_box_with_check': '☑️',
+ 'bamboo': '🎍',
+ 'banana': '🍌',
+ 'bangbang': '‼️',
+ 'bangladesh': '🇧🇩',
+ 'banjo': '🪕',
+ 'bank': '🏦',
+ 'bar_chart': '📊',
+ 'barbados': '🇧🇧',
+ 'barber': '💈',
+ 'baseball': '⚾',
+ 'basket': '🧺',
+ 'basketball': '🏀',
+ 'basketball_man': '⛹',
+ 'basketball_woman': '⛹️♀️',
+ 'bat': '🦇',
+ 'bath': '🛀',
+ 'bathtub': '🛁',
+ 'battery': '🔋',
+ 'beach_umbrella': '🏖',
+ 'bear': '🐻',
+ 'bearded_person': '🧔',
+ 'beaver': '🦫',
+ 'bed': '🛏',
+ 'bee': '🐝',
+ 'beer': '🍺',
+ 'beers': '🍻',
+ 'beetle': '🪲',
+ 'beginner': '🔰',
+ 'belarus': '🇧🇾',
+ 'belgium': '🇧🇪',
+ 'belize': '🇧🇿',
+ 'bell': '🔔',
+ 'bell_pepper': '🫑',
+ 'bellhop_bell': '🛎',
+ 'benin': '🇧🇯',
+ 'bento': '🍱',
+ 'bermuda': '🇧🇲',
+ 'beverage_box': '🧃',
+ 'bhutan': '🇧🇹',
+ 'bicyclist': '🚴',
+ 'bike': '🚲',
+ 'biking_man': '🚴',
+ 'biking_woman': '🚴♀️',
+ 'bikini': '👙',
+ 'billed_cap': '🧢',
+ 'billed_hat': '🧢',
+ 'biohazard': '☣',
+ 'bird': '🐦',
+ 'birthday': '🎂',
+ 'bison': '🦬',
+ 'black_cat': '🐈️⬛',
+ 'black_circle': '⚫',
+ 'black_flag': '🏴',
+ 'black_heart': '🖤',
+ 'black_joker': '🃏',
+ 'black_large_square': '⬛',
+ 'black_medium_small_square': '◾',
+ 'black_medium_square': '◼️',
+ 'black_nib': '✒️',
+ 'black_small_square': '▪️',
+ 'black_square_button': '🔲',
+ 'blond_haired_man': '👱️♂',
+ 'blond_haired_person': '👱',
+ 'blond_haired_woman': '👱️♀',
+ 'blonde_man': '👱',
+ 'blonde_woman': '👱♀️',
+ 'blossom': '🌼',
+ 'blowfish': '🐡',
+ 'blue_book': '📘',
+ 'blue_car': '🚙',
+ 'blue_heart': '💙',
+ 'blue_square': '🟦',
+ 'blueberries': '🫐',
+ 'blush': '😊',
+ 'boar': '🐗',
+ 'boat': '⛵',
+ 'bolivia': '🇧🇴',
+ 'bomb': '💣',
+ 'bone': '🦴',
+ 'book': '📖',
+ 'bookmark': '🔖',
+ 'bookmark_tabs': '📑',
+ 'books': '📚',
+ 'boom': '💥',
+ 'boomerang': '🪃',
+ 'boot': '👢',
+ 'bosnia_herzegovina': '🇧🇦',
+ 'botswana': '🇧🇼',
+ 'bouncing_ball_man': '⛹️♂',
+ 'bouncing_ball_person': '⛹',
+ 'bouncing_ball_woman': '⛹️♀',
+ 'bouquet': '💐',
+ 'bouvet_island': '🇧️🇻',
+ 'bow': '🙇',
+ 'bow_and_arrow': '🏹',
+ 'bowing_man': '🙇',
+ 'bowing_woman': '🙇♀️',
+ 'bowl_with_spoon': '🥣',
+ 'bowling': '🎳',
+ 'boxing_glove': '🥊',
+ 'boy': '👦',
+ 'brain': '🧠',
+ 'brazil': '🇧🇷',
+ 'bread': '🍞',
+ 'breast_feeding': '🤱',
+ 'breastfeeding': '🤱',
+ 'brick': '🧱',
+ 'bricks': '🧱',
+ 'bride_with_veil': '👰',
+ 'bridge_at_night': '🌉',
+ 'briefcase': '💼',
+ 'british_indian_ocean_territory': '🇮🇴',
+ 'british_virgin_islands': '🇻🇬',
+ 'broccoli': '🥦',
+ 'broken_heart': '💔',
+ 'broom': '🧹',
+ 'brown_circle': '🟤',
+ 'brown_heart': '🤎',
+ 'brown_square': '🟫',
+ 'brunei': '🇧🇳',
+ 'bubble_tea': '🧋',
+ 'bucket': '🪣',
+ 'bug': '🐛',
+ 'building_construction': '🏗',
+ 'bulb': '💡',
+ 'bulgaria': '🇧🇬',
+ 'bullettrain_front': '🚅',
+ 'bullettrain_side': '🚄',
+ 'burkina_faso': '🇧🇫',
+ 'burrito': '🌯',
+ 'burundi': '🇧🇮',
+ 'bus': '🚌',
+ 'business_suit_levitating': '🕴',
+ 'busstop': '🚏',
+ 'bust_in_silhouette': '👤',
+ 'busts_in_silhouette': '👥',
+ 'butter': '🧈',
+ 'butterfly': '🦋',
+ 'cactus': '🌵',
+ 'cake': '🍰',
+ 'calendar': '📆',
+ 'call_me_hand': '🤙',
+ 'calling': '📲',
+ 'cambodia': '🇰🇭',
+ 'camel': '🐫',
+ 'camera': '📷',
+ 'camera_flash': '📸',
+ 'cameroon': '🇨🇲',
+ 'camping': '🏕',
+ 'canada': '🇨🇦',
+ 'canary_islands': '🇮🇨',
+ 'cancer': '♋',
+ 'candle': '🕯',
+ 'candy': '🍬',
+ 'canned_food': '🥫',
+ 'canoe': '🛶',
+ 'cape_verde': '🇨🇻',
+ 'capital_abcd': '🔠',
+ 'capricorn': '♑',
+ 'car': '🚗',
+ 'card_file_box': '🗃',
+ 'card_index': '📇',
+ 'card_index_dividers': '🗂',
+ 'caribbean_netherlands': '🇧🇶',
+ 'carousel_horse': '🎠',
+ 'carpentry_saw': '🪚',
+ 'carrot': '🥕',
+ 'cartwheeling': '🤸',
+ 'cat': '🐱',
+ 'cat2': '🐈',
+ 'cayman_islands': '🇰🇾',
+ 'cd': '💿',
+ 'central_african_republic': '🇨🇫',
+ 'ceuta_melilla': '🇪️🇦',
+ 'chad': '🇹🇩',
+ 'chains': '⛓',
+ 'chair': '🪑',
+ 'champagne': '🍾',
+ 'chart': '💹',
+ 'chart_with_downwards_trend': '📉',
+ 'chart_with_upwards_trend': '📈',
+ 'checkered_flag': '🏁',
+ 'cheese': '🧀',
+ 'cherries': '🍒',
+ 'cherry_blossom': '🌸',
+ 'chess_pawn': '♟',
+ 'chestnut': '🌰',
+ 'chicken': '🐔',
+ 'child': '🧒',
+ 'children_crossing': '🚸',
+ 'chile': '🇨🇱',
+ 'chipmunk': '🐿',
+ 'chocolate_bar': '🍫',
+ 'chopsticks': '🥢',
+ 'christmas_island': '🇨🇽',
+ 'christmas_tree': '🎄',
+ 'church': '⛪',
+ 'cinema': '🎦',
+ 'circus_tent': '🎪',
+ 'city_sunrise': '🌇',
+ 'city_sunset': '🌆',
+ 'cityscape': '🏙',
+ 'cl': '🆑',
+ 'clamp': '🗜',
+ 'clap': '👏',
+ 'clapper': '🎬',
+ 'classical_building': '🏛',
+ 'climbing': '🧗',
+ 'climbing_man': '🧗♂️',
+ 'climbing_woman': '🧗♀️',
+ 'clinking_glasses': '🥂',
+ 'clipboard': '📋',
+ 'clipperton_island': '🇨️🇵',
+ 'clock1': '🕐',
+ 'clock10': '🕙',
+ 'clock1030': '🕥',
+ 'clock11': '🕚',
+ 'clock1130': '🕦',
+ 'clock12': '🕛',
+ 'clock1230': '🕧',
+ 'clock130': '🕜',
+ 'clock2': '🕑',
+ 'clock230': '🕝',
+ 'clock3': '🕒',
+ 'clock330': '🕞',
+ 'clock4': '🕓',
+ 'clock430': '🕟',
+ 'clock5': '🕔',
+ 'clock530': '🕠',
+ 'clock6': '🕕',
+ 'clock630': '🕡',
+ 'clock7': '🕖',
+ 'clock730': '🕢',
+ 'clock8': '🕗',
+ 'clock830': '🕣',
+ 'clock9': '🕘',
+ 'clock930': '🕤',
+ 'closed_book': '📕',
+ 'closed_lock_with_key': '🔐',
+ 'closed_umbrella': '🌂',
+ 'cloud': '☁️',
+ 'cloud_with_lightning': '🌩',
+ 'cloud_with_lightning_and_rain': '⛈',
+ 'cloud_with_rain': '🌧',
+ 'cloud_with_snow': '🌨',
+ 'clown_face': '🤡',
+ 'clubs': '♣️',
+ 'cn': '🇨🇳',
+ 'coat': '🧥',
+ 'cockroach': '🪳',
+ 'cocktail': '🍸',
+ 'coconut': '🥥',
+ 'cocos_islands': '🇨🇨',
+ 'coffee': '☕',
+ 'coffin': '⚰',
+ 'coin': '🪙',
+ 'cold': '🥶',
+ 'cold_face': '🥶',
+ 'cold_sweat': '😰',
+ 'collision': '💥',
+ 'colombia': '🇨🇴',
+ 'comet': '☄',
+ 'comoros': '🇰🇲',
+ 'compass': '🧭',
+ 'computer': '💻',
+ 'computer_mouse': '🖱',
+ 'confetti_ball': '🎊',
+ 'confounded': '😖',
+ 'confused': '😕',
+ 'congo_brazzaville': '🇨🇬',
+ 'congo_kinshasa': '🇨🇩',
+ 'congratulations': '㊗️',
+ 'construction': '🚧',
+ 'construction_worker': '👷',
+ 'construction_worker_man': '👷',
+ 'construction_worker_woman': '👷♀️',
+ 'control_knobs': '🎛',
+ 'convenience_store': '🏪',
+ 'cook': '🧑️🍳',
+ 'cook_islands': '🇨🇰',
+ 'cookie': '🍪',
+ 'cool': '🆒',
+ 'cop': '👮',
+ 'copyright': '©️',
+ 'corn': '🌽',
+ 'costa_rica': '🇨🇷',
+ 'cote_divoire': '🇨🇮',
+ 'couch_and_lamp': '🛋',
+ 'couple': '👫',
+ 'couple_with_heart': '💑',
+ 'couple_with_heart_man_man': '👨❤️👨',
+ 'couple_with_heart_woman_man': '💑',
+ 'couple_with_heart_woman_woman': '👩❤️👩',
+ 'couplekiss': '💏',
+ 'couplekiss_man_man': '👨❤️💋👨',
+ 'couplekiss_man_woman': '💏',
+ 'couplekiss_woman_woman': '👩❤️💋👩',
+ 'cow': '🐮',
+ 'cow2': '🐄',
+ 'cowboy_hat_face': '🤠',
+ 'crab': '🦀',
+ 'crayon': '🖍',
+ 'credit_card': '💳',
+ 'crescent_moon': '🌙',
+ 'cricket': '🦗',
+ 'cricket_game': '🏏',
+ 'croatia': '🇭🇷',
+ 'crocodile': '🐊',
+ 'croissant': '🥐',
+ 'crossed_fingers': '🤞',
+ 'crossed_flags': '🎌',
+ 'crossed_swords': '⚔',
+ 'crown': '👑',
+ 'cry': '😢',
+ 'crying_cat_face': '😿',
+ 'crystal_ball': '🔮',
+ 'cuba': '🇨🇺',
+ 'cucumber': '🥒',
+ 'cup_with_straw': '🥤',
+ 'cupcake': '🧁',
+ 'cupid': '💘',
+ 'curacao': '🇨🇼',
+ 'curling_stone': '🥌',
+ 'curly_haired_man': '👨️🦱',
+ 'curly_haired_woman': '👩️🦱',
+ 'curly_loop': '➰',
+ 'currency_exchange': '💱',
+ 'curry': '🍛',
+ 'cursing_face': '🤬',
+ 'custard': '🍮',
+ 'customs': '🛃',
+ 'cut_of_meat': '🥩',
+ 'cyclone': '🌀',
+ 'cyprus': '🇨🇾',
+ 'czech_republic': '🇨🇿',
+ 'dagger': '🗡',
+ 'dancer': '💃',
+ 'dancers': '👯',
+ 'dancing_men': '👯♂️',
+ 'dancing_women': '👯',
+ 'dango': '🍡',
+ 'dark_sunglasses': '🕶',
+ 'dart': '🎯',
+ 'dash': '💨',
+ 'date': '📅',
+ 'de': '🇩🇪',
+ 'deaf_man': '🧏️♂',
+ 'deaf_person': '🧏',
+ 'deaf_woman': '🧏️♀',
+ 'deciduous_tree': '🌳',
+ 'deer': '🦌',
+ 'denmark': '🇩🇰',
+ 'department_store': '🏬',
+ 'derelict_house': '🏚',
+ 'desert': '🏜',
+ 'desert_island': '🏝',
+ 'desktop_computer': '🖥',
+ 'detective': '🕵',
+ 'diamond_shape_with_a_dot_inside': '💠',
+ 'diamonds': '♦️',
+ 'diego_garcia': '🇩️🇬',
+ 'disappointed': '😞',
+ 'disappointed_relieved': '😥',
+ 'disguised_face': '🥸',
+ 'diving_mask': '🤿',
+ 'diya_lamp': '🪔',
+ 'dizzy': '💫',
+ 'dizzy_face': '😵',
+ 'djibouti': '🇩🇯',
+ 'dna': '🧬',
+ 'do_not_litter': '🚯',
+ 'dodo': '🦤',
+ 'dog': '🐶',
+ 'dog2': '🐕',
+ 'dollar': '💵',
+ 'dolls': '🎎',
+ 'dolphin': '🐬',
+ 'dominica': '🇩🇲',
+ 'dominican_republic': '🇩🇴',
+ 'door': '🚪',
+ 'doughnut': '🍩',
+ 'dove': '🕊',
+ 'dragon': '🐉',
+ 'dragon_face': '🐲',
+ 'dress': '👗',
+ 'dromedary_camel': '🐪',
+ 'drooling_face': '🤤',
+ 'drop_of_blood': '🩸',
+ 'droplet': '💧',
+ 'drum': '🥁',
+ 'duck': '🦆',
+ 'dumpling': '🥟',
+ 'dvd': '📀',
+ 'e-mail': '📧',
+ 'eagle': '🦅',
+ 'ear': '👂',
+ 'ear_of_rice': '🌾',
+ 'ear_with_hearing_aid': '🦻',
+ 'earth_africa': '🌍',
+ 'earth_americas': '🌎',
+ 'earth_asia': '🌏',
+ 'ecuador': '🇪🇨',
+ 'egg': '🥚',
+ 'eggplant': '🍆',
+ 'egypt': '🇪🇬',
+ 'eight': '8️⃣',
+ 'eight_pointed_black_star': '✴️',
+ 'eight_spoked_asterisk': '✳️',
+ 'eject_button': '⏏️',
+ 'el_salvador': '🇸🇻',
+ 'electric_plug': '🔌',
+ 'elephant': '🐘',
+ 'elevator': '🛗',
+ 'elf': '🧝',
+ 'elf_man': '🧝️♂',
+ 'elf_woman': '🧝️♀',
+ 'email': '✉️',
+ 'end': '🔚',
+ 'england': '🏴',
+ 'envelope': '✉',
+ 'envelope_with_arrow': '📩',
+ 'equatorial_guinea': '🇬🇶',
+ 'eritrea': '🇪🇷',
+ 'es': '🇪🇸',
+ 'estonia': '🇪🇪',
+ 'ethiopia': '🇪🇹',
+ 'eu': '🇪🇺',
+ 'euro': '💶',
+ 'european_castle': '🏰',
+ 'european_post_office': '🏤',
+ 'european_union': '🇪️🇺',
+ 'evergreen_tree': '🌲',
+ 'exclamation': '❗',
+ 'exploding_head': '🤯',
+ 'expressionless': '😑',
+ 'eye': '👁',
+ 'eye_speech_bubble': '👁️🗨',
+ 'eyeglasses': '👓',
+ 'eyes': '👀',
+ 'face_exhaling': '😮️💨',
+ 'face_in_clouds': '😶️🌫',
+ 'face_with_head_bandage': '🤕',
+ 'face_with_spiral_eyes': '😵️💫',
+ 'face_with_thermometer': '🤒',
+ 'facepalm': '🤦',
+ 'facepunch': '👊',
+ 'factory': '🏭',
+ 'factory_worker': '🧑️🏭',
+ 'fairy': '🧚',
+ 'fairy_man': '🧚️♂',
+ 'fairy_woman': '🧚️♀',
+ 'falafel': '🧆',
+ 'falkland_islands': '🇫🇰',
+ 'fallen_leaf': '🍂',
+ 'family': '👪',
+ 'family_man_boy': '👨👦',
+ 'family_man_boy_boy': '👨👦👦',
+ 'family_man_girl': '👨👧',
+ 'family_man_girl_boy': '👨👧👦',
+ 'family_man_girl_girl': '👨👧👧',
+ 'family_man_man_boy': '👨👨👦',
+ 'family_man_man_boy_boy': '👨👨👦👦',
+ 'family_man_man_girl': '👨👨👧',
+ 'family_man_man_girl_boy': '👨👨👧👦',
+ 'family_man_man_girl_girl': '👨👨👧👧',
+ 'family_man_woman_boy': '👪',
+ 'family_man_woman_boy_boy': '👨👩👦👦',
+ 'family_man_woman_girl': '👨👩👧',
+ 'family_man_woman_girl_boy': '👨👩👧👦',
+ 'family_man_woman_girl_girl': '👨👩👧👧',
+ 'family_woman_boy': '👩👦',
+ 'family_woman_boy_boy': '👩👦👦',
+ 'family_woman_girl': '👩👧',
+ 'family_woman_girl_boy': '👩👧👦',
+ 'family_woman_girl_girl': '👩👧👧',
+ 'family_woman_woman_boy': '👩👩👦',
+ 'family_woman_woman_boy_boy': '👩👩👦👦',
+ 'family_woman_woman_girl': '👩👩👧',
+ 'family_woman_woman_girl_boy': '👩👩👧👦',
+ 'family_woman_woman_girl_girl': '👩👩👧👧',
+ 'farmer': '🧑️🌾',
+ 'faroe_islands': '🇫🇴',
+ 'fast_forward': '⏩',
+ 'fax': '📠',
+ 'fearful': '😨',
+ 'feather': '🪶',
+ 'feet': '🐾',
+ 'female_detective': '🕵️♀️',
+ 'female_sign': '♀',
+ 'ferris_wheel': '🎡',
+ 'ferry': '⛴',
+ 'field_hockey': '🏑',
+ 'fiji': '🇫🇯',
+ 'file_cabinet': '🗄',
+ 'file_folder': '📁',
+ 'film_projector': '📽',
+ 'film_strip': '🎞',
+ 'finland': '🇫🇮',
+ 'fire': '🔥',
+ 'fire_engine': '🚒',
+ 'fire_extinguisher': '🧯',
+ 'firecracker': '🧨',
+ 'firefighter': '🧑️🚒',
+ 'fireworks': '🎆',
+ 'first_quarter_moon': '🌓',
+ 'first_quarter_moon_with_face': '🌛',
+ 'fish': '🐟',
+ 'fish_cake': '🍥',
+ 'fishing_pole_and_fish': '🎣',
+ 'fist': '✊',
+ 'fist_left': '🤛',
+ 'fist_oncoming': '👊',
+ 'fist_raised': '✊',
+ 'fist_right': '🤜',
+ 'five': '5️⃣',
+ 'flags': '🎏',
+ 'flamingo': '🦩',
+ 'flashlight': '🔦',
+ 'flat_shoe': '🥿',
+ 'flatbread': '🫓',
+ 'fleur_de_lis': '⚜',
+ 'flight_arrival': '🛬',
+ 'flight_departure': '🛫',
+ 'flipper': '🐬',
+ 'floppy_disk': '💾',
+ 'flower_playing_cards': '🎴',
+ 'flushed': '😳',
+ 'fly': '🪰',
+ 'flying_disc': '🥏',
+ 'flying_saucer': '🛸',
+ 'fog': '🌫',
+ 'foggy': '🌁',
+ 'fondue': '🫕',
+ 'foot': '🦶',
+ 'football': '🏈',
+ 'footprints': '👣',
+ 'fork_and_knife': '🍴',
+ 'fortune_cookie': '🥠',
+ 'fountain': '⛲',
+ 'fountain_pen': '🖋',
+ 'four': '4️⃣',
+ 'four_leaf_clover': '🍀',
+ 'fox_face': '🦊',
+ 'fr': '🇫🇷',
+ 'framed_picture': '🖼',
+ 'free': '🆓',
+ 'french_guiana': '🇬🇫',
+ 'french_polynesia': '🇵🇫',
+ 'french_southern_territories': '🇹🇫',
+ 'fried_egg': '🍳',
+ 'fried_shrimp': '🍤',
+ 'fries': '🍟',
+ 'frog': '🐸',
+ 'frowning': '😦',
+ 'frowning_face': '☹',
+ 'frowning_man': '🙍♂️',
+ 'frowning_person': '🙍',
+ 'frowning_woman': '🙍',
+ 'fu': '🖕',
+ 'fuelpump': '⛽',
+ 'full_moon': '🌕',
+ 'full_moon_with_face': '🌝',
+ 'funeral_urn': '⚱',
+ 'gabon': '🇬🇦',
+ 'gambia': '🇬🇲',
+ 'game_die': '🎲',
+ 'garlic': '🧄',
+ 'gb': '🇬️🇧',
+ 'gear': '⚙',
+ 'gem': '💎',
+ 'gemini': '♊',
+ 'genie': '🧞',
+ 'genie_man': '🧞️♂',
+ 'genie_woman': '🧞️♀',
+ 'georgia': '🇬🇪',
+ 'ghana': '🇬🇭',
+ 'ghost': '👻',
+ 'gibraltar': '🇬🇮',
+ 'gift': '🎁',
+ 'gift_heart': '💝',
+ 'giraffe': '🦒',
+ 'girl': '👧',
+ 'globe_with_meridians': '🌐',
+ 'gloves': '🧤',
+ 'goal_net': '🥅',
+ 'goat': '🐐',
+ 'goggles': '🥽',
+ 'golf': '⛳',
+ 'golfing': '🏌',
+ 'golfing_man': '🏌',
+ 'golfing_woman': '🏌️♀️',
+ 'gorilla': '🦍',
+ 'grapes': '🍇',
+ 'grasshopper': '🦗',
+ 'greece': '🇬🇷',
+ 'green_apple': '🍏',
+ 'green_book': '📗',
+ 'green_circle': '🟢',
+ 'green_heart': '💚',
+ 'green_salad': '🥗',
+ 'green_square': '🟩',
+ 'greenland': '🇬🇱',
+ 'grenada': '🇬🇩',
+ 'grey_exclamation': '❕',
+ 'grey_question': '❔',
+ 'grimacing': '😬',
+ 'grin': '😁',
+ 'grinning': '😀',
+ 'guadeloupe': '🇬🇵',
+ 'guam': '🇬🇺',
+ 'guard': '💂',
+ 'guardsman': '💂',
+ 'guardswoman': '💂♀️',
+ 'guatemala': '🇬🇹',
+ 'guernsey': '🇬🇬',
+ 'guide_dog': '🦮',
+ 'guinea': '🇬🇳',
+ 'guinea_bissau': '🇬🇼',
+ 'guitar': '🎸',
+ 'gun': '🔫',
+ 'guyana': '🇬🇾',
+ 'haircut': '💇',
+ 'haircut_man': '💇♂️',
+ 'haircut_woman': '💇',
+ 'haiti': '🇭🇹',
+ 'hamburger': '🍔',
+ 'hammer': '🔨',
+ 'hammer_and_pick': '⚒',
+ 'hammer_and_wrench': '🛠',
+ 'hamster': '🐹',
+ 'hand': '✋',
+ 'hand_over_mouth': '🤭',
+ 'handbag': '👜',
+ 'handball_person': '🤾',
+ 'handshake': '🤝',
+ 'hankey': '💩',
+ 'hash': '#️⃣',
+ 'hatched_chick': '🐥',
+ 'hatching_chick': '🐣',
+ 'headphones': '🎧',
+ 'headstone': '🪦',
+ 'health_worker': '🧑️⚕',
+ 'hear_no_evil': '🙉',
+ 'heard_mcdonald_islands': '🇭️🇲',
+ 'heart': '❤️',
+ 'heart_decoration': '💟',
+ 'heart_eyes': '😍',
+ 'heart_eyes_cat': '😻',
+ 'heart_on_fire': '❤️🔥',
+ 'heartbeat': '💓',
+ 'heartpulse': '💗',
+ 'hearts': '♥️',
+ 'heavy_check_mark': '✔️',
+ 'heavy_division_sign': '➗',
+ 'heavy_dollar_sign': '💲',
+ 'heavy_exclamation_mark': '❗',
+ 'heavy_heart_exclamation': '❣',
+ 'heavy_minus_sign': '➖',
+ 'heavy_multiplication_x': '✖️',
+ 'heavy_plus_sign': '➕',
+ 'hedgehog': '🦔',
+ 'helicopter': '🚁',
+ 'herb': '🌿',
+ 'hibiscus': '🌺',
+ 'high_brightness': '🔆',
+ 'high_heel': '👠',
+ 'hiking_boot': '🥾',
+ 'hindu_temple': '🛕',
+ 'hippopotamus': '🦛',
+ 'hocho': '🔪',
+ 'hole': '🕳',
+ 'honduras': '🇭🇳',
+ 'honey_pot': '🍯',
+ 'honeybee': '🐝',
+ 'hong_kong': '🇭🇰',
+ 'hook': '🪝',
+ 'horse': '🐴',
+ 'horse_racing': '🏇',
+ 'hospital': '🏥',
+ 'hot': '🥵',
+ 'hot_face': '🥵',
+ 'hot_pepper': '🌶',
+ 'hotdog': '🌭',
+ 'hotel': '🏨',
+ 'hotsprings': '♨️',
+ 'hourglass': '⌛',
+ 'hourglass_flowing_sand': '⏳',
+ 'house': '🏠',
+ 'house_with_garden': '🏡',
+ 'houses': '🏘',
+ 'hugs': '🤗',
+ 'hungary': '🇭🇺',
+ 'hushed': '😯',
+ 'hut': '🛖',
+ 'ice_cream': '🍨',
+ 'ice_cube': '🧊',
+ 'ice_hockey': '🏒',
+ 'ice_skate': '⛸',
+ 'icecream': '🍦',
+ 'iceland': '🇮🇸',
+ 'id': '🆔',
+ 'ideograph_advantage': '🉐',
+ 'imp': '👿',
+ 'inbox_tray': '📥',
+ 'incoming_envelope': '📨',
+ 'india': '🇮🇳',
+ 'indonesia': '🇮🇩',
+ 'infinity': '♾',
+ 'information_desk_person': '💁',
+ 'information_source': 'ℹ️',
+ 'innocent': '😇',
+ 'interrobang': '⁉️',
+ 'iphone': '📱',
+ 'iran': '🇮🇷',
+ 'iraq': '🇮🇶',
+ 'ireland': '🇮🇪',
+ 'isle_of_man': '🇮🇲',
+ 'israel': '🇮🇱',
+ 'it': '🇮🇹',
+ 'izakaya_lantern': '🏮',
+ 'jack_o_lantern': '🎃',
+ 'jamaica': '🇯🇲',
+ 'japan': '🗾',
+ 'japanese_castle': '🏯',
+ 'japanese_goblin': '👺',
+ 'japanese_ogre': '👹',
+ 'jeans': '👖',
+ 'jersey': '🇯🇪',
+ 'jigsaw': '🧩',
+ 'jordan': '🇯🇴',
+ 'joy': '😂',
+ 'joy_cat': '😹',
+ 'joystick': '🕹',
+ 'jp': '🇯🇵',
+ 'judge': '🧑️⚖',
+ 'juggling_person': '🤹',
+ 'kaaba': '🕋',
+ 'kangaroo': '🦘',
+ 'kazakhstan': '🇰🇿',
+ 'kenya': '🇰🇪',
+ 'key': '🔑',
+ 'keyboard': '⌨',
+ 'keycap_ten': '🔟',
+ 'kick_scooter': '🛴',
+ 'kimono': '👘',
+ 'kiribati': '🇰🇮',
+ 'kiss': '💋',
+ 'kissing': '😗',
+ 'kissing_cat': '😽',
+ 'kissing_closed_eyes': '😚',
+ 'kissing_heart': '😘',
+ 'kissing_smiling_eyes': '😙',
+ 'kite': '🪁',
+ 'kiwi_fruit': '🥝',
+ 'kneeling_man': '🧎️♂',
+ 'kneeling_person': '🧎',
+ 'kneeling_woman': '🧎️♀',
+ 'knife': '🔪',
+ 'knot': '🪢',
+ 'koala': '🐨',
+ 'koko': '🈁',
+ 'kosovo': '🇽🇰',
+ 'kr': '🇰🇷',
+ 'kuwait': '🇰🇼',
+ 'kyrgyzstan': '🇰🇬',
+ 'lab_coat': '🥼',
+ 'labcoat': '🥼',
+ 'label': '🏷',
+ 'lacrosse': '🥍',
+ 'ladder': '🪜',
+ 'lady_beetle': '🐞',
+ 'lantern': '🏮',
+ 'laos': '🇱🇦',
+ 'large_blue_circle': '🔵',
+ 'large_blue_diamond': '🔷',
+ 'large_orange_diamond': '🔶',
+ 'last_quarter_moon': '🌗',
+ 'last_quarter_moon_with_face': '🌜',
+ 'latin_cross': '✝',
+ 'latvia': '🇱🇻',
+ 'laughing': '😆',
+ 'leafy_green': '🥬',
+ 'leafy_greens': '🥬',
+ 'leaves': '🍃',
+ 'lebanon': '🇱🇧',
+ 'ledger': '📒',
+ 'left_luggage': '🛅',
+ 'left_right_arrow': '↔️',
+ 'left_speech_bubble': '🗨',
+ 'leftwards_arrow_with_hook': '↩️',
+ 'leg': '🦵',
+ 'lemon': '🍋',
+ 'leo': '♌',
+ 'leopard': '🐆',
+ 'lesotho': '🇱🇸',
+ 'level_slider': '🎚',
+ 'liberia': '🇱🇷',
+ 'libra': '♎',
+ 'libya': '🇱🇾',
+ 'liechtenstein': '🇱🇮',
+ 'light_rail': '🚈',
+ 'link': '🔗',
+ 'lion': '🦁',
+ 'lips': '👄',
+ 'lipstick': '💄',
+ 'lithuania': '🇱🇹',
+ 'lizard': '🦎',
+ 'llama': '🦙',
+ 'lobster': '🦞',
+ 'lock': '🔒',
+ 'lock_with_ink_pen': '🔏',
+ 'lollipop': '🍭',
+ 'long_drum': '🪘',
+ 'loop': '➿',
+ 'lotion_bottle': '🧴',
+ 'lotus_position': '🧘',
+ 'lotus_position_man': '🧘️♂',
+ 'lotus_position_woman': '🧘️♀',
+ 'loud_sound': '🔊',
+ 'loudspeaker': '📢',
+ 'love_hotel': '🏩',
+ 'love_letter': '💌',
+ 'love_you': '🤟',
+ 'love_you_gesture': '🤟',
+ 'low_brightness': '🔅',
+ 'luggage': '🧳',
+ 'lungs': '🫁',
+ 'luxembourg': '🇱🇺',
+ 'lying_face': '🤥',
+ 'm': 'Ⓜ️',
+ 'macau': '🇲🇴',
+ 'macedonia': '🇲🇰',
+ 'madagascar': '🇲🇬',
+ 'mag': '🔍',
+ 'mag_right': '🔎',
+ 'mage': '🧙',
+ 'mage_man': '🧙️♂',
+ 'mage_woman': '🧙️♀',
+ 'magic_wand': '🪄',
+ 'magnet': '🧲',
+ 'mahjong': '🀄',
+ 'mailbox': '📫',
+ 'mailbox_closed': '📪',
+ 'mailbox_with_mail': '📬',
+ 'mailbox_with_no_mail': '📭',
+ 'malawi': '🇲🇼',
+ 'malaysia': '🇲🇾',
+ 'maldives': '🇲🇻',
+ 'male_detective': '🕵',
+ 'male_sign': '♂',
+ 'mali': '🇲🇱',
+ 'malta': '🇲🇹',
+ 'mammoth': '🦣',
+ 'man': '👨',
+ 'man_artist': '👨🎨',
+ 'man_astronaut': '👨🚀',
+ 'man_beard': '🧔️♂',
+ 'man_cartwheeling': '🤸♂️',
+ 'man_cook': '👨🍳',
+ 'man_dancing': '🕺',
+ 'man_elf': '🧝♂️',
+ 'man_facepalming': '🤦♂️',
+ 'man_factory_worker': '👨🏭',
+ 'man_fairy': '🧚♂️',
+ 'man_farmer': '👨🌾',
+ 'man_feeding_baby': '👨️🍼',
+ 'man_firefighter': '👨🚒',
+ 'man_genie': '🧞♂️',
+ 'man_health_worker': '👨⚕️',
+ 'man_in_lotus_position': '🧘♂️',
+ 'man_in_manual_wheelchair': '👨️🦽',
+ 'man_in_motorized_wheelchair': '👨️🦼',
+ 'man_in_steamy_room': '🧖♂️',
+ 'man_in_tuxedo': '🤵',
+ 'man_judge': '👨⚖️',
+ 'man_juggling': '🤹♂️',
+ 'man_mechanic': '👨🔧',
+ 'man_office_worker': '👨💼',
+ 'man_pilot': '👨✈️',
+ 'man_playing_handball': '🤾♂️',
+ 'man_playing_water_polo': '🤽♂️',
+ 'man_scientist': '👨🔬',
+ 'man_shrugging': '🤷♂️',
+ 'man_singer': '👨🎤',
+ 'man_student': '👨🎓',
+ 'man_superhero': '🦸♂️',
+ 'man_supervillain': '🦹♂️',
+ 'man_teacher': '👨🏫',
+ 'man_technologist': '👨💻',
+ 'man_vampire': '🧛♂️',
+ 'man_with_gua_pi_mao': '👲',
+ 'man_with_probing_cane': '👨️🦯',
+ 'man_with_turban': '👳',
+ 'man_with_veil': '👰️♂',
+ 'man_zombie': '🧟♂️',
+ 'mandarin': '🍊',
+ 'mango': '🥭',
+ 'mans_shoe': '👞',
+ 'mantelpiece_clock': '🕰',
+ 'manual_wheelchair': '🦽',
+ 'maple_leaf': '🍁',
+ 'marshall_islands': '🇲🇭',
+ 'martial_arts_uniform': '🥋',
+ 'martinique': '🇲🇶',
+ 'mask': '😷',
+ 'massage': '💆',
+ 'massage_man': '💆♂️',
+ 'massage_woman': '💆',
+ 'mate': '🧉',
+ 'mauritania': '🇲🇷',
+ 'mauritius': '🇲🇺',
+ 'mayotte': '🇾🇹',
+ 'meat_on_bone': '🍖',
+ 'mechanic': '🧑️🔧',
+ 'mechanical_arm': '🦾',
+ 'mechanical_leg': '🦿',
+ 'medal_military': '🎖',
+ 'medal_sports': '🏅',
+ 'medical_symbol': '⚕',
+ 'mega': '📣',
+ 'melon': '🍈',
+ 'memo': '📝',
+ 'men_wrestling': '🤼♂️',
+ 'mending_heart': '❤️🩹',
+ 'menorah': '🕎',
+ 'mens': '🚹',
+ 'mermaid': '🧜♀️',
+ 'merman': '🧜♂️',
+ 'merperson': '🧜',
+ 'metal': '🤘',
+ 'metro': '🚇',
+ 'mexico': '🇲🇽',
+ 'microbe': '🦠',
+ 'micronesia': '🇫🇲',
+ 'microphone': '🎤',
+ 'microscope': '🔬',
+ 'middle_finger': '🖕',
+ 'military_helmet': '🪖',
+ 'milk_glass': '🥛',
+ 'milky_way': '🌌',
+ 'minibus': '🚐',
+ 'minidisc': '💽',
+ 'mirror': '🪞',
+ 'mobile_phone_off': '📴',
+ 'moldova': '🇲🇩',
+ 'monaco': '🇲🇨',
+ 'money_mouth_face': '🤑',
+ 'money_with_wings': '💸',
+ 'moneybag': '💰',
+ 'mongolia': '🇲🇳',
+ 'monkey': '🐒',
+ 'monkey_face': '🐵',
+ 'monocle': '🧐',
+ 'monocle_face': '🧐',
+ 'monorail': '🚝',
+ 'montenegro': '🇲🇪',
+ 'montserrat': '🇲🇸',
+ 'moon': '🌔',
+ 'moon_cake': '🥮',
+ 'morocco': '🇲🇦',
+ 'mortar_board': '🎓',
+ 'mosque': '🕌',
+ 'mosquito': '🦟',
+ 'motor_boat': '🛥',
+ 'motor_scooter': '🛵',
+ 'motorcycle': '🏍',
+ 'motorized_wheelchair': '🦼',
+ 'motorway': '🛣',
+ 'mount_fuji': '🗻',
+ 'mountain': '⛰',
+ 'mountain_bicyclist': '🚵',
+ 'mountain_biking_man': '🚵',
+ 'mountain_biking_woman': '🚵♀️',
+ 'mountain_cableway': '🚠',
+ 'mountain_railway': '🚞',
+ 'mountain_snow': '🏔',
+ 'mouse': '🐭',
+ 'mouse2': '🐁',
+ 'mouse_trap': '🪤',
+ 'movie_camera': '🎥',
+ 'moyai': '🗿',
+ 'mozambique': '🇲🇿',
+ 'mrs_claus': '🤶',
+ 'muscle': '💪',
+ 'mushroom': '🍄',
+ 'musical_keyboard': '🎹',
+ 'musical_note': '🎵',
+ 'musical_score': '🎼',
+ 'mute': '🔇',
+ 'mx_claus': '🧑️🎄',
+ 'myanmar': '🇲🇲',
+ 'nail_care': '💅',
+ 'name_badge': '📛',
+ 'namibia': '🇳🇦',
+ 'national_park': '🏞',
+ 'nauru': '🇳🇷',
+ 'nauseated_face': '🤢',
+ 'nazar_amulet': '🧿',
+ 'necktie': '👔',
+ 'negative_squared_cross_mark': '❎',
+ 'nepal': '🇳🇵',
+ 'nerd_face': '🤓',
+ 'nesting_dolls': '🪆',
+ 'netherlands': '🇳🇱',
+ 'neutral_face': '😐',
+ 'new': '🆕',
+ 'new_caledonia': '🇳🇨',
+ 'new_moon': '🌑',
+ 'new_moon_with_face': '🌚',
+ 'new_zealand': '🇳🇿',
+ 'newspaper': '📰',
+ 'newspaper_roll': '🗞',
+ 'next_track_button': '⏭',
+ 'ng': '🆖',
+ 'ng_man': '🙅️♂',
+ 'ng_woman': '🙅️♀',
+ 'nicaragua': '🇳🇮',
+ 'niger': '🇳🇪',
+ 'nigeria': '🇳🇬',
+ 'night_with_stars': '🌃',
+ 'nine': '9️⃣',
+ 'ninja': '🥷',
+ 'niue': '🇳🇺',
+ 'no_bell': '🔕',
+ 'no_bicycles': '🚳',
+ 'no_entry': '⛔',
+ 'no_entry_sign': '🚫',
+ 'no_good': '🙅',
+ 'no_good_man': '🙅♂️',
+ 'no_good_woman': '🙅',
+ 'no_mobile_phones': '📵',
+ 'no_mouth': '😶',
+ 'no_pedestrians': '🚷',
+ 'no_smoking': '🚭',
+ 'non-potable_water': '🚱',
+ 'norfolk_island': '🇳🇫',
+ 'north_korea': '🇰🇵',
+ 'northern_mariana_islands': '🇲🇵',
+ 'norway': '🇳🇴',
+ 'nose': '👃',
+ 'notebook': '📓',
+ 'notebook_with_decorative_cover': '📔',
+ 'notes': '🎶',
+ 'nut_and_bolt': '🔩',
+ 'o': '⭕',
+ 'o2': '🅾️',
+ 'ocean': '🌊',
+ 'octopus': '🐙',
+ 'oden': '🍢',
+ 'office': '🏢',
+ 'office_worker': '🧑️💼',
+ 'oil_drum': '🛢',
+ 'ok': '🆗',
+ 'ok_hand': '👌',
+ 'ok_man': '🙆♂️',
+ 'ok_person': '🙆',
+ 'ok_woman': '🙆',
+ 'old_key': '🗝',
+ 'older_adult': '🧓',
+ 'older_man': '👴',
+ 'older_woman': '👵',
+ 'olive': '🫒',
+ 'om': '🕉',
+ 'oman': '🇴🇲',
+ 'on': '🔛',
+ 'oncoming_automobile': '🚘',
+ 'oncoming_bus': '🚍',
+ 'oncoming_police_car': '🚔',
+ 'oncoming_taxi': '🚖',
+ 'one': '1️⃣',
+ 'one_piece_swimsuit': '🩱',
+ 'onion': '🧅',
+ 'open_book': '📖',
+ 'open_file_folder': '📂',
+ 'open_hands': '👐',
+ 'open_mouth': '😮',
+ 'open_umbrella': '☂',
+ 'ophiuchus': '⛎',
+ 'orange': '🍊',
+ 'orange_book': '📙',
+ 'orange_circle': '🟠',
+ 'orange_heart': '🧡',
+ 'orange_square': '🟧',
+ 'orangutan': '🦧',
+ 'orthodox_cross': '☦',
+ 'otter': '🦦',
+ 'outbox_tray': '📤',
+ 'owl': '🦉',
+ 'ox': '🐂',
+ 'oyster': '🦪',
+ 'package': '📦',
+ 'page_facing_up': '📄',
+ 'page_with_curl': '📃',
+ 'pager': '📟',
+ 'paintbrush': '🖌',
+ 'pakistan': '🇵🇰',
+ 'palau': '🇵🇼',
+ 'palestinian_territories': '🇵🇸',
+ 'palm_tree': '🌴',
+ 'palms_up': '🤲',
+ 'palms_up_together': '🤲',
+ 'panama': '🇵🇦',
+ 'pancakes': '🥞',
+ 'panda_face': '🐼',
+ 'paperclip': '📎',
+ 'paperclips': '🖇',
+ 'papua_new_guinea': '🇵🇬',
+ 'parachute': '🪂',
+ 'paraguay': '🇵🇾',
+ 'parasol_on_ground': '⛱',
+ 'parking': '🅿️',
+ 'parrot': '🦜',
+ 'part_alternation_mark': '〽️',
+ 'partly_sunny': '⛅',
+ 'partying': '🥳',
+ 'partying_face': '🥳',
+ 'passenger_ship': '🛳',
+ 'passport_control': '🛂',
+ 'pause_button': '⏸',
+ 'paw_prints': '🐾',
+ 'peace_symbol': '☮',
+ 'peach': '🍑',
+ 'peacock': '🦚',
+ 'peanuts': '🥜',
+ 'pear': '🍐',
+ 'pen': '🖊',
+ 'pencil': '📝',
+ 'pencil2': '✏️',
+ 'penguin': '🐧',
+ 'pensive': '😔',
+ 'people_holding_hands': '🧑️🤝️🧑',
+ 'people_hugging': '🫂',
+ 'performing_arts': '🎭',
+ 'persevere': '😣',
+ 'person_bald': '🧑️🦲',
+ 'person_curly_hair': '🧑️🦱',
+ 'person_feeding_baby': '🧑️🍼',
+ 'person_fencing': '🤺',
+ 'person_in_manual_wheelchair': '🧑️🦽',
+ 'person_in_motorized_wheelchair': '🧑️🦼',
+ 'person_in_tuxedo': '🤵',
+ 'person_red_hair': '🧑️🦰',
+ 'person_white_hair': '🧑️🦳',
+ 'person_with_probing_cane': '🧑️🦯',
+ 'person_with_turban': '👳',
+ 'person_with_veil': '👰',
+ 'peru': '🇵🇪',
+ 'petri_dish': '🧫',
+ 'philippines': '🇵🇭',
+ 'phone': '☎️',
+ 'pick': '⛏',
+ 'pickup_truck': '🛻',
+ 'pie': '🥧',
+ 'pig': '🐷',
+ 'pig2': '🐖',
+ 'pig_nose': '🐽',
+ 'pill': '💊',
+ 'pilot': '🧑️✈',
+ 'pinata': '🪅',
+ 'pinched_fingers': '🤌',
+ 'pinching_hand': '🤏',
+ 'pineapple': '🍍',
+ 'ping_pong': '🏓',
+ 'pirate_flag': '🏴☠️',
+ 'pisces': '♓',
+ 'pitcairn_islands': '🇵🇳',
+ 'pizza': '🍕',
+ 'placard': '🪧',
+ 'place_of_worship': '🛐',
+ 'plate_with_cutlery': '🍽',
+ 'play_or_pause_button': '⏯',
+ 'pleading': '🥺',
+ 'pleading_face': '🥺',
+ 'plunger': '🪠',
+ 'point_down': '👇',
+ 'point_left': '👈',
+ 'point_right': '👉',
+ 'point_up': '☝',
+ 'point_up_2': '👆',
+ 'poland': '🇵🇱',
+ 'polar_bear': '🐻️❄',
+ 'police_car': '🚓',
+ 'police_officer': '👮',
+ 'policeman': '👮',
+ 'policewoman': '👮♀️',
+ 'poodle': '🐩',
+ 'poop': '💩',
+ 'popcorn': '🍿',
+ 'portugal': '🇵🇹',
+ 'post_office': '🏣',
+ 'postal_horn': '📯',
+ 'postbox': '📮',
+ 'potable_water': '🚰',
+ 'potato': '🥔',
+ 'potted_plant': '🪴',
+ 'pouch': '👝',
+ 'poultry_leg': '🍗',
+ 'pound': '💷',
+ 'pout': '😡',
+ 'pouting_cat': '😾',
+ 'pouting_face': '🙎',
+ 'pouting_man': '🙎♂️',
+ 'pouting_woman': '🙎',
+ 'pray': '🙏',
+ 'prayer_beads': '📿',
+ 'pregnant_woman': '🤰',
+ 'pretzel': '🥨',
+ 'previous_track_button': '⏮',
+ 'prince': '🤴',
+ 'princess': '👸',
+ 'printer': '🖨',
+ 'probing_cane': '🦯',
+ 'puerto_rico': '🇵🇷',
+ 'punch': '👊',
+ 'purple_circle': '🟣',
+ 'purple_heart': '💜',
+ 'purple_square': '🟪',
+ 'purse': '👛',
+ 'pushpin': '📌',
+ 'put_litter_in_its_place': '🚮',
+ 'qatar': '🇶🇦',
+ 'question': '❓',
+ 'rabbit': '🐰',
+ 'rabbit2': '🐇',
+ 'raccoon': '🦝',
+ 'racehorse': '🐎',
+ 'racing_car': '🏎',
+ 'radio': '📻',
+ 'radio_button': '🔘',
+ 'radioactive': '☢',
+ 'rage': '😡',
+ 'railway_car': '🚃',
+ 'railway_track': '🛤',
+ 'rainbow': '🌈',
+ 'rainbow_flag': '🏳️🌈',
+ 'raised_back_of_hand': '🤚',
+ 'raised_eyebrow': '🤨',
+ 'raised_hand': '✋',
+ 'raised_hand_with_fingers_splayed': '🖐',
+ 'raised_hands': '🙌',
+ 'raising_hand': '🙋',
+ 'raising_hand_man': '🙋♂️',
+ 'raising_hand_woman': '🙋',
+ 'ram': '🐏',
+ 'ramen': '🍜',
+ 'rat': '🐀',
+ 'razor': '🪒',
+ 'receipt': '🧾',
+ 'record_button': '⏺',
+ 'recycle': '♻️',
+ 'red_car': '🚗',
+ 'red_circle': '🔴',
+ 'red_envelope': '🧧',
+ 'red_haired_man': '👨️🦰',
+ 'red_haired_woman': '👩️🦰',
+ 'red_square': '🟥',
+ 'registered': '®️',
+ 'relaxed': '☺️',
+ 'relieved': '😌',
+ 'reminder_ribbon': '🎗',
+ 'repeat': '🔁',
+ 'repeat_one': '🔂',
+ 'rescue_worker_helmet': '⛑',
+ 'restroom': '🚻',
+ 'reunion': '🇷🇪',
+ 'revolving_hearts': '💞',
+ 'rewind': '⏪',
+ 'rhinoceros': '🦏',
+ 'ribbon': '🎀',
+ 'rice': '🍚',
+ 'rice_ball': '🍙',
+ 'rice_cracker': '🍘',
+ 'rice_scene': '🎑',
+ 'right_anger_bubble': '🗯',
+ 'ring': '💍',
+ 'ringed_planet': '🪐',
+ 'robot': '🤖',
+ 'rock': '🪨',
+ 'rocket': '🚀',
+ 'rofl': '🤣',
+ 'roll_eyes': '🙄',
+ 'roll_of_paper': '🧻',
+ 'roller_coaster': '🎢',
+ 'roller_skate': '🛼',
+ 'romania': '🇷🇴',
+ 'rooster': '🐓',
+ 'rose': '🌹',
+ 'rosette': '🏵',
+ 'rotating_light': '🚨',
+ 'round_pushpin': '📍',
+ 'rowboat': '🚣',
+ 'rowing_man': '🚣',
+ 'rowing_woman': '🚣♀️',
+ 'ru': '🇷🇺',
+ 'rugby_football': '🏉',
+ 'runner': '🏃',
+ 'running': '🏃',
+ 'running_man': '🏃',
+ 'running_shirt_with_sash': '🎽',
+ 'running_woman': '🏃♀️',
+ 'rwanda': '🇷🇼',
+ 'sa': '🈂️',
+ 'safety_pin': '🧷',
+ 'safety_vest': '🦺',
+ 'sagittarius': '♐',
+ 'sailboat': '⛵',
+ 'sake': '🍶',
+ 'salt': '🧂',
+ 'samoa': '🇼🇸',
+ 'san_marino': '🇸🇲',
+ 'sandal': '👡',
+ 'sandwich': '🥪',
+ 'santa': '🎅',
+ 'sao_tome_principe': '🇸🇹',
+ 'sari': '🥻',
+ 'sassy_man': '💁️♂',
+ 'sassy_woman': '💁️♀',
+ 'satellite': '📡',
+ 'satisfied': '😆',
+ 'saudi_arabia': '🇸🇦',
+ 'sauna_man': '🧖️♂',
+ 'sauna_person': '🧖',
+ 'sauna_woman': '🧖️♀',
+ 'sauropod': '🦕',
+ 'saxophone': '🎷',
+ 'scarf': '🧣',
+ 'school': '🏫',
+ 'school_satchel': '🎒',
+ 'scientist': '🧑️🔬',
+ 'scissors': '✂️',
+ 'scorpion': '🦂',
+ 'scorpius': '♏',
+ 'scotland': '🏴',
+ 'scream': '😱',
+ 'scream_cat': '🙀',
+ 'screwdriver': '🪛',
+ 'scroll': '📜',
+ 'seal': '🦭',
+ 'seat': '💺',
+ 'secret': '㊙️',
+ 'see_no_evil': '🙈',
+ 'seedling': '🌱',
+ 'selfie': '🤳',
+ 'senegal': '🇸🇳',
+ 'serbia': '🇷🇸',
+ 'service_dog': '🐕️🦺',
+ 'seven': '7️⃣',
+ 'sewing_needle': '🪡',
+ 'seychelles': '🇸🇨',
+ 'shallow_pan_of_food': '🥘',
+ 'shamrock': '☘',
+ 'shark': '🦈',
+ 'shaved_ice': '🍧',
+ 'sheep': '🐑',
+ 'shell': '🐚',
+ 'shield': '🛡',
+ 'shinto_shrine': '⛩',
+ 'ship': '🚢',
+ 'shirt': '👕',
+ 'shit': '💩',
+ 'shoe': '👞',
+ 'shopping': '🛍',
+ 'shopping_cart': '🛒',
+ 'shorts': '🩳',
+ 'shower': '🚿',
+ 'shrimp': '🦐',
+ 'shrug': '🤷',
+ 'shushing': '🤫',
+ 'shushing_face': '🤫',
+ 'sierra_leone': '🇸🇱',
+ 'signal_strength': '📶',
+ 'singapore': '🇸🇬',
+ 'singer': '🧑️🎤',
+ 'sint_maarten': '🇸🇽',
+ 'six': '6️⃣',
+ 'six_pointed_star': '🔯',
+ 'skateboard': '🛹',
+ 'ski': '🎿',
+ 'skier': '⛷',
+ 'skull': '💀',
+ 'skull_and_crossbones': '☠',
+ 'skunk': '🦨',
+ 'sled': '🛷',
+ 'sleeping': '😴',
+ 'sleeping_bed': '🛌',
+ 'sleepy': '😪',
+ 'slightly_frowning_face': '🙁',
+ 'slightly_smiling_face': '🙂',
+ 'slot_machine': '🎰',
+ 'sloth': '🦥',
+ 'slovakia': '🇸🇰',
+ 'slovenia': '🇸🇮',
+ 'small_airplane': '🛩',
+ 'small_blue_diamond': '🔹',
+ 'small_orange_diamond': '🔸',
+ 'small_red_triangle': '🔺',
+ 'small_red_triangle_down': '🔻',
+ 'smile': '😄',
+ 'smile_cat': '😸',
+ 'smiley': '😃',
+ 'smiley_cat': '😺',
+ 'smiling_face_with_tear': '🥲',
+ 'smiling_face_with_three_hearts': '🥰',
+ 'smiling_imp': '😈',
+ 'smirk': '😏',
+ 'smirk_cat': '😼',
+ 'smoking': '🚬',
+ 'snail': '🐌',
+ 'snake': '🐍',
+ 'sneezing_face': '🤧',
+ 'snowboarder': '🏂',
+ 'snowflake': '❄️',
+ 'snowman': '⛄',
+ 'snowman_with_snow': '☃',
+ 'soap': '🧼',
+ 'sob': '😭',
+ 'soccer': '⚽',
+ 'socks': '🧦',
+ 'softball': '🥎',
+ 'solomon_islands': '🇸🇧',
+ 'somalia': '🇸🇴',
+ 'soon': '🔜',
+ 'sorceress': '🧙♀️',
+ 'sos': '🆘',
+ 'sound': '🔉',
+ 'south_africa': '🇿🇦',
+ 'south_georgia_south_sandwich_islands': '🇬🇸',
+ 'south_sudan': '🇸🇸',
+ 'space_invader': '👾',
+ 'spades': '♠️',
+ 'spaghetti': '🍝',
+ 'sparkle': '❇️',
+ 'sparkler': '🎇',
+ 'sparkles': '✨',
+ 'sparkling_heart': '💖',
+ 'speak_no_evil': '🙊',
+ 'speaker': '🔈',
+ 'speaking_head': '🗣',
+ 'speech_balloon': '💬',
+ 'speedboat': '🚤',
+ 'spider': '🕷',
+ 'spider_web': '🕸',
+ 'spiral_calendar': '🗓',
+ 'spiral_notepad': '🗒',
+ 'sponge': '🧽',
+ 'spoon': '🥄',
+ 'squid': '🦑',
+ 'sri_lanka': '🇱🇰',
+ 'st_barthelemy': '🇧🇱',
+ 'st_helena': '🇸🇭',
+ 'st_kitts_nevis': '🇰🇳',
+ 'st_lucia': '🇱🇨',
+ 'st_martin': '🇲️🇫',
+ 'st_pierre_miquelon': '🇵🇲',
+ 'st_vincent_grenadines': '🇻🇨',
+ 'stadium': '🏟',
+ 'standing_man': '🧍️♂',
+ 'standing_person': '🧍',
+ 'standing_woman': '🧍️♀',
+ 'star': '⭐',
+ 'star2': '🌟',
+ 'star_and_crescent': '☪',
+ 'star_of_david': '✡',
+ 'star_struck': '🤩',
+ 'stars': '🌠',
+ 'station': '🚉',
+ 'statue_of_liberty': '🗽',
+ 'steak': '🥩',
+ 'steam_locomotive': '🚂',
+ 'stethoscope': '🩺',
+ 'stew': '🍲',
+ 'stop_button': '⏹',
+ 'stop_sign': '🛑',
+ 'stopwatch': '⏱',
+ 'straight_ruler': '📏',
+ 'strawberry': '🍓',
+ 'stuck_out_tongue': '😛',
+ 'stuck_out_tongue_closed_eyes': '😝',
+ 'stuck_out_tongue_winking_eye': '😜',
+ 'student': '🧑️🎓',
+ 'studio_microphone': '🎙',
+ 'stuffed_flatbread': '🥙',
+ 'sudan': '🇸🇩',
+ 'sun_behind_large_cloud': '🌥',
+ 'sun_behind_rain_cloud': '🌦',
+ 'sun_behind_small_cloud': '🌤',
+ 'sun_with_face': '🌞',
+ 'sunflower': '🌻',
+ 'sunglasses': '😎',
+ 'sunny': '☀️',
+ 'sunrise': '🌅',
+ 'sunrise_over_mountains': '🌄',
+ 'superhero': '🦸',
+ 'superhero_man': '🦸️♂',
+ 'superhero_woman': '🦸️♀',
+ 'supervillain': '🦹',
+ 'supervillain_man': '🦹️♂',
+ 'supervillain_woman': '🦹️♀',
+ 'surfer': '🏄',
+ 'surfing_man': '🏄',
+ 'surfing_woman': '🏄♀️',
+ 'suriname': '🇸🇷',
+ 'sushi': '🍣',
+ 'suspension_railway': '🚟',
+ 'svalbard_jan_mayen': '🇸️🇯',
+ 'swan': '🦢',
+ 'swaziland': '🇸🇿',
+ 'sweat': '😓',
+ 'sweat_drops': '💦',
+ 'sweat_smile': '😅',
+ 'sweden': '🇸🇪',
+ 'sweet_potato': '🍠',
+ 'swim_brief': '🩲',
+ 'swimmer': '🏊',
+ 'swimming_man': '🏊',
+ 'swimming_woman': '🏊♀️',
+ 'switzerland': '🇨🇭',
+ 'symbols': '🔣',
+ 'symbols_over_mouth': '🤬',
+ 'synagogue': '🕍',
+ 'syria': '🇸🇾',
+ 'syringe': '💉',
+ 't-rex': '🦖',
+ 'taco': '🌮',
+ 'tada': '🎉',
+ 'taiwan': '🇹🇼',
+ 'tajikistan': '🇹🇯',
+ 'takeout_box': '🥡',
+ 'tamale': '🫔',
+ 'tanabata_tree': '🎋',
+ 'tangerine': '🍊',
+ 'tanzania': '🇹🇿',
+ 'taurus': '♉',
+ 'taxi': '🚕',
+ 'tea': '🍵',
+ 'teacher': '🧑️🏫',
+ 'teapot': '🫖',
+ 'technologist': '🧑️💻',
+ 'teddy_bear': '🧸',
+ 'telephone': '☎️',
+ 'telephone_receiver': '📞',
+ 'telescope': '🔭',
+ 'tennis': '🎾',
+ 'tent': '⛺',
+ 'test_tube': '🧪',
+ 'thailand': '🇹🇭',
+ 'thermometer': '🌡',
+ 'thinking': '🤔',
+ 'thong_sandal': '🩴',
+ 'thought_balloon': '💭',
+ 'thread': '🧵',
+ 'three': '3️⃣',
+ 'thumbsdown': '👎',
+ 'thumbsup': '👍',
+ 'ticket': '🎫',
+ 'tickets': '🎟',
+ 'tiger': '🐯',
+ 'tiger2': '🐅',
+ 'timer_clock': '⏲',
+ 'timor_leste': '🇹🇱',
+ 'tipping_hand_man': '💁♂️',
+ 'tipping_hand_person': '💁',
+ 'tipping_hand_woman': '💁',
+ 'tired_face': '😫',
+ 'tm': '™️',
+ 'togo': '🇹🇬',
+ 'toilet': '🚽',
+ 'toilet_paper': '🧻',
+ 'tokelau': '🇹🇰',
+ 'tokyo_tower': '🗼',
+ 'tomato': '🍅',
+ 'tonga': '🇹🇴',
+ 'tongue': '👅',
+ 'toolbox': '🧰',
+ 'tooth': '🦷',
+ 'toothbrush': '🪥',
+ 'top': '🔝',
+ 'tophat': '🎩',
+ 'tornado': '🌪',
+ 'tr': '🇹🇷',
+ 'trackball': '🖲',
+ 'tractor': '🚜',
+ 'traffic_light': '🚥',
+ 'train': '🚋',
+ 'train2': '🚆',
+ 'tram': '🚊',
+ 'transgender_flag': '🏳️⚧',
+ 'transgender_symbol': '⚧',
+ 'triangular_flag_on_post': '🚩',
+ 'triangular_ruler': '📐',
+ 'trident': '🔱',
+ 'trinidad_tobago': '🇹🇹',
+ 'tristan_da_cunha': '🇹️🇦',
+ 'triumph': '😤',
+ 'trolleybus': '🚎',
+ 'trophy': '🏆',
+ 'tropical_drink': '🍹',
+ 'tropical_fish': '🐠',
+ 'truck': '🚚',
+ 'trumpet': '🎺',
+ 'tshirt': '👕',
+ 'tulip': '🌷',
+ 'tumbler_glass': '🥃',
+ 'tunisia': '🇹🇳',
+ 'turkey': '🦃',
+ 'turkmenistan': '🇹🇲',
+ 'turks_caicos_islands': '🇹🇨',
+ 'turtle': '🐢',
+ 'tuvalu': '🇹🇻',
+ 'tv': '📺',
+ 'twisted_rightwards_arrows': '🔀',
+ 'two': '2️⃣',
+ 'two_hearts': '💕',
+ 'two_men_holding_hands': '👬',
+ 'two_women_holding_hands': '👭',
+ 'u5272': '🈹',
+ 'u5408': '🈴',
+ 'u55b6': '🈺',
+ 'u6307': '🈯',
+ 'u6708': '🈷️',
+ 'u6709': '🈶',
+ 'u6e80': '🈵',
+ 'u7121': '🈚',
+ 'u7533': '🈸',
+ 'u7981': '🈲',
+ 'u7a7a': '🈳',
+ 'uganda': '🇺🇬',
+ 'uk': '🇬🇧',
+ 'ukraine': '🇺🇦',
+ 'umbrella': '☔',
+ 'unamused': '😒',
+ 'underage': '🔞',
+ 'unicorn': '🦄',
+ 'united_arab_emirates': '🇦🇪',
+ 'united_nations': '🇺🇳',
+ 'unlock': '🔓',
+ 'up': '🆙',
+ 'upside_down_face': '🙃',
+ 'uruguay': '🇺🇾',
+ 'us': '🇺🇸',
+ 'us_outlying_islands': '🇺️🇲',
+ 'us_virgin_islands': '🇻🇮',
+ 'uzbekistan': '🇺🇿',
+ 'v': '✌',
+ 'vampire': '🧛',
+ 'vampire_man': '🧛️♂',
+ 'vampire_woman': '🧛️♀',
+ 'vanuatu': '🇻🇺',
+ 'vatican_city': '🇻🇦',
+ 'venezuela': '🇻🇪',
+ 'vertical_traffic_light': '🚦',
+ 'vhs': '📼',
+ 'vibration_mode': '📳',
+ 'video_camera': '📹',
+ 'video_game': '🎮',
+ 'vietnam': '🇻🇳',
+ 'violin': '🎻',
+ 'virgo': '♍',
+ 'volcano': '🌋',
+ 'volleyball': '🏐',
+ 'vomiting': '🤮',
+ 'vomiting_face': '🤮',
+ 'vs': '🆚',
+ 'vulcan_salute': '🖖',
+ 'waffle': '🧇',
+ 'wales': '🏴',
+ 'walking': '🚶',
+ 'walking_man': '🚶',
+ 'walking_woman': '🚶♀️',
+ 'wallis_futuna': '🇼🇫',
+ 'waning_crescent_moon': '🌘',
+ 'waning_gibbous_moon': '🌖',
+ 'warning': '⚠️',
+ 'wastebasket': '🗑',
+ 'watch': '⌚',
+ 'water_buffalo': '🐃',
+ 'water_polo': '🤽',
+ 'watermelon': '🍉',
+ 'wave': '👋',
+ 'wavy_dash': '〰️',
+ 'waxing_crescent_moon': '🌒',
+ 'waxing_gibbous_moon': '🌔',
+ 'wc': '🚾',
+ 'weary': '😩',
+ 'wedding': '💒',
+ 'weight_lifting': '🏋',
+ 'weight_lifting_man': '🏋',
+ 'weight_lifting_woman': '🏋️♀️',
+ 'western_sahara': '🇪🇭',
+ 'whale': '🐳',
+ 'whale2': '🐋',
+ 'wheel_of_dharma': '☸',
+ 'wheelchair': '♿',
+ 'white_check_mark': '✅',
+ 'white_circle': '⚪',
+ 'white_flag': '🏳',
+ 'white_flower': '💮',
+ 'white_haired_man': '👨️🦳',
+ 'white_haired_woman': '👩️🦳',
+ 'white_heart': '🤍',
+ 'white_large_square': '⬜',
+ 'white_medium_small_square': '◽',
+ 'white_medium_square': '◻️',
+ 'white_small_square': '▫️',
+ 'white_square_button': '🔳',
+ 'wilted_flower': '🥀',
+ 'wind_chime': '🎐',
+ 'wind_face': '🌬',
+ 'window': '🪟',
+ 'wine_glass': '🍷',
+ 'wink': '😉',
+ 'wizard': '🧙♂️',
+ 'wolf': '🐺',
+ 'woman': '👩',
+ 'woman_artist': '👩🎨',
+ 'woman_astronaut': '👩🚀',
+ 'woman_beard': '🧔️♀',
+ 'woman_cartwheeling': '🤸♀️',
+ 'woman_cook': '👩🍳',
+ 'woman_dancing': '💃',
+ 'woman_elf': '🧝♀️',
+ 'woman_facepalming': '🤦♀️',
+ 'woman_factory_worker': '👩🏭',
+ 'woman_fairy': '🧚♀️',
+ 'woman_farmer': '👩🌾',
+ 'woman_feeding_baby': '👩️🍼',
+ 'woman_firefighter': '👩🚒',
+ 'woman_genie': '🧞♀️',
+ 'woman_health_worker': '👩⚕️',
+ 'woman_in_lotus_position': '🧘♀️',
+ 'woman_in_manual_wheelchair': '👩️🦽',
+ 'woman_in_motorized_wheelchair': '👩️🦼',
+ 'woman_in_steamy_room': '🧖♀️',
+ 'woman_in_tuxedo': '🤵️♀',
+ 'woman_judge': '👩⚖️',
+ 'woman_juggling': '🤹♀️',
+ 'woman_mechanic': '👩🔧',
+ 'woman_office_worker': '👩💼',
+ 'woman_pilot': '👩✈️',
+ 'woman_playing_handball': '🤾♀️',
+ 'woman_playing_water_polo': '🤽♀️',
+ 'woman_scientist': '👩🔬',
+ 'woman_shrugging': '🤷',
+ 'woman_singer': '👩🎤',
+ 'woman_student': '👩🎓',
+ 'woman_superhero': '🦸♀️',
+ 'woman_supervillain': '🦹♀️',
+ 'woman_teacher': '👩🏫',
+ 'woman_technologist': '👩💻',
+ 'woman_vampire': '🧛♀️',
+ 'woman_with_headscarf': '🧕',
+ 'woman_with_probing_cane': '👩️🦯',
+ 'woman_with_turban': '👳♀️',
+ 'woman_with_veil': '👰️♀',
+ 'woman_zombie': '🧟♀️',
+ 'womans_clothes': '👚',
+ 'womans_hat': '👒',
+ 'women_wrestling': '🤼♀️',
+ 'womens': '🚺',
+ 'wood': '🪵',
+ 'woozy': '🥴',
+ 'woozy_face': '🥴',
+ 'world_map': '🗺',
+ 'worm': '🪱',
+ 'worried': '😟',
+ 'wrench': '🔧',
+ 'wrestling': '🤼',
+ 'writing_hand': '✍',
+ 'x': '❌',
+ 'yarn': '🧶',
+ 'yawning_face': '🥱',
+ 'yellow_circle': '🟡',
+ 'yellow_heart': '💛',
+ 'yellow_square': '🟨',
+ 'yemen': '🇾🇪',
+ 'yen': '💴',
+ 'yin_yang': '☯',
+ 'yo_yo': '🪀',
+ 'yum': '😋',
+ 'zambia': '🇿🇲',
+ 'zany': '🤪',
+ 'zany_face': '🤪',
+ 'zap': '⚡',
+ 'zebra': '🦓',
+ 'zero': '0️⃣',
+ 'zimbabwe': '🇿🇼',
+ 'zipper_mouth_face': '🤐',
+ 'zombie': '🧟',
+ 'zombie_man': '🧟️♂',
+ 'zombie_woman': '🧟️♀',
+ 'zzz': '💤',
+};
diff --git a/pkgs/markdown/lib/src/extension_set.dart b/pkgs/markdown/lib/src/extension_set.dart
new file mode 100644
index 0000000..58a25d8
--- /dev/null
+++ b/pkgs/markdown/lib/src/extension_set.dart
@@ -0,0 +1,103 @@
+import 'block_syntaxes/alert_block_syntax.dart';
+import 'block_syntaxes/block_syntax.dart';
+import 'block_syntaxes/fenced_code_block_syntax.dart';
+import 'block_syntaxes/footnote_def_syntax.dart';
+import 'block_syntaxes/header_with_id_syntax.dart';
+import 'block_syntaxes/ordered_list_with_checkbox_syntax.dart';
+import 'block_syntaxes/setext_header_with_id_syntax.dart';
+import 'block_syntaxes/table_syntax.dart';
+import 'block_syntaxes/unordered_list_with_checkbox_syntax.dart';
+import 'inline_syntaxes/autolink_extension_syntax.dart';
+import 'inline_syntaxes/color_swatch_syntax.dart';
+import 'inline_syntaxes/emoji_syntax.dart';
+import 'inline_syntaxes/inline_html_syntax.dart';
+import 'inline_syntaxes/inline_syntax.dart';
+import 'inline_syntaxes/strikethrough_syntax.dart';
+
+/// ExtensionSets provide a simple grouping mechanism for common Markdown
+/// flavors.
+///
+/// For example, the [gitHubFlavored] set of syntax extensions allows users to
+/// output HTML from their Markdown in a similar fashion to GitHub's parsing.
+class ExtensionSet {
+ /// The [ExtensionSet.none] extension set renders Markdown similar to
+ /// [Markdown.pl].
+ ///
+ /// However, this set does not render _exactly_ the same as Markdown.pl;
+ /// rather it is more-or-less the CommonMark standard of Markdown, without
+ /// fenced code blocks, or inline HTML.
+ ///
+ /// [Markdown.pl]: http://daringfireball.net/projects/markdown/syntax
+ static final ExtensionSet none = ExtensionSet(const [], const []);
+
+ /// The [commonMark] extension set is close to compliance with [CommonMark].
+ ///
+ /// [CommonMark]: http://commonmark.org/
+ static final ExtensionSet commonMark = ExtensionSet(
+ List<BlockSyntax>.unmodifiable(
+ <BlockSyntax>[const FencedCodeBlockSyntax()],
+ ),
+ List<InlineSyntax>.unmodifiable(
+ <InlineSyntax>[InlineHtmlSyntax()],
+ ),
+ );
+
+ /// The [gitHubWeb] extension set renders Markdown similarly to GitHub.
+ ///
+ /// This is different from the [gitHubFlavored] extension set in that GitHub
+ /// actually renders HTML different from straight [GitHub flavored Markdown].
+ ///
+ /// (The only difference currently is that [gitHubWeb] renders headers with
+ /// linkable IDs.)
+ ///
+ /// [GitHub flavored Markdown]: https://github.github.com/gfm/
+ static final ExtensionSet gitHubWeb = ExtensionSet(
+ List<BlockSyntax>.unmodifiable(
+ <BlockSyntax>[
+ const FencedCodeBlockSyntax(),
+ const HeaderWithIdSyntax(),
+ const SetextHeaderWithIdSyntax(),
+ const TableSyntax(),
+ const UnorderedListWithCheckboxSyntax(),
+ const OrderedListWithCheckboxSyntax(),
+ const FootnoteDefSyntax(),
+ const AlertBlockSyntax(),
+ ],
+ ),
+ List<InlineSyntax>.unmodifiable(
+ <InlineSyntax>[
+ InlineHtmlSyntax(),
+ StrikethroughSyntax(),
+ EmojiSyntax(),
+ ColorSwatchSyntax(),
+ AutolinkExtensionSyntax()
+ ],
+ ),
+ );
+
+ /// The [gitHubFlavored] extension set is close to compliance with the
+ /// [GitHub flavored Markdown spec](https://github.github.com/gfm/).
+ static final ExtensionSet gitHubFlavored = ExtensionSet(
+ List<BlockSyntax>.unmodifiable(
+ <BlockSyntax>[
+ const FencedCodeBlockSyntax(),
+ const TableSyntax(),
+ const UnorderedListWithCheckboxSyntax(),
+ const OrderedListWithCheckboxSyntax(),
+ const FootnoteDefSyntax(),
+ ],
+ ),
+ List<InlineSyntax>.unmodifiable(
+ <InlineSyntax>[
+ InlineHtmlSyntax(),
+ StrikethroughSyntax(),
+ AutolinkExtensionSyntax()
+ ],
+ ),
+ );
+
+ final List<BlockSyntax> blockSyntaxes;
+ final List<InlineSyntax> inlineSyntaxes;
+
+ ExtensionSet(this.blockSyntaxes, this.inlineSyntaxes);
+}
diff --git a/pkgs/markdown/lib/src/html_renderer.dart b/pkgs/markdown/lib/src/html_renderer.dart
new file mode 100644
index 0000000..404d870
--- /dev/null
+++ b/pkgs/markdown/lib/src/html_renderer.dart
@@ -0,0 +1,218 @@
+// 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:convert';
+
+import 'ast.dart';
+import 'block_syntaxes/block_syntax.dart';
+import 'document.dart';
+import 'extension_set.dart';
+import 'inline_syntaxes/inline_syntax.dart';
+
+/// Converts the given string of Markdown to HTML.
+String markdownToHtml(
+ String markdown, {
+ Iterable<BlockSyntax> blockSyntaxes = const [],
+ Iterable<InlineSyntax> inlineSyntaxes = const [],
+ ExtensionSet? extensionSet,
+ Resolver? linkResolver,
+ Resolver? imageLinkResolver,
+ bool inlineOnly = false,
+ bool encodeHtml = true,
+ bool enableTagfilter = false,
+ bool withDefaultBlockSyntaxes = true,
+ bool withDefaultInlineSyntaxes = true,
+}) {
+ final document = Document(
+ blockSyntaxes: blockSyntaxes,
+ inlineSyntaxes: inlineSyntaxes,
+ extensionSet: extensionSet,
+ linkResolver: linkResolver,
+ imageLinkResolver: imageLinkResolver,
+ encodeHtml: encodeHtml,
+ withDefaultBlockSyntaxes: withDefaultBlockSyntaxes,
+ withDefaultInlineSyntaxes: withDefaultInlineSyntaxes,
+ );
+
+ if (inlineOnly) return renderToHtml(document.parseInline(markdown));
+
+ final nodes = document.parse(markdown);
+
+ return '${renderToHtml(nodes, enableTagfilter: enableTagfilter)}\n';
+}
+
+/// Renders [nodes] to HTML.
+String renderToHtml(List<Node> nodes, {bool enableTagfilter = false}) =>
+ HtmlRenderer(
+ enableTagfilter: enableTagfilter,
+ ).render(nodes);
+
+const _blockTags = [
+ 'blockquote',
+ 'h1',
+ 'h2',
+ 'h3',
+ 'h4',
+ 'h5',
+ 'h6',
+ 'hr',
+ 'li',
+ 'ol',
+ 'p',
+ 'pre',
+ 'ul',
+ 'address',
+ 'article',
+ 'aside',
+ 'details',
+ 'dd',
+ 'div',
+ 'dl',
+ 'dt',
+ 'figcaption',
+ 'figure',
+ 'footer',
+ 'header',
+ 'hgroup',
+ 'main',
+ 'nav',
+ 'section',
+ 'table',
+ 'thead',
+ 'tbody',
+ 'th',
+ 'tr',
+ 'td',
+];
+
+/// Translates a parsed AST to HTML.
+class HtmlRenderer implements NodeVisitor {
+ late StringBuffer buffer;
+ late Set<String> uniqueIds;
+
+ final _elementStack = <Element>[];
+ String? _lastVisitedTag;
+ final bool _tagfilterEnabled;
+
+ HtmlRenderer({
+ bool enableTagfilter = false,
+ }) : _tagfilterEnabled = enableTagfilter;
+
+ String render(List<Node> nodes) {
+ buffer = StringBuffer();
+ uniqueIds = <String>{};
+
+ for (final node in nodes) {
+ node.accept(this);
+ }
+
+ return buffer.toString();
+ }
+
+ @override
+ void visitText(Text text) {
+ var content = text.textContent;
+
+ if (_tagfilterEnabled) {
+ content = _filterTags(content);
+ }
+ if (const ['br', 'p', 'li'].contains(_lastVisitedTag)) {
+ final lines = LineSplitter.split(content);
+ content = content.contains('<pre>')
+ ? lines.join('\n')
+ : lines.map((line) => line.trimLeft()).join('\n');
+ if (text.textContent.endsWith('\n')) {
+ content = '$content\n';
+ }
+ }
+ buffer.write(content);
+
+ _lastVisitedTag = null;
+ }
+
+ @override
+ bool visitElementBefore(Element element) {
+ // Hackish. Separate block-level elements with newlines.
+ if (buffer.isNotEmpty && _blockTags.contains(element.tag)) {
+ buffer.writeln();
+ }
+
+ buffer.write('<${element.tag}');
+
+ for (final entry in element.attributes.entries) {
+ buffer.write(' ${entry.key}="${entry.value}"');
+ }
+
+ final generatedId = element.generatedId;
+
+ // attach header anchor ids generated from text
+ if (generatedId != null) {
+ buffer.write(' id="${uniquifyId(generatedId)}"');
+ }
+
+ _lastVisitedTag = element.tag;
+
+ if (element.isEmpty) {
+ // Empty element like <hr/>.
+ buffer.write(' />');
+
+ if (element.tag == 'br') {
+ buffer.write('\n');
+ }
+
+ return false;
+ } else {
+ _elementStack.add(element);
+ buffer.write('>');
+ return true;
+ }
+ }
+
+ @override
+ void visitElementAfter(Element element) {
+ assert(identical(_elementStack.last, element));
+
+ if (element.children != null &&
+ element.children!.isNotEmpty &&
+ _blockTags.contains(_lastVisitedTag) &&
+ _blockTags.contains(element.tag)) {
+ buffer.writeln();
+ } else if (element.tag == 'blockquote') {
+ buffer.writeln();
+ }
+ buffer.write('</${element.tag}>');
+
+ _lastVisitedTag = _elementStack.removeLast().tag;
+ }
+
+ /// Uniquifies an id generated from text.
+ String uniquifyId(String id) {
+ if (!uniqueIds.contains(id)) {
+ uniqueIds.add(id);
+ return id;
+ }
+
+ var suffix = 2;
+ var suffixedId = '$id-$suffix';
+ while (uniqueIds.contains(suffixedId)) {
+ suffixedId = '$id-${suffix++}';
+ }
+ uniqueIds.add(suffixedId);
+ return suffixedId;
+ }
+
+ /// Filters some particular tags, see:
+ /// https://github.github.com/gfm/#disallowed-raw-html-extension-
+ // As said in the specification, this process should happen when rendering
+ // HTML output, so there should not be a dedicated syntax for this extension.
+ String _filterTags(String content) => content.replaceAll(
+ RegExp(
+ '<(?=(?:'
+ 'title|textarea|style|xmp|iframe|noembed|noframes|script|plaintext'
+ ')>)',
+ caseSensitive: false,
+ multiLine: true,
+ ),
+ '<');
+}
diff --git a/pkgs/markdown/lib/src/inline_parser.dart b/pkgs/markdown/lib/src/inline_parser.dart
new file mode 100644
index 0000000..e700ecf
--- /dev/null
+++ b/pkgs/markdown/lib/src/inline_parser.dart
@@ -0,0 +1,355 @@
+// 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 'ast.dart';
+import 'charcode.dart';
+import 'document.dart';
+import 'inline_syntaxes/autolink_syntax.dart';
+import 'inline_syntaxes/code_syntax.dart';
+import 'inline_syntaxes/decode_html_syntax.dart';
+import 'inline_syntaxes/delimiter_syntax.dart';
+import 'inline_syntaxes/email_autolink_syntax.dart';
+import 'inline_syntaxes/emphasis_syntax.dart';
+import 'inline_syntaxes/escape_html_syntax.dart';
+import 'inline_syntaxes/escape_syntax.dart';
+import 'inline_syntaxes/image_syntax.dart';
+import 'inline_syntaxes/inline_syntax.dart';
+import 'inline_syntaxes/line_break_syntax.dart';
+import 'inline_syntaxes/link_syntax.dart';
+import 'inline_syntaxes/soft_line_break_syntax.dart';
+import 'inline_syntaxes/text_syntax.dart';
+
+/// Maintains the internal state needed to parse inline span elements in
+/// Markdown.
+class InlineParser {
+ static final List<InlineSyntax> _defaultSyntaxes =
+ List<InlineSyntax>.unmodifiable(<InlineSyntax>[
+ EmailAutolinkSyntax(),
+ AutolinkSyntax(),
+ LineBreakSyntax(),
+ // Parse "**strong**" and "*emphasis*" tags.
+ EmphasisSyntax.asterisk(),
+ // Parse "__strong__" and "_emphasis_" tags.
+ EmphasisSyntax.underscore(),
+ CodeSyntax(),
+ SoftLineBreakSyntax(),
+ // We will add the LinkSyntax once we know about the specific link resolver.
+ ]);
+
+ /// The string of Markdown being parsed.
+ final String source;
+
+ /// The Markdown document this parser is parsing.
+ final Document document;
+
+ final syntaxes = <InlineSyntax>[];
+
+ /// The current read position.
+ int pos = 0;
+
+ /// Starting position of the last unconsumed text.
+ int start = 0;
+
+ /// The delimiter stack tracking possible opening delimiters and closing
+ /// delimiters for [DelimiterSyntax] nodes.
+ final _delimiterStack = <Delimiter>[];
+
+ /// The tree of parsed HTML nodes.
+ final _tree = <Node>[];
+
+ InlineParser(this.source, this.document) {
+ // User specified syntaxes are the first syntaxes to be evaluated.
+ syntaxes.addAll(document.inlineSyntaxes);
+
+ // This first RegExp matches plain text to accelerate parsing. It's written
+ // so that it does not match any prefix of any following syntaxes. Most
+ // Markdown is plain text, so it's faster to match one RegExp per 'word'
+ // rather than fail to match all the following RegExps at each non-syntax
+ // character position.
+ if (document.hasCustomInlineSyntaxes) {
+ // We should be less aggressive in blowing past "words".
+ syntaxes.add(TextSyntax(r'[A-Za-z0-9]+(?=\s)'));
+ } else {
+ syntaxes.add(TextSyntax(r'[ \tA-Za-z0-9]*[A-Za-z0-9](?=\s)'));
+ }
+
+ if (document.withDefaultInlineSyntaxes) {
+ // Custom link resolvers go after the generic text syntax.
+ syntaxes.addAll([
+ EscapeSyntax(),
+ DecodeHtmlSyntax(),
+ LinkSyntax(linkResolver: document.linkResolver),
+ ImageSyntax(linkResolver: document.imageLinkResolver)
+ ]);
+
+ syntaxes.addAll(_defaultSyntaxes);
+ }
+
+ if (encodeHtml) {
+ syntaxes.addAll([
+ EscapeHtmlSyntax(),
+ // Leave already-encoded HTML entities alone. Ensures we don't turn
+ // "&" into "&amp;"
+ TextSyntax('&[#a-zA-Z0-9]*;', startCharacter: $ampersand),
+ ]);
+ }
+ }
+
+ List<Node> parse() {
+ while (!isDone) {
+ // A right bracket (']') is special. Hitting this character triggers the
+ // "look for link or image" procedure.
+ // See https://spec.commonmark.org/0.30/#an-algorithm-for-parsing-nested-emphasis-and-links.
+ if (charAt(pos) == $rbracket) {
+ writeText();
+ _linkOrImage();
+ continue;
+ }
+
+ // See if the current text matches any defined Markdown syntax.
+ if (syntaxes.any((syntax) => syntax.tryMatch(this))) continue;
+
+ // If we got here, it's just text.
+ advanceBy(1);
+ }
+
+ // Write any trailing text content to a Text node.
+ writeText();
+ _processDelimiterRun(-1);
+ _combineAdjacentText(_tree);
+ return _tree;
+ }
+
+ /// Look back through the delimiter stack to see if we've found a link or
+ /// image.
+ ///
+ /// This is the "look for link or image" routine from the CommonMark spec:
+ /// https://spec.commonmark.org/0.30/#look-for-link-or-image.
+ void _linkOrImage() {
+ final index = _delimiterStack
+ .lastIndexWhere((d) => d.char == $lbracket || d.char == $exclamation);
+ if (index == -1) {
+ // Never found a possible open bracket. This is just a literal "]".
+ addNode(Text(']'));
+ advanceBy(1);
+ start = pos;
+ return;
+ }
+ final delimiter = _delimiterStack[index] as SimpleDelimiter;
+ if (!delimiter.isActive) {
+ _delimiterStack.removeAt(index);
+ addNode(Text(']'));
+ advanceBy(1);
+ start = pos;
+ return;
+ }
+ final syntax = delimiter.syntax;
+ if (syntax is LinkSyntax && syntaxes.any((e) => e is LinkSyntax)) {
+ final nodeIndex = _tree.lastIndexWhere((n) => n == delimiter.node);
+ final linkNodes = syntax.close(this, delimiter, null, getChildren: () {
+ _processDelimiterRun(index);
+ // All of the nodes which lie past [index] are children of this
+ // link/image.
+ final children = _tree.sublist(nodeIndex + 1, _tree.length);
+ _tree.removeRange(nodeIndex + 1, _tree.length);
+ return children;
+ });
+ if (linkNodes != null) {
+ _delimiterStack.removeAt(index);
+ if (delimiter.char == $lbracket) {
+ for (final d in _delimiterStack.sublist(0, index)) {
+ if (d.char == $lbracket) d.isActive = false;
+ }
+ }
+ _tree.replaceRange(nodeIndex, _tree.length, linkNodes);
+ advanceBy(1);
+ start = pos;
+ } else {
+ _delimiterStack.removeAt(index);
+ pos = start;
+ advanceBy(1);
+ }
+ } else {
+ throw StateError('Non-link syntax delimiter found with character '
+ '"${delimiter.char}"');
+ }
+ }
+
+ /// Rules 9 and 10.
+ bool _canFormEmphasis(Delimiter opener, Delimiter closer) {
+ if ((opener.canOpen && opener.canClose) ||
+ (closer.canOpen && closer.canClose)) {
+ return (opener.length + closer.length) % 3 != 0 ||
+ (opener.length % 3 == 0 && closer.length % 3 == 0);
+ } else {
+ return true;
+ }
+ }
+
+ /// Processes [DelimiterRun] type delimiters from [bottomIndex] and up.
+ ///
+ /// This is the same strategy as "process emphasis" routine according to the
+ /// CommonMark spec: https://spec.commonmark.org/0.30/#phase-2-inline-structure.
+ void _processDelimiterRun(int bottomIndex) {
+ var currentIndex = bottomIndex + 1;
+ // Track the lowest index where we might find an open delimiter given a
+ // closing delimiter length modulo 3.
+ // Each key in this map is an open delimiter character. Each value is a
+ // 3-element list. Each value in the list is the lowest index for the given
+ // delimiter length modulo 3 (0, 1, 2).
+ final openersBottom = <int, List<int>>{};
+ while (currentIndex < _delimiterStack.length) {
+ final closer = _delimiterStack[currentIndex];
+ if (!closer.canClose || closer is! DelimiterRun) {
+ currentIndex++;
+ continue;
+ }
+ openersBottom.putIfAbsent(closer.char, () => List.filled(3, bottomIndex));
+ final openersBottomPerCloserLength = openersBottom[closer.char]!;
+ final openerBottom = openersBottomPerCloserLength[closer.length % 3];
+ final openerIndex = _delimiterStack.lastIndexWhere(
+ (d) =>
+ d.char == closer.char && d.canOpen && _canFormEmphasis(d, closer),
+ currentIndex - 1);
+ if (openerIndex > bottomIndex && openerIndex > openerBottom) {
+ // Found an opener for [closer].
+ final opener = _delimiterStack[openerIndex];
+ if (opener is! DelimiterRun) {
+ currentIndex++;
+ continue;
+ }
+ final matchedTagIndex = opener.tags.lastIndexWhere((e) =>
+ opener.length >= e.indicatorLength &&
+ closer.length >= e.indicatorLength);
+ if (matchedTagIndex == -1) {
+ currentIndex++;
+ continue;
+ }
+ final matchedTag = opener.tags[matchedTagIndex];
+ final indicatorLength = matchedTag.indicatorLength;
+ final openerTextNode = opener.node;
+ final openerTextNodeIndex = _tree.indexOf(openerTextNode);
+ final closerTextNode = closer.node;
+ var closerTextNodeIndex = _tree.indexOf(closerTextNode);
+ final nodes = opener.syntax.close(
+ this,
+ opener,
+ closer,
+ tag: matchedTag.tag,
+ getChildren: () => _tree.sublist(
+ openerTextNodeIndex + 1,
+ closerTextNodeIndex,
+ ),
+ );
+ // Replace all of the nodes between the opener and the closer (which
+ // are now the new emphasis node's children) with the emphasis node.
+ _tree.replaceRange(
+ openerTextNodeIndex + 1,
+ closerTextNodeIndex,
+ nodes!,
+ );
+ // Slide [closerTextNodeIndex] back accordingly.
+ closerTextNodeIndex = openerTextNodeIndex + 2;
+
+ _delimiterStack.removeRange(openerIndex + 1, currentIndex);
+ // Slide [currentIndex] back accordingly.
+ currentIndex = openerIndex + 1;
+
+ // Remove delimiter characters, possibly removing nodes from the tree
+ // and Delimiters from the delimiter stack.
+ if (opener.length == indicatorLength) {
+ _tree.removeAt(openerTextNodeIndex);
+ _delimiterStack.removeAt(openerIndex);
+ // Slide [currentIndex] and [closerTextNodeIndex] back accordingly.
+ currentIndex--;
+ closerTextNodeIndex--;
+ } else {
+ final newOpenerTextNode =
+ Text(openerTextNode.text.substring(indicatorLength));
+ _tree[openerTextNodeIndex] = newOpenerTextNode;
+ opener.node = newOpenerTextNode;
+ }
+
+ if (closer.length == indicatorLength) {
+ _tree.removeAt(closerTextNodeIndex);
+ _delimiterStack.removeAt(currentIndex);
+ // [currentIndex] has just moved to point at the next delimiter;
+ // leave it.
+ } else {
+ final newCloserTextNode =
+ Text(closerTextNode.text.substring(indicatorLength));
+ _tree[closerTextNodeIndex] = newCloserTextNode;
+ closer.node = newCloserTextNode;
+ // [currentIndex] needs to be considered again; leave it.
+ }
+ } else {
+ // No opener is found.
+ openersBottomPerCloserLength[closer.length % 3] = currentIndex - 1;
+ if (!closer.canOpen) {
+ _delimiterStack.removeAt(currentIndex);
+ // This advances [currentIndex] to the next delimiter.
+ } else {
+ currentIndex++;
+ }
+ }
+ }
+
+ _delimiterStack.removeRange(bottomIndex + 1, _delimiterStack.length);
+ }
+
+ // Combine any remaining adjacent Text nodes. This is important to produce
+ // correct output across newlines, where whitespace is sometimes compressed.
+ void _combineAdjacentText(List<Node> nodes) {
+ for (var i = 0; i < nodes.length - 1; i++) {
+ final node = nodes[i];
+ if (node is Element && node.children != null) {
+ _combineAdjacentText(node.children!);
+ continue;
+ }
+ if (node is Text && nodes[i + 1] is Text) {
+ final buffer =
+ StringBuffer('${node.textContent}${nodes[i + 1].textContent}');
+ var j = i + 2;
+ while (j < nodes.length && nodes[j] is Text) {
+ buffer.write(nodes[j].textContent);
+ j++;
+ }
+ nodes[i] = Text(buffer.toString());
+ nodes.removeRange(i + 1, j);
+ }
+ }
+ }
+
+ int charAt(int index) => source.codeUnitAt(index);
+
+ void writeText() {
+ if (pos == start) {
+ return;
+ }
+ final text = source.substring(start, pos);
+ _tree.add(Text(text));
+ start = pos;
+ }
+
+ /// Add [node] to the current tree.
+ void addNode(Node node) {
+ _tree.add(node);
+ }
+
+ /// Push [delimiter] onto the stack of [Delimiter]s.
+ void pushDelimiter(Delimiter delimiter) => _delimiterStack.add(delimiter);
+
+ bool get isDone => pos == source.length;
+
+ void advanceBy(int length) {
+ pos += length;
+ }
+
+ void consume(int length) {
+ pos += length;
+ start = pos;
+ }
+
+ bool get encodeHtml => document.encodeHtml;
+}
diff --git a/pkgs/markdown/lib/src/inline_syntaxes/autolink_extension_syntax.dart b/pkgs/markdown/lib/src/inline_syntaxes/autolink_extension_syntax.dart
new file mode 100644
index 0000000..0333abb
--- /dev/null
+++ b/pkgs/markdown/lib/src/inline_syntaxes/autolink_extension_syntax.dart
@@ -0,0 +1,156 @@
+// 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 '../ast.dart';
+import '../charcode.dart';
+import '../inline_parser.dart';
+import '../util.dart';
+import 'inline_syntax.dart';
+
+/// Matches autolinks like `http://foo.com` and `foo@bar.com`.
+class AutolinkExtensionSyntax extends InlineSyntax {
+ static const _linkPattern =
+ // Autolinks can only come at the beginning of a line, after whitespace,
+ // or any of the delimiting characters *, _, ~, and (.
+ // Note: Disable this piece for now, as Safari does not support
+ // lookarounds. Consider re-enabling later.
+ // r'(?<=^|[\s*_~(>])'
+
+ // An extended url autolink will be recognised when one of the schemes
+ // http://, or https://, followed by a valid domain. See
+ // https://github.github.com/gfm/#extended-url-autolink.
+ r'(?:(?:https?|ftp):\/\/|www\.)'
+
+ // A valid domain consists of segments of alphanumeric characters,
+ // underscores (_) and hyphens (-) separated by periods (.). There must
+ // be at least one period, and no underscores may be present in the last
+ // two segments of the domain. See
+ // https://github.github.com/gfm/#valid-domain.
+ r'(?:[-_a-z0-9]+\.)*(?:[-a-z0-9]+\.[-a-z0-9]+)'
+
+ // After a valid domain, zero or more non-space non-< characters may
+ // follow.
+ r'[^\s<]*'
+
+ // Trailing punctuation (specifically, ?, !, ., ,, :, *, _, and ~) will
+ // not be considered part of the autolink, though they may be included in
+ // the interior of the link. See
+ // https://github.github.com/gfm/#extended-autolink-path-validation.
+ // Note: Do not use negative lookbehind, as Safari does not support it.
+ // '(?<![?!.,:*_~])'
+ r'[^\s<?!.,:*_~]';
+
+ // An extended email autolink, see
+ // https://github.github.com/gfm/#extended-email-autolink.
+ static const _emailPattern =
+ r'[-_.+a-z0-9]+@(?:[-_a-z0-9]+\.)+[-_a-z0-9]*[a-z0-9]';
+
+ AutolinkExtensionSyntax()
+ : super(
+ '($_linkPattern)|($_emailPattern)',
+ caseSensitive: false,
+ );
+
+ @override
+ bool tryMatch(InlineParser parser, [int? startMatchPos]) {
+ startMatchPos ??= parser.pos;
+ final startMatch = pattern.matchAsPrefix(parser.source, startMatchPos);
+ if (startMatch == null) {
+ return false;
+ }
+
+ // When it is a link and it is not at the beginning of a line, or preceded
+ // by whitespace, `*`, `_`, `~`, `(`, or `>`, it is invalid. See
+ // https://github.github.com/gfm/#autolinks-extension-.
+ if (startMatch[1] != null && parser.pos > 0) {
+ final precededBy = String.fromCharCode(parser.charAt(parser.pos - 1));
+ const validPrecedingChars = {'\n', ' ', '*', '_', '~', '(', '>'};
+ if (!validPrecedingChars.contains(precededBy)) {
+ return false;
+ }
+ }
+
+ // When it is an email link and followed by `_` or `-`, it is invalid. See
+ // https://github.github.com/gfm/#example-633
+ if (startMatch[2] != null && parser.source.length > startMatch.end) {
+ final followedBy = String.fromCharCode(parser.charAt(startMatch.end));
+ const invalidFollowingChars = {'_', '-'};
+ if (invalidFollowingChars.contains(followedBy)) {
+ return false;
+ }
+ }
+
+ parser.writeText();
+ return onMatch(parser, startMatch);
+ }
+
+ @override
+ bool onMatch(InlineParser parser, Match match) {
+ int consumeLength;
+
+ final isEmailLink = match[2] != null;
+ if (isEmailLink) {
+ consumeLength = match.match.length;
+ } else {
+ consumeLength = _getConsumeLength(match.match);
+ }
+
+ var text = match.match.substring(0, consumeLength);
+ text = parser.encodeHtml ? escapeHtml(text) : text;
+
+ var destination = text;
+ if (isEmailLink) {
+ destination = 'mailto:$destination';
+ } else if (destination[0] == 'w') {
+ // When there is no scheme specified, insert the scheme `http`.
+ destination = 'http://$destination';
+ }
+
+ final anchor = Element.text('a', text)
+ ..attributes['href'] = Uri.encodeFull(destination);
+
+ parser
+ ..addNode(anchor)
+ ..consume(consumeLength);
+
+ return true;
+ }
+
+ int _getConsumeLength(String text) {
+ var excludedLength = 0;
+
+ // When an autolink ends in `)`, see
+ // https://github.github.com/gfm/#example-625.
+ if (text.endsWith(')')) {
+ final match = RegExp(r'(\(.*)?(\)+)$').firstMatch(text)!;
+
+ if (match[1] == null) {
+ excludedLength = match[2]!.length;
+ } else {
+ var parenCount = 0;
+ for (var i = 0; i < text.length; i++) {
+ final char = text.codeUnitAt(i);
+ if (char == $lparen) {
+ parenCount++;
+ } else if (char == $rparen) {
+ parenCount--;
+ }
+ }
+ if (parenCount < 0) {
+ excludedLength = parenCount.abs();
+ }
+ }
+ }
+ // If an autolink ends in a semicolon `;`, see
+ // https://github.github.com/gfm/#example-627
+ else if (text.endsWith(';')) {
+ final match = RegExp(r'&[0-9a-z]+;$').firstMatch(text);
+ if (match != null) {
+ excludedLength = match.match.length;
+ }
+ }
+
+ return text.length - excludedLength;
+ }
+}
diff --git a/pkgs/markdown/lib/src/inline_syntaxes/autolink_syntax.dart b/pkgs/markdown/lib/src/inline_syntaxes/autolink_syntax.dart
new file mode 100644
index 0000000..073c1cc
--- /dev/null
+++ b/pkgs/markdown/lib/src/inline_syntaxes/autolink_syntax.dart
@@ -0,0 +1,27 @@
+// 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 '../ast.dart';
+import '../inline_parser.dart';
+import '../util.dart';
+import 'inline_syntax.dart';
+
+/// Matches autolinks like `<http://foo.com>`.
+class AutolinkSyntax extends InlineSyntax {
+ AutolinkSyntax() : super(r'<(([a-zA-Z][a-zA-Z\-\+\.]+):(?://)?[^\s>]*)>');
+
+ @override
+ bool onMatch(InlineParser parser, Match match) {
+ final url = match[1]!;
+ final text = parser.encodeHtml ? escapeHtml(url) : url;
+ final anchor = Element.text('a', text);
+
+ final destination = normalizeLinkDestination(url);
+ anchor.attributes['href'] =
+ parser.encodeHtml ? escapeHtml(destination) : destination;
+ parser.addNode(anchor);
+
+ return true;
+ }
+}
diff --git a/pkgs/markdown/lib/src/inline_syntaxes/code_syntax.dart b/pkgs/markdown/lib/src/inline_syntaxes/code_syntax.dart
new file mode 100644
index 0000000..35783d3
--- /dev/null
+++ b/pkgs/markdown/lib/src/inline_syntaxes/code_syntax.dart
@@ -0,0 +1,88 @@
+// 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 '../ast.dart';
+import '../charcode.dart';
+import '../inline_parser.dart';
+import '../util.dart';
+import 'inline_syntax.dart';
+
+/// Matches backtick-enclosed inline code blocks.
+class CodeSyntax extends InlineSyntax {
+ // This pattern matches:
+ //
+ // * a string of backticks (not followed by any more), followed by
+ // * a non-greedy string of anything, including newlines, ending with anything
+ // except a backtick, followed by
+ // * a string of backticks the same length as the first, not followed by any
+ // more.
+ //
+ // This conforms to the delimiters of inline code, both in Markdown.pl, and
+ // CommonMark.
+ static const _pattern = r'(`+(?!`))((?:.|\n)*?[^`])\1(?!`)';
+
+ CodeSyntax() : super(_pattern);
+
+ @override
+ bool tryMatch(InlineParser parser, [int? startMatchPos]) {
+ if (parser.pos > 0 && parser.charAt(parser.pos - 1) == $backquote) {
+ // Not really a match! We can't just sneak past one backtick to try the
+ // next character. An example of this situation would be:
+ //
+ // before ``` and `` after.
+ // ^--parser.pos
+ return false;
+ }
+
+ final match = pattern.matchAsPrefix(parser.source, parser.pos);
+ if (match == null) {
+ return false;
+ }
+ parser.writeText();
+ if (onMatch(parser, match)) parser.consume(match.match.length);
+ return true;
+ }
+
+ @override
+ bool onMatch(InlineParser parser, Match match) {
+ final markerLength = match[1]!.length;
+ final contentLength = match.match.length - markerLength * 2;
+ final contentStart = parser.pos + markerLength;
+ final contentEnd = contentStart + contentLength;
+
+ var code = parser.source.substring(contentStart, contentEnd);
+ if (_shouldStrip(code)) {
+ code = code.substring(1, code.length - 1);
+ }
+ code = code.replaceAll('\n', ' ');
+
+ if (parser.encodeHtml) {
+ code = escapeHtml(code, escapeApos: false);
+ }
+
+ parser.addNode(Element.text('code', code));
+ return true;
+ }
+
+ bool _shouldStrip(String code) {
+ // No stripping occurs if the code span contains only spaces:
+ // https://spec.commonmark.org/0.30/#example-334.
+ if (code.trim().isEmpty) {
+ return false;
+ }
+
+ // Only spaces, and not unicode whitespace in general, are stripped in this
+ // way, see https://spec.commonmark.org/0.30/#example-333.
+ final startsWithSpace = code.startsWith(' ') || code.startsWith('\n');
+ final endsWithSpace = code.endsWith(' ') || code.endsWith('\n');
+
+ // The stripping only happens if the space is on both sides of the string:
+ // https://spec.commonmark.org/0.30/#example-332.
+ if (!startsWithSpace || !endsWithSpace) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/pkgs/markdown/lib/src/inline_syntaxes/color_swatch_syntax.dart b/pkgs/markdown/lib/src/inline_syntaxes/color_swatch_syntax.dart
new file mode 100644
index 0000000..e44fdbf
--- /dev/null
+++ b/pkgs/markdown/lib/src/inline_syntaxes/color_swatch_syntax.dart
@@ -0,0 +1,79 @@
+// 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 '../ast.dart';
+import '../charcode.dart';
+import '../inline_parser.dart';
+import '../util.dart';
+import 'inline_syntax.dart';
+
+/// Matches code blocks containing a subset of CSS color syntax.
+class ColorSwatchSyntax extends InlineSyntax {
+ /// This pattern matches:
+ /// * GitHub Flavored Markup supports fewer of these options, GitLab Flavored
+ /// Markup supports all of these. Presumably GitHub will be more complete at
+ /// some point.
+ /// * CSS style '#' prefixed color hex codes in 3,4,6 or 8 digits in length.
+ /// * CSS style RGB()/RgbA()/Hsl()/HSLA() style color declarations, of any
+ /// capitalization.
+ /// EXAMPLES:
+ /// * `#f00`
+ /// * `#BA` (2 digit hex, regex will not match)
+ /// * `#F00a`
+ /// * `#F0BAD` (5 digit hex, regex will not match)
+ /// * `#FF0000`
+ /// * `#F000BAD` (7 digit hex, regex will not match)
+ /// * `#FF0000aA` (GitHub supports only this style)
+ /// * `RGB(0,255,0)`
+ /// * `rgb(0,255,0)`
+ /// * `RGB(0%,100%,0%)`
+ /// * `rgb(0%,100%,0%)`
+ /// * `RGBA(0,255,0,0.3)`
+ /// * `rgba(0,255,0,0.3)`
+ /// * `HSL(540,70%,50%)`
+ /// * `hsl(540,70%,50%)`
+ /// * `HSLA(540,70%,50%,0.3)`
+ /// * `Hsla(540,70%,50%,0.3)`
+ static const _pattern =
+ '`((#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8}))|'
+ r'([Rr][Gg][Bb][Aa]?\((\d+[%]?),(\d+[%]?),(\d+[%]?),?(\d+\.?\d+[%]?)?\))|'
+ r'([Hh][Ss][Ll][Aa]?\((\d+[%]?),(\d+[%]?),(\d+[%]?),?(\d+\.?\d+[%]?)?\)))`';
+
+ ColorSwatchSyntax() : super(_pattern);
+
+ @override
+ bool tryMatch(InlineParser parser, [int? startMatchPos]) {
+ if (parser.pos > 0 && parser.charAt(parser.pos - 1) == $backquote) {
+ // Not really a match! We can't just sneak past one backtick to try the
+ // next character. An example of this situation would be:
+ //
+ // before ``` and `` after.
+ // ^--parser.pos
+ return false;
+ }
+
+ final match = pattern.matchAsPrefix(parser.source, parser.pos);
+ if (match == null) {
+ return false;
+ }
+ parser.writeText();
+ if (onMatch(parser, match)) parser.consume(match.match.length);
+ return true;
+ }
+
+ @override
+ bool onMatch(InlineParser parser, Match match) {
+ var code = match[1]!.trim().replaceAll('\n', ' ');
+
+ if (parser.encodeHtml) code = escapeHtml(code);
+
+ parser.addNode(Element('code', [
+ Text(code),
+ Element.withTag('span')..attributes['style'] = 'background-color:$code;',
+ ])
+ ..attributes['class'] = 'gfm-color_chip');
+
+ return true;
+ }
+}
diff --git a/pkgs/markdown/lib/src/inline_syntaxes/decode_html_syntax.dart b/pkgs/markdown/lib/src/inline_syntaxes/decode_html_syntax.dart
new file mode 100644
index 0000000..1af15d5
--- /dev/null
+++ b/pkgs/markdown/lib/src/inline_syntaxes/decode_html_syntax.dart
@@ -0,0 +1,51 @@
+// 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 '../assets/html_entities.dart';
+import '../ast.dart';
+import '../charcode.dart';
+import '../inline_parser.dart';
+import '../patterns.dart';
+import '../util.dart';
+import 'inline_syntax.dart';
+
+/// Decodes numeric character references, for example decode `#` to `#`.
+// https://spec.commonmark.org/0.30/#entity-and-numeric-character-references
+class DecodeHtmlSyntax extends InlineSyntax {
+ DecodeHtmlSyntax()
+ : super(htmlCharactersPattern.pattern,
+ caseSensitive: false, startCharacter: $ampersand);
+
+ @override
+ bool tryMatch(InlineParser parser, [int? startMatchPos]) {
+ if (parser.pos > 0 && parser.charAt(parser.pos - 1) == $backquote) {
+ return false;
+ }
+
+ final match = pattern.matchAsPrefix(parser.source, parser.pos);
+ if (match == null) {
+ return false;
+ }
+
+ if (match[1] != null && htmlEntitiesMap[match.match] == null) {
+ return false;
+ }
+
+ parser.writeText();
+ if (onMatch(parser, match)) parser.consume(match.match.length);
+ return true;
+ }
+
+ @override
+ bool onMatch(InlineParser parser, Match match) {
+ var decodedText = decodeHtmlCharacterFromMatch(match);
+
+ if (parser.encodeHtml) {
+ decodedText = escapeHtml(decodedText);
+ }
+
+ parser.addNode(Text(decodedText));
+ return true;
+ }
+}
diff --git a/pkgs/markdown/lib/src/inline_syntaxes/delimiter_syntax.dart b/pkgs/markdown/lib/src/inline_syntaxes/delimiter_syntax.dart
new file mode 100644
index 0000000..f26bdc9
--- /dev/null
+++ b/pkgs/markdown/lib/src/inline_syntaxes/delimiter_syntax.dart
@@ -0,0 +1,337 @@
+// 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 '../ast.dart';
+import '../inline_parser.dart';
+import '../patterns.dart';
+import 'inline_syntax.dart';
+
+/// Matches syntax that has a pair of tags and becomes an element, like `*` for
+/// `<em>`. Allows nested tags.
+class DelimiterSyntax extends InlineSyntax {
+ /// Whether this is parsed according to the same nesting rules as [emphasis
+ /// delimiters][].
+ ///
+ /// [emphasis delimiters]: https://spec.commonmark.org/0.30/#can-open-emphasis
+ final bool requiresDelimiterRun;
+
+ /// Whether to allow intra-word delimiter runs. CommonMark emphasis and
+ /// strong emphasis does not allow this, but GitHub-Flavored Markdown allows
+ /// it on strikethrough.
+ final bool allowIntraWord;
+
+ final List<DelimiterTag>? tags;
+
+ /// Creates a new [DelimiterSyntax] which matches text on [pattern].
+ ///
+ /// The [pattern] is used to find the matching text. If [requiresDelimiterRun]
+ /// is passed, this syntax parses according to the same nesting rules as
+ /// emphasis delimiters. If [startCharacter] is passed, it is used as a
+ /// pre-matching check which is faster than matching against [pattern].
+ DelimiterSyntax(
+ super.pattern, {
+ this.requiresDelimiterRun = false,
+ super.startCharacter,
+ this.allowIntraWord = false,
+ this.tags,
+ });
+
+ @override
+ bool onMatch(InlineParser parser, Match match) {
+ final runLength = match.group(0)!.length;
+ final matchStart = parser.pos;
+ final matchEnd = parser.pos + runLength;
+ final text = Text(parser.source.substring(matchStart, matchEnd));
+ if (!requiresDelimiterRun) {
+ parser.pushDelimiter(SimpleDelimiter(
+ node: text,
+ length: runLength,
+ char: parser.source.codeUnitAt(matchStart),
+ canOpen: true,
+ canClose: false,
+ syntax: this,
+ endPos: matchEnd,
+ ));
+ parser.addNode(text);
+ return true;
+ }
+
+ final delimiterRun = DelimiterRun.tryParse(
+ parser,
+ matchStart,
+ matchEnd,
+ syntax: this,
+ node: text,
+ allowIntraWord: allowIntraWord,
+ tags: tags ?? const [],
+ );
+ if (delimiterRun != null) {
+ parser.pushDelimiter(delimiterRun);
+ parser.addNode(text);
+ return true;
+ } else {
+ parser.advanceBy(runLength);
+ return false;
+ }
+ }
+
+ /// Attempts to close this tag at the current position.
+ ///
+ /// If a tag cannot be closed at the current position (for example, if a link
+ /// reference cannot be found for a link tag's label), then `null` is
+ /// returned.
+ ///
+ /// If a tag can be closed at the current position, then this method calls
+ /// [getChildren], in which [parser] parses any nested text into child nodes.
+ /// The returned [Iterable] includes these children nodes.
+ Iterable<Node>? close(
+ InlineParser parser,
+ Delimiter opener,
+ Delimiter closer, {
+ required String tag,
+ required List<Node> Function() getChildren,
+ }) {
+ return [Element(tag, getChildren())];
+ }
+}
+
+class DelimiterTag {
+ DelimiterTag(this.tag, this.indicatorLength);
+
+ // Tag name of the HTML element.
+ final String tag;
+
+ final int indicatorLength;
+}
+
+/// A delimiter indicating the possible "open" or possible "close" of a tag for
+/// a [DelimiterSyntax].
+abstract class Delimiter {
+ /// The [Text] node representing the plain text representing this delimiter.
+ abstract Text node;
+
+ /// The type of delimiter.
+ ///
+ /// For the two-character image delimiter, `](links).
+ ///
+ /// Once we have parsed `Text [`, there is one (pending) link in the state
+ /// stack. It is, by default, active. Once we parse the next possible link,
+ /// `[more](links)`, as a real link, we must deactive the pending links (just
+ /// the one, in this case).
+ abstract bool isActive;
+
+ /// Whether this delimiter can open emphasis or strong emphasis.
+ bool get canOpen;
+
+ /// Whether this delimiter can close emphasis or strong emphasis.
+ bool get canClose;
+
+ /// The syntax which uses this delimiter to parse a tag.
+ DelimiterSyntax get syntax;
+}
+
+/// A simple delimiter implements the [Delimiter] interface with basic fields,
+/// and does not have the concept of "left-flanking" or "right-flanking".
+class SimpleDelimiter implements Delimiter {
+ @override
+ Text node;
+
+ @override
+ final int char;
+
+ @override
+ final int length;
+
+ @override
+ bool isActive;
+
+ @override
+ final bool canOpen;
+
+ @override
+ final bool canClose;
+
+ @override
+ final DelimiterSyntax syntax;
+
+ final int endPos;
+
+ SimpleDelimiter({
+ required this.node,
+ required this.char,
+ required this.length,
+ required this.canOpen,
+ required this.canClose,
+ required this.syntax,
+ required this.endPos,
+ }) : isActive = true;
+}
+
+/// An implementation of [Delimiter] which uses concepts of "left-flanking" and
+/// "right-flanking" to determine the values of [canOpen] and [canClose].
+///
+/// This is primarily used when parsing emphasis and strong emphasis, but can
+/// also be used by other extensions of [DelimiterSyntax].
+class DelimiterRun implements Delimiter {
+ /// According to
+ /// [CommonMark](https://spec.commonmark.org/0.30/#unicode-punctuation-character):
+ ///
+ /// > A punctuation character is an ASCII punctuation character or anything in
+ /// > the general Unicode categories `Pc`, `Pd`, `Pe`, `Pf`, `Pi`, `Po`, or
+ /// > `Ps`.
+ // This RegExp is inspired by
+ // https://github.com/commonmark/commonmark.js/blob/1f7d09099c20d7861a674674a5a88733f55ff729/lib/inlines.js#L39.
+ // I don't know if there is any way to simplify it or maintain it.
+ static final unicodePunctuationPattern = RegExp('['
+ '$asciiPunctuationEscaped'
+ r'\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE'
+ r'\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E'
+ r'\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E'
+ r'\u0964\u0965\u0970\u0AF0\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14'
+ r'\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB'
+ r'\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736'
+ r'\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F'
+ r'\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E'
+ r'\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051'
+ r'\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A'
+ r'\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC'
+ r'\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E42'
+ r'\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE'
+ r'\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF'
+ r'\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF'
+ r'\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19'
+ r'\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03'
+ r'\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F'
+ r'\uFF5B\uFF5D\uFF5F-\uFF65'
+ ']');
+
+ /// Unicode whitespace.
+ // See https://spec.commonmark.org/0.30/#unicode-whitespace-character.
+ // Unicode Zs: https://www.compart.com/en/unicode/category.
+ static const unicodeWhitespace = '\u0020\u0009\u000A\u000C\u000D'
+ '\u00A0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008'
+ '\u2009\u200A\u202F\u205F\u3000';
+
+ @override
+ Text node;
+
+ @override
+ final int char;
+
+ @override
+ int get length => node.text.length;
+
+ @override
+ bool isActive;
+
+ @override
+ final DelimiterSyntax syntax;
+
+ final bool allowIntraWord;
+
+ @override
+ final bool canOpen;
+
+ @override
+ final bool canClose;
+
+ final List<DelimiterTag> tags;
+
+ DelimiterRun._({
+ required this.node,
+ required this.char,
+ required this.syntax,
+ required this.tags,
+ required bool isLeftFlanking,
+ required bool isRightFlanking,
+ required bool isPrecededByPunctuation,
+ required bool isFollowedByPunctuation,
+ required this.allowIntraWord,
+ }) : canOpen = isLeftFlanking &&
+ (!isRightFlanking || allowIntraWord || isPrecededByPunctuation),
+ canClose = isRightFlanking &&
+ (!isLeftFlanking || allowIntraWord || isFollowedByPunctuation),
+ isActive = true;
+
+ /// Tries to parse a delimiter run from [runStart] (inclusive) to [runEnd]
+ /// (exclusive).
+ static DelimiterRun? tryParse(
+ InlineParser parser,
+ int runStart,
+ int runEnd, {
+ required DelimiterSyntax syntax,
+ required List<DelimiterTag> tags,
+ required Text node,
+ bool allowIntraWord = false,
+ }) {
+ bool precededByWhitespace;
+ bool followedByWhitespace;
+ bool precededByPunctuation;
+ bool followedByPunctuation;
+
+ if (runStart == 0) {
+ precededByWhitespace = true;
+ precededByPunctuation = false;
+ } else {
+ final preceding = parser.source.substring(runStart - 1, runStart);
+ precededByWhitespace = unicodeWhitespace.contains(preceding);
+ precededByPunctuation = !precededByWhitespace &&
+ unicodePunctuationPattern.hasMatch(preceding);
+ }
+
+ if (runEnd == parser.source.length) {
+ followedByWhitespace = true;
+ followedByPunctuation = false;
+ } else {
+ final following = parser.source.substring(runEnd, runEnd + 1);
+ followedByWhitespace = unicodeWhitespace.contains(following);
+ followedByPunctuation = !followedByWhitespace &&
+ unicodePunctuationPattern.hasMatch(following);
+ }
+
+ // If it is a left-flanking delimiter run, see
+ // https://spec.commonmark.org/0.30/#left-flanking-delimiter-run.
+ final isLeftFlanking = !followedByWhitespace &&
+ (!followedByPunctuation ||
+ precededByWhitespace ||
+ precededByPunctuation);
+
+ // If it is a right-flanking delimiter run, see
+ // https://spec.commonmark.org/0.30/#right-flanking-delimiter-run.
+ final isRightFlanking = !precededByWhitespace &&
+ (!precededByPunctuation ||
+ followedByWhitespace ||
+ followedByPunctuation);
+
+ // Make sure the shorter delimiter takes precedence.
+ tags.sort((a, b) => a.indicatorLength.compareTo(b.indicatorLength));
+
+ return DelimiterRun._(
+ node: node,
+ char: parser.charAt(runStart),
+ syntax: syntax,
+ tags: tags,
+ isLeftFlanking: isLeftFlanking,
+ isRightFlanking: isRightFlanking,
+ isPrecededByPunctuation: precededByPunctuation,
+ isFollowedByPunctuation: followedByPunctuation,
+ allowIntraWord: allowIntraWord,
+ );
+ }
+
+ @override
+ String toString() => '<char: $char, length: $length, canOpen: $canOpen, '
+ 'canClose: $canClose>';
+}
diff --git a/pkgs/markdown/lib/src/inline_syntaxes/email_autolink_syntax.dart b/pkgs/markdown/lib/src/inline_syntaxes/email_autolink_syntax.dart
new file mode 100644
index 0000000..212f1a4
--- /dev/null
+++ b/pkgs/markdown/lib/src/inline_syntaxes/email_autolink_syntax.dart
@@ -0,0 +1,31 @@
+// 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 '../ast.dart';
+import '../charcode.dart';
+import '../inline_parser.dart';
+import '../util.dart';
+import 'inline_syntax.dart';
+
+/// Matches autolinks like `<foo@bar.example.com>`.
+///
+/// See <https://spec.commonmark.org/0.30/#email-address>.
+class EmailAutolinkSyntax extends InlineSyntax {
+ static const _email =
+ r'''[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}'''
+ r'''[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*''';
+
+ EmailAutolinkSyntax() : super('<($_email)>', startCharacter: $lt);
+
+ @override
+ bool onMatch(InlineParser parser, Match match) {
+ final url = match[1]!;
+ final text = parser.encodeHtml ? escapeHtml(url) : url;
+ final anchor = Element.text('a', text);
+ anchor.attributes['href'] = Uri.encodeFull('mailto:$url');
+ parser.addNode(anchor);
+
+ return true;
+ }
+}
diff --git a/pkgs/markdown/lib/src/inline_syntaxes/emoji_syntax.dart b/pkgs/markdown/lib/src/inline_syntaxes/emoji_syntax.dart
new file mode 100644
index 0000000..a068c6b
--- /dev/null
+++ b/pkgs/markdown/lib/src/inline_syntaxes/emoji_syntax.dart
@@ -0,0 +1,32 @@
+// 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 '../ast.dart';
+import '../emojis.dart';
+import '../inline_parser.dart';
+import 'inline_syntax.dart';
+
+/// Matches GitHub Markdown emoji syntax like `:smile:`.
+///
+/// There is no formal specification of GitHub's support for this colon-based
+/// emoji support, so this syntax is based on the results of Markdown-enabled
+/// text fields at github.com.
+class EmojiSyntax extends InlineSyntax {
+ // Emoji "aliases" are mostly limited to lower-case letters, numbers, and
+ // underscores, but GitHub also supports `:+1:` and `:-1:`.
+ EmojiSyntax() : super(':([a-z0-9_+-]+):');
+
+ @override
+ bool onMatch(InlineParser parser, Match match) {
+ final alias = match[1]!;
+ final emoji = emojis[alias];
+ if (emoji == null) {
+ parser.advanceBy(1);
+ return false;
+ }
+ parser.addNode(Text(emoji));
+
+ return true;
+ }
+}
diff --git a/pkgs/markdown/lib/src/inline_syntaxes/emphasis_syntax.dart b/pkgs/markdown/lib/src/inline_syntaxes/emphasis_syntax.dart
new file mode 100644
index 0000000..58f2e70
--- /dev/null
+++ b/pkgs/markdown/lib/src/inline_syntaxes/emphasis_syntax.dart
@@ -0,0 +1,29 @@
+// 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 '../charcode.dart';
+import 'delimiter_syntax.dart';
+
+class EmphasisSyntax extends DelimiterSyntax {
+ /// Parses `__strong__` and `_emphasis_`.
+ EmphasisSyntax.underscore()
+ : super(
+ '_+',
+ requiresDelimiterRun: true,
+ tags: _tags,
+ startCharacter: $underscore,
+ );
+
+ /// Parses `**strong**` and `*emphasis*`.
+ EmphasisSyntax.asterisk()
+ : super(
+ r'\*+',
+ requiresDelimiterRun: true,
+ allowIntraWord: true,
+ tags: _tags,
+ startCharacter: $asterisk,
+ );
+
+ static final _tags = [DelimiterTag('em', 1), DelimiterTag('strong', 2)];
+}
diff --git a/pkgs/markdown/lib/src/inline_syntaxes/escape_html_syntax.dart b/pkgs/markdown/lib/src/inline_syntaxes/escape_html_syntax.dart
new file mode 100644
index 0000000..565f870
--- /dev/null
+++ b/pkgs/markdown/lib/src/inline_syntaxes/escape_html_syntax.dart
@@ -0,0 +1,20 @@
+// 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 '../ast.dart';
+import '../inline_parser.dart';
+import '../util.dart';
+import 'inline_syntax.dart';
+
+/// Encodes (`"`), (`<`), (`>`) and (`&`).
+class EscapeHtmlSyntax extends InlineSyntax {
+ EscapeHtmlSyntax() : super('["<>&]');
+ @override
+ bool onMatch(InlineParser parser, Match match) {
+ final text = escapeHtml(match[0]!);
+ parser.addNode(Text(text));
+
+ return true;
+ }
+}
diff --git a/pkgs/markdown/lib/src/inline_syntaxes/escape_syntax.dart b/pkgs/markdown/lib/src/inline_syntaxes/escape_syntax.dart
new file mode 100644
index 0000000..c112d93
--- /dev/null
+++ b/pkgs/markdown/lib/src/inline_syntaxes/escape_syntax.dart
@@ -0,0 +1,34 @@
+// 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 '../ast.dart';
+import '../charcode.dart';
+import '../inline_parser.dart';
+import '../patterns.dart';
+import '../util.dart';
+import 'inline_syntax.dart';
+
+/// Escape ASCII punctuation preceded by a backslash.
+///
+/// Backslashes before other characters are treated as literal backslashes.
+// See https://spec.commonmark.org/0.30/#backslash-escapes.
+class EscapeSyntax extends InlineSyntax {
+ EscapeSyntax()
+ : super('\\\\([$asciiPunctuationEscaped])', startCharacter: $backslash);
+
+ @override
+ bool onMatch(InlineParser parser, Match match) {
+ final chars = match.match;
+
+ String text;
+ if ('&"<>'.contains(match[1]!) && parser.encodeHtml) {
+ text = escapeHtml(match[1]!);
+ } else {
+ text = chars[1];
+ }
+
+ parser.addNode(Text(text));
+ return true;
+ }
+}
diff --git a/pkgs/markdown/lib/src/inline_syntaxes/footnote_ref_syntax.dart b/pkgs/markdown/lib/src/inline_syntaxes/footnote_ref_syntax.dart
new file mode 100644
index 0000000..155fd74
--- /dev/null
+++ b/pkgs/markdown/lib/src/inline_syntaxes/footnote_ref_syntax.dart
@@ -0,0 +1,72 @@
+import '../ast.dart' show Element, Node, Text;
+import '../charcode.dart';
+import 'link_syntax.dart' show LinkContext;
+
+/// The spec of GFM about footnotes is
+/// [missing](https://github.com/github/cmark-gfm/issues/283#issuecomment-1378868725).
+/// For source code of cmark-gfm, see the `noMatch` label of the
+/// `handle_close_bracket` function in [master@c32ef78](https://github.com/github/cmark-gfm/blob/c32ef78/src/inlines.c#L1236).
+/// A Rust implementation is also [available](https://github.com/wooorm/markdown-rs/blob/2498e31eecead798efc649502bbf5f86feaa94be/src/construct/gfm_label_start_footnote.rs).
+/// Footnotes shares the same syntax with [LinkSyntax],
+/// but have a different branch of handling the close bracket.
+class FootnoteRefSyntax {
+ static String? _footnoteLabel(String key) {
+ if (key.isEmpty || key.codeUnitAt(0) != $caret) {
+ return null;
+ }
+ key = key.substring(1).trim().toLowerCase();
+ if (key.isEmpty) {
+ return null;
+ }
+ return key;
+ }
+
+ static Iterable<Node>? tryCreateFootnoteLink(
+ LinkContext context,
+ String text, {
+ bool? secondary,
+ }) {
+ secondary ??= false;
+ final parser = context.parser;
+ final key = _footnoteLabel(text);
+ final refs = parser.document.footnoteReferences;
+ // `label` is what footnoteReferences stored, it is case sensitive.
+ final label =
+ refs.keys.firstWhere((k) => k.toLowerCase() == key, orElse: () => '');
+ // `count != null` means footnote was valid.
+ var count = refs[label];
+ // And then check if footnote was matched.
+ if (key == null || count == null) {
+ return null;
+ }
+ final result = <Node>[];
+ // There are 4 cases here: ![^...], [^...], ![...][^...], [...][^...]
+ if (context.opener.char == $exclamation) {
+ result.add(Text('!'));
+ }
+ refs[label] = ++count;
+ final labels = parser.document.footnoteLabels;
+ var pos = labels.indexOf(key);
+ if (pos < 0) {
+ pos = labels.length;
+ labels.add(key);
+ }
+
+ // `children` are text segments after '[^' before ']'.
+ final children = context.getChildren();
+ if (secondary) {
+ result.add(Text('['));
+ result.addAll(children);
+ result.add(Text(']'));
+ }
+ final id = Uri.encodeComponent(label);
+ final suffix = count > 1 ? '-$count' : '';
+ final link = Element('a', [Text('${pos + 1}')])
+ // Ignore GitHub's attribute: <data-footnote-ref>.
+ ..attributes['href'] = '#fn-$id'
+ ..attributes['id'] = 'fnref-$id$suffix';
+ final sup = Element('sup', [link])..attributes['class'] = 'footnote-ref';
+ result.add(sup);
+ return result;
+ }
+}
diff --git a/pkgs/markdown/lib/src/inline_syntaxes/image_syntax.dart b/pkgs/markdown/lib/src/inline_syntaxes/image_syntax.dart
new file mode 100644
index 0000000..a8b2c35
--- /dev/null
+++ b/pkgs/markdown/lib/src/inline_syntaxes/image_syntax.dart
@@ -0,0 +1,44 @@
+// 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 '../ast.dart';
+import '../charcode.dart';
+import '../util.dart';
+import 'link_syntax.dart';
+
+/// Matches images like `` and
+/// `![alternate text][label]`.
+class ImageSyntax extends LinkSyntax {
+ ImageSyntax({super.linkResolver})
+ : super(
+ pattern: r'!\[',
+ startCharacter: $exclamation,
+ );
+
+ @override
+ Element createNode(
+ String destination,
+ String? title, {
+ required List<Node> Function() getChildren,
+ }) {
+ final element = Element.empty('img');
+ final children = getChildren();
+ element.attributes['src'] = normalizeLinkDestination(
+ escapePunctuation(destination),
+ );
+ element.attributes['alt'] = children.map((node) {
+ // See https://spec.commonmark.org/0.30/#image-description.
+ // An image description may contain links. Fetch text from the alt
+ // attribute if this nested link is an image.
+ if (node is Element && node.tag == 'img') {
+ return node.attributes['alt'];
+ }
+ return node.textContent;
+ }).join();
+ if (title != null && title.isNotEmpty) {
+ element.attributes['title'] = normalizeLinkTitle(title);
+ }
+ return element;
+ }
+}
diff --git a/pkgs/markdown/lib/src/inline_syntaxes/inline_html_syntax.dart b/pkgs/markdown/lib/src/inline_syntaxes/inline_html_syntax.dart
new file mode 100644
index 0000000..3a7ce77
--- /dev/null
+++ b/pkgs/markdown/lib/src/inline_syntaxes/inline_html_syntax.dart
@@ -0,0 +1,44 @@
+// 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 '../../markdown.dart';
+import '../charcode.dart';
+import '../patterns.dart';
+
+/// Leave inline HTML tags alone, from
+/// [CommonMark 0.30](https://spec.commonmark.org/0.30/#raw-html).
+///
+/// This is not actually a good definition (nor CommonMark's) of an HTML tag,
+/// but it is fast. It will leave text like `<a href='hi">` alone, which is
+/// incorrect.
+///
+/// TODO(srawlins): improve accuracy while ensuring performance, once
+/// Markdown benchmarking is more mature.
+class InlineHtmlSyntax extends TextSyntax {
+ static const _pattern = '(?:$namedTagDefinition)'
+ // Or
+ '|'
+
+ // HTML comment, see
+ // https://spec.commonmark.org/0.30/#html-comment.
+ '<!--(?:(?:[^-<>]+-[^-<>]+)+|[^-<>]+)-->'
+ '|'
+
+ // Processing-instruction, see
+ // https://spec.commonmark.org/0.30/#processing-instruction
+ r'<\?.*?\?>'
+ '|'
+
+ // Declaration, see
+ // https://spec.commonmark.org/0.30/#declaration.
+ '(<![a-z]+.*?>)'
+ '|'
+
+ // CDATA section, see
+ // https://spec.commonmark.org/0.30/#cdata-section.
+ r'(<!\[CDATA\[.*?]]>)';
+
+ InlineHtmlSyntax()
+ : super(_pattern, startCharacter: $lt, caseSensitive: false);
+}
diff --git a/pkgs/markdown/lib/src/inline_syntaxes/inline_syntax.dart b/pkgs/markdown/lib/src/inline_syntaxes/inline_syntax.dart
new file mode 100644
index 0000000..997d03a
--- /dev/null
+++ b/pkgs/markdown/lib/src/inline_syntaxes/inline_syntax.dart
@@ -0,0 +1,58 @@
+// 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 '../inline_parser.dart';
+import '../util.dart';
+
+/// Represents one kind of Markdown tag that can be parsed.
+abstract class InlineSyntax {
+ final RegExp pattern;
+
+ /// The first character of [pattern], to be used as an efficient first check
+ /// that this syntax matches the current parser position.
+ final int? _startCharacter;
+
+ /// Create a new [InlineSyntax] which matches text on [pattern].
+ ///
+ /// If [startCharacter] is passed, it is used as a pre-matching check which
+ /// is faster than matching against [pattern].
+ ///
+ /// If [caseSensitive] is disabled, then case is ignored when matching
+ /// the [pattern].
+ InlineSyntax(String pattern, {int? startCharacter, bool caseSensitive = true})
+ : pattern =
+ RegExp(pattern, multiLine: true, caseSensitive: caseSensitive),
+ _startCharacter = startCharacter;
+
+ /// Tries to match at the parser's current position.
+ ///
+ /// The parser's position can be overriden with [startMatchPos].
+ /// Returns whether or not the pattern successfully matched.
+ bool tryMatch(InlineParser parser, [int? startMatchPos]) {
+ startMatchPos ??= parser.pos;
+
+ // Before matching with the regular expression [pattern], which can be
+ // expensive on some platforms, check if even the first character matches
+ // this syntax.
+ if (_startCharacter != null &&
+ parser.source.codeUnitAt(startMatchPos) != _startCharacter) {
+ return false;
+ }
+
+ final startMatch = pattern.matchAsPrefix(parser.source, startMatchPos);
+ if (startMatch == null) return false;
+
+ // Write any existing plain text up to this point.
+ parser.writeText();
+
+ if (onMatch(parser, startMatch)) parser.consume(startMatch.match.length);
+ return true;
+ }
+
+ /// Processes [match], adding nodes to [parser] and possibly advancing
+ /// [parser].
+ ///
+ /// Returns whether the caller should advance [parser] by `match[0].length`.
+ bool onMatch(InlineParser parser, Match match);
+}
diff --git a/pkgs/markdown/lib/src/inline_syntaxes/line_break_syntax.dart b/pkgs/markdown/lib/src/inline_syntaxes/line_break_syntax.dart
new file mode 100644
index 0000000..0a5fb01
--- /dev/null
+++ b/pkgs/markdown/lib/src/inline_syntaxes/line_break_syntax.dart
@@ -0,0 +1,19 @@
+// 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 '../ast.dart';
+import '../inline_parser.dart';
+import 'inline_syntax.dart';
+
+/// Represents a hard line break.
+class LineBreakSyntax extends InlineSyntax {
+ LineBreakSyntax() : super(r'(?:\\| +)\n');
+
+ /// Create a void <br> element.
+ @override
+ bool onMatch(InlineParser parser, Match match) {
+ parser.addNode(Element.empty('br'));
+ return true;
+ }
+}
diff --git a/pkgs/markdown/lib/src/inline_syntaxes/link_syntax.dart b/pkgs/markdown/lib/src/inline_syntaxes/link_syntax.dart
new file mode 100644
index 0000000..bca3efb
--- /dev/null
+++ b/pkgs/markdown/lib/src/inline_syntaxes/link_syntax.dart
@@ -0,0 +1,479 @@
+// 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 '../ast.dart';
+import '../charcode.dart';
+import '../document.dart';
+import '../inline_parser.dart';
+import '../util.dart';
+import 'delimiter_syntax.dart';
+import 'footnote_ref_syntax.dart';
+
+/// A helper class holds params of link context.
+/// Footnote creation needs other info in [_tryCreateReferenceLink].
+class LinkContext {
+ final InlineParser parser;
+ final SimpleDelimiter opener;
+ final List<Node> Function() getChildren;
+
+ const LinkContext(this.parser, this.opener, this.getChildren);
+}
+
+/// Matches links like `[blah][label]` and `[blah](url)`.
+class LinkSyntax extends DelimiterSyntax {
+ static final _entirelyWhitespacePattern = RegExp(r'^\s*$');
+
+ final Resolver linkResolver;
+
+ LinkSyntax({
+ Resolver? linkResolver,
+ String pattern = r'\[',
+ int startCharacter = $lbracket,
+ }) : linkResolver = (linkResolver ?? ((String _, [String? __]) => null)),
+ super(pattern, startCharacter: startCharacter);
+
+ @override
+ Iterable<Node>? close(
+ InlineParser parser,
+ covariant SimpleDelimiter opener,
+ Delimiter? closer, {
+ String? tag,
+ required List<Node> Function() getChildren,
+ }) {
+ final context = LinkContext(parser, opener, getChildren);
+ final text = parser.source.substring(opener.endPos, parser.pos);
+ // The current character is the `]` that closed the link text. Examine the
+ // next character, to determine what type of link we might have (a '('
+ // means a possible inline link; otherwise a possible reference link).
+ if (parser.pos + 1 >= parser.source.length) {
+ // The `]` is at the end of the document, but this may still be a valid
+ // shortcut reference link.
+ return _tryCreateReferenceLink(context, text);
+ }
+
+ // Peek at the next character; don't advance, so as to avoid later stepping
+ // backward.
+ final char = parser.charAt(parser.pos + 1);
+
+ if (char == $lparen) {
+ // Maybe an inline link, like `[text](destination)`.
+ parser.advanceBy(1);
+ final leftParenIndex = parser.pos;
+ final inlineLink = _parseInlineLink(parser);
+ if (inlineLink != null) {
+ return [
+ _tryCreateInlineLink(
+ parser,
+ inlineLink,
+ getChildren: getChildren,
+ ),
+ ];
+ }
+ // At this point, we've matched `[...](`, but that `(` did not pan out to
+ // be an inline link. We must now check if `[...]` is simply a shortcut
+ // reference link.
+
+ // Reset the parser position.
+ parser.pos = leftParenIndex;
+ parser.advanceBy(-1);
+ return _tryCreateReferenceLink(context, text);
+ }
+
+ if (char == $lbracket) {
+ parser.advanceBy(1);
+ // At this point, we've matched `[...][`. Maybe a *full* reference link,
+ // like `[foo][bar]` or a *collapsed* reference link, like `[foo][]`.
+ if (parser.pos + 1 < parser.source.length &&
+ parser.charAt(parser.pos + 1) == $rbracket) {
+ // That opening `[` is not actually part of the link. Maybe a
+ // *shortcut* reference link (followed by a `[`).
+ parser.advanceBy(1);
+ return _tryCreateReferenceLink(context, text);
+ }
+ final label = _parseReferenceLinkLabel(parser);
+ if (label != null) {
+ return _tryCreateReferenceLink(context, label, secondary: true);
+ }
+ return null;
+ }
+
+ // The link text (inside `[...]`) was not followed with a opening `(` nor
+ // an opening `[`. Perhaps just a simple shortcut reference link (`[...]`).
+ return _tryCreateReferenceLink(context, text);
+ }
+
+ /// Resolve a possible reference link.
+ ///
+ /// Uses [linkReferences], [linkResolver], and [createNode] to try to
+ /// resolve [label] into a [Node]. If [label] is defined in
+ /// [linkReferences] or can be resolved by [linkResolver], returns a [Node]
+ /// that links to the resolved URL.
+ ///
+ /// Otherwise, returns `null`.
+ ///
+ /// [label] does not need to be normalized.
+ Node? _resolveReferenceLink(
+ String label,
+ Map<String, LinkReference> linkReferences, {
+ required List<Node> Function() getChildren,
+ }) {
+ final linkReference = linkReferences[normalizeLinkLabel(label)];
+ if (linkReference != null) {
+ return createNode(
+ linkReference.destination,
+ linkReference.title,
+ getChildren: getChildren,
+ );
+ } else {
+ // This link has no reference definition. But we allow users of the
+ // library to specify a custom resolver function ([linkResolver]) that
+ // may choose to handle this. Otherwise, it's just treated as plain
+ // text.
+
+ // Normally, label text does not get parsed as inline Markdown. However,
+ // for the benefit of the link resolver, we need to at least escape
+ // brackets, so that, e.g. a link resolver can receive `[\[\]]` as `[]`.
+ final resolved = linkResolver(label
+ .replaceAll(r'\\', r'\')
+ .replaceAll(r'\[', '[')
+ .replaceAll(r'\]', ']'));
+ if (resolved != null) {
+ getChildren();
+ }
+ return resolved;
+ }
+ }
+
+ /// Create the node represented by a Markdown link.
+ Node createNode(
+ String destination,
+ String? title, {
+ required List<Node> Function() getChildren,
+ }) {
+ final children = getChildren();
+ final element = Element('a', children);
+ element.attributes['href'] = normalizeLinkDestination(
+ escapePunctuation(destination),
+ );
+ if (title != null && title.isNotEmpty) {
+ element.attributes['title'] = normalizeLinkTitle(
+ escapePunctuation(title),
+ );
+ }
+ return element;
+ }
+
+ /// Tries to create a reference link node.
+ ///
+ /// Returns the nodes if it was successfully created, `null` otherwise.
+ Iterable<Node>? _tryCreateReferenceLink(
+ LinkContext context,
+ String label, {
+ bool? secondary,
+ }) {
+ final parser = context.parser;
+ final getChildren = context.getChildren;
+ final link = _resolveReferenceLink(
+ label,
+ parser.document.linkReferences,
+ getChildren: getChildren,
+ );
+ if (link != null) {
+ return [link];
+ }
+ return FootnoteRefSyntax.tryCreateFootnoteLink(
+ context,
+ label,
+ secondary: secondary,
+ );
+ }
+
+ // Tries to create an inline link node.
+ //
+ /// Returns the link if it was successfully created, `null` otherwise.
+ Node _tryCreateInlineLink(
+ InlineParser parser,
+ InlineLink link, {
+ required List<Node> Function() getChildren,
+ }) {
+ return createNode(link.destination, link.title, getChildren: getChildren);
+ }
+
+ /// Parse a reference link label at the current position.
+ ///
+ /// Specifically, [parser.pos] is expected to be pointing at the `[` which
+ /// opens the link label.
+ ///
+ /// Returns the label if it could be parsed, or `null` if not.
+ String? _parseReferenceLinkLabel(InlineParser parser) {
+ // Walk past the opening `[`.
+ parser.advanceBy(1);
+ if (parser.isDone) return null;
+
+ final buffer = StringBuffer();
+ while (true) {
+ final char = parser.charAt(parser.pos);
+ if (char == $backslash) {
+ parser.advanceBy(1);
+ if (parser.isDone) return null;
+ final next = parser.charAt(parser.pos);
+ if (next != $backslash && next != $rbracket) {
+ buffer.writeCharCode(char);
+ }
+ buffer.writeCharCode(next);
+ } else if (char == $lbracket) {
+ return null;
+ } else if (char == $rbracket) {
+ break;
+ } else {
+ buffer.writeCharCode(char);
+ }
+ parser.advanceBy(1);
+ if (parser.isDone) return null;
+ // TODO(srawlins): only check 999 characters, for performance reasons?
+ }
+
+ final label = buffer.toString();
+
+ // A link label must contain at least one non-whitespace character.
+ if (_entirelyWhitespacePattern.hasMatch(label)) return null;
+
+ return label;
+ }
+
+ /// Parse an inline [InlineLink] at the current position.
+ ///
+ /// At this point, we have parsed a link's (or image's) opening `[`, and then
+ /// a matching closing `]`, and [parser.pos] is pointing at an opening `(`.
+ /// This method will then attempt to parse a link destination wrapped in `<>`,
+ /// such as `(<http://url>)`, or a bare link destination, such as
+ /// `(http://url)`, or a link destination with a title, such as
+ /// `(http://url "title")`.
+ ///
+ /// Returns the [InlineLink] if one was parsed, or `null` if not.
+ InlineLink? _parseInlineLink(InlineParser parser) {
+ // Start walking to the character just after the opening `(`.
+ parser.advanceBy(1);
+
+ _moveThroughWhitespace(parser);
+ if (parser.isDone) return null; // EOF. Not a link.
+
+ if (parser.charAt(parser.pos) == $lt) {
+ // Maybe a `<...>`-enclosed link destination.
+ return _parseInlineBracketedLink(parser);
+ } else {
+ return _parseInlineBareDestinationLink(parser);
+ }
+ }
+
+ /// Parse an inline link with a bracketed destination (a destination wrapped
+ /// in `<...>`). The current position of the parser must be the first
+ /// character of the destination.
+ ///
+ /// Returns the link if it was successfully created, `null` otherwise.
+ InlineLink? _parseInlineBracketedLink(InlineParser parser) {
+ parser.advanceBy(1);
+ if (parser.isDone) return null;
+
+ final buffer = StringBuffer();
+ while (true) {
+ final char = parser.charAt(parser.pos);
+ if (char == $backslash) {
+ parser.advanceBy(1);
+ if (parser.isDone) return null;
+ final next = parser.charAt(parser.pos);
+ // TODO: Follow the backslash spec better here.
+ // https://spec.commonmark.org/0.30/#backslash-escapes
+ if (next != $backslash && next != $gt) {
+ buffer.writeCharCode(char);
+ }
+ buffer.writeCharCode(next);
+ } else if (char == $lf || char == $cr || char == $ff) {
+ // Not a link (no line breaks allowed within `<...>`).
+ return null;
+ } else if (char == $space) {
+ buffer.write('%20');
+ } else if (char == $gt) {
+ break;
+ } else {
+ buffer.writeCharCode(char);
+ }
+ parser.advanceBy(1);
+ if (parser.isDone) return null;
+ }
+ final destination = buffer.toString();
+
+ parser.advanceBy(1);
+ if (parser.isDone) return null;
+ final char = parser.charAt(parser.pos);
+ if (char == $space || char == $lf || char == $cr || char == $ff) {
+ final title = _parseTitle(parser);
+ if (title == null &&
+ (parser.isDone || parser.charAt(parser.pos) != $rparen)) {
+ // This looked like an inline link, until we found this $space
+ // followed by mystery characters; no longer a link.
+ return null;
+ }
+ return InlineLink(destination, title: title);
+ } else if (char == $rparen) {
+ return InlineLink(destination);
+ } else {
+ // We parsed something like `[foo](<url>X`. Not a link.
+ return null;
+ }
+ }
+
+ /// Parse an inline link with a "bare" destination (a destination _not_
+ /// wrapped in `<...>`). The current position of the parser must be the first
+ /// character of the destination.
+ ///
+ /// Returns the link if it was successfully created, `null` otherwise.
+ InlineLink? _parseInlineBareDestinationLink(InlineParser parser) {
+ // According to
+ // [CommonMark](https://spec.commonmark.org/0.30/#link-destination):
+ //
+ // > A link destination consists of [...] a nonempty sequence of
+ // > characters [...], and includes parentheses only if (a) they are
+ // > backslash-escaped or (b) they are part of a balanced pair of
+ // > unescaped parentheses.
+ //
+ // We need to count the open parens. We start with 1 for the paren that
+ // opened the destination.
+ var parenCount = 1;
+ final buffer = StringBuffer();
+
+ while (true) {
+ final char = parser.charAt(parser.pos);
+ switch (char) {
+ case $backslash:
+ parser.advanceBy(1);
+ if (parser.isDone) return null; // EOF. Not a link.
+ final next = parser.charAt(parser.pos);
+ // Parentheses may be escaped.
+ //
+ // https://spec.commonmark.org/0.30/#example-494
+ if (next != $backslash && next != $lparen && next != $rparen) {
+ buffer.writeCharCode(char);
+ }
+ buffer.writeCharCode(next);
+ break;
+
+ case $space:
+ case $lf:
+ case $cr:
+ case $ff:
+ final destination = buffer.toString();
+ final title = _parseTitle(parser);
+ if (title == null &&
+ (parser.isDone || parser.charAt(parser.pos) != $rparen)) {
+ // This looked like an inline link, until we found this $space
+ // followed by mystery characters; no longer a link.
+ return null;
+ }
+ // [_parseTitle] made sure the title was follwed by a closing `)`
+ // (but it's up to the code here to examine the balance of
+ // parentheses).
+ parenCount--;
+ if (parenCount == 0) {
+ return InlineLink(destination, title: title);
+ }
+ break;
+
+ case $lparen:
+ parenCount++;
+ buffer.writeCharCode(char);
+ break;
+
+ case $rparen:
+ parenCount--;
+ if (parenCount == 0) {
+ final destination = buffer.toString();
+ return InlineLink(destination);
+ }
+ buffer.writeCharCode(char);
+ break;
+
+ default:
+ buffer.writeCharCode(char);
+ }
+ parser.advanceBy(1);
+ if (parser.isDone) return null; // EOF. Not a link.
+ }
+ }
+
+ // Walk the parser forward through any whitespace.
+ void _moveThroughWhitespace(InlineParser parser) {
+ while (!parser.isDone) {
+ final char = parser.charAt(parser.pos);
+ if (char != $space &&
+ char != $tab &&
+ char != $lf &&
+ char != $vt &&
+ char != $cr &&
+ char != $ff) {
+ return;
+ }
+ parser.advanceBy(1);
+ }
+ }
+
+ /// Parses a link title in [parser] at it's current position. The parser's
+ /// current position should be a whitespace character that followed a link
+ /// destination.
+ ///
+ /// Returns the title if it was successfully parsed, `null` otherwise.
+ String? _parseTitle(InlineParser parser) {
+ _moveThroughWhitespace(parser);
+ if (parser.isDone) return null;
+
+ // The whitespace should be followed by a title delimiter.
+ final delimiter = parser.charAt(parser.pos);
+ if (delimiter != $apostrophe &&
+ delimiter != $quote &&
+ delimiter != $lparen) {
+ return null;
+ }
+
+ final closeDelimiter = delimiter == $lparen ? $rparen : delimiter;
+ parser.advanceBy(1);
+ if (parser.isDone) return null;
+
+ // Now we look for an un-escaped closing delimiter.
+ final buffer = StringBuffer();
+ while (true) {
+ final char = parser.charAt(parser.pos);
+ if (char == $backslash) {
+ parser.advanceBy(1);
+ if (parser.isDone) return null;
+ final next = parser.charAt(parser.pos);
+ if (next != $backslash && next != closeDelimiter) {
+ buffer.writeCharCode(char);
+ }
+ buffer.writeCharCode(next);
+ } else if (char == closeDelimiter) {
+ break;
+ } else {
+ buffer.writeCharCode(char);
+ }
+ parser.advanceBy(1);
+ if (parser.isDone) return null;
+ }
+ final title = buffer.toString();
+
+ // Advance past the closing delimiter.
+ parser.advanceBy(1);
+ if (parser.isDone) return null;
+ _moveThroughWhitespace(parser);
+ if (parser.isDone) return null;
+ if (parser.charAt(parser.pos) != $rparen) return null;
+ return title;
+ }
+}
+
+class InlineLink {
+ final String destination;
+ final String? title;
+
+ InlineLink(this.destination, {this.title});
+}
diff --git a/pkgs/markdown/lib/src/inline_syntaxes/soft_line_break_syntax.dart b/pkgs/markdown/lib/src/inline_syntaxes/soft_line_break_syntax.dart
new file mode 100644
index 0000000..c8395d5
--- /dev/null
+++ b/pkgs/markdown/lib/src/inline_syntaxes/soft_line_break_syntax.dart
@@ -0,0 +1,21 @@
+// 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 '../charcode.dart';
+import '../inline_parser.dart';
+import 'inline_syntax.dart';
+
+/// Removes the single space before the line ending.
+// https://spec.commonmark.org/0.30/#soft-line-breaks.
+// If there are more than one spaces before the line ending, it may hit the hard
+// break syntax.
+class SoftLineBreakSyntax extends InlineSyntax {
+ SoftLineBreakSyntax() : super(' \n', startCharacter: $space);
+
+ @override
+ bool onMatch(InlineParser parser, Match match) {
+ parser.consume(1);
+ return false;
+ }
+}
diff --git a/pkgs/markdown/lib/src/inline_syntaxes/strikethrough_syntax.dart b/pkgs/markdown/lib/src/inline_syntaxes/strikethrough_syntax.dart
new file mode 100644
index 0000000..ebe5e1a
--- /dev/null
+++ b/pkgs/markdown/lib/src/inline_syntaxes/strikethrough_syntax.dart
@@ -0,0 +1,18 @@
+// 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 '../charcode.dart';
+import 'delimiter_syntax.dart';
+
+/// Matches strikethrough syntax according to the GFM spec.
+class StrikethroughSyntax extends DelimiterSyntax {
+ StrikethroughSyntax()
+ : super(
+ '~+',
+ requiresDelimiterRun: true,
+ allowIntraWord: true,
+ startCharacter: $tilde,
+ tags: [DelimiterTag('del', 1), DelimiterTag('del', 2)],
+ );
+}
diff --git a/pkgs/markdown/lib/src/inline_syntaxes/text_syntax.dart b/pkgs/markdown/lib/src/inline_syntaxes/text_syntax.dart
new file mode 100644
index 0000000..b73d09d
--- /dev/null
+++ b/pkgs/markdown/lib/src/inline_syntaxes/text_syntax.dart
@@ -0,0 +1,45 @@
+// 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 '../ast.dart';
+import '../inline_parser.dart';
+import '../util.dart';
+import 'inline_syntax.dart';
+
+/// Matches stuff that should just be passed through as straight text.
+class TextSyntax extends InlineSyntax {
+ final String substitute;
+
+ /// Create a new [TextSyntax] which matches text on [pattern].
+ ///
+ /// If [sub] is passed, it is used as a simple replacement for [pattern]. If
+ /// [startCharacter] is passed, it is used as a pre-matching check which is
+ /// faster than matching against [pattern].
+ TextSyntax(
+ super.pattern, {
+ String sub = '',
+ super.startCharacter,
+ super.caseSensitive,
+ }) : substitute = sub;
+
+ /// Adds a [Text] node to [parser] and returns `true` if there is a
+ /// [substitute], as long as the preceding character (if any) is not a `/`.
+ ///
+ /// Otherwise, the parser is advanced by the length of [match] and `false` is
+ /// returned.
+ @override
+ bool onMatch(InlineParser parser, Match match) {
+ if (substitute.isEmpty ||
+ (match.start > 0 &&
+ match.input.substring(match.start - 1, match.start) == '/')) {
+ // Just use the original matched text.
+ parser.advanceBy(match.match.length);
+ return false;
+ }
+
+ // Insert the substitution.
+ parser.addNode(Text(substitute));
+ return true;
+ }
+}
diff --git a/pkgs/markdown/lib/src/legacy_emojis.dart b/pkgs/markdown/lib/src/legacy_emojis.dart
new file mode 100644
index 0000000..d608841
--- /dev/null
+++ b/pkgs/markdown/lib/src/legacy_emojis.dart
@@ -0,0 +1,1578 @@
+// GENERATED FILE. DO NOT EDIT.
+//
+// This file was generated from emojilib's emoji data file:
+// https://raw.githubusercontent.com/muan/emojilib/v2.4.0/emojis.json
+// at 2022-05-02 10:18:54.685294 by the script, tool/update_emojis.dart.
+
+const emojis = <String, String>{
+ '+1': '👍',
+ '-1': '👎',
+ '100': '💯',
+ '1234': '🔢',
+ '1st_place_medal': '🥇',
+ '2nd_place_medal': '🥈',
+ '3rd_place_medal': '🥉',
+ '8ball': '🎱',
+ 'a': '🅰️',
+ 'ab': '🆎',
+ 'abacus': '🧮',
+ 'abc': '🔤',
+ 'abcd': '🔡',
+ 'accept': '🉑',
+ 'adult': '🧑',
+ 'aerial_tramway': '🚡',
+ 'afghanistan': '🇦🇫',
+ 'airplane': '✈️',
+ 'aland_islands': '🇦🇽',
+ 'alarm_clock': '⏰',
+ 'albania': '🇦🇱',
+ 'alembic': '⚗',
+ 'algeria': '🇩🇿',
+ 'alien': '👽',
+ 'ambulance': '🚑',
+ 'american_samoa': '🇦🇸',
+ 'amphora': '🏺',
+ 'anchor': '⚓',
+ 'andorra': '🇦🇩',
+ 'angel': '👼',
+ 'anger': '💢',
+ 'angola': '🇦🇴',
+ 'angry': '😠',
+ 'anguilla': '🇦🇮',
+ 'anguished': '😧',
+ 'ant': '🐜',
+ 'antarctica': '🇦🇶',
+ 'antigua_barbuda': '🇦🇬',
+ 'apple': '🍎',
+ 'aquarius': '♒',
+ 'argentina': '🇦🇷',
+ 'aries': '♈',
+ 'armenia': '🇦🇲',
+ 'arrow_backward': '◀️',
+ 'arrow_double_down': '⏬',
+ 'arrow_double_up': '⏫',
+ 'arrow_down': '⬇️',
+ 'arrow_down_small': '🔽',
+ 'arrow_forward': '▶️',
+ 'arrow_heading_down': '⤵️',
+ 'arrow_heading_up': '⤴️',
+ 'arrow_left': '⬅️',
+ 'arrow_lower_left': '↙️',
+ 'arrow_lower_right': '↘️',
+ 'arrow_right': '➡️',
+ 'arrow_right_hook': '↪️',
+ 'arrow_up': '⬆️',
+ 'arrow_up_down': '↕️',
+ 'arrow_up_small': '🔼',
+ 'arrow_upper_left': '↖️',
+ 'arrow_upper_right': '↗️',
+ 'arrows_clockwise': '🔃',
+ 'arrows_counterclockwise': '🔄',
+ 'art': '🎨',
+ 'articulated_lorry': '🚛',
+ 'artificial_satellite': '🛰',
+ 'aruba': '🇦🇼',
+ 'asterisk': '*⃣',
+ 'astonished': '😲',
+ 'athletic_shoe': '👟',
+ 'atm': '🏧',
+ 'atom_symbol': '⚛',
+ 'australia': '🇦🇺',
+ 'austria': '🇦🇹',
+ 'avocado': '🥑',
+ 'azerbaijan': '🇦🇿',
+ 'b': '🅱️',
+ 'baby': '👶',
+ 'baby_bottle': '🍼',
+ 'baby_chick': '🐤',
+ 'baby_symbol': '🚼',
+ 'back': '🔙',
+ 'bacon': '🥓',
+ 'badger': '🦡',
+ 'badminton': '🏸',
+ 'bagel': '🥯',
+ 'baggage_claim': '🛄',
+ 'baguette_bread': '🥖',
+ 'bahamas': '🇧🇸',
+ 'bahrain': '🇧🇭',
+ 'balance_scale': '⚖',
+ 'balloon': '🎈',
+ 'ballot_box': '🗳',
+ 'ballot_box_with_check': '☑️',
+ 'bamboo': '🎍',
+ 'banana': '🍌',
+ 'bangbang': '‼️',
+ 'bangladesh': '🇧🇩',
+ 'bank': '🏦',
+ 'bar_chart': '📊',
+ 'barbados': '🇧🇧',
+ 'barber': '💈',
+ 'baseball': '⚾',
+ 'basket': '🧺',
+ 'basketball': '🏀',
+ 'basketball_man': '⛹',
+ 'basketball_woman': '⛹️♀️',
+ 'bat': '🦇',
+ 'bath': '🛀',
+ 'bathtub': '🛁',
+ 'battery': '🔋',
+ 'beach_umbrella': '🏖',
+ 'bear': '🐻',
+ 'bearded_person': '🧔',
+ 'bed': '🛏',
+ 'beer': '🍺',
+ 'beers': '🍻',
+ 'beetle': '🐞',
+ 'beginner': '🔰',
+ 'belarus': '🇧🇾',
+ 'belgium': '🇧🇪',
+ 'belize': '🇧🇿',
+ 'bell': '🔔',
+ 'bellhop_bell': '🛎',
+ 'benin': '🇧🇯',
+ 'bento': '🍱',
+ 'bermuda': '🇧🇲',
+ 'bhutan': '🇧🇹',
+ 'bike': '🚲',
+ 'biking_man': '🚴',
+ 'biking_woman': '🚴♀️',
+ 'bikini': '👙',
+ 'billed_hat': '🧢',
+ 'biohazard': '☣',
+ 'bird': '🐦',
+ 'birthday': '🎂',
+ 'black_circle': '⚫',
+ 'black_flag': '🏴',
+ 'black_heart': '🖤',
+ 'black_joker': '🃏',
+ 'black_large_square': '⬛',
+ 'black_medium_small_square': '◾',
+ 'black_medium_square': '◼️',
+ 'black_nib': '✒️',
+ 'black_small_square': '▪️',
+ 'black_square_button': '🔲',
+ 'blonde_man': '👱',
+ 'blonde_woman': '👱♀️',
+ 'blossom': '🌼',
+ 'blowfish': '🐡',
+ 'blue_book': '📘',
+ 'blue_car': '🚙',
+ 'blue_heart': '💙',
+ 'blush': '😊',
+ 'boar': '🐗',
+ 'bolivia': '🇧🇴',
+ 'bomb': '💣',
+ 'bone': '🦴',
+ 'bookmark': '🔖',
+ 'bookmark_tabs': '📑',
+ 'books': '📚',
+ 'boom': '💥',
+ 'boot': '👢',
+ 'bosnia_herzegovina': '🇧🇦',
+ 'botswana': '🇧🇼',
+ 'bouquet': '💐',
+ 'bow_and_arrow': '🏹',
+ 'bowing_man': '🙇',
+ 'bowing_woman': '🙇♀️',
+ 'bowl_with_spoon': '🥣',
+ 'bowling': '🎳',
+ 'boxing_glove': '🥊',
+ 'boy': '👦',
+ 'brain': '🧠',
+ 'brazil': '🇧🇷',
+ 'bread': '🍞',
+ 'breastfeeding': '🤱',
+ 'brick': '🧱',
+ 'bride_with_veil': '👰',
+ 'bridge_at_night': '🌉',
+ 'briefcase': '💼',
+ 'british_indian_ocean_territory': '🇮🇴',
+ 'british_virgin_islands': '🇻🇬',
+ 'broccoli': '🥦',
+ 'broken_heart': '💔',
+ 'broom': '🧹',
+ 'brunei': '🇧🇳',
+ 'bug': '🐛',
+ 'building_construction': '🏗',
+ 'bulb': '💡',
+ 'bulgaria': '🇧🇬',
+ 'bullettrain_front': '🚅',
+ 'bullettrain_side': '🚄',
+ 'burkina_faso': '🇧🇫',
+ 'burrito': '🌯',
+ 'burundi': '🇧🇮',
+ 'bus': '🚌',
+ 'business_suit_levitating': '🕴',
+ 'busstop': '🚏',
+ 'bust_in_silhouette': '👤',
+ 'busts_in_silhouette': '👥',
+ 'butterfly': '🦋',
+ 'cactus': '🌵',
+ 'cake': '🍰',
+ 'calendar': '📆',
+ 'call_me_hand': '🤙',
+ 'calling': '📲',
+ 'cambodia': '🇰🇭',
+ 'camel': '🐫',
+ 'camera': '📷',
+ 'camera_flash': '📸',
+ 'cameroon': '🇨🇲',
+ 'camping': '🏕',
+ 'canada': '🇨🇦',
+ 'canary_islands': '🇮🇨',
+ 'cancer': '♋',
+ 'candle': '🕯',
+ 'candy': '🍬',
+ 'canned_food': '🥫',
+ 'canoe': '🛶',
+ 'cape_verde': '🇨🇻',
+ 'capital_abcd': '🔠',
+ 'capricorn': '♑',
+ 'card_file_box': '🗃',
+ 'card_index': '📇',
+ 'card_index_dividers': '🗂',
+ 'caribbean_netherlands': '🇧🇶',
+ 'carousel_horse': '🎠',
+ 'carrot': '🥕',
+ 'cat': '🐱',
+ 'cat2': '🐈',
+ 'cayman_islands': '🇰🇾',
+ 'cd': '💿',
+ 'central_african_republic': '🇨🇫',
+ 'chad': '🇹🇩',
+ 'chains': '⛓',
+ 'champagne': '🍾',
+ 'chart': '💹',
+ 'chart_with_downwards_trend': '📉',
+ 'chart_with_upwards_trend': '📈',
+ 'checkered_flag': '🏁',
+ 'cheese': '🧀',
+ 'cherries': '🍒',
+ 'cherry_blossom': '🌸',
+ 'chess_pawn': '♟',
+ 'chestnut': '🌰',
+ 'chicken': '🐔',
+ 'child': '🧒',
+ 'children_crossing': '🚸',
+ 'chile': '🇨🇱',
+ 'chipmunk': '🐿',
+ 'chocolate_bar': '🍫',
+ 'chopsticks': '🥢',
+ 'christmas_island': '🇨🇽',
+ 'christmas_tree': '🎄',
+ 'church': '⛪',
+ 'cinema': '🎦',
+ 'circus_tent': '🎪',
+ 'city_sunrise': '🌇',
+ 'city_sunset': '🌆',
+ 'cityscape': '🏙',
+ 'cl': '🆑',
+ 'clamp': '🗜',
+ 'clap': '👏',
+ 'clapper': '🎬',
+ 'classical_building': '🏛',
+ 'climbing_man': '🧗♂️',
+ 'climbing_woman': '🧗♀️',
+ 'clinking_glasses': '🥂',
+ 'clipboard': '📋',
+ 'clock1': '🕐',
+ 'clock10': '🕙',
+ 'clock1030': '🕥',
+ 'clock11': '🕚',
+ 'clock1130': '🕦',
+ 'clock12': '🕛',
+ 'clock1230': '🕧',
+ 'clock130': '🕜',
+ 'clock2': '🕑',
+ 'clock230': '🕝',
+ 'clock3': '🕒',
+ 'clock330': '🕞',
+ 'clock4': '🕓',
+ 'clock430': '🕟',
+ 'clock5': '🕔',
+ 'clock530': '🕠',
+ 'clock6': '🕕',
+ 'clock630': '🕡',
+ 'clock7': '🕖',
+ 'clock730': '🕢',
+ 'clock8': '🕗',
+ 'clock830': '🕣',
+ 'clock9': '🕘',
+ 'clock930': '🕤',
+ 'closed_book': '📕',
+ 'closed_lock_with_key': '🔐',
+ 'closed_umbrella': '🌂',
+ 'cloud': '☁️',
+ 'cloud_with_lightning': '🌩',
+ 'cloud_with_lightning_and_rain': '⛈',
+ 'cloud_with_rain': '🌧',
+ 'cloud_with_snow': '🌨',
+ 'clown_face': '🤡',
+ 'clubs': '♣️',
+ 'cn': '🇨🇳',
+ 'coat': '🧥',
+ 'cocktail': '🍸',
+ 'coconut': '🥥',
+ 'cocos_islands': '🇨🇨',
+ 'coffee': '☕',
+ 'coffin': '⚰',
+ 'cold': '🥶',
+ 'cold_sweat': '😰',
+ 'colombia': '🇨🇴',
+ 'comet': '☄',
+ 'comoros': '🇰🇲',
+ 'compass': '🧭',
+ 'computer': '💻',
+ 'computer_mouse': '🖱',
+ 'confetti_ball': '🎊',
+ 'confounded': '😖',
+ 'confused': '😕',
+ 'congo_brazzaville': '🇨🇬',
+ 'congo_kinshasa': '🇨🇩',
+ 'congratulations': '㊗️',
+ 'construction': '🚧',
+ 'construction_worker_man': '👷',
+ 'construction_worker_woman': '👷♀️',
+ 'control_knobs': '🎛',
+ 'convenience_store': '🏪',
+ 'cook_islands': '🇨🇰',
+ 'cookie': '🍪',
+ 'cool': '🆒',
+ 'copyright': '©️',
+ 'corn': '🌽',
+ 'costa_rica': '🇨🇷',
+ 'cote_divoire': '🇨🇮',
+ 'couch_and_lamp': '🛋',
+ 'couple': '👫',
+ 'couple_with_heart_man_man': '👨❤️👨',
+ 'couple_with_heart_woman_man': '💑',
+ 'couple_with_heart_woman_woman': '👩❤️👩',
+ 'couplekiss_man_man': '👨❤️💋👨',
+ 'couplekiss_man_woman': '💏',
+ 'couplekiss_woman_woman': '👩❤️💋👩',
+ 'cow': '🐮',
+ 'cow2': '🐄',
+ 'cowboy_hat_face': '🤠',
+ 'crab': '🦀',
+ 'crayon': '🖍',
+ 'credit_card': '💳',
+ 'crescent_moon': '🌙',
+ 'cricket': '🏏',
+ 'croatia': '🇭🇷',
+ 'crocodile': '🐊',
+ 'croissant': '🥐',
+ 'crossed_fingers': '🤞',
+ 'crossed_flags': '🎌',
+ 'crossed_swords': '⚔',
+ 'crown': '👑',
+ 'cry': '😢',
+ 'crying_cat_face': '😿',
+ 'crystal_ball': '🔮',
+ 'cuba': '🇨🇺',
+ 'cucumber': '🥒',
+ 'cup_with_straw': '🥤',
+ 'cupcake': '🧁',
+ 'cupid': '💘',
+ 'curacao': '🇨🇼',
+ 'curling_stone': '🥌',
+ 'curly_loop': '➰',
+ 'currency_exchange': '💱',
+ 'curry': '🍛',
+ 'custard': '🍮',
+ 'customs': '🛃',
+ 'cyclone': '🌀',
+ 'cyprus': '🇨🇾',
+ 'czech_republic': '🇨🇿',
+ 'dagger': '🗡',
+ 'dancer': '💃',
+ 'dancing_men': '👯♂️',
+ 'dancing_women': '👯',
+ 'dango': '🍡',
+ 'dark_sunglasses': '🕶',
+ 'dart': '🎯',
+ 'dash': '💨',
+ 'date': '📅',
+ 'de': '🇩🇪',
+ 'deciduous_tree': '🌳',
+ 'deer': '🦌',
+ 'denmark': '🇩🇰',
+ 'department_store': '🏬',
+ 'derelict_house': '🏚',
+ 'desert': '🏜',
+ 'desert_island': '🏝',
+ 'desktop_computer': '🖥',
+ 'diamond_shape_with_a_dot_inside': '💠',
+ 'diamonds': '♦️',
+ 'disappointed': '😞',
+ 'disappointed_relieved': '😥',
+ 'dizzy': '💫',
+ 'dizzy_face': '😵',
+ 'djibouti': '🇩🇯',
+ 'dna': '🧬',
+ 'do_not_litter': '🚯',
+ 'dog': '🐶',
+ 'dog2': '🐕',
+ 'dollar': '💵',
+ 'dolls': '🎎',
+ 'dolphin': '🐬',
+ 'dominica': '🇩🇲',
+ 'dominican_republic': '🇩🇴',
+ 'door': '🚪',
+ 'doughnut': '🍩',
+ 'dove': '🕊',
+ 'dragon': '🐉',
+ 'dragon_face': '🐲',
+ 'dress': '👗',
+ 'dromedary_camel': '🐪',
+ 'drooling_face': '🤤',
+ 'droplet': '💧',
+ 'drum': '🥁',
+ 'duck': '🦆',
+ 'dumpling': '🥟',
+ 'dvd': '📀',
+ 'e-mail': '📧',
+ 'eagle': '🦅',
+ 'ear': '👂',
+ 'ear_of_rice': '🌾',
+ 'earth_africa': '🌍',
+ 'earth_americas': '🌎',
+ 'earth_asia': '🌏',
+ 'ecuador': '🇪🇨',
+ 'egg': '🥚',
+ 'eggplant': '🍆',
+ 'egypt': '🇪🇬',
+ 'eight': '8️⃣',
+ 'eight_pointed_black_star': '✴️',
+ 'eight_spoked_asterisk': '✳️',
+ 'eject_button': '⏏️',
+ 'el_salvador': '🇸🇻',
+ 'electric_plug': '🔌',
+ 'elephant': '🐘',
+ 'email': '✉️',
+ 'end': '🔚',
+ 'england': '🏴',
+ 'envelope_with_arrow': '📩',
+ 'equatorial_guinea': '🇬🇶',
+ 'eritrea': '🇪🇷',
+ 'es': '🇪🇸',
+ 'estonia': '🇪🇪',
+ 'ethiopia': '🇪🇹',
+ 'eu': '🇪🇺',
+ 'euro': '💶',
+ 'european_castle': '🏰',
+ 'european_post_office': '🏤',
+ 'evergreen_tree': '🌲',
+ 'exclamation': '❗',
+ 'exploding_head': '🤯',
+ 'expressionless': '😑',
+ 'eye': '👁',
+ 'eyeglasses': '👓',
+ 'eyes': '👀',
+ 'face_with_head_bandage': '🤕',
+ 'face_with_thermometer': '🤒',
+ 'facepunch': '👊',
+ 'factory': '🏭',
+ 'falkland_islands': '🇫🇰',
+ 'fallen_leaf': '🍂',
+ 'family_man_boy': '👨👦',
+ 'family_man_boy_boy': '👨👦👦',
+ 'family_man_girl': '👨👧',
+ 'family_man_girl_boy': '👨👧👦',
+ 'family_man_girl_girl': '👨👧👧',
+ 'family_man_man_boy': '👨👨👦',
+ 'family_man_man_boy_boy': '👨👨👦👦',
+ 'family_man_man_girl': '👨👨👧',
+ 'family_man_man_girl_boy': '👨👨👧👦',
+ 'family_man_man_girl_girl': '👨👨👧👧',
+ 'family_man_woman_boy': '👪',
+ 'family_man_woman_boy_boy': '👨👩👦👦',
+ 'family_man_woman_girl': '👨👩👧',
+ 'family_man_woman_girl_boy': '👨👩👧👦',
+ 'family_man_woman_girl_girl': '👨👩👧👧',
+ 'family_woman_boy': '👩👦',
+ 'family_woman_boy_boy': '👩👦👦',
+ 'family_woman_girl': '👩👧',
+ 'family_woman_girl_boy': '👩👧👦',
+ 'family_woman_girl_girl': '👩👧👧',
+ 'family_woman_woman_boy': '👩👩👦',
+ 'family_woman_woman_boy_boy': '👩👩👦👦',
+ 'family_woman_woman_girl': '👩👩👧',
+ 'family_woman_woman_girl_boy': '👩👩👧👦',
+ 'family_woman_woman_girl_girl': '👩👩👧👧',
+ 'faroe_islands': '🇫🇴',
+ 'fast_forward': '⏩',
+ 'fax': '📠',
+ 'fearful': '😨',
+ 'female_detective': '🕵️♀️',
+ 'ferris_wheel': '🎡',
+ 'ferry': '⛴',
+ 'field_hockey': '🏑',
+ 'fiji': '🇫🇯',
+ 'file_cabinet': '🗄',
+ 'file_folder': '📁',
+ 'film_projector': '📽',
+ 'film_strip': '🎞',
+ 'finland': '🇫🇮',
+ 'fire': '🔥',
+ 'fire_engine': '🚒',
+ 'fire_extinguisher': '🧯',
+ 'firecracker': '🧨',
+ 'fireworks': '🎆',
+ 'first_quarter_moon': '🌓',
+ 'first_quarter_moon_with_face': '🌛',
+ 'fish': '🐟',
+ 'fish_cake': '🍥',
+ 'fishing_pole_and_fish': '🎣',
+ 'fist': '✊',
+ 'fist_left': '🤛',
+ 'fist_right': '🤜',
+ 'five': '5️⃣',
+ 'flags': '🎏',
+ 'flashlight': '🔦',
+ 'flat_shoe': '🥿',
+ 'fleur_de_lis': '⚜',
+ 'flight_arrival': '🛬',
+ 'flight_departure': '🛫',
+ 'floppy_disk': '💾',
+ 'flower_playing_cards': '🎴',
+ 'flushed': '😳',
+ 'flying_disc': '🥏',
+ 'flying_saucer': '🛸',
+ 'fog': '🌫',
+ 'foggy': '🌁',
+ 'foot': '🦶',
+ 'football': '🏈',
+ 'footprints': '👣',
+ 'fork_and_knife': '🍴',
+ 'fortune_cookie': '🥠',
+ 'fountain': '⛲',
+ 'fountain_pen': '🖋',
+ 'four': '4️⃣',
+ 'four_leaf_clover': '🍀',
+ 'fox_face': '🦊',
+ 'fr': '🇫🇷',
+ 'framed_picture': '🖼',
+ 'free': '🆓',
+ 'french_guiana': '🇬🇫',
+ 'french_polynesia': '🇵🇫',
+ 'french_southern_territories': '🇹🇫',
+ 'fried_egg': '🍳',
+ 'fried_shrimp': '🍤',
+ 'fries': '🍟',
+ 'frog': '🐸',
+ 'frowning': '😦',
+ 'frowning_face': '☹',
+ 'frowning_man': '🙍♂️',
+ 'frowning_woman': '🙍',
+ 'fu': '🖕',
+ 'fuelpump': '⛽',
+ 'full_moon': '🌕',
+ 'full_moon_with_face': '🌝',
+ 'funeral_urn': '⚱',
+ 'gabon': '🇬🇦',
+ 'gambia': '🇬🇲',
+ 'game_die': '🎲',
+ 'gear': '⚙',
+ 'gem': '💎',
+ 'gemini': '♊',
+ 'georgia': '🇬🇪',
+ 'ghana': '🇬🇭',
+ 'ghost': '👻',
+ 'gibraltar': '🇬🇮',
+ 'gift': '🎁',
+ 'gift_heart': '💝',
+ 'giraffe': '🦒',
+ 'girl': '👧',
+ 'globe_with_meridians': '🌐',
+ 'gloves': '🧤',
+ 'goal_net': '🥅',
+ 'goat': '🐐',
+ 'goggles': '🥽',
+ 'golf': '⛳',
+ 'golfing_man': '🏌',
+ 'golfing_woman': '🏌️♀️',
+ 'gorilla': '🦍',
+ 'grapes': '🍇',
+ 'grasshopper': '🦗',
+ 'greece': '🇬🇷',
+ 'green_apple': '🍏',
+ 'green_book': '📗',
+ 'green_heart': '💚',
+ 'green_salad': '🥗',
+ 'greenland': '🇬🇱',
+ 'grenada': '🇬🇩',
+ 'grey_exclamation': '❕',
+ 'grey_question': '❔',
+ 'grimacing': '😬',
+ 'grin': '😁',
+ 'grinning': '😀',
+ 'guadeloupe': '🇬🇵',
+ 'guam': '🇬🇺',
+ 'guardsman': '💂',
+ 'guardswoman': '💂♀️',
+ 'guatemala': '🇬🇹',
+ 'guernsey': '🇬🇬',
+ 'guinea': '🇬🇳',
+ 'guinea_bissau': '🇬🇼',
+ 'guitar': '🎸',
+ 'gun': '🔫',
+ 'guyana': '🇬🇾',
+ 'haircut_man': '💇♂️',
+ 'haircut_woman': '💇',
+ 'haiti': '🇭🇹',
+ 'hamburger': '🍔',
+ 'hammer': '🔨',
+ 'hammer_and_pick': '⚒',
+ 'hammer_and_wrench': '🛠',
+ 'hamster': '🐹',
+ 'hand_over_mouth': '🤭',
+ 'handbag': '👜',
+ 'handshake': '🤝',
+ 'hash': '#️⃣',
+ 'hatched_chick': '🐥',
+ 'hatching_chick': '🐣',
+ 'headphones': '🎧',
+ 'hear_no_evil': '🙉',
+ 'heart': '❤️',
+ 'heart_decoration': '💟',
+ 'heart_eyes': '😍',
+ 'heart_eyes_cat': '😻',
+ 'heartbeat': '💓',
+ 'heartpulse': '💗',
+ 'hearts': '♥️',
+ 'heavy_check_mark': '✔️',
+ 'heavy_division_sign': '➗',
+ 'heavy_dollar_sign': '💲',
+ 'heavy_heart_exclamation': '❣',
+ 'heavy_minus_sign': '➖',
+ 'heavy_multiplication_x': '✖️',
+ 'heavy_plus_sign': '➕',
+ 'hedgehog': '🦔',
+ 'helicopter': '🚁',
+ 'herb': '🌿',
+ 'hibiscus': '🌺',
+ 'high_brightness': '🔆',
+ 'high_heel': '👠',
+ 'hiking_boot': '🥾',
+ 'hippopotamus': '🦛',
+ 'hocho': '🔪',
+ 'hole': '🕳',
+ 'honduras': '🇭🇳',
+ 'honey_pot': '🍯',
+ 'honeybee': '🐝',
+ 'hong_kong': '🇭🇰',
+ 'horse': '🐴',
+ 'horse_racing': '🏇',
+ 'hospital': '🏥',
+ 'hot': '🥵',
+ 'hot_pepper': '🌶',
+ 'hotdog': '🌭',
+ 'hotel': '🏨',
+ 'hotsprings': '♨️',
+ 'hourglass': '⌛',
+ 'hourglass_flowing_sand': '⏳',
+ 'house': '🏠',
+ 'house_with_garden': '🏡',
+ 'houses': '🏘',
+ 'hugs': '🤗',
+ 'hungary': '🇭🇺',
+ 'hushed': '😯',
+ 'ice_cream': '🍨',
+ 'ice_hockey': '🏒',
+ 'ice_skate': '⛸',
+ 'icecream': '🍦',
+ 'iceland': '🇮🇸',
+ 'id': '🆔',
+ 'ideograph_advantage': '🉐',
+ 'imp': '👿',
+ 'inbox_tray': '📥',
+ 'incoming_envelope': '📨',
+ 'india': '🇮🇳',
+ 'indonesia': '🇮🇩',
+ 'infinity': '♾',
+ 'information_source': 'ℹ️',
+ 'innocent': '😇',
+ 'interrobang': '⁉️',
+ 'iphone': '📱',
+ 'iran': '🇮🇷',
+ 'iraq': '🇮🇶',
+ 'ireland': '🇮🇪',
+ 'isle_of_man': '🇮🇲',
+ 'israel': '🇮🇱',
+ 'it': '🇮🇹',
+ 'izakaya_lantern': '🏮',
+ 'jack_o_lantern': '🎃',
+ 'jamaica': '🇯🇲',
+ 'japan': '🗾',
+ 'japanese_castle': '🏯',
+ 'japanese_goblin': '👺',
+ 'japanese_ogre': '👹',
+ 'jeans': '👖',
+ 'jersey': '🇯🇪',
+ 'jigsaw': '🧩',
+ 'jordan': '🇯🇴',
+ 'joy': '😂',
+ 'joy_cat': '😹',
+ 'joystick': '🕹',
+ 'jp': '🇯🇵',
+ 'kaaba': '🕋',
+ 'kangaroo': '🦘',
+ 'kazakhstan': '🇰🇿',
+ 'kenya': '🇰🇪',
+ 'key': '🔑',
+ 'keyboard': '⌨',
+ 'keycap_ten': '🔟',
+ 'kick_scooter': '🛴',
+ 'kimono': '👘',
+ 'kiribati': '🇰🇮',
+ 'kiss': '💋',
+ 'kissing': '😗',
+ 'kissing_cat': '😽',
+ 'kissing_closed_eyes': '😚',
+ 'kissing_heart': '😘',
+ 'kissing_smiling_eyes': '😙',
+ 'kiwi_fruit': '🥝',
+ 'koala': '🐨',
+ 'koko': '🈁',
+ 'kosovo': '🇽🇰',
+ 'kr': '🇰🇷',
+ 'kuwait': '🇰🇼',
+ 'kyrgyzstan': '🇰🇬',
+ 'labcoat': '🥼',
+ 'label': '🏷',
+ 'lacrosse': '🥍',
+ 'laos': '🇱🇦',
+ 'large_blue_circle': '🔵',
+ 'large_blue_diamond': '🔷',
+ 'large_orange_diamond': '🔶',
+ 'last_quarter_moon': '🌗',
+ 'last_quarter_moon_with_face': '🌜',
+ 'latin_cross': '✝',
+ 'latvia': '🇱🇻',
+ 'laughing': '😆',
+ 'leafy_greens': '🥬',
+ 'leaves': '🍃',
+ 'lebanon': '🇱🇧',
+ 'ledger': '📒',
+ 'left_luggage': '🛅',
+ 'left_right_arrow': '↔️',
+ 'left_speech_bubble': '🗨',
+ 'leftwards_arrow_with_hook': '↩️',
+ 'leg': '🦵',
+ 'lemon': '🍋',
+ 'leo': '♌',
+ 'leopard': '🐆',
+ 'lesotho': '🇱🇸',
+ 'level_slider': '🎚',
+ 'liberia': '🇱🇷',
+ 'libra': '♎',
+ 'libya': '🇱🇾',
+ 'liechtenstein': '🇱🇮',
+ 'light_rail': '🚈',
+ 'link': '🔗',
+ 'lion': '🦁',
+ 'lips': '👄',
+ 'lipstick': '💄',
+ 'lithuania': '🇱🇹',
+ 'lizard': '🦎',
+ 'llama': '🦙',
+ 'lobster': '🦞',
+ 'lock': '🔒',
+ 'lock_with_ink_pen': '🔏',
+ 'lollipop': '🍭',
+ 'loop': '➿',
+ 'lotion_bottle': '🧴',
+ 'loud_sound': '🔊',
+ 'loudspeaker': '📢',
+ 'love_hotel': '🏩',
+ 'love_letter': '💌',
+ 'love_you': '🤟',
+ 'low_brightness': '🔅',
+ 'luggage': '🧳',
+ 'luxembourg': '🇱🇺',
+ 'lying_face': '🤥',
+ 'm': 'Ⓜ️',
+ 'macau': '🇲🇴',
+ 'macedonia': '🇲🇰',
+ 'madagascar': '🇲🇬',
+ 'mag': '🔍',
+ 'mag_right': '🔎',
+ 'magnet': '🧲',
+ 'mahjong': '🀄',
+ 'mailbox': '📫',
+ 'mailbox_closed': '📪',
+ 'mailbox_with_mail': '📬',
+ 'mailbox_with_no_mail': '📭',
+ 'malawi': '🇲🇼',
+ 'malaysia': '🇲🇾',
+ 'maldives': '🇲🇻',
+ 'male_detective': '🕵',
+ 'mali': '🇲🇱',
+ 'malta': '🇲🇹',
+ 'man': '👨',
+ 'man_artist': '👨🎨',
+ 'man_astronaut': '👨🚀',
+ 'man_cartwheeling': '🤸♂️',
+ 'man_cook': '👨🍳',
+ 'man_dancing': '🕺',
+ 'man_elf': '🧝♂️',
+ 'man_facepalming': '🤦♂️',
+ 'man_factory_worker': '👨🏭',
+ 'man_fairy': '🧚♂️',
+ 'man_farmer': '👨🌾',
+ 'man_firefighter': '👨🚒',
+ 'man_genie': '🧞♂️',
+ 'man_health_worker': '👨⚕️',
+ 'man_in_lotus_position': '🧘♂️',
+ 'man_in_steamy_room': '🧖♂️',
+ 'man_in_tuxedo': '🤵',
+ 'man_judge': '👨⚖️',
+ 'man_juggling': '🤹♂️',
+ 'man_mechanic': '👨🔧',
+ 'man_office_worker': '👨💼',
+ 'man_pilot': '👨✈️',
+ 'man_playing_handball': '🤾♂️',
+ 'man_playing_water_polo': '🤽♂️',
+ 'man_scientist': '👨🔬',
+ 'man_shrugging': '🤷♂️',
+ 'man_singer': '👨🎤',
+ 'man_student': '👨🎓',
+ 'man_superhero': '🦸♂️',
+ 'man_supervillain': '🦹♂️',
+ 'man_teacher': '👨🏫',
+ 'man_technologist': '👨💻',
+ 'man_vampire': '🧛♂️',
+ 'man_with_gua_pi_mao': '👲',
+ 'man_with_turban': '👳',
+ 'man_zombie': '🧟♂️',
+ 'mango': '🥭',
+ 'mans_shoe': '👞',
+ 'mantelpiece_clock': '🕰',
+ 'maple_leaf': '🍁',
+ 'marshall_islands': '🇲🇭',
+ 'martial_arts_uniform': '🥋',
+ 'martinique': '🇲🇶',
+ 'mask': '😷',
+ 'massage_man': '💆♂️',
+ 'massage_woman': '💆',
+ 'mauritania': '🇲🇷',
+ 'mauritius': '🇲🇺',
+ 'mayotte': '🇾🇹',
+ 'meat_on_bone': '🍖',
+ 'medal_military': '🎖',
+ 'medal_sports': '🏅',
+ 'mega': '📣',
+ 'melon': '🍈',
+ 'memo': '📝',
+ 'men_wrestling': '🤼♂️',
+ 'menorah': '🕎',
+ 'mens': '🚹',
+ 'mermaid': '🧜♀️',
+ 'merman': '🧜♂️',
+ 'metal': '🤘',
+ 'metro': '🚇',
+ 'mexico': '🇲🇽',
+ 'microbe': '🦠',
+ 'micronesia': '🇫🇲',
+ 'microphone': '🎤',
+ 'microscope': '🔬',
+ 'milk_glass': '🥛',
+ 'milky_way': '🌌',
+ 'minibus': '🚐',
+ 'minidisc': '💽',
+ 'mobile_phone_off': '📴',
+ 'moldova': '🇲🇩',
+ 'monaco': '🇲🇨',
+ 'money_mouth_face': '🤑',
+ 'money_with_wings': '💸',
+ 'moneybag': '💰',
+ 'mongolia': '🇲🇳',
+ 'monkey': '🐒',
+ 'monkey_face': '🐵',
+ 'monocle': '🧐',
+ 'monorail': '🚝',
+ 'montenegro': '🇲🇪',
+ 'montserrat': '🇲🇸',
+ 'moon_cake': '🥮',
+ 'morocco': '🇲🇦',
+ 'mortar_board': '🎓',
+ 'mosque': '🕌',
+ 'mosquito': '🦟',
+ 'motor_boat': '🛥',
+ 'motor_scooter': '🛵',
+ 'motorcycle': '🏍',
+ 'motorway': '🛣',
+ 'mount_fuji': '🗻',
+ 'mountain': '⛰',
+ 'mountain_biking_man': '🚵',
+ 'mountain_biking_woman': '🚵♀️',
+ 'mountain_cableway': '🚠',
+ 'mountain_railway': '🚞',
+ 'mountain_snow': '🏔',
+ 'mouse': '🐭',
+ 'mouse2': '🐁',
+ 'movie_camera': '🎥',
+ 'moyai': '🗿',
+ 'mozambique': '🇲🇿',
+ 'mrs_claus': '🤶',
+ 'muscle': '💪',
+ 'mushroom': '🍄',
+ 'musical_keyboard': '🎹',
+ 'musical_note': '🎵',
+ 'musical_score': '🎼',
+ 'mute': '🔇',
+ 'myanmar': '🇲🇲',
+ 'nail_care': '💅',
+ 'name_badge': '📛',
+ 'namibia': '🇳🇦',
+ 'national_park': '🏞',
+ 'nauru': '🇳🇷',
+ 'nauseated_face': '🤢',
+ 'nazar_amulet': '🧿',
+ 'necktie': '👔',
+ 'negative_squared_cross_mark': '❎',
+ 'nepal': '🇳🇵',
+ 'nerd_face': '🤓',
+ 'netherlands': '🇳🇱',
+ 'neutral_face': '😐',
+ 'new': '🆕',
+ 'new_caledonia': '🇳🇨',
+ 'new_moon': '🌑',
+ 'new_moon_with_face': '🌚',
+ 'new_zealand': '🇳🇿',
+ 'newspaper': '📰',
+ 'newspaper_roll': '🗞',
+ 'next_track_button': '⏭',
+ 'ng': '🆖',
+ 'nicaragua': '🇳🇮',
+ 'niger': '🇳🇪',
+ 'nigeria': '🇳🇬',
+ 'night_with_stars': '🌃',
+ 'nine': '9️⃣',
+ 'niue': '🇳🇺',
+ 'no_bell': '🔕',
+ 'no_bicycles': '🚳',
+ 'no_entry': '⛔',
+ 'no_entry_sign': '🚫',
+ 'no_good_man': '🙅♂️',
+ 'no_good_woman': '🙅',
+ 'no_mobile_phones': '📵',
+ 'no_mouth': '😶',
+ 'no_pedestrians': '🚷',
+ 'no_smoking': '🚭',
+ 'non-potable_water': '🚱',
+ 'norfolk_island': '🇳🇫',
+ 'north_korea': '🇰🇵',
+ 'northern_mariana_islands': '🇲🇵',
+ 'norway': '🇳🇴',
+ 'nose': '👃',
+ 'notebook': '📓',
+ 'notebook_with_decorative_cover': '📔',
+ 'notes': '🎶',
+ 'nut_and_bolt': '🔩',
+ 'o': '⭕',
+ 'o2': '🅾️',
+ 'ocean': '🌊',
+ 'octopus': '🐙',
+ 'oden': '🍢',
+ 'office': '🏢',
+ 'oil_drum': '🛢',
+ 'ok': '🆗',
+ 'ok_hand': '👌',
+ 'ok_man': '🙆♂️',
+ 'ok_woman': '🙆',
+ 'old_key': '🗝',
+ 'older_adult': '🧓',
+ 'older_man': '👴',
+ 'older_woman': '👵',
+ 'om': '🕉',
+ 'oman': '🇴🇲',
+ 'on': '🔛',
+ 'oncoming_automobile': '🚘',
+ 'oncoming_bus': '🚍',
+ 'oncoming_police_car': '🚔',
+ 'oncoming_taxi': '🚖',
+ 'one': '1️⃣',
+ 'open_book': '📖',
+ 'open_file_folder': '📂',
+ 'open_hands': '👐',
+ 'open_mouth': '😮',
+ 'open_umbrella': '☂',
+ 'ophiuchus': '⛎',
+ 'orange_book': '📙',
+ 'orange_heart': '🧡',
+ 'orthodox_cross': '☦',
+ 'outbox_tray': '📤',
+ 'owl': '🦉',
+ 'ox': '🐂',
+ 'package': '📦',
+ 'page_facing_up': '📄',
+ 'page_with_curl': '📃',
+ 'pager': '📟',
+ 'paintbrush': '🖌',
+ 'pakistan': '🇵🇰',
+ 'palau': '🇵🇼',
+ 'palestinian_territories': '🇵🇸',
+ 'palm_tree': '🌴',
+ 'palms_up': '🤲',
+ 'panama': '🇵🇦',
+ 'pancakes': '🥞',
+ 'panda_face': '🐼',
+ 'paperclip': '📎',
+ 'paperclips': '🖇',
+ 'papua_new_guinea': '🇵🇬',
+ 'paraguay': '🇵🇾',
+ 'parasol_on_ground': '⛱',
+ 'parking': '🅿️',
+ 'parrot': '🦜',
+ 'part_alternation_mark': '〽️',
+ 'partly_sunny': '⛅',
+ 'partying': '🥳',
+ 'passenger_ship': '🛳',
+ 'passport_control': '🛂',
+ 'pause_button': '⏸',
+ 'paw_prints': '🐾',
+ 'peace_symbol': '☮',
+ 'peach': '🍑',
+ 'peacock': '🦚',
+ 'peanuts': '🥜',
+ 'pear': '🍐',
+ 'pen': '🖊',
+ 'pencil2': '✏️',
+ 'penguin': '🐧',
+ 'pensive': '😔',
+ 'performing_arts': '🎭',
+ 'persevere': '😣',
+ 'person_fencing': '🤺',
+ 'peru': '🇵🇪',
+ 'petri_dish': '🧫',
+ 'philippines': '🇵🇭',
+ 'phone': '☎️',
+ 'pick': '⛏',
+ 'pie': '🥧',
+ 'pig': '🐷',
+ 'pig2': '🐖',
+ 'pig_nose': '🐽',
+ 'pill': '💊',
+ 'pineapple': '🍍',
+ 'ping_pong': '🏓',
+ 'pirate_flag': '🏴☠️',
+ 'pisces': '♓',
+ 'pitcairn_islands': '🇵🇳',
+ 'pizza': '🍕',
+ 'place_of_worship': '🛐',
+ 'plate_with_cutlery': '🍽',
+ 'play_or_pause_button': '⏯',
+ 'pleading': '🥺',
+ 'point_down': '👇',
+ 'point_left': '👈',
+ 'point_right': '👉',
+ 'point_up': '☝',
+ 'point_up_2': '👆',
+ 'poland': '🇵🇱',
+ 'police_car': '🚓',
+ 'policeman': '👮',
+ 'policewoman': '👮♀️',
+ 'poodle': '🐩',
+ 'poop': '💩',
+ 'popcorn': '🍿',
+ 'portugal': '🇵🇹',
+ 'post_office': '🏣',
+ 'postal_horn': '📯',
+ 'postbox': '📮',
+ 'potable_water': '🚰',
+ 'potato': '🥔',
+ 'pouch': '👝',
+ 'poultry_leg': '🍗',
+ 'pound': '💷',
+ 'pouting_cat': '😾',
+ 'pouting_man': '🙎♂️',
+ 'pouting_woman': '🙎',
+ 'pray': '🙏',
+ 'prayer_beads': '📿',
+ 'pregnant_woman': '🤰',
+ 'pretzel': '🥨',
+ 'previous_track_button': '⏮',
+ 'prince': '🤴',
+ 'princess': '👸',
+ 'printer': '🖨',
+ 'puerto_rico': '🇵🇷',
+ 'purple_heart': '💜',
+ 'purse': '👛',
+ 'pushpin': '📌',
+ 'put_litter_in_its_place': '🚮',
+ 'qatar': '🇶🇦',
+ 'question': '❓',
+ 'rabbit': '🐰',
+ 'rabbit2': '🐇',
+ 'raccoon': '🦝',
+ 'racehorse': '🐎',
+ 'racing_car': '🏎',
+ 'radio': '📻',
+ 'radio_button': '🔘',
+ 'radioactive': '☢',
+ 'rage': '😡',
+ 'railway_car': '🚃',
+ 'railway_track': '🛤',
+ 'rainbow': '🌈',
+ 'rainbow_flag': '🏳️🌈',
+ 'raised_back_of_hand': '🤚',
+ 'raised_eyebrow': '🤨',
+ 'raised_hand': '✋',
+ 'raised_hand_with_fingers_splayed': '🖐',
+ 'raised_hands': '🙌',
+ 'raising_hand_man': '🙋♂️',
+ 'raising_hand_woman': '🙋',
+ 'ram': '🐏',
+ 'ramen': '🍜',
+ 'rat': '🐀',
+ 'receipt': '🧾',
+ 'record_button': '⏺',
+ 'recycle': '♻️',
+ 'red_car': '🚗',
+ 'red_circle': '🔴',
+ 'red_envelope': '🧧',
+ 'registered': '®️',
+ 'relaxed': '☺️',
+ 'relieved': '😌',
+ 'reminder_ribbon': '🎗',
+ 'repeat': '🔁',
+ 'repeat_one': '🔂',
+ 'rescue_worker_helmet': '⛑',
+ 'restroom': '🚻',
+ 'reunion': '🇷🇪',
+ 'revolving_hearts': '💞',
+ 'rewind': '⏪',
+ 'rhinoceros': '🦏',
+ 'ribbon': '🎀',
+ 'rice': '🍚',
+ 'rice_ball': '🍙',
+ 'rice_cracker': '🍘',
+ 'rice_scene': '🎑',
+ 'right_anger_bubble': '🗯',
+ 'ring': '💍',
+ 'robot': '🤖',
+ 'rocket': '🚀',
+ 'rofl': '🤣',
+ 'roll_eyes': '🙄',
+ 'roller_coaster': '🎢',
+ 'romania': '🇷🇴',
+ 'rooster': '🐓',
+ 'rose': '🌹',
+ 'rosette': '🏵',
+ 'rotating_light': '🚨',
+ 'round_pushpin': '📍',
+ 'rowing_man': '🚣',
+ 'rowing_woman': '🚣♀️',
+ 'ru': '🇷🇺',
+ 'rugby_football': '🏉',
+ 'running_man': '🏃',
+ 'running_shirt_with_sash': '🎽',
+ 'running_woman': '🏃♀️',
+ 'rwanda': '🇷🇼',
+ 'sa': '🈂️',
+ 'safety_pin': '🧷',
+ 'sagittarius': '♐',
+ 'sailboat': '⛵',
+ 'sake': '🍶',
+ 'salt': '🧂',
+ 'samoa': '🇼🇸',
+ 'san_marino': '🇸🇲',
+ 'sandal': '👡',
+ 'sandwich': '🥪',
+ 'santa': '🎅',
+ 'sao_tome_principe': '🇸🇹',
+ 'satellite': '📡',
+ 'saudi_arabia': '🇸🇦',
+ 'sauropod': '🦕',
+ 'saxophone': '🎷',
+ 'scarf': '🧣',
+ 'school': '🏫',
+ 'school_satchel': '🎒',
+ 'scissors': '✂️',
+ 'scorpion': '🦂',
+ 'scorpius': '♏',
+ 'scotland': '🏴',
+ 'scream': '😱',
+ 'scream_cat': '🙀',
+ 'scroll': '📜',
+ 'seat': '💺',
+ 'secret': '㊙️',
+ 'see_no_evil': '🙈',
+ 'seedling': '🌱',
+ 'selfie': '🤳',
+ 'senegal': '🇸🇳',
+ 'serbia': '🇷🇸',
+ 'seven': '7️⃣',
+ 'seychelles': '🇸🇨',
+ 'shallow_pan_of_food': '🥘',
+ 'shamrock': '☘',
+ 'shark': '🦈',
+ 'shaved_ice': '🍧',
+ 'sheep': '🐑',
+ 'shell': '🐚',
+ 'shield': '🛡',
+ 'shinto_shrine': '⛩',
+ 'ship': '🚢',
+ 'shopping': '🛍',
+ 'shopping_cart': '🛒',
+ 'shower': '🚿',
+ 'shrimp': '🦐',
+ 'shushing': '🤫',
+ 'sierra_leone': '🇸🇱',
+ 'signal_strength': '📶',
+ 'singapore': '🇸🇬',
+ 'sint_maarten': '🇸🇽',
+ 'six': '6️⃣',
+ 'six_pointed_star': '🔯',
+ 'skateboard': '🛹',
+ 'ski': '🎿',
+ 'skier': '⛷',
+ 'skull': '💀',
+ 'skull_and_crossbones': '☠',
+ 'sled': '🛷',
+ 'sleeping': '😴',
+ 'sleeping_bed': '🛌',
+ 'sleepy': '😪',
+ 'slightly_frowning_face': '🙁',
+ 'slightly_smiling_face': '🙂',
+ 'slot_machine': '🎰',
+ 'slovakia': '🇸🇰',
+ 'slovenia': '🇸🇮',
+ 'small_airplane': '🛩',
+ 'small_blue_diamond': '🔹',
+ 'small_orange_diamond': '🔸',
+ 'small_red_triangle': '🔺',
+ 'small_red_triangle_down': '🔻',
+ 'smile': '😄',
+ 'smile_cat': '😸',
+ 'smiley': '😃',
+ 'smiley_cat': '😺',
+ 'smiling_face_with_three_hearts': '🥰',
+ 'smiling_imp': '😈',
+ 'smirk': '😏',
+ 'smirk_cat': '😼',
+ 'smoking': '🚬',
+ 'snail': '🐌',
+ 'snake': '🐍',
+ 'sneezing_face': '🤧',
+ 'snowboarder': '🏂',
+ 'snowflake': '❄️',
+ 'snowman': '⛄',
+ 'snowman_with_snow': '☃',
+ 'soap': '🧼',
+ 'sob': '😭',
+ 'soccer': '⚽',
+ 'socks': '🧦',
+ 'softball': '🥎',
+ 'solomon_islands': '🇸🇧',
+ 'somalia': '🇸🇴',
+ 'soon': '🔜',
+ 'sorceress': '🧙♀️',
+ 'sos': '🆘',
+ 'sound': '🔉',
+ 'south_africa': '🇿🇦',
+ 'south_georgia_south_sandwich_islands': '🇬🇸',
+ 'south_sudan': '🇸🇸',
+ 'space_invader': '👾',
+ 'spades': '♠️',
+ 'spaghetti': '🍝',
+ 'sparkle': '❇️',
+ 'sparkler': '🎇',
+ 'sparkles': '✨',
+ 'sparkling_heart': '💖',
+ 'speak_no_evil': '🙊',
+ 'speaker': '🔈',
+ 'speaking_head': '🗣',
+ 'speech_balloon': '💬',
+ 'speedboat': '🚤',
+ 'spider': '🕷',
+ 'spider_web': '🕸',
+ 'spiral_calendar': '🗓',
+ 'spiral_notepad': '🗒',
+ 'sponge': '🧽',
+ 'spoon': '🥄',
+ 'squid': '🦑',
+ 'sri_lanka': '🇱🇰',
+ 'st_barthelemy': '🇧🇱',
+ 'st_helena': '🇸🇭',
+ 'st_kitts_nevis': '🇰🇳',
+ 'st_lucia': '🇱🇨',
+ 'st_pierre_miquelon': '🇵🇲',
+ 'st_vincent_grenadines': '🇻🇨',
+ 'stadium': '🏟',
+ 'star': '⭐',
+ 'star2': '🌟',
+ 'star_and_crescent': '☪',
+ 'star_of_david': '✡',
+ 'star_struck': '🤩',
+ 'stars': '🌠',
+ 'station': '🚉',
+ 'statue_of_liberty': '🗽',
+ 'steak': '🥩',
+ 'steam_locomotive': '🚂',
+ 'stew': '🍲',
+ 'stop_button': '⏹',
+ 'stop_sign': '🛑',
+ 'stopwatch': '⏱',
+ 'straight_ruler': '📏',
+ 'strawberry': '🍓',
+ 'stuck_out_tongue': '😛',
+ 'stuck_out_tongue_closed_eyes': '😝',
+ 'stuck_out_tongue_winking_eye': '😜',
+ 'studio_microphone': '🎙',
+ 'stuffed_flatbread': '🥙',
+ 'sudan': '🇸🇩',
+ 'sun_behind_large_cloud': '🌥',
+ 'sun_behind_rain_cloud': '🌦',
+ 'sun_behind_small_cloud': '🌤',
+ 'sun_with_face': '🌞',
+ 'sunflower': '🌻',
+ 'sunglasses': '😎',
+ 'sunny': '☀️',
+ 'sunrise': '🌅',
+ 'sunrise_over_mountains': '🌄',
+ 'surfing_man': '🏄',
+ 'surfing_woman': '🏄♀️',
+ 'suriname': '🇸🇷',
+ 'sushi': '🍣',
+ 'suspension_railway': '🚟',
+ 'swan': '🦢',
+ 'swaziland': '🇸🇿',
+ 'sweat': '😓',
+ 'sweat_drops': '💦',
+ 'sweat_smile': '😅',
+ 'sweden': '🇸🇪',
+ 'sweet_potato': '🍠',
+ 'swimming_man': '🏊',
+ 'swimming_woman': '🏊♀️',
+ 'switzerland': '🇨🇭',
+ 'symbols': '🔣',
+ 'symbols_over_mouth': '🤬',
+ 'synagogue': '🕍',
+ 'syria': '🇸🇾',
+ 'syringe': '💉',
+ 't-rex': '🦖',
+ 'taco': '🌮',
+ 'tada': '🎉',
+ 'taiwan': '🇹🇼',
+ 'tajikistan': '🇹🇯',
+ 'takeout_box': '🥡',
+ 'tanabata_tree': '🎋',
+ 'tangerine': '🍊',
+ 'tanzania': '🇹🇿',
+ 'taurus': '♉',
+ 'taxi': '🚕',
+ 'tea': '🍵',
+ 'teddy_bear': '🧸',
+ 'telephone_receiver': '📞',
+ 'telescope': '🔭',
+ 'tennis': '🎾',
+ 'tent': '⛺',
+ 'test_tube': '🧪',
+ 'thailand': '🇹🇭',
+ 'thermometer': '🌡',
+ 'thinking': '🤔',
+ 'thought_balloon': '💭',
+ 'thread': '🧵',
+ 'three': '3️⃣',
+ 'ticket': '🎫',
+ 'tickets': '🎟',
+ 'tiger': '🐯',
+ 'tiger2': '🐅',
+ 'timer_clock': '⏲',
+ 'timor_leste': '🇹🇱',
+ 'tipping_hand_man': '💁♂️',
+ 'tipping_hand_woman': '💁',
+ 'tired_face': '😫',
+ 'tm': '™️',
+ 'togo': '🇹🇬',
+ 'toilet': '🚽',
+ 'toilet_paper': '🧻',
+ 'tokelau': '🇹🇰',
+ 'tokyo_tower': '🗼',
+ 'tomato': '🍅',
+ 'tonga': '🇹🇴',
+ 'tongue': '👅',
+ 'toolbox': '🧰',
+ 'tooth': '🦷',
+ 'top': '🔝',
+ 'tophat': '🎩',
+ 'tornado': '🌪',
+ 'tr': '🇹🇷',
+ 'trackball': '🖲',
+ 'tractor': '🚜',
+ 'traffic_light': '🚥',
+ 'train': '🚋',
+ 'train2': '🚆',
+ 'tram': '🚊',
+ 'triangular_flag_on_post': '🚩',
+ 'triangular_ruler': '📐',
+ 'trident': '🔱',
+ 'trinidad_tobago': '🇹🇹',
+ 'triumph': '😤',
+ 'trolleybus': '🚎',
+ 'trophy': '🏆',
+ 'tropical_drink': '🍹',
+ 'tropical_fish': '🐠',
+ 'truck': '🚚',
+ 'trumpet': '🎺',
+ 'tshirt': '👕',
+ 'tulip': '🌷',
+ 'tumbler_glass': '🥃',
+ 'tunisia': '🇹🇳',
+ 'turkey': '🦃',
+ 'turkmenistan': '🇹🇲',
+ 'turks_caicos_islands': '🇹🇨',
+ 'turtle': '🐢',
+ 'tuvalu': '🇹🇻',
+ 'tv': '📺',
+ 'twisted_rightwards_arrows': '🔀',
+ 'two': '2️⃣',
+ 'two_hearts': '💕',
+ 'two_men_holding_hands': '👬',
+ 'two_women_holding_hands': '👭',
+ 'u5272': '🈹',
+ 'u5408': '🈴',
+ 'u55b6': '🈺',
+ 'u6307': '🈯',
+ 'u6708': '🈷️',
+ 'u6709': '🈶',
+ 'u6e80': '🈵',
+ 'u7121': '🈚',
+ 'u7533': '🈸',
+ 'u7981': '🈲',
+ 'u7a7a': '🈳',
+ 'uganda': '🇺🇬',
+ 'uk': '🇬🇧',
+ 'ukraine': '🇺🇦',
+ 'umbrella': '☔',
+ 'unamused': '😒',
+ 'underage': '🔞',
+ 'unicorn': '🦄',
+ 'united_arab_emirates': '🇦🇪',
+ 'united_nations': '🇺🇳',
+ 'unlock': '🔓',
+ 'up': '🆙',
+ 'upside_down_face': '🙃',
+ 'uruguay': '🇺🇾',
+ 'us': '🇺🇸',
+ 'us_virgin_islands': '🇻🇮',
+ 'uzbekistan': '🇺🇿',
+ 'v': '✌',
+ 'vanuatu': '🇻🇺',
+ 'vatican_city': '🇻🇦',
+ 'venezuela': '🇻🇪',
+ 'vertical_traffic_light': '🚦',
+ 'vhs': '📼',
+ 'vibration_mode': '📳',
+ 'video_camera': '📹',
+ 'video_game': '🎮',
+ 'vietnam': '🇻🇳',
+ 'violin': '🎻',
+ 'virgo': '♍',
+ 'volcano': '🌋',
+ 'volleyball': '🏐',
+ 'vomiting': '🤮',
+ 'vs': '🆚',
+ 'vulcan_salute': '🖖',
+ 'wales': '🏴',
+ 'walking_man': '🚶',
+ 'walking_woman': '🚶♀️',
+ 'wallis_futuna': '🇼🇫',
+ 'waning_crescent_moon': '🌘',
+ 'waning_gibbous_moon': '🌖',
+ 'warning': '⚠️',
+ 'wastebasket': '🗑',
+ 'watch': '⌚',
+ 'water_buffalo': '🐃',
+ 'watermelon': '🍉',
+ 'wave': '👋',
+ 'wavy_dash': '〰️',
+ 'waxing_crescent_moon': '🌒',
+ 'waxing_gibbous_moon': '🌔',
+ 'wc': '🚾',
+ 'weary': '😩',
+ 'wedding': '💒',
+ 'weight_lifting_man': '🏋',
+ 'weight_lifting_woman': '🏋️♀️',
+ 'western_sahara': '🇪🇭',
+ 'whale': '🐳',
+ 'whale2': '🐋',
+ 'wheel_of_dharma': '☸',
+ 'wheelchair': '♿',
+ 'white_check_mark': '✅',
+ 'white_circle': '⚪',
+ 'white_flag': '🏳',
+ 'white_flower': '💮',
+ 'white_large_square': '⬜',
+ 'white_medium_small_square': '◽',
+ 'white_medium_square': '◻️',
+ 'white_small_square': '▫️',
+ 'white_square_button': '🔳',
+ 'wilted_flower': '🥀',
+ 'wind_chime': '🎐',
+ 'wind_face': '🌬',
+ 'wine_glass': '🍷',
+ 'wink': '😉',
+ 'wizard': '🧙♂️',
+ 'wolf': '🐺',
+ 'woman': '👩',
+ 'woman_artist': '👩🎨',
+ 'woman_astronaut': '👩🚀',
+ 'woman_cartwheeling': '🤸♀️',
+ 'woman_cook': '👩🍳',
+ 'woman_elf': '🧝♀️',
+ 'woman_facepalming': '🤦♀️',
+ 'woman_factory_worker': '👩🏭',
+ 'woman_fairy': '🧚♀️',
+ 'woman_farmer': '👩🌾',
+ 'woman_firefighter': '👩🚒',
+ 'woman_genie': '🧞♀️',
+ 'woman_health_worker': '👩⚕️',
+ 'woman_in_lotus_position': '🧘♀️',
+ 'woman_in_steamy_room': '🧖♀️',
+ 'woman_judge': '👩⚖️',
+ 'woman_juggling': '🤹♀️',
+ 'woman_mechanic': '👩🔧',
+ 'woman_office_worker': '👩💼',
+ 'woman_pilot': '👩✈️',
+ 'woman_playing_handball': '🤾♀️',
+ 'woman_playing_water_polo': '🤽♀️',
+ 'woman_scientist': '👩🔬',
+ 'woman_shrugging': '🤷',
+ 'woman_singer': '👩🎤',
+ 'woman_student': '👩🎓',
+ 'woman_superhero': '🦸♀️',
+ 'woman_supervillain': '🦹♀️',
+ 'woman_teacher': '👩🏫',
+ 'woman_technologist': '👩💻',
+ 'woman_vampire': '🧛♀️',
+ 'woman_with_headscarf': '🧕',
+ 'woman_with_turban': '👳♀️',
+ 'woman_zombie': '🧟♀️',
+ 'womans_clothes': '👚',
+ 'womans_hat': '👒',
+ 'women_wrestling': '🤼♀️',
+ 'womens': '🚺',
+ 'woozy': '🥴',
+ 'world_map': '🗺',
+ 'worried': '😟',
+ 'wrench': '🔧',
+ 'writing_hand': '✍',
+ 'x': '❌',
+ 'yarn': '🧶',
+ 'yellow_heart': '💛',
+ 'yemen': '🇾🇪',
+ 'yen': '💴',
+ 'yin_yang': '☯',
+ 'yum': '😋',
+ 'zambia': '🇿🇲',
+ 'zany': '🤪',
+ 'zap': '⚡',
+ 'zebra': '🦓',
+ 'zero': '0️⃣',
+ 'zimbabwe': '🇿🇼',
+ 'zipper_mouth_face': '🤐',
+ 'zzz': '💤',
+};
diff --git a/pkgs/markdown/lib/src/line.dart b/pkgs/markdown/lib/src/line.dart
new file mode 100644
index 0000000..1ccd5da
--- /dev/null
+++ b/pkgs/markdown/lib/src/line.dart
@@ -0,0 +1,47 @@
+// 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 'patterns.dart';
+
+/// A [Line] is a sequence of zero or more characters other than line feed
+/// (`U+000A`) or carriage return (`U+000D`), followed by a line ending or by
+/// the end of file.
+// See https://spec.commonmark.org/0.30/#line.
+class Line {
+ /// A sequence of zero or more characters other than the line ending.
+ final String content;
+
+ /// How many spaces of a tab that remains after part of it has been consumed.
+ // See: https://spec.commonmark.org/0.30/#example-6
+ // We cannot simply expand the `tabRemaining` to spaces, for example
+ //
+ // `>\t\tfoo`
+ //
+ // If we expand the 2 space width `tabRemaining` from blockquote block into 2
+ // spaces, so the string segment for the indented code block is:
+ //
+ // ` \tfoo`,
+ //
+ // then the output will be:
+ // ```html
+ // <pre><code>foo
+ // </code></pre>
+ // ```
+ // instead of the expected:
+ // ```html
+ // <pre><code> foo
+ // </code></pre>
+ // ```
+ final int? tabRemaining;
+
+ // A line containing no characters, or a line containing only spaces
+ // (`U+0020`) or tabs (`U+0009`), is called a blank line.
+ // https://spec.commonmark.org/0.30/#blank-line
+ final bool isBlankLine;
+
+ Line(
+ this.content, {
+ this.tabRemaining,
+ }) : isBlankLine = emptyPattern.hasMatch(content);
+}
diff --git a/pkgs/markdown/lib/src/link_parser.dart b/pkgs/markdown/lib/src/link_parser.dart
new file mode 100644
index 0000000..4db22ad
--- /dev/null
+++ b/pkgs/markdown/lib/src/link_parser.dart
@@ -0,0 +1,270 @@
+// Copyright (c) 2023, 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 'charcode.dart';
+import 'text_parser.dart';
+import 'util.dart';
+
+class LinkParser extends TextParser {
+ /// If there is a valid link formed.
+ bool get valid => _valid;
+ bool _valid = false;
+
+ /// Link label.
+ String? get label => _label;
+ String? _label;
+
+ /// Link destination.
+ String? get destination => _destination;
+ String? _destination;
+
+ /// Link title.
+ String? get title => _title;
+ String? _title;
+
+ LinkParser(super.source);
+
+ /// How many lines of the [source] have been consumed by link reference
+ /// definition.
+ int get unconsumedLines => _unconsumedLines;
+ int _unconsumedLines = 0;
+
+ /// Parses [source] to a link reference definition.
+ void parseDefinition() {
+ if (!parseLabel() || isDone || charAt() != $colon) {
+ return;
+ }
+
+ // Advance to the next character after the colon.
+ advance();
+ if (!_parseDestination()) {
+ return;
+ }
+
+ var precedingWhitespaces = moveThroughWhitespace();
+ if (isDone) {
+ _valid = true;
+ return;
+ }
+
+ final multiline = charAt() == $lf;
+ precedingWhitespaces += moveThroughWhitespace(multiLine: true);
+
+ // The title must be preceded by whitespaces.
+ if (precedingWhitespaces == 0 || isDone) {
+ _valid = isDone;
+ return;
+ }
+
+ final hasValidTitle = _parseTitle();
+ // For example: `[foo]: <bar> "baz` is a invalid definition, but this one is
+ // valid:
+ // ```
+ // [foo]: <bar>
+ // "baz
+ // ```
+ if (!hasValidTitle && !multiline) {
+ return;
+ }
+
+ if (hasValidTitle) {
+ moveThroughWhitespace();
+ if (!isDone && charAt() != $lf) {
+ // It is not a valid definition if the title is followed by
+ // non-whitespace characters, for example: `[foo]: <bar> "baz" hello`.
+ // See https://spec.commonmark.org/0.30/#example-209.
+ if (!multiline) {
+ return;
+ }
+ // But it is a valid link reference definition if this definition is
+ // multiline, see https://spec.commonmark.org/0.30/#example-210.
+ _title = null;
+ }
+ }
+
+ final linesUnconsumed = source.substring(pos).split('\n');
+ if (linesUnconsumed.isNotEmpty && linesUnconsumed.first.isBlank) {
+ linesUnconsumed.removeAt(0);
+ }
+ _unconsumedLines = linesUnconsumed.length;
+
+ _valid = true;
+ }
+
+ /// Parses the link label, returns `true` if there is a valid link label.
+ bool parseLabel() {
+ moveThroughWhitespace(multiLine: true);
+
+ if (length - pos < 2) {
+ return false;
+ }
+
+ if (charAt() != $lbracket) {
+ return false;
+ }
+
+ // Advance past the opening `[`.
+ advance();
+ final start = pos;
+
+ // A link label can have at most 999 characters inside the square brackets.
+ // See https://spec.commonmark.org/0.30/#link-label.
+ var maxLoop = 999;
+ while (true) {
+ if (maxLoop-- < 0) {
+ return false;
+ }
+ final char = charAt(pos);
+ if (char == $backslash) {
+ advance();
+ } else if (char == $lbracket) {
+ return false;
+ } else if (char == $rbracket) {
+ break;
+ }
+ advance();
+ if (isDone) {
+ return false;
+ }
+ }
+
+ final text = substring(start, pos);
+ if (text.isBlank) {
+ return false;
+ }
+
+ // Advance past the closing `]`.
+ advance();
+ _label = text;
+ return true;
+ }
+
+ /// Parses the link destination, returns `true` there is a valid link
+ /// destination.
+ bool _parseDestination() {
+ moveThroughWhitespace(multiLine: true);
+ if (isDone) {
+ return false;
+ }
+
+ final isValidDestination = charAt() == $lt
+ ? _parseBracketedDestination()
+ : _parseBareDestination();
+
+ return isValidDestination;
+ }
+
+ /// Parses bracketed destinations (destinations wrapped in `<...>`). The
+ /// current position of the parser must be the first character of the
+ /// destination.
+ ///
+ /// Returns `true` if there is a valid link destination.
+ bool _parseBracketedDestination() {
+ // Walk past the opening `<`.
+ advance();
+
+ final start = pos;
+ while (true) {
+ final char = charAt();
+ if (char == $backslash) {
+ advance();
+ } else if (char == $lf || char == $cr || char == $ff) {
+ return false;
+ } else if (char == $gt) {
+ break;
+ }
+ advance();
+ if (isDone) {
+ return false;
+ }
+ }
+
+ _destination = substring(start, pos);
+
+ // Advance past the closing `>`.
+ advance();
+ return true;
+ }
+
+ /// Parse "bare" destinations (destinations _not_ wrapped in `<...>`). The
+ /// current position of the parser must be the first character of the
+ /// destination.
+ ///
+ /// Returns `true` if there is a valid link destination.
+ bool _parseBareDestination() {
+ var parenCount = 0;
+ final start = pos;
+
+ while (true) {
+ final char = charAt();
+ if (char == $backslash) {
+ advance();
+ } else if (char == $space || char == $lf || char == $cr || char == $ff) {
+ break;
+ } else if (char == $lparen) {
+ parenCount++;
+ } else if (char == $rparen) {
+ parenCount--;
+ if (parenCount == 0) {
+ advance();
+ break;
+ }
+ }
+ advance();
+
+ // There is no ending delimiter, so `isDone` also means it is at the end
+ // of a link destination.
+ if (isDone) {
+ break;
+ }
+ }
+
+ _destination = substring(start, pos);
+ return true;
+ }
+
+ /// Parses the **optional** link title, returns `true` if there is a valid
+ /// link title.
+ bool _parseTitle() {
+ // See: https://spec.commonmark.org/0.30/#link-title
+ // The whitespace should be followed by a title delimiter.
+ final delimiter = charAt();
+ if (delimiter != $apostrophe &&
+ delimiter != $quote &&
+ delimiter != $lparen) {
+ return false;
+ }
+
+ final closeDelimiter = delimiter == $lparen ? $rparen : delimiter;
+ advance();
+ if (isDone) {
+ return false;
+ }
+ final start = pos;
+
+ // Looking for an un-escaped closing delimiter.
+ while (true) {
+ final char = charAt();
+ if (char == $backslash) {
+ advance();
+ } else if (char == closeDelimiter) {
+ break;
+ }
+ advance();
+ if (isDone) {
+ return false;
+ }
+ }
+
+ if (isDone) {
+ return false;
+ }
+
+ _title = substring(start, pos);
+
+ // Advance past the closing delimiter.
+ advance();
+ return true;
+ }
+}
diff --git a/pkgs/markdown/lib/src/patterns.dart b/pkgs/markdown/lib/src/patterns.dart
new file mode 100644
index 0000000..2690419
--- /dev/null
+++ b/pkgs/markdown/lib/src/patterns.dart
@@ -0,0 +1,162 @@
+// 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.
+
+/// The line contains only whitespace or is empty.
+final emptyPattern = RegExp(r'^(?:[ \t]*)$');
+
+/// A series of `=` or `-` (on the next line) define setext-style headers.
+final setextPattern = RegExp(r'^[ ]{0,3}(=+|-+)\s*$');
+
+/// Leading (and trailing) `#` define atx-style headers.
+///
+/// Starts with 1-6 unescaped `#` characters which must not be followed by a
+/// non-space character. Line may end with any number of `#` characters,.
+final headerPattern =
+ RegExp(r'^ {0,3}(#{1,6})(?:[ \x09\x0b\x0c].*?)?(?:\s(#*)\s*)?$');
+
+/// The line starts with `>` with one optional space after.
+final blockquotePattern = RegExp(r'^[ ]{0,3}>[ \t]?.*$');
+
+/// A line indented four spaces. Used for code blocks and lists.
+final indentPattern = RegExp(r'^(?: | {0,3}\t)(.*)$');
+
+/// Fenced code block.
+final codeFencePattern = RegExp(
+ '^([ ]{0,3})(?:(?<backtick>`{3,})(?<backtickInfo>[^`]*)|'
+ r'(?<tilde>~{3,})(?<tildeInfo>.*))$',
+);
+
+/// Fenced blockquotes.
+final blockquoteFencePattern = RegExp(r'^>{3}\s*$');
+
+/// Three or more hyphens, asterisks or underscores by themselves. Note that
+/// a line like `----` is valid as both HR and SETEXT. In case of a tie,
+/// SETEXT should win.
+final hrPattern = RegExp(r'^ {0,3}([-*_])[ \t]*\1[ \t]*\1(?:\1|[ \t])*$');
+
+/// **Unordered list**
+/// A line starting with one of these markers: `-`, `*`, `+`. May have up to
+/// three leading spaces before the marker and any number of spaces or tabs
+/// after.
+///
+/// **Ordered list**
+///
+/// A line starting with a number like `123.`. May have up to three leading
+/// spaces before the marker and any number of spaces or tabs after.
+final listPattern =
+ RegExp(r'^[ ]{0,3}(?:(\d{1,9})[\.)]|[*+-])(?:[ \t]+(.*))?$');
+
+/// A line of hyphens separated by at least one pipe.
+final tablePattern = RegExp(
+ r'^[ ]{0,3}\|?([ \t]*:?\-+:?[ \t]*\|[ \t]*)+([ \t]|[ \t]*:?\-+:?[ \t]*)?$');
+
+/// A line starting with `[^` and contains with `]:`, but without special chars
+/// (`\] \r\n\x00\t`) between. Same as [GFM](cmark-gfm/src/scanners.re:318).
+final footnotePattern = RegExp(r'(^[ ]{0,3})\[\^([^\] \r\n\x00\t]+)\]:[ \t]*');
+
+/// A pattern which should never be used. It just satisfies non-nullability of
+/// pattern fields.
+final dummyPattern = RegExp('');
+
+/// A [String] pattern to match a named tag like `<table>` or `</table>`.
+const namedTagDefinition =
+ // Opening tag begins.
+ '<'
+
+ // Tag name.
+ '[a-z][a-z0-9-]*'
+
+ // Attribute begins, see
+ // https://spec.commonmark.org/0.30/#attribute.
+ r'(?:\s+'
+
+ // Attribute name, see
+ // https://spec.commonmark.org/0.30/#attribute-name.
+ '[a-z_:][a-z0-9._:-]*'
+
+ //
+ '(?:'
+ // Attribute value specification, see
+ // https://spec.commonmark.org/0.30/#attribute-value-specification.
+ r'\s*=\s*'
+
+ // Attribute value, see
+ // https://spec.commonmark.org/0.30/#unquoted-attribute-value.
+ r'''(?:[^\s"'=<>`]+?|'[^']*?'|"[^"]*?")'''
+
+ // Attribute ends.
+ ')?)*'
+
+ // Opening tag ends.
+ r'\s*/?>'
+
+ // Or
+ '|'
+
+ // Closing tag, see
+ // https://spec.commonmark.org/0.30/#closing-tag.
+ r'</[a-z][a-z0-9-]*\s*>';
+
+/// A pattern to match the start of an HTML block.
+///
+/// The 7 conditions here correspond to the 7 start conditions in the Commonmark
+/// specification one by one: https://spec.commonmark.org/0.30/#html-block.
+final htmlBlockPattern = RegExp(
+ '^ {0,3}(?:'
+ '<(?<condition_1>pre|script|style|textarea)'
+ r'(?:\s|>|$)'
+ '|'
+ '(?<condition_2><!--)'
+ '|'
+ r'(?<condition_3><\?)'
+ '|'
+ '(?<condition_4><![a-z])'
+ '|'
+ r'(?<condition_5><!\[CDATA\[)'
+ '|'
+ '</?(?<condition_6>address|article|aside|base|basefont|blockquote|body|'
+ 'caption|center|col|colgroup|dd|details|dialog|dir|DIV|dl|dt|fieldset|'
+ 'figcaption|figure|footer|form|frame|frameset|h1|h2|h3|h4|h5|h6|head|'
+ 'header|hr|html|iframe|legend|li|link|main|menu|menuitem|nav|noframes|ol|'
+ 'optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|'
+ 'thead|title|tr|track|ul)'
+ r'(?:\s|>|/>|$)'
+ '|'
+
+ // Here we are more restrictive than the Commonmark definition (Rule #7).
+ // Otherwise some raw HTML test cases will fail, for example:
+ // https://spec.commonmark.org/0.30/#example-618.
+ // Because if a line is treated as an HTML block, it will output as a
+ // Text node directly, and the RawHtmlSyntax will not have a chance to
+ // validate if this HTML tag is legal or not.
+ '(?<condition_7>(?:$namedTagDefinition)\\s*\$))',
+ caseSensitive: false,
+);
+
+/// ASCII punctuation characters.
+// See https://spec.commonmark.org/0.30/#unicode-whitespace-character.
+const asciiPunctuationCharacters = r'''!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~''';
+
+/// ASCII punctuation characters with some characters escaped, in order to be
+// used in the RegExp character set.
+const asciiPunctuationEscaped = r'''!"#$%&'()*+,\-./:;<=>?@\[\\\]^_`{|}~''';
+
+/// A pattern to match HTML entity references and numeric character references.
+// https://spec.commonmark.org/0.30/#entity-and-numeric-character-references
+final htmlCharactersPattern = RegExp(
+ '&(?:([a-z0-9]+)|#([0-9]{1,7})|#x([a-f0-9]{1,6}));',
+ caseSensitive: false,
+);
+
+/// A line starts with `[`.
+final linkReferenceDefinitionPattern = RegExp(r'^[ ]{0,3}\[');
+
+/// Alert type patterns.
+///
+/// A alert block is similar to a blockquote, starts with `> [!TYPE]`, and only
+/// 5 types are supported (case-insensitive).
+final alertPattern = RegExp(
+ r'^\s{0,3}>\s{0,3}\\?\[!(note|tip|important|caution|warning)\\?\]\s*$',
+ caseSensitive: false,
+);
diff --git a/pkgs/markdown/lib/src/text_parser.dart b/pkgs/markdown/lib/src/text_parser.dart
new file mode 100644
index 0000000..2f696de
--- /dev/null
+++ b/pkgs/markdown/lib/src/text_parser.dart
@@ -0,0 +1,57 @@
+// 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 'charcode.dart';
+
+/// A parser to parse a segment of source text.
+class TextParser {
+ final String source;
+
+ TextParser(this.source);
+
+ /// The current read position.
+ var _position = 0;
+ int get pos => _position;
+
+ /// Whether the read position has reached the end of [source].
+ bool get isDone => _position == length;
+
+ /// The length of [source].
+ int get length => source.length;
+
+ /// Walk the parser forward through any whitespace.
+ ///
+ /// Set [multiLine] `true` to support multiline, otherwise it will stop before
+ /// the line feed [$lf].
+ int moveThroughWhitespace({bool multiLine = false}) {
+ var i = 0;
+ while (!isDone) {
+ final char = charAt();
+ if (char != $space &&
+ char != $tab &&
+ char != $vt &&
+ char != $cr &&
+ char != $ff &&
+ !(multiLine && char == $lf)) {
+ return i;
+ }
+
+ i++;
+ advance();
+ }
+ return i;
+ }
+
+ int charAt([int? position]) => source.codeUnitAt(position ?? _position);
+
+ /// Moves the read position one character ahead.
+ void advance() => advanceBy(1);
+
+ /// Moves the read position for [length] characters. [length] can be negative.
+ void advanceBy(int length) {
+ _position += length;
+ }
+
+ /// Substrings the [source] and returns a [String].
+ String substring(int start, [int? end]) => source.substring(start, end);
+}
diff --git a/pkgs/markdown/lib/src/util.dart b/pkgs/markdown/lib/src/util.dart
new file mode 100644
index 0000000..93ea1e6
--- /dev/null
+++ b/pkgs/markdown/lib/src/util.dart
@@ -0,0 +1,220 @@
+// 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:convert';
+
+import 'assets/case_folding.dart';
+import 'assets/html_entities.dart';
+import 'charcode.dart';
+import 'line.dart';
+import 'patterns.dart';
+
+/// One or more whitespace, for compressing.
+final _oneOrMoreWhitespacePattern = RegExp('[ \n\r\t]+');
+
+/// Escapes (`"`), (`<`), (`>`) and (`&`) characters.
+/// Escapes (`'`) if [escapeApos] is `true`.
+String escapeHtml(String html, {bool escapeApos = true}) =>
+ HtmlEscape(HtmlEscapeMode(
+ escapeApos: escapeApos,
+ escapeLtGt: true,
+ escapeQuot: true,
+ )).convert(html);
+
+/// Escapes (`"`), (`<`) and (`>`) characters.
+String escapeHtmlAttribute(String text) =>
+ const HtmlEscape(HtmlEscapeMode.attribute).convert(text);
+
+/// "Normalizes" a link label, according to the [CommonMark spec].
+///
+/// [CommonMark spec] https://spec.commonmark.org/0.30/#link-label
+String normalizeLinkLabel(String label) {
+ var text = label.trim().replaceAll(_oneOrMoreWhitespacePattern, ' ');
+ for (var i = 0; i < text.length; i++) {
+ final mapped = caseFoldingMap[text[i]];
+ if (mapped != null) {
+ text = text.replaceRange(i, i + 1, mapped);
+ }
+ }
+ return text;
+}
+
+/// Normalizes a link destination, including the process of HTML characters
+/// decoding and percent encoding.
+// See the description of these examples:
+// https://spec.commonmark.org/0.30/#example-501
+// https://spec.commonmark.org/0.30/#example-502
+String normalizeLinkDestination(String destination) {
+ // Split by url escaping characters
+ // Concatenate them with unmodified URL-escaping.
+ // URL-escaping should be left alone inside the destination
+ // Refer: https://spec.commonmark.org/0.30/#example-502.
+
+ final regex = RegExp('%[0-9A-Fa-f]{2}');
+
+ return destination.splitMapJoin(
+ regex,
+ onMatch: (m) => m.match,
+ onNonMatch: (e) {
+ try {
+ e = Uri.decodeFull(e);
+ } catch (_) {}
+ return Uri.encodeFull(decodeHtmlCharacters(e));
+ },
+ );
+}
+
+/// Normalizes a link title, including the process of HTML characters decoding
+/// and HTML characters escaping.
+// See the description of these examples:
+// https://spec.commonmark.org/0.30/#example-505
+// https://spec.commonmark.org/0.30/#example-506
+// https://spec.commonmark.org/0.30/#example-507
+// https://spec.commonmark.org/0.30/#example-508
+String normalizeLinkTitle(String title) =>
+ escapeHtmlAttribute(decodeHtmlCharacters(title));
+
+/// Decodes HTML entity and numeric character references, for example decode
+/// `#` to `#`.
+String decodeHtmlCharacters(String input) =>
+ input.replaceAllMapped(htmlCharactersPattern, decodeHtmlCharacterFromMatch);
+
+/// Decodes HTML entity and numeric character references from the given [match].
+String decodeHtmlCharacterFromMatch(Match match) {
+ final text = match.match;
+ final entity = match[1];
+ final decimalNumber = match[2];
+ final hexadecimalNumber = match[3];
+
+ // Entity references, see
+ // https://spec.commonmark.org/0.30/#entity-references.
+ if (entity != null) {
+ return htmlEntitiesMap[text] ?? text;
+ }
+
+ // Decimal numeric character references, see
+ // https://spec.commonmark.org/0.30/#decimal-numeric-character-references.
+ else if (decimalNumber != null) {
+ final decimalValue = int.parse(decimalNumber);
+ int hexValue;
+ if (decimalValue < 1114112 && decimalValue > 1) {
+ hexValue = int.parse(decimalValue.toRadixString(16), radix: 16);
+ } else {
+ hexValue = 0xFFFd;
+ }
+
+ return String.fromCharCode(hexValue);
+ }
+
+ // Hexadecimal numeric character references, see
+ // https://spec.commonmark.org/0.30/#hexadecimal-numeric-character-references.
+ else if (hexadecimalNumber != null) {
+ var hexValue = int.parse(hexadecimalNumber, radix: 16);
+ if (hexValue > 0x10ffff || hexValue == 0) {
+ hexValue = 0xFFFd;
+ }
+ return String.fromCharCode(hexValue);
+ }
+
+ return text;
+}
+
+extension MatchExtensions on Match {
+ /// Returns the whole match String
+ String get match => this[0]!;
+}
+
+/// Escapes the ASCII punctuation characters after backslash(`\`).
+String escapePunctuation(String input) {
+ final buffer = StringBuffer();
+
+ for (var i = 0; i < input.length; i++) {
+ if (input.codeUnitAt(i) == $backslash) {
+ final next = i + 1 < input.length ? input[i + 1] : null;
+ if (next != null && asciiPunctuationCharacters.contains(next)) {
+ i++;
+ }
+ }
+ buffer.write(input[i]);
+ }
+
+ return buffer.toString();
+}
+
+extension StringExtensions on String {
+ /// Calculates the length of indentation a `String` has.
+ ///
+ // The behavior of tabs: https://spec.commonmark.org/0.30/#tabs
+ int indentation() {
+ var length = 0;
+ for (final char in codeUnits) {
+ if (char != $space && char != $tab) {
+ break;
+ }
+ length += char == $tab ? 4 - (length % 4) : 1;
+ }
+ return length;
+ }
+
+ /// Removes up to [length] characters of leading whitespace.
+ // The way of handling tabs: https://spec.commonmark.org/0.30/#tabs
+ DedentedText dedent([int length = 4]) {
+ final whitespaceMatch = RegExp('^[ \t]{0,$length}').firstMatch(this);
+ const tabSize = 4;
+
+ int? tabRemaining;
+ var start = 0;
+ final whitespaces = whitespaceMatch?[0];
+ if (whitespaces != null) {
+ var indentLength = 0;
+ for (; start < whitespaces.length; start++) {
+ final isTab = whitespaces[start] == '\t';
+ if (isTab) {
+ indentLength += tabSize;
+ tabRemaining = 4;
+ } else {
+ indentLength += 1;
+ }
+ if (indentLength >= length) {
+ if (tabRemaining != null) {
+ tabRemaining = indentLength - length;
+ }
+ if (indentLength == length || isTab) {
+ start += 1;
+ }
+ break;
+ }
+ if (tabRemaining != null) {
+ tabRemaining = 0;
+ }
+ }
+ }
+ return DedentedText(substring(start), tabRemaining);
+ }
+
+ /// Adds [width] of spaces to the beginning of this string.
+ String prependSpace(int width) => '${" " * width}$this';
+
+ /// Whether this string contains only whitespaces.
+ bool get isBlank => trim().isEmpty;
+
+ /// Converts this string to a list of [Line].
+ List<Line> toLines() => LineSplitter.split(this).map(Line.new).toList();
+
+ /// Returns the last character.
+ String last([int n = 1]) => substring(length - n);
+}
+
+/// A class that describes a dedented text.
+class DedentedText {
+ /// The dedented text.
+ final String text;
+
+ /// How many spaces of a tab that remains after part of it has been consumed.
+ ///
+ /// `null` means we did not read a `tab`.
+ final int? tabRemaining;
+
+ DedentedText(this.text, this.tabRemaining);
+}
diff --git a/pkgs/markdown/lib/src/version.dart b/pkgs/markdown/lib/src/version.dart
new file mode 100644
index 0000000..bd14228
--- /dev/null
+++ b/pkgs/markdown/lib/src/version.dart
@@ -0,0 +1,2 @@
+// Generated code. Do not modify.
+const packageVersion = '7.3.1-wip';
diff --git a/pkgs/markdown/pubspec.yaml b/pkgs/markdown/pubspec.yaml
new file mode 100644
index 0000000..4f0c0d3
--- /dev/null
+++ b/pkgs/markdown/pubspec.yaml
@@ -0,0 +1,34 @@
+name: markdown
+version: 7.3.1-wip
+description: >-
+ A portable Markdown library written in Dart that can parse Markdown into HTML.
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/markdown
+
+topics:
+ - markdown
+
+executables:
+ markdown:
+
+environment:
+ sdk: ^3.2.0
+
+dependencies:
+ args: ^2.0.0
+ meta: ^1.3.0
+
+dev_dependencies:
+ build_runner: ^2.0.5
+ build_version: ^2.0.3
+ build_web_compilers: ^4.0.0
+ collection: ^1.15.0
+ dart_flutter_team_lints: ^3.0.0
+ html: ^0.15.0
+ http: ^1.0.0
+ io: ^1.0.0
+ path: ^1.8.0
+ pool: ^1.5.1
+ tar: ^1.0.3
+ test: ^1.16.0
+ web: '>=0.4.2 <2.0.0'
+ yaml: ^3.0.0
diff --git a/pkgs/markdown/test/blns.dart b/pkgs/markdown/test/blns.dart
new file mode 100644
index 0000000..4f06115
--- /dev/null
+++ b/pkgs/markdown/test/blns.dart
@@ -0,0 +1,526 @@
+// GENERATED FILE. DO NOT EDIT.
+//
+// This file was generated from big-list-of-naughty-strings's JSON file:
+// https://github.com/minimaxir/big-list-of-naughty-strings/raw/master/blns.json
+// at 2023-08-26 16:34:37.127975 by the script, tool/update_blns.dart.
+
+// ignore_for_file: text_direction_code_point_in_literal, use_raw_strings
+// ignore_for_file: lines_longer_than_80_chars
+
+const blns = <String>[
+ '',
+ 'undefined',
+ 'undef',
+ 'null',
+ 'NULL',
+ '(null)',
+ 'nil',
+ 'NIL',
+ 'true',
+ 'false',
+ 'True',
+ 'False',
+ 'TRUE',
+ 'FALSE',
+ 'None',
+ 'hasOwnProperty',
+ 'then',
+ '\\',
+ '\\\\',
+ '0',
+ '1',
+ '1.00',
+ '\$1.00',
+ '1/2',
+ '1E2',
+ '1E02',
+ '1E+02',
+ '-1',
+ '-1.00',
+ '-\$1.00',
+ '-1/2',
+ '-1E2',
+ '-1E02',
+ '-1E+02',
+ '1/0',
+ '0/0',
+ '-2147483648/-1',
+ '-9223372036854775808/-1',
+ '-0',
+ '-0.0',
+ '+0',
+ '+0.0',
+ '0.00',
+ '0..0',
+ '.',
+ '0.0.0',
+ '0,00',
+ '0,,0',
+ ',',
+ '0,0,0',
+ '0.0/0',
+ '1.0/0.0',
+ '0.0/0.0',
+ '1,0/0,0',
+ '0,0/0,0',
+ '--1',
+ '-',
+ '-.',
+ '-,',
+ '999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999',
+ 'NaN',
+ 'Infinity',
+ '-Infinity',
+ 'INF',
+ '1#INF',
+ '-1#IND',
+ '1#QNAN',
+ '1#SNAN',
+ '1#IND',
+ '0x0',
+ '0xffffffff',
+ '0xffffffffffffffff',
+ '0xabad1dea',
+ '123456789012345678901234567890123456789',
+ '1,000.00',
+ '1 000.00',
+ '1\'000.00',
+ '1,000,000.00',
+ '1 000 000.00',
+ '1\'000\'000.00',
+ '1.000,00',
+ '1 000,00',
+ '1\'000,00',
+ '1.000.000,00',
+ '1 000 000,00',
+ '1\'000\'000,00',
+ '01000',
+ '08',
+ '09',
+ '2.2250738585072011e-308',
+ ',./;\'[]\\-=',
+ '<>?:"{}|_+',
+ '!@#\$%^&*()`~',
+ '',
+ '',
+ '
',
+ '',
+ '',
+ '',
+ 'Ω≈ç√∫˜µ≤≥÷',
+ 'åß∂ƒ©˙∆˚¬…æ',
+ 'œ∑´®†¥¨ˆøπ“‘',
+ '¡™£¢∞§¶•ªº–≠',
+ '¸˛Ç◊ı˜Â¯˘¿',
+ 'ÅÍÎÏ˝ÓÔÒÚÆ☃',
+ 'Œ„´‰ˇÁ¨ˆØ∏”’',
+ '`⁄€‹›fifl‡°·‚—±',
+ '⅛⅜⅝⅞',
+ 'ЁЂЃЄЅІЇЈЉЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюя',
+ '٠١٢٣٤٥٦٧٨٩',
+ '⁰⁴⁵',
+ '₀₁₂',
+ '⁰⁴⁵₀₁₂',
+ 'ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็ ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็ ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็',
+ '\'',
+ '"',
+ '\'\'',
+ '""',
+ '\'"\'',
+ '"\'\'\'\'"\'"',
+ '"\'"\'"\'\'\'\'"',
+ '<foo val=“bar” />',
+ '<foo val=“bar” />',
+ '<foo val=”bar“ />',
+ '<foo val=`bar\' />',
+ '田中さんにあげて下さい',
+ 'パーティーへ行かないか',
+ '和製漢語',
+ '部落格',
+ '사회과학원 어학연구소',
+ '찦차를 타고 온 펲시맨과 쑛다리 똠방각하',
+ '社會科學院語學研究所',
+ '울란바토르',
+ '𠜎𠜱𠝹𠱓𠱸𠲖𠳏',
+ '𐐜 𐐔𐐇𐐝𐐀𐐡𐐇𐐓 𐐙𐐊𐐡𐐝𐐓/𐐝𐐇𐐗𐐊𐐤𐐔 𐐒𐐋𐐗 𐐒𐐌 𐐜 𐐡𐐀𐐖𐐇𐐤𐐓𐐝 𐐱𐑂 𐑄 𐐔𐐇𐐝𐐀𐐡𐐇𐐓 𐐏𐐆𐐅𐐤𐐆𐐚𐐊𐐡𐐝𐐆𐐓𐐆',
+ '表ポあA鷗ŒéB逍Üߪąñ丂㐀𠀀',
+ 'Ⱥ',
+ 'Ⱦ',
+ 'ヽ༼ຈل͜ຈ༽ノ ヽ༼ຈل͜ຈ༽ノ',
+ '(。◕ ∀ ◕。)',
+ '`ィ(´∀`∩',
+ '__ロ(,_,*)',
+ '・( ̄∀ ̄)・:*:',
+ '゚・✿ヾ╲(。◕‿◕。)╱✿・゚',
+ ',。・:*:・゜’( ☻ ω ☻ )。・:*:・゜’',
+ '(╯°□°)╯︵ ┻━┻)',
+ '(ノಥ益ಥ)ノ ┻━┻',
+ '┬─┬ノ( º _ ºノ)',
+ '( ͡° ͜ʖ ͡°)',
+ '¯\\_(ツ)_/¯',
+ '😍',
+ '👩🏽',
+ '👨🦰 👨🏿🦰 👨🦱 👨🏿🦱 🦹🏿♂️',
+ '👾 🙇 💁 🙅 🙆 🙋 🙎 🙍',
+ '🐵 🙈 🙉 🙊',
+ '❤️ 💔 💌 💕 💞 💓 💗 💖 💘 💝 💟 💜 💛 💚 💙',
+ '✋🏿 💪🏿 👐🏿 🙌🏿 👏🏿 🙏🏿',
+ '👨👩👦 👨👩👧👦 👨👨👦 👩👩👧 👨👦 👨👧👦 👩👦 👩👧👦',
+ '🚾 🆒 🆓 🆕 🆖 🆗 🆙 🏧',
+ '0️⃣ 1️⃣ 2️⃣ 3️⃣ 4️⃣ 5️⃣ 6️⃣ 7️⃣ 8️⃣ 9️⃣ 🔟',
+ '🇺🇸🇷🇺🇸 🇦🇫🇦🇲🇸',
+ '🇺🇸🇷🇺🇸🇦🇫🇦🇲',
+ '🇺🇸🇷🇺🇸🇦',
+ '123',
+ '١٢٣',
+ 'ثم نفس سقطت وبالتحديد،, جزيرتي باستخدام أن دنو. إذ هنا؟ الستار وتنصيب كان. أهّل ايطاليا، بريطانيا-فرنسا قد أخذ. سليمان، إتفاقية بين ما, يذكر الحدود أي بعد, معاملة بولندا، الإطلاق عل إيو.',
+ 'בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְאֵת הָאָרֶץ',
+ 'הָיְתָהtestالصفحات التّحول',
+ '﷽',
+ 'ﷺ',
+ 'مُنَاقَشَةُ سُبُلِ اِسْتِخْدَامِ اللُّغَةِ فِي النُّظُمِ الْقَائِمَةِ وَفِيم يَخُصَّ التَّطْبِيقَاتُ الْحاسُوبِيَّةُ، ',
+ '᚛ᚄᚓᚐᚋᚒᚄ ᚑᚄᚂᚑᚏᚅ᚜',
+ '᚛ ᚜',
+ 'test',
+ 'test',
+ '
test
',
+ 'testtest',
+ 'test',
+ 'Ṱ̺̺̕o͞ ̷i̲̬͇̪͙n̝̗͕v̟̜̘̦͟o̶̙̰̠kè͚̮̺̪̹̱̤ ̖t̝͕̳̣̻̪͞h̼͓̲̦̳̘̲e͇̣̰̦̬͎ ̢̼̻̱̘h͚͎͙̜̣̲ͅi̦̲̣̰̤v̻͍e̺̭̳̪̰-m̢iͅn̖̺̞̲̯̰d̵̼̟͙̩̼̘̳ ̞̥̱̳̭r̛̗̘e͙p͠r̼̞̻̭̗e̺̠̣͟s̘͇̳͍̝͉e͉̥̯̞̲͚̬͜ǹ̬͎͎̟̖͇̤t͍̬̤͓̼̭͘ͅi̪̱n͠g̴͉ ͏͉ͅc̬̟h͡a̫̻̯͘o̫̟̖͍̙̝͉s̗̦̲.̨̹͈̣',
+ '̡͓̞ͅI̗̘̦͝n͇͇͙v̮̫ok̲̫̙͈i̖͙̭̹̠̞n̡̻̮̣̺g̲͈͙̭͙̬͎ ̰t͔̦h̞̲e̢̤ ͍̬̲͖f̴̘͕̣è͖ẹ̥̩l͖͔͚i͓͚̦͠n͖͍̗͓̳̮g͍ ̨o͚̪͡f̘̣̬ ̖̘͖̟͙̮c҉͔̫͖͓͇͖ͅh̵̤̣͚͔á̗̼͕ͅo̼̣̥s̱͈̺̖̦̻͢.̛̖̞̠̫̰',
+ '̗̺͖̹̯͓Ṯ̤͍̥͇͈h̲́e͏͓̼̗̙̼̣͔ ͇̜̱̠͓͍ͅN͕͠e̗̱z̘̝̜̺͙p̤̺̹͍̯͚e̠̻̠͜r̨̤͍̺̖͔̖̖d̠̟̭̬̝͟i̦͖̩͓͔̤a̠̗̬͉̙n͚͜ ̻̞̰͚ͅh̵͉i̳̞v̢͇ḙ͎͟-҉̭̩̼͔m̤̭̫i͕͇̝̦n̗͙ḍ̟ ̯̲͕͞ǫ̟̯̰̲͙̻̝f ̪̰̰̗̖̭̘͘c̦͍̲̞͍̩̙ḥ͚a̮͎̟̙͜ơ̩̹͎s̤.̝̝ ҉Z̡̖̜͖̰̣͉̜a͖̰͙̬͡l̲̫̳͍̩g̡̟̼̱͚̞̬ͅo̗͜.̟',
+ '̦H̬̤̗̤͝e͜ ̜̥̝̻͍̟́w̕h̖̯͓o̝͙̖͎̱̮ ҉̺̙̞̟͈W̷̼̭a̺̪͍į͈͕̭͙̯̜t̶̼̮s̘͙͖̕ ̠̫̠B̻͍͙͉̳ͅe̵h̵̬͇̫͙i̹͓̳̳̮͎̫̕n͟d̴̪̜̖ ̰͉̩͇͙̲͞ͅT͖̼͓̪͢h͏͓̮̻e̬̝̟ͅ ̤̹̝W͙̞̝͔͇͝ͅa͏͓͔̹̼̣l̴͔̰̤̟͔ḽ̫.͕',
+ 'Z̮̞̠͙͔ͅḀ̗̞͈̻̗Ḷ͙͎̯̹̞͓G̻O̭̗̮',
+ '˙ɐnbᴉlɐ ɐuƃɐɯ ǝɹolop ʇǝ ǝɹoqɐl ʇn ʇunpᴉpᴉɔuᴉ ɹodɯǝʇ poɯsnᴉǝ op pǝs \'ʇᴉlǝ ƃuᴉɔsᴉdᴉpɐ ɹnʇǝʇɔǝsuoɔ \'ʇǝɯɐ ʇᴉs ɹolop ɯnsdᴉ ɯǝɹo˥',
+ '00˙Ɩ\$-',
+ 'The quick brown fox jumps over the lazy dog',
+ '𝐓𝐡𝐞 𝐪𝐮𝐢𝐜𝐤 𝐛𝐫𝐨𝐰𝐧 𝐟𝐨𝐱 𝐣𝐮𝐦𝐩𝐬 𝐨𝐯𝐞𝐫 𝐭𝐡𝐞 𝐥𝐚𝐳𝐲 𝐝𝐨𝐠',
+ '𝕿𝖍𝖊 𝖖𝖚𝖎𝖈𝖐 𝖇𝖗𝖔𝖜𝖓 𝖋𝖔𝖝 𝖏𝖚𝖒𝖕𝖘 𝖔𝖛𝖊𝖗 𝖙𝖍𝖊 𝖑𝖆𝖟𝖞 𝖉𝖔𝖌',
+ '𝑻𝒉𝒆 𝒒𝒖𝒊𝒄𝒌 𝒃𝒓𝒐𝒘𝒏 𝒇𝒐𝒙 𝒋𝒖𝒎𝒑𝒔 𝒐𝒗𝒆𝒓 𝒕𝒉𝒆 𝒍𝒂𝒛𝒚 𝒅𝒐𝒈',
+ '𝓣𝓱𝓮 𝓺𝓾𝓲𝓬𝓴 𝓫𝓻𝓸𝔀𝓷 𝓯𝓸𝔁 𝓳𝓾𝓶𝓹𝓼 𝓸𝓿𝓮𝓻 𝓽𝓱𝓮 𝓵𝓪𝔃𝔂 𝓭𝓸𝓰',
+ '𝕋𝕙𝕖 𝕢𝕦𝕚𝕔𝕜 𝕓𝕣𝕠𝕨𝕟 𝕗𝕠𝕩 𝕛𝕦𝕞𝕡𝕤 𝕠𝕧𝕖𝕣 𝕥𝕙𝕖 𝕝𝕒𝕫𝕪 𝕕𝕠𝕘',
+ '𝚃𝚑𝚎 𝚚𝚞𝚒𝚌𝚔 𝚋𝚛𝚘𝚠𝚗 𝚏𝚘𝚡 𝚓𝚞𝚖𝚙𝚜 𝚘𝚟𝚎𝚛 𝚝𝚑𝚎 𝚕𝚊𝚣𝚢 𝚍𝚘𝚐',
+ '⒯⒣⒠ ⒬⒰⒤⒞⒦ ⒝⒭⒪⒲⒩ ⒡⒪⒳ ⒥⒰⒨⒫⒮ ⒪⒱⒠⒭ ⒯⒣⒠ ⒧⒜⒵⒴ ⒟⒪⒢',
+ '<script>alert(123)</script>',
+ '<script>alert('123');</script>',
+ '<img src=x onerror=alert(123) />',
+ '<svg><script>123<1>alert(123)</script>',
+ '"><script>alert(123)</script>',
+ '\'><script>alert(123)</script>',
+ '><script>alert(123)</script>',
+ '</script><script>alert(123)</script>',
+ '< / script >< script >alert(123)< / script >',
+ ' onfocus=JaVaSCript:alert(123) autofocus',
+ '" onfocus=JaVaSCript:alert(123) autofocus',
+ '\' onfocus=JaVaSCript:alert(123) autofocus',
+ '<script>alert(123)</script>',
+ '<sc<script>ript>alert(123)</sc</script>ript>',
+ '--><script>alert(123)</script>',
+ '";alert(123);t="',
+ '\';alert(123);t=\'',
+ 'JavaSCript:alert(123)',
+ ';alert(123);',
+ 'src=JaVaSCript:prompt(132)',
+ '"><script>alert(123);</script x="',
+ '\'><script>alert(123);</script x=\'',
+ '><script>alert(123);</script x=',
+ '" autofocus onkeyup="javascript:alert(123)',
+ '\' autofocus onkeyup=\'javascript:alert(123)',
+ '<script\\x20type="text/javascript">javascript:alert(1);</script>',
+ '<script\\x3Etype="text/javascript">javascript:alert(1);</script>',
+ '<script\\x0Dtype="text/javascript">javascript:alert(1);</script>',
+ '<script\\x09type="text/javascript">javascript:alert(1);</script>',
+ '<script\\x0Ctype="text/javascript">javascript:alert(1);</script>',
+ '<script\\x2Ftype="text/javascript">javascript:alert(1);</script>',
+ '<script\\x0Atype="text/javascript">javascript:alert(1);</script>',
+ '\'`"><\\x3Cscript>javascript:alert(1)</script>',
+ '\'`"><\\x00script>javascript:alert(1)</script>',
+ 'ABC<div style="x\\x3Aexpression(javascript:alert(1)">DEF',
+ 'ABC<div style="x:expression\\x5C(javascript:alert(1)">DEF',
+ 'ABC<div style="x:expression\\x00(javascript:alert(1)">DEF',
+ 'ABC<div style="x:exp\\x00ression(javascript:alert(1)">DEF',
+ 'ABC<div style="x:exp\\x5Cression(javascript:alert(1)">DEF',
+ 'ABC<div style="x:\\x0Aexpression(javascript:alert(1)">DEF',
+ 'ABC<div style="x:\\x09expression(javascript:alert(1)">DEF',
+ 'ABC<div style="x:\\xE3\\x80\\x80expression(javascript:alert(1)">DEF',
+ 'ABC<div style="x:\\xE2\\x80\\x84expression(javascript:alert(1)">DEF',
+ 'ABC<div style="x:\\xC2\\xA0expression(javascript:alert(1)">DEF',
+ 'ABC<div style="x:\\xE2\\x80\\x80expression(javascript:alert(1)">DEF',
+ 'ABC<div style="x:\\xE2\\x80\\x8Aexpression(javascript:alert(1)">DEF',
+ 'ABC<div style="x:\\x0Dexpression(javascript:alert(1)">DEF',
+ 'ABC<div style="x:\\x0Cexpression(javascript:alert(1)">DEF',
+ 'ABC<div style="x:\\xE2\\x80\\x87expression(javascript:alert(1)">DEF',
+ 'ABC<div style="x:\\xEF\\xBB\\xBFexpression(javascript:alert(1)">DEF',
+ 'ABC<div style="x:\\x20expression(javascript:alert(1)">DEF',
+ 'ABC<div style="x:\\xE2\\x80\\x88expression(javascript:alert(1)">DEF',
+ 'ABC<div style="x:\\x00expression(javascript:alert(1)">DEF',
+ 'ABC<div style="x:\\xE2\\x80\\x8Bexpression(javascript:alert(1)">DEF',
+ 'ABC<div style="x:\\xE2\\x80\\x86expression(javascript:alert(1)">DEF',
+ 'ABC<div style="x:\\xE2\\x80\\x85expression(javascript:alert(1)">DEF',
+ 'ABC<div style="x:\\xE2\\x80\\x82expression(javascript:alert(1)">DEF',
+ 'ABC<div style="x:\\x0Bexpression(javascript:alert(1)">DEF',
+ 'ABC<div style="x:\\xE2\\x80\\x81expression(javascript:alert(1)">DEF',
+ 'ABC<div style="x:\\xE2\\x80\\x83expression(javascript:alert(1)">DEF',
+ 'ABC<div style="x:\\xE2\\x80\\x89expression(javascript:alert(1)">DEF',
+ '<a href="\\x0Bjavascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x0Fjavascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\xC2\\xA0javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x05javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\xE1\\xA0\\x8Ejavascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x18javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x11javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\xE2\\x80\\x88javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\xE2\\x80\\x89javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\xE2\\x80\\x80javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x17javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x03javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x0Ejavascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x1Ajavascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x00javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x10javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\xE2\\x80\\x82javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x20javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x13javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x09javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\xE2\\x80\\x8Ajavascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x14javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x19javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\xE2\\x80\\xAFjavascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x1Fjavascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\xE2\\x80\\x81javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x1Djavascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\xE2\\x80\\x87javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x07javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\xE1\\x9A\\x80javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\xE2\\x80\\x83javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x04javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x01javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x08javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\xE2\\x80\\x84javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\xE2\\x80\\x86javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\xE3\\x80\\x80javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x12javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x0Djavascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x0Ajavascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x0Cjavascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x15javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\xE2\\x80\\xA8javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x16javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x02javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x1Bjavascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x06javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\xE2\\x80\\xA9javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\xE2\\x80\\x85javascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x1Ejavascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\xE2\\x81\\x9Fjavascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="\\x1Cjavascript:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="javascript\\x00:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="javascript\\x3A:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="javascript\\x09:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="javascript\\x0D:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '<a href="javascript\\x0A:javascript:alert(1)" id="fuzzelement1">test</a>',
+ '`"\'><img src=xxx:x \\x0Aonerror=javascript:alert(1)>',
+ '`"\'><img src=xxx:x \\x22onerror=javascript:alert(1)>',
+ '`"\'><img src=xxx:x \\x0Bonerror=javascript:alert(1)>',
+ '`"\'><img src=xxx:x \\x0Donerror=javascript:alert(1)>',
+ '`"\'><img src=xxx:x \\x2Fonerror=javascript:alert(1)>',
+ '`"\'><img src=xxx:x \\x09onerror=javascript:alert(1)>',
+ '`"\'><img src=xxx:x \\x0Conerror=javascript:alert(1)>',
+ '`"\'><img src=xxx:x \\x00onerror=javascript:alert(1)>',
+ '`"\'><img src=xxx:x \\x27onerror=javascript:alert(1)>',
+ '`"\'><img src=xxx:x \\x20onerror=javascript:alert(1)>',
+ '"`\'><script>\\x3Bjavascript:alert(1)</script>',
+ '"`\'><script>\\x0Djavascript:alert(1)</script>',
+ '"`\'><script>\\xEF\\xBB\\xBFjavascript:alert(1)</script>',
+ '"`\'><script>\\xE2\\x80\\x81javascript:alert(1)</script>',
+ '"`\'><script>\\xE2\\x80\\x84javascript:alert(1)</script>',
+ '"`\'><script>\\xE3\\x80\\x80javascript:alert(1)</script>',
+ '"`\'><script>\\x09javascript:alert(1)</script>',
+ '"`\'><script>\\xE2\\x80\\x89javascript:alert(1)</script>',
+ '"`\'><script>\\xE2\\x80\\x85javascript:alert(1)</script>',
+ '"`\'><script>\\xE2\\x80\\x88javascript:alert(1)</script>',
+ '"`\'><script>\\x00javascript:alert(1)</script>',
+ '"`\'><script>\\xE2\\x80\\xA8javascript:alert(1)</script>',
+ '"`\'><script>\\xE2\\x80\\x8Ajavascript:alert(1)</script>',
+ '"`\'><script>\\xE1\\x9A\\x80javascript:alert(1)</script>',
+ '"`\'><script>\\x0Cjavascript:alert(1)</script>',
+ '"`\'><script>\\x2Bjavascript:alert(1)</script>',
+ '"`\'><script>\\xF0\\x90\\x96\\x9Ajavascript:alert(1)</script>',
+ '"`\'><script>-javascript:alert(1)</script>',
+ '"`\'><script>\\x0Ajavascript:alert(1)</script>',
+ '"`\'><script>\\xE2\\x80\\xAFjavascript:alert(1)</script>',
+ '"`\'><script>\\x7Ejavascript:alert(1)</script>',
+ '"`\'><script>\\xE2\\x80\\x87javascript:alert(1)</script>',
+ '"`\'><script>\\xE2\\x81\\x9Fjavascript:alert(1)</script>',
+ '"`\'><script>\\xE2\\x80\\xA9javascript:alert(1)</script>',
+ '"`\'><script>\\xC2\\x85javascript:alert(1)</script>',
+ '"`\'><script>\\xEF\\xBF\\xAEjavascript:alert(1)</script>',
+ '"`\'><script>\\xE2\\x80\\x83javascript:alert(1)</script>',
+ '"`\'><script>\\xE2\\x80\\x8Bjavascript:alert(1)</script>',
+ '"`\'><script>\\xEF\\xBF\\xBEjavascript:alert(1)</script>',
+ '"`\'><script>\\xE2\\x80\\x80javascript:alert(1)</script>',
+ '"`\'><script>\\x21javascript:alert(1)</script>',
+ '"`\'><script>\\xE2\\x80\\x82javascript:alert(1)</script>',
+ '"`\'><script>\\xE2\\x80\\x86javascript:alert(1)</script>',
+ '"`\'><script>\\xE1\\xA0\\x8Ejavascript:alert(1)</script>',
+ '"`\'><script>\\x0Bjavascript:alert(1)</script>',
+ '"`\'><script>\\x20javascript:alert(1)</script>',
+ '"`\'><script>\\xC2\\xA0javascript:alert(1)</script>',
+ '<img \\x00src=x onerror="alert(1)">',
+ '<img \\x47src=x onerror="javascript:alert(1)">',
+ '<img \\x11src=x onerror="javascript:alert(1)">',
+ '<img \\x12src=x onerror="javascript:alert(1)">',
+ '<img\\x47src=x onerror="javascript:alert(1)">',
+ '<img\\x10src=x onerror="javascript:alert(1)">',
+ '<img\\x13src=x onerror="javascript:alert(1)">',
+ '<img\\x32src=x onerror="javascript:alert(1)">',
+ '<img\\x47src=x onerror="javascript:alert(1)">',
+ '<img\\x11src=x onerror="javascript:alert(1)">',
+ '<img \\x47src=x onerror="javascript:alert(1)">',
+ '<img \\x34src=x onerror="javascript:alert(1)">',
+ '<img \\x39src=x onerror="javascript:alert(1)">',
+ '<img \\x00src=x onerror="javascript:alert(1)">',
+ '<img src\\x09=x onerror="javascript:alert(1)">',
+ '<img src\\x10=x onerror="javascript:alert(1)">',
+ '<img src\\x13=x onerror="javascript:alert(1)">',
+ '<img src\\x32=x onerror="javascript:alert(1)">',
+ '<img src\\x12=x onerror="javascript:alert(1)">',
+ '<img src\\x11=x onerror="javascript:alert(1)">',
+ '<img src\\x00=x onerror="javascript:alert(1)">',
+ '<img src\\x47=x onerror="javascript:alert(1)">',
+ '<img src=x\\x09onerror="javascript:alert(1)">',
+ '<img src=x\\x10onerror="javascript:alert(1)">',
+ '<img src=x\\x11onerror="javascript:alert(1)">',
+ '<img src=x\\x12onerror="javascript:alert(1)">',
+ '<img src=x\\x13onerror="javascript:alert(1)">',
+ '<img[a][b][c]src[d]=x[e]onerror=[f]"alert(1)">',
+ '<img src=x onerror=\\x09"javascript:alert(1)">',
+ '<img src=x onerror=\\x10"javascript:alert(1)">',
+ '<img src=x onerror=\\x11"javascript:alert(1)">',
+ '<img src=x onerror=\\x12"javascript:alert(1)">',
+ '<img src=x onerror=\\x32"javascript:alert(1)">',
+ '<img src=x onerror=\\x00"javascript:alert(1)">',
+ '<a href=javascript:javascript:alert(1)>XXX</a>',
+ '<img src="x` `<script>javascript:alert(1)</script>"` `>',
+ '<img src onerror /" \'"= alt=javascript:alert(1)//">',
+ '<title onpropertychange=javascript:alert(1)></title><title title=>',
+ '<a href=http://foo.bar/#x=`y></a><img alt="`><img src=x:x onerror=javascript:alert(1)></a>">',
+ '<!--[if]><script>javascript:alert(1)</script -->',
+ '<!--[if<img src=x onerror=javascript:alert(1)//]> -->',
+ '<script src="/\\%(jscript)s"></script>',
+ '<script src="\\\\%(jscript)s"></script>',
+ '<IMG """><SCRIPT>alert("XSS")</SCRIPT>">',
+ '<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>',
+ '<IMG SRC=# onmouseover="alert(\'xxs\')">',
+ '<IMG SRC= onmouseover="alert(\'xxs\')">',
+ '<IMG onmouseover="alert(\'xxs\')">',
+ '<IMG SRC=javascript:alert('XSS')>',
+ '<IMG SRC=javascript:alert('XSS')>',
+ '<IMG SRC=javascript:alert('XSS')>',
+ '<IMG SRC="jav ascript:alert(\'XSS\');">',
+ '<IMG SRC="jav	ascript:alert(\'XSS\');">',
+ '<IMG SRC="jav
ascript:alert(\'XSS\');">',
+ '<IMG SRC="jav
ascript:alert(\'XSS\');">',
+ 'perl -e \'print "<IMG SRC=java\\0script:alert(\\"XSS\\")>";\' > out',
+ '<IMG SRC="  javascript:alert(\'XSS\');">',
+ '<SCRIPT/XSS SRC="http://ha.ckers.org/xss.js"></SCRIPT>',
+ '<BODY onload!#\$%&()*~+-_.,:;?@[/|\\]^`=alert("XSS")>',
+ '<SCRIPT/SRC="http://ha.ckers.org/xss.js"></SCRIPT>',
+ '<<SCRIPT>alert("XSS");//<</SCRIPT>',
+ '<SCRIPT SRC=http://ha.ckers.org/xss.js?< B >',
+ '<SCRIPT SRC=//ha.ckers.org/.j>',
+ '<IMG SRC="javascript:alert(\'XSS\')"',
+ '<iframe src=http://ha.ckers.org/scriptlet.html <',
+ '\\";alert(\'XSS\');//',
+ '<u oncopy=alert()> Copy me</u>',
+ '<i onwheel=alert(1)> Scroll over me </i>',
+ '<plaintext>',
+ 'http://a/%%30%30',
+ '</textarea><script>alert(123)</script>',
+ '1;DROP TABLE users',
+ '1\'; DROP TABLE users-- 1',
+ '\' OR 1=1 -- 1',
+ '\' OR \'1\'=\'1',
+ '\'; EXEC sp_MSForEachTable \'DROP TABLE ?\'; --',
+ ' ',
+ '%',
+ '_',
+ '-',
+ '--',
+ '--version',
+ '--help',
+ '\$USER',
+ '/dev/null; touch /tmp/blns.fail ; echo',
+ '`touch /tmp/blns.fail`',
+ '\$(touch /tmp/blns.fail)',
+ '@{[system "touch /tmp/blns.fail"]}',
+ 'eval("puts \'hello world\'")',
+ 'System("ls -al /")',
+ '`ls -al /`',
+ 'Kernel.exec("ls -al /")',
+ 'Kernel.exit(1)',
+ '%x(\'ls -al /\')',
+ '<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE foo [ <!ELEMENT foo ANY ><!ENTITY xxe SYSTEM "file:///etc/passwd" >]><foo>&xxe;</foo>',
+ '\$HOME',
+ '\$ENV{\'HOME\'}',
+ '%d',
+ '%s%s%s%s%s',
+ '{0}',
+ '%*.*s',
+ '%@',
+ '%n',
+ 'File:///',
+ '../../../../../../../../../../../etc/passwd%00',
+ '../../../../../../../../../../../etc/hosts',
+ '() { 0; }; touch /tmp/blns.shellshock1.fail;',
+ '() { _; } >_[\$(\$())] { touch /tmp/blns.shellshock2.fail; }',
+ '<<< %s(un=\'%s\') = %u',
+ '+++ATH0',
+ 'CON',
+ 'PRN',
+ 'AUX',
+ 'CLOCK\$',
+ 'NUL',
+ 'A:',
+ 'ZZ:',
+ 'COM1',
+ 'LPT1',
+ 'LPT2',
+ 'LPT3',
+ 'COM2',
+ 'COM3',
+ 'COM4',
+ 'DCC SEND STARTKEYLOGGER 0 0 0',
+ 'Scunthorpe General Hospital',
+ 'Penistone Community Church',
+ 'Lightwater Country Park',
+ 'Jimmy Clitheroe',
+ 'Horniman Museum',
+ 'shitake mushrooms',
+ 'RomansInSussex.co.uk',
+ 'http://www.cum.qc.ca/',
+ 'Craig Cockburn, Software Specialist',
+ 'Linda Callahan',
+ 'Dr. Herman I. Libshitz',
+ 'magna cum laude',
+ 'Super Bowl XXX',
+ 'medieval erection of parapets',
+ 'evaluate',
+ 'mocha',
+ 'expression',
+ 'Arsenal canal',
+ 'classic',
+ 'Tyson Gay',
+ 'Dick Van Dyke',
+ 'basement',
+ 'If you\'re reading this, you\'ve been in a coma for almost 20 years now. We\'re trying a new technique. We don\'t know where this message will end up in your dream, but we hope it works. Please wake up, we miss you.',
+ 'Roses are [0;31mred[0m, violets are [0;34mblue. Hope you enjoy terminal hue',
+ 'But now...[20Cfor my greatest trick...[8m',
+ 'The quick brown fox... [Beeeep]',
+ 'Powerلُلُصّبُلُلصّبُررً ॣ ॣh ॣ ॣ冗',
+ '🏳0🌈️',
+ 'జ్ఞా',
+ 'گچپژ',
+ '{% print \'x\' * 64 * 1024**3 %}',
+ '{{ "".__class__.__mro__[2].__subclasses__()[40]("/etc/passwd").read() }}',
+];
diff --git a/pkgs/markdown/test/blns_test.dart b/pkgs/markdown/test/blns_test.dart
new file mode 100644
index 0000000..69790ef
--- /dev/null
+++ b/pkgs/markdown/test/blns_test.dart
@@ -0,0 +1,39 @@
+// Copyright (c) 2017, 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:markdown/markdown.dart';
+import 'package:test/test.dart';
+
+import 'blns.dart';
+
+// The BLNS tests merely test that `markdownToHtml` does not throw or hang while
+// processing "naughty string" inputs. While there are examples of multi-byte
+// characters, non-visible characters, etc., these tests should not be _relied
+// upon_ for testing multi-byte character support, etc.
+void main() {
+ test('parsing blns', () {
+ // This is more a test of update_blns.dart: we're testing that the strings
+ // were encoded half-decently, and nothing got globbed up into a big
+ // multiline string.
+ expect(blns, hasLength(515));
+ });
+
+ var index = 0;
+ for (final str in blns) {
+ test('blns string $index', () {
+ final result = markdownToHtml(str);
+ expect(result, const TypeMatcher<String>());
+ });
+ index++;
+ }
+
+ index = 0;
+ for (final str in blns) {
+ test('blns string $index w/ gitHubWeb', () {
+ final result = markdownToHtml(str, extensionSet: ExtensionSet.gitHubWeb);
+ expect(result, const TypeMatcher<String>());
+ });
+ index++;
+ }
+}
diff --git a/pkgs/markdown/test/common_mark/atx_headings.unit b/pkgs/markdown/test/common_mark/atx_headings.unit
new file mode 100644
index 0000000..1cb7e98
--- /dev/null
+++ b/pkgs/markdown/test/common_mark/atx_headings.unit
@@ -0,0 +1,112 @@
+>>> ATX headings - 62
+# foo
+## foo
+### foo
+#### foo
+##### foo
+###### foo
+<<<
+<h1>foo</h1>
+<h2>foo</h2>
+<h3>foo</h3>
+<h4>foo</h4>
+<h5>foo</h5>
+<h6>foo</h6>
+>>> ATX headings - 63
+####### foo
+<<<
+<p>####### foo</p>
+>>> ATX headings - 64
+#5 bolt
+
+#hashtag
+<<<
+<p>#5 bolt</p>
+<p>#hashtag</p>
+>>> ATX headings - 65
+\## foo
+<<<
+<p>## foo</p>
+>>> ATX headings - 66
+# foo *bar* \*baz\*
+<<<
+<h1>foo <em>bar</em> *baz*</h1>
+>>> ATX headings - 67
+# foo
+<<<
+<h1>foo</h1>
+>>> ATX headings - 68
+ ### foo
+ ## foo
+ # foo
+<<<
+<h3>foo</h3>
+<h2>foo</h2>
+<h1>foo</h1>
+>>> ATX headings - 69
+ # foo
+<<<
+<pre><code># foo
+</code></pre>
+>>> ATX headings - 70
+foo
+ # bar
+<<<
+<p>foo
+# bar</p>
+>>> ATX headings - 71
+## foo ##
+ ### bar ###
+<<<
+<h2>foo</h2>
+<h3>bar</h3>
+>>> ATX headings - 72
+# foo ##################################
+##### foo ##
+<<<
+<h1>foo</h1>
+<h5>foo</h5>
+>>> ATX headings - 73
+### foo ###
+<<<
+<h3>foo</h3>
+>>> ATX headings - 74
+### foo ### b
+<<<
+<h3>foo ### b</h3>
+>>> ATX headings - 75
+# foo#
+<<<
+<h1>foo#</h1>
+>>> ATX headings - 76
+### foo \###
+## foo #\##
+# foo \#
+<<<
+<h3>foo ###</h3>
+<h2>foo ###</h2>
+<h1>foo #</h1>
+>>> ATX headings - 77
+****
+## foo
+****
+<<<
+<hr />
+<h2>foo</h2>
+<hr />
+>>> ATX headings - 78
+Foo bar
+# baz
+Bar foo
+<<<
+<p>Foo bar</p>
+<h1>baz</h1>
+<p>Bar foo</p>
+>>> ATX headings - 79
+##
+#
+### ###
+<<<
+<h2></h2>
+<h1></h1>
+<h3></h3>
diff --git a/pkgs/markdown/test/common_mark/autolinks.unit b/pkgs/markdown/test/common_mark/autolinks.unit
new file mode 100644
index 0000000..0bb319c
--- /dev/null
+++ b/pkgs/markdown/test/common_mark/autolinks.unit
@@ -0,0 +1,76 @@
+>>> Autolinks - 594
+<http://foo.bar.baz>
+<<<
+<p><a href="http://foo.bar.baz">http://foo.bar.baz</a></p>
+>>> Autolinks - 595
+<https://foo.bar.baz/test?q=hello&id=22&boolean>
+<<<
+<p><a href="https://foo.bar.baz/test?q=hello&id=22&boolean">https://foo.bar.baz/test?q=hello&id=22&boolean</a></p>
+>>> Autolinks - 596
+<irc://foo.bar:2233/baz>
+<<<
+<p><a href="irc://foo.bar:2233/baz">irc://foo.bar:2233/baz</a></p>
+>>> Autolinks - 597
+<MAILTO:FOO@BAR.BAZ>
+<<<
+<p><a href="MAILTO:FOO@BAR.BAZ">MAILTO:FOO@BAR.BAZ</a></p>
+>>> Autolinks - 598
+<a+b+c:d>
+<<<
+<p><a href="a+b+c:d">a+b+c:d</a></p>
+>>> Autolinks - 599
+<made-up-scheme://foo,bar>
+<<<
+<p><a href="made-up-scheme://foo,bar">made-up-scheme://foo,bar</a></p>
+>>> Autolinks - 600
+<https://../>
+<<<
+<p><a href="https://../">https://../</a></p>
+>>> Autolinks - 601
+<localhost:5001/foo>
+<<<
+<p><a href="localhost:5001/foo">localhost:5001/foo</a></p>
+>>> Autolinks - 602
+<https://foo.bar/baz bim>
+<<<
+<p><https://foo.bar/baz bim></p>
+>>> Autolinks - 603
+<https://example.com/\[\>
+<<<
+<p><a href="https://example.com/%5C%5B%5C">https://example.com/\[\</a></p>
+>>> Autolinks - 604
+<foo@bar.example.com>
+<<<
+<p><a href="mailto:foo@bar.example.com">foo@bar.example.com</a></p>
+>>> Autolinks - 605
+<foo+special@Bar.baz-bar0.com>
+<<<
+<p><a href="mailto:foo+special@Bar.baz-bar0.com">foo+special@Bar.baz-bar0.com</a></p>
+>>> Autolinks - 606
+<foo\+@bar.example.com>
+<<<
+<p><foo+@bar.example.com></p>
+>>> Autolinks - 607
+<>
+<<<
+<p><></p>
+>>> Autolinks - 608
+< https://foo.bar >
+<<<
+<p>< https://foo.bar ></p>
+>>> Autolinks - 609
+<m:abc>
+<<<
+<p><m:abc></p>
+>>> Autolinks - 610
+<foo.bar.baz>
+<<<
+<p><foo.bar.baz></p>
+>>> Autolinks - 611
+https://example.com
+<<<
+<p>https://example.com</p>
+>>> Autolinks - 612
+foo@bar.example.com
+<<<
+<p>foo@bar.example.com</p>
diff --git a/pkgs/markdown/test/common_mark/backslash_escapes.unit b/pkgs/markdown/test/common_mark/backslash_escapes.unit
new file mode 100644
index 0000000..54766e4
--- /dev/null
+++ b/pkgs/markdown/test/common_mark/backslash_escapes.unit
@@ -0,0 +1,79 @@
+>>> Backslash escapes - 12
+\!\"\#\$\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\@\[\\\]\^\_\`\{\|\}\~
+<<<
+<p>!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~</p>
+>>> Backslash escapes - 13
+\ \A\a\ \3\φ\«
+<<<
+<p>\ \A\a\ \3\φ\«</p>
+>>> Backslash escapes - 14
+\*not emphasized*
+\<br/> not a tag
+\[not a link](/foo)
+\`not code`
+1\. not a list
+\* not a list
+\# not a heading
+\[foo]: /url "not a reference"
+\ö not a character entity
+<<<
+<p>*not emphasized*
+<br/> not a tag
+[not a link](/foo)
+`not code`
+1. not a list
+* not a list
+# not a heading
+[foo]: /url "not a reference"
+&ouml; not a character entity</p>
+>>> Backslash escapes - 15
+\\*emphasis*
+<<<
+<p>\<em>emphasis</em></p>
+>>> Backslash escapes - 16
+foo\
+bar
+<<<
+<p>foo<br />
+bar</p>
+>>> Backslash escapes - 17
+`` \[\` ``
+<<<
+<p><code>\[\`</code></p>
+>>> Backslash escapes - 18
+ \[\]
+<<<
+<pre><code>\[\]
+</code></pre>
+>>> Backslash escapes - 19
+~~~
+\[\]
+~~~
+<<<
+<pre><code>\[\]
+</code></pre>
+>>> Backslash escapes - 20
+<https://example.com?find=\*>
+<<<
+<p><a href="https://example.com?find=%5C*">https://example.com?find=\*</a></p>
+>>> Backslash escapes - 21
+<a href="/bar\/)">
+<<<
+<a href="/bar\/)">
+>>> Backslash escapes - 22
+[foo](/bar\* "ti\*tle")
+<<<
+<p><a href="/bar*" title="ti*tle">foo</a></p>
+>>> Backslash escapes - 23
+[foo]
+
+[foo]: /bar\* "ti\*tle"
+<<<
+<p><a href="/bar*" title="ti*tle">foo</a></p>
+>>> Backslash escapes - 24
+``` foo\+bar
+foo
+```
+<<<
+<pre><code class="language-foo+bar">foo
+</code></pre>
diff --git a/pkgs/markdown/test/common_mark/blank_lines.unit b/pkgs/markdown/test/common_mark/blank_lines.unit
new file mode 100644
index 0000000..86977bd
--- /dev/null
+++ b/pkgs/markdown/test/common_mark/blank_lines.unit
@@ -0,0 +1,12 @@
+>>> Blank lines - 227
+
+
+aaa
+
+
+# aaa
+
+
+<<<
+<p>aaa</p>
+<h1>aaa</h1>
diff --git a/pkgs/markdown/test/common_mark/block_quotes.unit b/pkgs/markdown/test/common_mark/block_quotes.unit
new file mode 100644
index 0000000..9931044
--- /dev/null
+++ b/pkgs/markdown/test/common_mark/block_quotes.unit
@@ -0,0 +1,239 @@
+>>> Block quotes - 228
+> # Foo
+> bar
+> baz
+<<<
+<blockquote>
+<h1>Foo</h1>
+<p>bar
+baz</p>
+</blockquote>
+>>> Block quotes - 229
+># Foo
+>bar
+> baz
+<<<
+<blockquote>
+<h1>Foo</h1>
+<p>bar
+baz</p>
+</blockquote>
+>>> Block quotes - 230
+ > # Foo
+ > bar
+ > baz
+<<<
+<blockquote>
+<h1>Foo</h1>
+<p>bar
+baz</p>
+</blockquote>
+>>> Block quotes - 231
+ > # Foo
+ > bar
+ > baz
+<<<
+<pre><code>> # Foo
+> bar
+> baz
+</code></pre>
+>>> Block quotes - 232
+> # Foo
+> bar
+baz
+<<<
+<blockquote>
+<h1>Foo</h1>
+<p>bar
+baz</p>
+</blockquote>
+>>> Block quotes - 233
+> bar
+baz
+> foo
+<<<
+<blockquote>
+<p>bar
+baz
+foo</p>
+</blockquote>
+>>> Block quotes - 234
+> foo
+---
+<<<
+<blockquote>
+<p>foo</p>
+</blockquote>
+<hr />
+>>> Block quotes - 235
+> - foo
+- bar
+<<<
+<blockquote>
+<ul>
+<li>foo</li>
+</ul>
+</blockquote>
+<ul>
+<li>bar</li>
+</ul>
+>>> Block quotes - 236
+> foo
+ bar
+<<<
+<blockquote>
+<pre><code>foo
+</code></pre>
+</blockquote>
+<pre><code>bar
+</code></pre>
+>>> Block quotes - 237
+> ```
+foo
+```
+<<<
+<blockquote>
+<pre><code></code></pre>
+</blockquote>
+<p>foo</p>
+<pre><code></code></pre>
+>>> Block quotes - 238
+> foo
+ - bar
+<<<
+<blockquote>
+<p>foo
+- bar</p>
+</blockquote>
+>>> Block quotes - 239
+>
+<<<
+<blockquote>
+</blockquote>
+>>> Block quotes - 240
+>
+>
+>
+<<<
+<blockquote>
+</blockquote>
+>>> Block quotes - 241
+>
+> foo
+>
+<<<
+<blockquote>
+<p>foo</p>
+</blockquote>
+>>> Block quotes - 242
+> foo
+
+> bar
+<<<
+<blockquote>
+<p>foo</p>
+</blockquote>
+<blockquote>
+<p>bar</p>
+</blockquote>
+>>> Block quotes - 243
+> foo
+> bar
+<<<
+<blockquote>
+<p>foo
+bar</p>
+</blockquote>
+>>> Block quotes - 244
+> foo
+>
+> bar
+<<<
+<blockquote>
+<p>foo</p>
+<p>bar</p>
+</blockquote>
+>>> Block quotes - 245
+foo
+> bar
+<<<
+<p>foo</p>
+<blockquote>
+<p>bar</p>
+</blockquote>
+>>> Block quotes - 246
+> aaa
+***
+> bbb
+<<<
+<blockquote>
+<p>aaa</p>
+</blockquote>
+<hr />
+<blockquote>
+<p>bbb</p>
+</blockquote>
+>>> Block quotes - 247
+> bar
+baz
+<<<
+<blockquote>
+<p>bar
+baz</p>
+</blockquote>
+>>> Block quotes - 248
+> bar
+
+baz
+<<<
+<blockquote>
+<p>bar</p>
+</blockquote>
+<p>baz</p>
+>>> Block quotes - 249
+> bar
+>
+baz
+<<<
+<blockquote>
+<p>bar</p>
+</blockquote>
+<p>baz</p>
+>>> Block quotes - 250
+> > > foo
+bar
+<<<
+<blockquote>
+<blockquote>
+<blockquote>
+<p>foo
+bar</p>
+</blockquote>
+</blockquote>
+</blockquote>
+>>> Block quotes - 251
+>>> foo
+> bar
+>>baz
+<<<
+<blockquote>
+<blockquote>
+<blockquote>
+<p>foo
+bar
+baz</p>
+</blockquote>
+</blockquote>
+</blockquote>
+>>> Block quotes - 252
+> code
+
+> not code
+<<<
+<blockquote>
+<pre><code>code
+</code></pre>
+</blockquote>
+<blockquote>
+<p>not code</p>
+</blockquote>
diff --git a/pkgs/markdown/test/common_mark/code_spans.unit b/pkgs/markdown/test/common_mark/code_spans.unit
new file mode 100644
index 0000000..5ff95dd
--- /dev/null
+++ b/pkgs/markdown/test/common_mark/code_spans.unit
@@ -0,0 +1,97 @@
+>>> Code spans - 328
+`foo`
+<<<
+<p><code>foo</code></p>
+>>> Code spans - 329
+`` foo ` bar ``
+<<<
+<p><code>foo ` bar</code></p>
+>>> Code spans - 330
+` `` `
+<<<
+<p><code>``</code></p>
+>>> Code spans - 331
+` `` `
+<<<
+<p><code> `` </code></p>
+>>> Code spans - 332
+` a`
+<<<
+<p><code> a</code></p>
+>>> Code spans - 333
+` b `
+<<<
+<p><code> b </code></p>
+>>> Code spans - 334
+` `
+` `
+<<<
+<p><code> </code>
+<code> </code></p>
+>>> Code spans - 335
+``
+foo
+bar
+baz
+``
+<<<
+<p><code>foo bar baz</code></p>
+>>> Code spans - 336
+``
+foo
+``
+<<<
+<p><code>foo </code></p>
+>>> Code spans - 337
+`foo bar
+baz`
+<<<
+<p><code>foo bar baz</code></p>
+>>> Code spans - 338
+`foo\`bar`
+<<<
+<p><code>foo\</code>bar`</p>
+>>> Code spans - 339
+``foo`bar``
+<<<
+<p><code>foo`bar</code></p>
+>>> Code spans - 340
+` foo `` bar `
+<<<
+<p><code>foo `` bar</code></p>
+>>> Code spans - 341
+*foo`*`
+<<<
+<p>*foo<code>*</code></p>
+>>> Code spans - 342
+[not a `link](/foo`)
+<<<
+<p>[not a <code>link](/foo</code>)</p>
+>>> Code spans - 343
+`<a href="`">`
+<<<
+<p><code><a href="</code>">`</p>
+>>> Code spans - 344
+<a href="`">`
+<<<
+<p><a href="`">`</p>
+>>> Code spans - 345
+`<https://foo.bar.`baz>`
+<<<
+<p><code><https://foo.bar.</code>baz>`</p>
+>>> Code spans - 346
+<https://foo.bar.`baz>`
+<<<
+<p><a href="https://foo.bar.%60baz">https://foo.bar.`baz</a>`</p>
+>>> Code spans - 347
+```foo``
+<<<
+<p>```foo``</p>
+>>> Code spans - 348
+`foo
+<<<
+<p>`foo</p>
+>>> Code spans - 349
+`foo``bar``
+<<<
+<p>`foo<code>bar</code></p>
diff --git a/pkgs/markdown/test/common_mark/emphasis_and_strong_emphasis.unit b/pkgs/markdown/test/common_mark/emphasis_and_strong_emphasis.unit
new file mode 100644
index 0000000..6c8181d
--- /dev/null
+++ b/pkgs/markdown/test/common_mark/emphasis_and_strong_emphasis.unit
@@ -0,0 +1,546 @@
+>>> Emphasis and strong emphasis - 350
+*foo bar*
+<<<
+<p><em>foo bar</em></p>
+>>> Emphasis and strong emphasis - 351
+a * foo bar*
+<<<
+<p>a * foo bar*</p>
+>>> Emphasis and strong emphasis - 352
+a*"foo"*
+<<<
+<p>a*"foo"*</p>
+>>> Emphasis and strong emphasis - 353
+* a *
+<<<
+<p>* a *</p>
+>>> Emphasis and strong emphasis - 354
+*$*alpha.
+
+*£*bravo.
+
+*€*charlie.
+<<<
+<p>*$*alpha.</p>
+<p><em>£</em>bravo.</p>
+<p><em>€</em>charlie.</p>
+>>> Emphasis and strong emphasis - 355
+foo*bar*
+<<<
+<p>foo<em>bar</em></p>
+>>> Emphasis and strong emphasis - 356
+5*6*78
+<<<
+<p>5<em>6</em>78</p>
+>>> Emphasis and strong emphasis - 357
+_foo bar_
+<<<
+<p><em>foo bar</em></p>
+>>> Emphasis and strong emphasis - 358
+_ foo bar_
+<<<
+<p>_ foo bar_</p>
+>>> Emphasis and strong emphasis - 359
+a_"foo"_
+<<<
+<p>a_"foo"_</p>
+>>> Emphasis and strong emphasis - 360
+foo_bar_
+<<<
+<p>foo_bar_</p>
+>>> Emphasis and strong emphasis - 361
+5_6_78
+<<<
+<p>5_6_78</p>
+>>> Emphasis and strong emphasis - 362
+пристаням_стремятся_
+<<<
+<p>пристаням_стремятся_</p>
+>>> Emphasis and strong emphasis - 363
+aa_"bb"_cc
+<<<
+<p>aa_"bb"_cc</p>
+>>> Emphasis and strong emphasis - 364
+foo-_(bar)_
+<<<
+<p>foo-<em>(bar)</em></p>
+>>> Emphasis and strong emphasis - 365
+_foo*
+<<<
+<p>_foo*</p>
+>>> Emphasis and strong emphasis - 366
+*foo bar *
+<<<
+<p>*foo bar *</p>
+>>> Emphasis and strong emphasis - 367
+*foo bar
+*
+<<<
+<p>*foo bar
+*</p>
+>>> Emphasis and strong emphasis - 368
+*(*foo)
+<<<
+<p>*(*foo)</p>
+>>> Emphasis and strong emphasis - 369
+*(*foo*)*
+<<<
+<p><em>(<em>foo</em>)</em></p>
+>>> Emphasis and strong emphasis - 370
+*foo*bar
+<<<
+<p><em>foo</em>bar</p>
+>>> Emphasis and strong emphasis - 371
+_foo bar _
+<<<
+<p>_foo bar _</p>
+>>> Emphasis and strong emphasis - 372
+_(_foo)
+<<<
+<p>_(_foo)</p>
+>>> Emphasis and strong emphasis - 373
+_(_foo_)_
+<<<
+<p><em>(<em>foo</em>)</em></p>
+>>> Emphasis and strong emphasis - 374
+_foo_bar
+<<<
+<p>_foo_bar</p>
+>>> Emphasis and strong emphasis - 375
+_пристаням_стремятся
+<<<
+<p>_пристаням_стремятся</p>
+>>> Emphasis and strong emphasis - 376
+_foo_bar_baz_
+<<<
+<p><em>foo_bar_baz</em></p>
+>>> Emphasis and strong emphasis - 377
+_(bar)_.
+<<<
+<p><em>(bar)</em>.</p>
+>>> Emphasis and strong emphasis - 378
+**foo bar**
+<<<
+<p><strong>foo bar</strong></p>
+>>> Emphasis and strong emphasis - 379
+** foo bar**
+<<<
+<p>** foo bar**</p>
+>>> Emphasis and strong emphasis - 380
+a**"foo"**
+<<<
+<p>a**"foo"**</p>
+>>> Emphasis and strong emphasis - 381
+foo**bar**
+<<<
+<p>foo<strong>bar</strong></p>
+>>> Emphasis and strong emphasis - 382
+__foo bar__
+<<<
+<p><strong>foo bar</strong></p>
+>>> Emphasis and strong emphasis - 383
+__ foo bar__
+<<<
+<p>__ foo bar__</p>
+>>> Emphasis and strong emphasis - 384
+__
+foo bar__
+<<<
+<p>__
+foo bar__</p>
+>>> Emphasis and strong emphasis - 385
+a__"foo"__
+<<<
+<p>a__"foo"__</p>
+>>> Emphasis and strong emphasis - 386
+foo__bar__
+<<<
+<p>foo__bar__</p>
+>>> Emphasis and strong emphasis - 387
+5__6__78
+<<<
+<p>5__6__78</p>
+>>> Emphasis and strong emphasis - 388
+пристаням__стремятся__
+<<<
+<p>пристаням__стремятся__</p>
+>>> Emphasis and strong emphasis - 389
+__foo, __bar__, baz__
+<<<
+<p><strong>foo, <strong>bar</strong>, baz</strong></p>
+>>> Emphasis and strong emphasis - 390
+foo-__(bar)__
+<<<
+<p>foo-<strong>(bar)</strong></p>
+>>> Emphasis and strong emphasis - 391
+**foo bar **
+<<<
+<p>**foo bar **</p>
+>>> Emphasis and strong emphasis - 392
+**(**foo)
+<<<
+<p>**(**foo)</p>
+>>> Emphasis and strong emphasis - 393
+*(**foo**)*
+<<<
+<p><em>(<strong>foo</strong>)</em></p>
+>>> Emphasis and strong emphasis - 394
+**Gomphocarpus (*Gomphocarpus physocarpus*, syn.
+*Asclepias physocarpa*)**
+<<<
+<p><strong>Gomphocarpus (<em>Gomphocarpus physocarpus</em>, syn.
+<em>Asclepias physocarpa</em>)</strong></p>
+>>> Emphasis and strong emphasis - 395
+**foo "*bar*" foo**
+<<<
+<p><strong>foo "<em>bar</em>" foo</strong></p>
+>>> Emphasis and strong emphasis - 396
+**foo**bar
+<<<
+<p><strong>foo</strong>bar</p>
+>>> Emphasis and strong emphasis - 397
+__foo bar __
+<<<
+<p>__foo bar __</p>
+>>> Emphasis and strong emphasis - 398
+__(__foo)
+<<<
+<p>__(__foo)</p>
+>>> Emphasis and strong emphasis - 399
+_(__foo__)_
+<<<
+<p><em>(<strong>foo</strong>)</em></p>
+>>> Emphasis and strong emphasis - 400
+__foo__bar
+<<<
+<p>__foo__bar</p>
+>>> Emphasis and strong emphasis - 401
+__пристаням__стремятся
+<<<
+<p>__пристаням__стремятся</p>
+>>> Emphasis and strong emphasis - 402
+__foo__bar__baz__
+<<<
+<p><strong>foo__bar__baz</strong></p>
+>>> Emphasis and strong emphasis - 403
+__(bar)__.
+<<<
+<p><strong>(bar)</strong>.</p>
+>>> Emphasis and strong emphasis - 404
+*foo [bar](/url)*
+<<<
+<p><em>foo <a href="/url">bar</a></em></p>
+>>> Emphasis and strong emphasis - 405
+*foo
+bar*
+<<<
+<p><em>foo
+bar</em></p>
+>>> Emphasis and strong emphasis - 406
+_foo __bar__ baz_
+<<<
+<p><em>foo <strong>bar</strong> baz</em></p>
+>>> Emphasis and strong emphasis - 407
+_foo _bar_ baz_
+<<<
+<p><em>foo <em>bar</em> baz</em></p>
+>>> Emphasis and strong emphasis - 408
+__foo_ bar_
+<<<
+<p><em><em>foo</em> bar</em></p>
+>>> Emphasis and strong emphasis - 409
+*foo *bar**
+<<<
+<p><em>foo <em>bar</em></em></p>
+>>> Emphasis and strong emphasis - 410
+*foo **bar** baz*
+<<<
+<p><em>foo <strong>bar</strong> baz</em></p>
+>>> Emphasis and strong emphasis - 411
+*foo**bar**baz*
+<<<
+<p><em>foo<strong>bar</strong>baz</em></p>
+>>> Emphasis and strong emphasis - 412
+*foo**bar*
+<<<
+<p><em>foo**bar</em></p>
+>>> Emphasis and strong emphasis - 413
+***foo** bar*
+<<<
+<p><em><strong>foo</strong> bar</em></p>
+>>> Emphasis and strong emphasis - 414
+*foo **bar***
+<<<
+<p><em>foo <strong>bar</strong></em></p>
+>>> Emphasis and strong emphasis - 415
+*foo**bar***
+<<<
+<p><em>foo<strong>bar</strong></em></p>
+>>> Emphasis and strong emphasis - 416
+foo***bar***baz
+<<<
+<p>foo<em><strong>bar</strong></em>baz</p>
+>>> Emphasis and strong emphasis - 417
+foo******bar*********baz
+<<<
+<p>foo<strong><strong><strong>bar</strong></strong></strong>***baz</p>
+>>> Emphasis and strong emphasis - 418
+*foo **bar *baz* bim** bop*
+<<<
+<p><em>foo <strong>bar <em>baz</em> bim</strong> bop</em></p>
+>>> Emphasis and strong emphasis - 419
+*foo [*bar*](/url)*
+<<<
+<p><em>foo <a href="/url"><em>bar</em></a></em></p>
+>>> Emphasis and strong emphasis - 420
+** is not an empty emphasis
+<<<
+<p>** is not an empty emphasis</p>
+>>> Emphasis and strong emphasis - 421
+**** is not an empty strong emphasis
+<<<
+<p>**** is not an empty strong emphasis</p>
+>>> Emphasis and strong emphasis - 422
+**foo [bar](/url)**
+<<<
+<p><strong>foo <a href="/url">bar</a></strong></p>
+>>> Emphasis and strong emphasis - 423
+**foo
+bar**
+<<<
+<p><strong>foo
+bar</strong></p>
+>>> Emphasis and strong emphasis - 424
+__foo _bar_ baz__
+<<<
+<p><strong>foo <em>bar</em> baz</strong></p>
+>>> Emphasis and strong emphasis - 425
+__foo __bar__ baz__
+<<<
+<p><strong>foo <strong>bar</strong> baz</strong></p>
+>>> Emphasis and strong emphasis - 426
+____foo__ bar__
+<<<
+<p><strong><strong>foo</strong> bar</strong></p>
+>>> Emphasis and strong emphasis - 427
+**foo **bar****
+<<<
+<p><strong>foo <strong>bar</strong></strong></p>
+>>> Emphasis and strong emphasis - 428
+**foo *bar* baz**
+<<<
+<p><strong>foo <em>bar</em> baz</strong></p>
+>>> Emphasis and strong emphasis - 429
+**foo*bar*baz**
+<<<
+<p><strong>foo<em>bar</em>baz</strong></p>
+>>> Emphasis and strong emphasis - 430
+***foo* bar**
+<<<
+<p><strong><em>foo</em> bar</strong></p>
+>>> Emphasis and strong emphasis - 431
+**foo *bar***
+<<<
+<p><strong>foo <em>bar</em></strong></p>
+>>> Emphasis and strong emphasis - 432
+**foo *bar **baz**
+bim* bop**
+<<<
+<p><strong>foo <em>bar <strong>baz</strong>
+bim</em> bop</strong></p>
+>>> Emphasis and strong emphasis - 433
+**foo [*bar*](/url)**
+<<<
+<p><strong>foo <a href="/url"><em>bar</em></a></strong></p>
+>>> Emphasis and strong emphasis - 434
+__ is not an empty emphasis
+<<<
+<p>__ is not an empty emphasis</p>
+>>> Emphasis and strong emphasis - 435
+____ is not an empty strong emphasis
+<<<
+<p>____ is not an empty strong emphasis</p>
+>>> Emphasis and strong emphasis - 436
+foo ***
+<<<
+<p>foo ***</p>
+>>> Emphasis and strong emphasis - 437
+foo *\**
+<<<
+<p>foo <em>*</em></p>
+>>> Emphasis and strong emphasis - 438
+foo *_*
+<<<
+<p>foo <em>_</em></p>
+>>> Emphasis and strong emphasis - 439
+foo *****
+<<<
+<p>foo *****</p>
+>>> Emphasis and strong emphasis - 440
+foo **\***
+<<<
+<p>foo <strong>*</strong></p>
+>>> Emphasis and strong emphasis - 441
+foo **_**
+<<<
+<p>foo <strong>_</strong></p>
+>>> Emphasis and strong emphasis - 442
+**foo*
+<<<
+<p>*<em>foo</em></p>
+>>> Emphasis and strong emphasis - 443
+*foo**
+<<<
+<p><em>foo</em>*</p>
+>>> Emphasis and strong emphasis - 444
+***foo**
+<<<
+<p>*<strong>foo</strong></p>
+>>> Emphasis and strong emphasis - 445
+****foo*
+<<<
+<p>***<em>foo</em></p>
+>>> Emphasis and strong emphasis - 446
+**foo***
+<<<
+<p><strong>foo</strong>*</p>
+>>> Emphasis and strong emphasis - 447
+*foo****
+<<<
+<p><em>foo</em>***</p>
+>>> Emphasis and strong emphasis - 448
+foo ___
+<<<
+<p>foo ___</p>
+>>> Emphasis and strong emphasis - 449
+foo _\__
+<<<
+<p>foo <em>_</em></p>
+>>> Emphasis and strong emphasis - 450
+foo _*_
+<<<
+<p>foo <em>*</em></p>
+>>> Emphasis and strong emphasis - 451
+foo _____
+<<<
+<p>foo _____</p>
+>>> Emphasis and strong emphasis - 452
+foo __\___
+<<<
+<p>foo <strong>_</strong></p>
+>>> Emphasis and strong emphasis - 453
+foo __*__
+<<<
+<p>foo <strong>*</strong></p>
+>>> Emphasis and strong emphasis - 454
+__foo_
+<<<
+<p>_<em>foo</em></p>
+>>> Emphasis and strong emphasis - 455
+_foo__
+<<<
+<p><em>foo</em>_</p>
+>>> Emphasis and strong emphasis - 456
+___foo__
+<<<
+<p>_<strong>foo</strong></p>
+>>> Emphasis and strong emphasis - 457
+____foo_
+<<<
+<p>___<em>foo</em></p>
+>>> Emphasis and strong emphasis - 458
+__foo___
+<<<
+<p><strong>foo</strong>_</p>
+>>> Emphasis and strong emphasis - 459
+_foo____
+<<<
+<p><em>foo</em>___</p>
+>>> Emphasis and strong emphasis - 460
+**foo**
+<<<
+<p><strong>foo</strong></p>
+>>> Emphasis and strong emphasis - 461
+*_foo_*
+<<<
+<p><em><em>foo</em></em></p>
+>>> Emphasis and strong emphasis - 462
+__foo__
+<<<
+<p><strong>foo</strong></p>
+>>> Emphasis and strong emphasis - 463
+_*foo*_
+<<<
+<p><em><em>foo</em></em></p>
+>>> Emphasis and strong emphasis - 464
+****foo****
+<<<
+<p><strong><strong>foo</strong></strong></p>
+>>> Emphasis and strong emphasis - 465
+____foo____
+<<<
+<p><strong><strong>foo</strong></strong></p>
+>>> Emphasis and strong emphasis - 466
+******foo******
+<<<
+<p><strong><strong><strong>foo</strong></strong></strong></p>
+>>> Emphasis and strong emphasis - 467
+***foo***
+<<<
+<p><em><strong>foo</strong></em></p>
+>>> Emphasis and strong emphasis - 468
+_____foo_____
+<<<
+<p><em><strong><strong>foo</strong></strong></em></p>
+>>> Emphasis and strong emphasis - 469
+*foo _bar* baz_
+<<<
+<p><em>foo _bar</em> baz_</p>
+>>> Emphasis and strong emphasis - 470
+*foo __bar *baz bim__ bam*
+<<<
+<p><em>foo <strong>bar *baz bim</strong> bam</em></p>
+>>> Emphasis and strong emphasis - 471
+**foo **bar baz**
+<<<
+<p>**foo <strong>bar baz</strong></p>
+>>> Emphasis and strong emphasis - 472
+*foo *bar baz*
+<<<
+<p>*foo <em>bar baz</em></p>
+>>> Emphasis and strong emphasis - 473
+*[bar*](/url)
+<<<
+<p>*<a href="/url">bar*</a></p>
+>>> Emphasis and strong emphasis - 474
+_foo [bar_](/url)
+<<<
+<p>_foo <a href="/url">bar_</a></p>
+>>> Emphasis and strong emphasis - 475
+*<img src="foo" title="*"/>
+<<<
+<p>*<img src="foo" title="*"/></p>
+>>> Emphasis and strong emphasis - 476
+**<a href="**">
+<<<
+<p>**<a href="**"></p>
+>>> Emphasis and strong emphasis - 477
+__<a href="__">
+<<<
+<p>__<a href="__"></p>
+>>> Emphasis and strong emphasis - 478
+*a `*`*
+<<<
+<p><em>a <code>*</code></em></p>
+>>> Emphasis and strong emphasis - 479
+_a `_`_
+<<<
+<p><em>a <code>_</code></em></p>
+>>> Emphasis and strong emphasis - 480
+**a<https://foo.bar/?q=**>
+<<<
+<p>**a<a href="https://foo.bar/?q=**">https://foo.bar/?q=**</a></p>
+>>> Emphasis and strong emphasis - 481
+__a<https://foo.bar/?q=__>
+<<<
+<p>__a<a href="https://foo.bar/?q=__">https://foo.bar/?q=__</a></p>
diff --git a/pkgs/markdown/test/common_mark/entity_and_numeric_character_references.unit b/pkgs/markdown/test/common_mark/entity_and_numeric_character_references.unit
new file mode 100644
index 0000000..2859435
--- /dev/null
+++ b/pkgs/markdown/test/common_mark/entity_and_numeric_character_references.unit
@@ -0,0 +1,93 @@
+>>> Entity and numeric character references - 25
+ & © Æ Ď
+¾ ℋ ⅆ
+∲ ≧̸
+<<<
+<p>& © Æ Ď
+¾ ℋ ⅆ
+∲ ≧̸</p>
+>>> Entity and numeric character references - 26
+# Ӓ Ϡ �
+<<<
+<p># Ӓ Ϡ �</p>
+>>> Entity and numeric character references - 27
+" ആ ಫ
+<<<
+<p>" ആ ಫ</p>
+>>> Entity and numeric character references - 28
+  &x; &#; &#x;
+�
+&#abcdef0;
+&ThisIsNotDefined; &hi?;
+<<<
+<p>&nbsp &x; &#; &#x;
+&#87654321;
+&#abcdef0;
+&ThisIsNotDefined; &hi?;</p>
+>>> Entity and numeric character references - 29
+©
+<<<
+<p>&copy</p>
+>>> Entity and numeric character references - 30
+&MadeUpEntity;
+<<<
+<p>&MadeUpEntity;</p>
+>>> Entity and numeric character references - 31
+<a href="öö.html">
+<<<
+<a href="öö.html">
+>>> Entity and numeric character references - 32
+[foo](/föö "föö")
+<<<
+<p><a href="/f%C3%B6%C3%B6" title="föö">foo</a></p>
+>>> Entity and numeric character references - 33
+[foo]
+
+[foo]: /föö "föö"
+<<<
+<p><a href="/f%C3%B6%C3%B6" title="föö">foo</a></p>
+>>> Entity and numeric character references - 34
+``` föö
+foo
+```
+<<<
+<pre><code class="language-föö">foo
+</code></pre>
+>>> Entity and numeric character references - 35
+`föö`
+<<<
+<p><code>f&ouml;&ouml;</code></p>
+>>> Entity and numeric character references - 36
+ föfö
+<<<
+<pre><code>f&ouml;f&ouml;
+</code></pre>
+>>> Entity and numeric character references - 37
+*foo*
+*foo*
+<<<
+<p>*foo*
+<em>foo</em></p>
+>>> Entity and numeric character references - 38
+* foo
+
+* foo
+<<<
+<p>* foo</p>
+<ul>
+<li>foo</li>
+</ul>
+>>> Entity and numeric character references - 39
+foo bar
+<<<
+<p>foo
+
+bar</p>
+>>> Entity and numeric character references - 40
+	foo
+<<<
+<p>foo</p>
+>>> Entity and numeric character references - 41
+[a](url "tit")
+<<<
+<p>[a](url "tit")</p>
diff --git a/pkgs/markdown/test/common_mark/fenced_code_blocks.unit b/pkgs/markdown/test/common_mark/fenced_code_blocks.unit
new file mode 100644
index 0000000..06dec58
--- /dev/null
+++ b/pkgs/markdown/test/common_mark/fenced_code_blocks.unit
@@ -0,0 +1,245 @@
+>>> Fenced code blocks - 119
+```
+<
+ >
+```
+<<<
+<pre><code><
+ >
+</code></pre>
+>>> Fenced code blocks - 120
+~~~
+<
+ >
+~~~
+<<<
+<pre><code><
+ >
+</code></pre>
+>>> Fenced code blocks - 121
+``
+foo
+``
+<<<
+<p><code>foo</code></p>
+>>> Fenced code blocks - 122
+```
+aaa
+~~~
+```
+<<<
+<pre><code>aaa
+~~~
+</code></pre>
+>>> Fenced code blocks - 123
+~~~
+aaa
+```
+~~~
+<<<
+<pre><code>aaa
+```
+</code></pre>
+>>> Fenced code blocks - 124
+````
+aaa
+```
+``````
+<<<
+<pre><code>aaa
+```
+</code></pre>
+>>> Fenced code blocks - 125
+~~~~
+aaa
+~~~
+~~~~
+<<<
+<pre><code>aaa
+~~~
+</code></pre>
+>>> Fenced code blocks - 126
+```
+<<<
+<pre><code></code></pre>
+>>> Fenced code blocks - 127
+`````
+
+```
+aaa
+<<<
+<pre><code>
+```
+aaa
+</code></pre>
+>>> Fenced code blocks - 128
+> ```
+> aaa
+
+bbb
+<<<
+<blockquote>
+<pre><code>aaa
+</code></pre>
+</blockquote>
+<p>bbb</p>
+>>> Fenced code blocks - 129
+```
+
+
+```
+<<<
+<pre><code>
+
+</code></pre>
+>>> Fenced code blocks - 130
+```
+```
+<<<
+<pre><code></code></pre>
+>>> Fenced code blocks - 131
+ ```
+ aaa
+aaa
+```
+<<<
+<pre><code>aaa
+aaa
+</code></pre>
+>>> Fenced code blocks - 132
+ ```
+aaa
+ aaa
+aaa
+ ```
+<<<
+<pre><code>aaa
+aaa
+aaa
+</code></pre>
+>>> Fenced code blocks - 133
+ ```
+ aaa
+ aaa
+ aaa
+ ```
+<<<
+<pre><code>aaa
+ aaa
+aaa
+</code></pre>
+>>> Fenced code blocks - 134
+ ```
+ aaa
+ ```
+<<<
+<pre><code>```
+aaa
+```
+</code></pre>
+>>> Fenced code blocks - 135
+```
+aaa
+ ```
+<<<
+<pre><code>aaa
+</code></pre>
+>>> Fenced code blocks - 136
+ ```
+aaa
+ ```
+<<<
+<pre><code>aaa
+</code></pre>
+>>> Fenced code blocks - 137
+```
+aaa
+ ```
+<<<
+<pre><code>aaa
+ ```
+</code></pre>
+>>> Fenced code blocks - 138
+``` ```
+aaa
+<<<
+<p><code> </code>
+aaa</p>
+>>> Fenced code blocks - 139
+~~~~~~
+aaa
+~~~ ~~
+<<<
+<pre><code>aaa
+~~~ ~~
+</code></pre>
+>>> Fenced code blocks - 140
+foo
+```
+bar
+```
+baz
+<<<
+<p>foo</p>
+<pre><code>bar
+</code></pre>
+<p>baz</p>
+>>> Fenced code blocks - 141
+foo
+---
+~~~
+bar
+~~~
+# baz
+<<<
+<h2>foo</h2>
+<pre><code>bar
+</code></pre>
+<h1>baz</h1>
+>>> Fenced code blocks - 142
+```ruby
+def foo(x)
+ return 3
+end
+```
+<<<
+<pre><code class="language-ruby">def foo(x)
+ return 3
+end
+</code></pre>
+>>> Fenced code blocks - 143
+~~~~ ruby startline=3 $%@#$
+def foo(x)
+ return 3
+end
+~~~~~~~
+<<<
+<pre><code class="language-ruby">def foo(x)
+ return 3
+end
+</code></pre>
+>>> Fenced code blocks - 144
+````;
+````
+<<<
+<pre><code class="language-;"></code></pre>
+>>> Fenced code blocks - 145
+``` aa ```
+foo
+<<<
+<p><code>aa</code>
+foo</p>
+>>> Fenced code blocks - 146
+~~~ aa ``` ~~~
+foo
+~~~
+<<<
+<pre><code class="language-aa">foo
+</code></pre>
+>>> Fenced code blocks - 147
+```
+``` aaa
+```
+<<<
+<pre><code>``` aaa
+</code></pre>
diff --git a/pkgs/markdown/test/common_mark/hard_line_breaks.unit b/pkgs/markdown/test/common_mark/hard_line_breaks.unit
new file mode 100644
index 0000000..8a3f0be
--- /dev/null
+++ b/pkgs/markdown/test/common_mark/hard_line_breaks.unit
@@ -0,0 +1,80 @@
+>>> Hard line breaks - 633
+foo
+baz
+<<<
+<p>foo<br />
+baz</p>
+>>> Hard line breaks - 634
+foo\
+baz
+<<<
+<p>foo<br />
+baz</p>
+>>> Hard line breaks - 635
+foo
+baz
+<<<
+<p>foo<br />
+baz</p>
+>>> Hard line breaks - 636
+foo
+ bar
+<<<
+<p>foo<br />
+bar</p>
+>>> Hard line breaks - 637
+foo\
+ bar
+<<<
+<p>foo<br />
+bar</p>
+>>> Hard line breaks - 638
+*foo
+bar*
+<<<
+<p><em>foo<br />
+bar</em></p>
+>>> Hard line breaks - 639
+*foo\
+bar*
+<<<
+<p><em>foo<br />
+bar</em></p>
+>>> Hard line breaks - 640
+`code
+span`
+<<<
+<p><code>code span</code></p>
+>>> Hard line breaks - 641
+`code\
+span`
+<<<
+<p><code>code\ span</code></p>
+>>> Hard line breaks - 642
+<a href="foo
+bar">
+<<<
+<p><a href="foo
+bar"></p>
+>>> Hard line breaks - 643
+<a href="foo\
+bar">
+<<<
+<p><a href="foo\
+bar"></p>
+>>> Hard line breaks - 644
+foo\
+<<<
+<p>foo\</p>
+>>> Hard line breaks - 645
+foo
+<<<
+<p>foo</p>
+>>> Hard line breaks - 646
+### foo\
+<<<
+<h3>foo\</h3>
+>>> Hard line breaks - 647
+### foo
+<<<
+<h3>foo</h3>
diff --git a/pkgs/markdown/test/common_mark/html_blocks.unit b/pkgs/markdown/test/common_mark/html_blocks.unit
new file mode 100644
index 0000000..3a01cd0
--- /dev/null
+++ b/pkgs/markdown/test/common_mark/html_blocks.unit
@@ -0,0 +1,449 @@
+>>> HTML blocks - 148
+<table><tr><td>
+<pre>
+**Hello**,
+
+_world_.
+</pre>
+</td></tr></table>
+<<<
+<table><tr><td>
+<pre>
+**Hello**,
+<p><em>world</em>.
+</pre></p>
+</td></tr></table>
+>>> HTML blocks - 149
+<table>
+ <tr>
+ <td>
+ hi
+ </td>
+ </tr>
+</table>
+
+okay.
+<<<
+<table>
+ <tr>
+ <td>
+ hi
+ </td>
+ </tr>
+</table>
+<p>okay.</p>
+>>> HTML blocks - 150
+ <div>
+ *hello*
+ <foo><a>
+<<<
+ <div>
+ *hello*
+ <foo><a>
+>>> HTML blocks - 151
+</div>
+*foo*
+<<<
+</div>
+*foo*
+>>> HTML blocks - 152
+<DIV CLASS="foo">
+
+*Markdown*
+
+</DIV>
+<<<
+<DIV CLASS="foo">
+<p><em>Markdown</em></p>
+</DIV>
+>>> HTML blocks - 153
+<div id="foo"
+ class="bar">
+</div>
+<<<
+<div id="foo"
+ class="bar">
+</div>
+>>> HTML blocks - 154
+<div id="foo" class="bar
+ baz">
+</div>
+<<<
+<div id="foo" class="bar
+ baz">
+</div>
+>>> HTML blocks - 155
+<div>
+*foo*
+
+*bar*
+<<<
+<div>
+*foo*
+<p><em>bar</em></p>
+>>> HTML blocks - 156
+<div id="foo"
+*hi*
+<<<
+<div id="foo"
+*hi*
+>>> HTML blocks - 157
+<div class
+foo
+<<<
+<div class
+foo
+>>> HTML blocks - 158
+<div *???-&&&-<---
+*foo*
+<<<
+<div *???-&&&-<---
+*foo*
+>>> HTML blocks - 159
+<div><a href="bar">*foo*</a></div>
+<<<
+<div><a href="bar">*foo*</a></div>
+>>> HTML blocks - 160
+<table><tr><td>
+foo
+</td></tr></table>
+<<<
+<table><tr><td>
+foo
+</td></tr></table>
+>>> HTML blocks - 161
+<div></div>
+``` c
+int x = 33;
+```
+<<<
+<div></div>
+``` c
+int x = 33;
+```
+>>> HTML blocks - 162
+<a href="foo">
+*bar*
+</a>
+<<<
+<a href="foo">
+*bar*
+</a>
+>>> HTML blocks - 163
+<Warning>
+*bar*
+</Warning>
+<<<
+<Warning>
+*bar*
+</Warning>
+>>> HTML blocks - 164
+<i class="foo">
+*bar*
+</i>
+<<<
+<i class="foo">
+*bar*
+</i>
+>>> HTML blocks - 165
+</ins>
+*bar*
+<<<
+</ins>
+*bar*
+>>> HTML blocks - 166
+<del>
+*foo*
+</del>
+<<<
+<del>
+*foo*
+</del>
+>>> HTML blocks - 167
+<del>
+
+*foo*
+
+</del>
+<<<
+<del>
+<p><em>foo</em></p>
+</del>
+>>> HTML blocks - 168
+<del>*foo*</del>
+<<<
+<p><del><em>foo</em></del></p>
+>>> HTML blocks - 169
+<pre language="haskell"><code>
+import Text.HTML.TagSoup
+
+main :: IO ()
+main = print $ parseTags tags
+</code></pre>
+okay
+<<<
+<pre language="haskell"><code>
+import Text.HTML.TagSoup
+
+main :: IO ()
+main = print $ parseTags tags
+</code></pre>
+<p>okay</p>
+>>> HTML blocks - 170
+<script type="text/javascript">
+// JavaScript example
+
+document.getElementById("demo").innerHTML = "Hello JavaScript!";
+</script>
+okay
+<<<
+<script type="text/javascript">
+// JavaScript example
+
+document.getElementById("demo").innerHTML = "Hello JavaScript!";
+</script>
+<p>okay</p>
+>>> HTML blocks - 171
+<textarea>
+
+*foo*
+
+_bar_
+
+</textarea>
+<<<
+<textarea>
+
+*foo*
+
+_bar_
+
+</textarea>
+>>> HTML blocks - 172
+<style
+ type="text/css">
+h1 {color:red;}
+
+p {color:blue;}
+</style>
+okay
+<<<
+<style
+ type="text/css">
+h1 {color:red;}
+
+p {color:blue;}
+</style>
+<p>okay</p>
+>>> HTML blocks - 173
+<style
+ type="text/css">
+
+foo
+<<<
+<style
+ type="text/css">
+
+foo
+>>> HTML blocks - 174
+> <div>
+> foo
+
+bar
+<<<
+<blockquote>
+<div>
+foo
+</blockquote>
+<p>bar</p>
+>>> HTML blocks - 175
+- <div>
+- foo
+<<<
+<ul>
+<li>
+<div>
+</li>
+<li>foo</li>
+</ul>
+>>> HTML blocks - 176
+<style>p{color:red;}</style>
+*foo*
+<<<
+<style>p{color:red;}</style>
+<p><em>foo</em></p>
+>>> HTML blocks - 177
+<!-- foo -->*bar*
+*baz*
+<<<
+<!-- foo -->*bar*
+<p><em>baz</em></p>
+>>> HTML blocks - 178
+<script>
+foo
+</script>1. *bar*
+<<<
+<script>
+foo
+</script>1. *bar*
+>>> HTML blocks - 179
+<!-- Foo
+
+bar
+ baz -->
+okay
+<<<
+<!-- Foo
+
+bar
+ baz -->
+<p>okay</p>
+>>> HTML blocks - 180
+<?php
+
+ echo '>';
+
+?>
+okay
+<<<
+<?php
+
+ echo '>';
+
+?>
+<p>okay</p>
+>>> HTML blocks - 181
+<!DOCTYPE html>
+<<<
+<!DOCTYPE html>
+>>> HTML blocks - 182
+<![CDATA[
+function matchwo(a,b)
+{
+ if (a < b && a < 0) then {
+ return 1;
+
+ } else {
+
+ return 0;
+ }
+}
+]]>
+okay
+<<<
+<![CDATA[
+function matchwo(a,b)
+{
+ if (a < b && a < 0) then {
+ return 1;
+
+ } else {
+
+ return 0;
+ }
+}
+]]>
+<p>okay</p>
+>>> HTML blocks - 183
+ <!-- foo -->
+
+ <!-- foo -->
+<<<
+ <!-- foo -->
+<pre><code><!-- foo -->
+</code></pre>
+>>> HTML blocks - 184
+ <div>
+
+ <div>
+<<<
+ <div>
+<pre><code><div>
+</code></pre>
+>>> HTML blocks - 185
+Foo
+<div>
+bar
+</div>
+<<<
+<p>Foo</p>
+<div>
+bar
+</div>
+>>> HTML blocks - 186
+<div>
+bar
+</div>
+*foo*
+<<<
+<div>
+bar
+</div>
+*foo*
+>>> HTML blocks - 187
+Foo
+<a href="bar">
+baz
+<<<
+<p>Foo
+<a href="bar">
+baz</p>
+>>> HTML blocks - 188
+<div>
+
+*Emphasized* text.
+
+</div>
+<<<
+<div>
+<p><em>Emphasized</em> text.</p>
+</div>
+>>> HTML blocks - 189
+<div>
+*Emphasized* text.
+</div>
+<<<
+<div>
+*Emphasized* text.
+</div>
+>>> HTML blocks - 190
+<table>
+
+<tr>
+
+<td>
+Hi
+</td>
+
+</tr>
+
+</table>
+<<<
+<table>
+<tr>
+<td>
+Hi
+</td>
+</tr>
+</table>
+>>> HTML blocks - 191
+<table>
+
+ <tr>
+
+ <td>
+ Hi
+ </td>
+
+ </tr>
+
+</table>
+<<<
+<table>
+ <tr>
+<pre><code><td>
+ Hi
+</td>
+</code></pre>
+ </tr>
+</table>
diff --git a/pkgs/markdown/test/common_mark/images.unit b/pkgs/markdown/test/common_mark/images.unit
new file mode 100644
index 0000000..ae42aa2
--- /dev/null
+++ b/pkgs/markdown/test/common_mark/images.unit
@@ -0,0 +1,121 @@
+>>> Images - 572
+
+<<<
+<p><img src="/url" alt="foo" title="title" /></p>
+>>> Images - 573
+![foo *bar*]
+
+[foo *bar*]: train.jpg "train & tracks"
+<<<
+<p><img src="train.jpg" alt="foo bar" title="train & tracks" /></p>
+>>> Images - 574
+](/url2)
+<<<
+<p><img src="/url2" alt="foo bar" /></p>
+>>> Images - 575
+](/url2)
+<<<
+<p><img src="/url2" alt="foo bar" /></p>
+>>> Images - 576
+![foo *bar*][]
+
+[foo *bar*]: train.jpg "train & tracks"
+<<<
+<p><img src="train.jpg" alt="foo bar" title="train & tracks" /></p>
+>>> Images - 577
+![foo *bar*][foobar]
+
+[FOOBAR]: train.jpg "train & tracks"
+<<<
+<p><img src="train.jpg" alt="foo bar" title="train & tracks" /></p>
+>>> Images - 578
+
+<<<
+<p><img src="train.jpg" alt="foo" /></p>
+>>> Images - 579
+My 
+<<<
+<p>My <img src="/path/to/train.jpg" alt="foo bar" title="title" /></p>
+>>> Images - 580
+
+<<<
+<p><img src="url" alt="foo" /></p>
+>>> Images - 581
+
+<<<
+<p><img src="/url" alt="" /></p>
+>>> Images - 582
+![foo][bar]
+
+[bar]: /url
+<<<
+<p><img src="/url" alt="foo" /></p>
+>>> Images - 583
+![foo][bar]
+
+[BAR]: /url
+<<<
+<p><img src="/url" alt="foo" /></p>
+>>> Images - 584
+![foo][]
+
+[foo]: /url "title"
+<<<
+<p><img src="/url" alt="foo" title="title" /></p>
+>>> Images - 585
+![*foo* bar][]
+
+[*foo* bar]: /url "title"
+<<<
+<p><img src="/url" alt="foo bar" title="title" /></p>
+>>> Images - 586
+![Foo][]
+
+[foo]: /url "title"
+<<<
+<p><img src="/url" alt="Foo" title="title" /></p>
+>>> Images - 587
+![foo]
+[]
+
+[foo]: /url "title"
+<<<
+<p><img src="/url" alt="foo" title="title" />
+[]</p>
+>>> Images - 588
+![foo]
+
+[foo]: /url "title"
+<<<
+<p><img src="/url" alt="foo" title="title" /></p>
+>>> Images - 589
+![*foo* bar]
+
+[*foo* bar]: /url "title"
+<<<
+<p><img src="/url" alt="foo bar" title="title" /></p>
+>>> Images - 590
+![[foo]]
+
+[[foo]]: /url "title"
+<<<
+<p>![[foo]]</p>
+<p>[[foo]]: /url "title"</p>
+>>> Images - 591
+![Foo]
+
+[foo]: /url "title"
+<<<
+<p><img src="/url" alt="Foo" title="title" /></p>
+>>> Images - 592
+!\[foo]
+
+[foo]: /url "title"
+<<<
+<p>![foo]</p>
+>>> Images - 593
+\![foo]
+
+[foo]: /url "title"
+<<<
+<p>!<a href="/url" title="title">foo</a></p>
diff --git a/pkgs/markdown/test/common_mark/indented_code_blocks.unit b/pkgs/markdown/test/common_mark/indented_code_blocks.unit
new file mode 100644
index 0000000..ce142fe
--- /dev/null
+++ b/pkgs/markdown/test/common_mark/indented_code_blocks.unit
@@ -0,0 +1,118 @@
+>>> Indented code blocks - 107
+ a simple
+ indented code block
+<<<
+<pre><code>a simple
+ indented code block
+</code></pre>
+>>> Indented code blocks - 108
+ - foo
+
+ bar
+<<<
+<ul>
+<li>
+<p>foo</p>
+<p>bar</p>
+</li>
+</ul>
+>>> Indented code blocks - 109
+1. foo
+
+ - bar
+<<<
+<ol>
+<li>
+<p>foo</p>
+<ul>
+<li>bar</li>
+</ul>
+</li>
+</ol>
+>>> Indented code blocks - 110
+ <a/>
+ *hi*
+
+ - one
+<<<
+<pre><code><a/>
+*hi*
+
+- one
+</code></pre>
+>>> Indented code blocks - 111
+ chunk1
+
+ chunk2
+
+
+
+ chunk3
+<<<
+<pre><code>chunk1
+
+chunk2
+
+
+
+chunk3
+</code></pre>
+>>> Indented code blocks - 112
+ chunk1
+
+ chunk2
+<<<
+<pre><code>chunk1
+
+ chunk2
+</code></pre>
+>>> Indented code blocks - 113
+Foo
+ bar
+
+<<<
+<p>Foo
+bar</p>
+>>> Indented code blocks - 114
+ foo
+bar
+<<<
+<pre><code>foo
+</code></pre>
+<p>bar</p>
+>>> Indented code blocks - 115
+# Heading
+ foo
+Heading
+------
+ foo
+----
+<<<
+<h1>Heading</h1>
+<pre><code>foo
+</code></pre>
+<h2>Heading</h2>
+<pre><code>foo
+</code></pre>
+<hr />
+>>> Indented code blocks - 116
+ foo
+ bar
+<<<
+<pre><code> foo
+bar
+</code></pre>
+>>> Indented code blocks - 117
+
+
+ foo
+
+
+<<<
+<pre><code>foo
+</code></pre>
+>>> Indented code blocks - 118
+ foo
+<<<
+<pre><code>foo
+</code></pre>
diff --git a/pkgs/markdown/test/common_mark/inlines.unit b/pkgs/markdown/test/common_mark/inlines.unit
new file mode 100644
index 0000000..3d2eebe
--- /dev/null
+++ b/pkgs/markdown/test/common_mark/inlines.unit
@@ -0,0 +1,4 @@
+>>> Inlines - 327
+`hi`lo`
+<<<
+<p><code>hi</code>lo`</p>
diff --git a/pkgs/markdown/test/common_mark/link_reference_definitions.unit b/pkgs/markdown/test/common_mark/link_reference_definitions.unit
new file mode 100644
index 0000000..0677259
--- /dev/null
+++ b/pkgs/markdown/test/common_mark/link_reference_definitions.unit
@@ -0,0 +1,202 @@
+>>> Link reference definitions - 192
+[foo]: /url "title"
+
+[foo]
+<<<
+<p><a href="/url" title="title">foo</a></p>
+>>> Link reference definitions - 193
+ [foo]:
+ /url
+ 'the title'
+
+[foo]
+<<<
+<p><a href="/url" title="the title">foo</a></p>
+>>> Link reference definitions - 194
+[Foo*bar\]]:my_(url) 'title (with parens)'
+
+[Foo*bar\]]
+<<<
+<p><a href="my_(url)" title="title (with parens)">Foo*bar]</a></p>
+>>> Link reference definitions - 195
+[Foo bar]:
+<my url>
+'title'
+
+[Foo bar]
+<<<
+<p><a href="my%20url" title="title">Foo bar</a></p>
+>>> Link reference definitions - 196
+[foo]: /url '
+title
+line1
+line2
+'
+
+[foo]
+<<<
+<p><a href="/url" title="
+title
+line1
+line2
+">foo</a></p>
+>>> Link reference definitions - 197
+[foo]: /url 'title
+
+with blank line'
+
+[foo]
+<<<
+<p>[foo]: /url 'title</p>
+<p>with blank line'</p>
+<p>[foo]</p>
+>>> Link reference definitions - 198
+[foo]:
+/url
+
+[foo]
+<<<
+<p><a href="/url">foo</a></p>
+>>> Link reference definitions - 199
+[foo]:
+
+[foo]
+<<<
+<p>[foo]:</p>
+<p>[foo]</p>
+>>> Link reference definitions - 200
+[foo]: <>
+
+[foo]
+<<<
+<p><a href="">foo</a></p>
+>>> Link reference definitions - 201
+[foo]: <bar>(baz)
+
+[foo]
+<<<
+<p>[foo]: <bar>(baz)</p>
+<p>[foo]</p>
+>>> Link reference definitions - 202
+[foo]: /url\bar\*baz "foo\"bar\baz"
+
+[foo]
+<<<
+<p><a href="/url%5Cbar*baz" title="foo"bar\baz">foo</a></p>
+>>> Link reference definitions - 203
+[foo]
+
+[foo]: url
+<<<
+<p><a href="url">foo</a></p>
+>>> Link reference definitions - 204
+[foo]
+
+[foo]: first
+[foo]: second
+<<<
+<p><a href="first">foo</a></p>
+>>> Link reference definitions - 205
+[FOO]: /url
+
+[Foo]
+<<<
+<p><a href="/url">Foo</a></p>
+>>> Link reference definitions - 206
+[ΑΓΩ]: /φου
+
+[αγω]
+<<<
+<p><a href="/%CF%86%CE%BF%CF%85">αγω</a></p>
+>>> Link reference definitions - 207
+[foo]: /url
+<<<
+
+>>> Link reference definitions - 208
+[
+foo
+]: /url
+bar
+<<<
+<p>bar</p>
+>>> Link reference definitions - 209
+[foo]: /url "title" ok
+<<<
+<p>[foo]: /url "title" ok</p>
+>>> Link reference definitions - 210
+[foo]: /url
+"title" ok
+<<<
+<p>"title" ok</p>
+>>> Link reference definitions - 211
+ [foo]: /url "title"
+
+[foo]
+<<<
+<pre><code>[foo]: /url "title"
+</code></pre>
+<p>[foo]</p>
+>>> Link reference definitions - 212
+```
+[foo]: /url
+```
+
+[foo]
+<<<
+<pre><code>[foo]: /url
+</code></pre>
+<p>[foo]</p>
+>>> Link reference definitions - 213
+Foo
+[bar]: /baz
+
+[bar]
+<<<
+<p>Foo
+[bar]: /baz</p>
+<p>[bar]</p>
+>>> Link reference definitions - 214
+# [Foo]
+[foo]: /url
+> bar
+<<<
+<h1><a href="/url">Foo</a></h1>
+<blockquote>
+<p>bar</p>
+</blockquote>
+>>> Link reference definitions - 215
+[foo]: /url
+bar
+===
+[foo]
+<<<
+<h1>bar</h1>
+<p><a href="/url">foo</a></p>
+>>> Link reference definitions - 216
+[foo]: /url
+===
+[foo]
+<<<
+<p>===
+<a href="/url">foo</a></p>
+>>> Link reference definitions - 217
+[foo]: /foo-url "foo"
+[bar]: /bar-url
+ "bar"
+[baz]: /baz-url
+
+[foo],
+[bar],
+[baz]
+<<<
+<p><a href="/foo-url" title="foo">foo</a>,
+<a href="/bar-url" title="bar">bar</a>,
+<a href="/baz-url">baz</a></p>
+>>> Link reference definitions - 218
+[foo]
+
+> [foo]: /url
+<<<
+<p><a href="/url">foo</a></p>
+<blockquote>
+</blockquote>
diff --git a/pkgs/markdown/test/common_mark/links.unit b/pkgs/markdown/test/common_mark/links.unit
new file mode 100644
index 0000000..ab399f5
--- /dev/null
+++ b/pkgs/markdown/test/common_mark/links.unit
@@ -0,0 +1,488 @@
+>>> Links - 482
+[link](/uri "title")
+<<<
+<p><a href="/uri" title="title">link</a></p>
+>>> Links - 483
+[link](/uri)
+<<<
+<p><a href="/uri">link</a></p>
+>>> Links - 484
+[](./target.md)
+<<<
+<p><a href="./target.md"></a></p>
+>>> Links - 485
+[link]()
+<<<
+<p><a href="">link</a></p>
+>>> Links - 486
+[link](<>)
+<<<
+<p><a href="">link</a></p>
+>>> Links - 487
+[]()
+<<<
+<p><a href=""></a></p>
+>>> Links - 488
+[link](/my uri)
+<<<
+<p>[link](/my uri)</p>
+>>> Links - 489
+[link](</my uri>)
+<<<
+<p><a href="/my%20uri">link</a></p>
+>>> Links - 490
+[link](foo
+bar)
+<<<
+<p>[link](foo
+bar)</p>
+>>> Links - 491
+[link](<foo
+bar>)
+<<<
+<p>[link](<foo
+bar>)</p>
+>>> Links - 492
+[a](<b)c>)
+<<<
+<p><a href="b)c">a</a></p>
+>>> Links - 493
+[link](<foo\>)
+<<<
+<p>[link](<foo>)</p>
+>>> Links - 494
+[a](<b)c
+[a](<b)c>
+[a](<b>c)
+<<<
+<p>[a](<b)c
+[a](<b)c>
+[a](<b>c)</p>
+>>> Links - 495
+[link](\(foo\))
+<<<
+<p><a href="(foo)">link</a></p>
+>>> Links - 496
+[link](foo(and(bar)))
+<<<
+<p><a href="foo(and(bar))">link</a></p>
+>>> Links - 497
+[link](foo(and(bar))
+<<<
+<p>[link](foo(and(bar))</p>
+>>> Links - 498
+[link](foo\(and\(bar\))
+<<<
+<p><a href="foo(and(bar)">link</a></p>
+>>> Links - 499
+[link](<foo(and(bar)>)
+<<<
+<p><a href="foo(and(bar)">link</a></p>
+>>> Links - 500
+[link](foo\)\:)
+<<<
+<p><a href="foo):">link</a></p>
+>>> Links - 501
+[link](#fragment)
+
+[link](https://example.com#fragment)
+
+[link](https://example.com?foo=3#frag)
+<<<
+<p><a href="#fragment">link</a></p>
+<p><a href="https://example.com#fragment">link</a></p>
+<p><a href="https://example.com?foo=3#frag">link</a></p>
+>>> Links - 502
+[link](foo\bar)
+<<<
+<p><a href="foo%5Cbar">link</a></p>
+>>> Links - 503
+[link](foo%20bä)
+<<<
+<p><a href="foo%20b%C3%A4">link</a></p>
+>>> Links - 504
+[link]("title")
+<<<
+<p><a href="%22title%22">link</a></p>
+>>> Links - 505
+[link](/url "title")
+[link](/url 'title')
+[link](/url (title))
+<<<
+<p><a href="/url" title="title">link</a>
+<a href="/url" title="title">link</a>
+<a href="/url" title="title">link</a></p>
+>>> Links - 506
+[link](/url "title \""")
+<<<
+<p><a href="/url" title="title """>link</a></p>
+>>> Links - 507
+[link](/url "title")
+<<<
+<p><a href="/url%C2%A0%22title%22">link</a></p>
+>>> Links - 508
+[link](/url "title "and" title")
+<<<
+<p>[link](/url "title "and" title")</p>
+>>> Links - 509
+[link](/url 'title "and" title')
+<<<
+<p><a href="/url" title="title "and" title">link</a></p>
+>>> Links - 510
+[link]( /uri
+ "title" )
+<<<
+<p><a href="/uri" title="title">link</a></p>
+>>> Links - 511
+[link] (/uri)
+<<<
+<p>[link] (/uri)</p>
+>>> Links - 512
+[link [foo [bar]]](/uri)
+<<<
+<p><a href="/uri">link [foo [bar]]</a></p>
+>>> Links - 513
+[link] bar](/uri)
+<<<
+<p>[link] bar](/uri)</p>
+>>> Links - 514
+[link [bar](/uri)
+<<<
+<p>[link <a href="/uri">bar</a></p>
+>>> Links - 515
+[link \[bar](/uri)
+<<<
+<p><a href="/uri">link [bar</a></p>
+>>> Links - 516
+[link *foo **bar** `#`*](/uri)
+<<<
+<p><a href="/uri">link <em>foo <strong>bar</strong> <code>#</code></em></a></p>
+>>> Links - 517
+[](/uri)
+<<<
+<p><a href="/uri"><img src="moon.jpg" alt="moon" /></a></p>
+>>> Links - 518
+[foo [bar](/uri)](/uri)
+<<<
+<p>[foo <a href="/uri">bar</a>](/uri)</p>
+>>> Links - 519
+[foo *[bar [baz](/uri)](/uri)*](/uri)
+<<<
+<p>[foo <em>[bar <a href="/uri">baz</a>](/uri)</em>](/uri)</p>
+>>> Links - 520
+](uri2)](uri3)
+<<<
+<p><img src="uri3" alt="[foo](uri2)" /></p>
+>>> Links - 521
+*[foo*](/uri)
+<<<
+<p>*<a href="/uri">foo*</a></p>
+>>> Links - 522
+[foo *bar](baz*)
+<<<
+<p><a href="baz*">foo *bar</a></p>
+>>> Links - 523
+*foo [bar* baz]
+<<<
+<p><em>foo [bar</em> baz]</p>
+>>> Links - 524
+[foo <bar attr="](baz)">
+<<<
+<p>[foo <bar attr="](baz)"></p>
+>>> Links - 525
+[foo`](/uri)`
+<<<
+<p>[foo<code>](/uri)</code></p>
+>>> Links - 526
+[foo<https://example.com/?search=](uri)>
+<<<
+<p>[foo<a href="https://example.com/?search=%5D(uri)">https://example.com/?search=](uri)</a></p>
+>>> Links - 527
+[foo][bar]
+
+[bar]: /url "title"
+<<<
+<p><a href="/url" title="title">foo</a></p>
+>>> Links - 528
+[link [foo [bar]]][ref]
+
+[ref]: /uri
+<<<
+<p><a href="/uri">link [foo [bar]]</a></p>
+>>> Links - 529
+[link \[bar][ref]
+
+[ref]: /uri
+<<<
+<p><a href="/uri">link [bar</a></p>
+>>> Links - 530
+[link *foo **bar** `#`*][ref]
+
+[ref]: /uri
+<<<
+<p><a href="/uri">link <em>foo <strong>bar</strong> <code>#</code></em></a></p>
+>>> Links - 531
+[][ref]
+
+[ref]: /uri
+<<<
+<p><a href="/uri"><img src="moon.jpg" alt="moon" /></a></p>
+>>> Links - 532
+[foo [bar](/uri)][ref]
+
+[ref]: /uri
+<<<
+<p>[foo <a href="/uri">bar</a>]<a href="/uri">ref</a></p>
+>>> Links - 533
+[foo *bar [baz][ref]*][ref]
+
+[ref]: /uri
+<<<
+<p>[foo <em>bar <a href="/uri">baz</a></em>]<a href="/uri">ref</a></p>
+>>> Links - 534
+*[foo*][ref]
+
+[ref]: /uri
+<<<
+<p>*<a href="/uri">foo*</a></p>
+>>> Links - 535
+[foo *bar][ref]*
+
+[ref]: /uri
+<<<
+<p><a href="/uri">foo *bar</a>*</p>
+>>> Links - 536
+[foo <bar attr="][ref]">
+
+[ref]: /uri
+<<<
+<p>[foo <bar attr="][ref]"></p>
+>>> Links - 537
+[foo`][ref]`
+
+[ref]: /uri
+<<<
+<p>[foo<code>][ref]</code></p>
+>>> Links - 538
+[foo<https://example.com/?search=][ref]>
+
+[ref]: /uri
+<<<
+<p>[foo<a href="https://example.com/?search=%5D%5Bref%5D">https://example.com/?search=][ref]</a></p>
+>>> Links - 539
+[foo][BaR]
+
+[bar]: /url "title"
+<<<
+<p><a href="/url" title="title">foo</a></p>
+>>> Links - 540
+[ẞ]
+
+[SS]: /url
+<<<
+<p><a href="/url">ẞ</a></p>
+>>> Links - 541
+[Foo
+ bar]: /url
+
+[Baz][Foo bar]
+<<<
+<p><a href="/url">Baz</a></p>
+>>> Links - 542
+[foo] [bar]
+
+[bar]: /url "title"
+<<<
+<p>[foo] <a href="/url" title="title">bar</a></p>
+>>> Links - 543
+[foo]
+[bar]
+
+[bar]: /url "title"
+<<<
+<p>[foo]
+<a href="/url" title="title">bar</a></p>
+>>> Links - 544
+[foo]: /url1
+
+[foo]: /url2
+
+[bar][foo]
+<<<
+<p><a href="/url1">bar</a></p>
+>>> Links - 545
+[bar][foo\!]
+
+[foo!]: /url
+<<<
+<p>[bar][foo!]</p>
+>>> Links - 546
+[foo][ref[]
+
+[ref[]: /uri
+<<<
+<p>[foo][ref[]</p>
+<p>[ref[]: /uri</p>
+>>> Links - 547
+[foo][ref[bar]]
+
+[ref[bar]]: /uri
+<<<
+<p>[foo][ref[bar]]</p>
+<p>[ref[bar]]: /uri</p>
+>>> Links - 548
+[[[foo]]]
+
+[[[foo]]]: /url
+<<<
+<p>[[[foo]]]</p>
+<p>[[[foo]]]: /url</p>
+>>> Links - 549
+[foo][ref\[]
+
+[ref\[]: /uri
+<<<
+<p><a href="/uri">foo</a></p>
+>>> Links - 550
+[bar\\]: /uri
+
+[bar\\]
+<<<
+<p><a href="/uri">bar\</a></p>
+>>> Links - 551
+[]
+
+[]: /uri
+<<<
+<p>[]</p>
+<p>[]: /uri</p>
+>>> Links - 552
+[
+ ]
+
+[
+ ]: /uri
+<<<
+<p>[
+]</p>
+<p>[
+]: /uri</p>
+>>> Links - 553
+[foo][]
+
+[foo]: /url "title"
+<<<
+<p><a href="/url" title="title">foo</a></p>
+>>> Links - 554
+[*foo* bar][]
+
+[*foo* bar]: /url "title"
+<<<
+<p><a href="/url" title="title"><em>foo</em> bar</a></p>
+>>> Links - 555
+[Foo][]
+
+[foo]: /url "title"
+<<<
+<p><a href="/url" title="title">Foo</a></p>
+>>> Links - 556
+[foo]
+[]
+
+[foo]: /url "title"
+<<<
+<p><a href="/url" title="title">foo</a>
+[]</p>
+>>> Links - 557
+[foo]
+
+[foo]: /url "title"
+<<<
+<p><a href="/url" title="title">foo</a></p>
+>>> Links - 558
+[*foo* bar]
+
+[*foo* bar]: /url "title"
+<<<
+<p><a href="/url" title="title"><em>foo</em> bar</a></p>
+>>> Links - 559
+[[*foo* bar]]
+
+[*foo* bar]: /url "title"
+<<<
+<p>[<a href="/url" title="title"><em>foo</em> bar</a>]</p>
+>>> Links - 560
+[[bar [foo]
+
+[foo]: /url
+<<<
+<p>[[bar <a href="/url">foo</a></p>
+>>> Links - 561
+[Foo]
+
+[foo]: /url "title"
+<<<
+<p><a href="/url" title="title">Foo</a></p>
+>>> Links - 562
+[foo] bar
+
+[foo]: /url
+<<<
+<p><a href="/url">foo</a> bar</p>
+>>> Links - 563
+\[foo]
+
+[foo]: /url "title"
+<<<
+<p>[foo]</p>
+>>> Links - 564
+[foo*]: /url
+
+*[foo*]
+<<<
+<p>*<a href="/url">foo*</a></p>
+>>> Links - 565
+[foo][bar]
+
+[foo]: /url1
+[bar]: /url2
+<<<
+<p><a href="/url2">foo</a></p>
+>>> Links - 566
+[foo][]
+
+[foo]: /url1
+<<<
+<p><a href="/url1">foo</a></p>
+>>> Links - 567
+[foo]()
+
+[foo]: /url1
+<<<
+<p><a href="">foo</a></p>
+>>> Links - 568
+[foo](not a link)
+
+[foo]: /url1
+<<<
+<p><a href="/url1">foo</a>(not a link)</p>
+>>> Links - 569
+[foo][bar][baz]
+
+[baz]: /url
+<<<
+<p>[foo]<a href="/url">bar</a></p>
+>>> Links - 570
+[foo][bar][baz]
+
+[baz]: /url1
+[bar]: /url2
+<<<
+<p><a href="/url2">foo</a><a href="/url1">baz</a></p>
+>>> Links - 571
+[foo][bar][baz]
+
+[baz]: /url1
+[foo]: /url2
+<<<
+<p>[foo]<a href="/url1">bar</a></p>
diff --git a/pkgs/markdown/test/common_mark/list_items.unit b/pkgs/markdown/test/common_mark/list_items.unit
new file mode 100644
index 0000000..46162a9
--- /dev/null
+++ b/pkgs/markdown/test/common_mark/list_items.unit
@@ -0,0 +1,586 @@
+>>> List items - 253
+A paragraph
+with two lines.
+
+ indented code
+
+> A block quote.
+<<<
+<p>A paragraph
+with two lines.</p>
+<pre><code>indented code
+</code></pre>
+<blockquote>
+<p>A block quote.</p>
+</blockquote>
+>>> List items - 254
+1. A paragraph
+ with two lines.
+
+ indented code
+
+ > A block quote.
+<<<
+<ol>
+<li>
+<p>A paragraph
+with two lines.</p>
+<pre><code>indented code
+</code></pre>
+<blockquote>
+<p>A block quote.</p>
+</blockquote>
+</li>
+</ol>
+>>> List items - 255
+- one
+
+ two
+<<<
+<ul>
+<li>one</li>
+</ul>
+<p>two</p>
+>>> List items - 256
+- one
+
+ two
+<<<
+<ul>
+<li>
+<p>one</p>
+<p>two</p>
+</li>
+</ul>
+>>> List items - 257
+ - one
+
+ two
+<<<
+<ul>
+<li>one</li>
+</ul>
+<pre><code> two
+</code></pre>
+>>> List items - 258
+ - one
+
+ two
+<<<
+<ul>
+<li>
+<p>one</p>
+<p>two</p>
+</li>
+</ul>
+>>> List items - 259
+ > > 1. one
+>>
+>> two
+<<<
+<blockquote>
+<blockquote>
+<ol>
+<li>
+<p>one</p>
+<p>two</p>
+</li>
+</ol>
+</blockquote>
+</blockquote>
+>>> List items - 260
+>>- one
+>>
+ > > two
+<<<
+<blockquote>
+<blockquote>
+<ul>
+<li>one</li>
+</ul>
+<p>two</p>
+</blockquote>
+</blockquote>
+>>> List items - 261
+-one
+
+2.two
+<<<
+<p>-one</p>
+<p>2.two</p>
+>>> List items - 262
+- foo
+
+
+ bar
+<<<
+<ul>
+<li>
+<p>foo</p>
+<p>bar</p>
+</li>
+</ul>
+>>> List items - 263
+1. foo
+
+ ```
+ bar
+ ```
+
+ baz
+
+ > bam
+<<<
+<ol>
+<li>
+<p>foo</p>
+<pre><code>bar
+</code></pre>
+<p>baz</p>
+<blockquote>
+<p>bam</p>
+</blockquote>
+</li>
+</ol>
+>>> List items - 264
+- Foo
+
+ bar
+
+
+ baz
+<<<
+<ul>
+<li>
+<p>Foo</p>
+<pre><code>bar
+
+
+baz
+</code></pre>
+</li>
+</ul>
+>>> List items - 265
+123456789. ok
+<<<
+<ol start="123456789">
+<li>ok</li>
+</ol>
+>>> List items - 266
+1234567890. not ok
+<<<
+<p>1234567890. not ok</p>
+>>> List items - 267
+0. ok
+<<<
+<ol start="0">
+<li>ok</li>
+</ol>
+>>> List items - 268
+003. ok
+<<<
+<ol start="3">
+<li>ok</li>
+</ol>
+>>> List items - 269
+-1. not ok
+<<<
+<p>-1. not ok</p>
+>>> List items - 270
+- foo
+
+ bar
+<<<
+<ul>
+<li>
+<p>foo</p>
+<pre><code>bar
+</code></pre>
+</li>
+</ul>
+>>> List items - 271
+ 10. foo
+
+ bar
+<<<
+<ol start="10">
+<li>
+<p>foo</p>
+<pre><code>bar
+</code></pre>
+</li>
+</ol>
+>>> List items - 272
+ indented code
+
+paragraph
+
+ more code
+<<<
+<pre><code>indented code
+</code></pre>
+<p>paragraph</p>
+<pre><code>more code
+</code></pre>
+>>> List items - 273
+1. indented code
+
+ paragraph
+
+ more code
+<<<
+<ol>
+<li>
+<pre><code>indented code
+</code></pre>
+<p>paragraph</p>
+<pre><code>more code
+</code></pre>
+</li>
+</ol>
+>>> List items - 274
+1. indented code
+
+ paragraph
+
+ more code
+<<<
+<ol>
+<li>
+<pre><code> indented code
+</code></pre>
+<p>paragraph</p>
+<pre><code>more code
+</code></pre>
+</li>
+</ol>
+>>> List items - 275
+ foo
+
+bar
+<<<
+<p>foo</p>
+<p>bar</p>
+>>> List items - 276
+- foo
+
+ bar
+<<<
+<ul>
+<li>foo</li>
+</ul>
+<p>bar</p>
+>>> List items - 277
+- foo
+
+ bar
+<<<
+<ul>
+<li>
+<p>foo</p>
+<p>bar</p>
+</li>
+</ul>
+>>> List items - 278
+-
+ foo
+-
+ ```
+ bar
+ ```
+-
+ baz
+<<<
+<ul>
+<li>foo</li>
+<li>
+<pre><code>bar
+</code></pre>
+</li>
+<li>
+<pre><code>baz
+</code></pre>
+</li>
+</ul>
+>>> List items - 279
+-
+ foo
+<<<
+<ul>
+<li>foo</li>
+</ul>
+>>> List items - 280
+-
+
+ foo
+<<<
+<ul>
+<li></li>
+</ul>
+<p>foo</p>
+>>> List items - 281
+- foo
+-
+- bar
+<<<
+<ul>
+<li>foo</li>
+<li></li>
+<li>bar</li>
+</ul>
+>>> List items - 282
+- foo
+-
+- bar
+<<<
+<ul>
+<li>foo</li>
+<li></li>
+<li>bar</li>
+</ul>
+>>> List items - 283
+1. foo
+2.
+3. bar
+<<<
+<ol>
+<li>foo</li>
+<li></li>
+<li>bar</li>
+</ol>
+>>> List items - 284
+*
+<<<
+<ul>
+<li></li>
+</ul>
+>>> List items - 285
+foo
+*
+
+foo
+1.
+<<<
+<p>foo
+*</p>
+<p>foo
+1.</p>
+>>> List items - 286
+ 1. A paragraph
+ with two lines.
+
+ indented code
+
+ > A block quote.
+<<<
+<ol>
+<li>
+<p>A paragraph
+with two lines.</p>
+<pre><code>indented code
+</code></pre>
+<blockquote>
+<p>A block quote.</p>
+</blockquote>
+</li>
+</ol>
+>>> List items - 287
+ 1. A paragraph
+ with two lines.
+
+ indented code
+
+ > A block quote.
+<<<
+<ol>
+<li>
+<p>A paragraph
+with two lines.</p>
+<pre><code>indented code
+</code></pre>
+<blockquote>
+<p>A block quote.</p>
+</blockquote>
+</li>
+</ol>
+>>> List items - 288
+ 1. A paragraph
+ with two lines.
+
+ indented code
+
+ > A block quote.
+<<<
+<ol>
+<li>
+<p>A paragraph
+with two lines.</p>
+<pre><code>indented code
+</code></pre>
+<blockquote>
+<p>A block quote.</p>
+</blockquote>
+</li>
+</ol>
+>>> List items - 289
+ 1. A paragraph
+ with two lines.
+
+ indented code
+
+ > A block quote.
+<<<
+<pre><code>1. A paragraph
+ with two lines.
+
+ indented code
+
+ > A block quote.
+</code></pre>
+>>> List items - 290
+ 1. A paragraph
+with two lines.
+
+ indented code
+
+ > A block quote.
+<<<
+<ol>
+<li>
+<p>A paragraph
+with two lines.</p>
+<pre><code>indented code
+</code></pre>
+<blockquote>
+<p>A block quote.</p>
+</blockquote>
+</li>
+</ol>
+>>> List items - 291
+ 1. A paragraph
+ with two lines.
+<<<
+<ol>
+<li>A paragraph
+with two lines.</li>
+</ol>
+>>> List items - 292
+> 1. > Blockquote
+continued here.
+<<<
+<blockquote>
+<ol>
+<li>
+<blockquote>
+<p>Blockquote
+continued here.</p>
+</blockquote>
+</li>
+</ol>
+</blockquote>
+>>> List items - 293
+> 1. > Blockquote
+> continued here.
+<<<
+<blockquote>
+<ol>
+<li>
+<blockquote>
+<p>Blockquote
+continued here.</p>
+</blockquote>
+</li>
+</ol>
+</blockquote>
+>>> List items - 294
+- foo
+ - bar
+ - baz
+ - boo
+<<<
+<ul>
+<li>foo
+<ul>
+<li>bar
+<ul>
+<li>baz
+<ul>
+<li>boo</li>
+</ul>
+</li>
+</ul>
+</li>
+</ul>
+</li>
+</ul>
+>>> List items - 295
+- foo
+ - bar
+ - baz
+ - boo
+<<<
+<ul>
+<li>foo</li>
+<li>bar</li>
+<li>baz</li>
+<li>boo</li>
+</ul>
+>>> List items - 296
+10) foo
+ - bar
+<<<
+<ol start="10">
+<li>foo
+<ul>
+<li>bar</li>
+</ul>
+</li>
+</ol>
+>>> List items - 297
+10) foo
+ - bar
+<<<
+<ol start="10">
+<li>foo</li>
+</ol>
+<ul>
+<li>bar</li>
+</ul>
+>>> List items - 298
+- - foo
+<<<
+<ul>
+<li>
+<ul>
+<li>foo</li>
+</ul>
+</li>
+</ul>
+>>> List items - 299
+1. - 2. foo
+<<<
+<ol>
+<li>
+<ul>
+<li>
+<ol start="2">
+<li>foo</li>
+</ol>
+</li>
+</ul>
+</li>
+</ol>
+>>> List items - 300
+- # Foo
+- Bar
+ ---
+ baz
+<<<
+<ul>
+<li>
+<h1>Foo</h1>
+</li>
+<li>
+<h2>Bar</h2>
+baz</li>
+</ul>
diff --git a/pkgs/markdown/test/common_mark/lists.unit b/pkgs/markdown/test/common_mark/lists.unit
new file mode 100644
index 0000000..fce59fe
--- /dev/null
+++ b/pkgs/markdown/test/common_mark/lists.unit
@@ -0,0 +1,406 @@
+>>> Lists - 301
+- foo
+- bar
++ baz
+<<<
+<ul>
+<li>foo</li>
+<li>bar</li>
+</ul>
+<ul>
+<li>baz</li>
+</ul>
+>>> Lists - 302
+1. foo
+2. bar
+3) baz
+<<<
+<ol>
+<li>foo</li>
+<li>bar</li>
+</ol>
+<ol start="3">
+<li>baz</li>
+</ol>
+>>> Lists - 303
+Foo
+- bar
+- baz
+<<<
+<p>Foo</p>
+<ul>
+<li>bar</li>
+<li>baz</li>
+</ul>
+>>> Lists - 304
+The number of windows in my house is
+14. The number of doors is 6.
+<<<
+<p>The number of windows in my house is
+14. The number of doors is 6.</p>
+>>> Lists - 305
+The number of windows in my house is
+1. The number of doors is 6.
+<<<
+<p>The number of windows in my house is</p>
+<ol>
+<li>The number of doors is 6.</li>
+</ol>
+>>> Lists - 306
+- foo
+
+- bar
+
+
+- baz
+<<<
+<ul>
+<li>
+<p>foo</p>
+</li>
+<li>
+<p>bar</p>
+</li>
+<li>
+<p>baz</p>
+</li>
+</ul>
+>>> Lists - 307
+- foo
+ - bar
+ - baz
+
+
+ bim
+<<<
+<ul>
+<li>foo
+<ul>
+<li>bar
+<ul>
+<li>
+<p>baz</p>
+<p>bim</p>
+</li>
+</ul>
+</li>
+</ul>
+</li>
+</ul>
+>>> Lists - 308
+- foo
+- bar
+
+<!-- -->
+
+- baz
+- bim
+<<<
+<ul>
+<li>foo</li>
+<li>bar</li>
+</ul>
+<!-- -->
+<ul>
+<li>baz</li>
+<li>bim</li>
+</ul>
+>>> Lists - 309
+- foo
+
+ notcode
+
+- foo
+
+<!-- -->
+
+ code
+<<<
+<ul>
+<li>
+<p>foo</p>
+<p>notcode</p>
+</li>
+<li>
+<p>foo</p>
+</li>
+</ul>
+<!-- -->
+<pre><code>code
+</code></pre>
+>>> Lists - 310
+- a
+ - b
+ - c
+ - d
+ - e
+ - f
+- g
+<<<
+<ul>
+<li>a</li>
+<li>b</li>
+<li>c</li>
+<li>d</li>
+<li>e</li>
+<li>f</li>
+<li>g</li>
+</ul>
+>>> Lists - 311
+1. a
+
+ 2. b
+
+ 3. c
+<<<
+<ol>
+<li>
+<p>a</p>
+</li>
+<li>
+<p>b</p>
+</li>
+<li>
+<p>c</p>
+</li>
+</ol>
+>>> Lists - 312
+- a
+ - b
+ - c
+ - d
+ - e
+<<<
+<ul>
+<li>a</li>
+<li>b</li>
+<li>c</li>
+<li>d
+- e</li>
+</ul>
+>>> Lists - 313
+1. a
+
+ 2. b
+
+ 3. c
+<<<
+<ol>
+<li>
+<p>a</p>
+</li>
+<li>
+<p>b</p>
+</li>
+</ol>
+<pre><code>3. c
+</code></pre>
+>>> Lists - 314
+- a
+- b
+
+- c
+<<<
+<ul>
+<li>
+<p>a</p>
+</li>
+<li>
+<p>b</p>
+</li>
+<li>
+<p>c</p>
+</li>
+</ul>
+>>> Lists - 315
+* a
+*
+
+* c
+<<<
+<ul>
+<li>
+<p>a</p>
+</li>
+<li></li>
+<li>
+<p>c</p>
+</li>
+</ul>
+>>> Lists - 316
+- a
+- b
+
+ c
+- d
+<<<
+<ul>
+<li>
+<p>a</p>
+</li>
+<li>
+<p>b</p>
+<p>c</p>
+</li>
+<li>
+<p>d</p>
+</li>
+</ul>
+>>> Lists - 317
+- a
+- b
+
+ [ref]: /url
+- d
+<<<
+<ul>
+<li>
+<p>a</p>
+</li>
+<li>
+<p>b</p>
+</li>
+<li>
+<p>d</p>
+</li>
+</ul>
+>>> Lists - 318
+- a
+- ```
+ b
+
+
+ ```
+- c
+<<<
+<ul>
+<li>a</li>
+<li>
+<pre><code>b
+
+
+</code></pre>
+</li>
+<li>c</li>
+</ul>
+>>> Lists - 319
+- a
+ - b
+
+ c
+- d
+<<<
+<ul>
+<li>a
+<ul>
+<li>
+<p>b</p>
+<p>c</p>
+</li>
+</ul>
+</li>
+<li>d</li>
+</ul>
+>>> Lists - 320
+* a
+ > b
+ >
+* c
+<<<
+<ul>
+<li>a
+<blockquote>
+<p>b</p>
+</blockquote>
+</li>
+<li>c</li>
+</ul>
+>>> Lists - 321
+- a
+ > b
+ ```
+ c
+ ```
+- d
+<<<
+<ul>
+<li>a
+<blockquote>
+<p>b</p>
+</blockquote>
+<pre><code>c
+</code></pre>
+</li>
+<li>d</li>
+</ul>
+>>> Lists - 322
+- a
+<<<
+<ul>
+<li>a</li>
+</ul>
+>>> Lists - 323
+- a
+ - b
+<<<
+<ul>
+<li>a
+<ul>
+<li>b</li>
+</ul>
+</li>
+</ul>
+>>> Lists - 324
+1. ```
+ foo
+ ```
+
+ bar
+<<<
+<ol>
+<li>
+<pre><code>foo
+</code></pre>
+<p>bar</p>
+</li>
+</ol>
+>>> Lists - 325
+* foo
+ * bar
+
+ baz
+<<<
+<ul>
+<li>
+<p>foo</p>
+<ul>
+<li>bar</li>
+</ul>
+<p>baz</p>
+</li>
+</ul>
+>>> Lists - 326
+- a
+ - b
+ - c
+
+- d
+ - e
+ - f
+<<<
+<ul>
+<li>
+<p>a</p>
+<ul>
+<li>b</li>
+<li>c</li>
+</ul>
+</li>
+<li>
+<p>d</p>
+<ul>
+<li>e</li>
+<li>f</li>
+</ul>
+</li>
+</ul>
diff --git a/pkgs/markdown/test/common_mark/paragraphs.unit b/pkgs/markdown/test/common_mark/paragraphs.unit
new file mode 100644
index 0000000..a03f4a1
--- /dev/null
+++ b/pkgs/markdown/test/common_mark/paragraphs.unit
@@ -0,0 +1,59 @@
+>>> Paragraphs - 219
+aaa
+
+bbb
+<<<
+<p>aaa</p>
+<p>bbb</p>
+>>> Paragraphs - 220
+aaa
+bbb
+
+ccc
+ddd
+<<<
+<p>aaa
+bbb</p>
+<p>ccc
+ddd</p>
+>>> Paragraphs - 221
+aaa
+
+
+bbb
+<<<
+<p>aaa</p>
+<p>bbb</p>
+>>> Paragraphs - 222
+ aaa
+ bbb
+<<<
+<p>aaa
+bbb</p>
+>>> Paragraphs - 223
+aaa
+ bbb
+ ccc
+<<<
+<p>aaa
+bbb
+ccc</p>
+>>> Paragraphs - 224
+ aaa
+bbb
+<<<
+<p>aaa
+bbb</p>
+>>> Paragraphs - 225
+ aaa
+bbb
+<<<
+<pre><code>aaa
+</code></pre>
+<p>bbb</p>
+>>> Paragraphs - 226
+aaa
+bbb
+<<<
+<p>aaa<br />
+bbb</p>
diff --git a/pkgs/markdown/test/common_mark/precedence.unit b/pkgs/markdown/test/common_mark/precedence.unit
new file mode 100644
index 0000000..4359ce9
--- /dev/null
+++ b/pkgs/markdown/test/common_mark/precedence.unit
@@ -0,0 +1,8 @@
+>>> Precedence - 42
+- `one
+- two`
+<<<
+<ul>
+<li>`one</li>
+<li>two`</li>
+</ul>
diff --git a/pkgs/markdown/test/common_mark/raw_html.unit b/pkgs/markdown/test/common_mark/raw_html.unit
new file mode 100644
index 0000000..329f96a
--- /dev/null
+++ b/pkgs/markdown/test/common_mark/raw_html.unit
@@ -0,0 +1,95 @@
+>>> Raw HTML - 613
+<a><bab><c2c>
+<<<
+<p><a><bab><c2c></p>
+>>> Raw HTML - 614
+<a/><b2/>
+<<<
+<p><a/><b2/></p>
+>>> Raw HTML - 615
+<a /><b2
+data="foo" >
+<<<
+<p><a /><b2
+data="foo" ></p>
+>>> Raw HTML - 616
+<a foo="bar" bam = 'baz <em>"</em>'
+_boolean zoop:33=zoop:33 />
+<<<
+<p><a foo="bar" bam = 'baz <em>"</em>'
+_boolean zoop:33=zoop:33 /></p>
+>>> Raw HTML - 617
+Foo <responsive-image src="foo.jpg" />
+<<<
+<p>Foo <responsive-image src="foo.jpg" /></p>
+>>> Raw HTML - 618
+<33> <__>
+<<<
+<p><33> <__></p>
+>>> Raw HTML - 619
+<a h*#ref="hi">
+<<<
+<p><a h*#ref="hi"></p>
+>>> Raw HTML - 620
+<a href="hi'> <a href=hi'>
+<<<
+<p><a href="hi'> <a href=hi'></p>
+>>> Raw HTML - 621
+< a><
+foo><bar/ >
+<foo bar=baz
+bim!bop />
+<<<
+<p>< a><
+foo><bar/ >
+<foo bar=baz
+bim!bop /></p>
+>>> Raw HTML - 622
+<a href='bar'title=title>
+<<<
+<p><a href='bar'title=title></p>
+>>> Raw HTML - 623
+</a></foo >
+<<<
+<p></a></foo ></p>
+>>> Raw HTML - 624
+</a href="foo">
+<<<
+<p></a href="foo"></p>
+>>> Raw HTML - 625
+foo <!-- this is a --
+comment - with hyphens -->
+<<<
+<p>foo <!-- this is a --
+comment - with hyphens --></p>
+>>> Raw HTML - 626
+foo <!--> foo -->
+
+foo <!---> foo -->
+<<<
+<p>foo <!--> foo --></p>
+<p>foo <!---> foo --></p>
+>>> Raw HTML - 627
+foo <?php echo $a; ?>
+<<<
+<p>foo <?php echo $a; ?></p>
+>>> Raw HTML - 628
+foo <!ELEMENT br EMPTY>
+<<<
+<p>foo <!ELEMENT br EMPTY></p>
+>>> Raw HTML - 629
+foo <![CDATA[>&<]]>
+<<<
+<p>foo <![CDATA[>&<]]></p>
+>>> Raw HTML - 630
+foo <a href="ö">
+<<<
+<p>foo <a href="ö"></p>
+>>> Raw HTML - 631
+foo <a href="\*">
+<<<
+<p>foo <a href="\*"></p>
+>>> Raw HTML - 632
+<a href="\"">
+<<<
+<p><a href="""></p>
diff --git a/pkgs/markdown/test/common_mark/setext_headings.unit b/pkgs/markdown/test/common_mark/setext_headings.unit
new file mode 100644
index 0000000..702c72c
--- /dev/null
+++ b/pkgs/markdown/test/common_mark/setext_headings.unit
@@ -0,0 +1,229 @@
+>>> Setext headings - 80
+Foo *bar*
+=========
+
+Foo *bar*
+---------
+<<<
+<h1>Foo <em>bar</em></h1>
+<h2>Foo <em>bar</em></h2>
+>>> Setext headings - 81
+Foo *bar
+baz*
+====
+<<<
+<h1>Foo <em>bar
+baz</em></h1>
+>>> Setext headings - 82
+ Foo *bar
+baz*
+====
+<<<
+<h1> Foo <em>bar
+baz</em></h1>
+>>> Setext headings - 83
+Foo
+-------------------------
+
+Foo
+=
+<<<
+<h2>Foo</h2>
+<h1>Foo</h1>
+>>> Setext headings - 84
+ Foo
+---
+
+ Foo
+-----
+
+ Foo
+ ===
+<<<
+<h2> Foo</h2>
+<h2> Foo</h2>
+<h1> Foo</h1>
+>>> Setext headings - 85
+ Foo
+ ---
+
+ Foo
+---
+<<<
+<pre><code>Foo
+---
+
+Foo
+</code></pre>
+<hr />
+>>> Setext headings - 86
+Foo
+ ----
+<<<
+<h2>Foo</h2>
+>>> Setext headings - 87
+Foo
+ ---
+<<<
+<p>Foo
+---</p>
+>>> Setext headings - 88
+Foo
+= =
+
+Foo
+--- -
+<<<
+<p>Foo
+= =</p>
+<p>Foo</p>
+<hr />
+>>> Setext headings - 89
+Foo
+-----
+<<<
+<h2>Foo</h2>
+>>> Setext headings - 90
+Foo\
+----
+<<<
+<h2>Foo\</h2>
+>>> Setext headings - 91
+`Foo
+----
+`
+
+<a title="a lot
+---
+of dashes"/>
+<<<
+<h2>`Foo</h2>
+<p>`</p>
+<h2><a title="a lot</h2>
+<p>of dashes"/></p>
+>>> Setext headings - 92
+> Foo
+---
+<<<
+<blockquote>
+<p>Foo</p>
+</blockquote>
+<hr />
+>>> Setext headings - 93
+> foo
+bar
+===
+<<<
+<blockquote>
+<p>foo
+bar
+===</p>
+</blockquote>
+>>> Setext headings - 94
+- Foo
+---
+<<<
+<ul>
+<li>Foo</li>
+</ul>
+<hr />
+>>> Setext headings - 95
+Foo
+Bar
+---
+<<<
+<h2>Foo
+Bar</h2>
+>>> Setext headings - 96
+---
+Foo
+---
+Bar
+---
+Baz
+<<<
+<hr />
+<h2>Foo</h2>
+<h2>Bar</h2>
+<p>Baz</p>
+>>> Setext headings - 97
+
+====
+<<<
+<p>====</p>
+>>> Setext headings - 98
+---
+---
+<<<
+<hr />
+<hr />
+>>> Setext headings - 99
+- foo
+-----
+<<<
+<ul>
+<li>foo</li>
+</ul>
+<hr />
+>>> Setext headings - 100
+ foo
+---
+<<<
+<pre><code>foo
+</code></pre>
+<hr />
+>>> Setext headings - 101
+> foo
+-----
+<<<
+<blockquote>
+<p>foo</p>
+</blockquote>
+<hr />
+>>> Setext headings - 102
+\> foo
+------
+<<<
+<h2>> foo</h2>
+>>> Setext headings - 103
+Foo
+
+bar
+---
+baz
+<<<
+<p>Foo</p>
+<h2>bar</h2>
+<p>baz</p>
+>>> Setext headings - 104
+Foo
+bar
+
+---
+
+baz
+<<<
+<p>Foo
+bar</p>
+<hr />
+<p>baz</p>
+>>> Setext headings - 105
+Foo
+bar
+* * *
+baz
+<<<
+<p>Foo
+bar</p>
+<hr />
+<p>baz</p>
+>>> Setext headings - 106
+Foo
+bar
+\---
+baz
+<<<
+<p>Foo
+bar
+---
+baz</p>
diff --git a/pkgs/markdown/test/common_mark/soft_line_breaks.unit b/pkgs/markdown/test/common_mark/soft_line_breaks.unit
new file mode 100644
index 0000000..88e82f2
--- /dev/null
+++ b/pkgs/markdown/test/common_mark/soft_line_breaks.unit
@@ -0,0 +1,12 @@
+>>> Soft line breaks - 648
+foo
+baz
+<<<
+<p>foo
+baz</p>
+>>> Soft line breaks - 649
+foo
+ baz
+<<<
+<p>foo
+baz</p>
diff --git a/pkgs/markdown/test/common_mark/tabs.unit b/pkgs/markdown/test/common_mark/tabs.unit
new file mode 100644
index 0000000..1239481
--- /dev/null
+++ b/pkgs/markdown/test/common_mark/tabs.unit
@@ -0,0 +1,87 @@
+>>> Tabs - 1
+ foo baz bim
+<<<
+<pre><code>foo baz bim
+</code></pre>
+>>> Tabs - 2
+ foo baz bim
+<<<
+<pre><code>foo baz bim
+</code></pre>
+>>> Tabs - 3
+ a a
+ ὐ a
+<<<
+<pre><code>a a
+ὐ a
+</code></pre>
+>>> Tabs - 4
+ - foo
+
+ bar
+<<<
+<ul>
+<li>
+<p>foo</p>
+<p>bar</p>
+</li>
+</ul>
+>>> Tabs - 5
+- foo
+
+ bar
+<<<
+<ul>
+<li>
+<p>foo</p>
+<pre><code> bar
+</code></pre>
+</li>
+</ul>
+>>> Tabs - 6
+> foo
+<<<
+<blockquote>
+<pre><code>foo
+</code></pre>
+</blockquote>
+>>> Tabs - 7
+- foo
+<<<
+<ul>
+<li>
+<pre><code> foo
+</code></pre>
+</li>
+</ul>
+>>> Tabs - 8
+ foo
+ bar
+<<<
+<pre><code>foo
+bar
+</code></pre>
+>>> Tabs - 9
+ - foo
+ - bar
+ - baz
+<<<
+<ul>
+<li>foo
+<ul>
+<li>bar
+<ul>
+<li>baz</li>
+</ul>
+</li>
+</ul>
+</li>
+</ul>
+>>> Tabs - 10
+# Foo
+<<<
+<h1>Foo</h1>
+>>> Tabs - 11
+* * *
+<<<
+<hr />
diff --git a/pkgs/markdown/test/common_mark/textual_content.unit b/pkgs/markdown/test/common_mark/textual_content.unit
new file mode 100644
index 0000000..0069e0f
--- /dev/null
+++ b/pkgs/markdown/test/common_mark/textual_content.unit
@@ -0,0 +1,12 @@
+>>> Textual content - 650
+hello $.;'there
+<<<
+<p>hello $.;'there</p>
+>>> Textual content - 651
+Foo χρῆν
+<<<
+<p>Foo χρῆν</p>
+>>> Textual content - 652
+Multiple spaces
+<<<
+<p>Multiple spaces</p>
diff --git a/pkgs/markdown/test/common_mark/thematic_breaks.unit b/pkgs/markdown/test/common_mark/thematic_breaks.unit
new file mode 100644
index 0000000..b4d490f
--- /dev/null
+++ b/pkgs/markdown/test/common_mark/thematic_breaks.unit
@@ -0,0 +1,126 @@
+>>> Thematic breaks - 43
+***
+---
+___
+<<<
+<hr />
+<hr />
+<hr />
+>>> Thematic breaks - 44
++++
+<<<
+<p>+++</p>
+>>> Thematic breaks - 45
+===
+<<<
+<p>===</p>
+>>> Thematic breaks - 46
+--
+**
+__
+<<<
+<p>--
+**
+__</p>
+>>> Thematic breaks - 47
+ ***
+ ***
+ ***
+<<<
+<hr />
+<hr />
+<hr />
+>>> Thematic breaks - 48
+ ***
+<<<
+<pre><code>***
+</code></pre>
+>>> Thematic breaks - 49
+Foo
+ ***
+<<<
+<p>Foo
+***</p>
+>>> Thematic breaks - 50
+_____________________________________
+<<<
+<hr />
+>>> Thematic breaks - 51
+ - - -
+<<<
+<hr />
+>>> Thematic breaks - 52
+ ** * ** * ** * **
+<<<
+<hr />
+>>> Thematic breaks - 53
+- - - -
+<<<
+<hr />
+>>> Thematic breaks - 54
+- - - -
+<<<
+<hr />
+>>> Thematic breaks - 55
+_ _ _ _ a
+
+a------
+
+---a---
+<<<
+<p>_ _ _ _ a</p>
+<p>a------</p>
+<p>---a---</p>
+>>> Thematic breaks - 56
+ *-*
+<<<
+<p><em>-</em></p>
+>>> Thematic breaks - 57
+- foo
+***
+- bar
+<<<
+<ul>
+<li>foo</li>
+</ul>
+<hr />
+<ul>
+<li>bar</li>
+</ul>
+>>> Thematic breaks - 58
+Foo
+***
+bar
+<<<
+<p>Foo</p>
+<hr />
+<p>bar</p>
+>>> Thematic breaks - 59
+Foo
+---
+bar
+<<<
+<h2>Foo</h2>
+<p>bar</p>
+>>> Thematic breaks - 60
+* Foo
+* * *
+* Bar
+<<<
+<ul>
+<li>Foo</li>
+</ul>
+<hr />
+<ul>
+<li>Bar</li>
+</ul>
+>>> Thematic breaks - 61
+- Foo
+- * * *
+<<<
+<ul>
+<li>Foo</li>
+<li>
+<hr />
+</li>
+</ul>
diff --git a/pkgs/markdown/test/crash_test.dart b/pkgs/markdown/test/crash_test.dart
new file mode 100644
index 0000000..a6f427d
--- /dev/null
+++ b/pkgs/markdown/test/crash_test.dart
@@ -0,0 +1,226 @@
+// 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 'dart:convert';
+import 'dart:io';
+import 'dart:isolate';
+
+import 'package:http/http.dart' as http;
+import 'package:http/retry.dart' as http;
+import 'package:markdown/markdown.dart';
+import 'package:pool/pool.dart';
+import 'package:tar/tar.dart';
+import 'package:test/test.dart';
+
+// ignore_for_file: avoid_dynamic_calls
+
+const extensions = [
+ '.md',
+ '.mkd',
+ '.mdwn',
+ '.mdown',
+ '.mdtxt',
+ '.mdtext',
+ '.markdown',
+ 'README',
+ 'CHANGELOG',
+];
+
+void main() async {
+ // This test is a really dumb and very slow crash-test.
+ // It downloads the latest package version for each package on pub.dev
+ // and tries to parse all `*.md` files in the package, counting the number
+ // of times where the parser throws.
+ //
+ // Needless to say, this test is very slow and running it eats a lot of CPU.
+ // But it's a fairly good way to try a lot of real-world markdown text to see
+ // if any of the poorly formatted markdown causes the parser to crash.
+ test(
+ 'crash test',
+ () async {
+ final started = DateTime.now();
+ var lastStatus = DateTime(0);
+ void status(String Function() message) {
+ if (DateTime.now().difference(lastStatus) >
+ const Duration(seconds: 30)) {
+ lastStatus = DateTime.now();
+ print(message());
+ }
+ }
+
+ final c = http.RetryClient(http.Client());
+ Future<dynamic> getJson(String url) async {
+ final u = Uri.tryParse(url);
+ if (u == null) {
+ return null;
+ }
+ try {
+ final data = await c.read(u);
+ try {
+ return jsonDecode(data);
+ } on FormatException {
+ return null;
+ }
+ } on http.ClientException {
+ return null;
+ } on IOException {
+ return null;
+ }
+ }
+
+ final packages =
+ ((await getJson('https://pub.dev/api/package-names'))['packages']
+ as List)
+ .cast<String>();
+ //.take(3).toList(); // useful when testing
+ print('## Found ${packages.length} packages to scan');
+
+ var count = 0;
+ final pool = Pool(50);
+ final packageVersions = <PackageVersion>[];
+ await Future.wait(packages.map((package) async {
+ await pool.withResource(() async {
+ final response = await getJson(
+ 'https://pub.dev/api/packages/$package',
+ );
+ final entry = response['latest'] as Map?;
+ if (entry != null) {
+ packageVersions.add(PackageVersion(
+ package: package,
+ version: entry['version'] as String,
+ archiveUrl: entry['archive_url'] as String,
+ ));
+ }
+ count++;
+ status(
+ () => 'Listed versions for $count / ${packages.length} packages',
+ );
+ });
+ }));
+
+ print('## Found ${packageVersions.length} package versions to scan');
+
+ count = 0;
+ final errors = <String>[];
+ var skipped = 0;
+ await Future.wait(packageVersions.map((pv) async {
+ await pool.withResource(() async {
+ final archiveUrl = Uri.tryParse(pv.archiveUrl);
+ if (archiveUrl == null) {
+ skipped++;
+ return;
+ }
+ late List<int> archive;
+ try {
+ archive = await c.readBytes(archiveUrl);
+ } on http.ClientException {
+ skipped++;
+ return;
+ } on IOException {
+ skipped++;
+ return;
+ }
+
+ final result = await _findMarkdownIssues(
+ pv.package,
+ pv.version,
+ archive,
+ );
+
+ // If tar decoding fails.
+ if (result == null) {
+ skipped++;
+ return;
+ }
+
+ errors.addAll(result);
+ result.forEach(print);
+ });
+ count++;
+ status(() =>
+ 'Scanned $count / ${packageVersions.length} (skipped $skipped),'
+ ' found ${errors.length} issues');
+ }));
+
+ await pool.close();
+ c.close();
+
+ print('## Finished scanning');
+ print('Scanned ${packageVersions.length} package versions in '
+ '${DateTime.now().difference(started)}');
+
+ if (errors.isNotEmpty) {
+ print('Found issues:');
+ errors.forEach(print);
+ fail('Found ${errors.length} cases where markdownToHtml threw!');
+ }
+ },
+ timeout: const Timeout(Duration(hours: 5)),
+ tags: 'crash_test', // skipped by default, see: dart_test.yaml
+ );
+}
+
+class PackageVersion {
+ final String package;
+ final String version;
+ final String archiveUrl;
+
+ PackageVersion({
+ required this.package,
+ required this.version,
+ required this.archiveUrl,
+ });
+}
+
+/// Scans [gzippedArchive] for markdown files and tries to parse them all.
+///
+/// Creates a list of issues that arose when parsing markdown files. The
+/// [package] and [version] strings are used to construct nice issues.
+/// An issue string may be multi-line, but should be printable.
+///
+/// Returns a list of issues, or `null` if decoding and parsing [gzippedArchive]
+/// failed.
+Future<List<String>?> _findMarkdownIssues(
+ String package,
+ String version,
+ List<int> gzippedArchive,
+) async {
+ return Isolate.run<List<String>?>(() async {
+ try {
+ final archive = gzip.decode(gzippedArchive);
+ final issues = <String>[];
+ await TarReader.forEach(Stream.value(archive), (entry) async {
+ if (extensions.any((ext) => entry.name.endsWith(ext))) {
+ late String contents;
+ try {
+ final bytes = await http.ByteStream(entry.contents).toBytes();
+ contents = utf8.decode(bytes);
+ } on FormatException {
+ return; // ignore invalid utf8
+ }
+ final start = DateTime.now();
+ try {
+ markdownToHtml(
+ contents,
+ extensionSet: ExtensionSet.gitHubWeb,
+ );
+ } catch (err, st) {
+ issues.add(
+ 'package:$package-$version/${entry.name}, throws: $err\n$st');
+ }
+ final time = DateTime.now().difference(start);
+ if (time.inSeconds > 30) {
+ issues.add(
+ 'package:$package-$version/${entry.name} took $time to process');
+ }
+ }
+ });
+ return issues;
+ } on FormatException {
+ return null;
+ }
+ }).timeout(const Duration(minutes: 2), onTimeout: () {
+ return ['package:$package-$version failed to be processed in 2 minutes'];
+ });
+}
diff --git a/pkgs/markdown/test/document_test.dart b/pkgs/markdown/test/document_test.dart
new file mode 100644
index 0000000..3e30697
--- /dev/null
+++ b/pkgs/markdown/test/document_test.dart
@@ -0,0 +1,125 @@
+// Copyright (c) 2011, 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:markdown/markdown.dart';
+import 'package:markdown/src/util.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('Document', () {
+ test('encodeHtml prevents less than and ampersand escaping', () {
+ final document = Document(encodeHtml: false);
+ final result = document.parseInline('< &');
+ expect(result, hasLength(1));
+ expect(
+ result[0],
+ const TypeMatcher<Text>().having((e) => e.text, 'text', equals('< &')),
+ );
+ });
+
+ group('with encodeHtml enabled', () {
+ final document = Document();
+
+ test('encodes HTML in an inline code snippet', () {
+ final result =
+ document.parseInline('``<p>Hello <em>Markdown</em></p>``');
+ final codeSnippet = result.single as Element;
+ expect(
+ codeSnippet.textContent,
+ equals('<p>Hello <em>Markdown</em></p>'),
+ );
+ });
+
+ test('encodes HTML in a fenced code block', () {
+ final lines = '```\n<p>Hello <em>Markdown</em></p>\n```\n'.toLines();
+ final result = document.parseLineList(lines);
+ final codeBlock = result.single as Element;
+ expect(
+ codeBlock.textContent,
+ equals('<p>Hello <em>Markdown</em></p>\n'),
+ );
+ });
+
+ test('encodes HTML in an indented code block', () {
+ final lines = ' <p>Hello <em>Markdown</em></p>\n'.toLines();
+ final result = document.parseLineList(lines);
+ final codeBlock = result.single as Element;
+ expect(
+ codeBlock.textContent,
+ equals('<p>Hello <em>Markdown</em></p>\n'),
+ );
+ });
+
+ test('encodeHtml spaces are preserved in text', () {
+ // Example to get a <p> tag rendered before a text node.
+ const contents = 'Sample\n\n<pre>\n A\n B\n</pre>';
+ final document = Document();
+ final nodes = BlockParser(contents.toLines(), document).parseLines();
+ final result = HtmlRenderer().render(nodes);
+ expect(result, '<p>\n</p>\n<pre>\n A\n B\n</pre>');
+ });
+
+ test('encode double quotes, greater than, and less than when escaped',
+ () {
+ const contents = r'\>\"\< Hello';
+ final document = Document();
+ final nodes = document.parseInline(contents);
+ expect(nodes, hasLength(1));
+ expect(
+ nodes.single,
+ const TypeMatcher<Text>().having(
+ (e) => e.text,
+ 'text',
+ '>"< Hello',
+ ),
+ );
+ });
+ });
+
+ group('with encodeHtml disabled', () {
+ final document = Document(encodeHtml: false);
+
+ test('leaves HTML alone, in a code snippet', () {
+ final result =
+ document.parseInline('```<p>Hello <em>Markdown</em></p>```');
+ final codeSnippet = result.single as Element;
+ expect(
+ codeSnippet.textContent,
+ equals('<p>Hello <em>Markdown</em></p>'),
+ );
+ });
+
+ test('leaves HTML alone, in a fenced code block', () {
+ final lines = '```\n<p>Hello <em>Markdown</em></p>\n```\n'.toLines();
+ final result = document.parseLineList(lines);
+ final codeBlock = result.single as Element;
+ expect(
+ codeBlock.textContent,
+ equals('<p>Hello <em>Markdown</em></p>\n'),
+ );
+ });
+
+ test('leaves HTML alone, in an indented code block', () {
+ final lines = ' <p>Hello <em>Markdown</em></p>\n'.toLines();
+ final result = document.parseLineList(lines);
+ final codeBlock = result.single as Element;
+ expect(
+ codeBlock.textContent,
+ equals('<p>Hello <em>Markdown</em></p>\n'),
+ );
+ });
+
+ test('leave double quotes, greater than, and less than when escaped', () {
+ const contents = r'\>\"\< Hello';
+ final document = Document(encodeHtml: false);
+ final nodes = document.parseInline(contents);
+ expect(nodes, hasLength(1));
+ expect(
+ nodes.single,
+ const TypeMatcher<Text>().having((e) => e.text, 'text', '>"< Hello'),
+ );
+ });
+ });
+ });
+}
diff --git a/pkgs/markdown/test/extensions/alert_extension.unit b/pkgs/markdown/test/extensions/alert_extension.unit
new file mode 100644
index 0000000..9a21f18
--- /dev/null
+++ b/pkgs/markdown/test/extensions/alert_extension.unit
@@ -0,0 +1,162 @@
+>>> type note
+> [!NoTe]
+> Test note alert.
+<<<
+<div class="markdown-alert markdown-alert-note">
+<p class="markdown-alert-title">Note</p>
+<p>Test note alert.</p>
+</div>
+>>> type tip
+> [!TiP]
+> Test tip alert.
+<<<
+<div class="markdown-alert markdown-alert-tip">
+<p class="markdown-alert-title">Tip</p>
+<p>Test tip alert.</p>
+</div>
+>>> type important
+> [!ImpoRtanT]
+> Test important alert.
+<<<
+<div class="markdown-alert markdown-alert-important">
+<p class="markdown-alert-title">Important</p>
+<p>Test important alert.</p>
+</div>
+>>> type warning
+> [!WarNinG]
+> Test warning alert.
+<<<
+<div class="markdown-alert markdown-alert-warning">
+<p class="markdown-alert-title">Warning</p>
+<p>Test warning alert.</p>
+</div>
+>>> type caution
+> [!CauTioN]
+> Test caution alert.
+<<<
+<div class="markdown-alert markdown-alert-caution">
+<p class="markdown-alert-title">Caution</p>
+<p>Test caution alert.</p>
+</div>
+>>> invalid type
+> [!foo]
+> Test foo alert.
+<<<
+<blockquote>
+<p>[!foo]
+Test foo alert.</p>
+</blockquote>
+>>> contents can both contain/not contain starting quote
+> [!NOTE]
+Test note alert.
+>Test note alert x2.
+<<<
+<div class="markdown-alert markdown-alert-note">
+<p class="markdown-alert-title">Note</p>
+<p>Test note alert.
+Test note alert x2.</p>
+</div>
+>>> spaces everywhere
+ > [!NOTE]
+> Test note alert.
+ > Test note alert x2.
+<<<
+<div class="markdown-alert markdown-alert-note">
+<p class="markdown-alert-title">Note</p>
+<p>Test note alert.
+Test note alert x2.</p>
+</div>
+>>> title has 3 more spaces then fallback to blockquote
+> [!NOTE]
+> Test blockquote.
+<<<
+<blockquote>
+<p>[!NOTE]
+Test blockquote.</p>
+</blockquote>
+>>> nested blockquote
+> [!NOTE]
+>> Test nested blockquote.
+<<<
+<div class="markdown-alert markdown-alert-note">
+<p class="markdown-alert-title">Note</p>
+<blockquote>
+<p>Test nested blockquote.</p>
+</blockquote>
+</div>
+>>> escape brackets
+> \[!note\]
+> Test escape brackets.
+<<<
+<div class="markdown-alert markdown-alert-note">
+<p class="markdown-alert-title">Note</p>
+<p>Test escape brackets.</p>
+</div>
+>>> terminates properly
+> [!note]
+> A sample note.
+
+Additional markdown text.
+<<<
+<div class="markdown-alert markdown-alert-note">
+<p class="markdown-alert-title">Note</p>
+<p>A sample note.</p>
+</div>
+<p>Additional markdown text.</p>
+>>> supports multiple quoted lines
+> [!note]
+> A sample note
+> with two lines.
+
+Additional markdown text.
+<<<
+<div class="markdown-alert markdown-alert-note">
+<p class="markdown-alert-title">Note</p>
+<p>A sample note
+with two lines.</p>
+</div>
+<p>Additional markdown text.</p>
+>>> supports multiple lines
+> [!note]
+> A sample note
+ with two lines.
+
+Additional markdown text.
+<<<
+<div class="markdown-alert markdown-alert-note">
+<p class="markdown-alert-title">Note</p>
+<p>A sample note
+with two lines.</p>
+</div>
+<p>Additional markdown text.</p>
+>>> supports continuation lines
+> [!note]
+> A sample note
+with two lines.
+
+Additional markdown text.
+<<<
+<div class="markdown-alert markdown-alert-note">
+<p class="markdown-alert-title">Note</p>
+<p>A sample note
+with two lines.</p>
+</div>
+<p>Additional markdown text.</p>
+>>> crash repro #584.1
+> [!Warning]
+>
+> Some extensions won't work on dynamic types.
+<<<
+<div class="markdown-alert markdown-alert-warning">
+<p class="markdown-alert-title">Warning</p>
+<p>Some extensions won't work on dynamic types.</p>
+</div>
+>>> crash repro #584.2
+> [!NOTE]
+>
+> if you receive the following error:
+<<<
+<div class="markdown-alert markdown-alert-note">
+<p class="markdown-alert-title">Note</p>
+<p>if you receive the following error:</p>
+</div>
diff --git a/pkgs/markdown/test/extensions/autolink_extension.unit b/pkgs/markdown/test/extensions/autolink_extension.unit
new file mode 100644
index 0000000..4a07fdc
--- /dev/null
+++ b/pkgs/markdown/test/extensions/autolink_extension.unit
@@ -0,0 +1,10 @@
+>>> not a link
+mhttp://www.foo.com
+<<<
+<p>mhttp://www.foo.com</p>
+>>> following a newline
+m
+http://www.foo.com
+<<<
+<p>m
+<a href="http://www.foo.com">http://www.foo.com</a></p>
\ No newline at end of file
diff --git a/pkgs/markdown/test/extensions/emojis.unit b/pkgs/markdown/test/extensions/emojis.unit
new file mode 100644
index 0000000..00197e5
--- /dev/null
+++ b/pkgs/markdown/test/extensions/emojis.unit
@@ -0,0 +1,42 @@
+>>> within a paragraph
+I love to :smile:.
+
+<<<
+<p>I love to 😄.</p>
+>>> within other inline syntax
+I *love to :smile:*
+<<<
+<p>I <em>love to 😄</em></p>
+>>> within blockquote
+> I love to :smile:.
+<<<
+<blockquote>
+<p>I love to 😄.</p>
+</blockquote>
+>>> within code block
+ I love to :smile:
+<<<
+<pre><code>I love to :smile:
+</code></pre>
+>>> within a link
+I love [to :smile:](http://www.google.com).
+<<<
+<p>I love <a href="http://www.google.com">to 😄</a>.</p>
+>>> within a reference link
+I love [to :smile:][].
+
+[to :smile:]: http://www.google.com
+<<<
+<p>I love <a href="http://www.google.com">to 😄</a>.</p>
+>>> within inline code
+I love to `:smile:`.
+<<<
+<p>I love to <code>:smile:</code>.</p>
+>>> with multiple code points
+Yay :australia:
+<<<
+<p>Yay 🇦🇺</p>
+>>> leaves unknown emojis alone
+I love :smiles:.
+<<<
+<p>I love :smiles:.</p>
diff --git a/pkgs/markdown/test/extensions/fenced_blockquotes.unit b/pkgs/markdown/test/extensions/fenced_blockquotes.unit
new file mode 100644
index 0000000..8124ad4
--- /dev/null
+++ b/pkgs/markdown/test/extensions/fenced_blockquotes.unit
@@ -0,0 +1,70 @@
+>>> simple block quote
+>>>
+# Foo
+bar
+baz
+>>>
+
+<<<
+<blockquote>
+<h1>Foo</h1>
+<p>bar
+baz</p>
+</blockquote>
+>>> has blank lines
+>>>
+
+foo
+
+
+
+
+bar
+
+
+>>>
+
+<<<
+<blockquote>
+<p>foo</p>
+<p>bar</p>
+</blockquote>
+>>> with nested block quote
+>>>
+foo
+> bar
+>>>
+
+<<<
+<blockquote>
+<p>foo</p>
+<blockquote>
+<p>bar</p>
+</blockquote>
+</blockquote>
+>>> with nested indented clode block
+>>>
+ foo
+ bar
+>>>
+
+<<<
+<blockquote>
+<pre><code>foo
+bar
+</code></pre>
+</blockquote>
+>>> with nested fenced clode block
+>>>
+```
+foo
+bar
+```
+>>>
+
+<<<
+<blockquote>
+<pre><code>foo
+bar
+</code></pre>
+</blockquote>
\ No newline at end of file
diff --git a/pkgs/markdown/test/extensions/fenced_code_blocks.unit b/pkgs/markdown/test/extensions/fenced_code_blocks.unit
new file mode 100644
index 0000000..596f49a
--- /dev/null
+++ b/pkgs/markdown/test/extensions/fenced_code_blocks.unit
@@ -0,0 +1,52 @@
+>>> without an optional language identifier
+```
+code
+```
+
+<<<
+<pre><code>code
+</code></pre>
+>>> with an optional language identifier
+```dart
+code
+```
+
+<<<
+<pre><code class="language-dart">code
+</code></pre>
+>>> escape HTML characters
+```
+<&>
+```
+
+<<<
+<pre><code><&>
+</code></pre>
+>>> Pandoc style without language identifier
+~~~~~
+code
+~~~~~
+
+<<<
+<pre><code>code
+</code></pre>
+>>> Pandoc style with language identifier
+~~~~~dart
+code
+~~~~~
+
+<<<
+<pre><code class="language-dart">code
+</code></pre>
+>>> Pandoc style with inner tildes row
+~~~~~
+~~~
+code
+~~~
+~~~~~
+
+<<<
+<pre><code>~~~
+code
+~~~
+</code></pre>
diff --git a/pkgs/markdown/test/extensions/footnote_block.unit b/pkgs/markdown/test/extensions/footnote_block.unit
new file mode 100644
index 0000000..ae730bf
--- /dev/null
+++ b/pkgs/markdown/test/extensions/footnote_block.unit
@@ -0,0 +1,288 @@
+>>> footnote reference in footnote definition
+
+Footnote 1 link[^first].
+
+[^first]: footnote reference in footnote definition[^first]
+
+<<<
+<p>Footnote 1 link<sup class="footnote-ref"><a href="#fn-first" id="fnref-first">1</a></sup>.</p>
+<section class="footnotes">
+<ol>
+<li id="fn-first">
+<p>footnote reference in footnote definition<sup class="footnote-ref"><a href="#fn-first" id="fnref-first-2">1</a></sup> <a href="#fnref-first" class="footnote-backref">↩</a> <a href="#fnref-first-2" class="footnote-backref">↩<sup class="footnote-ref">2</sup></a></p>
+</li>
+</ol>
+</section>
+>>> footnote reference cases
+[^fifth]: ending with another ']' and different order
+
+Footnote 1 link[^first].
+
+Footnote 2 link[^きゃくちゅう脚注].
+
+Footnote 3 link[^p1 p2].
+
+Footnote 4 link![^forth].
+
+Footnote 5 link![^fifth]].
+
+Footnote 6 link![^ sixth ].
+
+Footnote 7 link[^きゃくちゅう脚注].
+
+[^first]: Here is the footnote definition
+
+[^きゃくちゅう脚注]: unicode label and duplicated reference.
+
+[^p1 p2]: p1 p2
+
+[^ForTh]: start with '[' and with upper case
+
+[^sixth]: label-start-with-blank
+<<<
+<p>Footnote 1 link<sup class="footnote-ref"><a href="#fn-first" id="fnref-first">1</a></sup>.</p>
+<p>Footnote 2 link<sup class="footnote-ref"><a href="#fn-%E3%81%8D%E3%82%83%E3%81%8F%E3%81%A1%E3%82%85%E3%81%86%E8%84%9A%E6%B3%A8" id="fnref-%E3%81%8D%E3%82%83%E3%81%8F%E3%81%A1%E3%82%85%E3%81%86%E8%84%9A%E6%B3%A8">2</a></sup>.</p>
+<p>Footnote 3 link[^p1 p2].</p>
+<p>Footnote 4 link!<sup class="footnote-ref"><a href="#fn-ForTh" id="fnref-ForTh">3</a></sup>.</p>
+<p>Footnote 5 link!<sup class="footnote-ref"><a href="#fn-fifth" id="fnref-fifth">4</a></sup>].</p>
+<p>Footnote 6 link!<sup class="footnote-ref"><a href="#fn-sixth" id="fnref-sixth">5</a></sup>.</p>
+<p>Footnote 7 link<sup class="footnote-ref"><a href="#fn-%E3%81%8D%E3%82%83%E3%81%8F%E3%81%A1%E3%82%85%E3%81%86%E8%84%9A%E6%B3%A8" id="fnref-%E3%81%8D%E3%82%83%E3%81%8F%E3%81%A1%E3%82%85%E3%81%86%E8%84%9A%E6%B3%A8-2">2</a></sup>.</p>
+<p>[^p1 p2]: p1 p2</p>
+<section class="footnotes">
+<ol>
+<li id="fn-first">
+<p>Here is the footnote definition <a href="#fnref-first" class="footnote-backref">↩</a></p>
+</li>
+<li id="fn-%E3%81%8D%E3%82%83%E3%81%8F%E3%81%A1%E3%82%85%E3%81%86%E8%84%9A%E6%B3%A8">
+<p>unicode label and duplicated reference. <a href="#fnref-%E3%81%8D%E3%82%83%E3%81%8F%E3%81%A1%E3%82%85%E3%81%86%E8%84%9A%E6%B3%A8" class="footnote-backref">↩</a> <a href="#fnref-%E3%81%8D%E3%82%83%E3%81%8F%E3%81%A1%E3%82%85%E3%81%86%E8%84%9A%E6%B3%A8-2" class="footnote-backref">↩<sup class="footnote-ref">2</sup></a></p>
+</li>
+<li id="fn-ForTh">
+<p>start with '[' and with upper case <a href="#fnref-ForTh" class="footnote-backref">↩</a></p>
+</li>
+<li id="fn-fifth">
+<p>ending with another ']' and different order <a href="#fnref-fifth" class="footnote-backref">↩</a></p>
+</li>
+<li id="fn-sixth">
+<p>label-start-with-blank <a href="#fnref-sixth" class="footnote-backref">↩</a></p>
+</li>
+</ol>
+</section>
+>>> footnote labels with special chars
+empty label[^] and blank label[^ ]
+
+some[^-] strange[^^] but[^\[] labels[^\[\]]
+
+[^]:
+[^ ]:
+
+[^-]: valid1
+
+[^^]:valid2
+
+[^\[]: valid3
+
+[^\[\]]: this-is-link-not-footnote
+<<<
+<p>empty label[^] and blank label[^ ]</p>
+<p>some<sup class="footnote-ref"><a href="#fn--" id="fnref--">1</a></sup> strange<sup class="footnote-ref"><a href="#fn-%5E" id="fnref-%5E">2</a></sup> but<sup class="footnote-ref"><a href="#fn-%5C%5B" id="fnref-%5C%5B">3</a></sup> labels<a href="this-is-link-not-footnote">^[]</a></p>
+<p>[^]:
+[^ ]:</p>
+<section class="footnotes">
+<ol>
+<li id="fn--">
+<p>valid1 <a href="#fnref--" class="footnote-backref">↩</a></p>
+</li>
+<li id="fn-%5E">
+<p>valid2 <a href="#fnref-%5E" class="footnote-backref">↩</a></p>
+</li>
+<li id="fn-%5C%5B">
+<p>valid3 <a href="#fnref-%5C%5B" class="footnote-backref">↩</a></p>
+</li>
+</ol>
+</section>
+>>> footnote with paragraph
+test footnote[^first].
+
+[^first]: Footnote **can have markup**
+
+ and multiple paragraphs.
+
+"Smartypants, double quotes" and 'single quotes'
+<<<
+<p>test footnote<sup class="footnote-ref"><a href="#fn-first" id="fnref-first">1</a></sup>.</p>
+<p>"Smartypants, double quotes" and 'single quotes'</p>
+<section class="footnotes">
+<ol>
+<li id="fn-first">
+<p>Footnote <strong>can have markup</strong></p>
+<p>and multiple paragraphs. <a href="#fnref-first" class="footnote-backref">↩</a></p>
+</li>
+</ol>
+</section>
+>>> footnote adjacent paragraph
+Here is a footnote reference,[^1]
+[^1]: Here is the footnote.
+ Subsequent paragraphs
+<<<
+<p>Here is a footnote reference,<sup class="footnote-ref"><a href="#fn-1" id="fnref-1">1</a></sup></p>
+<section class="footnotes">
+<ol>
+<li id="fn-1">
+<p>Here is the footnote.
+Subsequent paragraphs <a href="#fnref-1" class="footnote-backref">↩</a></p>
+</li>
+</ol>
+</section>
+>>> footnote without ref
+Here is a footnote reference
+[^1]: Here is the footnote.
+<<<
+<p>Here is a footnote reference</p>
+>>> footnote example from github
+Here is a simple footnote[^1].
+
+A footnote can also have multiple lines[^2].
+
+You can also use words, to fit your writing style more closely[^note].
+
+[^1]: My reference.
+[^2]: Every new line should be prefixed with 2 spaces.
+ This allows you to have a footnote with multiple lines.
+[^note]:
+ Named footnotes will still render with numbers instead of the text but allow easier identification and linking.
+ This footnote also has been made with a different syntax using 4 spaces for new lines.
+<<<
+<p>Here is a simple footnote<sup class="footnote-ref"><a href="#fn-1" id="fnref-1">1</a></sup>.</p>
+<p>A footnote can also have multiple lines<sup class="footnote-ref"><a href="#fn-2" id="fnref-2">2</a></sup>.</p>
+<p>You can also use words, to fit your writing style more closely<sup class="footnote-ref"><a href="#fn-note" id="fnref-note">3</a></sup>.</p>
+<section class="footnotes">
+<ol>
+<li id="fn-1">
+<p>My reference. <a href="#fnref-1" class="footnote-backref">↩</a></p>
+</li>
+<li id="fn-2">
+<p>Every new line should be prefixed with 2 spaces.
+This allows you to have a footnote with multiple lines. <a href="#fnref-2" class="footnote-backref">↩</a></p>
+</li>
+<li id="fn-note">
+<p>Named footnotes will still render with numbers instead of the text but allow easier identification and linking.
+This footnote also has been made with a different syntax using 4 spaces for new lines. <a href="#fnref-note" class="footnote-backref">↩</a></p>
+</li>
+</ol>
+</section>
+>>> ![^ **image**] without definition should be formatted: unlike github
+![^ **image**]
+<<<
+<p>![^ <strong>image</strong>]</p>
+>>> ![^ **image**] with definition should be image: unlike github
+![^ **image**]
+
+[^ **image**]: valid-link
+<<<
+<p><img src="valid-link" alt="^ image" /></p>
+>>> ![^ **image**] with definition should be plain html: unlike github
+![^ **image**]
+
+[^ **image**]: invalid link
+<<<
+<p>![^ <strong>image</strong>]</p>
+<p>[^ <strong>image</strong>]: invalid link</p>
+>>> ![ ^**image**] without definition should be formatted
+![ ^**image**]
+<<<
+<p>![ ^<strong>image</strong>]</p>
+>>> ![^ ^**image**] without definition should be formatted: unlike github
+![^ ^**image**]
+<<<
+<p>![^ ^<strong>image</strong>]</p>
+>>> ![^ ^**image**] with definition should be image: unlike github
+![^ ^**image**]
+
+[^ ^**image**]: valid-link
+<<<
+<p><img src="valid-link" alt="^ ^image" /></p>
+>>> [^ **label**] without definition should be formatted: unlike github
+[^ **label**]
+<<<
+<p>[^ <strong>label</strong>]</p>
+>>> [adc][^**link**] without definition should be formatted: unlike github
+[adc][^**link**]
+<<<
+<p>[adc][^<strong>link</strong>]</p>
+>>> [adc][^**link**] with definition should be footnotes:
+[adc][^**link**]
+
+[^**link**]: valid-link
+<<<
+<p>[adc]<sup class="footnote-ref"><a href="#fn-**link**" id="fnref-**link**">1</a></sup></p>
+<section class="footnotes">
+<ol>
+<li id="fn-**link**">
+<p>valid-link <a href="#fnref-**link**" class="footnote-backref">↩</a></p>
+</li>
+</ol>
+</section>
+>>> \[^good] should be text
+\[^good]
+
+[^good]: good
+<<<
+<p>[^good]</p>
+>>>[^ nice] should be link
+[^ nice]
+
+[^ nice ]: good
+<<<
+<p><a href="good">^ nice</a></p>
+>>>[^ nice ] should be text
+[^ nice ]
+
+[^nice ]: good
+<<<
+<p>[^ nice ]</p>
+>>> [^\]] with definition should be link
+[^\]]
+
+[^\]]: good
+<<<
+<p><a href="good">^]</a></p>
+>>> [^a-\nb] with definition should be paragraph
+[^a-
+b]
+
+[^a-
+b]: good
+<<<
+<p><a href="good">^a-
+b</a></p>
+>>> [^a] with error definition should be text
+[^a]
+
+[^a\]: good
+<<<
+<p>[^a]</p>
+>>> [^a] with double definition contain '\]' should be text
+[^a]
+
+[^a\]:]: definition contain '\]'
+<<<
+<p>[^a]</p>
+>>> [^a] with double definition trailing should be footnote
+[^a]
+
+[^a]:]: good
+<<<
+<p><sup class="footnote-ref"><a href="#fn-a" id="fnref-a">1</a></sup></p>
+<section class="footnotes">
+<ol>
+<li id="fn-a">
+<p>]: good <a href="#fnref-a" class="footnote-backref">↩</a></p>
+</li>
+</ol>
+</section>
+>>> complete image link with definition would be image: unlike github
+
+
+[^image]: image footnote
+<<<
+<p><img src="example.png" alt="^image" /></p>
diff --git a/pkgs/markdown/test/extensions/headers_with_ids.unit b/pkgs/markdown/test/extensions/headers_with_ids.unit
new file mode 100644
index 0000000..7bad75c
--- /dev/null
+++ b/pkgs/markdown/test/extensions/headers_with_ids.unit
@@ -0,0 +1,41 @@
+>>> simple header
+# header
+
+<<<
+<h1 id="header">header</h1>
+>>> header that starts with garbage
+## 2. header again
+
+<<<
+<h2 id="2-header-again">2. header again</h2>
+>>> header with inline syntaxes
+### headers **rock** `etc.`
+
+<<<
+<h3 id="headers-rock-etc">headers <strong>rock</strong> <code>etc.</code></h3>
+>>> non-unique headers
+# header
+
+## header
+
+<<<
+<h1 id="header">header</h1>
+<h2 id="header-2">header</h2>
+>>> header starts with inline syntax
+# *headers* etc.
+<<<
+<h1 id="headers-etc"><em>headers</em> etc.</h1>
+>>> numbers-only headers (like a changelog)
+# 1.2.34
+
+## 1.23.4
+
+## 1.2.3+4
+<<<
+<h1 id="1234">1.2.34</h1>
+<h2 id="1234-2">1.23.4</h2>
+<h2 id="1234-3">1.2.3+4</h2>
+>>> no id
+# #
+<<<
+<h1></h1>
diff --git a/pkgs/markdown/test/extensions/inline_html.unit b/pkgs/markdown/test/extensions/inline_html.unit
new file mode 100644
index 0000000..1540118
--- /dev/null
+++ b/pkgs/markdown/test/extensions/inline_html.unit
@@ -0,0 +1,17 @@
+>>> within a paragraph
+Within a <em class="x">paragraph</EM>.
+
+<<<
+<p>Within a <em class="x">paragraph</EM>.</p>
+>>> not HTML
+Obviously, 3 < 5 and 7 > 2.
+Not HTML: <3>, <_a>, <>
+
+<<<
+<p>Obviously, 3 < 5 and 7 > 2.
+Not HTML: <3>, <_a>, <></p>
+>>> "markdown" within a tag is not parsed
+Text <a href="_foo_">And "_foo_"</a>.
+
+<<<
+<p>Text <a href="_foo_">And "<em>foo</em>"</a>.</p>
diff --git a/pkgs/markdown/test/extensions/ordered_list_with_checkboxes.unit b/pkgs/markdown/test/extensions/ordered_list_with_checkboxes.unit
new file mode 100644
index 0000000..8503e56
--- /dev/null
+++ b/pkgs/markdown/test/extensions/ordered_list_with_checkboxes.unit
@@ -0,0 +1,79 @@
+>>> checkbox with space
+1. [ ] one
+2. [ ] two
+<<<
+<ol class="contains-task-list">
+<li class="task-list-item"><input type="checkbox"></input>one</li>
+<li class="task-list-item"><input type="checkbox"></input>two</li>
+</ol>
+>>> empty checkbox
+1. [] one
+2. [] two
+<<<
+<ol>
+<li>[] one</li>
+<li>[] two</li>
+</ol>
+>>> checkbox with x
+1. [x] one
+2. [x] two
+<<<
+<ol class="contains-task-list">
+<li class="task-list-item"><input type="checkbox" checked="true"></input>one</li>
+<li class="task-list-item"><input type="checkbox" checked="true"></input>two</li>
+</ol>
+>>> checkbox with X
+1. [X] one
+2. [X] two
+<<<
+<ol class="contains-task-list">
+<li class="task-list-item"><input type="checkbox" checked="true"></input>one</li>
+<li class="task-list-item"><input type="checkbox" checked="true"></input>two</li>
+</ol>
+>>> mixed checkboxes
+1. [ ] one
+2. [] two
+3. [x] three
+4. [X] four
+5. five
+<<<
+<ol class="contains-task-list">
+<li class="task-list-item"><input type="checkbox"></input>one</li>
+<li>[] two</li>
+<li class="task-list-item"><input type="checkbox" checked="true"></input>three</li>
+<li class="task-list-item"><input type="checkbox" checked="true"></input>four</li>
+<li>five</li>
+</ol>
+>>> mixed leading spaces
+1. [ ] zero
+2. [ ] one
+3. [ ] two
+4. [ ] three
+5. [ ] four
+<<<
+<ol class="contains-task-list">
+<li class="task-list-item"><input type="checkbox"></input>zero</li>
+<li class="task-list-item"><input type="checkbox"></input>one</li>
+<li class="task-list-item"><input type="checkbox"></input>two</li>
+<li class="task-list-item"><input type="checkbox"></input>three</li>
+<li>
+<pre><code>[ ] four
+</code></pre>
+</li>
+</ol>
+>>> checkbox with empty content
+1. [ ] one
+2.
+3.
+4. four
+5. [ ] five
+6.
+<<<
+<ol class="contains-task-list">
+<li class="task-list-item"><input type="checkbox"></input>one</li>
+<li></li>
+<li></li>
+<li>four</li>
+<li class="task-list-item"><input type="checkbox"></input>five</li>
+<li></li>
+</ol>
\ No newline at end of file
diff --git a/pkgs/markdown/test/extensions/setext_headers_with_ids.unit b/pkgs/markdown/test/extensions/setext_headers_with_ids.unit
new file mode 100644
index 0000000..b8ef022
--- /dev/null
+++ b/pkgs/markdown/test/extensions/setext_headers_with_ids.unit
@@ -0,0 +1,18 @@
+>>> h1
+text
+===
+
+<<<
+<h1 id="text">text</h1>
+>>> h2
+text
+---
+
+<<<
+<h2 id="text">text</h2>
+>>> header with inline syntax
+header *emphasised*
+===
+
+<<<
+<h1 id="header-emphasised">header <em>emphasised</em></h1>
diff --git a/pkgs/markdown/test/extensions/strikethrough.unit b/pkgs/markdown/test/extensions/strikethrough.unit
new file mode 100644
index 0000000..5222d91
--- /dev/null
+++ b/pkgs/markdown/test/extensions/strikethrough.unit
@@ -0,0 +1,24 @@
+>>> Missing leading whitespace
+word pas~~t~~ word
+<<<
+<p>word pas<del>t</del> word</p>
+>>> Missing trailing whitespace
+word ~~p~~ast word
+<<<
+<p>word <del>p</del>ast word</p>
+>>> Middle of word
+word p~~as~~t word
+<<<
+<p>word p<del>as</del>t word</p>
+>>> Whitespace after opening
+word~~ past~~ word
+<<<
+<p>word~~ past~~ word</p>
+>>> Whitespace before closing
+word ~~past ~~word
+<<<
+<p>word ~~past ~~word</p>
+>>> mixed with emphasis and order changes
+**~~first~~** ~~**second**~~
+<<<
+<p><strong><del>first</del></strong> <del><strong>second</strong></del></p>
diff --git a/pkgs/markdown/test/extensions/tables.unit b/pkgs/markdown/test/extensions/tables.unit
new file mode 100644
index 0000000..c7a8cae
--- /dev/null
+++ b/pkgs/markdown/test/extensions/tables.unit
@@ -0,0 +1,378 @@
+>>> basic table
+head | cells
+-----|------
+body | cells
+
+<<<
+<table>
+<thead>
+<tr>
+<th>head</th>
+<th>cells</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>body</td>
+<td>cells</td>
+</tr>
+</tbody>
+</table>
+>>> multiple rows
+head | cells
+-----|------
+body | cells
+more | cells
+
+<<<
+<table>
+<thead>
+<tr>
+<th>head</th>
+<th>cells</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>body</td>
+<td>cells</td>
+</tr>
+<tr>
+<td>more</td>
+<td>cells</td>
+</tr>
+</tbody>
+</table>
+>>> rows wrapped in pipes
+| head | cells |
+|------|-------|
+| body | cells |
+
+<<<
+<table>
+<thead>
+<tr>
+<th>head</th>
+<th>cells</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>body</td>
+<td>cells</td>
+</tr>
+</tbody>
+</table>
+>>> rows wrapped in pipes, whitespace alignment row
+| head | cells |
+| -- | --- |
+| body | cells |
+
+<<<
+<table>
+<thead>
+<tr>
+<th>head</th>
+<th>cells</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>body</td>
+<td>cells</td>
+</tr>
+</tbody>
+</table>
+>>> rows wrapped in pipes, tabs in whitespace
+| head | cells |
+| -- | --- |
+| body | cells |
+
+<<<
+<table>
+<thead>
+<tr>
+<th>head</th>
+<th>cells</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>body</td>
+<td>cells</td>
+</tr>
+</tbody>
+</table>
+>>> cells with inline syntax
+head `code` | _cells_
+------------|--------
+*text* | <span>text</span>
+<<<
+<table>
+<thead>
+<tr>
+<th>head <code>code</code></th>
+<th><em>cells</em></th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td><em>text</em></td>
+<td><span>text</span></td>
+</tr>
+</tbody>
+</table>
+>>> cells are parsed before inline syntax
+header | _foo | bar_
+-------|------------|---
+text | text
+<<<
+<table>
+<thead>
+<tr>
+<th>header</th>
+<th>_foo</th>
+<th>bar_</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>text</td>
+<td>text</td>
+<td></td>
+</tr>
+</tbody>
+</table>
+>>> cells contain reference links
+header | header
+-------|--------
+text | [link][here]
+
+[here]: http://url
+<<<
+<table>
+<thead>
+<tr>
+<th>header</th>
+<th>header</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>text</td>
+<td><a href="http://url">link</a></td>
+</tr>
+</tbody>
+</table>
+>>> one column tables
+head
+-----|
+body
+<<<
+<table>
+<thead>
+<tr>
+<th>head</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>body</td>
+</tr>
+</tbody>
+</table>
+>>> varying cells per row
+head | foo | bar
+-----|-----|-----
+body
+row with | two cells
+<<<
+<table>
+<thead>
+<tr>
+<th>head</th>
+<th>foo</th>
+<th>bar</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>body</td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td>row with</td>
+<td>two cells</td>
+<td></td>
+</tr>
+</tbody>
+</table>
+>>> left, center, and right alignment
+head | cells | here
+:----|:-----:|----:
+body | cells | here
+too | many | cells | here
+
+<<<
+<table>
+<thead>
+<tr>
+<th align="left">head</th>
+<th align="center">cells</th>
+<th align="right">here</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td align="left">body</td>
+<td align="center">cells</td>
+<td align="right">here</td>
+</tr>
+<tr>
+<td align="left">too</td>
+<td align="center">many</td>
+<td align="right">cells</td>
+</tr>
+</tbody>
+</table>
+>>> left, center, and right alignment, with whitespace
+head | cells | here
+ :-- | :---: | ---:
+body | cells | here
+too | many | cells | here
+
+<<<
+<table>
+<thead>
+<tr>
+<th align="left">head</th>
+<th align="center">cells</th>
+<th align="right">here</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td align="left">body</td>
+<td align="center">cells</td>
+<td align="right">here</td>
+</tr>
+<tr>
+<td align="left">too</td>
+<td align="center">many</td>
+<td align="right">cells</td>
+</tr>
+</tbody>
+</table>
+>>> escape pipe
+| Name | Character |
+| --- | --- |
+| Backtick | ` |
+| Pipe | \| |
+
+<<<
+<table>
+<thead>
+<tr>
+<th>Name</th>
+<th>Character</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>Backtick</td>
+<td>`</td>
+</tr>
+<tr>
+<td>Pipe</td>
+<td>|</td>
+</tr>
+</tbody>
+</table>
+>>> escape pipe, preserve trailing whitespace
+| Name | Character |
+| --- | --- |
+| Pipe | \| abcdef |
+
+<<<
+<table>
+<thead>
+<tr>
+<th>Name</th>
+<th>Character</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>Pipe</td>
+<td>| abcdef</td>
+</tr>
+</tbody>
+</table>
+>>> trailing whitespace after final pipe
+| Name | Character |
+| --- | --- |
+| Pipe | abcdef |
+<<<
+<table>
+<thead>
+<tr>
+<th>Name</th>
+<th>Character</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>Pipe</td>
+<td>abcdef</td>
+</tr>
+</tbody>
+</table>
+>>> issue #531
+| A | [B](url) | C |
+|---|---|
+| a | b | c |
+<<<
+<p>| A | <a href="url">B</a> | C |
+|---|---|
+| a | b | c |</p>
+>>> trailing whitespace after delimiter row
+| Name | Value |
+| --- | --- |
+| Foo | bar |
+<<<
+<table>
+<thead>
+<tr>
+<th>Name</th>
+<th>Value</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>Foo</td>
+<td>bar</td>
+</tr>
+</tbody>
+</table>
+>>> can interrupt a paragraph
+paragraph
+foo | bar
+--- | ---
+baz | bim
+<<<
+<p>paragraph</p>
+<table>
+<thead>
+<tr>
+<th>foo</th>
+<th>bar</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>baz</td>
+<td>bim</td>
+</tr>
+</tbody>
+</table>
diff --git a/pkgs/markdown/test/extensions/unordered_list_with_checkboxes.unit b/pkgs/markdown/test/extensions/unordered_list_with_checkboxes.unit
new file mode 100644
index 0000000..f33161e
--- /dev/null
+++ b/pkgs/markdown/test/extensions/unordered_list_with_checkboxes.unit
@@ -0,0 +1,83 @@
+>>> checkbox with space
+* [ ] one
+* [ ] two
+<<<
+<ul class="contains-task-list">
+<li class="task-list-item"><input type="checkbox"></input>one</li>
+<li class="task-list-item"><input type="checkbox"></input>two</li>
+</ul>
+>>> empty checkbox
+* [] one
+* [] two
+<<<
+<ul>
+<li>[] one</li>
+<li>[] two</li>
+</ul>
+>>> checkbox with x
+* [x] one
+* [x] two
+<<<
+<ul class="contains-task-list">
+<li class="task-list-item"><input type="checkbox" checked="true"></input>one</li>
+<li class="task-list-item"><input type="checkbox" checked="true"></input>two</li>
+</ul>
+>>> checkbox with X
+* [X] one
+* [X] two
+<<<
+<ul class="contains-task-list">
+<li class="task-list-item"><input type="checkbox" checked="true"></input>one</li>
+<li class="task-list-item"><input type="checkbox" checked="true"></input>two</li>
+</ul>
+>>> mixed checkboxes
+* [ ] one
+* [] two
+* [x] three
+* [X] four
+* five
+<<<
+<ul class="contains-task-list">
+<li class="task-list-item"><input type="checkbox"></input>one</li>
+<li>[] two</li>
+<li class="task-list-item"><input type="checkbox" checked="true"></input>three</li>
+<li class="task-list-item"><input type="checkbox" checked="true"></input>four</li>
+<li>five</li>
+</ul>
+>>> mixed leading spaces
+*[ ] zero
+* [ ] one
+* [ ] two
+* [ ] three
+* [ ] four
+* [ ] five
+<<<
+<p>*[ ] zero</p>
+<ul class="contains-task-list">
+<li class="task-list-item"><input type="checkbox"></input>one</li>
+<li class="task-list-item"><input type="checkbox"></input>two</li>
+<li class="task-list-item"><input type="checkbox"></input>three</li>
+<li class="task-list-item"><input type="checkbox"></input>four</li>
+<li>
+<pre><code>[ ] five
+</code></pre>
+</li>
+</ul>
+>>> checkbox list separated with blank lines
+- [ ] A
+
+- [ ] B
+
+- [ ]
+<<<
+<ul class="contains-task-list">
+<li class="task-list-item">
+<p><input type="checkbox"></input>A</p>
+</li>
+<li class="task-list-item">
+<p><input type="checkbox"></input>B</p>
+</li>
+<li>
+<p>[ ]</p>
+</li>
+</ul>
\ No newline at end of file
diff --git a/pkgs/markdown/test/gfm/atx_headings.unit b/pkgs/markdown/test/gfm/atx_headings.unit
new file mode 100644
index 0000000..5a60cf4
--- /dev/null
+++ b/pkgs/markdown/test/gfm/atx_headings.unit
@@ -0,0 +1,112 @@
+>>> ATX headings - 32
+# foo
+## foo
+### foo
+#### foo
+##### foo
+###### foo
+<<<
+<h1>foo</h1>
+<h2>foo</h2>
+<h3>foo</h3>
+<h4>foo</h4>
+<h5>foo</h5>
+<h6>foo</h6>
+>>> ATX headings - 33
+####### foo
+<<<
+<p>####### foo</p>
+>>> ATX headings - 34
+#5 bolt
+
+#hashtag
+<<<
+<p>#5 bolt</p>
+<p>#hashtag</p>
+>>> ATX headings - 35
+\## foo
+<<<
+<p>## foo</p>
+>>> ATX headings - 36
+# foo *bar* \*baz\*
+<<<
+<h1>foo <em>bar</em> *baz*</h1>
+>>> ATX headings - 37
+# foo
+<<<
+<h1>foo</h1>
+>>> ATX headings - 38
+ ### foo
+ ## foo
+ # foo
+<<<
+<h3>foo</h3>
+<h2>foo</h2>
+<h1>foo</h1>
+>>> ATX headings - 39
+ # foo
+<<<
+<pre><code># foo
+</code></pre>
+>>> ATX headings - 40
+foo
+ # bar
+<<<
+<p>foo
+# bar</p>
+>>> ATX headings - 41
+## foo ##
+ ### bar ###
+<<<
+<h2>foo</h2>
+<h3>bar</h3>
+>>> ATX headings - 42
+# foo ##################################
+##### foo ##
+<<<
+<h1>foo</h1>
+<h5>foo</h5>
+>>> ATX headings - 43
+### foo ###
+<<<
+<h3>foo</h3>
+>>> ATX headings - 44
+### foo ### b
+<<<
+<h3>foo ### b</h3>
+>>> ATX headings - 45
+# foo#
+<<<
+<h1>foo#</h1>
+>>> ATX headings - 46
+### foo \###
+## foo #\##
+# foo \#
+<<<
+<h3>foo ###</h3>
+<h2>foo ###</h2>
+<h1>foo #</h1>
+>>> ATX headings - 47
+****
+## foo
+****
+<<<
+<hr />
+<h2>foo</h2>
+<hr />
+>>> ATX headings - 48
+Foo bar
+# baz
+Bar foo
+<<<
+<p>Foo bar</p>
+<h1>baz</h1>
+<p>Bar foo</p>
+>>> ATX headings - 49
+##
+#
+### ###
+<<<
+<h2></h2>
+<h1></h1>
+<h3></h3>
diff --git a/pkgs/markdown/test/gfm/autolinks.unit b/pkgs/markdown/test/gfm/autolinks.unit
new file mode 100644
index 0000000..6df5296
--- /dev/null
+++ b/pkgs/markdown/test/gfm/autolinks.unit
@@ -0,0 +1,76 @@
+>>> Autolinks - 602
+<http://foo.bar.baz>
+<<<
+<p><a href="http://foo.bar.baz">http://foo.bar.baz</a></p>
+>>> Autolinks - 603
+<http://foo.bar.baz/test?q=hello&id=22&boolean>
+<<<
+<p><a href="http://foo.bar.baz/test?q=hello&id=22&boolean">http://foo.bar.baz/test?q=hello&id=22&boolean</a></p>
+>>> Autolinks - 604
+<irc://foo.bar:2233/baz>
+<<<
+<p><a href="irc://foo.bar:2233/baz">irc://foo.bar:2233/baz</a></p>
+>>> Autolinks - 605
+<MAILTO:FOO@BAR.BAZ>
+<<<
+<p><a href="MAILTO:FOO@BAR.BAZ">MAILTO:FOO@BAR.BAZ</a></p>
+>>> Autolinks - 606
+<a+b+c:d>
+<<<
+<p><a href="a+b+c:d">a+b+c:d</a></p>
+>>> Autolinks - 607
+<made-up-scheme://foo,bar>
+<<<
+<p><a href="made-up-scheme://foo,bar">made-up-scheme://foo,bar</a></p>
+>>> Autolinks - 608
+<http://../>
+<<<
+<p><a href="http://../">http://../</a></p>
+>>> Autolinks - 609
+<localhost:5001/foo>
+<<<
+<p><a href="localhost:5001/foo">localhost:5001/foo</a></p>
+>>> Autolinks - 610
+<http://foo.bar/baz bim>
+<<<
+<p><http://foo.bar/baz bim></p>
+>>> Autolinks - 611
+<http://example.com/\[\>
+<<<
+<p><a href="http://example.com/%5C%5B%5C">http://example.com/\[\</a></p>
+>>> Autolinks - 612
+<foo@bar.example.com>
+<<<
+<p><a href="mailto:foo@bar.example.com">foo@bar.example.com</a></p>
+>>> Autolinks - 613
+<foo+special@Bar.baz-bar0.com>
+<<<
+<p><a href="mailto:foo+special@Bar.baz-bar0.com">foo+special@Bar.baz-bar0.com</a></p>
+>>> Autolinks - 614
+<foo\+@bar.example.com>
+<<<
+<p><foo+@bar.example.com></p>
+>>> Autolinks - 615
+<>
+<<<
+<p><></p>
+>>> Autolinks - 616
+< http://foo.bar >
+<<<
+<p>< http://foo.bar ></p>
+>>> Autolinks - 617
+<m:abc>
+<<<
+<p><m:abc></p>
+>>> Autolinks - 618
+<foo.bar.baz>
+<<<
+<p><foo.bar.baz></p>
+>>> Autolinks - 619
+http://example.com
+<<<
+<p>http://example.com</p>
+>>> Autolinks - 620
+foo@bar.example.com
+<<<
+<p>foo@bar.example.com</p>
diff --git a/pkgs/markdown/test/gfm/autolinks_extension.unit b/pkgs/markdown/test/gfm/autolinks_extension.unit
new file mode 100644
index 0000000..ff69a53
--- /dev/null
+++ b/pkgs/markdown/test/gfm/autolinks_extension.unit
@@ -0,0 +1,74 @@
+>>> Autolinks (extension) - 621
+www.commonmark.org
+<<<
+<p><a href="http://www.commonmark.org">www.commonmark.org</a></p>
+>>> Autolinks (extension) - 622
+Visit www.commonmark.org/help for more information.
+<<<
+<p>Visit <a href="http://www.commonmark.org/help">www.commonmark.org/help</a> for more information.</p>
+>>> Autolinks (extension) - 623
+Visit www.commonmark.org.
+
+Visit www.commonmark.org/a.b.
+<<<
+<p>Visit <a href="http://www.commonmark.org">www.commonmark.org</a>.</p>
+<p>Visit <a href="http://www.commonmark.org/a.b">www.commonmark.org/a.b</a>.</p>
+>>> Autolinks (extension) - 624
+www.google.com/search?q=Markup+(business)
+
+www.google.com/search?q=Markup+(business)))
+
+(www.google.com/search?q=Markup+(business))
+
+(www.google.com/search?q=Markup+(business)
+<<<
+<p><a href="http://www.google.com/search?q=Markup+(business)">www.google.com/search?q=Markup+(business)</a></p>
+<p><a href="http://www.google.com/search?q=Markup+(business)">www.google.com/search?q=Markup+(business)</a>))</p>
+<p>(<a href="http://www.google.com/search?q=Markup+(business)">www.google.com/search?q=Markup+(business)</a>)</p>
+<p>(<a href="http://www.google.com/search?q=Markup+(business)">www.google.com/search?q=Markup+(business)</a></p>
+>>> Autolinks (extension) - 625
+www.google.com/search?q=(business))+ok
+<<<
+<p><a href="http://www.google.com/search?q=(business))+ok">www.google.com/search?q=(business))+ok</a></p>
+>>> Autolinks (extension) - 626
+www.google.com/search?q=commonmark&hl=en
+
+www.google.com/search?q=commonmark&hl;
+<<<
+<p><a href="http://www.google.com/search?q=commonmark&hl=en">www.google.com/search?q=commonmark&hl=en</a></p>
+<p><a href="http://www.google.com/search?q=commonmark">www.google.com/search?q=commonmark</a>&hl;</p>
+>>> Autolinks (extension) - 627
+www.commonmark.org/he<lp
+<<<
+<p><a href="http://www.commonmark.org/he">www.commonmark.org/he</a><lp</p>
+>>> Autolinks (extension) - 628
+http://commonmark.org
+
+(Visit https://encrypted.google.com/search?q=Markup+(business))
+
+Anonymous FTP is available at ftp://foo.bar.baz.
+<<<
+<p><a href="http://commonmark.org">http://commonmark.org</a></p>
+<p>(Visit <a href="https://encrypted.google.com/search?q=Markup+(business)">https://encrypted.google.com/search?q=Markup+(business)</a>)</p>
+<p>Anonymous FTP is available at <a href="ftp://foo.bar.baz">ftp://foo.bar.baz</a>.</p>
+>>> Autolinks (extension) - 629
+foo@bar.baz
+<<<
+<p><a href="mailto:foo@bar.baz">foo@bar.baz</a></p>
+>>> Autolinks (extension) - 630
+hello@mail+xyz.example isn't valid, but hello+xyz@mail.example is.
+<<<
+<p>hello@mail+xyz.example isn't valid, but <a href="mailto:hello+xyz@mail.example">hello+xyz@mail.example</a> is.</p>
+>>> Autolinks (extension) - 631
+a.b-c_d@a.b
+
+a.b-c_d@a.b.
+
+a.b-c_d@a.b-
+
+a.b-c_d@a.b_
+<<<
+<p><a href="mailto:a.b-c_d@a.b">a.b-c_d@a.b</a></p>
+<p><a href="mailto:a.b-c_d@a.b">a.b-c_d@a.b</a>.</p>
+<p>a.b-c_d@a.b-</p>
+<p>a.b-c_d@a.b_</p>
diff --git a/pkgs/markdown/test/gfm/backslash_escapes.unit b/pkgs/markdown/test/gfm/backslash_escapes.unit
new file mode 100644
index 0000000..0e9074a
--- /dev/null
+++ b/pkgs/markdown/test/gfm/backslash_escapes.unit
@@ -0,0 +1,79 @@
+>>> Backslash escapes - 308
+\!\"\#\$\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\@\[\\\]\^\_\`\{\|\}\~
+<<<
+<p>!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~</p>
+>>> Backslash escapes - 309
+\ \A\a\ \3\φ\«
+<<<
+<p>\ \A\a\ \3\φ\«</p>
+>>> Backslash escapes - 310
+\*not emphasized*
+\<br/> not a tag
+\[not a link](/foo)
+\`not code`
+1\. not a list
+\* not a list
+\# not a heading
+\[foo]: /url "not a reference"
+\ö not a character entity
+<<<
+<p>*not emphasized*
+<br/> not a tag
+[not a link](/foo)
+`not code`
+1. not a list
+* not a list
+# not a heading
+[foo]: /url "not a reference"
+&ouml; not a character entity</p>
+>>> Backslash escapes - 311
+\\*emphasis*
+<<<
+<p>\<em>emphasis</em></p>
+>>> Backslash escapes - 312
+foo\
+bar
+<<<
+<p>foo<br />
+bar</p>
+>>> Backslash escapes - 313
+`` \[\` ``
+<<<
+<p><code>\[\`</code></p>
+>>> Backslash escapes - 314
+ \[\]
+<<<
+<pre><code>\[\]
+</code></pre>
+>>> Backslash escapes - 315
+~~~
+\[\]
+~~~
+<<<
+<pre><code>\[\]
+</code></pre>
+>>> Backslash escapes - 316
+<http://example.com?find=\*>
+<<<
+<p><a href="http://example.com?find=%5C*">http://example.com?find=\*</a></p>
+>>> Backslash escapes - 317
+<a href="/bar\/)">
+<<<
+<a href="/bar\/)">
+>>> Backslash escapes - 318
+[foo](/bar\* "ti\*tle")
+<<<
+<p><a href="/bar*" title="ti*tle">foo</a></p>
+>>> Backslash escapes - 319
+[foo]
+
+[foo]: /bar\* "ti\*tle"
+<<<
+<p><a href="/bar*" title="ti*tle">foo</a></p>
+>>> Backslash escapes - 320
+``` foo\+bar
+foo
+```
+<<<
+<pre><code class="language-foo+bar">foo
+</code></pre>
diff --git a/pkgs/markdown/test/gfm/blank_lines.unit b/pkgs/markdown/test/gfm/blank_lines.unit
new file mode 100644
index 0000000..be24ab7
--- /dev/null
+++ b/pkgs/markdown/test/gfm/blank_lines.unit
@@ -0,0 +1,12 @@
+>>> Blank lines - 197
+
+
+aaa
+
+
+# aaa
+
+
+<<<
+<p>aaa</p>
+<h1>aaa</h1>
diff --git a/pkgs/markdown/test/gfm/block_quotes.unit b/pkgs/markdown/test/gfm/block_quotes.unit
new file mode 100644
index 0000000..50d8757
--- /dev/null
+++ b/pkgs/markdown/test/gfm/block_quotes.unit
@@ -0,0 +1,239 @@
+>>> Block quotes - 206
+> # Foo
+> bar
+> baz
+<<<
+<blockquote>
+<h1>Foo</h1>
+<p>bar
+baz</p>
+</blockquote>
+>>> Block quotes - 207
+># Foo
+>bar
+> baz
+<<<
+<blockquote>
+<h1>Foo</h1>
+<p>bar
+baz</p>
+</blockquote>
+>>> Block quotes - 208
+ > # Foo
+ > bar
+ > baz
+<<<
+<blockquote>
+<h1>Foo</h1>
+<p>bar
+baz</p>
+</blockquote>
+>>> Block quotes - 209
+ > # Foo
+ > bar
+ > baz
+<<<
+<pre><code>> # Foo
+> bar
+> baz
+</code></pre>
+>>> Block quotes - 210
+> # Foo
+> bar
+baz
+<<<
+<blockquote>
+<h1>Foo</h1>
+<p>bar
+baz</p>
+</blockquote>
+>>> Block quotes - 211
+> bar
+baz
+> foo
+<<<
+<blockquote>
+<p>bar
+baz
+foo</p>
+</blockquote>
+>>> Block quotes - 212
+> foo
+---
+<<<
+<blockquote>
+<p>foo</p>
+</blockquote>
+<hr />
+>>> Block quotes - 213
+> - foo
+- bar
+<<<
+<blockquote>
+<ul>
+<li>foo</li>
+</ul>
+</blockquote>
+<ul>
+<li>bar</li>
+</ul>
+>>> Block quotes - 214
+> foo
+ bar
+<<<
+<blockquote>
+<pre><code>foo
+</code></pre>
+</blockquote>
+<pre><code>bar
+</code></pre>
+>>> Block quotes - 215
+> ```
+foo
+```
+<<<
+<blockquote>
+<pre><code></code></pre>
+</blockquote>
+<p>foo</p>
+<pre><code></code></pre>
+>>> Block quotes - 216
+> foo
+ - bar
+<<<
+<blockquote>
+<p>foo
+- bar</p>
+</blockquote>
+>>> Block quotes - 217
+>
+<<<
+<blockquote>
+</blockquote>
+>>> Block quotes - 218
+>
+>
+>
+<<<
+<blockquote>
+</blockquote>
+>>> Block quotes - 219
+>
+> foo
+>
+<<<
+<blockquote>
+<p>foo</p>
+</blockquote>
+>>> Block quotes - 220
+> foo
+
+> bar
+<<<
+<blockquote>
+<p>foo</p>
+</blockquote>
+<blockquote>
+<p>bar</p>
+</blockquote>
+>>> Block quotes - 221
+> foo
+> bar
+<<<
+<blockquote>
+<p>foo
+bar</p>
+</blockquote>
+>>> Block quotes - 222
+> foo
+>
+> bar
+<<<
+<blockquote>
+<p>foo</p>
+<p>bar</p>
+</blockquote>
+>>> Block quotes - 223
+foo
+> bar
+<<<
+<p>foo</p>
+<blockquote>
+<p>bar</p>
+</blockquote>
+>>> Block quotes - 224
+> aaa
+***
+> bbb
+<<<
+<blockquote>
+<p>aaa</p>
+</blockquote>
+<hr />
+<blockquote>
+<p>bbb</p>
+</blockquote>
+>>> Block quotes - 225
+> bar
+baz
+<<<
+<blockquote>
+<p>bar
+baz</p>
+</blockquote>
+>>> Block quotes - 226
+> bar
+
+baz
+<<<
+<blockquote>
+<p>bar</p>
+</blockquote>
+<p>baz</p>
+>>> Block quotes - 227
+> bar
+>
+baz
+<<<
+<blockquote>
+<p>bar</p>
+</blockquote>
+<p>baz</p>
+>>> Block quotes - 228
+> > > foo
+bar
+<<<
+<blockquote>
+<blockquote>
+<blockquote>
+<p>foo
+bar</p>
+</blockquote>
+</blockquote>
+</blockquote>
+>>> Block quotes - 229
+>>> foo
+> bar
+>>baz
+<<<
+<blockquote>
+<blockquote>
+<blockquote>
+<p>foo
+bar
+baz</p>
+</blockquote>
+</blockquote>
+</blockquote>
+>>> Block quotes - 230
+> code
+
+> not code
+<<<
+<blockquote>
+<pre><code>code
+</code></pre>
+</blockquote>
+<blockquote>
+<p>not code</p>
+</blockquote>
diff --git a/pkgs/markdown/test/gfm/code_spans.unit b/pkgs/markdown/test/gfm/code_spans.unit
new file mode 100644
index 0000000..68a4b3a
--- /dev/null
+++ b/pkgs/markdown/test/gfm/code_spans.unit
@@ -0,0 +1,97 @@
+>>> Code spans - 338
+`foo`
+<<<
+<p><code>foo</code></p>
+>>> Code spans - 339
+`` foo ` bar ``
+<<<
+<p><code>foo ` bar</code></p>
+>>> Code spans - 340
+` `` `
+<<<
+<p><code>``</code></p>
+>>> Code spans - 341
+` `` `
+<<<
+<p><code> `` </code></p>
+>>> Code spans - 342
+` a`
+<<<
+<p><code> a</code></p>
+>>> Code spans - 343
+` b `
+<<<
+<p><code> b </code></p>
+>>> Code spans - 344
+` `
+` `
+<<<
+<p><code> </code>
+<code> </code></p>
+>>> Code spans - 345
+``
+foo
+bar
+baz
+``
+<<<
+<p><code>foo bar baz</code></p>
+>>> Code spans - 346
+``
+foo
+``
+<<<
+<p><code>foo </code></p>
+>>> Code spans - 347
+`foo bar
+baz`
+<<<
+<p><code>foo bar baz</code></p>
+>>> Code spans - 348
+`foo\`bar`
+<<<
+<p><code>foo\</code>bar`</p>
+>>> Code spans - 349
+``foo`bar``
+<<<
+<p><code>foo`bar</code></p>
+>>> Code spans - 350
+` foo `` bar `
+<<<
+<p><code>foo `` bar</code></p>
+>>> Code spans - 351
+*foo`*`
+<<<
+<p>*foo<code>*</code></p>
+>>> Code spans - 352
+[not a `link](/foo`)
+<<<
+<p>[not a <code>link](/foo</code>)</p>
+>>> Code spans - 353
+`<a href="`">`
+<<<
+<p><code><a href="</code>">`</p>
+>>> Code spans - 354
+<a href="`">`
+<<<
+<p><a href="`">`</p>
+>>> Code spans - 355
+`<http://foo.bar.`baz>`
+<<<
+<p><code><http://foo.bar.</code>baz>`</p>
+>>> Code spans - 356
+<http://foo.bar.`baz>`
+<<<
+<p><a href="http://foo.bar.%60baz">http://foo.bar.`baz</a>`</p>
+>>> Code spans - 357
+```foo``
+<<<
+<p>```foo``</p>
+>>> Code spans - 358
+`foo
+<<<
+<p>`foo</p>
+>>> Code spans - 359
+`foo``bar``
+<<<
+<p>`foo<code>bar</code></p>
diff --git a/pkgs/markdown/test/gfm/disallowed_raw_html_extension.unit b/pkgs/markdown/test/gfm/disallowed_raw_html_extension.unit
new file mode 100644
index 0000000..34cc396
--- /dev/null
+++ b/pkgs/markdown/test/gfm/disallowed_raw_html_extension.unit
@@ -0,0 +1,11 @@
+>>> Disallowed Raw HTML (extension) - 652
+<strong> <title> <style> <em>
+
+<blockquote>
+ <xmp> is disallowed. <XMP> is also disallowed.
+</blockquote>
+<<<
+<p><strong> <title> <style> <em></p>
+<blockquote>
+<xmp> is disallowed. <XMP> is also disallowed.
+</blockquote>
diff --git a/pkgs/markdown/test/gfm/emphasis_and_strong_emphasis.unit b/pkgs/markdown/test/gfm/emphasis_and_strong_emphasis.unit
new file mode 100644
index 0000000..b6e8155
--- /dev/null
+++ b/pkgs/markdown/test/gfm/emphasis_and_strong_emphasis.unit
@@ -0,0 +1,536 @@
+>>> Emphasis and strong emphasis - 360
+*foo bar*
+<<<
+<p><em>foo bar</em></p>
+>>> Emphasis and strong emphasis - 361
+a * foo bar*
+<<<
+<p>a * foo bar*</p>
+>>> Emphasis and strong emphasis - 362
+a*"foo"*
+<<<
+<p>a*"foo"*</p>
+>>> Emphasis and strong emphasis - 363
+* a *
+<<<
+<p>* a *</p>
+>>> Emphasis and strong emphasis - 364
+foo*bar*
+<<<
+<p>foo<em>bar</em></p>
+>>> Emphasis and strong emphasis - 365
+5*6*78
+<<<
+<p>5<em>6</em>78</p>
+>>> Emphasis and strong emphasis - 366
+_foo bar_
+<<<
+<p><em>foo bar</em></p>
+>>> Emphasis and strong emphasis - 367
+_ foo bar_
+<<<
+<p>_ foo bar_</p>
+>>> Emphasis and strong emphasis - 368
+a_"foo"_
+<<<
+<p>a_"foo"_</p>
+>>> Emphasis and strong emphasis - 369
+foo_bar_
+<<<
+<p>foo_bar_</p>
+>>> Emphasis and strong emphasis - 370
+5_6_78
+<<<
+<p>5_6_78</p>
+>>> Emphasis and strong emphasis - 371
+пристаням_стремятся_
+<<<
+<p>пристаням_стремятся_</p>
+>>> Emphasis and strong emphasis - 372
+aa_"bb"_cc
+<<<
+<p>aa_"bb"_cc</p>
+>>> Emphasis and strong emphasis - 373
+foo-_(bar)_
+<<<
+<p>foo-<em>(bar)</em></p>
+>>> Emphasis and strong emphasis - 374
+_foo*
+<<<
+<p>_foo*</p>
+>>> Emphasis and strong emphasis - 375
+*foo bar *
+<<<
+<p>*foo bar *</p>
+>>> Emphasis and strong emphasis - 376
+*foo bar
+*
+<<<
+<p>*foo bar
+*</p>
+>>> Emphasis and strong emphasis - 377
+*(*foo)
+<<<
+<p>*(*foo)</p>
+>>> Emphasis and strong emphasis - 378
+*(*foo*)*
+<<<
+<p><em>(<em>foo</em>)</em></p>
+>>> Emphasis and strong emphasis - 379
+*foo*bar
+<<<
+<p><em>foo</em>bar</p>
+>>> Emphasis and strong emphasis - 380
+_foo bar _
+<<<
+<p>_foo bar _</p>
+>>> Emphasis and strong emphasis - 381
+_(_foo)
+<<<
+<p>_(_foo)</p>
+>>> Emphasis and strong emphasis - 382
+_(_foo_)_
+<<<
+<p><em>(<em>foo</em>)</em></p>
+>>> Emphasis and strong emphasis - 383
+_foo_bar
+<<<
+<p>_foo_bar</p>
+>>> Emphasis and strong emphasis - 384
+_пристаням_стремятся
+<<<
+<p>_пристаням_стремятся</p>
+>>> Emphasis and strong emphasis - 385
+_foo_bar_baz_
+<<<
+<p><em>foo_bar_baz</em></p>
+>>> Emphasis and strong emphasis - 386
+_(bar)_.
+<<<
+<p><em>(bar)</em>.</p>
+>>> Emphasis and strong emphasis - 387
+**foo bar**
+<<<
+<p><strong>foo bar</strong></p>
+>>> Emphasis and strong emphasis - 388
+** foo bar**
+<<<
+<p>** foo bar**</p>
+>>> Emphasis and strong emphasis - 389
+a**"foo"**
+<<<
+<p>a**"foo"**</p>
+>>> Emphasis and strong emphasis - 390
+foo**bar**
+<<<
+<p>foo<strong>bar</strong></p>
+>>> Emphasis and strong emphasis - 391
+__foo bar__
+<<<
+<p><strong>foo bar</strong></p>
+>>> Emphasis and strong emphasis - 392
+__ foo bar__
+<<<
+<p>__ foo bar__</p>
+>>> Emphasis and strong emphasis - 393
+__
+foo bar__
+<<<
+<p>__
+foo bar__</p>
+>>> Emphasis and strong emphasis - 394
+a__"foo"__
+<<<
+<p>a__"foo"__</p>
+>>> Emphasis and strong emphasis - 395
+foo__bar__
+<<<
+<p>foo__bar__</p>
+>>> Emphasis and strong emphasis - 396
+5__6__78
+<<<
+<p>5__6__78</p>
+>>> Emphasis and strong emphasis - 397
+пристаням__стремятся__
+<<<
+<p>пристаням__стремятся__</p>
+>>> Emphasis and strong emphasis - 398
+__foo, __bar__, baz__
+<<<
+<p><strong>foo, <strong>bar</strong>, baz</strong></p>
+>>> Emphasis and strong emphasis - 399
+foo-__(bar)__
+<<<
+<p>foo-<strong>(bar)</strong></p>
+>>> Emphasis and strong emphasis - 400
+**foo bar **
+<<<
+<p>**foo bar **</p>
+>>> Emphasis and strong emphasis - 401
+**(**foo)
+<<<
+<p>**(**foo)</p>
+>>> Emphasis and strong emphasis - 402
+*(**foo**)*
+<<<
+<p><em>(<strong>foo</strong>)</em></p>
+>>> Emphasis and strong emphasis - 403
+**Gomphocarpus (*Gomphocarpus physocarpus*, syn.
+*Asclepias physocarpa*)**
+<<<
+<p><strong>Gomphocarpus (<em>Gomphocarpus physocarpus</em>, syn.
+<em>Asclepias physocarpa</em>)</strong></p>
+>>> Emphasis and strong emphasis - 404
+**foo "*bar*" foo**
+<<<
+<p><strong>foo "<em>bar</em>" foo</strong></p>
+>>> Emphasis and strong emphasis - 405
+**foo**bar
+<<<
+<p><strong>foo</strong>bar</p>
+>>> Emphasis and strong emphasis - 406
+__foo bar __
+<<<
+<p>__foo bar __</p>
+>>> Emphasis and strong emphasis - 407
+__(__foo)
+<<<
+<p>__(__foo)</p>
+>>> Emphasis and strong emphasis - 408
+_(__foo__)_
+<<<
+<p><em>(<strong>foo</strong>)</em></p>
+>>> Emphasis and strong emphasis - 409
+__foo__bar
+<<<
+<p>__foo__bar</p>
+>>> Emphasis and strong emphasis - 410
+__пристаням__стремятся
+<<<
+<p>__пристаням__стремятся</p>
+>>> Emphasis and strong emphasis - 411
+__foo__bar__baz__
+<<<
+<p><strong>foo__bar__baz</strong></p>
+>>> Emphasis and strong emphasis - 412
+__(bar)__.
+<<<
+<p><strong>(bar)</strong>.</p>
+>>> Emphasis and strong emphasis - 413
+*foo [bar](/url)*
+<<<
+<p><em>foo <a href="/url">bar</a></em></p>
+>>> Emphasis and strong emphasis - 414
+*foo
+bar*
+<<<
+<p><em>foo
+bar</em></p>
+>>> Emphasis and strong emphasis - 415
+_foo __bar__ baz_
+<<<
+<p><em>foo <strong>bar</strong> baz</em></p>
+>>> Emphasis and strong emphasis - 416
+_foo _bar_ baz_
+<<<
+<p><em>foo <em>bar</em> baz</em></p>
+>>> Emphasis and strong emphasis - 417
+__foo_ bar_
+<<<
+<p><em><em>foo</em> bar</em></p>
+>>> Emphasis and strong emphasis - 418
+*foo *bar**
+<<<
+<p><em>foo <em>bar</em></em></p>
+>>> Emphasis and strong emphasis - 419
+*foo **bar** baz*
+<<<
+<p><em>foo <strong>bar</strong> baz</em></p>
+>>> Emphasis and strong emphasis - 420
+*foo**bar**baz*
+<<<
+<p><em>foo<strong>bar</strong>baz</em></p>
+>>> Emphasis and strong emphasis - 421
+*foo**bar*
+<<<
+<p><em>foo**bar</em></p>
+>>> Emphasis and strong emphasis - 422
+***foo** bar*
+<<<
+<p><em><strong>foo</strong> bar</em></p>
+>>> Emphasis and strong emphasis - 423
+*foo **bar***
+<<<
+<p><em>foo <strong>bar</strong></em></p>
+>>> Emphasis and strong emphasis - 424
+*foo**bar***
+<<<
+<p><em>foo<strong>bar</strong></em></p>
+>>> Emphasis and strong emphasis - 425
+foo***bar***baz
+<<<
+<p>foo<em><strong>bar</strong></em>baz</p>
+>>> Emphasis and strong emphasis - 426
+foo******bar*********baz
+<<<
+<p>foo<strong><strong><strong>bar</strong></strong></strong>***baz</p>
+>>> Emphasis and strong emphasis - 427
+*foo **bar *baz* bim** bop*
+<<<
+<p><em>foo <strong>bar <em>baz</em> bim</strong> bop</em></p>
+>>> Emphasis and strong emphasis - 428
+*foo [*bar*](/url)*
+<<<
+<p><em>foo <a href="/url"><em>bar</em></a></em></p>
+>>> Emphasis and strong emphasis - 429
+** is not an empty emphasis
+<<<
+<p>** is not an empty emphasis</p>
+>>> Emphasis and strong emphasis - 430
+**** is not an empty strong emphasis
+<<<
+<p>**** is not an empty strong emphasis</p>
+>>> Emphasis and strong emphasis - 431
+**foo [bar](/url)**
+<<<
+<p><strong>foo <a href="/url">bar</a></strong></p>
+>>> Emphasis and strong emphasis - 432
+**foo
+bar**
+<<<
+<p><strong>foo
+bar</strong></p>
+>>> Emphasis and strong emphasis - 433
+__foo _bar_ baz__
+<<<
+<p><strong>foo <em>bar</em> baz</strong></p>
+>>> Emphasis and strong emphasis - 434
+__foo __bar__ baz__
+<<<
+<p><strong>foo <strong>bar</strong> baz</strong></p>
+>>> Emphasis and strong emphasis - 435
+____foo__ bar__
+<<<
+<p><strong><strong>foo</strong> bar</strong></p>
+>>> Emphasis and strong emphasis - 436
+**foo **bar****
+<<<
+<p><strong>foo <strong>bar</strong></strong></p>
+>>> Emphasis and strong emphasis - 437
+**foo *bar* baz**
+<<<
+<p><strong>foo <em>bar</em> baz</strong></p>
+>>> Emphasis and strong emphasis - 438
+**foo*bar*baz**
+<<<
+<p><strong>foo<em>bar</em>baz</strong></p>
+>>> Emphasis and strong emphasis - 439
+***foo* bar**
+<<<
+<p><strong><em>foo</em> bar</strong></p>
+>>> Emphasis and strong emphasis - 440
+**foo *bar***
+<<<
+<p><strong>foo <em>bar</em></strong></p>
+>>> Emphasis and strong emphasis - 441
+**foo *bar **baz**
+bim* bop**
+<<<
+<p><strong>foo <em>bar <strong>baz</strong>
+bim</em> bop</strong></p>
+>>> Emphasis and strong emphasis - 442
+**foo [*bar*](/url)**
+<<<
+<p><strong>foo <a href="/url"><em>bar</em></a></strong></p>
+>>> Emphasis and strong emphasis - 443
+__ is not an empty emphasis
+<<<
+<p>__ is not an empty emphasis</p>
+>>> Emphasis and strong emphasis - 444
+____ is not an empty strong emphasis
+<<<
+<p>____ is not an empty strong emphasis</p>
+>>> Emphasis and strong emphasis - 445
+foo ***
+<<<
+<p>foo ***</p>
+>>> Emphasis and strong emphasis - 446
+foo *\**
+<<<
+<p>foo <em>*</em></p>
+>>> Emphasis and strong emphasis - 447
+foo *_*
+<<<
+<p>foo <em>_</em></p>
+>>> Emphasis and strong emphasis - 448
+foo *****
+<<<
+<p>foo *****</p>
+>>> Emphasis and strong emphasis - 449
+foo **\***
+<<<
+<p>foo <strong>*</strong></p>
+>>> Emphasis and strong emphasis - 450
+foo **_**
+<<<
+<p>foo <strong>_</strong></p>
+>>> Emphasis and strong emphasis - 451
+**foo*
+<<<
+<p>*<em>foo</em></p>
+>>> Emphasis and strong emphasis - 452
+*foo**
+<<<
+<p><em>foo</em>*</p>
+>>> Emphasis and strong emphasis - 453
+***foo**
+<<<
+<p>*<strong>foo</strong></p>
+>>> Emphasis and strong emphasis - 454
+****foo*
+<<<
+<p>***<em>foo</em></p>
+>>> Emphasis and strong emphasis - 455
+**foo***
+<<<
+<p><strong>foo</strong>*</p>
+>>> Emphasis and strong emphasis - 456
+*foo****
+<<<
+<p><em>foo</em>***</p>
+>>> Emphasis and strong emphasis - 457
+foo ___
+<<<
+<p>foo ___</p>
+>>> Emphasis and strong emphasis - 458
+foo _\__
+<<<
+<p>foo <em>_</em></p>
+>>> Emphasis and strong emphasis - 459
+foo _*_
+<<<
+<p>foo <em>*</em></p>
+>>> Emphasis and strong emphasis - 460
+foo _____
+<<<
+<p>foo _____</p>
+>>> Emphasis and strong emphasis - 461
+foo __\___
+<<<
+<p>foo <strong>_</strong></p>
+>>> Emphasis and strong emphasis - 462
+foo __*__
+<<<
+<p>foo <strong>*</strong></p>
+>>> Emphasis and strong emphasis - 463
+__foo_
+<<<
+<p>_<em>foo</em></p>
+>>> Emphasis and strong emphasis - 464
+_foo__
+<<<
+<p><em>foo</em>_</p>
+>>> Emphasis and strong emphasis - 465
+___foo__
+<<<
+<p>_<strong>foo</strong></p>
+>>> Emphasis and strong emphasis - 466
+____foo_
+<<<
+<p>___<em>foo</em></p>
+>>> Emphasis and strong emphasis - 467
+__foo___
+<<<
+<p><strong>foo</strong>_</p>
+>>> Emphasis and strong emphasis - 468
+_foo____
+<<<
+<p><em>foo</em>___</p>
+>>> Emphasis and strong emphasis - 469
+**foo**
+<<<
+<p><strong>foo</strong></p>
+>>> Emphasis and strong emphasis - 470
+*_foo_*
+<<<
+<p><em><em>foo</em></em></p>
+>>> Emphasis and strong emphasis - 471
+__foo__
+<<<
+<p><strong>foo</strong></p>
+>>> Emphasis and strong emphasis - 472
+_*foo*_
+<<<
+<p><em><em>foo</em></em></p>
+>>> Emphasis and strong emphasis - 473
+****foo****
+<<<
+<p><strong><strong>foo</strong></strong></p>
+>>> Emphasis and strong emphasis - 474
+____foo____
+<<<
+<p><strong><strong>foo</strong></strong></p>
+>>> Emphasis and strong emphasis - 475
+******foo******
+<<<
+<p><strong><strong><strong>foo</strong></strong></strong></p>
+>>> Emphasis and strong emphasis - 476
+***foo***
+<<<
+<p><em><strong>foo</strong></em></p>
+>>> Emphasis and strong emphasis - 477
+_____foo_____
+<<<
+<p><em><strong><strong>foo</strong></strong></em></p>
+>>> Emphasis and strong emphasis - 478
+*foo _bar* baz_
+<<<
+<p><em>foo _bar</em> baz_</p>
+>>> Emphasis and strong emphasis - 479
+*foo __bar *baz bim__ bam*
+<<<
+<p><em>foo <strong>bar *baz bim</strong> bam</em></p>
+>>> Emphasis and strong emphasis - 480
+**foo **bar baz**
+<<<
+<p>**foo <strong>bar baz</strong></p>
+>>> Emphasis and strong emphasis - 481
+*foo *bar baz*
+<<<
+<p>*foo <em>bar baz</em></p>
+>>> Emphasis and strong emphasis - 482
+*[bar*](/url)
+<<<
+<p>*<a href="/url">bar*</a></p>
+>>> Emphasis and strong emphasis - 483
+_foo [bar_](/url)
+<<<
+<p>_foo <a href="/url">bar_</a></p>
+>>> Emphasis and strong emphasis - 484
+*<img src="foo" title="*"/>
+<<<
+<p>*<img src="foo" title="*"/></p>
+>>> Emphasis and strong emphasis - 485
+**<a href="**">
+<<<
+<p>**<a href="**"></p>
+>>> Emphasis and strong emphasis - 486
+__<a href="__">
+<<<
+<p>__<a href="__"></p>
+>>> Emphasis and strong emphasis - 487
+*a `*`*
+<<<
+<p><em>a <code>*</code></em></p>
+>>> Emphasis and strong emphasis - 488
+_a `_`_
+<<<
+<p><em>a <code>_</code></em></p>
+>>> Emphasis and strong emphasis - 489
+**a<http://foo.bar/?q=**>
+<<<
+<p>**a<a href="http://foo.bar/?q=**">http://foo.bar/?q=**</a></p>
+>>> Emphasis and strong emphasis - 490
+__a<http://foo.bar/?q=__>
+<<<
+<p>__a<a href="http://foo.bar/?q=__">http://foo.bar/?q=__</a></p>
diff --git a/pkgs/markdown/test/gfm/entity_and_numeric_character_references.unit b/pkgs/markdown/test/gfm/entity_and_numeric_character_references.unit
new file mode 100644
index 0000000..2a6248e
--- /dev/null
+++ b/pkgs/markdown/test/gfm/entity_and_numeric_character_references.unit
@@ -0,0 +1,93 @@
+>>> Entity and numeric character references - 321
+ & © Æ Ď
+¾ ℋ ⅆ
+∲ ≧̸
+<<<
+<p>& © Æ Ď
+¾ ℋ ⅆ
+∲ ≧̸</p>
+>>> Entity and numeric character references - 322
+# Ӓ Ϡ �
+<<<
+<p># Ӓ Ϡ �</p>
+>>> Entity and numeric character references - 323
+" ആ ಫ
+<<<
+<p>" ആ ಫ</p>
+>>> Entity and numeric character references - 324
+  &x; &#; &#x;
+�
+&#abcdef0;
+&ThisIsNotDefined; &hi?;
+<<<
+<p>&nbsp &x; &#; &#x;
+&#987654321;
+&#abcdef0;
+&ThisIsNotDefined; &hi?;</p>
+>>> Entity and numeric character references - 325
+©
+<<<
+<p>&copy</p>
+>>> Entity and numeric character references - 326
+&MadeUpEntity;
+<<<
+<p>&MadeUpEntity;</p>
+>>> Entity and numeric character references - 327
+<a href="öö.html">
+<<<
+<a href="öö.html">
+>>> Entity and numeric character references - 328
+[foo](/föö "föö")
+<<<
+<p><a href="/f%C3%B6%C3%B6" title="föö">foo</a></p>
+>>> Entity and numeric character references - 329
+[foo]
+
+[foo]: /föö "föö"
+<<<
+<p><a href="/f%C3%B6%C3%B6" title="föö">foo</a></p>
+>>> Entity and numeric character references - 330
+``` föö
+foo
+```
+<<<
+<pre><code class="language-föö">foo
+</code></pre>
+>>> Entity and numeric character references - 331
+`föö`
+<<<
+<p><code>f&ouml;&ouml;</code></p>
+>>> Entity and numeric character references - 332
+ föfö
+<<<
+<pre><code>f&ouml;f&ouml;
+</code></pre>
+>>> Entity and numeric character references - 333
+*foo*
+*foo*
+<<<
+<p>*foo*
+<em>foo</em></p>
+>>> Entity and numeric character references - 334
+* foo
+
+* foo
+<<<
+<p>* foo</p>
+<ul>
+<li>foo</li>
+</ul>
+>>> Entity and numeric character references - 335
+foo bar
+<<<
+<p>foo
+
+bar</p>
+>>> Entity and numeric character references - 336
+	foo
+<<<
+<p>foo</p>
+>>> Entity and numeric character references - 337
+[a](url "tit")
+<<<
+<p>[a](url "tit")</p>
diff --git a/pkgs/markdown/test/gfm/fenced_code_blocks.unit b/pkgs/markdown/test/gfm/fenced_code_blocks.unit
new file mode 100644
index 0000000..f6fa843
--- /dev/null
+++ b/pkgs/markdown/test/gfm/fenced_code_blocks.unit
@@ -0,0 +1,245 @@
+>>> Fenced code blocks - 89
+```
+<
+ >
+```
+<<<
+<pre><code><
+ >
+</code></pre>
+>>> Fenced code blocks - 90
+~~~
+<
+ >
+~~~
+<<<
+<pre><code><
+ >
+</code></pre>
+>>> Fenced code blocks - 91
+``
+foo
+``
+<<<
+<p><code>foo</code></p>
+>>> Fenced code blocks - 92
+```
+aaa
+~~~
+```
+<<<
+<pre><code>aaa
+~~~
+</code></pre>
+>>> Fenced code blocks - 93
+~~~
+aaa
+```
+~~~
+<<<
+<pre><code>aaa
+```
+</code></pre>
+>>> Fenced code blocks - 94
+````
+aaa
+```
+``````
+<<<
+<pre><code>aaa
+```
+</code></pre>
+>>> Fenced code blocks - 95
+~~~~
+aaa
+~~~
+~~~~
+<<<
+<pre><code>aaa
+~~~
+</code></pre>
+>>> Fenced code blocks - 96
+```
+<<<
+<pre><code></code></pre>
+>>> Fenced code blocks - 97
+`````
+
+```
+aaa
+<<<
+<pre><code>
+```
+aaa
+</code></pre>
+>>> Fenced code blocks - 98
+> ```
+> aaa
+
+bbb
+<<<
+<blockquote>
+<pre><code>aaa
+</code></pre>
+</blockquote>
+<p>bbb</p>
+>>> Fenced code blocks - 99
+```
+
+
+```
+<<<
+<pre><code>
+
+</code></pre>
+>>> Fenced code blocks - 100
+```
+```
+<<<
+<pre><code></code></pre>
+>>> Fenced code blocks - 101
+ ```
+ aaa
+aaa
+```
+<<<
+<pre><code>aaa
+aaa
+</code></pre>
+>>> Fenced code blocks - 102
+ ```
+aaa
+ aaa
+aaa
+ ```
+<<<
+<pre><code>aaa
+aaa
+aaa
+</code></pre>
+>>> Fenced code blocks - 103
+ ```
+ aaa
+ aaa
+ aaa
+ ```
+<<<
+<pre><code>aaa
+ aaa
+aaa
+</code></pre>
+>>> Fenced code blocks - 104
+ ```
+ aaa
+ ```
+<<<
+<pre><code>```
+aaa
+```
+</code></pre>
+>>> Fenced code blocks - 105
+```
+aaa
+ ```
+<<<
+<pre><code>aaa
+</code></pre>
+>>> Fenced code blocks - 106
+ ```
+aaa
+ ```
+<<<
+<pre><code>aaa
+</code></pre>
+>>> Fenced code blocks - 107
+```
+aaa
+ ```
+<<<
+<pre><code>aaa
+ ```
+</code></pre>
+>>> Fenced code blocks - 108
+``` ```
+aaa
+<<<
+<p><code> </code>
+aaa</p>
+>>> Fenced code blocks - 109
+~~~~~~
+aaa
+~~~ ~~
+<<<
+<pre><code>aaa
+~~~ ~~
+</code></pre>
+>>> Fenced code blocks - 110
+foo
+```
+bar
+```
+baz
+<<<
+<p>foo</p>
+<pre><code>bar
+</code></pre>
+<p>baz</p>
+>>> Fenced code blocks - 111
+foo
+---
+~~~
+bar
+~~~
+# baz
+<<<
+<h2>foo</h2>
+<pre><code>bar
+</code></pre>
+<h1>baz</h1>
+>>> Fenced code blocks - 112
+```ruby
+def foo(x)
+ return 3
+end
+```
+<<<
+<pre><code class="language-ruby">def foo(x)
+ return 3
+end
+</code></pre>
+>>> Fenced code blocks - 113
+~~~~ ruby startline=3 $%@#$
+def foo(x)
+ return 3
+end
+~~~~~~~
+<<<
+<pre><code class="language-ruby">def foo(x)
+ return 3
+end
+</code></pre>
+>>> Fenced code blocks - 114
+````;
+````
+<<<
+<pre><code class="language-;"></code></pre>
+>>> Fenced code blocks - 115
+``` aa ```
+foo
+<<<
+<p><code>aa</code>
+foo</p>
+>>> Fenced code blocks - 116
+~~~ aa ``` ~~~
+foo
+~~~
+<<<
+<pre><code class="language-aa">foo
+</code></pre>
+>>> Fenced code blocks - 117
+```
+``` aaa
+```
+<<<
+<pre><code>``` aaa
+</code></pre>
diff --git a/pkgs/markdown/test/gfm/hard_line_breaks.unit b/pkgs/markdown/test/gfm/hard_line_breaks.unit
new file mode 100644
index 0000000..e8aa5c0
--- /dev/null
+++ b/pkgs/markdown/test/gfm/hard_line_breaks.unit
@@ -0,0 +1,80 @@
+>>> Hard line breaks - 653
+foo
+baz
+<<<
+<p>foo<br />
+baz</p>
+>>> Hard line breaks - 654
+foo\
+baz
+<<<
+<p>foo<br />
+baz</p>
+>>> Hard line breaks - 655
+foo
+baz
+<<<
+<p>foo<br />
+baz</p>
+>>> Hard line breaks - 656
+foo
+ bar
+<<<
+<p>foo<br />
+bar</p>
+>>> Hard line breaks - 657
+foo\
+ bar
+<<<
+<p>foo<br />
+bar</p>
+>>> Hard line breaks - 658
+*foo
+bar*
+<<<
+<p><em>foo<br />
+bar</em></p>
+>>> Hard line breaks - 659
+*foo\
+bar*
+<<<
+<p><em>foo<br />
+bar</em></p>
+>>> Hard line breaks - 660
+`code
+span`
+<<<
+<p><code>code span</code></p>
+>>> Hard line breaks - 661
+`code\
+span`
+<<<
+<p><code>code\ span</code></p>
+>>> Hard line breaks - 662
+<a href="foo
+bar">
+<<<
+<p><a href="foo
+bar"></p>
+>>> Hard line breaks - 663
+<a href="foo\
+bar">
+<<<
+<p><a href="foo\
+bar"></p>
+>>> Hard line breaks - 664
+foo\
+<<<
+<p>foo\</p>
+>>> Hard line breaks - 665
+foo
+<<<
+<p>foo</p>
+>>> Hard line breaks - 666
+### foo\
+<<<
+<h3>foo\</h3>
+>>> Hard line breaks - 667
+### foo
+<<<
+<h3>foo</h3>
diff --git a/pkgs/markdown/test/gfm/html_blocks.unit b/pkgs/markdown/test/gfm/html_blocks.unit
new file mode 100644
index 0000000..1af6420
--- /dev/null
+++ b/pkgs/markdown/test/gfm/html_blocks.unit
@@ -0,0 +1,433 @@
+>>> HTML blocks - 118
+<table><tr><td>
+<pre>
+**Hello**,
+
+_world_.
+</pre>
+</td></tr></table>
+<<<
+<table><tr><td>
+<pre>
+**Hello**,
+<p><em>world</em>.
+</pre></p>
+</td></tr></table>
+>>> HTML blocks - 119
+<table>
+ <tr>
+ <td>
+ hi
+ </td>
+ </tr>
+</table>
+
+okay.
+<<<
+<table>
+ <tr>
+ <td>
+ hi
+ </td>
+ </tr>
+</table>
+<p>okay.</p>
+>>> HTML blocks - 120
+ <div>
+ *hello*
+ <foo><a>
+<<<
+ <div>
+ *hello*
+ <foo><a>
+>>> HTML blocks - 121
+</div>
+*foo*
+<<<
+</div>
+*foo*
+>>> HTML blocks - 122
+<DIV CLASS="foo">
+
+*Markdown*
+
+</DIV>
+<<<
+<DIV CLASS="foo">
+<p><em>Markdown</em></p>
+</DIV>
+>>> HTML blocks - 123
+<div id="foo"
+ class="bar">
+</div>
+<<<
+<div id="foo"
+ class="bar">
+</div>
+>>> HTML blocks - 124
+<div id="foo" class="bar
+ baz">
+</div>
+<<<
+<div id="foo" class="bar
+ baz">
+</div>
+>>> HTML blocks - 125
+<div>
+*foo*
+
+*bar*
+<<<
+<div>
+*foo*
+<p><em>bar</em></p>
+>>> HTML blocks - 126
+<div id="foo"
+*hi*
+<<<
+<div id="foo"
+*hi*
+>>> HTML blocks - 127
+<div class
+foo
+<<<
+<div class
+foo
+>>> HTML blocks - 128
+<div *???-&&&-<---
+*foo*
+<<<
+<div *???-&&&-<---
+*foo*
+>>> HTML blocks - 129
+<div><a href="bar">*foo*</a></div>
+<<<
+<div><a href="bar">*foo*</a></div>
+>>> HTML blocks - 130
+<table><tr><td>
+foo
+</td></tr></table>
+<<<
+<table><tr><td>
+foo
+</td></tr></table>
+>>> HTML blocks - 131
+<div></div>
+``` c
+int x = 33;
+```
+<<<
+<div></div>
+``` c
+int x = 33;
+```
+>>> HTML blocks - 132
+<a href="foo">
+*bar*
+</a>
+<<<
+<a href="foo">
+*bar*
+</a>
+>>> HTML blocks - 133
+<Warning>
+*bar*
+</Warning>
+<<<
+<Warning>
+*bar*
+</Warning>
+>>> HTML blocks - 134
+<i class="foo">
+*bar*
+</i>
+<<<
+<i class="foo">
+*bar*
+</i>
+>>> HTML blocks - 135
+</ins>
+*bar*
+<<<
+</ins>
+*bar*
+>>> HTML blocks - 136
+<del>
+*foo*
+</del>
+<<<
+<del>
+*foo*
+</del>
+>>> HTML blocks - 137
+<del>
+
+*foo*
+
+</del>
+<<<
+<del>
+<p><em>foo</em></p>
+</del>
+>>> HTML blocks - 138
+<del>*foo*</del>
+<<<
+<p><del><em>foo</em></del></p>
+>>> HTML blocks - 139
+<pre language="haskell"><code>
+import Text.HTML.TagSoup
+
+main :: IO ()
+main = print $ parseTags tags
+</code></pre>
+okay
+<<<
+<pre language="haskell"><code>
+import Text.HTML.TagSoup
+
+main :: IO ()
+main = print $ parseTags tags
+</code></pre>
+<p>okay</p>
+>>> HTML blocks - 140
+<script type="text/javascript">
+// JavaScript example
+
+document.getElementById("demo").innerHTML = "Hello JavaScript!";
+</script>
+okay
+<<<
+<script type="text/javascript">
+// JavaScript example
+
+document.getElementById("demo").innerHTML = "Hello JavaScript!";
+</script>
+<p>okay</p>
+>>> HTML blocks - 141
+<style
+ type="text/css">
+h1 {color:red;}
+
+p {color:blue;}
+</style>
+okay
+<<<
+<style
+ type="text/css">
+h1 {color:red;}
+
+p {color:blue;}
+</style>
+<p>okay</p>
+>>> HTML blocks - 142
+<style
+ type="text/css">
+
+foo
+<<<
+<style
+ type="text/css">
+
+foo
+>>> HTML blocks - 143
+> <div>
+> foo
+
+bar
+<<<
+<blockquote>
+<div>
+foo
+</blockquote>
+<p>bar</p>
+>>> HTML blocks - 144
+- <div>
+- foo
+<<<
+<ul>
+<li>
+<div>
+</li>
+<li>foo</li>
+</ul>
+>>> HTML blocks - 145
+<style>p{color:red;}</style>
+*foo*
+<<<
+<style>p{color:red;}</style>
+<p><em>foo</em></p>
+>>> HTML blocks - 146
+<!-- foo -->*bar*
+*baz*
+<<<
+<!-- foo -->*bar*
+<p><em>baz</em></p>
+>>> HTML blocks - 147
+<script>
+foo
+</script>1. *bar*
+<<<
+<script>
+foo
+</script>1. *bar*
+>>> HTML blocks - 148
+<!-- Foo
+
+bar
+ baz -->
+okay
+<<<
+<!-- Foo
+
+bar
+ baz -->
+<p>okay</p>
+>>> HTML blocks - 149
+<?php
+
+ echo '>';
+
+?>
+okay
+<<<
+<?php
+
+ echo '>';
+
+?>
+<p>okay</p>
+>>> HTML blocks - 150
+<!DOCTYPE html>
+<<<
+<!DOCTYPE html>
+>>> HTML blocks - 151
+<![CDATA[
+function matchwo(a,b)
+{
+ if (a < b && a < 0) then {
+ return 1;
+
+ } else {
+
+ return 0;
+ }
+}
+]]>
+okay
+<<<
+<![CDATA[
+function matchwo(a,b)
+{
+ if (a < b && a < 0) then {
+ return 1;
+
+ } else {
+
+ return 0;
+ }
+}
+]]>
+<p>okay</p>
+>>> HTML blocks - 152
+ <!-- foo -->
+
+ <!-- foo -->
+<<<
+ <!-- foo -->
+<pre><code><!-- foo -->
+</code></pre>
+>>> HTML blocks - 153
+ <div>
+
+ <div>
+<<<
+ <div>
+<pre><code><div>
+</code></pre>
+>>> HTML blocks - 154
+Foo
+<div>
+bar
+</div>
+<<<
+<p>Foo</p>
+<div>
+bar
+</div>
+>>> HTML blocks - 155
+<div>
+bar
+</div>
+*foo*
+<<<
+<div>
+bar
+</div>
+*foo*
+>>> HTML blocks - 156
+Foo
+<a href="bar">
+baz
+<<<
+<p>Foo
+<a href="bar">
+baz</p>
+>>> HTML blocks - 157
+<div>
+
+*Emphasized* text.
+
+</div>
+<<<
+<div>
+<p><em>Emphasized</em> text.</p>
+</div>
+>>> HTML blocks - 158
+<div>
+*Emphasized* text.
+</div>
+<<<
+<div>
+*Emphasized* text.
+</div>
+>>> HTML blocks - 159
+<table>
+
+<tr>
+
+<td>
+Hi
+</td>
+
+</tr>
+
+</table>
+<<<
+<table>
+<tr>
+<td>
+Hi
+</td>
+</tr>
+</table>
+>>> HTML blocks - 160
+<table>
+
+ <tr>
+
+ <td>
+ Hi
+ </td>
+
+ </tr>
+
+</table>
+<<<
+<table>
+ <tr>
+<pre><code><td>
+ Hi
+</td>
+</code></pre>
+ </tr>
+</table>
diff --git a/pkgs/markdown/test/gfm/images.unit b/pkgs/markdown/test/gfm/images.unit
new file mode 100644
index 0000000..106e1a2
--- /dev/null
+++ b/pkgs/markdown/test/gfm/images.unit
@@ -0,0 +1,121 @@
+>>> Images - 580
+
+<<<
+<p><img src="/url" alt="foo" title="title" /></p>
+>>> Images - 581
+![foo *bar*]
+
+[foo *bar*]: train.jpg "train & tracks"
+<<<
+<p><img src="train.jpg" alt="foo bar" title="train & tracks" /></p>
+>>> Images - 582
+](/url2)
+<<<
+<p><img src="/url2" alt="foo bar" /></p>
+>>> Images - 583
+](/url2)
+<<<
+<p><img src="/url2" alt="foo bar" /></p>
+>>> Images - 584
+![foo *bar*][]
+
+[foo *bar*]: train.jpg "train & tracks"
+<<<
+<p><img src="train.jpg" alt="foo bar" title="train & tracks" /></p>
+>>> Images - 585
+![foo *bar*][foobar]
+
+[FOOBAR]: train.jpg "train & tracks"
+<<<
+<p><img src="train.jpg" alt="foo bar" title="train & tracks" /></p>
+>>> Images - 586
+
+<<<
+<p><img src="train.jpg" alt="foo" /></p>
+>>> Images - 587
+My 
+<<<
+<p>My <img src="/path/to/train.jpg" alt="foo bar" title="title" /></p>
+>>> Images - 588
+
+<<<
+<p><img src="url" alt="foo" /></p>
+>>> Images - 589
+
+<<<
+<p><img src="/url" alt="" /></p>
+>>> Images - 590
+![foo][bar]
+
+[bar]: /url
+<<<
+<p><img src="/url" alt="foo" /></p>
+>>> Images - 591
+![foo][bar]
+
+[BAR]: /url
+<<<
+<p><img src="/url" alt="foo" /></p>
+>>> Images - 592
+![foo][]
+
+[foo]: /url "title"
+<<<
+<p><img src="/url" alt="foo" title="title" /></p>
+>>> Images - 593
+![*foo* bar][]
+
+[*foo* bar]: /url "title"
+<<<
+<p><img src="/url" alt="foo bar" title="title" /></p>
+>>> Images - 594
+![Foo][]
+
+[foo]: /url "title"
+<<<
+<p><img src="/url" alt="Foo" title="title" /></p>
+>>> Images - 595
+![foo]
+[]
+
+[foo]: /url "title"
+<<<
+<p><img src="/url" alt="foo" title="title" />
+[]</p>
+>>> Images - 596
+![foo]
+
+[foo]: /url "title"
+<<<
+<p><img src="/url" alt="foo" title="title" /></p>
+>>> Images - 597
+![*foo* bar]
+
+[*foo* bar]: /url "title"
+<<<
+<p><img src="/url" alt="foo bar" title="title" /></p>
+>>> Images - 598
+![[foo]]
+
+[[foo]]: /url "title"
+<<<
+<p>![[foo]]</p>
+<p>[[foo]]: /url "title"</p>
+>>> Images - 599
+![Foo]
+
+[foo]: /url "title"
+<<<
+<p><img src="/url" alt="Foo" title="title" /></p>
+>>> Images - 600
+!\[foo]
+
+[foo]: /url "title"
+<<<
+<p>![foo]</p>
+>>> Images - 601
+\![foo]
+
+[foo]: /url "title"
+<<<
+<p>!<a href="/url" title="title">foo</a></p>
diff --git a/pkgs/markdown/test/gfm/indented_code_blocks.unit b/pkgs/markdown/test/gfm/indented_code_blocks.unit
new file mode 100644
index 0000000..68fc6d1
--- /dev/null
+++ b/pkgs/markdown/test/gfm/indented_code_blocks.unit
@@ -0,0 +1,118 @@
+>>> Indented code blocks - 77
+ a simple
+ indented code block
+<<<
+<pre><code>a simple
+ indented code block
+</code></pre>
+>>> Indented code blocks - 78
+ - foo
+
+ bar
+<<<
+<ul>
+<li>
+<p>foo</p>
+<p>bar</p>
+</li>
+</ul>
+>>> Indented code blocks - 79
+1. foo
+
+ - bar
+<<<
+<ol>
+<li>
+<p>foo</p>
+<ul>
+<li>bar</li>
+</ul>
+</li>
+</ol>
+>>> Indented code blocks - 80
+ <a/>
+ *hi*
+
+ - one
+<<<
+<pre><code><a/>
+*hi*
+
+- one
+</code></pre>
+>>> Indented code blocks - 81
+ chunk1
+
+ chunk2
+
+
+
+ chunk3
+<<<
+<pre><code>chunk1
+
+chunk2
+
+
+
+chunk3
+</code></pre>
+>>> Indented code blocks - 82
+ chunk1
+
+ chunk2
+<<<
+<pre><code>chunk1
+
+ chunk2
+</code></pre>
+>>> Indented code blocks - 83
+Foo
+ bar
+
+<<<
+<p>Foo
+bar</p>
+>>> Indented code blocks - 84
+ foo
+bar
+<<<
+<pre><code>foo
+</code></pre>
+<p>bar</p>
+>>> Indented code blocks - 85
+# Heading
+ foo
+Heading
+------
+ foo
+----
+<<<
+<h1>Heading</h1>
+<pre><code>foo
+</code></pre>
+<h2>Heading</h2>
+<pre><code>foo
+</code></pre>
+<hr />
+>>> Indented code blocks - 86
+ foo
+ bar
+<<<
+<pre><code> foo
+bar
+</code></pre>
+>>> Indented code blocks - 87
+
+
+ foo
+
+
+<<<
+<pre><code>foo
+</code></pre>
+>>> Indented code blocks - 88
+ foo
+<<<
+<pre><code>foo
+</code></pre>
diff --git a/pkgs/markdown/test/gfm/inlines.unit b/pkgs/markdown/test/gfm/inlines.unit
new file mode 100644
index 0000000..9e19a33
--- /dev/null
+++ b/pkgs/markdown/test/gfm/inlines.unit
@@ -0,0 +1,4 @@
+>>> Inlines - 307
+`hi`lo`
+<<<
+<p><code>hi</code>lo`</p>
diff --git a/pkgs/markdown/test/gfm/link_reference_definitions.unit b/pkgs/markdown/test/gfm/link_reference_definitions.unit
new file mode 100644
index 0000000..b09236f
--- /dev/null
+++ b/pkgs/markdown/test/gfm/link_reference_definitions.unit
@@ -0,0 +1,206 @@
+>>> Link reference definitions - 161
+[foo]: /url "title"
+
+[foo]
+<<<
+<p><a href="/url" title="title">foo</a></p>
+>>> Link reference definitions - 162
+ [foo]:
+ /url
+ 'the title'
+
+[foo]
+<<<
+<p><a href="/url" title="the title">foo</a></p>
+>>> Link reference definitions - 163
+[Foo*bar\]]:my_(url) 'title (with parens)'
+
+[Foo*bar\]]
+<<<
+<p><a href="my_(url)" title="title (with parens)">Foo*bar]</a></p>
+>>> Link reference definitions - 164
+[Foo bar]:
+<my url>
+'title'
+
+[Foo bar]
+<<<
+<p><a href="my%20url" title="title">Foo bar</a></p>
+>>> Link reference definitions - 165
+[foo]: /url '
+title
+line1
+line2
+'
+
+[foo]
+<<<
+<p><a href="/url" title="
+title
+line1
+line2
+">foo</a></p>
+>>> Link reference definitions - 166
+[foo]: /url 'title
+
+with blank line'
+
+[foo]
+<<<
+<p>[foo]: /url 'title</p>
+<p>with blank line'</p>
+<p>[foo]</p>
+>>> Link reference definitions - 167
+[foo]:
+/url
+
+[foo]
+<<<
+<p><a href="/url">foo</a></p>
+>>> Link reference definitions - 168
+[foo]:
+
+[foo]
+<<<
+<p>[foo]:</p>
+<p>[foo]</p>
+>>> Link reference definitions - 169
+[foo]: <>
+
+[foo]
+<<<
+<p><a href="">foo</a></p>
+>>> Link reference definitions - 170
+[foo]: <bar>(baz)
+
+[foo]
+<<<
+<p>[foo]: <bar>(baz)</p>
+<p>[foo]</p>
+>>> Link reference definitions - 171
+[foo]: /url\bar\*baz "foo\"bar\baz"
+
+[foo]
+<<<
+<p><a href="/url%5Cbar*baz" title="foo"bar\baz">foo</a></p>
+>>> Link reference definitions - 172
+[foo]
+
+[foo]: url
+<<<
+<p><a href="url">foo</a></p>
+>>> Link reference definitions - 173
+[foo]
+
+[foo]: first
+[foo]: second
+<<<
+<p><a href="first">foo</a></p>
+>>> Link reference definitions - 174
+[FOO]: /url
+
+[Foo]
+<<<
+<p><a href="/url">Foo</a></p>
+>>> Link reference definitions - 175
+[ΑΓΩ]: /φου
+
+[αγω]
+<<<
+<p><a href="/%CF%86%CE%BF%CF%85">αγω</a></p>
+>>> Link reference definitions - 176
+[foo]: /url
+<<<
+
+>>> Link reference definitions - 177
+[
+foo
+]: /url
+bar
+<<<
+<p>bar</p>
+>>> Link reference definitions - 178
+[foo]: /url "title" ok
+<<<
+<p>[foo]: /url "title" ok</p>
+>>> Link reference definitions - 179
+[foo]: /url
+"title" ok
+<<<
+<p>"title" ok</p>
+>>> Link reference definitions - 180
+ [foo]: /url "title"
+
+[foo]
+<<<
+<pre><code>[foo]: /url "title"
+</code></pre>
+<p>[foo]</p>
+>>> Link reference definitions - 181
+```
+[foo]: /url
+```
+
+[foo]
+<<<
+<pre><code>[foo]: /url
+</code></pre>
+<p>[foo]</p>
+>>> Link reference definitions - 182
+Foo
+[bar]: /baz
+
+[bar]
+<<<
+<p>Foo
+[bar]: /baz</p>
+<p>[bar]</p>
+>>> Link reference definitions - 183
+# [Foo]
+[foo]: /url
+> bar
+<<<
+<h1><a href="/url">Foo</a></h1>
+<blockquote>
+<p>bar</p>
+</blockquote>
+>>> Link reference definitions - 184
+[foo]: /url
+bar
+===
+[foo]
+<<<
+<h1>bar</h1>
+<p><a href="/url">foo</a></p>
+>>> Link reference definitions - 185
+[foo]: /url
+===
+[foo]
+<<<
+<p>===
+<a href="/url">foo</a></p>
+>>> Link reference definitions - 186
+[foo]: /foo-url "foo"
+[bar]: /bar-url
+ "bar"
+[baz]: /baz-url
+
+[foo],
+[bar],
+[baz]
+<<<
+<p><a href="/foo-url" title="foo">foo</a>,
+<a href="/bar-url" title="bar">bar</a>,
+<a href="/baz-url">baz</a></p>
+>>> Link reference definitions - 187
+[foo]
+
+> [foo]: /url
+<<<
+<p><a href="/url">foo</a></p>
+<blockquote>
+</blockquote>
+>>> Link reference definitions - 188
+[foo]: /url
+<<<
+
diff --git a/pkgs/markdown/test/gfm/links.unit b/pkgs/markdown/test/gfm/links.unit
new file mode 100644
index 0000000..7288c79
--- /dev/null
+++ b/pkgs/markdown/test/gfm/links.unit
@@ -0,0 +1,476 @@
+>>> Links - 493
+[link](/uri "title")
+<<<
+<p><a href="/uri" title="title">link</a></p>
+>>> Links - 494
+[link](/uri)
+<<<
+<p><a href="/uri">link</a></p>
+>>> Links - 495
+[link]()
+<<<
+<p><a href="">link</a></p>
+>>> Links - 496
+[link](<>)
+<<<
+<p><a href="">link</a></p>
+>>> Links - 497
+[link](/my uri)
+<<<
+<p>[link](/my uri)</p>
+>>> Links - 498
+[link](</my uri>)
+<<<
+<p><a href="/my%20uri">link</a></p>
+>>> Links - 499
+[link](foo
+bar)
+<<<
+<p>[link](foo
+bar)</p>
+>>> Links - 500
+[link](<foo
+bar>)
+<<<
+<p>[link](<foo
+bar>)</p>
+>>> Links - 501
+[a](<b)c>)
+<<<
+<p><a href="b)c">a</a></p>
+>>> Links - 502
+[link](<foo\>)
+<<<
+<p>[link](<foo>)</p>
+>>> Links - 503
+[a](<b)c
+[a](<b)c>
+[a](<b>c)
+<<<
+<p>[a](<b)c
+[a](<b)c>
+[a](<b>c)</p>
+>>> Links - 504
+[link](\(foo\))
+<<<
+<p><a href="(foo)">link</a></p>
+>>> Links - 505
+[link](foo(and(bar)))
+<<<
+<p><a href="foo(and(bar))">link</a></p>
+>>> Links - 506
+[link](foo\(and\(bar\))
+<<<
+<p><a href="foo(and(bar)">link</a></p>
+>>> Links - 507
+[link](<foo(and(bar)>)
+<<<
+<p><a href="foo(and(bar)">link</a></p>
+>>> Links - 508
+[link](foo\)\:)
+<<<
+<p><a href="foo):">link</a></p>
+>>> Links - 509
+[link](#fragment)
+
+[link](http://example.com#fragment)
+
+[link](http://example.com?foo=3#frag)
+<<<
+<p><a href="#fragment">link</a></p>
+<p><a href="http://example.com#fragment">link</a></p>
+<p><a href="http://example.com?foo=3#frag">link</a></p>
+>>> Links - 510
+[link](foo\bar)
+<<<
+<p><a href="foo%5Cbar">link</a></p>
+>>> Links - 511
+[link](foo%20bä)
+<<<
+<p><a href="foo%20b%C3%A4">link</a></p>
+>>> Links - 512
+[link]("title")
+<<<
+<p><a href="%22title%22">link</a></p>
+>>> Links - 513
+[link](/url "title")
+[link](/url 'title')
+[link](/url (title))
+<<<
+<p><a href="/url" title="title">link</a>
+<a href="/url" title="title">link</a>
+<a href="/url" title="title">link</a></p>
+>>> Links - 514
+[link](/url "title \""")
+<<<
+<p><a href="/url" title="title """>link</a></p>
+>>> Links - 515
+[link](/url "title")
+<<<
+<p><a href="/url%C2%A0%22title%22">link</a></p>
+>>> Links - 516
+[link](/url "title "and" title")
+<<<
+<p>[link](/url "title "and" title")</p>
+>>> Links - 517
+[link](/url 'title "and" title')
+<<<
+<p><a href="/url" title="title "and" title">link</a></p>
+>>> Links - 518
+[link]( /uri
+ "title" )
+<<<
+<p><a href="/uri" title="title">link</a></p>
+>>> Links - 519
+[link] (/uri)
+<<<
+<p>[link] (/uri)</p>
+>>> Links - 520
+[link [foo [bar]]](/uri)
+<<<
+<p><a href="/uri">link [foo [bar]]</a></p>
+>>> Links - 521
+[link] bar](/uri)
+<<<
+<p>[link] bar](/uri)</p>
+>>> Links - 522
+[link [bar](/uri)
+<<<
+<p>[link <a href="/uri">bar</a></p>
+>>> Links - 523
+[link \[bar](/uri)
+<<<
+<p><a href="/uri">link [bar</a></p>
+>>> Links - 524
+[link *foo **bar** `#`*](/uri)
+<<<
+<p><a href="/uri">link <em>foo <strong>bar</strong> <code>#</code></em></a></p>
+>>> Links - 525
+[](/uri)
+<<<
+<p><a href="/uri"><img src="moon.jpg" alt="moon" /></a></p>
+>>> Links - 526
+[foo [bar](/uri)](/uri)
+<<<
+<p>[foo <a href="/uri">bar</a>](/uri)</p>
+>>> Links - 527
+[foo *[bar [baz](/uri)](/uri)*](/uri)
+<<<
+<p>[foo <em>[bar <a href="/uri">baz</a>](/uri)</em>](/uri)</p>
+>>> Links - 528
+](uri2)](uri3)
+<<<
+<p><img src="uri3" alt="[foo](uri2)" /></p>
+>>> Links - 529
+*[foo*](/uri)
+<<<
+<p>*<a href="/uri">foo*</a></p>
+>>> Links - 530
+[foo *bar](baz*)
+<<<
+<p><a href="baz*">foo *bar</a></p>
+>>> Links - 531
+*foo [bar* baz]
+<<<
+<p><em>foo [bar</em> baz]</p>
+>>> Links - 532
+[foo <bar attr="](baz)">
+<<<
+<p>[foo <bar attr="](baz)"></p>
+>>> Links - 533
+[foo`](/uri)`
+<<<
+<p>[foo<code>](/uri)</code></p>
+>>> Links - 534
+[foo<http://example.com/?search=](uri)>
+<<<
+<p>[foo<a href="http://example.com/?search=%5D(uri)">http://example.com/?search=](uri)</a></p>
+>>> Links - 535
+[foo][bar]
+
+[bar]: /url "title"
+<<<
+<p><a href="/url" title="title">foo</a></p>
+>>> Links - 536
+[link [foo [bar]]][ref]
+
+[ref]: /uri
+<<<
+<p><a href="/uri">link [foo [bar]]</a></p>
+>>> Links - 537
+[link \[bar][ref]
+
+[ref]: /uri
+<<<
+<p><a href="/uri">link [bar</a></p>
+>>> Links - 538
+[link *foo **bar** `#`*][ref]
+
+[ref]: /uri
+<<<
+<p><a href="/uri">link <em>foo <strong>bar</strong> <code>#</code></em></a></p>
+>>> Links - 539
+[][ref]
+
+[ref]: /uri
+<<<
+<p><a href="/uri"><img src="moon.jpg" alt="moon" /></a></p>
+>>> Links - 540
+[foo [bar](/uri)][ref]
+
+[ref]: /uri
+<<<
+<p>[foo <a href="/uri">bar</a>]<a href="/uri">ref</a></p>
+>>> Links - 541
+[foo *bar [baz][ref]*][ref]
+
+[ref]: /uri
+<<<
+<p>[foo <em>bar <a href="/uri">baz</a></em>]<a href="/uri">ref</a></p>
+>>> Links - 542
+*[foo*][ref]
+
+[ref]: /uri
+<<<
+<p>*<a href="/uri">foo*</a></p>
+>>> Links - 543
+[foo *bar][ref]
+
+[ref]: /uri
+<<<
+<p><a href="/uri">foo *bar</a></p>
+>>> Links - 544
+[foo <bar attr="][ref]">
+
+[ref]: /uri
+<<<
+<p>[foo <bar attr="][ref]"></p>
+>>> Links - 545
+[foo`][ref]`
+
+[ref]: /uri
+<<<
+<p>[foo<code>][ref]</code></p>
+>>> Links - 546
+[foo<http://example.com/?search=][ref]>
+
+[ref]: /uri
+<<<
+<p>[foo<a href="http://example.com/?search=%5D%5Bref%5D">http://example.com/?search=][ref]</a></p>
+>>> Links - 547
+[foo][BaR]
+
+[bar]: /url "title"
+<<<
+<p><a href="/url" title="title">foo</a></p>
+>>> Links - 548
+[Толпой][Толпой] is a Russian word.
+
+[ТОЛПОЙ]: /url
+<<<
+<p><a href="/url">Толпой</a> is a Russian word.</p>
+>>> Links - 549
+[Foo
+ bar]: /url
+
+[Baz][Foo bar]
+<<<
+<p><a href="/url">Baz</a></p>
+>>> Links - 550
+[foo] [bar]
+
+[bar]: /url "title"
+<<<
+<p>[foo] <a href="/url" title="title">bar</a></p>
+>>> Links - 551
+[foo]
+[bar]
+
+[bar]: /url "title"
+<<<
+<p>[foo]
+<a href="/url" title="title">bar</a></p>
+>>> Links - 552
+[foo]: /url1
+
+[foo]: /url2
+
+[bar][foo]
+<<<
+<p><a href="/url1">bar</a></p>
+>>> Links - 553
+[bar][foo\!]
+
+[foo!]: /url
+<<<
+<p>[bar][foo!]</p>
+>>> Links - 554
+[foo][ref[]
+
+[ref[]: /uri
+<<<
+<p>[foo][ref[]</p>
+<p>[ref[]: /uri</p>
+>>> Links - 555
+[foo][ref[bar]]
+
+[ref[bar]]: /uri
+<<<
+<p>[foo][ref[bar]]</p>
+<p>[ref[bar]]: /uri</p>
+>>> Links - 556
+[[[foo]]]
+
+[[[foo]]]: /url
+<<<
+<p>[[[foo]]]</p>
+<p>[[[foo]]]: /url</p>
+>>> Links - 557
+[foo][ref\[]
+
+[ref\[]: /uri
+<<<
+<p><a href="/uri">foo</a></p>
+>>> Links - 558
+[bar\\]: /uri
+
+[bar\\]
+<<<
+<p><a href="/uri">bar\</a></p>
+>>> Links - 559
+[]
+
+[]: /uri
+<<<
+<p>[]</p>
+<p>[]: /uri</p>
+>>> Links - 560
+[
+ ]
+
+[
+ ]: /uri
+<<<
+<p>[
+]</p>
+<p>[
+]: /uri</p>
+>>> Links - 561
+[foo][]
+
+[foo]: /url "title"
+<<<
+<p><a href="/url" title="title">foo</a></p>
+>>> Links - 562
+[*foo* bar][]
+
+[*foo* bar]: /url "title"
+<<<
+<p><a href="/url" title="title"><em>foo</em> bar</a></p>
+>>> Links - 563
+[Foo][]
+
+[foo]: /url "title"
+<<<
+<p><a href="/url" title="title">Foo</a></p>
+>>> Links - 564
+[foo]
+[]
+
+[foo]: /url "title"
+<<<
+<p><a href="/url" title="title">foo</a>
+[]</p>
+>>> Links - 565
+[foo]
+
+[foo]: /url "title"
+<<<
+<p><a href="/url" title="title">foo</a></p>
+>>> Links - 566
+[*foo* bar]
+
+[*foo* bar]: /url "title"
+<<<
+<p><a href="/url" title="title"><em>foo</em> bar</a></p>
+>>> Links - 567
+[[*foo* bar]]
+
+[*foo* bar]: /url "title"
+<<<
+<p>[<a href="/url" title="title"><em>foo</em> bar</a>]</p>
+>>> Links - 568
+[[bar [foo]
+
+[foo]: /url
+<<<
+<p>[[bar <a href="/url">foo</a></p>
+>>> Links - 569
+[Foo]
+
+[foo]: /url "title"
+<<<
+<p><a href="/url" title="title">Foo</a></p>
+>>> Links - 570
+[foo] bar
+
+[foo]: /url
+<<<
+<p><a href="/url">foo</a> bar</p>
+>>> Links - 571
+\[foo]
+
+[foo]: /url "title"
+<<<
+<p>[foo]</p>
+>>> Links - 572
+[foo*]: /url
+
+*[foo*]
+<<<
+<p>*<a href="/url">foo*</a></p>
+>>> Links - 573
+[foo][bar]
+
+[foo]: /url1
+[bar]: /url2
+<<<
+<p><a href="/url2">foo</a></p>
+>>> Links - 574
+[foo][]
+
+[foo]: /url1
+<<<
+<p><a href="/url1">foo</a></p>
+>>> Links - 575
+[foo]()
+
+[foo]: /url1
+<<<
+<p><a href="">foo</a></p>
+>>> Links - 576
+[foo](not a link)
+
+[foo]: /url1
+<<<
+<p><a href="/url1">foo</a>(not a link)</p>
+>>> Links - 577
+[foo][bar][baz]
+
+[baz]: /url
+<<<
+<p>[foo]<a href="/url">bar</a></p>
+>>> Links - 578
+[foo][bar][baz]
+
+[baz]: /url1
+[bar]: /url2
+<<<
+<p><a href="/url2">foo</a><a href="/url1">baz</a></p>
+>>> Links - 579
+[foo][bar][baz]
+
+[baz]: /url1
+[foo]: /url2
+<<<
+<p>[foo]<a href="/url1">bar</a></p>
diff --git a/pkgs/markdown/test/gfm/list_items.unit b/pkgs/markdown/test/gfm/list_items.unit
new file mode 100644
index 0000000..3954af8
--- /dev/null
+++ b/pkgs/markdown/test/gfm/list_items.unit
@@ -0,0 +1,586 @@
+>>> List items - 231
+A paragraph
+with two lines.
+
+ indented code
+
+> A block quote.
+<<<
+<p>A paragraph
+with two lines.</p>
+<pre><code>indented code
+</code></pre>
+<blockquote>
+<p>A block quote.</p>
+</blockquote>
+>>> List items - 232
+1. A paragraph
+ with two lines.
+
+ indented code
+
+ > A block quote.
+<<<
+<ol>
+<li>
+<p>A paragraph
+with two lines.</p>
+<pre><code>indented code
+</code></pre>
+<blockquote>
+<p>A block quote.</p>
+</blockquote>
+</li>
+</ol>
+>>> List items - 233
+- one
+
+ two
+<<<
+<ul>
+<li>one</li>
+</ul>
+<p>two</p>
+>>> List items - 234
+- one
+
+ two
+<<<
+<ul>
+<li>
+<p>one</p>
+<p>two</p>
+</li>
+</ul>
+>>> List items - 235
+ - one
+
+ two
+<<<
+<ul>
+<li>one</li>
+</ul>
+<pre><code> two
+</code></pre>
+>>> List items - 236
+ - one
+
+ two
+<<<
+<ul>
+<li>
+<p>one</p>
+<p>two</p>
+</li>
+</ul>
+>>> List items - 237
+ > > 1. one
+>>
+>> two
+<<<
+<blockquote>
+<blockquote>
+<ol>
+<li>
+<p>one</p>
+<p>two</p>
+</li>
+</ol>
+</blockquote>
+</blockquote>
+>>> List items - 238
+>>- one
+>>
+ > > two
+<<<
+<blockquote>
+<blockquote>
+<ul>
+<li>one</li>
+</ul>
+<p>two</p>
+</blockquote>
+</blockquote>
+>>> List items - 239
+-one
+
+2.two
+<<<
+<p>-one</p>
+<p>2.two</p>
+>>> List items - 240
+- foo
+
+
+ bar
+<<<
+<ul>
+<li>
+<p>foo</p>
+<p>bar</p>
+</li>
+</ul>
+>>> List items - 241
+1. foo
+
+ ```
+ bar
+ ```
+
+ baz
+
+ > bam
+<<<
+<ol>
+<li>
+<p>foo</p>
+<pre><code>bar
+</code></pre>
+<p>baz</p>
+<blockquote>
+<p>bam</p>
+</blockquote>
+</li>
+</ol>
+>>> List items - 242
+- Foo
+
+ bar
+
+
+ baz
+<<<
+<ul>
+<li>
+<p>Foo</p>
+<pre><code>bar
+
+
+baz
+</code></pre>
+</li>
+</ul>
+>>> List items - 243
+123456789. ok
+<<<
+<ol start="123456789">
+<li>ok</li>
+</ol>
+>>> List items - 244
+1234567890. not ok
+<<<
+<p>1234567890. not ok</p>
+>>> List items - 245
+0. ok
+<<<
+<ol start="0">
+<li>ok</li>
+</ol>
+>>> List items - 246
+003. ok
+<<<
+<ol start="3">
+<li>ok</li>
+</ol>
+>>> List items - 247
+-1. not ok
+<<<
+<p>-1. not ok</p>
+>>> List items - 248
+- foo
+
+ bar
+<<<
+<ul>
+<li>
+<p>foo</p>
+<pre><code>bar
+</code></pre>
+</li>
+</ul>
+>>> List items - 249
+ 10. foo
+
+ bar
+<<<
+<ol start="10">
+<li>
+<p>foo</p>
+<pre><code>bar
+</code></pre>
+</li>
+</ol>
+>>> List items - 250
+ indented code
+
+paragraph
+
+ more code
+<<<
+<pre><code>indented code
+</code></pre>
+<p>paragraph</p>
+<pre><code>more code
+</code></pre>
+>>> List items - 251
+1. indented code
+
+ paragraph
+
+ more code
+<<<
+<ol>
+<li>
+<pre><code>indented code
+</code></pre>
+<p>paragraph</p>
+<pre><code>more code
+</code></pre>
+</li>
+</ol>
+>>> List items - 252
+1. indented code
+
+ paragraph
+
+ more code
+<<<
+<ol>
+<li>
+<pre><code> indented code
+</code></pre>
+<p>paragraph</p>
+<pre><code>more code
+</code></pre>
+</li>
+</ol>
+>>> List items - 253
+ foo
+
+bar
+<<<
+<p>foo</p>
+<p>bar</p>
+>>> List items - 254
+- foo
+
+ bar
+<<<
+<ul>
+<li>foo</li>
+</ul>
+<p>bar</p>
+>>> List items - 255
+- foo
+
+ bar
+<<<
+<ul>
+<li>
+<p>foo</p>
+<p>bar</p>
+</li>
+</ul>
+>>> List items - 256
+-
+ foo
+-
+ ```
+ bar
+ ```
+-
+ baz
+<<<
+<ul>
+<li>foo</li>
+<li>
+<pre><code>bar
+</code></pre>
+</li>
+<li>
+<pre><code>baz
+</code></pre>
+</li>
+</ul>
+>>> List items - 257
+-
+ foo
+<<<
+<ul>
+<li>foo</li>
+</ul>
+>>> List items - 258
+-
+
+ foo
+<<<
+<ul>
+<li></li>
+</ul>
+<p>foo</p>
+>>> List items - 259
+- foo
+-
+- bar
+<<<
+<ul>
+<li>foo</li>
+<li></li>
+<li>bar</li>
+</ul>
+>>> List items - 260
+- foo
+-
+- bar
+<<<
+<ul>
+<li>foo</li>
+<li></li>
+<li>bar</li>
+</ul>
+>>> List items - 261
+1. foo
+2.
+3. bar
+<<<
+<ol>
+<li>foo</li>
+<li></li>
+<li>bar</li>
+</ol>
+>>> List items - 262
+*
+<<<
+<ul>
+<li></li>
+</ul>
+>>> List items - 263
+foo
+*
+
+foo
+1.
+<<<
+<p>foo
+*</p>
+<p>foo
+1.</p>
+>>> List items - 264
+ 1. A paragraph
+ with two lines.
+
+ indented code
+
+ > A block quote.
+<<<
+<ol>
+<li>
+<p>A paragraph
+with two lines.</p>
+<pre><code>indented code
+</code></pre>
+<blockquote>
+<p>A block quote.</p>
+</blockquote>
+</li>
+</ol>
+>>> List items - 265
+ 1. A paragraph
+ with two lines.
+
+ indented code
+
+ > A block quote.
+<<<
+<ol>
+<li>
+<p>A paragraph
+with two lines.</p>
+<pre><code>indented code
+</code></pre>
+<blockquote>
+<p>A block quote.</p>
+</blockquote>
+</li>
+</ol>
+>>> List items - 266
+ 1. A paragraph
+ with two lines.
+
+ indented code
+
+ > A block quote.
+<<<
+<ol>
+<li>
+<p>A paragraph
+with two lines.</p>
+<pre><code>indented code
+</code></pre>
+<blockquote>
+<p>A block quote.</p>
+</blockquote>
+</li>
+</ol>
+>>> List items - 267
+ 1. A paragraph
+ with two lines.
+
+ indented code
+
+ > A block quote.
+<<<
+<pre><code>1. A paragraph
+ with two lines.
+
+ indented code
+
+ > A block quote.
+</code></pre>
+>>> List items - 268
+ 1. A paragraph
+with two lines.
+
+ indented code
+
+ > A block quote.
+<<<
+<ol>
+<li>
+<p>A paragraph
+with two lines.</p>
+<pre><code>indented code
+</code></pre>
+<blockquote>
+<p>A block quote.</p>
+</blockquote>
+</li>
+</ol>
+>>> List items - 269
+ 1. A paragraph
+ with two lines.
+<<<
+<ol>
+<li>A paragraph
+with two lines.</li>
+</ol>
+>>> List items - 270
+> 1. > Blockquote
+continued here.
+<<<
+<blockquote>
+<ol>
+<li>
+<blockquote>
+<p>Blockquote
+continued here.</p>
+</blockquote>
+</li>
+</ol>
+</blockquote>
+>>> List items - 271
+> 1. > Blockquote
+> continued here.
+<<<
+<blockquote>
+<ol>
+<li>
+<blockquote>
+<p>Blockquote
+continued here.</p>
+</blockquote>
+</li>
+</ol>
+</blockquote>
+>>> List items - 272
+- foo
+ - bar
+ - baz
+ - boo
+<<<
+<ul>
+<li>foo
+<ul>
+<li>bar
+<ul>
+<li>baz
+<ul>
+<li>boo</li>
+</ul>
+</li>
+</ul>
+</li>
+</ul>
+</li>
+</ul>
+>>> List items - 273
+- foo
+ - bar
+ - baz
+ - boo
+<<<
+<ul>
+<li>foo</li>
+<li>bar</li>
+<li>baz</li>
+<li>boo</li>
+</ul>
+>>> List items - 274
+10) foo
+ - bar
+<<<
+<ol start="10">
+<li>foo
+<ul>
+<li>bar</li>
+</ul>
+</li>
+</ol>
+>>> List items - 275
+10) foo
+ - bar
+<<<
+<ol start="10">
+<li>foo</li>
+</ol>
+<ul>
+<li>bar</li>
+</ul>
+>>> List items - 276
+- - foo
+<<<
+<ul>
+<li>
+<ul>
+<li>foo</li>
+</ul>
+</li>
+</ul>
+>>> List items - 277
+1. - 2. foo
+<<<
+<ol>
+<li>
+<ul>
+<li>
+<ol start="2">
+<li>foo</li>
+</ol>
+</li>
+</ul>
+</li>
+</ol>
+>>> List items - 278
+- # Foo
+- Bar
+ ---
+ baz
+<<<
+<ul>
+<li>
+<h1>Foo</h1>
+</li>
+<li>
+<h2>Bar</h2>
+baz</li>
+</ul>
diff --git a/pkgs/markdown/test/gfm/lists.unit b/pkgs/markdown/test/gfm/lists.unit
new file mode 100644
index 0000000..e5802db
--- /dev/null
+++ b/pkgs/markdown/test/gfm/lists.unit
@@ -0,0 +1,406 @@
+>>> Lists - 281
+- foo
+- bar
++ baz
+<<<
+<ul>
+<li>foo</li>
+<li>bar</li>
+</ul>
+<ul>
+<li>baz</li>
+</ul>
+>>> Lists - 282
+1. foo
+2. bar
+3) baz
+<<<
+<ol>
+<li>foo</li>
+<li>bar</li>
+</ol>
+<ol start="3">
+<li>baz</li>
+</ol>
+>>> Lists - 283
+Foo
+- bar
+- baz
+<<<
+<p>Foo</p>
+<ul>
+<li>bar</li>
+<li>baz</li>
+</ul>
+>>> Lists - 284
+The number of windows in my house is
+14. The number of doors is 6.
+<<<
+<p>The number of windows in my house is
+14. The number of doors is 6.</p>
+>>> Lists - 285
+The number of windows in my house is
+1. The number of doors is 6.
+<<<
+<p>The number of windows in my house is</p>
+<ol>
+<li>The number of doors is 6.</li>
+</ol>
+>>> Lists - 286
+- foo
+
+- bar
+
+
+- baz
+<<<
+<ul>
+<li>
+<p>foo</p>
+</li>
+<li>
+<p>bar</p>
+</li>
+<li>
+<p>baz</p>
+</li>
+</ul>
+>>> Lists - 287
+- foo
+ - bar
+ - baz
+
+
+ bim
+<<<
+<ul>
+<li>foo
+<ul>
+<li>bar
+<ul>
+<li>
+<p>baz</p>
+<p>bim</p>
+</li>
+</ul>
+</li>
+</ul>
+</li>
+</ul>
+>>> Lists - 288
+- foo
+- bar
+
+<!-- -->
+
+- baz
+- bim
+<<<
+<ul>
+<li>foo</li>
+<li>bar</li>
+</ul>
+<!-- -->
+<ul>
+<li>baz</li>
+<li>bim</li>
+</ul>
+>>> Lists - 289
+- foo
+
+ notcode
+
+- foo
+
+<!-- -->
+
+ code
+<<<
+<ul>
+<li>
+<p>foo</p>
+<p>notcode</p>
+</li>
+<li>
+<p>foo</p>
+</li>
+</ul>
+<!-- -->
+<pre><code>code
+</code></pre>
+>>> Lists - 290
+- a
+ - b
+ - c
+ - d
+ - e
+ - f
+- g
+<<<
+<ul>
+<li>a</li>
+<li>b</li>
+<li>c</li>
+<li>d</li>
+<li>e</li>
+<li>f</li>
+<li>g</li>
+</ul>
+>>> Lists - 291
+1. a
+
+ 2. b
+
+ 3. c
+<<<
+<ol>
+<li>
+<p>a</p>
+</li>
+<li>
+<p>b</p>
+</li>
+<li>
+<p>c</p>
+</li>
+</ol>
+>>> Lists - 292
+- a
+ - b
+ - c
+ - d
+ - e
+<<<
+<ul>
+<li>a</li>
+<li>b</li>
+<li>c</li>
+<li>d
+- e</li>
+</ul>
+>>> Lists - 293
+1. a
+
+ 2. b
+
+ 3. c
+<<<
+<ol>
+<li>
+<p>a</p>
+</li>
+<li>
+<p>b</p>
+</li>
+</ol>
+<pre><code>3. c
+</code></pre>
+>>> Lists - 294
+- a
+- b
+
+- c
+<<<
+<ul>
+<li>
+<p>a</p>
+</li>
+<li>
+<p>b</p>
+</li>
+<li>
+<p>c</p>
+</li>
+</ul>
+>>> Lists - 295
+* a
+*
+
+* c
+<<<
+<ul>
+<li>
+<p>a</p>
+</li>
+<li></li>
+<li>
+<p>c</p>
+</li>
+</ul>
+>>> Lists - 296
+- a
+- b
+
+ c
+- d
+<<<
+<ul>
+<li>
+<p>a</p>
+</li>
+<li>
+<p>b</p>
+<p>c</p>
+</li>
+<li>
+<p>d</p>
+</li>
+</ul>
+>>> Lists - 297
+- a
+- b
+
+ [ref]: /url
+- d
+<<<
+<ul>
+<li>
+<p>a</p>
+</li>
+<li>
+<p>b</p>
+</li>
+<li>
+<p>d</p>
+</li>
+</ul>
+>>> Lists - 298
+- a
+- ```
+ b
+
+
+ ```
+- c
+<<<
+<ul>
+<li>a</li>
+<li>
+<pre><code>b
+
+
+</code></pre>
+</li>
+<li>c</li>
+</ul>
+>>> Lists - 299
+- a
+ - b
+
+ c
+- d
+<<<
+<ul>
+<li>a
+<ul>
+<li>
+<p>b</p>
+<p>c</p>
+</li>
+</ul>
+</li>
+<li>d</li>
+</ul>
+>>> Lists - 300
+* a
+ > b
+ >
+* c
+<<<
+<ul>
+<li>a
+<blockquote>
+<p>b</p>
+</blockquote>
+</li>
+<li>c</li>
+</ul>
+>>> Lists - 301
+- a
+ > b
+ ```
+ c
+ ```
+- d
+<<<
+<ul>
+<li>a
+<blockquote>
+<p>b</p>
+</blockquote>
+<pre><code>c
+</code></pre>
+</li>
+<li>d</li>
+</ul>
+>>> Lists - 302
+- a
+<<<
+<ul>
+<li>a</li>
+</ul>
+>>> Lists - 303
+- a
+ - b
+<<<
+<ul>
+<li>a
+<ul>
+<li>b</li>
+</ul>
+</li>
+</ul>
+>>> Lists - 304
+1. ```
+ foo
+ ```
+
+ bar
+<<<
+<ol>
+<li>
+<pre><code>foo
+</code></pre>
+<p>bar</p>
+</li>
+</ol>
+>>> Lists - 305
+* foo
+ * bar
+
+ baz
+<<<
+<ul>
+<li>
+<p>foo</p>
+<ul>
+<li>bar</li>
+</ul>
+<p>baz</p>
+</li>
+</ul>
+>>> Lists - 306
+- a
+ - b
+ - c
+
+- d
+ - e
+ - f
+<<<
+<ul>
+<li>
+<p>a</p>
+<ul>
+<li>b</li>
+<li>c</li>
+</ul>
+</li>
+<li>
+<p>d</p>
+<ul>
+<li>e</li>
+<li>f</li>
+</ul>
+</li>
+</ul>
diff --git a/pkgs/markdown/test/gfm/paragraphs.unit b/pkgs/markdown/test/gfm/paragraphs.unit
new file mode 100644
index 0000000..a6fc2b1
--- /dev/null
+++ b/pkgs/markdown/test/gfm/paragraphs.unit
@@ -0,0 +1,59 @@
+>>> Paragraphs - 189
+aaa
+
+bbb
+<<<
+<p>aaa</p>
+<p>bbb</p>
+>>> Paragraphs - 190
+aaa
+bbb
+
+ccc
+ddd
+<<<
+<p>aaa
+bbb</p>
+<p>ccc
+ddd</p>
+>>> Paragraphs - 191
+aaa
+
+
+bbb
+<<<
+<p>aaa</p>
+<p>bbb</p>
+>>> Paragraphs - 192
+ aaa
+ bbb
+<<<
+<p>aaa
+bbb</p>
+>>> Paragraphs - 193
+aaa
+ bbb
+ ccc
+<<<
+<p>aaa
+bbb
+ccc</p>
+>>> Paragraphs - 194
+ aaa
+bbb
+<<<
+<p>aaa
+bbb</p>
+>>> Paragraphs - 195
+ aaa
+bbb
+<<<
+<pre><code>aaa
+</code></pre>
+<p>bbb</p>
+>>> Paragraphs - 196
+aaa
+bbb
+<<<
+<p>aaa<br />
+bbb</p>
diff --git a/pkgs/markdown/test/gfm/precedence.unit b/pkgs/markdown/test/gfm/precedence.unit
new file mode 100644
index 0000000..793a094
--- /dev/null
+++ b/pkgs/markdown/test/gfm/precedence.unit
@@ -0,0 +1,8 @@
+>>> Precedence - 12
+- `one
+- two`
+<<<
+<ul>
+<li>`one</li>
+<li>two`</li>
+</ul>
diff --git a/pkgs/markdown/test/gfm/raw_html.unit b/pkgs/markdown/test/gfm/raw_html.unit
new file mode 100644
index 0000000..b408353
--- /dev/null
+++ b/pkgs/markdown/test/gfm/raw_html.unit
@@ -0,0 +1,95 @@
+>>> Raw HTML - 632
+<a><bab><c2c>
+<<<
+<p><a><bab><c2c></p>
+>>> Raw HTML - 633
+<a/><b2/>
+<<<
+<p><a/><b2/></p>
+>>> Raw HTML - 634
+<a /><b2
+data="foo" >
+<<<
+<p><a /><b2
+data="foo" ></p>
+>>> Raw HTML - 635
+<a foo="bar" bam = 'baz <em>"</em>'
+_boolean zoop:33=zoop:33 />
+<<<
+<p><a foo="bar" bam = 'baz <em>"</em>'
+_boolean zoop:33=zoop:33 /></p>
+>>> Raw HTML - 636
+Foo <responsive-image src="foo.jpg" />
+<<<
+<p>Foo <responsive-image src="foo.jpg" /></p>
+>>> Raw HTML - 637
+<33> <__>
+<<<
+<p><33> <__></p>
+>>> Raw HTML - 638
+<a h*#ref="hi">
+<<<
+<p><a h*#ref="hi"></p>
+>>> Raw HTML - 639
+<a href="hi'> <a href=hi'>
+<<<
+<p><a href="hi'> <a href=hi'></p>
+>>> Raw HTML - 640
+< a><
+foo><bar/ >
+<foo bar=baz
+bim!bop />
+<<<
+<p>< a><
+foo><bar/ >
+<foo bar=baz
+bim!bop /></p>
+>>> Raw HTML - 641
+<a href='bar'title=title>
+<<<
+<p><a href='bar'title=title></p>
+>>> Raw HTML - 642
+</a></foo >
+<<<
+<p></a></foo ></p>
+>>> Raw HTML - 643
+</a href="foo">
+<<<
+<p></a href="foo"></p>
+>>> Raw HTML - 644
+foo <!-- this is a --
+comment - with hyphens -->
+<<<
+<p>foo <!-- this is a --
+comment - with hyphens --></p>
+>>> Raw HTML - 645
+foo <!--> foo -->
+
+foo <!---> foo -->
+<<<
+<p>foo <!--> foo --></p>
+<p>foo <!---> foo --></p>
+>>> Raw HTML - 646
+foo <?php echo $a; ?>
+<<<
+<p>foo <?php echo $a; ?></p>
+>>> Raw HTML - 647
+foo <!ELEMENT br EMPTY>
+<<<
+<p>foo <!ELEMENT br EMPTY></p>
+>>> Raw HTML - 648
+foo <![CDATA[>&<]]>
+<<<
+<p>foo <![CDATA[>&<]]></p>
+>>> Raw HTML - 649
+foo <a href="ö">
+<<<
+<p>foo <a href="ö"></p>
+>>> Raw HTML - 650
+foo <a href="\*">
+<<<
+<p>foo <a href="\*"></p>
+>>> Raw HTML - 651
+<a href="\"">
+<<<
+<p><a href="""></p>
diff --git a/pkgs/markdown/test/gfm/setext_headings.unit b/pkgs/markdown/test/gfm/setext_headings.unit
new file mode 100644
index 0000000..725a175
--- /dev/null
+++ b/pkgs/markdown/test/gfm/setext_headings.unit
@@ -0,0 +1,229 @@
+>>> Setext headings - 50
+Foo *bar*
+=========
+
+Foo *bar*
+---------
+<<<
+<h1>Foo <em>bar</em></h1>
+<h2>Foo <em>bar</em></h2>
+>>> Setext headings - 51
+Foo *bar
+baz*
+====
+<<<
+<h1>Foo <em>bar
+baz</em></h1>
+>>> Setext headings - 52
+ Foo *bar
+baz*
+====
+<<<
+<h1> Foo <em>bar
+baz</em></h1>
+>>> Setext headings - 53
+Foo
+-------------------------
+
+Foo
+=
+<<<
+<h2>Foo</h2>
+<h1>Foo</h1>
+>>> Setext headings - 54
+ Foo
+---
+
+ Foo
+-----
+
+ Foo
+ ===
+<<<
+<h2> Foo</h2>
+<h2> Foo</h2>
+<h1> Foo</h1>
+>>> Setext headings - 55
+ Foo
+ ---
+
+ Foo
+---
+<<<
+<pre><code>Foo
+---
+
+Foo
+</code></pre>
+<hr />
+>>> Setext headings - 56
+Foo
+ ----
+<<<
+<h2>Foo</h2>
+>>> Setext headings - 57
+Foo
+ ---
+<<<
+<p>Foo
+---</p>
+>>> Setext headings - 58
+Foo
+= =
+
+Foo
+--- -
+<<<
+<p>Foo
+= =</p>
+<p>Foo</p>
+<hr />
+>>> Setext headings - 59
+Foo
+-----
+<<<
+<h2>Foo</h2>
+>>> Setext headings - 60
+Foo\
+----
+<<<
+<h2>Foo\</h2>
+>>> Setext headings - 61
+`Foo
+----
+`
+
+<a title="a lot
+---
+of dashes"/>
+<<<
+<h2>`Foo</h2>
+<p>`</p>
+<h2><a title="a lot</h2>
+<p>of dashes"/></p>
+>>> Setext headings - 62
+> Foo
+---
+<<<
+<blockquote>
+<p>Foo</p>
+</blockquote>
+<hr />
+>>> Setext headings - 63
+> foo
+bar
+===
+<<<
+<blockquote>
+<p>foo
+bar
+===</p>
+</blockquote>
+>>> Setext headings - 64
+- Foo
+---
+<<<
+<ul>
+<li>Foo</li>
+</ul>
+<hr />
+>>> Setext headings - 65
+Foo
+Bar
+---
+<<<
+<h2>Foo
+Bar</h2>
+>>> Setext headings - 66
+---
+Foo
+---
+Bar
+---
+Baz
+<<<
+<hr />
+<h2>Foo</h2>
+<h2>Bar</h2>
+<p>Baz</p>
+>>> Setext headings - 67
+
+====
+<<<
+<p>====</p>
+>>> Setext headings - 68
+---
+---
+<<<
+<hr />
+<hr />
+>>> Setext headings - 69
+- foo
+-----
+<<<
+<ul>
+<li>foo</li>
+</ul>
+<hr />
+>>> Setext headings - 70
+ foo
+---
+<<<
+<pre><code>foo
+</code></pre>
+<hr />
+>>> Setext headings - 71
+> foo
+-----
+<<<
+<blockquote>
+<p>foo</p>
+</blockquote>
+<hr />
+>>> Setext headings - 72
+\> foo
+------
+<<<
+<h2>> foo</h2>
+>>> Setext headings - 73
+Foo
+
+bar
+---
+baz
+<<<
+<p>Foo</p>
+<h2>bar</h2>
+<p>baz</p>
+>>> Setext headings - 74
+Foo
+bar
+
+---
+
+baz
+<<<
+<p>Foo
+bar</p>
+<hr />
+<p>baz</p>
+>>> Setext headings - 75
+Foo
+bar
+* * *
+baz
+<<<
+<p>Foo
+bar</p>
+<hr />
+<p>baz</p>
+>>> Setext headings - 76
+Foo
+bar
+\---
+baz
+<<<
+<p>Foo
+bar
+---
+baz</p>
diff --git a/pkgs/markdown/test/gfm/soft_line_breaks.unit b/pkgs/markdown/test/gfm/soft_line_breaks.unit
new file mode 100644
index 0000000..2186b00
--- /dev/null
+++ b/pkgs/markdown/test/gfm/soft_line_breaks.unit
@@ -0,0 +1,12 @@
+>>> Soft line breaks - 668
+foo
+baz
+<<<
+<p>foo
+baz</p>
+>>> Soft line breaks - 669
+foo
+ baz
+<<<
+<p>foo
+baz</p>
diff --git a/pkgs/markdown/test/gfm/strikethrough_extension.unit b/pkgs/markdown/test/gfm/strikethrough_extension.unit
new file mode 100644
index 0000000..0d6f75f
--- /dev/null
+++ b/pkgs/markdown/test/gfm/strikethrough_extension.unit
@@ -0,0 +1,19 @@
+>>> Strikethrough (extension) - 491
+~~Hi~~ Hello, world!
+<<<
+<p><del>Hi</del> Hello, world!</p>
+>>> Strikethrough (extension) - 492
+This ~~has a
+
+new paragraph~~.
+<<<
+<p>This ~~has a</p>
+<p>new paragraph~~.</p>
+>>> single tilde
+~Hi~ there.
+<<<
+<p><del>Hi</del> there.</p>
+>>> single tilde with double tilde
+~Hi~~ there.
+<<<
+<p><del>Hi</del>~ there.</p>
diff --git a/pkgs/markdown/test/gfm/tables_extension.unit b/pkgs/markdown/test/gfm/tables_extension.unit
new file mode 100644
index 0000000..625fca8
--- /dev/null
+++ b/pkgs/markdown/test/gfm/tables_extension.unit
@@ -0,0 +1,153 @@
+>>> Tables (extension) - 198
+| foo | bar |
+| --- | --- |
+| baz | bim |
+<<<
+<table>
+<thead>
+<tr>
+<th>foo</th>
+<th>bar</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>baz</td>
+<td>bim</td>
+</tr>
+</tbody>
+</table>
+>>> Tables (extension) - 199
+| abc | defghi |
+:-: | -----------:
+bar | baz
+<<<
+<table>
+<thead>
+<tr>
+<th align="center">abc</th>
+<th align="right">defghi</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td align="center">bar</td>
+<td align="right">baz</td>
+</tr>
+</tbody>
+</table>
+>>> Tables (extension) - 200
+| f\|oo |
+| ------ |
+| b `\|` az |
+| b **\|** im |
+<<<
+<table>
+<thead>
+<tr>
+<th>f|oo</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>b <code>|</code> az</td>
+</tr>
+<tr>
+<td>b <strong>|</strong> im</td>
+</tr>
+</tbody>
+</table>
+>>> Tables (extension) - 201
+| abc | def |
+| --- | --- |
+| bar | baz |
+> bar
+<<<
+<table>
+<thead>
+<tr>
+<th>abc</th>
+<th>def</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>bar</td>
+<td>baz</td>
+</tr>
+</tbody>
+</table>
+<blockquote>
+<p>bar</p>
+</blockquote>
+>>> Tables (extension) - 202
+| abc | def |
+| --- | --- |
+| bar | baz |
+bar
+
+bar
+<<<
+<table>
+<thead>
+<tr>
+<th>abc</th>
+<th>def</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>bar</td>
+<td>baz</td>
+</tr>
+<tr>
+<td>bar</td>
+<td></td>
+</tr>
+</tbody>
+</table>
+<p>bar</p>
+>>> Tables (extension) - 203
+| abc | def |
+| --- |
+| bar |
+<<<
+<p>| abc | def |
+| --- |
+| bar |</p>
+>>> Tables (extension) - 204
+| abc | def |
+| --- | --- |
+| bar |
+| bar | baz | boo |
+<<<
+<table>
+<thead>
+<tr>
+<th>abc</th>
+<th>def</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>bar</td>
+<td></td>
+</tr>
+<tr>
+<td>bar</td>
+<td>baz</td>
+</tr>
+</tbody>
+</table>
+>>> Tables (extension) - 205
+| abc | def |
+| --- | --- |
+<<<
+<table>
+<thead>
+<tr>
+<th>abc</th>
+<th>def</th>
+</tr>
+</thead>
+</table>
diff --git a/pkgs/markdown/test/gfm/tabs.unit b/pkgs/markdown/test/gfm/tabs.unit
new file mode 100644
index 0000000..1239481
--- /dev/null
+++ b/pkgs/markdown/test/gfm/tabs.unit
@@ -0,0 +1,87 @@
+>>> Tabs - 1
+ foo baz bim
+<<<
+<pre><code>foo baz bim
+</code></pre>
+>>> Tabs - 2
+ foo baz bim
+<<<
+<pre><code>foo baz bim
+</code></pre>
+>>> Tabs - 3
+ a a
+ ὐ a
+<<<
+<pre><code>a a
+ὐ a
+</code></pre>
+>>> Tabs - 4
+ - foo
+
+ bar
+<<<
+<ul>
+<li>
+<p>foo</p>
+<p>bar</p>
+</li>
+</ul>
+>>> Tabs - 5
+- foo
+
+ bar
+<<<
+<ul>
+<li>
+<p>foo</p>
+<pre><code> bar
+</code></pre>
+</li>
+</ul>
+>>> Tabs - 6
+> foo
+<<<
+<blockquote>
+<pre><code>foo
+</code></pre>
+</blockquote>
+>>> Tabs - 7
+- foo
+<<<
+<ul>
+<li>
+<pre><code> foo
+</code></pre>
+</li>
+</ul>
+>>> Tabs - 8
+ foo
+ bar
+<<<
+<pre><code>foo
+bar
+</code></pre>
+>>> Tabs - 9
+ - foo
+ - bar
+ - baz
+<<<
+<ul>
+<li>foo
+<ul>
+<li>bar
+<ul>
+<li>baz</li>
+</ul>
+</li>
+</ul>
+</li>
+</ul>
+>>> Tabs - 10
+# Foo
+<<<
+<h1>Foo</h1>
+>>> Tabs - 11
+* * *
+<<<
+<hr />
diff --git a/pkgs/markdown/test/gfm/textual_content.unit b/pkgs/markdown/test/gfm/textual_content.unit
new file mode 100644
index 0000000..7a1b02c
--- /dev/null
+++ b/pkgs/markdown/test/gfm/textual_content.unit
@@ -0,0 +1,12 @@
+>>> Textual content - 670
+hello $.;'there
+<<<
+<p>hello $.;'there</p>
+>>> Textual content - 671
+Foo χρῆν
+<<<
+<p>Foo χρῆν</p>
+>>> Textual content - 672
+Multiple spaces
+<<<
+<p>Multiple spaces</p>
diff --git a/pkgs/markdown/test/gfm/thematic_breaks.unit b/pkgs/markdown/test/gfm/thematic_breaks.unit
new file mode 100644
index 0000000..85206c8
--- /dev/null
+++ b/pkgs/markdown/test/gfm/thematic_breaks.unit
@@ -0,0 +1,126 @@
+>>> Thematic breaks - 13
+***
+---
+___
+<<<
+<hr />
+<hr />
+<hr />
+>>> Thematic breaks - 14
++++
+<<<
+<p>+++</p>
+>>> Thematic breaks - 15
+===
+<<<
+<p>===</p>
+>>> Thematic breaks - 16
+--
+**
+__
+<<<
+<p>--
+**
+__</p>
+>>> Thematic breaks - 17
+ ***
+ ***
+ ***
+<<<
+<hr />
+<hr />
+<hr />
+>>> Thematic breaks - 18
+ ***
+<<<
+<pre><code>***
+</code></pre>
+>>> Thematic breaks - 19
+Foo
+ ***
+<<<
+<p>Foo
+***</p>
+>>> Thematic breaks - 20
+_____________________________________
+<<<
+<hr />
+>>> Thematic breaks - 21
+ - - -
+<<<
+<hr />
+>>> Thematic breaks - 22
+ ** * ** * ** * **
+<<<
+<hr />
+>>> Thematic breaks - 23
+- - - -
+<<<
+<hr />
+>>> Thematic breaks - 24
+- - - -
+<<<
+<hr />
+>>> Thematic breaks - 25
+_ _ _ _ a
+
+a------
+
+---a---
+<<<
+<p>_ _ _ _ a</p>
+<p>a------</p>
+<p>---a---</p>
+>>> Thematic breaks - 26
+ *-*
+<<<
+<p><em>-</em></p>
+>>> Thematic breaks - 27
+- foo
+***
+- bar
+<<<
+<ul>
+<li>foo</li>
+</ul>
+<hr />
+<ul>
+<li>bar</li>
+</ul>
+>>> Thematic breaks - 28
+Foo
+***
+bar
+<<<
+<p>Foo</p>
+<hr />
+<p>bar</p>
+>>> Thematic breaks - 29
+Foo
+---
+bar
+<<<
+<h2>Foo</h2>
+<p>bar</p>
+>>> Thematic breaks - 30
+* Foo
+* * *
+* Bar
+<<<
+<ul>
+<li>Foo</li>
+</ul>
+<hr />
+<ul>
+<li>Bar</li>
+</ul>
+>>> Thematic breaks - 31
+- Foo
+- * * *
+<<<
+<ul>
+<li>Foo</li>
+<li>
+<hr />
+</li>
+</ul>
diff --git a/pkgs/markdown/test/html_renderer_test.dart b/pkgs/markdown/test/html_renderer_test.dart
new file mode 100644
index 0000000..77c33d3
--- /dev/null
+++ b/pkgs/markdown/test/html_renderer_test.dart
@@ -0,0 +1,114 @@
+// 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:markdown/markdown.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('markdownToHtml', () {
+ const text = '# Hello **Markdown<em>!</em>**\n***';
+
+ test('with no syntaxes', () {
+ final result = markdownToHtml(
+ text,
+ withDefaultBlockSyntaxes: false,
+ withDefaultInlineSyntaxes: false,
+ encodeHtml: false,
+ );
+ expect(result, equals('# Hello **Markdown<em>!</em>**\n***\n'));
+ });
+
+ test('with no default syntaxes but with custom syntaxes', () {
+ final result = markdownToHtml(
+ text,
+ withDefaultBlockSyntaxes: false,
+ withDefaultInlineSyntaxes: false,
+ encodeHtml: false,
+ blockSyntaxes: [const HorizontalRuleSyntax()],
+ inlineSyntaxes: [
+ EmphasisSyntax.asterisk(),
+ ],
+ );
+
+ expect(
+ result,
+ equals('# Hello <strong>Markdown<em>!</em></strong>\n<hr />\n'),
+ );
+ });
+
+ test('with only default block syntaxes', () {
+ final result = markdownToHtml(
+ text,
+ withDefaultInlineSyntaxes: false,
+ encodeHtml: false,
+ );
+
+ expect(
+ result,
+ equals('<h1>Hello **Markdown<em>!</em>**</h1>\n<hr />\n'),
+ );
+ });
+
+ test('with only default inline syntaxes', () {
+ final result = markdownToHtml(
+ text,
+ withDefaultBlockSyntaxes: false,
+ encodeHtml: false,
+ );
+
+ expect(
+ result,
+ equals('# Hello <strong>Markdown<em>!</em></strong>\n***\n'),
+ );
+ });
+
+ test('with no default syntaxes but with encodeHtml enabled', () {
+ final result = markdownToHtml(
+ text,
+ withDefaultBlockSyntaxes: false,
+ withDefaultInlineSyntaxes: false,
+ );
+
+ expect(
+ result,
+ equals('# Hello **Markdown<em>!</em>**\n***\n'),
+ );
+ });
+ });
+
+ group('test InlineSyntax caseSensitive parameter', () {
+ const text = 'one BREAK two';
+
+ test('with caseSensitive enabled', () {
+ final result = markdownToHtml(
+ text,
+ inlineOnly: true,
+ inlineSyntaxes: [_BreakSyntax(true)],
+ );
+
+ expect(result, equals('one BREAK two'));
+ });
+
+ test('with caseSensitive disabled', () {
+ final result = markdownToHtml(
+ text,
+ inlineOnly: true,
+ inlineSyntaxes: [_BreakSyntax(false)],
+ );
+
+ expect(result, equals('one <break /> two'));
+ });
+ });
+}
+
+class _BreakSyntax extends InlineSyntax {
+ _BreakSyntax(bool caseSensitive)
+ : super('break', caseSensitive: caseSensitive);
+
+ @override
+ bool onMatch(InlineParser parser, Match match) {
+ parser.addNode(Element.empty('break'));
+ return true;
+ }
+}
diff --git a/pkgs/markdown/test/markdown_test.dart b/pkgs/markdown/test/markdown_test.dart
new file mode 100644
index 0000000..feb2d77
--- /dev/null
+++ b/pkgs/markdown/test/markdown_test.dart
@@ -0,0 +1,306 @@
+// Copyright (c) 2011, 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 'package:markdown/markdown.dart';
+import 'package:test/test.dart';
+
+import 'util.dart';
+
+void main() async {
+ testDirectory('original');
+
+ // Block syntax extensions.
+ testFile(
+ 'extensions/fenced_blockquotes.unit',
+ blockSyntaxes: [const FencedBlockquoteSyntax()],
+ );
+ testFile(
+ 'extensions/fenced_code_blocks.unit',
+ blockSyntaxes: [const FencedCodeBlockSyntax()],
+ );
+ testFile(
+ 'extensions/headers_with_ids.unit',
+ blockSyntaxes: [const HeaderWithIdSyntax()],
+ );
+ testFile(
+ 'extensions/ordered_list_with_checkboxes.unit',
+ blockSyntaxes: [const OrderedListWithCheckboxSyntax()],
+ );
+ testFile(
+ 'extensions/setext_headers_with_ids.unit',
+ blockSyntaxes: [const SetextHeaderWithIdSyntax()],
+ );
+ testFile(
+ 'extensions/tables.unit',
+ blockSyntaxes: [const TableSyntax()],
+ );
+ testFile(
+ 'extensions/unordered_list_with_checkboxes.unit',
+ blockSyntaxes: [const UnorderedListWithCheckboxSyntax()],
+ );
+ testFile(
+ 'extensions/alert_extension.unit',
+ blockSyntaxes: [const AlertBlockSyntax()],
+ );
+
+ // Inline syntax extensions
+ testFile(
+ 'extensions/autolink_extension.unit',
+ inlineSyntaxes: [AutolinkExtensionSyntax()],
+ );
+
+ testFile(
+ 'extensions/emojis.unit',
+ inlineSyntaxes: [EmojiSyntax()],
+ );
+ testFile(
+ 'extensions/inline_html.unit',
+ inlineSyntaxes: [InlineHtmlSyntax()],
+ );
+ testFile(
+ 'extensions/strikethrough.unit',
+ inlineSyntaxes: [StrikethroughSyntax()],
+ );
+ testFile(
+ 'extensions/footnote_block.unit',
+ blockSyntaxes: [const FootnoteDefSyntax()],
+ );
+
+ testDirectory('common_mark');
+ testDirectory('gfm');
+
+ group('Corner cases', () {
+ validateCore('Incorrect Links', '''
+5 Ethernet ([Music](
+''', '''
+<p>5 Ethernet ([Music](</p>
+''');
+
+ validateCore('Incorrect Links - Issue #623 - 1 - Bracketed link 1', '''
+[](<
+''', '''
+<p>[](<</p>
+''');
+
+ validateCore('Incorrect Links - Issue #623 - 2 - Bracketed link 2', '''
+[](<>
+''', '''
+<p>[](<></p>
+''');
+
+ validateCore('Incorrect Links - Issue #623 - 3 - Bracketed link 3', r'''
+[](<\
+''', r'''
+<p>[](<\</p>
+''');
+
+ validateCore('Incorrect Links - Issue #623 - 4 - Link title 1', '''
+[](www.example.com "
+''', '''
+<p>[](www.example.com "</p>
+''');
+
+ validateCore('Incorrect Links - Issue #623 - 5 - Link title 2', r'''
+[](www.example.com "\
+''', r'''
+<p>[](www.example.com "\</p>
+''');
+
+ validateCore('Incorrect Links - Issue #623 - 6 - Reference link label', r'''
+[][\
+''', r'''
+<p>[][\</p>
+''');
+
+ validateCore('Escaping code block language', '''
+```"/><a/href="url">arbitrary_html</a>
+```
+''', '''
+<pre><code class="language-"/><a/href="url">arbitrary_html</a>"></code></pre>
+''');
+
+ validateCore('Unicode ellipsis as punctuation', '''
+"Connecting dot **A** to **B.**…"
+''', '''
+<p>"Connecting dot <strong>A</strong> to <strong>B.</strong>…"</p>
+''');
+ });
+
+ group('Resolver', () {
+ Node? nyanResolver(String text, [_]) =>
+ text.isEmpty ? null : Text('~=[,,_${text}_,,]:3');
+ validateCore(
+ 'simple link resolver',
+ '''
+resolve [this] thing
+''',
+ '''
+<p>resolve ~=[,,_this_,,]:3 thing</p>
+''',
+ linkResolver: nyanResolver);
+
+ validateCore(
+ 'simple image resolver',
+ '''
+resolve ![this] thing
+''',
+ '''
+<p>resolve ~=[,,_this_,,]:3 thing</p>
+''',
+ imageLinkResolver: nyanResolver);
+
+ validateCore(
+ 'can resolve link containing inline tags',
+ '''
+resolve [*star* _underline_] thing
+''',
+ '''
+<p>resolve ~=[,,_*star* _underline__,,]:3 thing</p>
+''',
+ linkResolver: nyanResolver);
+
+ validateCore(
+ 'link resolver uses un-normalized link label',
+ '''
+resolve [TH IS] thing
+''',
+ '''
+<p>resolve ~=[,,_TH IS_,,]:3 thing</p>
+''',
+ linkResolver: nyanResolver);
+
+ validateCore(
+ 'can resolve escaped brackets',
+ r'''
+resolve [\[\]] thing
+''',
+ '''
+<p>resolve ~=[,,_[]_,,]:3 thing</p>
+''',
+ linkResolver: nyanResolver);
+
+ validateCore(
+ 'can choose to _not_ resolve something, like an empty link',
+ '''
+resolve [[]] thing
+''',
+ '''
+<p>resolve ~=[,,_[]_,,]:3 thing</p>
+''',
+ linkResolver: nyanResolver);
+ });
+
+ group('Custom inline syntax', () {
+ final nyanSyntax = <InlineSyntax>[TextSyntax('nyan', sub: '~=[,,_,,]:3')];
+ validateCore(
+ 'simple inline syntax',
+ '''
+nyan''',
+ '''<p>~=[,,_,,]:3</p>
+''',
+ inlineSyntaxes: nyanSyntax);
+
+ validateCore(
+ 'dart custom links',
+ 'links [are<foo>] awesome',
+ '<p>links <a>are<foo></a> awesome</p>\n',
+ linkResolver: (String text, [String? _]) => Element.text(
+ 'a',
+ text.replaceAll('<', '<'),
+ ),
+ );
+
+ // TODO(amouravski): need more tests here for custom syntaxes, as some
+ // things are not quite working properly. The regexps are sometime a little
+ // too greedy, I think.
+ });
+
+ group('Inline only', () {
+ validateCore(
+ 'simple line',
+ '''
+ This would normally create a paragraph.
+ ''',
+ '''
+ This would normally create a paragraph.
+ ''',
+ inlineOnly: true);
+ validateCore(
+ 'strong and em',
+ '''
+ This would _normally_ create a **paragraph**.
+ ''',
+ '''
+ This would <em>normally</em> create a <strong>paragraph</strong>.
+ ''',
+ inlineOnly: true);
+ validateCore(
+ 'link',
+ '''
+ This [link](http://www.example.com/) will work normally.
+ ''',
+ '''
+ This <a href="http://www.example.com/">link</a> will work normally.
+ ''',
+ inlineOnly: true);
+ validateCore(
+ 'references do not work',
+ '''
+ [This][] shouldn't work, though.
+ ''',
+ '''
+ [This][] shouldn't work, though.
+ ''',
+ inlineOnly: true);
+ validateCore(
+ 'less than and ampersand are escaped',
+ '''
+ < &
+ ''',
+ '''
+ < &
+ ''',
+ inlineOnly: true);
+ validateCore(
+ 'keeps newlines',
+ '''
+ This paragraph
+ continues after a newline.
+ ''',
+ '''
+ This paragraph
+ continues after a newline.
+ ''',
+ inlineOnly: true);
+ validateCore(
+ 'ignores block-level markdown syntax',
+ '''
+ 1. This will not be an <ol>.
+ ''',
+ '''
+ 1. This will not be an <ol>.
+ ''',
+ inlineOnly: true);
+ });
+
+ group('ExtensionSet', () {
+ test(
+ '3 asterisks separated with spaces horizontal rule while it is '
+ 'gitHubFlavored',
+ () {
+ // Because `gitHubFlavored` will put `UnorderedListWithCheckboxSyntax`
+ // before `HorizontalRuleSyntax`, the `* * *` will be parsed into an
+ // empty unordered list if `ListSyntax` does not skip the horizontal
+ // rule structure.
+ expect(
+ markdownToHtml('* * *', extensionSet: ExtensionSet.gitHubFlavored),
+ '<hr />\n',
+ );
+ },
+ );
+ });
+}
diff --git a/pkgs/markdown/test/original/autolinks.unit b/pkgs/markdown/test/original/autolinks.unit
new file mode 100644
index 0000000..d3658e8
--- /dev/null
+++ b/pkgs/markdown/test/original/autolinks.unit
@@ -0,0 +1,10 @@
+>>> basic link
+before <http://foo.com/> after
+
+<<<
+<p>before <a href="http://foo.com/">http://foo.com/</a> after</p>
+>>> handles ampersand in url
+<http://foo.com/?a=1&b=2>
+
+<<<
+<p><a href="http://foo.com/?a=1&b=2">http://foo.com/?a=1&b=2</a></p>
diff --git a/pkgs/markdown/test/original/backslash_escapes.unit b/pkgs/markdown/test/original/backslash_escapes.unit
new file mode 100644
index 0000000..e3446f3
--- /dev/null
+++ b/pkgs/markdown/test/original/backslash_escapes.unit
@@ -0,0 +1,38 @@
+>>> Escaped punctuation is indeed escaped.
+Punctuations like \! and \" and \# and \$ and \% and \& and \' and \( and \)
+and \* and \+ and \, and \- and \. and \/ and \: and \; and \< and \= and \>
+and \? and \@ and \[ and \\ and \] and \^ and \_ and \` and \{ and \| and \}
+and \~.
+
+<<<
+<p>Punctuations like ! and " and # and $ and % and & and ' and ( and )
+and * and + and , and - and . and / and : and ; and < and = and >
+and ? and @ and [ and \ and ] and ^ and _ and ` and { and | and }
+and ~.</p>
+>>> Inline code blocks can be escaped.
+Not \`code`.
+
+<<<
+<p>Not `code`.</p>
+>>> Emphasis can be escaped.
+\*Both* \_kinds_.
+
+<<<
+<p>*Both* _kinds_.</p>
+>>> Links can be escaped.
+Escaped brackets: \[foo](bar), and parens: [foo]\(bar).
+
+<<<
+<p>Escaped brackets: [foo](bar), and parens: [foo](bar).</p>
+>>> Reference links can be escaped.
+Front: \[foo][bar] and back: [foo]\[bar].
+
+<<<
+<p>Front: [foo][bar] and back: [foo][bar].</p>
+>>> Images can be escaped.
+Escapes the bang: \,
+and escapes the image: !\[img](img.png).
+
+<<<
+<p>Escapes the bang: !<a href="img.png">img</a>,
+and escapes the image: .</p>
diff --git a/pkgs/markdown/test/original/block_level_html.unit b/pkgs/markdown/test/original/block_level_html.unit
new file mode 100644
index 0000000..ce40760
--- /dev/null
+++ b/pkgs/markdown/test/original/block_level_html.unit
@@ -0,0 +1,38 @@
+>>> single line
+<table></table>
+
+<<<
+<table></table>
+>>> multi-line
+<table>
+ blah
+</table>
+
+<<<
+<table>
+ blah
+</table>
+>>> blank line ends block
+<table>
+ blah
+</table>
+
+para
+
+<<<
+<table>
+ blah
+</table>
+<p>para</p>
+>>> HTML can be bogus
+<bogus>
+blah
+</weird>
+
+para
+
+<<<
+<bogus>
+blah
+</weird>
+<p>para</p>
diff --git a/pkgs/markdown/test/original/block_quotes.unit b/pkgs/markdown/test/original/block_quotes.unit
new file mode 100644
index 0000000..548bc1a
--- /dev/null
+++ b/pkgs/markdown/test/original/block_quotes.unit
@@ -0,0 +1,54 @@
+>>> single line
+> blah
+
+<<<
+<blockquote>
+<p>blah</p>
+</blockquote>
+>>> with two paragraphs
+> first
+>
+> second
+
+<<<
+<blockquote>
+<p>first</p>
+<p>second</p>
+</blockquote>
+>>> nested
+> one
+>> two
+> > > three
+
+<<<
+<blockquote>
+<p>one</p>
+<blockquote>
+<p>two</p>
+<blockquote>
+<p>three</p>
+</blockquote>
+</blockquote>
+</blockquote>
+>>> qoute with lazy continuation
+> quote
+text
+<<<
+<blockquote>
+<p>quote
+text</p>
+</blockquote>
+>>> quote turns what might be an h2 into an hr
+> quote
+---
+
+<<<
+<blockquote>
+<p>quote</p>
+</blockquote>
+<hr />
+>>> issue #495
+ >
+<<<
+<blockquote>
+</blockquote>
diff --git a/pkgs/markdown/test/original/code_blocks.unit b/pkgs/markdown/test/original/code_blocks.unit
new file mode 100644
index 0000000..330a90f
--- /dev/null
+++ b/pkgs/markdown/test/original/code_blocks.unit
@@ -0,0 +1,65 @@
+>>> single line
+ code
+
+<<<
+<pre><code>code
+</code></pre>
+>>> include leading whitespace after indentation
+ zero
+ one
+ two
+ three
+
+<<<
+<pre><code>zero
+ one
+ two
+ three
+</code></pre>
+>>> code blocks separated by newlines form one block
+ zero
+ one
+
+ two
+
+ three
+
+<<<
+<pre><code>zero
+one
+
+two
+
+three
+</code></pre>
+>>> code blocks separated by two newlines form multiple blocks
+ zero
+ one
+
+
+ two
+
+
+ three
+
+<<<
+<pre><code>zero
+one
+
+
+two
+
+
+three
+</code></pre>
+>>> escape HTML characters
+ <&>
+
+<<<
+<pre><code><&>
+</code></pre>
+>>> issue #497
+ 'foo'
+<<<
+<pre><code>'foo'
+</code></pre>
diff --git a/pkgs/markdown/test/original/emphasis_and_strong.unit b/pkgs/markdown/test/original/emphasis_and_strong.unit
new file mode 100644
index 0000000..13a6dd2
--- /dev/null
+++ b/pkgs/markdown/test/original/emphasis_and_strong.unit
@@ -0,0 +1,83 @@
+>>> single asterisks
+before *em* after
+
+<<<
+<p>before <em>em</em> after</p>
+>>> single underscores
+before _em_ after
+
+<<<
+<p>before <em>em</em> after</p>
+>>> double asterisks
+before **strong** after
+
+<<<
+<p>before <strong>strong</strong> after</p>
+>>> double underscores
+before __strong__ after
+
+<<<
+<p>before <strong>strong</strong> after</p>
+>>> unmatched asterisk
+before *after
+
+<<<
+<p>before *after</p>
+>>> unmatched underscore
+before _after
+
+<<<
+<p>before _after</p>
+>>> multiple spans in one text
+a *one* b _two_ c
+
+<<<
+<p>a <em>one</em> b <em>two</em> c</p>
+>>> multi-line
+before *first
+second* after
+
+<<<
+<p>before <em>first
+second</em> after</p>
+>>> not processed when surrounded by spaces
+a * b * c _ d _ e
+
+<<<
+<p>a * b * c _ d _ e</p>
+>>> strong then emphasis
+**strong***em*
+
+<<<
+<p><strong>strong</strong><em>em</em></p>
+>>> emphasis then strong
+*em***strong**
+
+<<<
+<p><em>em</em><strong>strong</strong></p>
+>>> emphasis inside strong
+**strong *em***
+
+<<<
+<p><strong>strong <em>em</em></strong></p>
+>>> mismatched in nested
+*a _b* c_
+
+<<<
+<p><em>a _b</em> c_</p>
+>>> in the middle of a word
+a_b_c a__b__c a*b*c a**b**c
+<<<
+<p>a_b_c a__b__c a<em>b</em>c a<strong>b</strong>c</p>
+>>> prefixing a word
+_a_b __a__b *a*b **a**b
+<<<
+<p>_a_b __a__b <em>a</em>b <strong>a</strong>b</p>
+>>> suffixing a word
+a_b_ a__b__ a*b* a**b**
+<<<
+<p>a_b_ a__b__ a<em>b</em> a<strong>b</strong></p>
+>>> spanning words
+_a_b c_d_ __a__b c__d__ *a*b c*d* **a**b c**d**
+<<<
+<p><em>a_b c_d</em> <strong>a__b c__d</strong> <em>a</em>b c<em>d</em> <strong>a</strong>b c<strong>d</strong></p>
diff --git a/pkgs/markdown/test/original/fenced_code_block.unit b/pkgs/markdown/test/original/fenced_code_block.unit
new file mode 100644
index 0000000..779e747
--- /dev/null
+++ b/pkgs/markdown/test/original/fenced_code_block.unit
@@ -0,0 +1,7 @@
+>>> issue #497
+```
+'foo'
+```
+<<<
+<pre><code>'foo'
+</code></pre>
diff --git a/pkgs/markdown/test/original/hard_line_breaks.unit b/pkgs/markdown/test/original/hard_line_breaks.unit
new file mode 100644
index 0000000..c8d1626
--- /dev/null
+++ b/pkgs/markdown/test/original/hard_line_breaks.unit
@@ -0,0 +1,40 @@
+>>> hard line break in a paragraph, using backslash
+First line.\
+Second line.
+
+<<<
+<p>First line.<br />
+Second line.</p>
+>>> within emphasis, using backslash
+*Emphasised\
+text.*
+
+<<<
+<p><em>Emphasised<br />
+text.</em></p>
+>>> no escape within code, using backslash
+`Some\
+code`.
+
+<<<
+<p><code>Some\ code</code>.</p>
+>>> hard line break in a paragraph, using trailing spaces
+First line.
+Second line.
+
+<<<
+<p>First line.
+Second line.</p>
+>>> within emphasis, using trailing spaces
+*Emphasised
+text.*
+
+<<<
+<p><em>Emphasised
+text.</em></p>
+>>> no escape within code, using trailing spaces
+`Some
+code`.
+
+<<<
+<p><code>Some code</code>.</p>
diff --git a/pkgs/markdown/test/original/headers.unit b/pkgs/markdown/test/original/headers.unit
new file mode 100644
index 0000000..928da8b
--- /dev/null
+++ b/pkgs/markdown/test/original/headers.unit
@@ -0,0 +1,35 @@
+>>> h1
+# header
+
+<<<
+<h1>header</h1>
+>>> h2
+## header
+
+<<<
+<h2>header</h2>
+>>> h3
+### header
+
+<<<
+<h3>header</h3>
+>>> h4
+#### header
+
+<<<
+<h4>header</h4>
+>>> h5
+##### header
+
+<<<
+<h5>header</h5>
+>>> h6
+###### header
+
+<<<
+<h6>header</h6>
+>>> trailing "#" are removed
+# header ######
+
+<<<
+<h1>header</h1>
diff --git a/pkgs/markdown/test/original/horizontal_rules.unit b/pkgs/markdown/test/original/horizontal_rules.unit
new file mode 100644
index 0000000..36642e9
--- /dev/null
+++ b/pkgs/markdown/test/original/horizontal_rules.unit
@@ -0,0 +1,20 @@
+>>> from dashes
+---
+
+<<<
+<hr />
+>>> from asterisks
+***
+
+<<<
+<hr />
+>>> from underscores
+___
+
+<<<
+<hr />
+>>> can include up to two spaces
+_ _ _
+
+<<<
+<hr />
diff --git a/pkgs/markdown/test/original/html_block.unit b/pkgs/markdown/test/original/html_block.unit
new file mode 100644
index 0000000..f6b9216
--- /dev/null
+++ b/pkgs/markdown/test/original/html_block.unit
@@ -0,0 +1,11 @@
+>>> issue https://github.com/dart-lang/markdown/issues/547
+<?code-excerpt ?>
+```xml
+<q>
+</q>
+```
+<<<
+<?code-excerpt ?>
+<pre><code class="language-xml"><q>
+</q>
+</code></pre>
\ No newline at end of file
diff --git a/pkgs/markdown/test/original/html_encoding.unit b/pkgs/markdown/test/original/html_encoding.unit
new file mode 100644
index 0000000..dab9043
--- /dev/null
+++ b/pkgs/markdown/test/original/html_encoding.unit
@@ -0,0 +1,15 @@
+>>> less than and ampersand are escaped
+< &
+
+<<<
+<p>< &</p>
+>>> greater than is escaped
+not you >
+
+<<<
+<p>not you ></p>
+>>> existing entities are untouched
+&
+
+<<<
+<p>&</p>
diff --git a/pkgs/markdown/test/original/inline_code.unit b/pkgs/markdown/test/original/inline_code.unit
new file mode 100644
index 0000000..7315931
--- /dev/null
+++ b/pkgs/markdown/test/original/inline_code.unit
@@ -0,0 +1,87 @@
+>>> simple case
+before `source` after
+
+<<<
+<p>before <code>source</code> after</p>
+>>> single characters
+before `x` and `_` after
+
+<<<
+<p>before <code>x</code> and <code>_</code> after</p>
+>>> unmatched backtick
+before ` after
+
+<<<
+<p>before ` after</p>
+>>> multiple spans in one text
+a `one` b `two` c
+
+<<<
+<p>a <code>one</code> b <code>two</code> c</p>
+>>> multi-line
+before `first
+second` after
+
+<<<
+<p>before <code>first second</code> after</p>
+>>> simple double backticks
+before ``source`` after
+
+<<<
+<p>before <code>source</code> after</p>
+>>> even more backticks
+before ````source with ``` and```` after
+
+<<<
+<p>before <code>source with ``` and</code> after</p>
+>>> double backticks
+before ``can `contain` backticks`` after
+
+<<<
+<p>before <code>can `contain` backticks</code> after</p>
+>>> double backticks with spaces
+before `` `tick` `` after
+
+<<<
+<p>before <code>`tick`</code> after</p>
+>>> multiline single backticks with spaces
+before `in tick
+another` after
+
+<<<
+<p>before <code>in tick another</code> after</p>
+>>> multiline double backticks with spaces
+before ``in `tick`
+another`` after
+
+<<<
+<p>before <code>in `tick` another</code> after</p>
+>>> ignore markup inside code
+before `*b* _c_` after
+
+<<<
+<p>before <code>*b* _c_</code> after</p>
+>>> escape HTML characters
+`<&>`
+
+<<<
+<p><code><&></code></p>
+>>> escape HTML tags
+'*' `<em>`
+
+<<<
+<p>'*' <code><em></code></p>
+>>> leave unmatched backticks when first are too long
+before ``` tick `` after
+
+<<<
+<p>before ``` tick `` after</p>
+>>> leave unmatched backticks when first are too short
+before `` tick ``` after
+
+<<<
+<p>before `` tick ``` after</p>
+>>> issue #497
+`'foo'`
+<<<
+<p><code>'foo'</code></p>
diff --git a/pkgs/markdown/test/original/inline_images.unit b/pkgs/markdown/test/original/inline_images.unit
new file mode 100644
index 0000000..c8180ce
--- /dev/null
+++ b/pkgs/markdown/test/original/inline_images.unit
@@ -0,0 +1,29 @@
+>>> image
+
+
+<<<
+<p><img src="http://foo.com/foo.png" alt="" /></p>
+>>> alternate text
+
+
+<<<
+<p><img src="http://foo.com/foo.png" alt="alternate text" /></p>
+>>> title
+
+
+<<<
+<p><img src="http://foo.com/foo.png" alt="" title="optional title" /></p>
+>>> invalid alt text
+
+
+<<<
+<p><img src="http://foo.com/foo.png" alt="alt" /></p>
+>>> XSS
+)
+
+<<<
+<p><img src="%22onerror=%22alert('XSS')" alt="Uh oh..." /></p>
+>>> URL-escaping should be left alone inside the destination
+
+<<<
+<p><img src="https://example/foo%2Fvar" alt="" /></p>
\ No newline at end of file
diff --git a/pkgs/markdown/test/original/inline_links.unit b/pkgs/markdown/test/original/inline_links.unit
new file mode 100644
index 0000000..0a40407
--- /dev/null
+++ b/pkgs/markdown/test/original/inline_links.unit
@@ -0,0 +1,87 @@
+>>> double quotes for title
+links [are](http://foo.com "woo") awesome
+
+<<<
+<p>links <a href="http://foo.com" title="woo">are</a> awesome</p>
+>>> no title
+links [are](http://foo.com) awesome
+
+<<<
+<p>links <a href="http://foo.com">are</a> awesome</p>
+>>> can style link contents
+links [*are*](http://foo.com) awesome
+
+<<<
+<p>links <a href="http://foo.com"><em>are</em></a> awesome</p>
+>>> image inside link
+links [](http://foo.com) awesome
+
+<<<
+<p>links <a href="http://foo.com"><img src="/are.png" alt="" /></a> awesome</p>
+>>> image with alt inside link
+links [](http://foo.com) awesome
+
+<<<
+<p>links <a href="http://foo.com"><img src="/are.png" alt="my alt" /></a> awesome</p>
+>>> image with title inside link
+links [](http://foo.com) awesome
+
+<<<
+<p>links <a href="http://foo.com"><img src="/are.png" alt="" title="my title" /></a> awesome</p>
+>>> no URL
+links [are]() awesome
+
+<<<
+<p>links <a href="">are</a> awesome</p>
+>>> URL wrapped in angle brackets
+links [are](<http://example.com>) awesome
+
+<<<
+<p>links <a href="http://example.com">are</a> awesome</p>
+>>> URL wrapped in angle brackets with a title; https://github.com/commonmark/CommonMark/issues/521
+links [are](<http://example.com> "title") awesome
+
+<<<
+<p>links <a href="http://example.com" title="title">are</a> awesome</p>
+>>> multi-line link
+links [are
+awesome](<http://example.com>).
+
+<<<
+<p>links <a href="http://example.com">are
+awesome</a>.</p>
+>>> multi-line link with a title
+links [are](http://foo.com
+"woo") awesome
+
+<<<
+<p>links <a href="http://foo.com" title="woo">are</a> awesome</p>
+>>> not a real link
+links [are] (http://foo.com) awesome
+
+<<<
+<p>links [are] (http://foo.com) awesome</p>
+>>> resolver link without a resolver
+links [are *awesome*]
+
+<<<
+<p>links [are <em>awesome</em>]</p>
+>>> links with escaped parens
+[a](\(yes-a-link)
+[a](\(yes-a-link\))
+[a](\\(not-a-link\))
+[a](\\(yes-a-link\)))
+<<<
+<p><a href="(yes-a-link">a</a>
+<a href="(yes-a-link)">a</a>
+[a](\(not-a-link))
+<a href="(yes-a-link))">a</a></p>
+>>> links with unbalanced parentheses
+[foo](link(1.png) (what?)
+<<<
+<p>[foo](link(1.png) (what?)</p>
+>>> not an inline link: the title's ending quote is escaped
+links [are](<http://example.com> "title\") awesome
+
+<<<
+<p>links [are](<a href="http://example.com">http://example.com</a> "title") awesome</p>
\ No newline at end of file
diff --git a/pkgs/markdown/test/original/ordered_lists.unit b/pkgs/markdown/test/original/ordered_lists.unit
new file mode 100644
index 0000000..8156889
--- /dev/null
+++ b/pkgs/markdown/test/original/ordered_lists.unit
@@ -0,0 +1,49 @@
+>>> ordered list with multiple items
+1. one
+2. two
+10. ten
+<<<
+<ol>
+<li>one</li>
+<li>two</li>
+<li>ten</li>
+</ol>
+>>> ordered list with almost nested item
+1. one
+45. two
+ 12345. three
+
+<<<
+<ol>
+<li>one</li>
+<li>two</li>
+<li>three</li>
+</ol>
+>>> nested ordered lists
+1. one
+2. two
+ 3. three
+ 4. four
+5. five
+<<<
+<ol>
+<li>one</li>
+<li>two
+<ol start="3">
+<li>three</li>
+<li>four</li>
+</ol>
+</li>
+<li>five</li>
+</ol>
+>>> new list markers start new lists
+1. a
+* b
+
+<<<
+<ol>
+<li>a</li>
+</ol>
+<ul>
+<li>b</li>
+</ul>
diff --git a/pkgs/markdown/test/original/paragraphs.unit b/pkgs/markdown/test/original/paragraphs.unit
new file mode 100644
index 0000000..a20a556
--- /dev/null
+++ b/pkgs/markdown/test/original/paragraphs.unit
@@ -0,0 +1,55 @@
+>>> consecutive lines form a single paragraph
+This is the first line.
+This is the second line.
+
+<<<
+<p>This is the first line.
+This is the second line.</p>
+>>> are terminated by a header
+para
+# header
+
+<<<
+<p>para</p>
+<h1>header</h1>
+>>> are terminated by a hr
+para
+___
+
+<<<
+<p>para</p>
+<hr />
+>>> are terminated by an unordered list
+para
+* list
+
+<<<
+<p>para</p>
+<ul>
+<li>list</li>
+</ul>
+>>> are terminated by an ordered list
+para
+1. list
+
+<<<
+<p>para</p>
+<ol>
+<li>list</li>
+</ol>
+>>> take account of windows line endings
+line1
+
+line2
+
+
+<<<
+<p>line1</p>
+<p>line2</p>
+>>> cannot be terminated by indented code blocks
+para
+ not code
+
+<<<
+<p>para
+not code</p>
diff --git a/pkgs/markdown/test/original/reference_images.unit b/pkgs/markdown/test/original/reference_images.unit
new file mode 100644
index 0000000..b098d24
--- /dev/null
+++ b/pkgs/markdown/test/original/reference_images.unit
@@ -0,0 +1,34 @@
+>>> image
+![][foo]
+
+[foo]: http://foo.com/foo.png
+
+<<<
+<p><img src="http://foo.com/foo.png" alt="" /></p>
+>>> alternate text
+![alternate text][foo]
+
+[foo]: http://foo.com/foo.png
+
+<<<
+<p><img src="http://foo.com/foo.png" alt="alternate text" /></p>
+>>> title
+![][foo]
+
+[foo]: http://foo.com/foo.png "optional title"
+
+<<<
+<p><img src="http://foo.com/foo.png" alt="" title="optional title" /></p>
+>>> invalid alt text
+![`alt`][foo]
+
+[foo]: http://foo.com/foo.png "optional title"
+
+<<<
+<p><img src="http://foo.com/foo.png" alt="alt" title="optional title" /></p>
+>>> shortcut reference image
+![foo]
+
+[foo]: http://foo.com/foo.png
+<<<
+<p><img src="http://foo.com/foo.png" alt="foo" /></p>
diff --git a/pkgs/markdown/test/original/reference_links.unit b/pkgs/markdown/test/original/reference_links.unit
new file mode 100644
index 0000000..bc3e336
--- /dev/null
+++ b/pkgs/markdown/test/original/reference_links.unit
@@ -0,0 +1,100 @@
+>>> double quotes for title
+links [are][a] awesome
+
+[a]: http://foo.com "woo"
+
+<<<
+<p>links <a href="http://foo.com" title="woo">are</a> awesome</p>
+>>> single quoted title
+links [are][a] awesome
+
+[a]: http://foo.com 'woo'
+
+<<<
+<p>links <a href="http://foo.com" title="woo">are</a> awesome</p>
+>>> parentheses for title
+links [are][a] awesome
+
+[a]: http://foo.com (woo)
+
+<<<
+<p>links <a href="http://foo.com" title="woo">are</a> awesome</p>
+>>> no title
+links [are][a] awesome
+
+[a]: http://foo.com
+
+<<<
+<p>links <a href="http://foo.com">are</a> awesome</p>
+>>> unknown link becomes plaintext
+[not] [known]
+
+<<<
+<p>[not] [known]</p>
+>>> can style link contents
+links [*are*][a] awesome
+
+[a]: http://foo.com
+
+<<<
+<p>links <a href="http://foo.com"><em>are</em></a> awesome</p>
+>>> inline styles after a bad link are processed
+[bad] `code`
+
+<<<
+<p>[bad] <code>code</code></p>
+>>> empty reference uses text from link
+links [are][] awesome
+
+[are]: http://foo.com
+
+<<<
+<p>links <a href="http://foo.com">are</a> awesome</p>
+>>> references are case-insensitive
+links [ARE][] awesome
+
+[are]: http://foo.com
+
+<<<
+<p>links <a href="http://foo.com">ARE</a> awesome</p>
+>>> shortcut reference links
+links [are] awesome
+
+[are]: http://foo.com
+<<<
+<p>links <a href="http://foo.com">are</a> awesome</p>
+>>> reference definitions can span lines
+links [are] [awesome]
+
+[are]:
+http://foo.com
+[awesome]:
+http://bar.com
+"Long
+Title"
+<<<
+<p>links <a href="http://foo.com">are</a> <a href="http://bar.com" title="Long
+Title">awesome</a></p>
+>>> references can be defined in blocks
+> links [are] awesome
+>
+> [are]: http://foo.com
+<<<
+<blockquote>
+<p>links <a href="http://foo.com">are</a> awesome</p>
+</blockquote>
+>>> reference link regression for github.com/dart-lang/markdown/issues/176
+[![Coverage Status][coverage_status]][coverage_page]
+
+[coverage_page]:https://coveralls.io/github/yeradis/stay_points.dart?branch=master
+[coverage_status]: https://coveralls.io/repos/github/yeradis/stay_points.dart/badge.svg?branch=master
+<<<
+<p><a href="https://coveralls.io/github/yeradis/stay_points.dart?branch=master"><img src="https://coveralls.io/repos/github/yeradis/stay_points.dart/badge.svg?branch=master" alt="Coverage Status" /></a></p>
+>>> compressed reference link label is normalized
+Text [foo
+bar][].
+
+[foo bar]: http://bar.com
+<<<
+<p>Text <a href="http://bar.com">foo
+bar</a>.</p>
diff --git a/pkgs/markdown/test/original/setext_headers.unit b/pkgs/markdown/test/original/setext_headers.unit
new file mode 100644
index 0000000..f094801
--- /dev/null
+++ b/pkgs/markdown/test/original/setext_headers.unit
@@ -0,0 +1,32 @@
+>>> h1
+text
+===
+
+<<<
+<h1>text</h1>
+>>> h2
+text
+---
+
+<<<
+<h2>text</h2>
+>>> h1 bar on first line becomes text
+===
+
+<<<
+<p>===</p>
+>>> h2 bar on first line becomes list
+-
+
+<<<
+<ul>
+<li></li>
+</ul>
+>>> can be multiline
+header
+on two lines
+==
+
+<<<
+<h1>header
+on two lines</h1>
diff --git a/pkgs/markdown/test/original/strong.unit b/pkgs/markdown/test/original/strong.unit
new file mode 100644
index 0000000..e558424
--- /dev/null
+++ b/pkgs/markdown/test/original/strong.unit
@@ -0,0 +1,32 @@
+>>> using asterisks
+before **strong** after
+
+<<<
+<p>before <strong>strong</strong> after</p>
+>>> using underscores
+before __strong__ after
+
+<<<
+<p>before <strong>strong</strong> after</p>
+>>> unmatched asterisks
+before ** after
+
+<<<
+<p>before ** after</p>
+>>> unmatched underscores
+before __ after
+
+<<<
+<p>before __ after</p>
+>>> multiple spans in one text
+a **one** b __two__ c
+
+<<<
+<p>a <strong>one</strong> b <strong>two</strong> c</p>
+>>> multi-line
+before **first
+second** after
+
+<<<
+<p>before <strong>first
+second</strong> after</p>
diff --git a/pkgs/markdown/test/original/unordered_lists.unit b/pkgs/markdown/test/original/unordered_lists.unit
new file mode 100644
index 0000000..6017775
--- /dev/null
+++ b/pkgs/markdown/test/original/unordered_lists.unit
@@ -0,0 +1,151 @@
+>>> asterisk, plus and hyphen
+* star
+- dash
++ plus
+
+<<<
+<ul>
+<li>star</li>
+</ul>
+<ul>
+<li>dash</li>
+</ul>
+<ul>
+<li>plus</li>
+</ul>
+>>> new markers begin new lists
+* a
+1. b
+
+<<<
+<ul>
+<li>a</li>
+</ul>
+<ol>
+<li>b</li>
+</ol>
+>>> allow a tab after the marker
+* a
+1. b
+
+<<<
+<ul>
+<li>a</li>
+</ul>
+<ol>
+<li>b</li>
+</ol>
+>>> wrap items in paragraphs if blank lines separate
+* one
+
+* two
+
+<<<
+<ul>
+<li>
+<p>one</p>
+</li>
+<li>
+<p>two</p>
+</li>
+</ul>
+>>> force paragraph on item before and after blank lines
+* one
+* two
+
+* three
+
+<<<
+<ul>
+<li>
+<p>one</p>
+</li>
+<li>
+<p>two</p>
+</li>
+<li>
+<p>three</p>
+</li>
+</ul>
+>>> do not force paragraph if item is already block
+* > quote
+
+* # header
+
+<<<
+<ul>
+<li>
+<blockquote>
+<p>quote</p>
+</blockquote>
+</li>
+<li>
+<h1>header</h1>
+</li>
+</ul>
+>>> can contain multiple paragraphs
+* one
+
+ two
+
+* three
+
+<<<
+<ul>
+<li>
+<p>one</p>
+<p>two</p>
+</li>
+<li>
+<p>three</p>
+</li>
+</ul>
+>>> can span newlines
+* one
+ two
+* three
+
+<<<
+<ul>
+<li>one
+two</li>
+<li>three</li>
+</ul>
+>>> can nest lists
+* one
+ * nested one
+ * nested two
+
+* two
+
+<<<
+<ul>
+<li>
+<p>one</p>
+<ul>
+<li>nested one</li>
+<li>nested two</li>
+</ul>
+</li>
+<li>
+<p>two</p>
+</li>
+</ul>
+>>> list item allows lazy continuations
+- list
+item
+
+<<<
+<ul>
+<li>list
+item</li>
+</ul>
+>>> list item turns what might be an h2 into nothing
+- list
+---
+
+<<<
+<ul>
+<li>list</li>
+</ul>
+<hr />
diff --git a/pkgs/markdown/test/util.dart b/pkgs/markdown/test/util.dart
new file mode 100644
index 0000000..3d7dc6a
--- /dev/null
+++ b/pkgs/markdown/test/util.dart
@@ -0,0 +1,124 @@
+// Copyright (c) 2011, 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:io/ansi.dart' as ansi;
+import 'package:markdown/markdown.dart';
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+
+import '../tool/expected_output.dart';
+
+/// Runs tests defined in "*.unit" files inside directory [name].
+void testDirectory(String name, {ExtensionSet? extensionSet}) {
+ for (final dataCase in dataCasesUnder(testDirectory: name)) {
+ final description =
+ '${dataCase.directory}/${dataCase.file}.unit ${dataCase.description}';
+
+ final inlineSyntaxes = <InlineSyntax>[];
+ final blockSyntaxes = <BlockSyntax>[];
+ var enableTagfilter = false;
+
+ if (dataCase.file.endsWith('_extension')) {
+ final extension = dataCase.file.substring(
+ 0,
+ dataCase.file.lastIndexOf('_extension'),
+ );
+ switch (extension) {
+ case 'autolinks':
+ inlineSyntaxes.add(AutolinkExtensionSyntax());
+ break;
+ case 'strikethrough':
+ inlineSyntaxes.add(StrikethroughSyntax());
+ break;
+ case 'tables':
+ blockSyntaxes.add(const TableSyntax());
+ break;
+ case 'disallowed_raw_html':
+ enableTagfilter = true;
+ break;
+ default:
+ throw UnimplementedError('Unimplemented extension "$extension"');
+ }
+ }
+
+ validateCore(
+ description,
+ dataCase.input,
+ dataCase.expectedOutput,
+ extensionSet: extensionSet,
+ inlineSyntaxes: inlineSyntaxes,
+ blockSyntaxes: blockSyntaxes,
+ enableTagfilter: enableTagfilter,
+ );
+ }
+}
+
+void testFile(
+ String file, {
+ Iterable<BlockSyntax> blockSyntaxes = const [],
+ Iterable<InlineSyntax> inlineSyntaxes = const [],
+}) {
+ for (final dataCase
+ in dataCasesInFile(path: p.join(p.current, 'test', file))) {
+ final description =
+ '${dataCase.directory}/${dataCase.file}.unit ${dataCase.description}';
+ validateCore(
+ description,
+ dataCase.input,
+ dataCase.expectedOutput,
+ blockSyntaxes: blockSyntaxes,
+ inlineSyntaxes: inlineSyntaxes,
+ );
+ }
+}
+
+void validateCore(
+ String description,
+ String markdown,
+ String html, {
+ Iterable<BlockSyntax> blockSyntaxes = const [],
+ Iterable<InlineSyntax> inlineSyntaxes = const [],
+ ExtensionSet? extensionSet,
+ Resolver? linkResolver,
+ Resolver? imageLinkResolver,
+ bool inlineOnly = false,
+ bool enableTagfilter = false,
+}) {
+ test(description, () {
+ final result = markdownToHtml(
+ markdown,
+ blockSyntaxes: blockSyntaxes,
+ inlineSyntaxes: inlineSyntaxes,
+ extensionSet: extensionSet,
+ linkResolver: linkResolver,
+ imageLinkResolver: imageLinkResolver,
+ inlineOnly: inlineOnly,
+ enableTagfilter: enableTagfilter,
+ );
+
+ markdownPrintOnFailure(markdown, html, result);
+
+ expect(result, html);
+ });
+}
+
+String whitespaceColor(String input) => input
+ .replaceAll(' ', ansi.lightBlue.wrap('·')!)
+ .replaceAll('\t', ansi.backgroundDarkGray.wrap('\t')!);
+
+void markdownPrintOnFailure(String markdown, String expected, String actual) {
+ printOnFailure("""
+INPUT:
+'''r
+${whitespaceColor(markdown)}'''
+
+EXPECTED:
+'''r
+${whitespaceColor(expected)}'''
+
+GOT:
+'''r
+${whitespaceColor(actual)}'''
+""");
+}
diff --git a/pkgs/markdown/test/util_test.dart b/pkgs/markdown/test/util_test.dart
new file mode 100644
index 0000000..5a2108a
--- /dev/null
+++ b/pkgs/markdown/test/util_test.dart
@@ -0,0 +1,86 @@
+// 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 'package:markdown/markdown.dart';
+import 'package:markdown/src/util.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('String.toLines()', () {
+ test('a single line without a line ending', () {
+ const text = 'Foo';
+ final lines = text.toLines();
+
+ expect(lines.map((e) => e.toMap()), [
+ {
+ 'content': 'Foo',
+ 'isBlankLine': false,
+ }
+ ]);
+ });
+
+ test('a single line with a line ending', () {
+ const text = 'Foo\n';
+ final lines = text.toLines();
+
+ expect(lines.map((e) => e.toMap()), [
+ {
+ 'content': 'Foo',
+ 'isBlankLine': false,
+ },
+ ]);
+ });
+
+ test('multiple lines with a blank line in between', () {
+ const text = 'Foo\r\n\nBar';
+ final lines = text.toLines();
+
+ expect(lines.map((e) => e.toMap()), [
+ {
+ 'content': 'Foo',
+ 'isBlankLine': false,
+ },
+ {
+ 'content': '',
+ 'isBlankLine': true,
+ },
+ {
+ 'content': 'Bar',
+ 'isBlankLine': false,
+ }
+ ]);
+ });
+ });
+
+ group('String.indentation()', () {
+ test('only spaces', () {
+ expect(' '.indentation(), 3);
+ expect(' '.indentation(), 4);
+ expect(' '.indentation(), 5);
+ });
+
+ test('spaces and tabs', () {
+ expect('\t '.indentation(), 6);
+ expect(' \t '.indentation(), 5);
+ expect(' \t'.indentation(), 4);
+ expect('\t\t '.indentation(), 10);
+ expect(' \t\t '.indentation(), 9);
+ expect(' \t\t'.indentation(), 8);
+ });
+
+ test('spaces, tabs and non whitespace characters', () {
+ expect('\t foo'.indentation(), 6);
+ expect(' \t foo'.indentation(), 5);
+ expect(' \tfoo'.indentation(), 4);
+ });
+ });
+}
+
+extension on Line {
+ Map<String, dynamic> toMap() => {
+ 'content': content,
+ 'isBlankLine': isBlankLine,
+ if (tabRemaining != null) 'tabRemaining': tabRemaining,
+ };
+}
diff --git a/pkgs/markdown/test/version_test.dart b/pkgs/markdown/test/version_test.dart
new file mode 100644
index 0000000..93fedcb
--- /dev/null
+++ b/pkgs/markdown/test/version_test.dart
@@ -0,0 +1,41 @@
+// Copyright (c) 2020, 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:io';
+
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+import 'package:yaml/yaml.dart';
+
+void main() {
+ test('check versions', () async {
+ final binary = p.join(p.current, 'bin', 'markdown.dart');
+ final dartBin = Platform.executable;
+ final result = Process.runSync(dartBin, [binary, '--version']);
+ expect(
+ result.exitCode,
+ 0,
+ reason: 'Exit code expected: 0; actual: ${result.exitCode}\n\n'
+ 'stdout: ${result.stdout}\n\n'
+ 'stderr: ${result.stderr}',
+ );
+
+ final binVersion = (result.stdout as String).trim();
+
+ final pubspecFile = p.join(p.current, 'pubspec.yaml');
+
+ final pubspecContent =
+ loadYaml(File(pubspecFile).readAsStringSync()) as YamlMap;
+
+ expect(
+ binVersion,
+ pubspecContent['version'],
+ reason: 'The version reported by bin/markdown.dart should match the '
+ 'version in pubspec. Run `dart run build_runner build` to update.',
+ );
+ });
+}
diff --git a/pkgs/markdown/tool/README.md b/pkgs/markdown/tool/README.md
new file mode 100644
index 0000000..ff2f749
--- /dev/null
+++ b/pkgs/markdown/tool/README.md
@@ -0,0 +1,48 @@
+# Developer Tools
+
+This directory contains tools for developers of the Dart markdown package.
+
+## dartdoc_compare.dart
+
+When you make a change to the package that might have subtle consequences on
+how Markdown is parsed, it would be really great to see how your output compares
+to the previous output, on a large collection of Markdown.
+
+One such collection is the Dartdoc comments of any Dart package, which [dartdoc]
+translates into HTML, with the help of this markdown package. You can use the
+`dartdoc_compare.dart` script to compare what changes your code will make to
+dartdoc's output. Here's how it works:
+
+1. Clone the [dartdoc git repository].
+2. Get a copy of some Dart code that you would like to use for the comparison.
+3. Run the `dartdoc_compare.dart` script like so:
+
+ ```
+ $ dart tool/dartdoc_compare.dart \
+ --dartdoc-dir=<dartdoc repo> \
+ --before=<git SHA of "previous" code> \
+ <directory of dart code for comparison>
+ ```
+
+4. The tool will then walk through the following steps:
+
+ 1. cd into the dartdoc directory, change `pubspec.yaml` to depend on your
+ "before" version of markdown, and run `pub get`.
+ 2. cd into the directory of dart code, and run `pub get`.
+ 3. Run dartdoc.
+ 4. cd back into the dartdoc directory, change `pubspec.yaml` to depend on
+ your "after" version of markdown (defaults to HEAD), and run `pub get`.
+ 5. Repeat steps 2 and 3.
+ 6. Diff the output of steps 3 and 5, and show you how to diff it yourself.
+
+[dartdoc]: https://pub.dev/packages/dartdoc
+[dartdoc git repository]: https://github.com/dart-lang/dartdoc
+
+## stats.dart
+
+In an effort to make this package CommonMark-compliant, we have a script that
+runs the package through the CommonMark specs. To see help:
+
+```bash
+$ dart tool/stats.dart --help
+```
diff --git a/pkgs/markdown/tool/case_folding.txt b/pkgs/markdown/tool/case_folding.txt
new file mode 100644
index 0000000..932ace2
--- /dev/null
+++ b/pkgs/markdown/tool/case_folding.txt
@@ -0,0 +1,1624 @@
+# CaseFolding-14.0.0.txt
+# Date: 2021-03-08, 19:35:41 GMT
+# © 2021 Unicode®, Inc.
+# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
+# For terms of use, see http://www.unicode.org/terms_of_use.html
+#
+# Unicode Character Database
+# For documentation, see http://www.unicode.org/reports/tr44/
+#
+# Case Folding Properties
+#
+# This file is a supplement to the UnicodeData file.
+# It provides a case folding mapping generated from the Unicode Character Database.
+# If all characters are mapped according to the full mapping below, then
+# case differences (according to UnicodeData.txt and SpecialCasing.txt)
+# are eliminated.
+#
+# The data supports both implementations that require simple case foldings
+# (where string lengths don't change), and implementations that allow full case folding
+# (where string lengths may grow). Note that where they can be supported, the
+# full case foldings are superior: for example, they allow "MASSE" and "Maße" to match.
+#
+# All code points not listed in this file map to themselves.
+#
+# NOTE: case folding does not preserve normalization formats!
+#
+# For information on case folding, including how to have case folding
+# preserve normalization formats, see Section 3.13 Default Case Algorithms in
+# The Unicode Standard.
+#
+# ================================================================================
+# Format
+# ================================================================================
+# The entries in this file are in the following machine-readable format:
+#
+# <code>; <status>; <mapping>; # <name>
+#
+# The status field is:
+# C: common case folding, common mappings shared by both simple and full mappings.
+# F: full case folding, mappings that cause strings to grow in length. Multiple characters are separated by spaces.
+# S: simple case folding, mappings to single characters where different from F.
+# T: special case for uppercase I and dotted uppercase I
+# - For non-Turkic languages, this mapping is normally not used.
+# - For Turkic languages (tr, az), this mapping can be used instead of the normal mapping for these characters.
+# Note that the Turkic mappings do not maintain canonical equivalence without additional processing.
+# See the discussions of case mapping in the Unicode Standard for more information.
+#
+# Usage:
+# A. To do a simple case folding, use the mappings with status C + S.
+# B. To do a full case folding, use the mappings with status C + F.
+#
+# The mappings with status T can be used or omitted depending on the desired case-folding
+# behavior. (The default option is to exclude them.)
+#
+# =================================================================
+
+# Property: Case_Folding
+
+# All code points not explicitly listed for Case_Folding
+# have the value C for the status field, and the code point itself for the mapping field.
+
+# =================================================================
+0041; C; 0061; # LATIN CAPITAL LETTER A
+0042; C; 0062; # LATIN CAPITAL LETTER B
+0043; C; 0063; # LATIN CAPITAL LETTER C
+0044; C; 0064; # LATIN CAPITAL LETTER D
+0045; C; 0065; # LATIN CAPITAL LETTER E
+0046; C; 0066; # LATIN CAPITAL LETTER F
+0047; C; 0067; # LATIN CAPITAL LETTER G
+0048; C; 0068; # LATIN CAPITAL LETTER H
+0049; C; 0069; # LATIN CAPITAL LETTER I
+0049; T; 0131; # LATIN CAPITAL LETTER I
+004A; C; 006A; # LATIN CAPITAL LETTER J
+004B; C; 006B; # LATIN CAPITAL LETTER K
+004C; C; 006C; # LATIN CAPITAL LETTER L
+004D; C; 006D; # LATIN CAPITAL LETTER M
+004E; C; 006E; # LATIN CAPITAL LETTER N
+004F; C; 006F; # LATIN CAPITAL LETTER O
+0050; C; 0070; # LATIN CAPITAL LETTER P
+0051; C; 0071; # LATIN CAPITAL LETTER Q
+0052; C; 0072; # LATIN CAPITAL LETTER R
+0053; C; 0073; # LATIN CAPITAL LETTER S
+0054; C; 0074; # LATIN CAPITAL LETTER T
+0055; C; 0075; # LATIN CAPITAL LETTER U
+0056; C; 0076; # LATIN CAPITAL LETTER V
+0057; C; 0077; # LATIN CAPITAL LETTER W
+0058; C; 0078; # LATIN CAPITAL LETTER X
+0059; C; 0079; # LATIN CAPITAL LETTER Y
+005A; C; 007A; # LATIN CAPITAL LETTER Z
+00B5; C; 03BC; # MICRO SIGN
+00C0; C; 00E0; # LATIN CAPITAL LETTER A WITH GRAVE
+00C1; C; 00E1; # LATIN CAPITAL LETTER A WITH ACUTE
+00C2; C; 00E2; # LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+00C3; C; 00E3; # LATIN CAPITAL LETTER A WITH TILDE
+00C4; C; 00E4; # LATIN CAPITAL LETTER A WITH DIAERESIS
+00C5; C; 00E5; # LATIN CAPITAL LETTER A WITH RING ABOVE
+00C6; C; 00E6; # LATIN CAPITAL LETTER AE
+00C7; C; 00E7; # LATIN CAPITAL LETTER C WITH CEDILLA
+00C8; C; 00E8; # LATIN CAPITAL LETTER E WITH GRAVE
+00C9; C; 00E9; # LATIN CAPITAL LETTER E WITH ACUTE
+00CA; C; 00EA; # LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+00CB; C; 00EB; # LATIN CAPITAL LETTER E WITH DIAERESIS
+00CC; C; 00EC; # LATIN CAPITAL LETTER I WITH GRAVE
+00CD; C; 00ED; # LATIN CAPITAL LETTER I WITH ACUTE
+00CE; C; 00EE; # LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+00CF; C; 00EF; # LATIN CAPITAL LETTER I WITH DIAERESIS
+00D0; C; 00F0; # LATIN CAPITAL LETTER ETH
+00D1; C; 00F1; # LATIN CAPITAL LETTER N WITH TILDE
+00D2; C; 00F2; # LATIN CAPITAL LETTER O WITH GRAVE
+00D3; C; 00F3; # LATIN CAPITAL LETTER O WITH ACUTE
+00D4; C; 00F4; # LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+00D5; C; 00F5; # LATIN CAPITAL LETTER O WITH TILDE
+00D6; C; 00F6; # LATIN CAPITAL LETTER O WITH DIAERESIS
+00D8; C; 00F8; # LATIN CAPITAL LETTER O WITH STROKE
+00D9; C; 00F9; # LATIN CAPITAL LETTER U WITH GRAVE
+00DA; C; 00FA; # LATIN CAPITAL LETTER U WITH ACUTE
+00DB; C; 00FB; # LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+00DC; C; 00FC; # LATIN CAPITAL LETTER U WITH DIAERESIS
+00DD; C; 00FD; # LATIN CAPITAL LETTER Y WITH ACUTE
+00DE; C; 00FE; # LATIN CAPITAL LETTER THORN
+00DF; F; 0073 0073; # LATIN SMALL LETTER SHARP S
+0100; C; 0101; # LATIN CAPITAL LETTER A WITH MACRON
+0102; C; 0103; # LATIN CAPITAL LETTER A WITH BREVE
+0104; C; 0105; # LATIN CAPITAL LETTER A WITH OGONEK
+0106; C; 0107; # LATIN CAPITAL LETTER C WITH ACUTE
+0108; C; 0109; # LATIN CAPITAL LETTER C WITH CIRCUMFLEX
+010A; C; 010B; # LATIN CAPITAL LETTER C WITH DOT ABOVE
+010C; C; 010D; # LATIN CAPITAL LETTER C WITH CARON
+010E; C; 010F; # LATIN CAPITAL LETTER D WITH CARON
+0110; C; 0111; # LATIN CAPITAL LETTER D WITH STROKE
+0112; C; 0113; # LATIN CAPITAL LETTER E WITH MACRON
+0114; C; 0115; # LATIN CAPITAL LETTER E WITH BREVE
+0116; C; 0117; # LATIN CAPITAL LETTER E WITH DOT ABOVE
+0118; C; 0119; # LATIN CAPITAL LETTER E WITH OGONEK
+011A; C; 011B; # LATIN CAPITAL LETTER E WITH CARON
+011C; C; 011D; # LATIN CAPITAL LETTER G WITH CIRCUMFLEX
+011E; C; 011F; # LATIN CAPITAL LETTER G WITH BREVE
+0120; C; 0121; # LATIN CAPITAL LETTER G WITH DOT ABOVE
+0122; C; 0123; # LATIN CAPITAL LETTER G WITH CEDILLA
+0124; C; 0125; # LATIN CAPITAL LETTER H WITH CIRCUMFLEX
+0126; C; 0127; # LATIN CAPITAL LETTER H WITH STROKE
+0128; C; 0129; # LATIN CAPITAL LETTER I WITH TILDE
+012A; C; 012B; # LATIN CAPITAL LETTER I WITH MACRON
+012C; C; 012D; # LATIN CAPITAL LETTER I WITH BREVE
+012E; C; 012F; # LATIN CAPITAL LETTER I WITH OGONEK
+0130; F; 0069 0307; # LATIN CAPITAL LETTER I WITH DOT ABOVE
+0130; T; 0069; # LATIN CAPITAL LETTER I WITH DOT ABOVE
+0132; C; 0133; # LATIN CAPITAL LIGATURE IJ
+0134; C; 0135; # LATIN CAPITAL LETTER J WITH CIRCUMFLEX
+0136; C; 0137; # LATIN CAPITAL LETTER K WITH CEDILLA
+0139; C; 013A; # LATIN CAPITAL LETTER L WITH ACUTE
+013B; C; 013C; # LATIN CAPITAL LETTER L WITH CEDILLA
+013D; C; 013E; # LATIN CAPITAL LETTER L WITH CARON
+013F; C; 0140; # LATIN CAPITAL LETTER L WITH MIDDLE DOT
+0141; C; 0142; # LATIN CAPITAL LETTER L WITH STROKE
+0143; C; 0144; # LATIN CAPITAL LETTER N WITH ACUTE
+0145; C; 0146; # LATIN CAPITAL LETTER N WITH CEDILLA
+0147; C; 0148; # LATIN CAPITAL LETTER N WITH CARON
+0149; F; 02BC 006E; # LATIN SMALL LETTER N PRECEDED BY APOSTROPHE
+014A; C; 014B; # LATIN CAPITAL LETTER ENG
+014C; C; 014D; # LATIN CAPITAL LETTER O WITH MACRON
+014E; C; 014F; # LATIN CAPITAL LETTER O WITH BREVE
+0150; C; 0151; # LATIN CAPITAL LETTER O WITH DOUBLE ACUTE
+0152; C; 0153; # LATIN CAPITAL LIGATURE OE
+0154; C; 0155; # LATIN CAPITAL LETTER R WITH ACUTE
+0156; C; 0157; # LATIN CAPITAL LETTER R WITH CEDILLA
+0158; C; 0159; # LATIN CAPITAL LETTER R WITH CARON
+015A; C; 015B; # LATIN CAPITAL LETTER S WITH ACUTE
+015C; C; 015D; # LATIN CAPITAL LETTER S WITH CIRCUMFLEX
+015E; C; 015F; # LATIN CAPITAL LETTER S WITH CEDILLA
+0160; C; 0161; # LATIN CAPITAL LETTER S WITH CARON
+0162; C; 0163; # LATIN CAPITAL LETTER T WITH CEDILLA
+0164; C; 0165; # LATIN CAPITAL LETTER T WITH CARON
+0166; C; 0167; # LATIN CAPITAL LETTER T WITH STROKE
+0168; C; 0169; # LATIN CAPITAL LETTER U WITH TILDE
+016A; C; 016B; # LATIN CAPITAL LETTER U WITH MACRON
+016C; C; 016D; # LATIN CAPITAL LETTER U WITH BREVE
+016E; C; 016F; # LATIN CAPITAL LETTER U WITH RING ABOVE
+0170; C; 0171; # LATIN CAPITAL LETTER U WITH DOUBLE ACUTE
+0172; C; 0173; # LATIN CAPITAL LETTER U WITH OGONEK
+0174; C; 0175; # LATIN CAPITAL LETTER W WITH CIRCUMFLEX
+0176; C; 0177; # LATIN CAPITAL LETTER Y WITH CIRCUMFLEX
+0178; C; 00FF; # LATIN CAPITAL LETTER Y WITH DIAERESIS
+0179; C; 017A; # LATIN CAPITAL LETTER Z WITH ACUTE
+017B; C; 017C; # LATIN CAPITAL LETTER Z WITH DOT ABOVE
+017D; C; 017E; # LATIN CAPITAL LETTER Z WITH CARON
+017F; C; 0073; # LATIN SMALL LETTER LONG S
+0181; C; 0253; # LATIN CAPITAL LETTER B WITH HOOK
+0182; C; 0183; # LATIN CAPITAL LETTER B WITH TOPBAR
+0184; C; 0185; # LATIN CAPITAL LETTER TONE SIX
+0186; C; 0254; # LATIN CAPITAL LETTER OPEN O
+0187; C; 0188; # LATIN CAPITAL LETTER C WITH HOOK
+0189; C; 0256; # LATIN CAPITAL LETTER AFRICAN D
+018A; C; 0257; # LATIN CAPITAL LETTER D WITH HOOK
+018B; C; 018C; # LATIN CAPITAL LETTER D WITH TOPBAR
+018E; C; 01DD; # LATIN CAPITAL LETTER REVERSED E
+018F; C; 0259; # LATIN CAPITAL LETTER SCHWA
+0190; C; 025B; # LATIN CAPITAL LETTER OPEN E
+0191; C; 0192; # LATIN CAPITAL LETTER F WITH HOOK
+0193; C; 0260; # LATIN CAPITAL LETTER G WITH HOOK
+0194; C; 0263; # LATIN CAPITAL LETTER GAMMA
+0196; C; 0269; # LATIN CAPITAL LETTER IOTA
+0197; C; 0268; # LATIN CAPITAL LETTER I WITH STROKE
+0198; C; 0199; # LATIN CAPITAL LETTER K WITH HOOK
+019C; C; 026F; # LATIN CAPITAL LETTER TURNED M
+019D; C; 0272; # LATIN CAPITAL LETTER N WITH LEFT HOOK
+019F; C; 0275; # LATIN CAPITAL LETTER O WITH MIDDLE TILDE
+01A0; C; 01A1; # LATIN CAPITAL LETTER O WITH HORN
+01A2; C; 01A3; # LATIN CAPITAL LETTER OI
+01A4; C; 01A5; # LATIN CAPITAL LETTER P WITH HOOK
+01A6; C; 0280; # LATIN LETTER YR
+01A7; C; 01A8; # LATIN CAPITAL LETTER TONE TWO
+01A9; C; 0283; # LATIN CAPITAL LETTER ESH
+01AC; C; 01AD; # LATIN CAPITAL LETTER T WITH HOOK
+01AE; C; 0288; # LATIN CAPITAL LETTER T WITH RETROFLEX HOOK
+01AF; C; 01B0; # LATIN CAPITAL LETTER U WITH HORN
+01B1; C; 028A; # LATIN CAPITAL LETTER UPSILON
+01B2; C; 028B; # LATIN CAPITAL LETTER V WITH HOOK
+01B3; C; 01B4; # LATIN CAPITAL LETTER Y WITH HOOK
+01B5; C; 01B6; # LATIN CAPITAL LETTER Z WITH STROKE
+01B7; C; 0292; # LATIN CAPITAL LETTER EZH
+01B8; C; 01B9; # LATIN CAPITAL LETTER EZH REVERSED
+01BC; C; 01BD; # LATIN CAPITAL LETTER TONE FIVE
+01C4; C; 01C6; # LATIN CAPITAL LETTER DZ WITH CARON
+01C5; C; 01C6; # LATIN CAPITAL LETTER D WITH SMALL LETTER Z WITH CARON
+01C7; C; 01C9; # LATIN CAPITAL LETTER LJ
+01C8; C; 01C9; # LATIN CAPITAL LETTER L WITH SMALL LETTER J
+01CA; C; 01CC; # LATIN CAPITAL LETTER NJ
+01CB; C; 01CC; # LATIN CAPITAL LETTER N WITH SMALL LETTER J
+01CD; C; 01CE; # LATIN CAPITAL LETTER A WITH CARON
+01CF; C; 01D0; # LATIN CAPITAL LETTER I WITH CARON
+01D1; C; 01D2; # LATIN CAPITAL LETTER O WITH CARON
+01D3; C; 01D4; # LATIN CAPITAL LETTER U WITH CARON
+01D5; C; 01D6; # LATIN CAPITAL LETTER U WITH DIAERESIS AND MACRON
+01D7; C; 01D8; # LATIN CAPITAL LETTER U WITH DIAERESIS AND ACUTE
+01D9; C; 01DA; # LATIN CAPITAL LETTER U WITH DIAERESIS AND CARON
+01DB; C; 01DC; # LATIN CAPITAL LETTER U WITH DIAERESIS AND GRAVE
+01DE; C; 01DF; # LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON
+01E0; C; 01E1; # LATIN CAPITAL LETTER A WITH DOT ABOVE AND MACRON
+01E2; C; 01E3; # LATIN CAPITAL LETTER AE WITH MACRON
+01E4; C; 01E5; # LATIN CAPITAL LETTER G WITH STROKE
+01E6; C; 01E7; # LATIN CAPITAL LETTER G WITH CARON
+01E8; C; 01E9; # LATIN CAPITAL LETTER K WITH CARON
+01EA; C; 01EB; # LATIN CAPITAL LETTER O WITH OGONEK
+01EC; C; 01ED; # LATIN CAPITAL LETTER O WITH OGONEK AND MACRON
+01EE; C; 01EF; # LATIN CAPITAL LETTER EZH WITH CARON
+01F0; F; 006A 030C; # LATIN SMALL LETTER J WITH CARON
+01F1; C; 01F3; # LATIN CAPITAL LETTER DZ
+01F2; C; 01F3; # LATIN CAPITAL LETTER D WITH SMALL LETTER Z
+01F4; C; 01F5; # LATIN CAPITAL LETTER G WITH ACUTE
+01F6; C; 0195; # LATIN CAPITAL LETTER HWAIR
+01F7; C; 01BF; # LATIN CAPITAL LETTER WYNN
+01F8; C; 01F9; # LATIN CAPITAL LETTER N WITH GRAVE
+01FA; C; 01FB; # LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE
+01FC; C; 01FD; # LATIN CAPITAL LETTER AE WITH ACUTE
+01FE; C; 01FF; # LATIN CAPITAL LETTER O WITH STROKE AND ACUTE
+0200; C; 0201; # LATIN CAPITAL LETTER A WITH DOUBLE GRAVE
+0202; C; 0203; # LATIN CAPITAL LETTER A WITH INVERTED BREVE
+0204; C; 0205; # LATIN CAPITAL LETTER E WITH DOUBLE GRAVE
+0206; C; 0207; # LATIN CAPITAL LETTER E WITH INVERTED BREVE
+0208; C; 0209; # LATIN CAPITAL LETTER I WITH DOUBLE GRAVE
+020A; C; 020B; # LATIN CAPITAL LETTER I WITH INVERTED BREVE
+020C; C; 020D; # LATIN CAPITAL LETTER O WITH DOUBLE GRAVE
+020E; C; 020F; # LATIN CAPITAL LETTER O WITH INVERTED BREVE
+0210; C; 0211; # LATIN CAPITAL LETTER R WITH DOUBLE GRAVE
+0212; C; 0213; # LATIN CAPITAL LETTER R WITH INVERTED BREVE
+0214; C; 0215; # LATIN CAPITAL LETTER U WITH DOUBLE GRAVE
+0216; C; 0217; # LATIN CAPITAL LETTER U WITH INVERTED BREVE
+0218; C; 0219; # LATIN CAPITAL LETTER S WITH COMMA BELOW
+021A; C; 021B; # LATIN CAPITAL LETTER T WITH COMMA BELOW
+021C; C; 021D; # LATIN CAPITAL LETTER YOGH
+021E; C; 021F; # LATIN CAPITAL LETTER H WITH CARON
+0220; C; 019E; # LATIN CAPITAL LETTER N WITH LONG RIGHT LEG
+0222; C; 0223; # LATIN CAPITAL LETTER OU
+0224; C; 0225; # LATIN CAPITAL LETTER Z WITH HOOK
+0226; C; 0227; # LATIN CAPITAL LETTER A WITH DOT ABOVE
+0228; C; 0229; # LATIN CAPITAL LETTER E WITH CEDILLA
+022A; C; 022B; # LATIN CAPITAL LETTER O WITH DIAERESIS AND MACRON
+022C; C; 022D; # LATIN CAPITAL LETTER O WITH TILDE AND MACRON
+022E; C; 022F; # LATIN CAPITAL LETTER O WITH DOT ABOVE
+0230; C; 0231; # LATIN CAPITAL LETTER O WITH DOT ABOVE AND MACRON
+0232; C; 0233; # LATIN CAPITAL LETTER Y WITH MACRON
+023A; C; 2C65; # LATIN CAPITAL LETTER A WITH STROKE
+023B; C; 023C; # LATIN CAPITAL LETTER C WITH STROKE
+023D; C; 019A; # LATIN CAPITAL LETTER L WITH BAR
+023E; C; 2C66; # LATIN CAPITAL LETTER T WITH DIAGONAL STROKE
+0241; C; 0242; # LATIN CAPITAL LETTER GLOTTAL STOP
+0243; C; 0180; # LATIN CAPITAL LETTER B WITH STROKE
+0244; C; 0289; # LATIN CAPITAL LETTER U BAR
+0245; C; 028C; # LATIN CAPITAL LETTER TURNED V
+0246; C; 0247; # LATIN CAPITAL LETTER E WITH STROKE
+0248; C; 0249; # LATIN CAPITAL LETTER J WITH STROKE
+024A; C; 024B; # LATIN CAPITAL LETTER SMALL Q WITH HOOK TAIL
+024C; C; 024D; # LATIN CAPITAL LETTER R WITH STROKE
+024E; C; 024F; # LATIN CAPITAL LETTER Y WITH STROKE
+0345; C; 03B9; # COMBINING GREEK YPOGEGRAMMENI
+0370; C; 0371; # GREEK CAPITAL LETTER HETA
+0372; C; 0373; # GREEK CAPITAL LETTER ARCHAIC SAMPI
+0376; C; 0377; # GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA
+037F; C; 03F3; # GREEK CAPITAL LETTER YOT
+0386; C; 03AC; # GREEK CAPITAL LETTER ALPHA WITH TONOS
+0388; C; 03AD; # GREEK CAPITAL LETTER EPSILON WITH TONOS
+0389; C; 03AE; # GREEK CAPITAL LETTER ETA WITH TONOS
+038A; C; 03AF; # GREEK CAPITAL LETTER IOTA WITH TONOS
+038C; C; 03CC; # GREEK CAPITAL LETTER OMICRON WITH TONOS
+038E; C; 03CD; # GREEK CAPITAL LETTER UPSILON WITH TONOS
+038F; C; 03CE; # GREEK CAPITAL LETTER OMEGA WITH TONOS
+0390; F; 03B9 0308 0301; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
+0391; C; 03B1; # GREEK CAPITAL LETTER ALPHA
+0392; C; 03B2; # GREEK CAPITAL LETTER BETA
+0393; C; 03B3; # GREEK CAPITAL LETTER GAMMA
+0394; C; 03B4; # GREEK CAPITAL LETTER DELTA
+0395; C; 03B5; # GREEK CAPITAL LETTER EPSILON
+0396; C; 03B6; # GREEK CAPITAL LETTER ZETA
+0397; C; 03B7; # GREEK CAPITAL LETTER ETA
+0398; C; 03B8; # GREEK CAPITAL LETTER THETA
+0399; C; 03B9; # GREEK CAPITAL LETTER IOTA
+039A; C; 03BA; # GREEK CAPITAL LETTER KAPPA
+039B; C; 03BB; # GREEK CAPITAL LETTER LAMDA
+039C; C; 03BC; # GREEK CAPITAL LETTER MU
+039D; C; 03BD; # GREEK CAPITAL LETTER NU
+039E; C; 03BE; # GREEK CAPITAL LETTER XI
+039F; C; 03BF; # GREEK CAPITAL LETTER OMICRON
+03A0; C; 03C0; # GREEK CAPITAL LETTER PI
+03A1; C; 03C1; # GREEK CAPITAL LETTER RHO
+03A3; C; 03C3; # GREEK CAPITAL LETTER SIGMA
+03A4; C; 03C4; # GREEK CAPITAL LETTER TAU
+03A5; C; 03C5; # GREEK CAPITAL LETTER UPSILON
+03A6; C; 03C6; # GREEK CAPITAL LETTER PHI
+03A7; C; 03C7; # GREEK CAPITAL LETTER CHI
+03A8; C; 03C8; # GREEK CAPITAL LETTER PSI
+03A9; C; 03C9; # GREEK CAPITAL LETTER OMEGA
+03AA; C; 03CA; # GREEK CAPITAL LETTER IOTA WITH DIALYTIKA
+03AB; C; 03CB; # GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA
+03B0; F; 03C5 0308 0301; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS
+03C2; C; 03C3; # GREEK SMALL LETTER FINAL SIGMA
+03CF; C; 03D7; # GREEK CAPITAL KAI SYMBOL
+03D0; C; 03B2; # GREEK BETA SYMBOL
+03D1; C; 03B8; # GREEK THETA SYMBOL
+03D5; C; 03C6; # GREEK PHI SYMBOL
+03D6; C; 03C0; # GREEK PI SYMBOL
+03D8; C; 03D9; # GREEK LETTER ARCHAIC KOPPA
+03DA; C; 03DB; # GREEK LETTER STIGMA
+03DC; C; 03DD; # GREEK LETTER DIGAMMA
+03DE; C; 03DF; # GREEK LETTER KOPPA
+03E0; C; 03E1; # GREEK LETTER SAMPI
+03E2; C; 03E3; # COPTIC CAPITAL LETTER SHEI
+03E4; C; 03E5; # COPTIC CAPITAL LETTER FEI
+03E6; C; 03E7; # COPTIC CAPITAL LETTER KHEI
+03E8; C; 03E9; # COPTIC CAPITAL LETTER HORI
+03EA; C; 03EB; # COPTIC CAPITAL LETTER GANGIA
+03EC; C; 03ED; # COPTIC CAPITAL LETTER SHIMA
+03EE; C; 03EF; # COPTIC CAPITAL LETTER DEI
+03F0; C; 03BA; # GREEK KAPPA SYMBOL
+03F1; C; 03C1; # GREEK RHO SYMBOL
+03F4; C; 03B8; # GREEK CAPITAL THETA SYMBOL
+03F5; C; 03B5; # GREEK LUNATE EPSILON SYMBOL
+03F7; C; 03F8; # GREEK CAPITAL LETTER SHO
+03F9; C; 03F2; # GREEK CAPITAL LUNATE SIGMA SYMBOL
+03FA; C; 03FB; # GREEK CAPITAL LETTER SAN
+03FD; C; 037B; # GREEK CAPITAL REVERSED LUNATE SIGMA SYMBOL
+03FE; C; 037C; # GREEK CAPITAL DOTTED LUNATE SIGMA SYMBOL
+03FF; C; 037D; # GREEK CAPITAL REVERSED DOTTED LUNATE SIGMA SYMBOL
+0400; C; 0450; # CYRILLIC CAPITAL LETTER IE WITH GRAVE
+0401; C; 0451; # CYRILLIC CAPITAL LETTER IO
+0402; C; 0452; # CYRILLIC CAPITAL LETTER DJE
+0403; C; 0453; # CYRILLIC CAPITAL LETTER GJE
+0404; C; 0454; # CYRILLIC CAPITAL LETTER UKRAINIAN IE
+0405; C; 0455; # CYRILLIC CAPITAL LETTER DZE
+0406; C; 0456; # CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I
+0407; C; 0457; # CYRILLIC CAPITAL LETTER YI
+0408; C; 0458; # CYRILLIC CAPITAL LETTER JE
+0409; C; 0459; # CYRILLIC CAPITAL LETTER LJE
+040A; C; 045A; # CYRILLIC CAPITAL LETTER NJE
+040B; C; 045B; # CYRILLIC CAPITAL LETTER TSHE
+040C; C; 045C; # CYRILLIC CAPITAL LETTER KJE
+040D; C; 045D; # CYRILLIC CAPITAL LETTER I WITH GRAVE
+040E; C; 045E; # CYRILLIC CAPITAL LETTER SHORT U
+040F; C; 045F; # CYRILLIC CAPITAL LETTER DZHE
+0410; C; 0430; # CYRILLIC CAPITAL LETTER A
+0411; C; 0431; # CYRILLIC CAPITAL LETTER BE
+0412; C; 0432; # CYRILLIC CAPITAL LETTER VE
+0413; C; 0433; # CYRILLIC CAPITAL LETTER GHE
+0414; C; 0434; # CYRILLIC CAPITAL LETTER DE
+0415; C; 0435; # CYRILLIC CAPITAL LETTER IE
+0416; C; 0436; # CYRILLIC CAPITAL LETTER ZHE
+0417; C; 0437; # CYRILLIC CAPITAL LETTER ZE
+0418; C; 0438; # CYRILLIC CAPITAL LETTER I
+0419; C; 0439; # CYRILLIC CAPITAL LETTER SHORT I
+041A; C; 043A; # CYRILLIC CAPITAL LETTER KA
+041B; C; 043B; # CYRILLIC CAPITAL LETTER EL
+041C; C; 043C; # CYRILLIC CAPITAL LETTER EM
+041D; C; 043D; # CYRILLIC CAPITAL LETTER EN
+041E; C; 043E; # CYRILLIC CAPITAL LETTER O
+041F; C; 043F; # CYRILLIC CAPITAL LETTER PE
+0420; C; 0440; # CYRILLIC CAPITAL LETTER ER
+0421; C; 0441; # CYRILLIC CAPITAL LETTER ES
+0422; C; 0442; # CYRILLIC CAPITAL LETTER TE
+0423; C; 0443; # CYRILLIC CAPITAL LETTER U
+0424; C; 0444; # CYRILLIC CAPITAL LETTER EF
+0425; C; 0445; # CYRILLIC CAPITAL LETTER HA
+0426; C; 0446; # CYRILLIC CAPITAL LETTER TSE
+0427; C; 0447; # CYRILLIC CAPITAL LETTER CHE
+0428; C; 0448; # CYRILLIC CAPITAL LETTER SHA
+0429; C; 0449; # CYRILLIC CAPITAL LETTER SHCHA
+042A; C; 044A; # CYRILLIC CAPITAL LETTER HARD SIGN
+042B; C; 044B; # CYRILLIC CAPITAL LETTER YERU
+042C; C; 044C; # CYRILLIC CAPITAL LETTER SOFT SIGN
+042D; C; 044D; # CYRILLIC CAPITAL LETTER E
+042E; C; 044E; # CYRILLIC CAPITAL LETTER YU
+042F; C; 044F; # CYRILLIC CAPITAL LETTER YA
+0460; C; 0461; # CYRILLIC CAPITAL LETTER OMEGA
+0462; C; 0463; # CYRILLIC CAPITAL LETTER YAT
+0464; C; 0465; # CYRILLIC CAPITAL LETTER IOTIFIED E
+0466; C; 0467; # CYRILLIC CAPITAL LETTER LITTLE YUS
+0468; C; 0469; # CYRILLIC CAPITAL LETTER IOTIFIED LITTLE YUS
+046A; C; 046B; # CYRILLIC CAPITAL LETTER BIG YUS
+046C; C; 046D; # CYRILLIC CAPITAL LETTER IOTIFIED BIG YUS
+046E; C; 046F; # CYRILLIC CAPITAL LETTER KSI
+0470; C; 0471; # CYRILLIC CAPITAL LETTER PSI
+0472; C; 0473; # CYRILLIC CAPITAL LETTER FITA
+0474; C; 0475; # CYRILLIC CAPITAL LETTER IZHITSA
+0476; C; 0477; # CYRILLIC CAPITAL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT
+0478; C; 0479; # CYRILLIC CAPITAL LETTER UK
+047A; C; 047B; # CYRILLIC CAPITAL LETTER ROUND OMEGA
+047C; C; 047D; # CYRILLIC CAPITAL LETTER OMEGA WITH TITLO
+047E; C; 047F; # CYRILLIC CAPITAL LETTER OT
+0480; C; 0481; # CYRILLIC CAPITAL LETTER KOPPA
+048A; C; 048B; # CYRILLIC CAPITAL LETTER SHORT I WITH TAIL
+048C; C; 048D; # CYRILLIC CAPITAL LETTER SEMISOFT SIGN
+048E; C; 048F; # CYRILLIC CAPITAL LETTER ER WITH TICK
+0490; C; 0491; # CYRILLIC CAPITAL LETTER GHE WITH UPTURN
+0492; C; 0493; # CYRILLIC CAPITAL LETTER GHE WITH STROKE
+0494; C; 0495; # CYRILLIC CAPITAL LETTER GHE WITH MIDDLE HOOK
+0496; C; 0497; # CYRILLIC CAPITAL LETTER ZHE WITH DESCENDER
+0498; C; 0499; # CYRILLIC CAPITAL LETTER ZE WITH DESCENDER
+049A; C; 049B; # CYRILLIC CAPITAL LETTER KA WITH DESCENDER
+049C; C; 049D; # CYRILLIC CAPITAL LETTER KA WITH VERTICAL STROKE
+049E; C; 049F; # CYRILLIC CAPITAL LETTER KA WITH STROKE
+04A0; C; 04A1; # CYRILLIC CAPITAL LETTER BASHKIR KA
+04A2; C; 04A3; # CYRILLIC CAPITAL LETTER EN WITH DESCENDER
+04A4; C; 04A5; # CYRILLIC CAPITAL LIGATURE EN GHE
+04A6; C; 04A7; # CYRILLIC CAPITAL LETTER PE WITH MIDDLE HOOK
+04A8; C; 04A9; # CYRILLIC CAPITAL LETTER ABKHASIAN HA
+04AA; C; 04AB; # CYRILLIC CAPITAL LETTER ES WITH DESCENDER
+04AC; C; 04AD; # CYRILLIC CAPITAL LETTER TE WITH DESCENDER
+04AE; C; 04AF; # CYRILLIC CAPITAL LETTER STRAIGHT U
+04B0; C; 04B1; # CYRILLIC CAPITAL LETTER STRAIGHT U WITH STROKE
+04B2; C; 04B3; # CYRILLIC CAPITAL LETTER HA WITH DESCENDER
+04B4; C; 04B5; # CYRILLIC CAPITAL LIGATURE TE TSE
+04B6; C; 04B7; # CYRILLIC CAPITAL LETTER CHE WITH DESCENDER
+04B8; C; 04B9; # CYRILLIC CAPITAL LETTER CHE WITH VERTICAL STROKE
+04BA; C; 04BB; # CYRILLIC CAPITAL LETTER SHHA
+04BC; C; 04BD; # CYRILLIC CAPITAL LETTER ABKHASIAN CHE
+04BE; C; 04BF; # CYRILLIC CAPITAL LETTER ABKHASIAN CHE WITH DESCENDER
+04C0; C; 04CF; # CYRILLIC LETTER PALOCHKA
+04C1; C; 04C2; # CYRILLIC CAPITAL LETTER ZHE WITH BREVE
+04C3; C; 04C4; # CYRILLIC CAPITAL LETTER KA WITH HOOK
+04C5; C; 04C6; # CYRILLIC CAPITAL LETTER EL WITH TAIL
+04C7; C; 04C8; # CYRILLIC CAPITAL LETTER EN WITH HOOK
+04C9; C; 04CA; # CYRILLIC CAPITAL LETTER EN WITH TAIL
+04CB; C; 04CC; # CYRILLIC CAPITAL LETTER KHAKASSIAN CHE
+04CD; C; 04CE; # CYRILLIC CAPITAL LETTER EM WITH TAIL
+04D0; C; 04D1; # CYRILLIC CAPITAL LETTER A WITH BREVE
+04D2; C; 04D3; # CYRILLIC CAPITAL LETTER A WITH DIAERESIS
+04D4; C; 04D5; # CYRILLIC CAPITAL LIGATURE A IE
+04D6; C; 04D7; # CYRILLIC CAPITAL LETTER IE WITH BREVE
+04D8; C; 04D9; # CYRILLIC CAPITAL LETTER SCHWA
+04DA; C; 04DB; # CYRILLIC CAPITAL LETTER SCHWA WITH DIAERESIS
+04DC; C; 04DD; # CYRILLIC CAPITAL LETTER ZHE WITH DIAERESIS
+04DE; C; 04DF; # CYRILLIC CAPITAL LETTER ZE WITH DIAERESIS
+04E0; C; 04E1; # CYRILLIC CAPITAL LETTER ABKHASIAN DZE
+04E2; C; 04E3; # CYRILLIC CAPITAL LETTER I WITH MACRON
+04E4; C; 04E5; # CYRILLIC CAPITAL LETTER I WITH DIAERESIS
+04E6; C; 04E7; # CYRILLIC CAPITAL LETTER O WITH DIAERESIS
+04E8; C; 04E9; # CYRILLIC CAPITAL LETTER BARRED O
+04EA; C; 04EB; # CYRILLIC CAPITAL LETTER BARRED O WITH DIAERESIS
+04EC; C; 04ED; # CYRILLIC CAPITAL LETTER E WITH DIAERESIS
+04EE; C; 04EF; # CYRILLIC CAPITAL LETTER U WITH MACRON
+04F0; C; 04F1; # CYRILLIC CAPITAL LETTER U WITH DIAERESIS
+04F2; C; 04F3; # CYRILLIC CAPITAL LETTER U WITH DOUBLE ACUTE
+04F4; C; 04F5; # CYRILLIC CAPITAL LETTER CHE WITH DIAERESIS
+04F6; C; 04F7; # CYRILLIC CAPITAL LETTER GHE WITH DESCENDER
+04F8; C; 04F9; # CYRILLIC CAPITAL LETTER YERU WITH DIAERESIS
+04FA; C; 04FB; # CYRILLIC CAPITAL LETTER GHE WITH STROKE AND HOOK
+04FC; C; 04FD; # CYRILLIC CAPITAL LETTER HA WITH HOOK
+04FE; C; 04FF; # CYRILLIC CAPITAL LETTER HA WITH STROKE
+0500; C; 0501; # CYRILLIC CAPITAL LETTER KOMI DE
+0502; C; 0503; # CYRILLIC CAPITAL LETTER KOMI DJE
+0504; C; 0505; # CYRILLIC CAPITAL LETTER KOMI ZJE
+0506; C; 0507; # CYRILLIC CAPITAL LETTER KOMI DZJE
+0508; C; 0509; # CYRILLIC CAPITAL LETTER KOMI LJE
+050A; C; 050B; # CYRILLIC CAPITAL LETTER KOMI NJE
+050C; C; 050D; # CYRILLIC CAPITAL LETTER KOMI SJE
+050E; C; 050F; # CYRILLIC CAPITAL LETTER KOMI TJE
+0510; C; 0511; # CYRILLIC CAPITAL LETTER REVERSED ZE
+0512; C; 0513; # CYRILLIC CAPITAL LETTER EL WITH HOOK
+0514; C; 0515; # CYRILLIC CAPITAL LETTER LHA
+0516; C; 0517; # CYRILLIC CAPITAL LETTER RHA
+0518; C; 0519; # CYRILLIC CAPITAL LETTER YAE
+051A; C; 051B; # CYRILLIC CAPITAL LETTER QA
+051C; C; 051D; # CYRILLIC CAPITAL LETTER WE
+051E; C; 051F; # CYRILLIC CAPITAL LETTER ALEUT KA
+0520; C; 0521; # CYRILLIC CAPITAL LETTER EL WITH MIDDLE HOOK
+0522; C; 0523; # CYRILLIC CAPITAL LETTER EN WITH MIDDLE HOOK
+0524; C; 0525; # CYRILLIC CAPITAL LETTER PE WITH DESCENDER
+0526; C; 0527; # CYRILLIC CAPITAL LETTER SHHA WITH DESCENDER
+0528; C; 0529; # CYRILLIC CAPITAL LETTER EN WITH LEFT HOOK
+052A; C; 052B; # CYRILLIC CAPITAL LETTER DZZHE
+052C; C; 052D; # CYRILLIC CAPITAL LETTER DCHE
+052E; C; 052F; # CYRILLIC CAPITAL LETTER EL WITH DESCENDER
+0531; C; 0561; # ARMENIAN CAPITAL LETTER AYB
+0532; C; 0562; # ARMENIAN CAPITAL LETTER BEN
+0533; C; 0563; # ARMENIAN CAPITAL LETTER GIM
+0534; C; 0564; # ARMENIAN CAPITAL LETTER DA
+0535; C; 0565; # ARMENIAN CAPITAL LETTER ECH
+0536; C; 0566; # ARMENIAN CAPITAL LETTER ZA
+0537; C; 0567; # ARMENIAN CAPITAL LETTER EH
+0538; C; 0568; # ARMENIAN CAPITAL LETTER ET
+0539; C; 0569; # ARMENIAN CAPITAL LETTER TO
+053A; C; 056A; # ARMENIAN CAPITAL LETTER ZHE
+053B; C; 056B; # ARMENIAN CAPITAL LETTER INI
+053C; C; 056C; # ARMENIAN CAPITAL LETTER LIWN
+053D; C; 056D; # ARMENIAN CAPITAL LETTER XEH
+053E; C; 056E; # ARMENIAN CAPITAL LETTER CA
+053F; C; 056F; # ARMENIAN CAPITAL LETTER KEN
+0540; C; 0570; # ARMENIAN CAPITAL LETTER HO
+0541; C; 0571; # ARMENIAN CAPITAL LETTER JA
+0542; C; 0572; # ARMENIAN CAPITAL LETTER GHAD
+0543; C; 0573; # ARMENIAN CAPITAL LETTER CHEH
+0544; C; 0574; # ARMENIAN CAPITAL LETTER MEN
+0545; C; 0575; # ARMENIAN CAPITAL LETTER YI
+0546; C; 0576; # ARMENIAN CAPITAL LETTER NOW
+0547; C; 0577; # ARMENIAN CAPITAL LETTER SHA
+0548; C; 0578; # ARMENIAN CAPITAL LETTER VO
+0549; C; 0579; # ARMENIAN CAPITAL LETTER CHA
+054A; C; 057A; # ARMENIAN CAPITAL LETTER PEH
+054B; C; 057B; # ARMENIAN CAPITAL LETTER JHEH
+054C; C; 057C; # ARMENIAN CAPITAL LETTER RA
+054D; C; 057D; # ARMENIAN CAPITAL LETTER SEH
+054E; C; 057E; # ARMENIAN CAPITAL LETTER VEW
+054F; C; 057F; # ARMENIAN CAPITAL LETTER TIWN
+0550; C; 0580; # ARMENIAN CAPITAL LETTER REH
+0551; C; 0581; # ARMENIAN CAPITAL LETTER CO
+0552; C; 0582; # ARMENIAN CAPITAL LETTER YIWN
+0553; C; 0583; # ARMENIAN CAPITAL LETTER PIWR
+0554; C; 0584; # ARMENIAN CAPITAL LETTER KEH
+0555; C; 0585; # ARMENIAN CAPITAL LETTER OH
+0556; C; 0586; # ARMENIAN CAPITAL LETTER FEH
+0587; F; 0565 0582; # ARMENIAN SMALL LIGATURE ECH YIWN
+10A0; C; 2D00; # GEORGIAN CAPITAL LETTER AN
+10A1; C; 2D01; # GEORGIAN CAPITAL LETTER BAN
+10A2; C; 2D02; # GEORGIAN CAPITAL LETTER GAN
+10A3; C; 2D03; # GEORGIAN CAPITAL LETTER DON
+10A4; C; 2D04; # GEORGIAN CAPITAL LETTER EN
+10A5; C; 2D05; # GEORGIAN CAPITAL LETTER VIN
+10A6; C; 2D06; # GEORGIAN CAPITAL LETTER ZEN
+10A7; C; 2D07; # GEORGIAN CAPITAL LETTER TAN
+10A8; C; 2D08; # GEORGIAN CAPITAL LETTER IN
+10A9; C; 2D09; # GEORGIAN CAPITAL LETTER KAN
+10AA; C; 2D0A; # GEORGIAN CAPITAL LETTER LAS
+10AB; C; 2D0B; # GEORGIAN CAPITAL LETTER MAN
+10AC; C; 2D0C; # GEORGIAN CAPITAL LETTER NAR
+10AD; C; 2D0D; # GEORGIAN CAPITAL LETTER ON
+10AE; C; 2D0E; # GEORGIAN CAPITAL LETTER PAR
+10AF; C; 2D0F; # GEORGIAN CAPITAL LETTER ZHAR
+10B0; C; 2D10; # GEORGIAN CAPITAL LETTER RAE
+10B1; C; 2D11; # GEORGIAN CAPITAL LETTER SAN
+10B2; C; 2D12; # GEORGIAN CAPITAL LETTER TAR
+10B3; C; 2D13; # GEORGIAN CAPITAL LETTER UN
+10B4; C; 2D14; # GEORGIAN CAPITAL LETTER PHAR
+10B5; C; 2D15; # GEORGIAN CAPITAL LETTER KHAR
+10B6; C; 2D16; # GEORGIAN CAPITAL LETTER GHAN
+10B7; C; 2D17; # GEORGIAN CAPITAL LETTER QAR
+10B8; C; 2D18; # GEORGIAN CAPITAL LETTER SHIN
+10B9; C; 2D19; # GEORGIAN CAPITAL LETTER CHIN
+10BA; C; 2D1A; # GEORGIAN CAPITAL LETTER CAN
+10BB; C; 2D1B; # GEORGIAN CAPITAL LETTER JIL
+10BC; C; 2D1C; # GEORGIAN CAPITAL LETTER CIL
+10BD; C; 2D1D; # GEORGIAN CAPITAL LETTER CHAR
+10BE; C; 2D1E; # GEORGIAN CAPITAL LETTER XAN
+10BF; C; 2D1F; # GEORGIAN CAPITAL LETTER JHAN
+10C0; C; 2D20; # GEORGIAN CAPITAL LETTER HAE
+10C1; C; 2D21; # GEORGIAN CAPITAL LETTER HE
+10C2; C; 2D22; # GEORGIAN CAPITAL LETTER HIE
+10C3; C; 2D23; # GEORGIAN CAPITAL LETTER WE
+10C4; C; 2D24; # GEORGIAN CAPITAL LETTER HAR
+10C5; C; 2D25; # GEORGIAN CAPITAL LETTER HOE
+10C7; C; 2D27; # GEORGIAN CAPITAL LETTER YN
+10CD; C; 2D2D; # GEORGIAN CAPITAL LETTER AEN
+13F8; C; 13F0; # CHEROKEE SMALL LETTER YE
+13F9; C; 13F1; # CHEROKEE SMALL LETTER YI
+13FA; C; 13F2; # CHEROKEE SMALL LETTER YO
+13FB; C; 13F3; # CHEROKEE SMALL LETTER YU
+13FC; C; 13F4; # CHEROKEE SMALL LETTER YV
+13FD; C; 13F5; # CHEROKEE SMALL LETTER MV
+1C80; C; 0432; # CYRILLIC SMALL LETTER ROUNDED VE
+1C81; C; 0434; # CYRILLIC SMALL LETTER LONG-LEGGED DE
+1C82; C; 043E; # CYRILLIC SMALL LETTER NARROW O
+1C83; C; 0441; # CYRILLIC SMALL LETTER WIDE ES
+1C84; C; 0442; # CYRILLIC SMALL LETTER TALL TE
+1C85; C; 0442; # CYRILLIC SMALL LETTER THREE-LEGGED TE
+1C86; C; 044A; # CYRILLIC SMALL LETTER TALL HARD SIGN
+1C87; C; 0463; # CYRILLIC SMALL LETTER TALL YAT
+1C88; C; A64B; # CYRILLIC SMALL LETTER UNBLENDED UK
+1C90; C; 10D0; # GEORGIAN MTAVRULI CAPITAL LETTER AN
+1C91; C; 10D1; # GEORGIAN MTAVRULI CAPITAL LETTER BAN
+1C92; C; 10D2; # GEORGIAN MTAVRULI CAPITAL LETTER GAN
+1C93; C; 10D3; # GEORGIAN MTAVRULI CAPITAL LETTER DON
+1C94; C; 10D4; # GEORGIAN MTAVRULI CAPITAL LETTER EN
+1C95; C; 10D5; # GEORGIAN MTAVRULI CAPITAL LETTER VIN
+1C96; C; 10D6; # GEORGIAN MTAVRULI CAPITAL LETTER ZEN
+1C97; C; 10D7; # GEORGIAN MTAVRULI CAPITAL LETTER TAN
+1C98; C; 10D8; # GEORGIAN MTAVRULI CAPITAL LETTER IN
+1C99; C; 10D9; # GEORGIAN MTAVRULI CAPITAL LETTER KAN
+1C9A; C; 10DA; # GEORGIAN MTAVRULI CAPITAL LETTER LAS
+1C9B; C; 10DB; # GEORGIAN MTAVRULI CAPITAL LETTER MAN
+1C9C; C; 10DC; # GEORGIAN MTAVRULI CAPITAL LETTER NAR
+1C9D; C; 10DD; # GEORGIAN MTAVRULI CAPITAL LETTER ON
+1C9E; C; 10DE; # GEORGIAN MTAVRULI CAPITAL LETTER PAR
+1C9F; C; 10DF; # GEORGIAN MTAVRULI CAPITAL LETTER ZHAR
+1CA0; C; 10E0; # GEORGIAN MTAVRULI CAPITAL LETTER RAE
+1CA1; C; 10E1; # GEORGIAN MTAVRULI CAPITAL LETTER SAN
+1CA2; C; 10E2; # GEORGIAN MTAVRULI CAPITAL LETTER TAR
+1CA3; C; 10E3; # GEORGIAN MTAVRULI CAPITAL LETTER UN
+1CA4; C; 10E4; # GEORGIAN MTAVRULI CAPITAL LETTER PHAR
+1CA5; C; 10E5; # GEORGIAN MTAVRULI CAPITAL LETTER KHAR
+1CA6; C; 10E6; # GEORGIAN MTAVRULI CAPITAL LETTER GHAN
+1CA7; C; 10E7; # GEORGIAN MTAVRULI CAPITAL LETTER QAR
+1CA8; C; 10E8; # GEORGIAN MTAVRULI CAPITAL LETTER SHIN
+1CA9; C; 10E9; # GEORGIAN MTAVRULI CAPITAL LETTER CHIN
+1CAA; C; 10EA; # GEORGIAN MTAVRULI CAPITAL LETTER CAN
+1CAB; C; 10EB; # GEORGIAN MTAVRULI CAPITAL LETTER JIL
+1CAC; C; 10EC; # GEORGIAN MTAVRULI CAPITAL LETTER CIL
+1CAD; C; 10ED; # GEORGIAN MTAVRULI CAPITAL LETTER CHAR
+1CAE; C; 10EE; # GEORGIAN MTAVRULI CAPITAL LETTER XAN
+1CAF; C; 10EF; # GEORGIAN MTAVRULI CAPITAL LETTER JHAN
+1CB0; C; 10F0; # GEORGIAN MTAVRULI CAPITAL LETTER HAE
+1CB1; C; 10F1; # GEORGIAN MTAVRULI CAPITAL LETTER HE
+1CB2; C; 10F2; # GEORGIAN MTAVRULI CAPITAL LETTER HIE
+1CB3; C; 10F3; # GEORGIAN MTAVRULI CAPITAL LETTER WE
+1CB4; C; 10F4; # GEORGIAN MTAVRULI CAPITAL LETTER HAR
+1CB5; C; 10F5; # GEORGIAN MTAVRULI CAPITAL LETTER HOE
+1CB6; C; 10F6; # GEORGIAN MTAVRULI CAPITAL LETTER FI
+1CB7; C; 10F7; # GEORGIAN MTAVRULI CAPITAL LETTER YN
+1CB8; C; 10F8; # GEORGIAN MTAVRULI CAPITAL LETTER ELIFI
+1CB9; C; 10F9; # GEORGIAN MTAVRULI CAPITAL LETTER TURNED GAN
+1CBA; C; 10FA; # GEORGIAN MTAVRULI CAPITAL LETTER AIN
+1CBD; C; 10FD; # GEORGIAN MTAVRULI CAPITAL LETTER AEN
+1CBE; C; 10FE; # GEORGIAN MTAVRULI CAPITAL LETTER HARD SIGN
+1CBF; C; 10FF; # GEORGIAN MTAVRULI CAPITAL LETTER LABIAL SIGN
+1E00; C; 1E01; # LATIN CAPITAL LETTER A WITH RING BELOW
+1E02; C; 1E03; # LATIN CAPITAL LETTER B WITH DOT ABOVE
+1E04; C; 1E05; # LATIN CAPITAL LETTER B WITH DOT BELOW
+1E06; C; 1E07; # LATIN CAPITAL LETTER B WITH LINE BELOW
+1E08; C; 1E09; # LATIN CAPITAL LETTER C WITH CEDILLA AND ACUTE
+1E0A; C; 1E0B; # LATIN CAPITAL LETTER D WITH DOT ABOVE
+1E0C; C; 1E0D; # LATIN CAPITAL LETTER D WITH DOT BELOW
+1E0E; C; 1E0F; # LATIN CAPITAL LETTER D WITH LINE BELOW
+1E10; C; 1E11; # LATIN CAPITAL LETTER D WITH CEDILLA
+1E12; C; 1E13; # LATIN CAPITAL LETTER D WITH CIRCUMFLEX BELOW
+1E14; C; 1E15; # LATIN CAPITAL LETTER E WITH MACRON AND GRAVE
+1E16; C; 1E17; # LATIN CAPITAL LETTER E WITH MACRON AND ACUTE
+1E18; C; 1E19; # LATIN CAPITAL LETTER E WITH CIRCUMFLEX BELOW
+1E1A; C; 1E1B; # LATIN CAPITAL LETTER E WITH TILDE BELOW
+1E1C; C; 1E1D; # LATIN CAPITAL LETTER E WITH CEDILLA AND BREVE
+1E1E; C; 1E1F; # LATIN CAPITAL LETTER F WITH DOT ABOVE
+1E20; C; 1E21; # LATIN CAPITAL LETTER G WITH MACRON
+1E22; C; 1E23; # LATIN CAPITAL LETTER H WITH DOT ABOVE
+1E24; C; 1E25; # LATIN CAPITAL LETTER H WITH DOT BELOW
+1E26; C; 1E27; # LATIN CAPITAL LETTER H WITH DIAERESIS
+1E28; C; 1E29; # LATIN CAPITAL LETTER H WITH CEDILLA
+1E2A; C; 1E2B; # LATIN CAPITAL LETTER H WITH BREVE BELOW
+1E2C; C; 1E2D; # LATIN CAPITAL LETTER I WITH TILDE BELOW
+1E2E; C; 1E2F; # LATIN CAPITAL LETTER I WITH DIAERESIS AND ACUTE
+1E30; C; 1E31; # LATIN CAPITAL LETTER K WITH ACUTE
+1E32; C; 1E33; # LATIN CAPITAL LETTER K WITH DOT BELOW
+1E34; C; 1E35; # LATIN CAPITAL LETTER K WITH LINE BELOW
+1E36; C; 1E37; # LATIN CAPITAL LETTER L WITH DOT BELOW
+1E38; C; 1E39; # LATIN CAPITAL LETTER L WITH DOT BELOW AND MACRON
+1E3A; C; 1E3B; # LATIN CAPITAL LETTER L WITH LINE BELOW
+1E3C; C; 1E3D; # LATIN CAPITAL LETTER L WITH CIRCUMFLEX BELOW
+1E3E; C; 1E3F; # LATIN CAPITAL LETTER M WITH ACUTE
+1E40; C; 1E41; # LATIN CAPITAL LETTER M WITH DOT ABOVE
+1E42; C; 1E43; # LATIN CAPITAL LETTER M WITH DOT BELOW
+1E44; C; 1E45; # LATIN CAPITAL LETTER N WITH DOT ABOVE
+1E46; C; 1E47; # LATIN CAPITAL LETTER N WITH DOT BELOW
+1E48; C; 1E49; # LATIN CAPITAL LETTER N WITH LINE BELOW
+1E4A; C; 1E4B; # LATIN CAPITAL LETTER N WITH CIRCUMFLEX BELOW
+1E4C; C; 1E4D; # LATIN CAPITAL LETTER O WITH TILDE AND ACUTE
+1E4E; C; 1E4F; # LATIN CAPITAL LETTER O WITH TILDE AND DIAERESIS
+1E50; C; 1E51; # LATIN CAPITAL LETTER O WITH MACRON AND GRAVE
+1E52; C; 1E53; # LATIN CAPITAL LETTER O WITH MACRON AND ACUTE
+1E54; C; 1E55; # LATIN CAPITAL LETTER P WITH ACUTE
+1E56; C; 1E57; # LATIN CAPITAL LETTER P WITH DOT ABOVE
+1E58; C; 1E59; # LATIN CAPITAL LETTER R WITH DOT ABOVE
+1E5A; C; 1E5B; # LATIN CAPITAL LETTER R WITH DOT BELOW
+1E5C; C; 1E5D; # LATIN CAPITAL LETTER R WITH DOT BELOW AND MACRON
+1E5E; C; 1E5F; # LATIN CAPITAL LETTER R WITH LINE BELOW
+1E60; C; 1E61; # LATIN CAPITAL LETTER S WITH DOT ABOVE
+1E62; C; 1E63; # LATIN CAPITAL LETTER S WITH DOT BELOW
+1E64; C; 1E65; # LATIN CAPITAL LETTER S WITH ACUTE AND DOT ABOVE
+1E66; C; 1E67; # LATIN CAPITAL LETTER S WITH CARON AND DOT ABOVE
+1E68; C; 1E69; # LATIN CAPITAL LETTER S WITH DOT BELOW AND DOT ABOVE
+1E6A; C; 1E6B; # LATIN CAPITAL LETTER T WITH DOT ABOVE
+1E6C; C; 1E6D; # LATIN CAPITAL LETTER T WITH DOT BELOW
+1E6E; C; 1E6F; # LATIN CAPITAL LETTER T WITH LINE BELOW
+1E70; C; 1E71; # LATIN CAPITAL LETTER T WITH CIRCUMFLEX BELOW
+1E72; C; 1E73; # LATIN CAPITAL LETTER U WITH DIAERESIS BELOW
+1E74; C; 1E75; # LATIN CAPITAL LETTER U WITH TILDE BELOW
+1E76; C; 1E77; # LATIN CAPITAL LETTER U WITH CIRCUMFLEX BELOW
+1E78; C; 1E79; # LATIN CAPITAL LETTER U WITH TILDE AND ACUTE
+1E7A; C; 1E7B; # LATIN CAPITAL LETTER U WITH MACRON AND DIAERESIS
+1E7C; C; 1E7D; # LATIN CAPITAL LETTER V WITH TILDE
+1E7E; C; 1E7F; # LATIN CAPITAL LETTER V WITH DOT BELOW
+1E80; C; 1E81; # LATIN CAPITAL LETTER W WITH GRAVE
+1E82; C; 1E83; # LATIN CAPITAL LETTER W WITH ACUTE
+1E84; C; 1E85; # LATIN CAPITAL LETTER W WITH DIAERESIS
+1E86; C; 1E87; # LATIN CAPITAL LETTER W WITH DOT ABOVE
+1E88; C; 1E89; # LATIN CAPITAL LETTER W WITH DOT BELOW
+1E8A; C; 1E8B; # LATIN CAPITAL LETTER X WITH DOT ABOVE
+1E8C; C; 1E8D; # LATIN CAPITAL LETTER X WITH DIAERESIS
+1E8E; C; 1E8F; # LATIN CAPITAL LETTER Y WITH DOT ABOVE
+1E90; C; 1E91; # LATIN CAPITAL LETTER Z WITH CIRCUMFLEX
+1E92; C; 1E93; # LATIN CAPITAL LETTER Z WITH DOT BELOW
+1E94; C; 1E95; # LATIN CAPITAL LETTER Z WITH LINE BELOW
+1E96; F; 0068 0331; # LATIN SMALL LETTER H WITH LINE BELOW
+1E97; F; 0074 0308; # LATIN SMALL LETTER T WITH DIAERESIS
+1E98; F; 0077 030A; # LATIN SMALL LETTER W WITH RING ABOVE
+1E99; F; 0079 030A; # LATIN SMALL LETTER Y WITH RING ABOVE
+1E9A; F; 0061 02BE; # LATIN SMALL LETTER A WITH RIGHT HALF RING
+1E9B; C; 1E61; # LATIN SMALL LETTER LONG S WITH DOT ABOVE
+1E9E; F; 0073 0073; # LATIN CAPITAL LETTER SHARP S
+1E9E; S; 00DF; # LATIN CAPITAL LETTER SHARP S
+1EA0; C; 1EA1; # LATIN CAPITAL LETTER A WITH DOT BELOW
+1EA2; C; 1EA3; # LATIN CAPITAL LETTER A WITH HOOK ABOVE
+1EA4; C; 1EA5; # LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE
+1EA6; C; 1EA7; # LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE
+1EA8; C; 1EA9; # LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE
+1EAA; C; 1EAB; # LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE
+1EAC; C; 1EAD; # LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW
+1EAE; C; 1EAF; # LATIN CAPITAL LETTER A WITH BREVE AND ACUTE
+1EB0; C; 1EB1; # LATIN CAPITAL LETTER A WITH BREVE AND GRAVE
+1EB2; C; 1EB3; # LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE
+1EB4; C; 1EB5; # LATIN CAPITAL LETTER A WITH BREVE AND TILDE
+1EB6; C; 1EB7; # LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW
+1EB8; C; 1EB9; # LATIN CAPITAL LETTER E WITH DOT BELOW
+1EBA; C; 1EBB; # LATIN CAPITAL LETTER E WITH HOOK ABOVE
+1EBC; C; 1EBD; # LATIN CAPITAL LETTER E WITH TILDE
+1EBE; C; 1EBF; # LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE
+1EC0; C; 1EC1; # LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE
+1EC2; C; 1EC3; # LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE
+1EC4; C; 1EC5; # LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE
+1EC6; C; 1EC7; # LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW
+1EC8; C; 1EC9; # LATIN CAPITAL LETTER I WITH HOOK ABOVE
+1ECA; C; 1ECB; # LATIN CAPITAL LETTER I WITH DOT BELOW
+1ECC; C; 1ECD; # LATIN CAPITAL LETTER O WITH DOT BELOW
+1ECE; C; 1ECF; # LATIN CAPITAL LETTER O WITH HOOK ABOVE
+1ED0; C; 1ED1; # LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE
+1ED2; C; 1ED3; # LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE
+1ED4; C; 1ED5; # LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE
+1ED6; C; 1ED7; # LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE
+1ED8; C; 1ED9; # LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW
+1EDA; C; 1EDB; # LATIN CAPITAL LETTER O WITH HORN AND ACUTE
+1EDC; C; 1EDD; # LATIN CAPITAL LETTER O WITH HORN AND GRAVE
+1EDE; C; 1EDF; # LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE
+1EE0; C; 1EE1; # LATIN CAPITAL LETTER O WITH HORN AND TILDE
+1EE2; C; 1EE3; # LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW
+1EE4; C; 1EE5; # LATIN CAPITAL LETTER U WITH DOT BELOW
+1EE6; C; 1EE7; # LATIN CAPITAL LETTER U WITH HOOK ABOVE
+1EE8; C; 1EE9; # LATIN CAPITAL LETTER U WITH HORN AND ACUTE
+1EEA; C; 1EEB; # LATIN CAPITAL LETTER U WITH HORN AND GRAVE
+1EEC; C; 1EED; # LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE
+1EEE; C; 1EEF; # LATIN CAPITAL LETTER U WITH HORN AND TILDE
+1EF0; C; 1EF1; # LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW
+1EF2; C; 1EF3; # LATIN CAPITAL LETTER Y WITH GRAVE
+1EF4; C; 1EF5; # LATIN CAPITAL LETTER Y WITH DOT BELOW
+1EF6; C; 1EF7; # LATIN CAPITAL LETTER Y WITH HOOK ABOVE
+1EF8; C; 1EF9; # LATIN CAPITAL LETTER Y WITH TILDE
+1EFA; C; 1EFB; # LATIN CAPITAL LETTER MIDDLE-WELSH LL
+1EFC; C; 1EFD; # LATIN CAPITAL LETTER MIDDLE-WELSH V
+1EFE; C; 1EFF; # LATIN CAPITAL LETTER Y WITH LOOP
+1F08; C; 1F00; # GREEK CAPITAL LETTER ALPHA WITH PSILI
+1F09; C; 1F01; # GREEK CAPITAL LETTER ALPHA WITH DASIA
+1F0A; C; 1F02; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA
+1F0B; C; 1F03; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA
+1F0C; C; 1F04; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA
+1F0D; C; 1F05; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA
+1F0E; C; 1F06; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI
+1F0F; C; 1F07; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI
+1F18; C; 1F10; # GREEK CAPITAL LETTER EPSILON WITH PSILI
+1F19; C; 1F11; # GREEK CAPITAL LETTER EPSILON WITH DASIA
+1F1A; C; 1F12; # GREEK CAPITAL LETTER EPSILON WITH PSILI AND VARIA
+1F1B; C; 1F13; # GREEK CAPITAL LETTER EPSILON WITH DASIA AND VARIA
+1F1C; C; 1F14; # GREEK CAPITAL LETTER EPSILON WITH PSILI AND OXIA
+1F1D; C; 1F15; # GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA
+1F28; C; 1F20; # GREEK CAPITAL LETTER ETA WITH PSILI
+1F29; C; 1F21; # GREEK CAPITAL LETTER ETA WITH DASIA
+1F2A; C; 1F22; # GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA
+1F2B; C; 1F23; # GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA
+1F2C; C; 1F24; # GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA
+1F2D; C; 1F25; # GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA
+1F2E; C; 1F26; # GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI
+1F2F; C; 1F27; # GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI
+1F38; C; 1F30; # GREEK CAPITAL LETTER IOTA WITH PSILI
+1F39; C; 1F31; # GREEK CAPITAL LETTER IOTA WITH DASIA
+1F3A; C; 1F32; # GREEK CAPITAL LETTER IOTA WITH PSILI AND VARIA
+1F3B; C; 1F33; # GREEK CAPITAL LETTER IOTA WITH DASIA AND VARIA
+1F3C; C; 1F34; # GREEK CAPITAL LETTER IOTA WITH PSILI AND OXIA
+1F3D; C; 1F35; # GREEK CAPITAL LETTER IOTA WITH DASIA AND OXIA
+1F3E; C; 1F36; # GREEK CAPITAL LETTER IOTA WITH PSILI AND PERISPOMENI
+1F3F; C; 1F37; # GREEK CAPITAL LETTER IOTA WITH DASIA AND PERISPOMENI
+1F48; C; 1F40; # GREEK CAPITAL LETTER OMICRON WITH PSILI
+1F49; C; 1F41; # GREEK CAPITAL LETTER OMICRON WITH DASIA
+1F4A; C; 1F42; # GREEK CAPITAL LETTER OMICRON WITH PSILI AND VARIA
+1F4B; C; 1F43; # GREEK CAPITAL LETTER OMICRON WITH DASIA AND VARIA
+1F4C; C; 1F44; # GREEK CAPITAL LETTER OMICRON WITH PSILI AND OXIA
+1F4D; C; 1F45; # GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA
+1F50; F; 03C5 0313; # GREEK SMALL LETTER UPSILON WITH PSILI
+1F52; F; 03C5 0313 0300; # GREEK SMALL LETTER UPSILON WITH PSILI AND VARIA
+1F54; F; 03C5 0313 0301; # GREEK SMALL LETTER UPSILON WITH PSILI AND OXIA
+1F56; F; 03C5 0313 0342; # GREEK SMALL LETTER UPSILON WITH PSILI AND PERISPOMENI
+1F59; C; 1F51; # GREEK CAPITAL LETTER UPSILON WITH DASIA
+1F5B; C; 1F53; # GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA
+1F5D; C; 1F55; # GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA
+1F5F; C; 1F57; # GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI
+1F68; C; 1F60; # GREEK CAPITAL LETTER OMEGA WITH PSILI
+1F69; C; 1F61; # GREEK CAPITAL LETTER OMEGA WITH DASIA
+1F6A; C; 1F62; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA
+1F6B; C; 1F63; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA
+1F6C; C; 1F64; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA
+1F6D; C; 1F65; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA
+1F6E; C; 1F66; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI
+1F6F; C; 1F67; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI
+1F80; F; 1F00 03B9; # GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI
+1F81; F; 1F01 03B9; # GREEK SMALL LETTER ALPHA WITH DASIA AND YPOGEGRAMMENI
+1F82; F; 1F02 03B9; # GREEK SMALL LETTER ALPHA WITH PSILI AND VARIA AND YPOGEGRAMMENI
+1F83; F; 1F03 03B9; # GREEK SMALL LETTER ALPHA WITH DASIA AND VARIA AND YPOGEGRAMMENI
+1F84; F; 1F04 03B9; # GREEK SMALL LETTER ALPHA WITH PSILI AND OXIA AND YPOGEGRAMMENI
+1F85; F; 1F05 03B9; # GREEK SMALL LETTER ALPHA WITH DASIA AND OXIA AND YPOGEGRAMMENI
+1F86; F; 1F06 03B9; # GREEK SMALL LETTER ALPHA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI
+1F87; F; 1F07 03B9; # GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
+1F88; F; 1F00 03B9; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI
+1F88; S; 1F80; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI
+1F89; F; 1F01 03B9; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND PROSGEGRAMMENI
+1F89; S; 1F81; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND PROSGEGRAMMENI
+1F8A; F; 1F02 03B9; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA AND PROSGEGRAMMENI
+1F8A; S; 1F82; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA AND PROSGEGRAMMENI
+1F8B; F; 1F03 03B9; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA AND PROSGEGRAMMENI
+1F8B; S; 1F83; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA AND PROSGEGRAMMENI
+1F8C; F; 1F04 03B9; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA AND PROSGEGRAMMENI
+1F8C; S; 1F84; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA AND PROSGEGRAMMENI
+1F8D; F; 1F05 03B9; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA AND PROSGEGRAMMENI
+1F8D; S; 1F85; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA AND PROSGEGRAMMENI
+1F8E; F; 1F06 03B9; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
+1F8E; S; 1F86; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
+1F8F; F; 1F07 03B9; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
+1F8F; S; 1F87; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
+1F90; F; 1F20 03B9; # GREEK SMALL LETTER ETA WITH PSILI AND YPOGEGRAMMENI
+1F91; F; 1F21 03B9; # GREEK SMALL LETTER ETA WITH DASIA AND YPOGEGRAMMENI
+1F92; F; 1F22 03B9; # GREEK SMALL LETTER ETA WITH PSILI AND VARIA AND YPOGEGRAMMENI
+1F93; F; 1F23 03B9; # GREEK SMALL LETTER ETA WITH DASIA AND VARIA AND YPOGEGRAMMENI
+1F94; F; 1F24 03B9; # GREEK SMALL LETTER ETA WITH PSILI AND OXIA AND YPOGEGRAMMENI
+1F95; F; 1F25 03B9; # GREEK SMALL LETTER ETA WITH DASIA AND OXIA AND YPOGEGRAMMENI
+1F96; F; 1F26 03B9; # GREEK SMALL LETTER ETA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI
+1F97; F; 1F27 03B9; # GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
+1F98; F; 1F20 03B9; # GREEK CAPITAL LETTER ETA WITH PSILI AND PROSGEGRAMMENI
+1F98; S; 1F90; # GREEK CAPITAL LETTER ETA WITH PSILI AND PROSGEGRAMMENI
+1F99; F; 1F21 03B9; # GREEK CAPITAL LETTER ETA WITH DASIA AND PROSGEGRAMMENI
+1F99; S; 1F91; # GREEK CAPITAL LETTER ETA WITH DASIA AND PROSGEGRAMMENI
+1F9A; F; 1F22 03B9; # GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA AND PROSGEGRAMMENI
+1F9A; S; 1F92; # GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA AND PROSGEGRAMMENI
+1F9B; F; 1F23 03B9; # GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA AND PROSGEGRAMMENI
+1F9B; S; 1F93; # GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA AND PROSGEGRAMMENI
+1F9C; F; 1F24 03B9; # GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA AND PROSGEGRAMMENI
+1F9C; S; 1F94; # GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA AND PROSGEGRAMMENI
+1F9D; F; 1F25 03B9; # GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA AND PROSGEGRAMMENI
+1F9D; S; 1F95; # GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA AND PROSGEGRAMMENI
+1F9E; F; 1F26 03B9; # GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
+1F9E; S; 1F96; # GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
+1F9F; F; 1F27 03B9; # GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
+1F9F; S; 1F97; # GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
+1FA0; F; 1F60 03B9; # GREEK SMALL LETTER OMEGA WITH PSILI AND YPOGEGRAMMENI
+1FA1; F; 1F61 03B9; # GREEK SMALL LETTER OMEGA WITH DASIA AND YPOGEGRAMMENI
+1FA2; F; 1F62 03B9; # GREEK SMALL LETTER OMEGA WITH PSILI AND VARIA AND YPOGEGRAMMENI
+1FA3; F; 1F63 03B9; # GREEK SMALL LETTER OMEGA WITH DASIA AND VARIA AND YPOGEGRAMMENI
+1FA4; F; 1F64 03B9; # GREEK SMALL LETTER OMEGA WITH PSILI AND OXIA AND YPOGEGRAMMENI
+1FA5; F; 1F65 03B9; # GREEK SMALL LETTER OMEGA WITH DASIA AND OXIA AND YPOGEGRAMMENI
+1FA6; F; 1F66 03B9; # GREEK SMALL LETTER OMEGA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI
+1FA7; F; 1F67 03B9; # GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
+1FA8; F; 1F60 03B9; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND PROSGEGRAMMENI
+1FA8; S; 1FA0; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND PROSGEGRAMMENI
+1FA9; F; 1F61 03B9; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND PROSGEGRAMMENI
+1FA9; S; 1FA1; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND PROSGEGRAMMENI
+1FAA; F; 1F62 03B9; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA AND PROSGEGRAMMENI
+1FAA; S; 1FA2; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA AND PROSGEGRAMMENI
+1FAB; F; 1F63 03B9; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA AND PROSGEGRAMMENI
+1FAB; S; 1FA3; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA AND PROSGEGRAMMENI
+1FAC; F; 1F64 03B9; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA AND PROSGEGRAMMENI
+1FAC; S; 1FA4; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA AND PROSGEGRAMMENI
+1FAD; F; 1F65 03B9; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA AND PROSGEGRAMMENI
+1FAD; S; 1FA5; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA AND PROSGEGRAMMENI
+1FAE; F; 1F66 03B9; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
+1FAE; S; 1FA6; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
+1FAF; F; 1F67 03B9; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
+1FAF; S; 1FA7; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
+1FB2; F; 1F70 03B9; # GREEK SMALL LETTER ALPHA WITH VARIA AND YPOGEGRAMMENI
+1FB3; F; 03B1 03B9; # GREEK SMALL LETTER ALPHA WITH YPOGEGRAMMENI
+1FB4; F; 03AC 03B9; # GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI
+1FB6; F; 03B1 0342; # GREEK SMALL LETTER ALPHA WITH PERISPOMENI
+1FB7; F; 03B1 0342 03B9; # GREEK SMALL LETTER ALPHA WITH PERISPOMENI AND YPOGEGRAMMENI
+1FB8; C; 1FB0; # GREEK CAPITAL LETTER ALPHA WITH VRACHY
+1FB9; C; 1FB1; # GREEK CAPITAL LETTER ALPHA WITH MACRON
+1FBA; C; 1F70; # GREEK CAPITAL LETTER ALPHA WITH VARIA
+1FBB; C; 1F71; # GREEK CAPITAL LETTER ALPHA WITH OXIA
+1FBC; F; 03B1 03B9; # GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI
+1FBC; S; 1FB3; # GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI
+1FBE; C; 03B9; # GREEK PROSGEGRAMMENI
+1FC2; F; 1F74 03B9; # GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI
+1FC3; F; 03B7 03B9; # GREEK SMALL LETTER ETA WITH YPOGEGRAMMENI
+1FC4; F; 03AE 03B9; # GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI
+1FC6; F; 03B7 0342; # GREEK SMALL LETTER ETA WITH PERISPOMENI
+1FC7; F; 03B7 0342 03B9; # GREEK SMALL LETTER ETA WITH PERISPOMENI AND YPOGEGRAMMENI
+1FC8; C; 1F72; # GREEK CAPITAL LETTER EPSILON WITH VARIA
+1FC9; C; 1F73; # GREEK CAPITAL LETTER EPSILON WITH OXIA
+1FCA; C; 1F74; # GREEK CAPITAL LETTER ETA WITH VARIA
+1FCB; C; 1F75; # GREEK CAPITAL LETTER ETA WITH OXIA
+1FCC; F; 03B7 03B9; # GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI
+1FCC; S; 1FC3; # GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI
+1FD2; F; 03B9 0308 0300; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND VARIA
+1FD3; F; 03B9 0308 0301; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA
+1FD6; F; 03B9 0342; # GREEK SMALL LETTER IOTA WITH PERISPOMENI
+1FD7; F; 03B9 0308 0342; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND PERISPOMENI
+1FD8; C; 1FD0; # GREEK CAPITAL LETTER IOTA WITH VRACHY
+1FD9; C; 1FD1; # GREEK CAPITAL LETTER IOTA WITH MACRON
+1FDA; C; 1F76; # GREEK CAPITAL LETTER IOTA WITH VARIA
+1FDB; C; 1F77; # GREEK CAPITAL LETTER IOTA WITH OXIA
+1FE2; F; 03C5 0308 0300; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND VARIA
+1FE3; F; 03C5 0308 0301; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND OXIA
+1FE4; F; 03C1 0313; # GREEK SMALL LETTER RHO WITH PSILI
+1FE6; F; 03C5 0342; # GREEK SMALL LETTER UPSILON WITH PERISPOMENI
+1FE7; F; 03C5 0308 0342; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND PERISPOMENI
+1FE8; C; 1FE0; # GREEK CAPITAL LETTER UPSILON WITH VRACHY
+1FE9; C; 1FE1; # GREEK CAPITAL LETTER UPSILON WITH MACRON
+1FEA; C; 1F7A; # GREEK CAPITAL LETTER UPSILON WITH VARIA
+1FEB; C; 1F7B; # GREEK CAPITAL LETTER UPSILON WITH OXIA
+1FEC; C; 1FE5; # GREEK CAPITAL LETTER RHO WITH DASIA
+1FF2; F; 1F7C 03B9; # GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI
+1FF3; F; 03C9 03B9; # GREEK SMALL LETTER OMEGA WITH YPOGEGRAMMENI
+1FF4; F; 03CE 03B9; # GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI
+1FF6; F; 03C9 0342; # GREEK SMALL LETTER OMEGA WITH PERISPOMENI
+1FF7; F; 03C9 0342 03B9; # GREEK SMALL LETTER OMEGA WITH PERISPOMENI AND YPOGEGRAMMENI
+1FF8; C; 1F78; # GREEK CAPITAL LETTER OMICRON WITH VARIA
+1FF9; C; 1F79; # GREEK CAPITAL LETTER OMICRON WITH OXIA
+1FFA; C; 1F7C; # GREEK CAPITAL LETTER OMEGA WITH VARIA
+1FFB; C; 1F7D; # GREEK CAPITAL LETTER OMEGA WITH OXIA
+1FFC; F; 03C9 03B9; # GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI
+1FFC; S; 1FF3; # GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI
+2126; C; 03C9; # OHM SIGN
+212A; C; 006B; # KELVIN SIGN
+212B; C; 00E5; # ANGSTROM SIGN
+2132; C; 214E; # TURNED CAPITAL F
+2160; C; 2170; # ROMAN NUMERAL ONE
+2161; C; 2171; # ROMAN NUMERAL TWO
+2162; C; 2172; # ROMAN NUMERAL THREE
+2163; C; 2173; # ROMAN NUMERAL FOUR
+2164; C; 2174; # ROMAN NUMERAL FIVE
+2165; C; 2175; # ROMAN NUMERAL SIX
+2166; C; 2176; # ROMAN NUMERAL SEVEN
+2167; C; 2177; # ROMAN NUMERAL EIGHT
+2168; C; 2178; # ROMAN NUMERAL NINE
+2169; C; 2179; # ROMAN NUMERAL TEN
+216A; C; 217A; # ROMAN NUMERAL ELEVEN
+216B; C; 217B; # ROMAN NUMERAL TWELVE
+216C; C; 217C; # ROMAN NUMERAL FIFTY
+216D; C; 217D; # ROMAN NUMERAL ONE HUNDRED
+216E; C; 217E; # ROMAN NUMERAL FIVE HUNDRED
+216F; C; 217F; # ROMAN NUMERAL ONE THOUSAND
+2183; C; 2184; # ROMAN NUMERAL REVERSED ONE HUNDRED
+24B6; C; 24D0; # CIRCLED LATIN CAPITAL LETTER A
+24B7; C; 24D1; # CIRCLED LATIN CAPITAL LETTER B
+24B8; C; 24D2; # CIRCLED LATIN CAPITAL LETTER C
+24B9; C; 24D3; # CIRCLED LATIN CAPITAL LETTER D
+24BA; C; 24D4; # CIRCLED LATIN CAPITAL LETTER E
+24BB; C; 24D5; # CIRCLED LATIN CAPITAL LETTER F
+24BC; C; 24D6; # CIRCLED LATIN CAPITAL LETTER G
+24BD; C; 24D7; # CIRCLED LATIN CAPITAL LETTER H
+24BE; C; 24D8; # CIRCLED LATIN CAPITAL LETTER I
+24BF; C; 24D9; # CIRCLED LATIN CAPITAL LETTER J
+24C0; C; 24DA; # CIRCLED LATIN CAPITAL LETTER K
+24C1; C; 24DB; # CIRCLED LATIN CAPITAL LETTER L
+24C2; C; 24DC; # CIRCLED LATIN CAPITAL LETTER M
+24C3; C; 24DD; # CIRCLED LATIN CAPITAL LETTER N
+24C4; C; 24DE; # CIRCLED LATIN CAPITAL LETTER O
+24C5; C; 24DF; # CIRCLED LATIN CAPITAL LETTER P
+24C6; C; 24E0; # CIRCLED LATIN CAPITAL LETTER Q
+24C7; C; 24E1; # CIRCLED LATIN CAPITAL LETTER R
+24C8; C; 24E2; # CIRCLED LATIN CAPITAL LETTER S
+24C9; C; 24E3; # CIRCLED LATIN CAPITAL LETTER T
+24CA; C; 24E4; # CIRCLED LATIN CAPITAL LETTER U
+24CB; C; 24E5; # CIRCLED LATIN CAPITAL LETTER V
+24CC; C; 24E6; # CIRCLED LATIN CAPITAL LETTER W
+24CD; C; 24E7; # CIRCLED LATIN CAPITAL LETTER X
+24CE; C; 24E8; # CIRCLED LATIN CAPITAL LETTER Y
+24CF; C; 24E9; # CIRCLED LATIN CAPITAL LETTER Z
+2C00; C; 2C30; # GLAGOLITIC CAPITAL LETTER AZU
+2C01; C; 2C31; # GLAGOLITIC CAPITAL LETTER BUKY
+2C02; C; 2C32; # GLAGOLITIC CAPITAL LETTER VEDE
+2C03; C; 2C33; # GLAGOLITIC CAPITAL LETTER GLAGOLI
+2C04; C; 2C34; # GLAGOLITIC CAPITAL LETTER DOBRO
+2C05; C; 2C35; # GLAGOLITIC CAPITAL LETTER YESTU
+2C06; C; 2C36; # GLAGOLITIC CAPITAL LETTER ZHIVETE
+2C07; C; 2C37; # GLAGOLITIC CAPITAL LETTER DZELO
+2C08; C; 2C38; # GLAGOLITIC CAPITAL LETTER ZEMLJA
+2C09; C; 2C39; # GLAGOLITIC CAPITAL LETTER IZHE
+2C0A; C; 2C3A; # GLAGOLITIC CAPITAL LETTER INITIAL IZHE
+2C0B; C; 2C3B; # GLAGOLITIC CAPITAL LETTER I
+2C0C; C; 2C3C; # GLAGOLITIC CAPITAL LETTER DJERVI
+2C0D; C; 2C3D; # GLAGOLITIC CAPITAL LETTER KAKO
+2C0E; C; 2C3E; # GLAGOLITIC CAPITAL LETTER LJUDIJE
+2C0F; C; 2C3F; # GLAGOLITIC CAPITAL LETTER MYSLITE
+2C10; C; 2C40; # GLAGOLITIC CAPITAL LETTER NASHI
+2C11; C; 2C41; # GLAGOLITIC CAPITAL LETTER ONU
+2C12; C; 2C42; # GLAGOLITIC CAPITAL LETTER POKOJI
+2C13; C; 2C43; # GLAGOLITIC CAPITAL LETTER RITSI
+2C14; C; 2C44; # GLAGOLITIC CAPITAL LETTER SLOVO
+2C15; C; 2C45; # GLAGOLITIC CAPITAL LETTER TVRIDO
+2C16; C; 2C46; # GLAGOLITIC CAPITAL LETTER UKU
+2C17; C; 2C47; # GLAGOLITIC CAPITAL LETTER FRITU
+2C18; C; 2C48; # GLAGOLITIC CAPITAL LETTER HERU
+2C19; C; 2C49; # GLAGOLITIC CAPITAL LETTER OTU
+2C1A; C; 2C4A; # GLAGOLITIC CAPITAL LETTER PE
+2C1B; C; 2C4B; # GLAGOLITIC CAPITAL LETTER SHTA
+2C1C; C; 2C4C; # GLAGOLITIC CAPITAL LETTER TSI
+2C1D; C; 2C4D; # GLAGOLITIC CAPITAL LETTER CHRIVI
+2C1E; C; 2C4E; # GLAGOLITIC CAPITAL LETTER SHA
+2C1F; C; 2C4F; # GLAGOLITIC CAPITAL LETTER YERU
+2C20; C; 2C50; # GLAGOLITIC CAPITAL LETTER YERI
+2C21; C; 2C51; # GLAGOLITIC CAPITAL LETTER YATI
+2C22; C; 2C52; # GLAGOLITIC CAPITAL LETTER SPIDERY HA
+2C23; C; 2C53; # GLAGOLITIC CAPITAL LETTER YU
+2C24; C; 2C54; # GLAGOLITIC CAPITAL LETTER SMALL YUS
+2C25; C; 2C55; # GLAGOLITIC CAPITAL LETTER SMALL YUS WITH TAIL
+2C26; C; 2C56; # GLAGOLITIC CAPITAL LETTER YO
+2C27; C; 2C57; # GLAGOLITIC CAPITAL LETTER IOTATED SMALL YUS
+2C28; C; 2C58; # GLAGOLITIC CAPITAL LETTER BIG YUS
+2C29; C; 2C59; # GLAGOLITIC CAPITAL LETTER IOTATED BIG YUS
+2C2A; C; 2C5A; # GLAGOLITIC CAPITAL LETTER FITA
+2C2B; C; 2C5B; # GLAGOLITIC CAPITAL LETTER IZHITSA
+2C2C; C; 2C5C; # GLAGOLITIC CAPITAL LETTER SHTAPIC
+2C2D; C; 2C5D; # GLAGOLITIC CAPITAL LETTER TROKUTASTI A
+2C2E; C; 2C5E; # GLAGOLITIC CAPITAL LETTER LATINATE MYSLITE
+2C2F; C; 2C5F; # GLAGOLITIC CAPITAL LETTER CAUDATE CHRIVI
+2C60; C; 2C61; # LATIN CAPITAL LETTER L WITH DOUBLE BAR
+2C62; C; 026B; # LATIN CAPITAL LETTER L WITH MIDDLE TILDE
+2C63; C; 1D7D; # LATIN CAPITAL LETTER P WITH STROKE
+2C64; C; 027D; # LATIN CAPITAL LETTER R WITH TAIL
+2C67; C; 2C68; # LATIN CAPITAL LETTER H WITH DESCENDER
+2C69; C; 2C6A; # LATIN CAPITAL LETTER K WITH DESCENDER
+2C6B; C; 2C6C; # LATIN CAPITAL LETTER Z WITH DESCENDER
+2C6D; C; 0251; # LATIN CAPITAL LETTER ALPHA
+2C6E; C; 0271; # LATIN CAPITAL LETTER M WITH HOOK
+2C6F; C; 0250; # LATIN CAPITAL LETTER TURNED A
+2C70; C; 0252; # LATIN CAPITAL LETTER TURNED ALPHA
+2C72; C; 2C73; # LATIN CAPITAL LETTER W WITH HOOK
+2C75; C; 2C76; # LATIN CAPITAL LETTER HALF H
+2C7E; C; 023F; # LATIN CAPITAL LETTER S WITH SWASH TAIL
+2C7F; C; 0240; # LATIN CAPITAL LETTER Z WITH SWASH TAIL
+2C80; C; 2C81; # COPTIC CAPITAL LETTER ALFA
+2C82; C; 2C83; # COPTIC CAPITAL LETTER VIDA
+2C84; C; 2C85; # COPTIC CAPITAL LETTER GAMMA
+2C86; C; 2C87; # COPTIC CAPITAL LETTER DALDA
+2C88; C; 2C89; # COPTIC CAPITAL LETTER EIE
+2C8A; C; 2C8B; # COPTIC CAPITAL LETTER SOU
+2C8C; C; 2C8D; # COPTIC CAPITAL LETTER ZATA
+2C8E; C; 2C8F; # COPTIC CAPITAL LETTER HATE
+2C90; C; 2C91; # COPTIC CAPITAL LETTER THETHE
+2C92; C; 2C93; # COPTIC CAPITAL LETTER IAUDA
+2C94; C; 2C95; # COPTIC CAPITAL LETTER KAPA
+2C96; C; 2C97; # COPTIC CAPITAL LETTER LAULA
+2C98; C; 2C99; # COPTIC CAPITAL LETTER MI
+2C9A; C; 2C9B; # COPTIC CAPITAL LETTER NI
+2C9C; C; 2C9D; # COPTIC CAPITAL LETTER KSI
+2C9E; C; 2C9F; # COPTIC CAPITAL LETTER O
+2CA0; C; 2CA1; # COPTIC CAPITAL LETTER PI
+2CA2; C; 2CA3; # COPTIC CAPITAL LETTER RO
+2CA4; C; 2CA5; # COPTIC CAPITAL LETTER SIMA
+2CA6; C; 2CA7; # COPTIC CAPITAL LETTER TAU
+2CA8; C; 2CA9; # COPTIC CAPITAL LETTER UA
+2CAA; C; 2CAB; # COPTIC CAPITAL LETTER FI
+2CAC; C; 2CAD; # COPTIC CAPITAL LETTER KHI
+2CAE; C; 2CAF; # COPTIC CAPITAL LETTER PSI
+2CB0; C; 2CB1; # COPTIC CAPITAL LETTER OOU
+2CB2; C; 2CB3; # COPTIC CAPITAL LETTER DIALECT-P ALEF
+2CB4; C; 2CB5; # COPTIC CAPITAL LETTER OLD COPTIC AIN
+2CB6; C; 2CB7; # COPTIC CAPITAL LETTER CRYPTOGRAMMIC EIE
+2CB8; C; 2CB9; # COPTIC CAPITAL LETTER DIALECT-P KAPA
+2CBA; C; 2CBB; # COPTIC CAPITAL LETTER DIALECT-P NI
+2CBC; C; 2CBD; # COPTIC CAPITAL LETTER CRYPTOGRAMMIC NI
+2CBE; C; 2CBF; # COPTIC CAPITAL LETTER OLD COPTIC OOU
+2CC0; C; 2CC1; # COPTIC CAPITAL LETTER SAMPI
+2CC2; C; 2CC3; # COPTIC CAPITAL LETTER CROSSED SHEI
+2CC4; C; 2CC5; # COPTIC CAPITAL LETTER OLD COPTIC SHEI
+2CC6; C; 2CC7; # COPTIC CAPITAL LETTER OLD COPTIC ESH
+2CC8; C; 2CC9; # COPTIC CAPITAL LETTER AKHMIMIC KHEI
+2CCA; C; 2CCB; # COPTIC CAPITAL LETTER DIALECT-P HORI
+2CCC; C; 2CCD; # COPTIC CAPITAL LETTER OLD COPTIC HORI
+2CCE; C; 2CCF; # COPTIC CAPITAL LETTER OLD COPTIC HA
+2CD0; C; 2CD1; # COPTIC CAPITAL LETTER L-SHAPED HA
+2CD2; C; 2CD3; # COPTIC CAPITAL LETTER OLD COPTIC HEI
+2CD4; C; 2CD5; # COPTIC CAPITAL LETTER OLD COPTIC HAT
+2CD6; C; 2CD7; # COPTIC CAPITAL LETTER OLD COPTIC GANGIA
+2CD8; C; 2CD9; # COPTIC CAPITAL LETTER OLD COPTIC DJA
+2CDA; C; 2CDB; # COPTIC CAPITAL LETTER OLD COPTIC SHIMA
+2CDC; C; 2CDD; # COPTIC CAPITAL LETTER OLD NUBIAN SHIMA
+2CDE; C; 2CDF; # COPTIC CAPITAL LETTER OLD NUBIAN NGI
+2CE0; C; 2CE1; # COPTIC CAPITAL LETTER OLD NUBIAN NYI
+2CE2; C; 2CE3; # COPTIC CAPITAL LETTER OLD NUBIAN WAU
+2CEB; C; 2CEC; # COPTIC CAPITAL LETTER CRYPTOGRAMMIC SHEI
+2CED; C; 2CEE; # COPTIC CAPITAL LETTER CRYPTOGRAMMIC GANGIA
+2CF2; C; 2CF3; # COPTIC CAPITAL LETTER BOHAIRIC KHEI
+A640; C; A641; # CYRILLIC CAPITAL LETTER ZEMLYA
+A642; C; A643; # CYRILLIC CAPITAL LETTER DZELO
+A644; C; A645; # CYRILLIC CAPITAL LETTER REVERSED DZE
+A646; C; A647; # CYRILLIC CAPITAL LETTER IOTA
+A648; C; A649; # CYRILLIC CAPITAL LETTER DJERV
+A64A; C; A64B; # CYRILLIC CAPITAL LETTER MONOGRAPH UK
+A64C; C; A64D; # CYRILLIC CAPITAL LETTER BROAD OMEGA
+A64E; C; A64F; # CYRILLIC CAPITAL LETTER NEUTRAL YER
+A650; C; A651; # CYRILLIC CAPITAL LETTER YERU WITH BACK YER
+A652; C; A653; # CYRILLIC CAPITAL LETTER IOTIFIED YAT
+A654; C; A655; # CYRILLIC CAPITAL LETTER REVERSED YU
+A656; C; A657; # CYRILLIC CAPITAL LETTER IOTIFIED A
+A658; C; A659; # CYRILLIC CAPITAL LETTER CLOSED LITTLE YUS
+A65A; C; A65B; # CYRILLIC CAPITAL LETTER BLENDED YUS
+A65C; C; A65D; # CYRILLIC CAPITAL LETTER IOTIFIED CLOSED LITTLE YUS
+A65E; C; A65F; # CYRILLIC CAPITAL LETTER YN
+A660; C; A661; # CYRILLIC CAPITAL LETTER REVERSED TSE
+A662; C; A663; # CYRILLIC CAPITAL LETTER SOFT DE
+A664; C; A665; # CYRILLIC CAPITAL LETTER SOFT EL
+A666; C; A667; # CYRILLIC CAPITAL LETTER SOFT EM
+A668; C; A669; # CYRILLIC CAPITAL LETTER MONOCULAR O
+A66A; C; A66B; # CYRILLIC CAPITAL LETTER BINOCULAR O
+A66C; C; A66D; # CYRILLIC CAPITAL LETTER DOUBLE MONOCULAR O
+A680; C; A681; # CYRILLIC CAPITAL LETTER DWE
+A682; C; A683; # CYRILLIC CAPITAL LETTER DZWE
+A684; C; A685; # CYRILLIC CAPITAL LETTER ZHWE
+A686; C; A687; # CYRILLIC CAPITAL LETTER CCHE
+A688; C; A689; # CYRILLIC CAPITAL LETTER DZZE
+A68A; C; A68B; # CYRILLIC CAPITAL LETTER TE WITH MIDDLE HOOK
+A68C; C; A68D; # CYRILLIC CAPITAL LETTER TWE
+A68E; C; A68F; # CYRILLIC CAPITAL LETTER TSWE
+A690; C; A691; # CYRILLIC CAPITAL LETTER TSSE
+A692; C; A693; # CYRILLIC CAPITAL LETTER TCHE
+A694; C; A695; # CYRILLIC CAPITAL LETTER HWE
+A696; C; A697; # CYRILLIC CAPITAL LETTER SHWE
+A698; C; A699; # CYRILLIC CAPITAL LETTER DOUBLE O
+A69A; C; A69B; # CYRILLIC CAPITAL LETTER CROSSED O
+A722; C; A723; # LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF
+A724; C; A725; # LATIN CAPITAL LETTER EGYPTOLOGICAL AIN
+A726; C; A727; # LATIN CAPITAL LETTER HENG
+A728; C; A729; # LATIN CAPITAL LETTER TZ
+A72A; C; A72B; # LATIN CAPITAL LETTER TRESILLO
+A72C; C; A72D; # LATIN CAPITAL LETTER CUATRILLO
+A72E; C; A72F; # LATIN CAPITAL LETTER CUATRILLO WITH COMMA
+A732; C; A733; # LATIN CAPITAL LETTER AA
+A734; C; A735; # LATIN CAPITAL LETTER AO
+A736; C; A737; # LATIN CAPITAL LETTER AU
+A738; C; A739; # LATIN CAPITAL LETTER AV
+A73A; C; A73B; # LATIN CAPITAL LETTER AV WITH HORIZONTAL BAR
+A73C; C; A73D; # LATIN CAPITAL LETTER AY
+A73E; C; A73F; # LATIN CAPITAL LETTER REVERSED C WITH DOT
+A740; C; A741; # LATIN CAPITAL LETTER K WITH STROKE
+A742; C; A743; # LATIN CAPITAL LETTER K WITH DIAGONAL STROKE
+A744; C; A745; # LATIN CAPITAL LETTER K WITH STROKE AND DIAGONAL STROKE
+A746; C; A747; # LATIN CAPITAL LETTER BROKEN L
+A748; C; A749; # LATIN CAPITAL LETTER L WITH HIGH STROKE
+A74A; C; A74B; # LATIN CAPITAL LETTER O WITH LONG STROKE OVERLAY
+A74C; C; A74D; # LATIN CAPITAL LETTER O WITH LOOP
+A74E; C; A74F; # LATIN CAPITAL LETTER OO
+A750; C; A751; # LATIN CAPITAL LETTER P WITH STROKE THROUGH DESCENDER
+A752; C; A753; # LATIN CAPITAL LETTER P WITH FLOURISH
+A754; C; A755; # LATIN CAPITAL LETTER P WITH SQUIRREL TAIL
+A756; C; A757; # LATIN CAPITAL LETTER Q WITH STROKE THROUGH DESCENDER
+A758; C; A759; # LATIN CAPITAL LETTER Q WITH DIAGONAL STROKE
+A75A; C; A75B; # LATIN CAPITAL LETTER R ROTUNDA
+A75C; C; A75D; # LATIN CAPITAL LETTER RUM ROTUNDA
+A75E; C; A75F; # LATIN CAPITAL LETTER V WITH DIAGONAL STROKE
+A760; C; A761; # LATIN CAPITAL LETTER VY
+A762; C; A763; # LATIN CAPITAL LETTER VISIGOTHIC Z
+A764; C; A765; # LATIN CAPITAL LETTER THORN WITH STROKE
+A766; C; A767; # LATIN CAPITAL LETTER THORN WITH STROKE THROUGH DESCENDER
+A768; C; A769; # LATIN CAPITAL LETTER VEND
+A76A; C; A76B; # LATIN CAPITAL LETTER ET
+A76C; C; A76D; # LATIN CAPITAL LETTER IS
+A76E; C; A76F; # LATIN CAPITAL LETTER CON
+A779; C; A77A; # LATIN CAPITAL LETTER INSULAR D
+A77B; C; A77C; # LATIN CAPITAL LETTER INSULAR F
+A77D; C; 1D79; # LATIN CAPITAL LETTER INSULAR G
+A77E; C; A77F; # LATIN CAPITAL LETTER TURNED INSULAR G
+A780; C; A781; # LATIN CAPITAL LETTER TURNED L
+A782; C; A783; # LATIN CAPITAL LETTER INSULAR R
+A784; C; A785; # LATIN CAPITAL LETTER INSULAR S
+A786; C; A787; # LATIN CAPITAL LETTER INSULAR T
+A78B; C; A78C; # LATIN CAPITAL LETTER SALTILLO
+A78D; C; 0265; # LATIN CAPITAL LETTER TURNED H
+A790; C; A791; # LATIN CAPITAL LETTER N WITH DESCENDER
+A792; C; A793; # LATIN CAPITAL LETTER C WITH BAR
+A796; C; A797; # LATIN CAPITAL LETTER B WITH FLOURISH
+A798; C; A799; # LATIN CAPITAL LETTER F WITH STROKE
+A79A; C; A79B; # LATIN CAPITAL LETTER VOLAPUK AE
+A79C; C; A79D; # LATIN CAPITAL LETTER VOLAPUK OE
+A79E; C; A79F; # LATIN CAPITAL LETTER VOLAPUK UE
+A7A0; C; A7A1; # LATIN CAPITAL LETTER G WITH OBLIQUE STROKE
+A7A2; C; A7A3; # LATIN CAPITAL LETTER K WITH OBLIQUE STROKE
+A7A4; C; A7A5; # LATIN CAPITAL LETTER N WITH OBLIQUE STROKE
+A7A6; C; A7A7; # LATIN CAPITAL LETTER R WITH OBLIQUE STROKE
+A7A8; C; A7A9; # LATIN CAPITAL LETTER S WITH OBLIQUE STROKE
+A7AA; C; 0266; # LATIN CAPITAL LETTER H WITH HOOK
+A7AB; C; 025C; # LATIN CAPITAL LETTER REVERSED OPEN E
+A7AC; C; 0261; # LATIN CAPITAL LETTER SCRIPT G
+A7AD; C; 026C; # LATIN CAPITAL LETTER L WITH BELT
+A7AE; C; 026A; # LATIN CAPITAL LETTER SMALL CAPITAL I
+A7B0; C; 029E; # LATIN CAPITAL LETTER TURNED K
+A7B1; C; 0287; # LATIN CAPITAL LETTER TURNED T
+A7B2; C; 029D; # LATIN CAPITAL LETTER J WITH CROSSED-TAIL
+A7B3; C; AB53; # LATIN CAPITAL LETTER CHI
+A7B4; C; A7B5; # LATIN CAPITAL LETTER BETA
+A7B6; C; A7B7; # LATIN CAPITAL LETTER OMEGA
+A7B8; C; A7B9; # LATIN CAPITAL LETTER U WITH STROKE
+A7BA; C; A7BB; # LATIN CAPITAL LETTER GLOTTAL A
+A7BC; C; A7BD; # LATIN CAPITAL LETTER GLOTTAL I
+A7BE; C; A7BF; # LATIN CAPITAL LETTER GLOTTAL U
+A7C0; C; A7C1; # LATIN CAPITAL LETTER OLD POLISH O
+A7C2; C; A7C3; # LATIN CAPITAL LETTER ANGLICANA W
+A7C4; C; A794; # LATIN CAPITAL LETTER C WITH PALATAL HOOK
+A7C5; C; 0282; # LATIN CAPITAL LETTER S WITH HOOK
+A7C6; C; 1D8E; # LATIN CAPITAL LETTER Z WITH PALATAL HOOK
+A7C7; C; A7C8; # LATIN CAPITAL LETTER D WITH SHORT STROKE OVERLAY
+A7C9; C; A7CA; # LATIN CAPITAL LETTER S WITH SHORT STROKE OVERLAY
+A7D0; C; A7D1; # LATIN CAPITAL LETTER CLOSED INSULAR G
+A7D6; C; A7D7; # LATIN CAPITAL LETTER MIDDLE SCOTS S
+A7D8; C; A7D9; # LATIN CAPITAL LETTER SIGMOID S
+A7F5; C; A7F6; # LATIN CAPITAL LETTER REVERSED HALF H
+AB70; C; 13A0; # CHEROKEE SMALL LETTER A
+AB71; C; 13A1; # CHEROKEE SMALL LETTER E
+AB72; C; 13A2; # CHEROKEE SMALL LETTER I
+AB73; C; 13A3; # CHEROKEE SMALL LETTER O
+AB74; C; 13A4; # CHEROKEE SMALL LETTER U
+AB75; C; 13A5; # CHEROKEE SMALL LETTER V
+AB76; C; 13A6; # CHEROKEE SMALL LETTER GA
+AB77; C; 13A7; # CHEROKEE SMALL LETTER KA
+AB78; C; 13A8; # CHEROKEE SMALL LETTER GE
+AB79; C; 13A9; # CHEROKEE SMALL LETTER GI
+AB7A; C; 13AA; # CHEROKEE SMALL LETTER GO
+AB7B; C; 13AB; # CHEROKEE SMALL LETTER GU
+AB7C; C; 13AC; # CHEROKEE SMALL LETTER GV
+AB7D; C; 13AD; # CHEROKEE SMALL LETTER HA
+AB7E; C; 13AE; # CHEROKEE SMALL LETTER HE
+AB7F; C; 13AF; # CHEROKEE SMALL LETTER HI
+AB80; C; 13B0; # CHEROKEE SMALL LETTER HO
+AB81; C; 13B1; # CHEROKEE SMALL LETTER HU
+AB82; C; 13B2; # CHEROKEE SMALL LETTER HV
+AB83; C; 13B3; # CHEROKEE SMALL LETTER LA
+AB84; C; 13B4; # CHEROKEE SMALL LETTER LE
+AB85; C; 13B5; # CHEROKEE SMALL LETTER LI
+AB86; C; 13B6; # CHEROKEE SMALL LETTER LO
+AB87; C; 13B7; # CHEROKEE SMALL LETTER LU
+AB88; C; 13B8; # CHEROKEE SMALL LETTER LV
+AB89; C; 13B9; # CHEROKEE SMALL LETTER MA
+AB8A; C; 13BA; # CHEROKEE SMALL LETTER ME
+AB8B; C; 13BB; # CHEROKEE SMALL LETTER MI
+AB8C; C; 13BC; # CHEROKEE SMALL LETTER MO
+AB8D; C; 13BD; # CHEROKEE SMALL LETTER MU
+AB8E; C; 13BE; # CHEROKEE SMALL LETTER NA
+AB8F; C; 13BF; # CHEROKEE SMALL LETTER HNA
+AB90; C; 13C0; # CHEROKEE SMALL LETTER NAH
+AB91; C; 13C1; # CHEROKEE SMALL LETTER NE
+AB92; C; 13C2; # CHEROKEE SMALL LETTER NI
+AB93; C; 13C3; # CHEROKEE SMALL LETTER NO
+AB94; C; 13C4; # CHEROKEE SMALL LETTER NU
+AB95; C; 13C5; # CHEROKEE SMALL LETTER NV
+AB96; C; 13C6; # CHEROKEE SMALL LETTER QUA
+AB97; C; 13C7; # CHEROKEE SMALL LETTER QUE
+AB98; C; 13C8; # CHEROKEE SMALL LETTER QUI
+AB99; C; 13C9; # CHEROKEE SMALL LETTER QUO
+AB9A; C; 13CA; # CHEROKEE SMALL LETTER QUU
+AB9B; C; 13CB; # CHEROKEE SMALL LETTER QUV
+AB9C; C; 13CC; # CHEROKEE SMALL LETTER SA
+AB9D; C; 13CD; # CHEROKEE SMALL LETTER S
+AB9E; C; 13CE; # CHEROKEE SMALL LETTER SE
+AB9F; C; 13CF; # CHEROKEE SMALL LETTER SI
+ABA0; C; 13D0; # CHEROKEE SMALL LETTER SO
+ABA1; C; 13D1; # CHEROKEE SMALL LETTER SU
+ABA2; C; 13D2; # CHEROKEE SMALL LETTER SV
+ABA3; C; 13D3; # CHEROKEE SMALL LETTER DA
+ABA4; C; 13D4; # CHEROKEE SMALL LETTER TA
+ABA5; C; 13D5; # CHEROKEE SMALL LETTER DE
+ABA6; C; 13D6; # CHEROKEE SMALL LETTER TE
+ABA7; C; 13D7; # CHEROKEE SMALL LETTER DI
+ABA8; C; 13D8; # CHEROKEE SMALL LETTER TI
+ABA9; C; 13D9; # CHEROKEE SMALL LETTER DO
+ABAA; C; 13DA; # CHEROKEE SMALL LETTER DU
+ABAB; C; 13DB; # CHEROKEE SMALL LETTER DV
+ABAC; C; 13DC; # CHEROKEE SMALL LETTER DLA
+ABAD; C; 13DD; # CHEROKEE SMALL LETTER TLA
+ABAE; C; 13DE; # CHEROKEE SMALL LETTER TLE
+ABAF; C; 13DF; # CHEROKEE SMALL LETTER TLI
+ABB0; C; 13E0; # CHEROKEE SMALL LETTER TLO
+ABB1; C; 13E1; # CHEROKEE SMALL LETTER TLU
+ABB2; C; 13E2; # CHEROKEE SMALL LETTER TLV
+ABB3; C; 13E3; # CHEROKEE SMALL LETTER TSA
+ABB4; C; 13E4; # CHEROKEE SMALL LETTER TSE
+ABB5; C; 13E5; # CHEROKEE SMALL LETTER TSI
+ABB6; C; 13E6; # CHEROKEE SMALL LETTER TSO
+ABB7; C; 13E7; # CHEROKEE SMALL LETTER TSU
+ABB8; C; 13E8; # CHEROKEE SMALL LETTER TSV
+ABB9; C; 13E9; # CHEROKEE SMALL LETTER WA
+ABBA; C; 13EA; # CHEROKEE SMALL LETTER WE
+ABBB; C; 13EB; # CHEROKEE SMALL LETTER WI
+ABBC; C; 13EC; # CHEROKEE SMALL LETTER WO
+ABBD; C; 13ED; # CHEROKEE SMALL LETTER WU
+ABBE; C; 13EE; # CHEROKEE SMALL LETTER WV
+ABBF; C; 13EF; # CHEROKEE SMALL LETTER YA
+FB00; F; 0066 0066; # LATIN SMALL LIGATURE FF
+FB01; F; 0066 0069; # LATIN SMALL LIGATURE FI
+FB02; F; 0066 006C; # LATIN SMALL LIGATURE FL
+FB03; F; 0066 0066 0069; # LATIN SMALL LIGATURE FFI
+FB04; F; 0066 0066 006C; # LATIN SMALL LIGATURE FFL
+FB05; F; 0073 0074; # LATIN SMALL LIGATURE LONG S T
+FB06; F; 0073 0074; # LATIN SMALL LIGATURE ST
+FB13; F; 0574 0576; # ARMENIAN SMALL LIGATURE MEN NOW
+FB14; F; 0574 0565; # ARMENIAN SMALL LIGATURE MEN ECH
+FB15; F; 0574 056B; # ARMENIAN SMALL LIGATURE MEN INI
+FB16; F; 057E 0576; # ARMENIAN SMALL LIGATURE VEW NOW
+FB17; F; 0574 056D; # ARMENIAN SMALL LIGATURE MEN XEH
+FF21; C; FF41; # FULLWIDTH LATIN CAPITAL LETTER A
+FF22; C; FF42; # FULLWIDTH LATIN CAPITAL LETTER B
+FF23; C; FF43; # FULLWIDTH LATIN CAPITAL LETTER C
+FF24; C; FF44; # FULLWIDTH LATIN CAPITAL LETTER D
+FF25; C; FF45; # FULLWIDTH LATIN CAPITAL LETTER E
+FF26; C; FF46; # FULLWIDTH LATIN CAPITAL LETTER F
+FF27; C; FF47; # FULLWIDTH LATIN CAPITAL LETTER G
+FF28; C; FF48; # FULLWIDTH LATIN CAPITAL LETTER H
+FF29; C; FF49; # FULLWIDTH LATIN CAPITAL LETTER I
+FF2A; C; FF4A; # FULLWIDTH LATIN CAPITAL LETTER J
+FF2B; C; FF4B; # FULLWIDTH LATIN CAPITAL LETTER K
+FF2C; C; FF4C; # FULLWIDTH LATIN CAPITAL LETTER L
+FF2D; C; FF4D; # FULLWIDTH LATIN CAPITAL LETTER M
+FF2E; C; FF4E; # FULLWIDTH LATIN CAPITAL LETTER N
+FF2F; C; FF4F; # FULLWIDTH LATIN CAPITAL LETTER O
+FF30; C; FF50; # FULLWIDTH LATIN CAPITAL LETTER P
+FF31; C; FF51; # FULLWIDTH LATIN CAPITAL LETTER Q
+FF32; C; FF52; # FULLWIDTH LATIN CAPITAL LETTER R
+FF33; C; FF53; # FULLWIDTH LATIN CAPITAL LETTER S
+FF34; C; FF54; # FULLWIDTH LATIN CAPITAL LETTER T
+FF35; C; FF55; # FULLWIDTH LATIN CAPITAL LETTER U
+FF36; C; FF56; # FULLWIDTH LATIN CAPITAL LETTER V
+FF37; C; FF57; # FULLWIDTH LATIN CAPITAL LETTER W
+FF38; C; FF58; # FULLWIDTH LATIN CAPITAL LETTER X
+FF39; C; FF59; # FULLWIDTH LATIN CAPITAL LETTER Y
+FF3A; C; FF5A; # FULLWIDTH LATIN CAPITAL LETTER Z
+10400; C; 10428; # DESERET CAPITAL LETTER LONG I
+10401; C; 10429; # DESERET CAPITAL LETTER LONG E
+10402; C; 1042A; # DESERET CAPITAL LETTER LONG A
+10403; C; 1042B; # DESERET CAPITAL LETTER LONG AH
+10404; C; 1042C; # DESERET CAPITAL LETTER LONG O
+10405; C; 1042D; # DESERET CAPITAL LETTER LONG OO
+10406; C; 1042E; # DESERET CAPITAL LETTER SHORT I
+10407; C; 1042F; # DESERET CAPITAL LETTER SHORT E
+10408; C; 10430; # DESERET CAPITAL LETTER SHORT A
+10409; C; 10431; # DESERET CAPITAL LETTER SHORT AH
+1040A; C; 10432; # DESERET CAPITAL LETTER SHORT O
+1040B; C; 10433; # DESERET CAPITAL LETTER SHORT OO
+1040C; C; 10434; # DESERET CAPITAL LETTER AY
+1040D; C; 10435; # DESERET CAPITAL LETTER OW
+1040E; C; 10436; # DESERET CAPITAL LETTER WU
+1040F; C; 10437; # DESERET CAPITAL LETTER YEE
+10410; C; 10438; # DESERET CAPITAL LETTER H
+10411; C; 10439; # DESERET CAPITAL LETTER PEE
+10412; C; 1043A; # DESERET CAPITAL LETTER BEE
+10413; C; 1043B; # DESERET CAPITAL LETTER TEE
+10414; C; 1043C; # DESERET CAPITAL LETTER DEE
+10415; C; 1043D; # DESERET CAPITAL LETTER CHEE
+10416; C; 1043E; # DESERET CAPITAL LETTER JEE
+10417; C; 1043F; # DESERET CAPITAL LETTER KAY
+10418; C; 10440; # DESERET CAPITAL LETTER GAY
+10419; C; 10441; # DESERET CAPITAL LETTER EF
+1041A; C; 10442; # DESERET CAPITAL LETTER VEE
+1041B; C; 10443; # DESERET CAPITAL LETTER ETH
+1041C; C; 10444; # DESERET CAPITAL LETTER THEE
+1041D; C; 10445; # DESERET CAPITAL LETTER ES
+1041E; C; 10446; # DESERET CAPITAL LETTER ZEE
+1041F; C; 10447; # DESERET CAPITAL LETTER ESH
+10420; C; 10448; # DESERET CAPITAL LETTER ZHEE
+10421; C; 10449; # DESERET CAPITAL LETTER ER
+10422; C; 1044A; # DESERET CAPITAL LETTER EL
+10423; C; 1044B; # DESERET CAPITAL LETTER EM
+10424; C; 1044C; # DESERET CAPITAL LETTER EN
+10425; C; 1044D; # DESERET CAPITAL LETTER ENG
+10426; C; 1044E; # DESERET CAPITAL LETTER OI
+10427; C; 1044F; # DESERET CAPITAL LETTER EW
+104B0; C; 104D8; # OSAGE CAPITAL LETTER A
+104B1; C; 104D9; # OSAGE CAPITAL LETTER AI
+104B2; C; 104DA; # OSAGE CAPITAL LETTER AIN
+104B3; C; 104DB; # OSAGE CAPITAL LETTER AH
+104B4; C; 104DC; # OSAGE CAPITAL LETTER BRA
+104B5; C; 104DD; # OSAGE CAPITAL LETTER CHA
+104B6; C; 104DE; # OSAGE CAPITAL LETTER EHCHA
+104B7; C; 104DF; # OSAGE CAPITAL LETTER E
+104B8; C; 104E0; # OSAGE CAPITAL LETTER EIN
+104B9; C; 104E1; # OSAGE CAPITAL LETTER HA
+104BA; C; 104E2; # OSAGE CAPITAL LETTER HYA
+104BB; C; 104E3; # OSAGE CAPITAL LETTER I
+104BC; C; 104E4; # OSAGE CAPITAL LETTER KA
+104BD; C; 104E5; # OSAGE CAPITAL LETTER EHKA
+104BE; C; 104E6; # OSAGE CAPITAL LETTER KYA
+104BF; C; 104E7; # OSAGE CAPITAL LETTER LA
+104C0; C; 104E8; # OSAGE CAPITAL LETTER MA
+104C1; C; 104E9; # OSAGE CAPITAL LETTER NA
+104C2; C; 104EA; # OSAGE CAPITAL LETTER O
+104C3; C; 104EB; # OSAGE CAPITAL LETTER OIN
+104C4; C; 104EC; # OSAGE CAPITAL LETTER PA
+104C5; C; 104ED; # OSAGE CAPITAL LETTER EHPA
+104C6; C; 104EE; # OSAGE CAPITAL LETTER SA
+104C7; C; 104EF; # OSAGE CAPITAL LETTER SHA
+104C8; C; 104F0; # OSAGE CAPITAL LETTER TA
+104C9; C; 104F1; # OSAGE CAPITAL LETTER EHTA
+104CA; C; 104F2; # OSAGE CAPITAL LETTER TSA
+104CB; C; 104F3; # OSAGE CAPITAL LETTER EHTSA
+104CC; C; 104F4; # OSAGE CAPITAL LETTER TSHA
+104CD; C; 104F5; # OSAGE CAPITAL LETTER DHA
+104CE; C; 104F6; # OSAGE CAPITAL LETTER U
+104CF; C; 104F7; # OSAGE CAPITAL LETTER WA
+104D0; C; 104F8; # OSAGE CAPITAL LETTER KHA
+104D1; C; 104F9; # OSAGE CAPITAL LETTER GHA
+104D2; C; 104FA; # OSAGE CAPITAL LETTER ZA
+104D3; C; 104FB; # OSAGE CAPITAL LETTER ZHA
+10570; C; 10597; # VITHKUQI CAPITAL LETTER A
+10571; C; 10598; # VITHKUQI CAPITAL LETTER BBE
+10572; C; 10599; # VITHKUQI CAPITAL LETTER BE
+10573; C; 1059A; # VITHKUQI CAPITAL LETTER CE
+10574; C; 1059B; # VITHKUQI CAPITAL LETTER CHE
+10575; C; 1059C; # VITHKUQI CAPITAL LETTER DE
+10576; C; 1059D; # VITHKUQI CAPITAL LETTER DHE
+10577; C; 1059E; # VITHKUQI CAPITAL LETTER EI
+10578; C; 1059F; # VITHKUQI CAPITAL LETTER E
+10579; C; 105A0; # VITHKUQI CAPITAL LETTER FE
+1057A; C; 105A1; # VITHKUQI CAPITAL LETTER GA
+1057C; C; 105A3; # VITHKUQI CAPITAL LETTER HA
+1057D; C; 105A4; # VITHKUQI CAPITAL LETTER HHA
+1057E; C; 105A5; # VITHKUQI CAPITAL LETTER I
+1057F; C; 105A6; # VITHKUQI CAPITAL LETTER IJE
+10580; C; 105A7; # VITHKUQI CAPITAL LETTER JE
+10581; C; 105A8; # VITHKUQI CAPITAL LETTER KA
+10582; C; 105A9; # VITHKUQI CAPITAL LETTER LA
+10583; C; 105AA; # VITHKUQI CAPITAL LETTER LLA
+10584; C; 105AB; # VITHKUQI CAPITAL LETTER ME
+10585; C; 105AC; # VITHKUQI CAPITAL LETTER NE
+10586; C; 105AD; # VITHKUQI CAPITAL LETTER NJE
+10587; C; 105AE; # VITHKUQI CAPITAL LETTER O
+10588; C; 105AF; # VITHKUQI CAPITAL LETTER PE
+10589; C; 105B0; # VITHKUQI CAPITAL LETTER QA
+1058A; C; 105B1; # VITHKUQI CAPITAL LETTER RE
+1058C; C; 105B3; # VITHKUQI CAPITAL LETTER SE
+1058D; C; 105B4; # VITHKUQI CAPITAL LETTER SHE
+1058E; C; 105B5; # VITHKUQI CAPITAL LETTER TE
+1058F; C; 105B6; # VITHKUQI CAPITAL LETTER THE
+10590; C; 105B7; # VITHKUQI CAPITAL LETTER U
+10591; C; 105B8; # VITHKUQI CAPITAL LETTER VE
+10592; C; 105B9; # VITHKUQI CAPITAL LETTER XE
+10594; C; 105BB; # VITHKUQI CAPITAL LETTER Y
+10595; C; 105BC; # VITHKUQI CAPITAL LETTER ZE
+10C80; C; 10CC0; # OLD HUNGARIAN CAPITAL LETTER A
+10C81; C; 10CC1; # OLD HUNGARIAN CAPITAL LETTER AA
+10C82; C; 10CC2; # OLD HUNGARIAN CAPITAL LETTER EB
+10C83; C; 10CC3; # OLD HUNGARIAN CAPITAL LETTER AMB
+10C84; C; 10CC4; # OLD HUNGARIAN CAPITAL LETTER EC
+10C85; C; 10CC5; # OLD HUNGARIAN CAPITAL LETTER ENC
+10C86; C; 10CC6; # OLD HUNGARIAN CAPITAL LETTER ECS
+10C87; C; 10CC7; # OLD HUNGARIAN CAPITAL LETTER ED
+10C88; C; 10CC8; # OLD HUNGARIAN CAPITAL LETTER AND
+10C89; C; 10CC9; # OLD HUNGARIAN CAPITAL LETTER E
+10C8A; C; 10CCA; # OLD HUNGARIAN CAPITAL LETTER CLOSE E
+10C8B; C; 10CCB; # OLD HUNGARIAN CAPITAL LETTER EE
+10C8C; C; 10CCC; # OLD HUNGARIAN CAPITAL LETTER EF
+10C8D; C; 10CCD; # OLD HUNGARIAN CAPITAL LETTER EG
+10C8E; C; 10CCE; # OLD HUNGARIAN CAPITAL LETTER EGY
+10C8F; C; 10CCF; # OLD HUNGARIAN CAPITAL LETTER EH
+10C90; C; 10CD0; # OLD HUNGARIAN CAPITAL LETTER I
+10C91; C; 10CD1; # OLD HUNGARIAN CAPITAL LETTER II
+10C92; C; 10CD2; # OLD HUNGARIAN CAPITAL LETTER EJ
+10C93; C; 10CD3; # OLD HUNGARIAN CAPITAL LETTER EK
+10C94; C; 10CD4; # OLD HUNGARIAN CAPITAL LETTER AK
+10C95; C; 10CD5; # OLD HUNGARIAN CAPITAL LETTER UNK
+10C96; C; 10CD6; # OLD HUNGARIAN CAPITAL LETTER EL
+10C97; C; 10CD7; # OLD HUNGARIAN CAPITAL LETTER ELY
+10C98; C; 10CD8; # OLD HUNGARIAN CAPITAL LETTER EM
+10C99; C; 10CD9; # OLD HUNGARIAN CAPITAL LETTER EN
+10C9A; C; 10CDA; # OLD HUNGARIAN CAPITAL LETTER ENY
+10C9B; C; 10CDB; # OLD HUNGARIAN CAPITAL LETTER O
+10C9C; C; 10CDC; # OLD HUNGARIAN CAPITAL LETTER OO
+10C9D; C; 10CDD; # OLD HUNGARIAN CAPITAL LETTER NIKOLSBURG OE
+10C9E; C; 10CDE; # OLD HUNGARIAN CAPITAL LETTER RUDIMENTA OE
+10C9F; C; 10CDF; # OLD HUNGARIAN CAPITAL LETTER OEE
+10CA0; C; 10CE0; # OLD HUNGARIAN CAPITAL LETTER EP
+10CA1; C; 10CE1; # OLD HUNGARIAN CAPITAL LETTER EMP
+10CA2; C; 10CE2; # OLD HUNGARIAN CAPITAL LETTER ER
+10CA3; C; 10CE3; # OLD HUNGARIAN CAPITAL LETTER SHORT ER
+10CA4; C; 10CE4; # OLD HUNGARIAN CAPITAL LETTER ES
+10CA5; C; 10CE5; # OLD HUNGARIAN CAPITAL LETTER ESZ
+10CA6; C; 10CE6; # OLD HUNGARIAN CAPITAL LETTER ET
+10CA7; C; 10CE7; # OLD HUNGARIAN CAPITAL LETTER ENT
+10CA8; C; 10CE8; # OLD HUNGARIAN CAPITAL LETTER ETY
+10CA9; C; 10CE9; # OLD HUNGARIAN CAPITAL LETTER ECH
+10CAA; C; 10CEA; # OLD HUNGARIAN CAPITAL LETTER U
+10CAB; C; 10CEB; # OLD HUNGARIAN CAPITAL LETTER UU
+10CAC; C; 10CEC; # OLD HUNGARIAN CAPITAL LETTER NIKOLSBURG UE
+10CAD; C; 10CED; # OLD HUNGARIAN CAPITAL LETTER RUDIMENTA UE
+10CAE; C; 10CEE; # OLD HUNGARIAN CAPITAL LETTER EV
+10CAF; C; 10CEF; # OLD HUNGARIAN CAPITAL LETTER EZ
+10CB0; C; 10CF0; # OLD HUNGARIAN CAPITAL LETTER EZS
+10CB1; C; 10CF1; # OLD HUNGARIAN CAPITAL LETTER ENT-SHAPED SIGN
+10CB2; C; 10CF2; # OLD HUNGARIAN CAPITAL LETTER US
+118A0; C; 118C0; # WARANG CITI CAPITAL LETTER NGAA
+118A1; C; 118C1; # WARANG CITI CAPITAL LETTER A
+118A2; C; 118C2; # WARANG CITI CAPITAL LETTER WI
+118A3; C; 118C3; # WARANG CITI CAPITAL LETTER YU
+118A4; C; 118C4; # WARANG CITI CAPITAL LETTER YA
+118A5; C; 118C5; # WARANG CITI CAPITAL LETTER YO
+118A6; C; 118C6; # WARANG CITI CAPITAL LETTER II
+118A7; C; 118C7; # WARANG CITI CAPITAL LETTER UU
+118A8; C; 118C8; # WARANG CITI CAPITAL LETTER E
+118A9; C; 118C9; # WARANG CITI CAPITAL LETTER O
+118AA; C; 118CA; # WARANG CITI CAPITAL LETTER ANG
+118AB; C; 118CB; # WARANG CITI CAPITAL LETTER GA
+118AC; C; 118CC; # WARANG CITI CAPITAL LETTER KO
+118AD; C; 118CD; # WARANG CITI CAPITAL LETTER ENY
+118AE; C; 118CE; # WARANG CITI CAPITAL LETTER YUJ
+118AF; C; 118CF; # WARANG CITI CAPITAL LETTER UC
+118B0; C; 118D0; # WARANG CITI CAPITAL LETTER ENN
+118B1; C; 118D1; # WARANG CITI CAPITAL LETTER ODD
+118B2; C; 118D2; # WARANG CITI CAPITAL LETTER TTE
+118B3; C; 118D3; # WARANG CITI CAPITAL LETTER NUNG
+118B4; C; 118D4; # WARANG CITI CAPITAL LETTER DA
+118B5; C; 118D5; # WARANG CITI CAPITAL LETTER AT
+118B6; C; 118D6; # WARANG CITI CAPITAL LETTER AM
+118B7; C; 118D7; # WARANG CITI CAPITAL LETTER BU
+118B8; C; 118D8; # WARANG CITI CAPITAL LETTER PU
+118B9; C; 118D9; # WARANG CITI CAPITAL LETTER HIYO
+118BA; C; 118DA; # WARANG CITI CAPITAL LETTER HOLO
+118BB; C; 118DB; # WARANG CITI CAPITAL LETTER HORR
+118BC; C; 118DC; # WARANG CITI CAPITAL LETTER HAR
+118BD; C; 118DD; # WARANG CITI CAPITAL LETTER SSUU
+118BE; C; 118DE; # WARANG CITI CAPITAL LETTER SII
+118BF; C; 118DF; # WARANG CITI CAPITAL LETTER VIYO
+16E40; C; 16E60; # MEDEFAIDRIN CAPITAL LETTER M
+16E41; C; 16E61; # MEDEFAIDRIN CAPITAL LETTER S
+16E42; C; 16E62; # MEDEFAIDRIN CAPITAL LETTER V
+16E43; C; 16E63; # MEDEFAIDRIN CAPITAL LETTER W
+16E44; C; 16E64; # MEDEFAIDRIN CAPITAL LETTER ATIU
+16E45; C; 16E65; # MEDEFAIDRIN CAPITAL LETTER Z
+16E46; C; 16E66; # MEDEFAIDRIN CAPITAL LETTER KP
+16E47; C; 16E67; # MEDEFAIDRIN CAPITAL LETTER P
+16E48; C; 16E68; # MEDEFAIDRIN CAPITAL LETTER T
+16E49; C; 16E69; # MEDEFAIDRIN CAPITAL LETTER G
+16E4A; C; 16E6A; # MEDEFAIDRIN CAPITAL LETTER F
+16E4B; C; 16E6B; # MEDEFAIDRIN CAPITAL LETTER I
+16E4C; C; 16E6C; # MEDEFAIDRIN CAPITAL LETTER K
+16E4D; C; 16E6D; # MEDEFAIDRIN CAPITAL LETTER A
+16E4E; C; 16E6E; # MEDEFAIDRIN CAPITAL LETTER J
+16E4F; C; 16E6F; # MEDEFAIDRIN CAPITAL LETTER E
+16E50; C; 16E70; # MEDEFAIDRIN CAPITAL LETTER B
+16E51; C; 16E71; # MEDEFAIDRIN CAPITAL LETTER C
+16E52; C; 16E72; # MEDEFAIDRIN CAPITAL LETTER U
+16E53; C; 16E73; # MEDEFAIDRIN CAPITAL LETTER YU
+16E54; C; 16E74; # MEDEFAIDRIN CAPITAL LETTER L
+16E55; C; 16E75; # MEDEFAIDRIN CAPITAL LETTER Q
+16E56; C; 16E76; # MEDEFAIDRIN CAPITAL LETTER HP
+16E57; C; 16E77; # MEDEFAIDRIN CAPITAL LETTER NY
+16E58; C; 16E78; # MEDEFAIDRIN CAPITAL LETTER X
+16E59; C; 16E79; # MEDEFAIDRIN CAPITAL LETTER D
+16E5A; C; 16E7A; # MEDEFAIDRIN CAPITAL LETTER OE
+16E5B; C; 16E7B; # MEDEFAIDRIN CAPITAL LETTER N
+16E5C; C; 16E7C; # MEDEFAIDRIN CAPITAL LETTER R
+16E5D; C; 16E7D; # MEDEFAIDRIN CAPITAL LETTER O
+16E5E; C; 16E7E; # MEDEFAIDRIN CAPITAL LETTER AI
+16E5F; C; 16E7F; # MEDEFAIDRIN CAPITAL LETTER Y
+1E900; C; 1E922; # ADLAM CAPITAL LETTER ALIF
+1E901; C; 1E923; # ADLAM CAPITAL LETTER DAALI
+1E902; C; 1E924; # ADLAM CAPITAL LETTER LAAM
+1E903; C; 1E925; # ADLAM CAPITAL LETTER MIIM
+1E904; C; 1E926; # ADLAM CAPITAL LETTER BA
+1E905; C; 1E927; # ADLAM CAPITAL LETTER SINNYIIYHE
+1E906; C; 1E928; # ADLAM CAPITAL LETTER PE
+1E907; C; 1E929; # ADLAM CAPITAL LETTER BHE
+1E908; C; 1E92A; # ADLAM CAPITAL LETTER RA
+1E909; C; 1E92B; # ADLAM CAPITAL LETTER E
+1E90A; C; 1E92C; # ADLAM CAPITAL LETTER FA
+1E90B; C; 1E92D; # ADLAM CAPITAL LETTER I
+1E90C; C; 1E92E; # ADLAM CAPITAL LETTER O
+1E90D; C; 1E92F; # ADLAM CAPITAL LETTER DHA
+1E90E; C; 1E930; # ADLAM CAPITAL LETTER YHE
+1E90F; C; 1E931; # ADLAM CAPITAL LETTER WAW
+1E910; C; 1E932; # ADLAM CAPITAL LETTER NUN
+1E911; C; 1E933; # ADLAM CAPITAL LETTER KAF
+1E912; C; 1E934; # ADLAM CAPITAL LETTER YA
+1E913; C; 1E935; # ADLAM CAPITAL LETTER U
+1E914; C; 1E936; # ADLAM CAPITAL LETTER JIIM
+1E915; C; 1E937; # ADLAM CAPITAL LETTER CHI
+1E916; C; 1E938; # ADLAM CAPITAL LETTER HA
+1E917; C; 1E939; # ADLAM CAPITAL LETTER QAAF
+1E918; C; 1E93A; # ADLAM CAPITAL LETTER GA
+1E919; C; 1E93B; # ADLAM CAPITAL LETTER NYA
+1E91A; C; 1E93C; # ADLAM CAPITAL LETTER TU
+1E91B; C; 1E93D; # ADLAM CAPITAL LETTER NHA
+1E91C; C; 1E93E; # ADLAM CAPITAL LETTER VA
+1E91D; C; 1E93F; # ADLAM CAPITAL LETTER KHA
+1E91E; C; 1E940; # ADLAM CAPITAL LETTER GBE
+1E91F; C; 1E941; # ADLAM CAPITAL LETTER ZAL
+1E920; C; 1E942; # ADLAM CAPITAL LETTER KPO
+1E921; C; 1E943; # ADLAM CAPITAL LETTER SHA
+#
+# EOF
diff --git a/pkgs/markdown/tool/common_mark_stats.json b/pkgs/markdown/tool/common_mark_stats.json
new file mode 100644
index 0000000..2e0ba8f
--- /dev/null
+++ b/pkgs/markdown/tool/common_mark_stats.json
@@ -0,0 +1,706 @@
+{
+ "ATX headings": {
+ "62": "strict",
+ "63": "strict",
+ "64": "strict",
+ "65": "strict",
+ "66": "strict",
+ "67": "strict",
+ "68": "strict",
+ "69": "strict",
+ "70": "strict",
+ "71": "strict",
+ "72": "strict",
+ "73": "strict",
+ "74": "strict",
+ "75": "strict",
+ "76": "strict",
+ "77": "strict",
+ "78": "strict",
+ "79": "strict"
+ },
+ "Autolinks": {
+ "594": "strict",
+ "595": "strict",
+ "596": "strict",
+ "597": "strict",
+ "598": "strict",
+ "599": "strict",
+ "600": "strict",
+ "601": "strict",
+ "602": "strict",
+ "603": "strict",
+ "604": "strict",
+ "605": "strict",
+ "606": "strict",
+ "607": "strict",
+ "608": "strict",
+ "609": "strict",
+ "610": "strict",
+ "611": "strict",
+ "612": "strict"
+ },
+ "Backslash escapes": {
+ "12": "strict",
+ "13": "strict",
+ "14": "strict",
+ "15": "strict",
+ "16": "strict",
+ "17": "strict",
+ "18": "strict",
+ "19": "strict",
+ "20": "strict",
+ "21": "strict",
+ "22": "strict",
+ "23": "strict",
+ "24": "strict"
+ },
+ "Blank lines": {
+ "227": "strict"
+ },
+ "Block quotes": {
+ "228": "strict",
+ "229": "strict",
+ "230": "strict",
+ "231": "strict",
+ "232": "strict",
+ "233": "strict",
+ "234": "strict",
+ "235": "strict",
+ "236": "strict",
+ "237": "strict",
+ "238": "strict",
+ "239": "strict",
+ "240": "strict",
+ "241": "strict",
+ "242": "strict",
+ "243": "strict",
+ "244": "strict",
+ "245": "strict",
+ "246": "strict",
+ "247": "strict",
+ "248": "strict",
+ "249": "strict",
+ "250": "strict",
+ "251": "strict",
+ "252": "strict"
+ },
+ "Code spans": {
+ "328": "strict",
+ "329": "strict",
+ "330": "strict",
+ "331": "strict",
+ "332": "strict",
+ "333": "strict",
+ "334": "strict",
+ "335": "strict",
+ "336": "strict",
+ "337": "strict",
+ "338": "strict",
+ "339": "strict",
+ "340": "strict",
+ "341": "strict",
+ "342": "strict",
+ "343": "strict",
+ "344": "strict",
+ "345": "strict",
+ "346": "strict",
+ "347": "strict",
+ "348": "strict",
+ "349": "strict"
+ },
+ "Emphasis and strong emphasis": {
+ "350": "strict",
+ "351": "strict",
+ "352": "strict",
+ "353": "strict",
+ "354": "fail",
+ "355": "strict",
+ "356": "strict",
+ "357": "strict",
+ "358": "strict",
+ "359": "strict",
+ "360": "strict",
+ "361": "strict",
+ "362": "strict",
+ "363": "strict",
+ "364": "strict",
+ "365": "strict",
+ "366": "strict",
+ "367": "strict",
+ "368": "strict",
+ "369": "strict",
+ "370": "strict",
+ "371": "strict",
+ "372": "strict",
+ "373": "strict",
+ "374": "strict",
+ "375": "strict",
+ "376": "strict",
+ "377": "strict",
+ "378": "strict",
+ "379": "strict",
+ "380": "strict",
+ "381": "strict",
+ "382": "strict",
+ "383": "strict",
+ "384": "strict",
+ "385": "strict",
+ "386": "strict",
+ "387": "strict",
+ "388": "strict",
+ "389": "strict",
+ "390": "strict",
+ "391": "strict",
+ "392": "strict",
+ "393": "strict",
+ "394": "strict",
+ "395": "strict",
+ "396": "strict",
+ "397": "strict",
+ "398": "strict",
+ "399": "strict",
+ "400": "strict",
+ "401": "strict",
+ "402": "strict",
+ "403": "strict",
+ "404": "strict",
+ "405": "strict",
+ "406": "strict",
+ "407": "strict",
+ "408": "strict",
+ "409": "strict",
+ "410": "strict",
+ "411": "strict",
+ "412": "strict",
+ "413": "strict",
+ "414": "strict",
+ "415": "strict",
+ "416": "strict",
+ "417": "strict",
+ "418": "strict",
+ "419": "strict",
+ "420": "strict",
+ "421": "strict",
+ "422": "strict",
+ "423": "strict",
+ "424": "strict",
+ "425": "strict",
+ "426": "strict",
+ "427": "strict",
+ "428": "strict",
+ "429": "strict",
+ "430": "strict",
+ "431": "strict",
+ "432": "strict",
+ "433": "strict",
+ "434": "strict",
+ "435": "strict",
+ "436": "strict",
+ "437": "strict",
+ "438": "strict",
+ "439": "strict",
+ "440": "strict",
+ "441": "strict",
+ "442": "strict",
+ "443": "strict",
+ "444": "strict",
+ "445": "strict",
+ "446": "strict",
+ "447": "strict",
+ "448": "strict",
+ "449": "strict",
+ "450": "strict",
+ "451": "strict",
+ "452": "strict",
+ "453": "strict",
+ "454": "strict",
+ "455": "strict",
+ "456": "strict",
+ "457": "strict",
+ "458": "strict",
+ "459": "strict",
+ "460": "strict",
+ "461": "strict",
+ "462": "strict",
+ "463": "strict",
+ "464": "strict",
+ "465": "strict",
+ "466": "strict",
+ "467": "strict",
+ "468": "strict",
+ "469": "strict",
+ "470": "strict",
+ "471": "strict",
+ "472": "strict",
+ "473": "strict",
+ "474": "strict",
+ "475": "strict",
+ "476": "strict",
+ "477": "strict",
+ "478": "strict",
+ "479": "strict",
+ "480": "strict",
+ "481": "strict"
+ },
+ "Entity and numeric character references": {
+ "25": "loose",
+ "26": "strict",
+ "27": "strict",
+ "28": "strict",
+ "29": "strict",
+ "30": "strict",
+ "31": "strict",
+ "32": "strict",
+ "33": "strict",
+ "34": "strict",
+ "35": "strict",
+ "36": "strict",
+ "37": "strict",
+ "38": "strict",
+ "39": "strict",
+ "40": "loose",
+ "41": "strict"
+ },
+ "Fenced code blocks": {
+ "119": "strict",
+ "120": "strict",
+ "121": "strict",
+ "122": "strict",
+ "123": "strict",
+ "124": "strict",
+ "125": "strict",
+ "126": "strict",
+ "127": "strict",
+ "128": "strict",
+ "129": "strict",
+ "130": "strict",
+ "131": "strict",
+ "132": "strict",
+ "133": "strict",
+ "134": "strict",
+ "135": "strict",
+ "136": "strict",
+ "137": "strict",
+ "138": "strict",
+ "139": "strict",
+ "140": "strict",
+ "141": "strict",
+ "142": "strict",
+ "143": "strict",
+ "144": "strict",
+ "145": "strict",
+ "146": "strict",
+ "147": "strict"
+ },
+ "Hard line breaks": {
+ "633": "strict",
+ "634": "strict",
+ "635": "strict",
+ "636": "strict",
+ "637": "strict",
+ "638": "strict",
+ "639": "strict",
+ "640": "strict",
+ "641": "strict",
+ "642": "strict",
+ "643": "strict",
+ "644": "strict",
+ "645": "strict",
+ "646": "strict",
+ "647": "strict"
+ },
+ "HTML blocks": {
+ "148": "strict",
+ "149": "strict",
+ "150": "strict",
+ "151": "strict",
+ "152": "strict",
+ "153": "strict",
+ "154": "strict",
+ "155": "strict",
+ "156": "strict",
+ "157": "strict",
+ "158": "strict",
+ "159": "strict",
+ "160": "strict",
+ "161": "strict",
+ "162": "strict",
+ "163": "strict",
+ "164": "strict",
+ "165": "strict",
+ "166": "strict",
+ "167": "strict",
+ "168": "strict",
+ "169": "strict",
+ "170": "strict",
+ "171": "strict",
+ "172": "strict",
+ "173": "strict",
+ "174": "strict",
+ "175": "strict",
+ "176": "strict",
+ "177": "strict",
+ "178": "strict",
+ "179": "strict",
+ "180": "strict",
+ "181": "strict",
+ "182": "strict",
+ "183": "strict",
+ "184": "strict",
+ "185": "strict",
+ "186": "strict",
+ "187": "strict",
+ "188": "strict",
+ "189": "strict",
+ "190": "strict",
+ "191": "strict"
+ },
+ "Images": {
+ "572": "strict",
+ "573": "strict",
+ "574": "strict",
+ "575": "strict",
+ "576": "strict",
+ "577": "strict",
+ "578": "strict",
+ "579": "strict",
+ "580": "strict",
+ "581": "strict",
+ "582": "strict",
+ "583": "strict",
+ "584": "strict",
+ "585": "strict",
+ "586": "strict",
+ "587": "strict",
+ "588": "strict",
+ "589": "strict",
+ "590": "strict",
+ "591": "strict",
+ "592": "strict",
+ "593": "strict"
+ },
+ "Indented code blocks": {
+ "107": "strict",
+ "108": "strict",
+ "109": "strict",
+ "110": "strict",
+ "111": "strict",
+ "112": "strict",
+ "113": "strict",
+ "114": "strict",
+ "115": "strict",
+ "116": "strict",
+ "117": "strict",
+ "118": "strict"
+ },
+ "Inlines": {
+ "327": "strict"
+ },
+ "Link reference definitions": {
+ "192": "strict",
+ "193": "strict",
+ "194": "strict",
+ "195": "strict",
+ "196": "strict",
+ "197": "strict",
+ "198": "strict",
+ "199": "strict",
+ "200": "strict",
+ "201": "strict",
+ "202": "strict",
+ "203": "strict",
+ "204": "strict",
+ "205": "strict",
+ "206": "strict",
+ "207": "loose",
+ "208": "strict",
+ "209": "strict",
+ "210": "strict",
+ "211": "strict",
+ "212": "strict",
+ "213": "strict",
+ "214": "strict",
+ "215": "strict",
+ "216": "strict",
+ "217": "strict",
+ "218": "strict"
+ },
+ "Links": {
+ "482": "strict",
+ "483": "strict",
+ "484": "strict",
+ "485": "strict",
+ "486": "strict",
+ "487": "strict",
+ "488": "strict",
+ "489": "strict",
+ "490": "strict",
+ "491": "strict",
+ "492": "strict",
+ "493": "strict",
+ "494": "strict",
+ "495": "strict",
+ "496": "strict",
+ "497": "strict",
+ "498": "strict",
+ "499": "strict",
+ "500": "strict",
+ "501": "strict",
+ "502": "strict",
+ "503": "strict",
+ "504": "strict",
+ "505": "strict",
+ "506": "strict",
+ "507": "strict",
+ "508": "strict",
+ "509": "strict",
+ "510": "strict",
+ "511": "strict",
+ "512": "strict",
+ "513": "strict",
+ "514": "strict",
+ "515": "strict",
+ "516": "strict",
+ "517": "strict",
+ "518": "strict",
+ "519": "strict",
+ "520": "strict",
+ "521": "strict",
+ "522": "strict",
+ "523": "strict",
+ "524": "strict",
+ "525": "strict",
+ "526": "strict",
+ "527": "strict",
+ "528": "strict",
+ "529": "strict",
+ "530": "strict",
+ "531": "strict",
+ "532": "strict",
+ "533": "strict",
+ "534": "strict",
+ "535": "strict",
+ "536": "strict",
+ "537": "strict",
+ "538": "strict",
+ "539": "strict",
+ "540": "strict",
+ "541": "strict",
+ "542": "strict",
+ "543": "strict",
+ "544": "strict",
+ "545": "strict",
+ "546": "strict",
+ "547": "strict",
+ "548": "strict",
+ "549": "strict",
+ "550": "strict",
+ "551": "strict",
+ "552": "strict",
+ "553": "strict",
+ "554": "strict",
+ "555": "strict",
+ "556": "strict",
+ "557": "strict",
+ "558": "strict",
+ "559": "strict",
+ "560": "strict",
+ "561": "strict",
+ "562": "strict",
+ "563": "strict",
+ "564": "strict",
+ "565": "strict",
+ "566": "strict",
+ "567": "strict",
+ "568": "strict",
+ "569": "strict",
+ "570": "strict",
+ "571": "strict"
+ },
+ "List items": {
+ "253": "strict",
+ "254": "strict",
+ "255": "strict",
+ "256": "strict",
+ "257": "strict",
+ "258": "strict",
+ "259": "strict",
+ "260": "strict",
+ "261": "strict",
+ "262": "strict",
+ "263": "strict",
+ "264": "strict",
+ "265": "strict",
+ "266": "strict",
+ "267": "strict",
+ "268": "strict",
+ "269": "strict",
+ "270": "strict",
+ "271": "strict",
+ "272": "strict",
+ "273": "strict",
+ "274": "strict",
+ "275": "strict",
+ "276": "strict",
+ "277": "strict",
+ "278": "strict",
+ "279": "strict",
+ "280": "strict",
+ "281": "strict",
+ "282": "strict",
+ "283": "strict",
+ "284": "strict",
+ "285": "strict",
+ "286": "strict",
+ "287": "strict",
+ "288": "strict",
+ "289": "strict",
+ "290": "strict",
+ "291": "strict",
+ "292": "strict",
+ "293": "strict",
+ "294": "strict",
+ "295": "strict",
+ "296": "strict",
+ "297": "strict",
+ "298": "strict",
+ "299": "strict",
+ "300": "strict"
+ },
+ "Lists": {
+ "301": "strict",
+ "302": "strict",
+ "303": "strict",
+ "304": "strict",
+ "305": "strict",
+ "306": "strict",
+ "307": "strict",
+ "308": "strict",
+ "309": "strict",
+ "310": "strict",
+ "311": "strict",
+ "312": "strict",
+ "313": "strict",
+ "314": "strict",
+ "315": "strict",
+ "316": "strict",
+ "317": "strict",
+ "318": "strict",
+ "319": "strict",
+ "320": "strict",
+ "321": "strict",
+ "322": "strict",
+ "323": "strict",
+ "324": "strict",
+ "325": "strict",
+ "326": "strict"
+ },
+ "Paragraphs": {
+ "219": "strict",
+ "220": "strict",
+ "221": "strict",
+ "222": "strict",
+ "223": "strict",
+ "224": "strict",
+ "225": "strict",
+ "226": "strict"
+ },
+ "Precedence": {
+ "42": "strict"
+ },
+ "Raw HTML": {
+ "613": "strict",
+ "614": "strict",
+ "615": "strict",
+ "616": "strict",
+ "617": "strict",
+ "618": "strict",
+ "619": "strict",
+ "620": "strict",
+ "621": "strict",
+ "622": "strict",
+ "623": "strict",
+ "624": "strict",
+ "625": "loose",
+ "626": "loose",
+ "627": "strict",
+ "628": "strict",
+ "629": "strict",
+ "630": "strict",
+ "631": "strict",
+ "632": "strict"
+ },
+ "Setext headings": {
+ "80": "strict",
+ "81": "strict",
+ "82": "loose",
+ "83": "strict",
+ "84": "loose",
+ "85": "strict",
+ "86": "strict",
+ "87": "strict",
+ "88": "strict",
+ "89": "strict",
+ "90": "strict",
+ "91": "strict",
+ "92": "strict",
+ "93": "strict",
+ "94": "strict",
+ "95": "strict",
+ "96": "strict",
+ "97": "strict",
+ "98": "strict",
+ "99": "strict",
+ "100": "strict",
+ "101": "strict",
+ "102": "strict",
+ "103": "strict",
+ "104": "strict",
+ "105": "strict",
+ "106": "strict"
+ },
+ "Soft line breaks": {
+ "648": "strict",
+ "649": "strict"
+ },
+ "Tabs": {
+ "1": "strict",
+ "2": "strict",
+ "3": "strict",
+ "4": "strict",
+ "5": "strict",
+ "6": "loose",
+ "7": "strict",
+ "8": "strict",
+ "9": "strict",
+ "10": "strict",
+ "11": "strict"
+ },
+ "Textual content": {
+ "650": "strict",
+ "651": "strict",
+ "652": "strict"
+ },
+ "Thematic breaks": {
+ "43": "strict",
+ "44": "strict",
+ "45": "strict",
+ "46": "strict",
+ "47": "strict",
+ "48": "strict",
+ "49": "strict",
+ "50": "strict",
+ "51": "strict",
+ "52": "strict",
+ "53": "strict",
+ "54": "strict",
+ "55": "strict",
+ "56": "strict",
+ "57": "strict",
+ "58": "strict",
+ "59": "strict",
+ "60": "strict",
+ "61": "strict"
+ }
+}
diff --git a/pkgs/markdown/tool/common_mark_stats.txt b/pkgs/markdown/tool/common_mark_stats.txt
new file mode 100644
index 0000000..db3b891
--- /dev/null
+++ b/pkgs/markdown/tool/common_mark_stats.txt
@@ -0,0 +1,28 @@
+ 18 of 18 – 100.0% ATX headings
+ 19 of 19 – 100.0% Autolinks
+ 13 of 13 – 100.0% Backslash escapes
+ 1 of 1 – 100.0% Blank lines
+ 25 of 25 – 100.0% Block quotes
+ 22 of 22 – 100.0% Code spans
+ 131 of 132 – 99.2% Emphasis and strong emphasis
+ 17 of 17 – 100.0% Entity and numeric character references
+ 29 of 29 – 100.0% Fenced code blocks
+ 15 of 15 – 100.0% Hard line breaks
+ 44 of 44 – 100.0% HTML blocks
+ 22 of 22 – 100.0% Images
+ 12 of 12 – 100.0% Indented code blocks
+ 1 of 1 – 100.0% Inlines
+ 27 of 27 – 100.0% Link reference definitions
+ 90 of 90 – 100.0% Links
+ 48 of 48 – 100.0% List items
+ 26 of 26 – 100.0% Lists
+ 8 of 8 – 100.0% Paragraphs
+ 1 of 1 – 100.0% Precedence
+ 20 of 20 – 100.0% Raw HTML
+ 27 of 27 – 100.0% Setext headings
+ 2 of 2 – 100.0% Soft line breaks
+ 11 of 11 – 100.0% Tabs
+ 3 of 3 – 100.0% Textual content
+ 19 of 19 – 100.0% Thematic breaks
+ 651 of 652 – 99.8% TOTAL
+ 643 of 651 – 98.8% TOTAL Strict
diff --git a/pkgs/markdown/tool/common_mark_tests.json b/pkgs/markdown/tool/common_mark_tests.json
new file mode 100644
index 0000000..1f89e66
--- /dev/null
+++ b/pkgs/markdown/tool/common_mark_tests.json
@@ -0,0 +1,5218 @@
+[
+ {
+ "markdown": "\tfoo\tbaz\t\tbim\n",
+ "html": "<pre><code>foo\tbaz\t\tbim\n</code></pre>\n",
+ "example": 1,
+ "start_line": 355,
+ "end_line": 360,
+ "section": "Tabs"
+ },
+ {
+ "markdown": " \tfoo\tbaz\t\tbim\n",
+ "html": "<pre><code>foo\tbaz\t\tbim\n</code></pre>\n",
+ "example": 2,
+ "start_line": 362,
+ "end_line": 367,
+ "section": "Tabs"
+ },
+ {
+ "markdown": " a\ta\n ὐ\ta\n",
+ "html": "<pre><code>a\ta\nὐ\ta\n</code></pre>\n",
+ "example": 3,
+ "start_line": 369,
+ "end_line": 376,
+ "section": "Tabs"
+ },
+ {
+ "markdown": " - foo\n\n\tbar\n",
+ "html": "<ul>\n<li>\n<p>foo</p>\n<p>bar</p>\n</li>\n</ul>\n",
+ "example": 4,
+ "start_line": 382,
+ "end_line": 393,
+ "section": "Tabs"
+ },
+ {
+ "markdown": "- foo\n\n\t\tbar\n",
+ "html": "<ul>\n<li>\n<p>foo</p>\n<pre><code> bar\n</code></pre>\n</li>\n</ul>\n",
+ "example": 5,
+ "start_line": 395,
+ "end_line": 407,
+ "section": "Tabs"
+ },
+ {
+ "markdown": ">\t\tfoo\n",
+ "html": "<blockquote>\n<pre><code> foo\n</code></pre>\n</blockquote>\n",
+ "example": 6,
+ "start_line": 418,
+ "end_line": 425,
+ "section": "Tabs"
+ },
+ {
+ "markdown": "-\t\tfoo\n",
+ "html": "<ul>\n<li>\n<pre><code> foo\n</code></pre>\n</li>\n</ul>\n",
+ "example": 7,
+ "start_line": 427,
+ "end_line": 436,
+ "section": "Tabs"
+ },
+ {
+ "markdown": " foo\n\tbar\n",
+ "html": "<pre><code>foo\nbar\n</code></pre>\n",
+ "example": 8,
+ "start_line": 439,
+ "end_line": 446,
+ "section": "Tabs"
+ },
+ {
+ "markdown": " - foo\n - bar\n\t - baz\n",
+ "html": "<ul>\n<li>foo\n<ul>\n<li>bar\n<ul>\n<li>baz</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n",
+ "example": 9,
+ "start_line": 448,
+ "end_line": 464,
+ "section": "Tabs"
+ },
+ {
+ "markdown": "#\tFoo\n",
+ "html": "<h1>Foo</h1>\n",
+ "example": 10,
+ "start_line": 466,
+ "end_line": 470,
+ "section": "Tabs"
+ },
+ {
+ "markdown": "*\t*\t*\t\n",
+ "html": "<hr />\n",
+ "example": 11,
+ "start_line": 472,
+ "end_line": 476,
+ "section": "Tabs"
+ },
+ {
+ "markdown": "\\!\\\"\\#\\$\\%\\&\\'\\(\\)\\*\\+\\,\\-\\.\\/\\:\\;\\<\\=\\>\\?\\@\\[\\\\\\]\\^\\_\\`\\{\\|\\}\\~\n",
+ "html": "<p>!"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~</p>\n",
+ "example": 12,
+ "start_line": 489,
+ "end_line": 493,
+ "section": "Backslash escapes"
+ },
+ {
+ "markdown": "\\\t\\A\\a\\ \\3\\φ\\«\n",
+ "html": "<p>\\\t\\A\\a\\ \\3\\φ\\«</p>\n",
+ "example": 13,
+ "start_line": 499,
+ "end_line": 503,
+ "section": "Backslash escapes"
+ },
+ {
+ "markdown": "\\*not emphasized*\n\\<br/> not a tag\n\\[not a link](/foo)\n\\`not code`\n1\\. not a list\n\\* not a list\n\\# not a heading\n\\[foo]: /url \"not a reference\"\n\\ö not a character entity\n",
+ "html": "<p>*not emphasized*\n<br/> not a tag\n[not a link](/foo)\n`not code`\n1. not a list\n* not a list\n# not a heading\n[foo]: /url "not a reference"\n&ouml; not a character entity</p>\n",
+ "example": 14,
+ "start_line": 509,
+ "end_line": 529,
+ "section": "Backslash escapes"
+ },
+ {
+ "markdown": "\\\\*emphasis*\n",
+ "html": "<p>\\<em>emphasis</em></p>\n",
+ "example": 15,
+ "start_line": 534,
+ "end_line": 538,
+ "section": "Backslash escapes"
+ },
+ {
+ "markdown": "foo\\\nbar\n",
+ "html": "<p>foo<br />\nbar</p>\n",
+ "example": 16,
+ "start_line": 543,
+ "end_line": 549,
+ "section": "Backslash escapes"
+ },
+ {
+ "markdown": "`` \\[\\` ``\n",
+ "html": "<p><code>\\[\\`</code></p>\n",
+ "example": 17,
+ "start_line": 555,
+ "end_line": 559,
+ "section": "Backslash escapes"
+ },
+ {
+ "markdown": " \\[\\]\n",
+ "html": "<pre><code>\\[\\]\n</code></pre>\n",
+ "example": 18,
+ "start_line": 562,
+ "end_line": 567,
+ "section": "Backslash escapes"
+ },
+ {
+ "markdown": "~~~\n\\[\\]\n~~~\n",
+ "html": "<pre><code>\\[\\]\n</code></pre>\n",
+ "example": 19,
+ "start_line": 570,
+ "end_line": 577,
+ "section": "Backslash escapes"
+ },
+ {
+ "markdown": "<https://example.com?find=\\*>\n",
+ "html": "<p><a href=\"https://example.com?find=%5C*\">https://example.com?find=\\*</a></p>\n",
+ "example": 20,
+ "start_line": 580,
+ "end_line": 584,
+ "section": "Backslash escapes"
+ },
+ {
+ "markdown": "<a href=\"/bar\\/)\">\n",
+ "html": "<a href=\"/bar\\/)\">\n",
+ "example": 21,
+ "start_line": 587,
+ "end_line": 591,
+ "section": "Backslash escapes"
+ },
+ {
+ "markdown": "[foo](/bar\\* \"ti\\*tle\")\n",
+ "html": "<p><a href=\"/bar*\" title=\"ti*tle\">foo</a></p>\n",
+ "example": 22,
+ "start_line": 597,
+ "end_line": 601,
+ "section": "Backslash escapes"
+ },
+ {
+ "markdown": "[foo]\n\n[foo]: /bar\\* \"ti\\*tle\"\n",
+ "html": "<p><a href=\"/bar*\" title=\"ti*tle\">foo</a></p>\n",
+ "example": 23,
+ "start_line": 604,
+ "end_line": 610,
+ "section": "Backslash escapes"
+ },
+ {
+ "markdown": "``` foo\\+bar\nfoo\n```\n",
+ "html": "<pre><code class=\"language-foo+bar\">foo\n</code></pre>\n",
+ "example": 24,
+ "start_line": 613,
+ "end_line": 620,
+ "section": "Backslash escapes"
+ },
+ {
+ "markdown": " & © Æ Ď\n¾ ℋ ⅆ\n∲ ≧̸\n",
+ "html": "<p> & © Æ Ď\n¾ ℋ ⅆ\n∲ ≧̸</p>\n",
+ "example": 25,
+ "start_line": 649,
+ "end_line": 657,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "# Ӓ Ϡ �\n",
+ "html": "<p># Ӓ Ϡ �</p>\n",
+ "example": 26,
+ "start_line": 668,
+ "end_line": 672,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "" ആ ಫ\n",
+ "html": "<p>" ആ ಫ</p>\n",
+ "example": 27,
+ "start_line": 681,
+ "end_line": 685,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "  &x; &#; &#x;\n�\n&#abcdef0;\n&ThisIsNotDefined; &hi?;\n",
+ "html": "<p>&nbsp &x; &#; &#x;\n&#87654321;\n&#abcdef0;\n&ThisIsNotDefined; &hi?;</p>\n",
+ "example": 28,
+ "start_line": 690,
+ "end_line": 700,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "©\n",
+ "html": "<p>&copy</p>\n",
+ "example": 29,
+ "start_line": 707,
+ "end_line": 711,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "&MadeUpEntity;\n",
+ "html": "<p>&MadeUpEntity;</p>\n",
+ "example": 30,
+ "start_line": 717,
+ "end_line": 721,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "<a href=\"öö.html\">\n",
+ "html": "<a href=\"öö.html\">\n",
+ "example": 31,
+ "start_line": 728,
+ "end_line": 732,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "[foo](/föö \"föö\")\n",
+ "html": "<p><a href=\"/f%C3%B6%C3%B6\" title=\"föö\">foo</a></p>\n",
+ "example": 32,
+ "start_line": 735,
+ "end_line": 739,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "[foo]\n\n[foo]: /föö \"föö\"\n",
+ "html": "<p><a href=\"/f%C3%B6%C3%B6\" title=\"föö\">foo</a></p>\n",
+ "example": 33,
+ "start_line": 742,
+ "end_line": 748,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "``` föö\nfoo\n```\n",
+ "html": "<pre><code class=\"language-föö\">foo\n</code></pre>\n",
+ "example": 34,
+ "start_line": 751,
+ "end_line": 758,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "`föö`\n",
+ "html": "<p><code>f&ouml;&ouml;</code></p>\n",
+ "example": 35,
+ "start_line": 764,
+ "end_line": 768,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": " föfö\n",
+ "html": "<pre><code>f&ouml;f&ouml;\n</code></pre>\n",
+ "example": 36,
+ "start_line": 771,
+ "end_line": 776,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "*foo*\n*foo*\n",
+ "html": "<p>*foo*\n<em>foo</em></p>\n",
+ "example": 37,
+ "start_line": 783,
+ "end_line": 789,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "* foo\n\n* foo\n",
+ "html": "<p>* foo</p>\n<ul>\n<li>foo</li>\n</ul>\n",
+ "example": 38,
+ "start_line": 791,
+ "end_line": 800,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "foo bar\n",
+ "html": "<p>foo\n\nbar</p>\n",
+ "example": 39,
+ "start_line": 802,
+ "end_line": 808,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "	foo\n",
+ "html": "<p>\tfoo</p>\n",
+ "example": 40,
+ "start_line": 810,
+ "end_line": 814,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "[a](url "tit")\n",
+ "html": "<p>[a](url "tit")</p>\n",
+ "example": 41,
+ "start_line": 817,
+ "end_line": 821,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "- `one\n- two`\n",
+ "html": "<ul>\n<li>`one</li>\n<li>two`</li>\n</ul>\n",
+ "example": 42,
+ "start_line": 840,
+ "end_line": 848,
+ "section": "Precedence"
+ },
+ {
+ "markdown": "***\n---\n___\n",
+ "html": "<hr />\n<hr />\n<hr />\n",
+ "example": 43,
+ "start_line": 879,
+ "end_line": 887,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": "+++\n",
+ "html": "<p>+++</p>\n",
+ "example": 44,
+ "start_line": 892,
+ "end_line": 896,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": "===\n",
+ "html": "<p>===</p>\n",
+ "example": 45,
+ "start_line": 899,
+ "end_line": 903,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": "--\n**\n__\n",
+ "html": "<p>--\n**\n__</p>\n",
+ "example": 46,
+ "start_line": 908,
+ "end_line": 916,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": " ***\n ***\n ***\n",
+ "html": "<hr />\n<hr />\n<hr />\n",
+ "example": 47,
+ "start_line": 921,
+ "end_line": 929,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": " ***\n",
+ "html": "<pre><code>***\n</code></pre>\n",
+ "example": 48,
+ "start_line": 934,
+ "end_line": 939,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": "Foo\n ***\n",
+ "html": "<p>Foo\n***</p>\n",
+ "example": 49,
+ "start_line": 942,
+ "end_line": 948,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": "_____________________________________\n",
+ "html": "<hr />\n",
+ "example": 50,
+ "start_line": 953,
+ "end_line": 957,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": " - - -\n",
+ "html": "<hr />\n",
+ "example": 51,
+ "start_line": 962,
+ "end_line": 966,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": " ** * ** * ** * **\n",
+ "html": "<hr />\n",
+ "example": 52,
+ "start_line": 969,
+ "end_line": 973,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": "- - - -\n",
+ "html": "<hr />\n",
+ "example": 53,
+ "start_line": 976,
+ "end_line": 980,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": "- - - - \n",
+ "html": "<hr />\n",
+ "example": 54,
+ "start_line": 985,
+ "end_line": 989,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": "_ _ _ _ a\n\na------\n\n---a---\n",
+ "html": "<p>_ _ _ _ a</p>\n<p>a------</p>\n<p>---a---</p>\n",
+ "example": 55,
+ "start_line": 994,
+ "end_line": 1004,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": " *-*\n",
+ "html": "<p><em>-</em></p>\n",
+ "example": 56,
+ "start_line": 1010,
+ "end_line": 1014,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": "- foo\n***\n- bar\n",
+ "html": "<ul>\n<li>foo</li>\n</ul>\n<hr />\n<ul>\n<li>bar</li>\n</ul>\n",
+ "example": 57,
+ "start_line": 1019,
+ "end_line": 1031,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": "Foo\n***\nbar\n",
+ "html": "<p>Foo</p>\n<hr />\n<p>bar</p>\n",
+ "example": 58,
+ "start_line": 1036,
+ "end_line": 1044,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": "Foo\n---\nbar\n",
+ "html": "<h2>Foo</h2>\n<p>bar</p>\n",
+ "example": 59,
+ "start_line": 1053,
+ "end_line": 1060,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": "* Foo\n* * *\n* Bar\n",
+ "html": "<ul>\n<li>Foo</li>\n</ul>\n<hr />\n<ul>\n<li>Bar</li>\n</ul>\n",
+ "example": 60,
+ "start_line": 1066,
+ "end_line": 1078,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": "- Foo\n- * * *\n",
+ "html": "<ul>\n<li>Foo</li>\n<li>\n<hr />\n</li>\n</ul>\n",
+ "example": 61,
+ "start_line": 1083,
+ "end_line": 1093,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": "# foo\n## foo\n### foo\n#### foo\n##### foo\n###### foo\n",
+ "html": "<h1>foo</h1>\n<h2>foo</h2>\n<h3>foo</h3>\n<h4>foo</h4>\n<h5>foo</h5>\n<h6>foo</h6>\n",
+ "example": 62,
+ "start_line": 1112,
+ "end_line": 1126,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "####### foo\n",
+ "html": "<p>####### foo</p>\n",
+ "example": 63,
+ "start_line": 1131,
+ "end_line": 1135,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "#5 bolt\n\n#hashtag\n",
+ "html": "<p>#5 bolt</p>\n<p>#hashtag</p>\n",
+ "example": 64,
+ "start_line": 1146,
+ "end_line": 1153,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "\\## foo\n",
+ "html": "<p>## foo</p>\n",
+ "example": 65,
+ "start_line": 1158,
+ "end_line": 1162,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "# foo *bar* \\*baz\\*\n",
+ "html": "<h1>foo <em>bar</em> *baz*</h1>\n",
+ "example": 66,
+ "start_line": 1167,
+ "end_line": 1171,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "# foo \n",
+ "html": "<h1>foo</h1>\n",
+ "example": 67,
+ "start_line": 1176,
+ "end_line": 1180,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": " ### foo\n ## foo\n # foo\n",
+ "html": "<h3>foo</h3>\n<h2>foo</h2>\n<h1>foo</h1>\n",
+ "example": 68,
+ "start_line": 1185,
+ "end_line": 1193,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": " # foo\n",
+ "html": "<pre><code># foo\n</code></pre>\n",
+ "example": 69,
+ "start_line": 1198,
+ "end_line": 1203,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "foo\n # bar\n",
+ "html": "<p>foo\n# bar</p>\n",
+ "example": 70,
+ "start_line": 1206,
+ "end_line": 1212,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "## foo ##\n ### bar ###\n",
+ "html": "<h2>foo</h2>\n<h3>bar</h3>\n",
+ "example": 71,
+ "start_line": 1217,
+ "end_line": 1223,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "# foo ##################################\n##### foo ##\n",
+ "html": "<h1>foo</h1>\n<h5>foo</h5>\n",
+ "example": 72,
+ "start_line": 1228,
+ "end_line": 1234,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "### foo ### \n",
+ "html": "<h3>foo</h3>\n",
+ "example": 73,
+ "start_line": 1239,
+ "end_line": 1243,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "### foo ### b\n",
+ "html": "<h3>foo ### b</h3>\n",
+ "example": 74,
+ "start_line": 1250,
+ "end_line": 1254,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "# foo#\n",
+ "html": "<h1>foo#</h1>\n",
+ "example": 75,
+ "start_line": 1259,
+ "end_line": 1263,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "### foo \\###\n## foo #\\##\n# foo \\#\n",
+ "html": "<h3>foo ###</h3>\n<h2>foo ###</h2>\n<h1>foo #</h1>\n",
+ "example": 76,
+ "start_line": 1269,
+ "end_line": 1277,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "****\n## foo\n****\n",
+ "html": "<hr />\n<h2>foo</h2>\n<hr />\n",
+ "example": 77,
+ "start_line": 1283,
+ "end_line": 1291,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "Foo bar\n# baz\nBar foo\n",
+ "html": "<p>Foo bar</p>\n<h1>baz</h1>\n<p>Bar foo</p>\n",
+ "example": 78,
+ "start_line": 1294,
+ "end_line": 1302,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "## \n#\n### ###\n",
+ "html": "<h2></h2>\n<h1></h1>\n<h3></h3>\n",
+ "example": 79,
+ "start_line": 1307,
+ "end_line": 1315,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "Foo *bar*\n=========\n\nFoo *bar*\n---------\n",
+ "html": "<h1>Foo <em>bar</em></h1>\n<h2>Foo <em>bar</em></h2>\n",
+ "example": 80,
+ "start_line": 1347,
+ "end_line": 1356,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "Foo *bar\nbaz*\n====\n",
+ "html": "<h1>Foo <em>bar\nbaz</em></h1>\n",
+ "example": 81,
+ "start_line": 1361,
+ "end_line": 1368,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": " Foo *bar\nbaz*\t\n====\n",
+ "html": "<h1>Foo <em>bar\nbaz</em></h1>\n",
+ "example": 82,
+ "start_line": 1375,
+ "end_line": 1382,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "Foo\n-------------------------\n\nFoo\n=\n",
+ "html": "<h2>Foo</h2>\n<h1>Foo</h1>\n",
+ "example": 83,
+ "start_line": 1387,
+ "end_line": 1396,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": " Foo\n---\n\n Foo\n-----\n\n Foo\n ===\n",
+ "html": "<h2>Foo</h2>\n<h2>Foo</h2>\n<h1>Foo</h1>\n",
+ "example": 84,
+ "start_line": 1402,
+ "end_line": 1415,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": " Foo\n ---\n\n Foo\n---\n",
+ "html": "<pre><code>Foo\n---\n\nFoo\n</code></pre>\n<hr />\n",
+ "example": 85,
+ "start_line": 1420,
+ "end_line": 1433,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "Foo\n ---- \n",
+ "html": "<h2>Foo</h2>\n",
+ "example": 86,
+ "start_line": 1439,
+ "end_line": 1444,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "Foo\n ---\n",
+ "html": "<p>Foo\n---</p>\n",
+ "example": 87,
+ "start_line": 1449,
+ "end_line": 1455,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "Foo\n= =\n\nFoo\n--- -\n",
+ "html": "<p>Foo\n= =</p>\n<p>Foo</p>\n<hr />\n",
+ "example": 88,
+ "start_line": 1460,
+ "end_line": 1471,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "Foo \n-----\n",
+ "html": "<h2>Foo</h2>\n",
+ "example": 89,
+ "start_line": 1476,
+ "end_line": 1481,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "Foo\\\n----\n",
+ "html": "<h2>Foo\\</h2>\n",
+ "example": 90,
+ "start_line": 1486,
+ "end_line": 1491,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "`Foo\n----\n`\n\n<a title=\"a lot\n---\nof dashes\"/>\n",
+ "html": "<h2>`Foo</h2>\n<p>`</p>\n<h2><a title="a lot</h2>\n<p>of dashes"/></p>\n",
+ "example": 91,
+ "start_line": 1497,
+ "end_line": 1510,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "> Foo\n---\n",
+ "html": "<blockquote>\n<p>Foo</p>\n</blockquote>\n<hr />\n",
+ "example": 92,
+ "start_line": 1516,
+ "end_line": 1524,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "> foo\nbar\n===\n",
+ "html": "<blockquote>\n<p>foo\nbar\n===</p>\n</blockquote>\n",
+ "example": 93,
+ "start_line": 1527,
+ "end_line": 1537,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "- Foo\n---\n",
+ "html": "<ul>\n<li>Foo</li>\n</ul>\n<hr />\n",
+ "example": 94,
+ "start_line": 1540,
+ "end_line": 1548,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "Foo\nBar\n---\n",
+ "html": "<h2>Foo\nBar</h2>\n",
+ "example": 95,
+ "start_line": 1555,
+ "end_line": 1562,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "---\nFoo\n---\nBar\n---\nBaz\n",
+ "html": "<hr />\n<h2>Foo</h2>\n<h2>Bar</h2>\n<p>Baz</p>\n",
+ "example": 96,
+ "start_line": 1568,
+ "end_line": 1580,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "\n====\n",
+ "html": "<p>====</p>\n",
+ "example": 97,
+ "start_line": 1585,
+ "end_line": 1590,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "---\n---\n",
+ "html": "<hr />\n<hr />\n",
+ "example": 98,
+ "start_line": 1597,
+ "end_line": 1603,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "- foo\n-----\n",
+ "html": "<ul>\n<li>foo</li>\n</ul>\n<hr />\n",
+ "example": 99,
+ "start_line": 1606,
+ "end_line": 1614,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": " foo\n---\n",
+ "html": "<pre><code>foo\n</code></pre>\n<hr />\n",
+ "example": 100,
+ "start_line": 1617,
+ "end_line": 1624,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "> foo\n-----\n",
+ "html": "<blockquote>\n<p>foo</p>\n</blockquote>\n<hr />\n",
+ "example": 101,
+ "start_line": 1627,
+ "end_line": 1635,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "\\> foo\n------\n",
+ "html": "<h2>> foo</h2>\n",
+ "example": 102,
+ "start_line": 1641,
+ "end_line": 1646,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "Foo\n\nbar\n---\nbaz\n",
+ "html": "<p>Foo</p>\n<h2>bar</h2>\n<p>baz</p>\n",
+ "example": 103,
+ "start_line": 1672,
+ "end_line": 1682,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "Foo\nbar\n\n---\n\nbaz\n",
+ "html": "<p>Foo\nbar</p>\n<hr />\n<p>baz</p>\n",
+ "example": 104,
+ "start_line": 1688,
+ "end_line": 1700,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "Foo\nbar\n* * *\nbaz\n",
+ "html": "<p>Foo\nbar</p>\n<hr />\n<p>baz</p>\n",
+ "example": 105,
+ "start_line": 1706,
+ "end_line": 1716,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "Foo\nbar\n\\---\nbaz\n",
+ "html": "<p>Foo\nbar\n---\nbaz</p>\n",
+ "example": 106,
+ "start_line": 1721,
+ "end_line": 1731,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": " a simple\n indented code block\n",
+ "html": "<pre><code>a simple\n indented code block\n</code></pre>\n",
+ "example": 107,
+ "start_line": 1749,
+ "end_line": 1756,
+ "section": "Indented code blocks"
+ },
+ {
+ "markdown": " - foo\n\n bar\n",
+ "html": "<ul>\n<li>\n<p>foo</p>\n<p>bar</p>\n</li>\n</ul>\n",
+ "example": 108,
+ "start_line": 1763,
+ "end_line": 1774,
+ "section": "Indented code blocks"
+ },
+ {
+ "markdown": "1. foo\n\n - bar\n",
+ "html": "<ol>\n<li>\n<p>foo</p>\n<ul>\n<li>bar</li>\n</ul>\n</li>\n</ol>\n",
+ "example": 109,
+ "start_line": 1777,
+ "end_line": 1790,
+ "section": "Indented code blocks"
+ },
+ {
+ "markdown": " <a/>\n *hi*\n\n - one\n",
+ "html": "<pre><code><a/>\n*hi*\n\n- one\n</code></pre>\n",
+ "example": 110,
+ "start_line": 1797,
+ "end_line": 1808,
+ "section": "Indented code blocks"
+ },
+ {
+ "markdown": " chunk1\n\n chunk2\n \n \n \n chunk3\n",
+ "html": "<pre><code>chunk1\n\nchunk2\n\n\n\nchunk3\n</code></pre>\n",
+ "example": 111,
+ "start_line": 1813,
+ "end_line": 1830,
+ "section": "Indented code blocks"
+ },
+ {
+ "markdown": " chunk1\n \n chunk2\n",
+ "html": "<pre><code>chunk1\n \n chunk2\n</code></pre>\n",
+ "example": 112,
+ "start_line": 1836,
+ "end_line": 1845,
+ "section": "Indented code blocks"
+ },
+ {
+ "markdown": "Foo\n bar\n\n",
+ "html": "<p>Foo\nbar</p>\n",
+ "example": 113,
+ "start_line": 1851,
+ "end_line": 1858,
+ "section": "Indented code blocks"
+ },
+ {
+ "markdown": " foo\nbar\n",
+ "html": "<pre><code>foo\n</code></pre>\n<p>bar</p>\n",
+ "example": 114,
+ "start_line": 1865,
+ "end_line": 1872,
+ "section": "Indented code blocks"
+ },
+ {
+ "markdown": "# Heading\n foo\nHeading\n------\n foo\n----\n",
+ "html": "<h1>Heading</h1>\n<pre><code>foo\n</code></pre>\n<h2>Heading</h2>\n<pre><code>foo\n</code></pre>\n<hr />\n",
+ "example": 115,
+ "start_line": 1878,
+ "end_line": 1893,
+ "section": "Indented code blocks"
+ },
+ {
+ "markdown": " foo\n bar\n",
+ "html": "<pre><code> foo\nbar\n</code></pre>\n",
+ "example": 116,
+ "start_line": 1898,
+ "end_line": 1905,
+ "section": "Indented code blocks"
+ },
+ {
+ "markdown": "\n \n foo\n \n\n",
+ "html": "<pre><code>foo\n</code></pre>\n",
+ "example": 117,
+ "start_line": 1911,
+ "end_line": 1920,
+ "section": "Indented code blocks"
+ },
+ {
+ "markdown": " foo \n",
+ "html": "<pre><code>foo \n</code></pre>\n",
+ "example": 118,
+ "start_line": 1925,
+ "end_line": 1930,
+ "section": "Indented code blocks"
+ },
+ {
+ "markdown": "```\n<\n >\n```\n",
+ "html": "<pre><code><\n >\n</code></pre>\n",
+ "example": 119,
+ "start_line": 1980,
+ "end_line": 1989,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "~~~\n<\n >\n~~~\n",
+ "html": "<pre><code><\n >\n</code></pre>\n",
+ "example": 120,
+ "start_line": 1994,
+ "end_line": 2003,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "``\nfoo\n``\n",
+ "html": "<p><code>foo</code></p>\n",
+ "example": 121,
+ "start_line": 2007,
+ "end_line": 2013,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "```\naaa\n~~~\n```\n",
+ "html": "<pre><code>aaa\n~~~\n</code></pre>\n",
+ "example": 122,
+ "start_line": 2018,
+ "end_line": 2027,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "~~~\naaa\n```\n~~~\n",
+ "html": "<pre><code>aaa\n```\n</code></pre>\n",
+ "example": 123,
+ "start_line": 2030,
+ "end_line": 2039,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "````\naaa\n```\n``````\n",
+ "html": "<pre><code>aaa\n```\n</code></pre>\n",
+ "example": 124,
+ "start_line": 2044,
+ "end_line": 2053,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "~~~~\naaa\n~~~\n~~~~\n",
+ "html": "<pre><code>aaa\n~~~\n</code></pre>\n",
+ "example": 125,
+ "start_line": 2056,
+ "end_line": 2065,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "```\n",
+ "html": "<pre><code></code></pre>\n",
+ "example": 126,
+ "start_line": 2071,
+ "end_line": 2075,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "`````\n\n```\naaa\n",
+ "html": "<pre><code>\n```\naaa\n</code></pre>\n",
+ "example": 127,
+ "start_line": 2078,
+ "end_line": 2088,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "> ```\n> aaa\n\nbbb\n",
+ "html": "<blockquote>\n<pre><code>aaa\n</code></pre>\n</blockquote>\n<p>bbb</p>\n",
+ "example": 128,
+ "start_line": 2091,
+ "end_line": 2102,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "```\n\n \n```\n",
+ "html": "<pre><code>\n \n</code></pre>\n",
+ "example": 129,
+ "start_line": 2107,
+ "end_line": 2116,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "```\n```\n",
+ "html": "<pre><code></code></pre>\n",
+ "example": 130,
+ "start_line": 2121,
+ "end_line": 2126,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": " ```\n aaa\naaa\n```\n",
+ "html": "<pre><code>aaa\naaa\n</code></pre>\n",
+ "example": 131,
+ "start_line": 2133,
+ "end_line": 2142,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": " ```\naaa\n aaa\naaa\n ```\n",
+ "html": "<pre><code>aaa\naaa\naaa\n</code></pre>\n",
+ "example": 132,
+ "start_line": 2145,
+ "end_line": 2156,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": " ```\n aaa\n aaa\n aaa\n ```\n",
+ "html": "<pre><code>aaa\n aaa\naaa\n</code></pre>\n",
+ "example": 133,
+ "start_line": 2159,
+ "end_line": 2170,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": " ```\n aaa\n ```\n",
+ "html": "<pre><code>```\naaa\n```\n</code></pre>\n",
+ "example": 134,
+ "start_line": 2175,
+ "end_line": 2184,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "```\naaa\n ```\n",
+ "html": "<pre><code>aaa\n</code></pre>\n",
+ "example": 135,
+ "start_line": 2190,
+ "end_line": 2197,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": " ```\naaa\n ```\n",
+ "html": "<pre><code>aaa\n</code></pre>\n",
+ "example": 136,
+ "start_line": 2200,
+ "end_line": 2207,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "```\naaa\n ```\n",
+ "html": "<pre><code>aaa\n ```\n</code></pre>\n",
+ "example": 137,
+ "start_line": 2212,
+ "end_line": 2220,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "``` ```\naaa\n",
+ "html": "<p><code> </code>\naaa</p>\n",
+ "example": 138,
+ "start_line": 2226,
+ "end_line": 2232,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "~~~~~~\naaa\n~~~ ~~\n",
+ "html": "<pre><code>aaa\n~~~ ~~\n</code></pre>\n",
+ "example": 139,
+ "start_line": 2235,
+ "end_line": 2243,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "foo\n```\nbar\n```\nbaz\n",
+ "html": "<p>foo</p>\n<pre><code>bar\n</code></pre>\n<p>baz</p>\n",
+ "example": 140,
+ "start_line": 2249,
+ "end_line": 2260,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "foo\n---\n~~~\nbar\n~~~\n# baz\n",
+ "html": "<h2>foo</h2>\n<pre><code>bar\n</code></pre>\n<h1>baz</h1>\n",
+ "example": 141,
+ "start_line": 2266,
+ "end_line": 2278,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "```ruby\ndef foo(x)\n return 3\nend\n```\n",
+ "html": "<pre><code class=\"language-ruby\">def foo(x)\n return 3\nend\n</code></pre>\n",
+ "example": 142,
+ "start_line": 2288,
+ "end_line": 2299,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "~~~~ ruby startline=3 $%@#$\ndef foo(x)\n return 3\nend\n~~~~~~~\n",
+ "html": "<pre><code class=\"language-ruby\">def foo(x)\n return 3\nend\n</code></pre>\n",
+ "example": 143,
+ "start_line": 2302,
+ "end_line": 2313,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "````;\n````\n",
+ "html": "<pre><code class=\"language-;\"></code></pre>\n",
+ "example": 144,
+ "start_line": 2316,
+ "end_line": 2321,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "``` aa ```\nfoo\n",
+ "html": "<p><code>aa</code>\nfoo</p>\n",
+ "example": 145,
+ "start_line": 2326,
+ "end_line": 2332,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "~~~ aa ``` ~~~\nfoo\n~~~\n",
+ "html": "<pre><code class=\"language-aa\">foo\n</code></pre>\n",
+ "example": 146,
+ "start_line": 2337,
+ "end_line": 2344,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "```\n``` aaa\n```\n",
+ "html": "<pre><code>``` aaa\n</code></pre>\n",
+ "example": 147,
+ "start_line": 2349,
+ "end_line": 2356,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "<table><tr><td>\n<pre>\n**Hello**,\n\n_world_.\n</pre>\n</td></tr></table>\n",
+ "html": "<table><tr><td>\n<pre>\n**Hello**,\n<p><em>world</em>.\n</pre></p>\n</td></tr></table>\n",
+ "example": 148,
+ "start_line": 2428,
+ "end_line": 2443,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<table>\n <tr>\n <td>\n hi\n </td>\n </tr>\n</table>\n\nokay.\n",
+ "html": "<table>\n <tr>\n <td>\n hi\n </td>\n </tr>\n</table>\n<p>okay.</p>\n",
+ "example": 149,
+ "start_line": 2457,
+ "end_line": 2476,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": " <div>\n *hello*\n <foo><a>\n",
+ "html": " <div>\n *hello*\n <foo><a>\n",
+ "example": 150,
+ "start_line": 2479,
+ "end_line": 2487,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "</div>\n*foo*\n",
+ "html": "</div>\n*foo*\n",
+ "example": 151,
+ "start_line": 2492,
+ "end_line": 2498,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<DIV CLASS=\"foo\">\n\n*Markdown*\n\n</DIV>\n",
+ "html": "<DIV CLASS=\"foo\">\n<p><em>Markdown</em></p>\n</DIV>\n",
+ "example": 152,
+ "start_line": 2503,
+ "end_line": 2513,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<div id=\"foo\"\n class=\"bar\">\n</div>\n",
+ "html": "<div id=\"foo\"\n class=\"bar\">\n</div>\n",
+ "example": 153,
+ "start_line": 2519,
+ "end_line": 2527,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<div id=\"foo\" class=\"bar\n baz\">\n</div>\n",
+ "html": "<div id=\"foo\" class=\"bar\n baz\">\n</div>\n",
+ "example": 154,
+ "start_line": 2530,
+ "end_line": 2538,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<div>\n*foo*\n\n*bar*\n",
+ "html": "<div>\n*foo*\n<p><em>bar</em></p>\n",
+ "example": 155,
+ "start_line": 2542,
+ "end_line": 2551,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<div id=\"foo\"\n*hi*\n",
+ "html": "<div id=\"foo\"\n*hi*\n",
+ "example": 156,
+ "start_line": 2558,
+ "end_line": 2564,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<div class\nfoo\n",
+ "html": "<div class\nfoo\n",
+ "example": 157,
+ "start_line": 2567,
+ "end_line": 2573,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<div *???-&&&-<---\n*foo*\n",
+ "html": "<div *???-&&&-<---\n*foo*\n",
+ "example": 158,
+ "start_line": 2579,
+ "end_line": 2585,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<div><a href=\"bar\">*foo*</a></div>\n",
+ "html": "<div><a href=\"bar\">*foo*</a></div>\n",
+ "example": 159,
+ "start_line": 2591,
+ "end_line": 2595,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<table><tr><td>\nfoo\n</td></tr></table>\n",
+ "html": "<table><tr><td>\nfoo\n</td></tr></table>\n",
+ "example": 160,
+ "start_line": 2598,
+ "end_line": 2606,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<div></div>\n``` c\nint x = 33;\n```\n",
+ "html": "<div></div>\n``` c\nint x = 33;\n```\n",
+ "example": 161,
+ "start_line": 2615,
+ "end_line": 2625,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<a href=\"foo\">\n*bar*\n</a>\n",
+ "html": "<a href=\"foo\">\n*bar*\n</a>\n",
+ "example": 162,
+ "start_line": 2632,
+ "end_line": 2640,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<Warning>\n*bar*\n</Warning>\n",
+ "html": "<Warning>\n*bar*\n</Warning>\n",
+ "example": 163,
+ "start_line": 2645,
+ "end_line": 2653,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<i class=\"foo\">\n*bar*\n</i>\n",
+ "html": "<i class=\"foo\">\n*bar*\n</i>\n",
+ "example": 164,
+ "start_line": 2656,
+ "end_line": 2664,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "</ins>\n*bar*\n",
+ "html": "</ins>\n*bar*\n",
+ "example": 165,
+ "start_line": 2667,
+ "end_line": 2673,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<del>\n*foo*\n</del>\n",
+ "html": "<del>\n*foo*\n</del>\n",
+ "example": 166,
+ "start_line": 2682,
+ "end_line": 2690,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<del>\n\n*foo*\n\n</del>\n",
+ "html": "<del>\n<p><em>foo</em></p>\n</del>\n",
+ "example": 167,
+ "start_line": 2697,
+ "end_line": 2707,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<del>*foo*</del>\n",
+ "html": "<p><del><em>foo</em></del></p>\n",
+ "example": 168,
+ "start_line": 2715,
+ "end_line": 2719,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<pre language=\"haskell\"><code>\nimport Text.HTML.TagSoup\n\nmain :: IO ()\nmain = print $ parseTags tags\n</code></pre>\nokay\n",
+ "html": "<pre language=\"haskell\"><code>\nimport Text.HTML.TagSoup\n\nmain :: IO ()\nmain = print $ parseTags tags\n</code></pre>\n<p>okay</p>\n",
+ "example": 169,
+ "start_line": 2731,
+ "end_line": 2747,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<script type=\"text/javascript\">\n// JavaScript example\n\ndocument.getElementById(\"demo\").innerHTML = \"Hello JavaScript!\";\n</script>\nokay\n",
+ "html": "<script type=\"text/javascript\">\n// JavaScript example\n\ndocument.getElementById(\"demo\").innerHTML = \"Hello JavaScript!\";\n</script>\n<p>okay</p>\n",
+ "example": 170,
+ "start_line": 2752,
+ "end_line": 2766,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<textarea>\n\n*foo*\n\n_bar_\n\n</textarea>\n",
+ "html": "<textarea>\n\n*foo*\n\n_bar_\n\n</textarea>\n",
+ "example": 171,
+ "start_line": 2771,
+ "end_line": 2787,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<style\n type=\"text/css\">\nh1 {color:red;}\n\np {color:blue;}\n</style>\nokay\n",
+ "html": "<style\n type=\"text/css\">\nh1 {color:red;}\n\np {color:blue;}\n</style>\n<p>okay</p>\n",
+ "example": 172,
+ "start_line": 2791,
+ "end_line": 2807,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<style\n type=\"text/css\">\n\nfoo\n",
+ "html": "<style\n type=\"text/css\">\n\nfoo\n",
+ "example": 173,
+ "start_line": 2814,
+ "end_line": 2824,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "> <div>\n> foo\n\nbar\n",
+ "html": "<blockquote>\n<div>\nfoo\n</blockquote>\n<p>bar</p>\n",
+ "example": 174,
+ "start_line": 2827,
+ "end_line": 2838,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "- <div>\n- foo\n",
+ "html": "<ul>\n<li>\n<div>\n</li>\n<li>foo</li>\n</ul>\n",
+ "example": 175,
+ "start_line": 2841,
+ "end_line": 2851,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<style>p{color:red;}</style>\n*foo*\n",
+ "html": "<style>p{color:red;}</style>\n<p><em>foo</em></p>\n",
+ "example": 176,
+ "start_line": 2856,
+ "end_line": 2862,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<!-- foo -->*bar*\n*baz*\n",
+ "html": "<!-- foo -->*bar*\n<p><em>baz</em></p>\n",
+ "example": 177,
+ "start_line": 2865,
+ "end_line": 2871,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<script>\nfoo\n</script>1. *bar*\n",
+ "html": "<script>\nfoo\n</script>1. *bar*\n",
+ "example": 178,
+ "start_line": 2877,
+ "end_line": 2885,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<!-- Foo\n\nbar\n baz -->\nokay\n",
+ "html": "<!-- Foo\n\nbar\n baz -->\n<p>okay</p>\n",
+ "example": 179,
+ "start_line": 2890,
+ "end_line": 2902,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<?php\n\n echo '>';\n\n?>\nokay\n",
+ "html": "<?php\n\n echo '>';\n\n?>\n<p>okay</p>\n",
+ "example": 180,
+ "start_line": 2908,
+ "end_line": 2922,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<!DOCTYPE html>\n",
+ "html": "<!DOCTYPE html>\n",
+ "example": 181,
+ "start_line": 2927,
+ "end_line": 2931,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<![CDATA[\nfunction matchwo(a,b)\n{\n if (a < b && a < 0) then {\n return 1;\n\n } else {\n\n return 0;\n }\n}\n]]>\nokay\n",
+ "html": "<![CDATA[\nfunction matchwo(a,b)\n{\n if (a < b && a < 0) then {\n return 1;\n\n } else {\n\n return 0;\n }\n}\n]]>\n<p>okay</p>\n",
+ "example": 182,
+ "start_line": 2936,
+ "end_line": 2964,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": " <!-- foo -->\n\n <!-- foo -->\n",
+ "html": " <!-- foo -->\n<pre><code><!-- foo -->\n</code></pre>\n",
+ "example": 183,
+ "start_line": 2970,
+ "end_line": 2978,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": " <div>\n\n <div>\n",
+ "html": " <div>\n<pre><code><div>\n</code></pre>\n",
+ "example": 184,
+ "start_line": 2981,
+ "end_line": 2989,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "Foo\n<div>\nbar\n</div>\n",
+ "html": "<p>Foo</p>\n<div>\nbar\n</div>\n",
+ "example": 185,
+ "start_line": 2995,
+ "end_line": 3005,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<div>\nbar\n</div>\n*foo*\n",
+ "html": "<div>\nbar\n</div>\n*foo*\n",
+ "example": 186,
+ "start_line": 3012,
+ "end_line": 3022,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "Foo\n<a href=\"bar\">\nbaz\n",
+ "html": "<p>Foo\n<a href=\"bar\">\nbaz</p>\n",
+ "example": 187,
+ "start_line": 3027,
+ "end_line": 3035,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<div>\n\n*Emphasized* text.\n\n</div>\n",
+ "html": "<div>\n<p><em>Emphasized</em> text.</p>\n</div>\n",
+ "example": 188,
+ "start_line": 3068,
+ "end_line": 3078,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<div>\n*Emphasized* text.\n</div>\n",
+ "html": "<div>\n*Emphasized* text.\n</div>\n",
+ "example": 189,
+ "start_line": 3081,
+ "end_line": 3089,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<table>\n\n<tr>\n\n<td>\nHi\n</td>\n\n</tr>\n\n</table>\n",
+ "html": "<table>\n<tr>\n<td>\nHi\n</td>\n</tr>\n</table>\n",
+ "example": 190,
+ "start_line": 3103,
+ "end_line": 3123,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<table>\n\n <tr>\n\n <td>\n Hi\n </td>\n\n </tr>\n\n</table>\n",
+ "html": "<table>\n <tr>\n<pre><code><td>\n Hi\n</td>\n</code></pre>\n </tr>\n</table>\n",
+ "example": 191,
+ "start_line": 3130,
+ "end_line": 3151,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "[foo]: /url \"title\"\n\n[foo]\n",
+ "html": "<p><a href=\"/url\" title=\"title\">foo</a></p>\n",
+ "example": 192,
+ "start_line": 3179,
+ "end_line": 3185,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": " [foo]: \n /url \n 'the title' \n\n[foo]\n",
+ "html": "<p><a href=\"/url\" title=\"the title\">foo</a></p>\n",
+ "example": 193,
+ "start_line": 3188,
+ "end_line": 3196,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[Foo*bar\\]]:my_(url) 'title (with parens)'\n\n[Foo*bar\\]]\n",
+ "html": "<p><a href=\"my_(url)\" title=\"title (with parens)\">Foo*bar]</a></p>\n",
+ "example": 194,
+ "start_line": 3199,
+ "end_line": 3205,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[Foo bar]:\n<my url>\n'title'\n\n[Foo bar]\n",
+ "html": "<p><a href=\"my%20url\" title=\"title\">Foo bar</a></p>\n",
+ "example": 195,
+ "start_line": 3208,
+ "end_line": 3216,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]: /url '\ntitle\nline1\nline2\n'\n\n[foo]\n",
+ "html": "<p><a href=\"/url\" title=\"\ntitle\nline1\nline2\n\">foo</a></p>\n",
+ "example": 196,
+ "start_line": 3221,
+ "end_line": 3235,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]: /url 'title\n\nwith blank line'\n\n[foo]\n",
+ "html": "<p>[foo]: /url 'title</p>\n<p>with blank line'</p>\n<p>[foo]</p>\n",
+ "example": 197,
+ "start_line": 3240,
+ "end_line": 3250,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]:\n/url\n\n[foo]\n",
+ "html": "<p><a href=\"/url\">foo</a></p>\n",
+ "example": 198,
+ "start_line": 3255,
+ "end_line": 3262,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]:\n\n[foo]\n",
+ "html": "<p>[foo]:</p>\n<p>[foo]</p>\n",
+ "example": 199,
+ "start_line": 3267,
+ "end_line": 3274,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]: <>\n\n[foo]\n",
+ "html": "<p><a href=\"\">foo</a></p>\n",
+ "example": 200,
+ "start_line": 3279,
+ "end_line": 3285,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]: <bar>(baz)\n\n[foo]\n",
+ "html": "<p>[foo]: <bar>(baz)</p>\n<p>[foo]</p>\n",
+ "example": 201,
+ "start_line": 3290,
+ "end_line": 3297,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]: /url\\bar\\*baz \"foo\\\"bar\\baz\"\n\n[foo]\n",
+ "html": "<p><a href=\"/url%5Cbar*baz\" title=\"foo"bar\\baz\">foo</a></p>\n",
+ "example": 202,
+ "start_line": 3303,
+ "end_line": 3309,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]\n\n[foo]: url\n",
+ "html": "<p><a href=\"url\">foo</a></p>\n",
+ "example": 203,
+ "start_line": 3314,
+ "end_line": 3320,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]\n\n[foo]: first\n[foo]: second\n",
+ "html": "<p><a href=\"first\">foo</a></p>\n",
+ "example": 204,
+ "start_line": 3326,
+ "end_line": 3333,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[FOO]: /url\n\n[Foo]\n",
+ "html": "<p><a href=\"/url\">Foo</a></p>\n",
+ "example": 205,
+ "start_line": 3339,
+ "end_line": 3345,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[ΑΓΩ]: /φου\n\n[αγω]\n",
+ "html": "<p><a href=\"/%CF%86%CE%BF%CF%85\">αγω</a></p>\n",
+ "example": 206,
+ "start_line": 3348,
+ "end_line": 3354,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]: /url\n",
+ "html": "",
+ "example": 207,
+ "start_line": 3363,
+ "end_line": 3366,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[\nfoo\n]: /url\nbar\n",
+ "html": "<p>bar</p>\n",
+ "example": 208,
+ "start_line": 3371,
+ "end_line": 3378,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]: /url \"title\" ok\n",
+ "html": "<p>[foo]: /url "title" ok</p>\n",
+ "example": 209,
+ "start_line": 3384,
+ "end_line": 3388,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]: /url\n\"title\" ok\n",
+ "html": "<p>"title" ok</p>\n",
+ "example": 210,
+ "start_line": 3393,
+ "end_line": 3398,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": " [foo]: /url \"title\"\n\n[foo]\n",
+ "html": "<pre><code>[foo]: /url "title"\n</code></pre>\n<p>[foo]</p>\n",
+ "example": 211,
+ "start_line": 3404,
+ "end_line": 3412,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "```\n[foo]: /url\n```\n\n[foo]\n",
+ "html": "<pre><code>[foo]: /url\n</code></pre>\n<p>[foo]</p>\n",
+ "example": 212,
+ "start_line": 3418,
+ "end_line": 3428,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "Foo\n[bar]: /baz\n\n[bar]\n",
+ "html": "<p>Foo\n[bar]: /baz</p>\n<p>[bar]</p>\n",
+ "example": 213,
+ "start_line": 3433,
+ "end_line": 3442,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "# [Foo]\n[foo]: /url\n> bar\n",
+ "html": "<h1><a href=\"/url\">Foo</a></h1>\n<blockquote>\n<p>bar</p>\n</blockquote>\n",
+ "example": 214,
+ "start_line": 3448,
+ "end_line": 3457,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]: /url\nbar\n===\n[foo]\n",
+ "html": "<h1>bar</h1>\n<p><a href=\"/url\">foo</a></p>\n",
+ "example": 215,
+ "start_line": 3459,
+ "end_line": 3467,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]: /url\n===\n[foo]\n",
+ "html": "<p>===\n<a href=\"/url\">foo</a></p>\n",
+ "example": 216,
+ "start_line": 3469,
+ "end_line": 3476,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]: /foo-url \"foo\"\n[bar]: /bar-url\n \"bar\"\n[baz]: /baz-url\n\n[foo],\n[bar],\n[baz]\n",
+ "html": "<p><a href=\"/foo-url\" title=\"foo\">foo</a>,\n<a href=\"/bar-url\" title=\"bar\">bar</a>,\n<a href=\"/baz-url\">baz</a></p>\n",
+ "example": 217,
+ "start_line": 3482,
+ "end_line": 3495,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]\n\n> [foo]: /url\n",
+ "html": "<p><a href=\"/url\">foo</a></p>\n<blockquote>\n</blockquote>\n",
+ "example": 218,
+ "start_line": 3503,
+ "end_line": 3511,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "aaa\n\nbbb\n",
+ "html": "<p>aaa</p>\n<p>bbb</p>\n",
+ "example": 219,
+ "start_line": 3525,
+ "end_line": 3532,
+ "section": "Paragraphs"
+ },
+ {
+ "markdown": "aaa\nbbb\n\nccc\nddd\n",
+ "html": "<p>aaa\nbbb</p>\n<p>ccc\nddd</p>\n",
+ "example": 220,
+ "start_line": 3537,
+ "end_line": 3548,
+ "section": "Paragraphs"
+ },
+ {
+ "markdown": "aaa\n\n\nbbb\n",
+ "html": "<p>aaa</p>\n<p>bbb</p>\n",
+ "example": 221,
+ "start_line": 3553,
+ "end_line": 3561,
+ "section": "Paragraphs"
+ },
+ {
+ "markdown": " aaa\n bbb\n",
+ "html": "<p>aaa\nbbb</p>\n",
+ "example": 222,
+ "start_line": 3566,
+ "end_line": 3572,
+ "section": "Paragraphs"
+ },
+ {
+ "markdown": "aaa\n bbb\n ccc\n",
+ "html": "<p>aaa\nbbb\nccc</p>\n",
+ "example": 223,
+ "start_line": 3578,
+ "end_line": 3586,
+ "section": "Paragraphs"
+ },
+ {
+ "markdown": " aaa\nbbb\n",
+ "html": "<p>aaa\nbbb</p>\n",
+ "example": 224,
+ "start_line": 3592,
+ "end_line": 3598,
+ "section": "Paragraphs"
+ },
+ {
+ "markdown": " aaa\nbbb\n",
+ "html": "<pre><code>aaa\n</code></pre>\n<p>bbb</p>\n",
+ "example": 225,
+ "start_line": 3601,
+ "end_line": 3608,
+ "section": "Paragraphs"
+ },
+ {
+ "markdown": "aaa \nbbb \n",
+ "html": "<p>aaa<br />\nbbb</p>\n",
+ "example": 226,
+ "start_line": 3615,
+ "end_line": 3621,
+ "section": "Paragraphs"
+ },
+ {
+ "markdown": " \n\naaa\n \n\n# aaa\n\n \n",
+ "html": "<p>aaa</p>\n<h1>aaa</h1>\n",
+ "example": 227,
+ "start_line": 3632,
+ "end_line": 3644,
+ "section": "Blank lines"
+ },
+ {
+ "markdown": "> # Foo\n> bar\n> baz\n",
+ "html": "<blockquote>\n<h1>Foo</h1>\n<p>bar\nbaz</p>\n</blockquote>\n",
+ "example": 228,
+ "start_line": 3700,
+ "end_line": 3710,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "># Foo\n>bar\n> baz\n",
+ "html": "<blockquote>\n<h1>Foo</h1>\n<p>bar\nbaz</p>\n</blockquote>\n",
+ "example": 229,
+ "start_line": 3715,
+ "end_line": 3725,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": " > # Foo\n > bar\n > baz\n",
+ "html": "<blockquote>\n<h1>Foo</h1>\n<p>bar\nbaz</p>\n</blockquote>\n",
+ "example": 230,
+ "start_line": 3730,
+ "end_line": 3740,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": " > # Foo\n > bar\n > baz\n",
+ "html": "<pre><code>> # Foo\n> bar\n> baz\n</code></pre>\n",
+ "example": 231,
+ "start_line": 3745,
+ "end_line": 3754,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> # Foo\n> bar\nbaz\n",
+ "html": "<blockquote>\n<h1>Foo</h1>\n<p>bar\nbaz</p>\n</blockquote>\n",
+ "example": 232,
+ "start_line": 3760,
+ "end_line": 3770,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> bar\nbaz\n> foo\n",
+ "html": "<blockquote>\n<p>bar\nbaz\nfoo</p>\n</blockquote>\n",
+ "example": 233,
+ "start_line": 3776,
+ "end_line": 3786,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> foo\n---\n",
+ "html": "<blockquote>\n<p>foo</p>\n</blockquote>\n<hr />\n",
+ "example": 234,
+ "start_line": 3800,
+ "end_line": 3808,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> - foo\n- bar\n",
+ "html": "<blockquote>\n<ul>\n<li>foo</li>\n</ul>\n</blockquote>\n<ul>\n<li>bar</li>\n</ul>\n",
+ "example": 235,
+ "start_line": 3820,
+ "end_line": 3832,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> foo\n bar\n",
+ "html": "<blockquote>\n<pre><code>foo\n</code></pre>\n</blockquote>\n<pre><code>bar\n</code></pre>\n",
+ "example": 236,
+ "start_line": 3838,
+ "end_line": 3848,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> ```\nfoo\n```\n",
+ "html": "<blockquote>\n<pre><code></code></pre>\n</blockquote>\n<p>foo</p>\n<pre><code></code></pre>\n",
+ "example": 237,
+ "start_line": 3851,
+ "end_line": 3861,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> foo\n - bar\n",
+ "html": "<blockquote>\n<p>foo\n- bar</p>\n</blockquote>\n",
+ "example": 238,
+ "start_line": 3867,
+ "end_line": 3875,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": ">\n",
+ "html": "<blockquote>\n</blockquote>\n",
+ "example": 239,
+ "start_line": 3891,
+ "end_line": 3896,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": ">\n> \n> \n",
+ "html": "<blockquote>\n</blockquote>\n",
+ "example": 240,
+ "start_line": 3899,
+ "end_line": 3906,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": ">\n> foo\n> \n",
+ "html": "<blockquote>\n<p>foo</p>\n</blockquote>\n",
+ "example": 241,
+ "start_line": 3911,
+ "end_line": 3919,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> foo\n\n> bar\n",
+ "html": "<blockquote>\n<p>foo</p>\n</blockquote>\n<blockquote>\n<p>bar</p>\n</blockquote>\n",
+ "example": 242,
+ "start_line": 3924,
+ "end_line": 3935,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> foo\n> bar\n",
+ "html": "<blockquote>\n<p>foo\nbar</p>\n</blockquote>\n",
+ "example": 243,
+ "start_line": 3946,
+ "end_line": 3954,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> foo\n>\n> bar\n",
+ "html": "<blockquote>\n<p>foo</p>\n<p>bar</p>\n</blockquote>\n",
+ "example": 244,
+ "start_line": 3959,
+ "end_line": 3968,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "foo\n> bar\n",
+ "html": "<p>foo</p>\n<blockquote>\n<p>bar</p>\n</blockquote>\n",
+ "example": 245,
+ "start_line": 3973,
+ "end_line": 3981,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> aaa\n***\n> bbb\n",
+ "html": "<blockquote>\n<p>aaa</p>\n</blockquote>\n<hr />\n<blockquote>\n<p>bbb</p>\n</blockquote>\n",
+ "example": 246,
+ "start_line": 3987,
+ "end_line": 3999,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> bar\nbaz\n",
+ "html": "<blockquote>\n<p>bar\nbaz</p>\n</blockquote>\n",
+ "example": 247,
+ "start_line": 4005,
+ "end_line": 4013,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> bar\n\nbaz\n",
+ "html": "<blockquote>\n<p>bar</p>\n</blockquote>\n<p>baz</p>\n",
+ "example": 248,
+ "start_line": 4016,
+ "end_line": 4025,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> bar\n>\nbaz\n",
+ "html": "<blockquote>\n<p>bar</p>\n</blockquote>\n<p>baz</p>\n",
+ "example": 249,
+ "start_line": 4028,
+ "end_line": 4037,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> > > foo\nbar\n",
+ "html": "<blockquote>\n<blockquote>\n<blockquote>\n<p>foo\nbar</p>\n</blockquote>\n</blockquote>\n</blockquote>\n",
+ "example": 250,
+ "start_line": 4044,
+ "end_line": 4056,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": ">>> foo\n> bar\n>>baz\n",
+ "html": "<blockquote>\n<blockquote>\n<blockquote>\n<p>foo\nbar\nbaz</p>\n</blockquote>\n</blockquote>\n</blockquote>\n",
+ "example": 251,
+ "start_line": 4059,
+ "end_line": 4073,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> code\n\n> not code\n",
+ "html": "<blockquote>\n<pre><code>code\n</code></pre>\n</blockquote>\n<blockquote>\n<p>not code</p>\n</blockquote>\n",
+ "example": 252,
+ "start_line": 4081,
+ "end_line": 4093,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "A paragraph\nwith two lines.\n\n indented code\n\n> A block quote.\n",
+ "html": "<p>A paragraph\nwith two lines.</p>\n<pre><code>indented code\n</code></pre>\n<blockquote>\n<p>A block quote.</p>\n</blockquote>\n",
+ "example": 253,
+ "start_line": 4135,
+ "end_line": 4150,
+ "section": "List items"
+ },
+ {
+ "markdown": "1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n",
+ "html": "<ol>\n<li>\n<p>A paragraph\nwith two lines.</p>\n<pre><code>indented code\n</code></pre>\n<blockquote>\n<p>A block quote.</p>\n</blockquote>\n</li>\n</ol>\n",
+ "example": 254,
+ "start_line": 4157,
+ "end_line": 4176,
+ "section": "List items"
+ },
+ {
+ "markdown": "- one\n\n two\n",
+ "html": "<ul>\n<li>one</li>\n</ul>\n<p>two</p>\n",
+ "example": 255,
+ "start_line": 4190,
+ "end_line": 4199,
+ "section": "List items"
+ },
+ {
+ "markdown": "- one\n\n two\n",
+ "html": "<ul>\n<li>\n<p>one</p>\n<p>two</p>\n</li>\n</ul>\n",
+ "example": 256,
+ "start_line": 4202,
+ "end_line": 4213,
+ "section": "List items"
+ },
+ {
+ "markdown": " - one\n\n two\n",
+ "html": "<ul>\n<li>one</li>\n</ul>\n<pre><code> two\n</code></pre>\n",
+ "example": 257,
+ "start_line": 4216,
+ "end_line": 4226,
+ "section": "List items"
+ },
+ {
+ "markdown": " - one\n\n two\n",
+ "html": "<ul>\n<li>\n<p>one</p>\n<p>two</p>\n</li>\n</ul>\n",
+ "example": 258,
+ "start_line": 4229,
+ "end_line": 4240,
+ "section": "List items"
+ },
+ {
+ "markdown": " > > 1. one\n>>\n>> two\n",
+ "html": "<blockquote>\n<blockquote>\n<ol>\n<li>\n<p>one</p>\n<p>two</p>\n</li>\n</ol>\n</blockquote>\n</blockquote>\n",
+ "example": 259,
+ "start_line": 4251,
+ "end_line": 4266,
+ "section": "List items"
+ },
+ {
+ "markdown": ">>- one\n>>\n > > two\n",
+ "html": "<blockquote>\n<blockquote>\n<ul>\n<li>one</li>\n</ul>\n<p>two</p>\n</blockquote>\n</blockquote>\n",
+ "example": 260,
+ "start_line": 4278,
+ "end_line": 4291,
+ "section": "List items"
+ },
+ {
+ "markdown": "-one\n\n2.two\n",
+ "html": "<p>-one</p>\n<p>2.two</p>\n",
+ "example": 261,
+ "start_line": 4297,
+ "end_line": 4304,
+ "section": "List items"
+ },
+ {
+ "markdown": "- foo\n\n\n bar\n",
+ "html": "<ul>\n<li>\n<p>foo</p>\n<p>bar</p>\n</li>\n</ul>\n",
+ "example": 262,
+ "start_line": 4310,
+ "end_line": 4322,
+ "section": "List items"
+ },
+ {
+ "markdown": "1. foo\n\n ```\n bar\n ```\n\n baz\n\n > bam\n",
+ "html": "<ol>\n<li>\n<p>foo</p>\n<pre><code>bar\n</code></pre>\n<p>baz</p>\n<blockquote>\n<p>bam</p>\n</blockquote>\n</li>\n</ol>\n",
+ "example": 263,
+ "start_line": 4327,
+ "end_line": 4349,
+ "section": "List items"
+ },
+ {
+ "markdown": "- Foo\n\n bar\n\n\n baz\n",
+ "html": "<ul>\n<li>\n<p>Foo</p>\n<pre><code>bar\n\n\nbaz\n</code></pre>\n</li>\n</ul>\n",
+ "example": 264,
+ "start_line": 4355,
+ "end_line": 4373,
+ "section": "List items"
+ },
+ {
+ "markdown": "123456789. ok\n",
+ "html": "<ol start=\"123456789\">\n<li>ok</li>\n</ol>\n",
+ "example": 265,
+ "start_line": 4377,
+ "end_line": 4383,
+ "section": "List items"
+ },
+ {
+ "markdown": "1234567890. not ok\n",
+ "html": "<p>1234567890. not ok</p>\n",
+ "example": 266,
+ "start_line": 4386,
+ "end_line": 4390,
+ "section": "List items"
+ },
+ {
+ "markdown": "0. ok\n",
+ "html": "<ol start=\"0\">\n<li>ok</li>\n</ol>\n",
+ "example": 267,
+ "start_line": 4395,
+ "end_line": 4401,
+ "section": "List items"
+ },
+ {
+ "markdown": "003. ok\n",
+ "html": "<ol start=\"3\">\n<li>ok</li>\n</ol>\n",
+ "example": 268,
+ "start_line": 4404,
+ "end_line": 4410,
+ "section": "List items"
+ },
+ {
+ "markdown": "-1. not ok\n",
+ "html": "<p>-1. not ok</p>\n",
+ "example": 269,
+ "start_line": 4415,
+ "end_line": 4419,
+ "section": "List items"
+ },
+ {
+ "markdown": "- foo\n\n bar\n",
+ "html": "<ul>\n<li>\n<p>foo</p>\n<pre><code>bar\n</code></pre>\n</li>\n</ul>\n",
+ "example": 270,
+ "start_line": 4438,
+ "end_line": 4450,
+ "section": "List items"
+ },
+ {
+ "markdown": " 10. foo\n\n bar\n",
+ "html": "<ol start=\"10\">\n<li>\n<p>foo</p>\n<pre><code>bar\n</code></pre>\n</li>\n</ol>\n",
+ "example": 271,
+ "start_line": 4455,
+ "end_line": 4467,
+ "section": "List items"
+ },
+ {
+ "markdown": " indented code\n\nparagraph\n\n more code\n",
+ "html": "<pre><code>indented code\n</code></pre>\n<p>paragraph</p>\n<pre><code>more code\n</code></pre>\n",
+ "example": 272,
+ "start_line": 4474,
+ "end_line": 4486,
+ "section": "List items"
+ },
+ {
+ "markdown": "1. indented code\n\n paragraph\n\n more code\n",
+ "html": "<ol>\n<li>\n<pre><code>indented code\n</code></pre>\n<p>paragraph</p>\n<pre><code>more code\n</code></pre>\n</li>\n</ol>\n",
+ "example": 273,
+ "start_line": 4489,
+ "end_line": 4505,
+ "section": "List items"
+ },
+ {
+ "markdown": "1. indented code\n\n paragraph\n\n more code\n",
+ "html": "<ol>\n<li>\n<pre><code> indented code\n</code></pre>\n<p>paragraph</p>\n<pre><code>more code\n</code></pre>\n</li>\n</ol>\n",
+ "example": 274,
+ "start_line": 4511,
+ "end_line": 4527,
+ "section": "List items"
+ },
+ {
+ "markdown": " foo\n\nbar\n",
+ "html": "<p>foo</p>\n<p>bar</p>\n",
+ "example": 275,
+ "start_line": 4538,
+ "end_line": 4545,
+ "section": "List items"
+ },
+ {
+ "markdown": "- foo\n\n bar\n",
+ "html": "<ul>\n<li>foo</li>\n</ul>\n<p>bar</p>\n",
+ "example": 276,
+ "start_line": 4548,
+ "end_line": 4557,
+ "section": "List items"
+ },
+ {
+ "markdown": "- foo\n\n bar\n",
+ "html": "<ul>\n<li>\n<p>foo</p>\n<p>bar</p>\n</li>\n</ul>\n",
+ "example": 277,
+ "start_line": 4565,
+ "end_line": 4576,
+ "section": "List items"
+ },
+ {
+ "markdown": "-\n foo\n-\n ```\n bar\n ```\n-\n baz\n",
+ "html": "<ul>\n<li>foo</li>\n<li>\n<pre><code>bar\n</code></pre>\n</li>\n<li>\n<pre><code>baz\n</code></pre>\n</li>\n</ul>\n",
+ "example": 278,
+ "start_line": 4592,
+ "end_line": 4613,
+ "section": "List items"
+ },
+ {
+ "markdown": "- \n foo\n",
+ "html": "<ul>\n<li>foo</li>\n</ul>\n",
+ "example": 279,
+ "start_line": 4618,
+ "end_line": 4625,
+ "section": "List items"
+ },
+ {
+ "markdown": "-\n\n foo\n",
+ "html": "<ul>\n<li></li>\n</ul>\n<p>foo</p>\n",
+ "example": 280,
+ "start_line": 4632,
+ "end_line": 4641,
+ "section": "List items"
+ },
+ {
+ "markdown": "- foo\n-\n- bar\n",
+ "html": "<ul>\n<li>foo</li>\n<li></li>\n<li>bar</li>\n</ul>\n",
+ "example": 281,
+ "start_line": 4646,
+ "end_line": 4656,
+ "section": "List items"
+ },
+ {
+ "markdown": "- foo\n- \n- bar\n",
+ "html": "<ul>\n<li>foo</li>\n<li></li>\n<li>bar</li>\n</ul>\n",
+ "example": 282,
+ "start_line": 4661,
+ "end_line": 4671,
+ "section": "List items"
+ },
+ {
+ "markdown": "1. foo\n2.\n3. bar\n",
+ "html": "<ol>\n<li>foo</li>\n<li></li>\n<li>bar</li>\n</ol>\n",
+ "example": 283,
+ "start_line": 4676,
+ "end_line": 4686,
+ "section": "List items"
+ },
+ {
+ "markdown": "*\n",
+ "html": "<ul>\n<li></li>\n</ul>\n",
+ "example": 284,
+ "start_line": 4691,
+ "end_line": 4697,
+ "section": "List items"
+ },
+ {
+ "markdown": "foo\n*\n\nfoo\n1.\n",
+ "html": "<p>foo\n*</p>\n<p>foo\n1.</p>\n",
+ "example": 285,
+ "start_line": 4701,
+ "end_line": 4712,
+ "section": "List items"
+ },
+ {
+ "markdown": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n",
+ "html": "<ol>\n<li>\n<p>A paragraph\nwith two lines.</p>\n<pre><code>indented code\n</code></pre>\n<blockquote>\n<p>A block quote.</p>\n</blockquote>\n</li>\n</ol>\n",
+ "example": 286,
+ "start_line": 4723,
+ "end_line": 4742,
+ "section": "List items"
+ },
+ {
+ "markdown": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n",
+ "html": "<ol>\n<li>\n<p>A paragraph\nwith two lines.</p>\n<pre><code>indented code\n</code></pre>\n<blockquote>\n<p>A block quote.</p>\n</blockquote>\n</li>\n</ol>\n",
+ "example": 287,
+ "start_line": 4747,
+ "end_line": 4766,
+ "section": "List items"
+ },
+ {
+ "markdown": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n",
+ "html": "<ol>\n<li>\n<p>A paragraph\nwith two lines.</p>\n<pre><code>indented code\n</code></pre>\n<blockquote>\n<p>A block quote.</p>\n</blockquote>\n</li>\n</ol>\n",
+ "example": 288,
+ "start_line": 4771,
+ "end_line": 4790,
+ "section": "List items"
+ },
+ {
+ "markdown": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n",
+ "html": "<pre><code>1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n</code></pre>\n",
+ "example": 289,
+ "start_line": 4795,
+ "end_line": 4810,
+ "section": "List items"
+ },
+ {
+ "markdown": " 1. A paragraph\nwith two lines.\n\n indented code\n\n > A block quote.\n",
+ "html": "<ol>\n<li>\n<p>A paragraph\nwith two lines.</p>\n<pre><code>indented code\n</code></pre>\n<blockquote>\n<p>A block quote.</p>\n</blockquote>\n</li>\n</ol>\n",
+ "example": 290,
+ "start_line": 4825,
+ "end_line": 4844,
+ "section": "List items"
+ },
+ {
+ "markdown": " 1. A paragraph\n with two lines.\n",
+ "html": "<ol>\n<li>A paragraph\nwith two lines.</li>\n</ol>\n",
+ "example": 291,
+ "start_line": 4849,
+ "end_line": 4857,
+ "section": "List items"
+ },
+ {
+ "markdown": "> 1. > Blockquote\ncontinued here.\n",
+ "html": "<blockquote>\n<ol>\n<li>\n<blockquote>\n<p>Blockquote\ncontinued here.</p>\n</blockquote>\n</li>\n</ol>\n</blockquote>\n",
+ "example": 292,
+ "start_line": 4862,
+ "end_line": 4876,
+ "section": "List items"
+ },
+ {
+ "markdown": "> 1. > Blockquote\n> continued here.\n",
+ "html": "<blockquote>\n<ol>\n<li>\n<blockquote>\n<p>Blockquote\ncontinued here.</p>\n</blockquote>\n</li>\n</ol>\n</blockquote>\n",
+ "example": 293,
+ "start_line": 4879,
+ "end_line": 4893,
+ "section": "List items"
+ },
+ {
+ "markdown": "- foo\n - bar\n - baz\n - boo\n",
+ "html": "<ul>\n<li>foo\n<ul>\n<li>bar\n<ul>\n<li>baz\n<ul>\n<li>boo</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n",
+ "example": 294,
+ "start_line": 4907,
+ "end_line": 4928,
+ "section": "List items"
+ },
+ {
+ "markdown": "- foo\n - bar\n - baz\n - boo\n",
+ "html": "<ul>\n<li>foo</li>\n<li>bar</li>\n<li>baz</li>\n<li>boo</li>\n</ul>\n",
+ "example": 295,
+ "start_line": 4933,
+ "end_line": 4945,
+ "section": "List items"
+ },
+ {
+ "markdown": "10) foo\n - bar\n",
+ "html": "<ol start=\"10\">\n<li>foo\n<ul>\n<li>bar</li>\n</ul>\n</li>\n</ol>\n",
+ "example": 296,
+ "start_line": 4950,
+ "end_line": 4961,
+ "section": "List items"
+ },
+ {
+ "markdown": "10) foo\n - bar\n",
+ "html": "<ol start=\"10\">\n<li>foo</li>\n</ol>\n<ul>\n<li>bar</li>\n</ul>\n",
+ "example": 297,
+ "start_line": 4966,
+ "end_line": 4976,
+ "section": "List items"
+ },
+ {
+ "markdown": "- - foo\n",
+ "html": "<ul>\n<li>\n<ul>\n<li>foo</li>\n</ul>\n</li>\n</ul>\n",
+ "example": 298,
+ "start_line": 4981,
+ "end_line": 4991,
+ "section": "List items"
+ },
+ {
+ "markdown": "1. - 2. foo\n",
+ "html": "<ol>\n<li>\n<ul>\n<li>\n<ol start=\"2\">\n<li>foo</li>\n</ol>\n</li>\n</ul>\n</li>\n</ol>\n",
+ "example": 299,
+ "start_line": 4994,
+ "end_line": 5008,
+ "section": "List items"
+ },
+ {
+ "markdown": "- # Foo\n- Bar\n ---\n baz\n",
+ "html": "<ul>\n<li>\n<h1>Foo</h1>\n</li>\n<li>\n<h2>Bar</h2>\nbaz</li>\n</ul>\n",
+ "example": 300,
+ "start_line": 5013,
+ "end_line": 5027,
+ "section": "List items"
+ },
+ {
+ "markdown": "- foo\n- bar\n+ baz\n",
+ "html": "<ul>\n<li>foo</li>\n<li>bar</li>\n</ul>\n<ul>\n<li>baz</li>\n</ul>\n",
+ "example": 301,
+ "start_line": 5249,
+ "end_line": 5261,
+ "section": "Lists"
+ },
+ {
+ "markdown": "1. foo\n2. bar\n3) baz\n",
+ "html": "<ol>\n<li>foo</li>\n<li>bar</li>\n</ol>\n<ol start=\"3\">\n<li>baz</li>\n</ol>\n",
+ "example": 302,
+ "start_line": 5264,
+ "end_line": 5276,
+ "section": "Lists"
+ },
+ {
+ "markdown": "Foo\n- bar\n- baz\n",
+ "html": "<p>Foo</p>\n<ul>\n<li>bar</li>\n<li>baz</li>\n</ul>\n",
+ "example": 303,
+ "start_line": 5283,
+ "end_line": 5293,
+ "section": "Lists"
+ },
+ {
+ "markdown": "The number of windows in my house is\n14. The number of doors is 6.\n",
+ "html": "<p>The number of windows in my house is\n14. The number of doors is 6.</p>\n",
+ "example": 304,
+ "start_line": 5360,
+ "end_line": 5366,
+ "section": "Lists"
+ },
+ {
+ "markdown": "The number of windows in my house is\n1. The number of doors is 6.\n",
+ "html": "<p>The number of windows in my house is</p>\n<ol>\n<li>The number of doors is 6.</li>\n</ol>\n",
+ "example": 305,
+ "start_line": 5370,
+ "end_line": 5378,
+ "section": "Lists"
+ },
+ {
+ "markdown": "- foo\n\n- bar\n\n\n- baz\n",
+ "html": "<ul>\n<li>\n<p>foo</p>\n</li>\n<li>\n<p>bar</p>\n</li>\n<li>\n<p>baz</p>\n</li>\n</ul>\n",
+ "example": 306,
+ "start_line": 5384,
+ "end_line": 5403,
+ "section": "Lists"
+ },
+ {
+ "markdown": "- foo\n - bar\n - baz\n\n\n bim\n",
+ "html": "<ul>\n<li>foo\n<ul>\n<li>bar\n<ul>\n<li>\n<p>baz</p>\n<p>bim</p>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n",
+ "example": 307,
+ "start_line": 5405,
+ "end_line": 5427,
+ "section": "Lists"
+ },
+ {
+ "markdown": "- foo\n- bar\n\n<!-- -->\n\n- baz\n- bim\n",
+ "html": "<ul>\n<li>foo</li>\n<li>bar</li>\n</ul>\n<!-- -->\n<ul>\n<li>baz</li>\n<li>bim</li>\n</ul>\n",
+ "example": 308,
+ "start_line": 5435,
+ "end_line": 5453,
+ "section": "Lists"
+ },
+ {
+ "markdown": "- foo\n\n notcode\n\n- foo\n\n<!-- -->\n\n code\n",
+ "html": "<ul>\n<li>\n<p>foo</p>\n<p>notcode</p>\n</li>\n<li>\n<p>foo</p>\n</li>\n</ul>\n<!-- -->\n<pre><code>code\n</code></pre>\n",
+ "example": 309,
+ "start_line": 5456,
+ "end_line": 5479,
+ "section": "Lists"
+ },
+ {
+ "markdown": "- a\n - b\n - c\n - d\n - e\n - f\n- g\n",
+ "html": "<ul>\n<li>a</li>\n<li>b</li>\n<li>c</li>\n<li>d</li>\n<li>e</li>\n<li>f</li>\n<li>g</li>\n</ul>\n",
+ "example": 310,
+ "start_line": 5487,
+ "end_line": 5505,
+ "section": "Lists"
+ },
+ {
+ "markdown": "1. a\n\n 2. b\n\n 3. c\n",
+ "html": "<ol>\n<li>\n<p>a</p>\n</li>\n<li>\n<p>b</p>\n</li>\n<li>\n<p>c</p>\n</li>\n</ol>\n",
+ "example": 311,
+ "start_line": 5508,
+ "end_line": 5526,
+ "section": "Lists"
+ },
+ {
+ "markdown": "- a\n - b\n - c\n - d\n - e\n",
+ "html": "<ul>\n<li>a</li>\n<li>b</li>\n<li>c</li>\n<li>d\n- e</li>\n</ul>\n",
+ "example": 312,
+ "start_line": 5532,
+ "end_line": 5546,
+ "section": "Lists"
+ },
+ {
+ "markdown": "1. a\n\n 2. b\n\n 3. c\n",
+ "html": "<ol>\n<li>\n<p>a</p>\n</li>\n<li>\n<p>b</p>\n</li>\n</ol>\n<pre><code>3. c\n</code></pre>\n",
+ "example": 313,
+ "start_line": 5552,
+ "end_line": 5569,
+ "section": "Lists"
+ },
+ {
+ "markdown": "- a\n- b\n\n- c\n",
+ "html": "<ul>\n<li>\n<p>a</p>\n</li>\n<li>\n<p>b</p>\n</li>\n<li>\n<p>c</p>\n</li>\n</ul>\n",
+ "example": 314,
+ "start_line": 5575,
+ "end_line": 5592,
+ "section": "Lists"
+ },
+ {
+ "markdown": "* a\n*\n\n* c\n",
+ "html": "<ul>\n<li>\n<p>a</p>\n</li>\n<li></li>\n<li>\n<p>c</p>\n</li>\n</ul>\n",
+ "example": 315,
+ "start_line": 5597,
+ "end_line": 5612,
+ "section": "Lists"
+ },
+ {
+ "markdown": "- a\n- b\n\n c\n- d\n",
+ "html": "<ul>\n<li>\n<p>a</p>\n</li>\n<li>\n<p>b</p>\n<p>c</p>\n</li>\n<li>\n<p>d</p>\n</li>\n</ul>\n",
+ "example": 316,
+ "start_line": 5619,
+ "end_line": 5638,
+ "section": "Lists"
+ },
+ {
+ "markdown": "- a\n- b\n\n [ref]: /url\n- d\n",
+ "html": "<ul>\n<li>\n<p>a</p>\n</li>\n<li>\n<p>b</p>\n</li>\n<li>\n<p>d</p>\n</li>\n</ul>\n",
+ "example": 317,
+ "start_line": 5641,
+ "end_line": 5659,
+ "section": "Lists"
+ },
+ {
+ "markdown": "- a\n- ```\n b\n\n\n ```\n- c\n",
+ "html": "<ul>\n<li>a</li>\n<li>\n<pre><code>b\n\n\n</code></pre>\n</li>\n<li>c</li>\n</ul>\n",
+ "example": 318,
+ "start_line": 5664,
+ "end_line": 5683,
+ "section": "Lists"
+ },
+ {
+ "markdown": "- a\n - b\n\n c\n- d\n",
+ "html": "<ul>\n<li>a\n<ul>\n<li>\n<p>b</p>\n<p>c</p>\n</li>\n</ul>\n</li>\n<li>d</li>\n</ul>\n",
+ "example": 319,
+ "start_line": 5690,
+ "end_line": 5708,
+ "section": "Lists"
+ },
+ {
+ "markdown": "* a\n > b\n >\n* c\n",
+ "html": "<ul>\n<li>a\n<blockquote>\n<p>b</p>\n</blockquote>\n</li>\n<li>c</li>\n</ul>\n",
+ "example": 320,
+ "start_line": 5714,
+ "end_line": 5728,
+ "section": "Lists"
+ },
+ {
+ "markdown": "- a\n > b\n ```\n c\n ```\n- d\n",
+ "html": "<ul>\n<li>a\n<blockquote>\n<p>b</p>\n</blockquote>\n<pre><code>c\n</code></pre>\n</li>\n<li>d</li>\n</ul>\n",
+ "example": 321,
+ "start_line": 5734,
+ "end_line": 5752,
+ "section": "Lists"
+ },
+ {
+ "markdown": "- a\n",
+ "html": "<ul>\n<li>a</li>\n</ul>\n",
+ "example": 322,
+ "start_line": 5757,
+ "end_line": 5763,
+ "section": "Lists"
+ },
+ {
+ "markdown": "- a\n - b\n",
+ "html": "<ul>\n<li>a\n<ul>\n<li>b</li>\n</ul>\n</li>\n</ul>\n",
+ "example": 323,
+ "start_line": 5766,
+ "end_line": 5777,
+ "section": "Lists"
+ },
+ {
+ "markdown": "1. ```\n foo\n ```\n\n bar\n",
+ "html": "<ol>\n<li>\n<pre><code>foo\n</code></pre>\n<p>bar</p>\n</li>\n</ol>\n",
+ "example": 324,
+ "start_line": 5783,
+ "end_line": 5797,
+ "section": "Lists"
+ },
+ {
+ "markdown": "* foo\n * bar\n\n baz\n",
+ "html": "<ul>\n<li>\n<p>foo</p>\n<ul>\n<li>bar</li>\n</ul>\n<p>baz</p>\n</li>\n</ul>\n",
+ "example": 325,
+ "start_line": 5802,
+ "end_line": 5817,
+ "section": "Lists"
+ },
+ {
+ "markdown": "- a\n - b\n - c\n\n- d\n - e\n - f\n",
+ "html": "<ul>\n<li>\n<p>a</p>\n<ul>\n<li>b</li>\n<li>c</li>\n</ul>\n</li>\n<li>\n<p>d</p>\n<ul>\n<li>e</li>\n<li>f</li>\n</ul>\n</li>\n</ul>\n",
+ "example": 326,
+ "start_line": 5820,
+ "end_line": 5845,
+ "section": "Lists"
+ },
+ {
+ "markdown": "`hi`lo`\n",
+ "html": "<p><code>hi</code>lo`</p>\n",
+ "example": 327,
+ "start_line": 5854,
+ "end_line": 5858,
+ "section": "Inlines"
+ },
+ {
+ "markdown": "`foo`\n",
+ "html": "<p><code>foo</code></p>\n",
+ "example": 328,
+ "start_line": 5886,
+ "end_line": 5890,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "`` foo ` bar ``\n",
+ "html": "<p><code>foo ` bar</code></p>\n",
+ "example": 329,
+ "start_line": 5897,
+ "end_line": 5901,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "` `` `\n",
+ "html": "<p><code>``</code></p>\n",
+ "example": 330,
+ "start_line": 5907,
+ "end_line": 5911,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "` `` `\n",
+ "html": "<p><code> `` </code></p>\n",
+ "example": 331,
+ "start_line": 5915,
+ "end_line": 5919,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "` a`\n",
+ "html": "<p><code> a</code></p>\n",
+ "example": 332,
+ "start_line": 5924,
+ "end_line": 5928,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "` b `\n",
+ "html": "<p><code> b </code></p>\n",
+ "example": 333,
+ "start_line": 5933,
+ "end_line": 5937,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "` `\n` `\n",
+ "html": "<p><code> </code>\n<code> </code></p>\n",
+ "example": 334,
+ "start_line": 5941,
+ "end_line": 5947,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "``\nfoo\nbar \nbaz\n``\n",
+ "html": "<p><code>foo bar baz</code></p>\n",
+ "example": 335,
+ "start_line": 5952,
+ "end_line": 5960,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "``\nfoo \n``\n",
+ "html": "<p><code>foo </code></p>\n",
+ "example": 336,
+ "start_line": 5962,
+ "end_line": 5968,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "`foo bar \nbaz`\n",
+ "html": "<p><code>foo bar baz</code></p>\n",
+ "example": 337,
+ "start_line": 5973,
+ "end_line": 5978,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "`foo\\`bar`\n",
+ "html": "<p><code>foo\\</code>bar`</p>\n",
+ "example": 338,
+ "start_line": 5990,
+ "end_line": 5994,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "``foo`bar``\n",
+ "html": "<p><code>foo`bar</code></p>\n",
+ "example": 339,
+ "start_line": 6001,
+ "end_line": 6005,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "` foo `` bar `\n",
+ "html": "<p><code>foo `` bar</code></p>\n",
+ "example": 340,
+ "start_line": 6007,
+ "end_line": 6011,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "*foo`*`\n",
+ "html": "<p>*foo<code>*</code></p>\n",
+ "example": 341,
+ "start_line": 6019,
+ "end_line": 6023,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "[not a `link](/foo`)\n",
+ "html": "<p>[not a <code>link](/foo</code>)</p>\n",
+ "example": 342,
+ "start_line": 6028,
+ "end_line": 6032,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "`<a href=\"`\">`\n",
+ "html": "<p><code><a href="</code>">`</p>\n",
+ "example": 343,
+ "start_line": 6038,
+ "end_line": 6042,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "<a href=\"`\">`\n",
+ "html": "<p><a href=\"`\">`</p>\n",
+ "example": 344,
+ "start_line": 6047,
+ "end_line": 6051,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "`<https://foo.bar.`baz>`\n",
+ "html": "<p><code><https://foo.bar.</code>baz>`</p>\n",
+ "example": 345,
+ "start_line": 6056,
+ "end_line": 6060,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "<https://foo.bar.`baz>`\n",
+ "html": "<p><a href=\"https://foo.bar.%60baz\">https://foo.bar.`baz</a>`</p>\n",
+ "example": 346,
+ "start_line": 6065,
+ "end_line": 6069,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "```foo``\n",
+ "html": "<p>```foo``</p>\n",
+ "example": 347,
+ "start_line": 6075,
+ "end_line": 6079,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "`foo\n",
+ "html": "<p>`foo</p>\n",
+ "example": 348,
+ "start_line": 6082,
+ "end_line": 6086,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "`foo``bar``\n",
+ "html": "<p>`foo<code>bar</code></p>\n",
+ "example": 349,
+ "start_line": 6091,
+ "end_line": 6095,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "*foo bar*\n",
+ "html": "<p><em>foo bar</em></p>\n",
+ "example": 350,
+ "start_line": 6308,
+ "end_line": 6312,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "a * foo bar*\n",
+ "html": "<p>a * foo bar*</p>\n",
+ "example": 351,
+ "start_line": 6318,
+ "end_line": 6322,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "a*\"foo\"*\n",
+ "html": "<p>a*"foo"*</p>\n",
+ "example": 352,
+ "start_line": 6329,
+ "end_line": 6333,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "* a *\n",
+ "html": "<p>* a *</p>\n",
+ "example": 353,
+ "start_line": 6338,
+ "end_line": 6342,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*$*alpha.\n\n*£*bravo.\n\n*€*charlie.\n",
+ "html": "<p>*$*alpha.</p>\n<p>*£*bravo.</p>\n<p>*€*charlie.</p>\n",
+ "example": 354,
+ "start_line": 6347,
+ "end_line": 6357,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo*bar*\n",
+ "html": "<p>foo<em>bar</em></p>\n",
+ "example": 355,
+ "start_line": 6362,
+ "end_line": 6366,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "5*6*78\n",
+ "html": "<p>5<em>6</em>78</p>\n",
+ "example": 356,
+ "start_line": 6369,
+ "end_line": 6373,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_foo bar_\n",
+ "html": "<p><em>foo bar</em></p>\n",
+ "example": 357,
+ "start_line": 6378,
+ "end_line": 6382,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_ foo bar_\n",
+ "html": "<p>_ foo bar_</p>\n",
+ "example": 358,
+ "start_line": 6388,
+ "end_line": 6392,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "a_\"foo\"_\n",
+ "html": "<p>a_"foo"_</p>\n",
+ "example": 359,
+ "start_line": 6398,
+ "end_line": 6402,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo_bar_\n",
+ "html": "<p>foo_bar_</p>\n",
+ "example": 360,
+ "start_line": 6407,
+ "end_line": 6411,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "5_6_78\n",
+ "html": "<p>5_6_78</p>\n",
+ "example": 361,
+ "start_line": 6414,
+ "end_line": 6418,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "пристаням_стремятся_\n",
+ "html": "<p>пристаням_стремятся_</p>\n",
+ "example": 362,
+ "start_line": 6421,
+ "end_line": 6425,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "aa_\"bb\"_cc\n",
+ "html": "<p>aa_"bb"_cc</p>\n",
+ "example": 363,
+ "start_line": 6431,
+ "end_line": 6435,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo-_(bar)_\n",
+ "html": "<p>foo-<em>(bar)</em></p>\n",
+ "example": 364,
+ "start_line": 6442,
+ "end_line": 6446,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_foo*\n",
+ "html": "<p>_foo*</p>\n",
+ "example": 365,
+ "start_line": 6454,
+ "end_line": 6458,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo bar *\n",
+ "html": "<p>*foo bar *</p>\n",
+ "example": 366,
+ "start_line": 6464,
+ "end_line": 6468,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo bar\n*\n",
+ "html": "<p>*foo bar\n*</p>\n",
+ "example": 367,
+ "start_line": 6473,
+ "end_line": 6479,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*(*foo)\n",
+ "html": "<p>*(*foo)</p>\n",
+ "example": 368,
+ "start_line": 6486,
+ "end_line": 6490,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*(*foo*)*\n",
+ "html": "<p><em>(<em>foo</em>)</em></p>\n",
+ "example": 369,
+ "start_line": 6496,
+ "end_line": 6500,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo*bar\n",
+ "html": "<p><em>foo</em>bar</p>\n",
+ "example": 370,
+ "start_line": 6505,
+ "end_line": 6509,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_foo bar _\n",
+ "html": "<p>_foo bar _</p>\n",
+ "example": 371,
+ "start_line": 6518,
+ "end_line": 6522,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_(_foo)\n",
+ "html": "<p>_(_foo)</p>\n",
+ "example": 372,
+ "start_line": 6528,
+ "end_line": 6532,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_(_foo_)_\n",
+ "html": "<p><em>(<em>foo</em>)</em></p>\n",
+ "example": 373,
+ "start_line": 6537,
+ "end_line": 6541,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_foo_bar\n",
+ "html": "<p>_foo_bar</p>\n",
+ "example": 374,
+ "start_line": 6546,
+ "end_line": 6550,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_пристаням_стремятся\n",
+ "html": "<p>_пристаням_стремятся</p>\n",
+ "example": 375,
+ "start_line": 6553,
+ "end_line": 6557,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_foo_bar_baz_\n",
+ "html": "<p><em>foo_bar_baz</em></p>\n",
+ "example": 376,
+ "start_line": 6560,
+ "end_line": 6564,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_(bar)_.\n",
+ "html": "<p><em>(bar)</em>.</p>\n",
+ "example": 377,
+ "start_line": 6571,
+ "end_line": 6575,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo bar**\n",
+ "html": "<p><strong>foo bar</strong></p>\n",
+ "example": 378,
+ "start_line": 6580,
+ "end_line": 6584,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "** foo bar**\n",
+ "html": "<p>** foo bar**</p>\n",
+ "example": 379,
+ "start_line": 6590,
+ "end_line": 6594,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "a**\"foo\"**\n",
+ "html": "<p>a**"foo"**</p>\n",
+ "example": 380,
+ "start_line": 6601,
+ "end_line": 6605,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo**bar**\n",
+ "html": "<p>foo<strong>bar</strong></p>\n",
+ "example": 381,
+ "start_line": 6610,
+ "end_line": 6614,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__foo bar__\n",
+ "html": "<p><strong>foo bar</strong></p>\n",
+ "example": 382,
+ "start_line": 6619,
+ "end_line": 6623,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__ foo bar__\n",
+ "html": "<p>__ foo bar__</p>\n",
+ "example": 383,
+ "start_line": 6629,
+ "end_line": 6633,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__\nfoo bar__\n",
+ "html": "<p>__\nfoo bar__</p>\n",
+ "example": 384,
+ "start_line": 6637,
+ "end_line": 6643,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "a__\"foo\"__\n",
+ "html": "<p>a__"foo"__</p>\n",
+ "example": 385,
+ "start_line": 6649,
+ "end_line": 6653,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo__bar__\n",
+ "html": "<p>foo__bar__</p>\n",
+ "example": 386,
+ "start_line": 6658,
+ "end_line": 6662,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "5__6__78\n",
+ "html": "<p>5__6__78</p>\n",
+ "example": 387,
+ "start_line": 6665,
+ "end_line": 6669,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "пристаням__стремятся__\n",
+ "html": "<p>пристаням__стремятся__</p>\n",
+ "example": 388,
+ "start_line": 6672,
+ "end_line": 6676,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__foo, __bar__, baz__\n",
+ "html": "<p><strong>foo, <strong>bar</strong>, baz</strong></p>\n",
+ "example": 389,
+ "start_line": 6679,
+ "end_line": 6683,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo-__(bar)__\n",
+ "html": "<p>foo-<strong>(bar)</strong></p>\n",
+ "example": 390,
+ "start_line": 6690,
+ "end_line": 6694,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo bar **\n",
+ "html": "<p>**foo bar **</p>\n",
+ "example": 391,
+ "start_line": 6703,
+ "end_line": 6707,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**(**foo)\n",
+ "html": "<p>**(**foo)</p>\n",
+ "example": 392,
+ "start_line": 6716,
+ "end_line": 6720,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*(**foo**)*\n",
+ "html": "<p><em>(<strong>foo</strong>)</em></p>\n",
+ "example": 393,
+ "start_line": 6726,
+ "end_line": 6730,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**Gomphocarpus (*Gomphocarpus physocarpus*, syn.\n*Asclepias physocarpa*)**\n",
+ "html": "<p><strong>Gomphocarpus (<em>Gomphocarpus physocarpus</em>, syn.\n<em>Asclepias physocarpa</em>)</strong></p>\n",
+ "example": 394,
+ "start_line": 6733,
+ "end_line": 6739,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo \"*bar*\" foo**\n",
+ "html": "<p><strong>foo "<em>bar</em>" foo</strong></p>\n",
+ "example": 395,
+ "start_line": 6742,
+ "end_line": 6746,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo**bar\n",
+ "html": "<p><strong>foo</strong>bar</p>\n",
+ "example": 396,
+ "start_line": 6751,
+ "end_line": 6755,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__foo bar __\n",
+ "html": "<p>__foo bar __</p>\n",
+ "example": 397,
+ "start_line": 6763,
+ "end_line": 6767,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__(__foo)\n",
+ "html": "<p>__(__foo)</p>\n",
+ "example": 398,
+ "start_line": 6773,
+ "end_line": 6777,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_(__foo__)_\n",
+ "html": "<p><em>(<strong>foo</strong>)</em></p>\n",
+ "example": 399,
+ "start_line": 6783,
+ "end_line": 6787,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__foo__bar\n",
+ "html": "<p>__foo__bar</p>\n",
+ "example": 400,
+ "start_line": 6792,
+ "end_line": 6796,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__пристаням__стремятся\n",
+ "html": "<p>__пристаням__стремятся</p>\n",
+ "example": 401,
+ "start_line": 6799,
+ "end_line": 6803,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__foo__bar__baz__\n",
+ "html": "<p><strong>foo__bar__baz</strong></p>\n",
+ "example": 402,
+ "start_line": 6806,
+ "end_line": 6810,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__(bar)__.\n",
+ "html": "<p><strong>(bar)</strong>.</p>\n",
+ "example": 403,
+ "start_line": 6817,
+ "end_line": 6821,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo [bar](/url)*\n",
+ "html": "<p><em>foo <a href=\"/url\">bar</a></em></p>\n",
+ "example": 404,
+ "start_line": 6829,
+ "end_line": 6833,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo\nbar*\n",
+ "html": "<p><em>foo\nbar</em></p>\n",
+ "example": 405,
+ "start_line": 6836,
+ "end_line": 6842,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_foo __bar__ baz_\n",
+ "html": "<p><em>foo <strong>bar</strong> baz</em></p>\n",
+ "example": 406,
+ "start_line": 6848,
+ "end_line": 6852,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_foo _bar_ baz_\n",
+ "html": "<p><em>foo <em>bar</em> baz</em></p>\n",
+ "example": 407,
+ "start_line": 6855,
+ "end_line": 6859,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__foo_ bar_\n",
+ "html": "<p><em><em>foo</em> bar</em></p>\n",
+ "example": 408,
+ "start_line": 6862,
+ "end_line": 6866,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo *bar**\n",
+ "html": "<p><em>foo <em>bar</em></em></p>\n",
+ "example": 409,
+ "start_line": 6869,
+ "end_line": 6873,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo **bar** baz*\n",
+ "html": "<p><em>foo <strong>bar</strong> baz</em></p>\n",
+ "example": 410,
+ "start_line": 6876,
+ "end_line": 6880,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo**bar**baz*\n",
+ "html": "<p><em>foo<strong>bar</strong>baz</em></p>\n",
+ "example": 411,
+ "start_line": 6882,
+ "end_line": 6886,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo**bar*\n",
+ "html": "<p><em>foo**bar</em></p>\n",
+ "example": 412,
+ "start_line": 6906,
+ "end_line": 6910,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "***foo** bar*\n",
+ "html": "<p><em><strong>foo</strong> bar</em></p>\n",
+ "example": 413,
+ "start_line": 6919,
+ "end_line": 6923,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo **bar***\n",
+ "html": "<p><em>foo <strong>bar</strong></em></p>\n",
+ "example": 414,
+ "start_line": 6926,
+ "end_line": 6930,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo**bar***\n",
+ "html": "<p><em>foo<strong>bar</strong></em></p>\n",
+ "example": 415,
+ "start_line": 6933,
+ "end_line": 6937,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo***bar***baz\n",
+ "html": "<p>foo<em><strong>bar</strong></em>baz</p>\n",
+ "example": 416,
+ "start_line": 6944,
+ "end_line": 6948,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo******bar*********baz\n",
+ "html": "<p>foo<strong><strong><strong>bar</strong></strong></strong>***baz</p>\n",
+ "example": 417,
+ "start_line": 6950,
+ "end_line": 6954,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo **bar *baz* bim** bop*\n",
+ "html": "<p><em>foo <strong>bar <em>baz</em> bim</strong> bop</em></p>\n",
+ "example": 418,
+ "start_line": 6959,
+ "end_line": 6963,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo [*bar*](/url)*\n",
+ "html": "<p><em>foo <a href=\"/url\"><em>bar</em></a></em></p>\n",
+ "example": 419,
+ "start_line": 6966,
+ "end_line": 6970,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "** is not an empty emphasis\n",
+ "html": "<p>** is not an empty emphasis</p>\n",
+ "example": 420,
+ "start_line": 6975,
+ "end_line": 6979,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**** is not an empty strong emphasis\n",
+ "html": "<p>**** is not an empty strong emphasis</p>\n",
+ "example": 421,
+ "start_line": 6982,
+ "end_line": 6986,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo [bar](/url)**\n",
+ "html": "<p><strong>foo <a href=\"/url\">bar</a></strong></p>\n",
+ "example": 422,
+ "start_line": 6995,
+ "end_line": 6999,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo\nbar**\n",
+ "html": "<p><strong>foo\nbar</strong></p>\n",
+ "example": 423,
+ "start_line": 7002,
+ "end_line": 7008,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__foo _bar_ baz__\n",
+ "html": "<p><strong>foo <em>bar</em> baz</strong></p>\n",
+ "example": 424,
+ "start_line": 7014,
+ "end_line": 7018,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__foo __bar__ baz__\n",
+ "html": "<p><strong>foo <strong>bar</strong> baz</strong></p>\n",
+ "example": 425,
+ "start_line": 7021,
+ "end_line": 7025,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "____foo__ bar__\n",
+ "html": "<p><strong><strong>foo</strong> bar</strong></p>\n",
+ "example": 426,
+ "start_line": 7028,
+ "end_line": 7032,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo **bar****\n",
+ "html": "<p><strong>foo <strong>bar</strong></strong></p>\n",
+ "example": 427,
+ "start_line": 7035,
+ "end_line": 7039,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo *bar* baz**\n",
+ "html": "<p><strong>foo <em>bar</em> baz</strong></p>\n",
+ "example": 428,
+ "start_line": 7042,
+ "end_line": 7046,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo*bar*baz**\n",
+ "html": "<p><strong>foo<em>bar</em>baz</strong></p>\n",
+ "example": 429,
+ "start_line": 7049,
+ "end_line": 7053,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "***foo* bar**\n",
+ "html": "<p><strong><em>foo</em> bar</strong></p>\n",
+ "example": 430,
+ "start_line": 7056,
+ "end_line": 7060,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo *bar***\n",
+ "html": "<p><strong>foo <em>bar</em></strong></p>\n",
+ "example": 431,
+ "start_line": 7063,
+ "end_line": 7067,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo *bar **baz**\nbim* bop**\n",
+ "html": "<p><strong>foo <em>bar <strong>baz</strong>\nbim</em> bop</strong></p>\n",
+ "example": 432,
+ "start_line": 7072,
+ "end_line": 7078,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo [*bar*](/url)**\n",
+ "html": "<p><strong>foo <a href=\"/url\"><em>bar</em></a></strong></p>\n",
+ "example": 433,
+ "start_line": 7081,
+ "end_line": 7085,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__ is not an empty emphasis\n",
+ "html": "<p>__ is not an empty emphasis</p>\n",
+ "example": 434,
+ "start_line": 7090,
+ "end_line": 7094,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "____ is not an empty strong emphasis\n",
+ "html": "<p>____ is not an empty strong emphasis</p>\n",
+ "example": 435,
+ "start_line": 7097,
+ "end_line": 7101,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo ***\n",
+ "html": "<p>foo ***</p>\n",
+ "example": 436,
+ "start_line": 7107,
+ "end_line": 7111,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo *\\**\n",
+ "html": "<p>foo <em>*</em></p>\n",
+ "example": 437,
+ "start_line": 7114,
+ "end_line": 7118,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo *_*\n",
+ "html": "<p>foo <em>_</em></p>\n",
+ "example": 438,
+ "start_line": 7121,
+ "end_line": 7125,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo *****\n",
+ "html": "<p>foo *****</p>\n",
+ "example": 439,
+ "start_line": 7128,
+ "end_line": 7132,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo **\\***\n",
+ "html": "<p>foo <strong>*</strong></p>\n",
+ "example": 440,
+ "start_line": 7135,
+ "end_line": 7139,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo **_**\n",
+ "html": "<p>foo <strong>_</strong></p>\n",
+ "example": 441,
+ "start_line": 7142,
+ "end_line": 7146,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo*\n",
+ "html": "<p>*<em>foo</em></p>\n",
+ "example": 442,
+ "start_line": 7153,
+ "end_line": 7157,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo**\n",
+ "html": "<p><em>foo</em>*</p>\n",
+ "example": 443,
+ "start_line": 7160,
+ "end_line": 7164,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "***foo**\n",
+ "html": "<p>*<strong>foo</strong></p>\n",
+ "example": 444,
+ "start_line": 7167,
+ "end_line": 7171,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "****foo*\n",
+ "html": "<p>***<em>foo</em></p>\n",
+ "example": 445,
+ "start_line": 7174,
+ "end_line": 7178,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo***\n",
+ "html": "<p><strong>foo</strong>*</p>\n",
+ "example": 446,
+ "start_line": 7181,
+ "end_line": 7185,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo****\n",
+ "html": "<p><em>foo</em>***</p>\n",
+ "example": 447,
+ "start_line": 7188,
+ "end_line": 7192,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo ___\n",
+ "html": "<p>foo ___</p>\n",
+ "example": 448,
+ "start_line": 7198,
+ "end_line": 7202,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo _\\__\n",
+ "html": "<p>foo <em>_</em></p>\n",
+ "example": 449,
+ "start_line": 7205,
+ "end_line": 7209,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo _*_\n",
+ "html": "<p>foo <em>*</em></p>\n",
+ "example": 450,
+ "start_line": 7212,
+ "end_line": 7216,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo _____\n",
+ "html": "<p>foo _____</p>\n",
+ "example": 451,
+ "start_line": 7219,
+ "end_line": 7223,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo __\\___\n",
+ "html": "<p>foo <strong>_</strong></p>\n",
+ "example": 452,
+ "start_line": 7226,
+ "end_line": 7230,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo __*__\n",
+ "html": "<p>foo <strong>*</strong></p>\n",
+ "example": 453,
+ "start_line": 7233,
+ "end_line": 7237,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__foo_\n",
+ "html": "<p>_<em>foo</em></p>\n",
+ "example": 454,
+ "start_line": 7240,
+ "end_line": 7244,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_foo__\n",
+ "html": "<p><em>foo</em>_</p>\n",
+ "example": 455,
+ "start_line": 7251,
+ "end_line": 7255,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "___foo__\n",
+ "html": "<p>_<strong>foo</strong></p>\n",
+ "example": 456,
+ "start_line": 7258,
+ "end_line": 7262,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "____foo_\n",
+ "html": "<p>___<em>foo</em></p>\n",
+ "example": 457,
+ "start_line": 7265,
+ "end_line": 7269,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__foo___\n",
+ "html": "<p><strong>foo</strong>_</p>\n",
+ "example": 458,
+ "start_line": 7272,
+ "end_line": 7276,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_foo____\n",
+ "html": "<p><em>foo</em>___</p>\n",
+ "example": 459,
+ "start_line": 7279,
+ "end_line": 7283,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo**\n",
+ "html": "<p><strong>foo</strong></p>\n",
+ "example": 460,
+ "start_line": 7289,
+ "end_line": 7293,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*_foo_*\n",
+ "html": "<p><em><em>foo</em></em></p>\n",
+ "example": 461,
+ "start_line": 7296,
+ "end_line": 7300,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__foo__\n",
+ "html": "<p><strong>foo</strong></p>\n",
+ "example": 462,
+ "start_line": 7303,
+ "end_line": 7307,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_*foo*_\n",
+ "html": "<p><em><em>foo</em></em></p>\n",
+ "example": 463,
+ "start_line": 7310,
+ "end_line": 7314,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "****foo****\n",
+ "html": "<p><strong><strong>foo</strong></strong></p>\n",
+ "example": 464,
+ "start_line": 7320,
+ "end_line": 7324,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "____foo____\n",
+ "html": "<p><strong><strong>foo</strong></strong></p>\n",
+ "example": 465,
+ "start_line": 7327,
+ "end_line": 7331,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "******foo******\n",
+ "html": "<p><strong><strong><strong>foo</strong></strong></strong></p>\n",
+ "example": 466,
+ "start_line": 7338,
+ "end_line": 7342,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "***foo***\n",
+ "html": "<p><em><strong>foo</strong></em></p>\n",
+ "example": 467,
+ "start_line": 7347,
+ "end_line": 7351,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_____foo_____\n",
+ "html": "<p><em><strong><strong>foo</strong></strong></em></p>\n",
+ "example": 468,
+ "start_line": 7354,
+ "end_line": 7358,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo _bar* baz_\n",
+ "html": "<p><em>foo _bar</em> baz_</p>\n",
+ "example": 469,
+ "start_line": 7363,
+ "end_line": 7367,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo __bar *baz bim__ bam*\n",
+ "html": "<p><em>foo <strong>bar *baz bim</strong> bam</em></p>\n",
+ "example": 470,
+ "start_line": 7370,
+ "end_line": 7374,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo **bar baz**\n",
+ "html": "<p>**foo <strong>bar baz</strong></p>\n",
+ "example": 471,
+ "start_line": 7379,
+ "end_line": 7383,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo *bar baz*\n",
+ "html": "<p>*foo <em>bar baz</em></p>\n",
+ "example": 472,
+ "start_line": 7386,
+ "end_line": 7390,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*[bar*](/url)\n",
+ "html": "<p>*<a href=\"/url\">bar*</a></p>\n",
+ "example": 473,
+ "start_line": 7395,
+ "end_line": 7399,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_foo [bar_](/url)\n",
+ "html": "<p>_foo <a href=\"/url\">bar_</a></p>\n",
+ "example": 474,
+ "start_line": 7402,
+ "end_line": 7406,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*<img src=\"foo\" title=\"*\"/>\n",
+ "html": "<p>*<img src=\"foo\" title=\"*\"/></p>\n",
+ "example": 475,
+ "start_line": 7409,
+ "end_line": 7413,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**<a href=\"**\">\n",
+ "html": "<p>**<a href=\"**\"></p>\n",
+ "example": 476,
+ "start_line": 7416,
+ "end_line": 7420,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__<a href=\"__\">\n",
+ "html": "<p>__<a href=\"__\"></p>\n",
+ "example": 477,
+ "start_line": 7423,
+ "end_line": 7427,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*a `*`*\n",
+ "html": "<p><em>a <code>*</code></em></p>\n",
+ "example": 478,
+ "start_line": 7430,
+ "end_line": 7434,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_a `_`_\n",
+ "html": "<p><em>a <code>_</code></em></p>\n",
+ "example": 479,
+ "start_line": 7437,
+ "end_line": 7441,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**a<https://foo.bar/?q=**>\n",
+ "html": "<p>**a<a href=\"https://foo.bar/?q=**\">https://foo.bar/?q=**</a></p>\n",
+ "example": 480,
+ "start_line": 7444,
+ "end_line": 7448,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__a<https://foo.bar/?q=__>\n",
+ "html": "<p>__a<a href=\"https://foo.bar/?q=__\">https://foo.bar/?q=__</a></p>\n",
+ "example": 481,
+ "start_line": 7451,
+ "end_line": 7455,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "[link](/uri \"title\")\n",
+ "html": "<p><a href=\"/uri\" title=\"title\">link</a></p>\n",
+ "example": 482,
+ "start_line": 7539,
+ "end_line": 7543,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](/uri)\n",
+ "html": "<p><a href=\"/uri\">link</a></p>\n",
+ "example": 483,
+ "start_line": 7549,
+ "end_line": 7553,
+ "section": "Links"
+ },
+ {
+ "markdown": "[](./target.md)\n",
+ "html": "<p><a href=\"./target.md\"></a></p>\n",
+ "example": 484,
+ "start_line": 7555,
+ "end_line": 7559,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link]()\n",
+ "html": "<p><a href=\"\">link</a></p>\n",
+ "example": 485,
+ "start_line": 7562,
+ "end_line": 7566,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](<>)\n",
+ "html": "<p><a href=\"\">link</a></p>\n",
+ "example": 486,
+ "start_line": 7569,
+ "end_line": 7573,
+ "section": "Links"
+ },
+ {
+ "markdown": "[]()\n",
+ "html": "<p><a href=\"\"></a></p>\n",
+ "example": 487,
+ "start_line": 7576,
+ "end_line": 7580,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](/my uri)\n",
+ "html": "<p>[link](/my uri)</p>\n",
+ "example": 488,
+ "start_line": 7585,
+ "end_line": 7589,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](</my uri>)\n",
+ "html": "<p><a href=\"/my%20uri\">link</a></p>\n",
+ "example": 489,
+ "start_line": 7591,
+ "end_line": 7595,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](foo\nbar)\n",
+ "html": "<p>[link](foo\nbar)</p>\n",
+ "example": 490,
+ "start_line": 7600,
+ "end_line": 7606,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](<foo\nbar>)\n",
+ "html": "<p>[link](<foo\nbar>)</p>\n",
+ "example": 491,
+ "start_line": 7608,
+ "end_line": 7614,
+ "section": "Links"
+ },
+ {
+ "markdown": "[a](<b)c>)\n",
+ "html": "<p><a href=\"b)c\">a</a></p>\n",
+ "example": 492,
+ "start_line": 7619,
+ "end_line": 7623,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](<foo\\>)\n",
+ "html": "<p>[link](<foo>)</p>\n",
+ "example": 493,
+ "start_line": 7627,
+ "end_line": 7631,
+ "section": "Links"
+ },
+ {
+ "markdown": "[a](<b)c\n[a](<b)c>\n[a](<b>c)\n",
+ "html": "<p>[a](<b)c\n[a](<b)c>\n[a](<b>c)</p>\n",
+ "example": 494,
+ "start_line": 7636,
+ "end_line": 7644,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](\\(foo\\))\n",
+ "html": "<p><a href=\"(foo)\">link</a></p>\n",
+ "example": 495,
+ "start_line": 7648,
+ "end_line": 7652,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](foo(and(bar)))\n",
+ "html": "<p><a href=\"foo(and(bar))\">link</a></p>\n",
+ "example": 496,
+ "start_line": 7657,
+ "end_line": 7661,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](foo(and(bar))\n",
+ "html": "<p>[link](foo(and(bar))</p>\n",
+ "example": 497,
+ "start_line": 7666,
+ "end_line": 7670,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](foo\\(and\\(bar\\))\n",
+ "html": "<p><a href=\"foo(and(bar)\">link</a></p>\n",
+ "example": 498,
+ "start_line": 7673,
+ "end_line": 7677,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](<foo(and(bar)>)\n",
+ "html": "<p><a href=\"foo(and(bar)\">link</a></p>\n",
+ "example": 499,
+ "start_line": 7680,
+ "end_line": 7684,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](foo\\)\\:)\n",
+ "html": "<p><a href=\"foo):\">link</a></p>\n",
+ "example": 500,
+ "start_line": 7690,
+ "end_line": 7694,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](#fragment)\n\n[link](https://example.com#fragment)\n\n[link](https://example.com?foo=3#frag)\n",
+ "html": "<p><a href=\"#fragment\">link</a></p>\n<p><a href=\"https://example.com#fragment\">link</a></p>\n<p><a href=\"https://example.com?foo=3#frag\">link</a></p>\n",
+ "example": 501,
+ "start_line": 7699,
+ "end_line": 7709,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](foo\\bar)\n",
+ "html": "<p><a href=\"foo%5Cbar\">link</a></p>\n",
+ "example": 502,
+ "start_line": 7715,
+ "end_line": 7719,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](foo%20bä)\n",
+ "html": "<p><a href=\"foo%20b%C3%A4\">link</a></p>\n",
+ "example": 503,
+ "start_line": 7731,
+ "end_line": 7735,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](\"title\")\n",
+ "html": "<p><a href=\"%22title%22\">link</a></p>\n",
+ "example": 504,
+ "start_line": 7742,
+ "end_line": 7746,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](/url \"title\")\n[link](/url 'title')\n[link](/url (title))\n",
+ "html": "<p><a href=\"/url\" title=\"title\">link</a>\n<a href=\"/url\" title=\"title\">link</a>\n<a href=\"/url\" title=\"title\">link</a></p>\n",
+ "example": 505,
+ "start_line": 7751,
+ "end_line": 7759,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](/url \"title \\\""\")\n",
+ "html": "<p><a href=\"/url\" title=\"title ""\">link</a></p>\n",
+ "example": 506,
+ "start_line": 7765,
+ "end_line": 7769,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](/url \"title\")\n",
+ "html": "<p><a href=\"/url%C2%A0%22title%22\">link</a></p>\n",
+ "example": 507,
+ "start_line": 7776,
+ "end_line": 7780,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](/url \"title \"and\" title\")\n",
+ "html": "<p>[link](/url "title "and" title")</p>\n",
+ "example": 508,
+ "start_line": 7785,
+ "end_line": 7789,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](/url 'title \"and\" title')\n",
+ "html": "<p><a href=\"/url\" title=\"title "and" title\">link</a></p>\n",
+ "example": 509,
+ "start_line": 7794,
+ "end_line": 7798,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link]( /uri\n \"title\" )\n",
+ "html": "<p><a href=\"/uri\" title=\"title\">link</a></p>\n",
+ "example": 510,
+ "start_line": 7819,
+ "end_line": 7824,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link] (/uri)\n",
+ "html": "<p>[link] (/uri)</p>\n",
+ "example": 511,
+ "start_line": 7830,
+ "end_line": 7834,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link [foo [bar]]](/uri)\n",
+ "html": "<p><a href=\"/uri\">link [foo [bar]]</a></p>\n",
+ "example": 512,
+ "start_line": 7840,
+ "end_line": 7844,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link] bar](/uri)\n",
+ "html": "<p>[link] bar](/uri)</p>\n",
+ "example": 513,
+ "start_line": 7847,
+ "end_line": 7851,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link [bar](/uri)\n",
+ "html": "<p>[link <a href=\"/uri\">bar</a></p>\n",
+ "example": 514,
+ "start_line": 7854,
+ "end_line": 7858,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link \\[bar](/uri)\n",
+ "html": "<p><a href=\"/uri\">link [bar</a></p>\n",
+ "example": 515,
+ "start_line": 7861,
+ "end_line": 7865,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link *foo **bar** `#`*](/uri)\n",
+ "html": "<p><a href=\"/uri\">link <em>foo <strong>bar</strong> <code>#</code></em></a></p>\n",
+ "example": 516,
+ "start_line": 7870,
+ "end_line": 7874,
+ "section": "Links"
+ },
+ {
+ "markdown": "[](/uri)\n",
+ "html": "<p><a href=\"/uri\"><img src=\"moon.jpg\" alt=\"moon\" /></a></p>\n",
+ "example": 517,
+ "start_line": 7877,
+ "end_line": 7881,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo [bar](/uri)](/uri)\n",
+ "html": "<p>[foo <a href=\"/uri\">bar</a>](/uri)</p>\n",
+ "example": 518,
+ "start_line": 7886,
+ "end_line": 7890,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo *[bar [baz](/uri)](/uri)*](/uri)\n",
+ "html": "<p>[foo <em>[bar <a href=\"/uri\">baz</a>](/uri)</em>](/uri)</p>\n",
+ "example": 519,
+ "start_line": 7893,
+ "end_line": 7897,
+ "section": "Links"
+ },
+ {
+ "markdown": "](uri2)](uri3)\n",
+ "html": "<p><img src=\"uri3\" alt=\"[foo](uri2)\" /></p>\n",
+ "example": 520,
+ "start_line": 7900,
+ "end_line": 7904,
+ "section": "Links"
+ },
+ {
+ "markdown": "*[foo*](/uri)\n",
+ "html": "<p>*<a href=\"/uri\">foo*</a></p>\n",
+ "example": 521,
+ "start_line": 7910,
+ "end_line": 7914,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo *bar](baz*)\n",
+ "html": "<p><a href=\"baz*\">foo *bar</a></p>\n",
+ "example": 522,
+ "start_line": 7917,
+ "end_line": 7921,
+ "section": "Links"
+ },
+ {
+ "markdown": "*foo [bar* baz]\n",
+ "html": "<p><em>foo [bar</em> baz]</p>\n",
+ "example": 523,
+ "start_line": 7927,
+ "end_line": 7931,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo <bar attr=\"](baz)\">\n",
+ "html": "<p>[foo <bar attr=\"](baz)\"></p>\n",
+ "example": 524,
+ "start_line": 7937,
+ "end_line": 7941,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo`](/uri)`\n",
+ "html": "<p>[foo<code>](/uri)</code></p>\n",
+ "example": 525,
+ "start_line": 7944,
+ "end_line": 7948,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo<https://example.com/?search=](uri)>\n",
+ "html": "<p>[foo<a href=\"https://example.com/?search=%5D(uri)\">https://example.com/?search=](uri)</a></p>\n",
+ "example": 526,
+ "start_line": 7951,
+ "end_line": 7955,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo][bar]\n\n[bar]: /url \"title\"\n",
+ "html": "<p><a href=\"/url\" title=\"title\">foo</a></p>\n",
+ "example": 527,
+ "start_line": 7989,
+ "end_line": 7995,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link [foo [bar]]][ref]\n\n[ref]: /uri\n",
+ "html": "<p><a href=\"/uri\">link [foo [bar]]</a></p>\n",
+ "example": 528,
+ "start_line": 8004,
+ "end_line": 8010,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link \\[bar][ref]\n\n[ref]: /uri\n",
+ "html": "<p><a href=\"/uri\">link [bar</a></p>\n",
+ "example": 529,
+ "start_line": 8013,
+ "end_line": 8019,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link *foo **bar** `#`*][ref]\n\n[ref]: /uri\n",
+ "html": "<p><a href=\"/uri\">link <em>foo <strong>bar</strong> <code>#</code></em></a></p>\n",
+ "example": 530,
+ "start_line": 8024,
+ "end_line": 8030,
+ "section": "Links"
+ },
+ {
+ "markdown": "[][ref]\n\n[ref]: /uri\n",
+ "html": "<p><a href=\"/uri\"><img src=\"moon.jpg\" alt=\"moon\" /></a></p>\n",
+ "example": 531,
+ "start_line": 8033,
+ "end_line": 8039,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo [bar](/uri)][ref]\n\n[ref]: /uri\n",
+ "html": "<p>[foo <a href=\"/uri\">bar</a>]<a href=\"/uri\">ref</a></p>\n",
+ "example": 532,
+ "start_line": 8044,
+ "end_line": 8050,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo *bar [baz][ref]*][ref]\n\n[ref]: /uri\n",
+ "html": "<p>[foo <em>bar <a href=\"/uri\">baz</a></em>]<a href=\"/uri\">ref</a></p>\n",
+ "example": 533,
+ "start_line": 8053,
+ "end_line": 8059,
+ "section": "Links"
+ },
+ {
+ "markdown": "*[foo*][ref]\n\n[ref]: /uri\n",
+ "html": "<p>*<a href=\"/uri\">foo*</a></p>\n",
+ "example": 534,
+ "start_line": 8068,
+ "end_line": 8074,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo *bar][ref]*\n\n[ref]: /uri\n",
+ "html": "<p><a href=\"/uri\">foo *bar</a>*</p>\n",
+ "example": 535,
+ "start_line": 8077,
+ "end_line": 8083,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo <bar attr=\"][ref]\">\n\n[ref]: /uri\n",
+ "html": "<p>[foo <bar attr=\"][ref]\"></p>\n",
+ "example": 536,
+ "start_line": 8089,
+ "end_line": 8095,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo`][ref]`\n\n[ref]: /uri\n",
+ "html": "<p>[foo<code>][ref]</code></p>\n",
+ "example": 537,
+ "start_line": 8098,
+ "end_line": 8104,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo<https://example.com/?search=][ref]>\n\n[ref]: /uri\n",
+ "html": "<p>[foo<a href=\"https://example.com/?search=%5D%5Bref%5D\">https://example.com/?search=][ref]</a></p>\n",
+ "example": 538,
+ "start_line": 8107,
+ "end_line": 8113,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo][BaR]\n\n[bar]: /url \"title\"\n",
+ "html": "<p><a href=\"/url\" title=\"title\">foo</a></p>\n",
+ "example": 539,
+ "start_line": 8118,
+ "end_line": 8124,
+ "section": "Links"
+ },
+ {
+ "markdown": "[ẞ]\n\n[SS]: /url\n",
+ "html": "<p><a href=\"/url\">ẞ</a></p>\n",
+ "example": 540,
+ "start_line": 8129,
+ "end_line": 8135,
+ "section": "Links"
+ },
+ {
+ "markdown": "[Foo\n bar]: /url\n\n[Baz][Foo bar]\n",
+ "html": "<p><a href=\"/url\">Baz</a></p>\n",
+ "example": 541,
+ "start_line": 8141,
+ "end_line": 8148,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo] [bar]\n\n[bar]: /url \"title\"\n",
+ "html": "<p>[foo] <a href=\"/url\" title=\"title\">bar</a></p>\n",
+ "example": 542,
+ "start_line": 8154,
+ "end_line": 8160,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo]\n[bar]\n\n[bar]: /url \"title\"\n",
+ "html": "<p>[foo]\n<a href=\"/url\" title=\"title\">bar</a></p>\n",
+ "example": 543,
+ "start_line": 8163,
+ "end_line": 8171,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo]: /url1\n\n[foo]: /url2\n\n[bar][foo]\n",
+ "html": "<p><a href=\"/url1\">bar</a></p>\n",
+ "example": 544,
+ "start_line": 8204,
+ "end_line": 8212,
+ "section": "Links"
+ },
+ {
+ "markdown": "[bar][foo\\!]\n\n[foo!]: /url\n",
+ "html": "<p>[bar][foo!]</p>\n",
+ "example": 545,
+ "start_line": 8219,
+ "end_line": 8225,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo][ref[]\n\n[ref[]: /uri\n",
+ "html": "<p>[foo][ref[]</p>\n<p>[ref[]: /uri</p>\n",
+ "example": 546,
+ "start_line": 8231,
+ "end_line": 8238,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo][ref[bar]]\n\n[ref[bar]]: /uri\n",
+ "html": "<p>[foo][ref[bar]]</p>\n<p>[ref[bar]]: /uri</p>\n",
+ "example": 547,
+ "start_line": 8241,
+ "end_line": 8248,
+ "section": "Links"
+ },
+ {
+ "markdown": "[[[foo]]]\n\n[[[foo]]]: /url\n",
+ "html": "<p>[[[foo]]]</p>\n<p>[[[foo]]]: /url</p>\n",
+ "example": 548,
+ "start_line": 8251,
+ "end_line": 8258,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo][ref\\[]\n\n[ref\\[]: /uri\n",
+ "html": "<p><a href=\"/uri\">foo</a></p>\n",
+ "example": 549,
+ "start_line": 8261,
+ "end_line": 8267,
+ "section": "Links"
+ },
+ {
+ "markdown": "[bar\\\\]: /uri\n\n[bar\\\\]\n",
+ "html": "<p><a href=\"/uri\">bar\\</a></p>\n",
+ "example": 550,
+ "start_line": 8272,
+ "end_line": 8278,
+ "section": "Links"
+ },
+ {
+ "markdown": "[]\n\n[]: /uri\n",
+ "html": "<p>[]</p>\n<p>[]: /uri</p>\n",
+ "example": 551,
+ "start_line": 8284,
+ "end_line": 8291,
+ "section": "Links"
+ },
+ {
+ "markdown": "[\n ]\n\n[\n ]: /uri\n",
+ "html": "<p>[\n]</p>\n<p>[\n]: /uri</p>\n",
+ "example": 552,
+ "start_line": 8294,
+ "end_line": 8305,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo][]\n\n[foo]: /url \"title\"\n",
+ "html": "<p><a href=\"/url\" title=\"title\">foo</a></p>\n",
+ "example": 553,
+ "start_line": 8317,
+ "end_line": 8323,
+ "section": "Links"
+ },
+ {
+ "markdown": "[*foo* bar][]\n\n[*foo* bar]: /url \"title\"\n",
+ "html": "<p><a href=\"/url\" title=\"title\"><em>foo</em> bar</a></p>\n",
+ "example": 554,
+ "start_line": 8326,
+ "end_line": 8332,
+ "section": "Links"
+ },
+ {
+ "markdown": "[Foo][]\n\n[foo]: /url \"title\"\n",
+ "html": "<p><a href=\"/url\" title=\"title\">Foo</a></p>\n",
+ "example": 555,
+ "start_line": 8337,
+ "end_line": 8343,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo] \n[]\n\n[foo]: /url \"title\"\n",
+ "html": "<p><a href=\"/url\" title=\"title\">foo</a>\n[]</p>\n",
+ "example": 556,
+ "start_line": 8350,
+ "end_line": 8358,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo]\n\n[foo]: /url \"title\"\n",
+ "html": "<p><a href=\"/url\" title=\"title\">foo</a></p>\n",
+ "example": 557,
+ "start_line": 8370,
+ "end_line": 8376,
+ "section": "Links"
+ },
+ {
+ "markdown": "[*foo* bar]\n\n[*foo* bar]: /url \"title\"\n",
+ "html": "<p><a href=\"/url\" title=\"title\"><em>foo</em> bar</a></p>\n",
+ "example": 558,
+ "start_line": 8379,
+ "end_line": 8385,
+ "section": "Links"
+ },
+ {
+ "markdown": "[[*foo* bar]]\n\n[*foo* bar]: /url \"title\"\n",
+ "html": "<p>[<a href=\"/url\" title=\"title\"><em>foo</em> bar</a>]</p>\n",
+ "example": 559,
+ "start_line": 8388,
+ "end_line": 8394,
+ "section": "Links"
+ },
+ {
+ "markdown": "[[bar [foo]\n\n[foo]: /url\n",
+ "html": "<p>[[bar <a href=\"/url\">foo</a></p>\n",
+ "example": 560,
+ "start_line": 8397,
+ "end_line": 8403,
+ "section": "Links"
+ },
+ {
+ "markdown": "[Foo]\n\n[foo]: /url \"title\"\n",
+ "html": "<p><a href=\"/url\" title=\"title\">Foo</a></p>\n",
+ "example": 561,
+ "start_line": 8408,
+ "end_line": 8414,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo] bar\n\n[foo]: /url\n",
+ "html": "<p><a href=\"/url\">foo</a> bar</p>\n",
+ "example": 562,
+ "start_line": 8419,
+ "end_line": 8425,
+ "section": "Links"
+ },
+ {
+ "markdown": "\\[foo]\n\n[foo]: /url \"title\"\n",
+ "html": "<p>[foo]</p>\n",
+ "example": 563,
+ "start_line": 8431,
+ "end_line": 8437,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo*]: /url\n\n*[foo*]\n",
+ "html": "<p>*<a href=\"/url\">foo*</a></p>\n",
+ "example": 564,
+ "start_line": 8443,
+ "end_line": 8449,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo][bar]\n\n[foo]: /url1\n[bar]: /url2\n",
+ "html": "<p><a href=\"/url2\">foo</a></p>\n",
+ "example": 565,
+ "start_line": 8455,
+ "end_line": 8462,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo][]\n\n[foo]: /url1\n",
+ "html": "<p><a href=\"/url1\">foo</a></p>\n",
+ "example": 566,
+ "start_line": 8464,
+ "end_line": 8470,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo]()\n\n[foo]: /url1\n",
+ "html": "<p><a href=\"\">foo</a></p>\n",
+ "example": 567,
+ "start_line": 8474,
+ "end_line": 8480,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo](not a link)\n\n[foo]: /url1\n",
+ "html": "<p><a href=\"/url1\">foo</a>(not a link)</p>\n",
+ "example": 568,
+ "start_line": 8482,
+ "end_line": 8488,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo][bar][baz]\n\n[baz]: /url\n",
+ "html": "<p>[foo]<a href=\"/url\">bar</a></p>\n",
+ "example": 569,
+ "start_line": 8493,
+ "end_line": 8499,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo][bar][baz]\n\n[baz]: /url1\n[bar]: /url2\n",
+ "html": "<p><a href=\"/url2\">foo</a><a href=\"/url1\">baz</a></p>\n",
+ "example": 570,
+ "start_line": 8505,
+ "end_line": 8512,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo][bar][baz]\n\n[baz]: /url1\n[foo]: /url2\n",
+ "html": "<p>[foo]<a href=\"/url1\">bar</a></p>\n",
+ "example": 571,
+ "start_line": 8518,
+ "end_line": 8525,
+ "section": "Links"
+ },
+ {
+ "markdown": "\n",
+ "html": "<p><img src=\"/url\" alt=\"foo\" title=\"title\" /></p>\n",
+ "example": 572,
+ "start_line": 8541,
+ "end_line": 8545,
+ "section": "Images"
+ },
+ {
+ "markdown": "![foo *bar*]\n\n[foo *bar*]: train.jpg \"train & tracks\"\n",
+ "html": "<p><img src=\"train.jpg\" alt=\"foo bar\" title=\"train & tracks\" /></p>\n",
+ "example": 573,
+ "start_line": 8548,
+ "end_line": 8554,
+ "section": "Images"
+ },
+ {
+ "markdown": "](/url2)\n",
+ "html": "<p><img src=\"/url2\" alt=\"foo bar\" /></p>\n",
+ "example": 574,
+ "start_line": 8557,
+ "end_line": 8561,
+ "section": "Images"
+ },
+ {
+ "markdown": "](/url2)\n",
+ "html": "<p><img src=\"/url2\" alt=\"foo bar\" /></p>\n",
+ "example": 575,
+ "start_line": 8564,
+ "end_line": 8568,
+ "section": "Images"
+ },
+ {
+ "markdown": "![foo *bar*][]\n\n[foo *bar*]: train.jpg \"train & tracks\"\n",
+ "html": "<p><img src=\"train.jpg\" alt=\"foo bar\" title=\"train & tracks\" /></p>\n",
+ "example": 576,
+ "start_line": 8578,
+ "end_line": 8584,
+ "section": "Images"
+ },
+ {
+ "markdown": "![foo *bar*][foobar]\n\n[FOOBAR]: train.jpg \"train & tracks\"\n",
+ "html": "<p><img src=\"train.jpg\" alt=\"foo bar\" title=\"train & tracks\" /></p>\n",
+ "example": 577,
+ "start_line": 8587,
+ "end_line": 8593,
+ "section": "Images"
+ },
+ {
+ "markdown": "\n",
+ "html": "<p><img src=\"train.jpg\" alt=\"foo\" /></p>\n",
+ "example": 578,
+ "start_line": 8596,
+ "end_line": 8600,
+ "section": "Images"
+ },
+ {
+ "markdown": "My \n",
+ "html": "<p>My <img src=\"/path/to/train.jpg\" alt=\"foo bar\" title=\"title\" /></p>\n",
+ "example": 579,
+ "start_line": 8603,
+ "end_line": 8607,
+ "section": "Images"
+ },
+ {
+ "markdown": "\n",
+ "html": "<p><img src=\"url\" alt=\"foo\" /></p>\n",
+ "example": 580,
+ "start_line": 8610,
+ "end_line": 8614,
+ "section": "Images"
+ },
+ {
+ "markdown": "\n",
+ "html": "<p><img src=\"/url\" alt=\"\" /></p>\n",
+ "example": 581,
+ "start_line": 8617,
+ "end_line": 8621,
+ "section": "Images"
+ },
+ {
+ "markdown": "![foo][bar]\n\n[bar]: /url\n",
+ "html": "<p><img src=\"/url\" alt=\"foo\" /></p>\n",
+ "example": 582,
+ "start_line": 8626,
+ "end_line": 8632,
+ "section": "Images"
+ },
+ {
+ "markdown": "![foo][bar]\n\n[BAR]: /url\n",
+ "html": "<p><img src=\"/url\" alt=\"foo\" /></p>\n",
+ "example": 583,
+ "start_line": 8635,
+ "end_line": 8641,
+ "section": "Images"
+ },
+ {
+ "markdown": "![foo][]\n\n[foo]: /url \"title\"\n",
+ "html": "<p><img src=\"/url\" alt=\"foo\" title=\"title\" /></p>\n",
+ "example": 584,
+ "start_line": 8646,
+ "end_line": 8652,
+ "section": "Images"
+ },
+ {
+ "markdown": "![*foo* bar][]\n\n[*foo* bar]: /url \"title\"\n",
+ "html": "<p><img src=\"/url\" alt=\"foo bar\" title=\"title\" /></p>\n",
+ "example": 585,
+ "start_line": 8655,
+ "end_line": 8661,
+ "section": "Images"
+ },
+ {
+ "markdown": "![Foo][]\n\n[foo]: /url \"title\"\n",
+ "html": "<p><img src=\"/url\" alt=\"Foo\" title=\"title\" /></p>\n",
+ "example": 586,
+ "start_line": 8666,
+ "end_line": 8672,
+ "section": "Images"
+ },
+ {
+ "markdown": "![foo] \n[]\n\n[foo]: /url \"title\"\n",
+ "html": "<p><img src=\"/url\" alt=\"foo\" title=\"title\" />\n[]</p>\n",
+ "example": 587,
+ "start_line": 8678,
+ "end_line": 8686,
+ "section": "Images"
+ },
+ {
+ "markdown": "![foo]\n\n[foo]: /url \"title\"\n",
+ "html": "<p><img src=\"/url\" alt=\"foo\" title=\"title\" /></p>\n",
+ "example": 588,
+ "start_line": 8691,
+ "end_line": 8697,
+ "section": "Images"
+ },
+ {
+ "markdown": "![*foo* bar]\n\n[*foo* bar]: /url \"title\"\n",
+ "html": "<p><img src=\"/url\" alt=\"foo bar\" title=\"title\" /></p>\n",
+ "example": 589,
+ "start_line": 8700,
+ "end_line": 8706,
+ "section": "Images"
+ },
+ {
+ "markdown": "![[foo]]\n\n[[foo]]: /url \"title\"\n",
+ "html": "<p>![[foo]]</p>\n<p>[[foo]]: /url "title"</p>\n",
+ "example": 590,
+ "start_line": 8711,
+ "end_line": 8718,
+ "section": "Images"
+ },
+ {
+ "markdown": "![Foo]\n\n[foo]: /url \"title\"\n",
+ "html": "<p><img src=\"/url\" alt=\"Foo\" title=\"title\" /></p>\n",
+ "example": 591,
+ "start_line": 8723,
+ "end_line": 8729,
+ "section": "Images"
+ },
+ {
+ "markdown": "!\\[foo]\n\n[foo]: /url \"title\"\n",
+ "html": "<p>![foo]</p>\n",
+ "example": 592,
+ "start_line": 8735,
+ "end_line": 8741,
+ "section": "Images"
+ },
+ {
+ "markdown": "\\![foo]\n\n[foo]: /url \"title\"\n",
+ "html": "<p>!<a href=\"/url\" title=\"title\">foo</a></p>\n",
+ "example": 593,
+ "start_line": 8747,
+ "end_line": 8753,
+ "section": "Images"
+ },
+ {
+ "markdown": "<http://foo.bar.baz>\n",
+ "html": "<p><a href=\"http://foo.bar.baz\">http://foo.bar.baz</a></p>\n",
+ "example": 594,
+ "start_line": 8780,
+ "end_line": 8784,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<https://foo.bar.baz/test?q=hello&id=22&boolean>\n",
+ "html": "<p><a href=\"https://foo.bar.baz/test?q=hello&id=22&boolean\">https://foo.bar.baz/test?q=hello&id=22&boolean</a></p>\n",
+ "example": 595,
+ "start_line": 8787,
+ "end_line": 8791,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<irc://foo.bar:2233/baz>\n",
+ "html": "<p><a href=\"irc://foo.bar:2233/baz\">irc://foo.bar:2233/baz</a></p>\n",
+ "example": 596,
+ "start_line": 8794,
+ "end_line": 8798,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<MAILTO:FOO@BAR.BAZ>\n",
+ "html": "<p><a href=\"MAILTO:FOO@BAR.BAZ\">MAILTO:FOO@BAR.BAZ</a></p>\n",
+ "example": 597,
+ "start_line": 8803,
+ "end_line": 8807,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<a+b+c:d>\n",
+ "html": "<p><a href=\"a+b+c:d\">a+b+c:d</a></p>\n",
+ "example": 598,
+ "start_line": 8815,
+ "end_line": 8819,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<made-up-scheme://foo,bar>\n",
+ "html": "<p><a href=\"made-up-scheme://foo,bar\">made-up-scheme://foo,bar</a></p>\n",
+ "example": 599,
+ "start_line": 8822,
+ "end_line": 8826,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<https://../>\n",
+ "html": "<p><a href=\"https://../\">https://../</a></p>\n",
+ "example": 600,
+ "start_line": 8829,
+ "end_line": 8833,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<localhost:5001/foo>\n",
+ "html": "<p><a href=\"localhost:5001/foo\">localhost:5001/foo</a></p>\n",
+ "example": 601,
+ "start_line": 8836,
+ "end_line": 8840,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<https://foo.bar/baz bim>\n",
+ "html": "<p><https://foo.bar/baz bim></p>\n",
+ "example": 602,
+ "start_line": 8845,
+ "end_line": 8849,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<https://example.com/\\[\\>\n",
+ "html": "<p><a href=\"https://example.com/%5C%5B%5C\">https://example.com/\\[\\</a></p>\n",
+ "example": 603,
+ "start_line": 8854,
+ "end_line": 8858,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<foo@bar.example.com>\n",
+ "html": "<p><a href=\"mailto:foo@bar.example.com\">foo@bar.example.com</a></p>\n",
+ "example": 604,
+ "start_line": 8876,
+ "end_line": 8880,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<foo+special@Bar.baz-bar0.com>\n",
+ "html": "<p><a href=\"mailto:foo+special@Bar.baz-bar0.com\">foo+special@Bar.baz-bar0.com</a></p>\n",
+ "example": 605,
+ "start_line": 8883,
+ "end_line": 8887,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<foo\\+@bar.example.com>\n",
+ "html": "<p><foo+@bar.example.com></p>\n",
+ "example": 606,
+ "start_line": 8892,
+ "end_line": 8896,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<>\n",
+ "html": "<p><></p>\n",
+ "example": 607,
+ "start_line": 8901,
+ "end_line": 8905,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "< https://foo.bar >\n",
+ "html": "<p>< https://foo.bar ></p>\n",
+ "example": 608,
+ "start_line": 8908,
+ "end_line": 8912,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<m:abc>\n",
+ "html": "<p><m:abc></p>\n",
+ "example": 609,
+ "start_line": 8915,
+ "end_line": 8919,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<foo.bar.baz>\n",
+ "html": "<p><foo.bar.baz></p>\n",
+ "example": 610,
+ "start_line": 8922,
+ "end_line": 8926,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "https://example.com\n",
+ "html": "<p>https://example.com</p>\n",
+ "example": 611,
+ "start_line": 8929,
+ "end_line": 8933,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "foo@bar.example.com\n",
+ "html": "<p>foo@bar.example.com</p>\n",
+ "example": 612,
+ "start_line": 8936,
+ "end_line": 8940,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<a><bab><c2c>\n",
+ "html": "<p><a><bab><c2c></p>\n",
+ "example": 613,
+ "start_line": 9016,
+ "end_line": 9020,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "<a/><b2/>\n",
+ "html": "<p><a/><b2/></p>\n",
+ "example": 614,
+ "start_line": 9025,
+ "end_line": 9029,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "<a /><b2\ndata=\"foo\" >\n",
+ "html": "<p><a /><b2\ndata=\"foo\" ></p>\n",
+ "example": 615,
+ "start_line": 9034,
+ "end_line": 9040,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "<a foo=\"bar\" bam = 'baz <em>\"</em>'\n_boolean zoop:33=zoop:33 />\n",
+ "html": "<p><a foo=\"bar\" bam = 'baz <em>\"</em>'\n_boolean zoop:33=zoop:33 /></p>\n",
+ "example": 616,
+ "start_line": 9045,
+ "end_line": 9051,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "Foo <responsive-image src=\"foo.jpg\" />\n",
+ "html": "<p>Foo <responsive-image src=\"foo.jpg\" /></p>\n",
+ "example": 617,
+ "start_line": 9056,
+ "end_line": 9060,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "<33> <__>\n",
+ "html": "<p><33> <__></p>\n",
+ "example": 618,
+ "start_line": 9065,
+ "end_line": 9069,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "<a h*#ref=\"hi\">\n",
+ "html": "<p><a h*#ref="hi"></p>\n",
+ "example": 619,
+ "start_line": 9074,
+ "end_line": 9078,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "<a href=\"hi'> <a href=hi'>\n",
+ "html": "<p><a href="hi'> <a href=hi'></p>\n",
+ "example": 620,
+ "start_line": 9083,
+ "end_line": 9087,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "< a><\nfoo><bar/ >\n<foo bar=baz\nbim!bop />\n",
+ "html": "<p>< a><\nfoo><bar/ >\n<foo bar=baz\nbim!bop /></p>\n",
+ "example": 621,
+ "start_line": 9092,
+ "end_line": 9102,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "<a href='bar'title=title>\n",
+ "html": "<p><a href='bar'title=title></p>\n",
+ "example": 622,
+ "start_line": 9107,
+ "end_line": 9111,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "</a></foo >\n",
+ "html": "<p></a></foo ></p>\n",
+ "example": 623,
+ "start_line": 9116,
+ "end_line": 9120,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "</a href=\"foo\">\n",
+ "html": "<p></a href="foo"></p>\n",
+ "example": 624,
+ "start_line": 9125,
+ "end_line": 9129,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "foo <!-- this is a --\ncomment - with hyphens -->\n",
+ "html": "<p>foo <!-- this is a --\ncomment - with hyphens --></p>\n",
+ "example": 625,
+ "start_line": 9134,
+ "end_line": 9140,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "foo <!--> foo -->\n\nfoo <!---> foo -->\n",
+ "html": "<p>foo <!--> foo --></p>\n<p>foo <!---> foo --></p>\n",
+ "example": 626,
+ "start_line": 9142,
+ "end_line": 9149,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "foo <?php echo $a; ?>\n",
+ "html": "<p>foo <?php echo $a; ?></p>\n",
+ "example": 627,
+ "start_line": 9154,
+ "end_line": 9158,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "foo <!ELEMENT br EMPTY>\n",
+ "html": "<p>foo <!ELEMENT br EMPTY></p>\n",
+ "example": 628,
+ "start_line": 9163,
+ "end_line": 9167,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "foo <![CDATA[>&<]]>\n",
+ "html": "<p>foo <![CDATA[>&<]]></p>\n",
+ "example": 629,
+ "start_line": 9172,
+ "end_line": 9176,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "foo <a href=\"ö\">\n",
+ "html": "<p>foo <a href=\"ö\"></p>\n",
+ "example": 630,
+ "start_line": 9182,
+ "end_line": 9186,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "foo <a href=\"\\*\">\n",
+ "html": "<p>foo <a href=\"\\*\"></p>\n",
+ "example": 631,
+ "start_line": 9191,
+ "end_line": 9195,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "<a href=\"\\\"\">\n",
+ "html": "<p><a href="""></p>\n",
+ "example": 632,
+ "start_line": 9198,
+ "end_line": 9202,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "foo \nbaz\n",
+ "html": "<p>foo<br />\nbaz</p>\n",
+ "example": 633,
+ "start_line": 9212,
+ "end_line": 9218,
+ "section": "Hard line breaks"
+ },
+ {
+ "markdown": "foo\\\nbaz\n",
+ "html": "<p>foo<br />\nbaz</p>\n",
+ "example": 634,
+ "start_line": 9224,
+ "end_line": 9230,
+ "section": "Hard line breaks"
+ },
+ {
+ "markdown": "foo \nbaz\n",
+ "html": "<p>foo<br />\nbaz</p>\n",
+ "example": 635,
+ "start_line": 9235,
+ "end_line": 9241,
+ "section": "Hard line breaks"
+ },
+ {
+ "markdown": "foo \n bar\n",
+ "html": "<p>foo<br />\nbar</p>\n",
+ "example": 636,
+ "start_line": 9246,
+ "end_line": 9252,
+ "section": "Hard line breaks"
+ },
+ {
+ "markdown": "foo\\\n bar\n",
+ "html": "<p>foo<br />\nbar</p>\n",
+ "example": 637,
+ "start_line": 9255,
+ "end_line": 9261,
+ "section": "Hard line breaks"
+ },
+ {
+ "markdown": "*foo \nbar*\n",
+ "html": "<p><em>foo<br />\nbar</em></p>\n",
+ "example": 638,
+ "start_line": 9267,
+ "end_line": 9273,
+ "section": "Hard line breaks"
+ },
+ {
+ "markdown": "*foo\\\nbar*\n",
+ "html": "<p><em>foo<br />\nbar</em></p>\n",
+ "example": 639,
+ "start_line": 9276,
+ "end_line": 9282,
+ "section": "Hard line breaks"
+ },
+ {
+ "markdown": "`code \nspan`\n",
+ "html": "<p><code>code span</code></p>\n",
+ "example": 640,
+ "start_line": 9287,
+ "end_line": 9292,
+ "section": "Hard line breaks"
+ },
+ {
+ "markdown": "`code\\\nspan`\n",
+ "html": "<p><code>code\\ span</code></p>\n",
+ "example": 641,
+ "start_line": 9295,
+ "end_line": 9300,
+ "section": "Hard line breaks"
+ },
+ {
+ "markdown": "<a href=\"foo \nbar\">\n",
+ "html": "<p><a href=\"foo \nbar\"></p>\n",
+ "example": 642,
+ "start_line": 9305,
+ "end_line": 9311,
+ "section": "Hard line breaks"
+ },
+ {
+ "markdown": "<a href=\"foo\\\nbar\">\n",
+ "html": "<p><a href=\"foo\\\nbar\"></p>\n",
+ "example": 643,
+ "start_line": 9314,
+ "end_line": 9320,
+ "section": "Hard line breaks"
+ },
+ {
+ "markdown": "foo\\\n",
+ "html": "<p>foo\\</p>\n",
+ "example": 644,
+ "start_line": 9327,
+ "end_line": 9331,
+ "section": "Hard line breaks"
+ },
+ {
+ "markdown": "foo \n",
+ "html": "<p>foo</p>\n",
+ "example": 645,
+ "start_line": 9334,
+ "end_line": 9338,
+ "section": "Hard line breaks"
+ },
+ {
+ "markdown": "### foo\\\n",
+ "html": "<h3>foo\\</h3>\n",
+ "example": 646,
+ "start_line": 9341,
+ "end_line": 9345,
+ "section": "Hard line breaks"
+ },
+ {
+ "markdown": "### foo \n",
+ "html": "<h3>foo</h3>\n",
+ "example": 647,
+ "start_line": 9348,
+ "end_line": 9352,
+ "section": "Hard line breaks"
+ },
+ {
+ "markdown": "foo\nbaz\n",
+ "html": "<p>foo\nbaz</p>\n",
+ "example": 648,
+ "start_line": 9363,
+ "end_line": 9369,
+ "section": "Soft line breaks"
+ },
+ {
+ "markdown": "foo \n baz\n",
+ "html": "<p>foo\nbaz</p>\n",
+ "example": 649,
+ "start_line": 9375,
+ "end_line": 9381,
+ "section": "Soft line breaks"
+ },
+ {
+ "markdown": "hello $.;'there\n",
+ "html": "<p>hello $.;'there</p>\n",
+ "example": 650,
+ "start_line": 9395,
+ "end_line": 9399,
+ "section": "Textual content"
+ },
+ {
+ "markdown": "Foo χρῆν\n",
+ "html": "<p>Foo χρῆν</p>\n",
+ "example": 651,
+ "start_line": 9402,
+ "end_line": 9406,
+ "section": "Textual content"
+ },
+ {
+ "markdown": "Multiple spaces\n",
+ "html": "<p>Multiple spaces</p>\n",
+ "example": 652,
+ "start_line": 9411,
+ "end_line": 9415,
+ "section": "Textual content"
+ }
+]
\ No newline at end of file
diff --git a/pkgs/markdown/tool/dartdoc_compare.dart b/pkgs/markdown/tool/dartdoc_compare.dart
new file mode 100644
index 0000000..e6100ee
--- /dev/null
+++ b/pkgs/markdown/tool/dartdoc_compare.dart
@@ -0,0 +1,189 @@
+// Copyright (c) 2020, 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' show jsonDecode, jsonEncode;
+import 'dart:io' show Directory, File, Platform, Process, exitCode;
+
+import 'package:args/args.dart' show ArgParser;
+import 'package:path/path.dart' show absolute;
+import 'package:yaml/yaml.dart' show loadYaml;
+
+const _dartdocDir = 'dartdoc-dir';
+const _markdownBefore = 'before';
+const _markdownAfter = 'after';
+const _sdk = 'sdk';
+const _help = 'help';
+
+void main(List<String> arguments) {
+ final parser = ArgParser()
+ ..addSeparator('Usage: dartdoc-compare.dart [OPTIONS] <dart-package>')
+ ..addOption(_dartdocDir, help: 'Directory of the dartdoc package')
+ ..addOption(_markdownBefore, help: "Markdown package 'before' ref")
+ ..addOption(
+ _markdownAfter,
+ defaultsTo: 'HEAD',
+ help: "Markdown package 'after' ref (or 'local')",
+ )
+ ..addFlag(
+ _sdk,
+ negatable: false,
+ help: 'Is the package the SDK?',
+ )
+ ..addFlag(_help, abbr: 'h', hide: true);
+
+ final options = parser.parse(arguments);
+ if (options[_help] as bool) {
+ print(parser.usage);
+ exitCode = 0;
+ return;
+ }
+ if (options[_dartdocDir] == null || options[_markdownBefore] == null) {
+ print(
+ 'Invalid arguments: Options --$_dartdocDir and --$_markdownBefore '
+ 'must be specified',
+ );
+ print(parser.usage);
+ exitCode = 1;
+ return;
+ }
+ final comparer = DartdocCompare(
+ options[_dartdocDir] as String,
+ options[_markdownBefore] as String,
+ options[_markdownAfter] as String,
+ absolute(options[_dartdocDir] as String, 'bin/dartdoc.dart'),
+ absolute(options[_dartdocDir] as String, 'pubspec.yaml'),
+ options[_sdk] as bool,
+ );
+
+ String? path;
+ if (comparer.sdk) {
+ if (options.rest.isNotEmpty) {
+ path = options.rest.single;
+ }
+ } else {
+ path = options.rest.single;
+ }
+
+ if (comparer.compare(path)) {
+ exitCode = 0;
+ } else {
+ exitCode = 1;
+ }
+}
+
+class DartdocCompare {
+ final String dartdocDir;
+ final String markdownBefore;
+ final String markdownAfter;
+ final String dartdocBin;
+ final String dartdocPubspecPath;
+ final bool sdk;
+ final String markdownPath = File(Platform.script.path).parent.parent.path;
+
+ DartdocCompare(
+ this.dartdocDir,
+ this.markdownBefore,
+ this.markdownAfter,
+ this.dartdocBin,
+ this.dartdocPubspecPath,
+ this.sdk,
+ );
+
+ bool compare(String? package) {
+ // Generate docs with Markdown "Before".
+ final outBefore = _runDartdoc(markdownBefore, package);
+
+ // Generate docs with Markdown "After".
+ final outAfter = _runDartdoc(markdownAfter, package);
+
+ // Compare outputs
+ final diffOptions = ['-r', '-B', outBefore, outAfter];
+ final result = Process.runSync('diff', diffOptions, runInShell: true);
+ final nlines = '\n'.allMatches(result.stdout as String).length;
+ print('Diff lines: $nlines');
+ print('diff ${diffOptions.join(" ")}');
+ return result.exitCode == 0;
+ }
+
+ String _runDartdoc(String markdownRef, String? path) {
+ print('==========================================================');
+ print('Running dartdoc for $markdownRef...');
+ print('==========================================================');
+ _doInPath(dartdocDir, () {
+ final returnCode = _updateDartdocPubspec(markdownRef);
+ if (returnCode != 0) {
+ throw Exception("Could not update dartdoc's pubspec!");
+ }
+ });
+ return _doInPath(path, () {
+ if (!sdk) {
+ _system('pub', ['upgrade']);
+ }
+ final out = Directory.systemTemp
+ .createTempSync('dartdoc-compare-${markdownRef}__');
+ const cmd = 'dart';
+ final args = [dartdocBin, '--output=${out.path}'];
+
+ if (sdk) {
+ args.add('--sdk-docs');
+ }
+
+ print('Command: $cmd ${args.join(' ')}');
+ final startTime = DateTime.now();
+ _system(cmd, args);
+ final endTime = DateTime.now();
+ final duration = endTime.difference(startTime).inSeconds;
+ print('dartdoc generation for $markdownRef took $duration seconds.');
+ print('');
+
+ return out.path;
+ });
+ }
+
+ int _updateDartdocPubspec(String markdownRef) {
+ var dartdocPubspec =
+ loadYaml(File(dartdocPubspecPath).readAsStringSync()) as Map;
+ // make modifiable copy
+ dartdocPubspec = jsonDecode(jsonEncode(dartdocPubspec)) as Map;
+
+ final dependencies = dartdocPubspec['dependencies'] as Map;
+
+ if (markdownRef == 'local') {
+ dependencies['markdown'] = {
+ 'path': markdownPath,
+ };
+ } else {
+ dependencies['markdown'] = {
+ 'git': {
+ 'url': 'git://github.com/dart-lang/markdown.git',
+ 'ref': markdownRef
+ }
+ };
+ }
+
+ File(dartdocPubspecPath).writeAsStringSync(jsonEncode(dartdocPubspec));
+ return _system('pub', ['upgrade']);
+ }
+}
+
+int _system(String cmd, List<String> args) {
+ final result = Process.runSync(cmd, args);
+ print(result.stdout);
+ print(result.stderr);
+ return result.exitCode;
+}
+
+T _doInPath<T>(String? path, T Function() f) {
+ if (path == null) {
+ return f();
+ }
+
+ final former = Directory.current.path;
+ Directory.current = path;
+ try {
+ return f();
+ } finally {
+ Directory.current = former;
+ }
+}
diff --git a/pkgs/markdown/tool/entities.json b/pkgs/markdown/tool/entities.json
new file mode 100644
index 0000000..557170b
--- /dev/null
+++ b/pkgs/markdown/tool/entities.json
@@ -0,0 +1,2233 @@
+{
+ "Æ": { "codepoints": [198], "characters": "\u00C6" },
+ "Æ": { "codepoints": [198], "characters": "\u00C6" },
+ "&": { "codepoints": [38], "characters": "\u0026" },
+ "&": { "codepoints": [38], "characters": "\u0026" },
+ "Á": { "codepoints": [193], "characters": "\u00C1" },
+ "Á": { "codepoints": [193], "characters": "\u00C1" },
+ "Ă": { "codepoints": [258], "characters": "\u0102" },
+ "Â": { "codepoints": [194], "characters": "\u00C2" },
+ "Â": { "codepoints": [194], "characters": "\u00C2" },
+ "А": { "codepoints": [1040], "characters": "\u0410" },
+ "𝔄": { "codepoints": [120068], "characters": "\uD835\uDD04" },
+ "À": { "codepoints": [192], "characters": "\u00C0" },
+ "À": { "codepoints": [192], "characters": "\u00C0" },
+ "Α": { "codepoints": [913], "characters": "\u0391" },
+ "Ā": { "codepoints": [256], "characters": "\u0100" },
+ "⩓": { "codepoints": [10835], "characters": "\u2A53" },
+ "Ą": { "codepoints": [260], "characters": "\u0104" },
+ "𝔸": { "codepoints": [120120], "characters": "\uD835\uDD38" },
+ "⁡": { "codepoints": [8289], "characters": "\u2061" },
+ "Å": { "codepoints": [197], "characters": "\u00C5" },
+ "Å": { "codepoints": [197], "characters": "\u00C5" },
+ "𝒜": { "codepoints": [119964], "characters": "\uD835\uDC9C" },
+ "≔": { "codepoints": [8788], "characters": "\u2254" },
+ "Ã": { "codepoints": [195], "characters": "\u00C3" },
+ "Ã": { "codepoints": [195], "characters": "\u00C3" },
+ "Ä": { "codepoints": [196], "characters": "\u00C4" },
+ "Ä": { "codepoints": [196], "characters": "\u00C4" },
+ "∖": { "codepoints": [8726], "characters": "\u2216" },
+ "⫧": { "codepoints": [10983], "characters": "\u2AE7" },
+ "⌆": { "codepoints": [8966], "characters": "\u2306" },
+ "Б": { "codepoints": [1041], "characters": "\u0411" },
+ "∵": { "codepoints": [8757], "characters": "\u2235" },
+ "ℬ": { "codepoints": [8492], "characters": "\u212C" },
+ "Β": { "codepoints": [914], "characters": "\u0392" },
+ "𝔅": { "codepoints": [120069], "characters": "\uD835\uDD05" },
+ "𝔹": { "codepoints": [120121], "characters": "\uD835\uDD39" },
+ "˘": { "codepoints": [728], "characters": "\u02D8" },
+ "ℬ": { "codepoints": [8492], "characters": "\u212C" },
+ "≎": { "codepoints": [8782], "characters": "\u224E" },
+ "Ч": { "codepoints": [1063], "characters": "\u0427" },
+ "©": { "codepoints": [169], "characters": "\u00A9" },
+ "©": { "codepoints": [169], "characters": "\u00A9" },
+ "Ć": { "codepoints": [262], "characters": "\u0106" },
+ "⋒": { "codepoints": [8914], "characters": "\u22D2" },
+ "ⅅ": { "codepoints": [8517], "characters": "\u2145" },
+ "ℭ": { "codepoints": [8493], "characters": "\u212D" },
+ "Č": { "codepoints": [268], "characters": "\u010C" },
+ "Ç": { "codepoints": [199], "characters": "\u00C7" },
+ "Ç": { "codepoints": [199], "characters": "\u00C7" },
+ "Ĉ": { "codepoints": [264], "characters": "\u0108" },
+ "∰": { "codepoints": [8752], "characters": "\u2230" },
+ "Ċ": { "codepoints": [266], "characters": "\u010A" },
+ "¸": { "codepoints": [184], "characters": "\u00B8" },
+ "·": { "codepoints": [183], "characters": "\u00B7" },
+ "ℭ": { "codepoints": [8493], "characters": "\u212D" },
+ "Χ": { "codepoints": [935], "characters": "\u03A7" },
+ "⊙": { "codepoints": [8857], "characters": "\u2299" },
+ "⊖": { "codepoints": [8854], "characters": "\u2296" },
+ "⊕": { "codepoints": [8853], "characters": "\u2295" },
+ "⊗": { "codepoints": [8855], "characters": "\u2297" },
+ "∲": { "codepoints": [8754], "characters": "\u2232" },
+ "”": { "codepoints": [8221], "characters": "\u201D" },
+ "’": { "codepoints": [8217], "characters": "\u2019" },
+ "∷": { "codepoints": [8759], "characters": "\u2237" },
+ "⩴": { "codepoints": [10868], "characters": "\u2A74" },
+ "≡": { "codepoints": [8801], "characters": "\u2261" },
+ "∯": { "codepoints": [8751], "characters": "\u222F" },
+ "∮": { "codepoints": [8750], "characters": "\u222E" },
+ "ℂ": { "codepoints": [8450], "characters": "\u2102" },
+ "∐": { "codepoints": [8720], "characters": "\u2210" },
+ "∳": { "codepoints": [8755], "characters": "\u2233" },
+ "⨯": { "codepoints": [10799], "characters": "\u2A2F" },
+ "𝒞": { "codepoints": [119966], "characters": "\uD835\uDC9E" },
+ "⋓": { "codepoints": [8915], "characters": "\u22D3" },
+ "≍": { "codepoints": [8781], "characters": "\u224D" },
+ "ⅅ": { "codepoints": [8517], "characters": "\u2145" },
+ "⤑": { "codepoints": [10513], "characters": "\u2911" },
+ "Ђ": { "codepoints": [1026], "characters": "\u0402" },
+ "Ѕ": { "codepoints": [1029], "characters": "\u0405" },
+ "Џ": { "codepoints": [1039], "characters": "\u040F" },
+ "‡": { "codepoints": [8225], "characters": "\u2021" },
+ "↡": { "codepoints": [8609], "characters": "\u21A1" },
+ "⫤": { "codepoints": [10980], "characters": "\u2AE4" },
+ "Ď": { "codepoints": [270], "characters": "\u010E" },
+ "Д": { "codepoints": [1044], "characters": "\u0414" },
+ "∇": { "codepoints": [8711], "characters": "\u2207" },
+ "Δ": { "codepoints": [916], "characters": "\u0394" },
+ "𝔇": { "codepoints": [120071], "characters": "\uD835\uDD07" },
+ "´": { "codepoints": [180], "characters": "\u00B4" },
+ "˙": { "codepoints": [729], "characters": "\u02D9" },
+ "˝": { "codepoints": [733], "characters": "\u02DD" },
+ "`": { "codepoints": [96], "characters": "\u0060" },
+ "˜": { "codepoints": [732], "characters": "\u02DC" },
+ "⋄": { "codepoints": [8900], "characters": "\u22C4" },
+ "ⅆ": { "codepoints": [8518], "characters": "\u2146" },
+ "𝔻": { "codepoints": [120123], "characters": "\uD835\uDD3B" },
+ "¨": { "codepoints": [168], "characters": "\u00A8" },
+ "⃜": { "codepoints": [8412], "characters": "\u20DC" },
+ "≐": { "codepoints": [8784], "characters": "\u2250" },
+ "∯": { "codepoints": [8751], "characters": "\u222F" },
+ "¨": { "codepoints": [168], "characters": "\u00A8" },
+ "⇓": { "codepoints": [8659], "characters": "\u21D3" },
+ "⇐": { "codepoints": [8656], "characters": "\u21D0" },
+ "⇔": { "codepoints": [8660], "characters": "\u21D4" },
+ "⫤": { "codepoints": [10980], "characters": "\u2AE4" },
+ "⟸": { "codepoints": [10232], "characters": "\u27F8" },
+ "⟺": { "codepoints": [10234], "characters": "\u27FA" },
+ "⟹": { "codepoints": [10233], "characters": "\u27F9" },
+ "⇒": { "codepoints": [8658], "characters": "\u21D2" },
+ "⊨": { "codepoints": [8872], "characters": "\u22A8" },
+ "⇑": { "codepoints": [8657], "characters": "\u21D1" },
+ "⇕": { "codepoints": [8661], "characters": "\u21D5" },
+ "∥": { "codepoints": [8741], "characters": "\u2225" },
+ "↓": { "codepoints": [8595], "characters": "\u2193" },
+ "⤓": { "codepoints": [10515], "characters": "\u2913" },
+ "⇵": { "codepoints": [8693], "characters": "\u21F5" },
+ "̑": { "codepoints": [785], "characters": "\u0311" },
+ "⥐": { "codepoints": [10576], "characters": "\u2950" },
+ "⥞": { "codepoints": [10590], "characters": "\u295E" },
+ "↽": { "codepoints": [8637], "characters": "\u21BD" },
+ "⥖": { "codepoints": [10582], "characters": "\u2956" },
+ "⥟": { "codepoints": [10591], "characters": "\u295F" },
+ "⇁": { "codepoints": [8641], "characters": "\u21C1" },
+ "⥗": { "codepoints": [10583], "characters": "\u2957" },
+ "⊤": { "codepoints": [8868], "characters": "\u22A4" },
+ "↧": { "codepoints": [8615], "characters": "\u21A7" },
+ "⇓": { "codepoints": [8659], "characters": "\u21D3" },
+ "𝒟": { "codepoints": [119967], "characters": "\uD835\uDC9F" },
+ "Đ": { "codepoints": [272], "characters": "\u0110" },
+ "Ŋ": { "codepoints": [330], "characters": "\u014A" },
+ "Ð": { "codepoints": [208], "characters": "\u00D0" },
+ "Ð": { "codepoints": [208], "characters": "\u00D0" },
+ "É": { "codepoints": [201], "characters": "\u00C9" },
+ "É": { "codepoints": [201], "characters": "\u00C9" },
+ "Ě": { "codepoints": [282], "characters": "\u011A" },
+ "Ê": { "codepoints": [202], "characters": "\u00CA" },
+ "Ê": { "codepoints": [202], "characters": "\u00CA" },
+ "Э": { "codepoints": [1069], "characters": "\u042D" },
+ "Ė": { "codepoints": [278], "characters": "\u0116" },
+ "𝔈": { "codepoints": [120072], "characters": "\uD835\uDD08" },
+ "È": { "codepoints": [200], "characters": "\u00C8" },
+ "È": { "codepoints": [200], "characters": "\u00C8" },
+ "∈": { "codepoints": [8712], "characters": "\u2208" },
+ "Ē": { "codepoints": [274], "characters": "\u0112" },
+ "◻": { "codepoints": [9723], "characters": "\u25FB" },
+ "▫": { "codepoints": [9643], "characters": "\u25AB" },
+ "Ę": { "codepoints": [280], "characters": "\u0118" },
+ "𝔼": { "codepoints": [120124], "characters": "\uD835\uDD3C" },
+ "Ε": { "codepoints": [917], "characters": "\u0395" },
+ "⩵": { "codepoints": [10869], "characters": "\u2A75" },
+ "≂": { "codepoints": [8770], "characters": "\u2242" },
+ "⇌": { "codepoints": [8652], "characters": "\u21CC" },
+ "ℰ": { "codepoints": [8496], "characters": "\u2130" },
+ "⩳": { "codepoints": [10867], "characters": "\u2A73" },
+ "Η": { "codepoints": [919], "characters": "\u0397" },
+ "Ë": { "codepoints": [203], "characters": "\u00CB" },
+ "Ë": { "codepoints": [203], "characters": "\u00CB" },
+ "∃": { "codepoints": [8707], "characters": "\u2203" },
+ "ⅇ": { "codepoints": [8519], "characters": "\u2147" },
+ "Ф": { "codepoints": [1060], "characters": "\u0424" },
+ "𝔉": { "codepoints": [120073], "characters": "\uD835\uDD09" },
+ "◼": { "codepoints": [9724], "characters": "\u25FC" },
+ "▪": { "codepoints": [9642], "characters": "\u25AA" },
+ "𝔽": { "codepoints": [120125], "characters": "\uD835\uDD3D" },
+ "∀": { "codepoints": [8704], "characters": "\u2200" },
+ "ℱ": { "codepoints": [8497], "characters": "\u2131" },
+ "ℱ": { "codepoints": [8497], "characters": "\u2131" },
+ "Ѓ": { "codepoints": [1027], "characters": "\u0403" },
+ ">": { "codepoints": [62], "characters": "\u003E" },
+ ">": { "codepoints": [62], "characters": "\u003E" },
+ "Γ": { "codepoints": [915], "characters": "\u0393" },
+ "Ϝ": { "codepoints": [988], "characters": "\u03DC" },
+ "Ğ": { "codepoints": [286], "characters": "\u011E" },
+ "Ģ": { "codepoints": [290], "characters": "\u0122" },
+ "Ĝ": { "codepoints": [284], "characters": "\u011C" },
+ "Г": { "codepoints": [1043], "characters": "\u0413" },
+ "Ġ": { "codepoints": [288], "characters": "\u0120" },
+ "𝔊": { "codepoints": [120074], "characters": "\uD835\uDD0A" },
+ "⋙": { "codepoints": [8921], "characters": "\u22D9" },
+ "𝔾": { "codepoints": [120126], "characters": "\uD835\uDD3E" },
+ "≥": { "codepoints": [8805], "characters": "\u2265" },
+ "⋛": { "codepoints": [8923], "characters": "\u22DB" },
+ "≧": { "codepoints": [8807], "characters": "\u2267" },
+ "⪢": { "codepoints": [10914], "characters": "\u2AA2" },
+ "≷": { "codepoints": [8823], "characters": "\u2277" },
+ "⩾": { "codepoints": [10878], "characters": "\u2A7E" },
+ "≳": { "codepoints": [8819], "characters": "\u2273" },
+ "𝒢": { "codepoints": [119970], "characters": "\uD835\uDCA2" },
+ "≫": { "codepoints": [8811], "characters": "\u226B" },
+ "Ъ": { "codepoints": [1066], "characters": "\u042A" },
+ "ˇ": { "codepoints": [711], "characters": "\u02C7" },
+ "^": { "codepoints": [94], "characters": "\u005E" },
+ "Ĥ": { "codepoints": [292], "characters": "\u0124" },
+ "ℌ": { "codepoints": [8460], "characters": "\u210C" },
+ "ℋ": { "codepoints": [8459], "characters": "\u210B" },
+ "ℍ": { "codepoints": [8461], "characters": "\u210D" },
+ "─": { "codepoints": [9472], "characters": "\u2500" },
+ "ℋ": { "codepoints": [8459], "characters": "\u210B" },
+ "Ħ": { "codepoints": [294], "characters": "\u0126" },
+ "≎": { "codepoints": [8782], "characters": "\u224E" },
+ "≏": { "codepoints": [8783], "characters": "\u224F" },
+ "Е": { "codepoints": [1045], "characters": "\u0415" },
+ "IJ": { "codepoints": [306], "characters": "\u0132" },
+ "Ё": { "codepoints": [1025], "characters": "\u0401" },
+ "Í": { "codepoints": [205], "characters": "\u00CD" },
+ "Í": { "codepoints": [205], "characters": "\u00CD" },
+ "Î": { "codepoints": [206], "characters": "\u00CE" },
+ "Î": { "codepoints": [206], "characters": "\u00CE" },
+ "И": { "codepoints": [1048], "characters": "\u0418" },
+ "İ": { "codepoints": [304], "characters": "\u0130" },
+ "ℑ": { "codepoints": [8465], "characters": "\u2111" },
+ "Ì": { "codepoints": [204], "characters": "\u00CC" },
+ "Ì": { "codepoints": [204], "characters": "\u00CC" },
+ "ℑ": { "codepoints": [8465], "characters": "\u2111" },
+ "Ī": { "codepoints": [298], "characters": "\u012A" },
+ "ⅈ": { "codepoints": [8520], "characters": "\u2148" },
+ "⇒": { "codepoints": [8658], "characters": "\u21D2" },
+ "∬": { "codepoints": [8748], "characters": "\u222C" },
+ "∫": { "codepoints": [8747], "characters": "\u222B" },
+ "⋂": { "codepoints": [8898], "characters": "\u22C2" },
+ "⁣": { "codepoints": [8291], "characters": "\u2063" },
+ "⁢": { "codepoints": [8290], "characters": "\u2062" },
+ "Į": { "codepoints": [302], "characters": "\u012E" },
+ "𝕀": { "codepoints": [120128], "characters": "\uD835\uDD40" },
+ "Ι": { "codepoints": [921], "characters": "\u0399" },
+ "ℐ": { "codepoints": [8464], "characters": "\u2110" },
+ "Ĩ": { "codepoints": [296], "characters": "\u0128" },
+ "І": { "codepoints": [1030], "characters": "\u0406" },
+ "Ï": { "codepoints": [207], "characters": "\u00CF" },
+ "Ï": { "codepoints": [207], "characters": "\u00CF" },
+ "Ĵ": { "codepoints": [308], "characters": "\u0134" },
+ "Й": { "codepoints": [1049], "characters": "\u0419" },
+ "𝔍": { "codepoints": [120077], "characters": "\uD835\uDD0D" },
+ "𝕁": { "codepoints": [120129], "characters": "\uD835\uDD41" },
+ "𝒥": { "codepoints": [119973], "characters": "\uD835\uDCA5" },
+ "Ј": { "codepoints": [1032], "characters": "\u0408" },
+ "Є": { "codepoints": [1028], "characters": "\u0404" },
+ "Х": { "codepoints": [1061], "characters": "\u0425" },
+ "Ќ": { "codepoints": [1036], "characters": "\u040C" },
+ "Κ": { "codepoints": [922], "characters": "\u039A" },
+ "Ķ": { "codepoints": [310], "characters": "\u0136" },
+ "К": { "codepoints": [1050], "characters": "\u041A" },
+ "𝔎": { "codepoints": [120078], "characters": "\uD835\uDD0E" },
+ "𝕂": { "codepoints": [120130], "characters": "\uD835\uDD42" },
+ "𝒦": { "codepoints": [119974], "characters": "\uD835\uDCA6" },
+ "Љ": { "codepoints": [1033], "characters": "\u0409" },
+ "<": { "codepoints": [60], "characters": "\u003C" },
+ "<": { "codepoints": [60], "characters": "\u003C" },
+ "Ĺ": { "codepoints": [313], "characters": "\u0139" },
+ "Λ": { "codepoints": [923], "characters": "\u039B" },
+ "⟪": { "codepoints": [10218], "characters": "\u27EA" },
+ "ℒ": { "codepoints": [8466], "characters": "\u2112" },
+ "↞": { "codepoints": [8606], "characters": "\u219E" },
+ "Ľ": { "codepoints": [317], "characters": "\u013D" },
+ "Ļ": { "codepoints": [315], "characters": "\u013B" },
+ "Л": { "codepoints": [1051], "characters": "\u041B" },
+ "⟨": { "codepoints": [10216], "characters": "\u27E8" },
+ "←": { "codepoints": [8592], "characters": "\u2190" },
+ "⇤": { "codepoints": [8676], "characters": "\u21E4" },
+ "⇆": { "codepoints": [8646], "characters": "\u21C6" },
+ "⌈": { "codepoints": [8968], "characters": "\u2308" },
+ "⟦": { "codepoints": [10214], "characters": "\u27E6" },
+ "⥡": { "codepoints": [10593], "characters": "\u2961" },
+ "⇃": { "codepoints": [8643], "characters": "\u21C3" },
+ "⥙": { "codepoints": [10585], "characters": "\u2959" },
+ "⌊": { "codepoints": [8970], "characters": "\u230A" },
+ "↔": { "codepoints": [8596], "characters": "\u2194" },
+ "⥎": { "codepoints": [10574], "characters": "\u294E" },
+ "⊣": { "codepoints": [8867], "characters": "\u22A3" },
+ "↤": { "codepoints": [8612], "characters": "\u21A4" },
+ "⥚": { "codepoints": [10586], "characters": "\u295A" },
+ "⊲": { "codepoints": [8882], "characters": "\u22B2" },
+ "⧏": { "codepoints": [10703], "characters": "\u29CF" },
+ "⊴": { "codepoints": [8884], "characters": "\u22B4" },
+ "⥑": { "codepoints": [10577], "characters": "\u2951" },
+ "⥠": { "codepoints": [10592], "characters": "\u2960" },
+ "↿": { "codepoints": [8639], "characters": "\u21BF" },
+ "⥘": { "codepoints": [10584], "characters": "\u2958" },
+ "↼": { "codepoints": [8636], "characters": "\u21BC" },
+ "⥒": { "codepoints": [10578], "characters": "\u2952" },
+ "⇐": { "codepoints": [8656], "characters": "\u21D0" },
+ "⇔": { "codepoints": [8660], "characters": "\u21D4" },
+ "⋚": { "codepoints": [8922], "characters": "\u22DA" },
+ "≦": { "codepoints": [8806], "characters": "\u2266" },
+ "≶": { "codepoints": [8822], "characters": "\u2276" },
+ "⪡": { "codepoints": [10913], "characters": "\u2AA1" },
+ "⩽": { "codepoints": [10877], "characters": "\u2A7D" },
+ "≲": { "codepoints": [8818], "characters": "\u2272" },
+ "𝔏": { "codepoints": [120079], "characters": "\uD835\uDD0F" },
+ "⋘": { "codepoints": [8920], "characters": "\u22D8" },
+ "⇚": { "codepoints": [8666], "characters": "\u21DA" },
+ "Ŀ": { "codepoints": [319], "characters": "\u013F" },
+ "⟵": { "codepoints": [10229], "characters": "\u27F5" },
+ "⟷": { "codepoints": [10231], "characters": "\u27F7" },
+ "⟶": { "codepoints": [10230], "characters": "\u27F6" },
+ "⟸": { "codepoints": [10232], "characters": "\u27F8" },
+ "⟺": { "codepoints": [10234], "characters": "\u27FA" },
+ "⟹": { "codepoints": [10233], "characters": "\u27F9" },
+ "𝕃": { "codepoints": [120131], "characters": "\uD835\uDD43" },
+ "↙": { "codepoints": [8601], "characters": "\u2199" },
+ "↘": { "codepoints": [8600], "characters": "\u2198" },
+ "ℒ": { "codepoints": [8466], "characters": "\u2112" },
+ "↰": { "codepoints": [8624], "characters": "\u21B0" },
+ "Ł": { "codepoints": [321], "characters": "\u0141" },
+ "≪": { "codepoints": [8810], "characters": "\u226A" },
+ "⤅": { "codepoints": [10501], "characters": "\u2905" },
+ "М": { "codepoints": [1052], "characters": "\u041C" },
+ " ": { "codepoints": [8287], "characters": "\u205F" },
+ "ℳ": { "codepoints": [8499], "characters": "\u2133" },
+ "𝔐": { "codepoints": [120080], "characters": "\uD835\uDD10" },
+ "∓": { "codepoints": [8723], "characters": "\u2213" },
+ "𝕄": { "codepoints": [120132], "characters": "\uD835\uDD44" },
+ "ℳ": { "codepoints": [8499], "characters": "\u2133" },
+ "Μ": { "codepoints": [924], "characters": "\u039C" },
+ "Њ": { "codepoints": [1034], "characters": "\u040A" },
+ "Ń": { "codepoints": [323], "characters": "\u0143" },
+ "Ň": { "codepoints": [327], "characters": "\u0147" },
+ "Ņ": { "codepoints": [325], "characters": "\u0145" },
+ "Н": { "codepoints": [1053], "characters": "\u041D" },
+ "​": { "codepoints": [8203], "characters": "\u200B" },
+ "​": { "codepoints": [8203], "characters": "\u200B" },
+ "​": { "codepoints": [8203], "characters": "\u200B" },
+ "​": { "codepoints": [8203], "characters": "\u200B" },
+ "≫": { "codepoints": [8811], "characters": "\u226B" },
+ "≪": { "codepoints": [8810], "characters": "\u226A" },
+ "
": { "codepoints": [10], "characters": "\u000A" },
+ "𝔑": { "codepoints": [120081], "characters": "\uD835\uDD11" },
+ "⁠": { "codepoints": [8288], "characters": "\u2060" },
+ " ": { "codepoints": [160], "characters": "\u00A0" },
+ "ℕ": { "codepoints": [8469], "characters": "\u2115" },
+ "⫬": { "codepoints": [10988], "characters": "\u2AEC" },
+ "≢": { "codepoints": [8802], "characters": "\u2262" },
+ "≭": { "codepoints": [8813], "characters": "\u226D" },
+ "∦": { "codepoints": [8742], "characters": "\u2226" },
+ "∉": { "codepoints": [8713], "characters": "\u2209" },
+ "≠": { "codepoints": [8800], "characters": "\u2260" },
+ "≂̸": { "codepoints": [8770, 824], "characters": "\u2242\u0338" },
+ "∄": { "codepoints": [8708], "characters": "\u2204" },
+ "≯": { "codepoints": [8815], "characters": "\u226F" },
+ "≱": { "codepoints": [8817], "characters": "\u2271" },
+ "≧̸": { "codepoints": [8807, 824], "characters": "\u2267\u0338" },
+ "≫̸": { "codepoints": [8811, 824], "characters": "\u226B\u0338" },
+ "≹": { "codepoints": [8825], "characters": "\u2279" },
+ "⩾̸": { "codepoints": [10878, 824], "characters": "\u2A7E\u0338" },
+ "≵": { "codepoints": [8821], "characters": "\u2275" },
+ "≎̸": { "codepoints": [8782, 824], "characters": "\u224E\u0338" },
+ "≏̸": { "codepoints": [8783, 824], "characters": "\u224F\u0338" },
+ "⋪": { "codepoints": [8938], "characters": "\u22EA" },
+ "⧏̸": { "codepoints": [10703, 824], "characters": "\u29CF\u0338" },
+ "⋬": { "codepoints": [8940], "characters": "\u22EC" },
+ "≮": { "codepoints": [8814], "characters": "\u226E" },
+ "≰": { "codepoints": [8816], "characters": "\u2270" },
+ "≸": { "codepoints": [8824], "characters": "\u2278" },
+ "≪̸": { "codepoints": [8810, 824], "characters": "\u226A\u0338" },
+ "⩽̸": { "codepoints": [10877, 824], "characters": "\u2A7D\u0338" },
+ "≴": { "codepoints": [8820], "characters": "\u2274" },
+ "⪢̸": { "codepoints": [10914, 824], "characters": "\u2AA2\u0338" },
+ "⪡̸": { "codepoints": [10913, 824], "characters": "\u2AA1\u0338" },
+ "⊀": { "codepoints": [8832], "characters": "\u2280" },
+ "⪯̸": { "codepoints": [10927, 824], "characters": "\u2AAF\u0338" },
+ "⋠": { "codepoints": [8928], "characters": "\u22E0" },
+ "∌": { "codepoints": [8716], "characters": "\u220C" },
+ "⋫": { "codepoints": [8939], "characters": "\u22EB" },
+ "⧐̸": { "codepoints": [10704, 824], "characters": "\u29D0\u0338" },
+ "⋭": { "codepoints": [8941], "characters": "\u22ED" },
+ "⊏̸": { "codepoints": [8847, 824], "characters": "\u228F\u0338" },
+ "⋢": { "codepoints": [8930], "characters": "\u22E2" },
+ "⊐̸": { "codepoints": [8848, 824], "characters": "\u2290\u0338" },
+ "⋣": { "codepoints": [8931], "characters": "\u22E3" },
+ "⊂⃒": { "codepoints": [8834, 8402], "characters": "\u2282\u20D2" },
+ "⊈": { "codepoints": [8840], "characters": "\u2288" },
+ "⊁": { "codepoints": [8833], "characters": "\u2281" },
+ "⪰̸": { "codepoints": [10928, 824], "characters": "\u2AB0\u0338" },
+ "⋡": { "codepoints": [8929], "characters": "\u22E1" },
+ "≿̸": { "codepoints": [8831, 824], "characters": "\u227F\u0338" },
+ "⊃⃒": { "codepoints": [8835, 8402], "characters": "\u2283\u20D2" },
+ "⊉": { "codepoints": [8841], "characters": "\u2289" },
+ "≁": { "codepoints": [8769], "characters": "\u2241" },
+ "≄": { "codepoints": [8772], "characters": "\u2244" },
+ "≇": { "codepoints": [8775], "characters": "\u2247" },
+ "≉": { "codepoints": [8777], "characters": "\u2249" },
+ "∤": { "codepoints": [8740], "characters": "\u2224" },
+ "𝒩": { "codepoints": [119977], "characters": "\uD835\uDCA9" },
+ "Ñ": { "codepoints": [209], "characters": "\u00D1" },
+ "Ñ": { "codepoints": [209], "characters": "\u00D1" },
+ "Ν": { "codepoints": [925], "characters": "\u039D" },
+ "Œ": { "codepoints": [338], "characters": "\u0152" },
+ "Ó": { "codepoints": [211], "characters": "\u00D3" },
+ "Ó": { "codepoints": [211], "characters": "\u00D3" },
+ "Ô": { "codepoints": [212], "characters": "\u00D4" },
+ "Ô": { "codepoints": [212], "characters": "\u00D4" },
+ "О": { "codepoints": [1054], "characters": "\u041E" },
+ "Ő": { "codepoints": [336], "characters": "\u0150" },
+ "𝔒": { "codepoints": [120082], "characters": "\uD835\uDD12" },
+ "Ò": { "codepoints": [210], "characters": "\u00D2" },
+ "Ò": { "codepoints": [210], "characters": "\u00D2" },
+ "Ō": { "codepoints": [332], "characters": "\u014C" },
+ "Ω": { "codepoints": [937], "characters": "\u03A9" },
+ "Ο": { "codepoints": [927], "characters": "\u039F" },
+ "𝕆": { "codepoints": [120134], "characters": "\uD835\uDD46" },
+ "“": { "codepoints": [8220], "characters": "\u201C" },
+ "‘": { "codepoints": [8216], "characters": "\u2018" },
+ "⩔": { "codepoints": [10836], "characters": "\u2A54" },
+ "𝒪": { "codepoints": [119978], "characters": "\uD835\uDCAA" },
+ "Ø": { "codepoints": [216], "characters": "\u00D8" },
+ "Ø": { "codepoints": [216], "characters": "\u00D8" },
+ "Õ": { "codepoints": [213], "characters": "\u00D5" },
+ "Õ": { "codepoints": [213], "characters": "\u00D5" },
+ "⨷": { "codepoints": [10807], "characters": "\u2A37" },
+ "Ö": { "codepoints": [214], "characters": "\u00D6" },
+ "Ö": { "codepoints": [214], "characters": "\u00D6" },
+ "‾": { "codepoints": [8254], "characters": "\u203E" },
+ "⏞": { "codepoints": [9182], "characters": "\u23DE" },
+ "⎴": { "codepoints": [9140], "characters": "\u23B4" },
+ "⏜": { "codepoints": [9180], "characters": "\u23DC" },
+ "∂": { "codepoints": [8706], "characters": "\u2202" },
+ "П": { "codepoints": [1055], "characters": "\u041F" },
+ "𝔓": { "codepoints": [120083], "characters": "\uD835\uDD13" },
+ "Φ": { "codepoints": [934], "characters": "\u03A6" },
+ "Π": { "codepoints": [928], "characters": "\u03A0" },
+ "±": { "codepoints": [177], "characters": "\u00B1" },
+ "ℌ": { "codepoints": [8460], "characters": "\u210C" },
+ "ℙ": { "codepoints": [8473], "characters": "\u2119" },
+ "⪻": { "codepoints": [10939], "characters": "\u2ABB" },
+ "≺": { "codepoints": [8826], "characters": "\u227A" },
+ "⪯": { "codepoints": [10927], "characters": "\u2AAF" },
+ "≼": { "codepoints": [8828], "characters": "\u227C" },
+ "≾": { "codepoints": [8830], "characters": "\u227E" },
+ "″": { "codepoints": [8243], "characters": "\u2033" },
+ "∏": { "codepoints": [8719], "characters": "\u220F" },
+ "∷": { "codepoints": [8759], "characters": "\u2237" },
+ "∝": { "codepoints": [8733], "characters": "\u221D" },
+ "𝒫": { "codepoints": [119979], "characters": "\uD835\uDCAB" },
+ "Ψ": { "codepoints": [936], "characters": "\u03A8" },
+ """: { "codepoints": [34], "characters": "\u0022" },
+ """: { "codepoints": [34], "characters": "\u0022" },
+ "𝔔": { "codepoints": [120084], "characters": "\uD835\uDD14" },
+ "ℚ": { "codepoints": [8474], "characters": "\u211A" },
+ "𝒬": { "codepoints": [119980], "characters": "\uD835\uDCAC" },
+ "⤐": { "codepoints": [10512], "characters": "\u2910" },
+ "®": { "codepoints": [174], "characters": "\u00AE" },
+ "®": { "codepoints": [174], "characters": "\u00AE" },
+ "Ŕ": { "codepoints": [340], "characters": "\u0154" },
+ "⟫": { "codepoints": [10219], "characters": "\u27EB" },
+ "↠": { "codepoints": [8608], "characters": "\u21A0" },
+ "⤖": { "codepoints": [10518], "characters": "\u2916" },
+ "Ř": { "codepoints": [344], "characters": "\u0158" },
+ "Ŗ": { "codepoints": [342], "characters": "\u0156" },
+ "Р": { "codepoints": [1056], "characters": "\u0420" },
+ "ℜ": { "codepoints": [8476], "characters": "\u211C" },
+ "∋": { "codepoints": [8715], "characters": "\u220B" },
+ "⇋": { "codepoints": [8651], "characters": "\u21CB" },
+ "⥯": { "codepoints": [10607], "characters": "\u296F" },
+ "ℜ": { "codepoints": [8476], "characters": "\u211C" },
+ "Ρ": { "codepoints": [929], "characters": "\u03A1" },
+ "⟩": { "codepoints": [10217], "characters": "\u27E9" },
+ "→": { "codepoints": [8594], "characters": "\u2192" },
+ "⇥": { "codepoints": [8677], "characters": "\u21E5" },
+ "⇄": { "codepoints": [8644], "characters": "\u21C4" },
+ "⌉": { "codepoints": [8969], "characters": "\u2309" },
+ "⟧": { "codepoints": [10215], "characters": "\u27E7" },
+ "⥝": { "codepoints": [10589], "characters": "\u295D" },
+ "⇂": { "codepoints": [8642], "characters": "\u21C2" },
+ "⥕": { "codepoints": [10581], "characters": "\u2955" },
+ "⌋": { "codepoints": [8971], "characters": "\u230B" },
+ "⊢": { "codepoints": [8866], "characters": "\u22A2" },
+ "↦": { "codepoints": [8614], "characters": "\u21A6" },
+ "⥛": { "codepoints": [10587], "characters": "\u295B" },
+ "⊳": { "codepoints": [8883], "characters": "\u22B3" },
+ "⧐": { "codepoints": [10704], "characters": "\u29D0" },
+ "⊵": { "codepoints": [8885], "characters": "\u22B5" },
+ "⥏": { "codepoints": [10575], "characters": "\u294F" },
+ "⥜": { "codepoints": [10588], "characters": "\u295C" },
+ "↾": { "codepoints": [8638], "characters": "\u21BE" },
+ "⥔": { "codepoints": [10580], "characters": "\u2954" },
+ "⇀": { "codepoints": [8640], "characters": "\u21C0" },
+ "⥓": { "codepoints": [10579], "characters": "\u2953" },
+ "⇒": { "codepoints": [8658], "characters": "\u21D2" },
+ "ℝ": { "codepoints": [8477], "characters": "\u211D" },
+ "⥰": { "codepoints": [10608], "characters": "\u2970" },
+ "⇛": { "codepoints": [8667], "characters": "\u21DB" },
+ "ℛ": { "codepoints": [8475], "characters": "\u211B" },
+ "↱": { "codepoints": [8625], "characters": "\u21B1" },
+ "⧴": { "codepoints": [10740], "characters": "\u29F4" },
+ "Щ": { "codepoints": [1065], "characters": "\u0429" },
+ "Ш": { "codepoints": [1064], "characters": "\u0428" },
+ "Ь": { "codepoints": [1068], "characters": "\u042C" },
+ "Ś": { "codepoints": [346], "characters": "\u015A" },
+ "⪼": { "codepoints": [10940], "characters": "\u2ABC" },
+ "Š": { "codepoints": [352], "characters": "\u0160" },
+ "Ş": { "codepoints": [350], "characters": "\u015E" },
+ "Ŝ": { "codepoints": [348], "characters": "\u015C" },
+ "С": { "codepoints": [1057], "characters": "\u0421" },
+ "𝔖": { "codepoints": [120086], "characters": "\uD835\uDD16" },
+ "↓": { "codepoints": [8595], "characters": "\u2193" },
+ "←": { "codepoints": [8592], "characters": "\u2190" },
+ "→": { "codepoints": [8594], "characters": "\u2192" },
+ "↑": { "codepoints": [8593], "characters": "\u2191" },
+ "Σ": { "codepoints": [931], "characters": "\u03A3" },
+ "∘": { "codepoints": [8728], "characters": "\u2218" },
+ "𝕊": { "codepoints": [120138], "characters": "\uD835\uDD4A" },
+ "√": { "codepoints": [8730], "characters": "\u221A" },
+ "□": { "codepoints": [9633], "characters": "\u25A1" },
+ "⊓": { "codepoints": [8851], "characters": "\u2293" },
+ "⊏": { "codepoints": [8847], "characters": "\u228F" },
+ "⊑": { "codepoints": [8849], "characters": "\u2291" },
+ "⊐": { "codepoints": [8848], "characters": "\u2290" },
+ "⊒": { "codepoints": [8850], "characters": "\u2292" },
+ "⊔": { "codepoints": [8852], "characters": "\u2294" },
+ "𝒮": { "codepoints": [119982], "characters": "\uD835\uDCAE" },
+ "⋆": { "codepoints": [8902], "characters": "\u22C6" },
+ "⋐": { "codepoints": [8912], "characters": "\u22D0" },
+ "⋐": { "codepoints": [8912], "characters": "\u22D0" },
+ "⊆": { "codepoints": [8838], "characters": "\u2286" },
+ "≻": { "codepoints": [8827], "characters": "\u227B" },
+ "⪰": { "codepoints": [10928], "characters": "\u2AB0" },
+ "≽": { "codepoints": [8829], "characters": "\u227D" },
+ "≿": { "codepoints": [8831], "characters": "\u227F" },
+ "∋": { "codepoints": [8715], "characters": "\u220B" },
+ "∑": { "codepoints": [8721], "characters": "\u2211" },
+ "⋑": { "codepoints": [8913], "characters": "\u22D1" },
+ "⊃": { "codepoints": [8835], "characters": "\u2283" },
+ "⊇": { "codepoints": [8839], "characters": "\u2287" },
+ "⋑": { "codepoints": [8913], "characters": "\u22D1" },
+ "Þ": { "codepoints": [222], "characters": "\u00DE" },
+ "Þ": { "codepoints": [222], "characters": "\u00DE" },
+ "™": { "codepoints": [8482], "characters": "\u2122" },
+ "Ћ": { "codepoints": [1035], "characters": "\u040B" },
+ "Ц": { "codepoints": [1062], "characters": "\u0426" },
+ "	": { "codepoints": [9], "characters": "\u0009" },
+ "Τ": { "codepoints": [932], "characters": "\u03A4" },
+ "Ť": { "codepoints": [356], "characters": "\u0164" },
+ "Ţ": { "codepoints": [354], "characters": "\u0162" },
+ "Т": { "codepoints": [1058], "characters": "\u0422" },
+ "𝔗": { "codepoints": [120087], "characters": "\uD835\uDD17" },
+ "∴": { "codepoints": [8756], "characters": "\u2234" },
+ "Θ": { "codepoints": [920], "characters": "\u0398" },
+ "  ": { "codepoints": [8287, 8202], "characters": "\u205F\u200A" },
+ " ": { "codepoints": [8201], "characters": "\u2009" },
+ "∼": { "codepoints": [8764], "characters": "\u223C" },
+ "≃": { "codepoints": [8771], "characters": "\u2243" },
+ "≅": { "codepoints": [8773], "characters": "\u2245" },
+ "≈": { "codepoints": [8776], "characters": "\u2248" },
+ "𝕋": { "codepoints": [120139], "characters": "\uD835\uDD4B" },
+ "⃛": { "codepoints": [8411], "characters": "\u20DB" },
+ "𝒯": { "codepoints": [119983], "characters": "\uD835\uDCAF" },
+ "Ŧ": { "codepoints": [358], "characters": "\u0166" },
+ "Ú": { "codepoints": [218], "characters": "\u00DA" },
+ "Ú": { "codepoints": [218], "characters": "\u00DA" },
+ "↟": { "codepoints": [8607], "characters": "\u219F" },
+ "⥉": { "codepoints": [10569], "characters": "\u2949" },
+ "Ў": { "codepoints": [1038], "characters": "\u040E" },
+ "Ŭ": { "codepoints": [364], "characters": "\u016C" },
+ "Û": { "codepoints": [219], "characters": "\u00DB" },
+ "Û": { "codepoints": [219], "characters": "\u00DB" },
+ "У": { "codepoints": [1059], "characters": "\u0423" },
+ "Ű": { "codepoints": [368], "characters": "\u0170" },
+ "𝔘": { "codepoints": [120088], "characters": "\uD835\uDD18" },
+ "Ù": { "codepoints": [217], "characters": "\u00D9" },
+ "Ù": { "codepoints": [217], "characters": "\u00D9" },
+ "Ū": { "codepoints": [362], "characters": "\u016A" },
+ "_": { "codepoints": [95], "characters": "\u005F" },
+ "⏟": { "codepoints": [9183], "characters": "\u23DF" },
+ "⎵": { "codepoints": [9141], "characters": "\u23B5" },
+ "⏝": { "codepoints": [9181], "characters": "\u23DD" },
+ "⋃": { "codepoints": [8899], "characters": "\u22C3" },
+ "⊎": { "codepoints": [8846], "characters": "\u228E" },
+ "Ų": { "codepoints": [370], "characters": "\u0172" },
+ "𝕌": { "codepoints": [120140], "characters": "\uD835\uDD4C" },
+ "↑": { "codepoints": [8593], "characters": "\u2191" },
+ "⤒": { "codepoints": [10514], "characters": "\u2912" },
+ "⇅": { "codepoints": [8645], "characters": "\u21C5" },
+ "↕": { "codepoints": [8597], "characters": "\u2195" },
+ "⥮": { "codepoints": [10606], "characters": "\u296E" },
+ "⊥": { "codepoints": [8869], "characters": "\u22A5" },
+ "↥": { "codepoints": [8613], "characters": "\u21A5" },
+ "⇑": { "codepoints": [8657], "characters": "\u21D1" },
+ "⇕": { "codepoints": [8661], "characters": "\u21D5" },
+ "↖": { "codepoints": [8598], "characters": "\u2196" },
+ "↗": { "codepoints": [8599], "characters": "\u2197" },
+ "ϒ": { "codepoints": [978], "characters": "\u03D2" },
+ "Υ": { "codepoints": [933], "characters": "\u03A5" },
+ "Ů": { "codepoints": [366], "characters": "\u016E" },
+ "𝒰": { "codepoints": [119984], "characters": "\uD835\uDCB0" },
+ "Ũ": { "codepoints": [360], "characters": "\u0168" },
+ "Ü": { "codepoints": [220], "characters": "\u00DC" },
+ "Ü": { "codepoints": [220], "characters": "\u00DC" },
+ "⊫": { "codepoints": [8875], "characters": "\u22AB" },
+ "⫫": { "codepoints": [10987], "characters": "\u2AEB" },
+ "В": { "codepoints": [1042], "characters": "\u0412" },
+ "⊩": { "codepoints": [8873], "characters": "\u22A9" },
+ "⫦": { "codepoints": [10982], "characters": "\u2AE6" },
+ "⋁": { "codepoints": [8897], "characters": "\u22C1" },
+ "‖": { "codepoints": [8214], "characters": "\u2016" },
+ "‖": { "codepoints": [8214], "characters": "\u2016" },
+ "∣": { "codepoints": [8739], "characters": "\u2223" },
+ "|": { "codepoints": [124], "characters": "\u007C" },
+ "❘": { "codepoints": [10072], "characters": "\u2758" },
+ "≀": { "codepoints": [8768], "characters": "\u2240" },
+ " ": { "codepoints": [8202], "characters": "\u200A" },
+ "𝔙": { "codepoints": [120089], "characters": "\uD835\uDD19" },
+ "𝕍": { "codepoints": [120141], "characters": "\uD835\uDD4D" },
+ "𝒱": { "codepoints": [119985], "characters": "\uD835\uDCB1" },
+ "⊪": { "codepoints": [8874], "characters": "\u22AA" },
+ "Ŵ": { "codepoints": [372], "characters": "\u0174" },
+ "⋀": { "codepoints": [8896], "characters": "\u22C0" },
+ "𝔚": { "codepoints": [120090], "characters": "\uD835\uDD1A" },
+ "𝕎": { "codepoints": [120142], "characters": "\uD835\uDD4E" },
+ "𝒲": { "codepoints": [119986], "characters": "\uD835\uDCB2" },
+ "𝔛": { "codepoints": [120091], "characters": "\uD835\uDD1B" },
+ "Ξ": { "codepoints": [926], "characters": "\u039E" },
+ "𝕏": { "codepoints": [120143], "characters": "\uD835\uDD4F" },
+ "𝒳": { "codepoints": [119987], "characters": "\uD835\uDCB3" },
+ "Я": { "codepoints": [1071], "characters": "\u042F" },
+ "Ї": { "codepoints": [1031], "characters": "\u0407" },
+ "Ю": { "codepoints": [1070], "characters": "\u042E" },
+ "Ý": { "codepoints": [221], "characters": "\u00DD" },
+ "Ý": { "codepoints": [221], "characters": "\u00DD" },
+ "Ŷ": { "codepoints": [374], "characters": "\u0176" },
+ "Ы": { "codepoints": [1067], "characters": "\u042B" },
+ "𝔜": { "codepoints": [120092], "characters": "\uD835\uDD1C" },
+ "𝕐": { "codepoints": [120144], "characters": "\uD835\uDD50" },
+ "𝒴": { "codepoints": [119988], "characters": "\uD835\uDCB4" },
+ "Ÿ": { "codepoints": [376], "characters": "\u0178" },
+ "Ж": { "codepoints": [1046], "characters": "\u0416" },
+ "Ź": { "codepoints": [377], "characters": "\u0179" },
+ "Ž": { "codepoints": [381], "characters": "\u017D" },
+ "З": { "codepoints": [1047], "characters": "\u0417" },
+ "Ż": { "codepoints": [379], "characters": "\u017B" },
+ "​": { "codepoints": [8203], "characters": "\u200B" },
+ "Ζ": { "codepoints": [918], "characters": "\u0396" },
+ "ℨ": { "codepoints": [8488], "characters": "\u2128" },
+ "ℤ": { "codepoints": [8484], "characters": "\u2124" },
+ "𝒵": { "codepoints": [119989], "characters": "\uD835\uDCB5" },
+ "á": { "codepoints": [225], "characters": "\u00E1" },
+ "á": { "codepoints": [225], "characters": "\u00E1" },
+ "ă": { "codepoints": [259], "characters": "\u0103" },
+ "∾": { "codepoints": [8766], "characters": "\u223E" },
+ "∾̳": { "codepoints": [8766, 819], "characters": "\u223E\u0333" },
+ "∿": { "codepoints": [8767], "characters": "\u223F" },
+ "â": { "codepoints": [226], "characters": "\u00E2" },
+ "â": { "codepoints": [226], "characters": "\u00E2" },
+ "´": { "codepoints": [180], "characters": "\u00B4" },
+ "´": { "codepoints": [180], "characters": "\u00B4" },
+ "а": { "codepoints": [1072], "characters": "\u0430" },
+ "æ": { "codepoints": [230], "characters": "\u00E6" },
+ "æ": { "codepoints": [230], "characters": "\u00E6" },
+ "⁡": { "codepoints": [8289], "characters": "\u2061" },
+ "𝔞": { "codepoints": [120094], "characters": "\uD835\uDD1E" },
+ "à": { "codepoints": [224], "characters": "\u00E0" },
+ "à": { "codepoints": [224], "characters": "\u00E0" },
+ "ℵ": { "codepoints": [8501], "characters": "\u2135" },
+ "ℵ": { "codepoints": [8501], "characters": "\u2135" },
+ "α": { "codepoints": [945], "characters": "\u03B1" },
+ "ā": { "codepoints": [257], "characters": "\u0101" },
+ "⨿": { "codepoints": [10815], "characters": "\u2A3F" },
+ "&": { "codepoints": [38], "characters": "\u0026" },
+ "&": { "codepoints": [38], "characters": "\u0026" },
+ "∧": { "codepoints": [8743], "characters": "\u2227" },
+ "⩕": { "codepoints": [10837], "characters": "\u2A55" },
+ "⩜": { "codepoints": [10844], "characters": "\u2A5C" },
+ "⩘": { "codepoints": [10840], "characters": "\u2A58" },
+ "⩚": { "codepoints": [10842], "characters": "\u2A5A" },
+ "∠": { "codepoints": [8736], "characters": "\u2220" },
+ "⦤": { "codepoints": [10660], "characters": "\u29A4" },
+ "∠": { "codepoints": [8736], "characters": "\u2220" },
+ "∡": { "codepoints": [8737], "characters": "\u2221" },
+ "⦨": { "codepoints": [10664], "characters": "\u29A8" },
+ "⦩": { "codepoints": [10665], "characters": "\u29A9" },
+ "⦪": { "codepoints": [10666], "characters": "\u29AA" },
+ "⦫": { "codepoints": [10667], "characters": "\u29AB" },
+ "⦬": { "codepoints": [10668], "characters": "\u29AC" },
+ "⦭": { "codepoints": [10669], "characters": "\u29AD" },
+ "⦮": { "codepoints": [10670], "characters": "\u29AE" },
+ "⦯": { "codepoints": [10671], "characters": "\u29AF" },
+ "∟": { "codepoints": [8735], "characters": "\u221F" },
+ "⊾": { "codepoints": [8894], "characters": "\u22BE" },
+ "⦝": { "codepoints": [10653], "characters": "\u299D" },
+ "∢": { "codepoints": [8738], "characters": "\u2222" },
+ "Å": { "codepoints": [197], "characters": "\u00C5" },
+ "⍼": { "codepoints": [9084], "characters": "\u237C" },
+ "ą": { "codepoints": [261], "characters": "\u0105" },
+ "𝕒": { "codepoints": [120146], "characters": "\uD835\uDD52" },
+ "≈": { "codepoints": [8776], "characters": "\u2248" },
+ "⩰": { "codepoints": [10864], "characters": "\u2A70" },
+ "⩯": { "codepoints": [10863], "characters": "\u2A6F" },
+ "≊": { "codepoints": [8778], "characters": "\u224A" },
+ "≋": { "codepoints": [8779], "characters": "\u224B" },
+ "'": { "codepoints": [39], "characters": "\u0027" },
+ "≈": { "codepoints": [8776], "characters": "\u2248" },
+ "≊": { "codepoints": [8778], "characters": "\u224A" },
+ "å": { "codepoints": [229], "characters": "\u00E5" },
+ "å": { "codepoints": [229], "characters": "\u00E5" },
+ "𝒶": { "codepoints": [119990], "characters": "\uD835\uDCB6" },
+ "*": { "codepoints": [42], "characters": "\u002A" },
+ "≈": { "codepoints": [8776], "characters": "\u2248" },
+ "≍": { "codepoints": [8781], "characters": "\u224D" },
+ "ã": { "codepoints": [227], "characters": "\u00E3" },
+ "ã": { "codepoints": [227], "characters": "\u00E3" },
+ "ä": { "codepoints": [228], "characters": "\u00E4" },
+ "ä": { "codepoints": [228], "characters": "\u00E4" },
+ "∳": { "codepoints": [8755], "characters": "\u2233" },
+ "⨑": { "codepoints": [10769], "characters": "\u2A11" },
+ "⫭": { "codepoints": [10989], "characters": "\u2AED" },
+ "≌": { "codepoints": [8780], "characters": "\u224C" },
+ "϶": { "codepoints": [1014], "characters": "\u03F6" },
+ "‵": { "codepoints": [8245], "characters": "\u2035" },
+ "∽": { "codepoints": [8765], "characters": "\u223D" },
+ "⋍": { "codepoints": [8909], "characters": "\u22CD" },
+ "⊽": { "codepoints": [8893], "characters": "\u22BD" },
+ "⌅": { "codepoints": [8965], "characters": "\u2305" },
+ "⌅": { "codepoints": [8965], "characters": "\u2305" },
+ "⎵": { "codepoints": [9141], "characters": "\u23B5" },
+ "⎶": { "codepoints": [9142], "characters": "\u23B6" },
+ "≌": { "codepoints": [8780], "characters": "\u224C" },
+ "б": { "codepoints": [1073], "characters": "\u0431" },
+ "„": { "codepoints": [8222], "characters": "\u201E" },
+ "∵": { "codepoints": [8757], "characters": "\u2235" },
+ "∵": { "codepoints": [8757], "characters": "\u2235" },
+ "⦰": { "codepoints": [10672], "characters": "\u29B0" },
+ "϶": { "codepoints": [1014], "characters": "\u03F6" },
+ "ℬ": { "codepoints": [8492], "characters": "\u212C" },
+ "β": { "codepoints": [946], "characters": "\u03B2" },
+ "ℶ": { "codepoints": [8502], "characters": "\u2136" },
+ "≬": { "codepoints": [8812], "characters": "\u226C" },
+ "𝔟": { "codepoints": [120095], "characters": "\uD835\uDD1F" },
+ "⋂": { "codepoints": [8898], "characters": "\u22C2" },
+ "◯": { "codepoints": [9711], "characters": "\u25EF" },
+ "⋃": { "codepoints": [8899], "characters": "\u22C3" },
+ "⨀": { "codepoints": [10752], "characters": "\u2A00" },
+ "⨁": { "codepoints": [10753], "characters": "\u2A01" },
+ "⨂": { "codepoints": [10754], "characters": "\u2A02" },
+ "⨆": { "codepoints": [10758], "characters": "\u2A06" },
+ "★": { "codepoints": [9733], "characters": "\u2605" },
+ "▽": { "codepoints": [9661], "characters": "\u25BD" },
+ "△": { "codepoints": [9651], "characters": "\u25B3" },
+ "⨄": { "codepoints": [10756], "characters": "\u2A04" },
+ "⋁": { "codepoints": [8897], "characters": "\u22C1" },
+ "⋀": { "codepoints": [8896], "characters": "\u22C0" },
+ "⤍": { "codepoints": [10509], "characters": "\u290D" },
+ "⧫": { "codepoints": [10731], "characters": "\u29EB" },
+ "▪": { "codepoints": [9642], "characters": "\u25AA" },
+ "▴": { "codepoints": [9652], "characters": "\u25B4" },
+ "▾": { "codepoints": [9662], "characters": "\u25BE" },
+ "◂": { "codepoints": [9666], "characters": "\u25C2" },
+ "▸": { "codepoints": [9656], "characters": "\u25B8" },
+ "␣": { "codepoints": [9251], "characters": "\u2423" },
+ "▒": { "codepoints": [9618], "characters": "\u2592" },
+ "░": { "codepoints": [9617], "characters": "\u2591" },
+ "▓": { "codepoints": [9619], "characters": "\u2593" },
+ "█": { "codepoints": [9608], "characters": "\u2588" },
+ "=⃥": { "codepoints": [61, 8421], "characters": "\u003D\u20E5" },
+ "≡⃥": { "codepoints": [8801, 8421], "characters": "\u2261\u20E5" },
+ "⌐": { "codepoints": [8976], "characters": "\u2310" },
+ "𝕓": { "codepoints": [120147], "characters": "\uD835\uDD53" },
+ "⊥": { "codepoints": [8869], "characters": "\u22A5" },
+ "⊥": { "codepoints": [8869], "characters": "\u22A5" },
+ "⋈": { "codepoints": [8904], "characters": "\u22C8" },
+ "╗": { "codepoints": [9559], "characters": "\u2557" },
+ "╔": { "codepoints": [9556], "characters": "\u2554" },
+ "╖": { "codepoints": [9558], "characters": "\u2556" },
+ "╓": { "codepoints": [9555], "characters": "\u2553" },
+ "═": { "codepoints": [9552], "characters": "\u2550" },
+ "╦": { "codepoints": [9574], "characters": "\u2566" },
+ "╩": { "codepoints": [9577], "characters": "\u2569" },
+ "╤": { "codepoints": [9572], "characters": "\u2564" },
+ "╧": { "codepoints": [9575], "characters": "\u2567" },
+ "╝": { "codepoints": [9565], "characters": "\u255D" },
+ "╚": { "codepoints": [9562], "characters": "\u255A" },
+ "╜": { "codepoints": [9564], "characters": "\u255C" },
+ "╙": { "codepoints": [9561], "characters": "\u2559" },
+ "║": { "codepoints": [9553], "characters": "\u2551" },
+ "╬": { "codepoints": [9580], "characters": "\u256C" },
+ "╣": { "codepoints": [9571], "characters": "\u2563" },
+ "╠": { "codepoints": [9568], "characters": "\u2560" },
+ "╫": { "codepoints": [9579], "characters": "\u256B" },
+ "╢": { "codepoints": [9570], "characters": "\u2562" },
+ "╟": { "codepoints": [9567], "characters": "\u255F" },
+ "⧉": { "codepoints": [10697], "characters": "\u29C9" },
+ "╕": { "codepoints": [9557], "characters": "\u2555" },
+ "╒": { "codepoints": [9554], "characters": "\u2552" },
+ "┐": { "codepoints": [9488], "characters": "\u2510" },
+ "┌": { "codepoints": [9484], "characters": "\u250C" },
+ "─": { "codepoints": [9472], "characters": "\u2500" },
+ "╥": { "codepoints": [9573], "characters": "\u2565" },
+ "╨": { "codepoints": [9576], "characters": "\u2568" },
+ "┬": { "codepoints": [9516], "characters": "\u252C" },
+ "┴": { "codepoints": [9524], "characters": "\u2534" },
+ "⊟": { "codepoints": [8863], "characters": "\u229F" },
+ "⊞": { "codepoints": [8862], "characters": "\u229E" },
+ "⊠": { "codepoints": [8864], "characters": "\u22A0" },
+ "╛": { "codepoints": [9563], "characters": "\u255B" },
+ "╘": { "codepoints": [9560], "characters": "\u2558" },
+ "┘": { "codepoints": [9496], "characters": "\u2518" },
+ "└": { "codepoints": [9492], "characters": "\u2514" },
+ "│": { "codepoints": [9474], "characters": "\u2502" },
+ "╪": { "codepoints": [9578], "characters": "\u256A" },
+ "╡": { "codepoints": [9569], "characters": "\u2561" },
+ "╞": { "codepoints": [9566], "characters": "\u255E" },
+ "┼": { "codepoints": [9532], "characters": "\u253C" },
+ "┤": { "codepoints": [9508], "characters": "\u2524" },
+ "├": { "codepoints": [9500], "characters": "\u251C" },
+ "‵": { "codepoints": [8245], "characters": "\u2035" },
+ "˘": { "codepoints": [728], "characters": "\u02D8" },
+ "¦": { "codepoints": [166], "characters": "\u00A6" },
+ "¦": { "codepoints": [166], "characters": "\u00A6" },
+ "𝒷": { "codepoints": [119991], "characters": "\uD835\uDCB7" },
+ "⁏": { "codepoints": [8271], "characters": "\u204F" },
+ "∽": { "codepoints": [8765], "characters": "\u223D" },
+ "⋍": { "codepoints": [8909], "characters": "\u22CD" },
+ "\": { "codepoints": [92], "characters": "\u005C" },
+ "⧅": { "codepoints": [10693], "characters": "\u29C5" },
+ "⟈": { "codepoints": [10184], "characters": "\u27C8" },
+ "•": { "codepoints": [8226], "characters": "\u2022" },
+ "•": { "codepoints": [8226], "characters": "\u2022" },
+ "≎": { "codepoints": [8782], "characters": "\u224E" },
+ "⪮": { "codepoints": [10926], "characters": "\u2AAE" },
+ "≏": { "codepoints": [8783], "characters": "\u224F" },
+ "≏": { "codepoints": [8783], "characters": "\u224F" },
+ "ć": { "codepoints": [263], "characters": "\u0107" },
+ "∩": { "codepoints": [8745], "characters": "\u2229" },
+ "⩄": { "codepoints": [10820], "characters": "\u2A44" },
+ "⩉": { "codepoints": [10825], "characters": "\u2A49" },
+ "⩋": { "codepoints": [10827], "characters": "\u2A4B" },
+ "⩇": { "codepoints": [10823], "characters": "\u2A47" },
+ "⩀": { "codepoints": [10816], "characters": "\u2A40" },
+ "∩︀": { "codepoints": [8745, 65024], "characters": "\u2229\uFE00" },
+ "⁁": { "codepoints": [8257], "characters": "\u2041" },
+ "ˇ": { "codepoints": [711], "characters": "\u02C7" },
+ "⩍": { "codepoints": [10829], "characters": "\u2A4D" },
+ "č": { "codepoints": [269], "characters": "\u010D" },
+ "ç": { "codepoints": [231], "characters": "\u00E7" },
+ "ç": { "codepoints": [231], "characters": "\u00E7" },
+ "ĉ": { "codepoints": [265], "characters": "\u0109" },
+ "⩌": { "codepoints": [10828], "characters": "\u2A4C" },
+ "⩐": { "codepoints": [10832], "characters": "\u2A50" },
+ "ċ": { "codepoints": [267], "characters": "\u010B" },
+ "¸": { "codepoints": [184], "characters": "\u00B8" },
+ "¸": { "codepoints": [184], "characters": "\u00B8" },
+ "⦲": { "codepoints": [10674], "characters": "\u29B2" },
+ "¢": { "codepoints": [162], "characters": "\u00A2" },
+ "¢": { "codepoints": [162], "characters": "\u00A2" },
+ "·": { "codepoints": [183], "characters": "\u00B7" },
+ "𝔠": { "codepoints": [120096], "characters": "\uD835\uDD20" },
+ "ч": { "codepoints": [1095], "characters": "\u0447" },
+ "✓": { "codepoints": [10003], "characters": "\u2713" },
+ "✓": { "codepoints": [10003], "characters": "\u2713" },
+ "χ": { "codepoints": [967], "characters": "\u03C7" },
+ "○": { "codepoints": [9675], "characters": "\u25CB" },
+ "⧃": { "codepoints": [10691], "characters": "\u29C3" },
+ "ˆ": { "codepoints": [710], "characters": "\u02C6" },
+ "≗": { "codepoints": [8791], "characters": "\u2257" },
+ "↺": { "codepoints": [8634], "characters": "\u21BA" },
+ "↻": { "codepoints": [8635], "characters": "\u21BB" },
+ "®": { "codepoints": [174], "characters": "\u00AE" },
+ "Ⓢ": { "codepoints": [9416], "characters": "\u24C8" },
+ "⊛": { "codepoints": [8859], "characters": "\u229B" },
+ "⊚": { "codepoints": [8858], "characters": "\u229A" },
+ "⊝": { "codepoints": [8861], "characters": "\u229D" },
+ "≗": { "codepoints": [8791], "characters": "\u2257" },
+ "⨐": { "codepoints": [10768], "characters": "\u2A10" },
+ "⫯": { "codepoints": [10991], "characters": "\u2AEF" },
+ "⧂": { "codepoints": [10690], "characters": "\u29C2" },
+ "♣": { "codepoints": [9827], "characters": "\u2663" },
+ "♣": { "codepoints": [9827], "characters": "\u2663" },
+ ":": { "codepoints": [58], "characters": "\u003A" },
+ "≔": { "codepoints": [8788], "characters": "\u2254" },
+ "≔": { "codepoints": [8788], "characters": "\u2254" },
+ ",": { "codepoints": [44], "characters": "\u002C" },
+ "@": { "codepoints": [64], "characters": "\u0040" },
+ "∁": { "codepoints": [8705], "characters": "\u2201" },
+ "∘": { "codepoints": [8728], "characters": "\u2218" },
+ "∁": { "codepoints": [8705], "characters": "\u2201" },
+ "ℂ": { "codepoints": [8450], "characters": "\u2102" },
+ "≅": { "codepoints": [8773], "characters": "\u2245" },
+ "⩭": { "codepoints": [10861], "characters": "\u2A6D" },
+ "∮": { "codepoints": [8750], "characters": "\u222E" },
+ "𝕔": { "codepoints": [120148], "characters": "\uD835\uDD54" },
+ "∐": { "codepoints": [8720], "characters": "\u2210" },
+ "©": { "codepoints": [169], "characters": "\u00A9" },
+ "©": { "codepoints": [169], "characters": "\u00A9" },
+ "℗": { "codepoints": [8471], "characters": "\u2117" },
+ "↵": { "codepoints": [8629], "characters": "\u21B5" },
+ "✗": { "codepoints": [10007], "characters": "\u2717" },
+ "𝒸": { "codepoints": [119992], "characters": "\uD835\uDCB8" },
+ "⫏": { "codepoints": [10959], "characters": "\u2ACF" },
+ "⫑": { "codepoints": [10961], "characters": "\u2AD1" },
+ "⫐": { "codepoints": [10960], "characters": "\u2AD0" },
+ "⫒": { "codepoints": [10962], "characters": "\u2AD2" },
+ "⋯": { "codepoints": [8943], "characters": "\u22EF" },
+ "⤸": { "codepoints": [10552], "characters": "\u2938" },
+ "⤵": { "codepoints": [10549], "characters": "\u2935" },
+ "⋞": { "codepoints": [8926], "characters": "\u22DE" },
+ "⋟": { "codepoints": [8927], "characters": "\u22DF" },
+ "↶": { "codepoints": [8630], "characters": "\u21B6" },
+ "⤽": { "codepoints": [10557], "characters": "\u293D" },
+ "∪": { "codepoints": [8746], "characters": "\u222A" },
+ "⩈": { "codepoints": [10824], "characters": "\u2A48" },
+ "⩆": { "codepoints": [10822], "characters": "\u2A46" },
+ "⩊": { "codepoints": [10826], "characters": "\u2A4A" },
+ "⊍": { "codepoints": [8845], "characters": "\u228D" },
+ "⩅": { "codepoints": [10821], "characters": "\u2A45" },
+ "∪︀": { "codepoints": [8746, 65024], "characters": "\u222A\uFE00" },
+ "↷": { "codepoints": [8631], "characters": "\u21B7" },
+ "⤼": { "codepoints": [10556], "characters": "\u293C" },
+ "⋞": { "codepoints": [8926], "characters": "\u22DE" },
+ "⋟": { "codepoints": [8927], "characters": "\u22DF" },
+ "⋎": { "codepoints": [8910], "characters": "\u22CE" },
+ "⋏": { "codepoints": [8911], "characters": "\u22CF" },
+ "¤": { "codepoints": [164], "characters": "\u00A4" },
+ "¤": { "codepoints": [164], "characters": "\u00A4" },
+ "↶": { "codepoints": [8630], "characters": "\u21B6" },
+ "↷": { "codepoints": [8631], "characters": "\u21B7" },
+ "⋎": { "codepoints": [8910], "characters": "\u22CE" },
+ "⋏": { "codepoints": [8911], "characters": "\u22CF" },
+ "∲": { "codepoints": [8754], "characters": "\u2232" },
+ "∱": { "codepoints": [8753], "characters": "\u2231" },
+ "⌭": { "codepoints": [9005], "characters": "\u232D" },
+ "⇓": { "codepoints": [8659], "characters": "\u21D3" },
+ "⥥": { "codepoints": [10597], "characters": "\u2965" },
+ "†": { "codepoints": [8224], "characters": "\u2020" },
+ "ℸ": { "codepoints": [8504], "characters": "\u2138" },
+ "↓": { "codepoints": [8595], "characters": "\u2193" },
+ "‐": { "codepoints": [8208], "characters": "\u2010" },
+ "⊣": { "codepoints": [8867], "characters": "\u22A3" },
+ "⤏": { "codepoints": [10511], "characters": "\u290F" },
+ "˝": { "codepoints": [733], "characters": "\u02DD" },
+ "ď": { "codepoints": [271], "characters": "\u010F" },
+ "д": { "codepoints": [1076], "characters": "\u0434" },
+ "ⅆ": { "codepoints": [8518], "characters": "\u2146" },
+ "‡": { "codepoints": [8225], "characters": "\u2021" },
+ "⇊": { "codepoints": [8650], "characters": "\u21CA" },
+ "⩷": { "codepoints": [10871], "characters": "\u2A77" },
+ "°": { "codepoints": [176], "characters": "\u00B0" },
+ "°": { "codepoints": [176], "characters": "\u00B0" },
+ "δ": { "codepoints": [948], "characters": "\u03B4" },
+ "⦱": { "codepoints": [10673], "characters": "\u29B1" },
+ "⥿": { "codepoints": [10623], "characters": "\u297F" },
+ "𝔡": { "codepoints": [120097], "characters": "\uD835\uDD21" },
+ "⇃": { "codepoints": [8643], "characters": "\u21C3" },
+ "⇂": { "codepoints": [8642], "characters": "\u21C2" },
+ "⋄": { "codepoints": [8900], "characters": "\u22C4" },
+ "⋄": { "codepoints": [8900], "characters": "\u22C4" },
+ "♦": { "codepoints": [9830], "characters": "\u2666" },
+ "♦": { "codepoints": [9830], "characters": "\u2666" },
+ "¨": { "codepoints": [168], "characters": "\u00A8" },
+ "ϝ": { "codepoints": [989], "characters": "\u03DD" },
+ "⋲": { "codepoints": [8946], "characters": "\u22F2" },
+ "÷": { "codepoints": [247], "characters": "\u00F7" },
+ "÷": { "codepoints": [247], "characters": "\u00F7" },
+ "÷": { "codepoints": [247], "characters": "\u00F7" },
+ "⋇": { "codepoints": [8903], "characters": "\u22C7" },
+ "⋇": { "codepoints": [8903], "characters": "\u22C7" },
+ "ђ": { "codepoints": [1106], "characters": "\u0452" },
+ "⌞": { "codepoints": [8990], "characters": "\u231E" },
+ "⌍": { "codepoints": [8973], "characters": "\u230D" },
+ "$": { "codepoints": [36], "characters": "\u0024" },
+ "𝕕": { "codepoints": [120149], "characters": "\uD835\uDD55" },
+ "˙": { "codepoints": [729], "characters": "\u02D9" },
+ "≐": { "codepoints": [8784], "characters": "\u2250" },
+ "≑": { "codepoints": [8785], "characters": "\u2251" },
+ "∸": { "codepoints": [8760], "characters": "\u2238" },
+ "∔": { "codepoints": [8724], "characters": "\u2214" },
+ "⊡": { "codepoints": [8865], "characters": "\u22A1" },
+ "⌆": { "codepoints": [8966], "characters": "\u2306" },
+ "↓": { "codepoints": [8595], "characters": "\u2193" },
+ "⇊": { "codepoints": [8650], "characters": "\u21CA" },
+ "⇃": { "codepoints": [8643], "characters": "\u21C3" },
+ "⇂": { "codepoints": [8642], "characters": "\u21C2" },
+ "⤐": { "codepoints": [10512], "characters": "\u2910" },
+ "⌟": { "codepoints": [8991], "characters": "\u231F" },
+ "⌌": { "codepoints": [8972], "characters": "\u230C" },
+ "𝒹": { "codepoints": [119993], "characters": "\uD835\uDCB9" },
+ "ѕ": { "codepoints": [1109], "characters": "\u0455" },
+ "⧶": { "codepoints": [10742], "characters": "\u29F6" },
+ "đ": { "codepoints": [273], "characters": "\u0111" },
+ "⋱": { "codepoints": [8945], "characters": "\u22F1" },
+ "▿": { "codepoints": [9663], "characters": "\u25BF" },
+ "▾": { "codepoints": [9662], "characters": "\u25BE" },
+ "⇵": { "codepoints": [8693], "characters": "\u21F5" },
+ "⥯": { "codepoints": [10607], "characters": "\u296F" },
+ "⦦": { "codepoints": [10662], "characters": "\u29A6" },
+ "џ": { "codepoints": [1119], "characters": "\u045F" },
+ "⟿": { "codepoints": [10239], "characters": "\u27FF" },
+ "⩷": { "codepoints": [10871], "characters": "\u2A77" },
+ "≑": { "codepoints": [8785], "characters": "\u2251" },
+ "é": { "codepoints": [233], "characters": "\u00E9" },
+ "é": { "codepoints": [233], "characters": "\u00E9" },
+ "⩮": { "codepoints": [10862], "characters": "\u2A6E" },
+ "ě": { "codepoints": [283], "characters": "\u011B" },
+ "≖": { "codepoints": [8790], "characters": "\u2256" },
+ "ê": { "codepoints": [234], "characters": "\u00EA" },
+ "ê": { "codepoints": [234], "characters": "\u00EA" },
+ "≕": { "codepoints": [8789], "characters": "\u2255" },
+ "э": { "codepoints": [1101], "characters": "\u044D" },
+ "ė": { "codepoints": [279], "characters": "\u0117" },
+ "ⅇ": { "codepoints": [8519], "characters": "\u2147" },
+ "≒": { "codepoints": [8786], "characters": "\u2252" },
+ "𝔢": { "codepoints": [120098], "characters": "\uD835\uDD22" },
+ "⪚": { "codepoints": [10906], "characters": "\u2A9A" },
+ "è": { "codepoints": [232], "characters": "\u00E8" },
+ "è": { "codepoints": [232], "characters": "\u00E8" },
+ "⪖": { "codepoints": [10902], "characters": "\u2A96" },
+ "⪘": { "codepoints": [10904], "characters": "\u2A98" },
+ "⪙": { "codepoints": [10905], "characters": "\u2A99" },
+ "⏧": { "codepoints": [9191], "characters": "\u23E7" },
+ "ℓ": { "codepoints": [8467], "characters": "\u2113" },
+ "⪕": { "codepoints": [10901], "characters": "\u2A95" },
+ "⪗": { "codepoints": [10903], "characters": "\u2A97" },
+ "ē": { "codepoints": [275], "characters": "\u0113" },
+ "∅": { "codepoints": [8709], "characters": "\u2205" },
+ "∅": { "codepoints": [8709], "characters": "\u2205" },
+ "∅": { "codepoints": [8709], "characters": "\u2205" },
+ " ": { "codepoints": [8196], "characters": "\u2004" },
+ " ": { "codepoints": [8197], "characters": "\u2005" },
+ " ": { "codepoints": [8195], "characters": "\u2003" },
+ "ŋ": { "codepoints": [331], "characters": "\u014B" },
+ " ": { "codepoints": [8194], "characters": "\u2002" },
+ "ę": { "codepoints": [281], "characters": "\u0119" },
+ "𝕖": { "codepoints": [120150], "characters": "\uD835\uDD56" },
+ "⋕": { "codepoints": [8917], "characters": "\u22D5" },
+ "⧣": { "codepoints": [10723], "characters": "\u29E3" },
+ "⩱": { "codepoints": [10865], "characters": "\u2A71" },
+ "ε": { "codepoints": [949], "characters": "\u03B5" },
+ "ε": { "codepoints": [949], "characters": "\u03B5" },
+ "ϵ": { "codepoints": [1013], "characters": "\u03F5" },
+ "≖": { "codepoints": [8790], "characters": "\u2256" },
+ "≕": { "codepoints": [8789], "characters": "\u2255" },
+ "≂": { "codepoints": [8770], "characters": "\u2242" },
+ "⪖": { "codepoints": [10902], "characters": "\u2A96" },
+ "⪕": { "codepoints": [10901], "characters": "\u2A95" },
+ "=": { "codepoints": [61], "characters": "\u003D" },
+ "≟": { "codepoints": [8799], "characters": "\u225F" },
+ "≡": { "codepoints": [8801], "characters": "\u2261" },
+ "⩸": { "codepoints": [10872], "characters": "\u2A78" },
+ "⧥": { "codepoints": [10725], "characters": "\u29E5" },
+ "≓": { "codepoints": [8787], "characters": "\u2253" },
+ "⥱": { "codepoints": [10609], "characters": "\u2971" },
+ "ℯ": { "codepoints": [8495], "characters": "\u212F" },
+ "≐": { "codepoints": [8784], "characters": "\u2250" },
+ "≂": { "codepoints": [8770], "characters": "\u2242" },
+ "η": { "codepoints": [951], "characters": "\u03B7" },
+ "ð": { "codepoints": [240], "characters": "\u00F0" },
+ "ð": { "codepoints": [240], "characters": "\u00F0" },
+ "ë": { "codepoints": [235], "characters": "\u00EB" },
+ "ë": { "codepoints": [235], "characters": "\u00EB" },
+ "€": { "codepoints": [8364], "characters": "\u20AC" },
+ "!": { "codepoints": [33], "characters": "\u0021" },
+ "∃": { "codepoints": [8707], "characters": "\u2203" },
+ "ℰ": { "codepoints": [8496], "characters": "\u2130" },
+ "ⅇ": { "codepoints": [8519], "characters": "\u2147" },
+ "≒": { "codepoints": [8786], "characters": "\u2252" },
+ "ф": { "codepoints": [1092], "characters": "\u0444" },
+ "♀": { "codepoints": [9792], "characters": "\u2640" },
+ "ffi": { "codepoints": [64259], "characters": "\uFB03" },
+ "ff": { "codepoints": [64256], "characters": "\uFB00" },
+ "ffl": { "codepoints": [64260], "characters": "\uFB04" },
+ "𝔣": { "codepoints": [120099], "characters": "\uD835\uDD23" },
+ "fi": { "codepoints": [64257], "characters": "\uFB01" },
+ "fj": { "codepoints": [102, 106], "characters": "\u0066\u006A" },
+ "♭": { "codepoints": [9837], "characters": "\u266D" },
+ "fl": { "codepoints": [64258], "characters": "\uFB02" },
+ "▱": { "codepoints": [9649], "characters": "\u25B1" },
+ "ƒ": { "codepoints": [402], "characters": "\u0192" },
+ "𝕗": { "codepoints": [120151], "characters": "\uD835\uDD57" },
+ "∀": { "codepoints": [8704], "characters": "\u2200" },
+ "⋔": { "codepoints": [8916], "characters": "\u22D4" },
+ "⫙": { "codepoints": [10969], "characters": "\u2AD9" },
+ "⨍": { "codepoints": [10765], "characters": "\u2A0D" },
+ "½": { "codepoints": [189], "characters": "\u00BD" },
+ "½": { "codepoints": [189], "characters": "\u00BD" },
+ "⅓": { "codepoints": [8531], "characters": "\u2153" },
+ "¼": { "codepoints": [188], "characters": "\u00BC" },
+ "¼": { "codepoints": [188], "characters": "\u00BC" },
+ "⅕": { "codepoints": [8533], "characters": "\u2155" },
+ "⅙": { "codepoints": [8537], "characters": "\u2159" },
+ "⅛": { "codepoints": [8539], "characters": "\u215B" },
+ "⅔": { "codepoints": [8532], "characters": "\u2154" },
+ "⅖": { "codepoints": [8534], "characters": "\u2156" },
+ "¾": { "codepoints": [190], "characters": "\u00BE" },
+ "¾": { "codepoints": [190], "characters": "\u00BE" },
+ "⅗": { "codepoints": [8535], "characters": "\u2157" },
+ "⅜": { "codepoints": [8540], "characters": "\u215C" },
+ "⅘": { "codepoints": [8536], "characters": "\u2158" },
+ "⅚": { "codepoints": [8538], "characters": "\u215A" },
+ "⅝": { "codepoints": [8541], "characters": "\u215D" },
+ "⅞": { "codepoints": [8542], "characters": "\u215E" },
+ "⁄": { "codepoints": [8260], "characters": "\u2044" },
+ "⌢": { "codepoints": [8994], "characters": "\u2322" },
+ "𝒻": { "codepoints": [119995], "characters": "\uD835\uDCBB" },
+ "≧": { "codepoints": [8807], "characters": "\u2267" },
+ "⪌": { "codepoints": [10892], "characters": "\u2A8C" },
+ "ǵ": { "codepoints": [501], "characters": "\u01F5" },
+ "γ": { "codepoints": [947], "characters": "\u03B3" },
+ "ϝ": { "codepoints": [989], "characters": "\u03DD" },
+ "⪆": { "codepoints": [10886], "characters": "\u2A86" },
+ "ğ": { "codepoints": [287], "characters": "\u011F" },
+ "ĝ": { "codepoints": [285], "characters": "\u011D" },
+ "г": { "codepoints": [1075], "characters": "\u0433" },
+ "ġ": { "codepoints": [289], "characters": "\u0121" },
+ "≥": { "codepoints": [8805], "characters": "\u2265" },
+ "⋛": { "codepoints": [8923], "characters": "\u22DB" },
+ "≥": { "codepoints": [8805], "characters": "\u2265" },
+ "≧": { "codepoints": [8807], "characters": "\u2267" },
+ "⩾": { "codepoints": [10878], "characters": "\u2A7E" },
+ "⩾": { "codepoints": [10878], "characters": "\u2A7E" },
+ "⪩": { "codepoints": [10921], "characters": "\u2AA9" },
+ "⪀": { "codepoints": [10880], "characters": "\u2A80" },
+ "⪂": { "codepoints": [10882], "characters": "\u2A82" },
+ "⪄": { "codepoints": [10884], "characters": "\u2A84" },
+ "⋛︀": { "codepoints": [8923, 65024], "characters": "\u22DB\uFE00" },
+ "⪔": { "codepoints": [10900], "characters": "\u2A94" },
+ "𝔤": { "codepoints": [120100], "characters": "\uD835\uDD24" },
+ "≫": { "codepoints": [8811], "characters": "\u226B" },
+ "⋙": { "codepoints": [8921], "characters": "\u22D9" },
+ "ℷ": { "codepoints": [8503], "characters": "\u2137" },
+ "ѓ": { "codepoints": [1107], "characters": "\u0453" },
+ "≷": { "codepoints": [8823], "characters": "\u2277" },
+ "⪒": { "codepoints": [10898], "characters": "\u2A92" },
+ "⪥": { "codepoints": [10917], "characters": "\u2AA5" },
+ "⪤": { "codepoints": [10916], "characters": "\u2AA4" },
+ "≩": { "codepoints": [8809], "characters": "\u2269" },
+ "⪊": { "codepoints": [10890], "characters": "\u2A8A" },
+ "⪊": { "codepoints": [10890], "characters": "\u2A8A" },
+ "⪈": { "codepoints": [10888], "characters": "\u2A88" },
+ "⪈": { "codepoints": [10888], "characters": "\u2A88" },
+ "≩": { "codepoints": [8809], "characters": "\u2269" },
+ "⋧": { "codepoints": [8935], "characters": "\u22E7" },
+ "𝕘": { "codepoints": [120152], "characters": "\uD835\uDD58" },
+ "`": { "codepoints": [96], "characters": "\u0060" },
+ "ℊ": { "codepoints": [8458], "characters": "\u210A" },
+ "≳": { "codepoints": [8819], "characters": "\u2273" },
+ "⪎": { "codepoints": [10894], "characters": "\u2A8E" },
+ "⪐": { "codepoints": [10896], "characters": "\u2A90" },
+ ">": { "codepoints": [62], "characters": "\u003E" },
+ ">": { "codepoints": [62], "characters": "\u003E" },
+ "⪧": { "codepoints": [10919], "characters": "\u2AA7" },
+ "⩺": { "codepoints": [10874], "characters": "\u2A7A" },
+ "⋗": { "codepoints": [8919], "characters": "\u22D7" },
+ "⦕": { "codepoints": [10645], "characters": "\u2995" },
+ "⩼": { "codepoints": [10876], "characters": "\u2A7C" },
+ "⪆": { "codepoints": [10886], "characters": "\u2A86" },
+ "⥸": { "codepoints": [10616], "characters": "\u2978" },
+ "⋗": { "codepoints": [8919], "characters": "\u22D7" },
+ "⋛": { "codepoints": [8923], "characters": "\u22DB" },
+ "⪌": { "codepoints": [10892], "characters": "\u2A8C" },
+ "≷": { "codepoints": [8823], "characters": "\u2277" },
+ "≳": { "codepoints": [8819], "characters": "\u2273" },
+ "≩︀": { "codepoints": [8809, 65024], "characters": "\u2269\uFE00" },
+ "≩︀": { "codepoints": [8809, 65024], "characters": "\u2269\uFE00" },
+ "⇔": { "codepoints": [8660], "characters": "\u21D4" },
+ " ": { "codepoints": [8202], "characters": "\u200A" },
+ "½": { "codepoints": [189], "characters": "\u00BD" },
+ "ℋ": { "codepoints": [8459], "characters": "\u210B" },
+ "ъ": { "codepoints": [1098], "characters": "\u044A" },
+ "↔": { "codepoints": [8596], "characters": "\u2194" },
+ "⥈": { "codepoints": [10568], "characters": "\u2948" },
+ "↭": { "codepoints": [8621], "characters": "\u21AD" },
+ "ℏ": { "codepoints": [8463], "characters": "\u210F" },
+ "ĥ": { "codepoints": [293], "characters": "\u0125" },
+ "♥": { "codepoints": [9829], "characters": "\u2665" },
+ "♥": { "codepoints": [9829], "characters": "\u2665" },
+ "…": { "codepoints": [8230], "characters": "\u2026" },
+ "⊹": { "codepoints": [8889], "characters": "\u22B9" },
+ "𝔥": { "codepoints": [120101], "characters": "\uD835\uDD25" },
+ "⤥": { "codepoints": [10533], "characters": "\u2925" },
+ "⤦": { "codepoints": [10534], "characters": "\u2926" },
+ "⇿": { "codepoints": [8703], "characters": "\u21FF" },
+ "∻": { "codepoints": [8763], "characters": "\u223B" },
+ "↩": { "codepoints": [8617], "characters": "\u21A9" },
+ "↪": { "codepoints": [8618], "characters": "\u21AA" },
+ "𝕙": { "codepoints": [120153], "characters": "\uD835\uDD59" },
+ "―": { "codepoints": [8213], "characters": "\u2015" },
+ "𝒽": { "codepoints": [119997], "characters": "\uD835\uDCBD" },
+ "ℏ": { "codepoints": [8463], "characters": "\u210F" },
+ "ħ": { "codepoints": [295], "characters": "\u0127" },
+ "⁃": { "codepoints": [8259], "characters": "\u2043" },
+ "‐": { "codepoints": [8208], "characters": "\u2010" },
+ "í": { "codepoints": [237], "characters": "\u00ED" },
+ "í": { "codepoints": [237], "characters": "\u00ED" },
+ "⁣": { "codepoints": [8291], "characters": "\u2063" },
+ "î": { "codepoints": [238], "characters": "\u00EE" },
+ "î": { "codepoints": [238], "characters": "\u00EE" },
+ "и": { "codepoints": [1080], "characters": "\u0438" },
+ "е": { "codepoints": [1077], "characters": "\u0435" },
+ "¡": { "codepoints": [161], "characters": "\u00A1" },
+ "¡": { "codepoints": [161], "characters": "\u00A1" },
+ "⇔": { "codepoints": [8660], "characters": "\u21D4" },
+ "𝔦": { "codepoints": [120102], "characters": "\uD835\uDD26" },
+ "ì": { "codepoints": [236], "characters": "\u00EC" },
+ "ì": { "codepoints": [236], "characters": "\u00EC" },
+ "ⅈ": { "codepoints": [8520], "characters": "\u2148" },
+ "⨌": { "codepoints": [10764], "characters": "\u2A0C" },
+ "∭": { "codepoints": [8749], "characters": "\u222D" },
+ "⧜": { "codepoints": [10716], "characters": "\u29DC" },
+ "℩": { "codepoints": [8489], "characters": "\u2129" },
+ "ij": { "codepoints": [307], "characters": "\u0133" },
+ "ī": { "codepoints": [299], "characters": "\u012B" },
+ "ℑ": { "codepoints": [8465], "characters": "\u2111" },
+ "ℐ": { "codepoints": [8464], "characters": "\u2110" },
+ "ℑ": { "codepoints": [8465], "characters": "\u2111" },
+ "ı": { "codepoints": [305], "characters": "\u0131" },
+ "⊷": { "codepoints": [8887], "characters": "\u22B7" },
+ "Ƶ": { "codepoints": [437], "characters": "\u01B5" },
+ "∈": { "codepoints": [8712], "characters": "\u2208" },
+ "℅": { "codepoints": [8453], "characters": "\u2105" },
+ "∞": { "codepoints": [8734], "characters": "\u221E" },
+ "⧝": { "codepoints": [10717], "characters": "\u29DD" },
+ "ı": { "codepoints": [305], "characters": "\u0131" },
+ "∫": { "codepoints": [8747], "characters": "\u222B" },
+ "⊺": { "codepoints": [8890], "characters": "\u22BA" },
+ "ℤ": { "codepoints": [8484], "characters": "\u2124" },
+ "⊺": { "codepoints": [8890], "characters": "\u22BA" },
+ "⨗": { "codepoints": [10775], "characters": "\u2A17" },
+ "⨼": { "codepoints": [10812], "characters": "\u2A3C" },
+ "ё": { "codepoints": [1105], "characters": "\u0451" },
+ "į": { "codepoints": [303], "characters": "\u012F" },
+ "𝕚": { "codepoints": [120154], "characters": "\uD835\uDD5A" },
+ "ι": { "codepoints": [953], "characters": "\u03B9" },
+ "⨼": { "codepoints": [10812], "characters": "\u2A3C" },
+ "¿": { "codepoints": [191], "characters": "\u00BF" },
+ "¿": { "codepoints": [191], "characters": "\u00BF" },
+ "𝒾": { "codepoints": [119998], "characters": "\uD835\uDCBE" },
+ "∈": { "codepoints": [8712], "characters": "\u2208" },
+ "⋹": { "codepoints": [8953], "characters": "\u22F9" },
+ "⋵": { "codepoints": [8949], "characters": "\u22F5" },
+ "⋴": { "codepoints": [8948], "characters": "\u22F4" },
+ "⋳": { "codepoints": [8947], "characters": "\u22F3" },
+ "∈": { "codepoints": [8712], "characters": "\u2208" },
+ "⁢": { "codepoints": [8290], "characters": "\u2062" },
+ "ĩ": { "codepoints": [297], "characters": "\u0129" },
+ "і": { "codepoints": [1110], "characters": "\u0456" },
+ "ï": { "codepoints": [239], "characters": "\u00EF" },
+ "ï": { "codepoints": [239], "characters": "\u00EF" },
+ "ĵ": { "codepoints": [309], "characters": "\u0135" },
+ "й": { "codepoints": [1081], "characters": "\u0439" },
+ "𝔧": { "codepoints": [120103], "characters": "\uD835\uDD27" },
+ "ȷ": { "codepoints": [567], "characters": "\u0237" },
+ "𝕛": { "codepoints": [120155], "characters": "\uD835\uDD5B" },
+ "𝒿": { "codepoints": [119999], "characters": "\uD835\uDCBF" },
+ "ј": { "codepoints": [1112], "characters": "\u0458" },
+ "є": { "codepoints": [1108], "characters": "\u0454" },
+ "κ": { "codepoints": [954], "characters": "\u03BA" },
+ "ϰ": { "codepoints": [1008], "characters": "\u03F0" },
+ "ķ": { "codepoints": [311], "characters": "\u0137" },
+ "к": { "codepoints": [1082], "characters": "\u043A" },
+ "𝔨": { "codepoints": [120104], "characters": "\uD835\uDD28" },
+ "ĸ": { "codepoints": [312], "characters": "\u0138" },
+ "х": { "codepoints": [1093], "characters": "\u0445" },
+ "ќ": { "codepoints": [1116], "characters": "\u045C" },
+ "𝕜": { "codepoints": [120156], "characters": "\uD835\uDD5C" },
+ "𝓀": { "codepoints": [120000], "characters": "\uD835\uDCC0" },
+ "⇚": { "codepoints": [8666], "characters": "\u21DA" },
+ "⇐": { "codepoints": [8656], "characters": "\u21D0" },
+ "⤛": { "codepoints": [10523], "characters": "\u291B" },
+ "⤎": { "codepoints": [10510], "characters": "\u290E" },
+ "≦": { "codepoints": [8806], "characters": "\u2266" },
+ "⪋": { "codepoints": [10891], "characters": "\u2A8B" },
+ "⥢": { "codepoints": [10594], "characters": "\u2962" },
+ "ĺ": { "codepoints": [314], "characters": "\u013A" },
+ "⦴": { "codepoints": [10676], "characters": "\u29B4" },
+ "ℒ": { "codepoints": [8466], "characters": "\u2112" },
+ "λ": { "codepoints": [955], "characters": "\u03BB" },
+ "⟨": { "codepoints": [10216], "characters": "\u27E8" },
+ "⦑": { "codepoints": [10641], "characters": "\u2991" },
+ "⟨": { "codepoints": [10216], "characters": "\u27E8" },
+ "⪅": { "codepoints": [10885], "characters": "\u2A85" },
+ "«": { "codepoints": [171], "characters": "\u00AB" },
+ "«": { "codepoints": [171], "characters": "\u00AB" },
+ "←": { "codepoints": [8592], "characters": "\u2190" },
+ "⇤": { "codepoints": [8676], "characters": "\u21E4" },
+ "⤟": { "codepoints": [10527], "characters": "\u291F" },
+ "⤝": { "codepoints": [10525], "characters": "\u291D" },
+ "↩": { "codepoints": [8617], "characters": "\u21A9" },
+ "↫": { "codepoints": [8619], "characters": "\u21AB" },
+ "⤹": { "codepoints": [10553], "characters": "\u2939" },
+ "⥳": { "codepoints": [10611], "characters": "\u2973" },
+ "↢": { "codepoints": [8610], "characters": "\u21A2" },
+ "⪫": { "codepoints": [10923], "characters": "\u2AAB" },
+ "⤙": { "codepoints": [10521], "characters": "\u2919" },
+ "⪭": { "codepoints": [10925], "characters": "\u2AAD" },
+ "⪭︀": { "codepoints": [10925, 65024], "characters": "\u2AAD\uFE00" },
+ "⤌": { "codepoints": [10508], "characters": "\u290C" },
+ "❲": { "codepoints": [10098], "characters": "\u2772" },
+ "{": { "codepoints": [123], "characters": "\u007B" },
+ "[": { "codepoints": [91], "characters": "\u005B" },
+ "⦋": { "codepoints": [10635], "characters": "\u298B" },
+ "⦏": { "codepoints": [10639], "characters": "\u298F" },
+ "⦍": { "codepoints": [10637], "characters": "\u298D" },
+ "ľ": { "codepoints": [318], "characters": "\u013E" },
+ "ļ": { "codepoints": [316], "characters": "\u013C" },
+ "⌈": { "codepoints": [8968], "characters": "\u2308" },
+ "{": { "codepoints": [123], "characters": "\u007B" },
+ "л": { "codepoints": [1083], "characters": "\u043B" },
+ "⤶": { "codepoints": [10550], "characters": "\u2936" },
+ "“": { "codepoints": [8220], "characters": "\u201C" },
+ "„": { "codepoints": [8222], "characters": "\u201E" },
+ "⥧": { "codepoints": [10599], "characters": "\u2967" },
+ "⥋": { "codepoints": [10571], "characters": "\u294B" },
+ "↲": { "codepoints": [8626], "characters": "\u21B2" },
+ "≤": { "codepoints": [8804], "characters": "\u2264" },
+ "←": { "codepoints": [8592], "characters": "\u2190" },
+ "↢": { "codepoints": [8610], "characters": "\u21A2" },
+ "↽": { "codepoints": [8637], "characters": "\u21BD" },
+ "↼": { "codepoints": [8636], "characters": "\u21BC" },
+ "⇇": { "codepoints": [8647], "characters": "\u21C7" },
+ "↔": { "codepoints": [8596], "characters": "\u2194" },
+ "⇆": { "codepoints": [8646], "characters": "\u21C6" },
+ "⇋": { "codepoints": [8651], "characters": "\u21CB" },
+ "↭": { "codepoints": [8621], "characters": "\u21AD" },
+ "⋋": { "codepoints": [8907], "characters": "\u22CB" },
+ "⋚": { "codepoints": [8922], "characters": "\u22DA" },
+ "≤": { "codepoints": [8804], "characters": "\u2264" },
+ "≦": { "codepoints": [8806], "characters": "\u2266" },
+ "⩽": { "codepoints": [10877], "characters": "\u2A7D" },
+ "⩽": { "codepoints": [10877], "characters": "\u2A7D" },
+ "⪨": { "codepoints": [10920], "characters": "\u2AA8" },
+ "⩿": { "codepoints": [10879], "characters": "\u2A7F" },
+ "⪁": { "codepoints": [10881], "characters": "\u2A81" },
+ "⪃": { "codepoints": [10883], "characters": "\u2A83" },
+ "⋚︀": { "codepoints": [8922, 65024], "characters": "\u22DA\uFE00" },
+ "⪓": { "codepoints": [10899], "characters": "\u2A93" },
+ "⪅": { "codepoints": [10885], "characters": "\u2A85" },
+ "⋖": { "codepoints": [8918], "characters": "\u22D6" },
+ "⋚": { "codepoints": [8922], "characters": "\u22DA" },
+ "⪋": { "codepoints": [10891], "characters": "\u2A8B" },
+ "≶": { "codepoints": [8822], "characters": "\u2276" },
+ "≲": { "codepoints": [8818], "characters": "\u2272" },
+ "⥼": { "codepoints": [10620], "characters": "\u297C" },
+ "⌊": { "codepoints": [8970], "characters": "\u230A" },
+ "𝔩": { "codepoints": [120105], "characters": "\uD835\uDD29" },
+ "≶": { "codepoints": [8822], "characters": "\u2276" },
+ "⪑": { "codepoints": [10897], "characters": "\u2A91" },
+ "↽": { "codepoints": [8637], "characters": "\u21BD" },
+ "↼": { "codepoints": [8636], "characters": "\u21BC" },
+ "⥪": { "codepoints": [10602], "characters": "\u296A" },
+ "▄": { "codepoints": [9604], "characters": "\u2584" },
+ "љ": { "codepoints": [1113], "characters": "\u0459" },
+ "≪": { "codepoints": [8810], "characters": "\u226A" },
+ "⇇": { "codepoints": [8647], "characters": "\u21C7" },
+ "⌞": { "codepoints": [8990], "characters": "\u231E" },
+ "⥫": { "codepoints": [10603], "characters": "\u296B" },
+ "◺": { "codepoints": [9722], "characters": "\u25FA" },
+ "ŀ": { "codepoints": [320], "characters": "\u0140" },
+ "⎰": { "codepoints": [9136], "characters": "\u23B0" },
+ "⎰": { "codepoints": [9136], "characters": "\u23B0" },
+ "≨": { "codepoints": [8808], "characters": "\u2268" },
+ "⪉": { "codepoints": [10889], "characters": "\u2A89" },
+ "⪉": { "codepoints": [10889], "characters": "\u2A89" },
+ "⪇": { "codepoints": [10887], "characters": "\u2A87" },
+ "⪇": { "codepoints": [10887], "characters": "\u2A87" },
+ "≨": { "codepoints": [8808], "characters": "\u2268" },
+ "⋦": { "codepoints": [8934], "characters": "\u22E6" },
+ "⟬": { "codepoints": [10220], "characters": "\u27EC" },
+ "⇽": { "codepoints": [8701], "characters": "\u21FD" },
+ "⟦": { "codepoints": [10214], "characters": "\u27E6" },
+ "⟵": { "codepoints": [10229], "characters": "\u27F5" },
+ "⟷": { "codepoints": [10231], "characters": "\u27F7" },
+ "⟼": { "codepoints": [10236], "characters": "\u27FC" },
+ "⟶": { "codepoints": [10230], "characters": "\u27F6" },
+ "↫": { "codepoints": [8619], "characters": "\u21AB" },
+ "↬": { "codepoints": [8620], "characters": "\u21AC" },
+ "⦅": { "codepoints": [10629], "characters": "\u2985" },
+ "𝕝": { "codepoints": [120157], "characters": "\uD835\uDD5D" },
+ "⨭": { "codepoints": [10797], "characters": "\u2A2D" },
+ "⨴": { "codepoints": [10804], "characters": "\u2A34" },
+ "∗": { "codepoints": [8727], "characters": "\u2217" },
+ "_": { "codepoints": [95], "characters": "\u005F" },
+ "◊": { "codepoints": [9674], "characters": "\u25CA" },
+ "◊": { "codepoints": [9674], "characters": "\u25CA" },
+ "⧫": { "codepoints": [10731], "characters": "\u29EB" },
+ "(": { "codepoints": [40], "characters": "\u0028" },
+ "⦓": { "codepoints": [10643], "characters": "\u2993" },
+ "⇆": { "codepoints": [8646], "characters": "\u21C6" },
+ "⌟": { "codepoints": [8991], "characters": "\u231F" },
+ "⇋": { "codepoints": [8651], "characters": "\u21CB" },
+ "⥭": { "codepoints": [10605], "characters": "\u296D" },
+ "‎": { "codepoints": [8206], "characters": "\u200E" },
+ "⊿": { "codepoints": [8895], "characters": "\u22BF" },
+ "‹": { "codepoints": [8249], "characters": "\u2039" },
+ "𝓁": { "codepoints": [120001], "characters": "\uD835\uDCC1" },
+ "↰": { "codepoints": [8624], "characters": "\u21B0" },
+ "≲": { "codepoints": [8818], "characters": "\u2272" },
+ "⪍": { "codepoints": [10893], "characters": "\u2A8D" },
+ "⪏": { "codepoints": [10895], "characters": "\u2A8F" },
+ "[": { "codepoints": [91], "characters": "\u005B" },
+ "‘": { "codepoints": [8216], "characters": "\u2018" },
+ "‚": { "codepoints": [8218], "characters": "\u201A" },
+ "ł": { "codepoints": [322], "characters": "\u0142" },
+ "<": { "codepoints": [60], "characters": "\u003C" },
+ "<": { "codepoints": [60], "characters": "\u003C" },
+ "⪦": { "codepoints": [10918], "characters": "\u2AA6" },
+ "⩹": { "codepoints": [10873], "characters": "\u2A79" },
+ "⋖": { "codepoints": [8918], "characters": "\u22D6" },
+ "⋋": { "codepoints": [8907], "characters": "\u22CB" },
+ "⋉": { "codepoints": [8905], "characters": "\u22C9" },
+ "⥶": { "codepoints": [10614], "characters": "\u2976" },
+ "⩻": { "codepoints": [10875], "characters": "\u2A7B" },
+ "⦖": { "codepoints": [10646], "characters": "\u2996" },
+ "◃": { "codepoints": [9667], "characters": "\u25C3" },
+ "⊴": { "codepoints": [8884], "characters": "\u22B4" },
+ "◂": { "codepoints": [9666], "characters": "\u25C2" },
+ "⥊": { "codepoints": [10570], "characters": "\u294A" },
+ "⥦": { "codepoints": [10598], "characters": "\u2966" },
+ "≨︀": { "codepoints": [8808, 65024], "characters": "\u2268\uFE00" },
+ "≨︀": { "codepoints": [8808, 65024], "characters": "\u2268\uFE00" },
+ "∺": { "codepoints": [8762], "characters": "\u223A" },
+ "¯": { "codepoints": [175], "characters": "\u00AF" },
+ "¯": { "codepoints": [175], "characters": "\u00AF" },
+ "♂": { "codepoints": [9794], "characters": "\u2642" },
+ "✠": { "codepoints": [10016], "characters": "\u2720" },
+ "✠": { "codepoints": [10016], "characters": "\u2720" },
+ "↦": { "codepoints": [8614], "characters": "\u21A6" },
+ "↦": { "codepoints": [8614], "characters": "\u21A6" },
+ "↧": { "codepoints": [8615], "characters": "\u21A7" },
+ "↤": { "codepoints": [8612], "characters": "\u21A4" },
+ "↥": { "codepoints": [8613], "characters": "\u21A5" },
+ "▮": { "codepoints": [9646], "characters": "\u25AE" },
+ "⨩": { "codepoints": [10793], "characters": "\u2A29" },
+ "м": { "codepoints": [1084], "characters": "\u043C" },
+ "—": { "codepoints": [8212], "characters": "\u2014" },
+ "∡": { "codepoints": [8737], "characters": "\u2221" },
+ "𝔪": { "codepoints": [120106], "characters": "\uD835\uDD2A" },
+ "℧": { "codepoints": [8487], "characters": "\u2127" },
+ "µ": { "codepoints": [181], "characters": "\u00B5" },
+ "µ": { "codepoints": [181], "characters": "\u00B5" },
+ "∣": { "codepoints": [8739], "characters": "\u2223" },
+ "*": { "codepoints": [42], "characters": "\u002A" },
+ "⫰": { "codepoints": [10992], "characters": "\u2AF0" },
+ "·": { "codepoints": [183], "characters": "\u00B7" },
+ "·": { "codepoints": [183], "characters": "\u00B7" },
+ "−": { "codepoints": [8722], "characters": "\u2212" },
+ "⊟": { "codepoints": [8863], "characters": "\u229F" },
+ "∸": { "codepoints": [8760], "characters": "\u2238" },
+ "⨪": { "codepoints": [10794], "characters": "\u2A2A" },
+ "⫛": { "codepoints": [10971], "characters": "\u2ADB" },
+ "…": { "codepoints": [8230], "characters": "\u2026" },
+ "∓": { "codepoints": [8723], "characters": "\u2213" },
+ "⊧": { "codepoints": [8871], "characters": "\u22A7" },
+ "𝕞": { "codepoints": [120158], "characters": "\uD835\uDD5E" },
+ "∓": { "codepoints": [8723], "characters": "\u2213" },
+ "𝓂": { "codepoints": [120002], "characters": "\uD835\uDCC2" },
+ "∾": { "codepoints": [8766], "characters": "\u223E" },
+ "μ": { "codepoints": [956], "characters": "\u03BC" },
+ "⊸": { "codepoints": [8888], "characters": "\u22B8" },
+ "⊸": { "codepoints": [8888], "characters": "\u22B8" },
+ "⋙̸": { "codepoints": [8921, 824], "characters": "\u22D9\u0338" },
+ "≫⃒": { "codepoints": [8811, 8402], "characters": "\u226B\u20D2" },
+ "≫̸": { "codepoints": [8811, 824], "characters": "\u226B\u0338" },
+ "⇍": { "codepoints": [8653], "characters": "\u21CD" },
+ "⇎": { "codepoints": [8654], "characters": "\u21CE" },
+ "⋘̸": { "codepoints": [8920, 824], "characters": "\u22D8\u0338" },
+ "≪⃒": { "codepoints": [8810, 8402], "characters": "\u226A\u20D2" },
+ "≪̸": { "codepoints": [8810, 824], "characters": "\u226A\u0338" },
+ "⇏": { "codepoints": [8655], "characters": "\u21CF" },
+ "⊯": { "codepoints": [8879], "characters": "\u22AF" },
+ "⊮": { "codepoints": [8878], "characters": "\u22AE" },
+ "∇": { "codepoints": [8711], "characters": "\u2207" },
+ "ń": { "codepoints": [324], "characters": "\u0144" },
+ "∠⃒": { "codepoints": [8736, 8402], "characters": "\u2220\u20D2" },
+ "≉": { "codepoints": [8777], "characters": "\u2249" },
+ "⩰̸": { "codepoints": [10864, 824], "characters": "\u2A70\u0338" },
+ "≋̸": { "codepoints": [8779, 824], "characters": "\u224B\u0338" },
+ "ʼn": { "codepoints": [329], "characters": "\u0149" },
+ "≉": { "codepoints": [8777], "characters": "\u2249" },
+ "♮": { "codepoints": [9838], "characters": "\u266E" },
+ "♮": { "codepoints": [9838], "characters": "\u266E" },
+ "ℕ": { "codepoints": [8469], "characters": "\u2115" },
+ " ": { "codepoints": [160], "characters": "\u00A0" },
+ " ": { "codepoints": [160], "characters": "\u00A0" },
+ "≎̸": { "codepoints": [8782, 824], "characters": "\u224E\u0338" },
+ "≏̸": { "codepoints": [8783, 824], "characters": "\u224F\u0338" },
+ "⩃": { "codepoints": [10819], "characters": "\u2A43" },
+ "ň": { "codepoints": [328], "characters": "\u0148" },
+ "ņ": { "codepoints": [326], "characters": "\u0146" },
+ "≇": { "codepoints": [8775], "characters": "\u2247" },
+ "⩭̸": { "codepoints": [10861, 824], "characters": "\u2A6D\u0338" },
+ "⩂": { "codepoints": [10818], "characters": "\u2A42" },
+ "н": { "codepoints": [1085], "characters": "\u043D" },
+ "–": { "codepoints": [8211], "characters": "\u2013" },
+ "≠": { "codepoints": [8800], "characters": "\u2260" },
+ "⇗": { "codepoints": [8663], "characters": "\u21D7" },
+ "⤤": { "codepoints": [10532], "characters": "\u2924" },
+ "↗": { "codepoints": [8599], "characters": "\u2197" },
+ "↗": { "codepoints": [8599], "characters": "\u2197" },
+ "≐̸": { "codepoints": [8784, 824], "characters": "\u2250\u0338" },
+ "≢": { "codepoints": [8802], "characters": "\u2262" },
+ "⤨": { "codepoints": [10536], "characters": "\u2928" },
+ "≂̸": { "codepoints": [8770, 824], "characters": "\u2242\u0338" },
+ "∄": { "codepoints": [8708], "characters": "\u2204" },
+ "∄": { "codepoints": [8708], "characters": "\u2204" },
+ "𝔫": { "codepoints": [120107], "characters": "\uD835\uDD2B" },
+ "≧̸": { "codepoints": [8807, 824], "characters": "\u2267\u0338" },
+ "≱": { "codepoints": [8817], "characters": "\u2271" },
+ "≱": { "codepoints": [8817], "characters": "\u2271" },
+ "≧̸": { "codepoints": [8807, 824], "characters": "\u2267\u0338" },
+ "⩾̸": { "codepoints": [10878, 824], "characters": "\u2A7E\u0338" },
+ "⩾̸": { "codepoints": [10878, 824], "characters": "\u2A7E\u0338" },
+ "≵": { "codepoints": [8821], "characters": "\u2275" },
+ "≯": { "codepoints": [8815], "characters": "\u226F" },
+ "≯": { "codepoints": [8815], "characters": "\u226F" },
+ "⇎": { "codepoints": [8654], "characters": "\u21CE" },
+ "↮": { "codepoints": [8622], "characters": "\u21AE" },
+ "⫲": { "codepoints": [10994], "characters": "\u2AF2" },
+ "∋": { "codepoints": [8715], "characters": "\u220B" },
+ "⋼": { "codepoints": [8956], "characters": "\u22FC" },
+ "⋺": { "codepoints": [8954], "characters": "\u22FA" },
+ "∋": { "codepoints": [8715], "characters": "\u220B" },
+ "њ": { "codepoints": [1114], "characters": "\u045A" },
+ "⇍": { "codepoints": [8653], "characters": "\u21CD" },
+ "≦̸": { "codepoints": [8806, 824], "characters": "\u2266\u0338" },
+ "↚": { "codepoints": [8602], "characters": "\u219A" },
+ "‥": { "codepoints": [8229], "characters": "\u2025" },
+ "≰": { "codepoints": [8816], "characters": "\u2270" },
+ "↚": { "codepoints": [8602], "characters": "\u219A" },
+ "↮": { "codepoints": [8622], "characters": "\u21AE" },
+ "≰": { "codepoints": [8816], "characters": "\u2270" },
+ "≦̸": { "codepoints": [8806, 824], "characters": "\u2266\u0338" },
+ "⩽̸": { "codepoints": [10877, 824], "characters": "\u2A7D\u0338" },
+ "⩽̸": { "codepoints": [10877, 824], "characters": "\u2A7D\u0338" },
+ "≮": { "codepoints": [8814], "characters": "\u226E" },
+ "≴": { "codepoints": [8820], "characters": "\u2274" },
+ "≮": { "codepoints": [8814], "characters": "\u226E" },
+ "⋪": { "codepoints": [8938], "characters": "\u22EA" },
+ "⋬": { "codepoints": [8940], "characters": "\u22EC" },
+ "∤": { "codepoints": [8740], "characters": "\u2224" },
+ "𝕟": { "codepoints": [120159], "characters": "\uD835\uDD5F" },
+ "¬": { "codepoints": [172], "characters": "\u00AC" },
+ "¬": { "codepoints": [172], "characters": "\u00AC" },
+ "∉": { "codepoints": [8713], "characters": "\u2209" },
+ "⋹̸": { "codepoints": [8953, 824], "characters": "\u22F9\u0338" },
+ "⋵̸": { "codepoints": [8949, 824], "characters": "\u22F5\u0338" },
+ "∉": { "codepoints": [8713], "characters": "\u2209" },
+ "⋷": { "codepoints": [8951], "characters": "\u22F7" },
+ "⋶": { "codepoints": [8950], "characters": "\u22F6" },
+ "∌": { "codepoints": [8716], "characters": "\u220C" },
+ "∌": { "codepoints": [8716], "characters": "\u220C" },
+ "⋾": { "codepoints": [8958], "characters": "\u22FE" },
+ "⋽": { "codepoints": [8957], "characters": "\u22FD" },
+ "∦": { "codepoints": [8742], "characters": "\u2226" },
+ "∦": { "codepoints": [8742], "characters": "\u2226" },
+ "⫽⃥": { "codepoints": [11005, 8421], "characters": "\u2AFD\u20E5" },
+ "∂̸": { "codepoints": [8706, 824], "characters": "\u2202\u0338" },
+ "⨔": { "codepoints": [10772], "characters": "\u2A14" },
+ "⊀": { "codepoints": [8832], "characters": "\u2280" },
+ "⋠": { "codepoints": [8928], "characters": "\u22E0" },
+ "⪯̸": { "codepoints": [10927, 824], "characters": "\u2AAF\u0338" },
+ "⊀": { "codepoints": [8832], "characters": "\u2280" },
+ "⪯̸": { "codepoints": [10927, 824], "characters": "\u2AAF\u0338" },
+ "⇏": { "codepoints": [8655], "characters": "\u21CF" },
+ "↛": { "codepoints": [8603], "characters": "\u219B" },
+ "⤳̸": { "codepoints": [10547, 824], "characters": "\u2933\u0338" },
+ "↝̸": { "codepoints": [8605, 824], "characters": "\u219D\u0338" },
+ "↛": { "codepoints": [8603], "characters": "\u219B" },
+ "⋫": { "codepoints": [8939], "characters": "\u22EB" },
+ "⋭": { "codepoints": [8941], "characters": "\u22ED" },
+ "⊁": { "codepoints": [8833], "characters": "\u2281" },
+ "⋡": { "codepoints": [8929], "characters": "\u22E1" },
+ "⪰̸": { "codepoints": [10928, 824], "characters": "\u2AB0\u0338" },
+ "𝓃": { "codepoints": [120003], "characters": "\uD835\uDCC3" },
+ "∤": { "codepoints": [8740], "characters": "\u2224" },
+ "∦": { "codepoints": [8742], "characters": "\u2226" },
+ "≁": { "codepoints": [8769], "characters": "\u2241" },
+ "≄": { "codepoints": [8772], "characters": "\u2244" },
+ "≄": { "codepoints": [8772], "characters": "\u2244" },
+ "∤": { "codepoints": [8740], "characters": "\u2224" },
+ "∦": { "codepoints": [8742], "characters": "\u2226" },
+ "⋢": { "codepoints": [8930], "characters": "\u22E2" },
+ "⋣": { "codepoints": [8931], "characters": "\u22E3" },
+ "⊄": { "codepoints": [8836], "characters": "\u2284" },
+ "⫅̸": { "codepoints": [10949, 824], "characters": "\u2AC5\u0338" },
+ "⊈": { "codepoints": [8840], "characters": "\u2288" },
+ "⊂⃒": { "codepoints": [8834, 8402], "characters": "\u2282\u20D2" },
+ "⊈": { "codepoints": [8840], "characters": "\u2288" },
+ "⫅̸": { "codepoints": [10949, 824], "characters": "\u2AC5\u0338" },
+ "⊁": { "codepoints": [8833], "characters": "\u2281" },
+ "⪰̸": { "codepoints": [10928, 824], "characters": "\u2AB0\u0338" },
+ "⊅": { "codepoints": [8837], "characters": "\u2285" },
+ "⫆̸": { "codepoints": [10950, 824], "characters": "\u2AC6\u0338" },
+ "⊉": { "codepoints": [8841], "characters": "\u2289" },
+ "⊃⃒": { "codepoints": [8835, 8402], "characters": "\u2283\u20D2" },
+ "⊉": { "codepoints": [8841], "characters": "\u2289" },
+ "⫆̸": { "codepoints": [10950, 824], "characters": "\u2AC6\u0338" },
+ "≹": { "codepoints": [8825], "characters": "\u2279" },
+ "ñ": { "codepoints": [241], "characters": "\u00F1" },
+ "ñ": { "codepoints": [241], "characters": "\u00F1" },
+ "≸": { "codepoints": [8824], "characters": "\u2278" },
+ "⋪": { "codepoints": [8938], "characters": "\u22EA" },
+ "⋬": { "codepoints": [8940], "characters": "\u22EC" },
+ "⋫": { "codepoints": [8939], "characters": "\u22EB" },
+ "⋭": { "codepoints": [8941], "characters": "\u22ED" },
+ "ν": { "codepoints": [957], "characters": "\u03BD" },
+ "#": { "codepoints": [35], "characters": "\u0023" },
+ "№": { "codepoints": [8470], "characters": "\u2116" },
+ " ": { "codepoints": [8199], "characters": "\u2007" },
+ "⊭": { "codepoints": [8877], "characters": "\u22AD" },
+ "⤄": { "codepoints": [10500], "characters": "\u2904" },
+ "≍⃒": { "codepoints": [8781, 8402], "characters": "\u224D\u20D2" },
+ "⊬": { "codepoints": [8876], "characters": "\u22AC" },
+ "≥⃒": { "codepoints": [8805, 8402], "characters": "\u2265\u20D2" },
+ ">⃒": { "codepoints": [62, 8402], "characters": "\u003E\u20D2" },
+ "⧞": { "codepoints": [10718], "characters": "\u29DE" },
+ "⤂": { "codepoints": [10498], "characters": "\u2902" },
+ "≤⃒": { "codepoints": [8804, 8402], "characters": "\u2264\u20D2" },
+ "<⃒": { "codepoints": [60, 8402], "characters": "\u003C\u20D2" },
+ "⊴⃒": { "codepoints": [8884, 8402], "characters": "\u22B4\u20D2" },
+ "⤃": { "codepoints": [10499], "characters": "\u2903" },
+ "⊵⃒": { "codepoints": [8885, 8402], "characters": "\u22B5\u20D2" },
+ "∼⃒": { "codepoints": [8764, 8402], "characters": "\u223C\u20D2" },
+ "⇖": { "codepoints": [8662], "characters": "\u21D6" },
+ "⤣": { "codepoints": [10531], "characters": "\u2923" },
+ "↖": { "codepoints": [8598], "characters": "\u2196" },
+ "↖": { "codepoints": [8598], "characters": "\u2196" },
+ "⤧": { "codepoints": [10535], "characters": "\u2927" },
+ "Ⓢ": { "codepoints": [9416], "characters": "\u24C8" },
+ "ó": { "codepoints": [243], "characters": "\u00F3" },
+ "ó": { "codepoints": [243], "characters": "\u00F3" },
+ "⊛": { "codepoints": [8859], "characters": "\u229B" },
+ "⊚": { "codepoints": [8858], "characters": "\u229A" },
+ "ô": { "codepoints": [244], "characters": "\u00F4" },
+ "ô": { "codepoints": [244], "characters": "\u00F4" },
+ "о": { "codepoints": [1086], "characters": "\u043E" },
+ "⊝": { "codepoints": [8861], "characters": "\u229D" },
+ "ő": { "codepoints": [337], "characters": "\u0151" },
+ "⨸": { "codepoints": [10808], "characters": "\u2A38" },
+ "⊙": { "codepoints": [8857], "characters": "\u2299" },
+ "⦼": { "codepoints": [10684], "characters": "\u29BC" },
+ "œ": { "codepoints": [339], "characters": "\u0153" },
+ "⦿": { "codepoints": [10687], "characters": "\u29BF" },
+ "𝔬": { "codepoints": [120108], "characters": "\uD835\uDD2C" },
+ "˛": { "codepoints": [731], "characters": "\u02DB" },
+ "ò": { "codepoints": [242], "characters": "\u00F2" },
+ "ò": { "codepoints": [242], "characters": "\u00F2" },
+ "⧁": { "codepoints": [10689], "characters": "\u29C1" },
+ "⦵": { "codepoints": [10677], "characters": "\u29B5" },
+ "Ω": { "codepoints": [937], "characters": "\u03A9" },
+ "∮": { "codepoints": [8750], "characters": "\u222E" },
+ "↺": { "codepoints": [8634], "characters": "\u21BA" },
+ "⦾": { "codepoints": [10686], "characters": "\u29BE" },
+ "⦻": { "codepoints": [10683], "characters": "\u29BB" },
+ "‾": { "codepoints": [8254], "characters": "\u203E" },
+ "⧀": { "codepoints": [10688], "characters": "\u29C0" },
+ "ō": { "codepoints": [333], "characters": "\u014D" },
+ "ω": { "codepoints": [969], "characters": "\u03C9" },
+ "ο": { "codepoints": [959], "characters": "\u03BF" },
+ "⦶": { "codepoints": [10678], "characters": "\u29B6" },
+ "⊖": { "codepoints": [8854], "characters": "\u2296" },
+ "𝕠": { "codepoints": [120160], "characters": "\uD835\uDD60" },
+ "⦷": { "codepoints": [10679], "characters": "\u29B7" },
+ "⦹": { "codepoints": [10681], "characters": "\u29B9" },
+ "⊕": { "codepoints": [8853], "characters": "\u2295" },
+ "∨": { "codepoints": [8744], "characters": "\u2228" },
+ "↻": { "codepoints": [8635], "characters": "\u21BB" },
+ "⩝": { "codepoints": [10845], "characters": "\u2A5D" },
+ "ℴ": { "codepoints": [8500], "characters": "\u2134" },
+ "ℴ": { "codepoints": [8500], "characters": "\u2134" },
+ "ª": { "codepoints": [170], "characters": "\u00AA" },
+ "ª": { "codepoints": [170], "characters": "\u00AA" },
+ "º": { "codepoints": [186], "characters": "\u00BA" },
+ "º": { "codepoints": [186], "characters": "\u00BA" },
+ "⊶": { "codepoints": [8886], "characters": "\u22B6" },
+ "⩖": { "codepoints": [10838], "characters": "\u2A56" },
+ "⩗": { "codepoints": [10839], "characters": "\u2A57" },
+ "⩛": { "codepoints": [10843], "characters": "\u2A5B" },
+ "ℴ": { "codepoints": [8500], "characters": "\u2134" },
+ "ø": { "codepoints": [248], "characters": "\u00F8" },
+ "ø": { "codepoints": [248], "characters": "\u00F8" },
+ "⊘": { "codepoints": [8856], "characters": "\u2298" },
+ "õ": { "codepoints": [245], "characters": "\u00F5" },
+ "õ": { "codepoints": [245], "characters": "\u00F5" },
+ "⊗": { "codepoints": [8855], "characters": "\u2297" },
+ "⨶": { "codepoints": [10806], "characters": "\u2A36" },
+ "ö": { "codepoints": [246], "characters": "\u00F6" },
+ "ö": { "codepoints": [246], "characters": "\u00F6" },
+ "⌽": { "codepoints": [9021], "characters": "\u233D" },
+ "∥": { "codepoints": [8741], "characters": "\u2225" },
+ "¶": { "codepoints": [182], "characters": "\u00B6" },
+ "¶": { "codepoints": [182], "characters": "\u00B6" },
+ "∥": { "codepoints": [8741], "characters": "\u2225" },
+ "⫳": { "codepoints": [10995], "characters": "\u2AF3" },
+ "⫽": { "codepoints": [11005], "characters": "\u2AFD" },
+ "∂": { "codepoints": [8706], "characters": "\u2202" },
+ "п": { "codepoints": [1087], "characters": "\u043F" },
+ "%": { "codepoints": [37], "characters": "\u0025" },
+ ".": { "codepoints": [46], "characters": "\u002E" },
+ "‰": { "codepoints": [8240], "characters": "\u2030" },
+ "⊥": { "codepoints": [8869], "characters": "\u22A5" },
+ "‱": { "codepoints": [8241], "characters": "\u2031" },
+ "𝔭": { "codepoints": [120109], "characters": "\uD835\uDD2D" },
+ "φ": { "codepoints": [966], "characters": "\u03C6" },
+ "ϕ": { "codepoints": [981], "characters": "\u03D5" },
+ "ℳ": { "codepoints": [8499], "characters": "\u2133" },
+ "☎": { "codepoints": [9742], "characters": "\u260E" },
+ "π": { "codepoints": [960], "characters": "\u03C0" },
+ "⋔": { "codepoints": [8916], "characters": "\u22D4" },
+ "ϖ": { "codepoints": [982], "characters": "\u03D6" },
+ "ℏ": { "codepoints": [8463], "characters": "\u210F" },
+ "ℎ": { "codepoints": [8462], "characters": "\u210E" },
+ "ℏ": { "codepoints": [8463], "characters": "\u210F" },
+ "+": { "codepoints": [43], "characters": "\u002B" },
+ "⨣": { "codepoints": [10787], "characters": "\u2A23" },
+ "⊞": { "codepoints": [8862], "characters": "\u229E" },
+ "⨢": { "codepoints": [10786], "characters": "\u2A22" },
+ "∔": { "codepoints": [8724], "characters": "\u2214" },
+ "⨥": { "codepoints": [10789], "characters": "\u2A25" },
+ "⩲": { "codepoints": [10866], "characters": "\u2A72" },
+ "±": { "codepoints": [177], "characters": "\u00B1" },
+ "±": { "codepoints": [177], "characters": "\u00B1" },
+ "⨦": { "codepoints": [10790], "characters": "\u2A26" },
+ "⨧": { "codepoints": [10791], "characters": "\u2A27" },
+ "±": { "codepoints": [177], "characters": "\u00B1" },
+ "⨕": { "codepoints": [10773], "characters": "\u2A15" },
+ "𝕡": { "codepoints": [120161], "characters": "\uD835\uDD61" },
+ "£": { "codepoints": [163], "characters": "\u00A3" },
+ "£": { "codepoints": [163], "characters": "\u00A3" },
+ "≺": { "codepoints": [8826], "characters": "\u227A" },
+ "⪳": { "codepoints": [10931], "characters": "\u2AB3" },
+ "⪷": { "codepoints": [10935], "characters": "\u2AB7" },
+ "≼": { "codepoints": [8828], "characters": "\u227C" },
+ "⪯": { "codepoints": [10927], "characters": "\u2AAF" },
+ "≺": { "codepoints": [8826], "characters": "\u227A" },
+ "⪷": { "codepoints": [10935], "characters": "\u2AB7" },
+ "≼": { "codepoints": [8828], "characters": "\u227C" },
+ "⪯": { "codepoints": [10927], "characters": "\u2AAF" },
+ "⪹": { "codepoints": [10937], "characters": "\u2AB9" },
+ "⪵": { "codepoints": [10933], "characters": "\u2AB5" },
+ "⋨": { "codepoints": [8936], "characters": "\u22E8" },
+ "≾": { "codepoints": [8830], "characters": "\u227E" },
+ "′": { "codepoints": [8242], "characters": "\u2032" },
+ "ℙ": { "codepoints": [8473], "characters": "\u2119" },
+ "⪵": { "codepoints": [10933], "characters": "\u2AB5" },
+ "⪹": { "codepoints": [10937], "characters": "\u2AB9" },
+ "⋨": { "codepoints": [8936], "characters": "\u22E8" },
+ "∏": { "codepoints": [8719], "characters": "\u220F" },
+ "⌮": { "codepoints": [9006], "characters": "\u232E" },
+ "⌒": { "codepoints": [8978], "characters": "\u2312" },
+ "⌓": { "codepoints": [8979], "characters": "\u2313" },
+ "∝": { "codepoints": [8733], "characters": "\u221D" },
+ "∝": { "codepoints": [8733], "characters": "\u221D" },
+ "≾": { "codepoints": [8830], "characters": "\u227E" },
+ "⊰": { "codepoints": [8880], "characters": "\u22B0" },
+ "𝓅": { "codepoints": [120005], "characters": "\uD835\uDCC5" },
+ "ψ": { "codepoints": [968], "characters": "\u03C8" },
+ " ": { "codepoints": [8200], "characters": "\u2008" },
+ "𝔮": { "codepoints": [120110], "characters": "\uD835\uDD2E" },
+ "⨌": { "codepoints": [10764], "characters": "\u2A0C" },
+ "𝕢": { "codepoints": [120162], "characters": "\uD835\uDD62" },
+ "⁗": { "codepoints": [8279], "characters": "\u2057" },
+ "𝓆": { "codepoints": [120006], "characters": "\uD835\uDCC6" },
+ "ℍ": { "codepoints": [8461], "characters": "\u210D" },
+ "⨖": { "codepoints": [10774], "characters": "\u2A16" },
+ "?": { "codepoints": [63], "characters": "\u003F" },
+ "≟": { "codepoints": [8799], "characters": "\u225F" },
+ """: { "codepoints": [34], "characters": "\u0022" },
+ """: { "codepoints": [34], "characters": "\u0022" },
+ "⇛": { "codepoints": [8667], "characters": "\u21DB" },
+ "⇒": { "codepoints": [8658], "characters": "\u21D2" },
+ "⤜": { "codepoints": [10524], "characters": "\u291C" },
+ "⤏": { "codepoints": [10511], "characters": "\u290F" },
+ "⥤": { "codepoints": [10596], "characters": "\u2964" },
+ "∽̱": { "codepoints": [8765, 817], "characters": "\u223D\u0331" },
+ "ŕ": { "codepoints": [341], "characters": "\u0155" },
+ "√": { "codepoints": [8730], "characters": "\u221A" },
+ "⦳": { "codepoints": [10675], "characters": "\u29B3" },
+ "⟩": { "codepoints": [10217], "characters": "\u27E9" },
+ "⦒": { "codepoints": [10642], "characters": "\u2992" },
+ "⦥": { "codepoints": [10661], "characters": "\u29A5" },
+ "⟩": { "codepoints": [10217], "characters": "\u27E9" },
+ "»": { "codepoints": [187], "characters": "\u00BB" },
+ "»": { "codepoints": [187], "characters": "\u00BB" },
+ "→": { "codepoints": [8594], "characters": "\u2192" },
+ "⥵": { "codepoints": [10613], "characters": "\u2975" },
+ "⇥": { "codepoints": [8677], "characters": "\u21E5" },
+ "⤠": { "codepoints": [10528], "characters": "\u2920" },
+ "⤳": { "codepoints": [10547], "characters": "\u2933" },
+ "⤞": { "codepoints": [10526], "characters": "\u291E" },
+ "↪": { "codepoints": [8618], "characters": "\u21AA" },
+ "↬": { "codepoints": [8620], "characters": "\u21AC" },
+ "⥅": { "codepoints": [10565], "characters": "\u2945" },
+ "⥴": { "codepoints": [10612], "characters": "\u2974" },
+ "↣": { "codepoints": [8611], "characters": "\u21A3" },
+ "↝": { "codepoints": [8605], "characters": "\u219D" },
+ "⤚": { "codepoints": [10522], "characters": "\u291A" },
+ "∶": { "codepoints": [8758], "characters": "\u2236" },
+ "ℚ": { "codepoints": [8474], "characters": "\u211A" },
+ "⤍": { "codepoints": [10509], "characters": "\u290D" },
+ "❳": { "codepoints": [10099], "characters": "\u2773" },
+ "}": { "codepoints": [125], "characters": "\u007D" },
+ "]": { "codepoints": [93], "characters": "\u005D" },
+ "⦌": { "codepoints": [10636], "characters": "\u298C" },
+ "⦎": { "codepoints": [10638], "characters": "\u298E" },
+ "⦐": { "codepoints": [10640], "characters": "\u2990" },
+ "ř": { "codepoints": [345], "characters": "\u0159" },
+ "ŗ": { "codepoints": [343], "characters": "\u0157" },
+ "⌉": { "codepoints": [8969], "characters": "\u2309" },
+ "}": { "codepoints": [125], "characters": "\u007D" },
+ "р": { "codepoints": [1088], "characters": "\u0440" },
+ "⤷": { "codepoints": [10551], "characters": "\u2937" },
+ "⥩": { "codepoints": [10601], "characters": "\u2969" },
+ "”": { "codepoints": [8221], "characters": "\u201D" },
+ "”": { "codepoints": [8221], "characters": "\u201D" },
+ "↳": { "codepoints": [8627], "characters": "\u21B3" },
+ "ℜ": { "codepoints": [8476], "characters": "\u211C" },
+ "ℛ": { "codepoints": [8475], "characters": "\u211B" },
+ "ℜ": { "codepoints": [8476], "characters": "\u211C" },
+ "ℝ": { "codepoints": [8477], "characters": "\u211D" },
+ "▭": { "codepoints": [9645], "characters": "\u25AD" },
+ "®": { "codepoints": [174], "characters": "\u00AE" },
+ "®": { "codepoints": [174], "characters": "\u00AE" },
+ "⥽": { "codepoints": [10621], "characters": "\u297D" },
+ "⌋": { "codepoints": [8971], "characters": "\u230B" },
+ "𝔯": { "codepoints": [120111], "characters": "\uD835\uDD2F" },
+ "⇁": { "codepoints": [8641], "characters": "\u21C1" },
+ "⇀": { "codepoints": [8640], "characters": "\u21C0" },
+ "⥬": { "codepoints": [10604], "characters": "\u296C" },
+ "ρ": { "codepoints": [961], "characters": "\u03C1" },
+ "ϱ": { "codepoints": [1009], "characters": "\u03F1" },
+ "→": { "codepoints": [8594], "characters": "\u2192" },
+ "↣": { "codepoints": [8611], "characters": "\u21A3" },
+ "⇁": { "codepoints": [8641], "characters": "\u21C1" },
+ "⇀": { "codepoints": [8640], "characters": "\u21C0" },
+ "⇄": { "codepoints": [8644], "characters": "\u21C4" },
+ "⇌": { "codepoints": [8652], "characters": "\u21CC" },
+ "⇉": { "codepoints": [8649], "characters": "\u21C9" },
+ "↝": { "codepoints": [8605], "characters": "\u219D" },
+ "⋌": { "codepoints": [8908], "characters": "\u22CC" },
+ "˚": { "codepoints": [730], "characters": "\u02DA" },
+ "≓": { "codepoints": [8787], "characters": "\u2253" },
+ "⇄": { "codepoints": [8644], "characters": "\u21C4" },
+ "⇌": { "codepoints": [8652], "characters": "\u21CC" },
+ "‏": { "codepoints": [8207], "characters": "\u200F" },
+ "⎱": { "codepoints": [9137], "characters": "\u23B1" },
+ "⎱": { "codepoints": [9137], "characters": "\u23B1" },
+ "⫮": { "codepoints": [10990], "characters": "\u2AEE" },
+ "⟭": { "codepoints": [10221], "characters": "\u27ED" },
+ "⇾": { "codepoints": [8702], "characters": "\u21FE" },
+ "⟧": { "codepoints": [10215], "characters": "\u27E7" },
+ "⦆": { "codepoints": [10630], "characters": "\u2986" },
+ "𝕣": { "codepoints": [120163], "characters": "\uD835\uDD63" },
+ "⨮": { "codepoints": [10798], "characters": "\u2A2E" },
+ "⨵": { "codepoints": [10805], "characters": "\u2A35" },
+ ")": { "codepoints": [41], "characters": "\u0029" },
+ "⦔": { "codepoints": [10644], "characters": "\u2994" },
+ "⨒": { "codepoints": [10770], "characters": "\u2A12" },
+ "⇉": { "codepoints": [8649], "characters": "\u21C9" },
+ "›": { "codepoints": [8250], "characters": "\u203A" },
+ "𝓇": { "codepoints": [120007], "characters": "\uD835\uDCC7" },
+ "↱": { "codepoints": [8625], "characters": "\u21B1" },
+ "]": { "codepoints": [93], "characters": "\u005D" },
+ "’": { "codepoints": [8217], "characters": "\u2019" },
+ "’": { "codepoints": [8217], "characters": "\u2019" },
+ "⋌": { "codepoints": [8908], "characters": "\u22CC" },
+ "⋊": { "codepoints": [8906], "characters": "\u22CA" },
+ "▹": { "codepoints": [9657], "characters": "\u25B9" },
+ "⊵": { "codepoints": [8885], "characters": "\u22B5" },
+ "▸": { "codepoints": [9656], "characters": "\u25B8" },
+ "⧎": { "codepoints": [10702], "characters": "\u29CE" },
+ "⥨": { "codepoints": [10600], "characters": "\u2968" },
+ "℞": { "codepoints": [8478], "characters": "\u211E" },
+ "ś": { "codepoints": [347], "characters": "\u015B" },
+ "‚": { "codepoints": [8218], "characters": "\u201A" },
+ "≻": { "codepoints": [8827], "characters": "\u227B" },
+ "⪴": { "codepoints": [10932], "characters": "\u2AB4" },
+ "⪸": { "codepoints": [10936], "characters": "\u2AB8" },
+ "š": { "codepoints": [353], "characters": "\u0161" },
+ "≽": { "codepoints": [8829], "characters": "\u227D" },
+ "⪰": { "codepoints": [10928], "characters": "\u2AB0" },
+ "ş": { "codepoints": [351], "characters": "\u015F" },
+ "ŝ": { "codepoints": [349], "characters": "\u015D" },
+ "⪶": { "codepoints": [10934], "characters": "\u2AB6" },
+ "⪺": { "codepoints": [10938], "characters": "\u2ABA" },
+ "⋩": { "codepoints": [8937], "characters": "\u22E9" },
+ "⨓": { "codepoints": [10771], "characters": "\u2A13" },
+ "≿": { "codepoints": [8831], "characters": "\u227F" },
+ "с": { "codepoints": [1089], "characters": "\u0441" },
+ "⋅": { "codepoints": [8901], "characters": "\u22C5" },
+ "⊡": { "codepoints": [8865], "characters": "\u22A1" },
+ "⩦": { "codepoints": [10854], "characters": "\u2A66" },
+ "⇘": { "codepoints": [8664], "characters": "\u21D8" },
+ "⤥": { "codepoints": [10533], "characters": "\u2925" },
+ "↘": { "codepoints": [8600], "characters": "\u2198" },
+ "↘": { "codepoints": [8600], "characters": "\u2198" },
+ "§": { "codepoints": [167], "characters": "\u00A7" },
+ "§": { "codepoints": [167], "characters": "\u00A7" },
+ ";": { "codepoints": [59], "characters": "\u003B" },
+ "⤩": { "codepoints": [10537], "characters": "\u2929" },
+ "∖": { "codepoints": [8726], "characters": "\u2216" },
+ "∖": { "codepoints": [8726], "characters": "\u2216" },
+ "✶": { "codepoints": [10038], "characters": "\u2736" },
+ "𝔰": { "codepoints": [120112], "characters": "\uD835\uDD30" },
+ "⌢": { "codepoints": [8994], "characters": "\u2322" },
+ "♯": { "codepoints": [9839], "characters": "\u266F" },
+ "щ": { "codepoints": [1097], "characters": "\u0449" },
+ "ш": { "codepoints": [1096], "characters": "\u0448" },
+ "∣": { "codepoints": [8739], "characters": "\u2223" },
+ "∥": { "codepoints": [8741], "characters": "\u2225" },
+ "­": { "codepoints": [173], "characters": "\u00AD" },
+ "­": { "codepoints": [173], "characters": "\u00AD" },
+ "σ": { "codepoints": [963], "characters": "\u03C3" },
+ "ς": { "codepoints": [962], "characters": "\u03C2" },
+ "ς": { "codepoints": [962], "characters": "\u03C2" },
+ "∼": { "codepoints": [8764], "characters": "\u223C" },
+ "⩪": { "codepoints": [10858], "characters": "\u2A6A" },
+ "≃": { "codepoints": [8771], "characters": "\u2243" },
+ "≃": { "codepoints": [8771], "characters": "\u2243" },
+ "⪞": { "codepoints": [10910], "characters": "\u2A9E" },
+ "⪠": { "codepoints": [10912], "characters": "\u2AA0" },
+ "⪝": { "codepoints": [10909], "characters": "\u2A9D" },
+ "⪟": { "codepoints": [10911], "characters": "\u2A9F" },
+ "≆": { "codepoints": [8774], "characters": "\u2246" },
+ "⨤": { "codepoints": [10788], "characters": "\u2A24" },
+ "⥲": { "codepoints": [10610], "characters": "\u2972" },
+ "←": { "codepoints": [8592], "characters": "\u2190" },
+ "∖": { "codepoints": [8726], "characters": "\u2216" },
+ "⨳": { "codepoints": [10803], "characters": "\u2A33" },
+ "⧤": { "codepoints": [10724], "characters": "\u29E4" },
+ "∣": { "codepoints": [8739], "characters": "\u2223" },
+ "⌣": { "codepoints": [8995], "characters": "\u2323" },
+ "⪪": { "codepoints": [10922], "characters": "\u2AAA" },
+ "⪬": { "codepoints": [10924], "characters": "\u2AAC" },
+ "⪬︀": { "codepoints": [10924, 65024], "characters": "\u2AAC\uFE00" },
+ "ь": { "codepoints": [1100], "characters": "\u044C" },
+ "/": { "codepoints": [47], "characters": "\u002F" },
+ "⧄": { "codepoints": [10692], "characters": "\u29C4" },
+ "⌿": { "codepoints": [9023], "characters": "\u233F" },
+ "𝕤": { "codepoints": [120164], "characters": "\uD835\uDD64" },
+ "♠": { "codepoints": [9824], "characters": "\u2660" },
+ "♠": { "codepoints": [9824], "characters": "\u2660" },
+ "∥": { "codepoints": [8741], "characters": "\u2225" },
+ "⊓": { "codepoints": [8851], "characters": "\u2293" },
+ "⊓︀": { "codepoints": [8851, 65024], "characters": "\u2293\uFE00" },
+ "⊔": { "codepoints": [8852], "characters": "\u2294" },
+ "⊔︀": { "codepoints": [8852, 65024], "characters": "\u2294\uFE00" },
+ "⊏": { "codepoints": [8847], "characters": "\u228F" },
+ "⊑": { "codepoints": [8849], "characters": "\u2291" },
+ "⊏": { "codepoints": [8847], "characters": "\u228F" },
+ "⊑": { "codepoints": [8849], "characters": "\u2291" },
+ "⊐": { "codepoints": [8848], "characters": "\u2290" },
+ "⊒": { "codepoints": [8850], "characters": "\u2292" },
+ "⊐": { "codepoints": [8848], "characters": "\u2290" },
+ "⊒": { "codepoints": [8850], "characters": "\u2292" },
+ "□": { "codepoints": [9633], "characters": "\u25A1" },
+ "□": { "codepoints": [9633], "characters": "\u25A1" },
+ "▪": { "codepoints": [9642], "characters": "\u25AA" },
+ "▪": { "codepoints": [9642], "characters": "\u25AA" },
+ "→": { "codepoints": [8594], "characters": "\u2192" },
+ "𝓈": { "codepoints": [120008], "characters": "\uD835\uDCC8" },
+ "∖": { "codepoints": [8726], "characters": "\u2216" },
+ "⌣": { "codepoints": [8995], "characters": "\u2323" },
+ "⋆": { "codepoints": [8902], "characters": "\u22C6" },
+ "☆": { "codepoints": [9734], "characters": "\u2606" },
+ "★": { "codepoints": [9733], "characters": "\u2605" },
+ "ϵ": { "codepoints": [1013], "characters": "\u03F5" },
+ "ϕ": { "codepoints": [981], "characters": "\u03D5" },
+ "¯": { "codepoints": [175], "characters": "\u00AF" },
+ "⊂": { "codepoints": [8834], "characters": "\u2282" },
+ "⫅": { "codepoints": [10949], "characters": "\u2AC5" },
+ "⪽": { "codepoints": [10941], "characters": "\u2ABD" },
+ "⊆": { "codepoints": [8838], "characters": "\u2286" },
+ "⫃": { "codepoints": [10947], "characters": "\u2AC3" },
+ "⫁": { "codepoints": [10945], "characters": "\u2AC1" },
+ "⫋": { "codepoints": [10955], "characters": "\u2ACB" },
+ "⊊": { "codepoints": [8842], "characters": "\u228A" },
+ "⪿": { "codepoints": [10943], "characters": "\u2ABF" },
+ "⥹": { "codepoints": [10617], "characters": "\u2979" },
+ "⊂": { "codepoints": [8834], "characters": "\u2282" },
+ "⊆": { "codepoints": [8838], "characters": "\u2286" },
+ "⫅": { "codepoints": [10949], "characters": "\u2AC5" },
+ "⊊": { "codepoints": [8842], "characters": "\u228A" },
+ "⫋": { "codepoints": [10955], "characters": "\u2ACB" },
+ "⫇": { "codepoints": [10951], "characters": "\u2AC7" },
+ "⫕": { "codepoints": [10965], "characters": "\u2AD5" },
+ "⫓": { "codepoints": [10963], "characters": "\u2AD3" },
+ "≻": { "codepoints": [8827], "characters": "\u227B" },
+ "⪸": { "codepoints": [10936], "characters": "\u2AB8" },
+ "≽": { "codepoints": [8829], "characters": "\u227D" },
+ "⪰": { "codepoints": [10928], "characters": "\u2AB0" },
+ "⪺": { "codepoints": [10938], "characters": "\u2ABA" },
+ "⪶": { "codepoints": [10934], "characters": "\u2AB6" },
+ "⋩": { "codepoints": [8937], "characters": "\u22E9" },
+ "≿": { "codepoints": [8831], "characters": "\u227F" },
+ "∑": { "codepoints": [8721], "characters": "\u2211" },
+ "♪": { "codepoints": [9834], "characters": "\u266A" },
+ "¹": { "codepoints": [185], "characters": "\u00B9" },
+ "¹": { "codepoints": [185], "characters": "\u00B9" },
+ "²": { "codepoints": [178], "characters": "\u00B2" },
+ "²": { "codepoints": [178], "characters": "\u00B2" },
+ "³": { "codepoints": [179], "characters": "\u00B3" },
+ "³": { "codepoints": [179], "characters": "\u00B3" },
+ "⊃": { "codepoints": [8835], "characters": "\u2283" },
+ "⫆": { "codepoints": [10950], "characters": "\u2AC6" },
+ "⪾": { "codepoints": [10942], "characters": "\u2ABE" },
+ "⫘": { "codepoints": [10968], "characters": "\u2AD8" },
+ "⊇": { "codepoints": [8839], "characters": "\u2287" },
+ "⫄": { "codepoints": [10948], "characters": "\u2AC4" },
+ "⟉": { "codepoints": [10185], "characters": "\u27C9" },
+ "⫗": { "codepoints": [10967], "characters": "\u2AD7" },
+ "⥻": { "codepoints": [10619], "characters": "\u297B" },
+ "⫂": { "codepoints": [10946], "characters": "\u2AC2" },
+ "⫌": { "codepoints": [10956], "characters": "\u2ACC" },
+ "⊋": { "codepoints": [8843], "characters": "\u228B" },
+ "⫀": { "codepoints": [10944], "characters": "\u2AC0" },
+ "⊃": { "codepoints": [8835], "characters": "\u2283" },
+ "⊇": { "codepoints": [8839], "characters": "\u2287" },
+ "⫆": { "codepoints": [10950], "characters": "\u2AC6" },
+ "⊋": { "codepoints": [8843], "characters": "\u228B" },
+ "⫌": { "codepoints": [10956], "characters": "\u2ACC" },
+ "⫈": { "codepoints": [10952], "characters": "\u2AC8" },
+ "⫔": { "codepoints": [10964], "characters": "\u2AD4" },
+ "⫖": { "codepoints": [10966], "characters": "\u2AD6" },
+ "⇙": { "codepoints": [8665], "characters": "\u21D9" },
+ "⤦": { "codepoints": [10534], "characters": "\u2926" },
+ "↙": { "codepoints": [8601], "characters": "\u2199" },
+ "↙": { "codepoints": [8601], "characters": "\u2199" },
+ "⤪": { "codepoints": [10538], "characters": "\u292A" },
+ "ß": { "codepoints": [223], "characters": "\u00DF" },
+ "ß": { "codepoints": [223], "characters": "\u00DF" },
+ "⌖": { "codepoints": [8982], "characters": "\u2316" },
+ "τ": { "codepoints": [964], "characters": "\u03C4" },
+ "⎴": { "codepoints": [9140], "characters": "\u23B4" },
+ "ť": { "codepoints": [357], "characters": "\u0165" },
+ "ţ": { "codepoints": [355], "characters": "\u0163" },
+ "т": { "codepoints": [1090], "characters": "\u0442" },
+ "⃛": { "codepoints": [8411], "characters": "\u20DB" },
+ "⌕": { "codepoints": [8981], "characters": "\u2315" },
+ "𝔱": { "codepoints": [120113], "characters": "\uD835\uDD31" },
+ "∴": { "codepoints": [8756], "characters": "\u2234" },
+ "∴": { "codepoints": [8756], "characters": "\u2234" },
+ "θ": { "codepoints": [952], "characters": "\u03B8" },
+ "ϑ": { "codepoints": [977], "characters": "\u03D1" },
+ "ϑ": { "codepoints": [977], "characters": "\u03D1" },
+ "≈": { "codepoints": [8776], "characters": "\u2248" },
+ "∼": { "codepoints": [8764], "characters": "\u223C" },
+ " ": { "codepoints": [8201], "characters": "\u2009" },
+ "≈": { "codepoints": [8776], "characters": "\u2248" },
+ "∼": { "codepoints": [8764], "characters": "\u223C" },
+ "þ": { "codepoints": [254], "characters": "\u00FE" },
+ "þ": { "codepoints": [254], "characters": "\u00FE" },
+ "˜": { "codepoints": [732], "characters": "\u02DC" },
+ "×": { "codepoints": [215], "characters": "\u00D7" },
+ "×": { "codepoints": [215], "characters": "\u00D7" },
+ "⊠": { "codepoints": [8864], "characters": "\u22A0" },
+ "⨱": { "codepoints": [10801], "characters": "\u2A31" },
+ "⨰": { "codepoints": [10800], "characters": "\u2A30" },
+ "∭": { "codepoints": [8749], "characters": "\u222D" },
+ "⤨": { "codepoints": [10536], "characters": "\u2928" },
+ "⊤": { "codepoints": [8868], "characters": "\u22A4" },
+ "⌶": { "codepoints": [9014], "characters": "\u2336" },
+ "⫱": { "codepoints": [10993], "characters": "\u2AF1" },
+ "𝕥": { "codepoints": [120165], "characters": "\uD835\uDD65" },
+ "⫚": { "codepoints": [10970], "characters": "\u2ADA" },
+ "⤩": { "codepoints": [10537], "characters": "\u2929" },
+ "‴": { "codepoints": [8244], "characters": "\u2034" },
+ "™": { "codepoints": [8482], "characters": "\u2122" },
+ "▵": { "codepoints": [9653], "characters": "\u25B5" },
+ "▿": { "codepoints": [9663], "characters": "\u25BF" },
+ "◃": { "codepoints": [9667], "characters": "\u25C3" },
+ "⊴": { "codepoints": [8884], "characters": "\u22B4" },
+ "≜": { "codepoints": [8796], "characters": "\u225C" },
+ "▹": { "codepoints": [9657], "characters": "\u25B9" },
+ "⊵": { "codepoints": [8885], "characters": "\u22B5" },
+ "◬": { "codepoints": [9708], "characters": "\u25EC" },
+ "≜": { "codepoints": [8796], "characters": "\u225C" },
+ "⨺": { "codepoints": [10810], "characters": "\u2A3A" },
+ "⨹": { "codepoints": [10809], "characters": "\u2A39" },
+ "⧍": { "codepoints": [10701], "characters": "\u29CD" },
+ "⨻": { "codepoints": [10811], "characters": "\u2A3B" },
+ "⏢": { "codepoints": [9186], "characters": "\u23E2" },
+ "𝓉": { "codepoints": [120009], "characters": "\uD835\uDCC9" },
+ "ц": { "codepoints": [1094], "characters": "\u0446" },
+ "ћ": { "codepoints": [1115], "characters": "\u045B" },
+ "ŧ": { "codepoints": [359], "characters": "\u0167" },
+ "≬": { "codepoints": [8812], "characters": "\u226C" },
+ "↞": { "codepoints": [8606], "characters": "\u219E" },
+ "↠": { "codepoints": [8608], "characters": "\u21A0" },
+ "⇑": { "codepoints": [8657], "characters": "\u21D1" },
+ "⥣": { "codepoints": [10595], "characters": "\u2963" },
+ "ú": { "codepoints": [250], "characters": "\u00FA" },
+ "ú": { "codepoints": [250], "characters": "\u00FA" },
+ "↑": { "codepoints": [8593], "characters": "\u2191" },
+ "ў": { "codepoints": [1118], "characters": "\u045E" },
+ "ŭ": { "codepoints": [365], "characters": "\u016D" },
+ "û": { "codepoints": [251], "characters": "\u00FB" },
+ "û": { "codepoints": [251], "characters": "\u00FB" },
+ "у": { "codepoints": [1091], "characters": "\u0443" },
+ "⇅": { "codepoints": [8645], "characters": "\u21C5" },
+ "ű": { "codepoints": [369], "characters": "\u0171" },
+ "⥮": { "codepoints": [10606], "characters": "\u296E" },
+ "⥾": { "codepoints": [10622], "characters": "\u297E" },
+ "𝔲": { "codepoints": [120114], "characters": "\uD835\uDD32" },
+ "ù": { "codepoints": [249], "characters": "\u00F9" },
+ "ù": { "codepoints": [249], "characters": "\u00F9" },
+ "↿": { "codepoints": [8639], "characters": "\u21BF" },
+ "↾": { "codepoints": [8638], "characters": "\u21BE" },
+ "▀": { "codepoints": [9600], "characters": "\u2580" },
+ "⌜": { "codepoints": [8988], "characters": "\u231C" },
+ "⌜": { "codepoints": [8988], "characters": "\u231C" },
+ "⌏": { "codepoints": [8975], "characters": "\u230F" },
+ "◸": { "codepoints": [9720], "characters": "\u25F8" },
+ "ū": { "codepoints": [363], "characters": "\u016B" },
+ "¨": { "codepoints": [168], "characters": "\u00A8" },
+ "¨": { "codepoints": [168], "characters": "\u00A8" },
+ "ų": { "codepoints": [371], "characters": "\u0173" },
+ "𝕦": { "codepoints": [120166], "characters": "\uD835\uDD66" },
+ "↑": { "codepoints": [8593], "characters": "\u2191" },
+ "↕": { "codepoints": [8597], "characters": "\u2195" },
+ "↿": { "codepoints": [8639], "characters": "\u21BF" },
+ "↾": { "codepoints": [8638], "characters": "\u21BE" },
+ "⊎": { "codepoints": [8846], "characters": "\u228E" },
+ "υ": { "codepoints": [965], "characters": "\u03C5" },
+ "ϒ": { "codepoints": [978], "characters": "\u03D2" },
+ "υ": { "codepoints": [965], "characters": "\u03C5" },
+ "⇈": { "codepoints": [8648], "characters": "\u21C8" },
+ "⌝": { "codepoints": [8989], "characters": "\u231D" },
+ "⌝": { "codepoints": [8989], "characters": "\u231D" },
+ "⌎": { "codepoints": [8974], "characters": "\u230E" },
+ "ů": { "codepoints": [367], "characters": "\u016F" },
+ "◹": { "codepoints": [9721], "characters": "\u25F9" },
+ "𝓊": { "codepoints": [120010], "characters": "\uD835\uDCCA" },
+ "⋰": { "codepoints": [8944], "characters": "\u22F0" },
+ "ũ": { "codepoints": [361], "characters": "\u0169" },
+ "▵": { "codepoints": [9653], "characters": "\u25B5" },
+ "▴": { "codepoints": [9652], "characters": "\u25B4" },
+ "⇈": { "codepoints": [8648], "characters": "\u21C8" },
+ "ü": { "codepoints": [252], "characters": "\u00FC" },
+ "ü": { "codepoints": [252], "characters": "\u00FC" },
+ "⦧": { "codepoints": [10663], "characters": "\u29A7" },
+ "⇕": { "codepoints": [8661], "characters": "\u21D5" },
+ "⫨": { "codepoints": [10984], "characters": "\u2AE8" },
+ "⫩": { "codepoints": [10985], "characters": "\u2AE9" },
+ "⊨": { "codepoints": [8872], "characters": "\u22A8" },
+ "⦜": { "codepoints": [10652], "characters": "\u299C" },
+ "ϵ": { "codepoints": [1013], "characters": "\u03F5" },
+ "ϰ": { "codepoints": [1008], "characters": "\u03F0" },
+ "∅": { "codepoints": [8709], "characters": "\u2205" },
+ "ϕ": { "codepoints": [981], "characters": "\u03D5" },
+ "ϖ": { "codepoints": [982], "characters": "\u03D6" },
+ "∝": { "codepoints": [8733], "characters": "\u221D" },
+ "↕": { "codepoints": [8597], "characters": "\u2195" },
+ "ϱ": { "codepoints": [1009], "characters": "\u03F1" },
+ "ς": { "codepoints": [962], "characters": "\u03C2" },
+ "⊊︀": { "codepoints": [8842, 65024], "characters": "\u228A\uFE00" },
+ "⫋︀": { "codepoints": [10955, 65024], "characters": "\u2ACB\uFE00" },
+ "⊋︀": { "codepoints": [8843, 65024], "characters": "\u228B\uFE00" },
+ "⫌︀": { "codepoints": [10956, 65024], "characters": "\u2ACC\uFE00" },
+ "ϑ": { "codepoints": [977], "characters": "\u03D1" },
+ "⊲": { "codepoints": [8882], "characters": "\u22B2" },
+ "⊳": { "codepoints": [8883], "characters": "\u22B3" },
+ "в": { "codepoints": [1074], "characters": "\u0432" },
+ "⊢": { "codepoints": [8866], "characters": "\u22A2" },
+ "∨": { "codepoints": [8744], "characters": "\u2228" },
+ "⊻": { "codepoints": [8891], "characters": "\u22BB" },
+ "≚": { "codepoints": [8794], "characters": "\u225A" },
+ "⋮": { "codepoints": [8942], "characters": "\u22EE" },
+ "|": { "codepoints": [124], "characters": "\u007C" },
+ "|": { "codepoints": [124], "characters": "\u007C" },
+ "𝔳": { "codepoints": [120115], "characters": "\uD835\uDD33" },
+ "⊲": { "codepoints": [8882], "characters": "\u22B2" },
+ "⊂⃒": { "codepoints": [8834, 8402], "characters": "\u2282\u20D2" },
+ "⊃⃒": { "codepoints": [8835, 8402], "characters": "\u2283\u20D2" },
+ "𝕧": { "codepoints": [120167], "characters": "\uD835\uDD67" },
+ "∝": { "codepoints": [8733], "characters": "\u221D" },
+ "⊳": { "codepoints": [8883], "characters": "\u22B3" },
+ "𝓋": { "codepoints": [120011], "characters": "\uD835\uDCCB" },
+ "⫋︀": { "codepoints": [10955, 65024], "characters": "\u2ACB\uFE00" },
+ "⊊︀": { "codepoints": [8842, 65024], "characters": "\u228A\uFE00" },
+ "⫌︀": { "codepoints": [10956, 65024], "characters": "\u2ACC\uFE00" },
+ "⊋︀": { "codepoints": [8843, 65024], "characters": "\u228B\uFE00" },
+ "⦚": { "codepoints": [10650], "characters": "\u299A" },
+ "ŵ": { "codepoints": [373], "characters": "\u0175" },
+ "⩟": { "codepoints": [10847], "characters": "\u2A5F" },
+ "∧": { "codepoints": [8743], "characters": "\u2227" },
+ "≙": { "codepoints": [8793], "characters": "\u2259" },
+ "℘": { "codepoints": [8472], "characters": "\u2118" },
+ "𝔴": { "codepoints": [120116], "characters": "\uD835\uDD34" },
+ "𝕨": { "codepoints": [120168], "characters": "\uD835\uDD68" },
+ "℘": { "codepoints": [8472], "characters": "\u2118" },
+ "≀": { "codepoints": [8768], "characters": "\u2240" },
+ "≀": { "codepoints": [8768], "characters": "\u2240" },
+ "𝓌": { "codepoints": [120012], "characters": "\uD835\uDCCC" },
+ "⋂": { "codepoints": [8898], "characters": "\u22C2" },
+ "◯": { "codepoints": [9711], "characters": "\u25EF" },
+ "⋃": { "codepoints": [8899], "characters": "\u22C3" },
+ "▽": { "codepoints": [9661], "characters": "\u25BD" },
+ "𝔵": { "codepoints": [120117], "characters": "\uD835\uDD35" },
+ "⟺": { "codepoints": [10234], "characters": "\u27FA" },
+ "⟷": { "codepoints": [10231], "characters": "\u27F7" },
+ "ξ": { "codepoints": [958], "characters": "\u03BE" },
+ "⟸": { "codepoints": [10232], "characters": "\u27F8" },
+ "⟵": { "codepoints": [10229], "characters": "\u27F5" },
+ "⟼": { "codepoints": [10236], "characters": "\u27FC" },
+ "⋻": { "codepoints": [8955], "characters": "\u22FB" },
+ "⨀": { "codepoints": [10752], "characters": "\u2A00" },
+ "𝕩": { "codepoints": [120169], "characters": "\uD835\uDD69" },
+ "⨁": { "codepoints": [10753], "characters": "\u2A01" },
+ "⨂": { "codepoints": [10754], "characters": "\u2A02" },
+ "⟹": { "codepoints": [10233], "characters": "\u27F9" },
+ "⟶": { "codepoints": [10230], "characters": "\u27F6" },
+ "𝓍": { "codepoints": [120013], "characters": "\uD835\uDCCD" },
+ "⨆": { "codepoints": [10758], "characters": "\u2A06" },
+ "⨄": { "codepoints": [10756], "characters": "\u2A04" },
+ "△": { "codepoints": [9651], "characters": "\u25B3" },
+ "⋁": { "codepoints": [8897], "characters": "\u22C1" },
+ "⋀": { "codepoints": [8896], "characters": "\u22C0" },
+ "ý": { "codepoints": [253], "characters": "\u00FD" },
+ "ý": { "codepoints": [253], "characters": "\u00FD" },
+ "я": { "codepoints": [1103], "characters": "\u044F" },
+ "ŷ": { "codepoints": [375], "characters": "\u0177" },
+ "ы": { "codepoints": [1099], "characters": "\u044B" },
+ "¥": { "codepoints": [165], "characters": "\u00A5" },
+ "¥": { "codepoints": [165], "characters": "\u00A5" },
+ "𝔶": { "codepoints": [120118], "characters": "\uD835\uDD36" },
+ "ї": { "codepoints": [1111], "characters": "\u0457" },
+ "𝕪": { "codepoints": [120170], "characters": "\uD835\uDD6A" },
+ "𝓎": { "codepoints": [120014], "characters": "\uD835\uDCCE" },
+ "ю": { "codepoints": [1102], "characters": "\u044E" },
+ "ÿ": { "codepoints": [255], "characters": "\u00FF" },
+ "ÿ": { "codepoints": [255], "characters": "\u00FF" },
+ "ź": { "codepoints": [378], "characters": "\u017A" },
+ "ž": { "codepoints": [382], "characters": "\u017E" },
+ "з": { "codepoints": [1079], "characters": "\u0437" },
+ "ż": { "codepoints": [380], "characters": "\u017C" },
+ "ℨ": { "codepoints": [8488], "characters": "\u2128" },
+ "ζ": { "codepoints": [950], "characters": "\u03B6" },
+ "𝔷": { "codepoints": [120119], "characters": "\uD835\uDD37" },
+ "ж": { "codepoints": [1078], "characters": "\u0436" },
+ "⇝": { "codepoints": [8669], "characters": "\u21DD" },
+ "𝕫": { "codepoints": [120171], "characters": "\uD835\uDD6B" },
+ "𝓏": { "codepoints": [120015], "characters": "\uD835\uDCCF" },
+ "‍": { "codepoints": [8205], "characters": "\u200D" },
+ "‌": { "codepoints": [8204], "characters": "\u200C" }
+}
diff --git a/pkgs/markdown/tool/expected_output.dart b/pkgs/markdown/tool/expected_output.dart
new file mode 100644
index 0000000..aa7bab3
--- /dev/null
+++ b/pkgs/markdown/tool/expected_output.dart
@@ -0,0 +1,159 @@
+// 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:io';
+
+import 'package:path/path.dart' as p;
+
+/// Parse and yield data cases (each a [DataCase]) from [path].
+Iterable<DataCase> dataCasesInFile({
+ required String path,
+ String? baseDir,
+}) sync* {
+ final file = p.basename(path).replaceFirst(RegExp(r'\..+$'), '');
+ baseDir ??= p.relative(p.dirname(path), from: p.dirname(p.dirname(path)));
+
+ // Explicitly create a File, in case the entry is a Link.
+ final lines = File(path).readAsLinesSync();
+
+ final frontMatter = StringBuffer();
+
+ var i = 0;
+
+ while (!lines[i].startsWith('>>>')) {
+ frontMatter.write('${lines[i++]}\n');
+ }
+
+ while (i < lines.length) {
+ var description = lines[i++].replaceFirst(RegExp(r'>>>\s*'), '').trim();
+ final skip = description.startsWith('skip:');
+ if (description == '') {
+ description = 'line ${i + 1}';
+ } else {
+ description = 'line ${i + 1}: $description';
+ }
+
+ final input = StringBuffer();
+ while (!lines[i].startsWith('<<<')) {
+ input.writeln(lines[i++]);
+ }
+
+ final expectedOutput = StringBuffer();
+ while (++i < lines.length && !lines[i].startsWith('>>>')) {
+ expectedOutput.writeln(lines[i]);
+ }
+
+ final dataCase = DataCase(
+ directory: baseDir,
+ file: file,
+ front_matter: frontMatter.toString(),
+ description: description,
+ skip: skip,
+ input: input.toString(),
+ expectedOutput: expectedOutput.toString(),
+ );
+ yield dataCase;
+ }
+}
+
+/// Parse and return data cases (each a [DataCase]) from [directory].
+///
+/// By default, only read data cases from files with a `.unit` extension. Data
+/// cases are read from files located immediately in [directory], or
+/// recursively, according to [recursive].
+Iterable<DataCase> _dataCases({
+ required String directory,
+ String extension = 'unit',
+ bool recursive = true,
+}) {
+ final entries =
+ Directory(directory).listSync(recursive: recursive, followLinks: false);
+ final results = <DataCase>[];
+ for (final entry in entries) {
+ if (!entry.path.endsWith(extension)) {
+ continue;
+ }
+
+ final relativeDir =
+ p.relative(p.dirname(entry.path), from: p.dirname(directory));
+
+ results.addAll(dataCasesInFile(path: entry.path, baseDir: relativeDir));
+ }
+
+ // The API makes no guarantees on order. This is just here for stability in
+ // tests.
+ results.sort((a, b) {
+ final compare = a.directory.compareTo(b.directory);
+ if (compare != 0) return compare;
+
+ return a.file.compareTo(b.file);
+ });
+ return results;
+}
+
+/// Parse and yield data cases (each a [DataCase]) from [testDirectory].
+///
+/// By default, only read data cases from files with a `.unit` extension. Data
+/// cases are read from files located immediately in [testDirectory], or
+/// recursively, according to [recursive].
+///
+/// The typical use case of this method is to declare a library at the top of a
+/// Dart test file, then reference the symbol with a pound sign. Example:
+///
+/// ```dart
+/// library my_package.test.this_test;
+///
+/// import 'package:expected_output/expected_output.dart';
+/// import 'package:test/test.dart';
+///
+/// void main() {
+/// for (final dataCase
+/// in dataCasesUnder(library: #my_package.test.this_test)) {
+/// // ...
+/// }
+/// }
+/// ```
+Iterable<DataCase> dataCasesUnder({
+ required String testDirectory,
+ String extension = 'unit',
+ bool recursive = true,
+}) sync* {
+ final directory = p.join(p.current, 'test', testDirectory);
+ for (final dataCase in _dataCases(
+ directory: directory,
+ extension: extension,
+ recursive: recursive,
+ )) {
+ yield dataCase;
+ }
+}
+
+/// All of the data pertaining to a particular test case, namely the [input] and
+/// [expectedOutput].
+class DataCase {
+ final String directory;
+ final String file;
+
+ // ignore: non_constant_identifier_names
+ final String front_matter;
+ final String description;
+ final bool skip;
+ final String input;
+ final String expectedOutput;
+
+ DataCase({
+ this.directory = '',
+ this.file = '',
+ // ignore: non_constant_identifier_names
+ this.front_matter = '',
+ this.description = '',
+ this.skip = false,
+ required this.input,
+ required this.expectedOutput,
+ });
+
+ /// A good standard description for `test()`, derived from the data directory,
+ /// the particular data file, and the test case description.
+ String get testDescription => [directory, file, description].join(' ');
+}
diff --git a/pkgs/markdown/tool/gfm_stats.json b/pkgs/markdown/tool/gfm_stats.json
new file mode 100644
index 0000000..93c0cab
--- /dev/null
+++ b/pkgs/markdown/tool/gfm_stats.json
@@ -0,0 +1,732 @@
+{
+ "ATX headings": {
+ "32": "strict",
+ "33": "strict",
+ "34": "strict",
+ "35": "strict",
+ "36": "strict",
+ "37": "strict",
+ "38": "strict",
+ "39": "strict",
+ "40": "strict",
+ "41": "strict",
+ "42": "strict",
+ "43": "strict",
+ "44": "strict",
+ "45": "strict",
+ "46": "strict",
+ "47": "strict",
+ "48": "strict",
+ "49": "strict"
+ },
+ "Autolinks": {
+ "602": "strict",
+ "603": "strict",
+ "604": "strict",
+ "605": "strict",
+ "606": "strict",
+ "607": "strict",
+ "608": "strict",
+ "609": "strict",
+ "610": "strict",
+ "611": "strict",
+ "612": "strict",
+ "613": "strict",
+ "614": "strict",
+ "615": "strict",
+ "616": "strict",
+ "617": "strict",
+ "618": "strict",
+ "619": "strict",
+ "620": "strict"
+ },
+ "Autolinks (extension)": {
+ "621": "strict",
+ "622": "strict",
+ "623": "strict",
+ "624": "strict",
+ "625": "strict",
+ "626": "strict",
+ "627": "strict",
+ "628": "strict",
+ "629": "strict",
+ "630": "strict",
+ "631": "strict"
+ },
+ "Backslash escapes": {
+ "308": "strict",
+ "309": "strict",
+ "310": "strict",
+ "311": "strict",
+ "312": "strict",
+ "313": "strict",
+ "314": "strict",
+ "315": "strict",
+ "316": "strict",
+ "317": "strict",
+ "318": "strict",
+ "319": "strict",
+ "320": "strict"
+ },
+ "Blank lines": {
+ "197": "strict"
+ },
+ "Block quotes": {
+ "206": "strict",
+ "207": "strict",
+ "208": "strict",
+ "209": "strict",
+ "210": "strict",
+ "211": "strict",
+ "212": "strict",
+ "213": "strict",
+ "214": "strict",
+ "215": "strict",
+ "216": "strict",
+ "217": "strict",
+ "218": "strict",
+ "219": "strict",
+ "220": "strict",
+ "221": "strict",
+ "222": "strict",
+ "223": "strict",
+ "224": "strict",
+ "225": "strict",
+ "226": "strict",
+ "227": "strict",
+ "228": "strict",
+ "229": "strict",
+ "230": "strict"
+ },
+ "Code spans": {
+ "338": "strict",
+ "339": "strict",
+ "340": "strict",
+ "341": "strict",
+ "342": "strict",
+ "343": "strict",
+ "344": "strict",
+ "345": "strict",
+ "346": "strict",
+ "347": "strict",
+ "348": "strict",
+ "349": "strict",
+ "350": "strict",
+ "351": "strict",
+ "352": "strict",
+ "353": "strict",
+ "354": "strict",
+ "355": "strict",
+ "356": "strict",
+ "357": "strict",
+ "358": "strict",
+ "359": "strict"
+ },
+ "Disallowed Raw HTML (extension)": {
+ "652": "loose"
+ },
+ "Emphasis and strong emphasis": {
+ "360": "strict",
+ "361": "strict",
+ "362": "strict",
+ "363": "strict",
+ "364": "strict",
+ "365": "strict",
+ "366": "strict",
+ "367": "strict",
+ "368": "strict",
+ "369": "strict",
+ "370": "strict",
+ "371": "strict",
+ "372": "strict",
+ "373": "strict",
+ "374": "strict",
+ "375": "strict",
+ "376": "strict",
+ "377": "strict",
+ "378": "strict",
+ "379": "strict",
+ "380": "strict",
+ "381": "strict",
+ "382": "strict",
+ "383": "strict",
+ "384": "strict",
+ "385": "strict",
+ "386": "strict",
+ "387": "strict",
+ "388": "strict",
+ "389": "strict",
+ "390": "strict",
+ "391": "strict",
+ "392": "strict",
+ "393": "strict",
+ "394": "strict",
+ "395": "strict",
+ "396": "strict",
+ "397": "strict",
+ "398": "strict",
+ "399": "strict",
+ "400": "strict",
+ "401": "strict",
+ "402": "strict",
+ "403": "strict",
+ "404": "strict",
+ "405": "strict",
+ "406": "strict",
+ "407": "strict",
+ "408": "strict",
+ "409": "strict",
+ "410": "strict",
+ "411": "strict",
+ "412": "strict",
+ "413": "strict",
+ "414": "strict",
+ "415": "strict",
+ "416": "strict",
+ "417": "strict",
+ "418": "strict",
+ "419": "strict",
+ "420": "strict",
+ "421": "strict",
+ "422": "strict",
+ "423": "strict",
+ "424": "strict",
+ "425": "strict",
+ "426": "strict",
+ "427": "strict",
+ "428": "strict",
+ "429": "strict",
+ "430": "strict",
+ "431": "strict",
+ "432": "strict",
+ "433": "strict",
+ "434": "strict",
+ "435": "strict",
+ "436": "strict",
+ "437": "strict",
+ "438": "strict",
+ "439": "strict",
+ "440": "strict",
+ "441": "strict",
+ "442": "strict",
+ "443": "strict",
+ "444": "strict",
+ "445": "strict",
+ "446": "strict",
+ "447": "strict",
+ "448": "strict",
+ "449": "strict",
+ "450": "strict",
+ "451": "strict",
+ "452": "strict",
+ "453": "strict",
+ "454": "strict",
+ "455": "strict",
+ "456": "strict",
+ "457": "strict",
+ "458": "strict",
+ "459": "strict",
+ "460": "strict",
+ "461": "strict",
+ "462": "strict",
+ "463": "strict",
+ "464": "strict",
+ "465": "strict",
+ "466": "strict",
+ "467": "strict",
+ "468": "strict",
+ "469": "strict",
+ "470": "strict",
+ "471": "strict",
+ "472": "strict",
+ "473": "strict",
+ "474": "strict",
+ "475": "strict",
+ "476": "strict",
+ "477": "strict",
+ "478": "strict",
+ "479": "strict",
+ "480": "strict",
+ "481": "strict",
+ "482": "strict",
+ "483": "strict",
+ "484": "strict",
+ "485": "strict",
+ "486": "strict",
+ "487": "strict",
+ "488": "strict",
+ "489": "strict",
+ "490": "strict"
+ },
+ "Entity and numeric character references": {
+ "321": "loose",
+ "322": "strict",
+ "323": "strict",
+ "324": "strict",
+ "325": "strict",
+ "326": "strict",
+ "327": "strict",
+ "328": "strict",
+ "329": "strict",
+ "330": "strict",
+ "331": "strict",
+ "332": "strict",
+ "333": "strict",
+ "334": "strict",
+ "335": "strict",
+ "336": "loose",
+ "337": "strict"
+ },
+ "Fenced code blocks": {
+ "89": "strict",
+ "90": "strict",
+ "91": "strict",
+ "92": "strict",
+ "93": "strict",
+ "94": "strict",
+ "95": "strict",
+ "96": "strict",
+ "97": "strict",
+ "98": "strict",
+ "99": "strict",
+ "100": "strict",
+ "101": "strict",
+ "102": "strict",
+ "103": "strict",
+ "104": "strict",
+ "105": "strict",
+ "106": "strict",
+ "107": "strict",
+ "108": "strict",
+ "109": "strict",
+ "110": "strict",
+ "111": "strict",
+ "112": "strict",
+ "113": "strict",
+ "114": "strict",
+ "115": "strict",
+ "116": "strict",
+ "117": "strict"
+ },
+ "Hard line breaks": {
+ "653": "strict",
+ "654": "strict",
+ "655": "strict",
+ "656": "strict",
+ "657": "strict",
+ "658": "strict",
+ "659": "strict",
+ "660": "strict",
+ "661": "strict",
+ "662": "strict",
+ "663": "strict",
+ "664": "strict",
+ "665": "strict",
+ "666": "strict",
+ "667": "strict"
+ },
+ "HTML blocks": {
+ "118": "strict",
+ "119": "strict",
+ "120": "strict",
+ "121": "strict",
+ "122": "strict",
+ "123": "strict",
+ "124": "strict",
+ "125": "strict",
+ "126": "strict",
+ "127": "strict",
+ "128": "strict",
+ "129": "strict",
+ "130": "strict",
+ "131": "strict",
+ "132": "strict",
+ "133": "strict",
+ "134": "strict",
+ "135": "strict",
+ "136": "strict",
+ "137": "strict",
+ "138": "strict",
+ "139": "strict",
+ "140": "strict",
+ "141": "strict",
+ "142": "strict",
+ "143": "strict",
+ "144": "strict",
+ "145": "strict",
+ "146": "strict",
+ "147": "strict",
+ "148": "strict",
+ "149": "strict",
+ "150": "strict",
+ "151": "strict",
+ "152": "strict",
+ "153": "strict",
+ "154": "strict",
+ "155": "strict",
+ "156": "strict",
+ "157": "strict",
+ "158": "strict",
+ "159": "strict",
+ "160": "strict"
+ },
+ "Images": {
+ "580": "strict",
+ "581": "strict",
+ "582": "strict",
+ "583": "strict",
+ "584": "strict",
+ "585": "strict",
+ "586": "strict",
+ "587": "strict",
+ "588": "strict",
+ "589": "strict",
+ "590": "strict",
+ "591": "strict",
+ "592": "strict",
+ "593": "strict",
+ "594": "strict",
+ "595": "strict",
+ "596": "strict",
+ "597": "strict",
+ "598": "strict",
+ "599": "strict",
+ "600": "strict",
+ "601": "strict"
+ },
+ "Indented code blocks": {
+ "77": "strict",
+ "78": "strict",
+ "79": "strict",
+ "80": "strict",
+ "81": "strict",
+ "82": "strict",
+ "83": "strict",
+ "84": "strict",
+ "85": "strict",
+ "86": "strict",
+ "87": "strict",
+ "88": "strict"
+ },
+ "Inlines": {
+ "307": "strict"
+ },
+ "Link reference definitions": {
+ "161": "strict",
+ "162": "strict",
+ "163": "strict",
+ "164": "strict",
+ "165": "strict",
+ "166": "strict",
+ "167": "strict",
+ "168": "strict",
+ "169": "strict",
+ "170": "strict",
+ "171": "strict",
+ "172": "strict",
+ "173": "strict",
+ "174": "strict",
+ "175": "strict",
+ "176": "loose",
+ "177": "strict",
+ "178": "strict",
+ "179": "strict",
+ "180": "strict",
+ "181": "strict",
+ "182": "strict",
+ "183": "strict",
+ "184": "strict",
+ "185": "strict",
+ "186": "strict",
+ "187": "strict",
+ "188": "loose"
+ },
+ "Links": {
+ "493": "strict",
+ "494": "strict",
+ "495": "strict",
+ "496": "strict",
+ "497": "strict",
+ "498": "strict",
+ "499": "strict",
+ "500": "strict",
+ "501": "strict",
+ "502": "strict",
+ "503": "strict",
+ "504": "strict",
+ "505": "strict",
+ "506": "strict",
+ "507": "strict",
+ "508": "strict",
+ "509": "strict",
+ "510": "strict",
+ "511": "strict",
+ "512": "strict",
+ "513": "strict",
+ "514": "strict",
+ "515": "strict",
+ "516": "strict",
+ "517": "strict",
+ "518": "strict",
+ "519": "strict",
+ "520": "strict",
+ "521": "strict",
+ "522": "strict",
+ "523": "strict",
+ "524": "strict",
+ "525": "strict",
+ "526": "strict",
+ "527": "strict",
+ "528": "strict",
+ "529": "strict",
+ "530": "strict",
+ "531": "strict",
+ "532": "strict",
+ "533": "strict",
+ "534": "strict",
+ "535": "strict",
+ "536": "strict",
+ "537": "strict",
+ "538": "strict",
+ "539": "strict",
+ "540": "strict",
+ "541": "strict",
+ "542": "strict",
+ "543": "strict",
+ "544": "strict",
+ "545": "strict",
+ "546": "strict",
+ "547": "strict",
+ "548": "strict",
+ "549": "strict",
+ "550": "strict",
+ "551": "strict",
+ "552": "strict",
+ "553": "strict",
+ "554": "strict",
+ "555": "strict",
+ "556": "strict",
+ "557": "strict",
+ "558": "strict",
+ "559": "strict",
+ "560": "strict",
+ "561": "strict",
+ "562": "strict",
+ "563": "strict",
+ "564": "strict",
+ "565": "strict",
+ "566": "strict",
+ "567": "strict",
+ "568": "strict",
+ "569": "strict",
+ "570": "strict",
+ "571": "strict",
+ "572": "strict",
+ "573": "strict",
+ "574": "strict",
+ "575": "strict",
+ "576": "strict",
+ "577": "strict",
+ "578": "strict",
+ "579": "strict"
+ },
+ "List items": {
+ "231": "strict",
+ "232": "strict",
+ "233": "strict",
+ "234": "strict",
+ "235": "strict",
+ "236": "strict",
+ "237": "strict",
+ "238": "strict",
+ "239": "strict",
+ "240": "strict",
+ "241": "strict",
+ "242": "strict",
+ "243": "strict",
+ "244": "strict",
+ "245": "strict",
+ "246": "strict",
+ "247": "strict",
+ "248": "strict",
+ "249": "strict",
+ "250": "strict",
+ "251": "strict",
+ "252": "strict",
+ "253": "strict",
+ "254": "strict",
+ "255": "strict",
+ "256": "strict",
+ "257": "strict",
+ "258": "strict",
+ "259": "strict",
+ "260": "strict",
+ "261": "strict",
+ "262": "strict",
+ "263": "strict",
+ "264": "strict",
+ "265": "strict",
+ "266": "strict",
+ "267": "strict",
+ "268": "strict",
+ "269": "strict",
+ "270": "strict",
+ "271": "strict",
+ "272": "strict",
+ "273": "strict",
+ "274": "strict",
+ "275": "strict",
+ "276": "strict",
+ "277": "strict",
+ "278": "strict"
+ },
+ "Lists": {
+ "281": "strict",
+ "282": "strict",
+ "283": "strict",
+ "284": "strict",
+ "285": "strict",
+ "286": "strict",
+ "287": "strict",
+ "288": "strict",
+ "289": "strict",
+ "290": "strict",
+ "291": "strict",
+ "292": "strict",
+ "293": "strict",
+ "294": "strict",
+ "295": "strict",
+ "296": "strict",
+ "297": "strict",
+ "298": "strict",
+ "299": "strict",
+ "300": "strict",
+ "301": "strict",
+ "302": "strict",
+ "303": "strict",
+ "304": "strict",
+ "305": "strict",
+ "306": "strict"
+ },
+ "Paragraphs": {
+ "189": "strict",
+ "190": "strict",
+ "191": "strict",
+ "192": "strict",
+ "193": "strict",
+ "194": "strict",
+ "195": "strict",
+ "196": "strict"
+ },
+ "Precedence": {
+ "12": "strict"
+ },
+ "Raw HTML": {
+ "632": "strict",
+ "633": "strict",
+ "634": "strict",
+ "635": "strict",
+ "636": "strict",
+ "637": "strict",
+ "638": "strict",
+ "639": "strict",
+ "640": "strict",
+ "641": "strict",
+ "642": "strict",
+ "643": "strict",
+ "644": "loose",
+ "645": "loose",
+ "646": "strict",
+ "647": "strict",
+ "648": "strict",
+ "649": "strict",
+ "650": "strict",
+ "651": "strict"
+ },
+ "Setext headings": {
+ "50": "strict",
+ "51": "strict",
+ "52": "loose",
+ "53": "strict",
+ "54": "loose",
+ "55": "strict",
+ "56": "strict",
+ "57": "strict",
+ "58": "strict",
+ "59": "strict",
+ "60": "strict",
+ "61": "strict",
+ "62": "strict",
+ "63": "strict",
+ "64": "strict",
+ "65": "strict",
+ "66": "strict",
+ "67": "strict",
+ "68": "strict",
+ "69": "strict",
+ "70": "strict",
+ "71": "strict",
+ "72": "strict",
+ "73": "strict",
+ "74": "strict",
+ "75": "strict",
+ "76": "strict"
+ },
+ "Soft line breaks": {
+ "668": "strict",
+ "669": "strict"
+ },
+ "Strikethrough (extension)": {
+ "491": "strict",
+ "492": "strict"
+ },
+ "Tables (extension)": {
+ "198": "strict",
+ "199": "strict",
+ "200": "strict",
+ "201": "strict",
+ "202": "strict",
+ "203": "strict",
+ "204": "strict",
+ "205": "strict"
+ },
+ "Tabs": {
+ "1": "strict",
+ "2": "strict",
+ "3": "strict",
+ "4": "strict",
+ "5": "strict",
+ "6": "loose",
+ "7": "strict",
+ "8": "strict",
+ "9": "strict",
+ "10": "strict",
+ "11": "strict"
+ },
+ "Textual content": {
+ "670": "strict",
+ "671": "strict",
+ "672": "strict"
+ },
+ "Thematic breaks": {
+ "13": "strict",
+ "14": "strict",
+ "15": "strict",
+ "16": "strict",
+ "17": "strict",
+ "18": "strict",
+ "19": "strict",
+ "20": "strict",
+ "21": "strict",
+ "22": "strict",
+ "23": "strict",
+ "24": "strict",
+ "25": "strict",
+ "26": "strict",
+ "27": "strict",
+ "28": "strict",
+ "29": "strict",
+ "30": "strict",
+ "31": "strict"
+ }
+}
diff --git a/pkgs/markdown/tool/gfm_stats.txt b/pkgs/markdown/tool/gfm_stats.txt
new file mode 100644
index 0000000..06e240b
--- /dev/null
+++ b/pkgs/markdown/tool/gfm_stats.txt
@@ -0,0 +1,32 @@
+ 18 of 18 – 100.0% ATX headings
+ 19 of 19 – 100.0% Autolinks
+ 11 of 11 – 100.0% Autolinks (extension)
+ 13 of 13 – 100.0% Backslash escapes
+ 1 of 1 – 100.0% Blank lines
+ 25 of 25 – 100.0% Block quotes
+ 22 of 22 – 100.0% Code spans
+ 1 of 1 – 100.0% Disallowed Raw HTML (extension)
+ 131 of 131 – 100.0% Emphasis and strong emphasis
+ 17 of 17 – 100.0% Entity and numeric character references
+ 29 of 29 – 100.0% Fenced code blocks
+ 15 of 15 – 100.0% Hard line breaks
+ 43 of 43 – 100.0% HTML blocks
+ 22 of 22 – 100.0% Images
+ 12 of 12 – 100.0% Indented code blocks
+ 1 of 1 – 100.0% Inlines
+ 28 of 28 – 100.0% Link reference definitions
+ 87 of 87 – 100.0% Links
+ 48 of 48 – 100.0% List items
+ 26 of 26 – 100.0% Lists
+ 8 of 8 – 100.0% Paragraphs
+ 1 of 1 – 100.0% Precedence
+ 20 of 20 – 100.0% Raw HTML
+ 27 of 27 – 100.0% Setext headings
+ 2 of 2 – 100.0% Soft line breaks
+ 2 of 2 – 100.0% Strikethrough (extension)
+ 8 of 8 – 100.0% Tables (extension)
+ 11 of 11 – 100.0% Tabs
+ 3 of 3 – 100.0% Textual content
+ 19 of 19 – 100.0% Thematic breaks
+ 670 of 670 – 100.0% TOTAL
+ 660 of 670 – 98.5% TOTAL Strict
diff --git a/pkgs/markdown/tool/gfm_tests.json b/pkgs/markdown/tool/gfm_tests.json
new file mode 100644
index 0000000..e5e849c
--- /dev/null
+++ b/pkgs/markdown/tool/gfm_tests.json
@@ -0,0 +1,6076 @@
+[
+ {
+ "markdown": "\tfoo\tbaz\t\tbim\n",
+ "html": "<pre><code>foo\tbaz\t\tbim\n</code></pre>\n",
+ "example": 1,
+ "start_line": 368,
+ "end_line": 373,
+ "section": "Tabs",
+ "extensions": []
+ },
+ {
+ "markdown": " \tfoo\tbaz\t\tbim\n",
+ "html": "<pre><code>foo\tbaz\t\tbim\n</code></pre>\n",
+ "example": 2,
+ "start_line": 375,
+ "end_line": 380,
+ "section": "Tabs",
+ "extensions": []
+ },
+ {
+ "markdown": " a\ta\n \u1f50\ta\n",
+ "html": "<pre><code>a\ta\n\u1f50\ta\n</code></pre>\n",
+ "example": 3,
+ "start_line": 382,
+ "end_line": 389,
+ "section": "Tabs",
+ "extensions": []
+ },
+ {
+ "markdown": " - foo\n\n\tbar\n",
+ "html": "<ul>\n<li>\n<p>foo</p>\n<p>bar</p>\n</li>\n</ul>\n",
+ "example": 4,
+ "start_line": 395,
+ "end_line": 406,
+ "section": "Tabs",
+ "extensions": []
+ },
+ {
+ "markdown": "- foo\n\n\t\tbar\n",
+ "html": "<ul>\n<li>\n<p>foo</p>\n<pre><code> bar\n</code></pre>\n</li>\n</ul>\n",
+ "example": 5,
+ "start_line": 408,
+ "end_line": 420,
+ "section": "Tabs",
+ "extensions": []
+ },
+ {
+ "markdown": ">\t\tfoo\n",
+ "html": "<blockquote>\n<pre><code> foo\n</code></pre>\n</blockquote>\n",
+ "example": 6,
+ "start_line": 431,
+ "end_line": 438,
+ "section": "Tabs",
+ "extensions": []
+ },
+ {
+ "markdown": "-\t\tfoo\n",
+ "html": "<ul>\n<li>\n<pre><code> foo\n</code></pre>\n</li>\n</ul>\n",
+ "example": 7,
+ "start_line": 440,
+ "end_line": 449,
+ "section": "Tabs",
+ "extensions": []
+ },
+ {
+ "markdown": " foo\n\tbar\n",
+ "html": "<pre><code>foo\nbar\n</code></pre>\n",
+ "example": 8,
+ "start_line": 452,
+ "end_line": 459,
+ "section": "Tabs",
+ "extensions": []
+ },
+ {
+ "markdown": " - foo\n - bar\n\t - baz\n",
+ "html": "<ul>\n<li>foo\n<ul>\n<li>bar\n<ul>\n<li>baz</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n",
+ "example": 9,
+ "start_line": 461,
+ "end_line": 477,
+ "section": "Tabs",
+ "extensions": []
+ },
+ {
+ "markdown": "#\tFoo\n",
+ "html": "<h1>Foo</h1>\n",
+ "example": 10,
+ "start_line": 479,
+ "end_line": 483,
+ "section": "Tabs",
+ "extensions": []
+ },
+ {
+ "markdown": "*\t*\t*\t\n",
+ "html": "<hr />\n",
+ "example": 11,
+ "start_line": 485,
+ "end_line": 489,
+ "section": "Tabs",
+ "extensions": []
+ },
+ {
+ "markdown": "- `one\n- two`\n",
+ "html": "<ul>\n<li>`one</li>\n<li>two`</li>\n</ul>\n",
+ "example": 12,
+ "start_line": 512,
+ "end_line": 520,
+ "section": "Precedence",
+ "extensions": []
+ },
+ {
+ "markdown": "***\n---\n___\n",
+ "html": "<hr />\n<hr />\n<hr />\n",
+ "example": 13,
+ "start_line": 551,
+ "end_line": 559,
+ "section": "Thematic breaks",
+ "extensions": []
+ },
+ {
+ "markdown": "+++\n",
+ "html": "<p>+++</p>\n",
+ "example": 14,
+ "start_line": 564,
+ "end_line": 568,
+ "section": "Thematic breaks",
+ "extensions": []
+ },
+ {
+ "markdown": "===\n",
+ "html": "<p>===</p>\n",
+ "example": 15,
+ "start_line": 571,
+ "end_line": 575,
+ "section": "Thematic breaks",
+ "extensions": []
+ },
+ {
+ "markdown": "--\n**\n__\n",
+ "html": "<p>--\n**\n__</p>\n",
+ "example": 16,
+ "start_line": 580,
+ "end_line": 588,
+ "section": "Thematic breaks",
+ "extensions": []
+ },
+ {
+ "markdown": " ***\n ***\n ***\n",
+ "html": "<hr />\n<hr />\n<hr />\n",
+ "example": 17,
+ "start_line": 593,
+ "end_line": 601,
+ "section": "Thematic breaks",
+ "extensions": []
+ },
+ {
+ "markdown": " ***\n",
+ "html": "<pre><code>***\n</code></pre>\n",
+ "example": 18,
+ "start_line": 606,
+ "end_line": 611,
+ "section": "Thematic breaks",
+ "extensions": []
+ },
+ {
+ "markdown": "Foo\n ***\n",
+ "html": "<p>Foo\n***</p>\n",
+ "example": 19,
+ "start_line": 614,
+ "end_line": 620,
+ "section": "Thematic breaks",
+ "extensions": []
+ },
+ {
+ "markdown": "_____________________________________\n",
+ "html": "<hr />\n",
+ "example": 20,
+ "start_line": 625,
+ "end_line": 629,
+ "section": "Thematic breaks",
+ "extensions": []
+ },
+ {
+ "markdown": " - - -\n",
+ "html": "<hr />\n",
+ "example": 21,
+ "start_line": 634,
+ "end_line": 638,
+ "section": "Thematic breaks",
+ "extensions": []
+ },
+ {
+ "markdown": " ** * ** * ** * **\n",
+ "html": "<hr />\n",
+ "example": 22,
+ "start_line": 641,
+ "end_line": 645,
+ "section": "Thematic breaks",
+ "extensions": []
+ },
+ {
+ "markdown": "- - - -\n",
+ "html": "<hr />\n",
+ "example": 23,
+ "start_line": 648,
+ "end_line": 652,
+ "section": "Thematic breaks",
+ "extensions": []
+ },
+ {
+ "markdown": "- - - - \n",
+ "html": "<hr />\n",
+ "example": 24,
+ "start_line": 657,
+ "end_line": 661,
+ "section": "Thematic breaks",
+ "extensions": []
+ },
+ {
+ "markdown": "_ _ _ _ a\n\na------\n\n---a---\n",
+ "html": "<p>_ _ _ _ a</p>\n<p>a------</p>\n<p>---a---</p>\n",
+ "example": 25,
+ "start_line": 666,
+ "end_line": 676,
+ "section": "Thematic breaks",
+ "extensions": []
+ },
+ {
+ "markdown": " *-*\n",
+ "html": "<p><em>-</em></p>\n",
+ "example": 26,
+ "start_line": 682,
+ "end_line": 686,
+ "section": "Thematic breaks",
+ "extensions": []
+ },
+ {
+ "markdown": "- foo\n***\n- bar\n",
+ "html": "<ul>\n<li>foo</li>\n</ul>\n<hr />\n<ul>\n<li>bar</li>\n</ul>\n",
+ "example": 27,
+ "start_line": 691,
+ "end_line": 703,
+ "section": "Thematic breaks",
+ "extensions": []
+ },
+ {
+ "markdown": "Foo\n***\nbar\n",
+ "html": "<p>Foo</p>\n<hr />\n<p>bar</p>\n",
+ "example": 28,
+ "start_line": 708,
+ "end_line": 716,
+ "section": "Thematic breaks",
+ "extensions": []
+ },
+ {
+ "markdown": "Foo\n---\nbar\n",
+ "html": "<h2>Foo</h2>\n<p>bar</p>\n",
+ "example": 29,
+ "start_line": 725,
+ "end_line": 732,
+ "section": "Thematic breaks",
+ "extensions": []
+ },
+ {
+ "markdown": "* Foo\n* * *\n* Bar\n",
+ "html": "<ul>\n<li>Foo</li>\n</ul>\n<hr />\n<ul>\n<li>Bar</li>\n</ul>\n",
+ "example": 30,
+ "start_line": 738,
+ "end_line": 750,
+ "section": "Thematic breaks",
+ "extensions": []
+ },
+ {
+ "markdown": "- Foo\n- * * *\n",
+ "html": "<ul>\n<li>Foo</li>\n<li>\n<hr />\n</li>\n</ul>\n",
+ "example": 31,
+ "start_line": 755,
+ "end_line": 765,
+ "section": "Thematic breaks",
+ "extensions": []
+ },
+ {
+ "markdown": "# foo\n## foo\n### foo\n#### foo\n##### foo\n###### foo\n",
+ "html": "<h1>foo</h1>\n<h2>foo</h2>\n<h3>foo</h3>\n<h4>foo</h4>\n<h5>foo</h5>\n<h6>foo</h6>\n",
+ "example": 32,
+ "start_line": 784,
+ "end_line": 798,
+ "section": "ATX headings",
+ "extensions": []
+ },
+ {
+ "markdown": "####### foo\n",
+ "html": "<p>####### foo</p>\n",
+ "example": 33,
+ "start_line": 803,
+ "end_line": 807,
+ "section": "ATX headings",
+ "extensions": []
+ },
+ {
+ "markdown": "#5 bolt\n\n#hashtag\n",
+ "html": "<p>#5 bolt</p>\n<p>#hashtag</p>\n",
+ "example": 34,
+ "start_line": 818,
+ "end_line": 825,
+ "section": "ATX headings",
+ "extensions": []
+ },
+ {
+ "markdown": "\\## foo\n",
+ "html": "<p>## foo</p>\n",
+ "example": 35,
+ "start_line": 830,
+ "end_line": 834,
+ "section": "ATX headings",
+ "extensions": []
+ },
+ {
+ "markdown": "# foo *bar* \\*baz\\*\n",
+ "html": "<h1>foo <em>bar</em> *baz*</h1>\n",
+ "example": 36,
+ "start_line": 839,
+ "end_line": 843,
+ "section": "ATX headings",
+ "extensions": []
+ },
+ {
+ "markdown": "# foo \n",
+ "html": "<h1>foo</h1>\n",
+ "example": 37,
+ "start_line": 848,
+ "end_line": 852,
+ "section": "ATX headings",
+ "extensions": []
+ },
+ {
+ "markdown": " ### foo\n ## foo\n # foo\n",
+ "html": "<h3>foo</h3>\n<h2>foo</h2>\n<h1>foo</h1>\n",
+ "example": 38,
+ "start_line": 857,
+ "end_line": 865,
+ "section": "ATX headings",
+ "extensions": []
+ },
+ {
+ "markdown": " # foo\n",
+ "html": "<pre><code># foo\n</code></pre>\n",
+ "example": 39,
+ "start_line": 870,
+ "end_line": 875,
+ "section": "ATX headings",
+ "extensions": []
+ },
+ {
+ "markdown": "foo\n # bar\n",
+ "html": "<p>foo\n# bar</p>\n",
+ "example": 40,
+ "start_line": 878,
+ "end_line": 884,
+ "section": "ATX headings",
+ "extensions": []
+ },
+ {
+ "markdown": "## foo ##\n ### bar ###\n",
+ "html": "<h2>foo</h2>\n<h3>bar</h3>\n",
+ "example": 41,
+ "start_line": 889,
+ "end_line": 895,
+ "section": "ATX headings",
+ "extensions": []
+ },
+ {
+ "markdown": "# foo ##################################\n##### foo ##\n",
+ "html": "<h1>foo</h1>\n<h5>foo</h5>\n",
+ "example": 42,
+ "start_line": 900,
+ "end_line": 906,
+ "section": "ATX headings",
+ "extensions": []
+ },
+ {
+ "markdown": "### foo ### \n",
+ "html": "<h3>foo</h3>\n",
+ "example": 43,
+ "start_line": 911,
+ "end_line": 915,
+ "section": "ATX headings",
+ "extensions": []
+ },
+ {
+ "markdown": "### foo ### b\n",
+ "html": "<h3>foo ### b</h3>\n",
+ "example": 44,
+ "start_line": 922,
+ "end_line": 926,
+ "section": "ATX headings",
+ "extensions": []
+ },
+ {
+ "markdown": "# foo#\n",
+ "html": "<h1>foo#</h1>\n",
+ "example": 45,
+ "start_line": 931,
+ "end_line": 935,
+ "section": "ATX headings",
+ "extensions": []
+ },
+ {
+ "markdown": "### foo \\###\n## foo #\\##\n# foo \\#\n",
+ "html": "<h3>foo ###</h3>\n<h2>foo ###</h2>\n<h1>foo #</h1>\n",
+ "example": 46,
+ "start_line": 941,
+ "end_line": 949,
+ "section": "ATX headings",
+ "extensions": []
+ },
+ {
+ "markdown": "****\n## foo\n****\n",
+ "html": "<hr />\n<h2>foo</h2>\n<hr />\n",
+ "example": 47,
+ "start_line": 955,
+ "end_line": 963,
+ "section": "ATX headings",
+ "extensions": []
+ },
+ {
+ "markdown": "Foo bar\n# baz\nBar foo\n",
+ "html": "<p>Foo bar</p>\n<h1>baz</h1>\n<p>Bar foo</p>\n",
+ "example": 48,
+ "start_line": 966,
+ "end_line": 974,
+ "section": "ATX headings",
+ "extensions": []
+ },
+ {
+ "markdown": "## \n#\n### ###\n",
+ "html": "<h2></h2>\n<h1></h1>\n<h3></h3>\n",
+ "example": 49,
+ "start_line": 979,
+ "end_line": 987,
+ "section": "ATX headings",
+ "extensions": []
+ },
+ {
+ "markdown": "Foo *bar*\n=========\n\nFoo *bar*\n---------\n",
+ "html": "<h1>Foo <em>bar</em></h1>\n<h2>Foo <em>bar</em></h2>\n",
+ "example": 50,
+ "start_line": 1019,
+ "end_line": 1028,
+ "section": "Setext headings",
+ "extensions": []
+ },
+ {
+ "markdown": "Foo *bar\nbaz*\n====\n",
+ "html": "<h1>Foo <em>bar\nbaz</em></h1>\n",
+ "example": 51,
+ "start_line": 1033,
+ "end_line": 1040,
+ "section": "Setext headings",
+ "extensions": []
+ },
+ {
+ "markdown": " Foo *bar\nbaz*\t\n====\n",
+ "html": "<h1>Foo <em>bar\nbaz</em></h1>\n",
+ "example": 52,
+ "start_line": 1047,
+ "end_line": 1054,
+ "section": "Setext headings",
+ "extensions": []
+ },
+ {
+ "markdown": "Foo\n-------------------------\n\nFoo\n=\n",
+ "html": "<h2>Foo</h2>\n<h1>Foo</h1>\n",
+ "example": 53,
+ "start_line": 1059,
+ "end_line": 1068,
+ "section": "Setext headings",
+ "extensions": []
+ },
+ {
+ "markdown": " Foo\n---\n\n Foo\n-----\n\n Foo\n ===\n",
+ "html": "<h2>Foo</h2>\n<h2>Foo</h2>\n<h1>Foo</h1>\n",
+ "example": 54,
+ "start_line": 1074,
+ "end_line": 1087,
+ "section": "Setext headings",
+ "extensions": []
+ },
+ {
+ "markdown": " Foo\n ---\n\n Foo\n---\n",
+ "html": "<pre><code>Foo\n---\n\nFoo\n</code></pre>\n<hr />\n",
+ "example": 55,
+ "start_line": 1092,
+ "end_line": 1105,
+ "section": "Setext headings",
+ "extensions": []
+ },
+ {
+ "markdown": "Foo\n ---- \n",
+ "html": "<h2>Foo</h2>\n",
+ "example": 56,
+ "start_line": 1111,
+ "end_line": 1116,
+ "section": "Setext headings",
+ "extensions": []
+ },
+ {
+ "markdown": "Foo\n ---\n",
+ "html": "<p>Foo\n---</p>\n",
+ "example": 57,
+ "start_line": 1121,
+ "end_line": 1127,
+ "section": "Setext headings",
+ "extensions": []
+ },
+ {
+ "markdown": "Foo\n= =\n\nFoo\n--- -\n",
+ "html": "<p>Foo\n= =</p>\n<p>Foo</p>\n<hr />\n",
+ "example": 58,
+ "start_line": 1132,
+ "end_line": 1143,
+ "section": "Setext headings",
+ "extensions": []
+ },
+ {
+ "markdown": "Foo \n-----\n",
+ "html": "<h2>Foo</h2>\n",
+ "example": 59,
+ "start_line": 1148,
+ "end_line": 1153,
+ "section": "Setext headings",
+ "extensions": []
+ },
+ {
+ "markdown": "Foo\\\n----\n",
+ "html": "<h2>Foo\\</h2>\n",
+ "example": 60,
+ "start_line": 1158,
+ "end_line": 1163,
+ "section": "Setext headings",
+ "extensions": []
+ },
+ {
+ "markdown": "`Foo\n----\n`\n\n<a title=\"a lot\n---\nof dashes\"/>\n",
+ "html": "<h2>`Foo</h2>\n<p>`</p>\n<h2><a title="a lot</h2>\n<p>of dashes"/></p>\n",
+ "example": 61,
+ "start_line": 1169,
+ "end_line": 1182,
+ "section": "Setext headings",
+ "extensions": []
+ },
+ {
+ "markdown": "> Foo\n---\n",
+ "html": "<blockquote>\n<p>Foo</p>\n</blockquote>\n<hr />\n",
+ "example": 62,
+ "start_line": 1188,
+ "end_line": 1196,
+ "section": "Setext headings",
+ "extensions": []
+ },
+ {
+ "markdown": "> foo\nbar\n===\n",
+ "html": "<blockquote>\n<p>foo\nbar\n===</p>\n</blockquote>\n",
+ "example": 63,
+ "start_line": 1199,
+ "end_line": 1209,
+ "section": "Setext headings",
+ "extensions": []
+ },
+ {
+ "markdown": "- Foo\n---\n",
+ "html": "<ul>\n<li>Foo</li>\n</ul>\n<hr />\n",
+ "example": 64,
+ "start_line": 1212,
+ "end_line": 1220,
+ "section": "Setext headings",
+ "extensions": []
+ },
+ {
+ "markdown": "Foo\nBar\n---\n",
+ "html": "<h2>Foo\nBar</h2>\n",
+ "example": 65,
+ "start_line": 1227,
+ "end_line": 1234,
+ "section": "Setext headings",
+ "extensions": []
+ },
+ {
+ "markdown": "---\nFoo\n---\nBar\n---\nBaz\n",
+ "html": "<hr />\n<h2>Foo</h2>\n<h2>Bar</h2>\n<p>Baz</p>\n",
+ "example": 66,
+ "start_line": 1240,
+ "end_line": 1252,
+ "section": "Setext headings",
+ "extensions": []
+ },
+ {
+ "markdown": "\n====\n",
+ "html": "<p>====</p>\n",
+ "example": 67,
+ "start_line": 1257,
+ "end_line": 1262,
+ "section": "Setext headings",
+ "extensions": []
+ },
+ {
+ "markdown": "---\n---\n",
+ "html": "<hr />\n<hr />\n",
+ "example": 68,
+ "start_line": 1269,
+ "end_line": 1275,
+ "section": "Setext headings",
+ "extensions": []
+ },
+ {
+ "markdown": "- foo\n-----\n",
+ "html": "<ul>\n<li>foo</li>\n</ul>\n<hr />\n",
+ "example": 69,
+ "start_line": 1278,
+ "end_line": 1286,
+ "section": "Setext headings",
+ "extensions": []
+ },
+ {
+ "markdown": " foo\n---\n",
+ "html": "<pre><code>foo\n</code></pre>\n<hr />\n",
+ "example": 70,
+ "start_line": 1289,
+ "end_line": 1296,
+ "section": "Setext headings",
+ "extensions": []
+ },
+ {
+ "markdown": "> foo\n-----\n",
+ "html": "<blockquote>\n<p>foo</p>\n</blockquote>\n<hr />\n",
+ "example": 71,
+ "start_line": 1299,
+ "end_line": 1307,
+ "section": "Setext headings",
+ "extensions": []
+ },
+ {
+ "markdown": "\\> foo\n------\n",
+ "html": "<h2>> foo</h2>\n",
+ "example": 72,
+ "start_line": 1313,
+ "end_line": 1318,
+ "section": "Setext headings",
+ "extensions": []
+ },
+ {
+ "markdown": "Foo\n\nbar\n---\nbaz\n",
+ "html": "<p>Foo</p>\n<h2>bar</h2>\n<p>baz</p>\n",
+ "example": 73,
+ "start_line": 1344,
+ "end_line": 1354,
+ "section": "Setext headings",
+ "extensions": []
+ },
+ {
+ "markdown": "Foo\nbar\n\n---\n\nbaz\n",
+ "html": "<p>Foo\nbar</p>\n<hr />\n<p>baz</p>\n",
+ "example": 74,
+ "start_line": 1360,
+ "end_line": 1372,
+ "section": "Setext headings",
+ "extensions": []
+ },
+ {
+ "markdown": "Foo\nbar\n* * *\nbaz\n",
+ "html": "<p>Foo\nbar</p>\n<hr />\n<p>baz</p>\n",
+ "example": 75,
+ "start_line": 1378,
+ "end_line": 1388,
+ "section": "Setext headings",
+ "extensions": []
+ },
+ {
+ "markdown": "Foo\nbar\n\\---\nbaz\n",
+ "html": "<p>Foo\nbar\n---\nbaz</p>\n",
+ "example": 76,
+ "start_line": 1393,
+ "end_line": 1403,
+ "section": "Setext headings",
+ "extensions": []
+ },
+ {
+ "markdown": " a simple\n indented code block\n",
+ "html": "<pre><code>a simple\n indented code block\n</code></pre>\n",
+ "example": 77,
+ "start_line": 1421,
+ "end_line": 1428,
+ "section": "Indented code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": " - foo\n\n bar\n",
+ "html": "<ul>\n<li>\n<p>foo</p>\n<p>bar</p>\n</li>\n</ul>\n",
+ "example": 78,
+ "start_line": 1435,
+ "end_line": 1446,
+ "section": "Indented code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "1. foo\n\n - bar\n",
+ "html": "<ol>\n<li>\n<p>foo</p>\n<ul>\n<li>bar</li>\n</ul>\n</li>\n</ol>\n",
+ "example": 79,
+ "start_line": 1449,
+ "end_line": 1462,
+ "section": "Indented code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": " <a/>\n *hi*\n\n - one\n",
+ "html": "<pre><code><a/>\n*hi*\n\n- one\n</code></pre>\n",
+ "example": 80,
+ "start_line": 1469,
+ "end_line": 1480,
+ "section": "Indented code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": " chunk1\n\n chunk2\n \n \n \n chunk3\n",
+ "html": "<pre><code>chunk1\n\nchunk2\n\n\n\nchunk3\n</code></pre>\n",
+ "example": 81,
+ "start_line": 1485,
+ "end_line": 1502,
+ "section": "Indented code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": " chunk1\n \n chunk2\n",
+ "html": "<pre><code>chunk1\n \n chunk2\n</code></pre>\n",
+ "example": 82,
+ "start_line": 1508,
+ "end_line": 1517,
+ "section": "Indented code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "Foo\n bar\n\n",
+ "html": "<p>Foo\nbar</p>\n",
+ "example": 83,
+ "start_line": 1523,
+ "end_line": 1530,
+ "section": "Indented code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": " foo\nbar\n",
+ "html": "<pre><code>foo\n</code></pre>\n<p>bar</p>\n",
+ "example": 84,
+ "start_line": 1537,
+ "end_line": 1544,
+ "section": "Indented code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "# Heading\n foo\nHeading\n------\n foo\n----\n",
+ "html": "<h1>Heading</h1>\n<pre><code>foo\n</code></pre>\n<h2>Heading</h2>\n<pre><code>foo\n</code></pre>\n<hr />\n",
+ "example": 85,
+ "start_line": 1550,
+ "end_line": 1565,
+ "section": "Indented code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": " foo\n bar\n",
+ "html": "<pre><code> foo\nbar\n</code></pre>\n",
+ "example": 86,
+ "start_line": 1570,
+ "end_line": 1577,
+ "section": "Indented code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "\n \n foo\n \n\n",
+ "html": "<pre><code>foo\n</code></pre>\n",
+ "example": 87,
+ "start_line": 1583,
+ "end_line": 1592,
+ "section": "Indented code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": " foo \n",
+ "html": "<pre><code>foo \n</code></pre>\n",
+ "example": 88,
+ "start_line": 1597,
+ "end_line": 1602,
+ "section": "Indented code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "```\n<\n >\n```\n",
+ "html": "<pre><code><\n >\n</code></pre>\n",
+ "example": 89,
+ "start_line": 1652,
+ "end_line": 1661,
+ "section": "Fenced code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "~~~\n<\n >\n~~~\n",
+ "html": "<pre><code><\n >\n</code></pre>\n",
+ "example": 90,
+ "start_line": 1666,
+ "end_line": 1675,
+ "section": "Fenced code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "``\nfoo\n``\n",
+ "html": "<p><code>foo</code></p>\n",
+ "example": 91,
+ "start_line": 1679,
+ "end_line": 1685,
+ "section": "Fenced code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "```\naaa\n~~~\n```\n",
+ "html": "<pre><code>aaa\n~~~\n</code></pre>\n",
+ "example": 92,
+ "start_line": 1690,
+ "end_line": 1699,
+ "section": "Fenced code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "~~~\naaa\n```\n~~~\n",
+ "html": "<pre><code>aaa\n```\n</code></pre>\n",
+ "example": 93,
+ "start_line": 1702,
+ "end_line": 1711,
+ "section": "Fenced code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "````\naaa\n```\n``````\n",
+ "html": "<pre><code>aaa\n```\n</code></pre>\n",
+ "example": 94,
+ "start_line": 1716,
+ "end_line": 1725,
+ "section": "Fenced code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "~~~~\naaa\n~~~\n~~~~\n",
+ "html": "<pre><code>aaa\n~~~\n</code></pre>\n",
+ "example": 95,
+ "start_line": 1728,
+ "end_line": 1737,
+ "section": "Fenced code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "```\n",
+ "html": "<pre><code></code></pre>\n",
+ "example": 96,
+ "start_line": 1743,
+ "end_line": 1747,
+ "section": "Fenced code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "`````\n\n```\naaa\n",
+ "html": "<pre><code>\n```\naaa\n</code></pre>\n",
+ "example": 97,
+ "start_line": 1750,
+ "end_line": 1760,
+ "section": "Fenced code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "> ```\n> aaa\n\nbbb\n",
+ "html": "<blockquote>\n<pre><code>aaa\n</code></pre>\n</blockquote>\n<p>bbb</p>\n",
+ "example": 98,
+ "start_line": 1763,
+ "end_line": 1774,
+ "section": "Fenced code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "```\n\n \n```\n",
+ "html": "<pre><code>\n \n</code></pre>\n",
+ "example": 99,
+ "start_line": 1779,
+ "end_line": 1788,
+ "section": "Fenced code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "```\n```\n",
+ "html": "<pre><code></code></pre>\n",
+ "example": 100,
+ "start_line": 1793,
+ "end_line": 1798,
+ "section": "Fenced code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": " ```\n aaa\naaa\n```\n",
+ "html": "<pre><code>aaa\naaa\n</code></pre>\n",
+ "example": 101,
+ "start_line": 1805,
+ "end_line": 1814,
+ "section": "Fenced code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": " ```\naaa\n aaa\naaa\n ```\n",
+ "html": "<pre><code>aaa\naaa\naaa\n</code></pre>\n",
+ "example": 102,
+ "start_line": 1817,
+ "end_line": 1828,
+ "section": "Fenced code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": " ```\n aaa\n aaa\n aaa\n ```\n",
+ "html": "<pre><code>aaa\n aaa\naaa\n</code></pre>\n",
+ "example": 103,
+ "start_line": 1831,
+ "end_line": 1842,
+ "section": "Fenced code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": " ```\n aaa\n ```\n",
+ "html": "<pre><code>```\naaa\n```\n</code></pre>\n",
+ "example": 104,
+ "start_line": 1847,
+ "end_line": 1856,
+ "section": "Fenced code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "```\naaa\n ```\n",
+ "html": "<pre><code>aaa\n</code></pre>\n",
+ "example": 105,
+ "start_line": 1862,
+ "end_line": 1869,
+ "section": "Fenced code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": " ```\naaa\n ```\n",
+ "html": "<pre><code>aaa\n</code></pre>\n",
+ "example": 106,
+ "start_line": 1872,
+ "end_line": 1879,
+ "section": "Fenced code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "```\naaa\n ```\n",
+ "html": "<pre><code>aaa\n ```\n</code></pre>\n",
+ "example": 107,
+ "start_line": 1884,
+ "end_line": 1892,
+ "section": "Fenced code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "``` ```\naaa\n",
+ "html": "<p><code> </code>\naaa</p>\n",
+ "example": 108,
+ "start_line": 1898,
+ "end_line": 1904,
+ "section": "Fenced code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "~~~~~~\naaa\n~~~ ~~\n",
+ "html": "<pre><code>aaa\n~~~ ~~\n</code></pre>\n",
+ "example": 109,
+ "start_line": 1907,
+ "end_line": 1915,
+ "section": "Fenced code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "foo\n```\nbar\n```\nbaz\n",
+ "html": "<p>foo</p>\n<pre><code>bar\n</code></pre>\n<p>baz</p>\n",
+ "example": 110,
+ "start_line": 1921,
+ "end_line": 1932,
+ "section": "Fenced code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "foo\n---\n~~~\nbar\n~~~\n# baz\n",
+ "html": "<h2>foo</h2>\n<pre><code>bar\n</code></pre>\n<h1>baz</h1>\n",
+ "example": 111,
+ "start_line": 1938,
+ "end_line": 1950,
+ "section": "Fenced code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "```ruby\ndef foo(x)\n return 3\nend\n```\n",
+ "html": "<pre><code class=\"language-ruby\">def foo(x)\n return 3\nend\n</code></pre>\n",
+ "example": 112,
+ "start_line": 1960,
+ "end_line": 1971,
+ "section": "Fenced code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "~~~~ ruby startline=3 $%@#$\ndef foo(x)\n return 3\nend\n~~~~~~~\n",
+ "html": "<pre><code class=\"language-ruby\">def foo(x)\n return 3\nend\n</code></pre>\n",
+ "example": 113,
+ "start_line": 1974,
+ "end_line": 1985,
+ "section": "Fenced code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "````;\n````\n",
+ "html": "<pre><code class=\"language-;\"></code></pre>\n",
+ "example": 114,
+ "start_line": 1988,
+ "end_line": 1993,
+ "section": "Fenced code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "``` aa ```\nfoo\n",
+ "html": "<p><code>aa</code>\nfoo</p>\n",
+ "example": 115,
+ "start_line": 1998,
+ "end_line": 2004,
+ "section": "Fenced code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "~~~ aa ``` ~~~\nfoo\n~~~\n",
+ "html": "<pre><code class=\"language-aa\">foo\n</code></pre>\n",
+ "example": 116,
+ "start_line": 2009,
+ "end_line": 2016,
+ "section": "Fenced code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "```\n``` aaa\n```\n",
+ "html": "<pre><code>``` aaa\n</code></pre>\n",
+ "example": 117,
+ "start_line": 2021,
+ "end_line": 2028,
+ "section": "Fenced code blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<table><tr><td>\n<pre>\n**Hello**,\n\n_world_.\n</pre>\n</td></tr></table>\n",
+ "html": "<table><tr><td>\n<pre>\n**Hello**,\n<p><em>world</em>.\n</pre></p>\n</td></tr></table>\n",
+ "example": 118,
+ "start_line": 2100,
+ "end_line": 2115,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<table>\n <tr>\n <td>\n hi\n </td>\n </tr>\n</table>\n\nokay.\n",
+ "html": "<table>\n <tr>\n <td>\n hi\n </td>\n </tr>\n</table>\n<p>okay.</p>\n",
+ "example": 119,
+ "start_line": 2129,
+ "end_line": 2148,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": " <div>\n *hello*\n <foo><a>\n",
+ "html": " <div>\n *hello*\n <foo><a>\n",
+ "example": 120,
+ "start_line": 2151,
+ "end_line": 2159,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "</div>\n*foo*\n",
+ "html": "</div>\n*foo*\n",
+ "example": 121,
+ "start_line": 2164,
+ "end_line": 2170,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<DIV CLASS=\"foo\">\n\n*Markdown*\n\n</DIV>\n",
+ "html": "<DIV CLASS=\"foo\">\n<p><em>Markdown</em></p>\n</DIV>\n",
+ "example": 122,
+ "start_line": 2175,
+ "end_line": 2185,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<div id=\"foo\"\n class=\"bar\">\n</div>\n",
+ "html": "<div id=\"foo\"\n class=\"bar\">\n</div>\n",
+ "example": 123,
+ "start_line": 2191,
+ "end_line": 2199,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<div id=\"foo\" class=\"bar\n baz\">\n</div>\n",
+ "html": "<div id=\"foo\" class=\"bar\n baz\">\n</div>\n",
+ "example": 124,
+ "start_line": 2202,
+ "end_line": 2210,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<div>\n*foo*\n\n*bar*\n",
+ "html": "<div>\n*foo*\n<p><em>bar</em></p>\n",
+ "example": 125,
+ "start_line": 2214,
+ "end_line": 2223,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<div id=\"foo\"\n*hi*\n",
+ "html": "<div id=\"foo\"\n*hi*\n",
+ "example": 126,
+ "start_line": 2230,
+ "end_line": 2236,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<div class\nfoo\n",
+ "html": "<div class\nfoo\n",
+ "example": 127,
+ "start_line": 2239,
+ "end_line": 2245,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<div *???-&&&-<---\n*foo*\n",
+ "html": "<div *???-&&&-<---\n*foo*\n",
+ "example": 128,
+ "start_line": 2251,
+ "end_line": 2257,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<div><a href=\"bar\">*foo*</a></div>\n",
+ "html": "<div><a href=\"bar\">*foo*</a></div>\n",
+ "example": 129,
+ "start_line": 2263,
+ "end_line": 2267,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<table><tr><td>\nfoo\n</td></tr></table>\n",
+ "html": "<table><tr><td>\nfoo\n</td></tr></table>\n",
+ "example": 130,
+ "start_line": 2270,
+ "end_line": 2278,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<div></div>\n``` c\nint x = 33;\n```\n",
+ "html": "<div></div>\n``` c\nint x = 33;\n```\n",
+ "example": 131,
+ "start_line": 2287,
+ "end_line": 2297,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<a href=\"foo\">\n*bar*\n</a>\n",
+ "html": "<a href=\"foo\">\n*bar*\n</a>\n",
+ "example": 132,
+ "start_line": 2304,
+ "end_line": 2312,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<Warning>\n*bar*\n</Warning>\n",
+ "html": "<Warning>\n*bar*\n</Warning>\n",
+ "example": 133,
+ "start_line": 2317,
+ "end_line": 2325,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<i class=\"foo\">\n*bar*\n</i>\n",
+ "html": "<i class=\"foo\">\n*bar*\n</i>\n",
+ "example": 134,
+ "start_line": 2328,
+ "end_line": 2336,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "</ins>\n*bar*\n",
+ "html": "</ins>\n*bar*\n",
+ "example": 135,
+ "start_line": 2339,
+ "end_line": 2345,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<del>\n*foo*\n</del>\n",
+ "html": "<del>\n*foo*\n</del>\n",
+ "example": 136,
+ "start_line": 2354,
+ "end_line": 2362,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<del>\n\n*foo*\n\n</del>\n",
+ "html": "<del>\n<p><em>foo</em></p>\n</del>\n",
+ "example": 137,
+ "start_line": 2369,
+ "end_line": 2379,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<del>*foo*</del>\n",
+ "html": "<p><del><em>foo</em></del></p>\n",
+ "example": 138,
+ "start_line": 2387,
+ "end_line": 2391,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<pre language=\"haskell\"><code>\nimport Text.HTML.TagSoup\n\nmain :: IO ()\nmain = print $ parseTags tags\n</code></pre>\nokay\n",
+ "html": "<pre language=\"haskell\"><code>\nimport Text.HTML.TagSoup\n\nmain :: IO ()\nmain = print $ parseTags tags\n</code></pre>\n<p>okay</p>\n",
+ "example": 139,
+ "start_line": 2403,
+ "end_line": 2419,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<script type=\"text/javascript\">\n// JavaScript example\n\ndocument.getElementById(\"demo\").innerHTML = \"Hello JavaScript!\";\n</script>\nokay\n",
+ "html": "<script type=\"text/javascript\">\n// JavaScript example\n\ndocument.getElementById(\"demo\").innerHTML = \"Hello JavaScript!\";\n</script>\n<p>okay</p>\n",
+ "example": 140,
+ "start_line": 2424,
+ "end_line": 2438,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<style\n type=\"text/css\">\nh1 {color:red;}\n\np {color:blue;}\n</style>\nokay\n",
+ "html": "<style\n type=\"text/css\">\nh1 {color:red;}\n\np {color:blue;}\n</style>\n<p>okay</p>\n",
+ "example": 141,
+ "start_line": 2443,
+ "end_line": 2459,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<style\n type=\"text/css\">\n\nfoo\n",
+ "html": "<style\n type=\"text/css\">\n\nfoo\n",
+ "example": 142,
+ "start_line": 2466,
+ "end_line": 2476,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "> <div>\n> foo\n\nbar\n",
+ "html": "<blockquote>\n<div>\nfoo\n</blockquote>\n<p>bar</p>\n",
+ "example": 143,
+ "start_line": 2479,
+ "end_line": 2490,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "- <div>\n- foo\n",
+ "html": "<ul>\n<li>\n<div>\n</li>\n<li>foo</li>\n</ul>\n",
+ "example": 144,
+ "start_line": 2493,
+ "end_line": 2503,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<style>p{color:red;}</style>\n*foo*\n",
+ "html": "<style>p{color:red;}</style>\n<p><em>foo</em></p>\n",
+ "example": 145,
+ "start_line": 2508,
+ "end_line": 2514,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<!-- foo -->*bar*\n*baz*\n",
+ "html": "<!-- foo -->*bar*\n<p><em>baz</em></p>\n",
+ "example": 146,
+ "start_line": 2517,
+ "end_line": 2523,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<script>\nfoo\n</script>1. *bar*\n",
+ "html": "<script>\nfoo\n</script>1. *bar*\n",
+ "example": 147,
+ "start_line": 2529,
+ "end_line": 2537,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<!-- Foo\n\nbar\n baz -->\nokay\n",
+ "html": "<!-- Foo\n\nbar\n baz -->\n<p>okay</p>\n",
+ "example": 148,
+ "start_line": 2542,
+ "end_line": 2554,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<?php\n\n echo '>';\n\n?>\nokay\n",
+ "html": "<?php\n\n echo '>';\n\n?>\n<p>okay</p>\n",
+ "example": 149,
+ "start_line": 2560,
+ "end_line": 2574,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<!DOCTYPE html>\n",
+ "html": "<!DOCTYPE html>\n",
+ "example": 150,
+ "start_line": 2579,
+ "end_line": 2583,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<![CDATA[\nfunction matchwo(a,b)\n{\n if (a < b && a < 0) then {\n return 1;\n\n } else {\n\n return 0;\n }\n}\n]]>\nokay\n",
+ "html": "<![CDATA[\nfunction matchwo(a,b)\n{\n if (a < b && a < 0) then {\n return 1;\n\n } else {\n\n return 0;\n }\n}\n]]>\n<p>okay</p>\n",
+ "example": 151,
+ "start_line": 2588,
+ "end_line": 2616,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": " <!-- foo -->\n\n <!-- foo -->\n",
+ "html": " <!-- foo -->\n<pre><code><!-- foo -->\n</code></pre>\n",
+ "example": 152,
+ "start_line": 2621,
+ "end_line": 2629,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": " <div>\n\n <div>\n",
+ "html": " <div>\n<pre><code><div>\n</code></pre>\n",
+ "example": 153,
+ "start_line": 2632,
+ "end_line": 2640,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "Foo\n<div>\nbar\n</div>\n",
+ "html": "<p>Foo</p>\n<div>\nbar\n</div>\n",
+ "example": 154,
+ "start_line": 2646,
+ "end_line": 2656,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<div>\nbar\n</div>\n*foo*\n",
+ "html": "<div>\nbar\n</div>\n*foo*\n",
+ "example": 155,
+ "start_line": 2663,
+ "end_line": 2673,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "Foo\n<a href=\"bar\">\nbaz\n",
+ "html": "<p>Foo\n<a href=\"bar\">\nbaz</p>\n",
+ "example": 156,
+ "start_line": 2678,
+ "end_line": 2686,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<div>\n\n*Emphasized* text.\n\n</div>\n",
+ "html": "<div>\n<p><em>Emphasized</em> text.</p>\n</div>\n",
+ "example": 157,
+ "start_line": 2719,
+ "end_line": 2729,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<div>\n*Emphasized* text.\n</div>\n",
+ "html": "<div>\n*Emphasized* text.\n</div>\n",
+ "example": 158,
+ "start_line": 2732,
+ "end_line": 2740,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<table>\n\n<tr>\n\n<td>\nHi\n</td>\n\n</tr>\n\n</table>\n",
+ "html": "<table>\n<tr>\n<td>\nHi\n</td>\n</tr>\n</table>\n",
+ "example": 159,
+ "start_line": 2754,
+ "end_line": 2774,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "<table>\n\n <tr>\n\n <td>\n Hi\n </td>\n\n </tr>\n\n</table>\n",
+ "html": "<table>\n <tr>\n<pre><code><td>\n Hi\n</td>\n</code></pre>\n </tr>\n</table>\n",
+ "example": 160,
+ "start_line": 2781,
+ "end_line": 2802,
+ "section": "HTML blocks",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo]: /url \"title\"\n\n[foo]\n",
+ "html": "<p><a href=\"/url\" title=\"title\">foo</a></p>\n",
+ "example": 161,
+ "start_line": 2829,
+ "end_line": 2835,
+ "section": "Link reference definitions",
+ "extensions": []
+ },
+ {
+ "markdown": " [foo]: \n /url \n 'the title' \n\n[foo]\n",
+ "html": "<p><a href=\"/url\" title=\"the title\">foo</a></p>\n",
+ "example": 162,
+ "start_line": 2838,
+ "end_line": 2846,
+ "section": "Link reference definitions",
+ "extensions": []
+ },
+ {
+ "markdown": "[Foo*bar\\]]:my_(url) 'title (with parens)'\n\n[Foo*bar\\]]\n",
+ "html": "<p><a href=\"my_(url)\" title=\"title (with parens)\">Foo*bar]</a></p>\n",
+ "example": 163,
+ "start_line": 2849,
+ "end_line": 2855,
+ "section": "Link reference definitions",
+ "extensions": []
+ },
+ {
+ "markdown": "[Foo bar]:\n<my url>\n'title'\n\n[Foo bar]\n",
+ "html": "<p><a href=\"my%20url\" title=\"title\">Foo bar</a></p>\n",
+ "example": 164,
+ "start_line": 2858,
+ "end_line": 2866,
+ "section": "Link reference definitions",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo]: /url '\ntitle\nline1\nline2\n'\n\n[foo]\n",
+ "html": "<p><a href=\"/url\" title=\"\ntitle\nline1\nline2\n\">foo</a></p>\n",
+ "example": 165,
+ "start_line": 2871,
+ "end_line": 2885,
+ "section": "Link reference definitions",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo]: /url 'title\n\nwith blank line'\n\n[foo]\n",
+ "html": "<p>[foo]: /url 'title</p>\n<p>with blank line'</p>\n<p>[foo]</p>\n",
+ "example": 166,
+ "start_line": 2890,
+ "end_line": 2900,
+ "section": "Link reference definitions",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo]:\n/url\n\n[foo]\n",
+ "html": "<p><a href=\"/url\">foo</a></p>\n",
+ "example": 167,
+ "start_line": 2905,
+ "end_line": 2912,
+ "section": "Link reference definitions",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo]:\n\n[foo]\n",
+ "html": "<p>[foo]:</p>\n<p>[foo]</p>\n",
+ "example": 168,
+ "start_line": 2917,
+ "end_line": 2924,
+ "section": "Link reference definitions",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo]: <>\n\n[foo]\n",
+ "html": "<p><a href=\"\">foo</a></p>\n",
+ "example": 169,
+ "start_line": 2929,
+ "end_line": 2935,
+ "section": "Link reference definitions",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo]: <bar>(baz)\n\n[foo]\n",
+ "html": "<p>[foo]: <bar>(baz)</p>\n<p>[foo]</p>\n",
+ "example": 170,
+ "start_line": 2940,
+ "end_line": 2947,
+ "section": "Link reference definitions",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo]: /url\\bar\\*baz \"foo\\\"bar\\baz\"\n\n[foo]\n",
+ "html": "<p><a href=\"/url%5Cbar*baz\" title=\"foo"bar\\baz\">foo</a></p>\n",
+ "example": 171,
+ "start_line": 2953,
+ "end_line": 2959,
+ "section": "Link reference definitions",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo]\n\n[foo]: url\n",
+ "html": "<p><a href=\"url\">foo</a></p>\n",
+ "example": 172,
+ "start_line": 2964,
+ "end_line": 2970,
+ "section": "Link reference definitions",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo]\n\n[foo]: first\n[foo]: second\n",
+ "html": "<p><a href=\"first\">foo</a></p>\n",
+ "example": 173,
+ "start_line": 2976,
+ "end_line": 2983,
+ "section": "Link reference definitions",
+ "extensions": []
+ },
+ {
+ "markdown": "[FOO]: /url\n\n[Foo]\n",
+ "html": "<p><a href=\"/url\">Foo</a></p>\n",
+ "example": 174,
+ "start_line": 2989,
+ "end_line": 2995,
+ "section": "Link reference definitions",
+ "extensions": []
+ },
+ {
+ "markdown": "[\u0391\u0393\u03a9]: /\u03c6\u03bf\u03c5\n\n[\u03b1\u03b3\u03c9]\n",
+ "html": "<p><a href=\"/%CF%86%CE%BF%CF%85\">\u03b1\u03b3\u03c9</a></p>\n",
+ "example": 175,
+ "start_line": 2998,
+ "end_line": 3004,
+ "section": "Link reference definitions",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo]: /url\n",
+ "html": "",
+ "example": 176,
+ "start_line": 3010,
+ "end_line": 3013,
+ "section": "Link reference definitions",
+ "extensions": []
+ },
+ {
+ "markdown": "[\nfoo\n]: /url\nbar\n",
+ "html": "<p>bar</p>\n",
+ "example": 177,
+ "start_line": 3018,
+ "end_line": 3025,
+ "section": "Link reference definitions",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo]: /url \"title\" ok\n",
+ "html": "<p>[foo]: /url "title" ok</p>\n",
+ "example": 178,
+ "start_line": 3031,
+ "end_line": 3035,
+ "section": "Link reference definitions",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo]: /url\n\"title\" ok\n",
+ "html": "<p>"title" ok</p>\n",
+ "example": 179,
+ "start_line": 3040,
+ "end_line": 3045,
+ "section": "Link reference definitions",
+ "extensions": []
+ },
+ {
+ "markdown": " [foo]: /url \"title\"\n\n[foo]\n",
+ "html": "<pre><code>[foo]: /url "title"\n</code></pre>\n<p>[foo]</p>\n",
+ "example": 180,
+ "start_line": 3051,
+ "end_line": 3059,
+ "section": "Link reference definitions",
+ "extensions": []
+ },
+ {
+ "markdown": "```\n[foo]: /url\n```\n\n[foo]\n",
+ "html": "<pre><code>[foo]: /url\n</code></pre>\n<p>[foo]</p>\n",
+ "example": 181,
+ "start_line": 3065,
+ "end_line": 3075,
+ "section": "Link reference definitions",
+ "extensions": []
+ },
+ {
+ "markdown": "Foo\n[bar]: /baz\n\n[bar]\n",
+ "html": "<p>Foo\n[bar]: /baz</p>\n<p>[bar]</p>\n",
+ "example": 182,
+ "start_line": 3080,
+ "end_line": 3089,
+ "section": "Link reference definitions",
+ "extensions": []
+ },
+ {
+ "markdown": "# [Foo]\n[foo]: /url\n> bar\n",
+ "html": "<h1><a href=\"/url\">Foo</a></h1>\n<blockquote>\n<p>bar</p>\n</blockquote>\n",
+ "example": 183,
+ "start_line": 3095,
+ "end_line": 3104,
+ "section": "Link reference definitions",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo]: /url\nbar\n===\n[foo]\n",
+ "html": "<h1>bar</h1>\n<p><a href=\"/url\">foo</a></p>\n",
+ "example": 184,
+ "start_line": 3106,
+ "end_line": 3114,
+ "section": "Link reference definitions",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo]: /url\n===\n[foo]\n",
+ "html": "<p>===\n<a href=\"/url\">foo</a></p>\n",
+ "example": 185,
+ "start_line": 3116,
+ "end_line": 3123,
+ "section": "Link reference definitions",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo]: /foo-url \"foo\"\n[bar]: /bar-url\n \"bar\"\n[baz]: /baz-url\n\n[foo],\n[bar],\n[baz]\n",
+ "html": "<p><a href=\"/foo-url\" title=\"foo\">foo</a>,\n<a href=\"/bar-url\" title=\"bar\">bar</a>,\n<a href=\"/baz-url\">baz</a></p>\n",
+ "example": 186,
+ "start_line": 3129,
+ "end_line": 3142,
+ "section": "Link reference definitions",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo]\n\n> [foo]: /url\n",
+ "html": "<p><a href=\"/url\">foo</a></p>\n<blockquote>\n</blockquote>\n",
+ "example": 187,
+ "start_line": 3150,
+ "end_line": 3158,
+ "section": "Link reference definitions",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo]: /url\n",
+ "html": "",
+ "example": 188,
+ "start_line": 3167,
+ "end_line": 3170,
+ "section": "Link reference definitions",
+ "extensions": []
+ },
+ {
+ "markdown": "aaa\n\nbbb\n",
+ "html": "<p>aaa</p>\n<p>bbb</p>\n",
+ "example": 189,
+ "start_line": 3184,
+ "end_line": 3191,
+ "section": "Paragraphs",
+ "extensions": []
+ },
+ {
+ "markdown": "aaa\nbbb\n\nccc\nddd\n",
+ "html": "<p>aaa\nbbb</p>\n<p>ccc\nddd</p>\n",
+ "example": 190,
+ "start_line": 3196,
+ "end_line": 3207,
+ "section": "Paragraphs",
+ "extensions": []
+ },
+ {
+ "markdown": "aaa\n\n\nbbb\n",
+ "html": "<p>aaa</p>\n<p>bbb</p>\n",
+ "example": 191,
+ "start_line": 3212,
+ "end_line": 3220,
+ "section": "Paragraphs",
+ "extensions": []
+ },
+ {
+ "markdown": " aaa\n bbb\n",
+ "html": "<p>aaa\nbbb</p>\n",
+ "example": 192,
+ "start_line": 3225,
+ "end_line": 3231,
+ "section": "Paragraphs",
+ "extensions": []
+ },
+ {
+ "markdown": "aaa\n bbb\n ccc\n",
+ "html": "<p>aaa\nbbb\nccc</p>\n",
+ "example": 193,
+ "start_line": 3237,
+ "end_line": 3245,
+ "section": "Paragraphs",
+ "extensions": []
+ },
+ {
+ "markdown": " aaa\nbbb\n",
+ "html": "<p>aaa\nbbb</p>\n",
+ "example": 194,
+ "start_line": 3251,
+ "end_line": 3257,
+ "section": "Paragraphs",
+ "extensions": []
+ },
+ {
+ "markdown": " aaa\nbbb\n",
+ "html": "<pre><code>aaa\n</code></pre>\n<p>bbb</p>\n",
+ "example": 195,
+ "start_line": 3260,
+ "end_line": 3267,
+ "section": "Paragraphs",
+ "extensions": []
+ },
+ {
+ "markdown": "aaa \nbbb \n",
+ "html": "<p>aaa<br />\nbbb</p>\n",
+ "example": 196,
+ "start_line": 3274,
+ "end_line": 3280,
+ "section": "Paragraphs",
+ "extensions": []
+ },
+ {
+ "markdown": " \n\naaa\n \n\n# aaa\n\n \n",
+ "html": "<p>aaa</p>\n<h1>aaa</h1>\n",
+ "example": 197,
+ "start_line": 3291,
+ "end_line": 3303,
+ "section": "Blank lines",
+ "extensions": []
+ },
+ {
+ "markdown": "| foo | bar |\n| --- | --- |\n| baz | bim |\n",
+ "html": "<table>\n<thead>\n<tr>\n<th>foo</th>\n<th>bar</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>baz</td>\n<td>bim</td>\n</tr>\n</tbody>\n</table>\n",
+ "example": 198,
+ "start_line": 3326,
+ "end_line": 3345,
+ "section": "Tables (extension)",
+ "extensions": [
+ "table"
+ ]
+ },
+ {
+ "markdown": "| abc | defghi |\n:-: | -----------:\nbar | baz\n",
+ "html": "<table>\n<thead>\n<tr>\n<th align=\"center\">abc</th>\n<th align=\"right\">defghi</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td align=\"center\">bar</td>\n<td align=\"right\">baz</td>\n</tr>\n</tbody>\n</table>\n",
+ "example": 199,
+ "start_line": 3350,
+ "end_line": 3369,
+ "section": "Tables (extension)",
+ "extensions": [
+ "table"
+ ]
+ },
+ {
+ "markdown": "| f\\|oo |\n| ------ |\n| b `\\|` az |\n| b **\\|** im |\n",
+ "html": "<table>\n<thead>\n<tr>\n<th>f|oo</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>b <code>|</code> az</td>\n</tr>\n<tr>\n<td>b <strong>|</strong> im</td>\n</tr>\n</tbody>\n</table>\n",
+ "example": 200,
+ "start_line": 3374,
+ "end_line": 3395,
+ "section": "Tables (extension)",
+ "extensions": [
+ "table"
+ ]
+ },
+ {
+ "markdown": "| abc | def |\n| --- | --- |\n| bar | baz |\n> bar\n",
+ "html": "<table>\n<thead>\n<tr>\n<th>abc</th>\n<th>def</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>bar</td>\n<td>baz</td>\n</tr>\n</tbody>\n</table>\n<blockquote>\n<p>bar</p>\n</blockquote>\n",
+ "example": 201,
+ "start_line": 3400,
+ "end_line": 3423,
+ "section": "Tables (extension)",
+ "extensions": [
+ "table"
+ ]
+ },
+ {
+ "markdown": "| abc | def |\n| --- | --- |\n| bar | baz |\nbar\n\nbar\n",
+ "html": "<table>\n<thead>\n<tr>\n<th>abc</th>\n<th>def</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>bar</td>\n<td>baz</td>\n</tr>\n<tr>\n<td>bar</td>\n<td></td>\n</tr>\n</tbody>\n</table>\n<p>bar</p>\n",
+ "example": 202,
+ "start_line": 3425,
+ "end_line": 3452,
+ "section": "Tables (extension)",
+ "extensions": [
+ "table"
+ ]
+ },
+ {
+ "markdown": "| abc | def |\n| --- |\n| bar |\n",
+ "html": "<p>| abc | def |\n| --- |\n| bar |</p>\n",
+ "example": 203,
+ "start_line": 3457,
+ "end_line": 3465,
+ "section": "Tables (extension)",
+ "extensions": [
+ "table"
+ ]
+ },
+ {
+ "markdown": "| abc | def |\n| --- | --- |\n| bar |\n| bar | baz | boo |\n",
+ "html": "<table>\n<thead>\n<tr>\n<th>abc</th>\n<th>def</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>bar</td>\n<td></td>\n</tr>\n<tr>\n<td>bar</td>\n<td>baz</td>\n</tr>\n</tbody>\n</table>\n",
+ "example": 204,
+ "start_line": 3471,
+ "end_line": 3495,
+ "section": "Tables (extension)",
+ "extensions": [
+ "table"
+ ]
+ },
+ {
+ "markdown": "| abc | def |\n| --- | --- |\n",
+ "html": "<table>\n<thead>\n<tr>\n<th>abc</th>\n<th>def</th>\n</tr>\n</thead>\n</table>\n",
+ "example": 205,
+ "start_line": 3499,
+ "end_line": 3511,
+ "section": "Tables (extension)",
+ "extensions": [
+ "table"
+ ]
+ },
+ {
+ "markdown": "> # Foo\n> bar\n> baz\n",
+ "html": "<blockquote>\n<h1>Foo</h1>\n<p>bar\nbaz</p>\n</blockquote>\n",
+ "example": 206,
+ "start_line": 3565,
+ "end_line": 3575,
+ "section": "Block quotes",
+ "extensions": []
+ },
+ {
+ "markdown": "># Foo\n>bar\n> baz\n",
+ "html": "<blockquote>\n<h1>Foo</h1>\n<p>bar\nbaz</p>\n</blockquote>\n",
+ "example": 207,
+ "start_line": 3580,
+ "end_line": 3590,
+ "section": "Block quotes",
+ "extensions": []
+ },
+ {
+ "markdown": " > # Foo\n > bar\n > baz\n",
+ "html": "<blockquote>\n<h1>Foo</h1>\n<p>bar\nbaz</p>\n</blockquote>\n",
+ "example": 208,
+ "start_line": 3595,
+ "end_line": 3605,
+ "section": "Block quotes",
+ "extensions": []
+ },
+ {
+ "markdown": " > # Foo\n > bar\n > baz\n",
+ "html": "<pre><code>> # Foo\n> bar\n> baz\n</code></pre>\n",
+ "example": 209,
+ "start_line": 3610,
+ "end_line": 3619,
+ "section": "Block quotes",
+ "extensions": []
+ },
+ {
+ "markdown": "> # Foo\n> bar\nbaz\n",
+ "html": "<blockquote>\n<h1>Foo</h1>\n<p>bar\nbaz</p>\n</blockquote>\n",
+ "example": 210,
+ "start_line": 3625,
+ "end_line": 3635,
+ "section": "Block quotes",
+ "extensions": []
+ },
+ {
+ "markdown": "> bar\nbaz\n> foo\n",
+ "html": "<blockquote>\n<p>bar\nbaz\nfoo</p>\n</blockquote>\n",
+ "example": 211,
+ "start_line": 3641,
+ "end_line": 3651,
+ "section": "Block quotes",
+ "extensions": []
+ },
+ {
+ "markdown": "> foo\n---\n",
+ "html": "<blockquote>\n<p>foo</p>\n</blockquote>\n<hr />\n",
+ "example": 212,
+ "start_line": 3665,
+ "end_line": 3673,
+ "section": "Block quotes",
+ "extensions": []
+ },
+ {
+ "markdown": "> - foo\n- bar\n",
+ "html": "<blockquote>\n<ul>\n<li>foo</li>\n</ul>\n</blockquote>\n<ul>\n<li>bar</li>\n</ul>\n",
+ "example": 213,
+ "start_line": 3685,
+ "end_line": 3697,
+ "section": "Block quotes",
+ "extensions": []
+ },
+ {
+ "markdown": "> foo\n bar\n",
+ "html": "<blockquote>\n<pre><code>foo\n</code></pre>\n</blockquote>\n<pre><code>bar\n</code></pre>\n",
+ "example": 214,
+ "start_line": 3703,
+ "end_line": 3713,
+ "section": "Block quotes",
+ "extensions": []
+ },
+ {
+ "markdown": "> ```\nfoo\n```\n",
+ "html": "<blockquote>\n<pre><code></code></pre>\n</blockquote>\n<p>foo</p>\n<pre><code></code></pre>\n",
+ "example": 215,
+ "start_line": 3716,
+ "end_line": 3726,
+ "section": "Block quotes",
+ "extensions": []
+ },
+ {
+ "markdown": "> foo\n - bar\n",
+ "html": "<blockquote>\n<p>foo\n- bar</p>\n</blockquote>\n",
+ "example": 216,
+ "start_line": 3732,
+ "end_line": 3740,
+ "section": "Block quotes",
+ "extensions": []
+ },
+ {
+ "markdown": ">\n",
+ "html": "<blockquote>\n</blockquote>\n",
+ "example": 217,
+ "start_line": 3756,
+ "end_line": 3761,
+ "section": "Block quotes",
+ "extensions": []
+ },
+ {
+ "markdown": ">\n> \n> \n",
+ "html": "<blockquote>\n</blockquote>\n",
+ "example": 218,
+ "start_line": 3764,
+ "end_line": 3771,
+ "section": "Block quotes",
+ "extensions": []
+ },
+ {
+ "markdown": ">\n> foo\n> \n",
+ "html": "<blockquote>\n<p>foo</p>\n</blockquote>\n",
+ "example": 219,
+ "start_line": 3776,
+ "end_line": 3784,
+ "section": "Block quotes",
+ "extensions": []
+ },
+ {
+ "markdown": "> foo\n\n> bar\n",
+ "html": "<blockquote>\n<p>foo</p>\n</blockquote>\n<blockquote>\n<p>bar</p>\n</blockquote>\n",
+ "example": 220,
+ "start_line": 3789,
+ "end_line": 3800,
+ "section": "Block quotes",
+ "extensions": []
+ },
+ {
+ "markdown": "> foo\n> bar\n",
+ "html": "<blockquote>\n<p>foo\nbar</p>\n</blockquote>\n",
+ "example": 221,
+ "start_line": 3811,
+ "end_line": 3819,
+ "section": "Block quotes",
+ "extensions": []
+ },
+ {
+ "markdown": "> foo\n>\n> bar\n",
+ "html": "<blockquote>\n<p>foo</p>\n<p>bar</p>\n</blockquote>\n",
+ "example": 222,
+ "start_line": 3824,
+ "end_line": 3833,
+ "section": "Block quotes",
+ "extensions": []
+ },
+ {
+ "markdown": "foo\n> bar\n",
+ "html": "<p>foo</p>\n<blockquote>\n<p>bar</p>\n</blockquote>\n",
+ "example": 223,
+ "start_line": 3838,
+ "end_line": 3846,
+ "section": "Block quotes",
+ "extensions": []
+ },
+ {
+ "markdown": "> aaa\n***\n> bbb\n",
+ "html": "<blockquote>\n<p>aaa</p>\n</blockquote>\n<hr />\n<blockquote>\n<p>bbb</p>\n</blockquote>\n",
+ "example": 224,
+ "start_line": 3852,
+ "end_line": 3864,
+ "section": "Block quotes",
+ "extensions": []
+ },
+ {
+ "markdown": "> bar\nbaz\n",
+ "html": "<blockquote>\n<p>bar\nbaz</p>\n</blockquote>\n",
+ "example": 225,
+ "start_line": 3870,
+ "end_line": 3878,
+ "section": "Block quotes",
+ "extensions": []
+ },
+ {
+ "markdown": "> bar\n\nbaz\n",
+ "html": "<blockquote>\n<p>bar</p>\n</blockquote>\n<p>baz</p>\n",
+ "example": 226,
+ "start_line": 3881,
+ "end_line": 3890,
+ "section": "Block quotes",
+ "extensions": []
+ },
+ {
+ "markdown": "> bar\n>\nbaz\n",
+ "html": "<blockquote>\n<p>bar</p>\n</blockquote>\n<p>baz</p>\n",
+ "example": 227,
+ "start_line": 3893,
+ "end_line": 3902,
+ "section": "Block quotes",
+ "extensions": []
+ },
+ {
+ "markdown": "> > > foo\nbar\n",
+ "html": "<blockquote>\n<blockquote>\n<blockquote>\n<p>foo\nbar</p>\n</blockquote>\n</blockquote>\n</blockquote>\n",
+ "example": 228,
+ "start_line": 3909,
+ "end_line": 3921,
+ "section": "Block quotes",
+ "extensions": []
+ },
+ {
+ "markdown": ">>> foo\n> bar\n>>baz\n",
+ "html": "<blockquote>\n<blockquote>\n<blockquote>\n<p>foo\nbar\nbaz</p>\n</blockquote>\n</blockquote>\n</blockquote>\n",
+ "example": 229,
+ "start_line": 3924,
+ "end_line": 3938,
+ "section": "Block quotes",
+ "extensions": []
+ },
+ {
+ "markdown": "> code\n\n> not code\n",
+ "html": "<blockquote>\n<pre><code>code\n</code></pre>\n</blockquote>\n<blockquote>\n<p>not code</p>\n</blockquote>\n",
+ "example": 230,
+ "start_line": 3946,
+ "end_line": 3958,
+ "section": "Block quotes",
+ "extensions": []
+ },
+ {
+ "markdown": "A paragraph\nwith two lines.\n\n indented code\n\n> A block quote.\n",
+ "html": "<p>A paragraph\nwith two lines.</p>\n<pre><code>indented code\n</code></pre>\n<blockquote>\n<p>A block quote.</p>\n</blockquote>\n",
+ "example": 231,
+ "start_line": 4000,
+ "end_line": 4015,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n",
+ "html": "<ol>\n<li>\n<p>A paragraph\nwith two lines.</p>\n<pre><code>indented code\n</code></pre>\n<blockquote>\n<p>A block quote.</p>\n</blockquote>\n</li>\n</ol>\n",
+ "example": 232,
+ "start_line": 4022,
+ "end_line": 4041,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "- one\n\n two\n",
+ "html": "<ul>\n<li>one</li>\n</ul>\n<p>two</p>\n",
+ "example": 233,
+ "start_line": 4055,
+ "end_line": 4064,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "- one\n\n two\n",
+ "html": "<ul>\n<li>\n<p>one</p>\n<p>two</p>\n</li>\n</ul>\n",
+ "example": 234,
+ "start_line": 4067,
+ "end_line": 4078,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": " - one\n\n two\n",
+ "html": "<ul>\n<li>one</li>\n</ul>\n<pre><code> two\n</code></pre>\n",
+ "example": 235,
+ "start_line": 4081,
+ "end_line": 4091,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": " - one\n\n two\n",
+ "html": "<ul>\n<li>\n<p>one</p>\n<p>two</p>\n</li>\n</ul>\n",
+ "example": 236,
+ "start_line": 4094,
+ "end_line": 4105,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": " > > 1. one\n>>\n>> two\n",
+ "html": "<blockquote>\n<blockquote>\n<ol>\n<li>\n<p>one</p>\n<p>two</p>\n</li>\n</ol>\n</blockquote>\n</blockquote>\n",
+ "example": 237,
+ "start_line": 4116,
+ "end_line": 4131,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": ">>- one\n>>\n > > two\n",
+ "html": "<blockquote>\n<blockquote>\n<ul>\n<li>one</li>\n</ul>\n<p>two</p>\n</blockquote>\n</blockquote>\n",
+ "example": 238,
+ "start_line": 4143,
+ "end_line": 4156,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "-one\n\n2.two\n",
+ "html": "<p>-one</p>\n<p>2.two</p>\n",
+ "example": 239,
+ "start_line": 4162,
+ "end_line": 4169,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "- foo\n\n\n bar\n",
+ "html": "<ul>\n<li>\n<p>foo</p>\n<p>bar</p>\n</li>\n</ul>\n",
+ "example": 240,
+ "start_line": 4175,
+ "end_line": 4187,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "1. foo\n\n ```\n bar\n ```\n\n baz\n\n > bam\n",
+ "html": "<ol>\n<li>\n<p>foo</p>\n<pre><code>bar\n</code></pre>\n<p>baz</p>\n<blockquote>\n<p>bam</p>\n</blockquote>\n</li>\n</ol>\n",
+ "example": 241,
+ "start_line": 4192,
+ "end_line": 4214,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "- Foo\n\n bar\n\n\n baz\n",
+ "html": "<ul>\n<li>\n<p>Foo</p>\n<pre><code>bar\n\n\nbaz\n</code></pre>\n</li>\n</ul>\n",
+ "example": 242,
+ "start_line": 4220,
+ "end_line": 4238,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "123456789. ok\n",
+ "html": "<ol start=\"123456789\">\n<li>ok</li>\n</ol>\n",
+ "example": 243,
+ "start_line": 4242,
+ "end_line": 4248,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "1234567890. not ok\n",
+ "html": "<p>1234567890. not ok</p>\n",
+ "example": 244,
+ "start_line": 4251,
+ "end_line": 4255,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "0. ok\n",
+ "html": "<ol start=\"0\">\n<li>ok</li>\n</ol>\n",
+ "example": 245,
+ "start_line": 4260,
+ "end_line": 4266,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "003. ok\n",
+ "html": "<ol start=\"3\">\n<li>ok</li>\n</ol>\n",
+ "example": 246,
+ "start_line": 4269,
+ "end_line": 4275,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "-1. not ok\n",
+ "html": "<p>-1. not ok</p>\n",
+ "example": 247,
+ "start_line": 4280,
+ "end_line": 4284,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "- foo\n\n bar\n",
+ "html": "<ul>\n<li>\n<p>foo</p>\n<pre><code>bar\n</code></pre>\n</li>\n</ul>\n",
+ "example": 248,
+ "start_line": 4303,
+ "end_line": 4315,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": " 10. foo\n\n bar\n",
+ "html": "<ol start=\"10\">\n<li>\n<p>foo</p>\n<pre><code>bar\n</code></pre>\n</li>\n</ol>\n",
+ "example": 249,
+ "start_line": 4320,
+ "end_line": 4332,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": " indented code\n\nparagraph\n\n more code\n",
+ "html": "<pre><code>indented code\n</code></pre>\n<p>paragraph</p>\n<pre><code>more code\n</code></pre>\n",
+ "example": 250,
+ "start_line": 4339,
+ "end_line": 4351,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "1. indented code\n\n paragraph\n\n more code\n",
+ "html": "<ol>\n<li>\n<pre><code>indented code\n</code></pre>\n<p>paragraph</p>\n<pre><code>more code\n</code></pre>\n</li>\n</ol>\n",
+ "example": 251,
+ "start_line": 4354,
+ "end_line": 4370,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "1. indented code\n\n paragraph\n\n more code\n",
+ "html": "<ol>\n<li>\n<pre><code> indented code\n</code></pre>\n<p>paragraph</p>\n<pre><code>more code\n</code></pre>\n</li>\n</ol>\n",
+ "example": 252,
+ "start_line": 4376,
+ "end_line": 4392,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": " foo\n\nbar\n",
+ "html": "<p>foo</p>\n<p>bar</p>\n",
+ "example": 253,
+ "start_line": 4403,
+ "end_line": 4410,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "- foo\n\n bar\n",
+ "html": "<ul>\n<li>foo</li>\n</ul>\n<p>bar</p>\n",
+ "example": 254,
+ "start_line": 4413,
+ "end_line": 4422,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "- foo\n\n bar\n",
+ "html": "<ul>\n<li>\n<p>foo</p>\n<p>bar</p>\n</li>\n</ul>\n",
+ "example": 255,
+ "start_line": 4430,
+ "end_line": 4441,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "-\n foo\n-\n ```\n bar\n ```\n-\n baz\n",
+ "html": "<ul>\n<li>foo</li>\n<li>\n<pre><code>bar\n</code></pre>\n</li>\n<li>\n<pre><code>baz\n</code></pre>\n</li>\n</ul>\n",
+ "example": 256,
+ "start_line": 4458,
+ "end_line": 4479,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "- \n foo\n",
+ "html": "<ul>\n<li>foo</li>\n</ul>\n",
+ "example": 257,
+ "start_line": 4484,
+ "end_line": 4491,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "-\n\n foo\n",
+ "html": "<ul>\n<li></li>\n</ul>\n<p>foo</p>\n",
+ "example": 258,
+ "start_line": 4498,
+ "end_line": 4507,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "- foo\n-\n- bar\n",
+ "html": "<ul>\n<li>foo</li>\n<li></li>\n<li>bar</li>\n</ul>\n",
+ "example": 259,
+ "start_line": 4512,
+ "end_line": 4522,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "- foo\n- \n- bar\n",
+ "html": "<ul>\n<li>foo</li>\n<li></li>\n<li>bar</li>\n</ul>\n",
+ "example": 260,
+ "start_line": 4527,
+ "end_line": 4537,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "1. foo\n2.\n3. bar\n",
+ "html": "<ol>\n<li>foo</li>\n<li></li>\n<li>bar</li>\n</ol>\n",
+ "example": 261,
+ "start_line": 4542,
+ "end_line": 4552,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "*\n",
+ "html": "<ul>\n<li></li>\n</ul>\n",
+ "example": 262,
+ "start_line": 4557,
+ "end_line": 4563,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "foo\n*\n\nfoo\n1.\n",
+ "html": "<p>foo\n*</p>\n<p>foo\n1.</p>\n",
+ "example": 263,
+ "start_line": 4567,
+ "end_line": 4578,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n",
+ "html": "<ol>\n<li>\n<p>A paragraph\nwith two lines.</p>\n<pre><code>indented code\n</code></pre>\n<blockquote>\n<p>A block quote.</p>\n</blockquote>\n</li>\n</ol>\n",
+ "example": 264,
+ "start_line": 4589,
+ "end_line": 4608,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n",
+ "html": "<ol>\n<li>\n<p>A paragraph\nwith two lines.</p>\n<pre><code>indented code\n</code></pre>\n<blockquote>\n<p>A block quote.</p>\n</blockquote>\n</li>\n</ol>\n",
+ "example": 265,
+ "start_line": 4613,
+ "end_line": 4632,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n",
+ "html": "<ol>\n<li>\n<p>A paragraph\nwith two lines.</p>\n<pre><code>indented code\n</code></pre>\n<blockquote>\n<p>A block quote.</p>\n</blockquote>\n</li>\n</ol>\n",
+ "example": 266,
+ "start_line": 4637,
+ "end_line": 4656,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n",
+ "html": "<pre><code>1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n</code></pre>\n",
+ "example": 267,
+ "start_line": 4661,
+ "end_line": 4676,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": " 1. A paragraph\nwith two lines.\n\n indented code\n\n > A block quote.\n",
+ "html": "<ol>\n<li>\n<p>A paragraph\nwith two lines.</p>\n<pre><code>indented code\n</code></pre>\n<blockquote>\n<p>A block quote.</p>\n</blockquote>\n</li>\n</ol>\n",
+ "example": 268,
+ "start_line": 4691,
+ "end_line": 4710,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": " 1. A paragraph\n with two lines.\n",
+ "html": "<ol>\n<li>A paragraph\nwith two lines.</li>\n</ol>\n",
+ "example": 269,
+ "start_line": 4715,
+ "end_line": 4723,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "> 1. > Blockquote\ncontinued here.\n",
+ "html": "<blockquote>\n<ol>\n<li>\n<blockquote>\n<p>Blockquote\ncontinued here.</p>\n</blockquote>\n</li>\n</ol>\n</blockquote>\n",
+ "example": 270,
+ "start_line": 4728,
+ "end_line": 4742,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "> 1. > Blockquote\n> continued here.\n",
+ "html": "<blockquote>\n<ol>\n<li>\n<blockquote>\n<p>Blockquote\ncontinued here.</p>\n</blockquote>\n</li>\n</ol>\n</blockquote>\n",
+ "example": 271,
+ "start_line": 4745,
+ "end_line": 4759,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "- foo\n - bar\n - baz\n - boo\n",
+ "html": "<ul>\n<li>foo\n<ul>\n<li>bar\n<ul>\n<li>baz\n<ul>\n<li>boo</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n",
+ "example": 272,
+ "start_line": 4773,
+ "end_line": 4794,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "- foo\n - bar\n - baz\n - boo\n",
+ "html": "<ul>\n<li>foo</li>\n<li>bar</li>\n<li>baz</li>\n<li>boo</li>\n</ul>\n",
+ "example": 273,
+ "start_line": 4799,
+ "end_line": 4811,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "10) foo\n - bar\n",
+ "html": "<ol start=\"10\">\n<li>foo\n<ul>\n<li>bar</li>\n</ul>\n</li>\n</ol>\n",
+ "example": 274,
+ "start_line": 4816,
+ "end_line": 4827,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "10) foo\n - bar\n",
+ "html": "<ol start=\"10\">\n<li>foo</li>\n</ol>\n<ul>\n<li>bar</li>\n</ul>\n",
+ "example": 275,
+ "start_line": 4832,
+ "end_line": 4842,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "- - foo\n",
+ "html": "<ul>\n<li>\n<ul>\n<li>foo</li>\n</ul>\n</li>\n</ul>\n",
+ "example": 276,
+ "start_line": 4847,
+ "end_line": 4857,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "1. - 2. foo\n",
+ "html": "<ol>\n<li>\n<ul>\n<li>\n<ol start=\"2\">\n<li>foo</li>\n</ol>\n</li>\n</ul>\n</li>\n</ol>\n",
+ "example": 277,
+ "start_line": 4860,
+ "end_line": 4874,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "- # Foo\n- Bar\n ---\n baz\n",
+ "html": "<ul>\n<li>\n<h1>Foo</h1>\n</li>\n<li>\n<h2>Bar</h2>\nbaz</li>\n</ul>\n",
+ "example": 278,
+ "start_line": 4879,
+ "end_line": 4893,
+ "section": "List items",
+ "extensions": []
+ },
+ {
+ "markdown": "- foo\n- bar\n+ baz\n",
+ "html": "<ul>\n<li>foo</li>\n<li>bar</li>\n</ul>\n<ul>\n<li>baz</li>\n</ul>\n",
+ "example": 281,
+ "start_line": 5172,
+ "end_line": 5184,
+ "section": "Lists",
+ "extensions": []
+ },
+ {
+ "markdown": "1. foo\n2. bar\n3) baz\n",
+ "html": "<ol>\n<li>foo</li>\n<li>bar</li>\n</ol>\n<ol start=\"3\">\n<li>baz</li>\n</ol>\n",
+ "example": 282,
+ "start_line": 5187,
+ "end_line": 5199,
+ "section": "Lists",
+ "extensions": []
+ },
+ {
+ "markdown": "Foo\n- bar\n- baz\n",
+ "html": "<p>Foo</p>\n<ul>\n<li>bar</li>\n<li>baz</li>\n</ul>\n",
+ "example": 283,
+ "start_line": 5206,
+ "end_line": 5216,
+ "section": "Lists",
+ "extensions": []
+ },
+ {
+ "markdown": "The number of windows in my house is\n14. The number of doors is 6.\n",
+ "html": "<p>The number of windows in my house is\n14. The number of doors is 6.</p>\n",
+ "example": 284,
+ "start_line": 5283,
+ "end_line": 5289,
+ "section": "Lists",
+ "extensions": []
+ },
+ {
+ "markdown": "The number of windows in my house is\n1. The number of doors is 6.\n",
+ "html": "<p>The number of windows in my house is</p>\n<ol>\n<li>The number of doors is 6.</li>\n</ol>\n",
+ "example": 285,
+ "start_line": 5293,
+ "end_line": 5301,
+ "section": "Lists",
+ "extensions": []
+ },
+ {
+ "markdown": "- foo\n\n- bar\n\n\n- baz\n",
+ "html": "<ul>\n<li>\n<p>foo</p>\n</li>\n<li>\n<p>bar</p>\n</li>\n<li>\n<p>baz</p>\n</li>\n</ul>\n",
+ "example": 286,
+ "start_line": 5307,
+ "end_line": 5326,
+ "section": "Lists",
+ "extensions": []
+ },
+ {
+ "markdown": "- foo\n - bar\n - baz\n\n\n bim\n",
+ "html": "<ul>\n<li>foo\n<ul>\n<li>bar\n<ul>\n<li>\n<p>baz</p>\n<p>bim</p>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n",
+ "example": 287,
+ "start_line": 5328,
+ "end_line": 5350,
+ "section": "Lists",
+ "extensions": []
+ },
+ {
+ "markdown": "- foo\n- bar\n\n<!-- -->\n\n- baz\n- bim\n",
+ "html": "<ul>\n<li>foo</li>\n<li>bar</li>\n</ul>\n<!-- -->\n<ul>\n<li>baz</li>\n<li>bim</li>\n</ul>\n",
+ "example": 288,
+ "start_line": 5358,
+ "end_line": 5376,
+ "section": "Lists",
+ "extensions": []
+ },
+ {
+ "markdown": "- foo\n\n notcode\n\n- foo\n\n<!-- -->\n\n code\n",
+ "html": "<ul>\n<li>\n<p>foo</p>\n<p>notcode</p>\n</li>\n<li>\n<p>foo</p>\n</li>\n</ul>\n<!-- -->\n<pre><code>code\n</code></pre>\n",
+ "example": 289,
+ "start_line": 5379,
+ "end_line": 5402,
+ "section": "Lists",
+ "extensions": []
+ },
+ {
+ "markdown": "- a\n - b\n - c\n - d\n - e\n - f\n- g\n",
+ "html": "<ul>\n<li>a</li>\n<li>b</li>\n<li>c</li>\n<li>d</li>\n<li>e</li>\n<li>f</li>\n<li>g</li>\n</ul>\n",
+ "example": 290,
+ "start_line": 5410,
+ "end_line": 5428,
+ "section": "Lists",
+ "extensions": []
+ },
+ {
+ "markdown": "1. a\n\n 2. b\n\n 3. c\n",
+ "html": "<ol>\n<li>\n<p>a</p>\n</li>\n<li>\n<p>b</p>\n</li>\n<li>\n<p>c</p>\n</li>\n</ol>\n",
+ "example": 291,
+ "start_line": 5431,
+ "end_line": 5449,
+ "section": "Lists",
+ "extensions": []
+ },
+ {
+ "markdown": "- a\n - b\n - c\n - d\n - e\n",
+ "html": "<ul>\n<li>a</li>\n<li>b</li>\n<li>c</li>\n<li>d\n- e</li>\n</ul>\n",
+ "example": 292,
+ "start_line": 5455,
+ "end_line": 5469,
+ "section": "Lists",
+ "extensions": []
+ },
+ {
+ "markdown": "1. a\n\n 2. b\n\n 3. c\n",
+ "html": "<ol>\n<li>\n<p>a</p>\n</li>\n<li>\n<p>b</p>\n</li>\n</ol>\n<pre><code>3. c\n</code></pre>\n",
+ "example": 293,
+ "start_line": 5475,
+ "end_line": 5492,
+ "section": "Lists",
+ "extensions": []
+ },
+ {
+ "markdown": "- a\n- b\n\n- c\n",
+ "html": "<ul>\n<li>\n<p>a</p>\n</li>\n<li>\n<p>b</p>\n</li>\n<li>\n<p>c</p>\n</li>\n</ul>\n",
+ "example": 294,
+ "start_line": 5498,
+ "end_line": 5515,
+ "section": "Lists",
+ "extensions": []
+ },
+ {
+ "markdown": "* a\n*\n\n* c\n",
+ "html": "<ul>\n<li>\n<p>a</p>\n</li>\n<li></li>\n<li>\n<p>c</p>\n</li>\n</ul>\n",
+ "example": 295,
+ "start_line": 5520,
+ "end_line": 5535,
+ "section": "Lists",
+ "extensions": []
+ },
+ {
+ "markdown": "- a\n- b\n\n c\n- d\n",
+ "html": "<ul>\n<li>\n<p>a</p>\n</li>\n<li>\n<p>b</p>\n<p>c</p>\n</li>\n<li>\n<p>d</p>\n</li>\n</ul>\n",
+ "example": 296,
+ "start_line": 5542,
+ "end_line": 5561,
+ "section": "Lists",
+ "extensions": []
+ },
+ {
+ "markdown": "- a\n- b\n\n [ref]: /url\n- d\n",
+ "html": "<ul>\n<li>\n<p>a</p>\n</li>\n<li>\n<p>b</p>\n</li>\n<li>\n<p>d</p>\n</li>\n</ul>\n",
+ "example": 297,
+ "start_line": 5564,
+ "end_line": 5582,
+ "section": "Lists",
+ "extensions": []
+ },
+ {
+ "markdown": "- a\n- ```\n b\n\n\n ```\n- c\n",
+ "html": "<ul>\n<li>a</li>\n<li>\n<pre><code>b\n\n\n</code></pre>\n</li>\n<li>c</li>\n</ul>\n",
+ "example": 298,
+ "start_line": 5587,
+ "end_line": 5606,
+ "section": "Lists",
+ "extensions": []
+ },
+ {
+ "markdown": "- a\n - b\n\n c\n- d\n",
+ "html": "<ul>\n<li>a\n<ul>\n<li>\n<p>b</p>\n<p>c</p>\n</li>\n</ul>\n</li>\n<li>d</li>\n</ul>\n",
+ "example": 299,
+ "start_line": 5613,
+ "end_line": 5631,
+ "section": "Lists",
+ "extensions": []
+ },
+ {
+ "markdown": "* a\n > b\n >\n* c\n",
+ "html": "<ul>\n<li>a\n<blockquote>\n<p>b</p>\n</blockquote>\n</li>\n<li>c</li>\n</ul>\n",
+ "example": 300,
+ "start_line": 5637,
+ "end_line": 5651,
+ "section": "Lists",
+ "extensions": []
+ },
+ {
+ "markdown": "- a\n > b\n ```\n c\n ```\n- d\n",
+ "html": "<ul>\n<li>a\n<blockquote>\n<p>b</p>\n</blockquote>\n<pre><code>c\n</code></pre>\n</li>\n<li>d</li>\n</ul>\n",
+ "example": 301,
+ "start_line": 5657,
+ "end_line": 5675,
+ "section": "Lists",
+ "extensions": []
+ },
+ {
+ "markdown": "- a\n",
+ "html": "<ul>\n<li>a</li>\n</ul>\n",
+ "example": 302,
+ "start_line": 5680,
+ "end_line": 5686,
+ "section": "Lists",
+ "extensions": []
+ },
+ {
+ "markdown": "- a\n - b\n",
+ "html": "<ul>\n<li>a\n<ul>\n<li>b</li>\n</ul>\n</li>\n</ul>\n",
+ "example": 303,
+ "start_line": 5689,
+ "end_line": 5700,
+ "section": "Lists",
+ "extensions": []
+ },
+ {
+ "markdown": "1. ```\n foo\n ```\n\n bar\n",
+ "html": "<ol>\n<li>\n<pre><code>foo\n</code></pre>\n<p>bar</p>\n</li>\n</ol>\n",
+ "example": 304,
+ "start_line": 5706,
+ "end_line": 5720,
+ "section": "Lists",
+ "extensions": []
+ },
+ {
+ "markdown": "* foo\n * bar\n\n baz\n",
+ "html": "<ul>\n<li>\n<p>foo</p>\n<ul>\n<li>bar</li>\n</ul>\n<p>baz</p>\n</li>\n</ul>\n",
+ "example": 305,
+ "start_line": 5725,
+ "end_line": 5740,
+ "section": "Lists",
+ "extensions": []
+ },
+ {
+ "markdown": "- a\n - b\n - c\n\n- d\n - e\n - f\n",
+ "html": "<ul>\n<li>\n<p>a</p>\n<ul>\n<li>b</li>\n<li>c</li>\n</ul>\n</li>\n<li>\n<p>d</p>\n<ul>\n<li>e</li>\n<li>f</li>\n</ul>\n</li>\n</ul>\n",
+ "example": 306,
+ "start_line": 5743,
+ "end_line": 5768,
+ "section": "Lists",
+ "extensions": []
+ },
+ {
+ "markdown": "`hi`lo`\n",
+ "html": "<p><code>hi</code>lo`</p>\n",
+ "example": 307,
+ "start_line": 5777,
+ "end_line": 5781,
+ "section": "Inlines",
+ "extensions": []
+ },
+ {
+ "markdown": "\\!\\\"\\#\\$\\%\\&\\'\\(\\)\\*\\+\\,\\-\\.\\/\\:\\;\\<\\=\\>\\?\\@\\[\\\\\\]\\^\\_\\`\\{\\|\\}\\~\n",
+ "html": "<p>!"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~</p>\n",
+ "example": 308,
+ "start_line": 5791,
+ "end_line": 5795,
+ "section": "Backslash escapes",
+ "extensions": []
+ },
+ {
+ "markdown": "\\\t\\A\\a\\ \\3\\\u03c6\\\u00ab\n",
+ "html": "<p>\\\t\\A\\a\\ \\3\\\u03c6\\\u00ab</p>\n",
+ "example": 309,
+ "start_line": 5801,
+ "end_line": 5805,
+ "section": "Backslash escapes",
+ "extensions": []
+ },
+ {
+ "markdown": "\\*not emphasized*\n\\<br/> not a tag\n\\[not a link](/foo)\n\\`not code`\n1\\. not a list\n\\* not a list\n\\# not a heading\n\\[foo]: /url \"not a reference\"\n\\ö not a character entity\n",
+ "html": "<p>*not emphasized*\n<br/> not a tag\n[not a link](/foo)\n`not code`\n1. not a list\n* not a list\n# not a heading\n[foo]: /url "not a reference"\n&ouml; not a character entity</p>\n",
+ "example": 310,
+ "start_line": 5811,
+ "end_line": 5831,
+ "section": "Backslash escapes",
+ "extensions": []
+ },
+ {
+ "markdown": "\\\\*emphasis*\n",
+ "html": "<p>\\<em>emphasis</em></p>\n",
+ "example": 311,
+ "start_line": 5836,
+ "end_line": 5840,
+ "section": "Backslash escapes",
+ "extensions": []
+ },
+ {
+ "markdown": "foo\\\nbar\n",
+ "html": "<p>foo<br />\nbar</p>\n",
+ "example": 312,
+ "start_line": 5845,
+ "end_line": 5851,
+ "section": "Backslash escapes",
+ "extensions": []
+ },
+ {
+ "markdown": "`` \\[\\` ``\n",
+ "html": "<p><code>\\[\\`</code></p>\n",
+ "example": 313,
+ "start_line": 5857,
+ "end_line": 5861,
+ "section": "Backslash escapes",
+ "extensions": []
+ },
+ {
+ "markdown": " \\[\\]\n",
+ "html": "<pre><code>\\[\\]\n</code></pre>\n",
+ "example": 314,
+ "start_line": 5864,
+ "end_line": 5869,
+ "section": "Backslash escapes",
+ "extensions": []
+ },
+ {
+ "markdown": "~~~\n\\[\\]\n~~~\n",
+ "html": "<pre><code>\\[\\]\n</code></pre>\n",
+ "example": 315,
+ "start_line": 5872,
+ "end_line": 5879,
+ "section": "Backslash escapes",
+ "extensions": []
+ },
+ {
+ "markdown": "<http://example.com?find=\\*>\n",
+ "html": "<p><a href=\"http://example.com?find=%5C*\">http://example.com?find=\\*</a></p>\n",
+ "example": 316,
+ "start_line": 5882,
+ "end_line": 5886,
+ "section": "Backslash escapes",
+ "extensions": []
+ },
+ {
+ "markdown": "<a href=\"/bar\\/)\">\n",
+ "html": "<a href=\"/bar\\/)\">\n",
+ "example": 317,
+ "start_line": 5889,
+ "end_line": 5893,
+ "section": "Backslash escapes",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo](/bar\\* \"ti\\*tle\")\n",
+ "html": "<p><a href=\"/bar*\" title=\"ti*tle\">foo</a></p>\n",
+ "example": 318,
+ "start_line": 5899,
+ "end_line": 5903,
+ "section": "Backslash escapes",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo]\n\n[foo]: /bar\\* \"ti\\*tle\"\n",
+ "html": "<p><a href=\"/bar*\" title=\"ti*tle\">foo</a></p>\n",
+ "example": 319,
+ "start_line": 5906,
+ "end_line": 5912,
+ "section": "Backslash escapes",
+ "extensions": []
+ },
+ {
+ "markdown": "``` foo\\+bar\nfoo\n```\n",
+ "html": "<pre><code class=\"language-foo+bar\">foo\n</code></pre>\n",
+ "example": 320,
+ "start_line": 5915,
+ "end_line": 5922,
+ "section": "Backslash escapes",
+ "extensions": []
+ },
+ {
+ "markdown": " & © Æ Ď\n¾ ℋ ⅆ\n∲ ≧̸\n",
+ "html": "<p>\u00a0 & \u00a9 \u00c6 \u010e\n\u00be \u210b \u2146\n\u2232 \u2267\u0338</p>\n",
+ "example": 321,
+ "start_line": 5952,
+ "end_line": 5960,
+ "section": "Entity and numeric character references",
+ "extensions": []
+ },
+ {
+ "markdown": "# Ӓ Ϡ �\n",
+ "html": "<p># \u04d2 \u03e0 \ufffd</p>\n",
+ "example": 322,
+ "start_line": 5971,
+ "end_line": 5975,
+ "section": "Entity and numeric character references",
+ "extensions": []
+ },
+ {
+ "markdown": "" ആ ಫ\n",
+ "html": "<p>" \u0d06 \u0cab</p>\n",
+ "example": 323,
+ "start_line": 5984,
+ "end_line": 5988,
+ "section": "Entity and numeric character references",
+ "extensions": []
+ },
+ {
+ "markdown": "  &x; &#; &#x;\n�\n&#abcdef0;\n&ThisIsNotDefined; &hi?;\n",
+ "html": "<p>&nbsp &x; &#; &#x;\n&#987654321;\n&#abcdef0;\n&ThisIsNotDefined; &hi?;</p>\n",
+ "example": 324,
+ "start_line": 5993,
+ "end_line": 6003,
+ "section": "Entity and numeric character references",
+ "extensions": []
+ },
+ {
+ "markdown": "©\n",
+ "html": "<p>&copy</p>\n",
+ "example": 325,
+ "start_line": 6010,
+ "end_line": 6014,
+ "section": "Entity and numeric character references",
+ "extensions": []
+ },
+ {
+ "markdown": "&MadeUpEntity;\n",
+ "html": "<p>&MadeUpEntity;</p>\n",
+ "example": 326,
+ "start_line": 6020,
+ "end_line": 6024,
+ "section": "Entity and numeric character references",
+ "extensions": []
+ },
+ {
+ "markdown": "<a href=\"öö.html\">\n",
+ "html": "<a href=\"öö.html\">\n",
+ "example": 327,
+ "start_line": 6031,
+ "end_line": 6035,
+ "section": "Entity and numeric character references",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo](/föö \"föö\")\n",
+ "html": "<p><a href=\"/f%C3%B6%C3%B6\" title=\"f\u00f6\u00f6\">foo</a></p>\n",
+ "example": 328,
+ "start_line": 6038,
+ "end_line": 6042,
+ "section": "Entity and numeric character references",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo]\n\n[foo]: /föö \"föö\"\n",
+ "html": "<p><a href=\"/f%C3%B6%C3%B6\" title=\"f\u00f6\u00f6\">foo</a></p>\n",
+ "example": 329,
+ "start_line": 6045,
+ "end_line": 6051,
+ "section": "Entity and numeric character references",
+ "extensions": []
+ },
+ {
+ "markdown": "``` föö\nfoo\n```\n",
+ "html": "<pre><code class=\"language-f\u00f6\u00f6\">foo\n</code></pre>\n",
+ "example": 330,
+ "start_line": 6054,
+ "end_line": 6061,
+ "section": "Entity and numeric character references",
+ "extensions": []
+ },
+ {
+ "markdown": "`föö`\n",
+ "html": "<p><code>f&ouml;&ouml;</code></p>\n",
+ "example": 331,
+ "start_line": 6067,
+ "end_line": 6071,
+ "section": "Entity and numeric character references",
+ "extensions": []
+ },
+ {
+ "markdown": " föfö\n",
+ "html": "<pre><code>f&ouml;f&ouml;\n</code></pre>\n",
+ "example": 332,
+ "start_line": 6074,
+ "end_line": 6079,
+ "section": "Entity and numeric character references",
+ "extensions": []
+ },
+ {
+ "markdown": "*foo*\n*foo*\n",
+ "html": "<p>*foo*\n<em>foo</em></p>\n",
+ "example": 333,
+ "start_line": 6086,
+ "end_line": 6092,
+ "section": "Entity and numeric character references",
+ "extensions": []
+ },
+ {
+ "markdown": "* foo\n\n* foo\n",
+ "html": "<p>* foo</p>\n<ul>\n<li>foo</li>\n</ul>\n",
+ "example": 334,
+ "start_line": 6094,
+ "end_line": 6103,
+ "section": "Entity and numeric character references",
+ "extensions": []
+ },
+ {
+ "markdown": "foo bar\n",
+ "html": "<p>foo\n\nbar</p>\n",
+ "example": 335,
+ "start_line": 6105,
+ "end_line": 6111,
+ "section": "Entity and numeric character references",
+ "extensions": []
+ },
+ {
+ "markdown": "	foo\n",
+ "html": "<p>\tfoo</p>\n",
+ "example": 336,
+ "start_line": 6113,
+ "end_line": 6117,
+ "section": "Entity and numeric character references",
+ "extensions": []
+ },
+ {
+ "markdown": "[a](url "tit")\n",
+ "html": "<p>[a](url "tit")</p>\n",
+ "example": 337,
+ "start_line": 6120,
+ "end_line": 6124,
+ "section": "Entity and numeric character references",
+ "extensions": []
+ },
+ {
+ "markdown": "`foo`\n",
+ "html": "<p><code>foo</code></p>\n",
+ "example": 338,
+ "start_line": 6148,
+ "end_line": 6152,
+ "section": "Code spans",
+ "extensions": []
+ },
+ {
+ "markdown": "`` foo ` bar ``\n",
+ "html": "<p><code>foo ` bar</code></p>\n",
+ "example": 339,
+ "start_line": 6159,
+ "end_line": 6163,
+ "section": "Code spans",
+ "extensions": []
+ },
+ {
+ "markdown": "` `` `\n",
+ "html": "<p><code>``</code></p>\n",
+ "example": 340,
+ "start_line": 6169,
+ "end_line": 6173,
+ "section": "Code spans",
+ "extensions": []
+ },
+ {
+ "markdown": "` `` `\n",
+ "html": "<p><code> `` </code></p>\n",
+ "example": 341,
+ "start_line": 6177,
+ "end_line": 6181,
+ "section": "Code spans",
+ "extensions": []
+ },
+ {
+ "markdown": "` a`\n",
+ "html": "<p><code> a</code></p>\n",
+ "example": 342,
+ "start_line": 6186,
+ "end_line": 6190,
+ "section": "Code spans",
+ "extensions": []
+ },
+ {
+ "markdown": "`\u00a0b\u00a0`\n",
+ "html": "<p><code>\u00a0b\u00a0</code></p>\n",
+ "example": 343,
+ "start_line": 6195,
+ "end_line": 6199,
+ "section": "Code spans",
+ "extensions": []
+ },
+ {
+ "markdown": "`\u00a0`\n` `\n",
+ "html": "<p><code>\u00a0</code>\n<code> </code></p>\n",
+ "example": 344,
+ "start_line": 6203,
+ "end_line": 6209,
+ "section": "Code spans",
+ "extensions": []
+ },
+ {
+ "markdown": "``\nfoo\nbar \nbaz\n``\n",
+ "html": "<p><code>foo bar baz</code></p>\n",
+ "example": 345,
+ "start_line": 6214,
+ "end_line": 6222,
+ "section": "Code spans",
+ "extensions": []
+ },
+ {
+ "markdown": "``\nfoo \n``\n",
+ "html": "<p><code>foo </code></p>\n",
+ "example": 346,
+ "start_line": 6224,
+ "end_line": 6230,
+ "section": "Code spans",
+ "extensions": []
+ },
+ {
+ "markdown": "`foo bar \nbaz`\n",
+ "html": "<p><code>foo bar baz</code></p>\n",
+ "example": 347,
+ "start_line": 6235,
+ "end_line": 6240,
+ "section": "Code spans",
+ "extensions": []
+ },
+ {
+ "markdown": "`foo\\`bar`\n",
+ "html": "<p><code>foo\\</code>bar`</p>\n",
+ "example": 348,
+ "start_line": 6252,
+ "end_line": 6256,
+ "section": "Code spans",
+ "extensions": []
+ },
+ {
+ "markdown": "``foo`bar``\n",
+ "html": "<p><code>foo`bar</code></p>\n",
+ "example": 349,
+ "start_line": 6263,
+ "end_line": 6267,
+ "section": "Code spans",
+ "extensions": []
+ },
+ {
+ "markdown": "` foo `` bar `\n",
+ "html": "<p><code>foo `` bar</code></p>\n",
+ "example": 350,
+ "start_line": 6269,
+ "end_line": 6273,
+ "section": "Code spans",
+ "extensions": []
+ },
+ {
+ "markdown": "*foo`*`\n",
+ "html": "<p>*foo<code>*</code></p>\n",
+ "example": 351,
+ "start_line": 6281,
+ "end_line": 6285,
+ "section": "Code spans",
+ "extensions": []
+ },
+ {
+ "markdown": "[not a `link](/foo`)\n",
+ "html": "<p>[not a <code>link](/foo</code>)</p>\n",
+ "example": 352,
+ "start_line": 6290,
+ "end_line": 6294,
+ "section": "Code spans",
+ "extensions": []
+ },
+ {
+ "markdown": "`<a href=\"`\">`\n",
+ "html": "<p><code><a href="</code>">`</p>\n",
+ "example": 353,
+ "start_line": 6300,
+ "end_line": 6304,
+ "section": "Code spans",
+ "extensions": []
+ },
+ {
+ "markdown": "<a href=\"`\">`\n",
+ "html": "<p><a href=\"`\">`</p>\n",
+ "example": 354,
+ "start_line": 6309,
+ "end_line": 6313,
+ "section": "Code spans",
+ "extensions": []
+ },
+ {
+ "markdown": "`<http://foo.bar.`baz>`\n",
+ "html": "<p><code><http://foo.bar.</code>baz>`</p>\n",
+ "example": 355,
+ "start_line": 6318,
+ "end_line": 6322,
+ "section": "Code spans",
+ "extensions": []
+ },
+ {
+ "markdown": "<http://foo.bar.`baz>`\n",
+ "html": "<p><a href=\"http://foo.bar.%60baz\">http://foo.bar.`baz</a>`</p>\n",
+ "example": 356,
+ "start_line": 6327,
+ "end_line": 6331,
+ "section": "Code spans",
+ "extensions": []
+ },
+ {
+ "markdown": "```foo``\n",
+ "html": "<p>```foo``</p>\n",
+ "example": 357,
+ "start_line": 6337,
+ "end_line": 6341,
+ "section": "Code spans",
+ "extensions": []
+ },
+ {
+ "markdown": "`foo\n",
+ "html": "<p>`foo</p>\n",
+ "example": 358,
+ "start_line": 6344,
+ "end_line": 6348,
+ "section": "Code spans",
+ "extensions": []
+ },
+ {
+ "markdown": "`foo``bar``\n",
+ "html": "<p>`foo<code>bar</code></p>\n",
+ "example": 359,
+ "start_line": 6353,
+ "end_line": 6357,
+ "section": "Code spans",
+ "extensions": []
+ },
+ {
+ "markdown": "*foo bar*\n",
+ "html": "<p><em>foo bar</em></p>\n",
+ "example": 360,
+ "start_line": 6570,
+ "end_line": 6574,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "a * foo bar*\n",
+ "html": "<p>a * foo bar*</p>\n",
+ "example": 361,
+ "start_line": 6580,
+ "end_line": 6584,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "a*\"foo\"*\n",
+ "html": "<p>a*"foo"*</p>\n",
+ "example": 362,
+ "start_line": 6591,
+ "end_line": 6595,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "*\u00a0a\u00a0*\n",
+ "html": "<p>*\u00a0a\u00a0*</p>\n",
+ "example": 363,
+ "start_line": 6600,
+ "end_line": 6604,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "foo*bar*\n",
+ "html": "<p>foo<em>bar</em></p>\n",
+ "example": 364,
+ "start_line": 6609,
+ "end_line": 6613,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "5*6*78\n",
+ "html": "<p>5<em>6</em>78</p>\n",
+ "example": 365,
+ "start_line": 6616,
+ "end_line": 6620,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "_foo bar_\n",
+ "html": "<p><em>foo bar</em></p>\n",
+ "example": 366,
+ "start_line": 6625,
+ "end_line": 6629,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "_ foo bar_\n",
+ "html": "<p>_ foo bar_</p>\n",
+ "example": 367,
+ "start_line": 6635,
+ "end_line": 6639,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "a_\"foo\"_\n",
+ "html": "<p>a_"foo"_</p>\n",
+ "example": 368,
+ "start_line": 6645,
+ "end_line": 6649,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "foo_bar_\n",
+ "html": "<p>foo_bar_</p>\n",
+ "example": 369,
+ "start_line": 6654,
+ "end_line": 6658,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "5_6_78\n",
+ "html": "<p>5_6_78</p>\n",
+ "example": 370,
+ "start_line": 6661,
+ "end_line": 6665,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "\u043f\u0440\u0438\u0441\u0442\u0430\u043d\u044f\u043c_\u0441\u0442\u0440\u0435\u043c\u044f\u0442\u0441\u044f_\n",
+ "html": "<p>\u043f\u0440\u0438\u0441\u0442\u0430\u043d\u044f\u043c_\u0441\u0442\u0440\u0435\u043c\u044f\u0442\u0441\u044f_</p>\n",
+ "example": 371,
+ "start_line": 6668,
+ "end_line": 6672,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "aa_\"bb\"_cc\n",
+ "html": "<p>aa_"bb"_cc</p>\n",
+ "example": 372,
+ "start_line": 6678,
+ "end_line": 6682,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "foo-_(bar)_\n",
+ "html": "<p>foo-<em>(bar)</em></p>\n",
+ "example": 373,
+ "start_line": 6689,
+ "end_line": 6693,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "_foo*\n",
+ "html": "<p>_foo*</p>\n",
+ "example": 374,
+ "start_line": 6701,
+ "end_line": 6705,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "*foo bar *\n",
+ "html": "<p>*foo bar *</p>\n",
+ "example": 375,
+ "start_line": 6711,
+ "end_line": 6715,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "*foo bar\n*\n",
+ "html": "<p>*foo bar\n*</p>\n",
+ "example": 376,
+ "start_line": 6720,
+ "end_line": 6726,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "*(*foo)\n",
+ "html": "<p>*(*foo)</p>\n",
+ "example": 377,
+ "start_line": 6733,
+ "end_line": 6737,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "*(*foo*)*\n",
+ "html": "<p><em>(<em>foo</em>)</em></p>\n",
+ "example": 378,
+ "start_line": 6743,
+ "end_line": 6747,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "*foo*bar\n",
+ "html": "<p><em>foo</em>bar</p>\n",
+ "example": 379,
+ "start_line": 6752,
+ "end_line": 6756,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "_foo bar _\n",
+ "html": "<p>_foo bar _</p>\n",
+ "example": 380,
+ "start_line": 6765,
+ "end_line": 6769,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "_(_foo)\n",
+ "html": "<p>_(_foo)</p>\n",
+ "example": 381,
+ "start_line": 6775,
+ "end_line": 6779,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "_(_foo_)_\n",
+ "html": "<p><em>(<em>foo</em>)</em></p>\n",
+ "example": 382,
+ "start_line": 6784,
+ "end_line": 6788,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "_foo_bar\n",
+ "html": "<p>_foo_bar</p>\n",
+ "example": 383,
+ "start_line": 6793,
+ "end_line": 6797,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "_\u043f\u0440\u0438\u0441\u0442\u0430\u043d\u044f\u043c_\u0441\u0442\u0440\u0435\u043c\u044f\u0442\u0441\u044f\n",
+ "html": "<p>_\u043f\u0440\u0438\u0441\u0442\u0430\u043d\u044f\u043c_\u0441\u0442\u0440\u0435\u043c\u044f\u0442\u0441\u044f</p>\n",
+ "example": 384,
+ "start_line": 6800,
+ "end_line": 6804,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "_foo_bar_baz_\n",
+ "html": "<p><em>foo_bar_baz</em></p>\n",
+ "example": 385,
+ "start_line": 6807,
+ "end_line": 6811,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "_(bar)_.\n",
+ "html": "<p><em>(bar)</em>.</p>\n",
+ "example": 386,
+ "start_line": 6818,
+ "end_line": 6822,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "**foo bar**\n",
+ "html": "<p><strong>foo bar</strong></p>\n",
+ "example": 387,
+ "start_line": 6827,
+ "end_line": 6831,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "** foo bar**\n",
+ "html": "<p>** foo bar**</p>\n",
+ "example": 388,
+ "start_line": 6837,
+ "end_line": 6841,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "a**\"foo\"**\n",
+ "html": "<p>a**"foo"**</p>\n",
+ "example": 389,
+ "start_line": 6848,
+ "end_line": 6852,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "foo**bar**\n",
+ "html": "<p>foo<strong>bar</strong></p>\n",
+ "example": 390,
+ "start_line": 6857,
+ "end_line": 6861,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "__foo bar__\n",
+ "html": "<p><strong>foo bar</strong></p>\n",
+ "example": 391,
+ "start_line": 6866,
+ "end_line": 6870,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "__ foo bar__\n",
+ "html": "<p>__ foo bar__</p>\n",
+ "example": 392,
+ "start_line": 6876,
+ "end_line": 6880,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "__\nfoo bar__\n",
+ "html": "<p>__\nfoo bar__</p>\n",
+ "example": 393,
+ "start_line": 6884,
+ "end_line": 6890,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "a__\"foo\"__\n",
+ "html": "<p>a__"foo"__</p>\n",
+ "example": 394,
+ "start_line": 6896,
+ "end_line": 6900,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "foo__bar__\n",
+ "html": "<p>foo__bar__</p>\n",
+ "example": 395,
+ "start_line": 6905,
+ "end_line": 6909,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "5__6__78\n",
+ "html": "<p>5__6__78</p>\n",
+ "example": 396,
+ "start_line": 6912,
+ "end_line": 6916,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "\u043f\u0440\u0438\u0441\u0442\u0430\u043d\u044f\u043c__\u0441\u0442\u0440\u0435\u043c\u044f\u0442\u0441\u044f__\n",
+ "html": "<p>\u043f\u0440\u0438\u0441\u0442\u0430\u043d\u044f\u043c__\u0441\u0442\u0440\u0435\u043c\u044f\u0442\u0441\u044f__</p>\n",
+ "example": 397,
+ "start_line": 6919,
+ "end_line": 6923,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "__foo, __bar__, baz__\n",
+ "html": "<p><strong>foo, <strong>bar</strong>, baz</strong></p>\n",
+ "example": 398,
+ "start_line": 6926,
+ "end_line": 6930,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "foo-__(bar)__\n",
+ "html": "<p>foo-<strong>(bar)</strong></p>\n",
+ "example": 399,
+ "start_line": 6937,
+ "end_line": 6941,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "**foo bar **\n",
+ "html": "<p>**foo bar **</p>\n",
+ "example": 400,
+ "start_line": 6950,
+ "end_line": 6954,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "**(**foo)\n",
+ "html": "<p>**(**foo)</p>\n",
+ "example": 401,
+ "start_line": 6963,
+ "end_line": 6967,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "*(**foo**)*\n",
+ "html": "<p><em>(<strong>foo</strong>)</em></p>\n",
+ "example": 402,
+ "start_line": 6973,
+ "end_line": 6977,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "**Gomphocarpus (*Gomphocarpus physocarpus*, syn.\n*Asclepias physocarpa*)**\n",
+ "html": "<p><strong>Gomphocarpus (<em>Gomphocarpus physocarpus</em>, syn.\n<em>Asclepias physocarpa</em>)</strong></p>\n",
+ "example": 403,
+ "start_line": 6980,
+ "end_line": 6986,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "**foo \"*bar*\" foo**\n",
+ "html": "<p><strong>foo "<em>bar</em>" foo</strong></p>\n",
+ "example": 404,
+ "start_line": 6989,
+ "end_line": 6993,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "**foo**bar\n",
+ "html": "<p><strong>foo</strong>bar</p>\n",
+ "example": 405,
+ "start_line": 6998,
+ "end_line": 7002,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "__foo bar __\n",
+ "html": "<p>__foo bar __</p>\n",
+ "example": 406,
+ "start_line": 7010,
+ "end_line": 7014,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "__(__foo)\n",
+ "html": "<p>__(__foo)</p>\n",
+ "example": 407,
+ "start_line": 7020,
+ "end_line": 7024,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "_(__foo__)_\n",
+ "html": "<p><em>(<strong>foo</strong>)</em></p>\n",
+ "example": 408,
+ "start_line": 7030,
+ "end_line": 7034,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "__foo__bar\n",
+ "html": "<p>__foo__bar</p>\n",
+ "example": 409,
+ "start_line": 7039,
+ "end_line": 7043,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "__\u043f\u0440\u0438\u0441\u0442\u0430\u043d\u044f\u043c__\u0441\u0442\u0440\u0435\u043c\u044f\u0442\u0441\u044f\n",
+ "html": "<p>__\u043f\u0440\u0438\u0441\u0442\u0430\u043d\u044f\u043c__\u0441\u0442\u0440\u0435\u043c\u044f\u0442\u0441\u044f</p>\n",
+ "example": 410,
+ "start_line": 7046,
+ "end_line": 7050,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "__foo__bar__baz__\n",
+ "html": "<p><strong>foo__bar__baz</strong></p>\n",
+ "example": 411,
+ "start_line": 7053,
+ "end_line": 7057,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "__(bar)__.\n",
+ "html": "<p><strong>(bar)</strong>.</p>\n",
+ "example": 412,
+ "start_line": 7064,
+ "end_line": 7068,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "*foo [bar](/url)*\n",
+ "html": "<p><em>foo <a href=\"/url\">bar</a></em></p>\n",
+ "example": 413,
+ "start_line": 7076,
+ "end_line": 7080,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "*foo\nbar*\n",
+ "html": "<p><em>foo\nbar</em></p>\n",
+ "example": 414,
+ "start_line": 7083,
+ "end_line": 7089,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "_foo __bar__ baz_\n",
+ "html": "<p><em>foo <strong>bar</strong> baz</em></p>\n",
+ "example": 415,
+ "start_line": 7095,
+ "end_line": 7099,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "_foo _bar_ baz_\n",
+ "html": "<p><em>foo <em>bar</em> baz</em></p>\n",
+ "example": 416,
+ "start_line": 7102,
+ "end_line": 7106,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "__foo_ bar_\n",
+ "html": "<p><em><em>foo</em> bar</em></p>\n",
+ "example": 417,
+ "start_line": 7109,
+ "end_line": 7113,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "*foo *bar**\n",
+ "html": "<p><em>foo <em>bar</em></em></p>\n",
+ "example": 418,
+ "start_line": 7116,
+ "end_line": 7120,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "*foo **bar** baz*\n",
+ "html": "<p><em>foo <strong>bar</strong> baz</em></p>\n",
+ "example": 419,
+ "start_line": 7123,
+ "end_line": 7127,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "*foo**bar**baz*\n",
+ "html": "<p><em>foo<strong>bar</strong>baz</em></p>\n",
+ "example": 420,
+ "start_line": 7129,
+ "end_line": 7133,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "*foo**bar*\n",
+ "html": "<p><em>foo**bar</em></p>\n",
+ "example": 421,
+ "start_line": 7153,
+ "end_line": 7157,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "***foo** bar*\n",
+ "html": "<p><em><strong>foo</strong> bar</em></p>\n",
+ "example": 422,
+ "start_line": 7166,
+ "end_line": 7170,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "*foo **bar***\n",
+ "html": "<p><em>foo <strong>bar</strong></em></p>\n",
+ "example": 423,
+ "start_line": 7173,
+ "end_line": 7177,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "*foo**bar***\n",
+ "html": "<p><em>foo<strong>bar</strong></em></p>\n",
+ "example": 424,
+ "start_line": 7180,
+ "end_line": 7184,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "foo***bar***baz\n",
+ "html": "<p>foo<em><strong>bar</strong></em>baz</p>\n",
+ "example": 425,
+ "start_line": 7191,
+ "end_line": 7195,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "foo******bar*********baz\n",
+ "html": "<p>foo<strong><strong><strong>bar</strong></strong></strong>***baz</p>\n",
+ "example": 426,
+ "start_line": 7197,
+ "end_line": 7201,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "*foo **bar *baz* bim** bop*\n",
+ "html": "<p><em>foo <strong>bar <em>baz</em> bim</strong> bop</em></p>\n",
+ "example": 427,
+ "start_line": 7206,
+ "end_line": 7210,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "*foo [*bar*](/url)*\n",
+ "html": "<p><em>foo <a href=\"/url\"><em>bar</em></a></em></p>\n",
+ "example": 428,
+ "start_line": 7213,
+ "end_line": 7217,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "** is not an empty emphasis\n",
+ "html": "<p>** is not an empty emphasis</p>\n",
+ "example": 429,
+ "start_line": 7222,
+ "end_line": 7226,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "**** is not an empty strong emphasis\n",
+ "html": "<p>**** is not an empty strong emphasis</p>\n",
+ "example": 430,
+ "start_line": 7229,
+ "end_line": 7233,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "**foo [bar](/url)**\n",
+ "html": "<p><strong>foo <a href=\"/url\">bar</a></strong></p>\n",
+ "example": 431,
+ "start_line": 7242,
+ "end_line": 7246,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "**foo\nbar**\n",
+ "html": "<p><strong>foo\nbar</strong></p>\n",
+ "example": 432,
+ "start_line": 7249,
+ "end_line": 7255,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "__foo _bar_ baz__\n",
+ "html": "<p><strong>foo <em>bar</em> baz</strong></p>\n",
+ "example": 433,
+ "start_line": 7261,
+ "end_line": 7265,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "__foo __bar__ baz__\n",
+ "html": "<p><strong>foo <strong>bar</strong> baz</strong></p>\n",
+ "example": 434,
+ "start_line": 7268,
+ "end_line": 7272,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "____foo__ bar__\n",
+ "html": "<p><strong><strong>foo</strong> bar</strong></p>\n",
+ "example": 435,
+ "start_line": 7275,
+ "end_line": 7279,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "**foo **bar****\n",
+ "html": "<p><strong>foo <strong>bar</strong></strong></p>\n",
+ "example": 436,
+ "start_line": 7282,
+ "end_line": 7286,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "**foo *bar* baz**\n",
+ "html": "<p><strong>foo <em>bar</em> baz</strong></p>\n",
+ "example": 437,
+ "start_line": 7289,
+ "end_line": 7293,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "**foo*bar*baz**\n",
+ "html": "<p><strong>foo<em>bar</em>baz</strong></p>\n",
+ "example": 438,
+ "start_line": 7296,
+ "end_line": 7300,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "***foo* bar**\n",
+ "html": "<p><strong><em>foo</em> bar</strong></p>\n",
+ "example": 439,
+ "start_line": 7303,
+ "end_line": 7307,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "**foo *bar***\n",
+ "html": "<p><strong>foo <em>bar</em></strong></p>\n",
+ "example": 440,
+ "start_line": 7310,
+ "end_line": 7314,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "**foo *bar **baz**\nbim* bop**\n",
+ "html": "<p><strong>foo <em>bar <strong>baz</strong>\nbim</em> bop</strong></p>\n",
+ "example": 441,
+ "start_line": 7319,
+ "end_line": 7325,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "**foo [*bar*](/url)**\n",
+ "html": "<p><strong>foo <a href=\"/url\"><em>bar</em></a></strong></p>\n",
+ "example": 442,
+ "start_line": 7328,
+ "end_line": 7332,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "__ is not an empty emphasis\n",
+ "html": "<p>__ is not an empty emphasis</p>\n",
+ "example": 443,
+ "start_line": 7337,
+ "end_line": 7341,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "____ is not an empty strong emphasis\n",
+ "html": "<p>____ is not an empty strong emphasis</p>\n",
+ "example": 444,
+ "start_line": 7344,
+ "end_line": 7348,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "foo ***\n",
+ "html": "<p>foo ***</p>\n",
+ "example": 445,
+ "start_line": 7354,
+ "end_line": 7358,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "foo *\\**\n",
+ "html": "<p>foo <em>*</em></p>\n",
+ "example": 446,
+ "start_line": 7361,
+ "end_line": 7365,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "foo *_*\n",
+ "html": "<p>foo <em>_</em></p>\n",
+ "example": 447,
+ "start_line": 7368,
+ "end_line": 7372,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "foo *****\n",
+ "html": "<p>foo *****</p>\n",
+ "example": 448,
+ "start_line": 7375,
+ "end_line": 7379,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "foo **\\***\n",
+ "html": "<p>foo <strong>*</strong></p>\n",
+ "example": 449,
+ "start_line": 7382,
+ "end_line": 7386,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "foo **_**\n",
+ "html": "<p>foo <strong>_</strong></p>\n",
+ "example": 450,
+ "start_line": 7389,
+ "end_line": 7393,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "**foo*\n",
+ "html": "<p>*<em>foo</em></p>\n",
+ "example": 451,
+ "start_line": 7400,
+ "end_line": 7404,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "*foo**\n",
+ "html": "<p><em>foo</em>*</p>\n",
+ "example": 452,
+ "start_line": 7407,
+ "end_line": 7411,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "***foo**\n",
+ "html": "<p>*<strong>foo</strong></p>\n",
+ "example": 453,
+ "start_line": 7414,
+ "end_line": 7418,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "****foo*\n",
+ "html": "<p>***<em>foo</em></p>\n",
+ "example": 454,
+ "start_line": 7421,
+ "end_line": 7425,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "**foo***\n",
+ "html": "<p><strong>foo</strong>*</p>\n",
+ "example": 455,
+ "start_line": 7428,
+ "end_line": 7432,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "*foo****\n",
+ "html": "<p><em>foo</em>***</p>\n",
+ "example": 456,
+ "start_line": 7435,
+ "end_line": 7439,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "foo ___\n",
+ "html": "<p>foo ___</p>\n",
+ "example": 457,
+ "start_line": 7445,
+ "end_line": 7449,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "foo _\\__\n",
+ "html": "<p>foo <em>_</em></p>\n",
+ "example": 458,
+ "start_line": 7452,
+ "end_line": 7456,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "foo _*_\n",
+ "html": "<p>foo <em>*</em></p>\n",
+ "example": 459,
+ "start_line": 7459,
+ "end_line": 7463,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "foo _____\n",
+ "html": "<p>foo _____</p>\n",
+ "example": 460,
+ "start_line": 7466,
+ "end_line": 7470,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "foo __\\___\n",
+ "html": "<p>foo <strong>_</strong></p>\n",
+ "example": 461,
+ "start_line": 7473,
+ "end_line": 7477,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "foo __*__\n",
+ "html": "<p>foo <strong>*</strong></p>\n",
+ "example": 462,
+ "start_line": 7480,
+ "end_line": 7484,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "__foo_\n",
+ "html": "<p>_<em>foo</em></p>\n",
+ "example": 463,
+ "start_line": 7487,
+ "end_line": 7491,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "_foo__\n",
+ "html": "<p><em>foo</em>_</p>\n",
+ "example": 464,
+ "start_line": 7498,
+ "end_line": 7502,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "___foo__\n",
+ "html": "<p>_<strong>foo</strong></p>\n",
+ "example": 465,
+ "start_line": 7505,
+ "end_line": 7509,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "____foo_\n",
+ "html": "<p>___<em>foo</em></p>\n",
+ "example": 466,
+ "start_line": 7512,
+ "end_line": 7516,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "__foo___\n",
+ "html": "<p><strong>foo</strong>_</p>\n",
+ "example": 467,
+ "start_line": 7519,
+ "end_line": 7523,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "_foo____\n",
+ "html": "<p><em>foo</em>___</p>\n",
+ "example": 468,
+ "start_line": 7526,
+ "end_line": 7530,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "**foo**\n",
+ "html": "<p><strong>foo</strong></p>\n",
+ "example": 469,
+ "start_line": 7536,
+ "end_line": 7540,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "*_foo_*\n",
+ "html": "<p><em><em>foo</em></em></p>\n",
+ "example": 470,
+ "start_line": 7543,
+ "end_line": 7547,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "__foo__\n",
+ "html": "<p><strong>foo</strong></p>\n",
+ "example": 471,
+ "start_line": 7550,
+ "end_line": 7554,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "_*foo*_\n",
+ "html": "<p><em><em>foo</em></em></p>\n",
+ "example": 472,
+ "start_line": 7557,
+ "end_line": 7561,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "****foo****\n",
+ "html": "<p><strong><strong>foo</strong></strong></p>\n",
+ "example": 473,
+ "start_line": 7567,
+ "end_line": 7571,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "____foo____\n",
+ "html": "<p><strong><strong>foo</strong></strong></p>\n",
+ "example": 474,
+ "start_line": 7574,
+ "end_line": 7578,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "******foo******\n",
+ "html": "<p><strong><strong><strong>foo</strong></strong></strong></p>\n",
+ "example": 475,
+ "start_line": 7585,
+ "end_line": 7589,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "***foo***\n",
+ "html": "<p><em><strong>foo</strong></em></p>\n",
+ "example": 476,
+ "start_line": 7594,
+ "end_line": 7598,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "_____foo_____\n",
+ "html": "<p><em><strong><strong>foo</strong></strong></em></p>\n",
+ "example": 477,
+ "start_line": 7601,
+ "end_line": 7605,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "*foo _bar* baz_\n",
+ "html": "<p><em>foo _bar</em> baz_</p>\n",
+ "example": 478,
+ "start_line": 7610,
+ "end_line": 7614,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "*foo __bar *baz bim__ bam*\n",
+ "html": "<p><em>foo <strong>bar *baz bim</strong> bam</em></p>\n",
+ "example": 479,
+ "start_line": 7617,
+ "end_line": 7621,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "**foo **bar baz**\n",
+ "html": "<p>**foo <strong>bar baz</strong></p>\n",
+ "example": 480,
+ "start_line": 7626,
+ "end_line": 7630,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "*foo *bar baz*\n",
+ "html": "<p>*foo <em>bar baz</em></p>\n",
+ "example": 481,
+ "start_line": 7633,
+ "end_line": 7637,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "*[bar*](/url)\n",
+ "html": "<p>*<a href=\"/url\">bar*</a></p>\n",
+ "example": 482,
+ "start_line": 7642,
+ "end_line": 7646,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "_foo [bar_](/url)\n",
+ "html": "<p>_foo <a href=\"/url\">bar_</a></p>\n",
+ "example": 483,
+ "start_line": 7649,
+ "end_line": 7653,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "*<img src=\"foo\" title=\"*\"/>\n",
+ "html": "<p>*<img src=\"foo\" title=\"*\"/></p>\n",
+ "example": 484,
+ "start_line": 7656,
+ "end_line": 7660,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "**<a href=\"**\">\n",
+ "html": "<p>**<a href=\"**\"></p>\n",
+ "example": 485,
+ "start_line": 7663,
+ "end_line": 7667,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "__<a href=\"__\">\n",
+ "html": "<p>__<a href=\"__\"></p>\n",
+ "example": 486,
+ "start_line": 7670,
+ "end_line": 7674,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "*a `*`*\n",
+ "html": "<p><em>a <code>*</code></em></p>\n",
+ "example": 487,
+ "start_line": 7677,
+ "end_line": 7681,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "_a `_`_\n",
+ "html": "<p><em>a <code>_</code></em></p>\n",
+ "example": 488,
+ "start_line": 7684,
+ "end_line": 7688,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "**a<http://foo.bar/?q=**>\n",
+ "html": "<p>**a<a href=\"http://foo.bar/?q=**\">http://foo.bar/?q=**</a></p>\n",
+ "example": 489,
+ "start_line": 7691,
+ "end_line": 7695,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "__a<http://foo.bar/?q=__>\n",
+ "html": "<p>__a<a href=\"http://foo.bar/?q=__\">http://foo.bar/?q=__</a></p>\n",
+ "example": 490,
+ "start_line": 7698,
+ "end_line": 7702,
+ "section": "Emphasis and strong emphasis",
+ "extensions": []
+ },
+ {
+ "markdown": "~~Hi~~ Hello, world!\n",
+ "html": "<p><del>Hi</del> Hello, world!</p>\n",
+ "example": 491,
+ "start_line": 7714,
+ "end_line": 7718,
+ "section": "Strikethrough (extension)",
+ "extensions": [
+ "strikethrough"
+ ]
+ },
+ {
+ "markdown": "This ~~has a\n\nnew paragraph~~.\n",
+ "html": "<p>This ~~has a</p>\n<p>new paragraph~~.</p>\n",
+ "example": 492,
+ "start_line": 7723,
+ "end_line": 7730,
+ "section": "Strikethrough (extension)",
+ "extensions": [
+ "strikethrough"
+ ]
+ },
+ {
+ "markdown": "[link](/uri \"title\")\n",
+ "html": "<p><a href=\"/uri\" title=\"title\">link</a></p>\n",
+ "example": 493,
+ "start_line": 7809,
+ "end_line": 7813,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link](/uri)\n",
+ "html": "<p><a href=\"/uri\">link</a></p>\n",
+ "example": 494,
+ "start_line": 7818,
+ "end_line": 7822,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link]()\n",
+ "html": "<p><a href=\"\">link</a></p>\n",
+ "example": 495,
+ "start_line": 7827,
+ "end_line": 7831,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link](<>)\n",
+ "html": "<p><a href=\"\">link</a></p>\n",
+ "example": 496,
+ "start_line": 7834,
+ "end_line": 7838,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link](/my uri)\n",
+ "html": "<p>[link](/my uri)</p>\n",
+ "example": 497,
+ "start_line": 7843,
+ "end_line": 7847,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link](</my uri>)\n",
+ "html": "<p><a href=\"/my%20uri\">link</a></p>\n",
+ "example": 498,
+ "start_line": 7849,
+ "end_line": 7853,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link](foo\nbar)\n",
+ "html": "<p>[link](foo\nbar)</p>\n",
+ "example": 499,
+ "start_line": 7858,
+ "end_line": 7864,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link](<foo\nbar>)\n",
+ "html": "<p>[link](<foo\nbar>)</p>\n",
+ "example": 500,
+ "start_line": 7866,
+ "end_line": 7872,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[a](<b)c>)\n",
+ "html": "<p><a href=\"b)c\">a</a></p>\n",
+ "example": 501,
+ "start_line": 7877,
+ "end_line": 7881,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link](<foo\\>)\n",
+ "html": "<p>[link](<foo>)</p>\n",
+ "example": 502,
+ "start_line": 7885,
+ "end_line": 7889,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[a](<b)c\n[a](<b)c>\n[a](<b>c)\n",
+ "html": "<p>[a](<b)c\n[a](<b)c>\n[a](<b>c)</p>\n",
+ "example": 503,
+ "start_line": 7894,
+ "end_line": 7902,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link](\\(foo\\))\n",
+ "html": "<p><a href=\"(foo)\">link</a></p>\n",
+ "example": 504,
+ "start_line": 7906,
+ "end_line": 7910,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link](foo(and(bar)))\n",
+ "html": "<p><a href=\"foo(and(bar))\">link</a></p>\n",
+ "example": 505,
+ "start_line": 7915,
+ "end_line": 7919,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link](foo\\(and\\(bar\\))\n",
+ "html": "<p><a href=\"foo(and(bar)\">link</a></p>\n",
+ "example": 506,
+ "start_line": 7924,
+ "end_line": 7928,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link](<foo(and(bar)>)\n",
+ "html": "<p><a href=\"foo(and(bar)\">link</a></p>\n",
+ "example": 507,
+ "start_line": 7931,
+ "end_line": 7935,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link](foo\\)\\:)\n",
+ "html": "<p><a href=\"foo):\">link</a></p>\n",
+ "example": 508,
+ "start_line": 7941,
+ "end_line": 7945,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link](#fragment)\n\n[link](http://example.com#fragment)\n\n[link](http://example.com?foo=3#frag)\n",
+ "html": "<p><a href=\"#fragment\">link</a></p>\n<p><a href=\"http://example.com#fragment\">link</a></p>\n<p><a href=\"http://example.com?foo=3#frag\">link</a></p>\n",
+ "example": 509,
+ "start_line": 7950,
+ "end_line": 7960,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link](foo\\bar)\n",
+ "html": "<p><a href=\"foo%5Cbar\">link</a></p>\n",
+ "example": 510,
+ "start_line": 7966,
+ "end_line": 7970,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link](foo%20bä)\n",
+ "html": "<p><a href=\"foo%20b%C3%A4\">link</a></p>\n",
+ "example": 511,
+ "start_line": 7982,
+ "end_line": 7986,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link](\"title\")\n",
+ "html": "<p><a href=\"%22title%22\">link</a></p>\n",
+ "example": 512,
+ "start_line": 7993,
+ "end_line": 7997,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link](/url \"title\")\n[link](/url 'title')\n[link](/url (title))\n",
+ "html": "<p><a href=\"/url\" title=\"title\">link</a>\n<a href=\"/url\" title=\"title\">link</a>\n<a href=\"/url\" title=\"title\">link</a></p>\n",
+ "example": 513,
+ "start_line": 8002,
+ "end_line": 8010,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link](/url \"title \\\""\")\n",
+ "html": "<p><a href=\"/url\" title=\"title ""\">link</a></p>\n",
+ "example": 514,
+ "start_line": 8016,
+ "end_line": 8020,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link](/url\u00a0\"title\")\n",
+ "html": "<p><a href=\"/url%C2%A0%22title%22\">link</a></p>\n",
+ "example": 515,
+ "start_line": 8026,
+ "end_line": 8030,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link](/url \"title \"and\" title\")\n",
+ "html": "<p>[link](/url "title "and" title")</p>\n",
+ "example": 516,
+ "start_line": 8035,
+ "end_line": 8039,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link](/url 'title \"and\" title')\n",
+ "html": "<p><a href=\"/url\" title=\"title "and" title\">link</a></p>\n",
+ "example": 517,
+ "start_line": 8044,
+ "end_line": 8048,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link]( /uri\n \"title\" )\n",
+ "html": "<p><a href=\"/uri\" title=\"title\">link</a></p>\n",
+ "example": 518,
+ "start_line": 8068,
+ "end_line": 8073,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link] (/uri)\n",
+ "html": "<p>[link] (/uri)</p>\n",
+ "example": 519,
+ "start_line": 8079,
+ "end_line": 8083,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link [foo [bar]]](/uri)\n",
+ "html": "<p><a href=\"/uri\">link [foo [bar]]</a></p>\n",
+ "example": 520,
+ "start_line": 8089,
+ "end_line": 8093,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link] bar](/uri)\n",
+ "html": "<p>[link] bar](/uri)</p>\n",
+ "example": 521,
+ "start_line": 8096,
+ "end_line": 8100,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link [bar](/uri)\n",
+ "html": "<p>[link <a href=\"/uri\">bar</a></p>\n",
+ "example": 522,
+ "start_line": 8103,
+ "end_line": 8107,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link \\[bar](/uri)\n",
+ "html": "<p><a href=\"/uri\">link [bar</a></p>\n",
+ "example": 523,
+ "start_line": 8110,
+ "end_line": 8114,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link *foo **bar** `#`*](/uri)\n",
+ "html": "<p><a href=\"/uri\">link <em>foo <strong>bar</strong> <code>#</code></em></a></p>\n",
+ "example": 524,
+ "start_line": 8119,
+ "end_line": 8123,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[](/uri)\n",
+ "html": "<p><a href=\"/uri\"><img src=\"moon.jpg\" alt=\"moon\" /></a></p>\n",
+ "example": 525,
+ "start_line": 8126,
+ "end_line": 8130,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo [bar](/uri)](/uri)\n",
+ "html": "<p>[foo <a href=\"/uri\">bar</a>](/uri)</p>\n",
+ "example": 526,
+ "start_line": 8135,
+ "end_line": 8139,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo *[bar [baz](/uri)](/uri)*](/uri)\n",
+ "html": "<p>[foo <em>[bar <a href=\"/uri\">baz</a>](/uri)</em>](/uri)</p>\n",
+ "example": 527,
+ "start_line": 8142,
+ "end_line": 8146,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "](uri2)](uri3)\n",
+ "html": "<p><img src=\"uri3\" alt=\"[foo](uri2)\" /></p>\n",
+ "example": 528,
+ "start_line": 8149,
+ "end_line": 8153,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "*[foo*](/uri)\n",
+ "html": "<p>*<a href=\"/uri\">foo*</a></p>\n",
+ "example": 529,
+ "start_line": 8159,
+ "end_line": 8163,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo *bar](baz*)\n",
+ "html": "<p><a href=\"baz*\">foo *bar</a></p>\n",
+ "example": 530,
+ "start_line": 8166,
+ "end_line": 8170,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "*foo [bar* baz]\n",
+ "html": "<p><em>foo [bar</em> baz]</p>\n",
+ "example": 531,
+ "start_line": 8176,
+ "end_line": 8180,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo <bar attr=\"](baz)\">\n",
+ "html": "<p>[foo <bar attr=\"](baz)\"></p>\n",
+ "example": 532,
+ "start_line": 8186,
+ "end_line": 8190,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo`](/uri)`\n",
+ "html": "<p>[foo<code>](/uri)</code></p>\n",
+ "example": 533,
+ "start_line": 8193,
+ "end_line": 8197,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo<http://example.com/?search=](uri)>\n",
+ "html": "<p>[foo<a href=\"http://example.com/?search=%5D(uri)\">http://example.com/?search=](uri)</a></p>\n",
+ "example": 534,
+ "start_line": 8200,
+ "end_line": 8204,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo][bar]\n\n[bar]: /url \"title\"\n",
+ "html": "<p><a href=\"/url\" title=\"title\">foo</a></p>\n",
+ "example": 535,
+ "start_line": 8238,
+ "end_line": 8244,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link [foo [bar]]][ref]\n\n[ref]: /uri\n",
+ "html": "<p><a href=\"/uri\">link [foo [bar]]</a></p>\n",
+ "example": 536,
+ "start_line": 8253,
+ "end_line": 8259,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link \\[bar][ref]\n\n[ref]: /uri\n",
+ "html": "<p><a href=\"/uri\">link [bar</a></p>\n",
+ "example": 537,
+ "start_line": 8262,
+ "end_line": 8268,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[link *foo **bar** `#`*][ref]\n\n[ref]: /uri\n",
+ "html": "<p><a href=\"/uri\">link <em>foo <strong>bar</strong> <code>#</code></em></a></p>\n",
+ "example": 538,
+ "start_line": 8273,
+ "end_line": 8279,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[][ref]\n\n[ref]: /uri\n",
+ "html": "<p><a href=\"/uri\"><img src=\"moon.jpg\" alt=\"moon\" /></a></p>\n",
+ "example": 539,
+ "start_line": 8282,
+ "end_line": 8288,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo [bar](/uri)][ref]\n\n[ref]: /uri\n",
+ "html": "<p>[foo <a href=\"/uri\">bar</a>]<a href=\"/uri\">ref</a></p>\n",
+ "example": 540,
+ "start_line": 8293,
+ "end_line": 8299,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo *bar [baz][ref]*][ref]\n\n[ref]: /uri\n",
+ "html": "<p>[foo <em>bar <a href=\"/uri\">baz</a></em>]<a href=\"/uri\">ref</a></p>\n",
+ "example": 541,
+ "start_line": 8302,
+ "end_line": 8308,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "*[foo*][ref]\n\n[ref]: /uri\n",
+ "html": "<p>*<a href=\"/uri\">foo*</a></p>\n",
+ "example": 542,
+ "start_line": 8317,
+ "end_line": 8323,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo *bar][ref]\n\n[ref]: /uri\n",
+ "html": "<p><a href=\"/uri\">foo *bar</a></p>\n",
+ "example": 543,
+ "start_line": 8326,
+ "end_line": 8332,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo <bar attr=\"][ref]\">\n\n[ref]: /uri\n",
+ "html": "<p>[foo <bar attr=\"][ref]\"></p>\n",
+ "example": 544,
+ "start_line": 8338,
+ "end_line": 8344,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo`][ref]`\n\n[ref]: /uri\n",
+ "html": "<p>[foo<code>][ref]</code></p>\n",
+ "example": 545,
+ "start_line": 8347,
+ "end_line": 8353,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo<http://example.com/?search=][ref]>\n\n[ref]: /uri\n",
+ "html": "<p>[foo<a href=\"http://example.com/?search=%5D%5Bref%5D\">http://example.com/?search=][ref]</a></p>\n",
+ "example": 546,
+ "start_line": 8356,
+ "end_line": 8362,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo][BaR]\n\n[bar]: /url \"title\"\n",
+ "html": "<p><a href=\"/url\" title=\"title\">foo</a></p>\n",
+ "example": 547,
+ "start_line": 8367,
+ "end_line": 8373,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[\u0422\u043e\u043b\u043f\u043e\u0439][\u0422\u043e\u043b\u043f\u043e\u0439] is a Russian word.\n\n[\u0422\u041e\u041b\u041f\u041e\u0419]: /url\n",
+ "html": "<p><a href=\"/url\">\u0422\u043e\u043b\u043f\u043e\u0439</a> is a Russian word.</p>\n",
+ "example": 548,
+ "start_line": 8378,
+ "end_line": 8384,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[Foo\n bar]: /url\n\n[Baz][Foo bar]\n",
+ "html": "<p><a href=\"/url\">Baz</a></p>\n",
+ "example": 549,
+ "start_line": 8390,
+ "end_line": 8397,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo] [bar]\n\n[bar]: /url \"title\"\n",
+ "html": "<p>[foo] <a href=\"/url\" title=\"title\">bar</a></p>\n",
+ "example": 550,
+ "start_line": 8403,
+ "end_line": 8409,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo]\n[bar]\n\n[bar]: /url \"title\"\n",
+ "html": "<p>[foo]\n<a href=\"/url\" title=\"title\">bar</a></p>\n",
+ "example": 551,
+ "start_line": 8412,
+ "end_line": 8420,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo]: /url1\n\n[foo]: /url2\n\n[bar][foo]\n",
+ "html": "<p><a href=\"/url1\">bar</a></p>\n",
+ "example": 552,
+ "start_line": 8453,
+ "end_line": 8461,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[bar][foo\\!]\n\n[foo!]: /url\n",
+ "html": "<p>[bar][foo!]</p>\n",
+ "example": 553,
+ "start_line": 8468,
+ "end_line": 8474,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo][ref[]\n\n[ref[]: /uri\n",
+ "html": "<p>[foo][ref[]</p>\n<p>[ref[]: /uri</p>\n",
+ "example": 554,
+ "start_line": 8480,
+ "end_line": 8487,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo][ref[bar]]\n\n[ref[bar]]: /uri\n",
+ "html": "<p>[foo][ref[bar]]</p>\n<p>[ref[bar]]: /uri</p>\n",
+ "example": 555,
+ "start_line": 8490,
+ "end_line": 8497,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[[[foo]]]\n\n[[[foo]]]: /url\n",
+ "html": "<p>[[[foo]]]</p>\n<p>[[[foo]]]: /url</p>\n",
+ "example": 556,
+ "start_line": 8500,
+ "end_line": 8507,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo][ref\\[]\n\n[ref\\[]: /uri\n",
+ "html": "<p><a href=\"/uri\">foo</a></p>\n",
+ "example": 557,
+ "start_line": 8510,
+ "end_line": 8516,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[bar\\\\]: /uri\n\n[bar\\\\]\n",
+ "html": "<p><a href=\"/uri\">bar\\</a></p>\n",
+ "example": 558,
+ "start_line": 8521,
+ "end_line": 8527,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[]\n\n[]: /uri\n",
+ "html": "<p>[]</p>\n<p>[]: /uri</p>\n",
+ "example": 559,
+ "start_line": 8532,
+ "end_line": 8539,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[\n ]\n\n[\n ]: /uri\n",
+ "html": "<p>[\n]</p>\n<p>[\n]: /uri</p>\n",
+ "example": 560,
+ "start_line": 8542,
+ "end_line": 8553,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo][]\n\n[foo]: /url \"title\"\n",
+ "html": "<p><a href=\"/url\" title=\"title\">foo</a></p>\n",
+ "example": 561,
+ "start_line": 8565,
+ "end_line": 8571,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[*foo* bar][]\n\n[*foo* bar]: /url \"title\"\n",
+ "html": "<p><a href=\"/url\" title=\"title\"><em>foo</em> bar</a></p>\n",
+ "example": 562,
+ "start_line": 8574,
+ "end_line": 8580,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[Foo][]\n\n[foo]: /url \"title\"\n",
+ "html": "<p><a href=\"/url\" title=\"title\">Foo</a></p>\n",
+ "example": 563,
+ "start_line": 8585,
+ "end_line": 8591,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo] \n[]\n\n[foo]: /url \"title\"\n",
+ "html": "<p><a href=\"/url\" title=\"title\">foo</a>\n[]</p>\n",
+ "example": 564,
+ "start_line": 8598,
+ "end_line": 8606,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo]\n\n[foo]: /url \"title\"\n",
+ "html": "<p><a href=\"/url\" title=\"title\">foo</a></p>\n",
+ "example": 565,
+ "start_line": 8618,
+ "end_line": 8624,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[*foo* bar]\n\n[*foo* bar]: /url \"title\"\n",
+ "html": "<p><a href=\"/url\" title=\"title\"><em>foo</em> bar</a></p>\n",
+ "example": 566,
+ "start_line": 8627,
+ "end_line": 8633,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[[*foo* bar]]\n\n[*foo* bar]: /url \"title\"\n",
+ "html": "<p>[<a href=\"/url\" title=\"title\"><em>foo</em> bar</a>]</p>\n",
+ "example": 567,
+ "start_line": 8636,
+ "end_line": 8642,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[[bar [foo]\n\n[foo]: /url\n",
+ "html": "<p>[[bar <a href=\"/url\">foo</a></p>\n",
+ "example": 568,
+ "start_line": 8645,
+ "end_line": 8651,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[Foo]\n\n[foo]: /url \"title\"\n",
+ "html": "<p><a href=\"/url\" title=\"title\">Foo</a></p>\n",
+ "example": 569,
+ "start_line": 8656,
+ "end_line": 8662,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo] bar\n\n[foo]: /url\n",
+ "html": "<p><a href=\"/url\">foo</a> bar</p>\n",
+ "example": 570,
+ "start_line": 8667,
+ "end_line": 8673,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "\\[foo]\n\n[foo]: /url \"title\"\n",
+ "html": "<p>[foo]</p>\n",
+ "example": 571,
+ "start_line": 8679,
+ "end_line": 8685,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo*]: /url\n\n*[foo*]\n",
+ "html": "<p>*<a href=\"/url\">foo*</a></p>\n",
+ "example": 572,
+ "start_line": 8691,
+ "end_line": 8697,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo][bar]\n\n[foo]: /url1\n[bar]: /url2\n",
+ "html": "<p><a href=\"/url2\">foo</a></p>\n",
+ "example": 573,
+ "start_line": 8703,
+ "end_line": 8710,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo][]\n\n[foo]: /url1\n",
+ "html": "<p><a href=\"/url1\">foo</a></p>\n",
+ "example": 574,
+ "start_line": 8712,
+ "end_line": 8718,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo]()\n\n[foo]: /url1\n",
+ "html": "<p><a href=\"\">foo</a></p>\n",
+ "example": 575,
+ "start_line": 8722,
+ "end_line": 8728,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo](not a link)\n\n[foo]: /url1\n",
+ "html": "<p><a href=\"/url1\">foo</a>(not a link)</p>\n",
+ "example": 576,
+ "start_line": 8730,
+ "end_line": 8736,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo][bar][baz]\n\n[baz]: /url\n",
+ "html": "<p>[foo]<a href=\"/url\">bar</a></p>\n",
+ "example": 577,
+ "start_line": 8741,
+ "end_line": 8747,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo][bar][baz]\n\n[baz]: /url1\n[bar]: /url2\n",
+ "html": "<p><a href=\"/url2\">foo</a><a href=\"/url1\">baz</a></p>\n",
+ "example": 578,
+ "start_line": 8753,
+ "end_line": 8760,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "[foo][bar][baz]\n\n[baz]: /url1\n[foo]: /url2\n",
+ "html": "<p>[foo]<a href=\"/url1\">bar</a></p>\n",
+ "example": 579,
+ "start_line": 8766,
+ "end_line": 8773,
+ "section": "Links",
+ "extensions": []
+ },
+ {
+ "markdown": "\n",
+ "html": "<p><img src=\"/url\" alt=\"foo\" title=\"title\" /></p>\n",
+ "example": 580,
+ "start_line": 8789,
+ "end_line": 8793,
+ "section": "Images",
+ "extensions": []
+ },
+ {
+ "markdown": "![foo *bar*]\n\n[foo *bar*]: train.jpg \"train & tracks\"\n",
+ "html": "<p><img src=\"train.jpg\" alt=\"foo bar\" title=\"train & tracks\" /></p>\n",
+ "example": 581,
+ "start_line": 8796,
+ "end_line": 8802,
+ "section": "Images",
+ "extensions": []
+ },
+ {
+ "markdown": "](/url2)\n",
+ "html": "<p><img src=\"/url2\" alt=\"foo bar\" /></p>\n",
+ "example": 582,
+ "start_line": 8805,
+ "end_line": 8809,
+ "section": "Images",
+ "extensions": []
+ },
+ {
+ "markdown": "](/url2)\n",
+ "html": "<p><img src=\"/url2\" alt=\"foo bar\" /></p>\n",
+ "example": 583,
+ "start_line": 8812,
+ "end_line": 8816,
+ "section": "Images",
+ "extensions": []
+ },
+ {
+ "markdown": "![foo *bar*][]\n\n[foo *bar*]: train.jpg \"train & tracks\"\n",
+ "html": "<p><img src=\"train.jpg\" alt=\"foo bar\" title=\"train & tracks\" /></p>\n",
+ "example": 584,
+ "start_line": 8826,
+ "end_line": 8832,
+ "section": "Images",
+ "extensions": []
+ },
+ {
+ "markdown": "![foo *bar*][foobar]\n\n[FOOBAR]: train.jpg \"train & tracks\"\n",
+ "html": "<p><img src=\"train.jpg\" alt=\"foo bar\" title=\"train & tracks\" /></p>\n",
+ "example": 585,
+ "start_line": 8835,
+ "end_line": 8841,
+ "section": "Images",
+ "extensions": []
+ },
+ {
+ "markdown": "\n",
+ "html": "<p><img src=\"train.jpg\" alt=\"foo\" /></p>\n",
+ "example": 586,
+ "start_line": 8844,
+ "end_line": 8848,
+ "section": "Images",
+ "extensions": []
+ },
+ {
+ "markdown": "My \n",
+ "html": "<p>My <img src=\"/path/to/train.jpg\" alt=\"foo bar\" title=\"title\" /></p>\n",
+ "example": 587,
+ "start_line": 8851,
+ "end_line": 8855,
+ "section": "Images",
+ "extensions": []
+ },
+ {
+ "markdown": "\n",
+ "html": "<p><img src=\"url\" alt=\"foo\" /></p>\n",
+ "example": 588,
+ "start_line": 8858,
+ "end_line": 8862,
+ "section": "Images",
+ "extensions": []
+ },
+ {
+ "markdown": "\n",
+ "html": "<p><img src=\"/url\" alt=\"\" /></p>\n",
+ "example": 589,
+ "start_line": 8865,
+ "end_line": 8869,
+ "section": "Images",
+ "extensions": []
+ },
+ {
+ "markdown": "![foo][bar]\n\n[bar]: /url\n",
+ "html": "<p><img src=\"/url\" alt=\"foo\" /></p>\n",
+ "example": 590,
+ "start_line": 8874,
+ "end_line": 8880,
+ "section": "Images",
+ "extensions": []
+ },
+ {
+ "markdown": "![foo][bar]\n\n[BAR]: /url\n",
+ "html": "<p><img src=\"/url\" alt=\"foo\" /></p>\n",
+ "example": 591,
+ "start_line": 8883,
+ "end_line": 8889,
+ "section": "Images",
+ "extensions": []
+ },
+ {
+ "markdown": "![foo][]\n\n[foo]: /url \"title\"\n",
+ "html": "<p><img src=\"/url\" alt=\"foo\" title=\"title\" /></p>\n",
+ "example": 592,
+ "start_line": 8894,
+ "end_line": 8900,
+ "section": "Images",
+ "extensions": []
+ },
+ {
+ "markdown": "![*foo* bar][]\n\n[*foo* bar]: /url \"title\"\n",
+ "html": "<p><img src=\"/url\" alt=\"foo bar\" title=\"title\" /></p>\n",
+ "example": 593,
+ "start_line": 8903,
+ "end_line": 8909,
+ "section": "Images",
+ "extensions": []
+ },
+ {
+ "markdown": "![Foo][]\n\n[foo]: /url \"title\"\n",
+ "html": "<p><img src=\"/url\" alt=\"Foo\" title=\"title\" /></p>\n",
+ "example": 594,
+ "start_line": 8914,
+ "end_line": 8920,
+ "section": "Images",
+ "extensions": []
+ },
+ {
+ "markdown": "![foo] \n[]\n\n[foo]: /url \"title\"\n",
+ "html": "<p><img src=\"/url\" alt=\"foo\" title=\"title\" />\n[]</p>\n",
+ "example": 595,
+ "start_line": 8926,
+ "end_line": 8934,
+ "section": "Images",
+ "extensions": []
+ },
+ {
+ "markdown": "![foo]\n\n[foo]: /url \"title\"\n",
+ "html": "<p><img src=\"/url\" alt=\"foo\" title=\"title\" /></p>\n",
+ "example": 596,
+ "start_line": 8939,
+ "end_line": 8945,
+ "section": "Images",
+ "extensions": []
+ },
+ {
+ "markdown": "![*foo* bar]\n\n[*foo* bar]: /url \"title\"\n",
+ "html": "<p><img src=\"/url\" alt=\"foo bar\" title=\"title\" /></p>\n",
+ "example": 597,
+ "start_line": 8948,
+ "end_line": 8954,
+ "section": "Images",
+ "extensions": []
+ },
+ {
+ "markdown": "![[foo]]\n\n[[foo]]: /url \"title\"\n",
+ "html": "<p>![[foo]]</p>\n<p>[[foo]]: /url "title"</p>\n",
+ "example": 598,
+ "start_line": 8959,
+ "end_line": 8966,
+ "section": "Images",
+ "extensions": []
+ },
+ {
+ "markdown": "![Foo]\n\n[foo]: /url \"title\"\n",
+ "html": "<p><img src=\"/url\" alt=\"Foo\" title=\"title\" /></p>\n",
+ "example": 599,
+ "start_line": 8971,
+ "end_line": 8977,
+ "section": "Images",
+ "extensions": []
+ },
+ {
+ "markdown": "!\\[foo]\n\n[foo]: /url \"title\"\n",
+ "html": "<p>![foo]</p>\n",
+ "example": 600,
+ "start_line": 8983,
+ "end_line": 8989,
+ "section": "Images",
+ "extensions": []
+ },
+ {
+ "markdown": "\\![foo]\n\n[foo]: /url \"title\"\n",
+ "html": "<p>!<a href=\"/url\" title=\"title\">foo</a></p>\n",
+ "example": 601,
+ "start_line": 8995,
+ "end_line": 9001,
+ "section": "Images",
+ "extensions": []
+ },
+ {
+ "markdown": "<http://foo.bar.baz>\n",
+ "html": "<p><a href=\"http://foo.bar.baz\">http://foo.bar.baz</a></p>\n",
+ "example": 602,
+ "start_line": 9028,
+ "end_line": 9032,
+ "section": "Autolinks",
+ "extensions": []
+ },
+ {
+ "markdown": "<http://foo.bar.baz/test?q=hello&id=22&boolean>\n",
+ "html": "<p><a href=\"http://foo.bar.baz/test?q=hello&id=22&boolean\">http://foo.bar.baz/test?q=hello&id=22&boolean</a></p>\n",
+ "example": 603,
+ "start_line": 9035,
+ "end_line": 9039,
+ "section": "Autolinks",
+ "extensions": []
+ },
+ {
+ "markdown": "<irc://foo.bar:2233/baz>\n",
+ "html": "<p><a href=\"irc://foo.bar:2233/baz\">irc://foo.bar:2233/baz</a></p>\n",
+ "example": 604,
+ "start_line": 9042,
+ "end_line": 9046,
+ "section": "Autolinks",
+ "extensions": []
+ },
+ {
+ "markdown": "<MAILTO:FOO@BAR.BAZ>\n",
+ "html": "<p><a href=\"MAILTO:FOO@BAR.BAZ\">MAILTO:FOO@BAR.BAZ</a></p>\n",
+ "example": 605,
+ "start_line": 9051,
+ "end_line": 9055,
+ "section": "Autolinks",
+ "extensions": []
+ },
+ {
+ "markdown": "<a+b+c:d>\n",
+ "html": "<p><a href=\"a+b+c:d\">a+b+c:d</a></p>\n",
+ "example": 606,
+ "start_line": 9063,
+ "end_line": 9067,
+ "section": "Autolinks",
+ "extensions": []
+ },
+ {
+ "markdown": "<made-up-scheme://foo,bar>\n",
+ "html": "<p><a href=\"made-up-scheme://foo,bar\">made-up-scheme://foo,bar</a></p>\n",
+ "example": 607,
+ "start_line": 9070,
+ "end_line": 9074,
+ "section": "Autolinks",
+ "extensions": []
+ },
+ {
+ "markdown": "<http://../>\n",
+ "html": "<p><a href=\"http://../\">http://../</a></p>\n",
+ "example": 608,
+ "start_line": 9077,
+ "end_line": 9081,
+ "section": "Autolinks",
+ "extensions": []
+ },
+ {
+ "markdown": "<localhost:5001/foo>\n",
+ "html": "<p><a href=\"localhost:5001/foo\">localhost:5001/foo</a></p>\n",
+ "example": 609,
+ "start_line": 9084,
+ "end_line": 9088,
+ "section": "Autolinks",
+ "extensions": []
+ },
+ {
+ "markdown": "<http://foo.bar/baz bim>\n",
+ "html": "<p><http://foo.bar/baz bim></p>\n",
+ "example": 610,
+ "start_line": 9093,
+ "end_line": 9097,
+ "section": "Autolinks",
+ "extensions": []
+ },
+ {
+ "markdown": "<http://example.com/\\[\\>\n",
+ "html": "<p><a href=\"http://example.com/%5C%5B%5C\">http://example.com/\\[\\</a></p>\n",
+ "example": 611,
+ "start_line": 9102,
+ "end_line": 9106,
+ "section": "Autolinks",
+ "extensions": []
+ },
+ {
+ "markdown": "<foo@bar.example.com>\n",
+ "html": "<p><a href=\"mailto:foo@bar.example.com\">foo@bar.example.com</a></p>\n",
+ "example": 612,
+ "start_line": 9124,
+ "end_line": 9128,
+ "section": "Autolinks",
+ "extensions": []
+ },
+ {
+ "markdown": "<foo+special@Bar.baz-bar0.com>\n",
+ "html": "<p><a href=\"mailto:foo+special@Bar.baz-bar0.com\">foo+special@Bar.baz-bar0.com</a></p>\n",
+ "example": 613,
+ "start_line": 9131,
+ "end_line": 9135,
+ "section": "Autolinks",
+ "extensions": []
+ },
+ {
+ "markdown": "<foo\\+@bar.example.com>\n",
+ "html": "<p><foo+@bar.example.com></p>\n",
+ "example": 614,
+ "start_line": 9140,
+ "end_line": 9144,
+ "section": "Autolinks",
+ "extensions": []
+ },
+ {
+ "markdown": "<>\n",
+ "html": "<p><></p>\n",
+ "example": 615,
+ "start_line": 9149,
+ "end_line": 9153,
+ "section": "Autolinks",
+ "extensions": []
+ },
+ {
+ "markdown": "< http://foo.bar >\n",
+ "html": "<p>< http://foo.bar ></p>\n",
+ "example": 616,
+ "start_line": 9156,
+ "end_line": 9160,
+ "section": "Autolinks",
+ "extensions": []
+ },
+ {
+ "markdown": "<m:abc>\n",
+ "html": "<p><m:abc></p>\n",
+ "example": 617,
+ "start_line": 9163,
+ "end_line": 9167,
+ "section": "Autolinks",
+ "extensions": []
+ },
+ {
+ "markdown": "<foo.bar.baz>\n",
+ "html": "<p><foo.bar.baz></p>\n",
+ "example": 618,
+ "start_line": 9170,
+ "end_line": 9174,
+ "section": "Autolinks",
+ "extensions": []
+ },
+ {
+ "markdown": "http://example.com\n",
+ "html": "<p>http://example.com</p>\n",
+ "example": 619,
+ "start_line": 9177,
+ "end_line": 9181,
+ "section": "Autolinks",
+ "extensions": []
+ },
+ {
+ "markdown": "foo@bar.example.com\n",
+ "html": "<p>foo@bar.example.com</p>\n",
+ "example": 620,
+ "start_line": 9184,
+ "end_line": 9188,
+ "section": "Autolinks",
+ "extensions": []
+ },
+ {
+ "markdown": "www.commonmark.org\n",
+ "html": "<p><a href=\"http://www.commonmark.org\">www.commonmark.org</a></p>\n",
+ "example": 621,
+ "start_line": 9213,
+ "end_line": 9217,
+ "section": "Autolinks (extension)",
+ "extensions": [
+ "autolink"
+ ]
+ },
+ {
+ "markdown": "Visit www.commonmark.org/help for more information.\n",
+ "html": "<p>Visit <a href=\"http://www.commonmark.org/help\">www.commonmark.org/help</a> for more information.</p>\n",
+ "example": 622,
+ "start_line": 9221,
+ "end_line": 9225,
+ "section": "Autolinks (extension)",
+ "extensions": [
+ "autolink"
+ ]
+ },
+ {
+ "markdown": "Visit www.commonmark.org.\n\nVisit www.commonmark.org/a.b.\n",
+ "html": "<p>Visit <a href=\"http://www.commonmark.org\">www.commonmark.org</a>.</p>\n<p>Visit <a href=\"http://www.commonmark.org/a.b\">www.commonmark.org/a.b</a>.</p>\n",
+ "example": 623,
+ "start_line": 9233,
+ "end_line": 9240,
+ "section": "Autolinks (extension)",
+ "extensions": [
+ "autolink"
+ ]
+ },
+ {
+ "markdown": "www.google.com/search?q=Markup+(business)\n\nwww.google.com/search?q=Markup+(business)))\n\n(www.google.com/search?q=Markup+(business))\n\n(www.google.com/search?q=Markup+(business)\n",
+ "html": "<p><a href=\"http://www.google.com/search?q=Markup+(business)\">www.google.com/search?q=Markup+(business)</a></p>\n<p><a href=\"http://www.google.com/search?q=Markup+(business)\">www.google.com/search?q=Markup+(business)</a>))</p>\n<p>(<a href=\"http://www.google.com/search?q=Markup+(business)\">www.google.com/search?q=Markup+(business)</a>)</p>\n<p>(<a href=\"http://www.google.com/search?q=Markup+(business)\">www.google.com/search?q=Markup+(business)</a></p>\n",
+ "example": 624,
+ "start_line": 9247,
+ "end_line": 9260,
+ "section": "Autolinks (extension)",
+ "extensions": [
+ "autolink"
+ ]
+ },
+ {
+ "markdown": "www.google.com/search?q=(business))+ok\n",
+ "html": "<p><a href=\"http://www.google.com/search?q=(business))+ok\">www.google.com/search?q=(business))+ok</a></p>\n",
+ "example": 625,
+ "start_line": 9266,
+ "end_line": 9270,
+ "section": "Autolinks (extension)",
+ "extensions": [
+ "autolink"
+ ]
+ },
+ {
+ "markdown": "www.google.com/search?q=commonmark&hl=en\n\nwww.google.com/search?q=commonmark&hl;\n",
+ "html": "<p><a href=\"http://www.google.com/search?q=commonmark&hl=en\">www.google.com/search?q=commonmark&hl=en</a></p>\n<p><a href=\"http://www.google.com/search?q=commonmark\">www.google.com/search?q=commonmark</a>&hl;</p>\n",
+ "example": 626,
+ "start_line": 9277,
+ "end_line": 9284,
+ "section": "Autolinks (extension)",
+ "extensions": [
+ "autolink"
+ ]
+ },
+ {
+ "markdown": "www.commonmark.org/he<lp\n",
+ "html": "<p><a href=\"http://www.commonmark.org/he\">www.commonmark.org/he</a><lp</p>\n",
+ "example": 627,
+ "start_line": 9288,
+ "end_line": 9292,
+ "section": "Autolinks (extension)",
+ "extensions": [
+ "autolink"
+ ]
+ },
+ {
+ "markdown": "http://commonmark.org\n\n(Visit https://encrypted.google.com/search?q=Markup+(business))\n\nAnonymous FTP is available at ftp://foo.bar.baz.\n",
+ "html": "<p><a href=\"http://commonmark.org\">http://commonmark.org</a></p>\n<p>(Visit <a href=\"https://encrypted.google.com/search?q=Markup+(business)\">https://encrypted.google.com/search?q=Markup+(business)</a>)</p>\n<p>Anonymous FTP is available at <a href=\"ftp://foo.bar.baz\">ftp://foo.bar.baz</a>.</p>\n",
+ "example": 628,
+ "start_line": 9299,
+ "end_line": 9309,
+ "section": "Autolinks (extension)",
+ "extensions": [
+ "autolink"
+ ]
+ },
+ {
+ "markdown": "foo@bar.baz\n",
+ "html": "<p><a href=\"mailto:foo@bar.baz\">foo@bar.baz</a></p>\n",
+ "example": 629,
+ "start_line": 9325,
+ "end_line": 9329,
+ "section": "Autolinks (extension)",
+ "extensions": [
+ "autolink"
+ ]
+ },
+ {
+ "markdown": "hello@mail+xyz.example isn't valid, but hello+xyz@mail.example is.\n",
+ "html": "<p>hello@mail+xyz.example isn't valid, but <a href=\"mailto:hello+xyz@mail.example\">hello+xyz@mail.example</a> is.</p>\n",
+ "example": 630,
+ "start_line": 9333,
+ "end_line": 9337,
+ "section": "Autolinks (extension)",
+ "extensions": [
+ "autolink"
+ ]
+ },
+ {
+ "markdown": "a.b-c_d@a.b\n\na.b-c_d@a.b.\n\na.b-c_d@a.b-\n\na.b-c_d@a.b_\n",
+ "html": "<p><a href=\"mailto:a.b-c_d@a.b\">a.b-c_d@a.b</a></p>\n<p><a href=\"mailto:a.b-c_d@a.b\">a.b-c_d@a.b</a>.</p>\n<p>a.b-c_d@a.b-</p>\n<p>a.b-c_d@a.b_</p>\n",
+ "example": 631,
+ "start_line": 9343,
+ "end_line": 9356,
+ "section": "Autolinks (extension)",
+ "extensions": [
+ "autolink"
+ ]
+ },
+ {
+ "markdown": "<a><bab><c2c>\n",
+ "html": "<p><a><bab><c2c></p>\n",
+ "example": 632,
+ "start_line": 9434,
+ "end_line": 9438,
+ "section": "Raw HTML",
+ "extensions": []
+ },
+ {
+ "markdown": "<a/><b2/>\n",
+ "html": "<p><a/><b2/></p>\n",
+ "example": 633,
+ "start_line": 9443,
+ "end_line": 9447,
+ "section": "Raw HTML",
+ "extensions": []
+ },
+ {
+ "markdown": "<a /><b2\ndata=\"foo\" >\n",
+ "html": "<p><a /><b2\ndata=\"foo\" ></p>\n",
+ "example": 634,
+ "start_line": 9452,
+ "end_line": 9458,
+ "section": "Raw HTML",
+ "extensions": []
+ },
+ {
+ "markdown": "<a foo=\"bar\" bam = 'baz <em>\"</em>'\n_boolean zoop:33=zoop:33 />\n",
+ "html": "<p><a foo=\"bar\" bam = 'baz <em>\"</em>'\n_boolean zoop:33=zoop:33 /></p>\n",
+ "example": 635,
+ "start_line": 9463,
+ "end_line": 9469,
+ "section": "Raw HTML",
+ "extensions": []
+ },
+ {
+ "markdown": "Foo <responsive-image src=\"foo.jpg\" />\n",
+ "html": "<p>Foo <responsive-image src=\"foo.jpg\" /></p>\n",
+ "example": 636,
+ "start_line": 9474,
+ "end_line": 9478,
+ "section": "Raw HTML",
+ "extensions": []
+ },
+ {
+ "markdown": "<33> <__>\n",
+ "html": "<p><33> <__></p>\n",
+ "example": 637,
+ "start_line": 9483,
+ "end_line": 9487,
+ "section": "Raw HTML",
+ "extensions": []
+ },
+ {
+ "markdown": "<a h*#ref=\"hi\">\n",
+ "html": "<p><a h*#ref="hi"></p>\n",
+ "example": 638,
+ "start_line": 9492,
+ "end_line": 9496,
+ "section": "Raw HTML",
+ "extensions": []
+ },
+ {
+ "markdown": "<a href=\"hi'> <a href=hi'>\n",
+ "html": "<p><a href="hi'> <a href=hi'></p>\n",
+ "example": 639,
+ "start_line": 9501,
+ "end_line": 9505,
+ "section": "Raw HTML",
+ "extensions": []
+ },
+ {
+ "markdown": "< a><\nfoo><bar/ >\n<foo bar=baz\nbim!bop />\n",
+ "html": "<p>< a><\nfoo><bar/ >\n<foo bar=baz\nbim!bop /></p>\n",
+ "example": 640,
+ "start_line": 9510,
+ "end_line": 9520,
+ "section": "Raw HTML",
+ "extensions": []
+ },
+ {
+ "markdown": "<a href='bar'title=title>\n",
+ "html": "<p><a href='bar'title=title></p>\n",
+ "example": 641,
+ "start_line": 9525,
+ "end_line": 9529,
+ "section": "Raw HTML",
+ "extensions": []
+ },
+ {
+ "markdown": "</a></foo >\n",
+ "html": "<p></a></foo ></p>\n",
+ "example": 642,
+ "start_line": 9534,
+ "end_line": 9538,
+ "section": "Raw HTML",
+ "extensions": []
+ },
+ {
+ "markdown": "</a href=\"foo\">\n",
+ "html": "<p></a href="foo"></p>\n",
+ "example": 643,
+ "start_line": 9543,
+ "end_line": 9547,
+ "section": "Raw HTML",
+ "extensions": []
+ },
+ {
+ "markdown": "foo <!-- this is a --\ncomment - with hyphens -->\n",
+ "html": "<p>foo <!-- this is a --\ncomment - with hyphens --></p>\n",
+ "example": 644,
+ "start_line": 9552,
+ "end_line": 9558,
+ "section": "Raw HTML",
+ "extensions": []
+ },
+ {
+ "markdown": "foo <!--> foo -->\n\nfoo <!---> foo -->\n",
+ "html": "<p>foo <!--> foo --></p>\n<p>foo <!---> foo --></p>\n",
+ "example": 645,
+ "start_line": 9560,
+ "end_line": 9567,
+ "section": "Raw HTML",
+ "extensions": []
+ },
+ {
+ "markdown": "foo <?php echo $a; ?>\n",
+ "html": "<p>foo <?php echo $a; ?></p>\n",
+ "example": 646,
+ "start_line": 9572,
+ "end_line": 9576,
+ "section": "Raw HTML",
+ "extensions": []
+ },
+ {
+ "markdown": "foo <!ELEMENT br EMPTY>\n",
+ "html": "<p>foo <!ELEMENT br EMPTY></p>\n",
+ "example": 647,
+ "start_line": 9581,
+ "end_line": 9585,
+ "section": "Raw HTML",
+ "extensions": []
+ },
+ {
+ "markdown": "foo <![CDATA[>&<]]>\n",
+ "html": "<p>foo <![CDATA[>&<]]></p>\n",
+ "example": 648,
+ "start_line": 9590,
+ "end_line": 9594,
+ "section": "Raw HTML",
+ "extensions": []
+ },
+ {
+ "markdown": "foo <a href=\"ö\">\n",
+ "html": "<p>foo <a href=\"ö\"></p>\n",
+ "example": 649,
+ "start_line": 9600,
+ "end_line": 9604,
+ "section": "Raw HTML",
+ "extensions": []
+ },
+ {
+ "markdown": "foo <a href=\"\\*\">\n",
+ "html": "<p>foo <a href=\"\\*\"></p>\n",
+ "example": 650,
+ "start_line": 9609,
+ "end_line": 9613,
+ "section": "Raw HTML",
+ "extensions": []
+ },
+ {
+ "markdown": "<a href=\"\\\"\">\n",
+ "html": "<p><a href="""></p>\n",
+ "example": 651,
+ "start_line": 9616,
+ "end_line": 9620,
+ "section": "Raw HTML",
+ "extensions": []
+ },
+ {
+ "markdown": "<strong> <title> <style> <em>\n\n<blockquote>\n <xmp> is disallowed. <XMP> is also disallowed.\n</blockquote>\n",
+ "html": "<p><strong> <title> <style> <em></p>\n<blockquote>\n <xmp> is disallowed. <XMP> is also disallowed.\n</blockquote>\n",
+ "example": 652,
+ "start_line": 9647,
+ "end_line": 9658,
+ "section": "Disallowed Raw HTML (extension)",
+ "extensions": [
+ "tagfilter"
+ ]
+ },
+ {
+ "markdown": "foo \nbaz\n",
+ "html": "<p>foo<br />\nbaz</p>\n",
+ "example": 653,
+ "start_line": 9669,
+ "end_line": 9675,
+ "section": "Hard line breaks",
+ "extensions": []
+ },
+ {
+ "markdown": "foo\\\nbaz\n",
+ "html": "<p>foo<br />\nbaz</p>\n",
+ "example": 654,
+ "start_line": 9681,
+ "end_line": 9687,
+ "section": "Hard line breaks",
+ "extensions": []
+ },
+ {
+ "markdown": "foo \nbaz\n",
+ "html": "<p>foo<br />\nbaz</p>\n",
+ "example": 655,
+ "start_line": 9692,
+ "end_line": 9698,
+ "section": "Hard line breaks",
+ "extensions": []
+ },
+ {
+ "markdown": "foo \n bar\n",
+ "html": "<p>foo<br />\nbar</p>\n",
+ "example": 656,
+ "start_line": 9703,
+ "end_line": 9709,
+ "section": "Hard line breaks",
+ "extensions": []
+ },
+ {
+ "markdown": "foo\\\n bar\n",
+ "html": "<p>foo<br />\nbar</p>\n",
+ "example": 657,
+ "start_line": 9712,
+ "end_line": 9718,
+ "section": "Hard line breaks",
+ "extensions": []
+ },
+ {
+ "markdown": "*foo \nbar*\n",
+ "html": "<p><em>foo<br />\nbar</em></p>\n",
+ "example": 658,
+ "start_line": 9724,
+ "end_line": 9730,
+ "section": "Hard line breaks",
+ "extensions": []
+ },
+ {
+ "markdown": "*foo\\\nbar*\n",
+ "html": "<p><em>foo<br />\nbar</em></p>\n",
+ "example": 659,
+ "start_line": 9733,
+ "end_line": 9739,
+ "section": "Hard line breaks",
+ "extensions": []
+ },
+ {
+ "markdown": "`code \nspan`\n",
+ "html": "<p><code>code span</code></p>\n",
+ "example": 660,
+ "start_line": 9744,
+ "end_line": 9749,
+ "section": "Hard line breaks",
+ "extensions": []
+ },
+ {
+ "markdown": "`code\\\nspan`\n",
+ "html": "<p><code>code\\ span</code></p>\n",
+ "example": 661,
+ "start_line": 9752,
+ "end_line": 9757,
+ "section": "Hard line breaks",
+ "extensions": []
+ },
+ {
+ "markdown": "<a href=\"foo \nbar\">\n",
+ "html": "<p><a href=\"foo \nbar\"></p>\n",
+ "example": 662,
+ "start_line": 9762,
+ "end_line": 9768,
+ "section": "Hard line breaks",
+ "extensions": []
+ },
+ {
+ "markdown": "<a href=\"foo\\\nbar\">\n",
+ "html": "<p><a href=\"foo\\\nbar\"></p>\n",
+ "example": 663,
+ "start_line": 9771,
+ "end_line": 9777,
+ "section": "Hard line breaks",
+ "extensions": []
+ },
+ {
+ "markdown": "foo\\\n",
+ "html": "<p>foo\\</p>\n",
+ "example": 664,
+ "start_line": 9784,
+ "end_line": 9788,
+ "section": "Hard line breaks",
+ "extensions": []
+ },
+ {
+ "markdown": "foo \n",
+ "html": "<p>foo</p>\n",
+ "example": 665,
+ "start_line": 9791,
+ "end_line": 9795,
+ "section": "Hard line breaks",
+ "extensions": []
+ },
+ {
+ "markdown": "### foo\\\n",
+ "html": "<h3>foo\\</h3>\n",
+ "example": 666,
+ "start_line": 9798,
+ "end_line": 9802,
+ "section": "Hard line breaks",
+ "extensions": []
+ },
+ {
+ "markdown": "### foo \n",
+ "html": "<h3>foo</h3>\n",
+ "example": 667,
+ "start_line": 9805,
+ "end_line": 9809,
+ "section": "Hard line breaks",
+ "extensions": []
+ },
+ {
+ "markdown": "foo\nbaz\n",
+ "html": "<p>foo\nbaz</p>\n",
+ "example": 668,
+ "start_line": 9820,
+ "end_line": 9826,
+ "section": "Soft line breaks",
+ "extensions": []
+ },
+ {
+ "markdown": "foo \n baz\n",
+ "html": "<p>foo\nbaz</p>\n",
+ "example": 669,
+ "start_line": 9832,
+ "end_line": 9838,
+ "section": "Soft line breaks",
+ "extensions": []
+ },
+ {
+ "markdown": "hello $.;'there\n",
+ "html": "<p>hello $.;'there</p>\n",
+ "example": 670,
+ "start_line": 9852,
+ "end_line": 9856,
+ "section": "Textual content",
+ "extensions": []
+ },
+ {
+ "markdown": "Foo \u03c7\u03c1\u1fc6\u03bd\n",
+ "html": "<p>Foo \u03c7\u03c1\u1fc6\u03bd</p>\n",
+ "example": 671,
+ "start_line": 9859,
+ "end_line": 9863,
+ "section": "Textual content",
+ "extensions": []
+ },
+ {
+ "markdown": "Multiple spaces\n",
+ "html": "<p>Multiple spaces</p>\n",
+ "example": 672,
+ "start_line": 9868,
+ "end_line": 9872,
+ "section": "Textual content",
+ "extensions": []
+ }
+]
\ No newline at end of file
diff --git a/pkgs/markdown/tool/stats.dart b/pkgs/markdown/tool/stats.dart
new file mode 100644
index 0000000..67b1d39
--- /dev/null
+++ b/pkgs/markdown/tool/stats.dart
@@ -0,0 +1,266 @@
+// Copyright (c) 2020, 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:collection';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:args/args.dart';
+import 'package:collection/collection.dart';
+import 'package:path/path.dart' as p;
+
+import '../tool/expected_output.dart';
+import 'stats_lib.dart';
+
+final _configs = List<Config>.unmodifiable([
+ Config.commonMarkConfig,
+ Config.gfmConfig,
+]);
+
+Future<void> main(List<String> args) async {
+ final parser = ArgParser()
+ ..addOption(
+ 'section',
+ help: 'Restrict tests to one section, provided after the option.',
+ )
+ ..addFlag(
+ 'raw',
+ help: 'raw JSON format',
+ negatable: false,
+ )
+ ..addFlag(
+ 'update-files',
+ help: 'Update stats files in $toolDir',
+ negatable: false,
+ )
+ ..addFlag(
+ 'verbose',
+ help: 'Print details for failures and errors.',
+ negatable: false,
+ )
+ ..addFlag(
+ 'verbose-loose',
+ help: 'Print details for "loose" matches.',
+ negatable: false,
+ )
+ ..addOption('flavor', allowed: _configs.map((c) => c.prefix))
+ ..addFlag('help', negatable: false);
+
+ ArgResults options;
+
+ try {
+ options = parser.parse(args);
+ } on FormatException catch (e) {
+ stderr.writeln(e);
+ print(parser.usage);
+ exitCode = 64; // unix standard improper usage
+ return;
+ }
+
+ if (options['help'] as bool) {
+ print(parser.usage);
+ return;
+ }
+
+ final specifiedSection = options['section'] as String?;
+ final raw = options['raw'] as bool;
+ final verbose = options['verbose'] as bool;
+ final verboseLooseMatch = options['verbose-loose'] as bool;
+ final updateFiles = options['update-files'] as bool;
+
+ if (updateFiles && (raw || verbose || (specifiedSection != null))) {
+ stderr.writeln('The `update-files` flag must be used by itself');
+ print(parser.usage);
+ exitCode = 64; // unix standard improper usage
+ return;
+ }
+
+ var testPrefix = options['flavor'] as String?;
+ if (!updateFiles) {
+ testPrefix = _configs.first.prefix;
+ }
+
+ final testPrefixes =
+ testPrefix == null ? _configs.map((c) => c.prefix) : <String>[testPrefix];
+
+ for (final testPrefix in testPrefixes) {
+ await _processConfig(
+ testPrefix,
+ raw,
+ updateFiles,
+ verbose,
+ specifiedSection,
+ verboseLooseMatch,
+ );
+ }
+}
+
+final _sectionNameReplace = RegExp(r'[ \)\(]+');
+
+String _unitOutput(Iterable<DataCase> cases) => cases.map((dataCase) => '''
+>>> ${dataCase.front_matter}
+${dataCase.input}<<<
+${dataCase.expectedOutput}''').join();
+
+/// Set this to `true` and rerun `--update-files` to ease finding easy strict
+/// fixes.
+const _improveStrict = false;
+
+Future<void> _processConfig(
+ String testPrefix,
+ bool raw,
+ bool updateFiles,
+ bool verbose,
+ String? specifiedSection,
+ bool verboseLooseMatch,
+) async {
+ final config = _configs.singleWhere((c) => c.prefix == testPrefix);
+
+ final sections = loadCommonMarkSections(testPrefix);
+
+ final scores = SplayTreeMap<String, SplayTreeMap<int, CompareLevel>>(
+ compareAsciiLowerCaseNatural);
+
+ for (final entry in sections.entries) {
+ if (specifiedSection != null && entry.key != specifiedSection) {
+ continue;
+ }
+
+ final units = <DataCase>[];
+
+ for (final e in entry.value) {
+ final result = compareResult(
+ config,
+ e,
+ verboseFail: verbose,
+ verboseLooseMatch: verboseLooseMatch,
+ extensions: e.extensions,
+ );
+
+ units.add(DataCase(
+ front_matter: result.testCase.toString(),
+ input: result.testCase.markdown,
+ expectedOutput:
+ (_improveStrict && result.compareLevel == CompareLevel.loose)
+ ? result.testCase.html
+ : result.result!,
+ ));
+
+ final nestedMap = scores.putIfAbsent(
+ entry.key,
+ SplayTreeMap<int, CompareLevel>.new,
+ );
+ nestedMap[e.example] = result.compareLevel;
+ }
+
+ if (updateFiles && units.isNotEmpty) {
+ var fileName =
+ entry.key.toLowerCase().replaceAll(_sectionNameReplace, '_');
+ while (fileName.endsWith('_')) {
+ fileName = fileName.substring(0, fileName.length - 1);
+ }
+ fileName = '$fileName.unit';
+ File(p.join('test', testPrefix, fileName))
+ ..createSync(recursive: true)
+ ..writeAsStringSync(_unitOutput(units));
+ }
+ }
+
+ if (raw || updateFiles) {
+ await _printRaw(testPrefix, scores, updateFiles);
+ }
+
+ if (!raw || updateFiles) {
+ await _printFriendly(testPrefix, scores, updateFiles);
+ }
+}
+
+Object? _convert(Object? obj) {
+ if (obj is CompareLevel) {
+ return obj.name;
+ }
+ if (obj is Map) {
+ return obj
+ .map<String, Object?>((key, value) => MapEntry(key.toString(), value));
+ }
+ return obj;
+}
+
+Future<void> _printRaw(
+ String testPrefix,
+ Map<String, Map<int, CompareLevel>> scores,
+ bool updateFiles,
+) async {
+ IOSink sink;
+ if (updateFiles) {
+ final file = getStatsFile(testPrefix);
+ print('Updating ${file.path}');
+ sink = file.openWrite();
+ } else {
+ sink = stdout;
+ }
+
+ const encoder = JsonEncoder.withIndent(' ', _convert);
+ try {
+ sink.writeln(encoder.convert(scores));
+ // ignore: avoid_catching_errors
+ } on JsonUnsupportedObjectError catch (e) {
+ stderr.writeln(e.cause);
+ stderr.writeln(e.unsupportedObject.runtimeType);
+ rethrow;
+ }
+
+ await sink.flush();
+ await sink.close();
+}
+
+String _pct(int value, int total, String section) =>
+ '${value.toString().padLeft(4)} '
+ 'of ${total.toString().padLeft(4)} '
+ '– ${(100 * value / total).toStringAsFixed(1).padLeft(5)}% $section';
+
+Future<void> _printFriendly(
+ String testPrefix,
+ SplayTreeMap<String, SplayTreeMap<int, CompareLevel>> scores,
+ bool updateFiles,
+) async {
+ var totalValid = 0;
+ var totalStrict = 0;
+ var totalExamples = 0;
+
+ IOSink sink;
+ if (updateFiles) {
+ final path = p.join(toolDir, '${testPrefix}_stats.txt');
+ print('Updating $path');
+ final file = File(path);
+ sink = file.openWrite();
+ } else {
+ sink = stdout;
+ }
+
+ scores.forEach((section, Map<int, CompareLevel> map) {
+ final total = map.values.length;
+ totalExamples += total;
+
+ final sectionStrictCount =
+ map.values.where((val) => val == CompareLevel.strict).length;
+
+ final sectionLooseCount =
+ map.values.where((val) => val == CompareLevel.loose).length;
+
+ final sectionValidCount = sectionStrictCount + sectionLooseCount;
+
+ totalStrict += sectionStrictCount;
+ totalValid += sectionValidCount;
+
+ sink.writeln(_pct(sectionValidCount, total, section));
+ });
+
+ sink.writeln(_pct(totalValid, totalExamples, 'TOTAL'));
+ sink.writeln(_pct(totalStrict, totalValid, 'TOTAL Strict'));
+
+ await sink.flush();
+ await sink.close();
+}
diff --git a/pkgs/markdown/tool/stats_lib.dart b/pkgs/markdown/tool/stats_lib.dart
new file mode 100644
index 0000000..5ff6321
--- /dev/null
+++ b/pkgs/markdown/tool/stats_lib.dart
@@ -0,0 +1,276 @@
+// 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 'dart:mirrors';
+
+import 'package:html/dom.dart' show Element;
+import 'package:html/parser.dart' show parseFragment;
+import 'package:markdown/markdown.dart'
+ show
+ AutolinkExtensionSyntax,
+ BlockSyntax,
+ InlineSyntax,
+ StrikethroughSyntax,
+ TableSyntax,
+ markdownToHtml;
+import 'package:path/path.dart' as p;
+
+import '../test/util.dart';
+
+// Locate the "tool" directory. Use mirrors so that this works with the test
+// package, which loads this suite into an isolate.
+String get toolDir {
+ final path = (reflect(loadCommonMarkSections) as ClosureMirror)
+ .function
+ .location!
+ .sourceUri
+ .path;
+
+ return p.dirname(path);
+}
+
+File getStatsFile(String prefix) =>
+ File(p.join(toolDir, '${prefix}_stats.json'));
+
+Map<String, List<CommonMarkTestCase>> loadCommonMarkSections(
+ String testPrefix) {
+ final testFile = File(p.join(toolDir, '${testPrefix}_tests.json'));
+ final testsJson = testFile.readAsStringSync();
+
+ final testArray = jsonDecode(testsJson) as List;
+
+ final sections = <String, List<CommonMarkTestCase>>{};
+
+ for (final exampleMap in testArray) {
+ final exampleTest =
+ CommonMarkTestCase.fromJson(exampleMap as Map<String, dynamic>);
+
+ final sectionList =
+ sections.putIfAbsent(exampleTest.section, () => <CommonMarkTestCase>[]);
+
+ sectionList.add(exampleTest);
+ }
+
+ return sections;
+}
+
+class Config {
+ static final Config commonMarkConfig = Config._(
+ 'common_mark',
+ 'https://spec.commonmark.org/0.30/',
+ );
+ static final Config gfmConfig = Config._(
+ 'gfm',
+ 'https://github.github.com/gfm/',
+ );
+
+ final String prefix;
+ final String baseUrl;
+
+ Config._(this.prefix, this.baseUrl);
+}
+
+class CommonMarkTestCase {
+ final String markdown;
+ final String section;
+ final int example;
+ final String html;
+ final int startLine;
+ final int endLine;
+ final Set<String> extensions;
+
+ CommonMarkTestCase(
+ this.example,
+ this.section,
+ this.startLine,
+ this.endLine,
+ this.markdown,
+ this.html,
+ this.extensions,
+ );
+
+ factory CommonMarkTestCase.fromJson(Map<String, dynamic> json) {
+ return CommonMarkTestCase(
+ json['example'] as int,
+ json['section'] as String /*!*/,
+ json['start_line'] as int,
+ json['end_line'] as int,
+ json['markdown'] as String /*!*/,
+ json['html'] as String,
+ json['extensions'] == null
+ ? const {}
+ : Set.from(json['extensions'] as List),
+ );
+ }
+
+ @override
+ String toString() => '$section - $example';
+}
+
+enum CompareLevel { strict, loose, fail, error }
+
+class CompareResult {
+ final CompareLevel compareLevel;
+ final CommonMarkTestCase testCase;
+ final String? result;
+
+ CompareResult(this.testCase, this.result, this.compareLevel);
+}
+
+CompareResult compareResult(
+ Config config,
+ CommonMarkTestCase testCase, {
+ bool throwOnError = false,
+ bool verboseFail = false,
+ bool verboseLooseMatch = false,
+ Set<String> extensions = const {},
+}) {
+ var enabletagfilter = false;
+
+ String output;
+ final inlineSyntaxes = <InlineSyntax>[];
+ final blockSyntaxes = <BlockSyntax>[];
+
+ for (final extension in extensions) {
+ switch (extension) {
+ case 'autolink':
+ inlineSyntaxes.add(AutolinkExtensionSyntax());
+ break;
+ case 'strikethrough':
+ inlineSyntaxes.add(StrikethroughSyntax());
+ break;
+ case 'table':
+ blockSyntaxes.add(const TableSyntax());
+ break;
+ case 'tagfilter':
+ enabletagfilter = true;
+ break;
+ default:
+ throw UnimplementedError('Unimplemented extension "$extension"');
+ }
+ }
+
+ try {
+ output = markdownToHtml(
+ testCase.markdown,
+ inlineSyntaxes: inlineSyntaxes,
+ blockSyntaxes: blockSyntaxes,
+ enableTagfilter: enabletagfilter,
+ );
+ } catch (err, stackTrace) {
+ if (throwOnError) {
+ rethrow;
+ }
+ if (verboseFail) {
+ _printVerboseFailure(
+ config.baseUrl,
+ 'ERROR',
+ testCase,
+ 'Thrown: $err\n$stackTrace',
+ );
+ }
+
+ return CompareResult(testCase, null, CompareLevel.error);
+ }
+
+ if (testCase.html == output) {
+ return CompareResult(testCase, output, CompareLevel.strict);
+ }
+
+ final expectedParsed = parseFragment(testCase.html);
+ final actual = parseFragment(output);
+
+ final looseMatch = _compareHtml(expectedParsed.children, actual.children);
+
+ if (!looseMatch && verboseFail) {
+ _printVerboseFailure(config.baseUrl, 'FAIL', testCase, output);
+ }
+
+ if (looseMatch && verboseLooseMatch) {
+ _printVerboseFailure(config.baseUrl, 'LOOSE', testCase, output);
+ }
+
+ return CompareResult(
+ testCase,
+ output,
+ looseMatch ? CompareLevel.loose : CompareLevel.fail,
+ );
+}
+
+String _indent(String s) =>
+ s.splitMapJoin('\n', onNonMatch: (n) => ' ${whitespaceColor(n)}');
+
+void _printVerboseFailure(
+ String baseUrl,
+ String message,
+ CommonMarkTestCase testCase,
+ String actual,
+) {
+ print('$message: $baseUrl#example-${testCase.example} '
+ '@ ${testCase.section}');
+ print('input:');
+ print(_indent(testCase.markdown));
+ print('expected:');
+ print(_indent(testCase.html));
+ print('actual:');
+ print(_indent(actual));
+ print('-----------------------');
+}
+
+/// Compare two DOM trees for equality.
+bool _compareHtml(
+ List<Element> expectedElements,
+ List<Element> actualElements,
+) {
+ if (expectedElements.length != actualElements.length) {
+ return false;
+ }
+
+ for (var childNum = 0; childNum < expectedElements.length; childNum++) {
+ final expected = expectedElements[childNum];
+ final actual = actualElements[childNum];
+
+ if (expected.runtimeType != actual.runtimeType) {
+ return false;
+ }
+
+ if (expected.localName != actual.localName) {
+ return false;
+ }
+
+ if (expected.attributes.length != actual.attributes.length) {
+ return false;
+ }
+
+ final expectedAttrKeys = expected.attributes.keys.toList();
+ expectedAttrKeys.sort();
+
+ final actualAttrKeys = actual.attributes.keys.toList();
+ actualAttrKeys.sort();
+
+ for (var attrNum = 0; attrNum < actualAttrKeys.length; attrNum++) {
+ final expectedAttrKey = expectedAttrKeys[attrNum];
+ final actualAttrKey = actualAttrKeys[attrNum];
+
+ if (expectedAttrKey != actualAttrKey) {
+ return false;
+ }
+
+ if (expected.attributes[expectedAttrKey] !=
+ actual.attributes[actualAttrKey]) {
+ return false;
+ }
+ }
+
+ final childrenEqual = _compareHtml(expected.children, actual.children);
+
+ if (!childrenEqual) {
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/pkgs/markdown/tool/update-gh-pages.sh b/pkgs/markdown/tool/update-gh-pages.sh
new file mode 100755
index 0000000..97762ad
--- /dev/null
+++ b/pkgs/markdown/tool/update-gh-pages.sh
@@ -0,0 +1,13 @@
+# Echo every command being run.
+set +x
+
+# Fail fast if a command fails.
+set -e
+
+dart pub global activate peanut
+
+peanut -d example
+
+echo Now push updated gh-pages branch with:
+echo
+echo ' git push origin --set-upstream gh-pages'
diff --git a/pkgs/markdown/tool/update_blns.dart b/pkgs/markdown/tool/update_blns.dart
new file mode 100644
index 0000000..fbdabca
--- /dev/null
+++ b/pkgs/markdown/tool/update_blns.dart
@@ -0,0 +1,33 @@
+import 'dart:async';
+import 'dart:io';
+
+import 'update_shared.dart';
+
+const _blnsJsonRawUrl =
+ 'https://github.com/minimaxir/big-list-of-naughty-strings/raw/master/blns.json';
+const _blnsFilePath = 'test/blns.dart';
+
+Future<void> main() async {
+ final json = (await downloadJson(_blnsJsonRawUrl) as List).cast<String>();
+ final blnsContent = StringBuffer('''
+// GENERATED FILE. DO NOT EDIT.
+//
+// This file was generated from big-list-of-naughty-strings's JSON file:
+// $_blnsJsonRawUrl
+// at ${DateTime.now()} by the script, tool/update_blns.dart.
+
+// ignore_for_file: text_direction_code_point_in_literal, use_raw_strings
+// ignore_for_file: lines_longer_than_80_chars
+
+''');
+ blnsContent.writeln('const blns = <String>[');
+ for (final str in json) {
+ final escaped = str
+ .replaceAll(r'\', r'\\')
+ .replaceAll("'", r"\'")
+ .replaceAll(r'$', r'\$');
+ blnsContent.writeln(" '$escaped',");
+ }
+ blnsContent.writeln('];');
+ File(_blnsFilePath).writeAsStringSync(blnsContent.toString());
+}
diff --git a/pkgs/markdown/tool/update_case_folding.dart b/pkgs/markdown/tool/update_case_folding.dart
new file mode 100644
index 0000000..e53f70b
--- /dev/null
+++ b/pkgs/markdown/tool/update_case_folding.dart
@@ -0,0 +1,51 @@
+// 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 'dart:convert';
+import 'dart:io';
+
+import 'package:path/path.dart' as p;
+
+// Generates and updates unicode case folding map.
+// Here only extract status C + F capital letters.
+void main() {
+ // Downloaded from http://www.unicode.org/Public/14.0.0/ucd/CaseFolding.txt
+ final file = File('${p.current}/tool/case_folding.txt');
+
+ final result = <String, String>{};
+
+ for (final line in file.readAsLinesSync()) {
+ if (line.startsWith('#') ||
+ line.trim().isEmpty ||
+ !line.contains('CAPITAL LETTER')) {
+ continue;
+ }
+
+ final content = line.substring(0, line.indexOf('#'));
+ final match =
+ RegExp(r'([0-9A-F]{1,6});\s+[CF];\s+(.+);').firstMatch(content);
+ if (match == null) {
+ continue;
+ }
+
+ final key = String.fromCharCode(int.parse(match[1]!, radix: 16));
+ final value = match[2]!.split(RegExp('[ ]+')).map((e) {
+ return String.fromCharCode(int.parse(e, radix: 16));
+ }).join();
+ result[key] = value;
+ }
+
+ final outputPath = '${p.current}/lib/src/assets/case_folding.dart';
+ final stringMap = const JsonEncoder.withIndent(' ').convert(result);
+ final output = '''
+// Generated file. do not edit.
+//
+// Source: tool/case_folding.txt
+// Script: tool/update_case_folding.dart
+// ignore_for_file: prefer_single_quotes
+
+const caseFoldingMap = $stringMap;
+''';
+ File(outputPath).writeAsStringSync(output);
+}
diff --git a/pkgs/markdown/tool/update_emojis.dart b/pkgs/markdown/tool/update_emojis.dart
new file mode 100644
index 0000000..0b2b8a9
--- /dev/null
+++ b/pkgs/markdown/tool/update_emojis.dart
@@ -0,0 +1,56 @@
+// Copyright (c) 2017, 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';
+
+import 'update_shared.dart';
+
+// update_github_emojis.dart now generates the emoji list using the GitHub API
+// to retrieve the emoji list. It uses this emoji source as a source to keep
+// binary compatibility with the Unicode sequences for each emoji found here.
+const _emojisJsonRawUrl =
+ 'https://raw.githubusercontent.com/muan/emojilib/v2.4.0/emojis.json';
+const _emojisFilePath = 'lib/src/legacy_emojis.dart';
+
+Future<void> main() async {
+ final json =
+ (await downloadJson(_emojisJsonRawUrl) as Map<String, dynamic>).map(
+ (String alias, dynamic info) =>
+ MapEntry(alias, info as Map<String, dynamic>),
+ );
+ final emojisContent = StringBuffer('''
+// GENERATED FILE. DO NOT EDIT.
+//
+// This file was generated from emojilib's emoji data file:
+// $_emojisJsonRawUrl
+// at ${DateTime.now()} by the script, tool/update_emojis.dart.
+
+''');
+ emojisContent.writeln('const emojis = <String, String>{');
+ var emojiCount = 0;
+ final ignored = <String>[];
+ // Dump in sorted order now to facilitate comparison with new GitHub emoji.
+ final sortedKeys = json.keys.toList()..sort();
+ for (final alias in sortedKeys) {
+ final info = json[alias] as Map<String, dynamic>;
+ if (info['char'] != null) {
+ emojisContent.writeln(" '$alias': '${info['char']}',");
+ emojiCount++;
+ } else {
+ ignored.add(alias);
+ }
+ }
+ emojisContent.writeln('};');
+ File(_emojisFilePath).writeAsStringSync(emojisContent.toString());
+ print(
+ 'WARNING: This updates only the LEGACY emoji - to update the active '
+ 'emoji recognized by the markdown package, '
+ 'execute `update_github_emojis.dart`.',
+ );
+ print(
+ 'Wrote data to $_emojisFilePath for $emojiCount emoji, '
+ 'ignoring ${ignored.length}: ${ignored.join(', ')}.',
+ );
+}
diff --git a/pkgs/markdown/tool/update_entities.dart b/pkgs/markdown/tool/update_entities.dart
new file mode 100644
index 0000000..2d114bd
--- /dev/null
+++ b/pkgs/markdown/tool/update_entities.dart
@@ -0,0 +1,39 @@
+// 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 'dart:convert';
+import 'dart:io';
+import 'package:path/path.dart' as p;
+
+/// Generates and updates HTML entities.
+void main() {
+ // Original file: https://html.spec.whatwg.org/entities.json
+ final file = File('${p.current}/tool/entities.json');
+ final json = file.readAsStringSync();
+ final map = Map<String, Map<String, dynamic>>.from(jsonDecode(json) as Map);
+
+ final result = <String, String>{};
+ for (final name in map.keys) {
+ if (name.endsWith(';')) {
+ final value = map[name]!['characters'] as String;
+ result[name] = value;
+ }
+ }
+
+ final outputPath = '${p.current}/lib/src/assets/html_entities.dart';
+ final stringMap = const JsonEncoder.withIndent(' ')
+ .convert(result)
+ .replaceAll(r'"$"', r'r"$"')
+ .replaceAll(r'"\\"', r'r"\"');
+ final output = '''
+// Generated file. do not edit.
+//
+// Source: tool/entities.json
+// Script: tool/update_entities.dart
+// ignore_for_file: prefer_single_quotes
+
+const htmlEntitiesMap = $stringMap;
+''';
+ File(outputPath).writeAsStringSync(output);
+}
diff --git a/pkgs/markdown/tool/update_github_emojis.dart b/pkgs/markdown/tool/update_github_emojis.dart
new file mode 100644
index 0000000..fb07ac5
--- /dev/null
+++ b/pkgs/markdown/tool/update_github_emojis.dart
@@ -0,0 +1,348 @@
+// 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 'dart:async';
+import 'dart:io';
+
+import 'package:args/args.dart';
+import 'package:markdown/src/legacy_emojis.dart' as legacy;
+
+import 'update_shared.dart';
+
+/// Regular expression to parse unicode from GitHub emoji API output filenames.
+RegExp gitHubEmojiUnicodeFromFilenamePattern =
+ RegExp(r'.*unicode\/([A-Fa-f0-9\-]+)\.png');
+
+/// URL for GitHub's emoji API. We reconcile with our legacy emoji so that
+/// we don't change or break anything.
+/// There are essentially only TWO (2) emoji that change and the
+/// legacy emoji is still available with an alternate name.
+/// The 'beetle' emoji changes from `🐞` to `🪲`,
+/// legacy available as 'lady_beetle'.
+/// The 'cricket' emoji changes from `🏏` to `🦗`,
+/// legacy available as 'cricket_game'.
+/// (if the -g flag us used to force using the GitHub Unicode sequences for the
+/// emoji then additionally the 'email' emoji changes from '✉️' to '📧').
+const _emojisJsonRawUrl = 'https://api.github.com/emojis';
+const _emojisFilePath = 'lib/src/emojis.dart';
+
+/// Reference to emoji map within legacy_emojis.dart
+const legacyEmojis = legacy.emojis;
+
+/// AUTO GENERATED by `reconcile_emojis.dart` - this only needed to be done ONCE
+/// during the reconciliation process with the legacy emoji.
+/// This array is ONLY USED when the --useGitHubUnicodes option is used to
+/// minimize the visual differences in the output emoji.
+const legacyEmojisUsedVariationModifier = {
+ '263a',
+ '2600',
+ '2601',
+ '2744',
+ '2708',
+ '260e',
+ '2702',
+ '2712',
+ '270f',
+ '2764',
+ 'd83c-de37',
+ '2734',
+ '3299',
+ '3297',
+ 'd83c-dd70',
+ 'd83c-dd71',
+ 'd83c-dd7e',
+ '2668',
+ '203c',
+ '2049',
+ '303d',
+ '26a0',
+ '267b',
+ '2747',
+ '2733',
+ '24c2',
+ 'd83c-de02',
+ 'd83c-dd7f',
+ '23cf',
+ '25b6',
+ '25c0',
+ '27a1',
+ '2b05',
+ '2b06',
+ '2b07',
+ '2197',
+ '2198',
+ '2199',
+ '2196',
+ '2195',
+ '2194',
+ '21aa',
+ '21a9',
+ '2934',
+ '2935',
+ '2139',
+ '3030',
+ '2714',
+ '2716',
+ '00a9',
+ '00ae',
+ '2122',
+ '2611',
+ '25aa',
+ '25ab',
+ '25fc',
+ '25fb',
+ '2660',
+ '2663',
+ '2665',
+ '2666',
+};
+
+/// Special replacement character '�'
+const errorSpecialReplacement = '\u{FFFD}';
+
+const useOfGitHubUnicodeSequencesWarning = '''
+IMPORTANT NOTE: The use of the --useGitHubUnicodes switch will force using
+GitHub Unicode sequences.
+This option is essentially here only for completeness, not for
+release use.
+The slight visual differences of some emoji might also be another
+reason using --useGitHubUnicodes should be considered a *Breaking Change*.
+
+Some test will fail because of the different Unicode sequences
+and the emojis.unit file would need to be updated to contain the new
+expected GitHub versions of the Unicode sequences of the emoji in order
+for the tests to pass.
+''';
+
+/// The GitHub API URL will return a JSON map of all emoji in the form of
+/// `{ 'shortcode':'emojifilename' ... }`.
+/// The filenames are simply a list of all of the hex string of the
+/// *essential* Unicode codepoints representing the emoji.
+/// These sequences exclude the Unicode join zero width (0x200D) and
+/// variation select (0xFE0F) modifiers. (We will need to add these in to
+/// build our actually Unicode strings representing the emoji).
+/// Multiple Unicode codepoints are separated by '-'.
+/// Examples filenames (single and double code point examples):
+/// - "https://github.githubassets.com/images/icons/emoji/unicode/1f643.png?v8"
+/// - "https://github.githubassets.com/images/icons/emoji/unicode/1f1fa-1f1fe.png?v8"
+/// - "https://github.githubassets.com/images/icons/emoji/unicode/1f469-1f469-1f467-1f466.png?v8"
+/// NOTE: Some filenames will be GitHub 'custom' emoji that have
+/// no Unicode equivalent and these will not have hex codepoints,
+/// only the GitHub custom name.
+/// We will ignore these (there are only a 19 and they are mostly pixel art
+/// from the old Doom game).
+/// Example GitHub custom emoji filename:
+/// - "https://github.githubassets.com/images/icons/emoji/godmode.png?v8",
+String parseGitHubFilenameIntoUnicodeString(String emojiFilename) {
+ const variationSelector = 0xFE0F;
+ const zeroWidthJoiner = 0x200D;
+
+ try {
+ final rawHexList = gitHubEmojiUnicodeFromFilenamePattern
+ .firstMatch(emojiFilename)
+ ?.group(1);
+ if (rawHexList == null) {
+ // This is a GitHub custom emoji and it is represented by a PNG image only
+ // and there is no equivalent Unicode. We have to ignore.
+ return '';
+ }
+ var legacyUsedVariationCode = false;
+ if (legacyEmojisUsedVariationModifier.contains(rawHexList)) {
+ legacyUsedVariationCode = true;
+ }
+ final rawCodePointsHex = rawHexList
+ .split('-')
+ .map((hexstr) => int.parse(hexstr, radix: 16))
+ .toList();
+ final codePointsHex = <int>[];
+
+ if (legacyUsedVariationCode) {
+ // Just add single variation selector.
+ codePointsHex.addAll(rawCodePointsHex);
+ codePointsHex.add(variationSelector);
+ } else {
+ // Now insert the join zero width and
+ // variation select modifying Unicode chars.
+ for (var i = 0; i < rawCodePointsHex.length; i++) {
+ final codePointAtIndex = rawCodePointsHex[i];
+ codePointsHex.add(codePointAtIndex);
+ if (i < (rawCodePointsHex.length - 1)) {
+ codePointsHex.add(variationSelector);
+ // # and 0-9 don't use Zero Width Joiner.
+ if (codePointAtIndex == 0x23 ||
+ (codePointAtIndex >= 0x30 && codePointAtIndex <= 0x39)) {
+ // Don't add Zero Width Joiner.
+ } else {
+ codePointsHex.add(zeroWidthJoiner);
+ }
+ }
+ }
+ }
+ return String.fromCharCodes(codePointsHex);
+ } catch (e) {
+ print(
+ 'Invalid/Non-Conformant emoji filename encountered "$emojiFilename"!');
+ return errorSpecialReplacement;
+ }
+}
+
+Future<void> main(List<String> args) async {
+ final parser = ArgParser()
+ ..addFlag('help',
+ abbr: 'h', negatable: false, help: 'Print help text and exit.')
+ ..addFlag('useGitHubUnicodes',
+ abbr: 'g',
+ negatable: false,
+ help: 'Use the GitHub Unicode sequences instead of legacy sequences.')
+ ..addFlag('visualizeDifferentUnicodes',
+ abbr: 'v',
+ negatable: false,
+ help: 'Visualize any Unicode sequence differences.')
+ ..addOption('dumpMarkdownShortCodes',
+ abbr: 's',
+ defaultsTo: 'missing',
+ allowed: ['plain', 'tooltip'],
+ allowedHelp: {
+ 'plain': 'just shortcode',
+ 'tooltip':
+ '(shortcode with a link to provide emoji name in tooltips)',
+ },
+ help: 'Outputs all emoji shortcodes to stdout which can be used '
+ 'in markdown to show and tests all emoji.');
+ late final ArgResults results;
+
+ try {
+ results = parser.parse(args);
+ } catch (e) {
+ print(e);
+ printUsage(parser);
+ return;
+ }
+
+ if (results['help'] as bool) {
+ printUsage(parser);
+ return;
+ }
+
+ var totalEmojiWithDifferentUnicodeSequences = 0;
+ final useLegacyUnicodeSequences = !(results['useGitHubUnicodes'] as bool);
+ final visualizeUnicodeDiffs = results['visualizeDifferentUnicodes'] as bool;
+
+ final shortCodes =
+ (results['dumpMarkdownShortCodes'] as String).toLowerCase();
+ final dumpMarkdownShortCodes = shortCodes == 'plain';
+ final dumpMarkdownToolTipShortCodes = shortCodes == 'tooltip';
+
+ if (!useLegacyUnicodeSequences) {
+ // Issue warning of the implications of using
+ // full GitHub emoji Unicode sequences.
+ print(useOfGitHubUnicodeSequencesWarning);
+ }
+ if (visualizeUnicodeDiffs) {
+ print(
+ 'The following emoji have different Unicode sequences '
+ 'from those of legacy versions:',
+ );
+ }
+ final shortcodeToEmoji =
+ (await downloadJson(_emojisJsonRawUrl) as Map<String, dynamic>).map(
+ (String alias, dynamic filename) => MapEntry(
+ alias,
+ parseGitHubFilenameIntoUnicodeString(filename as String),
+ ),
+ );
+
+ // Now before we proceed we need to 'mix in' any legacy emoji alias shortcodes
+ // that are missing from the GitHub emoji list.
+ legacyEmojis.forEach((String shortCodeAlias, String emojiUnicode) {
+ if (!shortcodeToEmoji.containsKey(shortCodeAlias)) {
+ shortcodeToEmoji[shortCodeAlias] = emojiUnicode;
+ }
+ });
+
+ final emojisContent = StringBuffer('''
+// GENERATED FILE. DO NOT EDIT.
+//
+// This file was generated from GitHub's emoji API list endpoint:
+// $_emojisJsonRawUrl
+// at ${DateTime.now()} by the script, tool/update_github_emojis.dart.
+
+''');
+ emojisContent.writeln('const emojis = <String, String>{');
+ var emojiCount = 0;
+ final ignored = <String>[];
+ final errored = <String>[];
+ // Dump in sorted order now to facilitate comparison with new GitHub emoji.
+ final sortedKeys = shortcodeToEmoji.keys.toList()..sort();
+ for (final shortCodeAlias in sortedKeys) {
+ var emojiUnicode = shortcodeToEmoji[shortCodeAlias]!;
+ if (useLegacyUnicodeSequences &&
+ legacyEmojis.containsKey(shortCodeAlias) &&
+ shortCodeAlias != 'cricket' &&
+ shortCodeAlias != 'beetle') {
+ emojiUnicode = legacyEmojis[
+ shortCodeAlias]!; // Use legacy Unicode string if available.
+ }
+ if (legacyEmojis.containsKey(shortCodeAlias) &&
+ emojiUnicode != legacyEmojis[shortCodeAlias]) {
+ totalEmojiWithDifferentUnicodeSequences++;
+ if (visualizeUnicodeDiffs) {
+ print(
+ '$emojiUnicode was ${legacyEmojis[shortCodeAlias]} '
+ ':$shortCodeAlias:',
+ );
+ }
+ }
+ if (emojiUnicode != errorSpecialReplacement && emojiUnicode.isNotEmpty) {
+ emojisContent.writeln(" '$shortCodeAlias': '$emojiUnicode',");
+ if (dumpMarkdownShortCodes) {
+ print(':$shortCodeAlias:');
+ } else if (dumpMarkdownToolTipShortCodes) {
+ print('[:$shortCodeAlias:](## ":$shortCodeAlias: emoji")');
+ }
+ emojiCount++;
+ } else {
+ if (emojiUnicode == errorSpecialReplacement) {
+ errored.add(shortCodeAlias);
+ } else {
+ ignored.add(shortCodeAlias);
+ }
+ }
+ }
+ emojisContent.writeln('};');
+ File(_emojisFilePath).writeAsStringSync(emojisContent.toString());
+
+ if (dumpMarkdownShortCodes) {
+ // We are outputing the markdown to stdout, and presumably it
+ // is being captured, so we exit now to exclude the summary
+ // report from being included in the emoji markdown we have
+ // been outputing.
+ return;
+ }
+
+ print('''Wrote data to $_emojisFilePath for $emojiCount emoji,
+$totalEmojiWithDifferentUnicodeSequences emoji's Unicode sequences differ from legacy versions${!visualizeUnicodeDiffs ? " (run with -v flag to visualize)" : ""},
+ignoring ${ignored.length}: ${ignored.join(', ')},
+errored: ${errored.length} ${errored.join(', ')}.''');
+}
+
+void printUsage(ArgParser parser) {
+ print('''Usage: update_emojis.dart [--useGitHubUnicodes | -l]
+
+By default, the legacy Unicode sequences are used (for
+maximum visual compatability with the legacy emoji).
+The --useGitHubUnicodes flag can be used so that the
+Unicode sequences from GitHub are used for emoji's that
+existed within the legacy set. This will result in very slight visual
+differences for some emoji, but it will result in many more
+binary differences when comparing legacy_emoji.dart to emoji.dart.
+$useOfGitHubUnicodeSequencesWarning
+
+The --visualizeDifferentUnicodes flag can be used to visually
+verify that any different Unicode sequences produce the same
+emoji.
+
+${parser.usage}
+''');
+}
diff --git a/pkgs/markdown/tool/update_shared.dart b/pkgs/markdown/tool/update_shared.dart
new file mode 100644
index 0000000..6992cd5
--- /dev/null
+++ b/pkgs/markdown/tool/update_shared.dart
@@ -0,0 +1,21 @@
+// 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 'dart:convert';
+import 'dart:io';
+
+Future<Object?> downloadJson(String uri) async {
+ final client = HttpClient();
+ try {
+ final request = await client.getUrl(Uri.parse(uri));
+ final response = await request.close();
+
+ return response
+ .transform(utf8.decoder)
+ .transform(const JsonDecoder())
+ .single;
+ } finally {
+ client.close();
+ }
+}
diff --git a/pkgs/mime/.gitignore b/pkgs/mime/.gitignore
new file mode 100644
index 0000000..a433102
--- /dev/null
+++ b/pkgs/mime/.gitignore
@@ -0,0 +1,5 @@
+.packages
+.dart_tool/
+.pub/
+packages
+pubspec.lock
diff --git a/pkgs/mime/AUTHORS b/pkgs/mime/AUTHORS
new file mode 100644
index 0000000..e8063a8
--- /dev/null
+++ b/pkgs/mime/AUTHORS
@@ -0,0 +1,6 @@
+# Below is a list of people and organizations that have contributed
+# to the project. Names should be added to the list like so:
+#
+# Name/Organization <email address>
+
+Google Inc.
diff --git a/pkgs/mime/CHANGELOG.md b/pkgs/mime/CHANGELOG.md
new file mode 100644
index 0000000..fd5a249
--- /dev/null
+++ b/pkgs/mime/CHANGELOG.md
@@ -0,0 +1,110 @@
+## 2.0.0
+
+* **[Breaking]** `extensionFromMime(String mimeType)` returns `null` instead of `mimeType` for an unknown mime type.
+* Update `extensionFromMime` to return a default extension when a MIME type maps to multiple extensions.
+
+## 1.0.6
+
+* Add `topics` section to `pubspec.yaml`.
+* Move to `dart-lang/tools` monorepo.
+
+## 1.0.5
+
+* Update `video/mp4` mimeType lookup by header bytes.
+* Add `image/heic` mimeType lookup by header bytes.
+* Add `image/heif` mimeType lookup by header bytes.
+* Add m4b mimeType lookup by extension.
+* Add `text/markdown` mimeType lookup by extension.
+* Require Dart 3.2.0.
+
+## 1.0.4
+
+* Changed `.js` to `text/javascript` per
+ https://datatracker.ietf.org/doc/html/rfc9239.
+* Added `.mjs` as `text/javascript`.
+* Add `application/dicom` mimeType lookup by extension.
+* Require Dart 2.18.
+
+## 1.0.3
+
+* Add application/manifest+json lookup by extension.
+* Add application/toml mimeType lookup by extension.
+* Add audio/aac mimeType lookup by header bytes.
+* Add audio/mpeg mimeType lookup by header bytes.
+* Add audio/ogg mimeType lookup by header bytes.
+* Add audio/weba mimeType lookup by header bytes.
+* Add font/woff2 lookup by extension and header bytes.
+* Add image/avif mimeType lookup by extension.
+* Add image/heic mimeType lookup by extension.
+* Add image/heif mimeType lookup by extension.
+* Change audio/x-aac to audio/aac when detected by extension.
+
+## 1.0.2
+
+* Add audio/x-aiff mimeType lookup by header bytes.
+* Add audio/x-flac mimeType lookup by header bytes.
+* Add audio/x-wav mimeType lookup by header bytes.
+* Add audio/mp4 mimeType lookup by file path.
+
+## 1.0.1
+
+* Add image/webp mimeType lookup by header bytes.
+
+## 1.0.0
+
+* Stable null safety release.
+
+## 1.0.0-nullsafety.0
+
+* Update to null safety.
+
+## 0.9.7
+
+* Add `extensionFromMime` utility function.
+
+## 0.9.6+3
+
+* Change the mime type for Dart source from `application/dart` to `text/x-dart`.
+* Add example.
+* Fix links and code in README.
+
+## 0.9.6+2
+
+* Set max SDK version to `<3.0.0`, and adjust other dependencies.
+
+## 0.9.6+1
+
+* Stop using deprecated constants from the SDK.
+
+## 0.9.6
+
+* Updates to support Dart 2.0 core library changes (wave
+ 2.2). See [issue 31847][sdk#31847] for details.
+
+ [sdk#31847]: https://github.com/dart-lang/sdk/issues/31847
+
+## 0.9.5
+
+* Add support for the WebAssembly format.
+
+## 0.9.4
+
+* Updated Dart SDK requirement to `>= 1.8.3 <2.0.0`
+
+* Strong-mode clean.
+
+* Added support for glTF text and binary formats.
+
+## 0.9.3
+
+* Fixed erroneous behavior for listening and when pausing/resuming
+ stream of parts.
+
+## 0.9.2
+
+* Fixed erroneous behavior when pausing/canceling stream of parts but already
+ listened to one part.
+
+## 0.9.1
+
+* Handle parsing of MIME multipart content with no parts.
diff --git a/pkgs/mime/CONTRIBUTING.md b/pkgs/mime/CONTRIBUTING.md
new file mode 100644
index 0000000..463063a
--- /dev/null
+++ b/pkgs/mime/CONTRIBUTING.md
@@ -0,0 +1,37 @@
+Want to contribute? Great! First, read this page (including the small print at
+the end).
+
+### Before you contribute
+Before we can use your code, you must sign the
+[Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual)
+(CLA), which you can do online. The CLA is necessary mainly because you own the
+copyright to your changes, even after your contribution becomes part of our
+codebase, so we need your permission to use and distribute your code. We also
+need to be sure of various other things—for instance that you'll tell us if you
+know that your code infringes on other people's patents. You don't have to sign
+the CLA until after you've submitted your code for review and a member has
+approved it, but you must do it before we can put your code into our codebase.
+
+Before you start working on a larger contribution, you should get in touch with
+us first through the issue tracker with your idea so that we can help out and
+possibly guide you. Coordinating up front makes it much easier to avoid
+frustration later on.
+
+### Code reviews
+All submissions, including submissions by project members, require review.
+
+### File headers
+All files in the project must start with the following header.
+
+ // Copyright (c) 2015, 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.
+
+### The small print
+Contributions made by corporations are covered by a different agreement than the
+one above, the
+[Software Grant and Corporate Contributor License Agreement](https://developers.google.com/open-source/cla/corporate).
+
+### Adding an extension / MIME type mapping
+If a MIME type ends up with multiple extensions, it is recommended to define a
+preferred extension in `_defaultMimeTypeMap` in [extension.dart](lib/src/extension.dart).
\ No newline at end of file
diff --git a/pkgs/mime/LICENSE b/pkgs/mime/LICENSE
new file mode 100644
index 0000000..dbd2843
--- /dev/null
+++ b/pkgs/mime/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2015, 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.
diff --git a/pkgs/mime/README.md b/pkgs/mime/README.md
new file mode 100644
index 0000000..da06de6
--- /dev/null
+++ b/pkgs/mime/README.md
@@ -0,0 +1,70 @@
+[](https://github.com/dart-lang/tools/actions/workflows/mime.yml)
+[](https://pub.dev/packages/mime)
+[](https://pub.dev/packages/mime/publisher)
+
+Package for working with MIME type definitions and for processing
+streams of MIME multipart media types.
+
+## Determining the MIME type for a file
+
+The `MimeTypeResolver` class can be used to determine the MIME type of
+a file. It supports both using the extension of the file name and
+looking at magic bytes from the beginning of the file.
+
+There is a builtin instance of `MimeTypeResolver` accessible through
+the top level function `lookupMimeType`. This builtin instance has
+the most common file name extensions and magic bytes registered.
+
+```dart
+import 'package:mime/mime.dart';
+
+void main() {
+ print(lookupMimeType('test.html'));
+ // text/html
+
+ print(lookupMimeType('test', headerBytes: [0xFF, 0xD8]));
+ // image/jpeg
+
+ print(lookupMimeType('test.html', headerBytes: [0xFF, 0xD8]));
+ // image/jpeg
+}
+```
+
+You can build you own resolver by creating an instance of
+`MimeTypeResolver` and adding file name extensions and magic bytes
+using `addExtension` and `addMagicNumber`.
+
+## Processing MIME multipart media types
+
+The class `MimeMultipartTransformer` is used to process a `Stream` of
+bytes encoded using a MIME multipart media types encoding. The
+transformer provides a new `Stream` of `MimeMultipart` objects each of
+which have the headers and the content of each part. The content of a
+part is provided as a stream of bytes.
+
+Below is an example showing how to process an HTTP request and print
+the length of the content of each part.
+
+```dart
+// HTTP request with content type multipart/form-data.
+HttpRequest request = ...;
+// Determine the boundary form the content type header
+String boundary = request.headers.contentType.parameters['boundary'];
+
+// Process the body just calculating the length of each part.
+request
+ .transform(new MimeMultipartTransformer(boundary))
+ .map((part) => part.fold(0, (p, d) => p + d))
+ .listen((length) => print('Part with length $length'));
+```
+
+## Determining file extension by MIME type
+
+The top level function `extensionFromMime` can be used to determine the
+file extension of a given MIME type.
+
+```dart
+print(extensionFromMime('text/html')); // Prints "html".
+print(extensionFromMime('image/jpeg')); // Prints "jpg".
+print(extensionFromMime('application/pdf')); // Prints "pdf".
+```
diff --git a/pkgs/mime/analysis_options.yaml b/pkgs/mime/analysis_options.yaml
new file mode 100644
index 0000000..dc212e7
--- /dev/null
+++ b/pkgs/mime/analysis_options.yaml
@@ -0,0 +1,33 @@
+# https://dart.dev/tools/analysis#the-analysis-options-file
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-casts: true
+ strict-inference: true
+ strict-raw-types: true
+
+linter:
+ rules:
+ - avoid_bool_literals_in_conditional_expressions
+ - avoid_classes_with_only_static_members
+ - avoid_private_typedef_functions
+ - avoid_redundant_argument_values
+ - avoid_returning_this
+ - avoid_unused_constructor_parameters
+ - avoid_void_async
+ - cancel_subscriptions
+ - join_return_with_assignment
+ - literal_only_boolean_expressions
+ - missing_whitespace_between_adjacent_strings
+ - no_adjacent_strings_in_list
+ - no_runtimeType_toString
+ - prefer_const_declarations
+ - prefer_expression_function_bodies
+ - prefer_final_locals
+ - unnecessary_await_in_return
+ - unnecessary_breaks
+ - unnecessary_raw_strings
+ - use_if_null_to_convert_nulls_to_bools
+ - use_raw_strings
+ - use_string_buffers
diff --git a/pkgs/mime/example/example.dart b/pkgs/mime/example/example.dart
new file mode 100644
index 0000000..b54f8e7
--- /dev/null
+++ b/pkgs/mime/example/example.dart
@@ -0,0 +1,16 @@
+// 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:mime/mime.dart';
+
+void main() {
+ print(lookupMimeType('test.html'));
+ // text/html
+
+ print(lookupMimeType('test', headerBytes: [0xFF, 0xD8]));
+ // image/jpeg
+
+ print(lookupMimeType('test.html', headerBytes: [0xFF, 0xD8]));
+ // image/jpeg
+}
diff --git a/pkgs/mime/lib/mime.dart b/pkgs/mime/lib/mime.dart
new file mode 100644
index 0000000..cb45098
--- /dev/null
+++ b/pkgs/mime/lib/mime.dart
@@ -0,0 +1,15 @@
+// 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.
+
+/// Help for working with file format identifiers
+/// such as `text/html` and `image/png`.
+///
+/// More details, including a list of types, are in the Wikipedia article
+/// [Internet media type](http://en.wikipedia.org/wiki/Internet_media_type).
+library;
+
+export 'src/extension.dart';
+export 'src/mime_multipart_transformer.dart';
+export 'src/mime_shared.dart';
+export 'src/mime_type.dart';
diff --git a/pkgs/mime/lib/src/bound_multipart_stream.dart b/pkgs/mime/lib/src/bound_multipart_stream.dart
new file mode 100644
index 0000000..03b213b
--- /dev/null
+++ b/pkgs/mime/lib/src/bound_multipart_stream.dart
@@ -0,0 +1,372 @@
+// Copyright (c) 2014, 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 'char_code.dart' as char_code;
+import 'mime_shared.dart';
+
+/// Bytes for `()<>@,;:\\"/[]?={} \t`.
+const _separators = {
+ 40, 41, 60, 62, 64, 44, 59, 58, 92, 34, 47, 91, 93, 63, 61, 123, 125, 32, 9 //
+};
+
+bool _isTokenChar(int byte) =>
+ byte > 31 && byte < 128 && !_separators.contains(byte);
+
+int _toLowerCase(int byte) {
+ const delta = char_code.lowerA - char_code.upperA;
+ return (char_code.upperA <= byte && byte <= char_code.upperZ)
+ ? byte + delta
+ : byte;
+}
+
+void _expectByteValue(int val1, int val2) {
+ if (val1 != val2) {
+ throw const MimeMultipartException('Failed to parse multipart mime 1');
+ }
+}
+
+void _expectWhitespace(int byte) {
+ if (byte != char_code.sp && byte != char_code.ht) {
+ throw const MimeMultipartException('Failed to parse multipart mime 2');
+ }
+}
+
+class _MimeMultipart extends MimeMultipart {
+ @override
+ final Map<String, String> headers;
+ final Stream<List<int>> _stream;
+
+ _MimeMultipart(this.headers, this._stream);
+
+ @override
+ StreamSubscription<List<int>> listen(
+ void Function(List<int> data)? onData, {
+ void Function()? onDone,
+ Function? onError,
+ bool? cancelOnError,
+ }) =>
+ _stream.listen(
+ onData,
+ onDone: onDone,
+ onError: onError,
+ cancelOnError: cancelOnError,
+ );
+}
+
+class BoundMultipartStream {
+ static const int _startCode = 0;
+ static const int _boundaryEndingCode = 1;
+ static const int _boundaryEndCode = 2;
+ static const int _headerStartCode = 3;
+ static const int _headerFieldCode = 4;
+ static const int _headerValueStartCode = 5;
+ static const int _headerValueCode = 6;
+ static const int _headerValueFoldingOrEndingCode = 7;
+ static const int _headerValueFoldOrEndCode = 8;
+ static const int _headerEndingCode = 9;
+ static const int _contentCode = 10;
+ static const int _lastBoundaryDash2Code = 11;
+ static const int _lastBoundaryEndingCode = 12;
+ static const int _lastBoundaryEndCode = 13;
+ static const int _doneCode = 14;
+ static const int _failCode = 15;
+
+ final List<int> _boundary;
+ final List<int> _headerField = [];
+ final List<int> _headerValue = [];
+
+ // The following states belong to `_controller`, state changes will not be
+ // immediately acted upon but rather only after the current
+ // `_multipartController` is done.
+ static const int _controllerStateIdle = 0;
+ static const int _controllerStateActive = 1;
+ static const int _controllerStatePaused = 2;
+ static const int _controllerStateCanceled = 3;
+
+ int _controllerState = _controllerStateIdle;
+
+ final _controller = StreamController<MimeMultipart>(sync: true);
+
+ Stream<MimeMultipart> get stream => _controller.stream;
+
+ late StreamSubscription<void> _subscription;
+
+ StreamController<List<int>>? _multipartController;
+ Map<String, String>? _headers;
+
+ int _state = _startCode;
+ int _boundaryIndex = 2;
+
+ /// Current index into [_buffer].
+ ///
+ /// If index is negative then it is the index into the artificial prefix of
+ /// the boundary string.
+ int _index = 0;
+ List<int> _buffer = _placeholderBuffer;
+
+ BoundMultipartStream(this._boundary, Stream<List<int>> stream) {
+ _controller
+ ..onPause = _pauseStream
+ ..onResume = _resumeStream
+ ..onCancel = () {
+ _controllerState = _controllerStateCanceled;
+ _tryPropagateControllerState();
+ }
+ ..onListen = () {
+ _controllerState = _controllerStateActive;
+ _subscription = stream.listen((data) {
+ assert(_buffer == _placeholderBuffer);
+ _subscription.pause();
+ _buffer = data;
+ _index = 0;
+ _parse();
+ }, onDone: () {
+ if (_state != _doneCode) {
+ _controller
+ .addError(const MimeMultipartException('Bad multipart ending'));
+ }
+ _controller.close();
+ }, onError: _controller.addError);
+ };
+ }
+
+ void _resumeStream() {
+ assert(_controllerState == _controllerStatePaused);
+ _controllerState = _controllerStateActive;
+ _tryPropagateControllerState();
+ }
+
+ void _pauseStream() {
+ _controllerState = _controllerStatePaused;
+ _tryPropagateControllerState();
+ }
+
+ void _tryPropagateControllerState() {
+ if (_multipartController == null) {
+ switch (_controllerState) {
+ case _controllerStateActive:
+ if (_subscription.isPaused) _subscription.resume();
+ case _controllerStatePaused:
+ if (!_subscription.isPaused) _subscription.pause();
+ case _controllerStateCanceled:
+ _subscription.cancel();
+ default:
+ throw StateError('This code should never be reached.');
+ }
+ }
+ }
+
+ void _parse() {
+ // Number of boundary bytes to artificially place before the supplied data.
+ // The data to parse might be 'artificially' prefixed with a
+ // partial match of the boundary.
+ final boundaryPrefix = _boundaryIndex;
+ // Position where content starts. Will be null if no known content
+ // start exists. Will be negative of the content starts in the
+ // boundary prefix. Will be zero or position if the content starts
+ // in the current buffer.
+ var contentStartIndex =
+ _state == _contentCode && _boundaryIndex == 0 ? 0 : null;
+
+ // Function to report content data for the current part. The data
+ // reported is from the current content start index up til the
+ // current index. As the data can be artificially prefixed with a
+ // prefix of the boundary both the content start index and index
+ // can be negative.
+ void reportData() {
+ if (contentStartIndex! < 0) {
+ final contentLength = boundaryPrefix + _index - _boundaryIndex;
+ if (contentLength <= boundaryPrefix) {
+ _multipartController!.add(_boundary.sublist(0, contentLength));
+ } else {
+ _multipartController!.add(_boundary.sublist(0, boundaryPrefix));
+ _multipartController!
+ .add(_buffer.sublist(0, contentLength - boundaryPrefix));
+ }
+ } else {
+ final contentEndIndex = _index - _boundaryIndex;
+ _multipartController!
+ .add(_buffer.sublist(contentStartIndex, contentEndIndex));
+ }
+ }
+
+ while (
+ _index < _buffer.length && _state != _failCode && _state != _doneCode) {
+ final byte =
+ _index < 0 ? _boundary[boundaryPrefix + _index] : _buffer[_index];
+ switch (_state) {
+ case _startCode:
+ if (byte == _boundary[_boundaryIndex]) {
+ _boundaryIndex++;
+ if (_boundaryIndex == _boundary.length) {
+ _state = _boundaryEndingCode;
+ _boundaryIndex = 0;
+ }
+ } else {
+ // Restart matching of the boundary.
+ _index = _index - _boundaryIndex;
+ _boundaryIndex = 0;
+ }
+
+ case _boundaryEndingCode:
+ if (byte == char_code.cr) {
+ _state = _boundaryEndCode;
+ } else if (byte == char_code.dash) {
+ _state = _lastBoundaryDash2Code;
+ } else {
+ _expectWhitespace(byte);
+ }
+
+ case _boundaryEndCode:
+ _expectByteValue(byte, char_code.lf);
+ _multipartController?.close();
+ if (_multipartController != null) {
+ _multipartController = null;
+ _tryPropagateControllerState();
+ }
+ _state = _headerStartCode;
+
+ case _headerStartCode:
+ _headers = <String, String>{};
+ if (byte == char_code.cr) {
+ _state = _headerEndingCode;
+ } else {
+ // Start of new header field.
+ _headerField.add(_toLowerCase(byte));
+ _state = _headerFieldCode;
+ }
+
+ case _headerFieldCode:
+ if (byte == char_code.colon) {
+ _state = _headerValueStartCode;
+ } else {
+ if (!_isTokenChar(byte)) {
+ throw const MimeMultipartException('Invalid header field name');
+ }
+ _headerField.add(_toLowerCase(byte));
+ }
+
+ case _headerValueStartCode:
+ if (byte == char_code.cr) {
+ _state = _headerValueFoldingOrEndingCode;
+ } else if (byte != char_code.sp && byte != char_code.ht) {
+ // Start of new header value.
+ _headerValue.add(byte);
+ _state = _headerValueCode;
+ }
+
+ case _headerValueCode:
+ if (byte == char_code.cr) {
+ _state = _headerValueFoldingOrEndingCode;
+ } else {
+ _headerValue.add(byte);
+ }
+
+ case _headerValueFoldingOrEndingCode:
+ _expectByteValue(byte, char_code.lf);
+ _state = _headerValueFoldOrEndCode;
+
+ case _headerValueFoldOrEndCode:
+ if (byte == char_code.sp || byte == char_code.ht) {
+ _state = _headerValueStartCode;
+ } else {
+ final headerField = utf8.decode(_headerField);
+ final headerValue = utf8.decode(_headerValue);
+ _headers![headerField.toLowerCase()] = headerValue;
+ _headerField.clear();
+ _headerValue.clear();
+ if (byte == char_code.cr) {
+ _state = _headerEndingCode;
+ } else {
+ // Start of new header field.
+ _headerField.add(_toLowerCase(byte));
+ _state = _headerFieldCode;
+ }
+ }
+
+ case _headerEndingCode:
+ _expectByteValue(byte, char_code.lf);
+ _multipartController = StreamController(
+ sync: true,
+ onListen: () {
+ if (_subscription.isPaused) _subscription.resume();
+ },
+ onPause: _subscription.pause,
+ onResume: _subscription.resume);
+ _controller
+ .add(_MimeMultipart(_headers!, _multipartController!.stream));
+ _headers = null;
+ _state = _contentCode;
+ contentStartIndex = _index + 1;
+
+ case _contentCode:
+ if (byte == _boundary[_boundaryIndex]) {
+ _boundaryIndex++;
+ if (_boundaryIndex == _boundary.length) {
+ if (contentStartIndex != null) {
+ _index++;
+ reportData();
+ _index--;
+ }
+ _multipartController!.close();
+ _multipartController = null;
+ _tryPropagateControllerState();
+ _boundaryIndex = 0;
+ _state = _boundaryEndingCode;
+ }
+ } else {
+ // Restart matching of the boundary.
+ _index = _index - _boundaryIndex;
+ contentStartIndex ??= _index;
+ _boundaryIndex = 0;
+ }
+
+ case _lastBoundaryDash2Code:
+ _expectByteValue(byte, char_code.dash);
+ _state = _lastBoundaryEndingCode;
+
+ case _lastBoundaryEndingCode:
+ if (byte == char_code.cr) {
+ _state = _lastBoundaryEndCode;
+ } else {
+ _expectWhitespace(byte);
+ }
+
+ case _lastBoundaryEndCode:
+ _expectByteValue(byte, char_code.lf);
+ _multipartController?.close();
+ if (_multipartController != null) {
+ _multipartController = null;
+ _tryPropagateControllerState();
+ }
+ _state = _doneCode;
+
+ default:
+ // Should be unreachable.
+ assert(false);
+ }
+
+ // Move to the next byte.
+ _index++;
+ }
+
+ // Report any known content.
+ if (_state == _contentCode && contentStartIndex != null) {
+ reportData();
+ }
+
+ // Resume if at end.
+ if (_index == _buffer.length) {
+ _buffer = _placeholderBuffer;
+ _index = 0;
+ _subscription.resume();
+ }
+ }
+}
+
+// Used as a placeholder instead of having a nullable buffer.
+const _placeholderBuffer = <int>[];
diff --git a/pkgs/mime/lib/src/char_code.dart b/pkgs/mime/lib/src/char_code.dart
new file mode 100644
index 0000000..4cca1b1
--- /dev/null
+++ b/pkgs/mime/lib/src/char_code.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2014, 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.
+
+const int ht = 9;
+const int lf = 10;
+const int cr = 13;
+const int sp = 32;
+const int dash = 45;
+const int colon = 58;
+const int upperA = 65;
+const int upperZ = 90;
+const int lowerA = 97;
diff --git a/pkgs/mime/lib/src/default_extension_map.dart b/pkgs/mime/lib/src/default_extension_map.dart
new file mode 100644
index 0000000..287c957
--- /dev/null
+++ b/pkgs/mime/lib/src/default_extension_map.dart
@@ -0,0 +1,1012 @@
+// 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.
+
+const Map<String, String> defaultExtensionMap = <String, String>{
+ '123': 'application/vnd.lotus-1-2-3',
+ '3dml': 'text/vnd.in3d.3dml',
+ '3ds': 'image/x-3ds',
+ '3g2': 'video/3gpp2',
+ '3gp': 'video/3gpp',
+ '7z': 'application/x-7z-compressed',
+ 'aab': 'application/x-authorware-bin',
+ 'aac': 'audio/aac',
+ 'aam': 'application/x-authorware-map',
+ 'aas': 'application/x-authorware-seg',
+ 'abw': 'application/x-abiword',
+ 'ac': 'application/pkix-attr-cert',
+ 'acc': 'application/vnd.americandynamics.acc',
+ 'ace': 'application/x-ace-compressed',
+ 'acu': 'application/vnd.acucobol',
+ 'acutc': 'application/vnd.acucorp',
+ 'adp': 'audio/adpcm',
+ 'aep': 'application/vnd.audiograph',
+ 'afm': 'application/x-font-type1',
+ 'afp': 'application/vnd.ibm.modcap',
+ 'ahead': 'application/vnd.ahead.space',
+ 'ai': 'application/postscript',
+ 'aif': 'audio/x-aiff',
+ 'aifc': 'audio/x-aiff',
+ 'aiff': 'audio/x-aiff',
+ 'air': 'application/vnd.adobe.air-application-installer-package+zip',
+ 'ait': 'application/vnd.dvb.ait',
+ 'ami': 'application/vnd.amiga.ami',
+ 'apk': 'application/vnd.android.package-archive',
+ 'appcache': 'text/cache-manifest',
+ 'application': 'application/x-ms-application',
+ 'apr': 'application/vnd.lotus-approach',
+ 'arc': 'application/x-freearc',
+ 'asc': 'application/pgp-signature',
+ 'asf': 'video/x-ms-asf',
+ 'asm': 'text/x-asm',
+ 'aso': 'application/vnd.accpac.simply.aso',
+ 'asx': 'video/x-ms-asf',
+ 'atc': 'application/vnd.acucorp',
+ 'atom': 'application/atom+xml',
+ 'atomcat': 'application/atomcat+xml',
+ 'atomsvc': 'application/atomsvc+xml',
+ 'atx': 'application/vnd.antix.game-component',
+ 'au': 'audio/basic',
+ 'avi': 'video/x-msvideo',
+ 'avif': 'image/avif',
+ 'aw': 'application/applixware',
+ 'azf': 'application/vnd.airzip.filesecure.azf',
+ 'azs': 'application/vnd.airzip.filesecure.azs',
+ 'azw': 'application/vnd.amazon.ebook',
+ 'bat': 'application/x-msdownload',
+ 'bcpio': 'application/x-bcpio',
+ 'bdf': 'application/x-font-bdf',
+ 'bdm': 'application/vnd.syncml.dm+wbxml',
+ 'bed': 'application/vnd.realvnc.bed',
+ 'bh2': 'application/vnd.fujitsu.oasysprs',
+ 'bin': 'application/octet-stream',
+ 'blb': 'application/x-blorb',
+ 'blorb': 'application/x-blorb',
+ 'bmi': 'application/vnd.bmi',
+ 'bmp': 'image/bmp',
+ 'book': 'application/vnd.framemaker',
+ 'box': 'application/vnd.previewsystems.box',
+ 'boz': 'application/x-bzip2',
+ 'bpk': 'application/octet-stream',
+ 'btif': 'image/prs.btif',
+ 'bz': 'application/x-bzip',
+ 'bz2': 'application/x-bzip2',
+ 'c': 'text/x-c',
+ 'c11amc': 'application/vnd.cluetrust.cartomobile-config',
+ 'c11amz': 'application/vnd.cluetrust.cartomobile-config-pkg',
+ 'c4d': 'application/vnd.clonk.c4group',
+ 'c4f': 'application/vnd.clonk.c4group',
+ 'c4g': 'application/vnd.clonk.c4group',
+ 'c4p': 'application/vnd.clonk.c4group',
+ 'c4u': 'application/vnd.clonk.c4group',
+ 'cab': 'application/vnd.ms-cab-compressed',
+ 'caf': 'audio/x-caf',
+ 'cap': 'application/vnd.tcpdump.pcap',
+ 'car': 'application/vnd.curl.car',
+ 'cat': 'application/vnd.ms-pki.seccat',
+ 'cb7': 'application/x-cbr',
+ 'cba': 'application/x-cbr',
+ 'cbr': 'application/x-cbr',
+ 'cbt': 'application/x-cbr',
+ 'cbz': 'application/x-cbr',
+ 'cc': 'text/x-c',
+ 'cct': 'application/x-director',
+ 'ccxml': 'application/ccxml+xml',
+ 'cdbcmsg': 'application/vnd.contact.cmsg',
+ 'cdf': 'application/x-netcdf',
+ 'cdkey': 'application/vnd.mediastation.cdkey',
+ 'cdmia': 'application/cdmi-capability',
+ 'cdmic': 'application/cdmi-container',
+ 'cdmid': 'application/cdmi-domain',
+ 'cdmio': 'application/cdmi-object',
+ 'cdmiq': 'application/cdmi-queue',
+ 'cdx': 'chemical/x-cdx',
+ 'cdxml': 'application/vnd.chemdraw+xml',
+ 'cdy': 'application/vnd.cinderella',
+ 'cer': 'application/pkix-cert',
+ 'cfs': 'application/x-cfs-compressed',
+ 'cgm': 'image/cgm',
+ 'chat': 'application/x-chat',
+ 'chm': 'application/vnd.ms-htmlhelp',
+ 'chrt': 'application/vnd.kde.kchart',
+ 'cif': 'chemical/x-cif',
+ 'cii': 'application/vnd.anser-web-certificate-issue-initiation',
+ 'cil': 'application/vnd.ms-artgalry',
+ 'cla': 'application/vnd.claymore',
+ 'class': 'application/java-vm',
+ 'clkk': 'application/vnd.crick.clicker.keyboard',
+ 'clkp': 'application/vnd.crick.clicker.palette',
+ 'clkt': 'application/vnd.crick.clicker.template',
+ 'clkw': 'application/vnd.crick.clicker.wordbank',
+ 'clkx': 'application/vnd.crick.clicker',
+ 'clp': 'application/x-msclip',
+ 'cmc': 'application/vnd.cosmocaller',
+ 'cmdf': 'chemical/x-cmdf',
+ 'cml': 'chemical/x-cml',
+ 'cmp': 'application/vnd.yellowriver-custom-menu',
+ 'cmx': 'image/x-cmx',
+ 'cod': 'application/vnd.rim.cod',
+ 'com': 'application/x-msdownload',
+ 'conf': 'text/plain',
+ 'cpio': 'application/x-cpio',
+ 'cpp': 'text/x-c',
+ 'cpt': 'application/mac-compactpro',
+ 'crd': 'application/x-mscardfile',
+ 'crl': 'application/pkix-crl',
+ 'crt': 'application/x-x509-ca-cert',
+ 'cryptonote': 'application/vnd.rig.cryptonote',
+ 'csh': 'application/x-csh',
+ 'csml': 'chemical/x-csml',
+ 'csp': 'application/vnd.commonspace',
+ 'css': 'text/css',
+ 'cst': 'application/x-director',
+ 'csv': 'text/csv',
+ 'cu': 'application/cu-seeme',
+ 'curl': 'text/vnd.curl',
+ 'cww': 'application/prs.cww',
+ 'cxt': 'application/x-director',
+ 'cxx': 'text/x-c',
+ 'dae': 'model/vnd.collada+xml',
+ 'daf': 'application/vnd.mobius.daf',
+ 'dart': 'text/x-dart',
+ 'dataless': 'application/vnd.fdsn.seed',
+ 'davmount': 'application/davmount+xml',
+ 'dbk': 'application/docbook+xml',
+ 'dcm': 'application/dicom',
+ 'dcr': 'application/x-director',
+ 'dcurl': 'text/vnd.curl.dcurl',
+ 'dd2': 'application/vnd.oma.dd2+xml',
+ 'ddd': 'application/vnd.fujixerox.ddd',
+ 'deb': 'application/x-debian-package',
+ 'def': 'text/plain',
+ 'deploy': 'application/octet-stream',
+ 'der': 'application/x-x509-ca-cert',
+ 'dfac': 'application/vnd.dreamfactory',
+ 'dgc': 'application/x-dgc-compressed',
+ 'dic': 'text/x-c',
+ 'dir': 'application/x-director',
+ 'dis': 'application/vnd.mobius.dis',
+ 'dist': 'application/octet-stream',
+ 'distz': 'application/octet-stream',
+ 'djv': 'image/vnd.djvu',
+ 'djvu': 'image/vnd.djvu',
+ 'dll': 'application/x-msdownload',
+ 'dmg': 'application/x-apple-diskimage',
+ 'dmp': 'application/vnd.tcpdump.pcap',
+ 'dms': 'application/octet-stream',
+ 'dna': 'application/vnd.dna',
+ 'doc': 'application/msword',
+ 'docm': 'application/vnd.ms-word.document.macroenabled.12',
+ 'docx':
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'dot': 'application/msword',
+ 'dotm': 'application/vnd.ms-word.template.macroenabled.12',
+ 'dotx':
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
+ 'dp': 'application/vnd.osgi.dp',
+ 'dpg': 'application/vnd.dpgraph',
+ 'dra': 'audio/vnd.dra',
+ 'dsc': 'text/prs.lines.tag',
+ 'dssc': 'application/dssc+der',
+ 'dtb': 'application/x-dtbook+xml',
+ 'dtd': 'application/xml-dtd',
+ 'dts': 'audio/vnd.dts',
+ 'dtshd': 'audio/vnd.dts.hd',
+ 'dump': 'application/octet-stream',
+ 'dvb': 'video/vnd.dvb.file',
+ 'dvi': 'application/x-dvi',
+ 'dwf': 'model/vnd.dwf',
+ 'dwg': 'image/vnd.dwg',
+ 'dxf': 'image/vnd.dxf',
+ 'dxp': 'application/vnd.spotfire.dxp',
+ 'dxr': 'application/x-director',
+ 'ecelp4800': 'audio/vnd.nuera.ecelp4800',
+ 'ecelp7470': 'audio/vnd.nuera.ecelp7470',
+ 'ecelp9600': 'audio/vnd.nuera.ecelp9600',
+ 'ecma': 'application/ecmascript',
+ 'edm': 'application/vnd.novadigm.edm',
+ 'edx': 'application/vnd.novadigm.edx',
+ 'efif': 'application/vnd.picsel',
+ 'ei6': 'application/vnd.pg.osasli',
+ 'elc': 'application/octet-stream',
+ 'emf': 'application/x-msmetafile',
+ 'eml': 'message/rfc822',
+ 'emma': 'application/emma+xml',
+ 'emz': 'application/x-msmetafile',
+ 'eol': 'audio/vnd.digital-winds',
+ 'eot': 'application/vnd.ms-fontobject',
+ 'eps': 'application/postscript',
+ 'epub': 'application/epub+zip',
+ 'es3': 'application/vnd.eszigno3+xml',
+ 'esa': 'application/vnd.osgi.subsystem',
+ 'esf': 'application/vnd.epson.esf',
+ 'et3': 'application/vnd.eszigno3+xml',
+ 'etx': 'text/x-setext',
+ 'eva': 'application/x-eva',
+ 'evy': 'application/x-envoy',
+ 'exe': 'application/x-msdownload',
+ 'exi': 'application/exi',
+ 'ext': 'application/vnd.novadigm.ext',
+ 'ez': 'application/andrew-inset',
+ 'ez2': 'application/vnd.ezpix-album',
+ 'ez3': 'application/vnd.ezpix-package',
+ 'f': 'text/x-fortran',
+ 'f4v': 'video/x-f4v',
+ 'f77': 'text/x-fortran',
+ 'f90': 'text/x-fortran',
+ 'fbs': 'image/vnd.fastbidsheet',
+ 'fcdt': 'application/vnd.adobe.formscentral.fcdt',
+ 'fcs': 'application/vnd.isac.fcs',
+ 'fdf': 'application/vnd.fdf',
+ 'fe_launch': 'application/vnd.denovo.fcselayout-link',
+ 'fg5': 'application/vnd.fujitsu.oasysgp',
+ 'fgd': 'application/x-director',
+ 'fh': 'image/x-freehand',
+ 'fh4': 'image/x-freehand',
+ 'fh5': 'image/x-freehand',
+ 'fh7': 'image/x-freehand',
+ 'fhc': 'image/x-freehand',
+ 'fig': 'application/x-xfig',
+ 'flac': 'audio/x-flac',
+ 'fli': 'video/x-fli',
+ 'flo': 'application/vnd.micrografx.flo',
+ 'flv': 'video/x-flv',
+ 'flw': 'application/vnd.kde.kivio',
+ 'flx': 'text/vnd.fmi.flexstor',
+ 'fly': 'text/vnd.fly',
+ 'fm': 'application/vnd.framemaker',
+ 'fnc': 'application/vnd.frogans.fnc',
+ 'for': 'text/x-fortran',
+ 'fpx': 'image/vnd.fpx',
+ 'frame': 'application/vnd.framemaker',
+ 'fsc': 'application/vnd.fsc.weblaunch',
+ 'fst': 'image/vnd.fst',
+ 'ftc': 'application/vnd.fluxtime.clip',
+ 'fti': 'application/vnd.anser-web-funds-transfer-initiation',
+ 'fvt': 'video/vnd.fvt',
+ 'fxp': 'application/vnd.adobe.fxp',
+ 'fxpl': 'application/vnd.adobe.fxp',
+ 'fzs': 'application/vnd.fuzzysheet',
+ 'g2w': 'application/vnd.geoplan',
+ 'g3': 'image/g3fax',
+ 'g3w': 'application/vnd.geospace',
+ 'gac': 'application/vnd.groove-account',
+ 'gam': 'application/x-tads',
+ 'gbr': 'application/rpki-ghostbusters',
+ 'gca': 'application/x-gca-compressed',
+ 'gdl': 'model/vnd.gdl',
+ 'geo': 'application/vnd.dynageo',
+ 'gex': 'application/vnd.geometry-explorer',
+ 'ggb': 'application/vnd.geogebra.file',
+ 'ggt': 'application/vnd.geogebra.tool',
+ 'ghf': 'application/vnd.groove-help',
+ 'gif': 'image/gif',
+ 'gim': 'application/vnd.groove-identity-message',
+ 'glb': 'model/gltf-binary',
+ 'gltf': 'model/gltf+json',
+ 'gml': 'application/gml+xml',
+ 'gmx': 'application/vnd.gmx',
+ 'gnumeric': 'application/x-gnumeric',
+ 'gph': 'application/vnd.flographit',
+ 'gpx': 'application/gpx+xml',
+ 'gqf': 'application/vnd.grafeq',
+ 'gqs': 'application/vnd.grafeq',
+ 'gram': 'application/srgs',
+ 'gramps': 'application/x-gramps-xml',
+ 'gre': 'application/vnd.geometry-explorer',
+ 'grv': 'application/vnd.groove-injector',
+ 'grxml': 'application/srgs+xml',
+ 'gsf': 'application/x-font-ghostscript',
+ 'gtar': 'application/x-gtar',
+ 'gtm': 'application/vnd.groove-tool-message',
+ 'gtw': 'model/vnd.gtw',
+ 'gv': 'text/vnd.graphviz',
+ 'gxf': 'application/gxf',
+ 'gxt': 'application/vnd.geonext',
+ 'h': 'text/x-c',
+ 'h261': 'video/h261',
+ 'h263': 'video/h263',
+ 'h264': 'video/h264',
+ 'hal': 'application/vnd.hal+xml',
+ 'hbci': 'application/vnd.hbci',
+ 'hdf': 'application/x-hdf',
+ 'heic': 'image/heic',
+ 'heif': 'image/heif',
+ 'hh': 'text/x-c',
+ 'hlp': 'application/winhlp',
+ 'hpgl': 'application/vnd.hp-hpgl',
+ 'hpid': 'application/vnd.hp-hpid',
+ 'hps': 'application/vnd.hp-hps',
+ 'hqx': 'application/mac-binhex40',
+ 'htke': 'application/vnd.kenameaapp',
+ 'htm': 'text/html',
+ 'html': 'text/html',
+ 'hvd': 'application/vnd.yamaha.hv-dic',
+ 'hvp': 'application/vnd.yamaha.hv-voice',
+ 'hvs': 'application/vnd.yamaha.hv-script',
+ 'i2g': 'application/vnd.intergeo',
+ 'icc': 'application/vnd.iccprofile',
+ 'ice': 'x-conference/x-cooltalk',
+ 'icm': 'application/vnd.iccprofile',
+ 'ico': 'image/x-icon',
+ 'ics': 'text/calendar',
+ 'ief': 'image/ief',
+ 'ifb': 'text/calendar',
+ 'ifm': 'application/vnd.shana.informed.formdata',
+ 'iges': 'model/iges',
+ 'igl': 'application/vnd.igloader',
+ 'igm': 'application/vnd.insors.igm',
+ 'igs': 'model/iges',
+ 'igx': 'application/vnd.micrografx.igx',
+ 'iif': 'application/vnd.shana.informed.interchange',
+ 'imp': 'application/vnd.accpac.simply.imp',
+ 'ims': 'application/vnd.ms-ims',
+ 'in': 'text/plain',
+ 'ink': 'application/inkml+xml',
+ 'inkml': 'application/inkml+xml',
+ 'install': 'application/x-install-instructions',
+ 'iota': 'application/vnd.astraea-software.iota',
+ 'ipfix': 'application/ipfix',
+ 'ipk': 'application/vnd.shana.informed.package',
+ 'irm': 'application/vnd.ibm.rights-management',
+ 'irp': 'application/vnd.irepository.package+xml',
+ 'iso': 'application/x-iso9660-image',
+ 'itp': 'application/vnd.shana.informed.formtemplate',
+ 'ivp': 'application/vnd.immervision-ivp',
+ 'ivu': 'application/vnd.immervision-ivu',
+ 'jad': 'text/vnd.sun.j2me.app-descriptor',
+ 'jam': 'application/vnd.jam',
+ 'jar': 'application/java-archive',
+ 'java': 'text/x-java-source',
+ 'jisp': 'application/vnd.jisp',
+ 'jlt': 'application/vnd.hp-jlyt',
+ 'jnlp': 'application/x-java-jnlp-file',
+ 'joda': 'application/vnd.joost.joda-archive',
+ 'jpe': 'image/jpeg',
+ 'jpeg': 'image/jpeg',
+ 'jpg': 'image/jpeg',
+ 'jpgm': 'video/jpm',
+ 'jpgv': 'video/jpeg',
+ 'jpm': 'video/jpm',
+ 'js': 'text/javascript',
+ 'json': 'application/json',
+ 'jsonml': 'application/jsonml+json',
+ 'kar': 'audio/midi',
+ 'karbon': 'application/vnd.kde.karbon',
+ 'kfo': 'application/vnd.kde.kformula',
+ 'kia': 'application/vnd.kidspiration',
+ 'kml': 'application/vnd.google-earth.kml+xml',
+ 'kmz': 'application/vnd.google-earth.kmz',
+ 'kne': 'application/vnd.kinar',
+ 'knp': 'application/vnd.kinar',
+ 'kon': 'application/vnd.kde.kontour',
+ 'kpr': 'application/vnd.kde.kpresenter',
+ 'kpt': 'application/vnd.kde.kpresenter',
+ 'kpxx': 'application/vnd.ds-keypoint',
+ 'ksp': 'application/vnd.kde.kspread',
+ 'ktr': 'application/vnd.kahootz',
+ 'ktx': 'image/ktx',
+ 'ktz': 'application/vnd.kahootz',
+ 'kwd': 'application/vnd.kde.kword',
+ 'kwt': 'application/vnd.kde.kword',
+ 'lasxml': 'application/vnd.las.las+xml',
+ 'latex': 'application/x-latex',
+ 'lbd': 'application/vnd.llamagraphics.life-balance.desktop',
+ 'lbe': 'application/vnd.llamagraphics.life-balance.exchange+xml',
+ 'les': 'application/vnd.hhe.lesson-player',
+ 'lha': 'application/x-lzh-compressed',
+ 'link66': 'application/vnd.route66.link66+xml',
+ 'list': 'text/plain',
+ 'list3820': 'application/vnd.ibm.modcap',
+ 'listafp': 'application/vnd.ibm.modcap',
+ 'lnk': 'application/x-ms-shortcut',
+ 'log': 'text/plain',
+ 'lostxml': 'application/lost+xml',
+ 'lrf': 'application/octet-stream',
+ 'lrm': 'application/vnd.ms-lrm',
+ 'ltf': 'application/vnd.frogans.ltf',
+ 'lvp': 'audio/vnd.lucent.voice',
+ 'lwp': 'application/vnd.lotus-wordpro',
+ 'lzh': 'application/x-lzh-compressed',
+ 'm13': 'application/x-msmediaview',
+ 'm14': 'application/x-msmediaview',
+ 'm1v': 'video/mpeg',
+ 'm21': 'application/mp21',
+ 'm2a': 'audio/mpeg',
+ 'm2v': 'video/mpeg',
+ 'm3a': 'audio/mpeg',
+ 'm3u': 'audio/x-mpegurl',
+ 'm3u8': 'application/vnd.apple.mpegurl',
+ // Source: https://datatracker.ietf.org/doc/html/rfc4337#section-2
+ 'm4a': 'audio/mp4',
+ 'm4b': 'audio/mp4',
+ 'm4u': 'video/vnd.mpegurl',
+ 'm4v': 'video/x-m4v',
+ 'ma': 'application/mathematica',
+ 'mads': 'application/mads+xml',
+ 'mag': 'application/vnd.ecowin.chart',
+ 'maker': 'application/vnd.framemaker',
+ 'man': 'text/troff',
+ 'mar': 'application/octet-stream',
+ 'mathml': 'application/mathml+xml',
+ 'mb': 'application/mathematica',
+ 'mbk': 'application/vnd.mobius.mbk',
+ 'mbox': 'application/mbox',
+ 'mc1': 'application/vnd.medcalcdata',
+ 'mcd': 'application/vnd.mcd',
+ 'mcurl': 'text/vnd.curl.mcurl',
+ // https://www.rfc-editor.org/rfc/rfc7763
+ 'md': 'text/markdown',
+ 'markdown': 'text/markdown',
+ 'mdb': 'application/x-msaccess',
+ 'mdi': 'image/vnd.ms-modi',
+ 'me': 'text/troff',
+ 'mesh': 'model/mesh',
+ 'meta4': 'application/metalink4+xml',
+ 'metalink': 'application/metalink+xml',
+ 'mets': 'application/mets+xml',
+ 'mfm': 'application/vnd.mfmp',
+ 'mft': 'application/rpki-manifest',
+ 'mgp': 'application/vnd.osgeo.mapguide.package',
+ 'mgz': 'application/vnd.proteus.magazine',
+ 'mid': 'audio/midi',
+ 'midi': 'audio/midi',
+ 'mie': 'application/x-mie',
+ 'mif': 'application/vnd.mif',
+ 'mime': 'message/rfc822',
+ 'mj2': 'video/mj2',
+ 'mjp2': 'video/mj2',
+ 'mjs': 'text/javascript',
+ 'mk3d': 'video/x-matroska',
+ 'mka': 'audio/x-matroska',
+ 'mks': 'video/x-matroska',
+ 'mkv': 'video/x-matroska',
+ 'mlp': 'application/vnd.dolby.mlp',
+ 'mmd': 'application/vnd.chipnuts.karaoke-mmd',
+ 'mmf': 'application/vnd.smaf',
+ 'mmr': 'image/vnd.fujixerox.edmics-mmr',
+ 'mng': 'video/x-mng',
+ 'mny': 'application/x-msmoney',
+ 'mobi': 'application/x-mobipocket-ebook',
+ 'mods': 'application/mods+xml',
+ 'mov': 'video/quicktime',
+ 'movie': 'video/x-sgi-movie',
+ 'mp2': 'audio/mpeg',
+ 'mp21': 'application/mp21',
+ 'mp2a': 'audio/mpeg',
+ 'mp3': 'audio/mpeg',
+ 'mp4': 'video/mp4',
+ 'mp4a': 'audio/mp4',
+ 'mp4s': 'application/mp4',
+ 'mp4v': 'video/mp4',
+ 'mpc': 'application/vnd.mophun.certificate',
+ 'mpe': 'video/mpeg',
+ 'mpeg': 'video/mpeg',
+ 'mpg': 'video/mpeg',
+ 'mpg4': 'video/mp4',
+ 'mpga': 'audio/mpeg',
+ 'mpkg': 'application/vnd.apple.installer+xml',
+ 'mpm': 'application/vnd.blueice.multipass',
+ 'mpn': 'application/vnd.mophun.application',
+ 'mpp': 'application/vnd.ms-project',
+ 'mpt': 'application/vnd.ms-project',
+ 'mpy': 'application/vnd.ibm.minipay',
+ 'mqy': 'application/vnd.mobius.mqy',
+ 'mrc': 'application/marc',
+ 'mrcx': 'application/marcxml+xml',
+ 'ms': 'text/troff',
+ 'mscml': 'application/mediaservercontrol+xml',
+ 'mseed': 'application/vnd.fdsn.mseed',
+ 'mseq': 'application/vnd.mseq',
+ 'msf': 'application/vnd.epson.msf',
+ 'msh': 'model/mesh',
+ 'msi': 'application/x-msdownload',
+ 'msl': 'application/vnd.mobius.msl',
+ 'msty': 'application/vnd.muvee.style',
+ 'mts': 'model/vnd.mts',
+ 'mus': 'application/vnd.musician',
+ 'musicxml': 'application/vnd.recordare.musicxml+xml',
+ 'mvb': 'application/x-msmediaview',
+ 'mwf': 'application/vnd.mfer',
+ 'mxf': 'application/mxf',
+ 'mxl': 'application/vnd.recordare.musicxml',
+ 'mxml': 'application/xv+xml',
+ 'mxs': 'application/vnd.triscape.mxs',
+ 'mxu': 'video/vnd.mpegurl',
+ 'n-gage': 'application/vnd.nokia.n-gage.symbian.install',
+ 'n3': 'text/n3',
+ 'nb': 'application/mathematica',
+ 'nbp': 'application/vnd.wolfram.player',
+ 'nc': 'application/x-netcdf',
+ 'ncx': 'application/x-dtbncx+xml',
+ 'nfo': 'text/x-nfo',
+ 'ngdat': 'application/vnd.nokia.n-gage.data',
+ 'nitf': 'application/vnd.nitf',
+ 'nlu': 'application/vnd.neurolanguage.nlu',
+ 'nml': 'application/vnd.enliven',
+ 'nnd': 'application/vnd.noblenet-directory',
+ 'nns': 'application/vnd.noblenet-sealer',
+ 'nnw': 'application/vnd.noblenet-web',
+ 'npx': 'image/vnd.net-fpx',
+ 'nsc': 'application/x-conference',
+ 'nsf': 'application/vnd.lotus-notes',
+ 'ntf': 'application/vnd.nitf',
+ 'nzb': 'application/x-nzb',
+ 'oa2': 'application/vnd.fujitsu.oasys2',
+ 'oa3': 'application/vnd.fujitsu.oasys3',
+ 'oas': 'application/vnd.fujitsu.oasys',
+ 'obd': 'application/x-msbinder',
+ 'obj': 'application/x-tgif',
+ 'oda': 'application/oda',
+ 'odb': 'application/vnd.oasis.opendocument.database',
+ 'odc': 'application/vnd.oasis.opendocument.chart',
+ 'odf': 'application/vnd.oasis.opendocument.formula',
+ 'odft': 'application/vnd.oasis.opendocument.formula-template',
+ 'odg': 'application/vnd.oasis.opendocument.graphics',
+ 'odi': 'application/vnd.oasis.opendocument.image',
+ 'odm': 'application/vnd.oasis.opendocument.text-master',
+ 'odp': 'application/vnd.oasis.opendocument.presentation',
+ 'ods': 'application/vnd.oasis.opendocument.spreadsheet',
+ 'odt': 'application/vnd.oasis.opendocument.text',
+ 'oga': 'audio/ogg',
+ 'ogg': 'audio/ogg',
+ 'ogv': 'video/ogg',
+ 'ogx': 'application/ogg',
+ 'omdoc': 'application/omdoc+xml',
+ 'onepkg': 'application/onenote',
+ 'onetmp': 'application/onenote',
+ 'onetoc': 'application/onenote',
+ 'onetoc2': 'application/onenote',
+ 'opf': 'application/oebps-package+xml',
+ 'opml': 'text/x-opml',
+ 'oprc': 'application/vnd.palm',
+ 'org': 'application/vnd.lotus-organizer',
+ 'osf': 'application/vnd.yamaha.openscoreformat',
+ 'osfpvg': 'application/vnd.yamaha.openscoreformat.osfpvg+xml',
+ 'otc': 'application/vnd.oasis.opendocument.chart-template',
+ 'otf': 'application/x-font-otf',
+ 'otg': 'application/vnd.oasis.opendocument.graphics-template',
+ 'oth': 'application/vnd.oasis.opendocument.text-web',
+ 'oti': 'application/vnd.oasis.opendocument.image-template',
+ 'otp': 'application/vnd.oasis.opendocument.presentation-template',
+ 'ots': 'application/vnd.oasis.opendocument.spreadsheet-template',
+ 'ott': 'application/vnd.oasis.opendocument.text-template',
+ 'oxps': 'application/oxps',
+ 'oxt': 'application/vnd.openofficeorg.extension',
+ 'p': 'text/x-pascal',
+ 'p10': 'application/pkcs10',
+ 'p12': 'application/x-pkcs12',
+ 'p7b': 'application/x-pkcs7-certificates',
+ 'p7c': 'application/pkcs7-mime',
+ 'p7m': 'application/pkcs7-mime',
+ 'p7r': 'application/x-pkcs7-certreqresp',
+ 'p7s': 'application/pkcs7-signature',
+ 'p8': 'application/pkcs8',
+ 'pas': 'text/x-pascal',
+ 'paw': 'application/vnd.pawaafile',
+ 'pbd': 'application/vnd.powerbuilder6',
+ 'pbm': 'image/x-portable-bitmap',
+ 'pcap': 'application/vnd.tcpdump.pcap',
+ 'pcf': 'application/x-font-pcf',
+ 'pcl': 'application/vnd.hp-pcl',
+ 'pclxl': 'application/vnd.hp-pclxl',
+ 'pct': 'image/x-pict',
+ 'pcurl': 'application/vnd.curl.pcurl',
+ 'pcx': 'image/x-pcx',
+ 'pdb': 'application/vnd.palm',
+ 'pdf': 'application/pdf',
+ 'pfa': 'application/x-font-type1',
+ 'pfb': 'application/x-font-type1',
+ 'pfm': 'application/x-font-type1',
+ 'pfr': 'application/font-tdpfr',
+ 'pfx': 'application/x-pkcs12',
+ 'pgm': 'image/x-portable-graymap',
+ 'pgn': 'application/x-chess-pgn',
+ 'pgp': 'application/pgp-encrypted',
+ 'pic': 'image/x-pict',
+ 'pkg': 'application/octet-stream',
+ 'pki': 'application/pkixcmp',
+ 'pkipath': 'application/pkix-pkipath',
+ 'plb': 'application/vnd.3gpp.pic-bw-large',
+ 'plc': 'application/vnd.mobius.plc',
+ 'plf': 'application/vnd.pocketlearn',
+ 'pls': 'application/pls+xml',
+ 'pml': 'application/vnd.ctc-posml',
+ 'png': 'image/png',
+ 'pnm': 'image/x-portable-anymap',
+ 'portpkg': 'application/vnd.macports.portpkg',
+ 'pot': 'application/vnd.ms-powerpoint',
+ 'potm': 'application/vnd.ms-powerpoint.template.macroenabled.12',
+ 'potx':
+ 'application/vnd.openxmlformats-officedocument.presentationml.template',
+ 'ppam': 'application/vnd.ms-powerpoint.addin.macroenabled.12',
+ 'ppd': 'application/vnd.cups-ppd',
+ 'ppm': 'image/x-portable-pixmap',
+ 'pps': 'application/vnd.ms-powerpoint',
+ 'ppsm': 'application/vnd.ms-powerpoint.slideshow.macroenabled.12',
+ 'ppsx':
+ 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
+ 'ppt': 'application/vnd.ms-powerpoint',
+ 'pptm': 'application/vnd.ms-powerpoint.presentation.macroenabled.12',
+ 'pptx':
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ 'pqa': 'application/vnd.palm',
+ 'prc': 'application/x-mobipocket-ebook',
+ 'pre': 'application/vnd.lotus-freelance',
+ 'prf': 'application/pics-rules',
+ 'ps': 'application/postscript',
+ 'psb': 'application/vnd.3gpp.pic-bw-small',
+ 'psd': 'image/vnd.adobe.photoshop',
+ 'psf': 'application/x-font-linux-psf',
+ 'pskcxml': 'application/pskc+xml',
+ 'ptid': 'application/vnd.pvi.ptid1',
+ 'pub': 'application/x-mspublisher',
+ 'pvb': 'application/vnd.3gpp.pic-bw-var',
+ 'pwn': 'application/vnd.3m.post-it-notes',
+ 'pya': 'audio/vnd.ms-playready.media.pya',
+ 'pyv': 'video/vnd.ms-playready.media.pyv',
+ 'qam': 'application/vnd.epson.quickanime',
+ 'qbo': 'application/vnd.intu.qbo',
+ 'qfx': 'application/vnd.intu.qfx',
+ 'qps': 'application/vnd.publishare-delta-tree',
+ 'qt': 'video/quicktime',
+ 'qwd': 'application/vnd.quark.quarkxpress',
+ 'qwt': 'application/vnd.quark.quarkxpress',
+ 'qxb': 'application/vnd.quark.quarkxpress',
+ 'qxd': 'application/vnd.quark.quarkxpress',
+ 'qxl': 'application/vnd.quark.quarkxpress',
+ 'qxt': 'application/vnd.quark.quarkxpress',
+ 'ra': 'audio/x-pn-realaudio',
+ 'ram': 'audio/x-pn-realaudio',
+ 'rar': 'application/x-rar-compressed',
+ 'ras': 'image/x-cmu-raster',
+ 'rcprofile': 'application/vnd.ipunplugged.rcprofile',
+ 'rdf': 'application/rdf+xml',
+ 'rdz': 'application/vnd.data-vision.rdz',
+ 'rep': 'application/vnd.businessobjects',
+ 'res': 'application/x-dtbresource+xml',
+ 'rgb': 'image/x-rgb',
+ 'rif': 'application/reginfo+xml',
+ 'rip': 'audio/vnd.rip',
+ 'ris': 'application/x-research-info-systems',
+ 'rl': 'application/resource-lists+xml',
+ 'rlc': 'image/vnd.fujixerox.edmics-rlc',
+ 'rld': 'application/resource-lists-diff+xml',
+ 'rm': 'application/vnd.rn-realmedia',
+ 'rmi': 'audio/midi',
+ 'rmp': 'audio/x-pn-realaudio-plugin',
+ 'rms': 'application/vnd.jcp.javame.midlet-rms',
+ 'rmvb': 'application/vnd.rn-realmedia-vbr',
+ 'rnc': 'application/relax-ng-compact-syntax',
+ 'roa': 'application/rpki-roa',
+ 'roff': 'text/troff',
+ 'rp9': 'application/vnd.cloanto.rp9',
+ 'rpss': 'application/vnd.nokia.radio-presets',
+ 'rpst': 'application/vnd.nokia.radio-preset',
+ 'rq': 'application/sparql-query',
+ 'rs': 'application/rls-services+xml',
+ 'rsd': 'application/rsd+xml',
+ 'rss': 'application/rss+xml',
+ 'rtf': 'application/rtf',
+ 'rtx': 'text/richtext',
+ 's': 'text/x-asm',
+ 's3m': 'audio/s3m',
+ 'saf': 'application/vnd.yamaha.smaf-audio',
+ 'sbml': 'application/sbml+xml',
+ 'sc': 'application/vnd.ibm.secure-container',
+ 'scd': 'application/x-msschedule',
+ 'scm': 'application/vnd.lotus-screencam',
+ 'scq': 'application/scvp-cv-request',
+ 'scs': 'application/scvp-cv-response',
+ 'scurl': 'text/vnd.curl.scurl',
+ 'sda': 'application/vnd.stardivision.draw',
+ 'sdc': 'application/vnd.stardivision.calc',
+ 'sdd': 'application/vnd.stardivision.impress',
+ 'sdkd': 'application/vnd.solent.sdkm+xml',
+ 'sdkm': 'application/vnd.solent.sdkm+xml',
+ 'sdp': 'application/sdp',
+ 'sdw': 'application/vnd.stardivision.writer',
+ 'see': 'application/vnd.seemail',
+ 'seed': 'application/vnd.fdsn.seed',
+ 'sema': 'application/vnd.sema',
+ 'semd': 'application/vnd.semd',
+ 'semf': 'application/vnd.semf',
+ 'ser': 'application/java-serialized-object',
+ 'setpay': 'application/set-payment-initiation',
+ 'setreg': 'application/set-registration-initiation',
+ 'sfd-hdstx': 'application/vnd.hydrostatix.sof-data',
+ 'sfs': 'application/vnd.spotfire.sfs',
+ 'sfv': 'text/x-sfv',
+ 'sgi': 'image/sgi',
+ 'sgl': 'application/vnd.stardivision.writer-global',
+ 'sgm': 'text/sgml',
+ 'sgml': 'text/sgml',
+ 'sh': 'application/x-sh',
+ 'shar': 'application/x-shar',
+ 'shf': 'application/shf+xml',
+ 'sid': 'image/x-mrsid-image',
+ 'sig': 'application/pgp-signature',
+ 'sil': 'audio/silk',
+ 'silo': 'model/mesh',
+ 'sis': 'application/vnd.symbian.install',
+ 'sisx': 'application/vnd.symbian.install',
+ 'sit': 'application/x-stuffit',
+ 'sitx': 'application/x-stuffitx',
+ 'skd': 'application/vnd.koan',
+ 'skm': 'application/vnd.koan',
+ 'skp': 'application/vnd.koan',
+ 'skt': 'application/vnd.koan',
+ 'sldm': 'application/vnd.ms-powerpoint.slide.macroenabled.12',
+ 'sldx': 'application/vnd.openxmlformats-officedocument.presentationml.slide',
+ 'slt': 'application/vnd.epson.salt',
+ 'sm': 'application/vnd.stepmania.stepchart',
+ 'smf': 'application/vnd.stardivision.math',
+ 'smi': 'application/smil+xml',
+ 'smil': 'application/smil+xml',
+ 'smv': 'video/x-smv',
+ 'smzip': 'application/vnd.stepmania.package',
+ 'snd': 'audio/basic',
+ 'snf': 'application/x-font-snf',
+ 'so': 'application/octet-stream',
+ 'spc': 'application/x-pkcs7-certificates',
+ 'spf': 'application/vnd.yamaha.smaf-phrase',
+ 'spl': 'application/x-futuresplash',
+ 'spot': 'text/vnd.in3d.spot',
+ 'spp': 'application/scvp-vp-response',
+ 'spq': 'application/scvp-vp-request',
+ 'spx': 'audio/ogg',
+ 'sql': 'application/x-sql',
+ 'src': 'application/x-wais-source',
+ 'srt': 'application/x-subrip',
+ 'sru': 'application/sru+xml',
+ 'srx': 'application/sparql-results+xml',
+ 'ssdl': 'application/ssdl+xml',
+ 'sse': 'application/vnd.kodak-descriptor',
+ 'ssf': 'application/vnd.epson.ssf',
+ 'ssml': 'application/ssml+xml',
+ 'st': 'application/vnd.sailingtracker.track',
+ 'stc': 'application/vnd.sun.xml.calc.template',
+ 'std': 'application/vnd.sun.xml.draw.template',
+ 'stf': 'application/vnd.wt.stf',
+ 'sti': 'application/vnd.sun.xml.impress.template',
+ 'stk': 'application/hyperstudio',
+ 'stl': 'application/vnd.ms-pki.stl',
+ 'str': 'application/vnd.pg.format',
+ 'stw': 'application/vnd.sun.xml.writer.template',
+ 'sub': 'text/vnd.dvb.subtitle',
+ 'sus': 'application/vnd.sus-calendar',
+ 'susp': 'application/vnd.sus-calendar',
+ 'sv4cpio': 'application/x-sv4cpio',
+ 'sv4crc': 'application/x-sv4crc',
+ 'svc': 'application/vnd.dvb.service',
+ 'svd': 'application/vnd.svd',
+ 'svg': 'image/svg+xml',
+ 'svgz': 'image/svg+xml',
+ 'swa': 'application/x-director',
+ 'swf': 'application/x-shockwave-flash',
+ 'swi': 'application/vnd.aristanetworks.swi',
+ 'sxc': 'application/vnd.sun.xml.calc',
+ 'sxd': 'application/vnd.sun.xml.draw',
+ 'sxg': 'application/vnd.sun.xml.writer.global',
+ 'sxi': 'application/vnd.sun.xml.impress',
+ 'sxm': 'application/vnd.sun.xml.math',
+ 'sxw': 'application/vnd.sun.xml.writer',
+ 't': 'text/troff',
+ 't3': 'application/x-t3vm-image',
+ 'taglet': 'application/vnd.mynfc',
+ 'tao': 'application/vnd.tao.intent-module-archive',
+ 'tar': 'application/x-tar',
+ 'tcap': 'application/vnd.3gpp2.tcap',
+ 'tcl': 'application/x-tcl',
+ 'teacher': 'application/vnd.smart.teacher',
+ 'tei': 'application/tei+xml',
+ 'teicorpus': 'application/tei+xml',
+ 'tex': 'application/x-tex',
+ 'texi': 'application/x-texinfo',
+ 'texinfo': 'application/x-texinfo',
+ 'text': 'text/plain',
+ 'tfi': 'application/thraud+xml',
+ 'tfm': 'application/x-tex-tfm',
+ 'tga': 'image/x-tga',
+ 'thmx': 'application/vnd.ms-officetheme',
+ 'tif': 'image/tiff',
+ 'tiff': 'image/tiff',
+ 'tmo': 'application/vnd.tmobile-livetv',
+ // Source: https://toml.io/en/v1.0.0#mime-type
+ 'toml': 'application/toml',
+ 'torrent': 'application/x-bittorrent',
+ 'tpl': 'application/vnd.groove-tool-template',
+ 'tpt': 'application/vnd.trid.tpt',
+ 'tr': 'text/troff',
+ 'tra': 'application/vnd.trueapp',
+ 'trm': 'application/x-msterminal',
+ 'tsd': 'application/timestamped-data',
+ 'tsv': 'text/tab-separated-values',
+ 'ttc': 'application/x-font-ttf',
+ 'ttf': 'application/x-font-ttf',
+ 'ttl': 'text/turtle',
+ 'twd': 'application/vnd.simtech-mindmapper',
+ 'twds': 'application/vnd.simtech-mindmapper',
+ 'txd': 'application/vnd.genomatix.tuxedo',
+ 'txf': 'application/vnd.mobius.txf',
+ 'txt': 'text/plain',
+ 'u32': 'application/x-authorware-bin',
+ 'udeb': 'application/x-debian-package',
+ 'ufd': 'application/vnd.ufdl',
+ 'ufdl': 'application/vnd.ufdl',
+ 'ulx': 'application/x-glulx',
+ 'umj': 'application/vnd.umajin',
+ 'unityweb': 'application/vnd.unity',
+ 'uoml': 'application/vnd.uoml+xml',
+ 'uri': 'text/uri-list',
+ 'uris': 'text/uri-list',
+ 'urls': 'text/uri-list',
+ 'ustar': 'application/x-ustar',
+ 'utz': 'application/vnd.uiq.theme',
+ 'uu': 'text/x-uuencode',
+ 'uva': 'audio/vnd.dece.audio',
+ 'uvd': 'application/vnd.dece.data',
+ 'uvf': 'application/vnd.dece.data',
+ 'uvg': 'image/vnd.dece.graphic',
+ 'uvh': 'video/vnd.dece.hd',
+ 'uvi': 'image/vnd.dece.graphic',
+ 'uvm': 'video/vnd.dece.mobile',
+ 'uvp': 'video/vnd.dece.pd',
+ 'uvs': 'video/vnd.dece.sd',
+ 'uvt': 'application/vnd.dece.ttml+xml',
+ 'uvu': 'video/vnd.uvvu.mp4',
+ 'uvv': 'video/vnd.dece.video',
+ 'uvva': 'audio/vnd.dece.audio',
+ 'uvvd': 'application/vnd.dece.data',
+ 'uvvf': 'application/vnd.dece.data',
+ 'uvvg': 'image/vnd.dece.graphic',
+ 'uvvh': 'video/vnd.dece.hd',
+ 'uvvi': 'image/vnd.dece.graphic',
+ 'uvvm': 'video/vnd.dece.mobile',
+ 'uvvp': 'video/vnd.dece.pd',
+ 'uvvs': 'video/vnd.dece.sd',
+ 'uvvt': 'application/vnd.dece.ttml+xml',
+ 'uvvu': 'video/vnd.uvvu.mp4',
+ 'uvvv': 'video/vnd.dece.video',
+ 'uvvx': 'application/vnd.dece.unspecified',
+ 'uvvz': 'application/vnd.dece.zip',
+ 'uvx': 'application/vnd.dece.unspecified',
+ 'uvz': 'application/vnd.dece.zip',
+ 'vcard': 'text/vcard',
+ 'vcd': 'application/x-cdlink',
+ 'vcf': 'text/x-vcard',
+ 'vcg': 'application/vnd.groove-vcard',
+ 'vcs': 'text/x-vcalendar',
+ 'vcx': 'application/vnd.vcx',
+ 'vis': 'application/vnd.visionary',
+ 'viv': 'video/vnd.vivo',
+ 'vob': 'video/x-ms-vob',
+ 'vor': 'application/vnd.stardivision.writer',
+ 'vox': 'application/x-authorware-bin',
+ 'vrml': 'model/vrml',
+ 'vsd': 'application/vnd.visio',
+ 'vsf': 'application/vnd.vsf',
+ 'vss': 'application/vnd.visio',
+ 'vst': 'application/vnd.visio',
+ 'vsw': 'application/vnd.visio',
+ 'vtu': 'model/vnd.vtu',
+ 'vxml': 'application/voicexml+xml',
+ 'w3d': 'application/x-director',
+ 'wad': 'application/x-doom',
+ 'wasm': 'application/wasm',
+ 'wav': 'audio/x-wav',
+ 'wax': 'audio/x-ms-wax',
+ 'wbmp': 'image/vnd.wap.wbmp',
+ 'wbs': 'application/vnd.criticaltools.wbs+xml',
+ 'wbxml': 'application/vnd.wap.wbxml',
+ 'wcm': 'application/vnd.ms-works',
+ 'wdb': 'application/vnd.ms-works',
+ 'wdp': 'image/vnd.ms-photo',
+ 'weba': 'audio/webm',
+ 'webm': 'video/webm',
+ // Source: https://w3c.github.io/manifest/#media-type-registration
+ 'webmanifest': 'application/manifest+json',
+ 'webp': 'image/webp',
+ 'wg': 'application/vnd.pmi.widget',
+ 'wgt': 'application/widget',
+ 'wks': 'application/vnd.ms-works',
+ 'wm': 'video/x-ms-wm',
+ 'wma': 'audio/x-ms-wma',
+ 'wmd': 'application/x-ms-wmd',
+ 'wmf': 'application/x-msmetafile',
+ 'wml': 'text/vnd.wap.wml',
+ 'wmlc': 'application/vnd.wap.wmlc',
+ 'wmls': 'text/vnd.wap.wmlscript',
+ 'wmlsc': 'application/vnd.wap.wmlscriptc',
+ 'wmv': 'video/x-ms-wmv',
+ 'wmx': 'video/x-ms-wmx',
+ 'wmz': 'application/x-ms-wmz',
+ 'woff': 'application/x-font-woff',
+ 'woff2': 'font/woff2',
+ 'wpd': 'application/vnd.wordperfect',
+ 'wpl': 'application/vnd.ms-wpl',
+ 'wps': 'application/vnd.ms-works',
+ 'wqd': 'application/vnd.wqd',
+ 'wri': 'application/x-mswrite',
+ 'wrl': 'model/vrml',
+ 'wsdl': 'application/wsdl+xml',
+ 'wspolicy': 'application/wspolicy+xml',
+ 'wtb': 'application/vnd.webturbo',
+ 'wvx': 'video/x-ms-wvx',
+ 'x32': 'application/x-authorware-bin',
+ 'x3d': 'model/x3d+xml',
+ 'x3db': 'model/x3d+binary',
+ 'x3dbz': 'model/x3d+binary',
+ 'x3dv': 'model/x3d+vrml',
+ 'x3dvz': 'model/x3d+vrml',
+ 'x3dz': 'model/x3d+xml',
+ 'xaml': 'application/xaml+xml',
+ 'xap': 'application/x-silverlight-app',
+ 'xar': 'application/vnd.xara',
+ 'xbap': 'application/x-ms-xbap',
+ 'xbd': 'application/vnd.fujixerox.docuworks.binder',
+ 'xbm': 'image/x-xbitmap',
+ 'xdf': 'application/xcap-diff+xml',
+ 'xdm': 'application/vnd.syncml.dm+xml',
+ 'xdp': 'application/vnd.adobe.xdp+xml',
+ 'xdssc': 'application/dssc+xml',
+ 'xdw': 'application/vnd.fujixerox.docuworks',
+ 'xenc': 'application/xenc+xml',
+ 'xer': 'application/patch-ops-error+xml',
+ 'xfdf': 'application/vnd.adobe.xfdf',
+ 'xfdl': 'application/vnd.xfdl',
+ 'xht': 'application/xhtml+xml',
+ 'xhtml': 'application/xhtml+xml',
+ 'xhvml': 'application/xv+xml',
+ 'xif': 'image/vnd.xiff',
+ 'xla': 'application/vnd.ms-excel',
+ 'xlam': 'application/vnd.ms-excel.addin.macroenabled.12',
+ 'xlc': 'application/vnd.ms-excel',
+ 'xlf': 'application/x-xliff+xml',
+ 'xlm': 'application/vnd.ms-excel',
+ 'xls': 'application/vnd.ms-excel',
+ 'xlsb': 'application/vnd.ms-excel.sheet.binary.macroenabled.12',
+ 'xlsm': 'application/vnd.ms-excel.sheet.macroenabled.12',
+ 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'xlt': 'application/vnd.ms-excel',
+ 'xltm': 'application/vnd.ms-excel.template.macroenabled.12',
+ 'xltx':
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
+ 'xlw': 'application/vnd.ms-excel',
+ 'xm': 'audio/xm',
+ 'xml': 'application/xml',
+ 'xo': 'application/vnd.olpc-sugar',
+ 'xop': 'application/xop+xml',
+ 'xpi': 'application/x-xpinstall',
+ 'xpl': 'application/xproc+xml',
+ 'xpm': 'image/x-xpixmap',
+ 'xpr': 'application/vnd.is-xpr',
+ 'xps': 'application/vnd.ms-xpsdocument',
+ 'xpw': 'application/vnd.intercon.formnet',
+ 'xpx': 'application/vnd.intercon.formnet',
+ 'xsl': 'application/xml',
+ 'xslt': 'application/xslt+xml',
+ 'xsm': 'application/vnd.syncml+xml',
+ 'xspf': 'application/xspf+xml',
+ 'xul': 'application/vnd.mozilla.xul+xml',
+ 'xvm': 'application/xv+xml',
+ 'xvml': 'application/xv+xml',
+ 'xwd': 'image/x-xwindowdump',
+ 'xyz': 'chemical/x-xyz',
+ 'xz': 'application/x-xz',
+ 'yang': 'application/yang',
+ 'yin': 'application/yin+xml',
+ 'z1': 'application/x-zmachine',
+ 'z2': 'application/x-zmachine',
+ 'z3': 'application/x-zmachine',
+ 'z4': 'application/x-zmachine',
+ 'z5': 'application/x-zmachine',
+ 'z6': 'application/x-zmachine',
+ 'z7': 'application/x-zmachine',
+ 'z8': 'application/x-zmachine',
+ 'zaz': 'application/vnd.zzazz.deck+xml',
+ 'zip': 'application/zip',
+ 'zir': 'application/vnd.zul',
+ 'zirz': 'application/vnd.zul',
+ 'zmm': 'application/vnd.handheld-entertainment+xml',
+};
diff --git a/pkgs/mime/lib/src/extension.dart b/pkgs/mime/lib/src/extension.dart
new file mode 100644
index 0000000..293449a
--- /dev/null
+++ b/pkgs/mime/lib/src/extension.dart
@@ -0,0 +1,55 @@
+// Copyright (c) 2023, 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 'default_extension_map.dart';
+
+/// Default extension for recognized MIME types.
+///
+/// Is the inverse of [defaultExtensionMap], and where that
+/// map has multiple extensions which map to the same
+/// MIME type, this map maps that MIME type to a *default*
+/// extension.
+///
+/// Used by [extensionFromMime].
+final Map<String, String> _defaultMimeTypeMap = {
+ for (var entry in defaultExtensionMap.entries) entry.value: entry.key,
+ 'application/msword': 'doc',
+ 'application/vnd.ms-excel': 'xls',
+ 'application/vnd.ms-powerpoint': 'ppt',
+ 'application/x-debian-package': 'deb',
+ 'application/xhtml+xml': 'xhtml',
+ 'application/xml': 'xml',
+ 'audio/x-aiff': 'aif',
+ 'audio/midi': 'mid',
+ 'audio/mp4': 'm4a',
+ 'audio/ogg': 'ogg',
+ 'image/jpeg': 'jpg',
+ 'image/tiff': 'tif',
+ 'image/svg+xml': 'svg',
+ 'model/vrml': 'vrml',
+ 'text/calendar': 'ics',
+ 'text/html': 'html',
+ 'text/javascript': 'js',
+ 'text/markdown': 'md',
+ 'text/plain': 'txt',
+ 'text/sgml': 'sgml',
+ 'text/x-asm': 'asm',
+ 'text/x-c': 'c',
+ 'text/x-pascal': 'pas',
+ 'video/mp4': 'mp4',
+ 'video/mpeg': 'mpg',
+ 'video/quicktime': 'mov',
+ 'video/x-matroska': 'mkv',
+};
+
+/// The default file extension for a given MIME type.
+///
+/// If [mimeType] has multiple associated extensions,
+/// the returned string is one of those, chosen as the default
+/// extension for that MIME type.
+///
+/// Returns `null` if [mimeType] is not a recognized and
+/// supported MIME type.
+String? extensionFromMime(String mimeType) =>
+ _defaultMimeTypeMap[mimeType.toLowerCase()];
diff --git a/pkgs/mime/lib/src/magic_number.dart b/pkgs/mime/lib/src/magic_number.dart
new file mode 100644
index 0000000..c8b5c3b
--- /dev/null
+++ b/pkgs/mime/lib/src/magic_number.dart
@@ -0,0 +1,407 @@
+// 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.
+
+class MagicNumber {
+ final String mimeType;
+ final List<int> numbers;
+ final List<int>? mask;
+
+ const MagicNumber(this.mimeType, this.numbers, {this.mask});
+
+ bool matches(List<int> header) {
+ if (header.length < numbers.length) return false;
+
+ for (var i = 0; i < numbers.length; i++) {
+ if (mask != null) {
+ if ((mask![i] & numbers[i]) != (mask![i] & header[i])) return false;
+ } else {
+ if (numbers[i] != header[i]) return false;
+ }
+ }
+
+ return true;
+ }
+}
+
+const int initialMagicNumbersMaxLength = 12;
+
+const List<MagicNumber> initialMagicNumbers = [
+ MagicNumber('application/pdf', [0x25, 0x50, 0x44, 0x46]),
+ MagicNumber('application/postscript', [0x25, 0x51]),
+
+ /// AIFF is based on the EA IFF 85 Standard for Interchange Format Files.
+ /// -> 4 bytes have the ASCII characters 'F' 'O' 'R' 'M'.
+ /// -> 4 bytes indicating the size of the file
+ /// -> 4 bytes have the ASCII characters 'A' 'I' 'F' 'F'.
+ /// http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/AIFF/Docs/AIFF-1.3.pdf
+ MagicNumber('audio/x-aiff', [
+ 0x46,
+ 0x4F,
+ 0x52,
+ 0x4D,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x41,
+ 0x49,
+ 0x46,
+ 0x46
+ ], mask: [
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF
+ ]),
+
+ /// -> 4 bytes have the ASCII characters 'f' 'L' 'a' 'C'.
+ /// https://xiph.org/flac/format.html
+ MagicNumber('audio/x-flac', [0x66, 0x4C, 0x61, 0x43]),
+
+ /// The WAVE file format is based on the RIFF document format.
+ /// -> 4 bytes have the ASCII characters 'R' 'I' 'F' 'F'.
+ /// -> 4 bytes indicating the size of the file
+ /// -> 4 bytes have the ASCII characters 'W' 'A' 'V' 'E'.
+ /// http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf
+ MagicNumber('audio/x-wav', [
+ 0x52,
+ 0x49,
+ 0x46,
+ 0x46,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x57,
+ 0x41,
+ 0x56,
+ 0x45
+ ], mask: [
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF
+ ]),
+ MagicNumber('image/gif', [0x47, 0x49, 0x46, 0x38, 0x37, 0x61]),
+ MagicNumber('image/gif', [0x47, 0x49, 0x46, 0x38, 0x39, 0x61]),
+ MagicNumber('image/jpeg', [0xFF, 0xD8]),
+ MagicNumber('image/png', [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]),
+ MagicNumber('image/tiff', [0x49, 0x49, 0x2A, 0x00]),
+ MagicNumber('image/tiff', [0x4D, 0x4D, 0x00, 0x2A]),
+ MagicNumber('audio/aac', [0xFF, 0xF1]),
+ MagicNumber('audio/aac', [0xFF, 0xF9]),
+ MagicNumber('audio/weba', [0x1A, 0x45, 0xDF, 0xA3]),
+ MagicNumber('audio/mpeg', [0x49, 0x44, 0x33]),
+ MagicNumber('audio/mpeg', [0xFF, 0xFB]),
+ MagicNumber('audio/ogg', [0x4F, 0x70, 0x75]),
+ MagicNumber('video/3gpp', [
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x66,
+ 0x74,
+ 0x79,
+ 0x70,
+ 0x33,
+ 0x67,
+ 0x70,
+ 0x35
+ ], mask: [
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0x00,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF
+ ]),
+ MagicNumber('video/mp4', [
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x66,
+ 0x74,
+ 0x79,
+ 0x70,
+ 0x61,
+ 0x76,
+ 0x63,
+ 0x31
+ ], mask: [
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF
+ ]),
+ MagicNumber('video/mp4', [
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x66,
+ 0x74,
+ 0x79,
+ 0x70,
+ 0x69,
+ 0x73,
+ 0x6F,
+ 0x32
+ ], mask: [
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF
+ ]),
+ MagicNumber('video/mp4', [
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x66,
+ 0x74,
+ 0x79,
+ 0x70,
+ 0x69,
+ 0x73,
+ 0x6F,
+ 0x6D
+ ], mask: [
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF
+ ]),
+ MagicNumber('video/mp4', [
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x66,
+ 0x74,
+ 0x79,
+ 0x70,
+ 0x6D,
+ 0x70,
+ 0x34,
+ 0x31
+ ], mask: [
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF
+ ]),
+ MagicNumber('video/mp4', [
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x66,
+ 0x74,
+ 0x79,
+ 0x70,
+ 0x6D,
+ 0x70,
+ 0x34,
+ 0x32
+ ], mask: [
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF
+ ]),
+ MagicNumber('model/gltf-binary', [0x46, 0x54, 0x6C, 0x67]),
+
+ /// The WebP file format is based on the RIFF document format.
+ /// -> 4 bytes have the ASCII characters 'R' 'I' 'F' 'F'.
+ /// -> 4 bytes indicating the size of the file
+ /// -> 4 bytes have the ASCII characters 'W' 'E' 'B' 'P'.
+ /// https://developers.google.com/speed/webp/docs/riff_container
+ MagicNumber('image/webp', [
+ 0x52,
+ 0x49,
+ 0x46,
+ 0x46,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x57,
+ 0x45,
+ 0x42,
+ 0x50
+ ], mask: [
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF
+ ]),
+
+ MagicNumber('font/woff2', [0x77, 0x4f, 0x46, 0x32]),
+
+ /// High Efficiency Image File Format (ISO/IEC 23008-12).
+ /// -> 4 bytes indicating the ftyp box length.
+ /// -> 4 bytes have the ASCII characters 'f' 't' 'y' 'p'.
+ /// -> 4 bytes have the ASCII characters 'h' 'e' 'i' 'c'.
+ /// https://www.iana.org/assignments/media-types/image/heic
+ MagicNumber('image/heic', [
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x66,
+ 0x74,
+ 0x79,
+ 0x70,
+ 0x68,
+ 0x65,
+ 0x69,
+ 0x63
+ ], mask: [
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF
+ ]),
+
+ /// -> 4 bytes indicating the ftyp box length.
+ /// -> 4 bytes have the ASCII characters 'f' 't' 'y' 'p'.
+ /// -> 4 bytes have the ASCII characters 'h' 'e' 'i' 'x'.
+ MagicNumber('image/heic', [
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x66,
+ 0x74,
+ 0x79,
+ 0x70,
+ 0x68,
+ 0x65,
+ 0x69,
+ 0x78
+ ], mask: [
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF
+ ]),
+
+ /// -> 4 bytes indicating the ftyp box length.
+ /// -> 4 bytes have the ASCII characters 'f' 't' 'y' 'p'.
+ /// -> 4 bytes have the ASCII characters 'm' 'i' 'f' '1'.
+ MagicNumber('image/heif', [
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x66,
+ 0x74,
+ 0x79,
+ 0x70,
+ 0x6D,
+ 0x69,
+ 0x66,
+ 0x31
+ ], mask: [
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF
+ ]),
+];
diff --git a/pkgs/mime/lib/src/mime_multipart_transformer.dart b/pkgs/mime/lib/src/mime_multipart_transformer.dart
new file mode 100644
index 0000000..2648d7d
--- /dev/null
+++ b/pkgs/mime/lib/src/mime_multipart_transformer.dart
@@ -0,0 +1,42 @@
+// 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:async';
+import 'dart:typed_data';
+
+import 'bound_multipart_stream.dart';
+import 'char_code.dart' as char_code;
+import 'mime_shared.dart';
+
+Uint8List _getBoundary(String boundary) {
+ final charCodes = boundary.codeUnits;
+
+ final boundaryList = Uint8List(4 + charCodes.length);
+ // Set-up the matching boundary preceding it with CRLF and two
+ // dashes.
+ boundaryList[0] = char_code.cr;
+ boundaryList[1] = char_code.lf;
+ boundaryList[2] = char_code.dash;
+ boundaryList[3] = char_code.dash;
+ boundaryList.setRange(4, 4 + charCodes.length, charCodes);
+ return boundaryList;
+}
+
+/// Parser for MIME multipart types of data as described in RFC 2046
+/// section 5.1.1. The data is transformed into [MimeMultipart] objects, each
+/// of them streaming the multipart data.
+class MimeMultipartTransformer
+ extends StreamTransformerBase<List<int>, MimeMultipart> {
+ final List<int> _boundary;
+
+ /// Construct a new MIME multipart parser with the boundary
+ /// [boundary]. The boundary should be as specified in the content
+ /// type parameter, that is without the -- prefix.
+ MimeMultipartTransformer(String boundary)
+ : _boundary = _getBoundary(boundary);
+
+ @override
+ Stream<MimeMultipart> bind(Stream<List<int>> stream) =>
+ BoundMultipartStream(_boundary, stream).stream;
+}
diff --git a/pkgs/mime/lib/src/mime_shared.dart b/pkgs/mime/lib/src/mime_shared.dart
new file mode 100644
index 0000000..1c98971
--- /dev/null
+++ b/pkgs/mime/lib/src/mime_shared.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2014, 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 'mime_multipart_transformer.dart';
+
+class MimeMultipartException implements Exception {
+ final String message;
+
+ const MimeMultipartException([this.message = '']);
+
+ @override
+ String toString() => 'MimeMultipartException: $message';
+}
+
+/// A Mime Multipart class representing each part parsed by
+/// [MimeMultipartTransformer]. The data is streamed in as it become available.
+abstract class MimeMultipart extends Stream<List<int>> {
+ Map<String, String> get headers;
+}
diff --git a/pkgs/mime/lib/src/mime_type.dart b/pkgs/mime/lib/src/mime_type.dart
new file mode 100644
index 0000000..9cf9194
--- /dev/null
+++ b/pkgs/mime/lib/src/mime_type.dart
@@ -0,0 +1,109 @@
+// 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 'default_extension_map.dart';
+import 'magic_number.dart';
+
+final MimeTypeResolver _globalResolver = MimeTypeResolver();
+
+/// The maximum number of bytes needed, to match all default magic-numbers.
+int get defaultMagicNumbersMaxLength => _globalResolver.magicNumbersMaxLength;
+
+/// Extract the extension from [path] and use that for MIME-type lookup, using
+/// the default extension map.
+///
+/// If no matching MIME-type was found, `null` is returned.
+///
+/// If [headerBytes] is present, a match for known magic-numbers will be
+/// performed first. This allows the correct mime-type to be found, even though
+/// a file have been saved using the wrong file-name extension. If less than
+/// [defaultMagicNumbersMaxLength] bytes was provided, some magic-numbers won't
+/// be matched against.
+String? lookupMimeType(String path, {List<int>? headerBytes}) =>
+ _globalResolver.lookup(path, headerBytes: headerBytes);
+
+/// MIME-type resolver class, used to customize the lookup of mime-types.
+class MimeTypeResolver {
+ final Map<String, String> _extensionMap = {};
+ final List<MagicNumber> _magicNumbers = [];
+ final bool _useDefault;
+ int _magicNumbersMaxLength;
+
+ /// Create a new empty [MimeTypeResolver].
+ MimeTypeResolver.empty()
+ : _useDefault = false,
+ _magicNumbersMaxLength = 0;
+
+ /// Create a new [MimeTypeResolver] containing the default scope.
+ MimeTypeResolver()
+ : _useDefault = true,
+ _magicNumbersMaxLength = initialMagicNumbersMaxLength;
+
+ /// Get the maximum number of bytes required to match all magic numbers, when
+ /// performing [lookup] with headerBytes present.
+ int get magicNumbersMaxLength => _magicNumbersMaxLength;
+
+ /// Extract the extension from [path] and use that for MIME-type lookup.
+ ///
+ /// If no matching MIME-type was found, `null` is returned.
+ ///
+ /// If [headerBytes] is present, a match for known magic-numbers will be
+ /// performed first. This allows the correct mime-type to be found, even
+ /// though a file have been saved using the wrong file-name extension. If less
+ /// than [magicNumbersMaxLength] bytes was provided, some magic-numbers won't
+ /// be matched against.
+ String? lookup(String path, {List<int>? headerBytes}) {
+ String? result;
+ if (headerBytes != null) {
+ result = _matchMagic(headerBytes, _magicNumbers);
+ if (result != null) return result;
+ if (_useDefault) {
+ result = _matchMagic(headerBytes, initialMagicNumbers);
+ if (result != null) return result;
+ }
+ }
+ final ext = _ext(path);
+ result = _extensionMap[ext];
+ if (result != null) return result;
+ if (_useDefault) {
+ result = defaultExtensionMap[ext];
+ if (result != null) return result;
+ }
+ return null;
+ }
+
+ /// Add a new MIME-type mapping to the [MimeTypeResolver]. If the [extension]
+ /// is already present in the [MimeTypeResolver], it'll be overwritten.
+ void addExtension(String extension, String mimeType) {
+ _extensionMap[extension] = mimeType;
+ }
+
+ /// Add a new magic-number mapping to the [MimeTypeResolver].
+ ///
+ /// If [mask] is present,the [mask] is used to only perform matching on
+ /// selective bits. The [mask] must have the same length as [bytes].
+ void addMagicNumber(List<int> bytes, String mimeType, {List<int>? mask}) {
+ if (mask != null && bytes.length != mask.length) {
+ throw ArgumentError('Bytes and mask are of different lengths');
+ }
+ if (bytes.length > _magicNumbersMaxLength) {
+ _magicNumbersMaxLength = bytes.length;
+ }
+ _magicNumbers.add(MagicNumber(mimeType, bytes, mask: mask));
+ }
+
+ static String? _matchMagic(
+ List<int> headerBytes, List<MagicNumber> magicNumbers) {
+ for (var mn in magicNumbers) {
+ if (mn.matches(headerBytes)) return mn.mimeType;
+ }
+ return null;
+ }
+
+ static String _ext(String path) {
+ final index = path.lastIndexOf('.');
+ if (index < 0 || index + 1 >= path.length) return path;
+ return path.substring(index + 1).toLowerCase();
+ }
+}
diff --git a/pkgs/mime/pubspec.yaml b/pkgs/mime/pubspec.yaml
new file mode 100644
index 0000000..6f1dec7
--- /dev/null
+++ b/pkgs/mime/pubspec.yaml
@@ -0,0 +1,19 @@
+name: mime
+version: 2.0.0
+description: >-
+ Utilities for handling media (MIME) types, including determining a type from
+ a file extension and file contents.
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/mime
+issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Amime
+topics:
+ - magic-numbers
+ - mime
+ - mimetype
+ - multipart-form
+
+environment:
+ sdk: ^3.2.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ test: ^1.16.0
diff --git a/pkgs/mime/test/default_extension_map_test.dart b/pkgs/mime/test/default_extension_map_test.dart
new file mode 100644
index 0000000..b5539dd
--- /dev/null
+++ b/pkgs/mime/test/default_extension_map_test.dart
@@ -0,0 +1,16 @@
+// Copyright (c) 2020, 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:mime/src/default_extension_map.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('defaultExtensionMap', () {
+ test('keys are lowercase', () {
+ for (final key in defaultExtensionMap.keys) {
+ expect(key, equals(key.toLowerCase()));
+ }
+ });
+ });
+}
diff --git a/pkgs/mime/test/extension_test.dart b/pkgs/mime/test/extension_test.dart
new file mode 100644
index 0000000..40dae6d
--- /dev/null
+++ b/pkgs/mime/test/extension_test.dart
@@ -0,0 +1,33 @@
+// Copyright (c) 2023, 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:mime/mime.dart';
+import 'package:test/test.dart';
+
+void main() {
+ test('valid-mime-type', () {
+ expect(extensionFromMime('text/x-dart'), equals('dart'));
+ expect(extensionFromMime('text/javascript'), equals('js'));
+ expect(extensionFromMime('application/java-archive'), equals('jar'));
+ expect(extensionFromMime('application/json'), equals('json'));
+ expect(extensionFromMime('application/pdf'), equals('pdf'));
+ expect(extensionFromMime('application/vnd.ms-excel'), equals('xls'));
+ expect(extensionFromMime('application/xhtml+xml'), equals('xhtml'));
+ expect(extensionFromMime('image/jpeg'), equals('jpg'));
+ expect(extensionFromMime('image/png'), equals('png'));
+ expect(extensionFromMime('text/css'), equals('css'));
+ expect(extensionFromMime('text/html'), equals('html'));
+ expect(extensionFromMime('text/plain'), equals('txt'));
+ expect(extensionFromMime('text/x-c'), equals('c'));
+ });
+
+ test('invalid-mime-type', () {
+ expect(extensionFromMime('invalid-mime-type'), isNull);
+ expect(extensionFromMime('invalid/mime/type'), isNull);
+ });
+
+ test('unknown-mime-type', () {
+ expect(extensionFromMime('application/to-be-invented'), isNull);
+ });
+}
diff --git a/pkgs/mime/test/mime_multipart_transformer_test.dart b/pkgs/mime/test/mime_multipart_transformer_test.dart
new file mode 100644
index 0000000..109a9bc
--- /dev/null
+++ b/pkgs/mime/test/mime_multipart_transformer_test.dart
@@ -0,0 +1,469 @@
+// 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:math';
+
+import 'package:mime/mime.dart';
+import 'package:test/test.dart';
+
+void _writeInChunks(
+ List<int> data, int chunkSize, StreamController<List<int>> controller) {
+ if (chunkSize == -1) chunkSize = data.length;
+
+ for (var pos = 0; pos < data.length; pos += chunkSize) {
+ final remaining = data.length - pos;
+ final writeLength = min(chunkSize, remaining);
+ controller.add(data.sublist(pos, pos + writeLength));
+ }
+ controller.close();
+}
+
+enum TestMode { immediateListen, delayListen, pauseResume }
+
+void _runParseTest(
+ String message,
+ String boundary,
+ TestMode mode, [
+ List<Map<String, String>>? expectedHeaders,
+ List<String?>? expectedParts,
+ bool expectError = false,
+]) {
+ Future<void> testWrite(List<int> data, [int chunkSize = -1]) {
+ final controller = StreamController<List<int>>(sync: true);
+
+ final stream =
+ controller.stream.transform(MimeMultipartTransformer(boundary));
+ var i = 0;
+ final completer = Completer<void>();
+ final futures = <Future<void>>[];
+ stream.listen((multipart) {
+ final part = i++;
+ if (expectedHeaders != null) {
+ expect(multipart.headers, equals(expectedHeaders[part]));
+ }
+ switch (mode) {
+ case TestMode.immediateListen:
+ futures.add(multipart.fold<List<int>>(
+ [], (buffer, data) => buffer..addAll(data)).then((data) {
+ if (expectedParts?[part] != null) {
+ expect(data, equals(expectedParts?[part]!.codeUnits));
+ }
+ }));
+
+ case TestMode.delayListen:
+ futures.add(
+ Future(
+ () => multipart.fold<List<int>>(
+ [],
+ (buffer, data) => buffer..addAll(data),
+ ).then(
+ (data) {
+ if (expectedParts?[part] != null) {
+ expect(data, equals(expectedParts?[part]!.codeUnits));
+ }
+ },
+ ),
+ ),
+ );
+
+ case TestMode.pauseResume:
+ final completer = Completer<void>();
+ futures.add(completer.future);
+ final buffer = <int>[];
+ late StreamSubscription<List<int>> subscription;
+ subscription = multipart.listen((data) {
+ buffer.addAll(data);
+ subscription.pause();
+ Future(() => subscription.resume());
+ }, onDone: () {
+ if (expectedParts?[part] != null) {
+ expect(buffer, equals(expectedParts?[part]!.codeUnits));
+ }
+ completer.complete();
+ });
+ addTearDown(subscription.cancel);
+ }
+ }, onError: (Object error) {
+ // ignore: only_throw_errors
+ if (!expectError) throw error;
+ }, onDone: () {
+ if (expectedParts != null) {
+ expect(i, equals(expectedParts.length));
+ }
+ Future.wait(futures).then(completer.complete);
+ });
+
+ _writeInChunks(data, chunkSize, controller);
+
+ return completer.future;
+ }
+
+ Future<void> testFirstPartOnly(List<int> data, [int chunkSize = -1]) {
+ final completer = Completer<void>();
+ final controller = StreamController<List<int>>(sync: true);
+
+ final stream =
+ controller.stream.transform(MimeMultipartTransformer(boundary));
+
+ stream.first.then((multipart) {
+ if (expectedHeaders != null) {
+ expect(multipart.headers, equals(expectedHeaders[0]));
+ }
+ return multipart.fold<List<int>>([], (b, d) => b..addAll(d)).then(
+ (data) {
+ if (expectedParts != null && expectedParts[0] != null) {
+ expect(data, equals(expectedParts[0]!.codeUnits));
+ }
+ },
+ );
+ }).then((_) {
+ completer.complete();
+ });
+
+ _writeInChunks(data, chunkSize, controller);
+
+ return completer.future;
+ }
+
+ Future<void> testCompletePartAfterCancel(List<int> data, int parts,
+ [int chunkSize = -1]) {
+ final completer = Completer<void>();
+ final controller = StreamController<List<int>>(sync: true);
+ final stream =
+ controller.stream.transform(MimeMultipartTransformer(boundary));
+ late StreamSubscription<void> subscription;
+ var i = 0;
+ final futures = <Future<void>>[];
+ subscription = stream.listen((multipart) {
+ final partIndex = i;
+
+ if (partIndex >= parts) {
+ throw StateError('Expected no more parts, but got one.');
+ }
+
+ if (expectedHeaders != null) {
+ expect(multipart.headers, equals(expectedHeaders[partIndex]));
+ }
+ futures.add(
+ multipart.fold<List<int>>([], (b, d) => b..addAll(d)).then((data) {
+ if (expectedParts != null && expectedParts[partIndex] != null) {
+ expect(data, equals(expectedParts[partIndex]!.codeUnits));
+ }
+ }));
+
+ if (partIndex == (parts - 1)) {
+ subscription.cancel();
+ Future.wait(futures).then(completer.complete);
+ }
+ i++;
+ });
+
+ _writeInChunks(data, chunkSize, controller);
+
+ return completer.future;
+ }
+
+ // Test parsing the data three times delivering the data in
+ // different chunks.
+ final data = message.codeUnits;
+ test('test', () {
+ expect(
+ Future.wait([
+ testWrite(data),
+ testWrite(data, 10),
+ testWrite(data, 2),
+ testWrite(data, 1),
+ ]),
+ completes);
+ });
+
+ if (expectedParts!.isNotEmpty) {
+ test('test-first-part-only', () {
+ expect(
+ Future.wait([
+ testFirstPartOnly(data),
+ testFirstPartOnly(data, 10),
+ testFirstPartOnly(data, 2),
+ testFirstPartOnly(data, 1),
+ ]),
+ completes);
+ });
+
+ test('test-n-parts-only', () {
+ var numPartsExpected = expectedParts.length - 1;
+ if (numPartsExpected == 0) numPartsExpected = 1;
+
+ expect(
+ Future.wait([
+ testCompletePartAfterCancel(data, numPartsExpected),
+ testCompletePartAfterCancel(data, numPartsExpected, 10),
+ testCompletePartAfterCancel(data, numPartsExpected, 2),
+ testCompletePartAfterCancel(data, numPartsExpected, 1),
+ ]),
+ completes);
+ });
+ }
+}
+
+void _testParse(String message, String boundary,
+ [List<Map<String, String>>? expectedHeaders,
+ List<String?>? expectedParts,
+ bool expectError = false]) {
+ _runParseTest(message, boundary, TestMode.immediateListen, expectedHeaders,
+ expectedParts, expectError);
+ _runParseTest(message, boundary, TestMode.delayListen, expectedHeaders,
+ expectedParts, expectError);
+ _runParseTest(message, boundary, TestMode.pauseResume, expectedHeaders,
+ expectedParts, expectError);
+}
+
+void _testParseValid() {
+ // Empty message from Chrome form post.
+ var message = '------WebKitFormBoundaryU3FBruSkJKG0Yor1--\r\n';
+ _testParse(message, '----WebKitFormBoundaryU3FBruSkJKG0Yor1', [], []);
+
+ // Sample from Wikipedia.
+ message = '''
+This is a message with multiple parts in MIME format.\r
+--frontier\r
+Content-Type: text/plain\r
+\r
+This is the body of the message.\r
+--frontier\r
+Content-Type: application/octet-stream\r
+Content-Transfer-Encoding: base64\r
+\r
+PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHA+VGhpcyBpcyB0aGUg
+Ym9keSBvZiB0aGUgbWVzc2FnZS48L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg=\r
+--frontier--\r\n''';
+ var headers1 = <String, String>{'content-type': 'text/plain'};
+ var headers2 = <String, String>{
+ 'content-type': 'application/octet-stream',
+ 'content-transfer-encoding': 'base64'
+ };
+ var body1 = 'This is the body of the message.';
+ var body2 = '''
+PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHA+VGhpcyBpcyB0aGUg
+Ym9keSBvZiB0aGUgbWVzc2FnZS48L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg=''';
+ _testParse(message, 'frontier', [headers1, headers2], [body1, body2]);
+
+ // Sample from HTML 4.01 Specification.
+ message = '''
+\r\n--AaB03x\r
+Content-Disposition: form-data; name="submit-name"\r
+\r
+Larry\r
+--AaB03x\r
+Content-Disposition: form-data; name="files"; filename="file1.txt"\r
+Content-Type: text/plain\r
+\r
+... contents of file1.txt ...\r
+--AaB03x--\r\n''';
+ headers1 = <String, String>{
+ 'content-disposition': 'form-data; name="submit-name"'
+ };
+ headers2 = <String, String>{
+ 'content-type': 'text/plain',
+ 'content-disposition': 'form-data; name="files"; filename="file1.txt"'
+ };
+ body1 = 'Larry';
+ body2 = '... contents of file1.txt ...';
+ _testParse(message, 'AaB03x', [headers1, headers2], [body1, body2]);
+
+ // Longer form from submitting the following from Chrome.
+ //
+ // <html>
+ // <body>
+ // <FORM action="http://127.0.0.1:1234/"
+ // enctype="multipart/form-data"
+ // method='post'>
+ // <P>
+ // Text: <INPUT type='text' name='text_input'>
+ // Password: <INPUT type='password' name='password_input'>
+ // Checkbox: <INPUT type='checkbox' name='checkbox_input'>
+ // Radio: <INPUT type='radio' name='radio_input'>
+ // Send <INPUT type='submit'>
+ // </P>
+ // </FORM>
+ // </body>
+ // </html>
+
+ message = '''
+\r\n------WebKitFormBoundaryQ3cgYAmGRF8yOeYB\r
+Content-Disposition: form-data; name="text_input"\r
+\r
+text\r
+------WebKitFormBoundaryQ3cgYAmGRF8yOeYB\r
+Content-Disposition: form-data; name="password_input"\r
+\r
+password\r
+------WebKitFormBoundaryQ3cgYAmGRF8yOeYB\r
+Content-Disposition: form-data; name="checkbox_input"\r
+\r
+on\r
+------WebKitFormBoundaryQ3cgYAmGRF8yOeYB\r
+Content-Disposition: form-data; name="radio_input"\r
+\r
+on\r
+------WebKitFormBoundaryQ3cgYAmGRF8yOeYB--\r\n''';
+ headers1 = <String, String>{
+ 'content-disposition': 'form-data; name="text_input"'
+ };
+ headers2 = <String, String>{
+ 'content-disposition': 'form-data; name="password_input"'
+ };
+ final headers3 = <String, String>{
+ 'content-disposition': 'form-data; name="checkbox_input"'
+ };
+ final headers4 = <String, String>{
+ 'content-disposition': 'form-data; name="radio_input"'
+ };
+ body1 = 'text';
+ body2 = 'password';
+ const body3 = 'on';
+ const body4 = 'on';
+ _testParse(message, '----WebKitFormBoundaryQ3cgYAmGRF8yOeYB',
+ [headers1, headers2, headers3, headers4], [body1, body2, body3, body4]);
+
+ // Same form from Firefox.
+ message = '''
+\r\n-----------------------------52284550912143824192005403738\r
+Content-Disposition: form-data; name="text_input"\r
+\r
+text\r
+-----------------------------52284550912143824192005403738\r
+Content-Disposition: form-data; name="password_input"\r
+\r
+password\r
+-----------------------------52284550912143824192005403738\r
+Content-Disposition: form-data; name="checkbox_input"\r
+\r
+on\r
+-----------------------------52284550912143824192005403738\r
+Content-Disposition: form-data; name="radio_input"\r
+\r
+on\r
+-----------------------------52284550912143824192005403738--\r\n''';
+ _testParse(
+ message,
+ '---------------------------52284550912143824192005403738',
+ [headers1, headers2, headers3, headers4],
+ [body1, body2, body3, body4]);
+
+ // And Internet Explorer
+ message = '''
+\r\n-----------------------------7dc8f38c60326\r
+Content-Disposition: form-data; name="text_input"\r
+\r
+text\r
+-----------------------------7dc8f38c60326\r
+Content-Disposition: form-data; name="password_input"\r
+\r
+password\r
+-----------------------------7dc8f38c60326\r
+Content-Disposition: form-data; name="checkbox_input"\r
+\r
+on\r
+-----------------------------7dc8f38c60326\r
+Content-Disposition: form-data; name="radio_input"\r
+\r
+on\r
+-----------------------------7dc8f38c60326--\r\n''';
+ _testParse(message, '---------------------------7dc8f38c60326',
+ [headers1, headers2, headers3, headers4], [body1, body2, body3, body4]);
+
+ // Test boundary prefix inside prefix and content.
+ message = '''
+-\r
+--\r
+--b\r
+--bo\r
+--bou\r
+--boun\r
+--bound\r
+--bounda\r
+--boundar\r
+--boundary\r
+Content-Type: text/plain\r
+\r
+-\r
+--\r
+--b\r
+--bo\r
+--bou\r
+--boun\r
+--bound\r\r
+--bounda\r\r\r
+--boundar\r\r\r\r
+--boundary\r
+Content-Type: text/plain\r
+\r
+--boundar\r
+--bounda\r
+--bound\r
+--boun\r
+--bou\r
+--bo\r
+--b\r\r\r\r
+--\r\r\r
+-\r\r
+--boundary--\r\n''';
+ final headers = <String, String>{'content-type': 'text/plain'};
+ body1 = '''
+-\r
+--\r
+--b\r
+--bo\r
+--bou\r
+--boun\r
+--bound\r\r
+--bounda\r\r\r
+--boundar\r\r\r''';
+ body2 = '''
+--boundar\r
+--bounda\r
+--bound\r
+--boun\r
+--bou\r
+--bo\r
+--b\r\r\r\r
+--\r\r\r
+-\r''';
+ _testParse(message, 'boundary', [headers, headers], [body1, body2]);
+
+ // Without initial CRLF.
+ message = '''
+--xxx\r
+\r
+\r
+Body 1\r
+--xxx\r
+\r
+\r
+Body2\r
+--xxx--\r\n''';
+ _testParse(message, 'xxx', null, ['\r\nBody 1', '\r\nBody2']);
+}
+
+void _testParseInvalid() {
+ // Missing end boundary.
+ const message = '''
+\r
+--xxx\r
+\r
+\r
+Body 1\r
+--xxx\r
+\r
+\r
+Body2\r
+--xxx\r\n''';
+ _testParse(message, 'xxx', null, [null, null], true);
+}
+
+void main() {
+ _testParseValid();
+ _testParseInvalid();
+}
diff --git a/pkgs/mime/test/mime_type_test.dart b/pkgs/mime/test/mime_type_test.dart
new file mode 100644
index 0000000..23cb34a
--- /dev/null
+++ b/pkgs/mime/test/mime_type_test.dart
@@ -0,0 +1,320 @@
+// 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:math' as math;
+
+import 'package:mime/mime.dart';
+import 'package:mime/src/magic_number.dart';
+import 'package:test/test.dart';
+
+void _expectMimeType(String path, String? expectedMimeType,
+ {List<int>? headerBytes, MimeTypeResolver? resolver}) {
+ String? mimeType;
+ if (resolver == null) {
+ mimeType = lookupMimeType(path, headerBytes: headerBytes);
+ } else {
+ mimeType = resolver.lookup(path, headerBytes: headerBytes);
+ }
+
+ expect(mimeType, expectedMimeType);
+}
+
+void main() {
+ group('global-lookup', () {
+ test('by-path', () {
+ _expectMimeType('file.dart', 'text/x-dart');
+ // Test mixed-case
+ _expectMimeType('file.DaRT', 'text/x-dart');
+ _expectMimeType('file.dcm', 'application/dicom');
+ _expectMimeType('file.html', 'text/html');
+ _expectMimeType('file.xhtml', 'application/xhtml+xml');
+ _expectMimeType('file.jpeg', 'image/jpeg');
+ _expectMimeType('file.jpg', 'image/jpeg');
+ _expectMimeType('file.png', 'image/png');
+ _expectMimeType('file.gif', 'image/gif');
+ _expectMimeType('file.cc', 'text/x-c');
+ _expectMimeType('file.c', 'text/x-c');
+ _expectMimeType('file.css', 'text/css');
+ _expectMimeType('file.js', 'text/javascript');
+ _expectMimeType('file.mjs', 'text/javascript');
+ _expectMimeType('file.ps', 'application/postscript');
+ _expectMimeType('file.pdf', 'application/pdf');
+ _expectMimeType('file.tiff', 'image/tiff');
+ _expectMimeType('file.tif', 'image/tiff');
+ _expectMimeType('file.webp', 'image/webp');
+ _expectMimeType('file.mp3', 'audio/mpeg');
+ _expectMimeType('file.aac', 'audio/aac');
+ _expectMimeType('file.ogg', 'audio/ogg');
+ _expectMimeType('file.aiff', 'audio/x-aiff');
+ _expectMimeType('file.m4a', 'audio/mp4');
+ _expectMimeType('file.m4b', 'audio/mp4');
+ _expectMimeType('file.toml', 'application/toml');
+ _expectMimeType('file.md', 'text/markdown');
+ _expectMimeType('file.markdown', 'text/markdown');
+ _expectMimeType('file.heif', 'image/heif');
+ _expectMimeType('file.heic', 'image/heic');
+ });
+
+ test('unknown-mime-type', () {
+ _expectMimeType('file.unsupported-extension', null);
+ });
+
+ test('by-header-bytes', () {
+ _expectMimeType('file.jpg', 'image/png',
+ headerBytes: [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
+ _expectMimeType('file.jpg', 'image/gif', headerBytes: [
+ 0x47,
+ 0x49,
+ 0x46,
+ 0x38,
+ 0x39,
+ 0x61,
+ 0x0D,
+ 0x0A,
+ 0x1A,
+ 0x0A
+ ]);
+ _expectMimeType('file.gif', 'image/jpeg', headerBytes: [
+ 0xFF,
+ 0xD8,
+ 0x46,
+ 0x38,
+ 0x39,
+ 0x61,
+ 0x0D,
+ 0x0A,
+ 0x1A,
+ 0x0A
+ ]);
+ _expectMimeType('file', 'video/3gpp', headerBytes: [
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x04,
+ 0x66,
+ 0x74,
+ 0x79,
+ 0x70,
+ 0x33,
+ 0x67,
+ 0x70,
+ 0x35
+ ]);
+ _expectMimeType('file.mp4', 'video/mp4', headerBytes: [
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x04,
+ 0x66,
+ 0x74,
+ 0x79,
+ 0x70,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF
+ ]);
+ _expectMimeType('file', 'video/mp4', headerBytes: [
+ 0x00,
+ 0xF0,
+ 0xF0,
+ 0xF0,
+ 0x66,
+ 0x74,
+ 0x79,
+ 0x70,
+ 0x61,
+ 0x76,
+ 0x63,
+ 0x31
+ ]);
+ _expectMimeType('file', 'video/mp4', headerBytes: [
+ 0x00,
+ 0xF0,
+ 0xF0,
+ 0xF0,
+ 0x66,
+ 0x74,
+ 0x79,
+ 0x70,
+ 0x69,
+ 0x73,
+ 0x6F,
+ 0x32
+ ]);
+ _expectMimeType('file', 'video/mp4', headerBytes: [
+ 0x00,
+ 0xF0,
+ 0xF0,
+ 0xF0,
+ 0x66,
+ 0x74,
+ 0x79,
+ 0x70,
+ 0x69,
+ 0x73,
+ 0x6F,
+ 0x6D
+ ]);
+ _expectMimeType('file', 'video/mp4', headerBytes: [
+ 0x00,
+ 0xF0,
+ 0xF0,
+ 0xF0,
+ 0x66,
+ 0x74,
+ 0x79,
+ 0x70,
+ 0x6D,
+ 0x70,
+ 0x34,
+ 0x31
+ ]);
+ _expectMimeType('file', 'video/mp4', headerBytes: [
+ 0x00,
+ 0xF0,
+ 0xF0,
+ 0xF0,
+ 0x66,
+ 0x74,
+ 0x79,
+ 0x70,
+ 0x6D,
+ 0x70,
+ 0x34,
+ 0x32
+ ]);
+ _expectMimeType('file', 'image/webp', headerBytes: [
+ 0x52,
+ 0x49,
+ 0x46,
+ 0x46,
+ 0xE2,
+ 0x4A,
+ 0x01,
+ 0x00,
+ 0x57,
+ 0x45,
+ 0x42,
+ 0x50
+ ]);
+ _expectMimeType('file', 'audio/mpeg',
+ headerBytes: [0x49, 0x44, 0x33, 0x0D, 0x0A, 0x1A, 0x0A]);
+ _expectMimeType('file', 'audio/aac',
+ headerBytes: [0xFF, 0xF1, 0x0D, 0x0A, 0x1A, 0x0A]);
+ _expectMimeType('file', 'audio/ogg',
+ headerBytes: [0x4F, 0x70, 0x75, 0x0D, 0x0A, 0x1A, 0x0A]);
+ _expectMimeType('file', 'audio/x-aiff', headerBytes: [
+ 0x46,
+ 0x4F,
+ 0x52,
+ 0x4D,
+ 0x04,
+ 0x0B,
+ 0xEF,
+ 0xF4,
+ 0x41,
+ 0x49,
+ 0x46,
+ 0x46
+ ]);
+ _expectMimeType('file', 'audio/x-flac',
+ headerBytes: [0x66, 0x4C, 0x61, 0x43]);
+ _expectMimeType('file', 'audio/x-wav', headerBytes: [
+ 0x52,
+ 0x49,
+ 0x46,
+ 0x46,
+ 0xA6,
+ 0x4E,
+ 0x70,
+ 0x03,
+ 0x57,
+ 0x41,
+ 0x56,
+ 0x45
+ ]);
+ _expectMimeType('file', 'image/heic', headerBytes: [
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x18,
+ 0x66,
+ 0x74,
+ 0x79,
+ 0x70,
+ 0x68,
+ 0x65,
+ 0x69,
+ 0x63,
+ 0x00
+ ]);
+ _expectMimeType('file', 'image/heic', headerBytes: [
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x18,
+ 0x66,
+ 0x74,
+ 0x79,
+ 0x70,
+ 0x68,
+ 0x65,
+ 0x69,
+ 0x78,
+ 0x00
+ ]);
+ _expectMimeType('file', 'image/heif', headerBytes: [
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x18,
+ 0x66,
+ 0x74,
+ 0x79,
+ 0x70,
+ 0x6D,
+ 0x69,
+ 0x66,
+ 0x31,
+ 0x00
+ ]);
+ });
+ });
+
+ group('custom-resolver', () {
+ test('override-extension', () {
+ final resolver = MimeTypeResolver();
+ resolver.addExtension('jpg', 'my-mime-type');
+ _expectMimeType('file.jpg', 'my-mime-type', resolver: resolver);
+ });
+
+ test('fallthrough-extension', () {
+ final resolver = MimeTypeResolver();
+ resolver.addExtension('jpg2', 'my-mime-type');
+ _expectMimeType('file.jpg', 'image/jpeg', resolver: resolver);
+ });
+
+ test('with-mask', () {
+ final resolver = MimeTypeResolver.empty();
+ resolver.addMagicNumber([0x01, 0x02, 0x03], 'my-mime-type',
+ mask: [0x01, 0xFF, 0xFE]);
+ _expectMimeType('file', 'my-mime-type',
+ headerBytes: [0x01, 0x02, 0x03], resolver: resolver);
+ _expectMimeType('file', null,
+ headerBytes: [0x01, 0x03, 0x03], resolver: resolver);
+ _expectMimeType('file', 'my-mime-type',
+ headerBytes: [0xFF, 0x02, 0x02], resolver: resolver);
+ });
+ });
+
+ test('default magic number', () {
+ final actualMaxBytes = initialMagicNumbers.fold<int>(
+ 0,
+ (previous, magic) => math.max(previous, magic.numbers.length),
+ );
+
+ expect(initialMagicNumbersMaxLength, actualMaxBytes);
+ });
+}
diff --git a/pkgs/oauth2/.gitignore b/pkgs/oauth2/.gitignore
new file mode 100644
index 0000000..bbe1007
--- /dev/null
+++ b/pkgs/oauth2/.gitignore
@@ -0,0 +1,16 @@
+# Don’t commit the following directories created by pub.
+.buildlog
+.pub/
+.dart_tool/
+build/
+packages
+.packages
+
+# Or the files created by dart2js.
+*.dart.js
+*.js_
+*.js.deps
+*.js.map
+
+# Include when developing application packages.
+pubspec.lock
\ No newline at end of file
diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md
new file mode 100644
index 0000000..ff39de3
--- /dev/null
+++ b/pkgs/oauth2/CHANGELOG.md
@@ -0,0 +1,135 @@
+## 2.0.4-wip
+
+* Require Dart 3.4
+
+## 2.0.3
+
+* Require `package:http` v1.0.0
+* Move to `dart-lang/tools`.
+
+## 2.0.2
+
+* Require Dart 3.0.
+* Support `package:http` 1.0.0.
+
+## 2.0.1
+
+* Handle `expires_in` when encoded as string.
+* Populate the pubspec `repository` field.
+* Increase the minimum Dart SDK to `2.17.0`.
+
+## 2.0.0
+
+* Migrate to null safety.
+
+## 1.6.3
+
+* Added optional `codeVerifier` parameter to `AuthorizationCodeGrant` constructor.
+
+## 1.6.1
+
+* Added fix to make sure that credentials are only refreshed once when multiple calls are made.
+
+## 1.6.0
+
+* Added PKCE support to `AuthorizationCodeGrant`.
+
+## 1.5.0
+
+* Added support for `clientCredentialsGrant`.
+
+## 1.4.0
+
+* OpenID's id_token treated.
+
+## 1.3.0
+
+* Added `onCredentialsRefreshed` option when creating `Client` objects.
+
+## 1.2.3
+
+* Support the latest `package:http` release.
+
+## 1.2.2
+
+* Allow the stable 2.0 SDK.
+
+## 1.2.1
+
+* Updated SDK version to 2.0.0-dev.17.0
+
+## 1.2.0
+
+* Add a `getParameter()` parameter to `new AuthorizationCodeGrant()`, `new
+ Credentials()`, and `resourceOwnerPasswordGrant()`. This controls how the
+ authorization server's response is parsed for servers that don't provide the
+ standard JSON response.
+
+## 1.1.1
+
+* `resourceOwnerPasswordGrant()` now properly uses its HTTP client for requests
+ made by the OAuth2 client it returns.
+
+## 1.1.0
+
+* Add a `delimiter` parameter to `new AuthorizationCodeGrant()`, `new
+ Credentials()`, and `resourceOwnerPasswordGrant()`. This controls the
+ delimiter between scopes, which some authorization servers require to be
+ different values than the specified `' '`.
+
+## 1.0.2
+
+* Fix all strong-mode warnings.
+* Support `crypto` 1.0.0.
+* Support `http_parser` 3.0.0.
+
+## 1.0.1
+
+* Support `http_parser` 2.0.0.
+
+## 1.0.0
+
+### Breaking changes
+
+* Requests that use client authentication, such as the
+ `AuthorizationCodeGrant`'s access token request and `Credentials`' refresh
+ request, now use HTTP Basic authentication by default. This form of
+ authentication is strongly recommended by the OAuth 2.0 spec. The new
+ `basicAuth` parameter may be set to `false` to force form-based authentication
+ for servers that require it.
+
+* `new AuthorizationCodeGrant()` now takes `secret` as an optional named
+ argument rather than a required argument. This matches the OAuth 2.0 spec,
+ which says that a client secret is only required for confidential clients.
+
+* `new Client()` and `Credentials.refresh()` now take both `identifier` and
+ `secret` as optional named arguments rather than required arguments. This
+ matches the OAuth 2.0 spec, which says that the server may choose not to
+ require client authentication for some flows.
+
+* `new Credentials()` now takes named arguments rather than optional positional
+ arguments.
+
+### Non-breaking changes
+
+* Added a `resourceOwnerPasswordGrant` method.
+
+* The `scopes` argument to `AuthorizationCodeGrant.getAuthorizationUrl()` and
+ `new Credentials()` and the `newScopes` argument to `Credentials.refresh` now
+ take an `Iterable` rather than just a `List`.
+
+* The `scopes` argument to `AuthorizationCodeGrant.getAuthorizationUrl()` now
+ defaults to `null` rather than `const []`.
+
+# 0.9.3
+
+* Update the `http` dependency.
+
+* Since `http` 0.11.0 now works in non-`dart:io` contexts, `oauth2` does as
+ well.
+
+## 0.9.2
+
+* Expand the dependency on the HTTP package to include 0.10.x.
+
+* Add a README file.
diff --git a/pkgs/oauth2/LICENSE b/pkgs/oauth2/LICENSE
new file mode 100644
index 0000000..162572a
--- /dev/null
+++ b/pkgs/oauth2/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2014, 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.
diff --git a/pkgs/oauth2/README.md b/pkgs/oauth2/README.md
new file mode 100644
index 0000000..07a5976
--- /dev/null
+++ b/pkgs/oauth2/README.md
@@ -0,0 +1,260 @@
+[](https://github.com/dart-lang/tools/actions/workflows/oauth2.yml)
+[](https://pub.dev/packages/oauth2)
+[](https://pub.dev/packages/oauth2/publisher)
+
+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.
+
+## About OAuth2
+
+OAuth2 allows a client (the program using this library) to access and manipulate
+a resource that's owned by a resource owner (the end user) and lives on a remote
+server. The client directs the resource owner to an authorization server
+(usually but not always the same as the server that hosts the resource), where
+the resource owner tells the authorization server to give the client an access
+token. This token serves as proof that the client has permission to access
+resources on behalf of the resource owner.
+
+OAuth2 provides several different methods for the client to obtain
+authorization. At the time of writing, this library only supports the
+[Authorization Code Grant][authorizationCodeGrantSection],
+[Client Credentials Grant][clientCredentialsGrantSection] and
+[Resource Owner Password Grant][resourceOwnerPasswordGrantSection] flows, but
+more may be added in the future.
+
+## Authorization Code Grant
+
+**Resources:** [Class summary][authorizationCodeGrantMethod],
+[OAuth documentation][authorizationCodeGrantDocs]
+
+```dart
+import 'dart:io';
+
+import 'package:oauth2/oauth2.dart' as oauth2;
+
+// These URLs are endpoints that are provided by the authorization
+// server. They're usually included in the server's documentation of its
+// OAuth2 API.
+final authorizationEndpoint =
+ Uri.parse('http://example.com/oauth2/authorization');
+final tokenEndpoint = Uri.parse('http://example.com/oauth2/token');
+
+// The authorization server will issue each client a separate client
+// identifier and secret, which allows the server to tell which client
+// is accessing it. Some servers may also have an anonymous
+// identifier/secret pair that any client may use.
+//
+// Note that clients whose source code or binary executable is readily
+// available may not be able to make sure the client secret is kept a
+// secret. This is fine; OAuth2 servers generally won't rely on knowing
+// with certainty that a client is who it claims to be.
+final identifier = 'my client identifier';
+final secret = 'my client secret';
+
+// This is a URL on your application's server. The authorization server
+// will redirect the resource owner here once they've authorized the
+// client. The redirection will include the authorization code in the
+// query parameters.
+final redirectUrl = Uri.parse('http://my-site.com/oauth2-redirect');
+
+/// A file in which the users credentials are stored persistently. If the server
+/// issues a refresh token allowing the client to refresh outdated credentials,
+/// these may be valid indefinitely, meaning the user never has to
+/// re-authenticate.
+final credentialsFile = File('~/.myapp/credentials.json');
+
+/// Either load an OAuth2 client from saved credentials or authenticate a new
+/// one.
+Future<oauth2.Client> createClient() async {
+ var exists = await credentialsFile.exists();
+
+ // If the OAuth2 credentials have already been saved from a previous run, we
+ // just want to reload them.
+ if (exists) {
+ var credentials =
+ oauth2.Credentials.fromJson(await credentialsFile.readAsString());
+ return oauth2.Client(credentials, identifier: identifier, secret: secret);
+ }
+
+ // If we don't have OAuth2 credentials yet, we need to get the resource owner
+ // to authorize us. We're assuming here that we're a command-line application.
+ var grant = oauth2.AuthorizationCodeGrant(
+ identifier, authorizationEndpoint, tokenEndpoint,
+ secret: secret);
+
+ // A URL on the authorization server (authorizationEndpoint with some additional
+ // query parameters). Scopes and state can optionally be passed into this method.
+ var authorizationUrl = grant.getAuthorizationUrl(redirectUrl);
+
+ // Redirect the resource owner to the authorization URL. Once the resource
+ // owner has authorized, they'll be redirected to `redirectUrl` with an
+ // authorization code. The `redirect` should cause the browser to redirect to
+ // another URL which should also have a listener.
+ //
+ // `redirect` and `listen` are not shown implemented here. See below for the
+ // details.
+ await redirect(authorizationUrl);
+ var responseUrl = await listen(redirectUrl);
+
+ // Once the user is redirected to `redirectUrl`, pass the query parameters to
+ // the AuthorizationCodeGrant. It will validate them and extract the
+ // authorization code to create a new Client.
+ return await grant.handleAuthorizationResponse(responseUrl.queryParameters);
+}
+
+void main() async {
+ var client = await createClient();
+
+ // Once you have a Client, you can use it just like any other HTTP client.
+ print(await client.read('http://example.com/protected-resources.txt'));
+
+ // Once we're done with the client, save the credentials file. This ensures
+ // that if the credentials were automatically refreshed while using the
+ // client, the new credentials are available for the next run of the
+ // program.
+ await credentialsFile.writeAsString(client.credentials.toJson());
+}
+```
+
+<details>
+ <summary>Click here to learn how to implement `redirect` and `listen`.</summary>
+
+--------------------------------------------------------------------------------
+
+There is not a universal example for implementing `redirect` and `listen`,
+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][].
+
+ ```dart
+ if (await canLaunch(authorizationUrl.toString())) {
+ await launch(authorizationUrl.toString()); }
+
+ // ------- 8< -------
+
+ final linksStream = getLinksStream().listen((Uri uri) async {
+ if (uri.toString().startsWith(redirectUrl)) {
+ responseUrl = uri;
+ }
+ });
+ ```
+
+1. Launch a WebView inside the app and listen for a redirect using
+ [webview_flutter][].
+
+ ```dart
+ WebView(
+ javascriptMode: JavascriptMode.unrestricted,
+ initialUrl: authorizationUrl.toString(),
+ navigationDelegate: (navReq) {
+ if (navReq.url.startsWith(redirectUrl)) {
+ responseUrl = Uri.parse(navReq.url);
+ return NavigationDecision.prevent;
+ }
+ return NavigationDecision.navigate;
+ },
+ // ------- 8< -------
+ );
+ ```
+
+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.
+</details>
+
+## Client Credentials Grant
+
+**Resources:** [Method summary][clientCredentialsGrantMethod],
+[OAuth documentation][clientCredentialsGrantDocs]
+
+```dart
+// This URL is an endpoint that's provided by the authorization server. It's
+// usually included in the server's documentation of its OAuth2 API.
+final authorizationEndpoint =
+ Uri.parse('http://example.com/oauth2/authorization');
+
+// The OAuth2 specification expects a client's identifier and secret
+// to be sent when using the client credentials grant.
+//
+// Because the client credentials grant is not inherently associated with a user,
+// it is up to the server in question whether the returned token allows limited
+// API access.
+//
+// Either way, you must provide both a client identifier and a client secret:
+final identifier = 'my client identifier';
+final secret = 'my client secret';
+
+// Calling the top-level `clientCredentialsGrant` function will return a
+// [Client] instead.
+var client = await oauth2.clientCredentialsGrant(
+ authorizationEndpoint, identifier, secret);
+
+// With an authenticated client, you can make requests, and the `Bearer` token
+// returned by the server during the client credentials grant will be attached
+// to any request you make.
+var response =
+ await client.read('https://example.com/api/some_resource.json');
+
+// You can save the client's credentials, which consists of an access token, and
+// potentially a refresh token and expiry date, to a file. This way, subsequent runs
+// do not need to reauthenticate, and you can avoid saving the client identifier and
+// secret.
+await credentialsFile.writeAsString(client.credentials.toJson());
+```
+
+## Resource Owner Password Grant
+
+**Resources:** [Method summary][resourceOwnerPasswordGrantMethod],
+[OAuth documentation][resourceOwnerPasswordGrantDocs]
+
+```dart
+// This URL is an endpoint that's provided by the authorization server. It's
+// usually included in the server's documentation of its OAuth2 API.
+final authorizationEndpoint =
+ Uri.parse('http://example.com/oauth2/authorization');
+
+// The user should supply their own username and password.
+final username = 'example user';
+final password = 'example password';
+
+// The authorization server may issue each client a separate client
+// identifier and secret, which allows the server to tell which client
+// is accessing it. Some servers may also have an anonymous
+// identifier/secret pair that any client may use.
+//
+// Some servers don't require the client to authenticate itself, in which case
+// these should be omitted.
+final identifier = 'my client identifier';
+final secret = 'my client secret';
+
+// Make a request to the authorization endpoint that will produce the fully
+// authenticated Client.
+var client = await oauth2.resourceOwnerPasswordGrant(
+ authorizationEndpoint, username, password,
+ identifier: identifier, secret: secret);
+
+// Once you have the client, you can use it just like any other HTTP client.
+var result = await client.read('http://example.com/protected-resources.txt');
+
+// Once we're done with the client, save the credentials file. This will allow
+// us to re-use the credentials and avoid storing the username and password
+// directly.
+File('~/.myapp/credentials.json').writeAsString(client.credentials.toJson());
+```
+
+[authorizationCodeGrantDocs]: https://oauth.net/2/grant-types/authorization-code/
+[authorizationCodeGrantMethod]: https://pub.dev/documentation/oauth2/latest/oauth2/AuthorizationCodeGrant-class.html
+[authorizationCodeGrantSection]: #authorization-code-grant
+[clientCredentialsGrantDocs]: https://oauth.net/2/grant-types/client-credentials/
+[clientCredentialsGrantMethod]: https://pub.dev/documentation/oauth2/latest/oauth2/clientCredentialsGrant.html
+[clientCredentialsGrantSection]: #client-credentials-grant
+[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
+[url_launcher]: https://pub.dev/packages/url_launcher
+[webview_flutter]: https://pub.dev/packages/webview_flutter
diff --git a/pkgs/oauth2/analysis_options.yaml b/pkgs/oauth2/analysis_options.yaml
new file mode 100644
index 0000000..a5f5ea4
--- /dev/null
+++ b/pkgs/oauth2/analysis_options.yaml
@@ -0,0 +1,22 @@
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ errors:
+ # Too many exceptions
+ comment_references: ignore
+ language:
+ strict-casts: true
+ strict-raw-types: true
+
+linter:
+ rules:
+ - avoid_private_typedef_functions
+ - avoid_unused_constructor_parameters
+ - avoid_void_async
+ - cancel_subscriptions
+ - literal_only_boolean_expressions
+ - no_adjacent_strings_in_list
+ - no_runtimeType_toString
+ - prefer_const_declarations
+ - unnecessary_await_in_return
+ - use_string_buffers
diff --git a/pkgs/oauth2/example/main.dart b/pkgs/oauth2/example/main.dart
new file mode 100644
index 0000000..68e5aa0
--- /dev/null
+++ b/pkgs/oauth2/example/main.dart
@@ -0,0 +1,100 @@
+// 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 'dart:async';
+import 'dart:io';
+
+import 'package:oauth2/oauth2.dart' as oauth2;
+
+// These URLs are endpoints that are provided by the authorization
+// server. They're usually included in the server's documentation of its
+// OAuth2 API.
+final authorizationEndpoint =
+ Uri.parse('http://example.com/oauth2/authorization');
+final tokenEndpoint = Uri.parse('http://example.com/oauth2/token');
+
+// The authorization server will issue each client a separate client
+// identifier and secret, which allows the server to tell which client
+// is accessing it. Some servers may also have an anonymous
+// identifier/secret pair that any client may use.
+//
+// Note that clients whose source code or binary executable is readily
+// available may not be able to make sure the client secret is kept a
+// secret. This is fine; OAuth2 servers generally won't rely on knowing
+// with certainty that a client is who it claims to be.
+const identifier = 'my client identifier';
+const secret = 'my client secret';
+
+// This is a URL on your application's server. The authorization server
+// will redirect the resource owner here once they've authorized the
+// client. The redirection will include the authorization code in the
+// query parameters.
+final redirectUrl = Uri.parse('http://my-site.com/oauth2-redirect');
+
+/// A file in which the users credentials are stored persistently. If the server
+/// issues a refresh token allowing the client to refresh outdated credentials,
+/// these may be valid indefinitely, meaning the user never has to
+/// re-authenticate.
+final credentialsFile = File('~/.myapp/credentials.json');
+
+/// Either load an OAuth2 client from saved credentials or authenticate a new
+/// one.
+Future<oauth2.Client> createClient() async {
+ var exists = await credentialsFile.exists();
+
+ // If the OAuth2 credentials have already been saved from a previous run, we
+ // just want to reload them.
+ if (exists) {
+ var credentials =
+ oauth2.Credentials.fromJson(await credentialsFile.readAsString());
+ return oauth2.Client(credentials, identifier: identifier, secret: secret);
+ }
+
+ // If we don't have OAuth2 credentials yet, we need to get the resource owner
+ // to authorize us. We're assuming here that we're a command-line application.
+ var grant = oauth2.AuthorizationCodeGrant(
+ identifier, authorizationEndpoint, tokenEndpoint,
+ secret: secret);
+
+ // A URL on the authorization server (authorizationEndpoint with some
+ // additional query parameters). Scopes and state can optionally be passed
+ // into this method.
+ var authorizationUrl = grant.getAuthorizationUrl(redirectUrl);
+
+ // Redirect the resource owner to the authorization URL. Once the resource
+ // owner has authorized, they'll be redirected to `redirectUrl` with an
+ // authorization code. The `redirect` should cause the browser to redirect to
+ // another URL which should also have a listener.
+ //
+ // `redirect` and `listen` are not shown implemented here.
+ await redirect(authorizationUrl);
+ var responseUrl = await listen(redirectUrl);
+
+ // Once the user is redirected to `redirectUrl`, pass the query parameters to
+ // the AuthorizationCodeGrant. It will validate them and extract the
+ // authorization code to create a new Client.
+ return grant.handleAuthorizationResponse(responseUrl.queryParameters);
+}
+
+void main() async {
+ var client = await createClient();
+
+ // Once you have a Client, you can use it just like any other HTTP client.
+ print(await client.read(Uri.http('example.com', 'protected-resources.txt')));
+
+ // Once we're done with the client, save the credentials file. This ensures
+ // that if the credentials were automatically refreshed while using the
+ // client, the new credentials are available for the next run of the
+ // program.
+ await credentialsFile.writeAsString(client.credentials.toJson());
+}
+
+Future<void> redirect(Uri url) async {
+ // Client implementation detail
+}
+
+Future<Uri> listen(Uri url) async {
+ // Client implementation detail
+ return Uri();
+}
diff --git a/pkgs/oauth2/lib/oauth2.dart b/pkgs/oauth2/lib/oauth2.dart
new file mode 100644
index 0000000..45efc5c
--- /dev/null
+++ b/pkgs/oauth2/lib/oauth2.dart
@@ -0,0 +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.
+
+export 'src/authorization_code_grant.dart';
+export 'src/authorization_exception.dart';
+export 'src/client.dart';
+export 'src/client_credentials_grant.dart';
+export 'src/credentials.dart';
+export 'src/expiration_exception.dart';
+export 'src/resource_owner_password_grant.dart';
diff --git a/pkgs/oauth2/lib/src/authorization_code_grant.dart b/pkgs/oauth2/lib/src/authorization_code_grant.dart
new file mode 100644
index 0000000..fac56ba
--- /dev/null
+++ b/pkgs/oauth2/lib/src/authorization_code_grant.dart
@@ -0,0 +1,371 @@
+// 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:async';
+import 'dart:convert';
+import 'dart:math';
+
+import 'package:crypto/crypto.dart';
+import 'package:http/http.dart' as http;
+import 'package:http_parser/http_parser.dart';
+
+import 'authorization_exception.dart';
+import 'client.dart';
+import 'credentials.dart';
+import 'handle_access_token_response.dart';
+import 'parameters.dart';
+import 'utils.dart';
+
+/// A class for obtaining credentials via an [authorization code grant][].
+///
+/// This method of authorization involves sending the resource owner to the
+/// authorization server where they will authorize the client. They're then
+/// redirected back to your server, along with an authorization code. This is
+/// used to obtain [Credentials] and create a fully-authorized [Client].
+///
+/// To use this class, you must first call [getAuthorizationUrl] to get the URL
+/// to which to redirect the resource owner. Then once they've been redirected
+/// back to your application, call [handleAuthorizationResponse] or
+/// [handleAuthorizationCode] to process the authorization server's response and
+/// construct a [Client].
+///
+/// [authorization code grant]: http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-4.1
+class AuthorizationCodeGrant {
+ /// The function used to parse parameters from a host's response.
+ final GetParameters _getParameters;
+
+ /// The client identifier for this client.
+ ///
+ /// The authorization server will issue each client a separate client
+ /// identifier and secret, which allows the server to tell which client is
+ /// accessing it. Some servers may also have an anonymous identifier/secret
+ /// pair that any client may use.
+ ///
+ /// This is usually global to the program using this library.
+ final String identifier;
+
+ /// The client secret for this client.
+ ///
+ /// The authorization server will issue each client a separate client
+ /// identifier and secret, which allows the server to tell which client is
+ /// accessing it. Some servers may also have an anonymous identifier/secret
+ /// pair that any client may use.
+ ///
+ /// This is usually global to the program using this library.
+ ///
+ /// Note that clients whose source code or binary executable is readily
+ /// available may not be able to make sure the client secret is kept a secret.
+ /// This is fine; OAuth2 servers generally won't rely on knowing with
+ /// certainty that a client is who it claims to be.
+ final String? secret;
+
+ /// A URL provided by the authorization server that serves as the base for the
+ /// URL that the resource owner will be redirected to to authorize this
+ /// client.
+ ///
+ /// This will usually be listed in the authorization server's OAuth2 API
+ /// documentation.
+ final Uri authorizationEndpoint;
+
+ /// A URL provided by the authorization server that this library uses to
+ /// obtain long-lasting credentials.
+ ///
+ /// This will usually be listed in the authorization server's OAuth2 API
+ /// documentation.
+ final Uri tokenEndpoint;
+
+ /// Callback to be invoked whenever the credentials are refreshed.
+ ///
+ /// This will be passed as-is to the constructed [Client].
+ final CredentialsRefreshedCallback? _onCredentialsRefreshed;
+
+ /// Whether to use HTTP Basic authentication for authorizing the client.
+ final bool _basicAuth;
+
+ /// A [String] used to separate scopes; defaults to `" "`.
+ final String _delimiter;
+
+ /// The HTTP client used to make HTTP requests.
+ http.Client? _httpClient;
+
+ /// The URL to which the resource owner will be redirected after they
+ /// authorize this client with the authorization server.
+ Uri? _redirectEndpoint;
+
+ /// The scopes that the client is requesting access to.
+ List<String>? _scopes;
+
+ /// An opaque string that users of this library may specify that will be
+ /// included in the response query parameters.
+ String? _stateString;
+
+ /// The current state of the grant object.
+ _State _state = _State.initial;
+
+ /// Allowed characters for generating the _codeVerifier
+ static const String _charset =
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
+
+ /// The PKCE code verifier. Will be generated if one is not provided in the
+ /// constructor.
+ final String _codeVerifier;
+
+ /// Creates a new grant.
+ ///
+ /// If [basicAuth] is `true` (the default), the client credentials are sent to
+ /// the server using using HTTP Basic authentication as defined in [RFC 2617].
+ /// Otherwise, they're included in the request body. Note that the latter form
+ /// is not recommended by the OAuth 2.0 spec, and should only be used if the
+ /// server doesn't support Basic authentication.
+ ///
+ /// [RFC 2617]: https://tools.ietf.org/html/rfc2617
+ ///
+ /// [httpClient] is used for all HTTP requests made by this grant, as well as
+ /// those of the [Client] is constructs.
+ ///
+ /// [onCredentialsRefreshed] will be called by the constructed [Client]
+ /// whenever the credentials are refreshed.
+ ///
+ /// [codeVerifier] String to be used as PKCE code verifier. If none is
+ /// provided a random codeVerifier will be generated.
+ /// The codeVerifier must meet requirements specified in [RFC 7636].
+ ///
+ /// [RFC 7636]: https://tools.ietf.org/html/rfc7636#section-4.1
+ ///
+ /// The scope strings will be separated by the provided [delimiter]. This
+ /// defaults to `" "`, the OAuth2 standard, but some APIs (such as Facebook's)
+ /// use non-standard delimiters.
+ ///
+ /// By default, this follows the OAuth2 spec and requires the server's
+ /// responses to be in JSON format. However, some servers return non-standard
+ /// response formats, which can be parsed using the [getParameters] function.
+ ///
+ /// This function is passed the `Content-Type` header of the response as well
+ /// as its body as a UTF-8-decoded string. It should return a map in the same
+ /// format as the [standard JSON response][].
+ ///
+ /// [standard JSON response]: https://tools.ietf.org/html/rfc6749#section-5.1
+ AuthorizationCodeGrant(
+ this.identifier, this.authorizationEndpoint, this.tokenEndpoint,
+ {this.secret,
+ String? delimiter,
+ bool basicAuth = true,
+ http.Client? httpClient,
+ CredentialsRefreshedCallback? onCredentialsRefreshed,
+ Map<String, dynamic> Function(MediaType? contentType, String body)?
+ getParameters,
+ String? codeVerifier})
+ : _basicAuth = basicAuth,
+ _httpClient = httpClient ?? http.Client(),
+ _delimiter = delimiter ?? ' ',
+ _getParameters = getParameters ?? parseJsonParameters,
+ _onCredentialsRefreshed = onCredentialsRefreshed,
+ _codeVerifier = codeVerifier ?? _createCodeVerifier();
+
+ /// Returns the URL to which the resource owner should be redirected to
+ /// authorize this client.
+ ///
+ /// The resource owner will then be redirected to [redirect], which should
+ /// point to a server controlled by the client. This redirect will have
+ /// additional query parameters that should be passed to
+ /// [handleAuthorizationResponse].
+ ///
+ /// The specific permissions being requested from the authorization server may
+ /// be specified via [scopes]. The scope strings are specific to the
+ /// authorization server and may be found in its documentation. Note that you
+ /// may not be granted access to every scope you request; you may check the
+ /// [Credentials.scopes] field of [Client.credentials] to see which scopes you
+ /// were granted.
+ ///
+ /// An opaque [state] string may also be passed that will be present in the
+ /// query parameters provided to the redirect URL.
+ ///
+ /// It is a [StateError] to call this more than once.
+ Uri getAuthorizationUrl(Uri redirect,
+ {Iterable<String>? scopes, String? state}) {
+ if (_state != _State.initial) {
+ throw StateError('The authorization URL has already been generated.');
+ }
+ _state = _State.awaitingResponse;
+
+ var scopeList = scopes?.toList() ?? <String>[];
+ var codeChallenge = base64Url
+ .encode(sha256.convert(ascii.encode(_codeVerifier)).bytes)
+ .replaceAll('=', '');
+
+ _redirectEndpoint = redirect;
+ _scopes = scopeList;
+ _stateString = state;
+ var parameters = {
+ 'response_type': 'code',
+ 'client_id': identifier,
+ 'redirect_uri': redirect.toString(),
+ 'code_challenge': codeChallenge,
+ 'code_challenge_method': 'S256'
+ };
+
+ if (state != null) parameters['state'] = state;
+ if (scopeList.isNotEmpty) parameters['scope'] = scopeList.join(_delimiter);
+
+ return addQueryParameters(authorizationEndpoint, parameters);
+ }
+
+ /// Processes the query parameters added to a redirect from the authorization
+ /// server.
+ ///
+ /// Note that this "response" is not an HTTP response, but rather the data
+ /// passed to a server controlled by the client as query parameters on the
+ /// redirect URL.
+ ///
+ /// It is a [StateError] to call this more than once, to call it before
+ /// [getAuthorizationUrl] is called, or to call it after
+ /// [handleAuthorizationCode] is called.
+ ///
+ /// Throws [FormatException] if [parameters] is invalid according to the
+ /// OAuth2 spec or if the authorization server otherwise provides invalid
+ /// responses. If `state` was passed to [getAuthorizationUrl], this will throw
+ /// a [FormatException] if the `state` parameter doesn't match the original
+ /// value.
+ ///
+ /// Throws [AuthorizationException] if the authorization fails.
+ Future<Client> handleAuthorizationResponse(
+ Map<String, String> parameters) async {
+ if (_state == _State.initial) {
+ throw StateError('The authorization URL has not yet been generated.');
+ } else if (_state == _State.finished) {
+ throw StateError('The authorization code has already been received.');
+ }
+ _state = _State.finished;
+
+ if (_stateString != null) {
+ if (!parameters.containsKey('state')) {
+ throw FormatException('Invalid OAuth response for '
+ '"$authorizationEndpoint": parameter "state" expected to be '
+ '"$_stateString", was missing.');
+ } else if (parameters['state'] != _stateString) {
+ throw FormatException('Invalid OAuth response for '
+ '"$authorizationEndpoint": parameter "state" expected to be '
+ '"$_stateString", was "${parameters['state']}".');
+ }
+ }
+
+ if (parameters.containsKey('error')) {
+ var description = parameters['error_description'];
+ var uriString = parameters['error_uri'];
+ var uri = uriString == null ? null : Uri.parse(uriString);
+ throw AuthorizationException(parameters['error']!, description, uri);
+ } else if (!parameters.containsKey('code')) {
+ throw FormatException('Invalid OAuth response for '
+ '"$authorizationEndpoint": did not contain required parameter '
+ '"code".');
+ }
+
+ return _handleAuthorizationCode(parameters['code']);
+ }
+
+ /// Processes an authorization code directly.
+ ///
+ /// Usually [handleAuthorizationResponse] is preferable to this method, since
+ /// it validates all of the query parameters. However, some authorization
+ /// servers allow the user to copy and paste an authorization code into a
+ /// command-line application, in which case this method must be used.
+ ///
+ /// It is a [StateError] to call this more than once, to call it before
+ /// [getAuthorizationUrl] is called, or to call it after
+ /// [handleAuthorizationCode] is called.
+ ///
+ /// Throws [FormatException] if the authorization server provides invalid
+ /// responses while retrieving credentials.
+ ///
+ /// Throws [AuthorizationException] if the authorization fails.
+ Future<Client> handleAuthorizationCode(String authorizationCode) async {
+ if (_state == _State.initial) {
+ throw StateError('The authorization URL has not yet been generated.');
+ } else if (_state == _State.finished) {
+ throw StateError('The authorization code has already been received.');
+ }
+ _state = _State.finished;
+
+ return _handleAuthorizationCode(authorizationCode);
+ }
+
+ /// This works just like [handleAuthorizationCode], except it doesn't validate
+ /// the state beforehand.
+ Future<Client> _handleAuthorizationCode(String? authorizationCode) async {
+ var startTime = DateTime.now();
+
+ var headers = <String, String>{};
+
+ var body = {
+ 'grant_type': 'authorization_code',
+ 'code': authorizationCode,
+ 'redirect_uri': _redirectEndpoint.toString(),
+ 'code_verifier': _codeVerifier
+ };
+
+ var secret = this.secret;
+ if (_basicAuth && secret != null) {
+ headers['Authorization'] = basicAuthHeader(identifier, secret);
+ } else {
+ // The ID is required for this request any time basic auth isn't being
+ // used, even if there's no actual client authentication to be done.
+ body['client_id'] = identifier;
+ if (secret != null) body['client_secret'] = secret;
+ }
+
+ var response =
+ await _httpClient!.post(tokenEndpoint, headers: headers, body: body);
+
+ var credentials = handleAccessTokenResponse(
+ response, tokenEndpoint, startTime, _scopes, _delimiter,
+ getParameters: _getParameters);
+ return Client(credentials,
+ identifier: identifier,
+ secret: secret,
+ basicAuth: _basicAuth,
+ httpClient: _httpClient,
+ onCredentialsRefreshed: _onCredentialsRefreshed);
+ }
+
+ // Randomly generate a 128 character string to be used as the PKCE code
+ // verifier.
+ static String _createCodeVerifier() => List.generate(
+ 128,
+ (i) => _charset[Random.secure().nextInt(_charset.length)],
+ ).join();
+
+ /// Closes the grant and frees its resources.
+ ///
+ /// This will close the underlying HTTP client, which is shared by the
+ /// [Client] created by this grant, so it's not safe to close the grant and
+ /// continue using the client.
+ void close() {
+ _httpClient?.close();
+ _httpClient = null;
+ }
+}
+
+/// States that [AuthorizationCodeGrant] can be in.
+class _State {
+ /// [AuthorizationCodeGrant.getAuthorizationUrl] has not yet been called for
+ /// this grant.
+ static const initial = _State('initial');
+
+ // [AuthorizationCodeGrant.getAuthorizationUrl] has been called but neither
+ // [AuthorizationCodeGrant.handleAuthorizationResponse] nor
+ // [AuthorizationCodeGrant.handleAuthorizationCode] has been called.
+ static const awaitingResponse = _State('awaiting response');
+
+ // [AuthorizationCodeGrant.getAuthorizationUrl] and either
+ // [AuthorizationCodeGrant.handleAuthorizationResponse] or
+ // [AuthorizationCodeGrant.handleAuthorizationCode] have been called.
+ static const finished = _State('finished');
+
+ final String _name;
+
+ const _State(this._name);
+
+ @override
+ String toString() => _name;
+}
diff --git a/pkgs/oauth2/lib/src/authorization_exception.dart b/pkgs/oauth2/lib/src/authorization_exception.dart
new file mode 100644
index 0000000..14a5a3c
--- /dev/null
+++ b/pkgs/oauth2/lib/src/authorization_exception.dart
@@ -0,0 +1,39 @@
+// 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.
+
+/// An exception raised when OAuth2 authorization fails.
+class AuthorizationException implements Exception {
+ /// The name of the error.
+ ///
+ /// Possible names are enumerated in [the spec][].
+ ///
+ /// [the spec]: http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-5.2
+ final String error;
+
+ /// The description of the error, provided by the server.
+ ///
+ /// May be `null` if the server provided no description.
+ final String? description;
+
+ /// A URL for a page that describes the error in more detail, provided by the
+ /// server.
+ ///
+ /// May be `null` if the server provided no URL.
+ final Uri? uri;
+
+ /// Creates an AuthorizationException.
+ AuthorizationException(this.error, this.description, this.uri);
+
+ /// Provides a string description of the AuthorizationException.
+ @override
+ String toString() {
+ var header = 'OAuth authorization error ($error)';
+ if (description != null) {
+ header = '$header: $description';
+ } else if (uri != null) {
+ header = '$header: $uri';
+ }
+ return '$header.';
+ }
+}
diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart
new file mode 100644
index 0000000..1dd2282
--- /dev/null
+++ b/pkgs/oauth2/lib/src/client.dart
@@ -0,0 +1,187 @@
+// 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:async';
+
+import 'package:collection/collection.dart';
+import 'package:http/http.dart' as http;
+import 'package:http_parser/http_parser.dart';
+
+import 'authorization_exception.dart';
+import 'credentials.dart';
+import 'expiration_exception.dart';
+
+/// An OAuth2 client.
+///
+/// This acts as a drop-in replacement for an [http.Client], while sending
+/// OAuth2 authorization credentials along with each request.
+///
+/// The client also automatically refreshes its credentials if possible. When it
+/// makes a request, if its credentials are expired, it will first refresh them.
+/// This means that any request may throw an [AuthorizationException] if the
+/// refresh is not authorized for some reason, a [FormatException] if the
+/// authorization server provides ill-formatted responses, or an
+/// [ExpirationException] if the credentials are expired and can't be refreshed.
+///
+/// The client will also throw an [AuthorizationException] if the resource
+/// server returns a 401 response with a WWW-Authenticate header indicating that
+/// the current credentials are invalid.
+///
+/// If you already have a set of [Credentials], you can construct a [Client]
+/// directly. However, in order to first obtain the credentials, you must
+/// authorize. At the time of writing, the only authorization method this
+/// library supports is [AuthorizationCodeGrant].
+class Client extends http.BaseClient {
+ /// The client identifier for this client.
+ ///
+ /// The authorization server will issue each client a separate client
+ /// identifier and secret, which allows the server to tell which client is
+ /// accessing it. Some servers may also have an anonymous identifier/secret
+ /// pair that any client may use.
+ ///
+ /// This is usually global to the program using this library.
+ final String? identifier;
+
+ /// The client secret for this client.
+ ///
+ /// The authorization server will issue each client a separate client
+ /// identifier and secret, which allows the server to tell which client is
+ /// accessing it. Some servers may also have an anonymous identifier/secret
+ /// pair that any client may use.
+ ///
+ /// This is usually global to the program using this library.
+ ///
+ /// Note that clients whose source code or binary executable is readily
+ /// available may not be able to make sure the client secret is kept a secret.
+ /// This is fine; OAuth2 servers generally won't rely on knowing with
+ /// certainty that a client is who it claims to be.
+ final String? secret;
+
+ /// The credentials this client uses to prove to the resource server that it's
+ /// authorized.
+ ///
+ /// This may change from request to request as the credentials expire and the
+ /// client refreshes them automatically.
+ Credentials get credentials => _credentials;
+ Credentials _credentials;
+
+ /// Callback to be invoked whenever the credentials refreshed.
+ final CredentialsRefreshedCallback? _onCredentialsRefreshed;
+
+ /// Whether to use HTTP Basic authentication for authorizing the client.
+ final bool _basicAuth;
+
+ /// The underlying HTTP client.
+ http.Client? _httpClient;
+
+ /// Creates a new client from a pre-existing set of credentials.
+ ///
+ /// When authorizing a client for the first time, you should use
+ /// [AuthorizationCodeGrant] or [resourceOwnerPasswordGrant] instead of
+ /// constructing a [Client] directly.
+ ///
+ /// [httpClient] is the underlying client that this forwards requests to after
+ /// adding authorization credentials to them.
+ ///
+ /// Throws an [ArgumentError] if [secret] is passed without [identifier].
+ Client(this._credentials,
+ {this.identifier,
+ this.secret,
+ CredentialsRefreshedCallback? onCredentialsRefreshed,
+ bool basicAuth = true,
+ http.Client? httpClient})
+ : _basicAuth = basicAuth,
+ _onCredentialsRefreshed = onCredentialsRefreshed,
+ _httpClient = httpClient ?? http.Client() {
+ if (identifier == null && secret != null) {
+ throw ArgumentError('secret may not be passed without identifier.');
+ }
+ }
+
+ /// Sends an HTTP request with OAuth2 authorization credentials attached.
+ ///
+ /// This will also automatically refresh this client's [Credentials] before
+ /// sending the request if necessary.
+ @override
+ Future<http.StreamedResponse> send(http.BaseRequest request) async {
+ if (credentials.isExpired) {
+ if (!credentials.canRefresh) throw ExpirationException(credentials);
+ await refreshCredentials();
+ }
+
+ request.headers['authorization'] = 'Bearer ${credentials.accessToken}';
+ var response = await _httpClient!.send(request);
+
+ if (response.statusCode != 401) return response;
+ if (!response.headers.containsKey('www-authenticate')) return response;
+
+ List<AuthenticationChallenge> challenges;
+ try {
+ challenges = AuthenticationChallenge.parseHeader(
+ response.headers['www-authenticate']!);
+ } on FormatException {
+ return response;
+ }
+
+ var challenge = challenges
+ .firstWhereOrNull((challenge) => challenge.scheme == 'bearer');
+ if (challenge == null) return response;
+
+ var params = challenge.parameters;
+ if (!params.containsKey('error')) return response;
+
+ throw AuthorizationException(params['error']!, params['error_description'],
+ params['error_uri'] == null ? null : Uri.parse(params['error_uri']!));
+ }
+
+ /// A [Future] used to track whether [refreshCredentials] is running.
+ Future<Credentials>? _refreshingFuture;
+
+ /// Explicitly refreshes this client's credentials. Returns this client.
+ ///
+ /// This will throw a [StateError] if the [Credentials] can't be refreshed, an
+ /// [AuthorizationException] if refreshing the credentials fails, or a
+ /// [FormatException] if the authorization server returns invalid responses.
+ ///
+ /// You may request different scopes than the default by passing in
+ /// [newScopes]. These must be a subset of the scopes in the
+ /// [Credentials.scopes] field of [Client.credentials].
+ Future<Client> refreshCredentials([List<String>? newScopes]) async {
+ if (!credentials.canRefresh) {
+ var prefix = 'OAuth credentials';
+ if (credentials.isExpired) prefix = '$prefix have expired and';
+ throw StateError("$prefix can't be refreshed.");
+ }
+
+ // To make sure that only one refresh happens when credentials are expired
+ // we track it using the [_refreshingFuture]. And also make sure that the
+ // _onCredentialsRefreshed callback is only called once.
+ if (_refreshingFuture == null) {
+ try {
+ _refreshingFuture = credentials.refresh(
+ identifier: identifier,
+ secret: secret,
+ newScopes: newScopes,
+ basicAuth: _basicAuth,
+ httpClient: _httpClient,
+ );
+ _credentials = await _refreshingFuture!;
+ _onCredentialsRefreshed?.call(_credentials);
+ } finally {
+ _refreshingFuture = null;
+ }
+ } else {
+ await _refreshingFuture;
+ }
+
+ return this;
+ }
+
+ /// Closes this client and its underlying HTTP client.
+ @override
+ void close() {
+ _httpClient?.close();
+ _httpClient = null;
+ }
+}
diff --git a/pkgs/oauth2/lib/src/client_credentials_grant.dart b/pkgs/oauth2/lib/src/client_credentials_grant.dart
new file mode 100644
index 0000000..045d1a0
--- /dev/null
+++ b/pkgs/oauth2/lib/src/client_credentials_grant.dart
@@ -0,0 +1,79 @@
+// 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:async';
+
+import 'package:http/http.dart' as http;
+import 'package:http_parser/http_parser.dart';
+
+import 'client.dart';
+import 'handle_access_token_response.dart';
+import 'utils.dart';
+
+/// Obtains credentials using a [client credentials grant](https://tools.ietf.org/html/rfc6749#section-1.3.4).
+///
+/// This mode of authorization uses the client's [identifier] and [secret]
+/// to obtain an authorization token from the authorization server, instead
+/// of sending a user through a dedicated flow.
+///
+/// The client [identifier] and [secret] are required, and are
+/// used to identify and authenticate your specific OAuth2 client. These are
+/// usually global to the program using this library.
+///
+/// The specific permissions being requested from the authorization server may
+/// be specified via [scopes]. The scope strings are specific to the
+/// authorization server and may be found in its documentation. Note that you
+/// may not be granted access to every scope you request; you may check the
+/// [Credentials.scopes] field of [Client.credentials] to see which scopes you
+/// were granted.
+///
+/// The scope strings will be separated by the provided [delimiter]. This
+/// defaults to `" "`, the OAuth2 standard, but some APIs (such as Facebook's)
+/// use non-standard delimiters.
+///
+/// By default, this follows the OAuth2 spec and requires the server's responses
+/// to be in JSON format. However, some servers return non-standard response
+/// formats, which can be parsed using the [getParameters] function.
+///
+/// This function is passed the `Content-Type` header of the response as well as
+/// its body as a UTF-8-decoded string. It should return a map in the same
+/// format as the [standard JSON response](https://tools.ietf.org/html/rfc6749#section-5.1)
+Future<Client> clientCredentialsGrant(
+ Uri authorizationEndpoint, String? identifier, String? secret,
+ {Iterable<String>? scopes,
+ bool basicAuth = true,
+ http.Client? httpClient,
+ String? delimiter,
+ Map<String, dynamic> Function(MediaType? contentType, String body)?
+ getParameters}) async {
+ delimiter ??= ' ';
+ var startTime = DateTime.now();
+
+ var body = {'grant_type': 'client_credentials'};
+
+ var headers = <String, String>{};
+
+ if (identifier != null) {
+ if (basicAuth) {
+ headers['Authorization'] = basicAuthHeader(identifier, secret!);
+ } else {
+ body['client_id'] = identifier;
+ if (secret != null) body['client_secret'] = secret;
+ }
+ }
+
+ if (scopes != null && scopes.isNotEmpty) {
+ body['scope'] = scopes.join(delimiter);
+ }
+
+ httpClient ??= http.Client();
+ var response = await httpClient.post(authorizationEndpoint,
+ headers: headers, body: body);
+
+ var credentials = handleAccessTokenResponse(response, authorizationEndpoint,
+ startTime, scopes?.toList() ?? [], delimiter,
+ getParameters: getParameters);
+ return Client(credentials,
+ identifier: identifier, secret: secret, httpClient: httpClient);
+}
diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart
new file mode 100644
index 0000000..088b482
--- /dev/null
+++ b/pkgs/oauth2/lib/src/credentials.dart
@@ -0,0 +1,267 @@
+// 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:async';
+import 'dart:collection';
+import 'dart:convert';
+
+import 'package:http/http.dart' as http;
+import 'package:http_parser/http_parser.dart';
+
+import 'handle_access_token_response.dart';
+import 'parameters.dart';
+import 'utils.dart';
+
+/// Type of the callback when credentials are refreshed.
+typedef CredentialsRefreshedCallback = void Function(Credentials);
+
+/// Credentials that prove that a client is allowed to access a resource on the
+/// resource owner's behalf.
+///
+/// These credentials are long-lasting and can be safely persisted across
+/// multiple runs of the program.
+///
+/// Many authorization servers will attach an expiration date to a set of
+/// credentials, along with a token that can be used to refresh the credentials
+/// once they've expired. The [http.Client] will automatically refresh its
+/// credentials when necessary. It's also possible to explicitly refresh them
+/// via [http.Client.refreshCredentials] or [Credentials.refresh].
+///
+/// Note that a given set of credentials can only be refreshed once, so be sure
+/// to save the refreshed credentials for future use.
+class Credentials {
+ /// A [String] used to separate scopes; defaults to `" "`.
+ String _delimiter;
+
+ /// The token that is sent to the resource server to prove the authorization
+ /// of a client.
+ final String accessToken;
+
+ /// The token that is sent to the authorization server to refresh the
+ /// credentials.
+ ///
+ /// This may be `null`, indicating that the credentials can't be refreshed.
+ final String? refreshToken;
+
+ /// The token that is received from the authorization server to enable
+ /// End-Users to be Authenticated, contains Claims, represented as a
+ /// JSON Web Token (JWT).
+ ///
+ /// This may be `null`, indicating that the 'openid' scope was not
+ /// requested (or not supported).
+ ///
+ /// [spec]: https://openid.net/specs/openid-connect-core-1_0.html#IDToken
+ final String? idToken;
+
+ /// The URL of the authorization server endpoint that's used to refresh the
+ /// credentials.
+ ///
+ /// This may be `null`, indicating that the credentials can't be refreshed.
+ final Uri? tokenEndpoint;
+
+ /// The specific permissions being requested from the authorization server.
+ ///
+ /// The scope strings are specific to the authorization server and may be
+ /// found in its documentation.
+ final List<String>? scopes;
+
+ /// The date at which these credentials will expire.
+ ///
+ /// This is likely to be a few seconds earlier than the server's idea of the
+ /// expiration date.
+ final DateTime? expiration;
+
+ /// The function used to parse parameters from a host's response.
+ final GetParameters _getParameters;
+
+ /// Whether or not these credentials have expired.
+ ///
+ /// Note that it's possible the credentials will expire shortly after this is
+ /// called. However, since the client's expiration date is kept a few seconds
+ /// earlier than the server's, there should be enough leeway to rely on this.
+ bool get isExpired {
+ var expiration = this.expiration;
+ return expiration != null && DateTime.now().isAfter(expiration);
+ }
+
+ /// Whether it's possible to refresh these credentials.
+ bool get canRefresh => refreshToken != null && tokenEndpoint != null;
+
+ /// Creates a new set of credentials.
+ ///
+ /// This class is usually not constructed directly; rather, it's accessed via
+ /// [Client.credentials] after a [Client] is created by
+ /// [AuthorizationCodeGrant]. Alternately, it may be loaded from a serialized
+ /// form via [Credentials.fromJson].
+ ///
+ /// The scope strings will be separated by the provided [delimiter]. This
+ /// defaults to `" "`, the OAuth2 standard, but some APIs (such as Facebook's)
+ /// use non-standard delimiters.
+ ///
+ /// By default, this follows the OAuth2 spec and requires the server's
+ /// responses to be in JSON format. However, some servers return non-standard
+ /// response formats, which can be parsed using the [getParameters] function.
+ ///
+ /// This function is passed the `Content-Type` header of the response as well
+ /// as its body as a UTF-8-decoded string. It should return a map in the same
+ /// format as the [standard JSON response][].
+ ///
+ /// [standard JSON response]: https://tools.ietf.org/html/rfc6749#section-5.1
+ Credentials(this.accessToken,
+ {this.refreshToken,
+ this.idToken,
+ this.tokenEndpoint,
+ Iterable<String>? scopes,
+ this.expiration,
+ String? delimiter,
+ Map<String, dynamic> Function(MediaType? mediaType, String body)?
+ getParameters})
+ : scopes = UnmodifiableListView(
+ // Explicitly type-annotate the list literal to work around
+ // sdk#24202.
+ scopes == null ? <String>[] : scopes.toList()),
+ _delimiter = delimiter ?? ' ',
+ _getParameters = getParameters ?? parseJsonParameters;
+
+ /// Loads a set of credentials from a JSON-serialized form.
+ ///
+ /// Throws a [FormatException] if the JSON is incorrectly formatted.
+ factory Credentials.fromJson(String json) {
+ void validate(bool condition, String message) {
+ if (condition) return;
+ throw FormatException('Failed to load credentials: $message.\n\n$json');
+ }
+
+ dynamic parsed;
+ try {
+ parsed = jsonDecode(json);
+ } on FormatException {
+ validate(false, 'invalid JSON');
+ }
+
+ validate(parsed is Map, 'was not a JSON map');
+
+ parsed = parsed as Map;
+ validate(parsed.containsKey('accessToken'),
+ 'did not contain required field "accessToken"');
+ validate(
+ parsed['accessToken'] is String,
+ 'required field "accessToken" was not a string, was '
+ '${parsed["accessToken"]}',
+ );
+
+ for (var stringField in ['refreshToken', 'idToken', 'tokenEndpoint']) {
+ var value = parsed[stringField];
+ validate(value == null || value is String,
+ 'field "$stringField" was not a string, was "$value"');
+ }
+
+ var scopes = parsed['scopes'];
+ validate(scopes == null || scopes is List,
+ 'field "scopes" was not a list, was "$scopes"');
+
+ var tokenEndpoint = parsed['tokenEndpoint'];
+ Uri? tokenEndpointUri;
+ if (tokenEndpoint != null) {
+ tokenEndpointUri = Uri.parse(tokenEndpoint as String);
+ }
+
+ var expiration = parsed['expiration'];
+ DateTime? expirationDateTime;
+ if (expiration != null) {
+ validate(expiration is int,
+ 'field "expiration" was not an int, was "$expiration"');
+ expiration = expiration as int;
+ expirationDateTime = DateTime.fromMillisecondsSinceEpoch(expiration);
+ }
+
+ return Credentials(
+ parsed['accessToken'] as String,
+ refreshToken: parsed['refreshToken'] as String?,
+ idToken: parsed['idToken'] as String?,
+ tokenEndpoint: tokenEndpointUri,
+ scopes: (scopes as List).map((scope) => scope as String),
+ expiration: expirationDateTime,
+ );
+ }
+
+ /// Serializes a set of credentials to JSON.
+ ///
+ /// Nothing is guaranteed about the output except that it's valid JSON and
+ /// compatible with [Credentials.toJson].
+ String toJson() => jsonEncode({
+ 'accessToken': accessToken,
+ 'refreshToken': refreshToken,
+ 'idToken': idToken,
+ 'tokenEndpoint': tokenEndpoint?.toString(),
+ 'scopes': scopes,
+ 'expiration': expiration?.millisecondsSinceEpoch
+ });
+
+ /// Returns a new set of refreshed credentials.
+ ///
+ /// See [Client.identifier] and [Client.secret] for explanations of those
+ /// parameters.
+ ///
+ /// You may request different scopes than the default by passing in
+ /// [newScopes]. These must be a subset of [scopes].
+ ///
+ /// This throws an [ArgumentError] if [secret] is passed without [identifier],
+ /// a [StateError] if these credentials can't be refreshed, an
+ /// [AuthorizationException] if refreshing the credentials fails, or a
+ /// [FormatException] if the authorization server returns invalid responses.
+ Future<Credentials> refresh(
+ {String? identifier,
+ String? secret,
+ Iterable<String>? newScopes,
+ bool basicAuth = true,
+ http.Client? httpClient}) async {
+ var scopes = this.scopes;
+ if (newScopes != null) scopes = newScopes.toList();
+ scopes ??= [];
+ httpClient ??= http.Client();
+
+ if (identifier == null && secret != null) {
+ throw ArgumentError('secret may not be passed without identifier.');
+ }
+
+ var startTime = DateTime.now();
+ var tokenEndpoint = this.tokenEndpoint;
+ if (refreshToken == null) {
+ throw StateError("Can't refresh credentials without a refresh "
+ 'token.');
+ } else if (tokenEndpoint == null) {
+ throw StateError("Can't refresh credentials without a token "
+ 'endpoint.');
+ }
+
+ var headers = <String, String>{};
+
+ var body = {'grant_type': 'refresh_token', 'refresh_token': refreshToken};
+ if (scopes.isNotEmpty) body['scope'] = scopes.join(_delimiter);
+
+ if (basicAuth && secret != null) {
+ headers['Authorization'] = basicAuthHeader(identifier!, secret);
+ } else {
+ if (identifier != null) body['client_id'] = identifier;
+ if (secret != null) body['client_secret'] = secret;
+ }
+
+ var response =
+ await httpClient.post(tokenEndpoint, headers: headers, body: body);
+ var credentials = handleAccessTokenResponse(
+ response, tokenEndpoint, startTime, scopes, _delimiter,
+ getParameters: _getParameters);
+
+ // The authorization server may issue a new refresh token. If it doesn't,
+ // we should re-use the one we already have.
+ if (credentials.refreshToken != null) return credentials;
+ return Credentials(credentials.accessToken,
+ refreshToken: refreshToken,
+ idToken: credentials.idToken,
+ tokenEndpoint: credentials.tokenEndpoint,
+ scopes: credentials.scopes,
+ expiration: credentials.expiration);
+ }
+}
diff --git a/pkgs/oauth2/lib/src/expiration_exception.dart b/pkgs/oauth2/lib/src/expiration_exception.dart
new file mode 100644
index 0000000..d72fcf6
--- /dev/null
+++ b/pkgs/oauth2/lib/src/expiration_exception.dart
@@ -0,0 +1,19 @@
+// 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 'credentials.dart';
+
+/// An exception raised when attempting to use expired OAuth2 credentials.
+class ExpirationException implements Exception {
+ /// The expired credentials.
+ final Credentials credentials;
+
+ /// Creates an ExpirationException.
+ ExpirationException(this.credentials);
+
+ /// Provides a string description of the ExpirationException.
+ @override
+ String toString() =>
+ "OAuth2 credentials have expired and can't be refreshed.";
+}
diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart
new file mode 100644
index 0000000..f318e3b
--- /dev/null
+++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart
@@ -0,0 +1,158 @@
+// 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 'package:http/http.dart' as http;
+import 'package:http_parser/http_parser.dart';
+
+import 'authorization_exception.dart';
+import 'credentials.dart';
+import 'parameters.dart';
+
+/// The amount of time to add as a "grace period" for credential expiration.
+///
+/// This allows credential expiration checks to remain valid for a reasonable
+/// amount of time.
+const _expirationGrace = Duration(seconds: 10);
+
+/// Handles a response from the authorization server that contains an access
+/// token.
+///
+/// This response format is common across several different components of the
+/// OAuth2 flow.
+///
+/// By default, this follows the OAuth2 spec and requires the server's responses
+/// to be in JSON format. However, some servers return non-standard response
+/// formats, which can be parsed using the [getParameters] function.
+///
+/// This function is passed the `Content-Type` header of the response as well as
+/// its body as a UTF-8-decoded string. It should return a map in the same
+/// format as the [standard JSON response][].
+///
+/// [standard JSON response]: https://tools.ietf.org/html/rfc6749#section-5.1
+Credentials handleAccessTokenResponse(http.Response response, Uri tokenEndpoint,
+ DateTime startTime, List<String>? scopes, String delimiter,
+ {Map<String, dynamic> Function(MediaType? contentType, String body)?
+ getParameters}) {
+ getParameters ??= parseJsonParameters;
+
+ try {
+ if (response.statusCode != 200) {
+ _handleErrorResponse(response, tokenEndpoint, getParameters);
+ }
+
+ var contentTypeString = response.headers['content-type'];
+ if (contentTypeString == null) {
+ throw const FormatException('Missing Content-Type string.');
+ }
+
+ var parameters =
+ getParameters(MediaType.parse(contentTypeString), response.body);
+
+ for (var requiredParameter in ['access_token', 'token_type']) {
+ if (!parameters.containsKey(requiredParameter)) {
+ throw FormatException(
+ 'did not contain required parameter "$requiredParameter"');
+ } else if (parameters[requiredParameter] is! String) {
+ throw FormatException(
+ 'required parameter "$requiredParameter" was not a string, was '
+ '"${parameters[requiredParameter]}"');
+ }
+ }
+
+ // TODO(nweiz): support the "mac" token type
+ // (http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01)
+ if ((parameters['token_type'] as String).toLowerCase() != 'bearer') {
+ throw FormatException(
+ '"$tokenEndpoint": unknown token type "${parameters['token_type']}"');
+ }
+
+ var expiresIn = parameters['expires_in'];
+ if (expiresIn != null) {
+ if (expiresIn is String) {
+ try {
+ expiresIn = double.parse(expiresIn).toInt();
+ } on FormatException {
+ throw FormatException(
+ 'parameter "expires_in" could not be parsed as in, was: '
+ '"$expiresIn"',
+ );
+ }
+ } else if (expiresIn is! int) {
+ throw FormatException(
+ 'parameter "expires_in" was not an int, was: "$expiresIn"');
+ }
+ }
+
+ for (var name in ['refresh_token', 'id_token', 'scope']) {
+ var value = parameters[name];
+ if (value != null && value is! String) {
+ throw FormatException(
+ 'parameter "$name" was not a string, was "$value"');
+ }
+ }
+
+ var scope = parameters['scope'] as String?;
+ if (scope != null) scopes = scope.split(delimiter);
+
+ var expiration = expiresIn == null
+ ? null
+ : startTime.add(Duration(seconds: expiresIn as int) - _expirationGrace);
+
+ return Credentials(
+ parameters['access_token'] as String,
+ refreshToken: parameters['refresh_token'] as String?,
+ idToken: parameters['id_token'] as String?,
+ tokenEndpoint: tokenEndpoint,
+ scopes: scopes,
+ expiration: expiration,
+ );
+ } on FormatException catch (e) {
+ throw FormatException('Invalid OAuth response for "$tokenEndpoint": '
+ '${e.message}.\n\n${response.body}');
+ }
+}
+
+/// Throws the appropriate exception for an error response from the
+/// authorization server.
+void _handleErrorResponse(
+ http.Response response, Uri tokenEndpoint, GetParameters getParameters) {
+ // OAuth2 mandates a 400 or 401 response code for access token error
+ // responses. If it's not a 400 reponse, the server is either broken or
+ // off-spec.
+ if (response.statusCode != 400 && response.statusCode != 401) {
+ var reason = '';
+ var reasonPhrase = response.reasonPhrase;
+ if (reasonPhrase != null && reasonPhrase.isNotEmpty) {
+ reason = ' $reasonPhrase';
+ }
+ throw FormatException('OAuth request for "$tokenEndpoint" failed '
+ 'with status ${response.statusCode}$reason.\n\n${response.body}');
+ }
+
+ var contentTypeString = response.headers['content-type'];
+ var contentType =
+ contentTypeString == null ? null : MediaType.parse(contentTypeString);
+
+ var parameters = getParameters(contentType, response.body);
+
+ if (!parameters.containsKey('error')) {
+ throw const FormatException('did not contain required parameter "error"');
+ } else if (parameters['error'] is! String) {
+ throw FormatException('required parameter "error" was not a string, was '
+ '"${parameters["error"]}"');
+ }
+
+ for (var name in ['error_description', 'error_uri']) {
+ var value = parameters[name];
+
+ if (value != null && value is! String) {
+ throw FormatException('parameter "$name" was not a string, was "$value"');
+ }
+ }
+
+ var uriString = parameters['error_uri'] as String?;
+ var uri = uriString == null ? null : Uri.parse(uriString);
+ var description = parameters['error_description'] as String?;
+ throw AuthorizationException(parameters['error'] as String, description, uri);
+}
diff --git a/pkgs/oauth2/lib/src/parameters.dart b/pkgs/oauth2/lib/src/parameters.dart
new file mode 100644
index 0000000..ecc6559
--- /dev/null
+++ b/pkgs/oauth2/lib/src/parameters.dart
@@ -0,0 +1,33 @@
+// 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.
+
+import 'dart:convert';
+
+import 'package:http_parser/http_parser.dart';
+
+/// The type of a callback that parses parameters from an HTTP response.
+typedef GetParameters = Map<String, dynamic> Function(
+ MediaType? contentType, String body);
+
+/// Parses parameters from a response with a JSON body, as per the
+/// [OAuth2 spec][].
+///
+/// [OAuth2 spec]: https://tools.ietf.org/html/rfc6749#section-5.1
+Map<String, dynamic> parseJsonParameters(MediaType? contentType, String body) {
+ // The spec requires a content-type of application/json, but some endpoints
+ // (e.g. Dropbox) serve it as text/javascript instead.
+ if (contentType == null ||
+ (contentType.mimeType != 'application/json' &&
+ contentType.mimeType != 'text/javascript')) {
+ throw FormatException(
+ 'Content-Type was "$contentType", expected "application/json"');
+ }
+
+ var untypedParameters = jsonDecode(body);
+ if (untypedParameters is Map<String, dynamic>) {
+ return untypedParameters;
+ }
+
+ throw FormatException('Parameters must be a map, was "$untypedParameters"');
+}
diff --git a/pkgs/oauth2/lib/src/resource_owner_password_grant.dart b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart
new file mode 100644
index 0000000..96fb503
--- /dev/null
+++ b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart
@@ -0,0 +1,94 @@
+// 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:async';
+
+import 'package:http/http.dart' as http;
+import 'package:http_parser/http_parser.dart';
+
+import 'client.dart';
+import 'credentials.dart';
+import 'handle_access_token_response.dart';
+import 'utils.dart';
+
+/// Obtains credentials using a [resource owner password grant](https://tools.ietf.org/html/rfc6749#section-1.3.3).
+///
+/// This mode of authorization uses the user's username and password to obtain
+/// an authentication token, which can then be stored. This is safer than
+/// storing the username and password directly, but it should be avoided if any
+/// other authorization method is available, since it requires the user to
+/// provide their username and password to a third party (you).
+///
+/// The client [identifier] and [secret] may be issued by the server, and are
+/// used to identify and authenticate your specific OAuth2 client. These are
+/// usually global to the program using this library.
+///
+/// The specific permissions being requested from the authorization server may
+/// be specified via [scopes]. The scope strings are specific to the
+/// authorization server and may be found in its documentation. Note that you
+/// may not be granted access to every scope you request; you may check the
+/// [Credentials.scopes] field of [Client.credentials] to see which scopes you
+/// were granted.
+///
+/// The scope strings will be separated by the provided [delimiter]. This
+/// defaults to `" "`, the OAuth2 standard, but some APIs (such as Facebook's)
+/// use non-standard delimiters.
+///
+/// By default, this follows the OAuth2 spec and requires the server's responses
+/// to be in JSON format. However, some servers return non-standard response
+/// formats, which can be parsed using the [getParameters] function.
+///
+/// This function is passed the `Content-Type` header of the response as well as
+/// its body as a UTF-8-decoded string. It should return a map in the same
+/// format as the [standard JSON response][].
+///
+/// [standard JSON response]: https://tools.ietf.org/html/rfc6749#section-5.1
+Future<Client> resourceOwnerPasswordGrant(
+ Uri authorizationEndpoint, String username, String password,
+ {String? identifier,
+ String? secret,
+ Iterable<String>? scopes,
+ bool basicAuth = true,
+ CredentialsRefreshedCallback? onCredentialsRefreshed,
+ http.Client? httpClient,
+ String? delimiter,
+ Map<String, dynamic> Function(MediaType? contentType, String body)?
+ getParameters}) async {
+ delimiter ??= ' ';
+ var startTime = DateTime.now();
+
+ var body = {
+ 'grant_type': 'password',
+ 'username': username,
+ 'password': password
+ };
+
+ var headers = <String, String>{};
+
+ if (identifier != null) {
+ if (basicAuth) {
+ headers['Authorization'] = basicAuthHeader(identifier, secret!);
+ } else {
+ body['client_id'] = identifier;
+ if (secret != null) body['client_secret'] = secret;
+ }
+ }
+
+ if (scopes != null && scopes.isNotEmpty) {
+ body['scope'] = scopes.join(delimiter);
+ }
+
+ httpClient ??= http.Client();
+ var response = await httpClient.post(authorizationEndpoint,
+ headers: headers, body: body);
+
+ var credentials = handleAccessTokenResponse(
+ response, authorizationEndpoint, startTime, scopes?.toList(), delimiter,
+ getParameters: getParameters);
+ return Client(credentials,
+ identifier: identifier,
+ secret: secret,
+ httpClient: httpClient,
+ onCredentialsRefreshed: onCredentialsRefreshed);
+}
diff --git a/pkgs/oauth2/lib/src/utils.dart b/pkgs/oauth2/lib/src/utils.dart
new file mode 100644
index 0000000..2a22b9f
--- /dev/null
+++ b/pkgs/oauth2/lib/src/utils.dart
@@ -0,0 +1,15 @@
+// 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:convert';
+
+/// Adds additional query parameters to [url], overwriting the original
+/// parameters if a name conflict occurs.
+Uri addQueryParameters(Uri url, Map<String, String> parameters) => url.replace(
+ queryParameters: Map.from(url.queryParameters)..addAll(parameters));
+
+String basicAuthHeader(String identifier, String secret) {
+ var userPass = '${Uri.encodeFull(identifier)}:${Uri.encodeFull(secret)}';
+ return 'Basic ${base64Encode(ascii.encode(userPass))}';
+}
diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml
new file mode 100644
index 0000000..ab705ba
--- /dev/null
+++ b/pkgs/oauth2/pubspec.yaml
@@ -0,0 +1,21 @@
+name: oauth2
+version: 2.0.4-wip
+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
+ OAuth2 credentials.
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/oauth2
+issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aoauth2
+
+environment:
+ sdk: ^3.4.0
+
+dependencies:
+ collection: ^1.15.0
+ crypto: ^3.0.0
+ http: ^1.0.0
+ http_parser: ^4.0.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ test: ^1.16.0
diff --git a/pkgs/oauth2/test/authorization_code_grant_test.dart b/pkgs/oauth2/test/authorization_code_grant_test.dart
new file mode 100644
index 0000000..06e88af
--- /dev/null
+++ b/pkgs/oauth2/test/authorization_code_grant_test.dart
@@ -0,0 +1,391 @@
+// 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:async';
+import 'dart:convert';
+
+import 'package:http/http.dart' as http;
+import 'package:oauth2/oauth2.dart' as oauth2;
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+final redirectUrl = Uri.parse('http://example.com/redirect');
+
+void main() {
+ late ExpectClient client;
+ late oauth2.AuthorizationCodeGrant grant;
+ setUp(() {
+ client = ExpectClient();
+ grant = oauth2.AuthorizationCodeGrant(
+ 'identifier',
+ Uri.parse('https://example.com/authorization'),
+ Uri.parse('https://example.com/token'),
+ secret: 'secret',
+ httpClient: client);
+ });
+
+ group('.getAuthorizationUrl', () {
+ test('builds the correct URL', () {
+ expect(
+ grant.getAuthorizationUrl(redirectUrl).toString(),
+ allOf([
+ startsWith('https://example.com/authorization?response_type=code'),
+ contains('&client_id=identifier'),
+ contains('&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect'),
+ matches(r'&code_challenge=[A-Za-z0-9\+\/\-\_]{43}'),
+ contains('&code_challenge_method=S256')
+ ]));
+ });
+
+ test('builds the correct URL with scopes', () {
+ var authorizationUrl = grant
+ .getAuthorizationUrl(redirectUrl, scopes: ['scope', 'other/scope']);
+ expect(
+ authorizationUrl.toString(),
+ allOf([
+ startsWith('https://example.com/authorization?response_type=code'),
+ contains('&client_id=identifier'),
+ contains('&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect'),
+ matches(r'&code_challenge=[A-Za-z0-9\+\/\-\_]{43}'),
+ contains('&code_challenge_method=S256'),
+ contains('&scope=scope+other%2Fscope')
+ ]));
+ });
+
+ test('builds the correct URL with passed in code verifier', () {
+ const codeVerifier =
+ 'it1shei7LooGoh3looxaa4sieveijeib2zecauz2oo8aebae5aehee0ahPirewoh'
+ '5Bo6Maexooqui3uL2si6ahweiv7shauc1shahxooveoB3aeyahsaiye0Egh3raix';
+ const expectedCodeChallenge =
+ 'EjfFMv8TFPd3GuNxAn5COhlWBGpfZLimHett7ypJfJ0';
+ var grant = oauth2.AuthorizationCodeGrant(
+ 'identifier',
+ Uri.parse('https://example.com/authorization'),
+ Uri.parse('https://example.com/token'),
+ secret: 'secret',
+ httpClient: client,
+ codeVerifier: codeVerifier);
+ expect(
+ grant.getAuthorizationUrl(redirectUrl).toString(),
+ allOf([
+ startsWith('https://example.com/authorization?response_type=code'),
+ contains('&client_id=identifier'),
+ contains('&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect'),
+ contains('&code_challenge=$expectedCodeChallenge'),
+ contains('&code_challenge_method=S256')
+ ]));
+ });
+
+ test('separates scopes with the correct delimiter', () {
+ var grant = oauth2.AuthorizationCodeGrant(
+ 'identifier',
+ Uri.parse('https://example.com/authorization'),
+ Uri.parse('https://example.com/token'),
+ secret: 'secret',
+ httpClient: client,
+ delimiter: '_');
+ var authorizationUrl = grant
+ .getAuthorizationUrl(redirectUrl, scopes: ['scope', 'other/scope']);
+ expect(
+ authorizationUrl.toString(),
+ allOf([
+ startsWith('https://example.com/authorization?response_type=code'),
+ contains('&client_id=identifier'),
+ contains('&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect'),
+ matches(r'&code_challenge=[A-Za-z0-9\+\/\-\_]{43}'),
+ contains('&code_challenge_method=S256'),
+ contains('&scope=scope_other%2Fscope')
+ ]));
+ });
+
+ test('builds the correct URL with state', () {
+ var authorizationUrl =
+ grant.getAuthorizationUrl(redirectUrl, state: 'state');
+ expect(
+ authorizationUrl.toString(),
+ allOf([
+ startsWith('https://example.com/authorization?response_type=code'),
+ contains('&client_id=identifier'),
+ contains('&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect'),
+ matches(r'&code_challenge=[A-Za-z0-9\+\/\-\_]{43}'),
+ contains('&code_challenge_method=S256'),
+ contains('&state=state')
+ ]));
+ });
+
+ test('merges with existing query parameters', () {
+ grant = oauth2.AuthorizationCodeGrant(
+ 'identifier',
+ Uri.parse('https://example.com/authorization?query=value'),
+ Uri.parse('https://example.com/token'),
+ secret: 'secret',
+ httpClient: client);
+
+ var authorizationUrl = grant.getAuthorizationUrl(redirectUrl);
+ expect(
+ authorizationUrl.toString(),
+ allOf([
+ startsWith('https://example.com/authorization?query=value'),
+ contains('&response_type=code'),
+ contains('&client_id=identifier'),
+ contains('&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect'),
+ matches(r'&code_challenge=[A-Za-z0-9\+\/\-\_]{43}'),
+ contains('&code_challenge_method=S256'),
+ ]));
+ });
+
+ test("can't be called twice", () {
+ grant.getAuthorizationUrl(redirectUrl);
+ expect(() => grant.getAuthorizationUrl(redirectUrl), throwsStateError);
+ });
+ });
+
+ group('.handleAuthorizationResponse', () {
+ test("can't be called before .getAuthorizationUrl", () {
+ expect(grant.handleAuthorizationResponse({}), throwsStateError);
+ });
+
+ test("can't be called twice", () {
+ grant.getAuthorizationUrl(redirectUrl);
+ expect(grant.handleAuthorizationResponse({'code': 'auth code'}),
+ throwsFormatException);
+ expect(grant.handleAuthorizationResponse({'code': 'auth code'}),
+ throwsStateError);
+ });
+
+ test('must have a state parameter if the authorization URL did', () {
+ grant.getAuthorizationUrl(redirectUrl, state: 'state');
+ expect(grant.handleAuthorizationResponse({'code': 'auth code'}),
+ throwsFormatException);
+ });
+
+ test('must have the same state parameter the authorization URL did', () {
+ grant.getAuthorizationUrl(redirectUrl, state: 'state');
+ expect(
+ grant.handleAuthorizationResponse(
+ {'code': 'auth code', 'state': 'other state'}),
+ throwsFormatException);
+ });
+
+ test('must have a code parameter', () {
+ grant.getAuthorizationUrl(redirectUrl);
+ expect(grant.handleAuthorizationResponse({}), throwsFormatException);
+ });
+
+ test('with an error parameter throws an AuthorizationException', () {
+ grant.getAuthorizationUrl(redirectUrl);
+ expect(grant.handleAuthorizationResponse({'error': 'invalid_request'}),
+ throwsA(isA<oauth2.AuthorizationException>()));
+ });
+
+ test('sends an authorization code request', () {
+ grant.getAuthorizationUrl(redirectUrl);
+ client.expectRequest((request) {
+ expect(request.method, equals('POST'));
+ expect(request.url.toString(), equals(grant.tokenEndpoint.toString()));
+ expect(
+ request.bodyFields,
+ allOf([
+ containsPair('grant_type', 'authorization_code'),
+ containsPair('code', 'auth code'),
+ containsPair('redirect_uri', redirectUrl.toString()),
+ containsPair(
+ 'code_verifier', matches(r'[A-Za-z0-9\-\.\_\~]{128}'))
+ ]));
+ expect(request.headers,
+ containsPair('Authorization', 'Basic aWRlbnRpZmllcjpzZWNyZXQ='));
+
+ return Future.value(http.Response(
+ jsonEncode({
+ 'access_token': 'access token',
+ 'token_type': 'bearer',
+ }),
+ 200,
+ headers: {'content-type': 'application/json'}));
+ });
+
+ expect(
+ grant.handleAuthorizationResponse({'code': 'auth code'}).then(
+ (client) => client.credentials.accessToken),
+ completion(equals('access token')));
+ });
+ });
+
+ group('.handleAuthorizationCode', () {
+ test("can't be called before .getAuthorizationUrl", () {
+ expect(grant.handleAuthorizationCode('auth code'), throwsStateError);
+ });
+
+ test("can't be called twice", () {
+ grant.getAuthorizationUrl(redirectUrl);
+ expect(grant.handleAuthorizationCode('auth code'), throwsFormatException);
+ expect(grant.handleAuthorizationCode('auth code'), throwsStateError);
+ });
+
+ test('sends an authorization code request', () async {
+ grant.getAuthorizationUrl(redirectUrl);
+ client.expectRequest((request) {
+ expect(request.method, equals('POST'));
+ expect(request.url.toString(), equals(grant.tokenEndpoint.toString()));
+ expect(
+ request.bodyFields,
+ allOf([
+ containsPair('grant_type', 'authorization_code'),
+ containsPair('code', 'auth code'),
+ containsPair('redirect_uri', redirectUrl.toString()),
+ containsPair(
+ 'code_verifier', matches(r'[A-Za-z0-9\-\.\_\~]{128}'))
+ ]));
+ expect(request.headers,
+ containsPair('Authorization', 'Basic aWRlbnRpZmllcjpzZWNyZXQ='));
+
+ return Future.value(http.Response(
+ jsonEncode({
+ 'access_token': 'access token',
+ 'token_type': 'bearer',
+ }),
+ 200,
+ headers: {'content-type': 'application/json'}));
+ });
+
+ expect(
+ await grant.handleAuthorizationCode('auth code'),
+ isA<oauth2.Client>().having((c) => c.credentials.accessToken,
+ 'credentials.accessToken', 'access token'));
+ });
+ });
+
+ group('with basicAuth: false', () {
+ setUp(() {
+ client = ExpectClient();
+ grant = oauth2.AuthorizationCodeGrant(
+ 'identifier',
+ Uri.parse('https://example.com/authorization'),
+ Uri.parse('https://example.com/token'),
+ secret: 'secret',
+ basicAuth: false,
+ httpClient: client);
+ });
+
+ test('.handleAuthorizationResponse sends an authorization code request',
+ () {
+ grant.getAuthorizationUrl(redirectUrl);
+ client.expectRequest((request) {
+ expect(request.method, equals('POST'));
+ expect(request.url.toString(), equals(grant.tokenEndpoint.toString()));
+ expect(
+ request.bodyFields,
+ allOf([
+ containsPair('grant_type', 'authorization_code'),
+ containsPair('code', 'auth code'),
+ containsPair('redirect_uri', redirectUrl.toString()),
+ containsPair(
+ 'code_verifier', matches(r'[A-Za-z0-9\-\.\_\~]{128}')),
+ containsPair('client_id', 'identifier'),
+ containsPair('client_secret', 'secret')
+ ]));
+
+ return Future.value(http.Response(
+ jsonEncode({
+ 'access_token': 'access token',
+ 'token_type': 'bearer',
+ }),
+ 200,
+ headers: {'content-type': 'application/json'}));
+ });
+
+ expect(
+ grant.handleAuthorizationResponse({'code': 'auth code'}).then(
+ (client) => client.credentials.accessToken),
+ completion(equals('access token')));
+ });
+
+ test('.handleAuthorizationCode sends an authorization code request',
+ () async {
+ grant.getAuthorizationUrl(redirectUrl);
+ client.expectRequest((request) {
+ expect(request.method, equals('POST'));
+ expect(request.url.toString(), equals(grant.tokenEndpoint.toString()));
+ expect(
+ request.bodyFields,
+ allOf([
+ containsPair('grant_type', 'authorization_code'),
+ containsPair('code', 'auth code'),
+ containsPair('redirect_uri', redirectUrl.toString()),
+ containsPair(
+ 'code_verifier', matches(r'[A-Za-z0-9\-\.\_\~]{128}')),
+ containsPair('client_id', 'identifier'),
+ containsPair('client_secret', 'secret')
+ ]));
+
+ return Future.value(http.Response(
+ jsonEncode({
+ 'access_token': 'access token',
+ 'token_type': 'bearer',
+ }),
+ 200,
+ headers: {'content-type': 'application/json'}));
+ });
+
+ expect(
+ await grant.handleAuthorizationCode('auth code'),
+ isA<oauth2.Client>().having((c) => c.credentials.accessToken,
+ 'credentials.accessToken', 'access token'));
+ });
+ });
+
+ group('onCredentialsRefreshed', () {
+ test('is correctly propagated', () async {
+ var isCallbackInvoked = false;
+ var grant = oauth2.AuthorizationCodeGrant(
+ 'identifier',
+ Uri.parse('https://example.com/authorization'),
+ Uri.parse('https://example.com/token'),
+ secret: 'secret',
+ basicAuth: false,
+ httpClient: client, onCredentialsRefreshed: (credentials) {
+ isCallbackInvoked = true;
+ });
+
+ grant.getAuthorizationUrl(redirectUrl);
+ client.expectRequest(
+ (request) => Future.value(
+ http.Response(
+ jsonEncode({
+ 'access_token': 'access token',
+ 'token_type': 'bearer',
+ 'expires_in': -3600,
+ 'refresh_token': 'refresh token',
+ }),
+ 200,
+ headers: {'content-type': 'application/json'},
+ ),
+ ),
+ );
+
+ var oauth2Client = await grant.handleAuthorizationCode('auth code');
+
+ client.expectRequest(
+ (request) => Future.value(
+ http.Response(
+ jsonEncode({
+ 'access_token': 'new access token',
+ 'token_type': 'bearer',
+ }),
+ 200,
+ headers: {'content-type': 'application/json'},
+ ),
+ ),
+ );
+
+ client.expectRequest(
+ (request) => Future.value(http.Response('good job', 200)));
+
+ await oauth2Client.read(Uri.parse('http://example.com/resource'));
+
+ expect(isCallbackInvoked, equals(true));
+ });
+ });
+}
diff --git a/pkgs/oauth2/test/client_credentials_grant_test.dart b/pkgs/oauth2/test/client_credentials_grant_test.dart
new file mode 100644
index 0000000..28de425
--- /dev/null
+++ b/pkgs/oauth2/test/client_credentials_grant_test.dart
@@ -0,0 +1,113 @@
+// 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.
+
+@TestOn('vm')
+library;
+
+import 'dart:convert';
+
+import 'package:http/http.dart' as http;
+import 'package:oauth2/oauth2.dart' as oauth2;
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+final success = jsonEncode({
+ 'access_token': '2YotnFZFEjr1zCsicMWpAA',
+ 'token_type': 'bearer',
+ 'expires_in': 3600,
+ 'refresh_token': 'tGzv3JOkF0XG5Qx2TlKWIA',
+});
+
+String auth = 'Basic Y2xpZW50OnNlY3JldA==';
+Uri authEndpoint = Uri.parse('https://example.com');
+
+void main() {
+ late ExpectClient expectClient;
+
+ setUp(() => expectClient = ExpectClient());
+
+ group('basic', () {
+ test('builds correct request with client when using basic auth for client',
+ () async {
+ expectClient.expectRequest((request) async {
+ expect(auth, equals(request.headers['authorization']));
+ expect(request.bodyFields['grant_type'], equals('client_credentials'));
+ return http.Response(success, 200,
+ headers: {'content-type': 'application/json'});
+ });
+
+ var client = await oauth2.clientCredentialsGrant(
+ authEndpoint, 'client', 'secret',
+ httpClient: expectClient);
+
+ expect(client.credentials, isNotNull);
+ expect(client.credentials.accessToken, equals('2YotnFZFEjr1zCsicMWpAA'));
+ });
+
+ test('builds correct request when using query parameters for client',
+ () async {
+ expectClient.expectRequest((request) async {
+ expect(request.bodyFields['grant_type'], equals('client_credentials'));
+ expect(request.bodyFields['client_id'], equals('client'));
+ expect(request.bodyFields['client_secret'], equals('secret'));
+ return http.Response(success, 200,
+ headers: {'content-type': 'application/json'});
+ });
+
+ var client = await oauth2.clientCredentialsGrant(
+ authEndpoint, 'client', 'secret',
+ basicAuth: false, httpClient: expectClient);
+ expect(client.credentials, isNotNull);
+ expect(client.credentials.accessToken, equals('2YotnFZFEjr1zCsicMWpAA'));
+ });
+
+ test('builds correct request using scope', () async {
+ expectClient.expectRequest((request) async {
+ expect(auth, equals(request.headers['authorization']));
+ expect(request.bodyFields['grant_type'], equals('client_credentials'));
+ expect(request.bodyFields['scope'], equals('one two'));
+ return http.Response(success, 200,
+ headers: {'content-type': 'application/json'});
+ });
+
+ var client = await oauth2.clientCredentialsGrant(
+ authEndpoint, 'client', 'secret',
+ scopes: ['one', 'two'], httpClient: expectClient);
+ expect(client.credentials, isNotNull);
+ expect(client.credentials.accessToken, equals('2YotnFZFEjr1zCsicMWpAA'));
+ });
+
+ test('builds correct request using scope with custom delimiter', () async {
+ expectClient.expectRequest((request) async {
+ expect(request.bodyFields['grant_type'], equals('client_credentials'));
+ expect(request.bodyFields['scope'], equals('one,two'));
+ return http.Response(success, 200,
+ headers: {'content-type': 'application/json'});
+ });
+
+ await oauth2.clientCredentialsGrant(authEndpoint, 'client', 'secret',
+ scopes: ['one', 'two'], httpClient: expectClient, delimiter: ',');
+ });
+
+ test('merges with existing query parameters', () async {
+ var authEndpoint = Uri.parse('https://example.com?query=value');
+
+ expectClient.expectRequest((request) async {
+ expect(request.bodyFields['grant_type'], equals('client_credentials'));
+ expect(request.bodyFields['client_id'], equals('client'));
+ expect(request.bodyFields['client_secret'], equals('secret'));
+ expect(request.url.queryParameters['query'], equals('value'));
+ return http.Response(success, 200,
+ headers: {'content-type': 'application/json'});
+ });
+
+ var client = await oauth2.clientCredentialsGrant(
+ authEndpoint, 'client', 'secret',
+ basicAuth: false, httpClient: expectClient);
+ expect(client.credentials, isNotNull);
+ expect(client.credentials.accessToken, equals('2YotnFZFEjr1zCsicMWpAA'));
+ });
+ });
+}
diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart
new file mode 100644
index 0000000..3c30d36
--- /dev/null
+++ b/pkgs/oauth2/test/client_test.dart
@@ -0,0 +1,287 @@
+// 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:async';
+import 'dart:convert';
+
+import 'package:http/http.dart' as http;
+import 'package:oauth2/oauth2.dart' as oauth2;
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+final Uri requestUri = Uri.parse('http://example.com/resource');
+
+final Uri tokenEndpoint = Uri.parse('http://example.com/token');
+
+void main() {
+ late ExpectClient httpClient;
+
+ setUp(() => httpClient = ExpectClient());
+
+ group('with expired credentials', () {
+ test("that can't be refreshed throws an ExpirationException on send", () {
+ var expiration = DateTime.now().subtract(const Duration(hours: 1));
+ var credentials =
+ oauth2.Credentials('access token', expiration: expiration);
+ var client = oauth2.Client(credentials,
+ identifier: 'identifier', secret: 'secret', httpClient: httpClient);
+
+ expect(client.get(requestUri),
+ throwsA(const TypeMatcher<oauth2.ExpirationException>()));
+ });
+
+ test(
+ 'that can be refreshed refreshes the credentials and sends the '
+ 'request', () async {
+ var expiration = DateTime.now().subtract(const Duration(hours: 1));
+ var credentials = oauth2.Credentials('access token',
+ refreshToken: 'refresh token',
+ tokenEndpoint: tokenEndpoint,
+ expiration: expiration);
+ var client = oauth2.Client(credentials,
+ identifier: 'identifier', secret: 'secret', httpClient: httpClient);
+
+ httpClient.expectRequest((request) {
+ expect(request.method, equals('POST'));
+ expect(request.url.toString(), equals(tokenEndpoint.toString()));
+ return Future.value(http.Response(
+ jsonEncode(
+ {'access_token': 'new access token', 'token_type': 'bearer'}),
+ 200,
+ headers: {'content-type': 'application/json'}));
+ });
+
+ httpClient.expectRequest((request) {
+ expect(request.method, equals('GET'));
+ expect(request.url.toString(), equals(requestUri.toString()));
+ expect(request.headers['authorization'],
+ equals('Bearer new access token'));
+
+ return Future.value(http.Response('good job', 200));
+ });
+
+ await client.read(requestUri);
+ expect(client.credentials.accessToken, equals('new access token'));
+ });
+
+ test(
+ 'that can be refreshed refreshes only once if multiple requests are made',
+ () async {
+ var expiration = DateTime.now().subtract(const Duration(hours: 1));
+ var credentials = oauth2.Credentials('access token',
+ refreshToken: 'refresh token',
+ tokenEndpoint: tokenEndpoint,
+ expiration: expiration);
+ var client = oauth2.Client(credentials,
+ identifier: 'identifier', secret: 'secret', httpClient: httpClient);
+
+ httpClient.expectRequest((request) {
+ expect(request.method, equals('POST'));
+ expect(request.url.toString(), equals(tokenEndpoint.toString()));
+ return Future.value(http.Response(
+ jsonEncode(
+ {'access_token': 'new access token', 'token_type': 'bearer'}),
+ 200,
+ headers: {'content-type': 'application/json'}));
+ });
+
+ const numCalls = 2;
+
+ for (var i = 0; i < numCalls; i++) {
+ httpClient.expectRequest((request) {
+ expect(request.method, equals('GET'));
+ expect(request.url.toString(), equals(requestUri.toString()));
+ expect(request.headers['authorization'],
+ equals('Bearer new access token'));
+
+ return Future.value(http.Response('good job', 200));
+ });
+ }
+
+ await Future.wait(
+ List<Future<String>>.generate(
+ numCalls, (_) => client.read(requestUri)),
+ );
+
+ expect(client.credentials.accessToken, equals('new access token'));
+ },
+ );
+
+ test('that onCredentialsRefreshed is called', () async {
+ var callbackCalled = false;
+
+ var expiration = DateTime.now().subtract(const Duration(hours: 1));
+ var credentials = oauth2.Credentials('access token',
+ refreshToken: 'refresh token',
+ tokenEndpoint: tokenEndpoint,
+ expiration: expiration);
+ var client = oauth2.Client(credentials,
+ identifier: 'identifier',
+ secret: 'secret',
+ httpClient: httpClient, onCredentialsRefreshed: (credentials) {
+ callbackCalled = true;
+ expect(credentials.accessToken, equals('new access token'));
+ });
+
+ httpClient.expectRequest(
+ (request) => Future.value(
+ http.Response(
+ jsonEncode(
+ {'access_token': 'new access token', 'token_type': 'bearer'},
+ ),
+ 200,
+ headers: {'content-type': 'application/json'},
+ ),
+ ),
+ );
+
+ httpClient.expectRequest(
+ (request) => Future.value(http.Response('good job', 200)));
+
+ await client.read(requestUri);
+ expect(callbackCalled, equals(true));
+ });
+ });
+
+ group('with valid credentials', () {
+ test('sends a request with bearer authorization', () {
+ var credentials = oauth2.Credentials('access token');
+ var client = oauth2.Client(credentials,
+ identifier: 'identifier', secret: 'secret', httpClient: httpClient);
+
+ httpClient.expectRequest((request) {
+ expect(request.method, equals('GET'));
+ expect(request.url.toString(), equals(requestUri.toString()));
+ expect(request.headers['authorization'], equals('Bearer access token'));
+
+ return Future.value(http.Response('good job', 200));
+ });
+
+ expect(client.read(requestUri), completion(equals('good job')));
+ });
+
+ test('can manually refresh the credentials', () async {
+ var credentials = oauth2.Credentials('access token',
+ refreshToken: 'refresh token', tokenEndpoint: tokenEndpoint);
+ var client = oauth2.Client(credentials,
+ identifier: 'identifier', secret: 'secret', httpClient: httpClient);
+
+ httpClient.expectRequest((request) {
+ expect(request.method, equals('POST'));
+ expect(request.url.toString(), equals(tokenEndpoint.toString()));
+ return Future.value(http.Response(
+ jsonEncode(
+ {'access_token': 'new access token', 'token_type': 'bearer'}),
+ 200,
+ headers: {'content-type': 'application/json'}));
+ });
+
+ await client.refreshCredentials();
+ expect(client.credentials.accessToken, equals('new access token'));
+ });
+
+ test("without a refresh token can't manually refresh the credentials", () {
+ var credentials = oauth2.Credentials('access token');
+ var client = oauth2.Client(credentials,
+ identifier: 'identifier', secret: 'secret', httpClient: httpClient);
+
+ expect(client.refreshCredentials(), throwsA(isStateError));
+ });
+ });
+
+ group('with invalid credentials', () {
+ test('throws an AuthorizationException for a 401 response', () {
+ var credentials = oauth2.Credentials('access token');
+ var client = oauth2.Client(credentials,
+ identifier: 'identifier', secret: 'secret', httpClient: httpClient);
+
+ httpClient.expectRequest((request) {
+ expect(request.method, equals('GET'));
+ expect(request.url.toString(), equals(requestUri.toString()));
+ expect(request.headers['authorization'], equals('Bearer access token'));
+
+ var authenticate = 'Bearer error="invalid_token", error_description='
+ '"Something is terribly wrong."';
+ return Future.value(http.Response('bad job', 401,
+ headers: {'www-authenticate': authenticate}));
+ });
+
+ expect(client.read(requestUri),
+ throwsA(const TypeMatcher<oauth2.AuthorizationException>()));
+ });
+
+ test('passes through a 401 response without www-authenticate', () async {
+ var credentials = oauth2.Credentials('access token');
+ var client = oauth2.Client(credentials,
+ identifier: 'identifier', secret: 'secret', httpClient: httpClient);
+
+ httpClient.expectRequest((request) {
+ expect(request.method, equals('GET'));
+ expect(request.url.toString(), equals(requestUri.toString()));
+ expect(request.headers['authorization'], equals('Bearer access token'));
+
+ return Future.value(http.Response('bad job', 401));
+ });
+
+ expect((await client.get(requestUri)).statusCode, equals(401));
+ });
+
+ test('passes through a 401 response with invalid www-authenticate',
+ () async {
+ var credentials = oauth2.Credentials('access token');
+ var client = oauth2.Client(credentials,
+ identifier: 'identifier', secret: 'secret', httpClient: httpClient);
+
+ httpClient.expectRequest((request) {
+ expect(request.method, equals('GET'));
+ expect(request.url.toString(), equals(requestUri.toString()));
+ expect(request.headers['authorization'], equals('Bearer access token'));
+
+ var authenticate = 'Bearer error="invalid_token" error_description='
+ '"Something is terribly wrong."';
+ return Future.value(http.Response('bad job', 401,
+ headers: {'www-authenticate': authenticate}));
+ });
+
+ expect((await client.get(requestUri)).statusCode, equals(401));
+ });
+
+ test('passes through a 401 response with non-bearer www-authenticate',
+ () async {
+ var credentials = oauth2.Credentials('access token');
+ var client = oauth2.Client(credentials,
+ identifier: 'identifier', secret: 'secret', httpClient: httpClient);
+
+ httpClient.expectRequest((request) {
+ expect(request.method, equals('GET'));
+ expect(request.url.toString(), equals(requestUri.toString()));
+ expect(request.headers['authorization'], equals('Bearer access token'));
+
+ return Future.value(http.Response('bad job', 401,
+ headers: {'www-authenticate': 'Digest'}));
+ });
+
+ expect((await client.get(requestUri)).statusCode, equals(401));
+ });
+
+ test('passes through a 401 response with non-OAuth2 www-authenticate',
+ () async {
+ var credentials = oauth2.Credentials('access token');
+ var client = oauth2.Client(credentials,
+ identifier: 'identifier', secret: 'secret', httpClient: httpClient);
+
+ httpClient.expectRequest((request) {
+ expect(request.method, equals('GET'));
+ expect(request.url.toString(), equals(requestUri.toString()));
+ expect(request.headers['authorization'], equals('Bearer access token'));
+
+ return Future.value(http.Response('bad job', 401,
+ headers: {'www-authenticate': 'Bearer'}));
+ });
+
+ expect((await client.get(requestUri)).statusCode, equals(401));
+ });
+ });
+}
diff --git a/pkgs/oauth2/test/credentials_test.dart b/pkgs/oauth2/test/credentials_test.dart
new file mode 100644
index 0000000..d83bc7e
--- /dev/null
+++ b/pkgs/oauth2/test/credentials_test.dart
@@ -0,0 +1,331 @@
+// 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:async';
+import 'dart:convert';
+
+import 'package:http/http.dart' as http;
+import 'package:oauth2/oauth2.dart' as oauth2;
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+final Uri tokenEndpoint = Uri.parse('http://example.com/token');
+
+void main() {
+ late ExpectClient httpClient;
+
+ setUp(() => httpClient = ExpectClient());
+
+ test('is not expired if no expiration exists', () {
+ var credentials = oauth2.Credentials('access token');
+ expect(credentials.isExpired, isFalse);
+ });
+
+ test('is not expired if the expiration is in the future', () {
+ var expiration = DateTime.now().add(const Duration(hours: 1));
+ var credentials =
+ oauth2.Credentials('access token', expiration: expiration);
+ expect(credentials.isExpired, isFalse);
+ });
+
+ test('is expired if the expiration is in the past', () {
+ var expiration = DateTime.now().subtract(const Duration(hours: 1));
+ var credentials =
+ oauth2.Credentials('access token', expiration: expiration);
+ expect(credentials.isExpired, isTrue);
+ });
+
+ test("can't refresh without a refresh token", () {
+ var credentials =
+ oauth2.Credentials('access token', tokenEndpoint: tokenEndpoint);
+ expect(credentials.canRefresh, false);
+
+ expect(
+ credentials.refresh(
+ identifier: 'identifier', secret: 'secret', httpClient: httpClient),
+ throwsStateError);
+ });
+
+ test("can't refresh without a token endpoint", () {
+ var credentials =
+ oauth2.Credentials('access token', refreshToken: 'refresh token');
+ expect(credentials.canRefresh, false);
+
+ expect(
+ credentials.refresh(
+ identifier: 'identifier', secret: 'secret', httpClient: httpClient),
+ throwsStateError);
+ });
+
+ test('can refresh with a refresh token and a token endpoint', () async {
+ var credentials = oauth2.Credentials('access token',
+ refreshToken: 'refresh token',
+ tokenEndpoint: tokenEndpoint,
+ scopes: ['scope1', 'scope2']);
+ expect(credentials.canRefresh, true);
+
+ httpClient.expectRequest((request) {
+ expect(request.method, equals('POST'));
+ expect(request.url.toString(), equals(tokenEndpoint.toString()));
+ expect(
+ request.bodyFields,
+ equals({
+ 'grant_type': 'refresh_token',
+ 'refresh_token': 'refresh token',
+ 'scope': 'scope1 scope2'
+ }));
+ expect(
+ request.headers,
+ containsPair('Authorization',
+ 'Basic aWQlQzMlQUJudCVDNCVBQmZpZXI6cyVDMyVBQmNyZXQ='));
+
+ return Future.value(http.Response(
+ jsonEncode({
+ 'access_token': 'new access token',
+ 'token_type': 'bearer',
+ 'refresh_token': 'new refresh token'
+ }),
+ 200,
+ headers: {'content-type': 'application/json'}));
+ });
+
+ credentials = await credentials.refresh(
+ identifier: 'idëntīfier', secret: 'sëcret', httpClient: httpClient);
+ expect(credentials.accessToken, equals('new access token'));
+ expect(credentials.refreshToken, equals('new refresh token'));
+ });
+
+ test('sets proper scope string when using custom delimiter', () async {
+ var credentials = oauth2.Credentials('access token',
+ refreshToken: 'refresh token',
+ tokenEndpoint: tokenEndpoint,
+ scopes: ['scope1', 'scope2'],
+ delimiter: ',');
+ httpClient.expectRequest((http.Request request) {
+ expect(request.bodyFields['scope'], equals('scope1,scope2'));
+ return Future.value(http.Response(
+ jsonEncode({
+ 'access_token': 'new access token',
+ 'token_type': 'bearer',
+ 'refresh_token': 'new refresh token'
+ }),
+ 200,
+ headers: {'content-type': 'application/json'}));
+ });
+ await credentials.refresh(
+ identifier: 'idëntīfier', secret: 'sëcret', httpClient: httpClient);
+ });
+
+ test('can refresh without a client secret', () async {
+ var credentials = oauth2.Credentials('access token',
+ refreshToken: 'refresh token',
+ tokenEndpoint: tokenEndpoint,
+ scopes: ['scope1', 'scope2']);
+ expect(credentials.canRefresh, true);
+
+ httpClient.expectRequest((request) {
+ expect(request.method, equals('POST'));
+ expect(request.url.toString(), equals(tokenEndpoint.toString()));
+ expect(
+ request.bodyFields,
+ equals({
+ 'grant_type': 'refresh_token',
+ 'refresh_token': 'refresh token',
+ 'scope': 'scope1 scope2',
+ 'client_id': 'identifier'
+ }));
+
+ return Future.value(http.Response(
+ jsonEncode({
+ 'access_token': 'new access token',
+ 'token_type': 'bearer',
+ 'refresh_token': 'new refresh token'
+ }),
+ 200,
+ headers: {'content-type': 'application/json'}));
+ });
+
+ credentials = await credentials.refresh(
+ identifier: 'identifier', httpClient: httpClient);
+ expect(credentials.accessToken, equals('new access token'));
+ expect(credentials.refreshToken, equals('new refresh token'));
+ });
+
+ test('can refresh without client authentication', () async {
+ var credentials = oauth2.Credentials('access token',
+ refreshToken: 'refresh token',
+ tokenEndpoint: tokenEndpoint,
+ scopes: ['scope1', 'scope2']);
+ expect(credentials.canRefresh, true);
+
+ httpClient.expectRequest((request) {
+ expect(request.method, equals('POST'));
+ expect(request.url.toString(), equals(tokenEndpoint.toString()));
+ expect(
+ request.bodyFields,
+ equals({
+ 'grant_type': 'refresh_token',
+ 'refresh_token': 'refresh token',
+ 'scope': 'scope1 scope2'
+ }));
+
+ return Future.value(http.Response(
+ jsonEncode({
+ 'access_token': 'new access token',
+ 'token_type': 'bearer',
+ 'refresh_token': 'new refresh token'
+ }),
+ 200,
+ headers: {'content-type': 'application/json'}));
+ });
+
+ credentials = await credentials.refresh(httpClient: httpClient);
+ expect(credentials.accessToken, equals('new access token'));
+ expect(credentials.refreshToken, equals('new refresh token'));
+ });
+
+ test("uses the old refresh token if a new one isn't provided", () async {
+ var credentials = oauth2.Credentials('access token',
+ refreshToken: 'refresh token', tokenEndpoint: tokenEndpoint);
+ expect(credentials.canRefresh, true);
+
+ httpClient.expectRequest((request) {
+ expect(request.method, equals('POST'));
+ expect(request.url.toString(), equals(tokenEndpoint.toString()));
+ expect(
+ request.bodyFields,
+ equals({
+ 'grant_type': 'refresh_token',
+ 'refresh_token': 'refresh token'
+ }));
+ expect(
+ request.headers,
+ containsPair('Authorization',
+ 'Basic aWQlQzMlQUJudCVDNCVBQmZpZXI6cyVDMyVBQmNyZXQ='));
+
+ return Future.value(http.Response(
+ jsonEncode(
+ {'access_token': 'new access token', 'token_type': 'bearer'}),
+ 200,
+ headers: {'content-type': 'application/json'}));
+ });
+
+ credentials = await credentials.refresh(
+ identifier: 'idëntīfier', secret: 'sëcret', httpClient: httpClient);
+ expect(credentials.accessToken, equals('new access token'));
+ expect(credentials.refreshToken, equals('refresh token'));
+ });
+
+ test('uses form-field authentication if basicAuth is false', () async {
+ var credentials = oauth2.Credentials('access token',
+ refreshToken: 'refresh token',
+ tokenEndpoint: tokenEndpoint,
+ scopes: ['scope1', 'scope2']);
+ expect(credentials.canRefresh, true);
+
+ httpClient.expectRequest((request) {
+ expect(request.method, equals('POST'));
+ expect(request.url.toString(), equals(tokenEndpoint.toString()));
+ expect(
+ request.bodyFields,
+ equals({
+ 'grant_type': 'refresh_token',
+ 'refresh_token': 'refresh token',
+ 'scope': 'scope1 scope2',
+ 'client_id': 'idëntīfier',
+ 'client_secret': 'sëcret'
+ }));
+
+ return Future.value(http.Response(
+ jsonEncode({
+ 'access_token': 'new access token',
+ 'token_type': 'bearer',
+ 'refresh_token': 'new refresh token'
+ }),
+ 200,
+ headers: {'content-type': 'application/json'}));
+ });
+
+ credentials = await credentials.refresh(
+ identifier: 'idëntīfier',
+ secret: 'sëcret',
+ basicAuth: false,
+ httpClient: httpClient);
+ expect(credentials.accessToken, equals('new access token'));
+ expect(credentials.refreshToken, equals('new refresh token'));
+ });
+
+ group('fromJson', () {
+ oauth2.Credentials fromMap(Map<String, dynamic> map) =>
+ oauth2.Credentials.fromJson(jsonEncode(map));
+
+ test('should load the same credentials from toJson', () {
+ // Round the expiration down to milliseconds since epoch, since that's
+ // what the credentials file stores. Otherwise sub-millisecond time gets
+ // in the way.
+ var expiration = DateTime.now().subtract(const Duration(hours: 1));
+ expiration = DateTime.fromMillisecondsSinceEpoch(
+ expiration.millisecondsSinceEpoch);
+
+ var credentials = oauth2.Credentials('access token',
+ refreshToken: 'refresh token',
+ idToken: 'id token',
+ tokenEndpoint: tokenEndpoint,
+ scopes: ['scope1', 'scope2'],
+ expiration: expiration);
+ var reloaded = oauth2.Credentials.fromJson(credentials.toJson());
+
+ expect(reloaded.accessToken, equals(credentials.accessToken));
+ expect(reloaded.refreshToken, equals(credentials.refreshToken));
+ expect(reloaded.idToken, equals(credentials.idToken));
+ expect(reloaded.tokenEndpoint.toString(),
+ equals(credentials.tokenEndpoint.toString()));
+ expect(reloaded.scopes, equals(credentials.scopes));
+ expect(reloaded.expiration, equals(credentials.expiration));
+ });
+
+ test('should throw a FormatException for invalid JSON', () {
+ expect(
+ () => oauth2.Credentials.fromJson('foo bar'), throwsFormatException);
+ });
+
+ test("should throw a FormatException for JSON that's not a map", () {
+ expect(() => oauth2.Credentials.fromJson('null'), throwsFormatException);
+ });
+
+ test('should throw a FormatException if there is no accessToken', () {
+ expect(() => fromMap({}), throwsFormatException);
+ });
+
+ test('should throw a FormatException if accessToken is not a string', () {
+ expect(() => fromMap({'accessToken': 12}), throwsFormatException);
+ });
+
+ test('should throw a FormatException if refreshToken is not a string', () {
+ expect(() => fromMap({'accessToken': 'foo', 'refreshToken': 12}),
+ throwsFormatException);
+ });
+
+ test('should throw a FormatException if idToken is not a string', () {
+ expect(() => fromMap({'accessToken': 'foo', 'idToken': 12}),
+ throwsFormatException);
+ });
+
+ test('should throw a FormatException if tokenEndpoint is not a string', () {
+ expect(() => fromMap({'accessToken': 'foo', 'tokenEndpoint': 12}),
+ throwsFormatException);
+ });
+
+ test('should throw a FormatException if scopes is not a list', () {
+ expect(() => fromMap({'accessToken': 'foo', 'scopes': 12}),
+ throwsFormatException);
+ });
+
+ test('should throw a FormatException if expiration is not an int', () {
+ expect(() => fromMap({'accessToken': 'foo', 'expiration': '12'}),
+ throwsFormatException);
+ });
+ });
+}
diff --git a/pkgs/oauth2/test/handle_access_token_response_test.dart b/pkgs/oauth2/test/handle_access_token_response_test.dart
new file mode 100644
index 0000000..4d7b519
--- /dev/null
+++ b/pkgs/oauth2/test/handle_access_token_response_test.dart
@@ -0,0 +1,301 @@
+// 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:convert';
+
+import 'package:http/http.dart' as http;
+import 'package:oauth2/oauth2.dart' as oauth2;
+import 'package:oauth2/src/handle_access_token_response.dart';
+import 'package:oauth2/src/parameters.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+final Uri tokenEndpoint = Uri.parse('https://example.com/token');
+
+final DateTime startTime = DateTime.now();
+
+oauth2.Credentials handle(http.Response response,
+ {GetParameters? getParameters}) =>
+ handleAccessTokenResponse(
+ response, tokenEndpoint, startTime, ['scope'], ' ',
+ getParameters: getParameters);
+
+void main() {
+ group('an error response', () {
+ oauth2.Credentials handleError(
+ {String body = '{"error": "invalid_request"}',
+ int statusCode = 400,
+ Map<String, String> headers = const {
+ 'content-type': 'application/json'
+ }}) =>
+ handle(http.Response(body, statusCode, headers: headers));
+
+ test('causes an AuthorizationException', () {
+ expect(handleError, throwsAuthorizationException);
+ });
+
+ test('with a 401 code causes an AuthorizationException', () {
+ expect(() => handleError(statusCode: 401), throwsAuthorizationException);
+ });
+
+ test('with an unexpected code causes a FormatException', () {
+ expect(() => handleError(statusCode: 500), throwsFormatException);
+ });
+
+ test('with no content-type causes a FormatException', () {
+ expect(() => handleError(headers: {}), throwsFormatException);
+ });
+
+ test('with a non-JSON content-type causes a FormatException', () {
+ expect(() => handleError(headers: {'content-type': 'text/plain'}),
+ throwsFormatException);
+ });
+
+ test('with a non-JSON, non-plain content-type causes a FormatException',
+ () {
+ expect(() => handleError(headers: {'content-type': 'image/png'}),
+ throwsFormatException);
+ });
+
+ test(
+ 'with a JSON content-type and charset causes an '
+ 'AuthorizationException', () {
+ expect(
+ () => handleError(
+ headers: {'content-type': 'application/json; charset=UTF-8'}),
+ throwsAuthorizationException);
+ });
+
+ test('with invalid JSON causes a FormatException', () {
+ expect(() => handleError(body: 'not json'), throwsFormatException);
+ });
+
+ test('with a non-string error causes a FormatException', () {
+ expect(() => handleError(body: '{"error": 12}'), throwsFormatException);
+ });
+
+ test('with a non-string error_description causes a FormatException', () {
+ expect(
+ () => handleError(
+ body: jsonEncode(
+ {'error': 'invalid_request', 'error_description': 12})),
+ throwsFormatException);
+ });
+
+ test('with a non-string error_uri causes a FormatException', () {
+ expect(
+ () => handleError(
+ body: jsonEncode({'error': 'invalid_request', 'error_uri': 12})),
+ throwsFormatException);
+ });
+
+ test('with a string error_description causes a AuthorizationException', () {
+ expect(
+ () => handleError(
+ body: jsonEncode({
+ 'error': 'invalid_request',
+ 'error_description': 'description'
+ })),
+ throwsAuthorizationException);
+ });
+
+ test('with a string error_uri causes a AuthorizationException', () {
+ expect(
+ () => handleError(
+ body: jsonEncode({
+ 'error': 'invalid_request',
+ 'error_uri': 'http://example.com/error'
+ })),
+ throwsAuthorizationException);
+ });
+ });
+
+ group('a success response', () {
+ oauth2.Credentials handleSuccess(
+ {String contentType = 'application/json',
+ Object? accessToken = 'access token',
+ Object? tokenType = 'bearer',
+ Object? expiresIn,
+ Object? refreshToken,
+ Object? scope}) {
+ return handle(http.Response(
+ jsonEncode({
+ 'access_token': accessToken,
+ 'token_type': tokenType,
+ 'expires_in': expiresIn,
+ 'refresh_token': refreshToken,
+ 'scope': scope
+ }),
+ 200,
+ headers: {'content-type': contentType}));
+ }
+
+ test('returns the correct credentials', () {
+ var credentials = handleSuccess();
+ expect(credentials.accessToken, equals('access token'));
+ expect(credentials.tokenEndpoint.toString(),
+ equals(tokenEndpoint.toString()));
+ });
+
+ test('with no content-type causes a FormatException', () {
+ expect(() => handleSuccess(contentType: ''), throwsFormatException);
+ });
+
+ test('with a non-JSON content-type causes a FormatException', () {
+ expect(() => handleSuccess(contentType: 'text/plain'),
+ throwsFormatException);
+ });
+
+ test(
+ 'with a JSON content-type and charset returns the correct '
+ 'credentials', () {
+ var credentials =
+ handleSuccess(contentType: 'application/json; charset=UTF-8');
+ expect(credentials.accessToken, equals('access token'));
+ });
+
+ test('with a JavScript content-type returns the correct credentials', () {
+ var credentials = handleSuccess(contentType: 'text/javascript');
+ expect(credentials.accessToken, equals('access token'));
+ });
+
+ test('with custom getParameters() returns the correct credentials', () {
+ var body = '_${jsonEncode({
+ 'token_type': 'bearer',
+ 'access_token': 'access token'
+ })}';
+ var credentials = handle(
+ http.Response(body, 200, headers: {'content-type': 'text/plain'}),
+ getParameters: (contentType, body) =>
+ jsonDecode(body.substring(1)) as Map<String, dynamic>,
+ );
+ expect(credentials.accessToken, equals('access token'));
+ expect(credentials.tokenEndpoint.toString(),
+ equals(tokenEndpoint.toString()));
+ });
+
+ test('throws a FormatException if custom getParameters rejects response',
+ () {
+ var response = http.Response(
+ jsonEncode({
+ 'access_token': 'access token',
+ 'token_type': 'bearer',
+ 'expires_in': 24,
+ 'refresh_token': 'refresh token',
+ 'scope': 'scope',
+ }),
+ 200,
+ headers: {'content-type': 'foo/bar'});
+
+ expect(
+ () => handle(response,
+ getParameters: (contentType, body) => throw FormatException(
+ 'unsupported content-type: $contentType')),
+ throwsFormatException);
+ });
+
+ test('with a null access token throws a FormatException', () {
+ expect(() => handleSuccess(accessToken: null), throwsFormatException);
+ });
+
+ test('with a non-string access token throws a FormatException', () {
+ expect(() => handleSuccess(accessToken: 12), throwsFormatException);
+ });
+
+ test('with a null token type throws a FormatException', () {
+ expect(() => handleSuccess(tokenType: null), throwsFormatException);
+ });
+
+ test('with a non-string token type throws a FormatException', () {
+ expect(() => handleSuccess(tokenType: 12), throwsFormatException);
+ });
+
+ test('with a non-"bearer" token type throws a FormatException', () {
+ expect(() => handleSuccess(tokenType: 'mac'), throwsFormatException);
+ });
+
+ test('with a non-int expires-in throws a FormatException', () {
+ expect(() => handleSuccess(expiresIn: 'whenever'), throwsFormatException);
+ });
+
+ test(
+ 'with expires-in sets the expiration to ten seconds earlier than the '
+ 'server says', () {
+ var credentials = handleSuccess(expiresIn: 100);
+ expect(credentials.expiration?.millisecondsSinceEpoch,
+ startTime.millisecondsSinceEpoch + 90 * 1000);
+ });
+
+ test('with expires-in encoded as string', () {
+ var credentials = handleSuccess(expiresIn: '110');
+ expect(credentials.expiration?.millisecondsSinceEpoch,
+ startTime.millisecondsSinceEpoch + 100 * 1000);
+ });
+
+ test('with a non-string refresh token throws a FormatException', () {
+ expect(() => handleSuccess(refreshToken: 12), throwsFormatException);
+ });
+
+ test('with a refresh token sets the refresh token', () {
+ var credentials = handleSuccess(refreshToken: 'refresh me');
+ expect(credentials.refreshToken, equals('refresh me'));
+ });
+
+ test('with a non-string scope throws a FormatException', () {
+ expect(() => handleSuccess(scope: 12), throwsFormatException);
+ });
+
+ test('with a scope sets the scopes', () {
+ var credentials = handleSuccess(scope: 'scope1 scope2');
+ expect(credentials.scopes, equals(['scope1', 'scope2']));
+ });
+
+ test('with a custom scope delimiter sets the scopes', () {
+ var response = http.Response(
+ jsonEncode({
+ 'access_token': 'access token',
+ 'token_type': 'bearer',
+ 'expires_in': null,
+ 'refresh_token': null,
+ 'scope': 'scope1,scope2'
+ }),
+ 200,
+ headers: {'content-type': 'application/json'});
+ var credentials = handleAccessTokenResponse(
+ response, tokenEndpoint, startTime, ['scope'], ',');
+ expect(credentials.scopes, equals(['scope1', 'scope2']));
+ });
+ });
+
+ group('a success response with a id_token', () {
+ oauth2.Credentials handleSuccess(
+ {String contentType = 'application/json',
+ Object? accessToken = 'access token',
+ Object? tokenType = 'bearer',
+ Object? expiresIn,
+ Object? idToken = 'decode me',
+ Object? scope}) {
+ return handle(http.Response(
+ jsonEncode({
+ 'access_token': accessToken,
+ 'token_type': tokenType,
+ 'expires_in': expiresIn,
+ 'id_token': idToken,
+ 'scope': scope
+ }),
+ 200,
+ headers: {'content-type': contentType}));
+ }
+
+ test('with a non-string id token throws a FormatException', () {
+ expect(() => handleSuccess(idToken: 12), throwsFormatException);
+ });
+
+ test('with a id token sets the id token', () {
+ var credentials = handleSuccess(idToken: 'decode me');
+ expect(credentials.idToken, equals('decode me'));
+ });
+ });
+}
diff --git a/pkgs/oauth2/test/resource_owner_password_grant_test.dart b/pkgs/oauth2/test/resource_owner_password_grant_test.dart
new file mode 100644
index 0000000..7a5d9b5
--- /dev/null
+++ b/pkgs/oauth2/test/resource_owner_password_grant_test.dart
@@ -0,0 +1,168 @@
+// 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.
+
+@TestOn('vm')
+library;
+
+import 'dart:async';
+import 'dart:convert';
+
+import 'package:http/http.dart' as http;
+import 'package:oauth2/oauth2.dart' as oauth2;
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+final success = jsonEncode({
+ 'access_token': '2YotnFZFEjr1zCsicMWpAA',
+ 'token_type': 'bearer',
+ 'expires_in': 3600,
+ 'refresh_token': 'tGzv3JOkF0XG5Qx2TlKWIA',
+});
+
+String auth = 'Basic Y2xpZW50OnNlY3JldA==';
+Uri authEndpoint = Uri.parse('https://example.com');
+
+void main() {
+ late ExpectClient expectClient;
+
+ setUp(() => expectClient = ExpectClient());
+
+ group('basic', () {
+ test('builds correct request with client when using basic auth for client',
+ () async {
+ expectClient.expectRequest((request) async {
+ expect(auth, equals(request.headers['authorization']));
+ expect(request.bodyFields['grant_type'], equals('password'));
+ expect(request.bodyFields['username'], equals('username'));
+ expect(request.bodyFields['password'], equals('userpass'));
+ return http.Response(success, 200,
+ headers: {'content-type': 'application/json'});
+ });
+
+ var client = await oauth2.resourceOwnerPasswordGrant(
+ authEndpoint, 'username', 'userpass',
+ identifier: 'client', secret: 'secret', httpClient: expectClient);
+
+ expect(client.credentials, isNotNull);
+ expect(client.credentials.accessToken, equals('2YotnFZFEjr1zCsicMWpAA'));
+ });
+
+ test('passes the onCredentialsRefreshed callback to the client', () async {
+ expectClient.expectRequest((request) async {
+ return http.Response(
+ jsonEncode({
+ 'access_token': '2YotnFZFEjr1zCsicMWpAA',
+ 'token_type': 'bearer',
+ 'expires_in': -3600,
+ 'refresh_token': 'tGzv3JOkF0XG5Qx2TlKWIA',
+ }),
+ 200,
+ headers: {'content-type': 'application/json'});
+ });
+
+ var isCallbackInvoked = false;
+
+ var client = await oauth2.resourceOwnerPasswordGrant(
+ authEndpoint, 'username', 'userpass',
+ identifier: 'client', secret: 'secret', httpClient: expectClient,
+ onCredentialsRefreshed: (oauth2.Credentials credentials) {
+ isCallbackInvoked = true;
+ });
+
+ expectClient.expectRequest((request) {
+ return Future.value(http.Response(
+ jsonEncode(
+ {'access_token': 'new access token', 'token_type': 'bearer'}),
+ 200,
+ headers: {'content-type': 'application/json'}));
+ });
+
+ expectClient.expectRequest((request) {
+ return Future.value(http.Response('good job', 200));
+ });
+
+ await client.read(Uri.parse('http://example.com/resource'));
+ expect(isCallbackInvoked, equals(true));
+ });
+
+ test('builds correct request when using query parameters for client',
+ () async {
+ expectClient.expectRequest((request) async {
+ expect(request.bodyFields['grant_type'], equals('password'));
+ expect(request.bodyFields['client_id'], equals('client'));
+ expect(request.bodyFields['client_secret'], equals('secret'));
+ expect(request.bodyFields['username'], equals('username'));
+ expect(request.bodyFields['password'], equals('userpass'));
+ return http.Response(success, 200,
+ headers: {'content-type': 'application/json'});
+ });
+
+ var client = await oauth2.resourceOwnerPasswordGrant(
+ authEndpoint, 'username', 'userpass',
+ identifier: 'client',
+ secret: 'secret',
+ basicAuth: false,
+ httpClient: expectClient);
+ expect(client.credentials, isNotNull);
+ expect(client.credentials.accessToken, equals('2YotnFZFEjr1zCsicMWpAA'));
+ });
+
+ test('builds correct request using scope', () async {
+ expectClient.expectRequest((request) async {
+ expect(request.bodyFields['grant_type'], equals('password'));
+ expect(request.bodyFields['username'], equals('username'));
+ expect(request.bodyFields['password'], equals('userpass'));
+ expect(request.bodyFields['scope'], equals('one two'));
+ return http.Response(success, 200,
+ headers: {'content-type': 'application/json'});
+ });
+
+ var client = await oauth2.resourceOwnerPasswordGrant(
+ authEndpoint, 'username', 'userpass',
+ scopes: ['one', 'two'], httpClient: expectClient);
+ expect(client.credentials, isNotNull);
+ expect(client.credentials.accessToken, equals('2YotnFZFEjr1zCsicMWpAA'));
+ });
+
+ test('builds correct request using scope with custom delimiter', () async {
+ expectClient.expectRequest((request) async {
+ expect(request.bodyFields['grant_type'], equals('password'));
+ expect(request.bodyFields['username'], equals('username'));
+ expect(request.bodyFields['password'], equals('userpass'));
+ expect(request.bodyFields['scope'], equals('one,two'));
+ return http.Response(success, 200,
+ headers: {'content-type': 'application/json'});
+ });
+
+ await oauth2.resourceOwnerPasswordGrant(
+ authEndpoint, 'username', 'userpass',
+ scopes: ['one', 'two'], httpClient: expectClient, delimiter: ',');
+ });
+
+ test('merges with existing query parameters', () async {
+ var authEndpoint = Uri.parse('https://example.com?query=value');
+
+ expectClient.expectRequest((request) async {
+ expect(request.bodyFields['grant_type'], equals('password'));
+ expect(request.bodyFields['client_id'], equals('client'));
+ expect(request.bodyFields['client_secret'], equals('secret'));
+ expect(request.bodyFields['username'], equals('username'));
+ expect(request.bodyFields['password'], equals('userpass'));
+ expect(request.url.queryParameters['query'], equals('value'));
+ return http.Response(success, 200,
+ headers: {'content-type': 'application/json'});
+ });
+
+ var client = await oauth2.resourceOwnerPasswordGrant(
+ authEndpoint, 'username', 'userpass',
+ identifier: 'client',
+ secret: 'secret',
+ basicAuth: false,
+ httpClient: expectClient);
+ expect(client.credentials, isNotNull);
+ expect(client.credentials.accessToken, equals('2YotnFZFEjr1zCsicMWpAA'));
+ });
+ });
+}
diff --git a/pkgs/oauth2/test/utils.dart b/pkgs/oauth2/test/utils.dart
new file mode 100644
index 0000000..4f3b747
--- /dev/null
+++ b/pkgs/oauth2/test/utils.dart
@@ -0,0 +1,49 @@
+// 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:async';
+import 'dart:collection' show Queue;
+
+import 'package:http/http.dart' as http;
+import 'package:http/testing.dart';
+import 'package:oauth2/oauth2.dart' as oauth2;
+import 'package:test/test.dart';
+
+class ExpectClient extends MockClient {
+ final Queue<MockClientHandler> _handlers;
+
+ ExpectClient._(super.fn) : _handlers = Queue<MockClientHandler>();
+
+ factory ExpectClient() {
+ late ExpectClient client;
+ client = ExpectClient._((request) => client._handleRequest(request));
+ return client;
+ }
+
+ void expectRequest(MockClientHandler fn) {
+ var completer = Completer<void>();
+ expect(completer.future, completes);
+
+ _handlers.add((request) {
+ completer.complete(null);
+ return fn(request);
+ });
+ }
+
+ Future<http.Response> _handleRequest(http.Request request) {
+ if (_handlers.isEmpty) {
+ return Future.value(http.Response('not found', 404));
+ } else {
+ return _handlers.removeFirst()(request);
+ }
+ }
+}
+
+/// A matcher for functions that throw AuthorizationException.
+final Matcher throwsAuthorizationException =
+ throwsA(const TypeMatcher<oauth2.AuthorizationException>());
+
+/// A matcher for functions that throw ExpirationException.
+final Matcher throwsExpirationException =
+ throwsA(const TypeMatcher<oauth2.ExpirationException>());
diff --git a/pkgs/package_config/.gitignore b/pkgs/package_config/.gitignore
new file mode 100644
index 0000000..7b888b8
--- /dev/null
+++ b/pkgs/package_config/.gitignore
@@ -0,0 +1,7 @@
+.packages
+.pub
+.dart_tool/
+.vscode/
+packages
+pubspec.lock
+doc/api/
diff --git a/pkgs/package_config/AUTHORS b/pkgs/package_config/AUTHORS
new file mode 100644
index 0000000..e8063a8
--- /dev/null
+++ b/pkgs/package_config/AUTHORS
@@ -0,0 +1,6 @@
+# Below is a list of people and organizations that have contributed
+# to the project. Names should be added to the list like so:
+#
+# Name/Organization <email address>
+
+Google Inc.
diff --git a/pkgs/package_config/CHANGELOG.md b/pkgs/package_config/CHANGELOG.md
new file mode 100644
index 0000000..101a0fe
--- /dev/null
+++ b/pkgs/package_config/CHANGELOG.md
@@ -0,0 +1,108 @@
+## 2.1.1
+
+- Require Dart 3.4
+- Move to `dart-lang/tools` monorepo.
+
+## 2.1.0
+
+- Adds `minVersion` to `findPackageConfig` and `findPackageConfigVersion`
+ which allows ignoring earlier versions (which currently only means
+ ignoring version 1, aka. `.packages` files.)
+
+- Changes the version number of `SimplePackageConfig.empty` to the
+ current maximum version.
+
+- Improve file read performance; improve lookup performance.
+- Emit an error when a package is inside the package root of another package.
+- Fix a link in the readme.
+
+## 2.0.2
+
+- Update package description and README.
+- Change to package:lints for style checking.
+- Add an example.
+
+## 2.0.1
+
+- Use unique library names to correct docs issue.
+
+## 2.0.0
+
+- Migrate to null safety.
+- Remove legacy APIs.
+- Adds `relativeRoot` property to `Package` which controls whether to
+ make the root URI relative when writing a configuration file.
+
+## 1.9.3
+
+- Fix `Package` constructor not accepting relative `packageUriRoot`.
+
+## 1.9.2
+
+- Updated to support new rules for picking `package_config.json` over
+ a specified `.packages`.
+- Deduce package root from `.packages` derived package configuration,
+ and default all such packages to language version 2.7.
+
+## 1.9.1
+
+- Remove accidental transitive import of `dart:io` from entrypoints that are
+ supposed to be cross-platform compatible.
+
+## 1.9.0
+
+- Based on new JSON file format with more content.
+- This version includes all the new functionality intended for a 2.0.0
+ version, as well as the, now deprecated, version 1 functionality.
+ When we release 2.0.0, the deprecated functionality will be removed.
+
+## 1.1.0
+
+- Allow parsing files with default-package entries and metadata.
+ A default-package entry has an empty key and a valid package name
+ as value.
+ Metadata is attached as fragments to base URIs.
+
+## 1.0.5
+
+- Fix usage of SDK constants.
+
+## 1.0.4
+
+- Set max SDK version to <3.0.0.
+
+## 1.0.3
+
+- Removed unneeded dependency constraint on SDK.
+
+## 1.0.2
+
+- Update SDK constraint to be 2.0.0 dev friendly.
+
+## 1.0.1
+
+- Fix test to not write to sink after it's closed.
+
+## 1.0.0
+
+- Public API marked stable.
+
+## 0.1.5
+
+- `FilePackagesDirectoryPackages.getBase(..)` performance improvements.
+
+## 0.1.4
+
+- Strong mode fixes.
+
+## 0.1.3
+
+- Invalid test cleanup (to keep up with changes in `Uri`).
+
+## 0.1.1
+
+- Syntax updates.
+
+## 0.1.0
+
+- Initial implementation.
diff --git a/pkgs/package_config/LICENSE b/pkgs/package_config/LICENSE
new file mode 100644
index 0000000..7670007
--- /dev/null
+++ b/pkgs/package_config/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2019, 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.
diff --git a/pkgs/package_config/README.md b/pkgs/package_config/README.md
new file mode 100644
index 0000000..76fd3cb
--- /dev/null
+++ b/pkgs/package_config/README.md
@@ -0,0 +1,26 @@
+[](https://github.com/dart-lang/tools/actions/workflows/package_config.yaml)
+[](https://pub.dev/packages/package_config)
+[](https://pub.dev/packages/package_config/publisher)
+
+Support for working with **Package Configuration** files as described
+in the Package Configuration v2 [design document](https://github.com/dart-lang/language/blob/master/accepted/2.8/language-versioning/package-config-file-v2.md).
+
+A Dart package configuration file is used to resolve Dart package names (e.g.
+`foobar`) to Dart files containing the source code for that package (e.g.
+`file:///Users/myuser/.pub-cache/hosted/pub.dartlang.org/foobar-1.1.0`). The
+standard package configuration file is `.dart_tool/package_config.json`, and is
+written by the Dart tool when the command `dart pub get` is run.
+
+The primary libraries of this package are
+* `package_config.dart`:
+ Defines the `PackageConfig` class and other types needed to use
+ package configurations, and provides functions to find, read and
+ write package configuration files.
+
+* `package_config_types.dart`:
+ Just the `PackageConfig` class and other types needed to use
+ package configurations. This library does not depend on `dart:io`.
+
+The package includes deprecated backwards compatible functionality to
+work with the `.packages` file. This functionality will not be maintained,
+and will be removed in a future version of this package.
diff --git a/pkgs/package_config/analysis_options.yaml b/pkgs/package_config/analysis_options.yaml
new file mode 100644
index 0000000..c0249e5
--- /dev/null
+++ b/pkgs/package_config/analysis_options.yaml
@@ -0,0 +1,5 @@
+# Copyright (c) 2020, 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.
+
+include: package:dart_flutter_team_lints/analysis_options.yaml
diff --git a/pkgs/package_config/example/main.dart b/pkgs/package_config/example/main.dart
new file mode 100644
index 0000000..db137ca
--- /dev/null
+++ b/pkgs/package_config/example/main.dart
@@ -0,0 +1,19 @@
+// Copyright (c) 2020, 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 Directory;
+
+import 'package:package_config/package_config.dart';
+
+void main() async {
+ var packageConfig = await findPackageConfig(Directory.current);
+ if (packageConfig == null) {
+ print('Failed to locate or read package config.');
+ } else {
+ print('This package depends on ${packageConfig.packages.length} packages:');
+ for (var package in packageConfig.packages) {
+ print('- ${package.name}');
+ }
+ }
+}
diff --git a/pkgs/package_config/lib/package_config.dart b/pkgs/package_config/lib/package_config.dart
new file mode 100644
index 0000000..074c977
--- /dev/null
+++ b/pkgs/package_config/lib/package_config.dart
@@ -0,0 +1,199 @@
+// 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.
+
+/// A package configuration is a way to assign file paths to package URIs,
+/// and vice-versa.
+///
+/// This package provides functionality to find, read and write package
+/// configurations in the [specified format](https://github.com/dart-lang/language/blob/master/accepted/future-releases/language-versioning/package-config-file-v2.md).
+library;
+
+import 'dart:io' show Directory, File;
+import 'dart:typed_data' show Uint8List;
+
+import 'src/discovery.dart' as discover;
+import 'src/errors.dart' show throwError;
+import 'src/package_config.dart';
+import 'src/package_config_io.dart';
+
+export 'package_config_types.dart';
+
+/// Reads a specific package configuration file.
+///
+/// The file must exist and be readable.
+/// It must be either a valid `package_config.json` file
+/// or a valid `.packages` file.
+/// It is considered a `package_config.json` file if its first character
+/// is a `{`.
+///
+/// If the file is a `.packages` file (the file name is `.packages`)
+/// and [preferNewest] is true, the default, also checks if there is
+/// a `.dart_tool/package_config.json` file next
+/// to the original file, and if so, loads that instead.
+/// If [preferNewest] is set to false, a directly specified `.packages` file
+/// is loaded even if there is an available `package_config.json` file.
+/// The caller can determine this from the [PackageConfig.version]
+/// being 1 and look for a `package_config.json` file themselves.
+///
+/// If [onError] is provided, the configuration file parsing will report errors
+/// by calling that function, and then try to recover.
+/// The returned package configuration is a *best effort* attempt to create
+/// a valid configuration from the invalid configuration file.
+/// If no [onError] is provided, errors are thrown immediately.
+Future<PackageConfig> loadPackageConfig(File file,
+ {bool preferNewest = true, void Function(Object error)? onError}) =>
+ readAnyConfigFile(file, preferNewest, onError ?? throwError);
+
+/// Reads a specific package configuration URI.
+///
+/// The file of the URI must exist and be readable.
+/// It must be either a valid `package_config.json` file
+/// or a valid `.packages` file.
+/// It is considered a `package_config.json` file if its first
+/// non-whitespace character is a `{`.
+///
+/// If [preferNewest] is true, the default, and the file is a `.packages` file,
+/// as determined by its file name being `.packages`,
+/// first checks if there is a `.dart_tool/package_config.json` file
+/// next to the original file, and if so, loads that instead.
+/// The [file] *must not* be a `package:` URI.
+/// If [preferNewest] is set to false, a directly specified `.packages` file
+/// is loaded even if there is an available `package_config.json` file.
+/// The caller can determine this from the [PackageConfig.version]
+/// being 1 and look for a `package_config.json` file themselves.
+///
+/// If [loader] is provided, URIs are loaded using that function.
+/// The future returned by the loader must complete with a [Uint8List]
+/// containing the entire file content encoded as UTF-8,
+/// or with `null` if the file does not exist.
+/// The loader may throw at its own discretion, for situations where
+/// it determines that an error might be need user attention,
+/// but it is always allowed to return `null`.
+/// This function makes no attempt to catch such errors.
+/// As such, it may throw any error that [loader] throws.
+///
+/// If no [loader] is supplied, a default loader is used which
+/// only accepts `file:`, `http:` and `https:` URIs,
+/// and which uses the platform file system and HTTP requests to
+/// fetch file content. The default loader never throws because
+/// of an I/O issue, as long as the location URIs are valid.
+/// As such, it does not distinguish between a file not existing,
+/// and it being temporarily locked or unreachable.
+///
+/// If [onError] is provided, the configuration file parsing will report errors
+/// by calling that function, and then try to recover.
+/// The returned package configuration is a *best effort* attempt to create
+/// a valid configuration from the invalid configuration file.
+/// If no [onError] is provided, errors are thrown immediately.
+Future<PackageConfig> loadPackageConfigUri(Uri file,
+ {Future<Uint8List?> Function(Uri uri)? loader,
+ bool preferNewest = true,
+ void Function(Object error)? onError}) =>
+ readAnyConfigFileUri(file, loader, onError ?? throwError, preferNewest);
+
+/// Finds a package configuration relative to [directory].
+///
+/// If [directory] contains a package configuration,
+/// either a `.dart_tool/package_config.json` file or,
+/// if not, a `.packages`, then that file is loaded.
+///
+/// If no file is found in the current directory,
+/// then the parent directories are checked recursively,
+/// all the way to the root directory, to check if those contains
+/// a package configuration.
+/// If [recurse] is set to `false`, this parent directory check is not
+/// performed.
+///
+/// If [onError] is provided, the configuration file parsing will report errors
+/// by calling that function, and then try to recover.
+/// The returned package configuration is a *best effort* attempt to create
+/// a valid configuration from the invalid configuration file.
+/// If no [onError] is provided, errors are thrown immediately.
+///
+/// If [minVersion] is set to something greater than its default,
+/// any lower-version configuration files are ignored in the search.
+///
+/// Returns `null` if no configuration file is found.
+Future<PackageConfig?> findPackageConfig(Directory directory,
+ {bool recurse = true,
+ void Function(Object error)? onError,
+ int minVersion = 1}) {
+ if (minVersion > PackageConfig.maxVersion) {
+ throw ArgumentError.value(minVersion, 'minVersion',
+ 'Maximum known version is ${PackageConfig.maxVersion}');
+ }
+ return discover.findPackageConfig(
+ directory, minVersion, recurse, onError ?? throwError);
+}
+
+/// Finds a package configuration relative to [location].
+///
+/// If [location] contains a package configuration,
+/// either a `.dart_tool/package_config.json` file or,
+/// if not, a `.packages`, then that file is loaded.
+/// The [location] URI *must not* be a `package:` URI.
+/// It should be a hierarchical URI which is supported
+/// by [loader].
+///
+/// If no file is found in the current directory,
+/// then the parent directories are checked recursively,
+/// all the way to the root directory, to check if those contains
+/// a package configuration.
+/// If [recurse] is set to `false`, this parent directory check is not
+/// performed.
+///
+/// If [loader] is provided, URIs are loaded using that function.
+/// The future returned by the loader must complete with a [Uint8List]
+/// containing the entire file content,
+/// or with `null` if the file does not exist.
+/// The loader may throw at its own discretion, for situations where
+/// it determines that an error might be need user attention,
+/// but it is always allowed to return `null`.
+/// This function makes no attempt to catch such errors.
+///
+/// If no [loader] is supplied, a default loader is used which
+/// only accepts `file:`, `http:` and `https:` URIs,
+/// and which uses the platform file system and HTTP requests to
+/// fetch file content. The default loader never throws because
+/// of an I/O issue, as long as the location URIs are valid.
+/// As such, it does not distinguish between a file not existing,
+/// and it being temporarily locked or unreachable.
+///
+/// If [onError] is provided, the configuration file parsing will report errors
+/// by calling that function, and then try to recover.
+/// The returned package configuration is a *best effort* attempt to create
+/// a valid configuration from the invalid configuration file.
+/// If no [onError] is provided, errors are thrown immediately.
+///
+/// If [minVersion] is set to something greater than its default,
+/// any lower-version configuration files are ignored in the search.
+///
+/// Returns `null` if no configuration file is found.
+Future<PackageConfig?> findPackageConfigUri(Uri location,
+ {bool recurse = true,
+ int minVersion = 1,
+ Future<Uint8List?> Function(Uri uri)? loader,
+ void Function(Object error)? onError}) {
+ if (minVersion > PackageConfig.maxVersion) {
+ throw ArgumentError.value(minVersion, 'minVersion',
+ 'Maximum known version is ${PackageConfig.maxVersion}');
+ }
+ return discover.findPackageConfigUri(
+ location, minVersion, loader, onError ?? throwError, recurse);
+}
+
+/// Writes a package configuration to the provided directory.
+///
+/// Writes `.dart_tool/package_config.json` relative to [directory].
+/// If the `.dart_tool/` directory does not exist, it is created.
+/// If it cannot be created, this operation fails.
+///
+/// Also writes a `.packages` file in [directory].
+/// This will stop happening eventually as the `.packages` file becomes
+/// discontinued.
+/// A comment is generated if `[PackageConfig.extraData]` contains a
+/// `"generator"` entry.
+Future<void> savePackageConfig(
+ PackageConfig configuration, Directory directory) =>
+ writePackageConfigJsonFile(configuration, directory);
diff --git a/pkgs/package_config/lib/package_config_types.dart b/pkgs/package_config/lib/package_config_types.dart
new file mode 100644
index 0000000..825f7ac
--- /dev/null
+++ b/pkgs/package_config/lib/package_config_types.dart
@@ -0,0 +1,17 @@
+// 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.
+
+/// A package configuration is a way to assign file paths to package URIs,
+/// and vice-versa.
+///
+/// {@canonicalFor package_config.InvalidLanguageVersion}
+/// {@canonicalFor package_config.LanguageVersion}
+/// {@canonicalFor package_config.Package}
+/// {@canonicalFor package_config.PackageConfig}
+/// {@canonicalFor errors.PackageConfigError}
+library;
+
+export 'src/errors.dart' show PackageConfigError;
+export 'src/package_config.dart'
+ show InvalidLanguageVersion, LanguageVersion, Package, PackageConfig;
diff --git a/pkgs/package_config/lib/src/discovery.dart b/pkgs/package_config/lib/src/discovery.dart
new file mode 100644
index 0000000..b678410
--- /dev/null
+++ b/pkgs/package_config/lib/src/discovery.dart
@@ -0,0 +1,148 @@
+// 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:io';
+import 'dart:typed_data';
+
+import 'errors.dart';
+import 'package_config_impl.dart';
+import 'package_config_io.dart';
+import 'package_config_json.dart';
+import 'packages_file.dart' as packages_file;
+import 'util_io.dart' show defaultLoader, pathJoin;
+
+final Uri packageConfigJsonPath = Uri(path: '.dart_tool/package_config.json');
+final Uri dotPackagesPath = Uri(path: '.packages');
+final Uri currentPath = Uri(path: '.');
+final Uri parentPath = Uri(path: '..');
+
+/// Discover the package configuration for a Dart script.
+///
+/// The [baseDirectory] points to the directory of the Dart script.
+/// A package resolution strategy is found by going through the following steps,
+/// and stopping when something is found.
+///
+/// * Check if a `.dart_tool/package_config.json` file exists in the directory.
+/// * Check if a `.packages` file exists in the directory
+/// (if `minVersion <= 1`).
+/// * Repeat these checks for the parent directories until reaching the
+/// root directory if [recursive] is true.
+///
+/// If any of these tests succeed, a `PackageConfig` class is returned.
+/// Returns `null` if no configuration was found. If a configuration
+/// is needed, then the caller can supply [PackageConfig.empty].
+///
+/// If [minVersion] is greater than 1, `.packages` files are ignored.
+/// If [minVersion] is greater than the version read from the
+/// `package_config.json` file, it too is ignored.
+Future<PackageConfig?> findPackageConfig(Directory baseDirectory,
+ int minVersion, bool recursive, void Function(Object error) onError) async {
+ var directory = baseDirectory;
+ if (!directory.isAbsolute) directory = directory.absolute;
+ if (!await directory.exists()) {
+ return null;
+ }
+ do {
+ // Check for $cwd/.packages
+ var packageConfig =
+ await findPackageConfigInDirectory(directory, minVersion, onError);
+ if (packageConfig != null) return packageConfig;
+ if (!recursive) break;
+ // Check in parent directories.
+ var parentDirectory = directory.parent;
+ if (parentDirectory.path == directory.path) break;
+ directory = parentDirectory;
+ } while (true);
+ return null;
+}
+
+/// Similar to [findPackageConfig] but based on a URI.
+Future<PackageConfig?> findPackageConfigUri(
+ Uri location,
+ int minVersion,
+ Future<Uint8List?> Function(Uri uri)? loader,
+ void Function(Object error) onError,
+ bool recursive) async {
+ if (location.isScheme('package')) {
+ onError(PackageConfigArgumentError(
+ location, 'location', 'Must not be a package: URI'));
+ return null;
+ }
+ if (loader == null) {
+ if (location.isScheme('file')) {
+ return findPackageConfig(
+ Directory.fromUri(location.resolveUri(currentPath)),
+ minVersion,
+ recursive,
+ onError);
+ }
+ loader = defaultLoader;
+ }
+ if (!location.path.endsWith('/')) location = location.resolveUri(currentPath);
+ while (true) {
+ var file = location.resolveUri(packageConfigJsonPath);
+ var bytes = await loader(file);
+ if (bytes != null) {
+ var config = parsePackageConfigBytes(bytes, file, onError);
+ if (config.version >= minVersion) return config;
+ }
+ if (minVersion <= 1) {
+ file = location.resolveUri(dotPackagesPath);
+ bytes = await loader(file);
+ if (bytes != null) {
+ return packages_file.parse(bytes, file, onError);
+ }
+ }
+ if (!recursive) break;
+ var parent = location.resolveUri(parentPath);
+ if (parent == location) break;
+ location = parent;
+ }
+ return null;
+}
+
+/// Finds a `.packages` or `.dart_tool/package_config.json` file in [directory].
+///
+/// Loads the file, if it is there, and returns the resulting [PackageConfig].
+/// Returns `null` if the file isn't there.
+/// Reports a [FormatException] if a file is there but the content is not valid.
+/// If the file exists, but fails to be read, the file system error is reported.
+///
+/// If [onError] is supplied, parsing errors are reported using that, and
+/// a best-effort attempt is made to return a package configuration.
+/// This may be the empty package configuration.
+///
+/// If [minVersion] is greater than 1, `.packages` files are ignored.
+/// If [minVersion] is greater than the version read from the
+/// `package_config.json` file, it too is ignored.
+Future<PackageConfig?> findPackageConfigInDirectory(Directory directory,
+ int minVersion, void Function(Object error) onError) async {
+ var packageConfigFile = await checkForPackageConfigJsonFile(directory);
+ if (packageConfigFile != null) {
+ var config = await readPackageConfigJsonFile(packageConfigFile, onError);
+ if (config.version < minVersion) return null;
+ return config;
+ }
+ if (minVersion <= 1) {
+ packageConfigFile = await checkForDotPackagesFile(directory);
+ if (packageConfigFile != null) {
+ return await readDotPackagesFile(packageConfigFile, onError);
+ }
+ }
+ return null;
+}
+
+Future<File?> checkForPackageConfigJsonFile(Directory directory) async {
+ assert(directory.isAbsolute);
+ var file =
+ File(pathJoin(directory.path, '.dart_tool', 'package_config.json'));
+ if (await file.exists()) return file;
+ return null;
+}
+
+Future<File?> checkForDotPackagesFile(Directory directory) async {
+ var file = File(pathJoin(directory.path, '.packages'));
+ if (await file.exists()) return file;
+ return null;
+}
diff --git a/pkgs/package_config/lib/src/errors.dart b/pkgs/package_config/lib/src/errors.dart
new file mode 100644
index 0000000..a66fef7
--- /dev/null
+++ b/pkgs/package_config/lib/src/errors.dart
@@ -0,0 +1,34 @@
+// 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.
+
+/// General superclass of most errors and exceptions thrown by this package.
+///
+/// Only covers errors thrown while parsing package configuration files.
+/// Programming errors and I/O exceptions are not covered.
+abstract class PackageConfigError {
+ PackageConfigError._();
+}
+
+class PackageConfigArgumentError extends ArgumentError
+ implements PackageConfigError {
+ PackageConfigArgumentError(
+ Object? super.value, String super.name, String super.message)
+ : super.value();
+
+ PackageConfigArgumentError.from(ArgumentError error)
+ : super.value(error.invalidValue, error.name, error.message);
+}
+
+class PackageConfigFormatException extends FormatException
+ implements PackageConfigError {
+ PackageConfigFormatException(super.message, Object? super.source,
+ [super.offset]);
+
+ PackageConfigFormatException.from(FormatException exception)
+ : super(exception.message, exception.source, exception.offset);
+}
+
+/// The default `onError` handler.
+// ignore: only_throw_errors
+Never throwError(Object error) => throw error;
diff --git a/pkgs/package_config/lib/src/package_config.dart b/pkgs/package_config/lib/src/package_config.dart
new file mode 100644
index 0000000..155dfc5
--- /dev/null
+++ b/pkgs/package_config/lib/src/package_config.dart
@@ -0,0 +1,402 @@
+// 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:typed_data';
+
+import 'errors.dart';
+import 'package_config_impl.dart';
+import 'package_config_json.dart';
+
+/// A package configuration.
+///
+/// Associates configuration data to packages and files in packages.
+///
+/// More members may be added to this class in the future,
+/// so classes outside of this package must not implement [PackageConfig]
+/// or any subclass of it.
+abstract class PackageConfig {
+ /// The largest configuration version currently recognized.
+ static const int maxVersion = 2;
+
+ /// An empty package configuration.
+ ///
+ /// A package configuration with no available packages.
+ /// Is used as a default value where a package configuration
+ /// is expected, but none have been specified or found.
+ static const PackageConfig empty = SimplePackageConfig.empty();
+
+ /// Creates a package configuration with the provided available [packages].
+ ///
+ /// The packages must be valid packages (valid package name, valid
+ /// absolute directory URIs, valid language version, if any),
+ /// and there must not be two packages with the same name.
+ ///
+ /// The package's root ([Package.root]) and package-root
+ /// ([Package.packageUriRoot]) paths must satisfy a number of constraints
+ /// We say that one path (which we know ends with a `/` character)
+ /// is inside another path, if the latter path is a prefix of the former path,
+ /// including the two paths being the same.
+ ///
+ /// * No package's root must be the same as another package's root.
+ /// * The package-root of a package must be inside the package's root.
+ /// * If one package's package-root is inside another package's root,
+ /// then the latter package's package root must not be inside the former
+ /// package's root. (No getting between a package and its package root!)
+ /// This also disallows a package's root being the same as another
+ /// package's package root.
+ ///
+ /// If supplied, the [extraData] will be available as the
+ /// [PackageConfig.extraData] of the created configuration.
+ ///
+ /// The version of the resulting configuration is always [maxVersion].
+ factory PackageConfig(Iterable<Package> packages, {Object? extraData}) =>
+ SimplePackageConfig(maxVersion, packages, extraData);
+
+ /// Parses a package configuration file.
+ ///
+ /// The [bytes] must be an UTF-8 encoded JSON object
+ /// containing a valid package configuration.
+ ///
+ /// The [baseUri] is used as the base for resolving relative
+ /// URI references in the configuration file. If the configuration
+ /// has been read from a file, the [baseUri] can be the URI of that
+ /// file, or of the directory it occurs in.
+ ///
+ /// If [onError] is provided, errors found during parsing or building
+ /// the configuration are reported by calling [onError] instead of
+ /// throwing, and parser makes a *best effort* attempt to continue
+ /// despite the error. The input must still be valid JSON.
+ /// The result may be [PackageConfig.empty] if there is no way to
+ /// extract useful information from the bytes.
+ static PackageConfig parseBytes(Uint8List bytes, Uri baseUri,
+ {void Function(Object error)? onError}) =>
+ parsePackageConfigBytes(bytes, baseUri, onError ?? throwError);
+
+ /// Parses a package configuration file.
+ ///
+ /// The [configuration] must be a JSON object
+ /// containing a valid package configuration.
+ ///
+ /// The [baseUri] is used as the base for resolving relative
+ /// URI references in the configuration file. If the configuration
+ /// has been read from a file, the [baseUri] can be the URI of that
+ /// file, or of the directory it occurs in.
+ ///
+ /// If [onError] is provided, errors found during parsing or building
+ /// the configuration are reported by calling [onError] instead of
+ /// throwing, and parser makes a *best effort* attempt to continue
+ /// despite the error. The input must still be valid JSON.
+ /// The result may be [PackageConfig.empty] if there is no way to
+ /// extract useful information from the bytes.
+ static PackageConfig parseString(String configuration, Uri baseUri,
+ {void Function(Object error)? onError}) =>
+ parsePackageConfigString(configuration, baseUri, onError ?? throwError);
+
+ /// Parses the JSON data of a package configuration file.
+ ///
+ /// The [jsonData] must be a JSON-like Dart data structure,
+ /// like the one provided by parsing JSON text using `dart:convert`,
+ /// containing a valid package configuration.
+ ///
+ /// The [baseUri] is used as the base for resolving relative
+ /// URI references in the configuration file. If the configuration
+ /// has been read from a file, the [baseUri] can be the URI of that
+ /// file, or of the directory it occurs in.
+ ///
+ /// If [onError] is provided, errors found during parsing or building
+ /// the configuration are reported by calling [onError] instead of
+ /// throwing, and parser makes a *best effort* attempt to continue
+ /// despite the error. The input must still be valid JSON.
+ /// The result may be [PackageConfig.empty] if there is no way to
+ /// extract useful information from the bytes.
+ static PackageConfig parseJson(Object? jsonData, Uri baseUri,
+ {void Function(Object error)? onError}) =>
+ parsePackageConfigJson(jsonData, baseUri, onError ?? throwError);
+
+ /// Writes a configuration file for this configuration on [output].
+ ///
+ /// If [baseUri] is provided, URI references in the generated file
+ /// will be made relative to [baseUri] where possible.
+ static void writeBytes(PackageConfig configuration, Sink<Uint8List> output,
+ [Uri? baseUri]) {
+ writePackageConfigJsonUtf8(configuration, baseUri, output);
+ }
+
+ /// Writes a configuration JSON text for this configuration on [output].
+ ///
+ /// If [baseUri] is provided, URI references in the generated file
+ /// will be made relative to [baseUri] where possible.
+ static void writeString(PackageConfig configuration, StringSink output,
+ [Uri? baseUri]) {
+ writePackageConfigJsonString(configuration, baseUri, output);
+ }
+
+ /// Converts a configuration to a JSON-like data structure.
+ ///
+ /// If [baseUri] is provided, URI references in the generated data
+ /// will be made relative to [baseUri] where possible.
+ static Map<String, Object?> toJson(PackageConfig configuration,
+ [Uri? baseUri]) =>
+ packageConfigToJson(configuration, baseUri);
+
+ /// The configuration version number.
+ ///
+ /// Currently this is 1 or 2, where
+ /// * Version one is the `.packages` file format and
+ /// * Version two is the first `package_config.json` format.
+ ///
+ /// Instances of this class supports both, and the version
+ /// is only useful for detecting which kind of file the configuration
+ /// was read from.
+ int get version;
+
+ /// All the available packages of this configuration.
+ ///
+ /// No two of these packages have the same name,
+ /// and no two [Package.root] directories overlap.
+ Iterable<Package> get packages;
+
+ /// Look up a package by name.
+ ///
+ /// Returns the [Package] from [packages] with [packageName] as
+ /// [Package.name]. Returns `null` if the package is not available in the
+ /// current configuration.
+ Package? operator [](String packageName);
+
+ /// Provides the associated package for a specific [file] (or directory).
+ ///
+ /// Returns a [Package] which contains the [file]'s path, if any.
+ /// That is, the [Package.root] directory is a parent directory
+ /// of the [file]'s location.
+ ///
+ /// Returns `null` if the file does not belong to any package.
+ Package? packageOf(Uri file);
+
+ /// Resolves a `package:` URI to a non-package URI
+ ///
+ /// The [packageUri] must be a valid package URI. That means:
+ /// * A URI with `package` as scheme,
+ /// * with no authority part (`package://...`),
+ /// * with a path starting with a valid package name followed by a slash, and
+ /// * with no query or fragment part.
+ ///
+ /// Throws an [ArgumentError] (which also implements [PackageConfigError])
+ /// if the package URI is not valid.
+ ///
+ /// Returns `null` if the package name of [packageUri] is not available
+ /// in this package configuration.
+ /// Returns the remaining path of the package URI resolved relative to the
+ /// [Package.packageUriRoot] of the corresponding package.
+ Uri? resolve(Uri packageUri);
+
+ /// The package URI which resolves to [nonPackageUri].
+ ///
+ /// The [nonPackageUri] must not have any query or fragment part,
+ /// and it must not have `package` as scheme.
+ /// Throws an [ArgumentError] (which also implements [PackageConfigError])
+ /// if the non-package URI is not valid.
+ ///
+ /// Returns a package URI which [resolve] will convert to [nonPackageUri],
+ /// if any such URI exists. Returns `null` if no such package URI exists.
+ Uri? toPackageUri(Uri nonPackageUri);
+
+ /// Extra data associated with the package configuration.
+ ///
+ /// The data may be in any format, depending on who introduced it.
+ /// The standard `package_config.json` file storage will only store
+ /// JSON-like list/map data structures.
+ Object? get extraData;
+}
+
+/// Configuration data for a single package.
+abstract class Package {
+ /// Creates a package with the provided properties.
+ ///
+ /// The [name] must be a valid package name.
+ /// The [root] must be an absolute directory URI, meaning an absolute URI
+ /// with no query or fragment path and a path starting and ending with `/`.
+ /// The [packageUriRoot], if provided, must be either an absolute
+ /// directory URI or a relative URI reference which is then resolved
+ /// relative to [root]. It must then also be a subdirectory of [root],
+ /// or the same directory, and must end with `/`.
+ /// If [languageVersion] is supplied, it must be a valid Dart language
+ /// version, which means two decimal integer literals separated by a `.`,
+ /// where the integer literals have no leading zeros unless they are
+ /// a single zero digit.
+ ///
+ /// The [relativeRoot] controls whether the [root] is written as
+ /// relative to the `package_config.json` file when the package
+ /// configuration is written to a file. It defaults to being relative.
+ ///
+ /// If [extraData] is supplied, it will be available as the
+ /// [Package.extraData] of the created package.
+ factory Package(String name, Uri root,
+ {Uri? packageUriRoot,
+ LanguageVersion? languageVersion,
+ Object? extraData,
+ bool relativeRoot = true}) =>
+ SimplePackage.validate(name, root, packageUriRoot, languageVersion,
+ extraData, relativeRoot, throwError)!;
+
+ /// The package-name of the package.
+ String get name;
+
+ /// The location of the root of the package.
+ ///
+ /// Is always an absolute URI with no query or fragment parts,
+ /// and with a path ending in `/`.
+ ///
+ /// All files in the [root] directory are considered
+ /// part of the package for purposes where that that matters.
+ Uri get root;
+
+ /// The root of the files available through `package:` URIs.
+ ///
+ /// A `package:` URI with [name] as the package name is
+ /// resolved relative to this location.
+ ///
+ /// Is always an absolute URI with no query or fragment part
+ /// with a path ending in `/`,
+ /// and with a location which is a subdirectory
+ /// of the [root], or the same as the [root].
+ Uri get packageUriRoot;
+
+ /// The default language version associated with this package.
+ ///
+ /// Each package may have a default language version associated,
+ /// which is the language version used to parse and compile
+ /// Dart files in the package.
+ /// A package version is defined by two non-negative numbers,
+ /// the *major* and *minor* version numbers.
+ ///
+ /// A package may have no language version associated with it
+ /// in the package configuration, in which case tools should
+ /// use a default behavior for the package.
+ LanguageVersion? get languageVersion;
+
+ /// Extra data associated with the specific package.
+ ///
+ /// The data may be in any format, depending on who introduced it.
+ /// The standard `package_config.json` file storage will only store
+ /// JSON-like list/map data structures.
+ Object? get extraData;
+
+ /// Whether the [root] URI should be written as relative.
+ ///
+ /// When the configuration is written to a `package_config.json`
+ /// file, the [root] URI can be either relative to the file
+ /// location or absolute, controller by this value.
+ bool get relativeRoot;
+}
+
+/// A language version.
+///
+/// A language version is represented by two non-negative integers,
+/// the [major] and [minor] version numbers.
+///
+/// If errors during parsing are handled using an `onError` handler,
+/// then an *invalid* language version may be represented by an
+/// [InvalidLanguageVersion] object.
+abstract class LanguageVersion implements Comparable<LanguageVersion> {
+ /// The maximal value allowed by [major] and [minor] values;
+ static const int maxValue = 0x7FFFFFFF;
+ factory LanguageVersion(int major, int minor) {
+ RangeError.checkValueInInterval(major, 0, maxValue, 'major');
+ RangeError.checkValueInInterval(minor, 0, maxValue, 'major');
+ return SimpleLanguageVersion(major, minor, null);
+ }
+
+ /// Parses a language version string.
+ ///
+ /// A valid language version string has the form
+ ///
+ /// > *decimalNumber* `.` *decimalNumber*
+ ///
+ /// where a *decimalNumber* is a non-empty sequence of decimal digits
+ /// with no unnecessary leading zeros (the decimal number only starts
+ /// with a zero digit if that digit is the entire number).
+ /// No spaces are allowed in the string.
+ ///
+ /// If the [source] is valid then it is parsed into a valid
+ /// [LanguageVersion] object.
+ /// If not, then the [onError] is called with a [FormatException].
+ /// If [onError] is not supplied, it defaults to throwing the exception.
+ /// If the call does not throw, then an [InvalidLanguageVersion] is returned
+ /// containing the original [source].
+ static LanguageVersion parse(String source,
+ {void Function(Object error)? onError}) =>
+ parseLanguageVersion(source, onError ?? throwError);
+
+ /// The major language version.
+ ///
+ /// A non-negative integer less than 2<sup>31</sup>.
+ ///
+ /// The value is negative for objects representing *invalid* language
+ /// versions ([InvalidLanguageVersion]).
+ int get major;
+
+ /// The minor language version.
+ ///
+ /// A non-negative integer less than 2<sup>31</sup>.
+ ///
+ /// The value is negative for objects representing *invalid* language
+ /// versions ([InvalidLanguageVersion]).
+ int get minor;
+
+ /// Compares language versions.
+ ///
+ /// Two language versions are considered equal if they have the
+ /// same major and minor version numbers.
+ ///
+ /// A language version is greater then another if the former's major version
+ /// is greater than the latter's major version, or if they have
+ /// the same major version and the former's minor version is greater than
+ /// the latter's.
+ @override
+ int compareTo(LanguageVersion other);
+
+ /// Valid language versions with the same [major] and [minor] values are
+ /// equal.
+ ///
+ /// Invalid language versions ([InvalidLanguageVersion]) are not equal to
+ /// any other object.
+ @override
+ bool operator ==(Object other);
+
+ @override
+ int get hashCode;
+
+ /// A string representation of the language version.
+ ///
+ /// A valid language version is represented as
+ /// `"${version.major}.${version.minor}"`.
+ @override
+ String toString();
+}
+
+/// An *invalid* language version.
+///
+/// Stored in a [Package] when the original language version string
+/// was invalid and a `onError` handler was passed to the parser
+/// which did not throw on an error.
+abstract class InvalidLanguageVersion implements LanguageVersion {
+ /// The value -1 for an invalid language version.
+ @override
+ int get major;
+
+ /// The value -1 for an invalid language version.
+ @override
+ int get minor;
+
+ /// An invalid language version is only equal to itself.
+ @override
+ bool operator ==(Object other);
+
+ @override
+ int get hashCode;
+
+ /// The original invalid version string.
+ @override
+ String toString();
+}
diff --git a/pkgs/package_config/lib/src/package_config_impl.dart b/pkgs/package_config/lib/src/package_config_impl.dart
new file mode 100644
index 0000000..865e99a
--- /dev/null
+++ b/pkgs/package_config/lib/src/package_config_impl.dart
@@ -0,0 +1,568 @@
+// 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 'errors.dart';
+import 'package_config.dart';
+import 'util.dart';
+
+export 'package_config.dart';
+
+const bool _disallowPackagesInsidePackageUriRoot = false;
+
+// Implementations of the main data types exposed by the API of this package.
+
+class SimplePackageConfig implements PackageConfig {
+ @override
+ final int version;
+ final Map<String, Package> _packages;
+ final PackageTree _packageTree;
+ @override
+ final Object? extraData;
+
+ factory SimplePackageConfig(int version, Iterable<Package> packages,
+ [Object? extraData, void Function(Object error)? onError]) {
+ onError ??= throwError;
+ var validVersion = _validateVersion(version, onError);
+ var sortedPackages = [...packages]..sort(_compareRoot);
+ var packageTree = _validatePackages(packages, sortedPackages, onError);
+ return SimplePackageConfig._(validVersion, packageTree,
+ {for (var p in packageTree.allPackages) p.name: p}, extraData);
+ }
+
+ SimplePackageConfig._(
+ this.version, this._packageTree, this._packages, this.extraData);
+
+ /// Creates empty configuration.
+ ///
+ /// The empty configuration can be used in cases where no configuration is
+ /// found, but code expects a non-null configuration.
+ ///
+ /// The version number is [PackageConfig.maxVersion] to avoid
+ /// minimum-version filters discarding the configuration.
+ const SimplePackageConfig.empty()
+ : version = PackageConfig.maxVersion,
+ _packageTree = const EmptyPackageTree(),
+ _packages = const <String, Package>{},
+ extraData = null;
+
+ static int _validateVersion(
+ int version, void Function(Object error) onError) {
+ if (version < 0 || version > PackageConfig.maxVersion) {
+ onError(PackageConfigArgumentError(version, 'version',
+ 'Must be in the range 1 to ${PackageConfig.maxVersion}'));
+ return 2; // The minimal version supporting a SimplePackageConfig.
+ }
+ return version;
+ }
+
+ static PackageTree _validatePackages(Iterable<Package> originalPackages,
+ List<Package> packages, void Function(Object error) onError) {
+ var packageNames = <String>{};
+ var tree = TriePackageTree();
+ for (var originalPackage in packages) {
+ SimplePackage? newPackage;
+ if (originalPackage is! SimplePackage) {
+ // SimplePackage validates these properties.
+ newPackage = SimplePackage.validate(
+ originalPackage.name,
+ originalPackage.root,
+ originalPackage.packageUriRoot,
+ originalPackage.languageVersion,
+ originalPackage.extraData,
+ originalPackage.relativeRoot, (error) {
+ if (error is PackageConfigArgumentError) {
+ onError(PackageConfigArgumentError(packages, 'packages',
+ 'Package ${newPackage!.name}: ${error.message}'));
+ } else {
+ onError(error);
+ }
+ });
+ if (newPackage == null) continue;
+ } else {
+ newPackage = originalPackage;
+ }
+ var name = newPackage.name;
+ if (packageNames.contains(name)) {
+ onError(PackageConfigArgumentError(
+ name, 'packages', "Duplicate package name '$name'"));
+ continue;
+ }
+ packageNames.add(name);
+ tree.add(newPackage, (error) {
+ if (error is ConflictException) {
+ // There is a conflict with an existing package.
+ var existingPackage = error.existingPackage;
+ switch (error.conflictType) {
+ case ConflictType.sameRoots:
+ onError(PackageConfigArgumentError(
+ originalPackages,
+ 'packages',
+ 'Packages ${newPackage!.name} and ${existingPackage.name} '
+ 'have the same root directory: ${newPackage.root}.\n'));
+ break;
+ case ConflictType.interleaving:
+ // The new package is inside the package URI root of the existing
+ // package.
+ onError(PackageConfigArgumentError(
+ originalPackages,
+ 'packages',
+ 'Package ${newPackage!.name} is inside the root of '
+ 'package ${existingPackage.name}, and the package root '
+ 'of ${existingPackage.name} is inside the root of '
+ '${newPackage.name}.\n'
+ '${existingPackage.name} package root: '
+ '${existingPackage.packageUriRoot}\n'
+ '${newPackage.name} root: ${newPackage.root}\n'));
+ break;
+ case ConflictType.insidePackageRoot:
+ onError(PackageConfigArgumentError(
+ originalPackages,
+ 'packages',
+ 'Package ${newPackage!.name} is inside the package root of '
+ 'package ${existingPackage.name}.\n'
+ '${existingPackage.name} package root: '
+ '${existingPackage.packageUriRoot}\n'
+ '${newPackage.name} root: ${newPackage.root}\n'));
+ break;
+ }
+ } else {
+ // Any other error.
+ onError(error);
+ }
+ });
+ }
+ return tree;
+ }
+
+ @override
+ Iterable<Package> get packages => _packages.values;
+
+ @override
+ Package? operator [](String packageName) => _packages[packageName];
+
+ @override
+ Package? packageOf(Uri file) => _packageTree.packageOf(file);
+
+ @override
+ Uri? resolve(Uri packageUri) {
+ var packageName = checkValidPackageUri(packageUri, 'packageUri');
+ return _packages[packageName]?.packageUriRoot.resolveUri(
+ Uri(path: packageUri.path.substring(packageName.length + 1)));
+ }
+
+ @override
+ Uri? toPackageUri(Uri nonPackageUri) {
+ if (nonPackageUri.isScheme('package')) {
+ throw PackageConfigArgumentError(
+ nonPackageUri, 'nonPackageUri', 'Must not be a package URI');
+ }
+ if (nonPackageUri.hasQuery || nonPackageUri.hasFragment) {
+ throw PackageConfigArgumentError(nonPackageUri, 'nonPackageUri',
+ 'Must not have query or fragment part');
+ }
+ // Find package that file belongs to.
+ var package = _packageTree.packageOf(nonPackageUri);
+ if (package == null) return null;
+ // Check if it is inside the package URI root.
+ var path = nonPackageUri.toString();
+ var root = package.packageUriRoot.toString();
+ if (_beginsWith(package.root.toString().length, root, path)) {
+ var rest = path.substring(root.length);
+ return Uri(scheme: 'package', path: '${package.name}/$rest');
+ }
+ return null;
+ }
+}
+
+/// Configuration data for a single package.
+class SimplePackage implements Package {
+ @override
+ final String name;
+ @override
+ final Uri root;
+ @override
+ final Uri packageUriRoot;
+ @override
+ final LanguageVersion? languageVersion;
+ @override
+ final Object? extraData;
+ @override
+ final bool relativeRoot;
+
+ SimplePackage._(this.name, this.root, this.packageUriRoot,
+ this.languageVersion, this.extraData, this.relativeRoot);
+
+ /// Creates a [SimplePackage] with the provided content.
+ ///
+ /// The provided arguments must be valid.
+ ///
+ /// If the arguments are invalid then the error is reported by
+ /// calling [onError], then the erroneous entry is ignored.
+ ///
+ /// If [onError] is provided, the user is expected to be able to handle
+ /// errors themselves. An invalid [languageVersion] string
+ /// will be replaced with the string `"invalid"`. This allows
+ /// users to detect the difference between an absent version and
+ /// an invalid one.
+ ///
+ /// Returns `null` if the input is invalid and an approximately valid package
+ /// cannot be salvaged from the input.
+ static SimplePackage? validate(
+ String name,
+ Uri root,
+ Uri? packageUriRoot,
+ LanguageVersion? languageVersion,
+ Object? extraData,
+ bool relativeRoot,
+ void Function(Object error) onError) {
+ var fatalError = false;
+ var invalidIndex = checkPackageName(name);
+ if (invalidIndex >= 0) {
+ onError(PackageConfigFormatException(
+ 'Not a valid package name', name, invalidIndex));
+ fatalError = true;
+ }
+ if (root.isScheme('package')) {
+ onError(PackageConfigArgumentError(
+ '$root', 'root', 'Must not be a package URI'));
+ fatalError = true;
+ } else if (!isAbsoluteDirectoryUri(root)) {
+ onError(PackageConfigArgumentError(
+ '$root',
+ 'root',
+ 'In package $name: Not an absolute URI with no query or fragment '
+ 'with a path ending in /'));
+ // Try to recover. If the URI has a scheme,
+ // then ensure that the path ends with `/`.
+ if (!root.hasScheme) {
+ fatalError = true;
+ } else if (!root.path.endsWith('/')) {
+ root = root.replace(path: '${root.path}/');
+ }
+ }
+ if (packageUriRoot == null) {
+ packageUriRoot = root;
+ } else if (!fatalError) {
+ packageUriRoot = root.resolveUri(packageUriRoot);
+ if (!isAbsoluteDirectoryUri(packageUriRoot)) {
+ onError(PackageConfigArgumentError(
+ packageUriRoot,
+ 'packageUriRoot',
+ 'In package $name: Not an absolute URI with no query or fragment '
+ 'with a path ending in /'));
+ packageUriRoot = root;
+ } else if (!isUriPrefix(root, packageUriRoot)) {
+ onError(PackageConfigArgumentError(packageUriRoot, 'packageUriRoot',
+ 'The package URI root is not below the package root'));
+ packageUriRoot = root;
+ }
+ }
+ if (fatalError) return null;
+ return SimplePackage._(
+ name, root, packageUriRoot, languageVersion, extraData, relativeRoot);
+ }
+}
+
+/// Checks whether [source] is a valid Dart language version string.
+///
+/// The format is (as RegExp) `^(0|[1-9]\d+)\.(0|[1-9]\d+)$`.
+///
+/// Reports a format exception on [onError] if not, or if the numbers
+/// are too large (at most 32-bit signed integers).
+LanguageVersion parseLanguageVersion(
+ String? source, void Function(Object error) onError) {
+ var index = 0;
+ // Reads a positive decimal numeral. Returns the value of the numeral,
+ // or a negative number in case of an error.
+ // Starts at [index] and increments the index to the position after
+ // the numeral.
+ // It is an error if the numeral value is greater than 0x7FFFFFFFF.
+ // It is a recoverable error if the numeral starts with leading zeros.
+ int readNumeral() {
+ const maxValue = 0x7FFFFFFF;
+ if (index == source!.length) {
+ onError(PackageConfigFormatException('Missing number', source, index));
+ return -1;
+ }
+ var start = index;
+
+ var char = source.codeUnitAt(index);
+ var digit = char ^ 0x30;
+ if (digit > 9) {
+ onError(PackageConfigFormatException('Missing number', source, index));
+ return -1;
+ }
+ var firstDigit = digit;
+ var value = 0;
+ do {
+ value = value * 10 + digit;
+ if (value > maxValue) {
+ onError(
+ PackageConfigFormatException('Number too large', source, start));
+ return -1;
+ }
+ index++;
+ if (index == source.length) break;
+ char = source.codeUnitAt(index);
+ digit = char ^ 0x30;
+ } while (digit <= 9);
+ if (firstDigit == 0 && index > start + 1) {
+ onError(PackageConfigFormatException(
+ 'Leading zero not allowed', source, start));
+ }
+ return value;
+ }
+
+ var major = readNumeral();
+ if (major < 0) {
+ return SimpleInvalidLanguageVersion(source);
+ }
+ if (index == source!.length || source.codeUnitAt(index) != $dot) {
+ onError(PackageConfigFormatException("Missing '.'", source, index));
+ return SimpleInvalidLanguageVersion(source);
+ }
+ index++;
+ var minor = readNumeral();
+ if (minor < 0) {
+ return SimpleInvalidLanguageVersion(source);
+ }
+ if (index != source.length) {
+ onError(PackageConfigFormatException(
+ 'Unexpected trailing character', source, index));
+ return SimpleInvalidLanguageVersion(source);
+ }
+ return SimpleLanguageVersion(major, minor, source);
+}
+
+abstract class _SimpleLanguageVersionBase implements LanguageVersion {
+ @override
+ int compareTo(LanguageVersion other) {
+ var result = major.compareTo(other.major);
+ if (result != 0) return result;
+ return minor.compareTo(other.minor);
+ }
+}
+
+class SimpleLanguageVersion extends _SimpleLanguageVersionBase {
+ @override
+ final int major;
+ @override
+ final int minor;
+ String? _source;
+ SimpleLanguageVersion(this.major, this.minor, this._source);
+
+ @override
+ bool operator ==(Object other) =>
+ other is LanguageVersion && major == other.major && minor == other.minor;
+
+ @override
+ int get hashCode => (major * 17 ^ minor * 37) & 0x3FFFFFFF;
+
+ @override
+ String toString() => _source ??= '$major.$minor';
+}
+
+class SimpleInvalidLanguageVersion extends _SimpleLanguageVersionBase
+ implements InvalidLanguageVersion {
+ final String? _source;
+ SimpleInvalidLanguageVersion(this._source);
+ @override
+ int get major => -1;
+ @override
+ int get minor => -1;
+
+ @override
+ String toString() => _source!;
+}
+
+abstract class PackageTree {
+ Iterable<Package> get allPackages;
+ SimplePackage? packageOf(Uri file);
+}
+
+class _PackageTrieNode {
+ SimplePackage? package;
+
+ /// Indexed by path segment.
+ Map<String, _PackageTrieNode> map = {};
+}
+
+/// Packages of a package configuration ordered by root path.
+///
+/// A package has a root path and a package root path, where the latter
+/// contains the files exposed by `package:` URIs.
+///
+/// A package is said to be inside another package if the root path URI of
+/// the latter is a prefix of the root path URI of the former.
+///
+/// No two packages of a package may have the same root path.
+/// The package root path of a package must not be inside another package's
+/// root path.
+/// Entire other packages are allowed inside a package's root.
+class TriePackageTree implements PackageTree {
+ /// Indexed by URI scheme.
+ final Map<String, _PackageTrieNode> _map = {};
+
+ /// A list of all packages.
+ final List<SimplePackage> _packages = [];
+
+ @override
+ Iterable<Package> get allPackages sync* {
+ for (var package in _packages) {
+ yield package;
+ }
+ }
+
+ bool _checkConflict(_PackageTrieNode node, SimplePackage newPackage,
+ void Function(Object error) onError) {
+ var existingPackage = node.package;
+ if (existingPackage != null) {
+ // Trying to add package that is inside the existing package.
+ // 1) If it's an exact match it's not allowed (i.e. the roots can't be
+ // the same).
+ if (newPackage.root.path.length == existingPackage.root.path.length) {
+ onError(ConflictException(
+ newPackage, existingPackage, ConflictType.sameRoots));
+ return true;
+ }
+ // 2) The existing package has a packageUriRoot thats inside the
+ // root of the new package.
+ if (_beginsWith(0, newPackage.root.toString(),
+ existingPackage.packageUriRoot.toString())) {
+ onError(ConflictException(
+ newPackage, existingPackage, ConflictType.interleaving));
+ return true;
+ }
+
+ // For internal reasons we allow this (for now). One should still never do
+ // it though.
+ // 3) The new package is inside the packageUriRoot of existing package.
+ if (_disallowPackagesInsidePackageUriRoot) {
+ if (_beginsWith(0, existingPackage.packageUriRoot.toString(),
+ newPackage.root.toString())) {
+ onError(ConflictException(
+ newPackage, existingPackage, ConflictType.insidePackageRoot));
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /// Tries to add `newPackage` to the tree.
+ ///
+ /// Reports a [ConflictException] if the added package conflicts with an
+ /// existing package.
+ /// It conflicts if its root or package root is the same as an existing
+ /// package's root or package root, is between the two, or if it's inside the
+ /// package root of an existing package.
+ ///
+ /// If a conflict is detected between [newPackage] and a previous package,
+ /// then [onError] is called with a [ConflictException] object
+ /// and the [newPackage] is not added to the tree.
+ ///
+ /// The packages are added in order of their root path.
+ void add(SimplePackage newPackage, void Function(Object error) onError) {
+ var root = newPackage.root;
+ var node = _map[root.scheme] ??= _PackageTrieNode();
+ if (_checkConflict(node, newPackage, onError)) return;
+ var segments = root.pathSegments;
+ // Notice that we're skipping the last segment as it's always the empty
+ // string because roots are directories.
+ for (var i = 0; i < segments.length - 1; i++) {
+ var path = segments[i];
+ node = node.map[path] ??= _PackageTrieNode();
+ if (_checkConflict(node, newPackage, onError)) return;
+ }
+ node.package = newPackage;
+ _packages.add(newPackage);
+ }
+
+ bool _isMatch(
+ String path, _PackageTrieNode node, List<SimplePackage> potential) {
+ var currentPackage = node.package;
+ if (currentPackage != null) {
+ var currentPackageRootLength = currentPackage.root.toString().length;
+ if (path.length == currentPackageRootLength) return true;
+ var currentPackageUriRoot = currentPackage.packageUriRoot.toString();
+ // Is [file] inside the package root of [currentPackage]?
+ if (currentPackageUriRoot.length == currentPackageRootLength ||
+ _beginsWith(currentPackageRootLength, currentPackageUriRoot, path)) {
+ return true;
+ }
+ potential.add(currentPackage);
+ }
+ return false;
+ }
+
+ @override
+ SimplePackage? packageOf(Uri file) {
+ var currentTrieNode = _map[file.scheme];
+ if (currentTrieNode == null) return null;
+ var path = file.toString();
+ var potential = <SimplePackage>[];
+ if (_isMatch(path, currentTrieNode, potential)) {
+ return currentTrieNode.package;
+ }
+ var segments = file.pathSegments;
+
+ for (var i = 0; i < segments.length - 1; i++) {
+ var segment = segments[i];
+ currentTrieNode = currentTrieNode!.map[segment];
+ if (currentTrieNode == null) break;
+ if (_isMatch(path, currentTrieNode, potential)) {
+ return currentTrieNode.package;
+ }
+ }
+ if (potential.isEmpty) return null;
+ return potential.last;
+ }
+}
+
+class EmptyPackageTree implements PackageTree {
+ const EmptyPackageTree();
+
+ @override
+ Iterable<Package> get allPackages => const Iterable<Package>.empty();
+
+ @override
+ SimplePackage? packageOf(Uri file) => null;
+}
+
+/// Checks whether [longerPath] begins with [parentPath].
+///
+/// Skips checking the [start] first characters which are assumed to
+/// already have been matched.
+bool _beginsWith(int start, String parentPath, String longerPath) {
+ if (longerPath.length < parentPath.length) return false;
+ for (var i = start; i < parentPath.length; i++) {
+ if (longerPath.codeUnitAt(i) != parentPath.codeUnitAt(i)) return false;
+ }
+ return true;
+}
+
+enum ConflictType { sameRoots, interleaving, insidePackageRoot }
+
+/// Conflict between packages added to the same configuration.
+///
+/// The [package] conflicts with [existingPackage] if it has
+/// the same root path or the package URI root path
+/// of [existingPackage] is inside the root path of [package].
+class ConflictException {
+ /// The existing package that [package] conflicts with.
+ final SimplePackage existingPackage;
+
+ /// The package that could not be added without a conflict.
+ final SimplePackage package;
+
+ /// Whether the conflict is with the package URI root of [existingPackage].
+ final ConflictType conflictType;
+
+ /// Creates a root conflict between [package] and [existingPackage].
+ ConflictException(this.package, this.existingPackage, this.conflictType);
+}
+
+/// Used for sorting packages by root path.
+int _compareRoot(Package p1, Package p2) =>
+ p1.root.toString().compareTo(p2.root.toString());
diff --git a/pkgs/package_config/lib/src/package_config_io.dart b/pkgs/package_config/lib/src/package_config_io.dart
new file mode 100644
index 0000000..8c5773b
--- /dev/null
+++ b/pkgs/package_config/lib/src/package_config_io.dart
@@ -0,0 +1,166 @@
+// 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.
+
+// dart:io dependent functionality for reading and writing configuration files.
+
+import 'dart:convert';
+import 'dart:io';
+import 'dart:typed_data';
+
+import 'errors.dart';
+import 'package_config_impl.dart';
+import 'package_config_json.dart';
+import 'packages_file.dart' as packages_file;
+import 'util.dart';
+import 'util_io.dart';
+
+/// Name of directory where Dart tools store their configuration.
+///
+/// Directory is created in the package root directory.
+const dartToolDirName = '.dart_tool';
+
+/// Name of file containing new package configuration data.
+///
+/// File is stored in the dart tool directory.
+const packageConfigFileName = 'package_config.json';
+
+/// Name of file containing legacy package configuration data.
+///
+/// File is stored in the package root directory.
+const packagesFileName = '.packages';
+
+/// Reads a package configuration file.
+///
+/// Detects whether the [file] is a version one `.packages` file or
+/// a version two `package_config.json` file.
+///
+/// If the [file] is a `.packages` file and [preferNewest] is true,
+/// first checks whether there is an adjacent `.dart_tool/package_config.json`
+/// file, and if so, reads that instead.
+/// If [preferNewest] is false, the specified file is loaded even if it is
+/// a `.packages` file and there is an available `package_config.json` file.
+///
+/// The file must exist and be a normal file.
+Future<PackageConfig> readAnyConfigFile(
+ File file, bool preferNewest, void Function(Object error) onError) async {
+ if (preferNewest && fileName(file.path) == packagesFileName) {
+ var alternateFile = File(
+ pathJoin(dirName(file.path), dartToolDirName, packageConfigFileName));
+ if (alternateFile.existsSync()) {
+ return await readPackageConfigJsonFile(alternateFile, onError);
+ }
+ }
+ Uint8List bytes;
+ try {
+ bytes = await file.readAsBytes();
+ } catch (e) {
+ onError(e);
+ return const SimplePackageConfig.empty();
+ }
+ return parseAnyConfigFile(bytes, file.uri, onError);
+}
+
+/// Like [readAnyConfigFile] but uses a URI and an optional loader.
+Future<PackageConfig> readAnyConfigFileUri(
+ Uri file,
+ Future<Uint8List?> Function(Uri uri)? loader,
+ void Function(Object error) onError,
+ bool preferNewest) async {
+ if (file.isScheme('package')) {
+ throw PackageConfigArgumentError(
+ file, 'file', 'Must not be a package: URI');
+ }
+ if (loader == null) {
+ if (file.isScheme('file')) {
+ return await readAnyConfigFile(File.fromUri(file), preferNewest, onError);
+ }
+ loader = defaultLoader;
+ }
+ if (preferNewest && file.pathSegments.last == packagesFileName) {
+ var alternateFile = file.resolve('$dartToolDirName/$packageConfigFileName');
+ Uint8List? bytes;
+ try {
+ bytes = await loader(alternateFile);
+ } catch (e) {
+ onError(e);
+ return const SimplePackageConfig.empty();
+ }
+ if (bytes != null) {
+ return parsePackageConfigBytes(bytes, alternateFile, onError);
+ }
+ }
+ Uint8List? bytes;
+ try {
+ bytes = await loader(file);
+ } catch (e) {
+ onError(e);
+ return const SimplePackageConfig.empty();
+ }
+ if (bytes == null) {
+ onError(PackageConfigArgumentError(
+ file.toString(), 'file', 'File cannot be read'));
+ return const SimplePackageConfig.empty();
+ }
+ return parseAnyConfigFile(bytes, file, onError);
+}
+
+/// Parses a `.packages` or `package_config.json` file's contents.
+///
+/// Assumes it's a JSON file if the first non-whitespace character
+/// is `{`, otherwise assumes it's a `.packages` file.
+PackageConfig parseAnyConfigFile(
+ Uint8List bytes, Uri file, void Function(Object error) onError) {
+ var firstChar = firstNonWhitespaceChar(bytes);
+ if (firstChar != $lbrace) {
+ // Definitely not a JSON object, probably a .packages.
+ return packages_file.parse(bytes, file, onError);
+ }
+ return parsePackageConfigBytes(bytes, file, onError);
+}
+
+Future<PackageConfig> readPackageConfigJsonFile(
+ File file, void Function(Object error) onError) async {
+ Uint8List bytes;
+ try {
+ bytes = await file.readAsBytes();
+ } catch (error) {
+ onError(error);
+ return const SimplePackageConfig.empty();
+ }
+ return parsePackageConfigBytes(bytes, file.uri, onError);
+}
+
+Future<PackageConfig> readDotPackagesFile(
+ File file, void Function(Object error) onError) async {
+ Uint8List bytes;
+ try {
+ bytes = await file.readAsBytes();
+ } catch (error) {
+ onError(error);
+ return const SimplePackageConfig.empty();
+ }
+ return packages_file.parse(bytes, file.uri, onError);
+}
+
+Future<void> writePackageConfigJsonFile(
+ PackageConfig config, Directory targetDirectory) async {
+ // Write .dart_tool/package_config.json first.
+ var dartToolDir = Directory(pathJoin(targetDirectory.path, dartToolDirName));
+ await dartToolDir.create(recursive: true);
+ var file = File(pathJoin(dartToolDir.path, packageConfigFileName));
+ var baseUri = file.uri;
+
+ var sink = file.openWrite(encoding: utf8);
+ writePackageConfigJsonUtf8(config, baseUri, sink);
+ var doneJson = sink.close();
+
+ // Write .packages too.
+ file = File(pathJoin(targetDirectory.path, packagesFileName));
+ baseUri = file.uri;
+ sink = file.openWrite(encoding: utf8);
+ writeDotPackages(config, baseUri, sink);
+ var donePackages = sink.close();
+
+ await Future.wait([doneJson, donePackages]);
+}
diff --git a/pkgs/package_config/lib/src/package_config_json.dart b/pkgs/package_config/lib/src/package_config_json.dart
new file mode 100644
index 0000000..65560a0
--- /dev/null
+++ b/pkgs/package_config/lib/src/package_config_json.dart
@@ -0,0 +1,321 @@
+// 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.
+
+// Parsing and serialization of package configurations.
+
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'errors.dart';
+import 'package_config_impl.dart';
+import 'packages_file.dart' as packages_file;
+import 'util.dart';
+
+const String _configVersionKey = 'configVersion';
+const String _packagesKey = 'packages';
+const List<String> _topNames = [_configVersionKey, _packagesKey];
+const String _nameKey = 'name';
+const String _rootUriKey = 'rootUri';
+const String _packageUriKey = 'packageUri';
+const String _languageVersionKey = 'languageVersion';
+const List<String> _packageNames = [
+ _nameKey,
+ _rootUriKey,
+ _packageUriKey,
+ _languageVersionKey
+];
+
+const String _generatedKey = 'generated';
+const String _generatorKey = 'generator';
+const String _generatorVersionKey = 'generatorVersion';
+
+final _jsonUtf8Decoder = json.fuse(utf8).decoder;
+
+PackageConfig parsePackageConfigBytes(
+ Uint8List bytes, Uri file, void Function(Object error) onError) {
+ // TODO(lrn): Make this simpler. Maybe parse directly from bytes.
+ Object? jsonObject;
+ try {
+ jsonObject = _jsonUtf8Decoder.convert(bytes);
+ } on FormatException catch (e) {
+ onError(PackageConfigFormatException.from(e));
+ return const SimplePackageConfig.empty();
+ }
+ return parsePackageConfigJson(jsonObject, file, onError);
+}
+
+PackageConfig parsePackageConfigString(
+ String source, Uri file, void Function(Object error) onError) {
+ Object? jsonObject;
+ try {
+ jsonObject = jsonDecode(source);
+ } on FormatException catch (e) {
+ onError(PackageConfigFormatException.from(e));
+ return const SimplePackageConfig.empty();
+ }
+ return parsePackageConfigJson(jsonObject, file, onError);
+}
+
+/// Creates a [PackageConfig] from a parsed JSON-like object structure.
+///
+/// The [json] argument must be a JSON object (`Map<String, Object?>`)
+/// containing a `"configVersion"` entry with an integer value in the range
+/// 1 to [PackageConfig.maxVersion],
+/// and with a `"packages"` entry which is a JSON array (`List<Object?>`)
+/// containing JSON objects which each has the following properties:
+///
+/// * `"name"`: The package name as a string.
+/// * `"rootUri"`: The root of the package as a URI stored as a string.
+/// * `"packageUri"`: Optionally the root of for `package:` URI resolution
+/// for the package, as a relative URI below the root URI
+/// stored as a string.
+/// * `"languageVersion"`: Optionally a language version string which is a
+/// an integer numeral, a decimal point (`.`) and another integer numeral,
+/// where the integer numeral cannot have a sign, and can only have a
+/// leading zero if the entire numeral is a single zero.
+///
+/// The [baseLocation] is used as base URI to resolve the "rootUri"
+/// URI reference string.
+PackageConfig parsePackageConfigJson(
+ Object? json, Uri baseLocation, void Function(Object error) onError) {
+ if (!baseLocation.hasScheme || baseLocation.isScheme('package')) {
+ throw PackageConfigArgumentError(baseLocation.toString(), 'baseLocation',
+ 'Must be an absolute non-package: URI');
+ }
+
+ if (!baseLocation.path.endsWith('/')) {
+ baseLocation = baseLocation.resolveUri(Uri(path: '.'));
+ }
+
+ String typeName<T>() {
+ if (0 is T) return 'int';
+ if ('' is T) return 'string';
+ if (const <Object?>[] is T) return 'array';
+ return 'object';
+ }
+
+ T? checkType<T>(Object? value, String name, [String? packageName]) {
+ if (value is T) return value;
+ // The only types we are called with are [int], [String], [List<Object?>]
+ // and Map<String, Object?>. Recognize which to give a better error message.
+ var message =
+ "$name${packageName != null ? " of package $packageName" : ""}"
+ ' is not a JSON ${typeName<T>()}';
+ onError(PackageConfigFormatException(message, value));
+ return null;
+ }
+
+ Package? parsePackage(Map<String, Object?> entry) {
+ String? name;
+ String? rootUri;
+ String? packageUri;
+ String? languageVersion;
+ Map<String, Object?>? extraData;
+ var hasName = false;
+ var hasRoot = false;
+ var hasVersion = false;
+ entry.forEach((key, value) {
+ switch (key) {
+ case _nameKey:
+ hasName = true;
+ name = checkType<String>(value, _nameKey);
+ break;
+ case _rootUriKey:
+ hasRoot = true;
+ rootUri = checkType<String>(value, _rootUriKey, name);
+ break;
+ case _packageUriKey:
+ packageUri = checkType<String>(value, _packageUriKey, name);
+ break;
+ case _languageVersionKey:
+ hasVersion = true;
+ languageVersion = checkType<String>(value, _languageVersionKey, name);
+ break;
+ default:
+ (extraData ??= {})[key] = value;
+ break;
+ }
+ });
+ if (!hasName) {
+ onError(PackageConfigFormatException('Missing name entry', entry));
+ }
+ if (!hasRoot) {
+ onError(PackageConfigFormatException('Missing rootUri entry', entry));
+ }
+ if (name == null || rootUri == null) return null;
+ var parsedRootUri = Uri.parse(rootUri!);
+ var relativeRoot = !hasAbsolutePath(parsedRootUri);
+ var root = baseLocation.resolveUri(parsedRootUri);
+ if (!root.path.endsWith('/')) root = root.replace(path: '${root.path}/');
+ var packageRoot = root;
+ if (packageUri != null) packageRoot = root.resolve(packageUri!);
+ if (!packageRoot.path.endsWith('/')) {
+ packageRoot = packageRoot.replace(path: '${packageRoot.path}/');
+ }
+
+ LanguageVersion? version;
+ if (languageVersion != null) {
+ version = parseLanguageVersion(languageVersion, onError);
+ } else if (hasVersion) {
+ version = SimpleInvalidLanguageVersion('invalid');
+ }
+
+ return SimplePackage.validate(
+ name!, root, packageRoot, version, extraData, relativeRoot, (error) {
+ if (error is ArgumentError) {
+ onError(
+ PackageConfigFormatException(
+ error.message.toString(), error.invalidValue),
+ );
+ } else {
+ onError(error);
+ }
+ });
+ }
+
+ var map = checkType<Map<String, Object?>>(json, 'value');
+ if (map == null) return const SimplePackageConfig.empty();
+ Map<String, Object?>? extraData;
+ List<Package>? packageList;
+ int? configVersion;
+ map.forEach((key, value) {
+ switch (key) {
+ case _configVersionKey:
+ configVersion = checkType<int>(value, _configVersionKey) ?? 2;
+ break;
+ case _packagesKey:
+ var packageArray = checkType<List<Object?>>(value, _packagesKey) ?? [];
+ var packages = <Package>[];
+ for (var package in packageArray) {
+ var packageMap =
+ checkType<Map<String, Object?>>(package, 'package entry');
+ if (packageMap != null) {
+ var entry = parsePackage(packageMap);
+ if (entry != null) {
+ packages.add(entry);
+ }
+ }
+ }
+ packageList = packages;
+ break;
+ default:
+ (extraData ??= {})[key] = value;
+ break;
+ }
+ });
+ if (configVersion == null) {
+ onError(PackageConfigFormatException('Missing configVersion entry', json));
+ configVersion = 2;
+ }
+ if (packageList == null) {
+ onError(PackageConfigFormatException('Missing packages list', json));
+ packageList = [];
+ }
+ return SimplePackageConfig(configVersion!, packageList!, extraData, (error) {
+ if (error is ArgumentError) {
+ onError(
+ PackageConfigFormatException(
+ error.message.toString(), error.invalidValue),
+ );
+ } else {
+ onError(error);
+ }
+ });
+}
+
+final _jsonUtf8Encoder = JsonUtf8Encoder(' ');
+
+void writePackageConfigJsonUtf8(
+ PackageConfig config, Uri? baseUri, Sink<List<int>> output) {
+ // Can be optimized.
+ var data = packageConfigToJson(config, baseUri);
+ output.add(_jsonUtf8Encoder.convert(data) as Uint8List);
+}
+
+void writePackageConfigJsonString(
+ PackageConfig config, Uri? baseUri, StringSink output) {
+ // Can be optimized.
+ var data = packageConfigToJson(config, baseUri);
+ output.write(const JsonEncoder.withIndent(' ').convert(data));
+}
+
+Map<String, Object?> packageConfigToJson(PackageConfig config, Uri? baseUri) =>
+ <String, Object?>{
+ ...?_extractExtraData(config.extraData, _topNames),
+ _configVersionKey: PackageConfig.maxVersion,
+ _packagesKey: [
+ for (var package in config.packages)
+ <String, Object?>{
+ _nameKey: package.name,
+ _rootUriKey: trailingSlash((package.relativeRoot
+ ? relativizeUri(package.root, baseUri)
+ : package.root)
+ .toString()),
+ if (package.root != package.packageUriRoot)
+ _packageUriKey: trailingSlash(
+ relativizeUri(package.packageUriRoot, package.root)
+ .toString()),
+ if (package.languageVersion != null &&
+ package.languageVersion is! InvalidLanguageVersion)
+ _languageVersionKey: package.languageVersion.toString(),
+ ...?_extractExtraData(package.extraData, _packageNames),
+ }
+ ],
+ };
+
+void writeDotPackages(PackageConfig config, Uri baseUri, StringSink output) {
+ var extraData = config.extraData;
+ // Write .packages too.
+ String? comment;
+ if (extraData is Map<String, Object?>) {
+ var generator = extraData[_generatorKey];
+ if (generator is String) {
+ var generated = extraData[_generatedKey];
+ var generatorVersion = extraData[_generatorVersionKey];
+ comment = 'Generated by $generator'
+ "${generatorVersion is String ? " $generatorVersion" : ""}"
+ "${generated is String ? " on $generated" : ""}.";
+ }
+ }
+ packages_file.write(output, config, baseUri: baseUri, comment: comment);
+}
+
+/// If "extraData" is a JSON map, then return it, otherwise return null.
+///
+/// If the value contains any of the [reservedNames] for the current context,
+/// entries with that name in the extra data are dropped.
+Map<String, Object?>? _extractExtraData(
+ Object? data, Iterable<String> reservedNames) {
+ if (data is Map<String, Object?>) {
+ if (data.isEmpty) return null;
+ for (var name in reservedNames) {
+ if (data.containsKey(name)) {
+ var filteredData = {
+ for (var key in data.keys)
+ if (!reservedNames.contains(key)) key: data[key]
+ };
+ if (filteredData.isEmpty) return null;
+ for (var value in filteredData.values) {
+ if (!_validateJson(value)) return null;
+ }
+ return filteredData;
+ }
+ }
+ return data;
+ }
+ return null;
+}
+
+/// Checks that the object is a valid JSON-like data structure.
+bool _validateJson(Object? object) {
+ if (object == null || true == object || false == object) return true;
+ if (object is num || object is String) return true;
+ if (object is List<Object?>) {
+ return object.every(_validateJson);
+ }
+ if (object is Map<String, Object?>) {
+ return object.values.every(_validateJson);
+ }
+ return false;
+}
diff --git a/pkgs/package_config/lib/src/packages_file.dart b/pkgs/package_config/lib/src/packages_file.dart
new file mode 100644
index 0000000..bf68f2c
--- /dev/null
+++ b/pkgs/package_config/lib/src/packages_file.dart
@@ -0,0 +1,193 @@
+// 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 'errors.dart';
+import 'package_config_impl.dart';
+import 'util.dart';
+
+/// The language version prior to the release of language versioning.
+///
+/// This is the default language version used by all packages from a
+/// `.packages` file.
+final LanguageVersion _languageVersion = LanguageVersion(2, 7);
+
+/// Parses a `.packages` file into a [PackageConfig].
+///
+/// The [source] is the byte content of a `.packages` file, assumed to be
+/// UTF-8 encoded. In practice, all significant parts of the file must be ASCII,
+/// so Latin-1 or Windows-1252 encoding will also work fine.
+///
+/// If the file content is available as a string, its [String.codeUnits] can
+/// be used as the `source` argument of this function.
+///
+/// The [baseLocation] is used as a base URI to resolve all relative
+/// URI references against.
+/// If the content was read from a file, `baseLocation` should be the
+/// location of that file.
+///
+/// Returns a simple package configuration where each package's
+/// [Package.packageUriRoot] is the same as its [Package.root]
+/// and it has no [Package.languageVersion].
+PackageConfig parse(
+ List<int> source, Uri baseLocation, void Function(Object error) onError) {
+ if (baseLocation.isScheme('package')) {
+ onError(PackageConfigArgumentError(
+ baseLocation, 'baseLocation', 'Must not be a package: URI'));
+ return PackageConfig.empty;
+ }
+ var index = 0;
+ var packages = <Package>[];
+ var packageNames = <String>{};
+ while (index < source.length) {
+ var ignoreLine = false;
+ var start = index;
+ var separatorIndex = -1;
+ var end = source.length;
+ var char = source[index++];
+ if (char == $cr || char == $lf) {
+ continue;
+ }
+ if (char == $colon) {
+ onError(PackageConfigFormatException(
+ 'Missing package name', source, index - 1));
+ ignoreLine = true; // Ignore if package name is invalid.
+ } else {
+ ignoreLine = char == $hash; // Ignore if comment.
+ }
+ var queryStart = -1;
+ var fragmentStart = -1;
+ while (index < source.length) {
+ char = source[index++];
+ if (char == $colon && separatorIndex < 0) {
+ separatorIndex = index - 1;
+ } else if (char == $cr || char == $lf) {
+ end = index - 1;
+ break;
+ } else if (char == $question && queryStart < 0 && fragmentStart < 0) {
+ queryStart = index - 1;
+ } else if (char == $hash && fragmentStart < 0) {
+ fragmentStart = index - 1;
+ }
+ }
+ if (ignoreLine) continue;
+ if (separatorIndex < 0) {
+ onError(
+ PackageConfigFormatException("No ':' on line", source, index - 1));
+ continue;
+ }
+ var packageName = String.fromCharCodes(source, start, separatorIndex);
+ var invalidIndex = checkPackageName(packageName);
+ if (invalidIndex >= 0) {
+ onError(PackageConfigFormatException(
+ 'Not a valid package name', source, start + invalidIndex));
+ continue;
+ }
+ if (queryStart >= 0) {
+ onError(PackageConfigFormatException(
+ 'Location URI must not have query', source, queryStart));
+ end = queryStart;
+ } else if (fragmentStart >= 0) {
+ onError(PackageConfigFormatException(
+ 'Location URI must not have fragment', source, fragmentStart));
+ end = fragmentStart;
+ }
+ var packageValue = String.fromCharCodes(source, separatorIndex + 1, end);
+ Uri packageLocation;
+ try {
+ packageLocation = Uri.parse(packageValue);
+ } on FormatException catch (e) {
+ onError(PackageConfigFormatException.from(e));
+ continue;
+ }
+ var relativeRoot = !hasAbsolutePath(packageLocation);
+ packageLocation = baseLocation.resolveUri(packageLocation);
+ if (packageLocation.isScheme('package')) {
+ onError(PackageConfigFormatException(
+ 'Package URI as location for package', source, separatorIndex + 1));
+ continue;
+ }
+ var path = packageLocation.path;
+ if (!path.endsWith('/')) {
+ path += '/';
+ packageLocation = packageLocation.replace(path: path);
+ }
+ if (packageNames.contains(packageName)) {
+ onError(PackageConfigFormatException(
+ 'Same package name occurred more than once', source, start));
+ continue;
+ }
+ var rootUri = packageLocation;
+ if (path.endsWith('/lib/')) {
+ // Assume default Pub package layout. Include package itself in root.
+ rootUri =
+ packageLocation.replace(path: path.substring(0, path.length - 4));
+ }
+ var package = SimplePackage.validate(packageName, rootUri, packageLocation,
+ _languageVersion, null, relativeRoot, (error) {
+ if (error is ArgumentError) {
+ onError(PackageConfigFormatException(error.message.toString(), source));
+ } else {
+ onError(error);
+ }
+ });
+ if (package != null) {
+ packages.add(package);
+ packageNames.add(packageName);
+ }
+ }
+ return SimplePackageConfig(1, packages, null, onError);
+}
+
+/// Writes the configuration to a [StringSink].
+///
+/// If [comment] is provided, the output will contain this comment
+/// with `# ` in front of each line.
+/// Lines are defined as ending in line feed (`'\n'`). If the final
+/// line of the comment doesn't end in a line feed, one will be added.
+///
+/// If [baseUri] is provided, package locations will be made relative
+/// to the base URI, if possible, before writing.
+void write(StringSink output, PackageConfig config,
+ {Uri? baseUri, String? comment}) {
+ if (baseUri != null && !baseUri.isAbsolute) {
+ throw PackageConfigArgumentError(baseUri, 'baseUri', 'Must be absolute');
+ }
+
+ if (comment != null) {
+ var lines = comment.split('\n');
+ if (lines.last.isEmpty) lines.removeLast();
+ for (var commentLine in lines) {
+ output.write('# ');
+ output.writeln(commentLine);
+ }
+ } else {
+ output.write('# generated by package:package_config at ');
+ output.write(DateTime.now());
+ output.writeln();
+ }
+ for (var package in config.packages) {
+ var packageName = package.name;
+ var uri = package.packageUriRoot;
+ // Validate packageName.
+ if (!isValidPackageName(packageName)) {
+ throw PackageConfigArgumentError(
+ config, 'config', '"$packageName" is not a valid package name');
+ }
+ if (uri.scheme == 'package') {
+ throw PackageConfigArgumentError(
+ config, 'config', 'Package location must not be a package URI: $uri');
+ }
+ output.write(packageName);
+ output.write(':');
+ // If baseUri is provided, make the URI relative to baseUri.
+ if (baseUri != null) {
+ uri = relativizeUri(uri, baseUri)!;
+ }
+ if (!uri.path.endsWith('/')) {
+ uri = uri.replace(path: '${uri.path}/');
+ }
+ output.write(uri);
+ output.writeln();
+ }
+}
diff --git a/pkgs/package_config/lib/src/util.dart b/pkgs/package_config/lib/src/util.dart
new file mode 100644
index 0000000..4f0210c
--- /dev/null
+++ b/pkgs/package_config/lib/src/util.dart
@@ -0,0 +1,253 @@
+// 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.
+
+/// Utility methods used by more than one library in the package.
+library;
+
+import 'errors.dart';
+
+// All ASCII characters that are valid in a package name, with space
+// for all the invalid ones (including space).
+const String _validPackageNameCharacters =
+ r" ! $ &'()*+,-. 0123456789 ; = "
+ r'@ABCDEFGHIJKLMNOPQRSTUVWXYZ _ abcdefghijklmnopqrstuvwxyz ~ ';
+
+/// Tests whether something is a valid Dart package name.
+bool isValidPackageName(String string) {
+ return checkPackageName(string) < 0;
+}
+
+/// Check if a string is a valid package name.
+///
+/// Valid package names contain only characters in [_validPackageNameCharacters]
+/// and must contain at least one non-'.' character.
+///
+/// Returns `-1` if the string is valid.
+/// Otherwise returns the index of the first invalid character,
+/// or `string.length` if the string contains no non-'.' character.
+int checkPackageName(String string) {
+ // Becomes non-zero if any non-'.' character is encountered.
+ var nonDot = 0;
+ for (var i = 0; i < string.length; i++) {
+ var c = string.codeUnitAt(i);
+ if (c > 0x7f || _validPackageNameCharacters.codeUnitAt(c) <= $space) {
+ return i;
+ }
+ nonDot += c ^ $dot;
+ }
+ if (nonDot == 0) return string.length;
+ return -1;
+}
+
+/// Validate that a [Uri] is a valid `package:` URI.
+///
+/// Used to validate user input.
+///
+/// Returns the package name extracted from the package URI,
+/// which is the path segment between `package:` and the first `/`.
+String checkValidPackageUri(Uri packageUri, String name) {
+ if (packageUri.scheme != 'package') {
+ throw PackageConfigArgumentError(packageUri, name, 'Not a package: URI');
+ }
+ if (packageUri.hasAuthority) {
+ throw PackageConfigArgumentError(
+ packageUri, name, 'Package URIs must not have a host part');
+ }
+ if (packageUri.hasQuery) {
+ // A query makes no sense if resolved to a file: URI.
+ throw PackageConfigArgumentError(
+ packageUri, name, 'Package URIs must not have a query part');
+ }
+ if (packageUri.hasFragment) {
+ // We could leave the fragment after the URL when resolving,
+ // but it would be odd if "package:foo/foo.dart#1" and
+ // "package:foo/foo.dart#2" were considered different libraries.
+ // Keep the syntax open in case we ever get multiple libraries in one file.
+ throw PackageConfigArgumentError(
+ packageUri, name, 'Package URIs must not have a fragment part');
+ }
+ if (packageUri.path.startsWith('/')) {
+ throw PackageConfigArgumentError(
+ packageUri, name, "Package URIs must not start with a '/'");
+ }
+ var firstSlash = packageUri.path.indexOf('/');
+ if (firstSlash == -1) {
+ throw PackageConfigArgumentError(packageUri, name,
+ "Package URIs must start with the package name followed by a '/'");
+ }
+ var packageName = packageUri.path.substring(0, firstSlash);
+ var badIndex = checkPackageName(packageName);
+ if (badIndex >= 0) {
+ if (packageName.isEmpty) {
+ throw PackageConfigArgumentError(
+ packageUri, name, 'Package names mus be non-empty');
+ }
+ if (badIndex == packageName.length) {
+ throw PackageConfigArgumentError(packageUri, name,
+ "Package names must contain at least one non-'.' character");
+ }
+ assert(badIndex < packageName.length);
+ var badCharCode = packageName.codeUnitAt(badIndex);
+ var badChar = 'U+${badCharCode.toRadixString(16).padLeft(4, '0')}';
+ if (badCharCode >= 0x20 && badCharCode <= 0x7e) {
+ // Printable character.
+ badChar = "'${packageName[badIndex]}' ($badChar)";
+ }
+ throw PackageConfigArgumentError(
+ packageUri, name, 'Package names must not contain $badChar');
+ }
+ return packageName;
+}
+
+/// Checks whether URI is just an absolute directory.
+///
+/// * It must have a scheme.
+/// * It must not have a query or fragment.
+/// * The path must end with `/`.
+bool isAbsoluteDirectoryUri(Uri uri) {
+ if (uri.hasQuery) return false;
+ if (uri.hasFragment) return false;
+ if (!uri.hasScheme) return false;
+ var path = uri.path;
+ if (!path.endsWith('/')) return false;
+ return true;
+}
+
+/// Whether the former URI is a prefix of the latter.
+bool isUriPrefix(Uri prefix, Uri path) {
+ assert(!prefix.hasFragment);
+ assert(!prefix.hasQuery);
+ assert(!path.hasQuery);
+ assert(!path.hasFragment);
+ assert(prefix.path.endsWith('/'));
+ return path.toString().startsWith(prefix.toString());
+}
+
+/// Finds the first non-JSON-whitespace character in a file.
+///
+/// Used to heuristically detect whether a file is a JSON file or an .ini file.
+int firstNonWhitespaceChar(List<int> bytes) {
+ for (var i = 0; i < bytes.length; i++) {
+ var char = bytes[i];
+ if (char != 0x20 && char != 0x09 && char != 0x0a && char != 0x0d) {
+ return char;
+ }
+ }
+ return -1;
+}
+
+/// Appends a trailing `/` if the path doesn't end with one.
+String trailingSlash(String path) {
+ if (path.isEmpty || path.endsWith('/')) return path;
+ return '$path/';
+}
+
+/// Whether a URI should not be considered relative to the base URI.
+///
+/// Used to determine whether a parsed root URI is relative
+/// to the configuration file or not.
+/// If it is relative, then it's rewritten as relative when
+/// output again later. If not, it's output as absolute.
+bool hasAbsolutePath(Uri uri) =>
+ uri.hasScheme || uri.hasAuthority || uri.hasAbsolutePath;
+
+/// Attempts to return a relative path-only URI for [uri].
+///
+/// First removes any query or fragment part from [uri].
+///
+/// If [uri] is already relative (has no scheme), it's returned as-is.
+/// If that is not desired, the caller can pass `baseUri.resolveUri(uri)`
+/// as the [uri] instead.
+///
+/// If the [uri] has a scheme or authority part which differs from
+/// the [baseUri], or if there is no overlap in the paths of the
+/// two URIs at all, the [uri] is returned as-is.
+///
+/// Otherwise the result is a path-only URI which satisfies
+/// `baseUri.resolveUri(result) == uri`,
+///
+/// The `baseUri` must be absolute.
+Uri? relativizeUri(Uri? uri, Uri? baseUri) {
+ if (baseUri == null) return uri;
+ assert(baseUri.isAbsolute);
+ if (uri!.hasQuery || uri.hasFragment) {
+ uri = Uri(
+ scheme: uri.scheme,
+ userInfo: uri.hasAuthority ? uri.userInfo : null,
+ host: uri.hasAuthority ? uri.host : null,
+ port: uri.hasAuthority ? uri.port : null,
+ path: uri.path);
+ }
+
+ // Already relative. We assume the caller knows what they are doing.
+ if (!uri.isAbsolute) return uri;
+
+ if (baseUri.scheme != uri.scheme) {
+ return uri;
+ }
+
+ // If authority differs, we could remove the scheme, but it's not worth it.
+ if (uri.hasAuthority != baseUri.hasAuthority) return uri;
+ if (uri.hasAuthority) {
+ if (uri.userInfo != baseUri.userInfo ||
+ uri.host.toLowerCase() != baseUri.host.toLowerCase() ||
+ uri.port != baseUri.port) {
+ return uri;
+ }
+ }
+
+ baseUri = baseUri.normalizePath();
+ var base = [...baseUri.pathSegments];
+ if (base.isNotEmpty) base.removeLast();
+ uri = uri.normalizePath();
+ var target = [...uri.pathSegments];
+ if (target.isNotEmpty && target.last.isEmpty) target.removeLast();
+ var index = 0;
+ while (index < base.length && index < target.length) {
+ if (base[index] != target[index]) {
+ break;
+ }
+ index++;
+ }
+ if (index == base.length) {
+ if (index == target.length) {
+ return Uri(path: './');
+ }
+ return Uri(path: target.skip(index).join('/'));
+ } else if (index > 0) {
+ var buffer = StringBuffer();
+ for (var n = base.length - index; n > 0; --n) {
+ buffer.write('../');
+ }
+ buffer.writeAll(target.skip(index), '/');
+ return Uri(path: buffer.toString());
+ } else {
+ return uri;
+ }
+}
+
+// Character constants used by this package.
+/// "Line feed" control character.
+const int $lf = 0x0a;
+
+/// "Carriage return" control character.
+const int $cr = 0x0d;
+
+/// Space character.
+const int $space = 0x20;
+
+/// Character `#`.
+const int $hash = 0x23;
+
+/// Character `.`.
+const int $dot = 0x2e;
+
+/// Character `:`.
+const int $colon = 0x3a;
+
+/// Character `?`.
+const int $question = 0x3f;
+
+/// Character `{`.
+const int $lbrace = 0x7b;
diff --git a/pkgs/package_config/lib/src/util_io.dart b/pkgs/package_config/lib/src/util_io.dart
new file mode 100644
index 0000000..4680eef
--- /dev/null
+++ b/pkgs/package_config/lib/src/util_io.dart
@@ -0,0 +1,108 @@
+// Copyright (c) 2020, 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.
+
+/// Utility methods requiring dart:io and used by more than one library in the
+/// package.
+library;
+
+import 'dart:io';
+import 'dart:typed_data';
+
+Future<Uint8List?> defaultLoader(Uri uri) async {
+ if (uri.isScheme('file')) {
+ var file = File.fromUri(uri);
+ try {
+ return await file.readAsBytes();
+ } catch (_) {
+ return null;
+ }
+ }
+ if (uri.isScheme('http') || uri.isScheme('https')) {
+ return _httpGet(uri);
+ }
+ throw UnsupportedError('Default URI unsupported scheme: $uri');
+}
+
+Future<Uint8List?> _httpGet(Uri uri) async {
+ assert(uri.isScheme('http') || uri.isScheme('https'));
+ var client = HttpClient();
+ var request = await client.getUrl(uri);
+ var response = await request.close();
+ if (response.statusCode != HttpStatus.ok) {
+ return null;
+ }
+ var splitContent = await response.toList();
+ var totalLength = 0;
+ if (splitContent.length == 1) {
+ var part = splitContent[0];
+ if (part is Uint8List) {
+ return part;
+ }
+ }
+ for (var list in splitContent) {
+ totalLength += list.length;
+ }
+ var result = Uint8List(totalLength);
+ var offset = 0;
+ for (var contentPart in splitContent as Iterable<Uint8List>) {
+ result.setRange(offset, offset + contentPart.length, contentPart);
+ offset += contentPart.length;
+ }
+ return result;
+}
+
+/// The file name of a path.
+///
+/// The file name is everything after the last occurrence of
+/// [Platform.pathSeparator], or the entire string if no
+/// path separator occurs in the string.
+String fileName(String path) {
+ var separator = Platform.pathSeparator;
+ var lastSeparator = path.lastIndexOf(separator);
+ if (lastSeparator < 0) return path;
+ return path.substring(lastSeparator + separator.length);
+}
+
+/// The directory name of a path.
+///
+/// The directory name is everything before the last occurrence of
+/// [Platform.pathSeparator], or the empty string if no
+/// path separator occurs in the string.
+String dirName(String path) {
+ var separator = Platform.pathSeparator;
+ var lastSeparator = path.lastIndexOf(separator);
+ if (lastSeparator < 0) return '';
+ return path.substring(0, lastSeparator);
+}
+
+/// Join path parts with the [Platform.pathSeparator].
+///
+/// If a part ends with a path separator, then no extra separator is
+/// inserted.
+String pathJoin(String part1, String part2, [String? part3]) {
+ var separator = Platform.pathSeparator;
+ var separator1 = part1.endsWith(separator) ? '' : separator;
+ if (part3 == null) {
+ return '$part1$separator1$part2';
+ }
+ var separator2 = part2.endsWith(separator) ? '' : separator;
+ return '$part1$separator1$part2$separator2$part3';
+}
+
+/// Join an unknown number of path parts with [Platform.pathSeparator].
+///
+/// If a part ends with a path separator, then no extra separator is
+/// inserted.
+String pathJoinAll(Iterable<String> parts) {
+ var buffer = StringBuffer();
+ var separator = '';
+ for (var part in parts) {
+ buffer
+ ..write(separator)
+ ..write(part);
+ separator =
+ part.endsWith(Platform.pathSeparator) ? '' : Platform.pathSeparator;
+ }
+ return buffer.toString();
+}
diff --git a/pkgs/package_config/pubspec.yaml b/pkgs/package_config/pubspec.yaml
new file mode 100644
index 0000000..28f3e13
--- /dev/null
+++ b/pkgs/package_config/pubspec.yaml
@@ -0,0 +1,14 @@
+name: package_config
+version: 2.1.1
+description: Support for reading and writing Dart Package Configuration files.
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/package_config
+
+environment:
+ sdk: ^3.4.0
+
+dependencies:
+ path: ^1.8.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ test: ^1.16.0
diff --git a/pkgs/package_config/test/bench.dart b/pkgs/package_config/test/bench.dart
new file mode 100644
index 0000000..8428481
--- /dev/null
+++ b/pkgs/package_config/test/bench.dart
@@ -0,0 +1,71 @@
+// Copyright (c) 2021, 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:typed_data';
+
+import 'package:package_config/src/errors.dart';
+import 'package:package_config/src/package_config_json.dart';
+
+void bench(final int size, final bool doPrint) {
+ var sb = StringBuffer();
+ sb.writeln('{');
+ sb.writeln('"configVersion": 2,');
+ sb.writeln('"packages": [');
+ for (var i = 0; i < size; i++) {
+ if (i != 0) {
+ sb.writeln(',');
+ }
+ sb.writeln('{');
+ sb.writeln(' "name": "p_$i",');
+ sb.writeln(' "rootUri": "file:///p_$i/",');
+ sb.writeln(' "packageUri": "lib/",');
+ sb.writeln(' "languageVersion": "2.5",');
+ sb.writeln(' "nonstandard": true');
+ sb.writeln('}');
+ }
+ sb.writeln('],');
+ sb.writeln('"generator": "pub",');
+ sb.writeln('"other": [42]');
+ sb.writeln('}');
+ var stopwatch = Stopwatch()..start();
+ var config = parsePackageConfigBytes(
+ // ignore: unnecessary_cast
+ utf8.encode(sb.toString()) as Uint8List,
+ Uri.parse('file:///tmp/.dart_tool/file.dart'),
+ throwError,
+ );
+ final read = stopwatch.elapsedMilliseconds;
+
+ stopwatch.reset();
+ for (var i = 0; i < size; i++) {
+ if (config.packageOf(Uri.parse('file:///p_$i/lib/src/foo.dart'))!.name !=
+ 'p_$i') {
+ throw StateError('Unexpected result!');
+ }
+ }
+ final lookup = stopwatch.elapsedMilliseconds;
+
+ if (doPrint) {
+ print('Read file with $size packages in $read ms, '
+ 'looked up all packages in $lookup ms');
+ }
+}
+
+void main(List<String> args) {
+ if (args.length != 1 && args.length != 2) {
+ throw ArgumentError('Expects arguments: <size> <warmup iterations>?');
+ }
+ final size = int.parse(args[0]);
+ if (args.length > 1) {
+ final warmups = int.parse(args[1]);
+ print('Performing $warmups warmup iterations.');
+ for (var i = 0; i < warmups; i++) {
+ bench(10, false);
+ }
+ }
+
+ // Benchmark.
+ bench(size, true);
+}
diff --git a/pkgs/package_config/test/discovery_test.dart b/pkgs/package_config/test/discovery_test.dart
new file mode 100644
index 0000000..6d1b655
--- /dev/null
+++ b/pkgs/package_config/test/discovery_test.dart
@@ -0,0 +1,346 @@
+// 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.
+
+@TestOn('vm')
+library;
+
+import 'dart:io';
+
+import 'package:package_config/package_config.dart';
+import 'package:test/test.dart';
+
+import 'src/util.dart';
+import 'src/util_io.dart';
+
+const packagesFile = '''
+# A comment
+foo:file:///dart/packages/foo/
+bar:/dart/packages/bar/
+baz:packages/baz/
+''';
+
+const packageConfigFile = '''
+{
+ "configVersion": 2,
+ "packages": [
+ {
+ "name": "foo",
+ "rootUri": "file:///dart/packages/foo/"
+ },
+ {
+ "name": "bar",
+ "rootUri": "/dart/packages/bar/"
+ },
+ {
+ "name": "baz",
+ "rootUri": "../packages/baz/"
+ }
+ ],
+ "extra": [42]
+}
+''';
+
+void validatePackagesFile(PackageConfig resolver, Directory directory) {
+ expect(resolver, isNotNull);
+ expect(resolver.resolve(pkg('foo', 'bar/baz')),
+ equals(Uri.parse('file:///dart/packages/foo/bar/baz')));
+ expect(resolver.resolve(pkg('bar', 'baz/qux')),
+ equals(Uri.parse('file:///dart/packages/bar/baz/qux')));
+ expect(resolver.resolve(pkg('baz', 'qux/foo')),
+ equals(Uri.directory(directory.path).resolve('packages/baz/qux/foo')));
+ expect([for (var p in resolver.packages) p.name],
+ unorderedEquals(['foo', 'bar', 'baz']));
+}
+
+void main() {
+ group('findPackages', () {
+ // Finds package_config.json if there.
+ fileTest('package_config.json', {
+ '.packages': 'invalid .packages file',
+ 'script.dart': 'main(){}',
+ 'packages': {'shouldNotBeFound': <Never, Never>{}},
+ '.dart_tool': {
+ 'package_config.json': packageConfigFile,
+ }
+ }, (Directory directory) async {
+ var config = (await findPackageConfig(directory))!;
+ expect(config.version, 2); // Found package_config.json file.
+ validatePackagesFile(config, directory);
+ });
+
+ // Finds .packages if no package_config.json.
+ fileTest('.packages', {
+ '.packages': packagesFile,
+ 'script.dart': 'main(){}',
+ 'packages': {'shouldNotBeFound': <Object, Object>{}}
+ }, (Directory directory) async {
+ var config = (await findPackageConfig(directory))!;
+ expect(config.version, 1); // Found .packages file.
+ validatePackagesFile(config, directory);
+ });
+
+ // Finds package_config.json in super-directory.
+ fileTest('package_config.json recursive', {
+ '.packages': packagesFile,
+ '.dart_tool': {
+ 'package_config.json': packageConfigFile,
+ },
+ 'subdir': {
+ 'script.dart': 'main(){}',
+ }
+ }, (Directory directory) async {
+ var config = (await findPackageConfig(subdir(directory, 'subdir/')))!;
+ expect(config.version, 2);
+ validatePackagesFile(config, directory);
+ });
+
+ // Finds .packages in super-directory.
+ fileTest('.packages recursive', {
+ '.packages': packagesFile,
+ 'subdir': {'script.dart': 'main(){}'}
+ }, (Directory directory) async {
+ var config = (await findPackageConfig(subdir(directory, 'subdir/')))!;
+ expect(config.version, 1);
+ validatePackagesFile(config, directory);
+ });
+
+ // Does not find a packages/ directory, and returns null if nothing found.
+ fileTest('package directory packages not supported', {
+ 'packages': {
+ 'foo': <String, dynamic>{},
+ }
+ }, (Directory directory) async {
+ var config = await findPackageConfig(directory);
+ expect(config, null);
+ });
+
+ group('throws', () {
+ fileTest('invalid .packages', {
+ '.packages': 'not a .packages file',
+ }, (Directory directory) {
+ expect(findPackageConfig(directory), throwsA(isA<FormatException>()));
+ });
+
+ fileTest('invalid .packages as JSON', {
+ '.packages': packageConfigFile,
+ }, (Directory directory) {
+ expect(findPackageConfig(directory), throwsA(isA<FormatException>()));
+ });
+
+ fileTest('invalid .packages', {
+ '.dart_tool': {
+ 'package_config.json': 'not a JSON file',
+ }
+ }, (Directory directory) {
+ expect(findPackageConfig(directory), throwsA(isA<FormatException>()));
+ });
+
+ fileTest('invalid .packages as INI', {
+ '.dart_tool': {
+ 'package_config.json': packagesFile,
+ }
+ }, (Directory directory) {
+ expect(findPackageConfig(directory), throwsA(isA<FormatException>()));
+ });
+ });
+
+ group('handles error', () {
+ fileTest('invalid .packages', {
+ '.packages': 'not a .packages file',
+ }, (Directory directory) async {
+ var hadError = false;
+ await findPackageConfig(directory,
+ onError: expectAsync1((error) {
+ hadError = true;
+ expect(error, isA<FormatException>());
+ }, max: -1));
+ expect(hadError, true);
+ });
+
+ fileTest('invalid .packages as JSON', {
+ '.packages': packageConfigFile,
+ }, (Directory directory) async {
+ var hadError = false;
+ await findPackageConfig(directory,
+ onError: expectAsync1((error) {
+ hadError = true;
+ expect(error, isA<FormatException>());
+ }, max: -1));
+ expect(hadError, true);
+ });
+
+ fileTest('invalid package_config not JSON', {
+ '.dart_tool': {
+ 'package_config.json': 'not a JSON file',
+ }
+ }, (Directory directory) async {
+ var hadError = false;
+ await findPackageConfig(directory,
+ onError: expectAsync1((error) {
+ hadError = true;
+ expect(error, isA<FormatException>());
+ }, max: -1));
+ expect(hadError, true);
+ });
+
+ fileTest('invalid package config as INI', {
+ '.dart_tool': {
+ 'package_config.json': packagesFile,
+ }
+ }, (Directory directory) async {
+ var hadError = false;
+ await findPackageConfig(directory,
+ onError: expectAsync1((error) {
+ hadError = true;
+ expect(error, isA<FormatException>());
+ }, max: -1));
+ expect(hadError, true);
+ });
+ });
+
+ // Does not find .packages if no package_config.json and minVersion > 1.
+ fileTest('.packages ignored', {
+ '.packages': packagesFile,
+ 'script.dart': 'main(){}'
+ }, (Directory directory) async {
+ var config = await findPackageConfig(directory, minVersion: 2);
+ expect(config, null);
+ });
+
+ // Finds package_config.json in super-directory, with .packages in
+ // subdir and minVersion > 1.
+ fileTest('package_config.json recursive .packages ignored', {
+ '.dart_tool': {
+ 'package_config.json': packageConfigFile,
+ },
+ 'subdir': {
+ '.packages': packagesFile,
+ 'script.dart': 'main(){}',
+ }
+ }, (Directory directory) async {
+ var config = (await findPackageConfig(subdir(directory, 'subdir/'),
+ minVersion: 2))!;
+ expect(config.version, 2);
+ validatePackagesFile(config, directory);
+ });
+ });
+
+ group('loadPackageConfig', () {
+ // Load a specific files
+ group('package_config.json', () {
+ var files = {
+ '.packages': packagesFile,
+ '.dart_tool': {
+ 'package_config.json': packageConfigFile,
+ },
+ };
+ fileTest('directly', files, (Directory directory) async {
+ var file =
+ dirFile(subdir(directory, '.dart_tool'), 'package_config.json');
+ var config = await loadPackageConfig(file);
+ expect(config.version, 2);
+ validatePackagesFile(config, directory);
+ });
+ fileTest('indirectly through .packages', files,
+ (Directory directory) async {
+ var file = dirFile(directory, '.packages');
+ var config = await loadPackageConfig(file);
+ expect(config.version, 2);
+ validatePackagesFile(config, directory);
+ });
+ fileTest('prefer .packages', files, (Directory directory) async {
+ var file = dirFile(directory, '.packages');
+ var config = await loadPackageConfig(file, preferNewest: false);
+ expect(config.version, 1);
+ validatePackagesFile(config, directory);
+ });
+ });
+
+ fileTest('package_config.json non-default name', {
+ '.packages': packagesFile,
+ 'subdir': {
+ 'pheldagriff': packageConfigFile,
+ },
+ }, (Directory directory) async {
+ var file = dirFile(directory, 'subdir/pheldagriff');
+ var config = await loadPackageConfig(file);
+ expect(config.version, 2);
+ validatePackagesFile(config, directory);
+ });
+
+ fileTest('package_config.json named .packages', {
+ 'subdir': {
+ '.packages': packageConfigFile,
+ },
+ }, (Directory directory) async {
+ var file = dirFile(directory, 'subdir/.packages');
+ var config = await loadPackageConfig(file);
+ expect(config.version, 2);
+ validatePackagesFile(config, directory);
+ });
+
+ fileTest('.packages', {
+ '.packages': packagesFile,
+ }, (Directory directory) async {
+ var file = dirFile(directory, '.packages');
+ var config = await loadPackageConfig(file);
+ expect(config.version, 1);
+ validatePackagesFile(config, directory);
+ });
+
+ fileTest('.packages non-default name', {
+ 'pheldagriff': packagesFile,
+ }, (Directory directory) async {
+ var file = dirFile(directory, 'pheldagriff');
+ var config = await loadPackageConfig(file);
+ expect(config.version, 1);
+ validatePackagesFile(config, directory);
+ });
+
+ fileTest('no config found', {}, (Directory directory) {
+ var file = dirFile(directory, 'anyname');
+ expect(
+ () => loadPackageConfig(file), throwsA(isA<FileSystemException>()));
+ });
+
+ fileTest('no config found, handled', {}, (Directory directory) async {
+ var file = dirFile(directory, 'anyname');
+ var hadError = false;
+ await loadPackageConfig(file,
+ onError: expectAsync1((error) {
+ hadError = true;
+ expect(error, isA<FileSystemException>());
+ }, max: -1));
+ expect(hadError, true);
+ });
+
+ fileTest('specified file syntax error', {
+ 'anyname': 'syntax error',
+ }, (Directory directory) {
+ var file = dirFile(directory, 'anyname');
+ expect(() => loadPackageConfig(file), throwsFormatException);
+ });
+
+ // Find package_config.json in subdir even if initial file syntax error.
+ fileTest('specified file syntax onError', {
+ '.packages': 'syntax error',
+ '.dart_tool': {
+ 'package_config.json': packageConfigFile,
+ },
+ }, (Directory directory) async {
+ var file = dirFile(directory, '.packages');
+ var config = await loadPackageConfig(file);
+ expect(config.version, 2);
+ validatePackagesFile(config, directory);
+ });
+
+ // A file starting with `{` is a package_config.json file.
+ fileTest('file syntax error with {', {
+ '.packages': '{syntax error',
+ }, (Directory directory) {
+ var file = dirFile(directory, '.packages');
+ expect(() => loadPackageConfig(file), throwsFormatException);
+ });
+ });
+}
diff --git a/pkgs/package_config/test/discovery_uri_test.dart b/pkgs/package_config/test/discovery_uri_test.dart
new file mode 100644
index 0000000..542bf0a
--- /dev/null
+++ b/pkgs/package_config/test/discovery_uri_test.dart
@@ -0,0 +1,310 @@
+// 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.
+
+@TestOn('vm')
+library;
+
+import 'package:package_config/package_config.dart';
+import 'package:test/test.dart';
+
+import 'src/util.dart';
+
+const packagesFile = '''
+# A comment
+foo:file:///dart/packages/foo/
+bar:/dart/packages/bar/
+baz:packages/baz/
+''';
+
+const packageConfigFile = '''
+{
+ "configVersion": 2,
+ "packages": [
+ {
+ "name": "foo",
+ "rootUri": "file:///dart/packages/foo/"
+ },
+ {
+ "name": "bar",
+ "rootUri": "/dart/packages/bar/"
+ },
+ {
+ "name": "baz",
+ "rootUri": "../packages/baz/"
+ }
+ ],
+ "extra": [42]
+}
+''';
+
+void validatePackagesFile(PackageConfig resolver, Uri directory) {
+ expect(resolver, isNotNull);
+ expect(resolver.resolve(pkg('foo', 'bar/baz')),
+ equals(Uri.parse('file:///dart/packages/foo/bar/baz')));
+ expect(resolver.resolve(pkg('bar', 'baz/qux')),
+ equals(directory.resolve('/dart/packages/bar/baz/qux')));
+ expect(resolver.resolve(pkg('baz', 'qux/foo')),
+ equals(directory.resolve('packages/baz/qux/foo')));
+ expect([for (var p in resolver.packages) p.name],
+ unorderedEquals(['foo', 'bar', 'baz']));
+}
+
+void main() {
+ group('findPackages', () {
+ // Finds package_config.json if there.
+ loaderTest('package_config.json', {
+ '.packages': 'invalid .packages file',
+ 'script.dart': 'main(){}',
+ 'packages': {'shouldNotBeFound': <String, dynamic>{}},
+ '.dart_tool': {
+ 'package_config.json': packageConfigFile,
+ }
+ }, (directory, loader) async {
+ var config = (await findPackageConfigUri(directory, loader: loader))!;
+ expect(config.version, 2); // Found package_config.json file.
+ validatePackagesFile(config, directory);
+ });
+
+ // Finds .packages if no package_config.json.
+ loaderTest('.packages', {
+ '.packages': packagesFile,
+ 'script.dart': 'main(){}',
+ 'packages': {'shouldNotBeFound': <String, dynamic>{}}
+ }, (directory, loader) async {
+ var config = (await findPackageConfigUri(directory, loader: loader))!;
+ expect(config.version, 1); // Found .packages file.
+ validatePackagesFile(config, directory);
+ });
+
+ // Finds package_config.json in super-directory.
+ loaderTest('package_config.json recursive', {
+ '.packages': packagesFile,
+ '.dart_tool': {
+ 'package_config.json': packageConfigFile,
+ },
+ 'subdir': {
+ 'script.dart': 'main(){}',
+ }
+ }, (directory, loader) async {
+ var config = (await findPackageConfigUri(directory.resolve('subdir/'),
+ loader: loader))!;
+ expect(config.version, 2);
+ validatePackagesFile(config, directory);
+ });
+
+ // Finds .packages in super-directory.
+ loaderTest('.packages recursive', {
+ '.packages': packagesFile,
+ 'subdir': {'script.dart': 'main(){}'}
+ }, (directory, loader) async {
+ var config = (await findPackageConfigUri(directory.resolve('subdir/'),
+ loader: loader))!;
+ expect(config.version, 1);
+ validatePackagesFile(config, directory);
+ });
+
+ // Does not find a packages/ directory, and returns null if nothing found.
+ loaderTest('package directory packages not supported', {
+ 'packages': {
+ 'foo': <String, dynamic>{},
+ }
+ }, (Uri directory, loader) async {
+ var config = await findPackageConfigUri(directory, loader: loader);
+ expect(config, null);
+ });
+
+ loaderTest('invalid .packages', {
+ '.packages': 'not a .packages file',
+ }, (Uri directory, loader) {
+ expect(() => findPackageConfigUri(directory, loader: loader),
+ throwsA(isA<FormatException>()));
+ });
+
+ loaderTest('invalid .packages as JSON', {
+ '.packages': packageConfigFile,
+ }, (Uri directory, loader) {
+ expect(() => findPackageConfigUri(directory, loader: loader),
+ throwsA(isA<FormatException>()));
+ });
+
+ loaderTest('invalid .packages', {
+ '.dart_tool': {
+ 'package_config.json': 'not a JSON file',
+ }
+ }, (Uri directory, loader) {
+ expect(() => findPackageConfigUri(directory, loader: loader),
+ throwsA(isA<FormatException>()));
+ });
+
+ loaderTest('invalid .packages as INI', {
+ '.dart_tool': {
+ 'package_config.json': packagesFile,
+ }
+ }, (Uri directory, loader) {
+ expect(() => findPackageConfigUri(directory, loader: loader),
+ throwsA(isA<FormatException>()));
+ });
+
+ // Does not find .packages if no package_config.json and minVersion > 1.
+ loaderTest('.packages ignored', {
+ '.packages': packagesFile,
+ 'script.dart': 'main(){}'
+ }, (directory, loader) async {
+ var config =
+ await findPackageConfigUri(directory, minVersion: 2, loader: loader);
+ expect(config, null);
+ });
+
+ // Finds package_config.json in super-directory, with .packages in
+ // subdir and minVersion > 1.
+ loaderTest('package_config.json recursive ignores .packages', {
+ '.dart_tool': {
+ 'package_config.json': packageConfigFile,
+ },
+ 'subdir': {
+ '.packages': packagesFile,
+ 'script.dart': 'main(){}',
+ }
+ }, (directory, loader) async {
+ var config = (await findPackageConfigUri(directory.resolve('subdir/'),
+ minVersion: 2, loader: loader))!;
+ expect(config.version, 2);
+ validatePackagesFile(config, directory);
+ });
+ });
+
+ group('loadPackageConfig', () {
+ // Load a specific files
+ group('package_config.json', () {
+ var files = {
+ '.packages': packagesFile,
+ '.dart_tool': {
+ 'package_config.json': packageConfigFile,
+ },
+ };
+ loaderTest('directly', files, (Uri directory, loader) async {
+ var file = directory.resolve('.dart_tool/package_config.json');
+ var config = await loadPackageConfigUri(file, loader: loader);
+ expect(config.version, 2);
+ validatePackagesFile(config, directory);
+ });
+ loaderTest('indirectly through .packages', files,
+ (Uri directory, loader) async {
+ var file = directory.resolve('.packages');
+ var config = await loadPackageConfigUri(file, loader: loader);
+ expect(config.version, 2);
+ validatePackagesFile(config, directory);
+ });
+ });
+
+ loaderTest('package_config.json non-default name', {
+ '.packages': packagesFile,
+ 'subdir': {
+ 'pheldagriff': packageConfigFile,
+ },
+ }, (Uri directory, loader) async {
+ var file = directory.resolve('subdir/pheldagriff');
+ var config = await loadPackageConfigUri(file, loader: loader);
+ expect(config.version, 2);
+ validatePackagesFile(config, directory);
+ });
+
+ loaderTest('package_config.json named .packages', {
+ 'subdir': {
+ '.packages': packageConfigFile,
+ },
+ }, (Uri directory, loader) async {
+ var file = directory.resolve('subdir/.packages');
+ var config = await loadPackageConfigUri(file, loader: loader);
+ expect(config.version, 2);
+ validatePackagesFile(config, directory);
+ });
+
+ loaderTest('.packages', {
+ '.packages': packagesFile,
+ }, (Uri directory, loader) async {
+ var file = directory.resolve('.packages');
+ var config = await loadPackageConfigUri(file, loader: loader);
+ expect(config.version, 1);
+ validatePackagesFile(config, directory);
+ });
+
+ loaderTest('.packages non-default name', {
+ 'pheldagriff': packagesFile,
+ }, (Uri directory, loader) async {
+ var file = directory.resolve('pheldagriff');
+ var config = await loadPackageConfigUri(file, loader: loader);
+ expect(config.version, 1);
+ validatePackagesFile(config, directory);
+ });
+
+ loaderTest('no config found', {}, (Uri directory, loader) {
+ var file = directory.resolve('anyname');
+ expect(() => loadPackageConfigUri(file, loader: loader),
+ throwsA(isA<ArgumentError>()));
+ });
+
+ loaderTest('no config found, handle error', {},
+ (Uri directory, loader) async {
+ var file = directory.resolve('anyname');
+ var hadError = false;
+ await loadPackageConfigUri(file,
+ loader: loader,
+ onError: expectAsync1((error) {
+ hadError = true;
+ expect(error, isA<ArgumentError>());
+ }, max: -1));
+ expect(hadError, true);
+ });
+
+ loaderTest('specified file syntax error', {
+ 'anyname': 'syntax error',
+ }, (Uri directory, loader) {
+ var file = directory.resolve('anyname');
+ expect(() => loadPackageConfigUri(file, loader: loader),
+ throwsFormatException);
+ });
+
+ loaderTest('specified file syntax onError', {
+ 'anyname': 'syntax error',
+ }, (directory, loader) async {
+ var file = directory.resolve('anyname');
+ var hadError = false;
+ await loadPackageConfigUri(file,
+ loader: loader,
+ onError: expectAsync1((error) {
+ hadError = true;
+ expect(error, isA<FormatException>());
+ }, max: -1));
+ expect(hadError, true);
+ });
+
+ // Don't look for package_config.json if original file not named .packages.
+ loaderTest('specified file syntax error with alternative', {
+ 'anyname': 'syntax error',
+ '.dart_tool': {
+ 'package_config.json': packageConfigFile,
+ },
+ }, (directory, loader) async {
+ var file = directory.resolve('anyname');
+ expect(() => loadPackageConfigUri(file, loader: loader),
+ throwsFormatException);
+ });
+
+ // A file starting with `{` is a package_config.json file.
+ loaderTest('file syntax error with {', {
+ '.packages': '{syntax error',
+ }, (directory, loader) async {
+ var file = directory.resolve('.packages');
+ var hadError = false;
+ await loadPackageConfigUri(file,
+ loader: loader,
+ onError: expectAsync1((error) {
+ hadError = true;
+ expect(error, isA<FormatException>());
+ }, max: -1));
+ expect(hadError, true);
+ });
+ });
+}
diff --git a/pkgs/package_config/test/package_config_impl_test.dart b/pkgs/package_config/test/package_config_impl_test.dart
new file mode 100644
index 0000000..0f39963
--- /dev/null
+++ b/pkgs/package_config/test/package_config_impl_test.dart
@@ -0,0 +1,188 @@
+// Copyright (c) 2020, 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' show jsonDecode;
+
+import 'package:package_config/package_config_types.dart';
+import 'package:test/test.dart';
+import 'src/util.dart';
+
+void main() {
+ var unique = Object();
+ var root = Uri.file('/tmp/root/');
+
+ group('LanguageVersion', () {
+ test('minimal', () {
+ var version = LanguageVersion(3, 5);
+ expect(version.major, 3);
+ expect(version.minor, 5);
+ });
+
+ test('negative major', () {
+ expect(() => LanguageVersion(-1, 1), throwsArgumentError);
+ });
+
+ test('negative minor', () {
+ expect(() => LanguageVersion(1, -1), throwsArgumentError);
+ });
+
+ test('minimal parse', () {
+ var version = LanguageVersion.parse('3.5');
+ expect(version.major, 3);
+ expect(version.minor, 5);
+ });
+
+ void failParse(String name, String input) {
+ test('$name - error', () {
+ expect(() => LanguageVersion.parse(input),
+ throwsA(isA<PackageConfigError>()));
+ expect(() => LanguageVersion.parse(input), throwsFormatException);
+ var failed = false;
+ var actual = LanguageVersion.parse(input, onError: (_) {
+ failed = true;
+ });
+ expect(failed, true);
+ expect(actual, isA<LanguageVersion>());
+ });
+ }
+
+ failParse('Leading zero major', '01.1');
+ failParse('Leading zero minor', '1.01');
+ failParse('Sign+ major', '+1.1');
+ failParse('Sign- major', '-1.1');
+ failParse('Sign+ minor', '1.+1');
+ failParse('Sign- minor', '1.-1');
+ failParse('WhiteSpace 1', ' 1.1');
+ failParse('WhiteSpace 2', '1 .1');
+ failParse('WhiteSpace 3', '1. 1');
+ failParse('WhiteSpace 4', '1.1 ');
+ });
+
+ group('Package', () {
+ test('minimal', () {
+ var package = Package('name', root, extraData: unique);
+ expect(package.name, 'name');
+ expect(package.root, root);
+ expect(package.packageUriRoot, root);
+ expect(package.languageVersion, null);
+ expect(package.extraData, same(unique));
+ });
+
+ test('absolute package root', () {
+ var version = LanguageVersion(1, 1);
+ var absolute = root.resolve('foo/bar/');
+ var package = Package('name', root,
+ packageUriRoot: absolute,
+ relativeRoot: false,
+ languageVersion: version,
+ extraData: unique);
+ expect(package.name, 'name');
+ expect(package.root, root);
+ expect(package.packageUriRoot, absolute);
+ expect(package.languageVersion, version);
+ expect(package.extraData, same(unique));
+ expect(package.relativeRoot, false);
+ });
+
+ test('relative package root', () {
+ var relative = Uri.parse('foo/bar/');
+ var absolute = root.resolveUri(relative);
+ var package = Package('name', root,
+ packageUriRoot: relative, relativeRoot: true, extraData: unique);
+ expect(package.name, 'name');
+ expect(package.root, root);
+ expect(package.packageUriRoot, absolute);
+ expect(package.relativeRoot, true);
+ expect(package.languageVersion, null);
+ expect(package.extraData, same(unique));
+ });
+
+ for (var badName in ['a/z', 'a:z', '', '...']) {
+ test("Invalid name '$badName'", () {
+ expect(() => Package(badName, root), throwsPackageConfigError);
+ });
+ }
+
+ test('Invalid root, not absolute', () {
+ expect(
+ () => Package('name', Uri.parse('/foo/')), throwsPackageConfigError);
+ });
+
+ test('Invalid root, not ending in slash', () {
+ expect(() => Package('name', Uri.parse('file:///foo')),
+ throwsPackageConfigError);
+ });
+
+ test('invalid package root, not ending in slash', () {
+ expect(() => Package('name', root, packageUriRoot: Uri.parse('foo')),
+ throwsPackageConfigError);
+ });
+
+ test('invalid package root, not inside root', () {
+ expect(() => Package('name', root, packageUriRoot: Uri.parse('../baz/')),
+ throwsPackageConfigError);
+ });
+ });
+
+ group('package config', () {
+ test('empty', () {
+ var empty = PackageConfig([], extraData: unique);
+ expect(empty.version, 2);
+ expect(empty.packages, isEmpty);
+ expect(empty.extraData, same(unique));
+ expect(empty.resolve(pkg('a', 'b')), isNull);
+ });
+
+ test('single', () {
+ var package = Package('name', root);
+ var single = PackageConfig([package], extraData: unique);
+ expect(single.version, 2);
+ expect(single.packages, hasLength(1));
+ expect(single.extraData, same(unique));
+ expect(single.resolve(pkg('a', 'b')), isNull);
+ var resolved = single.resolve(pkg('name', 'a/b'));
+ expect(resolved, root.resolve('a/b'));
+ });
+ });
+ test('writeString', () {
+ var config = PackageConfig([
+ Package('foo', Uri.parse('file:///pkg/foo/'),
+ packageUriRoot: Uri.parse('file:///pkg/foo/lib/'),
+ relativeRoot: false,
+ languageVersion: LanguageVersion(2, 4),
+ extraData: {'foo': 'foo!'}),
+ Package('bar', Uri.parse('file:///pkg/bar/'),
+ packageUriRoot: Uri.parse('file:///pkg/bar/lib/'),
+ relativeRoot: true,
+ extraData: {'bar': 'bar!'}),
+ ], extraData: {
+ 'extra': 'data'
+ });
+ var buffer = StringBuffer();
+ PackageConfig.writeString(config, buffer, Uri.parse('file:///pkg/'));
+ var text = buffer.toString();
+ var json = jsonDecode(text); // Is valid JSON.
+ expect(json, {
+ 'configVersion': 2,
+ 'packages': unorderedEquals([
+ {
+ 'name': 'foo',
+ 'rootUri': 'file:///pkg/foo/',
+ 'packageUri': 'lib/',
+ 'languageVersion': '2.4',
+ 'foo': 'foo!',
+ },
+ {
+ 'name': 'bar',
+ 'rootUri': 'bar/',
+ 'packageUri': 'lib/',
+ 'bar': 'bar!',
+ },
+ ]),
+ 'extra': 'data',
+ });
+ });
+}
+
+final Matcher throwsPackageConfigError = throwsA(isA<PackageConfigError>());
diff --git a/pkgs/package_config/test/parse_test.dart b/pkgs/package_config/test/parse_test.dart
new file mode 100644
index 0000000..a92b9bf
--- /dev/null
+++ b/pkgs/package_config/test/parse_test.dart
@@ -0,0 +1,552 @@
+// 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:typed_data';
+
+import 'package:package_config/package_config_types.dart';
+import 'package:package_config/src/errors.dart';
+import 'package:package_config/src/package_config_json.dart';
+import 'package:package_config/src/packages_file.dart' as packages;
+import 'package:test/test.dart';
+
+import 'src/util.dart';
+
+void main() {
+ group('.packages', () {
+ test('valid', () {
+ var packagesFile = '# Generated by pub yadda yadda\n'
+ 'foo:file:///foo/lib/\n'
+ 'bar:/bar/lib/\n'
+ 'baz:lib/\n';
+ var result = packages.parse(utf8.encode(packagesFile),
+ Uri.parse('file:///tmp/file.dart'), throwError);
+ expect(result.version, 1);
+ expect({for (var p in result.packages) p.name}, {'foo', 'bar', 'baz'});
+ expect(result.resolve(pkg('foo', 'foo.dart')),
+ Uri.parse('file:///foo/lib/foo.dart'));
+ expect(result.resolve(pkg('bar', 'bar.dart')),
+ Uri.parse('file:///bar/lib/bar.dart'));
+ expect(result.resolve(pkg('baz', 'baz.dart')),
+ Uri.parse('file:///tmp/lib/baz.dart'));
+
+ var foo = result['foo']!;
+ expect(foo, isNotNull);
+ expect(foo.root, Uri.parse('file:///foo/'));
+ expect(foo.packageUriRoot, Uri.parse('file:///foo/lib/'));
+ expect(foo.languageVersion, LanguageVersion(2, 7));
+ expect(foo.relativeRoot, false);
+ });
+
+ test('valid empty', () {
+ var packagesFile = '# Generated by pub yadda yadda\n';
+ var result = packages.parse(
+ utf8.encode(packagesFile), Uri.file('/tmp/file.dart'), throwError);
+ expect(result.version, 1);
+ expect({for (var p in result.packages) p.name}, <String>{});
+ });
+
+ group('invalid', () {
+ var baseFile = Uri.file('/tmp/file.dart');
+ void testThrows(String name, String content) {
+ test(name, () {
+ expect(
+ () => packages.parse(utf8.encode(content), baseFile, throwError),
+ throwsA(isA<FormatException>()));
+ });
+ test('$name, handle error', () {
+ var hadError = false;
+ packages.parse(utf8.encode(content), baseFile, (error) {
+ hadError = true;
+ expect(error, isA<FormatException>());
+ });
+ expect(hadError, true);
+ });
+ }
+
+ testThrows('repeated package name', 'foo:lib/\nfoo:lib\n');
+ testThrows('no colon', 'foo\n');
+ testThrows('empty package name', ':lib/\n');
+ testThrows('dot only package name', '.:lib/\n');
+ testThrows('dot only package name', '..:lib/\n');
+ testThrows('invalid package name character', 'f\\o:lib/\n');
+ testThrows('package URI', 'foo:package:bar/lib/');
+ testThrows('location with query', 'f\\o:lib/?\n');
+ testThrows('location with fragment', 'f\\o:lib/#\n');
+ });
+ });
+
+ group('package_config.json', () {
+ test('valid', () {
+ var packageConfigFile = '''
+ {
+ "configVersion": 2,
+ "packages": [
+ {
+ "name": "foo",
+ "rootUri": "file:///foo/",
+ "packageUri": "lib/",
+ "languageVersion": "2.5",
+ "nonstandard": true
+ },
+ {
+ "name": "bar",
+ "rootUri": "/bar/",
+ "packageUri": "lib/",
+ "languageVersion": "9999.9999"
+ },
+ {
+ "name": "baz",
+ "rootUri": "../",
+ "packageUri": "lib/"
+ },
+ {
+ "name": "noslash",
+ "rootUri": "../noslash",
+ "packageUri": "lib"
+ }
+ ],
+ "generator": "pub",
+ "other": [42]
+ }
+ ''';
+ var config = parsePackageConfigBytes(
+ // ignore: unnecessary_cast
+ utf8.encode(packageConfigFile) as Uint8List,
+ Uri.parse('file:///tmp/.dart_tool/file.dart'),
+ throwError);
+ expect(config.version, 2);
+ expect({for (var p in config.packages) p.name},
+ {'foo', 'bar', 'baz', 'noslash'});
+
+ expect(config.resolve(pkg('foo', 'foo.dart')),
+ Uri.parse('file:///foo/lib/foo.dart'));
+ expect(config.resolve(pkg('bar', 'bar.dart')),
+ Uri.parse('file:///bar/lib/bar.dart'));
+ expect(config.resolve(pkg('baz', 'baz.dart')),
+ Uri.parse('file:///tmp/lib/baz.dart'));
+
+ var foo = config['foo']!;
+ expect(foo, isNotNull);
+ expect(foo.root, Uri.parse('file:///foo/'));
+ expect(foo.packageUriRoot, Uri.parse('file:///foo/lib/'));
+ expect(foo.languageVersion, LanguageVersion(2, 5));
+ expect(foo.extraData, {'nonstandard': true});
+ expect(foo.relativeRoot, false);
+
+ var bar = config['bar']!;
+ expect(bar, isNotNull);
+ expect(bar.root, Uri.parse('file:///bar/'));
+ expect(bar.packageUriRoot, Uri.parse('file:///bar/lib/'));
+ expect(bar.languageVersion, LanguageVersion(9999, 9999));
+ expect(bar.extraData, null);
+ expect(bar.relativeRoot, false);
+
+ var baz = config['baz']!;
+ expect(baz, isNotNull);
+ expect(baz.root, Uri.parse('file:///tmp/'));
+ expect(baz.packageUriRoot, Uri.parse('file:///tmp/lib/'));
+ expect(baz.languageVersion, null);
+ expect(baz.relativeRoot, true);
+
+ // No slash after root or package root. One is inserted.
+ var noslash = config['noslash']!;
+ expect(noslash, isNotNull);
+ expect(noslash.root, Uri.parse('file:///tmp/noslash/'));
+ expect(noslash.packageUriRoot, Uri.parse('file:///tmp/noslash/lib/'));
+ expect(noslash.languageVersion, null);
+ expect(noslash.relativeRoot, true);
+
+ expect(config.extraData, {
+ 'generator': 'pub',
+ 'other': [42]
+ });
+ });
+
+ test('valid other order', () {
+ // The ordering in the file is not important.
+ var packageConfigFile = '''
+ {
+ "generator": "pub",
+ "other": [42],
+ "packages": [
+ {
+ "languageVersion": "2.5",
+ "packageUri": "lib/",
+ "rootUri": "file:///foo/",
+ "name": "foo"
+ },
+ {
+ "packageUri": "lib/",
+ "languageVersion": "9999.9999",
+ "rootUri": "/bar/",
+ "name": "bar"
+ },
+ {
+ "packageUri": "lib/",
+ "name": "baz",
+ "rootUri": "../"
+ }
+ ],
+ "configVersion": 2
+ }
+ ''';
+ var config = parsePackageConfigBytes(
+ // ignore: unnecessary_cast
+ utf8.encode(packageConfigFile) as Uint8List,
+ Uri.parse('file:///tmp/.dart_tool/file.dart'),
+ throwError);
+ expect(config.version, 2);
+ expect({for (var p in config.packages) p.name}, {'foo', 'bar', 'baz'});
+
+ expect(config.resolve(pkg('foo', 'foo.dart')),
+ Uri.parse('file:///foo/lib/foo.dart'));
+ expect(config.resolve(pkg('bar', 'bar.dart')),
+ Uri.parse('file:///bar/lib/bar.dart'));
+ expect(config.resolve(pkg('baz', 'baz.dart')),
+ Uri.parse('file:///tmp/lib/baz.dart'));
+ expect(config.extraData, {
+ 'generator': 'pub',
+ 'other': [42]
+ });
+ });
+
+ // Check that a few minimal configurations are valid.
+ // These form the basis of invalid tests below.
+ var cfg = '"configVersion":2';
+ var pkgs = '"packages":[]';
+ var name = '"name":"foo"';
+ var root = '"rootUri":"/foo/"';
+ test('minimal', () {
+ var config = parsePackageConfigBytes(
+ // ignore: unnecessary_cast
+ utf8.encode('{$cfg,$pkgs}') as Uint8List,
+ Uri.parse('file:///tmp/.dart_tool/file.dart'),
+ throwError);
+ expect(config.version, 2);
+ expect(config.packages, isEmpty);
+ });
+ test('minimal package', () {
+ // A package must have a name and a rootUri, the remaining properties
+ // are optional.
+ var config = parsePackageConfigBytes(
+ // ignore: unnecessary_cast
+ utf8.encode('{$cfg,"packages":[{$name,$root}]}') as Uint8List,
+ Uri.parse('file:///tmp/.dart_tool/file.dart'),
+ throwError);
+ expect(config.version, 2);
+ expect(config.packages.first.name, 'foo');
+ });
+
+ test('nested packages', () {
+ var configBytes = utf8.encode(json.encode({
+ 'configVersion': 2,
+ 'packages': [
+ {'name': 'foo', 'rootUri': '/foo/', 'packageUri': 'lib/'},
+ {'name': 'bar', 'rootUri': '/foo/bar/', 'packageUri': 'lib/'},
+ {'name': 'baz', 'rootUri': '/foo/bar/baz/', 'packageUri': 'lib/'},
+ {'name': 'qux', 'rootUri': '/foo/qux/', 'packageUri': 'lib/'},
+ ]
+ }));
+ // ignore: unnecessary_cast
+ var config = parsePackageConfigBytes(configBytes as Uint8List,
+ Uri.parse('file:///tmp/.dart_tool/file.dart'), throwError);
+ expect(config.version, 2);
+ expect(config.packageOf(Uri.parse('file:///foo/lala/lala.dart'))!.name,
+ 'foo');
+ expect(config.packageOf(Uri.parse('file:///foo/bar/lala.dart'))!.name,
+ 'bar');
+ expect(config.packageOf(Uri.parse('file:///foo/bar/baz/lala.dart'))!.name,
+ 'baz');
+ expect(config.packageOf(Uri.parse('file:///foo/qux/lala.dart'))!.name,
+ 'qux');
+ expect(config.toPackageUri(Uri.parse('file:///foo/lib/diz')),
+ Uri.parse('package:foo/diz'));
+ expect(config.toPackageUri(Uri.parse('file:///foo/bar/lib/diz')),
+ Uri.parse('package:bar/diz'));
+ expect(config.toPackageUri(Uri.parse('file:///foo/bar/baz/lib/diz')),
+ Uri.parse('package:baz/diz'));
+ expect(config.toPackageUri(Uri.parse('file:///foo/qux/lib/diz')),
+ Uri.parse('package:qux/diz'));
+ });
+
+ test('nested packages 2', () {
+ var configBytes = utf8.encode(json.encode({
+ 'configVersion': 2,
+ 'packages': [
+ {'name': 'foo', 'rootUri': '/', 'packageUri': 'lib/'},
+ {'name': 'bar', 'rootUri': '/bar/', 'packageUri': 'lib/'},
+ {'name': 'baz', 'rootUri': '/bar/baz/', 'packageUri': 'lib/'},
+ {'name': 'qux', 'rootUri': '/qux/', 'packageUri': 'lib/'},
+ ]
+ }));
+ // ignore: unnecessary_cast
+ var config = parsePackageConfigBytes(configBytes as Uint8List,
+ Uri.parse('file:///tmp/.dart_tool/file.dart'), throwError);
+ expect(config.version, 2);
+ expect(
+ config.packageOf(Uri.parse('file:///lala/lala.dart'))!.name, 'foo');
+ expect(config.packageOf(Uri.parse('file:///bar/lala.dart'))!.name, 'bar');
+ expect(config.packageOf(Uri.parse('file:///bar/baz/lala.dart'))!.name,
+ 'baz');
+ expect(config.packageOf(Uri.parse('file:///qux/lala.dart'))!.name, 'qux');
+ expect(config.toPackageUri(Uri.parse('file:///lib/diz')),
+ Uri.parse('package:foo/diz'));
+ expect(config.toPackageUri(Uri.parse('file:///bar/lib/diz')),
+ Uri.parse('package:bar/diz'));
+ expect(config.toPackageUri(Uri.parse('file:///bar/baz/lib/diz')),
+ Uri.parse('package:baz/diz'));
+ expect(config.toPackageUri(Uri.parse('file:///qux/lib/diz')),
+ Uri.parse('package:qux/diz'));
+ });
+
+ test('packageOf is case sensitive on windows', () {
+ var configBytes = utf8.encode(json.encode({
+ 'configVersion': 2,
+ 'packages': [
+ {'name': 'foo', 'rootUri': 'file:///C:/Foo/', 'packageUri': 'lib/'},
+ ]
+ }));
+ var config = parsePackageConfigBytes(
+ // ignore: unnecessary_cast
+ configBytes as Uint8List,
+ Uri.parse('file:///C:/tmp/.dart_tool/file.dart'),
+ throwError);
+ expect(config.version, 2);
+ expect(
+ config.packageOf(Uri.parse('file:///C:/foo/lala/lala.dart')), null);
+ expect(config.packageOf(Uri.parse('file:///C:/Foo/lala/lala.dart'))!.name,
+ 'foo');
+ });
+
+ group('invalid', () {
+ void testThrows(String name, String source) {
+ test(name, () {
+ expect(
+ // ignore: unnecessary_cast
+ () => parsePackageConfigBytes(utf8.encode(source) as Uint8List,
+ Uri.parse('file:///tmp/.dart_tool/file.dart'), throwError),
+ throwsA(isA<FormatException>()));
+ });
+ }
+
+ void testThrowsContains(
+ String name, String source, String containsString) {
+ test(name, () {
+ dynamic exception;
+ try {
+ parsePackageConfigBytes(
+ // ignore: unnecessary_cast
+ utf8.encode(source) as Uint8List,
+ Uri.parse('file:///tmp/.dart_tool/file.dart'),
+ throwError,
+ );
+ } catch (e) {
+ exception = e;
+ }
+ if (exception == null) fail("Didn't get exception");
+ expect('$exception', contains(containsString));
+ });
+ }
+
+ testThrows('comment', '# comment\n {$cfg,$pkgs}');
+ testThrows('.packages file', 'foo:/foo\n');
+ testThrows('no configVersion', '{$pkgs}');
+ testThrows('no packages', '{$cfg}');
+ group('config version:', () {
+ testThrows('null', '{"configVersion":null,$pkgs}');
+ testThrows('string', '{"configVersion":"2",$pkgs}');
+ testThrows('array', '{"configVersion":[2],$pkgs}');
+ });
+ group('packages:', () {
+ testThrows('null', '{$cfg,"packages":null}');
+ testThrows('string', '{$cfg,"packages":"foo"}');
+ testThrows('object', '{$cfg,"packages":{}}');
+ });
+ group('packages entry:', () {
+ testThrows('null', '{$cfg,"packages":[null]}');
+ testThrows('string', '{$cfg,"packages":["foo"]}');
+ testThrows('array', '{$cfg,"packages":[[]]}');
+ });
+ group('package', () {
+ testThrows('no name', '{$cfg,"packages":[{$root}]}');
+ group('name:', () {
+ testThrows('null', '{$cfg,"packages":[{"name":null,$root}]}');
+ testThrows('num', '{$cfg,"packages":[{"name":1,$root}]}');
+ testThrows('object', '{$cfg,"packages":[{"name":{},$root}]}');
+ testThrows('empty', '{$cfg,"packages":[{"name":"",$root}]}');
+ testThrows('one-dot', '{$cfg,"packages":[{"name":".",$root}]}');
+ testThrows('two-dot', '{$cfg,"packages":[{"name":"..",$root}]}');
+ testThrows(
+ "invalid char '\\'", '{$cfg,"packages":[{"name":"\\",$root}]}');
+ testThrows(
+ "invalid char ':'", '{$cfg,"packages":[{"name":":",$root}]}');
+ testThrows(
+ "invalid char ' '", '{$cfg,"packages":[{"name":" ",$root}]}');
+ });
+
+ testThrows('no root', '{$cfg,"packages":[{$name}]}');
+ group('root:', () {
+ testThrows('null', '{$cfg,"packages":[{$name,"rootUri":null}]}');
+ testThrows('num', '{$cfg,"packages":[{$name,"rootUri":1}]}');
+ testThrows('object', '{$cfg,"packages":[{$name,"rootUri":{}}]}');
+ testThrows('fragment', '{$cfg,"packages":[{$name,"rootUri":"x/#"}]}');
+ testThrows('query', '{$cfg,"packages":[{$name,"rootUri":"x/?"}]}');
+ testThrows('package-URI',
+ '{$cfg,"packages":[{$name,"rootUri":"package:x/x/"}]}');
+ });
+ group('package-URI root:', () {
+ testThrows(
+ 'null', '{$cfg,"packages":[{$name,$root,"packageUri":null}]}');
+ testThrows('num', '{$cfg,"packages":[{$name,$root,"packageUri":1}]}');
+ testThrows(
+ 'object', '{$cfg,"packages":[{$name,$root,"packageUri":{}}]}');
+ testThrows('fragment',
+ '{$cfg,"packages":[{$name,$root,"packageUri":"x/#"}]}');
+ testThrows(
+ 'query', '{$cfg,"packages":[{$name,$root,"packageUri":"x/?"}]}');
+ testThrows('package: URI',
+ '{$cfg,"packages":[{$name,$root,"packageUri":"package:x/x/"}]}');
+ testThrows('not inside root',
+ '{$cfg,"packages":[{$name,$root,"packageUri":"../other/"}]}');
+ });
+ group('language version', () {
+ testThrows('null',
+ '{$cfg,"packages":[{$name,$root,"languageVersion":null}]}');
+ testThrows(
+ 'num', '{$cfg,"packages":[{$name,$root,"languageVersion":1}]}');
+ testThrows('object',
+ '{$cfg,"packages":[{$name,$root,"languageVersion":{}}]}');
+ testThrows('empty',
+ '{$cfg,"packages":[{$name,$root,"languageVersion":""}]}');
+ testThrows('non number.number',
+ '{$cfg,"packages":[{$name,$root,"languageVersion":"x.1"}]}');
+ testThrows('number.non number',
+ '{$cfg,"packages":[{$name,$root,"languageVersion":"1.x"}]}');
+ testThrows('non number',
+ '{$cfg,"packages":[{$name,$root,"languageVersion":"x"}]}');
+ testThrows('one number',
+ '{$cfg,"packages":[{$name,$root,"languageVersion":"1"}]}');
+ testThrows('three numbers',
+ '{$cfg,"packages":[{$name,$root,"languageVersion":"1.2.3"}]}');
+ testThrows('leading zero first',
+ '{$cfg,"packages":[{$name,$root,"languageVersion":"01.1"}]}');
+ testThrows('leading zero second',
+ '{$cfg,"packages":[{$name,$root,"languageVersion":"1.01"}]}');
+ testThrows('trailing-',
+ '{$cfg,"packages":[{$name,$root,"languageVersion":"1.1-1"}]}');
+ testThrows('trailing+',
+ '{$cfg,"packages":[{$name,$root,"languageVersion":"1.1+1"}]}');
+ });
+ });
+ testThrows('duplicate package name',
+ '{$cfg,"packages":[{$name,$root},{$name,"rootUri":"/other/"}]}');
+ testThrowsContains(
+ // The roots of foo and bar are the same.
+ 'same roots',
+ '{$cfg,"packages":[{$name,$root},{"name":"bar",$root}]}',
+ 'the same root directory');
+ testThrowsContains(
+ // The roots of foo and bar are the same.
+ 'same roots 2',
+ '{$cfg,"packages":[{$name,"rootUri":"/"},{"name":"bar","rootUri":"/"}]}',
+ 'the same root directory');
+ testThrowsContains(
+ // The root of bar is inside the root of foo,
+ // but the package root of foo is inside the root of bar.
+ 'between root and lib',
+ '{$cfg,"packages":['
+ '{"name":"foo","rootUri":"/foo/","packageUri":"bar/lib/"},'
+ '{"name":"bar","rootUri":"/foo/bar/","packageUri":"baz/lib"}]}',
+ 'package root of foo is inside the root of bar');
+
+ // This shouldn't be allowed, but for internal reasons it is.
+ test('package inside package root', () {
+ var config = parsePackageConfigBytes(
+ // ignore: unnecessary_cast
+ utf8.encode(
+ '{$cfg,"packages":['
+ '{"name":"foo","rootUri":"/foo/","packageUri":"lib/"},'
+ '{"name":"bar","rootUri":"/foo/lib/bar/","packageUri":"lib"}]}',
+ ) as Uint8List,
+ Uri.parse('file:///tmp/.dart_tool/file.dart'),
+ throwError);
+ expect(
+ config
+ .packageOf(Uri.parse('file:///foo/lib/bar/lib/lala.dart'))!
+ .name,
+ 'foo'); // why not bar?
+ expect(config.toPackageUri(Uri.parse('file:///foo/lib/bar/lib/diz')),
+ Uri.parse('package:foo/bar/lib/diz')); // why not package:bar/diz?
+ });
+ });
+ });
+
+ group('factories', () {
+ void testConfig(String name, PackageConfig config, PackageConfig expected) {
+ group(name, () {
+ test('structure', () {
+ expect(config.version, expected.version);
+ var expectedPackages = {for (var p in expected.packages) p.name};
+ var actualPackages = {for (var p in config.packages) p.name};
+ expect(actualPackages, expectedPackages);
+ });
+ for (var package in config.packages) {
+ var name = package.name;
+ test('package $name', () {
+ var expectedPackage = expected[name]!;
+ expect(expectedPackage, isNotNull);
+ expect(package.root, expectedPackage.root, reason: 'root');
+ expect(package.packageUriRoot, expectedPackage.packageUriRoot,
+ reason: 'package root');
+ expect(package.languageVersion, expectedPackage.languageVersion,
+ reason: 'languageVersion');
+ });
+ }
+ });
+ }
+
+ var configText = '''
+ {"configVersion": 2, "packages": [
+ {
+ "name": "foo",
+ "rootUri": "foo/",
+ "packageUri": "bar/",
+ "languageVersion": "1.2"
+ }
+ ]}
+ ''';
+ var baseUri = Uri.parse('file:///start/');
+ var config = PackageConfig([
+ Package('foo', Uri.parse('file:///start/foo/'),
+ packageUriRoot: Uri.parse('file:///start/foo/bar/'),
+ languageVersion: LanguageVersion(1, 2))
+ ]);
+ testConfig(
+ 'string', PackageConfig.parseString(configText, baseUri), config);
+ testConfig(
+ 'bytes',
+ PackageConfig.parseBytes(
+ Uint8List.fromList(configText.codeUnits), baseUri),
+ config);
+ testConfig('json', PackageConfig.parseJson(jsonDecode(configText), baseUri),
+ config);
+
+ baseUri = Uri.parse('file:///start2/');
+ config = PackageConfig([
+ Package('foo', Uri.parse('file:///start2/foo/'),
+ packageUriRoot: Uri.parse('file:///start2/foo/bar/'),
+ languageVersion: LanguageVersion(1, 2))
+ ]);
+ testConfig(
+ 'string2', PackageConfig.parseString(configText, baseUri), config);
+ testConfig(
+ 'bytes2',
+ PackageConfig.parseBytes(
+ Uint8List.fromList(configText.codeUnits), baseUri),
+ config);
+ testConfig('json2',
+ PackageConfig.parseJson(jsonDecode(configText), baseUri), config);
+ });
+}
diff --git a/pkgs/package_config/test/src/util.dart b/pkgs/package_config/test/src/util.dart
new file mode 100644
index 0000000..780ee80
--- /dev/null
+++ b/pkgs/package_config/test/src/util.dart
@@ -0,0 +1,57 @@
+// 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:typed_data';
+
+import 'package:test/test.dart';
+
+/// Creates a package: URI.
+Uri pkg(String packageName, String packagePath) {
+ var path =
+ "$packageName${packagePath.startsWith('/') ? "" : "/"}$packagePath";
+ return Uri(scheme: 'package', path: path);
+}
+
+// Remove if not used.
+String configFromPackages(List<List<String>> packages) => """
+{
+ "configVersion": 2,
+ "packages": [
+${packages.map((nu) => """
+ {
+ "name": "${nu[0]}",
+ "rootUri": "${nu[1]}"
+ }""").join(",\n")}
+ ]
+}
+""";
+
+/// Mimics a directory structure of [description] and runs [loaderTest].
+///
+/// Description is a map, each key is a file entry. If the value is a map,
+/// it's a subdirectory, otherwise it's a file and the value is the content
+/// as a string.
+void loaderTest(
+ String name,
+ Map<String, Object> description,
+ void Function(Uri root, Future<Uint8List?> Function(Uri) loader) loaderTest,
+) {
+ var root = Uri(scheme: 'test', path: '/');
+ Future<Uint8List?> loader(Uri uri) async {
+ var path = uri.path;
+ if (!uri.isScheme('test') || !path.startsWith('/')) return null;
+ var parts = path.split('/');
+ Object? value = description;
+ for (var i = 1; i < parts.length; i++) {
+ if (value is! Map<String, Object?>) return null;
+ value = value[parts[i]];
+ }
+ // ignore: unnecessary_cast
+ if (value is String) return utf8.encode(value) as Uint8List;
+ return null;
+ }
+
+ test(name, () => loaderTest(root, loader));
+}
diff --git a/pkgs/package_config/test/src/util_io.dart b/pkgs/package_config/test/src/util_io.dart
new file mode 100644
index 0000000..e032556
--- /dev/null
+++ b/pkgs/package_config/test/src/util_io.dart
@@ -0,0 +1,62 @@
+// Copyright (c) 2020, 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:package_config/src/util_io.dart';
+import 'package:test/test.dart';
+
+/// Creates a directory structure from [description] and runs [fileTest].
+///
+/// Description is a map, each key is a file entry. If the value is a map,
+/// it's a subdirectory, otherwise it's a file and the value is the content
+/// as a string.
+/// Introduces a group to hold the [setUp]/[tearDown] logic.
+void fileTest(String name, Map<String, Object> description,
+ void Function(Directory directory) fileTest) {
+ group('file-test', () {
+ var tempDir = Directory.systemTemp.createTempSync('pkgcfgtest');
+ setUp(() {
+ _createFiles(tempDir, description);
+ });
+ tearDown(() {
+ tempDir.deleteSync(recursive: true);
+ });
+ test(name, () => fileTest(tempDir));
+ });
+}
+
+/// Creates a set of files under a new temporary directory.
+/// Returns the temporary directory.
+///
+/// The [description] is a map from file names to content.
+/// If the content is again a map, it represents a subdirectory
+/// with the content as description.
+/// Otherwise the content should be a string,
+/// which is written to the file as UTF-8.
+// Directory createTestFiles(Map<String, Object> description) {
+// var target = Directory.systemTemp.createTempSync("pkgcfgtest");
+// _createFiles(target, description);
+// return target;
+// }
+
+// Creates temporary files in the target directory.
+void _createFiles(Directory target, Map<Object?, Object?> description) {
+ description.forEach((name, content) {
+ var entryName = pathJoin(target.path, '$name');
+ if (content is Map<Object?, Object?>) {
+ _createFiles(Directory(entryName)..createSync(), content);
+ } else {
+ File(entryName).writeAsStringSync(content as String, flush: true);
+ }
+ });
+}
+
+/// Creates a [Directory] for a subdirectory of [parent].
+Directory subdir(Directory parent, String dirName) =>
+ Directory(pathJoinAll([parent.path, ...dirName.split('/')]));
+
+/// Creates a [File] for an entry in the [directory] directory.
+File dirFile(Directory directory, String fileName) =>
+ File(pathJoin(directory.path, fileName));
diff --git a/pkgs/pool/.gitignore b/pkgs/pool/.gitignore
new file mode 100644
index 0000000..e450c83
--- /dev/null
+++ b/pkgs/pool/.gitignore
@@ -0,0 +1,5 @@
+# Don’t commit the following directories created by pub.
+.dart_tool/
+.packages
+.pub/
+pubspec.lock
diff --git a/pkgs/pool/CHANGELOG.md b/pkgs/pool/CHANGELOG.md
new file mode 100644
index 0000000..56424fc
--- /dev/null
+++ b/pkgs/pool/CHANGELOG.md
@@ -0,0 +1,105 @@
+## 1.5.2-wip
+
+* Require Dart 3.4.
+* Move to `dart-lang/tools` monorepo.
+
+## 1.5.1
+
+* Populate the pubspec `repository` field.
+
+## 1.5.0
+
+* Stable release for null safety.
+
+## 1.5.0-nullsafety.3
+
+* Update SDK constraints to `>=2.12.0-0 <3.0.0` based on beta release
+ guidelines.
+
+## 1.5.0-nullsafety.2
+
+* Allow prerelease versions of the 2.12 sdk.
+
+## 1.5.0-nullsafety.1
+
+* Allow 2.10 stable and 2.11.0 dev SDK versions.
+
+## 1.5.0-nullsafety
+
+* Migrate to null safety.
+* `forEach`: Avoid `await null` if the `Stream` is not paused.
+ Improves trivial benchmark by 40%.
+
+## 1.4.0
+
+* Add `forEach` to `Pool` to support efficient async processing of an
+ `Iterable`.
+
+* Throw ArgumentError if poolSize <= 0
+
+## 1.3.6
+
+* Set max SDK version to `<3.0.0`, and adjust other dependencies.
+
+## 1.3.5
+
+- Updated SDK version to 2.0.0-dev.17.0
+
+## 1.3.4
+
+* Modify code to eliminate Future flattening.
+
+## 1.3.3
+
+* Declare support for `async` 2.0.0.
+
+## 1.3.2
+
+* Update to make the code work with strong-mode clean Zone API.
+
+* Required minimum SDK of 1.23.0.
+
+## 1.3.1
+
+* Fix the type annotation of `Pool.withResource()` to indicate that it takes
+ `() -> FutureOr<T>`.
+
+## 1.3.0
+
+* Add a `Pool.done` getter that returns the same future returned by
+ `Pool.close()`.
+
+## 1.2.4
+
+* Fix a strong-mode error.
+
+## 1.2.3
+
+* Fix a bug in which `Pool.withResource()` could throw a `StateError` when
+ called immediately before closing the pool.
+
+## 1.2.2
+
+* Fix strong mode warnings and add generic method annotations.
+
+## 1.2.1
+
+* Internal changes only.
+
+## 1.2.0
+
+* Add `Pool.close()`, which forbids new resource requests and releases all
+ releasable resources.
+
+## 1.1.0
+
+* Add `PoolResource.allowRelease()`, which allows a resource to indicate that it
+ can be released without forcing it to deallocate immediately.
+
+## 1.0.2
+
+* Fixed the homepage.
+
+## 1.0.1
+
+* A `TimeoutException` is now correctly thrown if the pool detects a deadlock.
diff --git a/pkgs/pool/LICENSE b/pkgs/pool/LICENSE
new file mode 100644
index 0000000..000cd7b
--- /dev/null
+++ b/pkgs/pool/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2014, 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.
diff --git a/pkgs/pool/README.md b/pkgs/pool/README.md
new file mode 100644
index 0000000..461e872
--- /dev/null
+++ b/pkgs/pool/README.md
@@ -0,0 +1,57 @@
+[](https://github.com/dart-lang/tools/actions/workflows/pool.yaml)
+[](https://pub.dev/packages/pool)
+[](https://pub.dev/packages/pool/publisher)
+
+The pool package exposes a `Pool` class which makes it easy to manage a limited
+pool of resources.
+
+The easiest way to use a pool is by calling `withResource`. This runs a callback
+and returns its result, but only once there aren't too many other callbacks
+currently running.
+
+```dart
+// Create a Pool that will only allocate 10 resources at once. After 30 seconds
+// of inactivity with all resources checked out, the pool will throw an error.
+final pool = new Pool(10, timeout: new Duration(seconds: 30));
+
+Future<String> readFile(String path) {
+ // Since the call to [File.readAsString] is within [withResource], no more
+ // than ten files will be open at once.
+ return pool.withResource(() => new File(path).readAsString());
+}
+```
+
+For more fine-grained control, the user can also explicitly request generic
+`PoolResource` objects that can later be released back into the pool. This is
+what `withResource` does under the covers: requests a resource, then releases it
+once the callback completes.
+
+`Pool` ensures that only a limited number of resources are allocated at once.
+It's the caller's responsibility to ensure that the corresponding physical
+resource is only consumed when a `PoolResource` is allocated.
+
+```dart
+class PooledFile implements RandomAccessFile {
+ final RandomAccessFile _file;
+ final PoolResource _resource;
+
+ static Future<PooledFile> open(String path) {
+ return pool.request().then((resource) {
+ return new File(path).open().then((file) {
+ return new PooledFile._(file, resource);
+ });
+ });
+ }
+
+ PooledFile(this._file, this._resource);
+
+ // ...
+
+ Future<RandomAccessFile> close() {
+ return _file.close.then((_) {
+ _resource.release();
+ return this;
+ });
+ }
+}
+```
diff --git a/pkgs/pool/analysis_options.yaml b/pkgs/pool/analysis_options.yaml
new file mode 100644
index 0000000..44cda4d
--- /dev/null
+++ b/pkgs/pool/analysis_options.yaml
@@ -0,0 +1,5 @@
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-casts: true
diff --git a/pkgs/pool/benchmark/for_each_benchmark.dart b/pkgs/pool/benchmark/for_each_benchmark.dart
new file mode 100644
index 0000000..0cd2543
--- /dev/null
+++ b/pkgs/pool/benchmark/for_each_benchmark.dart
@@ -0,0 +1,55 @@
+// 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:pool/pool.dart';
+
+void main(List<String> args) async {
+ var poolSize = args.isEmpty ? 5 : int.parse(args.first);
+ print('Pool size: $poolSize');
+
+ final pool = Pool(poolSize);
+ final watch = Stopwatch()..start();
+ final start = DateTime.now();
+
+ DateTime? lastLog;
+ Duration? fastest;
+ late int fastestIteration;
+ var i = 1;
+
+ void log(bool force) {
+ var now = DateTime.now();
+ if (force ||
+ lastLog == null ||
+ now.difference(lastLog!) > const Duration(seconds: 1)) {
+ lastLog = now;
+ print([
+ now.difference(start),
+ i.toString().padLeft(10),
+ fastestIteration.toString().padLeft(7),
+ fastest!.inMicroseconds.toString().padLeft(9)
+ ].join(' '));
+ }
+ }
+
+ print(['Elapsed ', 'Iterations', 'Fastest', 'Time (us)'].join(' '));
+
+ for (;; i++) {
+ watch.reset();
+
+ var sum = await pool
+ .forEach<int, int>(Iterable<int>.generate(100000), (i) => i)
+ .reduce((a, b) => a + b);
+
+ assert(sum == 4999950000, 'was $sum');
+
+ var elapsed = watch.elapsed;
+ if (fastest == null || fastest > elapsed) {
+ fastest = elapsed;
+ fastestIteration = i;
+ log(true);
+ } else {
+ log(false);
+ }
+ }
+}
diff --git a/pkgs/pool/lib/pool.dart b/pkgs/pool/lib/pool.dart
new file mode 100644
index 0000000..70e9df1
--- /dev/null
+++ b/pkgs/pool/lib/pool.dart
@@ -0,0 +1,380 @@
+// Copyright (c) 2014, 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:collection';
+
+import 'package:async/async.dart';
+import 'package:stack_trace/stack_trace.dart';
+
+/// Manages an abstract pool of resources with a limit on how many may be in use
+/// at once.
+///
+/// When a resource is needed, the user should call [request]. When the returned
+/// future completes with a [PoolResource], the resource may be allocated. Once
+/// the resource has been released, the user should call [PoolResource.release].
+/// The pool will ensure that only a certain number of [PoolResource]s may be
+/// allocated at once.
+class Pool {
+ /// Completers for requests beyond the first [_maxAllocatedResources].
+ ///
+ /// When an item is released, the next element of [_requestedResources] will
+ /// be completed.
+ final _requestedResources = Queue<Completer<PoolResource>>();
+
+ /// Callbacks that must be called before additional resources can be
+ /// allocated.
+ ///
+ /// See [PoolResource.allowRelease].
+ final _onReleaseCallbacks = Queue<void Function()>();
+
+ /// Completers that will be completed once `onRelease` callbacks are done
+ /// running.
+ ///
+ /// These are kept in a queue to ensure that the earliest request completes
+ /// first regardless of what order the `onRelease` callbacks complete in.
+ final _onReleaseCompleters = Queue<Completer<PoolResource>>();
+
+ /// The maximum number of resources that may be allocated at once.
+ final int _maxAllocatedResources;
+
+ /// The number of resources that are currently allocated.
+ int _allocatedResources = 0;
+
+ /// The timeout timer.
+ ///
+ /// This timer is canceled as long as the pool is below the resource limit.
+ /// It's reset once the resource limit is reached and again every time an
+ /// resource is released or a new resource is requested. If it fires, that
+ /// indicates that the caller became deadlocked, likely due to files waiting
+ /// for additional files to be read before they could be closed.
+ ///
+ /// This is `null` if this pool shouldn't time out.
+ RestartableTimer? _timer;
+
+ /// The amount of time to wait before timing out the pending resources.
+ final Duration? _timeout;
+
+ /// A [FutureGroup] that tracks all the `onRelease` callbacks for resources
+ /// that have been marked releasable.
+ ///
+ /// This is `null` until [close] is called.
+ FutureGroup? _closeGroup;
+
+ /// Whether [close] has been called.
+ bool get isClosed => _closeMemo.hasRun;
+
+ /// A future that completes once the pool is closed and all its outstanding
+ /// resources have been released.
+ ///
+ /// If any [PoolResource.allowRelease] callback throws an exception after the
+ /// pool is closed, this completes with that exception.
+ Future get done => _closeMemo.future;
+
+ /// Creates a new pool with the given limit on how many resources may be
+ /// allocated at once.
+ ///
+ /// If [timeout] is passed, then if that much time passes without any activity
+ /// all pending [request] futures will throw a [TimeoutException]. This is
+ /// intended to avoid deadlocks.
+ Pool(this._maxAllocatedResources, {Duration? timeout}) : _timeout = timeout {
+ if (_maxAllocatedResources <= 0) {
+ throw ArgumentError.value(_maxAllocatedResources, 'maxAllocatedResources',
+ 'Must be greater than zero.');
+ }
+
+ if (timeout != null) {
+ // Start the timer canceled since we only want to start counting down once
+ // we've run out of available resources.
+ _timer = RestartableTimer(timeout, _onTimeout)..cancel();
+ }
+ }
+
+ /// Request a [PoolResource].
+ ///
+ /// If the maximum number of resources is already allocated, this will delay
+ /// until one of them is released.
+ Future<PoolResource> request() {
+ if (isClosed) {
+ throw StateError('request() may not be called on a closed Pool.');
+ }
+
+ if (_allocatedResources < _maxAllocatedResources) {
+ _allocatedResources++;
+ return Future.value(PoolResource._(this));
+ } else if (_onReleaseCallbacks.isNotEmpty) {
+ return _runOnRelease(_onReleaseCallbacks.removeFirst());
+ } else {
+ var completer = Completer<PoolResource>();
+ _requestedResources.add(completer);
+ _resetTimer();
+ return completer.future;
+ }
+ }
+
+ /// Requests a resource for the duration of [callback], which may return a
+ /// Future.
+ ///
+ /// The return value of [callback] is piped to the returned Future.
+ Future<T> withResource<T>(FutureOr<T> Function() callback) async {
+ if (isClosed) {
+ throw StateError('withResource() may not be called on a closed Pool.');
+ }
+
+ var resource = await request();
+ try {
+ return await callback();
+ } finally {
+ resource.release();
+ }
+ }
+
+ /// Returns a [Stream] containing the result of [action] applied to each
+ /// element of [elements].
+ ///
+ /// While [action] is invoked on each element of [elements] in order,
+ /// it's possible the return [Stream] may have items out-of-order – especially
+ /// if the completion time of [action] varies.
+ ///
+ /// If [action] throws an error the source item along with the error object
+ /// and [StackTrace] are passed to [onError], if it is provided. If [onError]
+ /// returns `true`, the error is added to the returned [Stream], otherwise
+ /// it is ignored.
+ ///
+ /// Errors thrown from iterating [elements] will not be passed to
+ /// [onError]. They will always be added to the returned stream as an error.
+ ///
+ /// Note: all of the resources of the this [Pool] will be used when the
+ /// returned [Stream] is listened to until it is completed or canceled.
+ ///
+ /// Note: if this [Pool] is closed before the returned [Stream] is listened
+ /// to, a [StateError] is thrown.
+ Stream<T> forEach<S, T>(
+ Iterable<S> elements, FutureOr<T> Function(S source) action,
+ {bool Function(S item, Object error, StackTrace stack)? onError}) {
+ onError ??= (item, e, s) => true;
+
+ var cancelPending = false;
+
+ Completer? resumeCompleter;
+ late StreamController<T> controller;
+
+ late Iterator<S> iterator;
+
+ Future<void> run(int _) async {
+ while (iterator.moveNext()) {
+ // caching `current` is necessary because there are async breaks
+ // in this code and `iterator` is shared across many workers
+ final current = iterator.current;
+
+ _resetTimer();
+
+ if (resumeCompleter != null) {
+ await resumeCompleter!.future;
+ }
+
+ if (cancelPending) {
+ break;
+ }
+
+ T value;
+ try {
+ value = await action(current);
+ } catch (e, stack) {
+ if (onError!(current, e, stack)) {
+ controller.addError(e, stack);
+ }
+ continue;
+ }
+ controller.add(value);
+ }
+ }
+
+ Future<void>? doneFuture;
+
+ void onListen() {
+ iterator = elements.iterator;
+
+ assert(doneFuture == null);
+ var futures = Iterable<Future<void>>.generate(
+ _maxAllocatedResources, (i) => withResource(() => run(i)));
+ doneFuture = Future.wait(futures, eagerError: true)
+ .then<void>((_) {})
+ .catchError(controller.addError);
+
+ doneFuture!.whenComplete(controller.close);
+ }
+
+ controller = StreamController<T>(
+ sync: true,
+ onListen: onListen,
+ onCancel: () async {
+ assert(!cancelPending);
+ cancelPending = true;
+ await doneFuture;
+ },
+ onPause: () {
+ assert(resumeCompleter == null);
+ resumeCompleter = Completer<void>();
+ },
+ onResume: () {
+ assert(resumeCompleter != null);
+ resumeCompleter!.complete();
+ resumeCompleter = null;
+ },
+ );
+
+ return controller.stream;
+ }
+
+ /// Closes the pool so that no more resources are requested.
+ ///
+ /// Existing resource requests remain unchanged.
+ ///
+ /// Any resources that are marked as releasable using
+ /// [PoolResource.allowRelease] are released immediately. Once all resources
+ /// have been released and any `onRelease` callbacks have completed, the
+ /// returned future completes successfully. If any `onRelease` callback throws
+ /// an error, the returned future completes with that error.
+ ///
+ /// This may be called more than once; it returns the same [Future] each time.
+ Future close() => _closeMemo.runOnce(_close);
+
+ Future<void> _close() {
+ if (_closeGroup != null) return _closeGroup!.future;
+
+ _resetTimer();
+
+ _closeGroup = FutureGroup();
+ for (var callback in _onReleaseCallbacks) {
+ _closeGroup!.add(Future.sync(callback));
+ }
+
+ _allocatedResources -= _onReleaseCallbacks.length;
+ _onReleaseCallbacks.clear();
+
+ if (_allocatedResources == 0) _closeGroup!.close();
+ return _closeGroup!.future;
+ }
+
+ final _closeMemo = AsyncMemoizer<void>();
+
+ /// If there are any pending requests, this will fire the oldest one.
+ void _onResourceReleased() {
+ _resetTimer();
+
+ if (_requestedResources.isNotEmpty) {
+ var pending = _requestedResources.removeFirst();
+ pending.complete(PoolResource._(this));
+ } else {
+ _allocatedResources--;
+ if (isClosed && _allocatedResources == 0) _closeGroup!.close();
+ }
+ }
+
+ /// If there are any pending requests, this will fire the oldest one after
+ /// running [onRelease].
+ void _onResourceReleaseAllowed(void Function() onRelease) {
+ _resetTimer();
+
+ if (_requestedResources.isNotEmpty) {
+ var pending = _requestedResources.removeFirst();
+ pending.complete(_runOnRelease(onRelease));
+ } else if (isClosed) {
+ _closeGroup!.add(Future.sync(onRelease));
+ _allocatedResources--;
+ if (_allocatedResources == 0) _closeGroup!.close();
+ } else {
+ var zone = Zone.current;
+ var registered = zone.registerCallback(onRelease);
+ _onReleaseCallbacks.add(() => zone.run(registered));
+ }
+ }
+
+ /// Runs [onRelease] and returns a Future that completes to a resource once an
+ /// [onRelease] callback completes.
+ ///
+ /// Futures returned by [_runOnRelease] always complete in the order they were
+ /// created, even if earlier [onRelease] callbacks take longer to run.
+ Future<PoolResource> _runOnRelease(void Function() onRelease) {
+ Future.sync(onRelease).then((value) {
+ _onReleaseCompleters.removeFirst().complete(PoolResource._(this));
+ }).catchError((Object error, StackTrace stackTrace) {
+ _onReleaseCompleters.removeFirst().completeError(error, stackTrace);
+ });
+
+ var completer = Completer<PoolResource>.sync();
+ _onReleaseCompleters.add(completer);
+ return completer.future;
+ }
+
+ /// A resource has been requested, allocated, or released.
+ void _resetTimer() {
+ if (_timer == null) return;
+
+ if (_requestedResources.isEmpty) {
+ _timer!.cancel();
+ } else {
+ _timer!.reset();
+ }
+ }
+
+ /// Handles [_timer] timing out by causing all pending resource completers to
+ /// emit exceptions.
+ void _onTimeout() {
+ for (var completer in _requestedResources) {
+ completer.completeError(
+ TimeoutException(
+ 'Pool deadlock: all resources have been '
+ 'allocated for too long.',
+ _timeout),
+ Chain.current());
+ }
+ _requestedResources.clear();
+ _timer = null;
+ }
+}
+
+/// A member of a [Pool].
+///
+/// A [PoolResource] is a token that indicates that a resource is allocated.
+/// When the associated resource is released, the user should call [release].
+class PoolResource {
+ final Pool _pool;
+
+ /// Whether `this` has been released yet.
+ bool _released = false;
+
+ PoolResource._(this._pool);
+
+ /// Tells the parent [Pool] that the resource associated with this resource is
+ /// no longer allocated, and that a new [PoolResource] may be allocated.
+ void release() {
+ if (_released) {
+ throw StateError('A PoolResource may only be released once.');
+ }
+ _released = true;
+ _pool._onResourceReleased();
+ }
+
+ /// Tells the parent [Pool] that the resource associated with this resource is
+ /// no longer necessary, but should remain allocated until more resources are
+ /// needed.
+ ///
+ /// When [Pool.request] is called and there are no remaining available
+ /// resources, the [onRelease] callback is called. It should free the
+ /// resource, and it may return a Future or `null`. Once that completes, the
+ /// [Pool.request] call will complete to a new [PoolResource].
+ ///
+ /// This is useful when a resource's main function is complete, but it may
+ /// produce additional information later on. For example, an isolate's task
+ /// may be complete, but it could still emit asynchronous errors.
+ void allowRelease(FutureOr<void> Function() onRelease) {
+ if (_released) {
+ throw StateError('A PoolResource may only be released once.');
+ }
+ _released = true;
+ _pool._onResourceReleaseAllowed(onRelease);
+ }
+}
diff --git a/pkgs/pool/pubspec.yaml b/pkgs/pool/pubspec.yaml
new file mode 100644
index 0000000..a205b74
--- /dev/null
+++ b/pkgs/pool/pubspec.yaml
@@ -0,0 +1,18 @@
+name: pool
+version: 1.5.2-wip
+description: >-
+ Manage a finite pool of resources.
+ Useful for controlling concurrent file system or network requests.
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/pool
+
+environment:
+ sdk: ^3.4.0
+
+dependencies:
+ async: ^2.5.0
+ stack_trace: ^1.10.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ fake_async: ^1.2.0
+ test: ^1.16.6
diff --git a/pkgs/pool/test/pool_test.dart b/pkgs/pool/test/pool_test.dart
new file mode 100644
index 0000000..6334a8a
--- /dev/null
+++ b/pkgs/pool/test/pool_test.dart
@@ -0,0 +1,745 @@
+// Copyright (c) 2014, 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 'package:fake_async/fake_async.dart';
+import 'package:pool/pool.dart';
+import 'package:stack_trace/stack_trace.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('request()', () {
+ test('resources can be requested freely up to the limit', () {
+ var pool = Pool(50);
+ for (var i = 0; i < 50; i++) {
+ expect(pool.request(), completes);
+ }
+ });
+
+ test('resources block past the limit', () {
+ FakeAsync().run((async) {
+ var pool = Pool(50);
+ for (var i = 0; i < 50; i++) {
+ expect(pool.request(), completes);
+ }
+ expect(pool.request(), doesNotComplete);
+
+ async.elapse(const Duration(seconds: 1));
+ });
+ });
+
+ test('a blocked resource is allocated when another is released', () {
+ FakeAsync().run((async) {
+ var pool = Pool(50);
+ for (var i = 0; i < 49; i++) {
+ expect(pool.request(), completes);
+ }
+
+ pool.request().then((lastAllocatedResource) {
+ // This will only complete once [lastAllocatedResource] is released.
+ expect(pool.request(), completes);
+
+ Future<void>.delayed(const Duration(microseconds: 1)).then((_) {
+ lastAllocatedResource.release();
+ });
+ });
+
+ async.elapse(const Duration(seconds: 1));
+ });
+ });
+ });
+
+ group('withResource()', () {
+ test('can be called freely up to the limit', () {
+ var pool = Pool(50);
+ for (var i = 0; i < 50; i++) {
+ pool.withResource(expectAsync0(() => Completer<void>().future));
+ }
+ });
+
+ test('blocks the callback past the limit', () {
+ FakeAsync().run((async) {
+ var pool = Pool(50);
+ for (var i = 0; i < 50; i++) {
+ pool.withResource(expectAsync0(() => Completer<void>().future));
+ }
+ pool.withResource(expectNoAsync());
+
+ async.elapse(const Duration(seconds: 1));
+ });
+ });
+
+ test('a blocked resource is allocated when another is released', () {
+ FakeAsync().run((async) {
+ var pool = Pool(50);
+ for (var i = 0; i < 49; i++) {
+ pool.withResource(expectAsync0(() => Completer<void>().future));
+ }
+
+ var completer = Completer<void>();
+ pool.withResource(() => completer.future);
+ var blockedResourceAllocated = false;
+ pool.withResource(() {
+ blockedResourceAllocated = true;
+ });
+
+ Future<void>.delayed(const Duration(microseconds: 1)).then((_) {
+ expect(blockedResourceAllocated, isFalse);
+ completer.complete();
+ return Future<void>.delayed(const Duration(microseconds: 1));
+ }).then((_) {
+ expect(blockedResourceAllocated, isTrue);
+ });
+
+ async.elapse(const Duration(seconds: 1));
+ });
+ });
+
+ // Regression test for #3.
+ test('can be called immediately before close()', () async {
+ var pool = Pool(1);
+ unawaited(pool.withResource(expectAsync0(() {})));
+ await pool.close();
+ });
+ });
+
+ group('with a timeout', () {
+ test("doesn't time out if there are no pending requests", () {
+ FakeAsync().run((async) {
+ var pool = Pool(50, timeout: const Duration(seconds: 5));
+ for (var i = 0; i < 50; i++) {
+ expect(pool.request(), completes);
+ }
+
+ async.elapse(const Duration(seconds: 6));
+ });
+ });
+
+ test('resets the timer if a resource is returned', () {
+ FakeAsync().run((async) {
+ var pool = Pool(50, timeout: const Duration(seconds: 5));
+ for (var i = 0; i < 49; i++) {
+ expect(pool.request(), completes);
+ }
+
+ pool.request().then((lastAllocatedResource) {
+ // This will only complete once [lastAllocatedResource] is released.
+ expect(pool.request(), completes);
+
+ Future<void>.delayed(const Duration(seconds: 3)).then((_) {
+ lastAllocatedResource.release();
+ expect(pool.request(), doesNotComplete);
+ });
+ });
+
+ async.elapse(const Duration(seconds: 6));
+ });
+ });
+
+ test('resets the timer if a resource is requested', () {
+ FakeAsync().run((async) {
+ var pool = Pool(50, timeout: const Duration(seconds: 5));
+ for (var i = 0; i < 50; i++) {
+ expect(pool.request(), completes);
+ }
+ expect(pool.request(), doesNotComplete);
+
+ Future<void>.delayed(const Duration(seconds: 3)).then((_) {
+ expect(pool.request(), doesNotComplete);
+ });
+
+ async.elapse(const Duration(seconds: 6));
+ });
+ });
+
+ test('times out if nothing happens', () {
+ FakeAsync().run((async) {
+ var pool = Pool(50, timeout: const Duration(seconds: 5));
+ for (var i = 0; i < 50; i++) {
+ expect(pool.request(), completes);
+ }
+ expect(pool.request(), throwsA(const TypeMatcher<TimeoutException>()));
+
+ async.elapse(const Duration(seconds: 6));
+ });
+ });
+ });
+
+ group('allowRelease()', () {
+ test('runs the callback once the resource limit is exceeded', () async {
+ var pool = Pool(50);
+ for (var i = 0; i < 49; i++) {
+ expect(pool.request(), completes);
+ }
+
+ var resource = await pool.request();
+ var onReleaseCalled = false;
+ resource.allowRelease(() => onReleaseCalled = true);
+ await Future<void>.delayed(Duration.zero);
+ expect(onReleaseCalled, isFalse);
+
+ expect(pool.request(), completes);
+ await Future<void>.delayed(Duration.zero);
+ expect(onReleaseCalled, isTrue);
+ });
+
+ test('runs the callback immediately if there are blocked requests',
+ () async {
+ var pool = Pool(1);
+ var resource = await pool.request();
+
+ // This will be blocked until [resource.allowRelease] is called.
+ expect(pool.request(), completes);
+
+ var onReleaseCalled = false;
+ resource.allowRelease(() => onReleaseCalled = true);
+ await Future<void>.delayed(Duration.zero);
+ expect(onReleaseCalled, isTrue);
+ });
+
+ test('blocks the request until the callback completes', () async {
+ var pool = Pool(1);
+ var resource = await pool.request();
+
+ var requestComplete = false;
+ unawaited(pool.request().then((_) => requestComplete = true));
+
+ var completer = Completer<void>();
+ resource.allowRelease(() => completer.future);
+ await Future<void>.delayed(Duration.zero);
+ expect(requestComplete, isFalse);
+
+ completer.complete();
+ await Future<void>.delayed(Duration.zero);
+ expect(requestComplete, isTrue);
+ });
+
+ test('completes requests in request order regardless of callback order',
+ () async {
+ var pool = Pool(2);
+ var resource1 = await pool.request();
+ var resource2 = await pool.request();
+
+ var request1Complete = false;
+ unawaited(pool.request().then((_) => request1Complete = true));
+ var request2Complete = false;
+ unawaited(pool.request().then((_) => request2Complete = true));
+
+ var onRelease1Called = false;
+ var completer1 = Completer<void>();
+ resource1.allowRelease(() {
+ onRelease1Called = true;
+ return completer1.future;
+ });
+ await Future<void>.delayed(Duration.zero);
+ expect(onRelease1Called, isTrue);
+
+ var onRelease2Called = false;
+ var completer2 = Completer<void>();
+ resource2.allowRelease(() {
+ onRelease2Called = true;
+ return completer2.future;
+ });
+ await Future<void>.delayed(Duration.zero);
+ expect(onRelease2Called, isTrue);
+ expect(request1Complete, isFalse);
+ expect(request2Complete, isFalse);
+
+ // Complete the second resource's onRelease callback first. Even though it
+ // was triggered by the second blocking request, it should complete the
+ // first one to preserve ordering.
+ completer2.complete();
+ await Future<void>.delayed(Duration.zero);
+ expect(request1Complete, isTrue);
+ expect(request2Complete, isFalse);
+
+ completer1.complete();
+ await Future<void>.delayed(Duration.zero);
+ expect(request1Complete, isTrue);
+ expect(request2Complete, isTrue);
+ });
+
+ test('runs onRequest in the zone it was created', () async {
+ var pool = Pool(1);
+ var resource = await pool.request();
+
+ var outerZone = Zone.current;
+ runZoned(() {
+ var innerZone = Zone.current;
+ expect(innerZone, isNot(equals(outerZone)));
+
+ resource.allowRelease(expectAsync0(() {
+ expect(Zone.current, equals(innerZone));
+ }));
+ });
+
+ await pool.request();
+ });
+ });
+
+ test("done doesn't complete without close", () async {
+ var pool = Pool(1);
+ unawaited(pool.done.then(expectAsync1((_) {}, count: 0)));
+
+ var resource = await pool.request();
+ resource.release();
+
+ await Future<void>.delayed(Duration.zero);
+ });
+
+ group('close()', () {
+ test('disallows request() and withResource()', () {
+ var pool = Pool(1)..close();
+ expect(pool.request, throwsStateError);
+ expect(() => pool.withResource(() {}), throwsStateError);
+ });
+
+ test('pending requests are fulfilled', () async {
+ var pool = Pool(1);
+ var resource1 = await pool.request();
+ expect(
+ pool.request().then((resource2) {
+ resource2.release();
+ }),
+ completes);
+ expect(pool.done, completes);
+ expect(pool.close(), completes);
+ resource1.release();
+ });
+
+ test('pending requests are fulfilled with allowRelease', () async {
+ var pool = Pool(1);
+ var resource1 = await pool.request();
+
+ var completer = Completer<void>();
+ expect(
+ pool.request().then((resource2) {
+ expect(completer.isCompleted, isTrue);
+ resource2.release();
+ }),
+ completes);
+ expect(pool.close(), completes);
+
+ resource1.allowRelease(() => completer.future);
+ await Future<void>.delayed(Duration.zero);
+
+ completer.complete();
+ });
+
+ test("doesn't complete until all resources are released", () async {
+ var pool = Pool(2);
+ var resource1 = await pool.request();
+ var resource2 = await pool.request();
+ var resource3Future = pool.request();
+
+ var resource1Released = false;
+ var resource2Released = false;
+ var resource3Released = false;
+ expect(
+ pool.close().then((_) {
+ expect(resource1Released, isTrue);
+ expect(resource2Released, isTrue);
+ expect(resource3Released, isTrue);
+ }),
+ completes);
+
+ resource1Released = true;
+ resource1.release();
+ await Future<void>.delayed(Duration.zero);
+
+ resource2Released = true;
+ resource2.release();
+ await Future<void>.delayed(Duration.zero);
+
+ var resource3 = await resource3Future;
+ resource3Released = true;
+ resource3.release();
+ });
+
+ test('active onReleases complete as usual', () async {
+ var pool = Pool(1);
+ var resource = await pool.request();
+
+ // Set up an onRelease callback whose completion is controlled by
+ // [completer].
+ var completer = Completer<void>();
+ resource.allowRelease(() => completer.future);
+ expect(
+ pool.request().then((_) {
+ expect(completer.isCompleted, isTrue);
+ }),
+ completes);
+
+ await Future<void>.delayed(Duration.zero);
+ unawaited(pool.close());
+
+ await Future<void>.delayed(Duration.zero);
+ completer.complete();
+ });
+
+ test('inactive onReleases fire', () async {
+ var pool = Pool(2);
+ var resource1 = await pool.request();
+ var resource2 = await pool.request();
+
+ var completer1 = Completer<void>();
+ resource1.allowRelease(() => completer1.future);
+ var completer2 = Completer<void>();
+ resource2.allowRelease(() => completer2.future);
+
+ expect(
+ pool.close().then((_) {
+ expect(completer1.isCompleted, isTrue);
+ expect(completer2.isCompleted, isTrue);
+ }),
+ completes);
+
+ await Future<void>.delayed(Duration.zero);
+ completer1.complete();
+
+ await Future<void>.delayed(Duration.zero);
+ completer2.complete();
+ });
+
+ test('new allowReleases fire immediately', () async {
+ var pool = Pool(1);
+ var resource = await pool.request();
+
+ var completer = Completer<void>();
+ expect(
+ pool.close().then((_) {
+ expect(completer.isCompleted, isTrue);
+ }),
+ completes);
+
+ await Future<void>.delayed(Duration.zero);
+ resource.allowRelease(() => completer.future);
+
+ await Future<void>.delayed(Duration.zero);
+ completer.complete();
+ });
+
+ test('an onRelease error is piped to the return value', () async {
+ var pool = Pool(1);
+ var resource = await pool.request();
+
+ var completer = Completer<void>();
+ resource.allowRelease(() => completer.future);
+
+ expect(pool.done, throwsA('oh no!'));
+ expect(pool.close(), throwsA('oh no!'));
+
+ await Future<void>.delayed(Duration.zero);
+ completer.completeError('oh no!');
+ });
+ });
+
+ group('forEach', () {
+ late Pool pool;
+
+ tearDown(() async {
+ await pool.close();
+ });
+
+ const delayedToStringDuration = Duration(milliseconds: 10);
+
+ Future<String> delayedToString(int i) =>
+ Future<String>.delayed(delayedToStringDuration, () => i.toString());
+
+ for (var itemCount in [0, 5]) {
+ for (var poolSize in [1, 5, 6]) {
+ test('poolSize: $poolSize, itemCount: $itemCount', () async {
+ pool = Pool(poolSize);
+
+ var finishedItems = 0;
+
+ await for (var item in pool.forEach(
+ Iterable.generate(itemCount, (i) {
+ expect(i, lessThanOrEqualTo(finishedItems + poolSize),
+ reason: 'the iterator should be called lazily');
+ return i;
+ }),
+ delayedToString)) {
+ expect(int.parse(item), lessThan(itemCount));
+ finishedItems++;
+ }
+
+ expect(finishedItems, itemCount);
+ });
+ }
+ }
+
+ test('pool closed before listen', () async {
+ pool = Pool(2);
+
+ var stream = pool.forEach(Iterable<int>.generate(5), delayedToString);
+
+ await pool.close();
+
+ expect(stream.toList(), throwsStateError);
+ });
+
+ test('completes even if the pool is partially used', () async {
+ pool = Pool(2);
+
+ var resource = await pool.request();
+
+ var stream = pool.forEach(<int>[], delayedToString);
+
+ expect(await stream.length, 0);
+
+ resource.release();
+ });
+
+ test('stream paused longer than timeout', () async {
+ pool = Pool(2, timeout: delayedToStringDuration);
+
+ var resource = await pool.request();
+
+ var stream = pool.forEach<int, int>(
+ Iterable.generate(100, (i) {
+ expect(i, lessThan(20),
+ reason: 'The timeout should happen '
+ 'before the entire iterable is iterated.');
+ return i;
+ }), (i) async {
+ await Future<void>.delayed(Duration(milliseconds: i));
+ return i;
+ });
+
+ await expectLater(
+ stream.toList,
+ throwsA(const TypeMatcher<TimeoutException>().having(
+ (te) => te.message,
+ 'message',
+ contains('Pool deadlock: '
+ 'all resources have been allocated for too long.'))));
+
+ resource.release();
+ });
+
+ group('timing and timeout', () {
+ for (var poolSize in [2, 8, 64]) {
+ for (var otherTaskCount
+ in [0, 1, 7, 63].where((otc) => otc < poolSize)) {
+ test('poolSize: $poolSize, otherTaskCount: $otherTaskCount',
+ () async {
+ final itemCount = 128;
+ pool = Pool(poolSize, timeout: const Duration(milliseconds: 20));
+
+ var otherTasks = await Future.wait(
+ Iterable<int>.generate(otherTaskCount)
+ .map((i) => pool.request()));
+
+ try {
+ var finishedItems = 0;
+
+ var watch = Stopwatch()..start();
+
+ await for (var item in pool.forEach(
+ Iterable.generate(itemCount, (i) {
+ expect(i, lessThanOrEqualTo(finishedItems + poolSize),
+ reason: 'the iterator should be called lazily');
+ return i;
+ }),
+ delayedToString)) {
+ expect(int.parse(item), lessThan(itemCount));
+ finishedItems++;
+ }
+
+ expect(finishedItems, itemCount);
+
+ final expectedElapsed =
+ delayedToStringDuration.inMicroseconds * 4;
+
+ expect((watch.elapsed ~/ itemCount).inMicroseconds,
+ lessThan(expectedElapsed / (poolSize - otherTaskCount)),
+ reason: 'Average time per task should be '
+ 'proportionate to the available pool resources.');
+ } finally {
+ for (var task in otherTasks) {
+ task.release();
+ }
+ }
+ });
+ }
+ }
+ }, testOn: 'vm');
+
+ test('partial iteration', () async {
+ pool = Pool(5);
+ var stream = pool.forEach(Iterable<int>.generate(100), delayedToString);
+ expect(await stream.take(10).toList(), hasLength(10));
+ });
+
+ test('pool close during data with waiting to be done', () async {
+ pool = Pool(5);
+
+ var stream = pool.forEach(Iterable<int>.generate(100), delayedToString);
+
+ var dataCount = 0;
+ var subscription = stream.listen((data) {
+ dataCount++;
+ pool.close();
+ });
+
+ await subscription.asFuture<void>();
+ expect(dataCount, 100);
+ await subscription.cancel();
+ });
+
+ test('pause and resume ', () async {
+ var generatedCount = 0;
+ var dataCount = 0;
+ final poolSize = 5;
+
+ pool = Pool(poolSize);
+
+ var stream = pool.forEach(
+ Iterable<int>.generate(40, (i) {
+ expect(generatedCount, lessThanOrEqualTo(dataCount + 2 * poolSize),
+ reason: 'The iterator should not be called '
+ 'much faster than the data is consumed.');
+ generatedCount++;
+ return i;
+ }),
+ delayedToString);
+
+ // ignore: cancel_subscriptions
+ late StreamSubscription subscription;
+
+ subscription = stream.listen(
+ (data) {
+ dataCount++;
+
+ if (int.parse(data) % 3 == 1) {
+ subscription.pause(Future(() async {
+ await Future<void>.delayed(const Duration(milliseconds: 100));
+ }));
+ }
+ },
+ onError: registerException,
+ onDone: expectAsync0(() {
+ expect(dataCount, 40);
+ }),
+ );
+ });
+
+ group('cancel', () {
+ final dataSize = 32;
+ for (var i = 1; i < 5; i++) {
+ test('with pool size $i', () async {
+ pool = Pool(i);
+
+ var stream =
+ pool.forEach(Iterable<int>.generate(dataSize), delayedToString);
+
+ var cancelCompleter = Completer<void>();
+
+ StreamSubscription subscription;
+
+ var eventCount = 0;
+ subscription = stream.listen((data) {
+ eventCount++;
+ if (int.parse(data) == dataSize ~/ 2) {
+ cancelCompleter.complete();
+ }
+ }, onError: registerException);
+
+ await cancelCompleter.future;
+
+ await subscription.cancel();
+
+ expect(eventCount, 1 + dataSize ~/ 2);
+ });
+ }
+ });
+
+ group('errors', () {
+ Future<void> errorInIterator({
+ bool Function(int item, Object error, StackTrace stack)? onError,
+ }) async {
+ pool = Pool(20);
+
+ var listFuture = pool
+ .forEach(
+ Iterable.generate(100, (i) {
+ if (i == 50) {
+ throw StateError('error while generating item in iterator');
+ }
+
+ return i;
+ }),
+ delayedToString,
+ onError: onError)
+ .toList();
+
+ await expectLater(() async => listFuture, throwsStateError);
+ }
+
+ test('iteration, no onError', () async {
+ await errorInIterator();
+ });
+ test('iteration, with onError', () async {
+ await errorInIterator(onError: (i, e, s) => false);
+ });
+
+ test('error in action, no onError', () async {
+ pool = Pool(20);
+
+ var listFuture = pool.forEach(Iterable<int>.generate(100), (i) async {
+ await Future<void>.delayed(const Duration(milliseconds: 10));
+ if (i == 10) {
+ throw UnsupportedError('10 is not supported');
+ }
+ return i.toString();
+ }).toList();
+
+ await expectLater(() async => listFuture, throwsUnsupportedError);
+ });
+
+ test('error in action, no onError', () async {
+ pool = Pool(20);
+
+ var list = await pool.forEach(Iterable<int>.generate(100),
+ (int i) async {
+ await Future<void>.delayed(const Duration(milliseconds: 10));
+ if (i % 10 == 0) {
+ throw UnsupportedError('Multiples of 10 not supported');
+ }
+ return i.toString();
+ },
+ onError: (item, error, stack) =>
+ error is! UnsupportedError).toList();
+
+ expect(list, hasLength(90));
+ });
+ });
+ });
+
+ test('throw error when pool limit <= 0', () {
+ expect(() => Pool(-1), throwsArgumentError);
+ expect(() => Pool(0), throwsArgumentError);
+ });
+}
+
+/// Returns a function that will cause the test to fail if it's called.
+///
+/// This should only be called within a [FakeAsync.run] zone.
+void Function() expectNoAsync() {
+ var stack = Trace.current(1);
+ return () => registerException(
+ TestFailure('Expected function not to be called.'), stack);
+}
+
+/// A matcher for Futures that asserts that they don't complete.
+///
+/// This should only be called within a [FakeAsync.run] zone.
+Matcher get doesNotComplete => predicate((Future future) {
+ var stack = Trace.current(1);
+ future.then((_) => registerException(
+ TestFailure('Expected future not to complete.'), stack));
+ return true;
+ });
diff --git a/pkgs/pub_semver/.gitignore b/pkgs/pub_semver/.gitignore
new file mode 100644
index 0000000..49ce72d
--- /dev/null
+++ b/pkgs/pub_semver/.gitignore
@@ -0,0 +1,3 @@
+.dart_tool/
+.packages
+pubspec.lock
diff --git a/pkgs/pub_semver/CHANGELOG.md b/pkgs/pub_semver/CHANGELOG.md
new file mode 100644
index 0000000..a31fbb2
--- /dev/null
+++ b/pkgs/pub_semver/CHANGELOG.md
@@ -0,0 +1,177 @@
+## 2.1.5
+
+- Require Dart `3.4.0`.
+- Move to `dart-lang/tools` monorepo.
+
+## 2.1.4
+
+- Added topics to `pubspec.yaml`.
+
+## 2.1.3
+
+- Add type parameters to the signatures of the `Version.preRelease` and
+ `Version.build` fields (`List` ==> `List<Object>`).
+ [#74](https://github.com/dart-lang/pub_semver/pull/74).
+- Require Dart 2.17.
+
+## 2.1.2
+
+- Add markdown badges to the readme.
+
+## 2.1.1
+
+- Fixed the version parsing pattern to only accept dots between version
+ components.
+
+## 2.1.0
+
+- Added `Version.canonicalizedVersion` to help scrub leading zeros and highlight
+ that `Version.toString()` preserves leading zeros.
+- Annotated `Version` with `@sealed` to discourage users from implementing the
+ interface.
+
+## 2.0.0
+
+- Stable null safety release.
+- `Version.primary` now throws `StateError` if the `versions` argument is empty.
+
+## 1.4.4
+
+- Fix a bug of `VersionRange.union` where ranges bounded at infinity would get
+ combined wrongly.
+
+# 1.4.3
+
+- Update Dart SDK constraint to `>=2.0.0 <3.0.0`.
+- Update `package:collection` constraint to `^1.0.0`.
+
+## 1.4.2
+
+* Set max SDK version to `<3.0.0`.
+
+## 1.4.1
+
+* Fix a bug where there upper bound of a version range with a build identifier
+ could accidentally be rewritten.
+
+## 1.4.0
+
+* Add a `Version.firstPreRelease` getter that returns the first possible
+ pre-release of a version.
+
+* Add a `Version.isFirstPreRelease` getter that returns whether a version is the
+ first possible pre-release.
+
+* `new VersionRange()` with an exclusive maximum now replaces the maximum with
+ its first pre-release version. This matches the existing semantics, where an
+ exclusive maximum would exclude pre-release versions of that maximum.
+
+ Explicitly representing this by changing the maximum version ensures that all
+ operations behave correctly with respect to the special pre-release semantics.
+ In particular, it fixes bugs where, for example,
+ `(>=1.0.0 <2.0.0-dev).union(>=2.0.0-dev <2.0.0)` and
+ `(>=1.0.0 <3.0.0).difference(^1.0.0)` wouldn't include `2.0.0-dev`.
+
+* Add an `alwaysIncludeMaxPreRelease` parameter to `new VersionRange()`, which
+ disables the replacement described above and allows users to create ranges
+ that do include the pre-release versions of an exclusive max version.
+
+## 1.3.7
+
+* Fix more bugs with `VersionRange.intersect()`, `VersionRange.difference()`,
+ and `VersionRange.union()` involving version ranges with pre-release maximums.
+
+## 1.3.6
+
+* Fix a bug where constraints that only allowed pre-release versions would be
+ parsed as empty constraints.
+
+## 1.3.5
+
+* Fix a bug where `VersionRange.intersect()` would return incorrect results for
+ pre-release versions with the same base version number as release versions.
+
+## 1.3.4
+
+* Fix a bug where `VersionRange.allowsAll()`, `VersionRange.allowsAny()`, and
+ `VersionRange.difference()` would return incorrect results for pre-release
+ versions with the same base version number as release versions.
+
+## 1.3.3
+
+* Fix a bug where `VersionRange.difference()` with a union constraint that
+ covered the entire range would crash.
+
+## 1.3.2
+
+* Fix a checked-mode error in `VersionRange.difference()`.
+
+## 1.3.1
+
+* Fix a new strong mode error.
+
+## 1.3.0
+
+* Make the `VersionUnion` class public. This was previously used internally to
+ implement `new VersionConstraint.unionOf()` and `VersionConstraint.union()`.
+ Now it's public so you can use it too.
+
+* Added `VersionConstraint.difference()`. This returns a constraint matching all
+ versions matched by one constraint but not another.
+
+* Make `VersionRange` implement `Comparable<VersionRange>`. Ranges are ordered
+ first by lower bound, then by upper bound.
+
+## 1.2.4
+
+* Fix all remaining strong mode warnings.
+
+## 1.2.3
+
+* Addressed three strong mode warnings.
+
+## 1.2.2
+
+* Make the package analyze under strong mode and compile with the DDC (Dart Dev
+ Compiler). Fix two issues with a private subclass of `VersionConstraint`
+ having different types for overridden methods.
+
+## 1.2.1
+
+* Allow version ranges like `>=1.2.3-dev.1 <1.2.3` to match pre-release versions
+ of `1.2.3`. Previously, these didn't match, since the pre-release versions had
+ the same major, minor, and patch numbers as the max; now an exception has been
+ added if they also have the same major, minor, and patch numbers as the min
+ *and* the min is also a pre-release version.
+
+## 1.2.0
+
+* Add a `VersionConstraint.union()` method and a `new
+ VersionConstraint.unionOf()` constructor. These each return a constraint that
+ matches multiple existing constraints.
+
+* Add a `VersionConstraint.allowsAll()` method, which returns whether one
+ constraint is a superset of another.
+
+* Add a `VersionConstraint.allowsAny()` method, which returns whether one
+ constraint overlaps another.
+
+* `Version` now implements `VersionRange`.
+
+## 1.1.0
+
+* Add support for the `^` operator for compatible versions according to pub's
+ notion of compatibility. `^1.2.3` is equivalent to `>=1.2.3 <2.0.0`; `^0.1.2`
+ is equivalent to `>=0.1.2 <0.2.0`.
+
+* Add `Version.nextBreaking`, which returns the next version that introduces
+ breaking changes after a given version.
+
+* Add `new VersionConstraint.compatibleWith()`, which returns a range covering
+ all versions compatible with a given version.
+
+* Add a custom `VersionRange.hashCode` to make it properly hashable.
+
+## 1.0.0
+
+* Initial release.
diff --git a/pkgs/pub_semver/LICENSE b/pkgs/pub_semver/LICENSE
new file mode 100644
index 0000000..000cd7b
--- /dev/null
+++ b/pkgs/pub_semver/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2014, 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.
diff --git a/pkgs/pub_semver/README.md b/pkgs/pub_semver/README.md
new file mode 100644
index 0000000..03c92a3
--- /dev/null
+++ b/pkgs/pub_semver/README.md
@@ -0,0 +1,107 @@
+[](https://github.com/dart-lang/tools/actions/workflows/pub_semver.yaml)
+[](https://pub.dev/packages/pub_semver)
+[](https://pub.dev/packages/pub_semver/publisher)
+
+Handles version numbers and version constraints in the same way that [pub][]
+does.
+
+## Semantics
+
+The semantics here very closely follow the
+[Semantic Versioning spec version 2.0.0-rc.1][semver]. It differs from semver
+in a few corner cases:
+
+ * **Version ordering does take build suffixes into account.** This is unlike
+ semver 2.0.0 but like earlier versions of semver. Version `1.2.3+1` is
+ considered a lower number than `1.2.3+2`.
+
+ Since a package may have published multiple versions that differ only by
+ build suffix, pub still has to pick one of them *somehow*. Semver leaves
+ that issue unresolved, so we just say that build numbers are sorted like
+ pre-release suffixes.
+
+ * **Pre-release versions are excluded from most max ranges.** Let's say a
+ user is depending on "foo" with constraint `>=1.0.0 <2.0.0` and that "foo"
+ has published these versions:
+
+ * `1.0.0`
+ * `1.1.0`
+ * `1.2.0`
+ * `2.0.0-alpha`
+ * `2.0.0-beta`
+ * `2.0.0`
+ * `2.1.0`
+
+ Versions `2.0.0` and `2.1.0` are excluded by the constraint since neither
+ matches `<2.0.0`. However, since semver specifies that pre-release versions
+ are lower than the non-prerelease version (i.e. `2.0.0-beta < 2.0.0`, then
+ the `<2.0.0` constraint does technically allow those.
+
+ But that's almost never what the user wants. If their package doesn't work
+ with foo `2.0.0`, it's certainly not likely to work with experimental,
+ unstable versions of `2.0.0`'s API, which is what pre-release versions
+ represent.
+
+ To handle that, `<` version ranges don't allow pre-release versions of the
+ maximum unless the max is itself a pre-release, or the min is a pre-release
+ of the same version. In other words, a `<2.0.0` constraint will prohibit not
+ just `2.0.0` but any pre-release of `2.0.0`. However, `<2.0.0-beta` will
+ exclude `2.0.0-beta` but allow `2.0.0-alpha`. Likewise, `>2.0.0-alpha
+ <2.0.0` will exclude `2.0.0-alpha` but allow `2.0.0-beta`.
+
+ * **Pre-release versions are avoided when possible.** The above case
+ handles pre-release versions at the top of the range, but what about in
+ the middle? What if "foo" has these versions:
+
+ * `1.0.0`
+ * `1.2.0-alpha`
+ * `1.2.0`
+ * `1.3.0-experimental`
+
+ When a number of versions are valid, pub chooses the best one where "best"
+ usually means "highest numbered". That follows the user's intuition that,
+ all else being equal, they want the latest and greatest. Here, that would
+ mean `1.3.0-experimental`. However, most users don't want to use unstable
+ versions of their dependencies.
+
+ We want pre-releases to be explicitly opt-in so that package consumers
+ don't get unpleasant surprises and so that package maintainers are free to
+ put out pre-releases and get feedback without dragging all of their users
+ onto the bleeding edge.
+
+ To accommodate that, when pub is choosing a version, it uses *priority*
+ order which is different from strict comparison ordering. Any stable
+ version is considered higher priority than any unstable version. The above
+ versions, in priority order, are:
+
+ * `1.2.0-alpha`
+ * `1.3.0-experimental`
+ * `1.0.0`
+ * `1.2.0`
+
+ This ensures that users only end up with an unstable version when there are
+ no alternatives. Usually this means they've picked a constraint that
+ specifically selects that unstable version -- they've deliberately opted
+ into it.
+
+ * **There is a notion of compatibility between pre-1.0.0 versions.** Semver
+ deems all pre-1.0.0 versions to be incompatible. This means that the only
+ way to ensure compatibility when depending on a pre-1.0.0 package is to
+ pin the dependency to an exact version. Pinned version constraints prevent
+ automatic patch and pre-release updates. To avoid this situation, pub
+ defines the "next breaking" version as the version which increments the
+ major version if it's greater than zero, and the minor version otherwise,
+ resets subsequent digits to zero, and strips any pre-release or build
+ suffix. For example, here are some versions along with their next breaking
+ ones:
+
+ `0.0.3` -> `0.1.0`
+ `0.7.2-alpha` -> `0.8.0`
+ `1.2.3` -> `2.0.0`
+
+ To make use of this, pub defines a "^" operator which yields a version
+ constraint greater than or equal to a given version, but less than its next
+ breaking one.
+
+[pub]: https://pub.dev
+[semver]: https://semver.org/spec/v2.0.0-rc.1.html
diff --git a/pkgs/pub_semver/analysis_options.yaml b/pkgs/pub_semver/analysis_options.yaml
new file mode 100644
index 0000000..76380a0
--- /dev/null
+++ b/pkgs/pub_semver/analysis_options.yaml
@@ -0,0 +1,31 @@
+# https://dart.dev/guides/language/analysis-options
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-casts: true
+ strict-inference: true
+ strict-raw-types: true
+
+linter:
+ rules:
+ - avoid_bool_literals_in_conditional_expressions
+ - avoid_classes_with_only_static_members
+ - avoid_private_typedef_functions
+ - avoid_redundant_argument_values
+ - avoid_returning_this
+ - avoid_unused_constructor_parameters
+ - avoid_void_async
+ - cancel_subscriptions
+ - cascade_invocations
+ - join_return_with_assignment
+ - literal_only_boolean_expressions
+ - missing_whitespace_between_adjacent_strings
+ - no_adjacent_strings_in_list
+ - no_runtimeType_toString
+ - prefer_const_declarations
+ - prefer_expression_function_bodies
+ - unnecessary_await_in_return
+ - use_if_null_to_convert_nulls_to_bools
+ - use_raw_strings
+ - use_string_buffers
diff --git a/pkgs/pub_semver/example/example.dart b/pkgs/pub_semver/example/example.dart
new file mode 100644
index 0000000..890343c
--- /dev/null
+++ b/pkgs/pub_semver/example/example.dart
@@ -0,0 +1,17 @@
+// Copyright (c) 2020, 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:pub_semver/pub_semver.dart';
+
+void main() {
+ final range = VersionConstraint.parse('^2.0.0');
+
+ for (var version in [
+ Version.parse('1.2.3-pre'),
+ Version.parse('2.0.0+123'),
+ Version.parse('3.0.0-dev'),
+ ]) {
+ print('$version ${version.isPreRelease} ${range.allows(version)}');
+ }
+}
diff --git a/pkgs/pub_semver/lib/pub_semver.dart b/pkgs/pub_semver/lib/pub_semver.dart
new file mode 100644
index 0000000..4b6487c
--- /dev/null
+++ b/pkgs/pub_semver/lib/pub_semver.dart
@@ -0,0 +1,8 @@
+// Copyright (c) 2014, 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/version.dart';
+export 'src/version_constraint.dart';
+export 'src/version_range.dart' hide CompatibleWithVersionRange;
+export 'src/version_union.dart';
diff --git a/pkgs/pub_semver/lib/src/patterns.dart b/pkgs/pub_semver/lib/src/patterns.dart
new file mode 100644
index 0000000..03119ac
--- /dev/null
+++ b/pkgs/pub_semver/lib/src/patterns.dart
@@ -0,0 +1,19 @@
+// Copyright (c) 2014, 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.
+
+/// Regex that matches a version number at the beginning of a string.
+final startVersion = RegExp(r'^' // Start at beginning.
+ r'(\d+)\.(\d+)\.(\d+)' // Version number.
+ r'(-([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?' // Pre-release.
+ r'(\+([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?'); // Build.
+
+/// Like [startVersion] but matches the entire string.
+final completeVersion = RegExp('${startVersion.pattern}\$');
+
+/// Parses a comparison operator ("<", ">", "<=", or ">=") at the beginning of
+/// a string.
+final startComparison = RegExp(r'^[<>]=?');
+
+/// The "compatible with" operator.
+const compatibleWithChar = '^';
diff --git a/pkgs/pub_semver/lib/src/utils.dart b/pkgs/pub_semver/lib/src/utils.dart
new file mode 100644
index 0000000..a9f714f
--- /dev/null
+++ b/pkgs/pub_semver/lib/src/utils.dart
@@ -0,0 +1,58 @@
+// Copyright (c) 2015, 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 'version.dart';
+import 'version_range.dart';
+
+/// Returns whether [range1] is immediately next to, but not overlapping,
+/// [range2].
+bool areAdjacent(VersionRange range1, VersionRange range2) {
+ if (range1.max != range2.min) return false;
+
+ return (range1.includeMax && !range2.includeMin) ||
+ (!range1.includeMax && range2.includeMin);
+}
+
+/// Returns whether [range1] allows lower versions than [range2].
+bool allowsLower(VersionRange range1, VersionRange range2) {
+ if (range1.min == null) return range2.min != null;
+ if (range2.min == null) return false;
+
+ var comparison = range1.min!.compareTo(range2.min!);
+ if (comparison == -1) return true;
+ if (comparison == 1) return false;
+ return range1.includeMin && !range2.includeMin;
+}
+
+/// Returns whether [range1] allows higher versions than [range2].
+bool allowsHigher(VersionRange range1, VersionRange range2) {
+ if (range1.max == null) return range2.max != null;
+ if (range2.max == null) return false;
+
+ var comparison = range1.max!.compareTo(range2.max!);
+ if (comparison == 1) return true;
+ if (comparison == -1) return false;
+ return range1.includeMax && !range2.includeMax;
+}
+
+/// Returns whether [range1] allows only versions lower than those allowed by
+/// [range2].
+bool strictlyLower(VersionRange range1, VersionRange range2) {
+ if (range1.max == null || range2.min == null) return false;
+
+ var comparison = range1.max!.compareTo(range2.min!);
+ if (comparison == -1) return true;
+ if (comparison == 1) return false;
+ return !range1.includeMax || !range2.includeMin;
+}
+
+/// Returns whether [range1] allows only versions higher than those allowed by
+/// [range2].
+bool strictlyHigher(VersionRange range1, VersionRange range2) =>
+ strictlyLower(range2, range1);
+
+bool equalsWithoutPreRelease(Version version1, Version version2) =>
+ version1.major == version2.major &&
+ version1.minor == version2.minor &&
+ version1.patch == version2.patch;
diff --git a/pkgs/pub_semver/lib/src/version.dart b/pkgs/pub_semver/lib/src/version.dart
new file mode 100644
index 0000000..90f3d53
--- /dev/null
+++ b/pkgs/pub_semver/lib/src/version.dart
@@ -0,0 +1,391 @@
+// Copyright (c) 2014, 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;
+
+import 'package:collection/collection.dart';
+import 'package:meta/meta.dart' show sealed;
+
+import 'patterns.dart';
+import 'version_constraint.dart';
+import 'version_range.dart';
+
+/// The equality operator to use for comparing version components.
+const _equality = IterableEquality<Object>();
+
+/// A parsed semantic version number.
+@sealed
+class Version implements VersionConstraint, VersionRange {
+ /// No released version: i.e. "0.0.0".
+ static Version get none => Version(0, 0, 0);
+
+ /// Compares [a] and [b] to see which takes priority over the other.
+ ///
+ /// Returns `1` if [a] takes priority over [b] and `-1` if vice versa. If
+ /// [a] and [b] are equivalent, returns `0`.
+ ///
+ /// Unlike [compareTo], which *orders* versions, this determines which
+ /// version a user is likely to prefer. In particular, it prioritizes
+ /// pre-release versions lower than stable versions, regardless of their
+ /// version numbers. Pub uses this when determining which version to prefer
+ /// when a number of versions are allowed. In that case, it will always
+ /// choose a stable version when possible.
+ ///
+ /// When used to sort a list, orders in ascending priority so that the
+ /// highest priority version is *last* in the result.
+ static int prioritize(Version a, Version b) {
+ // Sort all prerelease versions after all normal versions. This way
+ // the solver will prefer stable packages over unstable ones.
+ if (a.isPreRelease && !b.isPreRelease) return -1;
+ if (!a.isPreRelease && b.isPreRelease) return 1;
+
+ return a.compareTo(b);
+ }
+
+ /// Like [prioritize], but lower version numbers are considered greater than
+ /// higher version numbers.
+ ///
+ /// This still considers prerelease versions to be lower than non-prerelease
+ /// versions. Pub uses this when downgrading -- it chooses the lowest version
+ /// but still excludes pre-release versions when possible.
+ static int antiprioritize(Version a, Version b) {
+ if (a.isPreRelease && !b.isPreRelease) return -1;
+ if (!a.isPreRelease && b.isPreRelease) return 1;
+
+ return b.compareTo(a);
+ }
+
+ /// The major version number: "1" in "1.2.3".
+ final int major;
+
+ /// The minor version number: "2" in "1.2.3".
+ final int minor;
+
+ /// The patch version number: "3" in "1.2.3".
+ final int patch;
+
+ /// The pre-release identifier: "foo" in "1.2.3-foo".
+ ///
+ /// This is split into a list of components, each of which may be either a
+ /// string or a non-negative integer. It may also be empty, indicating that
+ /// this version has no pre-release identifier.
+ final List<Object> preRelease;
+
+ /// The build identifier: "foo" in "1.2.3+foo".
+ ///
+ /// This is split into a list of components, each of which may be either a
+ /// string or a non-negative integer. It may also be empty, indicating that
+ /// this version has no build identifier.
+ final List<Object> build;
+
+ /// The original string representation of the version number.
+ ///
+ /// This preserves textual artifacts like leading zeros that may be left out
+ /// of the parsed version.
+ final String _text;
+
+ @override
+ Version get min => this;
+ @override
+ Version get max => this;
+ @override
+ bool get includeMin => true;
+ @override
+ bool get includeMax => true;
+
+ Version._(this.major, this.minor, this.patch, String? preRelease,
+ String? build, this._text)
+ : preRelease = preRelease == null ? <Object>[] : _splitParts(preRelease),
+ build = build == null ? [] : _splitParts(build) {
+ if (major < 0) throw ArgumentError('Major version must be non-negative.');
+ if (minor < 0) throw ArgumentError('Minor version must be non-negative.');
+ if (patch < 0) throw ArgumentError('Patch version must be non-negative.');
+ }
+
+ /// Creates a new [Version] object.
+ factory Version(int major, int minor, int patch,
+ {String? pre, String? build}) {
+ var text = '$major.$minor.$patch';
+ if (pre != null) text += '-$pre';
+ if (build != null) text += '+$build';
+
+ return Version._(major, minor, patch, pre, build, text);
+ }
+
+ /// Creates a new [Version] by parsing [text].
+ factory Version.parse(String text) {
+ final match = completeVersion.firstMatch(text);
+ if (match == null) {
+ throw FormatException('Could not parse "$text".');
+ }
+
+ try {
+ var major = int.parse(match[1]!);
+ var minor = int.parse(match[2]!);
+ var patch = int.parse(match[3]!);
+
+ var preRelease = match[5];
+ var build = match[8];
+
+ return Version._(major, minor, patch, preRelease, build, text);
+ } on FormatException {
+ throw FormatException('Could not parse "$text".');
+ }
+ }
+
+ /// Returns the primary version out of [versions].
+ ///
+ /// This is the highest-numbered stable (non-prerelease) version. If there
+ /// are no stable versions, it's just the highest-numbered version.
+ ///
+ /// If [versions] is empty, throws a [StateError].
+ static Version primary(List<Version> versions) {
+ var primary = versions.first;
+ for (var version in versions.skip(1)) {
+ if ((!version.isPreRelease && primary.isPreRelease) ||
+ (version.isPreRelease == primary.isPreRelease && version > primary)) {
+ primary = version;
+ }
+ }
+ return primary;
+ }
+
+ /// Splits a string of dot-delimited identifiers into their component parts.
+ ///
+ /// Identifiers that are numeric are converted to numbers.
+ static List<Object> _splitParts(String text) => text
+ .split('.')
+ .map((part) =>
+ // Return an integer part if possible, otherwise return the string
+ // as-is
+ int.tryParse(part) ?? part)
+ .toList();
+
+ @override
+ bool operator ==(Object other) =>
+ other is Version &&
+ major == other.major &&
+ minor == other.minor &&
+ patch == other.patch &&
+ _equality.equals(preRelease, other.preRelease) &&
+ _equality.equals(build, other.build);
+
+ @override
+ int get hashCode =>
+ major ^
+ minor ^
+ patch ^
+ _equality.hash(preRelease) ^
+ _equality.hash(build);
+
+ bool operator <(Version other) => compareTo(other) < 0;
+ bool operator >(Version other) => compareTo(other) > 0;
+ bool operator <=(Version other) => compareTo(other) <= 0;
+ bool operator >=(Version other) => compareTo(other) >= 0;
+
+ @override
+ bool get isAny => false;
+ @override
+ bool get isEmpty => false;
+
+ /// Whether or not this is a pre-release version.
+ bool get isPreRelease => preRelease.isNotEmpty;
+
+ /// Gets the next major version number that follows this one.
+ ///
+ /// If this version is a pre-release of a major version release (i.e. the
+ /// minor and patch versions are zero), then it just strips the pre-release
+ /// suffix. Otherwise, it increments the major version and resets the minor
+ /// and patch.
+ Version get nextMajor {
+ if (isPreRelease && minor == 0 && patch == 0) {
+ return Version(major, minor, patch);
+ }
+
+ return _incrementMajor();
+ }
+
+ /// Gets the next minor version number that follows this one.
+ ///
+ /// If this version is a pre-release of a minor version release (i.e. the
+ /// patch version is zero), then it just strips the pre-release suffix.
+ /// Otherwise, it increments the minor version and resets the patch.
+ Version get nextMinor {
+ if (isPreRelease && patch == 0) {
+ return Version(major, minor, patch);
+ }
+
+ return _incrementMinor();
+ }
+
+ /// Gets the next patch version number that follows this one.
+ ///
+ /// If this version is a pre-release, then it just strips the pre-release
+ /// suffix. Otherwise, it increments the patch version.
+ Version get nextPatch {
+ if (isPreRelease) {
+ return Version(major, minor, patch);
+ }
+
+ return _incrementPatch();
+ }
+
+ /// Gets the next breaking version number that follows this one.
+ ///
+ /// Increments [major] if it's greater than zero, otherwise [minor], resets
+ /// subsequent digits to zero, and strips any [preRelease] or [build]
+ /// suffix.
+ Version get nextBreaking {
+ if (major == 0) {
+ return _incrementMinor();
+ }
+
+ return _incrementMajor();
+ }
+
+ /// Returns the first possible pre-release of this version.
+ Version get firstPreRelease => Version(major, minor, patch, pre: '0');
+
+ /// Returns whether this is the first possible pre-release of its version.
+ bool get isFirstPreRelease => preRelease.length == 1 && preRelease.first == 0;
+
+ Version _incrementMajor() => Version(major + 1, 0, 0);
+ Version _incrementMinor() => Version(major, minor + 1, 0);
+ Version _incrementPatch() => Version(major, minor, patch + 1);
+
+ /// Tests if [other] matches this version exactly.
+ @override
+ bool allows(Version other) => this == other;
+
+ @override
+ bool allowsAll(VersionConstraint other) => other.isEmpty || other == this;
+
+ @override
+ bool allowsAny(VersionConstraint other) => other.allows(this);
+
+ @override
+ VersionConstraint intersect(VersionConstraint other) =>
+ other.allows(this) ? this : VersionConstraint.empty;
+
+ @override
+ VersionConstraint union(VersionConstraint other) {
+ if (other.allows(this)) return other;
+
+ if (other is VersionRange) {
+ if (other.min == this) {
+ return VersionRange(
+ min: other.min,
+ max: other.max,
+ includeMin: true,
+ includeMax: other.includeMax,
+ alwaysIncludeMaxPreRelease: true);
+ }
+
+ if (other.max == this) {
+ return VersionRange(
+ min: other.min,
+ max: other.max,
+ includeMin: other.includeMin,
+ includeMax: true,
+ alwaysIncludeMaxPreRelease: true);
+ }
+ }
+
+ return VersionConstraint.unionOf([this, other]);
+ }
+
+ @override
+ VersionConstraint difference(VersionConstraint other) =>
+ other.allows(this) ? VersionConstraint.empty : this;
+
+ @override
+ int compareTo(VersionRange other) {
+ if (other is Version) {
+ if (major != other.major) return major.compareTo(other.major);
+ if (minor != other.minor) return minor.compareTo(other.minor);
+ if (patch != other.patch) return patch.compareTo(other.patch);
+
+ // Pre-releases always come before no pre-release string.
+ if (!isPreRelease && other.isPreRelease) return 1;
+ if (!other.isPreRelease && isPreRelease) return -1;
+
+ var comparison = _compareLists(preRelease, other.preRelease);
+ if (comparison != 0) return comparison;
+
+ // Builds always come after no build string.
+ if (build.isEmpty && other.build.isNotEmpty) return -1;
+ if (other.build.isEmpty && build.isNotEmpty) return 1;
+ return _compareLists(build, other.build);
+ } else {
+ return -other.compareTo(this);
+ }
+ }
+
+ /// Get non-canonical string representation of this [Version].
+ ///
+ /// If created with [Version.parse], the string from which the version was
+ /// parsed is returned. Unlike the [canonicalizedVersion] this preserves
+ /// artifacts such as leading zeros.
+ @override
+ String toString() => _text;
+
+ /// Get a canonicalized string representation of this [Version].
+ ///
+ /// Unlike [Version.toString()] this always returns a canonical string
+ /// representation of this [Version].
+ ///
+ /// **Example**
+ /// ```dart
+ /// final v = Version.parse('01.02.03-01.dev+pre.02');
+ ///
+ /// assert(v.toString() == '01.02.03-01.dev+pre.02');
+ /// assert(v.canonicalizedVersion == '1.2.3-1.dev+pre.2');
+ /// assert(Version.parse(v.canonicalizedVersion) == v);
+ /// ```
+ String get canonicalizedVersion => Version(
+ major,
+ minor,
+ patch,
+ pre: preRelease.isNotEmpty ? preRelease.join('.') : null,
+ build: build.isNotEmpty ? build.join('.') : null,
+ ).toString();
+
+ /// Compares a dot-separated component of two versions.
+ ///
+ /// This is used for the pre-release and build version parts. This follows
+ /// Rule 12 of the Semantic Versioning spec (v2.0.0-rc.1).
+ int _compareLists(List<Object> a, List<Object> b) {
+ for (var i = 0; i < math.max(a.length, b.length); i++) {
+ var aPart = (i < a.length) ? a[i] : null;
+ var bPart = (i < b.length) ? b[i] : null;
+
+ if (aPart == bPart) continue;
+
+ // Missing parts come before present ones.
+ if (aPart == null) return -1;
+ if (bPart == null) return 1;
+
+ if (aPart is num) {
+ if (bPart is num) {
+ // Compare two numbers.
+ return aPart.compareTo(bPart);
+ } else {
+ // Numbers come before strings.
+ return -1;
+ }
+ } else {
+ if (bPart is num) {
+ // Strings come after numbers.
+ return 1;
+ } else {
+ // Compare two strings.
+ return (aPart as String).compareTo(bPart as String);
+ }
+ }
+ }
+
+ // The lists are entirely equal.
+ return 0;
+ }
+}
diff --git a/pkgs/pub_semver/lib/src/version_constraint.dart b/pkgs/pub_semver/lib/src/version_constraint.dart
new file mode 100644
index 0000000..948118e
--- /dev/null
+++ b/pkgs/pub_semver/lib/src/version_constraint.dart
@@ -0,0 +1,287 @@
+// Copyright (c) 2014, 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 'patterns.dart';
+import 'utils.dart';
+import 'version.dart';
+import 'version_range.dart';
+import 'version_union.dart';
+
+/// A [VersionConstraint] is a predicate that can determine whether a given
+/// version is valid or not.
+///
+/// For example, a ">= 2.0.0" constraint allows any version that is "2.0.0" or
+/// greater. Version objects themselves implement this to match a specific
+/// version.
+abstract class VersionConstraint {
+ /// A [VersionConstraint] that allows all versions.
+ static VersionConstraint any = VersionRange();
+
+ /// A [VersionConstraint] that allows no versions -- the empty set.
+ static VersionConstraint empty = const _EmptyVersion();
+
+ /// Parses a version constraint.
+ ///
+ /// This string is one of:
+ ///
+ /// * "any". [any] version.
+ /// * "^" followed by a version string. Versions compatible with
+ /// ([VersionConstraint.compatibleWith]) the version.
+ /// * a series of version parts. Each part can be one of:
+ /// * A version string like `1.2.3`. In other words, anything that can be
+ /// parsed by [Version.parse()].
+ /// * A comparison operator (`<`, `>`, `<=`, or `>=`) followed by a
+ /// version string.
+ ///
+ /// Whitespace is ignored.
+ ///
+ /// Examples:
+ ///
+ /// any
+ /// ^0.7.2
+ /// ^1.0.0-alpha
+ /// 1.2.3-alpha
+ /// <=5.1.4
+ /// >2.0.4 <= 2.4.6
+ factory VersionConstraint.parse(String text) {
+ var originalText = text;
+
+ void skipWhitespace() {
+ text = text.trim();
+ }
+
+ skipWhitespace();
+
+ // Handle the "any" constraint.
+ if (text == 'any') return any;
+
+ // Try to parse and consume a version number.
+ Version? matchVersion() {
+ var version = startVersion.firstMatch(text);
+ if (version == null) return null;
+
+ text = text.substring(version.end);
+ return Version.parse(version[0]!);
+ }
+
+ // Try to parse and consume a comparison operator followed by a version.
+ VersionRange? matchComparison() {
+ var comparison = startComparison.firstMatch(text);
+ if (comparison == null) return null;
+
+ var op = comparison[0]!;
+ text = text.substring(comparison.end);
+ skipWhitespace();
+
+ var version = matchVersion();
+ if (version == null) {
+ throw FormatException('Expected version number after "$op" in '
+ '"$originalText", got "$text".');
+ }
+
+ return switch (op) {
+ '<=' => VersionRange(max: version, includeMax: true),
+ '<' => VersionRange(max: version, alwaysIncludeMaxPreRelease: true),
+ '>=' => VersionRange(min: version, includeMin: true),
+ '>' => VersionRange(min: version),
+ _ => throw UnsupportedError(op),
+ };
+ }
+
+ // Try to parse the "^" operator followed by a version.
+ VersionConstraint? matchCompatibleWith() {
+ if (!text.startsWith(compatibleWithChar)) return null;
+
+ text = text.substring(compatibleWithChar.length);
+ skipWhitespace();
+
+ var version = matchVersion();
+ if (version == null) {
+ throw FormatException('Expected version number after '
+ '"$compatibleWithChar" in "$originalText", got "$text".');
+ }
+
+ if (text.isNotEmpty) {
+ throw FormatException('Cannot include other constraints with '
+ '"$compatibleWithChar" constraint in "$originalText".');
+ }
+
+ return VersionConstraint.compatibleWith(version);
+ }
+
+ var compatibleWith = matchCompatibleWith();
+ if (compatibleWith != null) return compatibleWith;
+
+ Version? min;
+ var includeMin = false;
+ Version? max;
+ var includeMax = false;
+
+ for (;;) {
+ skipWhitespace();
+
+ if (text.isEmpty) break;
+
+ var newRange = matchVersion() ?? matchComparison();
+ if (newRange == null) {
+ throw FormatException('Could not parse version "$originalText". '
+ 'Unknown text at "$text".');
+ }
+
+ if (newRange.min != null) {
+ if (min == null || newRange.min! > min) {
+ min = newRange.min;
+ includeMin = newRange.includeMin;
+ } else if (newRange.min == min && !newRange.includeMin) {
+ includeMin = false;
+ }
+ }
+
+ if (newRange.max != null) {
+ if (max == null || newRange.max! < max) {
+ max = newRange.max;
+ includeMax = newRange.includeMax;
+ } else if (newRange.max == max && !newRange.includeMax) {
+ includeMax = false;
+ }
+ }
+ }
+
+ if (min == null && max == null) {
+ throw const FormatException('Cannot parse an empty string.');
+ }
+
+ if (min != null && max != null) {
+ if (min > max) return VersionConstraint.empty;
+ if (min == max) {
+ if (includeMin && includeMax) return min;
+ return VersionConstraint.empty;
+ }
+ }
+
+ return VersionRange(
+ min: min, includeMin: includeMin, max: max, includeMax: includeMax);
+ }
+
+ /// Creates a version constraint which allows all versions that are
+ /// backward compatible with [version].
+ ///
+ /// Versions are considered backward compatible with [version] if they
+ /// are greater than or equal to [version], but less than the next breaking
+ /// version ([Version.nextBreaking]) of [version].
+ factory VersionConstraint.compatibleWith(Version version) =>
+ CompatibleWithVersionRange(version);
+
+ /// Creates a new version constraint that is the intersection of
+ /// [constraints].
+ ///
+ /// It only allows versions that all of those constraints allow. If
+ /// constraints is empty, then it returns a VersionConstraint that allows
+ /// all versions.
+ factory VersionConstraint.intersection(
+ Iterable<VersionConstraint> constraints) {
+ var constraint = VersionRange();
+ for (var other in constraints) {
+ constraint = constraint.intersect(other) as VersionRange;
+ }
+ return constraint;
+ }
+
+ /// Creates a new version constraint that is the union of [constraints].
+ ///
+ /// It allows any versions that any of those constraints allows. If
+ /// [constraints] is empty, this returns a constraint that allows no versions.
+ factory VersionConstraint.unionOf(Iterable<VersionConstraint> constraints) {
+ var flattened = constraints.expand((constraint) {
+ if (constraint.isEmpty) return <VersionRange>[];
+ if (constraint is VersionUnion) return constraint.ranges;
+ if (constraint is VersionRange) return [constraint];
+ throw ArgumentError('Unknown VersionConstraint type $constraint.');
+ }).toList();
+
+ if (flattened.isEmpty) return VersionConstraint.empty;
+
+ if (flattened.any((constraint) => constraint.isAny)) {
+ return VersionConstraint.any;
+ }
+
+ flattened.sort();
+
+ var merged = <VersionRange>[];
+ for (var constraint in flattened) {
+ // Merge this constraint with the previous one, but only if they touch.
+ if (merged.isEmpty ||
+ (!merged.last.allowsAny(constraint) &&
+ !areAdjacent(merged.last, constraint))) {
+ merged.add(constraint);
+ } else {
+ merged[merged.length - 1] =
+ merged.last.union(constraint) as VersionRange;
+ }
+ }
+
+ if (merged.length == 1) return merged.single;
+ return VersionUnion.fromRanges(merged);
+ }
+
+ /// Returns `true` if this constraint allows no versions.
+ bool get isEmpty;
+
+ /// Returns `true` if this constraint allows all versions.
+ bool get isAny;
+
+ /// Returns `true` if this constraint allows [version].
+ bool allows(Version version);
+
+ /// Returns `true` if this constraint allows all the versions that [other]
+ /// allows.
+ bool allowsAll(VersionConstraint other);
+
+ /// Returns `true` if this constraint allows any of the versions that [other]
+ /// allows.
+ bool allowsAny(VersionConstraint other);
+
+ /// Returns a [VersionConstraint] that only allows [Version]s allowed by both
+ /// this and [other].
+ VersionConstraint intersect(VersionConstraint other);
+
+ /// Returns a [VersionConstraint] that allows [Version]s allowed by either
+ /// this or [other].
+ VersionConstraint union(VersionConstraint other);
+
+ /// Returns a [VersionConstraint] that allows [Version]s allowed by this but
+ /// not [other].
+ VersionConstraint difference(VersionConstraint other);
+}
+
+class _EmptyVersion implements VersionConstraint {
+ const _EmptyVersion();
+
+ @override
+ bool get isEmpty => true;
+
+ @override
+ bool get isAny => false;
+
+ @override
+ bool allows(Version other) => false;
+
+ @override
+ bool allowsAll(VersionConstraint other) => other.isEmpty;
+
+ @override
+ bool allowsAny(VersionConstraint other) => false;
+
+ @override
+ VersionConstraint intersect(VersionConstraint other) => this;
+
+ @override
+ VersionConstraint union(VersionConstraint other) => other;
+
+ @override
+ VersionConstraint difference(VersionConstraint other) => this;
+
+ @override
+ String toString() => '<empty>';
+}
diff --git a/pkgs/pub_semver/lib/src/version_range.dart b/pkgs/pub_semver/lib/src/version_range.dart
new file mode 100644
index 0000000..6f2ed54
--- /dev/null
+++ b/pkgs/pub_semver/lib/src/version_range.dart
@@ -0,0 +1,476 @@
+// Copyright (c) 2014, 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 'utils.dart';
+import 'version.dart';
+import 'version_constraint.dart';
+import 'version_union.dart';
+
+/// Constrains versions to a fall within a given range.
+///
+/// If there is a minimum, then this only allows versions that are at that
+/// minimum or greater. If there is a maximum, then only versions less than
+/// that are allowed. In other words, this allows `>= min, < max`.
+///
+/// Version ranges are ordered first by their lower bounds, then by their upper
+/// bounds. For example, `>=1.0.0 <2.0.0` is before `>=1.5.0 <2.0.0` is before
+/// `>=1.5.0 <3.0.0`.
+class VersionRange implements Comparable<VersionRange>, VersionConstraint {
+ /// The minimum end of the range.
+ ///
+ /// If [includeMin] is `true`, this will be the minimum allowed version.
+ /// Otherwise, it will be the highest version below the range that is not
+ /// allowed.
+ ///
+ /// This may be `null` in which case the range has no minimum end and allows
+ /// any version less than the maximum.
+ final Version? min;
+
+ /// The maximum end of the range.
+ ///
+ /// If [includeMax] is `true`, this will be the maximum allowed version.
+ /// Otherwise, it will be the lowest version above the range that is not
+ /// allowed.
+ ///
+ /// This may be `null` in which case the range has no maximum end and allows
+ /// any version greater than the minimum.
+ final Version? max;
+
+ /// If `true` then [min] is allowed by the range.
+ final bool includeMin;
+
+ /// If `true`, then [max] is allowed by the range.
+ final bool includeMax;
+
+ /// Creates a new version range from [min] to [max], either inclusive or
+ /// exclusive.
+ ///
+ /// If it is an error if [min] is greater than [max].
+ ///
+ /// Either [max] or [min] may be omitted to not clamp the range at that end.
+ /// If both are omitted, the range allows all versions.
+ ///
+ /// If [includeMin] is `true`, then the minimum end of the range is inclusive.
+ /// Likewise, passing [includeMax] as `true` makes the upper end inclusive.
+ ///
+ /// If [alwaysIncludeMaxPreRelease] is `true`, this will always include
+ /// pre-release versions of an exclusive [max]. Otherwise, it will use the
+ /// default behavior for pre-release versions of [max].
+ factory VersionRange(
+ {Version? min,
+ Version? max,
+ bool includeMin = false,
+ bool includeMax = false,
+ bool alwaysIncludeMaxPreRelease = false}) {
+ if (min != null && max != null && min > max) {
+ throw ArgumentError(
+ 'Minimum version ("$min") must be less than maximum ("$max").');
+ }
+
+ if (!alwaysIncludeMaxPreRelease &&
+ !includeMax &&
+ max != null &&
+ !max.isPreRelease &&
+ max.build.isEmpty &&
+ (min == null ||
+ !min.isPreRelease ||
+ !equalsWithoutPreRelease(min, max))) {
+ max = max.firstPreRelease;
+ }
+
+ return VersionRange._(min, max, includeMin, includeMax);
+ }
+
+ VersionRange._(this.min, this.max, this.includeMin, this.includeMax);
+
+ @override
+ bool operator ==(Object other) {
+ if (other is! VersionRange) return false;
+
+ return min == other.min &&
+ max == other.max &&
+ includeMin == other.includeMin &&
+ includeMax == other.includeMax;
+ }
+
+ @override
+ int get hashCode =>
+ min.hashCode ^
+ (max.hashCode * 3) ^
+ (includeMin.hashCode * 5) ^
+ (includeMax.hashCode * 7);
+
+ @override
+ bool get isEmpty => false;
+
+ @override
+ bool get isAny => min == null && max == null;
+
+ /// Tests if [other] falls within this version range.
+ @override
+ bool allows(Version other) {
+ if (min != null) {
+ if (other < min!) return false;
+ if (!includeMin && other == min) return false;
+ }
+
+ if (max != null) {
+ if (other > max!) return false;
+ if (!includeMax && other == max) return false;
+ }
+
+ return true;
+ }
+
+ @override
+ bool allowsAll(VersionConstraint other) {
+ if (other.isEmpty) return true;
+ if (other is Version) return allows(other);
+
+ if (other is VersionUnion) {
+ return other.ranges.every(allowsAll);
+ }
+
+ if (other is VersionRange) {
+ return !allowsLower(other, this) && !allowsHigher(other, this);
+ }
+
+ throw ArgumentError('Unknown VersionConstraint type $other.');
+ }
+
+ @override
+ bool allowsAny(VersionConstraint other) {
+ if (other.isEmpty) return false;
+ if (other is Version) return allows(other);
+
+ if (other is VersionUnion) {
+ return other.ranges.any(allowsAny);
+ }
+
+ if (other is VersionRange) {
+ return !strictlyLower(other, this) && !strictlyHigher(other, this);
+ }
+
+ throw ArgumentError('Unknown VersionConstraint type $other.');
+ }
+
+ @override
+ VersionConstraint intersect(VersionConstraint other) {
+ if (other.isEmpty) return other;
+ if (other is VersionUnion) return other.intersect(this);
+
+ // A range and a Version just yields the version if it's in the range.
+ if (other is Version) {
+ return allows(other) ? other : VersionConstraint.empty;
+ }
+
+ if (other is VersionRange) {
+ // Intersect the two ranges.
+ Version? intersectMin;
+ bool intersectIncludeMin;
+ if (allowsLower(this, other)) {
+ if (strictlyLower(this, other)) return VersionConstraint.empty;
+ intersectMin = other.min;
+ intersectIncludeMin = other.includeMin;
+ } else {
+ if (strictlyLower(other, this)) return VersionConstraint.empty;
+ intersectMin = min;
+ intersectIncludeMin = includeMin;
+ }
+
+ Version? intersectMax;
+ bool intersectIncludeMax;
+ if (allowsHigher(this, other)) {
+ intersectMax = other.max;
+ intersectIncludeMax = other.includeMax;
+ } else {
+ intersectMax = max;
+ intersectIncludeMax = includeMax;
+ }
+
+ if (intersectMin == null && intersectMax == null) {
+ // Open range.
+ return VersionRange();
+ }
+
+ // If the range is just a single version.
+ if (intersectMin == intersectMax) {
+ // Because we already verified that the lower range isn't strictly
+ // lower, there must be some overlap.
+ assert(intersectIncludeMin && intersectIncludeMax);
+ return intersectMin!;
+ }
+
+ // If we got here, there is an actual range.
+ return VersionRange(
+ min: intersectMin,
+ max: intersectMax,
+ includeMin: intersectIncludeMin,
+ includeMax: intersectIncludeMax,
+ alwaysIncludeMaxPreRelease: true);
+ }
+
+ throw ArgumentError('Unknown VersionConstraint type $other.');
+ }
+
+ @override
+ VersionConstraint union(VersionConstraint other) {
+ if (other is Version) {
+ if (allows(other)) return this;
+
+ if (other == min) {
+ return VersionRange(
+ min: min,
+ max: max,
+ includeMin: true,
+ includeMax: includeMax,
+ alwaysIncludeMaxPreRelease: true);
+ }
+
+ if (other == max) {
+ return VersionRange(
+ min: min,
+ max: max,
+ includeMin: includeMin,
+ includeMax: true,
+ alwaysIncludeMaxPreRelease: true);
+ }
+
+ return VersionConstraint.unionOf([this, other]);
+ }
+
+ if (other is VersionRange) {
+ // If the two ranges don't overlap, we won't be able to create a single
+ // VersionRange for both of them.
+ var edgesTouch = (max != null &&
+ max == other.min &&
+ (includeMax || other.includeMin)) ||
+ (min != null && min == other.max && (includeMin || other.includeMax));
+ if (!edgesTouch && !allowsAny(other)) {
+ return VersionConstraint.unionOf([this, other]);
+ }
+
+ Version? unionMin;
+ bool unionIncludeMin;
+ if (allowsLower(this, other)) {
+ unionMin = min;
+ unionIncludeMin = includeMin;
+ } else {
+ unionMin = other.min;
+ unionIncludeMin = other.includeMin;
+ }
+
+ Version? unionMax;
+ bool unionIncludeMax;
+ if (allowsHigher(this, other)) {
+ unionMax = max;
+ unionIncludeMax = includeMax;
+ } else {
+ unionMax = other.max;
+ unionIncludeMax = other.includeMax;
+ }
+
+ return VersionRange(
+ min: unionMin,
+ max: unionMax,
+ includeMin: unionIncludeMin,
+ includeMax: unionIncludeMax,
+ alwaysIncludeMaxPreRelease: true);
+ }
+
+ return VersionConstraint.unionOf([this, other]);
+ }
+
+ @override
+ VersionConstraint difference(VersionConstraint other) {
+ if (other.isEmpty) return this;
+
+ if (other is Version) {
+ if (!allows(other)) return this;
+
+ if (other == min) {
+ if (!includeMin) return this;
+ return VersionRange(
+ min: min,
+ max: max,
+ includeMax: includeMax,
+ alwaysIncludeMaxPreRelease: true);
+ }
+
+ if (other == max) {
+ if (!includeMax) return this;
+ return VersionRange(
+ min: min,
+ max: max,
+ includeMin: includeMin,
+ alwaysIncludeMaxPreRelease: true);
+ }
+
+ return VersionUnion.fromRanges([
+ VersionRange(
+ min: min,
+ max: other,
+ includeMin: includeMin,
+ alwaysIncludeMaxPreRelease: true),
+ VersionRange(
+ min: other,
+ max: max,
+ includeMax: includeMax,
+ alwaysIncludeMaxPreRelease: true)
+ ]);
+ } else if (other is VersionRange) {
+ if (!allowsAny(other)) return this;
+
+ VersionRange? before;
+ if (!allowsLower(this, other)) {
+ before = null;
+ } else if (min == other.min) {
+ assert(includeMin && !other.includeMin);
+ assert(min != null);
+ before = min;
+ } else {
+ before = VersionRange(
+ min: min,
+ max: other.min,
+ includeMin: includeMin,
+ includeMax: !other.includeMin,
+ alwaysIncludeMaxPreRelease: true);
+ }
+
+ VersionRange? after;
+ if (!allowsHigher(this, other)) {
+ after = null;
+ } else if (max == other.max) {
+ assert(includeMax && !other.includeMax);
+ assert(max != null);
+ after = max;
+ } else {
+ after = VersionRange(
+ min: other.max,
+ max: max,
+ includeMin: !other.includeMax,
+ includeMax: includeMax,
+ alwaysIncludeMaxPreRelease: true);
+ }
+
+ if (before == null && after == null) return VersionConstraint.empty;
+ if (before == null) return after!;
+ if (after == null) return before;
+ return VersionUnion.fromRanges([before, after]);
+ } else if (other is VersionUnion) {
+ var ranges = <VersionRange>[];
+ var current = this;
+
+ for (var range in other.ranges) {
+ // Skip any ranges that are strictly lower than [current].
+ if (strictlyLower(range, current)) continue;
+
+ // If we reach a range strictly higher than [current], no more ranges
+ // will be relevant so we can bail early.
+ if (strictlyHigher(range, current)) break;
+
+ var difference = current.difference(range);
+ if (difference.isEmpty) {
+ return VersionConstraint.empty;
+ } else if (difference is VersionUnion) {
+ // If [range] split [current] in half, we only need to continue
+ // checking future ranges against the latter half.
+ assert(difference.ranges.length == 2);
+ ranges.add(difference.ranges.first);
+ current = difference.ranges.last;
+ } else {
+ current = difference as VersionRange;
+ }
+ }
+
+ if (ranges.isEmpty) return current;
+ return VersionUnion.fromRanges(ranges..add(current));
+ }
+
+ throw ArgumentError('Unknown VersionConstraint type $other.');
+ }
+
+ @override
+ int compareTo(VersionRange other) {
+ if (min == null) {
+ if (other.min == null) return _compareMax(other);
+ return -1;
+ } else if (other.min == null) {
+ return 1;
+ }
+
+ var result = min!.compareTo(other.min!);
+ if (result != 0) return result;
+ if (includeMin != other.includeMin) return includeMin ? -1 : 1;
+
+ return _compareMax(other);
+ }
+
+ /// Compares the maximum values of `this` and [other].
+ int _compareMax(VersionRange other) {
+ if (max == null) {
+ if (other.max == null) return 0;
+ return 1;
+ } else if (other.max == null) {
+ return -1;
+ }
+
+ var result = max!.compareTo(other.max!);
+ if (result != 0) return result;
+ if (includeMax != other.includeMax) return includeMax ? 1 : -1;
+ return 0;
+ }
+
+ @override
+ String toString() {
+ var buffer = StringBuffer();
+
+ final min = this.min;
+ if (min != null) {
+ buffer
+ ..write(includeMin ? '>=' : '>')
+ ..write(min);
+ }
+
+ final max = this.max;
+
+ if (max != null) {
+ if (min != null) buffer.write(' ');
+ if (includeMax) {
+ buffer
+ ..write('<=')
+ ..write(max);
+ } else {
+ buffer.write('<');
+ if (max.isFirstPreRelease) {
+ // Since `"<$max"` would parse the same as `"<$max-0"`, we just emit
+ // `<$max` to avoid confusing "-0" suffixes.
+ buffer.write('${max.major}.${max.minor}.${max.patch}');
+ } else {
+ buffer.write(max);
+
+ // If `">=$min <$max"` would parse as `">=$min <$max-0"`, add `-*` to
+ // indicate that actually does allow pre-release versions.
+ var minIsPreReleaseOfMax = min != null &&
+ min.isPreRelease &&
+ equalsWithoutPreRelease(min, max);
+ if (!max.isPreRelease && max.build.isEmpty && !minIsPreReleaseOfMax) {
+ buffer.write('-∞');
+ }
+ }
+ }
+ }
+
+ if (min == null && max == null) buffer.write('any');
+ return buffer.toString();
+ }
+}
+
+class CompatibleWithVersionRange extends VersionRange {
+ CompatibleWithVersionRange(Version version)
+ : super._(version, version.nextBreaking.firstPreRelease, true, false);
+
+ @override
+ String toString() => '^$min';
+}
diff --git a/pkgs/pub_semver/lib/src/version_union.dart b/pkgs/pub_semver/lib/src/version_union.dart
new file mode 100644
index 0000000..844d3b8
--- /dev/null
+++ b/pkgs/pub_semver/lib/src/version_union.dart
@@ -0,0 +1,224 @@
+// Copyright (c) 2015, 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:collection/collection.dart';
+
+import 'utils.dart';
+import 'version.dart';
+import 'version_constraint.dart';
+import 'version_range.dart';
+
+/// A version constraint representing a union of multiple disjoint version
+/// ranges.
+///
+/// An instance of this will only be created if the version can't be represented
+/// as a non-compound value.
+class VersionUnion implements VersionConstraint {
+ /// The constraints that compose this union.
+ ///
+ /// This list has two invariants:
+ ///
+ /// * Its contents are sorted using the standard ordering of [VersionRange]s.
+ /// * Its contents are disjoint and non-adjacent. In other words, for any two
+ /// constraints next to each other in the list, there's some version between
+ /// those constraints that they don't match.
+ final List<VersionRange> ranges;
+
+ @override
+ bool get isEmpty => false;
+
+ @override
+ bool get isAny => false;
+
+ /// Creates a union from a list of ranges with no pre-processing.
+ ///
+ /// It's up to the caller to ensure that the invariants described in [ranges]
+ /// are maintained. They are not verified by this constructor. To
+ /// automatically ensure that they're maintained, use
+ /// [VersionConstraint.unionOf] instead.
+ VersionUnion.fromRanges(this.ranges);
+
+ @override
+ bool allows(Version version) =>
+ ranges.any((constraint) => constraint.allows(version));
+
+ @override
+ bool allowsAll(VersionConstraint other) {
+ var ourRanges = ranges.iterator;
+ var theirRanges = _rangesFor(other).iterator;
+
+ // Because both lists of ranges are ordered by minimum version, we can
+ // safely move through them linearly here.
+ var ourRangesMoved = ourRanges.moveNext();
+ var theirRangesMoved = theirRanges.moveNext();
+ while (ourRangesMoved && theirRangesMoved) {
+ if (ourRanges.current.allowsAll(theirRanges.current)) {
+ theirRangesMoved = theirRanges.moveNext();
+ } else {
+ ourRangesMoved = ourRanges.moveNext();
+ }
+ }
+
+ // If our ranges have allowed all of their ranges, we'll have consumed all
+ // of them.
+ return !theirRangesMoved;
+ }
+
+ @override
+ bool allowsAny(VersionConstraint other) {
+ var ourRanges = ranges.iterator;
+ var theirRanges = _rangesFor(other).iterator;
+
+ // Because both lists of ranges are ordered by minimum version, we can
+ // safely move through them linearly here.
+ var ourRangesMoved = ourRanges.moveNext();
+ var theirRangesMoved = theirRanges.moveNext();
+ while (ourRangesMoved && theirRangesMoved) {
+ if (ourRanges.current.allowsAny(theirRanges.current)) {
+ return true;
+ }
+
+ // Move the constraint with the lower max value forward. This ensures that
+ // we keep both lists in sync as much as possible.
+ if (allowsHigher(theirRanges.current, ourRanges.current)) {
+ ourRangesMoved = ourRanges.moveNext();
+ } else {
+ theirRangesMoved = theirRanges.moveNext();
+ }
+ }
+
+ return false;
+ }
+
+ @override
+ VersionConstraint intersect(VersionConstraint other) {
+ var ourRanges = ranges.iterator;
+ var theirRanges = _rangesFor(other).iterator;
+
+ // Because both lists of ranges are ordered by minimum version, we can
+ // safely move through them linearly here.
+ var newRanges = <VersionRange>[];
+ var ourRangesMoved = ourRanges.moveNext();
+ var theirRangesMoved = theirRanges.moveNext();
+ while (ourRangesMoved && theirRangesMoved) {
+ var intersection = ourRanges.current.intersect(theirRanges.current);
+
+ if (!intersection.isEmpty) newRanges.add(intersection as VersionRange);
+
+ // Move the constraint with the lower max value forward. This ensures that
+ // we keep both lists in sync as much as possible, and that large ranges
+ // have a chance to match multiple small ranges that they contain.
+ if (allowsHigher(theirRanges.current, ourRanges.current)) {
+ ourRangesMoved = ourRanges.moveNext();
+ } else {
+ theirRangesMoved = theirRanges.moveNext();
+ }
+ }
+
+ if (newRanges.isEmpty) return VersionConstraint.empty;
+ if (newRanges.length == 1) return newRanges.single;
+
+ return VersionUnion.fromRanges(newRanges);
+ }
+
+ @override
+ VersionConstraint difference(VersionConstraint other) {
+ var ourRanges = ranges.iterator;
+ var theirRanges = _rangesFor(other).iterator;
+
+ var newRanges = <VersionRange>[];
+ ourRanges.moveNext();
+ theirRanges.moveNext();
+ var current = ourRanges.current;
+
+ bool theirNextRange() {
+ if (theirRanges.moveNext()) return true;
+
+ // If there are no more of their ranges, none of the rest of our ranges
+ // need to be subtracted so we can add them as-is.
+ newRanges.add(current);
+ while (ourRanges.moveNext()) {
+ newRanges.add(ourRanges.current);
+ }
+ return false;
+ }
+
+ bool ourNextRange({bool includeCurrent = true}) {
+ if (includeCurrent) newRanges.add(current);
+ if (!ourRanges.moveNext()) return false;
+ current = ourRanges.current;
+ return true;
+ }
+
+ for (;;) {
+ // If the current ranges are disjoint, move the lowest one forward.
+ if (strictlyLower(theirRanges.current, current)) {
+ if (!theirNextRange()) break;
+ continue;
+ }
+
+ if (strictlyHigher(theirRanges.current, current)) {
+ if (!ourNextRange()) break;
+ continue;
+ }
+
+ // If we're here, we know [theirRanges.current] overlaps [current].
+ var difference = current.difference(theirRanges.current);
+ if (difference is VersionUnion) {
+ // If their range split [current] in half, we only need to continue
+ // checking future ranges against the latter half.
+ assert(difference.ranges.length == 2);
+ newRanges.add(difference.ranges.first);
+ current = difference.ranges.last;
+
+ // Since their range split [current], it definitely doesn't allow higher
+ // versions, so we should move their ranges forward.
+ if (!theirNextRange()) break;
+ } else if (difference.isEmpty) {
+ if (!ourNextRange(includeCurrent: false)) break;
+ } else {
+ current = difference as VersionRange;
+
+ // Move the constraint with the lower max value forward. This ensures
+ // that we keep both lists in sync as much as possible, and that large
+ // ranges have a chance to subtract or be subtracted by multiple small
+ // ranges that they contain.
+ if (allowsHigher(current, theirRanges.current)) {
+ if (!theirNextRange()) break;
+ } else {
+ if (!ourNextRange()) break;
+ }
+ }
+ }
+
+ if (newRanges.isEmpty) return VersionConstraint.empty;
+ if (newRanges.length == 1) return newRanges.single;
+ return VersionUnion.fromRanges(newRanges);
+ }
+
+ /// Returns [constraint] as a list of ranges.
+ ///
+ /// This is used to normalize ranges of various types.
+ List<VersionRange> _rangesFor(VersionConstraint constraint) {
+ if (constraint.isEmpty) return [];
+ if (constraint is VersionUnion) return constraint.ranges;
+ if (constraint is VersionRange) return [constraint];
+ throw ArgumentError('Unknown VersionConstraint type $constraint.');
+ }
+
+ @override
+ VersionConstraint union(VersionConstraint other) =>
+ VersionConstraint.unionOf([this, other]);
+
+ @override
+ bool operator ==(Object other) =>
+ other is VersionUnion &&
+ const ListEquality<VersionRange>().equals(ranges, other.ranges);
+
+ @override
+ int get hashCode => const ListEquality<VersionRange>().hash(ranges);
+
+ @override
+ String toString() => ranges.join(' or ');
+}
diff --git a/pkgs/pub_semver/pubspec.yaml b/pkgs/pub_semver/pubspec.yaml
new file mode 100644
index 0000000..290fb92
--- /dev/null
+++ b/pkgs/pub_semver/pubspec.yaml
@@ -0,0 +1,20 @@
+name: pub_semver
+version: 2.1.5
+description: >-
+ Versions and version constraints implementing pub's versioning policy. This
+ is very similar to vanilla semver, with a few corner cases.
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/pub_semver
+topics:
+ - dart-pub
+ - semver
+
+environment:
+ sdk: ^3.4.0
+
+dependencies:
+ collection: ^1.15.0
+ meta: ^1.3.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ test: ^1.16.0
diff --git a/pkgs/pub_semver/test/utils.dart b/pkgs/pub_semver/test/utils.dart
new file mode 100644
index 0000000..bd7aa8f
--- /dev/null
+++ b/pkgs/pub_semver/test/utils.dart
@@ -0,0 +1,123 @@
+// Copyright (c) 2014, 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:pub_semver/pub_semver.dart';
+import 'package:test/test.dart';
+
+/// Some stock example versions to use in tests.
+final v003 = Version.parse('0.0.3');
+final v010 = Version.parse('0.1.0');
+final v072 = Version.parse('0.7.2');
+final v080 = Version.parse('0.8.0');
+final v114 = Version.parse('1.1.4');
+final v123 = Version.parse('1.2.3');
+final v124 = Version.parse('1.2.4');
+final v130 = Version.parse('1.3.0');
+final v140 = Version.parse('1.4.0');
+final v200 = Version.parse('2.0.0');
+final v201 = Version.parse('2.0.1');
+final v234 = Version.parse('2.3.4');
+final v250 = Version.parse('2.5.0');
+final v300 = Version.parse('3.0.0');
+
+/// A range that allows pre-release versions of its max version.
+final includeMaxPreReleaseRange =
+ VersionRange(max: v200, alwaysIncludeMaxPreRelease: true);
+
+/// A [Matcher] that tests if a [VersionConstraint] allows or does not allow a
+/// given list of [Version]s.
+class _VersionConstraintMatcher implements Matcher {
+ final List<Version> _expected;
+ final bool _allow;
+
+ _VersionConstraintMatcher(this._expected, this._allow);
+
+ @override
+ bool matches(dynamic item, Map<dynamic, dynamic> matchState) =>
+ (item is VersionConstraint) &&
+ _expected.every((version) => item.allows(version) == _allow);
+
+ @override
+ Description describe(Description description) {
+ description.addAll(' ${_allow ? "allows" : "does not allow"} versions ',
+ ', ', '', _expected);
+ return description;
+ }
+
+ @override
+ Description describeMismatch(dynamic item, Description mismatchDescription,
+ Map<dynamic, dynamic> matchState, bool verbose) {
+ if (item is! VersionConstraint) {
+ mismatchDescription.add('was not a VersionConstraint');
+ return mismatchDescription;
+ }
+
+ var first = true;
+ for (var version in _expected) {
+ if (item.allows(version) != _allow) {
+ if (first) {
+ if (_allow) {
+ mismatchDescription.addDescriptionOf(item).add(' did not allow ');
+ } else {
+ mismatchDescription.addDescriptionOf(item).add(' allowed ');
+ }
+ } else {
+ mismatchDescription.add(' and ');
+ }
+ first = false;
+
+ mismatchDescription.add(version.toString());
+ }
+ }
+
+ return mismatchDescription;
+ }
+}
+
+/// Gets a [Matcher] that validates that a [VersionConstraint] allows all
+/// given versions.
+Matcher allows(Version v1,
+ [Version? v2,
+ Version? v3,
+ Version? v4,
+ Version? v5,
+ Version? v6,
+ Version? v7,
+ Version? v8]) {
+ var versions = _makeVersionList(v1, v2, v3, v4, v5, v6, v7, v8);
+ return _VersionConstraintMatcher(versions, true);
+}
+
+/// Gets a [Matcher] that validates that a [VersionConstraint] allows none of
+/// the given versions.
+Matcher doesNotAllow(Version v1,
+ [Version? v2,
+ Version? v3,
+ Version? v4,
+ Version? v5,
+ Version? v6,
+ Version? v7,
+ Version? v8]) {
+ var versions = _makeVersionList(v1, v2, v3, v4, v5, v6, v7, v8);
+ return _VersionConstraintMatcher(versions, false);
+}
+
+List<Version> _makeVersionList(Version v1,
+ [Version? v2,
+ Version? v3,
+ Version? v4,
+ Version? v5,
+ Version? v6,
+ Version? v7,
+ Version? v8]) {
+ var versions = [v1];
+ if (v2 != null) versions.add(v2);
+ if (v3 != null) versions.add(v3);
+ if (v4 != null) versions.add(v4);
+ if (v5 != null) versions.add(v5);
+ if (v6 != null) versions.add(v6);
+ if (v7 != null) versions.add(v7);
+ if (v8 != null) versions.add(v8);
+ return versions;
+}
diff --git a/pkgs/pub_semver/test/version_constraint_test.dart b/pkgs/pub_semver/test/version_constraint_test.dart
new file mode 100644
index 0000000..4fbcbe0
--- /dev/null
+++ b/pkgs/pub_semver/test/version_constraint_test.dart
@@ -0,0 +1,185 @@
+// Copyright (c) 2014, 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:pub_semver/pub_semver.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ test('any', () {
+ expect(VersionConstraint.any.isAny, isTrue);
+ expect(
+ VersionConstraint.any,
+ allows(Version.parse('0.0.0-blah'), Version.parse('1.2.3'),
+ Version.parse('12345.678.90')));
+ });
+
+ test('empty', () {
+ expect(VersionConstraint.empty.isEmpty, isTrue);
+ expect(VersionConstraint.empty.isAny, isFalse);
+ expect(
+ VersionConstraint.empty,
+ doesNotAllow(Version.parse('0.0.0-blah'), Version.parse('1.2.3'),
+ Version.parse('12345.678.90')));
+ });
+
+ group('parse()', () {
+ test('parses an exact version', () {
+ var constraint = VersionConstraint.parse('1.2.3-alpha');
+
+ expect(constraint is Version, isTrue);
+ expect(constraint, equals(Version(1, 2, 3, pre: 'alpha')));
+ });
+
+ test('parses "any"', () {
+ var constraint = VersionConstraint.parse('any');
+
+ expect(
+ constraint,
+ allows(Version.parse('0.0.0'), Version.parse('1.2.3'),
+ Version.parse('12345.678.90')));
+ });
+
+ test('parses a ">" minimum version', () {
+ var constraint = VersionConstraint.parse('>1.2.3');
+
+ expect(constraint,
+ allows(Version.parse('1.2.3+foo'), Version.parse('1.2.4')));
+ expect(
+ constraint,
+ doesNotAllow(Version.parse('1.2.1'), Version.parse('1.2.3-build'),
+ Version.parse('1.2.3')));
+ });
+
+ test('parses a ">=" minimum version', () {
+ var constraint = VersionConstraint.parse('>=1.2.3');
+
+ expect(
+ constraint,
+ allows(Version.parse('1.2.3'), Version.parse('1.2.3+foo'),
+ Version.parse('1.2.4')));
+ expect(constraint,
+ doesNotAllow(Version.parse('1.2.1'), Version.parse('1.2.3-build')));
+ });
+
+ test('parses a "<" maximum version', () {
+ var constraint = VersionConstraint.parse('<1.2.3');
+
+ expect(constraint,
+ allows(Version.parse('1.2.1'), Version.parse('1.2.2+foo')));
+ expect(
+ constraint,
+ doesNotAllow(Version.parse('1.2.3'), Version.parse('1.2.3+foo'),
+ Version.parse('1.2.4')));
+ });
+
+ test('parses a "<=" maximum version', () {
+ var constraint = VersionConstraint.parse('<=1.2.3');
+
+ expect(
+ constraint,
+ allows(Version.parse('1.2.1'), Version.parse('1.2.3-build'),
+ Version.parse('1.2.3')));
+ expect(constraint,
+ doesNotAllow(Version.parse('1.2.3+foo'), Version.parse('1.2.4')));
+ });
+
+ test('parses a series of space-separated constraints', () {
+ var constraint = VersionConstraint.parse('>1.0.0 >=1.2.3 <1.3.0');
+
+ expect(
+ constraint, allows(Version.parse('1.2.3'), Version.parse('1.2.5')));
+ expect(
+ constraint,
+ doesNotAllow(Version.parse('1.2.3-pre'), Version.parse('1.3.0'),
+ Version.parse('3.4.5')));
+ });
+
+ test('parses a pre-release-only constraint', () {
+ var constraint = VersionConstraint.parse('>=1.0.0-dev.2 <1.0.0');
+ expect(constraint,
+ allows(Version.parse('1.0.0-dev.2'), Version.parse('1.0.0-dev.3')));
+ expect(constraint,
+ doesNotAllow(Version.parse('1.0.0-dev.1'), Version.parse('1.0.0')));
+ });
+
+ test('ignores whitespace around comparison operators', () {
+ var constraint = VersionConstraint.parse(' >1.0.0>=1.2.3 < 1.3.0');
+
+ expect(
+ constraint, allows(Version.parse('1.2.3'), Version.parse('1.2.5')));
+ expect(
+ constraint,
+ doesNotAllow(Version.parse('1.2.3-pre'), Version.parse('1.3.0'),
+ Version.parse('3.4.5')));
+ });
+
+ test('does not allow "any" to be mixed with other constraints', () {
+ expect(() => VersionConstraint.parse('any 1.0.0'), throwsFormatException);
+ });
+
+ test('parses a "^" version', () {
+ expect(VersionConstraint.parse('^0.0.3'),
+ equals(VersionConstraint.compatibleWith(v003)));
+
+ expect(VersionConstraint.parse('^0.7.2'),
+ equals(VersionConstraint.compatibleWith(v072)));
+
+ expect(VersionConstraint.parse('^1.2.3'),
+ equals(VersionConstraint.compatibleWith(v123)));
+
+ var min = Version.parse('0.7.2-pre+1');
+ expect(VersionConstraint.parse('^0.7.2-pre+1'),
+ equals(VersionConstraint.compatibleWith(min)));
+ });
+
+ test('does not allow "^" to be mixed with other constraints', () {
+ expect(() => VersionConstraint.parse('>=1.2.3 ^1.0.0'),
+ throwsFormatException);
+ expect(() => VersionConstraint.parse('^1.0.0 <1.2.3'),
+ throwsFormatException);
+ });
+
+ test('ignores whitespace around "^"', () {
+ var constraint = VersionConstraint.parse(' ^ 1.2.3 ');
+
+ expect(constraint, equals(VersionConstraint.compatibleWith(v123)));
+ });
+
+ test('throws FormatException on a bad string', () {
+ var bad = [
+ '', ' ', // Empty string.
+ 'foo', // Bad text.
+ '>foo', // Bad text after operator.
+ '^foo', // Bad text after "^".
+ '1.0.0 foo', '1.0.0foo', // Bad text after version.
+ 'anything', // Bad text after "any".
+ '<>1.0.0', // Multiple operators.
+ '1.0.0<' // Trailing operator.
+ ];
+
+ for (var text in bad) {
+ expect(() => VersionConstraint.parse(text), throwsFormatException);
+ }
+ });
+ });
+
+ group('compatibleWith()', () {
+ test('returns the range of compatible versions', () {
+ var constraint = VersionConstraint.compatibleWith(v072);
+
+ expect(
+ constraint,
+ equals(VersionRange(
+ min: v072, includeMin: true, max: v072.nextBreaking)));
+ });
+
+ test('toString() uses "^"', () {
+ var constraint = VersionConstraint.compatibleWith(v072);
+
+ expect(constraint.toString(), equals('^0.7.2'));
+ });
+ });
+}
diff --git a/pkgs/pub_semver/test/version_range_test.dart b/pkgs/pub_semver/test/version_range_test.dart
new file mode 100644
index 0000000..5978df0
--- /dev/null
+++ b/pkgs/pub_semver/test/version_range_test.dart
@@ -0,0 +1,998 @@
+// Copyright (c) 2014, 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:pub_semver/pub_semver.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ group('constructor', () {
+ test('takes a min and max', () {
+ var range = VersionRange(min: v123, max: v124);
+ expect(range.isAny, isFalse);
+ expect(range.min, equals(v123));
+ expect(range.max, equals(v124.firstPreRelease));
+ });
+
+ group("doesn't make the max a pre-release if", () {
+ test("it's already a pre-release", () {
+ expect(VersionRange(max: Version.parse('1.2.4-pre')).max,
+ equals(Version.parse('1.2.4-pre')));
+ });
+
+ test('includeMax is true', () {
+ expect(VersionRange(max: v124, includeMax: true).max, equals(v124));
+ });
+
+ test('min is a prerelease of max', () {
+ expect(VersionRange(min: Version.parse('1.2.4-pre'), max: v124).max,
+ equals(v124));
+ });
+
+ test('max has a build identifier', () {
+ expect(VersionRange(max: Version.parse('1.2.4+1')).max,
+ equals(Version.parse('1.2.4+1')));
+ });
+ });
+
+ test('allows omitting max', () {
+ var range = VersionRange(min: v123);
+ expect(range.isAny, isFalse);
+ expect(range.min, equals(v123));
+ expect(range.max, isNull);
+ });
+
+ test('allows omitting min and max', () {
+ var range = VersionRange();
+ expect(range.isAny, isTrue);
+ expect(range.min, isNull);
+ expect(range.max, isNull);
+ });
+
+ test('takes includeMin', () {
+ var range = VersionRange(min: v123, includeMin: true);
+ expect(range.includeMin, isTrue);
+ });
+
+ test('includeMin defaults to false if omitted', () {
+ var range = VersionRange(min: v123);
+ expect(range.includeMin, isFalse);
+ });
+
+ test('takes includeMax', () {
+ var range = VersionRange(max: v123, includeMax: true);
+ expect(range.includeMax, isTrue);
+ });
+
+ test('includeMax defaults to false if omitted', () {
+ var range = VersionRange(max: v123);
+ expect(range.includeMax, isFalse);
+ });
+
+ test('throws if min > max', () {
+ expect(() => VersionRange(min: v124, max: v123), throwsArgumentError);
+ });
+ });
+
+ group('allows()', () {
+ test('version must be greater than min', () {
+ var range = VersionRange(min: v123);
+
+ expect(range, allows(Version.parse('1.3.3'), Version.parse('2.3.3')));
+ expect(
+ range, doesNotAllow(Version.parse('1.2.2'), Version.parse('1.2.3')));
+ });
+
+ test('version must be min or greater if includeMin', () {
+ var range = VersionRange(min: v123, includeMin: true);
+
+ expect(
+ range,
+ allows(Version.parse('1.2.3'), Version.parse('1.3.3'),
+ Version.parse('2.3.3')));
+ expect(range, doesNotAllow(Version.parse('1.2.2')));
+ });
+
+ test('pre-release versions of inclusive min are excluded', () {
+ var range = VersionRange(min: v123, includeMin: true);
+
+ expect(range, allows(Version.parse('1.2.4-dev')));
+ expect(range, doesNotAllow(Version.parse('1.2.3-dev')));
+ });
+
+ test('version must be less than max', () {
+ var range = VersionRange(max: v234);
+
+ expect(range, allows(Version.parse('2.3.3')));
+ expect(
+ range, doesNotAllow(Version.parse('2.3.4'), Version.parse('2.4.3')));
+ });
+
+ test('pre-release versions of non-pre-release max are excluded', () {
+ var range = VersionRange(max: v234);
+
+ expect(range, allows(Version.parse('2.3.3')));
+ expect(range,
+ doesNotAllow(Version.parse('2.3.4-dev'), Version.parse('2.3.4')));
+ });
+
+ test(
+ 'pre-release versions of non-pre-release max are included if min is a '
+ 'pre-release of the same version', () {
+ var range = VersionRange(min: Version.parse('2.3.4-dev.0'), max: v234);
+
+ expect(range, allows(Version.parse('2.3.4-dev.1')));
+ expect(
+ range,
+ doesNotAllow(Version.parse('2.3.3'), Version.parse('2.3.4-dev'),
+ Version.parse('2.3.4')));
+ });
+
+ test('pre-release versions of pre-release max are included', () {
+ var range = VersionRange(max: Version.parse('2.3.4-dev.2'));
+
+ expect(range, allows(Version.parse('2.3.4-dev.1')));
+ expect(
+ range,
+ doesNotAllow(
+ Version.parse('2.3.4-dev.2'), Version.parse('2.3.4-dev.3')));
+ });
+
+ test('version must be max or less if includeMax', () {
+ var range = VersionRange(min: v123, max: v234, includeMax: true);
+
+ expect(
+ range,
+ allows(
+ Version.parse('2.3.3'),
+ Version.parse('2.3.4'),
+ // Pre-releases of the max are allowed.
+ Version.parse('2.3.4-dev')));
+ expect(range, doesNotAllow(Version.parse('2.4.3')));
+ });
+
+ test('has no min if one was not set', () {
+ var range = VersionRange(max: v123);
+
+ expect(range, allows(Version.parse('0.0.0')));
+ expect(range, doesNotAllow(Version.parse('1.2.3')));
+ });
+
+ test('has no max if one was not set', () {
+ var range = VersionRange(min: v123);
+
+ expect(range, allows(Version.parse('1.3.3'), Version.parse('999.3.3')));
+ expect(range, doesNotAllow(Version.parse('1.2.3')));
+ });
+
+ test('allows any version if there is no min or max', () {
+ var range = VersionRange();
+
+ expect(range, allows(Version.parse('0.0.0'), Version.parse('999.99.9')));
+ });
+
+ test('allows pre-releases of the max with includeMaxPreRelease', () {
+ expect(includeMaxPreReleaseRange, allows(Version.parse('2.0.0-dev')));
+ });
+ });
+
+ group('allowsAll()', () {
+ test('allows an empty constraint', () {
+ expect(
+ VersionRange(min: v123, max: v250).allowsAll(VersionConstraint.empty),
+ isTrue);
+ });
+
+ test('allows allowed versions', () {
+ var range = VersionRange(min: v123, max: v250, includeMax: true);
+ expect(range.allowsAll(v123), isFalse);
+ expect(range.allowsAll(v124), isTrue);
+ expect(range.allowsAll(v250), isTrue);
+ expect(range.allowsAll(v300), isFalse);
+ });
+
+ test('with no min', () {
+ var range = VersionRange(max: v250);
+ expect(range.allowsAll(VersionRange(min: v080, max: v140)), isTrue);
+ expect(range.allowsAll(VersionRange(min: v080, max: v300)), isFalse);
+ expect(range.allowsAll(VersionRange(max: v140)), isTrue);
+ expect(range.allowsAll(VersionRange(max: v300)), isFalse);
+ expect(range.allowsAll(range), isTrue);
+ expect(range.allowsAll(VersionConstraint.any), isFalse);
+ });
+
+ test('with no max', () {
+ var range = VersionRange(min: v010);
+ expect(range.allowsAll(VersionRange(min: v080, max: v140)), isTrue);
+ expect(range.allowsAll(VersionRange(min: v003, max: v140)), isFalse);
+ expect(range.allowsAll(VersionRange(min: v080)), isTrue);
+ expect(range.allowsAll(VersionRange(min: v003)), isFalse);
+ expect(range.allowsAll(range), isTrue);
+ expect(range.allowsAll(VersionConstraint.any), isFalse);
+ });
+
+ test('with a min and max', () {
+ var range = VersionRange(min: v010, max: v250);
+ expect(range.allowsAll(VersionRange(min: v080, max: v140)), isTrue);
+ expect(range.allowsAll(VersionRange(min: v080, max: v300)), isFalse);
+ expect(range.allowsAll(VersionRange(min: v003, max: v140)), isFalse);
+ expect(range.allowsAll(VersionRange(min: v080)), isFalse);
+ expect(range.allowsAll(VersionRange(max: v140)), isFalse);
+ expect(range.allowsAll(range), isTrue);
+ });
+
+ test("allows a bordering range that's not more inclusive", () {
+ var exclusive = VersionRange(min: v010, max: v250);
+ var inclusive = VersionRange(
+ min: v010, includeMin: true, max: v250, includeMax: true);
+ expect(inclusive.allowsAll(exclusive), isTrue);
+ expect(inclusive.allowsAll(inclusive), isTrue);
+ expect(exclusive.allowsAll(inclusive), isFalse);
+ expect(exclusive.allowsAll(exclusive), isTrue);
+ });
+
+ test('allows unions that are completely contained', () {
+ var range = VersionRange(min: v114, max: v200);
+ expect(range.allowsAll(VersionRange(min: v123, max: v124).union(v140)),
+ isTrue);
+ expect(range.allowsAll(VersionRange(min: v010, max: v124).union(v140)),
+ isFalse);
+ expect(range.allowsAll(VersionRange(min: v123, max: v234).union(v140)),
+ isFalse);
+ });
+
+ group('pre-release versions', () {
+ test('of inclusive min are excluded', () {
+ var range = VersionRange(min: v123, includeMin: true);
+
+ expect(range.allowsAll(VersionConstraint.parse('>1.2.4-dev')), isTrue);
+ expect(range.allowsAll(VersionConstraint.parse('>1.2.3-dev')), isFalse);
+ });
+
+ test('of non-pre-release max are excluded', () {
+ var range = VersionRange(max: v234);
+
+ expect(range.allowsAll(VersionConstraint.parse('<2.3.3')), isTrue);
+ expect(range.allowsAll(VersionConstraint.parse('<2.3.4-dev')), isFalse);
+ });
+
+ test('of non-pre-release max are included with includeMaxPreRelease', () {
+ expect(
+ includeMaxPreReleaseRange
+ .allowsAll(VersionConstraint.parse('<2.0.0-dev')),
+ isTrue);
+ });
+
+ test(
+ 'of non-pre-release max are included if min is a pre-release of the '
+ 'same version', () {
+ var range = VersionRange(min: Version.parse('2.3.4-dev.0'), max: v234);
+
+ expect(
+ range.allowsAll(
+ VersionConstraint.parse('>2.3.4-dev.0 <2.3.4-dev.1')),
+ isTrue);
+ });
+
+ test('of pre-release max are included', () {
+ var range = VersionRange(max: Version.parse('2.3.4-dev.2'));
+
+ expect(
+ range.allowsAll(VersionConstraint.parse('<2.3.4-dev.1')), isTrue);
+ expect(
+ range.allowsAll(VersionConstraint.parse('<2.3.4-dev.2')), isTrue);
+ expect(
+ range.allowsAll(VersionConstraint.parse('<=2.3.4-dev.2')), isFalse);
+ expect(
+ range.allowsAll(VersionConstraint.parse('<2.3.4-dev.3')), isFalse);
+ });
+ });
+ });
+
+ group('allowsAny()', () {
+ test('disallows an empty constraint', () {
+ expect(
+ VersionRange(min: v123, max: v250).allowsAny(VersionConstraint.empty),
+ isFalse);
+ });
+
+ test('allows allowed versions', () {
+ var range = VersionRange(min: v123, max: v250, includeMax: true);
+ expect(range.allowsAny(v123), isFalse);
+ expect(range.allowsAny(v124), isTrue);
+ expect(range.allowsAny(v250), isTrue);
+ expect(range.allowsAny(v300), isFalse);
+ });
+
+ test('with no min', () {
+ var range = VersionRange(max: v200);
+ expect(range.allowsAny(VersionRange(min: v140, max: v300)), isTrue);
+ expect(range.allowsAny(VersionRange(min: v234, max: v300)), isFalse);
+ expect(range.allowsAny(VersionRange(min: v140)), isTrue);
+ expect(range.allowsAny(VersionRange(min: v234)), isFalse);
+ expect(range.allowsAny(range), isTrue);
+ });
+
+ test('with no max', () {
+ var range = VersionRange(min: v072);
+ expect(range.allowsAny(VersionRange(min: v003, max: v140)), isTrue);
+ expect(range.allowsAny(VersionRange(min: v003, max: v010)), isFalse);
+ expect(range.allowsAny(VersionRange(max: v080)), isTrue);
+ expect(range.allowsAny(VersionRange(max: v003)), isFalse);
+ expect(range.allowsAny(range), isTrue);
+ });
+
+ test('with a min and max', () {
+ var range = VersionRange(min: v072, max: v200);
+ expect(range.allowsAny(VersionRange(min: v003, max: v140)), isTrue);
+ expect(range.allowsAny(VersionRange(min: v140, max: v300)), isTrue);
+ expect(range.allowsAny(VersionRange(min: v003, max: v010)), isFalse);
+ expect(range.allowsAny(VersionRange(min: v234, max: v300)), isFalse);
+ expect(range.allowsAny(VersionRange(max: v010)), isFalse);
+ expect(range.allowsAny(VersionRange(min: v234)), isFalse);
+ expect(range.allowsAny(range), isTrue);
+ });
+
+ test('allows a bordering range when both are inclusive', () {
+ expect(
+ VersionRange(max: v250).allowsAny(VersionRange(min: v250)), isFalse);
+
+ expect(
+ VersionRange(max: v250, includeMax: true)
+ .allowsAny(VersionRange(min: v250)),
+ isFalse);
+
+ expect(
+ VersionRange(max: v250)
+ .allowsAny(VersionRange(min: v250, includeMin: true)),
+ isFalse);
+
+ expect(
+ VersionRange(max: v250, includeMax: true)
+ .allowsAny(VersionRange(min: v250, includeMin: true)),
+ isTrue);
+
+ expect(
+ VersionRange(min: v250).allowsAny(VersionRange(max: v250)), isFalse);
+
+ expect(
+ VersionRange(min: v250, includeMin: true)
+ .allowsAny(VersionRange(max: v250)),
+ isFalse);
+
+ expect(
+ VersionRange(min: v250)
+ .allowsAny(VersionRange(max: v250, includeMax: true)),
+ isFalse);
+
+ expect(
+ VersionRange(min: v250, includeMin: true)
+ .allowsAny(VersionRange(max: v250, includeMax: true)),
+ isTrue);
+ });
+
+ test('allows unions that are partially contained', () {
+ var range = VersionRange(min: v114, max: v200);
+ expect(range.allowsAny(VersionRange(min: v010, max: v080).union(v140)),
+ isTrue);
+ expect(range.allowsAny(VersionRange(min: v123, max: v234).union(v300)),
+ isTrue);
+ expect(range.allowsAny(VersionRange(min: v234, max: v300).union(v010)),
+ isFalse);
+ });
+
+ group('pre-release versions', () {
+ test('of inclusive min are excluded', () {
+ var range = VersionRange(min: v123, includeMin: true);
+
+ expect(range.allowsAny(VersionConstraint.parse('<1.2.4-dev')), isTrue);
+ expect(range.allowsAny(VersionConstraint.parse('<1.2.3-dev')), isFalse);
+ });
+
+ test('of non-pre-release max are excluded', () {
+ var range = VersionRange(max: v234);
+
+ expect(range.allowsAny(VersionConstraint.parse('>2.3.3')), isTrue);
+ expect(range.allowsAny(VersionConstraint.parse('>2.3.4-dev')), isFalse);
+ });
+
+ test('of non-pre-release max are included with includeMaxPreRelease', () {
+ expect(
+ includeMaxPreReleaseRange
+ .allowsAny(VersionConstraint.parse('>2.0.0-dev')),
+ isTrue);
+ });
+
+ test(
+ 'of non-pre-release max are included if min is a pre-release of the '
+ 'same version', () {
+ var range = VersionRange(min: Version.parse('2.3.4-dev.0'), max: v234);
+
+ expect(
+ range.allowsAny(VersionConstraint.parse('>2.3.4-dev.1')), isTrue);
+ expect(range.allowsAny(VersionConstraint.parse('>2.3.4')), isFalse);
+
+ expect(
+ range.allowsAny(VersionConstraint.parse('<2.3.4-dev.1')), isTrue);
+ expect(range.allowsAny(VersionConstraint.parse('<2.3.4-dev')), isFalse);
+ });
+
+ test('of pre-release max are included', () {
+ var range = VersionConstraint.parse('<2.3.4-dev.2');
+
+ expect(
+ range.allowsAny(VersionConstraint.parse('>2.3.4-dev.1')), isTrue);
+ expect(
+ range.allowsAny(VersionConstraint.parse('>2.3.4-dev.2')), isFalse);
+ expect(
+ range.allowsAny(VersionConstraint.parse('>2.3.4-dev.3')), isFalse);
+ });
+ });
+ });
+
+ group('intersect()', () {
+ test('two overlapping ranges', () {
+ expect(
+ VersionRange(min: v123, max: v250)
+ .intersect(VersionRange(min: v200, max: v300)),
+ equals(VersionRange(min: v200, max: v250)));
+ });
+
+ test('a non-overlapping range allows no versions', () {
+ var a = VersionRange(min: v114, max: v124);
+ var b = VersionRange(min: v200, max: v250);
+ expect(a.intersect(b).isEmpty, isTrue);
+ });
+
+ test('adjacent ranges allow no versions if exclusive', () {
+ var a = VersionRange(min: v114, max: v124);
+ var b = VersionRange(min: v124, max: v200);
+ expect(a.intersect(b).isEmpty, isTrue);
+ });
+
+ test('adjacent ranges allow version if inclusive', () {
+ var a = VersionRange(min: v114, max: v124, includeMax: true);
+ var b = VersionRange(min: v124, max: v200, includeMin: true);
+ expect(a.intersect(b), equals(v124));
+ });
+
+ test('with an open range', () {
+ var open = VersionRange();
+ var a = VersionRange(min: v114, max: v124);
+ expect(open.intersect(open), equals(open));
+ expect(a.intersect(open), equals(a));
+ });
+
+ test('returns the version if the range allows it', () {
+ expect(VersionRange(min: v114, max: v124).intersect(v123), equals(v123));
+ expect(
+ VersionRange(min: v123, max: v124).intersect(v114).isEmpty, isTrue);
+ });
+
+ test('with a range with a pre-release min, returns an empty constraint',
+ () {
+ expect(
+ VersionRange(max: v200)
+ .intersect(VersionConstraint.parse('>=2.0.0-dev')),
+ equals(VersionConstraint.empty));
+ });
+
+ test('with a range with a pre-release max, returns the original', () {
+ expect(
+ VersionRange(max: v200)
+ .intersect(VersionConstraint.parse('<2.0.0-dev')),
+ equals(VersionRange(max: v200)));
+ });
+
+ group('with includeMaxPreRelease', () {
+ test('preserves includeMaxPreRelease if the max version is included', () {
+ expect(
+ includeMaxPreReleaseRange
+ .intersect(VersionConstraint.parse('<1.0.0')),
+ equals(VersionConstraint.parse('<1.0.0')));
+ expect(
+ includeMaxPreReleaseRange
+ .intersect(VersionConstraint.parse('<2.0.0')),
+ equals(VersionConstraint.parse('<2.0.0')));
+ expect(includeMaxPreReleaseRange.intersect(includeMaxPreReleaseRange),
+ equals(includeMaxPreReleaseRange));
+ expect(
+ includeMaxPreReleaseRange
+ .intersect(VersionConstraint.parse('<3.0.0')),
+ equals(includeMaxPreReleaseRange));
+ expect(
+ includeMaxPreReleaseRange
+ .intersect(VersionConstraint.parse('>1.1.4')),
+ equals(VersionRange(
+ min: v114, max: v200, alwaysIncludeMaxPreRelease: true)));
+ });
+
+ test(
+ 'and a range with a pre-release min, returns '
+ 'an intersection', () {
+ expect(
+ includeMaxPreReleaseRange
+ .intersect(VersionConstraint.parse('>=2.0.0-dev')),
+ equals(VersionConstraint.parse('>=2.0.0-dev <2.0.0')));
+ });
+
+ test(
+ 'and a range with a pre-release max, returns '
+ 'the narrower constraint', () {
+ expect(
+ includeMaxPreReleaseRange
+ .intersect(VersionConstraint.parse('<2.0.0-dev')),
+ equals(VersionConstraint.parse('<2.0.0-dev')));
+ });
+ });
+ });
+
+ group('union()', () {
+ test('with a version returns the range if it contains the version', () {
+ var range = VersionRange(min: v114, max: v124);
+ expect(range.union(v123), equals(range));
+ });
+
+ test('with a version on the edge of the range, expands the range', () {
+ expect(
+ VersionRange(min: v114, max: v124, alwaysIncludeMaxPreRelease: true)
+ .union(v124),
+ equals(VersionRange(min: v114, max: v124, includeMax: true)));
+ expect(VersionRange(min: v114, max: v124).union(v114),
+ equals(VersionRange(min: v114, max: v124, includeMin: true)));
+ });
+
+ test(
+ 'with a version allows both the range and the version if the range '
+ "doesn't contain the version", () {
+ var result = VersionRange(min: v003, max: v114).union(v124);
+ expect(result, allows(v010));
+ expect(result, doesNotAllow(v123));
+ expect(result, allows(v124));
+ });
+
+ test('returns a VersionUnion for a disjoint range', () {
+ var result = VersionRange(min: v003, max: v114)
+ .union(VersionRange(min: v130, max: v200));
+ expect(result, allows(v080));
+ expect(result, doesNotAllow(v123));
+ expect(result, allows(v140));
+ });
+
+ test('returns a VersionUnion for a disjoint range with infinite end', () {
+ void isVersionUnion(VersionConstraint constraint) {
+ expect(constraint, allows(v080));
+ expect(constraint, doesNotAllow(v123));
+ expect(constraint, allows(v140));
+ }
+
+ for (final includeAMin in [true, false]) {
+ for (final includeAMax in [true, false]) {
+ for (final includeBMin in [true, false]) {
+ for (final includeBMax in [true, false]) {
+ final a = VersionRange(
+ min: v130, includeMin: includeAMin, includeMax: includeAMax);
+ final b = VersionRange(
+ max: v114, includeMin: includeBMin, includeMax: includeBMax);
+ isVersionUnion(a.union(b));
+ isVersionUnion(b.union(a));
+ }
+ }
+ }
+ }
+ });
+
+ test('considers open ranges disjoint', () {
+ var result = VersionRange(min: v003, max: v114)
+ .union(VersionRange(min: v114, max: v200));
+ expect(result, allows(v080));
+ expect(result, doesNotAllow(v114));
+ expect(result, allows(v140));
+
+ result = VersionRange(min: v114, max: v200)
+ .union(VersionRange(min: v003, max: v114));
+ expect(result, allows(v080));
+ expect(result, doesNotAllow(v114));
+ expect(result, allows(v140));
+ });
+
+ test('returns a merged range for an overlapping range', () {
+ var result = VersionRange(min: v003, max: v114)
+ .union(VersionRange(min: v080, max: v200));
+ expect(result, equals(VersionRange(min: v003, max: v200)));
+ });
+
+ test('considers closed ranges overlapping', () {
+ var result = VersionRange(min: v003, max: v114, includeMax: true)
+ .union(VersionRange(min: v114, max: v200));
+ expect(result, equals(VersionRange(min: v003, max: v200)));
+
+ result =
+ VersionRange(min: v003, max: v114, alwaysIncludeMaxPreRelease: true)
+ .union(VersionRange(min: v114, max: v200, includeMin: true));
+ expect(result, equals(VersionRange(min: v003, max: v200)));
+
+ result = VersionRange(min: v114, max: v200)
+ .union(VersionRange(min: v003, max: v114, includeMax: true));
+ expect(result, equals(VersionRange(min: v003, max: v200)));
+
+ result = VersionRange(min: v114, max: v200, includeMin: true).union(
+ VersionRange(min: v003, max: v114, alwaysIncludeMaxPreRelease: true));
+ expect(result, equals(VersionRange(min: v003, max: v200)));
+ });
+
+ test('includes edges if either range does', () {
+ var result = VersionRange(min: v003, max: v114, includeMin: true)
+ .union(VersionRange(min: v003, max: v114, includeMax: true));
+ expect(
+ result,
+ equals(VersionRange(
+ min: v003, max: v114, includeMin: true, includeMax: true)));
+ });
+
+ test('with a range with a pre-release min, returns a constraint with a gap',
+ () {
+ var result =
+ VersionRange(max: v200).union(VersionConstraint.parse('>=2.0.0-dev'));
+ expect(result, allows(v140));
+ expect(result, doesNotAllow(Version.parse('2.0.0-alpha')));
+ expect(result, allows(Version.parse('2.0.0-dev')));
+ expect(result, allows(Version.parse('2.0.0-dev.1')));
+ expect(result, allows(Version.parse('2.0.0')));
+ });
+
+ test('with a range with a pre-release max, returns the larger constraint',
+ () {
+ expect(
+ VersionRange(max: v200).union(VersionConstraint.parse('<2.0.0-dev')),
+ equals(VersionConstraint.parse('<2.0.0-dev')));
+ });
+
+ group('with includeMaxPreRelease', () {
+ test('adds includeMaxPreRelease if the max version is included', () {
+ expect(
+ includeMaxPreReleaseRange.union(VersionConstraint.parse('<1.0.0')),
+ equals(includeMaxPreReleaseRange));
+ expect(includeMaxPreReleaseRange.union(includeMaxPreReleaseRange),
+ equals(includeMaxPreReleaseRange));
+ expect(
+ includeMaxPreReleaseRange.union(VersionConstraint.parse('<2.0.0')),
+ equals(includeMaxPreReleaseRange));
+ expect(
+ includeMaxPreReleaseRange.union(VersionConstraint.parse('<3.0.0')),
+ equals(VersionConstraint.parse('<3.0.0')));
+ });
+
+ test('and a range with a pre-release min, returns any', () {
+ expect(
+ includeMaxPreReleaseRange
+ .union(VersionConstraint.parse('>=2.0.0-dev')),
+ equals(VersionConstraint.any));
+ });
+
+ test('and a range with a pre-release max, returns the original', () {
+ expect(
+ includeMaxPreReleaseRange
+ .union(VersionConstraint.parse('<2.0.0-dev')),
+ equals(includeMaxPreReleaseRange));
+ });
+ });
+ });
+
+ group('difference()', () {
+ test('with an empty range returns the original range', () {
+ expect(
+ VersionRange(min: v003, max: v114)
+ .difference(VersionConstraint.empty),
+ equals(VersionRange(min: v003, max: v114)));
+ });
+
+ test('with a version outside the range returns the original range', () {
+ expect(VersionRange(min: v003, max: v114).difference(v200),
+ equals(VersionRange(min: v003, max: v114)));
+ });
+
+ test('with a version in the range splits the range', () {
+ expect(
+ VersionRange(min: v003, max: v114).difference(v072),
+ equals(VersionConstraint.unionOf([
+ VersionRange(
+ min: v003, max: v072, alwaysIncludeMaxPreRelease: true),
+ VersionRange(min: v072, max: v114)
+ ])));
+ });
+
+ test('with the max version makes the max exclusive', () {
+ expect(
+ VersionRange(min: v003, max: v114, includeMax: true).difference(v114),
+ equals(VersionRange(
+ min: v003, max: v114, alwaysIncludeMaxPreRelease: true)));
+ });
+
+ test('with the min version makes the min exclusive', () {
+ expect(
+ VersionRange(min: v003, max: v114, includeMin: true).difference(v003),
+ equals(VersionRange(min: v003, max: v114)));
+ });
+
+ test('with a disjoint range returns the original', () {
+ expect(
+ VersionRange(min: v003, max: v114)
+ .difference(VersionRange(min: v123, max: v140)),
+ equals(VersionRange(min: v003, max: v114)));
+ });
+
+ test('with an adjacent range returns the original', () {
+ expect(
+ VersionRange(min: v003, max: v114, includeMax: true)
+ .difference(VersionRange(min: v114, max: v140)),
+ equals(VersionRange(min: v003, max: v114, includeMax: true)));
+ });
+
+ test('with a range at the beginning cuts off the beginning of the range',
+ () {
+ expect(
+ VersionRange(min: v080, max: v130)
+ .difference(VersionRange(min: v010, max: v114)),
+ equals(VersionConstraint.parse('>=1.1.4-0 <1.3.0')));
+ expect(
+ VersionRange(min: v080, max: v130)
+ .difference(VersionRange(max: v114)),
+ equals(VersionConstraint.parse('>=1.1.4-0 <1.3.0')));
+ expect(
+ VersionRange(min: v080, max: v130)
+ .difference(VersionRange(min: v010, max: v114, includeMax: true)),
+ equals(VersionRange(min: v114, max: v130)));
+ expect(
+ VersionRange(min: v080, max: v130, includeMin: true)
+ .difference(VersionRange(min: v010, max: v080, includeMax: true)),
+ equals(VersionRange(min: v080, max: v130)));
+ expect(
+ VersionRange(min: v080, max: v130, includeMax: true)
+ .difference(VersionRange(min: v080, max: v130)),
+ equals(VersionConstraint.parse('>=1.3.0-0 <=1.3.0')));
+ });
+
+ test('with a range at the end cuts off the end of the range', () {
+ expect(
+ VersionRange(min: v080, max: v130)
+ .difference(VersionRange(min: v114, max: v140)),
+ equals(VersionRange(min: v080, max: v114, includeMax: true)));
+ expect(
+ VersionRange(min: v080, max: v130)
+ .difference(VersionRange(min: v114)),
+ equals(VersionRange(min: v080, max: v114, includeMax: true)));
+ expect(
+ VersionRange(min: v080, max: v130)
+ .difference(VersionRange(min: v114, max: v140, includeMin: true)),
+ equals(VersionRange(
+ min: v080, max: v114, alwaysIncludeMaxPreRelease: true)));
+ expect(
+ VersionRange(min: v080, max: v130, includeMax: true)
+ .difference(VersionRange(min: v130, max: v140, includeMin: true)),
+ equals(VersionRange(
+ min: v080, max: v130, alwaysIncludeMaxPreRelease: true)));
+ expect(
+ VersionRange(min: v080, max: v130, includeMin: true)
+ .difference(VersionRange(min: v080, max: v130)),
+ equals(v080));
+ });
+
+ test('with a range in the middle cuts the range in half', () {
+ expect(
+ VersionRange(min: v003, max: v130)
+ .difference(VersionRange(min: v072, max: v114)),
+ equals(VersionConstraint.unionOf([
+ VersionRange(min: v003, max: v072, includeMax: true),
+ VersionConstraint.parse('>=1.1.4-0 <1.3.0')
+ ])));
+ });
+
+ test('with a totally covering range returns empty', () {
+ expect(
+ VersionRange(min: v114, max: v200)
+ .difference(VersionRange(min: v072, max: v300)),
+ isEmpty);
+ expect(
+ VersionRange(min: v003, max: v114)
+ .difference(VersionRange(min: v003, max: v114)),
+ isEmpty);
+ expect(
+ VersionRange(min: v003, max: v114, includeMin: true, includeMax: true)
+ .difference(VersionRange(
+ min: v003, max: v114, includeMin: true, includeMax: true)),
+ isEmpty);
+ });
+
+ test(
+ "with a version union that doesn't cover the range, returns the "
+ 'original', () {
+ expect(
+ VersionRange(min: v114, max: v140)
+ .difference(VersionConstraint.unionOf([v010, v200])),
+ equals(VersionRange(min: v114, max: v140)));
+ });
+
+ test('with a version union that intersects the ends, chops them off', () {
+ expect(
+ VersionRange(min: v114, max: v140).difference(
+ VersionConstraint.unionOf([
+ VersionRange(min: v080, max: v123),
+ VersionRange(min: v130, max: v200)
+ ])),
+ equals(VersionConstraint.parse('>=1.2.3-0 <=1.3.0')));
+ });
+
+ test('with a version union that intersects the middle, chops it up', () {
+ expect(
+ VersionRange(min: v114, max: v140)
+ .difference(VersionConstraint.unionOf([v123, v124, v130])),
+ equals(VersionConstraint.unionOf([
+ VersionRange(
+ min: v114, max: v123, alwaysIncludeMaxPreRelease: true),
+ VersionRange(
+ min: v123, max: v124, alwaysIncludeMaxPreRelease: true),
+ VersionRange(
+ min: v124, max: v130, alwaysIncludeMaxPreRelease: true),
+ VersionRange(min: v130, max: v140)
+ ])));
+ });
+
+ test('with a version union that covers the whole range, returns empty', () {
+ expect(
+ VersionRange(min: v114, max: v140).difference(
+ VersionConstraint.unionOf([v003, VersionRange(min: v010)])),
+ equals(VersionConstraint.empty));
+ });
+
+ test('with a range with a pre-release min, returns the original', () {
+ expect(
+ VersionRange(max: v200)
+ .difference(VersionConstraint.parse('>=2.0.0-dev')),
+ equals(VersionRange(max: v200)));
+ });
+
+ test('with a range with a pre-release max, returns null', () {
+ expect(
+ VersionRange(max: v200)
+ .difference(VersionConstraint.parse('<2.0.0-dev')),
+ equals(VersionConstraint.empty));
+ });
+
+ group('with includeMaxPreRelease', () {
+ group('for the minuend', () {
+ test('preserves includeMaxPreRelease if the max version is included',
+ () {
+ expect(
+ includeMaxPreReleaseRange
+ .difference(VersionConstraint.parse('<1.0.0')),
+ equals(VersionRange(
+ min: Version.parse('1.0.0-0'),
+ max: v200,
+ includeMin: true,
+ alwaysIncludeMaxPreRelease: true)));
+ expect(
+ includeMaxPreReleaseRange
+ .difference(VersionConstraint.parse('<2.0.0')),
+ equals(VersionRange(
+ min: v200.firstPreRelease,
+ max: v200,
+ includeMin: true,
+ alwaysIncludeMaxPreRelease: true)));
+ expect(
+ includeMaxPreReleaseRange.difference(includeMaxPreReleaseRange),
+ equals(VersionConstraint.empty));
+ expect(
+ includeMaxPreReleaseRange
+ .difference(VersionConstraint.parse('<3.0.0')),
+ equals(VersionConstraint.empty));
+ });
+
+ test('with a range with a pre-release min, adjusts the max', () {
+ expect(
+ includeMaxPreReleaseRange
+ .difference(VersionConstraint.parse('>=2.0.0-dev')),
+ equals(VersionConstraint.parse('<2.0.0-dev')));
+ });
+
+ test('with a range with a pre-release max, adjusts the min', () {
+ expect(
+ includeMaxPreReleaseRange
+ .difference(VersionConstraint.parse('<2.0.0-dev')),
+ equals(VersionConstraint.parse('>=2.0.0-dev <2.0.0')));
+ });
+ });
+
+ group('for the subtrahend', () {
+ group("doesn't create a pre-release minimum", () {
+ test('when cutting off the bottom', () {
+ expect(
+ VersionConstraint.parse('<3.0.0')
+ .difference(includeMaxPreReleaseRange),
+ equals(VersionRange(min: v200, max: v300, includeMin: true)));
+ });
+
+ test('with splitting down the middle', () {
+ expect(
+ VersionConstraint.parse('<4.0.0').difference(VersionRange(
+ min: v200,
+ max: v300,
+ includeMin: true,
+ alwaysIncludeMaxPreRelease: true)),
+ equals(VersionConstraint.unionOf([
+ VersionRange(max: v200, alwaysIncludeMaxPreRelease: true),
+ VersionConstraint.parse('>=3.0.0 <4.0.0')
+ ])));
+ });
+
+ test('can leave a single version', () {
+ expect(
+ VersionConstraint.parse('<=2.0.0')
+ .difference(includeMaxPreReleaseRange),
+ equals(v200));
+ });
+ });
+ });
+ });
+ });
+
+ test('isEmpty', () {
+ expect(VersionRange().isEmpty, isFalse);
+ expect(VersionRange(min: v123, max: v124).isEmpty, isFalse);
+ });
+
+ group('compareTo()', () {
+ test('orders by minimum first', () {
+ _expectComparesSmaller(VersionRange(min: v003, max: v080),
+ VersionRange(min: v010, max: v072));
+ _expectComparesSmaller(VersionRange(min: v003, max: v080),
+ VersionRange(min: v010, max: v080));
+ _expectComparesSmaller(VersionRange(min: v003, max: v080),
+ VersionRange(min: v010, max: v114));
+ });
+
+ test('orders by maximum second', () {
+ _expectComparesSmaller(VersionRange(min: v003, max: v010),
+ VersionRange(min: v003, max: v072));
+ });
+
+ test('includeMin comes before !includeMin', () {
+ _expectComparesSmaller(
+ VersionRange(min: v003, max: v080, includeMin: true),
+ VersionRange(min: v003, max: v080));
+ });
+
+ test('includeMax comes after !includeMax', () {
+ _expectComparesSmaller(VersionRange(min: v003, max: v080),
+ VersionRange(min: v003, max: v080, includeMax: true));
+ });
+
+ test('includeMaxPreRelease comes after !includeMaxPreRelease', () {
+ _expectComparesSmaller(
+ VersionRange(max: v200), includeMaxPreReleaseRange);
+ });
+
+ test('no minimum comes before small minimum', () {
+ _expectComparesSmaller(
+ VersionRange(max: v010), VersionRange(min: v003, max: v010));
+ _expectComparesSmaller(VersionRange(max: v010, includeMin: true),
+ VersionRange(min: v003, max: v010));
+ });
+
+ test('no maximium comes after large maximum', () {
+ _expectComparesSmaller(
+ VersionRange(min: v003, max: v300), VersionRange(min: v003));
+ _expectComparesSmaller(VersionRange(min: v003, max: v300),
+ VersionRange(min: v003, includeMax: true));
+ });
+ });
+}
+
+void _expectComparesSmaller(VersionRange smaller, VersionRange larger) {
+ expect(smaller.compareTo(larger), lessThan(0),
+ reason: 'expected $smaller to sort below $larger');
+ expect(larger.compareTo(smaller), greaterThan(0),
+ reason: 'expected $larger to sort above $smaller');
+}
diff --git a/pkgs/pub_semver/test/version_test.dart b/pkgs/pub_semver/test/version_test.dart
new file mode 100644
index 0000000..d7f1197
--- /dev/null
+++ b/pkgs/pub_semver/test/version_test.dart
@@ -0,0 +1,411 @@
+// Copyright (c) 2014, 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:pub_semver/pub_semver.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ test('none', () {
+ expect(Version.none.toString(), equals('0.0.0'));
+ });
+
+ test('prioritize()', () {
+ // A correctly sorted list of versions in order of increasing priority.
+ var versions = [
+ '1.0.0-alpha',
+ '2.0.0-alpha',
+ '1.0.0',
+ '1.0.0+build',
+ '1.0.1',
+ '1.1.0',
+ '2.0.0'
+ ];
+
+ // Ensure that every pair of versions is prioritized in the order that it
+ // appears in the list.
+ for (var i = 0; i < versions.length; i++) {
+ for (var j = 0; j < versions.length; j++) {
+ var a = Version.parse(versions[i]);
+ var b = Version.parse(versions[j]);
+ expect(Version.prioritize(a, b), equals(i.compareTo(j)));
+ }
+ }
+ });
+
+ test('antiprioritize()', () {
+ // A correctly sorted list of versions in order of increasing antipriority.
+ var versions = [
+ '2.0.0-alpha',
+ '1.0.0-alpha',
+ '2.0.0',
+ '1.1.0',
+ '1.0.1',
+ '1.0.0+build',
+ '1.0.0'
+ ];
+
+ // Ensure that every pair of versions is prioritized in the order that it
+ // appears in the list.
+ for (var i = 0; i < versions.length; i++) {
+ for (var j = 0; j < versions.length; j++) {
+ var a = Version.parse(versions[i]);
+ var b = Version.parse(versions[j]);
+ expect(Version.antiprioritize(a, b), equals(i.compareTo(j)));
+ }
+ }
+ });
+
+ group('constructor', () {
+ test('throws on negative numbers', () {
+ expect(() => Version(-1, 1, 1), throwsArgumentError);
+ expect(() => Version(1, -1, 1), throwsArgumentError);
+ expect(() => Version(1, 1, -1), throwsArgumentError);
+ });
+ });
+
+ group('comparison', () {
+ // A correctly sorted list of versions.
+ var versions = [
+ '1.0.0-alpha',
+ '1.0.0-alpha.1',
+ '1.0.0-beta.2',
+ '1.0.0-beta.11',
+ '1.0.0-rc.1',
+ '1.0.0-rc.1+build.1',
+ '1.0.0',
+ '1.0.0+0.3.7',
+ '1.3.7+build',
+ '1.3.7+build.2.b8f12d7',
+ '1.3.7+build.11.e0f985a',
+ '2.0.0',
+ '2.1.0',
+ '2.2.0',
+ '2.11.0',
+ '2.11.1'
+ ];
+
+ test('compareTo()', () {
+ // Ensure that every pair of versions compares in the order that it
+ // appears in the list.
+ for (var i = 0; i < versions.length; i++) {
+ for (var j = 0; j < versions.length; j++) {
+ var a = Version.parse(versions[i]);
+ var b = Version.parse(versions[j]);
+ expect(a.compareTo(b), equals(i.compareTo(j)));
+ }
+ }
+ });
+
+ test('operators', () {
+ for (var i = 0; i < versions.length; i++) {
+ for (var j = 0; j < versions.length; j++) {
+ var a = Version.parse(versions[i]);
+ var b = Version.parse(versions[j]);
+ expect(a < b, equals(i < j));
+ expect(a > b, equals(i > j));
+ expect(a <= b, equals(i <= j));
+ expect(a >= b, equals(i >= j));
+ expect(a == b, equals(i == j));
+ expect(a != b, equals(i != j));
+ }
+ }
+ });
+
+ test('equality', () {
+ expect(Version.parse('01.2.3'), equals(Version.parse('1.2.3')));
+ expect(Version.parse('1.02.3'), equals(Version.parse('1.2.3')));
+ expect(Version.parse('1.2.03'), equals(Version.parse('1.2.3')));
+ expect(Version.parse('1.2.3-01'), equals(Version.parse('1.2.3-1')));
+ expect(Version.parse('1.2.3+01'), equals(Version.parse('1.2.3+1')));
+ });
+ });
+
+ test('allows()', () {
+ expect(v123, allows(v123));
+ expect(
+ v123,
+ doesNotAllow(
+ Version.parse('2.2.3'),
+ Version.parse('1.3.3'),
+ Version.parse('1.2.4'),
+ Version.parse('1.2.3-dev'),
+ Version.parse('1.2.3+build')));
+ });
+
+ test('allowsAll()', () {
+ expect(v123.allowsAll(v123), isTrue);
+ expect(v123.allowsAll(v003), isFalse);
+ expect(v123.allowsAll(VersionRange(min: v114, max: v124)), isFalse);
+ expect(v123.allowsAll(VersionConstraint.any), isFalse);
+ expect(v123.allowsAll(VersionConstraint.empty), isTrue);
+ });
+
+ test('allowsAny()', () {
+ expect(v123.allowsAny(v123), isTrue);
+ expect(v123.allowsAny(v003), isFalse);
+ expect(v123.allowsAny(VersionRange(min: v114, max: v124)), isTrue);
+ expect(v123.allowsAny(VersionConstraint.any), isTrue);
+ expect(v123.allowsAny(VersionConstraint.empty), isFalse);
+ });
+
+ test('intersect()', () {
+ // Intersecting the same version returns the version.
+ expect(v123.intersect(v123), equals(v123));
+
+ // Intersecting a different version allows no versions.
+ expect(v123.intersect(v114).isEmpty, isTrue);
+
+ // Intersecting a range returns the version if the range allows it.
+ expect(v123.intersect(VersionRange(min: v114, max: v124)), equals(v123));
+
+ // Intersecting a range allows no versions if the range doesn't allow it.
+ expect(v114.intersect(VersionRange(min: v123, max: v124)).isEmpty, isTrue);
+ });
+
+ group('union()', () {
+ test('with the same version returns the version', () {
+ expect(v123.union(v123), equals(v123));
+ });
+
+ test('with a different version returns a version that matches both', () {
+ var result = v123.union(v080);
+ expect(result, allows(v123));
+ expect(result, allows(v080));
+
+ // Nothing in between should match.
+ expect(result, doesNotAllow(v114));
+ });
+
+ test('with a range returns the range if it contains the version', () {
+ var range = VersionRange(min: v114, max: v124);
+ expect(v123.union(range), equals(range));
+ });
+
+ test('with a range with the version on the edge, expands the range', () {
+ expect(
+ v124.union(VersionRange(
+ min: v114, max: v124, alwaysIncludeMaxPreRelease: true)),
+ equals(VersionRange(min: v114, max: v124, includeMax: true)));
+ expect(
+ v124.firstPreRelease.union(VersionRange(min: v114, max: v124)),
+ equals(VersionRange(
+ min: v114, max: v124.firstPreRelease, includeMax: true)));
+ expect(v114.union(VersionRange(min: v114, max: v124)),
+ equals(VersionRange(min: v114, max: v124, includeMin: true)));
+ });
+
+ test(
+ 'with a range allows both the range and the version if the range '
+ "doesn't contain the version", () {
+ var result = v123.union(VersionRange(min: v003, max: v114));
+ expect(result, allows(v123));
+ expect(result, allows(v010));
+ });
+ });
+
+ group('difference()', () {
+ test('with the same version returns an empty constraint', () {
+ expect(v123.difference(v123), isEmpty);
+ });
+
+ test('with a different version returns the original version', () {
+ expect(v123.difference(v080), equals(v123));
+ });
+
+ test('returns an empty constraint with a range that contains the version',
+ () {
+ expect(v123.difference(VersionRange(min: v114, max: v124)), isEmpty);
+ });
+
+ test("returns the version constraint with a range that doesn't contain it",
+ () {
+ expect(v123.difference(VersionRange(min: v140, max: v300)), equals(v123));
+ });
+ });
+
+ test('isEmpty', () {
+ expect(v123.isEmpty, isFalse);
+ });
+
+ test('nextMajor', () {
+ expect(v123.nextMajor, equals(v200));
+ expect(v114.nextMajor, equals(v200));
+ expect(v200.nextMajor, equals(v300));
+
+ // Ignores pre-release if not on a major version.
+ expect(Version.parse('1.2.3-dev').nextMajor, equals(v200));
+
+ // Just removes it if on a major version.
+ expect(Version.parse('2.0.0-dev').nextMajor, equals(v200));
+
+ // Strips build suffix.
+ expect(Version.parse('1.2.3+patch').nextMajor, equals(v200));
+ });
+
+ test('nextMinor', () {
+ expect(v123.nextMinor, equals(v130));
+ expect(v130.nextMinor, equals(v140));
+
+ // Ignores pre-release if not on a minor version.
+ expect(Version.parse('1.2.3-dev').nextMinor, equals(v130));
+
+ // Just removes it if on a minor version.
+ expect(Version.parse('1.3.0-dev').nextMinor, equals(v130));
+
+ // Strips build suffix.
+ expect(Version.parse('1.2.3+patch').nextMinor, equals(v130));
+ });
+
+ test('nextPatch', () {
+ expect(v123.nextPatch, equals(v124));
+ expect(v200.nextPatch, equals(v201));
+
+ // Just removes pre-release version if present.
+ expect(Version.parse('1.2.4-dev').nextPatch, equals(v124));
+
+ // Strips build suffix.
+ expect(Version.parse('1.2.3+patch').nextPatch, equals(v124));
+ });
+
+ test('nextBreaking', () {
+ expect(v123.nextBreaking, equals(v200));
+ expect(v072.nextBreaking, equals(v080));
+ expect(v003.nextBreaking, equals(v010));
+
+ // Removes pre-release version if present.
+ expect(Version.parse('1.2.3-dev').nextBreaking, equals(v200));
+
+ // Strips build suffix.
+ expect(Version.parse('1.2.3+patch').nextBreaking, equals(v200));
+ });
+
+ test('parse()', () {
+ expect(Version.parse('0.0.0'), equals(Version(0, 0, 0)));
+ expect(Version.parse('12.34.56'), equals(Version(12, 34, 56)));
+
+ expect(Version.parse('1.2.3-alpha.1'),
+ equals(Version(1, 2, 3, pre: 'alpha.1')));
+ expect(Version.parse('1.2.3-x.7.z-92'),
+ equals(Version(1, 2, 3, pre: 'x.7.z-92')));
+
+ expect(Version.parse('1.2.3+build.1'),
+ equals(Version(1, 2, 3, build: 'build.1')));
+ expect(Version.parse('1.2.3+x.7.z-92'),
+ equals(Version(1, 2, 3, build: 'x.7.z-92')));
+
+ expect(Version.parse('1.0.0-rc-1+build-1'),
+ equals(Version(1, 0, 0, pre: 'rc-1', build: 'build-1')));
+
+ expect(() => Version.parse('1.0'), throwsFormatException);
+ expect(() => Version.parse('1a2b3'), throwsFormatException);
+ expect(() => Version.parse('1.2.3.4'), throwsFormatException);
+ expect(() => Version.parse('1234'), throwsFormatException);
+ expect(() => Version.parse('-2.3.4'), throwsFormatException);
+ expect(() => Version.parse('1.3-pre'), throwsFormatException);
+ expect(() => Version.parse('1.3+build'), throwsFormatException);
+ expect(() => Version.parse('1.3+bu?!3ild'), throwsFormatException);
+ });
+
+ group('toString()', () {
+ test('returns the version string', () {
+ expect(Version(0, 0, 0).toString(), equals('0.0.0'));
+ expect(Version(12, 34, 56).toString(), equals('12.34.56'));
+
+ expect(
+ Version(1, 2, 3, pre: 'alpha.1').toString(), equals('1.2.3-alpha.1'));
+ expect(Version(1, 2, 3, pre: 'x.7.z-92').toString(),
+ equals('1.2.3-x.7.z-92'));
+
+ expect(Version(1, 2, 3, build: 'build.1').toString(),
+ equals('1.2.3+build.1'));
+ expect(Version(1, 2, 3, pre: 'pre', build: 'bui').toString(),
+ equals('1.2.3-pre+bui'));
+ });
+
+ test('preserves leading zeroes', () {
+ expect(Version.parse('001.02.0003-01.dev+pre.002').toString(),
+ equals('001.02.0003-01.dev+pre.002'));
+ });
+ });
+
+ group('canonicalizedVersion', () {
+ test('returns version string', () {
+ expect(Version(0, 0, 0).canonicalizedVersion, equals('0.0.0'));
+ expect(Version(12, 34, 56).canonicalizedVersion, equals('12.34.56'));
+
+ expect(Version(1, 2, 3, pre: 'alpha.1').canonicalizedVersion,
+ equals('1.2.3-alpha.1'));
+ expect(Version(1, 2, 3, pre: 'x.7.z-92').canonicalizedVersion,
+ equals('1.2.3-x.7.z-92'));
+
+ expect(Version(1, 2, 3, build: 'build.1').canonicalizedVersion,
+ equals('1.2.3+build.1'));
+ expect(Version(1, 2, 3, pre: 'pre', build: 'bui').canonicalizedVersion,
+ equals('1.2.3-pre+bui'));
+ });
+
+ test('discards leading zeroes', () {
+ expect(Version.parse('001.02.0003-01.dev+pre.002').canonicalizedVersion,
+ equals('1.2.3-1.dev+pre.2'));
+ });
+
+ test('example from documentation', () {
+ final v = Version.parse('01.02.03-01.dev+pre.02');
+
+ assert(v.toString() == '01.02.03-01.dev+pre.02');
+ assert(v.canonicalizedVersion == '1.2.3-1.dev+pre.2');
+ assert(Version.parse(v.canonicalizedVersion) == v);
+ });
+ });
+
+ group('primary', () {
+ test('single', () {
+ expect(
+ _primary([
+ '1.2.3',
+ ]).toString(),
+ '1.2.3',
+ );
+ });
+
+ test('normal', () {
+ expect(
+ _primary([
+ '1.2.3',
+ '1.2.2',
+ ]).toString(),
+ '1.2.3',
+ );
+ });
+
+ test('all prerelease', () {
+ expect(
+ _primary([
+ '1.2.2-dev.1',
+ '1.2.2-dev.2',
+ ]).toString(),
+ '1.2.2-dev.2',
+ );
+ });
+
+ test('later prerelease', () {
+ expect(
+ _primary([
+ '1.2.3',
+ '1.2.3-dev',
+ ]).toString(),
+ '1.2.3',
+ );
+ });
+
+ test('empty', () {
+ expect(() => Version.primary([]), throwsStateError);
+ });
+ });
+}
+
+Version _primary(List<String> input) =>
+ Version.primary(input.map(Version.parse).toList());
diff --git a/pkgs/pub_semver/test/version_union_test.dart b/pkgs/pub_semver/test/version_union_test.dart
new file mode 100644
index 0000000..857f10e
--- /dev/null
+++ b/pkgs/pub_semver/test/version_union_test.dart
@@ -0,0 +1,482 @@
+// Copyright (c) 2015, 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:pub_semver/pub_semver.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ group('factory', () {
+ test('ignores empty constraints', () {
+ expect(
+ VersionConstraint.unionOf([
+ VersionConstraint.empty,
+ VersionConstraint.empty,
+ v123,
+ VersionConstraint.empty
+ ]),
+ equals(v123));
+
+ expect(
+ VersionConstraint.unionOf(
+ [VersionConstraint.empty, VersionConstraint.empty]),
+ isEmpty);
+ });
+
+ test('returns an empty constraint for an empty list', () {
+ expect(VersionConstraint.unionOf([]), isEmpty);
+ });
+
+ test('any constraints override everything', () {
+ expect(
+ VersionConstraint.unionOf([
+ v123,
+ VersionConstraint.any,
+ v200,
+ VersionRange(min: v234, max: v250)
+ ]),
+ equals(VersionConstraint.any));
+ });
+
+ test('flattens other unions', () {
+ expect(
+ VersionConstraint.unionOf([
+ v072,
+ VersionConstraint.unionOf([v123, v124]),
+ v250
+ ]),
+ equals(VersionConstraint.unionOf([v072, v123, v124, v250])));
+ });
+
+ test('returns a single merged range as-is', () {
+ expect(
+ VersionConstraint.unionOf([
+ VersionRange(min: v080, max: v140),
+ VersionRange(min: v123, max: v200)
+ ]),
+ equals(VersionRange(min: v080, max: v200)));
+ });
+ });
+
+ group('equality', () {
+ test("doesn't depend on original order", () {
+ expect(
+ VersionConstraint.unionOf([
+ v250,
+ VersionRange(min: v201, max: v234),
+ v124,
+ v072,
+ VersionRange(min: v080, max: v114),
+ v123
+ ]),
+ equals(VersionConstraint.unionOf([
+ v072,
+ VersionRange(min: v080, max: v114),
+ v123,
+ v124,
+ VersionRange(min: v201, max: v234),
+ v250
+ ])));
+ });
+
+ test('merges overlapping ranges', () {
+ expect(
+ VersionConstraint.unionOf([
+ VersionRange(min: v003, max: v072),
+ VersionRange(min: v010, max: v080),
+ VersionRange(min: v114, max: v124),
+ VersionRange(min: v123, max: v130)
+ ]),
+ equals(VersionConstraint.unionOf([
+ VersionRange(min: v003, max: v080),
+ VersionRange(min: v114, max: v130)
+ ])));
+ });
+
+ test('merges adjacent ranges', () {
+ expect(
+ VersionConstraint.unionOf([
+ VersionRange(min: v003, max: v072, includeMax: true),
+ VersionRange(min: v072, max: v080),
+ VersionRange(
+ min: v114, max: v124, alwaysIncludeMaxPreRelease: true),
+ VersionRange(min: v124, max: v130, includeMin: true),
+ VersionRange(min: v130.firstPreRelease, max: v200, includeMin: true)
+ ]),
+ equals(VersionConstraint.unionOf([
+ VersionRange(min: v003, max: v080),
+ VersionRange(min: v114, max: v200)
+ ])));
+ });
+
+ test("doesn't merge not-quite-adjacent ranges", () {
+ expect(
+ VersionConstraint.unionOf([
+ VersionRange(min: v114, max: v124),
+ VersionRange(min: v124, max: v130, includeMin: true)
+ ]),
+ isNot(equals(VersionRange(min: v114, max: v130))));
+
+ expect(
+ VersionConstraint.unionOf([
+ VersionRange(min: v003, max: v072),
+ VersionRange(min: v072, max: v080)
+ ]),
+ isNot(equals(VersionRange(min: v003, max: v080))));
+ });
+
+ test('merges version numbers into ranges', () {
+ expect(
+ VersionConstraint.unionOf([
+ VersionRange(min: v003, max: v072),
+ v010,
+ VersionRange(min: v114, max: v124),
+ v123
+ ]),
+ equals(VersionConstraint.unionOf([
+ VersionRange(min: v003, max: v072),
+ VersionRange(min: v114, max: v124)
+ ])));
+ });
+
+ test('merges adjacent version numbers into ranges', () {
+ expect(
+ VersionConstraint.unionOf([
+ VersionRange(
+ min: v003, max: v072, alwaysIncludeMaxPreRelease: true),
+ v072,
+ v114,
+ VersionRange(min: v114, max: v124),
+ v124.firstPreRelease
+ ]),
+ equals(VersionConstraint.unionOf([
+ VersionRange(min: v003, max: v072, includeMax: true),
+ VersionRange(
+ min: v114,
+ max: v124.firstPreRelease,
+ includeMin: true,
+ includeMax: true)
+ ])));
+ });
+
+ test("doesn't merge not-quite-adjacent version numbers into ranges", () {
+ expect(
+ VersionConstraint.unionOf([VersionRange(min: v003, max: v072), v072]),
+ isNot(equals(VersionRange(min: v003, max: v072, includeMax: true))));
+ });
+ });
+
+ test('isEmpty returns false', () {
+ expect(
+ VersionConstraint.unionOf([
+ VersionRange(min: v003, max: v080),
+ VersionRange(min: v123, max: v130),
+ ]),
+ isNot(isEmpty));
+ });
+
+ test('isAny returns false', () {
+ expect(
+ VersionConstraint.unionOf([
+ VersionRange(min: v003, max: v080),
+ VersionRange(min: v123, max: v130),
+ ]).isAny,
+ isFalse);
+ });
+
+ test('allows() allows anything the components allow', () {
+ var union = VersionConstraint.unionOf([
+ VersionRange(min: v003, max: v080),
+ VersionRange(min: v123, max: v130),
+ v200
+ ]);
+
+ expect(union, allows(v010));
+ expect(union, doesNotAllow(v080));
+ expect(union, allows(v124));
+ expect(union, doesNotAllow(v140));
+ expect(union, allows(v200));
+ });
+
+ group('allowsAll()', () {
+ test('for a version, returns true if any component allows the version', () {
+ var union = VersionConstraint.unionOf([
+ VersionRange(min: v003, max: v080),
+ VersionRange(min: v123, max: v130),
+ v200
+ ]);
+
+ expect(union.allowsAll(v010), isTrue);
+ expect(union.allowsAll(v080), isFalse);
+ expect(union.allowsAll(v124), isTrue);
+ expect(union.allowsAll(v140), isFalse);
+ expect(union.allowsAll(v200), isTrue);
+ });
+
+ test(
+ 'for a version range, returns true if any component allows the whole '
+ 'range', () {
+ var union = VersionConstraint.unionOf([
+ VersionRange(min: v003, max: v080),
+ VersionRange(min: v123, max: v130)
+ ]);
+
+ expect(union.allowsAll(VersionRange(min: v003, max: v080)), isTrue);
+ expect(union.allowsAll(VersionRange(min: v010, max: v072)), isTrue);
+ expect(union.allowsAll(VersionRange(min: v010, max: v124)), isFalse);
+ });
+
+ group('for a union,', () {
+ var union = VersionConstraint.unionOf([
+ VersionRange(min: v003, max: v080),
+ VersionRange(min: v123, max: v130)
+ ]);
+
+ test('returns true if every constraint matches a different constraint',
+ () {
+ expect(
+ union.allowsAll(VersionConstraint.unionOf([
+ VersionRange(min: v010, max: v072),
+ VersionRange(min: v124, max: v130)
+ ])),
+ isTrue);
+ });
+
+ test('returns true if every constraint matches the same constraint', () {
+ expect(
+ union.allowsAll(VersionConstraint.unionOf([
+ VersionRange(min: v003, max: v010),
+ VersionRange(min: v072, max: v080)
+ ])),
+ isTrue);
+ });
+
+ test("returns false if there's an unmatched constraint", () {
+ expect(
+ union.allowsAll(VersionConstraint.unionOf([
+ VersionRange(min: v010, max: v072),
+ VersionRange(min: v124, max: v130),
+ VersionRange(min: v140, max: v200)
+ ])),
+ isFalse);
+ });
+
+ test("returns false if a constraint isn't fully matched", () {
+ expect(
+ union.allowsAll(VersionConstraint.unionOf([
+ VersionRange(min: v010, max: v114),
+ VersionRange(min: v124, max: v130)
+ ])),
+ isFalse);
+ });
+ });
+ });
+
+ group('allowsAny()', () {
+ test('for a version, returns true if any component allows the version', () {
+ var union = VersionConstraint.unionOf([
+ VersionRange(min: v003, max: v080),
+ VersionRange(min: v123, max: v130),
+ v200
+ ]);
+
+ expect(union.allowsAny(v010), isTrue);
+ expect(union.allowsAny(v080), isFalse);
+ expect(union.allowsAny(v124), isTrue);
+ expect(union.allowsAny(v140), isFalse);
+ expect(union.allowsAny(v200), isTrue);
+ });
+
+ test(
+ 'for a version range, returns true if any component allows part of '
+ 'the range', () {
+ var union =
+ VersionConstraint.unionOf([VersionRange(min: v003, max: v080), v123]);
+
+ expect(union.allowsAny(VersionRange(min: v010, max: v114)), isTrue);
+ expect(union.allowsAny(VersionRange(min: v114, max: v124)), isTrue);
+ expect(union.allowsAny(VersionRange(min: v124, max: v130)), isFalse);
+ });
+
+ group('for a union,', () {
+ var union = VersionConstraint.unionOf([
+ VersionRange(min: v010, max: v080),
+ VersionRange(min: v123, max: v130)
+ ]);
+
+ test('returns true if any constraint matches', () {
+ expect(
+ union.allowsAny(VersionConstraint.unionOf(
+ [v072, VersionRange(min: v200, max: v300)])),
+ isTrue);
+
+ expect(
+ union.allowsAny(VersionConstraint.unionOf(
+ [v003, VersionRange(min: v124, max: v300)])),
+ isTrue);
+ });
+
+ test('returns false if no constraint matches', () {
+ expect(
+ union.allowsAny(VersionConstraint.unionOf([
+ v003,
+ VersionRange(min: v130, max: v140),
+ VersionRange(min: v140, max: v200)
+ ])),
+ isFalse);
+ });
+ });
+ });
+
+ group('intersect()', () {
+ test('with an overlapping version, returns that version', () {
+ expect(
+ VersionConstraint.unionOf([
+ VersionRange(min: v010, max: v080),
+ VersionRange(min: v123, max: v140)
+ ]).intersect(v072),
+ equals(v072));
+ });
+
+ test('with a non-overlapping version, returns an empty constraint', () {
+ expect(
+ VersionConstraint.unionOf([
+ VersionRange(min: v010, max: v080),
+ VersionRange(min: v123, max: v140)
+ ]).intersect(v300),
+ isEmpty);
+ });
+
+ test('with an overlapping range, returns that range', () {
+ var range = VersionRange(min: v072, max: v080);
+ expect(
+ VersionConstraint.unionOf([
+ VersionRange(min: v010, max: v080),
+ VersionRange(min: v123, max: v140)
+ ]).intersect(range),
+ equals(range));
+ });
+
+ test('with a non-overlapping range, returns an empty constraint', () {
+ expect(
+ VersionConstraint.unionOf([
+ VersionRange(min: v010, max: v080),
+ VersionRange(min: v123, max: v140)
+ ]).intersect(VersionRange(min: v080, max: v123)),
+ isEmpty);
+ });
+
+ test('with a parially-overlapping range, returns the overlapping parts',
+ () {
+ expect(
+ VersionConstraint.unionOf([
+ VersionRange(min: v010, max: v080),
+ VersionRange(min: v123, max: v140)
+ ]).intersect(VersionRange(min: v072, max: v130)),
+ equals(VersionConstraint.unionOf([
+ VersionRange(min: v072, max: v080),
+ VersionRange(min: v123, max: v130)
+ ])));
+ });
+
+ group('for a union,', () {
+ var union = VersionConstraint.unionOf([
+ VersionRange(min: v003, max: v080),
+ VersionRange(min: v123, max: v130)
+ ]);
+
+ test('returns the overlapping parts', () {
+ expect(
+ union.intersect(VersionConstraint.unionOf([
+ v010,
+ VersionRange(min: v072, max: v124),
+ VersionRange(min: v124, max: v130)
+ ])),
+ equals(VersionConstraint.unionOf([
+ v010,
+ VersionRange(min: v072, max: v080),
+ VersionRange(min: v123, max: v124),
+ VersionRange(min: v124, max: v130)
+ ])));
+ });
+
+ test("drops parts that don't match", () {
+ expect(
+ union.intersect(VersionConstraint.unionOf([
+ v003,
+ VersionRange(min: v072, max: v080),
+ VersionRange(min: v080, max: v123)
+ ])),
+ equals(VersionRange(min: v072, max: v080)));
+ });
+ });
+ });
+
+ group('difference()', () {
+ test("ignores ranges that don't intersect", () {
+ expect(
+ VersionConstraint.unionOf([
+ VersionRange(min: v072, max: v080),
+ VersionRange(min: v123, max: v130)
+ ]).difference(VersionConstraint.unionOf([
+ VersionRange(min: v003, max: v010),
+ VersionRange(min: v080, max: v123),
+ VersionRange(min: v140)
+ ])),
+ equals(VersionConstraint.unionOf([
+ VersionRange(min: v072, max: v080),
+ VersionRange(min: v123, max: v130)
+ ])));
+ });
+
+ test('removes overlapping portions', () {
+ expect(
+ VersionConstraint.unionOf([
+ VersionRange(min: v010, max: v080),
+ VersionRange(min: v123, max: v130)
+ ]).difference(VersionConstraint.unionOf(
+ [VersionRange(min: v003, max: v072), VersionRange(min: v124)])),
+ equals(VersionConstraint.unionOf([
+ VersionRange(
+ min: v072.firstPreRelease, max: v080, includeMin: true),
+ VersionRange(min: v123, max: v124, includeMax: true)
+ ])));
+ });
+
+ test('removes multiple portions from the same range', () {
+ expect(
+ VersionConstraint.unionOf([
+ VersionRange(min: v010, max: v114),
+ VersionRange(min: v130, max: v200)
+ ]).difference(VersionConstraint.unionOf([v072, v080])),
+ equals(VersionConstraint.unionOf([
+ VersionRange(
+ min: v010, max: v072, alwaysIncludeMaxPreRelease: true),
+ VersionRange(
+ min: v072, max: v080, alwaysIncludeMaxPreRelease: true),
+ VersionRange(min: v080, max: v114),
+ VersionRange(min: v130, max: v200)
+ ])));
+ });
+
+ test('removes the same range from multiple ranges', () {
+ expect(
+ VersionConstraint.unionOf([
+ VersionRange(min: v010, max: v072),
+ VersionRange(min: v080, max: v123),
+ VersionRange(min: v124, max: v130),
+ VersionRange(min: v200, max: v234),
+ VersionRange(min: v250, max: v300)
+ ]).difference(VersionRange(min: v114, max: v201)),
+ equals(VersionConstraint.unionOf([
+ VersionRange(min: v010, max: v072),
+ VersionRange(min: v080, max: v114, includeMax: true),
+ VersionRange(
+ min: v201.firstPreRelease, max: v234, includeMin: true),
+ VersionRange(min: v250, max: v300)
+ ])));
+ });
+ });
+}
diff --git a/pkgs/pubspec_parse/.gitignore b/pkgs/pubspec_parse/.gitignore
new file mode 100644
index 0000000..ec8eae3
--- /dev/null
+++ b/pkgs/pubspec_parse/.gitignore
@@ -0,0 +1,4 @@
+# Don’t commit the following directories created by pub.
+.dart_tool/
+.packages
+pubspec.lock
diff --git a/pkgs/pubspec_parse/CHANGELOG.md b/pkgs/pubspec_parse/CHANGELOG.md
new file mode 100644
index 0000000..5aeb498
--- /dev/null
+++ b/pkgs/pubspec_parse/CHANGELOG.md
@@ -0,0 +1,110 @@
+## 1.5.0
+
+- Added fields to `Pubspec`: `executables`, `resolution`, `workspace`.
+- Require Dart 3.6
+- Update dependencies.
+
+## 1.4.0
+
+- Require Dart 3.2
+- Seal the `Dependency` class.
+- Set `Pubspec.environment` to non-nullable.
+- Remove deprecated package_api_docs rule
+- Move to `dart-lang/tools` monorepo.
+
+## 1.3.0
+
+- Require Dart 3.0
+- Added support for `ignored_advisories` field.
+- Added structural equality for `Dependency` subclasses and `HostedDetails`.
+
+## 1.2.3
+
+- Added topics to `pubspec.yaml`.
+
+## 1.2.2
+
+- Require Dart SDK >= 2.18.0
+- Required `json_annotation: ^4.8.0`
+- Added support for `topics` field.
+
+## 1.2.1
+
+- Added support for `funding` field.
+
+## 1.2.0
+
+- Added support for `screenshots` field.
+- Update `HostedDetails` to reflect how `hosted` dependencies are parsed in
+ Dart 2.15:
+ - Add `HostedDetails.declaredName` as the (optional) `name` property in a
+ `hosted` block.
+ - `HostedDetails.name` now falls back to the name of the dependency if no
+ name is declared in the block.
+- Require Dart SDK >= 2.14.0
+
+## 1.1.0
+
+- Export `HostedDetails` publicly.
+
+## 1.0.0
+
+- Migrate to null-safety.
+- Pubspec: `author` and `authors` are both now deprecated.
+ See https://dart.dev/tools/pub/pubspec#authorauthors
+
+## 0.1.8
+
+- Allow the latest `package:pub_semver`.
+
+## 0.1.7
+
+- Allow `package:yaml` `v3.x`.
+
+## 0.1.6
+
+- Update SDK requirement to `>=2.7.0 <3.0.0`.
+- Allow `package:json_annotation` `v4.x`.
+
+## 0.1.5
+
+- Update SDK requirement to `>=2.2.0 <3.0.0`.
+- Support the latest `package:json_annotation`.
+
+## 0.1.4
+
+- Added `lenient` named argument to `Pubspec.fromJson` to ignore format and type errors.
+
+## 0.1.3
+
+- Added support for `flutter`, `issue_tracker`, `publish_to`, and `repository`
+ fields.
+
+## 0.1.2+3
+
+- Support the latest version of `package:json_annotation`.
+
+## 0.1.2+2
+
+- Support `package:json_annotation` v1.
+
+## 0.1.2+1
+
+- Support the Dart 2 stable release.
+
+## 0.1.2
+
+- Allow superfluous `version` keys with `git` and `path` dependencies.
+- Improve errors when unsupported keys are provided in dependencies.
+- Provide better errors with invalid `sdk` dependency values.
+- Support "scp-like syntax" for Git SSH URIs in the form
+ `[user@]host.xz:path/to/repo.git/`.
+
+## 0.1.1
+
+- Fixed name collision with error type in latest `package:json_annotation`.
+- Improved parsing of hosted dependencies and environment constraints.
+
+## 0.1.0
+
+- Initial release.
diff --git a/pkgs/pubspec_parse/LICENSE b/pkgs/pubspec_parse/LICENSE
new file mode 100644
index 0000000..4d1ad40
--- /dev/null
+++ b/pkgs/pubspec_parse/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2018, 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.
diff --git a/pkgs/pubspec_parse/README.md b/pkgs/pubspec_parse/README.md
new file mode 100644
index 0000000..1d04aa4
--- /dev/null
+++ b/pkgs/pubspec_parse/README.md
@@ -0,0 +1,12 @@
+[](https://github.com/dart-lang/tools/actions/workflows/pubspec_parse.yaml)
+[](https://pub.dev/packages/pubspec_parse)
+[](https://pub.dev/packages/pubspec_parse/publisher)
+
+## What's this?
+
+Supports parsing `pubspec.yaml` files with robust error reporting and support
+for most of the documented features.
+
+## More information
+
+Read more about the [pubspec format](https://dart.dev/tools/pub/pubspec).
diff --git a/pkgs/pubspec_parse/analysis_options.yaml b/pkgs/pubspec_parse/analysis_options.yaml
new file mode 100644
index 0000000..93eeebf
--- /dev/null
+++ b/pkgs/pubspec_parse/analysis_options.yaml
@@ -0,0 +1,30 @@
+# https://dart.dev/guides/language/analysis-options
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-casts: true
+ strict-inference: true
+
+linter:
+ rules:
+ - avoid_bool_literals_in_conditional_expressions
+ - avoid_classes_with_only_static_members
+ - avoid_private_typedef_functions
+ - avoid_redundant_argument_values
+ - avoid_returning_this
+ - avoid_unused_constructor_parameters
+ - avoid_void_async
+ - cancel_subscriptions
+ - cascade_invocations
+ - join_return_with_assignment
+ - literal_only_boolean_expressions
+ - missing_whitespace_between_adjacent_strings
+ - no_adjacent_strings_in_list
+ - no_runtimeType_toString
+ - prefer_const_declarations
+ - prefer_expression_function_bodies
+ - prefer_final_locals
+ - require_trailing_commas
+ - unnecessary_await_in_return
+ - use_string_buffers
diff --git a/pkgs/pubspec_parse/build.yaml b/pkgs/pubspec_parse/build.yaml
new file mode 100644
index 0000000..2003bc2
--- /dev/null
+++ b/pkgs/pubspec_parse/build.yaml
@@ -0,0 +1,25 @@
+# Read about `build.yaml` at https://pub.dev/packages/build_config
+# To update generated code, run `pub run build_runner build`
+targets:
+ $default:
+ builders:
+ json_serializable:
+ generate_for:
+ - lib/src/pubspec.dart
+ - lib/src/dependency.dart
+ options:
+ any_map: true
+ checked: true
+ create_to_json: false
+ field_rename: snake
+
+ # The end-user of a builder which applies "source_gen|combining_builder"
+ # may configure the builder to ignore specific lints for their project
+ source_gen|combining_builder:
+ options:
+ ignore_for_file:
+ - deprecated_member_use_from_same_package
+ - lines_longer_than_80_chars
+ - require_trailing_commas
+ # https://github.com/google/json_serializable.dart/issues/945
+ - unnecessary_cast
diff --git a/pkgs/pubspec_parse/dart_test.yaml b/pkgs/pubspec_parse/dart_test.yaml
new file mode 100644
index 0000000..1d7ac69
--- /dev/null
+++ b/pkgs/pubspec_parse/dart_test.yaml
@@ -0,0 +1,3 @@
+tags:
+ presubmit-only:
+ skip: "Should only be run during presubmit"
diff --git a/pkgs/pubspec_parse/lib/pubspec_parse.dart b/pkgs/pubspec_parse/lib/pubspec_parse.dart
new file mode 100644
index 0000000..b5c12e4
--- /dev/null
+++ b/pkgs/pubspec_parse/lib/pubspec_parse.dart
@@ -0,0 +1,14 @@
+// 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.
+
+export 'src/dependency.dart'
+ show
+ Dependency,
+ GitDependency,
+ HostedDependency,
+ HostedDetails,
+ PathDependency,
+ SdkDependency;
+export 'src/pubspec.dart' show Pubspec;
+export 'src/screenshot.dart' show Screenshot;
diff --git a/pkgs/pubspec_parse/lib/src/dependency.dart b/pkgs/pubspec_parse/lib/src/dependency.dart
new file mode 100644
index 0000000..24c65ea
--- /dev/null
+++ b/pkgs/pubspec_parse/lib/src/dependency.dart
@@ -0,0 +1,277 @@
+// 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.
+
+import 'package:collection/collection.dart';
+import 'package:json_annotation/json_annotation.dart';
+import 'package:pub_semver/pub_semver.dart';
+import 'package:yaml/yaml.dart';
+
+part 'dependency.g.dart';
+
+Map<String, Dependency> parseDeps(Map? source) =>
+ source?.map((k, v) {
+ final key = k as String;
+ Dependency? value;
+ try {
+ value = _fromJson(v, k);
+ } on CheckedFromJsonException catch (e) {
+ if (e.map is! YamlMap) {
+ // This is likely a "synthetic" map created from a String value
+ // Use `source` to throw this exception with an actual YamlMap and
+ // extract the associated error information.
+ throw CheckedFromJsonException(source, key, e.className!, e.message);
+ }
+ rethrow;
+ }
+
+ if (value == null) {
+ throw CheckedFromJsonException(
+ source,
+ key,
+ 'Pubspec',
+ 'Not a valid dependency value.',
+ );
+ }
+ return MapEntry(key, value);
+ }) ??
+ {};
+
+const _sourceKeys = ['sdk', 'git', 'path', 'hosted'];
+
+/// Returns `null` if the data could not be parsed.
+Dependency? _fromJson(Object? data, String name) {
+ if (data is String || data == null) {
+ return _$HostedDependencyFromJson({'version': data});
+ }
+
+ if (data is Map) {
+ final matchedKeys =
+ data.keys.cast<String>().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));
+
+ return $checkedNew<Dependency>('Dependency', data, () {
+ if (firstUnrecognizedKey != null) {
+ throw UnrecognizedKeysException(
+ [firstUnrecognizedKey],
+ data,
+ _sourceKeys,
+ );
+ }
+ if (matchedKeys.length > 1) {
+ throw CheckedFromJsonException(
+ data,
+ matchedKeys[1],
+ 'Dependency',
+ 'A dependency may only have one source.',
+ );
+ }
+
+ final key = matchedKeys.single;
+
+ return switch (key) {
+ 'git' => GitDependency.fromData(data[key]),
+ 'path' => PathDependency.fromData(data[key]),
+ 'sdk' => _$SdkDependencyFromJson(data),
+ 'hosted' => _$HostedDependencyFromJson(data)
+ ..hosted?._nameOfPackage = name,
+ _ => throw StateError('There is a bug in pubspec_parse.'),
+ };
+ });
+ }
+ }
+
+ // Not a String or a Map – return null so parent logic can throw proper error
+ return null;
+}
+
+sealed class Dependency {}
+
+@JsonSerializable()
+class SdkDependency extends Dependency {
+ final String sdk;
+ @JsonKey(fromJson: _constraintFromString)
+ final VersionConstraint version;
+
+ SdkDependency(this.sdk, {VersionConstraint? version})
+ : version = version ?? VersionConstraint.any;
+
+ @override
+ bool operator ==(Object other) =>
+ other is SdkDependency && other.sdk == sdk && other.version == version;
+
+ @override
+ int get hashCode => Object.hash(sdk, version);
+
+ @override
+ String toString() => 'SdkDependency: $sdk';
+}
+
+@JsonSerializable()
+class GitDependency extends Dependency {
+ @JsonKey(fromJson: parseGitUri)
+ final Uri url;
+ final String? ref;
+ final String? path;
+
+ GitDependency(this.url, {this.ref, this.path});
+
+ factory GitDependency.fromData(Object? data) {
+ if (data is String) {
+ data = {'url': data};
+ }
+
+ if (data is Map) {
+ return _$GitDependencyFromJson(data);
+ }
+
+ throw ArgumentError.value(data, 'git', 'Must be a String or a Map.');
+ }
+
+ @override
+ bool operator ==(Object other) =>
+ other is GitDependency &&
+ other.url == url &&
+ other.ref == ref &&
+ other.path == path;
+
+ @override
+ int get hashCode => Object.hash(url, ref, path);
+
+ @override
+ String toString() => 'GitDependency: url@$url';
+}
+
+Uri? parseGitUriOrNull(String? value) =>
+ value == null ? null : parseGitUri(value);
+
+Uri parseGitUri(String value) => _tryParseScpUri(value) ?? Uri.parse(value);
+
+/// Supports URIs like `[user@]host.xz:path/to/repo.git/`
+/// See https://git-scm.com/docs/git-clone#_git_urls_a_id_urls_a
+Uri? _tryParseScpUri(String value) {
+ final colonIndex = value.indexOf(':');
+
+ if (colonIndex < 0) {
+ return null;
+ } else if (colonIndex == value.indexOf('://')) {
+ // If the first colon is part of a scheme, it's not an scp-like URI
+ return null;
+ }
+ final slashIndex = value.indexOf('/');
+
+ if (slashIndex >= 0 && slashIndex < colonIndex) {
+ // Per docs: This syntax is only recognized if there are no slashes before
+ // the first colon. This helps differentiate a local path that contains a
+ // colon. For example the local path foo:bar could be specified as an
+ // absolute path or ./foo:bar to avoid being misinterpreted as an ssh url.
+ return null;
+ }
+
+ final atIndex = value.indexOf('@');
+ if (colonIndex > atIndex) {
+ final user = atIndex >= 0 ? value.substring(0, atIndex) : null;
+ final host = value.substring(atIndex + 1, colonIndex);
+ final path = value.substring(colonIndex + 1);
+ return Uri(scheme: 'ssh', userInfo: user, host: host, path: path);
+ }
+ return null;
+}
+
+class PathDependency extends Dependency {
+ final String path;
+
+ PathDependency(this.path);
+
+ factory PathDependency.fromData(Object? data) {
+ if (data is String) {
+ return PathDependency(data);
+ }
+ throw ArgumentError.value(data, 'path', 'Must be a String.');
+ }
+
+ @override
+ bool operator ==(Object other) =>
+ other is PathDependency && other.path == path;
+
+ @override
+ int get hashCode => path.hashCode;
+
+ @override
+ String toString() => 'PathDependency: path@$path';
+}
+
+@JsonSerializable(disallowUnrecognizedKeys: true)
+class HostedDependency extends Dependency {
+ @JsonKey(fromJson: _constraintFromString)
+ final VersionConstraint version;
+
+ @JsonKey(disallowNullValue: true)
+ final HostedDetails? hosted;
+
+ HostedDependency({VersionConstraint? version, this.hosted})
+ : version = version ?? VersionConstraint.any;
+
+ @override
+ bool operator ==(Object other) =>
+ other is HostedDependency &&
+ other.version == version &&
+ other.hosted == hosted;
+
+ @override
+ int get hashCode => Object.hash(version, hosted);
+
+ @override
+ String toString() => 'HostedDependency: $version';
+}
+
+@JsonSerializable(disallowUnrecognizedKeys: true)
+class HostedDetails {
+ /// The name of the target dependency as declared in a `hosted` block.
+ ///
+ /// This may be null if no explicit name is present, for instance because the
+ /// hosted dependency was declared as a string (`hosted: pub.example.org`).
+ @JsonKey(name: 'name')
+ final String? declaredName;
+
+ @JsonKey(fromJson: parseGitUriOrNull, disallowNullValue: true)
+ final Uri? url;
+
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ String? _nameOfPackage;
+
+ /// The name of this package on the package repository.
+ ///
+ /// If this hosted block has a [declaredName], that one will be used.
+ /// Otherwise, the name will be inferred from the surrounding package name.
+ String get name => declaredName ?? _nameOfPackage!;
+
+ HostedDetails(this.declaredName, this.url);
+
+ factory HostedDetails.fromJson(Object data) {
+ if (data is String) {
+ data = {'url': data};
+ }
+
+ if (data is Map) {
+ return _$HostedDetailsFromJson(data);
+ }
+
+ throw ArgumentError.value(data, 'hosted', 'Must be a Map or String.');
+ }
+
+ @override
+ bool operator ==(Object other) =>
+ other is HostedDetails && other.name == name && other.url == url;
+
+ @override
+ int get hashCode => Object.hash(name, url);
+}
+
+VersionConstraint _constraintFromString(String? input) =>
+ input == null ? VersionConstraint.any : VersionConstraint.parse(input);
diff --git a/pkgs/pubspec_parse/lib/src/dependency.g.dart b/pkgs/pubspec_parse/lib/src/dependency.g.dart
new file mode 100644
index 0000000..1a504f1
--- /dev/null
+++ b/pkgs/pubspec_parse/lib/src/dependency.g.dart
@@ -0,0 +1,72 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+// ignore_for_file: deprecated_member_use_from_same_package, lines_longer_than_80_chars, require_trailing_commas, unnecessary_cast
+
+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;
+ },
+ );
+
+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;
+ },
+ );
+
+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
new file mode 100644
index 0000000..eb77908
--- /dev/null
+++ b/pkgs/pubspec_parse/lib/src/pubspec.dart
@@ -0,0 +1,258 @@
+// 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.
+
+import 'package:checked_yaml/checked_yaml.dart';
+import 'package:json_annotation/json_annotation.dart';
+import 'package:pub_semver/pub_semver.dart';
+
+import 'dependency.dart';
+import 'screenshot.dart';
+
+part 'pubspec.g.dart';
+
+@JsonSerializable()
+class Pubspec {
+ // TODO: executables
+
+ final String name;
+
+ @JsonKey(fromJson: _versionFromString)
+ final Version? version;
+
+ final String? description;
+
+ /// This should be a URL pointing to the website for the package.
+ final String? homepage;
+
+ /// Specifies where to publish this package.
+ ///
+ /// Accepted values: `null`, `'none'` or an `http` or `https` URL.
+ ///
+ /// [More information](https://dart.dev/tools/pub/pubspec#publish_to).
+ final String? publishTo;
+
+ /// Optional field to specify the source code repository of the package.
+ /// Useful when a package has both a home page and a repository.
+ final Uri? repository;
+
+ /// Optional field to a web page where developers can report new issues or
+ /// view existing ones.
+ final Uri? issueTracker;
+
+ /// Optional field to list the URLs where the package authors accept
+ /// support or funding.
+ final List<Uri>? funding;
+
+ /// Optional field to list the topics that this packages belongs to.
+ final List<String>? topics;
+
+ /// Optional field to list advisories to be ignored by the client.
+ final List<String>? ignoredAdvisories;
+
+ /// Optional field for specifying included screenshot files.
+ @JsonKey(fromJson: parseScreenshots)
+ final List<Screenshot>? screenshots;
+
+ /// 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',
+ )
+ String? get author {
+ if (authors.length == 1) {
+ return authors.single;
+ }
+ return null;
+ }
+
+ @Deprecated(
+ 'See https://dart.dev/tools/pub/pubspec#authorauthors',
+ )
+ final List<String> authors;
+ final String? documentation;
+
+ @JsonKey(fromJson: _environmentMap)
+ final Map<String, VersionConstraint?> environment;
+
+ @JsonKey(fromJson: parseDeps)
+ final Map<String, Dependency> dependencies;
+
+ @JsonKey(fromJson: parseDeps)
+ final Map<String, Dependency> devDependencies;
+
+ @JsonKey(fromJson: parseDeps)
+ final Map<String, Dependency> dependencyOverrides;
+
+ /// Optional configuration specific to [Flutter](https://flutter.io/)
+ /// packages.
+ ///
+ /// May include
+ /// [assets](https://flutter.io/docs/development/ui/assets-and-images)
+ /// and other settings.
+ final Map<String, dynamic>? flutter;
+
+ /// Optional field to specify executables
+ @JsonKey(fromJson: _executablesMap)
+ final Map<String, String?> executables;
+
+ /// If this package is a Pub Workspace, this field lists the sub-packages.
+ final List<String>? workspace;
+
+ /// Specifies how to resolve dependencies with the surrounding Pub Workspace.
+ final String? resolution;
+
+ /// If [author] and [authors] are both provided, their values are combined
+ /// with duplicates eliminated.
+ Pubspec(
+ this.name, {
+ this.version,
+ this.publishTo,
+ @Deprecated(
+ 'See https://dart.dev/tools/pub/pubspec#authorauthors',
+ )
+ String? author,
+ @Deprecated(
+ 'See https://dart.dev/tools/pub/pubspec#authorauthors',
+ )
+ List<String>? authors,
+ Map<String, VersionConstraint?>? environment,
+ this.homepage,
+ this.repository,
+ this.issueTracker,
+ this.funding,
+ this.topics,
+ this.ignoredAdvisories,
+ this.screenshots,
+ this.documentation,
+ this.description,
+ this.workspace,
+ this.resolution,
+ Map<String, Dependency>? dependencies,
+ Map<String, Dependency>? devDependencies,
+ Map<String, Dependency>? dependencyOverrides,
+ this.flutter,
+ Map<String, String?>? 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 {} {
+ if (name.isEmpty) {
+ throw ArgumentError.value(name, 'name', '"name" cannot be empty.');
+ }
+
+ if (publishTo != null && publishTo != 'none') {
+ try {
+ final targetUri = Uri.parse(publishTo!);
+ if (!(targetUri.isScheme('http') || targetUri.isScheme('https'))) {
+ throw const FormatException('Must be an http or https URL.');
+ }
+ } on FormatException catch (e) {
+ throw ArgumentError.value(publishTo, 'publishTo', e.message);
+ }
+ }
+ }
+
+ factory Pubspec.fromJson(Map json, {bool lenient = false}) {
+ if (lenient) {
+ while (json.isNotEmpty) {
+ // Attempting to remove top-level properties that cause parsing errors.
+ try {
+ return _$PubspecFromJson(json);
+ } on CheckedFromJsonException catch (e) {
+ if (e.map == json && json.containsKey(e.key)) {
+ json = Map.from(json)..remove(e.key);
+ continue;
+ }
+ rethrow;
+ }
+ }
+ }
+
+ return _$PubspecFromJson(json);
+ }
+
+ /// Parses source [yaml] into [Pubspec].
+ ///
+ /// When [lenient] is set, top-level property-parsing or type cast errors are
+ /// ignored and `null` values are returned.
+ factory Pubspec.parse(String yaml, {Uri? sourceUrl, bool lenient = false}) =>
+ checkedYamlDecode(
+ yaml,
+ (map) => Pubspec.fromJson(map!, lenient: lenient),
+ sourceUrl: sourceUrl,
+ );
+
+ static List<String> _normalizeAuthors(String? author, List<String>? authors) {
+ final value = <String>{
+ if (author != null) author,
+ ...?authors,
+ };
+ return value.toList();
+ }
+}
+
+Version? _versionFromString(String? input) =>
+ input == null ? null : Version.parse(input);
+
+Map<String, VersionConstraint?> _environmentMap(Map? source) =>
+ source?.map((k, value) {
+ final key = k as String;
+ if (key == 'dart') {
+ // github.com/dart-lang/pub/blob/d84173eeb03c3/lib/src/pubspec.dart#L342
+ // 'dart' is not allowed as a key!
+ throw CheckedFromJsonException(
+ source,
+ 'dart',
+ 'VersionConstraint',
+ 'Use "sdk" to for Dart SDK constraints.',
+ badKey: true,
+ );
+ }
+
+ VersionConstraint? constraint;
+ if (value == null) {
+ constraint = null;
+ } else if (value is String) {
+ try {
+ constraint = VersionConstraint.parse(value);
+ } on FormatException catch (e) {
+ throw CheckedFromJsonException(source, key, 'Pubspec', e.message);
+ }
+
+ return MapEntry(key, constraint);
+ } else {
+ throw CheckedFromJsonException(
+ source,
+ key,
+ 'VersionConstraint',
+ '`$value` is not a String.',
+ );
+ }
+
+ return MapEntry(key, constraint);
+ }) ??
+ {};
+
+Map<String, String?> _executablesMap(Map? source) =>
+ source?.map((k, value) {
+ final key = k as String;
+ if (value == null) {
+ return MapEntry(key, null);
+ } else if (value is String) {
+ return MapEntry(key, value);
+ } else {
+ throw CheckedFromJsonException(
+ source,
+ key,
+ 'String',
+ '`$value` is not a String.',
+ );
+ }
+ }) ??
+ {};
diff --git a/pkgs/pubspec_parse/lib/src/pubspec.g.dart b/pkgs/pubspec_parse/lib/src/pubspec.g.dart
new file mode 100644
index 0000000..58e015a
--- /dev/null
+++ b/pkgs/pubspec_parse/lib/src/pubspec.g.dart
@@ -0,0 +1,69 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+// ignore_for_file: deprecated_member_use_from_same_package, lines_longer_than_80_chars, require_trailing_commas, unnecessary_cast
+
+part of 'pubspec.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+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<dynamic>?)?.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<dynamic>?)
+ ?.map((e) => Uri.parse(e as String))
+ .toList()),
+ topics: $checkedConvert('topics',
+ (v) => (v as List<dynamic>?)?.map((e) => e as String).toList()),
+ ignoredAdvisories: $checkedConvert('ignored_advisories',
+ (v) => (v as List<dynamic>?)?.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<dynamic>?)?.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'
+ },
+ );
diff --git a/pkgs/pubspec_parse/lib/src/screenshot.dart b/pkgs/pubspec_parse/lib/src/screenshot.dart
new file mode 100644
index 0000000..f5f0be2
--- /dev/null
+++ b/pkgs/pubspec_parse/lib/src/screenshot.dart
@@ -0,0 +1,65 @@
+// Copyright (c) 2021, 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:json_annotation/json_annotation.dart';
+
+@JsonSerializable()
+class Screenshot {
+ final String description;
+ final String path;
+
+ Screenshot(this.description, this.path);
+}
+
+List<Screenshot> parseScreenshots(List? input) {
+ final res = <Screenshot>[];
+ if (input == null) {
+ return res;
+ }
+
+ for (final e in input) {
+ if (e is! Map) continue;
+
+ final description = e['description'];
+ if (description == null) {
+ throw CheckedFromJsonException(
+ e,
+ 'description',
+ 'Screenshot',
+ 'Missing required key `description`',
+ );
+ }
+
+ if (description is! String) {
+ throw CheckedFromJsonException(
+ e,
+ 'description',
+ 'Screenshot',
+ '`$description` is not a String',
+ );
+ }
+
+ final path = e['path'];
+ if (path == null) {
+ throw CheckedFromJsonException(
+ e,
+ 'path',
+ 'Screenshot',
+ 'Missing required key `path`',
+ );
+ }
+
+ if (path is! String) {
+ throw CheckedFromJsonException(
+ e,
+ 'path',
+ 'Screenshot',
+ '`$path` is not a String',
+ );
+ }
+
+ res.add(Screenshot(description, path));
+ }
+ return res;
+}
diff --git a/pkgs/pubspec_parse/pubspec.yaml b/pkgs/pubspec_parse/pubspec.yaml
new file mode 100644
index 0000000..73a1117
--- /dev/null
+++ b/pkgs/pubspec_parse/pubspec.yaml
@@ -0,0 +1,32 @@
+name: pubspec_parse
+version: 1.5.0
+description: >-
+ Simple package for parsing pubspec.yaml files with a type-safe API and rich
+ error reporting.
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/pubspec_parse
+
+topics:
+- dart-pub
+
+environment:
+ sdk: ^3.6.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
+
+dev_dependencies:
+ build_runner: ^2.4.6
+ build_verify: ^3.0.0
+ dart_flutter_team_lints: ^3.0.0
+ json_serializable: ^6.9.1
+ path: ^1.9.0
+ # Needed because we are configuring `combining_builder`
+ source_gen: ^2.0.0
+ stack_trace: ^1.10.0
+ test: ^1.24.4
+ 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
new file mode 100644
index 0000000..f1e4f57
--- /dev/null
+++ b/pkgs/pubspec_parse/test/dependency_test.dart
@@ -0,0 +1,446 @@
+// 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.
+
+import 'dart:io';
+
+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('hosted', _hostedDependency);
+ group('git', _gitDependency);
+ group('sdk', _sdkDependency);
+ group('path', _pathDependency);
+
+ group('errors', () {
+ test('List', () {
+ _expectThrows(
+ [],
+ r'''
+line 4, column 10: Unsupported value for "dep". Not a valid dependency value.
+ ╷
+4 │ "dep": []
+ │ ^^
+ ╵''',
+ );
+ });
+
+ test('int', () {
+ _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', () {
+ _expectThrows(
+ {'path': 'a', 'git': 'b'},
+ r'''
+line 6, column 11: Unsupported value for "git". A dependency may only have one source.
+ ╷
+6 │ "git": "b"
+ │ ^^^
+ ╵''',
+ );
+ });
+
+ test('map with unsupported keys', () {
+ _expectThrows(
+ {'bob': 'a', 'jones': 'b'},
+ r'''
+line 5, column 4: Unrecognized keys: [bob]; supported keys: [sdk, git, path, hosted]
+ ╷
+5 │ "bob": "a",
+ │ ^^^^^
+ ╵''',
+ );
+ });
+ });
+}
+
+void _hostedDependency() {
+ test('null', () async {
+ final dep = await _dependency<HostedDependency>(null);
+ expect(dep.version.toString(), 'any');
+ expect(dep.hosted, isNull);
+ expect(dep.toString(), 'HostedDependency: any');
+ });
+
+ test('empty map', () async {
+ final dep = await _dependency<HostedDependency>({});
+ expect(dep.hosted, isNull);
+ expect(dep.toString(), 'HostedDependency: any');
+ });
+
+ test('string version', () async {
+ final dep = await _dependency<HostedDependency>('^1.0.0');
+ expect(dep.version.toString(), '^1.0.0');
+ expect(dep.hosted, isNull);
+ expect(dep.toString(), 'HostedDependency: ^1.0.0');
+ });
+
+ test('bad string version', () {
+ _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 {
+ final dep = await _dependency<HostedDependency>({'version': '^1.0.0'});
+ expect(dep.version.toString(), '^1.0.0');
+ expect(dep.hosted, isNull);
+ expect(dep.toString(), 'HostedDependency: ^1.0.0');
+ });
+
+ test('map w/ version and hosted as Map', () async {
+ final dep = await _dependency<HostedDependency>({
+ 'version': '^1.0.0',
+ 'hosted': {'name': 'hosted_name', 'url': 'https://hosted_url'},
+ });
+ expect(dep.version.toString(), '^1.0.0');
+ expect(dep.hosted!.name, 'hosted_name');
+ expect(dep.hosted!.url.toString(), 'https://hosted_url');
+ expect(dep.toString(), 'HostedDependency: ^1.0.0');
+ });
+
+ test('map /w hosted as a map without name', () async {
+ final dep = await _dependency<HostedDependency>(
+ {
+ 'version': '^1.0.0',
+ 'hosted': {'url': 'https://hosted_url'},
+ },
+ skipTryPub: true, // todo: Unskip once pub supports this syntax
+ );
+ expect(dep.version.toString(), '^1.0.0');
+ expect(dep.hosted!.declaredName, isNull);
+ expect(dep.hosted!.name, 'dep');
+ expect(dep.hosted!.url.toString(), 'https://hosted_url');
+ expect(dep.toString(), 'HostedDependency: ^1.0.0');
+ });
+
+ test('map w/ bad version value', () {
+ _expectThrows(
+ {
+ 'version': 'not a version',
+ 'hosted': {'name': 'hosted_name', 'url': 'hosted_url'},
+ },
+ r'''
+line 5, column 15: Unsupported value for "version". Could not parse version "not a version". Unknown text at "not a version".
+ ╷
+5 │ "version": "not a version",
+ │ ^^^^^^^^^^^^^^^
+ ╵''',
+ );
+ });
+
+ test('map w/ extra keys should fail', () {
+ _expectThrows(
+ {
+ 'version': '^1.0.0',
+ 'hosted': {'name': 'hosted_name', 'url': 'hosted_url'},
+ 'not_supported': null,
+ },
+ r'''
+line 10, column 4: Unrecognized keys: [not_supported]; supported keys: [sdk, git, path, hosted]
+ ╷
+10 │ "not_supported": null
+ │ ^^^^^^^^^^^^^^^
+ ╵''',
+ );
+ });
+
+ test('map w/ version and hosted as String', () async {
+ final dep = await _dependency<HostedDependency>(
+ {'version': '^1.0.0', 'hosted': 'hosted_url'},
+ skipTryPub: true, // todo: Unskip once put supports this
+ );
+ expect(dep.version.toString(), '^1.0.0');
+ expect(dep.hosted!.declaredName, isNull);
+ expect(dep.hosted!.name, 'dep');
+ expect(dep.hosted!.url, Uri.parse('hosted_url'));
+ expect(dep.toString(), 'HostedDependency: ^1.0.0');
+ });
+
+ test('map w/ hosted as String', () async {
+ final dep = await _dependency<HostedDependency>({'hosted': 'hosted_url'});
+ expect(dep.version, VersionConstraint.any);
+ expect(dep.hosted!.declaredName, isNull);
+ expect(dep.hosted!.name, 'dep');
+ expect(dep.hosted!.url, Uri.parse('hosted_url'));
+ expect(dep.toString(), 'HostedDependency: any');
+ });
+
+ test('map w/ null hosted should error', () {
+ _expectThrows(
+ {'hosted': null},
+ r'''
+line 5, column 4: These keys had `null` values, which is not allowed: [hosted]
+ ╷
+5 │ "hosted": null
+ │ ^^^^^^^^
+ ╵''',
+ );
+ });
+
+ test('map w/ null version is fine', () async {
+ final dep = await _dependency<HostedDependency>({'version': null});
+ expect(dep.version, VersionConstraint.any);
+ expect(dep.hosted, isNull);
+ expect(dep.toString(), 'HostedDependency: any');
+ });
+}
+
+void _sdkDependency() {
+ test('without version', () async {
+ final dep = await _dependency<SdkDependency>({'sdk': 'flutter'});
+ expect(dep.sdk, 'flutter');
+ expect(dep.version, VersionConstraint.any);
+ expect(dep.toString(), 'SdkDependency: flutter');
+ });
+
+ test('with version', () async {
+ final dep = await _dependency<SdkDependency>(
+ {'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'",
+ );
+ });
+
+ test('number content', () {
+ _expectThrowsContaining(
+ {'sdk': 42},
+ r"type 'int' is not a subtype of type 'String'",
+ );
+ });
+}
+
+void _gitDependency() {
+ test('string', () async {
+ final dep = await _dependency<GitDependency>({'git': 'url'});
+ expect(dep.url.toString(), 'url');
+ expect(dep.path, isNull);
+ expect(dep.ref, isNull);
+ expect(dep.toString(), 'GitDependency: url@url');
+ });
+
+ test('string with version key is ignored', () async {
+ // Regression test for https://github.com/dart-lang/pubspec_parse/issues/13
+ final dep =
+ await _dependency<GitDependency>({'git': 'url', 'version': '^1.2.3'});
+ expect(dep.url.toString(), 'url');
+ expect(dep.path, isNull);
+ expect(dep.ref, isNull);
+ expect(dep.toString(), 'GitDependency: url@url');
+ });
+
+ test('string with user@ URL', () async {
+ final skipTryParse = Platform.environment.containsKey('TRAVIS');
+ if (skipTryParse) {
+ print('FYI: not validating git@ URI on travis due to failure');
+ }
+ final dep = await _dependency<GitDependency>(
+ {'git': 'git@localhost:dep.git'},
+ skipTryPub: skipTryParse,
+ );
+ expect(dep.url.toString(), 'ssh://git@localhost/dep.git');
+ expect(dep.path, isNull);
+ expect(dep.ref, isNull);
+ expect(dep.toString(), 'GitDependency: url@ssh://git@localhost/dep.git');
+ });
+
+ test('string with random extra key fails', () {
+ _expectThrows(
+ {'git': 'url', 'bob': '^1.2.3'},
+ r'''
+line 6, column 4: Unrecognized keys: [bob]; supported keys: [sdk, git, path, hosted]
+ ╷
+6 │ "bob": "^1.2.3"
+ │ ^^^^^
+ ╵''',
+ );
+ });
+
+ test('map', () async {
+ final dep = await _dependency<GitDependency>({
+ 'git': {'url': 'url', 'path': 'path', 'ref': 'ref'},
+ });
+ expect(dep.url.toString(), 'url');
+ expect(dep.path, 'path');
+ expect(dep.ref, 'ref');
+ expect(dep.toString(), 'GitDependency: url@url');
+ });
+
+ test('git - null content', () {
+ _expectThrows(
+ {'git': null},
+ r'''
+line 5, column 11: Unsupported value for "git". Must be a String or a Map.
+ ╷
+5 │ "git": null
+ │ ┌───────────^
+6 │ │ }
+ │ └──^
+ ╵''',
+ );
+ });
+
+ test('git - int content', () {
+ _expectThrows(
+ {'git': 42},
+ r'''
+line 5, column 11: Unsupported value for "git". Must be a String or a Map.
+ ╷
+5 │ "git": 42
+ │ ┌───────────^
+6 │ │ }
+ │ └──^
+ ╵''',
+ );
+ });
+
+ test('git - empty map', () {
+ _expectThrowsContaining(
+ {'git': <String, dynamic>{}},
+ 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'",
+ );
+ });
+
+ test('git - int url', () {
+ _expectThrowsContaining(
+ {
+ 'git': {'url': 42},
+ },
+ r"type 'int' is not a subtype of type 'String'",
+ );
+ });
+}
+
+void _pathDependency() {
+ test('valid', () async {
+ final dep = await _dependency<PathDependency>({'path': '../path'});
+ expect(dep.path, '../path');
+ expect(dep.toString(), 'PathDependency: path@../path');
+ });
+
+ test('valid with version key is ignored', () async {
+ final dep = await _dependency<PathDependency>(
+ {'path': '../path', 'version': '^1.2.3'},
+ );
+ expect(dep.path, '../path');
+ expect(dep.toString(), 'PathDependency: path@../path');
+ });
+
+ test('valid with random extra key fails', () {
+ _expectThrows(
+ {'path': '../path', 'bob': '^1.2.3'},
+ r'''
+line 6, column 4: Unrecognized keys: [bob]; supported keys: [sdk, git, path, hosted]
+ ╷
+6 │ "bob": "^1.2.3"
+ │ ^^^^^
+ ╵''',
+ );
+ });
+
+ test('null content', () {
+ _expectThrows(
+ {'path': null},
+ r'''
+line 5, column 12: Unsupported value for "path". Must be a String.
+ ╷
+5 │ "path": null
+ │ ┌────────────^
+6 │ │ }
+ │ └──^
+ ╵''',
+ );
+ });
+
+ test('int content', () {
+ _expectThrows(
+ {'path': 42},
+ r'''
+line 5, column 12: Unsupported value for "path". Must be a String.
+ ╷
+5 │ "path": 42
+ │ ┌────────────^
+6 │ │ }
+ │ └──^
+ ╵''',
+ );
+ });
+}
+
+void _expectThrows(Object content, String expectedError) {
+ expectParseThrows(
+ {
+ 'name': 'sample',
+ 'dependencies': {'dep': content},
+ },
+ expectedError,
+ );
+}
+
+void _expectThrowsContaining(Object content, String errorText) {
+ expectParseThrowsContaining(
+ {
+ 'name': 'sample',
+ 'dependencies': {'dep': content},
+ },
+ errorText,
+ );
+}
+
+Future<T> _dependency<T extends Dependency>(
+ Object? content, {
+ bool skipTryPub = false,
+}) async {
+ final value = await parse(
+ {
+ ...defaultPubspec,
+ 'dependencies': {'dep': content},
+ },
+ skipTryPub: skipTryPub,
+ );
+ expect(value.name, 'sample');
+ expect(value.dependencies, hasLength(1));
+
+ final entry = value.dependencies.entries.single;
+ expect(entry.key, 'dep');
+
+ return entry.value as T;
+}
diff --git a/pkgs/pubspec_parse/test/ensure_build_test.dart b/pkgs/pubspec_parse/test/ensure_build_test.dart
new file mode 100644
index 0000000..0e4371c
--- /dev/null
+++ b/pkgs/pubspec_parse/test/ensure_build_test.dart
@@ -0,0 +1,18 @@
+// 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.
+
+@Timeout.factor(2)
+@TestOn('vm')
+@Tags(['presubmit-only'])
+library;
+
+import 'package:build_verify/build_verify.dart';
+import 'package:test/test.dart';
+
+void main() {
+ test(
+ 'ensure_build',
+ () => expectBuildClean(packageRelativeDirectory: 'pkgs/pubspec_parse/'),
+ );
+}
diff --git a/pkgs/pubspec_parse/test/git_uri_test.dart b/pkgs/pubspec_parse/test/git_uri_test.dart
new file mode 100644
index 0000000..be89ba8
--- /dev/null
+++ b/pkgs/pubspec_parse/test/git_uri_test.dart
@@ -0,0 +1,25 @@
+import 'package:pubspec_parse/src/dependency.dart';
+import 'package:test/test.dart';
+
+void main() {
+ for (var item in {
+ 'git@github.com:google/grinder.dart.git':
+ 'ssh://git@github.com/google/grinder.dart.git',
+ 'host.xz:path/to/repo.git/': 'ssh://host.xz/path/to/repo.git/',
+ 'http:path/to/repo.git/': 'ssh://http/path/to/repo.git/',
+ 'file:path/to/repo.git/': 'ssh://file/path/to/repo.git/',
+ './foo:bar': 'foo%3Abar',
+ '/path/to/repo.git/': '/path/to/repo.git/',
+ 'file:///path/to/repo.git/': 'file:///path/to/repo.git/',
+ }.entries) {
+ test(item.key, () {
+ final uri = parseGitUri(item.key);
+
+ printOnFailure(
+ [uri.scheme, uri.userInfo, uri.host, uri.port, uri.path].join('\n'),
+ );
+
+ expect(uri, Uri.parse(item.value));
+ });
+ }
+}
diff --git a/pkgs/pubspec_parse/test/parse_test.dart b/pkgs/pubspec_parse/test/parse_test.dart
new file mode 100644
index 0000000..e0698af
--- /dev/null
+++ b/pkgs/pubspec_parse/test/parse_test.dart
@@ -0,0 +1,827 @@
+// 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
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:pub_semver/pub_semver.dart';
+import 'package:test/test.dart';
+
+import 'test_utils.dart';
+
+void main() {
+ test('minimal set values', () async {
+ final value = await parse(defaultPubspec);
+ expect(value.name, 'sample');
+ expect(value.version, isNull);
+ expect(value.publishTo, isNull);
+ expect(value.description, isNull);
+ 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.documentation, isNull);
+ expect(value.dependencies, isEmpty);
+ expect(value.devDependencies, isEmpty);
+ expect(value.dependencyOverrides, isEmpty);
+ expect(value.flutter, isNull);
+ expect(value.repository, isNull);
+ expect(value.issueTracker, isNull);
+ expect(value.screenshots, isEmpty);
+ expect(value.workspace, isNull);
+ expect(value.resolution, isNull);
+ expect(value.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',
+ },
+ },
+ skipTryPub: true,
+ );
+ expect(value.name, 'sample');
+ expect(value.version, version);
+ expect(value.publishTo, 'none');
+ expect(value.description, 'description');
+ expect(value.homepage, 'homepage');
+ expect(value.author, 'name@example.com');
+ expect(value.authors, ['name@example.com']);
+ expect(value.environment, hasLength(1));
+ expect(value.environment, containsPair('sdk', sdkConstraint));
+ expect(value.documentation, 'documentation');
+ expect(value.dependencies, isEmpty);
+ expect(value.devDependencies, isEmpty);
+ expect(value.dependencyOverrides, isEmpty);
+ expect(value.repository, Uri.parse('https://github.com/example/repo'));
+ expect(
+ value.issueTracker,
+ Uri.parse('https://github.com/example/repo/issues'),
+ );
+ expect(value.funding, hasLength(1));
+ expect(value.funding!.single.toString(), 'https://patreon.com/example');
+ expect(value.topics, hasLength(2));
+ expect(value.topics!.first, 'widget');
+ expect(value.topics!.last, 'button');
+ expect(value.ignoredAdvisories, hasLength(2));
+ expect(value.ignoredAdvisories!.first, '111');
+ expect(value.ignoredAdvisories!.last, '222');
+ expect(value.screenshots, hasLength(1));
+ expect(value.screenshots!.first.description, 'my screenshot');
+ expect(value.screenshots!.first.path, 'path/to/screenshot');
+ expect(value.executables, hasLength(2));
+ expect(value.executables.keys, contains('my_script'));
+ expect(value.executables.keys, contains('my_script2'));
+ expect(value.executables['my_script'], 'bin/my_script.dart');
+ expect(value.executables['my_script2'], 'bin/my_script2.dart');
+ expect(value.workspace, hasLength(2));
+ expect(value.workspace!.first, 'pkg1');
+ expect(value.workspace!.last, 'pkg2');
+ expect(value.resolution, 'workspace');
+ });
+
+ 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,
+ );
+ expect(value.name, 'sample');
+ expect(value.environment, hasLength(2));
+ expect(value.environment, containsPair('bob', isNull));
+ });
+
+ group('publish_to', () {
+ for (var entry in {
+ 42: "Unsupported value for \"publish_to\". type 'int' is not a subtype of type 'String?'",
+ '##not a uri!': r'''
+line 3, column 16: Unsupported value for "publish_to". Must be an http or https URL.
+ ╷
+3 │ "publish_to": "##not a uri!"
+ │ ^^^^^^^^^^^^^^
+ ╵''',
+ '/cool/beans': r'''
+line 3, column 16: Unsupported value for "publish_to". Must be an http or https URL.
+ ╷
+3 │ "publish_to": "/cool/beans"
+ │ ^^^^^^^^^^^^^
+ ╵''',
+ 'file:///Users/kevmoo/': r'''
+line 3, column 16: Unsupported value for "publish_to". Must be an http or https URL.
+ ╷
+3 │ "publish_to": "file:///Users/kevmoo/"
+ │ ^^^^^^^^^^^^^^^^^^^^^^^
+ ╵''',
+ }.entries) {
+ test('cannot be `${entry.key}`', () {
+ expectParseThrowsContaining(
+ {'name': 'sample', 'publish_to': entry.key},
+ entry.value,
+ skipTryPub: true,
+ );
+ });
+ }
+
+ for (var entry in {
+ null: null,
+ 'http': 'http://example.com',
+ 'https': 'https://example.com',
+ 'none': 'none',
+ }.entries) {
+ test('can be ${entry.key}', () async {
+ final value = await parse({
+ ...defaultPubspec,
+ 'publish_to': entry.value,
+ });
+ expect(value.publishTo, entry.value);
+ });
+ }
+ });
+
+ group('author, authors', () {
+ test('one author', () async {
+ final value = await parse({
+ ...defaultPubspec,
+ 'author': 'name@example.com',
+ });
+ expect(value.author, 'name@example.com');
+ expect(value.authors, ['name@example.com']);
+ });
+
+ test('one author, via authors', () async {
+ final value = await parse({
+ ...defaultPubspec,
+ 'authors': ['name@example.com'],
+ });
+ expect(value.author, 'name@example.com');
+ expect(value.authors, ['name@example.com']);
+ });
+
+ test('many authors', () async {
+ final value = await parse({
+ ...defaultPubspec,
+ 'authors': ['name@example.com', 'name2@example.com'],
+ });
+ expect(value.author, isNull);
+ expect(value.authors, ['name@example.com', 'name2@example.com']);
+ });
+
+ test('author and authors', () async {
+ final value = await parse({
+ ...defaultPubspec,
+ 'author': 'name@example.com',
+ 'authors': ['name2@example.com'],
+ });
+ expect(value.author, isNull);
+ expect(value.authors, ['name@example.com', 'name2@example.com']);
+ });
+
+ test('duplicate author values', () async {
+ final value = await parse({
+ ...defaultPubspec,
+ 'author': 'name@example.com',
+ 'authors': ['name@example.com', 'name@example.com'],
+ });
+ expect(value.author, 'name@example.com');
+ expect(value.authors, ['name@example.com']);
+ });
+
+ test('flutter', () async {
+ final value = await parse({
+ ...defaultPubspec,
+ 'flutter': {'key': 'value'},
+ });
+ expect(value.flutter, {'key': 'value'});
+ });
+ });
+
+ group('executables', () {
+ test('one executable', () async {
+ final value = await parse({
+ ...defaultPubspec,
+ 'executables': {'my_script': 'bin/my_script.dart'},
+ });
+ expect(value.executables, hasLength(1));
+ expect(value.executables.keys, contains('my_script'));
+ expect(value.executables['my_script'], 'bin/my_script.dart');
+ });
+
+ test('many executables', () async {
+ final value = await parse({
+ ...defaultPubspec,
+ 'executables': {
+ 'my_script': 'bin/my_script.dart',
+ 'my_script2': 'bin/my_script2.dart',
+ },
+ });
+ expect(value.executables, hasLength(2));
+ expect(value.executables.keys, contains('my_script'));
+ expect(value.executables.keys, contains('my_script2'));
+ expect(value.executables['my_script'], 'bin/my_script.dart');
+ expect(value.executables['my_script2'], 'bin/my_script2.dart');
+ });
+
+ test('invalid value', () async {
+ expectParseThrowsContaining(
+ {
+ ...defaultPubspec,
+ 'executables': {
+ 'script': 32,
+ },
+ },
+ 'Unsupported value for "script". `32` is not a String.',
+ skipTryPub: true,
+ );
+ });
+
+ test('invalid executable - lenient', () async {
+ final value = await parse(
+ {
+ ...defaultPubspec,
+ 'executables': 'Invalid value',
+ },
+ lenient: true,
+ );
+ expect(value.name, 'sample');
+ expect(value.executables, isEmpty);
+ });
+ });
+
+ group('invalid', () {
+ test('null', () {
+ expectParseThrows(
+ null,
+ r'''
+line 1, column 1: Not a map
+ ╷
+1 │ null
+ │ ^^^^
+ ╵''',
+ );
+ });
+ test('empty string', () {
+ expectParseThrows(
+ '',
+ r'''
+line 1, column 1: Not a map
+ ╷
+1 │ ""
+ │ ^^
+ ╵''',
+ );
+ });
+ test('array', () {
+ expectParseThrows(
+ [],
+ r'''
+line 1, column 1: Not a map
+ ╷
+1 │ []
+ │ ^^
+ ╵''',
+ );
+ });
+
+ test('missing name', () {
+ expectParseThrowsContaining(
+ {},
+ "Missing key \"name\". type 'Null' is not a subtype of type 'String'",
+ );
+ });
+
+ test('null name value', () {
+ expectParseThrowsContaining(
+ {'name': null},
+ "Unsupported value for \"name\". type 'Null' is not a subtype of type 'String'",
+ );
+ });
+
+ test('empty name value', () {
+ expectParseThrows(
+ {'name': ''},
+ r'''
+line 2, column 10: Unsupported value for "name". "name" cannot be empty.
+ ╷
+2 │ "name": ""
+ │ ^^
+ ╵''',
+ );
+ });
+
+ test('"dart" is an invalid environment key', () {
+ expectParseThrows(
+ {
+ 'name': 'sample',
+ 'environment': {'dart': 'cool'},
+ },
+ r'''
+line 4, column 3: Use "sdk" to for Dart SDK constraints.
+ ╷
+4 │ "dart": "cool"
+ │ ^^^^^^
+ ╵''',
+ );
+ });
+
+ test('environment values cannot be int', () {
+ expectParseThrows(
+ {
+ 'name': 'sample',
+ 'environment': {'sdk': 42},
+ },
+ r'''
+line 4, column 10: Unsupported value for "sdk". `42` is not a String.
+ ╷
+4 │ "sdk": 42
+ │ ┌──────────^
+5 │ │ }
+ │ └─^
+ ╵''',
+ );
+ });
+
+ test('version', () {
+ expectParseThrows(
+ {'name': 'sample', 'version': 'invalid'},
+ r'''
+line 3, column 13: Unsupported value for "version". Could not parse "invalid".
+ ╷
+3 │ "version": "invalid"
+ │ ^^^^^^^^^
+ ╵''',
+ );
+ });
+
+ test('invalid environment value', () {
+ expectParseThrows(
+ {
+ 'name': 'sample',
+ 'environment': {'sdk': 'silly'},
+ },
+ r'''
+line 4, column 10: Unsupported value for "sdk". Could not parse version "silly". Unknown text at "silly".
+ ╷
+4 │ "sdk": "silly"
+ │ ^^^^^^^
+ ╵''',
+ );
+ });
+
+ test('bad repository url', () {
+ expectParseThrowsContaining(
+ {
+ ...defaultPubspec,
+ 'repository': {'x': 'y'},
+ },
+ "Unsupported value for \"repository\". type 'YamlMap' is not a subtype of type 'String'",
+ skipTryPub: true,
+ );
+ });
+
+ test('bad issue_tracker url', () {
+ expectParseThrowsContaining(
+ {
+ 'name': 'sample',
+ 'issue_tracker': {'x': 'y'},
+ },
+ "Unsupported value for \"issue_tracker\". type 'YamlMap' is not a subtype of type 'String'",
+ skipTryPub: true,
+ );
+ });
+ });
+
+ group('funding', () {
+ test('not a list', () {
+ expectParseThrowsContaining(
+ {
+ ...defaultPubspec,
+ 'funding': 1,
+ },
+ "Unsupported value for \"funding\". type 'int' is not a subtype of type 'List<dynamic>?'",
+ skipTryPub: true,
+ );
+ });
+
+ test('not an uri', () {
+ expectParseThrowsContaining(
+ {
+ ...defaultPubspec,
+ 'funding': [1],
+ },
+ "Unsupported value for \"funding\". type 'int' is not a subtype of type 'String'",
+ skipTryPub: true,
+ );
+ });
+
+ test('not an uri', () {
+ expectParseThrows(
+ {
+ ...defaultPubspec,
+ 'funding': ['ht tps://example.com/'],
+ },
+ r'''
+line 6, column 13: Unsupported value for "funding". Illegal scheme character at offset 2.
+ ╷
+6 │ "funding": [
+ │ ┌─────────────^
+7 │ │ "ht tps://example.com/"
+8 │ └ ]
+ ╵''',
+ skipTryPub: true,
+ );
+ });
+ });
+ group('topics', () {
+ test('not a list', () {
+ expectParseThrowsContaining(
+ {
+ ...defaultPubspec,
+ 'topics': 1,
+ },
+ "Unsupported value for \"topics\". type 'int' is not a subtype of type 'List<dynamic>?'",
+ skipTryPub: true,
+ );
+ });
+
+ test('not a string', () {
+ expectParseThrowsContaining(
+ {
+ ...defaultPubspec,
+ 'topics': [1],
+ },
+ "Unsupported value for \"topics\". type 'int' is not a subtype of type 'String'",
+ skipTryPub: true,
+ );
+ });
+
+ test('invalid data - lenient', () async {
+ final value = await parse(
+ {
+ ...defaultPubspec,
+ 'topics': [1],
+ },
+ skipTryPub: true,
+ lenient: true,
+ );
+ expect(value.name, 'sample');
+ expect(value.topics, isNull);
+ });
+ });
+
+ group('ignored_advisories', () {
+ test('not a list', () {
+ expectParseThrowsContaining(
+ {
+ ...defaultPubspec,
+ 'ignored_advisories': 1,
+ },
+ "Unsupported value for \"ignored_advisories\". type 'int' is not a subtype of type 'List<dynamic>?'",
+ skipTryPub: true,
+ );
+ });
+
+ test('not a string', () {
+ expectParseThrowsContaining(
+ {
+ ...defaultPubspec,
+ 'ignored_advisories': [1],
+ },
+ "Unsupported value for \"ignored_advisories\". type 'int' is not a subtype of type 'String'",
+ skipTryPub: true,
+ );
+ });
+
+ test('invalid data - lenient', () async {
+ final value = await parse(
+ {
+ ...defaultPubspec,
+ 'ignored_advisories': [1],
+ },
+ skipTryPub: true,
+ lenient: true,
+ );
+ expect(value.name, 'sample');
+ expect(value.ignoredAdvisories, isNull);
+ });
+ });
+
+ group('screenshots', () {
+ test('one screenshot', () async {
+ final value = await parse({
+ ...defaultPubspec,
+ 'screenshots': [
+ {'description': 'my screenshot', 'path': 'path/to/screenshot'},
+ ],
+ });
+ expect(value.screenshots, hasLength(1));
+ expect(value.screenshots!.first.description, 'my screenshot');
+ expect(value.screenshots!.first.path, 'path/to/screenshot');
+ });
+
+ test('many screenshots', () async {
+ final value = await parse({
+ ...defaultPubspec,
+ 'screenshots': [
+ {'description': 'my screenshot', 'path': 'path/to/screenshot'},
+ {
+ 'description': 'my second screenshot',
+ 'path': 'path/to/screenshot2',
+ },
+ ],
+ });
+ expect(value.screenshots, hasLength(2));
+ expect(value.screenshots!.first.description, 'my screenshot');
+ expect(value.screenshots!.first.path, 'path/to/screenshot');
+ expect(value.screenshots!.last.description, 'my second screenshot');
+ expect(value.screenshots!.last.path, 'path/to/screenshot2');
+ });
+
+ test('one screenshot plus invalid entries', () async {
+ final value = await parse({
+ ...defaultPubspec,
+ 'screenshots': [
+ 42,
+ {
+ 'description': 'my screenshot',
+ 'path': 'path/to/screenshot',
+ 'extraKey': 'not important',
+ },
+ 'not a screenshot',
+ ],
+ });
+ expect(value.screenshots, hasLength(1));
+ expect(value.screenshots!.first.description, 'my screenshot');
+ expect(value.screenshots!.first.path, 'path/to/screenshot');
+ });
+
+ test('invalid entries', () async {
+ final value = await parse({
+ ...defaultPubspec,
+ 'screenshots': [
+ 42,
+ 'not a screenshot',
+ ],
+ });
+ expect(value.screenshots, isEmpty);
+ });
+
+ test('missing key `dessription', () {
+ expectParseThrows(
+ {
+ ...defaultPubspec,
+ 'screenshots': [
+ {'path': 'my/path'},
+ ],
+ },
+ r'''
+line 7, column 3: Missing key "description". Missing required key `description`
+ ╷
+7 │ ┌ {
+8 │ │ "path": "my/path"
+9 │ └ }
+ ╵''',
+ skipTryPub: true,
+ );
+ });
+
+ test('missing key `path`', () {
+ expectParseThrows(
+ {
+ ...defaultPubspec,
+ 'screenshots': [
+ {'description': 'my screenshot'},
+ ],
+ },
+ r'''
+line 7, column 3: Missing key "path". Missing required key `path`
+ ╷
+7 │ ┌ {
+8 │ │ "description": "my screenshot"
+9 │ └ }
+ ╵''',
+ skipTryPub: true,
+ );
+ });
+
+ test('Value of description not a String`', () {
+ expectParseThrows(
+ {
+ ...defaultPubspec,
+ 'screenshots': [
+ {'description': 42},
+ ],
+ },
+ r'''
+line 8, column 19: Unsupported value for "description". `42` is not a String
+ ╷
+8 │ "description": 42
+ │ ┌───────────────────^
+9 │ │ }
+ │ └──^
+ ╵''',
+ skipTryPub: true,
+ );
+ });
+
+ test('Value of path not a String`', () {
+ expectParseThrows(
+ {
+ ...defaultPubspec,
+ 'screenshots': [
+ {
+ 'description': '',
+ 'path': 42,
+ },
+ ],
+ },
+ r'''
+line 9, column 12: Unsupported value for "path". `42` is not a String
+ ╷
+9 │ "path": 42
+ │ ┌────────────^
+10 │ │ }
+ │ └──^
+ ╵''',
+ skipTryPub: true,
+ );
+ });
+
+ test('invalid screenshot - lenient', () async {
+ final value = await parse(
+ {
+ ...defaultPubspec,
+ 'screenshots': 'Invalid value',
+ },
+ lenient: true,
+ );
+ expect(value.name, 'sample');
+ expect(value.screenshots, isEmpty);
+ });
+ });
+
+ group('lenient', () {
+ test('null', () {
+ expectParseThrows(
+ null,
+ r'''
+line 1, column 1: Not a map
+ ╷
+1 │ null
+ │ ^^^^
+ ╵''',
+ lenient: true,
+ );
+ });
+
+ test('empty string', () {
+ expectParseThrows(
+ '',
+ r'''
+line 1, column 1: Not a map
+ ╷
+1 │ ""
+ │ ^^
+ ╵''',
+ lenient: true,
+ );
+ });
+
+ test('name cannot be empty', () {
+ expectParseThrowsContaining(
+ {},
+ "Missing key \"name\". type 'Null' is not a subtype of type 'String'",
+ lenient: true,
+ );
+ });
+
+ test('bad repository url', () async {
+ 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,
+ );
+ 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,
+ );
+ expect(value.name, 'sample');
+ expect(value.repository, isNull);
+ expect(value.issueTracker, isNull);
+ });
+
+ test('deep error throws with lenient', () {
+ expect(
+ () => parse(
+ {
+ 'name': 'sample',
+ 'dependencies': {
+ 'foo': {
+ 'git': {'url': 1},
+ },
+ },
+ 'issue_tracker': {'x': 'y'},
+ },
+ skipTryPub: true,
+ lenient: true,
+ ),
+ throwsException,
+ );
+ });
+ });
+
+ group('workspaces', () {
+ test('workspace key must be a list', () {
+ expectParseThrowsContaining(
+ {
+ ...defaultPubspec,
+ 'workspace': 42,
+ },
+ 'Unsupported value for "workspace". type \'int\' is not a subtype of type \'List<dynamic>?\' in type cast',
+ skipTryPub: true,
+ );
+ });
+
+ test('workspace key must be a list of strings', () {
+ expectParseThrowsContaining(
+ {
+ ...defaultPubspec,
+ 'workspace': [42],
+ },
+ 'Unsupported value for "workspace". type \'int\' is not a subtype of type \'String\' in type cast',
+ skipTryPub: true,
+ );
+ });
+
+ test('resolution key must be a string', () {
+ expectParseThrowsContaining(
+ {
+ 'name': 'sample',
+ 'environment': {'sdk': '^3.6.0'},
+ 'resolution': 42,
+ },
+ 'Unsupported value for "resolution". type \'int\' is not a subtype of type \'String?\' in type cast',
+ skipTryPub: true,
+ );
+ });
+ });
+}
diff --git a/pkgs/pubspec_parse/test/pub_utils.dart b/pkgs/pubspec_parse/test/pub_utils.dart
new file mode 100644
index 0000000..a60aa2a
--- /dev/null
+++ b/pkgs/pubspec_parse/test/pub_utils.dart
@@ -0,0 +1,88 @@
+// 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.
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+import 'package:test_process/test_process.dart';
+
+Future<ProcResult> tryPub(String content) async {
+ await d.file('pubspec.yaml', content).create();
+
+ final proc = await TestProcess.start(
+ Platform.resolvedExecutable,
+ ['pub', 'get', '--offline'],
+ workingDirectory: d.sandbox,
+ // Don't pass current process VM options to child
+ environment: Map.from(Platform.environment)..remove('DART_VM_OPTIONS'),
+ );
+
+ final result = await ProcResult.fromTestProcess(proc);
+
+ printOnFailure(
+ [
+ '-----BEGIN pub output-----',
+ result.toString().trim(),
+ '-----END pub output-----',
+ ].join('\n'),
+ );
+
+ if (result.exitCode == 0) {
+ final lockContent =
+ File(p.join(d.sandbox, 'pubspec.lock')).readAsStringSync();
+
+ printOnFailure(
+ [
+ '-----BEGIN pubspec.lock-----',
+ lockContent.trim(),
+ '-----END pubspec.lock-----',
+ ].join('\n'),
+ );
+ }
+
+ return result;
+}
+
+class ProcResult {
+ final int exitCode;
+ final List<ProcLine> lines;
+
+ bool get cleanParse => exitCode == 0 || exitCode == 66 || exitCode == 69;
+
+ ProcResult(this.exitCode, this.lines);
+
+ static Future<ProcResult> fromTestProcess(TestProcess proc) async {
+ final items = <ProcLine>[];
+
+ final values = await Future.wait([
+ proc.exitCode,
+ proc.stdoutStream().forEach((line) => items.add(ProcLine(false, line))),
+ proc.stderrStream().forEach((line) => items.add(ProcLine(true, line))),
+ ]);
+
+ return ProcResult(values[0] as int, items);
+ }
+
+ @override
+ String toString() {
+ final buffer = StringBuffer('Exit code: $exitCode');
+ for (var line in lines) {
+ buffer.write('\n$line');
+ }
+ return buffer.toString();
+ }
+}
+
+class ProcLine {
+ final bool isError;
+ final String line;
+
+ ProcLine(this.isError, this.line);
+
+ @override
+ String toString() => '${isError ? 'err' : 'out'} $line';
+}
diff --git a/pkgs/pubspec_parse/test/test_utils.dart b/pkgs/pubspec_parse/test/test_utils.dart
new file mode 100644
index 0000000..cc46522
--- /dev/null
+++ b/pkgs/pubspec_parse/test/test_utils.dart
@@ -0,0 +1,157 @@
+// 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.
+
+import 'dart:convert';
+
+import 'package:checked_yaml/checked_yaml.dart';
+import 'package:json_annotation/json_annotation.dart';
+import 'package:pubspec_parse/pubspec_parse.dart';
+import 'package:stack_trace/stack_trace.dart';
+import 'package:test/test.dart';
+
+import 'pub_utils.dart';
+
+const defaultPubspec = {
+ 'name': 'sample',
+ 'environment': {'sdk': '>=2.12.0 <3.0.0'},
+};
+
+String _encodeJson(Object? input) =>
+ const JsonEncoder.withIndent(' ').convert(input);
+
+Matcher _throwsParsedYamlException(String prettyValue) => throwsA(
+ const TypeMatcher<ParsedYamlException>().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;
+ StackTrace? innerStack;
+
+ if (innerError is CheckedFromJsonException) {
+ final cfje = innerError;
+
+ if (cfje.innerError != null) {
+ innerError = cfje.innerError;
+ innerStack = cfje.innerStack;
+ }
+ }
+
+ if (innerError != null) {
+ final items = [innerError];
+ if (innerStack != null) {
+ items.add(Trace.format(innerStack));
+ }
+
+ final content =
+ LineSplitter.split(items.join('\n')).map((e) => ' $e').join('\n');
+
+ printOnFailure('Inner error details:\n$content');
+ }
+}
+
+Future<Pubspec> parse(
+ Object? content, {
+ bool quietOnError = false,
+ bool skipTryPub = false,
+ bool lenient = false,
+}) async {
+ final encoded = _encodeJson(content);
+
+ ProcResult? pubResult;
+ if (!skipTryPub) {
+ // ignore: deprecated_member_use
+ pubResult = await tryPub(encoded);
+ expect(pubResult, isNotNull);
+ }
+
+ try {
+ final value = Pubspec.parse(encoded, lenient: lenient);
+
+ if (pubResult != null) {
+ addTearDown(() {
+ expect(
+ pubResult!.cleanParse,
+ isTrue,
+ reason:
+ 'On success, parsing from the pub client should also succeed.',
+ );
+ });
+ }
+ return value;
+ } catch (e) {
+ if (pubResult != null) {
+ addTearDown(() {
+ expect(
+ pubResult!.cleanParse,
+ isFalse,
+ reason: 'On failure, parsing from the pub client should also fail.',
+ );
+ });
+ }
+ if (e is ParsedYamlException) {
+ if (!quietOnError) {
+ _printDebugParsedYamlException(e);
+ }
+ }
+ rethrow;
+ }
+}
+
+void expectParseThrows(
+ Object? content,
+ String expectedError, {
+ bool skipTryPub = false,
+ bool lenient = false,
+}) =>
+ expect(
+ () => parse(
+ content,
+ lenient: lenient,
+ quietOnError: true,
+ skipTryPub: skipTryPub,
+ ),
+ _throwsParsedYamlException(expectedError),
+ );
+
+void expectParseThrowsContaining(
+ Object? content,
+ String errorFragment, {
+ bool skipTryPub = false,
+ bool lenient = false,
+}) {
+ expect(
+ () => parse(
+ content,
+ lenient: lenient,
+ quietOnError: true,
+ skipTryPub: skipTryPub,
+ ),
+ _throwsParsedYamlExceptionContaining(errorFragment),
+ );
+}
+
+// ignore: prefer_expression_function_bodies
+Matcher _throwsParsedYamlExceptionContaining(String errorFragment) {
+ return throwsA(
+ const TypeMatcher<ParsedYamlException>().having(
+ (e) {
+ final message = e.formattedMessage;
+ printOnFailure("Actual error format:\nr'''\n$message'''");
+ _printDebugParsedYamlException(e);
+ return message;
+ },
+ 'formattedMessage',
+ contains(errorFragment),
+ ),
+ );
+}
diff --git a/pkgs/source_map_stack_trace/.gitignore b/pkgs/source_map_stack_trace/.gitignore
new file mode 100644
index 0000000..f73b2f9
--- /dev/null
+++ b/pkgs/source_map_stack_trace/.gitignore
@@ -0,0 +1,4 @@
+.dart_tool/
+.packages
+.pub/
+pubspec.lock
diff --git a/pkgs/source_map_stack_trace/.test_config b/pkgs/source_map_stack_trace/.test_config
new file mode 100644
index 0000000..531426a
--- /dev/null
+++ b/pkgs/source_map_stack_trace/.test_config
@@ -0,0 +1,5 @@
+{
+ "test_package": {
+ "platforms": ["vm"]
+ }
+}
\ No newline at end of file
diff --git a/pkgs/source_map_stack_trace/AUTHORS b/pkgs/source_map_stack_trace/AUTHORS
new file mode 100644
index 0000000..e8063a8
--- /dev/null
+++ b/pkgs/source_map_stack_trace/AUTHORS
@@ -0,0 +1,6 @@
+# Below is a list of people and organizations that have contributed
+# to the project. Names should be added to the list like so:
+#
+# Name/Organization <email address>
+
+Google Inc.
diff --git a/pkgs/source_map_stack_trace/CHANGELOG.md b/pkgs/source_map_stack_trace/CHANGELOG.md
new file mode 100644
index 0000000..00c66d9
--- /dev/null
+++ b/pkgs/source_map_stack_trace/CHANGELOG.md
@@ -0,0 +1,82 @@
+## 2.1.3-wip
+
+## 2.1.2
+
+* Require Dart 3.3.0
+* Move to `dart-lang/tools` monorepo.
+
+## 2.1.1
+
+* Populate the pubspec `repository` field.
+
+## 2.1.0
+
+* Stable release for null safety.
+* Require Dart 2.12
+
+## 2.0.0
+
+### Breaking Changes
+
+* Removed dependency on `package_resolver` and changed the apis to accept a
+ `Map<String, Uri>` which maps package names to the base uri to resolve the
+ `package:` uris for those packages.
+* The `sdkRoot` argument must be an `Uri`. Use `Uri.parse` for use
+ cases previously passing a `String`.
+* The deprecated `packageRoot` argument has been removed.
+
+## 1.1.5
+
+* Set max SDK version to `<3.0.0`.
+
+## 1.1.4
+
+* Support source maps that depend on the uri of the location to resolve spans
+ correctly.
+
+## 1.1.3
+
+* Add a missing dependency on `path`.
+
+## 1.1.2
+
+* Fix a typo in the previous fix.
+
+## 1.1.1
+
+* Don't crash if the `SyncPackageResolver` has no package information at all.
+
+## 1.1.0
+
+* `mapStackTrace()` now uses a `SyncPackageResolver` object from the
+ [`package_resolver` package][package_resolver] to recreate `package:` URIs.
+
+* **Deprecation**: the `packageRoot` parameter to `mapStackTrace` is deprecated
+ in favor of the `packageInfo` parameter described above. It will be removed in
+ a future release.
+
+[package_resolver]: https://pub.dartlang.org/packages/package_resolver
+
+## 1.0.5
+
+* Add compatibility for member names that include named arguments.
+
+## 1.0.4
+
+* Add compatibility for Dart 1.10-style name munging.
+
+## 1.0.3
+
+* Prefer "dart:" URLs to "package:" URLs.
+
+## 1.0.2
+
+* Fix an off-by-one bug that was causing line numbers to be slightly off.
+
+## 1.0.1
+
+* Don't crash when mapping stack chains.
+
+## 1.0.0
+
+* Initial release.
diff --git a/pkgs/source_map_stack_trace/LICENSE b/pkgs/source_map_stack_trace/LICENSE
new file mode 100644
index 0000000..633672a
--- /dev/null
+++ b/pkgs/source_map_stack_trace/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2015, 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.
diff --git a/pkgs/source_map_stack_trace/README.md b/pkgs/source_map_stack_trace/README.md
new file mode 100644
index 0000000..9b11612
--- /dev/null
+++ b/pkgs/source_map_stack_trace/README.md
@@ -0,0 +1,50 @@
+[](https://github.com/dart-lang/tools/actions/workflows/source_map_stack_trace.yml)
+[](https://pub.dev/packages/source_map_stack_trace)
+[](https://pub.dev/packages/source_map_stack_trace/publisher)
+
+`source_map_stack_trace` is a package for converting stack traces generated by
+dart2js-compiled JavaScript code into readable native Dart stack traces using
+source maps. For example:
+
+```dart
+import 'package:source_map_stack_trace/source_map_stack_trace.dart';
+
+void main() {
+ var jsTrace = // Get a StackTrace generated by dart2js.
+ var mapping = // Get a source map mapping the JS to the Dart source.
+
+ // Convert jsTrace to refer to the Dart source instead.
+ var dartTrace = mapStackTrace(jsTrace, sourceMap);
+ print(dartTrace);
+}
+```
+
+This can convert the following JavaScript trace:
+
+```
+expect_async_test.dart.browser_test.dart.js 2636:15 dart.wrapException
+expect_async_test.dart.browser_test.dart.js 14661:15 main__closure16.call$0
+expect_async_test.dart.browser_test.dart.js 18237:26 Declarer_test__closure.call$1
+expect_async_test.dart.browser_test.dart.js 17905:23 StackZoneSpecification_registerUnaryCallback__closure.call$0
+expect_async_test.dart.browser_test.dart.js 17876:16 StackZoneSpecification._stack_zone_specification$_run$2
+expect_async_test.dart.browser_test.dart.js 17899:26 StackZoneSpecification_registerUnaryCallback_closure.call$1
+expect_async_test.dart.browser_test.dart.js 6115:16 _rootRunUnary
+expect_async_test.dart.browser_test.dart.js 8576:39 _CustomZone.runUnary$2
+expect_async_test.dart.browser_test.dart.js 7135:57 _Future__propagateToListeners_handleValueCallback.call$0
+expect_async_test.dart.browser_test.dart.js 7031:147 dart._Future.static._Future__propagateToListeners
+```
+
+to:
+
+```
+dart:_internal/compiler/js_lib/js_helper.dart 1210:1 wrapException
+test/frontend/expect_async_test.dart 24:5 main.<fn>.<fn>
+package:test/src/backend/declarer.dart 45:48 Declarer.test.<fn>.<fn>
+package:stack_trace/src/stack_zone_specification.dart 134:30 StackZoneSpecification.registerUnaryCallback.<fn>.<fn>
+package:stack_trace/src/stack_zone_specification.dart 210:7 StackZoneSpecification._run
+package:stack_trace/src/stack_zone_specification.dart 135:5 StackZoneSpecification.registerUnaryCallback.<fn>
+dart:async/zone.dart 904:14 _rootRunUnary
+dart:async/zone.dart 806:3 _CustomZone.runUnary
+dart:async/future_impl.dart 486:13 _Future._propagateToListeners.handleValueCallback
+dart:async/future_impl.dart 567:32 _Future._propagateToListeners
+```
diff --git a/pkgs/source_map_stack_trace/analysis_options.yaml b/pkgs/source_map_stack_trace/analysis_options.yaml
new file mode 100644
index 0000000..5ee158a
--- /dev/null
+++ b/pkgs/source_map_stack_trace/analysis_options.yaml
@@ -0,0 +1,7 @@
+# https://dart.dev/tools/analysis#the-analysis-options-file
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-casts: true
+ strict-raw-types: true
diff --git a/pkgs/source_map_stack_trace/lib/source_map_stack_trace.dart b/pkgs/source_map_stack_trace/lib/source_map_stack_trace.dart
new file mode 100644
index 0000000..a3925cd
--- /dev/null
+++ b/pkgs/source_map_stack_trace/lib/source_map_stack_trace.dart
@@ -0,0 +1,104 @@
+// Copyright (c) 2015, 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:path/path.dart' as p;
+import 'package:source_maps/source_maps.dart';
+import 'package:stack_trace/stack_trace.dart';
+
+/// Convert [stackTrace], a stack trace generated by dart2js-compiled
+/// JavaScript, to a native-looking stack trace using [sourceMap].
+///
+/// [minified] indicates whether or not the dart2js code was minified. If it
+/// hasn't, this tries to clean up the stack frame member names.
+///
+/// The [packageMap] maps package names to the base uri used to resolve the
+/// `package:` uris for those packages. It is used to it's used to reconstruct
+/// `package:` URIs for stack frames that come from packages.
+///
+/// [sdkRoot] is the URI surfaced in the stack traces for SDK libraries.
+/// If it's passed, stack frames from the SDK will have `dart:` URLs.
+StackTrace mapStackTrace(Mapping sourceMap, StackTrace stackTrace,
+ {bool minified = false, Map<String, Uri>? packageMap, Uri? sdkRoot}) {
+ if (stackTrace is Chain) {
+ return Chain(stackTrace.traces.map((trace) {
+ return Trace.from(mapStackTrace(sourceMap, trace,
+ minified: minified, packageMap: packageMap, sdkRoot: sdkRoot));
+ }));
+ }
+
+ var sdkLib = sdkRoot == null ? null : '$sdkRoot/lib';
+
+ var trace = Trace.from(stackTrace);
+ return Trace(trace.frames.map((frame) {
+ var line = frame.line;
+ // If there's no line information, there's no way to translate this frame.
+ // We could return it as-is, but these lines are usually not useful anyways.
+ if (line == null) return null;
+
+ // If there's no column, try using the first column of the line.
+ var column = frame.column ?? 0;
+
+ // Subtract 1 because stack traces use 1-indexed lines and columns and
+ // source maps uses 0-indexed.
+ var span =
+ sourceMap.spanFor(line - 1, column - 1, uri: frame.uri.toString());
+
+ // If we can't find a source span, ignore the frame. It's probably something
+ // internal that the user doesn't care about.
+ if (span == null) return null;
+
+ var sourceUrl = span.sourceUrl.toString();
+ if (sdkLib != null && p.url.isWithin(sdkLib, sourceUrl)) {
+ sourceUrl = 'dart:${p.url.relative(sourceUrl, from: sdkLib)}';
+ } else if (packageMap != null) {
+ for (var package in packageMap.keys) {
+ var packageUrl = packageMap[package].toString();
+ if (!p.url.isWithin(packageUrl, sourceUrl)) continue;
+
+ sourceUrl =
+ 'package:$package/${p.url.relative(sourceUrl, from: packageUrl)}';
+ break;
+ }
+ }
+
+ return Frame(
+ Uri.parse(sourceUrl),
+ span.start.line + 1,
+ span.start.column + 1,
+ // If the dart2js output is minified, there's no use trying to prettify
+ // its member names. Use the span's identifier if available, otherwise
+ // use the minified member name.
+ minified
+ ? (span.isIdentifier ? span.text : frame.member)
+ : _prettifyMember(frame.member!));
+ }).whereType<Frame>());
+}
+
+/// Reformats a JS member name to make it look more Dart-like.
+String _prettifyMember(String member) {
+ return member
+ // Get rid of the noise that Firefox sometimes adds.
+ .replaceAll(RegExp(r'/?<$'), '')
+ // Get rid of arity indicators and named arguments.
+ .replaceAll(RegExp(r'\$\d+(\$[a-zA-Z_0-9]+)*$'), '')
+ // Convert closures to <fn>.
+ .replaceAllMapped(
+ RegExp(r'(_+)closure\d*\.call$'),
+ // The number of underscores before "closure" indicates how nested it
+ // is.
+ (match) => '.<fn>' * match[1]!.length)
+ // Get rid of explicitly-generated calls.
+ .replaceAll(RegExp(r'\.call$'), '')
+ // Get rid of the top-level method prefix.
+ .replaceAll(RegExp(r'^dart\.'), '')
+ // Get rid of library namespaces.
+ .replaceAll(RegExp(r'[a-zA-Z_0-9]+\$'), '')
+ // Get rid of the static method prefix. The class name also exists in the
+ // invocation, so we're not getting rid of any information.
+ .replaceAll(RegExp(r'^[a-zA-Z_0-9]+.(static|dart).'), '')
+ // Convert underscores after identifiers to dots. This runs the risk of
+ // incorrectly converting members that contain underscores, but those are
+ // contrary to the style guide anyway.
+ .replaceAllMapped(RegExp(r'([a-zA-Z0-9]+)_'), (match) => '${match[1]!}.');
+}
diff --git a/pkgs/source_map_stack_trace/pubspec.yaml b/pkgs/source_map_stack_trace/pubspec.yaml
new file mode 100644
index 0000000..4e67109
--- /dev/null
+++ b/pkgs/source_map_stack_trace/pubspec.yaml
@@ -0,0 +1,18 @@
+name: source_map_stack_trace
+version: 2.1.3-wip
+description: A package for applying source maps to stack traces.
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/source_map_stack_trace
+issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Asource_map_stack_trace
+
+environment:
+ sdk: ^3.3.0
+
+dependencies:
+ path: ^1.8.0
+ source_maps: ^0.10.10
+ stack_trace: ^1.10.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ source_span: ^1.8.0
+ test: ^1.16.0
diff --git a/pkgs/source_map_stack_trace/test/source_map_stack_trace_test.dart b/pkgs/source_map_stack_trace/test/source_map_stack_trace_test.dart
new file mode 100644
index 0000000..dd4d710
--- /dev/null
+++ b/pkgs/source_map_stack_trace/test/source_map_stack_trace_test.dart
@@ -0,0 +1,264 @@
+// Copyright (c) 2015, 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:path/path.dart' as p;
+import 'package:source_map_stack_trace/source_map_stack_trace.dart';
+import 'package:source_maps/source_maps.dart';
+import 'package:source_span/source_span.dart';
+import 'package:stack_trace/stack_trace.dart';
+import 'package:test/test.dart';
+
+/// A simple [Mapping] for tests that don't need anything special.
+final _simpleMapping = parseJson((SourceMapBuilder()
+ ..addSpan(
+ SourceMapSpan.identifier(
+ SourceLocation(1, line: 1, column: 3, sourceUrl: 'foo.dart'),
+ 'qux'),
+ SourceSpan(SourceLocation(8, line: 5, column: 0),
+ SourceLocation(18, line: 15, column: 0), '\n' * 10)))
+ .build('foo.dart.js.map'));
+
+final _packageMap = {
+ 'bar': Uri.parse('packages/bar'),
+ 'foo': Uri.parse('packages/foo'),
+};
+
+void main() {
+ test('maps a JS line and column to a Dart line and span', () {
+ var trace = Trace.parse('foo.dart.js 10:11 foo');
+ var frame = _mapTrace(_simpleMapping, trace).frames.first;
+ expect(frame.uri, equals(Uri.parse('foo.dart')));
+
+ // These are +1 because stack_trace is 1-based whereas source_span is
+ // 0-basd.
+ expect(frame.line, equals(2));
+ expect(frame.column, equals(4));
+ });
+
+ test('ignores JS frames without line info', () {
+ var trace = Trace.parse('''
+foo.dart.js 10:11 foo
+foo.dart.js bar
+foo.dart.js 10:11 baz
+''');
+ var frames = _mapTrace(_simpleMapping, trace).frames;
+
+ expect(frames.length, equals(2));
+ expect(frames.first.member, equals('foo'));
+ expect(frames.last.member, equals('baz'));
+ });
+
+ test('ignores JS frames without corresponding spans', () {
+ var trace = Trace.parse('''
+foo.dart.js 10:11 foo
+foo.dart.js 1:1 bar
+foo.dart.js 10:11 baz
+''');
+
+ var frames = _mapTrace(_simpleMapping, trace).frames;
+
+ expect(frames.length, equals(2));
+ expect(frames.first.member, equals('foo'));
+ expect(frames.last.member, equals('baz'));
+ });
+
+ test('include frames from JS files not covered by the source map bundle', () {
+ var trace = Trace.parse('''
+foo.dart.js 10:11 foo
+jquery.js 10:1 foo
+bar.dart.js 10:11 foo
+''');
+ var builder = SourceMapBuilder()
+ ..addSpan(
+ SourceMapSpan.identifier(
+ SourceLocation(1,
+ line: 1, column: 3, sourceUrl: 'packages/foo/foo.dart'),
+ 'qux'),
+ SourceSpan(SourceLocation(8, line: 5, column: 0),
+ SourceLocation(12, line: 9, column: 1), '\n' * 4));
+ var sourceMapJson1 = builder.build('foo.dart.js.map');
+ sourceMapJson1['file'] = 'foo.dart.js';
+
+ builder = SourceMapBuilder()
+ ..addSpan(
+ SourceMapSpan.identifier(
+ SourceLocation(1,
+ line: 1, column: 3, sourceUrl: 'packages/bar/bar.dart'),
+ 'qux'),
+ SourceSpan(SourceLocation(8, line: 5, column: 0),
+ SourceLocation(12, line: 9, column: 1), '\n' * 4));
+ var sourceMapJson2 = builder.build('bar.dart.js.map');
+ sourceMapJson2['file'] = 'bar.dart.js';
+
+ var bundle = [sourceMapJson1, sourceMapJson2];
+ var mapping = parseJsonExtended(bundle);
+ var frames = _mapTrace(mapping, trace, packageMap: _packageMap).frames;
+
+ expect(frames.length, equals(3));
+
+ var frame = frames[0];
+ expect(frame.uri, equals(Uri.parse('package:foo/foo.dart')));
+ expect(frame.line, equals(2));
+ expect(frame.column, equals(4));
+
+ frame = frames[1];
+ expect(p.basename(frame.uri.toString()), equals('jquery.js'));
+ expect(frame.line, equals(10));
+
+ frame = frames[2];
+ expect(frame.uri, equals(Uri.parse('package:bar/bar.dart')));
+ expect(frame.line, equals(2));
+ expect(frame.column, equals(4));
+ });
+
+ test('falls back to column 0 for unlisted column', () {
+ var trace = Trace.parse('foo.dart.js 10 foo');
+ var builder = SourceMapBuilder()
+ ..addSpan(
+ SourceMapSpan.identifier(
+ SourceLocation(1, line: 1, column: 3, sourceUrl: 'foo.dart'),
+ 'qux'),
+ SourceSpan(SourceLocation(8, line: 5, column: 0),
+ SourceLocation(12, line: 9, column: 1), '\n' * 4));
+
+ var mapping = parseJson(builder.build('foo.dart.js.map'));
+ var frame = _mapTrace(mapping, trace).frames.first;
+ expect(frame.uri, equals(Uri.parse('foo.dart')));
+ expect(frame.line, equals(2));
+ expect(frame.column, equals(4));
+ });
+
+ test('uses package: URIs for frames within a packageResolver.packageMap URL',
+ () {
+ var trace = Trace.parse('foo.dart.js 10 foo');
+ var builder = SourceMapBuilder()
+ ..addSpan(
+ SourceMapSpan.identifier(
+ SourceLocation(1,
+ line: 1, column: 3, sourceUrl: 'packages/foo/foo.dart'),
+ 'qux'),
+ SourceSpan(SourceLocation(8, line: 5, column: 0),
+ SourceLocation(12, line: 9, column: 1), '\n' * 4));
+
+ var mapping = parseJson(builder.build('foo.dart.js.map'));
+ var mappedTrace = _mapTrace(mapping, trace, packageMap: _packageMap);
+ var frame = mappedTrace.frames.first;
+ expect(frame.uri, equals(Uri.parse('package:foo/foo.dart')));
+ expect(frame.line, equals(2));
+ expect(frame.column, equals(4));
+ });
+
+ test('uses dart: URIs for frames within sdkRoot', () {
+ var trace = Trace.parse('foo.dart.js 10 foo');
+ var builder = SourceMapBuilder()
+ ..addSpan(
+ SourceMapSpan.identifier(
+ SourceLocation(1,
+ line: 1, column: 3, sourceUrl: 'sdk/lib/async/foo.dart'),
+ 'qux'),
+ SourceSpan(SourceLocation(8, line: 5, column: 0),
+ SourceLocation(12, line: 9, column: 1), '\n' * 4));
+
+ var mapping = parseJson(builder.build('foo.dart.js.map'));
+ var frame =
+ _mapTrace(mapping, trace, sdkRoot: Uri.parse('sdk/')).frames.first;
+ expect(frame.uri, equals(Uri.parse('dart:async/foo.dart')));
+ expect(frame.line, equals(2));
+ expect(frame.column, equals(4));
+ });
+
+ test('converts a stack chain', () {
+ var trace = Chain([
+ Trace.parse('foo.dart.js 10:11 foo'),
+ Trace.parse('foo.dart.js 10:11 bar')
+ ]);
+ var traces = _mapChain(_simpleMapping, trace).traces;
+
+ var frame = traces.first.frames.single;
+ expect(frame.uri, equals(Uri.parse('foo.dart')));
+ expect(frame.member, equals('foo'));
+ expect(frame.line, equals(2));
+ expect(frame.column, equals(4));
+
+ frame = traces.last.frames.single;
+ expect(frame.uri, equals(Uri.parse('foo.dart')));
+ expect(frame.member, equals('bar'));
+ expect(frame.line, equals(2));
+ expect(frame.column, equals(4));
+ });
+
+ group('cleans up', () {
+ test('Firefox junk', () {
+ expect(_prettify('foo/<'), equals('foo'));
+ expect(_prettify('foo<'), equals('foo'));
+ });
+
+ test('arity indicators', () {
+ expect(_prettify(r'foo$1'), equals('foo'));
+ expect(_prettify(r'foo$1234'), equals('foo'));
+ });
+
+ test('named arguments', () {
+ expect(_prettify(r'foo$1$bar'), equals('foo'));
+ expect(_prettify(r'foo$123$bar$bang$qux'), equals('foo'));
+ });
+
+ test('closures', () {
+ expect(_prettify('foo_closure.call'), equals('foo.<fn>'));
+ });
+
+ test('nested closures', () {
+ expect(_prettify('foo__closure.call'), equals('foo.<fn>.<fn>'));
+ expect(
+ _prettify('foo____closure.call'), equals('foo.<fn>.<fn>.<fn>.<fn>'));
+ });
+
+ test('.call', () {
+ expect(_prettify('foo.call'), equals('foo'));
+ });
+
+ test('top-level functions', () {
+ expect(_prettify('dart.foo'), equals('foo'));
+ });
+
+ test('library namespaces', () {
+ expect(_prettify(r'my_library$foo'), equals('foo'));
+ });
+
+ test('static methods', () {
+ expect(_prettify(r'Foo.static.foo'), equals('foo'));
+ });
+
+ test('instance methods', () {
+ expect(_prettify(r'Foo_bar__baz'), equals('Foo.bar._baz'));
+ });
+
+ test('lots of stuff', () {
+ expect(_prettify(r'lib$Foo.static.lib$Foo_closure.call$0/<'),
+ equals('Foo.<fn>'));
+ });
+ });
+}
+
+/// Like [mapStackTrace], but is guaranteed to return a [Trace] so it can be
+/// inspected.
+Trace _mapTrace(Mapping sourceMap, StackTrace stackTrace,
+ {bool minified = false, Map<String, Uri>? packageMap, Uri? sdkRoot}) {
+ return Trace.from(mapStackTrace(sourceMap, stackTrace,
+ minified: minified, packageMap: packageMap, sdkRoot: sdkRoot));
+}
+
+/// Like [mapStackTrace], but is guaranteed to return a [Chain] so it can be
+/// inspected.
+Chain _mapChain(Mapping sourceMap, StackTrace stackTrace,
+ {bool minified = false, Map<String, Uri>? packageMap, Uri? sdkRoot}) {
+ return Chain.forTrace(mapStackTrace(sourceMap, stackTrace,
+ minified: minified, packageMap: packageMap, sdkRoot: sdkRoot));
+}
+
+/// Runs the mapper's prettification logic on [member] and returns the result.
+String? _prettify(String member) {
+ var trace = Trace([Frame(Uri.parse('foo.dart.js'), 10, 11, member)]);
+ return _mapTrace(_simpleMapping, trace).frames.first.member;
+}
diff --git a/pkgs/source_maps/.gitignore b/pkgs/source_maps/.gitignore
new file mode 100644
index 0000000..f73b2f9
--- /dev/null
+++ b/pkgs/source_maps/.gitignore
@@ -0,0 +1,4 @@
+.dart_tool/
+.packages
+.pub/
+pubspec.lock
diff --git a/pkgs/source_maps/CHANGELOG.md b/pkgs/source_maps/CHANGELOG.md
new file mode 100644
index 0000000..b06ac72
--- /dev/null
+++ b/pkgs/source_maps/CHANGELOG.md
@@ -0,0 +1,133 @@
+## 0.10.14-wip
+
+## 0.10.13
+
+* Require Dart 3.3
+* Move to `dart-lang/tools` monorepo.
+
+## 0.10.12
+
+* Add additional types at API boundaries.
+
+## 0.10.11
+
+* Populate the pubspec `repository` field.
+* Update the source map documentation link in the readme.
+
+## 0.10.10
+
+* Stable release for null safety.
+
+## 0.10.9
+
+* Fix a number of document comment issues.
+* Allow parsing source map files with a missing `names` field.
+
+## 0.10.8
+
+* Preserve source-map extensions in `SingleMapping`. Extensions are keys in the
+ json map that start with `"x_"`.
+
+## 0.10.7
+
+* Set max SDK version to `<3.0.0`, and adjust other dependencies.
+
+## 0.10.6
+
+* Require version 2.0.0 of the Dart SDK.
+
+## 0.10.5
+
+* Add a `SingleMapping.files` field which provides access to `SourceFile`s
+ representing the `"sourcesContent"` fields in the source map.
+
+* Add an `includeSourceContents` flag to `SingleMapping.toJson()` which
+ indicates whether to include source file contents in the source map.
+
+## 0.10.4
+* Implement `highlight` in `SourceMapFileSpan`.
+* Require version `^1.3.0` of `source_span`.
+
+## 0.10.3
+ * Add `addMapping` and `containsMapping` members to `MappingBundle`.
+
+## 0.10.2
+ * Support for extended source map format.
+ * Polish `MappingBundle.spanFor` handling of URIs that have a suffix that
+ exactly match a source map in the MappingBundle.
+
+## 0.10.1+5
+ * Fix strong mode warning in test.
+
+## 0.10.1+4
+
+* Extend `MappingBundle.spanFor` to accept requests for output files that
+ don't have source maps.
+
+## 0.10.1+3
+
+* Add `MappingBundle` class that handles extended source map format that
+ supports source maps for multiple output files in a single mapper.
+ Extend `Mapping.spanFor` API to accept a uri parameter that is optional
+ for normal source maps but required for MappingBundle source maps.
+
+## 0.10.1+2
+
+* Fix more strong mode warnings.
+
+## 0.10.1+1
+
+* Fix all strong mode warnings.
+
+## 0.10.1
+
+* Add a `mapUrl` named argument to `parse` and `parseJson`. This argument is
+ used to resolve source URLs for source spans.
+
+## 0.10.0+2
+
+* Fix analyzer error (FileSpan has a new field since `source_span` 1.1.1)
+
+## 0.10.0+1
+
+* Remove an unnecessary warning printed when the "file" field is missing from a
+ Json formatted source map. This field is optional and its absence is not
+ unusual.
+
+## 0.10.0
+
+* Remove the `Span`, `Location` and `SourceFile` classes. Use the
+ corresponding `source_span` classes instead.
+
+## 0.9.4
+
+* Update `SpanFormatException` with `source` and `offset`.
+
+* All methods that take `Span`s, `Location`s, and `SourceFile`s as inputs now
+ also accept the corresponding `source_span` classes as well. Using the old
+ classes is now deprecated and will be unsupported in version 0.10.0.
+
+## 0.9.3
+
+* Support writing SingleMapping objects to source map version 3 format.
+* Support the `sourceRoot` field in the SingleMapping class.
+* Support updating the `targetUrl` field in the SingleMapping class.
+
+## 0.9.2+2
+
+* Fix a bug in `FixedSpan.getLocationMessage`.
+
+## 0.9.2+1
+
+* Minor readability improvements to `FixedSpan.getLocationMessage` and
+ `SpanException.toString`.
+
+## 0.9.2
+
+* Add `SpanException` and `SpanFormatException` classes.
+
+## 0.9.1
+
+* Support unmapped areas in source maps.
+
+* Increase the readability of location messages.
diff --git a/pkgs/source_maps/LICENSE b/pkgs/source_maps/LICENSE
new file mode 100644
index 0000000..162572a
--- /dev/null
+++ b/pkgs/source_maps/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2014, 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.
diff --git a/pkgs/source_maps/README.md b/pkgs/source_maps/README.md
new file mode 100644
index 0000000..cf80291
--- /dev/null
+++ b/pkgs/source_maps/README.md
@@ -0,0 +1,25 @@
+[](https://github.com/dart-lang/tools/actions/workflows/source_maps.yaml)
+[](https://pub.dev/packages/source_maps)
+[](https://pub.dev/packages/source_maps/publisher)
+
+This project implements a Dart pub package to work with source maps.
+
+## Docs and usage
+
+The implementation is based on the [source map version 3 spec][spec] which was
+originated from the [Closure Compiler][closure] and has been implemented in
+Chrome and Firefox.
+
+In this package we provide:
+
+ * Data types defining file locations and spans: these are not part of the
+ original source map specification. These data types are great for tracking
+ source locations on source maps, but they can also be used by tools to
+ reporting useful error messages that include on source locations.
+ * A builder that creates a source map programmatically and produces the encoded
+ source map format.
+ * A parser that reads the source map format and provides APIs to read the
+ mapping information.
+
+[closure]: https://github.com/google/closure-compiler/wiki/Source-Maps
+[spec]: https://docs.google.com/a/google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit
diff --git a/pkgs/source_maps/analysis_options.yaml b/pkgs/source_maps/analysis_options.yaml
new file mode 100644
index 0000000..d978f81
--- /dev/null
+++ b/pkgs/source_maps/analysis_options.yaml
@@ -0,0 +1 @@
+include: package:dart_flutter_team_lints/analysis_options.yaml
diff --git a/pkgs/source_maps/lib/builder.dart b/pkgs/source_maps/lib/builder.dart
new file mode 100644
index 0000000..9043c63
--- /dev/null
+++ b/pkgs/source_maps/lib/builder.dart
@@ -0,0 +1,84 @@
+// 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.
+
+/// Contains a builder object useful for creating source maps programatically.
+library;
+
+// TODO(sigmund): add a builder for multi-section mappings.
+
+import 'dart:convert';
+
+import 'package:source_span/source_span.dart';
+
+import 'parser.dart';
+import 'src/source_map_span.dart';
+
+/// Builds a source map given a set of mappings.
+class SourceMapBuilder {
+ final List<Entry> _entries = <Entry>[];
+
+ /// Adds an entry mapping the [targetOffset] to [source].
+ void addFromOffset(SourceLocation source, SourceFile targetFile,
+ int targetOffset, String identifier) {
+ ArgumentError.checkNotNull(targetFile, 'targetFile');
+ _entries.add(Entry(source, targetFile.location(targetOffset), identifier));
+ }
+
+ /// Adds an entry mapping [target] to [source].
+ ///
+ /// If [isIdentifier] is true or if [target] is a [SourceMapSpan] with
+ /// `isIdentifier` set to true, this entry is considered to represent an
+ /// identifier whose value will be stored in the source map. [isIdentifier]
+ /// takes precedence over [target]'s `isIdentifier` value.
+ void addSpan(SourceSpan source, SourceSpan target, {bool? isIdentifier}) {
+ isIdentifier ??= source is SourceMapSpan ? source.isIdentifier : false;
+
+ var name = isIdentifier ? source.text : null;
+ _entries.add(Entry(source.start, target.start, name));
+ }
+
+ /// Adds an entry mapping [target] to [source].
+ void addLocation(
+ SourceLocation source, SourceLocation target, String? identifier) {
+ _entries.add(Entry(source, target, identifier));
+ }
+
+ /// Encodes all mappings added to this builder as a json map.
+ Map<String, dynamic> build(String fileUrl) {
+ return SingleMapping.fromEntries(_entries, fileUrl).toJson();
+ }
+
+ /// Encodes all mappings added to this builder as a json string.
+ String toJson(String fileUrl) => jsonEncode(build(fileUrl));
+}
+
+/// An entry in the source map builder.
+class Entry implements Comparable<Entry> {
+ /// Span denoting the original location in the input source file
+ final SourceLocation source;
+
+ /// Span indicating the corresponding location in the target file.
+ final SourceLocation target;
+
+ /// An identifier name, when this location is the start of an identifier.
+ final String? identifierName;
+
+ /// Creates a new [Entry] mapping [target] to [source].
+ Entry(this.source, this.target, this.identifierName);
+
+ /// Implements [Comparable] to ensure that entries are ordered by their
+ /// location in the target file. We sort primarily by the target offset
+ /// because source map files are encoded by printing each mapping in order as
+ /// they appear in the target file.
+ @override
+ int compareTo(Entry other) {
+ var res = target.compareTo(other.target);
+ if (res != 0) return res;
+ res = source.sourceUrl
+ .toString()
+ .compareTo(other.source.sourceUrl.toString());
+ if (res != 0) return res;
+ return source.compareTo(other.source);
+ }
+}
diff --git a/pkgs/source_maps/lib/parser.dart b/pkgs/source_maps/lib/parser.dart
new file mode 100644
index 0000000..590dfc6
--- /dev/null
+++ b/pkgs/source_maps/lib/parser.dart
@@ -0,0 +1,718 @@
+// 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.
+
+/// Contains the top-level function to parse source maps version 3.
+library;
+
+import 'dart:convert';
+
+import 'package:source_span/source_span.dart';
+
+import 'builder.dart' as builder;
+import 'src/source_map_span.dart';
+import 'src/utils.dart';
+import 'src/vlq.dart';
+
+/// Parses a source map directly from a json string.
+///
+/// [mapUrl], which may be either a [String] or a [Uri], indicates the URL of
+/// the source map file itself. If it's passed, any URLs in the source
+/// map will be interpreted as relative to this URL when generating spans.
+// TODO(sigmund): evaluate whether other maps should have the json parsed, or
+// the string represenation.
+// TODO(tjblasi): Ignore the first line of [jsonMap] if the JSON safety string
+// `)]}'` begins the string representation of the map.
+Mapping parse(String jsonMap,
+ {Map<String, Map>? otherMaps, /*String|Uri*/ Object? mapUrl}) =>
+ parseJson(jsonDecode(jsonMap) as Map, otherMaps: otherMaps, mapUrl: mapUrl);
+
+/// Parses a source map or source map bundle directly from a json string.
+///
+/// [mapUrl], which may be either a [String] or a [Uri], indicates the URL of
+/// the source map file itself. If it's passed, any URLs in the source
+/// map will be interpreted as relative to this URL when generating spans.
+Mapping parseExtended(String jsonMap,
+ {Map<String, Map>? otherMaps, /*String|Uri*/ Object? mapUrl}) =>
+ parseJsonExtended(jsonDecode(jsonMap),
+ otherMaps: otherMaps, mapUrl: mapUrl);
+
+/// Parses a source map or source map bundle.
+///
+/// [mapUrl], which may be either a [String] or a [Uri], indicates the URL of
+/// the source map file itself. If it's passed, any URLs in the source
+/// map will be interpreted as relative to this URL when generating spans.
+Mapping parseJsonExtended(/*List|Map*/ Object? json,
+ {Map<String, Map>? otherMaps, /*String|Uri*/ Object? mapUrl}) {
+ if (json is List) {
+ return MappingBundle.fromJson(json, mapUrl: mapUrl);
+ }
+ return parseJson(json as Map);
+}
+
+/// Parses a source map.
+///
+/// [mapUrl], which may be either a [String] or a [Uri], indicates the URL of
+/// the source map file itself. If it's passed, any URLs in the source
+/// map will be interpreted as relative to this URL when generating spans.
+Mapping parseJson(Map map,
+ {Map<String, Map>? otherMaps, /*String|Uri*/ Object? mapUrl}) {
+ if (map['version'] != 3) {
+ throw ArgumentError('unexpected source map version: ${map["version"]}. '
+ 'Only version 3 is supported.');
+ }
+
+ if (map.containsKey('sections')) {
+ if (map.containsKey('mappings') ||
+ map.containsKey('sources') ||
+ map.containsKey('names')) {
+ throw const FormatException('map containing "sections" '
+ 'cannot contain "mappings", "sources", or "names".');
+ }
+ return MultiSectionMapping.fromJson(map['sections'] as List, otherMaps,
+ mapUrl: mapUrl);
+ }
+ return SingleMapping.fromJson(map.cast<String, dynamic>(), mapUrl: mapUrl);
+}
+
+/// A mapping parsed out of a source map.
+abstract class Mapping {
+ /// Returns the span associated with [line] and [column].
+ ///
+ /// [uri] is the optional location of the output file to find the span for
+ /// to disambiguate cases where a mapping may have different mappings for
+ /// different output files.
+ SourceMapSpan? spanFor(int line, int column,
+ {Map<String, SourceFile>? files, String? uri});
+
+ /// Returns the span associated with [location].
+ SourceMapSpan? spanForLocation(SourceLocation location,
+ {Map<String, SourceFile>? files}) {
+ return spanFor(location.line, location.column,
+ uri: location.sourceUrl?.toString(), files: files);
+ }
+}
+
+/// A meta-level map containing sections.
+class MultiSectionMapping extends Mapping {
+ /// For each section, the start line offset.
+ final List<int> _lineStart = <int>[];
+
+ /// For each section, the start column offset.
+ final List<int> _columnStart = <int>[];
+
+ /// For each section, the actual source map information, which is not adjusted
+ /// for offsets.
+ final List<Mapping> _maps = <Mapping>[];
+
+ /// Creates a section mapping from json.
+ MultiSectionMapping.fromJson(List sections, Map<String, Map>? otherMaps,
+ {/*String|Uri*/ Object? mapUrl}) {
+ for (var section in sections.cast<Map>()) {
+ var offset = section['offset'] as Map?;
+ if (offset == null) throw const FormatException('section missing offset');
+
+ var line = offset['line'] as int?;
+ if (line == null) throw const FormatException('offset missing line');
+
+ var column = offset['column'] as int?;
+ if (column == null) throw const FormatException('offset missing column');
+
+ _lineStart.add(line);
+ _columnStart.add(column);
+
+ var url = section['url'] as String?;
+ var map = section['map'] as Map?;
+
+ if (url != null && map != null) {
+ throw const FormatException(
+ "section can't use both url and map entries");
+ } else if (url != null) {
+ var other = otherMaps?[url];
+ if (otherMaps == null || other == null) {
+ throw FormatException(
+ 'section contains refers to $url, but no map was '
+ 'given for it. Make sure a map is passed in "otherMaps"');
+ }
+ _maps.add(parseJson(other, otherMaps: otherMaps, mapUrl: url));
+ } else if (map != null) {
+ _maps.add(parseJson(map, otherMaps: otherMaps, mapUrl: mapUrl));
+ } else {
+ throw const FormatException('section missing url or map');
+ }
+ }
+ if (_lineStart.isEmpty) {
+ throw const FormatException('expected at least one section');
+ }
+ }
+
+ int _indexFor(int line, int column) {
+ for (var i = 0; i < _lineStart.length; i++) {
+ if (line < _lineStart[i]) return i - 1;
+ if (line == _lineStart[i] && column < _columnStart[i]) return i - 1;
+ }
+ return _lineStart.length - 1;
+ }
+
+ @override
+ SourceMapSpan? spanFor(int line, int column,
+ {Map<String, SourceFile>? files, String? uri}) {
+ // TODO(jacobr): perhaps verify that targetUrl matches the actual uri
+ // or at least ends in the same file name.
+ var index = _indexFor(line, column);
+ return _maps[index].spanFor(
+ line - _lineStart[index], column - _columnStart[index],
+ files: files);
+ }
+
+ @override
+ String toString() {
+ var buff = StringBuffer('$runtimeType : [');
+ for (var i = 0; i < _lineStart.length; i++) {
+ buff
+ ..write('(')
+ ..write(_lineStart[i])
+ ..write(',')
+ ..write(_columnStart[i])
+ ..write(':')
+ ..write(_maps[i])
+ ..write(')');
+ }
+ buff.write(']');
+ return buff.toString();
+ }
+}
+
+class MappingBundle extends Mapping {
+ final Map<String, SingleMapping> _mappings = {};
+
+ MappingBundle();
+
+ MappingBundle.fromJson(List json, {/*String|Uri*/ Object? mapUrl}) {
+ for (var map in json) {
+ addMapping(parseJson(map as Map, mapUrl: mapUrl) as SingleMapping);
+ }
+ }
+
+ void addMapping(SingleMapping mapping) {
+ // TODO(jacobr): verify that targetUrl is valid uri instead of a windows
+ // path.
+ // TODO: Remove type arg https://github.com/dart-lang/sdk/issues/42227
+ var targetUrl = ArgumentError.checkNotNull<String>(
+ mapping.targetUrl, 'mapping.targetUrl');
+ _mappings[targetUrl] = mapping;
+ }
+
+ /// Encodes the Mapping mappings as a json map.
+ List toJson() => _mappings.values.map((v) => v.toJson()).toList();
+
+ @override
+ String toString() {
+ var buff = StringBuffer();
+ for (var map in _mappings.values) {
+ buff.write(map.toString());
+ }
+ return buff.toString();
+ }
+
+ bool containsMapping(String url) => _mappings.containsKey(url);
+
+ @override
+ SourceMapSpan? spanFor(int line, int column,
+ {Map<String, SourceFile>? files, String? uri}) {
+ // TODO: Remove type arg https://github.com/dart-lang/sdk/issues/42227
+ uri = ArgumentError.checkNotNull<String>(uri, 'uri');
+
+ // Find the longest suffix of the uri that matches the sourcemap
+ // where the suffix starts after a path segment boundary.
+ // We consider ":" and "/" as path segment boundaries so that
+ // "package:" uris can be handled with minimal special casing. Having a
+ // few false positive path segment boundaries is not a significant issue
+ // as we prefer the longest matching prefix.
+ // Using package:path `path.split` to find path segment boundaries would
+ // not generate all of the path segment boundaries we want for "package:"
+ // urls as "package:package_name" would be one path segment when we want
+ // "package" and "package_name" to be sepearate path segments.
+
+ var onBoundary = true;
+ var separatorCodeUnits = ['/'.codeUnitAt(0), ':'.codeUnitAt(0)];
+ for (var i = 0; i < uri.length; ++i) {
+ if (onBoundary) {
+ var candidate = uri.substring(i);
+ var candidateMapping = _mappings[candidate];
+ if (candidateMapping != null) {
+ return candidateMapping.spanFor(line, column,
+ files: files, uri: candidate);
+ }
+ }
+ onBoundary = separatorCodeUnits.contains(uri.codeUnitAt(i));
+ }
+
+ // Note: when there is no source map for an uri, this behaves like an
+ // identity function, returning the requested location as the result.
+
+ // Create a mock offset for the output location. We compute it in terms
+ // of the input line and column to minimize the chances that two different
+ // line and column locations are mapped to the same offset.
+ var offset = line * 1000000 + column;
+ var location = SourceLocation(offset,
+ line: line, column: column, sourceUrl: Uri.parse(uri));
+ return SourceMapSpan(location, location, '');
+ }
+}
+
+/// A map containing direct source mappings.
+class SingleMapping extends Mapping {
+ /// Source urls used in the mapping, indexed by id.
+ final List<String> urls;
+
+ /// Source names used in the mapping, indexed by id.
+ final List<String> names;
+
+ /// The [SourceFile]s to which the entries in [lines] refer.
+ ///
+ /// This is in the same order as [urls]. If this was constructed using
+ /// [SingleMapping.fromEntries], this contains files from any [FileLocation]s
+ /// used to build the mapping. If it was parsed from JSON, it contains files
+ /// for any sources whose contents were provided via the `"sourcesContent"`
+ /// field.
+ ///
+ /// Files whose contents aren't available are `null`.
+ final List<SourceFile?> files;
+
+ /// Entries indicating the beginning of each span.
+ final List<TargetLineEntry> lines;
+
+ /// Url of the target file.
+ String? targetUrl;
+
+ /// Source root prepended to all entries in [urls].
+ String? sourceRoot;
+
+ final Uri? _mapUrl;
+
+ final Map<String, dynamic> extensions;
+
+ SingleMapping._(this.targetUrl, this.files, this.urls, this.names, this.lines)
+ : _mapUrl = null,
+ extensions = {};
+
+ factory SingleMapping.fromEntries(Iterable<builder.Entry> entries,
+ [String? fileUrl]) {
+ // The entries needs to be sorted by the target offsets.
+ var sourceEntries = entries.toList()..sort();
+ var lines = <TargetLineEntry>[];
+
+ // Indices associated with file urls that will be part of the source map. We
+ // rely on map order so that `urls.keys[urls[u]] == u`
+ var urls = <String, int>{};
+
+ // Indices associated with identifiers that will be part of the source map.
+ // We rely on map order so that `names.keys[names[n]] == n`
+ var names = <String, int>{};
+
+ /// The file for each URL, indexed by [urls]' values.
+ var files = <int, SourceFile>{};
+
+ int? lineNum;
+ late List<TargetEntry> targetEntries;
+ for (var sourceEntry in sourceEntries) {
+ if (lineNum == null || sourceEntry.target.line > lineNum) {
+ lineNum = sourceEntry.target.line;
+ targetEntries = <TargetEntry>[];
+ lines.add(TargetLineEntry(lineNum, targetEntries));
+ }
+
+ var sourceUrl = sourceEntry.source.sourceUrl;
+ var urlId = urls.putIfAbsent(
+ sourceUrl == null ? '' : sourceUrl.toString(), () => urls.length);
+
+ if (sourceEntry.source is FileLocation) {
+ files.putIfAbsent(
+ urlId, () => (sourceEntry.source as FileLocation).file);
+ }
+
+ var sourceEntryIdentifierName = sourceEntry.identifierName;
+ var srcNameId = sourceEntryIdentifierName == null
+ ? null
+ : names.putIfAbsent(sourceEntryIdentifierName, () => names.length);
+ targetEntries.add(TargetEntry(sourceEntry.target.column, urlId,
+ sourceEntry.source.line, sourceEntry.source.column, srcNameId));
+ }
+ return SingleMapping._(fileUrl, urls.values.map((i) => files[i]).toList(),
+ urls.keys.toList(), names.keys.toList(), lines);
+ }
+
+ SingleMapping.fromJson(Map<String, dynamic> map, {Object? mapUrl})
+ : targetUrl = map['file'] as String?,
+ urls = List<String>.from(map['sources'] as List),
+ names = List<String>.from((map['names'] as List?) ?? []),
+ files = List.filled((map['sources'] as List).length, null),
+ sourceRoot = map['sourceRoot'] as String?,
+ lines = <TargetLineEntry>[],
+ _mapUrl = mapUrl is String ? Uri.parse(mapUrl) : (mapUrl as Uri?),
+ extensions = {} {
+ var sourcesContent = map['sourcesContent'] == null
+ ? const <String?>[]
+ : List<String?>.from(map['sourcesContent'] as List);
+ for (var i = 0; i < urls.length && i < sourcesContent.length; i++) {
+ var source = sourcesContent[i];
+ if (source == null) continue;
+ files[i] = SourceFile.fromString(source, url: urls[i]);
+ }
+
+ var line = 0;
+ var column = 0;
+ var srcUrlId = 0;
+ var srcLine = 0;
+ var srcColumn = 0;
+ var srcNameId = 0;
+ var tokenizer = _MappingTokenizer(map['mappings'] as String);
+ var entries = <TargetEntry>[];
+
+ while (tokenizer.hasTokens) {
+ if (tokenizer.nextKind.isNewLine) {
+ if (entries.isNotEmpty) {
+ lines.add(TargetLineEntry(line, entries));
+ entries = <TargetEntry>[];
+ }
+ line++;
+ column = 0;
+ tokenizer._consumeNewLine();
+ continue;
+ }
+
+ // Decode the next entry, using the previous encountered values to
+ // decode the relative values.
+ //
+ // We expect 1, 4, or 5 values. If present, values are expected in the
+ // following order:
+ // 0: the starting column in the current line of the generated file
+ // 1: the id of the original source file
+ // 2: the starting line in the original source
+ // 3: the starting column in the original source
+ // 4: the id of the original symbol name
+ // The values are relative to the previous encountered values.
+ if (tokenizer.nextKind.isNewSegment) throw _segmentError(0, line);
+ column += tokenizer._consumeValue();
+ if (!tokenizer.nextKind.isValue) {
+ entries.add(TargetEntry(column));
+ } else {
+ srcUrlId += tokenizer._consumeValue();
+ if (srcUrlId >= urls.length) {
+ throw StateError(
+ 'Invalid source url id. $targetUrl, $line, $srcUrlId');
+ }
+ if (!tokenizer.nextKind.isValue) throw _segmentError(2, line);
+ srcLine += tokenizer._consumeValue();
+ if (!tokenizer.nextKind.isValue) throw _segmentError(3, line);
+ srcColumn += tokenizer._consumeValue();
+ if (!tokenizer.nextKind.isValue) {
+ entries.add(TargetEntry(column, srcUrlId, srcLine, srcColumn));
+ } else {
+ srcNameId += tokenizer._consumeValue();
+ if (srcNameId >= names.length) {
+ throw StateError('Invalid name id: $targetUrl, $line, $srcNameId');
+ }
+ entries.add(
+ TargetEntry(column, srcUrlId, srcLine, srcColumn, srcNameId));
+ }
+ }
+ if (tokenizer.nextKind.isNewSegment) tokenizer._consumeNewSegment();
+ }
+ if (entries.isNotEmpty) {
+ lines.add(TargetLineEntry(line, entries));
+ }
+
+ map.forEach((name, value) {
+ if (name.startsWith('x_')) extensions[name] = value;
+ });
+ }
+
+ /// Encodes the Mapping mappings as a json map.
+ ///
+ /// If [includeSourceContents] is `true`, this includes the source file
+ /// contents from [files] in the map if possible.
+ Map<String, dynamic> toJson({bool includeSourceContents = false}) {
+ var buff = StringBuffer();
+ var line = 0;
+ var column = 0;
+ var srcLine = 0;
+ var srcColumn = 0;
+ var srcUrlId = 0;
+ var srcNameId = 0;
+ var first = true;
+
+ for (var entry in lines) {
+ var nextLine = entry.line;
+ if (nextLine > line) {
+ for (var i = line; i < nextLine; ++i) {
+ buff.write(';');
+ }
+ line = nextLine;
+ column = 0;
+ first = true;
+ }
+
+ for (var segment in entry.entries) {
+ if (!first) buff.write(',');
+ first = false;
+ column = _append(buff, column, segment.column);
+
+ // Encoding can be just the column offset if there is no source
+ // information.
+ var newUrlId = segment.sourceUrlId;
+ if (newUrlId == null) continue;
+ srcUrlId = _append(buff, srcUrlId, newUrlId);
+ srcLine = _append(buff, srcLine, segment.sourceLine!);
+ srcColumn = _append(buff, srcColumn, segment.sourceColumn!);
+
+ if (segment.sourceNameId == null) continue;
+ srcNameId = _append(buff, srcNameId, segment.sourceNameId!);
+ }
+ }
+
+ var result = <String, dynamic>{
+ 'version': 3,
+ 'sourceRoot': sourceRoot ?? '',
+ 'sources': urls,
+ 'names': names,
+ 'mappings': buff.toString(),
+ };
+ if (targetUrl != null) result['file'] = targetUrl!;
+
+ if (includeSourceContents) {
+ result['sourcesContent'] = files.map((file) => file?.getText(0)).toList();
+ }
+ extensions.forEach((name, value) => result[name] = value);
+
+ return result;
+ }
+
+ /// Appends to [buff] a VLQ encoding of [newValue] using the difference
+ /// between [oldValue] and [newValue]
+ static int _append(StringBuffer buff, int oldValue, int newValue) {
+ buff.writeAll(encodeVlq(newValue - oldValue));
+ return newValue;
+ }
+
+ StateError _segmentError(int seen, int line) =>
+ 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];
+ }
+
+ @override
+ SourceMapSpan? spanFor(int line, int column,
+ {Map<String, SourceFile>? files, String? uri}) {
+ var entry = _findColumn(line, column, _findLine(line));
+ if (entry == null) return null;
+
+ var sourceUrlId = entry.sourceUrlId;
+ if (sourceUrlId == null) return null;
+
+ var url = urls[sourceUrlId];
+ if (sourceRoot != null) {
+ url = '$sourceRoot$url';
+ }
+
+ var sourceNameId = entry.sourceNameId;
+ var file = files?[url];
+ if (file != null) {
+ var start = file.getOffset(entry.sourceLine!, entry.sourceColumn);
+ if (sourceNameId != null) {
+ var text = names[sourceNameId];
+ return SourceMapFileSpan(file.span(start, start + text.length),
+ isIdentifier: true);
+ } else {
+ return SourceMapFileSpan(file.location(start).pointSpan());
+ }
+ } else {
+ var start = SourceLocation(0,
+ sourceUrl: _mapUrl?.resolve(url) ?? url,
+ line: entry.sourceLine,
+ column: entry.sourceColumn);
+
+ // Offset and other context is not available.
+ if (sourceNameId != null) {
+ return SourceMapSpan.identifier(start, names[sourceNameId]);
+ } else {
+ return SourceMapSpan(start, start, '');
+ }
+ }
+ }
+
+ @override
+ String toString() {
+ return (StringBuffer('$runtimeType : [')
+ ..write('targetUrl: ')
+ ..write(targetUrl)
+ ..write(', sourceRoot: ')
+ ..write(sourceRoot)
+ ..write(', urls: ')
+ ..write(urls)
+ ..write(', names: ')
+ ..write(names)
+ ..write(', lines: ')
+ ..write(lines)
+ ..write(']'))
+ .toString();
+ }
+
+ String get debugString {
+ var buff = StringBuffer();
+ for (var lineEntry in lines) {
+ var line = lineEntry.line;
+ for (var entry in lineEntry.entries) {
+ buff
+ ..write(targetUrl)
+ ..write(': ')
+ ..write(line)
+ ..write(':')
+ ..write(entry.column);
+ var sourceUrlId = entry.sourceUrlId;
+ if (sourceUrlId != null) {
+ buff
+ ..write(' --> ')
+ ..write(sourceRoot)
+ ..write(urls[sourceUrlId])
+ ..write(': ')
+ ..write(entry.sourceLine)
+ ..write(':')
+ ..write(entry.sourceColumn);
+ }
+ var sourceNameId = entry.sourceNameId;
+ if (sourceNameId != null) {
+ buff
+ ..write(' (')
+ ..write(names[sourceNameId])
+ ..write(')');
+ }
+ buff.write('\n');
+ }
+ }
+ return buff.toString();
+ }
+}
+
+/// A line entry read from a source map.
+class TargetLineEntry {
+ final int line;
+ List<TargetEntry> entries;
+ TargetLineEntry(this.line, this.entries);
+
+ @override
+ String toString() => '$runtimeType: $line $entries';
+}
+
+/// A target segment entry read from a source map
+class TargetEntry {
+ final int column;
+ final int? sourceUrlId;
+ final int? sourceLine;
+ final int? sourceColumn;
+ final int? sourceNameId;
+
+ TargetEntry(this.column,
+ [this.sourceUrlId,
+ this.sourceLine,
+ this.sourceColumn,
+ this.sourceNameId]);
+
+ @override
+ String toString() => '$runtimeType: '
+ '($column, $sourceUrlId, $sourceLine, $sourceColumn, $sourceNameId)';
+}
+
+/// A character iterator over a string that can peek one character ahead.
+class _MappingTokenizer implements Iterator<String> {
+ final String _internal;
+ final int _length;
+ int index = -1;
+ _MappingTokenizer(String internal)
+ : _internal = internal,
+ _length = internal.length;
+
+ // Iterator API is used by decodeVlq to consume VLQ entries.
+ @override
+ bool moveNext() => ++index < _length;
+
+ @override
+ String get current => (index >= 0 && index < _length)
+ ? _internal[index]
+ : throw RangeError.index(index, _internal);
+
+ bool get hasTokens => index < _length - 1 && _length > 0;
+
+ _TokenKind get nextKind {
+ if (!hasTokens) return _TokenKind.eof;
+ var next = _internal[index + 1];
+ if (next == ';') return _TokenKind.line;
+ if (next == ',') return _TokenKind.segment;
+ return _TokenKind.value;
+ }
+
+ int _consumeValue() => decodeVlq(this);
+ void _consumeNewLine() {
+ ++index;
+ }
+
+ void _consumeNewSegment() {
+ ++index;
+ }
+
+ // Print the state of the iterator, with colors indicating the current
+ // position.
+ @override
+ String toString() {
+ var buff = StringBuffer();
+ for (var i = 0; i < index; i++) {
+ buff.write(_internal[i]);
+ }
+ buff.write('[31m');
+ try {
+ buff.write(current);
+ // TODO: Determine whether this try / catch can be removed.
+ // ignore: avoid_catching_errors
+ } on RangeError catch (_) {}
+ buff.write('[0m');
+ for (var i = index + 1; i < _internal.length; i++) {
+ buff.write(_internal[i]);
+ }
+ buff.write(' ($index)');
+ return buff.toString();
+ }
+}
+
+class _TokenKind {
+ static const _TokenKind line = _TokenKind(isNewLine: true);
+ static const _TokenKind segment = _TokenKind(isNewSegment: true);
+ static const _TokenKind eof = _TokenKind(isEof: true);
+ static const _TokenKind value = _TokenKind();
+ final bool isNewLine;
+ final bool isNewSegment;
+ final bool isEof;
+ bool get isValue => !isNewLine && !isNewSegment && !isEof;
+
+ const _TokenKind(
+ {this.isNewLine = false, this.isNewSegment = false, this.isEof = false});
+}
diff --git a/pkgs/source_maps/lib/printer.dart b/pkgs/source_maps/lib/printer.dart
new file mode 100644
index 0000000..32523d6
--- /dev/null
+++ b/pkgs/source_maps/lib/printer.dart
@@ -0,0 +1,262 @@
+// 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.
+
+/// Contains a code printer that generates code by recording the source maps.
+library;
+
+import 'package:source_span/source_span.dart';
+
+import 'builder.dart';
+import 'src/source_map_span.dart';
+import 'src/utils.dart';
+
+/// A simple printer that keeps track of offset locations and records source
+/// maps locations.
+class Printer {
+ final String filename;
+ final StringBuffer _buff = StringBuffer();
+ final SourceMapBuilder _maps = SourceMapBuilder();
+ String get text => _buff.toString();
+ String get map => _maps.toJson(filename);
+
+ /// Current source location mapping.
+ SourceLocation? _loc;
+
+ /// Current line in the buffer;
+ int _line = 0;
+
+ /// Current column in the buffer.
+ int _column = 0;
+
+ Printer(this.filename);
+
+ /// Add [str] contents to the output, tracking new lines to track correct
+ /// positions for span locations. When [projectMarks] is true, this method
+ /// adds a source map location on each new line, projecting that every new
+ /// line in the target file (printed here) corresponds to a new line in the
+ /// source file.
+ void add(String str, {bool projectMarks = false}) {
+ var chars = str.runes.toList();
+ var length = chars.length;
+ for (var i = 0; i < length; i++) {
+ var c = chars[i];
+ if (c == lineFeed ||
+ (c == carriageReturn &&
+ (i + 1 == length || chars[i + 1] != lineFeed))) {
+ // Return not followed by line-feed is treated as a new line.
+ _line++;
+ _column = 0;
+ {
+ // **Warning**: Any calls to `mark` will change the value of `_loc`,
+ // so this local variable is no longer up to date after that point.
+ //
+ // This is why it has been put inside its own block to limit the
+ // scope in which it is available.
+ var loc = _loc;
+ if (projectMarks && loc != null) {
+ if (loc is FileLocation) {
+ var file = loc.file;
+ mark(file.location(file.getOffset(loc.line + 1)));
+ } else {
+ mark(SourceLocation(0,
+ sourceUrl: loc.sourceUrl, line: loc.line + 1, column: 0));
+ }
+ }
+ }
+ } else {
+ _column++;
+ }
+ }
+ _buff.write(str);
+ }
+
+ /// Append a [total] number of spaces in the target file. Typically used for
+ /// formatting indentation.
+ void addSpaces(int total) {
+ for (var i = 0; i < total; i++) {
+ _buff.write(' ');
+ }
+ _column += total;
+ }
+
+ /// Marks that the current point in the target file corresponds to the [mark]
+ /// in the source file, which can be either a [SourceLocation] or a
+ /// [SourceSpan]. When the mark is a [SourceMapSpan] with `isIdentifier` set,
+ /// this also records the name of the identifier in the source map
+ /// information.
+ void mark(Object mark) {
+ late final SourceLocation loc;
+ String? identifier;
+ if (mark is SourceLocation) {
+ loc = mark;
+ } else if (mark is SourceSpan) {
+ loc = mark.start;
+ if (mark is SourceMapSpan && mark.isIdentifier) identifier = mark.text;
+ }
+ _maps.addLocation(loc,
+ SourceLocation(_buff.length, line: _line, column: _column), identifier);
+ _loc = loc;
+ }
+}
+
+/// A more advanced printer that keeps track of offset locations to record
+/// source maps, but additionally allows nesting of different kind of items,
+/// including [NestedPrinter]s, and it let's you automatically indent text.
+///
+/// This class is especially useful when doing code generation, where different
+/// pieces of the code are generated independently on separate printers, and are
+/// finally put together in the end.
+class NestedPrinter implements NestedItem {
+ /// Items recoded by this printer, which can be [String] literals,
+ /// [NestedItem]s, and source map information like [SourceLocation] and
+ /// [SourceSpan].
+ final List<Object> _items = [];
+
+ /// Internal buffer to merge consecutive strings added to this printer.
+ StringBuffer? _buff;
+
+ /// Current indentation, which can be updated from outside this class.
+ int indent;
+
+ /// [Printer] used during the last call to [build], if any.
+ Printer? printer;
+
+ /// Returns the text produced after calling [build].
+ String? get text => printer?.text;
+
+ /// Returns the source-map information produced after calling [build].
+ String? get map => printer?.map;
+
+ /// Item used to indicate that the following item is copied from the original
+ /// source code, and hence we should preserve source-maps on every new line.
+ static final _original = Object();
+
+ NestedPrinter([this.indent = 0]);
+
+ /// Adds [object] to this printer. [object] can be a [String],
+ /// [NestedPrinter], or anything implementing [NestedItem]. If [object] is a
+ /// [String], the value is appended directly, without doing any formatting
+ /// changes. If you wish to add a line of code with automatic indentation, use
+ /// [addLine] instead. [NestedPrinter]s and [NestedItem]s are not processed
+ /// until [build] gets called later on. We ensure that [build] emits every
+ /// object in the order that they were added to this printer.
+ ///
+ /// The [location] and [span] parameters indicate the corresponding source map
+ /// location of [object] in the original input. Only one, [location] or
+ /// [span], should be provided at a time.
+ ///
+ /// Indicate [isOriginal] when [object] is copied directly from the user code.
+ /// Setting [isOriginal] will make this printer propagate source map locations
+ /// on every line-break.
+ void add(Object object,
+ {SourceLocation? location, SourceSpan? span, bool isOriginal = false}) {
+ if (object is! String || location != null || span != null || isOriginal) {
+ _flush();
+ assert(location == null || span == null);
+ if (location != null) _items.add(location);
+ if (span != null) _items.add(span);
+ if (isOriginal) _items.add(_original);
+ }
+
+ if (object is String) {
+ _appendString(object);
+ } else {
+ _items.add(object);
+ }
+ }
+
+ /// Append `2 * indent` spaces to this printer.
+ void insertIndent() => _indent(indent);
+
+ /// Add a [line], autoindenting to the current value of [indent]. Note,
+ /// indentation is not inferred from the contents added to this printer. If a
+ /// line starts or ends an indentation block, you need to also update [indent]
+ /// accordingly. Also, indentation is not adapted for nested printers. If
+ /// you add a [NestedPrinter] to this printer, its indentation is set
+ /// separately and will not include any the indentation set here.
+ ///
+ /// The [location] and [span] parameters indicate the corresponding source map
+ /// location of [line] in the original input. Only one, [location] or
+ /// [span], should be provided at a time.
+ void addLine(String? line, {SourceLocation? location, SourceSpan? span}) {
+ if (location != null || span != null) {
+ _flush();
+ assert(location == null || span == null);
+ if (location != null) _items.add(location);
+ if (span != null) _items.add(span);
+ }
+ if (line == null) return;
+ if (line != '') {
+ // We don't indent empty lines.
+ _indent(indent);
+ _appendString(line);
+ }
+ _appendString('\n');
+ }
+
+ /// Appends a string merging it with any previous strings, if possible.
+ void _appendString(String s) {
+ var buf = _buff ??= StringBuffer();
+ buf.write(s);
+ }
+
+ /// Adds all of the current [_buff] contents as a string item.
+ void _flush() {
+ if (_buff != null) {
+ _items.add(_buff.toString());
+ _buff = null;
+ }
+ }
+
+ void _indent(int indent) {
+ for (var i = 0; i < indent; i++) {
+ _appendString(' ');
+ }
+ }
+
+ /// Returns a string representation of all the contents appended to this
+ /// printer, including source map location tokens.
+ @override
+ String toString() {
+ _flush();
+ return (StringBuffer()..writeAll(_items)).toString();
+ }
+
+ /// Builds the output of this printer and source map information. After
+ /// calling this function, you can use [text] and [map] to retrieve the
+ /// geenrated code and source map information, respectively.
+ void build(String filename) {
+ writeTo(printer = Printer(filename));
+ }
+
+ /// Implements the [NestedItem] interface.
+ @override
+ void writeTo(Printer printer) {
+ _flush();
+ var propagate = false;
+ for (var item in _items) {
+ if (item is NestedItem) {
+ item.writeTo(printer);
+ } else if (item is String) {
+ printer.add(item, projectMarks: propagate);
+ propagate = false;
+ } else if (item is SourceLocation || item is SourceSpan) {
+ printer.mark(item);
+ } else if (item == _original) {
+ // we insert booleans when we are about to quote text that was copied
+ // from the original source. In such case, we will propagate marks on
+ // every new-line.
+ propagate = true;
+ } else {
+ throw UnsupportedError('Unknown item type: $item');
+ }
+ }
+ }
+}
+
+/// An item added to a [NestedPrinter].
+abstract class NestedItem {
+ /// Write the contents of this item into [printer].
+ void writeTo(Printer printer);
+}
diff --git a/pkgs/source_maps/lib/refactor.dart b/pkgs/source_maps/lib/refactor.dart
new file mode 100644
index 0000000..a518a0c
--- /dev/null
+++ b/pkgs/source_maps/lib/refactor.dart
@@ -0,0 +1,140 @@
+// 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.
+
+/// Tools to help implement refactoring like transformations to Dart code.
+///
+/// [TextEditTransaction] supports making a series of changes to a text buffer.
+/// [guessIndent] helps to guess the appropriate indentiation for the new code.
+library;
+
+import 'package:source_span/source_span.dart';
+
+import 'printer.dart';
+import 'src/utils.dart';
+
+/// Editable text transaction.
+///
+/// Applies a series of edits using original location
+/// information, and composes them into the edited string.
+class TextEditTransaction {
+ final SourceFile? file;
+ final String original;
+ final _edits = <_TextEdit>[];
+
+ /// Creates a new transaction.
+ TextEditTransaction(this.original, this.file);
+
+ bool get hasEdits => _edits.isNotEmpty;
+
+ /// Edit the original text, replacing text on the range [begin] and [end]
+ /// with the [replacement]. [replacement] can be either a string or a
+ /// [NestedPrinter].
+ void edit(int begin, int end, Object replacement) {
+ _edits.add(_TextEdit(begin, end, replacement));
+ }
+
+ /// Create a source map [SourceLocation] for [offset], if [file] is not
+ /// `null`.
+ SourceLocation? _loc(int offset) => file?.location(offset);
+
+ /// Applies all pending [edit]s and returns a [NestedPrinter] containing the
+ /// rewritten string and source map information. [file]`.location` is given to
+ /// the underlying printer to indicate the name of the generated file that
+ /// will contains the source map information.
+ ///
+ /// Throws [UnsupportedError] if the edits were overlapping. If no edits were
+ /// made, the printer simply contains the original string.
+ NestedPrinter commit() {
+ var printer = NestedPrinter();
+ if (_edits.isEmpty) {
+ return printer..add(original, location: _loc(0), isOriginal: true);
+ }
+
+ // Sort edits by start location.
+ _edits.sort();
+
+ var consumed = 0;
+ for (var edit in _edits) {
+ if (consumed > edit.begin) {
+ var sb = StringBuffer();
+ sb
+ ..write(file?.location(edit.begin).toolString)
+ ..write(': overlapping edits. Insert at offset ')
+ ..write(edit.begin)
+ ..write(' but have consumed ')
+ ..write(consumed)
+ ..write(' input characters. List of edits:');
+ for (var e in _edits) {
+ sb
+ ..write('\n ')
+ ..write(e);
+ }
+ throw UnsupportedError(sb.toString());
+ }
+
+ // Add characters from the original string between this edit and the last
+ // one, if any.
+ var betweenEdits = original.substring(consumed, edit.begin);
+ printer
+ ..add(betweenEdits, location: _loc(consumed), isOriginal: true)
+ ..add(edit.replace, location: _loc(edit.begin));
+ consumed = edit.end;
+ }
+
+ // Add any text from the end of the original string that was not replaced.
+ printer.add(original.substring(consumed),
+ location: _loc(consumed), isOriginal: true);
+ return printer;
+ }
+}
+
+class _TextEdit implements Comparable<_TextEdit> {
+ final int begin;
+ final int end;
+
+ /// The replacement used by the edit, can be a string or a [NestedPrinter].
+ final Object replace;
+
+ _TextEdit(this.begin, this.end, this.replace);
+
+ int get length => end - begin;
+
+ @override
+ String toString() => '(Edit @ $begin,$end: "$replace")';
+
+ @override
+ int compareTo(_TextEdit other) {
+ var diff = begin - other.begin;
+ if (diff != 0) return diff;
+ return end - other.end;
+ }
+}
+
+/// Returns all whitespace characters at the start of [charOffset]'s line.
+String guessIndent(String code, int charOffset) {
+ // Find the beginning of the line
+ var lineStart = 0;
+ for (var i = charOffset - 1; i >= 0; i--) {
+ var c = code.codeUnitAt(i);
+ if (c == lineFeed || c == carriageReturn) {
+ lineStart = i + 1;
+ break;
+ }
+ }
+
+ // Grab all the whitespace
+ var whitespaceEnd = code.length;
+ for (var i = lineStart; i < code.length; i++) {
+ var c = code.codeUnitAt(i);
+ if (c != _space && c != _tab) {
+ whitespaceEnd = i;
+ break;
+ }
+ }
+
+ return code.substring(lineStart, whitespaceEnd);
+}
+
+const int _tab = 9;
+const int _space = 32;
diff --git a/pkgs/source_maps/lib/source_maps.dart b/pkgs/source_maps/lib/source_maps.dart
new file mode 100644
index 0000000..244dee7
--- /dev/null
+++ b/pkgs/source_maps/lib/source_maps.dart
@@ -0,0 +1,38 @@
+// 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.
+
+/// Library to create and parse source maps.
+///
+/// Create a source map using [SourceMapBuilder]. For example:
+///
+/// ```dart
+/// var json = (new SourceMapBuilder()
+/// ..add(inputSpan1, outputSpan1)
+/// ..add(inputSpan2, outputSpan2)
+/// ..add(inputSpan3, outputSpan3)
+/// .toJson(outputFile);
+/// ```
+///
+/// Use the source_span package's [SourceSpan] and [SourceFile] classes to
+/// specify span locations.
+///
+/// Parse a source map using [parse], and call `spanFor` on the returned mapping
+/// object. For example:
+///
+/// ```dart
+/// var mapping = parse(json);
+/// mapping.spanFor(outputSpan1.line, outputSpan1.column)
+/// ```
+library;
+
+import 'package:source_span/source_span.dart';
+
+import 'builder.dart';
+import 'parser.dart';
+
+export 'builder.dart';
+export 'parser.dart';
+export 'printer.dart';
+export 'refactor.dart';
+export 'src/source_map_span.dart';
diff --git a/pkgs/source_maps/lib/src/source_map_span.dart b/pkgs/source_maps/lib/src/source_map_span.dart
new file mode 100644
index 0000000..aad8a32
--- /dev/null
+++ b/pkgs/source_maps/lib/src/source_map_span.dart
@@ -0,0 +1,72 @@
+// Copyright (c) 2014, 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_span/source_span.dart';
+
+/// A [SourceSpan] for spans coming from or being written to source maps.
+///
+/// These spans have an extra piece of metadata: whether or not they represent
+/// an identifier (see [isIdentifier]).
+class SourceMapSpan extends SourceSpanBase {
+ /// Whether this span represents an identifier.
+ ///
+ /// If this is `true`, [text] is the value of the identifier.
+ final bool isIdentifier;
+
+ SourceMapSpan(super.start, super.end, super.text,
+ {this.isIdentifier = false});
+
+ /// Creates a [SourceMapSpan] for an identifier with value [text] starting at
+ /// [start].
+ ///
+ /// The [end] location is determined by adding [text] to [start].
+ SourceMapSpan.identifier(SourceLocation start, String text)
+ : this(
+ start,
+ SourceLocation(start.offset + text.length,
+ sourceUrl: start.sourceUrl,
+ line: start.line,
+ column: start.column + text.length),
+ text,
+ isIdentifier: true);
+}
+
+/// A wrapper aruond a [FileSpan] that implements [SourceMapSpan].
+class SourceMapFileSpan implements SourceMapSpan, FileSpan {
+ final FileSpan _inner;
+ @override
+ final bool isIdentifier;
+
+ @override
+ SourceFile get file => _inner.file;
+ @override
+ FileLocation get start => _inner.start;
+ @override
+ FileLocation get end => _inner.end;
+ @override
+ String get text => _inner.text;
+ @override
+ String get context => _inner.context;
+ @override
+ Uri? get sourceUrl => _inner.sourceUrl;
+ @override
+ int get length => _inner.length;
+
+ SourceMapFileSpan(this._inner, {this.isIdentifier = false});
+
+ @override
+ int compareTo(SourceSpan other) => _inner.compareTo(other);
+ @override
+ String highlight({Object? color}) => _inner.highlight(color: color);
+ @override
+ SourceSpan union(SourceSpan other) => _inner.union(other);
+ @override
+ FileSpan expand(FileSpan other) => _inner.expand(other);
+ @override
+ String message(String message, {Object? color}) =>
+ _inner.message(message, color: color);
+ @override
+ String toString() =>
+ _inner.toString().replaceAll('FileSpan', 'SourceMapFileSpan');
+}
diff --git a/pkgs/source_maps/lib/src/utils.dart b/pkgs/source_maps/lib/src/utils.dart
new file mode 100644
index 0000000..ba04fbb
--- /dev/null
+++ b/pkgs/source_maps/lib/src/utils.dart
@@ -0,0 +1,32 @@
+// 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.
+
+/// Utilities that shouldn't be in this package.
+library;
+
+/// Find the first entry in a sorted [list] that matches a monotonic predicate.
+/// Given a result `n`, that all items before `n` will not match, `n` matches,
+/// and all items after `n` match too. The result is -1 when there are no
+/// items, 0 when all items match, and list.length when none does.
+// TODO(sigmund): remove this function after dartbug.com/5624 is fixed.
+int binarySearch<T>(List<T> list, bool Function(T) matches) {
+ if (list.isEmpty) return -1;
+ if (matches(list.first)) return 0;
+ if (!matches(list.last)) return list.length;
+
+ var min = 0;
+ var max = list.length - 1;
+ while (min < max) {
+ var half = min + ((max - min) ~/ 2);
+ if (matches(list[half])) {
+ max = half;
+ } else {
+ min = half + 1;
+ }
+ }
+ return max;
+}
+
+const int lineFeed = 10;
+const int carriageReturn = 13;
diff --git a/pkgs/source_maps/lib/src/vlq.dart b/pkgs/source_maps/lib/src/vlq.dart
new file mode 100644
index 0000000..3b0562d
--- /dev/null
+++ b/pkgs/source_maps/lib/src/vlq.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.
+
+/// Utilities to encode and decode VLQ values used in source maps.
+///
+/// Sourcemaps are encoded with variable length numbers as base64 encoded
+/// strings with the least significant digit coming first. Each base64 digit
+/// encodes a 5-bit value (0-31) and a continuation bit. Signed values can be
+/// represented by using the least significant bit of the value as the sign bit.
+///
+/// For more details see the source map [version 3 documentation](https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?usp=sharing).
+library;
+
+import 'dart:math';
+
+const int vlqBaseShift = 5;
+
+const int vlqBaseMask = (1 << 5) - 1;
+
+const int vlqContinuationBit = 1 << 5;
+
+const int vlqContinuationMask = 1 << 5;
+
+const String base64Digits =
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
+
+final Map<String, int> _digits = () {
+ var map = <String, int>{};
+ for (var i = 0; i < 64; i++) {
+ map[base64Digits[i]] = i;
+ }
+ return map;
+}();
+
+final int maxInt32 = (pow(2, 31) as int) - 1;
+final int minInt32 = -(pow(2, 31) as int);
+
+/// Creates the VLQ encoding of [value] as a sequence of characters
+Iterable<String> encodeVlq(int value) {
+ if (value < minInt32 || value > maxInt32) {
+ throw ArgumentError('expected 32 bit int, got: $value');
+ }
+ var res = <String>[];
+ var signBit = 0;
+ if (value < 0) {
+ signBit = 1;
+ value = -value;
+ }
+ value = (value << 1) | signBit;
+ do {
+ var digit = value & vlqBaseMask;
+ value >>= vlqBaseShift;
+ if (value > 0) {
+ digit |= vlqContinuationBit;
+ }
+ res.add(base64Digits[digit]);
+ } while (value > 0);
+ return res;
+}
+
+/// Decodes a value written as a sequence of VLQ characters. The first input
+/// character will be `chars.current` after calling `chars.moveNext` once. The
+/// iterator is advanced until a stop character is found (a character without
+/// the [vlqContinuationBit]).
+int decodeVlq(Iterator<String> chars) {
+ var result = 0;
+ var stop = false;
+ var shift = 0;
+ while (!stop) {
+ if (!chars.moveNext()) throw StateError('incomplete VLQ value');
+ var char = chars.current;
+ var digit = _digits[char];
+ if (digit == null) {
+ throw FormatException('invalid character in VLQ encoding: $char');
+ }
+ stop = (digit & vlqContinuationBit) == 0;
+ digit &= vlqBaseMask;
+ result += digit << shift;
+ shift += vlqBaseShift;
+ }
+
+ // Result uses the least significant bit as a sign bit. We convert it into a
+ // two-complement value. For example,
+ // 2 (10 binary) becomes 1
+ // 3 (11 binary) becomes -1
+ // 4 (100 binary) becomes 2
+ // 5 (101 binary) becomes -2
+ // 6 (110 binary) becomes 3
+ // 7 (111 binary) becomes -3
+ var negate = (result & 1) == 1;
+ result = result >> 1;
+ result = negate ? -result : result;
+
+ // TODO(sigmund): can we detect this earlier?
+ if (result < minInt32 || result > maxInt32) {
+ throw FormatException(
+ 'expected an encoded 32 bit int, but we got: $result');
+ }
+ return result;
+}
diff --git a/pkgs/source_maps/pubspec.yaml b/pkgs/source_maps/pubspec.yaml
new file mode 100644
index 0000000..32cbf4f
--- /dev/null
+++ b/pkgs/source_maps/pubspec.yaml
@@ -0,0 +1,15 @@
+name: source_maps
+version: 0.10.14-wip
+description: A library to programmatically manipulate source map files.
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/source_maps
+
+environment:
+ sdk: ^3.3.0
+
+dependencies:
+ source_span: ^1.8.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ term_glyph: ^1.2.0
+ test: ^1.16.0
diff --git a/pkgs/source_maps/test/builder_test.dart b/pkgs/source_maps/test/builder_test.dart
new file mode 100644
index 0000000..4f773e7
--- /dev/null
+++ b/pkgs/source_maps/test/builder_test.dart
@@ -0,0 +1,32 @@
+// 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 'package:source_maps/source_maps.dart';
+import 'package:test/test.dart';
+
+import 'common.dart';
+
+void main() {
+ test('builder - with span', () {
+ var map = (SourceMapBuilder()
+ ..addSpan(inputVar1, outputVar1)
+ ..addSpan(inputFunction, outputFunction)
+ ..addSpan(inputVar2, outputVar2)
+ ..addSpan(inputExpr, outputExpr))
+ .build(output.url.toString());
+ expect(map, equals(expectedMap));
+ });
+
+ test('builder - with location', () {
+ var str = (SourceMapBuilder()
+ ..addLocation(inputVar1.start, outputVar1.start, 'longVar1')
+ ..addLocation(inputFunction.start, outputFunction.start, 'longName')
+ ..addLocation(inputVar2.start, outputVar2.start, 'longVar2')
+ ..addLocation(inputExpr.start, outputExpr.start, null))
+ .toJson(output.url.toString());
+ expect(str, jsonEncode(expectedMap));
+ });
+}
diff --git a/pkgs/source_maps/test/common.dart b/pkgs/source_maps/test/common.dart
new file mode 100644
index 0000000..e225ff5
--- /dev/null
+++ b/pkgs/source_maps/test/common.dart
@@ -0,0 +1,107 @@
+// 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.
+
+/// Common input/output used by builder, parser and end2end tests
+library;
+
+import 'package:source_maps/source_maps.dart';
+import 'package:source_span/source_span.dart';
+import 'package:test/test.dart';
+
+/// Content of the source file
+const String inputContent = '''
+/** this is a comment. */
+int longVar1 = 3;
+
+// this is a comment too
+int longName(int longVar2) {
+ return longVar1 + longVar2;
+}
+''';
+final input = SourceFile.fromString(inputContent, url: 'input.dart');
+
+/// A span in the input file
+SourceMapSpan ispan(int start, int end, [bool isIdentifier = false]) =>
+ SourceMapFileSpan(input.span(start, end), isIdentifier: isIdentifier);
+
+SourceMapSpan inputVar1 = ispan(30, 38, true);
+SourceMapSpan inputFunction = ispan(74, 82, true);
+SourceMapSpan inputVar2 = ispan(87, 95, true);
+
+SourceMapSpan inputVar1NoSymbol = ispan(30, 38);
+SourceMapSpan inputFunctionNoSymbol = ispan(74, 82);
+SourceMapSpan inputVar2NoSymbol = ispan(87, 95);
+
+SourceMapSpan inputExpr = ispan(108, 127);
+
+/// Content of the target file
+const String outputContent = '''
+var x = 3;
+f(y) => x + y;
+''';
+final output = SourceFile.fromString(outputContent, url: 'output.dart');
+
+/// A span in the output file
+SourceMapSpan ospan(int start, int end, [bool isIdentifier = false]) =>
+ SourceMapFileSpan(output.span(start, end), isIdentifier: isIdentifier);
+
+SourceMapSpan outputVar1 = ospan(4, 5, true);
+SourceMapSpan outputFunction = ospan(11, 12, true);
+SourceMapSpan outputVar2 = ospan(13, 14, true);
+SourceMapSpan outputVar1NoSymbol = ospan(4, 5);
+SourceMapSpan outputFunctionNoSymbol = ospan(11, 12);
+SourceMapSpan outputVar2NoSymbol = ospan(13, 14);
+SourceMapSpan outputExpr = ospan(19, 24);
+
+/// Expected output mapping when recording the following four mappings:
+/// inputVar1 <= outputVar1
+/// inputFunction <= outputFunction
+/// inputVar2 <= outputVar2
+/// inputExpr <= outputExpr
+///
+/// This mapping is stored in the tests so we can independently test the builder
+/// and parser algorithms without relying entirely on end2end tests.
+const Map<String, dynamic> expectedMap = {
+ 'version': 3,
+ 'sourceRoot': '',
+ 'sources': ['input.dart'],
+ 'names': ['longVar1', 'longName', 'longVar2'],
+ 'mappings': 'IACIA;AAGAC,EAAaC,MACR',
+ 'file': 'output.dart'
+};
+
+void check(SourceSpan outputSpan, Mapping mapping, SourceMapSpan inputSpan,
+ bool realOffsets) {
+ var line = outputSpan.start.line;
+ var column = outputSpan.start.column;
+ var files = realOffsets ? {'input.dart': input} : null;
+ var span = mapping.spanFor(line, column, files: files)!;
+ var span2 = mapping.spanForLocation(outputSpan.start, files: files)!;
+
+ // Both mapping APIs are equivalent.
+ expect(span.start.offset, span2.start.offset);
+ expect(span.start.line, span2.start.line);
+ expect(span.start.column, span2.start.column);
+ expect(span.end.offset, span2.end.offset);
+ expect(span.end.line, span2.end.line);
+ expect(span.end.column, span2.end.column);
+
+ // Mapping matches our input location (modulo using real offsets)
+ expect(span.start.line, inputSpan.start.line);
+ expect(span.start.column, inputSpan.start.column);
+ expect(span.sourceUrl, inputSpan.sourceUrl);
+ expect(span.start.offset, realOffsets ? inputSpan.start.offset : 0);
+
+ // Mapping includes the identifier, if any
+ if (inputSpan.isIdentifier) {
+ expect(span.end.line, inputSpan.end.line);
+ expect(span.end.column, inputSpan.end.column);
+ expect(span.end.offset, span.start.offset + inputSpan.text.length);
+ if (realOffsets) expect(span.end.offset, inputSpan.end.offset);
+ } else {
+ expect(span.end.offset, span.start.offset);
+ expect(span.end.line, span.start.line);
+ expect(span.end.column, span.start.column);
+ }
+}
diff --git a/pkgs/source_maps/test/end2end_test.dart b/pkgs/source_maps/test/end2end_test.dart
new file mode 100644
index 0000000..84dd5ba
--- /dev/null
+++ b/pkgs/source_maps/test/end2end_test.dart
@@ -0,0 +1,160 @@
+// 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:source_maps/source_maps.dart';
+import 'package:source_span/source_span.dart';
+import 'package:test/test.dart';
+
+import 'common.dart';
+
+void main() {
+ test('end-to-end setup', () {
+ expect(inputVar1.text, 'longVar1');
+ expect(inputFunction.text, 'longName');
+ expect(inputVar2.text, 'longVar2');
+ expect(inputVar1NoSymbol.text, 'longVar1');
+ expect(inputFunctionNoSymbol.text, 'longName');
+ expect(inputVar2NoSymbol.text, 'longVar2');
+ expect(inputExpr.text, 'longVar1 + longVar2');
+
+ expect(outputVar1.text, 'x');
+ expect(outputFunction.text, 'f');
+ expect(outputVar2.text, 'y');
+ expect(outputVar1NoSymbol.text, 'x');
+ expect(outputFunctionNoSymbol.text, 'f');
+ expect(outputVar2NoSymbol.text, 'y');
+ expect(outputExpr.text, 'x + y');
+ });
+
+ test('build + parse', () {
+ var map = (SourceMapBuilder()
+ ..addSpan(inputVar1, outputVar1)
+ ..addSpan(inputFunction, outputFunction)
+ ..addSpan(inputVar2, outputVar2)
+ ..addSpan(inputExpr, outputExpr))
+ .build(output.url.toString());
+ var mapping = parseJson(map);
+ check(outputVar1, mapping, inputVar1, false);
+ check(outputVar2, mapping, inputVar2, false);
+ check(outputFunction, mapping, inputFunction, false);
+ check(outputExpr, mapping, inputExpr, false);
+ });
+
+ test('build + parse - no symbols', () {
+ var map = (SourceMapBuilder()
+ ..addSpan(inputVar1NoSymbol, outputVar1NoSymbol)
+ ..addSpan(inputFunctionNoSymbol, outputFunctionNoSymbol)
+ ..addSpan(inputVar2NoSymbol, outputVar2NoSymbol)
+ ..addSpan(inputExpr, outputExpr))
+ .build(output.url.toString());
+ var mapping = parseJson(map);
+ check(outputVar1NoSymbol, mapping, inputVar1NoSymbol, false);
+ check(outputVar2NoSymbol, mapping, inputVar2NoSymbol, false);
+ check(outputFunctionNoSymbol, mapping, inputFunctionNoSymbol, false);
+ check(outputExpr, mapping, inputExpr, false);
+ });
+
+ test('build + parse, repeated entries', () {
+ var map = (SourceMapBuilder()
+ ..addSpan(inputVar1, outputVar1)
+ ..addSpan(inputVar1, outputVar1)
+ ..addSpan(inputFunction, outputFunction)
+ ..addSpan(inputFunction, outputFunction)
+ ..addSpan(inputVar2, outputVar2)
+ ..addSpan(inputVar2, outputVar2)
+ ..addSpan(inputExpr, outputExpr)
+ ..addSpan(inputExpr, outputExpr))
+ .build(output.url.toString());
+ var mapping = parseJson(map);
+ check(outputVar1, mapping, inputVar1, false);
+ check(outputVar2, mapping, inputVar2, false);
+ check(outputFunction, mapping, inputFunction, false);
+ check(outputExpr, mapping, inputExpr, false);
+ });
+
+ test('build + parse - no symbols, repeated entries', () {
+ var map = (SourceMapBuilder()
+ ..addSpan(inputVar1NoSymbol, outputVar1NoSymbol)
+ ..addSpan(inputVar1NoSymbol, outputVar1NoSymbol)
+ ..addSpan(inputFunctionNoSymbol, outputFunctionNoSymbol)
+ ..addSpan(inputFunctionNoSymbol, outputFunctionNoSymbol)
+ ..addSpan(inputVar2NoSymbol, outputVar2NoSymbol)
+ ..addSpan(inputVar2NoSymbol, outputVar2NoSymbol)
+ ..addSpan(inputExpr, outputExpr))
+ .build(output.url.toString());
+ var mapping = parseJson(map);
+ check(outputVar1NoSymbol, mapping, inputVar1NoSymbol, false);
+ check(outputVar2NoSymbol, mapping, inputVar2NoSymbol, false);
+ check(outputFunctionNoSymbol, mapping, inputFunctionNoSymbol, false);
+ check(outputExpr, mapping, inputExpr, false);
+ });
+
+ test('build + parse with file', () {
+ var json = (SourceMapBuilder()
+ ..addSpan(inputVar1, outputVar1)
+ ..addSpan(inputFunction, outputFunction)
+ ..addSpan(inputVar2, outputVar2)
+ ..addSpan(inputExpr, outputExpr))
+ .toJson(output.url.toString());
+ var mapping = parse(json);
+ check(outputVar1, mapping, inputVar1, true);
+ check(outputVar2, mapping, inputVar2, true);
+ check(outputFunction, mapping, inputFunction, true);
+ check(outputExpr, mapping, inputExpr, true);
+ });
+
+ test('printer projecting marks + parse', () {
+ var out = inputContent.replaceAll('long', '_s');
+ var file = SourceFile.fromString(out, url: 'output2.dart');
+ var printer = Printer('output2.dart');
+ printer.mark(ispan(0, 0));
+
+ var segments = inputContent.split('long');
+ expect(segments.length, 6);
+ printer.add(segments[0], projectMarks: true);
+ printer.mark(inputVar1);
+ printer.add('_s');
+ printer.add(segments[1], projectMarks: true);
+ printer.mark(inputFunction);
+ printer.add('_s');
+ printer.add(segments[2], projectMarks: true);
+ printer.mark(inputVar2);
+ printer.add('_s');
+ printer.add(segments[3], projectMarks: true);
+ printer.mark(inputExpr);
+ printer.add('_s');
+ printer.add(segments[4], projectMarks: true);
+ printer.add('_s');
+ printer.add(segments[5], projectMarks: true);
+
+ expect(printer.text, out);
+
+ var mapping = parse(printer.map);
+ void checkHelper(SourceMapSpan inputSpan, int adjustment) {
+ var start = inputSpan.start.offset - adjustment;
+ var end = (inputSpan.end.offset - adjustment) - 2;
+ var span = SourceMapFileSpan(file.span(start, end),
+ isIdentifier: inputSpan.isIdentifier);
+ check(span, mapping, inputSpan, true);
+ }
+
+ checkHelper(inputVar1, 0);
+ checkHelper(inputFunction, 2);
+ checkHelper(inputVar2, 4);
+ checkHelper(inputExpr, 6);
+
+ // We projected correctly lines that have no mappings
+ check(file.span(66, 66), mapping, ispan(45, 45), true);
+ check(file.span(63, 64), mapping, ispan(45, 45), true);
+ check(file.span(68, 68), mapping, ispan(70, 70), true);
+ check(file.span(71, 71), mapping, ispan(70, 70), true);
+
+ // Start of the last line
+ var oOffset = out.length - 2;
+ var iOffset = inputContent.length - 2;
+ check(file.span(oOffset, oOffset), mapping, ispan(iOffset, iOffset), true);
+ check(file.span(oOffset + 1, oOffset + 1), mapping, ispan(iOffset, iOffset),
+ true);
+ });
+}
diff --git a/pkgs/source_maps/test/parser_test.dart b/pkgs/source_maps/test/parser_test.dart
new file mode 100644
index 0000000..6cfe928
--- /dev/null
+++ b/pkgs/source_maps/test/parser_test.dart
@@ -0,0 +1,431 @@
+// 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.
+
+// ignore_for_file: inference_failure_on_collection_literal
+// ignore_for_file: inference_failure_on_instance_creation
+
+import 'dart:convert';
+
+import 'package:source_maps/source_maps.dart';
+import 'package:source_span/source_span.dart';
+import 'package:test/test.dart';
+
+import 'common.dart';
+
+const Map<String, dynamic> _mapWithNoSourceLocation = {
+ 'version': 3,
+ 'sourceRoot': '',
+ 'sources': ['input.dart'],
+ 'names': [],
+ 'mappings': 'A',
+ 'file': 'output.dart'
+};
+
+const Map<String, dynamic> _mapWithSourceLocation = {
+ 'version': 3,
+ 'sourceRoot': '',
+ 'sources': ['input.dart'],
+ 'names': [],
+ 'mappings': 'AAAA',
+ 'file': 'output.dart'
+};
+
+const Map<String, dynamic> _mapWithSourceLocationAndMissingNames = {
+ 'version': 3,
+ 'sourceRoot': '',
+ 'sources': ['input.dart'],
+ 'mappings': 'AAAA',
+ 'file': 'output.dart'
+};
+
+const Map<String, dynamic> _mapWithSourceLocationAndName = {
+ 'version': 3,
+ 'sourceRoot': '',
+ 'sources': ['input.dart'],
+ 'names': ['var'],
+ 'mappings': 'AAAAA',
+ 'file': 'output.dart'
+};
+
+const Map<String, dynamic> _mapWithSourceLocationAndName1 = {
+ 'version': 3,
+ 'sourceRoot': 'pkg/',
+ 'sources': ['input1.dart'],
+ 'names': ['var1'],
+ 'mappings': 'AAAAA',
+ 'file': 'output.dart'
+};
+
+const Map<String, dynamic> _mapWithSourceLocationAndName2 = {
+ 'version': 3,
+ 'sourceRoot': 'pkg/',
+ 'sources': ['input2.dart'],
+ 'names': ['var2'],
+ 'mappings': 'AAAAA',
+ 'file': 'output2.dart'
+};
+
+const Map<String, dynamic> _mapWithSourceLocationAndName3 = {
+ 'version': 3,
+ 'sourceRoot': 'pkg/',
+ 'sources': ['input3.dart'],
+ 'names': ['var3'],
+ 'mappings': 'AAAAA',
+ 'file': '3/output.dart'
+};
+
+const _sourceMapBundle = [
+ _mapWithSourceLocationAndName1,
+ _mapWithSourceLocationAndName2,
+ _mapWithSourceLocationAndName3,
+];
+
+void main() {
+ test('parse', () {
+ var mapping = parseJson(expectedMap);
+ check(outputVar1, mapping, inputVar1, false);
+ check(outputVar2, mapping, inputVar2, false);
+ check(outputFunction, mapping, inputFunction, false);
+ check(outputExpr, mapping, inputExpr, false);
+ });
+
+ test('parse + json', () {
+ var mapping = parse(jsonEncode(expectedMap));
+ check(outputVar1, mapping, inputVar1, false);
+ check(outputVar2, mapping, inputVar2, false);
+ check(outputFunction, mapping, inputFunction, false);
+ check(outputExpr, mapping, inputExpr, false);
+ });
+
+ test('parse with file', () {
+ var mapping = parseJson(expectedMap);
+ check(outputVar1, mapping, inputVar1, true);
+ check(outputVar2, mapping, inputVar2, true);
+ check(outputFunction, mapping, inputFunction, true);
+ check(outputExpr, mapping, inputExpr, true);
+ });
+
+ test('parse with no source location', () {
+ var map = parse(jsonEncode(_mapWithNoSourceLocation)) as SingleMapping;
+ expect(map.lines.length, 1);
+ expect(map.lines.first.entries.length, 1);
+ var entry = map.lines.first.entries.first;
+
+ expect(entry.column, 0);
+ expect(entry.sourceUrlId, null);
+ expect(entry.sourceColumn, null);
+ expect(entry.sourceLine, null);
+ expect(entry.sourceNameId, null);
+ });
+
+ test('parse with source location and no name', () {
+ var map = parse(jsonEncode(_mapWithSourceLocation)) as SingleMapping;
+ expect(map.lines.length, 1);
+ expect(map.lines.first.entries.length, 1);
+ var entry = map.lines.first.entries.first;
+
+ expect(entry.column, 0);
+ expect(entry.sourceUrlId, 0);
+ expect(entry.sourceColumn, 0);
+ expect(entry.sourceLine, 0);
+ expect(entry.sourceNameId, null);
+ });
+
+ test('parse with source location and missing names entry', () {
+ var map = parse(jsonEncode(_mapWithSourceLocationAndMissingNames))
+ as SingleMapping;
+ expect(map.lines.length, 1);
+ expect(map.lines.first.entries.length, 1);
+ var entry = map.lines.first.entries.first;
+
+ expect(entry.column, 0);
+ expect(entry.sourceUrlId, 0);
+ expect(entry.sourceColumn, 0);
+ expect(entry.sourceLine, 0);
+ expect(entry.sourceNameId, null);
+ });
+
+ test('parse with source location and name', () {
+ var map = parse(jsonEncode(_mapWithSourceLocationAndName)) as SingleMapping;
+ expect(map.lines.length, 1);
+ expect(map.lines.first.entries.length, 1);
+ var entry = map.lines.first.entries.first;
+
+ expect(entry.sourceUrlId, 0);
+ expect(entry.sourceUrlId, 0);
+ expect(entry.sourceColumn, 0);
+ expect(entry.sourceLine, 0);
+ expect(entry.sourceNameId, 0);
+ });
+
+ test('parse with source root', () {
+ var inputMap = Map.from(_mapWithSourceLocation);
+ inputMap['sourceRoot'] = '/pkg/';
+ var mapping = parseJson(inputMap) as SingleMapping;
+ expect(mapping.spanFor(0, 0)?.sourceUrl, Uri.parse('/pkg/input.dart'));
+ expect(
+ mapping
+ .spanForLocation(
+ SourceLocation(0, sourceUrl: Uri.parse('ignored.dart')))
+ ?.sourceUrl,
+ Uri.parse('/pkg/input.dart'));
+
+ var newSourceRoot = '/new/';
+
+ mapping.sourceRoot = newSourceRoot;
+ inputMap['sourceRoot'] = newSourceRoot;
+
+ expect(mapping.toJson(), equals(inputMap));
+ });
+
+ test('parse with map URL', () {
+ var inputMap = Map.from(_mapWithSourceLocation);
+ inputMap['sourceRoot'] = 'pkg/';
+ var mapping = parseJson(inputMap, mapUrl: 'file:///path/to/map');
+ expect(mapping.spanFor(0, 0)?.sourceUrl,
+ Uri.parse('file:///path/to/pkg/input.dart'));
+ });
+
+ group('parse with bundle', () {
+ var mapping =
+ parseJsonExtended(_sourceMapBundle, mapUrl: 'file:///path/to/map');
+
+ test('simple', () {
+ expect(
+ mapping
+ .spanForLocation(SourceLocation(0,
+ sourceUrl: Uri.file('/path/to/output.dart')))
+ ?.sourceUrl,
+ Uri.parse('file:///path/to/pkg/input1.dart'));
+ expect(
+ mapping
+ .spanForLocation(SourceLocation(0,
+ sourceUrl: Uri.file('/path/to/output2.dart')))
+ ?.sourceUrl,
+ Uri.parse('file:///path/to/pkg/input2.dart'));
+ expect(
+ mapping
+ .spanForLocation(SourceLocation(0,
+ sourceUrl: Uri.file('/path/to/3/output.dart')))
+ ?.sourceUrl,
+ Uri.parse('file:///path/to/pkg/input3.dart'));
+
+ expect(
+ mapping.spanFor(0, 0, uri: 'file:///path/to/output.dart')?.sourceUrl,
+ Uri.parse('file:///path/to/pkg/input1.dart'));
+ expect(
+ mapping.spanFor(0, 0, uri: 'file:///path/to/output2.dart')?.sourceUrl,
+ Uri.parse('file:///path/to/pkg/input2.dart'));
+ expect(
+ mapping
+ .spanFor(0, 0, uri: 'file:///path/to/3/output.dart')
+ ?.sourceUrl,
+ Uri.parse('file:///path/to/pkg/input3.dart'));
+ });
+
+ test('package uris', () {
+ expect(
+ mapping
+ .spanForLocation(SourceLocation(0,
+ sourceUrl: Uri.parse('package:1/output.dart')))
+ ?.sourceUrl,
+ Uri.parse('file:///path/to/pkg/input1.dart'));
+ expect(
+ mapping
+ .spanForLocation(SourceLocation(0,
+ sourceUrl: Uri.parse('package:2/output2.dart')))
+ ?.sourceUrl,
+ Uri.parse('file:///path/to/pkg/input2.dart'));
+ expect(
+ mapping
+ .spanForLocation(SourceLocation(0,
+ sourceUrl: Uri.parse('package:3/output.dart')))
+ ?.sourceUrl,
+ Uri.parse('file:///path/to/pkg/input3.dart'));
+
+ expect(mapping.spanFor(0, 0, uri: 'package:1/output.dart')?.sourceUrl,
+ Uri.parse('file:///path/to/pkg/input1.dart'));
+ expect(mapping.spanFor(0, 0, uri: 'package:2/output2.dart')?.sourceUrl,
+ Uri.parse('file:///path/to/pkg/input2.dart'));
+ expect(mapping.spanFor(0, 0, uri: 'package:3/output.dart')?.sourceUrl,
+ Uri.parse('file:///path/to/pkg/input3.dart'));
+ });
+
+ test('unmapped path', () {
+ var span = mapping.spanFor(0, 0, uri: 'unmapped_output.dart')!;
+ expect(span.sourceUrl, Uri.parse('unmapped_output.dart'));
+ expect(span.start.line, equals(0));
+ expect(span.start.column, equals(0));
+
+ span = mapping.spanFor(10, 5, uri: 'unmapped_output.dart')!;
+ expect(span.sourceUrl, Uri.parse('unmapped_output.dart'));
+ expect(span.start.line, equals(10));
+ expect(span.start.column, equals(5));
+ });
+
+ test('missing path', () {
+ expect(() => mapping.spanFor(0, 0), throwsA(anything));
+ });
+
+ test('incomplete paths', () {
+ expect(mapping.spanFor(0, 0, uri: 'output.dart')?.sourceUrl,
+ Uri.parse('file:///path/to/pkg/input1.dart'));
+ expect(mapping.spanFor(0, 0, uri: 'output2.dart')?.sourceUrl,
+ Uri.parse('file:///path/to/pkg/input2.dart'));
+ expect(mapping.spanFor(0, 0, uri: '3/output.dart')?.sourceUrl,
+ Uri.parse('file:///path/to/pkg/input3.dart'));
+ });
+
+ test('parseExtended', () {
+ var mapping = parseExtended(jsonEncode(_sourceMapBundle),
+ mapUrl: 'file:///path/to/map');
+
+ expect(mapping.spanFor(0, 0, uri: 'output.dart')?.sourceUrl,
+ Uri.parse('file:///path/to/pkg/input1.dart'));
+ expect(mapping.spanFor(0, 0, uri: 'output2.dart')?.sourceUrl,
+ Uri.parse('file:///path/to/pkg/input2.dart'));
+ expect(mapping.spanFor(0, 0, uri: '3/output.dart')?.sourceUrl,
+ Uri.parse('file:///path/to/pkg/input3.dart'));
+ });
+
+ test('build bundle incrementally', () {
+ var mapping = MappingBundle();
+
+ mapping.addMapping(parseJson(_mapWithSourceLocationAndName1,
+ mapUrl: 'file:///path/to/map') as SingleMapping);
+ expect(mapping.spanFor(0, 0, uri: 'output.dart')?.sourceUrl,
+ Uri.parse('file:///path/to/pkg/input1.dart'));
+
+ expect(mapping.containsMapping('output2.dart'), isFalse);
+ mapping.addMapping(parseJson(_mapWithSourceLocationAndName2,
+ mapUrl: 'file:///path/to/map') as SingleMapping);
+ expect(mapping.containsMapping('output2.dart'), isTrue);
+ expect(mapping.spanFor(0, 0, uri: 'output2.dart')?.sourceUrl,
+ Uri.parse('file:///path/to/pkg/input2.dart'));
+
+ expect(mapping.containsMapping('3/output.dart'), isFalse);
+ mapping.addMapping(parseJson(_mapWithSourceLocationAndName3,
+ mapUrl: 'file:///path/to/map') as SingleMapping);
+ expect(mapping.containsMapping('3/output.dart'), isTrue);
+ expect(mapping.spanFor(0, 0, uri: '3/output.dart')?.sourceUrl,
+ Uri.parse('file:///path/to/pkg/input3.dart'));
+ });
+
+ // Test that the source map can handle cases where the uri passed in is
+ // not from the expected host but it is still unambiguous which source
+ // map should be used.
+ test('different paths', () {
+ expect(
+ mapping
+ .spanForLocation(SourceLocation(0,
+ sourceUrl: Uri.parse('http://localhost/output.dart')))
+ ?.sourceUrl,
+ Uri.parse('file:///path/to/pkg/input1.dart'));
+ expect(
+ mapping
+ .spanForLocation(SourceLocation(0,
+ sourceUrl: Uri.parse('http://localhost/output2.dart')))
+ ?.sourceUrl,
+ Uri.parse('file:///path/to/pkg/input2.dart'));
+ expect(
+ mapping
+ .spanForLocation(SourceLocation(0,
+ sourceUrl: Uri.parse('http://localhost/3/output.dart')))
+ ?.sourceUrl,
+ Uri.parse('file:///path/to/pkg/input3.dart'));
+
+ expect(
+ mapping.spanFor(0, 0, uri: 'http://localhost/output.dart')?.sourceUrl,
+ Uri.parse('file:///path/to/pkg/input1.dart'));
+ expect(
+ mapping
+ .spanFor(0, 0, uri: 'http://localhost/output2.dart')
+ ?.sourceUrl,
+ Uri.parse('file:///path/to/pkg/input2.dart'));
+ expect(
+ mapping
+ .spanFor(0, 0, uri: 'http://localhost/3/output.dart')
+ ?.sourceUrl,
+ Uri.parse('file:///path/to/pkg/input3.dart'));
+ });
+ });
+
+ test('parse and re-emit', () {
+ for (var expected in [
+ expectedMap,
+ _mapWithNoSourceLocation,
+ _mapWithSourceLocation,
+ _mapWithSourceLocationAndName
+ ]) {
+ var mapping = parseJson(expected) as SingleMapping;
+ expect(mapping.toJson(), equals(expected));
+
+ mapping = parseJsonExtended(expected) as SingleMapping;
+ expect(mapping.toJson(), equals(expected));
+ }
+
+ var mapping = parseJsonExtended(_sourceMapBundle) as MappingBundle;
+ expect(mapping.toJson(), equals(_sourceMapBundle));
+ });
+
+ test('parse extensions', () {
+ var map = Map.from(expectedMap);
+ map['x_foo'] = 'a';
+ map['x_bar'] = [3];
+ var mapping = parseJson(map) as SingleMapping;
+ expect(mapping.toJson(), equals(map));
+ expect(mapping.extensions['x_foo'], equals('a'));
+ expect((mapping.extensions['x_bar'] as List).first, equals(3));
+ });
+
+ group('source files', () {
+ group('from fromEntries()', () {
+ test('are null for non-FileLocations', () {
+ var mapping = SingleMapping.fromEntries([
+ Entry(SourceLocation(10, line: 1, column: 8), outputVar1.start, null)
+ ]);
+ expect(mapping.files, equals([null]));
+ });
+
+ test("use a file location's file", () {
+ var mapping = SingleMapping.fromEntries(
+ [Entry(inputVar1.start, outputVar1.start, null)]);
+ expect(mapping.files, equals([input]));
+ });
+ });
+
+ group('from parse()', () {
+ group('are null', () {
+ test('with no sourcesContent field', () {
+ var mapping = parseJson(expectedMap) as SingleMapping;
+ expect(mapping.files, equals([null]));
+ });
+
+ test('with null sourcesContent values', () {
+ var map = Map.from(expectedMap);
+ map['sourcesContent'] = [null];
+ var mapping = parseJson(map) as SingleMapping;
+ expect(mapping.files, equals([null]));
+ });
+
+ test('with a too-short sourcesContent', () {
+ var map = Map.from(expectedMap);
+ map['sourcesContent'] = [];
+ var mapping = parseJson(map) as SingleMapping;
+ expect(mapping.files, equals([null]));
+ });
+ });
+
+ test('are parsed from sourcesContent', () {
+ var map = Map.from(expectedMap);
+ map['sourcesContent'] = ['hello, world!'];
+ var mapping = parseJson(map) as SingleMapping;
+
+ var file = mapping.files[0]!;
+ expect(file.url, equals(Uri.parse('input.dart')));
+ expect(file.getText(0), equals('hello, world!'));
+ });
+ });
+ });
+}
diff --git a/pkgs/source_maps/test/printer_test.dart b/pkgs/source_maps/test/printer_test.dart
new file mode 100644
index 0000000..89265e3
--- /dev/null
+++ b/pkgs/source_maps/test/printer_test.dart
@@ -0,0 +1,126 @@
+// 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 'package:source_maps/source_maps.dart';
+import 'package:source_span/source_span.dart';
+import 'package:test/test.dart';
+
+import 'common.dart';
+
+void main() {
+ test('printer', () {
+ var printer = Printer('output.dart');
+ printer
+ ..add('var ')
+ ..mark(inputVar1)
+ ..add('x = 3;\n')
+ ..mark(inputFunction)
+ ..add('f(')
+ ..mark(inputVar2)
+ ..add('y) => ')
+ ..mark(inputExpr)
+ ..add('x + y;\n');
+ expect(printer.text, outputContent);
+ expect(printer.map, jsonEncode(expectedMap));
+ });
+
+ test('printer projecting marks', () {
+ var out = inputContent.replaceAll('long', '_s');
+ var printer = Printer('output2.dart');
+
+ var segments = inputContent.split('long');
+ expect(segments.length, 6);
+ printer
+ ..mark(ispan(0, 0))
+ ..add(segments[0], projectMarks: true)
+ ..mark(inputVar1)
+ ..add('_s')
+ ..add(segments[1], projectMarks: true)
+ ..mark(inputFunction)
+ ..add('_s')
+ ..add(segments[2], projectMarks: true)
+ ..mark(inputVar2)
+ ..add('_s')
+ ..add(segments[3], projectMarks: true)
+ ..mark(inputExpr)
+ ..add('_s')
+ ..add(segments[4], projectMarks: true)
+ ..add('_s')
+ ..add(segments[5], projectMarks: true);
+
+ expect(printer.text, out);
+ // 8 new lines in the source map:
+ expect(printer.map.split(';').length, 8);
+
+ SourceMapSpan asFixed(SourceMapSpan s) =>
+ SourceMapSpan(s.start, s.end, s.text, isIdentifier: s.isIdentifier);
+
+ // The result is the same if we use fixed positions
+ var printer2 = Printer('output2.dart');
+ printer2
+ ..mark(SourceLocation(0, sourceUrl: 'input.dart').pointSpan())
+ ..add(segments[0], projectMarks: true)
+ ..mark(asFixed(inputVar1))
+ ..add('_s')
+ ..add(segments[1], projectMarks: true)
+ ..mark(asFixed(inputFunction))
+ ..add('_s')
+ ..add(segments[2], projectMarks: true)
+ ..mark(asFixed(inputVar2))
+ ..add('_s')
+ ..add(segments[3], projectMarks: true)
+ ..mark(asFixed(inputExpr))
+ ..add('_s')
+ ..add(segments[4], projectMarks: true)
+ ..add('_s')
+ ..add(segments[5], projectMarks: true);
+
+ expect(printer2.text, out);
+ expect(printer2.map, printer.map);
+ });
+
+ group('nested printer', () {
+ test('simple use', () {
+ var printer = NestedPrinter();
+ printer
+ ..add('var ')
+ ..add('x = 3;\n', span: inputVar1)
+ ..add('f(', span: inputFunction)
+ ..add('y) => ', span: inputVar2)
+ ..add('x + y;\n', span: inputExpr)
+ ..build('output.dart');
+ expect(printer.text, outputContent);
+ expect(printer.map, jsonEncode(expectedMap));
+ });
+
+ test('nested use', () {
+ var printer = NestedPrinter();
+ printer
+ ..add('var ')
+ ..add(NestedPrinter()..add('x = 3;\n', span: inputVar1))
+ ..add('f(', span: inputFunction)
+ ..add(NestedPrinter()..add('y) => ', span: inputVar2))
+ ..add('x + y;\n', span: inputExpr)
+ ..build('output.dart');
+ expect(printer.text, outputContent);
+ expect(printer.map, jsonEncode(expectedMap));
+ });
+
+ test('add indentation', () {
+ var out = inputContent.replaceAll('long', '_s');
+ var lines = inputContent.trim().split('\n');
+ expect(lines.length, 7);
+ var printer = NestedPrinter();
+ for (var i = 0; i < lines.length; i++) {
+ if (i == 5) printer.indent++;
+ printer.addLine(lines[i].replaceAll('long', '_s').trim());
+ if (i == 5) printer.indent--;
+ }
+ printer.build('output.dart');
+ expect(printer.text, out);
+ });
+ });
+}
diff --git a/pkgs/source_maps/test/refactor_test.dart b/pkgs/source_maps/test/refactor_test.dart
new file mode 100644
index 0000000..5bc3818
--- /dev/null
+++ b/pkgs/source_maps/test/refactor_test.dart
@@ -0,0 +1,199 @@
+// 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:source_maps/parser.dart' show Mapping, parse;
+import 'package:source_maps/refactor.dart';
+import 'package:source_span/source_span.dart';
+import 'package:term_glyph/term_glyph.dart' as term_glyph;
+import 'package:test/test.dart';
+
+void main() {
+ setUpAll(() {
+ term_glyph.ascii = true;
+ });
+
+ group('conflict detection', () {
+ var original = '0123456789abcdefghij';
+ var file = SourceFile.fromString(original);
+
+ test('no conflict, in order', () {
+ var txn = TextEditTransaction(original, file);
+ txn.edit(2, 4, '.');
+ txn.edit(5, 5, '|');
+ txn.edit(6, 6, '-');
+ txn.edit(6, 7, '_');
+ expect((txn.commit()..build('')).text, '01.4|5-_789abcdefghij');
+ });
+
+ test('no conflict, out of order', () {
+ var txn = TextEditTransaction(original, file);
+ txn.edit(2, 4, '.');
+ txn.edit(5, 5, '|');
+
+ // Regresion test for issue #404: there is no conflict/overlap for edits
+ // that don't remove any of the original code.
+ txn.edit(6, 7, '_');
+ txn.edit(6, 6, '-');
+ expect((txn.commit()..build('')).text, '01.4|5-_789abcdefghij');
+ });
+
+ test('conflict', () {
+ var txn = TextEditTransaction(original, file);
+ txn.edit(2, 4, '.');
+ txn.edit(3, 3, '-');
+ expect(
+ () => txn.commit(),
+ throwsA(
+ predicate((e) => e.toString().contains('overlapping edits'))));
+ });
+ });
+
+ test('generated source maps', () {
+ var original =
+ '0123456789\n0*23456789\n01*3456789\nabcdefghij\nabcd*fghij\n';
+ var file = SourceFile.fromString(original);
+ var txn = TextEditTransaction(original, file);
+ txn.edit(27, 29, '__\n ');
+ txn.edit(34, 35, '___');
+ var printer = (txn.commit()..build(''));
+ var output = printer.text;
+ var map = parse(printer.map!);
+ expect(output,
+ '0123456789\n0*23456789\n01*34__\n 789\na___cdefghij\nabcd*fghij\n');
+
+ // Line 1 and 2 are unmodified: mapping any column returns the beginning
+ // of the corresponding line:
+ expect(
+ _span(1, 1, map, file),
+ 'line 1, column 1: \n'
+ ' ,\n'
+ '1 | 0123456789\n'
+ ' | ^\n'
+ " '");
+ expect(
+ _span(1, 5, map, file),
+ 'line 1, column 1: \n'
+ ' ,\n'
+ '1 | 0123456789\n'
+ ' | ^\n'
+ " '");
+ expect(
+ _span(2, 1, map, file),
+ 'line 2, column 1: \n'
+ ' ,\n'
+ '2 | 0*23456789\n'
+ ' | ^\n'
+ " '");
+ expect(
+ _span(2, 8, map, file),
+ 'line 2, column 1: \n'
+ ' ,\n'
+ '2 | 0*23456789\n'
+ ' | ^\n'
+ " '");
+
+ // Line 3 is modified part way: mappings before the edits have the right
+ // mapping, after the edits the mapping is null.
+ expect(
+ _span(3, 1, map, file),
+ 'line 3, column 1: \n'
+ ' ,\n'
+ '3 | 01*3456789\n'
+ ' | ^\n'
+ " '");
+ expect(
+ _span(3, 5, map, file),
+ 'line 3, column 1: \n'
+ ' ,\n'
+ '3 | 01*3456789\n'
+ ' | ^\n'
+ " '");
+
+ // Start of edits map to beginning of the edit secion:
+ expect(
+ _span(3, 6, map, file),
+ 'line 3, column 6: \n'
+ ' ,\n'
+ '3 | 01*3456789\n'
+ ' | ^\n'
+ " '");
+ expect(
+ _span(3, 7, map, file),
+ 'line 3, column 6: \n'
+ ' ,\n'
+ '3 | 01*3456789\n'
+ ' | ^\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);
+ expect(
+ _span(4, 5, map, file),
+ 'line 3, column 8: \n'
+ ' ,\n'
+ '3 | 01*3456789\n'
+ ' | ^\n'
+ " '");
+
+ // Subsequent lines are still mapped correctly:
+ // a (in a___cd...)
+ expect(
+ _span(5, 1, map, file),
+ 'line 4, column 1: \n'
+ ' ,\n'
+ '4 | abcdefghij\n'
+ ' | ^\n'
+ " '");
+ // _ (in a___cd...)
+ expect(
+ _span(5, 2, map, file),
+ 'line 4, column 2: \n'
+ ' ,\n'
+ '4 | abcdefghij\n'
+ ' | ^\n'
+ " '");
+ // _ (in a___cd...)
+ expect(
+ _span(5, 3, map, file),
+ 'line 4, column 2: \n'
+ ' ,\n'
+ '4 | abcdefghij\n'
+ ' | ^\n'
+ " '");
+ // _ (in a___cd...)
+ expect(
+ _span(5, 4, map, file),
+ 'line 4, column 2: \n'
+ ' ,\n'
+ '4 | abcdefghij\n'
+ ' | ^\n'
+ " '");
+ // c (in a___cd...)
+ expect(
+ _span(5, 5, map, file),
+ 'line 4, column 3: \n'
+ ' ,\n'
+ '4 | abcdefghij\n'
+ ' | ^\n'
+ " '");
+ expect(
+ _span(6, 1, map, file),
+ 'line 5, column 1: \n'
+ ' ,\n'
+ '5 | abcd*fghij\n'
+ ' | ^\n'
+ " '");
+ expect(
+ _span(6, 8, map, file),
+ 'line 5, column 1: \n'
+ ' ,\n'
+ '5 | abcd*fghij\n'
+ ' | ^\n'
+ " '");
+ });
+}
+
+String? _span(int line, int column, Mapping map, SourceFile file) =>
+ map.spanFor(line - 1, column - 1, files: {'': file})?.message('').trim();
diff --git a/pkgs/source_maps/test/utils_test.dart b/pkgs/source_maps/test/utils_test.dart
new file mode 100644
index 0000000..2516d1e
--- /dev/null
+++ b/pkgs/source_maps/test/utils_test.dart
@@ -0,0 +1,53 @@
+// 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.
+
+/// Tests for the binary search utility algorithm.
+library;
+
+import 'package:source_maps/src/utils.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('binary search', () {
+ test('empty', () {
+ expect(binarySearch([], (x) => true), -1);
+ });
+
+ test('single element', () {
+ expect(binarySearch([1], (x) => true), 0);
+ expect(binarySearch([1], (x) => false), 1);
+ });
+
+ test('no matches', () {
+ var list = [1, 2, 3, 4, 5, 6, 7];
+ expect(binarySearch(list, (x) => false), list.length);
+ });
+
+ test('all match', () {
+ var list = [1, 2, 3, 4, 5, 6, 7];
+ expect(binarySearch(list, (x) => true), 0);
+ });
+
+ test('compare with linear search', () {
+ for (var size = 0; size < 100; size++) {
+ var list = <int>[];
+ for (var i = 0; i < size; i++) {
+ list.add(i);
+ }
+ for (var pos = 0; pos <= size; pos++) {
+ expect(binarySearch(list, (x) => x >= pos),
+ _linearSearch(list, (x) => x >= pos));
+ }
+ }
+ });
+ });
+}
+
+int _linearSearch<T>(List<T> list, bool Function(T) predicate) {
+ if (list.isEmpty) return -1;
+ for (var i = 0; i < list.length; i++) {
+ if (predicate(list[i])) return i;
+ }
+ return list.length;
+}
diff --git a/pkgs/source_maps/test/vlq_test.dart b/pkgs/source_maps/test/vlq_test.dart
new file mode 100644
index 0000000..4568cff
--- /dev/null
+++ b/pkgs/source_maps/test/vlq_test.dart
@@ -0,0 +1,59 @@
+// 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:math';
+
+import 'package:source_maps/src/vlq.dart';
+import 'package:test/test.dart';
+
+void main() {
+ test('encode and decode - simple values', () {
+ expect(encodeVlq(1).join(''), 'C');
+ expect(encodeVlq(2).join(''), 'E');
+ expect(encodeVlq(3).join(''), 'G');
+ expect(encodeVlq(100).join(''), 'oG');
+ expect(decodeVlq('C'.split('').iterator), 1);
+ expect(decodeVlq('E'.split('').iterator), 2);
+ expect(decodeVlq('G'.split('').iterator), 3);
+ expect(decodeVlq('oG'.split('').iterator), 100);
+ });
+
+ test('encode and decode', () {
+ for (var i = -10000; i < 10000; i++) {
+ _checkEncodeDecode(i);
+ }
+ });
+
+ test('only 32-bit ints allowed', () {
+ var maxInt = (pow(2, 31) as int) - 1;
+ var minInt = -(pow(2, 31) as int);
+ _checkEncodeDecode(maxInt - 1);
+ _checkEncodeDecode(minInt + 1);
+ _checkEncodeDecode(maxInt);
+ _checkEncodeDecode(minInt);
+
+ expect(encodeVlq(minInt).join(''), 'hgggggE');
+ expect(decodeVlq('hgggggE'.split('').iterator), minInt);
+
+ expect(() => encodeVlq(maxInt + 1), throwsA(anything));
+ expect(() => encodeVlq(maxInt + 2), throwsA(anything));
+ expect(() => encodeVlq(minInt - 1), throwsA(anything));
+ expect(() => encodeVlq(minInt - 2), throwsA(anything));
+
+ // if we allowed more than 32 bits, these would be the expected encodings
+ // for the large numbers above.
+ expect(() => decodeVlq('ggggggE'.split('').iterator), throwsA(anything));
+ expect(() => decodeVlq('igggggE'.split('').iterator), throwsA(anything));
+ expect(() => decodeVlq('jgggggE'.split('').iterator), throwsA(anything));
+ expect(() => decodeVlq('lgggggE'.split('').iterator), throwsA(anything));
+ },
+ // This test uses integers so large they overflow in JS.
+ testOn: 'dart-vm');
+}
+
+void _checkEncodeDecode(int value) {
+ var encoded = encodeVlq(value);
+ expect(decodeVlq(encoded.iterator), value);
+ expect(decodeVlq(encoded.join('').split('').iterator), value);
+}
diff --git a/pkgs/source_span/.gitignore b/pkgs/source_span/.gitignore
new file mode 100644
index 0000000..ab3cb76
--- /dev/null
+++ b/pkgs/source_span/.gitignore
@@ -0,0 +1,16 @@
+# Don’t commit the following directories created by pub.
+.buildlog
+.dart_tool/
+.pub/
+build/
+packages
+.packages
+
+# Or the files created by dart2js.
+*.dart.js
+*.js_
+*.js.deps
+*.js.map
+
+# Include when developing application packages.
+pubspec.lock
diff --git a/pkgs/source_span/CHANGELOG.md b/pkgs/source_span/CHANGELOG.md
new file mode 100644
index 0000000..b8319d7
--- /dev/null
+++ b/pkgs/source_span/CHANGELOG.md
@@ -0,0 +1,240 @@
+## 1.10.1
+
+* Require Dart 3.1
+* Move to `dart-lang/tools` monorepo.
+
+## 1.10.0
+
+* Add a `SourceFile.codeUnits` property.
+* Require Dart 2.18
+* Add an API usage example in `example/`.
+
+## 1.9.1
+
+* Properly handle multi-line labels for multi-span highlights.
+
+* Populate the pubspec `repository` field.
+
+## 1.9.0
+
+* Add `SourceSpanWithContextExtension.subspan` that returns a
+ `SourceSpanWithContext` rather than a plain `SourceSpan`.
+
+## 1.8.2
+
+* Fix a bug where highlighting multiple spans with `null` URLs could cause an
+ assertion error. Now when multiple spans are passed with `null` URLs, they're
+ highlighted as though they all come from different source files.
+
+## 1.8.1
+
+* Fix a bug where the URL header for the highlights with multiple files would
+ get omitted only one span has a non-null URI.
+
+## 1.8.0
+
+* Stable release for null safety.
+
+## 1.7.0
+
+* Add a `SourceSpan.subspan()` extension method which returns a slice of an
+ existing source span.
+
+## 1.6.0
+
+* Add support for highlighting multiple source spans at once, providing more
+ context for span-based messages. This is exposed through the new APIs
+ `SourceSpan.highlightMultiple()` and `SourceSpan.messageMultiple()` (both
+ extension methods), `MultiSourceSpanException`, and
+ `MultiSourceSpanFormatException`.
+
+## 1.5.6
+
+* Fix padding around line numbers that are powers of 10 in
+ `FileSpan.highlight()`.
+
+## 1.5.5
+
+* Fix a bug where `FileSpan.highlight()` would crash for spans that covered a
+ trailing newline and a single additional empty line.
+
+## 1.5.4
+
+* `FileSpan.highlight()` now properly highlights point spans at the beginning of
+ lines.
+
+## 1.5.3
+
+* Fix an edge case where `FileSpan.highlight()` would put the highlight
+ indicator in the wrong position when highlighting a point span after the end
+ of a file.
+
+## 1.5.2
+
+* `SourceFile.span()` now goes to the end of the file by default, rather than
+ ending one character before the end of the file. This matches the documented
+ behavior.
+
+* `FileSpan.context` now includes the full line on which the span appears for
+ empty spans at the beginning and end of lines.
+
+* Fix an edge case where `FileSpan.highlight()` could crash when highlighting a
+ span that ended with an empty line.
+
+## 1.5.1
+
+* Produce better source span highlights for multi-line spans that cover the
+ entire last line of the span, including the newline.
+
+* Produce better source span highlights for spans that contain Windows-style
+ newlines.
+
+## 1.5.0
+
+* Improve the output of `SourceSpan.highlight()` and `SourceSpan.message()`:
+
+ * They now include line numbers.
+ * They will now print every line of a multiline span.
+ * They will now use Unicode box-drawing characters by default (this can be
+ controlled using [`term_glyph.ascii`][]).
+
+[`term_glyph.ascii`]: https://pub.dartlang.org/documentation/term_glyph/latest/term_glyph/ascii.html
+
+## 1.4.1
+
+* Set max SDK version to `<3.0.0`, and adjust other dependencies.
+
+## 1.4.0
+
+* The `new SourceFile()` constructor is deprecated. This constructed a source
+ file from a string's runes, rather than its code units, which runs counter to
+ the way Dart handles strings otherwise. The `new StringFile.fromString()`
+ constructor (see below) should be used instead.
+
+* The `new SourceFile.fromString()` constructor was added. This works like `new
+ SourceFile()`, except it uses code units rather than runes.
+
+* The current behavior when characters larger than `0xFFFF` are passed to `new
+ SourceFile.decoded()` is now considered deprecated.
+
+## 1.3.1
+
+* Properly highlight spans for lines that include tabs with
+ `SourceSpan.highlight()` and `SourceSpan.message()`.
+
+## 1.3.0
+
+* Add `SourceSpan.highlight()`, which returns just the highlighted text that
+ would be included in `SourceSpan.message()`.
+
+## 1.2.4
+
+* Fix a new strong mode error.
+
+## 1.2.3
+
+* Fix a bug where a point span at the end of a file without a trailing newline
+ would be printed incorrectly.
+
+## 1.2.2
+
+* Allow `SourceSpanException.message`, `SourceSpanFormatException.source`, and
+ `SourceSpanWithContext.context` to be overridden in strong mode.
+
+## 1.2.1
+
+* Fix the declared type of `FileSpan.start` and `FileSpan.end`. In 1.2.0 these
+ were mistakenly changed from `FileLocation` to `SourceLocation`.
+
+## 1.2.0
+
+* **Deprecated:** Extending `SourceLocation` directly is deprecated. Instead,
+ extend the new `SourceLocationBase` class or mix in the new
+ `SourceLocationMixin` mixin.
+
+* Dramatically improve the performance of `FileLocation`.
+
+## 1.1.6
+
+* Optimize `getLine()` in `SourceFile` when repeatedly called.
+
+## 1.1.5
+
+* Fixed another case in which `FileSpan.union` could throw an exception for
+ external implementations of `FileSpan`.
+
+## 1.1.4
+
+* Eliminated dart2js warning about overriding `==`, but not `hashCode`.
+
+## 1.1.3
+
+* `FileSpan.compareTo`, `FileSpan.==`, `FileSpan.union`, and `FileSpan.expand`
+ no longer throw exceptions for external implementations of `FileSpan`.
+
+* `FileSpan.hashCode` now fully agrees with `FileSpan.==`.
+
+## 1.1.2
+
+* Fixed validation in `SourceSpanWithContext` to allow multiple occurrences of
+ `text` within `context`.
+
+## 1.1.1
+
+* Fixed `FileSpan`'s context to include the full span text, not just the first
+ line of it.
+
+## 1.1.0
+
+* Added `SourceSpanWithContext`: a span that also includes the full line of text
+ that contains the span.
+
+## 1.0.3
+
+* Cleanup equality operator to accept any Object rather than just a
+ `SourceLocation`.
+
+## 1.0.2
+
+* Avoid unintentionally allocating extra objects for internal `FileSpan`
+ operations.
+
+* Ensure that `SourceSpan.operator==` works on arbitrary `Object`s.
+
+## 1.0.1
+
+* Use a more compact internal representation for `FileSpan`.
+
+## 1.0.0
+
+This package was extracted from the
+[`source_maps`](https://pub.dev/packages/source_maps) package, but the
+API has many differences. Among them:
+
+* `Span` has been renamed to `SourceSpan` and `Location` has been renamed to
+ `SourceLocation` to clarify their purpose and maintain consistency with the
+ package name. Likewise, `SpanException` is now `SourceSpanException` and
+ `SpanFormatException` is not `SourceSpanFormatException`.
+
+* `FixedSpan` and `FixedLocation` have been rolled into the `Span` and
+ `Location` classes, respectively.
+
+* `SourceFile` is more aggressive about validating its arguments. Out-of-bounds
+ lines, columns, and offsets will now throw errors rather than be silently
+ clamped.
+
+* `SourceSpan.sourceUrl`, `SourceLocation.sourceUrl`, and `SourceFile.url` now
+ return `Uri` objects rather than `String`s. The constructors allow either
+ `String`s or `Uri`s.
+
+* `Span.getLocationMessage` and `SourceFile.getLocationMessage` are now
+ `SourceSpan.message` and `SourceFile.message`, respectively. Rather than
+ taking both a `useColor` and a `color` parameter, they now take a single
+ `color` parameter that controls both whether and which color is used.
+
+* `Span.isIdentifier` has been removed. This property doesn't make sense outside
+ of a source map context.
+
+* `SourceFileSegment` has been removed. This class wasn't widely used and was
+ inconsistent in its choice of which parameters were considered relative and
+ which absolute.
diff --git a/pkgs/source_span/LICENSE b/pkgs/source_span/LICENSE
new file mode 100644
index 0000000..000cd7b
--- /dev/null
+++ b/pkgs/source_span/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2014, 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.
diff --git a/pkgs/source_span/README.md b/pkgs/source_span/README.md
new file mode 100644
index 0000000..b4ce25f
--- /dev/null
+++ b/pkgs/source_span/README.md
@@ -0,0 +1,21 @@
+[](https://github.com/dart-lang/tools/actions/workflows/source_span.yaml)
+[](https://pub.dev/packages/source_span)
+[](https://pub.dev/packages/source_span/publisher)
+
+## About this package
+
+`source_span` is a library for tracking locations in source code. It's designed
+to provide a standard representation for source code locations and spans so that
+disparate packages can easily pass them among one another, and to make it easy
+to generate human-friendly messages associated with a given piece of code.
+
+The most commonly-used class is the package's namesake, `SourceSpan`. It
+represents a span of characters in some source file, and is often attached to an
+object that has been parsed to indicate where it was parsed from. It provides
+access to the text of the span via `SourceSpan.text` and can be used to produce
+human-friendly messages using `SourceSpan.message()`.
+
+When parsing code from a file, `SourceFile` is useful. Not only does it provide
+an efficient means of computing line and column numbers, `SourceFile.span()`
+returns special `FileSpan`s that are able to provide more context for their
+error messages.
diff --git a/pkgs/source_span/analysis_options.yaml b/pkgs/source_span/analysis_options.yaml
new file mode 100644
index 0000000..d2ebdbf
--- /dev/null
+++ b/pkgs/source_span/analysis_options.yaml
@@ -0,0 +1,32 @@
+# https://dart.dev/guides/language/analysis-options
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-casts: true
+ strict-inference: true
+ strict-raw-types: true
+
+linter:
+ rules:
+ - avoid_bool_literals_in_conditional_expressions
+ - avoid_classes_with_only_static_members
+ - avoid_private_typedef_functions
+ - avoid_redundant_argument_values
+ - avoid_returning_this
+ - avoid_unused_constructor_parameters
+ - avoid_void_async
+ - cancel_subscriptions
+ - cascade_invocations
+ - join_return_with_assignment
+ - literal_only_boolean_expressions
+ - missing_whitespace_between_adjacent_strings
+ - no_adjacent_strings_in_list
+ - prefer_const_declarations
+ - prefer_expression_function_bodies
+ - prefer_final_locals
+ - unnecessary_await_in_return
+ - unnecessary_raw_strings
+ - use_if_null_to_convert_nulls_to_bools
+ - use_raw_strings
+ - use_string_buffers
diff --git a/pkgs/source_span/example/main.dart b/pkgs/source_span/example/main.dart
new file mode 100644
index 0000000..e296765
--- /dev/null
+++ b/pkgs/source_span/example/main.dart
@@ -0,0 +1,51 @@
+// Copyright (c) 2023, 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:source_span/source_span.dart';
+
+void main(List<String> args) {
+ final file = File('README.md');
+ final contents = file.readAsStringSync();
+
+ final sourceFile = SourceFile.fromString(contents, url: file.uri);
+ final spans = _parseFile(contents, sourceFile);
+
+ for (var span in spans.take(30)) {
+ print('[${span.start.line + 1}:${span.start.column + 1}] ${span.text}');
+ }
+}
+
+Iterable<SourceSpan> _parseFile(String contents, SourceFile sourceFile) sync* {
+ var wordStart = 0;
+ var inWhiteSpace = true;
+
+ for (var i = 0; i < contents.length; i++) {
+ final codeUnit = contents.codeUnitAt(i);
+
+ if (codeUnit == _eol || codeUnit == _space) {
+ if (!inWhiteSpace) {
+ inWhiteSpace = true;
+
+ // emit a word
+ yield sourceFile.span(wordStart, i);
+ }
+ } else {
+ if (inWhiteSpace) {
+ inWhiteSpace = false;
+
+ wordStart = i;
+ }
+ }
+ }
+
+ if (!inWhiteSpace) {
+ // emit a word
+ yield sourceFile.span(wordStart, contents.length);
+ }
+}
+
+const int _eol = 10;
+const int _space = 32;
diff --git a/pkgs/source_span/lib/source_span.dart b/pkgs/source_span/lib/source_span.dart
new file mode 100644
index 0000000..534a3a7
--- /dev/null
+++ b/pkgs/source_span/lib/source_span.dart
@@ -0,0 +1,11 @@
+// Copyright (c) 2014, 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/file.dart';
+export 'src/location.dart';
+export 'src/location_mixin.dart';
+export 'src/span.dart';
+export 'src/span_exception.dart';
+export 'src/span_mixin.dart';
+export 'src/span_with_context.dart';
diff --git a/pkgs/source_span/lib/src/charcode.dart b/pkgs/source_span/lib/src/charcode.dart
new file mode 100644
index 0000000..5182638
--- /dev/null
+++ b/pkgs/source_span/lib/src/charcode.dart
@@ -0,0 +1,15 @@
+// Copyright (c) 2020, 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.
+
+/// "Carriage return" control character.
+const int $cr = 0x0D;
+
+/// "Line feed" control character.
+const int $lf = 0x0A;
+
+/// Space character.
+const int $space = 0x20;
+
+/// "Horizontal Tab" control character, common name.
+const int $tab = 0x09;
diff --git a/pkgs/source_span/lib/src/colors.dart b/pkgs/source_span/lib/src/colors.dart
new file mode 100644
index 0000000..b48d468
--- /dev/null
+++ b/pkgs/source_span/lib/src/colors.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2014, 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.
+
+// Color constants used for generating messages.
+const String red = '\u001b[31m';
+
+const String yellow = '\u001b[33m';
+
+const String blue = '\u001b[34m';
+
+const String none = '\u001b[0m';
diff --git a/pkgs/source_span/lib/src/file.dart b/pkgs/source_span/lib/src/file.dart
new file mode 100644
index 0000000..74c9234
--- /dev/null
+++ b/pkgs/source_span/lib/src/file.dart
@@ -0,0 +1,454 @@
+// Copyright (c) 2014, 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;
+import 'dart:typed_data';
+
+import 'location.dart';
+import 'location_mixin.dart';
+import 'span.dart';
+import 'span_mixin.dart';
+import 'span_with_context.dart';
+
+// Constants to determine end-of-lines.
+const int _lf = 10;
+const int _cr = 13;
+
+/// A class representing a source file.
+///
+/// This doesn't necessarily have to correspond to a file on disk, just a chunk
+/// of text usually with a URL associated with it.
+class SourceFile {
+ /// The URL where the source file is located.
+ ///
+ /// This may be null, indicating that the URL is unknown or unavailable.
+ final Uri? url;
+
+ /// An array of offsets for each line beginning in the file.
+ ///
+ /// Each offset refers to the first character *after* the newline. If the
+ /// source file has a trailing newline, the final offset won't actually be in
+ /// the file.
+ final _lineStarts = <int>[0];
+
+ /// The code units of the characters in the file.
+ ///
+ /// If this was constructed with the deprecated `SourceFile()` constructor,
+ /// this will instead contain the code _points_ of the characters in the file
+ /// (so characters above 2^16 are represented as individual integers rather
+ /// than surrogate pairs).
+ List<int> get codeUnits => _decodedChars;
+
+ /// The code units of the characters in this file.
+ final Uint32List _decodedChars;
+
+ /// The length of the file in characters.
+ int get length => _decodedChars.length;
+
+ /// The number of lines in the file.
+ int get lines => _lineStarts.length;
+
+ /// The line that the offset fell on the last time [getLine] was called.
+ ///
+ /// In many cases, sequential calls to getLine() are for nearby, usually
+ /// increasing offsets. In that case, we can find the line for an offset
+ /// quickly by first checking to see if the offset is on the same line as the
+ /// previous result.
+ int? _cachedLine;
+
+ /// This constructor is deprecated.
+ ///
+ /// Use [SourceFile.fromString] instead.
+ @Deprecated('Will be removed in 2.0.0')
+ SourceFile(String text, {Object? url}) : this.decoded(text.runes, url: url);
+
+ /// Creates a new source file from [text].
+ ///
+ /// [url] may be either a [String], a [Uri], or `null`.
+ SourceFile.fromString(String text, {Object? url})
+ : this.decoded(text.codeUnits, url: url);
+
+ /// Creates a new source file from a list of decoded code units.
+ ///
+ /// [url] may be either a [String], a [Uri], or `null`.
+ ///
+ /// Currently, if [decodedChars] contains characters larger than `0xFFFF`,
+ /// they'll be treated as single characters rather than being split into
+ /// surrogate pairs. **This behavior is deprecated**. For
+ /// forwards-compatibility, callers should only pass in characters less than
+ /// or equal to `0xFFFF`.
+ SourceFile.decoded(Iterable<int> decodedChars, {Object? url})
+ : url = url is String ? Uri.parse(url) : url as Uri?,
+ _decodedChars = Uint32List.fromList(decodedChars.toList()) {
+ for (var i = 0; i < _decodedChars.length; i++) {
+ var c = _decodedChars[i];
+ if (c == _cr) {
+ // Return not followed by newline is treated as a newline
+ final j = i + 1;
+ if (j >= _decodedChars.length || _decodedChars[j] != _lf) c = _lf;
+ }
+ if (c == _lf) _lineStarts.add(i + 1);
+ }
+ }
+
+ /// Returns a span from [start] to [end] (exclusive).
+ ///
+ /// If [end] isn't passed, it defaults to the end of the file.
+ FileSpan span(int start, [int? end]) {
+ end ??= length;
+ return _FileSpan(this, start, end);
+ }
+
+ /// Returns a location at [offset].
+ FileLocation location(int offset) => FileLocation._(this, offset);
+
+ /// Gets the 0-based line corresponding to [offset].
+ int getLine(int offset) {
+ if (offset < 0) {
+ throw RangeError('Offset may not be negative, was $offset.');
+ } else if (offset > length) {
+ throw RangeError('Offset $offset must not be greater than the number '
+ 'of characters in the file, $length.');
+ }
+
+ if (offset < _lineStarts.first) return -1;
+ if (offset >= _lineStarts.last) return _lineStarts.length - 1;
+
+ if (_isNearCachedLine(offset)) return _cachedLine!;
+
+ _cachedLine = _binarySearch(offset) - 1;
+ return _cachedLine!;
+ }
+
+ /// Returns `true` if [offset] is near [_cachedLine].
+ ///
+ /// Checks on [_cachedLine] and the next line. If it's on the next line, it
+ /// updates [_cachedLine] to point to that.
+ bool _isNearCachedLine(int offset) {
+ if (_cachedLine == null) return false;
+ final cachedLine = _cachedLine!;
+
+ // See if it's before the cached line.
+ if (offset < _lineStarts[cachedLine]) return false;
+
+ // See if it's on the cached line.
+ if (cachedLine >= _lineStarts.length - 1 ||
+ offset < _lineStarts[cachedLine + 1]) {
+ return true;
+ }
+
+ // See if it's on the next line.
+ if (cachedLine >= _lineStarts.length - 2 ||
+ offset < _lineStarts[cachedLine + 2]) {
+ _cachedLine = cachedLine + 1;
+ return true;
+ }
+
+ return false;
+ }
+
+ /// Binary search through [_lineStarts] to find the line containing [offset].
+ ///
+ /// Returns the index of the line in [_lineStarts].
+ int _binarySearch(int offset) {
+ var min = 0;
+ var max = _lineStarts.length - 1;
+ while (min < max) {
+ final half = min + ((max - min) ~/ 2);
+ if (_lineStarts[half] > offset) {
+ max = half;
+ } else {
+ min = half + 1;
+ }
+ }
+
+ return max;
+ }
+
+ /// Gets the 0-based column corresponding to [offset].
+ ///
+ /// If [line] is passed, it's assumed to be the line containing [offset] and
+ /// is used to more efficiently compute the column.
+ int getColumn(int offset, {int? line}) {
+ if (offset < 0) {
+ throw RangeError('Offset may not be negative, was $offset.');
+ } else if (offset > length) {
+ throw RangeError('Offset $offset must be not be greater than the '
+ 'number of characters in the file, $length.');
+ }
+
+ if (line == null) {
+ line = getLine(offset);
+ } else if (line < 0) {
+ throw RangeError('Line may not be negative, was $line.');
+ } else if (line >= lines) {
+ throw RangeError('Line $line must be less than the number of '
+ 'lines in the file, $lines.');
+ }
+
+ final lineStart = _lineStarts[line];
+ if (lineStart > offset) {
+ throw RangeError('Line $line comes after offset $offset.');
+ }
+
+ return offset - lineStart;
+ }
+
+ /// Gets the offset for a [line] and [column].
+ ///
+ /// [column] defaults to 0.
+ int getOffset(int line, [int? column]) {
+ column ??= 0;
+
+ if (line < 0) {
+ throw RangeError('Line may not be negative, was $line.');
+ } else if (line >= lines) {
+ throw RangeError('Line $line must be less than the number of '
+ 'lines in the file, $lines.');
+ } else if (column < 0) {
+ throw RangeError('Column may not be negative, was $column.');
+ }
+
+ final result = _lineStarts[line] + column;
+ if (result > length ||
+ (line + 1 < lines && result >= _lineStarts[line + 1])) {
+ throw RangeError("Line $line doesn't have $column columns.");
+ }
+
+ return result;
+ }
+
+ /// Returns the text of the file from [start] to [end] (exclusive).
+ ///
+ /// If [end] isn't passed, it defaults to the end of the file.
+ String getText(int start, [int? end]) =>
+ String.fromCharCodes(_decodedChars.sublist(start, end));
+}
+
+/// A [SourceLocation] within a [SourceFile].
+///
+/// Unlike the base [SourceLocation], [FileLocation] lazily computes its line
+/// and column values based on its offset and the contents of [file].
+///
+/// A [FileLocation] can be created using [SourceFile.location].
+class FileLocation extends SourceLocationMixin implements SourceLocation {
+ /// The [file] that `this` belongs to.
+ final SourceFile file;
+
+ @override
+ final int offset;
+
+ @override
+ Uri? get sourceUrl => file.url;
+
+ @override
+ int get line => file.getLine(offset);
+
+ @override
+ int get column => file.getColumn(offset);
+
+ FileLocation._(this.file, this.offset) {
+ if (offset < 0) {
+ throw RangeError('Offset may not be negative, was $offset.');
+ } else if (offset > file.length) {
+ throw RangeError('Offset $offset must not be greater than the number '
+ 'of characters in the file, ${file.length}.');
+ }
+ }
+
+ @override
+ FileSpan pointSpan() => _FileSpan(file, offset, offset);
+}
+
+/// A [SourceSpan] within a [SourceFile].
+///
+/// Unlike the base [SourceSpan], [FileSpan] lazily computes its line and column
+/// values based on its offset and the contents of [file]. [SourceSpan.message]
+/// is also able to provide more context then [SourceSpan.message], and
+/// [SourceSpan.union] will return a [FileSpan] if possible.
+///
+/// A [FileSpan] can be created using [SourceFile.span].
+abstract class FileSpan implements SourceSpanWithContext {
+ /// The [file] that `this` belongs to.
+ SourceFile get file;
+
+ @override
+ FileLocation get start;
+
+ @override
+ FileLocation get end;
+
+ /// Returns a new span that covers both `this` and [other].
+ ///
+ /// Unlike [union], [other] may be disjoint from `this`. If it is, the text
+ /// between the two will be covered by the returned span.
+ FileSpan expand(FileSpan other);
+}
+
+/// The implementation of [FileSpan].
+///
+/// This is split into a separate class so that `is _FileSpan` checks can be run
+/// to make certain operations more efficient. If we used `is FileSpan`, that
+/// would break if external classes implemented the interface.
+class _FileSpan extends SourceSpanMixin implements FileSpan {
+ @override
+ final SourceFile file;
+
+ /// The offset of the beginning of the span.
+ ///
+ /// [start] is lazily generated from this to avoid allocating unnecessary
+ /// objects.
+ final int _start;
+
+ /// The offset of the end of the span.
+ ///
+ /// [end] is lazily generated from this to avoid allocating unnecessary
+ /// objects.
+ final int _end;
+
+ @override
+ Uri? get sourceUrl => file.url;
+
+ @override
+ int get length => _end - _start;
+
+ @override
+ FileLocation get start => FileLocation._(file, _start);
+
+ @override
+ FileLocation get end => FileLocation._(file, _end);
+
+ @override
+ String get text => file.getText(_start, _end);
+
+ @override
+ String get context {
+ final endLine = file.getLine(_end);
+ final endColumn = file.getColumn(_end);
+
+ int? endOffset;
+ if (endColumn == 0 && endLine != 0) {
+ // If [end] is at the very beginning of the line, the span covers the
+ // previous newline, so we only want to include the previous line in the
+ // context...
+
+ if (length == 0) {
+ // ...unless this is a point span, in which case we want to include the
+ // next line (or the empty string if this is the end of the file).
+ return endLine == file.lines - 1
+ ? ''
+ : file.getText(
+ file.getOffset(endLine), file.getOffset(endLine + 1));
+ }
+
+ endOffset = _end;
+ } else if (endLine == file.lines - 1) {
+ // If the span covers the last line of the file, the context should go all
+ // the way to the end of the file.
+ endOffset = file.length;
+ } else {
+ // Otherwise, the context should cover the full line on which [end]
+ // appears.
+ endOffset = file.getOffset(endLine + 1);
+ }
+
+ return file.getText(file.getOffset(file.getLine(_start)), endOffset);
+ }
+
+ _FileSpan(this.file, this._start, this._end) {
+ if (_end < _start) {
+ throw ArgumentError('End $_end must come after start $_start.');
+ } else if (_end > file.length) {
+ throw RangeError('End $_end must not be greater than the number '
+ 'of characters in the file, ${file.length}.');
+ } else if (_start < 0) {
+ throw RangeError('Start may not be negative, was $_start.');
+ }
+ }
+
+ @override
+ int compareTo(SourceSpan other) {
+ if (other is! _FileSpan) return super.compareTo(other);
+
+ final result = _start.compareTo(other._start);
+ return result == 0 ? _end.compareTo(other._end) : result;
+ }
+
+ @override
+ SourceSpan union(SourceSpan other) {
+ if (other is! FileSpan) return super.union(other);
+
+ final span = expand(other);
+
+ if (other is _FileSpan) {
+ if (_start > other._end || other._start > _end) {
+ throw ArgumentError('Spans $this and $other are disjoint.');
+ }
+ } else {
+ if (_start > other.end.offset || other.start.offset > _end) {
+ throw ArgumentError('Spans $this and $other are disjoint.');
+ }
+ }
+
+ return span;
+ }
+
+ @override
+ bool operator ==(Object other) {
+ if (other is! FileSpan) return super == other;
+ if (other is! _FileSpan) {
+ return super == other && sourceUrl == other.sourceUrl;
+ }
+
+ return _start == other._start &&
+ _end == other._end &&
+ sourceUrl == other.sourceUrl;
+ }
+
+ @override
+ int get hashCode => Object.hash(_start, _end, sourceUrl);
+
+ /// Returns a new span that covers both `this` and [other].
+ ///
+ /// Unlike [union], [other] may be disjoint from `this`. If it is, the text
+ /// between the two will be covered by the returned span.
+ @override
+ FileSpan expand(FileSpan other) {
+ if (sourceUrl != other.sourceUrl) {
+ throw ArgumentError('Source URLs "$sourceUrl" and '
+ " \"${other.sourceUrl}\" don't match.");
+ }
+
+ if (other is _FileSpan) {
+ final start = math.min(_start, other._start);
+ final end = math.max(_end, other._end);
+ return _FileSpan(file, start, end);
+ } else {
+ final start = math.min(_start, other.start.offset);
+ final end = math.max(_end, other.end.offset);
+ return _FileSpan(file, start, end);
+ }
+ }
+
+ /// See `SourceSpanExtension.subspan`.
+ FileSpan subspan(int start, [int? end]) {
+ RangeError.checkValidRange(start, end, length);
+ if (start == 0 && (end == null || end == length)) return this;
+ return file.span(_start + start, end == null ? _end : _start + end);
+ }
+}
+
+// TODO(#52): Move these to instance methods in the next breaking release.
+/// Extension methods on the [FileSpan] API.
+extension FileSpanExtension on FileSpan {
+ /// See `SourceSpanExtension.subspan`.
+ FileSpan subspan(int start, [int? end]) {
+ RangeError.checkValidRange(start, end, length);
+ if (start == 0 && (end == null || end == length)) return this;
+
+ final startOffset = this.start.offset;
+ return file.span(
+ startOffset + start, end == null ? this.end.offset : startOffset + end);
+ }
+}
diff --git a/pkgs/source_span/lib/src/highlighter.dart b/pkgs/source_span/lib/src/highlighter.dart
new file mode 100644
index 0000000..19e04d0
--- /dev/null
+++ b/pkgs/source_span/lib/src/highlighter.dart
@@ -0,0 +1,727 @@
+// 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.
+
+import 'dart:math' as math;
+
+import 'package:collection/collection.dart';
+import 'package:path/path.dart' as p;
+import 'package:term_glyph/term_glyph.dart' as glyph;
+
+import 'charcode.dart';
+import 'colors.dart' as colors;
+import 'location.dart';
+import 'span.dart';
+import 'span_with_context.dart';
+import 'utils.dart';
+
+/// A class for writing a chunk of text with a particular span highlighted.
+class Highlighter {
+ /// The lines to display, including context around the highlighted spans.
+ final List<_Line> _lines;
+
+ /// The color to highlight the primary [_Highlight] within its context, or
+ /// `null` if it should not be colored.
+ final String? _primaryColor;
+
+ /// The color to highlight the secondary [_Highlight]s within their context,
+ /// or `null` if they should not be colored.
+ final String? _secondaryColor;
+
+ /// The number of characters before the bar in the sidebar.
+ final int _paddingBeforeSidebar;
+
+ /// The maximum number of multiline spans that cover any part of a single
+ /// line in [_lines].
+ final int _maxMultilineSpans;
+
+ /// Whether [_lines] includes lines from multiple different files.
+ final bool _multipleFiles;
+
+ /// The buffer to which to write the result.
+ final _buffer = StringBuffer();
+
+ /// The number of spaces to render for hard tabs that appear in `_span.text`.
+ ///
+ /// We don't want to render raw tabs, because they'll mess up our character
+ /// alignment.
+ static const _spacesPerTab = 4;
+
+ /// Creates a [Highlighter] that will return a string highlighting [span]
+ /// within the text of its file when [highlight] is called.
+ ///
+ /// [color] may either be a [String], a [bool], or `null`. If it's a string,
+ /// it indicates an [ANSI terminal color escape][] that should be used to
+ /// highlight [span]'s text (for example, `"\u001b[31m"` will color red). If
+ /// it's `true`, it indicates that the text should be highlighted using the
+ /// default color. If it's `false` or `null`, it indicates that no color
+ /// should be used.
+ ///
+ /// [ANSI terminal color escape]: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
+ Highlighter(SourceSpan span, {Object? color})
+ : this._(_collateLines([_Highlight(span, primary: true)]), () {
+ if (color == true) return colors.red;
+ if (color == false) return null;
+ return color as String?;
+ }(), null);
+
+ /// Creates a [Highlighter] that will return a string highlighting
+ /// [primarySpan] as well as all the spans in [secondarySpans] within the text
+ /// of their file when [highlight] is called.
+ ///
+ /// Each span has an associated label that will be written alongside it. For
+ /// [primarySpan] this message is [primaryLabel], and for [secondarySpans] the
+ /// labels are the map values.
+ ///
+ /// If [color] is `true`, this will use [ANSI terminal color escapes][] to
+ /// highlight the text. The [primarySpan] will be highlighted with
+ /// [primaryColor] (which defaults to red), and the [secondarySpans] will be
+ /// highlighted with [secondaryColor] (which defaults to blue). These
+ /// arguments are ignored if [color] is `false`.
+ ///
+ /// [ANSI terminal color escape]: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
+ Highlighter.multiple(SourceSpan primarySpan, String primaryLabel,
+ Map<SourceSpan, String> secondarySpans,
+ {bool color = false, String? primaryColor, String? secondaryColor})
+ : this._(
+ _collateLines([
+ _Highlight(primarySpan, label: primaryLabel, primary: true),
+ for (var entry in secondarySpans.entries)
+ _Highlight(entry.key, label: entry.value)
+ ]),
+ color ? (primaryColor ?? colors.red) : null,
+ color ? (secondaryColor ?? colors.blue) : null);
+
+ Highlighter._(this._lines, this._primaryColor, this._secondaryColor)
+ : _paddingBeforeSidebar = 1 +
+ math.max<int>(
+ // In a purely mathematical world, floor(log10(n)) would give the
+ // number of digits in n, but floating point errors render that
+ // unreliable in practice.
+ (_lines.last.number + 1).toString().length,
+ // If [_lines] aren't contiguous, we'll write "..." in place of a
+ // line number.
+ _contiguous(_lines) ? 0 : 3,
+ ),
+ _maxMultilineSpans = _lines
+ .map((line) => line.highlights
+ .where((highlight) => isMultiline(highlight.span))
+ .length)
+ .reduce(math.max),
+ _multipleFiles = !isAllTheSame(_lines.map((line) => line.url));
+
+ /// Returns whether [lines] contains any adjacent lines from the same source
+ /// file that aren't adjacent in the original file.
+ static bool _contiguous(List<_Line> lines) {
+ for (var i = 0; i < lines.length - 1; i++) {
+ final thisLine = lines[i];
+ final nextLine = lines[i + 1];
+ if (thisLine.number + 1 != nextLine.number &&
+ thisLine.url == nextLine.url) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /// Collect all the source lines from the contexts of all spans in
+ /// [highlights], and associates them with the highlights that cover them.
+ static List<_Line> _collateLines(List<_Highlight> highlights) {
+ // Assign spans without URLs opaque Objects as keys. Each such Object will
+ // be different, but they can then be used later on to determine which lines
+ // came from the same span even if they'd all otherwise have `null` URLs.
+ final highlightsByUrl = groupBy<_Highlight, Object>(
+ highlights, (highlight) => highlight.span.sourceUrl ?? Object());
+ for (var list in highlightsByUrl.values) {
+ list.sort((highlight1, highlight2) =>
+ highlight1.span.compareTo(highlight2.span));
+ }
+
+ return highlightsByUrl.entries.expand((entry) {
+ final url = entry.key;
+ final highlightsForFile = entry.value;
+
+ // First, create a list of all the lines in the current file that we have
+ // context for along with their line numbers.
+ final lines = <_Line>[];
+ for (var highlight in highlightsForFile) {
+ final context = highlight.span.context;
+ // If [highlight.span.context] contains lines prior to the one
+ // [highlight.span.text] appears on, write those first.
+ final lineStart = findLineStart(
+ context, highlight.span.text, highlight.span.start.column)!;
+
+ final linesBeforeSpan =
+ '\n'.allMatches(context.substring(0, lineStart)).length;
+
+ var lineNumber = highlight.span.start.line - linesBeforeSpan;
+ for (var line in context.split('\n')) {
+ // Only add a line if it hasn't already been added for a previous span
+ if (lines.isEmpty || lineNumber > lines.last.number) {
+ lines.add(_Line(line, lineNumber, url));
+ }
+ lineNumber++;
+ }
+ }
+
+ // Next, associate each line with each highlights that covers it.
+ final activeHighlights = <_Highlight>[];
+ var highlightIndex = 0;
+ for (var line in lines) {
+ activeHighlights
+ .removeWhere((highlight) => highlight.span.end.line < line.number);
+
+ final oldHighlightLength = activeHighlights.length;
+ for (var highlight in highlightsForFile.skip(highlightIndex)) {
+ if (highlight.span.start.line > line.number) break;
+ activeHighlights.add(highlight);
+ }
+ highlightIndex += activeHighlights.length - oldHighlightLength;
+
+ line.highlights.addAll(activeHighlights);
+ }
+
+ return lines;
+ }).toList();
+ }
+
+ /// Returns the highlighted span text.
+ ///
+ /// This method should only be called once.
+ String highlight() {
+ _writeFileStart(_lines.first.url);
+
+ // Each index of this list represents a column after the sidebar that could
+ // contain a line indicating an active highlight. If it's `null`, that
+ // column is empty; if it contains a highlight, it should be drawn for that
+ // column.
+ final highlightsByColumn =
+ List<_Highlight?>.filled(_maxMultilineSpans, null);
+
+ for (var i = 0; i < _lines.length; i++) {
+ final line = _lines[i];
+ if (i > 0) {
+ final lastLine = _lines[i - 1];
+ if (lastLine.url != line.url) {
+ _writeSidebar(end: glyph.upEnd);
+ _buffer.writeln();
+ _writeFileStart(line.url);
+ } else if (lastLine.number + 1 != line.number) {
+ _writeSidebar(text: '...');
+ _buffer.writeln();
+ }
+ }
+
+ // If a highlight covers the entire first line other than initial
+ // whitespace, don't bother pointing out exactly where it begins. Iterate
+ // in reverse so that longer highlights (which are sorted after shorter
+ // highlights) appear further out, leading to fewer crossed lines.
+ for (var highlight in line.highlights.reversed) {
+ if (isMultiline(highlight.span) &&
+ highlight.span.start.line == line.number &&
+ _isOnlyWhitespace(
+ line.text.substring(0, highlight.span.start.column))) {
+ replaceFirstNull(highlightsByColumn, highlight);
+ }
+ }
+
+ _writeSidebar(line: line.number);
+ _buffer.write(' ');
+ _writeMultilineHighlights(line, highlightsByColumn);
+ if (highlightsByColumn.isNotEmpty) _buffer.write(' ');
+
+ final primaryIdx =
+ line.highlights.indexWhere((highlight) => highlight.isPrimary);
+ final primary = primaryIdx == -1 ? null : line.highlights[primaryIdx];
+
+ if (primary != null) {
+ _writeHighlightedText(
+ line.text,
+ primary.span.start.line == line.number
+ ? primary.span.start.column
+ : 0,
+ primary.span.end.line == line.number
+ ? primary.span.end.column
+ : line.text.length,
+ color: _primaryColor);
+ } else {
+ _writeText(line.text);
+ }
+ _buffer.writeln();
+
+ // Always write the primary span's indicator first so that it's right next
+ // to the highlighted text.
+ if (primary != null) _writeIndicator(line, primary, highlightsByColumn);
+ for (var highlight in line.highlights) {
+ if (highlight.isPrimary) continue;
+ _writeIndicator(line, highlight, highlightsByColumn);
+ }
+ }
+
+ _writeSidebar(end: glyph.upEnd);
+ return _buffer.toString();
+ }
+
+ /// Writes the beginning of the file highlight for the file with the given
+ /// [url] (or opaque object if it comes from a span with a null URL).
+ void _writeFileStart(Object url) {
+ if (!_multipleFiles || url is! Uri) {
+ _writeSidebar(end: glyph.downEnd);
+ } else {
+ _writeSidebar(end: glyph.topLeftCorner);
+ _colorize(() => _buffer.write('${glyph.horizontalLine * 2}>'),
+ color: colors.blue);
+ _buffer.write(' ${p.prettyUri(url)}');
+ }
+ _buffer.writeln();
+ }
+
+ /// Writes the post-sidebar highlight bars for [line] according to
+ /// [highlightsByColumn].
+ ///
+ /// If [current] is passed, it's the highlight for which an indicator is being
+ /// written. If it appears in [highlightsByColumn], a horizontal line is
+ /// written from its column to the rightmost column.
+ void _writeMultilineHighlights(
+ _Line line, List<_Highlight?> highlightsByColumn,
+ {_Highlight? current}) {
+ // Whether we've written a sidebar indicator for opening a new span on this
+ // line, and which color should be used for that indicator's rightward line.
+ var openedOnThisLine = false;
+ String? openedOnThisLineColor;
+
+ final currentColor = current == null
+ ? null
+ : current.isPrimary
+ ? _primaryColor
+ : _secondaryColor;
+ var foundCurrent = false;
+ for (var highlight in highlightsByColumn) {
+ final startLine = highlight?.span.start.line;
+ final endLine = highlight?.span.end.line;
+ if (current != null && highlight == current) {
+ foundCurrent = true;
+ assert(startLine == line.number || endLine == line.number);
+ _colorize(() {
+ _buffer.write(startLine == line.number
+ ? glyph.topLeftCorner
+ : glyph.bottomLeftCorner);
+ }, color: currentColor);
+ } else if (foundCurrent) {
+ _colorize(() {
+ _buffer.write(highlight == null ? glyph.horizontalLine : glyph.cross);
+ }, color: currentColor);
+ } else if (highlight == null) {
+ if (openedOnThisLine) {
+ _colorize(() => _buffer.write(glyph.horizontalLine),
+ color: openedOnThisLineColor);
+ } else {
+ _buffer.write(' ');
+ }
+ } else {
+ _colorize(() {
+ final vertical = openedOnThisLine ? glyph.cross : glyph.verticalLine;
+ if (current != null) {
+ _buffer.write(vertical);
+ } else if (startLine == line.number) {
+ _colorize(() {
+ _buffer
+ .write(glyph.glyphOrAscii(openedOnThisLine ? '┬' : '┌', '/'));
+ }, color: openedOnThisLineColor);
+ openedOnThisLine = true;
+ openedOnThisLineColor ??=
+ highlight.isPrimary ? _primaryColor : _secondaryColor;
+ } else if (endLine == line.number &&
+ highlight.span.end.column == line.text.length) {
+ _buffer.write(highlight.label == null
+ ? glyph.glyphOrAscii('└', r'\')
+ : vertical);
+ } else {
+ _colorize(() {
+ _buffer.write(vertical);
+ }, color: openedOnThisLineColor);
+ }
+ }, color: highlight.isPrimary ? _primaryColor : _secondaryColor);
+ }
+ }
+ }
+
+ // Writes [text], with text between [startColumn] and [endColumn] colorized in
+ // the same way as [_colorize].
+ void _writeHighlightedText(String text, int startColumn, int endColumn,
+ {required String? color}) {
+ _writeText(text.substring(0, startColumn));
+ _colorize(() => _writeText(text.substring(startColumn, endColumn)),
+ color: color);
+ _writeText(text.substring(endColumn, text.length));
+ }
+
+ /// Writes an indicator for where [highlight] starts, ends, or both below
+ /// [line].
+ ///
+ /// This may either add or remove [highlight] from [highlightsByColumn].
+ void _writeIndicator(
+ _Line line, _Highlight highlight, List<_Highlight?> highlightsByColumn) {
+ final color = highlight.isPrimary ? _primaryColor : _secondaryColor;
+ if (!isMultiline(highlight.span)) {
+ _writeSidebar();
+ _buffer.write(' ');
+ _writeMultilineHighlights(line, highlightsByColumn, current: highlight);
+ if (highlightsByColumn.isNotEmpty) _buffer.write(' ');
+
+ final underlineLength = _colorize(() {
+ final start = _buffer.length;
+ _writeUnderline(line, highlight.span,
+ highlight.isPrimary ? '^' : glyph.horizontalLineBold);
+ return _buffer.length - start;
+ }, color: color);
+ _writeLabel(highlight, highlightsByColumn, underlineLength);
+ } else if (highlight.span.start.line == line.number) {
+ if (highlightsByColumn.contains(highlight)) return;
+ replaceFirstNull(highlightsByColumn, highlight);
+
+ _writeSidebar();
+ _buffer.write(' ');
+ _writeMultilineHighlights(line, highlightsByColumn, current: highlight);
+ _colorize(() => _writeArrow(line, highlight.span.start.column),
+ color: color);
+ _buffer.writeln();
+ } else if (highlight.span.end.line == line.number) {
+ final coversWholeLine = highlight.span.end.column == line.text.length;
+ if (coversWholeLine && highlight.label == null) {
+ replaceWithNull(highlightsByColumn, highlight);
+ return;
+ }
+
+ _writeSidebar();
+ _buffer.write(' ');
+ _writeMultilineHighlights(line, highlightsByColumn, current: highlight);
+
+ final underlineLength = _colorize(() {
+ final start = _buffer.length;
+ if (coversWholeLine) {
+ _buffer.write(glyph.horizontalLine * 3);
+ } else {
+ _writeArrow(line, math.max(highlight.span.end.column - 1, 0),
+ beginning: false);
+ }
+ return _buffer.length - start;
+ }, color: color);
+ _writeLabel(highlight, highlightsByColumn, underlineLength);
+ replaceWithNull(highlightsByColumn, highlight);
+ }
+ }
+
+ /// Underlines the portion of [line] covered by [span] with repeated instances
+ /// of [character].
+ void _writeUnderline(_Line line, SourceSpan span, String character) {
+ assert(!isMultiline(span));
+ assert(line.text.contains(span.text),
+ '"${line.text}" should contain "${span.text}"');
+
+ var startColumn = span.start.column;
+ var endColumn = span.end.column;
+
+ // Adjust the start and end columns to account for any tabs that were
+ // converted to spaces.
+ final tabsBefore = _countTabs(line.text.substring(0, startColumn));
+ final tabsInside = _countTabs(line.text.substring(startColumn, endColumn));
+ startColumn += tabsBefore * (_spacesPerTab - 1);
+ endColumn += (tabsBefore + tabsInside) * (_spacesPerTab - 1);
+
+ _buffer
+ ..write(' ' * startColumn)
+ ..write(character * math.max(endColumn - startColumn, 1));
+ }
+
+ /// Write an arrow pointing to column [column] in [line].
+ ///
+ /// If the arrow points to a tab character, this will point to the beginning
+ /// of the tab if [beginning] is `true` and the end if it's `false`.
+ void _writeArrow(_Line line, int column, {bool beginning = true}) {
+ final tabs =
+ _countTabs(line.text.substring(0, column + (beginning ? 0 : 1)));
+ _buffer
+ ..write(glyph.horizontalLine * (1 + column + tabs * (_spacesPerTab - 1)))
+ ..write('^');
+ }
+
+ /// Writes [highlight]'s label.
+ ///
+ /// The `_buffer` is assumed to be written to the point where the first line
+ /// of `highlight.label` can be written after a space, but this takes care of
+ /// writing indentation and highlight columns for later lines.
+ ///
+ /// The [highlightsByColumn] are used to write ongoing highlight lines if the
+ /// label is more than one line long.
+ ///
+ /// The [underlineLength] is the length of the line written between the
+ /// highlights and the beginning of the first label.
+ void _writeLabel(_Highlight highlight, List<_Highlight?> highlightsByColumn,
+ int underlineLength) {
+ final label = highlight.label;
+ if (label == null) {
+ _buffer.writeln();
+ return;
+ }
+
+ final lines = label.split('\n');
+ final color = highlight.isPrimary ? _primaryColor : _secondaryColor;
+ _colorize(() => _buffer.write(' ${lines.first}'), color: color);
+ _buffer.writeln();
+
+ for (var text in lines.skip(1)) {
+ _writeSidebar();
+ _buffer.write(' ');
+ for (var columnHighlight in highlightsByColumn) {
+ if (columnHighlight == null || columnHighlight == highlight) {
+ _buffer.write(' ');
+ } else {
+ _buffer.write(glyph.verticalLine);
+ }
+ }
+
+ _buffer.write(' ' * underlineLength);
+ _colorize(() => _buffer.write(' $text'), color: color);
+ _buffer.writeln();
+ }
+ }
+
+ /// Writes a snippet from the source text, converting hard tab characters into
+ /// plain indentation.
+ void _writeText(String text) {
+ for (var char in text.codeUnits) {
+ if (char == $tab) {
+ _buffer.write(' ' * _spacesPerTab);
+ } else {
+ _buffer.writeCharCode(char);
+ }
+ }
+ }
+
+ // Writes a sidebar to [buffer] that includes [line] as the line number if
+ // given and writes [end] at the end (defaults to [glyphs.verticalLine]).
+ //
+ // If [text] is given, it's used in place of the line number. It can't be
+ // passed at the same time as [line].
+ void _writeSidebar({int? line, String? text, String? end}) {
+ assert(line == null || text == null);
+
+ // Add 1 to line to convert from computer-friendly 0-indexed line numbers to
+ // human-friendly 1-indexed line numbers.
+ if (line != null) text = (line + 1).toString();
+ _colorize(() {
+ _buffer
+ ..write((text ?? '').padRight(_paddingBeforeSidebar))
+ ..write(end ?? glyph.verticalLine);
+ }, color: colors.blue);
+ }
+
+ /// Returns the number of hard tabs in [text].
+ int _countTabs(String text) {
+ var count = 0;
+ for (var char in text.codeUnits) {
+ if (char == $tab) count++;
+ }
+ return count;
+ }
+
+ /// Returns whether [text] contains only space or tab characters.
+ bool _isOnlyWhitespace(String text) {
+ for (var char in text.codeUnits) {
+ if (char != $space && char != $tab) return false;
+ }
+ return true;
+ }
+
+ /// Colors all text written to [_buffer] during [callback], if colorization is
+ /// enabled and [color] is not `null`.
+ T _colorize<T>(T Function() callback, {required String? color}) {
+ if (_primaryColor != null && color != null) _buffer.write(color);
+ final result = callback();
+ if (_primaryColor != null && color != null) _buffer.write(colors.none);
+ return result;
+ }
+}
+
+/// Information about how to highlight a single section of a source file.
+class _Highlight {
+ /// The section of the source file to highlight.
+ ///
+ /// This is normalized to make it easier for [Highlighter] to work with.
+ final SourceSpanWithContext span;
+
+ /// Whether this is the primary span in the highlight.
+ ///
+ /// The primary span is highlighted with a different character and colored
+ /// differently than non-primary spans.
+ final bool isPrimary;
+
+ /// The label to include inline when highlighting [span].
+ ///
+ /// This helps distinguish clarify what each highlight means when multiple are
+ /// used in the same message.
+ final String? label;
+
+ _Highlight(SourceSpan span, {String? label, bool primary = false})
+ : span = (() {
+ var newSpan = _normalizeContext(span);
+ newSpan = _normalizeNewlines(newSpan);
+ newSpan = _normalizeTrailingNewline(newSpan);
+ return _normalizeEndOfLine(newSpan);
+ })(),
+ isPrimary = primary,
+ label = label?.replaceAll('\r\n', '\n');
+
+ /// Normalizes [span] to ensure that it's a [SourceSpanWithContext] whose
+ /// context actually contains its text at the expected column.
+ ///
+ /// If it's not already a [SourceSpanWithContext], adjust the start and end
+ /// locations' line and column fields so that the highlighter can assume they
+ /// match up with the context.
+ static SourceSpanWithContext _normalizeContext(SourceSpan span) =>
+ span is SourceSpanWithContext &&
+ findLineStart(span.context, span.text, span.start.column) != null
+ ? span
+ : SourceSpanWithContext(
+ SourceLocation(span.start.offset,
+ sourceUrl: span.sourceUrl, line: 0, column: 0),
+ SourceLocation(span.end.offset,
+ sourceUrl: span.sourceUrl,
+ line: countCodeUnits(span.text, $lf),
+ column: _lastLineLength(span.text)),
+ span.text,
+ span.text);
+
+ /// Normalizes [span] to replace Windows-style newlines with Unix-style
+ /// newlines.
+ static SourceSpanWithContext _normalizeNewlines(SourceSpanWithContext span) {
+ final text = span.text;
+ if (!text.contains('\r\n')) return span;
+
+ var endOffset = span.end.offset;
+ for (var i = 0; i < text.length - 1; i++) {
+ if (text.codeUnitAt(i) == $cr && text.codeUnitAt(i + 1) == $lf) {
+ endOffset--;
+ }
+ }
+
+ return SourceSpanWithContext(
+ span.start,
+ SourceLocation(endOffset,
+ sourceUrl: span.sourceUrl,
+ line: span.end.line,
+ column: span.end.column),
+ text.replaceAll('\r\n', '\n'),
+ span.context.replaceAll('\r\n', '\n'));
+ }
+
+ /// Normalizes [span] to remove a trailing newline from `span.context`.
+ ///
+ /// If necessary, also adjust `span.end` so that it doesn't point past where
+ /// the trailing newline used to be.
+ static SourceSpanWithContext _normalizeTrailingNewline(
+ SourceSpanWithContext span) {
+ if (!span.context.endsWith('\n')) return span;
+
+ // If there's a full blank line on the end of [span.context], it's probably
+ // significant, so we shouldn't trim it.
+ if (span.text.endsWith('\n\n')) return span;
+
+ final context = span.context.substring(0, span.context.length - 1);
+ var text = span.text;
+ var start = span.start;
+ var end = span.end;
+ if (span.text.endsWith('\n') && _isTextAtEndOfContext(span)) {
+ text = span.text.substring(0, span.text.length - 1);
+ if (text.isEmpty) {
+ end = start;
+ } else {
+ end = SourceLocation(span.end.offset - 1,
+ sourceUrl: span.sourceUrl,
+ line: span.end.line - 1,
+ column: _lastLineLength(context));
+ start = span.start.offset == span.end.offset ? end : span.start;
+ }
+ }
+ return SourceSpanWithContext(start, end, text, context);
+ }
+
+ /// Normalizes [span] so that the end location is at the end of a line rather
+ /// than at the beginning of the next line.
+ static SourceSpanWithContext _normalizeEndOfLine(SourceSpanWithContext span) {
+ if (span.end.column != 0) return span;
+ if (span.end.line == span.start.line) return span;
+
+ final text = span.text.substring(0, span.text.length - 1);
+
+ return SourceSpanWithContext(
+ span.start,
+ SourceLocation(span.end.offset - 1,
+ sourceUrl: span.sourceUrl,
+ line: span.end.line - 1,
+ column: text.length - text.lastIndexOf('\n') - 1),
+ text,
+ // If the context also ends with a newline, it's possible that we don't
+ // have the full context for that line, so we shouldn't print it at all.
+ span.context.endsWith('\n')
+ ? span.context.substring(0, span.context.length - 1)
+ : span.context);
+ }
+
+ /// Returns the length of the last line in [text], whether or not it ends in a
+ /// newline.
+ static int _lastLineLength(String text) {
+ if (text.isEmpty) {
+ return 0;
+ } else if (text.codeUnitAt(text.length - 1) == $lf) {
+ return text.length == 1
+ ? 0
+ : text.length - text.lastIndexOf('\n', text.length - 2) - 1;
+ } else {
+ return text.length - text.lastIndexOf('\n') - 1;
+ }
+ }
+
+ /// Returns whether [span]'s text runs all the way to the end of its context.
+ static bool _isTextAtEndOfContext(SourceSpanWithContext span) =>
+ findLineStart(span.context, span.text, span.start.column)! +
+ span.start.column +
+ span.length ==
+ span.context.length;
+
+ @override
+ String toString() {
+ final buffer = StringBuffer();
+ if (isPrimary) buffer.write('primary ');
+ buffer.write('${span.start.line}:${span.start.column}-'
+ '${span.end.line}:${span.end.column}');
+ if (label != null) buffer.write(' ($label)');
+ return buffer.toString();
+ }
+}
+
+/// A single line of the source file being highlighted.
+class _Line {
+ /// The text of the line, not including the trailing newline.
+ final String text;
+
+ /// The 0-based line number in the source file.
+ final int number;
+
+ /// The URL of the source file in which this line appears.
+ ///
+ /// For lines created from spans without an explicit URL, this is an opaque
+ /// object that differs between lines that come from different spans.
+ final Object url;
+
+ /// All highlights that cover any portion of this line, in source span order.
+ ///
+ /// This is populated after the initial line is created.
+ final highlights = <_Highlight>[];
+
+ _Line(this.text, this.number, this.url);
+
+ @override
+ String toString() => '$number: "$text" (${highlights.join(', ')})';
+}
diff --git a/pkgs/source_span/lib/src/location.dart b/pkgs/source_span/lib/src/location.dart
new file mode 100644
index 0000000..8f22d7b
--- /dev/null
+++ b/pkgs/source_span/lib/src/location.dart
@@ -0,0 +1,102 @@
+// Copyright (c) 2014, 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 'span.dart';
+
+// TODO(nweiz): Use SourceLocationMixin once we decide to cut a release with
+// breaking changes. See SourceLocationMixin for details.
+
+/// A class that describes a single location within a source file.
+///
+/// This class should not be extended. Instead, [SourceLocationBase] should be
+/// extended instead.
+class SourceLocation implements Comparable<SourceLocation> {
+ /// URL of the source containing this location.
+ ///
+ /// This may be null, indicating that the source URL is unknown or
+ /// unavailable.
+ final Uri? sourceUrl;
+
+ /// The 0-based offset of this location in the source.
+ final int offset;
+
+ /// The 0-based line of this location in the source.
+ final int line;
+
+ /// The 0-based column of this location in the source
+ final int column;
+
+ /// Returns a representation of this location in the `source:line:column`
+ /// format used by text editors.
+ ///
+ /// This prints 1-based lines and columns.
+ String get toolString {
+ final source = sourceUrl ?? 'unknown source';
+ return '$source:${line + 1}:${column + 1}';
+ }
+
+ /// Creates a new location indicating [offset] within [sourceUrl].
+ ///
+ /// [line] and [column] default to assuming the source is a single line. This
+ /// means that [line] defaults to 0 and [column] defaults to [offset].
+ ///
+ /// [sourceUrl] may be either a [String], a [Uri], or `null`.
+ SourceLocation(this.offset, {Object? sourceUrl, int? line, int? column})
+ : sourceUrl =
+ sourceUrl is String ? Uri.parse(sourceUrl) : sourceUrl as Uri?,
+ line = line ?? 0,
+ column = column ?? offset {
+ if (offset < 0) {
+ throw RangeError('Offset may not be negative, was $offset.');
+ } else if (line != null && line < 0) {
+ throw RangeError('Line may not be negative, was $line.');
+ } else if (column != null && column < 0) {
+ throw RangeError('Column may not be negative, was $column.');
+ }
+ }
+
+ /// Returns the distance in characters between `this` and [other].
+ ///
+ /// This always returns a non-negative value.
+ int distance(SourceLocation other) {
+ if (sourceUrl != other.sourceUrl) {
+ throw ArgumentError('Source URLs "$sourceUrl" and '
+ "\"${other.sourceUrl}\" don't match.");
+ }
+ return (offset - other.offset).abs();
+ }
+
+ /// Returns a span that covers only a single point: this location.
+ SourceSpan pointSpan() => SourceSpan(this, this, '');
+
+ /// Compares two locations.
+ ///
+ /// [other] must have the same source URL as `this`.
+ @override
+ int compareTo(SourceLocation other) {
+ if (sourceUrl != other.sourceUrl) {
+ throw ArgumentError('Source URLs "$sourceUrl" and '
+ "\"${other.sourceUrl}\" don't match.");
+ }
+ return offset - other.offset;
+ }
+
+ @override
+ bool operator ==(Object other) =>
+ other is SourceLocation &&
+ sourceUrl == other.sourceUrl &&
+ offset == other.offset;
+
+ @override
+ int get hashCode => (sourceUrl?.hashCode ?? 0) + offset;
+
+ @override
+ String toString() => '<$runtimeType: $offset $toolString>';
+}
+
+/// A base class for source locations with [offset], [line], and [column] known
+/// at construction time.
+class SourceLocationBase extends SourceLocation {
+ SourceLocationBase(super.offset, {super.sourceUrl, super.line, super.column});
+}
diff --git a/pkgs/source_span/lib/src/location_mixin.dart b/pkgs/source_span/lib/src/location_mixin.dart
new file mode 100644
index 0000000..a44f5e2
--- /dev/null
+++ b/pkgs/source_span/lib/src/location_mixin.dart
@@ -0,0 +1,55 @@
+// Copyright (c) 2015, 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 'location.dart';
+import 'span.dart';
+
+// Note: this class duplicates a lot of functionality of [SourceLocation]. This
+// is because in order for SourceLocation to use SourceLocationMixin,
+// SourceLocationMixin couldn't implement SourceLocation. In SourceSpan we
+// handle this by making the class itself non-extensible, but that would be a
+// breaking change for SourceLocation. So until we want to endure the pain of
+// cutting a release with breaking changes, we duplicate the code here.
+
+/// A mixin for easily implementing [SourceLocation].
+abstract class SourceLocationMixin implements SourceLocation {
+ @override
+ String get toolString {
+ final source = sourceUrl ?? 'unknown source';
+ return '$source:${line + 1}:${column + 1}';
+ }
+
+ @override
+ int distance(SourceLocation other) {
+ if (sourceUrl != other.sourceUrl) {
+ throw ArgumentError('Source URLs "$sourceUrl" and '
+ "\"${other.sourceUrl}\" don't match.");
+ }
+ return (offset - other.offset).abs();
+ }
+
+ @override
+ SourceSpan pointSpan() => SourceSpan(this, this, '');
+
+ @override
+ int compareTo(SourceLocation other) {
+ if (sourceUrl != other.sourceUrl) {
+ throw ArgumentError('Source URLs "$sourceUrl" and '
+ "\"${other.sourceUrl}\" don't match.");
+ }
+ return offset - other.offset;
+ }
+
+ @override
+ bool operator ==(Object other) =>
+ other is SourceLocation &&
+ sourceUrl == other.sourceUrl &&
+ offset == other.offset;
+
+ @override
+ int get hashCode => (sourceUrl?.hashCode ?? 0) + offset;
+
+ @override
+ String toString() => '<$runtimeType: $offset $toolString>';
+}
diff --git a/pkgs/source_span/lib/src/span.dart b/pkgs/source_span/lib/src/span.dart
new file mode 100644
index 0000000..941dedc
--- /dev/null
+++ b/pkgs/source_span/lib/src/span.dart
@@ -0,0 +1,193 @@
+// Copyright (c) 2014, 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:path/path.dart' as p;
+import 'package:term_glyph/term_glyph.dart' as glyph;
+
+import 'file.dart';
+import 'highlighter.dart';
+import 'location.dart';
+import 'span_mixin.dart';
+import 'span_with_context.dart';
+import 'utils.dart';
+
+/// A class that describes a segment of source text.
+abstract class SourceSpan implements Comparable<SourceSpan> {
+ /// The start location of this span.
+ SourceLocation get start;
+
+ /// The end location of this span, exclusive.
+ SourceLocation get end;
+
+ /// The source text for this span.
+ String get text;
+
+ /// The URL of the source (typically a file) of this span.
+ ///
+ /// This may be null, indicating that the source URL is unknown or
+ /// unavailable.
+ Uri? get sourceUrl;
+
+ /// The length of this span, in characters.
+ int get length;
+
+ /// Creates a new span from [start] to [end] (exclusive) containing [text].
+ ///
+ /// [start] and [end] must have the same source URL and [start] must come
+ /// before [end]. [text] must have a number of characters equal to the
+ /// distance between [start] and [end].
+ factory SourceSpan(SourceLocation start, SourceLocation end, String text) =>
+ SourceSpanBase(start, end, text);
+
+ /// Creates a new span that's the union of `this` and [other].
+ ///
+ /// The two spans must have the same source URL and may not be disjoint.
+ /// [text] is computed by combining `this.text` and `other.text`.
+ SourceSpan union(SourceSpan other);
+
+ /// Compares two spans.
+ ///
+ /// [other] must have the same source URL as `this`. This orders spans by
+ /// [start] then [length].
+ @override
+ int compareTo(SourceSpan other);
+
+ /// Formats [message] in a human-friendly way associated with this span.
+ ///
+ /// [color] may either be a [String], a [bool], or `null`. If it's a string,
+ /// it indicates an [ANSI terminal color escape][] that should
+ /// be used to highlight the span's text (for example, `"\u001b[31m"` will
+ /// color red). If it's `true`, it indicates that the text should be
+ /// highlighted using the default color. If it's `false` or `null`, it
+ /// indicates that the text shouldn't be highlighted.
+ ///
+ /// This uses the full range of Unicode characters to highlight the source
+ /// span if [glyph.ascii] is `false` (the default), but only uses ASCII
+ /// characters if it's `true`.
+ ///
+ /// [ANSI terminal color escape]: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
+ String message(String message, {Object? color});
+
+ /// Prints the text associated with this span in a user-friendly way.
+ ///
+ /// This is identical to [message], except that it doesn't print the file
+ /// name, line number, column number, or message. If [length] is 0 and this
+ /// isn't a [SourceSpanWithContext], returns an empty string.
+ ///
+ /// [color] may either be a [String], a [bool], or `null`. If it's a string,
+ /// it indicates an [ANSI terminal color escape][] that should
+ /// be used to highlight the span's text (for example, `"\u001b[31m"` will
+ /// color red). If it's `true`, it indicates that the text should be
+ /// highlighted using the default color. If it's `false` or `null`, it
+ /// indicates that the text shouldn't be highlighted.
+ ///
+ /// This uses the full range of Unicode characters to highlight the source
+ /// span if [glyph.ascii] is `false` (the default), but only uses ASCII
+ /// characters if it's `true`.
+ ///
+ /// [ANSI terminal color escape]: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
+ String highlight({Object? color});
+}
+
+/// A base class for source spans with [start], [end], and [text] known at
+/// construction time.
+class SourceSpanBase extends SourceSpanMixin {
+ @override
+ final SourceLocation start;
+ @override
+ final SourceLocation end;
+ @override
+ final String text;
+
+ SourceSpanBase(this.start, this.end, this.text) {
+ if (end.sourceUrl != start.sourceUrl) {
+ throw ArgumentError('Source URLs "${start.sourceUrl}" and '
+ " \"${end.sourceUrl}\" don't match.");
+ } else if (end.offset < start.offset) {
+ throw ArgumentError('End $end must come after start $start.');
+ } else if (text.length != start.distance(end)) {
+ throw ArgumentError('Text "$text" must be ${start.distance(end)} '
+ 'characters long.');
+ }
+ }
+}
+
+// TODO(#52): Move these to instance methods in the next breaking release.
+/// Extension methods on the base [SourceSpan] API.
+extension SourceSpanExtension on SourceSpan {
+ /// Like [SourceSpan.message], but also highlights [secondarySpans] to provide
+ /// the user with additional context.
+ ///
+ /// Each span takes a label ([label] for this span, and the values of the
+ /// [secondarySpans] map for the secondary spans) that's used to indicate to
+ /// the user what that particular span represents.
+ ///
+ /// If [color] is `true`, [ANSI terminal color escapes][] are used to color
+ /// the resulting string. By default this span is colored red and the
+ /// secondary spans are colored blue, but that can be customized by passing
+ /// ANSI escape strings to [primaryColor] or [secondaryColor].
+ ///
+ /// [ANSI terminal color escapes]: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
+ ///
+ /// Each span in [secondarySpans] must refer to the same document as this
+ /// span. Throws an [ArgumentError] if any secondary span has a different
+ /// source URL than this span.
+ ///
+ /// Note that while this will work with plain [SourceSpan]s, it will produce
+ /// much more useful output with [SourceSpanWithContext]s (including
+ /// [FileSpan]s).
+ String messageMultiple(
+ String message, String label, Map<SourceSpan, String> secondarySpans,
+ {bool color = false, String? primaryColor, String? secondaryColor}) {
+ final buffer = StringBuffer()
+ ..write('line ${start.line + 1}, column ${start.column + 1}');
+ if (sourceUrl != null) buffer.write(' of ${p.prettyUri(sourceUrl)}');
+ buffer
+ ..writeln(': $message')
+ ..write(highlightMultiple(label, secondarySpans,
+ color: color,
+ primaryColor: primaryColor,
+ secondaryColor: secondaryColor));
+ return buffer.toString();
+ }
+
+ /// Like [SourceSpan.highlight], but also highlights [secondarySpans] to
+ /// provide the user with additional context.
+ ///
+ /// Each span takes a label ([label] for this span, and the values of the
+ /// [secondarySpans] map for the secondary spans) that's used to indicate to
+ /// the user what that particular span represents.
+ ///
+ /// If [color] is `true`, [ANSI terminal color escapes][] are used to color
+ /// the resulting string. By default this span is colored red and the
+ /// secondary spans are colored blue, but that can be customized by passing
+ /// ANSI escape strings to [primaryColor] or [secondaryColor].
+ ///
+ /// [ANSI terminal color escapes]: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
+ ///
+ /// Each span in [secondarySpans] must refer to the same document as this
+ /// span. Throws an [ArgumentError] if any secondary span has a different
+ /// source URL than this span.
+ ///
+ /// Note that while this will work with plain [SourceSpan]s, it will produce
+ /// much more useful output with [SourceSpanWithContext]s (including
+ /// [FileSpan]s).
+ String highlightMultiple(String label, Map<SourceSpan, String> secondarySpans,
+ {bool color = false, String? primaryColor, String? secondaryColor}) =>
+ Highlighter.multiple(this, label, secondarySpans,
+ color: color,
+ primaryColor: primaryColor,
+ secondaryColor: secondaryColor)
+ .highlight();
+
+ /// Returns a span from [start] code units (inclusive) to [end] code units
+ /// (exclusive) after the beginning of this span.
+ SourceSpan subspan(int start, [int? end]) {
+ RangeError.checkValidRange(start, end, length);
+ if (start == 0 && (end == null || end == length)) return this;
+
+ final locations = subspanLocations(this, start, end);
+ return SourceSpan(locations[0], locations[1], text.substring(start, end));
+ }
+}
diff --git a/pkgs/source_span/lib/src/span_exception.dart b/pkgs/source_span/lib/src/span_exception.dart
new file mode 100644
index 0000000..90ad690
--- /dev/null
+++ b/pkgs/source_span/lib/src/span_exception.dart
@@ -0,0 +1,114 @@
+// Copyright (c) 2014, 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 'span.dart';
+
+/// A class for exceptions that have source span information attached.
+class SourceSpanException implements Exception {
+ // This is a getter so that subclasses can override it.
+ /// A message describing the exception.
+ String get message => _message;
+ final String _message;
+
+ // This is a getter so that subclasses can override it.
+ /// The span associated with this exception.
+ ///
+ /// This may be `null` if the source location can't be determined.
+ SourceSpan? get span => _span;
+ final SourceSpan? _span;
+
+ SourceSpanException(this._message, this._span);
+
+ /// Returns a string representation of `this`.
+ ///
+ /// [color] may either be a [String], a [bool], or `null`. If it's a string,
+ /// it indicates an ANSI terminal color escape that should be used to
+ /// highlight the span's text. If it's `true`, it indicates that the text
+ /// should be highlighted using the default color. If it's `false` or `null`,
+ /// it indicates that the text shouldn't be highlighted.
+ @override
+ String toString({Object? color}) {
+ if (span == null) return message;
+ return 'Error on ${span!.message(message, color: color)}';
+ }
+}
+
+/// A [SourceSpanException] that's also a [FormatException].
+class SourceSpanFormatException extends SourceSpanException
+ implements FormatException {
+ @override
+ final dynamic source;
+
+ @override
+ int? get offset => span?.start.offset;
+
+ SourceSpanFormatException(super.message, super.span, [this.source]);
+}
+
+/// A [SourceSpanException] that also highlights some secondary spans to provide
+/// the user with extra context.
+///
+/// Each span has a label ([primaryLabel] for the primary, and the values of the
+/// [secondarySpans] map for the secondary spans) that's used to indicate to the
+/// user what that particular span represents.
+class MultiSourceSpanException extends SourceSpanException {
+ /// A label to attach to [span] that provides additional information and helps
+ /// distinguish it from [secondarySpans].
+ final String primaryLabel;
+
+ /// A map whose keys are secondary spans that should be highlighted.
+ ///
+ /// Each span's value is a label to attach to that span that provides
+ /// additional information and helps distinguish it from [secondarySpans].
+ final Map<SourceSpan, String> secondarySpans;
+
+ MultiSourceSpanException(super.message, super.span, this.primaryLabel,
+ Map<SourceSpan, String> secondarySpans)
+ : secondarySpans = Map.unmodifiable(secondarySpans);
+
+ /// Returns a string representation of `this`.
+ ///
+ /// [color] may either be a [String], a [bool], or `null`. If it's a string,
+ /// it indicates an ANSI terminal color escape that should be used to
+ /// highlight the primary span's text. If it's `true`, it indicates that the
+ /// text should be highlighted using the default color. If it's `false` or
+ /// `null`, it indicates that the text shouldn't be highlighted.
+ ///
+ /// If [color] is `true` or a string, [secondaryColor] is used to highlight
+ /// [secondarySpans].
+ @override
+ String toString({Object? color, String? secondaryColor}) {
+ if (span == null) return message;
+
+ var useColor = false;
+ String? primaryColor;
+ if (color is String) {
+ useColor = true;
+ primaryColor = color;
+ } else if (color == true) {
+ useColor = true;
+ }
+
+ final formatted = span!.messageMultiple(
+ message, primaryLabel, secondarySpans,
+ color: useColor,
+ primaryColor: primaryColor,
+ secondaryColor: secondaryColor);
+ return 'Error on $formatted';
+ }
+}
+
+/// A [MultiSourceSpanException] that's also a [FormatException].
+class MultiSourceSpanFormatException extends MultiSourceSpanException
+ implements FormatException {
+ @override
+ final dynamic source;
+
+ @override
+ int? get offset => span?.start.offset;
+
+ MultiSourceSpanFormatException(
+ super.message, super.span, super.primaryLabel, super.secondarySpans,
+ [this.source]);
+}
diff --git a/pkgs/source_span/lib/src/span_mixin.dart b/pkgs/source_span/lib/src/span_mixin.dart
new file mode 100644
index 0000000..29b6119
--- /dev/null
+++ b/pkgs/source_span/lib/src/span_mixin.dart
@@ -0,0 +1,84 @@
+// Copyright (c) 2014, 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:path/path.dart' as p;
+
+import 'highlighter.dart';
+import 'span.dart';
+import 'span_with_context.dart';
+import 'utils.dart';
+
+/// A mixin for easily implementing [SourceSpan].
+///
+/// This implements the [SourceSpan] methods in terms of [start], [end], and
+/// [text]. This assumes that [start] and [end] have the same source URL, that
+/// [start] comes before [end], and that [text] has a number of characters equal
+/// to the distance between [start] and [end].
+abstract class SourceSpanMixin implements SourceSpan {
+ @override
+ Uri? get sourceUrl => start.sourceUrl;
+
+ @override
+ int get length => end.offset - start.offset;
+
+ @override
+ int compareTo(SourceSpan other) {
+ final result = start.compareTo(other.start);
+ return result == 0 ? end.compareTo(other.end) : result;
+ }
+
+ @override
+ SourceSpan union(SourceSpan other) {
+ if (sourceUrl != other.sourceUrl) {
+ throw ArgumentError('Source URLs "$sourceUrl" and '
+ " \"${other.sourceUrl}\" don't match.");
+ }
+
+ final start = min(this.start, other.start);
+ final end = max(this.end, other.end);
+ final beginSpan = start == this.start ? this : other;
+ final endSpan = end == this.end ? this : other;
+
+ if (beginSpan.end.compareTo(endSpan.start) < 0) {
+ throw ArgumentError('Spans $this and $other are disjoint.');
+ }
+
+ final text = beginSpan.text +
+ endSpan.text.substring(beginSpan.end.distance(endSpan.start));
+ return SourceSpan(start, end, text);
+ }
+
+ @override
+ String message(String message, {Object? color}) {
+ final buffer = StringBuffer()
+ ..write('line ${start.line + 1}, column ${start.column + 1}');
+ if (sourceUrl != null) buffer.write(' of ${p.prettyUri(sourceUrl)}');
+ buffer.write(': $message');
+
+ final highlight = this.highlight(color: color);
+ if (highlight.isNotEmpty) {
+ buffer
+ ..writeln()
+ ..write(highlight);
+ }
+
+ return buffer.toString();
+ }
+
+ @override
+ String highlight({Object? color}) {
+ if (this is! SourceSpanWithContext && length == 0) return '';
+ return Highlighter(this, color: color).highlight();
+ }
+
+ @override
+ bool operator ==(Object other) =>
+ other is SourceSpan && start == other.start && end == other.end;
+
+ @override
+ int get hashCode => Object.hash(start, end);
+
+ @override
+ String toString() => '<$runtimeType: from $start to $end "$text">';
+}
diff --git a/pkgs/source_span/lib/src/span_with_context.dart b/pkgs/source_span/lib/src/span_with_context.dart
new file mode 100644
index 0000000..776c789
--- /dev/null
+++ b/pkgs/source_span/lib/src/span_with_context.dart
@@ -0,0 +1,51 @@
+// Copyright (c) 2014, 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 'location.dart';
+import 'span.dart';
+import 'utils.dart';
+
+/// A class that describes a segment of source text with additional context.
+class SourceSpanWithContext extends SourceSpanBase {
+ // This is a getter so that subclasses can override it.
+ /// Text around the span, which includes the line containing this span.
+ String get context => _context;
+ final String _context;
+
+ /// Creates a new span from [start] to [end] (exclusive) containing [text], in
+ /// the given [context].
+ ///
+ /// [start] and [end] must have the same source URL and [start] must come
+ /// before [end]. [text] must have a number of characters equal to the
+ /// distance between [start] and [end]. [context] must contain [text], and
+ /// [text] should start at `start.column` from the beginning of a line in
+ /// [context].
+ SourceSpanWithContext(
+ SourceLocation start, SourceLocation end, String text, this._context)
+ : super(start, end, text) {
+ if (!context.contains(text)) {
+ throw ArgumentError('The context line "$context" must contain "$text".');
+ }
+
+ if (findLineStart(context, text, start.column) == null) {
+ throw ArgumentError('The span text "$text" must start at '
+ 'column ${start.column + 1} in a line within "$context".');
+ }
+ }
+}
+
+// TODO(#52): Move these to instance methods in the next breaking release.
+/// Extension methods on the base [SourceSpan] API.
+extension SourceSpanWithContextExtension on SourceSpanWithContext {
+ /// Returns a span from [start] code units (inclusive) to [end] code units
+ /// (exclusive) after the beginning of this span.
+ SourceSpanWithContext subspan(int start, [int? end]) {
+ RangeError.checkValidRange(start, end, length);
+ if (start == 0 && (end == null || end == length)) return this;
+
+ final locations = subspanLocations(this, start, end);
+ return SourceSpanWithContext(
+ locations[0], locations[1], text.substring(start, end), context);
+ }
+}
diff --git a/pkgs/source_span/lib/src/utils.dart b/pkgs/source_span/lib/src/utils.dart
new file mode 100644
index 0000000..aba14ec
--- /dev/null
+++ b/pkgs/source_span/lib/src/utils.dart
@@ -0,0 +1,145 @@
+// Copyright (c) 2014, 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 'charcode.dart';
+import 'location.dart';
+import 'span.dart';
+import 'span_with_context.dart';
+
+/// Returns the minimum of [obj1] and [obj2] according to
+/// [Comparable.compareTo].
+T min<T extends Comparable<T>>(T obj1, T obj2) =>
+ obj1.compareTo(obj2) > 0 ? obj2 : obj1;
+
+/// Returns the maximum of [obj1] and [obj2] according to
+/// [Comparable.compareTo].
+T max<T extends Comparable<T>>(T obj1, T obj2) =>
+ obj1.compareTo(obj2) > 0 ? obj1 : obj2;
+
+/// Returns whether all elements of [iter] are the same value, according to
+/// `==`.
+bool isAllTheSame(Iterable<Object?> iter) {
+ if (iter.isEmpty) return true;
+ final firstValue = iter.first;
+ for (var value in iter.skip(1)) {
+ if (value != firstValue) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/// Returns whether [span] covers multiple lines.
+bool isMultiline(SourceSpan span) => span.start.line != span.end.line;
+
+/// Sets the first `null` element of [list] to [element].
+void replaceFirstNull<E>(List<E?> list, E element) {
+ final index = list.indexOf(null);
+ if (index < 0) throw ArgumentError('$list contains no null elements.');
+ list[index] = element;
+}
+
+/// Sets the element of [list] that currently contains [element] to `null`.
+void replaceWithNull<E>(List<E?> list, E element) {
+ final index = list.indexOf(element);
+ if (index < 0) {
+ throw ArgumentError('$list contains no elements matching $element.');
+ }
+
+ list[index] = null;
+}
+
+/// Returns the number of instances of [codeUnit] in [string].
+int countCodeUnits(String string, int codeUnit) {
+ var count = 0;
+ for (var codeUnitToCheck in string.codeUnits) {
+ if (codeUnitToCheck == codeUnit) count++;
+ }
+ return count;
+}
+
+/// Finds a line in [context] containing [text] at the specified [column].
+///
+/// Returns the index in [context] where that line begins, or null if none
+/// exists.
+int? findLineStart(String context, String text, int column) {
+ // If the text is empty, we just want to find the first line that has at least
+ // [column] characters.
+ if (text.isEmpty) {
+ var beginningOfLine = 0;
+ while (true) {
+ final index = context.indexOf('\n', beginningOfLine);
+ if (index == -1) {
+ return context.length - beginningOfLine >= column
+ ? beginningOfLine
+ : null;
+ }
+
+ if (index - beginningOfLine >= column) return beginningOfLine;
+ beginningOfLine = index + 1;
+ }
+ }
+
+ var index = context.indexOf(text);
+ while (index != -1) {
+ // Start looking before [index] in case [text] starts with a newline.
+ final lineStart = index == 0 ? 0 : context.lastIndexOf('\n', index - 1) + 1;
+ final textColumn = index - lineStart;
+ if (column == textColumn) return lineStart;
+ index = context.indexOf(text, index + 1);
+ }
+ // ignore: avoid_returning_null
+ return null;
+}
+
+/// Returns a two-element list containing the start and end locations of the
+/// span from [start] code units (inclusive) to [end] code units (exclusive)
+/// after the beginning of [span].
+///
+/// This is factored out so it can be shared between
+/// [SourceSpanExtension.subspan] and [SourceSpanWithContextExtension.subspan].
+List<SourceLocation> subspanLocations(SourceSpan span, int start, [int? end]) {
+ final text = span.text;
+ final startLocation = span.start;
+ var line = startLocation.line;
+ var column = startLocation.column;
+
+ // Adjust [line] and [column] as necessary if the character at [i] in [text]
+ // is a newline.
+ void consumeCodePoint(int i) {
+ final codeUnit = text.codeUnitAt(i);
+ if (codeUnit == $lf ||
+ // A carriage return counts as a newline, but only if it's not
+ // followed by a line feed.
+ (codeUnit == $cr &&
+ (i + 1 == text.length || text.codeUnitAt(i + 1) != $lf))) {
+ line += 1;
+ column = 0;
+ } else {
+ column += 1;
+ }
+ }
+
+ for (var i = 0; i < start; i++) {
+ consumeCodePoint(i);
+ }
+
+ final newStartLocation = SourceLocation(startLocation.offset + start,
+ sourceUrl: span.sourceUrl, line: line, column: column);
+
+ SourceLocation newEndLocation;
+ if (end == null || end == span.length) {
+ newEndLocation = span.end;
+ } else if (end == start) {
+ newEndLocation = newStartLocation;
+ } else {
+ for (var i = start; i < end; i++) {
+ consumeCodePoint(i);
+ }
+ newEndLocation = SourceLocation(startLocation.offset + end,
+ sourceUrl: span.sourceUrl, line: line, column: column);
+ }
+
+ return [newStartLocation, newEndLocation];
+}
diff --git a/pkgs/source_span/pubspec.yaml b/pkgs/source_span/pubspec.yaml
new file mode 100644
index 0000000..8757b2d
--- /dev/null
+++ b/pkgs/source_span/pubspec.yaml
@@ -0,0 +1,17 @@
+name: source_span
+version: 1.10.1
+description: >-
+ Provides a standard representation for source code locations and spans.
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/source_span
+
+environment:
+ sdk: ^3.1.0
+
+dependencies:
+ collection: ^1.15.0
+ path: ^1.8.0
+ term_glyph: ^1.2.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ test: ^1.16.0
diff --git a/pkgs/source_span/test/file_test.dart b/pkgs/source_span/test/file_test.dart
new file mode 100644
index 0000000..dff51ee
--- /dev/null
+++ b/pkgs/source_span/test/file_test.dart
@@ -0,0 +1,530 @@
+// Copyright (c) 2014, 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_span/source_span.dart';
+import 'package:test/test.dart';
+
+void main() {
+ late SourceFile file;
+ setUp(() {
+ file = SourceFile.fromString('''
+foo bar baz
+whiz bang boom
+zip zap zop''', url: 'foo.dart');
+ });
+
+ group('errors', () {
+ group('for span()', () {
+ test('end must come after start', () {
+ expect(() => file.span(10, 5), throwsArgumentError);
+ });
+
+ test('start may not be negative', () {
+ expect(() => file.span(-1, 5), throwsRangeError);
+ });
+
+ test('end may not be outside the file', () {
+ expect(() => file.span(10, 100), throwsRangeError);
+ });
+ });
+
+ group('for location()', () {
+ test('offset may not be negative', () {
+ expect(() => file.location(-1), throwsRangeError);
+ });
+
+ test('offset may not be outside the file', () {
+ expect(() => file.location(100), throwsRangeError);
+ });
+ });
+
+ group('for getLine()', () {
+ test('offset may not be negative', () {
+ expect(() => file.getLine(-1), throwsRangeError);
+ });
+
+ test('offset may not be outside the file', () {
+ expect(() => file.getLine(100), throwsRangeError);
+ });
+ });
+
+ group('for getColumn()', () {
+ test('offset may not be negative', () {
+ expect(() => file.getColumn(-1), throwsRangeError);
+ });
+
+ test('offset may not be outside the file', () {
+ expect(() => file.getColumn(100), throwsRangeError);
+ });
+
+ test('line may not be negative', () {
+ expect(() => file.getColumn(1, line: -1), throwsRangeError);
+ });
+
+ test('line may not be outside the file', () {
+ expect(() => file.getColumn(1, line: 100), throwsRangeError);
+ });
+
+ test('line must be accurate', () {
+ expect(() => file.getColumn(1, line: 1), throwsRangeError);
+ });
+ });
+
+ group('getOffset()', () {
+ test('line may not be negative', () {
+ expect(() => file.getOffset(-1), throwsRangeError);
+ });
+
+ test('column may not be negative', () {
+ expect(() => file.getOffset(1, -1), throwsRangeError);
+ });
+
+ test('line may not be outside the file', () {
+ expect(() => file.getOffset(100), throwsRangeError);
+ });
+
+ test('column may not be outside the file', () {
+ expect(() => file.getOffset(2, 100), throwsRangeError);
+ });
+
+ test('column may not be outside the line', () {
+ expect(() => file.getOffset(1, 20), throwsRangeError);
+ });
+ });
+
+ group('for getText()', () {
+ test('end must come after start', () {
+ expect(() => file.getText(10, 5), throwsArgumentError);
+ });
+
+ test('start may not be negative', () {
+ expect(() => file.getText(-1, 5), throwsRangeError);
+ });
+
+ test('end may not be outside the file', () {
+ expect(() => file.getText(10, 100), throwsRangeError);
+ });
+ });
+
+ group('for span().union()', () {
+ test('source URLs must match', () {
+ final other = SourceSpan(SourceLocation(10), SourceLocation(11), '_');
+
+ expect(() => file.span(9, 10).union(other), throwsArgumentError);
+ });
+
+ test('spans may not be disjoint', () {
+ expect(() => file.span(9, 10).union(file.span(11, 12)),
+ throwsArgumentError);
+ });
+ });
+
+ test('for span().expand() source URLs must match', () {
+ final other = SourceFile.fromString('''
+foo bar baz
+whiz bang boom
+zip zap zop''', url: 'bar.dart').span(10, 11);
+
+ expect(() => file.span(9, 10).expand(other), throwsArgumentError);
+ });
+ });
+
+ test('fields work correctly', () {
+ expect(file.url, equals(Uri.parse('foo.dart')));
+ expect(file.lines, equals(3));
+ expect(file.length, equals(38));
+ });
+
+ group('new SourceFile()', () {
+ test('handles CRLF correctly', () {
+ expect(SourceFile.fromString('foo\r\nbar').getLine(6), equals(1));
+ });
+
+ test('handles a lone CR correctly', () {
+ expect(SourceFile.fromString('foo\rbar').getLine(5), equals(1));
+ });
+ });
+
+ group('span()', () {
+ test('returns a span between the given offsets', () {
+ final span = file.span(5, 10);
+ expect(span.start, equals(file.location(5)));
+ expect(span.end, equals(file.location(10)));
+ });
+
+ test('end defaults to the end of the file', () {
+ final span = file.span(5);
+ expect(span.start, equals(file.location(5)));
+ expect(span.end, equals(file.location(file.length)));
+ });
+ });
+
+ group('getLine()', () {
+ test('works for a middle character on the line', () {
+ expect(file.getLine(15), equals(1));
+ });
+
+ test('works for the first character of a line', () {
+ expect(file.getLine(12), equals(1));
+ });
+
+ test('works for a newline character', () {
+ expect(file.getLine(11), equals(0));
+ });
+
+ test('works for the last offset', () {
+ expect(file.getLine(file.length), equals(2));
+ });
+ });
+
+ group('getColumn()', () {
+ test('works for a middle character on the line', () {
+ expect(file.getColumn(15), equals(3));
+ });
+
+ test('works for the first character of a line', () {
+ expect(file.getColumn(12), equals(0));
+ });
+
+ test('works for a newline character', () {
+ expect(file.getColumn(11), equals(11));
+ });
+
+ test('works when line is passed as well', () {
+ expect(file.getColumn(12, line: 1), equals(0));
+ });
+
+ test('works for the last offset', () {
+ expect(file.getColumn(file.length), equals(11));
+ });
+ });
+
+ group('getOffset()', () {
+ test('works for a middle character on the line', () {
+ expect(file.getOffset(1, 3), equals(15));
+ });
+
+ test('works for the first character of a line', () {
+ expect(file.getOffset(1), equals(12));
+ });
+
+ test('works for a newline character', () {
+ expect(file.getOffset(0, 11), equals(11));
+ });
+
+ test('works for the last offset', () {
+ expect(file.getOffset(2, 11), equals(file.length));
+ });
+ });
+
+ group('getText()', () {
+ test('returns a substring of the source', () {
+ expect(file.getText(8, 15), equals('baz\nwhi'));
+ });
+
+ test('end defaults to the end of the file', () {
+ expect(file.getText(20), equals('g boom\nzip zap zop'));
+ });
+ });
+
+ group('FileLocation', () {
+ test('reports the correct line number', () {
+ expect(file.location(15).line, equals(1));
+ });
+
+ test('reports the correct column number', () {
+ expect(file.location(15).column, equals(3));
+ });
+
+ test('pointSpan() returns a FileSpan', () {
+ final location = file.location(15);
+ final span = location.pointSpan();
+ expect(span, isA<FileSpan>());
+ expect(span.start, equals(location));
+ expect(span.end, equals(location));
+ expect(span.text, isEmpty);
+ });
+ });
+
+ group('FileSpan', () {
+ test('text returns a substring of the source', () {
+ expect(file.span(8, 15).text, equals('baz\nwhi'));
+ });
+
+ test('text includes the last char when end is defaulted to EOF', () {
+ expect(file.span(29).text, equals('p zap zop'));
+ });
+
+ group('context', () {
+ test("contains the span's text", () {
+ final span = file.span(8, 15);
+ expect(span.context.contains(span.text), isTrue);
+ expect(span.context, equals('foo bar baz\nwhiz bang boom\n'));
+ });
+
+ test('contains the previous line for a point span at the end of a line',
+ () {
+ final span = file.span(25, 25);
+ expect(span.context, equals('whiz bang boom\n'));
+ });
+
+ test('contains the next line for a point span at the beginning of a line',
+ () {
+ final span = file.span(12, 12);
+ expect(span.context, equals('whiz bang boom\n'));
+ });
+
+ group('for a point span at the end of a file', () {
+ test('without a newline, contains the last line', () {
+ final span = file.span(file.length, file.length);
+ expect(span.context, equals('zip zap zop'));
+ });
+
+ test('with a newline, contains an empty line', () {
+ file = SourceFile.fromString('''
+foo bar baz
+whiz bang boom
+zip zap zop
+''', url: 'foo.dart');
+
+ final span = file.span(file.length, file.length);
+ expect(span.context, isEmpty);
+ });
+ });
+ });
+
+ group('union()', () {
+ late FileSpan span;
+ setUp(() {
+ span = file.span(5, 12);
+ });
+
+ test('works with a preceding adjacent span', () {
+ final other = file.span(0, 5);
+ final result = span.union(other);
+ expect(result.start, equals(other.start));
+ expect(result.end, equals(span.end));
+ expect(result.text, equals('foo bar baz\n'));
+ });
+
+ test('works with a preceding overlapping span', () {
+ final other = file.span(0, 8);
+ final result = span.union(other);
+ expect(result.start, equals(other.start));
+ expect(result.end, equals(span.end));
+ expect(result.text, equals('foo bar baz\n'));
+ });
+
+ test('works with a following adjacent span', () {
+ final other = file.span(12, 16);
+ final result = span.union(other);
+ expect(result.start, equals(span.start));
+ expect(result.end, equals(other.end));
+ expect(result.text, equals('ar baz\nwhiz'));
+ });
+
+ test('works with a following overlapping span', () {
+ final other = file.span(9, 16);
+ final result = span.union(other);
+ expect(result.start, equals(span.start));
+ expect(result.end, equals(other.end));
+ expect(result.text, equals('ar baz\nwhiz'));
+ });
+
+ test('works with an internal overlapping span', () {
+ final other = file.span(7, 10);
+ expect(span.union(other), equals(span));
+ });
+
+ test('works with an external overlapping span', () {
+ final other = file.span(0, 16);
+ expect(span.union(other), equals(other));
+ });
+
+ test('returns a FileSpan for a FileSpan input', () {
+ expect(span.union(file.span(0, 5)), isA<FileSpan>());
+ });
+
+ test('returns a base SourceSpan for a SourceSpan input', () {
+ final other = SourceSpan(SourceLocation(0, sourceUrl: 'foo.dart'),
+ SourceLocation(5, sourceUrl: 'foo.dart'), 'hey, ');
+ final result = span.union(other);
+ expect(result, isNot(isA<FileSpan>()));
+ expect(result.start, equals(other.start));
+ expect(result.end, equals(span.end));
+ expect(result.text, equals('hey, ar baz\n'));
+ });
+ });
+
+ group('expand()', () {
+ late FileSpan span;
+ setUp(() {
+ span = file.span(5, 12);
+ });
+
+ test('works with a preceding nonadjacent span', () {
+ final other = file.span(0, 3);
+ final result = span.expand(other);
+ expect(result.start, equals(other.start));
+ expect(result.end, equals(span.end));
+ expect(result.text, equals('foo bar baz\n'));
+ });
+
+ test('works with a preceding overlapping span', () {
+ final other = file.span(0, 8);
+ final result = span.expand(other);
+ expect(result.start, equals(other.start));
+ expect(result.end, equals(span.end));
+ expect(result.text, equals('foo bar baz\n'));
+ });
+
+ test('works with a following nonadjacent span', () {
+ final other = file.span(14, 16);
+ final result = span.expand(other);
+ expect(result.start, equals(span.start));
+ expect(result.end, equals(other.end));
+ expect(result.text, equals('ar baz\nwhiz'));
+ });
+
+ test('works with a following overlapping span', () {
+ final other = file.span(9, 16);
+ final result = span.expand(other);
+ expect(result.start, equals(span.start));
+ expect(result.end, equals(other.end));
+ expect(result.text, equals('ar baz\nwhiz'));
+ });
+
+ test('works with an internal overlapping span', () {
+ final other = file.span(7, 10);
+ expect(span.expand(other), equals(span));
+ });
+
+ test('works with an external overlapping span', () {
+ final other = file.span(0, 16);
+ expect(span.expand(other), equals(other));
+ });
+ });
+
+ group('subspan()', () {
+ late FileSpan span;
+ setUp(() {
+ span = file.span(5, 11); // "ar baz"
+ });
+
+ group('errors', () {
+ test('start must be greater than zero', () {
+ expect(() => span.subspan(-1), throwsRangeError);
+ });
+
+ test('start must be less than or equal to length', () {
+ expect(() => span.subspan(span.length + 1), throwsRangeError);
+ });
+
+ test('end must be greater than start', () {
+ expect(() => span.subspan(2, 1), throwsRangeError);
+ });
+
+ test('end must be less than or equal to length', () {
+ expect(() => span.subspan(0, span.length + 1), throwsRangeError);
+ });
+ });
+
+ test('preserves the source URL', () {
+ final result = span.subspan(1, 2);
+ expect(result.start.sourceUrl, equals(span.sourceUrl));
+ expect(result.end.sourceUrl, equals(span.sourceUrl));
+ });
+
+ group('returns the original span', () {
+ test('with an implicit end',
+ () => expect(span.subspan(0), equals(span)));
+
+ test('with an explicit end',
+ () => expect(span.subspan(0, span.length), equals(span)));
+ });
+
+ group('within a single line', () {
+ test('returns a strict substring of the original span', () {
+ final result = span.subspan(1, 5);
+ expect(result.text, equals('r ba'));
+ expect(result.start.offset, equals(6));
+ expect(result.start.line, equals(0));
+ expect(result.start.column, equals(6));
+ expect(result.end.offset, equals(10));
+ expect(result.end.line, equals(0));
+ expect(result.end.column, equals(10));
+ });
+
+ test('an implicit end goes to the end of the original span', () {
+ final result = span.subspan(1);
+ expect(result.text, equals('r baz'));
+ expect(result.start.offset, equals(6));
+ expect(result.start.line, equals(0));
+ expect(result.start.column, equals(6));
+ expect(result.end.offset, equals(11));
+ expect(result.end.line, equals(0));
+ expect(result.end.column, equals(11));
+ });
+
+ test('can return an empty span', () {
+ final result = span.subspan(3, 3);
+ expect(result.text, isEmpty);
+ expect(result.start.offset, equals(8));
+ expect(result.start.line, equals(0));
+ expect(result.start.column, equals(8));
+ expect(result.end, equals(result.start));
+ });
+ });
+
+ group('across multiple lines', () {
+ setUp(() {
+ span = file.span(22, 30); // "boom\nzip"
+ });
+
+ test('with start and end in the middle of a line', () {
+ final result = span.subspan(3, 6);
+ expect(result.text, equals('m\nz'));
+ expect(result.start.offset, equals(25));
+ expect(result.start.line, equals(1));
+ expect(result.start.column, equals(13));
+ expect(result.end.offset, equals(28));
+ expect(result.end.line, equals(2));
+ expect(result.end.column, equals(1));
+ });
+
+ test('with start at the end of a line', () {
+ final result = span.subspan(4, 6);
+ expect(result.text, equals('\nz'));
+ expect(result.start.offset, equals(26));
+ expect(result.start.line, equals(1));
+ expect(result.start.column, equals(14));
+ });
+
+ test('with start at the beginning of a line', () {
+ final result = span.subspan(5, 6);
+ expect(result.text, equals('z'));
+ expect(result.start.offset, equals(27));
+ expect(result.start.line, equals(2));
+ expect(result.start.column, equals(0));
+ });
+
+ test('with end at the end of a line', () {
+ final result = span.subspan(3, 4);
+ expect(result.text, equals('m'));
+ expect(result.end.offset, equals(26));
+ expect(result.end.line, equals(1));
+ expect(result.end.column, equals(14));
+ });
+
+ test('with end at the beginning of a line', () {
+ final result = span.subspan(3, 5);
+ expect(result.text, equals('m\n'));
+ expect(result.end.offset, equals(27));
+ expect(result.end.line, equals(2));
+ expect(result.end.column, equals(0));
+ });
+ });
+ });
+ });
+}
diff --git a/pkgs/source_span/test/highlight_test.dart b/pkgs/source_span/test/highlight_test.dart
new file mode 100644
index 0000000..93c42db
--- /dev/null
+++ b/pkgs/source_span/test/highlight_test.dart
@@ -0,0 +1,605 @@
+// Copyright (c) 2014, 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: prefer_interpolation_to_compose_strings
+
+import 'package:source_span/source_span.dart';
+import 'package:source_span/src/colors.dart' as colors;
+import 'package:term_glyph/term_glyph.dart' as glyph;
+import 'package:test/test.dart';
+
+void main() {
+ late bool oldAscii;
+ setUpAll(() {
+ oldAscii = glyph.ascii;
+ glyph.ascii = true;
+ });
+
+ tearDownAll(() {
+ glyph.ascii = oldAscii;
+ });
+
+ late SourceFile file;
+ setUp(() {
+ file = SourceFile.fromString('''
+foo bar baz
+whiz bang boom
+zip zap zop
+''');
+ });
+
+ test('points to the span in the source', () {
+ expect(file.span(4, 7).highlight(), equals("""
+ ,
+1 | foo bar baz
+ | ^^^
+ '"""));
+ });
+
+ test('gracefully handles a missing source URL', () {
+ final span = SourceFile.fromString('foo bar baz').span(4, 7);
+ expect(span.highlight(), equals("""
+ ,
+1 | foo bar baz
+ | ^^^
+ '"""));
+ });
+
+ group('highlights a point span', () {
+ test('in the middle of a line', () {
+ expect(file.location(4).pointSpan().highlight(), equals("""
+ ,
+1 | foo bar baz
+ | ^
+ '"""));
+ });
+
+ test('at the beginning of the file', () {
+ expect(file.location(0).pointSpan().highlight(), equals("""
+ ,
+1 | foo bar baz
+ | ^
+ '"""));
+ });
+
+ test('at the beginning of a line', () {
+ expect(file.location(12).pointSpan().highlight(), equals("""
+ ,
+2 | whiz bang boom
+ | ^
+ '"""));
+ });
+
+ test('at the end of a line', () {
+ expect(file.location(11).pointSpan().highlight(), equals("""
+ ,
+1 | foo bar baz
+ | ^
+ '"""));
+ });
+
+ test('at the end of the file', () {
+ expect(file.location(38).pointSpan().highlight(), equals("""
+ ,
+3 | zip zap zop
+ | ^
+ '"""));
+ });
+
+ test('after the end of the file', () {
+ expect(file.location(39).pointSpan().highlight(), equals("""
+ ,
+4 |
+ | ^
+ '"""));
+ });
+
+ test('at the end of the file with no trailing newline', () {
+ file = SourceFile.fromString('zip zap zop');
+ expect(file.location(10).pointSpan().highlight(), equals("""
+ ,
+1 | zip zap zop
+ | ^
+ '"""));
+ });
+
+ test('after the end of the file with no trailing newline', () {
+ file = SourceFile.fromString('zip zap zop');
+ expect(file.location(11).pointSpan().highlight(), equals("""
+ ,
+1 | zip zap zop
+ | ^
+ '"""));
+ });
+
+ test('in an empty file', () {
+ expect(SourceFile.fromString('').location(0).pointSpan().highlight(),
+ equals("""
+ ,
+1 |
+ | ^
+ '"""));
+ });
+
+ test('on an empty line', () {
+ final file = SourceFile.fromString('foo\n\nbar');
+ expect(file.location(4).pointSpan().highlight(), equals("""
+ ,
+2 |
+ | ^
+ '"""));
+ });
+ });
+
+ test('highlights a single-line file without a newline', () {
+ expect(SourceFile.fromString('foo bar').span(0, 7).highlight(), equals("""
+ ,
+1 | foo bar
+ | ^^^^^^^
+ '"""));
+ });
+
+ test('highlights text including a trailing newline', () {
+ expect(file.span(8, 12).highlight(), equals("""
+ ,
+1 | foo bar baz
+ | ^^^
+ '"""));
+ });
+
+ test('highlights a single empty line', () {
+ expect(
+ SourceFile.fromString('foo\n\nbar').span(4, 5).highlight(), equals("""
+ ,
+2 |
+ | ^
+ '"""));
+ });
+
+ test('highlights a trailing newline', () {
+ expect(file.span(11, 12).highlight(), equals("""
+ ,
+1 | foo bar baz
+ | ^
+ '"""));
+ });
+
+ group('with a multiline span', () {
+ test('highlights the middle of the first and last lines', () {
+ expect(file.span(4, 34).highlight(), equals("""
+ ,
+1 | foo bar baz
+ | ,-----^
+2 | | whiz bang boom
+3 | | zip zap zop
+ | '-------^
+ '"""));
+ });
+
+ test('works when it begins at the end of a line', () {
+ expect(file.span(11, 34).highlight(), equals("""
+ ,
+1 | foo bar baz
+ | ,------------^
+2 | | whiz bang boom
+3 | | zip zap zop
+ | '-------^
+ '"""));
+ });
+
+ test('works when it ends at the beginning of a line', () {
+ expect(file.span(4, 28).highlight(), equals("""
+ ,
+1 | foo bar baz
+ | ,-----^
+2 | | whiz bang boom
+3 | | zip zap zop
+ | '-^
+ '"""));
+ });
+
+ test('highlights the full first line', () {
+ expect(file.span(0, 34).highlight(), equals("""
+ ,
+1 | / foo bar baz
+2 | | whiz bang boom
+3 | | zip zap zop
+ | '-------^
+ '"""));
+ });
+
+ test("highlights the full first line even if it's indented", () {
+ final file = SourceFile.fromString('''
+ foo bar baz
+ whiz bang boom
+ zip zap zop
+''');
+
+ expect(file.span(2, 38).highlight(), equals("""
+ ,
+1 | / foo bar baz
+2 | | whiz bang boom
+3 | | zip zap zop
+ | '-------^
+ '"""));
+ });
+
+ test("highlights the full first line if it's empty", () {
+ final file = SourceFile.fromString('''
+foo
+
+bar
+''');
+
+ expect(file.span(4, 9).highlight(), equals(r"""
+ ,
+2 | /
+3 | \ bar
+ '"""));
+ });
+
+ test('highlights the full last line', () {
+ expect(file.span(4, 27).highlight(), equals(r"""
+ ,
+1 | foo bar baz
+ | ,-----^
+2 | \ whiz bang boom
+ '"""));
+ });
+
+ test('highlights the full last line with no trailing newline', () {
+ expect(file.span(4, 26).highlight(), equals(r"""
+ ,
+1 | foo bar baz
+ | ,-----^
+2 | \ whiz bang boom
+ '"""));
+ });
+
+ test('highlights the full last line with a trailing Windows newline', () {
+ final file = SourceFile.fromString('''
+foo bar baz\r
+whiz bang boom\r
+zip zap zop\r
+''');
+
+ expect(file.span(4, 29).highlight(), equals(r"""
+ ,
+1 | foo bar baz
+ | ,-----^
+2 | \ whiz bang boom
+ '"""));
+ });
+
+ test('highlights the full last line at the end of the file', () {
+ expect(file.span(4, 39).highlight(), equals(r"""
+ ,
+1 | foo bar baz
+ | ,-----^
+2 | | whiz bang boom
+3 | \ zip zap zop
+ '"""));
+ });
+
+ test(
+ 'highlights the full last line at the end of the file with no trailing '
+ 'newline', () {
+ final file = SourceFile.fromString('''
+foo bar baz
+whiz bang boom
+zip zap zop''');
+
+ expect(file.span(4, 38).highlight(), equals(r"""
+ ,
+1 | foo bar baz
+ | ,-----^
+2 | | whiz bang boom
+3 | \ zip zap zop
+ '"""));
+ });
+
+ test("highlights the full last line if it's empty", () {
+ final file = SourceFile.fromString('''
+foo
+
+bar
+''');
+
+ expect(file.span(0, 5).highlight(), equals(r"""
+ ,
+1 | / foo
+2 | \
+ '"""));
+ });
+
+ test('highlights multiple empty lines', () {
+ final file = SourceFile.fromString('foo\n\n\n\nbar');
+ expect(file.span(4, 7).highlight(), equals(r"""
+ ,
+2 | /
+3 | |
+4 | \
+ '"""));
+ });
+
+ // Regression test for #32
+ test('highlights the end of a line and an empty line', () {
+ final file = SourceFile.fromString('foo\n\n');
+ expect(file.span(3, 5).highlight(), equals(r"""
+ ,
+1 | foo
+ | ,----^
+2 | \
+ '"""));
+ });
+ });
+
+ group('prints tabs as spaces', () {
+ group('in a single-line span', () {
+ test('before the highlighted section', () {
+ final span = SourceFile.fromString('foo\tbar baz').span(4, 7);
+
+ expect(span.highlight(), equals("""
+ ,
+1 | foo bar baz
+ | ^^^
+ '"""));
+ });
+
+ test('within the highlighted section', () {
+ final span = SourceFile.fromString('foo bar\tbaz bang').span(4, 11);
+
+ expect(span.highlight(), equals("""
+ ,
+1 | foo bar baz bang
+ | ^^^^^^^^^^
+ '"""));
+ });
+
+ test('after the highlighted section', () {
+ final span = SourceFile.fromString('foo bar\tbaz').span(4, 7);
+
+ expect(span.highlight(), equals("""
+ ,
+1 | foo bar baz
+ | ^^^
+ '"""));
+ });
+ });
+
+ group('in a multi-line span', () {
+ test('before the highlighted section', () {
+ final span = SourceFile.fromString('''
+foo\tbar baz
+whiz bang boom
+''').span(4, 21);
+
+ expect(span.highlight(), equals("""
+ ,
+1 | foo bar baz
+ | ,--------^
+2 | | whiz bang boom
+ | '---------^
+ '"""));
+ });
+
+ test('within the first highlighted line', () {
+ final span = SourceFile.fromString('''
+foo bar\tbaz
+whiz bang boom
+''').span(4, 21);
+
+ expect(span.highlight(), equals("""
+ ,
+1 | foo bar baz
+ | ,-----^
+2 | | whiz bang boom
+ | '---------^
+ '"""));
+ });
+
+ test('at the beginning of the first highlighted line', () {
+ final span = SourceFile.fromString('''
+foo bar\tbaz
+whiz bang boom
+''').span(7, 21);
+
+ expect(span.highlight(), equals("""
+ ,
+1 | foo bar baz
+ | ,--------^
+2 | | whiz bang boom
+ | '---------^
+ '"""));
+ });
+
+ test('within a middle highlighted line', () {
+ final span = SourceFile.fromString('''
+foo bar baz
+whiz\tbang boom
+zip zap zop
+''').span(4, 34);
+
+ expect(span.highlight(), equals("""
+ ,
+1 | foo bar baz
+ | ,-----^
+2 | | whiz bang boom
+3 | | zip zap zop
+ | '-------^
+ '"""));
+ });
+
+ test('within the last highlighted line', () {
+ final span = SourceFile.fromString('''
+foo bar baz
+whiz\tbang boom
+''').span(4, 21);
+
+ expect(span.highlight(), equals("""
+ ,
+1 | foo bar baz
+ | ,-----^
+2 | | whiz bang boom
+ | '------------^
+ '"""));
+ });
+
+ test('at the end of the last highlighted line', () {
+ final span = SourceFile.fromString('''
+foo bar baz
+whiz\tbang boom
+''').span(4, 17);
+
+ expect(span.highlight(), equals("""
+ ,
+1 | foo bar baz
+ | ,-----^
+2 | | whiz bang boom
+ | '--------^
+ '"""));
+ });
+
+ test('after the highlighted section', () {
+ final span = SourceFile.fromString('''
+foo bar baz
+whiz bang\tboom
+''').span(4, 21);
+
+ expect(span.highlight(), equals("""
+ ,
+1 | foo bar baz
+ | ,-----^
+2 | | whiz bang boom
+ | '---------^
+ '"""));
+ });
+ });
+ });
+
+ group('supports lines of preceding and following context for a span', () {
+ test('within a single line', () {
+ final span = SourceSpanWithContext(
+ SourceLocation(20, line: 2, column: 5, sourceUrl: 'foo.dart'),
+ SourceLocation(27, line: 2, column: 12, sourceUrl: 'foo.dart'),
+ 'foo bar',
+ 'previous\nlines\n-----foo bar-----\nfollowing line\n');
+
+ expect(span.highlight(), equals("""
+ ,
+1 | previous
+2 | lines
+3 | -----foo bar-----
+ | ^^^^^^^
+4 | following line
+ '"""));
+ });
+
+ test('covering a full line', () {
+ final span = SourceSpanWithContext(
+ SourceLocation(15, line: 2, column: 0, sourceUrl: 'foo.dart'),
+ SourceLocation(33, line: 3, column: 0, sourceUrl: 'foo.dart'),
+ '-----foo bar-----\n',
+ 'previous\nlines\n-----foo bar-----\nfollowing line\n');
+
+ expect(span.highlight(), equals("""
+ ,
+1 | previous
+2 | lines
+3 | -----foo bar-----
+ | ^^^^^^^^^^^^^^^^^
+4 | following line
+ '"""));
+ });
+
+ test('covering multiple full lines', () {
+ final span = SourceSpanWithContext(
+ SourceLocation(15, line: 2, column: 0, sourceUrl: 'foo.dart'),
+ SourceLocation(23, line: 4, column: 0, sourceUrl: 'foo.dart'),
+ 'foo\nbar\n',
+ 'previous\nlines\nfoo\nbar\nfollowing line\n');
+
+ expect(span.highlight(), equals(r"""
+ ,
+1 | previous
+2 | lines
+3 | / foo
+4 | \ bar
+5 | following line
+ '"""));
+ });
+ });
+
+ group('colors', () {
+ test("doesn't colorize if color is false", () {
+ expect(file.span(4, 7).highlight(color: false), equals("""
+ ,
+1 | foo bar baz
+ | ^^^
+ '"""));
+ });
+
+ test('colorizes if color is true', () {
+ expect(file.span(4, 7).highlight(color: true), equals('''
+${colors.blue} ,${colors.none}
+${colors.blue}1 |${colors.none} foo ${colors.red}bar${colors.none} baz
+${colors.blue} |${colors.none} ${colors.red} ^^^${colors.none}
+${colors.blue} '${colors.none}'''));
+ });
+
+ test("uses the given color if it's passed", () {
+ expect(file.span(4, 7).highlight(color: colors.yellow), equals('''
+${colors.blue} ,${colors.none}
+${colors.blue}1 |${colors.none} foo ${colors.yellow}bar${colors.none} baz
+${colors.blue} |${colors.none} ${colors.yellow} ^^^${colors.none}
+${colors.blue} '${colors.none}'''));
+ });
+
+ test('colorizes a multiline span', () {
+ expect(file.span(4, 34).highlight(color: true), equals('''
+${colors.blue} ,${colors.none}
+${colors.blue}1 |${colors.none} foo ${colors.red}bar baz${colors.none}
+${colors.blue} |${colors.none} ${colors.red},${colors.none}${colors.red}-----^${colors.none}
+${colors.blue}2 |${colors.none} ${colors.red}|${colors.none} ${colors.red}whiz bang boom${colors.none}
+${colors.blue}3 |${colors.none} ${colors.red}|${colors.none} ${colors.red}zip zap${colors.none} zop
+${colors.blue} |${colors.none} ${colors.red}'${colors.none}${colors.red}-------^${colors.none}
+${colors.blue} '${colors.none}'''));
+ });
+
+ test('colorizes a multiline span that highlights full lines', () {
+ expect(file.span(0, 39).highlight(color: true), equals('''
+${colors.blue} ,${colors.none}
+${colors.blue}1 |${colors.none} ${colors.red}/${colors.none} ${colors.red}foo bar baz${colors.none}
+${colors.blue}2 |${colors.none} ${colors.red}|${colors.none} ${colors.red}whiz bang boom${colors.none}
+${colors.blue}3 |${colors.none} ${colors.red}\\${colors.none} ${colors.red}zip zap zop${colors.none}
+${colors.blue} '${colors.none}'''));
+ });
+ });
+
+ group('line numbers have appropriate padding', () {
+ test('with line number 9', () {
+ expect(
+ SourceFile.fromString('\n' * 8 + 'foo bar baz\n')
+ .span(8, 11)
+ .highlight(),
+ equals("""
+ ,
+9 | foo bar baz
+ | ^^^
+ '"""));
+ });
+
+ test('with line number 10', () {
+ expect(
+ SourceFile.fromString('\n' * 9 + 'foo bar baz\n')
+ .span(9, 12)
+ .highlight(),
+ equals("""
+ ,
+10 | foo bar baz
+ | ^^^
+ '"""));
+ });
+ });
+}
diff --git a/pkgs/source_span/test/location_test.dart b/pkgs/source_span/test/location_test.dart
new file mode 100644
index 0000000..bbe259b
--- /dev/null
+++ b/pkgs/source_span/test/location_test.dart
@@ -0,0 +1,97 @@
+// Copyright (c) 2014, 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_span/source_span.dart';
+import 'package:test/test.dart';
+
+void main() {
+ late SourceLocation location;
+ setUp(() {
+ location = SourceLocation(15, line: 2, column: 6, sourceUrl: 'foo.dart');
+ });
+
+ group('errors', () {
+ group('for new SourceLocation()', () {
+ test('offset may not be negative', () {
+ expect(() => SourceLocation(-1), throwsRangeError);
+ });
+
+ test('line may not be negative', () {
+ expect(() => SourceLocation(0, line: -1), throwsRangeError);
+ });
+
+ test('column may not be negative', () {
+ expect(() => SourceLocation(0, column: -1), throwsRangeError);
+ });
+ });
+
+ test('for distance() source URLs must match', () {
+ expect(() => location.distance(SourceLocation(0)), throwsArgumentError);
+ });
+
+ test('for compareTo() source URLs must match', () {
+ expect(() => location.compareTo(SourceLocation(0)), throwsArgumentError);
+ });
+ });
+
+ test('fields work correctly', () {
+ expect(location.sourceUrl, equals(Uri.parse('foo.dart')));
+ expect(location.offset, equals(15));
+ expect(location.line, equals(2));
+ expect(location.column, equals(6));
+ });
+
+ group('toolString', () {
+ test('returns a computer-readable representation', () {
+ expect(location.toolString, equals('foo.dart:3:7'));
+ });
+
+ test('gracefully handles a missing source URL', () {
+ final location = SourceLocation(15, line: 2, column: 6);
+ expect(location.toolString, equals('unknown source:3:7'));
+ });
+ });
+
+ test('distance returns the absolute distance between locations', () {
+ final other = SourceLocation(10, sourceUrl: 'foo.dart');
+ expect(location.distance(other), equals(5));
+ expect(other.distance(location), equals(5));
+ });
+
+ test('pointSpan returns an empty span at location', () {
+ final span = location.pointSpan();
+ expect(span.start, equals(location));
+ expect(span.end, equals(location));
+ expect(span.text, isEmpty);
+ });
+
+ group('compareTo()', () {
+ test('sorts by offset', () {
+ final other = SourceLocation(20, sourceUrl: 'foo.dart');
+ expect(location.compareTo(other), lessThan(0));
+ expect(other.compareTo(location), greaterThan(0));
+ });
+
+ test('considers equal locations equal', () {
+ expect(location.compareTo(location), equals(0));
+ });
+ });
+
+ group('equality', () {
+ test('two locations with the same offset and source are equal', () {
+ final other = SourceLocation(15, sourceUrl: 'foo.dart');
+ expect(location, equals(other));
+ });
+
+ test("a different offset isn't equal", () {
+ final other = SourceLocation(10, sourceUrl: 'foo.dart');
+ expect(location, isNot(equals(other)));
+ });
+
+ test("a different source isn't equal", () {
+ final other = SourceLocation(15, sourceUrl: 'bar.dart');
+ expect(location, isNot(equals(other)));
+ });
+ });
+}
diff --git a/pkgs/source_span/test/multiple_highlight_test.dart b/pkgs/source_span/test/multiple_highlight_test.dart
new file mode 100644
index 0000000..139d53c
--- /dev/null
+++ b/pkgs/source_span/test/multiple_highlight_test.dart
@@ -0,0 +1,423 @@
+// 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 'package:source_span/source_span.dart';
+import 'package:term_glyph/term_glyph.dart' as glyph;
+import 'package:test/test.dart';
+
+void main() {
+ late bool oldAscii;
+ setUpAll(() {
+ oldAscii = glyph.ascii;
+ glyph.ascii = true;
+ });
+
+ tearDownAll(() {
+ glyph.ascii = oldAscii;
+ });
+
+ late SourceFile file;
+ setUp(() {
+ file = SourceFile.fromString('''
+foo bar baz
+whiz bang boom
+zip zap zop
+fwee fwoo fwip
+argle bargle boo
+gibble bibble bop
+''', url: 'file1.txt');
+ });
+
+ test('highlights spans on separate lines', () {
+ expect(
+ file.span(17, 21).highlightMultiple(
+ 'one', {file.span(31, 34): 'two', file.span(4, 7): 'three'}),
+ equals("""
+ ,
+1 | foo bar baz
+ | === three
+2 | whiz bang boom
+ | ^^^^ one
+3 | zip zap zop
+ | === two
+ '"""));
+ });
+
+ test('highlights spans on the same line', () {
+ expect(
+ file.span(17, 21).highlightMultiple(
+ 'one', {file.span(22, 26): 'two', file.span(12, 16): 'three'}),
+ equals("""
+ ,
+2 | whiz bang boom
+ | ^^^^ one
+ | ==== three
+ | ==== two
+ '"""));
+ });
+
+ test('highlights overlapping spans on the same line', () {
+ expect(
+ file.span(17, 21).highlightMultiple(
+ 'one', {file.span(20, 26): 'two', file.span(12, 18): 'three'}),
+ equals("""
+ ,
+2 | whiz bang boom
+ | ^^^^ one
+ | ====== three
+ | ====== two
+ '"""));
+ });
+
+ test('highlights multiple multiline spans', () {
+ expect(
+ file.span(27, 54).highlightMultiple(
+ 'one', {file.span(54, 89): 'two', file.span(0, 27): 'three'}),
+ equals("""
+ ,
+1 | / foo bar baz
+2 | | whiz bang boom
+ | '--- three
+3 | / zip zap zop
+4 | | fwee fwoo fwip
+ | '--- one
+5 | / argle bargle boo
+6 | | gibble bibble bop
+ | '--- two
+ '"""));
+ });
+
+ test('highlights multiple overlapping multiline spans', () {
+ expect(
+ file.span(12, 70).highlightMultiple(
+ 'one', {file.span(54, 89): 'two', file.span(0, 27): 'three'}),
+ equals("""
+ ,
+1 | /- foo bar baz
+2 | |/ whiz bang boom
+ | '+--- three
+3 | | zip zap zop
+4 | | fwee fwoo fwip
+5 | /+ argle bargle boo
+ | |'--- one
+6 | | gibble bibble bop
+ | '---- two
+ '"""));
+ });
+
+ test('highlights many layers of overlaps', () {
+ expect(
+ file.span(0, 54).highlightMultiple('one', {
+ file.span(12, 77): 'two',
+ file.span(27, 84): 'three',
+ file.span(39, 88): 'four'
+ }),
+ equals("""
+ ,
+1 | /--- foo bar baz
+2 | |/-- whiz bang boom
+3 | ||/- zip zap zop
+4 | |||/ fwee fwoo fwip
+ | '+++--- one
+5 | ||| argle bargle boo
+6 | ||| gibble bibble bop
+ | '++------^ two
+ | '+-------------^ three
+ | '--- four
+ '"""));
+ });
+
+ group("highlights a multiline span that's a subset", () {
+ test('with no first or last line overlap', () {
+ expect(
+ file
+ .span(27, 53)
+ .highlightMultiple('inner', {file.span(12, 70): 'outer'}),
+ equals("""
+ ,
+2 | /- whiz bang boom
+3 | |/ zip zap zop
+4 | || fwee fwoo fwip
+ | |'--- inner
+5 | | argle bargle boo
+ | '---- outer
+ '"""));
+ });
+
+ test('overlapping the whole first line', () {
+ expect(
+ file
+ .span(12, 53)
+ .highlightMultiple('inner', {file.span(12, 70): 'outer'}),
+ equals("""
+ ,
+2 | // whiz bang boom
+3 | || zip zap zop
+4 | || fwee fwoo fwip
+ | |'--- inner
+5 | | argle bargle boo
+ | '---- outer
+ '"""));
+ });
+
+ test('overlapping part of first line', () {
+ expect(
+ file
+ .span(17, 53)
+ .highlightMultiple('inner', {file.span(12, 70): 'outer'}),
+ equals("""
+ ,
+2 | /- whiz bang boom
+ | |,------^
+3 | || zip zap zop
+4 | || fwee fwoo fwip
+ | |'--- inner
+5 | | argle bargle boo
+ | '---- outer
+ '"""));
+ });
+
+ test('overlapping the whole last line', () {
+ expect(
+ file
+ .span(27, 70)
+ .highlightMultiple('inner', {file.span(12, 70): 'outer'}),
+ equals("""
+ ,
+2 | /- whiz bang boom
+3 | |/ zip zap zop
+4 | || fwee fwoo fwip
+5 | || argle bargle boo
+ | |'--- inner
+ | '---- outer
+ '"""));
+ });
+
+ test('overlapping part of the last line', () {
+ expect(
+ file
+ .span(27, 66)
+ .highlightMultiple('inner', {file.span(12, 70): 'outer'}),
+ equals("""
+ ,
+2 | /- whiz bang boom
+3 | |/ zip zap zop
+4 | || fwee fwoo fwip
+5 | || argle bargle boo
+ | |'------------^ inner
+ | '---- outer
+ '"""));
+ });
+ });
+
+ group('a single-line span in a multiline span', () {
+ test('on the first line', () {
+ expect(
+ file
+ .span(17, 21)
+ .highlightMultiple('inner', {file.span(12, 70): 'outer'}),
+ equals("""
+ ,
+2 | / whiz bang boom
+ | | ^^^^ inner
+3 | | zip zap zop
+4 | | fwee fwoo fwip
+5 | | argle bargle boo
+ | '--- outer
+ '"""));
+ });
+
+ test('in the middle', () {
+ expect(
+ file
+ .span(31, 34)
+ .highlightMultiple('inner', {file.span(12, 70): 'outer'}),
+ equals("""
+ ,
+2 | / whiz bang boom
+3 | | zip zap zop
+ | | ^^^ inner
+4 | | fwee fwoo fwip
+5 | | argle bargle boo
+ | '--- outer
+ '"""));
+ });
+
+ test('on the last line', () {
+ expect(
+ file
+ .span(60, 66)
+ .highlightMultiple('inner', {file.span(12, 70): 'outer'}),
+ equals("""
+ ,
+2 | / whiz bang boom
+3 | | zip zap zop
+4 | | fwee fwoo fwip
+5 | | argle bargle boo
+ | | ^^^^^^ inner
+ | '--- outer
+ '"""));
+ });
+ });
+
+ group('writes headers when highlighting multiple files', () {
+ test('writes all file URLs', () {
+ final span2 = SourceFile.fromString('''
+quibble bibble boop
+''', url: 'file2.txt').span(8, 14);
+
+ expect(
+ file.span(31, 34).highlightMultiple('one', {span2: 'two'}), equals("""
+ ,--> file1.txt
+3 | zip zap zop
+ | ^^^ one
+ '
+ ,--> file2.txt
+1 | quibble bibble boop
+ | ====== two
+ '"""));
+ });
+
+ test('allows secondary spans to have null URL', () {
+ final span2 = SourceSpan(SourceLocation(1), SourceLocation(4), 'foo');
+
+ expect(
+ file.span(31, 34).highlightMultiple('one', {span2: 'two'}), equals("""
+ ,--> file1.txt
+3 | zip zap zop
+ | ^^^ one
+ '
+ ,
+1 | foo
+ | === two
+ '"""));
+ });
+
+ test('allows primary span to have null URL', () {
+ final span1 = SourceSpan(SourceLocation(1), SourceLocation(4), 'foo');
+
+ expect(
+ span1.highlightMultiple('one', {file.span(31, 34): 'two'}), equals("""
+ ,
+1 | foo
+ | ^^^ one
+ '
+ ,--> file1.txt
+3 | zip zap zop
+ | === two
+ '"""));
+ });
+ });
+
+ test('highlights multiple null URLs as separate files', () {
+ final span1 = SourceSpan(SourceLocation(1), SourceLocation(4), 'foo');
+ final span2 = SourceSpan(SourceLocation(1), SourceLocation(4), 'bar');
+
+ expect(span1.highlightMultiple('one', {span2: 'two'}), equals("""
+ ,
+1 | foo
+ | ^^^ one
+ '
+ ,
+1 | bar
+ | === two
+ '"""));
+ });
+
+ group('indents mutli-line labels', () {
+ test('for the primary label', () {
+ expect(file.span(17, 21).highlightMultiple('line 1\nline 2\nline 3', {}),
+ equals("""
+ ,
+2 | whiz bang boom
+ | ^^^^ line 1
+ | line 2
+ | line 3
+ '"""));
+ });
+
+ group('for a secondary label', () {
+ test('on the same line', () {
+ expect(
+ file.span(17, 21).highlightMultiple(
+ 'primary', {file.span(22, 26): 'line 1\nline 2\nline 3'}),
+ equals("""
+ ,
+2 | whiz bang boom
+ | ^^^^ primary
+ | ==== line 1
+ | line 2
+ | line 3
+ '"""));
+ });
+
+ test('on a different line', () {
+ expect(
+ file.span(17, 21).highlightMultiple(
+ 'primary', {file.span(31, 34): 'line 1\nline 2\nline 3'}),
+ equals("""
+ ,
+2 | whiz bang boom
+ | ^^^^ primary
+3 | zip zap zop
+ | === line 1
+ | line 2
+ | line 3
+ '"""));
+ });
+ });
+
+ group('for a multiline span', () {
+ test('that covers the whole last line', () {
+ expect(
+ file.span(12, 70).highlightMultiple('line 1\nline 2\nline 3', {}),
+ equals("""
+ ,
+2 | / whiz bang boom
+3 | | zip zap zop
+4 | | fwee fwoo fwip
+5 | | argle bargle boo
+ | '--- line 1
+ | line 2
+ | line 3
+ '"""));
+ });
+
+ test('that covers part of the last line', () {
+ expect(
+ file.span(12, 66).highlightMultiple('line 1\nline 2\nline 3', {}),
+ equals("""
+ ,
+2 | / whiz bang boom
+3 | | zip zap zop
+4 | | fwee fwoo fwip
+5 | | argle bargle boo
+ | '------------^ line 1
+ | line 2
+ | line 3
+ '"""));
+ });
+ });
+
+ test('with an overlapping span', () {
+ expect(
+ file.span(12, 70).highlightMultiple('line 1\nline 2\nline 3',
+ {file.span(54, 89): 'two', file.span(0, 27): 'three'}),
+ equals("""
+ ,
+1 | /- foo bar baz
+2 | |/ whiz bang boom
+ | '+--- three
+3 | | zip zap zop
+4 | | fwee fwoo fwip
+5 | /+ argle bargle boo
+ | |'--- line 1
+ | | line 2
+ | | line 3
+6 | | gibble bibble bop
+ | '---- two
+ '"""));
+ });
+ });
+}
diff --git a/pkgs/source_span/test/span_test.dart b/pkgs/source_span/test/span_test.dart
new file mode 100644
index 0000000..22c498e
--- /dev/null
+++ b/pkgs/source_span/test/span_test.dart
@@ -0,0 +1,432 @@
+// Copyright (c) 2014, 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_span/source_span.dart';
+import 'package:source_span/src/colors.dart' as colors;
+import 'package:term_glyph/term_glyph.dart' as glyph;
+import 'package:test/test.dart';
+
+void main() {
+ late bool oldAscii;
+
+ setUpAll(() {
+ oldAscii = glyph.ascii;
+ glyph.ascii = true;
+ });
+
+ tearDownAll(() {
+ glyph.ascii = oldAscii;
+ });
+
+ late SourceSpan span;
+ setUp(() {
+ span = SourceSpan(SourceLocation(5, sourceUrl: 'foo.dart'),
+ SourceLocation(12, sourceUrl: 'foo.dart'), 'foo bar');
+ });
+
+ group('errors', () {
+ group('for new SourceSpan()', () {
+ test('source URLs must match', () {
+ final start = SourceLocation(0, sourceUrl: 'foo.dart');
+ final end = SourceLocation(1, sourceUrl: 'bar.dart');
+ expect(() => SourceSpan(start, end, '_'), throwsArgumentError);
+ });
+
+ test('end must come after start', () {
+ final start = SourceLocation(1);
+ final end = SourceLocation(0);
+ expect(() => SourceSpan(start, end, '_'), throwsArgumentError);
+ });
+
+ test('text must be the right length', () {
+ final start = SourceLocation(0);
+ final end = SourceLocation(1);
+ expect(() => SourceSpan(start, end, 'abc'), throwsArgumentError);
+ });
+ });
+
+ group('for new SourceSpanWithContext()', () {
+ test('context must contain text', () {
+ final start = SourceLocation(2);
+ final end = SourceLocation(5);
+ expect(() => SourceSpanWithContext(start, end, 'abc', '--axc--'),
+ throwsArgumentError);
+ });
+
+ test('text starts at start.column in context', () {
+ final start = SourceLocation(3);
+ final end = SourceLocation(5);
+ expect(() => SourceSpanWithContext(start, end, 'abc', '--abc--'),
+ throwsArgumentError);
+ });
+
+ test('text starts at start.column of line in multi-line context', () {
+ final start = SourceLocation(4, line: 55, column: 3);
+ final end = SourceLocation(7, line: 55, column: 6);
+ expect(() => SourceSpanWithContext(start, end, 'abc', '\n--abc--'),
+ throwsArgumentError);
+ expect(() => SourceSpanWithContext(start, end, 'abc', '\n----abc--'),
+ throwsArgumentError);
+ expect(() => SourceSpanWithContext(start, end, 'abc', '\n\n--abc--'),
+ throwsArgumentError);
+
+ // However, these are valid:
+ SourceSpanWithContext(start, end, 'abc', '\n---abc--');
+ SourceSpanWithContext(start, end, 'abc', '\n\n---abc--');
+ });
+
+ test('text can occur multiple times in context', () {
+ final start1 = SourceLocation(4, line: 55, column: 2);
+ final end1 = SourceLocation(7, line: 55, column: 5);
+ final start2 = SourceLocation(4, line: 55, column: 8);
+ final end2 = SourceLocation(7, line: 55, column: 11);
+ SourceSpanWithContext(start1, end1, 'abc', '--abc---abc--\n');
+ SourceSpanWithContext(start1, end1, 'abc', '--abc--abc--\n');
+ SourceSpanWithContext(start2, end2, 'abc', '--abc---abc--\n');
+ SourceSpanWithContext(start2, end2, 'abc', '---abc--abc--\n');
+ expect(
+ () => SourceSpanWithContext(start1, end1, 'abc', '---abc--abc--\n'),
+ throwsArgumentError);
+ expect(
+ () => SourceSpanWithContext(start2, end2, 'abc', '--abc--abc--\n'),
+ throwsArgumentError);
+ });
+ });
+
+ group('for union()', () {
+ test('source URLs must match', () {
+ final other = SourceSpan(SourceLocation(12, sourceUrl: 'bar.dart'),
+ SourceLocation(13, sourceUrl: 'bar.dart'), '_');
+
+ expect(() => span.union(other), throwsArgumentError);
+ });
+
+ test('spans may not be disjoint', () {
+ final other = SourceSpan(SourceLocation(13, sourceUrl: 'foo.dart'),
+ SourceLocation(14, sourceUrl: 'foo.dart'), '_');
+
+ expect(() => span.union(other), throwsArgumentError);
+ });
+ });
+
+ test('for compareTo() source URLs must match', () {
+ final other = SourceSpan(SourceLocation(12, sourceUrl: 'bar.dart'),
+ SourceLocation(13, sourceUrl: 'bar.dart'), '_');
+
+ expect(() => span.compareTo(other), throwsArgumentError);
+ });
+ });
+
+ test('fields work correctly', () {
+ expect(span.start, equals(SourceLocation(5, sourceUrl: 'foo.dart')));
+ expect(span.end, equals(SourceLocation(12, sourceUrl: 'foo.dart')));
+ expect(span.sourceUrl, equals(Uri.parse('foo.dart')));
+ expect(span.length, equals(7));
+ });
+
+ group('union()', () {
+ test('works with a preceding adjacent span', () {
+ final other = SourceSpan(SourceLocation(0, sourceUrl: 'foo.dart'),
+ SourceLocation(5, sourceUrl: 'foo.dart'), 'hey, ');
+
+ final result = span.union(other);
+ expect(result.start, equals(other.start));
+ expect(result.end, equals(span.end));
+ expect(result.text, equals('hey, foo bar'));
+ });
+
+ test('works with a preceding overlapping span', () {
+ final other = SourceSpan(SourceLocation(0, sourceUrl: 'foo.dart'),
+ SourceLocation(8, sourceUrl: 'foo.dart'), 'hey, foo');
+
+ final result = span.union(other);
+ expect(result.start, equals(other.start));
+ expect(result.end, equals(span.end));
+ expect(result.text, equals('hey, foo bar'));
+ });
+
+ test('works with a following adjacent span', () {
+ final other = SourceSpan(SourceLocation(12, sourceUrl: 'foo.dart'),
+ SourceLocation(16, sourceUrl: 'foo.dart'), ' baz');
+
+ final result = span.union(other);
+ expect(result.start, equals(span.start));
+ expect(result.end, equals(other.end));
+ expect(result.text, equals('foo bar baz'));
+ });
+
+ test('works with a following overlapping span', () {
+ final other = SourceSpan(SourceLocation(9, sourceUrl: 'foo.dart'),
+ SourceLocation(16, sourceUrl: 'foo.dart'), 'bar baz');
+
+ final result = span.union(other);
+ expect(result.start, equals(span.start));
+ expect(result.end, equals(other.end));
+ expect(result.text, equals('foo bar baz'));
+ });
+
+ test('works with an internal overlapping span', () {
+ final other = SourceSpan(SourceLocation(7, sourceUrl: 'foo.dart'),
+ SourceLocation(10, sourceUrl: 'foo.dart'), 'o b');
+
+ expect(span.union(other), equals(span));
+ });
+
+ test('works with an external overlapping span', () {
+ final other = SourceSpan(SourceLocation(0, sourceUrl: 'foo.dart'),
+ SourceLocation(16, sourceUrl: 'foo.dart'), 'hey, foo bar baz');
+
+ expect(span.union(other), equals(other));
+ });
+ });
+
+ group('subspan()', () {
+ group('errors', () {
+ test('start must be greater than zero', () {
+ expect(() => span.subspan(-1), throwsRangeError);
+ });
+
+ test('start must be less than or equal to length', () {
+ expect(() => span.subspan(span.length + 1), throwsRangeError);
+ });
+
+ test('end must be greater than start', () {
+ expect(() => span.subspan(2, 1), throwsRangeError);
+ });
+
+ test('end must be less than or equal to length', () {
+ expect(() => span.subspan(0, span.length + 1), throwsRangeError);
+ });
+ });
+
+ test('preserves the source URL', () {
+ final result = span.subspan(1, 2);
+ expect(result.start.sourceUrl, equals(span.sourceUrl));
+ expect(result.end.sourceUrl, equals(span.sourceUrl));
+ });
+
+ test('preserves the context', () {
+ final start = SourceLocation(2);
+ final end = SourceLocation(5);
+ final span = SourceSpanWithContext(start, end, 'abc', '--abc--');
+ expect(span.subspan(1, 2).context, equals('--abc--'));
+ });
+
+ group('returns the original span', () {
+ test('with an implicit end', () => expect(span.subspan(0), equals(span)));
+
+ test('with an explicit end',
+ () => expect(span.subspan(0, span.length), equals(span)));
+ });
+
+ group('within a single line', () {
+ test('returns a strict substring of the original span', () {
+ final result = span.subspan(1, 5);
+ expect(result.text, equals('oo b'));
+ expect(result.start.offset, equals(6));
+ expect(result.start.line, equals(0));
+ expect(result.start.column, equals(6));
+ expect(result.end.offset, equals(10));
+ expect(result.end.line, equals(0));
+ expect(result.end.column, equals(10));
+ });
+
+ test('an implicit end goes to the end of the original span', () {
+ final result = span.subspan(1);
+ expect(result.text, equals('oo bar'));
+ expect(result.start.offset, equals(6));
+ expect(result.start.line, equals(0));
+ expect(result.start.column, equals(6));
+ expect(result.end.offset, equals(12));
+ expect(result.end.line, equals(0));
+ expect(result.end.column, equals(12));
+ });
+
+ test('can return an empty span', () {
+ final result = span.subspan(3, 3);
+ expect(result.text, isEmpty);
+ expect(result.start.offset, equals(8));
+ expect(result.start.line, equals(0));
+ expect(result.start.column, equals(8));
+ expect(result.end, equals(result.start));
+ });
+ });
+
+ group('across multiple lines', () {
+ setUp(() {
+ span = SourceSpan(
+ SourceLocation(5, line: 2, column: 0),
+ SourceLocation(16, line: 4, column: 3),
+ 'foo\n'
+ 'bar\n'
+ 'baz');
+ });
+
+ test('with start and end in the middle of a line', () {
+ final result = span.subspan(2, 5);
+ expect(result.text, equals('o\nb'));
+ expect(result.start.offset, equals(7));
+ expect(result.start.line, equals(2));
+ expect(result.start.column, equals(2));
+ expect(result.end.offset, equals(10));
+ expect(result.end.line, equals(3));
+ expect(result.end.column, equals(1));
+ });
+
+ test('with start at the end of a line', () {
+ final result = span.subspan(3, 5);
+ expect(result.text, equals('\nb'));
+ expect(result.start.offset, equals(8));
+ expect(result.start.line, equals(2));
+ expect(result.start.column, equals(3));
+ });
+
+ test('with start at the beginning of a line', () {
+ final result = span.subspan(4, 5);
+ expect(result.text, equals('b'));
+ expect(result.start.offset, equals(9));
+ expect(result.start.line, equals(3));
+ expect(result.start.column, equals(0));
+ });
+
+ test('with end at the end of a line', () {
+ final result = span.subspan(2, 3);
+ expect(result.text, equals('o'));
+ expect(result.end.offset, equals(8));
+ expect(result.end.line, equals(2));
+ expect(result.end.column, equals(3));
+ });
+
+ test('with end at the beginning of a line', () {
+ final result = span.subspan(2, 4);
+ expect(result.text, equals('o\n'));
+ expect(result.end.offset, equals(9));
+ expect(result.end.line, equals(3));
+ expect(result.end.column, equals(0));
+ });
+ });
+ });
+
+ group('message()', () {
+ test('prints the text being described', () {
+ expect(span.message('oh no'), equals("""
+line 1, column 6 of foo.dart: oh no
+ ,
+1 | foo bar
+ | ^^^^^^^
+ '"""));
+ });
+
+ test('gracefully handles a missing source URL', () {
+ final span = SourceSpan(SourceLocation(5), SourceLocation(12), 'foo bar');
+
+ expect(span.message('oh no'), equalsIgnoringWhitespace("""
+line 1, column 6: oh no
+ ,
+1 | foo bar
+ | ^^^^^^^
+ '"""));
+ });
+
+ test('gracefully handles empty text', () {
+ final span = SourceSpan(SourceLocation(5), SourceLocation(5), '');
+
+ expect(span.message('oh no'), equals('line 1, column 6: oh no'));
+ });
+
+ test("doesn't colorize if color is false", () {
+ expect(span.message('oh no', color: false), equals("""
+line 1, column 6 of foo.dart: oh no
+ ,
+1 | foo bar
+ | ^^^^^^^
+ '"""));
+ });
+
+ test('colorizes if color is true', () {
+ expect(span.message('oh no', color: true), equals("""
+line 1, column 6 of foo.dart: oh no
+${colors.blue} ,${colors.none}
+${colors.blue}1 |${colors.none} ${colors.red}foo bar${colors.none}
+${colors.blue} |${colors.none} ${colors.red}^^^^^^^${colors.none}
+${colors.blue} '${colors.none}"""));
+ });
+
+ test("uses the given color if it's passed", () {
+ expect(span.message('oh no', color: colors.yellow), equals("""
+line 1, column 6 of foo.dart: oh no
+${colors.blue} ,${colors.none}
+${colors.blue}1 |${colors.none} ${colors.yellow}foo bar${colors.none}
+${colors.blue} |${colors.none} ${colors.yellow}^^^^^^^${colors.none}
+${colors.blue} '${colors.none}"""));
+ });
+
+ test('with context, underlines the right column', () {
+ final spanWithContext = SourceSpanWithContext(
+ SourceLocation(5, sourceUrl: 'foo.dart'),
+ SourceLocation(12, sourceUrl: 'foo.dart'),
+ 'foo bar',
+ '-----foo bar-----');
+
+ expect(spanWithContext.message('oh no', color: colors.yellow), equals("""
+line 1, column 6 of foo.dart: oh no
+${colors.blue} ,${colors.none}
+${colors.blue}1 |${colors.none} -----${colors.yellow}foo bar${colors.none}-----
+${colors.blue} |${colors.none} ${colors.yellow} ^^^^^^^${colors.none}
+${colors.blue} '${colors.none}"""));
+ });
+ });
+
+ group('compareTo()', () {
+ test('sorts by start location first', () {
+ final other = SourceSpan(SourceLocation(6, sourceUrl: 'foo.dart'),
+ SourceLocation(14, sourceUrl: 'foo.dart'), 'oo bar b');
+
+ expect(span.compareTo(other), lessThan(0));
+ expect(other.compareTo(span), greaterThan(0));
+ });
+
+ test('sorts by length second', () {
+ final other = SourceSpan(SourceLocation(5, sourceUrl: 'foo.dart'),
+ SourceLocation(14, sourceUrl: 'foo.dart'), 'foo bar b');
+
+ expect(span.compareTo(other), lessThan(0));
+ expect(other.compareTo(span), greaterThan(0));
+ });
+
+ test('considers equal spans equal', () {
+ expect(span.compareTo(span), equals(0));
+ });
+ });
+
+ group('equality', () {
+ test('two spans with the same locations are equal', () {
+ final other = SourceSpan(SourceLocation(5, sourceUrl: 'foo.dart'),
+ SourceLocation(12, sourceUrl: 'foo.dart'), 'foo bar');
+
+ expect(span, equals(other));
+ });
+
+ test("a different start isn't equal", () {
+ final other = SourceSpan(SourceLocation(0, sourceUrl: 'foo.dart'),
+ SourceLocation(12, sourceUrl: 'foo.dart'), 'hey, foo bar');
+
+ expect(span, isNot(equals(other)));
+ });
+
+ test("a different end isn't equal", () {
+ final other = SourceSpan(SourceLocation(5, sourceUrl: 'foo.dart'),
+ SourceLocation(16, sourceUrl: 'foo.dart'), 'foo bar baz');
+
+ expect(span, isNot(equals(other)));
+ });
+
+ test("a different source URL isn't equal", () {
+ final other = SourceSpan(SourceLocation(5, sourceUrl: 'bar.dart'),
+ SourceLocation(12, sourceUrl: 'bar.dart'), 'foo bar');
+
+ expect(span, isNot(equals(other)));
+ });
+ });
+}
diff --git a/pkgs/source_span/test/utils_test.dart b/pkgs/source_span/test/utils_test.dart
new file mode 100644
index 0000000..91397c0
--- /dev/null
+++ b/pkgs/source_span/test/utils_test.dart
@@ -0,0 +1,58 @@
+// 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:source_span/src/utils.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('find line start', () {
+ test('skip entries in wrong column', () {
+ const context = '0_bb\n1_bbb\n2b____\n3bbb\n';
+ final index = findLineStart(context, 'b', 1)!;
+ expect(index, 11);
+ expect(context.substring(index - 1, index + 3), '\n2b_');
+ });
+
+ test('end of line column for empty text', () {
+ const context = '0123\n56789\nabcdefgh\n';
+ final index = findLineStart(context, '', 5)!;
+ expect(index, 5);
+ expect(context[index], '5');
+ });
+
+ test('column at the end of the file for empty text', () {
+ var context = '0\n2\n45\n';
+ var index = findLineStart(context, '', 2)!;
+ expect(index, 4);
+ expect(context[index], '4');
+
+ context = '0\n2\n45';
+ index = findLineStart(context, '', 2)!;
+ expect(index, 4);
+ });
+
+ test('empty text in empty context', () {
+ final index = findLineStart('', '', 0);
+ expect(index, 0);
+ });
+
+ test('found on the first line', () {
+ const context = '0\n2\n45\n';
+ final index = findLineStart(context, '0', 0);
+ expect(index, 0);
+ });
+
+ test('finds text that starts with a newline', () {
+ const context = '0\n2\n45\n';
+ final index = findLineStart(context, '\n2', 1);
+ expect(index, 0);
+ });
+
+ test('not found', () {
+ const context = '0\n2\n45\n';
+ final index = findLineStart(context, '0', 1);
+ expect(index, isNull);
+ });
+ });
+}
diff --git a/pkgs/sse/.gitignore b/pkgs/sse/.gitignore
new file mode 100644
index 0000000..1467782
--- /dev/null
+++ b/pkgs/sse/.gitignore
@@ -0,0 +1,3 @@
+.dart_tool
+pubspec.lock
+test/web/index.dart.js.deps
diff --git a/pkgs/sse/AUTHORS b/pkgs/sse/AUTHORS
new file mode 100644
index 0000000..7c12ae6
--- /dev/null
+++ b/pkgs/sse/AUTHORS
@@ -0,0 +1,6 @@
+# Below is a list of people and organizations that have contributed
+# to the Dart project. Names should be added to the list like so:
+#
+# Name/Organization <email address>
+
+Google Inc.
diff --git a/pkgs/sse/CHANGELOG.md b/pkgs/sse/CHANGELOG.md
new file mode 100644
index 0000000..0387ba9
--- /dev/null
+++ b/pkgs/sse/CHANGELOG.md
@@ -0,0 +1,178 @@
+## 4.1.7
+
+- Move to `dart-lang/tools` monorepo.
+
+## 4.1.6
+
+- Require package `web: '>=0.5.0 <2.0.0'`.
+
+## 4.1.5
+
+- Drop unneeded dependency on `package:js`.
+- Update the minimum Dart SDK version to `3.3.0`.
+- Support the latest `package:web`.
+
+## 4.1.4
+
+- Fix incorrect cast causing failure with `dart2wasm`.
+
+## 4.1.3
+
+- Update the minimum Dart SDK version to `3.2.0`.
+
+## 4.1.2
+
+- Send `fetch` requests instead of `XHR` requests.
+- Add an optional `debugKey` parameter to `SseClient` to include in logging.
+- Add a dependency on `package:js`.
+- Update the minimum Dart SDK version to `2.16.0`.
+
+## 4.1.1
+
+- Apply `keepAlive` logic to `SocketException`s.
+- Switch from using `package:pedantic` to `package:lints`
+- Rev the minimum required SDK to 2.15.
+- Populate the pubspec `repository` field.
+
+## 4.1.0
+
+- Limit the number of concurrent requests to prevent Chrome from automatically
+ dropping them on the floor.
+
+## 4.0.0
+
+- Support null safety.
+
+## 3.8.3
+
+- Require the latest shelf and remove dead code.
+
+## 3.8.2
+
+- Complete `onConnected` with an error if the `SseClient` receives an error
+ before the connection is successfully opened.
+
+## 3.8.1
+
+- Fix an issue where closing the `SseConnection` stream would result in an
+ error.
+
+## 3.8.0
+
+- Add `onConnected` to replace `onOpen`.
+- Fix an issue where failed requests would not add a `done` event to the
+ connection `sink`.
+
+## 3.7.0
+
+- Deprecate the client's `onOpen` getter. Messages will now be buffered until a
+ connection is established.
+
+## 3.6.1
+
+- Drop dependency on `package:uuid`.
+
+## 3.6.0
+
+- Improve performance by buffering out of order messages in the server instead
+ of the client.
+
+\*\* Note \*\* This is not modelled as a breaking change as the server can
+handle messages from older clients. However, clients should be using the latest
+server if they require order guarantees.
+
+## 3.5.0
+
+- Add new `shutdown` methods on `SseHandler` and `SseConnection` to allow
+ closing connections immediately, ignoring any keep-alive periods.
+
+## 3.4.0
+
+- Remove `onClose` from `SseConnection` and ensure the corresponding
+ `sink.close` correctly fires.
+
+## 3.3.0
+
+- Add an `onClose` event to the `SseConnection`. This allows consumers to listen
+ to this event in lue of `sseConnection.sink.done` as that is not guaranteed to
+ fire.
+
+## 3.2.2
+
+- Fix an issue where `keepAlive` may cause state errors when attempting to send
+ messages on a closed stream.
+
+## 3.2.1
+
+- Fix an issue where `keepAlive` would only allow a single reconnection.
+
+## 3.2.0
+
+- Re-expose `isInKeepAlivePeriod` flag on `SseConnection`. This flag will be
+ `true` when a connection has been dropped and is in the keep-alive period
+ waiting for a client to reconnect.
+
+## 3.1.2
+
+- Fix an issue where the `SseClient` would not send a `done` event when there
+ was an error with the SSE connection.
+
+## 3.1.1
+
+- Make `isInKeepAlive` on `SseConnection` private.
+
+**Note that this is a breaking change but in actuality no one should be
+depending on this API.**
+
+## 3.1.0
+
+- Add optional `keepAlive` parameter to the `SseHandler`. If `keepAlive` is
+ supplied, the connection will remain active for this period after a disconnect
+ and can be reconnected transparently. If there is no reconnect within that
+ period, the connection will be closed normally.
+
+## 3.0.0
+
+- Add retry logic.
+
+**Possible Breaking Change Error messages may now be delayed up to 5 seconds in
+the client.**
+
+## 2.1.2
+
+- Remove `package:http` dependency.
+
+## 2.1.1
+
+- Use proper headers delimiter.
+
+## 2.1.0
+
+- Support Firefox.
+
+## 2.0.3
+
+- Fix an issue where messages could come out of order.
+
+## 2.0.2
+
+- Support the latest `package:stream_channel`.
+- Require Dart SDK `>=2.1.0 <3.0.0`.
+
+## 2.0.1
+
+- Update to `package:uuid` version 2.0.
+
+## 2.0.0
+
+- No longer expose `close` and `onClose` on an `SseConnection`. This is simply
+ handled by the underlying `stream` / `sink`.
+- Fix a bug where resources of the `SseConnection` were not properly closed.
+
+## 1.0.0
+
+- Internal cleanup.
+
+## 0.0.1
+
+- Initial commit.
diff --git a/pkgs/sse/LICENSE b/pkgs/sse/LICENSE
new file mode 100644
index 0000000..a0d5f54
--- /dev/null
+++ b/pkgs/sse/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2019, 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.
diff --git a/pkgs/sse/README.md b/pkgs/sse/README.md
new file mode 100644
index 0000000..ef51415
--- /dev/null
+++ b/pkgs/sse/README.md
@@ -0,0 +1,14 @@
+[](https://github.com/dart-lang/tools/actions/workflows/sse.yaml)
+[](https://pub.dev/packages/sse)
+[](https://pub.dev/packages/sse/publisher)
+
+This package provides support for bi-directional communication through Server
+Sent Events and corresponding POST requests.
+
+This package is not intended to be a general purpose SSE package, but instead is
+a bidirectional protocol for use when Websockets are unavailable. That is, both
+the client and the server expose a `sink` and `stream` on which to send and
+receive messages respectively.
+
+Both the server and client have implicit assumptions on each other and therefore
+a client from this package must be paired with a server from this package.
diff --git a/pkgs/sse/analysis_options.yaml b/pkgs/sse/analysis_options.yaml
new file mode 100644
index 0000000..6729bd9
--- /dev/null
+++ b/pkgs/sse/analysis_options.yaml
@@ -0,0 +1,13 @@
+# https://dart.dev/guides/language/analysis-options
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-casts: true
+
+linter:
+ rules:
+ - avoid_unused_constructor_parameters
+ - cancel_subscriptions
+ - literal_only_boolean_expressions
+ - no_adjacent_strings_in_list
diff --git a/pkgs/sse/example/index.dart b/pkgs/sse/example/index.dart
new file mode 100644
index 0000000..0ed7596
--- /dev/null
+++ b/pkgs/sse/example/index.dart
@@ -0,0 +1,15 @@
+// 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 'package:sse/client/sse_client.dart';
+
+/// A basic example which should be used in a browser that supports SSE.
+void main() {
+ var channel = SseClient('/sseHandler');
+
+ channel.stream.listen((s) {
+ // Listen for messages and send them back.
+ channel.sink.add(s);
+ });
+}
diff --git a/pkgs/sse/example/server.dart b/pkgs/sse/example/server.dart
new file mode 100644
index 0000000..b6ee750
--- /dev/null
+++ b/pkgs/sse/example/server.dart
@@ -0,0 +1,21 @@
+// 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 'package:shelf/shelf_io.dart' as io;
+import 'package:sse/server/sse_handler.dart';
+
+/// A basic server which sets up an SSE handler.
+///
+/// When a client connects it will send a simple message and print the
+/// response.
+void main() async {
+ var handler = SseHandler(Uri.parse('/sseHandler'));
+ await io.serve(handler.handler, 'localhost', 0);
+ var connections = handler.connections;
+ while (await connections.hasNext) {
+ var connection = await connections.next;
+ connection.sink.add('foo');
+ connection.stream.listen(print);
+ }
+}
diff --git a/pkgs/sse/lib/client/sse_client.dart b/pkgs/sse/lib/client/sse_client.dart
new file mode 100644
index 0000000..4d3df49
--- /dev/null
+++ b/pkgs/sse/lib/client/sse_client.dart
@@ -0,0 +1,166 @@
+// 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:async';
+import 'dart:convert';
+import 'dart:js_interop';
+
+import 'package:logging/logging.dart';
+import 'package:pool/pool.dart';
+import 'package:stream_channel/stream_channel.dart';
+import 'package:web/web.dart';
+
+import '../src/util/uuid.dart';
+
+/// Limit for the number of concurrent outgoing requests.
+///
+/// Chrome drops outgoing requests on the floor after some threshold. To prevent
+/// these errors we buffer outgoing requests with a pool.
+///
+/// Note Chrome's limit is 6000. So this gives us plenty of headroom.
+final _requestPool = Pool(1000);
+
+/// A client for bi-directional sse communication.
+///
+/// The client can send any JSON-encodable messages to the server by adding
+/// them to the [sink] and listen to messages from the server on the [stream].
+class SseClient extends StreamChannelMixin<String?> {
+ final String _clientId;
+
+ final _incomingController = StreamController<String>();
+
+ final _outgoingController = StreamController<String>();
+
+ final _logger = Logger('SseClient');
+
+ final _onConnected = Completer<void>();
+
+ int _lastMessageId = -1;
+
+ late EventSource _eventSource;
+
+ late String _serverUrl;
+
+ Timer? _errorTimer;
+
+ /// [serverUrl] is the URL under which the server is listening for
+ /// incoming bi-directional SSE connections. [debugKey] is an optional key
+ /// that can be used to identify the SSE connection.
+ SseClient(String serverUrl, {String? debugKey})
+ : _clientId = debugKey == null
+ ? generateUuidV4()
+ : '$debugKey-${generateUuidV4()}' {
+ _serverUrl = '$serverUrl?sseClientId=$_clientId';
+ _eventSource =
+ EventSource(_serverUrl, EventSourceInit(withCredentials: true));
+ _eventSource.onOpen.first.whenComplete(() {
+ _onConnected.complete();
+ _outgoingController.stream
+ .listen(_onOutgoingMessage, onDone: _onOutgoingDone);
+ });
+ _eventSource.addEventListener('message', _onIncomingMessage.toJS);
+ _eventSource.addEventListener('control', _onIncomingControlMessage.toJS);
+
+ _eventSource.onOpen.listen((_) {
+ _errorTimer?.cancel();
+ });
+ _eventSource.onError.listen((error) {
+ if (!(_errorTimer?.isActive ?? false)) {
+ // By default the SSE client uses keep-alive.
+ // Allow for a retry to connect before giving up.
+ _errorTimer = Timer(const Duration(seconds: 5), () {
+ _closeWithError(error);
+ });
+ }
+ });
+ }
+
+ @Deprecated('Use onConnected instead.')
+ Stream<Event> get onOpen => _eventSource.onOpen;
+
+ Future<void> get onConnected => _onConnected.future;
+
+ /// Add messages to this [StreamSink] to send them to the server.
+ ///
+ /// The message added to the sink has to be JSON encodable. Messages that fail
+ /// to encode will be logged through a [Logger].
+ @override
+ StreamSink<String> get sink => _outgoingController.sink;
+
+ /// [Stream] of messages sent from the server to this client.
+ ///
+ /// A message is a decoded JSON object.
+ @override
+ Stream<String> get stream => _incomingController.stream;
+
+ void close() {
+ _eventSource.close();
+ // If the initial connection was never established. Add a listener so close
+ // adds a done event to [sink].
+ if (!_onConnected.isCompleted) _outgoingController.stream.drain<void>();
+ _incomingController.close();
+ _outgoingController.close();
+ }
+
+ void _closeWithError(Object error) {
+ _incomingController.addError(error);
+ close();
+ if (!_onConnected.isCompleted) {
+ // This call must happen after the call to close() which checks
+ // whether the completer was completed earlier.
+ _onConnected.completeError(error);
+ }
+ }
+
+ void _onIncomingControlMessage(Event message) {
+ var data = (message as MessageEvent).data;
+ if (data.dartify() == 'close') {
+ close();
+ } else {
+ throw UnsupportedError('[$_clientId] Illegal Control Message "$data"');
+ }
+ }
+
+ void _onIncomingMessage(Event message) {
+ var decoded =
+ jsonDecode(((message as MessageEvent).data as JSString).toDart);
+ _incomingController.add(decoded as String);
+ }
+
+ void _onOutgoingDone() {
+ close();
+ }
+
+ void _onOutgoingMessage(String? message) async {
+ String? encodedMessage;
+ await _requestPool.withResource(() async {
+ try {
+ encodedMessage = jsonEncode(message);
+ // ignore: avoid_catching_errors
+ } on JsonUnsupportedObjectError catch (e) {
+ _logger.warning('[$_clientId] Unable to encode outgoing message: $e');
+ // ignore: avoid_catching_errors
+ } on ArgumentError catch (e) {
+ _logger.warning('[$_clientId] Invalid argument: $e');
+ }
+ try {
+ final url = '$_serverUrl&messageId=${++_lastMessageId}';
+ await _fetch(
+ url,
+ RequestInit(
+ method: 'POST',
+ body: encodedMessage?.toJS,
+ credentials: 'include'));
+ } catch (error) {
+ final augmentedError =
+ '[$_clientId] SSE client failed to send $message:\n $error';
+ _logger.severe(augmentedError);
+ _closeWithError(augmentedError);
+ }
+ });
+ }
+}
+
+Future<void> _fetch(String resourceUrl, RequestInit options) =>
+ window.fetch(resourceUrl.toJS, options).toDart;
diff --git a/pkgs/sse/lib/server/sse_handler.dart b/pkgs/sse/lib/server/sse_handler.dart
new file mode 100644
index 0000000..bfed935
--- /dev/null
+++ b/pkgs/sse/lib/server/sse_handler.dart
@@ -0,0 +1,5 @@
+// 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.
+
+export 'package:sse/src/server/sse_handler.dart' show SseConnection, SseHandler;
diff --git a/pkgs/sse/lib/src/server/sse_handler.dart b/pkgs/sse/lib/src/server/sse_handler.dart
new file mode 100644
index 0000000..376fe27
--- /dev/null
+++ b/pkgs/sse/lib/src/server/sse_handler.dart
@@ -0,0 +1,299 @@
+// 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:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:async/async.dart';
+import 'package:collection/collection.dart';
+import 'package:logging/logging.dart';
+import 'package:shelf/shelf.dart' as shelf;
+import 'package:stream_channel/stream_channel.dart';
+
+// RFC 2616 requires carriage return delimiters.
+String _sseHeaders(String? origin) => 'HTTP/1.1 200 OK\r\n'
+ 'Content-Type: text/event-stream\r\n'
+ 'Cache-Control: no-cache\r\n'
+ 'Connection: keep-alive\r\n'
+ 'Access-Control-Allow-Credentials: true\r\n'
+ "${origin != null ? 'Access-Control-Allow-Origin: $origin\r\n' : ''}"
+ '\r\n\r\n';
+
+class _SseMessage {
+ final int id;
+ final String message;
+ _SseMessage(this.id, this.message);
+}
+
+/// A bi-directional SSE connection between server and browser.
+class SseConnection extends StreamChannelMixin<String> {
+ /// Incoming messages from the Browser client.
+ final _incomingController = StreamController<String>();
+
+ /// Outgoing messages to the Browser client.
+ final _outgoingController = StreamController<String>();
+
+ Sink _sink;
+
+ /// How long to wait after a connection drops before considering it closed.
+ final Duration? _keepAlive;
+
+ /// A timer counting down the KeepAlive period (null if hasn't disconnected).
+ Timer? _keepAliveTimer;
+
+ /// Whether this connection is currently in the KeepAlive timeout period.
+ bool get isInKeepAlivePeriod => _keepAliveTimer?.isActive ?? false;
+
+ /// The id of the last processed incoming message.
+ int _lastProcessedId = -1;
+
+ /// Incoming messages that have yet to be processed.
+ final _pendingMessages =
+ HeapPriorityQueue<_SseMessage>((a, b) => a.id.compareTo(b.id));
+
+ final _closedCompleter = Completer<void>();
+
+ /// Wraps the `_outgoingController.stream` to buffer events to enable keep
+ /// alive.
+ late StreamQueue _outgoingStreamQueue;
+
+ /// Creates an [SseConnection] for the supplied [_sink].
+ ///
+ /// If [keepAlive] is supplied, the connection will remain active for this
+ /// period after a disconnect and can be reconnected transparently. If there
+ /// is no reconnect within that period, the connection will be closed
+ /// normally.
+ ///
+ /// If [keepAlive] is not supplied, the connection will be closed immediately
+ /// after a disconnect.
+ SseConnection(this._sink, {Duration? keepAlive}) : _keepAlive = keepAlive {
+ _outgoingStreamQueue = StreamQueue(_outgoingController.stream);
+ unawaited(_setUpListener());
+ _outgoingController.onCancel = _close;
+ _incomingController.onCancel = _close;
+ }
+
+ Future<void> _setUpListener() async {
+ while (
+ !_outgoingController.isClosed && await _outgoingStreamQueue.hasNext) {
+ // If we're in a KeepAlive timeout, there's nowhere to send messages so
+ // wait a short period and check again.
+ if (isInKeepAlivePeriod) {
+ await Future<void>.delayed(const Duration(milliseconds: 200));
+ continue;
+ }
+
+ // Peek the data so we don't remove it from the stream if we're unable to
+ // send it.
+ final data = await _outgoingStreamQueue.peek;
+
+ // Ignore outgoing messages since the connection may have closed while
+ // waiting for the keep alive.
+ if (_closedCompleter.isCompleted) break;
+
+ try {
+ // JSON encode the message to escape new lines.
+ _sink.add('data: ${json.encode(data)}\n');
+ _sink.add('\n');
+ await _outgoingStreamQueue.next; // Consume from stream if no errors.
+ } catch (e) {
+ if ((e is StateError || e is SocketException) &&
+ (_keepAlive != null && !_closedCompleter.isCompleted)) {
+ // If we got here then the sink may have closed but the stream.onDone
+ // hasn't fired yet, so pause the subscription and skip calling
+ // `next` so the message remains in the queue to try again.
+ _handleDisconnect();
+ } else {
+ rethrow;
+ }
+ }
+ }
+ }
+
+ /// The message added to the sink has to be JSON encodable.
+ @override
+ StreamSink<String> get sink => _outgoingController.sink;
+
+ // Add messages to this [StreamSink] to send them to the server.
+ /// [Stream] of messages sent from the server to this client.
+ ///
+ /// A message is a decoded JSON object.
+ @override
+ Stream<String> get stream => _incomingController.stream;
+
+ /// Adds an incoming [message] to the [stream].
+ ///
+ /// This will buffer messages to guarantee order.
+ void _addIncomingMessage(int id, String message) {
+ _pendingMessages.add(_SseMessage(id, message));
+ while (_pendingMessages.isNotEmpty) {
+ var pendingMessage = _pendingMessages.first;
+ // Only process the next incremental message.
+ if (pendingMessage.id - _lastProcessedId <= 1) {
+ _incomingController.sink.add(pendingMessage.message);
+ _lastProcessedId = pendingMessage.id;
+ _pendingMessages.removeFirst();
+ } else {
+ // A message came out of order. Wait until we receive the previous
+ // messages to process.
+ break;
+ }
+ }
+ }
+
+ void _acceptReconnection(Sink sink) {
+ _keepAliveTimer?.cancel();
+ _sink = sink;
+ }
+
+ void _handleDisconnect() {
+ if (_keepAlive == null) {
+ // Close immediately if we're not keeping alive.
+ _close();
+ } else if (!isInKeepAlivePeriod && !_closedCompleter.isCompleted) {
+ // Otherwise if we didn't already have an active timer and we've not
+ // already been completely closed, set a timer to close after the timeout
+ // period.
+ // If the connection comes back, this will be cancelled and all messages
+ // left in the queue tried again.
+ _keepAliveTimer = Timer(_keepAlive, _close);
+ }
+ }
+
+ void _close() {
+ if (!_closedCompleter.isCompleted) {
+ _closedCompleter.complete();
+ // Cancel any existing timer in case we were told to explicitly shut down
+ // to avoid keeping the process alive.
+ _keepAliveTimer?.cancel();
+ _sink.close();
+ if (!_outgoingController.isClosed) {
+ _outgoingStreamQueue.cancel(immediate: true);
+ _outgoingController.close();
+ }
+ if (!_incomingController.isClosed) _incomingController.close();
+ }
+ }
+
+ /// Immediately close the connection, ignoring any keepAlive period.
+ void shutdown() {
+ _close();
+ }
+}
+
+/// [SseHandler] handles requests on a user defined path to create
+/// two-way communications of JSON encodable data between server and clients.
+///
+/// A server sends messages to a client through an SSE channel, while
+/// a client sends message to a server through HTTP POST requests.
+class SseHandler {
+ final _logger = Logger('SseHandler');
+ final Uri _uri;
+ final Duration? _keepAlive;
+ final _connections = <String?, SseConnection>{};
+ final _connectionController = StreamController<SseConnection>();
+
+ StreamQueue<SseConnection>? _connectionsStream;
+
+ /// [_uri] is the URL under which the server is listening for
+ /// incoming bi-directional SSE connections.
+ ///
+ /// If [keepAlive] is supplied, connections will remain active for this
+ /// period after a disconnect and can be reconnected transparently. If there
+ /// is no reconnect within that period, the connection will be closed
+ /// normally.
+ ///
+ /// If [keepAlive] is not supplied, connections will be closed immediately
+ /// after a disconnect.
+ SseHandler(this._uri, {Duration? keepAlive}) : _keepAlive = keepAlive;
+
+ StreamQueue<SseConnection> get connections =>
+ _connectionsStream ??= StreamQueue(_connectionController.stream);
+
+ shelf.Handler get handler => _handle;
+
+ int get numberOfClients => _connections.length;
+
+ shelf.Response _createSseConnection(shelf.Request req, String path) {
+ req.hijack((channel) async {
+ var sink = utf8.encoder.startChunkedConversion(channel.sink);
+ sink.add(_sseHeaders(req.headers['origin']));
+ var clientId = req.url.queryParameters['sseClientId'];
+
+ // Check if we already have a connection for this ID that is in the
+ // process of timing out
+ // (in which case we can reconnect it transparently).
+ if (_connections[clientId] != null &&
+ _connections[clientId]!.isInKeepAlivePeriod) {
+ _connections[clientId]!._acceptReconnection(sink);
+ } else {
+ var connection = SseConnection(sink, keepAlive: _keepAlive);
+ _connections[clientId] = connection;
+ unawaited(connection._closedCompleter.future.then((_) {
+ _connections.remove(clientId);
+ }));
+ _connectionController.add(connection);
+ }
+ // Remove connection when it is remotely closed or the stream is
+ // cancelled.
+ channel.stream.listen((_) {
+ // SSE is unidirectional. Responses are handled through POST requests.
+ }, onDone: () {
+ _connections[clientId]?._handleDisconnect();
+ });
+ });
+ }
+
+ String _getOriginalPath(shelf.Request req) => req.requestedUri.path;
+
+ Future<shelf.Response> _handle(shelf.Request req) async {
+ var path = _getOriginalPath(req);
+ if (_uri.path != path) {
+ return shelf.Response.notFound('');
+ }
+
+ if (req.headers['accept'] == 'text/event-stream' && req.method == 'GET') {
+ return _createSseConnection(req, path);
+ }
+
+ if (req.headers['accept'] != 'text/event-stream' && req.method == 'POST') {
+ return _handleIncomingMessage(req, path);
+ }
+
+ return shelf.Response.notFound('');
+ }
+
+ Future<shelf.Response> _handleIncomingMessage(
+ shelf.Request req, String path) async {
+ String? clientId;
+ try {
+ clientId = req.url.queryParameters['sseClientId'];
+ var messageId = int.parse(req.url.queryParameters['messageId'] ?? '0');
+ var message = await req.readAsString();
+ var jsonObject = json.decode(message) as String;
+ _connections[clientId]?._addIncomingMessage(messageId, jsonObject);
+ } catch (e, st) {
+ _logger.fine('[$clientId] Failed to handle incoming message. $e $st');
+ }
+ return shelf.Response.ok('', headers: {
+ 'access-control-allow-credentials': 'true',
+ 'access-control-allow-origin': _originFor(req),
+ });
+ }
+
+ String _originFor(shelf.Request req) =>
+ // Firefox does not set header "origin".
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1508661
+ req.headers['origin'] ?? req.headers['host']!;
+
+ /// Immediately close all connections, ignoring any keepAlive periods.
+ void shutdown() {
+ for (final connection in _connections.values) {
+ connection.shutdown();
+ }
+ }
+}
+
+void closeSink(SseConnection connection) => connection._sink.close();
diff --git a/pkgs/sse/lib/src/util/uuid.dart b/pkgs/sse/lib/src/util/uuid.dart
new file mode 100644
index 0000000..a1aa398
--- /dev/null
+++ b/pkgs/sse/lib/src/util/uuid.dart
@@ -0,0 +1,32 @@
+// Copyright (c) 2020, 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' show Random;
+
+/// Returns a unique ID in the format:
+///
+/// f47ac10b-58cc-4372-a567-0e02b2c3d479
+///
+/// The generated uuids are 128 bit numbers encoded in a specific string format.
+/// For more information, see
+/// [en.wikipedia.org/wiki/Universally_unique_identifier](http://en.wikipedia.org/wiki/Universally_unique_identifier).
+String generateUuidV4() {
+ final random = Random();
+
+ int generateBits(int bitCount) => random.nextInt(1 << bitCount);
+
+ String printDigits(int value, int count) =>
+ value.toRadixString(16).padLeft(count, '0');
+ String bitsDigits(int bitCount, int digitCount) =>
+ printDigits(generateBits(bitCount), digitCount);
+
+ // Generate xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx / 8-4-4-4-12.
+ var special = 8 + random.nextInt(4);
+
+ return '${bitsDigits(16, 4)}${bitsDigits(16, 4)}-'
+ '${bitsDigits(16, 4)}-'
+ '4${bitsDigits(12, 3)}-'
+ '${printDigits(special, 1)}${bitsDigits(12, 3)}-'
+ '${bitsDigits(16, 4)}${bitsDigits(16, 4)}${bitsDigits(16, 4)}';
+}
diff --git a/pkgs/sse/pubspec.yaml b/pkgs/sse/pubspec.yaml
new file mode 100644
index 0000000..bd70f74
--- /dev/null
+++ b/pkgs/sse/pubspec.yaml
@@ -0,0 +1,25 @@
+name: sse
+version: 4.1.7
+description: >-
+ Provides client and server functionality for setting up bi-directional
+ communication through Server Sent Events (SSE) and corresponding POST
+ requests.
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/sse
+
+environment:
+ sdk: ^3.3.0
+
+dependencies:
+ async: ^2.0.8
+ collection: ^1.0.0
+ logging: ^1.0.0
+ pool: ^1.5.0
+ shelf: ^1.1.0
+ stream_channel: ^2.0.0
+ web: '>=0.5.0 <2.0.0'
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ shelf_static: ^1.0.0
+ test: ^1.16.6
+ webdriver: ^3.0.0
diff --git a/pkgs/sse/test/sse_test.dart b/pkgs/sse/test/sse_test.dart
new file mode 100644
index 0000000..0455baa
--- /dev/null
+++ b/pkgs/sse/test/sse_test.dart
@@ -0,0 +1,270 @@
+// 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.
+
+@TestOn('vm')
+library;
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:async/async.dart';
+import 'package:shelf/shelf.dart' as shelf;
+import 'package:shelf/shelf_io.dart' as io;
+import 'package:shelf_static/shelf_static.dart';
+import 'package:sse/server/sse_handler.dart';
+import 'package:sse/src/server/sse_handler.dart' show closeSink;
+import 'package:test/test.dart';
+import 'package:webdriver/async_io.dart';
+
+void main() {
+ late HttpServer server;
+ late WebDriver webdriver;
+ late SseHandler handler;
+ late Process chromeDriver;
+
+ setUpAll(() async {
+ try {
+ chromeDriver = await Process.start(
+ 'chromedriver', ['--port=4444', '--url-base=wd/hub']);
+ } catch (e) {
+ throw StateError(
+ 'Could not start ChromeDriver. Is it installed?\nError: $e');
+ }
+ });
+
+ tearDownAll(() {
+ chromeDriver.kill();
+ });
+
+ group('SSE', () {
+ setUp(() async {
+ handler = SseHandler(Uri.parse('/test'));
+
+ var cascade = shelf.Cascade()
+ .add(handler.handler)
+ .add(_faviconHandler)
+ .add(createStaticHandler('test/web',
+ listDirectories: true, defaultDocument: 'index.html'));
+
+ server = await io.serve(cascade.handler, 'localhost', 0);
+ var capabilities = Capabilities.chrome
+ ..addAll({
+ Capabilities.chromeOptions: {
+ 'args': ['--headless']
+ }
+ });
+ webdriver = await createDriver(desired: capabilities);
+ });
+
+ tearDown(() async {
+ await webdriver.quit();
+ await server.close();
+ });
+
+ test('can round trip messages', () async {
+ await webdriver.get('http://localhost:${server.port}');
+ var connection = await handler.connections.next;
+ connection.sink.add('blah');
+ expect(await connection.stream.first, 'blah');
+ });
+
+ test('can send a significant number of requests', () async {
+ await webdriver.get('http://localhost:${server.port}');
+ var connection = await handler.connections.next;
+ var limit = 7000;
+ for (var i = 0; i < limit; i++) {
+ connection.sink.add('$i');
+ }
+ await connection.stream.take(limit).drain<void>();
+ });
+
+ test('messages arrive in-order', () async {
+ expect(handler.numberOfClients, 0);
+ await webdriver.get('http://localhost:${server.port}');
+ var connection = await handler.connections.next;
+ expect(handler.numberOfClients, 1);
+
+ var expected = <String>[];
+ var count = 100;
+ for (var i = 0; i < count; i++) {
+ expected.add(i.toString());
+ }
+ connection.sink.add('send $count');
+
+ expect(await connection.stream.take(count).toList(), equals(expected));
+ });
+
+ test('multiple clients can connect', () async {
+ var connections = handler.connections;
+ await webdriver.get('http://localhost:${server.port}');
+ await connections.next;
+ await webdriver.get('http://localhost:${server.port}');
+ await connections.next;
+ });
+
+ test('routes data correctly', () async {
+ var connections = handler.connections;
+ await webdriver.get('http://localhost:${server.port}');
+ var connectionA = await connections.next;
+ connectionA.sink.add('foo');
+ expect(await connectionA.stream.first, 'foo');
+
+ await webdriver.get('http://localhost:${server.port}');
+ var connectionB = await connections.next;
+ connectionB.sink.add('bar');
+ expect(await connectionB.stream.first, 'bar');
+ });
+
+ test('can close from the server', () async {
+ expect(handler.numberOfClients, 0);
+ await webdriver.get('http://localhost:${server.port}');
+ var connection = await handler.connections.next;
+ expect(handler.numberOfClients, 1);
+ await connection.sink.close();
+ await pumpEventQueue();
+ expect(handler.numberOfClients, 0);
+ });
+
+ test('client reconnects after being disconnected', () async {
+ expect(handler.numberOfClients, 0);
+ await webdriver.get('http://localhost:${server.port}');
+ var connection = await handler.connections.next;
+ expect(handler.numberOfClients, 1);
+ await connection.sink.close();
+ await pumpEventQueue();
+ expect(handler.numberOfClients, 0);
+
+ // Ensure the client reconnects
+ await handler.connections.next;
+ });
+
+ test('can close from the client-side', () async {
+ expect(handler.numberOfClients, 0);
+ await webdriver.get('http://localhost:${server.port}');
+ var connection = await handler.connections.next;
+ expect(handler.numberOfClients, 1);
+
+ var closeButton = await webdriver.findElement(const By.tagName('button'));
+ await closeButton.click();
+
+ // Should complete since the connection is closed.
+ await connection.stream.drain<void>();
+ expect(handler.numberOfClients, 0);
+ });
+
+ test('cancelling the listener closes the connection', () async {
+ expect(handler.numberOfClients, 0);
+ await webdriver.get('http://localhost:${server.port}');
+ var connection = await handler.connections.next;
+ expect(handler.numberOfClients, 1);
+
+ var sub = connection.stream.listen((_) {});
+ await sub.cancel();
+ await pumpEventQueue();
+ expect(handler.numberOfClients, 0);
+ });
+
+ test('disconnects when navigating away', () async {
+ await webdriver.get('http://localhost:${server.port}');
+ expect(handler.numberOfClients, 1);
+
+ await webdriver.get('chrome://version/');
+ expect(handler.numberOfClients, 0);
+ });
+ });
+
+ group('SSE with server keep-alive', () {
+ setUp(() async {
+ handler =
+ SseHandler(Uri.parse('/test'), keepAlive: const Duration(seconds: 5));
+
+ var cascade = shelf.Cascade()
+ .add(handler.handler)
+ .add(_faviconHandler)
+ .add(createStaticHandler('test/web',
+ listDirectories: true, defaultDocument: 'index.html'));
+
+ server = await io.serve(cascade.handler, 'localhost', 0);
+ var capabilities = Capabilities.chrome
+ ..addAll({
+ Capabilities.chromeOptions: {
+ 'args': ['--headless']
+ }
+ });
+ webdriver = await createDriver(desired: capabilities);
+ });
+
+ tearDown(() async {
+ await webdriver.quit();
+ await server.close();
+ });
+
+ test('client reconnect use the same connection', () async {
+ expect(handler.numberOfClients, 0);
+ await webdriver.get('http://localhost:${server.port}');
+ var connection = await handler.connections.next;
+ expect(handler.numberOfClients, 1);
+
+ // Close the underlying connection.
+ closeSink(connection);
+ // Ensure we can still round-trip data on the original connection and that
+ // the connection is no longer marked keep-alive once it's reconnected.
+ connection.sink.add('bar');
+ var queue = StreamQueue(connection.stream);
+ expect(await queue.next, 'bar');
+
+ // Now check that we can reconnect multiple times.
+ closeSink(connection);
+ connection.sink.add('bar');
+ expect(await queue.next, 'bar');
+ expect(handler.numberOfClients, 1);
+ });
+
+ test('messages sent during disconnect arrive in-order', () async {
+ expect(handler.numberOfClients, 0);
+ await webdriver.get('http://localhost:${server.port}');
+ var connection = await handler.connections.next;
+ expect(handler.numberOfClients, 1);
+
+ // Close the underlying connection.
+ closeSink(connection);
+ connection.sink.add('one');
+ connection.sink.add('two');
+ await pumpEventQueue();
+
+ // Ensure there's still a connection.
+ expect(handler.numberOfClients, 1);
+
+ // Ensure messages arrive in the same order
+ expect(await connection.stream.take(2).toList(), equals(['one', 'two']));
+ });
+
+ test('explicit shutdown does not wait for keepAlive', () async {
+ expect(handler.numberOfClients, 0);
+ await webdriver.get('http://localhost:${server.port}');
+ await handler.connections.next;
+ expect(handler.numberOfClients, 1);
+
+ // Close the underlying connection.
+ handler.shutdown();
+
+ // Wait for a short period to allow the connection to close, but not
+ // long enough that the 30second keep-alive may have expired.
+ var maxPumps = 50;
+ while (handler.numberOfClients > 0 && maxPumps-- > 0) {
+ await pumpEventQueue(times: 1);
+ }
+
+ // Ensure there are not connected clients.
+ expect(handler.numberOfClients, 0);
+ });
+ }, timeout: const Timeout(Duration(seconds: 120)));
+}
+
+FutureOr<shelf.Response> _faviconHandler(shelf.Request request) {
+ if (request.url.path.endsWith('favicon.ico')) {
+ return shelf.Response.ok('');
+ }
+ return shelf.Response.notFound('');
+}
diff --git a/pkgs/sse/test/web/index.dart b/pkgs/sse/test/web/index.dart
new file mode 100644
index 0000000..c4d78cd
--- /dev/null
+++ b/pkgs/sse/test/web/index.dart
@@ -0,0 +1,25 @@
+// 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 'package:sse/client/sse_client.dart';
+import 'package:web/web.dart';
+
+void main() {
+ var channel = SseClient('/test');
+
+ document.querySelector('button')!.onClick.listen((_) {
+ channel.sink.close();
+ });
+
+ channel.stream.listen((s) {
+ if (s.startsWith('send ')) {
+ var count = int.parse(s.split(' ').last);
+ for (var i = 0; i < count; i++) {
+ channel.sink.add('$i');
+ }
+ } else {
+ channel.sink.add(s);
+ }
+ });
+}
diff --git a/pkgs/sse/test/web/index.dart.js b/pkgs/sse/test/web/index.dart.js
new file mode 100644
index 0000000..e1b37b9
--- /dev/null
+++ b/pkgs/sse/test/web/index.dart.js
@@ -0,0 +1,8851 @@
+// Generated by dart2js (NullSafetyMode.sound, csp, intern-composite-values), the Dart to JavaScript compiler version: 3.4.0-157.0.dev.
+// The code supports the following hooks:
+// dartPrint(message):
+// if this function is defined it is called instead of the Dart [print]
+// method.
+//
+// dartMainRunner(main, args):
+// if this function is defined, the Dart [main] method will not be invoked
+// directly. Instead, a closure that will invoke [main], and its arguments
+// [args] is passed to [dartMainRunner].
+//
+// dartDeferredLibraryLoader(uri, successCallback, errorCallback, loadId, loadPriority):
+// if this function is defined, it will be called when a deferred library
+// is loaded. It should load and eval the javascript of `uri`, and call
+// successCallback. If it fails to do so, it should call errorCallback with
+// an error. The loadId argument is the deferred import that resulted in
+// this uri being loaded. The loadPriority argument is the priority the
+// library should be loaded with as specified in the code via the
+// load-priority annotation (0: normal, 1: high).
+// dartDeferredLibraryMultiLoader(uris, successCallback, errorCallback, loadId, loadPriority):
+// if this function is defined, it will be called when a deferred library
+// is loaded. It should load and eval the javascript of every URI in `uris`,
+// and call successCallback. If it fails to do so, it should call
+// errorCallback with an error. The loadId argument is the deferred import
+// that resulted in this uri being loaded. The loadPriority argument is the
+// priority the library should be loaded with as specified in the code via
+// the load-priority annotation (0: normal, 1: high).
+//
+// dartCallInstrumentation(id, qualifiedName):
+// if this function is defined, it will be called at each entry of a
+// method or constructor. Used only when compiling programs with
+// --experiment-call-instrumentation.
+(function dartProgram() {
+ function copyProperties(from, to) {
+ var keys = Object.keys(from);
+ for (var i = 0; i < keys.length; i++) {
+ var key = keys[i];
+ to[key] = from[key];
+ }
+ }
+ function mixinPropertiesHard(from, to) {
+ var keys = Object.keys(from);
+ for (var i = 0; i < keys.length; i++) {
+ var key = keys[i];
+ if (!to.hasOwnProperty(key)) {
+ to[key] = from[key];
+ }
+ }
+ }
+ function mixinPropertiesEasy(from, to) {
+ Object.assign(to, from);
+ }
+ var supportsDirectProtoAccess = function() {
+ var cls = function() {
+ };
+ cls.prototype = {p: {}};
+ var object = new cls();
+ if (!(Object.getPrototypeOf(object) && Object.getPrototypeOf(object).p === cls.prototype.p))
+ return false;
+ try {
+ if (typeof navigator != "undefined" && typeof navigator.userAgent == "string" && navigator.userAgent.indexOf("Chrome/") >= 0)
+ return true;
+ if (typeof version == "function" && version.length == 0) {
+ var v = version();
+ if (/^\d+\.\d+\.\d+\.\d+$/.test(v))
+ return true;
+ }
+ } catch (_) {
+ }
+ return false;
+ }();
+ function inherit(cls, sup) {
+ cls.prototype.constructor = cls;
+ cls.prototype["$is" + cls.name] = cls;
+ if (sup != null) {
+ if (supportsDirectProtoAccess) {
+ Object.setPrototypeOf(cls.prototype, sup.prototype);
+ return;
+ }
+ var clsPrototype = Object.create(sup.prototype);
+ copyProperties(cls.prototype, clsPrototype);
+ cls.prototype = clsPrototype;
+ }
+ }
+ function inheritMany(sup, classes) {
+ for (var i = 0; i < classes.length; i++) {
+ inherit(classes[i], sup);
+ }
+ }
+ function mixinEasy(cls, mixin) {
+ mixinPropertiesEasy(mixin.prototype, cls.prototype);
+ cls.prototype.constructor = cls;
+ }
+ function mixinHard(cls, mixin) {
+ mixinPropertiesHard(mixin.prototype, cls.prototype);
+ cls.prototype.constructor = cls;
+ }
+ function lazy(holder, name, getterName, initializer) {
+ var uninitializedSentinel = holder;
+ holder[name] = uninitializedSentinel;
+ holder[getterName] = function() {
+ if (holder[name] === uninitializedSentinel) {
+ holder[name] = initializer();
+ }
+ holder[getterName] = function() {
+ return this[name];
+ };
+ return holder[name];
+ };
+ }
+ function lazyFinal(holder, name, getterName, initializer) {
+ var uninitializedSentinel = holder;
+ holder[name] = uninitializedSentinel;
+ holder[getterName] = function() {
+ if (holder[name] === uninitializedSentinel) {
+ var value = initializer();
+ if (holder[name] !== uninitializedSentinel) {
+ A.throwLateFieldADI(name);
+ }
+ holder[name] = value;
+ }
+ var finalValue = holder[name];
+ holder[getterName] = function() {
+ return finalValue;
+ };
+ return finalValue;
+ };
+ }
+ function makeConstList(list) {
+ list.immutable$list = Array;
+ list.fixed$length = Array;
+ return list;
+ }
+ function convertToFastObject(properties) {
+ function t() {
+ }
+ t.prototype = properties;
+ new t();
+ return properties;
+ }
+ function convertAllToFastObject(arrayOfObjects) {
+ for (var i = 0; i < arrayOfObjects.length; ++i) {
+ convertToFastObject(arrayOfObjects[i]);
+ }
+ }
+ var functionCounter = 0;
+ function instanceTearOffGetter(isIntercepted, parameters) {
+ var cache = null;
+ return isIntercepted ? function(receiver) {
+ if (cache === null)
+ cache = A.closureFromTearOff(parameters);
+ return new cache(receiver, this);
+ } : function() {
+ if (cache === null)
+ cache = A.closureFromTearOff(parameters);
+ return new cache(this, null);
+ };
+ }
+ function staticTearOffGetter(parameters) {
+ var cache = null;
+ return function() {
+ if (cache === null)
+ cache = A.closureFromTearOff(parameters).prototype;
+ return cache;
+ };
+ }
+ var typesOffset = 0;
+ function tearOffParameters(container, isStatic, isIntercepted, requiredParameterCount, optionalParameterDefaultValues, callNames, funsOrNames, funType, applyIndex, needsDirectAccess) {
+ if (typeof funType == "number") {
+ funType += typesOffset;
+ }
+ return {co: container, iS: isStatic, iI: isIntercepted, rC: requiredParameterCount, dV: optionalParameterDefaultValues, cs: callNames, fs: funsOrNames, fT: funType, aI: applyIndex || 0, nDA: needsDirectAccess};
+ }
+ function installStaticTearOff(holder, getterName, requiredParameterCount, optionalParameterDefaultValues, callNames, funsOrNames, funType, applyIndex) {
+ var parameters = tearOffParameters(holder, true, false, requiredParameterCount, optionalParameterDefaultValues, callNames, funsOrNames, funType, applyIndex, false);
+ var getterFunction = staticTearOffGetter(parameters);
+ holder[getterName] = getterFunction;
+ }
+ function installInstanceTearOff(prototype, getterName, isIntercepted, requiredParameterCount, optionalParameterDefaultValues, callNames, funsOrNames, funType, applyIndex, needsDirectAccess) {
+ isIntercepted = !!isIntercepted;
+ var parameters = tearOffParameters(prototype, false, isIntercepted, requiredParameterCount, optionalParameterDefaultValues, callNames, funsOrNames, funType, applyIndex, !!needsDirectAccess);
+ var getterFunction = instanceTearOffGetter(isIntercepted, parameters);
+ prototype[getterName] = getterFunction;
+ }
+ function setOrUpdateInterceptorsByTag(newTags) {
+ var tags = init.interceptorsByTag;
+ if (!tags) {
+ init.interceptorsByTag = newTags;
+ return;
+ }
+ copyProperties(newTags, tags);
+ }
+ function setOrUpdateLeafTags(newTags) {
+ var tags = init.leafTags;
+ if (!tags) {
+ init.leafTags = newTags;
+ return;
+ }
+ copyProperties(newTags, tags);
+ }
+ function updateTypes(newTypes) {
+ var types = init.types;
+ var length = types.length;
+ types.push.apply(types, newTypes);
+ return length;
+ }
+ function updateHolder(holder, newHolder) {
+ copyProperties(newHolder, holder);
+ return holder;
+ }
+ var hunkHelpers = function() {
+ var mkInstance = function(isIntercepted, requiredParameterCount, optionalParameterDefaultValues, callNames, applyIndex) {
+ return function(container, getterName, name, funType) {
+ return installInstanceTearOff(container, getterName, isIntercepted, requiredParameterCount, optionalParameterDefaultValues, callNames, [name], funType, applyIndex, false);
+ };
+ },
+ mkStatic = function(requiredParameterCount, optionalParameterDefaultValues, callNames, applyIndex) {
+ return function(container, getterName, name, funType) {
+ return installStaticTearOff(container, getterName, requiredParameterCount, optionalParameterDefaultValues, callNames, [name], funType, applyIndex);
+ };
+ };
+ return {inherit: inherit, inheritMany: inheritMany, mixin: mixinEasy, mixinHard: mixinHard, installStaticTearOff: installStaticTearOff, installInstanceTearOff: installInstanceTearOff, _instance_0u: mkInstance(0, 0, null, ["call$0"], 0), _instance_1u: mkInstance(0, 1, null, ["call$1"], 0), _instance_2u: mkInstance(0, 2, null, ["call$2"], 0), _instance_0i: mkInstance(1, 0, null, ["call$0"], 0), _instance_1i: mkInstance(1, 1, null, ["call$1"], 0), _instance_2i: mkInstance(1, 2, null, ["call$2"], 0), _static_0: mkStatic(0, null, ["call$0"], 0), _static_1: mkStatic(1, null, ["call$1"], 0), _static_2: mkStatic(2, null, ["call$2"], 0), makeConstList: makeConstList, lazy: lazy, lazyFinal: lazyFinal, updateHolder: updateHolder, convertToFastObject: convertToFastObject, updateTypes: updateTypes, setOrUpdateInterceptorsByTag: setOrUpdateInterceptorsByTag, setOrUpdateLeafTags: setOrUpdateLeafTags};
+ }();
+ function initializeDeferredHunk(hunk) {
+ typesOffset = init.types.length;
+ hunk(hunkHelpers, init, holders, $);
+ }
+ var J = {
+ makeDispatchRecord(interceptor, proto, extension, indexability) {
+ return {i: interceptor, p: proto, e: extension, x: indexability};
+ },
+ getNativeInterceptor(object) {
+ var proto, objectProto, $constructor, interceptor, t1,
+ record = object[init.dispatchPropertyName];
+ if (record == null)
+ if ($.initNativeDispatchFlag == null) {
+ A.initNativeDispatch();
+ record = object[init.dispatchPropertyName];
+ }
+ if (record != null) {
+ proto = record.p;
+ if (false === proto)
+ return record.i;
+ if (true === proto)
+ return object;
+ objectProto = Object.getPrototypeOf(object);
+ if (proto === objectProto)
+ return record.i;
+ if (record.e === objectProto)
+ throw A.wrapException(A.UnimplementedError$("Return interceptor for " + A.S(proto(object, record))));
+ }
+ $constructor = object.constructor;
+ if ($constructor == null)
+ interceptor = null;
+ else {
+ t1 = $._JS_INTEROP_INTERCEPTOR_TAG;
+ if (t1 == null)
+ t1 = $._JS_INTEROP_INTERCEPTOR_TAG = init.getIsolateTag("_$dart_js");
+ interceptor = $constructor[t1];
+ }
+ if (interceptor != null)
+ return interceptor;
+ interceptor = A.lookupAndCacheInterceptor(object);
+ if (interceptor != null)
+ return interceptor;
+ if (typeof object == "function")
+ return B.JavaScriptFunction_methods;
+ proto = Object.getPrototypeOf(object);
+ if (proto == null)
+ return B.PlainJavaScriptObject_methods;
+ if (proto === Object.prototype)
+ return B.PlainJavaScriptObject_methods;
+ if (typeof $constructor == "function") {
+ t1 = $._JS_INTEROP_INTERCEPTOR_TAG;
+ if (t1 == null)
+ t1 = $._JS_INTEROP_INTERCEPTOR_TAG = init.getIsolateTag("_$dart_js");
+ Object.defineProperty($constructor, t1, {value: B.UnknownJavaScriptObject_methods, enumerable: false, writable: true, configurable: true});
+ return B.UnknownJavaScriptObject_methods;
+ }
+ return B.UnknownJavaScriptObject_methods;
+ },
+ JSArray_JSArray$fixed($length, $E) {
+ if ($length < 0 || $length > 4294967295)
+ throw A.wrapException(A.RangeError$range($length, 0, 4294967295, "length", null));
+ return J.JSArray_JSArray$markFixed(new Array($length), $E);
+ },
+ JSArray_JSArray$growable($length, $E) {
+ if ($length < 0)
+ throw A.wrapException(A.ArgumentError$("Length must be a non-negative integer: " + $length, null));
+ return A._setArrayType(new Array($length), $E._eval$1("JSArray<0>"));
+ },
+ JSArray_JSArray$markFixed(allocation, $E) {
+ return J.JSArray_markFixedList(A._setArrayType(allocation, $E._eval$1("JSArray<0>")), $E);
+ },
+ JSArray_markFixedList(list, $T) {
+ list.fixed$length = Array;
+ return list;
+ },
+ JSArray_markUnmodifiableList(list) {
+ list.fixed$length = Array;
+ list.immutable$list = Array;
+ return list;
+ },
+ getInterceptor$(receiver) {
+ if (typeof receiver == "number") {
+ if (Math.floor(receiver) == receiver)
+ return J.JSInt.prototype;
+ return J.JSNumNotInt.prototype;
+ }
+ if (typeof receiver == "string")
+ return J.JSString.prototype;
+ if (receiver == null)
+ return J.JSNull.prototype;
+ if (typeof receiver == "boolean")
+ return J.JSBool.prototype;
+ if (Array.isArray(receiver))
+ return J.JSArray.prototype;
+ if (typeof receiver != "object") {
+ if (typeof receiver == "function")
+ return J.JavaScriptFunction.prototype;
+ if (typeof receiver == "symbol")
+ return J.JavaScriptSymbol.prototype;
+ if (typeof receiver == "bigint")
+ return J.JavaScriptBigInt.prototype;
+ return receiver;
+ }
+ if (receiver instanceof A.Object)
+ return receiver;
+ return J.getNativeInterceptor(receiver);
+ },
+ getInterceptor$asx(receiver) {
+ if (typeof receiver == "string")
+ return J.JSString.prototype;
+ if (receiver == null)
+ return receiver;
+ if (Array.isArray(receiver))
+ return J.JSArray.prototype;
+ if (typeof receiver != "object") {
+ if (typeof receiver == "function")
+ return J.JavaScriptFunction.prototype;
+ if (typeof receiver == "symbol")
+ return J.JavaScriptSymbol.prototype;
+ if (typeof receiver == "bigint")
+ return J.JavaScriptBigInt.prototype;
+ return receiver;
+ }
+ if (receiver instanceof A.Object)
+ return receiver;
+ return J.getNativeInterceptor(receiver);
+ },
+ getInterceptor$ax(receiver) {
+ if (receiver == null)
+ return receiver;
+ if (Array.isArray(receiver))
+ return J.JSArray.prototype;
+ if (typeof receiver != "object") {
+ if (typeof receiver == "function")
+ return J.JavaScriptFunction.prototype;
+ if (typeof receiver == "symbol")
+ return J.JavaScriptSymbol.prototype;
+ if (typeof receiver == "bigint")
+ return J.JavaScriptBigInt.prototype;
+ return receiver;
+ }
+ if (receiver instanceof A.Object)
+ return receiver;
+ return J.getNativeInterceptor(receiver);
+ },
+ getInterceptor$s(receiver) {
+ if (typeof receiver == "string")
+ return J.JSString.prototype;
+ if (receiver == null)
+ return receiver;
+ if (!(receiver instanceof A.Object))
+ return J.UnknownJavaScriptObject.prototype;
+ return receiver;
+ },
+ get$hashCode$(receiver) {
+ return J.getInterceptor$(receiver).get$hashCode(receiver);
+ },
+ get$iterator$ax(receiver) {
+ return J.getInterceptor$ax(receiver).get$iterator(receiver);
+ },
+ get$length$asx(receiver) {
+ return J.getInterceptor$asx(receiver).get$length(receiver);
+ },
+ get$runtimeType$(receiver) {
+ return J.getInterceptor$(receiver).get$runtimeType(receiver);
+ },
+ $eq$(receiver, a0) {
+ if (receiver == null)
+ return a0 == null;
+ if (typeof receiver != "object")
+ return a0 != null && receiver === a0;
+ return J.getInterceptor$(receiver).$eq(receiver, a0);
+ },
+ matchAsPrefix$2$s(receiver, a0, a1) {
+ return J.getInterceptor$s(receiver).matchAsPrefix$2(receiver, a0, a1);
+ },
+ noSuchMethod$1$(receiver, a0) {
+ return J.getInterceptor$(receiver).noSuchMethod$1(receiver, a0);
+ },
+ toString$0$(receiver) {
+ return J.getInterceptor$(receiver).toString$0(receiver);
+ },
+ Interceptor: function Interceptor() {
+ },
+ JSBool: function JSBool() {
+ },
+ JSNull: function JSNull() {
+ },
+ JavaScriptObject: function JavaScriptObject() {
+ },
+ LegacyJavaScriptObject: function LegacyJavaScriptObject() {
+ },
+ PlainJavaScriptObject: function PlainJavaScriptObject() {
+ },
+ UnknownJavaScriptObject: function UnknownJavaScriptObject() {
+ },
+ JavaScriptFunction: function JavaScriptFunction() {
+ },
+ JavaScriptBigInt: function JavaScriptBigInt() {
+ },
+ JavaScriptSymbol: function JavaScriptSymbol() {
+ },
+ JSArray: function JSArray(t0) {
+ this.$ti = t0;
+ },
+ JSUnmodifiableArray: function JSUnmodifiableArray(t0) {
+ this.$ti = t0;
+ },
+ ArrayIterator: function ArrayIterator(t0, t1, t2) {
+ var _ = this;
+ _._iterable = t0;
+ _._length = t1;
+ _._index = 0;
+ _._current = null;
+ _.$ti = t2;
+ },
+ JSNumber: function JSNumber() {
+ },
+ JSInt: function JSInt() {
+ },
+ JSNumNotInt: function JSNumNotInt() {
+ },
+ JSString: function JSString() {
+ }
+ },
+ A = {JS_CONST: function JS_CONST() {
+ },
+ checkNotNullable(value, $name, $T) {
+ return value;
+ },
+ isToStringVisiting(object) {
+ var t1, i;
+ for (t1 = $.toStringVisiting.length, i = 0; i < t1; ++i)
+ if (object === $.toStringVisiting[i])
+ return true;
+ return false;
+ },
+ IterableElementError_noElement() {
+ return new A.StateError("No element");
+ },
+ IterableElementError_tooFew() {
+ return new A.StateError("Too few elements");
+ },
+ LateError: function LateError(t0) {
+ this._message = t0;
+ },
+ nullFuture_closure: function nullFuture_closure() {
+ },
+ EfficientLengthIterable: function EfficientLengthIterable() {
+ },
+ ListIterable: function ListIterable() {
+ },
+ ListIterator: function ListIterator(t0, t1, t2) {
+ var _ = this;
+ _.__internal$_iterable = t0;
+ _.__internal$_length = t1;
+ _.__internal$_index = 0;
+ _.__internal$_current = null;
+ _.$ti = t2;
+ },
+ FixedLengthListMixin: function FixedLengthListMixin() {
+ },
+ Symbol: function Symbol(t0) {
+ this._name = t0;
+ },
+ unminifyOrTag(rawClassName) {
+ var preserved = init.mangledGlobalNames[rawClassName];
+ if (preserved != null)
+ return preserved;
+ return rawClassName;
+ },
+ isJsIndexable(object, record) {
+ var result;
+ if (record != null) {
+ result = record.x;
+ if (result != null)
+ return result;
+ }
+ return type$.JavaScriptIndexingBehavior_dynamic._is(object);
+ },
+ S(value) {
+ var result;
+ if (typeof value == "string")
+ return value;
+ if (typeof value == "number") {
+ if (value !== 0)
+ return "" + value;
+ } else if (true === value)
+ return "true";
+ else if (false === value)
+ return "false";
+ else if (value == null)
+ return "null";
+ result = J.toString$0$(value);
+ return result;
+ },
+ Primitives_objectHashCode(object) {
+ var hash,
+ property = $.Primitives__identityHashCodeProperty;
+ if (property == null)
+ property = $.Primitives__identityHashCodeProperty = Symbol("identityHashCode");
+ hash = object[property];
+ if (hash == null) {
+ hash = Math.random() * 0x3fffffff | 0;
+ object[property] = hash;
+ }
+ return hash;
+ },
+ Primitives_parseInt(source, radix) {
+ var decimalMatch, maxCharCode, digitsPart, t1, i, _null = null,
+ match = /^\s*[+-]?((0x[a-f0-9]+)|(\d+)|([a-z0-9]+))\s*$/i.exec(source);
+ if (match == null)
+ return _null;
+ if (3 >= match.length)
+ return A.ioore(match, 3);
+ decimalMatch = match[3];
+ if (radix == null) {
+ if (decimalMatch != null)
+ return parseInt(source, 10);
+ if (match[2] != null)
+ return parseInt(source, 16);
+ return _null;
+ }
+ if (radix < 2 || radix > 36)
+ throw A.wrapException(A.RangeError$range(radix, 2, 36, "radix", _null));
+ if (radix === 10 && decimalMatch != null)
+ return parseInt(source, 10);
+ if (radix < 10 || decimalMatch == null) {
+ maxCharCode = radix <= 10 ? 47 + radix : 86 + radix;
+ digitsPart = match[1];
+ for (t1 = digitsPart.length, i = 0; i < t1; ++i)
+ if ((digitsPart.charCodeAt(i) | 32) > maxCharCode)
+ return _null;
+ }
+ return parseInt(source, radix);
+ },
+ Primitives_objectTypeName(object) {
+ return A.Primitives__objectTypeNameNewRti(object);
+ },
+ Primitives__objectTypeNameNewRti(object) {
+ var interceptor, dispatchName, $constructor, constructorName;
+ if (object instanceof A.Object)
+ return A._rtiToString(A.instanceType(object), null);
+ interceptor = J.getInterceptor$(object);
+ if (interceptor === B.Interceptor_methods || interceptor === B.JavaScriptObject_methods || type$.UnknownJavaScriptObject._is(object)) {
+ dispatchName = B.C_JS_CONST(object);
+ if (dispatchName !== "Object" && dispatchName !== "")
+ return dispatchName;
+ $constructor = object.constructor;
+ if (typeof $constructor == "function") {
+ constructorName = $constructor.name;
+ if (typeof constructorName == "string" && constructorName !== "Object" && constructorName !== "")
+ return constructorName;
+ }
+ }
+ return A._rtiToString(A.instanceType(object), null);
+ },
+ Primitives_safeToString(object) {
+ if (typeof object == "number" || A._isBool(object))
+ return J.toString$0$(object);
+ if (typeof object == "string")
+ return JSON.stringify(object);
+ if (object instanceof A.Closure)
+ return object.toString$0(0);
+ return "Instance of '" + A.Primitives_objectTypeName(object) + "'";
+ },
+ Primitives_stringFromCharCode(charCode) {
+ var bits;
+ if (0 <= charCode) {
+ if (charCode <= 65535)
+ return String.fromCharCode(charCode);
+ if (charCode <= 1114111) {
+ bits = charCode - 65536;
+ return String.fromCharCode((B.JSInt_methods._shrOtherPositive$1(bits, 10) | 55296) >>> 0, bits & 1023 | 56320);
+ }
+ }
+ throw A.wrapException(A.RangeError$range(charCode, 0, 1114111, null, null));
+ },
+ Primitives_lazyAsJsDate(receiver) {
+ if (receiver.date === void 0)
+ receiver.date = new Date(receiver._value);
+ return receiver.date;
+ },
+ Primitives_getYear(receiver) {
+ return receiver.isUtc ? A.Primitives_lazyAsJsDate(receiver).getUTCFullYear() + 0 : A.Primitives_lazyAsJsDate(receiver).getFullYear() + 0;
+ },
+ Primitives_getMonth(receiver) {
+ return receiver.isUtc ? A.Primitives_lazyAsJsDate(receiver).getUTCMonth() + 1 : A.Primitives_lazyAsJsDate(receiver).getMonth() + 1;
+ },
+ Primitives_getDay(receiver) {
+ return receiver.isUtc ? A.Primitives_lazyAsJsDate(receiver).getUTCDate() + 0 : A.Primitives_lazyAsJsDate(receiver).getDate() + 0;
+ },
+ Primitives_getHours(receiver) {
+ return receiver.isUtc ? A.Primitives_lazyAsJsDate(receiver).getUTCHours() + 0 : A.Primitives_lazyAsJsDate(receiver).getHours() + 0;
+ },
+ Primitives_getMinutes(receiver) {
+ return receiver.isUtc ? A.Primitives_lazyAsJsDate(receiver).getUTCMinutes() + 0 : A.Primitives_lazyAsJsDate(receiver).getMinutes() + 0;
+ },
+ Primitives_getSeconds(receiver) {
+ return receiver.isUtc ? A.Primitives_lazyAsJsDate(receiver).getUTCSeconds() + 0 : A.Primitives_lazyAsJsDate(receiver).getSeconds() + 0;
+ },
+ Primitives_getMilliseconds(receiver) {
+ return receiver.isUtc ? A.Primitives_lazyAsJsDate(receiver).getUTCMilliseconds() + 0 : A.Primitives_lazyAsJsDate(receiver).getMilliseconds() + 0;
+ },
+ Primitives_functionNoSuchMethod($function, positionalArguments, namedArguments) {
+ var $arguments, namedArgumentList, t1 = {};
+ t1.argumentCount = 0;
+ $arguments = [];
+ namedArgumentList = [];
+ t1.argumentCount = positionalArguments.length;
+ B.JSArray_methods.addAll$1($arguments, positionalArguments);
+ t1.names = "";
+ if (namedArguments != null && namedArguments.__js_helper$_length !== 0)
+ namedArguments.forEach$1(0, new A.Primitives_functionNoSuchMethod_closure(t1, namedArgumentList, $arguments));
+ return J.noSuchMethod$1$($function, new A.JSInvocationMirror(B.Symbol_call, 0, $arguments, namedArgumentList, 0));
+ },
+ Primitives_applyFunction($function, positionalArguments, namedArguments) {
+ var t1, argumentCount, jsStub;
+ if (Array.isArray(positionalArguments))
+ t1 = namedArguments == null || namedArguments.__js_helper$_length === 0;
+ else
+ t1 = false;
+ if (t1) {
+ argumentCount = positionalArguments.length;
+ if (argumentCount === 0) {
+ if (!!$function.call$0)
+ return $function.call$0();
+ } else if (argumentCount === 1) {
+ if (!!$function.call$1)
+ return $function.call$1(positionalArguments[0]);
+ } else if (argumentCount === 2) {
+ if (!!$function.call$2)
+ return $function.call$2(positionalArguments[0], positionalArguments[1]);
+ } else if (argumentCount === 3) {
+ if (!!$function.call$3)
+ return $function.call$3(positionalArguments[0], positionalArguments[1], positionalArguments[2]);
+ } else if (argumentCount === 4) {
+ if (!!$function.call$4)
+ return $function.call$4(positionalArguments[0], positionalArguments[1], positionalArguments[2], positionalArguments[3]);
+ } else if (argumentCount === 5)
+ if (!!$function.call$5)
+ return $function.call$5(positionalArguments[0], positionalArguments[1], positionalArguments[2], positionalArguments[3], positionalArguments[4]);
+ jsStub = $function["call" + "$" + argumentCount];
+ if (jsStub != null)
+ return jsStub.apply($function, positionalArguments);
+ }
+ return A.Primitives__generalApplyFunction($function, positionalArguments, namedArguments);
+ },
+ Primitives__generalApplyFunction($function, positionalArguments, namedArguments) {
+ var defaultValuesClosure, t1, defaultValues, interceptor, jsFunction, maxArguments, missingDefaults, keys, _i, defaultValue, used, key,
+ $arguments = Array.isArray(positionalArguments) ? positionalArguments : A.List_List$of(positionalArguments, true, type$.dynamic),
+ argumentCount = $arguments.length,
+ requiredParameterCount = $function.$requiredArgCount;
+ if (argumentCount < requiredParameterCount)
+ return A.Primitives_functionNoSuchMethod($function, $arguments, namedArguments);
+ defaultValuesClosure = $function.$defaultValues;
+ t1 = defaultValuesClosure == null;
+ defaultValues = !t1 ? defaultValuesClosure() : null;
+ interceptor = J.getInterceptor$($function);
+ jsFunction = interceptor["call*"];
+ if (typeof jsFunction == "string")
+ jsFunction = interceptor[jsFunction];
+ if (t1) {
+ if (namedArguments != null && namedArguments.__js_helper$_length !== 0)
+ return A.Primitives_functionNoSuchMethod($function, $arguments, namedArguments);
+ if (argumentCount === requiredParameterCount)
+ return jsFunction.apply($function, $arguments);
+ return A.Primitives_functionNoSuchMethod($function, $arguments, namedArguments);
+ }
+ if (Array.isArray(defaultValues)) {
+ if (namedArguments != null && namedArguments.__js_helper$_length !== 0)
+ return A.Primitives_functionNoSuchMethod($function, $arguments, namedArguments);
+ maxArguments = requiredParameterCount + defaultValues.length;
+ if (argumentCount > maxArguments)
+ return A.Primitives_functionNoSuchMethod($function, $arguments, null);
+ if (argumentCount < maxArguments) {
+ missingDefaults = defaultValues.slice(argumentCount - requiredParameterCount);
+ if ($arguments === positionalArguments)
+ $arguments = A.List_List$of($arguments, true, type$.dynamic);
+ B.JSArray_methods.addAll$1($arguments, missingDefaults);
+ }
+ return jsFunction.apply($function, $arguments);
+ } else {
+ if (argumentCount > requiredParameterCount)
+ return A.Primitives_functionNoSuchMethod($function, $arguments, namedArguments);
+ if ($arguments === positionalArguments)
+ $arguments = A.List_List$of($arguments, true, type$.dynamic);
+ keys = Object.keys(defaultValues);
+ if (namedArguments == null)
+ for (t1 = keys.length, _i = 0; _i < keys.length; keys.length === t1 || (0, A.throwConcurrentModificationError)(keys), ++_i) {
+ defaultValue = defaultValues[A._asString(keys[_i])];
+ if (B.C__Required === defaultValue)
+ return A.Primitives_functionNoSuchMethod($function, $arguments, namedArguments);
+ B.JSArray_methods.add$1($arguments, defaultValue);
+ }
+ else {
+ for (t1 = keys.length, used = 0, _i = 0; _i < keys.length; keys.length === t1 || (0, A.throwConcurrentModificationError)(keys), ++_i) {
+ key = A._asString(keys[_i]);
+ if (namedArguments.containsKey$1(key)) {
+ ++used;
+ B.JSArray_methods.add$1($arguments, namedArguments.$index(0, key));
+ } else {
+ defaultValue = defaultValues[key];
+ if (B.C__Required === defaultValue)
+ return A.Primitives_functionNoSuchMethod($function, $arguments, namedArguments);
+ B.JSArray_methods.add$1($arguments, defaultValue);
+ }
+ }
+ if (used !== namedArguments.__js_helper$_length)
+ return A.Primitives_functionNoSuchMethod($function, $arguments, namedArguments);
+ }
+ return jsFunction.apply($function, $arguments);
+ }
+ },
+ ioore(receiver, index) {
+ if (receiver == null)
+ J.get$length$asx(receiver);
+ throw A.wrapException(A.diagnoseIndexError(receiver, index));
+ },
+ diagnoseIndexError(indexable, index) {
+ var $length, _s5_ = "index";
+ if (!A._isInt(index))
+ return new A.ArgumentError(true, index, _s5_, null);
+ $length = A._asInt(J.get$length$asx(indexable));
+ if (index < 0 || index >= $length)
+ return A.IndexError$withLength(index, $length, indexable, null, _s5_);
+ return A.RangeError$value(index, _s5_);
+ },
+ wrapException(ex) {
+ return A.initializeExceptionWrapper(new Error(), ex);
+ },
+ initializeExceptionWrapper(wrapper, ex) {
+ var t1;
+ if (ex == null)
+ ex = new A.TypeError();
+ wrapper.dartException = ex;
+ t1 = A.toStringWrapper;
+ if ("defineProperty" in Object) {
+ Object.defineProperty(wrapper, "message", {get: t1});
+ wrapper.name = "";
+ } else
+ wrapper.toString = t1;
+ return wrapper;
+ },
+ toStringWrapper() {
+ return J.toString$0$(this.dartException);
+ },
+ throwExpression(ex) {
+ throw A.wrapException(ex);
+ },
+ throwExpressionWithWrapper(ex, wrapper) {
+ throw A.initializeExceptionWrapper(wrapper, ex);
+ },
+ throwConcurrentModificationError(collection) {
+ throw A.wrapException(A.ConcurrentModificationError$(collection));
+ },
+ TypeErrorDecoder_extractPattern(message) {
+ var match, $arguments, argumentsExpr, expr, method, receiver;
+ message = A.quoteStringForRegExp(message.replace(String({}), "$receiver$"));
+ match = message.match(/\\\$[a-zA-Z]+\\\$/g);
+ if (match == null)
+ match = A._setArrayType([], type$.JSArray_String);
+ $arguments = match.indexOf("\\$arguments\\$");
+ argumentsExpr = match.indexOf("\\$argumentsExpr\\$");
+ expr = match.indexOf("\\$expr\\$");
+ method = match.indexOf("\\$method\\$");
+ receiver = match.indexOf("\\$receiver\\$");
+ return new A.TypeErrorDecoder(message.replace(new RegExp("\\\\\\$arguments\\\\\\$", "g"), "((?:x|[^x])*)").replace(new RegExp("\\\\\\$argumentsExpr\\\\\\$", "g"), "((?:x|[^x])*)").replace(new RegExp("\\\\\\$expr\\\\\\$", "g"), "((?:x|[^x])*)").replace(new RegExp("\\\\\\$method\\\\\\$", "g"), "((?:x|[^x])*)").replace(new RegExp("\\\\\\$receiver\\\\\\$", "g"), "((?:x|[^x])*)"), $arguments, argumentsExpr, expr, method, receiver);
+ },
+ TypeErrorDecoder_provokeCallErrorOn(expression) {
+ return function($expr$) {
+ var $argumentsExpr$ = "$arguments$";
+ try {
+ $expr$.$method$($argumentsExpr$);
+ } catch (e) {
+ return e.message;
+ }
+ }(expression);
+ },
+ TypeErrorDecoder_provokePropertyErrorOn(expression) {
+ return function($expr$) {
+ try {
+ $expr$.$method$;
+ } catch (e) {
+ return e.message;
+ }
+ }(expression);
+ },
+ JsNoSuchMethodError$(_message, match) {
+ var t1 = match == null,
+ t2 = t1 ? null : match.method;
+ return new A.JsNoSuchMethodError(_message, t2, t1 ? null : match.receiver);
+ },
+ unwrapException(ex) {
+ var t1;
+ if (ex == null)
+ return new A.NullThrownFromJavaScriptException(ex);
+ if (ex instanceof A.ExceptionAndStackTrace) {
+ t1 = ex.dartException;
+ return A.saveStackTrace(ex, t1 == null ? type$.Object._as(t1) : t1);
+ }
+ if (typeof ex !== "object")
+ return ex;
+ if ("dartException" in ex)
+ return A.saveStackTrace(ex, ex.dartException);
+ return A._unwrapNonDartException(ex);
+ },
+ saveStackTrace(ex, error) {
+ if (type$.Error._is(error))
+ if (error.$thrownJsError == null)
+ error.$thrownJsError = ex;
+ return error;
+ },
+ _unwrapNonDartException(ex) {
+ var message, number, ieErrorCode, nsme, notClosure, nullCall, nullLiteralCall, undefCall, undefLiteralCall, nullProperty, undefProperty, undefLiteralProperty, match;
+ if (!("message" in ex))
+ return ex;
+ message = ex.message;
+ if ("number" in ex && typeof ex.number == "number") {
+ number = ex.number;
+ ieErrorCode = number & 65535;
+ if ((B.JSInt_methods._shrOtherPositive$1(number, 16) & 8191) === 10)
+ switch (ieErrorCode) {
+ case 438:
+ return A.saveStackTrace(ex, A.JsNoSuchMethodError$(A.S(message) + " (Error " + ieErrorCode + ")", null));
+ case 445:
+ case 5007:
+ A.S(message);
+ return A.saveStackTrace(ex, new A.NullError());
+ }
+ }
+ if (ex instanceof TypeError) {
+ nsme = $.$get$TypeErrorDecoder_noSuchMethodPattern();
+ notClosure = $.$get$TypeErrorDecoder_notClosurePattern();
+ nullCall = $.$get$TypeErrorDecoder_nullCallPattern();
+ nullLiteralCall = $.$get$TypeErrorDecoder_nullLiteralCallPattern();
+ undefCall = $.$get$TypeErrorDecoder_undefinedCallPattern();
+ undefLiteralCall = $.$get$TypeErrorDecoder_undefinedLiteralCallPattern();
+ nullProperty = $.$get$TypeErrorDecoder_nullPropertyPattern();
+ $.$get$TypeErrorDecoder_nullLiteralPropertyPattern();
+ undefProperty = $.$get$TypeErrorDecoder_undefinedPropertyPattern();
+ undefLiteralProperty = $.$get$TypeErrorDecoder_undefinedLiteralPropertyPattern();
+ match = nsme.matchTypeError$1(message);
+ if (match != null)
+ return A.saveStackTrace(ex, A.JsNoSuchMethodError$(A._asString(message), match));
+ else {
+ match = notClosure.matchTypeError$1(message);
+ if (match != null) {
+ match.method = "call";
+ return A.saveStackTrace(ex, A.JsNoSuchMethodError$(A._asString(message), match));
+ } else if (nullCall.matchTypeError$1(message) != null || nullLiteralCall.matchTypeError$1(message) != null || undefCall.matchTypeError$1(message) != null || undefLiteralCall.matchTypeError$1(message) != null || nullProperty.matchTypeError$1(message) != null || nullLiteralCall.matchTypeError$1(message) != null || undefProperty.matchTypeError$1(message) != null || undefLiteralProperty.matchTypeError$1(message) != null) {
+ A._asString(message);
+ return A.saveStackTrace(ex, new A.NullError());
+ }
+ }
+ return A.saveStackTrace(ex, new A.UnknownJsTypeError(typeof message == "string" ? message : ""));
+ }
+ if (ex instanceof RangeError) {
+ if (typeof message == "string" && message.indexOf("call stack") !== -1)
+ return new A.StackOverflowError();
+ message = function(ex) {
+ try {
+ return String(ex);
+ } catch (e) {
+ }
+ return null;
+ }(ex);
+ return A.saveStackTrace(ex, new A.ArgumentError(false, null, null, typeof message == "string" ? message.replace(/^RangeError:\s*/, "") : message));
+ }
+ if (typeof InternalError == "function" && ex instanceof InternalError)
+ if (typeof message == "string" && message === "too much recursion")
+ return new A.StackOverflowError();
+ return ex;
+ },
+ getTraceFromException(exception) {
+ var trace;
+ if (exception instanceof A.ExceptionAndStackTrace)
+ return exception.stackTrace;
+ if (exception == null)
+ return new A._StackTrace(exception);
+ trace = exception.$cachedTrace;
+ if (trace != null)
+ return trace;
+ trace = new A._StackTrace(exception);
+ if (typeof exception === "object")
+ exception.$cachedTrace = trace;
+ return trace;
+ },
+ objectHashCode(object) {
+ if (object == null)
+ return J.get$hashCode$(object);
+ if (typeof object == "object")
+ return A.Primitives_objectHashCode(object);
+ return J.get$hashCode$(object);
+ },
+ _invokeClosure(closure, numberOfArguments, arg1, arg2, arg3, arg4) {
+ type$.Function._as(closure);
+ switch (A._asInt(numberOfArguments)) {
+ case 0:
+ return closure.call$0();
+ case 1:
+ return closure.call$1(arg1);
+ case 2:
+ return closure.call$2(arg1, arg2);
+ case 3:
+ return closure.call$3(arg1, arg2, arg3);
+ case 4:
+ return closure.call$4(arg1, arg2, arg3, arg4);
+ }
+ throw A.wrapException(new A._Exception("Unsupported number of arguments for wrapped closure"));
+ },
+ convertDartClosureToJS(closure, arity) {
+ var $function = closure.$identity;
+ if (!!$function)
+ return $function;
+ $function = A.convertDartClosureToJSUncached(closure, arity);
+ closure.$identity = $function;
+ return $function;
+ },
+ convertDartClosureToJSUncached(closure, arity) {
+ var entry;
+ switch (arity) {
+ case 0:
+ entry = closure.call$0;
+ break;
+ case 1:
+ entry = closure.call$1;
+ break;
+ case 2:
+ entry = closure.call$2;
+ break;
+ case 3:
+ entry = closure.call$3;
+ break;
+ case 4:
+ entry = closure.call$4;
+ break;
+ default:
+ entry = null;
+ }
+ if (entry != null)
+ return entry.bind(closure);
+ return function(closure, arity, invoke) {
+ return function(a1, a2, a3, a4) {
+ return invoke(closure, arity, a1, a2, a3, a4);
+ };
+ }(closure, arity, A._invokeClosure);
+ },
+ Closure_fromTearOff(parameters) {
+ var $prototype, $constructor, t2, trampoline, applyTrampoline, i, stub, stub0, stubName, stubCallName,
+ container = parameters.co,
+ isStatic = parameters.iS,
+ isIntercepted = parameters.iI,
+ needsDirectAccess = parameters.nDA,
+ applyTrampolineIndex = parameters.aI,
+ funsOrNames = parameters.fs,
+ callNames = parameters.cs,
+ $name = funsOrNames[0],
+ callName = callNames[0],
+ $function = container[$name],
+ t1 = parameters.fT;
+ t1.toString;
+ $prototype = isStatic ? Object.create(new A.StaticClosure().constructor.prototype) : Object.create(new A.BoundClosure(null, null).constructor.prototype);
+ $prototype.$initialize = $prototype.constructor;
+ $constructor = isStatic ? function static_tear_off() {
+ this.$initialize();
+ } : function tear_off(a, b) {
+ this.$initialize(a, b);
+ };
+ $prototype.constructor = $constructor;
+ $constructor.prototype = $prototype;
+ $prototype.$_name = $name;
+ $prototype.$_target = $function;
+ t2 = !isStatic;
+ if (t2)
+ trampoline = A.Closure_forwardCallTo($name, $function, isIntercepted, needsDirectAccess);
+ else {
+ $prototype.$static_name = $name;
+ trampoline = $function;
+ }
+ $prototype.$signature = A.Closure__computeSignatureFunctionNewRti(t1, isStatic, isIntercepted);
+ $prototype[callName] = trampoline;
+ for (applyTrampoline = trampoline, i = 1; i < funsOrNames.length; ++i) {
+ stub = funsOrNames[i];
+ if (typeof stub == "string") {
+ stub0 = container[stub];
+ stubName = stub;
+ stub = stub0;
+ } else
+ stubName = "";
+ stubCallName = callNames[i];
+ if (stubCallName != null) {
+ if (t2)
+ stub = A.Closure_forwardCallTo(stubName, stub, isIntercepted, needsDirectAccess);
+ $prototype[stubCallName] = stub;
+ }
+ if (i === applyTrampolineIndex)
+ applyTrampoline = stub;
+ }
+ $prototype["call*"] = applyTrampoline;
+ $prototype.$requiredArgCount = parameters.rC;
+ $prototype.$defaultValues = parameters.dV;
+ return $constructor;
+ },
+ Closure__computeSignatureFunctionNewRti(functionType, isStatic, isIntercepted) {
+ if (typeof functionType == "number")
+ return functionType;
+ if (typeof functionType == "string") {
+ if (isStatic)
+ throw A.wrapException("Cannot compute signature for static tearoff.");
+ return function(recipe, evalOnReceiver) {
+ return function() {
+ return evalOnReceiver(this, recipe);
+ };
+ }(functionType, A.BoundClosure_evalRecipe);
+ }
+ throw A.wrapException("Error in functionType of tearoff");
+ },
+ Closure_cspForwardCall(arity, needsDirectAccess, stubName, $function) {
+ var getReceiver = A.BoundClosure_receiverOf;
+ switch (needsDirectAccess ? -1 : arity) {
+ case 0:
+ return function(entry, receiverOf) {
+ return function() {
+ return receiverOf(this)[entry]();
+ };
+ }(stubName, getReceiver);
+ case 1:
+ return function(entry, receiverOf) {
+ return function(a) {
+ return receiverOf(this)[entry](a);
+ };
+ }(stubName, getReceiver);
+ case 2:
+ return function(entry, receiverOf) {
+ return function(a, b) {
+ return receiverOf(this)[entry](a, b);
+ };
+ }(stubName, getReceiver);
+ case 3:
+ return function(entry, receiverOf) {
+ return function(a, b, c) {
+ return receiverOf(this)[entry](a, b, c);
+ };
+ }(stubName, getReceiver);
+ case 4:
+ return function(entry, receiverOf) {
+ return function(a, b, c, d) {
+ return receiverOf(this)[entry](a, b, c, d);
+ };
+ }(stubName, getReceiver);
+ case 5:
+ return function(entry, receiverOf) {
+ return function(a, b, c, d, e) {
+ return receiverOf(this)[entry](a, b, c, d, e);
+ };
+ }(stubName, getReceiver);
+ default:
+ return function(f, receiverOf) {
+ return function() {
+ return f.apply(receiverOf(this), arguments);
+ };
+ }($function, getReceiver);
+ }
+ },
+ Closure_forwardCallTo(stubName, $function, isIntercepted, needsDirectAccess) {
+ if (isIntercepted)
+ return A.Closure_forwardInterceptedCallTo(stubName, $function, needsDirectAccess);
+ return A.Closure_cspForwardCall($function.length, needsDirectAccess, stubName, $function);
+ },
+ Closure_cspForwardInterceptedCall(arity, needsDirectAccess, stubName, $function) {
+ var getReceiver = A.BoundClosure_receiverOf,
+ getInterceptor = A.BoundClosure_interceptorOf;
+ switch (needsDirectAccess ? -1 : arity) {
+ case 0:
+ throw A.wrapException(new A.RuntimeError("Intercepted function with no arguments."));
+ case 1:
+ return function(entry, interceptorOf, receiverOf) {
+ return function() {
+ return interceptorOf(this)[entry](receiverOf(this));
+ };
+ }(stubName, getInterceptor, getReceiver);
+ case 2:
+ return function(entry, interceptorOf, receiverOf) {
+ return function(a) {
+ return interceptorOf(this)[entry](receiverOf(this), a);
+ };
+ }(stubName, getInterceptor, getReceiver);
+ case 3:
+ return function(entry, interceptorOf, receiverOf) {
+ return function(a, b) {
+ return interceptorOf(this)[entry](receiverOf(this), a, b);
+ };
+ }(stubName, getInterceptor, getReceiver);
+ case 4:
+ return function(entry, interceptorOf, receiverOf) {
+ return function(a, b, c) {
+ return interceptorOf(this)[entry](receiverOf(this), a, b, c);
+ };
+ }(stubName, getInterceptor, getReceiver);
+ case 5:
+ return function(entry, interceptorOf, receiverOf) {
+ return function(a, b, c, d) {
+ return interceptorOf(this)[entry](receiverOf(this), a, b, c, d);
+ };
+ }(stubName, getInterceptor, getReceiver);
+ case 6:
+ return function(entry, interceptorOf, receiverOf) {
+ return function(a, b, c, d, e) {
+ return interceptorOf(this)[entry](receiverOf(this), a, b, c, d, e);
+ };
+ }(stubName, getInterceptor, getReceiver);
+ default:
+ return function(f, interceptorOf, receiverOf) {
+ return function() {
+ var a = [receiverOf(this)];
+ Array.prototype.push.apply(a, arguments);
+ return f.apply(interceptorOf(this), a);
+ };
+ }($function, getInterceptor, getReceiver);
+ }
+ },
+ Closure_forwardInterceptedCallTo(stubName, $function, needsDirectAccess) {
+ var arity, t1;
+ if ($.BoundClosure__interceptorFieldNameCache == null)
+ $.BoundClosure__interceptorFieldNameCache = A.BoundClosure__computeFieldNamed("interceptor");
+ if ($.BoundClosure__receiverFieldNameCache == null)
+ $.BoundClosure__receiverFieldNameCache = A.BoundClosure__computeFieldNamed("receiver");
+ arity = $function.length;
+ t1 = A.Closure_cspForwardInterceptedCall(arity, needsDirectAccess, stubName, $function);
+ return t1;
+ },
+ closureFromTearOff(parameters) {
+ return A.Closure_fromTearOff(parameters);
+ },
+ BoundClosure_evalRecipe(closure, recipe) {
+ return A._Universe_evalInEnvironment(init.typeUniverse, A.instanceType(closure._receiver), recipe);
+ },
+ BoundClosure_receiverOf(closure) {
+ return closure._receiver;
+ },
+ BoundClosure_interceptorOf(closure) {
+ return closure._interceptor;
+ },
+ BoundClosure__computeFieldNamed(fieldName) {
+ var t1, i, $name,
+ template = new A.BoundClosure("receiver", "interceptor"),
+ names = J.JSArray_markFixedList(Object.getOwnPropertyNames(template), type$.nullable_Object);
+ for (t1 = names.length, i = 0; i < t1; ++i) {
+ $name = names[i];
+ if (template[$name] === fieldName)
+ return $name;
+ }
+ throw A.wrapException(A.ArgumentError$("Field name " + fieldName + " not found.", null));
+ },
+ throwCyclicInit(staticName) {
+ throw A.wrapException(new A._CyclicInitializationError(staticName));
+ },
+ getIsolateAffinityTag($name) {
+ return init.getIsolateTag($name);
+ },
+ lookupAndCacheInterceptor(obj) {
+ var interceptor, interceptorClass, altTag, mark, t1,
+ tag = A._asString($.getTagFunction.call$1(obj)),
+ record = $.dispatchRecordsForInstanceTags[tag];
+ if (record != null) {
+ Object.defineProperty(obj, init.dispatchPropertyName, {value: record, enumerable: false, writable: true, configurable: true});
+ return record.i;
+ }
+ interceptor = $.interceptorsForUncacheableTags[tag];
+ if (interceptor != null)
+ return interceptor;
+ interceptorClass = init.interceptorsByTag[tag];
+ if (interceptorClass == null) {
+ altTag = A._asStringQ($.alternateTagFunction.call$2(obj, tag));
+ if (altTag != null) {
+ record = $.dispatchRecordsForInstanceTags[altTag];
+ if (record != null) {
+ Object.defineProperty(obj, init.dispatchPropertyName, {value: record, enumerable: false, writable: true, configurable: true});
+ return record.i;
+ }
+ interceptor = $.interceptorsForUncacheableTags[altTag];
+ if (interceptor != null)
+ return interceptor;
+ interceptorClass = init.interceptorsByTag[altTag];
+ tag = altTag;
+ }
+ }
+ if (interceptorClass == null)
+ return null;
+ interceptor = interceptorClass.prototype;
+ mark = tag[0];
+ if (mark === "!") {
+ record = A.makeLeafDispatchRecord(interceptor);
+ $.dispatchRecordsForInstanceTags[tag] = record;
+ Object.defineProperty(obj, init.dispatchPropertyName, {value: record, enumerable: false, writable: true, configurable: true});
+ return record.i;
+ }
+ if (mark === "~") {
+ $.interceptorsForUncacheableTags[tag] = interceptor;
+ return interceptor;
+ }
+ if (mark === "-") {
+ t1 = A.makeLeafDispatchRecord(interceptor);
+ Object.defineProperty(Object.getPrototypeOf(obj), init.dispatchPropertyName, {value: t1, enumerable: false, writable: true, configurable: true});
+ return t1.i;
+ }
+ if (mark === "+")
+ return A.patchInteriorProto(obj, interceptor);
+ if (mark === "*")
+ throw A.wrapException(A.UnimplementedError$(tag));
+ if (init.leafTags[tag] === true) {
+ t1 = A.makeLeafDispatchRecord(interceptor);
+ Object.defineProperty(Object.getPrototypeOf(obj), init.dispatchPropertyName, {value: t1, enumerable: false, writable: true, configurable: true});
+ return t1.i;
+ } else
+ return A.patchInteriorProto(obj, interceptor);
+ },
+ patchInteriorProto(obj, interceptor) {
+ var proto = Object.getPrototypeOf(obj);
+ Object.defineProperty(proto, init.dispatchPropertyName, {value: J.makeDispatchRecord(interceptor, proto, null, null), enumerable: false, writable: true, configurable: true});
+ return interceptor;
+ },
+ makeLeafDispatchRecord(interceptor) {
+ return J.makeDispatchRecord(interceptor, false, null, !!interceptor.$isJavaScriptIndexingBehavior);
+ },
+ makeDefaultDispatchRecord(tag, interceptorClass, proto) {
+ var interceptor = interceptorClass.prototype;
+ if (init.leafTags[tag] === true)
+ return A.makeLeafDispatchRecord(interceptor);
+ else
+ return J.makeDispatchRecord(interceptor, proto, null, null);
+ },
+ initNativeDispatch() {
+ if (true === $.initNativeDispatchFlag)
+ return;
+ $.initNativeDispatchFlag = true;
+ A.initNativeDispatchContinue();
+ },
+ initNativeDispatchContinue() {
+ var map, tags, fun, i, tag, proto, record, interceptorClass;
+ $.dispatchRecordsForInstanceTags = Object.create(null);
+ $.interceptorsForUncacheableTags = Object.create(null);
+ A.initHooks();
+ map = init.interceptorsByTag;
+ tags = Object.getOwnPropertyNames(map);
+ if (typeof window != "undefined") {
+ window;
+ fun = function() {
+ };
+ for (i = 0; i < tags.length; ++i) {
+ tag = tags[i];
+ proto = $.prototypeForTagFunction.call$1(tag);
+ if (proto != null) {
+ record = A.makeDefaultDispatchRecord(tag, map[tag], proto);
+ if (record != null) {
+ Object.defineProperty(proto, init.dispatchPropertyName, {value: record, enumerable: false, writable: true, configurable: true});
+ fun.prototype = proto;
+ }
+ }
+ }
+ }
+ for (i = 0; i < tags.length; ++i) {
+ tag = tags[i];
+ if (/^[A-Za-z_]/.test(tag)) {
+ interceptorClass = map[tag];
+ map["!" + tag] = interceptorClass;
+ map["~" + tag] = interceptorClass;
+ map["-" + tag] = interceptorClass;
+ map["+" + tag] = interceptorClass;
+ map["*" + tag] = interceptorClass;
+ }
+ }
+ },
+ initHooks() {
+ var transformers, i, transformer, getTag, getUnknownTag, prototypeForTag,
+ hooks = B.C_JS_CONST0();
+ hooks = A.applyHooksTransformer(B.C_JS_CONST1, A.applyHooksTransformer(B.C_JS_CONST2, A.applyHooksTransformer(B.C_JS_CONST3, A.applyHooksTransformer(B.C_JS_CONST3, A.applyHooksTransformer(B.C_JS_CONST4, A.applyHooksTransformer(B.C_JS_CONST5, A.applyHooksTransformer(B.C_JS_CONST6(B.C_JS_CONST), hooks)))))));
+ if (typeof dartNativeDispatchHooksTransformer != "undefined") {
+ transformers = dartNativeDispatchHooksTransformer;
+ if (typeof transformers == "function")
+ transformers = [transformers];
+ if (Array.isArray(transformers))
+ for (i = 0; i < transformers.length; ++i) {
+ transformer = transformers[i];
+ if (typeof transformer == "function")
+ hooks = transformer(hooks) || hooks;
+ }
+ }
+ getTag = hooks.getTag;
+ getUnknownTag = hooks.getUnknownTag;
+ prototypeForTag = hooks.prototypeForTag;
+ $.getTagFunction = new A.initHooks_closure(getTag);
+ $.alternateTagFunction = new A.initHooks_closure0(getUnknownTag);
+ $.prototypeForTagFunction = new A.initHooks_closure1(prototypeForTag);
+ },
+ applyHooksTransformer(transformer, hooks) {
+ return transformer(hooks) || hooks;
+ },
+ createRecordTypePredicate(shape, fieldRtis) {
+ var $length = fieldRtis.length,
+ $function = init.rttc["" + $length + ";" + shape];
+ if ($function == null)
+ return null;
+ if ($length === 0)
+ return $function;
+ if ($length === $function.length)
+ return $function.apply(null, fieldRtis);
+ return $function(fieldRtis);
+ },
+ quoteStringForRegExp(string) {
+ if (/[[\]{}()*+?.\\^$|]/.test(string))
+ return string.replace(/[[\]{}()*+?.\\^$|]/g, "\\$&");
+ return string;
+ },
+ ConstantMapView: function ConstantMapView(t0, t1) {
+ this._collection$_map = t0;
+ this.$ti = t1;
+ },
+ ConstantMap: function ConstantMap() {
+ },
+ ConstantStringMap: function ConstantStringMap(t0, t1, t2) {
+ this._jsIndex = t0;
+ this._values = t1;
+ this.$ti = t2;
+ },
+ JSInvocationMirror: function JSInvocationMirror(t0, t1, t2, t3, t4) {
+ var _ = this;
+ _._memberName = t0;
+ _.__js_helper$_kind = t1;
+ _._arguments = t2;
+ _._namedArgumentNames = t3;
+ _._typeArgumentCount = t4;
+ },
+ Primitives_functionNoSuchMethod_closure: function Primitives_functionNoSuchMethod_closure(t0, t1, t2) {
+ this._box_0 = t0;
+ this.namedArgumentList = t1;
+ this.$arguments = t2;
+ },
+ TypeErrorDecoder: function TypeErrorDecoder(t0, t1, t2, t3, t4, t5) {
+ var _ = this;
+ _._pattern = t0;
+ _._arguments = t1;
+ _._argumentsExpr = t2;
+ _._expr = t3;
+ _._method = t4;
+ _._receiver = t5;
+ },
+ NullError: function NullError() {
+ },
+ JsNoSuchMethodError: function JsNoSuchMethodError(t0, t1, t2) {
+ this.__js_helper$_message = t0;
+ this._method = t1;
+ this._receiver = t2;
+ },
+ UnknownJsTypeError: function UnknownJsTypeError(t0) {
+ this.__js_helper$_message = t0;
+ },
+ NullThrownFromJavaScriptException: function NullThrownFromJavaScriptException(t0) {
+ this._irritant = t0;
+ },
+ ExceptionAndStackTrace: function ExceptionAndStackTrace(t0, t1) {
+ this.dartException = t0;
+ this.stackTrace = t1;
+ },
+ _StackTrace: function _StackTrace(t0) {
+ this._exception = t0;
+ this._trace = null;
+ },
+ Closure: function Closure() {
+ },
+ Closure0Args: function Closure0Args() {
+ },
+ Closure2Args: function Closure2Args() {
+ },
+ TearOffClosure: function TearOffClosure() {
+ },
+ StaticClosure: function StaticClosure() {
+ },
+ BoundClosure: function BoundClosure(t0, t1) {
+ this._receiver = t0;
+ this._interceptor = t1;
+ },
+ _CyclicInitializationError: function _CyclicInitializationError(t0) {
+ this.variableName = t0;
+ },
+ RuntimeError: function RuntimeError(t0) {
+ this.message = t0;
+ },
+ _Required: function _Required() {
+ },
+ JsLinkedHashMap: function JsLinkedHashMap(t0) {
+ var _ = this;
+ _.__js_helper$_length = 0;
+ _._last = _._first = _.__js_helper$_rest = _._nums = _._strings = null;
+ _._modifications = 0;
+ _.$ti = t0;
+ },
+ LinkedHashMapCell: function LinkedHashMapCell(t0, t1) {
+ this.hashMapCellKey = t0;
+ this.hashMapCellValue = t1;
+ this._next = null;
+ },
+ LinkedHashMapKeyIterable: function LinkedHashMapKeyIterable(t0, t1) {
+ this._map = t0;
+ this.$ti = t1;
+ },
+ LinkedHashMapKeyIterator: function LinkedHashMapKeyIterator(t0, t1, t2) {
+ var _ = this;
+ _._map = t0;
+ _._modifications = t1;
+ _.__js_helper$_current = _._cell = null;
+ _.$ti = t2;
+ },
+ initHooks_closure: function initHooks_closure(t0) {
+ this.getTag = t0;
+ },
+ initHooks_closure0: function initHooks_closure0(t0) {
+ this.getUnknownTag = t0;
+ },
+ initHooks_closure1: function initHooks_closure1(t0) {
+ this.prototypeForTag = t0;
+ },
+ StringMatch: function StringMatch(t0, t1) {
+ this.start = t0;
+ this.pattern = t1;
+ },
+ _checkValidIndex(index, list, $length) {
+ if (index >>> 0 !== index || index >= $length)
+ throw A.wrapException(A.diagnoseIndexError(list, index));
+ },
+ NativeByteBuffer: function NativeByteBuffer() {
+ },
+ NativeTypedData: function NativeTypedData() {
+ },
+ NativeByteData: function NativeByteData() {
+ },
+ NativeTypedArray: function NativeTypedArray() {
+ },
+ NativeTypedArrayOfDouble: function NativeTypedArrayOfDouble() {
+ },
+ NativeTypedArrayOfInt: function NativeTypedArrayOfInt() {
+ },
+ NativeFloat32List: function NativeFloat32List() {
+ },
+ NativeFloat64List: function NativeFloat64List() {
+ },
+ NativeInt16List: function NativeInt16List() {
+ },
+ NativeInt32List: function NativeInt32List() {
+ },
+ NativeInt8List: function NativeInt8List() {
+ },
+ NativeUint16List: function NativeUint16List() {
+ },
+ NativeUint32List: function NativeUint32List() {
+ },
+ NativeUint8ClampedList: function NativeUint8ClampedList() {
+ },
+ NativeUint8List: function NativeUint8List() {
+ },
+ _NativeTypedArrayOfDouble_NativeTypedArray_ListMixin: function _NativeTypedArrayOfDouble_NativeTypedArray_ListMixin() {
+ },
+ _NativeTypedArrayOfDouble_NativeTypedArray_ListMixin_FixedLengthListMixin: function _NativeTypedArrayOfDouble_NativeTypedArray_ListMixin_FixedLengthListMixin() {
+ },
+ _NativeTypedArrayOfInt_NativeTypedArray_ListMixin: function _NativeTypedArrayOfInt_NativeTypedArray_ListMixin() {
+ },
+ _NativeTypedArrayOfInt_NativeTypedArray_ListMixin_FixedLengthListMixin: function _NativeTypedArrayOfInt_NativeTypedArray_ListMixin_FixedLengthListMixin() {
+ },
+ Rti__getQuestionFromStar(universe, rti) {
+ var question = rti._precomputed1;
+ return question == null ? rti._precomputed1 = A._Universe__lookupQuestionRti(universe, rti._primary, true) : question;
+ },
+ Rti__getFutureFromFutureOr(universe, rti) {
+ var future = rti._precomputed1;
+ return future == null ? rti._precomputed1 = A._Universe__lookupInterfaceRti(universe, "Future", [rti._primary]) : future;
+ },
+ Rti__isUnionOfFunctionType(rti) {
+ var kind = rti._kind;
+ if (kind === 6 || kind === 7 || kind === 8)
+ return A.Rti__isUnionOfFunctionType(rti._primary);
+ return kind === 12 || kind === 13;
+ },
+ Rti__getCanonicalRecipe(rti) {
+ return rti._canonicalRecipe;
+ },
+ findType(recipe) {
+ return A._Universe_eval(init.typeUniverse, recipe, false);
+ },
+ _substitute(universe, rti, typeArguments, depth) {
+ var baseType, substitutedBaseType, interfaceTypeArguments, substitutedInterfaceTypeArguments, base, substitutedBase, $arguments, substitutedArguments, t1, fields, substitutedFields, returnType, substitutedReturnType, functionParameters, substitutedFunctionParameters, bounds, substitutedBounds, index, argument,
+ kind = rti._kind;
+ switch (kind) {
+ case 5:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ return rti;
+ case 6:
+ baseType = rti._primary;
+ substitutedBaseType = A._substitute(universe, baseType, typeArguments, depth);
+ if (substitutedBaseType === baseType)
+ return rti;
+ return A._Universe__lookupStarRti(universe, substitutedBaseType, true);
+ case 7:
+ baseType = rti._primary;
+ substitutedBaseType = A._substitute(universe, baseType, typeArguments, depth);
+ if (substitutedBaseType === baseType)
+ return rti;
+ return A._Universe__lookupQuestionRti(universe, substitutedBaseType, true);
+ case 8:
+ baseType = rti._primary;
+ substitutedBaseType = A._substitute(universe, baseType, typeArguments, depth);
+ if (substitutedBaseType === baseType)
+ return rti;
+ return A._Universe__lookupFutureOrRti(universe, substitutedBaseType, true);
+ case 9:
+ interfaceTypeArguments = rti._rest;
+ substitutedInterfaceTypeArguments = A._substituteArray(universe, interfaceTypeArguments, typeArguments, depth);
+ if (substitutedInterfaceTypeArguments === interfaceTypeArguments)
+ return rti;
+ return A._Universe__lookupInterfaceRti(universe, rti._primary, substitutedInterfaceTypeArguments);
+ case 10:
+ base = rti._primary;
+ substitutedBase = A._substitute(universe, base, typeArguments, depth);
+ $arguments = rti._rest;
+ substitutedArguments = A._substituteArray(universe, $arguments, typeArguments, depth);
+ if (substitutedBase === base && substitutedArguments === $arguments)
+ return rti;
+ return A._Universe__lookupBindingRti(universe, substitutedBase, substitutedArguments);
+ case 11:
+ t1 = rti._primary;
+ fields = rti._rest;
+ substitutedFields = A._substituteArray(universe, fields, typeArguments, depth);
+ if (substitutedFields === fields)
+ return rti;
+ return A._Universe__lookupRecordRti(universe, t1, substitutedFields);
+ case 12:
+ returnType = rti._primary;
+ substitutedReturnType = A._substitute(universe, returnType, typeArguments, depth);
+ functionParameters = rti._rest;
+ substitutedFunctionParameters = A._substituteFunctionParameters(universe, functionParameters, typeArguments, depth);
+ if (substitutedReturnType === returnType && substitutedFunctionParameters === functionParameters)
+ return rti;
+ return A._Universe__lookupFunctionRti(universe, substitutedReturnType, substitutedFunctionParameters);
+ case 13:
+ bounds = rti._rest;
+ depth += bounds.length;
+ substitutedBounds = A._substituteArray(universe, bounds, typeArguments, depth);
+ base = rti._primary;
+ substitutedBase = A._substitute(universe, base, typeArguments, depth);
+ if (substitutedBounds === bounds && substitutedBase === base)
+ return rti;
+ return A._Universe__lookupGenericFunctionRti(universe, substitutedBase, substitutedBounds, true);
+ case 14:
+ index = rti._primary;
+ if (index < depth)
+ return rti;
+ argument = typeArguments[index - depth];
+ if (argument == null)
+ return rti;
+ return argument;
+ default:
+ throw A.wrapException(A.AssertionError$("Attempted to substitute unexpected RTI kind " + kind));
+ }
+ },
+ _substituteArray(universe, rtiArray, typeArguments, depth) {
+ var changed, i, rti, substitutedRti,
+ $length = rtiArray.length,
+ result = A._Utils_newArrayOrEmpty($length);
+ for (changed = false, i = 0; i < $length; ++i) {
+ rti = rtiArray[i];
+ substitutedRti = A._substitute(universe, rti, typeArguments, depth);
+ if (substitutedRti !== rti)
+ changed = true;
+ result[i] = substitutedRti;
+ }
+ return changed ? result : rtiArray;
+ },
+ _substituteNamed(universe, namedArray, typeArguments, depth) {
+ var changed, i, t1, t2, rti, substitutedRti,
+ $length = namedArray.length,
+ result = A._Utils_newArrayOrEmpty($length);
+ for (changed = false, i = 0; i < $length; i += 3) {
+ t1 = namedArray[i];
+ t2 = namedArray[i + 1];
+ rti = namedArray[i + 2];
+ substitutedRti = A._substitute(universe, rti, typeArguments, depth);
+ if (substitutedRti !== rti)
+ changed = true;
+ result.splice(i, 3, t1, t2, substitutedRti);
+ }
+ return changed ? result : namedArray;
+ },
+ _substituteFunctionParameters(universe, functionParameters, typeArguments, depth) {
+ var result,
+ requiredPositional = functionParameters._requiredPositional,
+ substitutedRequiredPositional = A._substituteArray(universe, requiredPositional, typeArguments, depth),
+ optionalPositional = functionParameters._optionalPositional,
+ substitutedOptionalPositional = A._substituteArray(universe, optionalPositional, typeArguments, depth),
+ named = functionParameters._named,
+ substitutedNamed = A._substituteNamed(universe, named, typeArguments, depth);
+ if (substitutedRequiredPositional === requiredPositional && substitutedOptionalPositional === optionalPositional && substitutedNamed === named)
+ return functionParameters;
+ result = new A._FunctionParameters();
+ result._requiredPositional = substitutedRequiredPositional;
+ result._optionalPositional = substitutedOptionalPositional;
+ result._named = substitutedNamed;
+ return result;
+ },
+ _setArrayType(target, rti) {
+ target[init.arrayRti] = rti;
+ return target;
+ },
+ closureFunctionType(closure) {
+ var signature = closure.$signature;
+ if (signature != null) {
+ if (typeof signature == "number")
+ return A.getTypeFromTypesTable(signature);
+ return closure.$signature();
+ }
+ return null;
+ },
+ instanceOrFunctionType(object, testRti) {
+ var rti;
+ if (A.Rti__isUnionOfFunctionType(testRti))
+ if (object instanceof A.Closure) {
+ rti = A.closureFunctionType(object);
+ if (rti != null)
+ return rti;
+ }
+ return A.instanceType(object);
+ },
+ instanceType(object) {
+ if (object instanceof A.Object)
+ return A._instanceType(object);
+ if (Array.isArray(object))
+ return A._arrayInstanceType(object);
+ return A._instanceTypeFromConstructor(J.getInterceptor$(object));
+ },
+ _arrayInstanceType(object) {
+ var rti = object[init.arrayRti],
+ defaultRti = type$.JSArray_dynamic;
+ if (rti == null)
+ return defaultRti;
+ if (rti.constructor !== defaultRti.constructor)
+ return defaultRti;
+ return rti;
+ },
+ _instanceType(object) {
+ var rti = object.$ti;
+ return rti != null ? rti : A._instanceTypeFromConstructor(object);
+ },
+ _instanceTypeFromConstructor(instance) {
+ var $constructor = instance.constructor,
+ probe = $constructor.$ccache;
+ if (probe != null)
+ return probe;
+ return A._instanceTypeFromConstructorMiss(instance, $constructor);
+ },
+ _instanceTypeFromConstructorMiss(instance, $constructor) {
+ var effectiveConstructor = instance instanceof A.Closure ? Object.getPrototypeOf(Object.getPrototypeOf(instance)).constructor : $constructor,
+ rti = A._Universe_findErasedType(init.typeUniverse, effectiveConstructor.name);
+ $constructor.$ccache = rti;
+ return rti;
+ },
+ getTypeFromTypesTable(index) {
+ var rti,
+ table = init.types,
+ type = table[index];
+ if (typeof type == "string") {
+ rti = A._Universe_eval(init.typeUniverse, type, false);
+ table[index] = rti;
+ return rti;
+ }
+ return type;
+ },
+ getRuntimeTypeOfDartObject(object) {
+ return A.createRuntimeType(A._instanceType(object));
+ },
+ _structuralTypeOf(object) {
+ var functionRti = object instanceof A.Closure ? A.closureFunctionType(object) : null;
+ if (functionRti != null)
+ return functionRti;
+ if (type$.TrustedGetRuntimeType._is(object))
+ return J.get$runtimeType$(object)._rti;
+ if (Array.isArray(object))
+ return A._arrayInstanceType(object);
+ return A.instanceType(object);
+ },
+ createRuntimeType(rti) {
+ var t1 = rti._cachedRuntimeType;
+ return t1 == null ? rti._cachedRuntimeType = A._createRuntimeType(rti) : t1;
+ },
+ _createRuntimeType(rti) {
+ var starErasedRti, t1,
+ s = rti._canonicalRecipe,
+ starErasedRecipe = s.replace(/\*/g, "");
+ if (starErasedRecipe === s)
+ return rti._cachedRuntimeType = new A._Type(rti);
+ starErasedRti = A._Universe_eval(init.typeUniverse, starErasedRecipe, true);
+ t1 = starErasedRti._cachedRuntimeType;
+ return t1 == null ? starErasedRti._cachedRuntimeType = A._createRuntimeType(starErasedRti) : t1;
+ },
+ typeLiteral(recipe) {
+ return A.createRuntimeType(A._Universe_eval(init.typeUniverse, recipe, false));
+ },
+ _installSpecializedIsTest(object) {
+ var t1, unstarred, unstarredKind, isFn, $name, predicate, testRti = this;
+ if (testRti === type$.Object)
+ return A._finishIsFn(testRti, object, A._isObject);
+ if (!A.isSoundTopType(testRti))
+ t1 = testRti === type$.legacy_Object;
+ else
+ t1 = true;
+ if (t1)
+ return A._finishIsFn(testRti, object, A._isTop);
+ t1 = testRti._kind;
+ if (t1 === 7)
+ return A._finishIsFn(testRti, object, A._generalNullableIsTestImplementation);
+ if (t1 === 1)
+ return A._finishIsFn(testRti, object, A._isNever);
+ unstarred = t1 === 6 ? testRti._primary : testRti;
+ unstarredKind = unstarred._kind;
+ if (unstarredKind === 8)
+ return A._finishIsFn(testRti, object, A._isFutureOr);
+ if (unstarred === type$.int)
+ isFn = A._isInt;
+ else if (unstarred === type$.double || unstarred === type$.num)
+ isFn = A._isNum;
+ else if (unstarred === type$.String)
+ isFn = A._isString;
+ else
+ isFn = unstarred === type$.bool ? A._isBool : null;
+ if (isFn != null)
+ return A._finishIsFn(testRti, object, isFn);
+ if (unstarredKind === 9) {
+ $name = unstarred._primary;
+ if (unstarred._rest.every(A.isDefinitelyTopType)) {
+ testRti._specializedTestResource = "$is" + $name;
+ if ($name === "List")
+ return A._finishIsFn(testRti, object, A._isListTestViaProperty);
+ return A._finishIsFn(testRti, object, A._isTestViaProperty);
+ }
+ } else if (unstarredKind === 11) {
+ predicate = A.createRecordTypePredicate(unstarred._primary, unstarred._rest);
+ return A._finishIsFn(testRti, object, predicate == null ? A._isNever : predicate);
+ }
+ return A._finishIsFn(testRti, object, A._generalIsTestImplementation);
+ },
+ _finishIsFn(testRti, object, isFn) {
+ testRti._is = isFn;
+ return testRti._is(object);
+ },
+ _installSpecializedAsCheck(object) {
+ var t1, testRti = this,
+ asFn = A._generalAsCheckImplementation;
+ if (!A.isSoundTopType(testRti))
+ t1 = testRti === type$.legacy_Object;
+ else
+ t1 = true;
+ if (t1)
+ asFn = A._asTop;
+ else if (testRti === type$.Object)
+ asFn = A._asObject;
+ else {
+ t1 = A.isNullable(testRti);
+ if (t1)
+ asFn = A._generalNullableAsCheckImplementation;
+ }
+ testRti._as = asFn;
+ return testRti._as(object);
+ },
+ _nullIs(testRti) {
+ var t1,
+ kind = testRti._kind;
+ if (!A.isSoundTopType(testRti))
+ if (!(testRti === type$.legacy_Object))
+ if (!(testRti === type$.legacy_Never))
+ if (kind !== 7)
+ if (!(kind === 6 && A._nullIs(testRti._primary)))
+ t1 = kind === 8 && A._nullIs(testRti._primary) || testRti === type$.Null || testRti === type$.JSNull;
+ else
+ t1 = true;
+ else
+ t1 = true;
+ else
+ t1 = true;
+ else
+ t1 = true;
+ else
+ t1 = true;
+ return t1;
+ },
+ _generalIsTestImplementation(object) {
+ var testRti = this;
+ if (object == null)
+ return A._nullIs(testRti);
+ return A.isSubtype(init.typeUniverse, A.instanceOrFunctionType(object, testRti), testRti);
+ },
+ _generalNullableIsTestImplementation(object) {
+ if (object == null)
+ return true;
+ return this._primary._is(object);
+ },
+ _isTestViaProperty(object) {
+ var tag, testRti = this;
+ if (object == null)
+ return A._nullIs(testRti);
+ tag = testRti._specializedTestResource;
+ if (object instanceof A.Object)
+ return !!object[tag];
+ return !!J.getInterceptor$(object)[tag];
+ },
+ _isListTestViaProperty(object) {
+ var tag, testRti = this;
+ if (object == null)
+ return A._nullIs(testRti);
+ if (typeof object != "object")
+ return false;
+ if (Array.isArray(object))
+ return true;
+ tag = testRti._specializedTestResource;
+ if (object instanceof A.Object)
+ return !!object[tag];
+ return !!J.getInterceptor$(object)[tag];
+ },
+ _generalAsCheckImplementation(object) {
+ var testRti = this;
+ if (object == null) {
+ if (A.isNullable(testRti))
+ return object;
+ } else if (testRti._is(object))
+ return object;
+ A._failedAsCheck(object, testRti);
+ },
+ _generalNullableAsCheckImplementation(object) {
+ var testRti = this;
+ if (object == null)
+ return object;
+ else if (testRti._is(object))
+ return object;
+ A._failedAsCheck(object, testRti);
+ },
+ _failedAsCheck(object, testRti) {
+ throw A.wrapException(A._TypeError$fromMessage(A._Error_compose(object, A._rtiToString(testRti, null))));
+ },
+ _Error_compose(object, checkedTypeDescription) {
+ return A.Error_safeToString(object) + ": type '" + A._rtiToString(A._structuralTypeOf(object), null) + "' is not a subtype of type '" + checkedTypeDescription + "'";
+ },
+ _TypeError$fromMessage(message) {
+ return new A._TypeError("TypeError: " + message);
+ },
+ _TypeError__TypeError$forType(object, type) {
+ return new A._TypeError("TypeError: " + A._Error_compose(object, type));
+ },
+ _isFutureOr(object) {
+ var testRti = this,
+ unstarred = testRti._kind === 6 ? testRti._primary : testRti;
+ return unstarred._primary._is(object) || A.Rti__getFutureFromFutureOr(init.typeUniverse, unstarred)._is(object);
+ },
+ _isObject(object) {
+ return object != null;
+ },
+ _asObject(object) {
+ if (object != null)
+ return object;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "Object"));
+ },
+ _isTop(object) {
+ return true;
+ },
+ _asTop(object) {
+ return object;
+ },
+ _isNever(object) {
+ return false;
+ },
+ _isBool(object) {
+ return true === object || false === object;
+ },
+ _asBool(object) {
+ if (true === object)
+ return true;
+ if (false === object)
+ return false;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "bool"));
+ },
+ _asBoolS(object) {
+ if (true === object)
+ return true;
+ if (false === object)
+ return false;
+ if (object == null)
+ return object;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "bool"));
+ },
+ _asBoolQ(object) {
+ if (true === object)
+ return true;
+ if (false === object)
+ return false;
+ if (object == null)
+ return object;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "bool?"));
+ },
+ _asDouble(object) {
+ if (typeof object == "number")
+ return object;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "double"));
+ },
+ _asDoubleS(object) {
+ if (typeof object == "number")
+ return object;
+ if (object == null)
+ return object;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "double"));
+ },
+ _asDoubleQ(object) {
+ if (typeof object == "number")
+ return object;
+ if (object == null)
+ return object;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "double?"));
+ },
+ _isInt(object) {
+ return typeof object == "number" && Math.floor(object) === object;
+ },
+ _asInt(object) {
+ if (typeof object == "number" && Math.floor(object) === object)
+ return object;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "int"));
+ },
+ _asIntS(object) {
+ if (typeof object == "number" && Math.floor(object) === object)
+ return object;
+ if (object == null)
+ return object;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "int"));
+ },
+ _asIntQ(object) {
+ if (typeof object == "number" && Math.floor(object) === object)
+ return object;
+ if (object == null)
+ return object;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "int?"));
+ },
+ _isNum(object) {
+ return typeof object == "number";
+ },
+ _asNum(object) {
+ if (typeof object == "number")
+ return object;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "num"));
+ },
+ _asNumS(object) {
+ if (typeof object == "number")
+ return object;
+ if (object == null)
+ return object;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "num"));
+ },
+ _asNumQ(object) {
+ if (typeof object == "number")
+ return object;
+ if (object == null)
+ return object;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "num?"));
+ },
+ _isString(object) {
+ return typeof object == "string";
+ },
+ _asString(object) {
+ if (typeof object == "string")
+ return object;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "String"));
+ },
+ _asStringS(object) {
+ if (typeof object == "string")
+ return object;
+ if (object == null)
+ return object;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "String"));
+ },
+ _asStringQ(object) {
+ if (typeof object == "string")
+ return object;
+ if (object == null)
+ return object;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "String?"));
+ },
+ _rtiArrayToString(array, genericContext) {
+ var s, sep, i;
+ for (s = "", sep = "", i = 0; i < array.length; ++i, sep = ", ")
+ s += sep + A._rtiToString(array[i], genericContext);
+ return s;
+ },
+ _recordRtiToString(recordType, genericContext) {
+ var fieldCount, names, namesIndex, s, comma, i,
+ partialShape = recordType._primary,
+ fields = recordType._rest;
+ if ("" === partialShape)
+ return "(" + A._rtiArrayToString(fields, genericContext) + ")";
+ fieldCount = fields.length;
+ names = partialShape.split(",");
+ namesIndex = names.length - fieldCount;
+ for (s = "(", comma = "", i = 0; i < fieldCount; ++i, comma = ", ") {
+ s += comma;
+ if (namesIndex === 0)
+ s += "{";
+ s += A._rtiToString(fields[i], genericContext);
+ if (namesIndex >= 0)
+ s += " " + names[namesIndex];
+ ++namesIndex;
+ }
+ return s + "})";
+ },
+ _functionRtiToString(functionType, genericContext, bounds) {
+ var boundsLength, outerContextLength, offset, i, t1, t2, typeParametersText, typeSep, t3, t4, boundRti, kind, parameters, requiredPositional, requiredPositionalLength, optionalPositional, optionalPositionalLength, named, namedLength, returnTypeText, argumentsText, sep, _s2_ = ", ";
+ if (bounds != null) {
+ boundsLength = bounds.length;
+ if (genericContext == null) {
+ genericContext = A._setArrayType([], type$.JSArray_String);
+ outerContextLength = null;
+ } else
+ outerContextLength = genericContext.length;
+ offset = genericContext.length;
+ for (i = boundsLength; i > 0; --i)
+ B.JSArray_methods.add$1(genericContext, "T" + (offset + i));
+ for (t1 = type$.nullable_Object, t2 = type$.legacy_Object, typeParametersText = "<", typeSep = "", i = 0; i < boundsLength; ++i, typeSep = _s2_) {
+ t3 = genericContext.length;
+ t4 = t3 - 1 - i;
+ if (!(t4 >= 0))
+ return A.ioore(genericContext, t4);
+ typeParametersText = B.JSString_methods.$add(typeParametersText + typeSep, genericContext[t4]);
+ boundRti = bounds[i];
+ kind = boundRti._kind;
+ if (!(kind === 2 || kind === 3 || kind === 4 || kind === 5 || boundRti === t1))
+ t3 = boundRti === t2;
+ else
+ t3 = true;
+ if (!t3)
+ typeParametersText += " extends " + A._rtiToString(boundRti, genericContext);
+ }
+ typeParametersText += ">";
+ } else {
+ typeParametersText = "";
+ outerContextLength = null;
+ }
+ t1 = functionType._primary;
+ parameters = functionType._rest;
+ requiredPositional = parameters._requiredPositional;
+ requiredPositionalLength = requiredPositional.length;
+ optionalPositional = parameters._optionalPositional;
+ optionalPositionalLength = optionalPositional.length;
+ named = parameters._named;
+ namedLength = named.length;
+ returnTypeText = A._rtiToString(t1, genericContext);
+ for (argumentsText = "", sep = "", i = 0; i < requiredPositionalLength; ++i, sep = _s2_)
+ argumentsText += sep + A._rtiToString(requiredPositional[i], genericContext);
+ if (optionalPositionalLength > 0) {
+ argumentsText += sep + "[";
+ for (sep = "", i = 0; i < optionalPositionalLength; ++i, sep = _s2_)
+ argumentsText += sep + A._rtiToString(optionalPositional[i], genericContext);
+ argumentsText += "]";
+ }
+ if (namedLength > 0) {
+ argumentsText += sep + "{";
+ for (sep = "", i = 0; i < namedLength; i += 3, sep = _s2_) {
+ argumentsText += sep;
+ if (named[i + 1])
+ argumentsText += "required ";
+ argumentsText += A._rtiToString(named[i + 2], genericContext) + " " + named[i];
+ }
+ argumentsText += "}";
+ }
+ if (outerContextLength != null) {
+ genericContext.toString;
+ genericContext.length = outerContextLength;
+ }
+ return typeParametersText + "(" + argumentsText + ") => " + returnTypeText;
+ },
+ _rtiToString(rti, genericContext) {
+ var questionArgument, s, argumentKind, $name, $arguments, t1, t2,
+ kind = rti._kind;
+ if (kind === 5)
+ return "erased";
+ if (kind === 2)
+ return "dynamic";
+ if (kind === 3)
+ return "void";
+ if (kind === 1)
+ return "Never";
+ if (kind === 4)
+ return "any";
+ if (kind === 6)
+ return A._rtiToString(rti._primary, genericContext);
+ if (kind === 7) {
+ questionArgument = rti._primary;
+ s = A._rtiToString(questionArgument, genericContext);
+ argumentKind = questionArgument._kind;
+ return (argumentKind === 12 || argumentKind === 13 ? "(" + s + ")" : s) + "?";
+ }
+ if (kind === 8)
+ return "FutureOr<" + A._rtiToString(rti._primary, genericContext) + ">";
+ if (kind === 9) {
+ $name = A._unminifyOrTag(rti._primary);
+ $arguments = rti._rest;
+ return $arguments.length > 0 ? $name + ("<" + A._rtiArrayToString($arguments, genericContext) + ">") : $name;
+ }
+ if (kind === 11)
+ return A._recordRtiToString(rti, genericContext);
+ if (kind === 12)
+ return A._functionRtiToString(rti, genericContext, null);
+ if (kind === 13)
+ return A._functionRtiToString(rti._primary, genericContext, rti._rest);
+ if (kind === 14) {
+ t1 = rti._primary;
+ t2 = genericContext.length;
+ t1 = t2 - 1 - t1;
+ if (!(t1 >= 0 && t1 < t2))
+ return A.ioore(genericContext, t1);
+ return genericContext[t1];
+ }
+ return "?";
+ },
+ _unminifyOrTag(rawClassName) {
+ var preserved = init.mangledGlobalNames[rawClassName];
+ if (preserved != null)
+ return preserved;
+ return rawClassName;
+ },
+ _Universe_findRule(universe, targetType) {
+ var rule = universe.tR[targetType];
+ for (; typeof rule == "string";)
+ rule = universe.tR[rule];
+ return rule;
+ },
+ _Universe_findErasedType(universe, cls) {
+ var $length, erased, $arguments, i, $interface,
+ t1 = universe.eT,
+ probe = t1[cls];
+ if (probe == null)
+ return A._Universe_eval(universe, cls, false);
+ else if (typeof probe == "number") {
+ $length = probe;
+ erased = A._Universe__lookupTerminalRti(universe, 5, "#");
+ $arguments = A._Utils_newArrayOrEmpty($length);
+ for (i = 0; i < $length; ++i)
+ $arguments[i] = erased;
+ $interface = A._Universe__lookupInterfaceRti(universe, cls, $arguments);
+ t1[cls] = $interface;
+ return $interface;
+ } else
+ return probe;
+ },
+ _Universe_addRules(universe, rules) {
+ return A._Utils_objectAssign(universe.tR, rules);
+ },
+ _Universe_addErasedTypes(universe, types) {
+ return A._Utils_objectAssign(universe.eT, types);
+ },
+ _Universe_eval(universe, recipe, normalize) {
+ var rti,
+ t1 = universe.eC,
+ probe = t1.get(recipe);
+ if (probe != null)
+ return probe;
+ rti = A._Parser_parse(A._Parser_create(universe, null, recipe, normalize));
+ t1.set(recipe, rti);
+ return rti;
+ },
+ _Universe_evalInEnvironment(universe, environment, recipe) {
+ var probe, rti,
+ cache = environment._evalCache;
+ if (cache == null)
+ cache = environment._evalCache = new Map();
+ probe = cache.get(recipe);
+ if (probe != null)
+ return probe;
+ rti = A._Parser_parse(A._Parser_create(universe, environment, recipe, true));
+ cache.set(recipe, rti);
+ return rti;
+ },
+ _Universe_bind(universe, environment, argumentsRti) {
+ var argumentsRecipe, probe, rti,
+ cache = environment._bindCache;
+ if (cache == null)
+ cache = environment._bindCache = new Map();
+ argumentsRecipe = argumentsRti._canonicalRecipe;
+ probe = cache.get(argumentsRecipe);
+ if (probe != null)
+ return probe;
+ rti = A._Universe__lookupBindingRti(universe, environment, argumentsRti._kind === 10 ? argumentsRti._rest : [argumentsRti]);
+ cache.set(argumentsRecipe, rti);
+ return rti;
+ },
+ _Universe__installTypeTests(universe, rti) {
+ rti._as = A._installSpecializedAsCheck;
+ rti._is = A._installSpecializedIsTest;
+ return rti;
+ },
+ _Universe__lookupTerminalRti(universe, kind, key) {
+ var rti, t1,
+ probe = universe.eC.get(key);
+ if (probe != null)
+ return probe;
+ rti = new A.Rti(null, null);
+ rti._kind = kind;
+ rti._canonicalRecipe = key;
+ t1 = A._Universe__installTypeTests(universe, rti);
+ universe.eC.set(key, t1);
+ return t1;
+ },
+ _Universe__lookupStarRti(universe, baseType, normalize) {
+ var t1,
+ key = baseType._canonicalRecipe + "*",
+ probe = universe.eC.get(key);
+ if (probe != null)
+ return probe;
+ t1 = A._Universe__createStarRti(universe, baseType, key, normalize);
+ universe.eC.set(key, t1);
+ return t1;
+ },
+ _Universe__createStarRti(universe, baseType, key, normalize) {
+ var baseKind, t1, rti;
+ if (normalize) {
+ baseKind = baseType._kind;
+ if (!A.isSoundTopType(baseType))
+ t1 = baseType === type$.Null || baseType === type$.JSNull || baseKind === 7 || baseKind === 6;
+ else
+ t1 = true;
+ if (t1)
+ return baseType;
+ }
+ rti = new A.Rti(null, null);
+ rti._kind = 6;
+ rti._primary = baseType;
+ rti._canonicalRecipe = key;
+ return A._Universe__installTypeTests(universe, rti);
+ },
+ _Universe__lookupQuestionRti(universe, baseType, normalize) {
+ var t1,
+ key = baseType._canonicalRecipe + "?",
+ probe = universe.eC.get(key);
+ if (probe != null)
+ return probe;
+ t1 = A._Universe__createQuestionRti(universe, baseType, key, normalize);
+ universe.eC.set(key, t1);
+ return t1;
+ },
+ _Universe__createQuestionRti(universe, baseType, key, normalize) {
+ var baseKind, t1, starArgument, rti;
+ if (normalize) {
+ baseKind = baseType._kind;
+ if (!A.isSoundTopType(baseType))
+ if (!(baseType === type$.Null || baseType === type$.JSNull))
+ if (baseKind !== 7)
+ t1 = baseKind === 8 && A.isNullable(baseType._primary);
+ else
+ t1 = true;
+ else
+ t1 = true;
+ else
+ t1 = true;
+ if (t1)
+ return baseType;
+ else if (baseKind === 1 || baseType === type$.legacy_Never)
+ return type$.Null;
+ else if (baseKind === 6) {
+ starArgument = baseType._primary;
+ if (starArgument._kind === 8 && A.isNullable(starArgument._primary))
+ return starArgument;
+ else
+ return A.Rti__getQuestionFromStar(universe, baseType);
+ }
+ }
+ rti = new A.Rti(null, null);
+ rti._kind = 7;
+ rti._primary = baseType;
+ rti._canonicalRecipe = key;
+ return A._Universe__installTypeTests(universe, rti);
+ },
+ _Universe__lookupFutureOrRti(universe, baseType, normalize) {
+ var t1,
+ key = baseType._canonicalRecipe + "/",
+ probe = universe.eC.get(key);
+ if (probe != null)
+ return probe;
+ t1 = A._Universe__createFutureOrRti(universe, baseType, key, normalize);
+ universe.eC.set(key, t1);
+ return t1;
+ },
+ _Universe__createFutureOrRti(universe, baseType, key, normalize) {
+ var t1, rti;
+ if (normalize) {
+ t1 = baseType._kind;
+ if (A.isSoundTopType(baseType) || baseType === type$.Object || baseType === type$.legacy_Object)
+ return baseType;
+ else if (t1 === 1)
+ return A._Universe__lookupInterfaceRti(universe, "Future", [baseType]);
+ else if (baseType === type$.Null || baseType === type$.JSNull)
+ return type$.nullable_Future_Null;
+ }
+ rti = new A.Rti(null, null);
+ rti._kind = 8;
+ rti._primary = baseType;
+ rti._canonicalRecipe = key;
+ return A._Universe__installTypeTests(universe, rti);
+ },
+ _Universe__lookupGenericFunctionParameterRti(universe, index) {
+ var rti, t1,
+ key = "" + index + "^",
+ probe = universe.eC.get(key);
+ if (probe != null)
+ return probe;
+ rti = new A.Rti(null, null);
+ rti._kind = 14;
+ rti._primary = index;
+ rti._canonicalRecipe = key;
+ t1 = A._Universe__installTypeTests(universe, rti);
+ universe.eC.set(key, t1);
+ return t1;
+ },
+ _Universe__canonicalRecipeJoin($arguments) {
+ var s, sep, i,
+ $length = $arguments.length;
+ for (s = "", sep = "", i = 0; i < $length; ++i, sep = ",")
+ s += sep + $arguments[i]._canonicalRecipe;
+ return s;
+ },
+ _Universe__canonicalRecipeJoinNamed($arguments) {
+ var s, sep, i, t1, nameSep,
+ $length = $arguments.length;
+ for (s = "", sep = "", i = 0; i < $length; i += 3, sep = ",") {
+ t1 = $arguments[i];
+ nameSep = $arguments[i + 1] ? "!" : ":";
+ s += sep + t1 + nameSep + $arguments[i + 2]._canonicalRecipe;
+ }
+ return s;
+ },
+ _Universe__lookupInterfaceRti(universe, $name, $arguments) {
+ var probe, rti, t1,
+ s = $name;
+ if ($arguments.length > 0)
+ s += "<" + A._Universe__canonicalRecipeJoin($arguments) + ">";
+ probe = universe.eC.get(s);
+ if (probe != null)
+ return probe;
+ rti = new A.Rti(null, null);
+ rti._kind = 9;
+ rti._primary = $name;
+ rti._rest = $arguments;
+ if ($arguments.length > 0)
+ rti._precomputed1 = $arguments[0];
+ rti._canonicalRecipe = s;
+ t1 = A._Universe__installTypeTests(universe, rti);
+ universe.eC.set(s, t1);
+ return t1;
+ },
+ _Universe__lookupBindingRti(universe, base, $arguments) {
+ var newBase, newArguments, key, probe, rti, t1;
+ if (base._kind === 10) {
+ newBase = base._primary;
+ newArguments = base._rest.concat($arguments);
+ } else {
+ newArguments = $arguments;
+ newBase = base;
+ }
+ key = newBase._canonicalRecipe + (";<" + A._Universe__canonicalRecipeJoin(newArguments) + ">");
+ probe = universe.eC.get(key);
+ if (probe != null)
+ return probe;
+ rti = new A.Rti(null, null);
+ rti._kind = 10;
+ rti._primary = newBase;
+ rti._rest = newArguments;
+ rti._canonicalRecipe = key;
+ t1 = A._Universe__installTypeTests(universe, rti);
+ universe.eC.set(key, t1);
+ return t1;
+ },
+ _Universe__lookupRecordRti(universe, partialShapeTag, fields) {
+ var rti, t1,
+ key = "+" + (partialShapeTag + "(" + A._Universe__canonicalRecipeJoin(fields) + ")"),
+ probe = universe.eC.get(key);
+ if (probe != null)
+ return probe;
+ rti = new A.Rti(null, null);
+ rti._kind = 11;
+ rti._primary = partialShapeTag;
+ rti._rest = fields;
+ rti._canonicalRecipe = key;
+ t1 = A._Universe__installTypeTests(universe, rti);
+ universe.eC.set(key, t1);
+ return t1;
+ },
+ _Universe__lookupFunctionRti(universe, returnType, parameters) {
+ var sep, key, probe, rti, t1,
+ s = returnType._canonicalRecipe,
+ requiredPositional = parameters._requiredPositional,
+ requiredPositionalLength = requiredPositional.length,
+ optionalPositional = parameters._optionalPositional,
+ optionalPositionalLength = optionalPositional.length,
+ named = parameters._named,
+ namedLength = named.length,
+ recipe = "(" + A._Universe__canonicalRecipeJoin(requiredPositional);
+ if (optionalPositionalLength > 0) {
+ sep = requiredPositionalLength > 0 ? "," : "";
+ recipe += sep + "[" + A._Universe__canonicalRecipeJoin(optionalPositional) + "]";
+ }
+ if (namedLength > 0) {
+ sep = requiredPositionalLength > 0 ? "," : "";
+ recipe += sep + "{" + A._Universe__canonicalRecipeJoinNamed(named) + "}";
+ }
+ key = s + (recipe + ")");
+ probe = universe.eC.get(key);
+ if (probe != null)
+ return probe;
+ rti = new A.Rti(null, null);
+ rti._kind = 12;
+ rti._primary = returnType;
+ rti._rest = parameters;
+ rti._canonicalRecipe = key;
+ t1 = A._Universe__installTypeTests(universe, rti);
+ universe.eC.set(key, t1);
+ return t1;
+ },
+ _Universe__lookupGenericFunctionRti(universe, baseFunctionType, bounds, normalize) {
+ var t1,
+ key = baseFunctionType._canonicalRecipe + ("<" + A._Universe__canonicalRecipeJoin(bounds) + ">"),
+ probe = universe.eC.get(key);
+ if (probe != null)
+ return probe;
+ t1 = A._Universe__createGenericFunctionRti(universe, baseFunctionType, bounds, key, normalize);
+ universe.eC.set(key, t1);
+ return t1;
+ },
+ _Universe__createGenericFunctionRti(universe, baseFunctionType, bounds, key, normalize) {
+ var $length, typeArguments, count, i, bound, substitutedBase, substitutedBounds, rti;
+ if (normalize) {
+ $length = bounds.length;
+ typeArguments = A._Utils_newArrayOrEmpty($length);
+ for (count = 0, i = 0; i < $length; ++i) {
+ bound = bounds[i];
+ if (bound._kind === 1) {
+ typeArguments[i] = bound;
+ ++count;
+ }
+ }
+ if (count > 0) {
+ substitutedBase = A._substitute(universe, baseFunctionType, typeArguments, 0);
+ substitutedBounds = A._substituteArray(universe, bounds, typeArguments, 0);
+ return A._Universe__lookupGenericFunctionRti(universe, substitutedBase, substitutedBounds, bounds !== substitutedBounds);
+ }
+ }
+ rti = new A.Rti(null, null);
+ rti._kind = 13;
+ rti._primary = baseFunctionType;
+ rti._rest = bounds;
+ rti._canonicalRecipe = key;
+ return A._Universe__installTypeTests(universe, rti);
+ },
+ _Parser_create(universe, environment, recipe, normalize) {
+ return {u: universe, e: environment, r: recipe, s: [], p: 0, n: normalize};
+ },
+ _Parser_parse(parser) {
+ var t2, i, ch, t3, array, end, item,
+ source = parser.r,
+ t1 = parser.s;
+ for (t2 = source.length, i = 0; i < t2;) {
+ ch = source.charCodeAt(i);
+ if (ch >= 48 && ch <= 57)
+ i = A._Parser_handleDigit(i + 1, ch, source, t1);
+ else if ((((ch | 32) >>> 0) - 97 & 65535) < 26 || ch === 95 || ch === 36 || ch === 124)
+ i = A._Parser_handleIdentifier(parser, i, source, t1, false);
+ else if (ch === 46)
+ i = A._Parser_handleIdentifier(parser, i, source, t1, true);
+ else {
+ ++i;
+ switch (ch) {
+ case 44:
+ break;
+ case 58:
+ t1.push(false);
+ break;
+ case 33:
+ t1.push(true);
+ break;
+ case 59:
+ t1.push(A._Parser_toType(parser.u, parser.e, t1.pop()));
+ break;
+ case 94:
+ t1.push(A._Universe__lookupGenericFunctionParameterRti(parser.u, t1.pop()));
+ break;
+ case 35:
+ t1.push(A._Universe__lookupTerminalRti(parser.u, 5, "#"));
+ break;
+ case 64:
+ t1.push(A._Universe__lookupTerminalRti(parser.u, 2, "@"));
+ break;
+ case 126:
+ t1.push(A._Universe__lookupTerminalRti(parser.u, 3, "~"));
+ break;
+ case 60:
+ t1.push(parser.p);
+ parser.p = t1.length;
+ break;
+ case 62:
+ A._Parser_handleTypeArguments(parser, t1);
+ break;
+ case 38:
+ A._Parser_handleExtendedOperations(parser, t1);
+ break;
+ case 42:
+ t3 = parser.u;
+ t1.push(A._Universe__lookupStarRti(t3, A._Parser_toType(t3, parser.e, t1.pop()), parser.n));
+ break;
+ case 63:
+ t3 = parser.u;
+ t1.push(A._Universe__lookupQuestionRti(t3, A._Parser_toType(t3, parser.e, t1.pop()), parser.n));
+ break;
+ case 47:
+ t3 = parser.u;
+ t1.push(A._Universe__lookupFutureOrRti(t3, A._Parser_toType(t3, parser.e, t1.pop()), parser.n));
+ break;
+ case 40:
+ t1.push(-3);
+ t1.push(parser.p);
+ parser.p = t1.length;
+ break;
+ case 41:
+ A._Parser_handleArguments(parser, t1);
+ break;
+ case 91:
+ t1.push(parser.p);
+ parser.p = t1.length;
+ break;
+ case 93:
+ array = t1.splice(parser.p);
+ A._Parser_toTypes(parser.u, parser.e, array);
+ parser.p = t1.pop();
+ t1.push(array);
+ t1.push(-1);
+ break;
+ case 123:
+ t1.push(parser.p);
+ parser.p = t1.length;
+ break;
+ case 125:
+ array = t1.splice(parser.p);
+ A._Parser_toTypesNamed(parser.u, parser.e, array);
+ parser.p = t1.pop();
+ t1.push(array);
+ t1.push(-2);
+ break;
+ case 43:
+ end = source.indexOf("(", i);
+ t1.push(source.substring(i, end));
+ t1.push(-4);
+ t1.push(parser.p);
+ parser.p = t1.length;
+ i = end + 1;
+ break;
+ default:
+ throw "Bad character " + ch;
+ }
+ }
+ }
+ item = t1.pop();
+ return A._Parser_toType(parser.u, parser.e, item);
+ },
+ _Parser_handleDigit(i, digit, source, stack) {
+ var t1, ch,
+ value = digit - 48;
+ for (t1 = source.length; i < t1; ++i) {
+ ch = source.charCodeAt(i);
+ if (!(ch >= 48 && ch <= 57))
+ break;
+ value = value * 10 + (ch - 48);
+ }
+ stack.push(value);
+ return i;
+ },
+ _Parser_handleIdentifier(parser, start, source, stack, hasPeriod) {
+ var t1, ch, t2, string, environment, recipe,
+ i = start + 1;
+ for (t1 = source.length; i < t1; ++i) {
+ ch = source.charCodeAt(i);
+ if (ch === 46) {
+ if (hasPeriod)
+ break;
+ hasPeriod = true;
+ } else {
+ if (!((((ch | 32) >>> 0) - 97 & 65535) < 26 || ch === 95 || ch === 36 || ch === 124))
+ t2 = ch >= 48 && ch <= 57;
+ else
+ t2 = true;
+ if (!t2)
+ break;
+ }
+ }
+ string = source.substring(start, i);
+ if (hasPeriod) {
+ t1 = parser.u;
+ environment = parser.e;
+ if (environment._kind === 10)
+ environment = environment._primary;
+ recipe = A._Universe_findRule(t1, environment._primary)[string];
+ if (recipe == null)
+ A.throwExpression('No "' + string + '" in "' + A.Rti__getCanonicalRecipe(environment) + '"');
+ stack.push(A._Universe_evalInEnvironment(t1, environment, recipe));
+ } else
+ stack.push(string);
+ return i;
+ },
+ _Parser_handleTypeArguments(parser, stack) {
+ var base,
+ t1 = parser.u,
+ $arguments = A._Parser_collectArray(parser, stack),
+ head = stack.pop();
+ if (typeof head == "string")
+ stack.push(A._Universe__lookupInterfaceRti(t1, head, $arguments));
+ else {
+ base = A._Parser_toType(t1, parser.e, head);
+ switch (base._kind) {
+ case 12:
+ stack.push(A._Universe__lookupGenericFunctionRti(t1, base, $arguments, parser.n));
+ break;
+ default:
+ stack.push(A._Universe__lookupBindingRti(t1, base, $arguments));
+ break;
+ }
+ }
+ },
+ _Parser_handleArguments(parser, stack) {
+ var optionalPositional, named, requiredPositional, returnType, parameters, _null = null,
+ t1 = parser.u,
+ head = stack.pop();
+ if (typeof head == "number")
+ switch (head) {
+ case -1:
+ optionalPositional = stack.pop();
+ named = _null;
+ break;
+ case -2:
+ named = stack.pop();
+ optionalPositional = _null;
+ break;
+ default:
+ stack.push(head);
+ named = _null;
+ optionalPositional = named;
+ break;
+ }
+ else {
+ stack.push(head);
+ named = _null;
+ optionalPositional = named;
+ }
+ requiredPositional = A._Parser_collectArray(parser, stack);
+ head = stack.pop();
+ switch (head) {
+ case -3:
+ head = stack.pop();
+ if (optionalPositional == null)
+ optionalPositional = t1.sEA;
+ if (named == null)
+ named = t1.sEA;
+ returnType = A._Parser_toType(t1, parser.e, head);
+ parameters = new A._FunctionParameters();
+ parameters._requiredPositional = requiredPositional;
+ parameters._optionalPositional = optionalPositional;
+ parameters._named = named;
+ stack.push(A._Universe__lookupFunctionRti(t1, returnType, parameters));
+ return;
+ case -4:
+ stack.push(A._Universe__lookupRecordRti(t1, stack.pop(), requiredPositional));
+ return;
+ default:
+ throw A.wrapException(A.AssertionError$("Unexpected state under `()`: " + A.S(head)));
+ }
+ },
+ _Parser_handleExtendedOperations(parser, stack) {
+ var $top = stack.pop();
+ if (0 === $top) {
+ stack.push(A._Universe__lookupTerminalRti(parser.u, 1, "0&"));
+ return;
+ }
+ if (1 === $top) {
+ stack.push(A._Universe__lookupTerminalRti(parser.u, 4, "1&"));
+ return;
+ }
+ throw A.wrapException(A.AssertionError$("Unexpected extended operation " + A.S($top)));
+ },
+ _Parser_collectArray(parser, stack) {
+ var array = stack.splice(parser.p);
+ A._Parser_toTypes(parser.u, parser.e, array);
+ parser.p = stack.pop();
+ return array;
+ },
+ _Parser_toType(universe, environment, item) {
+ if (typeof item == "string")
+ return A._Universe__lookupInterfaceRti(universe, item, universe.sEA);
+ else if (typeof item == "number") {
+ environment.toString;
+ return A._Parser_indexToType(universe, environment, item);
+ } else
+ return item;
+ },
+ _Parser_toTypes(universe, environment, items) {
+ var i,
+ $length = items.length;
+ for (i = 0; i < $length; ++i)
+ items[i] = A._Parser_toType(universe, environment, items[i]);
+ },
+ _Parser_toTypesNamed(universe, environment, items) {
+ var i,
+ $length = items.length;
+ for (i = 2; i < $length; i += 3)
+ items[i] = A._Parser_toType(universe, environment, items[i]);
+ },
+ _Parser_indexToType(universe, environment, index) {
+ var typeArguments, len,
+ kind = environment._kind;
+ if (kind === 10) {
+ if (index === 0)
+ return environment._primary;
+ typeArguments = environment._rest;
+ len = typeArguments.length;
+ if (index <= len)
+ return typeArguments[index - 1];
+ index -= len;
+ environment = environment._primary;
+ kind = environment._kind;
+ } else if (index === 0)
+ return environment;
+ if (kind !== 9)
+ throw A.wrapException(A.AssertionError$("Indexed base must be an interface type"));
+ typeArguments = environment._rest;
+ if (index <= typeArguments.length)
+ return typeArguments[index - 1];
+ throw A.wrapException(A.AssertionError$("Bad index " + index + " for " + environment.toString$0(0)));
+ },
+ isSubtype(universe, s, t) {
+ var result,
+ sCache = s._isSubtypeCache;
+ if (sCache == null)
+ sCache = s._isSubtypeCache = new Map();
+ result = sCache.get(t);
+ if (result == null) {
+ result = A._isSubtype(universe, s, null, t, null, false) ? 1 : 0;
+ sCache.set(t, result);
+ }
+ if (0 === result)
+ return false;
+ if (1 === result)
+ return true;
+ return true;
+ },
+ _isSubtype(universe, s, sEnv, t, tEnv, isLegacy) {
+ var t1, sKind, leftTypeVariable, tKind, t2, sBounds, tBounds, sLength, i, sBound, tBound;
+ if (s === t)
+ return true;
+ if (!A.isSoundTopType(t))
+ t1 = t === type$.legacy_Object;
+ else
+ t1 = true;
+ if (t1)
+ return true;
+ sKind = s._kind;
+ if (sKind === 4)
+ return true;
+ if (A.isSoundTopType(s))
+ return false;
+ t1 = s._kind;
+ if (t1 === 1)
+ return true;
+ leftTypeVariable = sKind === 14;
+ if (leftTypeVariable)
+ if (A._isSubtype(universe, sEnv[s._primary], sEnv, t, tEnv, false))
+ return true;
+ tKind = t._kind;
+ t1 = s === type$.Null || s === type$.JSNull;
+ if (t1) {
+ if (tKind === 8)
+ return A._isSubtype(universe, s, sEnv, t._primary, tEnv, false);
+ return t === type$.Null || t === type$.JSNull || tKind === 7 || tKind === 6;
+ }
+ if (t === type$.Object) {
+ if (sKind === 8)
+ return A._isSubtype(universe, s._primary, sEnv, t, tEnv, false);
+ if (sKind === 6)
+ return A._isSubtype(universe, s._primary, sEnv, t, tEnv, false);
+ return sKind !== 7;
+ }
+ if (sKind === 6)
+ return A._isSubtype(universe, s._primary, sEnv, t, tEnv, false);
+ if (tKind === 6) {
+ t1 = A.Rti__getQuestionFromStar(universe, t);
+ return A._isSubtype(universe, s, sEnv, t1, tEnv, false);
+ }
+ if (sKind === 8) {
+ if (!A._isSubtype(universe, s._primary, sEnv, t, tEnv, false))
+ return false;
+ return A._isSubtype(universe, A.Rti__getFutureFromFutureOr(universe, s), sEnv, t, tEnv, false);
+ }
+ if (sKind === 7) {
+ t1 = A._isSubtype(universe, type$.Null, sEnv, t, tEnv, false);
+ return t1 && A._isSubtype(universe, s._primary, sEnv, t, tEnv, false);
+ }
+ if (tKind === 8) {
+ if (A._isSubtype(universe, s, sEnv, t._primary, tEnv, false))
+ return true;
+ return A._isSubtype(universe, s, sEnv, A.Rti__getFutureFromFutureOr(universe, t), tEnv, false);
+ }
+ if (tKind === 7) {
+ t1 = A._isSubtype(universe, s, sEnv, type$.Null, tEnv, false);
+ return t1 || A._isSubtype(universe, s, sEnv, t._primary, tEnv, false);
+ }
+ if (leftTypeVariable)
+ return false;
+ t1 = sKind !== 12;
+ if ((!t1 || sKind === 13) && t === type$.Function)
+ return true;
+ t2 = sKind === 11;
+ if (t2 && t === type$.Record)
+ return true;
+ if (tKind === 13) {
+ if (s === type$.JavaScriptFunction)
+ return true;
+ if (sKind !== 13)
+ return false;
+ sBounds = s._rest;
+ tBounds = t._rest;
+ sLength = sBounds.length;
+ if (sLength !== tBounds.length)
+ return false;
+ sEnv = sEnv == null ? sBounds : sBounds.concat(sEnv);
+ tEnv = tEnv == null ? tBounds : tBounds.concat(tEnv);
+ for (i = 0; i < sLength; ++i) {
+ sBound = sBounds[i];
+ tBound = tBounds[i];
+ if (!A._isSubtype(universe, sBound, sEnv, tBound, tEnv, false) || !A._isSubtype(universe, tBound, tEnv, sBound, sEnv, false))
+ return false;
+ }
+ return A._isFunctionSubtype(universe, s._primary, sEnv, t._primary, tEnv, false);
+ }
+ if (tKind === 12) {
+ if (s === type$.JavaScriptFunction)
+ return true;
+ if (t1)
+ return false;
+ return A._isFunctionSubtype(universe, s, sEnv, t, tEnv, false);
+ }
+ if (sKind === 9) {
+ if (tKind !== 9)
+ return false;
+ return A._isInterfaceSubtype(universe, s, sEnv, t, tEnv, false);
+ }
+ if (t2 && tKind === 11)
+ return A._isRecordSubtype(universe, s, sEnv, t, tEnv, false);
+ return false;
+ },
+ _isFunctionSubtype(universe, s, sEnv, t, tEnv, isLegacy) {
+ var sParameters, tParameters, sRequiredPositional, tRequiredPositional, sRequiredPositionalLength, tRequiredPositionalLength, requiredPositionalDelta, sOptionalPositional, tOptionalPositional, sOptionalPositionalLength, tOptionalPositionalLength, i, t1, sNamed, tNamed, sNamedLength, tNamedLength, sIndex, tIndex, tName, sName, sIsRequired;
+ if (!A._isSubtype(universe, s._primary, sEnv, t._primary, tEnv, false))
+ return false;
+ sParameters = s._rest;
+ tParameters = t._rest;
+ sRequiredPositional = sParameters._requiredPositional;
+ tRequiredPositional = tParameters._requiredPositional;
+ sRequiredPositionalLength = sRequiredPositional.length;
+ tRequiredPositionalLength = tRequiredPositional.length;
+ if (sRequiredPositionalLength > tRequiredPositionalLength)
+ return false;
+ requiredPositionalDelta = tRequiredPositionalLength - sRequiredPositionalLength;
+ sOptionalPositional = sParameters._optionalPositional;
+ tOptionalPositional = tParameters._optionalPositional;
+ sOptionalPositionalLength = sOptionalPositional.length;
+ tOptionalPositionalLength = tOptionalPositional.length;
+ if (sRequiredPositionalLength + sOptionalPositionalLength < tRequiredPositionalLength + tOptionalPositionalLength)
+ return false;
+ for (i = 0; i < sRequiredPositionalLength; ++i) {
+ t1 = sRequiredPositional[i];
+ if (!A._isSubtype(universe, tRequiredPositional[i], tEnv, t1, sEnv, false))
+ return false;
+ }
+ for (i = 0; i < requiredPositionalDelta; ++i) {
+ t1 = sOptionalPositional[i];
+ if (!A._isSubtype(universe, tRequiredPositional[sRequiredPositionalLength + i], tEnv, t1, sEnv, false))
+ return false;
+ }
+ for (i = 0; i < tOptionalPositionalLength; ++i) {
+ t1 = sOptionalPositional[requiredPositionalDelta + i];
+ if (!A._isSubtype(universe, tOptionalPositional[i], tEnv, t1, sEnv, false))
+ return false;
+ }
+ sNamed = sParameters._named;
+ tNamed = tParameters._named;
+ sNamedLength = sNamed.length;
+ tNamedLength = tNamed.length;
+ for (sIndex = 0, tIndex = 0; tIndex < tNamedLength; tIndex += 3) {
+ tName = tNamed[tIndex];
+ for (; true;) {
+ if (sIndex >= sNamedLength)
+ return false;
+ sName = sNamed[sIndex];
+ sIndex += 3;
+ if (tName < sName)
+ return false;
+ sIsRequired = sNamed[sIndex - 2];
+ if (sName < tName) {
+ if (sIsRequired)
+ return false;
+ continue;
+ }
+ t1 = tNamed[tIndex + 1];
+ if (sIsRequired && !t1)
+ return false;
+ t1 = sNamed[sIndex - 1];
+ if (!A._isSubtype(universe, tNamed[tIndex + 2], tEnv, t1, sEnv, false))
+ return false;
+ break;
+ }
+ }
+ for (; sIndex < sNamedLength;) {
+ if (sNamed[sIndex + 1])
+ return false;
+ sIndex += 3;
+ }
+ return true;
+ },
+ _isInterfaceSubtype(universe, s, sEnv, t, tEnv, isLegacy) {
+ var rule, recipes, $length, supertypeArgs, i,
+ sName = s._primary,
+ tName = t._primary;
+ for (; sName !== tName;) {
+ rule = universe.tR[sName];
+ if (rule == null)
+ return false;
+ if (typeof rule == "string") {
+ sName = rule;
+ continue;
+ }
+ recipes = rule[tName];
+ if (recipes == null)
+ return false;
+ $length = recipes.length;
+ supertypeArgs = $length > 0 ? new Array($length) : init.typeUniverse.sEA;
+ for (i = 0; i < $length; ++i)
+ supertypeArgs[i] = A._Universe_evalInEnvironment(universe, s, recipes[i]);
+ return A._areArgumentsSubtypes(universe, supertypeArgs, null, sEnv, t._rest, tEnv, false);
+ }
+ return A._areArgumentsSubtypes(universe, s._rest, null, sEnv, t._rest, tEnv, false);
+ },
+ _areArgumentsSubtypes(universe, sArgs, sVariances, sEnv, tArgs, tEnv, isLegacy) {
+ var i,
+ $length = sArgs.length;
+ for (i = 0; i < $length; ++i)
+ if (!A._isSubtype(universe, sArgs[i], sEnv, tArgs[i], tEnv, false))
+ return false;
+ return true;
+ },
+ _isRecordSubtype(universe, s, sEnv, t, tEnv, isLegacy) {
+ var i,
+ sFields = s._rest,
+ tFields = t._rest,
+ sCount = sFields.length;
+ if (sCount !== tFields.length)
+ return false;
+ if (s._primary !== t._primary)
+ return false;
+ for (i = 0; i < sCount; ++i)
+ if (!A._isSubtype(universe, sFields[i], sEnv, tFields[i], tEnv, false))
+ return false;
+ return true;
+ },
+ isNullable(t) {
+ var t1,
+ kind = t._kind;
+ if (!(t === type$.Null || t === type$.JSNull))
+ if (!A.isSoundTopType(t))
+ if (kind !== 7)
+ if (!(kind === 6 && A.isNullable(t._primary)))
+ t1 = kind === 8 && A.isNullable(t._primary);
+ else
+ t1 = true;
+ else
+ t1 = true;
+ else
+ t1 = true;
+ else
+ t1 = true;
+ return t1;
+ },
+ isDefinitelyTopType(t) {
+ var t1;
+ if (!A.isSoundTopType(t))
+ t1 = t === type$.legacy_Object;
+ else
+ t1 = true;
+ return t1;
+ },
+ isSoundTopType(t) {
+ var kind = t._kind;
+ return kind === 2 || kind === 3 || kind === 4 || kind === 5 || t === type$.nullable_Object;
+ },
+ _Utils_objectAssign(o, other) {
+ var i, key,
+ keys = Object.keys(other),
+ $length = keys.length;
+ for (i = 0; i < $length; ++i) {
+ key = keys[i];
+ o[key] = other[key];
+ }
+ },
+ _Utils_newArrayOrEmpty($length) {
+ return $length > 0 ? new Array($length) : init.typeUniverse.sEA;
+ },
+ Rti: function Rti(t0, t1) {
+ var _ = this;
+ _._as = t0;
+ _._is = t1;
+ _._cachedRuntimeType = _._specializedTestResource = _._isSubtypeCache = _._precomputed1 = null;
+ _._kind = 0;
+ _._canonicalRecipe = _._bindCache = _._evalCache = _._rest = _._primary = null;
+ },
+ _FunctionParameters: function _FunctionParameters() {
+ this._named = this._optionalPositional = this._requiredPositional = null;
+ },
+ _Type: function _Type(t0) {
+ this._rti = t0;
+ },
+ _Error: function _Error() {
+ },
+ _TypeError: function _TypeError(t0) {
+ this.__rti$_message = t0;
+ },
+ _AsyncRun__initializeScheduleImmediate() {
+ var div, span, t1 = {};
+ if (self.scheduleImmediate != null)
+ return A.async__AsyncRun__scheduleImmediateJsOverride$closure();
+ if (self.MutationObserver != null && self.document != null) {
+ div = self.document.createElement("div");
+ span = self.document.createElement("span");
+ t1.storedCallback = null;
+ new self.MutationObserver(A.convertDartClosureToJS(new A._AsyncRun__initializeScheduleImmediate_internalCallback(t1), 1)).observe(div, {childList: true});
+ return new A._AsyncRun__initializeScheduleImmediate_closure(t1, div, span);
+ } else if (self.setImmediate != null)
+ return A.async__AsyncRun__scheduleImmediateWithSetImmediate$closure();
+ return A.async__AsyncRun__scheduleImmediateWithTimer$closure();
+ },
+ _AsyncRun__scheduleImmediateJsOverride(callback) {
+ self.scheduleImmediate(A.convertDartClosureToJS(new A._AsyncRun__scheduleImmediateJsOverride_internalCallback(type$.void_Function._as(callback)), 0));
+ },
+ _AsyncRun__scheduleImmediateWithSetImmediate(callback) {
+ self.setImmediate(A.convertDartClosureToJS(new A._AsyncRun__scheduleImmediateWithSetImmediate_internalCallback(type$.void_Function._as(callback)), 0));
+ },
+ _AsyncRun__scheduleImmediateWithTimer(callback) {
+ A.Timer__createTimer(B.Duration_0, type$.void_Function._as(callback));
+ },
+ Timer__createTimer(duration, callback) {
+ return A._TimerImpl$(duration._duration / 1000 | 0, callback);
+ },
+ _TimerImpl$(milliseconds, callback) {
+ var t1 = new A._TimerImpl();
+ t1._TimerImpl$2(milliseconds, callback);
+ return t1;
+ },
+ _makeAsyncAwaitCompleter($T) {
+ return new A._AsyncAwaitCompleter(new A._Future($.Zone__current, $T._eval$1("_Future<0>")), $T._eval$1("_AsyncAwaitCompleter<0>"));
+ },
+ _asyncStartSync(bodyFunction, completer) {
+ bodyFunction.call$2(0, null);
+ completer.isSync = true;
+ return completer._future;
+ },
+ _asyncAwait(object, bodyFunction) {
+ A._awaitOnObject(object, bodyFunction);
+ },
+ _asyncReturn(object, completer) {
+ completer.complete$1(object);
+ },
+ _asyncRethrow(object, completer) {
+ completer.completeError$2(A.unwrapException(object), A.getTraceFromException(object));
+ },
+ _awaitOnObject(object, bodyFunction) {
+ var t1, future,
+ thenCallback = new A._awaitOnObject_closure(bodyFunction),
+ errorCallback = new A._awaitOnObject_closure0(bodyFunction);
+ if (object instanceof A._Future)
+ object._thenAwait$1$2(thenCallback, errorCallback, type$.dynamic);
+ else {
+ t1 = type$.dynamic;
+ if (object instanceof A._Future)
+ object.then$1$2$onError(thenCallback, errorCallback, t1);
+ else {
+ future = new A._Future($.Zone__current, type$._Future_dynamic);
+ future._state = 8;
+ future._resultOrListeners = object;
+ future._thenAwait$1$2(thenCallback, errorCallback, t1);
+ }
+ }
+ },
+ _wrapJsFunctionForAsync($function) {
+ var $protected = function(fn, ERROR) {
+ return function(errorCode, result) {
+ while (true) {
+ try {
+ fn(errorCode, result);
+ break;
+ } catch (error) {
+ result = error;
+ errorCode = ERROR;
+ }
+ }
+ };
+ }($function, 1);
+ return $.Zone__current.registerBinaryCallback$3$1(new A._wrapJsFunctionForAsync_closure($protected), type$.void, type$.int, type$.dynamic);
+ },
+ AsyncError$(error, stackTrace) {
+ var t1 = A.checkNotNullable(error, "error", type$.Object);
+ return new A.AsyncError(t1, stackTrace == null ? A.AsyncError_defaultStackTrace(error) : stackTrace);
+ },
+ AsyncError_defaultStackTrace(error) {
+ var stackTrace;
+ if (type$.Error._is(error)) {
+ stackTrace = error.get$stackTrace();
+ if (stackTrace != null)
+ return stackTrace;
+ }
+ return B._StringStackTrace_3uE;
+ },
+ Future_Future$sync(computation, $T) {
+ var result, error, stackTrace, future, replacement, t1, exception;
+ try {
+ result = computation.call$0();
+ t1 = $T._eval$1("Future<0>")._is(result) ? result : A._Future$value(result, $T);
+ return t1;
+ } catch (exception) {
+ error = A.unwrapException(exception);
+ stackTrace = A.getTraceFromException(exception);
+ future = new A._Future($.Zone__current, $T._eval$1("_Future<0>"));
+ type$.Object._as(error);
+ type$.nullable_StackTrace._as(stackTrace);
+ replacement = null;
+ if (replacement != null)
+ future._asyncCompleteError$2(replacement.get$error(), replacement.get$stackTrace());
+ else
+ future._asyncCompleteError$2(error, stackTrace);
+ return future;
+ }
+ },
+ Future_Future$value(value, $T) {
+ var t1 = value == null ? $T._as(value) : value,
+ t2 = new A._Future($.Zone__current, $T._eval$1("_Future<0>"));
+ t2._asyncComplete$1(t1);
+ return t2;
+ },
+ Completer_Completer($T) {
+ return new A._AsyncCompleter(new A._Future($.Zone__current, $T._eval$1("_Future<0>")), $T._eval$1("_AsyncCompleter<0>"));
+ },
+ _Future$value(value, $T) {
+ var t1 = new A._Future($.Zone__current, $T._eval$1("_Future<0>"));
+ $T._as(value);
+ t1._state = 8;
+ t1._resultOrListeners = value;
+ return t1;
+ },
+ _Future__chainCoreFutureSync(source, target) {
+ var t1, t2, listeners;
+ for (t1 = type$._Future_dynamic; t2 = source._state, (t2 & 4) !== 0;)
+ source = t1._as(source._resultOrListeners);
+ if ((t2 & 24) !== 0) {
+ listeners = target._removeListeners$0();
+ target._cloneResult$1(source);
+ A._Future__propagateToListeners(target, listeners);
+ } else {
+ listeners = type$.nullable__FutureListener_dynamic_dynamic._as(target._resultOrListeners);
+ target._setChained$1(source);
+ source._prependListeners$1(listeners);
+ }
+ },
+ _Future__chainCoreFutureAsync(source, target) {
+ var t2, t3, listeners, _box_0 = {},
+ t1 = _box_0.source = source;
+ for (t2 = type$._Future_dynamic; t3 = t1._state, (t3 & 4) !== 0; t1 = source) {
+ source = t2._as(t1._resultOrListeners);
+ _box_0.source = source;
+ }
+ if ((t3 & 24) === 0) {
+ listeners = type$.nullable__FutureListener_dynamic_dynamic._as(target._resultOrListeners);
+ target._setChained$1(t1);
+ _box_0.source._prependListeners$1(listeners);
+ return;
+ }
+ if ((t3 & 16) === 0 && target._resultOrListeners == null) {
+ target._cloneResult$1(t1);
+ return;
+ }
+ target._state ^= 2;
+ A._rootScheduleMicrotask(null, null, target._zone, type$.void_Function._as(new A._Future__chainCoreFutureAsync_closure(_box_0, target)));
+ },
+ _Future__propagateToListeners(source, listeners) {
+ var t2, t3, t4, _box_0, t5, t6, hasError, asyncError, nextListener, nextListener0, sourceResult, t7, zone, oldZone, result, current, _box_1 = {},
+ t1 = _box_1.source = source;
+ for (t2 = type$.AsyncError, t3 = type$.nullable__FutureListener_dynamic_dynamic, t4 = type$.Future_dynamic; true;) {
+ _box_0 = {};
+ t5 = t1._state;
+ t6 = (t5 & 16) === 0;
+ hasError = !t6;
+ if (listeners == null) {
+ if (hasError && (t5 & 1) === 0) {
+ asyncError = t2._as(t1._resultOrListeners);
+ A._rootHandleError(asyncError.error, asyncError.stackTrace);
+ }
+ return;
+ }
+ _box_0.listener = listeners;
+ nextListener = listeners._nextListener;
+ for (t1 = listeners; nextListener != null; t1 = nextListener, nextListener = nextListener0) {
+ t1._nextListener = null;
+ A._Future__propagateToListeners(_box_1.source, t1);
+ _box_0.listener = nextListener;
+ nextListener0 = nextListener._nextListener;
+ }
+ t5 = _box_1.source;
+ sourceResult = t5._resultOrListeners;
+ _box_0.listenerHasError = hasError;
+ _box_0.listenerValueOrError = sourceResult;
+ if (t6) {
+ t7 = t1.state;
+ t7 = (t7 & 1) !== 0 || (t7 & 15) === 8;
+ } else
+ t7 = true;
+ if (t7) {
+ zone = t1.result._zone;
+ if (hasError) {
+ t5 = t5._zone === zone;
+ t5 = !(t5 || t5);
+ } else
+ t5 = false;
+ if (t5) {
+ t2._as(sourceResult);
+ A._rootHandleError(sourceResult.error, sourceResult.stackTrace);
+ return;
+ }
+ oldZone = $.Zone__current;
+ if (oldZone !== zone)
+ $.Zone__current = zone;
+ else
+ oldZone = null;
+ t1 = t1.state;
+ if ((t1 & 15) === 8)
+ new A._Future__propagateToListeners_handleWhenCompleteCallback(_box_0, _box_1, hasError).call$0();
+ else if (t6) {
+ if ((t1 & 1) !== 0)
+ new A._Future__propagateToListeners_handleValueCallback(_box_0, sourceResult).call$0();
+ } else if ((t1 & 2) !== 0)
+ new A._Future__propagateToListeners_handleError(_box_1, _box_0).call$0();
+ if (oldZone != null)
+ $.Zone__current = oldZone;
+ t1 = _box_0.listenerValueOrError;
+ if (t1 instanceof A._Future) {
+ t5 = _box_0.listener.$ti;
+ t5 = t5._eval$1("Future<2>")._is(t1) || !t5._rest[1]._is(t1);
+ } else
+ t5 = false;
+ if (t5) {
+ t4._as(t1);
+ result = _box_0.listener.result;
+ if ((t1._state & 24) !== 0) {
+ current = t3._as(result._resultOrListeners);
+ result._resultOrListeners = null;
+ listeners = result._reverseListeners$1(current);
+ result._state = t1._state & 30 | result._state & 1;
+ result._resultOrListeners = t1._resultOrListeners;
+ _box_1.source = t1;
+ continue;
+ } else
+ A._Future__chainCoreFutureSync(t1, result);
+ return;
+ }
+ }
+ result = _box_0.listener.result;
+ current = t3._as(result._resultOrListeners);
+ result._resultOrListeners = null;
+ listeners = result._reverseListeners$1(current);
+ t1 = _box_0.listenerHasError;
+ t5 = _box_0.listenerValueOrError;
+ if (!t1) {
+ result.$ti._precomputed1._as(t5);
+ result._state = 8;
+ result._resultOrListeners = t5;
+ } else {
+ t2._as(t5);
+ result._state = result._state & 1 | 16;
+ result._resultOrListeners = t5;
+ }
+ _box_1.source = result;
+ t1 = result;
+ }
+ },
+ _registerErrorHandler(errorHandler, zone) {
+ var t1;
+ if (type$.dynamic_Function_Object_StackTrace._is(errorHandler))
+ return zone.registerBinaryCallback$3$1(errorHandler, type$.dynamic, type$.Object, type$.StackTrace);
+ t1 = type$.dynamic_Function_Object;
+ if (t1._is(errorHandler))
+ return t1._as(errorHandler);
+ throw A.wrapException(A.ArgumentError$value(errorHandler, "onError", string$.Error_));
+ },
+ _microtaskLoop() {
+ var entry, next;
+ for (entry = $._nextCallback; entry != null; entry = $._nextCallback) {
+ $._lastPriorityCallback = null;
+ next = entry.next;
+ $._nextCallback = next;
+ if (next == null)
+ $._lastCallback = null;
+ entry.callback.call$0();
+ }
+ },
+ _startMicrotaskLoop() {
+ $._isInCallbackLoop = true;
+ try {
+ A._microtaskLoop();
+ } finally {
+ $._lastPriorityCallback = null;
+ $._isInCallbackLoop = false;
+ if ($._nextCallback != null)
+ $.$get$_AsyncRun__scheduleImmediateClosure().call$1(A.async___startMicrotaskLoop$closure());
+ }
+ },
+ _scheduleAsyncCallback(callback) {
+ var newEntry = new A._AsyncCallbackEntry(callback),
+ lastCallback = $._lastCallback;
+ if (lastCallback == null) {
+ $._nextCallback = $._lastCallback = newEntry;
+ if (!$._isInCallbackLoop)
+ $.$get$_AsyncRun__scheduleImmediateClosure().call$1(A.async___startMicrotaskLoop$closure());
+ } else
+ $._lastCallback = lastCallback.next = newEntry;
+ },
+ _schedulePriorityAsyncCallback(callback) {
+ var entry, lastPriorityCallback, next,
+ t1 = $._nextCallback;
+ if (t1 == null) {
+ A._scheduleAsyncCallback(callback);
+ $._lastPriorityCallback = $._lastCallback;
+ return;
+ }
+ entry = new A._AsyncCallbackEntry(callback);
+ lastPriorityCallback = $._lastPriorityCallback;
+ if (lastPriorityCallback == null) {
+ entry.next = t1;
+ $._nextCallback = $._lastPriorityCallback = entry;
+ } else {
+ next = lastPriorityCallback.next;
+ entry.next = next;
+ $._lastPriorityCallback = lastPriorityCallback.next = entry;
+ if (next == null)
+ $._lastCallback = entry;
+ }
+ },
+ scheduleMicrotask(callback) {
+ var _null = null,
+ currentZone = $.Zone__current;
+ if (B.C__RootZone === currentZone) {
+ A._rootScheduleMicrotask(_null, _null, B.C__RootZone, callback);
+ return;
+ }
+ A._rootScheduleMicrotask(_null, _null, currentZone, type$.void_Function._as(currentZone.bindCallbackGuarded$1(callback)));
+ },
+ StreamIterator_StreamIterator(stream, $T) {
+ A.checkNotNullable(stream, "stream", type$.Object);
+ return new A._StreamIterator($T._eval$1("_StreamIterator<0>"));
+ },
+ StreamController_StreamController($T) {
+ var _null = null;
+ return new A._AsyncStreamController(_null, _null, _null, _null, $T._eval$1("_AsyncStreamController<0>"));
+ },
+ _runGuarded(notificationHandler) {
+ return;
+ },
+ _BufferingStreamSubscription__registerDataHandler(zone, handleData, $T) {
+ var t1 = handleData == null ? A.async___nullDataHandler$closure() : handleData;
+ return type$.$env_1_1_void._bind$1($T)._eval$1("1(2)")._as(t1);
+ },
+ _BufferingStreamSubscription__registerErrorHandler(zone, handleError) {
+ if (handleError == null)
+ handleError = A.async___nullErrorHandler$closure();
+ if (type$.void_Function_Object_StackTrace._is(handleError))
+ return zone.registerBinaryCallback$3$1(handleError, type$.dynamic, type$.Object, type$.StackTrace);
+ if (type$.void_Function_Object._is(handleError))
+ return type$.dynamic_Function_Object._as(handleError);
+ throw A.wrapException(A.ArgumentError$("handleError callback must take either an Object (the error), or both an Object (the error) and a StackTrace.", null));
+ },
+ _nullDataHandler(value) {
+ },
+ _nullErrorHandler(error, stackTrace) {
+ A._rootHandleError(type$.Object._as(error), type$.StackTrace._as(stackTrace));
+ },
+ _nullDoneHandler() {
+ },
+ _cancelAndValue(subscription, future, value) {
+ var cancelFuture = subscription.cancel$0(),
+ t1 = $.$get$Future__nullFuture();
+ if (cancelFuture !== t1)
+ cancelFuture.whenComplete$1(new A._cancelAndValue_closure(future, value));
+ else
+ future._complete$1(value);
+ },
+ Timer_Timer(duration, callback) {
+ var t1 = $.Zone__current;
+ if (t1 === B.C__RootZone)
+ return A.Timer__createTimer(duration, type$.void_Function._as(callback));
+ return A.Timer__createTimer(duration, type$.void_Function._as(t1.bindCallbackGuarded$1(callback)));
+ },
+ _rootHandleError(error, stackTrace) {
+ A._schedulePriorityAsyncCallback(new A._rootHandleError_closure(error, stackTrace));
+ },
+ _rootRun($self, $parent, zone, f, $R) {
+ var old,
+ t1 = $.Zone__current;
+ if (t1 === zone)
+ return f.call$0();
+ $.Zone__current = zone;
+ old = t1;
+ try {
+ t1 = f.call$0();
+ return t1;
+ } finally {
+ $.Zone__current = old;
+ }
+ },
+ _rootRunUnary($self, $parent, zone, f, arg, $R, $T) {
+ var old,
+ t1 = $.Zone__current;
+ if (t1 === zone)
+ return f.call$1(arg);
+ $.Zone__current = zone;
+ old = t1;
+ try {
+ t1 = f.call$1(arg);
+ return t1;
+ } finally {
+ $.Zone__current = old;
+ }
+ },
+ _rootRunBinary($self, $parent, zone, f, arg1, arg2, $R, T1, T2) {
+ var old,
+ t1 = $.Zone__current;
+ if (t1 === zone)
+ return f.call$2(arg1, arg2);
+ $.Zone__current = zone;
+ old = t1;
+ try {
+ t1 = f.call$2(arg1, arg2);
+ return t1;
+ } finally {
+ $.Zone__current = old;
+ }
+ },
+ _rootScheduleMicrotask($self, $parent, zone, f) {
+ type$.void_Function._as(f);
+ if (B.C__RootZone !== zone)
+ f = zone.bindCallbackGuarded$1(f);
+ A._scheduleAsyncCallback(f);
+ },
+ _AsyncRun__initializeScheduleImmediate_internalCallback: function _AsyncRun__initializeScheduleImmediate_internalCallback(t0) {
+ this._box_0 = t0;
+ },
+ _AsyncRun__initializeScheduleImmediate_closure: function _AsyncRun__initializeScheduleImmediate_closure(t0, t1, t2) {
+ this._box_0 = t0;
+ this.div = t1;
+ this.span = t2;
+ },
+ _AsyncRun__scheduleImmediateJsOverride_internalCallback: function _AsyncRun__scheduleImmediateJsOverride_internalCallback(t0) {
+ this.callback = t0;
+ },
+ _AsyncRun__scheduleImmediateWithSetImmediate_internalCallback: function _AsyncRun__scheduleImmediateWithSetImmediate_internalCallback(t0) {
+ this.callback = t0;
+ },
+ _TimerImpl: function _TimerImpl() {
+ this._handle = null;
+ },
+ _TimerImpl_internalCallback: function _TimerImpl_internalCallback(t0, t1) {
+ this.$this = t0;
+ this.callback = t1;
+ },
+ _AsyncAwaitCompleter: function _AsyncAwaitCompleter(t0, t1) {
+ this._future = t0;
+ this.isSync = false;
+ this.$ti = t1;
+ },
+ _awaitOnObject_closure: function _awaitOnObject_closure(t0) {
+ this.bodyFunction = t0;
+ },
+ _awaitOnObject_closure0: function _awaitOnObject_closure0(t0) {
+ this.bodyFunction = t0;
+ },
+ _wrapJsFunctionForAsync_closure: function _wrapJsFunctionForAsync_closure(t0) {
+ this.$protected = t0;
+ },
+ AsyncError: function AsyncError(t0, t1) {
+ this.error = t0;
+ this.stackTrace = t1;
+ },
+ _Completer: function _Completer() {
+ },
+ _AsyncCompleter: function _AsyncCompleter(t0, t1) {
+ this.future = t0;
+ this.$ti = t1;
+ },
+ _SyncCompleter: function _SyncCompleter(t0, t1) {
+ this.future = t0;
+ this.$ti = t1;
+ },
+ _FutureListener: function _FutureListener(t0, t1, t2, t3, t4) {
+ var _ = this;
+ _._nextListener = null;
+ _.result = t0;
+ _.state = t1;
+ _.callback = t2;
+ _.errorCallback = t3;
+ _.$ti = t4;
+ },
+ _Future: function _Future(t0, t1) {
+ var _ = this;
+ _._state = 0;
+ _._zone = t0;
+ _._resultOrListeners = null;
+ _.$ti = t1;
+ },
+ _Future__addListener_closure: function _Future__addListener_closure(t0, t1) {
+ this.$this = t0;
+ this.listener = t1;
+ },
+ _Future__prependListeners_closure: function _Future__prependListeners_closure(t0, t1) {
+ this._box_0 = t0;
+ this.$this = t1;
+ },
+ _Future__chainForeignFuture_closure: function _Future__chainForeignFuture_closure(t0) {
+ this.$this = t0;
+ },
+ _Future__chainForeignFuture_closure0: function _Future__chainForeignFuture_closure0(t0) {
+ this.$this = t0;
+ },
+ _Future__chainForeignFuture_closure1: function _Future__chainForeignFuture_closure1(t0, t1, t2) {
+ this.$this = t0;
+ this.e = t1;
+ this.s = t2;
+ },
+ _Future__chainCoreFutureAsync_closure: function _Future__chainCoreFutureAsync_closure(t0, t1) {
+ this._box_0 = t0;
+ this.target = t1;
+ },
+ _Future__asyncCompleteWithValue_closure: function _Future__asyncCompleteWithValue_closure(t0, t1) {
+ this.$this = t0;
+ this.value = t1;
+ },
+ _Future__asyncCompleteError_closure: function _Future__asyncCompleteError_closure(t0, t1, t2) {
+ this.$this = t0;
+ this.error = t1;
+ this.stackTrace = t2;
+ },
+ _Future__propagateToListeners_handleWhenCompleteCallback: function _Future__propagateToListeners_handleWhenCompleteCallback(t0, t1, t2) {
+ this._box_0 = t0;
+ this._box_1 = t1;
+ this.hasError = t2;
+ },
+ _Future__propagateToListeners_handleWhenCompleteCallback_closure: function _Future__propagateToListeners_handleWhenCompleteCallback_closure(t0) {
+ this.originalSource = t0;
+ },
+ _Future__propagateToListeners_handleValueCallback: function _Future__propagateToListeners_handleValueCallback(t0, t1) {
+ this._box_0 = t0;
+ this.sourceResult = t1;
+ },
+ _Future__propagateToListeners_handleError: function _Future__propagateToListeners_handleError(t0, t1) {
+ this._box_1 = t0;
+ this._box_0 = t1;
+ },
+ _AsyncCallbackEntry: function _AsyncCallbackEntry(t0) {
+ this.callback = t0;
+ this.next = null;
+ },
+ Stream: function Stream() {
+ },
+ Stream_length_closure: function Stream_length_closure(t0, t1) {
+ this._box_0 = t0;
+ this.$this = t1;
+ },
+ Stream_length_closure0: function Stream_length_closure0(t0, t1) {
+ this._box_0 = t0;
+ this.future = t1;
+ },
+ Stream_first_closure: function Stream_first_closure(t0) {
+ this.future = t0;
+ },
+ Stream_first_closure0: function Stream_first_closure0(t0, t1, t2) {
+ this.$this = t0;
+ this.subscription = t1;
+ this.future = t2;
+ },
+ _StreamController: function _StreamController() {
+ },
+ _StreamController__subscribe_closure: function _StreamController__subscribe_closure(t0) {
+ this.$this = t0;
+ },
+ _StreamController__recordCancel_complete: function _StreamController__recordCancel_complete(t0) {
+ this.$this = t0;
+ },
+ _AsyncStreamControllerDispatch: function _AsyncStreamControllerDispatch() {
+ },
+ _AsyncStreamController: function _AsyncStreamController(t0, t1, t2, t3, t4) {
+ var _ = this;
+ _._varData = null;
+ _._state = 0;
+ _._doneFuture = null;
+ _.onListen = t0;
+ _.onPause = t1;
+ _.onResume = t2;
+ _.onCancel = t3;
+ _.$ti = t4;
+ },
+ _ControllerStream: function _ControllerStream(t0, t1) {
+ this._controller = t0;
+ this.$ti = t1;
+ },
+ _ControllerSubscription: function _ControllerSubscription(t0, t1, t2, t3, t4, t5, t6) {
+ var _ = this;
+ _._controller = t0;
+ _._onData = t1;
+ _._onError = t2;
+ _._onDone = t3;
+ _._zone = t4;
+ _._state = t5;
+ _._pending = _._cancelFuture = null;
+ _.$ti = t6;
+ },
+ _StreamSinkWrapper: function _StreamSinkWrapper(t0, t1) {
+ this._async$_target = t0;
+ this.$ti = t1;
+ },
+ _BufferingStreamSubscription: function _BufferingStreamSubscription() {
+ },
+ _BufferingStreamSubscription_asFuture_closure: function _BufferingStreamSubscription_asFuture_closure(t0, t1) {
+ this._box_0 = t0;
+ this.result = t1;
+ },
+ _BufferingStreamSubscription_asFuture_closure0: function _BufferingStreamSubscription_asFuture_closure0(t0, t1) {
+ this.$this = t0;
+ this.result = t1;
+ },
+ _BufferingStreamSubscription_asFuture__closure: function _BufferingStreamSubscription_asFuture__closure(t0, t1, t2) {
+ this.result = t0;
+ this.error = t1;
+ this.stackTrace = t2;
+ },
+ _BufferingStreamSubscription__sendError_sendError: function _BufferingStreamSubscription__sendError_sendError(t0, t1, t2) {
+ this.$this = t0;
+ this.error = t1;
+ this.stackTrace = t2;
+ },
+ _BufferingStreamSubscription__sendDone_sendDone: function _BufferingStreamSubscription__sendDone_sendDone(t0) {
+ this.$this = t0;
+ },
+ _StreamImpl: function _StreamImpl() {
+ },
+ _DelayedEvent: function _DelayedEvent() {
+ },
+ _DelayedData: function _DelayedData(t0, t1) {
+ this.value = t0;
+ this.next = null;
+ this.$ti = t1;
+ },
+ _DelayedError: function _DelayedError(t0, t1) {
+ this.error = t0;
+ this.stackTrace = t1;
+ this.next = null;
+ },
+ _DelayedDone: function _DelayedDone() {
+ },
+ _PendingEvents: function _PendingEvents(t0) {
+ var _ = this;
+ _._state = 0;
+ _.lastPendingEvent = _.firstPendingEvent = null;
+ _.$ti = t0;
+ },
+ _PendingEvents_schedule_closure: function _PendingEvents_schedule_closure(t0, t1) {
+ this.$this = t0;
+ this.dispatch = t1;
+ },
+ _StreamIterator: function _StreamIterator(t0) {
+ this.$ti = t0;
+ },
+ _cancelAndValue_closure: function _cancelAndValue_closure(t0, t1) {
+ this.future = t0;
+ this.value = t1;
+ },
+ _Zone: function _Zone() {
+ },
+ _rootHandleError_closure: function _rootHandleError_closure(t0, t1) {
+ this.error = t0;
+ this.stackTrace = t1;
+ },
+ _RootZone: function _RootZone() {
+ },
+ _RootZone_bindCallbackGuarded_closure: function _RootZone_bindCallbackGuarded_closure(t0, t1) {
+ this.$this = t0;
+ this.f = t1;
+ },
+ _RootZone_bindUnaryCallbackGuarded_closure: function _RootZone_bindUnaryCallbackGuarded_closure(t0, t1, t2) {
+ this.$this = t0;
+ this.f = t1;
+ this.T = t2;
+ },
+ _HashMap__getTableEntry(table, key) {
+ var entry = table[key];
+ return entry === table ? null : entry;
+ },
+ _HashMap__setTableEntry(table, key, value) {
+ if (value == null)
+ table[key] = table;
+ else
+ table[key] = value;
+ },
+ _HashMap__newHashTable() {
+ var table = Object.create(null);
+ A._HashMap__setTableEntry(table, "<non-identifier-key>", table);
+ delete table["<non-identifier-key>"];
+ return table;
+ },
+ LinkedHashMap_LinkedHashMap$_empty($K, $V) {
+ return new A.JsLinkedHashMap($K._eval$1("@<0>")._bind$1($V)._eval$1("JsLinkedHashMap<1,2>"));
+ },
+ MapBase_mapToString(m) {
+ var result, t1 = {};
+ if (A.isToStringVisiting(m))
+ return "{...}";
+ result = new A.StringBuffer("");
+ try {
+ B.JSArray_methods.add$1($.toStringVisiting, m);
+ result._contents += "{";
+ t1.first = true;
+ m.forEach$1(0, new A.MapBase_mapToString_closure(t1, result));
+ result._contents += "}";
+ } finally {
+ if (0 >= $.toStringVisiting.length)
+ return A.ioore($.toStringVisiting, -1);
+ $.toStringVisiting.pop();
+ }
+ t1 = result._contents;
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ ListQueue$($E) {
+ return new A.ListQueue(A.List_List$filled(A.ListQueue__calculateCapacity(null), null, false, $E._eval$1("0?")), $E._eval$1("ListQueue<0>"));
+ },
+ ListQueue__calculateCapacity(initialCapacity) {
+ return 8;
+ },
+ _HashMap: function _HashMap() {
+ },
+ _IdentityHashMap: function _IdentityHashMap(t0) {
+ var _ = this;
+ _._collection$_length = 0;
+ _._collection$_keys = _._collection$_rest = _._collection$_nums = _._collection$_strings = null;
+ _.$ti = t0;
+ },
+ _HashMapKeyIterable: function _HashMapKeyIterable(t0, t1) {
+ this._collection$_map = t0;
+ this.$ti = t1;
+ },
+ _HashMapKeyIterator: function _HashMapKeyIterator(t0, t1, t2) {
+ var _ = this;
+ _._collection$_map = t0;
+ _._collection$_keys = t1;
+ _._offset = 0;
+ _._collection$_current = null;
+ _.$ti = t2;
+ },
+ ListBase: function ListBase() {
+ },
+ MapBase: function MapBase() {
+ },
+ MapBase_mapToString_closure: function MapBase_mapToString_closure(t0, t1) {
+ this._box_0 = t0;
+ this.result = t1;
+ },
+ _UnmodifiableMapMixin: function _UnmodifiableMapMixin() {
+ },
+ MapView: function MapView() {
+ },
+ UnmodifiableMapView: function UnmodifiableMapView() {
+ },
+ ListQueue: function ListQueue(t0, t1) {
+ var _ = this;
+ _._table = t0;
+ _._modificationCount = _._tail = _._head = 0;
+ _.$ti = t1;
+ },
+ _ListQueueIterator: function _ListQueueIterator(t0, t1, t2, t3, t4) {
+ var _ = this;
+ _._queue = t0;
+ _._end = t1;
+ _._modificationCount = t2;
+ _._position = t3;
+ _._collection$_current = null;
+ _.$ti = t4;
+ },
+ _UnmodifiableMapView_MapView__UnmodifiableMapMixin: function _UnmodifiableMapView_MapView__UnmodifiableMapMixin() {
+ },
+ _parseJson(source, reviver) {
+ var e, exception, t1, parsed = null;
+ try {
+ parsed = JSON.parse(source);
+ } catch (exception) {
+ e = A.unwrapException(exception);
+ t1 = A.FormatException$(String(e), null, null);
+ throw A.wrapException(t1);
+ }
+ t1 = A._convertJsonToDartLazy(parsed);
+ return t1;
+ },
+ _convertJsonToDartLazy(object) {
+ var i;
+ if (object == null)
+ return null;
+ if (typeof object != "object")
+ return object;
+ if (Object.getPrototypeOf(object) !== Array.prototype)
+ return new A._JsonMap(object, Object.create(null));
+ for (i = 0; i < object.length; ++i)
+ object[i] = A._convertJsonToDartLazy(object[i]);
+ return object;
+ },
+ JsonUnsupportedObjectError$(unsupportedObject, cause, partialResult) {
+ return new A.JsonUnsupportedObjectError(unsupportedObject, cause);
+ },
+ _defaultToEncodable(object) {
+ return object.toJson$0();
+ },
+ _JsonStringStringifier$(_sink, _toEncodable) {
+ return new A._JsonStringStringifier(_sink, [], A.convert___defaultToEncodable$closure());
+ },
+ _JsonStringStringifier_stringify(object, toEncodable, indent) {
+ var t1,
+ output = new A.StringBuffer(""),
+ stringifier = A._JsonStringStringifier$(output, toEncodable);
+ stringifier.writeObject$1(object);
+ t1 = output._contents;
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ _JsonMap: function _JsonMap(t0, t1) {
+ this._original = t0;
+ this._processed = t1;
+ this._data = null;
+ },
+ _JsonMapKeyIterable: function _JsonMapKeyIterable(t0) {
+ this._parent = t0;
+ },
+ Codec: function Codec() {
+ },
+ Converter: function Converter() {
+ },
+ JsonUnsupportedObjectError: function JsonUnsupportedObjectError(t0, t1) {
+ this.unsupportedObject = t0;
+ this.cause = t1;
+ },
+ JsonCyclicError: function JsonCyclicError(t0, t1) {
+ this.unsupportedObject = t0;
+ this.cause = t1;
+ },
+ JsonCodec: function JsonCodec() {
+ },
+ JsonEncoder: function JsonEncoder(t0) {
+ this._toEncodable = t0;
+ },
+ JsonDecoder: function JsonDecoder(t0) {
+ this._reviver = t0;
+ },
+ _JsonStringifier: function _JsonStringifier() {
+ },
+ _JsonStringifier_writeMap_closure: function _JsonStringifier_writeMap_closure(t0, t1) {
+ this._box_0 = t0;
+ this.keyValueList = t1;
+ },
+ _JsonStringStringifier: function _JsonStringStringifier(t0, t1, t2) {
+ this._sink = t0;
+ this._seen = t1;
+ this._toEncodable = t2;
+ },
+ int_parse(source, radix) {
+ var value = A.Primitives_parseInt(source, radix);
+ if (value != null)
+ return value;
+ throw A.wrapException(A.FormatException$(source, null, null));
+ },
+ Error__throw(error, stackTrace) {
+ error = A.wrapException(error);
+ if (error == null)
+ error = type$.Object._as(error);
+ error.stack = stackTrace.toString$0(0);
+ throw error;
+ throw A.wrapException("unreachable");
+ },
+ List_List$filled($length, fill, growable, $E) {
+ var i,
+ result = growable ? J.JSArray_JSArray$growable($length, $E) : J.JSArray_JSArray$fixed($length, $E);
+ if ($length !== 0 && fill != null)
+ for (i = 0; i < result.length; ++i)
+ result[i] = fill;
+ return result;
+ },
+ List_List$of(elements, growable, $E) {
+ var t1 = A.List_List$_of(elements, $E);
+ return t1;
+ },
+ List_List$_of(elements, $E) {
+ var list, t1;
+ if (Array.isArray(elements))
+ return A._setArrayType(elements.slice(0), $E._eval$1("JSArray<0>"));
+ list = A._setArrayType([], $E._eval$1("JSArray<0>"));
+ for (t1 = J.get$iterator$ax(elements); t1.moveNext$0();)
+ B.JSArray_methods.add$1(list, t1.get$current());
+ return list;
+ },
+ StringBuffer__writeAll(string, objects, separator) {
+ var iterator = J.get$iterator$ax(objects);
+ if (!iterator.moveNext$0())
+ return string;
+ if (separator.length === 0) {
+ do
+ string += A.S(iterator.get$current());
+ while (iterator.moveNext$0());
+ } else {
+ string += A.S(iterator.get$current());
+ for (; iterator.moveNext$0();)
+ string = string + separator + A.S(iterator.get$current());
+ }
+ return string;
+ },
+ NoSuchMethodError_NoSuchMethodError$withInvocation(receiver, invocation) {
+ return new A.NoSuchMethodError(receiver, invocation.get$memberName(), invocation.get$positionalArguments(), invocation.get$namedArguments());
+ },
+ StackTrace_current() {
+ return A.getTraceFromException(new Error());
+ },
+ DateTime__fourDigits(n) {
+ var absN = Math.abs(n),
+ sign = n < 0 ? "-" : "";
+ if (absN >= 1000)
+ return "" + n;
+ if (absN >= 100)
+ return sign + "0" + absN;
+ if (absN >= 10)
+ return sign + "00" + absN;
+ return sign + "000" + absN;
+ },
+ DateTime__threeDigits(n) {
+ if (n >= 100)
+ return "" + n;
+ if (n >= 10)
+ return "0" + n;
+ return "00" + n;
+ },
+ DateTime__twoDigits(n) {
+ if (n >= 10)
+ return "" + n;
+ return "0" + n;
+ },
+ Error_safeToString(object) {
+ if (typeof object == "number" || A._isBool(object) || object == null)
+ return J.toString$0$(object);
+ if (typeof object == "string")
+ return JSON.stringify(object);
+ return A.Primitives_safeToString(object);
+ },
+ Error_throwWithStackTrace(error, stackTrace) {
+ A.checkNotNullable(error, "error", type$.Object);
+ A.checkNotNullable(stackTrace, "stackTrace", type$.StackTrace);
+ A.Error__throw(error, stackTrace);
+ },
+ AssertionError$(message) {
+ return new A.AssertionError(message);
+ },
+ ArgumentError$(message, $name) {
+ return new A.ArgumentError(false, null, $name, message);
+ },
+ ArgumentError$value(value, $name, message) {
+ return new A.ArgumentError(true, value, $name, message);
+ },
+ ArgumentError$notNull($name) {
+ return new A.ArgumentError(false, null, $name, "Must not be null");
+ },
+ RangeError$(message) {
+ var _null = null;
+ return new A.RangeError(_null, _null, false, _null, _null, message);
+ },
+ RangeError$value(value, $name) {
+ return new A.RangeError(null, null, true, value, $name, "Value not in range");
+ },
+ RangeError$range(invalidValue, minValue, maxValue, $name, message) {
+ return new A.RangeError(minValue, maxValue, true, invalidValue, $name, "Invalid value");
+ },
+ RangeError_checkValidRange(start, end, $length) {
+ if (0 > start || start > $length)
+ throw A.wrapException(A.RangeError$range(start, 0, $length, "start", null));
+ if (end != null) {
+ if (start > end || end > $length)
+ throw A.wrapException(A.RangeError$range(end, start, $length, "end", null));
+ return end;
+ }
+ return $length;
+ },
+ RangeError_checkNotNegative(value, $name) {
+ if (value < 0)
+ throw A.wrapException(A.RangeError$range(value, 0, null, $name, null));
+ return value;
+ },
+ IndexError$withLength(invalidValue, $length, indexable, message, $name) {
+ return new A.IndexError($length, true, invalidValue, $name, "Index out of range");
+ },
+ UnsupportedError$(message) {
+ return new A.UnsupportedError(message);
+ },
+ UnimplementedError$(message) {
+ return new A.UnimplementedError(message);
+ },
+ StateError$(message) {
+ return new A.StateError(message);
+ },
+ ConcurrentModificationError$(modifiedObject) {
+ return new A.ConcurrentModificationError(modifiedObject);
+ },
+ FormatException$(message, source, offset) {
+ return new A.FormatException(message, source, offset);
+ },
+ Iterable_iterableToShortString(iterable, leftDelimiter, rightDelimiter) {
+ var parts, t1;
+ if (A.isToStringVisiting(iterable)) {
+ if (leftDelimiter === "(" && rightDelimiter === ")")
+ return "(...)";
+ return leftDelimiter + "..." + rightDelimiter;
+ }
+ parts = A._setArrayType([], type$.JSArray_String);
+ B.JSArray_methods.add$1($.toStringVisiting, iterable);
+ try {
+ A._iterablePartsToStrings(iterable, parts);
+ } finally {
+ if (0 >= $.toStringVisiting.length)
+ return A.ioore($.toStringVisiting, -1);
+ $.toStringVisiting.pop();
+ }
+ t1 = A.StringBuffer__writeAll(leftDelimiter, type$.Iterable_dynamic._as(parts), ", ") + rightDelimiter;
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ Iterable_iterableToFullString(iterable, leftDelimiter, rightDelimiter) {
+ var buffer, t1;
+ if (A.isToStringVisiting(iterable))
+ return leftDelimiter + "..." + rightDelimiter;
+ buffer = new A.StringBuffer(leftDelimiter);
+ B.JSArray_methods.add$1($.toStringVisiting, iterable);
+ try {
+ t1 = buffer;
+ t1._contents = A.StringBuffer__writeAll(t1._contents, iterable, ", ");
+ } finally {
+ if (0 >= $.toStringVisiting.length)
+ return A.ioore($.toStringVisiting, -1);
+ $.toStringVisiting.pop();
+ }
+ buffer._contents += rightDelimiter;
+ t1 = buffer._contents;
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ _iterablePartsToStrings(iterable, parts) {
+ var next, ultimateString, penultimateString, penultimate, ultimate, ultimate0, elision,
+ it = iterable.get$iterator(iterable),
+ $length = 0, count = 0;
+ while (true) {
+ if (!($length < 80 || count < 3))
+ break;
+ if (!it.moveNext$0())
+ return;
+ next = A.S(it.get$current());
+ B.JSArray_methods.add$1(parts, next);
+ $length += next.length + 2;
+ ++count;
+ }
+ if (!it.moveNext$0()) {
+ if (count <= 5)
+ return;
+ if (0 >= parts.length)
+ return A.ioore(parts, -1);
+ ultimateString = parts.pop();
+ if (0 >= parts.length)
+ return A.ioore(parts, -1);
+ penultimateString = parts.pop();
+ } else {
+ penultimate = it.get$current();
+ ++count;
+ if (!it.moveNext$0()) {
+ if (count <= 4) {
+ B.JSArray_methods.add$1(parts, A.S(penultimate));
+ return;
+ }
+ ultimateString = A.S(penultimate);
+ if (0 >= parts.length)
+ return A.ioore(parts, -1);
+ penultimateString = parts.pop();
+ $length += ultimateString.length + 2;
+ } else {
+ ultimate = it.get$current();
+ ++count;
+ for (; it.moveNext$0(); penultimate = ultimate, ultimate = ultimate0) {
+ ultimate0 = it.get$current();
+ ++count;
+ if (count > 100) {
+ while (true) {
+ if (!($length > 75 && count > 3))
+ break;
+ if (0 >= parts.length)
+ return A.ioore(parts, -1);
+ $length -= parts.pop().length + 2;
+ --count;
+ }
+ B.JSArray_methods.add$1(parts, "...");
+ return;
+ }
+ }
+ penultimateString = A.S(penultimate);
+ ultimateString = A.S(ultimate);
+ $length += ultimateString.length + penultimateString.length + 4;
+ }
+ }
+ if (count > parts.length + 2) {
+ $length += 5;
+ elision = "...";
+ } else
+ elision = null;
+ while (true) {
+ if (!($length > 80 && parts.length > 3))
+ break;
+ if (0 >= parts.length)
+ return A.ioore(parts, -1);
+ $length -= parts.pop().length + 2;
+ if (elision == null) {
+ $length += 5;
+ elision = "...";
+ }
+ }
+ if (elision != null)
+ B.JSArray_methods.add$1(parts, elision);
+ B.JSArray_methods.add$1(parts, penultimateString);
+ B.JSArray_methods.add$1(parts, ultimateString);
+ },
+ NoSuchMethodError_toString_closure: function NoSuchMethodError_toString_closure(t0, t1) {
+ this._box_0 = t0;
+ this.sb = t1;
+ },
+ DateTime: function DateTime(t0, t1) {
+ this._value = t0;
+ this.isUtc = t1;
+ },
+ Duration: function Duration(t0) {
+ this._duration = t0;
+ },
+ Error: function Error() {
+ },
+ AssertionError: function AssertionError(t0) {
+ this.message = t0;
+ },
+ TypeError: function TypeError() {
+ },
+ ArgumentError: function ArgumentError(t0, t1, t2, t3) {
+ var _ = this;
+ _._hasValue = t0;
+ _.invalidValue = t1;
+ _.name = t2;
+ _.message = t3;
+ },
+ RangeError: function RangeError(t0, t1, t2, t3, t4, t5) {
+ var _ = this;
+ _.start = t0;
+ _.end = t1;
+ _._hasValue = t2;
+ _.invalidValue = t3;
+ _.name = t4;
+ _.message = t5;
+ },
+ IndexError: function IndexError(t0, t1, t2, t3, t4) {
+ var _ = this;
+ _.length = t0;
+ _._hasValue = t1;
+ _.invalidValue = t2;
+ _.name = t3;
+ _.message = t4;
+ },
+ NoSuchMethodError: function NoSuchMethodError(t0, t1, t2, t3) {
+ var _ = this;
+ _._core$_receiver = t0;
+ _._core$_memberName = t1;
+ _._core$_arguments = t2;
+ _._namedArguments = t3;
+ },
+ UnsupportedError: function UnsupportedError(t0) {
+ this.message = t0;
+ },
+ UnimplementedError: function UnimplementedError(t0) {
+ this.message = t0;
+ },
+ StateError: function StateError(t0) {
+ this.message = t0;
+ },
+ ConcurrentModificationError: function ConcurrentModificationError(t0) {
+ this.modifiedObject = t0;
+ },
+ OutOfMemoryError: function OutOfMemoryError() {
+ },
+ StackOverflowError: function StackOverflowError() {
+ },
+ _Exception: function _Exception(t0) {
+ this.message = t0;
+ },
+ FormatException: function FormatException(t0, t1, t2) {
+ this.message = t0;
+ this.source = t1;
+ this.offset = t2;
+ },
+ Iterable: function Iterable() {
+ },
+ Null: function Null() {
+ },
+ Object: function Object() {
+ },
+ _StringStackTrace: function _StringStackTrace(t0) {
+ this._stackTrace = t0;
+ },
+ StringBuffer: function StringBuffer(t0) {
+ this._contents = t0;
+ },
+ _convertDartFunctionFast(f) {
+ var ret,
+ existing = f.$dart_jsFunction;
+ if (existing != null)
+ return existing;
+ ret = function(_call, f) {
+ return function() {
+ return _call(f, Array.prototype.slice.apply(arguments));
+ };
+ }(A._callDartFunctionFast, f);
+ ret[$.$get$DART_CLOSURE_PROPERTY_NAME()] = f;
+ f.$dart_jsFunction = ret;
+ return ret;
+ },
+ _callDartFunctionFast(callback, $arguments) {
+ type$.List_dynamic._as($arguments);
+ type$.Function._as(callback);
+ return A.Primitives_applyFunction(callback, $arguments, null);
+ },
+ allowInterop(f, $F) {
+ if (typeof f == "function")
+ return f;
+ else
+ return $F._as(A._convertDartFunctionFast(f));
+ },
+ promiseToFuture(jsPromise, $T) {
+ var t1 = new A._Future($.Zone__current, $T._eval$1("_Future<0>")),
+ completer = new A._AsyncCompleter(t1, $T._eval$1("_AsyncCompleter<0>"));
+ jsPromise.then(A.convertDartClosureToJS(new A.promiseToFuture_closure(completer, $T), 1), A.convertDartClosureToJS(new A.promiseToFuture_closure0(completer), 1));
+ return t1;
+ },
+ _noDartifyRequired(o) {
+ return o == null || typeof o === "boolean" || typeof o === "number" || typeof o === "string" || o instanceof Int8Array || o instanceof Uint8Array || o instanceof Uint8ClampedArray || o instanceof Int16Array || o instanceof Uint16Array || o instanceof Int32Array || o instanceof Uint32Array || o instanceof Float32Array || o instanceof Float64Array || o instanceof ArrayBuffer || o instanceof DataView;
+ },
+ dartify(o) {
+ if (A._noDartifyRequired(o))
+ return o;
+ return new A.dartify_convert(new A._IdentityHashMap(type$._IdentityHashMap_of_nullable_Object_and_nullable_Object)).call$1(o);
+ },
+ promiseToFuture_closure: function promiseToFuture_closure(t0, t1) {
+ this.completer = t0;
+ this.T = t1;
+ },
+ promiseToFuture_closure0: function promiseToFuture_closure0(t0) {
+ this.completer = t0;
+ },
+ dartify_convert: function dartify_convert(t0) {
+ this._convertedObjects = t0;
+ },
+ NullRejectionException: function NullRejectionException(t0) {
+ this.isUndefined = t0;
+ },
+ _JSRandom: function _JSRandom() {
+ },
+ AsyncMemoizer: function AsyncMemoizer(t0, t1) {
+ this._completer = t0;
+ this.$ti = t1;
+ },
+ Level: function Level(t0, t1) {
+ this.name = t0;
+ this.value = t1;
+ },
+ LogRecord: function LogRecord(t0, t1, t2) {
+ this.level = t0;
+ this.message = t1;
+ this.loggerName = t2;
+ },
+ Logger_Logger($name) {
+ return $.Logger__loggers.putIfAbsent$2($name, new A.Logger_Logger_closure($name));
+ },
+ Logger: function Logger(t0, t1, t2) {
+ var _ = this;
+ _.name = t0;
+ _.parent = t1;
+ _._level = null;
+ _._children = t2;
+ },
+ Logger_Logger_closure: function Logger_Logger_closure(t0) {
+ this.name = t0;
+ },
+ Pool: function Pool(t0, t1, t2, t3, t4) {
+ var _ = this;
+ _._requestedResources = t0;
+ _._onReleaseCallbacks = t1;
+ _._onReleaseCompleters = t2;
+ _._maxAllocatedResources = t3;
+ _._allocatedResources = 0;
+ _._timer = null;
+ _._closeMemo = t4;
+ },
+ Pool__runOnRelease_closure: function Pool__runOnRelease_closure(t0) {
+ this.$this = t0;
+ },
+ Pool__runOnRelease_closure0: function Pool__runOnRelease_closure0(t0) {
+ this.$this = t0;
+ },
+ PoolResource: function PoolResource(t0) {
+ this._pool = t0;
+ this._released = false;
+ },
+ SseClient$(serverUrl) {
+ var t3, t4, t5,
+ t1 = type$.String,
+ t2 = A.StreamController_StreamController(t1);
+ t1 = A.StreamController_StreamController(t1);
+ t3 = A.Logger_Logger("SseClient");
+ t4 = $.Zone__current;
+ t5 = A.generateUuidV4();
+ t1 = new A.SseClient(t5, t2, t1, t3, new A._AsyncCompleter(new A._Future(t4, type$._Future_void), type$._AsyncCompleter_void));
+ t1.SseClient$2$debugKey(serverUrl, null);
+ return t1;
+ },
+ SseClient: function SseClient(t0, t1, t2, t3, t4) {
+ var _ = this;
+ _._clientId = t0;
+ _._incomingController = t1;
+ _._outgoingController = t2;
+ _._logger = t3;
+ _._onConnected = t4;
+ _._lastMessageId = -1;
+ _.__SseClient__serverUrl_A = _.__SseClient__eventSource_A = $;
+ _._errorTimer = null;
+ },
+ SseClient_closure: function SseClient_closure(t0) {
+ this.$this = t0;
+ },
+ SseClient_closure0: function SseClient_closure0(t0) {
+ this.$this = t0;
+ },
+ SseClient_closure1: function SseClient_closure1(t0) {
+ this.$this = t0;
+ },
+ SseClient__closure: function SseClient__closure(t0, t1) {
+ this.$this = t0;
+ this.error = t1;
+ },
+ SseClient__onOutgoingMessage_closure: function SseClient__onOutgoingMessage_closure(t0, t1, t2) {
+ this._box_0 = t0;
+ this.$this = t1;
+ this.message = t2;
+ },
+ generateUuidV4() {
+ var t1 = new A.generateUuidV4_printDigits(),
+ t2 = new A.generateUuidV4_bitsDigits(t1, new A.generateUuidV4_generateBits(B.C__JSRandom)),
+ t3 = B.C__JSRandom.nextInt$1(4);
+ return A.S(t2.call$2(16, 4)) + A.S(t2.call$2(16, 4)) + "-" + A.S(t2.call$2(16, 4)) + "-4" + A.S(t2.call$2(12, 3)) + "-" + A.S(t1.call$2(8 + t3, 1)) + A.S(t2.call$2(12, 3)) + "-" + A.S(t2.call$2(16, 4)) + A.S(t2.call$2(16, 4)) + A.S(t2.call$2(16, 4));
+ },
+ generateUuidV4_generateBits: function generateUuidV4_generateBits(t0) {
+ this.random = t0;
+ },
+ generateUuidV4_printDigits: function generateUuidV4_printDigits() {
+ },
+ generateUuidV4_bitsDigits: function generateUuidV4_bitsDigits(t0, t1) {
+ this.printDigits = t0;
+ this.generateBits = t1;
+ },
+ StreamChannelMixin: function StreamChannelMixin() {
+ },
+ _EventStreamSubscription$(_target, _eventType, onData, _useCapture, $T) {
+ var t1;
+ if (onData == null)
+ t1 = null;
+ else {
+ t1 = A._wrapZone(new A._EventStreamSubscription_closure(onData), type$.JSObject);
+ t1 = t1 == null ? null : type$.JavaScriptFunction._as(A.allowInterop(t1, type$.Function));
+ }
+ t1 = new A._EventStreamSubscription(_target, _eventType, t1, false, $T._eval$1("_EventStreamSubscription<0>"));
+ t1._tryResume$0();
+ return t1;
+ },
+ _wrapZone(callback, $T) {
+ var t1 = $.Zone__current;
+ if (t1 === B.C__RootZone)
+ return callback;
+ return t1.bindUnaryCallbackGuarded$1$1(callback, $T);
+ },
+ EventStreamProvider: function EventStreamProvider(t0, t1) {
+ this._eventType = t0;
+ this.$ti = t1;
+ },
+ _EventStream: function _EventStream(t0, t1, t2, t3) {
+ var _ = this;
+ _._target = t0;
+ _._eventType = t1;
+ _._useCapture = t2;
+ _.$ti = t3;
+ },
+ _ElementEventStreamImpl: function _ElementEventStreamImpl(t0, t1, t2, t3) {
+ var _ = this;
+ _._target = t0;
+ _._eventType = t1;
+ _._useCapture = t2;
+ _.$ti = t3;
+ },
+ _EventStreamSubscription: function _EventStreamSubscription(t0, t1, t2, t3, t4) {
+ var _ = this;
+ _._target = t0;
+ _._eventType = t1;
+ _._streams$_onData = t2;
+ _._useCapture = t3;
+ _.$ti = t4;
+ },
+ _EventStreamSubscription_closure: function _EventStreamSubscription_closure(t0) {
+ this.onData = t0;
+ },
+ _EventStreamSubscription_onData_closure: function _EventStreamSubscription_onData_closure(t0) {
+ this.handleData = t0;
+ },
+ main() {
+ var t2,
+ channel = A.SseClient$("/test"),
+ t1 = type$.nullable_JSObject._as(type$.JSObject._as(self.document).querySelector("button"));
+ t1.toString;
+ t2 = type$._ElementEventStreamImpl_JSObject;
+ A._EventStreamSubscription$(t1, "click", t2._eval$1("~(1)?")._as(new A.main_closure(channel)), false, t2._precomputed1);
+ t2 = channel._incomingController;
+ new A._ControllerStream(t2, A._instanceType(t2)._eval$1("_ControllerStream<1>")).listen$1(new A.main_closure0(channel));
+ },
+ main_closure: function main_closure(t0) {
+ this.channel = t0;
+ },
+ main_closure0: function main_closure0(t0) {
+ this.channel = t0;
+ },
+ throwLateFieldNI(fieldName) {
+ A.throwExpressionWithWrapper(new A.LateError("Field '" + fieldName + "' has not been initialized."), new Error());
+ },
+ throwLateFieldADI(fieldName) {
+ A.throwExpressionWithWrapper(new A.LateError("Field '" + fieldName + "' has been assigned during initialization."), new Error());
+ }
+ },
+ B = {};
+ var holders = [A, J, B];
+ var $ = {};
+ A.JS_CONST.prototype = {};
+ J.Interceptor.prototype = {
+ $eq(receiver, other) {
+ return receiver === other;
+ },
+ get$hashCode(receiver) {
+ return A.Primitives_objectHashCode(receiver);
+ },
+ toString$0(receiver) {
+ return "Instance of '" + A.Primitives_objectTypeName(receiver) + "'";
+ },
+ noSuchMethod$1(receiver, invocation) {
+ throw A.wrapException(A.NoSuchMethodError_NoSuchMethodError$withInvocation(receiver, type$.Invocation._as(invocation)));
+ },
+ get$runtimeType(receiver) {
+ return A.createRuntimeType(A._instanceTypeFromConstructor(this));
+ }
+ };
+ J.JSBool.prototype = {
+ toString$0(receiver) {
+ return String(receiver);
+ },
+ get$hashCode(receiver) {
+ return receiver ? 519018 : 218159;
+ },
+ get$runtimeType(receiver) {
+ return A.createRuntimeType(type$.bool);
+ },
+ $isTrustedGetRuntimeType: 1,
+ $isbool: 1
+ };
+ J.JSNull.prototype = {
+ $eq(receiver, other) {
+ return null == other;
+ },
+ toString$0(receiver) {
+ return "null";
+ },
+ get$hashCode(receiver) {
+ return 0;
+ },
+ $isTrustedGetRuntimeType: 1,
+ $isNull: 1
+ };
+ J.JavaScriptObject.prototype = {$isJSObject: 1};
+ J.LegacyJavaScriptObject.prototype = {
+ get$hashCode(receiver) {
+ return 0;
+ },
+ toString$0(receiver) {
+ return String(receiver);
+ }
+ };
+ J.PlainJavaScriptObject.prototype = {};
+ J.UnknownJavaScriptObject.prototype = {};
+ J.JavaScriptFunction.prototype = {
+ toString$0(receiver) {
+ var dartClosure = receiver[$.$get$DART_CLOSURE_PROPERTY_NAME()];
+ if (dartClosure == null)
+ return this.super$LegacyJavaScriptObject$toString(receiver);
+ return "JavaScript function for " + J.toString$0$(dartClosure);
+ },
+ $isFunction: 1
+ };
+ J.JavaScriptBigInt.prototype = {
+ get$hashCode(receiver) {
+ return 0;
+ },
+ toString$0(receiver) {
+ return String(receiver);
+ }
+ };
+ J.JavaScriptSymbol.prototype = {
+ get$hashCode(receiver) {
+ return 0;
+ },
+ toString$0(receiver) {
+ return String(receiver);
+ }
+ };
+ J.JSArray.prototype = {
+ add$1(receiver, value) {
+ A._arrayInstanceType(receiver)._precomputed1._as(value);
+ if (!!receiver.fixed$length)
+ A.throwExpression(A.UnsupportedError$("add"));
+ receiver.push(value);
+ },
+ addAll$1(receiver, collection) {
+ var t1;
+ A._arrayInstanceType(receiver)._eval$1("Iterable<1>")._as(collection);
+ if (!!receiver.fixed$length)
+ A.throwExpression(A.UnsupportedError$("addAll"));
+ if (Array.isArray(collection)) {
+ this._addAllFromArray$1(receiver, collection);
+ return;
+ }
+ for (t1 = J.get$iterator$ax(collection); t1.moveNext$0();)
+ receiver.push(t1.get$current());
+ },
+ _addAllFromArray$1(receiver, array) {
+ var len, i;
+ type$.JSArray_dynamic._as(array);
+ len = array.length;
+ if (len === 0)
+ return;
+ if (receiver === array)
+ throw A.wrapException(A.ConcurrentModificationError$(receiver));
+ for (i = 0; i < len; ++i)
+ receiver.push(array[i]);
+ },
+ get$last(receiver) {
+ var t1 = receiver.length;
+ if (t1 > 0)
+ return receiver[t1 - 1];
+ throw A.wrapException(A.IterableElementError_noElement());
+ },
+ setRange$4(receiver, start, end, iterable, skipCount) {
+ var $length, otherList, t1, i;
+ A._arrayInstanceType(receiver)._eval$1("Iterable<1>")._as(iterable);
+ if (!!receiver.immutable$list)
+ A.throwExpression(A.UnsupportedError$("setRange"));
+ A.RangeError_checkValidRange(start, end, receiver.length);
+ $length = end - start;
+ if ($length === 0)
+ return;
+ A.RangeError_checkNotNegative(skipCount, "skipCount");
+ otherList = iterable;
+ t1 = J.getInterceptor$asx(otherList);
+ if (skipCount + $length > t1.get$length(otherList))
+ throw A.wrapException(A.IterableElementError_tooFew());
+ if (skipCount < start)
+ for (i = $length - 1; i >= 0; --i)
+ receiver[start + i] = t1.$index(otherList, skipCount + i);
+ else
+ for (i = 0; i < $length; ++i)
+ receiver[start + i] = t1.$index(otherList, skipCount + i);
+ },
+ get$isNotEmpty(receiver) {
+ return receiver.length !== 0;
+ },
+ toString$0(receiver) {
+ return A.Iterable_iterableToFullString(receiver, "[", "]");
+ },
+ get$iterator(receiver) {
+ return new J.ArrayIterator(receiver, receiver.length, A._arrayInstanceType(receiver)._eval$1("ArrayIterator<1>"));
+ },
+ get$hashCode(receiver) {
+ return A.Primitives_objectHashCode(receiver);
+ },
+ get$length(receiver) {
+ return receiver.length;
+ },
+ $index(receiver, index) {
+ if (!(index >= 0 && index < receiver.length))
+ throw A.wrapException(A.diagnoseIndexError(receiver, index));
+ return receiver[index];
+ },
+ $indexSet(receiver, index, value) {
+ A._arrayInstanceType(receiver)._precomputed1._as(value);
+ if (!!receiver.immutable$list)
+ A.throwExpression(A.UnsupportedError$("indexed set"));
+ if (!(index >= 0 && index < receiver.length))
+ throw A.wrapException(A.diagnoseIndexError(receiver, index));
+ receiver[index] = value;
+ },
+ $isIterable: 1,
+ $isList: 1
+ };
+ J.JSUnmodifiableArray.prototype = {};
+ J.ArrayIterator.prototype = {
+ get$current() {
+ var t1 = this._current;
+ return t1 == null ? this.$ti._precomputed1._as(t1) : t1;
+ },
+ moveNext$0() {
+ var t2, _this = this,
+ t1 = _this._iterable,
+ $length = t1.length;
+ if (_this._length !== $length) {
+ t1 = A.throwConcurrentModificationError(t1);
+ throw A.wrapException(t1);
+ }
+ t2 = _this._index;
+ if (t2 >= $length) {
+ _this.set$_current(null);
+ return false;
+ }
+ _this.set$_current(t1[t2]);
+ ++_this._index;
+ return true;
+ },
+ set$_current(_current) {
+ this._current = this.$ti._eval$1("1?")._as(_current);
+ }
+ };
+ J.JSNumber.prototype = {
+ toRadixString$1(receiver, radix) {
+ var result, t1, t2, match, exponent;
+ if (radix < 2 || radix > 36)
+ throw A.wrapException(A.RangeError$range(radix, 2, 36, "radix", null));
+ result = receiver.toString(radix);
+ t1 = result.length;
+ t2 = t1 - 1;
+ if (!(t2 >= 0))
+ return A.ioore(result, t2);
+ if (result.charCodeAt(t2) !== 41)
+ return result;
+ match = /^([\da-z]+)(?:\.([\da-z]+))?\(e\+(\d+)\)$/.exec(result);
+ if (match == null)
+ A.throwExpression(A.UnsupportedError$("Unexpected toString result: " + result));
+ t1 = match.length;
+ if (1 >= t1)
+ return A.ioore(match, 1);
+ result = match[1];
+ if (3 >= t1)
+ return A.ioore(match, 3);
+ exponent = +match[3];
+ t1 = match[2];
+ if (t1 != null) {
+ result += t1;
+ exponent -= t1.length;
+ }
+ return result + B.JSString_methods.$mul("0", exponent);
+ },
+ toString$0(receiver) {
+ if (receiver === 0 && 1 / receiver < 0)
+ return "-0.0";
+ else
+ return "" + receiver;
+ },
+ get$hashCode(receiver) {
+ var absolute, floorLog2, factor, scaled,
+ intValue = receiver | 0;
+ if (receiver === intValue)
+ return intValue & 536870911;
+ absolute = Math.abs(receiver);
+ floorLog2 = Math.log(absolute) / 0.6931471805599453 | 0;
+ factor = Math.pow(2, floorLog2);
+ scaled = absolute < 1 ? absolute / factor : factor / absolute;
+ return ((scaled * 9007199254740992 | 0) + (scaled * 3542243181176521 | 0)) * 599197 + floorLog2 * 1259 & 536870911;
+ },
+ _tdivFast$1(receiver, other) {
+ return (receiver | 0) === receiver ? receiver / other | 0 : this._tdivSlow$1(receiver, other);
+ },
+ _tdivSlow$1(receiver, other) {
+ var quotient = receiver / other;
+ if (quotient >= -2147483648 && quotient <= 2147483647)
+ return quotient | 0;
+ if (quotient > 0) {
+ if (quotient !== 1 / 0)
+ return Math.floor(quotient);
+ } else if (quotient > -1 / 0)
+ return Math.ceil(quotient);
+ throw A.wrapException(A.UnsupportedError$("Result of truncating division is " + A.S(quotient) + ": " + A.S(receiver) + " ~/ " + other));
+ },
+ _shlPositive$1(receiver, other) {
+ return other > 31 ? 0 : receiver << other >>> 0;
+ },
+ _shrOtherPositive$1(receiver, other) {
+ var t1;
+ if (receiver > 0)
+ t1 = this._shrBothPositive$1(receiver, other);
+ else {
+ t1 = other > 31 ? 31 : other;
+ t1 = receiver >> t1 >>> 0;
+ }
+ return t1;
+ },
+ _shrBothPositive$1(receiver, other) {
+ return other > 31 ? 0 : receiver >>> other;
+ },
+ get$runtimeType(receiver) {
+ return A.createRuntimeType(type$.num);
+ },
+ $isdouble: 1,
+ $isnum: 1
+ };
+ J.JSInt.prototype = {
+ get$runtimeType(receiver) {
+ return A.createRuntimeType(type$.int);
+ },
+ $isTrustedGetRuntimeType: 1,
+ $isint: 1
+ };
+ J.JSNumNotInt.prototype = {
+ get$runtimeType(receiver) {
+ return A.createRuntimeType(type$.double);
+ },
+ $isTrustedGetRuntimeType: 1
+ };
+ J.JSString.prototype = {
+ matchAsPrefix$2(receiver, string, start) {
+ var t1, t2, i, t3, _null = null;
+ if (start < 0 || start > string.length)
+ throw A.wrapException(A.RangeError$range(start, 0, string.length, _null, _null));
+ t1 = receiver.length;
+ t2 = string.length;
+ if (start + t1 > t2)
+ return _null;
+ for (i = 0; i < t1; ++i) {
+ t3 = start + i;
+ if (!(t3 >= 0 && t3 < t2))
+ return A.ioore(string, t3);
+ if (string.charCodeAt(t3) !== receiver.charCodeAt(i))
+ return _null;
+ }
+ return new A.StringMatch(start, receiver);
+ },
+ $add(receiver, other) {
+ return receiver + other;
+ },
+ endsWith$1(receiver, other) {
+ var otherLength = other.length,
+ t1 = receiver.length;
+ if (otherLength > t1)
+ return false;
+ return other === this.substring$1(receiver, t1 - otherLength);
+ },
+ startsWith$2(receiver, pattern, index) {
+ var endIndex;
+ if (index < 0 || index > receiver.length)
+ throw A.wrapException(A.RangeError$range(index, 0, receiver.length, null, null));
+ if (typeof pattern == "string") {
+ endIndex = index + pattern.length;
+ if (endIndex > receiver.length)
+ return false;
+ return pattern === receiver.substring(index, endIndex);
+ }
+ return J.matchAsPrefix$2$s(pattern, receiver, index) != null;
+ },
+ startsWith$1(receiver, pattern) {
+ return this.startsWith$2(receiver, pattern, 0);
+ },
+ substring$2(receiver, start, end) {
+ return receiver.substring(start, A.RangeError_checkValidRange(start, end, receiver.length));
+ },
+ substring$1(receiver, start) {
+ return this.substring$2(receiver, start, null);
+ },
+ $mul(receiver, times) {
+ var s, result;
+ if (0 >= times)
+ return "";
+ if (times === 1 || receiver.length === 0)
+ return receiver;
+ if (times !== times >>> 0)
+ throw A.wrapException(B.C_OutOfMemoryError);
+ for (s = receiver, result = ""; true;) {
+ if ((times & 1) === 1)
+ result = s + result;
+ times = times >>> 1;
+ if (times === 0)
+ break;
+ s += s;
+ }
+ return result;
+ },
+ padLeft$2(receiver, width, padding) {
+ var delta = width - receiver.length;
+ if (delta <= 0)
+ return receiver;
+ return this.$mul(padding, delta) + receiver;
+ },
+ lastIndexOf$2(receiver, pattern, start) {
+ var t1, t2;
+ if (start == null)
+ start = receiver.length;
+ else if (start < 0 || start > receiver.length)
+ throw A.wrapException(A.RangeError$range(start, 0, receiver.length, null, null));
+ t1 = pattern.length;
+ t2 = receiver.length;
+ if (start + t1 > t2)
+ start = t2 - t1;
+ return receiver.lastIndexOf(pattern, start);
+ },
+ lastIndexOf$1(receiver, pattern) {
+ return this.lastIndexOf$2(receiver, pattern, null);
+ },
+ toString$0(receiver) {
+ return receiver;
+ },
+ get$hashCode(receiver) {
+ var t1, hash, i;
+ for (t1 = receiver.length, hash = 0, i = 0; i < t1; ++i) {
+ hash = hash + receiver.charCodeAt(i) & 536870911;
+ hash = hash + ((hash & 524287) << 10) & 536870911;
+ hash ^= hash >> 6;
+ }
+ hash = hash + ((hash & 67108863) << 3) & 536870911;
+ hash ^= hash >> 11;
+ return hash + ((hash & 16383) << 15) & 536870911;
+ },
+ get$runtimeType(receiver) {
+ return A.createRuntimeType(type$.String);
+ },
+ get$length(receiver) {
+ return receiver.length;
+ },
+ $isTrustedGetRuntimeType: 1,
+ $isPattern: 1,
+ $isString: 1
+ };
+ A.LateError.prototype = {
+ toString$0(_) {
+ return "LateInitializationError: " + this._message;
+ }
+ };
+ A.nullFuture_closure.prototype = {
+ call$0() {
+ return A.Future_Future$value(null, type$.Null);
+ },
+ $signature: 7
+ };
+ A.EfficientLengthIterable.prototype = {};
+ A.ListIterable.prototype = {
+ get$iterator(_) {
+ var _this = this;
+ return new A.ListIterator(_this, _this.get$length(_this), A._instanceType(_this)._eval$1("ListIterator<ListIterable.E>"));
+ },
+ get$isEmpty(_) {
+ return this.get$length(this) === 0;
+ }
+ };
+ A.ListIterator.prototype = {
+ get$current() {
+ var t1 = this.__internal$_current;
+ return t1 == null ? this.$ti._precomputed1._as(t1) : t1;
+ },
+ moveNext$0() {
+ var t3, _this = this,
+ t1 = _this.__internal$_iterable,
+ t2 = J.getInterceptor$asx(t1),
+ $length = t2.get$length(t1);
+ if (_this.__internal$_length !== $length)
+ throw A.wrapException(A.ConcurrentModificationError$(t1));
+ t3 = _this.__internal$_index;
+ if (t3 >= $length) {
+ _this.set$__internal$_current(null);
+ return false;
+ }
+ _this.set$__internal$_current(t2.elementAt$1(t1, t3));
+ ++_this.__internal$_index;
+ return true;
+ },
+ set$__internal$_current(_current) {
+ this.__internal$_current = this.$ti._eval$1("1?")._as(_current);
+ }
+ };
+ A.FixedLengthListMixin.prototype = {};
+ A.Symbol.prototype = {
+ get$hashCode(_) {
+ var hash = this._hashCode;
+ if (hash != null)
+ return hash;
+ hash = 664597 * B.JSString_methods.get$hashCode(this._name) & 536870911;
+ this._hashCode = hash;
+ return hash;
+ },
+ toString$0(_) {
+ return 'Symbol("' + this._name + '")';
+ },
+ $eq(_, other) {
+ if (other == null)
+ return false;
+ return other instanceof A.Symbol && this._name === other._name;
+ },
+ $isSymbol0: 1
+ };
+ A.ConstantMapView.prototype = {};
+ A.ConstantMap.prototype = {
+ get$isEmpty(_) {
+ return this.get$length(this) === 0;
+ },
+ toString$0(_) {
+ return A.MapBase_mapToString(this);
+ },
+ $isMap: 1
+ };
+ A.ConstantStringMap.prototype = {
+ get$length(_) {
+ return this._values.length;
+ },
+ get$_keys() {
+ var keys = this.$keys;
+ if (keys == null) {
+ keys = Object.keys(this._jsIndex);
+ this.$keys = keys;
+ }
+ return keys;
+ },
+ forEach$1(_, f) {
+ var keys, values, t1, i;
+ this.$ti._eval$1("~(1,2)")._as(f);
+ keys = this.get$_keys();
+ values = this._values;
+ for (t1 = keys.length, i = 0; i < t1; ++i)
+ f.call$2(keys[i], values[i]);
+ }
+ };
+ A.JSInvocationMirror.prototype = {
+ get$memberName() {
+ var t1 = this._memberName;
+ if (t1 instanceof A.Symbol)
+ return t1;
+ return this._memberName = new A.Symbol(A._asString(t1));
+ },
+ get$positionalArguments() {
+ var t1, t2, argumentCount, list, index, _this = this;
+ if (_this.__js_helper$_kind === 1)
+ return B.List_empty;
+ t1 = _this._arguments;
+ t2 = J.getInterceptor$asx(t1);
+ argumentCount = t2.get$length(t1) - J.get$length$asx(_this._namedArgumentNames) - _this._typeArgumentCount;
+ if (argumentCount === 0)
+ return B.List_empty;
+ list = [];
+ for (index = 0; index < argumentCount; ++index)
+ list.push(t2.$index(t1, index));
+ return J.JSArray_markUnmodifiableList(list);
+ },
+ get$namedArguments() {
+ var t1, t2, namedArgumentCount, t3, t4, namedArgumentsStartIndex, map, i, _this = this;
+ if (_this.__js_helper$_kind !== 0)
+ return B.Map_empty;
+ t1 = _this._namedArgumentNames;
+ t2 = J.getInterceptor$asx(t1);
+ namedArgumentCount = t2.get$length(t1);
+ t3 = _this._arguments;
+ t4 = J.getInterceptor$asx(t3);
+ namedArgumentsStartIndex = t4.get$length(t3) - namedArgumentCount - _this._typeArgumentCount;
+ if (namedArgumentCount === 0)
+ return B.Map_empty;
+ map = new A.JsLinkedHashMap(type$.JsLinkedHashMap_Symbol_dynamic);
+ for (i = 0; i < namedArgumentCount; ++i)
+ map.$indexSet(0, new A.Symbol(A._asString(t2.$index(t1, i))), t4.$index(t3, namedArgumentsStartIndex + i));
+ return new A.ConstantMapView(map, type$.ConstantMapView_Symbol_dynamic);
+ },
+ $isInvocation: 1
+ };
+ A.Primitives_functionNoSuchMethod_closure.prototype = {
+ call$2($name, argument) {
+ var t1;
+ A._asString($name);
+ t1 = this._box_0;
+ t1.names = t1.names + "$" + $name;
+ B.JSArray_methods.add$1(this.namedArgumentList, $name);
+ B.JSArray_methods.add$1(this.$arguments, argument);
+ ++t1.argumentCount;
+ },
+ $signature: 12
+ };
+ A.TypeErrorDecoder.prototype = {
+ matchTypeError$1(message) {
+ var result, t1, _this = this,
+ match = new RegExp(_this._pattern).exec(message);
+ if (match == null)
+ return null;
+ result = Object.create(null);
+ t1 = _this._arguments;
+ if (t1 !== -1)
+ result.arguments = match[t1 + 1];
+ t1 = _this._argumentsExpr;
+ if (t1 !== -1)
+ result.argumentsExpr = match[t1 + 1];
+ t1 = _this._expr;
+ if (t1 !== -1)
+ result.expr = match[t1 + 1];
+ t1 = _this._method;
+ if (t1 !== -1)
+ result.method = match[t1 + 1];
+ t1 = _this._receiver;
+ if (t1 !== -1)
+ result.receiver = match[t1 + 1];
+ return result;
+ }
+ };
+ A.NullError.prototype = {
+ toString$0(_) {
+ return "Null check operator used on a null value";
+ }
+ };
+ A.JsNoSuchMethodError.prototype = {
+ toString$0(_) {
+ var t2, _this = this,
+ _s38_ = "NoSuchMethodError: method not found: '",
+ t1 = _this._method;
+ if (t1 == null)
+ return "NoSuchMethodError: " + _this.__js_helper$_message;
+ t2 = _this._receiver;
+ if (t2 == null)
+ return _s38_ + t1 + "' (" + _this.__js_helper$_message + ")";
+ return _s38_ + t1 + "' on '" + t2 + "' (" + _this.__js_helper$_message + ")";
+ }
+ };
+ A.UnknownJsTypeError.prototype = {
+ toString$0(_) {
+ var t1 = this.__js_helper$_message;
+ return t1.length === 0 ? "Error" : "Error: " + t1;
+ }
+ };
+ A.NullThrownFromJavaScriptException.prototype = {
+ toString$0(_) {
+ return "Throw of null ('" + (this._irritant === null ? "null" : "undefined") + "' from JavaScript)";
+ }
+ };
+ A.ExceptionAndStackTrace.prototype = {};
+ A._StackTrace.prototype = {
+ toString$0(_) {
+ var trace,
+ t1 = this._trace;
+ if (t1 != null)
+ return t1;
+ t1 = this._exception;
+ trace = t1 !== null && typeof t1 === "object" ? t1.stack : null;
+ return this._trace = trace == null ? "" : trace;
+ },
+ $isStackTrace: 1
+ };
+ A.Closure.prototype = {
+ toString$0(_) {
+ var $constructor = this.constructor,
+ $name = $constructor == null ? null : $constructor.name;
+ return "Closure '" + A.unminifyOrTag($name == null ? "unknown" : $name) + "'";
+ },
+ $isFunction: 1,
+ get$$call() {
+ return this;
+ },
+ "call*": "call$1",
+ $requiredArgCount: 1,
+ $defaultValues: null
+ };
+ A.Closure0Args.prototype = {"call*": "call$0", $requiredArgCount: 0};
+ A.Closure2Args.prototype = {"call*": "call$2", $requiredArgCount: 2};
+ A.TearOffClosure.prototype = {};
+ A.StaticClosure.prototype = {
+ toString$0(_) {
+ var $name = this.$static_name;
+ if ($name == null)
+ return "Closure of unknown static method";
+ return "Closure '" + A.unminifyOrTag($name) + "'";
+ }
+ };
+ A.BoundClosure.prototype = {
+ $eq(_, other) {
+ if (other == null)
+ return false;
+ if (this === other)
+ return true;
+ if (!(other instanceof A.BoundClosure))
+ return false;
+ return this.$_target === other.$_target && this._receiver === other._receiver;
+ },
+ get$hashCode(_) {
+ return (A.objectHashCode(this._receiver) ^ A.Primitives_objectHashCode(this.$_target)) >>> 0;
+ },
+ toString$0(_) {
+ return "Closure '" + this.$_name + "' of " + ("Instance of '" + A.Primitives_objectTypeName(this._receiver) + "'");
+ }
+ };
+ A._CyclicInitializationError.prototype = {
+ toString$0(_) {
+ return "Reading static variable '" + this.variableName + "' during its initialization";
+ }
+ };
+ A.RuntimeError.prototype = {
+ toString$0(_) {
+ return "RuntimeError: " + this.message;
+ }
+ };
+ A._Required.prototype = {};
+ A.JsLinkedHashMap.prototype = {
+ get$length(_) {
+ return this.__js_helper$_length;
+ },
+ get$isEmpty(_) {
+ return this.__js_helper$_length === 0;
+ },
+ get$keys() {
+ return new A.LinkedHashMapKeyIterable(this, A._instanceType(this)._eval$1("LinkedHashMapKeyIterable<1>"));
+ },
+ containsKey$1(key) {
+ var strings = this._strings;
+ if (strings == null)
+ return false;
+ return strings[key] != null;
+ },
+ $index(_, key) {
+ var strings, cell, t1, nums, _null = null;
+ if (typeof key == "string") {
+ strings = this._strings;
+ if (strings == null)
+ return _null;
+ cell = strings[key];
+ t1 = cell == null ? _null : cell.hashMapCellValue;
+ return t1;
+ } else if (typeof key == "number" && (key & 0x3fffffff) === key) {
+ nums = this._nums;
+ if (nums == null)
+ return _null;
+ cell = nums[key];
+ t1 = cell == null ? _null : cell.hashMapCellValue;
+ return t1;
+ } else
+ return this.internalGet$1(key);
+ },
+ internalGet$1(key) {
+ var bucket, index,
+ rest = this.__js_helper$_rest;
+ if (rest == null)
+ return null;
+ bucket = rest[this.internalComputeHashCode$1(key)];
+ index = this.internalFindBucketIndex$2(bucket, key);
+ if (index < 0)
+ return null;
+ return bucket[index].hashMapCellValue;
+ },
+ $indexSet(_, key, value) {
+ var strings, nums, rest, hash, bucket, index, _this = this,
+ t1 = A._instanceType(_this);
+ t1._precomputed1._as(key);
+ t1._rest[1]._as(value);
+ if (typeof key == "string") {
+ strings = _this._strings;
+ _this._addHashTableEntry$3(strings == null ? _this._strings = _this._newHashTable$0() : strings, key, value);
+ } else if (typeof key == "number" && (key & 0x3fffffff) === key) {
+ nums = _this._nums;
+ _this._addHashTableEntry$3(nums == null ? _this._nums = _this._newHashTable$0() : nums, key, value);
+ } else {
+ rest = _this.__js_helper$_rest;
+ if (rest == null)
+ rest = _this.__js_helper$_rest = _this._newHashTable$0();
+ hash = _this.internalComputeHashCode$1(key);
+ bucket = rest[hash];
+ if (bucket == null)
+ rest[hash] = [_this._newLinkedCell$2(key, value)];
+ else {
+ index = _this.internalFindBucketIndex$2(bucket, key);
+ if (index >= 0)
+ bucket[index].hashMapCellValue = value;
+ else
+ bucket.push(_this._newLinkedCell$2(key, value));
+ }
+ }
+ },
+ putIfAbsent$2(key, ifAbsent) {
+ var t2, value, _this = this,
+ t1 = A._instanceType(_this);
+ t1._precomputed1._as(key);
+ t1._eval$1("2()")._as(ifAbsent);
+ if (_this.containsKey$1(key)) {
+ t2 = _this.$index(0, key);
+ return t2 == null ? t1._rest[1]._as(t2) : t2;
+ }
+ value = ifAbsent.call$0();
+ _this.$indexSet(0, key, value);
+ return value;
+ },
+ forEach$1(_, action) {
+ var cell, modifications, _this = this;
+ A._instanceType(_this)._eval$1("~(1,2)")._as(action);
+ cell = _this._first;
+ modifications = _this._modifications;
+ for (; cell != null;) {
+ action.call$2(cell.hashMapCellKey, cell.hashMapCellValue);
+ if (modifications !== _this._modifications)
+ throw A.wrapException(A.ConcurrentModificationError$(_this));
+ cell = cell._next;
+ }
+ },
+ _addHashTableEntry$3(table, key, value) {
+ var cell,
+ t1 = A._instanceType(this);
+ t1._precomputed1._as(key);
+ t1._rest[1]._as(value);
+ cell = table[key];
+ if (cell == null)
+ table[key] = this._newLinkedCell$2(key, value);
+ else
+ cell.hashMapCellValue = value;
+ },
+ _newLinkedCell$2(key, value) {
+ var _this = this,
+ t1 = A._instanceType(_this),
+ cell = new A.LinkedHashMapCell(t1._precomputed1._as(key), t1._rest[1]._as(value));
+ if (_this._first == null)
+ _this._first = _this._last = cell;
+ else
+ _this._last = _this._last._next = cell;
+ ++_this.__js_helper$_length;
+ _this._modifications = _this._modifications + 1 & 1073741823;
+ return cell;
+ },
+ internalComputeHashCode$1(key) {
+ return J.get$hashCode$(key) & 1073741823;
+ },
+ internalFindBucketIndex$2(bucket, key) {
+ var $length, i;
+ if (bucket == null)
+ return -1;
+ $length = bucket.length;
+ for (i = 0; i < $length; ++i)
+ if (J.$eq$(bucket[i].hashMapCellKey, key))
+ return i;
+ return -1;
+ },
+ toString$0(_) {
+ return A.MapBase_mapToString(this);
+ },
+ _newHashTable$0() {
+ var table = Object.create(null);
+ table["<non-identifier-key>"] = table;
+ delete table["<non-identifier-key>"];
+ return table;
+ }
+ };
+ A.LinkedHashMapCell.prototype = {};
+ A.LinkedHashMapKeyIterable.prototype = {
+ get$length(_) {
+ return this._map.__js_helper$_length;
+ },
+ get$isEmpty(_) {
+ return this._map.__js_helper$_length === 0;
+ },
+ get$iterator(_) {
+ var t1 = this._map,
+ t2 = new A.LinkedHashMapKeyIterator(t1, t1._modifications, this.$ti._eval$1("LinkedHashMapKeyIterator<1>"));
+ t2._cell = t1._first;
+ return t2;
+ }
+ };
+ A.LinkedHashMapKeyIterator.prototype = {
+ get$current() {
+ return this.__js_helper$_current;
+ },
+ moveNext$0() {
+ var cell, _this = this,
+ t1 = _this._map;
+ if (_this._modifications !== t1._modifications)
+ throw A.wrapException(A.ConcurrentModificationError$(t1));
+ cell = _this._cell;
+ if (cell == null) {
+ _this.set$__js_helper$_current(null);
+ return false;
+ } else {
+ _this.set$__js_helper$_current(cell.hashMapCellKey);
+ _this._cell = cell._next;
+ return true;
+ }
+ },
+ set$__js_helper$_current(_current) {
+ this.__js_helper$_current = this.$ti._eval$1("1?")._as(_current);
+ }
+ };
+ A.initHooks_closure.prototype = {
+ call$1(o) {
+ return this.getTag(o);
+ },
+ $signature: 8
+ };
+ A.initHooks_closure0.prototype = {
+ call$2(o, tag) {
+ return this.getUnknownTag(o, tag);
+ },
+ $signature: 13
+ };
+ A.initHooks_closure1.prototype = {
+ call$1(tag) {
+ return this.prototypeForTag(A._asString(tag));
+ },
+ $signature: 14
+ };
+ A.StringMatch.prototype = {};
+ A.NativeByteBuffer.prototype = {
+ get$runtimeType(receiver) {
+ return B.Type_ByteBuffer_RkP;
+ },
+ $isTrustedGetRuntimeType: 1
+ };
+ A.NativeTypedData.prototype = {};
+ A.NativeByteData.prototype = {
+ get$runtimeType(receiver) {
+ return B.Type_ByteData_zNC;
+ },
+ $isTrustedGetRuntimeType: 1
+ };
+ A.NativeTypedArray.prototype = {
+ get$length(receiver) {
+ return receiver.length;
+ },
+ $isJavaScriptIndexingBehavior: 1
+ };
+ A.NativeTypedArrayOfDouble.prototype = {
+ $index(receiver, index) {
+ A._checkValidIndex(index, receiver, receiver.length);
+ return receiver[index];
+ },
+ $isIterable: 1,
+ $isList: 1
+ };
+ A.NativeTypedArrayOfInt.prototype = {$isIterable: 1, $isList: 1};
+ A.NativeFloat32List.prototype = {
+ get$runtimeType(receiver) {
+ return B.Type_Float32List_LB7;
+ },
+ $isTrustedGetRuntimeType: 1
+ };
+ A.NativeFloat64List.prototype = {
+ get$runtimeType(receiver) {
+ return B.Type_Float64List_LB7;
+ },
+ $isTrustedGetRuntimeType: 1
+ };
+ A.NativeInt16List.prototype = {
+ get$runtimeType(receiver) {
+ return B.Type_Int16List_uXf;
+ },
+ $index(receiver, index) {
+ A._checkValidIndex(index, receiver, receiver.length);
+ return receiver[index];
+ },
+ $isTrustedGetRuntimeType: 1
+ };
+ A.NativeInt32List.prototype = {
+ get$runtimeType(receiver) {
+ return B.Type_Int32List_O50;
+ },
+ $index(receiver, index) {
+ A._checkValidIndex(index, receiver, receiver.length);
+ return receiver[index];
+ },
+ $isTrustedGetRuntimeType: 1
+ };
+ A.NativeInt8List.prototype = {
+ get$runtimeType(receiver) {
+ return B.Type_Int8List_ekJ;
+ },
+ $index(receiver, index) {
+ A._checkValidIndex(index, receiver, receiver.length);
+ return receiver[index];
+ },
+ $isTrustedGetRuntimeType: 1
+ };
+ A.NativeUint16List.prototype = {
+ get$runtimeType(receiver) {
+ return B.Type_Uint16List_2bx;
+ },
+ $index(receiver, index) {
+ A._checkValidIndex(index, receiver, receiver.length);
+ return receiver[index];
+ },
+ $isTrustedGetRuntimeType: 1
+ };
+ A.NativeUint32List.prototype = {
+ get$runtimeType(receiver) {
+ return B.Type_Uint32List_2bx;
+ },
+ $index(receiver, index) {
+ A._checkValidIndex(index, receiver, receiver.length);
+ return receiver[index];
+ },
+ $isTrustedGetRuntimeType: 1
+ };
+ A.NativeUint8ClampedList.prototype = {
+ get$runtimeType(receiver) {
+ return B.Type_Uint8ClampedList_Jik;
+ },
+ get$length(receiver) {
+ return receiver.length;
+ },
+ $index(receiver, index) {
+ A._checkValidIndex(index, receiver, receiver.length);
+ return receiver[index];
+ },
+ $isTrustedGetRuntimeType: 1
+ };
+ A.NativeUint8List.prototype = {
+ get$runtimeType(receiver) {
+ return B.Type_Uint8List_WLA;
+ },
+ get$length(receiver) {
+ return receiver.length;
+ },
+ $index(receiver, index) {
+ A._checkValidIndex(index, receiver, receiver.length);
+ return receiver[index];
+ },
+ $isTrustedGetRuntimeType: 1
+ };
+ A._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin.prototype = {};
+ A._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin_FixedLengthListMixin.prototype = {};
+ A._NativeTypedArrayOfInt_NativeTypedArray_ListMixin.prototype = {};
+ A._NativeTypedArrayOfInt_NativeTypedArray_ListMixin_FixedLengthListMixin.prototype = {};
+ A.Rti.prototype = {
+ _eval$1(recipe) {
+ return A._Universe_evalInEnvironment(init.typeUniverse, this, recipe);
+ },
+ _bind$1(typeOrTuple) {
+ return A._Universe_bind(init.typeUniverse, this, typeOrTuple);
+ }
+ };
+ A._FunctionParameters.prototype = {};
+ A._Type.prototype = {
+ toString$0(_) {
+ return A._rtiToString(this._rti, null);
+ }
+ };
+ A._Error.prototype = {
+ toString$0(_) {
+ return this.__rti$_message;
+ }
+ };
+ A._TypeError.prototype = {$isTypeError: 1};
+ A._AsyncRun__initializeScheduleImmediate_internalCallback.prototype = {
+ call$1(_) {
+ var t1 = this._box_0,
+ f = t1.storedCallback;
+ t1.storedCallback = null;
+ f.call$0();
+ },
+ $signature: 4
+ };
+ A._AsyncRun__initializeScheduleImmediate_closure.prototype = {
+ call$1(callback) {
+ var t1, t2;
+ this._box_0.storedCallback = type$.void_Function._as(callback);
+ t1 = this.div;
+ t2 = this.span;
+ t1.firstChild ? t1.removeChild(t2) : t1.appendChild(t2);
+ },
+ $signature: 15
+ };
+ A._AsyncRun__scheduleImmediateJsOverride_internalCallback.prototype = {
+ call$0() {
+ this.callback.call$0();
+ },
+ $signature: 2
+ };
+ A._AsyncRun__scheduleImmediateWithSetImmediate_internalCallback.prototype = {
+ call$0() {
+ this.callback.call$0();
+ },
+ $signature: 2
+ };
+ A._TimerImpl.prototype = {
+ _TimerImpl$2(milliseconds, callback) {
+ if (self.setTimeout != null)
+ this._handle = self.setTimeout(A.convertDartClosureToJS(new A._TimerImpl_internalCallback(this, callback), 0), milliseconds);
+ else
+ throw A.wrapException(A.UnsupportedError$("`setTimeout()` not found."));
+ },
+ cancel$0() {
+ if (self.setTimeout != null) {
+ var t1 = this._handle;
+ if (t1 == null)
+ return;
+ self.clearTimeout(t1);
+ this._handle = null;
+ } else
+ throw A.wrapException(A.UnsupportedError$("Canceling a timer."));
+ },
+ $isTimer: 1
+ };
+ A._TimerImpl_internalCallback.prototype = {
+ call$0() {
+ this.$this._handle = null;
+ this.callback.call$0();
+ },
+ $signature: 0
+ };
+ A._AsyncAwaitCompleter.prototype = {
+ complete$1(value) {
+ var t2, _this = this,
+ t1 = _this.$ti;
+ t1._eval$1("1/?")._as(value);
+ if (value == null)
+ value = t1._precomputed1._as(value);
+ if (!_this.isSync)
+ _this._future._asyncComplete$1(value);
+ else {
+ t2 = _this._future;
+ if (t1._eval$1("Future<1>")._is(value))
+ t2._chainFuture$1(value);
+ else
+ t2._completeWithValue$1(value);
+ }
+ },
+ completeError$2(e, st) {
+ var t1 = this._future;
+ if (this.isSync)
+ t1._completeError$2(e, st);
+ else
+ t1._asyncCompleteError$2(e, st);
+ },
+ $isCompleter: 1
+ };
+ A._awaitOnObject_closure.prototype = {
+ call$1(result) {
+ return this.bodyFunction.call$2(0, result);
+ },
+ $signature: 3
+ };
+ A._awaitOnObject_closure0.prototype = {
+ call$2(error, stackTrace) {
+ this.bodyFunction.call$2(1, new A.ExceptionAndStackTrace(error, type$.StackTrace._as(stackTrace)));
+ },
+ $signature: 16
+ };
+ A._wrapJsFunctionForAsync_closure.prototype = {
+ call$2(errorCode, result) {
+ this.$protected(A._asInt(errorCode), result);
+ },
+ $signature: 17
+ };
+ A.AsyncError.prototype = {
+ toString$0(_) {
+ return A.S(this.error);
+ },
+ $isError: 1,
+ get$stackTrace() {
+ return this.stackTrace;
+ }
+ };
+ A._Completer.prototype = {
+ completeError$2(error, stackTrace) {
+ A.checkNotNullable(error, "error", type$.Object);
+ if ((this.future._state & 30) !== 0)
+ throw A.wrapException(A.StateError$("Future already completed"));
+ if (stackTrace == null)
+ stackTrace = A.AsyncError_defaultStackTrace(error);
+ this._completeError$2(error, stackTrace);
+ },
+ completeError$1(error) {
+ return this.completeError$2(error, null);
+ },
+ $isCompleter: 1
+ };
+ A._AsyncCompleter.prototype = {
+ complete$1(value) {
+ var t2,
+ t1 = this.$ti;
+ t1._eval$1("1/?")._as(value);
+ t2 = this.future;
+ if ((t2._state & 30) !== 0)
+ throw A.wrapException(A.StateError$("Future already completed"));
+ t2._asyncComplete$1(t1._eval$1("1/")._as(value));
+ },
+ complete$0() {
+ return this.complete$1(null);
+ },
+ _completeError$2(error, stackTrace) {
+ this.future._asyncCompleteError$2(error, stackTrace);
+ }
+ };
+ A._SyncCompleter.prototype = {
+ complete$1(value) {
+ var t2,
+ t1 = this.$ti;
+ t1._eval$1("1/?")._as(value);
+ t2 = this.future;
+ if ((t2._state & 30) !== 0)
+ throw A.wrapException(A.StateError$("Future already completed"));
+ t2._complete$1(t1._eval$1("1/")._as(value));
+ },
+ _completeError$2(error, stackTrace) {
+ this.future._completeError$2(error, stackTrace);
+ }
+ };
+ A._FutureListener.prototype = {
+ matchesErrorTest$1(asyncError) {
+ if ((this.state & 15) !== 6)
+ return true;
+ return this.result._zone.runUnary$2$2(type$.bool_Function_Object._as(this.callback), asyncError.error, type$.bool, type$.Object);
+ },
+ handleError$1(asyncError) {
+ var exception, _this = this,
+ errorCallback = _this.errorCallback,
+ result = null,
+ t1 = type$.dynamic,
+ t2 = type$.Object,
+ t3 = asyncError.error,
+ t4 = _this.result._zone;
+ if (type$.dynamic_Function_Object_StackTrace._is(errorCallback))
+ result = t4.runBinary$3$3(errorCallback, t3, asyncError.stackTrace, t1, t2, type$.StackTrace);
+ else
+ result = t4.runUnary$2$2(type$.dynamic_Function_Object._as(errorCallback), t3, t1, t2);
+ try {
+ t1 = _this.$ti._eval$1("2/")._as(result);
+ return t1;
+ } catch (exception) {
+ if (type$.TypeError._is(A.unwrapException(exception))) {
+ if ((_this.state & 1) !== 0)
+ throw A.wrapException(A.ArgumentError$("The error handler of Future.then must return a value of the returned future's type", "onError"));
+ throw A.wrapException(A.ArgumentError$("The error handler of Future.catchError must return a value of the future's type", "onError"));
+ } else
+ throw exception;
+ }
+ }
+ };
+ A._Future.prototype = {
+ _setChained$1(source) {
+ this._state = this._state & 1 | 4;
+ this._resultOrListeners = source;
+ },
+ then$1$2$onError(f, onError, $R) {
+ var currentZone, result, t2,
+ t1 = this.$ti;
+ t1._bind$1($R)._eval$1("1/(2)")._as(f);
+ currentZone = $.Zone__current;
+ if (currentZone === B.C__RootZone) {
+ if (onError != null && !type$.dynamic_Function_Object_StackTrace._is(onError) && !type$.dynamic_Function_Object._is(onError))
+ throw A.wrapException(A.ArgumentError$value(onError, "onError", string$.Error_));
+ } else {
+ $R._eval$1("@<0/>")._bind$1(t1._precomputed1)._eval$1("1(2)")._as(f);
+ if (onError != null)
+ onError = A._registerErrorHandler(onError, currentZone);
+ }
+ result = new A._Future(currentZone, $R._eval$1("_Future<0>"));
+ t2 = onError == null ? 1 : 3;
+ this._addListener$1(new A._FutureListener(result, t2, f, onError, t1._eval$1("@<1>")._bind$1($R)._eval$1("_FutureListener<1,2>")));
+ return result;
+ },
+ then$1$1(f, $R) {
+ return this.then$1$2$onError(f, null, $R);
+ },
+ _thenAwait$1$2(f, onError, $E) {
+ var result,
+ t1 = this.$ti;
+ t1._bind$1($E)._eval$1("1/(2)")._as(f);
+ result = new A._Future($.Zone__current, $E._eval$1("_Future<0>"));
+ this._addListener$1(new A._FutureListener(result, 19, f, onError, t1._eval$1("@<1>")._bind$1($E)._eval$1("_FutureListener<1,2>")));
+ return result;
+ },
+ whenComplete$1(action) {
+ var t1, result;
+ type$.dynamic_Function._as(action);
+ t1 = this.$ti;
+ result = new A._Future($.Zone__current, t1);
+ this._addListener$1(new A._FutureListener(result, 8, action, null, t1._eval$1("@<1>")._bind$1(t1._precomputed1)._eval$1("_FutureListener<1,2>")));
+ return result;
+ },
+ _setErrorObject$1(error) {
+ this._state = this._state & 1 | 16;
+ this._resultOrListeners = error;
+ },
+ _cloneResult$1(source) {
+ this._state = source._state & 30 | this._state & 1;
+ this._resultOrListeners = source._resultOrListeners;
+ },
+ _addListener$1(listener) {
+ var source, _this = this,
+ t1 = _this._state;
+ if (t1 <= 3) {
+ listener._nextListener = type$.nullable__FutureListener_dynamic_dynamic._as(_this._resultOrListeners);
+ _this._resultOrListeners = listener;
+ } else {
+ if ((t1 & 4) !== 0) {
+ source = type$._Future_dynamic._as(_this._resultOrListeners);
+ if ((source._state & 24) === 0) {
+ source._addListener$1(listener);
+ return;
+ }
+ _this._cloneResult$1(source);
+ }
+ A._rootScheduleMicrotask(null, null, _this._zone, type$.void_Function._as(new A._Future__addListener_closure(_this, listener)));
+ }
+ },
+ _prependListeners$1(listeners) {
+ var t1, existingListeners, next, cursor, next0, source, _this = this, _box_0 = {};
+ _box_0.listeners = listeners;
+ if (listeners == null)
+ return;
+ t1 = _this._state;
+ if (t1 <= 3) {
+ existingListeners = type$.nullable__FutureListener_dynamic_dynamic._as(_this._resultOrListeners);
+ _this._resultOrListeners = listeners;
+ if (existingListeners != null) {
+ next = listeners._nextListener;
+ for (cursor = listeners; next != null; cursor = next, next = next0)
+ next0 = next._nextListener;
+ cursor._nextListener = existingListeners;
+ }
+ } else {
+ if ((t1 & 4) !== 0) {
+ source = type$._Future_dynamic._as(_this._resultOrListeners);
+ if ((source._state & 24) === 0) {
+ source._prependListeners$1(listeners);
+ return;
+ }
+ _this._cloneResult$1(source);
+ }
+ _box_0.listeners = _this._reverseListeners$1(listeners);
+ A._rootScheduleMicrotask(null, null, _this._zone, type$.void_Function._as(new A._Future__prependListeners_closure(_box_0, _this)));
+ }
+ },
+ _removeListeners$0() {
+ var current = type$.nullable__FutureListener_dynamic_dynamic._as(this._resultOrListeners);
+ this._resultOrListeners = null;
+ return this._reverseListeners$1(current);
+ },
+ _reverseListeners$1(listeners) {
+ var current, prev, next;
+ for (current = listeners, prev = null; current != null; prev = current, current = next) {
+ next = current._nextListener;
+ current._nextListener = prev;
+ }
+ return prev;
+ },
+ _chainForeignFuture$1(source) {
+ var e, s, exception, _this = this;
+ _this._state ^= 2;
+ try {
+ source.then$1$2$onError(new A._Future__chainForeignFuture_closure(_this), new A._Future__chainForeignFuture_closure0(_this), type$.Null);
+ } catch (exception) {
+ e = A.unwrapException(exception);
+ s = A.getTraceFromException(exception);
+ A.scheduleMicrotask(new A._Future__chainForeignFuture_closure1(_this, e, s));
+ }
+ },
+ _complete$1(value) {
+ var listeners, _this = this,
+ t1 = _this.$ti;
+ t1._eval$1("1/")._as(value);
+ if (t1._eval$1("Future<1>")._is(value))
+ if (t1._is(value))
+ A._Future__chainCoreFutureSync(value, _this);
+ else
+ _this._chainForeignFuture$1(value);
+ else {
+ listeners = _this._removeListeners$0();
+ t1._precomputed1._as(value);
+ _this._state = 8;
+ _this._resultOrListeners = value;
+ A._Future__propagateToListeners(_this, listeners);
+ }
+ },
+ _completeWithValue$1(value) {
+ var listeners, _this = this;
+ _this.$ti._precomputed1._as(value);
+ listeners = _this._removeListeners$0();
+ _this._state = 8;
+ _this._resultOrListeners = value;
+ A._Future__propagateToListeners(_this, listeners);
+ },
+ _completeError$2(error, stackTrace) {
+ var listeners;
+ type$.Object._as(error);
+ type$.StackTrace._as(stackTrace);
+ listeners = this._removeListeners$0();
+ this._setErrorObject$1(A.AsyncError$(error, stackTrace));
+ A._Future__propagateToListeners(this, listeners);
+ },
+ _asyncComplete$1(value) {
+ var t1 = this.$ti;
+ t1._eval$1("1/")._as(value);
+ if (t1._eval$1("Future<1>")._is(value)) {
+ this._chainFuture$1(value);
+ return;
+ }
+ this._asyncCompleteWithValue$1(value);
+ },
+ _asyncCompleteWithValue$1(value) {
+ var _this = this;
+ _this.$ti._precomputed1._as(value);
+ _this._state ^= 2;
+ A._rootScheduleMicrotask(null, null, _this._zone, type$.void_Function._as(new A._Future__asyncCompleteWithValue_closure(_this, value)));
+ },
+ _chainFuture$1(value) {
+ var t1 = this.$ti;
+ t1._eval$1("Future<1>")._as(value);
+ if (t1._is(value)) {
+ A._Future__chainCoreFutureAsync(value, this);
+ return;
+ }
+ this._chainForeignFuture$1(value);
+ },
+ _asyncCompleteError$2(error, stackTrace) {
+ type$.StackTrace._as(stackTrace);
+ this._state ^= 2;
+ A._rootScheduleMicrotask(null, null, this._zone, type$.void_Function._as(new A._Future__asyncCompleteError_closure(this, error, stackTrace)));
+ },
+ $isFuture: 1
+ };
+ A._Future__addListener_closure.prototype = {
+ call$0() {
+ A._Future__propagateToListeners(this.$this, this.listener);
+ },
+ $signature: 0
+ };
+ A._Future__prependListeners_closure.prototype = {
+ call$0() {
+ A._Future__propagateToListeners(this.$this, this._box_0.listeners);
+ },
+ $signature: 0
+ };
+ A._Future__chainForeignFuture_closure.prototype = {
+ call$1(value) {
+ var error, stackTrace, exception,
+ t1 = this.$this;
+ t1._state ^= 2;
+ try {
+ t1._completeWithValue$1(t1.$ti._precomputed1._as(value));
+ } catch (exception) {
+ error = A.unwrapException(exception);
+ stackTrace = A.getTraceFromException(exception);
+ t1._completeError$2(error, stackTrace);
+ }
+ },
+ $signature: 4
+ };
+ A._Future__chainForeignFuture_closure0.prototype = {
+ call$2(error, stackTrace) {
+ this.$this._completeError$2(type$.Object._as(error), type$.StackTrace._as(stackTrace));
+ },
+ $signature: 5
+ };
+ A._Future__chainForeignFuture_closure1.prototype = {
+ call$0() {
+ this.$this._completeError$2(this.e, this.s);
+ },
+ $signature: 0
+ };
+ A._Future__chainCoreFutureAsync_closure.prototype = {
+ call$0() {
+ A._Future__chainCoreFutureSync(this._box_0.source, this.target);
+ },
+ $signature: 0
+ };
+ A._Future__asyncCompleteWithValue_closure.prototype = {
+ call$0() {
+ this.$this._completeWithValue$1(this.value);
+ },
+ $signature: 0
+ };
+ A._Future__asyncCompleteError_closure.prototype = {
+ call$0() {
+ this.$this._completeError$2(this.error, this.stackTrace);
+ },
+ $signature: 0
+ };
+ A._Future__propagateToListeners_handleWhenCompleteCallback.prototype = {
+ call$0() {
+ var e, s, t1, exception, t2, originalSource, _this = this, completeResult = null;
+ try {
+ t1 = _this._box_0.listener;
+ completeResult = t1.result._zone.run$1$1(type$.dynamic_Function._as(t1.callback), type$.dynamic);
+ } catch (exception) {
+ e = A.unwrapException(exception);
+ s = A.getTraceFromException(exception);
+ t1 = _this.hasError && type$.AsyncError._as(_this._box_1.source._resultOrListeners).error === e;
+ t2 = _this._box_0;
+ if (t1)
+ t2.listenerValueOrError = type$.AsyncError._as(_this._box_1.source._resultOrListeners);
+ else
+ t2.listenerValueOrError = A.AsyncError$(e, s);
+ t2.listenerHasError = true;
+ return;
+ }
+ if (completeResult instanceof A._Future && (completeResult._state & 24) !== 0) {
+ if ((completeResult._state & 16) !== 0) {
+ t1 = _this._box_0;
+ t1.listenerValueOrError = type$.AsyncError._as(completeResult._resultOrListeners);
+ t1.listenerHasError = true;
+ }
+ return;
+ }
+ if (completeResult instanceof A._Future) {
+ originalSource = _this._box_1.source;
+ t1 = _this._box_0;
+ t1.listenerValueOrError = completeResult.then$1$1(new A._Future__propagateToListeners_handleWhenCompleteCallback_closure(originalSource), type$.dynamic);
+ t1.listenerHasError = false;
+ }
+ },
+ $signature: 0
+ };
+ A._Future__propagateToListeners_handleWhenCompleteCallback_closure.prototype = {
+ call$1(_) {
+ return this.originalSource;
+ },
+ $signature: 18
+ };
+ A._Future__propagateToListeners_handleValueCallback.prototype = {
+ call$0() {
+ var e, s, t1, t2, t3, t4, t5, exception;
+ try {
+ t1 = this._box_0;
+ t2 = t1.listener;
+ t3 = t2.$ti;
+ t4 = t3._precomputed1;
+ t5 = t4._as(this.sourceResult);
+ t1.listenerValueOrError = t2.result._zone.runUnary$2$2(t3._eval$1("2/(1)")._as(t2.callback), t5, t3._eval$1("2/"), t4);
+ } catch (exception) {
+ e = A.unwrapException(exception);
+ s = A.getTraceFromException(exception);
+ t1 = this._box_0;
+ t1.listenerValueOrError = A.AsyncError$(e, s);
+ t1.listenerHasError = true;
+ }
+ },
+ $signature: 0
+ };
+ A._Future__propagateToListeners_handleError.prototype = {
+ call$0() {
+ var asyncError, e, s, t1, exception, t2, _this = this;
+ try {
+ asyncError = type$.AsyncError._as(_this._box_1.source._resultOrListeners);
+ t1 = _this._box_0;
+ if (t1.listener.matchesErrorTest$1(asyncError) && t1.listener.errorCallback != null) {
+ t1.listenerValueOrError = t1.listener.handleError$1(asyncError);
+ t1.listenerHasError = false;
+ }
+ } catch (exception) {
+ e = A.unwrapException(exception);
+ s = A.getTraceFromException(exception);
+ t1 = type$.AsyncError._as(_this._box_1.source._resultOrListeners);
+ t2 = _this._box_0;
+ if (t1.error === e)
+ t2.listenerValueOrError = t1;
+ else
+ t2.listenerValueOrError = A.AsyncError$(e, s);
+ t2.listenerHasError = true;
+ }
+ },
+ $signature: 0
+ };
+ A._AsyncCallbackEntry.prototype = {};
+ A.Stream.prototype = {
+ get$length(_) {
+ var t1 = {},
+ future = new A._Future($.Zone__current, type$._Future_int);
+ t1.count = 0;
+ this.listen$4$cancelOnError$onDone$onError(new A.Stream_length_closure(t1, this), true, new A.Stream_length_closure0(t1, future), future.get$_completeError());
+ return future;
+ },
+ get$first(_) {
+ var future = new A._Future($.Zone__current, A._instanceType(this)._eval$1("_Future<1>")),
+ subscription = this.listen$4$cancelOnError$onDone$onError(null, true, new A.Stream_first_closure(future), future.get$_completeError());
+ subscription.onData$1(new A.Stream_first_closure0(this, subscription, future));
+ return future;
+ }
+ };
+ A.Stream_length_closure.prototype = {
+ call$1(_) {
+ A._instanceType(this.$this)._precomputed1._as(_);
+ ++this._box_0.count;
+ },
+ $signature() {
+ return A._instanceType(this.$this)._eval$1("~(1)");
+ }
+ };
+ A.Stream_length_closure0.prototype = {
+ call$0() {
+ this.future._complete$1(this._box_0.count);
+ },
+ $signature: 0
+ };
+ A.Stream_first_closure.prototype = {
+ call$0() {
+ var e, s, t1, exception, stackTrace;
+ try {
+ t1 = A.IterableElementError_noElement();
+ throw A.wrapException(t1);
+ } catch (exception) {
+ e = A.unwrapException(exception);
+ s = A.getTraceFromException(exception);
+ t1 = e;
+ stackTrace = s;
+ if (stackTrace == null)
+ stackTrace = A.AsyncError_defaultStackTrace(t1);
+ this.future._completeError$2(t1, stackTrace);
+ }
+ },
+ $signature: 0
+ };
+ A.Stream_first_closure0.prototype = {
+ call$1(value) {
+ A._cancelAndValue(this.subscription, this.future, A._instanceType(this.$this)._precomputed1._as(value));
+ },
+ $signature() {
+ return A._instanceType(this.$this)._eval$1("~(1)");
+ }
+ };
+ A._StreamController.prototype = {
+ get$_pendingEvents() {
+ var t1, _this = this;
+ if ((_this._state & 8) === 0)
+ return A._instanceType(_this)._eval$1("_PendingEvents<1>?")._as(_this._varData);
+ t1 = A._instanceType(_this);
+ return t1._eval$1("_PendingEvents<1>?")._as(t1._eval$1("_StreamControllerAddStreamState<1>")._as(_this._varData).get$_varData());
+ },
+ _ensurePendingEvents$0() {
+ var events, t1, _this = this;
+ if ((_this._state & 8) === 0) {
+ events = _this._varData;
+ if (events == null)
+ events = _this._varData = new A._PendingEvents(A._instanceType(_this)._eval$1("_PendingEvents<1>"));
+ return A._instanceType(_this)._eval$1("_PendingEvents<1>")._as(events);
+ }
+ t1 = A._instanceType(_this);
+ events = t1._eval$1("_StreamControllerAddStreamState<1>")._as(_this._varData).get$_varData();
+ return t1._eval$1("_PendingEvents<1>")._as(events);
+ },
+ get$_subscription() {
+ var varData = this._varData;
+ if ((this._state & 8) !== 0)
+ varData = type$._StreamControllerAddStreamState_nullable_Object._as(varData).get$_varData();
+ return A._instanceType(this)._eval$1("_ControllerSubscription<1>")._as(varData);
+ },
+ _badEventState$0() {
+ if ((this._state & 4) !== 0)
+ return new A.StateError("Cannot add event after closing");
+ return new A.StateError("Cannot add event while adding a stream");
+ },
+ _ensureDoneFuture$0() {
+ var t1 = this._doneFuture;
+ if (t1 == null)
+ t1 = this._doneFuture = (this._state & 2) !== 0 ? $.$get$Future__nullFuture() : new A._Future($.Zone__current, type$._Future_void);
+ return t1;
+ },
+ add$1(_, value) {
+ var t2, _this = this,
+ t1 = A._instanceType(_this);
+ t1._precomputed1._as(value);
+ t2 = _this._state;
+ if (t2 >= 4)
+ throw A.wrapException(_this._badEventState$0());
+ if ((t2 & 1) !== 0)
+ _this._sendData$1(value);
+ else if ((t2 & 3) === 0)
+ _this._ensurePendingEvents$0().add$1(0, new A._DelayedData(value, t1._eval$1("_DelayedData<1>")));
+ },
+ close$0() {
+ var _this = this,
+ t1 = _this._state;
+ if ((t1 & 4) !== 0)
+ return _this._ensureDoneFuture$0();
+ if (t1 >= 4)
+ throw A.wrapException(_this._badEventState$0());
+ t1 = _this._state = t1 | 4;
+ if ((t1 & 1) !== 0)
+ _this._sendDone$0();
+ else if ((t1 & 3) === 0)
+ _this._ensurePendingEvents$0().add$1(0, B.C__DelayedDone);
+ return _this._ensureDoneFuture$0();
+ },
+ _subscribe$4(onData, onError, onDone, cancelOnError) {
+ var t2, t3, t4, t5, t6, t7, t8, subscription, pendingEvents, addState, _this = this,
+ t1 = A._instanceType(_this);
+ t1._eval$1("~(1)?")._as(onData);
+ type$.nullable_void_Function._as(onDone);
+ if ((_this._state & 3) !== 0)
+ throw A.wrapException(A.StateError$("Stream has already been listened to."));
+ t2 = $.Zone__current;
+ t3 = cancelOnError ? 1 : 0;
+ t4 = onError != null ? 32 : 0;
+ t5 = A._BufferingStreamSubscription__registerDataHandler(t2, onData, t1._precomputed1);
+ t6 = A._BufferingStreamSubscription__registerErrorHandler(t2, onError);
+ t7 = onDone == null ? A.async___nullDoneHandler$closure() : onDone;
+ t8 = type$.void_Function;
+ subscription = new A._ControllerSubscription(_this, t5, t6, t8._as(t7), t2, t3 | t4, t1._eval$1("_ControllerSubscription<1>"));
+ pendingEvents = _this.get$_pendingEvents();
+ t4 = _this._state |= 1;
+ if ((t4 & 8) !== 0) {
+ addState = t1._eval$1("_StreamControllerAddStreamState<1>")._as(_this._varData);
+ addState.set$_varData(subscription);
+ addState.resume$0();
+ } else
+ _this._varData = subscription;
+ subscription._setPendingEvents$1(pendingEvents);
+ t1 = t8._as(new A._StreamController__subscribe_closure(_this));
+ t2 = subscription._state;
+ subscription._state = t2 | 64;
+ t1.call$0();
+ subscription._state &= 4294967231;
+ subscription._checkState$1((t2 & 4) !== 0);
+ return subscription;
+ },
+ _recordCancel$1(subscription) {
+ var result, onCancel, cancelResult, e, s, exception, result0, _this = this,
+ t1 = A._instanceType(_this);
+ t1._eval$1("StreamSubscription<1>")._as(subscription);
+ result = null;
+ if ((_this._state & 8) !== 0)
+ result = t1._eval$1("_StreamControllerAddStreamState<1>")._as(_this._varData).cancel$0();
+ _this._varData = null;
+ _this._state = _this._state & 4294967286 | 2;
+ onCancel = _this.onCancel;
+ if (onCancel != null)
+ if (result == null)
+ try {
+ cancelResult = onCancel.call$0();
+ if (cancelResult instanceof A._Future)
+ result = cancelResult;
+ } catch (exception) {
+ e = A.unwrapException(exception);
+ s = A.getTraceFromException(exception);
+ result0 = new A._Future($.Zone__current, type$._Future_void);
+ result0._asyncCompleteError$2(e, s);
+ result = result0;
+ }
+ else
+ result = result.whenComplete$1(onCancel);
+ t1 = new A._StreamController__recordCancel_complete(_this);
+ if (result != null)
+ result = result.whenComplete$1(t1);
+ else
+ t1.call$0();
+ return result;
+ },
+ $isStreamController: 1,
+ $is_StreamControllerLifecycle: 1,
+ $is_EventDispatch: 1
+ };
+ A._StreamController__subscribe_closure.prototype = {
+ call$0() {
+ A._runGuarded(this.$this.onListen);
+ },
+ $signature: 0
+ };
+ A._StreamController__recordCancel_complete.prototype = {
+ call$0() {
+ var doneFuture = this.$this._doneFuture;
+ if (doneFuture != null && (doneFuture._state & 30) === 0)
+ doneFuture._asyncComplete$1(null);
+ },
+ $signature: 0
+ };
+ A._AsyncStreamControllerDispatch.prototype = {
+ _sendData$1(data) {
+ var t1 = this.$ti;
+ t1._precomputed1._as(data);
+ this.get$_subscription()._addPending$1(new A._DelayedData(data, t1._eval$1("_DelayedData<1>")));
+ },
+ _sendError$2(error, stackTrace) {
+ this.get$_subscription()._addPending$1(new A._DelayedError(error, stackTrace));
+ },
+ _sendDone$0() {
+ this.get$_subscription()._addPending$1(B.C__DelayedDone);
+ }
+ };
+ A._AsyncStreamController.prototype = {};
+ A._ControllerStream.prototype = {
+ get$hashCode(_) {
+ return (A.Primitives_objectHashCode(this._controller) ^ 892482866) >>> 0;
+ },
+ $eq(_, other) {
+ if (other == null)
+ return false;
+ if (this === other)
+ return true;
+ return other instanceof A._ControllerStream && other._controller === this._controller;
+ }
+ };
+ A._ControllerSubscription.prototype = {
+ _onCancel$0() {
+ return this._controller._recordCancel$1(this);
+ },
+ _onPause$0() {
+ var t1 = this._controller,
+ t2 = A._instanceType(t1);
+ t2._eval$1("StreamSubscription<1>")._as(this);
+ if ((t1._state & 8) !== 0)
+ t2._eval$1("_StreamControllerAddStreamState<1>")._as(t1._varData).pause$0();
+ A._runGuarded(t1.onPause);
+ },
+ _onResume$0() {
+ var t1 = this._controller,
+ t2 = A._instanceType(t1);
+ t2._eval$1("StreamSubscription<1>")._as(this);
+ if ((t1._state & 8) !== 0)
+ t2._eval$1("_StreamControllerAddStreamState<1>")._as(t1._varData).resume$0();
+ A._runGuarded(t1.onResume);
+ }
+ };
+ A._StreamSinkWrapper.prototype = {};
+ A._BufferingStreamSubscription.prototype = {
+ _setPendingEvents$1(pendingEvents) {
+ var _this = this;
+ A._instanceType(_this)._eval$1("_PendingEvents<1>?")._as(pendingEvents);
+ if (pendingEvents == null)
+ return;
+ _this.set$_pending(pendingEvents);
+ if (pendingEvents.lastPendingEvent != null) {
+ _this._state |= 128;
+ pendingEvents.schedule$1(_this);
+ }
+ },
+ onData$1(handleData) {
+ var t1 = A._instanceType(this);
+ this.set$_onData(A._BufferingStreamSubscription__registerDataHandler(this._zone, t1._eval$1("~(1)?")._as(handleData), t1._precomputed1));
+ },
+ cancel$0() {
+ var t1 = this._state &= 4294967279;
+ if ((t1 & 8) === 0)
+ this._cancel$0();
+ t1 = this._cancelFuture;
+ return t1 == null ? $.$get$Future__nullFuture() : t1;
+ },
+ asFuture$1$1(futureValue, $E) {
+ var result, _this = this, t1 = {};
+ t1.resultValue = null;
+ if (!$E._is(null))
+ throw A.wrapException(A.ArgumentError$notNull("futureValue"));
+ $E._as(futureValue);
+ t1.resultValue = futureValue;
+ result = new A._Future($.Zone__current, $E._eval$1("_Future<0>"));
+ _this.set$_onDone(new A._BufferingStreamSubscription_asFuture_closure(t1, result));
+ _this._state |= 32;
+ _this._onError = new A._BufferingStreamSubscription_asFuture_closure0(_this, result);
+ return result;
+ },
+ _cancel$0() {
+ var t2, _this = this,
+ t1 = _this._state |= 8;
+ if ((t1 & 128) !== 0) {
+ t2 = _this._pending;
+ if (t2._state === 1)
+ t2._state = 3;
+ }
+ if ((t1 & 64) === 0)
+ _this.set$_pending(null);
+ _this._cancelFuture = _this._onCancel$0();
+ },
+ _onPause$0() {
+ },
+ _onResume$0() {
+ },
+ _onCancel$0() {
+ return null;
+ },
+ _addPending$1($event) {
+ var t1, _this = this,
+ pending = _this._pending;
+ if (pending == null) {
+ pending = new A._PendingEvents(A._instanceType(_this)._eval$1("_PendingEvents<1>"));
+ _this.set$_pending(pending);
+ }
+ pending.add$1(0, $event);
+ t1 = _this._state;
+ if ((t1 & 128) === 0) {
+ t1 |= 128;
+ _this._state = t1;
+ if (t1 < 256)
+ pending.schedule$1(_this);
+ }
+ },
+ _sendData$1(data) {
+ var t2, _this = this,
+ t1 = A._instanceType(_this)._precomputed1;
+ t1._as(data);
+ t2 = _this._state;
+ _this._state = t2 | 64;
+ _this._zone.runUnaryGuarded$1$2(_this._onData, data, t1);
+ _this._state &= 4294967231;
+ _this._checkState$1((t2 & 4) !== 0);
+ },
+ _sendError$2(error, stackTrace) {
+ var cancelFuture, _this = this,
+ t1 = _this._state,
+ t2 = new A._BufferingStreamSubscription__sendError_sendError(_this, error, stackTrace);
+ if ((t1 & 1) !== 0) {
+ _this._state = t1 | 16;
+ _this._cancel$0();
+ cancelFuture = _this._cancelFuture;
+ if (cancelFuture != null && cancelFuture !== $.$get$Future__nullFuture())
+ cancelFuture.whenComplete$1(t2);
+ else
+ t2.call$0();
+ } else {
+ t2.call$0();
+ _this._checkState$1((t1 & 4) !== 0);
+ }
+ },
+ _sendDone$0() {
+ var cancelFuture, _this = this,
+ t1 = new A._BufferingStreamSubscription__sendDone_sendDone(_this);
+ _this._cancel$0();
+ _this._state |= 16;
+ cancelFuture = _this._cancelFuture;
+ if (cancelFuture != null && cancelFuture !== $.$get$Future__nullFuture())
+ cancelFuture.whenComplete$1(t1);
+ else
+ t1.call$0();
+ },
+ _checkState$1(wasInputPaused) {
+ var t2, isInputPaused, _this = this,
+ t1 = _this._state;
+ if ((t1 & 128) !== 0 && _this._pending.lastPendingEvent == null) {
+ t1 = _this._state = t1 & 4294967167;
+ if ((t1 & 4) !== 0)
+ if (t1 < 256) {
+ t2 = _this._pending;
+ t2 = t2 == null ? null : t2.lastPendingEvent == null;
+ t2 = t2 !== false;
+ } else
+ t2 = false;
+ else
+ t2 = false;
+ if (t2) {
+ t1 &= 4294967291;
+ _this._state = t1;
+ }
+ }
+ for (; true; wasInputPaused = isInputPaused) {
+ if ((t1 & 8) !== 0) {
+ _this.set$_pending(null);
+ return;
+ }
+ isInputPaused = (t1 & 4) !== 0;
+ if (wasInputPaused === isInputPaused)
+ break;
+ _this._state = t1 ^ 64;
+ if (isInputPaused)
+ _this._onPause$0();
+ else
+ _this._onResume$0();
+ t1 = _this._state &= 4294967231;
+ }
+ if ((t1 & 128) !== 0 && t1 < 256)
+ _this._pending.schedule$1(_this);
+ },
+ set$_onData(_onData) {
+ this._onData = A._instanceType(this)._eval$1("~(1)")._as(_onData);
+ },
+ set$_onDone(_onDone) {
+ this._onDone = type$.void_Function._as(_onDone);
+ },
+ set$_pending(_pending) {
+ this._pending = A._instanceType(this)._eval$1("_PendingEvents<1>?")._as(_pending);
+ },
+ $isStreamSubscription: 1,
+ $is_EventDispatch: 1
+ };
+ A._BufferingStreamSubscription_asFuture_closure.prototype = {
+ call$0() {
+ this.result._complete$1(this._box_0.resultValue);
+ },
+ $signature: 0
+ };
+ A._BufferingStreamSubscription_asFuture_closure0.prototype = {
+ call$2(error, stackTrace) {
+ var cancelFuture = this.$this.cancel$0(),
+ t1 = this.result;
+ if (cancelFuture !== $.$get$Future__nullFuture())
+ cancelFuture.whenComplete$1(new A._BufferingStreamSubscription_asFuture__closure(t1, error, stackTrace));
+ else
+ t1._completeError$2(error, stackTrace);
+ },
+ $signature: 5
+ };
+ A._BufferingStreamSubscription_asFuture__closure.prototype = {
+ call$0() {
+ this.result._completeError$2(this.error, this.stackTrace);
+ },
+ $signature: 2
+ };
+ A._BufferingStreamSubscription__sendError_sendError.prototype = {
+ call$0() {
+ var onError, t3, t4,
+ t1 = this.$this,
+ t2 = t1._state;
+ if ((t2 & 8) !== 0 && (t2 & 16) === 0)
+ return;
+ t1._state = t2 | 64;
+ onError = t1._onError;
+ t2 = this.error;
+ t3 = type$.Object;
+ t4 = t1._zone;
+ if (type$.void_Function_Object_StackTrace._is(onError))
+ t4.runBinaryGuarded$2$3(onError, t2, this.stackTrace, t3, type$.StackTrace);
+ else
+ t4.runUnaryGuarded$1$2(type$.void_Function_Object._as(onError), t2, t3);
+ t1._state &= 4294967231;
+ },
+ $signature: 0
+ };
+ A._BufferingStreamSubscription__sendDone_sendDone.prototype = {
+ call$0() {
+ var t1 = this.$this,
+ t2 = t1._state;
+ if ((t2 & 16) === 0)
+ return;
+ t1._state = t2 | 74;
+ t1._zone.runGuarded$1(t1._onDone);
+ t1._state &= 4294967231;
+ },
+ $signature: 0
+ };
+ A._StreamImpl.prototype = {
+ listen$4$cancelOnError$onDone$onError(onData, cancelOnError, onDone, onError) {
+ var t1 = this.$ti;
+ t1._eval$1("~(1)?")._as(onData);
+ type$.nullable_void_Function._as(onDone);
+ return this._controller._subscribe$4(t1._eval$1("~(1)?")._as(onData), onError, onDone, cancelOnError === true);
+ },
+ listen$1(onData) {
+ return this.listen$4$cancelOnError$onDone$onError(onData, null, null, null);
+ },
+ listen$2$cancelOnError(onData, cancelOnError) {
+ return this.listen$4$cancelOnError$onDone$onError(onData, cancelOnError, null, null);
+ },
+ listen$2$onDone(onData, onDone) {
+ return this.listen$4$cancelOnError$onDone$onError(onData, null, onDone, null);
+ }
+ };
+ A._DelayedEvent.prototype = {
+ set$next(next) {
+ this.next = type$.nullable__DelayedEvent_dynamic._as(next);
+ },
+ get$next() {
+ return this.next;
+ }
+ };
+ A._DelayedData.prototype = {
+ perform$1(dispatch) {
+ this.$ti._eval$1("_EventDispatch<1>")._as(dispatch)._sendData$1(this.value);
+ }
+ };
+ A._DelayedError.prototype = {
+ perform$1(dispatch) {
+ dispatch._sendError$2(this.error, this.stackTrace);
+ }
+ };
+ A._DelayedDone.prototype = {
+ perform$1(dispatch) {
+ dispatch._sendDone$0();
+ },
+ get$next() {
+ return null;
+ },
+ set$next(_) {
+ throw A.wrapException(A.StateError$("No events after a done."));
+ },
+ $is_DelayedEvent: 1
+ };
+ A._PendingEvents.prototype = {
+ schedule$1(dispatch) {
+ var t1, _this = this;
+ _this.$ti._eval$1("_EventDispatch<1>")._as(dispatch);
+ t1 = _this._state;
+ if (t1 === 1)
+ return;
+ if (t1 >= 1) {
+ _this._state = 1;
+ return;
+ }
+ A.scheduleMicrotask(new A._PendingEvents_schedule_closure(_this, dispatch));
+ _this._state = 1;
+ },
+ add$1(_, $event) {
+ var _this = this,
+ lastEvent = _this.lastPendingEvent;
+ if (lastEvent == null)
+ _this.firstPendingEvent = _this.lastPendingEvent = $event;
+ else {
+ lastEvent.set$next($event);
+ _this.lastPendingEvent = $event;
+ }
+ }
+ };
+ A._PendingEvents_schedule_closure.prototype = {
+ call$0() {
+ var t2, $event, nextEvent,
+ t1 = this.$this,
+ oldState = t1._state;
+ t1._state = 0;
+ if (oldState === 3)
+ return;
+ t2 = t1.$ti._eval$1("_EventDispatch<1>")._as(this.dispatch);
+ $event = t1.firstPendingEvent;
+ nextEvent = $event.get$next();
+ t1.firstPendingEvent = nextEvent;
+ if (nextEvent == null)
+ t1.lastPendingEvent = null;
+ $event.perform$1(t2);
+ },
+ $signature: 0
+ };
+ A._StreamIterator.prototype = {};
+ A._cancelAndValue_closure.prototype = {
+ call$0() {
+ return this.future._complete$1(this.value);
+ },
+ $signature: 0
+ };
+ A._Zone.prototype = {$isZone: 1};
+ A._rootHandleError_closure.prototype = {
+ call$0() {
+ A.Error_throwWithStackTrace(this.error, this.stackTrace);
+ },
+ $signature: 0
+ };
+ A._RootZone.prototype = {
+ runGuarded$1(f) {
+ var e, s, exception;
+ type$.void_Function._as(f);
+ try {
+ if (B.C__RootZone === $.Zone__current) {
+ f.call$0();
+ return;
+ }
+ A._rootRun(null, null, this, f, type$.void);
+ } catch (exception) {
+ e = A.unwrapException(exception);
+ s = A.getTraceFromException(exception);
+ A._rootHandleError(type$.Object._as(e), type$.StackTrace._as(s));
+ }
+ },
+ runUnaryGuarded$1$2(f, arg, $T) {
+ var e, s, exception;
+ $T._eval$1("~(0)")._as(f);
+ $T._as(arg);
+ try {
+ if (B.C__RootZone === $.Zone__current) {
+ f.call$1(arg);
+ return;
+ }
+ A._rootRunUnary(null, null, this, f, arg, type$.void, $T);
+ } catch (exception) {
+ e = A.unwrapException(exception);
+ s = A.getTraceFromException(exception);
+ A._rootHandleError(type$.Object._as(e), type$.StackTrace._as(s));
+ }
+ },
+ runBinaryGuarded$2$3(f, arg1, arg2, T1, T2) {
+ var e, s, exception;
+ T1._eval$1("@<0>")._bind$1(T2)._eval$1("~(1,2)")._as(f);
+ T1._as(arg1);
+ T2._as(arg2);
+ try {
+ if (B.C__RootZone === $.Zone__current) {
+ f.call$2(arg1, arg2);
+ return;
+ }
+ A._rootRunBinary(null, null, this, f, arg1, arg2, type$.void, T1, T2);
+ } catch (exception) {
+ e = A.unwrapException(exception);
+ s = A.getTraceFromException(exception);
+ A._rootHandleError(type$.Object._as(e), type$.StackTrace._as(s));
+ }
+ },
+ bindCallbackGuarded$1(f) {
+ return new A._RootZone_bindCallbackGuarded_closure(this, type$.void_Function._as(f));
+ },
+ bindUnaryCallbackGuarded$1$1(f, $T) {
+ return new A._RootZone_bindUnaryCallbackGuarded_closure(this, $T._eval$1("~(0)")._as(f), $T);
+ },
+ run$1$1(f, $R) {
+ $R._eval$1("0()")._as(f);
+ if ($.Zone__current === B.C__RootZone)
+ return f.call$0();
+ return A._rootRun(null, null, this, f, $R);
+ },
+ runUnary$2$2(f, arg, $R, $T) {
+ $R._eval$1("@<0>")._bind$1($T)._eval$1("1(2)")._as(f);
+ $T._as(arg);
+ if ($.Zone__current === B.C__RootZone)
+ return f.call$1(arg);
+ return A._rootRunUnary(null, null, this, f, arg, $R, $T);
+ },
+ runBinary$3$3(f, arg1, arg2, $R, T1, T2) {
+ $R._eval$1("@<0>")._bind$1(T1)._bind$1(T2)._eval$1("1(2,3)")._as(f);
+ T1._as(arg1);
+ T2._as(arg2);
+ if ($.Zone__current === B.C__RootZone)
+ return f.call$2(arg1, arg2);
+ return A._rootRunBinary(null, null, this, f, arg1, arg2, $R, T1, T2);
+ },
+ registerBinaryCallback$3$1(f, $R, T1, T2) {
+ return $R._eval$1("@<0>")._bind$1(T1)._bind$1(T2)._eval$1("1(2,3)")._as(f);
+ }
+ };
+ A._RootZone_bindCallbackGuarded_closure.prototype = {
+ call$0() {
+ return this.$this.runGuarded$1(this.f);
+ },
+ $signature: 0
+ };
+ A._RootZone_bindUnaryCallbackGuarded_closure.prototype = {
+ call$1(arg) {
+ var t1 = this.T;
+ return this.$this.runUnaryGuarded$1$2(this.f, t1._as(arg), t1);
+ },
+ $signature() {
+ return this.T._eval$1("~(0)");
+ }
+ };
+ A._HashMap.prototype = {
+ get$length(_) {
+ return this._collection$_length;
+ },
+ get$isEmpty(_) {
+ return this._collection$_length === 0;
+ },
+ get$keys() {
+ return new A._HashMapKeyIterable(this, this.$ti._eval$1("_HashMapKeyIterable<1>"));
+ },
+ containsKey$1(key) {
+ var strings, nums;
+ if (typeof key == "string" && key !== "__proto__") {
+ strings = this._collection$_strings;
+ return strings == null ? false : strings[key] != null;
+ } else if (typeof key == "number" && (key & 1073741823) === key) {
+ nums = this._collection$_nums;
+ return nums == null ? false : nums[key] != null;
+ } else
+ return this._containsKey$1(key);
+ },
+ _containsKey$1(key) {
+ var rest = this._collection$_rest;
+ if (rest == null)
+ return false;
+ return this._findBucketIndex$2(this._getBucket$2(rest, key), key) >= 0;
+ },
+ $index(_, key) {
+ var strings, t1, nums;
+ if (typeof key == "string" && key !== "__proto__") {
+ strings = this._collection$_strings;
+ t1 = strings == null ? null : A._HashMap__getTableEntry(strings, key);
+ return t1;
+ } else if (typeof key == "number" && (key & 1073741823) === key) {
+ nums = this._collection$_nums;
+ t1 = nums == null ? null : A._HashMap__getTableEntry(nums, key);
+ return t1;
+ } else
+ return this._get$1(key);
+ },
+ _get$1(key) {
+ var bucket, index,
+ rest = this._collection$_rest;
+ if (rest == null)
+ return null;
+ bucket = this._getBucket$2(rest, key);
+ index = this._findBucketIndex$2(bucket, key);
+ return index < 0 ? null : bucket[index + 1];
+ },
+ $indexSet(_, key, value) {
+ var strings, nums, rest, hash, bucket, index, _this = this,
+ t1 = _this.$ti;
+ t1._precomputed1._as(key);
+ t1._rest[1]._as(value);
+ if (typeof key == "string" && key !== "__proto__") {
+ strings = _this._collection$_strings;
+ _this._collection$_addHashTableEntry$3(strings == null ? _this._collection$_strings = A._HashMap__newHashTable() : strings, key, value);
+ } else if (typeof key == "number" && (key & 1073741823) === key) {
+ nums = _this._collection$_nums;
+ _this._collection$_addHashTableEntry$3(nums == null ? _this._collection$_nums = A._HashMap__newHashTable() : nums, key, value);
+ } else {
+ rest = _this._collection$_rest;
+ if (rest == null)
+ rest = _this._collection$_rest = A._HashMap__newHashTable();
+ hash = A.objectHashCode(key) & 1073741823;
+ bucket = rest[hash];
+ if (bucket == null) {
+ A._HashMap__setTableEntry(rest, hash, [key, value]);
+ ++_this._collection$_length;
+ _this._collection$_keys = null;
+ } else {
+ index = _this._findBucketIndex$2(bucket, key);
+ if (index >= 0)
+ bucket[index + 1] = value;
+ else {
+ bucket.push(key, value);
+ ++_this._collection$_length;
+ _this._collection$_keys = null;
+ }
+ }
+ }
+ },
+ forEach$1(_, action) {
+ var keys, $length, t2, i, key, t3, _this = this,
+ t1 = _this.$ti;
+ t1._eval$1("~(1,2)")._as(action);
+ keys = _this._computeKeys$0();
+ for ($length = keys.length, t2 = t1._precomputed1, t1 = t1._rest[1], i = 0; i < $length; ++i) {
+ key = keys[i];
+ t2._as(key);
+ t3 = _this.$index(0, key);
+ action.call$2(key, t3 == null ? t1._as(t3) : t3);
+ if (keys !== _this._collection$_keys)
+ throw A.wrapException(A.ConcurrentModificationError$(_this));
+ }
+ },
+ _computeKeys$0() {
+ var strings, names, entries, index, i, nums, rest, bucket, $length, i0, _this = this,
+ result = _this._collection$_keys;
+ if (result != null)
+ return result;
+ result = A.List_List$filled(_this._collection$_length, null, false, type$.dynamic);
+ strings = _this._collection$_strings;
+ if (strings != null) {
+ names = Object.getOwnPropertyNames(strings);
+ entries = names.length;
+ for (index = 0, i = 0; i < entries; ++i) {
+ result[index] = names[i];
+ ++index;
+ }
+ } else
+ index = 0;
+ nums = _this._collection$_nums;
+ if (nums != null) {
+ names = Object.getOwnPropertyNames(nums);
+ entries = names.length;
+ for (i = 0; i < entries; ++i) {
+ result[index] = +names[i];
+ ++index;
+ }
+ }
+ rest = _this._collection$_rest;
+ if (rest != null) {
+ names = Object.getOwnPropertyNames(rest);
+ entries = names.length;
+ for (i = 0; i < entries; ++i) {
+ bucket = rest[names[i]];
+ $length = bucket.length;
+ for (i0 = 0; i0 < $length; i0 += 2) {
+ result[index] = bucket[i0];
+ ++index;
+ }
+ }
+ }
+ return _this._collection$_keys = result;
+ },
+ _collection$_addHashTableEntry$3(table, key, value) {
+ var t1 = this.$ti;
+ t1._precomputed1._as(key);
+ t1._rest[1]._as(value);
+ if (table[key] == null) {
+ ++this._collection$_length;
+ this._collection$_keys = null;
+ }
+ A._HashMap__setTableEntry(table, key, value);
+ },
+ _getBucket$2(table, key) {
+ return table[A.objectHashCode(key) & 1073741823];
+ }
+ };
+ A._IdentityHashMap.prototype = {
+ _findBucketIndex$2(bucket, key) {
+ var $length, i, t1;
+ if (bucket == null)
+ return -1;
+ $length = bucket.length;
+ for (i = 0; i < $length; i += 2) {
+ t1 = bucket[i];
+ if (t1 == null ? key == null : t1 === key)
+ return i;
+ }
+ return -1;
+ }
+ };
+ A._HashMapKeyIterable.prototype = {
+ get$length(_) {
+ return this._collection$_map._collection$_length;
+ },
+ get$isEmpty(_) {
+ return this._collection$_map._collection$_length === 0;
+ },
+ get$iterator(_) {
+ var t1 = this._collection$_map;
+ return new A._HashMapKeyIterator(t1, t1._computeKeys$0(), this.$ti._eval$1("_HashMapKeyIterator<1>"));
+ }
+ };
+ A._HashMapKeyIterator.prototype = {
+ get$current() {
+ var t1 = this._collection$_current;
+ return t1 == null ? this.$ti._precomputed1._as(t1) : t1;
+ },
+ moveNext$0() {
+ var _this = this,
+ keys = _this._collection$_keys,
+ offset = _this._offset,
+ t1 = _this._collection$_map;
+ if (keys !== t1._collection$_keys)
+ throw A.wrapException(A.ConcurrentModificationError$(t1));
+ else if (offset >= keys.length) {
+ _this.set$_collection$_current(null);
+ return false;
+ } else {
+ _this.set$_collection$_current(keys[offset]);
+ _this._offset = offset + 1;
+ return true;
+ }
+ },
+ set$_collection$_current(_current) {
+ this._collection$_current = this.$ti._eval$1("1?")._as(_current);
+ }
+ };
+ A.ListBase.prototype = {
+ get$iterator(receiver) {
+ return new A.ListIterator(receiver, this.get$length(receiver), A.instanceType(receiver)._eval$1("ListIterator<ListBase.E>"));
+ },
+ elementAt$1(receiver, index) {
+ return this.$index(receiver, index);
+ },
+ get$isNotEmpty(receiver) {
+ return this.get$length(receiver) !== 0;
+ },
+ toString$0(receiver) {
+ return A.Iterable_iterableToFullString(receiver, "[", "]");
+ }
+ };
+ A.MapBase.prototype = {
+ forEach$1(_, action) {
+ var t2, key, t3,
+ t1 = A._instanceType(this);
+ t1._eval$1("~(MapBase.K,MapBase.V)")._as(action);
+ for (t2 = this.get$keys(), t2 = t2.get$iterator(t2), t1 = t1._eval$1("MapBase.V"); t2.moveNext$0();) {
+ key = t2.get$current();
+ t3 = this.$index(0, key);
+ action.call$2(key, t3 == null ? t1._as(t3) : t3);
+ }
+ },
+ get$length(_) {
+ var t1 = this.get$keys();
+ return t1.get$length(t1);
+ },
+ get$isEmpty(_) {
+ var t1 = this.get$keys();
+ return t1.get$isEmpty(t1);
+ },
+ toString$0(_) {
+ return A.MapBase_mapToString(this);
+ },
+ $isMap: 1
+ };
+ A.MapBase_mapToString_closure.prototype = {
+ call$2(k, v) {
+ var t2,
+ t1 = this._box_0;
+ if (!t1.first)
+ this.result._contents += ", ";
+ t1.first = false;
+ t1 = this.result;
+ t2 = A.S(k);
+ t2 = t1._contents += t2;
+ t1._contents = t2 + ": ";
+ t2 = A.S(v);
+ t1._contents += t2;
+ },
+ $signature: 10
+ };
+ A._UnmodifiableMapMixin.prototype = {};
+ A.MapView.prototype = {
+ forEach$1(_, action) {
+ this._collection$_map.forEach$1(0, A._instanceType(this)._eval$1("~(1,2)")._as(action));
+ },
+ get$isEmpty(_) {
+ return this._collection$_map.__js_helper$_length === 0;
+ },
+ get$length(_) {
+ return this._collection$_map.__js_helper$_length;
+ },
+ toString$0(_) {
+ return A.MapBase_mapToString(this._collection$_map);
+ },
+ $isMap: 1
+ };
+ A.UnmodifiableMapView.prototype = {};
+ A.ListQueue.prototype = {
+ get$iterator(_) {
+ var _this = this;
+ return new A._ListQueueIterator(_this, _this._tail, _this._modificationCount, _this._head, _this.$ti._eval$1("_ListQueueIterator<1>"));
+ },
+ get$isEmpty(_) {
+ return this._head === this._tail;
+ },
+ get$length(_) {
+ return (this._tail - this._head & this._table.length - 1) >>> 0;
+ },
+ elementAt$1(_, index) {
+ var t2, t3, _this = this,
+ t1 = _this.get$length(0);
+ if (0 > index || index >= t1)
+ A.throwExpression(A.IndexError$withLength(index, t1, _this, null, "index"));
+ t1 = _this._table;
+ t2 = t1.length;
+ t3 = (_this._head + index & t2 - 1) >>> 0;
+ if (!(t3 >= 0 && t3 < t2))
+ return A.ioore(t1, t3);
+ t3 = t1[t3];
+ return t3 == null ? _this.$ti._precomputed1._as(t3) : t3;
+ },
+ toString$0(_) {
+ return A.Iterable_iterableToFullString(this, "{", "}");
+ },
+ removeFirst$0() {
+ var t2, result, _this = this,
+ t1 = _this._head;
+ if (t1 === _this._tail)
+ throw A.wrapException(A.IterableElementError_noElement());
+ ++_this._modificationCount;
+ t2 = _this._table;
+ if (!(t1 < t2.length))
+ return A.ioore(t2, t1);
+ result = t2[t1];
+ if (result == null)
+ result = _this.$ti._precomputed1._as(result);
+ B.JSArray_methods.$indexSet(t2, t1, null);
+ _this._head = (_this._head + 1 & _this._table.length - 1) >>> 0;
+ return result;
+ },
+ _add$1(element) {
+ var t2, t3, newTable, split, _this = this,
+ t1 = _this.$ti;
+ t1._precomputed1._as(element);
+ B.JSArray_methods.$indexSet(_this._table, _this._tail, element);
+ t2 = _this._tail;
+ t3 = _this._table.length;
+ t2 = (t2 + 1 & t3 - 1) >>> 0;
+ _this._tail = t2;
+ if (_this._head === t2) {
+ newTable = A.List_List$filled(t3 * 2, null, false, t1._eval$1("1?"));
+ t1 = _this._table;
+ t2 = _this._head;
+ split = t1.length - t2;
+ B.JSArray_methods.setRange$4(newTable, 0, split, t1, t2);
+ B.JSArray_methods.setRange$4(newTable, split, split + _this._head, _this._table, 0);
+ _this._head = 0;
+ _this._tail = _this._table.length;
+ _this.set$_table(newTable);
+ }
+ ++_this._modificationCount;
+ },
+ set$_table(_table) {
+ this._table = this.$ti._eval$1("List<1?>")._as(_table);
+ },
+ $isQueue: 1
+ };
+ A._ListQueueIterator.prototype = {
+ get$current() {
+ var t1 = this._collection$_current;
+ return t1 == null ? this.$ti._precomputed1._as(t1) : t1;
+ },
+ moveNext$0() {
+ var t2, t3, _this = this,
+ t1 = _this._queue;
+ if (_this._modificationCount !== t1._modificationCount)
+ A.throwExpression(A.ConcurrentModificationError$(t1));
+ t2 = _this._position;
+ if (t2 === _this._end) {
+ _this.set$_collection$_current(null);
+ return false;
+ }
+ t3 = t1._table;
+ if (!(t2 < t3.length))
+ return A.ioore(t3, t2);
+ _this.set$_collection$_current(t3[t2]);
+ _this._position = (_this._position + 1 & t1._table.length - 1) >>> 0;
+ return true;
+ },
+ set$_collection$_current(_current) {
+ this._collection$_current = this.$ti._eval$1("1?")._as(_current);
+ }
+ };
+ A._UnmodifiableMapView_MapView__UnmodifiableMapMixin.prototype = {};
+ A._JsonMap.prototype = {
+ $index(_, key) {
+ var result,
+ t1 = this._processed;
+ if (t1 == null)
+ return this._data.$index(0, key);
+ else if (typeof key != "string")
+ return null;
+ else {
+ result = t1[key];
+ return typeof result == "undefined" ? this._process$1(key) : result;
+ }
+ },
+ get$length(_) {
+ return this._processed == null ? this._data.__js_helper$_length : this._convert$_computeKeys$0().length;
+ },
+ get$isEmpty(_) {
+ return this.get$length(0) === 0;
+ },
+ get$keys() {
+ if (this._processed == null) {
+ var t1 = this._data;
+ return new A.LinkedHashMapKeyIterable(t1, A._instanceType(t1)._eval$1("LinkedHashMapKeyIterable<1>"));
+ }
+ return new A._JsonMapKeyIterable(this);
+ },
+ forEach$1(_, f) {
+ var keys, i, key, value, _this = this;
+ type$.void_Function_String_dynamic._as(f);
+ if (_this._processed == null)
+ return _this._data.forEach$1(0, f);
+ keys = _this._convert$_computeKeys$0();
+ for (i = 0; i < keys.length; ++i) {
+ key = keys[i];
+ value = _this._processed[key];
+ if (typeof value == "undefined") {
+ value = A._convertJsonToDartLazy(_this._original[key]);
+ _this._processed[key] = value;
+ }
+ f.call$2(key, value);
+ if (keys !== _this._data)
+ throw A.wrapException(A.ConcurrentModificationError$(_this));
+ }
+ },
+ _convert$_computeKeys$0() {
+ var keys = type$.nullable_List_dynamic._as(this._data);
+ if (keys == null)
+ keys = this._data = A._setArrayType(Object.keys(this._original), type$.JSArray_String);
+ return keys;
+ },
+ _process$1(key) {
+ var result;
+ if (!Object.prototype.hasOwnProperty.call(this._original, key))
+ return null;
+ result = A._convertJsonToDartLazy(this._original[key]);
+ return this._processed[key] = result;
+ }
+ };
+ A._JsonMapKeyIterable.prototype = {
+ get$length(_) {
+ return this._parent.get$length(0);
+ },
+ elementAt$1(_, index) {
+ var t1 = this._parent;
+ if (t1._processed == null)
+ t1 = t1.get$keys().elementAt$1(0, index);
+ else {
+ t1 = t1._convert$_computeKeys$0();
+ if (!(index >= 0 && index < t1.length))
+ return A.ioore(t1, index);
+ t1 = t1[index];
+ }
+ return t1;
+ },
+ get$iterator(_) {
+ var t1 = this._parent;
+ if (t1._processed == null) {
+ t1 = t1.get$keys();
+ t1 = t1.get$iterator(t1);
+ } else {
+ t1 = t1._convert$_computeKeys$0();
+ t1 = new J.ArrayIterator(t1, t1.length, A._arrayInstanceType(t1)._eval$1("ArrayIterator<1>"));
+ }
+ return t1;
+ }
+ };
+ A.Codec.prototype = {};
+ A.Converter.prototype = {};
+ A.JsonUnsupportedObjectError.prototype = {
+ toString$0(_) {
+ var safeString = A.Error_safeToString(this.unsupportedObject);
+ return (this.cause != null ? "Converting object to an encodable object failed:" : "Converting object did not return an encodable object:") + " " + safeString;
+ }
+ };
+ A.JsonCyclicError.prototype = {
+ toString$0(_) {
+ return "Cyclic error in JSON stringify";
+ }
+ };
+ A.JsonCodec.prototype = {
+ decode$2$reviver(source, reviver) {
+ var t1 = A._parseJson(source, this.get$decoder()._reviver);
+ return t1;
+ },
+ encode$2$toEncodable(value, toEncodable) {
+ var t1 = A._JsonStringStringifier_stringify(value, this.get$encoder()._toEncodable, null);
+ return t1;
+ },
+ get$encoder() {
+ return B.JsonEncoder_null;
+ },
+ get$decoder() {
+ return B.JsonDecoder_null;
+ }
+ };
+ A.JsonEncoder.prototype = {};
+ A.JsonDecoder.prototype = {};
+ A._JsonStringifier.prototype = {
+ writeStringContent$1(s) {
+ var offset, i, charCode, t1, t2, _this = this,
+ $length = s.length;
+ for (offset = 0, i = 0; i < $length; ++i) {
+ charCode = s.charCodeAt(i);
+ if (charCode > 92) {
+ if (charCode >= 55296) {
+ t1 = charCode & 64512;
+ if (t1 === 55296) {
+ t2 = i + 1;
+ t2 = !(t2 < $length && (s.charCodeAt(t2) & 64512) === 56320);
+ } else
+ t2 = false;
+ if (!t2)
+ if (t1 === 56320) {
+ t1 = i - 1;
+ t1 = !(t1 >= 0 && (s.charCodeAt(t1) & 64512) === 55296);
+ } else
+ t1 = false;
+ else
+ t1 = true;
+ if (t1) {
+ if (i > offset)
+ _this.writeStringSlice$3(s, offset, i);
+ offset = i + 1;
+ _this.writeCharCode$1(92);
+ _this.writeCharCode$1(117);
+ _this.writeCharCode$1(100);
+ t1 = charCode >>> 8 & 15;
+ _this.writeCharCode$1(t1 < 10 ? 48 + t1 : 87 + t1);
+ t1 = charCode >>> 4 & 15;
+ _this.writeCharCode$1(t1 < 10 ? 48 + t1 : 87 + t1);
+ t1 = charCode & 15;
+ _this.writeCharCode$1(t1 < 10 ? 48 + t1 : 87 + t1);
+ }
+ }
+ continue;
+ }
+ if (charCode < 32) {
+ if (i > offset)
+ _this.writeStringSlice$3(s, offset, i);
+ offset = i + 1;
+ _this.writeCharCode$1(92);
+ switch (charCode) {
+ case 8:
+ _this.writeCharCode$1(98);
+ break;
+ case 9:
+ _this.writeCharCode$1(116);
+ break;
+ case 10:
+ _this.writeCharCode$1(110);
+ break;
+ case 12:
+ _this.writeCharCode$1(102);
+ break;
+ case 13:
+ _this.writeCharCode$1(114);
+ break;
+ default:
+ _this.writeCharCode$1(117);
+ _this.writeCharCode$1(48);
+ _this.writeCharCode$1(48);
+ t1 = charCode >>> 4 & 15;
+ _this.writeCharCode$1(t1 < 10 ? 48 + t1 : 87 + t1);
+ t1 = charCode & 15;
+ _this.writeCharCode$1(t1 < 10 ? 48 + t1 : 87 + t1);
+ break;
+ }
+ } else if (charCode === 34 || charCode === 92) {
+ if (i > offset)
+ _this.writeStringSlice$3(s, offset, i);
+ offset = i + 1;
+ _this.writeCharCode$1(92);
+ _this.writeCharCode$1(charCode);
+ }
+ }
+ if (offset === 0)
+ _this.writeString$1(s);
+ else if (offset < $length)
+ _this.writeStringSlice$3(s, offset, $length);
+ },
+ _checkCycle$1(object) {
+ var t1, t2, i, t3;
+ for (t1 = this._seen, t2 = t1.length, i = 0; i < t2; ++i) {
+ t3 = t1[i];
+ if (object == null ? t3 == null : object === t3)
+ throw A.wrapException(new A.JsonCyclicError(object, null));
+ }
+ B.JSArray_methods.add$1(t1, object);
+ },
+ writeObject$1(object) {
+ var customJson, e, t1, exception, _this = this;
+ if (_this.writeJsonValue$1(object))
+ return;
+ _this._checkCycle$1(object);
+ try {
+ customJson = _this._toEncodable.call$1(object);
+ if (!_this.writeJsonValue$1(customJson)) {
+ t1 = A.JsonUnsupportedObjectError$(object, null, _this.get$_partialResult());
+ throw A.wrapException(t1);
+ }
+ t1 = _this._seen;
+ if (0 >= t1.length)
+ return A.ioore(t1, -1);
+ t1.pop();
+ } catch (exception) {
+ e = A.unwrapException(exception);
+ t1 = A.JsonUnsupportedObjectError$(object, e, _this.get$_partialResult());
+ throw A.wrapException(t1);
+ }
+ },
+ writeJsonValue$1(object) {
+ var t1, success, _this = this;
+ if (typeof object == "number") {
+ if (!isFinite(object))
+ return false;
+ _this.writeNumber$1(object);
+ return true;
+ } else if (object === true) {
+ _this.writeString$1("true");
+ return true;
+ } else if (object === false) {
+ _this.writeString$1("false");
+ return true;
+ } else if (object == null) {
+ _this.writeString$1("null");
+ return true;
+ } else if (typeof object == "string") {
+ _this.writeString$1('"');
+ _this.writeStringContent$1(object);
+ _this.writeString$1('"');
+ return true;
+ } else if (type$.List_dynamic._is(object)) {
+ _this._checkCycle$1(object);
+ _this.writeList$1(object);
+ t1 = _this._seen;
+ if (0 >= t1.length)
+ return A.ioore(t1, -1);
+ t1.pop();
+ return true;
+ } else if (type$.Map_dynamic_dynamic._is(object)) {
+ _this._checkCycle$1(object);
+ success = _this.writeMap$1(object);
+ t1 = _this._seen;
+ if (0 >= t1.length)
+ return A.ioore(t1, -1);
+ t1.pop();
+ return success;
+ } else
+ return false;
+ },
+ writeList$1(list) {
+ var t1, i, _this = this;
+ _this.writeString$1("[");
+ t1 = J.getInterceptor$asx(list);
+ if (t1.get$isNotEmpty(list)) {
+ _this.writeObject$1(t1.$index(list, 0));
+ for (i = 1; i < t1.get$length(list); ++i) {
+ _this.writeString$1(",");
+ _this.writeObject$1(t1.$index(list, i));
+ }
+ }
+ _this.writeString$1("]");
+ },
+ writeMap$1(map) {
+ var t1, keyValueList, i, separator, t2, _this = this, _box_0 = {};
+ if (map.get$isEmpty(map)) {
+ _this.writeString$1("{}");
+ return true;
+ }
+ t1 = map.get$length(map) * 2;
+ keyValueList = A.List_List$filled(t1, null, false, type$.nullable_Object);
+ i = _box_0.i = 0;
+ _box_0.allStringKeys = true;
+ map.forEach$1(0, new A._JsonStringifier_writeMap_closure(_box_0, keyValueList));
+ if (!_box_0.allStringKeys)
+ return false;
+ _this.writeString$1("{");
+ for (separator = '"'; i < t1; i += 2, separator = ',"') {
+ _this.writeString$1(separator);
+ _this.writeStringContent$1(A._asString(keyValueList[i]));
+ _this.writeString$1('":');
+ t2 = i + 1;
+ if (!(t2 < t1))
+ return A.ioore(keyValueList, t2);
+ _this.writeObject$1(keyValueList[t2]);
+ }
+ _this.writeString$1("}");
+ return true;
+ }
+ };
+ A._JsonStringifier_writeMap_closure.prototype = {
+ call$2(key, value) {
+ var t1, t2;
+ if (typeof key != "string")
+ this._box_0.allStringKeys = false;
+ t1 = this.keyValueList;
+ t2 = this._box_0;
+ B.JSArray_methods.$indexSet(t1, t2.i++, key);
+ B.JSArray_methods.$indexSet(t1, t2.i++, value);
+ },
+ $signature: 10
+ };
+ A._JsonStringStringifier.prototype = {
+ get$_partialResult() {
+ var t1 = this._sink._contents;
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ writeNumber$1(number) {
+ var t1 = this._sink,
+ t2 = B.JSNumber_methods.toString$0(number);
+ t1._contents += t2;
+ },
+ writeString$1(string) {
+ this._sink._contents += string;
+ },
+ writeStringSlice$3(string, start, end) {
+ this._sink._contents += B.JSString_methods.substring$2(string, start, end);
+ },
+ writeCharCode$1(charCode) {
+ var t1 = this._sink,
+ t2 = A.Primitives_stringFromCharCode(charCode);
+ t1._contents += t2;
+ }
+ };
+ A.NoSuchMethodError_toString_closure.prototype = {
+ call$2(key, value) {
+ var t1, t2, t3;
+ type$.Symbol._as(key);
+ t1 = this.sb;
+ t2 = this._box_0;
+ t3 = t1._contents += t2.comma;
+ t3 += key._name;
+ t1._contents = t3;
+ t1._contents = t3 + ": ";
+ t3 = A.Error_safeToString(value);
+ t1._contents += t3;
+ t2.comma = ", ";
+ },
+ $signature: 19
+ };
+ A.DateTime.prototype = {
+ $eq(_, other) {
+ if (other == null)
+ return false;
+ return other instanceof A.DateTime && this._value === other._value && this.isUtc === other.isUtc;
+ },
+ get$hashCode(_) {
+ var t1 = this._value;
+ return (t1 ^ B.JSInt_methods._shrOtherPositive$1(t1, 30)) & 1073741823;
+ },
+ toString$0(_) {
+ var _this = this,
+ y = A.DateTime__fourDigits(A.Primitives_getYear(_this)),
+ m = A.DateTime__twoDigits(A.Primitives_getMonth(_this)),
+ d = A.DateTime__twoDigits(A.Primitives_getDay(_this)),
+ h = A.DateTime__twoDigits(A.Primitives_getHours(_this)),
+ min = A.DateTime__twoDigits(A.Primitives_getMinutes(_this)),
+ sec = A.DateTime__twoDigits(A.Primitives_getSeconds(_this)),
+ ms = A.DateTime__threeDigits(A.Primitives_getMilliseconds(_this)),
+ t1 = y + "-" + m;
+ if (_this.isUtc)
+ return t1 + "-" + d + " " + h + ":" + min + ":" + sec + "." + ms + "Z";
+ else
+ return t1 + "-" + d + " " + h + ":" + min + ":" + sec + "." + ms;
+ }
+ };
+ A.Duration.prototype = {
+ $eq(_, other) {
+ if (other == null)
+ return false;
+ return other instanceof A.Duration && this._duration === other._duration;
+ },
+ get$hashCode(_) {
+ return B.JSInt_methods.get$hashCode(this._duration);
+ },
+ toString$0(_) {
+ var minutesPadding, seconds, secondsPadding,
+ microseconds = this._duration,
+ microseconds0 = microseconds % 3600000000,
+ minutes = B.JSInt_methods._tdivFast$1(microseconds0, 60000000);
+ microseconds0 %= 60000000;
+ minutesPadding = minutes < 10 ? "0" : "";
+ seconds = B.JSInt_methods._tdivFast$1(microseconds0, 1000000);
+ secondsPadding = seconds < 10 ? "0" : "";
+ return "" + (microseconds / 3600000000 | 0) + ":" + minutesPadding + minutes + ":" + secondsPadding + seconds + "." + B.JSString_methods.padLeft$2(B.JSInt_methods.toString$0(microseconds0 % 1000000), 6, "0");
+ }
+ };
+ A.Error.prototype = {
+ get$stackTrace() {
+ return A.getTraceFromException(this.$thrownJsError);
+ }
+ };
+ A.AssertionError.prototype = {
+ toString$0(_) {
+ var t1 = this.message;
+ if (t1 != null)
+ return "Assertion failed: " + A.Error_safeToString(t1);
+ return "Assertion failed";
+ }
+ };
+ A.TypeError.prototype = {};
+ A.ArgumentError.prototype = {
+ get$_errorName() {
+ return "Invalid argument" + (!this._hasValue ? "(s)" : "");
+ },
+ get$_errorExplanation() {
+ return "";
+ },
+ toString$0(_) {
+ var _this = this,
+ $name = _this.name,
+ nameString = $name == null ? "" : " (" + $name + ")",
+ message = _this.message,
+ messageString = message == null ? "" : ": " + A.S(message),
+ prefix = _this.get$_errorName() + nameString + messageString;
+ if (!_this._hasValue)
+ return prefix;
+ return prefix + _this.get$_errorExplanation() + ": " + A.Error_safeToString(_this.get$invalidValue());
+ },
+ get$invalidValue() {
+ return this.invalidValue;
+ }
+ };
+ A.RangeError.prototype = {
+ get$invalidValue() {
+ return A._asNumQ(this.invalidValue);
+ },
+ get$_errorName() {
+ return "RangeError";
+ },
+ get$_errorExplanation() {
+ var explanation,
+ start = this.start,
+ end = this.end;
+ if (start == null)
+ explanation = end != null ? ": Not less than or equal to " + A.S(end) : "";
+ else if (end == null)
+ explanation = ": Not greater than or equal to " + A.S(start);
+ else if (end > start)
+ explanation = ": Not in inclusive range " + A.S(start) + ".." + A.S(end);
+ else
+ explanation = end < start ? ": Valid value range is empty" : ": Only valid value is " + A.S(start);
+ return explanation;
+ }
+ };
+ A.IndexError.prototype = {
+ get$invalidValue() {
+ return A._asInt(this.invalidValue);
+ },
+ get$_errorName() {
+ return "RangeError";
+ },
+ get$_errorExplanation() {
+ if (A._asInt(this.invalidValue) < 0)
+ return ": index must not be negative";
+ var t1 = this.length;
+ if (t1 === 0)
+ return ": no indices are valid";
+ return ": index should be less than " + t1;
+ },
+ get$length(receiver) {
+ return this.length;
+ }
+ };
+ A.NoSuchMethodError.prototype = {
+ toString$0(_) {
+ var $arguments, t1, _i, t2, t3, argument, receiverText, actualParameters, _this = this, _box_0 = {},
+ sb = new A.StringBuffer("");
+ _box_0.comma = "";
+ $arguments = _this._core$_arguments;
+ for (t1 = $arguments.length, _i = 0, t2 = "", t3 = ""; _i < t1; ++_i, t3 = ", ") {
+ argument = $arguments[_i];
+ sb._contents = t2 + t3;
+ t2 = A.Error_safeToString(argument);
+ t2 = sb._contents += t2;
+ _box_0.comma = ", ";
+ }
+ _this._namedArguments.forEach$1(0, new A.NoSuchMethodError_toString_closure(_box_0, sb));
+ receiverText = A.Error_safeToString(_this._core$_receiver);
+ actualParameters = sb.toString$0(0);
+ return "NoSuchMethodError: method not found: '" + _this._core$_memberName._name + "'\nReceiver: " + receiverText + "\nArguments: [" + actualParameters + "]";
+ }
+ };
+ A.UnsupportedError.prototype = {
+ toString$0(_) {
+ return "Unsupported operation: " + this.message;
+ }
+ };
+ A.UnimplementedError.prototype = {
+ toString$0(_) {
+ return "UnimplementedError: " + this.message;
+ }
+ };
+ A.StateError.prototype = {
+ toString$0(_) {
+ return "Bad state: " + this.message;
+ }
+ };
+ A.ConcurrentModificationError.prototype = {
+ toString$0(_) {
+ var t1 = this.modifiedObject;
+ if (t1 == null)
+ return "Concurrent modification during iteration.";
+ return "Concurrent modification during iteration: " + A.Error_safeToString(t1) + ".";
+ }
+ };
+ A.OutOfMemoryError.prototype = {
+ toString$0(_) {
+ return "Out of Memory";
+ },
+ get$stackTrace() {
+ return null;
+ },
+ $isError: 1
+ };
+ A.StackOverflowError.prototype = {
+ toString$0(_) {
+ return "Stack Overflow";
+ },
+ get$stackTrace() {
+ return null;
+ },
+ $isError: 1
+ };
+ A._Exception.prototype = {
+ toString$0(_) {
+ return "Exception: " + this.message;
+ }
+ };
+ A.FormatException.prototype = {
+ toString$0(_) {
+ var t1, lineEnd, lineNum, lineStart, previousCharWasCR, i, char, end, start, prefix, postfix,
+ message = this.message,
+ report = "" !== message ? "FormatException: " + message : "FormatException",
+ offset = this.offset,
+ source = this.source;
+ if (typeof source == "string") {
+ if (offset != null)
+ t1 = offset < 0 || offset > source.length;
+ else
+ t1 = false;
+ if (t1)
+ offset = null;
+ if (offset == null) {
+ if (source.length > 78)
+ source = B.JSString_methods.substring$2(source, 0, 75) + "...";
+ return report + "\n" + source;
+ }
+ for (lineEnd = source.length, lineNum = 1, lineStart = 0, previousCharWasCR = false, i = 0; i < offset; ++i) {
+ if (!(i < lineEnd))
+ return A.ioore(source, i);
+ char = source.charCodeAt(i);
+ if (char === 10) {
+ if (lineStart !== i || !previousCharWasCR)
+ ++lineNum;
+ lineStart = i + 1;
+ previousCharWasCR = false;
+ } else if (char === 13) {
+ ++lineNum;
+ lineStart = i + 1;
+ previousCharWasCR = true;
+ }
+ }
+ report = lineNum > 1 ? report + (" (at line " + lineNum + ", character " + (offset - lineStart + 1) + ")\n") : report + (" (at character " + (offset + 1) + ")\n");
+ for (i = offset; i < lineEnd; ++i) {
+ if (!(i >= 0))
+ return A.ioore(source, i);
+ char = source.charCodeAt(i);
+ if (char === 10 || char === 13) {
+ lineEnd = i;
+ break;
+ }
+ }
+ if (lineEnd - lineStart > 78)
+ if (offset - lineStart < 75) {
+ end = lineStart + 75;
+ start = lineStart;
+ prefix = "";
+ postfix = "...";
+ } else {
+ if (lineEnd - offset < 75) {
+ start = lineEnd - 75;
+ end = lineEnd;
+ postfix = "";
+ } else {
+ start = offset - 36;
+ end = offset + 36;
+ postfix = "...";
+ }
+ prefix = "...";
+ }
+ else {
+ end = lineEnd;
+ start = lineStart;
+ prefix = "";
+ postfix = "";
+ }
+ return report + prefix + B.JSString_methods.substring$2(source, start, end) + postfix + "\n" + B.JSString_methods.$mul(" ", offset - start + prefix.length) + "^\n";
+ } else
+ return offset != null ? report + (" (at offset " + A.S(offset) + ")") : report;
+ }
+ };
+ A.Iterable.prototype = {
+ get$length(_) {
+ var count,
+ it = this.get$iterator(this);
+ for (count = 0; it.moveNext$0();)
+ ++count;
+ return count;
+ },
+ elementAt$1(_, index) {
+ var iterator, skipCount;
+ A.RangeError_checkNotNegative(index, "index");
+ iterator = this.get$iterator(this);
+ for (skipCount = index; iterator.moveNext$0();) {
+ if (skipCount === 0)
+ return iterator.get$current();
+ --skipCount;
+ }
+ throw A.wrapException(A.IndexError$withLength(index, index - skipCount, this, null, "index"));
+ },
+ toString$0(_) {
+ return A.Iterable_iterableToShortString(this, "(", ")");
+ }
+ };
+ A.Null.prototype = {
+ get$hashCode(_) {
+ return A.Object.prototype.get$hashCode.call(this, 0);
+ },
+ toString$0(_) {
+ return "null";
+ }
+ };
+ A.Object.prototype = {$isObject: 1,
+ $eq(_, other) {
+ return this === other;
+ },
+ get$hashCode(_) {
+ return A.Primitives_objectHashCode(this);
+ },
+ toString$0(_) {
+ return "Instance of '" + A.Primitives_objectTypeName(this) + "'";
+ },
+ noSuchMethod$1(_, invocation) {
+ throw A.wrapException(A.NoSuchMethodError_NoSuchMethodError$withInvocation(this, type$.Invocation._as(invocation)));
+ },
+ get$runtimeType(_) {
+ return A.getRuntimeTypeOfDartObject(this);
+ },
+ toString() {
+ return this.toString$0(this);
+ }
+ };
+ A._StringStackTrace.prototype = {
+ toString$0(_) {
+ return this._stackTrace;
+ },
+ $isStackTrace: 1
+ };
+ A.StringBuffer.prototype = {
+ get$length(_) {
+ return this._contents.length;
+ },
+ toString$0(_) {
+ var t1 = this._contents;
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ $isStringSink: 1
+ };
+ A.promiseToFuture_closure.prototype = {
+ call$1(r) {
+ return this.completer.complete$1(this.T._eval$1("0/?")._as(r));
+ },
+ $signature: 3
+ };
+ A.promiseToFuture_closure0.prototype = {
+ call$1(e) {
+ if (e == null)
+ return this.completer.completeError$1(new A.NullRejectionException(e === undefined));
+ return this.completer.completeError$1(e);
+ },
+ $signature: 3
+ };
+ A.dartify_convert.prototype = {
+ call$1(o) {
+ var t1, millisSinceEpoch, proto, t2, dartObject, originalKeys, dartKeys, i, jsKey, dartKey, l, $length;
+ if (A._noDartifyRequired(o))
+ return o;
+ t1 = this._convertedObjects;
+ o.toString;
+ if (t1.containsKey$1(o))
+ return t1.$index(0, o);
+ if (o instanceof Date) {
+ millisSinceEpoch = o.getTime();
+ if (Math.abs(millisSinceEpoch) > 864e13)
+ A.throwExpression(A.ArgumentError$("DateTime is outside valid range: " + millisSinceEpoch, null));
+ A.checkNotNullable(true, "isUtc", type$.bool);
+ return new A.DateTime(millisSinceEpoch, true);
+ }
+ if (o instanceof RegExp)
+ throw A.wrapException(A.ArgumentError$("structured clone of RegExp", null));
+ if (typeof Promise != "undefined" && o instanceof Promise)
+ return A.promiseToFuture(o, type$.nullable_Object);
+ proto = Object.getPrototypeOf(o);
+ if (proto === Object.prototype || proto === null) {
+ t2 = type$.nullable_Object;
+ dartObject = A.LinkedHashMap_LinkedHashMap$_empty(t2, t2);
+ t1.$indexSet(0, o, dartObject);
+ originalKeys = Object.keys(o);
+ dartKeys = [];
+ for (t1 = J.getInterceptor$ax(originalKeys), t2 = t1.get$iterator(originalKeys); t2.moveNext$0();)
+ dartKeys.push(A.dartify(t2.get$current()));
+ for (i = 0; i < t1.get$length(originalKeys); ++i) {
+ jsKey = t1.$index(originalKeys, i);
+ if (!(i < dartKeys.length))
+ return A.ioore(dartKeys, i);
+ dartKey = dartKeys[i];
+ if (jsKey != null)
+ dartObject.$indexSet(0, dartKey, this.call$1(o[jsKey]));
+ }
+ return dartObject;
+ }
+ if (o instanceof Array) {
+ l = o;
+ dartObject = [];
+ t1.$indexSet(0, o, dartObject);
+ $length = A._asInt(o.length);
+ for (t1 = J.getInterceptor$asx(l), i = 0; i < $length; ++i)
+ dartObject.push(this.call$1(t1.$index(l, i)));
+ return dartObject;
+ }
+ return o;
+ },
+ $signature: 20
+ };
+ A.NullRejectionException.prototype = {
+ toString$0(_) {
+ return "Promise was rejected with a value of `" + (this.isUndefined ? "undefined" : "null") + "`.";
+ }
+ };
+ A._JSRandom.prototype = {
+ nextInt$1(max) {
+ if (max <= 0 || max > 4294967296)
+ throw A.wrapException(A.RangeError$("max must be in range 0 < max \u2264 2^32, was " + max));
+ return Math.random() * max >>> 0;
+ }
+ };
+ A.AsyncMemoizer.prototype = {};
+ A.Level.prototype = {
+ $eq(_, other) {
+ if (other == null)
+ return false;
+ return other instanceof A.Level && this.value === other.value;
+ },
+ get$hashCode(_) {
+ return this.value;
+ },
+ toString$0(_) {
+ return this.name;
+ }
+ };
+ A.LogRecord.prototype = {
+ toString$0(_) {
+ return "[" + this.level.name + "] " + this.loggerName + ": " + this.message;
+ }
+ };
+ A.Logger.prototype = {
+ get$fullName() {
+ var t1 = this.parent,
+ t2 = t1 == null ? null : t1.name.length !== 0,
+ t3 = this.name;
+ return t2 === true ? t1.get$fullName() + "." + t3 : t3;
+ },
+ get$level() {
+ var t1, effectiveLevel;
+ if (this.parent == null) {
+ t1 = this._level;
+ t1.toString;
+ effectiveLevel = t1;
+ } else {
+ t1 = $.$get$Logger_root()._level;
+ t1.toString;
+ effectiveLevel = t1;
+ }
+ return effectiveLevel;
+ },
+ log$4(logLevel, message, error, stackTrace) {
+ var record, _this = this,
+ t1 = logLevel.value;
+ if (t1 >= _this.get$level().value) {
+ if (t1 >= 2000) {
+ A.StackTrace_current();
+ logLevel.toString$0(0);
+ }
+ t1 = _this.get$fullName();
+ Date.now();
+ $.LogRecord__nextNumber = $.LogRecord__nextNumber + 1;
+ record = new A.LogRecord(logLevel, message, t1);
+ if (_this.parent == null)
+ _this._publish$1(record);
+ else
+ $.$get$Logger_root()._publish$1(record);
+ }
+ },
+ _publish$1(record) {
+ return null;
+ }
+ };
+ A.Logger_Logger_closure.prototype = {
+ call$0() {
+ var dot, $parent, t1,
+ thisName = this.name;
+ if (B.JSString_methods.startsWith$1(thisName, "."))
+ A.throwExpression(A.ArgumentError$("name shouldn't start with a '.'", null));
+ if (B.JSString_methods.endsWith$1(thisName, "."))
+ A.throwExpression(A.ArgumentError$("name shouldn't end with a '.'", null));
+ dot = B.JSString_methods.lastIndexOf$1(thisName, ".");
+ if (dot === -1)
+ $parent = thisName !== "" ? A.Logger_Logger("") : null;
+ else {
+ $parent = A.Logger_Logger(B.JSString_methods.substring$2(thisName, 0, dot));
+ thisName = B.JSString_methods.substring$1(thisName, dot + 1);
+ }
+ t1 = new A.Logger(thisName, $parent, A.LinkedHashMap_LinkedHashMap$_empty(type$.String, type$.Logger));
+ if ($parent == null)
+ t1._level = B.Level_INFO_800;
+ else
+ $parent._children.$indexSet(0, thisName, t1);
+ return t1;
+ },
+ $signature: 21
+ };
+ A.Pool.prototype = {
+ request$0() {
+ var t1, t2, _this = this;
+ if ((_this._closeMemo._completer.future._state & 30) !== 0)
+ throw A.wrapException(A.StateError$("request() may not be called on a closed Pool."));
+ t1 = _this._allocatedResources;
+ if (t1 < _this._maxAllocatedResources) {
+ _this._allocatedResources = t1 + 1;
+ return A.Future_Future$value(new A.PoolResource(_this), type$.PoolResource);
+ } else {
+ t1 = _this._onReleaseCallbacks;
+ if (!t1.get$isEmpty(0))
+ return _this._runOnRelease$1(t1.removeFirst$0());
+ else {
+ t1 = new A._Future($.Zone__current, type$._Future_PoolResource);
+ t2 = _this._requestedResources;
+ t2._add$1(t2.$ti._precomputed1._as(new A._AsyncCompleter(t1, type$._AsyncCompleter_PoolResource)));
+ _this._resetTimer$0();
+ return t1;
+ }
+ }
+ },
+ withResource$1$1(callback, $T) {
+ return this.withResource$body$Pool($T._eval$1("0/()")._as(callback), $T, $T);
+ },
+ withResource$body$Pool(callback, $T, $async$type) {
+ var $async$goto = 0,
+ $async$completer = A._makeAsyncAwaitCompleter($async$type),
+ $async$returnValue, $async$handler = 2, $async$currentError, $async$next = [], $async$self = this, resource, t1, t2;
+ var $async$withResource$1$1 = A._wrapJsFunctionForAsync(function($async$errorCode, $async$result) {
+ if ($async$errorCode === 1) {
+ $async$currentError = $async$result;
+ $async$goto = $async$handler;
+ }
+ while (true)
+ switch ($async$goto) {
+ case 0:
+ // Function start
+ if (($async$self._closeMemo._completer.future._state & 30) !== 0)
+ throw A.wrapException(A.StateError$("withResource() may not be called on a closed Pool."));
+ $async$goto = 3;
+ return A._asyncAwait($async$self.request$0(), $async$withResource$1$1);
+ case 3:
+ // returning from await.
+ resource = $async$result;
+ $async$handler = 4;
+ t1 = callback.call$0();
+ $async$goto = 7;
+ return A._asyncAwait($T._eval$1("Future<0>")._is(t1) ? t1 : A._Future$value($T._as(t1), $T), $async$withResource$1$1);
+ case 7:
+ // returning from await.
+ t1 = $async$result;
+ $async$returnValue = t1;
+ $async$next = [1];
+ // goto finally
+ $async$goto = 5;
+ break;
+ $async$next.push(6);
+ // goto finally
+ $async$goto = 5;
+ break;
+ case 4:
+ // uncaught
+ $async$next = [2];
+ case 5:
+ // finally
+ $async$handler = 2;
+ t1 = resource;
+ if (t1._released)
+ A.throwExpression(A.StateError$("A PoolResource may only be released once."));
+ t1._released = true;
+ t1 = t1._pool;
+ t1._resetTimer$0();
+ t2 = t1._requestedResources;
+ if (!t2.get$isEmpty(0))
+ t2.removeFirst$0().complete$1(new A.PoolResource(t1));
+ else {
+ t2 = --t1._allocatedResources;
+ if ((t1._closeMemo._completer.future._state & 30) !== 0 && t2 === 0)
+ null.close$0();
+ }
+ // goto the next finally handler
+ $async$goto = $async$next.pop();
+ break;
+ case 6:
+ // after finally
+ case 1:
+ // return
+ return A._asyncReturn($async$returnValue, $async$completer);
+ case 2:
+ // rethrow
+ return A._asyncRethrow($async$currentError, $async$completer);
+ }
+ });
+ return A._asyncStartSync($async$withResource$1$1, $async$completer);
+ },
+ _runOnRelease$1(onRelease) {
+ var t1 = A.Future_Future$sync(type$.dynamic_Function._as(onRelease), type$.dynamic).then$1$1(new A.Pool__runOnRelease_closure(this), type$.Null),
+ onError = new A.Pool__runOnRelease_closure0(this),
+ t2 = t1.$ti,
+ t3 = $.Zone__current;
+ if (t3 !== B.C__RootZone)
+ onError = A._registerErrorHandler(onError, t3);
+ t1._addListener$1(new A._FutureListener(new A._Future(t3, t2), 2, null, onError, t2._eval$1("@<1>")._bind$1(t2._precomputed1)._eval$1("_FutureListener<1,2>")));
+ t1 = new A._Future($.Zone__current, type$._Future_PoolResource);
+ t2 = this._onReleaseCompleters;
+ t2._add$1(t2.$ti._precomputed1._as(new A._SyncCompleter(t1, type$._SyncCompleter_PoolResource)));
+ return t1;
+ },
+ _resetTimer$0() {
+ var t2,
+ t1 = this._timer;
+ if (t1 == null)
+ return;
+ t2 = this._requestedResources;
+ if (t2._head === t2._tail)
+ t1._restartable_timer$_timer.cancel$0();
+ else {
+ t1._restartable_timer$_timer.cancel$0();
+ t1._restartable_timer$_timer = A.Timer_Timer(t1._restartable_timer$_duration, t1._callback);
+ }
+ }
+ };
+ A.Pool__runOnRelease_closure.prototype = {
+ call$1(value) {
+ var t1 = this.$this;
+ t1._onReleaseCompleters.removeFirst$0().complete$1(new A.PoolResource(t1));
+ },
+ $signature: 4
+ };
+ A.Pool__runOnRelease_closure0.prototype = {
+ call$2(error, stackTrace) {
+ type$.Object._as(error);
+ type$.StackTrace._as(stackTrace);
+ this.$this._onReleaseCompleters.removeFirst$0().completeError$2(error, stackTrace);
+ },
+ $signature: 5
+ };
+ A.PoolResource.prototype = {};
+ A.SseClient.prototype = {
+ SseClient$2$debugKey(serverUrl, debugKey) {
+ var t2, t3, _this = this,
+ t1 = serverUrl + "?sseClientId=" + _this._clientId;
+ _this.__SseClient__serverUrl_A = t1;
+ t2 = type$.JSObject;
+ t1 = t2._as(new self.EventSource(t1, {withCredentials: true}));
+ _this.__SseClient__eventSource_A = t1;
+ new A._EventStream(t1, "open", false, type$._EventStream_JSObject).get$first(0).whenComplete$1(new A.SseClient_closure(_this));
+ t1 = type$.Function;
+ t3 = type$.JavaScriptFunction;
+ _this.__SseClient__eventSource_A.addEventListener("message", t3._as(A.allowInterop(_this.get$_onIncomingMessage(), t1)));
+ _this.__SseClient__eventSource_A.addEventListener("control", t3._as(A.allowInterop(_this.get$_onIncomingControlMessage(), t1)));
+ t1 = type$.nullable_void_Function_JSObject;
+ A._EventStreamSubscription$(_this.__SseClient__eventSource_A, "open", t1._as(new A.SseClient_closure0(_this)), false, t2);
+ A._EventStreamSubscription$(_this.__SseClient__eventSource_A, "error", t1._as(new A.SseClient_closure1(_this)), false, t2);
+ },
+ close$0() {
+ var _this = this,
+ t1 = _this.__SseClient__eventSource_A;
+ t1 === $ && A.throwLateFieldNI("_eventSource");
+ t1.close();
+ if ((_this._onConnected.future._state & 30) === 0) {
+ t1 = _this._outgoingController;
+ new A._ControllerStream(t1, A._instanceType(t1)._eval$1("_ControllerStream<1>")).listen$2$cancelOnError(null, true).asFuture$1$1(null, type$.void);
+ }
+ _this._incomingController.close$0();
+ _this._outgoingController.close$0();
+ },
+ _closeWithError$1(error) {
+ var stackTrace, t2,
+ t1 = this._incomingController;
+ A.checkNotNullable(error, "error", type$.Object);
+ if (t1._state >= 4)
+ A.throwExpression(t1._badEventState$0());
+ stackTrace = A.AsyncError_defaultStackTrace(error);
+ t2 = t1._state;
+ if ((t2 & 1) !== 0)
+ t1._sendError$2(error, stackTrace);
+ else if ((t2 & 3) === 0)
+ t1._ensurePendingEvents$0().add$1(0, new A._DelayedError(error, stackTrace));
+ this.close$0();
+ t1 = this._onConnected;
+ if ((t1.future._state & 30) === 0)
+ t1.completeError$1(error);
+ },
+ _onIncomingControlMessage$1(message) {
+ var data = type$.JSObject._as(message).data;
+ if (J.$eq$(A.dartify(data), "close"))
+ this.close$0();
+ else
+ throw A.wrapException(A.UnsupportedError$("[" + this._clientId + '] Illegal Control Message "' + A.S(data) + '"'));
+ },
+ _onIncomingMessage$1(message) {
+ this._incomingController.add$1(0, A._asString(B.C_JsonCodec.decode$2$reviver(A._asString(type$.JSObject._as(message).data), null)));
+ },
+ _onOutgoingDone$0() {
+ this.close$0();
+ },
+ _onOutgoingMessage$1(message) {
+ return this._onOutgoingMessage$body$SseClient(A._asStringQ(message));
+ },
+ _onOutgoingMessage$body$SseClient(message) {
+ var $async$goto = 0,
+ $async$completer = A._makeAsyncAwaitCompleter(type$.void),
+ $async$self = this, t1;
+ var $async$_onOutgoingMessage$1 = A._wrapJsFunctionForAsync(function($async$errorCode, $async$result) {
+ if ($async$errorCode === 1)
+ return A._asyncRethrow($async$result, $async$completer);
+ while (true)
+ switch ($async$goto) {
+ case 0:
+ // Function start
+ t1 = {};
+ t1.encodedMessage = null;
+ $async$goto = 2;
+ return A._asyncAwait($.$get$_requestPool().withResource$1$1(new A.SseClient__onOutgoingMessage_closure(t1, $async$self, message), type$.Null), $async$_onOutgoingMessage$1);
+ case 2:
+ // returning from await.
+ // implicit return
+ return A._asyncReturn(null, $async$completer);
+ }
+ });
+ return A._asyncStartSync($async$_onOutgoingMessage$1, $async$completer);
+ }
+ };
+ A.SseClient_closure.prototype = {
+ call$0() {
+ var t2,
+ t1 = this.$this;
+ t1._onConnected.complete$0();
+ t2 = t1._outgoingController;
+ new A._ControllerStream(t2, A._instanceType(t2)._eval$1("_ControllerStream<1>")).listen$2$onDone(t1.get$_onOutgoingMessage(), t1.get$_onOutgoingDone());
+ },
+ $signature: 2
+ };
+ A.SseClient_closure0.prototype = {
+ call$1(_) {
+ var t1 = this.$this._errorTimer;
+ if (t1 != null)
+ t1.cancel$0();
+ },
+ $signature: 1
+ };
+ A.SseClient_closure1.prototype = {
+ call$1(error) {
+ var t1 = this.$this,
+ t2 = t1._errorTimer;
+ t2 = t2 == null ? null : t2._handle != null;
+ if (t2 !== true)
+ t1._errorTimer = A.Timer_Timer(B.Duration_5000000, new A.SseClient__closure(t1, error));
+ },
+ $signature: 1
+ };
+ A.SseClient__closure.prototype = {
+ call$0() {
+ this.$this._closeWithError$1(this.error);
+ },
+ $signature: 0
+ };
+ A.SseClient__onOutgoingMessage_closure.prototype = {
+ call$0() {
+ var $async$goto = 0,
+ $async$completer = A._makeAsyncAwaitCompleter(type$.Null),
+ $async$handler = 1, $async$currentError, $async$self = this, e, e0, url, error, augmentedError, exception, t1, t2, $async$exception;
+ var $async$call$0 = A._wrapJsFunctionForAsync(function($async$errorCode, $async$result) {
+ if ($async$errorCode === 1) {
+ $async$currentError = $async$result;
+ $async$goto = $async$handler;
+ }
+ while (true)
+ switch ($async$goto) {
+ case 0:
+ // Function start
+ try {
+ $async$self._box_0.encodedMessage = B.C_JsonCodec.encode$2$toEncodable($async$self.message, null);
+ } catch (exception) {
+ t1 = A.unwrapException(exception);
+ if (t1 instanceof A.JsonUnsupportedObjectError) {
+ e = t1;
+ t1 = $async$self.$this;
+ t1._logger.log$4(B.Level_WARNING_900, "[" + t1._clientId + "] Unable to encode outgoing message: " + A.S(e), null, null);
+ } else if (t1 instanceof A.ArgumentError) {
+ e0 = t1;
+ t1 = $async$self.$this;
+ t1._logger.log$4(B.Level_WARNING_900, "[" + t1._clientId + "] Invalid argument: " + A.S(e0), null, null);
+ } else
+ throw exception;
+ }
+ $async$handler = 3;
+ t1 = $async$self.$this;
+ t2 = t1.__SseClient__serverUrl_A;
+ t2 === $ && A.throwLateFieldNI("_serverUrl");
+ url = t2 + "&messageId=" + ++t1._lastMessageId;
+ t1 = $async$self._box_0.encodedMessage;
+ if (t1 == null)
+ t1 = null;
+ t1 = {method: "POST", body: t1, credentials: "include"};
+ t2 = type$.JSObject;
+ $async$goto = 6;
+ return A._asyncAwait(A.promiseToFuture(t2._as(t2._as(self.window).fetch(url, t1)), t2), $async$call$0);
+ case 6:
+ // returning from await.
+ $async$handler = 1;
+ // goto after finally
+ $async$goto = 5;
+ break;
+ case 3:
+ // catch
+ $async$handler = 2;
+ $async$exception = $async$currentError;
+ error = A.unwrapException($async$exception);
+ t1 = $async$self.$this;
+ augmentedError = "[" + t1._clientId + "] SSE client failed to send " + A.S($async$self.message) + ":\n " + A.S(error);
+ t1._logger.log$4(B.Level_SEVERE_1000, augmentedError, null, null);
+ t1._closeWithError$1(augmentedError);
+ // goto after finally
+ $async$goto = 5;
+ break;
+ case 2:
+ // uncaught
+ // goto rethrow
+ $async$goto = 1;
+ break;
+ case 5:
+ // after finally
+ // implicit return
+ return A._asyncReturn(null, $async$completer);
+ case 1:
+ // rethrow
+ return A._asyncRethrow($async$currentError, $async$completer);
+ }
+ });
+ return A._asyncStartSync($async$call$0, $async$completer);
+ },
+ $signature: 7
+ };
+ A.generateUuidV4_generateBits.prototype = {
+ call$1(bitCount) {
+ return this.random.nextInt$1(B.JSInt_methods._shlPositive$1(1, bitCount));
+ },
+ $signature: 23
+ };
+ A.generateUuidV4_printDigits.prototype = {
+ call$2(value, count) {
+ return B.JSString_methods.padLeft$2(B.JSInt_methods.toRadixString$1(value, 16), count, "0");
+ },
+ $signature: 11
+ };
+ A.generateUuidV4_bitsDigits.prototype = {
+ call$2(bitCount, digitCount) {
+ return this.printDigits.call$2(this.generateBits.call$1(bitCount), digitCount);
+ },
+ $signature: 11
+ };
+ A.StreamChannelMixin.prototype = {};
+ A.EventStreamProvider.prototype = {};
+ A._EventStream.prototype = {
+ listen$4$cancelOnError$onDone$onError(onData, cancelOnError, onDone, onError) {
+ var t1 = A._instanceType(this);
+ t1._eval$1("~(1)?")._as(onData);
+ type$.nullable_void_Function._as(onDone);
+ return A._EventStreamSubscription$(this._target, this._eventType, onData, false, t1._precomputed1);
+ }
+ };
+ A._ElementEventStreamImpl.prototype = {};
+ A._EventStreamSubscription.prototype = {
+ cancel$0() {
+ var _this = this,
+ emptyFuture = A.Future_Future$value(null, type$.void);
+ if (_this._target == null)
+ return emptyFuture;
+ _this._unlisten$0();
+ _this._streams$_onData = _this._target = null;
+ return emptyFuture;
+ },
+ onData$1(handleData) {
+ var t1, _this = this;
+ _this.$ti._eval$1("~(1)?")._as(handleData);
+ if (_this._target == null)
+ throw A.wrapException(A.StateError$("Subscription has been canceled."));
+ _this._unlisten$0();
+ t1 = A._wrapZone(new A._EventStreamSubscription_onData_closure(handleData), type$.JSObject);
+ t1 = t1 == null ? null : type$.JavaScriptFunction._as(A.allowInterop(t1, type$.Function));
+ _this._streams$_onData = t1;
+ _this._tryResume$0();
+ },
+ _tryResume$0() {
+ var t1 = this._streams$_onData;
+ if (t1 != null)
+ this._target.addEventListener(this._eventType, t1, false);
+ },
+ _unlisten$0() {
+ var t1 = this._streams$_onData;
+ if (t1 != null)
+ this._target.removeEventListener(this._eventType, t1, false);
+ },
+ $isStreamSubscription: 1
+ };
+ A._EventStreamSubscription_closure.prototype = {
+ call$1(e) {
+ return this.onData.call$1(type$.JSObject._as(e));
+ },
+ $signature: 1
+ };
+ A._EventStreamSubscription_onData_closure.prototype = {
+ call$1(e) {
+ return this.handleData.call$1(type$.JSObject._as(e));
+ },
+ $signature: 1
+ };
+ A.main_closure.prototype = {
+ call$1(_) {
+ this.channel._outgoingController.close$0();
+ },
+ $signature: 1
+ };
+ A.main_closure0.prototype = {
+ call$1(s) {
+ var count, t1, t2, t3, i, t4, t5, lastEvent;
+ A._asString(s);
+ if (B.JSString_methods.startsWith$1(s, "send ")) {
+ count = A.int_parse(B.JSArray_methods.get$last(s.split(" ")), null);
+ for (t1 = this.channel._outgoingController, t2 = A._instanceType(t1), t3 = t2._precomputed1, t2 = t2._eval$1("_DelayedData<1>"), i = 0; i < count; ++i) {
+ t4 = t3._as("" + i);
+ t5 = t1._state;
+ if (t5 >= 4)
+ A.throwExpression(t1._badEventState$0());
+ if ((t5 & 1) !== 0)
+ t1._sendData$1(t4);
+ else if ((t5 & 3) === 0) {
+ t5 = t1._ensurePendingEvents$0();
+ t4 = new A._DelayedData(t4, t2);
+ lastEvent = t5.lastPendingEvent;
+ if (lastEvent == null)
+ t5.firstPendingEvent = t5.lastPendingEvent = t4;
+ else {
+ lastEvent.set$next(t4);
+ t5.lastPendingEvent = t4;
+ }
+ }
+ }
+ } else {
+ t1 = this.channel._outgoingController;
+ t1.add$1(0, A._instanceType(t1)._precomputed1._as(s));
+ }
+ },
+ $signature: 24
+ };
+ (function aliases() {
+ var _ = J.LegacyJavaScriptObject.prototype;
+ _.super$LegacyJavaScriptObject$toString = _.toString$0;
+ })();
+ (function installTearOffs() {
+ var _static_1 = hunkHelpers._static_1,
+ _static_0 = hunkHelpers._static_0,
+ _static_2 = hunkHelpers._static_2,
+ _instance_2_u = hunkHelpers._instance_2u,
+ _instance_1_u = hunkHelpers._instance_1u,
+ _instance_0_u = hunkHelpers._instance_0u;
+ _static_1(A, "async__AsyncRun__scheduleImmediateJsOverride$closure", "_AsyncRun__scheduleImmediateJsOverride", 6);
+ _static_1(A, "async__AsyncRun__scheduleImmediateWithSetImmediate$closure", "_AsyncRun__scheduleImmediateWithSetImmediate", 6);
+ _static_1(A, "async__AsyncRun__scheduleImmediateWithTimer$closure", "_AsyncRun__scheduleImmediateWithTimer", 6);
+ _static_0(A, "async___startMicrotaskLoop$closure", "_startMicrotaskLoop", 0);
+ _static_1(A, "async___nullDataHandler$closure", "_nullDataHandler", 3);
+ _static_2(A, "async___nullErrorHandler$closure", "_nullErrorHandler", 9);
+ _static_0(A, "async___nullDoneHandler$closure", "_nullDoneHandler", 0);
+ _instance_2_u(A._Future.prototype, "get$_completeError", "_completeError$2", 9);
+ _static_1(A, "convert___defaultToEncodable$closure", "_defaultToEncodable", 8);
+ var _;
+ _instance_1_u(_ = A.SseClient.prototype, "get$_onIncomingControlMessage", "_onIncomingControlMessage$1", 1);
+ _instance_1_u(_, "get$_onIncomingMessage", "_onIncomingMessage$1", 1);
+ _instance_0_u(_, "get$_onOutgoingDone", "_onOutgoingDone$0", 0);
+ _instance_1_u(_, "get$_onOutgoingMessage", "_onOutgoingMessage$1", 22);
+ })();
+ (function inheritance() {
+ var _mixin = hunkHelpers.mixin,
+ _inherit = hunkHelpers.inherit,
+ _inheritMany = hunkHelpers.inheritMany;
+ _inherit(A.Object, null);
+ _inheritMany(A.Object, [A.JS_CONST, J.Interceptor, J.ArrayIterator, A.Error, A.Closure, A.Iterable, A.ListIterator, A.FixedLengthListMixin, A.Symbol, A.MapView, A.ConstantMap, A.JSInvocationMirror, A.TypeErrorDecoder, A.NullThrownFromJavaScriptException, A.ExceptionAndStackTrace, A._StackTrace, A._Required, A.MapBase, A.LinkedHashMapCell, A.LinkedHashMapKeyIterator, A.StringMatch, A.Rti, A._FunctionParameters, A._Type, A._TimerImpl, A._AsyncAwaitCompleter, A.AsyncError, A._Completer, A._FutureListener, A._Future, A._AsyncCallbackEntry, A.Stream, A._StreamController, A._AsyncStreamControllerDispatch, A._BufferingStreamSubscription, A._StreamSinkWrapper, A._DelayedEvent, A._DelayedDone, A._PendingEvents, A._StreamIterator, A._Zone, A._HashMapKeyIterator, A.ListBase, A._UnmodifiableMapMixin, A._ListQueueIterator, A.Codec, A.Converter, A._JsonStringifier, A.DateTime, A.Duration, A.OutOfMemoryError, A.StackOverflowError, A._Exception, A.FormatException, A.Null, A._StringStackTrace, A.StringBuffer, A.NullRejectionException, A._JSRandom, A.AsyncMemoizer, A.Level, A.LogRecord, A.Logger, A.Pool, A.PoolResource, A.StreamChannelMixin, A.EventStreamProvider, A._EventStreamSubscription]);
+ _inheritMany(J.Interceptor, [J.JSBool, J.JSNull, J.JavaScriptObject, J.JavaScriptBigInt, J.JavaScriptSymbol, J.JSNumber, J.JSString]);
+ _inheritMany(J.JavaScriptObject, [J.LegacyJavaScriptObject, J.JSArray, A.NativeByteBuffer, A.NativeTypedData]);
+ _inheritMany(J.LegacyJavaScriptObject, [J.PlainJavaScriptObject, J.UnknownJavaScriptObject, J.JavaScriptFunction]);
+ _inherit(J.JSUnmodifiableArray, J.JSArray);
+ _inheritMany(J.JSNumber, [J.JSInt, J.JSNumNotInt]);
+ _inheritMany(A.Error, [A.LateError, A.TypeError, A.JsNoSuchMethodError, A.UnknownJsTypeError, A._CyclicInitializationError, A.RuntimeError, A._Error, A.JsonUnsupportedObjectError, A.AssertionError, A.ArgumentError, A.NoSuchMethodError, A.UnsupportedError, A.UnimplementedError, A.StateError, A.ConcurrentModificationError]);
+ _inheritMany(A.Closure, [A.Closure0Args, A.Closure2Args, A.TearOffClosure, A.initHooks_closure, A.initHooks_closure1, A._AsyncRun__initializeScheduleImmediate_internalCallback, A._AsyncRun__initializeScheduleImmediate_closure, A._awaitOnObject_closure, A._Future__chainForeignFuture_closure, A._Future__propagateToListeners_handleWhenCompleteCallback_closure, A.Stream_length_closure, A.Stream_first_closure0, A._RootZone_bindUnaryCallbackGuarded_closure, A.promiseToFuture_closure, A.promiseToFuture_closure0, A.dartify_convert, A.Pool__runOnRelease_closure, A.SseClient_closure0, A.SseClient_closure1, A.generateUuidV4_generateBits, A._EventStreamSubscription_closure, A._EventStreamSubscription_onData_closure, A.main_closure, A.main_closure0]);
+ _inheritMany(A.Closure0Args, [A.nullFuture_closure, A._AsyncRun__scheduleImmediateJsOverride_internalCallback, A._AsyncRun__scheduleImmediateWithSetImmediate_internalCallback, A._TimerImpl_internalCallback, A._Future__addListener_closure, A._Future__prependListeners_closure, A._Future__chainForeignFuture_closure1, A._Future__chainCoreFutureAsync_closure, A._Future__asyncCompleteWithValue_closure, A._Future__asyncCompleteError_closure, A._Future__propagateToListeners_handleWhenCompleteCallback, A._Future__propagateToListeners_handleValueCallback, A._Future__propagateToListeners_handleError, A.Stream_length_closure0, A.Stream_first_closure, A._StreamController__subscribe_closure, A._StreamController__recordCancel_complete, A._BufferingStreamSubscription_asFuture_closure, A._BufferingStreamSubscription_asFuture__closure, A._BufferingStreamSubscription__sendError_sendError, A._BufferingStreamSubscription__sendDone_sendDone, A._PendingEvents_schedule_closure, A._cancelAndValue_closure, A._rootHandleError_closure, A._RootZone_bindCallbackGuarded_closure, A.Logger_Logger_closure, A.SseClient_closure, A.SseClient__closure, A.SseClient__onOutgoingMessage_closure]);
+ _inherit(A.EfficientLengthIterable, A.Iterable);
+ _inheritMany(A.EfficientLengthIterable, [A.ListIterable, A.LinkedHashMapKeyIterable, A._HashMapKeyIterable]);
+ _inherit(A._UnmodifiableMapView_MapView__UnmodifiableMapMixin, A.MapView);
+ _inherit(A.UnmodifiableMapView, A._UnmodifiableMapView_MapView__UnmodifiableMapMixin);
+ _inherit(A.ConstantMapView, A.UnmodifiableMapView);
+ _inherit(A.ConstantStringMap, A.ConstantMap);
+ _inheritMany(A.Closure2Args, [A.Primitives_functionNoSuchMethod_closure, A.initHooks_closure0, A._awaitOnObject_closure0, A._wrapJsFunctionForAsync_closure, A._Future__chainForeignFuture_closure0, A._BufferingStreamSubscription_asFuture_closure0, A.MapBase_mapToString_closure, A._JsonStringifier_writeMap_closure, A.NoSuchMethodError_toString_closure, A.Pool__runOnRelease_closure0, A.generateUuidV4_printDigits, A.generateUuidV4_bitsDigits]);
+ _inherit(A.NullError, A.TypeError);
+ _inheritMany(A.TearOffClosure, [A.StaticClosure, A.BoundClosure]);
+ _inheritMany(A.MapBase, [A.JsLinkedHashMap, A._HashMap, A._JsonMap]);
+ _inheritMany(A.NativeTypedData, [A.NativeByteData, A.NativeTypedArray]);
+ _inheritMany(A.NativeTypedArray, [A._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin, A._NativeTypedArrayOfInt_NativeTypedArray_ListMixin]);
+ _inherit(A._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin_FixedLengthListMixin, A._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin);
+ _inherit(A.NativeTypedArrayOfDouble, A._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin_FixedLengthListMixin);
+ _inherit(A._NativeTypedArrayOfInt_NativeTypedArray_ListMixin_FixedLengthListMixin, A._NativeTypedArrayOfInt_NativeTypedArray_ListMixin);
+ _inherit(A.NativeTypedArrayOfInt, A._NativeTypedArrayOfInt_NativeTypedArray_ListMixin_FixedLengthListMixin);
+ _inheritMany(A.NativeTypedArrayOfDouble, [A.NativeFloat32List, A.NativeFloat64List]);
+ _inheritMany(A.NativeTypedArrayOfInt, [A.NativeInt16List, A.NativeInt32List, A.NativeInt8List, A.NativeUint16List, A.NativeUint32List, A.NativeUint8ClampedList, A.NativeUint8List]);
+ _inherit(A._TypeError, A._Error);
+ _inheritMany(A._Completer, [A._AsyncCompleter, A._SyncCompleter]);
+ _inherit(A._AsyncStreamController, A._StreamController);
+ _inheritMany(A.Stream, [A._StreamImpl, A._EventStream]);
+ _inherit(A._ControllerStream, A._StreamImpl);
+ _inherit(A._ControllerSubscription, A._BufferingStreamSubscription);
+ _inheritMany(A._DelayedEvent, [A._DelayedData, A._DelayedError]);
+ _inherit(A._RootZone, A._Zone);
+ _inherit(A._IdentityHashMap, A._HashMap);
+ _inheritMany(A.ListIterable, [A.ListQueue, A._JsonMapKeyIterable]);
+ _inherit(A.JsonCyclicError, A.JsonUnsupportedObjectError);
+ _inherit(A.JsonCodec, A.Codec);
+ _inheritMany(A.Converter, [A.JsonEncoder, A.JsonDecoder]);
+ _inherit(A._JsonStringStringifier, A._JsonStringifier);
+ _inheritMany(A.ArgumentError, [A.RangeError, A.IndexError]);
+ _inherit(A.SseClient, A.StreamChannelMixin);
+ _inherit(A._ElementEventStreamImpl, A._EventStream);
+ _mixin(A._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin, A.ListBase);
+ _mixin(A._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin_FixedLengthListMixin, A.FixedLengthListMixin);
+ _mixin(A._NativeTypedArrayOfInt_NativeTypedArray_ListMixin, A.ListBase);
+ _mixin(A._NativeTypedArrayOfInt_NativeTypedArray_ListMixin_FixedLengthListMixin, A.FixedLengthListMixin);
+ _mixin(A._AsyncStreamController, A._AsyncStreamControllerDispatch);
+ _mixin(A._UnmodifiableMapView_MapView__UnmodifiableMapMixin, A._UnmodifiableMapMixin);
+ })();
+ var init = {
+ typeUniverse: {eC: new Map(), tR: {}, eT: {}, tPV: {}, sEA: []},
+ mangledGlobalNames: {int: "int", double: "double", num: "num", String: "String", bool: "bool", Null: "Null", List: "List", Object: "Object", Map: "Map"},
+ mangledNames: {},
+ types: ["~()", "~(JSObject)", "Null()", "~(@)", "Null(@)", "Null(Object,StackTrace)", "~(~())", "Future<Null>()", "@(@)", "~(Object,StackTrace)", "~(Object?,Object?)", "String(int,int)", "~(String,@)", "@(@,String)", "@(String)", "Null(~())", "Null(@,StackTrace)", "~(int,@)", "_Future<@>(@)", "~(Symbol0,@)", "Object?(Object?)", "Logger()", "~(String?)", "int(int)", "~(String)"],
+ interceptorsByTag: null,
+ leafTags: null,
+ arrayRti: Symbol("$ti")
+ };
+ A._Universe_addRules(init.typeUniverse, JSON.parse('{"PlainJavaScriptObject":"LegacyJavaScriptObject","UnknownJavaScriptObject":"LegacyJavaScriptObject","JavaScriptFunction":"LegacyJavaScriptObject","JSBool":{"bool":[],"TrustedGetRuntimeType":[]},"JSNull":{"Null":[],"TrustedGetRuntimeType":[]},"JavaScriptObject":{"JSObject":[]},"LegacyJavaScriptObject":{"JSObject":[]},"JSArray":{"List":["1"],"JSObject":[],"Iterable":["1"]},"JSUnmodifiableArray":{"JSArray":["1"],"List":["1"],"JSObject":[],"Iterable":["1"]},"JSNumber":{"double":[],"num":[]},"JSInt":{"double":[],"int":[],"num":[],"TrustedGetRuntimeType":[]},"JSNumNotInt":{"double":[],"num":[],"TrustedGetRuntimeType":[]},"JSString":{"String":[],"Pattern":[],"TrustedGetRuntimeType":[]},"LateError":{"Error":[]},"EfficientLengthIterable":{"Iterable":["1"]},"ListIterable":{"Iterable":["1"]},"Symbol":{"Symbol0":[]},"ConstantMapView":{"UnmodifiableMapView":["1","2"],"_UnmodifiableMapView_MapView__UnmodifiableMapMixin":["1","2"],"MapView":["1","2"],"_UnmodifiableMapMixin":["1","2"],"Map":["1","2"]},"ConstantMap":{"Map":["1","2"]},"ConstantStringMap":{"ConstantMap":["1","2"],"Map":["1","2"]},"JSInvocationMirror":{"Invocation":[]},"NullError":{"TypeError":[],"Error":[]},"JsNoSuchMethodError":{"Error":[]},"UnknownJsTypeError":{"Error":[]},"_StackTrace":{"StackTrace":[]},"Closure":{"Function":[]},"Closure0Args":{"Function":[]},"Closure2Args":{"Function":[]},"TearOffClosure":{"Function":[]},"StaticClosure":{"Function":[]},"BoundClosure":{"Function":[]},"_CyclicInitializationError":{"Error":[]},"RuntimeError":{"Error":[]},"JsLinkedHashMap":{"MapBase":["1","2"],"Map":["1","2"],"MapBase.K":"1","MapBase.V":"2"},"LinkedHashMapKeyIterable":{"Iterable":["1"]},"NativeByteBuffer":{"JSObject":[],"TrustedGetRuntimeType":[]},"NativeTypedData":{"JSObject":[]},"NativeByteData":{"JSObject":[],"TrustedGetRuntimeType":[]},"NativeTypedArray":{"JavaScriptIndexingBehavior":["1"],"JSObject":[]},"NativeTypedArrayOfDouble":{"ListBase":["double"],"List":["double"],"JavaScriptIndexingBehavior":["double"],"JSObject":[],"Iterable":["double"],"FixedLengthListMixin":["double"]},"NativeTypedArrayOfInt":{"ListBase":["int"],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JSObject":[],"Iterable":["int"],"FixedLengthListMixin":["int"]},"NativeFloat32List":{"ListBase":["double"],"List":["double"],"JavaScriptIndexingBehavior":["double"],"JSObject":[],"Iterable":["double"],"FixedLengthListMixin":["double"],"TrustedGetRuntimeType":[],"ListBase.E":"double"},"NativeFloat64List":{"ListBase":["double"],"List":["double"],"JavaScriptIndexingBehavior":["double"],"JSObject":[],"Iterable":["double"],"FixedLengthListMixin":["double"],"TrustedGetRuntimeType":[],"ListBase.E":"double"},"NativeInt16List":{"ListBase":["int"],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JSObject":[],"Iterable":["int"],"FixedLengthListMixin":["int"],"TrustedGetRuntimeType":[],"ListBase.E":"int"},"NativeInt32List":{"ListBase":["int"],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JSObject":[],"Iterable":["int"],"FixedLengthListMixin":["int"],"TrustedGetRuntimeType":[],"ListBase.E":"int"},"NativeInt8List":{"ListBase":["int"],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JSObject":[],"Iterable":["int"],"FixedLengthListMixin":["int"],"TrustedGetRuntimeType":[],"ListBase.E":"int"},"NativeUint16List":{"ListBase":["int"],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JSObject":[],"Iterable":["int"],"FixedLengthListMixin":["int"],"TrustedGetRuntimeType":[],"ListBase.E":"int"},"NativeUint32List":{"ListBase":["int"],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JSObject":[],"Iterable":["int"],"FixedLengthListMixin":["int"],"TrustedGetRuntimeType":[],"ListBase.E":"int"},"NativeUint8ClampedList":{"ListBase":["int"],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JSObject":[],"Iterable":["int"],"FixedLengthListMixin":["int"],"TrustedGetRuntimeType":[],"ListBase.E":"int"},"NativeUint8List":{"ListBase":["int"],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JSObject":[],"Iterable":["int"],"FixedLengthListMixin":["int"],"TrustedGetRuntimeType":[],"ListBase.E":"int"},"_Error":{"Error":[]},"_TypeError":{"TypeError":[],"Error":[]},"_Future":{"Future":["1"]},"_TimerImpl":{"Timer":[]},"_AsyncAwaitCompleter":{"Completer":["1"]},"AsyncError":{"Error":[]},"_Completer":{"Completer":["1"]},"_AsyncCompleter":{"_Completer":["1"],"Completer":["1"]},"_SyncCompleter":{"_Completer":["1"],"Completer":["1"]},"_StreamController":{"StreamController":["1"],"_StreamControllerLifecycle":["1"],"_EventDispatch":["1"]},"_AsyncStreamController":{"_AsyncStreamControllerDispatch":["1"],"_StreamController":["1"],"StreamController":["1"],"_StreamControllerLifecycle":["1"],"_EventDispatch":["1"]},"_ControllerStream":{"_StreamImpl":["1"],"Stream":["1"]},"_ControllerSubscription":{"_BufferingStreamSubscription":["1"],"StreamSubscription":["1"],"_EventDispatch":["1"]},"_BufferingStreamSubscription":{"StreamSubscription":["1"],"_EventDispatch":["1"]},"_StreamImpl":{"Stream":["1"]},"_DelayedData":{"_DelayedEvent":["1"]},"_DelayedError":{"_DelayedEvent":["@"]},"_DelayedDone":{"_DelayedEvent":["@"]},"_Zone":{"Zone":[]},"_RootZone":{"_Zone":[],"Zone":[]},"_HashMap":{"MapBase":["1","2"],"Map":["1","2"]},"_IdentityHashMap":{"_HashMap":["1","2"],"MapBase":["1","2"],"Map":["1","2"],"MapBase.K":"1","MapBase.V":"2"},"_HashMapKeyIterable":{"Iterable":["1"]},"MapBase":{"Map":["1","2"]},"MapView":{"Map":["1","2"]},"UnmodifiableMapView":{"_UnmodifiableMapView_MapView__UnmodifiableMapMixin":["1","2"],"MapView":["1","2"],"_UnmodifiableMapMixin":["1","2"],"Map":["1","2"]},"ListQueue":{"Queue":["1"],"ListIterable":["1"],"Iterable":["1"],"ListIterable.E":"1"},"_JsonMap":{"MapBase":["String","@"],"Map":["String","@"],"MapBase.K":"String","MapBase.V":"@"},"_JsonMapKeyIterable":{"ListIterable":["String"],"Iterable":["String"],"ListIterable.E":"String"},"JsonUnsupportedObjectError":{"Error":[]},"JsonCyclicError":{"Error":[]},"JsonCodec":{"Codec":["Object?","String"]},"JsonEncoder":{"Converter":["Object?","String"]},"JsonDecoder":{"Converter":["String","Object?"]},"double":{"num":[]},"int":{"num":[]},"String":{"Pattern":[]},"AssertionError":{"Error":[]},"TypeError":{"Error":[]},"ArgumentError":{"Error":[]},"RangeError":{"Error":[]},"IndexError":{"Error":[]},"NoSuchMethodError":{"Error":[]},"UnsupportedError":{"Error":[]},"UnimplementedError":{"Error":[]},"StateError":{"Error":[]},"ConcurrentModificationError":{"Error":[]},"OutOfMemoryError":{"Error":[]},"StackOverflowError":{"Error":[]},"_StringStackTrace":{"StackTrace":[]},"StringBuffer":{"StringSink":[]},"_EventStream":{"Stream":["1"]},"_ElementEventStreamImpl":{"_EventStream":["1"],"Stream":["1"]},"_EventStreamSubscription":{"StreamSubscription":["1"]},"Int8List":{"List":["int"],"Iterable":["int"]},"Uint8List":{"List":["int"],"Iterable":["int"]},"Uint8ClampedList":{"List":["int"],"Iterable":["int"]},"Int16List":{"List":["int"],"Iterable":["int"]},"Uint16List":{"List":["int"],"Iterable":["int"]},"Int32List":{"List":["int"],"Iterable":["int"]},"Uint32List":{"List":["int"],"Iterable":["int"]},"Float32List":{"List":["double"],"Iterable":["double"]},"Float64List":{"List":["double"],"Iterable":["double"]}}'));
+ A._Universe_addErasedTypes(init.typeUniverse, JSON.parse('{"EfficientLengthIterable":1,"NativeTypedArray":1,"_DelayedEvent":1,"StreamChannelMixin":1}'));
+ var string$ = {
+ Error_: "Error handler must accept one Object or one Object and a StackTrace as arguments, and return a value of the returned future's type"
+ };
+ var type$ = (function rtii() {
+ var findType = A.findType;
+ return {
+ $env_1_1_void: findType("@<~>"),
+ AsyncError: findType("AsyncError"),
+ ConstantMapView_Symbol_dynamic: findType("ConstantMapView<Symbol0,@>"),
+ Error: findType("Error"),
+ Function: findType("Function"),
+ Future_dynamic: findType("Future<@>"),
+ Invocation: findType("Invocation"),
+ Iterable_dynamic: findType("Iterable<@>"),
+ JSArray_String: findType("JSArray<String>"),
+ JSArray_dynamic: findType("JSArray<@>"),
+ JSNull: findType("JSNull"),
+ JSObject: findType("JSObject"),
+ JavaScriptFunction: findType("JavaScriptFunction"),
+ JavaScriptIndexingBehavior_dynamic: findType("JavaScriptIndexingBehavior<@>"),
+ JsLinkedHashMap_Symbol_dynamic: findType("JsLinkedHashMap<Symbol0,@>"),
+ List_dynamic: findType("List<@>"),
+ Logger: findType("Logger"),
+ Map_dynamic_dynamic: findType("Map<@,@>"),
+ Null: findType("Null"),
+ Object: findType("Object"),
+ PoolResource: findType("PoolResource"),
+ Record: findType("Record"),
+ StackTrace: findType("StackTrace"),
+ String: findType("String"),
+ Symbol: findType("Symbol0"),
+ TrustedGetRuntimeType: findType("TrustedGetRuntimeType"),
+ TypeError: findType("TypeError"),
+ UnknownJavaScriptObject: findType("UnknownJavaScriptObject"),
+ _AsyncCompleter_PoolResource: findType("_AsyncCompleter<PoolResource>"),
+ _AsyncCompleter_void: findType("_AsyncCompleter<~>"),
+ _ElementEventStreamImpl_JSObject: findType("_ElementEventStreamImpl<JSObject>"),
+ _EventStream_JSObject: findType("_EventStream<JSObject>"),
+ _Future_PoolResource: findType("_Future<PoolResource>"),
+ _Future_dynamic: findType("_Future<@>"),
+ _Future_int: findType("_Future<int>"),
+ _Future_void: findType("_Future<~>"),
+ _IdentityHashMap_of_nullable_Object_and_nullable_Object: findType("_IdentityHashMap<Object?,Object?>"),
+ _StreamControllerAddStreamState_nullable_Object: findType("_StreamControllerAddStreamState<Object?>"),
+ _SyncCompleter_PoolResource: findType("_SyncCompleter<PoolResource>"),
+ bool: findType("bool"),
+ bool_Function_Object: findType("bool(Object)"),
+ double: findType("double"),
+ dynamic: findType("@"),
+ dynamic_Function: findType("@()"),
+ dynamic_Function_Object: findType("@(Object)"),
+ dynamic_Function_Object_StackTrace: findType("@(Object,StackTrace)"),
+ int: findType("int"),
+ legacy_Never: findType("0&*"),
+ legacy_Object: findType("Object*"),
+ nullable_Future_Null: findType("Future<Null>?"),
+ nullable_JSObject: findType("JSObject?"),
+ nullable_List_dynamic: findType("List<@>?"),
+ nullable_Object: findType("Object?"),
+ nullable_StackTrace: findType("StackTrace?"),
+ nullable__DelayedEvent_dynamic: findType("_DelayedEvent<@>?"),
+ nullable__FutureListener_dynamic_dynamic: findType("_FutureListener<@,@>?"),
+ nullable_void_Function: findType("~()?"),
+ nullable_void_Function_JSObject: findType("~(JSObject)?"),
+ num: findType("num"),
+ void: findType("~"),
+ void_Function: findType("~()"),
+ void_Function_Object: findType("~(Object)"),
+ void_Function_Object_StackTrace: findType("~(Object,StackTrace)"),
+ void_Function_String_dynamic: findType("~(String,@)")
+ };
+ })();
+ (function constants() {
+ var makeConstList = hunkHelpers.makeConstList;
+ B.Interceptor_methods = J.Interceptor.prototype;
+ B.JSArray_methods = J.JSArray.prototype;
+ B.JSInt_methods = J.JSInt.prototype;
+ B.JSNumber_methods = J.JSNumber.prototype;
+ B.JSString_methods = J.JSString.prototype;
+ B.JavaScriptFunction_methods = J.JavaScriptFunction.prototype;
+ B.JavaScriptObject_methods = J.JavaScriptObject.prototype;
+ B.PlainJavaScriptObject_methods = J.PlainJavaScriptObject.prototype;
+ B.UnknownJavaScriptObject_methods = J.UnknownJavaScriptObject.prototype;
+ B.C_JS_CONST = function getTagFallback(o) {
+ var s = Object.prototype.toString.call(o);
+ return s.substring(8, s.length - 1);
+};
+ B.C_JS_CONST0 = function() {
+ var toStringFunction = Object.prototype.toString;
+ function getTag(o) {
+ var s = toStringFunction.call(o);
+ return s.substring(8, s.length - 1);
+ }
+ function getUnknownTag(object, tag) {
+ if (/^HTML[A-Z].*Element$/.test(tag)) {
+ var name = toStringFunction.call(object);
+ if (name == "[object Object]") return null;
+ return "HTMLElement";
+ }
+ }
+ function getUnknownTagGenericBrowser(object, tag) {
+ if (object instanceof HTMLElement) return "HTMLElement";
+ return getUnknownTag(object, tag);
+ }
+ function prototypeForTag(tag) {
+ if (typeof window == "undefined") return null;
+ if (typeof window[tag] == "undefined") return null;
+ var constructor = window[tag];
+ if (typeof constructor != "function") return null;
+ return constructor.prototype;
+ }
+ function discriminator(tag) { return null; }
+ var isBrowser = typeof HTMLElement == "function";
+ return {
+ getTag: getTag,
+ getUnknownTag: isBrowser ? getUnknownTagGenericBrowser : getUnknownTag,
+ prototypeForTag: prototypeForTag,
+ discriminator: discriminator };
+};
+ B.C_JS_CONST6 = function(getTagFallback) {
+ return function(hooks) {
+ if (typeof navigator != "object") return hooks;
+ var userAgent = navigator.userAgent;
+ if (typeof userAgent != "string") return hooks;
+ if (userAgent.indexOf("DumpRenderTree") >= 0) return hooks;
+ if (userAgent.indexOf("Chrome") >= 0) {
+ function confirm(p) {
+ return typeof window == "object" && window[p] && window[p].name == p;
+ }
+ if (confirm("Window") && confirm("HTMLElement")) return hooks;
+ }
+ hooks.getTag = getTagFallback;
+ };
+};
+ B.C_JS_CONST1 = function(hooks) {
+ if (typeof dartExperimentalFixupGetTag != "function") return hooks;
+ hooks.getTag = dartExperimentalFixupGetTag(hooks.getTag);
+};
+ B.C_JS_CONST5 = function(hooks) {
+ if (typeof navigator != "object") return hooks;
+ var userAgent = navigator.userAgent;
+ if (typeof userAgent != "string") return hooks;
+ if (userAgent.indexOf("Firefox") == -1) return hooks;
+ var getTag = hooks.getTag;
+ var quickMap = {
+ "BeforeUnloadEvent": "Event",
+ "DataTransfer": "Clipboard",
+ "GeoGeolocation": "Geolocation",
+ "Location": "!Location",
+ "WorkerMessageEvent": "MessageEvent",
+ "XMLDocument": "!Document"};
+ function getTagFirefox(o) {
+ var tag = getTag(o);
+ return quickMap[tag] || tag;
+ }
+ hooks.getTag = getTagFirefox;
+};
+ B.C_JS_CONST4 = function(hooks) {
+ if (typeof navigator != "object") return hooks;
+ var userAgent = navigator.userAgent;
+ if (typeof userAgent != "string") return hooks;
+ if (userAgent.indexOf("Trident/") == -1) return hooks;
+ var getTag = hooks.getTag;
+ var quickMap = {
+ "BeforeUnloadEvent": "Event",
+ "DataTransfer": "Clipboard",
+ "HTMLDDElement": "HTMLElement",
+ "HTMLDTElement": "HTMLElement",
+ "HTMLPhraseElement": "HTMLElement",
+ "Position": "Geoposition"
+ };
+ function getTagIE(o) {
+ var tag = getTag(o);
+ var newTag = quickMap[tag];
+ if (newTag) return newTag;
+ if (tag == "Object") {
+ if (window.DataView && (o instanceof window.DataView)) return "DataView";
+ }
+ return tag;
+ }
+ function prototypeForTagIE(tag) {
+ var constructor = window[tag];
+ if (constructor == null) return null;
+ return constructor.prototype;
+ }
+ hooks.getTag = getTagIE;
+ hooks.prototypeForTag = prototypeForTagIE;
+};
+ B.C_JS_CONST2 = function(hooks) {
+ var getTag = hooks.getTag;
+ var prototypeForTag = hooks.prototypeForTag;
+ function getTagFixed(o) {
+ var tag = getTag(o);
+ if (tag == "Document") {
+ if (!!o.xmlVersion) return "!Document";
+ return "!HTMLDocument";
+ }
+ return tag;
+ }
+ function prototypeForTagFixed(tag) {
+ if (tag == "Document") return null;
+ return prototypeForTag(tag);
+ }
+ hooks.getTag = getTagFixed;
+ hooks.prototypeForTag = prototypeForTagFixed;
+};
+ B.C_JS_CONST3 = function(hooks) { return hooks; }
+;
+ B.C_JsonCodec = new A.JsonCodec();
+ B.C_OutOfMemoryError = new A.OutOfMemoryError();
+ B.C__DelayedDone = new A._DelayedDone();
+ B.C__JSRandom = new A._JSRandom();
+ B.C__Required = new A._Required();
+ B.C__RootZone = new A._RootZone();
+ B.Duration_0 = new A.Duration(0);
+ B.Duration_5000000 = new A.Duration(5000000);
+ B.JsonDecoder_null = new A.JsonDecoder(null);
+ B.JsonEncoder_null = new A.JsonEncoder(null);
+ B.Level_INFO_800 = new A.Level("INFO", 800);
+ B.Level_SEVERE_1000 = new A.Level("SEVERE", 1000);
+ B.Level_WARNING_900 = new A.Level("WARNING", 900);
+ B.List_empty = A._setArrayType(makeConstList([]), type$.JSArray_dynamic);
+ B.Object_empty = {};
+ B.Map_empty = new A.ConstantStringMap(B.Object_empty, [], A.findType("ConstantStringMap<Symbol0,@>"));
+ B.Symbol_call = new A.Symbol("call");
+ B.Type_ByteBuffer_RkP = A.typeLiteral("ByteBuffer");
+ B.Type_ByteData_zNC = A.typeLiteral("ByteData");
+ B.Type_Float32List_LB7 = A.typeLiteral("Float32List");
+ B.Type_Float64List_LB7 = A.typeLiteral("Float64List");
+ B.Type_Int16List_uXf = A.typeLiteral("Int16List");
+ B.Type_Int32List_O50 = A.typeLiteral("Int32List");
+ B.Type_Int8List_ekJ = A.typeLiteral("Int8List");
+ B.Type_Uint16List_2bx = A.typeLiteral("Uint16List");
+ B.Type_Uint32List_2bx = A.typeLiteral("Uint32List");
+ B.Type_Uint8ClampedList_Jik = A.typeLiteral("Uint8ClampedList");
+ B.Type_Uint8List_WLA = A.typeLiteral("Uint8List");
+ B._StringStackTrace_3uE = new A._StringStackTrace("");
+ })();
+ (function staticFields() {
+ $._JS_INTEROP_INTERCEPTOR_TAG = null;
+ $.toStringVisiting = A._setArrayType([], A.findType("JSArray<Object>"));
+ $.Primitives__identityHashCodeProperty = null;
+ $.BoundClosure__receiverFieldNameCache = null;
+ $.BoundClosure__interceptorFieldNameCache = null;
+ $.getTagFunction = null;
+ $.alternateTagFunction = null;
+ $.prototypeForTagFunction = null;
+ $.dispatchRecordsForInstanceTags = null;
+ $.interceptorsForUncacheableTags = null;
+ $.initNativeDispatchFlag = null;
+ $._nextCallback = null;
+ $._lastCallback = null;
+ $._lastPriorityCallback = null;
+ $._isInCallbackLoop = false;
+ $.Zone__current = B.C__RootZone;
+ $.LogRecord__nextNumber = 0;
+ $.Logger__loggers = A.LinkedHashMap_LinkedHashMap$_empty(type$.String, type$.Logger);
+ })();
+ (function lazyInitializers() {
+ var _lazyFinal = hunkHelpers.lazyFinal;
+ _lazyFinal($, "DART_CLOSURE_PROPERTY_NAME", "$get$DART_CLOSURE_PROPERTY_NAME", () => A.getIsolateAffinityTag("_$dart_dartClosure"));
+ _lazyFinal($, "nullFuture", "$get$nullFuture", () => B.C__RootZone.run$1$1(new A.nullFuture_closure(), A.findType("Future<Null>")));
+ _lazyFinal($, "TypeErrorDecoder_noSuchMethodPattern", "$get$TypeErrorDecoder_noSuchMethodPattern", () => A.TypeErrorDecoder_extractPattern(A.TypeErrorDecoder_provokeCallErrorOn({
+ toString: function() {
+ return "$receiver$";
+ }
+ })));
+ _lazyFinal($, "TypeErrorDecoder_notClosurePattern", "$get$TypeErrorDecoder_notClosurePattern", () => A.TypeErrorDecoder_extractPattern(A.TypeErrorDecoder_provokeCallErrorOn({$method$: null,
+ toString: function() {
+ return "$receiver$";
+ }
+ })));
+ _lazyFinal($, "TypeErrorDecoder_nullCallPattern", "$get$TypeErrorDecoder_nullCallPattern", () => A.TypeErrorDecoder_extractPattern(A.TypeErrorDecoder_provokeCallErrorOn(null)));
+ _lazyFinal($, "TypeErrorDecoder_nullLiteralCallPattern", "$get$TypeErrorDecoder_nullLiteralCallPattern", () => A.TypeErrorDecoder_extractPattern(function() {
+ var $argumentsExpr$ = "$arguments$";
+ try {
+ null.$method$($argumentsExpr$);
+ } catch (e) {
+ return e.message;
+ }
+ }()));
+ _lazyFinal($, "TypeErrorDecoder_undefinedCallPattern", "$get$TypeErrorDecoder_undefinedCallPattern", () => A.TypeErrorDecoder_extractPattern(A.TypeErrorDecoder_provokeCallErrorOn(void 0)));
+ _lazyFinal($, "TypeErrorDecoder_undefinedLiteralCallPattern", "$get$TypeErrorDecoder_undefinedLiteralCallPattern", () => A.TypeErrorDecoder_extractPattern(function() {
+ var $argumentsExpr$ = "$arguments$";
+ try {
+ (void 0).$method$($argumentsExpr$);
+ } catch (e) {
+ return e.message;
+ }
+ }()));
+ _lazyFinal($, "TypeErrorDecoder_nullPropertyPattern", "$get$TypeErrorDecoder_nullPropertyPattern", () => A.TypeErrorDecoder_extractPattern(A.TypeErrorDecoder_provokePropertyErrorOn(null)));
+ _lazyFinal($, "TypeErrorDecoder_nullLiteralPropertyPattern", "$get$TypeErrorDecoder_nullLiteralPropertyPattern", () => A.TypeErrorDecoder_extractPattern(function() {
+ try {
+ null.$method$;
+ } catch (e) {
+ return e.message;
+ }
+ }()));
+ _lazyFinal($, "TypeErrorDecoder_undefinedPropertyPattern", "$get$TypeErrorDecoder_undefinedPropertyPattern", () => A.TypeErrorDecoder_extractPattern(A.TypeErrorDecoder_provokePropertyErrorOn(void 0)));
+ _lazyFinal($, "TypeErrorDecoder_undefinedLiteralPropertyPattern", "$get$TypeErrorDecoder_undefinedLiteralPropertyPattern", () => A.TypeErrorDecoder_extractPattern(function() {
+ try {
+ (void 0).$method$;
+ } catch (e) {
+ return e.message;
+ }
+ }()));
+ _lazyFinal($, "_AsyncRun__scheduleImmediateClosure", "$get$_AsyncRun__scheduleImmediateClosure", () => A._AsyncRun__initializeScheduleImmediate());
+ _lazyFinal($, "Future__nullFuture", "$get$Future__nullFuture", () => A.findType("_Future<Null>")._as($.$get$nullFuture()));
+ _lazyFinal($, "Logger_root", "$get$Logger_root", () => A.Logger_Logger(""));
+ _lazyFinal($, "_requestPool", "$get$_requestPool", () => {
+ var t4,
+ t1 = A.findType("Completer<PoolResource>"),
+ t2 = A.ListQueue$(t1),
+ t3 = A.ListQueue$(type$.void_Function);
+ t1 = A.ListQueue$(t1);
+ t4 = A.Completer_Completer(type$.dynamic);
+ return new A.Pool(t2, t3, t1, 1000, new A.AsyncMemoizer(t4, A.findType("AsyncMemoizer<@>")));
+ });
+ })();
+ (function nativeSupport() {
+ !function() {
+ var intern = function(s) {
+ var o = {};
+ o[s] = 1;
+ return Object.keys(hunkHelpers.convertToFastObject(o))[0];
+ };
+ init.getIsolateTag = function(name) {
+ return intern("___dart_" + name + init.isolateTag);
+ };
+ var tableProperty = "___dart_isolate_tags_";
+ var usedProperties = Object[tableProperty] || (Object[tableProperty] = Object.create(null));
+ var rootProperty = "_ZxYxX";
+ for (var i = 0;; i++) {
+ var property = intern(rootProperty + "_" + i + "_");
+ if (!(property in usedProperties)) {
+ usedProperties[property] = 1;
+ init.isolateTag = property;
+ break;
+ }
+ }
+ init.dispatchPropertyName = init.getIsolateTag("dispatch_record");
+ }();
+ hunkHelpers.setOrUpdateInterceptorsByTag({ArrayBuffer: A.NativeByteBuffer, ArrayBufferView: A.NativeTypedData, DataView: A.NativeByteData, Float32Array: A.NativeFloat32List, Float64Array: A.NativeFloat64List, Int16Array: A.NativeInt16List, Int32Array: A.NativeInt32List, Int8Array: A.NativeInt8List, Uint16Array: A.NativeUint16List, Uint32Array: A.NativeUint32List, Uint8ClampedArray: A.NativeUint8ClampedList, CanvasPixelArray: A.NativeUint8ClampedList, Uint8Array: A.NativeUint8List});
+ hunkHelpers.setOrUpdateLeafTags({ArrayBuffer: true, ArrayBufferView: false, DataView: true, Float32Array: true, Float64Array: true, Int16Array: true, Int32Array: true, Int8Array: true, Uint16Array: true, Uint32Array: true, Uint8ClampedArray: true, CanvasPixelArray: true, Uint8Array: false});
+ A.NativeTypedArray.$nativeSuperclassTag = "ArrayBufferView";
+ A._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin.$nativeSuperclassTag = "ArrayBufferView";
+ A._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin_FixedLengthListMixin.$nativeSuperclassTag = "ArrayBufferView";
+ A.NativeTypedArrayOfDouble.$nativeSuperclassTag = "ArrayBufferView";
+ A._NativeTypedArrayOfInt_NativeTypedArray_ListMixin.$nativeSuperclassTag = "ArrayBufferView";
+ A._NativeTypedArrayOfInt_NativeTypedArray_ListMixin_FixedLengthListMixin.$nativeSuperclassTag = "ArrayBufferView";
+ A.NativeTypedArrayOfInt.$nativeSuperclassTag = "ArrayBufferView";
+ })();
+ Function.prototype.call$1 = function(a) {
+ return this(a);
+ };
+ Function.prototype.call$0 = function() {
+ return this();
+ };
+ Function.prototype.call$2 = function(a, b) {
+ return this(a, b);
+ };
+ Function.prototype.call$3 = function(a, b, c) {
+ return this(a, b, c);
+ };
+ Function.prototype.call$4 = function(a, b, c, d) {
+ return this(a, b, c, d);
+ };
+ Function.prototype.call$1$1 = function(a) {
+ return this(a);
+ };
+ convertAllToFastObject(holders);
+ convertToFastObject($);
+ (function(callback) {
+ if (typeof document === "undefined") {
+ callback(null);
+ return;
+ }
+ if (typeof document.currentScript != "undefined") {
+ callback(document.currentScript);
+ return;
+ }
+ var scripts = document.scripts;
+ function onLoad(event) {
+ for (var i = 0; i < scripts.length; ++i) {
+ scripts[i].removeEventListener("load", onLoad, false);
+ }
+ callback(event.target);
+ }
+ for (var i = 0; i < scripts.length; ++i) {
+ scripts[i].addEventListener("load", onLoad, false);
+ }
+ })(function(currentScript) {
+ init.currentScript = currentScript;
+ var callMain = A.main;
+ if (typeof dartMainRunner === "function") {
+ dartMainRunner(callMain, []);
+ } else {
+ callMain([]);
+ }
+ });
+})();
diff --git a/pkgs/sse/test/web/index.html b/pkgs/sse/test/web/index.html
new file mode 100644
index 0000000..be26763
--- /dev/null
+++ b/pkgs/sse/test/web/index.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>SSE Broadcast Channel Test</title>
+</head>
+
+<body>
+ <button type="button">Close Sink</button>
+ <script type="application/javascript" src="index.dart.js"></script>
+</body>
+
+</html>
diff --git a/pkgs/sse/tool/build_js.sh b/pkgs/sse/tool/build_js.sh
new file mode 100755
index 0000000..ef29b70
--- /dev/null
+++ b/pkgs/sse/tool/build_js.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+dart compile js --no-source-maps test/web/index.dart -o test/web/index.dart.js
diff --git a/pkgs/stack_trace/.gitignore b/pkgs/stack_trace/.gitignore
new file mode 100644
index 0000000..f023015
--- /dev/null
+++ b/pkgs/stack_trace/.gitignore
@@ -0,0 +1,6 @@
+# See https://dart.dev/guides/libraries/private-files
+# Don’t commit the following directories created by pub.
+.dart_tool/
+.packages
+.pub/
+pubspec.lock
diff --git a/pkgs/stack_trace/CHANGELOG.md b/pkgs/stack_trace/CHANGELOG.md
new file mode 100644
index 0000000..e92cf9c
--- /dev/null
+++ b/pkgs/stack_trace/CHANGELOG.md
@@ -0,0 +1,363 @@
+## 1.12.1
+
+* Move to `dart-lang/tools` monorepo.
+
+## 1.12.0
+
+* Added support for parsing Wasm frames of Chrome (V8), Firefox, Safari.
+* Require Dart 3.4 or greater
+
+## 1.11.1
+
+* Make use of `@pragma('vm:awaiter-link')` to make package work better with
+ Dart VM's builtin awaiter stack unwinding. No other changes.
+
+## 1.11.0
+
+* Added the parameter `zoneValues` to `Chain.capture` to be able to use custom
+ zone values with the `runZoned` internal calls.
+* Populate the pubspec `repository` field.
+* Require Dart 2.18 or greater
+
+## 1.10.0
+
+* Stable release for null safety.
+* Fix broken test, `test/chain/vm_test.dart`, which incorrectly handles
+ asynchronous suspension gap markers at the end of stack traces.
+
+## 1.10.0-nullsafety.6
+
+* Fix bug parsing asynchronous suspension gap markers at the end of stack
+ traces, when parsing with `Trace.parse` and `Chain.parse`.
+* Update SDK constraints to `>=2.12.0-0 <3.0.0` based on beta release
+ guidelines.
+
+## 1.10.0-nullsafety.5
+
+* Allow prerelease versions of the 2.12 sdk.
+
+## 1.10.0-nullsafety.4
+
+* Allow the `2.10.0` stable and dev SDKs.
+
+## 1.10.0-nullsafety.3
+
+* Fix bug parsing asynchronous suspension gap markers at the end of stack
+ traces.
+
+## 1.10.0-nullsafety.2
+
+* Forward fix for a change in SDK type promotion behavior.
+
+## 1.10.0-nullsafety.1
+
+* Allow 2.10 stable and 2.11.0 dev SDK versions.
+
+## 1.10.0-nullsafety
+
+* Opt in to null safety.
+
+## 1.9.6 (backpublish)
+
+* Fix bug parsing asynchronous suspension gap markers at the end of stack
+ traces. (Also fixed separately in 1.10.0-nullsafety.3)
+* Fix bug parsing asynchronous suspension gap markers at the end of stack
+ traces, when parsing with `Trace.parse` and `Chain.parse`. (Also fixed
+ separately in 1.10.0-nullsafety.6)
+
+## 1.9.5
+
+* Parse the format for `data:` URIs that the Dart VM has used since `2.2.0`.
+
+## 1.9.4
+
+* Add support for firefox anonymous stack traces.
+* Add support for chrome eval stack traces without a column.
+* Change the argument type to `Chain.capture` from `Function(dynamic, Chain)` to
+ `Function(Object, Chain)`. Existing functions which take `dynamic` are still
+ fine, but new uses can have a safer type.
+
+## 1.9.3
+
+* Set max SDK version to `<3.0.0`.
+
+## 1.9.2
+
+* Fix Dart 2.0 runtime cast failure in test.
+
+## 1.9.1
+
+* Preserve the original chain for a trace to handle cases where an
+ error is rethrown.
+
+## 1.9.0
+
+* Add an `errorZone` parameter to `Chain.capture()` that makes it avoid creating
+ an error zone.
+
+## 1.8.3
+
+* `Chain.forTrace()` now returns a full stack chain for *all* `StackTrace`s
+ within `Chain.capture()`, even those that haven't been processed by
+ `dart:async` yet.
+
+* `Chain.forTrace()` now uses the Dart VM's stack chain information when called
+ synchronously within `Chain.capture()`. This matches the existing behavior
+ outside `Chain.capture()`.
+
+* `Chain.forTrace()` now trims the VM's stack chains for the innermost stack
+ trace within `Chain.capture()` (unless it's called synchronously, as above).
+ This avoids duplicated frames and makes the format of the innermost traces
+ consistent with the other traces in the chain.
+
+## 1.8.2
+
+* Update to use strong-mode clean Zone API.
+
+## 1.8.1
+
+* Use official generic function syntax.
+
+* Updated minimum SDK to 1.23.0.
+
+## 1.8.0
+
+* Add a `Trace.original` field to provide access to the original `StackTrace`s
+ from which the `Trace` was created, and a matching constructor parameter to
+ `new Trace()`.
+
+## 1.7.4
+
+* Always run `onError` callbacks for `Chain.capture()` in the parent zone.
+
+## 1.7.3
+
+* Fix broken links in the README.
+
+## 1.7.2
+
+* `Trace.foldFrames()` and `Chain.foldFrames()` now remove the outermost folded
+ frame. This matches the behavior of `.terse` with core frames.
+
+* Fix bug parsing a friendly frame with spaces in the member name.
+
+* Fix bug parsing a friendly frame where the location is a data url.
+
+## 1.7.1
+
+* Make `Trace.parse()`, `Chain.parse()`, treat the VM's new causal asynchronous
+ stack traces as chains. Outside of a `Chain.capture()` block, `new
+ Chain.current()` will return a stack chain constructed from the asynchronous
+ stack traces.
+
+## 1.7.0
+
+* Add a `Chain.disable()` function that disables stack-chain tracking.
+
+* Fix a bug where `Chain.capture(..., when: false)` would throw if an error was
+ emitted without a stack trace.
+
+## 1.6.8
+
+* Add a note to the documentation of `Chain.terse` and `Trace.terse`.
+
+## 1.6.7
+
+* Fix a bug where `new Frame.caller()` returned the wrong depth of frame on
+ Dartium.
+
+## 1.6.6
+
+* `new Trace.current()` and `new Chain.current()` now skip an extra frame when
+ run in a JS context. This makes their return values match the VM context.
+
+## 1.6.5
+
+* Really fix strong mode warnings.
+
+## 1.6.4
+
+* Fix a syntax error introduced in 1.6.3.
+
+## 1.6.3
+
+* Make `Chain.capture()` generic. Its signature is now `T Chain.capture<T>(T
+ callback(), ...)`.
+
+## 1.6.2
+
+* Fix all strong mode warnings.
+
+## 1.6.1
+
+* Use `StackTrace.current` in Dart SDK 1.14 to get the current stack trace.
+
+## 1.6.0
+
+* Add a `when` parameter to `Chain.capture()`. This allows capturing to be
+ easily enabled and disabled based on whether the application is running in
+ debug/development mode or not.
+
+* Deprecate the `ChainHandler` typedef. This didn't provide any value over
+ directly annotating the function argument, and it made the documentation less
+ clear.
+
+## 1.5.1
+
+* Fix a crash in `Chain.foldFrames()` and `Chain.terse` when one of the chain's
+ traces has no frames.
+
+## 1.5.0
+
+* `new Chain.parse()` now parses all the stack trace formats supported by `new
+ Trace.parse()`. Formats other than that emitted by `Chain.toString()` will
+ produce single-element chains.
+
+* `new Trace.parse()` now parses the output of `Chain.toString()`. It produces
+ the same result as `Chain.parse().toTrace()`.
+
+## 1.4.2
+
+* Improve the display of `data:` URIs in stack traces.
+
+## 1.4.1
+
+* Fix a crashing bug in `UnparsedFrame.toString()`.
+
+## 1.4.0
+
+* `new Trace.parse()` and related constructors will no longer throw an exception
+ if they encounter an unparseable stack frame. Instead, they will generate an
+ `UnparsedFrame`, which exposes no metadata but preserves the frame's original
+ text.
+
+* Properly parse native-code V8 frames.
+
+## 1.3.5
+
+* Properly shorten library names for pathnames of folded frames on Windows.
+
+## 1.3.4
+
+* No longer say that stack chains aren't supported on dart2js now that
+ [sdk#15171][] is fixed. Note that this fix only applies to Dart 1.12.
+
+[sdk#15171]: https://github.com/dart-lang/sdk/issues/15171
+
+## 1.3.3
+
+* When a `null` stack trace is passed to a completer or stream controller in
+ nested `Chain.capture()` blocks, substitute the inner block's chain rather
+ than the outer block's.
+
+* Add support for empty chains and chains of empty traces to `Chain.parse()`.
+
+* Don't crash when parsing stack traces from Dart VM stack overflows.
+
+## 1.3.2
+
+* Don't crash when running `Trace.terse` on empty stack traces.
+
+## 1.3.1
+
+* Support more types of JavaScriptCore stack frames.
+
+## 1.3.0
+
+* Support stack traces generated by JavaScriptCore. They can be explicitly
+ parsed via `new Trace.parseJSCore` and `new Frame.parseJSCore`.
+
+## 1.2.4
+
+* Fix a type annotation in `LazyTrace`.
+
+## 1.2.3
+
+* Fix a crash in `Chain.parse`.
+
+## 1.2.2
+
+* Don't print the first folded frame of terse stack traces. This frame
+ is always just an internal isolate message handler anyway. This
+ improves the readability of stack traces, especially in stack chains.
+
+* Remove the line numbers and specific files in all terse folded frames, not
+ just those from core libraries.
+
+* Make padding consistent across all stack traces for `Chain.toString()`.
+
+## 1.2.1
+
+* Add `terse` to `LazyTrace.foldFrames()`.
+
+* Further improve stack chains when using the VM's async/await implementation.
+
+## 1.2.0
+
+* Add a `terse` argument to `Trace.foldFrames()` and `Chain.foldFrames()`. This
+ allows them to inherit the behavior of `Trace.terse` and `Chain.terse` without
+ having to duplicate the logic.
+
+## 1.1.3
+
+* Produce nicer-looking stack chains when using the VM's async/await
+ implementation.
+
+## 1.1.2
+
+* Support VM frames without line *or* column numbers, which async/await programs
+ occasionally generate.
+
+* Replace `<<anonymous closure>_async_body>` in VM frames' members with the
+ terser `<async>`.
+
+## 1.1.1
+
+* Widen the SDK constraint to include 1.7.0-dev.4.0.
+
+## 1.1.0
+
+* Unify the parsing of Safari and Firefox stack traces. This fixes an error in
+ Firefox trace parsing.
+
+* Deprecate `Trace.parseSafari6_0`, `Trace.parseSafari6_1`,
+ `Frame.parseSafari6_0`, and `Frame.parseSafari6_1`.
+
+* Add `Frame.parseSafari`.
+
+## 1.0.3
+
+* Use `Zone.errorCallback` to attach stack chains to all errors without the need
+ for `Chain.track`, which is now deprecated.
+
+## 1.0.2
+
+* Remove a workaround for [issue 17083][].
+
+[issue 17083]: https://github.com/dart-lang/sdk/issues/17083
+
+## 1.0.1
+
+* Synchronous errors in the [Chain.capture] callback are now handled correctly.
+
+## 1.0.0
+
+* No API changes, just declared stable.
+
+## 0.9.3+2
+
+* Update the dependency on path.
+
+* Improve the formatting of library URIs in stack traces.
+
+## 0.9.3+1
+
+* If an error is thrown in `Chain.capture`'s `onError` handler, that error is
+ handled by the parent zone. This matches the behavior of `runZoned` in
+ `dart:async`.
+
+## 0.9.3
+
+* Add a `Chain.foldFrames` method that parallels `Trace.foldFrames`.
+
+* Record anonymous method frames in IE10 as "<fn>".
diff --git a/pkgs/stack_trace/LICENSE b/pkgs/stack_trace/LICENSE
new file mode 100644
index 0000000..162572a
--- /dev/null
+++ b/pkgs/stack_trace/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2014, 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.
diff --git a/pkgs/stack_trace/README.md b/pkgs/stack_trace/README.md
new file mode 100644
index 0000000..b10a556
--- /dev/null
+++ b/pkgs/stack_trace/README.md
@@ -0,0 +1,169 @@
+[](https://github.com/dart-lang/tools/actions/workflows/stack_trace.yaml)
+[](https://pub.dev/packages/stack_trace)
+[](https://pub.dev/packages/stack_trace/publisher)
+
+This library provides the ability to parse, inspect, and manipulate stack traces
+produced by the underlying Dart implementation. It also provides functions to
+produce string representations of stack traces in a more readable format than
+the native [StackTrace] implementation.
+
+`Trace`s can be parsed from native [StackTrace]s using `Trace.from`, or captured
+using `Trace.current`. Native [StackTrace]s can also be directly converted to
+human-readable strings using `Trace.format`.
+
+[StackTrace]: https://api.dart.dev/stable/dart-core/StackTrace-class.html
+
+Here's an example native stack trace from debugging this library:
+
+ #0 Object.noSuchMethod (dart:core-patch:1884:25)
+ #1 Trace.terse.<anonymous closure> (file:///usr/local/google-old/home/goog/dart/dart/pkg/stack_trace/lib/src/trace.dart:47:21)
+ #2 IterableMixinWorkaround.reduce (dart:collection:29:29)
+ #3 List.reduce (dart:core-patch:1247:42)
+ #4 Trace.terse (file:///usr/local/google-old/home/goog/dart/dart/pkg/stack_trace/lib/src/trace.dart:40:35)
+ #5 format (file:///usr/local/google-old/home/goog/dart/dart/pkg/stack_trace/lib/stack_trace.dart:24:28)
+ #6 main.<anonymous closure> (file:///usr/local/google-old/home/goog/dart/dart/test.dart:21:29)
+ #7 _CatchErrorFuture._sendError (dart:async:525:24)
+ #8 _FutureImpl._setErrorWithoutAsyncTrace (dart:async:393:26)
+ #9 _FutureImpl._setError (dart:async:378:31)
+ #10 _ThenFuture._sendValue (dart:async:490:16)
+ #11 _FutureImpl._handleValue.<anonymous closure> (dart:async:349:28)
+ #12 Timer.run.<anonymous closure> (dart:async:2402:21)
+ #13 Timer.Timer.<anonymous closure> (dart:async-patch:15:15)
+
+and its human-readable representation:
+
+ dart:core-patch 1884:25 Object.noSuchMethod
+ pkg/stack_trace/lib/src/trace.dart 47:21 Trace.terse.<fn>
+ dart:collection 29:29 IterableMixinWorkaround.reduce
+ dart:core-patch 1247:42 List.reduce
+ pkg/stack_trace/lib/src/trace.dart 40:35 Trace.terse
+ pkg/stack_trace/lib/stack_trace.dart 24:28 format
+ test.dart 21:29 main.<fn>
+ dart:async 525:24 _CatchErrorFuture._sendError
+ dart:async 393:26 _FutureImpl._setErrorWithoutAsyncTrace
+ dart:async 378:31 _FutureImpl._setError
+ dart:async 490:16 _ThenFuture._sendValue
+ dart:async 349:28 _FutureImpl._handleValue.<fn>
+ dart:async 2402:21 Timer.run.<fn>
+ dart:async-patch 15:15 Timer.Timer.<fn>
+
+You can further clean up the stack trace using `Trace.terse`. This folds
+together multiple stack frames from the Dart core libraries, so that only the
+core library method that was directly called from user code is visible. For
+example:
+
+ dart:core Object.noSuchMethod
+ pkg/stack_trace/lib/src/trace.dart 47:21 Trace.terse.<fn>
+ dart:core List.reduce
+ pkg/stack_trace/lib/src/trace.dart 40:35 Trace.terse
+ pkg/stack_trace/lib/stack_trace.dart 24:28 format
+ test.dart 21:29 main.<fn>
+
+## Stack Chains
+
+This library also provides the ability to capture "stack chains" with the
+`Chain` class. When writing asynchronous code, a single stack trace isn't very
+useful, since the call stack is unwound every time something async happens. A
+stack chain tracks stack traces through asynchronous calls, so that you can see
+the full path from `main` down to the error.
+
+To use stack chains, just wrap the code that you want to track in
+`Chain.capture`. This will create a new [Zone][] in which stack traces are
+recorded and woven into chains every time an asynchronous call occurs. Zones are
+sticky, too, so any asynchronous operations started in the `Chain.capture`
+callback will have their chains tracked, as will asynchronous operations they
+start and so on.
+
+Here's an example of some code that doesn't capture its stack chains:
+
+```dart
+import 'dart:async';
+
+void main() {
+ _scheduleAsync();
+}
+
+void _scheduleAsync() {
+ Future.delayed(Duration(seconds: 1)).then((_) => _runAsync());
+}
+
+void _runAsync() {
+ throw 'oh no!';
+}
+```
+
+If we run this, it prints the following:
+
+ Unhandled exception:
+ oh no!
+ #0 _runAsync (file:///Users/kevmoo/github/stack_trace/example/example.dart:12:3)
+ #1 _scheduleAsync.<anonymous closure> (file:///Users/kevmoo/github/stack_trace/example/example.dart:8:52)
+ <asynchronous suspension>
+
+Notice how there's no mention of `main` in that stack trace. All we know is that
+the error was in `runAsync`; we don't know why `runAsync` was called.
+
+Now let's look at the same code with stack chains captured:
+
+```dart
+import 'dart:async';
+
+import 'package:stack_trace/stack_trace.dart';
+
+void main() {
+ Chain.capture(_scheduleAsync);
+}
+
+void _scheduleAsync() {
+ Future.delayed(Duration(seconds: 1)).then((_) => _runAsync());
+}
+
+void _runAsync() {
+ throw 'oh no!';
+}
+```
+
+Now if we run it, it prints this:
+
+ Unhandled exception:
+ oh no!
+ example/example.dart 14:3 _runAsync
+ example/example.dart 10:52 _scheduleAsync.<fn>
+ package:stack_trace/src/stack_zone_specification.dart 126:26 StackZoneSpecification._registerUnaryCallback.<fn>.<fn>
+ package:stack_trace/src/stack_zone_specification.dart 208:15 StackZoneSpecification._run
+ package:stack_trace/src/stack_zone_specification.dart 126:14 StackZoneSpecification._registerUnaryCallback.<fn>
+ dart:async/zone.dart 1406:47 _rootRunUnary
+ dart:async/zone.dart 1307:19 _CustomZone.runUnary
+ ===== asynchronous gap ===========================
+ dart:async/zone.dart 1328:19 _CustomZone.registerUnaryCallback
+ dart:async/future_impl.dart 315:23 Future.then
+ example/example.dart 10:40 _scheduleAsync
+ package:stack_trace/src/chain.dart 97:24 Chain.capture.<fn>
+ dart:async/zone.dart 1398:13 _rootRun
+ dart:async/zone.dart 1300:19 _CustomZone.run
+ dart:async/zone.dart 1803:10 _runZoned
+ dart:async/zone.dart 1746:10 runZoned
+ package:stack_trace/src/chain.dart 95:12 Chain.capture
+ example/example.dart 6:9 main
+ dart:isolate-patch/isolate_patch.dart 297:19 _delayEntrypointInvocation.<fn>
+ dart:isolate-patch/isolate_patch.dart 192:12 _RawReceivePortImpl._handleMessage
+
+That's a lot of text! If you look closely, though, you can see that `main` is
+listed in the first trace in the chain.
+
+Thankfully, you can call `Chain.terse` just like `Trace.terse` to get rid of all
+the frames you don't care about. The terse version of the stack chain above is
+this:
+
+ test.dart 17:3 runAsync
+ test.dart 13:28 scheduleAsync.<fn>
+ ===== asynchronous gap ===========================
+ dart:async _Future.then
+ test.dart 13:12 scheduleAsync
+ test.dart 7:18 main.<fn>
+ package:stack_trace Chain.capture
+ test.dart 6:16 main
+
+That's a lot easier to understand!
+
+[Zone]: https://api.dart.dev/stable/dart-async/Zone-class.html
diff --git a/pkgs/stack_trace/analysis_options.yaml b/pkgs/stack_trace/analysis_options.yaml
new file mode 100644
index 0000000..4eb82ce
--- /dev/null
+++ b/pkgs/stack_trace/analysis_options.yaml
@@ -0,0 +1,22 @@
+# https://dart.dev/tools/analysis#the-analysis-options-file
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-casts: true
+ strict-raw-types: true
+
+linter:
+ rules:
+ - avoid_private_typedef_functions
+ - avoid_redundant_argument_values
+ - avoid_unused_constructor_parameters
+ - avoid_void_async
+ - cancel_subscriptions
+ - literal_only_boolean_expressions
+ - missing_whitespace_between_adjacent_strings
+ - no_adjacent_strings_in_list
+ - no_runtimeType_toString
+ - prefer_const_declarations
+ - unnecessary_await_in_return
+ - use_string_buffers
diff --git a/pkgs/stack_trace/example/example.dart b/pkgs/stack_trace/example/example.dart
new file mode 100644
index 0000000..d601ca4
--- /dev/null
+++ b/pkgs/stack_trace/example/example.dart
@@ -0,0 +1,15 @@
+import 'dart:async';
+
+import 'package:stack_trace/stack_trace.dart';
+
+void main() {
+ Chain.capture(_scheduleAsync);
+}
+
+void _scheduleAsync() {
+ Future<void>.delayed(const Duration(seconds: 1)).then((_) => _runAsync());
+}
+
+void _runAsync() {
+ throw StateError('oh no!');
+}
diff --git a/pkgs/stack_trace/lib/src/chain.dart b/pkgs/stack_trace/lib/src/chain.dart
new file mode 100644
index 0000000..6a815c6
--- /dev/null
+++ b/pkgs/stack_trace/lib/src/chain.dart
@@ -0,0 +1,264 @@
+// 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:math' as math;
+
+import 'frame.dart';
+import 'lazy_chain.dart';
+import 'stack_zone_specification.dart';
+import 'trace.dart';
+import 'utils.dart';
+
+/// A function that handles errors in the zone wrapped by [Chain.capture].
+@Deprecated('Will be removed in stack_trace 2.0.0.')
+typedef ChainHandler = void Function(dynamic error, Chain chain);
+
+/// An opaque key used to track the current [StackZoneSpecification].
+final _specKey = Object();
+
+/// A chain of stack traces.
+///
+/// A stack chain is a collection of one or more stack traces that collectively
+/// represent the path from `main` through nested function calls to a particular
+/// code location, usually where an error was thrown. Multiple stack traces are
+/// necessary when using asynchronous functions, since the program's stack is
+/// reset before each asynchronous callback is run.
+///
+/// Stack chains can be automatically tracked using [Chain.capture]. This sets
+/// up a new [Zone] in which the current stack chain is tracked and can be
+/// accessed using [Chain.current]. Any errors that would be top-leveled in
+/// the zone can be handled, along with their associated chains, with the
+/// `onError` callback. For example:
+///
+/// Chain.capture(() {
+/// // ...
+/// }, onError: (error, stackChain) {
+/// print("Caught error $error\n"
+/// "$stackChain");
+/// });
+class Chain implements StackTrace {
+ /// The stack traces that make up this chain.
+ ///
+ /// Like the frames in a stack trace, the traces are ordered from most local
+ /// to least local. The first one is the trace where the actual exception was
+ /// raised, the second one is where that callback was scheduled, and so on.
+ final List<Trace> traces;
+
+ /// The [StackZoneSpecification] for the current zone.
+ static StackZoneSpecification? get _currentSpec =>
+ Zone.current[_specKey] as StackZoneSpecification?;
+
+ /// If [when] is `true`, runs [callback] in a [Zone] in which the current
+ /// stack chain is tracked and automatically associated with (most) errors.
+ ///
+ /// If [when] is `false`, this does not track stack chains. Instead, it's
+ /// identical to [runZoned], except that it wraps any errors in
+ /// [Chain.forTrace]—which will only wrap the trace unless there's a different
+ /// [Chain.capture] active. This makes it easy for the caller to only capture
+ /// stack chains in debug mode or during development.
+ ///
+ /// If [onError] is passed, any error in the zone that would otherwise go
+ /// unhandled is passed to it, along with the [Chain] associated with that
+ /// error. Note that if [callback] produces multiple unhandled errors,
+ /// [onError] may be called more than once. If [onError] isn't passed, the
+ /// parent Zone's `unhandledErrorHandler` will be called with the error and
+ /// its chain.
+ ///
+ /// The zone this creates will be an error zone if either [onError] is
+ /// not `null` and [when] is false,
+ /// or if both [when] and [errorZone] are `true`.
+ /// If [errorZone] is `false`, [onError] must be `null`.
+ ///
+ /// If [callback] returns a value, it will be returned by [capture] as well.
+ ///
+ /// [zoneValues] is added to the [runZoned] calls.
+ static T capture<T>(T Function() callback,
+ {void Function(Object error, Chain)? onError,
+ bool when = true,
+ bool errorZone = true,
+ Map<Object?, Object?>? zoneValues}) {
+ if (!errorZone && onError != null) {
+ throw ArgumentError.value(
+ onError, 'onError', 'must be null if errorZone is false');
+ }
+
+ if (!when) {
+ if (onError == null) return runZoned(callback, zoneValues: zoneValues);
+ return runZonedGuarded(callback, (error, stackTrace) {
+ onError(error, Chain.forTrace(stackTrace));
+ }, zoneValues: zoneValues) as T;
+ }
+
+ var spec = StackZoneSpecification(onError, errorZone: errorZone);
+ return runZoned(() {
+ try {
+ return callback();
+ } on Object catch (error, stackTrace) {
+ // Forward synchronous errors through the async error path to match the
+ // behavior of `runZonedGuarded`.
+ Zone.current.handleUncaughtError(error, stackTrace);
+
+ // If the expected return type of capture() is not nullable, this will
+ // throw a cast exception. But the only other alternative is to throw
+ // some other exception. Casting null to T at least lets existing uses
+ // where T is a nullable type continue to work.
+ return null as T;
+ }
+ }, zoneSpecification: spec.toSpec(), zoneValues: {
+ ...?zoneValues,
+ _specKey: spec,
+ StackZoneSpecification.disableKey: false
+ });
+ }
+
+ /// If [when] is `true` and this is called within a [Chain.capture] zone, runs
+ /// [callback] in a [Zone] in which chain capturing is disabled.
+ ///
+ /// If [callback] returns a value, it will be returned by [disable] as well.
+ static T disable<T>(T Function() callback, {bool when = true}) {
+ var zoneValues =
+ when ? {_specKey: null, StackZoneSpecification.disableKey: true} : null;
+
+ return runZoned(callback, zoneValues: zoneValues);
+ }
+
+ /// Returns [futureOrStream] unmodified.
+ ///
+ /// Prior to Dart 1.7, this was necessary to ensure that stack traces for
+ /// exceptions reported with [Completer.completeError] and
+ /// [StreamController.addError] were tracked correctly.
+ @Deprecated('Chain.track is not necessary in Dart 1.7+.')
+ static dynamic track(Object? futureOrStream) => futureOrStream;
+
+ /// Returns the current stack chain.
+ ///
+ /// By default, the first frame of the first trace will be the line where
+ /// [Chain.current] is called. If [level] is passed, the first trace will
+ /// start that many frames up instead.
+ ///
+ /// If this is called outside of a [capture] zone, it just returns a
+ /// single-trace chain.
+ factory Chain.current([int level = 0]) {
+ if (_currentSpec != null) return _currentSpec!.currentChain(level + 1);
+
+ var chain = Chain.forTrace(StackTrace.current);
+ return LazyChain(() {
+ // JS includes a frame for the call to StackTrace.current, but the VM
+ // doesn't, so we skip an extra frame in a JS context.
+ var first = Trace(chain.traces.first.frames.skip(level + (inJS ? 2 : 1)),
+ original: chain.traces.first.original.toString());
+ return Chain([first, ...chain.traces.skip(1)]);
+ });
+ }
+
+ /// Returns the stack chain associated with [trace].
+ ///
+ /// The first stack trace in the returned chain will always be [trace]
+ /// (converted to a [Trace] if necessary). If there is no chain associated
+ /// with [trace] or if this is called outside of a [capture] zone, this just
+ /// returns a single-trace chain containing [trace].
+ ///
+ /// If [trace] is already a [Chain], it will be returned as-is.
+ factory Chain.forTrace(StackTrace trace) {
+ if (trace is Chain) return trace;
+ if (_currentSpec != null) return _currentSpec!.chainFor(trace);
+ if (trace is Trace) return Chain([trace]);
+ return LazyChain(() => Chain.parse(trace.toString()));
+ }
+
+ /// Parses a string representation of a stack chain.
+ ///
+ /// If [chain] is the output of a call to [Chain.toString], it will be parsed
+ /// as a full stack chain. Otherwise, it will be parsed as in [Trace.parse]
+ /// and returned as a single-trace chain.
+ factory Chain.parse(String chain) {
+ if (chain.isEmpty) return Chain([]);
+ if (chain.contains(vmChainGap)) {
+ return Chain(chain
+ .split(vmChainGap)
+ .where((line) => line.isNotEmpty)
+ .map(Trace.parseVM));
+ }
+ if (!chain.contains(chainGap)) return Chain([Trace.parse(chain)]);
+
+ return Chain(chain.split(chainGap).map(Trace.parseFriendly));
+ }
+
+ /// Returns a new [Chain] comprised of [traces].
+ Chain(Iterable<Trace> traces) : traces = List<Trace>.unmodifiable(traces);
+
+ /// Returns a terser version of this chain.
+ ///
+ /// This calls [Trace.terse] on every trace in [traces], and discards any
+ /// trace that contain only internal frames.
+ ///
+ /// This won't do anything with a raw JavaScript trace, since there's no way
+ /// to determine which frames come from which Dart libraries. However, the
+ /// [`source_map_stack_trace`](https://pub.dev/packages/source_map_stack_trace)
+ /// package can be used to convert JavaScript traces into Dart-style traces.
+ Chain get terse => foldFrames((_) => false, terse: true);
+
+ /// Returns a new [Chain] based on this chain where multiple stack frames
+ /// matching [predicate] are folded together.
+ ///
+ /// This means that whenever there are multiple frames in a row that match
+ /// [predicate], only the last one is kept. In addition, traces that are
+ /// composed entirely of frames matching [predicate] are omitted.
+ ///
+ /// This is useful for limiting the amount of library code that appears in a
+ /// stack trace by only showing user code and code that's called by user code.
+ ///
+ /// If [terse] is true, this will also fold together frames from the core
+ /// library or from this package, and simplify core library frames as in
+ /// [Trace.terse].
+ Chain foldFrames(bool Function(Frame) predicate, {bool terse = false}) {
+ var foldedTraces =
+ traces.map((trace) => trace.foldFrames(predicate, terse: terse));
+ var nonEmptyTraces = foldedTraces.where((trace) {
+ // Ignore traces that contain only folded frames.
+ if (trace.frames.length > 1) return true;
+ if (trace.frames.isEmpty) return false;
+
+ // In terse mode, the trace may have removed an outer folded frame,
+ // leaving a single non-folded frame. We can detect a folded frame because
+ // it has no line information.
+ if (!terse) return false;
+ return trace.frames.single.line != null;
+ });
+
+ // If all the traces contain only internal processing, preserve the last
+ // (top-most) one so that the chain isn't empty.
+ if (nonEmptyTraces.isEmpty && foldedTraces.isNotEmpty) {
+ return Chain([foldedTraces.last]);
+ }
+
+ return Chain(nonEmptyTraces);
+ }
+
+ /// Converts this chain to a [Trace].
+ ///
+ /// The trace version of a chain is just the concatenation of all the traces
+ /// in the chain.
+ Trace toTrace() => Trace(traces.expand((trace) => trace.frames));
+
+ @override
+ String toString() {
+ // Figure out the longest path so we know how much to pad.
+ var longest = traces
+ .map((trace) => trace.frames
+ .map((frame) => frame.location.length)
+ .fold(0, math.max))
+ .fold(0, math.max);
+
+ // Don't call out to [Trace.toString] here because that doesn't ensure that
+ // padding is consistent across all traces.
+ return traces
+ .map((trace) => trace.frames
+ .map((frame) =>
+ '${frame.location.padRight(longest)} ${frame.member}\n')
+ .join())
+ .join(chainGap);
+ }
+}
diff --git a/pkgs/stack_trace/lib/src/frame.dart b/pkgs/stack_trace/lib/src/frame.dart
new file mode 100644
index 0000000..d4043b7
--- /dev/null
+++ b/pkgs/stack_trace/lib/src/frame.dart
@@ -0,0 +1,458 @@
+// 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:path/path.dart' as path;
+
+import 'trace.dart';
+import 'unparsed_frame.dart';
+
+// #1 Foo._bar (file:///home/nweiz/code/stuff.dart:42:21)
+// #1 Foo._bar (file:///home/nweiz/code/stuff.dart:42)
+// #1 Foo._bar (file:///home/nweiz/code/stuff.dart)
+final _vmFrame = RegExp(r'^#\d+\s+(\S.*) \((.+?)((?::\d+){0,2})\)$');
+
+// at Object.stringify (native)
+// at VW.call$0 (https://example.com/stuff.dart.js:560:28)
+// at VW.call$0 (eval as fn
+// (https://example.com/stuff.dart.js:560:28), efn:3:28)
+// at https://example.com/stuff.dart.js:560:28
+final _v8JsFrame =
+ RegExp(r'^\s*at (?:(\S.*?)(?: \[as [^\]]+\])? \((.*)\)|(.*))$');
+
+// https://example.com/stuff.dart.js:560:28
+// https://example.com/stuff.dart.js:560
+//
+// Group 1: URI, required
+// Group 2: line number, required
+// Group 3: column number, optional
+final _v8JsUrlLocation = RegExp(r'^(.*?):(\d+)(?::(\d+))?$|native$');
+
+// With names:
+//
+// at Error.f (wasm://wasm/0006d966:wasm-function[119]:0xbb13)
+// at g (wasm://wasm/0006d966:wasm-function[796]:0x143b4)
+//
+// Without names:
+//
+// at wasm://wasm/0005168a:wasm-function[119]:0xbb13
+// at wasm://wasm/0005168a:wasm-function[796]:0x143b4
+//
+// Matches named groups:
+//
+// - "member": optional, `Error.f` in the first example, NA in the second.
+// - "uri": `wasm://wasm/0006d966`.
+// - "index": `119`.
+// - "offset": (hex number) `bb13`.
+//
+// To avoid having multiple groups for the same part of the frame, this regex
+// matches unmatched parentheses after the member name.
+final _v8WasmFrame = RegExp(r'^\s*at (?:(?<member>.+) )?'
+ r'(?:\(?(?:(?<uri>\S+):wasm-function\[(?<index>\d+)\]'
+ r'\:0x(?<offset>[0-9a-fA-F]+))\)?)$');
+
+// eval as function (https://example.com/stuff.dart.js:560:28), efn:3:28
+// eval as function (https://example.com/stuff.dart.js:560:28)
+// eval as function (eval as otherFunction
+// (https://example.com/stuff.dart.js:560:28))
+final _v8EvalLocation =
+ RegExp(r'^eval at (?:\S.*?) \((.*)\)(?:, .*?:\d+:\d+)?$');
+
+// anonymous/<@https://example.com/stuff.js line 693 > Function:3:40
+// anonymous/<@https://example.com/stuff.js line 693 > eval:3:40
+final _firefoxEvalLocation =
+ RegExp(r'(\S+)@(\S+) line (\d+) >.* (Function|eval):\d+:\d+');
+
+// .VW.call$0@https://example.com/stuff.dart.js:560
+// .VW.call$0("arg")@https://example.com/stuff.dart.js:560
+// .VW.call$0/name<@https://example.com/stuff.dart.js:560
+// .VW.call$0@https://example.com/stuff.dart.js:560:36
+// https://example.com/stuff.dart.js:560
+final _firefoxSafariJSFrame = RegExp(r'^'
+ r'(?:' // Member description. Not present in some Safari frames.
+ r'([^@(/]*)' // The actual name of the member.
+ r'(?:\(.*\))?' // Arguments to the member, sometimes captured by Firefox.
+ r'((?:/[^/]*)*)' // Extra characters indicating a nested closure.
+ r'(?:\(.*\))?' // Arguments to the closure.
+ r'@'
+ r')?'
+ r'(.*?)' // The frame's URL.
+ r':'
+ r'(\d*)' // The line number. Empty in Safari if it's unknown.
+ r'(?::(\d*))?' // The column number. Not present in older browsers and
+ // empty in Safari if it's unknown.
+ r'$');
+
+// With names:
+//
+// g@http://localhost:8080/test.wasm:wasm-function[796]:0x143b4
+// f@http://localhost:8080/test.wasm:wasm-function[795]:0x143a8
+// main@http://localhost:8080/test.wasm:wasm-function[792]:0x14390
+//
+// Without names:
+//
+// @http://localhost:8080/test.wasm:wasm-function[796]:0x143b4
+// @http://localhost:8080/test.wasm:wasm-function[795]:0x143a8
+// @http://localhost:8080/test.wasm:wasm-function[792]:0x14390
+//
+// JSShell in the command line uses a different format, which this regex also
+// parses.
+//
+// With names:
+//
+// main@/home/user/test.mjs line 29 > WebAssembly.compile:wasm-function[792]:0x14378
+//
+// Without names:
+//
+// @/home/user/test.mjs line 29 > WebAssembly.compile:wasm-function[792]:0x14378
+//
+// Matches named groups:
+//
+// - "member": Function name, may be empty: `g`.
+// - "uri": `http://localhost:8080/test.wasm`.
+// - "index": `796`.
+// - "offset": (in hex) `143b4`.
+final _firefoxWasmFrame =
+ RegExp(r'^(?<member>.*?)@(?:(?<uri>\S+).*?:wasm-function'
+ r'\[(?<index>\d+)\]:0x(?<offset>[0-9a-fA-F]+))$');
+
+// With names:
+//
+// (Note: Lines below are literal text, e.g. <?> is not a placeholder, it's a
+// part of the stack frame.)
+//
+// <?>.wasm-function[g]@[wasm code]
+// <?>.wasm-function[f]@[wasm code]
+// <?>.wasm-function[main]@[wasm code]
+//
+// Without names:
+//
+// <?>.wasm-function[796]@[wasm code]
+// <?>.wasm-function[795]@[wasm code]
+// <?>.wasm-function[792]@[wasm code]
+//
+// Matches named group "member": `g` or `796`.
+final _safariWasmFrame =
+ RegExp(r'^.*?wasm-function\[(?<member>.*)\]@\[wasm code\]$');
+
+// foo/bar.dart 10:11 Foo._bar
+// foo/bar.dart 10:11 (anonymous function).dart.fn
+// https://dart.dev/foo/bar.dart Foo._bar
+// data:... 10:11 Foo._bar
+final _friendlyFrame = RegExp(r'^(\S+)(?: (\d+)(?::(\d+))?)?\s+([^\d].*)$');
+
+/// A regular expression that matches asynchronous member names generated by the
+/// VM.
+final _asyncBody = RegExp(r'<(<anonymous closure>|[^>]+)_async_body>');
+
+final _initialDot = RegExp(r'^\.');
+
+/// A single stack frame. Each frame points to a precise location in Dart code.
+class Frame {
+ /// The URI of the file in which the code is located.
+ ///
+ /// This URI will usually have the scheme `dart`, `file`, `http`, or `https`.
+ final Uri uri;
+
+ /// The line number on which the code location is located.
+ ///
+ /// This can be null, indicating that the line number is unknown or
+ /// unimportant.
+ final int? line;
+
+ /// The column number of the code location.
+ ///
+ /// This can be null, indicating that the column number is unknown or
+ /// unimportant.
+ final int? column;
+
+ /// The name of the member in which the code location occurs.
+ ///
+ /// Anonymous closures are represented as `<fn>` in this member string.
+ final String? member;
+
+ /// Whether this stack frame comes from the Dart core libraries.
+ bool get isCore => uri.scheme == 'dart';
+
+ /// Returns a human-friendly description of the library that this stack frame
+ /// comes from.
+ ///
+ /// This will usually be the string form of [uri], but a relative URI will be
+ /// used if possible. Data URIs will be truncated.
+ String get library {
+ if (uri.scheme == 'data') return 'data:...';
+ return path.prettyUri(uri);
+ }
+
+ /// Returns the name of the package this stack frame comes from, or `null` if
+ /// this stack frame doesn't come from a `package:` URL.
+ String? get package {
+ if (uri.scheme != 'package') return null;
+ return uri.path.split('/').first;
+ }
+
+ /// A human-friendly description of the code location.
+ String get location {
+ if (line == null) return library;
+ if (column == null) return '$library $line';
+ return '$library $line:$column';
+ }
+
+ /// Returns a single frame of the current stack.
+ ///
+ /// By default, this will return the frame above the current method. If
+ /// [level] is `0`, it will return the current method's frame; if [level] is
+ /// higher than `1`, it will return higher frames.
+ factory Frame.caller([int level = 1]) {
+ if (level < 0) {
+ throw ArgumentError('Argument [level] must be greater than or equal '
+ 'to 0.');
+ }
+
+ return Trace.current(level + 1).frames.first;
+ }
+
+ /// Parses a string representation of a Dart VM stack frame.
+ factory Frame.parseVM(String frame) => _catchFormatException(frame, () {
+ // The VM sometimes folds multiple stack frames together and replaces
+ // them with "...".
+ if (frame == '...') {
+ return Frame(Uri(), null, null, '...');
+ }
+
+ var match = _vmFrame.firstMatch(frame);
+ if (match == null) return UnparsedFrame(frame);
+
+ // Get the pieces out of the regexp match. Function, URI and line should
+ // always be found. The column is optional.
+ var member = match[1]!
+ .replaceAll(_asyncBody, '<async>')
+ .replaceAll('<anonymous closure>', '<fn>');
+ var uri = match[2]!.startsWith('<data:')
+ ? Uri.dataFromString('')
+ : Uri.parse(match[2]!);
+
+ var lineAndColumn = match[3]!.split(':');
+ var line =
+ lineAndColumn.length > 1 ? int.parse(lineAndColumn[1]) : null;
+ var column =
+ lineAndColumn.length > 2 ? int.parse(lineAndColumn[2]) : null;
+ return Frame(uri, line, column, member);
+ });
+
+ /// Parses a string representation of a Chrome/V8 stack frame.
+ factory Frame.parseV8(String frame) => _catchFormatException(frame, () {
+ // Try to match a Wasm frame first: the Wasm frame regex won't match a
+ // JS frame but the JS frame regex may match a Wasm frame.
+ var match = _v8WasmFrame.firstMatch(frame);
+ if (match != null) {
+ final member = match.namedGroup('member');
+ final uri = _uriOrPathToUri(match.namedGroup('uri')!);
+ final functionIndex = match.namedGroup('index')!;
+ final functionOffset =
+ int.parse(match.namedGroup('offset')!, radix: 16);
+ return Frame(uri, 1, functionOffset + 1, member ?? functionIndex);
+ }
+
+ match = _v8JsFrame.firstMatch(frame);
+ if (match != null) {
+ // v8 location strings can be arbitrarily-nested, since it adds a
+ // layer of nesting for each eval performed on that line.
+ Frame parseJsLocation(String location, String member) {
+ var evalMatch = _v8EvalLocation.firstMatch(location);
+ while (evalMatch != null) {
+ location = evalMatch[1]!;
+ evalMatch = _v8EvalLocation.firstMatch(location);
+ }
+
+ if (location == 'native') {
+ return Frame(Uri.parse('native'), null, null, member);
+ }
+
+ var urlMatch = _v8JsUrlLocation.firstMatch(location);
+ if (urlMatch == null) return UnparsedFrame(frame);
+
+ final uri = _uriOrPathToUri(urlMatch[1]!);
+ final line = int.parse(urlMatch[2]!);
+ final columnMatch = urlMatch[3];
+ final column = columnMatch != null ? int.parse(columnMatch) : null;
+ return Frame(uri, line, column, member);
+ }
+
+ // V8 stack frames can be in two forms.
+ if (match[2] != null) {
+ // The first form looks like " at FUNCTION (LOCATION)". V8 proper
+ // lists anonymous functions within eval as "<anonymous>", while
+ // IE10 lists them as "Anonymous function".
+ return parseJsLocation(
+ match[2]!,
+ match[1]!
+ .replaceAll('<anonymous>', '<fn>')
+ .replaceAll('Anonymous function', '<fn>')
+ .replaceAll('(anonymous function)', '<fn>'));
+ } else {
+ // The second form looks like " at LOCATION", and is used for
+ // anonymous functions.
+ return parseJsLocation(match[3]!, '<fn>');
+ }
+ }
+
+ return UnparsedFrame(frame);
+ });
+
+ /// Parses a string representation of a JavaScriptCore stack trace.
+ factory Frame.parseJSCore(String frame) => Frame.parseV8(frame);
+
+ /// Parses a string representation of an IE stack frame.
+ ///
+ /// IE10+ frames look just like V8 frames. Prior to IE10, stack traces can't
+ /// be retrieved.
+ factory Frame.parseIE(String frame) => Frame.parseV8(frame);
+
+ /// Parses a Firefox 'eval' or 'function' stack frame.
+ ///
+ /// For example:
+ ///
+ /// ```
+ /// anonymous/<@https://example.com/stuff.js line 693 > Function:3:40
+ /// anonymous/<@https://example.com/stuff.js line 693 > eval:3:40
+ /// ```
+ factory Frame._parseFirefoxEval(String frame) =>
+ _catchFormatException(frame, () {
+ final match = _firefoxEvalLocation.firstMatch(frame);
+ if (match == null) return UnparsedFrame(frame);
+ var member = match[1]!.replaceAll('/<', '');
+ final uri = _uriOrPathToUri(match[2]!);
+ final line = int.parse(match[3]!);
+ if (member.isEmpty || member == 'anonymous') {
+ member = '<fn>';
+ }
+ return Frame(uri, line, null, member);
+ });
+
+ /// Parses a string representation of a Firefox or Safari stack frame.
+ factory Frame.parseFirefox(String frame) => _catchFormatException(frame, () {
+ var match = _firefoxSafariJSFrame.firstMatch(frame);
+ if (match != null) {
+ if (match[3]!.contains(' line ')) {
+ return Frame._parseFirefoxEval(frame);
+ }
+
+ // Normally this is a URI, but in a jsshell trace it can be a path.
+ var uri = _uriOrPathToUri(match[3]!);
+
+ var member = match[1];
+ if (member != null) {
+ member +=
+ List.filled('/'.allMatches(match[2]!).length, '.<fn>').join();
+ if (member == '') member = '<fn>';
+
+ // Some Firefox members have initial dots. We remove them for
+ // consistency with other platforms.
+ member = member.replaceFirst(_initialDot, '');
+ } else {
+ member = '<fn>';
+ }
+
+ var line = match[4] == '' ? null : int.parse(match[4]!);
+ var column =
+ match[5] == null || match[5] == '' ? null : int.parse(match[5]!);
+ return Frame(uri, line, column, member);
+ }
+
+ match = _firefoxWasmFrame.firstMatch(frame);
+ if (match != null) {
+ final member = match.namedGroup('member')!;
+ final uri = _uriOrPathToUri(match.namedGroup('uri')!);
+ final functionIndex = match.namedGroup('index')!;
+ final functionOffset =
+ int.parse(match.namedGroup('offset')!, radix: 16);
+ return Frame(uri, 1, functionOffset + 1,
+ member.isNotEmpty ? member : functionIndex);
+ }
+
+ match = _safariWasmFrame.firstMatch(frame);
+ if (match != null) {
+ final member = match.namedGroup('member')!;
+ return Frame(Uri(path: 'wasm code'), null, null, member);
+ }
+
+ return UnparsedFrame(frame);
+ });
+
+ /// Parses a string representation of a Safari 6.0 stack frame.
+ @Deprecated('Use Frame.parseSafari instead.')
+ factory Frame.parseSafari6_0(String frame) => Frame.parseFirefox(frame);
+
+ /// Parses a string representation of a Safari 6.1+ stack frame.
+ @Deprecated('Use Frame.parseSafari instead.')
+ factory Frame.parseSafari6_1(String frame) => Frame.parseFirefox(frame);
+
+ /// Parses a string representation of a Safari stack frame.
+ factory Frame.parseSafari(String frame) => Frame.parseFirefox(frame);
+
+ /// Parses this package's string representation of a stack frame.
+ factory Frame.parseFriendly(String frame) => _catchFormatException(frame, () {
+ var match = _friendlyFrame.firstMatch(frame);
+ if (match == null) {
+ throw FormatException(
+ "Couldn't parse package:stack_trace stack trace line '$frame'.");
+ }
+ // Fake truncated data urls generated by the friendly stack trace format
+ // cause Uri.parse to throw an exception so we have to special case
+ // them.
+ var uri = match[1] == 'data:...'
+ ? Uri.dataFromString('')
+ : Uri.parse(match[1]!);
+ // If there's no scheme, this is a relative URI. We should interpret it
+ // as relative to the current working directory.
+ if (uri.scheme == '') {
+ uri = path.toUri(path.absolute(path.fromUri(uri)));
+ }
+
+ var line = match[2] == null ? null : int.parse(match[2]!);
+ var column = match[3] == null ? null : int.parse(match[3]!);
+ return Frame(uri, line, column, match[4]);
+ });
+
+ /// A regular expression matching an absolute URI.
+ static final _uriRegExp = RegExp(r'^[a-zA-Z][-+.a-zA-Z\d]*://');
+
+ /// A regular expression matching a Windows path.
+ static final _windowsRegExp = RegExp(r'^([a-zA-Z]:[\\/]|\\\\)');
+
+ /// Converts [uriOrPath], which can be a URI, a Windows path, or a Posix path,
+ /// to a URI (absolute if possible).
+ static Uri _uriOrPathToUri(String uriOrPath) {
+ if (uriOrPath.contains(_uriRegExp)) {
+ return Uri.parse(uriOrPath);
+ } else if (uriOrPath.contains(_windowsRegExp)) {
+ return Uri.file(uriOrPath, windows: true);
+ } else if (uriOrPath.startsWith('/')) {
+ return Uri.file(uriOrPath, windows: false);
+ }
+
+ // As far as I've seen, Firefox and V8 both always report absolute paths in
+ // their stack frames. However, if we do get a relative path, we should
+ // handle it gracefully.
+ if (uriOrPath.contains('\\')) return path.windows.toUri(uriOrPath);
+ return Uri.parse(uriOrPath);
+ }
+
+ /// Runs [body] and returns its result.
+ ///
+ /// If [body] throws a [FormatException], returns an [UnparsedFrame] with
+ /// [text] instead.
+ static Frame _catchFormatException(String text, Frame Function() body) {
+ try {
+ return body();
+ } on FormatException catch (_) {
+ return UnparsedFrame(text);
+ }
+ }
+
+ Frame(this.uri, this.line, this.column, this.member);
+
+ @override
+ String toString() => '$location in $member';
+}
diff --git a/pkgs/stack_trace/lib/src/lazy_chain.dart b/pkgs/stack_trace/lib/src/lazy_chain.dart
new file mode 100644
index 0000000..063ed59
--- /dev/null
+++ b/pkgs/stack_trace/lib/src/lazy_chain.dart
@@ -0,0 +1,33 @@
+// Copyright (c) 2017, 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 'chain.dart';
+import 'frame.dart';
+import 'lazy_trace.dart';
+import 'trace.dart';
+
+/// A thunk for lazily constructing a [Chain].
+typedef ChainThunk = Chain Function();
+
+/// A wrapper around a [ChainThunk]. This works around issue 9579 by avoiding
+/// the conversion of native [StackTrace]s to strings until it's absolutely
+/// necessary.
+class LazyChain implements Chain {
+ final ChainThunk _thunk;
+ late final Chain _chain = _thunk();
+
+ LazyChain(this._thunk);
+
+ @override
+ List<Trace> get traces => _chain.traces;
+ @override
+ Chain get terse => _chain.terse;
+ @override
+ Chain foldFrames(bool Function(Frame) predicate, {bool terse = false}) =>
+ LazyChain(() => _chain.foldFrames(predicate, terse: terse));
+ @override
+ Trace toTrace() => LazyTrace(_chain.toTrace);
+ @override
+ String toString() => _chain.toString();
+}
diff --git a/pkgs/stack_trace/lib/src/lazy_trace.dart b/pkgs/stack_trace/lib/src/lazy_trace.dart
new file mode 100644
index 0000000..3ecaa2d
--- /dev/null
+++ b/pkgs/stack_trace/lib/src/lazy_trace.dart
@@ -0,0 +1,33 @@
+// 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 'frame.dart';
+import 'trace.dart';
+
+/// A thunk for lazily constructing a [Trace].
+typedef TraceThunk = Trace Function();
+
+/// A wrapper around a [TraceThunk]. This works around issue 9579 by avoiding
+/// the conversion of native [StackTrace]s to strings until it's absolutely
+/// necessary.
+class LazyTrace implements Trace {
+ final TraceThunk _thunk;
+ late final Trace _trace = _thunk();
+
+ LazyTrace(this._thunk);
+
+ @override
+ List<Frame> get frames => _trace.frames;
+ @override
+ StackTrace get original => _trace.original;
+ @override
+ StackTrace get vmTrace => _trace.vmTrace;
+ @override
+ Trace get terse => LazyTrace(() => _trace.terse);
+ @override
+ Trace foldFrames(bool Function(Frame) predicate, {bool terse = false}) =>
+ LazyTrace(() => _trace.foldFrames(predicate, terse: terse));
+ @override
+ String toString() => _trace.toString();
+}
diff --git a/pkgs/stack_trace/lib/src/stack_zone_specification.dart b/pkgs/stack_trace/lib/src/stack_zone_specification.dart
new file mode 100644
index 0000000..901a5ee
--- /dev/null
+++ b/pkgs/stack_trace/lib/src/stack_zone_specification.dart
@@ -0,0 +1,262 @@
+// 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 'chain.dart';
+import 'lazy_chain.dart';
+import 'lazy_trace.dart';
+import 'trace.dart';
+import 'utils.dart';
+
+/// A class encapsulating the zone specification for a [Chain.capture] zone.
+///
+/// Until they're materialized and exposed to the user, stack chains are tracked
+/// as linked lists of [Trace]s using the [_Node] class. These nodes are stored
+/// in three distinct ways:
+///
+/// * When a callback is registered, a node is created and stored as a captured
+/// local variable until the callback is run.
+///
+/// * When a callback is run, its captured node is set as the [_currentNode] so
+/// it can be available to [Chain.current] and to be linked into additional
+/// chains when more callbacks are scheduled.
+///
+/// * When a callback throws an error or a Future or Stream emits an error, the
+/// current node is associated with that error's stack trace using the
+/// [_chains] expando.
+///
+/// Since [ZoneSpecification] can't be extended or even implemented, in order to
+/// get a real [ZoneSpecification] instance it's necessary to call [toSpec].
+class StackZoneSpecification {
+ /// An opaque object used as a zone value to disable chain tracking in a given
+ /// zone.
+ ///
+ /// If `Zone.current[disableKey]` is `true`, no stack chains will be tracked.
+ static final disableKey = Object();
+
+ /// Whether chain-tracking is disabled in the current zone.
+ bool get _disabled => Zone.current[disableKey] == true;
+
+ /// The expando that associates stack chains with [StackTrace]s.
+ ///
+ /// The chains are associated with stack traces rather than errors themselves
+ /// because it's a common practice to throw strings as errors, which can't be
+ /// used with expandos.
+ ///
+ /// The chain associated with a given stack trace doesn't contain a node for
+ /// that stack trace.
+ final _chains = Expando<_Node>('stack chains');
+
+ /// The error handler for the zone.
+ ///
+ /// If this is null, that indicates that any unhandled errors should be passed
+ /// to the parent zone.
+ final void Function(Object error, Chain)? _onError;
+
+ /// The most recent node of the current stack chain.
+ _Node? _currentNode;
+
+ /// Whether this is an error zone.
+ final bool _errorZone;
+
+ StackZoneSpecification(this._onError, {bool errorZone = true})
+ : _errorZone = errorZone;
+
+ /// Converts this specification to a real [ZoneSpecification].
+ ZoneSpecification toSpec() => ZoneSpecification(
+ handleUncaughtError: _errorZone ? _handleUncaughtError : null,
+ registerCallback: _registerCallback,
+ registerUnaryCallback: _registerUnaryCallback,
+ registerBinaryCallback: _registerBinaryCallback,
+ errorCallback: _errorCallback);
+
+ /// Returns the current stack chain.
+ ///
+ /// By default, the first frame of the first trace will be the line where
+ /// [currentChain] is called. If [level] is passed, the first trace will start
+ /// that many frames up instead.
+ Chain currentChain([int level = 0]) => _createNode(level + 1).toChain();
+
+ /// Returns the stack chain associated with [trace], if one exists.
+ ///
+ /// The first stack trace in the returned chain will always be [trace]
+ /// (converted to a [Trace] if necessary). If there is no chain associated
+ /// with [trace], this just returns a single-trace chain containing [trace].
+ Chain chainFor(StackTrace? trace) {
+ if (trace is Chain) return trace;
+ trace ??= StackTrace.current;
+
+ var previous = _chains[trace] ?? _currentNode;
+ if (previous == null) {
+ // If there's no [_currentNode], we're running synchronously beneath
+ // [Chain.capture] and we should fall back to the VM's stack chaining. We
+ // can't use [Chain.from] here because it'll just call [chainFor] again.
+ if (trace is Trace) return Chain([trace]);
+ return LazyChain(() => Chain.parse(trace!.toString()));
+ } else {
+ if (trace is! Trace) {
+ var original = trace;
+ trace = LazyTrace(() => Trace.parse(_trimVMChain(original)));
+ }
+
+ return _Node(trace, previous).toChain();
+ }
+ }
+
+ /// Tracks the current stack chain so it can be set to [_currentNode] when
+ /// [f] is run.
+ ZoneCallback<R> _registerCallback<R>(
+ Zone self, ZoneDelegate parent, Zone zone, R Function() f) {
+ if (_disabled) return parent.registerCallback(zone, f);
+ var node = _createNode(1);
+ return parent.registerCallback(zone, () => _run(f, node));
+ }
+
+ /// Tracks the current stack chain so it can be set to [_currentNode] when
+ /// [f] is run.
+ ZoneUnaryCallback<R, T> _registerUnaryCallback<R, T>(
+ Zone self,
+ ZoneDelegate parent,
+ Zone zone,
+ @pragma('vm:awaiter-link') R Function(T) f) {
+ if (_disabled) return parent.registerUnaryCallback(zone, f);
+ var node = _createNode(1);
+ return parent.registerUnaryCallback(
+ zone, (arg) => _run(() => f(arg), node));
+ }
+
+ /// Tracks the current stack chain so it can be set to [_currentNode] when
+ /// [f] is run.
+ ZoneBinaryCallback<R, T1, T2> _registerBinaryCallback<R, T1, T2>(
+ Zone self, ZoneDelegate parent, Zone zone, R Function(T1, T2) f) {
+ if (_disabled) return parent.registerBinaryCallback(zone, f);
+
+ var node = _createNode(1);
+ return parent.registerBinaryCallback(
+ zone, (arg1, arg2) => _run(() => f(arg1, arg2), node));
+ }
+
+ /// Looks up the chain associated with [stackTrace] and passes it either to
+ /// [_onError] or [parent]'s error handler.
+ void _handleUncaughtError(Zone self, ZoneDelegate parent, Zone zone,
+ Object error, StackTrace stackTrace) {
+ if (_disabled) {
+ parent.handleUncaughtError(zone, error, stackTrace);
+ return;
+ }
+
+ var stackChain = chainFor(stackTrace);
+ if (_onError == null) {
+ parent.handleUncaughtError(zone, error, stackChain);
+ return;
+ }
+
+ // TODO(nweiz): Currently this copies a lot of logic from [runZoned]. Just
+ // allow [runBinary] to throw instead once issue 18134 is fixed.
+ try {
+ // TODO(rnystrom): Is the null-assertion correct here? It is nullable in
+ // Zone. Should we check for that here?
+ self.parent!.runBinary(_onError, error, stackChain);
+ } on Object catch (newError, newStackTrace) {
+ if (identical(newError, error)) {
+ parent.handleUncaughtError(zone, error, stackChain);
+ } else {
+ parent.handleUncaughtError(zone, newError, newStackTrace);
+ }
+ }
+ }
+
+ /// Attaches the current stack chain to [stackTrace], replacing it if
+ /// necessary.
+ AsyncError? _errorCallback(Zone self, ZoneDelegate parent, Zone zone,
+ Object error, StackTrace? stackTrace) {
+ if (_disabled) return parent.errorCallback(zone, error, stackTrace);
+
+ // Go up two levels to get through [_CustomZone.errorCallback].
+ if (stackTrace == null) {
+ stackTrace = _createNode(2).toChain();
+ } else {
+ if (_chains[stackTrace] == null) _chains[stackTrace] = _createNode(2);
+ }
+
+ var asyncError = parent.errorCallback(zone, error, stackTrace);
+ return asyncError ?? AsyncError(error, stackTrace);
+ }
+
+ /// Creates a [_Node] with the current stack trace and linked to
+ /// [_currentNode].
+ ///
+ /// By default, the first frame of the first trace will be the line where
+ /// [_createNode] is called. If [level] is passed, the first trace will start
+ /// that many frames up instead.
+ _Node _createNode([int level = 0]) =>
+ _Node(_currentTrace(level + 1), _currentNode);
+
+ // TODO(nweiz): use a more robust way of detecting and tracking errors when
+ // issue 15105 is fixed.
+ /// Runs [f] with [_currentNode] set to [node].
+ ///
+ /// If [f] throws an error, this associates [node] with that error's stack
+ /// trace.
+ T _run<T>(T Function() f, _Node node) {
+ var previousNode = _currentNode;
+ _currentNode = node;
+ try {
+ return f();
+ } catch (e, stackTrace) {
+ // We can see the same stack trace multiple times if it's rethrown through
+ // guarded callbacks. The innermost chain will have the most
+ // information so it should take precedence.
+ _chains[stackTrace] ??= node;
+ rethrow;
+ } finally {
+ _currentNode = previousNode;
+ }
+ }
+
+ /// Like [Trace.current], but if the current stack trace has VM chaining
+ /// enabled, this only returns the innermost sub-trace.
+ Trace _currentTrace([int? level]) {
+ var stackTrace = StackTrace.current;
+ return LazyTrace(() {
+ var text = _trimVMChain(stackTrace);
+ var trace = Trace.parse(text);
+ // JS includes a frame for the call to StackTrace.current, but the VM
+ // doesn't, so we skip an extra frame in a JS context.
+ return Trace(trace.frames.skip((level ?? 0) + (inJS ? 2 : 1)),
+ original: text);
+ });
+ }
+
+ /// Removes the VM's stack chains from the native [trace], since we're
+ /// generating our own and we don't want duplicate frames.
+ String _trimVMChain(StackTrace trace) {
+ var text = trace.toString();
+ var index = text.indexOf(vmChainGap);
+ return index == -1 ? text : text.substring(0, index);
+ }
+}
+
+/// A linked list node representing a single entry in a stack chain.
+class _Node {
+ /// The stack trace for this link of the chain.
+ final Trace trace;
+
+ /// The previous node in the chain.
+ final _Node? previous;
+
+ _Node(StackTrace trace, [this.previous]) : trace = Trace.from(trace);
+
+ /// Converts this to a [Chain].
+ Chain toChain() {
+ var nodes = <Trace>[];
+ _Node? node = this;
+ while (node != null) {
+ nodes.add(node.trace);
+ node = node.previous;
+ }
+ return Chain(nodes);
+ }
+}
diff --git a/pkgs/stack_trace/lib/src/trace.dart b/pkgs/stack_trace/lib/src/trace.dart
new file mode 100644
index 0000000..b8c62f5
--- /dev/null
+++ b/pkgs/stack_trace/lib/src/trace.dart
@@ -0,0 +1,341 @@
+// 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:math' as math;
+
+import 'chain.dart';
+import 'frame.dart';
+import 'lazy_trace.dart';
+import 'unparsed_frame.dart';
+import 'utils.dart';
+import 'vm_trace.dart';
+
+final _terseRegExp = RegExp(r'(-patch)?([/\\].*)?$');
+
+/// A RegExp to match V8's stack traces.
+///
+/// V8's traces start with a line that's either just "Error" or else is a
+/// description of the exception that occurred. That description can be multiple
+/// lines, so we just look for any line other than the first that begins with
+/// three or four spaces and "at".
+final _v8Trace = RegExp(r'\n ?at ');
+
+/// A RegExp to match indidual lines of V8's stack traces.
+///
+/// This is intended to filter out the leading exception details of the trace
+/// though it is possible for the message to match this as well.
+final _v8TraceLine = RegExp(r' ?at ');
+
+/// A RegExp to match Firefox's eval and Function stack traces.
+///
+/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/stack
+///
+/// These stack traces look like:
+///
+/// ````
+/// anonymous/<@https://example.com/stuff.js line 693 > Function:3:40
+/// anonymous/<@https://example.com/stuff.js line 693 > eval:3:40
+/// ````
+final _firefoxEvalTrace = RegExp(r'@\S+ line \d+ >.* (Function|eval):\d+:\d+');
+
+/// A RegExp to match Firefox and Safari's stack traces.
+///
+/// Firefox and Safari have very similar stack trace formats, so we use the same
+/// logic for parsing them.
+///
+/// Firefox's trace frames start with the name of the function in which the
+/// error occurred, possibly including its parameters inside `()`. For example,
+/// `.VW.call$0("arg")@https://example.com/stuff.dart.js:560`.
+///
+/// Safari traces occasionally don't include the initial method name followed by
+/// "@", and they always have both the line and column number (or just a
+/// trailing colon if no column number is available). They can also contain
+/// empty lines or lines consisting only of `[native code]`.
+final _firefoxSafariTrace = RegExp(
+ r'^'
+ r'(' // Member description. Not present in some Safari frames.
+ r'([.0-9A-Za-z_$/<]|\(.*\))*' // Member name and arguments.
+ r'@'
+ r')?'
+ r'[^\s]*' // Frame URL.
+ r':\d*' // Line or column number. Some older frames only have a line number.
+ r'$',
+ multiLine: true);
+
+/// A RegExp to match this package's stack traces.
+final _friendlyTrace =
+ RegExp(r'^[^\s<][^\s]*( \d+(:\d+)?)?[ \t]+[^\s]+$', multiLine: true);
+
+/// A stack trace, comprised of a list of stack frames.
+class Trace implements StackTrace {
+ /// The stack frames that comprise this stack trace.
+ final List<Frame> frames;
+
+ /// The original stack trace from which this trace was parsed.
+ final StackTrace original;
+
+ /// Returns a human-readable representation of [stackTrace]. If [terse] is
+ /// set, this folds together multiple stack frames from the Dart core
+ /// libraries, so that only the core library method directly called from user
+ /// code is visible (see [Trace.terse]).
+ static String format(StackTrace stackTrace, {bool terse = true}) {
+ var trace = Trace.from(stackTrace);
+ if (terse) trace = trace.terse;
+ return trace.toString();
+ }
+
+ /// Returns the current stack trace.
+ ///
+ /// By default, the first frame of this trace will be the line where
+ /// [Trace.current] is called. If [level] is passed, the trace will start that
+ /// many frames up instead.
+ factory Trace.current([int level = 0]) {
+ if (level < 0) {
+ throw ArgumentError('Argument [level] must be greater than or equal '
+ 'to 0.');
+ }
+
+ var trace = Trace.from(StackTrace.current);
+ return LazyTrace(
+ () =>
+ // JS includes a frame for the call to StackTrace.current, but the VM
+ // doesn't, so we skip an extra frame in a JS context.
+ Trace(trace.frames.skip(level + (inJS ? 2 : 1)),
+ original: trace.original.toString()),
+ );
+ }
+
+ /// Returns a new stack trace containing the same data as [trace].
+ ///
+ /// If [trace] is a native [StackTrace], its data will be parsed out; if it's
+ /// a [Trace], it will be returned as-is.
+ factory Trace.from(StackTrace trace) {
+ if (trace is Trace) return trace;
+ if (trace is Chain) return trace.toTrace();
+ return LazyTrace(() => Trace.parse(trace.toString()));
+ }
+
+ /// Parses a string representation of a stack trace.
+ ///
+ /// [trace] should be formatted in the same way as a Dart VM or browser stack
+ /// trace. If it's formatted as a stack chain, this will return the equivalent
+ /// of [Chain.toTrace].
+ factory Trace.parse(String trace) {
+ try {
+ if (trace.isEmpty) return Trace(<Frame>[]);
+ if (trace.contains(_v8Trace)) return Trace.parseV8(trace);
+ if (trace.contains('\tat ')) return Trace.parseJSCore(trace);
+ if (trace.contains(_firefoxSafariTrace) ||
+ trace.contains(_firefoxEvalTrace)) {
+ return Trace.parseFirefox(trace);
+ }
+ if (trace.contains(chainGap)) return Chain.parse(trace).toTrace();
+ if (trace.contains(_friendlyTrace)) {
+ return Trace.parseFriendly(trace);
+ }
+
+ // Default to parsing the stack trace as a VM trace. This is also hit on
+ // IE and Safari, where the stack trace is just an empty string (issue
+ // 11257).
+ return Trace.parseVM(trace);
+ } on FormatException catch (error) {
+ throw FormatException('${error.message}\nStack trace:\n$trace');
+ }
+ }
+
+ /// Parses a string representation of a Dart VM stack trace.
+ Trace.parseVM(String trace) : this(_parseVM(trace), original: trace);
+
+ static List<Frame> _parseVM(String trace) {
+ // Ignore [vmChainGap]. This matches the behavior of
+ // `Chain.parse().toTrace()`.
+ var lines = trace
+ .trim()
+ .replaceAll(vmChainGap, '')
+ .split('\n')
+ .where((line) => line.isNotEmpty);
+
+ if (lines.isEmpty) {
+ return [];
+ }
+
+ var frames = lines.take(lines.length - 1).map(Frame.parseVM).toList();
+
+ // TODO(nweiz): Remove this when issue 23614 is fixed.
+ if (!lines.last.endsWith('.da')) {
+ frames.add(Frame.parseVM(lines.last));
+ }
+
+ return frames;
+ }
+
+ /// Parses a string representation of a Chrome/V8 stack trace.
+ Trace.parseV8(String trace)
+ : this(
+ trace
+ .split('\n')
+ .skip(1)
+ // It's possible that an Exception's description contains a line
+ // that looks like a V8 trace line, which will screw this up.
+ // Unfortunately, that's impossible to detect.
+ .skipWhile((line) => !line.startsWith(_v8TraceLine))
+ .map(Frame.parseV8),
+ original: trace);
+
+ /// Parses a string representation of a JavaScriptCore stack trace.
+ Trace.parseJSCore(String trace)
+ : this(
+ trace
+ .split('\n')
+ .where((line) => line != '\tat ')
+ .map(Frame.parseV8),
+ original: trace);
+
+ /// Parses a string representation of an Internet Explorer stack trace.
+ ///
+ /// IE10+ traces look just like V8 traces. Prior to IE10, stack traces can't
+ /// be retrieved.
+ Trace.parseIE(String trace) : this.parseV8(trace);
+
+ /// Parses a string representation of a Firefox stack trace.
+ Trace.parseFirefox(String trace)
+ : this(
+ trace
+ .trim()
+ .split('\n')
+ .where((line) => line.isNotEmpty && line != '[native code]')
+ .map(Frame.parseFirefox),
+ original: trace);
+
+ /// Parses a string representation of a Safari stack trace.
+ Trace.parseSafari(String trace) : this.parseFirefox(trace);
+
+ /// Parses a string representation of a Safari 6.1+ stack trace.
+ @Deprecated('Use Trace.parseSafari instead.')
+ Trace.parseSafari6_1(String trace) : this.parseSafari(trace);
+
+ /// Parses a string representation of a Safari 6.0 stack trace.
+ @Deprecated('Use Trace.parseSafari instead.')
+ Trace.parseSafari6_0(String trace)
+ : this(
+ trace
+ .trim()
+ .split('\n')
+ .where((line) => line != '[native code]')
+ .map(Frame.parseFirefox),
+ original: trace);
+
+ /// Parses this package's string representation of a stack trace.
+ ///
+ /// This also parses string representations of [Chain]s. They parse to the
+ /// same trace that [Chain.toTrace] would return.
+ Trace.parseFriendly(String trace)
+ : this(
+ trace.isEmpty
+ ? []
+ : trace
+ .trim()
+ .split('\n')
+ // Filter out asynchronous gaps from [Chain]s.
+ .where((line) => !line.startsWith('====='))
+ .map(Frame.parseFriendly),
+ original: trace);
+
+ /// Returns a new [Trace] comprised of [frames].
+ Trace(Iterable<Frame> frames, {String? original})
+ : frames = List<Frame>.unmodifiable(frames),
+ original = StackTrace.fromString(original ?? '');
+
+ /// Returns a VM-style [StackTrace] object.
+ ///
+ /// The return value's [toString] method will always return a string
+ /// representation in the Dart VM's stack trace format, regardless of what
+ /// platform is being used.
+ StackTrace get vmTrace => VMTrace(frames);
+
+ /// Returns a terser version of this trace.
+ ///
+ /// This is accomplished by folding together multiple stack frames from the
+ /// core library or from this package, as in [foldFrames]. Remaining core
+ /// library frames have their libraries, "-patch" suffixes, and line numbers
+ /// removed. If the outermost frame of the stack trace is a core library
+ /// frame, it's removed entirely.
+ ///
+ /// This won't do anything with a raw JavaScript trace, since there's no way
+ /// to determine which frames come from which Dart libraries. However, the
+ /// [`source_map_stack_trace`][https://pub.dev/packages/source_map_stack_trace]
+ /// package can be used to convert JavaScript traces into Dart-style traces.
+ ///
+ /// For custom folding, see [foldFrames].
+ Trace get terse => foldFrames((_) => false, terse: true);
+
+ /// Returns a new [Trace] based on `this` where multiple stack frames matching
+ /// [predicate] are folded together.
+ ///
+ /// This means that whenever there are multiple frames in a row that match
+ /// [predicate], only the last one is kept. This is useful for limiting the
+ /// amount of library code that appears in a stack trace by only showing user
+ /// code and code that's called by user code.
+ ///
+ /// If [terse] is true, this will also fold together frames from the core
+ /// library or from this package, simplify core library frames, and
+ /// potentially remove the outermost frame as in [Trace.terse].
+ Trace foldFrames(bool Function(Frame) predicate, {bool terse = false}) {
+ if (terse) {
+ var oldPredicate = predicate;
+ predicate = (frame) {
+ if (oldPredicate(frame)) return true;
+
+ if (frame.isCore) return true;
+ if (frame.package == 'stack_trace') return true;
+
+ // Ignore async stack frames without any line or column information.
+ // These come from the VM's async/await implementation and represent
+ // internal frames. They only ever show up in stack chains and are
+ // always surrounded by other traces that are actually useful, so we can
+ // just get rid of them.
+ // TODO(nweiz): Get rid of this logic some time after issue 22009 is
+ // fixed.
+ if (!frame.member!.contains('<async>')) return false;
+ return frame.line == null;
+ };
+ }
+
+ var newFrames = <Frame>[];
+ for (var frame in frames.reversed) {
+ if (frame is UnparsedFrame || !predicate(frame)) {
+ newFrames.add(frame);
+ } else if (newFrames.isEmpty || !predicate(newFrames.last)) {
+ newFrames.add(Frame(frame.uri, frame.line, frame.column, frame.member));
+ }
+ }
+
+ if (terse) {
+ newFrames = newFrames.map((frame) {
+ if (frame is UnparsedFrame || !predicate(frame)) return frame;
+ var library = frame.library.replaceAll(_terseRegExp, '');
+ return Frame(Uri.parse(library), null, null, frame.member);
+ }).toList();
+
+ if (newFrames.length > 1 && predicate(newFrames.first)) {
+ newFrames.removeAt(0);
+ }
+ }
+
+ return Trace(newFrames.reversed, original: original.toString());
+ }
+
+ @override
+ String toString() {
+ // Figure out the longest path so we know how much to pad.
+ var longest =
+ frames.map((frame) => frame.location.length).fold(0, math.max);
+
+ // Print out the stack trace nicely formatted.
+ return frames.map((frame) {
+ if (frame is UnparsedFrame) return '$frame\n';
+ return '${frame.location.padRight(longest)} ${frame.member}\n';
+ }).join();
+ }
+}
diff --git a/pkgs/stack_trace/lib/src/unparsed_frame.dart b/pkgs/stack_trace/lib/src/unparsed_frame.dart
new file mode 100644
index 0000000..27e97f6
--- /dev/null
+++ b/pkgs/stack_trace/lib/src/unparsed_frame.dart
@@ -0,0 +1,33 @@
+// Copyright (c) 2015, 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 'frame.dart';
+
+/// A frame that failed to parse.
+///
+/// The [member] property contains the original frame's contents.
+class UnparsedFrame implements Frame {
+ @override
+ final Uri uri = Uri(path: 'unparsed');
+ @override
+ final int? line = null;
+ @override
+ final int? column = null;
+ @override
+ final bool isCore = false;
+ @override
+ final String library = 'unparsed';
+ @override
+ final String? package = null;
+ @override
+ final String location = 'unparsed';
+
+ @override
+ final String member;
+
+ UnparsedFrame(this.member);
+
+ @override
+ String toString() => member;
+}
diff --git a/pkgs/stack_trace/lib/src/utils.dart b/pkgs/stack_trace/lib/src/utils.dart
new file mode 100644
index 0000000..bd971fe
--- /dev/null
+++ b/pkgs/stack_trace/lib/src/utils.dart
@@ -0,0 +1,15 @@
+// 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.
+
+/// The line used in the string representation of stack chains to represent
+/// the gap between traces.
+const chainGap = '===== asynchronous gap ===========================\n';
+
+/// The line used in the string representation of VM stack chains to represent
+/// the gap between traces.
+final vmChainGap = RegExp(r'^<asynchronous suspension>\n?$', multiLine: true);
+
+// TODO(nweiz): When cross-platform imports work, use them to set this.
+/// Whether we're running in a JS context.
+const bool inJS = 0.0 is int;
diff --git a/pkgs/stack_trace/lib/src/vm_trace.dart b/pkgs/stack_trace/lib/src/vm_trace.dart
new file mode 100644
index 0000000..005b7af
--- /dev/null
+++ b/pkgs/stack_trace/lib/src/vm_trace.dart
@@ -0,0 +1,32 @@
+// 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 'frame.dart';
+
+/// An implementation of [StackTrace] that emulates the behavior of the VM's
+/// implementation.
+///
+/// In particular, when [toString] is called, this returns a string in the VM's
+/// stack trace format.
+class VMTrace implements StackTrace {
+ /// The stack frames that comprise this stack trace.
+ final List<Frame> frames;
+
+ VMTrace(this.frames);
+
+ @override
+ String toString() {
+ var i = 1;
+ return frames.map((frame) {
+ var number = '#${i++}'.padRight(8);
+ var member = frame.member!
+ .replaceAllMapped(RegExp(r'[^.]+\.<async>'),
+ (match) => '${match[1]}.<${match[1]}_async_body>')
+ .replaceAll('<fn>', '<anonymous closure>');
+ var line = frame.line ?? 0;
+ var column = frame.column ?? 0;
+ return '$number$member (${frame.uri}:$line:$column)\n';
+ }).join();
+ }
+}
diff --git a/pkgs/stack_trace/lib/stack_trace.dart b/pkgs/stack_trace/lib/stack_trace.dart
new file mode 100644
index 0000000..fad30ce
--- /dev/null
+++ b/pkgs/stack_trace/lib/stack_trace.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/chain.dart';
+export 'src/frame.dart';
+export 'src/trace.dart';
+export 'src/unparsed_frame.dart';
diff --git a/pkgs/stack_trace/pubspec.yaml b/pkgs/stack_trace/pubspec.yaml
new file mode 100644
index 0000000..4f387b1
--- /dev/null
+++ b/pkgs/stack_trace/pubspec.yaml
@@ -0,0 +1,14 @@
+name: stack_trace
+version: 1.12.1
+description: A package for manipulating stack traces and printing them readably.
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/stack_trace
+
+environment:
+ sdk: ^3.4.0
+
+dependencies:
+ path: ^1.8.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ test: ^1.16.6
diff --git a/pkgs/stack_trace/test/chain/chain_test.dart b/pkgs/stack_trace/test/chain/chain_test.dart
new file mode 100644
index 0000000..d5426dd
--- /dev/null
+++ b/pkgs/stack_trace/test/chain/chain_test.dart
@@ -0,0 +1,375 @@
+// Copyright (c) 2015, 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 'package:path/path.dart' as p;
+import 'package:stack_trace/stack_trace.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ group('Chain.parse()', () {
+ test('parses a real Chain', () async {
+ // ignore: only_throw_errors
+ final chain = await captureFuture(() => inMicrotask(() => throw 'error'));
+
+ expect(
+ Chain.parse(chain.toString()).toString(),
+ equals(chain.toString()),
+ );
+ });
+
+ test('parses an empty string', () {
+ var chain = Chain.parse('');
+ expect(chain.traces, isEmpty);
+ });
+
+ test('parses a chain containing empty traces', () {
+ var chain =
+ Chain.parse('===== asynchronous gap ===========================\n'
+ '===== asynchronous gap ===========================\n');
+ expect(chain.traces, hasLength(3));
+ expect(chain.traces[0].frames, isEmpty);
+ expect(chain.traces[1].frames, isEmpty);
+ expect(chain.traces[2].frames, isEmpty);
+ });
+
+ test('parses a chain with VM gaps', () {
+ final chain =
+ Chain.parse('#1 MyClass.run (package:my_lib.dart:134:5)\n'
+ '<asynchronous suspension>\n'
+ '#2 main (file:///my_app.dart:9:3)\n'
+ '<asynchronous suspension>\n');
+ expect(chain.traces, hasLength(2));
+ expect(chain.traces[0].frames, hasLength(1));
+ expect(chain.traces[0].frames[0].toString(),
+ equals('package:my_lib.dart 134:5 in MyClass.run'));
+ expect(chain.traces[1].frames, hasLength(1));
+ expect(
+ chain.traces[1].frames[0].toString(),
+ anyOf(
+ equals('/my_app.dart 9:3 in main'), // VM
+ equals('file:///my_app.dart 9:3 in main'), // Browser
+ ),
+ );
+ });
+ });
+
+ group('Chain.capture()', () {
+ test('with onError blocks errors', () {
+ Chain.capture(() {
+ return Future<void>.error('oh no');
+ }, onError: expectAsync2((error, chain) {
+ expect(error, equals('oh no'));
+ expect(chain, isA<Chain>());
+ })).then(expectAsync1((_) {}, count: 0),
+ onError: expectAsync2((_, __) {}, count: 0));
+ });
+
+ test('with no onError blocks errors', () {
+ runZonedGuarded(() {
+ Chain.capture(() => Future<void>.error('oh no')).then(
+ expectAsync1((_) {}, count: 0),
+ onError: expectAsync2((_, __) {}, count: 0));
+ }, expectAsync2((error, chain) {
+ expect(error, equals('oh no'));
+ expect(chain, isA<Chain>());
+ }));
+ });
+
+ test("with errorZone: false doesn't block errors", () {
+ expect(Chain.capture(() => Future<void>.error('oh no'), errorZone: false),
+ throwsA('oh no'));
+ });
+
+ test("doesn't allow onError and errorZone: false", () {
+ expect(() => Chain.capture(() {}, onError: (_, __) {}, errorZone: false),
+ throwsArgumentError);
+ });
+
+ group('with when: false', () {
+ test("with no onError doesn't block errors", () {
+ expect(Chain.capture(() => Future<void>.error('oh no'), when: false),
+ throwsA('oh no'));
+ });
+
+ test('with onError blocks errors', () {
+ Chain.capture(() {
+ return Future<void>.error('oh no');
+ }, onError: expectAsync2((error, chain) {
+ expect(error, equals('oh no'));
+ expect(chain, isA<Chain>());
+ }), when: false);
+ });
+
+ test("doesn't enable chain-tracking", () {
+ return Chain.disable(() {
+ return Chain.capture(() {
+ var completer = Completer<Chain>();
+ inMicrotask(() {
+ completer.complete(Chain.current());
+ });
+
+ return completer.future.then((chain) {
+ expect(chain.traces, hasLength(1));
+ });
+ }, when: false);
+ });
+ });
+ });
+ });
+
+ test('Chain.capture() with custom zoneValues', () {
+ return Chain.capture(() {
+ expect(Zone.current[#enabled], true);
+ }, zoneValues: {#enabled: true});
+ });
+
+ group('Chain.disable()', () {
+ test('disables chain-tracking', () {
+ return Chain.disable(() {
+ var completer = Completer<Chain>();
+ inMicrotask(() => completer.complete(Chain.current()));
+
+ return completer.future.then((chain) {
+ expect(chain.traces, hasLength(1));
+ });
+ });
+ });
+
+ test('Chain.capture() re-enables chain-tracking', () {
+ return Chain.disable(() {
+ return Chain.capture(() {
+ var completer = Completer<Chain>();
+ inMicrotask(() => completer.complete(Chain.current()));
+
+ return completer.future.then((chain) {
+ expect(chain.traces, hasLength(2));
+ });
+ });
+ });
+ });
+
+ test('preserves parent zones of the capture zone', () {
+ // The outer disable call turns off the test package's chain-tracking.
+ return Chain.disable(() {
+ return runZoned(() {
+ return Chain.capture(() {
+ expect(Chain.disable(() => Zone.current[#enabled]), isTrue);
+ });
+ }, zoneValues: {#enabled: true});
+ });
+ });
+
+ test('preserves child zones of the capture zone', () {
+ // The outer disable call turns off the test package's chain-tracking.
+ return Chain.disable(() {
+ return Chain.capture(() {
+ return runZoned(() {
+ expect(Chain.disable(() => Zone.current[#enabled]), isTrue);
+ }, zoneValues: {#enabled: true});
+ });
+ });
+ });
+
+ test("with when: false doesn't disable", () {
+ return Chain.capture(() {
+ return Chain.disable(() {
+ var completer = Completer<Chain>();
+ inMicrotask(() => completer.complete(Chain.current()));
+
+ return completer.future.then((chain) {
+ expect(chain.traces, hasLength(2));
+ });
+ }, when: false);
+ });
+ });
+ });
+
+ test('toString() ensures that all traces are aligned', () {
+ var chain = Chain([
+ Trace.parse('short 10:11 Foo.bar\n'),
+ Trace.parse('loooooooooooong 10:11 Zop.zoop')
+ ]);
+
+ expect(
+ chain.toString(),
+ equals('short 10:11 Foo.bar\n'
+ '===== asynchronous gap ===========================\n'
+ 'loooooooooooong 10:11 Zop.zoop\n'));
+ });
+
+ var userSlashCode = p.join('user', 'code.dart');
+ group('Chain.terse', () {
+ test('makes each trace terse', () {
+ var chain = Chain([
+ Trace.parse('dart:core 10:11 Foo.bar\n'
+ 'dart:core 10:11 Bar.baz\n'
+ 'user/code.dart 10:11 Bang.qux\n'
+ 'dart:core 10:11 Zip.zap\n'
+ 'dart:core 10:11 Zop.zoop'),
+ Trace.parse('user/code.dart 10:11 Bang.qux\n'
+ 'dart:core 10:11 Foo.bar\n'
+ 'package:stack_trace/stack_trace.dart 10:11 Bar.baz\n'
+ 'dart:core 10:11 Zip.zap\n'
+ 'user/code.dart 10:11 Zop.zoop')
+ ]);
+
+ expect(
+ chain.terse.toString(),
+ equals('dart:core Bar.baz\n'
+ '$userSlashCode 10:11 Bang.qux\n'
+ '===== asynchronous gap ===========================\n'
+ '$userSlashCode 10:11 Bang.qux\n'
+ 'dart:core Zip.zap\n'
+ '$userSlashCode 10:11 Zop.zoop\n'));
+ });
+
+ test('eliminates internal-only traces', () {
+ var chain = Chain([
+ Trace.parse('user/code.dart 10:11 Foo.bar\n'
+ 'dart:core 10:11 Bar.baz'),
+ Trace.parse('dart:core 10:11 Foo.bar\n'
+ 'package:stack_trace/stack_trace.dart 10:11 Bar.baz\n'
+ 'dart:core 10:11 Zip.zap'),
+ Trace.parse('user/code.dart 10:11 Foo.bar\n'
+ 'dart:core 10:11 Bar.baz')
+ ]);
+
+ expect(
+ chain.terse.toString(),
+ equals('$userSlashCode 10:11 Foo.bar\n'
+ '===== asynchronous gap ===========================\n'
+ '$userSlashCode 10:11 Foo.bar\n'));
+ });
+
+ test("doesn't return an empty chain", () {
+ var chain = Chain([
+ Trace.parse('dart:core 10:11 Foo.bar\n'
+ 'package:stack_trace/stack_trace.dart 10:11 Bar.baz\n'
+ 'dart:core 10:11 Zip.zap'),
+ Trace.parse('dart:core 10:11 A.b\n'
+ 'package:stack_trace/stack_trace.dart 10:11 C.d\n'
+ 'dart:core 10:11 E.f')
+ ]);
+
+ expect(chain.terse.toString(), equals('dart:core E.f\n'));
+ });
+
+ // Regression test for #9
+ test("doesn't crash on empty traces", () {
+ var chain = Chain([
+ Trace.parse('user/code.dart 10:11 Bang.qux'),
+ Trace([]),
+ Trace.parse('user/code.dart 10:11 Bang.qux')
+ ]);
+
+ expect(
+ chain.terse.toString(),
+ equals('$userSlashCode 10:11 Bang.qux\n'
+ '===== asynchronous gap ===========================\n'
+ '$userSlashCode 10:11 Bang.qux\n'));
+ });
+ });
+
+ group('Chain.foldFrames', () {
+ test('folds each trace', () {
+ var chain = Chain([
+ Trace.parse('a.dart 10:11 Foo.bar\n'
+ 'a.dart 10:11 Bar.baz\n'
+ 'b.dart 10:11 Bang.qux\n'
+ 'a.dart 10:11 Zip.zap\n'
+ 'a.dart 10:11 Zop.zoop'),
+ Trace.parse('a.dart 10:11 Foo.bar\n'
+ 'a.dart 10:11 Bar.baz\n'
+ 'a.dart 10:11 Bang.qux\n'
+ 'a.dart 10:11 Zip.zap\n'
+ 'b.dart 10:11 Zop.zoop')
+ ]);
+
+ var folded = chain.foldFrames((frame) => frame.library == 'a.dart');
+ expect(
+ folded.toString(),
+ equals('a.dart 10:11 Bar.baz\n'
+ 'b.dart 10:11 Bang.qux\n'
+ 'a.dart 10:11 Zop.zoop\n'
+ '===== asynchronous gap ===========================\n'
+ 'a.dart 10:11 Zip.zap\n'
+ 'b.dart 10:11 Zop.zoop\n'));
+ });
+
+ test('with terse: true, folds core frames as well', () {
+ var chain = Chain([
+ Trace.parse('a.dart 10:11 Foo.bar\n'
+ 'dart:async-patch/future.dart 10:11 Zip.zap\n'
+ 'b.dart 10:11 Bang.qux\n'
+ 'dart:core 10:11 Bar.baz\n'
+ 'a.dart 10:11 Zop.zoop'),
+ Trace.parse('a.dart 10:11 Foo.bar\n'
+ 'a.dart 10:11 Bar.baz\n'
+ 'a.dart 10:11 Bang.qux\n'
+ 'a.dart 10:11 Zip.zap\n'
+ 'b.dart 10:11 Zop.zoop')
+ ]);
+
+ var folded =
+ chain.foldFrames((frame) => frame.library == 'a.dart', terse: true);
+ expect(
+ folded.toString(),
+ equals('dart:async Zip.zap\n'
+ 'b.dart 10:11 Bang.qux\n'
+ '===== asynchronous gap ===========================\n'
+ 'a.dart Zip.zap\n'
+ 'b.dart 10:11 Zop.zoop\n'));
+ });
+
+ test('eliminates completely-folded traces', () {
+ var chain = Chain([
+ Trace.parse('a.dart 10:11 Foo.bar\n'
+ 'b.dart 10:11 Bang.qux'),
+ Trace.parse('a.dart 10:11 Foo.bar\n'
+ 'a.dart 10:11 Bang.qux'),
+ Trace.parse('a.dart 10:11 Zip.zap\n'
+ 'b.dart 10:11 Zop.zoop')
+ ]);
+
+ var folded = chain.foldFrames((frame) => frame.library == 'a.dart');
+ expect(
+ folded.toString(),
+ equals('a.dart 10:11 Foo.bar\n'
+ 'b.dart 10:11 Bang.qux\n'
+ '===== asynchronous gap ===========================\n'
+ 'a.dart 10:11 Zip.zap\n'
+ 'b.dart 10:11 Zop.zoop\n'));
+ });
+
+ test("doesn't return an empty trace", () {
+ var chain = Chain([
+ Trace.parse('a.dart 10:11 Foo.bar\n'
+ 'a.dart 10:11 Bang.qux')
+ ]);
+
+ var folded = chain.foldFrames((frame) => frame.library == 'a.dart');
+ expect(folded.toString(), equals('a.dart 10:11 Bang.qux\n'));
+ });
+ });
+
+ test('Chain.toTrace eliminates asynchronous gaps', () {
+ var trace = Chain([
+ Trace.parse('user/code.dart 10:11 Foo.bar\n'
+ 'dart:core 10:11 Bar.baz'),
+ Trace.parse('user/code.dart 10:11 Foo.bar\n'
+ 'dart:core 10:11 Bar.baz')
+ ]).toTrace();
+
+ expect(
+ trace.toString(),
+ equals('$userSlashCode 10:11 Foo.bar\n'
+ 'dart:core 10:11 Bar.baz\n'
+ '$userSlashCode 10:11 Foo.bar\n'
+ 'dart:core 10:11 Bar.baz\n'));
+ });
+}
diff --git a/pkgs/stack_trace/test/chain/dart2js_test.dart b/pkgs/stack_trace/test/chain/dart2js_test.dart
new file mode 100644
index 0000000..abb842d
--- /dev/null
+++ b/pkgs/stack_trace/test/chain/dart2js_test.dart
@@ -0,0 +1,337 @@
+// Copyright (c) 2015, 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: only_throw_errors
+
+// dart2js chain tests are separated out because dart2js stack traces are
+// inconsistent due to inlining and browser differences. These tests don't
+// assert anything about the content of the traces, just the number of traces in
+// a chain.
+@TestOn('js')
+library;
+
+import 'dart:async';
+
+import 'package:stack_trace/stack_trace.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ group('capture() with onError catches exceptions', () {
+ test('thrown synchronously', () async {
+ var chain = await captureFuture(() => throw 'error');
+ expect(chain.traces, hasLength(1));
+ });
+
+ test('thrown in a microtask', () async {
+ var chain = await captureFuture(() => inMicrotask(() => throw 'error'));
+ expect(chain.traces, hasLength(2));
+ });
+
+ test('thrown in a one-shot timer', () async {
+ var chain =
+ await captureFuture(() => inOneShotTimer(() => throw 'error'));
+ expect(chain.traces, hasLength(2));
+ });
+
+ test('thrown in a periodic timer', () async {
+ var chain =
+ await captureFuture(() => inPeriodicTimer(() => throw 'error'));
+ expect(chain.traces, hasLength(2));
+ });
+
+ test('thrown in a nested series of asynchronous operations', () async {
+ var chain = await captureFuture(() {
+ inPeriodicTimer(() {
+ inOneShotTimer(() => inMicrotask(() => throw 'error'));
+ });
+ });
+
+ expect(chain.traces, hasLength(4));
+ });
+
+ test('thrown in a long future chain', () async {
+ var chain = await captureFuture(() => inFutureChain(() => throw 'error'));
+
+ // Despite many asynchronous operations, there's only one level of
+ // nested calls, so there should be only two traces in the chain. This
+ // is important; programmers expect stack trace memory consumption to be
+ // O(depth of program), not O(length of program).
+ expect(chain.traces, hasLength(2));
+ });
+
+ test('thrown in new Future()', () async {
+ var chain = await captureFuture(() => inNewFuture(() => throw 'error'));
+ expect(chain.traces, hasLength(3));
+ });
+
+ test('thrown in new Future.sync()', () async {
+ var chain = await captureFuture(() {
+ inMicrotask(() => inSyncFuture(() => throw 'error'));
+ });
+
+ expect(chain.traces, hasLength(3));
+ });
+
+ test('multiple times', () {
+ var completer = Completer<void>();
+ var first = true;
+
+ Chain.capture(() {
+ inMicrotask(() => throw 'first error');
+ inPeriodicTimer(() => throw 'second error');
+ }, onError: (error, chain) {
+ try {
+ if (first) {
+ expect(error, equals('first error'));
+ expect(chain.traces, hasLength(2));
+ first = false;
+ } else {
+ expect(error, equals('second error'));
+ expect(chain.traces, hasLength(2));
+ completer.complete();
+ }
+ } on Object catch (error, stackTrace) {
+ completer.completeError(error, stackTrace);
+ }
+ });
+
+ return completer.future;
+ });
+
+ test('passed to a completer', () async {
+ var trace = Trace.current();
+ var chain = await captureFuture(() {
+ inMicrotask(() => completerErrorFuture(trace));
+ });
+
+ expect(chain.traces, hasLength(3));
+
+ // The first trace is the trace that was manually reported for the
+ // error.
+ expect(chain.traces.first.toString(), equals(trace.toString()));
+ });
+
+ test('passed to a completer with no stack trace', () async {
+ var chain = await captureFuture(() {
+ inMicrotask(completerErrorFuture);
+ });
+
+ expect(chain.traces, hasLength(2));
+ });
+
+ test('passed to a stream controller', () async {
+ var trace = Trace.current();
+ var chain = await captureFuture(() {
+ inMicrotask(() => controllerErrorStream(trace).listen(null));
+ });
+
+ expect(chain.traces, hasLength(3));
+ expect(chain.traces.first.toString(), equals(trace.toString()));
+ });
+
+ test('passed to a stream controller with no stack trace', () async {
+ var chain = await captureFuture(() {
+ inMicrotask(() => controllerErrorStream().listen(null));
+ });
+
+ expect(chain.traces, hasLength(2));
+ });
+
+ test('and relays them to the parent zone', () {
+ var completer = Completer<void>();
+
+ runZonedGuarded(() {
+ Chain.capture(() {
+ inMicrotask(() => throw 'error');
+ }, onError: (error, chain) {
+ expect(error, equals('error'));
+ expect(chain.traces, hasLength(2));
+ throw error;
+ });
+ }, (error, chain) {
+ try {
+ expect(error, equals('error'));
+ expect(chain,
+ isA<Chain>().having((c) => c.traces, 'traces', hasLength(2)));
+ completer.complete();
+ } on Object catch (error, stackTrace) {
+ completer.completeError(error, stackTrace);
+ }
+ });
+
+ return completer.future;
+ });
+ });
+
+ test('capture() without onError passes exceptions to parent zone', () {
+ var completer = Completer<void>();
+
+ runZonedGuarded(() {
+ Chain.capture(() => inMicrotask(() => throw 'error'));
+ }, (error, chain) {
+ try {
+ expect(error, equals('error'));
+ expect(chain,
+ isA<Chain>().having((c) => c.traces, 'traces', hasLength(2)));
+ completer.complete();
+ } on Object catch (error, stackTrace) {
+ completer.completeError(error, stackTrace);
+ }
+ });
+
+ return completer.future;
+ });
+
+ group('current() within capture()', () {
+ test('called in a microtask', () async {
+ var completer = Completer<Chain>();
+ Chain.capture(() {
+ inMicrotask(() => completer.complete(Chain.current()));
+ });
+
+ var chain = await completer.future;
+ expect(chain.traces, hasLength(2));
+ });
+
+ test('called in a one-shot timer', () async {
+ var completer = Completer<Chain>();
+ Chain.capture(() {
+ inOneShotTimer(() => completer.complete(Chain.current()));
+ });
+
+ var chain = await completer.future;
+ expect(chain.traces, hasLength(2));
+ });
+
+ test('called in a periodic timer', () async {
+ var completer = Completer<Chain>();
+ Chain.capture(() {
+ inPeriodicTimer(() => completer.complete(Chain.current()));
+ });
+
+ var chain = await completer.future;
+ expect(chain.traces, hasLength(2));
+ });
+
+ test('called in a nested series of asynchronous operations', () async {
+ var completer = Completer<Chain>();
+ Chain.capture(() {
+ inPeriodicTimer(() {
+ inOneShotTimer(() {
+ inMicrotask(() => completer.complete(Chain.current()));
+ });
+ });
+ });
+
+ var chain = await completer.future;
+ expect(chain.traces, hasLength(4));
+ });
+
+ test('called in a long future chain', () async {
+ var completer = Completer<Chain>();
+ Chain.capture(() {
+ inFutureChain(() => completer.complete(Chain.current()));
+ });
+
+ var chain = await completer.future;
+ expect(chain.traces, hasLength(2));
+ });
+ });
+
+ test(
+ 'current() outside of capture() returns a chain wrapping the current trace',
+ () =>
+ // The test runner runs all tests with chains enabled.
+ Chain.disable(() async {
+ var completer = Completer<Chain>();
+ inMicrotask(() => completer.complete(Chain.current()));
+
+ var chain = await completer.future;
+ // Since the chain wasn't loaded within [Chain.capture], the full stack
+ // chain isn't available and it just returns the current stack when
+ // called.
+ expect(chain.traces, hasLength(1));
+ }),
+ );
+
+ group('forTrace() within capture()', () {
+ test('called for a stack trace from a microtask', () async {
+ var chain = await Chain.capture(
+ () => chainForTrace(inMicrotask, () => throw 'error'));
+
+ // Because [chainForTrace] has to set up a future chain to capture the
+ // stack trace while still showing it to the zone specification, it adds
+ // an additional level of async nesting and so an additional trace.
+ expect(chain.traces, hasLength(3));
+ });
+
+ test('called for a stack trace from a one-shot timer', () async {
+ var chain = await Chain.capture(
+ () => chainForTrace(inOneShotTimer, () => throw 'error'));
+
+ expect(chain.traces, hasLength(3));
+ });
+
+ test('called for a stack trace from a periodic timer', () async {
+ var chain = await Chain.capture(
+ () => chainForTrace(inPeriodicTimer, () => throw 'error'));
+
+ expect(chain.traces, hasLength(3));
+ });
+
+ test(
+ 'called for a stack trace from a nested series of asynchronous '
+ 'operations', () async {
+ var chain = await Chain.capture(() => chainForTrace((callback) {
+ inPeriodicTimer(() => inOneShotTimer(() => inMicrotask(callback)));
+ }, () => throw 'error'));
+
+ expect(chain.traces, hasLength(5));
+ });
+
+ test('called for a stack trace from a long future chain', () async {
+ var chain = await Chain.capture(
+ () => chainForTrace(inFutureChain, () => throw 'error'));
+
+ expect(chain.traces, hasLength(3));
+ });
+
+ test(
+ 'called for an unregistered stack trace returns a chain wrapping that '
+ 'trace', () {
+ late StackTrace trace;
+ var chain = Chain.capture(() {
+ try {
+ throw 'error';
+ } catch (_, stackTrace) {
+ trace = stackTrace;
+ return Chain.forTrace(stackTrace);
+ }
+ });
+
+ expect(chain.traces, hasLength(1));
+ expect(
+ chain.traces.first.toString(), equals(Trace.from(trace).toString()));
+ });
+ });
+
+ test(
+ 'forTrace() outside of capture() returns a chain wrapping the given '
+ 'trace', () {
+ late StackTrace trace;
+ var chain = Chain.capture(() {
+ try {
+ throw 'error';
+ } catch (_, stackTrace) {
+ trace = stackTrace;
+ return Chain.forTrace(stackTrace);
+ }
+ });
+
+ expect(chain.traces, hasLength(1));
+ expect(chain.traces.first.toString(), equals(Trace.from(trace).toString()));
+ });
+}
diff --git a/pkgs/stack_trace/test/chain/utils.dart b/pkgs/stack_trace/test/chain/utils.dart
new file mode 100644
index 0000000..27fb0e6
--- /dev/null
+++ b/pkgs/stack_trace/test/chain/utils.dart
@@ -0,0 +1,94 @@
+// Copyright (c) 2015, 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 'package:stack_trace/stack_trace.dart';
+import 'package:test/test.dart';
+
+/// Runs [callback] in a microtask callback.
+void inMicrotask(void Function() callback) => scheduleMicrotask(callback);
+
+/// Runs [callback] in a one-shot timer callback.
+void inOneShotTimer(void Function() callback) => Timer.run(callback);
+
+/// Runs [callback] once in a periodic timer callback.
+void inPeriodicTimer(void Function() callback) {
+ var count = 0;
+ Timer.periodic(const Duration(milliseconds: 1), (timer) {
+ count++;
+ if (count != 5) return;
+ timer.cancel();
+ callback();
+ });
+}
+
+/// Runs [callback] within a long asynchronous Future chain.
+void inFutureChain(void Function() callback) {
+ Future(() {})
+ .then((_) => Future(() {}))
+ .then((_) => Future(() {}))
+ .then((_) => Future(() {}))
+ .then((_) => Future(() {}))
+ .then((_) => callback())
+ .then((_) => Future(() {}));
+}
+
+void inNewFuture(void Function() callback) {
+ Future(callback);
+}
+
+void inSyncFuture(void Function() callback) {
+ Future.sync(callback);
+}
+
+/// Returns a Future that completes to an error using a completer.
+///
+/// If [trace] is passed, it's used as the stack trace for the error.
+Future<void> completerErrorFuture([StackTrace? trace]) {
+ var completer = Completer<void>();
+ completer.completeError('error', trace);
+ return completer.future;
+}
+
+/// Returns a Stream that emits an error using a controller.
+///
+/// If [trace] is passed, it's used as the stack trace for the error.
+Stream<void> controllerErrorStream([StackTrace? trace]) {
+ var controller = StreamController<void>();
+ controller.addError('error', trace);
+ return controller.stream;
+}
+
+/// Runs [callback] within [asyncFn], then converts any errors raised into a
+/// [Chain] with [Chain.forTrace].
+Future<Chain> chainForTrace(
+ void Function(void Function()) asyncFn, void Function() callback) {
+ var completer = Completer<Chain>();
+ asyncFn(() {
+ // We use `new Future.value().then(...)` here as opposed to [new Future] or
+ // [new Future.sync] because those methods don't pass the exception through
+ // the zone specification before propagating it, so there's no chance to
+ // attach a chain to its stack trace. See issue 15105.
+ Future<void>.value()
+ .then((_) => callback())
+ .catchError(completer.completeError);
+ });
+
+ return completer.future
+ .catchError((_, StackTrace stackTrace) => Chain.forTrace(stackTrace));
+}
+
+/// Runs [callback] in a [Chain.capture] zone and returns a Future that
+/// completes to the stack chain for an error thrown by [callback].
+///
+/// [callback] is expected to throw the string `"error"`.
+Future<Chain> captureFuture(void Function() callback) {
+ var completer = Completer<Chain>();
+ Chain.capture(callback, onError: (error, chain) {
+ expect(error, equals('error'));
+ completer.complete(chain);
+ });
+ return completer.future;
+}
diff --git a/pkgs/stack_trace/test/chain/vm_test.dart b/pkgs/stack_trace/test/chain/vm_test.dart
new file mode 100644
index 0000000..5c6c0b7
--- /dev/null
+++ b/pkgs/stack_trace/test/chain/vm_test.dart
@@ -0,0 +1,508 @@
+// Copyright (c) 2015, 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: only_throw_errors
+
+// VM chain tests can rely on stronger guarantees about the contents of the
+// stack traces than dart2js.
+@TestOn('dart-vm')
+library;
+
+import 'dart:async';
+
+import 'package:stack_trace/src/utils.dart';
+import 'package:stack_trace/stack_trace.dart';
+import 'package:test/test.dart';
+
+import '../utils.dart';
+import 'utils.dart';
+
+void main() {
+ group('capture() with onError catches exceptions', () {
+ test('thrown synchronously', () async {
+ late StackTrace vmTrace;
+ var chain = await captureFuture(() {
+ try {
+ throw 'error';
+ } catch (_, stackTrace) {
+ vmTrace = stackTrace;
+ rethrow;
+ }
+ });
+
+ // Because there's no chain context for a synchronous error, we fall back
+ // on the VM's stack chain tracking.
+ expect(
+ chain.toString(), equals(Chain.parse(vmTrace.toString()).toString()));
+ });
+
+ test('thrown in a microtask', () {
+ return captureFuture(() => inMicrotask(() => throw 'error'))
+ .then((chain) {
+ // Since there was only one asynchronous operation, there should be only
+ // two traces in the chain.
+ expect(chain.traces, hasLength(2));
+
+ // The first frame of the first trace should be the line on which the
+ // actual error was thrown.
+ expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
+
+ // The second trace should describe the stack when the error callback
+ // was scheduled.
+ expect(chain.traces[1].frames,
+ contains(frameMember(startsWith('inMicrotask'))));
+ });
+ });
+
+ test('thrown in a one-shot timer', () {
+ return captureFuture(() => inOneShotTimer(() => throw 'error'))
+ .then((chain) {
+ expect(chain.traces, hasLength(2));
+ expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
+ expect(chain.traces[1].frames,
+ contains(frameMember(startsWith('inOneShotTimer'))));
+ });
+ });
+
+ test('thrown in a periodic timer', () {
+ return captureFuture(() => inPeriodicTimer(() => throw 'error'))
+ .then((chain) {
+ expect(chain.traces, hasLength(2));
+ expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
+ expect(chain.traces[1].frames,
+ contains(frameMember(startsWith('inPeriodicTimer'))));
+ });
+ });
+
+ test('thrown in a nested series of asynchronous operations', () {
+ return captureFuture(() {
+ inPeriodicTimer(() {
+ inOneShotTimer(() => inMicrotask(() => throw 'error'));
+ });
+ }).then((chain) {
+ expect(chain.traces, hasLength(4));
+ expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
+ expect(chain.traces[1].frames,
+ contains(frameMember(startsWith('inMicrotask'))));
+ expect(chain.traces[2].frames,
+ contains(frameMember(startsWith('inOneShotTimer'))));
+ expect(chain.traces[3].frames,
+ contains(frameMember(startsWith('inPeriodicTimer'))));
+ });
+ });
+
+ test('thrown in a long future chain', () {
+ return captureFuture(() => inFutureChain(() => throw 'error'))
+ .then((chain) {
+ // Despite many asynchronous operations, there's only one level of
+ // nested calls, so there should be only two traces in the chain. This
+ // is important; programmers expect stack trace memory consumption to be
+ // O(depth of program), not O(length of program).
+ expect(chain.traces, hasLength(2));
+
+ expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
+ expect(chain.traces[1].frames,
+ contains(frameMember(startsWith('inFutureChain'))));
+ });
+ });
+
+ test('thrown in new Future()', () {
+ return captureFuture(() => inNewFuture(() => throw 'error'))
+ .then((chain) {
+ expect(chain.traces, hasLength(3));
+ expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
+
+ // The second trace is the one captured by
+ // [StackZoneSpecification.errorCallback]. Because that runs
+ // asynchronously within [new Future], it doesn't actually refer to the
+ // source file at all.
+ expect(chain.traces[1].frames,
+ everyElement(frameLibrary(isNot(contains('chain_test')))));
+
+ expect(chain.traces[2].frames,
+ contains(frameMember(startsWith('inNewFuture'))));
+ });
+ });
+
+ test('thrown in new Future.sync()', () {
+ return captureFuture(() {
+ inMicrotask(() => inSyncFuture(() => throw 'error'));
+ }).then((chain) {
+ expect(chain.traces, hasLength(3));
+ expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
+ expect(chain.traces[1].frames,
+ contains(frameMember(startsWith('inSyncFuture'))));
+ expect(chain.traces[2].frames,
+ contains(frameMember(startsWith('inMicrotask'))));
+ });
+ });
+
+ test('multiple times', () {
+ var completer = Completer<void>();
+ var first = true;
+
+ Chain.capture(() {
+ inMicrotask(() => throw 'first error');
+ inPeriodicTimer(() => throw 'second error');
+ }, onError: (error, chain) {
+ try {
+ if (first) {
+ expect(error, equals('first error'));
+ expect(chain.traces[1].frames,
+ contains(frameMember(startsWith('inMicrotask'))));
+ first = false;
+ } else {
+ expect(error, equals('second error'));
+ expect(chain.traces[1].frames,
+ contains(frameMember(startsWith('inPeriodicTimer'))));
+ completer.complete();
+ }
+ } on Object catch (error, stackTrace) {
+ completer.completeError(error, stackTrace);
+ }
+ });
+
+ return completer.future;
+ });
+
+ test('passed to a completer', () {
+ var trace = Trace.current();
+ return captureFuture(() {
+ inMicrotask(() => completerErrorFuture(trace));
+ }).then((chain) {
+ expect(chain.traces, hasLength(3));
+
+ // The first trace is the trace that was manually reported for the
+ // error.
+ expect(chain.traces.first.toString(), equals(trace.toString()));
+
+ // The second trace is the trace that was captured when
+ // [Completer.addError] was called.
+ expect(chain.traces[1].frames,
+ contains(frameMember(startsWith('completerErrorFuture'))));
+
+ // The third trace is the automatically-captured trace from when the
+ // microtask was scheduled.
+ expect(chain.traces[2].frames,
+ contains(frameMember(startsWith('inMicrotask'))));
+ });
+ });
+
+ test('passed to a completer with no stack trace', () {
+ return captureFuture(() {
+ inMicrotask(completerErrorFuture);
+ }).then((chain) {
+ expect(chain.traces, hasLength(2));
+
+ // The first trace is the one captured when [Completer.addError] was
+ // called.
+ expect(chain.traces[0].frames,
+ contains(frameMember(startsWith('completerErrorFuture'))));
+
+ // The second trace is the automatically-captured trace from when the
+ // microtask was scheduled.
+ expect(chain.traces[1].frames,
+ contains(frameMember(startsWith('inMicrotask'))));
+ });
+ });
+
+ test('passed to a stream controller', () {
+ var trace = Trace.current();
+ return captureFuture(() {
+ inMicrotask(() => controllerErrorStream(trace).listen(null));
+ }).then((chain) {
+ expect(chain.traces, hasLength(3));
+ expect(chain.traces.first.toString(), equals(trace.toString()));
+ expect(chain.traces[1].frames,
+ contains(frameMember(startsWith('controllerErrorStream'))));
+ expect(chain.traces[2].frames,
+ contains(frameMember(startsWith('inMicrotask'))));
+ });
+ });
+
+ test('passed to a stream controller with no stack trace', () {
+ return captureFuture(() {
+ inMicrotask(() => controllerErrorStream().listen(null));
+ }).then((chain) {
+ expect(chain.traces, hasLength(2));
+ expect(chain.traces[0].frames,
+ contains(frameMember(startsWith('controllerErrorStream'))));
+ expect(chain.traces[1].frames,
+ contains(frameMember(startsWith('inMicrotask'))));
+ });
+ });
+
+ test('and relays them to the parent zone', () {
+ var completer = Completer<void>();
+
+ runZonedGuarded(() {
+ Chain.capture(() {
+ inMicrotask(() => throw 'error');
+ }, onError: (error, chain) {
+ expect(error, equals('error'));
+ expect(chain.traces[1].frames,
+ contains(frameMember(startsWith('inMicrotask'))));
+ throw error;
+ });
+ }, (error, chain) {
+ try {
+ expect(error, equals('error'));
+ expect(
+ chain,
+ isA<Chain>().having((c) => c.traces[1].frames, 'traces[1].frames',
+ contains(frameMember(startsWith('inMicrotask')))));
+ completer.complete();
+ } on Object catch (error, stackTrace) {
+ completer.completeError(error, stackTrace);
+ }
+ });
+
+ return completer.future;
+ });
+ });
+
+ test('capture() without onError passes exceptions to parent zone', () {
+ var completer = Completer<void>();
+
+ runZonedGuarded(() {
+ Chain.capture(() => inMicrotask(() => throw 'error'));
+ }, (error, chain) {
+ try {
+ expect(error, equals('error'));
+ expect(
+ chain,
+ isA<Chain>().having((c) => c.traces[1].frames, 'traces[1].frames',
+ contains(frameMember(startsWith('inMicrotask')))));
+ completer.complete();
+ } on Object catch (error, stackTrace) {
+ completer.completeError(error, stackTrace);
+ }
+ });
+
+ return completer.future;
+ });
+
+ group('current() within capture()', () {
+ test('called in a microtask', () {
+ var completer = Completer<Chain>();
+ Chain.capture(() {
+ inMicrotask(() => completer.complete(Chain.current()));
+ });
+
+ return completer.future.then((chain) {
+ expect(chain.traces, hasLength(2));
+ expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
+ expect(chain.traces[1].frames,
+ contains(frameMember(startsWith('inMicrotask'))));
+ });
+ });
+
+ test('called in a one-shot timer', () {
+ var completer = Completer<Chain>();
+ Chain.capture(() {
+ inOneShotTimer(() => completer.complete(Chain.current()));
+ });
+
+ return completer.future.then((chain) {
+ expect(chain.traces, hasLength(2));
+ expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
+ expect(chain.traces[1].frames,
+ contains(frameMember(startsWith('inOneShotTimer'))));
+ });
+ });
+
+ test('called in a periodic timer', () {
+ var completer = Completer<Chain>();
+ Chain.capture(() {
+ inPeriodicTimer(() => completer.complete(Chain.current()));
+ });
+
+ return completer.future.then((chain) {
+ expect(chain.traces, hasLength(2));
+ expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
+ expect(chain.traces[1].frames,
+ contains(frameMember(startsWith('inPeriodicTimer'))));
+ });
+ });
+
+ test('called in a nested series of asynchronous operations', () {
+ var completer = Completer<Chain>();
+ Chain.capture(() {
+ inPeriodicTimer(() {
+ inOneShotTimer(() {
+ inMicrotask(() => completer.complete(Chain.current()));
+ });
+ });
+ });
+
+ return completer.future.then((chain) {
+ expect(chain.traces, hasLength(4));
+ expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
+ expect(chain.traces[1].frames,
+ contains(frameMember(startsWith('inMicrotask'))));
+ expect(chain.traces[2].frames,
+ contains(frameMember(startsWith('inOneShotTimer'))));
+ expect(chain.traces[3].frames,
+ contains(frameMember(startsWith('inPeriodicTimer'))));
+ });
+ });
+
+ test('called in a long future chain', () {
+ var completer = Completer<Chain>();
+ Chain.capture(() {
+ inFutureChain(() => completer.complete(Chain.current()));
+ });
+
+ return completer.future.then((chain) {
+ expect(chain.traces, hasLength(2));
+ expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
+ expect(chain.traces[1].frames,
+ contains(frameMember(startsWith('inFutureChain'))));
+ });
+ });
+ });
+
+ test(
+ 'current() outside of capture() returns a chain wrapping the current '
+ 'trace', () {
+ // The test runner runs all tests with chains enabled.
+ return Chain.disable(() {
+ var completer = Completer<Chain>();
+ inMicrotask(() => completer.complete(Chain.current()));
+
+ return completer.future.then((chain) {
+ // Since the chain wasn't loaded within [Chain.capture], the full stack
+ // chain isn't available and it just returns the current stack when
+ // called.
+ expect(chain.traces, hasLength(1));
+ expect(
+ chain.traces.first.frames.first, frameMember(startsWith('main')));
+ });
+ });
+ });
+
+ group('forTrace() within capture()', () {
+ test('called for a stack trace from a microtask', () {
+ return Chain.capture(() {
+ return chainForTrace(inMicrotask, () => throw 'error');
+ }).then((chain) {
+ // Because [chainForTrace] has to set up a future chain to capture the
+ // stack trace while still showing it to the zone specification, it adds
+ // an additional level of async nesting and so an additional trace.
+ expect(chain.traces, hasLength(3));
+ expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
+ expect(chain.traces[1].frames,
+ contains(frameMember(startsWith('chainForTrace'))));
+ expect(chain.traces[2].frames,
+ contains(frameMember(startsWith('inMicrotask'))));
+ });
+ });
+
+ test('called for a stack trace from a one-shot timer', () {
+ return Chain.capture(() {
+ return chainForTrace(inOneShotTimer, () => throw 'error');
+ }).then((chain) {
+ expect(chain.traces, hasLength(3));
+ expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
+ expect(chain.traces[1].frames,
+ contains(frameMember(startsWith('chainForTrace'))));
+ expect(chain.traces[2].frames,
+ contains(frameMember(startsWith('inOneShotTimer'))));
+ });
+ });
+
+ test('called for a stack trace from a periodic timer', () {
+ return Chain.capture(() {
+ return chainForTrace(inPeriodicTimer, () => throw 'error');
+ }).then((chain) {
+ expect(chain.traces, hasLength(3));
+ expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
+ expect(chain.traces[1].frames,
+ contains(frameMember(startsWith('chainForTrace'))));
+ expect(chain.traces[2].frames,
+ contains(frameMember(startsWith('inPeriodicTimer'))));
+ });
+ });
+
+ test(
+ 'called for a stack trace from a nested series of asynchronous '
+ 'operations', () {
+ return Chain.capture(() {
+ return chainForTrace((callback) {
+ inPeriodicTimer(() => inOneShotTimer(() => inMicrotask(callback)));
+ }, () => throw 'error');
+ }).then((chain) {
+ expect(chain.traces, hasLength(5));
+ expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
+ expect(chain.traces[1].frames,
+ contains(frameMember(startsWith('chainForTrace'))));
+ expect(chain.traces[2].frames,
+ contains(frameMember(startsWith('inMicrotask'))));
+ expect(chain.traces[3].frames,
+ contains(frameMember(startsWith('inOneShotTimer'))));
+ expect(chain.traces[4].frames,
+ contains(frameMember(startsWith('inPeriodicTimer'))));
+ });
+ });
+
+ test('called for a stack trace from a long future chain', () {
+ return Chain.capture(() {
+ return chainForTrace(inFutureChain, () => throw 'error');
+ }).then((chain) {
+ expect(chain.traces, hasLength(3));
+ expect(chain.traces[0].frames.first, frameMember(startsWith('main')));
+ expect(chain.traces[1].frames,
+ contains(frameMember(startsWith('chainForTrace'))));
+ expect(chain.traces[2].frames,
+ contains(frameMember(startsWith('inFutureChain'))));
+ });
+ });
+
+ test('called for an unregistered stack trace uses the current chain',
+ () async {
+ late StackTrace trace;
+ var chain = await Chain.capture(() async {
+ try {
+ throw 'error';
+ } catch (_, stackTrace) {
+ trace = stackTrace;
+ return Chain.forTrace(stackTrace);
+ }
+ });
+
+ expect(chain.traces, hasLength(greaterThan(1)));
+
+ // Assert that we've trimmed the VM's stack chains here to avoid
+ // duplication.
+ expect(chain.traces.first.toString(),
+ equals(Chain.parse(trace.toString()).traces.first.toString()));
+ });
+ });
+
+ test(
+ 'forTrace() outside of capture() returns a chain describing the VM stack '
+ 'chain', () {
+ // Disable the test package's chain-tracking.
+ return Chain.disable(() async {
+ late StackTrace trace;
+ await Chain.capture(() async {
+ try {
+ throw 'error';
+ } catch (_, stackTrace) {
+ trace = stackTrace;
+ }
+ });
+
+ final chain = Chain.forTrace(trace);
+ final traceStr = trace.toString();
+ final gaps = vmChainGap.allMatches(traceStr);
+ // If the trace ends on a gap, there's no sub-trace following the gap.
+ final expectedLength =
+ (gaps.last.end == traceStr.length) ? gaps.length : gaps.length + 1;
+ expect(chain.traces, hasLength(expectedLength));
+ expect(
+ chain.traces.first.frames, contains(frameMember(startsWith('main'))));
+ });
+ });
+}
diff --git a/pkgs/stack_trace/test/frame_test.dart b/pkgs/stack_trace/test/frame_test.dart
new file mode 100644
index 0000000..a5dfc20
--- /dev/null
+++ b/pkgs/stack_trace/test/frame_test.dart
@@ -0,0 +1,729 @@
+// 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:path/path.dart' as path;
+import 'package:stack_trace/stack_trace.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('.parseVM', () {
+ test('parses a stack frame with column correctly', () {
+ var frame = Frame.parseVM('#1 Foo._bar '
+ '(file:///home/nweiz/code/stuff.dart:42:21)');
+ expect(
+ frame.uri, equals(Uri.parse('file:///home/nweiz/code/stuff.dart')));
+ expect(frame.line, equals(42));
+ expect(frame.column, equals(21));
+ expect(frame.member, equals('Foo._bar'));
+ });
+
+ test('parses a stack frame without column correctly', () {
+ var frame = Frame.parseVM('#1 Foo._bar '
+ '(file:///home/nweiz/code/stuff.dart:24)');
+ expect(
+ frame.uri, equals(Uri.parse('file:///home/nweiz/code/stuff.dart')));
+ expect(frame.line, equals(24));
+ expect(frame.column, null);
+ expect(frame.member, equals('Foo._bar'));
+ });
+
+ // This can happen with async stack traces. See issue 22009.
+ test('parses a stack frame without line or column correctly', () {
+ var frame = Frame.parseVM('#1 Foo._bar '
+ '(file:///home/nweiz/code/stuff.dart)');
+ expect(
+ frame.uri, equals(Uri.parse('file:///home/nweiz/code/stuff.dart')));
+ expect(frame.line, isNull);
+ expect(frame.column, isNull);
+ expect(frame.member, equals('Foo._bar'));
+ });
+
+ test('converts "<anonymous closure>" to "<fn>"', () {
+ String? parsedMember(String member) =>
+ Frame.parseVM('#0 $member (foo:0:0)').member;
+
+ expect(parsedMember('Foo.<anonymous closure>'), equals('Foo.<fn>'));
+ expect(parsedMember('<anonymous closure>.<anonymous closure>.bar'),
+ equals('<fn>.<fn>.bar'));
+ });
+
+ test('converts "<<anonymous closure>_async_body>" to "<async>"', () {
+ var frame =
+ Frame.parseVM('#0 Foo.<<anonymous closure>_async_body> (foo:0:0)');
+ expect(frame.member, equals('Foo.<async>'));
+ });
+
+ test('converts "<function_name_async_body>" to "<async>"', () {
+ var frame = Frame.parseVM('#0 Foo.<function_name_async_body> (foo:0:0)');
+ expect(frame.member, equals('Foo.<async>'));
+ });
+
+ test('parses a folded frame correctly', () {
+ var frame = Frame.parseVM('...');
+
+ expect(frame.member, equals('...'));
+ expect(frame.uri, equals(Uri()));
+ expect(frame.line, isNull);
+ expect(frame.column, isNull);
+ });
+ });
+
+ group('.parseV8', () {
+ test('returns an UnparsedFrame for malformed frames', () {
+ expectIsUnparsed(Frame.parseV8, '');
+ expectIsUnparsed(Frame.parseV8, '#1');
+ expectIsUnparsed(Frame.parseV8, '#1 Foo');
+ expectIsUnparsed(Frame.parseV8, '#1 (dart:async/future.dart:10:15)');
+ expectIsUnparsed(Frame.parseV8, 'Foo (dart:async/future.dart:10:15)');
+ });
+
+ test('parses a stack frame correctly', () {
+ var frame = Frame.parseV8(' at VW.call\$0 '
+ '(https://example.com/stuff.dart.js:560:28)');
+ expect(frame.uri, equals(Uri.parse('https://example.com/stuff.dart.js')));
+ expect(frame.line, equals(560));
+ expect(frame.column, equals(28));
+ expect(frame.member, equals('VW.call\$0'));
+ });
+
+ test('parses a stack frame with a : in the authority', () {
+ var frame = Frame.parseV8(' at VW.call\$0 '
+ '(http://localhost:8080/stuff.dart.js:560:28)');
+ expect(
+ frame.uri, equals(Uri.parse('http://localhost:8080/stuff.dart.js')));
+ expect(frame.line, equals(560));
+ expect(frame.column, equals(28));
+ expect(frame.member, equals('VW.call\$0'));
+ });
+
+ test('parses a stack frame with an absolute POSIX path correctly', () {
+ var frame = Frame.parseV8(' at VW.call\$0 '
+ '(/path/to/stuff.dart.js:560:28)');
+ expect(frame.uri, equals(Uri.parse('file:///path/to/stuff.dart.js')));
+ expect(frame.line, equals(560));
+ expect(frame.column, equals(28));
+ expect(frame.member, equals('VW.call\$0'));
+ });
+
+ test('parses a stack frame with an absolute Windows path correctly', () {
+ var frame = Frame.parseV8(' at VW.call\$0 '
+ r'(C:\path\to\stuff.dart.js:560:28)');
+ expect(frame.uri, equals(Uri.parse('file:///C:/path/to/stuff.dart.js')));
+ expect(frame.line, equals(560));
+ expect(frame.column, equals(28));
+ expect(frame.member, equals('VW.call\$0'));
+ });
+
+ test('parses a stack frame with a Windows UNC path correctly', () {
+ var frame = Frame.parseV8(' at VW.call\$0 '
+ r'(\\mount\path\to\stuff.dart.js:560:28)');
+ expect(
+ frame.uri, equals(Uri.parse('file://mount/path/to/stuff.dart.js')));
+ expect(frame.line, equals(560));
+ expect(frame.column, equals(28));
+ expect(frame.member, equals('VW.call\$0'));
+ });
+
+ test('parses a stack frame with a relative POSIX path correctly', () {
+ var frame = Frame.parseV8(' at VW.call\$0 '
+ '(path/to/stuff.dart.js:560:28)');
+ expect(frame.uri, equals(Uri.parse('path/to/stuff.dart.js')));
+ expect(frame.line, equals(560));
+ expect(frame.column, equals(28));
+ expect(frame.member, equals('VW.call\$0'));
+ });
+
+ test('parses a stack frame with a relative Windows path correctly', () {
+ var frame = Frame.parseV8(' at VW.call\$0 '
+ r'(path\to\stuff.dart.js:560:28)');
+ expect(frame.uri, equals(Uri.parse('path/to/stuff.dart.js')));
+ expect(frame.line, equals(560));
+ expect(frame.column, equals(28));
+ expect(frame.member, equals('VW.call\$0'));
+ });
+
+ test('parses an anonymous stack frame correctly', () {
+ var frame =
+ Frame.parseV8(' at https://example.com/stuff.dart.js:560:28');
+ expect(frame.uri, equals(Uri.parse('https://example.com/stuff.dart.js')));
+ expect(frame.line, equals(560));
+ expect(frame.column, equals(28));
+ expect(frame.member, equals('<fn>'));
+ });
+
+ test('parses a native stack frame correctly', () {
+ var frame = Frame.parseV8(' at Object.stringify (native)');
+ expect(frame.uri, Uri.parse('native'));
+ expect(frame.line, isNull);
+ expect(frame.column, isNull);
+ expect(frame.member, equals('Object.stringify'));
+ });
+
+ test('parses a stack frame with [as ...] correctly', () {
+ // Ignore "[as ...]", since other stack trace formats don't support a
+ // similar construct.
+ var frame = Frame.parseV8(' at VW.call\$0 [as call\$4] '
+ '(https://example.com/stuff.dart.js:560:28)');
+ expect(frame.uri, equals(Uri.parse('https://example.com/stuff.dart.js')));
+ expect(frame.line, equals(560));
+ expect(frame.column, equals(28));
+ expect(frame.member, equals('VW.call\$0'));
+ });
+
+ test('parses a basic eval stack frame correctly', () {
+ var frame = Frame.parseV8(' at eval (eval at <anonymous> '
+ '(https://example.com/stuff.dart.js:560:28))');
+ expect(frame.uri, equals(Uri.parse('https://example.com/stuff.dart.js')));
+ expect(frame.line, equals(560));
+ expect(frame.column, equals(28));
+ expect(frame.member, equals('eval'));
+ });
+
+ test('parses an IE10 eval stack frame correctly', () {
+ var frame = Frame.parseV8(' at eval (eval at Anonymous function '
+ '(https://example.com/stuff.dart.js:560:28))');
+ expect(frame.uri, equals(Uri.parse('https://example.com/stuff.dart.js')));
+ expect(frame.line, equals(560));
+ expect(frame.column, equals(28));
+ expect(frame.member, equals('eval'));
+ });
+
+ test('parses an eval stack frame with inner position info correctly', () {
+ var frame = Frame.parseV8(' at eval (eval at <anonymous> '
+ '(https://example.com/stuff.dart.js:560:28), <anonymous>:3:28)');
+ expect(frame.uri, equals(Uri.parse('https://example.com/stuff.dart.js')));
+ expect(frame.line, equals(560));
+ expect(frame.column, equals(28));
+ expect(frame.member, equals('eval'));
+ });
+
+ test('parses a nested eval stack frame correctly', () {
+ var frame = Frame.parseV8(' at eval (eval at <anonymous> '
+ '(eval at sub (https://example.com/stuff.dart.js:560:28)))');
+ expect(frame.uri, equals(Uri.parse('https://example.com/stuff.dart.js')));
+ expect(frame.line, equals(560));
+ expect(frame.column, equals(28));
+ expect(frame.member, equals('eval'));
+ });
+
+ test('converts "<anonymous>" to "<fn>"', () {
+ String? parsedMember(String member) =>
+ Frame.parseV8(' at $member (foo:0:0)').member;
+
+ expect(parsedMember('Foo.<anonymous>'), equals('Foo.<fn>'));
+ expect(
+ parsedMember('<anonymous>.<anonymous>.bar'), equals('<fn>.<fn>.bar'));
+ });
+
+ test('returns an UnparsedFrame for malformed frames', () {
+ expectIsUnparsed(Frame.parseV8, '');
+ expectIsUnparsed(Frame.parseV8, ' at');
+ expectIsUnparsed(Frame.parseV8, ' at Foo');
+ expectIsUnparsed(Frame.parseV8, ' at Foo (dart:async/future.dart)');
+ expectIsUnparsed(Frame.parseV8, ' at (dart:async/future.dart:10:15)');
+ expectIsUnparsed(Frame.parseV8, 'Foo (dart:async/future.dart:10:15)');
+ expectIsUnparsed(Frame.parseV8, ' at dart:async/future.dart');
+ expectIsUnparsed(Frame.parseV8, 'dart:async/future.dart:10:15');
+ });
+ });
+
+ group('.parseFirefox/.parseSafari', () {
+ test('parses a Firefox stack trace with anonymous function', () {
+ var trace = Trace.parse('''
+Foo._bar@https://example.com/stuff.js:18056:12
+anonymous/<@https://example.com/stuff.js line 693 > Function:3:40
+baz@https://pub.dev/buz.js:56355:55
+ ''');
+ expect(trace.frames[0].uri,
+ equals(Uri.parse('https://example.com/stuff.js')));
+ expect(trace.frames[0].line, equals(18056));
+ expect(trace.frames[0].column, equals(12));
+ expect(trace.frames[0].member, equals('Foo._bar'));
+ expect(trace.frames[1].uri,
+ equals(Uri.parse('https://example.com/stuff.js')));
+ expect(trace.frames[1].line, equals(693));
+ expect(trace.frames[1].column, isNull);
+ expect(trace.frames[1].member, equals('<fn>'));
+ expect(trace.frames[2].uri, equals(Uri.parse('https://pub.dev/buz.js')));
+ expect(trace.frames[2].line, equals(56355));
+ expect(trace.frames[2].column, equals(55));
+ expect(trace.frames[2].member, equals('baz'));
+ });
+
+ test('parses a Firefox stack trace with nested evals in anonymous function',
+ () {
+ var trace = Trace.parse('''
+ Foo._bar@https://example.com/stuff.js:18056:12
+ anonymous@file:///C:/example.html line 7 > eval line 1 > eval:1:1
+ anonymous@file:///C:/example.html line 45 > Function:1:1
+ ''');
+ expect(trace.frames[0].uri,
+ equals(Uri.parse('https://example.com/stuff.js')));
+ expect(trace.frames[0].line, equals(18056));
+ expect(trace.frames[0].column, equals(12));
+ expect(trace.frames[0].member, equals('Foo._bar'));
+ expect(trace.frames[1].uri, equals(Uri.parse('file:///C:/example.html')));
+ expect(trace.frames[1].line, equals(7));
+ expect(trace.frames[1].column, isNull);
+ expect(trace.frames[1].member, equals('<fn>'));
+ expect(trace.frames[2].uri, equals(Uri.parse('file:///C:/example.html')));
+ expect(trace.frames[2].line, equals(45));
+ expect(trace.frames[2].column, isNull);
+ expect(trace.frames[2].member, equals('<fn>'));
+ });
+
+ test('parses a simple stack frame correctly', () {
+ var frame = Frame.parseFirefox(
+ '.VW.call\$0@https://example.com/stuff.dart.js:560');
+ expect(frame.uri, equals(Uri.parse('https://example.com/stuff.dart.js')));
+ expect(frame.line, equals(560));
+ expect(frame.column, isNull);
+ expect(frame.member, equals('VW.call\$0'));
+ });
+
+ test('parses a stack frame with an absolute POSIX path correctly', () {
+ var frame = Frame.parseFirefox('.VW.call\$0@/path/to/stuff.dart.js:560');
+ expect(frame.uri, equals(Uri.parse('file:///path/to/stuff.dart.js')));
+ expect(frame.line, equals(560));
+ expect(frame.column, isNull);
+ expect(frame.member, equals('VW.call\$0'));
+ });
+
+ test('parses a stack frame with an absolute Windows path correctly', () {
+ var frame =
+ Frame.parseFirefox(r'.VW.call$0@C:\path\to\stuff.dart.js:560');
+ expect(frame.uri, equals(Uri.parse('file:///C:/path/to/stuff.dart.js')));
+ expect(frame.line, equals(560));
+ expect(frame.column, isNull);
+ expect(frame.member, equals('VW.call\$0'));
+ });
+
+ test('parses a stack frame with a Windows UNC path correctly', () {
+ var frame =
+ Frame.parseFirefox(r'.VW.call$0@\\mount\path\to\stuff.dart.js:560');
+ expect(
+ frame.uri, equals(Uri.parse('file://mount/path/to/stuff.dart.js')));
+ expect(frame.line, equals(560));
+ expect(frame.column, isNull);
+ expect(frame.member, equals('VW.call\$0'));
+ });
+
+ test('parses a stack frame with a relative POSIX path correctly', () {
+ var frame = Frame.parseFirefox('.VW.call\$0@path/to/stuff.dart.js:560');
+ expect(frame.uri, equals(Uri.parse('path/to/stuff.dart.js')));
+ expect(frame.line, equals(560));
+ expect(frame.column, isNull);
+ expect(frame.member, equals('VW.call\$0'));
+ });
+
+ test('parses a stack frame with a relative Windows path correctly', () {
+ var frame = Frame.parseFirefox(r'.VW.call$0@path\to\stuff.dart.js:560');
+ expect(frame.uri, equals(Uri.parse('path/to/stuff.dart.js')));
+ expect(frame.line, equals(560));
+ expect(frame.column, isNull);
+ expect(frame.member, equals('VW.call\$0'));
+ });
+
+ test('parses a simple anonymous stack frame correctly', () {
+ var frame = Frame.parseFirefox('@https://example.com/stuff.dart.js:560');
+ expect(frame.uri, equals(Uri.parse('https://example.com/stuff.dart.js')));
+ expect(frame.line, equals(560));
+ expect(frame.column, isNull);
+ expect(frame.member, equals('<fn>'));
+ });
+
+ test('parses a nested anonymous stack frame correctly', () {
+ var frame =
+ Frame.parseFirefox('.foo/<@https://example.com/stuff.dart.js:560');
+ expect(frame.uri, equals(Uri.parse('https://example.com/stuff.dart.js')));
+ expect(frame.line, equals(560));
+ expect(frame.column, isNull);
+ expect(frame.member, equals('foo.<fn>'));
+
+ frame = Frame.parseFirefox('.foo/@https://example.com/stuff.dart.js:560');
+ expect(frame.uri, equals(Uri.parse('https://example.com/stuff.dart.js')));
+ expect(frame.line, equals(560));
+ expect(frame.column, isNull);
+ expect(frame.member, equals('foo.<fn>'));
+ });
+
+ test('parses a named nested anonymous stack frame correctly', () {
+ var frame = Frame.parseFirefox(
+ '.foo/.name<@https://example.com/stuff.dart.js:560');
+ expect(frame.uri, equals(Uri.parse('https://example.com/stuff.dart.js')));
+ expect(frame.line, equals(560));
+ expect(frame.column, isNull);
+ expect(frame.member, equals('foo.<fn>'));
+
+ frame = Frame.parseFirefox(
+ '.foo/.name@https://example.com/stuff.dart.js:560');
+ expect(frame.uri, equals(Uri.parse('https://example.com/stuff.dart.js')));
+ expect(frame.line, equals(560));
+ expect(frame.column, isNull);
+ expect(frame.member, equals('foo.<fn>'));
+ });
+
+ test('parses a stack frame with parameters correctly', () {
+ var frame = Frame.parseFirefox(
+ '.foo(12, "@)()/<")@https://example.com/stuff.dart.js:560');
+ expect(frame.uri, equals(Uri.parse('https://example.com/stuff.dart.js')));
+ expect(frame.line, equals(560));
+ expect(frame.column, isNull);
+ expect(frame.member, equals('foo'));
+ });
+
+ test('parses a nested anonymous stack frame with parameters correctly', () {
+ var frame = Frame.parseFirefox(
+ '.foo(12, "@)()/<")/.fn<@https://example.com/stuff.dart.js:560',
+ );
+ expect(frame.uri, equals(Uri.parse('https://example.com/stuff.dart.js')));
+ expect(frame.line, equals(560));
+ expect(frame.column, isNull);
+ expect(frame.member, equals('foo.<fn>'));
+ });
+
+ test(
+ 'parses a deeply-nested anonymous stack frame with parameters '
+ 'correctly', () {
+ var frame = Frame.parseFirefox('.convertDartClosureToJS/\$function</<@'
+ 'https://example.com/stuff.dart.js:560');
+ expect(frame.uri, equals(Uri.parse('https://example.com/stuff.dart.js')));
+ expect(frame.line, equals(560));
+ expect(frame.column, isNull);
+ expect(frame.member, equals('convertDartClosureToJS.<fn>.<fn>'));
+ });
+
+ test('returns an UnparsedFrame for malformed frames', () {
+ expectIsUnparsed(Frame.parseFirefox, '');
+ expectIsUnparsed(Frame.parseFirefox, '.foo');
+ expectIsUnparsed(Frame.parseFirefox, '.foo@dart:async/future.dart');
+ expectIsUnparsed(Frame.parseFirefox, '.foo(@dart:async/future.dart:10');
+ expectIsUnparsed(Frame.parseFirefox, '@dart:async/future.dart');
+ });
+
+ test('parses a simple stack frame correctly', () {
+ var frame =
+ Frame.parseFirefox('foo\$bar@https://dart.dev/foo/bar.dart:10:11');
+ expect(frame.uri, equals(Uri.parse('https://dart.dev/foo/bar.dart')));
+ expect(frame.line, equals(10));
+ expect(frame.column, equals(11));
+ expect(frame.member, equals('foo\$bar'));
+ });
+
+ test('parses an anonymous stack frame correctly', () {
+ var frame = Frame.parseFirefox('https://dart.dev/foo/bar.dart:10:11');
+ expect(frame.uri, equals(Uri.parse('https://dart.dev/foo/bar.dart')));
+ expect(frame.line, equals(10));
+ expect(frame.column, equals(11));
+ expect(frame.member, equals('<fn>'));
+ });
+
+ test('parses a stack frame with no line correctly', () {
+ var frame =
+ Frame.parseFirefox('foo\$bar@https://dart.dev/foo/bar.dart::11');
+ expect(frame.uri, equals(Uri.parse('https://dart.dev/foo/bar.dart')));
+ expect(frame.line, isNull);
+ expect(frame.column, equals(11));
+ expect(frame.member, equals('foo\$bar'));
+ });
+
+ test('parses a stack frame with no column correctly', () {
+ var frame =
+ Frame.parseFirefox('foo\$bar@https://dart.dev/foo/bar.dart:10:');
+ expect(frame.uri, equals(Uri.parse('https://dart.dev/foo/bar.dart')));
+ expect(frame.line, equals(10));
+ expect(frame.column, isNull);
+ expect(frame.member, equals('foo\$bar'));
+ });
+
+ test('parses a stack frame with no line or column correctly', () {
+ var frame =
+ Frame.parseFirefox('foo\$bar@https://dart.dev/foo/bar.dart:10:11');
+ expect(frame.uri, equals(Uri.parse('https://dart.dev/foo/bar.dart')));
+ expect(frame.line, equals(10));
+ expect(frame.column, equals(11));
+ expect(frame.member, equals('foo\$bar'));
+ });
+ });
+
+ group('.parseFriendly', () {
+ test('parses a simple stack frame correctly', () {
+ var frame = Frame.parseFriendly(
+ 'https://dart.dev/foo/bar.dart 10:11 Foo.<fn>.bar');
+ expect(frame.uri, equals(Uri.parse('https://dart.dev/foo/bar.dart')));
+ expect(frame.line, equals(10));
+ expect(frame.column, equals(11));
+ expect(frame.member, equals('Foo.<fn>.bar'));
+ });
+
+ test('parses a stack frame with no line or column correctly', () {
+ var frame =
+ Frame.parseFriendly('https://dart.dev/foo/bar.dart Foo.<fn>.bar');
+ expect(frame.uri, equals(Uri.parse('https://dart.dev/foo/bar.dart')));
+ expect(frame.line, isNull);
+ expect(frame.column, isNull);
+ expect(frame.member, equals('Foo.<fn>.bar'));
+ });
+
+ test('parses a stack frame with no column correctly', () {
+ var frame =
+ Frame.parseFriendly('https://dart.dev/foo/bar.dart 10 Foo.<fn>.bar');
+ expect(frame.uri, equals(Uri.parse('https://dart.dev/foo/bar.dart')));
+ expect(frame.line, equals(10));
+ expect(frame.column, isNull);
+ expect(frame.member, equals('Foo.<fn>.bar'));
+ });
+
+ test('parses a stack frame with a relative path correctly', () {
+ var frame = Frame.parseFriendly('foo/bar.dart 10:11 Foo.<fn>.bar');
+ expect(frame.uri,
+ equals(path.toUri(path.absolute(path.join('foo', 'bar.dart')))));
+ expect(frame.line, equals(10));
+ expect(frame.column, equals(11));
+ expect(frame.member, equals('Foo.<fn>.bar'));
+ });
+
+ test('returns an UnparsedFrame for malformed frames', () {
+ expectIsUnparsed(Frame.parseFriendly, '');
+ expectIsUnparsed(Frame.parseFriendly, 'foo/bar.dart');
+ expectIsUnparsed(Frame.parseFriendly, 'foo/bar.dart 10:11');
+ });
+
+ test('parses a data url stack frame with no line or column correctly', () {
+ var frame = Frame.parseFriendly('data:... main');
+ expect(frame.uri.scheme, equals('data'));
+ expect(frame.line, isNull);
+ expect(frame.column, isNull);
+ expect(frame.member, equals('main'));
+ });
+
+ test('parses a data url stack frame correctly', () {
+ var frame = Frame.parseFriendly('data:... 10:11 main');
+ expect(frame.uri.scheme, equals('data'));
+ expect(frame.line, equals(10));
+ expect(frame.column, equals(11));
+ expect(frame.member, equals('main'));
+ });
+
+ test('parses a stack frame with spaces in the member name correctly', () {
+ var frame = Frame.parseFriendly(
+ 'foo/bar.dart 10:11 (anonymous function).dart.fn');
+ expect(frame.uri,
+ equals(path.toUri(path.absolute(path.join('foo', 'bar.dart')))));
+ expect(frame.line, equals(10));
+ expect(frame.column, equals(11));
+ expect(frame.member, equals('(anonymous function).dart.fn'));
+ });
+
+ test(
+ 'parses a stack frame with spaces in the member name and no line or '
+ 'column correctly', () {
+ var frame = Frame.parseFriendly(
+ 'https://dart.dev/foo/bar.dart (anonymous function).dart.fn');
+ expect(frame.uri, equals(Uri.parse('https://dart.dev/foo/bar.dart')));
+ expect(frame.line, isNull);
+ expect(frame.column, isNull);
+ expect(frame.member, equals('(anonymous function).dart.fn'));
+ });
+ });
+
+ test('only considers dart URIs to be core', () {
+ bool isCore(String library) =>
+ Frame.parseVM('#0 Foo ($library:0:0)').isCore;
+
+ expect(isCore('dart:core'), isTrue);
+ expect(isCore('dart:async'), isTrue);
+ expect(isCore('dart:core/uri.dart'), isTrue);
+ expect(isCore('dart:async/future.dart'), isTrue);
+ expect(isCore('bart:core'), isFalse);
+ expect(isCore('sdart:core'), isFalse);
+ expect(isCore('darty:core'), isFalse);
+ expect(isCore('bart:core/uri.dart'), isFalse);
+ });
+
+ group('.library', () {
+ test('returns the URI string for non-file URIs', () {
+ expect(Frame.parseVM('#0 Foo (dart:async/future.dart:0:0)').library,
+ equals('dart:async/future.dart'));
+ expect(
+ Frame.parseVM('#0 Foo '
+ '(https://dart.dev/stuff/thing.dart:0:0)')
+ .library,
+ equals('https://dart.dev/stuff/thing.dart'));
+ });
+
+ test('returns the relative path for file URIs', () {
+ expect(Frame.parseVM('#0 Foo (foo/bar.dart:0:0)').library,
+ equals(path.join('foo', 'bar.dart')));
+ });
+
+ test('truncates legacy data: URIs', () {
+ var frame = Frame.parseVM(
+ '#0 Foo (data:application/dart;charset=utf-8,blah:0:0)');
+ expect(frame.library, equals('data:...'));
+ });
+
+ test('truncates data: URIs', () {
+ var frame = Frame.parseVM(
+ '#0 main (<data:application/dart;charset=utf-8>:1:15)');
+ expect(frame.library, equals('data:...'));
+ });
+ });
+
+ group('.location', () {
+ test(
+ 'returns the library and line/column numbers for non-core '
+ 'libraries', () {
+ expect(
+ Frame.parseVM('#0 Foo '
+ '(https://dart.dev/thing.dart:5:10)')
+ .location,
+ equals('https://dart.dev/thing.dart 5:10'));
+ expect(Frame.parseVM('#0 Foo (foo/bar.dart:1:2)').location,
+ equals('${path.join('foo', 'bar.dart')} 1:2'));
+ });
+ });
+
+ group('.package', () {
+ test('returns null for non-package URIs', () {
+ expect(
+ Frame.parseVM('#0 Foo (dart:async/future.dart:0:0)').package, isNull);
+ expect(
+ Frame.parseVM('#0 Foo '
+ '(https://dart.dev/stuff/thing.dart:0:0)')
+ .package,
+ isNull);
+ });
+
+ test('returns the package name for package: URIs', () {
+ expect(Frame.parseVM('#0 Foo (package:foo/foo.dart:0:0)').package,
+ equals('foo'));
+ expect(Frame.parseVM('#0 Foo (package:foo/zap/bar.dart:0:0)').package,
+ equals('foo'));
+ });
+ });
+
+ group('.toString()', () {
+ test(
+ 'returns the library and line/column numbers for non-core '
+ 'libraries', () {
+ expect(
+ Frame.parseVM('#0 Foo (https://dart.dev/thing.dart:5:10)').toString(),
+ equals('https://dart.dev/thing.dart 5:10 in Foo'));
+ });
+
+ test('converts "<anonymous closure>" to "<fn>"', () {
+ expect(
+ Frame.parseVM('#0 Foo.<anonymous closure> '
+ '(dart:core/uri.dart:5:10)')
+ .toString(),
+ equals('dart:core/uri.dart 5:10 in Foo.<fn>'));
+ });
+
+ test('prints a frame without a column correctly', () {
+ expect(Frame.parseVM('#0 Foo (dart:core/uri.dart:5)').toString(),
+ equals('dart:core/uri.dart 5 in Foo'));
+ });
+
+ test('prints relative paths as relative', () {
+ var relative = path.normalize('relative/path/to/foo.dart');
+ expect(Frame.parseFriendly('$relative 5:10 Foo').toString(),
+ equals('$relative 5:10 in Foo'));
+ });
+ });
+
+ test('parses a V8 Wasm frame with a name', () {
+ var frame = Frame.parseV8(' at Error._throwWithCurrentStackTrace '
+ '(wasm://wasm/0006d966:wasm-function[119]:0xbb13)');
+ expect(frame.uri, Uri.parse('wasm://wasm/0006d966'));
+ expect(frame.line, 1);
+ expect(frame.column, 0xbb13 + 1);
+ expect(frame.member, 'Error._throwWithCurrentStackTrace');
+ });
+
+ test('parses a V8 Wasm frame with a name with spaces', () {
+ var frame = Frame.parseV8(' at main tear-off trampoline '
+ '(wasm://wasm/0017fbea:wasm-function[863]:0x23cc8)');
+ expect(frame.uri, Uri.parse('wasm://wasm/0017fbea'));
+ expect(frame.line, 1);
+ expect(frame.column, 0x23cc8 + 1);
+ expect(frame.member, 'main tear-off trampoline');
+ });
+
+ test('parses a V8 Wasm frame with a name with colons and parens', () {
+ var frame = Frame.parseV8(' at a::b::c() '
+ '(https://a.b.com/x/y/z.wasm:wasm-function[66334]:0x12c28ad)');
+ expect(frame.uri, Uri.parse('https://a.b.com/x/y/z.wasm'));
+ expect(frame.line, 1);
+ expect(frame.column, 0x12c28ad + 1);
+ expect(frame.member, 'a::b::c()');
+ });
+
+ test('parses a V8 Wasm frame without a name', () {
+ var frame =
+ Frame.parseV8(' at wasm://wasm/0006d966:wasm-function[119]:0xbb13');
+ expect(frame.uri, Uri.parse('wasm://wasm/0006d966'));
+ expect(frame.line, 1);
+ expect(frame.column, 0xbb13 + 1);
+ expect(frame.member, '119');
+ });
+
+ test('parses a Firefox Wasm frame with a name', () {
+ var frame = Frame.parseFirefox(
+ 'g@http://localhost:8080/test.wasm:wasm-function[796]:0x143b4');
+ expect(frame.uri, Uri.parse('http://localhost:8080/test.wasm'));
+ expect(frame.line, 1);
+ expect(frame.column, 0x143b4 + 1);
+ expect(frame.member, 'g');
+ });
+
+ test('parses a Firefox Wasm frame with a name with spaces', () {
+ var frame = Frame.parseFirefox(
+ 'main tear-off trampoline@http://localhost:8080/test.wasm:wasm-function[794]:0x14387');
+ expect(frame.uri, Uri.parse('http://localhost:8080/test.wasm'));
+ expect(frame.line, 1);
+ expect(frame.column, 0x14387 + 1);
+ expect(frame.member, 'main tear-off trampoline');
+ });
+
+ test('parses a Firefox Wasm frame without a name', () {
+ var frame = Frame.parseFirefox(
+ '@http://localhost:8080/test.wasm:wasm-function[796]:0x143b4');
+ expect(frame.uri, Uri.parse('http://localhost:8080/test.wasm'));
+ expect(frame.line, 1);
+ expect(frame.column, 0x143b4 + 1);
+ expect(frame.member, '796');
+ });
+
+ test('parses a Safari Wasm frame with a name', () {
+ var frame = Frame.parseSafari('<?>.wasm-function[g]@[wasm code]');
+ expect(frame.uri, Uri.parse('wasm code'));
+ expect(frame.line, null);
+ expect(frame.column, null);
+ expect(frame.member, 'g');
+ });
+
+ test('parses a Safari Wasm frame with a name', () {
+ var frame = Frame.parseSafari(
+ '<?>.wasm-function[main tear-off trampoline]@[wasm code]');
+ expect(frame.uri, Uri.parse('wasm code'));
+ expect(frame.line, null);
+ expect(frame.column, null);
+ expect(frame.member, 'main tear-off trampoline');
+ });
+
+ test('parses a Safari Wasm frame without a name', () {
+ var frame = Frame.parseSafari('<?>.wasm-function[796]@[wasm code]');
+ expect(frame.uri, Uri.parse('wasm code'));
+ expect(frame.line, null);
+ expect(frame.column, null);
+ expect(frame.member, '796');
+ });
+}
+
+void expectIsUnparsed(Frame Function(String) constructor, String text) {
+ var frame = constructor(text);
+ expect(frame, isA<UnparsedFrame>());
+ expect(frame.toString(), equals(text));
+}
diff --git a/pkgs/stack_trace/test/trace_test.dart b/pkgs/stack_trace/test/trace_test.dart
new file mode 100644
index 0000000..e09de95
--- /dev/null
+++ b/pkgs/stack_trace/test/trace_test.dart
@@ -0,0 +1,615 @@
+// 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:path/path.dart' as path;
+import 'package:stack_trace/stack_trace.dart';
+import 'package:test/test.dart';
+
+void main() {
+ // This just shouldn't crash.
+ test('a native stack trace is parseable', Trace.current);
+
+ group('.parse', () {
+ test('.parse parses a V8 stack trace with eval statment correctly', () {
+ var trace = Trace.parse(r'''Error
+ at Object.eval (eval at Foo (main.dart.js:588), <anonymous>:3:47)''');
+ expect(trace.frames[0].uri, Uri.parse('main.dart.js'));
+ expect(trace.frames[0].member, equals('Object.eval'));
+ expect(trace.frames[0].line, equals(588));
+ expect(trace.frames[0].column, isNull);
+ });
+
+ test('.parse parses a VM stack trace correctly', () {
+ var trace = Trace.parse(
+ '#0 Foo._bar (file:///home/nweiz/code/stuff.dart:42:21)\n'
+ '#1 zip.<anonymous closure>.zap (dart:async/future.dart:0:2)\n'
+ '#2 zip.<anonymous closure>.zap (https://pub.dev/thing.dart:1:100)',
+ );
+
+ expect(trace.frames[0].uri,
+ equals(Uri.parse('file:///home/nweiz/code/stuff.dart')));
+ expect(trace.frames[1].uri, equals(Uri.parse('dart:async/future.dart')));
+ expect(
+ trace.frames[2].uri, equals(Uri.parse('https://pub.dev/thing.dart')));
+ });
+
+ test('parses a V8 stack trace correctly', () {
+ var trace = Trace.parse('Error\n'
+ ' at Foo._bar (https://example.com/stuff.js:42:21)\n'
+ ' at https://example.com/stuff.js:0:2\n'
+ ' at zip.<anonymous>.zap '
+ '(https://pub.dev/thing.js:1:100)');
+
+ expect(trace.frames[0].uri,
+ equals(Uri.parse('https://example.com/stuff.js')));
+ expect(trace.frames[1].uri,
+ equals(Uri.parse('https://example.com/stuff.js')));
+ expect(
+ trace.frames[2].uri, equals(Uri.parse('https://pub.dev/thing.js')));
+
+ trace = Trace.parse('Exception: foo\n'
+ ' at Foo._bar (https://example.com/stuff.js:42:21)\n'
+ ' at https://example.com/stuff.js:0:2\n'
+ ' at zip.<anonymous>.zap '
+ '(https://pub.dev/thing.js:1:100)');
+
+ expect(trace.frames[0].uri,
+ equals(Uri.parse('https://example.com/stuff.js')));
+ expect(trace.frames[1].uri,
+ equals(Uri.parse('https://example.com/stuff.js')));
+ expect(
+ trace.frames[2].uri, equals(Uri.parse('https://pub.dev/thing.js')));
+
+ trace = Trace.parse('Exception: foo\n'
+ ' bar\n'
+ ' at Foo._bar (https://example.com/stuff.js:42:21)\n'
+ ' at https://example.com/stuff.js:0:2\n'
+ ' at zip.<anonymous>.zap '
+ '(https://pub.dev/thing.js:1:100)');
+
+ expect(trace.frames[0].uri,
+ equals(Uri.parse('https://example.com/stuff.js')));
+ expect(trace.frames[1].uri,
+ equals(Uri.parse('https://example.com/stuff.js')));
+ expect(
+ trace.frames[2].uri, equals(Uri.parse('https://pub.dev/thing.js')));
+
+ trace = Trace.parse('Exception: foo\n'
+ ' bar\n'
+ ' at Foo._bar (https://example.com/stuff.js:42:21)\n'
+ ' at https://example.com/stuff.js:0:2\n'
+ ' at (anonymous function).zip.zap '
+ '(https://pub.dev/thing.js:1:100)');
+
+ expect(trace.frames[0].uri,
+ equals(Uri.parse('https://example.com/stuff.js')));
+ expect(trace.frames[1].uri,
+ equals(Uri.parse('https://example.com/stuff.js')));
+ expect(trace.frames[1].member, equals('<fn>'));
+ expect(
+ trace.frames[2].uri, equals(Uri.parse('https://pub.dev/thing.js')));
+ expect(trace.frames[2].member, equals('<fn>.zip.zap'));
+ });
+
+ // JavaScriptCore traces are just like V8, except that it doesn't have a
+ // header and it starts with a tab rather than spaces.
+ test('parses a JavaScriptCore stack trace correctly', () {
+ var trace =
+ Trace.parse('\tat Foo._bar (https://example.com/stuff.js:42:21)\n'
+ '\tat https://example.com/stuff.js:0:2\n'
+ '\tat zip.<anonymous>.zap '
+ '(https://pub.dev/thing.js:1:100)');
+
+ expect(trace.frames[0].uri,
+ equals(Uri.parse('https://example.com/stuff.js')));
+ expect(trace.frames[1].uri,
+ equals(Uri.parse('https://example.com/stuff.js')));
+ expect(
+ trace.frames[2].uri, equals(Uri.parse('https://pub.dev/thing.js')));
+
+ trace = Trace.parse('\tat Foo._bar (https://example.com/stuff.js:42:21)\n'
+ '\tat \n'
+ '\tat zip.<anonymous>.zap '
+ '(https://pub.dev/thing.js:1:100)');
+
+ expect(trace.frames[0].uri,
+ equals(Uri.parse('https://example.com/stuff.js')));
+ expect(
+ trace.frames[1].uri, equals(Uri.parse('https://pub.dev/thing.js')));
+ });
+
+ test('parses a Firefox/Safari stack trace correctly', () {
+ var trace = Trace.parse('Foo._bar@https://example.com/stuff.js:42\n'
+ 'zip/<@https://example.com/stuff.js:0\n'
+ 'zip.zap(12, "@)()/<")@https://pub.dev/thing.js:1');
+
+ expect(trace.frames[0].uri,
+ equals(Uri.parse('https://example.com/stuff.js')));
+ expect(trace.frames[1].uri,
+ equals(Uri.parse('https://example.com/stuff.js')));
+ expect(
+ trace.frames[2].uri, equals(Uri.parse('https://pub.dev/thing.js')));
+
+ trace = Trace.parse('zip/<@https://example.com/stuff.js:0\n'
+ 'Foo._bar@https://example.com/stuff.js:42\n'
+ 'zip.zap(12, "@)()/<")@https://pub.dev/thing.js:1');
+
+ expect(trace.frames[0].uri,
+ equals(Uri.parse('https://example.com/stuff.js')));
+ expect(trace.frames[1].uri,
+ equals(Uri.parse('https://example.com/stuff.js')));
+ expect(
+ trace.frames[2].uri, equals(Uri.parse('https://pub.dev/thing.js')));
+
+ trace = Trace.parse('zip.zap(12, "@)()/<")@https://pub.dev/thing.js:1\n'
+ 'zip/<@https://example.com/stuff.js:0\n'
+ 'Foo._bar@https://example.com/stuff.js:42');
+
+ expect(
+ trace.frames[0].uri, equals(Uri.parse('https://pub.dev/thing.js')));
+ expect(trace.frames[1].uri,
+ equals(Uri.parse('https://example.com/stuff.js')));
+ expect(trace.frames[2].uri,
+ equals(Uri.parse('https://example.com/stuff.js')));
+ });
+
+ test('parses a Firefox/Safari stack trace containing native code correctly',
+ () {
+ var trace = Trace.parse('Foo._bar@https://example.com/stuff.js:42\n'
+ 'zip/<@https://example.com/stuff.js:0\n'
+ 'zip.zap(12, "@)()/<")@https://pub.dev/thing.js:1\n'
+ '[native code]');
+
+ expect(trace.frames[0].uri,
+ equals(Uri.parse('https://example.com/stuff.js')));
+ expect(trace.frames[1].uri,
+ equals(Uri.parse('https://example.com/stuff.js')));
+ expect(
+ trace.frames[2].uri, equals(Uri.parse('https://pub.dev/thing.js')));
+ expect(trace.frames.length, equals(3));
+ });
+
+ test('parses a Firefox/Safari stack trace without a method name correctly',
+ () {
+ var trace = Trace.parse('https://example.com/stuff.js:42\n'
+ 'zip/<@https://example.com/stuff.js:0\n'
+ 'zip.zap(12, "@)()/<")@https://pub.dev/thing.js:1');
+
+ expect(trace.frames[0].uri,
+ equals(Uri.parse('https://example.com/stuff.js')));
+ expect(trace.frames[0].member, equals('<fn>'));
+ expect(trace.frames[1].uri,
+ equals(Uri.parse('https://example.com/stuff.js')));
+ expect(
+ trace.frames[2].uri, equals(Uri.parse('https://pub.dev/thing.js')));
+ });
+
+ test('parses a Firefox/Safari stack trace with an empty line correctly',
+ () {
+ var trace = Trace.parse('Foo._bar@https://example.com/stuff.js:42\n'
+ '\n'
+ 'zip/<@https://example.com/stuff.js:0\n'
+ 'zip.zap(12, "@)()/<")@https://pub.dev/thing.js:1');
+
+ expect(trace.frames[0].uri,
+ equals(Uri.parse('https://example.com/stuff.js')));
+ expect(trace.frames[1].uri,
+ equals(Uri.parse('https://example.com/stuff.js')));
+ expect(
+ trace.frames[2].uri, equals(Uri.parse('https://pub.dev/thing.js')));
+ });
+
+ test('parses a Firefox/Safari stack trace with a column number correctly',
+ () {
+ var trace = Trace.parse('Foo._bar@https://example.com/stuff.js:42:2\n'
+ 'zip/<@https://example.com/stuff.js:0\n'
+ 'zip.zap(12, "@)()/<")@https://pub.dev/thing.js:1');
+
+ expect(trace.frames[0].uri,
+ equals(Uri.parse('https://example.com/stuff.js')));
+ expect(trace.frames[0].line, equals(42));
+ expect(trace.frames[0].column, equals(2));
+ expect(trace.frames[1].uri,
+ equals(Uri.parse('https://example.com/stuff.js')));
+ expect(
+ trace.frames[2].uri, equals(Uri.parse('https://pub.dev/thing.js')));
+ });
+
+ test('parses a package:stack_trace stack trace correctly', () {
+ var trace =
+ Trace.parse('https://dart.dev/foo/bar.dart 10:11 Foo.<fn>.bar\n'
+ 'https://dart.dev/foo/baz.dart Foo.<fn>.bar');
+
+ expect(trace.frames[0].uri,
+ equals(Uri.parse('https://dart.dev/foo/bar.dart')));
+ expect(trace.frames[1].uri,
+ equals(Uri.parse('https://dart.dev/foo/baz.dart')));
+ });
+
+ test('parses a package:stack_trace stack chain correctly', () {
+ var trace =
+ Trace.parse('https://dart.dev/foo/bar.dart 10:11 Foo.<fn>.bar\n'
+ 'https://dart.dev/foo/baz.dart Foo.<fn>.bar\n'
+ '===== asynchronous gap ===========================\n'
+ 'https://dart.dev/foo/bang.dart 10:11 Foo.<fn>.bar\n'
+ 'https://dart.dev/foo/quux.dart Foo.<fn>.bar');
+
+ expect(trace.frames[0].uri,
+ equals(Uri.parse('https://dart.dev/foo/bar.dart')));
+ expect(trace.frames[1].uri,
+ equals(Uri.parse('https://dart.dev/foo/baz.dart')));
+ expect(trace.frames[2].uri,
+ equals(Uri.parse('https://dart.dev/foo/bang.dart')));
+ expect(trace.frames[3].uri,
+ equals(Uri.parse('https://dart.dev/foo/quux.dart')));
+ });
+
+ test('parses a package:stack_trace stack chain with end gap correctly', () {
+ var trace = Trace.parse(
+ 'https://dart.dev/foo/bar.dart 10:11 Foo.<fn>.bar\n'
+ 'https://dart.dev/foo/baz.dart Foo.<fn>.bar\n'
+ 'https://dart.dev/foo/bang.dart 10:11 Foo.<fn>.bar\n'
+ 'https://dart.dev/foo/quux.dart Foo.<fn>.bar===== asynchronous gap ===========================\n',
+ );
+
+ expect(trace.frames.length, 4);
+ expect(trace.frames[0].uri,
+ equals(Uri.parse('https://dart.dev/foo/bar.dart')));
+ expect(trace.frames[1].uri,
+ equals(Uri.parse('https://dart.dev/foo/baz.dart')));
+ expect(trace.frames[2].uri,
+ equals(Uri.parse('https://dart.dev/foo/bang.dart')));
+ expect(trace.frames[3].uri,
+ equals(Uri.parse('https://dart.dev/foo/quux.dart')));
+ });
+
+ test('parses a real package:stack_trace stack trace correctly', () {
+ var traceString = Trace.current().toString();
+ expect(Trace.parse(traceString).toString(), equals(traceString));
+ });
+
+ test('parses an empty string correctly', () {
+ var trace = Trace.parse('');
+ expect(trace.frames, isEmpty);
+ expect(trace.toString(), equals(''));
+ });
+
+ test('parses trace with async gap correctly', () {
+ var trace = Trace.parse('#0 bop (file:///pull.dart:42:23)\n'
+ '<asynchronous suspension>\n'
+ '#1 twist (dart:the/future.dart:0:2)\n'
+ '#2 main (dart:my/file.dart:4:6)\n');
+
+ expect(trace.frames.length, 3);
+ expect(trace.frames[0].uri, equals(Uri.parse('file:///pull.dart')));
+ expect(trace.frames[1].uri, equals(Uri.parse('dart:the/future.dart')));
+ expect(trace.frames[2].uri, equals(Uri.parse('dart:my/file.dart')));
+ });
+
+ test('parses trace with async gap at end correctly', () {
+ var trace = Trace.parse('#0 bop (file:///pull.dart:42:23)\n'
+ '#1 twist (dart:the/future.dart:0:2)\n'
+ '<asynchronous suspension>\n');
+
+ expect(trace.frames.length, 2);
+ expect(trace.frames[0].uri, equals(Uri.parse('file:///pull.dart')));
+ expect(trace.frames[1].uri, equals(Uri.parse('dart:the/future.dart')));
+ });
+
+ test('parses a V8 stack frace with Wasm frames correctly', () {
+ var trace = Trace.parse(
+ '\tat Error._throwWithCurrentStackTrace (wasm://wasm/0006d892:wasm-function[119]:0xbaf8)\n'
+ '\tat main (wasm://wasm/0006d892:wasm-function[792]:0x14378)\n'
+ '\tat main tear-off trampoline (wasm://wasm/0006d892:wasm-function[794]:0x14387)\n'
+ '\tat _invokeMain (wasm://wasm/0006d892:wasm-function[70]:0xa56c)\n'
+ '\tat InstantiatedApp.invokeMain (/home/user/test.mjs:361:37)\n'
+ '\tat main (/home/user/run_wasm.js:416:21)\n'
+ '\tat async action (/home/user/run_wasm.js:353:38)\n'
+ '\tat async eventLoop (/home/user/run_wasm.js:329:9)');
+
+ expect(trace.frames.length, 8);
+
+ for (final frame in trace.frames) {
+ expect(frame is UnparsedFrame, false);
+ }
+
+ expect(trace.frames[0].uri, Uri.parse('wasm://wasm/0006d892'));
+ expect(trace.frames[0].line, 1);
+ expect(trace.frames[0].column, 0xbaf8 + 1);
+ expect(trace.frames[0].member, 'Error._throwWithCurrentStackTrace');
+
+ expect(trace.frames[4].uri, Uri.parse('file:///home/user/test.mjs'));
+ expect(trace.frames[4].line, 361);
+ expect(trace.frames[4].column, 37);
+ expect(trace.frames[4].member, 'InstantiatedApp.invokeMain');
+
+ expect(trace.frames[5].uri, Uri.parse('file:///home/user/run_wasm.js'));
+ expect(trace.frames[5].line, 416);
+ expect(trace.frames[5].column, 21);
+ expect(trace.frames[5].member, 'main');
+ });
+
+ test('parses Firefox stack frace with Wasm frames correctly', () {
+ var trace = Trace.parse(
+ 'Error._throwWithCurrentStackTrace@http://localhost:8080/test.wasm:wasm-function[119]:0xbaf8\n'
+ 'main@http://localhost:8080/test.wasm:wasm-function[792]:0x14378\n'
+ 'main tear-off trampoline@http://localhost:8080/test.wasm:wasm-function[794]:0x14387\n'
+ '_invokeMain@http://localhost:8080/test.wasm:wasm-function[70]:0xa56c\n'
+ 'invoke@http://localhost:8080/test.mjs:48:26');
+
+ expect(trace.frames.length, 5);
+
+ for (final frame in trace.frames) {
+ expect(frame is UnparsedFrame, false);
+ }
+
+ expect(trace.frames[0].uri, Uri.parse('http://localhost:8080/test.wasm'));
+ expect(trace.frames[0].line, 1);
+ expect(trace.frames[0].column, 0xbaf8 + 1);
+ expect(trace.frames[0].member, 'Error._throwWithCurrentStackTrace');
+
+ expect(trace.frames[4].uri, Uri.parse('http://localhost:8080/test.mjs'));
+ expect(trace.frames[4].line, 48);
+ expect(trace.frames[4].column, 26);
+ expect(trace.frames[4].member, 'invoke');
+ });
+
+ test('parses JSShell stack frace with Wasm frames correctly', () {
+ var trace = Trace.parse(
+ 'Error._throwWithCurrentStackTrace@/home/user/test.mjs line 29 > WebAssembly.compile:wasm-function[119]:0xbaf8\n'
+ 'main@/home/user/test.mjs line 29 > WebAssembly.compile:wasm-function[792]:0x14378\n'
+ 'main tear-off trampoline@/home/user/test.mjs line 29 > WebAssembly.compile:wasm-function[794]:0x14387\n'
+ '_invokeMain@/home/user/test.mjs line 29 > WebAssembly.compile:wasm-function[70]:0xa56c\n'
+ 'invokeMain@/home/user/test.mjs:361:37\n'
+ 'main@/home/user/run_wasm.js:416:21\n'
+ 'async*action@/home/user/run_wasm.js:353:44\n'
+ 'eventLoop@/home/user/run_wasm.js:329:15\n'
+ 'self.dartMainRunner@/home/user/run_wasm.js:354:14\n'
+ '@/home/user/run_wasm.js:419:15');
+
+ expect(trace.frames.length, 10);
+
+ for (final frame in trace.frames) {
+ expect(frame is UnparsedFrame, false);
+ }
+
+ expect(trace.frames[0].uri, Uri.parse('file:///home/user/test.mjs'));
+ expect(trace.frames[0].line, 1);
+ expect(trace.frames[0].column, 0xbaf8 + 1);
+ expect(trace.frames[0].member, 'Error._throwWithCurrentStackTrace');
+
+ expect(trace.frames[4].uri, Uri.parse('file:///home/user/test.mjs'));
+ expect(trace.frames[4].line, 361);
+ expect(trace.frames[4].column, 37);
+ expect(trace.frames[4].member, 'invokeMain');
+
+ expect(trace.frames[9].uri, Uri.parse('file:///home/user/run_wasm.js'));
+ expect(trace.frames[9].line, 419);
+ expect(trace.frames[9].column, 15);
+ expect(trace.frames[9].member, '<fn>');
+ });
+
+ test('parses Safari stack frace with Wasm frames correctly', () {
+ var trace = Trace.parse(
+ '<?>.wasm-function[Error._throwWithCurrentStackTrace]@[wasm code]\n'
+ '<?>.wasm-function[main]@[wasm code]\n'
+ '<?>.wasm-function[main tear-off trampoline]@[wasm code]\n'
+ '<?>.wasm-function[_invokeMain]@[wasm code]\n'
+ 'invokeMain@/home/user/test.mjs:361:48\n'
+ '@/home/user/run_wasm.js:416:31');
+
+ expect(trace.frames.length, 6);
+
+ for (final frame in trace.frames) {
+ expect(frame is UnparsedFrame, false);
+ }
+
+ expect(trace.frames[0].uri, Uri.parse('wasm code'));
+ expect(trace.frames[0].line, null);
+ expect(trace.frames[0].column, null);
+ expect(trace.frames[0].member, 'Error._throwWithCurrentStackTrace');
+
+ expect(trace.frames[4].uri, Uri.parse('file:///home/user/test.mjs'));
+ expect(trace.frames[4].line, 361);
+ expect(trace.frames[4].column, 48);
+ expect(trace.frames[4].member, 'invokeMain');
+
+ expect(trace.frames[5].uri, Uri.parse('file:///home/user/run_wasm.js'));
+ expect(trace.frames[5].line, 416);
+ expect(trace.frames[5].column, 31);
+ expect(trace.frames[5].member, '<fn>');
+ });
+ });
+
+ test('.toString() nicely formats the stack trace', () {
+ var trace = Trace.parse('''
+#0 Foo._bar (foo/bar.dart:42:21)
+#1 zip.<anonymous closure>.zap (dart:async/future.dart:0:2)
+#2 zip.<anonymous closure>.zap (https://pub.dev/thing.dart:1:100)
+''');
+
+ expect(trace.toString(), equals('''
+${path.join('foo', 'bar.dart')} 42:21 Foo._bar
+dart:async/future.dart 0:2 zip.<fn>.zap
+https://pub.dev/thing.dart 1:100 zip.<fn>.zap
+'''));
+ });
+
+ test('.vmTrace returns a native-style trace', () {
+ var uri = path.toUri(path.absolute('foo'));
+ var trace = Trace([
+ Frame(uri, 10, 20, 'Foo.<fn>'),
+ Frame(Uri.parse('https://dart.dev/foo.dart'), null, null, 'bar'),
+ Frame(Uri.parse('dart:async'), 15, null, 'baz'),
+ ]);
+
+ expect(
+ trace.vmTrace.toString(),
+ equals('#1 Foo.<anonymous closure> ($uri:10:20)\n'
+ '#2 bar (https://dart.dev/foo.dart:0:0)\n'
+ '#3 baz (dart:async:15:0)\n'));
+ });
+
+ group('folding', () {
+ group('.terse', () {
+ test('folds core frames together bottom-up', () {
+ var trace = Trace.parse('''
+#1 top (dart:async/future.dart:0:2)
+#2 bottom (dart:core/uri.dart:1:100)
+#0 notCore (foo.dart:42:21)
+#3 top (dart:io:5:10)
+#4 bottom (dart:async-patch/future.dart:9:11)
+#5 alsoNotCore (bar.dart:10:20)
+''');
+
+ expect(trace.terse.toString(), equals('''
+dart:core bottom
+foo.dart 42:21 notCore
+dart:async bottom
+bar.dart 10:20 alsoNotCore
+'''));
+ });
+
+ test('folds empty async frames', () {
+ var trace = Trace.parse('''
+#0 top (dart:async/future.dart:0:2)
+#1 empty.<<anonymous closure>_async_body> (bar.dart)
+#2 bottom (dart:async-patch/future.dart:9:11)
+#3 notCore (foo.dart:42:21)
+''');
+
+ expect(trace.terse.toString(), equals('''
+dart:async bottom
+foo.dart 42:21 notCore
+'''));
+ });
+
+ test('removes the bottom-most async frame', () {
+ var trace = Trace.parse('''
+#0 notCore (foo.dart:42:21)
+#1 top (dart:async/future.dart:0:2)
+#2 bottom (dart:core/uri.dart:1:100)
+#3 top (dart:io:5:10)
+#4 bottom (dart:async-patch/future.dart:9:11)
+''');
+
+ expect(trace.terse.toString(), equals('''
+foo.dart 42:21 notCore
+'''));
+ });
+
+ test("won't make a trace empty", () {
+ var trace = Trace.parse('''
+#1 top (dart:async/future.dart:0:2)
+#2 bottom (dart:core/uri.dart:1:100)
+''');
+
+ expect(trace.terse.toString(), equals('''
+dart:core bottom
+'''));
+ });
+
+ test("won't panic on an empty trace", () {
+ expect(Trace.parse('').terse.toString(), equals(''));
+ });
+ });
+
+ group('.foldFrames', () {
+ test('folds frames together bottom-up', () {
+ var trace = Trace.parse('''
+#0 notFoo (foo.dart:42:21)
+#1 fooTop (bar.dart:0:2)
+#2 fooBottom (foo.dart:1:100)
+#3 alsoNotFoo (bar.dart:10:20)
+#4 fooTop (dart:io/socket.dart:5:10)
+#5 fooBottom (dart:async-patch/future.dart:9:11)
+''');
+
+ var folded =
+ trace.foldFrames((frame) => frame.member!.startsWith('foo'));
+ expect(folded.toString(), equals('''
+foo.dart 42:21 notFoo
+foo.dart 1:100 fooBottom
+bar.dart 10:20 alsoNotFoo
+dart:async-patch/future.dart 9:11 fooBottom
+'''));
+ });
+
+ test('will never fold unparsed frames', () {
+ var trace = Trace.parse(r'''
+.g"cs$#:b";a#>sw{*{ul$"$xqwr`p
+%+j-?uppx<([j@#nu{{>*+$%x-={`{
+!e($b{nj)zs?cgr%!;bmw.+$j+pfj~
+''');
+
+ expect(trace.foldFrames((frame) => true).toString(), equals(r'''
+.g"cs$#:b";a#>sw{*{ul$"$xqwr`p
+%+j-?uppx<([j@#nu{{>*+$%x-={`{
+!e($b{nj)zs?cgr%!;bmw.+$j+pfj~
+'''));
+ });
+
+ group('with terse: true', () {
+ test('folds core frames as well', () {
+ var trace = Trace.parse('''
+#0 notFoo (foo.dart:42:21)
+#1 fooTop (bar.dart:0:2)
+#2 coreBottom (dart:async/future.dart:0:2)
+#3 alsoNotFoo (bar.dart:10:20)
+#4 fooTop (foo.dart:9:11)
+#5 coreBottom (dart:async-patch/future.dart:9:11)
+''');
+
+ var folded = trace.foldFrames(
+ (frame) => frame.member!.startsWith('foo'),
+ terse: true);
+ expect(folded.toString(), equals('''
+foo.dart 42:21 notFoo
+dart:async coreBottom
+bar.dart 10:20 alsoNotFoo
+'''));
+ });
+
+ test('shortens folded frames', () {
+ var trace = Trace.parse('''
+#0 notFoo (foo.dart:42:21)
+#1 fooTop (bar.dart:0:2)
+#2 fooBottom (package:foo/bar.dart:0:2)
+#3 alsoNotFoo (bar.dart:10:20)
+#4 fooTop (foo.dart:9:11)
+#5 fooBottom (foo/bar.dart:9:11)
+#6 againNotFoo (bar.dart:20:20)
+''');
+
+ var folded = trace.foldFrames(
+ (frame) => frame.member!.startsWith('foo'),
+ terse: true);
+ expect(folded.toString(), equals('''
+foo.dart 42:21 notFoo
+package:foo fooBottom
+bar.dart 10:20 alsoNotFoo
+foo fooBottom
+bar.dart 20:20 againNotFoo
+'''));
+ });
+
+ test('removes the bottom-most folded frame', () {
+ var trace = Trace.parse('''
+#2 fooTop (package:foo/bar.dart:0:2)
+#3 notFoo (bar.dart:10:20)
+#5 fooBottom (foo/bar.dart:9:11)
+''');
+
+ var folded = trace.foldFrames(
+ (frame) => frame.member!.startsWith('foo'),
+ terse: true);
+ expect(folded.toString(), equals('''
+package:foo fooTop
+bar.dart 10:20 notFoo
+'''));
+ });
+ });
+ });
+ });
+}
diff --git a/pkgs/stack_trace/test/utils.dart b/pkgs/stack_trace/test/utils.dart
new file mode 100644
index 0000000..98cb5ed
--- /dev/null
+++ b/pkgs/stack_trace/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 'package:stack_trace/stack_trace.dart';
+import 'package:test/test.dart';
+
+/// Returns a matcher that runs [matcher] against a [Frame]'s `member` field.
+Matcher frameMember(Object? matcher) =>
+ isA<Frame>().having((p0) => p0.member, 'member', matcher);
+
+/// Returns a matcher that runs [matcher] against a [Frame]'s `library` field.
+Matcher frameLibrary(Object? matcher) =>
+ isA<Frame>().having((p0) => p0.library, 'library', matcher);
diff --git a/pkgs/stack_trace/test/vm_test.dart b/pkgs/stack_trace/test/vm_test.dart
new file mode 100644
index 0000000..70ac014
--- /dev/null
+++ b/pkgs/stack_trace/test/vm_test.dart
@@ -0,0 +1,112 @@
+// 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.
+
+/// This file tests stack_trace's ability to parse live stack traces. It's a
+/// dual of dartium_test.dart, since method names can differ somewhat from
+/// platform to platform. No similar file exists for dart2js since the specific
+/// method names there are implementation details.
+@TestOn('vm')
+library;
+
+import 'package:path/path.dart' as path;
+import 'package:stack_trace/stack_trace.dart';
+import 'package:test/test.dart';
+
+// The name of this (trivial) function is verified as part of the test
+String getStackTraceString() => StackTrace.current.toString();
+
+// The name of this (trivial) function is verified as part of the test
+StackTrace getStackTraceObject() => StackTrace.current;
+
+Frame getCaller([int? level]) {
+ if (level == null) return Frame.caller();
+ return Frame.caller(level);
+}
+
+Frame nestedGetCaller(int level) => getCaller(level);
+
+Trace getCurrentTrace([int level = 0]) => Trace.current(level);
+
+Trace nestedGetCurrentTrace(int level) => getCurrentTrace(level);
+
+void main() {
+ group('Trace', () {
+ test('.parse parses a real stack trace correctly', () {
+ var string = getStackTraceString();
+ var trace = Trace.parse(string);
+ expect(path.url.basename(trace.frames.first.uri.path),
+ equals('vm_test.dart'));
+ expect(trace.frames.first.member, equals('getStackTraceString'));
+ });
+
+ test('converts from a native stack trace correctly', () {
+ var trace = Trace.from(getStackTraceObject());
+ expect(path.url.basename(trace.frames.first.uri.path),
+ equals('vm_test.dart'));
+ expect(trace.frames.first.member, equals('getStackTraceObject'));
+ });
+
+ test('.from handles a stack overflow trace correctly', () {
+ void overflow() => overflow();
+
+ late Trace? trace;
+ try {
+ overflow();
+ } catch (_, stackTrace) {
+ trace = Trace.from(stackTrace);
+ }
+
+ expect(trace!.frames.first.member, equals('main.<fn>.<fn>.overflow'));
+ });
+
+ group('.current()', () {
+ test('with no argument returns a trace starting at the current frame',
+ () {
+ var trace = Trace.current();
+ expect(trace.frames.first.member, equals('main.<fn>.<fn>.<fn>'));
+ });
+
+ test('at level 0 returns a trace starting at the current frame', () {
+ var trace = Trace.current();
+ expect(trace.frames.first.member, equals('main.<fn>.<fn>.<fn>'));
+ });
+
+ test('at level 1 returns a trace starting at the parent frame', () {
+ var trace = getCurrentTrace(1);
+ expect(trace.frames.first.member, equals('main.<fn>.<fn>.<fn>'));
+ });
+
+ test('at level 2 returns a trace starting at the grandparent frame', () {
+ var trace = nestedGetCurrentTrace(2);
+ expect(trace.frames.first.member, equals('main.<fn>.<fn>.<fn>'));
+ });
+
+ test('throws an ArgumentError for negative levels', () {
+ expect(() => Trace.current(-1), throwsArgumentError);
+ });
+ });
+ });
+
+ group('Frame.caller()', () {
+ test('with no argument returns the parent frame', () {
+ expect(getCaller().member, equals('main.<fn>.<fn>'));
+ });
+
+ test('at level 0 returns the current frame', () {
+ expect(getCaller(0).member, equals('getCaller'));
+ });
+
+ test('at level 1 returns the current frame', () {
+ expect(getCaller(1).member, equals('main.<fn>.<fn>'));
+ });
+
+ test('at level 2 returns the grandparent frame', () {
+ expect(nestedGetCaller(2).member, equals('main.<fn>.<fn>'));
+ });
+
+ test('throws an ArgumentError for negative levels', () {
+ expect(() => Frame.caller(-1), throwsArgumentError);
+ });
+ });
+}
diff --git a/pkgs/stream_channel/.gitignore b/pkgs/stream_channel/.gitignore
new file mode 100644
index 0000000..1447012
--- /dev/null
+++ b/pkgs/stream_channel/.gitignore
@@ -0,0 +1,10 @@
+.buildlog
+.dart_tool/
+.DS_Store
+.idea
+.pub/
+.settings/
+build/
+packages
+.packages
+pubspec.lock
diff --git a/pkgs/stream_channel/AUTHORS b/pkgs/stream_channel/AUTHORS
new file mode 100644
index 0000000..e8063a8
--- /dev/null
+++ b/pkgs/stream_channel/AUTHORS
@@ -0,0 +1,6 @@
+# Below is a list of people and organizations that have contributed
+# to the project. Names should be added to the list like so:
+#
+# Name/Organization <email address>
+
+Google Inc.
diff --git a/pkgs/stream_channel/CHANGELOG.md b/pkgs/stream_channel/CHANGELOG.md
new file mode 100644
index 0000000..9dd3990
--- /dev/null
+++ b/pkgs/stream_channel/CHANGELOG.md
@@ -0,0 +1,162 @@
+## 2.1.4
+
+* Fix `StreamChannelMixin` so that it can be used as a mixin again.
+
+## 2.1.3
+
+* Require Dart 3.3
+* Move to `dart-lang/tools` monorepo.
+
+## 2.1.2
+
+* Require Dart 2.19
+* Add an example.
+* Fix a race condition in `IsolateChannel.connectReceive()` where the channel
+ could hang forever if its sink was closed before the connection was established.
+
+## 2.1.1
+
+* Require Dart 2.14
+* Populate the pubspec `repository` field.
+* Handle multichannel messages where the ID element is a `double` at runtime
+ instead of an `int`. When reading an array with `dart2wasm` numbers within the
+ array are parsed as `double`.
+
+## 2.1.0
+
+* Stable release for null safety.
+
+## 2.0.0
+
+**Breaking changes**
+
+* `IsolateChannel` requires a separate import
+ `package:stram_channel/isolate_channel.dart`.
+ `package:stream_channel/stream_channel.dart` will now not trigger any platform
+ concerns due to importing `dart:isolate`.
+* Remove `JsonDocumentTransformer` class. The `jsonDocument` top level is still
+ available.
+* Remove `StreamChannelTransformer.typed`. Use `.cast` on the transformed
+ channel instead.
+* Change `Future<dynamic>` returns to `Future<void>`.
+
+## 1.7.0
+
+* Make `IsolateChannel` available through
+ `package:stream_channel/isolate_channel.dart`. This will be the required
+ import in the next release.
+* Require `2.0.0` or newer SDK.
+* Internal style changes.
+
+## 1.6.8
+
+* Set max SDK version to `<3.0.0`, and adjust other dependencies.
+
+## 1.6.7+1
+
+* Fix Dart 2 runtime types in `IsolateChannel`.
+
+## 1.6.7
+
+* Update SDK version to 2.0.0-dev.17.0.
+* Add a type argument to `MultiChannel`.
+
+## 1.6.6
+
+* Fix a Dart 2 issue with inner stream transformation in `GuaranteeChannel`.
+
+* Fix a Dart 2 issue with `StreamChannelTransformer.fromCodec()`.
+
+## 1.6.5
+
+* Fix an issue with `JsonDocumentTransformer.bind` where it created an internal
+ stream channel which didn't get a properly inferred type for its `sink`.
+
+## 1.6.4
+
+* Fix a race condition in `MultiChannel` where messages from a remote virtual
+ channel could get dropped if the corresponding local channel wasn't registered
+ quickly enough.
+
+## 1.6.3
+
+* Use `pumpEventQueue()` from test.
+
+## 1.6.2
+
+* Declare support for `async` 2.0.0.
+
+## 1.6.1
+
+* Fix the type of `StreamChannel.transform()`. This previously inverted the
+ generic parameters, so it only really worked with transformers where both
+ generic types were identical.
+
+## 1.6.0
+
+* `Disconnector.disconnect()` now returns a future that completes when all the
+ inner `StreamSink.close()` futures have completed.
+
+## 1.5.0
+
+* Add `new StreamChannel.withCloseGuarantee()` to provide the specific guarantee
+ that closing the sink causes the stream to close before it emits any more
+ events. This is the only guarantee that isn't automatically preserved when
+ transforming a channel.
+
+* `StreamChannelTransformer`s provided by the `stream_channel` package now
+ properly provide the guarantee that closing the sink causes the stream to
+ close before it emits any more events
+
+## 1.4.0
+
+* Add `StreamChannel.cast()`, which soundly coerces the generic type of a
+ channel.
+
+* Add `StreamChannelTransformer.typed()`, which soundly coerces the generic type
+ of a transformer.
+
+## 1.3.2
+
+* Fix all strong-mode errors and warnings.
+
+## 1.3.1
+
+* Make `IsolateChannel` slightly more efficient.
+
+* Make `MultiChannel` follow the stream channel rules.
+
+## 1.3.0
+
+* Add `Disconnector`, a transformer that allows the caller to disconnect the
+ transformed channel.
+
+## 1.2.0
+
+* Add `new StreamChannel.withGuarantees()`, which creates a channel with extra
+ wrapping to ensure that it obeys the stream channel guarantees.
+
+* Add `StreamChannelController`, which can be used to create custom
+ `StreamChannel` objects.
+
+## 1.1.1
+
+* Fix the type annotation for `StreamChannel.transform()`'s parameter.
+
+## 1.1.0
+
+* Add `StreamChannel.transformStream()`, `StreamChannel.transformSink()`,
+ `StreamChannel.changeStream()`, and `StreamChannel.changeSink()` to support
+ changing only the stream or only the sink of a channel.
+
+* Be more explicit about `JsonDocumentTransformer`'s error-handling behavior.
+
+## 1.0.1
+
+* Fix `MultiChannel`'s constructor to take a `StreamChannel`. This is
+ technically a breaking change, but since 1.0.0 was only released an hour ago,
+ we're treating it as a bug fix.
+
+## 1.0.0
+
+* Initial version
diff --git a/pkgs/stream_channel/LICENSE b/pkgs/stream_channel/LICENSE
new file mode 100644
index 0000000..dbd2843
--- /dev/null
+++ b/pkgs/stream_channel/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2015, 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.
diff --git a/pkgs/stream_channel/README.md b/pkgs/stream_channel/README.md
new file mode 100644
index 0000000..3677ccf
--- /dev/null
+++ b/pkgs/stream_channel/README.md
@@ -0,0 +1,20 @@
+[](https://github.com/dart-lang/tools/actions/workflows/stream_channel.yaml)
+[](https://pub.dev/packages/stream_channel)
+[](https://pub.dev/packages/stream_channel/publisher)
+
+This package exposes the `StreamChannel` interface, which represents a two-way
+communication channel. Each `StreamChannel` exposes a `Stream` for receiving
+data and a `StreamSink` for sending it.
+
+`StreamChannel` helps abstract communication logic away from the underlying
+protocol. For example, the [`test`][test] package re-uses its test suite
+communication protocol for both WebSocket connections to browser suites and
+Isolate connections to VM tests.
+
+[test]: https://pub.dev/packages/test
+
+This package also contains utilities for dealing with `StreamChannel`s and with
+two-way communications in general. For documentation of these utilities, see
+[the API docs][api].
+
+[api]: https://pub.dev/documentation/stream_channel/latest/
diff --git a/pkgs/stream_channel/analysis_options.yaml b/pkgs/stream_channel/analysis_options.yaml
new file mode 100644
index 0000000..44cda4d
--- /dev/null
+++ b/pkgs/stream_channel/analysis_options.yaml
@@ -0,0 +1,5 @@
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-casts: true
diff --git a/pkgs/stream_channel/example/example.dart b/pkgs/stream_channel/example/example.dart
new file mode 100644
index 0000000..b41d8d9
--- /dev/null
+++ b/pkgs/stream_channel/example/example.dart
@@ -0,0 +1,110 @@
+// Copyright (c) 2023, 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';
+import 'dart:isolate';
+
+import 'package:stream_channel/isolate_channel.dart';
+import 'package:stream_channel/stream_channel.dart';
+
+Future<void> main() async {
+ // A StreamChannel<T>, is in simplest terms, a wrapper around a Stream<T> and
+ // a StreamSink<T>. For example, you can create a channel that wraps standard
+ // IO:
+ var stdioChannel = StreamChannel(stdin, stdout);
+ stdioChannel.sink.add('Hello!\n'.codeUnits);
+
+ // Like a Stream<T> can be transformed with a StreamTransformer<T>, a
+ // StreamChannel<T> can be transformed with a StreamChannelTransformer<T>.
+ // For example, we can handle standard input as strings:
+ var stringChannel = stdioChannel
+ .transform(StreamChannelTransformer.fromCodec(utf8))
+ .transformStream(const LineSplitter());
+ stringChannel.sink.add('world!\n');
+
+ // You can implement StreamChannel<T> by extending StreamChannelMixin<T>, but
+ // it's much easier to use a StreamChannelController<T>. A controller has two
+ // StreamChannel<T> members: `local` and `foreign`. The creator of a
+ // controller should work with the `local` channel, while the recipient should
+ // work with the `foreign` channel, and usually will not have direct access to
+ // the underlying controller.
+ var ctrl = StreamChannelController<String>();
+ ctrl.local.stream.listen((event) {
+ // Do something useful here...
+ });
+
+ // You can also pipe events from one channel to another.
+ ctrl
+ ..foreign.pipe(stringChannel)
+ ..local.sink.add('Piped!\n');
+ await ctrl.local.sink.close();
+
+ // The StreamChannel<T> interface provides several guarantees, which can be
+ // found here:
+ // https://pub.dev/documentation/stream_channel/latest/stream_channel/StreamChannel-class.html
+ //
+ // By calling `StreamChannel<T>.withGuarantees()`, you can create a
+ // StreamChannel<T> that provides all guarantees.
+ var dummyCtrl0 = StreamChannelController<String>();
+ var guaranteedChannel = StreamChannel.withGuarantees(
+ dummyCtrl0.foreign.stream, dummyCtrl0.foreign.sink);
+
+ // To close a StreamChannel, use `sink.close()`.
+ await guaranteedChannel.sink.close();
+
+ // A MultiChannel<T> multiplexes multiple virtual channels across a single
+ // underlying transport layer. For example, an application listening over
+ // standard I/O can still support multiple clients if it has a mechanism to
+ // separate events from different clients.
+ //
+ // A MultiChannel<T> splits events into numbered channels, which are
+ // instances of VirtualChannel<T>.
+ var dummyCtrl1 = StreamChannelController<String>();
+ var multiChannel = MultiChannel<String>(dummyCtrl1.foreign);
+ var channel1 = multiChannel.virtualChannel();
+ await multiChannel.sink.close();
+
+ // The client/peer should also create its own MultiChannel<T>, connected to
+ // the underlying transport, use the corresponding ID's to handle events in
+ // their respective channels. It is up to you how to communicate channel ID's
+ // across different endpoints.
+ var dummyCtrl2 = StreamChannelController<String>();
+ var multiChannel2 = MultiChannel<String>(dummyCtrl2.foreign);
+ var channel2 = multiChannel2.virtualChannel(channel1.id);
+ await channel2.sink.close();
+ await multiChannel2.sink.close();
+
+ // Multiple instances of a Dart application can communicate easily across
+ // `SendPort`/`ReceivePort` pairs by means of the `IsolateChannel<T>` class.
+ // Typically, one endpoint will create a `ReceivePort`, and call the
+ // `IsolateChannel.connectReceive` constructor. The other endpoint will be
+ // given the corresponding `SendPort`, and then call
+ // `IsolateChannel.connectSend`.
+ var recv = ReceivePort();
+ var recvChannel = IsolateChannel<void>.connectReceive(recv);
+ var sendChannel = IsolateChannel<void>.connectSend(recv.sendPort);
+
+ // You must manually close `IsolateChannel<T>` sinks, however.
+ await recvChannel.sink.close();
+ await sendChannel.sink.close();
+
+ // You can use the `Disconnector` transformer to cause a channel to act as
+ // though the remote end of its transport had disconnected.
+ var disconnector = Disconnector<String>();
+ var disconnectable = stringChannel.transform(disconnector);
+ disconnectable.sink.add('Still connected!');
+ await disconnector.disconnect();
+
+ // Additionally:
+ // * The `DelegatingStreamController<T>` class can be extended to build a
+ // basis for wrapping other `StreamChannel<T>` objects.
+ // * The `jsonDocument` transformer converts events to/from JSON, using
+ // the `json` codec from `dart:convert`.
+ // * `package:json_rpc_2` directly builds on top of
+ // `package:stream_channel`, so any compatible transport can be used to
+ // create interactive client/server or peer-to-peer applications (i.e.
+ // language servers, microservices, etc.
+}
diff --git a/pkgs/stream_channel/lib/isolate_channel.dart b/pkgs/stream_channel/lib/isolate_channel.dart
new file mode 100644
index 0000000..5d9f6e1
--- /dev/null
+++ b/pkgs/stream_channel/lib/isolate_channel.dart
@@ -0,0 +1,5 @@
+// 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.
+
+export 'src/isolate_channel.dart' show IsolateChannel;
diff --git a/pkgs/stream_channel/lib/src/close_guarantee_channel.dart b/pkgs/stream_channel/lib/src/close_guarantee_channel.dart
new file mode 100644
index 0000000..13432d1
--- /dev/null
+++ b/pkgs/stream_channel/lib/src/close_guarantee_channel.dart
@@ -0,0 +1,91 @@
+// 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.
+
+import 'dart:async';
+
+import 'package:async/async.dart';
+
+import '../stream_channel.dart';
+
+/// A [StreamChannel] that specifically enforces the stream channel guarantee
+/// that closing the sink causes the stream to close before it emits any more
+/// events
+///
+/// This is exposed via [StreamChannel.withCloseGuarantee].
+class CloseGuaranteeChannel<T> extends StreamChannelMixin<T> {
+ @override
+ Stream<T> get stream => _stream;
+ late final _CloseGuaranteeStream<T> _stream;
+
+ @override
+ StreamSink<T> get sink => _sink;
+ late final _CloseGuaranteeSink<T> _sink;
+
+ /// The subscription to the inner stream.
+ StreamSubscription<T>? _subscription;
+
+ /// Whether the sink has closed, causing the underlying channel to disconnect.
+ bool _disconnected = false;
+
+ CloseGuaranteeChannel(Stream<T> innerStream, StreamSink<T> innerSink) {
+ _sink = _CloseGuaranteeSink<T>(innerSink, this);
+ _stream = _CloseGuaranteeStream<T>(innerStream, this);
+ }
+}
+
+/// The stream for [CloseGuaranteeChannel].
+///
+/// This wraps the inner stream to save the subscription on the channel when
+/// [listen] is called.
+class _CloseGuaranteeStream<T> extends Stream<T> {
+ /// The inner stream this is delegating to.
+ final Stream<T> _inner;
+
+ /// The [CloseGuaranteeChannel] this belongs to.
+ final CloseGuaranteeChannel<T> _channel;
+
+ _CloseGuaranteeStream(this._inner, this._channel);
+
+ @override
+ StreamSubscription<T> listen(void Function(T)? onData,
+ {Function? onError, void Function()? onDone, bool? cancelOnError}) {
+ // If the channel is already disconnected, we shouldn't dispatch anything
+ // but a done event.
+ if (_channel._disconnected) {
+ onData = null;
+ onError = null;
+ }
+
+ var subscription = _inner.listen(onData,
+ onError: onError, onDone: onDone, cancelOnError: cancelOnError);
+ if (!_channel._disconnected) {
+ _channel._subscription = subscription;
+ }
+ return subscription;
+ }
+}
+
+/// The sink for [CloseGuaranteeChannel].
+///
+/// This wraps the inner sink to cancel the stream subscription when the sink is
+/// canceled.
+class _CloseGuaranteeSink<T> extends DelegatingStreamSink<T> {
+ /// The [CloseGuaranteeChannel] this belongs to.
+ final CloseGuaranteeChannel<T> _channel;
+
+ _CloseGuaranteeSink(super.inner, this._channel);
+
+ @override
+ Future<void> close() {
+ var done = super.close();
+ _channel._disconnected = true;
+ var subscription = _channel._subscription;
+ if (subscription != null) {
+ // Don't dispatch anything but a done event.
+ subscription.onData(null);
+ subscription.onError(null);
+ }
+ return done;
+ }
+}
diff --git a/pkgs/stream_channel/lib/src/delegating_stream_channel.dart b/pkgs/stream_channel/lib/src/delegating_stream_channel.dart
new file mode 100644
index 0000000..4484a59
--- /dev/null
+++ b/pkgs/stream_channel/lib/src/delegating_stream_channel.dart
@@ -0,0 +1,23 @@
+// 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.
+
+import 'dart:async';
+
+import '../stream_channel.dart';
+
+/// A simple delegating wrapper around [StreamChannel].
+///
+/// Subclasses can override individual methods, or use this to expose only
+/// [StreamChannel] methods.
+class DelegatingStreamChannel<T> extends StreamChannelMixin<T> {
+ /// The inner channel to which methods are forwarded.
+ final StreamChannel<T> _inner;
+
+ @override
+ Stream<T> get stream => _inner.stream;
+ @override
+ StreamSink<T> get sink => _inner.sink;
+
+ DelegatingStreamChannel(this._inner);
+}
diff --git a/pkgs/stream_channel/lib/src/disconnector.dart b/pkgs/stream_channel/lib/src/disconnector.dart
new file mode 100644
index 0000000..3414e9c
--- /dev/null
+++ b/pkgs/stream_channel/lib/src/disconnector.dart
@@ -0,0 +1,153 @@
+// 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.
+
+import 'dart:async';
+
+import 'package:async/async.dart';
+
+import '../stream_channel.dart';
+
+/// Allows the caller to force a channel to disconnect.
+///
+/// When [disconnect] is called, the channel (or channels) transformed by this
+/// transformer will act as though the remote end had disconnected—the stream
+/// will emit a done event, and the sink will ignore future inputs. The inner
+/// sink will also be closed to notify the remote end of the disconnection.
+///
+/// If a channel is transformed after the [disconnect] has been called, it will
+/// be disconnected immediately.
+class Disconnector<T> implements StreamChannelTransformer<T, T> {
+ /// Whether [disconnect] has been called.
+ bool get isDisconnected => _disconnectMemo.hasRun;
+
+ /// The sinks for transformed channels.
+ ///
+ /// Note that we assume that transformed channels provide the stream channel
+ /// guarantees. This allows us to only track sinks, because we know closing
+ /// the underlying sink will cause the stream to emit a done event.
+ final _sinks = <_DisconnectorSink<T>>[];
+
+ /// Disconnects all channels that have been transformed.
+ ///
+ /// Returns a future that completes when all inner sinks' [StreamSink.close]
+ /// futures have completed. Note that a [StreamController]'s sink won't close
+ /// until the corresponding stream has a listener.
+ Future<void> disconnect() => _disconnectMemo.runOnce(() {
+ var futures = _sinks.map((sink) => sink._disconnect()).toList();
+ _sinks.clear();
+ return Future.wait(futures, eagerError: true);
+ });
+ final _disconnectMemo = AsyncMemoizer<List<void>>();
+
+ @override
+ StreamChannel<T> bind(StreamChannel<T> channel) {
+ return channel.changeSink((innerSink) {
+ var sink = _DisconnectorSink<T>(innerSink);
+
+ if (isDisconnected) {
+ // Ignore errors here, because otherwise there would be no way for the
+ // user to handle them gracefully.
+ sink._disconnect().catchError((_) {});
+ } else {
+ _sinks.add(sink);
+ }
+
+ return sink;
+ });
+ }
+}
+
+/// A sink wrapper that can force a disconnection.
+class _DisconnectorSink<T> implements StreamSink<T> {
+ /// The inner sink.
+ final StreamSink<T> _inner;
+
+ @override
+ Future<void> get done => _inner.done;
+
+ /// Whether [Disconnector.disconnect] has been called.
+ var _isDisconnected = false;
+
+ /// Whether the user has called [close].
+ var _closed = false;
+
+ /// The subscription to the stream passed to [addStream], if a stream is
+ /// currently being added.
+ StreamSubscription<T>? _addStreamSubscription;
+
+ /// The completer for the future returned by [addStream], if a stream is
+ /// currently being added.
+ Completer? _addStreamCompleter;
+
+ /// Whether we're currently adding a stream with [addStream].
+ bool get _inAddStream => _addStreamSubscription != null;
+
+ _DisconnectorSink(this._inner);
+
+ @override
+ void add(T data) {
+ if (_closed) throw StateError('Cannot add event after closing.');
+ if (_inAddStream) {
+ throw StateError('Cannot add event while adding stream.');
+ }
+ if (_isDisconnected) return;
+
+ _inner.add(data);
+ }
+
+ @override
+ void addError(Object error, [StackTrace? stackTrace]) {
+ if (_closed) throw StateError('Cannot add event after closing.');
+ if (_inAddStream) {
+ throw StateError('Cannot add event while adding stream.');
+ }
+ if (_isDisconnected) return;
+
+ _inner.addError(error, stackTrace);
+ }
+
+ @override
+ Future<void> addStream(Stream<T> stream) {
+ if (_closed) throw StateError('Cannot add stream after closing.');
+ if (_inAddStream) {
+ throw StateError('Cannot add stream while adding stream.');
+ }
+ if (_isDisconnected) return Future.value();
+
+ _addStreamCompleter = Completer.sync();
+ _addStreamSubscription = stream.listen(_inner.add,
+ onError: _inner.addError, onDone: _addStreamCompleter!.complete);
+ return _addStreamCompleter!.future.then((_) {
+ _addStreamCompleter = null;
+ _addStreamSubscription = null;
+ });
+ }
+
+ @override
+ Future<void> close() {
+ if (_inAddStream) {
+ throw StateError('Cannot close sink while adding stream.');
+ }
+
+ _closed = true;
+ return _inner.close();
+ }
+
+ /// Disconnects this sink.
+ ///
+ /// This closes the underlying sink and stops forwarding events. It returns
+ /// the [StreamSink.close] future for the underlying sink.
+ Future<void> _disconnect() {
+ _isDisconnected = true;
+ var future = _inner.close();
+
+ if (_inAddStream) {
+ _addStreamCompleter!.complete(_addStreamSubscription!.cancel());
+ _addStreamCompleter = null;
+ _addStreamSubscription = null;
+ }
+
+ return future;
+ }
+}
diff --git a/pkgs/stream_channel/lib/src/guarantee_channel.dart b/pkgs/stream_channel/lib/src/guarantee_channel.dart
new file mode 100644
index 0000000..30ebe2e
--- /dev/null
+++ b/pkgs/stream_channel/lib/src/guarantee_channel.dart
@@ -0,0 +1,207 @@
+// 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.
+
+import 'dart:async';
+
+import 'package:async/async.dart';
+
+import '../stream_channel.dart';
+
+/// A [StreamChannel] that enforces the stream channel guarantees.
+///
+/// This is exposed via [StreamChannel.withGuarantees].
+class GuaranteeChannel<T> extends StreamChannelMixin<T> {
+ @override
+ Stream<T> get stream => _streamController.stream;
+
+ @override
+ StreamSink<T> get sink => _sink;
+ late final _GuaranteeSink<T> _sink;
+
+ /// The controller for [stream].
+ ///
+ /// This intermediate controller allows us to continue listening for a done
+ /// event even after the user has canceled their subscription, and to send our
+ /// own done event when the sink is closed.
+ late final StreamController<T> _streamController;
+
+ /// The subscription to the inner stream.
+ StreamSubscription<T>? _subscription;
+
+ /// Whether the sink has closed, causing the underlying channel to disconnect.
+ bool _disconnected = false;
+
+ GuaranteeChannel(Stream<T> innerStream, StreamSink<T> innerSink,
+ {bool allowSinkErrors = true}) {
+ _sink = _GuaranteeSink<T>(innerSink, this, allowErrors: allowSinkErrors);
+
+ // Enforce the single-subscription guarantee by changing a broadcast stream
+ // to single-subscription.
+ if (innerStream.isBroadcast) {
+ innerStream =
+ innerStream.transform(SingleSubscriptionTransformer<T, T>());
+ }
+
+ _streamController = StreamController<T>(
+ onListen: () {
+ // If the sink has disconnected, we've already called
+ // [_streamController.close].
+ if (_disconnected) return;
+
+ _subscription = innerStream.listen(_streamController.add,
+ onError: _streamController.addError, onDone: () {
+ _sink._onStreamDisconnected();
+ _streamController.close();
+ });
+ },
+ sync: true);
+ }
+
+ /// Called by [_GuaranteeSink] when the user closes it.
+ ///
+ /// The sink closing indicates that the connection is closed, so the stream
+ /// should stop emitting events.
+ void _onSinkDisconnected() {
+ _disconnected = true;
+ var subscription = _subscription;
+ if (subscription != null) subscription.cancel();
+ _streamController.close();
+ }
+}
+
+/// The sink for [GuaranteeChannel].
+///
+/// This wraps the inner sink to ignore events and cancel any in-progress
+/// [addStream] calls when the underlying channel closes.
+class _GuaranteeSink<T> implements StreamSink<T> {
+ /// The inner sink being wrapped.
+ final StreamSink<T> _inner;
+
+ /// The [GuaranteeChannel] this belongs to.
+ final GuaranteeChannel<T> _channel;
+
+ @override
+ Future<void> get done => _doneCompleter.future;
+ final _doneCompleter = Completer<void>();
+
+ /// Whether connection is disconnected.
+ ///
+ /// This can happen because the stream has emitted a done event, or because
+ /// the user added an error when [_allowErrors] is `false`.
+ bool _disconnected = false;
+
+ /// Whether the user has called [close].
+ bool _closed = false;
+
+ /// The subscription to the stream passed to [addStream], if a stream is
+ /// currently being added.
+ StreamSubscription<T>? _addStreamSubscription;
+
+ /// The completer for the future returned by [addStream], if a stream is
+ /// currently being added.
+ Completer? _addStreamCompleter;
+
+ /// Whether we're currently adding a stream with [addStream].
+ bool get _inAddStream => _addStreamSubscription != null;
+
+ /// Whether errors are passed on to the underlying sink.
+ ///
+ /// If this is `false`, any error passed to the sink is piped to [done] and
+ /// the underlying sink is closed.
+ final bool _allowErrors;
+
+ _GuaranteeSink(this._inner, this._channel, {bool allowErrors = true})
+ : _allowErrors = allowErrors;
+
+ @override
+ void add(T data) {
+ if (_closed) throw StateError('Cannot add event after closing.');
+ if (_inAddStream) {
+ throw StateError('Cannot add event while adding stream.');
+ }
+ if (_disconnected) return;
+
+ _inner.add(data);
+ }
+
+ @override
+ void addError(Object error, [StackTrace? stackTrace]) {
+ if (_closed) throw StateError('Cannot add event after closing.');
+ if (_inAddStream) {
+ throw StateError('Cannot add event while adding stream.');
+ }
+ if (_disconnected) return;
+
+ _addError(error, stackTrace);
+ }
+
+ /// Like [addError], but doesn't check to ensure that an error can be added.
+ ///
+ /// This is called from [addStream], so it shouldn't fail if a stream is being
+ /// added.
+ void _addError(Object error, [StackTrace? stackTrace]) {
+ if (_allowErrors) {
+ _inner.addError(error, stackTrace);
+ return;
+ }
+
+ _doneCompleter.completeError(error, stackTrace);
+
+ // Treat an error like both the stream and sink disconnecting.
+ _onStreamDisconnected();
+ _channel._onSinkDisconnected();
+
+ // Ignore errors from the inner sink. We're already surfacing one error, and
+ // if the user handles it we don't want them to have another top-level.
+ _inner.close().catchError((_) {});
+ }
+
+ @override
+ Future<void> addStream(Stream<T> stream) {
+ if (_closed) throw StateError('Cannot add stream after closing.');
+ if (_inAddStream) {
+ throw StateError('Cannot add stream while adding stream.');
+ }
+ if (_disconnected) return Future.value();
+
+ _addStreamCompleter = Completer.sync();
+ _addStreamSubscription = stream.listen(_inner.add,
+ onError: _addError, onDone: _addStreamCompleter!.complete);
+ return _addStreamCompleter!.future.then((_) {
+ _addStreamCompleter = null;
+ _addStreamSubscription = null;
+ });
+ }
+
+ @override
+ Future<void> close() {
+ if (_inAddStream) {
+ throw StateError('Cannot close sink while adding stream.');
+ }
+
+ if (_closed) return done;
+ _closed = true;
+
+ if (!_disconnected) {
+ _channel._onSinkDisconnected();
+ _doneCompleter.complete(_inner.close());
+ }
+
+ return done;
+ }
+
+ /// Called by [GuaranteeChannel] when the stream emits a done event.
+ ///
+ /// The stream being done indicates that the connection is closed, so the
+ /// sink should stop forwarding events.
+ void _onStreamDisconnected() {
+ _disconnected = true;
+ if (!_doneCompleter.isCompleted) _doneCompleter.complete();
+
+ if (!_inAddStream) return;
+ _addStreamCompleter!.complete(_addStreamSubscription!.cancel());
+ _addStreamCompleter = null;
+ _addStreamSubscription = null;
+ }
+}
diff --git a/pkgs/stream_channel/lib/src/isolate_channel.dart b/pkgs/stream_channel/lib/src/isolate_channel.dart
new file mode 100644
index 0000000..15c68a4
--- /dev/null
+++ b/pkgs/stream_channel/lib/src/isolate_channel.dart
@@ -0,0 +1,115 @@
+// 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.
+
+import 'dart:async';
+import 'dart:isolate';
+
+import 'package:async/async.dart';
+
+import '../stream_channel.dart';
+
+/// A [StreamChannel] that communicates over a [ReceivePort]/[SendPort] pair,
+/// presumably with another isolate.
+///
+/// The remote endpoint doesn't necessarily need to be running an
+/// [IsolateChannel]. This can be used with any two ports, although the
+/// [StreamChannel] semantics mean that this class will treat them as being
+/// paired (for example, closing the [sink] will cause the [stream] to stop
+/// emitting events).
+///
+/// The underlying isolate ports have no notion of closing connections. This
+/// means that [stream] won't close unless [sink] is closed, and that closing
+/// [sink] won't cause the remote endpoint to close. Users should take care to
+/// ensure that they always close the [sink] of every [IsolateChannel] they use
+/// to avoid leaving dangling [ReceivePort]s.
+class IsolateChannel<T> extends StreamChannelMixin<T> {
+ @override
+ final Stream<T> stream;
+ @override
+ final StreamSink<T> sink;
+
+ /// Connects to a remote channel that was created with
+ /// [IsolateChannel.connectSend].
+ ///
+ /// These constructors establish a connection using only a single
+ /// [SendPort]/[ReceivePort] pair, as long as each side uses one of the
+ /// connect constructors.
+ ///
+ /// The connection protocol is guaranteed to remain compatible across versions
+ /// at least until the next major version release. If the protocol is
+ /// violated, the resulting channel will emit a single value on its stream and
+ /// then close.
+ factory IsolateChannel.connectReceive(ReceivePort receivePort) {
+ // We can't use a [StreamChannelCompleter] here because we need the return
+ // value to be an [IsolateChannel].
+ var isCompleted = false;
+ var streamCompleter = StreamCompleter<T>();
+ var sinkCompleter = StreamSinkCompleter<T>();
+
+ var channel = IsolateChannel<T>._(streamCompleter.stream, sinkCompleter.sink
+ .transform(StreamSinkTransformer.fromHandlers(handleDone: (sink) {
+ if (!isCompleted) {
+ receivePort.close();
+ streamCompleter.setSourceStream(const Stream.empty());
+ sinkCompleter.setDestinationSink(NullStreamSink<T>());
+ }
+ sink.close();
+ })));
+
+ // The first message across the ReceivePort should be a SendPort pointing to
+ // the remote end. If it's not, we'll make the stream emit an error
+ // complaining.
+ late StreamSubscription<dynamic> subscription;
+ subscription = receivePort.listen((message) {
+ isCompleted = true;
+ if (message is SendPort) {
+ var controller =
+ StreamChannelController<T>(allowForeignErrors: false, sync: true);
+ SubscriptionStream(subscription).cast<T>().pipe(controller.local.sink);
+ controller.local.stream
+ .listen((data) => message.send(data), onDone: receivePort.close);
+
+ streamCompleter.setSourceStream(controller.foreign.stream);
+ sinkCompleter.setDestinationSink(controller.foreign.sink);
+ return;
+ }
+
+ streamCompleter.setError(
+ StateError('Unexpected Isolate response "$message".'),
+ StackTrace.current);
+ sinkCompleter.setDestinationSink(NullStreamSink<T>());
+ subscription.cancel();
+ });
+
+ return channel;
+ }
+
+ /// Connects to a remote channel that was created with
+ /// [IsolateChannel.connectReceive].
+ ///
+ /// These constructors establish a connection using only a single
+ /// [SendPort]/[ReceivePort] pair, as long as each side uses one of the
+ /// connect constructors.
+ ///
+ /// The connection protocol is guaranteed to remain compatible across versions
+ /// at least until the next major version release.
+ factory IsolateChannel.connectSend(SendPort sendPort) {
+ var receivePort = ReceivePort();
+ sendPort.send(receivePort.sendPort);
+ return IsolateChannel(receivePort, sendPort);
+ }
+
+ /// Creates a stream channel that receives messages from [receivePort] and
+ /// sends them over [sendPort].
+ factory IsolateChannel(ReceivePort receivePort, SendPort sendPort) {
+ var controller =
+ StreamChannelController<T>(allowForeignErrors: false, sync: true);
+ receivePort.cast<T>().pipe(controller.local.sink);
+ controller.local.stream
+ .listen((data) => sendPort.send(data), onDone: receivePort.close);
+ return IsolateChannel._(controller.foreign.stream, controller.foreign.sink);
+ }
+
+ IsolateChannel._(this.stream, this.sink);
+}
diff --git a/pkgs/stream_channel/lib/src/json_document_transformer.dart b/pkgs/stream_channel/lib/src/json_document_transformer.dart
new file mode 100644
index 0000000..3feda43
--- /dev/null
+++ b/pkgs/stream_channel/lib/src/json_document_transformer.dart
@@ -0,0 +1,35 @@
+// 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.
+
+import 'dart:convert';
+
+import 'package:async/async.dart';
+
+import '../stream_channel.dart';
+
+/// A [StreamChannelTransformer] that transforms JSON documents—strings that
+/// contain individual objects encoded as JSON—into decoded Dart objects.
+///
+/// This decodes JSON that's emitted by the transformed channel's stream, and
+/// encodes objects so that JSON is passed to the transformed channel's sink.
+///
+/// If the transformed channel emits invalid JSON, this emits a
+/// [FormatException]. If an unencodable object is added to the sink, it
+/// synchronously throws a [JsonUnsupportedObjectError].
+final StreamChannelTransformer<Object?, String> jsonDocument =
+ const _JsonDocument();
+
+class _JsonDocument implements StreamChannelTransformer<Object?, String> {
+ const _JsonDocument();
+
+ @override
+ StreamChannel<Object?> bind(StreamChannel<String> channel) {
+ var stream = channel.stream.map(jsonDecode);
+ var sink = StreamSinkTransformer<Object, String>.fromHandlers(
+ handleData: (data, sink) {
+ sink.add(jsonEncode(data));
+ }).bind(channel.sink);
+ return StreamChannel.withCloseGuarantee(stream, sink);
+ }
+}
diff --git a/pkgs/stream_channel/lib/src/multi_channel.dart b/pkgs/stream_channel/lib/src/multi_channel.dart
new file mode 100644
index 0000000..4894239
--- /dev/null
+++ b/pkgs/stream_channel/lib/src/multi_channel.dart
@@ -0,0 +1,274 @@
+// 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.
+
+import 'dart:async';
+
+import 'package:async/async.dart';
+
+import '../stream_channel.dart';
+
+/// A class that multiplexes multiple virtual channels across a single
+/// underlying transport layer.
+///
+/// This should be connected to another [MultiChannel] on the other end of the
+/// underlying channel. It starts with a single default virtual channel,
+/// accessible via [stream] and [sink]. Additional virtual channels can be
+/// created with [virtualChannel].
+///
+/// When a virtual channel is created by one endpoint, the other must connect to
+/// it before messages may be sent through it. The first endpoint passes its
+/// [VirtualChannel.id] to the second, which then creates a channel from that id
+/// also using [virtualChannel]. For example:
+///
+/// ```dart
+/// // First endpoint
+/// var virtual = multiChannel.virtualChannel();
+/// multiChannel.sink.add({
+/// "channel": virtual.id
+/// });
+///
+/// // Second endpoint
+/// multiChannel.stream.listen((message) {
+/// var virtual = multiChannel.virtualChannel(message["channel"]);
+/// // ...
+/// });
+/// ```
+///
+/// Sending errors across a [MultiChannel] is not supported. Any errors from the
+/// underlying stream will be reported only via the default
+/// [MultiChannel.stream].
+///
+/// Each virtual channel may be closed individually. When all of them are
+/// closed, the underlying [StreamSink] is closed automatically.
+abstract class MultiChannel<T> implements StreamChannel<T> {
+ /// The default input stream.
+ ///
+ /// This connects to the remote [sink].
+ @override
+ Stream<T> get stream;
+
+ /// The default output stream.
+ ///
+ /// This connects to the remote [stream]. If this is closed, the remote
+ /// [stream] will close, but other virtual channels will remain open and new
+ /// virtual channels may be opened.
+ @override
+ StreamSink<T> get sink;
+
+ /// Creates a new [MultiChannel] that sends and receives messages over
+ /// [inner].
+ ///
+ /// The inner channel must take JSON-like objects.
+ factory MultiChannel(StreamChannel<dynamic> inner) => _MultiChannel<T>(inner);
+
+ /// Creates a new virtual channel.
+ ///
+ /// If [id] is not passed, this creates a virtual channel from scratch. Before
+ /// it's used, its [VirtualChannel.id] must be sent to the remote endpoint
+ /// where [virtualChannel] should be called with that id.
+ ///
+ /// If [id] is passed, this creates a virtual channel corresponding to the
+ /// channel with that id on the remote channel.
+ ///
+ /// Throws an [ArgumentError] if a virtual channel already exists for [id].
+ /// Throws a [StateError] if the underlying channel is closed.
+ VirtualChannel<T> virtualChannel([int? id]);
+}
+
+/// The implementation of [MultiChannel].
+///
+/// This is private so that [VirtualChannel] can inherit from [MultiChannel]
+/// without having to implement all the private members.
+class _MultiChannel<T> extends StreamChannelMixin<T>
+ implements MultiChannel<T> {
+ /// The inner channel over which all communication is conducted.
+ ///
+ /// This will be `null` if the underlying communication channel is closed.
+ StreamChannel<dynamic>? _inner;
+
+ /// The subscription to [_inner].stream.
+ StreamSubscription<dynamic>? _innerStreamSubscription;
+
+ @override
+ Stream<T> get stream => _mainController.foreign.stream;
+ @override
+ StreamSink<T> get sink => _mainController.foreign.sink;
+
+ /// The controller for this channel.
+ final _mainController = StreamChannelController<T>(sync: true);
+
+ /// A map from input IDs to [StreamChannelController]s that should be used to
+ /// communicate over those channels.
+ final _controllers = <int, StreamChannelController<T>>{};
+
+ /// Input IDs of controllers in [_controllers] that we've received messages
+ /// for but that have not yet had a local [virtualChannel] created.
+ final _pendingIds = <int>{};
+
+ /// Input IDs of virtual channels that used to exist but have since been
+ /// closed.
+ final _closedIds = <int>{};
+
+ /// The next id to use for a local virtual channel.
+ ///
+ /// Ids are used to identify virtual channels. Each message is tagged with an
+ /// id; the receiving [MultiChannel] uses this id to look up which
+ /// [VirtualChannel] the message should be dispatched to.
+ ///
+ /// The id scheme for virtual channels is somewhat complicated. This is
+ /// necessary to ensure that there are no conflicts even when both endpoints
+ /// have virtual channels with the same id; since both endpoints can send and
+ /// receive messages across each virtual channel, a naïve scheme would make it
+ /// impossible to tell whether a message was from a channel that originated in
+ /// the remote endpoint or a reply on a channel that originated in the local
+ /// endpoint.
+ ///
+ /// The trick is that each endpoint only uses odd ids for its own channels.
+ /// When sending a message over a channel that was created by the remote
+ /// endpoint, the channel's id plus one is used. This way each [MultiChannel]
+ /// knows that if an incoming message has an odd id, it's coming from a
+ /// channel that was originally created remotely, but if it has an even id,
+ /// it's coming from a channel that was originally created locally.
+ var _nextId = 1;
+
+ _MultiChannel(StreamChannel<dynamic> inner) : _inner = inner {
+ // The default connection is a special case which has id 0 on both ends.
+ // This allows it to begin connected without having to send over an id.
+ _controllers[0] = _mainController;
+ _mainController.local.stream.listen(
+ (message) => _inner!.sink.add(<Object?>[0, message]),
+ onDone: () => _closeChannel(0, 0));
+
+ _innerStreamSubscription = _inner!.stream.cast<List>().listen((message) {
+ var id = (message[0] as num).toInt();
+
+ // If the channel was closed before an incoming message was processed,
+ // ignore that message.
+ if (_closedIds.contains(id)) return;
+
+ var controller = _controllers.putIfAbsent(id, () {
+ // If we receive a message for a controller that doesn't have a local
+ // counterpart yet, create a controller for it to buffer incoming
+ // messages for when a local connection is created.
+ _pendingIds.add(id);
+ return StreamChannelController(sync: true);
+ });
+
+ if (message.length > 1) {
+ controller.local.sink.add(message[1] as T);
+ } else {
+ // A message without data indicates that the channel has been closed. We
+ // can just close the sink here without doing any more cleanup, because
+ // the sink closing will cause the stream to emit a done event which
+ // will trigger more cleanup.
+ controller.local.sink.close();
+ }
+ },
+ onDone: _closeInnerChannel,
+ onError: _mainController.local.sink.addError);
+ }
+
+ @override
+ VirtualChannel<T> virtualChannel([int? id]) {
+ int inputId;
+ int outputId;
+ if (id != null) {
+ // Since the user is passing in an id, we're connected to a remote
+ // VirtualChannel. This means messages they send over this channel will
+ // have the original odd id, but our replies will have an even id.
+ inputId = id;
+ outputId = id + 1;
+ } else {
+ // Since we're generating an id, we originated this VirtualChannel. This
+ // means messages we send over this channel will have the original odd id,
+ // but the remote channel's replies will have an even id.
+ inputId = _nextId + 1;
+ outputId = _nextId;
+ _nextId += 2;
+ }
+
+ // If the inner channel has already closed, create new virtual channels in a
+ // closed state.
+ if (_inner == null) {
+ return VirtualChannel._(
+ this, inputId, const Stream.empty(), NullStreamSink());
+ }
+
+ late StreamChannelController<T> controller;
+ if (_pendingIds.remove(inputId)) {
+ // If we've already received messages for this channel, use the controller
+ // where those messages are buffered.
+ controller = _controllers[inputId]!;
+ } else if (_controllers.containsKey(inputId) ||
+ _closedIds.contains(inputId)) {
+ throw ArgumentError('A virtual channel with id $id already exists.');
+ } else {
+ controller = StreamChannelController(sync: true);
+ _controllers[inputId] = controller;
+ }
+
+ controller.local.stream.listen(
+ (message) => _inner!.sink.add(<Object?>[outputId, message]),
+ onDone: () => _closeChannel(inputId, outputId));
+ return VirtualChannel._(
+ this, outputId, controller.foreign.stream, controller.foreign.sink);
+ }
+
+ /// Closes the virtual channel for which incoming messages have [inputId] and
+ /// outgoing messages have [outputId].
+ void _closeChannel(int inputId, int outputId) {
+ _closedIds.add(inputId);
+ var controller = _controllers.remove(inputId)!;
+ controller.local.sink.close();
+
+ if (_inner == null) return;
+
+ // A message without data indicates that the virtual channel has been
+ // closed.
+ _inner!.sink.add([outputId]);
+ if (_controllers.isEmpty) _closeInnerChannel();
+ }
+
+ /// Closes the underlying communication channel.
+ void _closeInnerChannel() {
+ _inner!.sink.close();
+ _innerStreamSubscription!.cancel();
+ _inner = null;
+
+ // Convert this to a list because the close is dispatched synchronously, and
+ // that could conceivably remove a controller from [_controllers].
+ for (var controller in _controllers.values.toList(growable: false)) {
+ controller.local.sink.close();
+ }
+ _controllers.clear();
+ }
+}
+
+/// A virtual channel created by [MultiChannel].
+///
+/// This implements [MultiChannel] for convenience.
+/// [VirtualChannel.virtualChannel] is semantically identical to the parent's
+/// [MultiChannel.virtualChannel].
+class VirtualChannel<T> extends StreamChannelMixin<T>
+ implements MultiChannel<T> {
+ /// The [MultiChannel] that created this.
+ final MultiChannel<T> _parent;
+
+ /// The identifier for this channel.
+ ///
+ /// This can be sent across the [MultiChannel] to provide the remote endpoint
+ /// a means to connect to this channel. Nothing about this is guaranteed
+ /// except that it will be JSON-serializable.
+ final int id;
+
+ @override
+ final Stream<T> stream;
+ @override
+ final StreamSink<T> sink;
+
+ VirtualChannel._(this._parent, this.id, this.stream, this.sink);
+
+ @override
+ VirtualChannel<T> virtualChannel([int? id]) => _parent.virtualChannel(id);
+}
diff --git a/pkgs/stream_channel/lib/src/stream_channel_completer.dart b/pkgs/stream_channel/lib/src/stream_channel_completer.dart
new file mode 100644
index 0000000..9d007eb
--- /dev/null
+++ b/pkgs/stream_channel/lib/src/stream_channel_completer.dart
@@ -0,0 +1,74 @@
+// 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.
+
+import 'package:async/async.dart';
+
+import '../stream_channel.dart';
+
+/// A [channel] where the source and destination are provided later.
+///
+/// The [channel] is a normal channel that can be listened to and that events
+/// can be added to immediately, but until [setChannel] is called it won't emit
+/// any events and all events added to it will be buffered.
+class StreamChannelCompleter<T> {
+ /// The completer for this channel's stream.
+ final _streamCompleter = StreamCompleter<T>();
+
+ /// The completer for this channel's sink.
+ final _sinkCompleter = StreamSinkCompleter<T>();
+
+ /// The channel for this completer.
+ StreamChannel<T> get channel => _channel;
+ late final StreamChannel<T> _channel;
+
+ /// Whether [setChannel] has been called.
+ bool _set = false;
+
+ /// Convert a `Future<StreamChannel>` to a `StreamChannel`.
+ ///
+ /// This creates a channel using a channel completer, and sets the source
+ /// channel to the result of the future when the future completes.
+ ///
+ /// If the future completes with an error, the returned channel's stream will
+ /// instead contain just that error. The sink will silently discard all
+ /// events.
+ static StreamChannel fromFuture(Future<StreamChannel> channelFuture) {
+ var completer = StreamChannelCompleter<void>();
+ channelFuture.then(completer.setChannel, onError: completer.setError);
+ return completer.channel;
+ }
+
+ StreamChannelCompleter() {
+ _channel = StreamChannel<T>(_streamCompleter.stream, _sinkCompleter.sink);
+ }
+
+ /// Set a channel as the source and destination for [channel].
+ ///
+ /// A channel may be set at most once.
+ ///
+ /// Either [setChannel] or [setError] may be called at most once. Trying to
+ /// call either of them again will fail.
+ void setChannel(StreamChannel<T> channel) {
+ if (_set) throw StateError('The channel has already been set.');
+ _set = true;
+
+ _streamCompleter.setSourceStream(channel.stream);
+ _sinkCompleter.setDestinationSink(channel.sink);
+ }
+
+ /// Indicates that there was an error connecting the channel.
+ ///
+ /// This makes the stream emit [error] and close. It makes the sink discard
+ /// all its events.
+ ///
+ /// Either [setChannel] or [setError] may be called at most once. Trying to
+ /// call either of them again will fail.
+ void setError(Object error, [StackTrace? stackTrace]) {
+ if (_set) throw StateError('The channel has already been set.');
+ _set = true;
+
+ _streamCompleter.setError(error, stackTrace);
+ _sinkCompleter.setDestinationSink(NullStreamSink());
+ }
+}
diff --git a/pkgs/stream_channel/lib/src/stream_channel_controller.dart b/pkgs/stream_channel/lib/src/stream_channel_controller.dart
new file mode 100644
index 0000000..25d5239
--- /dev/null
+++ b/pkgs/stream_channel/lib/src/stream_channel_controller.dart
@@ -0,0 +1,67 @@
+// 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.
+
+/// @docImport 'isolate_channel.dart';
+library;
+
+import 'dart:async';
+
+import '../stream_channel.dart';
+
+/// A controller for exposing a new [StreamChannel].
+///
+/// This exposes two connected [StreamChannel]s, [local] and [foreign]. The
+/// user's code should use [local] to emit and receive events. Then [foreign]
+/// can be returned for others to use. For example, here's a simplified version
+/// of the implementation of [IsolateChannel.new]:
+///
+/// ```dart
+/// StreamChannel isolateChannel(ReceivePort receivePort, SendPort sendPort) {
+/// var controller = new StreamChannelController(allowForeignErrors: false);
+///
+/// // Pipe all events from the receive port into the local sink...
+/// receivePort.pipe(controller.local.sink);
+///
+/// // ...and all events from the local stream into the send port.
+/// controller.local.stream.listen(sendPort.send, onDone: receivePort.close);
+///
+/// // Then return the foreign controller for your users to use.
+/// return controller.foreign;
+/// }
+/// ```
+class StreamChannelController<T> {
+ /// The local channel.
+ ///
+ /// This channel should be used directly by the creator of this
+ /// [StreamChannelController] to send and receive events.
+ StreamChannel<T> get local => _local;
+ late final StreamChannel<T> _local;
+
+ /// The foreign channel.
+ ///
+ /// This channel should be returned to external users so they can communicate
+ /// with [local].
+ StreamChannel<T> get foreign => _foreign;
+ late final StreamChannel<T> _foreign;
+
+ /// Creates a [StreamChannelController].
+ ///
+ /// If [sync] is true, events added to either channel's sink are synchronously
+ /// dispatched to the other channel's stream. This should only be done if the
+ /// source of those events is already asynchronous.
+ ///
+ /// If [allowForeignErrors] is `false`, errors are not allowed to be passed to
+ /// the foreign channel's sink. If any are, the connection will close and the
+ /// error will be forwarded to the foreign channel's [StreamSink.done] future.
+ /// This guarantees that the local stream will never emit errors.
+ StreamChannelController({bool allowForeignErrors = true, bool sync = false}) {
+ var localToForeignController = StreamController<T>(sync: sync);
+ var foreignToLocalController = StreamController<T>(sync: sync);
+ _local = StreamChannel<T>.withGuarantees(
+ foreignToLocalController.stream, localToForeignController.sink);
+ _foreign = StreamChannel<T>.withGuarantees(
+ localToForeignController.stream, foreignToLocalController.sink,
+ allowSinkErrors: allowForeignErrors);
+ }
+}
diff --git a/pkgs/stream_channel/lib/src/stream_channel_transformer.dart b/pkgs/stream_channel/lib/src/stream_channel_transformer.dart
new file mode 100644
index 0000000..cf62c76
--- /dev/null
+++ b/pkgs/stream_channel/lib/src/stream_channel_transformer.dart
@@ -0,0 +1,58 @@
+// 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.
+
+import 'dart:async';
+import 'dart:convert';
+
+import 'package:async/async.dart';
+
+import '../stream_channel.dart';
+
+/// A [StreamChannelTransformer] transforms the events being passed to and
+/// emitted by a [StreamChannel].
+///
+/// This works on the same principle as [StreamTransformer] and
+/// [StreamSinkTransformer]. Each transformer defines a [bind] method that takes
+/// in the original [StreamChannel] and returns the transformed version.
+///
+/// Transformers must be able to have [bind] called multiple times. If a
+/// subclass implements [bind] explicitly, it should be sure that the returned
+/// stream follows the second stream channel guarantee: closing the sink causes
+/// the stream to close before it emits any more events. This guarantee is
+/// invalidated when an asynchronous gap is added between the original stream's
+/// event dispatch and the returned stream's, for example by transforming it
+/// with a [StreamTransformer]. The guarantee can be easily preserved using
+/// [StreamChannel.withCloseGuarantee].
+class StreamChannelTransformer<S, T> {
+ /// The transformer to use on the channel's stream.
+ final StreamTransformer<T, S> _streamTransformer;
+
+ /// The transformer to use on the channel's sink.
+ final StreamSinkTransformer<S, T> _sinkTransformer;
+
+ /// Creates a [StreamChannelTransformer] from existing stream and sink
+ /// transformers.
+ const StreamChannelTransformer(
+ this._streamTransformer, this._sinkTransformer);
+
+ /// Creates a [StreamChannelTransformer] from a codec's encoder and decoder.
+ ///
+ /// All input to the inner channel's sink is encoded using [Codec.encoder],
+ /// and all output from its stream is decoded using [Codec.decoder].
+ StreamChannelTransformer.fromCodec(Codec<S, T> codec)
+ : this(codec.decoder,
+ StreamSinkTransformer.fromStreamTransformer(codec.encoder));
+
+ /// Transforms the events sent to and emitted by [channel].
+ ///
+ /// Creates a new channel. When events are passed to the returned channel's
+ /// sink, the transformer will transform them and pass the transformed
+ /// versions to `channel.sink`. When events are emitted from the
+ /// `channel.straem`, the transformer will transform them and pass the
+ /// transformed versions to the returned channel's stream.
+ StreamChannel<S> bind(StreamChannel<T> channel) =>
+ StreamChannel<S>.withCloseGuarantee(
+ channel.stream.transform(_streamTransformer),
+ _sinkTransformer.bind(channel.sink));
+}
diff --git a/pkgs/stream_channel/lib/stream_channel.dart b/pkgs/stream_channel/lib/stream_channel.dart
new file mode 100644
index 0000000..8e53096
--- /dev/null
+++ b/pkgs/stream_channel/lib/stream_channel.dart
@@ -0,0 +1,181 @@
+// 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.
+
+import 'dart:async';
+
+import 'package:async/async.dart';
+
+import 'src/close_guarantee_channel.dart';
+import 'src/guarantee_channel.dart';
+import 'src/stream_channel_transformer.dart';
+
+export 'src/delegating_stream_channel.dart';
+export 'src/disconnector.dart';
+export 'src/json_document_transformer.dart';
+export 'src/multi_channel.dart';
+export 'src/stream_channel_completer.dart';
+export 'src/stream_channel_controller.dart';
+export 'src/stream_channel_transformer.dart';
+
+/// An abstract class representing a two-way communication channel.
+///
+/// Users should consider the [stream] emitting a "done" event to be the
+/// canonical indicator that the channel has closed. If they wish to close the
+/// channel, they should close the [sink]—canceling the stream subscription is
+/// not sufficient. Protocol errors may be emitted through the stream or through
+/// [sink].done, depending on their underlying cause. Note that the sink may
+/// silently drop events if the channel closes before [sink].close is called.
+///
+/// Implementations are strongly encouraged to mix in or extend
+/// [StreamChannelMixin] to get default implementations of the various instance
+/// methods. Adding new methods to this interface will not be considered a
+/// breaking change if implementations are also added to [StreamChannelMixin].
+///
+/// Implementations must provide the following guarantees:
+///
+/// * The stream is single-subscription, and must follow all the guarantees of
+/// single-subscription streams.
+///
+/// * Closing the sink causes the stream to close before it emits any more
+/// events.
+///
+/// * After the stream closes, the sink is automatically closed. If this
+/// happens, sink methods should silently drop their arguments until
+/// [sink].close is called.
+///
+/// * If the stream closes before it has a listener, the sink should silently
+/// drop events if possible.
+///
+/// * Canceling the stream's subscription has no effect on the sink. The channel
+/// must still be able to respond to the other endpoint closing the channel
+/// even after the subscription has been canceled.
+///
+/// * The sink *either* forwards errors to the other endpoint *or* closes as
+/// soon as an error is added and forwards that error to the [sink].done
+/// future.
+///
+/// These guarantees allow users to interact uniformly with all implementations,
+/// and ensure that either endpoint closing the stream produces consistent
+/// behavior.
+abstract class StreamChannel<T> {
+ /// The single-subscription stream that emits values from the other endpoint.
+ Stream<T> get stream;
+
+ /// The sink for sending values to the other endpoint.
+ StreamSink<T> get sink;
+
+ /// Creates a new [StreamChannel] that communicates over [stream] and [sink].
+ ///
+ /// Note that this stream/sink pair must provide the guarantees listed in the
+ /// [StreamChannel] documentation. If they don't do so natively,
+ /// [StreamChannel.withGuarantees] should be used instead.
+ factory StreamChannel(Stream<T> stream, StreamSink<T> sink) =>
+ _StreamChannel<T>(stream, sink);
+
+ /// Creates a new [StreamChannel] that communicates over [stream] and [sink].
+ ///
+ /// Unlike [StreamChannel.new], this enforces the guarantees listed in the
+ /// [StreamChannel] documentation. This makes it somewhat less efficient than
+ /// just wrapping a stream and a sink directly, so [StreamChannel.new] should
+ /// be used when the guarantees are provided natively.
+ ///
+ /// If [allowSinkErrors] is `false`, errors are not allowed to be passed to
+ /// [sink]. If any are, the connection will close and the error will be
+ /// forwarded to [sink].done.
+ factory StreamChannel.withGuarantees(Stream<T> stream, StreamSink<T> sink,
+ {bool allowSinkErrors = true}) =>
+ GuaranteeChannel(stream, sink, allowSinkErrors: allowSinkErrors);
+
+ /// Creates a new [StreamChannel] that communicates over [stream] and [sink].
+ ///
+ /// This specifically enforces the second guarantee: closing the sink causes
+ /// the stream to close before it emits any more events. This guarantee is
+ /// invalidated when an asynchronous gap is added between the original
+ /// stream's event dispatch and the returned stream's, for example by
+ /// transforming it with a [StreamTransformer]. This is a lighter-weight way
+ /// of preserving that guarantee in particular than
+ /// [StreamChannel.withGuarantees].
+ factory StreamChannel.withCloseGuarantee(
+ Stream<T> stream, StreamSink<T> sink) =>
+ CloseGuaranteeChannel(stream, sink);
+
+ /// Connects this to [other], so that any values emitted by either are sent
+ /// directly to the other.
+ void pipe(StreamChannel<T> other);
+
+ /// Transforms this using [transformer].
+ ///
+ /// This is identical to calling `transformer.bind(channel)`.
+ StreamChannel<S> transform<S>(StreamChannelTransformer<S, T> transformer);
+
+ /// Transforms only the [stream] component of this using [transformer].
+ StreamChannel<T> transformStream(StreamTransformer<T, T> transformer);
+
+ /// Transforms only the [sink] component of this using [transformer].
+ StreamChannel<T> transformSink(StreamSinkTransformer<T, T> transformer);
+
+ /// Returns a copy of this with [stream] replaced by [change]'s return
+ /// value.
+ StreamChannel<T> changeStream(Stream<T> Function(Stream<T>) change);
+
+ /// Returns a copy of this with [sink] replaced by [change]'s return
+ /// value.
+ StreamChannel<T> changeSink(StreamSink<T> Function(StreamSink<T>) change);
+
+ /// Returns a copy of this with the generic type coerced to [S].
+ ///
+ /// If any events emitted by [stream] aren't of type [S], they're converted
+ /// into [TypeError] events (`CastError` on some SDK versions). Similarly, if
+ /// any events are added to [sink] that aren't of type [S], a [TypeError] is
+ /// thrown.
+ StreamChannel<S> cast<S>();
+}
+
+/// An implementation of [StreamChannel] that simply takes a stream and a sink
+/// as parameters.
+///
+/// This is distinct from [StreamChannel] so that it can use
+/// [StreamChannelMixin].
+class _StreamChannel<T> extends StreamChannelMixin<T> {
+ @override
+ final Stream<T> stream;
+ @override
+ final StreamSink<T> sink;
+
+ _StreamChannel(this.stream, this.sink);
+}
+
+/// A mixin that implements the instance methods of [StreamChannel] in terms of
+/// [stream] and [sink].
+abstract mixin class StreamChannelMixin<T> implements StreamChannel<T> {
+ @override
+ void pipe(StreamChannel<T> other) {
+ stream.pipe(other.sink);
+ other.stream.pipe(sink);
+ }
+
+ @override
+ StreamChannel<S> transform<S>(StreamChannelTransformer<S, T> transformer) =>
+ transformer.bind(this);
+
+ @override
+ StreamChannel<T> transformStream(StreamTransformer<T, T> transformer) =>
+ changeStream(transformer.bind);
+
+ @override
+ StreamChannel<T> transformSink(StreamSinkTransformer<T, T> transformer) =>
+ changeSink(transformer.bind);
+
+ @override
+ StreamChannel<T> changeStream(Stream<T> Function(Stream<T>) change) =>
+ StreamChannel.withCloseGuarantee(change(stream), sink);
+
+ @override
+ StreamChannel<T> changeSink(StreamSink<T> Function(StreamSink<T>) change) =>
+ StreamChannel.withCloseGuarantee(stream, change(sink));
+
+ @override
+ StreamChannel<S> cast<S>() => StreamChannel(
+ stream.cast(), StreamController(sync: true)..stream.cast<T>().pipe(sink));
+}
diff --git a/pkgs/stream_channel/pubspec.yaml b/pkgs/stream_channel/pubspec.yaml
new file mode 100644
index 0000000..f050fca
--- /dev/null
+++ b/pkgs/stream_channel/pubspec.yaml
@@ -0,0 +1,16 @@
+name: stream_channel
+version: 2.1.4
+description: >-
+ An abstraction for two-way communication channels based on the Dart Stream
+ class.
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/stream_channel
+
+environment:
+ sdk: ^3.3.0
+
+dependencies:
+ async: ^2.5.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ test: ^1.16.6
diff --git a/pkgs/stream_channel/test/disconnector_test.dart b/pkgs/stream_channel/test/disconnector_test.dart
new file mode 100644
index 0000000..bbba568
--- /dev/null
+++ b/pkgs/stream_channel/test/disconnector_test.dart
@@ -0,0 +1,152 @@
+// 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.
+
+import 'dart:async';
+
+import 'package:async/async.dart';
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test/test.dart';
+
+void main() {
+ late StreamController streamController;
+ late StreamController sinkController;
+ late Disconnector disconnector;
+ late StreamChannel channel;
+ setUp(() {
+ streamController = StreamController<void>();
+ sinkController = StreamController<void>();
+ disconnector = Disconnector();
+ channel = StreamChannel.withGuarantees(
+ streamController.stream, sinkController.sink)
+ .transform(disconnector);
+ });
+
+ group('before disconnection', () {
+ test('forwards events from the sink as normal', () {
+ channel.sink.add(1);
+ channel.sink.add(2);
+ channel.sink.add(3);
+ channel.sink.close();
+
+ expect(sinkController.stream.toList(), completion(equals([1, 2, 3])));
+ });
+
+ test('forwards events to the stream as normal', () {
+ streamController.add(1);
+ streamController.add(2);
+ streamController.add(3);
+ streamController.close();
+
+ expect(channel.stream.toList(), completion(equals([1, 2, 3])));
+ });
+
+ test("events can't be added when the sink is explicitly closed", () {
+ sinkController.stream.listen(null); // Work around sdk#19095.
+
+ expect(channel.sink.close(), completes);
+ expect(() => channel.sink.add(1), throwsStateError);
+ expect(() => channel.sink.addError('oh no'), throwsStateError);
+ expect(() => channel.sink.addStream(Stream.fromIterable([])),
+ throwsStateError);
+ });
+
+ test("events can't be added while a stream is being added", () {
+ var controller = StreamController<void>();
+ channel.sink.addStream(controller.stream);
+
+ expect(() => channel.sink.add(1), throwsStateError);
+ expect(() => channel.sink.addError('oh no'), throwsStateError);
+ expect(() => channel.sink.addStream(Stream.fromIterable([])),
+ throwsStateError);
+ expect(() => channel.sink.close(), throwsStateError);
+
+ controller.close();
+ });
+ });
+
+ test('cancels addStream when disconnected', () async {
+ var canceled = false;
+ var controller = StreamController<void>(onCancel: () {
+ canceled = true;
+ });
+ expect(channel.sink.addStream(controller.stream), completes);
+ unawaited(disconnector.disconnect());
+
+ await pumpEventQueue();
+ expect(canceled, isTrue);
+ });
+
+ test('disconnect() returns the close future from the inner sink', () async {
+ var streamController = StreamController<void>();
+ var sinkController = StreamController<void>();
+ var disconnector = Disconnector<void>();
+ var sink = _CloseCompleterSink(sinkController.sink);
+ StreamChannel.withGuarantees(streamController.stream, sink)
+ .transform(disconnector);
+
+ var disconnectFutureFired = false;
+ expect(
+ disconnector.disconnect().then((_) {
+ disconnectFutureFired = true;
+ }),
+ completes);
+
+ // Give the future time to fire early if it's going to.
+ await pumpEventQueue();
+ expect(disconnectFutureFired, isFalse);
+
+ // When the inner sink's close future completes, so should the
+ // disconnector's.
+ sink.completer.complete();
+ await pumpEventQueue();
+ expect(disconnectFutureFired, isTrue);
+ });
+
+ group('after disconnection', () {
+ setUp(() {
+ disconnector.disconnect();
+ });
+
+ test('closes the inner sink and ignores events to the outer sink', () {
+ channel.sink.add(1);
+ channel.sink.add(2);
+ channel.sink.add(3);
+ channel.sink.close();
+
+ expect(sinkController.stream.toList(), completion(isEmpty));
+ });
+
+ test('closes the stream', () {
+ expect(channel.stream.toList(), completion(isEmpty));
+ });
+
+ test('completes done', () {
+ sinkController.stream.listen(null); // Work around sdk#19095.
+ expect(channel.sink.done, completes);
+ });
+
+ test('still emits state errors after explicit close', () {
+ sinkController.stream.listen(null); // Work around sdk#19095.
+ expect(channel.sink.close(), completes);
+
+ expect(() => channel.sink.add(1), throwsStateError);
+ expect(() => channel.sink.addError('oh no'), throwsStateError);
+ });
+ });
+}
+
+/// A [StreamSink] wrapper that adds the ability to manually complete the Future
+/// returned by [close] using [completer].
+class _CloseCompleterSink extends DelegatingStreamSink {
+ /// The completer for the future returned by [close].
+ final completer = Completer<void>();
+
+ _CloseCompleterSink(super.inner);
+
+ @override
+ Future<void> close() {
+ super.close();
+ return completer.future;
+ }
+}
diff --git a/pkgs/stream_channel/test/isolate_channel_test.dart b/pkgs/stream_channel/test/isolate_channel_test.dart
new file mode 100644
index 0000000..3a8b42e
--- /dev/null
+++ b/pkgs/stream_channel/test/isolate_channel_test.dart
@@ -0,0 +1,174 @@
+// 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.
+
+@TestOn('vm')
+library;
+
+import 'dart:async';
+import 'dart:isolate';
+
+import 'package:stream_channel/isolate_channel.dart';
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test/test.dart';
+
+void main() {
+ late ReceivePort receivePort;
+ late SendPort sendPort;
+ late StreamChannel channel;
+ setUp(() {
+ receivePort = ReceivePort();
+ var receivePortForSend = ReceivePort();
+ sendPort = receivePortForSend.sendPort;
+ channel = IsolateChannel(receivePortForSend, receivePort.sendPort);
+ });
+
+ tearDown(() {
+ receivePort.close();
+ channel.sink.close();
+ });
+
+ test('the channel can send messages', () {
+ channel.sink.add(1);
+ channel.sink.add(2);
+ channel.sink.add(3);
+
+ expect(receivePort.take(3).toList(), completion(equals([1, 2, 3])));
+ });
+
+ test('the channel can receive messages', () {
+ sendPort.send(1);
+ sendPort.send(2);
+ sendPort.send(3);
+
+ expect(channel.stream.take(3).toList(), completion(equals([1, 2, 3])));
+ });
+
+ test("events can't be added to an explicitly-closed sink", () {
+ expect(channel.sink.close(), completes);
+ expect(() => channel.sink.add(1), throwsStateError);
+ expect(() => channel.sink.addError('oh no'), throwsStateError);
+ expect(() => channel.sink.addStream(Stream.fromIterable([])),
+ throwsStateError);
+ });
+
+ test("events can't be added while a stream is being added", () {
+ var controller = StreamController<void>();
+ channel.sink.addStream(controller.stream);
+
+ expect(() => channel.sink.add(1), throwsStateError);
+ expect(() => channel.sink.addError('oh no'), throwsStateError);
+ expect(() => channel.sink.addStream(Stream.fromIterable([])),
+ throwsStateError);
+ expect(() => channel.sink.close(), throwsStateError);
+
+ controller.close();
+ });
+
+ group('stream channel rules', () {
+ test(
+ 'closing the sink causes the stream to close before it emits any more '
+ 'events', () {
+ sendPort.send(1);
+ sendPort.send(2);
+ sendPort.send(3);
+ sendPort.send(4);
+ sendPort.send(5);
+
+ channel.stream.listen(expectAsync1((message) {
+ expect(message, equals(1));
+ channel.sink.close();
+ }, count: 1));
+ });
+
+ test("cancelling the stream's subscription has no effect on the sink",
+ () async {
+ unawaited(channel.stream.listen(null).cancel());
+ await pumpEventQueue();
+
+ channel.sink.add(1);
+ channel.sink.add(2);
+ channel.sink.add(3);
+ expect(receivePort.take(3).toList(), completion(equals([1, 2, 3])));
+ });
+
+ test('the sink closes as soon as an error is added', () async {
+ channel.sink.addError('oh no');
+ channel.sink.add(1);
+ expect(channel.sink.done, throwsA('oh no'));
+
+ // Since the sink is closed, the stream should also be closed.
+ expect(channel.stream.isEmpty, completion(isTrue));
+
+ // The other end shouldn't receive the next event, since the sink was
+ // closed. Pump the event queue to give it a chance to.
+ receivePort.listen(expectAsync1((_) {}, count: 0));
+ await pumpEventQueue();
+ });
+
+ test('the sink closes as soon as an error is added via addStream',
+ () async {
+ var canceled = false;
+ var controller = StreamController<void>(onCancel: () {
+ canceled = true;
+ });
+
+ // This future shouldn't get the error, because it's sent to [Sink.done].
+ expect(channel.sink.addStream(controller.stream), completes);
+
+ controller.addError('oh no');
+ expect(channel.sink.done, throwsA('oh no'));
+ await pumpEventQueue();
+ expect(canceled, isTrue);
+
+ // Even though the sink is closed, this shouldn't throw an error because
+ // the user didn't explicitly close it.
+ channel.sink.add(1);
+ });
+ });
+
+ group('connect constructors', () {
+ late ReceivePort connectPort;
+ setUp(() {
+ connectPort = ReceivePort();
+ });
+
+ tearDown(() {
+ connectPort.close();
+ });
+
+ test('create a connected pair of channels', () async {
+ var channel1 = IsolateChannel<int>.connectReceive(connectPort);
+ var channel2 = IsolateChannel<int>.connectSend(connectPort.sendPort);
+
+ channel1.sink.add(1);
+ channel1.sink.add(2);
+ channel1.sink.add(3);
+ expect(await channel2.stream.take(3).toList(), equals([1, 2, 3]));
+
+ channel2.sink.add(4);
+ channel2.sink.add(5);
+ channel2.sink.add(6);
+ expect(await channel1.stream.take(3).toList(), equals([4, 5, 6]));
+
+ await channel2.sink.close();
+ });
+
+ test('the receiving channel produces an error if it gets the wrong message',
+ () {
+ var connectedChannel = IsolateChannel<int>.connectReceive(connectPort);
+ connectPort.sendPort.send('wrong value');
+
+ expect(connectedChannel.stream.toList(), throwsStateError);
+ expect(connectedChannel.sink.done, completes);
+ });
+
+ test('the receiving channel closes gracefully without a connection',
+ () async {
+ var connectedChannel = IsolateChannel<int>.connectReceive(connectPort);
+ await connectedChannel.sink.close();
+ await expectLater(connectedChannel.stream.toList(), completion(isEmpty));
+ await expectLater(connectedChannel.sink.done, completes);
+ });
+ });
+}
diff --git a/pkgs/stream_channel/test/json_document_transformer_test.dart b/pkgs/stream_channel/test/json_document_transformer_test.dart
new file mode 100644
index 0000000..290c4e2
--- /dev/null
+++ b/pkgs/stream_channel/test/json_document_transformer_test.dart
@@ -0,0 +1,46 @@
+// 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.
+
+import 'dart:async';
+import 'dart:convert';
+
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test/test.dart';
+
+void main() {
+ late StreamController<String> streamController;
+ late StreamController<String> sinkController;
+ late StreamChannel<String> channel;
+ setUp(() {
+ streamController = StreamController<String>();
+ sinkController = StreamController<String>();
+ channel =
+ StreamChannel<String>(streamController.stream, sinkController.sink);
+ });
+
+ test('decodes JSON emitted by the channel', () {
+ var transformed = channel.transform(jsonDocument);
+ streamController.add('{"foo": "bar"}');
+ expect(transformed.stream.first, completion(equals({'foo': 'bar'})));
+ });
+
+ test('encodes objects added to the channel', () {
+ var transformed = channel.transform(jsonDocument);
+ transformed.sink.add({'foo': 'bar'});
+ expect(sinkController.stream.first,
+ completion(equals(jsonEncode({'foo': 'bar'}))));
+ });
+
+ test('emits a stream error when incoming JSON is malformed', () {
+ var transformed = channel.transform(jsonDocument);
+ streamController.add('{invalid');
+ expect(transformed.stream.first, throwsFormatException);
+ });
+
+ test('synchronously throws if an unencodable object is added', () {
+ var transformed = channel.transform(jsonDocument);
+ expect(() => transformed.sink.add(Object()),
+ throwsA(const TypeMatcher<JsonUnsupportedObjectError>()));
+ });
+}
diff --git a/pkgs/stream_channel/test/multi_channel_test.dart b/pkgs/stream_channel/test/multi_channel_test.dart
new file mode 100644
index 0000000..ee6f8d2
--- /dev/null
+++ b/pkgs/stream_channel/test/multi_channel_test.dart
@@ -0,0 +1,478 @@
+// 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.
+
+import 'dart:async';
+
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test/test.dart';
+
+void main() {
+ late StreamChannelController controller;
+ late MultiChannel channel1;
+ late MultiChannel channel2;
+ setUp(() {
+ controller = StreamChannelController();
+ channel1 = MultiChannel<int>(controller.local);
+ channel2 = MultiChannel<int>(controller.foreign);
+ });
+
+ group('the default virtual channel', () {
+ test('begins connected', () {
+ var first = true;
+ channel2.stream.listen(expectAsync1((message) {
+ if (first) {
+ expect(message, equals(1));
+ first = false;
+ } else {
+ expect(message, equals(2));
+ }
+ }, count: 2));
+
+ channel1.sink.add(1);
+ channel1.sink.add(2);
+ });
+
+ test('closes the remote virtual channel when it closes', () {
+ expect(channel2.stream.toList(), completion(isEmpty));
+ expect(channel2.sink.done, completes);
+
+ channel1.sink.close();
+ });
+
+ test('closes the local virtual channel when it closes', () {
+ expect(channel1.stream.toList(), completion(isEmpty));
+ expect(channel1.sink.done, completes);
+
+ channel1.sink.close();
+ });
+
+ test(
+ "doesn't closes the local virtual channel when the stream "
+ 'subscription is canceled', () {
+ channel1.sink.done.then(expectAsync1((_) {}, count: 0));
+
+ channel1.stream.listen((_) {}).cancel();
+
+ // Ensure that there's enough time for the channel to close if it's going
+ // to.
+ return pumpEventQueue();
+ });
+
+ test(
+ 'closes the underlying channel when it closes without any other '
+ 'virtual channels', () {
+ expect(controller.local.sink.done, completes);
+ expect(controller.foreign.sink.done, completes);
+
+ channel1.sink.close();
+ });
+
+ test(
+ "doesn't close the underlying channel when it closes with other "
+ 'virtual channels', () {
+ controller.local.sink.done.then(expectAsync1((_) {}, count: 0));
+ controller.foreign.sink.done.then(expectAsync1((_) {}, count: 0));
+
+ // Establish another virtual connection which should keep the underlying
+ // connection open.
+ channel2.virtualChannel(channel1.virtualChannel().id);
+ channel1.sink.close();
+
+ // Ensure that there's enough time for the underlying channel to complete
+ // if it's going to.
+ return pumpEventQueue();
+ });
+ });
+
+ group('a locally-created virtual channel', () {
+ late VirtualChannel virtual1;
+ late VirtualChannel virtual2;
+ setUp(() {
+ virtual1 = channel1.virtualChannel();
+ virtual2 = channel2.virtualChannel(virtual1.id);
+ });
+
+ test('sends messages only to the other virtual channel', () {
+ var first = true;
+ virtual2.stream.listen(expectAsync1((message) {
+ if (first) {
+ expect(message, equals(1));
+ first = false;
+ } else {
+ expect(message, equals(2));
+ }
+ }, count: 2));
+
+ // No other virtual channels should receive the message.
+ for (var i = 0; i < 10; i++) {
+ var virtual = channel2.virtualChannel(channel1.virtualChannel().id);
+ virtual.stream.listen(expectAsync1((_) {}, count: 0));
+ }
+ channel2.stream.listen(expectAsync1((_) {}, count: 0));
+
+ virtual1.sink.add(1);
+ virtual1.sink.add(2);
+ });
+
+ test('closes the remote virtual channel when it closes', () {
+ expect(virtual2.stream.toList(), completion(isEmpty));
+ expect(virtual2.sink.done, completes);
+
+ virtual1.sink.close();
+ });
+
+ test('closes the local virtual channel when it closes', () {
+ expect(virtual1.stream.toList(), completion(isEmpty));
+ expect(virtual1.sink.done, completes);
+
+ virtual1.sink.close();
+ });
+
+ test(
+ "doesn't closes the local virtual channel when the stream "
+ 'subscription is canceled', () {
+ virtual1.sink.done.then(expectAsync1((_) {}, count: 0));
+ virtual1.stream.listen((_) {}).cancel();
+
+ // Ensure that there's enough time for the channel to close if it's going
+ // to.
+ return pumpEventQueue();
+ });
+
+ test(
+ 'closes the underlying channel when it closes without any other '
+ 'virtual channels', () async {
+ // First close the default channel so we can test the new channel as the
+ // last living virtual channel.
+ unawaited(channel1.sink.close());
+
+ await channel2.stream.toList();
+ expect(controller.local.sink.done, completes);
+ expect(controller.foreign.sink.done, completes);
+
+ unawaited(virtual1.sink.close());
+ });
+
+ test(
+ "doesn't close the underlying channel when it closes with other "
+ 'virtual channels', () {
+ controller.local.sink.done.then(expectAsync1((_) {}, count: 0));
+ controller.foreign.sink.done.then(expectAsync1((_) {}, count: 0));
+
+ virtual1.sink.close();
+
+ // Ensure that there's enough time for the underlying channel to complete
+ // if it's going to.
+ return pumpEventQueue();
+ });
+
+ test("doesn't conflict with a remote virtual channel", () {
+ var virtual3 = channel2.virtualChannel();
+ var virtual4 = channel1.virtualChannel(virtual3.id);
+
+ // This is an implementation detail, but we assert it here to make sure
+ // we're properly testing two channels with the same id.
+ expect(virtual1.id, equals(virtual3.id));
+
+ virtual2.stream
+ .listen(expectAsync1((message) => expect(message, equals(1))));
+ virtual4.stream
+ .listen(expectAsync1((message) => expect(message, equals(2))));
+
+ virtual1.sink.add(1);
+ virtual3.sink.add(2);
+ });
+ });
+
+ group('a remotely-created virtual channel', () {
+ late VirtualChannel virtual1;
+ late VirtualChannel virtual2;
+ setUp(() {
+ virtual1 = channel1.virtualChannel();
+ virtual2 = channel2.virtualChannel(virtual1.id);
+ });
+
+ test('sends messages only to the other virtual channel', () {
+ var first = true;
+ virtual1.stream.listen(expectAsync1((message) {
+ if (first) {
+ expect(message, equals(1));
+ first = false;
+ } else {
+ expect(message, equals(2));
+ }
+ }, count: 2));
+
+ // No other virtual channels should receive the message.
+ for (var i = 0; i < 10; i++) {
+ var virtual = channel2.virtualChannel(channel1.virtualChannel().id);
+ virtual.stream.listen(expectAsync1((_) {}, count: 0));
+ }
+ channel1.stream.listen(expectAsync1((_) {}, count: 0));
+
+ virtual2.sink.add(1);
+ virtual2.sink.add(2);
+ });
+
+ test('closes the remote virtual channel when it closes', () {
+ expect(virtual1.stream.toList(), completion(isEmpty));
+ expect(virtual1.sink.done, completes);
+
+ virtual2.sink.close();
+ });
+
+ test('closes the local virtual channel when it closes', () {
+ expect(virtual2.stream.toList(), completion(isEmpty));
+ expect(virtual2.sink.done, completes);
+
+ virtual2.sink.close();
+ });
+
+ test(
+ "doesn't closes the local virtual channel when the stream "
+ 'subscription is canceled', () {
+ virtual2.sink.done.then(expectAsync1((_) {}, count: 0));
+ virtual2.stream.listen((_) {}).cancel();
+
+ // Ensure that there's enough time for the channel to close if it's going
+ // to.
+ return pumpEventQueue();
+ });
+
+ test(
+ 'closes the underlying channel when it closes without any other '
+ 'virtual channels', () async {
+ // First close the default channel so we can test the new channel as the
+ // last living virtual channel.
+ unawaited(channel2.sink.close());
+
+ await channel1.stream.toList();
+ expect(controller.local.sink.done, completes);
+ expect(controller.foreign.sink.done, completes);
+
+ unawaited(virtual2.sink.close());
+ });
+
+ test(
+ "doesn't close the underlying channel when it closes with other "
+ 'virtual channels', () {
+ controller.local.sink.done.then(expectAsync1((_) {}, count: 0));
+ controller.foreign.sink.done.then(expectAsync1((_) {}, count: 0));
+
+ virtual2.sink.close();
+
+ // Ensure that there's enough time for the underlying channel to complete
+ // if it's going to.
+ return pumpEventQueue();
+ });
+
+ test("doesn't allow another virtual channel with the same id", () {
+ expect(() => channel2.virtualChannel(virtual1.id), throwsArgumentError);
+ });
+
+ test('dispatches events received before the virtual channel is created',
+ () async {
+ virtual1 = channel1.virtualChannel();
+
+ virtual1.sink.add(1);
+ await pumpEventQueue();
+
+ virtual1.sink.add(2);
+ await pumpEventQueue();
+
+ expect(channel2.virtualChannel(virtual1.id).stream, emitsInOrder([1, 2]));
+ });
+
+ test(
+ 'dispatches close events received before the virtual channel is '
+ 'created', () async {
+ virtual1 = channel1.virtualChannel();
+
+ unawaited(virtual1.sink.close());
+ await pumpEventQueue();
+
+ expect(channel2.virtualChannel(virtual1.id).stream.toList(),
+ completion(isEmpty));
+ });
+ });
+
+ group('when the underlying stream', () {
+ late VirtualChannel virtual1;
+ late VirtualChannel virtual2;
+ setUp(() {
+ virtual1 = channel1.virtualChannel();
+ virtual2 = channel2.virtualChannel(virtual1.id);
+ });
+
+ test('closes, all virtual channels close', () {
+ expect(channel1.stream.toList(), completion(isEmpty));
+ expect(channel1.sink.done, completes);
+ expect(channel2.stream.toList(), completion(isEmpty));
+ expect(channel2.sink.done, completes);
+ expect(virtual1.stream.toList(), completion(isEmpty));
+ expect(virtual1.sink.done, completes);
+ expect(virtual2.stream.toList(), completion(isEmpty));
+ expect(virtual2.sink.done, completes);
+
+ controller.local.sink.close();
+ });
+
+ test('closes, more virtual channels are created closed', () async {
+ unawaited(channel2.sink.close());
+ unawaited(virtual2.sink.close());
+
+ // Wait for the existing channels to emit done events.
+ await channel1.stream.toList();
+ await virtual1.stream.toList();
+
+ var virtual = channel1.virtualChannel();
+ expect(virtual.stream.toList(), completion(isEmpty));
+ expect(virtual.sink.done, completes);
+
+ virtual = channel1.virtualChannel();
+ expect(virtual.stream.toList(), completion(isEmpty));
+ expect(virtual.sink.done, completes);
+ });
+
+ test('emits an error, the error is sent only to the default channel', () {
+ channel1.stream.listen(expectAsync1((_) {}, count: 0),
+ onError: expectAsync1((error) => expect(error, equals('oh no'))));
+ virtual1.stream.listen(expectAsync1((_) {}, count: 0),
+ onError: expectAsync1((_) {}, count: 0));
+
+ controller.foreign.sink.addError('oh no');
+ });
+ });
+
+ group('stream channel rules', () {
+ group('for the main stream:', () {
+ test(
+ 'closing the sink causes the stream to close before it emits any '
+ 'more events', () {
+ channel1.sink.add(1);
+ channel1.sink.add(2);
+ channel1.sink.add(3);
+
+ channel2.stream.listen(expectAsync1((message) {
+ expect(message, equals(1));
+ channel2.sink.close();
+ }, count: 1));
+ });
+
+ test('after the stream closes, the sink ignores events', () async {
+ unawaited(channel1.sink.close());
+
+ // Wait for the done event to be delivered.
+ await channel2.stream.toList();
+ channel2.sink.add(1);
+ channel2.sink.add(2);
+ channel2.sink.add(3);
+ unawaited(channel2.sink.close());
+
+ // None of our channel.sink additions should make it to the other
+ // endpoint.
+ channel1.stream.listen(expectAsync1((_) {}, count: 0));
+ await pumpEventQueue();
+ });
+
+ test("canceling the stream's subscription has no effect on the sink",
+ () async {
+ unawaited(channel1.stream.listen(null).cancel());
+ await pumpEventQueue();
+
+ channel1.sink.add(1);
+ channel1.sink.add(2);
+ channel1.sink.add(3);
+ unawaited(channel1.sink.close());
+ expect(channel2.stream.toList(), completion(equals([1, 2, 3])));
+ });
+
+ test("canceling the stream's subscription doesn't stop a done event",
+ () async {
+ unawaited(channel1.stream.listen(null).cancel());
+ await pumpEventQueue();
+
+ unawaited(channel2.sink.close());
+ await pumpEventQueue();
+
+ channel1.sink.add(1);
+ channel1.sink.add(2);
+ channel1.sink.add(3);
+ unawaited(channel1.sink.close());
+
+ // The sink should be ignoring events because the channel closed.
+ channel2.stream.listen(expectAsync1((_) {}, count: 0));
+ await pumpEventQueue();
+ });
+ });
+
+ group('for a virtual channel:', () {
+ late VirtualChannel virtual1;
+ late VirtualChannel virtual2;
+ setUp(() {
+ virtual1 = channel1.virtualChannel();
+ virtual2 = channel2.virtualChannel(virtual1.id);
+ });
+
+ test(
+ 'closing the sink causes the stream to close before it emits any '
+ 'more events', () {
+ virtual1.sink.add(1);
+ virtual1.sink.add(2);
+ virtual1.sink.add(3);
+
+ virtual2.stream.listen(expectAsync1((message) {
+ expect(message, equals(1));
+ virtual2.sink.close();
+ }, count: 1));
+ });
+
+ test('after the stream closes, the sink ignores events', () async {
+ unawaited(virtual1.sink.close());
+
+ // Wait for the done event to be delivered.
+ await virtual2.stream.toList();
+ virtual2.sink.add(1);
+ virtual2.sink.add(2);
+ virtual2.sink.add(3);
+ unawaited(virtual2.sink.close());
+
+ // None of our virtual.sink additions should make it to the other
+ // endpoint.
+ virtual1.stream.listen(expectAsync1((_) {}, count: 0));
+ await pumpEventQueue();
+ });
+
+ test("canceling the stream's subscription has no effect on the sink",
+ () async {
+ unawaited(virtual1.stream.listen(null).cancel());
+ await pumpEventQueue();
+
+ virtual1.sink.add(1);
+ virtual1.sink.add(2);
+ virtual1.sink.add(3);
+ unawaited(virtual1.sink.close());
+ expect(virtual2.stream.toList(), completion(equals([1, 2, 3])));
+ });
+
+ test("canceling the stream's subscription doesn't stop a done event",
+ () async {
+ unawaited(virtual1.stream.listen(null).cancel());
+ await pumpEventQueue();
+
+ unawaited(virtual2.sink.close());
+ await pumpEventQueue();
+
+ virtual1.sink.add(1);
+ virtual1.sink.add(2);
+ virtual1.sink.add(3);
+ unawaited(virtual1.sink.close());
+
+ // The sink should be ignoring events because the stream closed.
+ virtual2.stream.listen(expectAsync1((_) {}, count: 0));
+ await pumpEventQueue();
+ });
+ });
+ });
+}
diff --git a/pkgs/stream_channel/test/stream_channel_completer_test.dart b/pkgs/stream_channel/test/stream_channel_completer_test.dart
new file mode 100644
index 0000000..c6fddc0
--- /dev/null
+++ b/pkgs/stream_channel/test/stream_channel_completer_test.dart
@@ -0,0 +1,120 @@
+// 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.
+
+import 'dart:async';
+
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test/test.dart';
+
+void main() {
+ late StreamChannelCompleter completer;
+ late StreamController streamController;
+ late StreamController sinkController;
+ late StreamChannel innerChannel;
+ setUp(() {
+ completer = StreamChannelCompleter();
+ streamController = StreamController<void>();
+ sinkController = StreamController<void>();
+ innerChannel = StreamChannel(streamController.stream, sinkController.sink);
+ });
+
+ group('when a channel is set before accessing', () {
+ test('forwards events through the stream', () {
+ completer.setChannel(innerChannel);
+ expect(completer.channel.stream.toList(), completion(equals([1, 2, 3])));
+
+ streamController.add(1);
+ streamController.add(2);
+ streamController.add(3);
+ streamController.close();
+ });
+
+ test('forwards events through the sink', () {
+ completer.setChannel(innerChannel);
+ expect(sinkController.stream.toList(), completion(equals([1, 2, 3])));
+
+ completer.channel.sink.add(1);
+ completer.channel.sink.add(2);
+ completer.channel.sink.add(3);
+ completer.channel.sink.close();
+ });
+
+ test('forwards an error through the stream', () {
+ completer.setError('oh no');
+ expect(completer.channel.stream.first, throwsA('oh no'));
+ });
+
+ test('drops sink events', () {
+ completer.setError('oh no');
+ expect(completer.channel.sink.done, completes);
+ completer.channel.sink.add(1);
+ completer.channel.sink.addError('oh no');
+ });
+ });
+
+ group('when a channel is set after accessing', () {
+ test('forwards events through the stream', () async {
+ expect(completer.channel.stream.toList(), completion(equals([1, 2, 3])));
+ await pumpEventQueue();
+
+ completer.setChannel(innerChannel);
+ streamController.add(1);
+ streamController.add(2);
+ streamController.add(3);
+ unawaited(streamController.close());
+ });
+
+ test('forwards events through the sink', () async {
+ completer.channel.sink.add(1);
+ completer.channel.sink.add(2);
+ completer.channel.sink.add(3);
+ unawaited(completer.channel.sink.close());
+ await pumpEventQueue();
+
+ completer.setChannel(innerChannel);
+ expect(sinkController.stream.toList(), completion(equals([1, 2, 3])));
+ });
+
+ test('forwards an error through the stream', () async {
+ expect(completer.channel.stream.first, throwsA('oh no'));
+ await pumpEventQueue();
+
+ completer.setError('oh no');
+ });
+
+ test('drops sink events', () async {
+ expect(completer.channel.sink.done, completes);
+ completer.channel.sink.add(1);
+ completer.channel.sink.addError('oh no');
+ await pumpEventQueue();
+
+ completer.setError('oh no');
+ });
+ });
+
+ group('forFuture', () {
+ test('forwards a StreamChannel', () {
+ var channel =
+ StreamChannelCompleter.fromFuture(Future.value(innerChannel));
+ channel.sink.add(1);
+ channel.sink.close();
+ streamController.sink.add(2);
+ streamController.sink.close();
+
+ expect(sinkController.stream.toList(), completion(equals([1])));
+ expect(channel.stream.toList(), completion(equals([2])));
+ });
+
+ test('forwards an error', () {
+ var channel = StreamChannelCompleter.fromFuture(Future.error('oh no'));
+ expect(channel.stream.toList(), throwsA('oh no'));
+ });
+ });
+
+ test("doesn't allow the channel to be set multiple times", () {
+ completer.setChannel(innerChannel);
+ expect(() => completer.setChannel(innerChannel), throwsStateError);
+ expect(() => completer.setChannel(innerChannel), throwsStateError);
+ });
+}
diff --git a/pkgs/stream_channel/test/stream_channel_controller_test.dart b/pkgs/stream_channel/test/stream_channel_controller_test.dart
new file mode 100644
index 0000000..3d661e3
--- /dev/null
+++ b/pkgs/stream_channel/test/stream_channel_controller_test.dart
@@ -0,0 +1,104 @@
+// 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.
+
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('asynchronously', () {
+ late StreamChannelController controller;
+ setUp(() {
+ controller = StreamChannelController();
+ });
+
+ test('forwards events from the local sink to the foreign stream', () {
+ controller.local.sink
+ ..add(1)
+ ..add(2)
+ ..add(3)
+ ..close();
+ expect(controller.foreign.stream.toList(), completion(equals([1, 2, 3])));
+ });
+
+ test('forwards events from the foreign sink to the local stream', () {
+ controller.foreign.sink
+ ..add(1)
+ ..add(2)
+ ..add(3)
+ ..close();
+ expect(controller.local.stream.toList(), completion(equals([1, 2, 3])));
+ });
+
+ test(
+ 'with allowForeignErrors: false, shuts down the connection if an '
+ 'error is added to the foreign channel', () {
+ controller = StreamChannelController(allowForeignErrors: false);
+
+ controller.foreign.sink.addError('oh no');
+ expect(controller.foreign.sink.done, throwsA('oh no'));
+ expect(controller.foreign.stream.toList(), completion(isEmpty));
+ expect(controller.local.sink.done, completes);
+ expect(controller.local.stream.toList(), completion(isEmpty));
+ });
+ });
+
+ group('synchronously', () {
+ late StreamChannelController controller;
+ setUp(() {
+ controller = StreamChannelController(sync: true);
+ });
+
+ test(
+ 'synchronously forwards events from the local sink to the foreign '
+ 'stream', () {
+ var receivedEvent = false;
+ var receivedError = false;
+ var receivedDone = false;
+ controller.foreign.stream.listen(expectAsync1((event) {
+ expect(event, equals(1));
+ receivedEvent = true;
+ }), onError: expectAsync1((error) {
+ expect(error, equals('oh no'));
+ receivedError = true;
+ }), onDone: expectAsync0(() {
+ receivedDone = true;
+ }));
+
+ controller.local.sink.add(1);
+ expect(receivedEvent, isTrue);
+
+ controller.local.sink.addError('oh no');
+ expect(receivedError, isTrue);
+
+ controller.local.sink.close();
+ expect(receivedDone, isTrue);
+ });
+
+ test(
+ 'synchronously forwards events from the foreign sink to the local '
+ 'stream', () {
+ var receivedEvent = false;
+ var receivedError = false;
+ var receivedDone = false;
+ controller.local.stream.listen(expectAsync1((event) {
+ expect(event, equals(1));
+ receivedEvent = true;
+ }), onError: expectAsync1((error) {
+ expect(error, equals('oh no'));
+ receivedError = true;
+ }), onDone: expectAsync0(() {
+ receivedDone = true;
+ }));
+
+ controller.foreign.sink.add(1);
+ expect(receivedEvent, isTrue);
+
+ controller.foreign.sink.addError('oh no');
+ expect(receivedError, isTrue);
+
+ controller.foreign.sink.close();
+ expect(receivedDone, isTrue);
+ });
+ });
+}
diff --git a/pkgs/stream_channel/test/stream_channel_test.dart b/pkgs/stream_channel/test/stream_channel_test.dart
new file mode 100644
index 0000000..166671d
--- /dev/null
+++ b/pkgs/stream_channel/test/stream_channel_test.dart
@@ -0,0 +1,180 @@
+// 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.
+
+import 'dart:async';
+import 'dart:convert';
+
+import 'package:async/async.dart';
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test/test.dart';
+
+void main() {
+ test("pipe() pipes data from each channel's stream into the other's sink",
+ () {
+ var otherStreamController = StreamController<int>();
+ var otherSinkController = StreamController<int>();
+ var otherChannel =
+ StreamChannel(otherStreamController.stream, otherSinkController.sink);
+
+ var streamController = StreamController<int>();
+ var sinkController = StreamController<int>();
+ var channel = StreamChannel(streamController.stream, sinkController.sink);
+
+ channel.pipe(otherChannel);
+
+ streamController.add(1);
+ streamController.add(2);
+ streamController.add(3);
+ streamController.close();
+ expect(otherSinkController.stream.toList(), completion(equals([1, 2, 3])));
+
+ otherStreamController.add(4);
+ otherStreamController.add(5);
+ otherStreamController.add(6);
+ otherStreamController.close();
+ expect(sinkController.stream.toList(), completion(equals([4, 5, 6])));
+ });
+
+ test('transform() transforms the channel', () async {
+ var streamController = StreamController<List<int>>();
+ var sinkController = StreamController<List<int>>();
+ var channel = StreamChannel(streamController.stream, sinkController.sink);
+
+ var transformed = channel
+ .cast<List<int>>()
+ .transform(StreamChannelTransformer.fromCodec(utf8));
+
+ streamController.add([102, 111, 111, 98, 97, 114]);
+ unawaited(streamController.close());
+ expect(await transformed.stream.toList(), equals(['foobar']));
+
+ transformed.sink.add('fblthp');
+ unawaited(transformed.sink.close());
+ expect(
+ sinkController.stream.toList(),
+ completion(equals([
+ [102, 98, 108, 116, 104, 112]
+ ])));
+ });
+
+ test('transformStream() transforms only the stream', () async {
+ var streamController = StreamController<String>();
+ var sinkController = StreamController<String>();
+ var channel = StreamChannel(streamController.stream, sinkController.sink);
+
+ var transformed =
+ channel.cast<String>().transformStream(const LineSplitter());
+
+ streamController.add('hello world');
+ streamController.add(' what\nis');
+ streamController.add('\nup');
+ unawaited(streamController.close());
+ expect(await transformed.stream.toList(),
+ equals(['hello world what', 'is', 'up']));
+
+ transformed.sink.add('fbl\nthp');
+ unawaited(transformed.sink.close());
+ expect(sinkController.stream.toList(), completion(equals(['fbl\nthp'])));
+ });
+
+ test('transformSink() transforms only the sink', () async {
+ var streamController = StreamController<String>();
+ var sinkController = StreamController<String>();
+ var channel = StreamChannel(streamController.stream, sinkController.sink);
+
+ var transformed = channel.cast<String>().transformSink(
+ const StreamSinkTransformer.fromStreamTransformer(LineSplitter()));
+
+ streamController.add('fbl\nthp');
+ unawaited(streamController.close());
+ expect(await transformed.stream.toList(), equals(['fbl\nthp']));
+
+ transformed.sink.add('hello world');
+ transformed.sink.add(' what\nis');
+ transformed.sink.add('\nup');
+ unawaited(transformed.sink.close());
+ expect(sinkController.stream.toList(),
+ completion(equals(['hello world what', 'is', 'up'])));
+ });
+
+ test('changeStream() changes the stream', () {
+ var streamController = StreamController<int>();
+ var sinkController = StreamController<int>();
+ var channel = StreamChannel(streamController.stream, sinkController.sink);
+
+ var newController = StreamController<int>();
+ var changed = channel.changeStream((stream) {
+ expect(stream, equals(channel.stream));
+ return newController.stream;
+ });
+
+ newController.add(10);
+ newController.close();
+
+ streamController.add(20);
+ streamController.close();
+
+ expect(changed.stream.toList(), completion(equals([10])));
+ });
+
+ test('changeSink() changes the sink', () {
+ var streamController = StreamController<int>();
+ var sinkController = StreamController<int>();
+ var channel = StreamChannel(streamController.stream, sinkController.sink);
+
+ var newController = StreamController<int>();
+ var changed = channel.changeSink((sink) {
+ expect(sink, equals(channel.sink));
+ return newController.sink;
+ });
+
+ expect(newController.stream.toList(), completion(equals([10])));
+ streamController.stream.listen(expectAsync1((_) {}, count: 0));
+
+ changed.sink.add(10);
+ changed.sink.close();
+ });
+
+ group('StreamChannelMixin', () {
+ test('can be used as a mixin', () async {
+ var channel = StreamChannelMixinAsMixin<int>();
+ expect(channel.stream, emitsInOrder([1, 2, 3]));
+ channel.sink
+ ..add(1)
+ ..add(2)
+ ..add(3);
+ await channel.controller.close();
+ });
+
+ test('can be extended', () async {
+ var channel = StreamChannelMixinAsSuperclass<int>();
+ expect(channel.stream, emitsInOrder([1, 2, 3]));
+ channel.sink
+ ..add(1)
+ ..add(2)
+ ..add(3);
+ await channel.controller.close();
+ });
+ });
+}
+
+class StreamChannelMixinAsMixin<T> with StreamChannelMixin<T> {
+ final controller = StreamController<T>();
+
+ @override
+ StreamSink<T> get sink => controller.sink;
+
+ @override
+ Stream<T> get stream => controller.stream;
+}
+
+class StreamChannelMixinAsSuperclass<T> extends StreamChannelMixin<T> {
+ final controller = StreamController<T>();
+
+ @override
+ StreamSink<T> get sink => controller.sink;
+
+ @override
+ Stream<T> get stream => controller.stream;
+}
diff --git a/pkgs/stream_channel/test/with_close_guarantee_test.dart b/pkgs/stream_channel/test/with_close_guarantee_test.dart
new file mode 100644
index 0000000..9c0b729
--- /dev/null
+++ b/pkgs/stream_channel/test/with_close_guarantee_test.dart
@@ -0,0 +1,69 @@
+// 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.
+
+import 'dart:async';
+
+import 'package:async/async.dart';
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test/test.dart';
+
+final _delayTransformer = StreamTransformer.fromHandlers(
+ handleData: (data, sink) => Future.microtask(() => sink.add(data)),
+ handleDone: (sink) => Future.microtask(() => sink.close()));
+
+final _delaySinkTransformer =
+ StreamSinkTransformer.fromStreamTransformer(_delayTransformer);
+
+void main() {
+ late StreamChannelController controller;
+ late StreamChannel channel;
+ setUp(() {
+ controller = StreamChannelController();
+
+ // Add a bunch of layers of asynchronous dispatch between the channel and
+ // the underlying controllers.
+ var stream = controller.foreign.stream;
+ var sink = controller.foreign.sink;
+ for (var i = 0; i < 10; i++) {
+ stream = stream.transform(_delayTransformer);
+ sink = _delaySinkTransformer.bind(sink);
+ }
+
+ channel = StreamChannel.withCloseGuarantee(stream, sink);
+ });
+
+ test(
+ 'closing the event sink causes the stream to close before it emits any '
+ 'more events', () async {
+ controller.local.sink.add(1);
+ controller.local.sink.add(2);
+ controller.local.sink.add(3);
+
+ expect(
+ channel.stream
+ .listen(expectAsync1((event) {
+ if (event == 2) channel.sink.close();
+ }, count: 2))
+ .asFuture<void>(),
+ completes);
+
+ await pumpEventQueue();
+ });
+
+ test(
+ 'closing the event sink before events are emitted causes the stream to '
+ 'close immediately', () async {
+ unawaited(channel.sink.close());
+ channel.stream.listen(expectAsync1((_) {}, count: 0),
+ onError: expectAsync2((_, __) {}, count: 0),
+ onDone: expectAsync0(() {}));
+
+ controller.local.sink.add(1);
+ controller.local.sink.add(2);
+ controller.local.sink.add(3);
+ unawaited(controller.local.sink.close());
+
+ await pumpEventQueue();
+ });
+}
diff --git a/pkgs/stream_channel/test/with_guarantees_test.dart b/pkgs/stream_channel/test/with_guarantees_test.dart
new file mode 100644
index 0000000..f026079
--- /dev/null
+++ b/pkgs/stream_channel/test/with_guarantees_test.dart
@@ -0,0 +1,200 @@
+// 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.
+
+import 'dart:async';
+
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test/test.dart';
+
+void main() {
+ late StreamController streamController;
+ late StreamController sinkController;
+ late StreamChannel channel;
+ setUp(() {
+ streamController = StreamController<void>();
+ sinkController = StreamController<void>();
+ channel = StreamChannel.withGuarantees(
+ streamController.stream, sinkController.sink);
+ });
+
+ group('with a broadcast stream', () {
+ setUp(() {
+ streamController = StreamController.broadcast();
+ channel = StreamChannel.withGuarantees(
+ streamController.stream, sinkController.sink);
+ });
+
+ test('buffers events', () async {
+ streamController.add(1);
+ streamController.add(2);
+ streamController.add(3);
+ await pumpEventQueue();
+
+ expect(channel.stream.toList(), completion(equals([1, 2, 3])));
+ unawaited(streamController.close());
+ });
+
+ test('only allows a single subscription', () {
+ channel.stream.listen(null);
+ expect(() => channel.stream.listen(null), throwsStateError);
+ });
+ });
+
+ test(
+ 'closing the event sink causes the stream to close before it emits any '
+ 'more events', () {
+ streamController.add(1);
+ streamController.add(2);
+ streamController.add(3);
+
+ expect(
+ channel.stream
+ .listen(expectAsync1((event) {
+ if (event == 2) channel.sink.close();
+ }, count: 2))
+ .asFuture<void>(),
+ completes);
+ });
+
+ test('after the stream closes, the sink ignores events', () async {
+ unawaited(streamController.close());
+
+ // Wait for the done event to be delivered.
+ await channel.stream.toList();
+ channel.sink.add(1);
+ channel.sink.add(2);
+ channel.sink.add(3);
+ unawaited(channel.sink.close());
+
+ // None of our channel.sink additions should make it to the other endpoint.
+ sinkController.stream.listen(expectAsync1((_) {}, count: 0),
+ onDone: expectAsync0(() {}, count: 0));
+ await pumpEventQueue();
+ });
+
+ test("canceling the stream's subscription has no effect on the sink",
+ () async {
+ unawaited(channel.stream.listen(null).cancel());
+ await pumpEventQueue();
+
+ channel.sink.add(1);
+ channel.sink.add(2);
+ channel.sink.add(3);
+ unawaited(channel.sink.close());
+ expect(sinkController.stream.toList(), completion(equals([1, 2, 3])));
+ });
+
+ test("canceling the stream's subscription doesn't stop a done event",
+ () async {
+ unawaited(channel.stream.listen(null).cancel());
+ await pumpEventQueue();
+
+ unawaited(streamController.close());
+ await pumpEventQueue();
+
+ channel.sink.add(1);
+ channel.sink.add(2);
+ channel.sink.add(3);
+ unawaited(channel.sink.close());
+
+ // The sink should be ignoring events because the stream closed.
+ sinkController.stream.listen(expectAsync1((_) {}, count: 0),
+ onDone: expectAsync0(() {}, count: 0));
+ await pumpEventQueue();
+ });
+
+ test('forwards errors to the other endpoint', () {
+ channel.sink.addError('error');
+ expect(sinkController.stream.first, throwsA('error'));
+ });
+
+ test('Sink.done completes once the stream is done', () {
+ channel.stream.listen(null);
+ expect(channel.sink.done, completes);
+ streamController.close();
+ });
+
+ test("events can't be added to an explicitly-closed sink", () {
+ sinkController.stream.listen(null); // Work around sdk#19095.
+
+ expect(channel.sink.close(), completes);
+ expect(() => channel.sink.add(1), throwsStateError);
+ expect(() => channel.sink.addError('oh no'), throwsStateError);
+ expect(() => channel.sink.addStream(Stream.fromIterable([])),
+ throwsStateError);
+ });
+
+ test("events can't be added while a stream is being added", () {
+ var controller = StreamController<void>();
+ channel.sink.addStream(controller.stream);
+
+ expect(() => channel.sink.add(1), throwsStateError);
+ expect(() => channel.sink.addError('oh no'), throwsStateError);
+ expect(() => channel.sink.addStream(Stream.fromIterable([])),
+ throwsStateError);
+ expect(() => channel.sink.close(), throwsStateError);
+
+ controller.close();
+ });
+
+ group('with allowSinkErrors: false', () {
+ setUp(() {
+ streamController = StreamController<void>();
+ sinkController = StreamController<void>();
+ channel = StreamChannel.withGuarantees(
+ streamController.stream, sinkController.sink,
+ allowSinkErrors: false);
+ });
+
+ test('forwards errors to Sink.done but not the stream', () {
+ channel.sink.addError('oh no');
+ expect(channel.sink.done, throwsA('oh no'));
+ sinkController.stream
+ .listen(null, onError: expectAsync1((dynamic _) {}, count: 0));
+ });
+
+ test('adding an error causes the stream to emit a done event', () {
+ expect(channel.sink.done, throwsA('oh no'));
+
+ streamController.add(1);
+ streamController.add(2);
+ streamController.add(3);
+
+ expect(
+ channel.stream
+ .listen(expectAsync1((event) {
+ if (event == 2) channel.sink.addError('oh no');
+ }, count: 2))
+ .asFuture<void>(),
+ completes);
+ });
+
+ test('adding an error closes the inner sink', () {
+ channel.sink.addError('oh no');
+ expect(channel.sink.done, throwsA('oh no'));
+ expect(sinkController.stream.toList(), completion(isEmpty));
+ });
+
+ test(
+ 'adding an error via via addStream causes the stream to emit a done '
+ 'event', () async {
+ var canceled = false;
+ var controller = StreamController<void>(onCancel: () {
+ canceled = true;
+ });
+
+ // This future shouldn't get the error, because it's sent to [Sink.done].
+ expect(channel.sink.addStream(controller.stream), completes);
+
+ controller.addError('oh no');
+ expect(channel.sink.done, throwsA('oh no'));
+ await pumpEventQueue();
+ expect(canceled, isTrue);
+
+ // Even though the sink is closed, this shouldn't throw an error because
+ // the user didn't explicitly close it.
+ channel.sink.add(1);
+ });
+ });
+}
diff --git a/pkgs/stream_transform/.gitignore b/pkgs/stream_transform/.gitignore
new file mode 100644
index 0000000..bfffcc6
--- /dev/null
+++ b/pkgs/stream_transform/.gitignore
@@ -0,0 +1,6 @@
+.pub/
+.dart_tool/
+build/
+packages
+pubspec.lock
+.packages
diff --git a/pkgs/stream_transform/CHANGELOG.md b/pkgs/stream_transform/CHANGELOG.md
new file mode 100644
index 0000000..b09778b
--- /dev/null
+++ b/pkgs/stream_transform/CHANGELOG.md
@@ -0,0 +1,189 @@
+## 2.1.2-wip
+
+- Require Dart 3.4 or greater.
+
+## 2.1.1
+
+- Require Dart 3.1 or greater
+- Forward errors from the `trigger` future through to the result stream in
+ `takeUntil`. Previously an error would have not closed the stream, and instead
+ raised as an unhandled async error.
+- Move to `dart-lang/tools` monorepo.
+
+## 2.1.0
+
+- Add `whereNotNull`.
+
+## 2.0.1
+
+- Require Dart 2.14 or greater.
+- Wait for the future returned from `StreamSubscription.cancel()` before
+ listening to the subsequent stream in `switchLatest` and `switchMap`.
+
+## 2.0.0
+
+- Migrate to null safety.
+- Improve tests of `switchMap` and improve documentation with links and
+ clarification.
+- Add `trailing` argument to `throttle`.
+
+## 1.2.0
+
+- Add support for emitting the "leading" event in `debounce`.
+
+## 1.1.1
+
+- Fix a bug in `asyncMapSample`, `buffer`, `combineLatest`,
+ `combineLatestAll`, `merge`, and `mergeAll` which would cause an exception
+ when cancelling a subscription after using the transformer if the original
+ stream(s) returned `null` from cancelling their subscriptions.
+
+## 1.1.0
+
+- Add `concurrentAsyncExpand` to interleave events emitted by multiple sub
+ streams created by a callback.
+
+## 1.0.0
+
+- Remove the top level methods and retain the extensions only.
+
+## 0.0.20
+
+- Add extension methods for most transformers. These should be used in place
+ of the current methods. All current implementations are deprecated and will
+ be removed in the next major version bump.
+ - Migrating typical use: Instead of
+ `stream.transform(debounce(Duration(seconds: 1)))` use
+ `stream.debounce(Duration(seconds: 1))`.
+ - To migrate a usage where a `StreamTransformer` instance is stored or
+ passed see "Getting a StreamTransformer instance" on the README.
+- The `map` and `chainTransformers` utilities are no longer useful with the
+ new patterns so they are deprecated without a replacement. If you still have
+ a need for them they can be replicated with `StreamTransformer.fromBind`:
+
+ ```
+ // Replace `map(convert)`
+ StreamTransformer.fromBind((s) => s.map(convert));
+
+ // Replace `chainTransformers(first, second)`
+ StreamTransformer.fromBind((s) => s.transform(first).transform(second));
+ ```
+
+## 0.0.19
+
+- Add `asyncMapSample` transform.
+
+## 0.0.18
+
+- Internal cleanup. Passed "trigger" streams or futures now allow `<void>`
+ generic type rather than an implicit `dynamic>`
+
+## 0.0.17
+
+- Add concrete types to the `onError` callback in `tap`.
+
+## 0.0.16+1
+
+- Remove usage of Set literal which is not available before Dart 2.2.0
+
+## 0.0.16
+
+- Allow a `combine` callback to return a `FutureOr<T>` in `scan`. There are no
+ behavior changes for synchronous callbacks. **Potential breaking change** In
+ the unlikely situation where `scan` was used to produce a `Stream<Future>`
+ inference may now fail and require explicit generic type arguments.
+- Add `combineLatest`.
+- Add `combineLatestAll`.
+
+## 0.0.15
+
+- Add `whereType`.
+
+## 0.0.14+1
+
+- Allow using non-dev Dart 2 SDK.
+
+## 0.0.14
+
+- `asyncWhere` will now forward exceptions thrown by the callback through the
+ result Stream.
+- Added `concurrentAsyncMap`.
+
+## 0.0.13
+
+- `mergeAll` now accepts an `Iterable<Stream>` instead of only `List<Stream>`.
+
+## 0.0.12
+
+- Add `chainTransformers` and `map` for use cases where `StreamTransformer`
+ instances are stored as variables or passed to methods other than `transform`.
+
+## 0.0.11
+
+- Renamed `concat` as `followedBy` to match the naming of `Iterable.followedBy`.
+ `concat` is now deprecated.
+
+## 0.0.10
+
+- Updates to support Dart 2.0 core library changes (wave
+ 2.2). See [issue 31847][sdk#31847] for details.
+
+ [sdk#31847]: https://github.com/dart-lang/sdk/issues/31847
+
+## 0.0.9
+
+- Add `asyncMapBuffer`.
+
+## 0.0.8
+
+- Add `takeUntil`.
+
+## 0.0.7
+
+- Bug Fix: Streams produced with `scan` and `switchMap` now correctly report
+ `isBroadcast`.
+- Add `startWith`, `startWithMany`, and `startWithStream`.
+
+## 0.0.6
+
+- Bug Fix: Some transformers did not correctly add data to all listeners on
+ broadcast streams. Fixed for `throttle`, `debounce`, `asyncWhere` and `audit`.
+- Bug Fix: Only call the `tap` data callback once per event rather than once per
+ listener.
+- Bug Fix: Allow canceling and re-listening to broadcast streams after a
+ `merge` transform.
+- Bug Fix: Broadcast streams which are buffered using a single-subscription
+ trigger can be canceled and re-listened.
+- Bug Fix: Buffer outputs one more value if there is a pending trigger before
+ the trigger closes.
+- Bug Fix: Single-subscription streams concatted after broadcast streams are
+ handled correctly.
+- Use sync `StreamControllers` for forwarding where possible.
+
+## 0.0.5
+
+- Bug Fix: Allow compiling switchLatest with Dart2Js.
+- Add `asyncWhere`: Like `where` but allows an asynchronous predicate.
+
+## 0.0.4
+- Add `scan`: fold which returns intermediate values
+- Add `throttle`: block events for a duration after emitting a value
+- Add `audit`: emits the last event received after a duration
+
+## 0.0.3
+
+- Add `tap`: React to values as they pass without being a subscriber on a stream
+- Add `switchMap` and `switchLatest`: Flatten a Stream of Streams into a Stream
+ which forwards values from the most recent Stream
+
+## 0.0.2
+
+- Add `concat`: Appends streams in series
+- Add `merge` and `mergeAll`: Interleaves streams
+
+## 0.0.1
+
+- Initial release with the following utilities:
+ - `buffer`: Collects events in a `List` until a `trigger` stream fires.
+ - `debounce`, `debounceBuffer`: Collect or drop events which occur closer in
+ time than a given duration.
diff --git a/pkgs/stream_transform/LICENSE b/pkgs/stream_transform/LICENSE
new file mode 100644
index 0000000..03af64a
--- /dev/null
+++ b/pkgs/stream_transform/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2017, 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.
diff --git a/pkgs/stream_transform/README.md b/pkgs/stream_transform/README.md
new file mode 100644
index 0000000..e7049bd
--- /dev/null
+++ b/pkgs/stream_transform/README.md
@@ -0,0 +1,141 @@
+[](https://github.com/dart-lang/tools/actions/workflows/stream_transform.yaml)
+[](https://pub.dev/packages/stream_transform)
+[](https://pub.dev/packages/stream_transform/publisher)
+
+Extension methods on `Stream` adding common transform operators.
+
+## Operators
+
+### asyncMapBuffer, asyncMapSample, concurrentAsyncMap
+
+Alternatives to `asyncMap`. `asyncMapBuffer` prevents the callback from
+overlapping execution and collects events while it is executing.
+`asyncMapSample` prevents overlapping execution and discards events while it is
+executing. `concurrentAsyncMap` allows overlap and removes ordering guarantees
+for higher throughput.
+
+Like `asyncMap` but events are buffered in a List until previous events have
+been processed rather than being called for each element individually.
+
+### asyncWhere
+
+Like `where` but allows an asynchronous predicate.
+
+### audit
+
+Waits for a period of time after receiving a value and then only emits the most
+recent value.
+
+### buffer
+
+Collects values from a source stream until a `trigger` stream fires and the
+collected values are emitted.
+
+### combineLatest, combineLatestAll
+
+Combine the most recent event from multiple streams through a callback or into a
+list.
+
+### debounce, debounceBuffer
+
+Prevents a source stream from emitting too frequently by dropping or collecting
+values that occur within a given duration.
+
+### followedBy
+
+Appends the values of a stream after another stream finishes.
+
+### merge, mergeAll, concurrentAsyncExpand
+
+Interleaves events from multiple streams into a single stream.
+
+### scan
+
+Scan is like fold, but instead of producing a single value it yields each
+intermediate accumulation.
+
+### startWith, startWithMany, startWithStream
+
+Prepend a value, an iterable, or a stream to the beginning of another stream.
+
+### switchMap, switchLatest
+
+Flatten a Stream of Streams into a Stream which forwards values from the most
+recent Stream
+
+### takeUntil
+
+Let values through until a Future fires.
+
+### tap
+
+Taps into a single-subscriber stream to react to values as they pass, without
+being a real subscriber.
+
+### throttle
+
+Blocks events for a duration after an event is successfully emitted.
+
+### whereType
+
+Like `Iterable.whereType` for a stream.
+
+## Comparison to Rx Operators
+
+The semantics and naming in this package have some overlap, and some conflict,
+with the [ReactiveX](https://reactivex.io/) suite of libraries. Some of the
+conflict is intentional - Dart `Stream` predates `Observable` and coherence with
+the Dart ecosystem semantics and naming is a strictly higher priority than
+consistency with ReactiveX.
+
+Rx Operator Category | variation | `stream_transform`
+------------------------- | ------------------------------------------------------ | ------------------
+[`sample`][rx_sample] | `sample/throttleLast(Duration)` | `sample(Stream.periodic(Duration), longPoll: false)`
+​ | `throttleFirst(Duration)` | [`throttle`][throttle]
+​ | `sample(Observable)` | `sample(trigger, longPoll: false)`
+[`debounce`][rx_debounce] | `debounce/throttleWithTimeout(Duration)` | [`debounce`][debounce]
+​ | `debounce(Observable)` | No equivalent
+[`buffer`][rx_buffer] | `buffer(boundary)`, `bufferWithTime`,`bufferWithCount` | No equivalent
+​ | `buffer(boundaryClosingSelector)` | `buffer(trigger, longPoll: false)`
+RxJs extensions | [`audit(callback)`][rxjs_audit] | No equivalent
+​ | [`auditTime(Duration)`][rxjs_auditTime] | [`audit`][audit]
+​ | [`exhaustMap`][rxjs_exhaustMap] | No equivalent
+​ | [`throttleTime(trailing: true)`][rxjs_throttleTime] | `throttle(trailing: true)`
+​ | `throttleTime(leading: false, trailing: true)` | No equivalent
+No equivalent? | | [`asyncMapBuffer`][asyncMapBuffer]
+​ | | [`asyncMapSample`][asyncMapSample]
+​ | | [`buffer`][buffer]
+​ | | [`sample`][sample]
+​ | | [`debounceBuffer`][debounceBuffer]
+​ | | `debounce(leading: true, trailing: false)`
+​ | | `debounce(leading: true, trailing: true)`
+
+[rx_sample]:https://reactivex.io/documentation/operators/sample.html
+[rx_debounce]:https://reactivex.io/documentation/operators/debounce.html
+[rx_buffer]:https://reactivex.io/documentation/operators/buffer.html
+[rxjs_audit]:https://rxjs.dev/api/operators/audit
+[rxjs_auditTime]:https://rxjs.dev/api/operators/auditTime
+[rxjs_throttleTime]:https://rxjs.dev/api/operators/throttleTime
+[rxjs_exhaustMap]:https://rxjs.dev/api/operators/exhaustMap
+[asyncMapBuffer]:https://pub.dev/documentation/stream_transform/latest/stream_transform/AsyncMap/asyncMapBuffer.html
+[asyncMapSample]:https://pub.dev/documentation/stream_transform/latest/stream_transform/AsyncMap/asyncMapSample.html
+[audit]:https://pub.dev/documentation/stream_transform/latest/stream_transform/RateLimit/audit.html
+[buffer]:https://pub.dev/documentation/stream_transform/latest/stream_transform/RateLimit/buffer.html
+[sample]:https://pub.dev/documentation/stream_transform/latest/stream_transform/RateLimit/sample.html
+[debounceBuffer]:https://pub.dev/documentation/stream_transform/latest/stream_transform/RateLimit/debounceBuffer.html
+[debounce]:https://pub.dev/documentation/stream_transform/latest/stream_transform/RateLimit/debounce.html
+[throttle]:https://pub.dev/documentation/stream_transform/latest/stream_transform/RateLimit/throttle.html
+
+## Getting a `StreamTransformer` instance
+
+It may be useful to pass an instance of `StreamTransformer` so that it can be
+used with `stream.transform` calls rather than reference the specific operator
+in place. Any operator on `Stream` that returns a `Stream` can be modeled as a
+`StreamTransformer` using the [`fromBind` constructor][fromBind].
+
+```dart
+final debounce = StreamTransformer.fromBind(
+ (s) => s.debounce(const Duration(milliseconds: 100)));
+```
+
+[fromBind]: https://api.dart.dev/stable/dart-async/StreamTransformer/StreamTransformer.fromBind.html
diff --git a/pkgs/stream_transform/analysis_options.yaml b/pkgs/stream_transform/analysis_options.yaml
new file mode 100644
index 0000000..05f1af1
--- /dev/null
+++ b/pkgs/stream_transform/analysis_options.yaml
@@ -0,0 +1,16 @@
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-casts: true
+ strict-raw-types: true
+
+linter:
+ rules:
+ - avoid_bool_literals_in_conditional_expressions
+ - avoid_classes_with_only_static_members
+ - avoid_returning_this
+ - avoid_unused_constructor_parameters
+ - cascade_invocations
+ - join_return_with_assignment
+ - no_adjacent_strings_in_list
diff --git a/pkgs/stream_transform/example/index.html b/pkgs/stream_transform/example/index.html
new file mode 100644
index 0000000..aecdc09
--- /dev/null
+++ b/pkgs/stream_transform/example/index.html
@@ -0,0 +1,11 @@
+<html>
+ <head>
+ <script defer src="main.dart.js" type="application/javascript"></script>
+ </head>
+ <body>
+ <input id="first_input"><br>
+ <input id="second_input"><br>
+ <p id="output">
+ </p>
+ </body>
+</html>
diff --git a/pkgs/stream_transform/example/main.dart b/pkgs/stream_transform/example/main.dart
new file mode 100644
index 0000000..8224393
--- /dev/null
+++ b/pkgs/stream_transform/example/main.dart
@@ -0,0 +1,25 @@
+// 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 'package:stream_transform/stream_transform.dart';
+import 'package:web/web.dart';
+
+void main() {
+ var firstInput = document.querySelector('#first_input') as HTMLInputElement;
+ var secondInput = document.querySelector('#second_input') as HTMLInputElement;
+ var output = document.querySelector('#output')!;
+
+ _inputValues(firstInput)
+ .combineLatest(_inputValues(secondInput),
+ (first, second) => 'First: $first, Second: $second')
+ .tap((v) {
+ print('Saw: $v');
+ }).forEach((v) {
+ output.text = v;
+ });
+}
+
+Stream<String?> _inputValues(HTMLInputElement element) => element.onKeyUp
+ .debounce(const Duration(milliseconds: 100))
+ .map((_) => element.value);
diff --git a/pkgs/stream_transform/lib/src/aggregate_sample.dart b/pkgs/stream_transform/lib/src/aggregate_sample.dart
new file mode 100644
index 0000000..f2ff8ed
--- /dev/null
+++ b/pkgs/stream_transform/lib/src/aggregate_sample.dart
@@ -0,0 +1,146 @@
+// 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:async';
+
+import 'common_callbacks.dart';
+
+extension AggregateSample<T> on Stream<T> {
+ /// Computes a value based on sequences of events, then emits that value when
+ /// [trigger] emits an event.
+ ///
+ /// Every time this stream emits an event, an intermediate value is created
+ /// by combining the new event with the previous intermediate value, or with
+ /// `null` if there is no previous value, using the [aggregate] function.
+ ///
+ /// When [trigger] emits value, the returned stream emits the current
+ /// intermediate value and clears it.
+ ///
+ /// If [longPoll] is `false`, if there is no intermediate value when [trigger]
+ /// emits an event, the [onEmpty] function is called with a [Sink] which can
+ /// add events to the returned stream.
+ ///
+ /// If [longPoll] is `true`, and there is no intermediate value when [trigger]
+ /// emits one or more events, then the *next* event from this stream is
+ /// immediately put through [aggregate] and emitted on the returned stream.
+ /// Subsequent events on [trigger] while there have been no events on this
+ /// stream are ignored.
+ /// In that case, [onEmpty] is never used.
+ ///
+ /// The result stream will close as soon as there is a guarantee it will not
+ /// emit any more events. There will not be any more events emitted if:
+ /// - [trigger] is closed and there is no waiting long poll.
+ /// - Or, the source stream is closed and there are no buffered events.
+ ///
+ /// If the source stream is a broadcast stream, the result will be as well.
+ /// Errors from the source stream or the trigger are immediately forwarded to
+ /// the output.
+ Stream<S> aggregateSample<S>(
+ {required Stream<void> trigger,
+ required S Function(T, S?) aggregate,
+ required bool longPoll,
+ required void Function(Sink<S>) onEmpty}) {
+ var controller = isBroadcast
+ ? StreamController<S>.broadcast(sync: true)
+ : StreamController<S>(sync: true);
+
+ S? currentResults;
+ var hasCurrentResults = false;
+ var activeLongPoll = false;
+ var isTriggerDone = false;
+ var isValueDone = false;
+ StreamSubscription<T>? valueSub;
+ StreamSubscription<void>? triggerSub;
+
+ void emit(S results) {
+ currentResults = null;
+ hasCurrentResults = false;
+ controller.add(results);
+ }
+
+ void onValue(T value) {
+ currentResults = aggregate(value, currentResults);
+ hasCurrentResults = true;
+ if (!longPoll) return;
+
+ if (activeLongPoll) {
+ activeLongPoll = false;
+ emit(currentResults as S);
+ }
+
+ if (isTriggerDone) {
+ valueSub!.cancel();
+ controller.close();
+ }
+ }
+
+ void onValuesDone() {
+ isValueDone = true;
+ if (!hasCurrentResults) {
+ triggerSub?.cancel();
+ controller.close();
+ }
+ }
+
+ void onTrigger(_) {
+ if (hasCurrentResults) {
+ emit(currentResults as S);
+ } else if (longPoll) {
+ activeLongPoll = true;
+ } else {
+ onEmpty(controller);
+ }
+
+ if (isValueDone) {
+ triggerSub!.cancel();
+ controller.close();
+ }
+ }
+
+ void onTriggerDone() {
+ isTriggerDone = true;
+ if (!activeLongPoll) {
+ valueSub?.cancel();
+ controller.close();
+ }
+ }
+
+ controller.onListen = () {
+ assert(valueSub == null);
+ valueSub =
+ listen(onValue, onError: controller.addError, onDone: onValuesDone);
+ final priorTriggerSub = triggerSub;
+ if (priorTriggerSub != null) {
+ if (priorTriggerSub.isPaused) priorTriggerSub.resume();
+ } else {
+ triggerSub = trigger.listen(onTrigger,
+ onError: controller.addError, onDone: onTriggerDone);
+ }
+ if (!isBroadcast) {
+ controller
+ ..onPause = () {
+ valueSub?.pause();
+ triggerSub?.pause();
+ }
+ ..onResume = () {
+ valueSub?.resume();
+ triggerSub?.resume();
+ };
+ }
+ controller.onCancel = () {
+ var cancels = <Future<void>>[if (!isValueDone) valueSub!.cancel()];
+ valueSub = null;
+ if (trigger.isBroadcast || !isBroadcast) {
+ if (!isTriggerDone) cancels.add(triggerSub!.cancel());
+ triggerSub = null;
+ } else {
+ triggerSub!.pause();
+ }
+ if (cancels.isEmpty) return null;
+ return cancels.wait.then(ignoreArgument);
+ };
+ };
+ return controller.stream;
+ }
+}
diff --git a/pkgs/stream_transform/lib/src/async_expand.dart b/pkgs/stream_transform/lib/src/async_expand.dart
new file mode 100644
index 0000000..28d2f40
--- /dev/null
+++ b/pkgs/stream_transform/lib/src/async_expand.dart
@@ -0,0 +1,89 @@
+// 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 'dart:async';
+
+import 'common_callbacks.dart';
+import 'switch.dart';
+
+/// Alternatives to [asyncExpand].
+///
+/// The built in [asyncExpand] will not overlap the inner streams and every
+/// event will be sent to the callback individually.
+///
+/// - [concurrentAsyncExpand] allow overlap and merges inner streams without
+/// ordering guarantees.
+extension AsyncExpand<T> on Stream<T> {
+ /// Like [asyncExpand] but the [convert] callback may be called for an element
+ /// before the [Stream] emitted by the previous element has closed.
+ ///
+ /// Events on the result stream will be emitted in the order they are emitted
+ /// by the sub streams, which may not match the order of this stream.
+ ///
+ /// Errors from [convert], the source stream, or any of the sub streams are
+ /// forwarded to the result stream.
+ ///
+ /// The result stream will not close until the source stream closes and all
+ /// sub streams have closed.
+ ///
+ /// If the source stream is a broadcast stream, the result will be as well,
+ /// regardless of the types of streams created by [convert]. In this case,
+ /// some care should be taken:
+ /// - If [convert] returns a single subscription stream it may be listened to
+ /// and never canceled.
+ /// - For any period of time where there are no listeners on the result
+ /// stream, any sub streams from previously emitted events will be ignored,
+ /// regardless of whether they emit further events after a listener is added
+ /// back.
+ ///
+ /// See also:
+ /// - [switchMap], which cancels subscriptions to the previous sub stream
+ /// instead of concurrently emitting events from all sub streams.
+ Stream<S> concurrentAsyncExpand<S>(Stream<S> Function(T) convert) {
+ final controller = isBroadcast
+ ? StreamController<S>.broadcast(sync: true)
+ : StreamController<S>(sync: true);
+
+ controller.onListen = () {
+ final subscriptions = <StreamSubscription<dynamic>>[];
+ final outerSubscription = map(convert).listen((inner) {
+ if (isBroadcast && !inner.isBroadcast) {
+ inner = inner.asBroadcastStream();
+ }
+ final subscription =
+ inner.listen(controller.add, onError: controller.addError);
+ subscription.onDone(() {
+ subscriptions.remove(subscription);
+ if (subscriptions.isEmpty) controller.close();
+ });
+ subscriptions.add(subscription);
+ }, onError: controller.addError);
+ outerSubscription.onDone(() {
+ subscriptions.remove(outerSubscription);
+ if (subscriptions.isEmpty) controller.close();
+ });
+ subscriptions.add(outerSubscription);
+ if (!isBroadcast) {
+ controller
+ ..onPause = () {
+ for (final subscription in subscriptions) {
+ subscription.pause();
+ }
+ }
+ ..onResume = () {
+ for (final subscription in subscriptions) {
+ subscription.resume();
+ }
+ };
+ }
+ controller.onCancel = () {
+ if (subscriptions.isEmpty) return null;
+ return [for (var s in subscriptions) s.cancel()]
+ .wait
+ .then(ignoreArgument);
+ };
+ };
+ return controller.stream;
+ }
+}
diff --git a/pkgs/stream_transform/lib/src/async_map.dart b/pkgs/stream_transform/lib/src/async_map.dart
new file mode 100644
index 0000000..094df9c
--- /dev/null
+++ b/pkgs/stream_transform/lib/src/async_map.dart
@@ -0,0 +1,136 @@
+// Copyright (c) 2017, 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 'aggregate_sample.dart';
+import 'common_callbacks.dart';
+import 'from_handlers.dart';
+import 'rate_limit.dart';
+
+/// Alternatives to [asyncMap].
+///
+/// The built in [asyncMap] will not overlap execution of the passed callback,
+/// and every event will be sent to the callback individually.
+///
+/// - [asyncMapBuffer] prevents the callback from overlapping execution and
+/// collects events while it is executing to process in batches.
+/// - [asyncMapSample] prevents overlapping execution and discards events while
+/// it is executing.
+/// - [concurrentAsyncMap] allows overlap and removes ordering guarantees.
+extension AsyncMap<T> on Stream<T> {
+ /// Like [asyncMap] but events are buffered until previous events have been
+ /// processed by [convert].
+ ///
+ /// If this stream is a broadcast stream the result will be as well.
+ /// When used with a broadcast stream behavior also differs from [asyncMap] in
+ /// that the [convert] function is only called once per event, rather than
+ /// once per listener per event.
+ ///
+ /// The first event from this stream is always passed to [convert] as a
+ /// list with a single element.
+ /// After that, events are buffered until the previous Future returned from
+ /// [convert] has completed.
+ ///
+ /// Errors from this stream are forwarded directly to the result stream.
+ /// Errors during the conversion are also forwarded to the result stream and
+ /// are considered completing work so the next values are let through.
+ ///
+ /// The result stream will not close until this stream closes and all pending
+ /// conversions have finished.
+ Stream<S> asyncMapBuffer<S>(Future<S> Function(List<T>) convert) {
+ var workFinished = StreamController<void>()
+ // Let the first event through.
+ ..add(null);
+ return buffer(workFinished.stream)._asyncMapThen(convert, workFinished.add);
+ }
+
+ /// Like [asyncMap] but events are discarded while work is happening in
+ /// [convert].
+ ///
+ /// If this stream is a broadcast stream the result will be as well.
+ /// When used with a broadcast stream behavior also differs from [asyncMap] in
+ /// that the [convert] function is only called once per event, rather than
+ /// once per listener per event.
+ ///
+ /// If no work is happening when an event is emitted it will be immediately
+ /// passed to [convert]. If there is ongoing work when an event is emitted it
+ /// will be held until the work is finished. New events emitted will replace a
+ /// pending event.
+ ///
+ /// Errors from this stream are forwarded directly to the result stream.
+ /// Errors during the conversion are also forwarded to the result stream and
+ /// are considered completing work so the next values are let through.
+ ///
+ /// The result stream will not close until this stream closes and all pending
+ /// conversions have finished.
+ Stream<S> asyncMapSample<S>(Future<S> Function(T) convert) {
+ var workFinished = StreamController<void>()
+ // Let the first event through.
+ ..add(null);
+ return aggregateSample(
+ trigger: workFinished.stream,
+ aggregate: _dropPrevious,
+ longPoll: true,
+ onEmpty: ignoreArgument)
+ ._asyncMapThen(convert, workFinished.add);
+ }
+
+ /// Like [asyncMap] but the [convert] callback may be called for an element
+ /// before processing for the previous element is finished.
+ ///
+ /// Events on the result stream will be emitted in the order that [convert]
+ /// completed which may not match the order of this stream.
+ ///
+ /// If this stream is a broadcast stream the result will be as well.
+ /// When used with a broadcast stream behavior also differs from [asyncMap] in
+ /// that the [convert] function is only called once per event, rather than
+ /// once per listener per event. The [convert] callback won't be called for
+ /// events while a broadcast stream has no listener.
+ ///
+ /// Errors from [convert] or this stream are forwarded directly to the
+ /// result stream.
+ ///
+ /// The result stream will not close until this stream closes and all pending
+ /// conversions have finished.
+ Stream<S> concurrentAsyncMap<S>(FutureOr<S> Function(T) convert) {
+ var valuesWaiting = 0;
+ var sourceDone = false;
+ return transformByHandlers(onData: (element, sink) {
+ valuesWaiting++;
+ () async {
+ try {
+ sink.add(await convert(element));
+ } catch (e, st) {
+ sink.addError(e, st);
+ }
+ valuesWaiting--;
+ if (valuesWaiting <= 0 && sourceDone) sink.close();
+ }();
+ }, onDone: (sink) {
+ sourceDone = true;
+ if (valuesWaiting <= 0) sink.close();
+ });
+ }
+
+ /// Like [Stream.asyncMap] but the [convert] is only called once per event,
+ /// rather than once per listener, and [then] is called after completing the
+ /// work.
+ Stream<S> _asyncMapThen<S>(
+ Future<S> Function(T) convert, void Function(void) then) {
+ Future<void>? pendingEvent;
+ return transformByHandlers(onData: (event, sink) {
+ pendingEvent =
+ convert(event).then(sink.add).catchError(sink.addError).then(then);
+ }, onDone: (sink) {
+ if (pendingEvent != null) {
+ pendingEvent!.then((_) => sink.close());
+ } else {
+ sink.close();
+ }
+ });
+ }
+}
+
+T _dropPrevious<T>(T event, _) => event;
diff --git a/pkgs/stream_transform/lib/src/combine_latest.dart b/pkgs/stream_transform/lib/src/combine_latest.dart
new file mode 100644
index 0000000..f02a19e
--- /dev/null
+++ b/pkgs/stream_transform/lib/src/combine_latest.dart
@@ -0,0 +1,240 @@
+// 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:async';
+
+import 'common_callbacks.dart';
+
+/// Utilities to combine events from multiple streams through a callback or into
+/// a list.
+extension CombineLatest<T> on Stream<T> {
+ /// Combines the latest values from this stream with the latest values from
+ /// [other] using [combine].
+ ///
+ /// No event will be emitted until both the source stream and [other] have
+ /// each emitted at least one event. If either the source stream or [other]
+ /// emit multiple events before the other emits the first event, all but the
+ /// last value will be discarded. Once both streams have emitted at least
+ /// once, the result stream will emit any time either input stream emits.
+ ///
+ /// The result stream will not close until both the source stream and [other]
+ /// have closed.
+ ///
+ /// For example:
+ ///
+ /// source.combineLatest(other, (a, b) => a + b);
+ ///
+ /// source: --1--2--------4--|
+ /// other: -------3--|
+ /// result: -------5------7--|
+ ///
+ /// Errors thrown by [combine], along with any errors on the source stream or
+ /// [other], are forwarded to the result stream.
+ ///
+ /// If the source stream is a broadcast stream, the result stream will be as
+ /// well, regardless of [other]'s type. If a single subscription stream is
+ /// combined with a broadcast stream it may never be canceled.
+ Stream<S> combineLatest<T2, S>(
+ Stream<T2> other, FutureOr<S> Function(T, T2) combine) {
+ final controller = isBroadcast
+ ? StreamController<S>.broadcast(sync: true)
+ : StreamController<S>(sync: true);
+
+ other =
+ (isBroadcast && !other.isBroadcast) ? other.asBroadcastStream() : other;
+
+ StreamSubscription<T>? sourceSubscription;
+ StreamSubscription<T2>? otherSubscription;
+
+ var sourceDone = false;
+ var otherDone = false;
+
+ late T latestSource;
+ late T2 latestOther;
+
+ var sourceStarted = false;
+ var otherStarted = false;
+
+ void emitCombined() {
+ if (!sourceStarted || !otherStarted) return;
+ FutureOr<S> result;
+ try {
+ result = combine(latestSource, latestOther);
+ } catch (e, s) {
+ controller.addError(e, s);
+ return;
+ }
+ if (result is Future<S>) {
+ sourceSubscription!.pause();
+ otherSubscription!.pause();
+ result
+ .then(controller.add, onError: controller.addError)
+ .whenComplete(() {
+ sourceSubscription!.resume();
+ otherSubscription!.resume();
+ });
+ } else {
+ controller.add(result);
+ }
+ }
+
+ controller.onListen = () {
+ assert(sourceSubscription == null);
+ sourceSubscription = listen(
+ (s) {
+ sourceStarted = true;
+ latestSource = s;
+ emitCombined();
+ },
+ onError: controller.addError,
+ onDone: () {
+ sourceDone = true;
+ if (otherDone) {
+ controller.close();
+ } else if (!sourceStarted) {
+ // Nothing can ever be emitted
+ otherSubscription!.cancel();
+ controller.close();
+ }
+ });
+ otherSubscription = other.listen(
+ (o) {
+ otherStarted = true;
+ latestOther = o;
+ emitCombined();
+ },
+ onError: controller.addError,
+ onDone: () {
+ otherDone = true;
+ if (sourceDone) {
+ controller.close();
+ } else if (!otherStarted) {
+ // Nothing can ever be emitted
+ sourceSubscription!.cancel();
+ controller.close();
+ }
+ });
+ if (!isBroadcast) {
+ controller
+ ..onPause = () {
+ sourceSubscription!.pause();
+ otherSubscription!.pause();
+ }
+ ..onResume = () {
+ sourceSubscription!.resume();
+ otherSubscription!.resume();
+ };
+ }
+ controller.onCancel = () {
+ var cancels = [
+ sourceSubscription!.cancel(),
+ otherSubscription!.cancel()
+ ];
+ sourceSubscription = null;
+ otherSubscription = null;
+ return cancels.wait.then(ignoreArgument);
+ };
+ };
+ return controller.stream;
+ }
+
+ /// Combine the latest value emitted from the source stream with the latest
+ /// values emitted from [others].
+ ///
+ /// [combineLatestAll] subscribes to the source stream and [others] and when
+ /// any one of the streams emits, the result stream will emit a [List<T>] of
+ /// the latest values emitted from all streams.
+ ///
+ /// No event will be emitted until all source streams emit at least once. If a
+ /// source stream emits multiple values before another starts emitting, all
+ /// but the last value will be discarded. Once all source streams have emitted
+ /// at least once, the result stream will emit any time any source stream
+ /// emits.
+ ///
+ /// The result stream will not close until all source streams have closed.
+ /// When a source stream closes, the result stream will continue to emit the
+ /// last value from the closed stream when the other source streams emit until
+ /// the result stream has closed. If a source stream closes without emitting
+ /// any value, the result stream will close as well.
+ ///
+ /// For example:
+ ///
+ /// final combined = first
+ /// .combineLatestAll([second, third])
+ /// .map((data) => data.join());
+ ///
+ /// first: a----b------------------c--------d---|
+ /// second: --1---------2-----------------|
+ /// third: -------&----------%---|
+ /// combined: -------b1&--b2&---b2%---c2%------d2%-|
+ ///
+ /// Errors thrown by any source stream will be forwarded to the result stream.
+ ///
+ /// If the source stream is a broadcast stream, the result stream will be as
+ /// well, regardless of the types of [others]. If a single subscription stream
+ /// is combined with a broadcast source stream, it may never be canceled.
+ Stream<List<T>> combineLatestAll(Iterable<Stream<T>> others) {
+ final controller = isBroadcast
+ ? StreamController<List<T>>.broadcast(sync: true)
+ : StreamController<List<T>>(sync: true);
+
+ final allStreams = [
+ this,
+ for (final other in others)
+ !isBroadcast || other.isBroadcast ? other : other.asBroadcastStream(),
+ ];
+
+ controller.onListen = () {
+ final subscriptions = <StreamSubscription<T>>[];
+
+ final latestData = List<T?>.filled(allStreams.length, null);
+ final hasEmitted = <int>{};
+ void handleData(int index, T data) {
+ latestData[index] = data;
+ hasEmitted.add(index);
+ if (hasEmitted.length == allStreams.length) {
+ controller.add(List.from(latestData));
+ }
+ }
+
+ var streamId = 0;
+ for (final stream in allStreams) {
+ final index = streamId;
+
+ final subscription = stream.listen((data) => handleData(index, data),
+ onError: controller.addError);
+ subscription.onDone(() {
+ assert(subscriptions.contains(subscription));
+ subscriptions.remove(subscription);
+ if (subscriptions.isEmpty || !hasEmitted.contains(index)) {
+ controller.close();
+ }
+ });
+ subscriptions.add(subscription);
+
+ streamId++;
+ }
+ if (!isBroadcast) {
+ controller
+ ..onPause = () {
+ for (final subscription in subscriptions) {
+ subscription.pause();
+ }
+ }
+ ..onResume = () {
+ for (final subscription in subscriptions) {
+ subscription.resume();
+ }
+ };
+ }
+ controller.onCancel = () {
+ if (subscriptions.isEmpty) return null;
+ return [for (var s in subscriptions) s.cancel()]
+ .wait
+ .then(ignoreArgument);
+ };
+ };
+ return controller.stream;
+ }
+}
diff --git a/pkgs/stream_transform/lib/src/common_callbacks.dart b/pkgs/stream_transform/lib/src/common_callbacks.dart
new file mode 100644
index 0000000..e211cf9
--- /dev/null
+++ b/pkgs/stream_transform/lib/src/common_callbacks.dart
@@ -0,0 +1,5 @@
+// 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.
+
+void ignoreArgument(Object? _) {}
diff --git a/pkgs/stream_transform/lib/src/concatenate.dart b/pkgs/stream_transform/lib/src/concatenate.dart
new file mode 100644
index 0000000..0330dd7
--- /dev/null
+++ b/pkgs/stream_transform/lib/src/concatenate.dart
@@ -0,0 +1,112 @@
+// Copyright (c) 2017, 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';
+
+/// Utilities to append or prepend to a stream.
+extension Concatenate<T> on Stream<T> {
+ /// Emits all values and errors from [next] following all values and errors
+ /// from this stream.
+ ///
+ /// If this stream never finishes, the [next] stream will never get a
+ /// listener.
+ ///
+ /// If this stream is a broadcast stream, the result will be as well.
+ /// If a single-subscription follows a broadcast stream it may be listened
+ /// to and never canceled since there may be broadcast listeners added later.
+ ///
+ /// If a broadcast stream follows any other stream it will miss any events or
+ /// errors which occur before this stream is done.
+ /// If a broadcast stream follows a single-subscription stream, pausing the
+ /// stream while it is listening to the second stream will cause events to be
+ /// dropped rather than buffered.
+ Stream<T> followedBy(Stream<T> next) {
+ var controller = isBroadcast
+ ? StreamController<T>.broadcast(sync: true)
+ : StreamController<T>(sync: true);
+
+ next = isBroadcast && !next.isBroadcast ? next.asBroadcastStream() : next;
+
+ StreamSubscription<T>? subscription;
+ var currentStream = this;
+ var thisDone = false;
+ var secondDone = false;
+
+ late void Function() currentDoneHandler;
+
+ void listen() {
+ subscription = currentStream.listen(controller.add,
+ onError: controller.addError, onDone: () => currentDoneHandler());
+ }
+
+ void onSecondDone() {
+ secondDone = true;
+ controller.close();
+ }
+
+ void onThisDone() {
+ thisDone = true;
+ currentStream = next;
+ currentDoneHandler = onSecondDone;
+ listen();
+ }
+
+ currentDoneHandler = onThisDone;
+
+ controller.onListen = () {
+ assert(subscription == null);
+ listen();
+ if (!isBroadcast) {
+ controller
+ ..onPause = () {
+ if (!thisDone || !next.isBroadcast) return subscription!.pause();
+ subscription!.cancel();
+ subscription = null;
+ }
+ ..onResume = () {
+ if (!thisDone || !next.isBroadcast) return subscription!.resume();
+ listen();
+ };
+ }
+ controller.onCancel = () {
+ if (secondDone) return null;
+ var toCancel = subscription!;
+ subscription = null;
+ return toCancel.cancel();
+ };
+ };
+ return controller.stream;
+ }
+
+ /// Emits [initial] before any values or errors from the this stream.
+ ///
+ /// If this stream is a broadcast stream the result will be as well.
+ /// If this stream is a broadcast stream, the returned stream will only
+ /// contain events of this stream that are emitted after the [initial] value
+ /// has been emitted on the returned stream.
+ Stream<T> startWith(T initial) =>
+ startWithStream(Future.value(initial).asStream());
+
+ /// Emits all values in [initial] before any values or errors from this
+ /// stream.
+ ///
+ /// If this stream is a broadcast stream the result will be as well.
+ /// If this stream is a broadcast stream it will miss any events which
+ /// occur before the initial values are all emitted.
+ Stream<T> startWithMany(Iterable<T> initial) =>
+ startWithStream(Stream.fromIterable(initial));
+
+ /// Emits all values and errors in [initial] before any values or errors from
+ /// this stream.
+ ///
+ /// If this stream is a broadcast stream the result will be as well.
+ /// If this stream is a broadcast stream it will miss any events which occur
+ /// before [initial] closes.
+ Stream<T> startWithStream(Stream<T> initial) {
+ if (isBroadcast && !initial.isBroadcast) {
+ initial = initial.asBroadcastStream();
+ }
+ return initial.followedBy(this);
+ }
+}
diff --git a/pkgs/stream_transform/lib/src/from_handlers.dart b/pkgs/stream_transform/lib/src/from_handlers.dart
new file mode 100644
index 0000000..1146a13
--- /dev/null
+++ b/pkgs/stream_transform/lib/src/from_handlers.dart
@@ -0,0 +1,58 @@
+// Copyright (c) 2017, 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';
+
+extension TransformByHandlers<S> on Stream<S> {
+ /// Transform a stream by callbacks.
+ ///
+ /// This is similar to `transform(StreamTransformer.fromHandler(...))` except
+ /// that the handlers are called once per event rather than called for the
+ /// same event for each listener on a broadcast stream.
+ Stream<T> transformByHandlers<T>(
+ {required void Function(S, EventSink<T>) onData,
+ void Function(Object, StackTrace, EventSink<T>)? onError,
+ void Function(EventSink<T>)? onDone}) {
+ final handleError = onError ?? _defaultHandleError;
+ final handleDone = onDone ?? _defaultHandleDone;
+
+ var controller = isBroadcast
+ ? StreamController<T>.broadcast(sync: true)
+ : StreamController<T>(sync: true);
+
+ StreamSubscription<S>? subscription;
+ controller.onListen = () {
+ assert(subscription == null);
+ var valuesDone = false;
+ subscription = listen((value) => onData(value, controller),
+ onError: (Object error, StackTrace stackTrace) {
+ handleError(error, stackTrace, controller);
+ }, onDone: () {
+ valuesDone = true;
+ handleDone(controller);
+ });
+ if (!isBroadcast) {
+ controller
+ ..onPause = subscription!.pause
+ ..onResume = subscription!.resume;
+ }
+ controller.onCancel = () {
+ var toCancel = subscription;
+ subscription = null;
+ if (!valuesDone) return toCancel!.cancel();
+ return null;
+ };
+ };
+ return controller.stream;
+ }
+
+ static void _defaultHandleError<T>(
+ Object error, StackTrace stackTrace, EventSink<T> sink) {
+ sink.addError(error, stackTrace);
+ }
+
+ static void _defaultHandleDone<T>(EventSink<T> sink) {
+ sink.close();
+ }
+}
diff --git a/pkgs/stream_transform/lib/src/merge.dart b/pkgs/stream_transform/lib/src/merge.dart
new file mode 100644
index 0000000..3bfe06c
--- /dev/null
+++ b/pkgs/stream_transform/lib/src/merge.dart
@@ -0,0 +1,102 @@
+// Copyright (c) 2017, 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 'common_callbacks.dart';
+
+/// Utilities to interleave events from multiple streams.
+extension Merge<T> on Stream<T> {
+ /// Merges values and errors from this stream and [other] in any order as they
+ /// arrive.
+ ///
+ /// The result stream will not close until both this stream and [other] have
+ /// closed.
+ ///
+ /// For example:
+ ///
+ /// final result = source.merge(other);
+ ///
+ /// source: 1--2-----3--|
+ /// other: ------4-------5--|
+ /// result: 1--2--4--3----5--|
+ ///
+ /// If this stream is a broadcast stream, the result stream will be as
+ /// well, regardless of [other]'s type. If a single subscription stream is
+ /// merged into a broadcast stream it may never be canceled since there may be
+ /// broadcast listeners added later.
+ ///
+ /// If a broadcast stream is merged into a single-subscription stream any
+ /// events emitted by [other] before the result stream has a subscriber will
+ /// be discarded.
+ Stream<T> merge(Stream<T> other) => mergeAll([other]);
+
+ /// Merges values and errors from this stream and any stream in [others] in
+ /// any order as they arrive.
+ ///
+ /// The result stream will not close until this stream and all streams
+ /// in [others] have closed.
+ ///
+ /// For example:
+ ///
+ /// final result = first.mergeAll([second, third]);
+ ///
+ /// first: 1--2--------3--|
+ /// second: ---------4-------5--|
+ /// third: ------6---------------7--|
+ /// result: 1--2--6--4--3----5----7--|
+ ///
+ /// If this stream is a broadcast stream, the result stream will be as
+ /// well, regardless the types of streams in [others]. If a single
+ /// subscription stream is merged into a broadcast stream it may never be
+ /// canceled since there may be broadcast listeners added later.
+ ///
+ /// If a broadcast stream is merged into a single-subscription stream any
+ /// events emitted by that stream before the result stream has a subscriber
+ /// will be discarded.
+ Stream<T> mergeAll(Iterable<Stream<T>> others) {
+ final controller = isBroadcast
+ ? StreamController<T>.broadcast(sync: true)
+ : StreamController<T>(sync: true);
+
+ final allStreams = [
+ this,
+ for (final other in others)
+ !isBroadcast || other.isBroadcast ? other : other.asBroadcastStream(),
+ ];
+
+ controller.onListen = () {
+ final subscriptions = <StreamSubscription<T>>[];
+ for (final stream in allStreams) {
+ final subscription =
+ stream.listen(controller.add, onError: controller.addError);
+ subscription.onDone(() {
+ subscriptions.remove(subscription);
+ if (subscriptions.isEmpty) controller.close();
+ });
+ subscriptions.add(subscription);
+ }
+ if (!isBroadcast) {
+ controller
+ ..onPause = () {
+ for (final subscription in subscriptions) {
+ subscription.pause();
+ }
+ }
+ ..onResume = () {
+ for (final subscription in subscriptions) {
+ subscription.resume();
+ }
+ };
+ }
+ controller.onCancel = () {
+ if (subscriptions.isEmpty) return null;
+ return [for (var s in subscriptions) s.cancel()]
+ .wait
+ .then(ignoreArgument);
+ };
+ };
+ return controller.stream;
+ }
+}
diff --git a/pkgs/stream_transform/lib/src/rate_limit.dart b/pkgs/stream_transform/lib/src/rate_limit.dart
new file mode 100644
index 0000000..299c230
--- /dev/null
+++ b/pkgs/stream_transform/lib/src/rate_limit.dart
@@ -0,0 +1,356 @@
+// 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:async';
+
+import 'aggregate_sample.dart';
+import 'common_callbacks.dart';
+import 'from_handlers.dart';
+
+/// Utilities to rate limit events.
+///
+/// - [debounce] - emit the the _first_ or _last_ event of a series of closely
+/// spaced events.
+/// - [debounceBuffer] - emit _all_ events at the _end_ of a series of closely
+/// spaced events.
+/// - [throttle] - emit the _first_ event at the _beginning_ of the period.
+/// - [audit] - emit the _last_ event at the _end_ of the period.
+/// - [buffer] - emit _all_ events on a _trigger_.
+extension RateLimit<T> on Stream<T> {
+ /// Suppresses events with less inter-event spacing than [duration].
+ ///
+ /// Events which are emitted with less than [duration] elapsed between them
+ /// are considered to be part of the same "series". If [leading] is `true`,
+ /// the first event of this series is emitted immediately. If [trailing] is
+ /// `true` the last event of this series is emitted with a delay of at least
+ /// [duration]. By default only trailing events are emitted, both arguments
+ /// must be specified with `leading: true, trailing: false` to emit only
+ /// leading events.
+ ///
+ /// If this stream is a broadcast stream, the result will be as well.
+ /// Errors are forwarded immediately.
+ ///
+ /// If there is a trailing event waiting during the debounce period when the
+ /// source stream closes the returned stream will wait to emit it following
+ /// the debounce period before closing. If there is no pending debounced event
+ /// when this stream closes the returned stream will close immediately.
+ ///
+ /// For example:
+ ///
+ /// source.debounce(Duration(seconds: 1));
+ ///
+ /// source: 1-2-3---4---5-6-|
+ /// result: ------3---4-----6|
+ ///
+ /// source.debounce(Duration(seconds: 1), leading: true, trailing: false);
+ ///
+ /// source: 1-2-3---4---5-6-|
+ /// result: 1-------4---5---|
+ ///
+ /// source.debounce(Duration(seconds: 1), leading: true);
+ ///
+ /// source: 1-2-3---4---5-6-|
+ /// result: 1-----3-4---5---6|
+ ///
+ /// To collect values emitted during the debounce period see [debounceBuffer].
+ Stream<T> debounce(Duration duration,
+ {bool leading = false, bool trailing = true}) =>
+ _debounceAggregate(duration, _dropPrevious,
+ leading: leading, trailing: trailing);
+
+ /// Buffers values until this stream does not emit for [duration] then emits
+ /// the collected values.
+ ///
+ /// Values will always be delayed by at least [duration], and values which
+ /// come within this time will be aggregated into the same list.
+ ///
+ /// If this stream is a broadcast stream, the result will be as well.
+ /// Errors are forwarded immediately.
+ ///
+ /// If there are events waiting during the debounce period when this stream
+ /// closes the returned stream will wait to emit them following the debounce
+ /// period before closing. If there are no pending debounced events when this
+ /// stream closes the returned stream will close immediately.
+ ///
+ /// To keep only the most recent event during the debounce period see
+ /// [debounce].
+ Stream<List<T>> debounceBuffer(Duration duration) =>
+ _debounceAggregate(duration, _collect, leading: false, trailing: true);
+
+ /// Reduces the rate that events are emitted to at most once per [duration].
+ ///
+ /// No events will ever be emitted within [duration] of another event on the
+ /// result stream.
+ /// If this stream is a broadcast stream, the result will be as well.
+ /// Errors are forwarded immediately.
+ ///
+ /// If [trailing] is `false`, source events emitted during the [duration]
+ /// period following a result event are discarded.
+ /// The result stream will not emit an event until this stream emits an event
+ /// following the throttled period.
+ /// If this stream is consistently emitting events with less than
+ /// [duration] between events, the time between events on the result stream
+ /// may still be more than [duration].
+ /// The result stream will close immediately when this stream closes.
+ ///
+ /// If [trailing] is `true`, the latest source event emitted during the
+ /// [duration] period following an result event is held and emitted following
+ /// the period.
+ /// If this stream is consistently emitting events with less than [duration]
+ /// between events, the time between events on the result stream will be
+ /// [duration].
+ /// If this stream closes the result stream will wait to emit a pending event
+ /// before closing.
+ ///
+ /// For example:
+ ///
+ /// source.throttle(Duration(seconds: 6));
+ ///
+ /// source: 1-2-3---4-5-6---7-8-|
+ /// result: 1-------4-------7---|
+ ///
+ /// source.throttle(Duration(seconds: 6), trailing: true);
+ ///
+ /// source: 1-2-3---4-5----6--|
+ /// result: 1-----3-----5-----6|
+ ///
+ /// source.throttle(Duration(seconds: 6), trailing: true);
+ ///
+ /// source: 1-2-----------3|
+ /// result: 1-----2-------3|
+ ///
+ /// See also:
+ /// - [audit], which emits the most recent event at the end of the period.
+ /// Compared to `audit`, `throttle` will not introduce delay to forwarded
+ /// elements, except for the [trailing] events.
+ /// - [debounce], which uses inter-event spacing instead of a fixed period
+ /// from the first event in a window. Compared to `debouce`, `throttle` cannot
+ /// be starved by having events emitted continuously within [duration].
+ Stream<T> throttle(Duration duration, {bool trailing = false}) =>
+ trailing ? _throttleTrailing(duration) : _throttle(duration);
+
+ Stream<T> _throttle(Duration duration) {
+ Timer? timer;
+
+ return transformByHandlers(onData: (data, sink) {
+ if (timer == null) {
+ sink.add(data);
+ timer = Timer(duration, () {
+ timer = null;
+ });
+ }
+ });
+ }
+
+ Stream<T> _throttleTrailing(Duration duration) {
+ Timer? timer;
+ T? pending;
+ var hasPending = false;
+ var isDone = false;
+
+ return transformByHandlers(onData: (data, sink) {
+ void onTimer() {
+ if (hasPending) {
+ sink.add(pending as T);
+ if (isDone) {
+ sink.close();
+ } else {
+ timer = Timer(duration, onTimer);
+ hasPending = false;
+ pending = null;
+ }
+ } else {
+ timer = null;
+ }
+ }
+
+ if (timer == null) {
+ sink.add(data);
+ timer = Timer(duration, onTimer);
+ } else {
+ hasPending = true;
+ pending = data;
+ }
+ }, onDone: (sink) {
+ isDone = true;
+ if (hasPending) return; // Will be closed by timer.
+ sink.close();
+ timer?.cancel();
+ timer = null;
+ });
+ }
+
+ /// Audit a single event from each [duration] length period where there are
+ /// events on this stream.
+ ///
+ /// No events will ever be emitted within [duration] of another event on the
+ /// result stream.
+ /// If this stream is a broadcast stream, the result will be as well.
+ /// Errors are forwarded immediately.
+ ///
+ /// The first event will begin the audit period. At the end of the audit
+ /// period the most recent event is emitted, and the next event restarts the
+ /// audit period.
+ ///
+ /// If the event that started the period is the one that is emitted it will be
+ /// delayed by [duration]. If a later event comes in within the period it's
+ /// delay will be shorter by the difference in arrival times.
+ ///
+ /// If there is no pending event when this stream closes the output
+ /// stream will close immediately. If there is a pending event the output
+ /// stream will wait to emit it before closing.
+ ///
+ /// For example:
+ ///
+ /// source.audit(Duration(seconds: 5));
+ ///
+ /// source: a------b--c----d--|
+ /// output: -----a------c--------d|
+ ///
+ /// See also:
+ /// - [throttle], which emits the _first_ event during the window, instead of
+ /// the last event in the window. Compared to `throttle`, `audit` will
+ /// introduce delay to forwarded events.
+ /// - [debounce], which only emits after the stream has not emitted for some
+ /// period. Compared to `debouce`, `audit` cannot be starved by having events
+ /// emitted continuously within [duration].
+ Stream<T> audit(Duration duration) {
+ Timer? timer;
+ var shouldClose = false;
+ T recentData;
+
+ return transformByHandlers(onData: (data, sink) {
+ recentData = data;
+ timer ??= Timer(duration, () {
+ sink.add(recentData);
+ timer = null;
+ if (shouldClose) {
+ sink.close();
+ }
+ });
+ }, onDone: (sink) {
+ if (timer != null) {
+ shouldClose = true;
+ } else {
+ sink.close();
+ }
+ });
+ }
+
+ /// Buffers the values emitted on this stream and emits them when [trigger]
+ /// emits an event.
+ ///
+ /// If [longPoll] is `false`, if there are no buffered values when [trigger]
+ /// emits an empty list is immediately emitted.
+ ///
+ /// If [longPoll] is `true`, and there are no buffered values when [trigger]
+ /// emits one or more events, then the *next* value from this stream is
+ /// immediately emitted on the returned stream as a single element list.
+ /// Subsequent events on [trigger] while there have been no events on this
+ /// stream are ignored.
+ ///
+ /// The result stream will close as soon as there is a guarantee it will not
+ /// emit any more events. There will not be any more events emitted if:
+ /// - [trigger] is closed and there is no waiting long poll.
+ /// - Or, this stream is closed and previously buffered events have been
+ /// delivered.
+ ///
+ /// If this stream is a broadcast stream, the result will be as well.
+ /// Errors from this stream or the trigger are immediately forwarded to the
+ /// output.
+ ///
+ /// See also:
+ /// - [sample] which use a [trigger] stream in the same way, but keeps only
+ /// the most recent source event.
+ Stream<List<T>> buffer(Stream<void> trigger, {bool longPoll = true}) =>
+ aggregateSample(
+ trigger: trigger,
+ aggregate: _collect,
+ longPoll: longPoll,
+ onEmpty: _empty);
+
+ /// Emits the most recent new value from this stream when [trigger] emits an
+ /// event.
+ ///
+ /// If [longPoll] is `false`, then an event on [trigger] when there is no
+ /// pending source event will be ignored.
+ /// If [longPoll] is `true` (the default), then an event on [trigger] when
+ /// there is no pending source event will cause the next source event
+ /// to immediately flow to the result stream.
+ ///
+ /// If [longPoll] is `false`, if there is no pending source event when
+ /// [trigger] emits, then the trigger event will be ignored.
+ ///
+ /// If [longPoll] is `true`, and there are no buffered values when [trigger]
+ /// emits one or more events, then the *next* value from this stream is
+ /// immediately emitted on the returned stream as a single element list.
+ /// Subsequent events on [trigger] while there have been no events on this
+ /// stream are ignored.
+ ///
+ /// The result stream will close as soon as there is a guarantee it will not
+ /// emit any more events. There will not be any more events emitted if:
+ /// - [trigger] is closed and there is no waiting long poll.
+ /// - Or, this source stream is closed and any pending source event has been
+ /// delivered.
+ ///
+ /// If this source stream is a broadcast stream, the result will be as well.
+ /// Errors from this source stream or the trigger are immediately forwarded to
+ /// the output.
+ ///
+ /// See also:
+ /// - [buffer] which use [trigger] stream in the same way, but keeps a list of
+ /// pending source events.
+ Stream<T> sample(Stream<void> trigger, {bool longPoll = true}) =>
+ aggregateSample(
+ trigger: trigger,
+ aggregate: _dropPrevious,
+ longPoll: longPoll,
+ onEmpty: ignoreArgument);
+
+ /// Aggregates values until this source stream does not emit for [duration],
+ /// then emits the aggregated values.
+ Stream<S> _debounceAggregate<S>(
+ Duration duration, S Function(T element, S? soFar) collect,
+ {required bool leading, required bool trailing}) {
+ Timer? timer;
+ S? soFar;
+ var hasPending = false;
+ var shouldClose = false;
+ var emittedLatestAsLeading = false;
+
+ return transformByHandlers(onData: (value, sink) {
+ void emit() {
+ sink.add(soFar as S);
+ soFar = null;
+ hasPending = false;
+ }
+
+ timer?.cancel();
+ soFar = collect(value, soFar);
+ hasPending = true;
+ if (timer == null && leading) {
+ emittedLatestAsLeading = true;
+ emit();
+ } else {
+ emittedLatestAsLeading = false;
+ }
+ timer = Timer(duration, () {
+ if (trailing && !emittedLatestAsLeading) emit();
+ if (shouldClose) sink.close();
+ timer = null;
+ });
+ }, onDone: (EventSink<S> sink) {
+ if (hasPending && trailing) {
+ shouldClose = true;
+ } else {
+ timer?.cancel();
+ sink.close();
+ }
+ });
+ }
+}
+
+T _dropPrevious<T>(T element, _) => element;
+List<T> _collect<T>(T event, List<T>? soFar) => (soFar ?? <T>[])..add(event);
+void _empty<T>(Sink<List<T>> sink) => sink.add([]);
diff --git a/pkgs/stream_transform/lib/src/scan.dart b/pkgs/stream_transform/lib/src/scan.dart
new file mode 100644
index 0000000..acd3c76
--- /dev/null
+++ b/pkgs/stream_transform/lib/src/scan.dart
@@ -0,0 +1,31 @@
+// Copyright (c) 2017, 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';
+
+/// A utility similar to [fold] which emits intermediate accumulations.
+extension Scan<T> on Stream<T> {
+ /// Emits a sequence of the accumulated values from repeatedly applying
+ /// [combine].
+ ///
+ /// Like [fold], but instead of producing a single value it yields each
+ /// intermediate result.
+ ///
+ /// If [combine] returns a future it will not be called again for subsequent
+ /// events from the source until it completes, therefore [combine] is always
+ /// called for elements in order, and the result stream always maintains the
+ /// same order as this stream.
+ Stream<S> scan<S>(
+ S initialValue, FutureOr<S> Function(S soFar, T element) combine) {
+ var accumulated = initialValue;
+ return asyncMap((value) {
+ var result = combine(accumulated, value);
+ if (result is Future<S>) {
+ return result.then((r) => accumulated = r);
+ } else {
+ return accumulated = result;
+ }
+ });
+ }
+}
diff --git a/pkgs/stream_transform/lib/src/switch.dart b/pkgs/stream_transform/lib/src/switch.dart
new file mode 100644
index 0000000..546036e
--- /dev/null
+++ b/pkgs/stream_transform/lib/src/switch.dart
@@ -0,0 +1,135 @@
+// Copyright (c) 2017, 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 'async_expand.dart';
+import 'common_callbacks.dart';
+
+/// A utility to take events from the most recent sub stream returned by a
+/// callback.
+extension Switch<T> on Stream<T> {
+ /// Maps events to a Stream and emits values from the most recently created
+ /// Stream.
+ ///
+ /// When the source emits a value it will be converted to a [Stream] using
+ /// [convert] and the output will switch to emitting events from that result.
+ /// Like [asyncExpand] but the [Stream] emitted by a previous element
+ /// will be ignored as soon as the source stream emits a new event.
+ ///
+ /// This means that the source stream is not paused until a sub stream
+ /// returned from the [convert] callback is done. Instead, the subscription
+ /// to the sub stream is canceled as soon as the source stream emits a new
+ /// event.
+ ///
+ /// Errors from [convert], the source stream, or any of the sub streams are
+ /// forwarded to the result stream.
+ ///
+ /// The result stream will not close until the source stream closes and
+ /// the current sub stream have closed.
+ ///
+ /// If the source stream is a broadcast stream, the result will be as well,
+ /// regardless of the types of streams created by [convert]. In this case,
+ /// some care should be taken:
+ ///
+ /// * If [convert] returns a single subscription stream it may be listened to
+ /// and never canceled.
+ ///
+ /// See also:
+ /// - [concurrentAsyncExpand], which emits events from all sub streams
+ /// concurrently instead of cancelling subscriptions to previous subs
+ /// streams.
+ Stream<S> switchMap<S>(Stream<S> Function(T) convert) {
+ return map(convert).switchLatest();
+ }
+}
+
+/// A utility to take events from the most recent sub stream.
+extension SwitchLatest<T> on Stream<Stream<T>> {
+ /// Emits values from the most recently emitted Stream.
+ ///
+ /// When the source emits a stream, the output will switch to emitting events
+ /// from that stream.
+ ///
+ /// Whether the source stream is a single-subscription stream or a
+ /// broadcast stream, the result stream will be the same kind of stream,
+ /// regardless of the types of streams emitted.
+ Stream<T> switchLatest() {
+ var controller = isBroadcast
+ ? StreamController<T>.broadcast(sync: true)
+ : StreamController<T>(sync: true);
+
+ controller.onListen = () {
+ StreamSubscription<T>? innerSubscription;
+ var outerStreamDone = false;
+
+ void listenToInnerStream(Stream<T> innerStream) {
+ assert(innerSubscription == null);
+ var subscription = innerStream
+ .listen(controller.add, onError: controller.addError, onDone: () {
+ innerSubscription = null;
+ if (outerStreamDone) controller.close();
+ });
+ // If a pause happens during an innerSubscription.cancel,
+ // we still listen to the next stream when the cancel is done.
+ // Then we immediately pause it again here.
+ if (controller.isPaused) subscription.pause();
+ innerSubscription = subscription;
+ }
+
+ var addError = controller.addError;
+ final outerSubscription = listen(null, onError: addError, onDone: () {
+ outerStreamDone = true;
+ if (innerSubscription == null) controller.close();
+ });
+ outerSubscription.onData((innerStream) async {
+ var currentSubscription = innerSubscription;
+ if (currentSubscription == null) {
+ listenToInnerStream(innerStream);
+ return;
+ }
+ innerSubscription = null;
+ outerSubscription.pause();
+ try {
+ await currentSubscription.cancel();
+ } catch (error, stack) {
+ controller.addError(error, stack);
+ } finally {
+ if (!isBroadcast && !controller.hasListener) {
+ // Result single-subscription stream subscription was cancelled
+ // while waiting for previous innerStream cancel.
+ //
+ // Ensure that the last received stream is also listened to and
+ // cancelled, then do nothing further.
+ innerStream.listen(null).cancel().ignore();
+ } else {
+ outerSubscription.resume();
+ listenToInnerStream(innerStream);
+ }
+ }
+ });
+ if (!isBroadcast) {
+ controller
+ ..onPause = () {
+ innerSubscription?.pause();
+ outerSubscription.pause();
+ }
+ ..onResume = () {
+ innerSubscription?.resume();
+ outerSubscription.resume();
+ };
+ }
+ controller.onCancel = () {
+ var sub = innerSubscription;
+ var cancels = [
+ if (!outerStreamDone) outerSubscription.cancel(),
+ if (sub != null) sub.cancel(),
+ ];
+ if (cancels.isEmpty) return null;
+ return cancels.wait.then(ignoreArgument);
+ };
+ };
+ return controller.stream;
+ }
+}
diff --git a/pkgs/stream_transform/lib/src/take_until.dart b/pkgs/stream_transform/lib/src/take_until.dart
new file mode 100644
index 0000000..e6deaa1
--- /dev/null
+++ b/pkgs/stream_transform/lib/src/take_until.dart
@@ -0,0 +1,64 @@
+// Copyright (c) 2017, 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';
+
+/// A utility to end a stream based on an external trigger.
+extension TakeUntil<T> on Stream<T> {
+ /// Takes values from this stream which are emitted before [trigger]
+ /// completes.
+ ///
+ /// Completing [trigger] differs from canceling a subscription in that values
+ /// which are emitted before the trigger, but have further asynchronous delays
+ /// in transformations following the takeUtil, will still go through.
+ /// Cancelling a subscription immediately stops values.
+ ///
+ /// If [trigger] completes as an error, the error will be forwarded through
+ /// the result stream before the result stream closes.
+ ///
+ /// If [trigger] completes as a value or as an error after this stream has
+ /// already ended, the completion will be ignored.
+ Stream<T> takeUntil(Future<void> trigger) {
+ var controller = isBroadcast
+ ? StreamController<T>.broadcast(sync: true)
+ : StreamController<T>(sync: true);
+
+ StreamSubscription<T>? subscription;
+ var isDone = false;
+ trigger.then((_) {
+ if (isDone) return;
+ isDone = true;
+ subscription?.cancel();
+ controller.close();
+ }, onError: (Object error, StackTrace stackTrace) {
+ if (isDone) return;
+ isDone = true;
+ controller
+ ..addError(error, stackTrace)
+ ..close();
+ });
+
+ controller.onListen = () {
+ if (isDone) return;
+ subscription =
+ listen(controller.add, onError: controller.addError, onDone: () {
+ if (isDone) return;
+ isDone = true;
+ controller.close();
+ });
+ if (!isBroadcast) {
+ controller
+ ..onPause = subscription!.pause
+ ..onResume = subscription!.resume;
+ }
+ controller.onCancel = () {
+ if (isDone) return null;
+ var toCancel = subscription!;
+ subscription = null;
+ return toCancel.cancel();
+ };
+ };
+ return controller.stream;
+ }
+}
diff --git a/pkgs/stream_transform/lib/src/tap.dart b/pkgs/stream_transform/lib/src/tap.dart
new file mode 100644
index 0000000..4b16ab5
--- /dev/null
+++ b/pkgs/stream_transform/lib/src/tap.dart
@@ -0,0 +1,44 @@
+// Copyright (c) 2017, 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 'from_handlers.dart';
+
+/// A utility to chain extra behavior on a stream.
+extension Tap<T> on Stream<T> {
+ /// Taps into this stream to allow additional handling on a single-subscriber
+ /// stream without first wrapping as a broadcast stream.
+ ///
+ /// The [onValue] callback will be called with every value from this stream
+ /// before it is forwarded to listeners on the resulting stream.
+ /// May be null if only [onError] or [onDone] callbacks are needed.
+ ///
+ /// The [onError] callback will be called with every error from this stream
+ /// before it is forwarded to listeners on the resulting stream.
+ ///
+ /// The [onDone] callback will be called after this stream closes and before
+ /// the resulting stream is closed.
+ ///
+ /// Errors from any of the callbacks are caught and ignored.
+ ///
+ /// The callbacks may not be called until the tapped stream has a listener,
+ /// and may not be called after the listener has canceled the subscription.
+ Stream<T> tap(void Function(T)? onValue,
+ {void Function(Object, StackTrace)? onError,
+ void Function()? onDone}) =>
+ transformByHandlers(onData: (value, sink) {
+ try {
+ onValue?.call(value);
+ } catch (_) {/*Ignore*/}
+ sink.add(value);
+ }, onError: (error, stackTrace, sink) {
+ try {
+ onError?.call(error, stackTrace);
+ } catch (_) {/*Ignore*/}
+ sink.addError(error, stackTrace);
+ }, onDone: (sink) {
+ try {
+ onDone?.call();
+ } catch (_) {/*Ignore*/}
+ sink.close();
+ });
+}
diff --git a/pkgs/stream_transform/lib/src/where.dart b/pkgs/stream_transform/lib/src/where.dart
new file mode 100644
index 0000000..76aa28a
--- /dev/null
+++ b/pkgs/stream_transform/lib/src/where.dart
@@ -0,0 +1,71 @@
+// 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:async';
+
+import 'from_handlers.dart';
+
+/// Utilities to filter events.
+extension Where<T> on Stream<T> {
+ /// Discards events from this stream that are not of type [S].
+ ///
+ /// If the source stream is a broadcast stream the result will be as well.
+ ///
+ /// Errors from the source stream are forwarded directly to the result stream.
+ ///
+ /// [S] should be a subtype of the stream's generic type, otherwise nothing of
+ /// type [S] could possibly be emitted, however there is no static or runtime
+ /// checking that this is the case.
+ Stream<S> whereType<S>() => transformByHandlers(onData: (event, sink) {
+ if (event is S) sink.add(event);
+ });
+
+ /// Discards events from this stream based on an asynchronous [test] callback.
+ ///
+ /// Like [where] but allows the [test] to return a [Future].
+ ///
+ /// Events on the result stream will be emitted in the order that [test]
+ /// completes which may not match the order of this stream.
+ ///
+ /// If the source stream is a broadcast stream the result will be as well.
+ /// When used with a broadcast stream behavior also differs from [where] in
+ /// that the [test] function is only called once per event, rather than once
+ /// per listener per event.
+ ///
+ /// Errors from the source stream are forwarded directly to the result stream.
+ /// Errors from [test] are also forwarded to the result stream.
+ ///
+ /// The result stream will not close until the source stream closes and all
+ /// pending [test] calls have finished.
+ Stream<T> asyncWhere(FutureOr<bool> Function(T) test) {
+ var valuesWaiting = 0;
+ var sourceDone = false;
+ return transformByHandlers(onData: (element, sink) {
+ valuesWaiting++;
+ () async {
+ try {
+ if (await test(element)) sink.add(element);
+ } catch (e, st) {
+ sink.addError(e, st);
+ }
+ valuesWaiting--;
+ if (valuesWaiting <= 0 && sourceDone) sink.close();
+ }();
+ }, onDone: (sink) {
+ sourceDone = true;
+ if (valuesWaiting <= 0) sink.close();
+ });
+ }
+}
+
+extension WhereNotNull<T extends Object> on Stream<T?> {
+ /// Discards `null` events from this stream.
+ ///
+ /// If the source stream is a broadcast stream the result will be as well.
+ ///
+ /// Errors from the source stream are forwarded directly to the result stream.
+ Stream<T> whereNotNull() => transformByHandlers(onData: (event, sink) {
+ if (event != null) sink.add(event);
+ });
+}
diff --git a/pkgs/stream_transform/lib/stream_transform.dart b/pkgs/stream_transform/lib/stream_transform.dart
new file mode 100644
index 0000000..edf4df9
--- /dev/null
+++ b/pkgs/stream_transform/lib/stream_transform.dart
@@ -0,0 +1,15 @@
+// Copyright (c) 2017, 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/async_expand.dart';
+export 'src/async_map.dart';
+export 'src/combine_latest.dart';
+export 'src/concatenate.dart';
+export 'src/merge.dart';
+export 'src/rate_limit.dart';
+export 'src/scan.dart';
+export 'src/switch.dart';
+export 'src/take_until.dart';
+export 'src/tap.dart';
+export 'src/where.dart';
diff --git a/pkgs/stream_transform/pubspec.yaml b/pkgs/stream_transform/pubspec.yaml
new file mode 100644
index 0000000..91840b7
--- /dev/null
+++ b/pkgs/stream_transform/pubspec.yaml
@@ -0,0 +1,14 @@
+name: stream_transform
+version: 2.1.2-wip
+description: A collection of utilities to transform and manipulate streams.
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/stream_transform
+
+environment:
+ sdk: ^3.4.0
+
+dev_dependencies:
+ async: ^2.5.0
+ dart_flutter_team_lints: ^3.0.0
+ fake_async: ^1.3.0
+ test: ^1.16.0
+ web: ^1.1.0
diff --git a/pkgs/stream_transform/test/async_expand_test.dart b/pkgs/stream_transform/test/async_expand_test.dart
new file mode 100644
index 0000000..8d84300
--- /dev/null
+++ b/pkgs/stream_transform/test/async_expand_test.dart
@@ -0,0 +1,195 @@
+// 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:async';
+
+import 'package:stream_transform/stream_transform.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ test('forwards errors from the convert callback', () async {
+ var errors = <String>[];
+ var source = Stream.fromIterable([1, 2, 3]);
+ source.concurrentAsyncExpand<void>((i) {
+ // ignore: only_throw_errors
+ throw 'Error: $i';
+ }).listen((_) {}, onError: errors.add);
+ await Future<void>(() {});
+ expect(errors, ['Error: 1', 'Error: 2', 'Error: 3']);
+ });
+
+ for (var outerType in streamTypes) {
+ for (var innerType in streamTypes) {
+ group('concurrentAsyncExpand $outerType to $innerType', () {
+ late StreamController<int> outerController;
+ late bool outerCanceled;
+ late List<StreamController<String>> innerControllers;
+ late List<bool> innerCanceled;
+ late List<String> emittedValues;
+ late bool isDone;
+ late List<String> errors;
+ late Stream<String> transformed;
+ late StreamSubscription<String> subscription;
+
+ setUp(() {
+ outerController = createController(outerType)
+ ..onCancel = () {
+ outerCanceled = true;
+ };
+ outerCanceled = false;
+ innerControllers = [];
+ innerCanceled = [];
+ emittedValues = [];
+ errors = [];
+ isDone = false;
+ transformed = outerController.stream.concurrentAsyncExpand((i) {
+ var index = innerControllers.length;
+ innerCanceled.add(false);
+ innerControllers.add(createController<String>(innerType)
+ ..onCancel = () {
+ innerCanceled[index] = true;
+ });
+ return innerControllers.last.stream;
+ });
+ subscription = transformed
+ .listen(emittedValues.add, onError: errors.add, onDone: () {
+ isDone = true;
+ });
+ });
+
+ test('interleaves events from sub streams', () async {
+ outerController
+ ..add(1)
+ ..add(2);
+ await Future<void>(() {});
+ expect(emittedValues, isEmpty);
+ expect(innerControllers, hasLength(2));
+ innerControllers[0].add('First');
+ innerControllers[1].add('Second');
+ innerControllers[0].add('First again');
+ await Future<void>(() {});
+ expect(emittedValues, ['First', 'Second', 'First again']);
+ });
+
+ test('forwards errors from outer stream', () async {
+ outerController.addError('Error');
+ await Future<void>(() {});
+ expect(errors, ['Error']);
+ });
+
+ test('forwards errors from inner streams', () async {
+ outerController
+ ..add(1)
+ ..add(2);
+ await Future<void>(() {});
+ innerControllers[0].addError('Error 1');
+ innerControllers[1].addError('Error 2');
+ await Future<void>(() {});
+ expect(errors, ['Error 1', 'Error 2']);
+ });
+
+ test('can continue handling events after an error in outer stream',
+ () async {
+ outerController
+ ..addError('Error')
+ ..add(1);
+ await Future<void>(() {});
+ innerControllers[0].add('First');
+ await Future<void>(() {});
+ expect(emittedValues, ['First']);
+ expect(errors, ['Error']);
+ });
+
+ test('cancels outer subscription if output canceled', () async {
+ await subscription.cancel();
+ expect(outerCanceled, true);
+ });
+
+ if (outerType != 'broadcast' || innerType != 'single subscription') {
+ // A single subscription inner stream in a broadcast outer stream is
+ // not canceled.
+ test('cancels inner subscriptions if output canceled', () async {
+ outerController
+ ..add(1)
+ ..add(2);
+ await Future<void>(() {});
+ await subscription.cancel();
+ expect(innerCanceled, [true, true]);
+ });
+ }
+
+ test('stays open if any inner stream is still open', () async {
+ outerController.add(1);
+ await outerController.close();
+ await Future<void>(() {});
+ expect(isDone, false);
+ });
+
+ test('stays open if outer stream is still open', () async {
+ outerController.add(1);
+ await Future<void>(() {});
+ await innerControllers[0].close();
+ await Future<void>(() {});
+ expect(isDone, false);
+ });
+
+ test('closes after all inner streams and outer stream close', () async {
+ outerController.add(1);
+ await Future<void>(() {});
+ await innerControllers[0].close();
+ await outerController.close();
+ await Future<void>(() {});
+ expect(isDone, true);
+ });
+
+ if (outerType == 'broadcast') {
+ test('multiple listerns all get values', () async {
+ var otherValues = <String>[];
+ transformed.listen(otherValues.add);
+ outerController.add(1);
+ await Future<void>(() {});
+ innerControllers[0].add('First');
+ await Future<void>(() {});
+ expect(emittedValues, ['First']);
+ expect(otherValues, ['First']);
+ });
+
+ test('multiple listeners get closed', () async {
+ var otherDone = false;
+ transformed.listen(null, onDone: () => otherDone = true);
+ outerController.add(1);
+ await Future<void>(() {});
+ await innerControllers[0].close();
+ await outerController.close();
+ await Future<void>(() {});
+ expect(isDone, true);
+ expect(otherDone, true);
+ });
+
+ test('can cancel and relisten', () async {
+ outerController
+ ..add(1)
+ ..add(2);
+ await Future(() {});
+ innerControllers[0].add('First');
+ innerControllers[1].add('Second');
+ await Future(() {});
+ await subscription.cancel();
+ innerControllers[0].add('Ignored');
+ await Future(() {});
+ subscription = transformed.listen(emittedValues.add);
+ innerControllers[0].add('Also ignored');
+ outerController.add(3);
+ await Future(() {});
+ innerControllers[2].add('More');
+ await Future(() {});
+ expect(emittedValues, ['First', 'Second', 'More']);
+ });
+ }
+ });
+ }
+ }
+}
diff --git a/pkgs/stream_transform/test/async_map_buffer_test.dart b/pkgs/stream_transform/test/async_map_buffer_test.dart
new file mode 100644
index 0000000..2386217
--- /dev/null
+++ b/pkgs/stream_transform/test/async_map_buffer_test.dart
@@ -0,0 +1,204 @@
+// Copyright (c) 2017, 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 'package:stream_transform/stream_transform.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ late StreamController<int> values;
+ late List<String> emittedValues;
+ late bool valuesCanceled;
+ late bool isDone;
+ late List<String> errors;
+ late Stream<String> transformed;
+ late StreamSubscription<String> subscription;
+
+ Completer<String>? finishWork;
+ List<int>? workArgument;
+
+ /// Represents the async `convert` function and asserts that is is only called
+ /// after the previous iteration has completed.
+ Future<String> work(List<int> values) {
+ expect(finishWork, isNull,
+ reason: 'See $values befor previous work is complete');
+ workArgument = values;
+ finishWork = Completer()
+ ..future.then((_) {
+ workArgument = null;
+ finishWork = null;
+ }).catchError((_) {
+ workArgument = null;
+ finishWork = null;
+ });
+ return finishWork!.future;
+ }
+
+ for (var streamType in streamTypes) {
+ group('asyncMapBuffer for stream type: [$streamType]', () {
+ setUp(() {
+ valuesCanceled = false;
+ values = createController(streamType)
+ ..onCancel = () {
+ valuesCanceled = true;
+ };
+ emittedValues = [];
+ errors = [];
+ isDone = false;
+ finishWork = null;
+ workArgument = null;
+ transformed = values.stream.asyncMapBuffer(work);
+ subscription = transformed
+ .listen(emittedValues.add, onError: errors.add, onDone: () {
+ isDone = true;
+ });
+ });
+
+ test('does not emit before work finishes', () async {
+ values.add(1);
+ await Future(() {});
+ expect(emittedValues, isEmpty);
+ expect(workArgument, [1]);
+ finishWork!.complete('result');
+ await Future(() {});
+ expect(emittedValues, ['result']);
+ });
+
+ test('buffers values while work is ongoing', () async {
+ values.add(1);
+ await Future(() {});
+ values
+ ..add(2)
+ ..add(3);
+ await Future(() {});
+ finishWork!.complete('');
+ await Future(() {});
+ expect(workArgument, [2, 3]);
+ });
+
+ test('forwards errors without waiting for work', () async {
+ values.add(1);
+ await Future(() {});
+ values.addError('error');
+ await Future(() {});
+ expect(errors, ['error']);
+ });
+
+ test('forwards errors which occur during the work', () async {
+ values.add(1);
+ await Future(() {});
+ finishWork!.completeError('error');
+ await Future(() {});
+ expect(errors, ['error']);
+ });
+
+ test('can continue handling events after an error', () async {
+ values.add(1);
+ await Future(() {});
+ finishWork!.completeError('error');
+ values.add(2);
+ await Future(() {});
+ expect(workArgument, [2]);
+ finishWork!.completeError('another');
+ await Future(() {});
+ expect(errors, ['error', 'another']);
+ });
+
+ test('does not start next work early due to an error in values',
+ () async {
+ values.add(1);
+ await Future(() {});
+ values
+ ..addError('error')
+ ..add(2);
+ await Future(() {});
+ expect(errors, ['error']);
+ // [work] will assert that the second iteration is not called because
+ // the first has not completed.
+ });
+
+ test('cancels value subscription when output canceled', () async {
+ expect(valuesCanceled, false);
+ await subscription.cancel();
+ expect(valuesCanceled, true);
+ });
+
+ test('closes when values end if no work is pending', () async {
+ expect(isDone, false);
+ await values.close();
+ await Future(() {});
+ expect(isDone, true);
+ });
+
+ test('waits for pending work when values close', () async {
+ values.add(1);
+ await Future(() {});
+ expect(isDone, false);
+ values.add(2);
+ await values.close();
+ expect(isDone, false);
+ finishWork!.complete('');
+ await Future(() {});
+ // Still a pending value
+ expect(isDone, false);
+ finishWork!.complete('');
+ await Future(() {});
+ expect(isDone, true);
+ });
+
+ test('forwards errors from values', () async {
+ values.addError('error');
+ await Future(() {});
+ expect(errors, ['error']);
+ });
+
+ if (streamType == 'broadcast') {
+ test('multiple listeners all get values', () async {
+ var otherValues = <String>[];
+ transformed.listen(otherValues.add);
+ values.add(1);
+ await Future(() {});
+ finishWork!.complete('result');
+ await Future(() {});
+ expect(emittedValues, ['result']);
+ expect(otherValues, ['result']);
+ });
+
+ test('multiple listeners get done when values end', () async {
+ var otherDone = false;
+ transformed.listen(null, onDone: () => otherDone = true);
+ values.add(1);
+ await Future(() {});
+ await values.close();
+ expect(isDone, false);
+ expect(otherDone, false);
+ finishWork!.complete('');
+ await Future(() {});
+ expect(isDone, true);
+ expect(otherDone, true);
+ });
+
+ test('can cancel and relisten', () async {
+ values.add(1);
+ await Future(() {});
+ finishWork!.complete('first');
+ await Future(() {});
+ await subscription.cancel();
+ values.add(2);
+ await Future(() {});
+ subscription = transformed.listen(emittedValues.add);
+ values.add(3);
+ await Future(() {});
+ expect(workArgument, [3]);
+ finishWork!.complete('second');
+ await Future(() {});
+ expect(emittedValues, ['first', 'second']);
+ });
+ }
+ });
+ }
+}
diff --git a/pkgs/stream_transform/test/async_map_sample_test.dart b/pkgs/stream_transform/test/async_map_sample_test.dart
new file mode 100644
index 0000000..62b1b92
--- /dev/null
+++ b/pkgs/stream_transform/test/async_map_sample_test.dart
@@ -0,0 +1,209 @@
+// 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:async';
+
+import 'package:stream_transform/stream_transform.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ late StreamController<int> values;
+ late List<String> emittedValues;
+ late bool valuesCanceled;
+ late bool isDone;
+ late List<String> errors;
+ late Stream<String> transformed;
+ late StreamSubscription<String> subscription;
+
+ Completer<String>? finishWork;
+ int? workArgument;
+
+ /// Represents the async `convert` function and asserts that is is only called
+ /// after the previous iteration has completed.
+ Future<String> work(int value) {
+ expect(finishWork, isNull,
+ reason: 'See $values befor previous work is complete');
+ workArgument = value;
+ finishWork = Completer()
+ ..future.then((_) {
+ workArgument = null;
+ finishWork = null;
+ }).catchError((_) {
+ workArgument = null;
+ finishWork = null;
+ });
+ return finishWork!.future;
+ }
+
+ for (var streamType in streamTypes) {
+ group('asyncMapSample for stream type: [$streamType]', () {
+ setUp(() {
+ valuesCanceled = false;
+ values = createController(streamType)
+ ..onCancel = () {
+ valuesCanceled = true;
+ };
+ emittedValues = [];
+ errors = [];
+ isDone = false;
+ finishWork = null;
+ workArgument = null;
+ transformed = values.stream.asyncMapSample(work);
+ subscription = transformed
+ .listen(emittedValues.add, onError: errors.add, onDone: () {
+ isDone = true;
+ });
+ });
+
+ test('does not emit before work finishes', () async {
+ values.add(1);
+ await Future(() {});
+ expect(emittedValues, isEmpty);
+ expect(workArgument, 1);
+ finishWork!.complete('result');
+ await Future(() {});
+ expect(emittedValues, ['result']);
+ });
+
+ test('buffers values while work is ongoing', () async {
+ values.add(1);
+ await Future(() {});
+ values
+ ..add(2)
+ ..add(3);
+ await Future(() {});
+ finishWork!.complete('');
+ await Future(() {});
+ expect(workArgument, 3);
+ });
+
+ test('forwards errors without waiting for work', () async {
+ values.add(1);
+ await Future(() {});
+ values.addError('error');
+ await Future(() {});
+ expect(errors, ['error']);
+ });
+
+ test('forwards errors which occur during the work', () async {
+ values.add(1);
+ await Future(() {});
+ finishWork!.completeError('error');
+ await Future(() {});
+ expect(errors, ['error']);
+ });
+
+ test('can continue handling events after an error', () async {
+ values.add(1);
+ await Future(() {});
+ finishWork!.completeError('error');
+ values.add(2);
+ await Future(() {});
+ expect(workArgument, 2);
+ finishWork!.completeError('another');
+ await Future(() {});
+ expect(errors, ['error', 'another']);
+ });
+
+ test('does not start next work early due to an error in values',
+ () async {
+ values.add(1);
+ await Future(() {});
+ values
+ ..addError('error')
+ ..add(2);
+ await Future(() {});
+ expect(errors, ['error']);
+ // [work] will assert that the second iteration is not called because
+ // the first has not completed.
+ });
+
+ test('cancels value subscription when output canceled', () async {
+ expect(valuesCanceled, false);
+ await subscription.cancel();
+ expect(valuesCanceled, true);
+ });
+
+ test('closes when values end if no work is pending', () async {
+ expect(isDone, false);
+ await values.close();
+ await Future(() {});
+ expect(isDone, true);
+ });
+
+ test('waits for pending work when values close', () async {
+ values.add(1);
+ await Future(() {});
+ expect(isDone, false);
+ values.add(2);
+ await values.close();
+ expect(isDone, false);
+ finishWork!.complete('');
+ await Future(() {});
+ // Still a pending value
+ expect(isDone, false);
+ finishWork!.complete('');
+ await Future(() {});
+ expect(isDone, true);
+ });
+
+ test('forwards errors from values', () async {
+ values.addError('error');
+ await Future(() {});
+ expect(errors, ['error']);
+ });
+
+ if (streamType == 'broadcast') {
+ test('multiple listeners all get values', () async {
+ var otherValues = <String>[];
+ transformed.listen(otherValues.add);
+ values.add(1);
+ await Future(() {});
+ finishWork!.complete('result');
+ await Future(() {});
+ expect(emittedValues, ['result']);
+ expect(otherValues, ['result']);
+ });
+
+ test('multiple listeners get done when values end', () async {
+ var otherDone = false;
+ transformed.listen(null, onDone: () => otherDone = true);
+ values.add(1);
+ await Future(() {});
+ await values.close();
+ expect(isDone, false);
+ expect(otherDone, false);
+ finishWork!.complete('');
+ await Future(() {});
+ expect(isDone, true);
+ expect(otherDone, true);
+ });
+
+ test('can cancel and relisten', () async {
+ values.add(1);
+ await Future(() {});
+ finishWork!.complete('first');
+ await Future(() {});
+ await subscription.cancel();
+ values.add(2);
+ await Future(() {});
+ subscription = transformed.listen(emittedValues.add);
+ values.add(3);
+ await Future(() {});
+ expect(workArgument, 3);
+ finishWork!.complete('second');
+ await Future(() {});
+ expect(emittedValues, ['first', 'second']);
+ });
+ }
+ });
+ }
+
+ test('allows nulls', () async {
+ var stream = Stream<int?>.value(null);
+ await stream.asyncMapSample(expectAsync1((_) async {})).drain<void>();
+ });
+}
diff --git a/pkgs/stream_transform/test/async_where_test.dart b/pkgs/stream_transform/test/async_where_test.dart
new file mode 100644
index 0000000..6ea4e76
--- /dev/null
+++ b/pkgs/stream_transform/test/async_where_test.dart
@@ -0,0 +1,90 @@
+// Copyright (c) 2017, 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 'package:stream_transform/stream_transform.dart';
+import 'package:test/test.dart';
+
+void main() {
+ test('forwards only events that pass the predicate', () async {
+ var values = Stream.fromIterable([1, 2, 3, 4]);
+ var filtered = values.asyncWhere((e) async => e > 2);
+ expect(await filtered.toList(), [3, 4]);
+ });
+
+ test('allows predicates that go through event loop', () async {
+ var values = Stream.fromIterable([1, 2, 3, 4]);
+ var filtered = values.asyncWhere((e) async {
+ await Future(() {});
+ return e > 2;
+ });
+ expect(await filtered.toList(), [3, 4]);
+ });
+
+ test('allows synchronous predicate', () async {
+ var values = Stream.fromIterable([1, 2, 3, 4]);
+ var filtered = values.asyncWhere((e) => e > 2);
+ expect(await filtered.toList(), [3, 4]);
+ });
+
+ test('can result in empty stream', () async {
+ var values = Stream.fromIterable([1, 2, 3, 4]);
+ var filtered = values.asyncWhere((e) => e > 4);
+ expect(await filtered.isEmpty, true);
+ });
+
+ test('forwards values to multiple listeners', () async {
+ var values = StreamController<int>.broadcast();
+ var filtered = values.stream.asyncWhere((e) async => e > 2);
+ var firstValues = <int>[];
+ var secondValues = <int>[];
+ filtered
+ ..listen(firstValues.add)
+ ..listen(secondValues.add);
+ values
+ ..add(1)
+ ..add(2)
+ ..add(3)
+ ..add(4);
+ await Future(() {});
+ expect(firstValues, [3, 4]);
+ expect(secondValues, [3, 4]);
+ });
+
+ test('closes streams with multiple listeners', () async {
+ var values = StreamController<int>.broadcast();
+ var predicate = Completer<bool>();
+ var filtered = values.stream.asyncWhere((_) => predicate.future);
+ var firstDone = false;
+ var secondDone = false;
+ filtered
+ ..listen(null, onDone: () => firstDone = true)
+ ..listen(null, onDone: () => secondDone = true);
+ values.add(1);
+ await values.close();
+ expect(firstDone, false);
+ expect(secondDone, false);
+
+ predicate.complete(true);
+ await Future(() {});
+ expect(firstDone, true);
+ expect(secondDone, true);
+ });
+
+ test('forwards errors emitted by the test callback', () async {
+ var errors = <Object>[];
+ var emitted = <Object>[];
+ var values = Stream.fromIterable([1, 2, 3, 4]);
+ var filtered = values.asyncWhere((e) async {
+ await Future(() {});
+ if (e.isEven) throw Exception('$e');
+ return true;
+ });
+ var done = Completer<Object?>();
+ filtered.listen(emitted.add, onError: errors.add, onDone: done.complete);
+ await done.future;
+ expect(emitted, [1, 3]);
+ expect(errors.map((e) => '$e'), ['Exception: 2', 'Exception: 4']);
+ });
+}
diff --git a/pkgs/stream_transform/test/audit_test.dart b/pkgs/stream_transform/test/audit_test.dart
new file mode 100644
index 0000000..28537db
--- /dev/null
+++ b/pkgs/stream_transform/test/audit_test.dart
@@ -0,0 +1,140 @@
+// Copyright (c) 2017, 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 'package:fake_async/fake_async.dart';
+import 'package:stream_transform/stream_transform.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ for (var streamType in streamTypes) {
+ group('Stream type [$streamType]', () {
+ late StreamController<int> values;
+ late List<int> emittedValues;
+ late bool valuesCanceled;
+ late bool isDone;
+ late List<String> errors;
+ late Stream<int> transformed;
+ late StreamSubscription<int> subscription;
+
+ group('audit', () {
+ setUp(() {
+ valuesCanceled = false;
+ values = createController(streamType)
+ ..onCancel = () {
+ valuesCanceled = true;
+ };
+ emittedValues = [];
+ errors = [];
+ isDone = false;
+ transformed = values.stream.audit(const Duration(milliseconds: 6));
+ });
+
+ void listen() {
+ subscription = transformed
+ .listen(emittedValues.add, onError: errors.add, onDone: () {
+ isDone = true;
+ });
+ }
+
+ test('cancels values', () async {
+ listen();
+ await subscription.cancel();
+ expect(valuesCanceled, true);
+ });
+
+ test('swallows values that come faster than duration', () {
+ fakeAsync((async) {
+ listen();
+ values
+ ..add(1)
+ ..add(2)
+ ..close();
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [2]);
+ });
+ });
+
+ test('outputs multiple values spaced further than duration', () {
+ fakeAsync((async) {
+ listen();
+ values.add(1);
+ async.elapse(const Duration(milliseconds: 6));
+ values.add(2);
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [1, 2]);
+ });
+ });
+
+ test('waits for pending value to close', () {
+ fakeAsync((async) {
+ listen();
+ values
+ ..add(1)
+ ..close();
+ expect(isDone, false);
+ async.elapse(const Duration(milliseconds: 6));
+ expect(isDone, true);
+ });
+ });
+
+ test('closes output if there are no pending values', () {
+ fakeAsync((async) {
+ listen();
+ values.add(1);
+ async.elapse(const Duration(milliseconds: 6));
+ values
+ ..add(2)
+ ..close();
+ expect(isDone, false);
+ expect(emittedValues, [1]);
+ async.elapse(const Duration(milliseconds: 6));
+ expect(isDone, true);
+ expect(emittedValues, [1, 2]);
+ });
+ });
+
+ test('does not starve output if many values come closer than duration',
+ () {
+ fakeAsync((async) {
+ listen();
+ values.add(1);
+ async.elapse(const Duration(milliseconds: 3));
+ values.add(2);
+ async.elapse(const Duration(milliseconds: 3));
+ values.add(3);
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [2, 3]);
+ });
+ });
+
+ if (streamType == 'broadcast') {
+ test('multiple listeners all get the values', () {
+ fakeAsync((async) {
+ listen();
+ values.add(1);
+ async.elapse(const Duration(milliseconds: 3));
+ values.add(2);
+ var otherValues = <int>[];
+ transformed.listen(otherValues.add);
+ values.add(3);
+ async.elapse(const Duration(milliseconds: 3));
+ values.add(4);
+ async.elapse(const Duration(milliseconds: 3));
+ values
+ ..add(5)
+ ..close();
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [3, 5]);
+ expect(otherValues, [3, 5]);
+ });
+ });
+ }
+ });
+ });
+ }
+}
diff --git a/pkgs/stream_transform/test/buffer_test.dart b/pkgs/stream_transform/test/buffer_test.dart
new file mode 100644
index 0000000..830f555
--- /dev/null
+++ b/pkgs/stream_transform/test/buffer_test.dart
@@ -0,0 +1,305 @@
+// Copyright (c) 2017, 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 'package:stream_transform/stream_transform.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ late StreamController<void> trigger;
+ late StreamController<int> values;
+ late List<List<int>> emittedValues;
+ late bool valuesCanceled;
+ late bool triggerCanceled;
+ late bool triggerPaused;
+ late bool isDone;
+ late List<String> errors;
+ late Stream<List<int>> transformed;
+ late StreamSubscription<List<int>> subscription;
+
+ void setUpForStreamTypes(String triggerType, String valuesType,
+ {required bool longPoll}) {
+ valuesCanceled = false;
+ triggerCanceled = false;
+ triggerPaused = false;
+ trigger = createController(triggerType)
+ ..onCancel = () {
+ triggerCanceled = true;
+ };
+ if (triggerType == 'single subscription') {
+ trigger.onPause = () {
+ triggerPaused = true;
+ };
+ }
+ values = createController(valuesType)
+ ..onCancel = () {
+ valuesCanceled = true;
+ };
+ emittedValues = [];
+ errors = [];
+ isDone = false;
+ transformed = values.stream.buffer(trigger.stream, longPoll: longPoll);
+ subscription =
+ transformed.listen(emittedValues.add, onError: errors.add, onDone: () {
+ isDone = true;
+ });
+ }
+
+ for (var triggerType in streamTypes) {
+ for (var valuesType in streamTypes) {
+ group('Trigger type: [$triggerType], Values type: [$valuesType]', () {
+ group('general behavior', () {
+ setUp(() {
+ setUpForStreamTypes(triggerType, valuesType, longPoll: true);
+ });
+
+ test('does not emit before `trigger`', () async {
+ values.add(1);
+ await Future(() {});
+ expect(emittedValues, isEmpty);
+ trigger.add(null);
+ await Future(() {});
+ expect(emittedValues, [
+ [1]
+ ]);
+ });
+
+ test('groups values between trigger', () async {
+ values
+ ..add(1)
+ ..add(2);
+ await Future(() {});
+ trigger.add(null);
+ values
+ ..add(3)
+ ..add(4);
+ await Future(() {});
+ trigger.add(null);
+ await Future(() {});
+ expect(emittedValues, [
+ [1, 2],
+ [3, 4]
+ ]);
+ });
+
+ test('cancels value subscription when output canceled', () async {
+ expect(valuesCanceled, false);
+ await subscription.cancel();
+ expect(valuesCanceled, true);
+ });
+
+ test('closes when trigger ends', () async {
+ expect(isDone, false);
+ await trigger.close();
+ await Future(() {});
+ expect(isDone, true);
+ });
+
+ test('closes after outputting final values when source closes',
+ () async {
+ expect(isDone, false);
+ values.add(1);
+ await values.close();
+ expect(isDone, false);
+ trigger.add(null);
+ await Future(() {});
+ expect(emittedValues, [
+ [1]
+ ]);
+ expect(isDone, true);
+ });
+
+ test('closes when source closes and there are no buffered', () async {
+ expect(isDone, false);
+ await values.close();
+ await Future(() {});
+ expect(isDone, true);
+ });
+
+ test('forwards errors from trigger', () async {
+ trigger.addError('error');
+ await Future(() {});
+ expect(errors, ['error']);
+ });
+
+ test('forwards errors from values', () async {
+ values.addError('error');
+ await Future(() {});
+ expect(errors, ['error']);
+ });
+ });
+
+ group('long polling', () {
+ setUp(() {
+ setUpForStreamTypes(triggerType, valuesType, longPoll: true);
+ });
+
+ test('emits immediately if trigger emits before a value', () async {
+ trigger.add(null);
+ await Future(() {});
+ expect(emittedValues, isEmpty);
+ values.add(1);
+ await Future(() {});
+ expect(emittedValues, [
+ [1]
+ ]);
+ });
+
+ test('two triggers in a row - emit buffere then emit next value',
+ () async {
+ values
+ ..add(1)
+ ..add(2);
+ await Future(() {});
+ trigger
+ ..add(null)
+ ..add(null);
+ await Future(() {});
+ values.add(3);
+ await Future(() {});
+ expect(emittedValues, [
+ [1, 2],
+ [3]
+ ]);
+ });
+
+ test('pre-emptive trigger then trigger after values', () async {
+ trigger.add(null);
+ await Future(() {});
+ values
+ ..add(1)
+ ..add(2);
+ await Future(() {});
+ trigger.add(null);
+ await Future(() {});
+ expect(emittedValues, [
+ [1],
+ [2]
+ ]);
+ });
+
+ test('multiple pre-emptive triggers, only emits first value',
+ () async {
+ trigger
+ ..add(null)
+ ..add(null);
+ await Future(() {});
+ values
+ ..add(1)
+ ..add(2);
+ await Future(() {});
+ expect(emittedValues, [
+ [1]
+ ]);
+ });
+
+ test('closes if there is no waiting long poll when source closes',
+ () async {
+ expect(isDone, false);
+ values.add(1);
+ trigger.add(null);
+ await values.close();
+ await Future(() {});
+ expect(isDone, true);
+ });
+
+ test('waits to emit if there waiting long poll when trigger closes',
+ () async {
+ trigger.add(null);
+ await trigger.close();
+ expect(isDone, false);
+ values.add(1);
+ await Future(() {});
+ expect(emittedValues, [
+ [1]
+ ]);
+ expect(isDone, true);
+ });
+ });
+
+ group('immediate polling', () {
+ setUp(() {
+ setUpForStreamTypes(triggerType, valuesType, longPoll: false);
+ });
+
+ test('emits empty list before values', () async {
+ trigger.add(null);
+ await Future(() {});
+ expect(emittedValues, [<int>[]]);
+ });
+
+ test('emits empty list after emitting values', () async {
+ values
+ ..add(1)
+ ..add(2);
+ await Future(() {});
+ trigger
+ ..add(null)
+ ..add(null);
+ await Future(() {});
+ expect(emittedValues, [
+ [1, 2],
+ <int>[]
+ ]);
+ });
+ });
+ });
+ }
+ }
+
+ test('always cancels trigger if values is singlesubscription', () async {
+ setUpForStreamTypes('broadcast', 'single subscription', longPoll: true);
+ expect(triggerCanceled, false);
+ await subscription.cancel();
+ expect(triggerCanceled, true);
+
+ setUpForStreamTypes('single subscription', 'single subscription',
+ longPoll: true);
+ expect(triggerCanceled, false);
+ await subscription.cancel();
+ expect(triggerCanceled, true);
+ });
+
+ test('cancels trigger if trigger is broadcast', () async {
+ setUpForStreamTypes('broadcast', 'broadcast', longPoll: true);
+ expect(triggerCanceled, false);
+ await subscription.cancel();
+ expect(triggerCanceled, true);
+ });
+
+ test('pauses single subscription trigger for broadcast values', () async {
+ setUpForStreamTypes('single subscription', 'broadcast', longPoll: true);
+ expect(triggerCanceled, false);
+ expect(triggerPaused, false);
+ await subscription.cancel();
+ expect(triggerCanceled, false);
+ expect(triggerPaused, true);
+ });
+
+ for (var triggerType in streamTypes) {
+ test('cancel and relisten with [$triggerType] trigger', () async {
+ setUpForStreamTypes(triggerType, 'broadcast', longPoll: true);
+ values.add(1);
+ trigger.add(null);
+ await Future(() {});
+ expect(emittedValues, [
+ [1]
+ ]);
+ await subscription.cancel();
+ values.add(2);
+ trigger.add(null);
+ await Future(() {});
+ subscription = transformed.listen(emittedValues.add);
+ values.add(3);
+ trigger.add(null);
+ await Future(() {});
+ expect(emittedValues, [
+ [1],
+ [3]
+ ]);
+ });
+ }
+}
diff --git a/pkgs/stream_transform/test/combine_latest_all_test.dart b/pkgs/stream_transform/test/combine_latest_all_test.dart
new file mode 100644
index 0000000..f4b719c
--- /dev/null
+++ b/pkgs/stream_transform/test/combine_latest_all_test.dart
@@ -0,0 +1,166 @@
+// 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:async';
+
+import 'package:stream_transform/stream_transform.dart';
+import 'package:test/test.dart';
+
+Future<void> tick() => Future(() {});
+
+void main() {
+ group('combineLatestAll', () {
+ test('emits latest values', () async {
+ final first = StreamController<String>();
+ final second = StreamController<String>();
+ final third = StreamController<String>();
+ final combined = first.stream.combineLatestAll(
+ [second.stream, third.stream]).map((data) => data.join());
+
+ // first: a----b------------------c--------d---|
+ // second: --1---------2-----------------|
+ // third: -------&----------%---|
+ // combined: -------b1&--b2&---b2%---c2%------d2%-|
+
+ expect(combined,
+ emitsInOrder(['b1&', 'b2&', 'b2%', 'c2%', 'd2%', emitsDone]));
+
+ first.add('a');
+ await tick();
+ second.add('1');
+ await tick();
+ first.add('b');
+ await tick();
+ third.add('&');
+ await tick();
+ second.add('2');
+ await tick();
+ third.add('%');
+ await tick();
+ await third.close();
+ await tick();
+ first.add('c');
+ await tick();
+ await second.close();
+ await tick();
+ first.add('d');
+ await tick();
+ await first.close();
+ });
+
+ test('ends if a Stream closes without ever emitting a value', () async {
+ final first = StreamController<String>();
+ final second = StreamController<String>();
+ final combined = first.stream.combineLatestAll([second.stream]);
+
+ // first: -a------b-------|
+ // second: -----|
+ // combined: -----|
+
+ expect(combined, emits(emitsDone));
+
+ first.add('a');
+ await tick();
+ await second.close();
+ await tick();
+ first.add('b');
+ });
+
+ test('forwards errors', () async {
+ final first = StreamController<String>();
+ final second = StreamController<String>();
+ final combined = first.stream
+ .combineLatestAll([second.stream]).map((data) => data.join());
+
+ // first: -a---------|
+ // second: ----1---#
+ // combined: ----a1--#
+
+ expect(combined, emitsThrough(emitsError('doh')));
+
+ first.add('a');
+ await tick();
+ second.add('1');
+ await tick();
+ second.addError('doh');
+ });
+
+ test('ends after both streams have ended', () async {
+ final first = StreamController<String>();
+ final second = StreamController<String>();
+
+ var done = false;
+ first.stream.combineLatestAll([second.stream]).listen(null,
+ onDone: () => done = true);
+
+ // first: -a---|
+ // second: --------1--|
+ // combined: --------a1-|
+
+ first.add('a');
+ await tick();
+ await first.close();
+ await tick();
+
+ expect(done, isFalse);
+
+ second.add('1');
+ await tick();
+ await second.close();
+ await tick();
+
+ expect(done, isTrue);
+ });
+
+ group('broadcast source', () {
+ test('can cancel and relisten to broadcast stream', () async {
+ final first = StreamController<String>.broadcast();
+ final second = StreamController<String>.broadcast();
+ final combined = first.stream
+ .combineLatestAll([second.stream]).map((data) => data.join());
+
+ // first: a------b----------------c------d----e---|
+ // second: --1---------2---3---4------5-|
+ // combined: --a1---b1---b2--b3--b4-----c5--d5---e5--|
+ // sub1: ^-----------------!
+ // sub2: ----------------------^-----------------|
+
+ expect(combined.take(4), emitsInOrder(['a1', 'b1', 'b2', 'b3']));
+
+ first.add('a');
+ await tick();
+ second.add('1');
+ await tick();
+ first.add('b');
+ await tick();
+ second.add('2');
+ await tick();
+ second.add('3');
+ await tick();
+
+ // First subscription is canceled here by .take(4)
+ expect(first.hasListener, isFalse);
+ expect(second.hasListener, isFalse);
+
+ // This emit is thrown away because there are no subscribers
+ second.add('4');
+ await tick();
+
+ expect(combined, emitsInOrder(['c5', 'd5', 'e5', emitsDone]));
+
+ first.add('c');
+ await tick();
+ second.add('5');
+ await tick();
+ await second.close();
+ await tick();
+ first.add('d');
+ await tick();
+ first.add('e');
+ await tick();
+ await first.close();
+ });
+ });
+ });
+}
diff --git a/pkgs/stream_transform/test/combine_latest_test.dart b/pkgs/stream_transform/test/combine_latest_test.dart
new file mode 100644
index 0000000..1985c75
--- /dev/null
+++ b/pkgs/stream_transform/test/combine_latest_test.dart
@@ -0,0 +1,179 @@
+// 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:async';
+
+import 'package:stream_transform/stream_transform.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('combineLatest', () {
+ test('flows through combine callback', () async {
+ var source = StreamController<int>();
+ var other = StreamController<int>();
+ int sum(int a, int b) => a + b;
+
+ var results = <int>[];
+ unawaited(
+ source.stream.combineLatest(other.stream, sum).forEach(results.add));
+
+ source.add(1);
+ await Future(() {});
+ expect(results, isEmpty);
+
+ other.add(2);
+ await Future(() {});
+ expect(results, [3]);
+
+ source.add(3);
+ await Future(() {});
+ expect(results, [3, 5]);
+
+ source.add(4);
+ await Future(() {});
+ expect(results, [3, 5, 6]);
+
+ other.add(5);
+ await Future(() {});
+ expect(results, [3, 5, 6, 9]);
+ });
+
+ test('can combine different typed streams', () async {
+ var source = StreamController<String>();
+ var other = StreamController<int>();
+ String times(String a, int b) => a * b;
+
+ var results = <String>[];
+ unawaited(source.stream
+ .combineLatest(other.stream, times)
+ .forEach(results.add));
+
+ source
+ ..add('a')
+ ..add('b');
+ await Future(() {});
+ expect(results, isEmpty);
+
+ other.add(2);
+ await Future(() {});
+ expect(results, ['bb']);
+
+ other.add(3);
+ await Future(() {});
+ expect(results, ['bb', 'bbb']);
+
+ source.add('c');
+ await Future(() {});
+ expect(results, ['bb', 'bbb', 'ccc']);
+ });
+
+ test('ends after both streams have ended', () async {
+ var source = StreamController<int>();
+ var other = StreamController<int>();
+ int sum(int a, int b) => a + b;
+
+ var done = false;
+ source.stream
+ .combineLatest(other.stream, sum)
+ .listen(null, onDone: () => done = true);
+
+ source.add(1);
+
+ await source.close();
+ await Future(() {});
+ expect(done, false);
+
+ await other.close();
+ await Future(() {});
+ expect(done, true);
+ });
+
+ test('ends if source stream closes without ever emitting a value',
+ () async {
+ var source = const Stream<int>.empty();
+ var other = StreamController<int>();
+
+ int sum(int a, int b) => a + b;
+
+ var done = false;
+ source
+ .combineLatest(other.stream, sum)
+ .listen(null, onDone: () => done = true);
+
+ await Future(() {});
+ // Nothing can ever be emitted on the result, may as well close.
+ expect(done, true);
+ });
+
+ test('ends if other stream closes without ever emitting a value', () async {
+ var source = StreamController<int>();
+ var other = const Stream<int>.empty();
+
+ int sum(int a, int b) => a + b;
+
+ var done = false;
+ source.stream
+ .combineLatest(other, sum)
+ .listen(null, onDone: () => done = true);
+
+ await Future(() {});
+ // Nothing can ever be emitted on the result, may as well close.
+ expect(done, true);
+ });
+
+ test('forwards errors', () async {
+ var source = StreamController<int>();
+ var other = StreamController<int>();
+ int sum(int a, int b) => throw _NumberedException(3);
+
+ var errors = <Object>[];
+ source.stream
+ .combineLatest(other.stream, sum)
+ .listen(null, onError: errors.add);
+
+ source.addError(_NumberedException(1));
+ other.addError(_NumberedException(2));
+
+ source.add(1);
+ other.add(2);
+
+ await Future(() {});
+
+ expect(errors, [_isException(1), _isException(2), _isException(3)]);
+ });
+
+ group('broadcast source', () {
+ test('can cancel and relisten to broadcast stream', () async {
+ var source = StreamController<int>.broadcast();
+ var other = StreamController<int>();
+ int combine(int a, int b) => a + b;
+
+ var emittedValues = <int>[];
+ var transformed = source.stream.combineLatest(other.stream, combine);
+
+ var subscription = transformed.listen(emittedValues.add);
+
+ source.add(1);
+ other.add(2);
+ await Future(() {});
+ expect(emittedValues, [3]);
+
+ await subscription.cancel();
+
+ subscription = transformed.listen(emittedValues.add);
+ source.add(3);
+ await Future(() {});
+ expect(emittedValues, [3, 5]);
+ });
+ });
+ });
+}
+
+class _NumberedException implements Exception {
+ final int id;
+ _NumberedException(this.id);
+}
+
+Matcher _isException(int id) =>
+ const TypeMatcher<_NumberedException>().having((n) => n.id, 'id', id);
diff --git a/pkgs/stream_transform/test/concurrent_async_map_test.dart b/pkgs/stream_transform/test/concurrent_async_map_test.dart
new file mode 100644
index 0000000..1807f9f
--- /dev/null
+++ b/pkgs/stream_transform/test/concurrent_async_map_test.dart
@@ -0,0 +1,157 @@
+// 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.
+
+import 'dart:async';
+
+import 'package:stream_transform/stream_transform.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ late StreamController<int> controller;
+ late List<String> emittedValues;
+ late bool valuesCanceled;
+ late bool isDone;
+ late List<String> errors;
+ late Stream<String> transformed;
+ late StreamSubscription<String> subscription;
+
+ late List<Completer<String>> finishWork;
+ late List<dynamic> values;
+
+ Future<String> convert(int value) {
+ values.add(value);
+ var completer = Completer<String>();
+ finishWork.add(completer);
+ return completer.future;
+ }
+
+ for (var streamType in streamTypes) {
+ group('concurrentAsyncMap for stream type: [$streamType]', () {
+ setUp(() {
+ valuesCanceled = false;
+ controller = createController(streamType)
+ ..onCancel = () {
+ valuesCanceled = true;
+ };
+ emittedValues = [];
+ errors = [];
+ isDone = false;
+ finishWork = [];
+ values = [];
+ transformed = controller.stream.concurrentAsyncMap(convert);
+ subscription = transformed
+ .listen(emittedValues.add, onError: errors.add, onDone: () {
+ isDone = true;
+ });
+ });
+
+ test('does not emit before convert finishes', () async {
+ controller.add(1);
+ await Future(() {});
+ expect(emittedValues, isEmpty);
+ expect(values, [1]);
+ finishWork.first.complete('result');
+ await Future(() {});
+ expect(emittedValues, ['result']);
+ });
+
+ test('allows calls to convert before the last one finished', () async {
+ controller
+ ..add(1)
+ ..add(2)
+ ..add(3);
+ await Future(() {});
+ expect(values, [1, 2, 3]);
+ });
+
+ test('forwards errors directly without waiting for previous convert',
+ () async {
+ controller.add(1);
+ await Future(() {});
+ controller.addError('error');
+ await Future(() {});
+ expect(errors, ['error']);
+ });
+
+ test('forwards errors which occur during the convert', () async {
+ controller.add(1);
+ await Future(() {});
+ finishWork.first.completeError('error');
+ await Future(() {});
+ expect(errors, ['error']);
+ });
+
+ test('can continue handling events after an error', () async {
+ controller.add(1);
+ await Future(() {});
+ finishWork[0].completeError('error');
+ controller.add(2);
+ await Future(() {});
+ expect(values, [1, 2]);
+ finishWork[1].completeError('another');
+ await Future(() {});
+ expect(errors, ['error', 'another']);
+ });
+
+ test('cancels value subscription when output canceled', () async {
+ expect(valuesCanceled, false);
+ await subscription.cancel();
+ expect(valuesCanceled, true);
+ });
+
+ test('closes when values end if no conversion is pending', () async {
+ expect(isDone, false);
+ await controller.close();
+ await Future(() {});
+ expect(isDone, true);
+ });
+
+ if (streamType == 'broadcast') {
+ test('multiple listeners all get values', () async {
+ var otherValues = <String>[];
+ transformed.listen(otherValues.add);
+ controller.add(1);
+ await Future(() {});
+ finishWork.first.complete('result');
+ await Future(() {});
+ expect(emittedValues, ['result']);
+ expect(otherValues, ['result']);
+ });
+
+ test('multiple listeners get done when values end', () async {
+ var otherDone = false;
+ transformed.listen(null, onDone: () => otherDone = true);
+ controller.add(1);
+ await Future(() {});
+ await controller.close();
+ expect(isDone, false);
+ expect(otherDone, false);
+ finishWork.first.complete('');
+ await Future(() {});
+ expect(isDone, true);
+ expect(otherDone, true);
+ });
+
+ test('can cancel and relisten', () async {
+ controller.add(1);
+ await Future(() {});
+ finishWork.first.complete('first');
+ await Future(() {});
+ await subscription.cancel();
+ controller.add(2);
+ await Future(() {});
+ subscription = transformed.listen(emittedValues.add);
+ controller.add(3);
+ await Future(() {});
+ expect(values, [1, 3]);
+ finishWork[1].complete('second');
+ await Future(() {});
+ expect(emittedValues, ['first', 'second']);
+ });
+ }
+ });
+ }
+}
diff --git a/pkgs/stream_transform/test/debounce_test.dart b/pkgs/stream_transform/test/debounce_test.dart
new file mode 100644
index 0000000..19de055
--- /dev/null
+++ b/pkgs/stream_transform/test/debounce_test.dart
@@ -0,0 +1,310 @@
+// Copyright (c) 2017, 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 'package:fake_async/fake_async.dart';
+import 'package:stream_transform/stream_transform.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ for (var streamType in streamTypes) {
+ group('Stream type [$streamType]', () {
+ group('debounce - trailing', () {
+ late StreamController<int> values;
+ late List<int> emittedValues;
+ late bool valuesCanceled;
+ late bool isDone;
+ late List<String> errors;
+ late StreamSubscription<int> subscription;
+ late Stream<int> transformed;
+
+ setUp(() async {
+ valuesCanceled = false;
+ values = createController(streamType)
+ ..onCancel = () {
+ valuesCanceled = true;
+ };
+ emittedValues = [];
+ errors = [];
+ isDone = false;
+ transformed = values.stream.debounce(const Duration(milliseconds: 5));
+ });
+
+ void listen() {
+ subscription = transformed
+ .listen(emittedValues.add, onError: errors.add, onDone: () {
+ isDone = true;
+ });
+ }
+
+ test('cancels values', () async {
+ listen();
+ await subscription.cancel();
+ expect(valuesCanceled, true);
+ });
+
+ test('swallows values that come faster than duration', () {
+ fakeAsync((async) {
+ listen();
+ values
+ ..add(1)
+ ..add(2)
+ ..close();
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [2]);
+ });
+ });
+
+ test('outputs multiple values spaced further than duration', () {
+ fakeAsync((async) {
+ listen();
+ values.add(1);
+ async.elapse(const Duration(milliseconds: 6));
+ values.add(2);
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [1, 2]);
+ });
+ });
+
+ test('waits for pending value to close', () {
+ fakeAsync((async) {
+ listen();
+ values.add(1);
+ async.elapse(const Duration(milliseconds: 6));
+ values.close();
+ async.flushMicrotasks();
+ expect(isDone, true);
+ });
+ });
+
+ test('closes output if there are no pending values', () {
+ fakeAsync((async) {
+ listen();
+ values.add(1);
+ async.elapse(const Duration(milliseconds: 6));
+ values
+ ..add(2)
+ ..close();
+ async.flushMicrotasks();
+ expect(isDone, false);
+ async.elapse(const Duration(milliseconds: 6));
+ expect(isDone, true);
+ });
+ });
+
+ if (streamType == 'broadcast') {
+ test('multiple listeners all get values', () {
+ fakeAsync((async) {
+ listen();
+ var otherValues = <int>[];
+ transformed.listen(otherValues.add);
+ values
+ ..add(1)
+ ..add(2);
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [2]);
+ expect(otherValues, [2]);
+ });
+ });
+ }
+ });
+
+ group('debounce - leading', () {
+ late StreamController<int> values;
+ late List<int> emittedValues;
+ late Stream<int> transformed;
+ late bool isDone;
+
+ setUp(() async {
+ values = createController(streamType);
+ emittedValues = [];
+ isDone = false;
+ transformed = values.stream.debounce(const Duration(milliseconds: 5),
+ leading: true, trailing: false);
+ });
+
+ void listen() {
+ transformed.listen(emittedValues.add, onDone: () {
+ isDone = true;
+ });
+ }
+
+ test('swallows values that come faster than duration', () async {
+ listen();
+ values
+ ..add(1)
+ ..add(2);
+ await values.close();
+ expect(emittedValues, [1]);
+ });
+
+ test('outputs multiple values spaced further than duration', () {
+ fakeAsync((async) {
+ listen();
+ values.add(1);
+ async.elapse(const Duration(milliseconds: 6));
+ values.add(2);
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [1, 2]);
+ });
+ });
+
+ if (streamType == 'broadcast') {
+ test('multiple listeners all get values', () {
+ fakeAsync((async) {
+ listen();
+ var otherValues = <int>[];
+ transformed.listen(otherValues.add);
+ values
+ ..add(1)
+ ..add(2);
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [1]);
+ expect(otherValues, [1]);
+ });
+ });
+ }
+
+ test('closes output immediately if not waiting for trailing value',
+ () async {
+ listen();
+ values.add(1);
+ await values.close();
+ expect(isDone, true);
+ });
+ });
+
+ group('debounce - leading and trailing', () {
+ late StreamController<int> values;
+ late List<int> emittedValues;
+ late Stream<int> transformed;
+
+ setUp(() async {
+ values = createController(streamType);
+ emittedValues = [];
+ transformed = values.stream.debounce(const Duration(milliseconds: 5),
+ leading: true, trailing: true);
+ });
+ void listen() {
+ transformed.listen(emittedValues.add);
+ }
+
+ test('swallows values that come faster than duration', () {
+ fakeAsync((async) {
+ listen();
+ values
+ ..add(1)
+ ..add(2)
+ ..add(3)
+ ..close();
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [1, 3]);
+ });
+ });
+
+ test('outputs multiple values spaced further than duration', () {
+ fakeAsync((async) {
+ listen();
+ values.add(1);
+ async.elapse(const Duration(milliseconds: 6));
+ values.add(2);
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [1, 2]);
+ });
+ });
+
+ if (streamType == 'broadcast') {
+ test('multiple listeners all get values', () {
+ fakeAsync((async) {
+ listen();
+ var otherValues = <int>[];
+ transformed.listen(otherValues.add);
+ values
+ ..add(1)
+ ..add(2);
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [1, 2]);
+ expect(otherValues, [1, 2]);
+ });
+ });
+ }
+ });
+
+ group('debounceBuffer', () {
+ late StreamController<int> values;
+ late List<List<int>> emittedValues;
+ late List<String> errors;
+ late Stream<List<int>> transformed;
+
+ setUp(() async {
+ values = createController(streamType);
+ emittedValues = [];
+ errors = [];
+ transformed =
+ values.stream.debounceBuffer(const Duration(milliseconds: 5));
+ });
+ void listen() {
+ transformed.listen(emittedValues.add, onError: errors.add);
+ }
+
+ test('Emits all values as a list', () {
+ fakeAsync((async) {
+ listen();
+ values
+ ..add(1)
+ ..add(2)
+ ..close();
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [
+ [1, 2]
+ ]);
+ });
+ });
+
+ test('separate lists for multiple values spaced further than duration',
+ () {
+ fakeAsync((async) {
+ listen();
+ values.add(1);
+ async.elapse(const Duration(milliseconds: 6));
+ values.add(2);
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [
+ [1],
+ [2]
+ ]);
+ });
+ });
+
+ if (streamType == 'broadcast') {
+ test('multiple listeners all get values', () {
+ fakeAsync((async) {
+ listen();
+ var otherValues = <List<int>>[];
+ transformed.listen(otherValues.add);
+ values
+ ..add(1)
+ ..add(2);
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [
+ [1, 2]
+ ]);
+ expect(otherValues, [
+ [1, 2]
+ ]);
+ });
+ });
+ }
+ });
+ });
+ }
+ test('allows nulls', () async {
+ final values = Stream<int?>.fromIterable([null]);
+ final transformed = values.debounce(const Duration(milliseconds: 1));
+ expect(await transformed.toList(), [null]);
+ });
+}
diff --git a/pkgs/stream_transform/test/followd_by_test.dart b/pkgs/stream_transform/test/followd_by_test.dart
new file mode 100644
index 0000000..d600d13
--- /dev/null
+++ b/pkgs/stream_transform/test/followd_by_test.dart
@@ -0,0 +1,159 @@
+// Copyright (c) 2017, 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 'package:stream_transform/stream_transform.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ for (var firstType in streamTypes) {
+ for (var secondType in streamTypes) {
+ group('followedBy [$firstType] with [$secondType]', () {
+ late StreamController<int> first;
+ late StreamController<int> second;
+
+ late List<int> emittedValues;
+ late bool firstCanceled;
+ late bool secondCanceled;
+ late bool secondListened;
+ late bool isDone;
+ late List<String> errors;
+ late Stream<int> transformed;
+ late StreamSubscription<int> subscription;
+
+ setUp(() async {
+ firstCanceled = false;
+ secondCanceled = false;
+ secondListened = false;
+ first = createController(firstType)
+ ..onCancel = () {
+ firstCanceled = true;
+ };
+ second = createController(secondType)
+ ..onCancel = () {
+ secondCanceled = true;
+ }
+ ..onListen = () {
+ secondListened = true;
+ };
+ emittedValues = [];
+ errors = [];
+ isDone = false;
+ transformed = first.stream.followedBy(second.stream);
+ subscription = transformed
+ .listen(emittedValues.add, onError: errors.add, onDone: () {
+ isDone = true;
+ });
+ });
+
+ test('adds all values from both streams', () async {
+ first
+ ..add(1)
+ ..add(2);
+ await first.close();
+ await Future(() {});
+ second
+ ..add(3)
+ ..add(4);
+ await Future(() {});
+ expect(emittedValues, [1, 2, 3, 4]);
+ });
+
+ test('Does not listen to second stream before first stream finishes',
+ () async {
+ expect(secondListened, false);
+ await first.close();
+ expect(secondListened, true);
+ });
+
+ test('closes stream after both inputs close', () async {
+ await first.close();
+ await second.close();
+ expect(isDone, true);
+ });
+
+ test('cancels any type of first stream on cancel', () async {
+ await subscription.cancel();
+ expect(firstCanceled, true);
+ });
+
+ if (firstType == 'single subscription') {
+ test(
+ 'cancels any type of second stream on cancel if first is '
+ 'broadcast', () async {
+ await first.close();
+ await subscription.cancel();
+ expect(secondCanceled, true);
+ });
+
+ if (secondType == 'broadcast') {
+ test('can pause and resume during second stream - dropping values',
+ () async {
+ await first.close();
+ subscription.pause();
+ second.add(1);
+ await Future(() {});
+ subscription.resume();
+ second.add(2);
+ await Future(() {});
+ expect(emittedValues, [2]);
+ });
+ } else {
+ test('can pause and resume during second stream - buffering values',
+ () async {
+ await first.close();
+ subscription.pause();
+ second.add(1);
+ await Future(() {});
+ subscription.resume();
+ second.add(2);
+ await Future(() {});
+ expect(emittedValues, [1, 2]);
+ });
+ }
+ }
+
+ if (firstType == 'broadcast') {
+ test('can cancel and relisten during first stream', () async {
+ await subscription.cancel();
+ first.add(1);
+ subscription = transformed.listen(emittedValues.add);
+ first.add(2);
+ await Future(() {});
+ expect(emittedValues, [2]);
+ });
+
+ test('can cancel and relisten during second stream', () async {
+ await first.close();
+ await subscription.cancel();
+ second.add(2);
+ await Future(() {});
+ subscription = transformed.listen(emittedValues.add);
+ second.add(3);
+ await Future(() {});
+ expect(emittedValues, [3]);
+ });
+
+ test('forwards values to multiple listeners', () async {
+ var otherValues = <int>[];
+ transformed.listen(otherValues.add);
+ first.add(1);
+ await first.close();
+ second.add(2);
+ await Future(() {});
+ var thirdValues = <int>[];
+ transformed.listen(thirdValues.add);
+ second.add(3);
+ await Future(() {});
+ expect(emittedValues, [1, 2, 3]);
+ expect(otherValues, [1, 2, 3]);
+ expect(thirdValues, [3]);
+ });
+ }
+ });
+ }
+ }
+}
diff --git a/pkgs/stream_transform/test/from_handlers_test.dart b/pkgs/stream_transform/test/from_handlers_test.dart
new file mode 100644
index 0000000..694199c
--- /dev/null
+++ b/pkgs/stream_transform/test/from_handlers_test.dart
@@ -0,0 +1,183 @@
+// Copyright (c) 2017, 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 'package:stream_transform/src/from_handlers.dart';
+import 'package:test/test.dart';
+
+void main() {
+ late StreamController<int> values;
+ late List<int> emittedValues;
+ late bool valuesCanceled;
+ late bool isDone;
+ late List<String> errors;
+ late Stream<int> transformed;
+ late StreamSubscription<int> subscription;
+
+ void setUpForController(StreamController<int> controller,
+ Stream<int> Function(Stream<int>) transform) {
+ valuesCanceled = false;
+ values = controller
+ ..onCancel = () {
+ valuesCanceled = true;
+ };
+ emittedValues = [];
+ errors = [];
+ isDone = false;
+ transformed = transform(values.stream);
+ subscription =
+ transformed.listen(emittedValues.add, onError: errors.add, onDone: () {
+ isDone = true;
+ });
+ }
+
+ group('default from_handlers', () {
+ group('Single subscription stream', () {
+ setUp(() {
+ setUpForController(StreamController(),
+ (s) => s.transformByHandlers(onData: (e, sink) => sink.add(e)));
+ });
+
+ test('has correct stream type', () {
+ expect(transformed.isBroadcast, false);
+ });
+
+ test('forwards values', () async {
+ values
+ ..add(1)
+ ..add(2);
+ await Future(() {});
+ expect(emittedValues, [1, 2]);
+ });
+
+ test('forwards errors', () async {
+ values.addError('error');
+ await Future(() {});
+ expect(errors, ['error']);
+ });
+
+ test('forwards done', () async {
+ await values.close();
+ expect(isDone, true);
+ });
+
+ test('forwards cancel', () async {
+ await subscription.cancel();
+ expect(valuesCanceled, true);
+ });
+ });
+
+ group('broadcast stream with muliple listeners', () {
+ late List<int> emittedValues2;
+ late List<String> errors2;
+ late bool isDone2;
+ late StreamSubscription<int> subscription2;
+
+ setUp(() {
+ setUpForController(StreamController.broadcast(),
+ (s) => s.transformByHandlers(onData: (e, sink) => sink.add(e)));
+ emittedValues2 = [];
+ errors2 = [];
+ isDone2 = false;
+ subscription2 = transformed
+ .listen(emittedValues2.add, onError: errors2.add, onDone: () {
+ isDone2 = true;
+ });
+ });
+
+ test('has correct stream type', () {
+ expect(transformed.isBroadcast, true);
+ });
+
+ test('forwards values', () async {
+ values
+ ..add(1)
+ ..add(2);
+ await Future(() {});
+ expect(emittedValues, [1, 2]);
+ expect(emittedValues2, [1, 2]);
+ });
+
+ test('forwards errors', () async {
+ values.addError('error');
+ await Future(() {});
+ expect(errors, ['error']);
+ expect(errors2, ['error']);
+ });
+
+ test('forwards done', () async {
+ await values.close();
+ expect(isDone, true);
+ expect(isDone2, true);
+ });
+
+ test('forwards cancel', () async {
+ await subscription.cancel();
+ expect(valuesCanceled, false);
+ await subscription2.cancel();
+ expect(valuesCanceled, true);
+ });
+ });
+ });
+
+ group('custom handlers', () {
+ group('single subscription', () {
+ setUp(() async {
+ setUpForController(
+ StreamController(),
+ (s) => s.transformByHandlers(onData: (value, sink) {
+ sink.add(value + 1);
+ }));
+ });
+ test('uses transform from handleData', () async {
+ values
+ ..add(1)
+ ..add(2);
+ await Future(() {});
+ expect(emittedValues, [2, 3]);
+ });
+ });
+
+ group('broadcast stream with multiple listeners', () {
+ late int dataCallCount;
+ late int doneCallCount;
+ late int errorCallCount;
+
+ setUp(() async {
+ dataCallCount = 0;
+ doneCallCount = 0;
+ errorCallCount = 0;
+ setUpForController(
+ StreamController.broadcast(),
+ (s) => s.transformByHandlers(onData: (value, sink) {
+ dataCallCount++;
+ }, onError: (error, stackTrace, sink) {
+ errorCallCount++;
+ sink.addError(error, stackTrace);
+ }, onDone: (sink) {
+ doneCallCount++;
+ }));
+ transformed.listen((_) {}, onError: (_, __) {});
+ });
+
+ test('handles data once', () async {
+ values.add(1);
+ await Future(() {});
+ expect(dataCallCount, 1);
+ });
+
+ test('handles done once', () async {
+ await values.close();
+ expect(doneCallCount, 1);
+ });
+
+ test('handles errors once', () async {
+ values.addError('error');
+ await Future(() {});
+ expect(errorCallCount, 1);
+ });
+ });
+ });
+}
diff --git a/pkgs/stream_transform/test/merge_test.dart b/pkgs/stream_transform/test/merge_test.dart
new file mode 100644
index 0000000..ecbf97f
--- /dev/null
+++ b/pkgs/stream_transform/test/merge_test.dart
@@ -0,0 +1,140 @@
+// Copyright (c) 2017, 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 'package:stream_transform/stream_transform.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('merge', () {
+ test('includes all values', () async {
+ var first = Stream.fromIterable([1, 2, 3]);
+ var second = Stream.fromIterable([4, 5, 6]);
+ var allValues = await first.merge(second).toList();
+ expect(allValues, containsAllInOrder([1, 2, 3]));
+ expect(allValues, containsAllInOrder([4, 5, 6]));
+ expect(allValues, hasLength(6));
+ });
+
+ test('cancels both sources', () async {
+ var firstCanceled = false;
+ var first = StreamController<int>()
+ ..onCancel = () {
+ firstCanceled = true;
+ };
+ var secondCanceled = false;
+ var second = StreamController<int>()
+ ..onCancel = () {
+ secondCanceled = true;
+ };
+ var subscription = first.stream.merge(second.stream).listen((_) {});
+ await subscription.cancel();
+ expect(firstCanceled, true);
+ expect(secondCanceled, true);
+ });
+
+ test('completes when both sources complete', () async {
+ var first = StreamController<int>();
+ var second = StreamController<int>();
+ var isDone = false;
+ first.stream.merge(second.stream).listen((_) {}, onDone: () {
+ isDone = true;
+ });
+ await first.close();
+ expect(isDone, false);
+ await second.close();
+ expect(isDone, true);
+ });
+
+ test('can cancel and relisten to broadcast stream', () async {
+ var first = StreamController<int>.broadcast();
+ var second = StreamController<int>();
+ var emittedValues = <int>[];
+ var transformed = first.stream.merge(second.stream);
+ var subscription = transformed.listen(emittedValues.add);
+ first.add(1);
+ second.add(2);
+ await Future(() {});
+ expect(emittedValues, contains(1));
+ expect(emittedValues, contains(2));
+ await subscription.cancel();
+ emittedValues = [];
+ subscription = transformed.listen(emittedValues.add);
+ first.add(3);
+ second.add(4);
+ await Future(() {});
+ expect(emittedValues, contains(3));
+ expect(emittedValues, contains(4));
+ });
+ });
+
+ group('mergeAll', () {
+ test('includes all values', () async {
+ var first = Stream.fromIterable([1, 2, 3]);
+ var second = Stream.fromIterable([4, 5, 6]);
+ var third = Stream.fromIterable([7, 8, 9]);
+ var allValues = await first.mergeAll([second, third]).toList();
+ expect(allValues, containsAllInOrder([1, 2, 3]));
+ expect(allValues, containsAllInOrder([4, 5, 6]));
+ expect(allValues, containsAllInOrder([7, 8, 9]));
+ expect(allValues, hasLength(9));
+ });
+
+ test('handles mix of broadcast and single-subscription', () async {
+ var firstCanceled = false;
+ var first = StreamController<int>.broadcast()
+ ..onCancel = () {
+ firstCanceled = true;
+ };
+ var secondBroadcastCanceled = false;
+ var secondBroadcast = StreamController<int>.broadcast()
+ ..onCancel = () {
+ secondBroadcastCanceled = true;
+ };
+ var secondSingleCanceled = false;
+ var secondSingle = StreamController<int>()
+ ..onCancel = () {
+ secondSingleCanceled = true;
+ };
+
+ var merged =
+ first.stream.mergeAll([secondBroadcast.stream, secondSingle.stream]);
+
+ var firstListenerValues = <int>[];
+ var secondListenerValues = <int>[];
+
+ var firstSubscription = merged.listen(firstListenerValues.add);
+ var secondSubscription = merged.listen(secondListenerValues.add);
+
+ first.add(1);
+ secondBroadcast.add(2);
+ secondSingle.add(3);
+
+ await Future(() {});
+ await firstSubscription.cancel();
+
+ expect(firstCanceled, false);
+ expect(secondBroadcastCanceled, false);
+ expect(secondSingleCanceled, false);
+
+ first.add(4);
+ secondBroadcast.add(5);
+ secondSingle.add(6);
+
+ await Future(() {});
+ await secondSubscription.cancel();
+
+ await Future(() {});
+ expect(firstCanceled, true);
+ expect(secondBroadcastCanceled, true);
+ expect(secondSingleCanceled, false,
+ reason: 'Single subscription streams merged into broadcast streams '
+ 'are not canceled');
+
+ expect(firstListenerValues, [1, 2, 3]);
+ expect(secondListenerValues, [1, 2, 3, 4, 5, 6]);
+ });
+ });
+}
diff --git a/pkgs/stream_transform/test/sample_test.dart b/pkgs/stream_transform/test/sample_test.dart
new file mode 100644
index 0000000..66ca09d
--- /dev/null
+++ b/pkgs/stream_transform/test/sample_test.dart
@@ -0,0 +1,291 @@
+// 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 'dart:async';
+
+import 'package:stream_transform/stream_transform.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ late StreamController<void> trigger;
+ late StreamController<int> values;
+ late List<int> emittedValues;
+ late bool valuesCanceled;
+ late bool triggerCanceled;
+ late bool triggerPaused;
+ late bool isDone;
+ late List<String> errors;
+ late Stream<int> transformed;
+ late StreamSubscription<int> subscription;
+
+ void setUpForStreamTypes(String triggerType, String valuesType,
+ {required bool longPoll}) {
+ valuesCanceled = false;
+ triggerCanceled = false;
+ triggerPaused = false;
+ trigger = createController(triggerType)
+ ..onCancel = () {
+ triggerCanceled = true;
+ };
+ if (triggerType == 'single subscription') {
+ trigger.onPause = () {
+ triggerPaused = true;
+ };
+ }
+ values = createController(valuesType)
+ ..onCancel = () {
+ valuesCanceled = true;
+ };
+ emittedValues = [];
+ errors = [];
+ isDone = false;
+ transformed = values.stream.sample(trigger.stream, longPoll: longPoll);
+ subscription =
+ transformed.listen(emittedValues.add, onError: errors.add, onDone: () {
+ isDone = true;
+ });
+ }
+
+ for (var triggerType in streamTypes) {
+ for (var valuesType in streamTypes) {
+ group('Trigger type: [$triggerType], Values type: [$valuesType]', () {
+ group('general behavior', () {
+ setUp(() {
+ setUpForStreamTypes(triggerType, valuesType, longPoll: true);
+ });
+
+ test('does not emit before `trigger`', () async {
+ values.add(1);
+ await Future(() {});
+ expect(emittedValues, isEmpty);
+ trigger.add(null);
+ await Future(() {});
+ expect(emittedValues, [1]);
+ });
+
+ test('keeps most recent event between triggers', () async {
+ values
+ ..add(1)
+ ..add(2);
+ await Future(() {});
+ trigger.add(null);
+ values
+ ..add(3)
+ ..add(4);
+ await Future(() {});
+ trigger.add(null);
+ await Future(() {});
+ expect(emittedValues, [2, 4]);
+ });
+
+ test('cancels value subscription when output canceled', () async {
+ expect(valuesCanceled, false);
+ await subscription.cancel();
+ expect(valuesCanceled, true);
+ });
+
+ test('closes when trigger ends', () async {
+ expect(isDone, false);
+ await trigger.close();
+ await Future(() {});
+ expect(isDone, true);
+ });
+
+ test('closes after outputting final values when source closes',
+ () async {
+ expect(isDone, false);
+ values.add(1);
+ await values.close();
+ expect(isDone, false);
+ trigger.add(null);
+ await Future(() {});
+ expect(emittedValues, [1]);
+ expect(isDone, true);
+ });
+
+ test('closes when source closes and there is no pending', () async {
+ expect(isDone, false);
+ await values.close();
+ await Future(() {});
+ expect(isDone, true);
+ });
+
+ test('forwards errors from trigger', () async {
+ trigger.addError('error');
+ await Future(() {});
+ expect(errors, ['error']);
+ });
+
+ test('forwards errors from values', () async {
+ values.addError('error');
+ await Future(() {});
+ expect(errors, ['error']);
+ });
+ });
+
+ group('long polling', () {
+ setUp(() {
+ setUpForStreamTypes(triggerType, valuesType, longPoll: true);
+ });
+
+ test('emits immediately if trigger emits before a value', () async {
+ trigger.add(null);
+ await Future(() {});
+ expect(emittedValues, isEmpty);
+ values.add(1);
+ await Future(() {});
+ expect(emittedValues, [1]);
+ });
+
+ test('two triggers in a row - emit buffere then emit next value',
+ () async {
+ values
+ ..add(1)
+ ..add(2);
+ await Future(() {});
+ trigger
+ ..add(null)
+ ..add(null);
+ await Future(() {});
+ values.add(3);
+ await Future(() {});
+ expect(emittedValues, [2, 3]);
+ });
+
+ test('pre-emptive trigger then trigger after values', () async {
+ trigger.add(null);
+ await Future(() {});
+ values
+ ..add(1)
+ ..add(2);
+ await Future(() {});
+ trigger.add(null);
+ await Future(() {});
+ expect(emittedValues, [1, 2]);
+ });
+
+ test('multiple pre-emptive triggers, only emits first value',
+ () async {
+ trigger
+ ..add(null)
+ ..add(null);
+ await Future(() {});
+ values
+ ..add(1)
+ ..add(2);
+ await Future(() {});
+ expect(emittedValues, [1]);
+ });
+
+ test('closes if there is no waiting long poll when source closes',
+ () async {
+ expect(isDone, false);
+ values.add(1);
+ trigger.add(null);
+ await values.close();
+ await Future(() {});
+ expect(isDone, true);
+ });
+
+ test('waits to emit if there waiting long poll when trigger closes',
+ () async {
+ trigger.add(null);
+ await trigger.close();
+ expect(isDone, false);
+ values.add(1);
+ await Future(() {});
+ expect(emittedValues, [1]);
+ expect(isDone, true);
+ });
+ });
+
+ group('immediate polling', () {
+ setUp(() {
+ setUpForStreamTypes(triggerType, valuesType, longPoll: false);
+ });
+
+ test('ignores trigger before values', () async {
+ trigger.add(null);
+ await Future(() {});
+ values
+ ..add(1)
+ ..add(2);
+ await Future(() {});
+ trigger.add(null);
+ await Future(() {});
+ expect(emittedValues, [2]);
+ });
+
+ test('ignores trigger if no pending values', () async {
+ values
+ ..add(1)
+ ..add(2);
+ await Future(() {});
+ trigger
+ ..add(null)
+ ..add(null);
+ await Future(() {});
+ values
+ ..add(3)
+ ..add(4);
+ await Future(() {});
+ trigger.add(null);
+ await Future(() {});
+ expect(emittedValues, [2, 4]);
+ });
+ });
+ });
+ }
+ }
+
+ test('always cancels trigger if values is singlesubscription', () async {
+ setUpForStreamTypes('broadcast', 'single subscription', longPoll: true);
+ expect(triggerCanceled, false);
+ await subscription.cancel();
+ expect(triggerCanceled, true);
+
+ setUpForStreamTypes('single subscription', 'single subscription',
+ longPoll: true);
+ expect(triggerCanceled, false);
+ await subscription.cancel();
+ expect(triggerCanceled, true);
+ });
+
+ test('cancels trigger if trigger is broadcast', () async {
+ setUpForStreamTypes('broadcast', 'broadcast', longPoll: true);
+ expect(triggerCanceled, false);
+ await subscription.cancel();
+ expect(triggerCanceled, true);
+ });
+
+ test('pauses single subscription trigger for broadcast values', () async {
+ setUpForStreamTypes('single subscription', 'broadcast', longPoll: true);
+ expect(triggerCanceled, false);
+ expect(triggerPaused, false);
+ await subscription.cancel();
+ expect(triggerCanceled, false);
+ expect(triggerPaused, true);
+ });
+
+ for (var triggerType in streamTypes) {
+ test('cancel and relisten with [$triggerType] trigger', () async {
+ setUpForStreamTypes(triggerType, 'broadcast', longPoll: true);
+ values.add(1);
+ trigger.add(null);
+ await Future(() {});
+ expect(emittedValues, [1]);
+ await subscription.cancel();
+ values.add(2);
+ trigger.add(null);
+ await Future(() {});
+ subscription = transformed.listen(emittedValues.add);
+ values.add(3);
+ trigger.add(null);
+ await Future(() {});
+ expect(emittedValues, [1, 3]);
+ });
+ }
+}
diff --git a/pkgs/stream_transform/test/scan_test.dart b/pkgs/stream_transform/test/scan_test.dart
new file mode 100644
index 0000000..3c749e7
--- /dev/null
+++ b/pkgs/stream_transform/test/scan_test.dart
@@ -0,0 +1,109 @@
+// Copyright (c) 2017, 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 'package:stream_transform/stream_transform.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('Scan', () {
+ test('produces intermediate values', () async {
+ var source = Stream.fromIterable([1, 2, 3, 4]);
+ int sum(int x, int y) => x + y;
+ var result = await source.scan(0, sum).toList();
+
+ expect(result, [1, 3, 6, 10]);
+ });
+
+ test('can create a broadcast stream', () {
+ var source = StreamController<int>.broadcast();
+
+ var transformed = source.stream.scan(null, (_, __) {});
+
+ expect(transformed.isBroadcast, true);
+ });
+
+ test('forwards errors from source', () async {
+ var source = StreamController<int>();
+
+ int sum(int x, int y) => x + y;
+
+ var errors = <Object>[];
+
+ source.stream.scan(0, sum).listen(null, onError: errors.add);
+
+ source.addError(StateError('fail'));
+ await Future(() {});
+
+ expect(errors, [isStateError]);
+ });
+
+ group('with async combine', () {
+ test('returns a Stream of non-futures', () async {
+ var source = Stream.fromIterable([1, 2, 3, 4]);
+ Future<int> sum(int x, int y) async => x + y;
+ var result = await source.scan(0, sum).toList();
+
+ expect(result, [1, 3, 6, 10]);
+ });
+
+ test('can return a Stream of futures when specified', () async {
+ var source = Stream.fromIterable([1, 2]);
+ Future<int> sum(Future<int> x, int y) async => (await x) + y;
+ var result =
+ await source.scan<Future<int>>(Future.value(0), sum).toList();
+
+ expect(result, [
+ const TypeMatcher<Future<void>>(),
+ const TypeMatcher<Future<void>>()
+ ]);
+ expect(await result.wait, [1, 3]);
+ });
+
+ test('does not call for subsequent values while waiting', () async {
+ var source = StreamController<int>();
+
+ var calledWith = <int>[];
+ var block = Completer<void>();
+ Future<int> combine(int x, int y) async {
+ calledWith.add(y);
+ await block.future;
+ return x + y;
+ }
+
+ var results = <int>[];
+
+ unawaited(source.stream.scan(0, combine).forEach(results.add));
+
+ source
+ ..add(1)
+ ..add(2);
+ await Future(() {});
+ expect(calledWith, [1]);
+ expect(results, isEmpty);
+
+ block.complete();
+ await Future(() {});
+ expect(calledWith, [1, 2]);
+ expect(results, [1, 3]);
+ });
+
+ test('forwards async errors', () async {
+ var source = StreamController<int>();
+
+ Future<int> combine(int x, int y) async => throw StateError('fail');
+
+ var errors = <Object>[];
+
+ source.stream.scan(0, combine).listen(null, onError: errors.add);
+
+ source.add(1);
+ await Future(() {});
+
+ expect(errors, [isStateError]);
+ });
+ });
+ });
+}
diff --git a/pkgs/stream_transform/test/start_with_test.dart b/pkgs/stream_transform/test/start_with_test.dart
new file mode 100644
index 0000000..35f0330
--- /dev/null
+++ b/pkgs/stream_transform/test/start_with_test.dart
@@ -0,0 +1,167 @@
+// Copyright (c) 2017, 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 'package:stream_transform/stream_transform.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ late StreamController<int> values;
+ late Stream<int> transformed;
+ late StreamSubscription<int> subscription;
+
+ late List<int> emittedValues;
+ late bool isDone;
+
+ void setupForStreamType(
+ String streamType, Stream<int> Function(Stream<int>) transform) {
+ emittedValues = [];
+ isDone = false;
+ values = createController(streamType);
+ transformed = transform(values.stream);
+ subscription =
+ transformed.listen(emittedValues.add, onDone: () => isDone = true);
+ }
+
+ for (var streamType in streamTypes) {
+ group('startWith then [$streamType]', () {
+ setUp(() => setupForStreamType(streamType, (s) => s.startWith(1)));
+
+ test('outputs all values', () async {
+ values
+ ..add(2)
+ ..add(3);
+ await Future(() {});
+ expect(emittedValues, [1, 2, 3]);
+ });
+
+ test('outputs initial when followed by empty stream', () async {
+ await values.close();
+ expect(emittedValues, [1]);
+ });
+
+ test('closes with values', () async {
+ expect(isDone, false);
+ await values.close();
+ expect(isDone, true);
+ });
+
+ if (streamType == 'broadcast') {
+ test('can cancel and relisten', () async {
+ values.add(2);
+ await Future(() {});
+ await subscription.cancel();
+ subscription = transformed.listen(emittedValues.add);
+ values.add(3);
+ await Future(() {});
+ await Future(() {});
+ expect(emittedValues, [1, 2, 3]);
+ });
+ }
+ });
+
+ group('startWithMany then [$streamType]', () {
+ setUp(() async {
+ setupForStreamType(streamType, (s) => s.startWithMany([1, 2]));
+ // Ensure all initial values go through
+ await Future(() {});
+ });
+
+ test('outputs all values', () async {
+ values
+ ..add(3)
+ ..add(4);
+ await Future(() {});
+ expect(emittedValues, [1, 2, 3, 4]);
+ });
+
+ test('outputs initial when followed by empty stream', () async {
+ await values.close();
+ expect(emittedValues, [1, 2]);
+ });
+
+ test('closes with values', () async {
+ expect(isDone, false);
+ await values.close();
+ expect(isDone, true);
+ });
+
+ if (streamType == 'broadcast') {
+ test('can cancel and relisten', () async {
+ values.add(3);
+ await Future(() {});
+ await subscription.cancel();
+ subscription = transformed.listen(emittedValues.add);
+ values.add(4);
+ await Future(() {});
+ expect(emittedValues, [1, 2, 3, 4]);
+ });
+ }
+ });
+
+ for (var startingStreamType in streamTypes) {
+ group('startWithStream [$startingStreamType] then [$streamType]', () {
+ late StreamController<int> starting;
+ setUp(() async {
+ starting = createController(startingStreamType);
+ setupForStreamType(
+ streamType, (s) => s.startWithStream(starting.stream));
+ });
+
+ test('outputs all values', () async {
+ starting
+ ..add(1)
+ ..add(2);
+ await starting.close();
+ values
+ ..add(3)
+ ..add(4);
+ await Future(() {});
+ expect(emittedValues, [1, 2, 3, 4]);
+ });
+
+ test('closes with values', () async {
+ expect(isDone, false);
+ await starting.close();
+ expect(isDone, false);
+ await values.close();
+ expect(isDone, true);
+ });
+
+ if (streamType == 'broadcast') {
+ test('can cancel and relisten during starting', () async {
+ starting.add(1);
+ await Future(() {});
+ await subscription.cancel();
+ subscription = transformed.listen(emittedValues.add);
+ starting.add(2);
+ await starting.close();
+ values
+ ..add(3)
+ ..add(4);
+ await Future(() {});
+ expect(emittedValues, [1, 2, 3, 4]);
+ });
+
+ test('can cancel and relisten during values', () async {
+ starting
+ ..add(1)
+ ..add(2);
+ await starting.close();
+ values.add(3);
+ await Future(() {});
+ await subscription.cancel();
+ subscription = transformed.listen(emittedValues.add);
+ values.add(4);
+ await Future(() {});
+ expect(emittedValues, [1, 2, 3, 4]);
+ });
+ }
+ });
+ }
+ }
+}
diff --git a/pkgs/stream_transform/test/switch_test.dart b/pkgs/stream_transform/test/switch_test.dart
new file mode 100644
index 0000000..9e70c08
--- /dev/null
+++ b/pkgs/stream_transform/test/switch_test.dart
@@ -0,0 +1,229 @@
+// Copyright (c) 2017, 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 'package:stream_transform/stream_transform.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ for (var outerType in streamTypes) {
+ for (var innerType in streamTypes) {
+ group('Outer type: [$outerType], Inner type: [$innerType]', () {
+ late StreamController<int> first;
+ late StreamController<int> second;
+ late StreamController<int> third;
+ late StreamController<Stream<int>> outer;
+
+ late List<int> emittedValues;
+ late bool firstCanceled;
+ late bool outerCanceled;
+ late bool isDone;
+ late List<String> errors;
+ late StreamSubscription<int> subscription;
+
+ setUp(() async {
+ firstCanceled = false;
+ outerCanceled = false;
+ outer = createController(outerType)
+ ..onCancel = () {
+ outerCanceled = true;
+ };
+ first = createController(innerType)
+ ..onCancel = () {
+ firstCanceled = true;
+ };
+ second = createController(innerType);
+ third = createController(innerType);
+ emittedValues = [];
+ errors = [];
+ isDone = false;
+ subscription = outer.stream
+ .switchLatest()
+ .listen(emittedValues.add, onError: errors.add, onDone: () {
+ isDone = true;
+ });
+ });
+
+ test('forwards events', () async {
+ outer.add(first.stream);
+ await Future(() {});
+ first
+ ..add(1)
+ ..add(2);
+ await Future(() {});
+
+ outer.add(second.stream);
+ await Future(() {});
+ second
+ ..add(3)
+ ..add(4);
+ await Future(() {});
+
+ expect(emittedValues, [1, 2, 3, 4]);
+ });
+
+ test('forwards errors from outer Stream', () async {
+ outer.addError('error');
+ await Future(() {});
+ expect(errors, ['error']);
+ });
+
+ test('forwards errors from inner Stream', () async {
+ outer.add(first.stream);
+ await Future(() {});
+ first.addError('error');
+ await Future(() {});
+ expect(errors, ['error']);
+ });
+
+ test('closes when final stream is done', () async {
+ outer.add(first.stream);
+ await Future(() {});
+
+ outer.add(second.stream);
+ await Future(() {});
+
+ await outer.close();
+ expect(isDone, false);
+
+ await second.close();
+ expect(isDone, true);
+ });
+
+ test(
+ 'closes when outer stream closes if latest inner stream already '
+ 'closed', () async {
+ outer.add(first.stream);
+ await Future(() {});
+ await first.close();
+ expect(isDone, false);
+
+ await outer.close();
+ expect(isDone, true);
+ });
+
+ test('cancels listeners on previous streams', () async {
+ outer.add(first.stream);
+ await Future(() {});
+
+ outer.add(second.stream);
+ await Future(() {});
+ expect(firstCanceled, true);
+ });
+
+ if (innerType != 'broadcast') {
+ test('waits for cancel before listening to subsequent stream',
+ () async {
+ var cancelWork = Completer<void>();
+ first.onCancel = () => cancelWork.future;
+ outer.add(first.stream);
+ await Future(() {});
+
+ var cancelDone = false;
+ second.onListen = expectAsync0(() {
+ expect(cancelDone, true);
+ });
+ outer.add(second.stream);
+ await Future(() {});
+ cancelWork.complete();
+ cancelDone = true;
+ });
+
+ test('all streams are listened to, even while cancelling', () async {
+ var cancelWork = Completer<void>();
+ first.onCancel = () => cancelWork.future;
+ outer.add(first.stream);
+ await Future(() {});
+
+ var cancelDone = false;
+ second.onListen = expectAsync0(() {
+ expect(cancelDone, true);
+ });
+ third.onListen = expectAsync0(() {
+ expect(cancelDone, true);
+ });
+ outer
+ ..add(second.stream)
+ ..add(third.stream);
+ await Future(() {});
+ cancelWork.complete();
+ cancelDone = true;
+ });
+ }
+
+ if (outerType != 'broadcast' && innerType != 'broadcast') {
+ test('pausing while cancelling an inner stream is respected',
+ () async {
+ var cancelWork = Completer<void>();
+ first.onCancel = () => cancelWork.future;
+ outer.add(first.stream);
+ await Future(() {});
+
+ var cancelDone = false;
+ second.onListen = expectAsync0(() {
+ expect(cancelDone, true);
+ });
+ outer.add(second.stream);
+ await Future(() {});
+ subscription.pause();
+ cancelWork.complete();
+ cancelDone = true;
+ await Future(() {});
+ expect(second.isPaused, true);
+ subscription.resume();
+ });
+ }
+
+ test('cancels listener on current and outer stream on cancel',
+ () async {
+ outer.add(first.stream);
+ await Future(() {});
+ await subscription.cancel();
+
+ await Future(() {});
+ expect(outerCanceled, true);
+ expect(firstCanceled, true);
+ });
+ });
+ }
+ }
+
+ group('switchMap', () {
+ test('uses map function', () async {
+ var outer = StreamController<List<int>>();
+
+ var values = <int>[];
+ outer.stream.switchMap(Stream.fromIterable).listen(values.add);
+
+ outer.add([1, 2, 3]);
+ await Future(() {});
+ outer.add([4, 5, 6]);
+ await Future(() {});
+ expect(values, [1, 2, 3, 4, 5, 6]);
+ });
+
+ test('can create a broadcast stream', () async {
+ var outer = StreamController<int>.broadcast();
+
+ var transformed =
+ outer.stream.switchMap((_) => const Stream<int>.empty());
+
+ expect(transformed.isBroadcast, true);
+ });
+
+ test('forwards errors from the convert callback', () async {
+ var errors = <String>[];
+ var source = Stream.fromIterable([1, 2, 3]);
+ source.switchMap<int>((i) {
+ // ignore: only_throw_errors
+ throw 'Error: $i';
+ }).listen((_) {}, onError: errors.add);
+ await Future<void>(() {});
+ expect(errors, ['Error: 1', 'Error: 2', 'Error: 3']);
+ });
+ });
+}
diff --git a/pkgs/stream_transform/test/take_until_test.dart b/pkgs/stream_transform/test/take_until_test.dart
new file mode 100644
index 0000000..982b3da
--- /dev/null
+++ b/pkgs/stream_transform/test/take_until_test.dart
@@ -0,0 +1,135 @@
+// Copyright (c) 2017, 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 'package:stream_transform/stream_transform.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ for (var streamType in streamTypes) {
+ group('takeUntil on Stream type [$streamType]', () {
+ late StreamController<int> values;
+ late List<int> emittedValues;
+ late bool valuesCanceled;
+ late bool isDone;
+ late List<String> errors;
+ late Stream<int> transformed;
+ late StreamSubscription<int> subscription;
+ late Completer<void> closeTrigger;
+
+ setUp(() {
+ valuesCanceled = false;
+ values = createController(streamType)
+ ..onCancel = () {
+ valuesCanceled = true;
+ };
+ emittedValues = [];
+ errors = [];
+ isDone = false;
+ closeTrigger = Completer();
+ transformed = values.stream.takeUntil(closeTrigger.future);
+ subscription = transformed
+ .listen(emittedValues.add, onError: errors.add, onDone: () {
+ isDone = true;
+ });
+ });
+
+ test('forwards cancellation', () async {
+ await subscription.cancel();
+ expect(valuesCanceled, true);
+ });
+
+ test('lets values through before trigger', () async {
+ values
+ ..add(1)
+ ..add(2);
+ await Future(() {});
+ expect(emittedValues, [1, 2]);
+ });
+
+ test('forwards errors', () async {
+ values.addError('error');
+ await Future(() {});
+ expect(errors, ['error']);
+ });
+
+ test('sends done if original strem ends', () async {
+ await values.close();
+ expect(isDone, true);
+ });
+
+ test('sends done when trigger fires', () async {
+ closeTrigger.complete();
+ await Future(() {});
+ expect(isDone, true);
+ });
+
+ test('forwards errors from the close trigger', () async {
+ closeTrigger.completeError('sad');
+ await Future(() {});
+ expect(errors, ['sad']);
+ expect(isDone, true);
+ });
+
+ test('ignores errors from the close trigger after stream closed',
+ () async {
+ await values.close();
+ closeTrigger.completeError('sad');
+ await Future(() {});
+ expect(errors, <Object>[]);
+ });
+
+ test('cancels value subscription when trigger fires', () async {
+ closeTrigger.complete();
+ await Future(() {});
+ expect(valuesCanceled, true);
+ });
+
+ if (streamType == 'broadcast') {
+ test('multiple listeners all get values', () async {
+ var otherValues = <Object>[];
+ transformed.listen(otherValues.add);
+ values
+ ..add(1)
+ ..add(2);
+ await Future(() {});
+ expect(emittedValues, [1, 2]);
+ expect(otherValues, [1, 2]);
+ });
+
+ test('multiple listeners get done when trigger fires', () async {
+ var otherDone = false;
+ transformed.listen(null, onDone: () => otherDone = true);
+ closeTrigger.complete();
+ await Future(() {});
+ expect(otherDone, true);
+ expect(isDone, true);
+ });
+
+ test('multiple listeners get done when values end', () async {
+ var otherDone = false;
+ transformed.listen(null, onDone: () => otherDone = true);
+ await values.close();
+ expect(otherDone, true);
+ expect(isDone, true);
+ });
+
+ test('can cancel and relisten before trigger fires', () async {
+ values.add(1);
+ await Future(() {});
+ await subscription.cancel();
+ values.add(2);
+ await Future(() {});
+ subscription = transformed.listen(emittedValues.add);
+ values.add(3);
+ await Future(() {});
+ expect(emittedValues, [1, 3]);
+ });
+ }
+ });
+ }
+}
diff --git a/pkgs/stream_transform/test/tap_test.dart b/pkgs/stream_transform/test/tap_test.dart
new file mode 100644
index 0000000..f2b4346
--- /dev/null
+++ b/pkgs/stream_transform/test/tap_test.dart
@@ -0,0 +1,116 @@
+// Copyright (c) 2017, 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 'package:stream_transform/stream_transform.dart';
+import 'package:test/test.dart';
+
+void main() {
+ test('calls function for values', () async {
+ var valuesSeen = <int>[];
+ var stream = Stream.fromIterable([1, 2, 3]);
+ await stream.tap(valuesSeen.add).last;
+ expect(valuesSeen, [1, 2, 3]);
+ });
+
+ test('forwards values', () async {
+ var stream = Stream.fromIterable([1, 2, 3]);
+ var values = await stream.tap((_) {}).toList();
+ expect(values, [1, 2, 3]);
+ });
+
+ test('calls function for errors', () async {
+ dynamic error;
+ var source = StreamController<int>();
+ source.stream.tap((_) {}, onError: (e, st) {
+ error = e;
+ }).listen((_) {}, onError: (_) {});
+ source.addError('error');
+ await Future(() {});
+ expect(error, 'error');
+ });
+
+ test('forwards errors', () async {
+ dynamic error;
+ var source = StreamController<int>();
+ source.stream.tap((_) {}, onError: (e, st) {}).listen((_) {},
+ onError: (Object e) {
+ error = e;
+ });
+ source.addError('error');
+ await Future(() {});
+ expect(error, 'error');
+ });
+
+ test('calls function on done', () async {
+ var doneCalled = false;
+ var source = StreamController<int>();
+ source.stream.tap((_) {}, onDone: () {
+ doneCalled = true;
+ }).listen((_) {});
+ await source.close();
+ expect(doneCalled, true);
+ });
+
+ test('forwards only once with multiple listeners on a broadcast stream',
+ () async {
+ var dataCallCount = 0;
+ var source = StreamController<int>.broadcast();
+ source.stream.tap((_) {
+ dataCallCount++;
+ })
+ ..listen((_) {})
+ ..listen((_) {});
+ source.add(1);
+ await Future(() {});
+ expect(dataCallCount, 1);
+ });
+
+ test(
+ 'forwards errors only once with multiple listeners on a broadcast stream',
+ () async {
+ var errorCallCount = 0;
+ var source = StreamController<int>.broadcast();
+ source.stream.tap((_) {}, onError: (_, __) {
+ errorCallCount++;
+ })
+ ..listen((_) {}, onError: (_, __) {})
+ ..listen((_) {}, onError: (_, __) {});
+ source.addError('error');
+ await Future(() {});
+ expect(errorCallCount, 1);
+ });
+
+ test('calls onDone only once with multiple listeners on a broadcast stream',
+ () async {
+ var doneCallCount = 0;
+ var source = StreamController<int>.broadcast();
+ source.stream.tap((_) {}, onDone: () {
+ doneCallCount++;
+ })
+ ..listen((_) {})
+ ..listen((_) {});
+ await source.close();
+ expect(doneCallCount, 1);
+ });
+
+ test('forwards values to multiple listeners', () async {
+ var source = StreamController<int>.broadcast();
+ var emittedValues1 = <int>[];
+ var emittedValues2 = <int>[];
+ source.stream.tap((_) {})
+ ..listen(emittedValues1.add)
+ ..listen(emittedValues2.add);
+ source.add(1);
+ await Future(() {});
+ expect(emittedValues1, [1]);
+ expect(emittedValues2, [1]);
+ });
+
+ test('allows null callback', () async {
+ var stream = Stream.fromIterable([1, 2, 3]);
+ await stream.tap(null).last;
+ });
+}
diff --git a/pkgs/stream_transform/test/throttle_test.dart b/pkgs/stream_transform/test/throttle_test.dart
new file mode 100644
index 0000000..07f607a
--- /dev/null
+++ b/pkgs/stream_transform/test/throttle_test.dart
@@ -0,0 +1,193 @@
+// Copyright (c) 2017, 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 'package:fake_async/fake_async.dart';
+import 'package:stream_transform/stream_transform.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ for (var streamType in streamTypes) {
+ group('Stream type [$streamType]', () {
+ late StreamController<int> values;
+ late List<int> emittedValues;
+ late bool valuesCanceled;
+ late bool isDone;
+ late Stream<int> transformed;
+ late StreamSubscription<int> subscription;
+
+ group('throttle - trailing: false', () {
+ setUp(() async {
+ valuesCanceled = false;
+ values = createController(streamType)
+ ..onCancel = () {
+ valuesCanceled = true;
+ };
+ emittedValues = [];
+ isDone = false;
+ transformed = values.stream.throttle(const Duration(milliseconds: 5));
+ });
+
+ void listen() {
+ subscription = transformed.listen(emittedValues.add, onDone: () {
+ isDone = true;
+ });
+ }
+
+ test('cancels values', () async {
+ listen();
+ await subscription.cancel();
+ expect(valuesCanceled, true);
+ });
+
+ test('swallows values that come faster than duration', () {
+ fakeAsync((async) {
+ listen();
+ values
+ ..add(1)
+ ..add(2)
+ ..close();
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [1]);
+ });
+ });
+
+ test('outputs multiple values spaced further than duration', () {
+ fakeAsync((async) {
+ listen();
+ values.add(1);
+ async.elapse(const Duration(milliseconds: 6));
+ values.add(2);
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [1, 2]);
+ async.elapse(const Duration(milliseconds: 6));
+ });
+ });
+
+ test('closes output immediately', () {
+ fakeAsync((async) {
+ listen();
+ values.add(1);
+ async.elapse(const Duration(milliseconds: 6));
+ values
+ ..add(2)
+ ..close();
+ async.flushMicrotasks();
+ expect(isDone, true);
+ });
+ });
+
+ if (streamType == 'broadcast') {
+ test('multiple listeners all get values', () {
+ fakeAsync((async) {
+ listen();
+ var otherValues = <int>[];
+ transformed.listen(otherValues.add);
+ values.add(1);
+ async.flushMicrotasks();
+ expect(emittedValues, [1]);
+ expect(otherValues, [1]);
+ });
+ });
+ }
+ });
+
+ group('throttle - trailing: true', () {
+ setUp(() async {
+ valuesCanceled = false;
+ values = createController(streamType)
+ ..onCancel = () {
+ valuesCanceled = true;
+ };
+ emittedValues = [];
+ isDone = false;
+ transformed = values.stream
+ .throttle(const Duration(milliseconds: 5), trailing: true);
+ });
+ void listen() {
+ subscription = transformed.listen(emittedValues.add, onDone: () {
+ isDone = true;
+ });
+ }
+
+ test('emits both first and last in a period', () {
+ fakeAsync((async) {
+ listen();
+ values
+ ..add(1)
+ ..add(2)
+ ..close();
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [1, 2]);
+ });
+ });
+
+ test('swallows values that are not the latest in a period', () {
+ fakeAsync((async) {
+ listen();
+ values
+ ..add(1)
+ ..add(2)
+ ..add(3)
+ ..close();
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [1, 3]);
+ });
+ });
+
+ test('waits to output the last value even if the stream closes',
+ () async {
+ fakeAsync((async) {
+ listen();
+ values
+ ..add(1)
+ ..add(2)
+ ..close();
+ async.flushMicrotasks();
+ expect(isDone, false);
+ expect(emittedValues, [1],
+ reason: 'Should not be emitted until after duration');
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [1, 2]);
+ expect(isDone, true);
+ async.elapse(const Duration(milliseconds: 6));
+ });
+ });
+
+ test('closes immediately if there is no pending value', () {
+ fakeAsync((async) {
+ listen();
+ values
+ ..add(1)
+ ..close();
+ async.flushMicrotasks();
+ expect(isDone, true);
+ });
+ });
+
+ if (streamType == 'broadcast') {
+ test('multiple listeners all get values', () {
+ fakeAsync((async) {
+ listen();
+ var otherValues = <int>[];
+ transformed.listen(otherValues.add);
+ values
+ ..add(1)
+ ..add(2);
+ async.flushMicrotasks();
+ expect(emittedValues, [1]);
+ expect(otherValues, [1]);
+ async.elapse(const Duration(milliseconds: 6));
+ expect(emittedValues, [1, 2]);
+ expect(otherValues, [1, 2]);
+ });
+ });
+ }
+ });
+ });
+ }
+}
diff --git a/pkgs/stream_transform/test/utils.dart b/pkgs/stream_transform/test/utils.dart
new file mode 100644
index 0000000..42d9613
--- /dev/null
+++ b/pkgs/stream_transform/test/utils.dart
@@ -0,0 +1,19 @@
+// Copyright (c) 2017, 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';
+
+StreamController<T> createController<T>(String streamType) {
+ switch (streamType) {
+ case 'single subscription':
+ return StreamController<T>();
+ case 'broadcast':
+ return StreamController<T>.broadcast();
+ default:
+ throw ArgumentError.value(
+ streamType, 'streamType', 'Must be one of $streamTypes');
+ }
+}
+
+const streamTypes = ['single subscription', 'broadcast'];
diff --git a/pkgs/stream_transform/test/where_not_null_test.dart b/pkgs/stream_transform/test/where_not_null_test.dart
new file mode 100644
index 0000000..c9af794
--- /dev/null
+++ b/pkgs/stream_transform/test/where_not_null_test.dart
@@ -0,0 +1,56 @@
+// 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 'dart:async';
+
+import 'package:stream_transform/stream_transform.dart';
+import 'package:test/test.dart';
+
+void main() {
+ test('forwards only events that match the type', () async {
+ var values = Stream.fromIterable([null, 'a', null, 'b']);
+ var filtered = values.whereNotNull();
+ expect(await filtered.toList(), ['a', 'b']);
+ });
+
+ test('can result in empty stream', () async {
+ var values = Stream<Object?>.fromIterable([null, null]);
+ var filtered = values.whereNotNull();
+ expect(await filtered.isEmpty, true);
+ });
+
+ test('forwards values to multiple listeners', () async {
+ var values = StreamController<Object?>.broadcast();
+ var filtered = values.stream.whereNotNull();
+ var firstValues = <Object>[];
+ var secondValues = <Object>[];
+ filtered
+ ..listen(firstValues.add)
+ ..listen(secondValues.add);
+ values
+ ..add(null)
+ ..add('a')
+ ..add(null)
+ ..add('b');
+ await Future(() {});
+ expect(firstValues, ['a', 'b']);
+ expect(secondValues, ['a', 'b']);
+ });
+
+ test('closes streams with multiple listeners', () async {
+ var values = StreamController<Object?>.broadcast();
+ var filtered = values.stream.whereNotNull();
+ var firstDone = false;
+ var secondDone = false;
+ filtered
+ ..listen(null, onDone: () => firstDone = true)
+ ..listen(null, onDone: () => secondDone = true);
+ values
+ ..add(null)
+ ..add('a');
+ await values.close();
+ expect(firstDone, true);
+ expect(secondDone, true);
+ });
+}
diff --git a/pkgs/stream_transform/test/where_type_test.dart b/pkgs/stream_transform/test/where_type_test.dart
new file mode 100644
index 0000000..4cbea37
--- /dev/null
+++ b/pkgs/stream_transform/test/where_type_test.dart
@@ -0,0 +1,56 @@
+// 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.
+
+import 'dart:async';
+
+import 'package:stream_transform/stream_transform.dart';
+import 'package:test/test.dart';
+
+void main() {
+ test('forwards only events that match the type', () async {
+ var values = Stream.fromIterable([1, 'a', 2, 'b']);
+ var filtered = values.whereType<String>();
+ expect(await filtered.toList(), ['a', 'b']);
+ });
+
+ test('can result in empty stream', () async {
+ var values = Stream.fromIterable([1, 2, 3, 4]);
+ var filtered = values.whereType<String>();
+ expect(await filtered.isEmpty, true);
+ });
+
+ test('forwards values to multiple listeners', () async {
+ var values = StreamController<Object>.broadcast();
+ var filtered = values.stream.whereType<String>();
+ var firstValues = <Object>[];
+ var secondValues = <Object>[];
+ filtered
+ ..listen(firstValues.add)
+ ..listen(secondValues.add);
+ values
+ ..add(1)
+ ..add('a')
+ ..add(2)
+ ..add('b');
+ await Future(() {});
+ expect(firstValues, ['a', 'b']);
+ expect(secondValues, ['a', 'b']);
+ });
+
+ test('closes streams with multiple listeners', () async {
+ var values = StreamController<Object>.broadcast();
+ var filtered = values.stream.whereType<String>();
+ var firstDone = false;
+ var secondDone = false;
+ filtered
+ ..listen(null, onDone: () => firstDone = true)
+ ..listen(null, onDone: () => secondDone = true);
+ values
+ ..add(1)
+ ..add('a');
+ await values.close();
+ expect(firstDone, true);
+ expect(secondDone, true);
+ });
+}
diff --git a/pkgs/string_scanner/.gitignore b/pkgs/string_scanner/.gitignore
new file mode 100644
index 0000000..fb97bde
--- /dev/null
+++ b/pkgs/string_scanner/.gitignore
@@ -0,0 +1,5 @@
+# Don’t commit the following directories created by pub.
+.dart_tool/
+.pub/
+.packages
+pubspec.lock
diff --git a/pkgs/string_scanner/CHANGELOG.md b/pkgs/string_scanner/CHANGELOG.md
new file mode 100644
index 0000000..082e9f2
--- /dev/null
+++ b/pkgs/string_scanner/CHANGELOG.md
@@ -0,0 +1,175 @@
+## 1.4.1
+
+* Move to `dart-lang/tools` monorepo.
+
+## 1.4.0
+
+* Fix `LineScanner`'s handling of `\r\n`'s to preventing errors scanning
+ zero-length matches when between CR and LF. CR is treated as a new line only
+ if not immediately followed by a LF.
+* Fix `LineScanner`'s updating of `column` when setting `position` if the
+ current position is not `0`.
+
+## 1.3.0
+
+* Require Dart 3.1.0
+
+* Add a `SpanScanner.spanFromPosition()` method which takes raw code units
+ rather than `SpanScanner.spanFrom()`'s `LineScannerState`s.
+
+## 1.2.0
+
+* Require Dart 2.18.0
+
+* Add better support for reading code points in the Unicode supplementary plane:
+
+ * Added `StringScanner.readCodePoint()`, which consumes an entire Unicode code
+ point even if it's represented by two UTF-16 code units.
+
+ * Added `StringScanner.peekCodePoint()`, which returns an entire Unicode code
+ point even if it's represented by two UTF-16 code units.
+
+ * `StringScanner.scanChar()` and `StringScanner.expectChar()` will now
+ properly consume two UTF-16 code units if they're passed Unicode code points
+ in the supplementary plane.
+
+## 1.1.1
+
+* Populate the pubspec `repository` field.
+* Switch to `package:lints`.
+* Remove a dependency on `package:charcode`.
+
+## 1.1.0
+
+* Stable release for null safety.
+
+## 1.1.0-nullsafety.3
+
+* Update SDK constraints to `>=2.12.0-0 <3.0.0` based on beta release
+ guidelines.
+
+## 1.1.0-nullsafety.2
+
+* Allow prerelease versions of the 2.12 sdk.
+
+## 1.1.0-nullsafety.1
+
+- Allow 2.10 stable and 2.11.0 dev SDK versions.
+
+## 1.1.0-nullsafety
+
+- Migrate to null safety.
+
+## 1.0.5
+
+- Added an example.
+
+- Update Dart SDK constraint to `>=2.0.0 <3.0.0`.
+
+## 1.0.4
+
+* Add @alwaysThrows annotation to error method.
+
+## 1.0.3
+
+* Set max SDK version to `<3.0.0`, and adjust other dependencies.
+
+## 1.0.2
+
+* `SpanScanner` no longer crashes when creating a span that contains a UTF-16
+ surrogate pair.
+
+## 1.0.1
+
+* Fix the error text emitted by `StringScanner.expectChar()`.
+
+## 1.0.0
+
+* **Breaking change**: `StringScanner.error()`'s `length` argument now defaults
+ to `0` rather than `1` when no match data is available.
+
+* **Breaking change**: `StringScanner.lastMatch` and related methods are now
+ reset when the scanner's position changes without producing a new match.
+
+**Note**: While the changes in `1.0.0` are user-visible, they're unlikely to
+actually break any code in practice. Unless you know that your package is
+incompatible with 0.1.x, consider using 0.1.5 as your lower bound rather
+than 1.0.0. For example, `string_scanner: ">=0.1.5 <2.0.0"`.
+
+## 0.1.5
+
+* Add `new SpanScanner.within()`, which scans within a existing `FileSpan`.
+
+* Add `StringScanner.scanChar()` and `StringScanner.expectChar()`.
+
+## 0.1.4+1
+
+* Remove the dependency on `path`, since we don't actually import it.
+
+## 0.1.4
+
+* Add `new SpanScanner.eager()` for creating a `SpanScanner` that eagerly
+ computes its current line and column numbers.
+
+## 0.1.3+2
+
+* Fix `LineScanner`'s handling of carriage returns to match that of
+ `SpanScanner`.
+
+## 0.1.3+1
+
+* Fixed the homepage URL.
+
+## 0.1.3
+
+* Add an optional `endState` argument to `SpanScanner.spanFrom`.
+
+## 0.1.2
+
+* Add `StringScanner.substring`, which returns a substring of the source string.
+
+## 0.1.1
+
+* Declare `SpanScanner`'s exposed `SourceSpan`s and `SourceLocation`s to be
+ `FileSpan`s and `FileLocation`s. They always were underneath, but callers may
+ now rely on it.
+
+* Add `SpanScanner.location`, which returns the scanner's current
+ `SourceLocation`.
+
+## 0.1.0
+
+* Switch from `source_maps`' `Span` class to `source_span`'s `SourceSpan` class.
+
+* `new StringScanner()`'s `sourceUrl` parameter is now named to make it clear
+ that it can be safely `null`.
+
+* `new StringScannerException()` takes different arguments in a different order
+ to match `SpanFormatException`.
+
+* `StringScannerException.string` has been renamed to
+ `StringScannerException.source` to match the `FormatException` interface.
+
+## 0.0.3
+
+* Make `StringScannerException` inherit from source_map's `SpanFormatException`.
+
+## 0.0.2
+
+* `new StringScanner()` now takes an optional `sourceUrl` argument that provides
+ the URL of the source file. This is used for error reporting.
+
+* Add `StringScanner.readChar()` and `StringScanner.peekChar()` methods for
+ doing character-by-character scanning.
+
+* Scanners now throw `StringScannerException`s which provide more detailed
+ access to information about the errors that were thrown and can provide
+ terminal-colored messages.
+
+* Add a `LineScanner` subclass of `StringScanner` that automatically tracks line
+ and column information of the text being scanned.
+
+* Add a `SpanScanner` subclass of `LineScanner` that exposes matched ranges as
+ [source map][] `Span` objects.
+
+[source_map]: https://pub.dev/packages/source_maps
diff --git a/pkgs/string_scanner/LICENSE b/pkgs/string_scanner/LICENSE
new file mode 100644
index 0000000..000cd7b
--- /dev/null
+++ b/pkgs/string_scanner/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2014, 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.
diff --git a/pkgs/string_scanner/README.md b/pkgs/string_scanner/README.md
new file mode 100644
index 0000000..e06e325
--- /dev/null
+++ b/pkgs/string_scanner/README.md
@@ -0,0 +1,41 @@
+[](https://github.com/dart-lang/string_scanner/actions/workflows/test-package.yml)
+[](https://pub.dev/packages/string_scanner)
+[](https://pub.dev/packages/string_scanner/publisher)
+
+This package exposes a `StringScanner` type that makes it easy to parse a string
+using a series of `Pattern`s. For example:
+
+```dart
+import 'dart:math' as math;
+
+import 'package:string_scanner/string_scanner.dart';
+
+num parseNumber(String source) {
+ // Scan a number ("1", "1.5", "-3").
+ final scanner = StringScanner(source);
+
+ // [Scanner.scan] tries to consume a [Pattern] and returns whether or not it
+ // succeeded. It will move the scan pointer past the end of the pattern.
+ final negative = scanner.scan('-');
+
+ // [Scanner.expect] consumes a [Pattern] and throws a [FormatError] if it
+ // fails. Like [Scanner.scan], it will move the scan pointer forward.
+ scanner.expect(RegExp(r'\d+'));
+
+ // [Scanner.lastMatch] holds the [MatchData] for the most recent call to
+ // [Scanner.scan], [Scanner.expect], or [Scanner.matches].
+ var number = num.parse(scanner.lastMatch![0]!);
+
+ if (scanner.scan('.')) {
+ scanner.expect(RegExp(r'\d+'));
+ final decimal = scanner.lastMatch![0]!;
+ number += int.parse(decimal) / math.pow(10, decimal.length);
+ }
+
+ // [Scanner.expectDone] will throw a [FormatError] if there's any input that
+ // hasn't yet been consumed.
+ scanner.expectDone();
+
+ return (negative ? -1 : 1) * number;
+}
+```
diff --git a/pkgs/string_scanner/analysis_options.yaml b/pkgs/string_scanner/analysis_options.yaml
new file mode 100644
index 0000000..59f763a
--- /dev/null
+++ b/pkgs/string_scanner/analysis_options.yaml
@@ -0,0 +1,32 @@
+# https://dart.dev/guides/language/analysis-options
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-casts: true
+ strict-inference: true
+ strict-raw-types: true
+
+linter:
+ rules:
+ - avoid_bool_literals_in_conditional_expressions
+ - avoid_classes_with_only_static_members
+ - avoid_private_typedef_functions
+ - avoid_redundant_argument_values
+ - avoid_returning_this
+ - avoid_unused_constructor_parameters
+ - avoid_void_async
+ - cancel_subscriptions
+ - join_return_with_assignment
+ - literal_only_boolean_expressions
+ - missing_whitespace_between_adjacent_strings
+ - no_adjacent_strings_in_list
+ - no_runtimeType_toString
+ - prefer_const_declarations
+ - prefer_expression_function_bodies
+ - prefer_final_locals
+ - unnecessary_await_in_return
+ - unnecessary_raw_strings
+ - use_if_null_to_convert_nulls_to_bools
+ - use_raw_strings
+ - use_string_buffers
diff --git a/pkgs/string_scanner/example/example.dart b/pkgs/string_scanner/example/example.dart
new file mode 100644
index 0000000..ec9dd76
--- /dev/null
+++ b/pkgs/string_scanner/example/example.dart
@@ -0,0 +1,40 @@
+// 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:math' as math;
+
+import 'package:string_scanner/string_scanner.dart';
+
+void main(List<String> args) {
+ print(parseNumber(args.single));
+}
+
+num parseNumber(String source) {
+ // Scan a number ("1", "1.5", "-3").
+ final scanner = StringScanner(source);
+
+ // [Scanner.scan] tries to consume a [Pattern] and returns whether or not it
+ // succeeded. It will move the scan pointer past the end of the pattern.
+ final negative = scanner.scan('-');
+
+ // [Scanner.expect] consumes a [Pattern] and throws a [FormatError] if it
+ // fails. Like [Scanner.scan], it will move the scan pointer forward.
+ scanner.expect(RegExp(r'\d+'));
+
+ // [Scanner.lastMatch] holds the [MatchData] for the most recent call to
+ // [Scanner.scan], [Scanner.expect], or [Scanner.matches].
+ var number = num.parse(scanner.lastMatch![0]!);
+
+ if (scanner.scan('.')) {
+ scanner.expect(RegExp(r'\d+'));
+ final decimal = scanner.lastMatch![0]!;
+ number += int.parse(decimal) / math.pow(10, decimal.length);
+ }
+
+ // [Scanner.expectDone] will throw a [FormatError] if there's any input that
+ // hasn't yet been consumed.
+ scanner.expectDone();
+
+ return (negative ? -1 : 1) * number;
+}
diff --git a/pkgs/string_scanner/lib/src/charcode.dart b/pkgs/string_scanner/lib/src/charcode.dart
new file mode 100644
index 0000000..d157749
--- /dev/null
+++ b/pkgs/string_scanner/lib/src/charcode.dart
@@ -0,0 +1,24 @@
+// Copyright (c) 2020, 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.
+
+/// Character '\'.
+const int $backslash = 0x5C;
+
+/// "Carriage return" control character.
+const int $cr = 0x0D;
+
+/// Character '"'.
+const int $doubleQuote = 0x22;
+
+/// Character 'f'.
+const int $f = 0x66;
+
+/// "Line feed" control character.
+const int $lf = 0x0A;
+
+/// Space character.
+const int $space = 0x20;
+
+/// Character 'x'.
+const int $x = 0x78;
diff --git a/pkgs/string_scanner/lib/src/eager_span_scanner.dart b/pkgs/string_scanner/lib/src/eager_span_scanner.dart
new file mode 100644
index 0000000..1ccc746
--- /dev/null
+++ b/pkgs/string_scanner/lib/src/eager_span_scanner.dart
@@ -0,0 +1,133 @@
+// Copyright (c) 2015, 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 'charcode.dart';
+import 'line_scanner.dart';
+import 'span_scanner.dart';
+import 'utils.dart';
+
+// TODO(nweiz): Currently this duplicates code in line_scanner.dart. Once
+// sdk#23770 is fully complete, we should move the shared code into a mixin.
+
+/// A regular expression matching newlines across platforms.
+final _newlineRegExp = RegExp(r'\r\n?|\n');
+
+/// A [SpanScanner] that tracks the line and column eagerly, like [LineScanner].
+class EagerSpanScanner extends SpanScanner {
+ @override
+ int get line => _line;
+ int _line = 0;
+
+ @override
+ int get column => _column;
+ int _column = 0;
+
+ @override
+ LineScannerState get state =>
+ _EagerSpanScannerState(this, position, line, column);
+
+ bool get _betweenCRLF => peekChar(-1) == $cr && peekChar() == $lf;
+
+ @override
+ set state(LineScannerState state) {
+ if (state is! _EagerSpanScannerState || !identical(state._scanner, this)) {
+ throw ArgumentError('The given LineScannerState was not returned by '
+ 'this LineScanner.');
+ }
+
+ super.position = state.position;
+ _line = state.line;
+ _column = state.column;
+ }
+
+ @override
+ set position(int newPosition) {
+ final oldPosition = position;
+ super.position = newPosition;
+
+ if (newPosition > oldPosition) {
+ final newlines = _newlinesIn(string.substring(oldPosition, newPosition));
+ _line += newlines.length;
+ if (newlines.isEmpty) {
+ _column += newPosition - oldPosition;
+ } else {
+ _column = newPosition - newlines.last.end;
+ }
+ } else {
+ final newlines = _newlinesIn(string.substring(newPosition, oldPosition));
+ if (_betweenCRLF) newlines.removeLast();
+
+ _line -= newlines.length;
+ if (newlines.isEmpty) {
+ _column -= oldPosition - newPosition;
+ } else {
+ _column =
+ newPosition - string.lastIndexOf(_newlineRegExp, newPosition) - 1;
+ }
+ }
+ }
+
+ EagerSpanScanner(super.string, {super.sourceUrl, super.position});
+
+ @override
+ bool scanChar(int character) {
+ if (!super.scanChar(character)) return false;
+ _adjustLineAndColumn(character);
+ return true;
+ }
+
+ @override
+ int readChar() {
+ final character = super.readChar();
+ _adjustLineAndColumn(character);
+ return character;
+ }
+
+ /// Adjusts [_line] and [_column] after having consumed [character].
+ void _adjustLineAndColumn(int character) {
+ if (character == $lf || (character == $cr && peekChar() != $lf)) {
+ _line += 1;
+ _column = 0;
+ } else {
+ _column += inSupplementaryPlane(character) ? 2 : 1;
+ }
+ }
+
+ @override
+ bool scan(Pattern pattern) {
+ if (!super.scan(pattern)) return false;
+ final firstMatch = lastMatch![0]!;
+
+ final newlines = _newlinesIn(firstMatch);
+ _line += newlines.length;
+ if (newlines.isEmpty) {
+ _column += firstMatch.length;
+ } else {
+ _column = firstMatch.length - newlines.last.end;
+ }
+
+ return true;
+ }
+
+ /// Returns a list of [Match]es describing all the newlines in [text], which
+ /// is assumed to end at [position].
+ List<Match> _newlinesIn(String text) {
+ final newlines = _newlineRegExp.allMatches(text).toList();
+ if (_betweenCRLF) newlines.removeLast();
+ return newlines;
+ }
+}
+
+/// A class representing the state of an [EagerSpanScanner].
+class _EagerSpanScannerState implements LineScannerState {
+ final EagerSpanScanner _scanner;
+ @override
+ final int position;
+ @override
+ final int line;
+ @override
+ final int column;
+
+ _EagerSpanScannerState(this._scanner, this.position, this.line, this.column);
+}
diff --git a/pkgs/string_scanner/lib/src/exception.dart b/pkgs/string_scanner/lib/src/exception.dart
new file mode 100644
index 0000000..57af541
--- /dev/null
+++ b/pkgs/string_scanner/lib/src/exception.dart
@@ -0,0 +1,21 @@
+// Copyright (c) 2014, 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_span/source_span.dart';
+
+import 'string_scanner.dart';
+
+/// An exception thrown by a [StringScanner] that failed to parse a string.
+class StringScannerException extends SourceSpanFormatException {
+ @override
+ String get source => super.source as String;
+
+ /// The URL of the source file being parsed.
+ ///
+ /// This may be `null`, indicating that the source URL is unknown.
+ Uri? get sourceUrl => span?.sourceUrl;
+
+ StringScannerException(
+ super.message, SourceSpan super.span, String super.source);
+}
diff --git a/pkgs/string_scanner/lib/src/line_scanner.dart b/pkgs/string_scanner/lib/src/line_scanner.dart
new file mode 100644
index 0000000..b18d610
--- /dev/null
+++ b/pkgs/string_scanner/lib/src/line_scanner.dart
@@ -0,0 +1,183 @@
+// Copyright (c) 2014, 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 'charcode.dart';
+import 'string_scanner.dart';
+import 'utils.dart';
+
+// Note that much of this code is duplicated in eager_span_scanner.dart.
+
+/// A regular expression matching newlines. A newline is either a `\n`, a `\r\n`
+/// or a `\r` that is not immediately followed by a `\n`.
+final _newlineRegExp = RegExp(r'\n|\r\n|\r(?!\n)');
+
+/// A subclass of [StringScanner] that tracks line and column information.
+class LineScanner extends StringScanner {
+ /// The scanner's current (zero-based) line number.
+ int get line => _line;
+ int _line = 0;
+
+ /// The scanner's current (zero-based) column number.
+ int get column => _column;
+ int _column = 0;
+
+ /// The scanner's state, including line and column information.
+ ///
+ /// This can be used to efficiently save and restore the state of the scanner
+ /// when backtracking. A given [LineScannerState] is only valid for the
+ /// [LineScanner] that created it.
+ ///
+ /// This does not include the scanner's match information.
+ LineScannerState get state =>
+ LineScannerState._(this, position, line, column);
+
+ /// Whether the current position is between a CR character and an LF
+ /// charactet.
+ bool get _betweenCRLF => peekChar(-1) == $cr && peekChar() == $lf;
+
+ set state(LineScannerState state) {
+ if (!identical(state._scanner, this)) {
+ throw ArgumentError('The given LineScannerState was not returned by '
+ 'this LineScanner.');
+ }
+
+ super.position = state.position;
+ _line = state.line;
+ _column = state.column;
+ }
+
+ @override
+ set position(int newPosition) {
+ if (newPosition == position) {
+ return;
+ }
+
+ final oldPosition = position;
+ super.position = newPosition;
+
+ if (newPosition == 0) {
+ _line = 0;
+ _column = 0;
+ } else if (newPosition > oldPosition) {
+ final newlines = _newlinesIn(string.substring(oldPosition, newPosition),
+ endPosition: newPosition);
+ _line += newlines.length;
+ if (newlines.isEmpty) {
+ _column += newPosition - oldPosition;
+ } else {
+ // The regex got a substring, so we need to account for where it started
+ // in the string.
+ final offsetOfLastNewline = oldPosition + newlines.last.end;
+ _column = newPosition - offsetOfLastNewline;
+ }
+ } else if (newPosition < oldPosition) {
+ final newlines = _newlinesIn(string.substring(newPosition, oldPosition),
+ endPosition: oldPosition);
+
+ _line -= newlines.length;
+ if (newlines.isEmpty) {
+ _column -= oldPosition - newPosition;
+ } else {
+ // To compute the new column, we need to locate the last newline before
+ // the new position. When searching, we must exclude the CR if we're
+ // between a CRLF because it's not considered a newline.
+ final crOffset = _betweenCRLF ? -1 : 0;
+ // Additionally, if we use newPosition as the end of the search and the
+ // character at that position itself (the next character) is a newline
+ // we should not use it, so also offset to account for that.
+ const currentCharOffset = -1;
+ final lastNewline = string.lastIndexOf(
+ _newlineRegExp, newPosition + currentCharOffset + crOffset);
+
+ // Now we need to know the offset after the newline. This is the index
+ // above plus the length of the newline (eg. if we found `\r\n`) we need
+ // to add two. However if no newline was found, that index is 0.
+ final offsetAfterLastNewline = lastNewline == -1
+ ? 0
+ : string[lastNewline] == '\r' && string[lastNewline + 1] == '\n'
+ ? lastNewline + 2
+ : lastNewline + 1;
+
+ _column = newPosition - offsetAfterLastNewline;
+ }
+ }
+ }
+
+ LineScanner(super.string, {super.sourceUrl, super.position});
+
+ @override
+ bool scanChar(int character) {
+ if (!super.scanChar(character)) return false;
+ _adjustLineAndColumn(character);
+ return true;
+ }
+
+ @override
+ int readChar() {
+ final character = super.readChar();
+ _adjustLineAndColumn(character);
+ return character;
+ }
+
+ /// Adjusts [_line] and [_column] after having consumed [character].
+ void _adjustLineAndColumn(int character) {
+ if (character == $lf || (character == $cr && peekChar() != $lf)) {
+ _line += 1;
+ _column = 0;
+ } else {
+ _column += inSupplementaryPlane(character) ? 2 : 1;
+ }
+ }
+
+ @override
+ bool scan(Pattern pattern) {
+ if (!super.scan(pattern)) return false;
+
+ final newlines = _newlinesIn(lastMatch![0]!, endPosition: position);
+ _line += newlines.length;
+ if (newlines.isEmpty) {
+ _column += lastMatch![0]!.length;
+ } else {
+ _column = lastMatch![0]!.length - newlines.last.end;
+ }
+
+ return true;
+ }
+
+ /// Returns a list of [Match]es describing all the newlines in [text], which
+ /// ends at [endPosition].
+ ///
+ /// If [text] ends with `\r`, it will only be treated as a newline if the next
+ /// character at [position] is not a `\n`.
+ List<Match> _newlinesIn(String text, {required int endPosition}) {
+ final newlines = _newlineRegExp.allMatches(text).toList();
+ // If the last character is a `\r` it will have been treated as a newline,
+ // but this is only valid if the next character is not a `\n`.
+ if (endPosition < string.length &&
+ text.endsWith('\r') &&
+ string[endPosition] == '\n') {
+ // newlines should never be empty here, because if `text` ends with `\r`
+ // it would have matched `\r(?!\n)` in the newline regex.
+ newlines.removeLast();
+ }
+ return newlines;
+ }
+}
+
+/// A class representing the state of a [LineScanner].
+class LineScannerState {
+ /// The [LineScanner] that created this.
+ final LineScanner _scanner;
+
+ /// The position of the scanner in this state.
+ final int position;
+
+ /// The zero-based line number of the scanner in this state.
+ final int line;
+
+ /// The zero-based column number of the scanner in this state.
+ final int column;
+
+ LineScannerState._(this._scanner, this.position, this.line, this.column);
+}
diff --git a/pkgs/string_scanner/lib/src/relative_span_scanner.dart b/pkgs/string_scanner/lib/src/relative_span_scanner.dart
new file mode 100644
index 0000000..cd9af0e
--- /dev/null
+++ b/pkgs/string_scanner/lib/src/relative_span_scanner.dart
@@ -0,0 +1,132 @@
+// 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.
+
+import 'package:source_span/source_span.dart';
+
+import 'exception.dart';
+import 'line_scanner.dart';
+import 'span_scanner.dart';
+import 'string_scanner.dart';
+import 'utils.dart';
+
+/// A [SpanScanner] that scans within an existing [FileSpan].
+///
+/// This re-implements chunks of [SpanScanner] rather than using a dummy span or
+/// inheritance because scanning is often a performance-critical operation, so
+/// it's important to avoid adding extra overhead when relative scanning isn't
+/// needed.
+class RelativeSpanScanner extends StringScanner implements SpanScanner {
+ /// The source of the scanner.
+ ///
+ /// This caches line break information and is used to generate [SourceSpan]s.
+ final SourceFile _sourceFile;
+
+ /// The start location of the span within which this scanner is scanning.
+ ///
+ /// This is used to convert between span-relative and file-relative fields.
+ final FileLocation _startLocation;
+
+ @override
+ int get line =>
+ _sourceFile.getLine(_startLocation.offset + position) -
+ _startLocation.line;
+
+ @override
+ int get column {
+ final line = _sourceFile.getLine(_startLocation.offset + position);
+ final column =
+ _sourceFile.getColumn(_startLocation.offset + position, line: line);
+ return line == _startLocation.line
+ ? column - _startLocation.column
+ : column;
+ }
+
+ @override
+ LineScannerState get state => _SpanScannerState(this, position);
+
+ @override
+ set state(LineScannerState state) {
+ if (state is! _SpanScannerState || !identical(state._scanner, this)) {
+ throw ArgumentError('The given LineScannerState was not returned by '
+ 'this LineScanner.');
+ }
+
+ position = state.position;
+ }
+
+ @override
+ FileSpan? get lastSpan => _lastSpan;
+ FileSpan? _lastSpan;
+
+ @override
+ FileLocation get location =>
+ _sourceFile.location(_startLocation.offset + position);
+
+ @override
+ FileSpan get emptySpan => location.pointSpan();
+
+ RelativeSpanScanner(FileSpan span)
+ : _sourceFile = span.file,
+ _startLocation = span.start,
+ super(span.text, sourceUrl: span.sourceUrl);
+
+ @override
+ FileSpan spanFrom(LineScannerState startState, [LineScannerState? endState]) {
+ final endPosition = endState == null ? position : endState.position;
+ return _sourceFile.span(_startLocation.offset + startState.position,
+ _startLocation.offset + endPosition);
+ }
+
+ @override
+ FileSpan spanFromPosition(int startPosition, [int? endPosition]) {
+ RangeError.checkValidRange(
+ startPosition,
+ endPosition,
+ _sourceFile.length - _startLocation.offset,
+ 'startPosition',
+ 'endPosition');
+ return _sourceFile.span(_startLocation.offset + startPosition,
+ _startLocation.offset + (endPosition ?? position));
+ }
+
+ @override
+ bool matches(Pattern pattern) {
+ if (!super.matches(pattern)) {
+ _lastSpan = null;
+ return false;
+ }
+
+ _lastSpan = _sourceFile.span(_startLocation.offset + position,
+ _startLocation.offset + lastMatch!.end);
+ return true;
+ }
+
+ @override
+ Never error(String message, {Match? match, int? position, int? length}) {
+ validateErrorArgs(string, match, position, length);
+
+ if (match == null && position == null && length == null) match = lastMatch;
+ position ??= match == null ? this.position : match.start;
+ length ??= match == null ? 1 : match.end - match.start;
+
+ final span = _sourceFile.span(_startLocation.offset + position,
+ _startLocation.offset + position + length);
+ throw StringScannerException(message, span, string);
+ }
+}
+
+/// A class representing the state of a [SpanScanner].
+class _SpanScannerState implements LineScannerState {
+ /// The [SpanScanner] that created this.
+ final RelativeSpanScanner _scanner;
+
+ @override
+ final int position;
+ @override
+ int get line => _scanner._sourceFile.getLine(position);
+ @override
+ int get column => _scanner._sourceFile.getColumn(position);
+
+ _SpanScannerState(this._scanner, this.position);
+}
diff --git a/pkgs/string_scanner/lib/src/span_scanner.dart b/pkgs/string_scanner/lib/src/span_scanner.dart
new file mode 100644
index 0000000..509cf60
--- /dev/null
+++ b/pkgs/string_scanner/lib/src/span_scanner.dart
@@ -0,0 +1,142 @@
+// Copyright (c) 2014, 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_span/source_span.dart';
+
+import 'eager_span_scanner.dart';
+import 'exception.dart';
+import 'line_scanner.dart';
+import 'relative_span_scanner.dart';
+import 'string_scanner.dart';
+import 'utils.dart';
+
+/// A subclass of [LineScanner] that exposes matched ranges as source map
+/// [FileSpan]s.
+class SpanScanner extends StringScanner implements LineScanner {
+ /// The source of the scanner.
+ ///
+ /// This caches line break information and is used to generate [FileSpan]s.
+ final SourceFile _sourceFile;
+
+ @override
+ int get line => _sourceFile.getLine(position);
+ @override
+ int get column => _sourceFile.getColumn(position);
+
+ @override
+ LineScannerState get state => _SpanScannerState(this, position);
+
+ @override
+ set state(LineScannerState state) {
+ if (state is! _SpanScannerState || !identical(state._scanner, this)) {
+ throw ArgumentError('The given LineScannerState was not returned by '
+ 'this LineScanner.');
+ }
+
+ position = state.position;
+ }
+
+ /// The [FileSpan] for [lastMatch].
+ ///
+ /// This is the span for the entire match. There's no way to get spans for
+ /// subgroups since [Match] exposes no information about their positions.
+ FileSpan? get lastSpan {
+ if (lastMatch == null) _lastSpan = null;
+ return _lastSpan;
+ }
+
+ FileSpan? _lastSpan;
+
+ /// The current location of the scanner.
+ FileLocation get location => _sourceFile.location(position);
+
+ /// Returns an empty span at the current location.
+ FileSpan get emptySpan => location.pointSpan();
+
+ /// Creates a new [SpanScanner] that starts scanning from [position].
+ ///
+ /// [sourceUrl] is used as [SourceLocation.sourceUrl] for the returned
+ /// [FileSpan]s as well as for error reporting. It can be a [String], a
+ /// [Uri], or `null`.
+ SpanScanner(super.string, {super.sourceUrl, super.position})
+ : _sourceFile = SourceFile.fromString(string, url: sourceUrl);
+
+ /// Creates a new [SpanScanner] that eagerly computes line and column numbers.
+ ///
+ /// In general [SpanScanner.new] will be more efficient, since it avoids extra
+ /// computation on every scan. However, eager scanning can be useful for
+ /// situations where the normal course of parsing frequently involves
+ /// accessing the current line and column numbers.
+ ///
+ /// Note that *only* the `line` and `column` fields on the `SpanScanner`
+ /// itself and its `LineScannerState` are eagerly computed. To limit their
+ /// memory footprint, returned spans and locations will still lazily compute
+ /// their line and column numbers.
+ factory SpanScanner.eager(String string, {sourceUrl, int? position}) =
+ EagerSpanScanner;
+
+ /// Creates a new [SpanScanner] that scans within [span].
+ ///
+ /// This scans through [span]`.text, but emits new spans from [span]`.file` in
+ /// their appropriate relative positions. The [string] field contains only
+ /// [span]`.text`, and [position], [line], and [column] are all relative to
+ /// the span.
+ factory SpanScanner.within(FileSpan span) = RelativeSpanScanner;
+
+ /// Creates a [FileSpan] representing the source range between [startState]
+ /// and the current position.
+ FileSpan spanFrom(LineScannerState startState, [LineScannerState? endState]) {
+ final endPosition = endState == null ? position : endState.position;
+ return _sourceFile.span(startState.position, endPosition);
+ }
+
+ /// Creates a [FileSpan] representing the source range between [startPosition]
+ /// and [endPosition], or the current position if [endPosition] is null.
+ ///
+ /// Each position should be a code unit offset into the string being scanned,
+ /// with the same conventions as [StringScanner.position].
+ ///
+ /// Throws a [RangeError] if [startPosition] or [endPosition] aren't within
+ /// this source file.
+ FileSpan spanFromPosition(int startPosition, [int? endPosition]) =>
+ _sourceFile.span(startPosition, endPosition ?? position);
+
+ @override
+ bool matches(Pattern pattern) {
+ if (!super.matches(pattern)) {
+ _lastSpan = null;
+ return false;
+ }
+
+ _lastSpan = _sourceFile.span(position, lastMatch!.end);
+ return true;
+ }
+
+ @override
+ Never error(String message, {Match? match, int? position, int? length}) {
+ validateErrorArgs(string, match, position, length);
+
+ if (match == null && position == null && length == null) match = lastMatch;
+ position ??= match == null ? this.position : match.start;
+ length ??= match == null ? 0 : match.end - match.start;
+
+ final span = _sourceFile.span(position, position + length);
+ throw StringScannerException(message, span, string);
+ }
+}
+
+/// A class representing the state of a [SpanScanner].
+class _SpanScannerState implements LineScannerState {
+ /// The [SpanScanner] that created this.
+ final SpanScanner _scanner;
+
+ @override
+ final int position;
+ @override
+ int get line => _scanner._sourceFile.getLine(position);
+ @override
+ int get column => _scanner._sourceFile.getColumn(position);
+
+ _SpanScannerState(this._scanner, this.position);
+}
diff --git a/pkgs/string_scanner/lib/src/string_scanner.dart b/pkgs/string_scanner/lib/src/string_scanner.dart
new file mode 100644
index 0000000..1466944
--- /dev/null
+++ b/pkgs/string_scanner/lib/src/string_scanner.dart
@@ -0,0 +1,272 @@
+// Copyright (c) 2014, 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_span/source_span.dart';
+
+import 'charcode.dart';
+import 'exception.dart';
+import 'utils.dart';
+
+/// A class that scans through a string using [Pattern]s.
+class StringScanner {
+ /// The URL of the source of the string being scanned.
+ ///
+ /// This is used for error reporting. It may be `null`, indicating that the
+ /// source URL is unknown or unavailable.
+ final Uri? sourceUrl;
+
+ /// The string being scanned through.
+ final String string;
+
+ /// The current position of the scanner in the string, in characters.
+ int get position => _position;
+ set position(int position) {
+ if (position.isNegative || position > string.length) {
+ throw ArgumentError('Invalid position $position');
+ }
+
+ _position = position;
+ _lastMatch = null;
+ }
+
+ int _position = 0;
+
+ /// The data about the previous match made by the scanner.
+ ///
+ /// If the last match failed, this will be `null`.
+ Match? get lastMatch {
+ // Lazily unset [_lastMatch] so that we avoid extra assignments in
+ // character-by-character methods that are used in core loops.
+ if (_position != _lastMatchPosition) _lastMatch = null;
+ return _lastMatch;
+ }
+
+ Match? _lastMatch;
+ int? _lastMatchPosition;
+
+ /// The portion of the string that hasn't yet been scanned.
+ String get rest => string.substring(position);
+
+ /// Whether the scanner has completely consumed [string].
+ bool get isDone => position == string.length;
+
+ /// Creates a new [StringScanner] that starts scanning from [position].
+ ///
+ /// [position] defaults to 0, the beginning of the string. [sourceUrl] is the
+ /// URL of the source of the string being scanned, if available. It can be
+ /// a [String], a [Uri], or `null`.
+ StringScanner(this.string, {Object? sourceUrl, int? position})
+ : sourceUrl = sourceUrl == null
+ ? null
+ : sourceUrl is String
+ ? Uri.parse(sourceUrl)
+ : sourceUrl as Uri {
+ if (position != null) this.position = position;
+ }
+
+ /// Consumes a single character and returns its character code.
+ ///
+ /// This throws a [FormatException] if the string has been fully consumed. It
+ /// doesn't affect [lastMatch].
+ int readChar() {
+ if (isDone) _fail('more input');
+ return string.codeUnitAt(_position++);
+ }
+
+ /// Returns the character code of the character [offset] away from [position].
+ ///
+ /// [offset] defaults to zero, and may be negative to inspect already-consumed
+ /// characters.
+ ///
+ /// This returns `null` if [offset] points outside the string. It doesn't
+ /// affect [lastMatch].
+ int? peekChar([int? offset]) {
+ offset ??= 0;
+ final index = position + offset;
+ if (index < 0 || index >= string.length) return null;
+ return string.codeUnitAt(index);
+ }
+
+ /// If the next character in the string is [character], consumes it.
+ ///
+ /// If [character] is a Unicode code point in a supplementary plane, this will
+ /// consume two code units. Dart's string representation is UTF-16, which
+ /// represents supplementary-plane code units as two code units.
+ ///
+ /// Returns whether or not [character] was consumed.
+ bool scanChar(int character) {
+ if (inSupplementaryPlane(character)) {
+ if (_position + 1 >= string.length ||
+ string.codeUnitAt(_position) != highSurrogate(character) ||
+ string.codeUnitAt(_position + 1) != lowSurrogate(character)) {
+ return false;
+ } else {
+ _position += 2;
+ return true;
+ }
+ } else {
+ if (isDone) return false;
+ if (string.codeUnitAt(_position) != character) return false;
+ _position++;
+ return true;
+ }
+ }
+
+ /// If the next character in the string is [character], consumes it.
+ ///
+ /// If [character] is a Unicode code point in a supplementary plane, this will
+ /// consume two code units. Dart's string representation is UTF-16, which
+ /// represents supplementary-plane code units as two code units.
+ ///
+ /// If [character] could not be consumed, throws a [FormatException]
+ /// describing the position of the failure. [name] is used in this error as
+ /// the expected name of the character being matched; if it's `null`, the
+ /// character itself is used instead.
+ void expectChar(int character, {String? name}) {
+ if (scanChar(character)) return;
+
+ if (name == null) {
+ if (character == $backslash) {
+ name = r'"\"';
+ } else if (character == $doubleQuote) {
+ name = r'"\""';
+ } else {
+ name = '"${String.fromCharCode(character)}"';
+ }
+ }
+
+ _fail(name);
+ }
+
+ /// Consumes a single Unicode code unit and returns it.
+ ///
+ /// This works like [readChar], except that it automatically handles UTF-16
+ /// surrogate pairs. Specifically, if the next two code units form a surrogate
+ /// pair, consumes them both and returns the corresponding Unicode code point.
+ ///
+ /// If next two characters are not a surrogate pair, the next code unit is
+ /// returned as-is, even if it's an unpaired surrogate.
+ int readCodePoint() {
+ final first = readChar();
+ if (!isHighSurrogate(first)) return first;
+
+ final next = peekChar();
+ if (next == null || !isLowSurrogate(next)) return first;
+
+ readChar();
+ return decodeSurrogatePair(first, next);
+ }
+
+ /// Returns the Unicode code point immediately after [position].
+ ///
+ /// This works like [peekChar], except that it automatically handles UTF-16
+ /// surrogate pairs. Specifically, if the next two code units form a surrogate
+ /// pair, returns the corresponding Unicode code point.
+ ///
+ /// If next two characters are not a surrogate pair, the next code unit is
+ /// returned as-is, even if it's an unpaired surrogate.
+ int? peekCodePoint() {
+ final first = peekChar();
+ if (first == null || !isHighSurrogate(first)) return first;
+
+ final next = peekChar(1);
+ if (next == null || !isLowSurrogate(next)) return first;
+
+ return decodeSurrogatePair(first, next);
+ }
+
+ /// If [pattern] matches at the current position of the string, scans forward
+ /// until the end of the match.
+ ///
+ /// Returns whether or not [pattern] matched.
+ bool scan(Pattern pattern) {
+ final success = matches(pattern);
+ if (success) {
+ _position = _lastMatch!.end;
+ _lastMatchPosition = _position;
+ }
+ return success;
+ }
+
+ /// If [pattern] matches at the current position of the string, scans forward
+ /// until the end of the match.
+ ///
+ /// If [pattern] did not match, throws a [FormatException] describing the
+ /// position of the failure. [name] is used in this error as the expected name
+ /// of the pattern being matched; if it's `null`, the pattern itself is used
+ /// instead.
+ void expect(Pattern pattern, {String? name}) {
+ if (scan(pattern)) return;
+
+ if (name == null) {
+ if (pattern is RegExp) {
+ final source = pattern.pattern;
+ name = '/$source/';
+ } else {
+ name =
+ pattern.toString().replaceAll(r'\', r'\\').replaceAll('"', r'\"');
+ name = '"$name"';
+ }
+ }
+ _fail(name);
+ }
+
+ /// If the string has not been fully consumed, this throws a
+ /// [FormatException].
+ void expectDone() {
+ if (isDone) return;
+ _fail('no more input');
+ }
+
+ /// Returns whether or not [pattern] matches at the current position of the
+ /// string.
+ ///
+ /// This doesn't move the scan pointer forward.
+ bool matches(Pattern pattern) {
+ _lastMatch = pattern.matchAsPrefix(string, position);
+ _lastMatchPosition = _position;
+ return _lastMatch != null;
+ }
+
+ /// Returns the substring of [string] between [start] and [end].
+ ///
+ /// Unlike [String.substring], [end] defaults to [position] rather than the
+ /// end of the string.
+ String substring(int start, [int? end]) {
+ end ??= position;
+ return string.substring(start, end);
+ }
+
+ /// Throws a [FormatException] with [message] as well as a detailed
+ /// description of the location of the error in the string.
+ ///
+ /// [match] is the match information for the span of the string with which the
+ /// error is associated. This should be a match returned by this scanner's
+ /// [lastMatch] property. By default, the error is associated with the last
+ /// match.
+ ///
+ /// If [position] and/or [length] are passed, they are used as the error span
+ /// instead. If only [length] is passed, [position] defaults to the current
+ /// position; if only [position] is passed, [length] defaults to 0.
+ ///
+ /// It's an error to pass [match] at the same time as [position] or [length].
+ Never error(String message, {Match? match, int? position, int? length}) {
+ validateErrorArgs(string, match, position, length);
+
+ if (match == null && position == null && length == null) match = lastMatch;
+ position ??= match == null ? this.position : match.start;
+ length ??= match == null ? 0 : match.end - match.start;
+
+ final sourceFile = SourceFile.fromString(string, url: sourceUrl);
+ final span = sourceFile.span(position, position + length);
+ throw StringScannerException(message, span, string);
+ }
+
+ // TODO(nweiz): Make this handle long lines more gracefully.
+ /// Throws a [FormatException] describing that [name] is expected at the
+ /// current position in the string.
+ Never _fail(String name) {
+ error('expected $name.', position: position, length: 0);
+ }
+}
diff --git a/pkgs/string_scanner/lib/src/utils.dart b/pkgs/string_scanner/lib/src/utils.dart
new file mode 100644
index 0000000..39891a1
--- /dev/null
+++ b/pkgs/string_scanner/lib/src/utils.dart
@@ -0,0 +1,95 @@
+// Copyright (c) 2014, 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 'string_scanner.dart';
+
+/// Validates the arguments passed to [StringScanner.error].
+void validateErrorArgs(
+ String string, Match? match, int? position, int? length) {
+ if (match != null && (position != null || length != null)) {
+ throw ArgumentError("Can't pass both match and position/length.");
+ }
+
+ if (position != null) {
+ if (position < 0) {
+ throw RangeError('position must be greater than or equal to 0.');
+ } else if (position > string.length) {
+ throw RangeError('position must be less than or equal to the '
+ 'string length.');
+ }
+ }
+
+ if (length != null && length < 0) {
+ throw RangeError('length must be greater than or equal to 0.');
+ }
+
+ if (position != null && length != null && position + length > string.length) {
+ throw RangeError('position plus length must not go beyond the end of '
+ 'the string.');
+ }
+}
+
+// See https://en.wikipedia.org/wiki/UTF-16#Code_points_from_U+010000_to_U+10FFFF
+// for documentation on how UTF-16 encoding works and definitions of various
+// related terms.
+
+/// The inclusive lower bound of Unicode's supplementary plane.
+const _supplementaryPlaneLowerBound = 0x10000;
+
+/// The inclusive upper bound of Unicode's supplementary plane.
+const _supplementaryPlaneUpperBound = 0x10FFFF;
+
+/// The inclusive lower bound of the UTF-16 high surrogate block.
+const _highSurrogateLowerBound = 0xD800;
+
+/// The inclusive lower bound of the UTF-16 low surrogate block.
+const _lowSurrogateLowerBound = 0xDC00;
+
+/// The number of low bits in each code unit of a surrogate pair that goes into
+/// determining which code point it encodes.
+const _surrogateBits = 10;
+
+/// A bit mask that covers the lower [_surrogateBits] of a code point, which can
+/// be used to extract the value of a surrogate or the low surrogate value of a
+/// code unit.
+const _surrogateValueMask = (1 << _surrogateBits) - 1;
+
+/// Returns whether [codePoint] is in the Unicode supplementary plane, and thus
+/// must be represented as a surrogate pair in UTF-16.
+bool inSupplementaryPlane(int codePoint) =>
+ codePoint >= _supplementaryPlaneLowerBound &&
+ codePoint <= _supplementaryPlaneUpperBound;
+
+/// Returns whether [codeUnit] is a UTF-16 high surrogate.
+bool isHighSurrogate(int codeUnit) =>
+ (codeUnit & ~_surrogateValueMask) == _highSurrogateLowerBound;
+
+/// Returns whether [codeUnit] is a UTF-16 low surrogate.
+bool isLowSurrogate(int codeUnit) =>
+ (codeUnit >> _surrogateBits) == (_lowSurrogateLowerBound >> _surrogateBits);
+
+/// Returns the high surrogate needed to encode the supplementary-plane
+/// [codePoint].
+int highSurrogate(int codePoint) {
+ assert(inSupplementaryPlane(codePoint));
+ return ((codePoint - _supplementaryPlaneLowerBound) >> _surrogateBits) +
+ _highSurrogateLowerBound;
+}
+
+/// Returns the low surrogate needed to encode the supplementary-plane
+/// [codePoint].
+int lowSurrogate(int codePoint) {
+ assert(inSupplementaryPlane(codePoint));
+ return ((codePoint - _supplementaryPlaneLowerBound) & _surrogateValueMask) +
+ _lowSurrogateLowerBound;
+}
+
+/// Converts a UTF-16 surrogate pair into the Unicode code unit it represents.
+int decodeSurrogatePair(int highSurrogate, int lowSurrogate) {
+ assert(isHighSurrogate(highSurrogate));
+ assert(isLowSurrogate(lowSurrogate));
+ return _supplementaryPlaneLowerBound +
+ (((highSurrogate & _surrogateValueMask) << _surrogateBits) |
+ (lowSurrogate & _surrogateValueMask));
+}
diff --git a/pkgs/string_scanner/lib/string_scanner.dart b/pkgs/string_scanner/lib/string_scanner.dart
new file mode 100644
index 0000000..e641ae7
--- /dev/null
+++ b/pkgs/string_scanner/lib/string_scanner.dart
@@ -0,0 +1,11 @@
+// Copyright (c) 2014, 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.
+
+/// A library for parsing strings using a sequence of patterns.
+library;
+
+export 'src/exception.dart';
+export 'src/line_scanner.dart';
+export 'src/span_scanner.dart';
+export 'src/string_scanner.dart';
diff --git a/pkgs/string_scanner/pubspec.yaml b/pkgs/string_scanner/pubspec.yaml
new file mode 100644
index 0000000..9b259cf
--- /dev/null
+++ b/pkgs/string_scanner/pubspec.yaml
@@ -0,0 +1,14 @@
+name: string_scanner
+version: 1.4.1
+description: A class for parsing strings using a sequence of patterns.
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/string_scanner
+
+environment:
+ sdk: ^3.1.0
+
+dependencies:
+ source_span: ^1.8.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ test: ^1.16.6
diff --git a/pkgs/string_scanner/test/error_test.dart b/pkgs/string_scanner/test/error_test.dart
new file mode 100644
index 0000000..1f98c32
--- /dev/null
+++ b/pkgs/string_scanner/test/error_test.dart
@@ -0,0 +1,143 @@
+// Copyright (c) 2014, 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:string_scanner/string_scanner.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ test('defaults to the last match', () {
+ final scanner = StringScanner('foo bar baz');
+ scanner.expect('foo ');
+ scanner.expect('bar');
+ expect(() => scanner.error('oh no!'), throwsStringScannerException('bar'));
+ });
+
+ group('with match', () {
+ test('supports an earlier match', () {
+ final scanner = StringScanner('foo bar baz');
+ scanner.expect('foo ');
+ final match = scanner.lastMatch;
+ scanner.expect('bar');
+ expect(() => scanner.error('oh no!', match: match),
+ throwsStringScannerException('foo '));
+ });
+
+ test('supports a match on a previous line', () {
+ final scanner = StringScanner('foo bar baz\ndo re mi\nearth fire water');
+ scanner.expect('foo bar baz\ndo ');
+ scanner.expect('re');
+ final match = scanner.lastMatch;
+ scanner.expect(' mi\nearth ');
+ expect(() => scanner.error('oh no!', match: match),
+ throwsStringScannerException('re'));
+ });
+
+ test('supports a multiline match', () {
+ final scanner = StringScanner('foo bar baz\ndo re mi\nearth fire water');
+ scanner.expect('foo bar ');
+ scanner.expect('baz\ndo');
+ final match = scanner.lastMatch;
+ scanner.expect(' re mi');
+ expect(() => scanner.error('oh no!', match: match),
+ throwsStringScannerException('baz\ndo'));
+ });
+
+ test('supports a match after position', () {
+ final scanner = StringScanner('foo bar baz');
+ scanner.expect('foo ');
+ scanner.expect('bar');
+ final match = scanner.lastMatch;
+ scanner.position = 0;
+ expect(() => scanner.error('oh no!', match: match),
+ throwsStringScannerException('bar'));
+ });
+ });
+
+ group('with position and/or length', () {
+ test('defaults to length 0', () {
+ final scanner = StringScanner('foo bar baz');
+ scanner.expect('foo ');
+ expect(() => scanner.error('oh no!', position: 1),
+ throwsStringScannerException(''));
+ });
+
+ test('defaults to the current position', () {
+ final scanner = StringScanner('foo bar baz');
+ scanner.expect('foo ');
+ expect(() => scanner.error('oh no!', length: 3),
+ throwsStringScannerException('bar'));
+ });
+
+ test('supports an earlier position', () {
+ final scanner = StringScanner('foo bar baz');
+ scanner.expect('foo ');
+ expect(() => scanner.error('oh no!', position: 1, length: 2),
+ throwsStringScannerException('oo'));
+ });
+
+ test('supports a position on a previous line', () {
+ final scanner = StringScanner('foo bar baz\ndo re mi\nearth fire water');
+ scanner.expect('foo bar baz\ndo re mi\nearth');
+ expect(() => scanner.error('oh no!', position: 15, length: 2),
+ throwsStringScannerException('re'));
+ });
+
+ test('supports a multiline length', () {
+ final scanner = StringScanner('foo bar baz\ndo re mi\nearth fire water');
+ scanner.expect('foo bar baz\ndo re mi\nearth');
+ expect(() => scanner.error('oh no!', position: 8, length: 8),
+ throwsStringScannerException('baz\ndo r'));
+ });
+
+ test('supports a position after the current one', () {
+ final scanner = StringScanner('foo bar baz');
+ expect(() => scanner.error('oh no!', position: 4, length: 3),
+ throwsStringScannerException('bar'));
+ });
+
+ test('supports a length of zero', () {
+ final scanner = StringScanner('foo bar baz');
+ expect(() => scanner.error('oh no!', position: 4, length: 0),
+ throwsStringScannerException(''));
+ });
+ });
+
+ group('argument errors', () {
+ late StringScanner scanner;
+ setUp(() {
+ scanner = StringScanner('foo bar baz');
+ scanner.scan('foo');
+ });
+
+ test('if match is passed with position', () {
+ expect(
+ () => scanner.error('oh no!', match: scanner.lastMatch, position: 1),
+ throwsArgumentError);
+ });
+
+ test('if match is passed with length', () {
+ expect(() => scanner.error('oh no!', match: scanner.lastMatch, length: 1),
+ throwsArgumentError);
+ });
+
+ test('if position is negative', () {
+ expect(() => scanner.error('oh no!', position: -1), throwsArgumentError);
+ });
+
+ test('if position is outside the string', () {
+ expect(() => scanner.error('oh no!', position: 100), throwsArgumentError);
+ });
+
+ test('if position + length is outside the string', () {
+ expect(() => scanner.error('oh no!', position: 7, length: 7),
+ throwsArgumentError);
+ });
+
+ test('if length is negative', () {
+ expect(() => scanner.error('oh no!', length: -1), throwsArgumentError);
+ });
+ });
+}
diff --git a/pkgs/string_scanner/test/line_scanner_test.dart b/pkgs/string_scanner/test/line_scanner_test.dart
new file mode 100644
index 0000000..1af5c36
--- /dev/null
+++ b/pkgs/string_scanner/test/line_scanner_test.dart
@@ -0,0 +1,465 @@
+// Copyright (c) 2014, 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:string_scanner/src/charcode.dart';
+import 'package:string_scanner/string_scanner.dart';
+import 'package:test/test.dart';
+
+void main() {
+ late LineScanner scanner;
+ setUp(() {
+ scanner = LineScanner('foo\nbar\r\nbaz');
+ });
+
+ test('begins with line and column 0', () {
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(0));
+ });
+
+ group('scan()', () {
+ test('consuming no newlines increases the column but not the line', () {
+ scanner.expect('foo');
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(3));
+ });
+
+ test('consuming a LF resets the column and increases the line', () {
+ scanner.expect('foo\nba');
+ expect(scanner.line, equals(1));
+ expect(scanner.column, equals(2));
+ });
+
+ test('consuming multiple LFs resets the column and increases the line', () {
+ scanner.expect('foo\nbar\r\nb');
+ expect(scanner.line, equals(2));
+ expect(scanner.column, equals(1));
+ });
+
+ test('consuming a CR LF increases the line only after the LF', () {
+ scanner.expect('foo\nbar\r');
+ expect(scanner.line, equals(1));
+ expect(scanner.column, equals(4));
+
+ scanner.expect('\nb');
+ expect(scanner.line, equals(2));
+ expect(scanner.column, equals(1));
+ });
+
+ test('consuming a CR not followed by LF increases the line', () {
+ scanner = LineScanner('foo\nbar\rbaz');
+ scanner.expect('foo\nbar\r');
+ expect(scanner.line, equals(2));
+ expect(scanner.column, equals(0));
+
+ scanner.expect('b');
+ expect(scanner.line, equals(2));
+ expect(scanner.column, equals(1));
+ });
+
+ test('consuming a CR at the end increases the line', () {
+ scanner = LineScanner('foo\nbar\r');
+ scanner.expect('foo\nbar\r');
+ expect(scanner.line, equals(2));
+ expect(scanner.column, equals(0));
+ expect(scanner.isDone, isTrue);
+ });
+
+ test('consuming a mix of CR, LF, CR+LF increases the line', () {
+ scanner = LineScanner('0\n1\r2\r\n3');
+ scanner.expect('0\n1\r2\r\n3');
+ expect(scanner.line, equals(3));
+ expect(scanner.column, equals(1));
+ });
+
+ test('scanning a zero length match between CR LF does not fail', () {
+ scanner.expect('foo\nbar\r');
+ expect(scanner.line, equals(1));
+ expect(scanner.column, equals(4));
+ scanner.expect(RegExp('(?!x)'));
+ expect(scanner.line, equals(1));
+ expect(scanner.column, equals(4));
+ });
+ });
+
+ group('readChar()', () {
+ test('on a non-newline character increases the column but not the line',
+ () {
+ scanner.readChar();
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(1));
+ });
+
+ test('consuming a LF resets the column and increases the line', () {
+ scanner.expect('foo');
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(3));
+
+ scanner.readChar();
+ expect(scanner.line, equals(1));
+ expect(scanner.column, equals(0));
+ });
+
+ test('consuming a CR LF increases the line only after the LF', () {
+ scanner = LineScanner('foo\r\nbar');
+ scanner.expect('foo');
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(3));
+
+ scanner.readChar();
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(4));
+
+ scanner.readChar();
+ expect(scanner.line, equals(1));
+ expect(scanner.column, equals(0));
+ });
+
+ test('consuming a CR not followed by a LF increases the line', () {
+ scanner = LineScanner('foo\nbar\rbaz');
+ scanner.expect('foo\nbar');
+ expect(scanner.line, equals(1));
+ expect(scanner.column, equals(3));
+
+ scanner.readChar();
+ expect(scanner.line, equals(2));
+ expect(scanner.column, equals(0));
+ });
+
+ test('consuming a CR at the end increases the line', () {
+ scanner = LineScanner('foo\nbar\r');
+ scanner.expect('foo\nbar');
+ expect(scanner.line, equals(1));
+ expect(scanner.column, equals(3));
+
+ scanner.readChar();
+ expect(scanner.line, equals(2));
+ expect(scanner.column, equals(0));
+ });
+
+ test('consuming a mix of CR, LF, CR+LF increases the line', () {
+ scanner = LineScanner('0\n1\r2\r\n3');
+ for (var i = 0; i < scanner.string.length; i++) {
+ scanner.readChar();
+ }
+
+ expect(scanner.line, equals(3));
+ expect(scanner.column, equals(1));
+ });
+ });
+
+ group('readCodePoint()', () {
+ test('on a non-newline character increases the column but not the line',
+ () {
+ scanner.readCodePoint();
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(1));
+ });
+
+ test('consuming a newline resets the column and increases the line', () {
+ scanner.expect('foo');
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(3));
+
+ scanner.readCodePoint();
+ expect(scanner.line, equals(1));
+ expect(scanner.column, equals(0));
+ });
+
+ test("consuming halfway through a CR LF doesn't count as a line", () {
+ scanner.expect('foo\nbar');
+ expect(scanner.line, equals(1));
+ expect(scanner.column, equals(3));
+
+ scanner.readCodePoint();
+ expect(scanner.line, equals(1));
+ expect(scanner.column, equals(4));
+
+ scanner.readCodePoint();
+ expect(scanner.line, equals(2));
+ expect(scanner.column, equals(0));
+ });
+ });
+
+ group('scanChar()', () {
+ test('on a non-newline character increases the column but not the line',
+ () {
+ scanner.scanChar($f);
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(1));
+ });
+
+ test('consuming a LF resets the column and increases the line', () {
+ scanner.expect('foo');
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(3));
+
+ scanner.scanChar($lf);
+ expect(scanner.line, equals(1));
+ expect(scanner.column, equals(0));
+ });
+
+ test('consuming a CR LF increases the line only after the LF', () {
+ scanner.expect('foo\nbar');
+ expect(scanner.line, equals(1));
+ expect(scanner.column, equals(3));
+
+ scanner.scanChar($cr);
+ expect(scanner.line, equals(1));
+ expect(scanner.column, equals(4));
+
+ scanner.scanChar($lf);
+ expect(scanner.line, equals(2));
+ expect(scanner.column, equals(0));
+ });
+
+ test('consuming a CR not followed by LF increases the line', () {
+ scanner = LineScanner('foo\rbar');
+ scanner.expect('foo');
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(3));
+
+ scanner.scanChar($cr);
+ expect(scanner.line, equals(1));
+ expect(scanner.column, equals(0));
+ });
+
+ test('consuming a CR at the end increases the line', () {
+ scanner = LineScanner('foo\r');
+ scanner.expect('foo');
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(3));
+
+ scanner.scanChar($cr);
+ expect(scanner.line, equals(1));
+ expect(scanner.column, equals(0));
+ });
+
+ test('consuming a mix of CR, LF, CR+LF increases the line', () {
+ scanner = LineScanner('0\n1\r2\r\n3');
+ for (var i = 0; i < scanner.string.length; i++) {
+ scanner.scanChar(scanner.string[i].codeUnits.single);
+ }
+
+ expect(scanner.line, equals(3));
+ expect(scanner.column, equals(1));
+ });
+ });
+
+ group('before a surrogate pair', () {
+ final codePoint = '\uD83D\uDC6D'.runes.first;
+ const highSurrogate = 0xD83D;
+
+ late LineScanner scanner;
+ setUp(() {
+ scanner = LineScanner('foo: \uD83D\uDC6D');
+ expect(scanner.scan('foo: '), isTrue);
+ });
+
+ test('readChar returns the high surrogate and moves into the pair', () {
+ expect(scanner.readChar(), equals(highSurrogate));
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(6));
+ expect(scanner.position, equals(6));
+ });
+
+ test('readCodePoint returns the code unit and moves past the pair', () {
+ expect(scanner.readCodePoint(), equals(codePoint));
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(7));
+ expect(scanner.position, equals(7));
+ });
+
+ test('scanChar with the high surrogate moves into the pair', () {
+ expect(scanner.scanChar(highSurrogate), isTrue);
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(6));
+ expect(scanner.position, equals(6));
+ });
+
+ test('scanChar with the code point moves past the pair', () {
+ expect(scanner.scanChar(codePoint), isTrue);
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(7));
+ expect(scanner.position, equals(7));
+ });
+
+ test('expectChar with the high surrogate moves into the pair', () {
+ scanner.expectChar(highSurrogate);
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(6));
+ expect(scanner.position, equals(6));
+ });
+
+ test('expectChar with the code point moves past the pair', () {
+ scanner.expectChar(codePoint);
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(7));
+ expect(scanner.position, equals(7));
+ });
+ });
+
+ group('position=', () {
+ test('forward through LFs sets the line and column', () {
+ scanner = LineScanner('foo\nbar\nbaz');
+ scanner.position = 9; // "foo\nbar\nb"
+ expect(scanner.line, equals(2));
+ expect(scanner.column, equals(1));
+ });
+
+ test('forward from non-zero character through LFs sets the line and column',
+ () {
+ scanner = LineScanner('foo\nbar\nbaz');
+ scanner.expect('fo');
+ scanner.position = 9; // "foo\nbar\nb"
+ expect(scanner.line, equals(2));
+ expect(scanner.column, equals(1));
+ });
+
+ test('forward through CR LFs sets the line and column', () {
+ scanner = LineScanner('foo\r\nbar\r\nbaz');
+ scanner.position = 11; // "foo\r\nbar\r\nb"
+ expect(scanner.line, equals(2));
+ expect(scanner.column, equals(1));
+ });
+
+ test('forward through CR not followed by LFs sets the line and column', () {
+ scanner = LineScanner('foo\rbar\rbaz');
+ scanner.position = 9; // "foo\rbar\rb"
+ expect(scanner.line, equals(2));
+ expect(scanner.column, equals(1));
+ });
+
+ test('forward through CR at end sets the line and column', () {
+ scanner = LineScanner('foo\rbar\r');
+ scanner.position = 8; // "foo\rbar\r"
+ expect(scanner.line, equals(2));
+ expect(scanner.column, equals(0));
+ });
+
+ test('forward through a mix of CR, LF, CR+LF sets the line and column', () {
+ scanner = LineScanner('0\n1\r2\r\n3');
+ scanner.position = scanner.string.length;
+
+ expect(scanner.line, equals(3));
+ expect(scanner.column, equals(1));
+ });
+
+ test('forward through no newlines sets the column', () {
+ scanner.position = 2; // "fo"
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(2));
+ });
+
+ test('backward through LFs sets the line and column', () {
+ scanner = LineScanner('foo\nbar\nbaz');
+ scanner.expect('foo\nbar\nbaz');
+ scanner.position = 2; // "fo"
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(2));
+ });
+
+ test('backward through CR LFs sets the line and column', () {
+ scanner = LineScanner('foo\r\nbar\r\nbaz');
+ scanner.expect('foo\r\nbar\r\nbaz');
+ scanner.position = 2; // "fo"
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(2));
+ });
+
+ test('backward through CR not followed by LFs sets the line and column',
+ () {
+ scanner = LineScanner('foo\rbar\rbaz');
+ scanner.expect('foo\rbar\rbaz');
+ scanner.position = 2; // "fo"
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(2));
+ });
+
+ test('backward through CR at end sets the line and column', () {
+ scanner = LineScanner('foo\rbar\r');
+ scanner.expect('foo\rbar\r');
+ scanner.position = 2; // "fo"
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(2));
+ });
+
+ test('backward through a mix of CR, LF, CR+LF sets the line and column',
+ () {
+ scanner = LineScanner('0\n1\r2\r\n3');
+ scanner.expect(scanner.string);
+
+ scanner.position = 1;
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(1));
+ });
+
+ test('backward through no newlines sets the column', () {
+ scanner.expect('foo\nbar\r\nbaz');
+ scanner.position = 10; // "foo\nbar\r\nb"
+ expect(scanner.line, equals(2));
+ expect(scanner.column, equals(1));
+ });
+
+ test("forward halfway through a CR LF doesn't count as a line", () {
+ scanner.position = 8; // "foo\nbar\r"
+ expect(scanner.line, equals(1));
+ expect(scanner.column, equals(4));
+ });
+
+ test('forward from halfway through a CR LF counts as a line', () {
+ scanner.expect('foo\nbar\r');
+ scanner.position = 11; // "foo\nbar\r\nba"
+ expect(scanner.line, equals(2));
+ expect(scanner.column, equals(2));
+ });
+
+ test('backward to between CR LF', () {
+ scanner.expect('foo\nbar\r\nbaz');
+ scanner.position = 8; // "foo\nbar\r"
+ expect(scanner.line, equals(1));
+ expect(scanner.column, equals(4));
+ });
+
+ test('backward from between CR LF', () {
+ scanner.expect('foo\nbar\r');
+ expect(scanner.line, equals(1));
+ expect(scanner.column, equals(4));
+ scanner.position = 5; // "foo\nb"
+ expect(scanner.line, equals(1));
+ expect(scanner.column, equals(1));
+ });
+
+ test('backward to after CR LF', () {
+ scanner.expect('foo\nbar\r\nbaz');
+ scanner.position = 9; // "foo\nbar\r\n"
+ expect(scanner.line, equals(2));
+ expect(scanner.column, equals(0));
+ });
+
+ test('backward to before CR LF', () {
+ scanner.expect('foo\nbar\r\nbaz');
+ scanner.position = 7; // "foo\nbar"
+ expect(scanner.line, equals(1));
+ expect(scanner.column, equals(3));
+ });
+ });
+
+ test('state= restores the line, column, and position', () {
+ scanner.expect('foo\nb');
+ final state = scanner.state;
+
+ scanner.scan('ar\nba');
+ scanner.state = state;
+ expect(scanner.rest, equals('ar\r\nbaz'));
+ expect(scanner.line, equals(1));
+ expect(scanner.column, equals(1));
+ });
+
+ test('state= rejects a foreign state', () {
+ scanner.scan('foo\nb');
+
+ expect(() => LineScanner(scanner.string).state = scanner.state,
+ throwsArgumentError);
+ });
+}
diff --git a/pkgs/string_scanner/test/span_scanner_test.dart b/pkgs/string_scanner/test/span_scanner_test.dart
new file mode 100644
index 0000000..93d9c47
--- /dev/null
+++ b/pkgs/string_scanner/test/span_scanner_test.dart
@@ -0,0 +1,238 @@
+// Copyright (c) 2014, 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_span/source_span.dart';
+import 'package:string_scanner/string_scanner.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ testForImplementation(
+ 'lazy',
+ ([String? string]) =>
+ SpanScanner(string ?? 'foo\nbar\nbaz', sourceUrl: 'source'));
+
+ testForImplementation(
+ 'eager',
+ ([String? string]) =>
+ SpanScanner.eager(string ?? 'foo\nbar\nbaz', sourceUrl: 'source'));
+
+ group('within', () {
+ const text = 'first\nbefore: foo\nbar\nbaz :after\nlast';
+ final startOffset = text.indexOf('foo');
+
+ late SpanScanner scanner;
+ setUp(() {
+ final file = SourceFile.fromString(text, url: 'source');
+ scanner =
+ SpanScanner.within(file.span(startOffset, text.indexOf(' :after')));
+ });
+
+ test('string only includes the span text', () {
+ expect(scanner.string, equals('foo\nbar\nbaz'));
+ });
+
+ test('line and column are span-relative', () {
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(0));
+
+ scanner.scan('foo');
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(3));
+
+ scanner.scan('\n');
+ expect(scanner.line, equals(1));
+ expect(scanner.column, equals(0));
+ });
+
+ test('tracks the span for the last match', () {
+ scanner.scan('fo');
+ scanner.scan('o\nba');
+
+ final span = scanner.lastSpan!;
+ expect(span.start.offset, equals(startOffset + 2));
+ expect(span.start.line, equals(1));
+ expect(span.start.column, equals(10));
+ expect(span.start.sourceUrl, equals(Uri.parse('source')));
+
+ expect(span.end.offset, equals(startOffset + 6));
+ expect(span.end.line, equals(2));
+ expect(span.end.column, equals(2));
+ expect(span.start.sourceUrl, equals(Uri.parse('source')));
+
+ expect(span.text, equals('o\nba'));
+ });
+
+ test('.spanFrom() returns a span from a previous state', () {
+ scanner.scan('fo');
+ final state = scanner.state;
+ scanner.scan('o\nba');
+ scanner.scan('r\nba');
+
+ final span = scanner.spanFrom(state);
+ expect(span.text, equals('o\nbar\nba'));
+ });
+
+ test('.spanFromPosition() returns a span from a previous state', () {
+ scanner.scan('fo');
+ final start = scanner.position;
+ scanner.scan('o\nba');
+ scanner.scan('r\nba');
+
+ final span = scanner.spanFromPosition(start + 2, start + 5);
+ expect(span.text, equals('bar'));
+ });
+
+ test('.emptySpan returns an empty span at the current location', () {
+ scanner.scan('foo\nba');
+
+ final span = scanner.emptySpan;
+ expect(span.start.offset, equals(startOffset + 6));
+ expect(span.start.line, equals(2));
+ expect(span.start.column, equals(2));
+ expect(span.start.sourceUrl, equals(Uri.parse('source')));
+
+ expect(span.end.offset, equals(startOffset + 6));
+ expect(span.end.line, equals(2));
+ expect(span.end.column, equals(2));
+ expect(span.start.sourceUrl, equals(Uri.parse('source')));
+
+ expect(span.text, equals(''));
+ });
+
+ test('.error() uses an absolute span', () {
+ scanner.expect('foo');
+ expect(
+ () => scanner.error('oh no!'), throwsStringScannerException('foo'));
+ });
+
+ test('.isDone returns true at the end of the span', () {
+ scanner.expect('foo\nbar\nbaz');
+ expect(scanner.isDone, isTrue);
+ });
+ });
+}
+
+void testForImplementation(
+ String name, SpanScanner Function([String string]) create) {
+ group('for a $name scanner', () {
+ late SpanScanner scanner;
+ setUp(() => scanner = create());
+
+ test('tracks the span for the last match', () {
+ scanner.scan('fo');
+ scanner.scan('o\nba');
+
+ final span = scanner.lastSpan!;
+ expect(span.start.offset, equals(2));
+ expect(span.start.line, equals(0));
+ expect(span.start.column, equals(2));
+ expect(span.start.sourceUrl, equals(Uri.parse('source')));
+
+ expect(span.end.offset, equals(6));
+ expect(span.end.line, equals(1));
+ expect(span.end.column, equals(2));
+ expect(span.start.sourceUrl, equals(Uri.parse('source')));
+
+ expect(span.text, equals('o\nba'));
+ });
+
+ test('.spanFrom() returns a span from a previous state', () {
+ scanner.scan('fo');
+ final state = scanner.state;
+ scanner.scan('o\nba');
+ scanner.scan('r\nba');
+
+ final span = scanner.spanFrom(state);
+ expect(span.text, equals('o\nbar\nba'));
+ });
+
+ test('.spanFromPosition() returns a span from a previous state', () {
+ scanner.scan('fo');
+ final start = scanner.position;
+ scanner.scan('o\nba');
+ scanner.scan('r\nba');
+
+ final span = scanner.spanFromPosition(start + 2, start + 5);
+ expect(span.text, equals('bar'));
+ });
+
+ test('.emptySpan returns an empty span at the current location', () {
+ scanner.scan('foo\nba');
+
+ final span = scanner.emptySpan;
+ expect(span.start.offset, equals(6));
+ expect(span.start.line, equals(1));
+ expect(span.start.column, equals(2));
+ expect(span.start.sourceUrl, equals(Uri.parse('source')));
+
+ expect(span.end.offset, equals(6));
+ expect(span.end.line, equals(1));
+ expect(span.end.column, equals(2));
+ expect(span.start.sourceUrl, equals(Uri.parse('source')));
+
+ expect(span.text, equals(''));
+ });
+
+ group('before a surrogate pair', () {
+ final codePoint = '\uD83D\uDC6D'.runes.first;
+ const highSurrogate = 0xD83D;
+
+ late SpanScanner scanner;
+ setUp(() {
+ scanner = create('foo: \uD83D\uDC6D bar');
+ expect(scanner.scan('foo: '), isTrue);
+ });
+
+ test('readChar returns the high surrogate and moves into the pair', () {
+ expect(scanner.readChar(), equals(highSurrogate));
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(6));
+ expect(scanner.position, equals(6));
+ });
+
+ test('readCodePoint returns the code unit and moves past the pair', () {
+ expect(scanner.readCodePoint(), equals(codePoint));
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(7));
+ expect(scanner.position, equals(7));
+ });
+
+ test('scanChar with the high surrogate moves into the pair', () {
+ expect(scanner.scanChar(highSurrogate), isTrue);
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(6));
+ expect(scanner.position, equals(6));
+ });
+
+ test('scanChar with the code point moves past the pair', () {
+ expect(scanner.scanChar(codePoint), isTrue);
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(7));
+ expect(scanner.position, equals(7));
+ });
+
+ test('expectChar with the high surrogate moves into the pair', () {
+ scanner.expectChar(highSurrogate);
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(6));
+ expect(scanner.position, equals(6));
+ });
+
+ test('expectChar with the code point moves past the pair', () {
+ scanner.expectChar(codePoint);
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(7));
+ expect(scanner.position, equals(7));
+ });
+
+ test('spanFrom covers the surrogate pair', () {
+ final state = scanner.state;
+ scanner.scan('\uD83D\uDC6D b');
+ expect(scanner.spanFrom(state).text, equals('\uD83D\uDC6D b'));
+ });
+ });
+ });
+}
diff --git a/pkgs/string_scanner/test/string_scanner_test.dart b/pkgs/string_scanner/test/string_scanner_test.dart
new file mode 100644
index 0000000..36a737e
--- /dev/null
+++ b/pkgs/string_scanner/test/string_scanner_test.dart
@@ -0,0 +1,564 @@
+// Copyright (c) 2014, 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:string_scanner/src/charcode.dart';
+import 'package:string_scanner/string_scanner.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('with an empty string', () {
+ late StringScanner scanner;
+ setUp(() {
+ scanner = StringScanner('');
+ });
+
+ test('is done', () {
+ expect(scanner.isDone, isTrue);
+ expect(scanner.expectDone, isNot(throwsFormatException));
+ });
+
+ test('rest is empty', () {
+ expect(scanner.rest, isEmpty);
+ });
+
+ test('lastMatch is null', () {
+ expect(scanner.lastMatch, isNull);
+ });
+
+ test('position is zero', () {
+ expect(scanner.position, equals(0));
+ });
+
+ test("readChar fails and doesn't change the state", () {
+ expect(scanner.readChar, throwsFormatException);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(0));
+ });
+
+ test("readCodePoint fails and doesn't change the state", () {
+ expect(scanner.readCodePoint, throwsFormatException);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(0));
+ });
+
+ test("peekChar returns null and doesn't change the state", () {
+ expect(scanner.peekChar(), isNull);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(0));
+ });
+
+ test("peekCodePoint returns null and doesn't change the state", () {
+ expect(scanner.peekCodePoint(), isNull);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(0));
+ });
+
+ test("scanChar returns false and doesn't change the state", () {
+ expect(scanner.scanChar($f), isFalse);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(0));
+ });
+
+ test("expectChar fails and doesn't change the state", () {
+ expect(() => scanner.expectChar($f), throwsFormatException);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(0));
+ });
+
+ test("scan returns false and doesn't change the state", () {
+ expect(scanner.scan(RegExp('.')), isFalse);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(0));
+ });
+
+ test("expect throws a FormatException and doesn't change the state", () {
+ expect(() => scanner.expect(RegExp('.')), throwsFormatException);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(0));
+ });
+
+ test("matches returns false and doesn't change the state", () {
+ expect(scanner.matches(RegExp('.')), isFalse);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(0));
+ });
+
+ test('substring returns the empty string', () {
+ expect(scanner.substring(0), isEmpty);
+ });
+
+ test('setting position to 1 throws an ArgumentError', () {
+ expect(() {
+ scanner.position = 1;
+ }, throwsArgumentError);
+ });
+
+ test('setting position to -1 throws an ArgumentError', () {
+ expect(() {
+ scanner.position = -1;
+ }, throwsArgumentError);
+ });
+ });
+
+ group('at the beginning of a string', () {
+ late StringScanner scanner;
+ setUp(() {
+ scanner = StringScanner('foo bar');
+ });
+
+ test('is not done', () {
+ expect(scanner.isDone, isFalse);
+ expect(scanner.expectDone, throwsFormatException);
+ });
+
+ test('rest is the whole string', () {
+ expect(scanner.rest, equals('foo bar'));
+ });
+
+ test('lastMatch is null', () {
+ expect(scanner.lastMatch, isNull);
+ });
+
+ test('position is zero', () {
+ expect(scanner.position, equals(0));
+ });
+
+ test('readChar returns the first character and moves forward', () {
+ expect(scanner.readChar(), equals(0x66));
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(1));
+ });
+
+ test('readCodePoint returns the first character and moves forward', () {
+ expect(scanner.readCodePoint(), equals(0x66));
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(1));
+ });
+
+ test('peekChar returns the first character', () {
+ expect(scanner.peekChar(), equals(0x66));
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(0));
+ });
+
+ test('peekChar with an argument returns the nth character', () {
+ expect(scanner.peekChar(4), equals(0x62));
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(0));
+ });
+
+ test('peekCodePoint returns the first character', () {
+ expect(scanner.peekCodePoint(), equals(0x66));
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(0));
+ });
+
+ test('a matching scanChar returns true moves forward', () {
+ expect(scanner.scanChar($f), isTrue);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(1));
+ });
+
+ test('a non-matching scanChar returns false and does nothing', () {
+ expect(scanner.scanChar($x), isFalse);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(0));
+ });
+
+ test('a matching expectChar moves forward', () {
+ scanner.expectChar($f);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(1));
+ });
+
+ test('a non-matching expectChar fails', () {
+ expect(() => scanner.expectChar($x), throwsFormatException);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(0));
+ });
+
+ test('a matching scan returns true and changes the state', () {
+ expect(scanner.scan(RegExp('f(..)')), isTrue);
+ expect(scanner.lastMatch![1], equals('oo'));
+ expect(scanner.position, equals(3));
+ expect(scanner.rest, equals(' bar'));
+ });
+
+ test('a non-matching scan returns false and sets lastMatch to null', () {
+ expect(scanner.matches(RegExp('f(..)')), isTrue);
+ expect(scanner.lastMatch, isNotNull);
+
+ expect(scanner.scan(RegExp('b(..)')), isFalse);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(0));
+ expect(scanner.rest, equals('foo bar'));
+ });
+
+ test('a matching expect changes the state', () {
+ scanner.expect(RegExp('f(..)'));
+ expect(scanner.lastMatch![1], equals('oo'));
+ expect(scanner.position, equals(3));
+ expect(scanner.rest, equals(' bar'));
+ });
+
+ test(
+ 'a non-matching expect throws a FormatException and sets lastMatch to '
+ 'null', () {
+ expect(scanner.matches(RegExp('f(..)')), isTrue);
+ expect(scanner.lastMatch, isNotNull);
+
+ expect(() => scanner.expect(RegExp('b(..)')), throwsFormatException);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(0));
+ expect(scanner.rest, equals('foo bar'));
+ });
+
+ test('a matching matches returns true and only changes lastMatch', () {
+ expect(scanner.matches(RegExp('f(..)')), isTrue);
+ expect(scanner.lastMatch![1], equals('oo'));
+ expect(scanner.position, equals(0));
+ expect(scanner.rest, equals('foo bar'));
+ });
+
+ test("a non-matching matches returns false and doesn't change the state",
+ () {
+ expect(scanner.matches(RegExp('b(..)')), isFalse);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(0));
+ expect(scanner.rest, equals('foo bar'));
+ });
+
+ test('substring from the beginning returns the empty string', () {
+ expect(scanner.substring(0), isEmpty);
+ });
+
+ test('substring with a custom end returns the substring', () {
+ expect(scanner.substring(0, 3), equals('foo'));
+ });
+
+ test('substring with the string length returns the whole string', () {
+ expect(scanner.substring(0, 7), equals('foo bar'));
+ });
+
+ test('setting position to 1 moves the cursor forward', () {
+ scanner.position = 1;
+ expect(scanner.position, equals(1));
+ expect(scanner.rest, equals('oo bar'));
+
+ expect(scanner.scan(RegExp('oo.')), isTrue);
+ expect(scanner.lastMatch![0], equals('oo '));
+ expect(scanner.position, equals(4));
+ expect(scanner.rest, equals('bar'));
+ });
+
+ test('setting position beyond the string throws an ArgumentError', () {
+ expect(() {
+ scanner.position = 8;
+ }, throwsArgumentError);
+ });
+
+ test('setting position to -1 throws an ArgumentError', () {
+ expect(() {
+ scanner.position = -1;
+ }, throwsArgumentError);
+ });
+
+ test('scan accepts any Pattern', () {
+ expect(scanner.scan('foo'), isTrue);
+ expect(scanner.lastMatch![0], equals('foo'));
+ expect(scanner.position, equals(3));
+ expect(scanner.rest, equals(' bar'));
+ });
+
+ test('scans multiple times', () {
+ expect(scanner.scan(RegExp('f(..)')), isTrue);
+ expect(scanner.lastMatch![1], equals('oo'));
+ expect(scanner.position, equals(3));
+ expect(scanner.rest, equals(' bar'));
+
+ expect(scanner.scan(RegExp(' b(..)')), isTrue);
+ expect(scanner.lastMatch![1], equals('ar'));
+ expect(scanner.position, equals(7));
+ expect(scanner.rest, equals(''));
+ expect(scanner.isDone, isTrue);
+ expect(scanner.expectDone, isNot(throwsFormatException));
+ });
+ });
+
+ group('after a scan', () {
+ late StringScanner scanner;
+ setUp(() {
+ scanner = StringScanner('foo bar');
+ expect(scanner.scan('foo'), isTrue);
+ });
+
+ test('readChar returns the first character and unsets the last match', () {
+ expect(scanner.readChar(), equals($space));
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(4));
+ });
+
+ test('readCodePoint returns the first character and unsets the last match',
+ () {
+ expect(scanner.readCodePoint(), equals($space));
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(4));
+ });
+
+ test('a matching scanChar returns true and unsets the last match', () {
+ expect(scanner.scanChar($space), isTrue);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(4));
+ });
+
+ test('a matching expectChar returns true and unsets the last match', () {
+ scanner.expectChar($space);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(4));
+ });
+ });
+
+ group('at the end of a string', () {
+ late StringScanner scanner;
+ setUp(() {
+ scanner = StringScanner('foo bar');
+ expect(scanner.scan('foo bar'), isTrue);
+ });
+
+ test('is done', () {
+ expect(scanner.isDone, isTrue);
+ expect(scanner.expectDone, isNot(throwsFormatException));
+ });
+
+ test('rest is empty', () {
+ expect(scanner.rest, isEmpty);
+ });
+
+ test('position is zero', () {
+ expect(scanner.position, equals(7));
+ });
+
+ test("readChar fails and doesn't change the state", () {
+ expect(scanner.readChar, throwsFormatException);
+ expect(scanner.lastMatch, isNotNull);
+ expect(scanner.position, equals(7));
+ });
+
+ test("readCodePoint fails and doesn't change the state", () {
+ expect(scanner.readCodePoint, throwsFormatException);
+ expect(scanner.lastMatch, isNotNull);
+ expect(scanner.position, equals(7));
+ });
+
+ test("peekChar returns null and doesn't change the state", () {
+ expect(scanner.peekChar(), isNull);
+ expect(scanner.lastMatch, isNotNull);
+ expect(scanner.position, equals(7));
+ });
+
+ test("peekCodePoint returns null and doesn't change the state", () {
+ expect(scanner.peekCodePoint(), isNull);
+ expect(scanner.lastMatch, isNotNull);
+ expect(scanner.position, equals(7));
+ });
+
+ test("scanChar returns false and doesn't change the state", () {
+ expect(scanner.scanChar($f), isFalse);
+ expect(scanner.lastMatch, isNotNull);
+ expect(scanner.position, equals(7));
+ });
+
+ test("expectChar fails and doesn't change the state", () {
+ expect(() => scanner.expectChar($f), throwsFormatException);
+ expect(scanner.lastMatch, isNotNull);
+ expect(scanner.position, equals(7));
+ });
+
+ test('scan returns false and sets lastMatch to null', () {
+ expect(scanner.scan(RegExp('.')), isFalse);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(7));
+ });
+
+ test('expect throws a FormatException and sets lastMatch to null', () {
+ expect(() => scanner.expect(RegExp('.')), throwsFormatException);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(7));
+ });
+
+ test('matches returns false sets lastMatch to null', () {
+ expect(scanner.matches(RegExp('.')), isFalse);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(7));
+ });
+
+ test('substring from the beginning returns the whole string', () {
+ expect(scanner.substring(0), equals('foo bar'));
+ });
+
+ test('substring with a custom start returns a substring from there', () {
+ expect(scanner.substring(4), equals('bar'));
+ });
+
+ test('substring with a custom start and end returns that substring', () {
+ expect(scanner.substring(3, 5), equals(' b'));
+ });
+
+ test('setting position to 1 moves the cursor backward', () {
+ scanner.position = 1;
+ expect(scanner.position, equals(1));
+ expect(scanner.rest, equals('oo bar'));
+
+ expect(scanner.scan(RegExp('oo.')), isTrue);
+ expect(scanner.lastMatch![0], equals('oo '));
+ expect(scanner.position, equals(4));
+ expect(scanner.rest, equals('bar'));
+ });
+
+ test('setting and resetting position clears lastMatch', () {
+ final oldPosition = scanner.position;
+ scanner.position = 1;
+ scanner.position = oldPosition;
+ expect(scanner.lastMatch, isNull);
+ });
+
+ test('setting position beyond the string throws an ArgumentError', () {
+ expect(() {
+ scanner.position = 8;
+ }, throwsArgumentError);
+ });
+
+ test('setting position to -1 throws an ArgumentError', () {
+ expect(() {
+ scanner.position = -1;
+ }, throwsArgumentError);
+ });
+ });
+
+ group('before a surrogate pair', () {
+ final codePoint = '\uD83D\uDC6D'.runes.first;
+ const highSurrogate = 0xD83D;
+
+ late StringScanner scanner;
+ setUp(() {
+ scanner = StringScanner('foo: \uD83D\uDC6D');
+ expect(scanner.scan('foo: '), isTrue);
+ });
+
+ test('readChar returns the high surrogate and moves into the pair', () {
+ expect(scanner.readChar(), equals(highSurrogate));
+ expect(scanner.position, equals(6));
+ });
+
+ test('readCodePoint returns the code unit and moves past the pair', () {
+ expect(scanner.readCodePoint(), equals(codePoint));
+ expect(scanner.position, equals(7));
+ });
+
+ test('peekChar returns the high surrogate', () {
+ expect(scanner.peekChar(), equals(highSurrogate));
+ expect(scanner.position, equals(5));
+ });
+
+ test('peekCodePoint returns the code unit', () {
+ expect(scanner.peekCodePoint(), equals(codePoint));
+ expect(scanner.position, equals(5));
+ });
+
+ test('scanChar with the high surrogate moves into the pair', () {
+ expect(scanner.scanChar(highSurrogate), isTrue);
+ expect(scanner.position, equals(6));
+ });
+
+ test('scanChar with the code point moves past the pair', () {
+ expect(scanner.scanChar(codePoint), isTrue);
+ expect(scanner.position, equals(7));
+ });
+
+ test('expectChar with the high surrogate moves into the pair', () {
+ scanner.expectChar(highSurrogate);
+ expect(scanner.position, equals(6));
+ });
+
+ test('expectChar with the code point moves past the pair', () {
+ scanner.expectChar(codePoint);
+ expect(scanner.position, equals(7));
+ });
+ });
+
+ group('before an invalid surrogate pair', () {
+ // This surrogate pair is invalid because U+E000 is just outside the range
+ // of low surrogates. If it were interpreted as a surrogate pair anyway, the
+ // value would be U+110000, which is outside of the Unicode gamut.
+ const codePoint = 0x110000;
+ const highSurrogate = 0xD800;
+
+ late StringScanner scanner;
+ setUp(() {
+ scanner = StringScanner('foo: \uD800\uE000');
+ expect(scanner.scan('foo: '), isTrue);
+ });
+
+ test('readChar returns the high surrogate and moves into the pair', () {
+ expect(scanner.readChar(), equals(highSurrogate));
+ expect(scanner.position, equals(6));
+ });
+
+ test('readCodePoint returns the high surrogate and moves past the pair',
+ () {
+ expect(scanner.readCodePoint(), equals(highSurrogate));
+ expect(scanner.position, equals(6));
+ });
+
+ test('peekChar returns the high surrogate', () {
+ expect(scanner.peekChar(), equals(highSurrogate));
+ expect(scanner.position, equals(5));
+ });
+
+ test('peekCodePoint returns the high surrogate', () {
+ expect(scanner.peekCodePoint(), equals(highSurrogate));
+ expect(scanner.position, equals(5));
+ });
+
+ test('scanChar with the high surrogate moves into the pair', () {
+ expect(scanner.scanChar(highSurrogate), isTrue);
+ expect(scanner.position, equals(6));
+ });
+
+ test('scanChar with the fake code point returns false', () {
+ expect(scanner.scanChar(codePoint), isFalse);
+ expect(scanner.position, equals(5));
+ });
+
+ test('expectChar with the high surrogate moves into the pair', () {
+ scanner.expectChar(highSurrogate);
+ expect(scanner.position, equals(6));
+ });
+
+ test('expectChar with the fake code point fails', () {
+ expect(() => scanner.expectChar(codePoint), throwsRangeError);
+ });
+ });
+
+ group('a scanner constructed with a custom position', () {
+ test('starts scanning from that position', () {
+ final scanner = StringScanner('foo bar', position: 1);
+ expect(scanner.position, equals(1));
+ expect(scanner.rest, equals('oo bar'));
+
+ expect(scanner.scan(RegExp('oo.')), isTrue);
+ expect(scanner.lastMatch![0], equals('oo '));
+ expect(scanner.position, equals(4));
+ expect(scanner.rest, equals('bar'));
+ });
+
+ test('throws an ArgumentError if the position is -1', () {
+ expect(() => StringScanner('foo bar', position: -1), throwsArgumentError);
+ });
+
+ test('throws an ArgumentError if the position is beyond the string', () {
+ expect(() => StringScanner('foo bar', position: 8), throwsArgumentError);
+ });
+ });
+}
diff --git a/pkgs/string_scanner/test/utils.dart b/pkgs/string_scanner/test/utils.dart
new file mode 100644
index 0000000..ca03c06
--- /dev/null
+++ b/pkgs/string_scanner/test/utils.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2014, 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:string_scanner/string_scanner.dart';
+import 'package:test/test.dart';
+
+/// Returns a matcher that asserts that a closure throws a
+/// [StringScannerException] with the given [text].
+Matcher throwsStringScannerException(String text) =>
+ throwsA(const TypeMatcher<StringScannerException>()
+ .having((e) => e.span!.text, 'span.text', text));
diff --git a/pkgs/term_glyph/.gitignore b/pkgs/term_glyph/.gitignore
new file mode 100644
index 0000000..01d42c0
--- /dev/null
+++ b/pkgs/term_glyph/.gitignore
@@ -0,0 +1,4 @@
+.dart_tool/
+.pub/
+.packages
+pubspec.lock
diff --git a/pkgs/term_glyph/AUTHORS b/pkgs/term_glyph/AUTHORS
new file mode 100644
index 0000000..e8063a8
--- /dev/null
+++ b/pkgs/term_glyph/AUTHORS
@@ -0,0 +1,6 @@
+# Below is a list of people and organizations that have contributed
+# to the project. Names should be added to the list like so:
+#
+# Name/Organization <email address>
+
+Google Inc.
diff --git a/pkgs/term_glyph/CHANGELOG.md b/pkgs/term_glyph/CHANGELOG.md
new file mode 100644
index 0000000..bf8eb79
--- /dev/null
+++ b/pkgs/term_glyph/CHANGELOG.md
@@ -0,0 +1,33 @@
+## 1.2.3-wip
+
+## 1.2.2
+
+* Require Dart 3.1
+* Move to `dart-lang/tools` monorepo.
+
+## 1.2.1
+
+* Migrate to `package:lints`.
+* Populate the pubspec `repository` field.
+
+## 1.2.0
+
+* Stable release for null safety.
+* Update SDK constraints to `>=2.12.0-0 <3.0.0` based on beta release
+ guidelines.
+
+## 1.1.0
+
+* Add a `GlyphSet` class that can be used to easily choose which set of glyphs
+ to use for a particular chunk of code.
+
+* Add `asciiGlyphs`, `unicodeGlyphs`, and `glyphs` getters that provide access
+ to `GlyphSet`s.
+
+## 1.0.1
+
+* Set max SDK version to `<3.0.0`.
+
+## 1.0.0
+
+* Initial version.
diff --git a/pkgs/term_glyph/LICENSE b/pkgs/term_glyph/LICENSE
new file mode 100644
index 0000000..03af64a
--- /dev/null
+++ b/pkgs/term_glyph/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2017, 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.
diff --git a/pkgs/term_glyph/README.md b/pkgs/term_glyph/README.md
new file mode 100644
index 0000000..75039aa
--- /dev/null
+++ b/pkgs/term_glyph/README.md
@@ -0,0 +1,47 @@
+[](https://github.com/dart-lang/tools/actions/workflows/term_glyph.yaml)
+[](https://pub.dev/packages/term_glyph)
+[](https://pub.dev/packages/term_glyph/publisher)
+
+This library contains getters for useful Unicode glyphs as well as plain ASCII
+alternatives. It's intended to be used in command-line applications that may run
+in places where Unicode isn't well-supported and libraries that may be used by
+those applications.
+
+We recommend that you import this library with the prefix "glyph". For example:
+
+```dart
+import 'package:term_glyph/term_glyph.dart' as glyph;
+
+/// Formats [items] into a bulleted list, with one item per line.
+String bulletedList(List<String> items) =>
+ items.map((item) => "${glyph.bullet} $item").join("\n");
+```
+
+## ASCII Mode
+
+Some shells are unable to display Unicode characters, so this package is able to
+transparently switch its glyphs to ASCII alternatives by setting [the `ascii`
+attribute][ascii]. When this attribute is `true`, all glyphs use ASCII
+characters instead. It currently defaults to `false`, although in the future it
+may default to `true` for applications running on the Dart VM on Windows. For
+example:
+
+[ascii]: https://pub.dev/documentation/term_glyph/latest/term_glyph/ascii.html
+
+```dart
+import 'dart:io';
+
+import 'package:term_glyph/term_glyph.dart' as glyph;
+
+void main() {
+ glyph.ascii = Platform.isWindows;
+
+ // Prints "Unicode => ASCII" on Windows, "Unicode ━▶ ASCII" everywhere else.
+ print("Unicode ${glyph.rightArrow} ASCII");
+}
+```
+
+All ASCII glyphs are guaranteed to be the same number of characters as the
+corresponding Unicode glyphs, so that they line up properly when printed on a
+terminal. The specific ASCII text for a given Unicode glyph may change over
+time; this is not considered a breaking change.
diff --git a/pkgs/term_glyph/analysis_options.yaml b/pkgs/term_glyph/analysis_options.yaml
new file mode 100644
index 0000000..6d74ee9
--- /dev/null
+++ b/pkgs/term_glyph/analysis_options.yaml
@@ -0,0 +1,32 @@
+# https://dart.dev/guides/language/analysis-options
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-casts: true
+ strict-inference: true
+ strict-raw-types: true
+
+linter:
+ rules:
+ - avoid_bool_literals_in_conditional_expressions
+ - avoid_classes_with_only_static_members
+ - avoid_private_typedef_functions
+ - avoid_redundant_argument_values
+ - avoid_returning_this
+ - avoid_unused_constructor_parameters
+ - avoid_void_async
+ - cancel_subscriptions
+ - join_return_with_assignment
+ - literal_only_boolean_expressions
+ - missing_whitespace_between_adjacent_strings
+ - no_adjacent_strings_in_list
+ - no_runtimeType_toString
+ - prefer_const_declarations
+ - prefer_expression_function_bodies
+ - prefer_final_locals
+ - unnecessary_await_in_return
+ - unnecessary_breaks
+ - use_if_null_to_convert_nulls_to_bools
+ - use_raw_strings
+ - use_string_buffers
diff --git a/pkgs/term_glyph/data.csv b/pkgs/term_glyph/data.csv
new file mode 100644
index 0000000..92a72f7
--- /dev/null
+++ b/pkgs/term_glyph/data.csv
@@ -0,0 +1,85 @@
+# Miscellaneous
+bullet,•,*,A bullet point.
+
+# Arrows
+leftArrow,←,<,"A left-pointing arrow.
+
+Note that the Unicode arrow glyphs may overlap with adjacent characters in some
+terminal fonts, and should generally be surrounding by spaces."
+rightArrow,→,>,"A right-pointing arrow.
+
+Note that the Unicode arrow glyphs may overlap with adjacent characters in some
+terminal fonts, and should generally be surrounding by spaces."
+upArrow,↑,^,An upwards-pointing arrow.
+downArrow,↓,v,A downwards-pointing arrow.
+longLeftArrow,◀━,<=,A two-character left-pointing arrow.
+longRightArrow,━▶,=>,A two-character right-pointing arrow.
+
+# Box drawing characters
+
+## Normal
+horizontalLine,─,-,A horizontal line that can be used to draw a box.
+verticalLine,│,|,A vertical line that can be used to draw a box.
+topLeftCorner,┌,",",The upper left-hand corner of a box.
+topRightCorner,┐,",",The upper right-hand corner of a box.
+bottomLeftCorner,└,',The lower left-hand corner of a box.
+bottomRightCorner,┘,',The lower right-hand corner of a box.
+cross,┼,+,An intersection of vertical and horizontal box lines.
+teeUp,┴,+,A horizontal box line with a vertical line going up from the middle.
+teeDown,┬,+,A horizontal box line with a vertical line going down from the middle.
+teeLeft,┤,+,A vertical box line with a horizontal line going left from the middle.
+teeRight,├,+,A vertical box line with a horizontal line going right from the middle.
+upEnd,╵,',The top half of a vertical box line.
+downEnd,╷,",",The bottom half of a vertical box line.
+leftEnd,╴,-,The left half of a horizontal box line.
+rightEnd,╶,-,The right half of a horizontal box line.
+
+## Bold
+horizontalLineBold,━,=,A bold horizontal line that can be used to draw a box.
+verticalLineBold,┃,|,A bold vertical line that can be used to draw a box.
+topLeftCornerBold,┏,",",The bold upper left-hand corner of a box.
+topRightCornerBold,┓,",",The bold upper right-hand corner of a box.
+bottomLeftCornerBold,┗,',The bold lower left-hand corner of a box.
+bottomRightCornerBold,┛,',The bold lower right-hand corner of a box.
+crossBold,╋,+,An intersection of bold vertical and horizontal box lines.
+teeUpBold,┻,+,A bold horizontal box line with a vertical line going up from the middle.
+teeDownBold,┳,+,A bold horizontal box line with a vertical line going down from the middle.
+teeLeftBold,┫,+,A bold vertical box line with a horizontal line going left from the middle.
+teeRightBold,┣,+,A bold vertical box line with a horizontal line going right from the middle.
+upEndBold,╹,',The top half of a bold vertical box line.
+downEndBold,╻,",",The bottom half of a bold vertical box line.
+leftEndBold,╸,-,The left half of a bold horizontal box line.
+rightEndBold,╺,-,The right half of a bold horizontal box line.
+
+## Double
+horizontalLineDouble,═,=,A double horizontal line that can be used to draw a box.
+verticalLineDouble,║,|,A double vertical line that can be used to draw a box.
+topLeftCornerDouble,╔,",",The double upper left-hand corner of a box.
+topRightCornerDouble,╗,",",The double upper right-hand corner of a box.
+bottomLeftCornerDouble,╚,"""",The double lower left-hand corner of a box.
+bottomRightCornerDouble,╝,"""",The double lower right-hand corner of a box.
+crossDouble,╬,+,An intersection of double vertical and horizontal box lines.
+teeUpDouble,╩,+,A double horizontal box line with a vertical line going up from the middle.
+teeDownDouble,╦,+,A double horizontal box line with a vertical line going down from the middle.
+teeLeftDouble,╣,+,A double vertical box line with a horizontal line going left from the middle.
+teeRightDouble,╠,+,A double vertical box line with a horizontal line going right from the middle.
+
+## Dashed
+
+### Double
+horizontalLineDoubleDash,╌,-,A dashed horizontal line that can be used to draw a box.
+horizontalLineDoubleDashBold,╍,-,A bold dashed horizontal line that can be used to draw a box.
+verticalLineDoubleDash,╎,|,A dashed vertical line that can be used to draw a box.
+verticalLineDoubleDashBold,╏,|,A bold dashed vertical line that can be used to draw a box.
+
+### Triple
+horizontalLineTripleDash,┄,-,A dashed horizontal line that can be used to draw a box.
+horizontalLineTripleDashBold,┅,-,A bold dashed horizontal line that can be used to draw a box.
+verticalLineTripleDash,┆,|,A dashed vertical line that can be used to draw a box.
+verticalLineTripleDashBold,┇,|,A bold dashed vertical line that can be used to draw a box.
+
+### Quadruple
+horizontalLineQuadrupleDash,┈,-,A dashed horizontal line that can be used to draw a box.
+horizontalLineQuadrupleDashBold,┉,-,A bold dashed horizontal line that can be used to draw a box.
+verticalLineQuadrupleDash,┊,|,A dashed vertical line that can be used to draw a box.
+verticalLineQuadrupleDashBold,┋,|,A bold dashed vertical line that can be used to draw a box.
diff --git a/pkgs/term_glyph/lib/src/generated/ascii_glyph_set.dart b/pkgs/term_glyph/lib/src/generated/ascii_glyph_set.dart
new file mode 100644
index 0000000..08534a0
--- /dev/null
+++ b/pkgs/term_glyph/lib/src/generated/ascii_glyph_set.dart
@@ -0,0 +1,139 @@
+// 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.
+
+// Don't modify this file by hand! It's generated by tool/generate.dart.
+
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'glyph_set.dart';
+
+/// A [GlyphSet] that includes only ASCII glyphs.
+class AsciiGlyphSet implements GlyphSet {
+ const AsciiGlyphSet();
+
+ /// Returns [glyph] if `this` supports Unicode glyphs and [alternative]
+ /// otherwise.
+ @override
+ String glyphOrAscii(String glyph, String alternative) => alternative;
+ @override
+ String get bullet => '*';
+ @override
+ String get leftArrow => '<';
+ @override
+ String get rightArrow => '>';
+ @override
+ String get upArrow => '^';
+ @override
+ String get downArrow => 'v';
+ @override
+ String get longLeftArrow => '<=';
+ @override
+ String get longRightArrow => '=>';
+ @override
+ String get horizontalLine => '-';
+ @override
+ String get verticalLine => '|';
+ @override
+ String get topLeftCorner => ',';
+ @override
+ String get topRightCorner => ',';
+ @override
+ String get bottomLeftCorner => "'";
+ @override
+ String get bottomRightCorner => "'";
+ @override
+ String get cross => '+';
+ @override
+ String get teeUp => '+';
+ @override
+ String get teeDown => '+';
+ @override
+ String get teeLeft => '+';
+ @override
+ String get teeRight => '+';
+ @override
+ String get upEnd => "'";
+ @override
+ String get downEnd => ',';
+ @override
+ String get leftEnd => '-';
+ @override
+ String get rightEnd => '-';
+ @override
+ String get horizontalLineBold => '=';
+ @override
+ String get verticalLineBold => '|';
+ @override
+ String get topLeftCornerBold => ',';
+ @override
+ String get topRightCornerBold => ',';
+ @override
+ String get bottomLeftCornerBold => "'";
+ @override
+ String get bottomRightCornerBold => "'";
+ @override
+ String get crossBold => '+';
+ @override
+ String get teeUpBold => '+';
+ @override
+ String get teeDownBold => '+';
+ @override
+ String get teeLeftBold => '+';
+ @override
+ String get teeRightBold => '+';
+ @override
+ String get upEndBold => "'";
+ @override
+ String get downEndBold => ',';
+ @override
+ String get leftEndBold => '-';
+ @override
+ String get rightEndBold => '-';
+ @override
+ String get horizontalLineDouble => '=';
+ @override
+ String get verticalLineDouble => '|';
+ @override
+ String get topLeftCornerDouble => ',';
+ @override
+ String get topRightCornerDouble => ',';
+ @override
+ String get bottomLeftCornerDouble => '"';
+ @override
+ String get bottomRightCornerDouble => '"';
+ @override
+ String get crossDouble => '+';
+ @override
+ String get teeUpDouble => '+';
+ @override
+ String get teeDownDouble => '+';
+ @override
+ String get teeLeftDouble => '+';
+ @override
+ String get teeRightDouble => '+';
+ @override
+ String get horizontalLineDoubleDash => '-';
+ @override
+ String get horizontalLineDoubleDashBold => '-';
+ @override
+ String get verticalLineDoubleDash => '|';
+ @override
+ String get verticalLineDoubleDashBold => '|';
+ @override
+ String get horizontalLineTripleDash => '-';
+ @override
+ String get horizontalLineTripleDashBold => '-';
+ @override
+ String get verticalLineTripleDash => '|';
+ @override
+ String get verticalLineTripleDashBold => '|';
+ @override
+ String get horizontalLineQuadrupleDash => '-';
+ @override
+ String get horizontalLineQuadrupleDashBold => '-';
+ @override
+ String get verticalLineQuadrupleDash => '|';
+ @override
+ String get verticalLineQuadrupleDashBold => '|';
+}
diff --git a/pkgs/term_glyph/lib/src/generated/glyph_set.dart b/pkgs/term_glyph/lib/src/generated/glyph_set.dart
new file mode 100644
index 0000000..c8cc4a9
--- /dev/null
+++ b/pkgs/term_glyph/lib/src/generated/glyph_set.dart
@@ -0,0 +1,222 @@
+// 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.
+
+// Don't modify this file by hand! It's generated by tool/generate.dart.
+
+// ignore_for_file: lines_longer_than_80_chars
+
+/// A class that provides access to every configurable glyph.
+///
+/// This is provided as a class so that individual chunks of code can choose
+/// between `ascii` and `unicode` glyphs. For example:
+///
+/// ```dart
+/// import 'package:term_glyph/term_glyph.dart' as glyph;
+///
+/// /// Adds a vertical line to the left of [text].
+/// ///
+/// /// If [unicode] is `true`, this uses Unicode for the line. If it's
+/// /// `false`, this uses plain ASCII characters. If it's `null`, it
+/// /// defaults to [glyph.ascii].
+/// void addVerticalLine(String text, {bool unicode}) {
+/// var glyphs =
+/// (unicode ?? !glyph.ascii) ? glyph.unicodeGlyphs : glyph.asciiGlyphs;
+///
+/// return text
+/// .split('\n')
+/// .map((line) => '${glyphs.verticalLine} $line')
+/// .join('\n');
+/// }
+/// ```
+abstract class GlyphSet {
+ /// Returns [glyph] if `this` supports Unicode glyphs and [alternative]
+ /// otherwise.
+ String glyphOrAscii(String glyph, String alternative);
+
+ /// A bullet point.
+ String get bullet;
+
+ /// A left-pointing arrow.
+ ///
+ /// Note that the Unicode arrow glyphs may overlap with adjacent characters in some
+ /// terminal fonts, and should generally be surrounding by spaces.
+ String get leftArrow;
+
+ /// A right-pointing arrow.
+ ///
+ /// Note that the Unicode arrow glyphs may overlap with adjacent characters in some
+ /// terminal fonts, and should generally be surrounding by spaces.
+ String get rightArrow;
+
+ /// An upwards-pointing arrow.
+ String get upArrow;
+
+ /// A downwards-pointing arrow.
+ String get downArrow;
+
+ /// A two-character left-pointing arrow.
+ String get longLeftArrow;
+
+ /// A two-character right-pointing arrow.
+ String get longRightArrow;
+
+ /// A horizontal line that can be used to draw a box.
+ String get horizontalLine;
+
+ /// A vertical line that can be used to draw a box.
+ String get verticalLine;
+
+ /// The upper left-hand corner of a box.
+ String get topLeftCorner;
+
+ /// The upper right-hand corner of a box.
+ String get topRightCorner;
+
+ /// The lower left-hand corner of a box.
+ String get bottomLeftCorner;
+
+ /// The lower right-hand corner of a box.
+ String get bottomRightCorner;
+
+ /// An intersection of vertical and horizontal box lines.
+ String get cross;
+
+ /// A horizontal box line with a vertical line going up from the middle.
+ String get teeUp;
+
+ /// A horizontal box line with a vertical line going down from the middle.
+ String get teeDown;
+
+ /// A vertical box line with a horizontal line going left from the middle.
+ String get teeLeft;
+
+ /// A vertical box line with a horizontal line going right from the middle.
+ String get teeRight;
+
+ /// The top half of a vertical box line.
+ String get upEnd;
+
+ /// The bottom half of a vertical box line.
+ String get downEnd;
+
+ /// The left half of a horizontal box line.
+ String get leftEnd;
+
+ /// The right half of a horizontal box line.
+ String get rightEnd;
+
+ /// A bold horizontal line that can be used to draw a box.
+ String get horizontalLineBold;
+
+ /// A bold vertical line that can be used to draw a box.
+ String get verticalLineBold;
+
+ /// The bold upper left-hand corner of a box.
+ String get topLeftCornerBold;
+
+ /// The bold upper right-hand corner of a box.
+ String get topRightCornerBold;
+
+ /// The bold lower left-hand corner of a box.
+ String get bottomLeftCornerBold;
+
+ /// The bold lower right-hand corner of a box.
+ String get bottomRightCornerBold;
+
+ /// An intersection of bold vertical and horizontal box lines.
+ String get crossBold;
+
+ /// A bold horizontal box line with a vertical line going up from the middle.
+ String get teeUpBold;
+
+ /// A bold horizontal box line with a vertical line going down from the middle.
+ String get teeDownBold;
+
+ /// A bold vertical box line with a horizontal line going left from the middle.
+ String get teeLeftBold;
+
+ /// A bold vertical box line with a horizontal line going right from the middle.
+ String get teeRightBold;
+
+ /// The top half of a bold vertical box line.
+ String get upEndBold;
+
+ /// The bottom half of a bold vertical box line.
+ String get downEndBold;
+
+ /// The left half of a bold horizontal box line.
+ String get leftEndBold;
+
+ /// The right half of a bold horizontal box line.
+ String get rightEndBold;
+
+ /// A double horizontal line that can be used to draw a box.
+ String get horizontalLineDouble;
+
+ /// A double vertical line that can be used to draw a box.
+ String get verticalLineDouble;
+
+ /// The double upper left-hand corner of a box.
+ String get topLeftCornerDouble;
+
+ /// The double upper right-hand corner of a box.
+ String get topRightCornerDouble;
+
+ /// The double lower left-hand corner of a box.
+ String get bottomLeftCornerDouble;
+
+ /// The double lower right-hand corner of a box.
+ String get bottomRightCornerDouble;
+
+ /// An intersection of double vertical and horizontal box lines.
+ String get crossDouble;
+
+ /// A double horizontal box line with a vertical line going up from the middle.
+ String get teeUpDouble;
+
+ /// A double horizontal box line with a vertical line going down from the middle.
+ String get teeDownDouble;
+
+ /// A double vertical box line with a horizontal line going left from the middle.
+ String get teeLeftDouble;
+
+ /// A double vertical box line with a horizontal line going right from the middle.
+ String get teeRightDouble;
+
+ /// A dashed horizontal line that can be used to draw a box.
+ String get horizontalLineDoubleDash;
+
+ /// A bold dashed horizontal line that can be used to draw a box.
+ String get horizontalLineDoubleDashBold;
+
+ /// A dashed vertical line that can be used to draw a box.
+ String get verticalLineDoubleDash;
+
+ /// A bold dashed vertical line that can be used to draw a box.
+ String get verticalLineDoubleDashBold;
+
+ /// A dashed horizontal line that can be used to draw a box.
+ String get horizontalLineTripleDash;
+
+ /// A bold dashed horizontal line that can be used to draw a box.
+ String get horizontalLineTripleDashBold;
+
+ /// A dashed vertical line that can be used to draw a box.
+ String get verticalLineTripleDash;
+
+ /// A bold dashed vertical line that can be used to draw a box.
+ String get verticalLineTripleDashBold;
+
+ /// A dashed horizontal line that can be used to draw a box.
+ String get horizontalLineQuadrupleDash;
+
+ /// A bold dashed horizontal line that can be used to draw a box.
+ String get horizontalLineQuadrupleDashBold;
+
+ /// A dashed vertical line that can be used to draw a box.
+ String get verticalLineQuadrupleDash;
+
+ /// A bold dashed vertical line that can be used to draw a box.
+ String get verticalLineQuadrupleDashBold;
+}
diff --git a/pkgs/term_glyph/lib/src/generated/top_level.dart b/pkgs/term_glyph/lib/src/generated/top_level.dart
new file mode 100644
index 0000000..848ef6d
--- /dev/null
+++ b/pkgs/term_glyph/lib/src/generated/top_level.dart
@@ -0,0 +1,382 @@
+// 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.
+
+// Don't modify this file by hand! It's generated by tool/generate.dart.
+
+// ignore_for_file: lines_longer_than_80_chars
+
+import '../../term_glyph.dart' as glyph;
+
+/// A bullet point.
+///
+/// If [glyph.ascii] is `false`, this is "•". If it's `true`, this is
+/// "*" instead.
+String get bullet => glyph.glyphs.bullet;
+
+/// A left-pointing arrow.
+///
+/// Note that the Unicode arrow glyphs may overlap with adjacent characters in some
+/// terminal fonts, and should generally be surrounding by spaces.
+///
+/// If [glyph.ascii] is `false`, this is "←". If it's `true`, this is
+/// "<" instead.
+String get leftArrow => glyph.glyphs.leftArrow;
+
+/// A right-pointing arrow.
+///
+/// Note that the Unicode arrow glyphs may overlap with adjacent characters in some
+/// terminal fonts, and should generally be surrounding by spaces.
+///
+/// If [glyph.ascii] is `false`, this is "→". If it's `true`, this is
+/// ">" instead.
+String get rightArrow => glyph.glyphs.rightArrow;
+
+/// An upwards-pointing arrow.
+///
+/// If [glyph.ascii] is `false`, this is "↑". If it's `true`, this is
+/// "^" instead.
+String get upArrow => glyph.glyphs.upArrow;
+
+/// A downwards-pointing arrow.
+///
+/// If [glyph.ascii] is `false`, this is "↓". If it's `true`, this is
+/// "v" instead.
+String get downArrow => glyph.glyphs.downArrow;
+
+/// A two-character left-pointing arrow.
+///
+/// If [glyph.ascii] is `false`, this is "◀━". If it's `true`, this is
+/// "<=" instead.
+String get longLeftArrow => glyph.glyphs.longLeftArrow;
+
+/// A two-character right-pointing arrow.
+///
+/// If [glyph.ascii] is `false`, this is "━▶". If it's `true`, this is
+/// "=>" instead.
+String get longRightArrow => glyph.glyphs.longRightArrow;
+
+/// A horizontal line that can be used to draw a box.
+///
+/// If [glyph.ascii] is `false`, this is "─". If it's `true`, this is
+/// "-" instead.
+String get horizontalLine => glyph.glyphs.horizontalLine;
+
+/// A vertical line that can be used to draw a box.
+///
+/// If [glyph.ascii] is `false`, this is "│". If it's `true`, this is
+/// "|" instead.
+String get verticalLine => glyph.glyphs.verticalLine;
+
+/// The upper left-hand corner of a box.
+///
+/// If [glyph.ascii] is `false`, this is "┌". If it's `true`, this is
+/// "," instead.
+String get topLeftCorner => glyph.glyphs.topLeftCorner;
+
+/// The upper right-hand corner of a box.
+///
+/// If [glyph.ascii] is `false`, this is "┐". If it's `true`, this is
+/// "," instead.
+String get topRightCorner => glyph.glyphs.topRightCorner;
+
+/// The lower left-hand corner of a box.
+///
+/// If [glyph.ascii] is `false`, this is "└". If it's `true`, this is
+/// "'" instead.
+String get bottomLeftCorner => glyph.glyphs.bottomLeftCorner;
+
+/// The lower right-hand corner of a box.
+///
+/// If [glyph.ascii] is `false`, this is "┘". If it's `true`, this is
+/// "'" instead.
+String get bottomRightCorner => glyph.glyphs.bottomRightCorner;
+
+/// An intersection of vertical and horizontal box lines.
+///
+/// If [glyph.ascii] is `false`, this is "┼". If it's `true`, this is
+/// "+" instead.
+String get cross => glyph.glyphs.cross;
+
+/// A horizontal box line with a vertical line going up from the middle.
+///
+/// If [glyph.ascii] is `false`, this is "┴". If it's `true`, this is
+/// "+" instead.
+String get teeUp => glyph.glyphs.teeUp;
+
+/// A horizontal box line with a vertical line going down from the middle.
+///
+/// If [glyph.ascii] is `false`, this is "┬". If it's `true`, this is
+/// "+" instead.
+String get teeDown => glyph.glyphs.teeDown;
+
+/// A vertical box line with a horizontal line going left from the middle.
+///
+/// If [glyph.ascii] is `false`, this is "┤". If it's `true`, this is
+/// "+" instead.
+String get teeLeft => glyph.glyphs.teeLeft;
+
+/// A vertical box line with a horizontal line going right from the middle.
+///
+/// If [glyph.ascii] is `false`, this is "├". If it's `true`, this is
+/// "+" instead.
+String get teeRight => glyph.glyphs.teeRight;
+
+/// The top half of a vertical box line.
+///
+/// If [glyph.ascii] is `false`, this is "╵". If it's `true`, this is
+/// "'" instead.
+String get upEnd => glyph.glyphs.upEnd;
+
+/// The bottom half of a vertical box line.
+///
+/// If [glyph.ascii] is `false`, this is "╷". If it's `true`, this is
+/// "," instead.
+String get downEnd => glyph.glyphs.downEnd;
+
+/// The left half of a horizontal box line.
+///
+/// If [glyph.ascii] is `false`, this is "╴". If it's `true`, this is
+/// "-" instead.
+String get leftEnd => glyph.glyphs.leftEnd;
+
+/// The right half of a horizontal box line.
+///
+/// If [glyph.ascii] is `false`, this is "╶". If it's `true`, this is
+/// "-" instead.
+String get rightEnd => glyph.glyphs.rightEnd;
+
+/// A bold horizontal line that can be used to draw a box.
+///
+/// If [glyph.ascii] is `false`, this is "━". If it's `true`, this is
+/// "=" instead.
+String get horizontalLineBold => glyph.glyphs.horizontalLineBold;
+
+/// A bold vertical line that can be used to draw a box.
+///
+/// If [glyph.ascii] is `false`, this is "┃". If it's `true`, this is
+/// "|" instead.
+String get verticalLineBold => glyph.glyphs.verticalLineBold;
+
+/// The bold upper left-hand corner of a box.
+///
+/// If [glyph.ascii] is `false`, this is "┏". If it's `true`, this is
+/// "," instead.
+String get topLeftCornerBold => glyph.glyphs.topLeftCornerBold;
+
+/// The bold upper right-hand corner of a box.
+///
+/// If [glyph.ascii] is `false`, this is "┓". If it's `true`, this is
+/// "," instead.
+String get topRightCornerBold => glyph.glyphs.topRightCornerBold;
+
+/// The bold lower left-hand corner of a box.
+///
+/// If [glyph.ascii] is `false`, this is "┗". If it's `true`, this is
+/// "'" instead.
+String get bottomLeftCornerBold => glyph.glyphs.bottomLeftCornerBold;
+
+/// The bold lower right-hand corner of a box.
+///
+/// If [glyph.ascii] is `false`, this is "┛". If it's `true`, this is
+/// "'" instead.
+String get bottomRightCornerBold => glyph.glyphs.bottomRightCornerBold;
+
+/// An intersection of bold vertical and horizontal box lines.
+///
+/// If [glyph.ascii] is `false`, this is "╋". If it's `true`, this is
+/// "+" instead.
+String get crossBold => glyph.glyphs.crossBold;
+
+/// A bold horizontal box line with a vertical line going up from the middle.
+///
+/// If [glyph.ascii] is `false`, this is "┻". If it's `true`, this is
+/// "+" instead.
+String get teeUpBold => glyph.glyphs.teeUpBold;
+
+/// A bold horizontal box line with a vertical line going down from the middle.
+///
+/// If [glyph.ascii] is `false`, this is "┳". If it's `true`, this is
+/// "+" instead.
+String get teeDownBold => glyph.glyphs.teeDownBold;
+
+/// A bold vertical box line with a horizontal line going left from the middle.
+///
+/// If [glyph.ascii] is `false`, this is "┫". If it's `true`, this is
+/// "+" instead.
+String get teeLeftBold => glyph.glyphs.teeLeftBold;
+
+/// A bold vertical box line with a horizontal line going right from the middle.
+///
+/// If [glyph.ascii] is `false`, this is "┣". If it's `true`, this is
+/// "+" instead.
+String get teeRightBold => glyph.glyphs.teeRightBold;
+
+/// The top half of a bold vertical box line.
+///
+/// If [glyph.ascii] is `false`, this is "╹". If it's `true`, this is
+/// "'" instead.
+String get upEndBold => glyph.glyphs.upEndBold;
+
+/// The bottom half of a bold vertical box line.
+///
+/// If [glyph.ascii] is `false`, this is "╻". If it's `true`, this is
+/// "," instead.
+String get downEndBold => glyph.glyphs.downEndBold;
+
+/// The left half of a bold horizontal box line.
+///
+/// If [glyph.ascii] is `false`, this is "╸". If it's `true`, this is
+/// "-" instead.
+String get leftEndBold => glyph.glyphs.leftEndBold;
+
+/// The right half of a bold horizontal box line.
+///
+/// If [glyph.ascii] is `false`, this is "╺". If it's `true`, this is
+/// "-" instead.
+String get rightEndBold => glyph.glyphs.rightEndBold;
+
+/// A double horizontal line that can be used to draw a box.
+///
+/// If [glyph.ascii] is `false`, this is "═". If it's `true`, this is
+/// "=" instead.
+String get horizontalLineDouble => glyph.glyphs.horizontalLineDouble;
+
+/// A double vertical line that can be used to draw a box.
+///
+/// If [glyph.ascii] is `false`, this is "║". If it's `true`, this is
+/// "|" instead.
+String get verticalLineDouble => glyph.glyphs.verticalLineDouble;
+
+/// The double upper left-hand corner of a box.
+///
+/// If [glyph.ascii] is `false`, this is "╔". If it's `true`, this is
+/// "," instead.
+String get topLeftCornerDouble => glyph.glyphs.topLeftCornerDouble;
+
+/// The double upper right-hand corner of a box.
+///
+/// If [glyph.ascii] is `false`, this is "╗". If it's `true`, this is
+/// "," instead.
+String get topRightCornerDouble => glyph.glyphs.topRightCornerDouble;
+
+/// The double lower left-hand corner of a box.
+///
+/// If [glyph.ascii] is `false`, this is "╚". If it's `true`, this is
+/// """ instead.
+String get bottomLeftCornerDouble => glyph.glyphs.bottomLeftCornerDouble;
+
+/// The double lower right-hand corner of a box.
+///
+/// If [glyph.ascii] is `false`, this is "╝". If it's `true`, this is
+/// """ instead.
+String get bottomRightCornerDouble => glyph.glyphs.bottomRightCornerDouble;
+
+/// An intersection of double vertical and horizontal box lines.
+///
+/// If [glyph.ascii] is `false`, this is "╬". If it's `true`, this is
+/// "+" instead.
+String get crossDouble => glyph.glyphs.crossDouble;
+
+/// A double horizontal box line with a vertical line going up from the middle.
+///
+/// If [glyph.ascii] is `false`, this is "╩". If it's `true`, this is
+/// "+" instead.
+String get teeUpDouble => glyph.glyphs.teeUpDouble;
+
+/// A double horizontal box line with a vertical line going down from the middle.
+///
+/// If [glyph.ascii] is `false`, this is "╦". If it's `true`, this is
+/// "+" instead.
+String get teeDownDouble => glyph.glyphs.teeDownDouble;
+
+/// A double vertical box line with a horizontal line going left from the middle.
+///
+/// If [glyph.ascii] is `false`, this is "╣". If it's `true`, this is
+/// "+" instead.
+String get teeLeftDouble => glyph.glyphs.teeLeftDouble;
+
+/// A double vertical box line with a horizontal line going right from the middle.
+///
+/// If [glyph.ascii] is `false`, this is "╠". If it's `true`, this is
+/// "+" instead.
+String get teeRightDouble => glyph.glyphs.teeRightDouble;
+
+/// A dashed horizontal line that can be used to draw a box.
+///
+/// If [glyph.ascii] is `false`, this is "╌". If it's `true`, this is
+/// "-" instead.
+String get horizontalLineDoubleDash => glyph.glyphs.horizontalLineDoubleDash;
+
+/// A bold dashed horizontal line that can be used to draw a box.
+///
+/// If [glyph.ascii] is `false`, this is "╍". If it's `true`, this is
+/// "-" instead.
+String get horizontalLineDoubleDashBold =>
+ glyph.glyphs.horizontalLineDoubleDashBold;
+
+/// A dashed vertical line that can be used to draw a box.
+///
+/// If [glyph.ascii] is `false`, this is "╎". If it's `true`, this is
+/// "|" instead.
+String get verticalLineDoubleDash => glyph.glyphs.verticalLineDoubleDash;
+
+/// A bold dashed vertical line that can be used to draw a box.
+///
+/// If [glyph.ascii] is `false`, this is "╏". If it's `true`, this is
+/// "|" instead.
+String get verticalLineDoubleDashBold =>
+ glyph.glyphs.verticalLineDoubleDashBold;
+
+/// A dashed horizontal line that can be used to draw a box.
+///
+/// If [glyph.ascii] is `false`, this is "┄". If it's `true`, this is
+/// "-" instead.
+String get horizontalLineTripleDash => glyph.glyphs.horizontalLineTripleDash;
+
+/// A bold dashed horizontal line that can be used to draw a box.
+///
+/// If [glyph.ascii] is `false`, this is "┅". If it's `true`, this is
+/// "-" instead.
+String get horizontalLineTripleDashBold =>
+ glyph.glyphs.horizontalLineTripleDashBold;
+
+/// A dashed vertical line that can be used to draw a box.
+///
+/// If [glyph.ascii] is `false`, this is "┆". If it's `true`, this is
+/// "|" instead.
+String get verticalLineTripleDash => glyph.glyphs.verticalLineTripleDash;
+
+/// A bold dashed vertical line that can be used to draw a box.
+///
+/// If [glyph.ascii] is `false`, this is "┇". If it's `true`, this is
+/// "|" instead.
+String get verticalLineTripleDashBold =>
+ glyph.glyphs.verticalLineTripleDashBold;
+
+/// A dashed horizontal line that can be used to draw a box.
+///
+/// If [glyph.ascii] is `false`, this is "┈". If it's `true`, this is
+/// "-" instead.
+String get horizontalLineQuadrupleDash =>
+ glyph.glyphs.horizontalLineQuadrupleDash;
+
+/// A bold dashed horizontal line that can be used to draw a box.
+///
+/// If [glyph.ascii] is `false`, this is "┉". If it's `true`, this is
+/// "-" instead.
+String get horizontalLineQuadrupleDashBold =>
+ glyph.glyphs.horizontalLineQuadrupleDashBold;
+
+/// A dashed vertical line that can be used to draw a box.
+///
+/// If [glyph.ascii] is `false`, this is "┊". If it's `true`, this is
+/// "|" instead.
+String get verticalLineQuadrupleDash => glyph.glyphs.verticalLineQuadrupleDash;
+
+/// A bold dashed vertical line that can be used to draw a box.
+///
+/// If [glyph.ascii] is `false`, this is "┋". If it's `true`, this is
+/// "|" instead.
+String get verticalLineQuadrupleDashBold =>
+ glyph.glyphs.verticalLineQuadrupleDashBold;
diff --git a/pkgs/term_glyph/lib/src/generated/unicode_glyph_set.dart b/pkgs/term_glyph/lib/src/generated/unicode_glyph_set.dart
new file mode 100644
index 0000000..7264e6d
--- /dev/null
+++ b/pkgs/term_glyph/lib/src/generated/unicode_glyph_set.dart
@@ -0,0 +1,139 @@
+// 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.
+
+// Don't modify this file by hand! It's generated by tool/generate.dart.
+
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'glyph_set.dart';
+
+/// A [GlyphSet] that includes only Unicode glyphs.
+class UnicodeGlyphSet implements GlyphSet {
+ const UnicodeGlyphSet();
+
+ /// Returns [glyph] if `this` supports Unicode glyphs and [alternative]
+ /// otherwise.
+ @override
+ String glyphOrAscii(String glyph, String alternative) => glyph;
+ @override
+ String get bullet => '•';
+ @override
+ String get leftArrow => '←';
+ @override
+ String get rightArrow => '→';
+ @override
+ String get upArrow => '↑';
+ @override
+ String get downArrow => '↓';
+ @override
+ String get longLeftArrow => '◀━';
+ @override
+ String get longRightArrow => '━▶';
+ @override
+ String get horizontalLine => '─';
+ @override
+ String get verticalLine => '│';
+ @override
+ String get topLeftCorner => '┌';
+ @override
+ String get topRightCorner => '┐';
+ @override
+ String get bottomLeftCorner => '└';
+ @override
+ String get bottomRightCorner => '┘';
+ @override
+ String get cross => '┼';
+ @override
+ String get teeUp => '┴';
+ @override
+ String get teeDown => '┬';
+ @override
+ String get teeLeft => '┤';
+ @override
+ String get teeRight => '├';
+ @override
+ String get upEnd => '╵';
+ @override
+ String get downEnd => '╷';
+ @override
+ String get leftEnd => '╴';
+ @override
+ String get rightEnd => '╶';
+ @override
+ String get horizontalLineBold => '━';
+ @override
+ String get verticalLineBold => '┃';
+ @override
+ String get topLeftCornerBold => '┏';
+ @override
+ String get topRightCornerBold => '┓';
+ @override
+ String get bottomLeftCornerBold => '┗';
+ @override
+ String get bottomRightCornerBold => '┛';
+ @override
+ String get crossBold => '╋';
+ @override
+ String get teeUpBold => '┻';
+ @override
+ String get teeDownBold => '┳';
+ @override
+ String get teeLeftBold => '┫';
+ @override
+ String get teeRightBold => '┣';
+ @override
+ String get upEndBold => '╹';
+ @override
+ String get downEndBold => '╻';
+ @override
+ String get leftEndBold => '╸';
+ @override
+ String get rightEndBold => '╺';
+ @override
+ String get horizontalLineDouble => '═';
+ @override
+ String get verticalLineDouble => '║';
+ @override
+ String get topLeftCornerDouble => '╔';
+ @override
+ String get topRightCornerDouble => '╗';
+ @override
+ String get bottomLeftCornerDouble => '╚';
+ @override
+ String get bottomRightCornerDouble => '╝';
+ @override
+ String get crossDouble => '╬';
+ @override
+ String get teeUpDouble => '╩';
+ @override
+ String get teeDownDouble => '╦';
+ @override
+ String get teeLeftDouble => '╣';
+ @override
+ String get teeRightDouble => '╠';
+ @override
+ String get horizontalLineDoubleDash => '╌';
+ @override
+ String get horizontalLineDoubleDashBold => '╍';
+ @override
+ String get verticalLineDoubleDash => '╎';
+ @override
+ String get verticalLineDoubleDashBold => '╏';
+ @override
+ String get horizontalLineTripleDash => '┄';
+ @override
+ String get horizontalLineTripleDashBold => '┅';
+ @override
+ String get verticalLineTripleDash => '┆';
+ @override
+ String get verticalLineTripleDashBold => '┇';
+ @override
+ String get horizontalLineQuadrupleDash => '┈';
+ @override
+ String get horizontalLineQuadrupleDashBold => '┉';
+ @override
+ String get verticalLineQuadrupleDash => '┊';
+ @override
+ String get verticalLineQuadrupleDashBold => '┋';
+}
diff --git a/pkgs/term_glyph/lib/term_glyph.dart b/pkgs/term_glyph/lib/term_glyph.dart
new file mode 100644
index 0000000..9f2b422
--- /dev/null
+++ b/pkgs/term_glyph/lib/term_glyph.dart
@@ -0,0 +1,37 @@
+// Copyright (c) 2017, 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 'src/generated/ascii_glyph_set.dart';
+import 'src/generated/glyph_set.dart';
+import 'src/generated/unicode_glyph_set.dart';
+
+export 'src/generated/glyph_set.dart';
+export 'src/generated/top_level.dart';
+
+/// A [GlyphSet] that always returns ASCII glyphs.
+const GlyphSet asciiGlyphs = AsciiGlyphSet();
+
+/// A [GlyphSet] that always returns Unicode glyphs.
+const GlyphSet unicodeGlyphs = UnicodeGlyphSet();
+
+/// Returns [asciiGlyphs] if [ascii] is `true` or [unicodeGlyphs] otherwise.
+///
+/// Returns [unicodeGlyphs] by default.
+GlyphSet get glyphs => _glyphs;
+GlyphSet _glyphs = unicodeGlyphs;
+
+/// Whether the glyph getters return plain ASCII, as opposed to Unicode
+/// characters or sequences.
+///
+/// Defaults to `false`.
+bool get ascii => glyphs == asciiGlyphs;
+
+set ascii(bool value) {
+ _glyphs = value ? asciiGlyphs : unicodeGlyphs;
+}
+
+/// Returns [glyph] if Unicode glyph are allowed, and [alternative] if they
+/// aren't.
+String glyphOrAscii(String glyph, String alternative) =>
+ glyphs.glyphOrAscii(glyph, alternative);
diff --git a/pkgs/term_glyph/pubspec.yaml b/pkgs/term_glyph/pubspec.yaml
new file mode 100644
index 0000000..bac16f1
--- /dev/null
+++ b/pkgs/term_glyph/pubspec.yaml
@@ -0,0 +1,12 @@
+name: term_glyph
+version: 1.2.3-wip
+description: Useful Unicode glyphs and ASCII substitutes.
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/term_glyph
+
+environment:
+ sdk: ^3.1.0
+
+dev_dependencies:
+ csv: ^6.0.0
+ dart_flutter_team_lints: ^3.0.0
+ test: ^1.16.6
diff --git a/pkgs/term_glyph/test/symbol_test.dart b/pkgs/term_glyph/test/symbol_test.dart
new file mode 100644
index 0000000..b3b4d09
--- /dev/null
+++ b/pkgs/term_glyph/test/symbol_test.dart
@@ -0,0 +1,60 @@
+// Copyright (c) 2017, 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:term_glyph/term_glyph.dart' as glyph;
+import 'package:test/test.dart';
+
+void main() {
+ group('with ascii = false', () {
+ setUpAll(() {
+ glyph.ascii = false;
+ });
+
+ test('glyph getters return Unicode versions', () {
+ expect(glyph.topLeftCorner, equals('┌'));
+ expect(glyph.teeUpBold, equals('┻'));
+ expect(glyph.longLeftArrow, equals('◀━'));
+ });
+
+ test('glyphOrAscii returns the first argument', () {
+ expect(glyph.glyphOrAscii('A', 'B'), equals('A'));
+ });
+
+ test('glyphs returns unicodeGlyphs', () {
+ expect(glyph.glyphs, equals(glyph.unicodeGlyphs));
+ });
+
+ test('asciiGlyphs still returns ASCII characters', () {
+ expect(glyph.asciiGlyphs.topLeftCorner, equals(','));
+ expect(glyph.asciiGlyphs.teeUpBold, equals('+'));
+ expect(glyph.asciiGlyphs.longLeftArrow, equals('<='));
+ });
+ });
+
+ group('with ascii = true', () {
+ setUpAll(() {
+ glyph.ascii = true;
+ });
+
+ test('glyphs return ASCII versions', () {
+ expect(glyph.topLeftCorner, equals(','));
+ expect(glyph.teeUpBold, equals('+'));
+ expect(glyph.longLeftArrow, equals('<='));
+ });
+
+ test('glyphOrAscii returns the second argument', () {
+ expect(glyph.glyphOrAscii('A', 'B'), equals('B'));
+ });
+
+ test('glyphs returns asciiGlyphs', () {
+ expect(glyph.glyphs, equals(glyph.asciiGlyphs));
+ });
+
+ test('unicodeGlyphs still returns Unicode characters', () {
+ expect(glyph.unicodeGlyphs.topLeftCorner, equals('┌'));
+ expect(glyph.unicodeGlyphs.teeUpBold, equals('┻'));
+ expect(glyph.unicodeGlyphs.longLeftArrow, equals('◀━'));
+ });
+ });
+}
diff --git a/pkgs/term_glyph/tool/generate.dart b/pkgs/term_glyph/tool/generate.dart
new file mode 100644
index 0000000..b96b7bd
--- /dev/null
+++ b/pkgs/term_glyph/tool/generate.dart
@@ -0,0 +1,156 @@
+// Copyright (c) 2017, 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:csv/csv.dart';
+
+void main() {
+ final csv = CsvCodec(eol: '\n');
+ final data = csv.decoder.convert(File('data.csv').readAsStringSync());
+
+ // Remove comments and empty lines.
+ data.removeWhere((row) => row.length < 3);
+
+ Directory(_generatedDir).createSync(recursive: true);
+
+ _writeGlyphSetInterface(data);
+ _writeGlyphSet(data, ascii: false);
+ _writeGlyphSet(data, ascii: true);
+ _writeTopLevel(data);
+
+ final result = Process.runSync(Platform.resolvedExecutable, [
+ 'format',
+ _generatedDir,
+ ]);
+ print(result.stderr);
+ exit(result.exitCode);
+}
+
+const _generatedDir = 'lib/src/generated';
+
+/// Writes `lib/src/generated/glyph_set.dart`.
+void _writeGlyphSetInterface(List<List<dynamic>> data) {
+ final file =
+ File('$_generatedDir/glyph_set.dart').openSync(mode: FileMode.write);
+ file.writeStringSync(_header);
+ file.writeStringSync(r'''
+
+ /// A class that provides access to every configurable glyph.
+ ///
+ /// This is provided as a class so that individual chunks of code can choose
+ /// between `ascii` and `unicode` glyphs. For example:
+ ///
+ /// ```dart
+ /// import 'package:term_glyph/term_glyph.dart' as glyph;
+ ///
+ /// /// Adds a vertical line to the left of [text].
+ /// ///
+ /// /// If [unicode] is `true`, this uses Unicode for the line. If it's
+ /// /// `false`, this uses plain ASCII characters. If it's `null`, it
+ /// /// defaults to [glyph.ascii].
+ /// void addVerticalLine(String text, {bool unicode}) {
+ /// var glyphs =
+ /// (unicode ?? !glyph.ascii) ? glyph.unicodeGlyphs : glyph.asciiGlyphs;
+ ///
+ /// return text
+ /// .split('\n')
+ /// .map((line) => '${glyphs.verticalLine} $line')
+ /// .join('\n');
+ /// }
+ /// ```
+ abstract class GlyphSet {
+ /// Returns [glyph] if `this` supports Unicode glyphs and [alternative]
+ /// otherwise.
+ String glyphOrAscii(String glyph, String alternative);
+ ''');
+
+ for (var glyph in data) {
+ for (var line in (glyph[3] as String).split('\n')) {
+ file.writeStringSync('/// $line\n');
+ }
+
+ file.writeStringSync('String get ${glyph[0]};');
+ }
+
+ file.writeStringSync('}');
+ file.closeSync();
+}
+
+/// Writes `lib/src/generated/${prefix.toLowerCase()}_glyph_set.dart`.
+///
+/// If [ascii] is `true`, this writes the ASCII glyph set. Otherwise it writes
+/// the Unicode glyph set.
+void _writeGlyphSet(List<List<dynamic>> data, {required bool ascii}) {
+ final file =
+ File('$_generatedDir/${ascii ? "ascii" : "unicode"}_glyph_set.dart')
+ .openSync(mode: FileMode.write);
+
+ final className = '${ascii ? "Ascii" : "Unicode"}GlyphSet';
+ file.writeStringSync('''
+ $_header
+
+
+ import 'glyph_set.dart';
+
+ /// A [GlyphSet] that includes only ${ascii ? "ASCII" : "Unicode"} glyphs.
+ class $className implements GlyphSet {
+ const $className();
+ /// Returns [glyph] if `this` supports Unicode glyphs and [alternative]
+ /// otherwise.
+ @override
+ String glyphOrAscii(String glyph, String alternative) =>
+ ${ascii ? "alternative" : "glyph"};
+ ''');
+
+ final index = ascii ? 2 : 1;
+ for (var glyph in data) {
+ file.writeStringSync('''
+ @override
+ String get ${glyph[0]} => ${_quote(glyph[index] as String)};
+ ''');
+ }
+
+ file.writeStringSync('}');
+ file.closeSync();
+}
+
+/// Writes `lib/src/generated/top_level.dart`.
+void _writeTopLevel(List<List<dynamic>> data) {
+ final file =
+ File('$_generatedDir/top_level.dart').openSync(mode: FileMode.write);
+
+ file.writeStringSync('''
+ $_header
+
+ import '../../term_glyph.dart' as glyph;
+ ''');
+
+ for (var glyph in data) {
+ for (var line in (glyph[3] as String).split('\n')) {
+ file.writeStringSync('/// $line\n');
+ }
+
+ file.writeStringSync('''
+ ///
+ /// If [glyph.ascii] is `false`, this is "${glyph[1]}". If it's `true`, this is
+ /// "${glyph[2]}" instead.
+ String get ${glyph[0]} => glyph.glyphs.${glyph[0]};
+ ''');
+ }
+
+ file.closeSync();
+}
+
+String _quote(String input) => input.contains("'") ? '"$input"' : "'$input'";
+
+const _header = '''
+// 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.
+
+// Don't modify this file by hand! It's generated by tool/generate.dart.
+
+// ignore_for_file: lines_longer_than_80_chars
+''';
diff --git a/pkgs/test_reflective_loader/.gitignore b/pkgs/test_reflective_loader/.gitignore
new file mode 100644
index 0000000..2a2c261
--- /dev/null
+++ b/pkgs/test_reflective_loader/.gitignore
@@ -0,0 +1,11 @@
+.buildlog
+.DS_Store
+.idea
+.dart_tool/
+.pub/
+.project
+.settings/
+build/
+packages
+.packages
+pubspec.lock
diff --git a/pkgs/test_reflective_loader/AUTHORS b/pkgs/test_reflective_loader/AUTHORS
new file mode 100644
index 0000000..e8063a8
--- /dev/null
+++ b/pkgs/test_reflective_loader/AUTHORS
@@ -0,0 +1,6 @@
+# Below is a list of people and organizations that have contributed
+# to the project. Names should be added to the list like so:
+#
+# Name/Organization <email address>
+
+Google Inc.
diff --git a/pkgs/test_reflective_loader/CHANGELOG.md b/pkgs/test_reflective_loader/CHANGELOG.md
new file mode 100644
index 0000000..803eb0e
--- /dev/null
+++ b/pkgs/test_reflective_loader/CHANGELOG.md
@@ -0,0 +1,72 @@
+## 0.2.3
+
+- Require Dart `^3.1.0`.
+- Move to `dart-lang/tools` monorepo.
+
+## 0.2.2
+
+- Update to package:lints 2.0.0 and move it to a dev dependency.
+
+## 0.2.1
+
+- Use package:lints for analysis.
+- Populate the pubspec `repository` field.
+
+## 0.2.0
+
+- Stable null safety release.
+
+## 0.2.0-nullsafety.0
+
+- Migrate to the null safety language feature.
+
+## 0.1.9
+
+- Add `@SkippedTest` annotation and `skip_test` prefix.
+
+## 0.1.8
+
+- Update `FailingTest` to add named parameters `issue` and `reason`.
+
+## 0.1.7
+
+- Update documentation comments.
+- Remove `@MirrorsUsed` annotation on `dart:mirrors`.
+
+## 0.1.6
+
+- Make `FailingTest` public, with the URI of the issue that causes
+ the test to break.
+
+## 0.1.5
+
+- Set max SDK version to `<3.0.0`, and adjust other dependencies.
+
+## 0.1.3
+
+- Fix `@failingTest` to fail when the test passes.
+
+## 0.1.2
+
+- Update the pubspec `dependencies` section to include `package:test`
+
+## 0.1.1
+
+- For `@failingTest` tests, properly handle when the test fails by throwing an
+ exception in a timer task
+- Analyze this package in strong mode
+
+## 0.1.0
+
+- Switched from 'package:unittest' to 'package:test'.
+- Since 'package:test' does not define 'solo_test', in order to keep this
+ functionality, `defineReflectiveSuite` must be used to wrap all
+ `defineReflectiveTests` invocations.
+
+## 0.0.4
+
+- Added @failingTest, @assertFailingTest and @soloTest annotations.
+
+## 0.0.1
+
+- Initial version
diff --git a/pkgs/test_reflective_loader/LICENSE b/pkgs/test_reflective_loader/LICENSE
new file mode 100644
index 0000000..633672a
--- /dev/null
+++ b/pkgs/test_reflective_loader/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2015, 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.
diff --git a/pkgs/test_reflective_loader/README.md b/pkgs/test_reflective_loader/README.md
new file mode 100644
index 0000000..9b5a83d
--- /dev/null
+++ b/pkgs/test_reflective_loader/README.md
@@ -0,0 +1,28 @@
+[](https://github.com/dart-lang/tools/actions/workflows/test_reflective_loader.yaml)
+[](https://pub.dev/packages/test_reflective_loader)
+[](https://pub.dev/packages/test_reflective_loader/publisher)
+
+Support for discovering 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.
+
+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.
+
+Methods with names starting with `solo_test_` are run using the `solo_test()` function.
+
+Methods with names starting with `fail_` are expected to fail.
+
+Methods with names starting with `solo_fail_` are run using the `solo_test()` function
+and expected to fail.
+
+Method returning `Future` class instances are asynchronous, so `tearDown()` is
+executed after the returned `Future` completes.
+
+## Features and bugs
+
+Please file feature requests and bugs at the [issue tracker][tracker].
+
+[tracker]: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Atest_reflective_loader
diff --git a/pkgs/test_reflective_loader/analysis_options.yaml b/pkgs/test_reflective_loader/analysis_options.yaml
new file mode 100644
index 0000000..ea61158
--- /dev/null
+++ b/pkgs/test_reflective_loader/analysis_options.yaml
@@ -0,0 +1,5 @@
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+linter:
+ rules:
+ - public_member_api_docs
diff --git a/pkgs/test_reflective_loader/lib/test_reflective_loader.dart b/pkgs/test_reflective_loader/lib/test_reflective_loader.dart
new file mode 100644
index 0000000..cb69bf3
--- /dev/null
+++ b/pkgs/test_reflective_loader/lib/test_reflective_loader.dart
@@ -0,0 +1,354 @@
+// Copyright (c) 2015, 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:mirrors';
+
+import 'package:test/test.dart' as test_package;
+
+/// A marker annotation used to annotate test methods which are expected to fail
+/// when asserts are enabled.
+const Object assertFailingTest = _AssertFailingTest();
+
+/// A marker annotation used to annotate test methods which are expected to
+/// fail.
+const Object failingTest = FailingTest();
+
+/// A marker annotation used to instruct dart2js to keep reflection information
+/// for the annotated classes.
+const Object reflectiveTest = _ReflectiveTest();
+
+/// A marker annotation used to annotate test methods that should be skipped.
+const Object skippedTest = SkippedTest();
+
+/// A marker annotation used to annotate "solo" groups and tests.
+const Object soloTest = _SoloTest();
+
+final List<_Group> _currentGroups = <_Group>[];
+int _currentSuiteLevel = 0;
+String _currentSuiteName = '';
+
+/// Is `true` the application is running in the checked mode.
+final bool _isCheckedMode = () {
+ try {
+ assert(false);
+ return false;
+ } catch (_) {
+ return true;
+ }
+}();
+
+/// Run the [define] function parameter that calls [defineReflectiveTests] to
+/// add normal and "solo" tests, and also calls [defineReflectiveSuite] to
+/// create embedded suites. If the current suite is the top-level one, perform
+/// check for "solo" groups and tests, and run all or only "solo" items.
+void defineReflectiveSuite(void Function() define, {String name = ''}) {
+ var groupName = _currentSuiteName;
+ _currentSuiteLevel++;
+ try {
+ _currentSuiteName = _combineNames(_currentSuiteName, name);
+ define();
+ } finally {
+ _currentSuiteName = groupName;
+ _currentSuiteLevel--;
+ }
+ _addTestsIfTopLevelSuite();
+}
+
+/// Runs test methods existing in the given [type].
+///
+/// If there is a "solo" test method in the top-level suite, only "solo" methods
+/// are run.
+///
+/// If there is a "solo" test type, only its test methods are run.
+///
+/// Otherwise all tests methods of all test types are run.
+///
+/// Each method is run with a new instance of [type].
+/// So, [type] should have a default constructor.
+///
+/// If [type] declares method `setUp`, it methods will be invoked before any
+/// test method invocation.
+///
+/// If [type] declares method `tearDown`, it will be invoked after any test
+/// method invocation. If method returns [Future] to test some asynchronous
+/// behavior, then `tearDown` will be invoked in `Future.complete`.
+void defineReflectiveTests(Type type) {
+ var classMirror = reflectClass(type);
+ if (!classMirror.metadata.any((InstanceMirror annotation) =>
+ annotation.type.reflectedType == _ReflectiveTest)) {
+ var name = MirrorSystem.getName(classMirror.qualifiedName);
+ throw Exception('Class $name must have annotation "@reflectiveTest" '
+ 'in order to be run by runReflectiveTests.');
+ }
+
+ _Group group;
+ {
+ var isSolo = _hasAnnotationInstance(classMirror, soloTest);
+ var className = MirrorSystem.getName(classMirror.simpleName);
+ group = _Group(isSolo, _combineNames(_currentSuiteName, className));
+ _currentGroups.add(group);
+ }
+
+ classMirror.instanceMembers
+ .forEach((Symbol symbol, MethodMirror memberMirror) {
+ // we need only methods
+ if (!memberMirror.isRegularMethod) {
+ return;
+ }
+ // prepare information about the method
+ var memberName = MirrorSystem.getName(symbol);
+ var isSolo = memberName.startsWith('solo_') ||
+ _hasAnnotationInstance(memberMirror, soloTest);
+ // test_
+ if (memberName.startsWith('test_')) {
+ if (_hasSkippedTestAnnotation(memberMirror)) {
+ group.addSkippedTest(memberName);
+ } else {
+ group.addTest(isSolo, memberName, memberMirror, () {
+ if (_hasFailingTestAnnotation(memberMirror) ||
+ _isCheckedMode && _hasAssertFailingTestAnnotation(memberMirror)) {
+ return _runFailingTest(classMirror, symbol);
+ } else {
+ return _runTest(classMirror, symbol);
+ }
+ });
+ }
+ return;
+ }
+ // solo_test_
+ if (memberName.startsWith('solo_test_')) {
+ group.addTest(true, memberName, memberMirror, () {
+ return _runTest(classMirror, symbol);
+ });
+ }
+ // fail_test_
+ if (memberName.startsWith('fail_')) {
+ group.addTest(isSolo, memberName, memberMirror, () {
+ return _runFailingTest(classMirror, symbol);
+ });
+ }
+ // solo_fail_test_
+ if (memberName.startsWith('solo_fail_')) {
+ group.addTest(true, memberName, memberMirror, () {
+ return _runFailingTest(classMirror, symbol);
+ });
+ }
+ // skip_test_
+ if (memberName.startsWith('skip_test_')) {
+ group.addSkippedTest(memberName);
+ }
+ });
+
+ // Support for the case of missing enclosing [defineReflectiveSuite].
+ _addTestsIfTopLevelSuite();
+}
+
+/// If the current suite is the top-level one, add tests to the `test` package.
+void _addTestsIfTopLevelSuite() {
+ if (_currentSuiteLevel == 0) {
+ void runTests({required bool allGroups, required bool allTests}) {
+ for (var group in _currentGroups) {
+ if (allGroups || group.isSolo) {
+ for (var test in group.tests) {
+ if (allTests || test.isSolo) {
+ test_package.test(test.name, test.function,
+ timeout: test.timeout, skip: test.isSkipped);
+ }
+ }
+ }
+ }
+ }
+
+ if (_currentGroups.any((g) => g.hasSoloTest)) {
+ runTests(allGroups: true, allTests: false);
+ } else if (_currentGroups.any((g) => g.isSolo)) {
+ runTests(allGroups: false, allTests: true);
+ } else {
+ runTests(allGroups: true, allTests: true);
+ }
+ _currentGroups.clear();
+ }
+}
+
+/// Return the combination of the [base] and [addition] names.
+/// If any other two is `null`, then the other one is returned.
+String _combineNames(String base, String addition) {
+ if (base.isEmpty) {
+ return addition;
+ } else if (addition.isEmpty) {
+ return base;
+ } else {
+ return '$base | $addition';
+ }
+}
+
+Object? _getAnnotationInstance(DeclarationMirror declaration, Type type) {
+ for (var annotation in declaration.metadata) {
+ if ((annotation.reflectee as Object).runtimeType == type) {
+ return annotation.reflectee;
+ }
+ }
+ return null;
+}
+
+bool _hasAnnotationInstance(DeclarationMirror declaration, Object instance) =>
+ declaration.metadata.any((InstanceMirror annotation) =>
+ identical(annotation.reflectee, instance));
+
+bool _hasAssertFailingTestAnnotation(MethodMirror method) =>
+ _hasAnnotationInstance(method, assertFailingTest);
+
+bool _hasFailingTestAnnotation(MethodMirror method) =>
+ _hasAnnotationInstance(method, failingTest);
+
+bool _hasSkippedTestAnnotation(MethodMirror method) =>
+ _hasAnnotationInstance(method, skippedTest);
+
+Future<Object?> _invokeSymbolIfExists(
+ InstanceMirror instanceMirror, Symbol symbol) {
+ Object? invocationResult;
+ InstanceMirror? closure;
+ try {
+ closure = instanceMirror.getField(symbol);
+ // ignore: avoid_catching_errors
+ } on NoSuchMethodError {
+ // ignore
+ }
+
+ if (closure is ClosureMirror) {
+ invocationResult = closure.apply([]).reflectee;
+ }
+ return Future.value(invocationResult);
+}
+
+/// Run a test that is expected to fail, and confirm that it fails.
+///
+/// This properly handles the following cases:
+/// - The test fails by throwing an exception
+/// - The test returns a future which completes with an error.
+/// - An exception is thrown to the zone handler from a timer task.
+Future<Object?>? _runFailingTest(ClassMirror classMirror, Symbol symbol) {
+ var passed = false;
+ return runZonedGuarded(() {
+ // ignore: void_checks
+ return Future.sync(() => _runTest(classMirror, symbol)).then<void>((_) {
+ passed = true;
+ test_package.fail('Test passed - expected to fail.');
+ }).catchError((Object e) {
+ // if passed, and we call fail(), rethrow this exception
+ if (passed) {
+ // ignore: only_throw_errors
+ throw e;
+ }
+ // otherwise, an exception is not a failure for _runFailingTest
+ });
+ }, (e, st) {
+ // if passed, and we call fail(), rethrow this exception
+ if (passed) {
+ // ignore: only_throw_errors
+ throw e;
+ }
+ // otherwise, an exception is not a failure for _runFailingTest
+ });
+}
+
+Future<void> _runTest(ClassMirror classMirror, Symbol symbol) async {
+ var instanceMirror = classMirror.newInstance(const Symbol(''), []);
+ try {
+ await _invokeSymbolIfExists(instanceMirror, #setUp);
+ await instanceMirror.invoke(symbol, []).reflectee;
+ } finally {
+ await _invokeSymbolIfExists(instanceMirror, #tearDown);
+ }
+}
+
+typedef _TestFunction = dynamic Function();
+
+/// A marker annotation used to annotate test methods which are expected to
+/// fail.
+class FailingTest {
+ /// Initialize this annotation with the given arguments.
+ ///
+ /// [issue] is a full URI describing the failure and used for tracking.
+ /// [reason] is a free form textual description.
+ const FailingTest({String? issue, String? reason});
+}
+
+/// A marker annotation used to annotate test methods which are skipped.
+class SkippedTest {
+ /// Initialize this annotation with the given arguments.
+ ///
+ /// [issue] is a full URI describing the failure and used for tracking.
+ /// [reason] is a free form textual description.
+ const SkippedTest({String? issue, String? reason});
+}
+
+/// A marker annotation used to annotate test methods with additional timeout
+/// information.
+class TestTimeout {
+ final test_package.Timeout _timeout;
+
+ /// Initialize this annotation with the given timeout.
+ const TestTimeout(test_package.Timeout timeout) : _timeout = timeout;
+}
+
+/// A marker annotation used to annotate test methods which are expected to fail
+/// when asserts are enabled.
+class _AssertFailingTest {
+ const _AssertFailingTest();
+}
+
+/// Information about a type based test group.
+class _Group {
+ final bool isSolo;
+ final String name;
+ final List<_Test> tests = <_Test>[];
+
+ _Group(this.isSolo, this.name);
+
+ bool get hasSoloTest => tests.any((test) => test.isSolo);
+
+ void addSkippedTest(String name) {
+ var fullName = _combineNames(this.name, name);
+ tests.add(_Test.skipped(isSolo, fullName));
+ }
+
+ void addTest(bool isSolo, String name, MethodMirror memberMirror,
+ _TestFunction function) {
+ var fullName = _combineNames(this.name, name);
+ var timeout =
+ _getAnnotationInstance(memberMirror, TestTimeout) as TestTimeout?;
+ tests.add(_Test(isSolo, fullName, function, timeout?._timeout));
+ }
+}
+
+/// A marker annotation used to instruct dart2js to keep reflection information
+/// for the annotated classes.
+class _ReflectiveTest {
+ const _ReflectiveTest();
+}
+
+/// A marker annotation used to annotate "solo" groups and tests.
+class _SoloTest {
+ const _SoloTest();
+}
+
+/// Information about a test.
+class _Test {
+ final bool isSolo;
+ final String name;
+ final _TestFunction function;
+ final test_package.Timeout? timeout;
+
+ final bool isSkipped;
+
+ _Test(this.isSolo, this.name, this.function, this.timeout)
+ : isSkipped = false;
+
+ _Test.skipped(this.isSolo, this.name)
+ : isSkipped = true,
+ function = (() {}),
+ timeout = null;
+}
diff --git a/pkgs/test_reflective_loader/pubspec.yaml b/pkgs/test_reflective_loader/pubspec.yaml
new file mode 100644
index 0000000..569933f
--- /dev/null
+++ b/pkgs/test_reflective_loader/pubspec.yaml
@@ -0,0 +1,13 @@
+name: test_reflective_loader
+version: 0.2.3
+description: Support for discovering tests and test suites using reflection.
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/test_reflective_loader
+
+environment:
+ sdk: ^3.1.0
+
+dependencies:
+ test: ^1.16.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
diff --git a/pkgs/test_reflective_loader/test/test_reflective_loader_test.dart b/pkgs/test_reflective_loader/test/test_reflective_loader_test.dart
new file mode 100644
index 0000000..fad98a5
--- /dev/null
+++ b/pkgs/test_reflective_loader/test/test_reflective_loader_test.dart
@@ -0,0 +1,48 @@
+// Copyright (c) 2017, 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: non_constant_identifier_names
+
+import 'dart:async';
+
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+void main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(TestReflectiveLoaderTest);
+ });
+}
+
+@reflectiveTest
+class TestReflectiveLoaderTest {
+ void test_passes() {
+ expect(true, true);
+ }
+
+ @failingTest
+ void test_fails() {
+ expect(false, true);
+ }
+
+ @failingTest
+ void test_fails_throws_sync() {
+ throw StateError('foo');
+ }
+
+ @failingTest
+ Future test_fails_throws_async() {
+ return Future.error('foo');
+ }
+
+ @skippedTest
+ void test_fails_but_skipped() {
+ throw StateError('foo');
+ }
+
+ @skippedTest
+ void test_times_out_but_skipped() {
+ while (true) {}
+ }
+}
diff --git a/pkgs/timing/.gitignore b/pkgs/timing/.gitignore
new file mode 100644
index 0000000..1ddf798
--- /dev/null
+++ b/pkgs/timing/.gitignore
@@ -0,0 +1,7 @@
+.packages
+/build/
+pubspec.lock
+
+# Files generated by dart tools
+.dart_tool
+doc/
diff --git a/pkgs/timing/CHANGELOG.md b/pkgs/timing/CHANGELOG.md
new file mode 100644
index 0000000..8cdb8ea
--- /dev/null
+++ b/pkgs/timing/CHANGELOG.md
@@ -0,0 +1,34 @@
+## 1.0.2
+
+- Require Dart `3.4`.
+- Move to `dart-lang/tools` monorepo.
+
+## 1.0.1
+
+- Require Dart `2.14`.
+
+## 1.0.0
+
+- Enable null safety.
+- Require Dart `2.12`.
+
+## 0.1.1+3
+
+- Allow `package:json_annotation` `'>=1.0.0 <5.0.0'`.
+
+## 0.1.1+2
+
+- Support the latest version of `package:json_annotation`.
+- Require Dart 2.2 or later.
+
+## 0.1.1+1
+
+- Support the latest version of `package:json_annotation`.
+
+## 0.1.1
+
+- Add JSON serialization
+
+## 0.1.0
+
+- Initial release
diff --git a/pkgs/timing/LICENSE b/pkgs/timing/LICENSE
new file mode 100644
index 0000000..9972f6e
--- /dev/null
+++ b/pkgs/timing/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2018, 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.
diff --git a/pkgs/timing/README.md b/pkgs/timing/README.md
new file mode 100644
index 0000000..9dab7cc
--- /dev/null
+++ b/pkgs/timing/README.md
@@ -0,0 +1,30 @@
+[](https://github.com/dart-lang/tools/actions/workflows/timing.yaml)
+[](https://pub.dev/packages/timing)
+[](https://pub.dev/packages/timing/publisher)
+
+Timing is a simple package for tracking performance of both async and sync actions
+
+## Usage
+
+```dart
+var tracker = AsyncTimeTracker();
+await tracker.track(() async {
+ // some async code here
+});
+
+// Use results
+print('${tracker.duration} ${tracker.innerDuration} ${tracker.slices}');
+```
+
+## Building
+
+Use the following command to re-generate `lib/src/timing.g.dart` file:
+
+```bash
+dart pub run build_runner build
+```
+
+## Publishing automation
+
+For information about our publishing automation and release process, see
+https://github.com/dart-lang/ecosystem/wiki/Publishing-automation.
diff --git a/pkgs/timing/analysis_options.yaml b/pkgs/timing/analysis_options.yaml
new file mode 100644
index 0000000..396236d
--- /dev/null
+++ b/pkgs/timing/analysis_options.yaml
@@ -0,0 +1,2 @@
+# https://dart.dev/tools/analysis#the-analysis-options-file
+include: package:dart_flutter_team_lints/analysis_options.yaml
diff --git a/pkgs/timing/lib/src/clock.dart b/pkgs/timing/lib/src/clock.dart
new file mode 100644
index 0000000..6a9d295
--- /dev/null
+++ b/pkgs/timing/lib/src/clock.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2017, 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';
+
+/// A function that returns the current [DateTime].
+typedef _Clock = DateTime Function();
+DateTime _defaultClock() => DateTime.now();
+
+const _zoneKey = #timing_Clock;
+
+/// Returns the current [DateTime].
+///
+/// May be overridden for tests using [scopeClock].
+DateTime now() => (Zone.current[_zoneKey] as _Clock? ?? _defaultClock)();
+
+/// Runs [f], with [clock] scoped whenever [now] is called.
+T scopeClock<T>(DateTime Function() clock, T Function() f) =>
+ runZoned(f, zoneValues: {_zoneKey: clock});
diff --git a/pkgs/timing/lib/src/timing.dart b/pkgs/timing/lib/src/timing.dart
new file mode 100644
index 0000000..049ba81
--- /dev/null
+++ b/pkgs/timing/lib/src/timing.dart
@@ -0,0 +1,338 @@
+// 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.
+
+import 'dart:async';
+
+import 'package:json_annotation/json_annotation.dart';
+
+import 'clock.dart';
+
+part 'timing.g.dart';
+
+/// The timings of an operation, including its [startTime], [stopTime], and
+/// [duration].
+@JsonSerializable()
+class TimeSlice {
+ /// The total duration of this operation, equivalent to taking the difference
+ /// between [stopTime] and [startTime].
+ Duration get duration => stopTime.difference(startTime);
+
+ final DateTime startTime;
+
+ final DateTime stopTime;
+
+ TimeSlice(this.startTime, this.stopTime);
+
+ factory TimeSlice.fromJson(Map<String, dynamic> json) =>
+ _$TimeSliceFromJson(json);
+
+ Map<String, dynamic> toJson() => _$TimeSliceToJson(this);
+
+ @override
+ String toString() => '($startTime + $duration)';
+}
+
+/// The timings of an async operation, consist of several sync [slices] and
+/// includes total [startTime], [stopTime], and [duration].
+@JsonSerializable()
+class TimeSliceGroup implements TimeSlice {
+ final List<TimeSlice> slices;
+
+ @override
+ DateTime get startTime => slices.first.startTime;
+
+ @override
+ DateTime get stopTime => slices.last.stopTime;
+
+ /// The total duration of this operation, equivalent to taking the difference
+ /// between [stopTime] and [startTime].
+ @override
+ Duration get duration => stopTime.difference(startTime);
+
+ /// Sum of [duration]s of all [slices].
+ ///
+ /// If some of slices implements [TimeSliceGroup] [innerDuration] will be used
+ /// to compute sum.
+ Duration get innerDuration => slices.fold(
+ Duration.zero,
+ (duration, slice) =>
+ duration +
+ (slice is TimeSliceGroup ? slice.innerDuration : slice.duration));
+
+ TimeSliceGroup(this.slices);
+
+ /// Constructs TimeSliceGroup from JSON representation
+ factory TimeSliceGroup.fromJson(Map<String, dynamic> json) =>
+ _$TimeSliceGroupFromJson(json);
+
+ @override
+ Map<String, dynamic> toJson() => _$TimeSliceGroupToJson(this);
+
+ @override
+ String toString() => slices.toString();
+}
+
+abstract class TimeTracker implements TimeSlice {
+ /// Whether tracking is active.
+ ///
+ /// Tracking is only active after `isStarted` and before `isFinished`.
+ bool get isTracking;
+
+ /// Whether tracking is finished.
+ ///
+ /// Tracker can't be used as [TimeSlice] before it is finished
+ bool get isFinished;
+
+ /// Whether tracking was started.
+ ///
+ /// Equivalent of `isTracking || isFinished`
+ bool get isStarted;
+
+ T track<T>(T Function() action);
+}
+
+/// Tracks only sync actions
+class SyncTimeTracker implements TimeTracker {
+ /// When this operation started, call [_start] to set this.
+ @override
+ DateTime get startTime => _startTime!;
+ DateTime? _startTime;
+
+ /// When this operation stopped, call [_stop] to set this.
+ @override
+ DateTime get stopTime => _stopTime!;
+ DateTime? _stopTime;
+
+ /// Start tracking this operation, must only be called once, before [_stop].
+ void _start() {
+ assert(_startTime == null && _stopTime == null);
+ _startTime = now();
+ }
+
+ /// Stop tracking this operation, must only be called once, after [_start].
+ void _stop() {
+ assert(_startTime != null && _stopTime == null);
+ _stopTime = now();
+ }
+
+ /// Splits tracker into two slices.
+ ///
+ /// Returns new [TimeSlice] started on [startTime] and ended now. Modifies
+ /// [startTime] of tracker to current time point
+ ///
+ /// Don't change state of tracker. Can be called only while [isTracking], and
+ /// tracker will sill be tracking after call.
+ TimeSlice _split() {
+ if (!isTracking) {
+ throw StateError('Can be only called while tracking');
+ }
+ final splitPoint = now();
+ final prevSlice = TimeSlice(_startTime!, splitPoint);
+ _startTime = splitPoint;
+ return prevSlice;
+ }
+
+ @override
+ T track<T>(T Function() action) {
+ if (isStarted) {
+ throw StateError('Can not be tracked twice');
+ }
+ _start();
+ try {
+ return action();
+ } finally {
+ _stop();
+ }
+ }
+
+ @override
+ bool get isStarted => _startTime != null;
+
+ @override
+ bool get isTracking => _startTime != null && _stopTime == null;
+
+ @override
+ bool get isFinished => _startTime != null && _stopTime != null;
+
+ @override
+ Duration get duration => _stopTime!.difference(_startTime!);
+
+ /// Converts to JSON representation
+ ///
+ /// Can't be used before [isFinished]
+ @override
+ Map<String, dynamic> toJson() => _$TimeSliceToJson(this);
+}
+
+/// Async actions returning [Future] will be tracked as single sync time span
+/// from the beginning of execution till completion of future
+class SimpleAsyncTimeTracker extends SyncTimeTracker {
+ @override
+ T track<T>(T Function() action) {
+ if (isStarted) {
+ throw StateError('Can not be tracked twice');
+ }
+ T result;
+ _start();
+ try {
+ result = action();
+ } catch (_) {
+ _stop();
+ rethrow;
+ }
+ if (result is Future) {
+ return result.whenComplete(_stop) as T;
+ } else {
+ _stop();
+ return result;
+ }
+ }
+}
+
+/// No-op implementation of [SyncTimeTracker] that does nothing.
+class NoOpTimeTracker implements TimeTracker {
+ static final sharedInstance = NoOpTimeTracker();
+
+ @override
+ Duration get duration =>
+ throw UnsupportedError('Unsupported in no-op implementation');
+
+ @override
+ DateTime get startTime =>
+ throw UnsupportedError('Unsupported in no-op implementation');
+
+ @override
+ DateTime get stopTime =>
+ throw UnsupportedError('Unsupported in no-op implementation');
+
+ @override
+ bool get isStarted =>
+ throw UnsupportedError('Unsupported in no-op implementation');
+
+ @override
+ bool get isTracking =>
+ throw UnsupportedError('Unsupported in no-op implementation');
+
+ @override
+ bool get isFinished =>
+ throw UnsupportedError('Unsupported in no-op implementation');
+
+ @override
+ T track<T>(T Function() action) => action();
+
+ @override
+ Map<String, dynamic> toJson() =>
+ throw UnsupportedError('Unsupported in no-op implementation');
+}
+
+/// Track all async execution as disjoint time [slices] in ascending order.
+///
+/// Can [track] both async and sync actions.
+/// Can exclude time of tested trackers.
+///
+/// If tracked action spawns some dangled async executions behavior is't
+/// defined. Tracked might or might not track time of such executions
+class AsyncTimeTracker extends TimeSliceGroup implements TimeTracker {
+ final bool trackNested;
+
+ static const _zoneKey = #timing_AsyncTimeTracker;
+
+ AsyncTimeTracker({this.trackNested = true}) : super([]);
+
+ T _trackSyncSlice<T>(ZoneDelegate parent, Zone zone, T Function() action) {
+ // Ignore dangling runs after tracker completes
+ if (isFinished) {
+ return action();
+ }
+
+ final isNestedRun = slices.isNotEmpty &&
+ slices.last is SyncTimeTracker &&
+ (slices.last as SyncTimeTracker).isTracking;
+ final isExcludedNestedTrack = !trackNested && zone[_zoneKey] != this;
+
+ // Exclude nested sync tracks
+ if (isNestedRun && isExcludedNestedTrack) {
+ final timer = slices.last as SyncTimeTracker;
+ // Split already tracked time into new slice.
+ // Replace tracker in slices.last with splitted slice, to indicate for
+ // recursive calls that we not tracking.
+ slices.last = parent.run(zone, timer._split);
+ try {
+ return action();
+ } finally {
+ // Split tracker again and discard slice from nested tracker
+ parent.run(zone, timer._split);
+ // Add tracker back to list of slices and continue tracking
+ slices.add(timer);
+ }
+ }
+
+ // Exclude nested async tracks
+ if (isExcludedNestedTrack) {
+ return action();
+ }
+
+ // Split time slices in nested sync runs
+ if (isNestedRun) {
+ return action();
+ }
+
+ final timer = SyncTimeTracker();
+ slices.add(timer);
+
+ // Pass to parent zone, in case of overwritten clock
+ return parent.runUnary(zone, timer.track, action);
+ }
+
+ static final asyncTimeTrackerZoneSpecification = ZoneSpecification(
+ run: <R>(Zone self, ZoneDelegate parent, Zone zone, R Function() f) {
+ final tracker = self[_zoneKey] as AsyncTimeTracker;
+ return tracker._trackSyncSlice(parent, zone, () => parent.run(zone, f));
+ },
+ runUnary: <R, T>(Zone self, ZoneDelegate parent, Zone zone, R Function(T) f,
+ T arg) {
+ final tracker = self[_zoneKey] as AsyncTimeTracker;
+ return tracker._trackSyncSlice(
+ parent, zone, () => parent.runUnary(zone, f, arg));
+ },
+ runBinary: <R, T1, T2>(Zone self, ZoneDelegate parent, Zone zone,
+ R Function(T1, T2) f, T1 arg1, T2 arg2) {
+ final tracker = self[_zoneKey] as AsyncTimeTracker;
+ return tracker._trackSyncSlice(
+ parent, zone, () => parent.runBinary(zone, f, arg1, arg2));
+ },
+ );
+
+ @override
+ T track<T>(T Function() action) {
+ if (isStarted) {
+ throw StateError('Can not be tracked twice');
+ }
+ _tracking = true;
+ final result = runZoned(action,
+ zoneSpecification: asyncTimeTrackerZoneSpecification,
+ zoneValues: {_zoneKey: this});
+ if (result is Future) {
+ return result
+ // Break possible sync processing of future completion, so slice
+ // trackers can be finished
+ .whenComplete(Future.value)
+ .whenComplete(() => _tracking = false) as T;
+ } else {
+ _tracking = false;
+ return result;
+ }
+ }
+
+ bool? _tracking;
+
+ @override
+ bool get isStarted => _tracking != null;
+
+ @override
+ bool get isFinished => _tracking == false;
+
+ @override
+ bool get isTracking => _tracking == true;
+}
diff --git a/pkgs/timing/lib/src/timing.g.dart b/pkgs/timing/lib/src/timing.g.dart
new file mode 100644
index 0000000..679c082
--- /dev/null
+++ b/pkgs/timing/lib/src/timing.g.dart
@@ -0,0 +1,29 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'timing.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+TimeSlice _$TimeSliceFromJson(Map<String, dynamic> json) => TimeSlice(
+ DateTime.parse(json['startTime'] as String),
+ DateTime.parse(json['stopTime'] as String),
+ );
+
+Map<String, dynamic> _$TimeSliceToJson(TimeSlice instance) => <String, dynamic>{
+ 'startTime': instance.startTime.toIso8601String(),
+ 'stopTime': instance.stopTime.toIso8601String(),
+ };
+
+TimeSliceGroup _$TimeSliceGroupFromJson(Map<String, dynamic> json) =>
+ TimeSliceGroup(
+ (json['slices'] as List<dynamic>)
+ .map((e) => TimeSlice.fromJson(e as Map<String, dynamic>))
+ .toList(),
+ );
+
+Map<String, dynamic> _$TimeSliceGroupToJson(TimeSliceGroup instance) =>
+ <String, dynamic>{
+ 'slices': instance.slices,
+ };
diff --git a/pkgs/timing/lib/timing.dart b/pkgs/timing/lib/timing.dart
new file mode 100644
index 0000000..5cb16d4
--- /dev/null
+++ b/pkgs/timing/lib/timing.dart
@@ -0,0 +1,13 @@
+// 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.
+
+export 'src/timing.dart'
+ show
+ AsyncTimeTracker,
+ NoOpTimeTracker,
+ SimpleAsyncTimeTracker,
+ SyncTimeTracker,
+ TimeSlice,
+ TimeSliceGroup,
+ TimeTracker;
diff --git a/pkgs/timing/pubspec.yaml b/pkgs/timing/pubspec.yaml
new file mode 100644
index 0000000..891a8af
--- /dev/null
+++ b/pkgs/timing/pubspec.yaml
@@ -0,0 +1,18 @@
+name: timing
+version: 1.0.2
+description: >-
+ A simple package for tracking the performance of synchronous and asynchronous
+ actions.
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/timing
+
+environment:
+ sdk: ^3.4.0
+
+dependencies:
+ json_annotation: ^4.9.0
+
+dev_dependencies:
+ build_runner: ^2.0.6
+ dart_flutter_team_lints: ^3.0.0
+ json_serializable: ^6.0.0
+ test: ^1.17.10
diff --git a/pkgs/timing/test/timing_test.dart b/pkgs/timing/test/timing_test.dart
new file mode 100644
index 0000000..b5836d9
--- /dev/null
+++ b/pkgs/timing/test/timing_test.dart
@@ -0,0 +1,416 @@
+// 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: only_throw_errors, inference_failure_on_instance_creation
+
+import 'dart:async';
+
+import 'package:test/test.dart';
+import 'package:timing/src/clock.dart';
+import 'package:timing/src/timing.dart';
+
+void _noop() {}
+
+void main() {
+ late DateTime time;
+ final startTime = DateTime(2017);
+ DateTime fakeClock() => time;
+
+ late TimeTracker tracker;
+ late TimeTracker nestedTracker;
+
+ T scopedTrack<T>(T Function() f) =>
+ scopeClock(fakeClock, () => tracker.track(f));
+
+ setUp(() {
+ time = startTime;
+ });
+
+ void canHandleSync([void Function() additionalExpects = _noop]) {
+ test('Can track sync code', () {
+ expect(tracker.isStarted, false);
+ expect(tracker.isTracking, false);
+ expect(tracker.isFinished, false);
+ scopedTrack(() {
+ expect(tracker.isStarted, true);
+ expect(tracker.isTracking, true);
+ expect(tracker.isFinished, false);
+ time = time.add(const Duration(seconds: 5));
+ });
+ expect(tracker.isStarted, true);
+ expect(tracker.isTracking, false);
+ expect(tracker.isFinished, true);
+ expect(tracker.startTime, startTime);
+ expect(tracker.stopTime, time);
+ expect(tracker.duration, const Duration(seconds: 5));
+ additionalExpects();
+ });
+
+ test('Can track handled sync exceptions', () async {
+ scopedTrack(() {
+ try {
+ time = time.add(const Duration(seconds: 4));
+ throw 'error';
+ } on String {
+ time = time.add(const Duration(seconds: 1));
+ }
+ });
+ expect(tracker.isFinished, true);
+ expect(tracker.startTime, startTime);
+ expect(tracker.stopTime, time);
+ expect(tracker.duration, const Duration(seconds: 5));
+ additionalExpects();
+ });
+
+ test('Can track in case of unhandled sync exceptions', () async {
+ expect(
+ () => scopedTrack(() {
+ time = time.add(const Duration(seconds: 5));
+ throw 'error';
+ }),
+ throwsA(const TypeMatcher<String>()));
+ expect(tracker.startTime, startTime);
+ expect(tracker.stopTime, time);
+ expect(tracker.duration, const Duration(seconds: 5));
+ additionalExpects();
+ });
+
+ test('Can be nested sync', () {
+ scopedTrack(() {
+ time = time.add(const Duration(seconds: 1));
+ nestedTracker.track(() {
+ time = time.add(const Duration(seconds: 2));
+ });
+ time = time.add(const Duration(seconds: 4));
+ });
+ expect(tracker.isFinished, true);
+ expect(tracker.startTime, startTime);
+ expect(tracker.stopTime, time);
+ expect(tracker.duration, const Duration(seconds: 7));
+ expect(nestedTracker.startTime.isAfter(startTime), true);
+ expect(nestedTracker.stopTime.isBefore(time), true);
+ expect(nestedTracker.duration, const Duration(seconds: 2));
+ additionalExpects();
+ });
+ }
+
+ void canHandleAsync([void Function() additionalExpects = _noop]) {
+ test('Can track async code', () async {
+ expect(tracker.isStarted, false);
+ expect(tracker.isTracking, false);
+ expect(tracker.isFinished, false);
+ await scopedTrack(() => Future(() {
+ expect(tracker.isStarted, true);
+ expect(tracker.isTracking, true);
+ expect(tracker.isFinished, false);
+ time = time.add(const Duration(seconds: 5));
+ }));
+ expect(tracker.isStarted, true);
+ expect(tracker.isTracking, false);
+ expect(tracker.isFinished, true);
+ expect(tracker.startTime, startTime);
+ expect(tracker.stopTime, time);
+ expect(tracker.duration, const Duration(seconds: 5));
+ additionalExpects();
+ });
+
+ test('Can track handled async exceptions', () async {
+ await scopedTrack(() {
+ time = time.add(const Duration(seconds: 1));
+ return Future(() {
+ time = time.add(const Duration(seconds: 2));
+ throw 'error';
+ }).then((_) {
+ time = time.add(const Duration(seconds: 4));
+ }).catchError((error, stack) {
+ time = time.add(const Duration(seconds: 8));
+ });
+ });
+ expect(tracker.isFinished, true);
+ expect(tracker.startTime, startTime);
+ expect(tracker.stopTime, time);
+ expect(tracker.duration, const Duration(seconds: 11));
+ additionalExpects();
+ });
+
+ test('Can track in case of unhandled async exceptions', () async {
+ final future = scopedTrack(() {
+ time = time.add(const Duration(seconds: 1));
+ return Future(() {
+ time = time.add(const Duration(seconds: 2));
+ throw 'error';
+ }).then((_) {
+ time = time.add(const Duration(seconds: 4));
+ });
+ });
+ await expectLater(future, throwsA(const TypeMatcher<String>()));
+ expect(tracker.isFinished, true);
+ expect(tracker.startTime, startTime);
+ expect(tracker.stopTime, time);
+ expect(tracker.duration, const Duration(seconds: 3));
+ additionalExpects();
+ });
+
+ test('Can be nested async', () async {
+ await scopedTrack(() async {
+ time = time.add(const Duration(milliseconds: 1));
+ await Future.value();
+ time = time.add(const Duration(milliseconds: 2));
+ await nestedTracker.track(() async {
+ time = time.add(const Duration(milliseconds: 4));
+ await Future.value();
+ time = time.add(const Duration(milliseconds: 8));
+ await Future.value();
+ time = time.add(const Duration(milliseconds: 16));
+ });
+ time = time.add(const Duration(milliseconds: 32));
+ await Future.value();
+ time = time.add(const Duration(milliseconds: 64));
+ });
+ expect(tracker.isFinished, true);
+ expect(tracker.startTime, startTime);
+ expect(tracker.stopTime, time);
+ expect(tracker.duration, const Duration(milliseconds: 127));
+ expect(nestedTracker.startTime.isAfter(startTime), true);
+ expect(nestedTracker.stopTime.isBefore(time), true);
+ expect(nestedTracker.duration, const Duration(milliseconds: 28));
+ additionalExpects();
+ });
+ }
+
+ group('SyncTimeTracker', () {
+ setUp(() {
+ tracker = SyncTimeTracker();
+ nestedTracker = SyncTimeTracker();
+ });
+
+ canHandleSync();
+
+ test('Can not track async code', () async {
+ await scopedTrack(() => Future(() {
+ time = time.add(const Duration(seconds: 5));
+ }));
+ expect(tracker.isFinished, true);
+ expect(tracker.startTime, startTime);
+ expect(tracker.stopTime, startTime);
+ expect(tracker.duration, const Duration(seconds: 0));
+ });
+ });
+
+ group('AsyncTimeTracker.simple', () {
+ setUp(() {
+ tracker = SimpleAsyncTimeTracker();
+ nestedTracker = SimpleAsyncTimeTracker();
+ });
+
+ canHandleSync();
+
+ canHandleAsync();
+
+ test('Can not distinguish own async code', () async {
+ final future = scopedTrack(() => Future(() {
+ time = time.add(const Duration(seconds: 5));
+ }));
+ time = time.add(const Duration(seconds: 10));
+ await future;
+ expect(tracker.isFinished, true);
+ expect(tracker.startTime, startTime);
+ expect(tracker.stopTime, time);
+ expect(tracker.duration, const Duration(seconds: 15));
+ });
+ });
+
+ group('AsyncTimeTracker', () {
+ late AsyncTimeTracker asyncTracker;
+ late AsyncTimeTracker nestedAsyncTracker;
+ setUp(() {
+ tracker = asyncTracker = AsyncTimeTracker();
+ nestedTracker = nestedAsyncTracker = AsyncTimeTracker();
+ });
+
+ canHandleSync(() {
+ expect(asyncTracker.innerDuration, asyncTracker.duration);
+ expect(asyncTracker.slices.length, 1);
+ });
+
+ canHandleAsync(() {
+ expect(asyncTracker.innerDuration, asyncTracker.duration);
+ expect(asyncTracker.slices.length, greaterThan(1));
+ });
+
+ test('Can track complex async innerDuration', () async {
+ final completer = Completer();
+ final future = scopedTrack(() async {
+ time = time.add(const Duration(seconds: 1)); // Tracked sync
+ await Future.value();
+ time = time.add(const Duration(seconds: 2)); // Tracked async
+ await completer.future;
+ time = time.add(const Duration(seconds: 4)); // Tracked async, delayed
+ }).then((_) {
+ time = time.add(const Duration(seconds: 8)); // Async, after tracking
+ });
+ time = time.add(const Duration(seconds: 16)); // Sync, between slices
+
+ await Future(() {
+ // Async, between slices
+ time = time.add(const Duration(seconds: 32));
+ completer.complete();
+ });
+ await future;
+ expect(asyncTracker.isFinished, true);
+ expect(asyncTracker.startTime, startTime);
+ expect(asyncTracker.stopTime.isBefore(time), true);
+ expect(asyncTracker.duration, const Duration(seconds: 55));
+ expect(asyncTracker.innerDuration, const Duration(seconds: 7));
+ expect(asyncTracker.slices.length, greaterThan(1));
+ });
+
+ test('Can exclude nested sync', () {
+ tracker = asyncTracker = AsyncTimeTracker(trackNested: false);
+ scopedTrack(() {
+ time = time.add(const Duration(seconds: 1));
+ nestedAsyncTracker.track(() {
+ time = time.add(const Duration(seconds: 2));
+ });
+ time = time.add(const Duration(seconds: 4));
+ });
+ expect(asyncTracker.isFinished, true);
+ expect(asyncTracker.startTime, startTime);
+ expect(asyncTracker.stopTime, time);
+ expect(asyncTracker.duration, const Duration(seconds: 7));
+ expect(asyncTracker.innerDuration, const Duration(seconds: 5));
+ expect(asyncTracker.slices.length, greaterThan(1));
+ expect(nestedAsyncTracker.startTime.isAfter(startTime), true);
+ expect(nestedAsyncTracker.stopTime.isBefore(time), true);
+ expect(nestedAsyncTracker.duration, const Duration(seconds: 2));
+ expect(nestedAsyncTracker.innerDuration, const Duration(seconds: 2));
+ expect(nestedAsyncTracker.slices.length, 1);
+ });
+
+ test('Can exclude complex nested sync', () {
+ tracker = asyncTracker = AsyncTimeTracker(trackNested: false);
+ nestedAsyncTracker = AsyncTimeTracker(trackNested: false);
+ final nestedAsyncTracker2 = AsyncTimeTracker(trackNested: false);
+ scopedTrack(() {
+ time = time.add(const Duration(seconds: 1));
+ nestedAsyncTracker.track(() {
+ time = time.add(const Duration(seconds: 2));
+ nestedAsyncTracker2.track(() {
+ time = time.add(const Duration(seconds: 4));
+ });
+ time = time.add(const Duration(seconds: 8));
+ });
+ time = time.add(const Duration(seconds: 16));
+ });
+ expect(asyncTracker.isFinished, true);
+ expect(asyncTracker.startTime, startTime);
+ expect(asyncTracker.stopTime, time);
+ expect(asyncTracker.duration, const Duration(seconds: 31));
+ expect(asyncTracker.innerDuration, const Duration(seconds: 17));
+ expect(asyncTracker.slices.length, greaterThan(1));
+ expect(nestedAsyncTracker.startTime.isAfter(startTime), true);
+ expect(nestedAsyncTracker.stopTime.isBefore(time), true);
+ expect(nestedAsyncTracker.duration, const Duration(seconds: 14));
+ expect(nestedAsyncTracker.innerDuration, const Duration(seconds: 10));
+ expect(nestedAsyncTracker.slices.length, greaterThan(1));
+ expect(nestedAsyncTracker2.startTime.isAfter(startTime), true);
+ expect(nestedAsyncTracker2.stopTime.isBefore(time), true);
+ expect(nestedAsyncTracker2.duration, const Duration(seconds: 4));
+ expect(nestedAsyncTracker2.innerDuration, const Duration(seconds: 4));
+ expect(nestedAsyncTracker2.slices.length, 1);
+ });
+
+ test(
+ 'Can track all on grand-parent level and '
+ 'exclude grand-childrens from parent', () {
+ tracker = asyncTracker = AsyncTimeTracker(trackNested: true);
+ nestedAsyncTracker = AsyncTimeTracker(trackNested: false);
+ final nestedAsyncTracker2 = AsyncTimeTracker();
+ scopedTrack(() {
+ time = time.add(const Duration(seconds: 1));
+ nestedAsyncTracker.track(() {
+ time = time.add(const Duration(seconds: 2));
+ nestedAsyncTracker2.track(() {
+ time = time.add(const Duration(seconds: 4));
+ });
+ time = time.add(const Duration(seconds: 8));
+ });
+ time = time.add(const Duration(seconds: 16));
+ });
+ expect(asyncTracker.isFinished, true);
+ expect(asyncTracker.startTime, startTime);
+ expect(asyncTracker.stopTime, time);
+ expect(asyncTracker.duration, const Duration(seconds: 31));
+ expect(asyncTracker.innerDuration, const Duration(seconds: 31));
+ expect(asyncTracker.slices.length, 1);
+ expect(nestedAsyncTracker.startTime.isAfter(startTime), true);
+ expect(nestedAsyncTracker.stopTime.isBefore(time), true);
+ expect(nestedAsyncTracker.duration, const Duration(seconds: 14));
+ expect(nestedAsyncTracker.innerDuration, const Duration(seconds: 10));
+ expect(nestedAsyncTracker.slices.length, greaterThan(1));
+ expect(nestedAsyncTracker2.startTime.isAfter(startTime), true);
+ expect(nestedAsyncTracker2.stopTime.isBefore(time), true);
+ expect(nestedAsyncTracker2.duration, const Duration(seconds: 4));
+ expect(nestedAsyncTracker2.innerDuration, const Duration(seconds: 4));
+ expect(nestedAsyncTracker2.slices.length, 1);
+ });
+
+ test('Can exclude nested async', () async {
+ tracker = asyncTracker = AsyncTimeTracker(trackNested: false);
+ await scopedTrack(() async {
+ time = time.add(const Duration(seconds: 1));
+ await nestedAsyncTracker.track(() async {
+ time = time.add(const Duration(seconds: 2));
+ await Future.value();
+ time = time.add(const Duration(seconds: 4));
+ await Future.value();
+ time = time.add(const Duration(seconds: 8));
+ });
+ time = time.add(const Duration(seconds: 16));
+ });
+ expect(asyncTracker.isFinished, true);
+ expect(asyncTracker.startTime, startTime);
+ expect(asyncTracker.stopTime, time);
+ expect(asyncTracker.duration, const Duration(seconds: 31));
+ expect(asyncTracker.innerDuration, const Duration(seconds: 17));
+ expect(asyncTracker.slices.length, greaterThan(1));
+ expect(nestedAsyncTracker.startTime.isAfter(startTime), true);
+ expect(nestedAsyncTracker.stopTime.isBefore(time), true);
+ expect(nestedAsyncTracker.duration, const Duration(seconds: 14));
+ expect(nestedAsyncTracker.innerDuration, const Duration(seconds: 14));
+ expect(nestedAsyncTracker.slices.length, greaterThan(1));
+ });
+
+ test('Can handle callbacks in excluded nested async', () async {
+ tracker = asyncTracker = AsyncTimeTracker(trackNested: false);
+ await scopedTrack(() async {
+ time = time.add(const Duration(seconds: 1));
+ final completer = Completer();
+ final future = completer.future.then((_) {
+ time = time.add(const Duration(seconds: 2));
+ });
+ await nestedAsyncTracker.track(() async {
+ time = time.add(const Duration(seconds: 4));
+ await Future.value();
+ time = time.add(const Duration(seconds: 8));
+ completer.complete();
+ await future;
+ time = time.add(const Duration(seconds: 16));
+ });
+ time = time.add(const Duration(seconds: 32));
+ });
+ expect(asyncTracker.isFinished, true);
+ expect(asyncTracker.startTime, startTime);
+ expect(asyncTracker.stopTime, time);
+ expect(asyncTracker.duration, const Duration(seconds: 63));
+ expect(asyncTracker.innerDuration, const Duration(seconds: 35));
+ expect(asyncTracker.slices.length, greaterThan(1));
+ expect(nestedAsyncTracker.startTime.isAfter(startTime), true);
+ expect(nestedAsyncTracker.stopTime.isBefore(time), true);
+ expect(nestedAsyncTracker.duration, const Duration(seconds: 30));
+ expect(nestedAsyncTracker.innerDuration, const Duration(seconds: 28));
+ expect(nestedAsyncTracker.slices.length, greaterThan(1));
+ });
+ });
+}
diff --git a/pkgs/unified_analytics/.gitignore b/pkgs/unified_analytics/.gitignore
new file mode 100644
index 0000000..9377e38
--- /dev/null
+++ b/pkgs/unified_analytics/.gitignore
@@ -0,0 +1,14 @@
+# Files and directories created by pub.
+.dart_tool/
+.packages
+
+# Conventional directory for build outputs.
+build/
+
+# Omit committing pubspec.lock for library packages; see
+# https://dart.dev/guides/libraries/private-files#pubspeclock.
+pubspec.lock
+
+.vscode/
+coverage/
+*.DS_Store
diff --git a/pkgs/unified_analytics/CHANGELOG.md b/pkgs/unified_analytics/CHANGELOG.md
new file mode 100644
index 0000000..7e42cba
--- /dev/null
+++ b/pkgs/unified_analytics/CHANGELOG.md
@@ -0,0 +1,188 @@
+## 7.0.1
+- Fixed `UnsupportedError` thrown when Event.exception is called without providing a value for `args`.
+
+## 7.0.0
+- Added a required parameter `screen` to the `Event.devtoolsEvent` constructor.
+- Added an optional parameter `additionalMetrics` to the `Event.devtoolsEvent` constructor.
+- Added `CustomMetrics` class for unified_analytics clients to define custom event metrics.
+- Removed parameters `uiDurationMicros`, `rasterDurationMicros`, `shaderCompilationDurationMicros`,
+`traceEventCount`, `cpuSampleCount`, `cpuStackDepth`, `heapDiffObjectsBefore`, `heapDiffObjectsAfter`,
+`heapObjectsTotal`, `rootSetCount`, `rowCount`, `inspectorTreeControllerId`, `androidAppId`, `iosBundleId`
+from the `Event.devtoolsEvent` constructor.
+
+## 6.1.5
+- Remove any `data` entries with a null value in the `Event.exception` constructor.
+
+## 6.1.4
+
+- Fix formatting and remove dependency on `package:intl`.
+- Remove direct usage of `package:path` in favor of `package:file`.
+- Added `androidAppId` and `iosBundleId` to the `Event.devtoolsEvent` constructor.
+
+## 6.1.3
+
+- Require Dart 3.4.
+- Added `isWasm` parameter to the `Event.devtoolsEvent` constructor.
+- Added an optional parameter `data` to the `Event.exception` constructor.
+
+## 6.1.2
+
+- Avoid opening large telemetry log files to prevent out of memory errors.
+
+## 6.1.1
+
+- Fixed bug where calling `Analytics.send` could result in a `FileSystemException` when unable to write to a log file.
+
+## 6.1.0
+
+- Added new event constructor `Event.devtoolsEvent` for the single devtools event with a new enum value `DashEvent.devtoolsEvent`
+
+## 6.0.0
+
+- Consolidate `Session` functionality into `UserProperty` to prevent race condition crash where session logic crashed before initializing `UserProperty`
+- Get rid of `late` variables throughout implementation class, `AnalyticsImpl`
+- Any error events (`Event.analyticsException`) encountered within package will be sent when invoking `Analytics.close`; replacing `ErrorHandler` functionality
+- Exposing new method for `FakeAnalytics.sendPendingErrorEvents` to send error events on command
+- Added `Event.fromJson` static method to generate instance of `Event` from JSON
+- Remove unused parameters `measurementId` and `apiSecret` from the `Analytics.test` constructor
+- Remove `Analytics.test` factory constructor in favor of `Analytics.fake` static method to return a `FakeAnalytics` instance
+- Remove `FakeAnalytics` default constructor in favor of `Analytics.fake`
+
+## 5.8.8
+
+- [Bug fix](https://github.com/dart-lang/tools/issues/252) rewrite the other call site for the session file
+
+## 5.8.7
+
+- [Bug fix](https://github.com/dart-lang/tools/issues/252) to rewrite the `last_ping` key into the session json file
+
+## 5.8.6
+
+- Refactored session handler class to use the last modified timestamp as the last ping value to prevent writing to file with each send
+- Bumping intl package to 0.19.0 to fix version solving issue with flutter_tools
+
+## 5.8.5
+
+- Fix late initialization error for `Analytics.userProperty` [bug](https://github.com/dart-lang/tools/issues/238)
+
+## 5.8.4
+
+- Exporting all enums from [`enums.dart`](https://github.com/dart-lang/tools/blob/main/pkgs/unified_analytics/lib/src/enums.dart) through `lib/testing.dart`
+
+## 5.8.3
+
+- [Fix bug](https://github.com/flutter/flutter/issues/143792) when parsing session json file
+
+## 5.8.2
+
+- Added new event `Event.analyticsException` to track internal errors for this package
+- Redirecting the `Analytics.test` factory to return an instance of `FakeAnalytics`
+- Exposing new helper function that can be used to parse the Dart SDK version
+
+## 5.8.1
+
+- Refactor logic for `okToSend` and `shouldShowMessage`
+- Check devtools config file for legacy opt out status
+
+## 5.8.0
+
+- Fix template string for consent message
+- Add `enabledFeatures` to constructor to collect features enabled for each dash tool
+
+## 5.7.0
+
+- Added the `Event.commandUsageValues` constructor
+
+## 5.6.0
+
+- Added the `Event.timing` constructor
+
+## 5.5.0
+
+- Edit to the `Event.flutterCommandResult` constructor to add `commandHasTerminal`
+- Added timeout for `Analytics.setTelemetry` to prevent the clients from hanging
+- Added the `Event.appleUsageEvent` constructor
+- Added the `Event.exception` constructor
+
+## 5.4.0
+
+- Added the `Event.codeSizeAnalysis` constructor
+
+## 5.3.0
+
+- User property "host_os_version" added to provide detail version information about the host
+- User property "locale" added to provide language related information
+- User property "client_ide" (optional) added to provide the IDE used by the Dash tool using this package, if applicable
+- Added the `Event.flutterCommandResult` constructor
+
+## 5.2.0
+
+- Added the `Event.hotRunnerInfo` constructor
+
+## 5.1.0
+
+- Added the `Event.flutterBuildInfo` constructor
+
+## 5.0.0
+
+- Update to the latest version of `package:dart_flutter_team_lints`
+- Using internal futures list to store send events
+- Added the `Event.doctorValidatorResult` constructor
+
+## 4.0.1
+
+- Adding constant for the NoOpAnalytics instance client ID to enable clients to reference it in tests
+
+## 4.0.0
+
+- Enhanced `LogFileStats` data to include information about flutter channel counts and tool counts
+- Added new method to suppress telemetry collection temporarily for current invocation via `analytics.suppressTelemetry()`
+- Added `SurveyHandler` feature to `Analytics` instance to fetch available surveys from remote endpoint to display to users along with functionality to dismiss them
+- Surveys will be disabled for any users that have been opted out
+- Shipping `FakeAnalytics` for clients of this tool that need to ensure workflows are sending events in tests
+- Adding getter to `Analytics` instance to fetch the client ID being sent to GA4
+
+## 3.0.0
+
+- Allow latest package versions for `file` and `http`
+- Introducing new `Event` class that will standardize what event data can be sent with each event
+- Deprecating the `sendEvent` method in favor of the `send` method
+
+## 2.0.0
+
+- Refactoring `dateStamp` utility function to be defined in `utils.dart` instead of having static methods in `Initializer` and `ConfigHandler`
+- Remove the `pddFlag` now that the revisions to the PDD have been finalized to persist data in the log file and session json file
+- Opting out will now delete the contents of the CLIENT ID, session json, and log files; opting back in will regenerate them as events send
+- `enableAsserts` parameter added to constructors for `Analytics` to check body of POST request for Google Analytics 4 limitations
+- Now checking if write permissions are enabled for user's home directory, if not allowed, `NoOpAnalytics` returned by `Analytics` factory constructor
+
+## 1.1.0
+
+- Added a `okToSend` getter so that clients can easily and accurately check the state of the consent mechanism.
+- Initialize the config file with user opted out if user was opted out in legacy Flutter and Dart analytics
+
+## 1.0.1
+
+- Error handling on the `analytics.sendEvent(...)` method to silently error out and return a `500` http status code to let tools using this package know Google Analytics did not receive the event (all successful requests will have a status code of `2xx` provided by Google Analytics)
+
+## 1.0.0
+
+- Error handling functionality added to prevent malformed session json data from causing a crash
+- Creating a new analytics constructor to point to a test instance of Google Analytics for developers
+- Align supported tool list with PDD
+- Exposing a new instance method that will need to be invoked when a client has successfully shown the consent message to the user `clientShowedMessage()`
+- Adding and incrementing a tool's version will automatically use the current consent message version instead of incrementing by 1
+- Default constructor has disabled the usage of local log file and session json file until revisions have landed to the privacy document
+
+## 0.1.2
+
+- Implemented fake Google Analytics Client for `Analytics.test(...)` constructor; marked with visible for testing annotation
+
+## 0.1.1
+
+- Bumping intl package to 0.18.0 to fix version solving issue with flutter_tools
+- LogFileStats includes more information about how many events are persisted and total count of how many times each event was sent
+
+## 0.1.0
+
+- Initial version
diff --git a/pkgs/unified_analytics/LICENSE b/pkgs/unified_analytics/LICENSE
new file mode 100644
index 0000000..ac90031
--- /dev/null
+++ b/pkgs/unified_analytics/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2023, 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/unified_analytics/README.md b/pkgs/unified_analytics/README.md
new file mode 100644
index 0000000..4263cfb
--- /dev/null
+++ b/pkgs/unified_analytics/README.md
@@ -0,0 +1,17 @@
+[](https://github.com/dart-lang/tools/actions/workflows/unified_analytics.yml)
+[](https://pub.dev/packages/unified_analytics)
+[](https://pub.dev/packages/unified_analytics/publisher)
+
+## What's this?
+
+This package is intended to be used on Dart and Flutter related
+tooling only. It provides APIs to send events to Google Analytics using the
+Measurement Protocol.
+
+This is not intended to be general purpose or consumed by the community. It is
+responsible for toggling analytics collection for related tooling on each
+developer's machine.
+
+## Using this package
+
+Refer to the [usage guide](USAGE_GUIDE.md).
diff --git a/pkgs/unified_analytics/USAGE_GUIDE.md b/pkgs/unified_analytics/USAGE_GUIDE.md
new file mode 100644
index 0000000..ef5bbea
--- /dev/null
+++ b/pkgs/unified_analytics/USAGE_GUIDE.md
@@ -0,0 +1,202 @@
+This package is intended to be used on Dart and Flutter related tooling only.
+It provides APIs to send events to Google Analytics using the Measurement Protocol.
+
+## Usage
+
+To get started using this package, import at the entrypoint dart file and
+initialize with the required parameters.
+
+The example file shows an end-to-end usage guide for using this package and
+can be referred to here [unified_analytics_example.dart](example/unified_analytics_example.dart).
+
+**IMPORTANT**: It is best practice to close the http client connection when finished
+sending events, otherwise, you may notice that the dart process hangs on exit. The example below
+shows how to handle closing the connection via `analytics.close()` method.
+
+[Link to documentation for http client's close method](https://pub.dev/documentation/http/latest/http/Client-class.html)
+
+
+## Opting In and Out of Analytics Collection
+
+It will be important for each tool to expose a trivial method to
+disabling or enabling analytics collection. Based on how the user interacts
+with the tool, this can be done through the CLI, IDE, etc. The tool will
+then pass a boolean to an API exposed by the package as shown below.
+
+```dart
+// Begin by initializing the class
+final Analytics analytics = Analytics(...);
+
+// The boolean below simulates the user deciding to opt-out
+// of Analytics collection
+final bool status = false;
+
+// Call the method to pass the boolean
+analytics.setTelemetry(status);
+```
+
+## Displaying Consent Message to Users
+
+When a user first uses any tool with this package enabled, the tool using
+this package will need to ensure that the user has seen the consent message.
+The tool using this package should check with the `Analytics` instance
+by invoking the `shouldShowMessage` getter. When this getter returns
+`true`, this means that the user has not been enrolled into analytics
+collection yet. It is at this point that the tool using this package will
+invoke the `getConsentMessage` getter to return a string to share with the
+user (each tool will have their own method of displaying the message
+through cli stdout, popup modal, etc.). Once the message has been shown,
+the tool using this package will need to confirm to the `Analytics` instance
+that it has shown the message; it is at this point that the user has
+officially been onboarded to analytics collection.
+
+
+
+```dart
+// Begin by initializing the class near the entrypoint
+final Analytics analytics = Analytics(...);
+
+// This conditional should always run; the first time it is run, this
+// will return true since the consent message has never been shown
+if (analytics.shouldShowMessage) {
+
+ // Simulates displaying the message, this will vary from
+ // client to client; ie. stdout, popup in IDE, etc.
+ print(analytics.getConsentMessage);
+
+ // After receiving confirmation that the message has been
+ // displayed, invoking the below method will successfully
+ // onboard the tool into the config file and allow for
+ // events to be sent on the next creation of the analytics
+ // instance
+ analytics.clientShowedMessage();
+}
+```
+
+## Checking User Opt-In Status
+
+Some tools may need to know if the user has opted in for Analytics
+collection in order to enable additional functionality. The example below
+shows how to check the status.
+
+```dart
+// Begin by initializing the class
+final Analytics analytics = Analytics(...);
+
+// This getter will return a boolean showing the status;
+// print statement used for trivial usage example
+print('This user's status: ${analytics.telemetryEnabled}'); // true if opted-in
+```
+
+## Checking for New Versions of Consent Message
+
+In the event that the package consent messaging needs has been updated, an
+API has been exposed on an instance of `Analytics` that will notify the tool
+using this package whether to display the message again.
+
+```dart
+// Begin by initializing the class
+//
+// This is assuming that the tool has already been onboarded
+// and that the user has already seen the previous version of
+// the consent message
+final Analytics analytics = Analytics(...);
+
+
+// Much like the first example, if there is a new version of
+// the tools message that needs to be shown, use the same work
+// workflow
+if (analytics.shouldShowMessage) {
+
+ // Simulates displaying the message, this will vary from
+ // client to client; ie. stdout, popup in IDE, etc.
+ print(analytics.getConsentMessage);
+
+ // After receiving confirmation that the message has been
+ // displayed, invoking the below method will successfully
+ // onboard the tool into the config file and allow for
+ // events to be sent on the next creation of the analytics
+ // instance
+ analytics.clientShowedMessage();
+}
+```
+
+It is important to note events will not be sent if there is a new version of
+the consent message.
+
+## Developing Within `package:unified_analytics`
+
+### Adding new data classes
+
+#### User properties
+In Google Analytics, new data fields can be collected as user properties
+or events.
+
+User properties are key-value pairs that can be used to segment users. For example,
+the Flutter channel used. To request that a new user property
+be added, file an issue [using this template](https://github.com/dart-lang/tools/issues/new?template=unified_analytics_user_property.yml).
+
+To add a new user property, add a new property to the `UserProperty` class
+in the [`user_property.dart` file](./lib/src/user_property.dart).
+
+#### Events
+Events are actions that the user, or tool, performs. In Google Analytics,
+events can have associated data. This event data is stored
+in key-value pairs.
+
+To request new events, or event data, file an issue
+[using this template](https://github.com/dart-lang/tools/issues/new?template=unified_analytics_event.yml).
+
+To add a new event, create a new field in the `DashEvent` enum (if necessary) in
+the [`enums.dart` file](./lib/src/enums.dart).
+
+Then, add event data, create a new constructor for the `Event` class
+in the [`event.dart` file](./lib/src/event.dart).
+
+
+### Testing event collection
+
+When contributing to this package, if the developer needs to verify that
+events have been sent, the developer should the use development constructor
+so that the events being sent are not going into the production instance.
+
+```dart
+final Analytics analytics = Analytics.development(...);
+```
+
+Reach out to maintainers to get access to the test Google Analytics endpoint.
+
+## Advanced Usage: Querying Locally Persisted Logs
+
+This package enables tools to persist the events that have been sent
+to Google Analytics for logging by default. This can be very helpful if
+tools would like to understand the user's activity level across all
+related tooling. For example, if querying the locally persisted logs
+shows that the user has not been active for N number of days, a tool that
+works within an IDE can prompt the user with a survey to understand why their
+level of activity has dropped.
+
+The snippet below shows how to invoke the query and a sample response.
+
+```dart
+// Begin by initializing the class
+final Analytics analytics = Analytics(...);
+
+// Printing the query results returns json formatted
+// string to view; data can also be accessed through
+// [LogFileStats] getters
+print(analytics.logFileStats());
+```
+Refer to the `LogFileStats` instance [variables](lib/src/log_handler.dart) for details on the result.
+
+Explanation of the each key above
+
+- startDateTime: the earliest event that was sent
+- minsFromStartDateTime: the number of minutes elapsed since the earliest message
+- endDateTime: the latest, most recent event that was sent
+- minsFromEndDateTime: the number of minutes elapsed since the latest message
+- sessionCount: count of sessions; sessions have a minimum time of 30 minutes
+- flutterChannelCount: count of flutter channels (can be 0 if developer is a Dart dev only)
+- toolCount: count of the Dart and Flutter tools sending analytics
+- recordCount: count of the total number of events in the log file
+- eventCount: counts each unique event and how many times they occurred in the log file
\ No newline at end of file
diff --git a/pkgs/unified_analytics/analysis_options.yaml b/pkgs/unified_analytics/analysis_options.yaml
new file mode 100644
index 0000000..746ad64
--- /dev/null
+++ b/pkgs/unified_analytics/analysis_options.yaml
@@ -0,0 +1,10 @@
+# This file configures the static analysis results for your project (errors,
+# warnings, and lints).
+
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+linter:
+ rules:
+ - avoid_catches_without_on_clauses
+ - prefer_final_in_for_each
+ - prefer_final_locals
diff --git a/pkgs/unified_analytics/coverage_runner.sh b/pkgs/unified_analytics/coverage_runner.sh
new file mode 100755
index 0000000..4043614
--- /dev/null
+++ b/pkgs/unified_analytics/coverage_runner.sh
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+# Generate `coverage/lcov.info` file
+flutter test --coverage
+
+# Generate HTML report
+# Note: on macOS you need to have lcov installed on your system (`brew install lcov`) to use this:
+genhtml coverage/lcov.info -o coverage/html
+
+# Open the report
+open coverage/html/index.html
diff --git a/pkgs/unified_analytics/example/sample_rate.dart b/pkgs/unified_analytics/example/sample_rate.dart
new file mode 100644
index 0000000..51b0cdb
--- /dev/null
+++ b/pkgs/unified_analytics/example/sample_rate.dart
@@ -0,0 +1,90 @@
+// Copyright (c) 2023, 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:unified_analytics/src/utils.dart';
+
+/// The purpose of this example file is to demonstrate the sampling
+/// rate functionality from the survey handler.
+///
+/// It defines a `remoteUniqueId` that subs in for a real survey's unique
+/// ID that is hosted in the remote json file.
+///
+/// Begin the simulation by setting the `testSampleRate` and `iterations`
+/// variables, where `iterations` simulates real users and `testSampleRate`
+/// represents a fraction of how many people should be sampled.
+///
+/// In this example, we have set the `testSampleRate` to `0.3`, meaning we want
+/// sample 30% of users, and `iterations` to `10,000`, which simulates `10,000`
+/// users.
+///
+/// Running the script with predefined seed of `123` will
+/// generate the below `stdout`
+/// ```
+/// Test sample rate = 0.3
+/// Number of iterations = 10000
+/// ---
+///
+/// Count of iterations sampled (successes) = 3046
+/// Actual sample rate = 0.3046
+/// ---
+///
+/// Runtime = 8ms
+/// ```
+///
+/// The actual results yielded 3,046 people selected for a rate
+/// of `30.46%` which is about the `30%` defined in `testSampleRate`.
+void main() {
+ // Seed has been set to replicate results
+ //
+ // Test with your own seed and alter other parameters
+ // as needed
+ final uuidGenerator = Uuid(123);
+
+ // Randomly generate an ID that will simulate being used for
+ // a given survey
+ final remoteUniqueId = uuidGenerator.generateV4();
+
+ // Define a sampling rate that we would like to test
+ //
+ // Setting 0.3 means any generated doubles less than or
+ // equal to 0.3 results in a survey getting delievered
+ const testSampleRate = 0.3;
+
+ // Define how many iterations to run, each iteration can
+ // be thought of as a developer using a dash tool
+ const iterations = 10000;
+
+ // Initializing a counter that will count the number of
+ // iterations that were below the sampling rate
+ var count = 0;
+
+ final start = DateTime.now();
+ for (var i = 0; i < iterations; i++) {
+ // Each newly generated ID is simulating a unique
+ // developer's CLIENT ID that is persisted on their disk
+ final clientId = uuidGenerator.generateV4();
+
+ // Generate a double that will be compared against the sampleRate
+ final generatedDouble = sampleRate(remoteUniqueId, clientId);
+
+ // Count successes if the generated double is less than our
+ // testing sample rate
+ if (generatedDouble <= testSampleRate) {
+ count++;
+ }
+ }
+ final end = DateTime.now();
+
+ print('''
+Test sample rate = $testSampleRate
+Number of iterations = $iterations
+---
+
+Count of iterations sampled (successes) = $count
+Actual sample rate = ${(count / iterations).toStringAsFixed(4)}
+---
+
+Runtime = ${end.difference(start).inMilliseconds}ms
+''');
+}
diff --git a/pkgs/unified_analytics/example/serving_surveys.dart b/pkgs/unified_analytics/example/serving_surveys.dart
new file mode 100644
index 0000000..d56339e
--- /dev/null
+++ b/pkgs/unified_analytics/example/serving_surveys.dart
@@ -0,0 +1,165 @@
+// Copyright (c) 2023, 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:clock/clock.dart';
+import 'package:file/file.dart';
+import 'package:file/memory.dart';
+
+import 'package:unified_analytics/src/constants.dart';
+import 'package:unified_analytics/src/enums.dart';
+import 'package:unified_analytics/src/survey_handler.dart';
+import 'package:unified_analytics/unified_analytics.dart';
+
+/// This example code is intended to only be used as guidance for
+/// clients using this package. Clients using this package should avoid
+/// the use of the [Analytics.fake] static method.
+///
+/// It was used in this example file so that the real [FileSystem] was swapped
+/// out for a [MemoryFileSystem] so that repeated runs of this script yield
+/// the same results.
+void main() async {
+ late final MemoryFileSystem fs;
+ late final Analytics analytics;
+ late final Directory home;
+ // We need to initialize with a fake clock since the surveys have
+ // a period of time they are valid for
+ await withClock(Clock.fixed(DateTime(2023, 3, 3, 12, 0)), () async {
+ // Use a memory file system to repeatedly run this example
+ // file with the test instance
+ fs = MemoryFileSystem(style: FileSystemStyle.posix);
+ home = fs.directory('home');
+ home.createSync();
+
+ // The purpose of `initialAnalytics` is so that the tool is able to
+ // send events after its first run; this instance won't be used below
+ //
+ // ignore: invalid_use_of_visible_for_testing_member
+ final initialAnalytics = Analytics.fake(
+ tool: DashTool.flutterTool,
+ homeDirectory: home,
+ dartVersion: 'dartVersion',
+ fs: fs,
+ platform: DevicePlatform.macos,
+ );
+ // The below command allows `DashTool.flutterTool` to send telemetry
+ initialAnalytics.clientShowedMessage();
+
+ // ignore: invalid_use_of_visible_for_testing_member
+ analytics = Analytics.fake(
+ tool: DashTool.flutterTool,
+ homeDirectory: home,
+ dartVersion: 'dartVersion',
+ fs: fs,
+ platform: DevicePlatform.macos,
+ surveyHandler: FakeSurveyHandler.fromList(
+ dismissedSurveyFile: home
+ .childDirectory(kDartToolDirectoryName)
+ .childFile(kDismissedSurveyFileName),
+ initializedSurveys: [
+ Survey(
+ uniqueId: 'uniqueId',
+ startDate: DateTime(2023, 1, 1),
+ endDate: DateTime(2023, 5, 31),
+ description: 'description',
+ snoozeForMinutes: 10,
+ samplingRate: 1.0,
+ excludeDashToolList: [],
+ conditionList: [],
+ buttonList: [
+ SurveyButton(
+ buttonText: 'View Survey',
+ action: 'accept',
+ promptRemainsVisible: false,
+ url: 'http://example.com',
+ ),
+ SurveyButton(
+ buttonText: 'More Info',
+ action: 'snooze',
+ promptRemainsVisible: true,
+ url: 'http://example2.com',
+ ),
+ SurveyButton(
+ buttonText: 'Dismiss Survey',
+ action: 'dismiss',
+ promptRemainsVisible: false,
+ )
+ ],
+ ),
+ ],
+ ));
+ });
+
+ // Each client of this package will be able to fetch all of
+ // the available surveys with the below method
+ //
+ // Sample rate will be applied automatically; it also won't
+ // fetch any surveys in the snooze period or if they have
+ // been dismissed
+ final surveyList = await analytics.fetchAvailableSurveys();
+ assert(surveyList.length == 1);
+
+ // Grab the first and only survey to simulate displaying it to a user
+ final survey = surveyList.first;
+ print('Simulating displaying the survey with a print below:');
+ print('Survey id: ${survey.uniqueId}\n');
+
+ // Immediately after displaying the survey, the method below
+ // should be run so that no other clients using this tool will show
+ // it at the same time
+ //
+ // It will "snoozed" when the below is run as well as reported to
+ // Google Analytics 4 that this survey was shown
+ analytics.surveyShown(survey);
+
+ // Get the file where this is persisted to show it getting updated
+ final persistedSurveyFile = home
+ .childDirectory(kDartToolDirectoryName)
+ .childFile(kDismissedSurveyFileName);
+ print('The contents of the json file '
+ 'after invoking `analytics.surveyShown(survey);`');
+ print('${persistedSurveyFile.readAsStringSync()}\n');
+
+ // Change the index below to decide which button to simulate pressing
+ //
+ // 0 - accept
+ // 1 - snooze
+ // 2 - dismiss
+ final selectedButtonIndex = 1;
+ assert([0, 1, 2].contains(selectedButtonIndex));
+
+ // Get the survey button by index that will need to be passed along with
+ // the survey to simulate an interaction with the survey
+ final selectedSurveyButton = survey.buttonList[selectedButtonIndex];
+ print('The simulated button pressed was: '
+ '"${selectedSurveyButton.buttonText}" '
+ '(action = ${selectedSurveyButton.action})\n');
+
+ // The below method will handle whatever action the button
+ analytics.surveyInteracted(
+ survey: survey,
+ surveyButton: selectedSurveyButton,
+ );
+
+ // Conditional to check if there is a URl to route to
+ if (selectedSurveyButton.url != null) {
+ print('***This button also has a survey URL link '
+ 'to route to at "${selectedSurveyButton.url}"***\n');
+ }
+
+ // Conditional to check what simulating a popup to stay up
+ if (selectedSurveyButton.promptRemainsVisible) {
+ print('***This button has its promptRemainsVisible field set to `true` '
+ 'so this simulates what seeing a pop up again would look like***\n');
+ }
+
+ print('The contents of the json file '
+ 'after invoking '
+ '`analytics.surveyInteracted(survey: survey, '
+ 'surveyButton: selectedSurveyButton);`');
+ print('${persistedSurveyFile.readAsStringSync()}\n');
+
+ // Demonstrating that the survey doesn't get returned again
+ print('Attempting to fetch surveys again will result in an empty list');
+ print(await analytics.fetchAvailableSurveys());
+}
diff --git a/pkgs/unified_analytics/example/unified_analytics_example.dart b/pkgs/unified_analytics/example/unified_analytics_example.dart
new file mode 100644
index 0000000..e0ddaff
--- /dev/null
+++ b/pkgs/unified_analytics/example/unified_analytics_example.dart
@@ -0,0 +1,65 @@
+// Copyright (c) 2023, 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:unified_analytics/unified_analytics.dart';
+
+// Globally instantiate the analytics class at the entry
+// point of the tool
+//
+// Development constructor used here so we don't push
+// to production when running
+final Analytics analytics = Analytics.development(
+ tool: DashTool.flutterTool,
+ flutterChannel: 'ey-test-channel',
+ flutterVersion: 'Flutter 3.6.0-7.0.pre.47',
+ clientIde: 'VSCode',
+ dartVersion: 'Dart 2.19.0',
+ // This can be set to true while testing to validate
+ // against GA4 usage limitations (character limits, etc.)
+ enableAsserts: false,
+ enabledFeatures: 'feature-1,feature-2',
+);
+
+// Timing a process and sending the event
+void main() async {
+ final start = DateTime.now();
+
+ // Each client using this package will have it's own
+ // method to show the message but the below is a trivial
+ // example of how to properly initialize the analytics instance
+ if (analytics.shouldShowMessage) {
+ // Simulates displaying the message, this will vary from
+ // client to client; ie. stdout, popup in IDE, etc.
+ print(analytics.getConsentMessage);
+
+ // After receiving confirmation that the message has been
+ // displayed, invoking the below method will successfully
+ // onboard the tool into the config file and allow for
+ // events to be sent on the next creation of the analytics
+ // instance
+ //
+ // The rest of the example below assumes that the tool has
+ // already been onboarded in a previous run
+ analytics.clientShowedMessage();
+ }
+
+ print('Current user ${analytics.clientId} '
+ 'is opted in: ${analytics.telemetryEnabled}');
+
+ // Example of long running process
+ await Future<void>.delayed(const Duration(milliseconds: 100));
+
+ // Calculate the metric to send
+ final runTime = DateTime.now().difference(start).inMilliseconds;
+
+ // Create the event that will be sent for the hot reload time
+ // as an example
+ final hotReloadEvent = Event.hotReloadTime(timeMs: runTime);
+
+ // Make a call to the [Analytics] api to send the data
+ analytics.send(hotReloadEvent);
+
+ // Close the client connection on exit
+ await analytics.close();
+}
diff --git a/pkgs/unified_analytics/lib/src/analytics.dart b/pkgs/unified_analytics/lib/src/analytics.dart
new file mode 100644
index 0000000..cc8b4bc
--- /dev/null
+++ b/pkgs/unified_analytics/lib/src/analytics.dart
@@ -0,0 +1,857 @@
+// Copyright (c) 2023, 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:http/http.dart';
+import 'package:meta/meta.dart';
+
+import 'asserts.dart';
+import 'config_handler.dart';
+import 'constants.dart';
+import 'enums.dart';
+import 'event.dart';
+import 'ga_client.dart';
+import 'initializer.dart';
+import 'log_handler.dart';
+import 'survey_handler.dart';
+import 'user_property.dart';
+import 'utils.dart';
+
+abstract class Analytics {
+ /// The default factory constructor that will return an implementation
+ /// of the [Analytics] abstract class using the [LocalFileSystem].
+ ///
+ /// If [enableAsserts] is set to `true`, then asserts for GA4 limitations
+ /// will be enabled.
+ ///
+ /// [flutterChannel] and [flutterVersion] are nullable in case the client
+ /// using this package is unable to resolve those values.
+ ///
+ /// An optional parameter [clientIde] is also available for dart and flutter
+ /// tooling that are running from IDEs can be resolved. Such as "VSCode"
+ /// running the flutter-tool.
+ ///
+ /// [enabledFeatures] is also an optional field that can be added to collect
+ /// any features that are enabled for a user. For example,
+ /// "enable-linux-desktop,cli-animations" are two features that can be enabled
+ /// for the flutter-tool.
+ factory Analytics({
+ required DashTool tool,
+ required String dartVersion,
+ String? flutterChannel,
+ String? flutterVersion,
+ String? clientIde,
+ String? enabledFeatures,
+ bool enableAsserts = false,
+ }) {
+ // Create the instance of the file system so clients don't need
+ // resolve on their own
+ const FileSystem fs = LocalFileSystem();
+
+ // Ensure that the home directory has permissions enabled to write
+ final homeDirectory = getHomeDirectory(fs);
+ if (homeDirectory == null ||
+ !checkDirectoryForWritePermissions(homeDirectory)) {
+ return const NoOpAnalytics();
+ }
+
+ // Resolve the OS using dart:io
+ final DevicePlatform platform;
+ if (io.Platform.operatingSystem == 'linux') {
+ platform = DevicePlatform.linux;
+ } else if (io.Platform.operatingSystem == 'macos') {
+ platform = DevicePlatform.macos;
+ } else {
+ platform = DevicePlatform.windows;
+ }
+
+ // Create the instance of the GA Client which will create
+ // an [http.Client] to send requests
+ final gaClient = GAClient(
+ measurementId: kGoogleAnalyticsMeasurementId,
+ apiSecret: kGoogleAnalyticsApiSecret,
+ );
+
+ final firstRun = runInitialization(homeDirectory: homeDirectory);
+
+ return AnalyticsImpl(
+ tool: tool,
+ homeDirectory: homeDirectory,
+ flutterChannel: flutterChannel,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ platform: platform,
+ toolsMessageVersion: kToolsMessageVersion,
+ fs: fs,
+ gaClient: gaClient,
+ surveyHandler: SurveyHandler(
+ dismissedSurveyFile: homeDirectory
+ .childDirectory(kDartToolDirectoryName)
+ .childFile(kDismissedSurveyFileName),
+ ),
+ enableAsserts: enableAsserts,
+ clientIde: clientIde,
+ enabledFeatures: enabledFeatures,
+ firstRun: firstRun,
+ );
+ }
+
+ /// Factory constructor to return the [AnalyticsImpl] class with
+ /// Google Analytics credentials that point to a test instance and
+ /// not the production instance where live data will be sent.
+ ///
+ /// By default, [enableAsserts] is set to `true` to check against
+ /// GA4 limitations.
+ ///
+ /// [flutterChannel] and [flutterVersion] are nullable in case the client
+ /// using this package is unable to resolve those values.
+ factory Analytics.development({
+ required DashTool tool,
+ required String dartVersion,
+ String? flutterChannel,
+ String? flutterVersion,
+ String? clientIde,
+ String? enabledFeatures,
+ bool enableAsserts = true,
+ }) {
+ // Create the instance of the file system so clients don't need
+ // resolve on their own
+ const FileSystem fs = LocalFileSystem();
+
+ // Ensure that the home directory has permissions enabled to write
+ final homeDirectory = getHomeDirectory(fs);
+ if (homeDirectory == null) {
+ throw Exception('Unable to determine the home directory, '
+ 'ensure it is available in the environment');
+ }
+ if (!checkDirectoryForWritePermissions(homeDirectory)) {
+ throw Exception('Permissions error on the home directory!');
+ }
+
+ // Resolve the OS using dart:io
+ final DevicePlatform platform;
+ if (io.Platform.operatingSystem == 'linux') {
+ platform = DevicePlatform.linux;
+ } else if (io.Platform.operatingSystem == 'macos') {
+ platform = DevicePlatform.macos;
+ } else {
+ platform = DevicePlatform.windows;
+ }
+
+ // Credentials defined below for the test Google Analytics instance
+ const kTestMeasurementId = 'G-N1NXG28J5B';
+ const kTestApiSecret = '4yT8__oER3Cd84dtx6r-_A';
+
+ // Create the instance of the GA Client which will create
+ // an [http.Client] to send requests
+ final gaClient = GAClient(
+ measurementId: kTestMeasurementId,
+ apiSecret: kTestApiSecret,
+ );
+
+ final firstRun = runInitialization(homeDirectory: homeDirectory);
+
+ return AnalyticsImpl(
+ tool: tool,
+ homeDirectory: homeDirectory,
+ flutterChannel: flutterChannel,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ platform: platform,
+ toolsMessageVersion: kToolsMessageVersion,
+ fs: fs,
+ gaClient: gaClient,
+ surveyHandler: SurveyHandler(
+ dismissedSurveyFile: homeDirectory
+ .childDirectory(kDartToolDirectoryName)
+ .childFile(kDismissedSurveyFileName),
+ ),
+ enableAsserts: enableAsserts,
+ clientIde: clientIde,
+ enabledFeatures: enabledFeatures,
+ firstRun: firstRun,
+ );
+ }
+
+ /// The shared identifier for Flutter and Dart related tooling using
+ /// package:unified_analytics.
+ String get clientId;
+
+ /// Retrieves the consent message to prompt users with on first
+ /// run or when the message has been updated.
+ String get getConsentMessage;
+
+ /// Returns true if it is OK to send an analytics message. Do not cache,
+ /// as this depends on factors that can change, such as the configuration
+ /// file contents.
+ bool get okToSend;
+
+ /// Returns a map object with all of the tools that have been parsed
+ /// out of the configuration file.
+ Map<String, ToolInfo> get parsedTools;
+
+ /// Boolean that lets the client know if they should display the message.
+ bool get shouldShowMessage;
+
+ /// Boolean indicating whether or not telemetry is enabled.
+ bool get telemetryEnabled;
+
+ /// Returns a map representation of the [UserProperty] for the [Analytics]
+ /// instance.
+ ///
+ /// This is what will get sent to Google Analytics with every request.
+ Map<String, Map<String, Object?>> get userPropertyMap;
+
+ /// Method to be invoked by the client using this package to confirm
+ /// that the client has shown the message and that it can be added to
+ /// the config file and start sending events the next time it starts up.
+ void clientShowedMessage();
+
+ /// Call this method when the tool using this package is closed.
+ ///
+ /// Prevents the tool from hanging when if there are still requests
+ /// that need to be sent off.
+ ///
+ /// Providing [delayDuration] in milliseconds will allow the instance
+ /// to wait the provided time before closing the http connection. Keeping
+ /// the connection open for some time will allow any pending events that
+ /// are waiting to be sent to the Google Analytics server. Default value
+ /// of 250 ms applied.
+ Future<void> close({int delayDuration = kDelayDuration});
+
+ /// Method to fetch surveys from the endpoint [kContextualSurveyUrl].
+ ///
+ /// Any survey that is returned by this method has already passed
+ /// the survey conditions specified in the remote survey metadata file.
+ ///
+ /// If the method returns an empty list, then there are no surveys to be
+ /// shared with the user.
+ Future<List<Survey>> fetchAvailableSurveys();
+
+ /// Query the persisted event data stored on the user's machine.
+ ///
+ /// Returns null if there are no persisted logs.
+ LogFileStats? logFileStats();
+
+ /// Send preconfigured events using specific named constructors
+ /// on the [Event] class.
+ ///
+ /// Example
+ /// ```dart
+ /// analytics.send(Event.memory(periodSec: 123));
+ /// ```
+ void send(Event event);
+
+ /// Pass a boolean to either enable or disable telemetry and make
+ /// the necessary changes in the persisted configuration file.
+ ///
+ /// Setting the telemetry status will also send an event to GA
+ /// indicating the latest status of the telemetry from [reportingBool].
+ Future<void> setTelemetry(bool reportingBool);
+
+ /// Calling this will result in telemetry collection being suppressed for
+ /// the current invocation.
+ ///
+ /// If you would like to permanently disable telemetry
+ /// collection use:
+ ///
+ /// ```dart
+ /// analytics.setTelemetry(false)
+ /// ```
+ void suppressTelemetry();
+
+ /// Method to run after interacting with a [Survey] instance.
+ ///
+ /// Pass a [Survey] instance which can be retrieved from
+ /// [Analytics.fetchAvailableSurveys].
+ ///
+ /// [surveyButton] is the button that was interacted with by the user.
+ void surveyInteracted({
+ required Survey survey,
+ required SurveyButton surveyButton,
+ });
+
+ /// Method to be called after a survey has been shown to the user.
+ ///
+ /// Calling this will snooze the survey so it won't be shown immediately.
+ ///
+ /// The snooze period is defined by the [Survey.snoozeForMinutes] field.
+ void surveyShown(Survey survey);
+
+ /// Returns an instance of [FakeAnalytics] which can be used in tests to check
+ /// for certain [Event] instances within [FakeAnalytics.sentEvents].
+ @visibleForTesting
+ static FakeAnalytics fake({
+ required DashTool tool,
+ required Directory homeDirectory,
+ required String dartVersion,
+ required MemoryFileSystem fs,
+ String? flutterChannel,
+ String? flutterVersion,
+ String? clientIde,
+ String? enabledFeatures,
+ SurveyHandler? surveyHandler,
+ GAClient? gaClient,
+ DevicePlatform platform = DevicePlatform.linux,
+ int toolsMessageVersion = kToolsMessageVersion,
+ String toolsMessage = kToolsMessage,
+ bool enableAsserts = true,
+ }) {
+ final firstRun = runInitialization(homeDirectory: homeDirectory);
+
+ return FakeAnalytics._(
+ tool: tool,
+ homeDirectory: homeDirectory,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ platform: platform,
+ fs: fs,
+ surveyHandler: surveyHandler ??
+ FakeSurveyHandler.fromList(
+ dismissedSurveyFile: homeDirectory
+ .childDirectory(kDartToolDirectoryName)
+ .childFile(kDismissedSurveyFileName),
+ initializedSurveys: [],
+ ),
+ gaClient: gaClient ?? const FakeGAClient(),
+ clientIde: clientIde,
+ enabledFeatures: enabledFeatures,
+ firstRun: firstRun,
+ enableAsserts: enableAsserts,
+ );
+ }
+}
+
+class AnalyticsImpl implements Analytics {
+ final DashTool tool;
+ final FileSystem fs;
+ final int toolsMessageVersion;
+ final ConfigHandler _configHandler;
+ final GAClient _gaClient;
+ final SurveyHandler _surveyHandler;
+ final File _clientIdFile;
+ final UserProperty _userProperty;
+ final LogHandler _logHandler;
+
+ /// Tells the client if they need to show a message to the
+ /// user; this will return true if it is the first time the
+ /// package is being used for a developer or if the consent
+ /// message has been updated by the package.
+ bool _showMessage = false;
+
+ /// When set to `true`, various assert statements will be enabled
+ /// to ensure usage of this class is within GA4 limitations.
+ final bool _enableAsserts;
+
+ /// Telemetry suppression flag that is set via [Analytics.suppressTelemetry].
+ bool _telemetrySuppressed = false;
+
+ /// Indicates if this is the first run for a given tool.
+ bool _firstRun = false;
+
+ /// The list of futures that will contain all of the send events
+ /// from the [GAClient].
+ final _futures = <Future<Response>>[];
+
+ /// Internal value for the client id which will be lazily loaded.
+ String? _clientId;
+
+ /// Internal collection of [Event]s that have been sent
+ /// for errors encountered within package:unified_analytics.
+ ///
+ /// Stores each of the events that have been sent to GA4 so that the
+ /// same error doesn't get sent twice.
+ final Set<Event> _sentErrorEvents = {};
+
+ AnalyticsImpl({
+ required this.tool,
+ required Directory homeDirectory,
+ required String? flutterChannel,
+ required String? flutterVersion,
+ required String? clientIde,
+ required String? enabledFeatures,
+ required String dartVersion,
+ required DevicePlatform platform,
+ required this.toolsMessageVersion,
+ required this.fs,
+ required GAClient gaClient,
+ required SurveyHandler surveyHandler,
+ required bool enableAsserts,
+ required bool firstRun,
+ }) : _gaClient = gaClient,
+ _surveyHandler = surveyHandler,
+ _enableAsserts = enableAsserts,
+ _clientIdFile = homeDirectory
+ .childDirectory(kDartToolDirectoryName)
+ .childFile(kClientIdFileName),
+ _userProperty = UserProperty(
+ sessionFile: homeDirectory
+ .childDirectory(kDartToolDirectoryName)
+ .childFile(kSessionFileName),
+ flutterChannel: flutterChannel,
+ host: platform.label,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ tool: tool.label,
+ // We truncate this to a maximum of 36 characters since this can
+ // a very long string for some operating systems
+ hostOsVersion:
+ truncateStringToLength(io.Platform.operatingSystemVersion, 36),
+ locale: io.Platform.localeName,
+ clientIde: clientIde,
+ enabledFeatures: enabledFeatures,
+ ),
+ _configHandler = ConfigHandler(
+ homeDirectory: homeDirectory,
+ configFile: homeDirectory
+ .childDirectory(kDartToolDirectoryName)
+ .childFile(kConfigFileName),
+ ),
+ _logHandler = LogHandler(
+ logFile: homeDirectory
+ .childDirectory(kDartToolDirectoryName)
+ .childFile(kLogFileName),
+ ) {
+ // This initializer class will let the instance know
+ // if it was the first run; if it is, nothing will be sent
+ // on the first run
+ if (firstRun) {
+ _showMessage = true;
+ _firstRun = true;
+ } else {
+ _showMessage = false;
+ _firstRun = false;
+ }
+
+ // Check if the tool has already been onboarded, and if it
+ // has, check if the latest message version is greater to
+ // prompt the client to show a message
+ //
+ // If the tool has not been added to the config file, then
+ // we will show the message as well
+ final currentVersion =
+ _configHandler.parsedTools[tool.label]?.versionNumber ?? -1;
+ if (currentVersion < toolsMessageVersion) {
+ _showMessage = true;
+
+ // If the message version has been updated, it will be considered
+ // as if it was a first run and any events attempting to get sent
+ // will be blocked
+ _firstRun = true;
+ }
+ }
+
+ @override
+ String get clientId {
+ if (!_clientIdFile.existsSync()) {
+ createClientIdFile(clientIdFile: _clientIdFile);
+ }
+ _clientId ??= _clientIdFile.readAsStringSync();
+
+ return _clientId!;
+ }
+
+ @override
+ String get getConsentMessage {
+ // The command to swap in the consent message
+ final commandString =
+ tool == DashTool.flutterTool || tool == DashTool.devtools
+ ? 'flutter'
+ : 'dart';
+
+ return kToolsMessage
+ .replaceAll('{{ toolDescription }}', tool.description)
+ .replaceAll('{{ toolName }}', commandString);
+ }
+
+ /// Checking the [telemetryEnabled] boolean reflects what the
+ /// config file reflects.
+ ///
+ /// Checking the [_showMessage] boolean indicates if the consent
+ /// message has been shown for the user, this boolean is set to `true`
+ /// when the tool using this package invokes the [clientShowedMessage]
+ /// method.
+ ///
+ /// If the user has suppressed telemetry [_telemetrySuppressed] will
+ /// return `true` to prevent events from being sent for current invocation.
+ ///
+ /// Checking if it is the first time a tool is running with this package
+ /// as indicated by [_firstRun].
+ @override
+ bool get okToSend =>
+ telemetryEnabled && !_showMessage && !_telemetrySuppressed && !_firstRun;
+
+ @override
+ Map<String, ToolInfo> get parsedTools => _configHandler.parsedTools;
+
+ @override
+ bool get shouldShowMessage => _showMessage;
+
+ @override
+ bool get telemetryEnabled => _configHandler.telemetryEnabled;
+
+ @override
+ Map<String, Map<String, Object?>> get userPropertyMap =>
+ _userProperty.preparePayload();
+
+ @override
+ void clientShowedMessage() {
+ // Check the tool needs to be added to the config file
+ if (!_configHandler.parsedTools.containsKey(tool.label)) {
+ _configHandler.addTool(
+ tool: tool.label,
+ versionNumber: toolsMessageVersion,
+ );
+ }
+
+ // When the tool already exists but the consent message version
+ // has been updated
+ if (_configHandler.parsedTools[tool.label]!.versionNumber <
+ toolsMessageVersion) {
+ _configHandler.incrementToolVersion(
+ tool: tool.label,
+ newVersionNumber: toolsMessageVersion,
+ );
+ }
+ _showMessage = false;
+ }
+
+ @override
+ Future<void> close({int delayDuration = kDelayDuration}) async {
+ // Collect any errors encountered and send
+ _sendPendingErrorEvents();
+
+ await Future.wait(_futures).timeout(
+ Duration(milliseconds: delayDuration),
+ onTimeout: () => [],
+ );
+ _gaClient.close();
+ }
+
+ @override
+ Future<List<Survey>> fetchAvailableSurveys() async {
+ final surveysToShow = <Survey>[];
+ if (!okToSend) return surveysToShow;
+
+ final logFileStats = _logHandler.logFileStats();
+
+ // Call for surveys that have already been dismissed from
+ // persisted survey ids on disk
+ final persistedSurveyMap = _surveyHandler.fetchPersistedSurveys();
+
+ for (final survey in await _surveyHandler.fetchSurveyList()) {
+ // If the survey has listed the tool running this package in the exclude
+ // list, it will not be returned
+ if (survey.excludeDashToolList.contains(tool)) continue;
+
+ // Apply the survey's sample rate; if the generated value from
+ // the client id and survey's uniqueId are less, it will not get
+ // sent to the user
+ if (survey.samplingRate < sampleRate(clientId, survey.uniqueId)) {
+ continue;
+ }
+
+ // If the survey has been permanently dismissed or has temporarily
+ // been snoozed, skip it
+ if (surveySnoozedOrDismissed(survey, persistedSurveyMap)) continue;
+
+ // Counter to check each survey condition, if all are met, then
+ // this integer will be equal to the number of conditions in
+ // [Survey.conditionList]
+ var conditionsMet = 0;
+ if (logFileStats != null) {
+ for (final condition in survey.conditionList) {
+ // Retrieve the value from the [LogFileStats] with
+ // the label provided in the condtion
+ final logFileStatsValue =
+ logFileStats.getValueByString(condition.field);
+
+ if (logFileStatsValue == null) continue;
+
+ switch (condition.operatorString) {
+ case '>=':
+ if (logFileStatsValue >= condition.value) conditionsMet++;
+ case '<=':
+ if (logFileStatsValue <= condition.value) conditionsMet++;
+ case '>':
+ if (logFileStatsValue > condition.value) conditionsMet++;
+ case '<':
+ if (logFileStatsValue < condition.value) conditionsMet++;
+ case '==':
+ if (logFileStatsValue == condition.value) conditionsMet++;
+ case '!=':
+ if (logFileStatsValue != condition.value) conditionsMet++;
+ }
+ }
+ }
+
+ if (conditionsMet == survey.conditionList.length) {
+ surveysToShow.add(survey);
+ }
+ }
+
+ return surveysToShow;
+ }
+
+ @override
+ LogFileStats? logFileStats() => _logHandler.logFileStats();
+
+ @override
+ void send(Event event) {
+ if (!okToSend) return;
+
+ // Construct the body of the request
+ final body = generateRequestBody(
+ clientId: clientId,
+ eventName: event.eventName,
+ eventData: event.eventData,
+ userProperty: _userProperty,
+ );
+
+ if (_enableAsserts) checkBody(body);
+
+ _logHandler.save(data: body);
+
+ final gaClientFuture = _gaClient.sendData(body);
+ _futures.add(gaClientFuture);
+ gaClientFuture.whenComplete(() => _futures.remove(gaClientFuture));
+ }
+
+ @override
+ Future<void> setTelemetry(bool reportingBool) {
+ _configHandler.setTelemetry(reportingBool);
+
+ // Creation of the [Event] for opting out
+ final collectionEvent =
+ Event.analyticsCollectionEnabled(status: reportingBool);
+
+ // The body of the request that will be sent to GA4
+ final Map<String, Object?> body;
+
+ if (reportingBool) {
+ // Recreate the session and client id file; no need to
+ // recreate the log file since it will only receives events
+ // to persist from events sent
+ createClientIdFile(clientIdFile: _clientIdFile);
+ createSessionFile(sessionFile: _userProperty.sessionFile);
+
+ // Reread the client ID string so an empty string is not being
+ // sent to GA4 since the persisted files are cleared when a user
+ // decides to opt out of telemetry collection
+ _clientId = _clientIdFile.readAsStringSync();
+
+ // We must construct the body at this point after we have read in the
+ // new client id string that was generated
+ body = generateRequestBody(
+ clientId: clientId,
+ eventName: collectionEvent.eventName,
+ eventData: collectionEvent.eventData,
+ userProperty: _userProperty,
+ );
+
+ _logHandler.save(data: body);
+ } else {
+ // Construct the body of the request to signal
+ // telemetry status toggling
+ body = generateRequestBody(
+ clientId: clientId,
+ eventName: collectionEvent.eventName,
+ eventData: collectionEvent.eventData,
+ userProperty: _userProperty,
+ );
+
+ // For opted out users, data in the persisted files is cleared
+ _userProperty.sessionFile.writeAsStringSync('');
+ _logHandler.logFile.writeAsStringSync('');
+ _clientIdFile.writeAsStringSync('');
+
+ _clientId = '';
+ }
+
+ // Pass to the google analytics client to send with a
+ // timeout incase http clients hang
+ return _gaClient.sendData(body).timeout(
+ const Duration(milliseconds: kDelayDuration),
+ onTimeout: () => Response('', 200),
+ );
+ }
+
+ @override
+ void suppressTelemetry() => _telemetrySuppressed = true;
+
+ @override
+ void surveyInteracted({
+ required Survey survey,
+ required SurveyButton surveyButton,
+ }) {
+ // Any action, except for 'snooze' will permanently dismiss a given survey
+ final permanentlyDismissed = surveyButton.action == 'snooze' ? false : true;
+ _surveyHandler.dismiss(survey, permanentlyDismissed);
+ send(Event.surveyAction(
+ surveyId: survey.uniqueId,
+ status: surveyButton.action,
+ ));
+ }
+
+ @override
+ void surveyShown(Survey survey) {
+ _surveyHandler.dismiss(survey, false);
+ send(Event.surveyShown(surveyId: survey.uniqueId));
+ }
+
+ /// Send any pending error events, useful for tests to avoid closing
+ /// the connection.
+ ///
+ /// In the main implementation, [AnalyticsImpl], error events are only
+ /// sent on exit when [close] is invoked. This helper method can instead
+ /// have those error events sent immediately to help with tests that check
+ /// [FakeAnalytics.sentEvents].
+ void _sendPendingErrorEvents() {
+ // Collect any errors encountered and send
+ final errorEvents = {..._userProperty.errorSet, ..._logHandler.errorSet};
+ errorEvents
+ .where((event) =>
+ event.eventName == DashEvent.analyticsException &&
+ !_sentErrorEvents.contains(event))
+ .forEach(send);
+
+ // Ensure the same event doesn't get sent again
+ _sentErrorEvents.addAll(errorEvents);
+
+ // Clear error sets
+ _userProperty.errorSet.clear();
+ _logHandler.errorSet.clear();
+ }
+}
+
+/// This fake instance of [Analytics] is intended to be used by clients of
+/// this package for testing purposes. It exposes a list [sentEvents] that
+/// keeps track of all events that have been sent.
+///
+/// This is useful for confirming that events are being sent for a given
+/// workflow. Invoking the [send] method on this instance will not make any
+/// network requests to Google Analytics.
+class FakeAnalytics extends AnalyticsImpl {
+ /// Use this list to check for events that have been emitted when
+ /// invoking the send method
+ final List<Event> sentEvents = [];
+
+ /// Class to use when you want to see which events were sent
+ FakeAnalytics._({
+ required super.tool,
+ required super.homeDirectory,
+ required super.dartVersion,
+ required super.platform,
+ required super.fs,
+ required super.surveyHandler,
+ required super.firstRun,
+ super.flutterChannel,
+ super.flutterVersion,
+ super.clientIde,
+ super.enabledFeatures,
+ super.toolsMessageVersion = kToolsMessageVersion,
+ super.gaClient = const FakeGAClient(),
+ super.enableAsserts = true,
+ });
+
+ /// Getter to reference the private [UserProperty].
+ UserProperty get userProperty => _userProperty;
+
+ @override
+ void send(Event event) {
+ if (!okToSend) return;
+
+ // Construct the body of the request
+ final body = generateRequestBody(
+ clientId: clientId,
+ eventName: event.eventName,
+ eventData: event.eventData,
+ userProperty: _userProperty,
+ );
+
+ if (_enableAsserts) checkBody(body);
+
+ _logHandler.save(data: body);
+
+ // Using this list to validate that events are being sent
+ // for internal methods in the `Analytics` instance
+ sentEvents.add(event);
+ }
+
+ /// Public instance method to invoke private method that sends any
+ /// pending error events.
+ ///
+ /// If this is never invoked, any pending error events will be sent
+ /// when invoking the [close] method.
+ void sendPendingErrorEvents() => _sendPendingErrorEvents();
+}
+
+/// An implementation that will never send events.
+///
+/// This is for clients that opt to either not send analytics, or will migrate
+/// to use [AnalyticsImpl] at a later time.
+class NoOpAnalytics implements Analytics {
+ /// The hard-coded client ID value for each NoOp instance.
+ static String get staticClientId => 'xxxx-xxxx';
+
+ @override
+ final String getConsentMessage = '';
+
+ @override
+ final bool okToSend = false;
+
+ @override
+ final Map<String, ToolInfo> parsedTools = const <String, ToolInfo>{};
+
+ @override
+ final bool shouldShowMessage = false;
+
+ @override
+ final bool telemetryEnabled = false;
+
+ @override
+ final Map<String, Map<String, Object?>> userPropertyMap =
+ const <String, Map<String, Object?>>{};
+
+ const NoOpAnalytics();
+
+ @override
+ String get clientId => staticClientId;
+
+ @override
+ void clientShowedMessage() {}
+
+ @override
+ Future<void> close({int delayDuration = kDelayDuration}) async {}
+
+ @override
+ Future<List<Survey>> fetchAvailableSurveys() async => const <Survey>[];
+
+ @override
+ LogFileStats? logFileStats() => null;
+
+ @override
+ Future<Response>? send(Event event) => null;
+
+ @override
+ Future<void> setTelemetry(bool reportingBool) async {}
+
+ @override
+ void suppressTelemetry() {}
+
+ @override
+ void surveyInteracted({
+ required Survey survey,
+ required SurveyButton surveyButton,
+ }) {}
+
+ @override
+ void surveyShown(Survey survey) {}
+}
diff --git a/pkgs/unified_analytics/lib/src/asserts.dart b/pkgs/unified_analytics/lib/src/asserts.dart
new file mode 100644
index 0000000..199f4ee
--- /dev/null
+++ b/pkgs/unified_analytics/lib/src/asserts.dart
@@ -0,0 +1,172 @@
+// Copyright (c) 2023, 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.
+
+/// Matches only alphabetic characters.
+final RegExp alphabeticPattern = RegExp(r'^[A-Za-z]+$');
+
+/// Matches strings that contain alphanumeric characters and underscores.
+final RegExp alphaNumericPattern = RegExp(r'^[A-Za-z0-9_]+$');
+
+/// Checks that the body of the request being sent to
+/// GA4 is within the limitations.
+///
+/// Limitations can be found:
+/// https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events?client_type=gtag#limitations
+void checkBody(Map<String, Object?> body) {
+ // Ensure we have the correct top level keys
+ if (!body.keys.contains('client_id')) {
+ throw AnalyticsException('client_id missing from top level keys');
+ }
+ if (!body.keys.contains('events')) {
+ throw AnalyticsException('events missing from top level keys');
+ }
+ if (!body.keys.contains('user_properties')) {
+ throw AnalyticsException('user_properties missing from top level keys');
+ }
+
+ final events = body['events'] as List;
+ final userProperties = body['user_properties'] as Map<String, Object?>;
+
+ // GA4 Limitation:
+ // Requests can have a maximum of 25 events
+ if (events.length > 25) {
+ throw AnalyticsException('25 is the max number of events');
+ }
+
+ // Checks for each event object
+ for (final eventMap in events.cast<Map<String, Object?>>()) {
+ final eventName = eventMap['name'] as String;
+
+ // GA4 Limitation:
+ // Event names must be 40 characters or fewer, may only contain
+ // alpha-numeric characters and underscores, and must start
+ // with an alphabetic character
+ if (eventName.length > 40) {
+ throw AnalyticsException(
+ 'Limit event names to 40 chars or less\n'
+ 'Event name: "$eventName" is too long',
+ );
+ }
+ if (!alphaNumericPattern.hasMatch(eventName)) {
+ throw AnalyticsException(
+ 'Event name can only have alphanumeric chars and underscores\n'
+ 'Event name: "$eventName" contains invalid characters',
+ );
+ }
+ if (!alphabeticPattern.hasMatch(eventName[0])) {
+ throw AnalyticsException(
+ 'Event name first char must be alphabetic char\n'
+ 'Event name: "$eventName" must begin with a valid character',
+ );
+ }
+
+ final eventParams = eventMap['params'] as Map<String, Object?>;
+
+ // GA4 Limitation:
+ // Events can have a maximum of 25 parameters
+ if (eventParams.length > 25) {
+ throw AnalyticsException(
+ 'Limit params for each event to less than 25\n'
+ 'Event: "$eventName" has too many parameters',
+ );
+ }
+
+ // Loop through each of the event parameters
+ for (final entry in eventParams.entries) {
+ final key = entry.key;
+ final value = entry.value;
+
+ // GA4 Limitation:
+ // Ensure that each value for the event params is one
+ // of the following types:
+ // `String`, `int`, `double`, or `bool`
+ if (!(value is String ||
+ value is int ||
+ value is double ||
+ value is bool)) {
+ throw AnalyticsException(
+ 'Values for event params have to be String, int, double, or bool\n'
+ 'Value for "$key" is not a valid type for event: "$eventName"',
+ );
+ }
+
+ // GA4 Limitation:
+ // Parameter names (including item parameters) must be 40 characters
+ // or fewer, may only contain alpha-numeric characters and underscores,
+ // and must start with an alphabetic character
+ if (key.length > 40) {
+ throw AnalyticsException(
+ 'Limit event param names to 40 chars or less\n'
+ 'The key: "$key" under the event: "$eventName" is too long',
+ );
+ }
+ if (!alphaNumericPattern.hasMatch(key)) {
+ throw AnalyticsException(
+ 'Event param name can only have alphanumeric chars and underscores\n'
+ 'The key: "$key" under the event: "$eventName" contains '
+ 'invalid characters',
+ );
+ }
+ if (!alphabeticPattern.hasMatch(key[0])) {
+ throw AnalyticsException(
+ 'Event param name first char must be alphabetic char\n'
+ 'The key: "$key" under the event: "$eventName" must begin '
+ 'in a valid character',
+ );
+ }
+
+ // GA4 Limitation:
+ // Parameter values (including item parameter values) must be 100
+ // characters or fewer
+ if (value.runtimeType == String) {
+ value as String;
+ if (value.length > 100) {
+ throw AnalyticsException(
+ 'Limit characters in event param value to 100 chars or less\n'
+ 'Value for "$key" is too long, value="$value"',
+ );
+ }
+ }
+ }
+ }
+
+ // GA4 Limitation:
+ // Events can have a maximum of 25 user properties
+ if (userProperties.length > 25) {
+ throw AnalyticsException('Limit user properties to 25 or less');
+ }
+
+ // Checks for each user property item
+ for (final entry in userProperties.entries) {
+ final key = entry.key;
+ final value = entry.value as Map<String, Object?>;
+
+ // GA4 Limitation:
+ // User property names must be 24 characters or fewer
+ if (key.length > 24) {
+ throw AnalyticsException('Limit user property names to 24 chars or less\n'
+ 'The user property key: "$key" is too long');
+ }
+
+ // GA4 Limitation:
+ // User property values must be 36 characters or fewer
+ final userPropValue = value['value'];
+ if (userPropValue is String && userPropValue.length > 36) {
+ throw AnalyticsException(
+ 'Limit user property values to 36 chars or less\n'
+ 'For the user property key "$key", the value "${value['value']}" '
+ 'is too long',
+ );
+ }
+ }
+}
+
+class AnalyticsException implements Exception {
+ final String message;
+
+ AnalyticsException(this.message);
+
+ @override
+ String toString() => 'AnalyticsException: $message';
+}
diff --git a/pkgs/unified_analytics/lib/src/config_handler.dart b/pkgs/unified_analytics/lib/src/config_handler.dart
new file mode 100644
index 0000000..8db4660
--- /dev/null
+++ b/pkgs/unified_analytics/lib/src/config_handler.dart
@@ -0,0 +1,224 @@
+// Copyright (c) 2023, 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 'package:clock/clock.dart';
+import 'package:convert/convert.dart';
+import 'package:file/file.dart';
+
+import 'initializer.dart';
+import 'utils.dart';
+
+/// The regex pattern used to parse the disable analytics line.
+const String telemetryFlagPattern = r'^reporting=([0|1]) *$';
+
+/// The regex pattern used to parse the tools info
+/// from the configuration file.
+///
+/// Example:
+/// ```text
+/// flutter-tool=2022-10-26,1
+/// devtools=2022-11-26,1
+/// ```
+const String toolPattern =
+ r'^([A-Za-z0-9]+[A-Za-z0-9_-]*)=([0-9]{4}-[0-9]{2}-[0-9]{2}),([0-9]+)$';
+
+class ConfigHandler {
+ /// Regex pattern implementation for matching a line in the config file.
+ static RegExp telemetryFlagRegex =
+ RegExp(telemetryFlagPattern, multiLine: true);
+ static RegExp toolRegex = RegExp(toolPattern, multiLine: true);
+
+ final Directory homeDirectory;
+ final File configFile;
+
+ final Map<String, ToolInfo> parsedTools = <String, ToolInfo>{};
+
+ DateTime configFileLastModified;
+
+ /// Reporting enabled unless specified by user
+ bool _telemetryEnabled = true;
+
+ ConfigHandler({
+ required this.homeDirectory,
+ required this.configFile,
+ }) : configFileLastModified = configFile.lastModifiedSync() {
+ // Call the method to parse the contents of the config file when
+ // this class is initialized
+ parseConfig();
+ }
+
+ /// Returns the telemetry state from the config file.
+ ///
+ /// Method will reparse the config file if it detects that the
+ /// last modified datetime is different from what was parsed when
+ /// the class was initialized.
+ bool get telemetryEnabled {
+ if (configFileLastModified.isBefore(configFile.lastModifiedSync())) {
+ parseConfig();
+ configFileLastModified = configFile.lastModifiedSync();
+ }
+
+ return _telemetryEnabled;
+ }
+
+ /// Responsibe for the creation of the configuration line
+ /// for the tool being passed in by the user and adding a
+ /// [ToolInfo] object.
+ void addTool({
+ required String tool,
+ required int versionNumber,
+ }) {
+ // Create the new instance of [ToolInfo] to be added
+ // to the [parsedTools] map
+ parsedTools[tool] = ToolInfo(
+ lastRun: clock.now(),
+ versionNumber: versionNumber,
+ );
+
+ // New string to be appended to the bottom of the configuration file
+ // with a newline character for new tools to be added
+ var newTool = '$tool=$dateStamp,$versionNumber\n';
+ if (!configFile.readAsStringSync().endsWith('\n')) {
+ newTool = '\n$newTool';
+ }
+ configFile.writeAsStringSync(newTool, mode: FileMode.append);
+ configFileLastModified = configFile.lastModifiedSync();
+ }
+
+ /// Will increment the version number and update the date
+ /// in the config file for the provided tool name while
+ /// also incrementing the version number in [ToolInfo].
+ void incrementToolVersion({
+ required String tool,
+ required int newVersionNumber,
+ }) {
+ if (!parsedTools.containsKey(tool)) {
+ return;
+ }
+
+ // Read in the config file contents and use a regex pattern to
+ // match the line for the current tool (ie. flutter-tools=2023-01-05,1)
+ final configString = configFile.readAsStringSync();
+ final pattern = '^($tool)=([0-9]{4}-[0-9]{2}-[0-9]{2}),([0-9]+)\$';
+
+ final regex = RegExp(pattern, multiLine: true);
+ final matches = regex.allMatches(configString);
+
+ // If there isn't exactly one match for the given tool, that suggests the
+ // file has been altered and needs to be reset
+ if (matches.length != 1) {
+ resetConfig();
+ return;
+ }
+
+ // Construct the new tool line for the config line and replace it
+ // in the original config string to prep for writing back out
+ final newToolString = '$tool=$dateStamp,$newVersionNumber';
+ final newConfigString = configString.replaceAll(regex, newToolString);
+ configFile.writeAsStringSync(newConfigString);
+
+ final toolInfo = parsedTools[tool];
+ if (toolInfo == null) {
+ return;
+ }
+
+ // Update the [ToolInfo] object for the current tool
+ toolInfo.lastRun = clock.now();
+ toolInfo.versionNumber = newVersionNumber;
+ }
+
+ /// Method responsible for reading in the config file stored on
+ /// user's machine and parsing out the following: all the tools that
+ /// have been logged in the file, the dates they were last run, and
+ /// determining if telemetry is enabled by parsing the file.
+ void parseConfig() {
+ // Begin with the assumption that telemetry is always enabled
+ _telemetryEnabled = true;
+
+ // Read the configuration file as a string and run the two regex patterns
+ // on it to get information around which tools have been parsed and whether
+ // or not telemetry has been disabled by the user
+ final configString = configFile.readAsStringSync();
+
+ // Collect the tools logged in the configuration file
+ toolRegex.allMatches(configString).forEach((RegExpMatch element) {
+ // Extract the information relevant for the [ToolInfo] class
+ final tool = element.group(1) as String;
+ final lastRun = DateTime.parse(element.group(2) as String);
+ final versionNumber = int.parse(element.group(3) as String);
+
+ // Initialize an instance of the [ToolInfo] class to store
+ // in the [parsedTools] map object
+ parsedTools[tool] = ToolInfo(
+ lastRun: lastRun,
+ versionNumber: versionNumber,
+ );
+ });
+
+ // Check for lines signaling that the user has disabled analytics,
+ // if multiple lines are found, the more conservative value will be used
+ telemetryFlagRegex.allMatches(configString).forEach((RegExpMatch element) {
+ // Conditional for recording telemetry as being disabled
+ if (element.group(1) == '0') {
+ _telemetryEnabled = false;
+ }
+ });
+ }
+
+ /// This will reset the configuration file and clear the
+ /// [parsedTools] map and trigger parsing the config again.
+ void resetConfig() {
+ createConfigFile(
+ configFile: configFile,
+ homeDirectory: homeDirectory,
+ );
+ parsedTools.clear();
+ parseConfig();
+ }
+
+ /// Disables the reporting capabilities if [reportingBool] is set to `false`.
+ Future<void> setTelemetry(bool reportingBool) async {
+ final flag = reportingBool ? '1' : '0';
+ final configString = await configFile.readAsString();
+
+ final matches = telemetryFlagRegex.allMatches(configString);
+
+ // If there isn't exactly one match for the reporting flag, that suggests
+ // the file has been altered and needs to be reset
+ if (matches.length != 1) {
+ resetConfig();
+ return;
+ }
+
+ final newTelemetryString = 'reporting=$flag';
+
+ final newConfigString =
+ configString.replaceAll(telemetryFlagRegex, newTelemetryString);
+
+ await configFile.writeAsString(newConfigString);
+ configFileLastModified = configFile.lastModifiedSync();
+
+ _telemetryEnabled = reportingBool;
+ }
+}
+
+class ToolInfo {
+ DateTime lastRun;
+ int versionNumber;
+
+ ToolInfo({
+ required this.lastRun,
+ required this.versionNumber,
+ });
+
+ @override
+ String toString() {
+ return json.encode(<String, Object?>{
+ 'lastRun': FixedDateTimeFormatter('YYYY-MM-DD').encode(lastRun),
+ 'versionNumber': versionNumber,
+ });
+ }
+}
diff --git a/pkgs/unified_analytics/lib/src/constants.dart b/pkgs/unified_analytics/lib/src/constants.dart
new file mode 100644
index 0000000..2be4d0a
--- /dev/null
+++ b/pkgs/unified_analytics/lib/src/constants.dart
@@ -0,0 +1,119 @@
+// Copyright (c) 2023, 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.
+
+/// URL endpoint for sending Google Analytics Events.
+const String kAnalyticsUrl = 'https://www.google-analytics.com/mp/collect';
+
+/// Name for the text file that will contain the user's randomly generated
+/// client id.
+const String kClientIdFileName = 'CLIENT_ID';
+
+/// Name for the file where telemetry status and tools data will be stored.
+const String kConfigFileName = 'dart-flutter-telemetry.config';
+
+/// The string that will provide the boilerplate for the configuration file
+/// stored on the user's machine.
+const String kConfigString = '''
+# INTRODUCTION
+#
+# This is the Flutter and Dart telemetry reporting
+# configuration file.
+#
+# Lines starting with a #" are documentation that
+# the tools maintain automatically.
+#
+# All other lines are configuration lines. They have
+# the form "name=value". If multiple lines contain
+# the same configuration name with different values,
+# the parser will default to a conservative value.
+
+# DISABLING TELEMETRY REPORTING
+#
+# To disable telemetry reporting, set "reporting" to
+# the value "0" and to enable, set to "1":
+reporting=1
+
+# NOTIFICATIONS
+#
+# Each tool records when it last informed the user about
+# analytics reporting and the privacy policy.
+#
+# The following tools have so far read this file:
+#
+# dart-tools (Dart CLI developer tool)
+# devtools (DevTools debugging and performance tools)
+# flutter-tools (Flutter CLI developer tool)
+#
+# For each one, the file may contain a configuration line
+# where the name is the code in the list above, e.g. "dart-tool",
+# and the value is a date in the form YYYY-MM-DD, a comma, and
+# a number representing the version of the message that was
+# displayed.''';
+
+/// Link to contextual survey metadata file.
+const String kContextualSurveyUrl =
+ 'https://storage.googleapis.com/flutter-uxr/surveys/contextual-survey-metadata.json';
+
+/// Name of the directory where all of the files necessary for this package
+/// will be located.
+const String kDartToolDirectoryName = '.dart-tool';
+
+/// The default time to wait before closing the http connection to allow for
+/// pending events to be sent.
+const int kDelayDuration = 250;
+
+/// Name of the file where we persist dismissed survey ids.
+const String kDismissedSurveyFileName =
+ 'dart-flutter-telemetry-dismissed-surveys.json';
+
+/// The API secret associated with the GA4 instance's Measurement Protocol.
+const String kGoogleAnalyticsApiSecret = 'Ka1jc8tZSzWc_GXMWHfPHA';
+
+/// The measurement ID related to the GA4 instance.
+///
+/// Serves as an identifier for a web data stream.
+const String kGoogleAnalyticsMeasurementId = 'G-04BXPVBCWJ';
+
+/// How many data records to store in the log file.
+const int kLogFileLength = 2500;
+
+/// The maximum allowed size of the telemetry log file.
+///
+/// 25 MiB.
+const int kMaxLogFileSize = 25 * (1 << 20);
+
+/// Filename for the log file to persist sent events on user's machine.
+const String kLogFileName = 'dart-flutter-telemetry.log';
+
+/// The current version of the package, should be in line with pubspec version.
+const String kPackageVersion = '7.0.1';
+
+/// The minimum length for a session.
+const int kSessionDurationMinutes = 30;
+
+/// Name for the json file where the session details will be stored.
+const String kSessionFileName = 'dart-flutter-telemetry-session.json';
+
+/// The message that should be shown to the user.
+const String kToolsMessage = '''
+The {{ toolDescription }} uses Google Analytics to report usage and diagnostic
+data along with package dependencies, and crash reporting to send basic crash
+reports. This data is used to help improve the Dart platform, Flutter framework,
+and related tools.
+
+Telemetry is not sent on the very first run. To disable reporting of telemetry,
+run this terminal command:
+
+ {{ toolName }} --disable-analytics
+
+If you opt out of telemetry, an opt-out event will be sent, and then no further
+information will be sent. This data is collected in accordance with the Google
+Privacy Policy (https://policies.google.com/privacy).
+''';
+
+/// The version number for the message below.
+///
+/// If the message below is altered, the version should be incremented so that
+/// users can be prompted with the updated messaging.
+const int kToolsMessageVersion = 1;
diff --git a/pkgs/unified_analytics/lib/src/enums.dart b/pkgs/unified_analytics/lib/src/enums.dart
new file mode 100644
index 0000000..d67ab66
--- /dev/null
+++ b/pkgs/unified_analytics/lib/src/enums.dart
@@ -0,0 +1,234 @@
+// Copyright (c) 2023, 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.
+
+/// The valid dash tool labels stored in the [DashTool] enum.
+List<String> get validDashTools =>
+ DashTool.values.map((e) => e.label).toList()..sort();
+
+/// Values for the event name to be sent to Google Analytics.
+///
+/// The [label] for each enum value is what will be logged, the [description]
+/// is here for documentation purposes.
+///
+/// Set the nullable [toolOwner] parameter if the event belongs to one specific
+/// tool, otherwise, if multiple tools will be sending the event, leave it null.
+enum DashEvent {
+ // Events that can be sent by all tools; these
+ // events should not be tool specific; toolOwner
+ // not necessary for these events
+
+ analyticsCollectionEnabled(
+ label: 'analytics_collection_enabled',
+ description: 'The opt-in status for analytics collection',
+ ),
+ analyticsException(
+ label: 'analytics_exception',
+ description: 'Errors that are encountered within package:unified_analytics',
+ ),
+ exception(
+ label: 'exception',
+ description: 'General errors to log',
+ ),
+ surveyAction(
+ label: 'survey_action',
+ description: 'Actions taken by users when shown survey',
+ ),
+ surveyShown(
+ label: 'survey_shown',
+ description: 'Survey shown to the user',
+ ),
+ timing(
+ label: 'timing',
+ description: 'Events for timing how long a process takes',
+ ),
+
+ // Events for the Dart CLI
+
+ dartCliCommandExecuted(
+ label: 'dart_cli_command_executed',
+ description: 'Information about the execution of a Dart CLI command',
+ toolOwner: DashTool.dartTool,
+ ),
+ pubGet(
+ label: 'pub_get',
+ description: 'Pub package resolution details',
+ toolOwner: DashTool.dartTool,
+ ),
+
+ // Events for Flutter devtools
+
+ devtoolsEvent(
+ label: 'devtools_event',
+ description: 'Information for various devtools events',
+ toolOwner: DashTool.devtools,
+ ),
+
+ // Events for the Flutter CLI
+
+ appleUsageEvent(
+ label: 'apple_usage_event',
+ description:
+ 'Events related to iOS/macOS workflows within the flutter tool',
+ toolOwner: DashTool.flutterTool,
+ ),
+ codeSizeAnalysis(
+ label: 'code_size_analysis',
+ description: 'Indicates when the "--analyize-size" command is run',
+ toolOwner: DashTool.flutterTool,
+ ),
+ commandUsageValues(
+ label: 'command_usage_values',
+ description: 'Contains command level custom dimensions from legacy '
+ 'flutter analytics',
+ toolOwner: DashTool.flutterTool,
+ ),
+ doctorValidatorResult(
+ label: 'doctor_validator_result',
+ description: 'Results from a specific doctor validator',
+ toolOwner: DashTool.flutterTool,
+ ),
+ flutterBuildInfo(
+ label: 'flutter_build_info',
+ description: 'Information for a flutter build invocation',
+ toolOwner: DashTool.flutterTool,
+ ),
+ flutterCommandResult(
+ label: 'flutter_command_result',
+ description: 'Provides information about flutter commands that ran',
+ toolOwner: DashTool.flutterTool,
+ ),
+ hotReloadTime(
+ label: 'hot_reload_time',
+ description: 'Hot reload duration',
+ toolOwner: DashTool.flutterTool,
+ ),
+ hotRunnerInfo(
+ label: 'hot_runner_info',
+ description: 'Information related to the Flutter hot runner',
+ toolOwner: DashTool.flutterTool,
+ ),
+
+ // Events for language_server below
+
+ clientNotification(
+ label: 'client_notification',
+ description: 'Notifications sent from the client',
+ ),
+ clientRequest(
+ label: 'client_request',
+ description: 'Requests sent from the client',
+ ),
+ commandExecuted(
+ label: 'command_executed',
+ description: 'Number of times a command is executed',
+ ),
+ contextStructure(
+ label: 'context_structure',
+ description: 'Structure of the analysis contexts being analyzed',
+ ),
+ lintUsageCount(
+ label: 'lint_usage_count',
+ description: 'Number of times a given lint is enabled',
+ ),
+ memoryInfo(
+ label: 'memory_info',
+ description: 'Memory usage information',
+ ),
+ pluginRequest(
+ label: 'plugin_request',
+ description: 'Request responses from plugins',
+ ),
+ pluginUse(
+ label: 'plugin_use',
+ description: 'Information about how often a plugin was used',
+ ),
+ serverSession(
+ label: 'server_session',
+ description: 'Dart Analyzer Server session data',
+ ),
+ severityAdjustment(
+ label: 'severity_adjustment',
+ description: 'Number of times diagnostic severity is changed',
+ ),
+ ;
+
+ final String label;
+ final String description;
+ final DashTool? toolOwner;
+ const DashEvent({
+ required this.label,
+ required this.description,
+ this.toolOwner,
+ });
+
+ /// This takes in the string label for a given [DashEvent] and returns the
+ /// enum for that string label.
+ static DashEvent? fromLabel(String label) =>
+ DashEvent.values.where((e) => e.label == label).firstOrNull;
+}
+
+/// Officially-supported clients of this package as logical
+/// tools, grouped by user point of view. Derived directly
+/// from the PDD.
+enum DashTool {
+ androidStudioPlugins(
+ label: 'android-studio-plugins',
+ description: 'Android Studio IDE plugins for Dart and Flutter',
+ ),
+ dartTool(
+ label: 'dart-tool',
+ description: 'Dart CLI developer tool',
+ ),
+ devtools(
+ label: 'devtools',
+ description: 'DevTools debugging and performance tools',
+ ),
+ flutterTool(
+ label: 'flutter-tool',
+ description: 'Flutter CLI developer tool',
+ ),
+ intellijPlugins(
+ label: 'intellij-plugins',
+ description: 'IntelliJ IDE plugins for Dart and Flutter',
+ ),
+ vscodePlugins(
+ label: 'vscode-plugins',
+ description: 'VS Code IDE extensions for Dart and Flutter',
+ );
+
+ /// String used as the control flag and the value of the tool key in
+ /// analytics.
+ final String label;
+
+ /// The "notice string", a human-readable description of the logical tool
+ /// grouping.
+ final String description;
+
+ const DashTool({
+ required this.label,
+ required this.description,
+ });
+
+ /// This takes in the string label for a given [DashTool] and returns the
+ /// enum for that string label.
+ static DashTool fromLabel(String label) {
+ final tool = DashTool.values.where((t) => t.label == label).firstOrNull;
+ if (tool != null) return tool;
+
+ throw Exception('The tool $label from the survey metadata file is not '
+ 'a valid DashTool enum value\n'
+ 'Valid labels for dash tools: ${validDashTools.join(', ')}');
+ }
+}
+
+/// Enumerate options for platforms supported.
+enum DevicePlatform {
+ windows('Windows'),
+ macos('macOS'),
+ linux('Linux'),
+ ;
+
+ final String label;
+ const DevicePlatform(this.label);
+}
diff --git a/pkgs/unified_analytics/lib/src/event.dart b/pkgs/unified_analytics/lib/src/event.dart
new file mode 100644
index 0000000..ed30665
--- /dev/null
+++ b/pkgs/unified_analytics/lib/src/event.dart
@@ -0,0 +1,860 @@
+// Copyright (c) 2023, 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 'enums.dart';
+
+final class Event {
+ final DashEvent eventName;
+ final Map<String, Object?> eventData;
+
+ /// Event that is emitted whenever a user has opted in
+ /// or out of the analytics collection.
+ ///
+ /// [status] - boolean value where `true` indicates user is opting in.
+ Event.analyticsCollectionEnabled({required bool status})
+ : eventName = DashEvent.analyticsCollectionEnabled,
+ eventData = {'status': status};
+
+ /// Event that is emitted when an error occurs within
+ /// `package:unified_analytics`, tools that are using this package
+ /// should not use this event constructor.
+ ///
+ /// Tools using this package should instead use the more generic
+ /// [Event.exception] constructor.
+ ///
+ /// [workflow] - refers to what process caused the error, such as
+ /// "LogHandler.logFileStats".
+ ///
+ /// [error] - the name of the error, such as "FormatException".
+ ///
+ /// [description] - the description of the error being caught.
+ Event.analyticsException({
+ required String workflow,
+ required String error,
+ String? description,
+ }) : eventName = DashEvent.analyticsException,
+ eventData = {
+ 'workflow': workflow,
+ 'error': error,
+ if (description != null) 'description': description,
+ };
+
+ /// This is for various workflows within the flutter tool related
+ /// to iOS and macOS workflows.
+ ///
+ /// [workflow] - which workflow is running, such as "assemble".
+ ///
+ /// [parameter] - subcategory of the workflow, such as "ios-archive".
+ ///
+ /// [result] - usually to indicate success or failure of the workflow.
+ Event.appleUsageEvent({
+ required String workflow,
+ required String parameter,
+ String? result,
+ }) : eventName = DashEvent.appleUsageEvent,
+ eventData = {
+ 'workflow': workflow,
+ 'parameter': parameter,
+ if (result != null) 'result': result,
+ };
+
+ /// Event that is emitted periodically to report the performance of the
+ /// analysis server's handling of a specific kind of notification from the
+ /// client.
+ ///
+ /// [duration] - json encoded percentile values indicating how long it took
+ /// from the time the server started handling the notification until the
+ /// server had finished handling the notification.
+ ///
+ /// [latency] - json encoded percentile values indicating how long it took
+ /// from the time the notification was sent until the server started
+ /// handling it.
+ ///
+ /// [method] - the name of the notification method that was sent.
+ Event.clientNotification({
+ required String duration,
+ required String latency,
+ required String method,
+ }) : eventName = DashEvent.clientNotification,
+ eventData = {
+ 'duration': duration,
+ 'latency': latency,
+ 'method': method,
+ };
+
+ /// Event that is emitted periodically to report the performance of the
+ /// analysis server's handling of a specific kind of request from the client.
+ ///
+ /// [duration] - json encoded percentile values indicating how long it took
+ /// from the time the server started handling the request until the server
+ /// had send a response.
+ ///
+ /// [latency] - json encoded percentile values indicating how long it took
+ /// from the time the request was sent until the server started handling
+ /// it.
+ ///
+ /// [method] - the name of the request method that was sent.
+ ///
+ /// If the method is `workspace/didChangeWorkspaceFolders`, then the following
+ /// parameters should be included:
+ ///
+ /// [added] - json encoded percentile values indicating the number of folders
+ /// that were added.
+ ///
+ /// [removed] - json encoded percentile values indicating the number of
+ /// folders that were removed.
+ ///
+ /// If the method is `initialized`, then the following parameters should be
+ /// included:
+ ///
+ /// [openWorkspacePaths] - json encoded percentile values indicating the
+ /// number of workspace paths that were opened.
+ ///
+ /// If the method is `analysis.setAnalysisRoots`, then the following
+ /// parameters should be included:
+ ///
+ /// [included] - json encoded percentile values indicating the number of
+ /// analysis roots in the included list.
+ ///
+ /// [excluded] - json encoded percentile values indicating the number of
+ /// analysis roots in the excluded list.
+ ///
+ /// If the method is `analysis.setPriorityFiles`, then the following
+ /// parameters should be included:
+ ///
+ /// [files] - json encoded percentile values indicating the number of priority
+ /// files.
+ ///
+ Event.clientRequest({
+ required String duration,
+ required String latency,
+ required String method,
+ String? added,
+ String? excluded,
+ String? files,
+ String? included,
+ String? openWorkspacePaths,
+ String? removed,
+ }) : eventName = DashEvent.clientRequest,
+ eventData = {
+ if (added != null) 'added': added,
+ 'duration': duration,
+ if (excluded != null) 'excluded': excluded,
+ if (files != null) 'files': files,
+ if (included != null) 'included': included,
+ 'latency': latency,
+ 'method': method,
+ if (openWorkspacePaths != null)
+ 'openWorkspacePaths': openWorkspacePaths,
+ if (removed != null) 'removed': removed,
+ };
+
+ /// An event that reports when the code size measurement is run
+ /// via `--analyze-size`.
+ ///
+ /// [platform] - string identifier for which platform was run "ios", "apk",
+ /// "aab", etc.
+ Event.codeSizeAnalysis({required String platform})
+ : eventName = DashEvent.codeSizeAnalysis,
+ eventData = {
+ 'platform': platform,
+ };
+
+ /// Event that is emitted periodically to report the number of times a given
+ /// command has been executed.
+ ///
+ /// [count] - the number of times the command was executed.
+ ///
+ /// [name] - the name of the command that was executed.
+ Event.commandExecuted({
+ required int count,
+ required String name,
+ }) : eventName = DashEvent.commandExecuted,
+ eventData = {
+ 'count': count,
+ 'name': name,
+ };
+
+ /// Event to capture usage values for different flutter commands.
+ ///
+ /// There are several implementations of the `FlutterCommand` class within the
+ /// flutter-tool that pass information based on the [workflow] being ran. An
+ /// example of a [workflow] can be "create". The optional parameters for this
+ /// constructor are a superset of all the implementations of `FlutterCommand`.
+ /// There should never be a time where all of the parameters are passed to
+ /// this constructor.
+ Event.commandUsageValues({
+ required String workflow,
+ required bool commandHasTerminal,
+
+ // Assemble && build bundle implementation parameters
+ String? buildBundleTargetPlatform,
+ bool? buildBundleIsModule,
+
+ // Build aar implementation parameters
+ String? buildAarProjectType,
+ String? buildAarTargetPlatform,
+
+ // Build apk implementation parameters
+ String? buildApkTargetPlatform,
+ String? buildApkBuildMode,
+ bool? buildApkSplitPerAbi,
+
+ // Build app bundle implementation parameters
+ String? buildAppBundleTargetPlatform,
+ String? buildAppBundleBuildMode,
+
+ // Create implementation parameters
+ String? createProjectType,
+ String? createAndroidLanguage,
+ String? createIosLanguage,
+
+ // Packages implementation parameters
+ int? packagesNumberPlugins,
+ bool? packagesProjectModule,
+ String? packagesAndroidEmbeddingVersion,
+
+ // Run implementation parameters
+ bool? runIsEmulator,
+ String? runTargetName,
+ String? runTargetOsVersion,
+ String? runModeName,
+ bool? runProjectModule,
+ String? runProjectHostLanguage,
+ String? runAndroidEmbeddingVersion,
+ bool? runEnableImpeller,
+ String? runIOSInterfaceType,
+ bool? runIsTest,
+ }) : eventName = DashEvent.commandUsageValues,
+ eventData = {
+ 'workflow': workflow,
+ 'commandHasTerminal': commandHasTerminal,
+ if (buildBundleTargetPlatform != null)
+ 'buildBundleTargetPlatform': buildBundleTargetPlatform,
+ if (buildBundleIsModule != null)
+ 'buildBundleIsModule': buildBundleIsModule,
+ if (buildAarProjectType != null)
+ 'buildAarProjectType': buildAarProjectType,
+ if (buildAarTargetPlatform != null)
+ 'buildAarTargetPlatform': buildAarTargetPlatform,
+ if (buildApkTargetPlatform != null)
+ 'buildApkTargetPlatform': buildApkTargetPlatform,
+ if (buildApkBuildMode != null) 'buildApkBuildMode': buildApkBuildMode,
+ if (buildApkSplitPerAbi != null)
+ 'buildApkSplitPerAbi': buildApkSplitPerAbi,
+ if (buildAppBundleTargetPlatform != null)
+ 'buildAppBundleTargetPlatform': buildAppBundleTargetPlatform,
+ if (buildAppBundleBuildMode != null)
+ 'buildAppBundleBuildMode': buildAppBundleBuildMode,
+ if (createProjectType != null) 'createProjectType': createProjectType,
+ if (createAndroidLanguage != null)
+ 'createAndroidLanguage': createAndroidLanguage,
+ if (createIosLanguage != null) 'createIosLanguage': createIosLanguage,
+ if (packagesNumberPlugins != null)
+ 'packagesNumberPlugins': packagesNumberPlugins,
+ if (packagesProjectModule != null)
+ 'packagesProjectModule': packagesProjectModule,
+ if (packagesAndroidEmbeddingVersion != null)
+ 'packagesAndroidEmbeddingVersion': packagesAndroidEmbeddingVersion,
+ if (runIsEmulator != null) 'runIsEmulator': runIsEmulator,
+ if (runTargetName != null) 'runTargetName': runTargetName,
+ if (runTargetOsVersion != null)
+ 'runTargetOsVersion': runTargetOsVersion,
+ if (runModeName != null) 'runModeName': runModeName,
+ if (runProjectModule != null) 'runProjectModule': runProjectModule,
+ if (runProjectHostLanguage != null)
+ 'runProjectHostLanguage': runProjectHostLanguage,
+ if (runAndroidEmbeddingVersion != null)
+ 'runAndroidEmbeddingVersion': runAndroidEmbeddingVersion,
+ if (runEnableImpeller != null) 'runEnableImpeller': runEnableImpeller,
+ if (runIOSInterfaceType != null)
+ 'runIOSInterfaceType': runIOSInterfaceType,
+ if (runIsTest != null) 'runIsTest': runIsTest,
+ };
+
+ /// 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.
+ ///
+ /// [immediateFileLineCount] - the number of lines in the immediate files.
+ ///
+ /// [numberOfContexts] - the number of analysis context created.
+ ///
+ /// [transitiveFileCount] - the number of files reachable from the files in
+ /// each analysis context, where files can be counted multiple times if
+ /// they are reachable from multiple contexts.
+ ///
+ /// [transitiveFileLineCount] - the number of lines in the transitive files,
+ /// where files can be counted multiple times if they are reachable from
+ /// multiple contexts.
+ ///
+ /// [transitiveFileUniqueCount] - the number of unique files reachable from
+ /// the files in each analysis context.
+ ///
+ /// [transitiveFileUniqueLineCount] - the number of lines in the unique
+ /// transitive files.
+ Event.contextStructure({
+ required int contextsFromBothFiles,
+ required int contextsFromOptionsFiles,
+ required int contextsFromPackagesFiles,
+ required int contextsWithoutFiles,
+ required int immediateFileCount,
+ required int immediateFileLineCount,
+ required int numberOfContexts,
+ required int transitiveFileCount,
+ required int transitiveFileLineCount,
+ required int transitiveFileUniqueCount,
+ required int transitiveFileUniqueLineCount,
+ }) : eventName = DashEvent.contextStructure,
+ eventData = {
+ 'contextsFromBothFiles': contextsFromBothFiles,
+ 'contextsFromOptionsFiles': contextsFromOptionsFiles,
+ 'contextsFromPackagesFiles': contextsFromPackagesFiles,
+ 'contextsWithoutFiles': contextsWithoutFiles,
+ 'immediateFileCount': immediateFileCount,
+ 'immediateFileLineCount': immediateFileLineCount,
+ 'numberOfContexts': numberOfContexts,
+ 'transitiveFileCount': transitiveFileCount,
+ 'transitiveFileLineCount': transitiveFileLineCount,
+ 'transitiveFileUniqueCount': transitiveFileUniqueCount,
+ 'transitiveFileUniqueLineCount': transitiveFileUniqueLineCount,
+ };
+
+ /// Event that is emitted when a Dart CLI command has been executed.
+ ///
+ /// [name] - the name of the command that was executed
+ ///
+ /// [enabledExperiments] - a set of Dart language experiments enabled when
+ /// running the command.
+ ///
+ /// [exitCode] - the process exit code set as a result of running the command.
+ Event.dartCliCommandExecuted({
+ required String name,
+ required String enabledExperiments,
+ int? exitCode,
+ }) : eventName = DashEvent.dartCliCommandExecuted,
+ eventData = {
+ 'name': name,
+ 'enabledExperiments': enabledExperiments,
+ if (exitCode != null) 'exitCode': exitCode,
+ };
+
+ /// Event that is sent from DevTools for various different actions as
+ /// indicated by the [eventCategory].
+ ///
+ /// The optional parameters in the parameter list contain metadata that is
+ /// sent with each event, when available.
+ ///
+ /// [additionalMetrics] may contain any additional data for the event being
+ /// sent. This often looks like metrics that are unique to the event or to a
+ /// specific screen.
+ Event.devtoolsEvent({
+ required String screen,
+ required String eventCategory,
+ required String label,
+ required int value,
+
+ // Defaulted values
+ bool userInitiatedInteraction = true,
+
+ // Optional parameters
+ String? g3Username,
+ String? userApp,
+ String? userBuild,
+ String? userPlatform,
+ String? devtoolsPlatform,
+ String? devtoolsChrome,
+ String? devtoolsVersion,
+ String? ideLaunched,
+ String? isExternalBuild,
+ String? isEmbedded,
+ String? ideLaunchedFeature,
+ String? isWasm,
+ CustomMetrics? additionalMetrics,
+ }) : eventName = DashEvent.devtoolsEvent,
+ eventData = {
+ 'screen': screen,
+ 'eventCategory': eventCategory,
+ 'label': label,
+ 'value': value,
+
+ 'userInitiatedInteraction': userInitiatedInteraction,
+
+ // Optional parameters
+ if (g3Username != null) 'g3Username': g3Username,
+ if (userApp != null) 'userApp': userApp,
+ if (userBuild != null) 'userBuild': userBuild,
+ if (userPlatform != null) 'userPlatform': userPlatform,
+ if (devtoolsPlatform != null) 'devtoolsPlatform': devtoolsPlatform,
+ if (devtoolsChrome != null) 'devtoolsChrome': devtoolsChrome,
+ if (devtoolsVersion != null) 'devtoolsVersion': devtoolsVersion,
+ if (ideLaunched != null) 'ideLaunched': ideLaunched,
+ if (isExternalBuild != null) 'isExternalBuild': isExternalBuild,
+ if (isEmbedded != null) 'isEmbedded': isEmbedded,
+ if (ideLaunchedFeature != null)
+ 'ideLaunchedFeature': ideLaunchedFeature,
+ if (isWasm != null) 'isWasm': isWasm,
+ if (additionalMetrics != null) ...additionalMetrics.toMap(),
+ };
+
+ /// Event that contains the results for a specific doctor validator.
+ ///
+ /// [validatorName] - the name for the doctor validator.
+ ///
+ /// [result] - the final result for a specific doctor validator.
+ ///
+ /// [partOfGroupedValidator] - `true` indicates that this validator belongs
+ /// to a grouped validator.
+ ///
+ /// [doctorInvocationId] - epoch formatted timestamp that can be used in
+ /// combination with the client ID in GA4 to group the validators that
+ /// ran in one doctor invocation.
+ ///
+ /// [statusInfo] - optional description of the result from the
+ /// doctor validator.
+ Event.doctorValidatorResult({
+ required String validatorName,
+ required String result,
+ required bool partOfGroupedValidator,
+ required int doctorInvocationId,
+ String? statusInfo,
+ }) : eventName = DashEvent.doctorValidatorResult,
+ eventData = {
+ 'validatorName': validatorName,
+ 'result': result,
+ 'partOfGroupedValidator': partOfGroupedValidator,
+ 'doctorInvocationId': doctorInvocationId,
+ if (statusInfo != null) 'statusInfo': statusInfo,
+ };
+
+ /// Generic event for all dash tools to use when encountering an
+ /// exception that we want to log.
+ ///
+ /// [exception] - string representation of the exception that occured.
+ /// [data] - optional structured data to include with the exception event.
+ Event.exception({
+ required String exception,
+ Map<String, Object?> data = const <String, Object?>{},
+ }) : eventName = DashEvent.exception,
+ eventData = {
+ 'exception': exception,
+ ...Map.from(data)..removeWhere((key, value) => value == null),
+ };
+
+ /// Event that is emitted from the flutter tool when a build invocation
+ /// has been run by the user.
+ ///
+ /// [label] - the identifier for that build event.
+ ///
+ /// [buildType] - the identifier for which platform the build event was for,
+ /// examples include "ios", "gradle", and "web".
+ ///
+ /// [command] - the command that was ran to kick off the build event.
+ ///
+ /// [settings] - the settings used for the build event related to
+ /// configuration and other relevant build information.
+ ///
+ /// [error] - short identifier used to explain the cause of the build error,
+ /// stacktraces should not be passed to this parameter.
+ Event.flutterBuildInfo({
+ required String label,
+ required String buildType,
+ String? command,
+ String? settings,
+ String? error,
+ }) : eventName = DashEvent.flutterBuildInfo,
+ eventData = {
+ 'label': label,
+ 'buildType': buildType,
+ if (command != null) 'command': command,
+ if (settings != null) 'settings': settings,
+ if (error != null) 'error': error,
+ };
+
+ /// Provides information about which flutter command was run
+ /// and whether it was successful.
+ ///
+ /// [commandPath] - information about the flutter command, such as "build/apk".
+ ///
+ /// [result] - if the command failed or succeeded.
+ ///
+ /// [commandHasTerminal] - boolean indicating if the flutter command ran with
+ /// a terminal.
+ ///
+ /// [maxRss] - maximum resident size for a given flutter command.
+ Event.flutterCommandResult({
+ required String commandPath,
+ required String result,
+ required bool commandHasTerminal,
+ int? maxRss,
+ }) : eventName = DashEvent.flutterCommandResult,
+ eventData = {
+ 'commandPath': commandPath,
+ 'result': result,
+ 'commandHasTerminal': commandHasTerminal,
+ if (maxRss != null) 'maxRss': maxRss,
+ };
+
+ // TODO: eliasyishak, remove this or replace once we have a generic
+ // timing event that can be used by potentially more than one DashTool
+ Event.hotReloadTime({required int timeMs})
+ : eventName = DashEvent.hotReloadTime,
+ eventData = {'timeMs': timeMs};
+
+ /// Events to be sent for the Flutter Hot Runner.
+ Event.hotRunnerInfo({
+ required String label,
+ required String targetPlatform,
+ required String sdkName,
+ required bool emulator,
+ required bool fullRestart,
+ String? reason,
+ int? finalLibraryCount,
+ int? syncedLibraryCount,
+ int? syncedClassesCount,
+ int? syncedProceduresCount,
+ int? syncedBytes,
+ int? invalidatedSourcesCount,
+ int? transferTimeInMs,
+ int? overallTimeInMs,
+ int? compileTimeInMs,
+ int? findInvalidatedTimeInMs,
+ int? scannedSourcesCount,
+ int? reassembleTimeInMs,
+ int? reloadVMTimeInMs,
+ }) : eventName = DashEvent.hotRunnerInfo,
+ eventData = {
+ 'label': label,
+ 'targetPlatform': targetPlatform,
+ 'sdkName': sdkName,
+ 'emulator': emulator,
+ 'fullRestart': fullRestart,
+ if (reason != null) 'reason': reason,
+ if (finalLibraryCount != null) 'finalLibraryCount': finalLibraryCount,
+ if (syncedLibraryCount != null)
+ 'syncedLibraryCount': syncedLibraryCount,
+ if (syncedClassesCount != null)
+ 'syncedClassesCount': syncedClassesCount,
+ if (syncedProceduresCount != null)
+ 'syncedProceduresCount': syncedProceduresCount,
+ if (syncedBytes != null) 'syncedBytes': syncedBytes,
+ if (invalidatedSourcesCount != null)
+ 'invalidatedSourcesCount': invalidatedSourcesCount,
+ if (transferTimeInMs != null) 'transferTimeInMs': transferTimeInMs,
+ if (overallTimeInMs != null) 'overallTimeInMs': overallTimeInMs,
+ if (compileTimeInMs != null) 'compileTimeInMs': compileTimeInMs,
+ if (findInvalidatedTimeInMs != null)
+ 'findInvalidatedTimeInMs': findInvalidatedTimeInMs,
+ if (scannedSourcesCount != null)
+ 'scannedSourcesCount': scannedSourcesCount,
+ if (reassembleTimeInMs != null)
+ 'reassembleTimeInMs': reassembleTimeInMs,
+ if (reloadVMTimeInMs != null) 'reloadVMTimeInMs': reloadVMTimeInMs,
+ };
+
+ // TODO: eliasyishak, add better dartdocs to explain each param
+ /// Event that is emitted periodically to report the number of times each lint
+ /// has been enabled.
+ ///
+ /// [count] - the number of contexts in which the lint was enabled.
+ ///
+ /// [name] - the name of the lint.
+ Event.lintUsageCount({
+ required int count,
+ required String name,
+ }) : eventName = DashEvent.lintUsageCount,
+ eventData = {
+ 'count': count,
+ 'name': name,
+ };
+
+ /// Event that is emitted periodically to report the amount of memory being
+ /// used.
+ ///
+ /// [rss] - the resident set size in megabytes.
+ ///
+ /// If this is not the first time memory has been reported for this session,
+ /// then the following parameters should be included:
+ ///
+ /// [periodSec] - the number of seconds since the last memory usage data was
+ /// gathered.
+ ///
+ /// [mbPerSec] - the number of megabytes of memory that were added or
+ /// subtracted per second since the last report.
+ Event.memoryInfo({
+ required int rss,
+ int? periodSec,
+ double? mbPerSec,
+ }) : eventName = DashEvent.memoryInfo,
+ eventData = {
+ 'rss': rss,
+ if (periodSec != null) 'periodSec': periodSec,
+ if (mbPerSec != null) 'mbPerSec': mbPerSec
+ };
+
+ /// Event that is emitted periodically to report the performance of plugins
+ /// when handling requests.
+ ///
+ /// [duration] - json encoded percentile values indicating how long it took
+ /// from the time the request was sent to the plugin until the response
+ /// was processed by the server.
+ ///
+ /// [method] - the name of the request sent to the plugin.
+ ///
+ /// [pluginId] - the id of the plugin whose performance is being reported.
+ Event.pluginRequest({
+ required String duration,
+ required String method,
+ required String pluginId,
+ }) : eventName = DashEvent.pluginRequest,
+ eventData = {
+ 'duration': duration,
+ 'method': method,
+ 'pluginId': pluginId,
+ };
+
+ /// Event that is emitted periodically to report the frequency with which a
+ /// given plugin has been used.
+ ///
+ /// [count] - the number of times plugins usage was changed, which will always
+ /// be at least one.
+ ///
+ /// [enabled] - json encoded percentile values indicating the number of
+ /// contexts for which the plugin was enabled.
+ ///
+ /// [pluginId] - the id of the plugin associated with the data.
+ Event.pluginUse({
+ required int count,
+ required String enabled,
+ required String pluginId,
+ }) : eventName = DashEvent.pluginUse,
+ eventData = {
+ 'count': count,
+ 'enabled': enabled,
+ 'pluginId': pluginId,
+ };
+
+ /// Event that is emitted when `pub get` is run.
+ ///
+ /// [packageName] - the name of the package that was resolved
+ ///
+ /// [version] - the resolved, canonicalized package version
+ ///
+ /// [dependencyType] - the kind of dependency that resulted in this package
+ /// being resolved (e.g., direct, transitive, or dev dependencies).
+ Event.pubGet({
+ required String packageName,
+ required String version,
+ required String dependencyType,
+ }) : eventName = DashEvent.pubGet,
+ eventData = {
+ 'packageName': packageName,
+ 'version': version,
+ 'dependencyType': dependencyType,
+ };
+
+ /// Event that is emitted on shutdown to report information about the whole
+ /// session for which the analysis server was running.
+ ///
+ /// [clientId] - the id of the client that started the server.
+ ///
+ /// [clientVersion] - the version of the client that started the server.
+ ///
+ /// [duration] - the number of milliseconds for which the server was running.
+ ///
+ /// [flags] - the flags passed to the analysis server on startup, without any
+ /// argument values for flags that take values, or an empty string if
+ /// there were no arguments.
+ ///
+ /// [parameters] - the names of the parameters passed to the `initialize`
+ /// request, or an empty string if the `initialize` request was not sent
+ /// or if there were no parameters given.
+ Event.serverSession({
+ required String clientId,
+ required String clientVersion,
+ required int duration,
+ required String flags,
+ required String parameters,
+ }) : eventName = DashEvent.serverSession,
+ eventData = {
+ 'clientId': clientId,
+ 'clientVersion': clientVersion,
+ 'duration': duration,
+ 'flags': flags,
+ 'parameters': parameters,
+ };
+
+ /// Event that is emitted periodically to report the number of times the
+ /// severity of a diagnostic was changed in the analysis options file.
+ ///
+ /// [diagnostic] - the name of the diagnostic whose severity was changed.
+ ///
+ /// [adjustments] - json encoded map of severities to the number of times the
+ /// diagnostic's severity was changed to the key.
+ Event.severityAdjustment({
+ required String diagnostic,
+ required String adjustments,
+ }) : eventName = DashEvent.severityAdjustment,
+ eventData = {
+ 'diagnostic': diagnostic,
+ 'adjustments': adjustments,
+ };
+
+ /// Event that is emitted by `package:unified_analytics` when
+ /// the user takes action when prompted with a survey.
+ ///
+ /// [surveyId] - the unique id for a given survey.
+ ///
+ /// [status] - the string identifier for a given `SurveyButton` under the
+ /// `action` field.
+ Event.surveyAction({
+ required String surveyId,
+ required String status,
+ }) : eventName = DashEvent.surveyAction,
+ eventData = {
+ 'surveyId': surveyId,
+ 'status': status,
+ };
+
+ /// Event that is emitted by `package:unified_analytics` when the
+ /// user has been shown a survey.
+ ///
+ /// [surveyId] - the unique id for a given survey.
+ Event.surveyShown({
+ required String surveyId,
+ }) : eventName = DashEvent.surveyShown,
+ eventData = {
+ 'surveyId': surveyId,
+ };
+
+ /// Event that records how long a given process takes to complete.
+ ///
+ /// [workflow] - the overall process or command being run, for example
+ /// "build" is a possible value for the flutter tool.
+ ///
+ /// [variableName] - the specific variable being measured, for example
+ /// "gradle" would indicate how long it took for a gradle build under the
+ /// "build" [workflow].
+ ///
+ /// [elapsedMilliseconds] - how long the process took in milliseconds.
+ ///
+ /// [label] - an optional field that can be used for further filtering, for
+ /// example, "success" can indicate how long a successful build in gradle
+ /// takes to complete.
+ Event.timing({
+ required String workflow,
+ required String variableName,
+ required int elapsedMilliseconds,
+ String? label,
+ }) : eventName = DashEvent.timing,
+ eventData = {
+ 'workflow': workflow,
+ 'variableName': variableName,
+ 'elapsedMilliseconds': elapsedMilliseconds,
+ if (label != null) 'label': label,
+ };
+
+ /// Private constructor to be used when deserializing JSON into an instance
+ /// of [Event].
+ Event._({required this.eventName, required this.eventData});
+
+ @override
+ int get hashCode => Object.hash(eventName, jsonEncode(eventData));
+
+ @override
+ bool operator ==(Object other) =>
+ other is Event &&
+ other.runtimeType == runtimeType &&
+ other.eventName == eventName &&
+ _compareEventData(other.eventData, eventData);
+
+ /// Converts an instance of [Event] to JSON.
+ String toJson() => jsonEncode({
+ 'eventName': eventName.label,
+ 'eventData': eventData,
+ });
+
+ @override
+ String toString() => toJson();
+
+ /// Utility function to take in two maps [a] and [b] and compares them
+ /// to ensure that they have the same keys and values
+ bool _compareEventData(Map<String, Object?> a, Map<String, Object?> b) {
+ final keySetA = a.keys.toSet();
+ final keySetB = b.keys.toSet();
+ final intersection = keySetA.intersection(keySetB);
+
+ // Ensure that the keys are the same for each object
+ if (intersection.length != keySetA.length ||
+ intersection.length != keySetB.length) {
+ return false;
+ }
+
+ // Ensure that each of the key's values are the same
+ for (final key in a.keys) {
+ if (a[key] != b[key]) return false;
+ }
+
+ return true;
+ }
+
+ /// Returns a valid instance of [Event] if [json] follows the correct schema.
+ ///
+ /// Common use case for this static method involves clients of this package
+ /// that have a client-server setup where the server sends events that the
+ /// client creates.
+ static Event? fromJson(String json) {
+ try {
+ final jsonMap = jsonDecode(json) as Map<String, Object?>;
+
+ // Ensure that eventName is a string and a valid label and
+ // eventData is a nested object
+ if (jsonMap
+ case {
+ 'eventName': final String eventName,
+ 'eventData': final Map<String, Object?> eventData,
+ }) {
+ final dashEvent = DashEvent.fromLabel(eventName);
+ if (dashEvent == null) return null;
+
+ return Event._(
+ eventName: dashEvent,
+ eventData: eventData,
+ );
+ }
+
+ return null;
+ } on FormatException {
+ return null;
+ }
+ }
+}
+
+/// A base class for custom metrics that will be defined by a unified_analytics
+/// client.
+///
+/// This base type can be used as a parameter in any event constructor that
+/// allows custom metrics to be added by a unified_analytics client.
+abstract base class CustomMetrics {
+ /// Converts the custom metrics data to a [Map] object.
+ ///
+ /// This must be a JSON encodable [Map].
+ Map<String, Object> toMap();
+}
diff --git a/pkgs/unified_analytics/lib/src/ga_client.dart b/pkgs/unified_analytics/lib/src/ga_client.dart
new file mode 100644
index 0000000..2c3e83e
--- /dev/null
+++ b/pkgs/unified_analytics/lib/src/ga_client.dart
@@ -0,0 +1,86 @@
+// Copyright (c) 2023, 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 'package:http/http.dart' as http;
+
+import 'constants.dart';
+
+class FakeGAClient implements GAClient {
+ const FakeGAClient();
+
+ @override
+ String get apiSecret => throw UnimplementedError();
+
+ @override
+ String get measurementId => throw UnimplementedError();
+
+ @override
+ String get postUrl => throw UnimplementedError();
+
+ @override
+ http.Client get _client => throw UnimplementedError();
+
+ @override
+ void close() {}
+
+ @override
+ Future<http.Response> sendData(Map<String, Object?> body) =>
+ Future<http.Response>.value(http.Response('', 200));
+}
+
+class GAClient {
+ final String measurementId;
+ final String apiSecret;
+ final String postUrl;
+ final http.Client _client;
+
+ GAClient({
+ required this.measurementId,
+ required this.apiSecret,
+ }) : postUrl = '$kAnalyticsUrl?'
+ 'measurement_id=$measurementId&api_secret=$apiSecret',
+ _client = http.Client();
+
+ /// Closes the http client's connection to prevent lingering requests.
+ void close() => _client.close();
+
+ /// Receive the payload in Map form and parse
+ /// into JSON to send to GA.
+ ///
+ /// The [http.Response] returned from this method can be
+ /// checked to ensure that events have been sent. A response
+ /// status code of `2xx` indicates a successful send event.
+ /// A response status code of `500` indicates an error occured on the send
+ /// can the error message can be found in the [http.Response.body].
+ Future<http.Response> sendData(Map<String, Object?> body) async {
+ final uri = Uri.parse(postUrl);
+
+ // Using a try catch all since post method can result in several
+ // errors; clients using this method can check the awaited status
+ // code to get a specific error message if the status code returned
+ // is a 500 error status code
+ try {
+ return await _client.post(
+ uri,
+ headers: <String, String>{
+ 'Content-Type': 'application/json; charset=UTF-8',
+ },
+ body: jsonEncode(body),
+ );
+ // ignore: avoid_catches_without_on_clauses
+ } catch (error) {
+ return Future<http.Response>.value(
+ http.Response(
+ error.toString(),
+ 500,
+ headers: <String, String>{
+ 'content-type': 'text/plain; charset=utf-8',
+ },
+ ),
+ );
+ }
+ }
+}
diff --git a/pkgs/unified_analytics/lib/src/initializer.dart b/pkgs/unified_analytics/lib/src/initializer.dart
new file mode 100644
index 0000000..131968b
--- /dev/null
+++ b/pkgs/unified_analytics/lib/src/initializer.dart
@@ -0,0 +1,118 @@
+// Copyright (c) 2023, 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:clock/clock.dart';
+import 'package:file/file.dart';
+
+import 'constants.dart';
+import 'utils.dart';
+
+/// Creates the text file that will contain the client ID
+/// which will be used across all related tools for analytics
+/// reporting in GA.
+void createClientIdFile({required File clientIdFile}) {
+ clientIdFile.createSync(recursive: true);
+ clientIdFile.writeAsStringSync(Uuid().generateV4());
+}
+
+/// Creates the configuration file with the default message
+/// in the user's home directory.
+void createConfigFile({
+ required File configFile,
+ required Directory homeDirectory,
+}) {
+ configFile.createSync(recursive: true);
+
+ // If the user was previously opted out, then we will
+ // replace the line that assumes automatic opt in with
+ // an opt out from the start
+ if (legacyOptOut(homeDirectory: homeDirectory)) {
+ configFile.writeAsStringSync(
+ kConfigString.replaceAll('reporting=1', 'reporting=0'));
+ } else {
+ configFile.writeAsStringSync(kConfigString);
+ }
+}
+
+/// Creates that file that will persist dismissed survey ids.
+void createDismissedSurveyFile({required File dismissedSurveyFile}) {
+ dismissedSurveyFile.createSync(recursive: true);
+ dismissedSurveyFile.writeAsStringSync('{}');
+}
+
+/// Creates that log file that will store the record formatted
+/// events locally on the user's machine.
+void createLogFile({required File logFile}) {
+ logFile.createSync(recursive: true);
+}
+
+/// Creates the session file which will contain
+/// the current session id which is the current timestamp.
+///
+/// It also returns the timestamp used for the session if it needs
+/// to be accessed.
+DateTime createSessionFile({required File sessionFile}) {
+ final now = clock.now();
+ sessionFile.createSync(recursive: true);
+ writeSessionContents(sessionFile: sessionFile);
+
+ return now;
+}
+
+/// Performs all of the initialization checks for the required files.
+///
+/// Returns `true` if the config file was created indicating it is the first
+/// time this package was run on a user's machine.
+///
+/// Checks for the following:
+/// - Config file
+/// - Client ID file
+/// - Session JSON file
+/// - Log file
+/// - Dismissed survey JSON file
+bool runInitialization({
+ required Directory homeDirectory,
+}) {
+ var firstRun = false;
+ final dartToolDirectory =
+ homeDirectory.childDirectory(kDartToolDirectoryName);
+
+ // When the config file doesn't exist, initialize it with the default tools
+ // and the current date.
+ final configFile = dartToolDirectory.childFile(kConfigFileName);
+ if (!configFile.existsSync()) {
+ firstRun = true;
+ createConfigFile(
+ configFile: configFile,
+ homeDirectory: homeDirectory,
+ );
+ }
+
+ // Begin initialization checks for the client id.
+ final clientFile = dartToolDirectory.childFile(kClientIdFileName);
+ if (!clientFile.existsSync()) {
+ createClientIdFile(clientIdFile: clientFile);
+ }
+
+ // Begin initialization checks for the session file.
+ final sessionFile = dartToolDirectory.childFile(kSessionFileName);
+ if (!sessionFile.existsSync()) {
+ createSessionFile(sessionFile: sessionFile);
+ }
+
+ // Begin initialization checks for the log file to persist events locally.
+ final logFile = dartToolDirectory.childFile(kLogFileName);
+ if (!logFile.existsSync()) {
+ createLogFile(logFile: logFile);
+ }
+
+ // Begin initialization checks for the dismissed survey file.
+ final dismissedSurveyFile =
+ dartToolDirectory.childFile(kDismissedSurveyFileName);
+ if (!dismissedSurveyFile.existsSync()) {
+ createDismissedSurveyFile(dismissedSurveyFile: dismissedSurveyFile);
+ }
+
+ return firstRun;
+}
diff --git a/pkgs/unified_analytics/lib/src/log_handler.dart b/pkgs/unified_analytics/lib/src/log_handler.dart
new file mode 100644
index 0000000..a6ef2f5
--- /dev/null
+++ b/pkgs/unified_analytics/lib/src/log_handler.dart
@@ -0,0 +1,464 @@
+// Copyright (c) 2023, 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 'package:clock/clock.dart';
+import 'package:file/file.dart';
+
+import 'constants.dart';
+import 'event.dart';
+import 'initializer.dart';
+
+/// Data class that will be returned when analyzing the
+/// persisted log file on the client's machine.
+class LogFileStats {
+ /// The oldest timestamp in the log file.
+ final DateTime startDateTime;
+
+ /// Number of minutes from [startDateTime] to now using [clock].
+ final int minsFromStartDateTime;
+
+ /// The latest timestamp in the log file.
+ final DateTime endDateTime;
+
+ /// Number of minutes from [endDateTime] to now using [clock].
+ final int minsFromEndDateTime;
+
+ /// The number of unique session ids found in the log file.
+ final int sessionCount;
+
+ /// The map containing all of the flutter channels and a count
+ /// of how many events were under each channel.
+ ///
+ /// ```
+ /// {
+ /// 'stable': 123,
+ /// 'beta': 50,
+ /// 'master': 5,
+ /// }
+ /// ```
+ final Map<String, int> flutterChannelCount;
+
+ /// The map containing all of the tools that have sent events
+ /// and how many events were sent by each tool.
+ ///
+ /// ```
+ /// {
+ /// 'flutter-tool': 500,
+ /// 'dart-tool': 45,
+ /// 'vscode-plugins': 321,
+ /// }
+ /// ```
+ final Map<String, int> toolCount;
+
+ /// The map containing all of the events in the file along with
+ /// how many times they have occured.
+ ///
+ /// ```
+ /// {
+ /// 'client_request': 345,
+ /// 'hot_reload_time': 765,
+ /// 'memory_info': 90,
+ /// }
+ /// ```
+ final Map<String, int> eventCount;
+
+ /// Total number of records in the log file.
+ final int recordCount;
+
+ /// Contains the data from the [LogHandler.logFileStats] method.
+ const LogFileStats({
+ required this.startDateTime,
+ required this.minsFromStartDateTime,
+ required this.endDateTime,
+ required this.minsFromEndDateTime,
+ required this.sessionCount,
+ required this.flutterChannelCount,
+ required this.toolCount,
+ required this.recordCount,
+ required this.eventCount,
+ });
+
+ /// Pass in a string label for one of the instance variables
+ /// and return the integer value of that label.
+ ///
+ /// If label passed for [DateTime] instance variable, integer
+ /// in the form of [DateTime.millisecondsSinceEpoch] will be
+ /// returned.
+ ///
+ /// Returns null if the label passed does not match anything.
+ int? getValueByString(String label) {
+ // When querying counts, the label will include the
+ // key for the appropriate map
+ //
+ // Example: logFileStats.toolCount.flutter-tool is asking
+ // for the number of events sent via flutter cli
+ final parts = label.split('.');
+ String? key;
+ if (parts.length >= 3) {
+ // Assign the first two parts of the string as the label
+ // ie. logFileStats.toolCount.flutter-tool -> logFileStats.toolCount
+ label = parts.sublist(0, 2).join('.');
+ key = parts.sublist(2, parts.length).join('.');
+ }
+
+ switch (label) {
+ case 'logFileStats.startDateTime':
+ return startDateTime.millisecondsSinceEpoch;
+ case 'logFileStats.minsFromStartDateTime':
+ return minsFromStartDateTime;
+ case 'logFileStats.endDateTime':
+ return endDateTime.millisecondsSinceEpoch;
+ case 'logFileStats.minsFromEndDateTime':
+ return minsFromEndDateTime;
+ case 'logFileStats.sessionCount':
+ return sessionCount;
+ case 'logFileStats.recordCount':
+ return recordCount;
+ case 'logFileStats.flutterChannelCount':
+ if (key != null && flutterChannelCount.containsKey(key)) {
+ return flutterChannelCount[key];
+ }
+ case 'logFileStats.toolCount':
+ if (key != null && toolCount.containsKey(key)) {
+ return toolCount[key];
+ }
+ case 'logFileStats.eventCount':
+ if (key != null && eventCount.containsKey(key)) {
+ return eventCount[key];
+ }
+ }
+
+ return null;
+ }
+
+ @override
+ String toString() {
+ const encoder = JsonEncoder.withIndent(' ');
+ return encoder.convert({
+ 'startDateTime': startDateTime.toString(),
+ 'minsFromStartDateTime': minsFromStartDateTime,
+ 'endDateTime': endDateTime.toString(),
+ 'minsFromEndDateTime': minsFromEndDateTime,
+ 'sessionCount': sessionCount,
+ 'recordCount': recordCount,
+ 'eventCount': eventCount,
+ 'toolCount': toolCount,
+ 'flutterChannelCount': flutterChannelCount,
+ });
+ }
+}
+
+/// This class is responsible for writing to a log
+/// file that has been initialized by the [createLogFile].
+///
+/// It will be treated as an append only log and will be limited
+/// to have has many data records as specified by [kLogFileLength].
+class LogHandler {
+ final File logFile;
+
+ /// Contains instances of [Event.analyticsException] that were encountered
+ /// during a workflow and will be sent to GA4 for collection.
+ final Set<Event> errorSet = {};
+
+ /// A log handler constructor that will delegate saving
+ /// logs and retrieving stats from the persisted log.
+ LogHandler({required this.logFile});
+
+ /// Get stats from the persisted log file.
+ ///
+ /// Note that some developers may only be Dart
+ /// developers and will not have any data for flutter
+ /// related metrics.
+ LogFileStats? logFileStats() {
+ // Parse each line of the log file through [LogItem],
+ // some returned records may be null if malformed, they will be
+ // removed later through `whereType<LogItem>`
+ final records = logFile
+ .readAsLinesSync()
+ .map((String e) {
+ try {
+ return LogItem.fromRecord(jsonDecode(e) as Map<String, Object?>);
+ } on FormatException catch (err) {
+ errorSet.add(Event.analyticsException(
+ workflow: 'LogFileStats.logFileStats',
+ error: err.runtimeType.toString(),
+ description: 'message: ${err.message}\nsource: ${err.source}',
+ ));
+
+ return null;
+ // ignore: avoid_catching_errors
+ } on TypeError catch (err) {
+ errorSet.add(Event.analyticsException(
+ workflow: 'LogFileStats.logFileStats',
+ error: err.runtimeType.toString(),
+ ));
+
+ return null;
+ }
+ })
+ .whereType<LogItem>()
+ .toList();
+
+ if (records.isEmpty) return null;
+
+ // Get the start and end dates for the log file
+ final startDateTime = records.first.localTime;
+ final endDateTime = records.last.localTime;
+
+ // Map with counters for user properties
+ final counter = <String, Set<Object>>{
+ 'sessions': <int>{},
+ 'flutter_channel': <String>{},
+ 'tool': <String>{},
+ };
+
+ // Map of counters for each event
+ final eventCount = <String, int>{};
+ final flutterChannelCount = <String, int>{};
+ final toolCount = <String, int>{};
+ for (final record in records) {
+ counter['sessions']!.add(record.sessionId);
+ counter['tool']!.add(record.tool);
+ if (record.flutterChannel != null) {
+ counter['flutter_channel']!.add(record.flutterChannel!);
+ }
+
+ // Count each event, if it doesn't exist in the [eventCount]
+ // it will be added first
+ if (!eventCount.containsKey(record.eventName)) {
+ eventCount[record.eventName] = 0;
+ }
+ eventCount[record.eventName] = eventCount[record.eventName]! + 1;
+
+ // Counting how many events were recorded for each tool
+ if (!toolCount.containsKey(record.tool)) {
+ toolCount[record.tool] = 0;
+ }
+ toolCount[record.tool] = toolCount[record.tool]! + 1;
+
+ // Necessary to perform a null check for flutter channel because
+ // not all events will have information about flutter
+ if (record.flutterChannel != null) {
+ final flutterChannel = record.flutterChannel!;
+ if (!flutterChannelCount.containsKey(flutterChannel)) {
+ flutterChannelCount[flutterChannel] = 0;
+ }
+
+ flutterChannelCount[flutterChannel] =
+ flutterChannelCount[flutterChannel]! + 1;
+ }
+ }
+
+ final now = clock.now();
+
+ return LogFileStats(
+ startDateTime: startDateTime,
+ minsFromStartDateTime: now.difference(startDateTime).inMinutes,
+ endDateTime: endDateTime,
+ minsFromEndDateTime: now.difference(endDateTime).inMinutes,
+ sessionCount: counter['sessions']!.length,
+ flutterChannelCount: flutterChannelCount,
+ toolCount: toolCount,
+ eventCount: eventCount,
+ recordCount: records.length,
+ );
+ }
+
+ /// Saves the data passed in as a single line in the log file.
+ ///
+ /// This will keep the max number of records limited to equal to
+ /// or less than [kLogFileLength] records.
+ void save({required Map<String, Object?> data}) {
+ try {
+ final stat = logFile.statSync();
+ List<String> records;
+ if (stat.size > kMaxLogFileSize) {
+ logFile.deleteSync();
+ logFile.createSync();
+ records = [];
+ } else {
+ records = logFile.readAsLinesSync();
+ }
+ final content = '${jsonEncode(data)}\n';
+
+ // When the record count is less than the max, add as normal;
+ // else drop the oldest records until equal to max
+ if (records.length < kLogFileLength) {
+ logFile.writeAsStringSync(content, mode: FileMode.writeOnlyAppend);
+ } else {
+ records.add(content);
+ records = records.skip(records.length - kLogFileLength).toList();
+
+ logFile.writeAsStringSync(records.join('\n'));
+ }
+ } on FileSystemException {
+ // Logging isn't important enough to warrant raising a
+ // FileSystemException that will surprise consumers of this package.
+ }
+ }
+}
+
+/// Data class for each record persisted on the client's machine.
+class LogItem {
+ final String eventName;
+ final int sessionId;
+ final String? flutterChannel;
+ final String host;
+ final String? flutterVersion;
+ final String dartVersion;
+ final String tool;
+ final DateTime localTime;
+ final String hostOsVersion;
+ final String locale;
+ final String? clientIde;
+
+ LogItem({
+ required this.eventName,
+ required this.sessionId,
+ this.flutterChannel,
+ required this.host,
+ this.flutterVersion,
+ required this.dartVersion,
+ required this.tool,
+ required this.localTime,
+ required this.hostOsVersion,
+ required this.locale,
+ required this.clientIde,
+ });
+
+ /// Serves a parser for each record in the log file.
+ ///
+ /// Using this method guarantees that we have parsed out
+ /// fields that are necessary for the [LogHandler.logFileStats]
+ /// method.
+ ///
+ /// If the returned value is null, that indicates a malformed
+ /// record which can be discarded during analysis.
+ ///
+ /// Example of what a record looks like:
+ /// ```
+ /// {
+ /// "client_id": "ffcea97b-db5e-4c66-98c2-3942de4fac40",
+ /// "events": [
+ /// {
+ /// "name": "hot_reload_time",
+ /// "params": {
+ /// "timeMs": 135
+ /// }
+ /// }
+ /// ],
+ /// "user_properties": {
+ /// "session_id": {
+ /// "value": 1699385899950
+ /// },
+ /// "flutter_channel": {
+ /// "value": "ey-test-channel"
+ /// },
+ /// "host": {
+ /// "value": "macOS"
+ /// },
+ /// "flutter_version": {
+ /// "value": "Flutter 3.6.0-7.0.pre.47"
+ /// },
+ /// "dart_version": {
+ /// "value": "Dart 2.19.0"
+ /// },
+ /// "analytics_pkg_version": {
+ /// "value": "5.2.0"
+ /// },
+ /// "tool": {
+ /// "value": "flutter-tool"
+ /// },
+ /// "local_time": {
+ /// "value": "2023-11-07 15:09:03.025559 -0500"
+ /// },
+ /// "host_os_version": {
+ /// "value": "Version 14.1 (Build 23B74)"
+ /// },
+ /// "locale": {
+ /// "value": "en"
+ /// },
+ /// "clientIde": {
+ /// "value": "VSCode"
+ /// }
+ /// }
+ /// }
+ /// ```
+ static LogItem? fromRecord(Map<String, Object?> record) {
+ if (!record.containsKey('user_properties') ||
+ !record.containsKey('events')) {
+ return null;
+ }
+
+ // Parse out values from the top level key = 'events' and return
+ // a map for the one event in the value
+ final eventProp =
+ (record['events']! as List<Object?>).first as Map<String, Object?>;
+ final eventName = eventProp['name'] as String;
+
+ // Parse the data out of the `user_properties` value
+ final userProps = record['user_properties'] as Map<String, Object?>;
+
+ // Parse out the values from the top level key = 'user_properties`
+ final sessionId =
+ (userProps['session_id']! as Map<String, Object?>)['value'] as int?;
+ final flutterChannel = (userProps['flutter_channel']!
+ as Map<String, Object?>)['value'] as String?;
+ final host =
+ (userProps['host']! as Map<String, Object?>)['value'] as String?;
+ final flutterVersion = (userProps['flutter_version']!
+ as Map<String, Object?>)['value'] as String?;
+ final dartVersion = (userProps['dart_version']!
+ as Map<String, Object?>)['value'] as String?;
+ final tool =
+ (userProps['tool']! as Map<String, Object?>)['value'] as String?;
+ final localTimeString =
+ (userProps['local_time']! as Map<String, Object?>)['value'] as String?;
+ final hostOsVersion = (userProps['host_os_version']!
+ as Map<String, Object?>)['value'] as String?;
+ final locale =
+ (userProps['locale']! as Map<String, Object?>)['value'] as String?;
+ final clientIde =
+ (userProps['client_ide']! as Map<String, Object?>)['value'] as String?;
+
+ // If any of the above values are null, return null since that
+ // indicates the record is malformed; note that `flutter_version`,
+ // `flutter_channel`, and `client_ide` are nullable fields in the log file
+ final values = <Object?>[
+ // Values associated with the top level key = 'events'
+ eventName,
+
+ // Values associated with the top level key = 'events'
+ sessionId,
+ host,
+ dartVersion,
+ tool,
+ localTimeString,
+ hostOsVersion,
+ locale,
+ ];
+ for (final value in values) {
+ if (value == null) return null;
+ }
+
+ // Parse the local time from the string extracted
+ final localTime = DateTime.parse(localTimeString!).toLocal();
+
+ return LogItem(
+ eventName: eventName,
+ sessionId: sessionId!,
+ flutterChannel: flutterChannel,
+ host: host!,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion!,
+ tool: tool!,
+ localTime: localTime,
+ hostOsVersion: hostOsVersion!,
+ locale: locale!,
+ clientIde: clientIde,
+ );
+ }
+}
diff --git a/pkgs/unified_analytics/lib/src/survey_handler.dart b/pkgs/unified_analytics/lib/src/survey_handler.dart
new file mode 100644
index 0000000..7461dfc
--- /dev/null
+++ b/pkgs/unified_analytics/lib/src/survey_handler.dart
@@ -0,0 +1,366 @@
+// Copyright (c) 2023, 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 'package:clock/clock.dart';
+import 'package:file/file.dart';
+import 'package:http/http.dart' as http;
+
+import 'constants.dart';
+import 'enums.dart';
+import 'initializer.dart';
+import 'log_handler.dart';
+
+class Condition {
+ /// How to query the log file.
+ ///
+ ///
+ /// Example: logFileStats.recordCount refers to the
+ /// total record count being returned by [LogFileStats].
+ final String field;
+
+ /// String representation of operator.
+ ///
+ ///
+ /// Allowed values:
+ /// - '>=' `greater than or equal to`
+ /// - '<=' `less than or equal to`
+ /// - '>' `greater than`
+ /// - '<' `less then`
+ /// - '==' `equals`
+ /// - '!=' `not equal`
+ final String operatorString;
+
+ /// The value we will be comparing against using the [operatorString].
+ final int value;
+
+ /// One of the conditions that need to be valid for
+ /// a survey to be returned to the user.
+ ///
+ /// Example of raw json:
+ /// ```
+ /// {
+ /// "field": "logFileStats.recordCount",
+ /// "operator": ">=",
+ /// "value": 1000
+ /// }
+ /// ```
+ Condition(
+ this.field,
+ this.operatorString,
+ this.value,
+ );
+
+ Condition.fromJson(Map<String, dynamic> json)
+ : field = json['field'] as String,
+ operatorString = json['operator'] as String,
+ value = json['value'] as int;
+
+ Map<String, Object?> toMap() => <String, Object?>{
+ 'field': field,
+ 'operator': operatorString,
+ 'value': value,
+ };
+
+ @override
+ String toString() => jsonEncode(toMap());
+}
+
+/// Data class for the persisted survey contents.
+///
+/// [uniqueId] is the identifier for each survey, [timestamp] refers
+/// to when the survey was added to the persisted file.
+///
+/// The boolean [snoozed] is set to `true` if the survey has been dismissed
+/// temporarily by the user. When set to `false` this indicates that the survey
+/// has been dismissed permanently and will not be shown to the user again.
+class PersistedSurvey {
+ final String uniqueId;
+ final bool snoozed;
+ final DateTime timestamp;
+
+ PersistedSurvey({
+ required this.uniqueId,
+ required this.snoozed,
+ required this.timestamp,
+ });
+
+ @override
+ String toString() => jsonEncode({
+ 'uniqueId': uniqueId,
+ 'snoozed': snoozed,
+ 'timestamp': timestamp.toString(),
+ });
+}
+
+class Survey {
+ final String uniqueId;
+ final DateTime startDate;
+ final DateTime endDate;
+ final String description;
+ final int snoozeForMinutes;
+ final double samplingRate;
+ final List<DashTool> excludeDashToolList;
+ final List<Condition> conditionList;
+ final List<SurveyButton> buttonList;
+
+ /// A data class that contains the relevant information for a given
+ /// survey parsed from the survey's metadata file.
+ const Survey({
+ required this.uniqueId,
+ required this.startDate,
+ required this.endDate,
+ required this.description,
+ required this.snoozeForMinutes,
+ required this.samplingRate,
+ required this.excludeDashToolList,
+ required this.conditionList,
+ required this.buttonList,
+ });
+
+ /// Parse the contents of the json metadata file hosted externally.
+ Survey.fromJson(Map<String, dynamic> json)
+ : uniqueId = json['uniqueId'] as String,
+ startDate = DateTime.parse(json['startDate'] as String),
+ endDate = DateTime.parse(json['endDate'] as String),
+ description = json['description'] as String,
+ // Handle both string and integer fields
+ snoozeForMinutes = json['snoozeForMinutes'] is String
+ ? int.parse(json['snoozeForMinutes'] as String)
+ : json['snoozeForMinutes'] as int,
+ // Handle both string and double fields
+ samplingRate = json['samplingRate'] is String
+ ? double.parse(json['samplingRate'] as String)
+ : json['samplingRate'] as double,
+ excludeDashToolList = (json['excludeDashTools'] as List<dynamic>)
+ .map((e) => DashTool.fromLabel(e as String))
+ .toList(),
+ conditionList = (json['conditions'] as List<dynamic>).map((e) {
+ return Condition.fromJson(e as Map<String, dynamic>);
+ }).toList(),
+ buttonList = (json['buttons'] as List<dynamic>).map((e) {
+ return SurveyButton.fromJson(e as Map<String, dynamic>);
+ }).toList();
+
+ @override
+ String toString() {
+ const encoder = JsonEncoder.withIndent(' ');
+ return encoder.convert({
+ 'uniqueId': uniqueId,
+ 'startDate': startDate.toString(),
+ 'endDate': endDate.toString(),
+ 'description': description,
+ 'snoozeForMinutes': snoozeForMinutes,
+ 'samplingRate': samplingRate,
+ 'conditionList': conditionList.map((e) => e.toMap()).toList(),
+ 'buttonList': buttonList.map((e) => e.toMap()).toList(),
+ });
+ }
+}
+
+class SurveyButton {
+ final String buttonText;
+ final String action;
+ final bool promptRemainsVisible;
+ final String? url;
+
+ SurveyButton({
+ required this.buttonText,
+ required this.action,
+ required this.promptRemainsVisible,
+ this.url,
+ });
+
+ SurveyButton.fromJson(Map<String, dynamic> json)
+ : buttonText = json['buttonText'] as String,
+ action = json['action'] as String,
+ promptRemainsVisible = json['promptRemainsVisible'] as bool,
+ url = json['url'] as String?;
+
+ Map<String, Object?> toMap() => <String, Object?>{
+ 'buttonText': buttonText,
+ 'action': action,
+ 'promptRemainsVisible': promptRemainsVisible,
+ 'url': url,
+ };
+}
+
+class SurveyHandler {
+ final File dismissedSurveyFile;
+
+ SurveyHandler({required this.dismissedSurveyFile});
+
+ /// Invoking this method will persist the survey's id in
+ /// the local file with either a snooze or permanently dismissed
+ /// indicator.
+ ///
+ /// In the snoozed state, the survey will be prompted again after
+ /// the survey's specified snooze period.
+ ///
+ /// Each entry for a survey will have the following format:
+ /// ```
+ /// {
+ /// "survey-unique-id": {
+ /// "status": "snoozed", // status is either snoozed or dismissed
+ /// "timestamp": 1690219834859
+ /// }
+ /// }
+ /// ```
+ void dismiss(Survey survey, bool permanently) {
+ final contents = _parseJsonFile();
+
+ // Add the new data and write back out to the file
+ final status = permanently ? 'dismissed' : 'snoozed';
+ contents[survey.uniqueId] = {
+ 'status': status,
+ 'timestamp': clock.now().millisecondsSinceEpoch,
+ };
+
+ dismissedSurveyFile.writeAsStringSync(jsonEncode(contents));
+ }
+
+ /// Retrieve a list of strings for each [Survey] persisted on disk.
+ ///
+ /// The survey may be in a snoozed or dismissed state based on user action.
+ Map<String, PersistedSurvey> fetchPersistedSurveys() {
+ final contents = _parseJsonFile();
+
+ // Initialize the list of persisted surveys and add to them
+ // as they are being parsed
+ final persistedSurveys = <String, PersistedSurvey>{};
+ contents.forEach((key, value) {
+ value as Map<String, dynamic>;
+
+ final uniqueId = key;
+ final snoozed = value['status'] == 'snoozed' ? true : false;
+ final timestamp =
+ DateTime.fromMillisecondsSinceEpoch(value['timestamp'] as int);
+
+ persistedSurveys[uniqueId] = PersistedSurvey(
+ uniqueId: uniqueId,
+ snoozed: snoozed,
+ timestamp: timestamp,
+ );
+ });
+
+ return persistedSurveys;
+ }
+
+ /// Retrieves the survey metadata file from [kContextualSurveyUrl].
+ Future<List<Survey>> fetchSurveyList() async {
+ final List<dynamic> body;
+ try {
+ final payload = await _fetchContents();
+ body = jsonDecode(payload) as List<dynamic>;
+ // ignore: avoid_catches_without_on_clauses
+ } catch (err) {
+ return [];
+ }
+
+ final surveyList = parseSurveysFromJson(body);
+
+ return surveyList;
+ }
+
+ /// Fetches the json in string form from the remote location.
+ Future<String> _fetchContents() async {
+ final uri = Uri.parse(kContextualSurveyUrl);
+ final response = await http.get(uri);
+ return response.body;
+ }
+
+ /// Method to return a Map representation of the json persisted file.
+ Map<String, dynamic> _parseJsonFile() {
+ Map<String, dynamic> contents;
+ try {
+ contents = jsonDecode(dismissedSurveyFile.readAsStringSync())
+ as Map<String, dynamic>;
+ } on FormatException {
+ createDismissedSurveyFile(dismissedSurveyFile: dismissedSurveyFile);
+ contents = {};
+ } on FileSystemException {
+ createDismissedSurveyFile(dismissedSurveyFile: dismissedSurveyFile);
+ contents = {};
+ }
+
+ return contents;
+ }
+
+ /// Function to ensure that each survey is still valid by
+ /// checking the [Survey.startDate] and [Survey.endDate]
+ /// against the current [clock] now date.
+ static bool checkSurveyDate(Survey survey) {
+ final now = clock.now();
+ return survey.startDate.isBefore(now) && survey.endDate.isAfter(now);
+ }
+
+ /// Function that takes in a json data structure that is in
+ /// the form of a list and returns a list of [Survey] items.
+ ///
+ /// This will also check the survey's dates to make sure it
+ /// has not expired.
+ static List<Survey> parseSurveysFromJson(List<dynamic> body) => body
+ .map((element) {
+ // Error handling to skip any surveys from the remote location
+ // that fail to parse
+ try {
+ return Survey.fromJson(element as Map<String, dynamic>);
+ // ignore: avoid_catching_errors
+ } on TypeError {
+ return null;
+ } on FormatException {
+ return null;
+ } on Exception {
+ return null;
+ }
+ })
+ .whereType<Survey>()
+ .where(checkSurveyDate)
+ .toList();
+}
+
+class FakeSurveyHandler extends SurveyHandler {
+ final List<Survey> _fakeInitializedSurveys = [];
+
+ /// Use this class in tests if you can provide the
+ /// list of [Survey] objects.
+ ///
+ /// Important: the surveys in the [initializedSurveys] list
+ /// will have their dates checked to ensure they are valid; it is
+ /// recommended to use `package:clock` to set a fixed time for testing.
+ FakeSurveyHandler.fromList({
+ required super.dismissedSurveyFile,
+ required List<Survey> initializedSurveys,
+ }) {
+ // We must pass the surveys from the list to the
+ // `checkSurveyDate` function here and not for the
+ // `.fromString()` constructor because the `parseSurveysFromJson`
+ // method already checks their date
+ for (final survey in initializedSurveys) {
+ if (SurveyHandler.checkSurveyDate(survey)) {
+ _fakeInitializedSurveys.add(survey);
+ }
+ }
+ }
+
+ /// Use this class in tests if you can provide raw
+ /// json strings to simulate a response from a remote server.
+ FakeSurveyHandler.fromString({
+ required super.dismissedSurveyFile,
+ required String content,
+ }) {
+ final body = jsonDecode(content) as List<dynamic>;
+ for (final fakeSurvey in SurveyHandler.parseSurveysFromJson(body)) {
+ _fakeInitializedSurveys.add(fakeSurvey);
+ }
+ }
+
+ @override
+ Future<List<Survey>> fetchSurveyList() =>
+ Future<List<Survey>>.value(_fakeInitializedSurveys);
+
+ @override
+ Future<String> _fetchContents() => throw UnimplementedError();
+}
diff --git a/pkgs/unified_analytics/lib/src/user_property.dart b/pkgs/unified_analytics/lib/src/user_property.dart
new file mode 100644
index 0000000..f0e177d
--- /dev/null
+++ b/pkgs/unified_analytics/lib/src/user_property.dart
@@ -0,0 +1,162 @@
+// Copyright (c) 2023, 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 'package:clock/clock.dart';
+import 'package:file/file.dart';
+
+import 'constants.dart';
+import 'event.dart';
+import 'initializer.dart';
+import 'utils.dart';
+
+class UserProperty {
+ final String? flutterChannel;
+ final String host;
+ final String? flutterVersion;
+ final String dartVersion;
+ final String tool;
+ final String hostOsVersion;
+ final String locale;
+ final String? clientIde;
+ final String? enabledFeatures;
+
+ final File sessionFile;
+
+ /// Contains instances of [Event.analyticsException] that were encountered
+ /// during a workflow and will be sent to GA4 for collection.
+ final Set<Event> errorSet = {};
+
+ int? _sessionId;
+
+ /// This class is intended to capture all of the user's
+ /// metadata when the class gets initialized as well as collecting
+ /// session data to send in the json payload to Google Analytics.
+ UserProperty({
+ required this.flutterChannel,
+ required this.host,
+ required this.flutterVersion,
+ required this.dartVersion,
+ required this.tool,
+ required this.hostOsVersion,
+ required this.locale,
+ required this.clientIde,
+ required this.enabledFeatures,
+ required this.sessionFile,
+ });
+
+ /// This will use the data parsed from the
+ /// session file in the dart-tool directory
+ /// to get the session id if the last ping was within
+ /// [kSessionDurationMinutes].
+ ///
+ /// If time since last ping exceeds the duration, then the file
+ /// will be updated with a new session id and that will be returned.
+ ///
+ /// Note, the file will always be updated when calling this method
+ /// because the last ping variable will always need to be persisted.
+ int? getSessionId() {
+ _refreshSessionData();
+ final now = clock.now();
+
+ // Convert the epoch time from the last ping into datetime and check if we
+ // are within the kSessionDurationMinutes.
+ final lastPingDateTime = sessionFile.lastModifiedSync();
+ if (now.difference(lastPingDateTime).inMinutes > kSessionDurationMinutes) {
+ // Update the session file with the latest session id
+ _sessionId = now.millisecondsSinceEpoch;
+ writeSessionContents(sessionFile: sessionFile);
+ } else {
+ // Update the last modified timestamp with the current timestamp so that
+ // we can use it for the next _lastPing calculation
+ sessionFile.setLastModifiedSync(now);
+ }
+
+ return _sessionId;
+ }
+
+ /// This method will take the data in this class and convert it into
+ /// a Map that is suitable for the POST request schema.
+ ///
+ /// This will call the [UserProperty] object's [UserProperty.getSessionId]
+ /// method which will update the session file and get a new session id
+ /// if necessary.
+ ///
+ /// https://developers.google.com/analytics/devguides/collection/protocol/ga4/user-properties?client_type=gtag
+ Map<String, Map<String, Object?>> preparePayload() {
+ return <String, Map<String, Object?>>{
+ for (final entry in _toMap().entries)
+ entry.key: <String, Object?>{'value': entry.value}
+ };
+ }
+
+ @override
+ String toString() {
+ return jsonEncode(_toMap());
+ }
+
+ /// This will go to the session file within the dart-tool
+ /// directory and fetch the latest data from the session file to update
+ /// the class's variables. If the session file is malformed, a new
+ /// session file will be recreated.
+ ///
+ /// This allows the session data in this class to always be up
+ /// to date incase another tool is also calling this package and
+ /// making updates to the session file.
+ void _refreshSessionData() {
+ /// Using a nested function here to reduce verbosity
+ void parseContents() {
+ final sessionFileContents = sessionFile.readAsStringSync();
+ final sessionObj =
+ jsonDecode(sessionFileContents) as Map<String, Object?>;
+ _sessionId = sessionObj['session_id'] as int;
+ }
+
+ try {
+ // Failing to parse the contents will result in the current timestamp
+ // being used as the session id and will get used to recreate the file
+ parseContents();
+ } on FormatException catch (err) {
+ final now = createSessionFile(sessionFile: sessionFile);
+
+ errorSet.add(Event.analyticsException(
+ workflow: 'UserProperty._refreshSessionData',
+ error: err.runtimeType.toString(),
+ description: 'message: ${err.message}\nsource: ${err.source}',
+ ));
+
+ // Fallback to setting the session id as the current time
+ _sessionId = now.millisecondsSinceEpoch;
+ } on FileSystemException catch (err) {
+ final now = createSessionFile(sessionFile: sessionFile);
+
+ errorSet.add(Event.analyticsException(
+ workflow: 'UserProperty._refreshSessionData',
+ error: err.runtimeType.toString(),
+ description: err.osError?.toString(),
+ ));
+
+ // Fallback to setting the session id as the current time
+ _sessionId = now.millisecondsSinceEpoch;
+ }
+ }
+
+ /// Convert the data stored in this class into a map while also
+ /// getting the latest session id using the [UserProperty] class.
+ Map<String, Object?> _toMap() => <String, Object?>{
+ 'session_id': getSessionId(),
+ 'flutter_channel': flutterChannel,
+ 'host': host,
+ 'flutter_version': flutterVersion,
+ 'dart_version': dartVersion,
+ 'analytics_pkg_version': kPackageVersion,
+ 'tool': tool,
+ 'local_time': formatDateTime(clock.now()),
+ 'host_os_version': hostOsVersion,
+ 'locale': locale,
+ 'client_ide': clientIde,
+ 'enabled_features': enabledFeatures,
+ };
+}
diff --git a/pkgs/unified_analytics/lib/src/utils.dart b/pkgs/unified_analytics/lib/src/utils.dart
new file mode 100644
index 0000000..41848a2
--- /dev/null
+++ b/pkgs/unified_analytics/lib/src/utils.dart
@@ -0,0 +1,340 @@
+// Copyright (c) 2023, 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' as io;
+import 'dart:math' show Random;
+
+import 'package:clock/clock.dart';
+import 'package:convert/convert.dart';
+import 'package:file/file.dart';
+
+import 'enums.dart';
+import 'event.dart';
+import 'survey_handler.dart';
+import 'user_property.dart';
+
+/// Get a string representation of the current date in the following format:
+/// ```text
+/// yyyy-MM-dd (2023-01-09)
+/// ```
+String get dateStamp {
+ return FixedDateTimeFormatter('YYYY-MM-DD').encode(clock.now());
+}
+
+/// Reads in a directory and returns `true` if write permissions are enabled.
+///
+/// Uses the [FileStat] method `modeString()` to return a string in the form
+/// of `rwxrwxrwx` where the second character in the string indicates if write
+/// is enabled with a `w` or disabled with `-`.
+bool checkDirectoryForWritePermissions(Directory directory) {
+ if (!directory.existsSync()) return false;
+
+ final fileStat = directory.statSync();
+ return fileStat.modeString()[1] == 'w';
+}
+
+/// Format time as 'yyyy-MM-dd HH:mm:ss Z' where Z is the difference between the
+/// timezone of t and UTC formatted according to RFC 822.
+String formatDateTime(DateTime t) {
+ final sign = t.timeZoneOffset.isNegative ? '-' : '+';
+ final tzOffset = t.timeZoneOffset.abs();
+ final hoursOffset = tzOffset.inHours;
+ final minutesOffset =
+ tzOffset.inMinutes - (Duration.minutesPerHour * hoursOffset);
+ assert(hoursOffset < 24);
+ assert(minutesOffset < 60);
+
+ String twoDigits(int n) => (n >= 10) ? '$n' : '0$n';
+ return '$t $sign${twoDigits(hoursOffset)}${twoDigits(minutesOffset)}';
+}
+
+/// Construct the Map that will be converted to json for the
+/// body of the request.
+///
+/// Follows the following schema:
+///
+/// ```
+/// {
+/// "client_id": "46cc0ba6-f604-4fd9-aa2f-8a20beb24cd4",
+/// "events": [{ "name": "testing", "params": { "time_ns": 345 } }],
+/// "user_properties": {
+/// "session_id": { "value": 1673466750423 },
+/// "flutter_channel": { "value": "ey-test-channel" },
+/// "host": { "value": "macos" },
+/// "flutter_version": { "value": "Flutter 3.6.0-7.0.pre.47" },
+/// "dart_version": { "value": "Dart 2.19.0" },
+/// "tool": { "value": "flutter-tools" },
+/// "local_time": { "value": "2023-01-11 14:53:31.471816 -0500" }
+/// }
+/// }
+/// ```
+/// https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events?client_type=gtag
+Map<String, Object?> generateRequestBody({
+ required String clientId,
+ required DashEvent eventName,
+ required Map<String, Object?> eventData,
+ required UserProperty userProperty,
+}) =>
+ <String, Object?>{
+ 'client_id': clientId,
+ 'events': <Map<String, Object?>>[
+ <String, Object?>{
+ 'name': eventName.label,
+ 'params': eventData,
+ }
+ ],
+ 'user_properties': userProperty.preparePayload()
+ };
+
+/// This will use environment variables to get the user's
+/// home directory where all the directory will be created that will
+/// contain all of the analytics files.
+Directory? getHomeDirectory(FileSystem fs) {
+ String? home;
+ final envVars = io.Platform.environment;
+
+ if (io.Platform.isMacOS) {
+ home = envVars['HOME'];
+ } else if (io.Platform.isLinux) {
+ home = envVars['HOME'];
+ } else if (io.Platform.isWindows) {
+ home = envVars['AppData'];
+ }
+
+ if (home == null) return null;
+
+ return fs.directory(home);
+}
+
+/// Returns `true` if user has opted out of legacy analytics in
+/// Dart or Flutter.
+///
+/// Checks legacy opt-out status for the Flutter
+/// and Dart in the following locations.
+///
+/// Dart: `$HOME/.dart/dartdev.json`
+/// ```
+/// {
+/// "firstRun": false,
+/// "enabled": false, <-- THIS USER HAS OPTED OUT
+/// "disclosureShown": true,
+/// "clientId": "52710e60-7c70-4335-b3a4-9d922630f12a"
+/// }
+/// ```
+///
+/// Flutter: `$HOME/.flutter`
+/// ```
+/// {
+/// "firstRun": false,
+/// "clientId": "4c3a3d1e-e545-47e7-b4f8-10129f6ab169",
+/// "enabled": false <-- THIS USER HAS OPTED OUT
+/// }
+/// ```
+///
+/// Devtools: `$HOME/.flutter-devtools/.devtools`
+/// ```
+/// {
+/// "analyticsEnabled": false, <-- THIS USER HAS OPTED OUT
+/// "isFirstRun": false,
+/// "lastReleaseNotesVersion": "2.31.0",
+/// "2023-Q4": {
+/// "surveyActionTaken": false,
+/// "surveyShownCount": 0
+/// }
+/// }
+/// ```
+bool legacyOptOut({required Directory homeDirectory}) {
+ // List of Maps for each of the config file, `key` refers to the
+ // key in the json file that indicates if the user has been opted
+ // out or not
+ final legacyConfigFiles = [
+ (
+ tool: DashTool.dartTool,
+ file: homeDirectory.childDirectory('.dart').childFile('dartdev.json'),
+ key: 'enabled',
+ ),
+ (
+ tool: DashTool.flutterTool,
+ file: homeDirectory.childFile('.flutter'),
+ key: 'enabled',
+ ),
+ (
+ tool: DashTool.devtools,
+ file: homeDirectory
+ .childDirectory('.flutter-devtools')
+ .childFile('.devtools'),
+ key: 'analyticsEnabled',
+ ),
+ ];
+ for (final legacyConfigObj in legacyConfigFiles) {
+ final legacyFile = legacyConfigObj.file;
+ final lookupKey = legacyConfigObj.key;
+
+ if (legacyFile.existsSync()) {
+ try {
+ final legacyFileObj =
+ jsonDecode(legacyFile.readAsStringSync()) as Map<String, Object?>;
+ if (legacyFileObj.containsKey(lookupKey) &&
+ legacyFileObj[lookupKey] == false) {
+ return true;
+ }
+ } on FormatException {
+ // In the case of an error when parsing the json file, return true
+ // which will result in the user being opted out of unified_analytics
+ //
+ // A corrupted file could mean they opted out previously but for some
+ // reason, the file was written incorrectly
+ return true;
+ } on FileSystemException {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+/// Helper method that can be used to resolve the Dart SDK version for clients
+/// of package:unified_analytics.
+///
+/// Input [versionString] for this method should be the returned string from
+/// [io.Platform.version].
+///
+/// For tools that don't already have a method for resolving the Dart
+/// SDK version in semver notation, this helper can be used. This uses
+/// the [io.Platform.version] to parse the semver.
+///
+/// Example for stable version:
+/// `3.3.0 (stable) (Tue Feb 13 10:25:19 2024 +0000) on "macos_arm64"` into
+/// `3.3.0`.
+///
+/// Example for non-stable version:
+/// `2.1.0-dev.8.0.flutter-312ae32` into `2.1.0 (build 2.1.0-dev.8.0 312ae32)`.
+String parseDartSDKVersion(String versionString) {
+ versionString = versionString.trim();
+ final justVersion = versionString.split(' ')[0];
+
+ // For non-stable versions, this regex will include build information
+ return justVersion.replaceFirstMapped(RegExp(r'(\d+\.\d+\.\d+)(.+)'),
+ (Match match) {
+ final noFlutter = match[2]!.replaceAll('.flutter-', ' ');
+ return '${match[1]} (build ${match[1]}$noFlutter)'.trim();
+ });
+}
+
+/// Will use two strings to produce a double for applying a sampling
+/// rate for [Survey] to be returned to the user.
+double sampleRate(String string1, String string2) =>
+ ((string1.hashCode + string2.hashCode) % 101) / 100;
+
+/// Function to check if a given [Survey] can be shown again
+/// by checking if it was snoozed or permanently dismissed.
+///
+/// If the [Survey] doesn't exist in the persisted file, then it
+/// will be shown to the user.
+///
+/// If the [Survey] has been permanently dismissed, we will not
+/// show it to the user.
+///
+/// If the [Survey] has been snoozed, we will check the timestamp
+/// that it was snoozed at with the current time from [clock]
+/// and if the snooze period has elapsed, then we will show it to the user.
+bool surveySnoozedOrDismissed(
+ Survey survey,
+ Map<String, PersistedSurvey> persistedSurveyMap,
+) {
+ // If this survey hasn't been persisted yet, it is okay to pass
+ // to the user
+ if (!persistedSurveyMap.containsKey(survey.uniqueId)) return false;
+
+ final persistedSurveyObj = persistedSurveyMap[survey.uniqueId]!;
+
+ // If the survey has been dismissed permanently, we will not show the
+ // survey
+ if (!persistedSurveyObj.snoozed) return true;
+
+ // Find how many minutes has elapsed from the timestamp and now
+ final minutesElapsed =
+ clock.now().difference(persistedSurveyObj.timestamp).inMinutes;
+
+ return survey.snoozeForMinutes > minutesElapsed;
+}
+
+/// Due to some limitations for GA4, this function can be used to
+/// truncate fields that we may not care about truncating, such as
+/// the host os details.
+///
+/// [maxLength] represents the maximum length allowed for the string.
+///
+/// Example:
+/// "Linux 6.2.0-1015-azure #15~22.04.1-Ubuntu SMP Fri Oct 6 13:20:44 UTC 2023"
+///
+/// The above string is what is returned by [io.Platform.operatingSystemVersion]
+/// for certain machines running GitHub Actions, this function will truncate
+/// that value down to the maximum length at 36 characters and return the below
+///
+/// Return:
+/// "Linux 6.2.0-1015-azure #15~22."
+///
+/// This should only be used on fields that are okay to be truncated, this
+/// should not be used for parameters on the [Event] constructors.
+String truncateStringToLength(String str, int maxLength) {
+ if (maxLength <= 0) {
+ throw ArgumentError(
+ 'The length to truncate a string must be greater than 0');
+ }
+
+ if (maxLength > str.length) return str;
+
+ return str.substring(0, maxLength);
+}
+
+/// Writes the JSON string payload to the provided [sessionFile].
+///
+/// The `last_ping` key:value pair has been deprecated, it remains included
+/// for backward compatibility.
+void writeSessionContents({required File sessionFile}) {
+ final now = clock.now();
+ sessionFile.writeAsStringSync('{"session_id": ${now.millisecondsSinceEpoch}, '
+ '"last_ping": ${now.millisecondsSinceEpoch}}');
+}
+
+/// A UUID generator.
+///
+/// This will generate unique IDs in the format:
+///
+/// f47ac10b-58cc-4372-a567-0e02b2c3d479
+///
+/// The generated uuids are 128 bit numbers encoded in a specific string format.
+/// For more information, see
+/// [en.wikipedia.org/wiki/Universally_unique_identifier](http://en.wikipedia.org/wiki/Universally_unique_identifier).
+///
+/// This class was taken from the previous `usage` package (https://github.com/dart-lang/usage/blob/master/lib/uuid/uuid.dart).
+class Uuid {
+ final Random _random;
+
+ Uuid([int? seed]) : _random = Random(seed);
+
+ /// Generate a version 4 (random) uuid. This is a uuid scheme that only uses
+ /// random numbers as the source of the generated uuid.
+ String generateV4() {
+ // Generate xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx / 8-4-4-4-12.
+ final special = 8 + _random.nextInt(4);
+
+ return '${_bitsDigits(16, 4)}${_bitsDigits(16, 4)}-'
+ '${_bitsDigits(16, 4)}-'
+ '4${_bitsDigits(12, 3)}-'
+ '${_printDigits(special, 1)}${_bitsDigits(12, 3)}-'
+ '${_bitsDigits(16, 4)}${_bitsDigits(16, 4)}${_bitsDigits(16, 4)}';
+ }
+
+ String _bitsDigits(int bitCount, int digitCount) =>
+ _printDigits(_generateBits(bitCount), digitCount);
+
+ int _generateBits(int bitCount) => _random.nextInt(1 << bitCount);
+
+ String _printDigits(int value, int count) =>
+ value.toRadixString(16).padLeft(count, '0');
+}
diff --git a/pkgs/unified_analytics/lib/testing.dart b/pkgs/unified_analytics/lib/testing.dart
new file mode 100644
index 0000000..900212f
--- /dev/null
+++ b/pkgs/unified_analytics/lib/testing.dart
@@ -0,0 +1,5 @@
+// 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.
+
+export 'src/enums.dart';
diff --git a/pkgs/unified_analytics/lib/unified_analytics.dart b/pkgs/unified_analytics/lib/unified_analytics.dart
new file mode 100644
index 0000000..bd8e17a
--- /dev/null
+++ b/pkgs/unified_analytics/lib/unified_analytics.dart
@@ -0,0 +1,11 @@
+// Copyright (c) 2023, 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/analytics.dart' show Analytics, FakeAnalytics, NoOpAnalytics;
+export 'src/config_handler.dart' show ToolInfo;
+export 'src/enums.dart' show DashTool;
+export 'src/event.dart' show CustomMetrics, Event;
+export 'src/log_handler.dart' show LogFileStats;
+export 'src/survey_handler.dart' show Survey, SurveyButton, SurveyHandler;
+export 'src/utils.dart' show parseDartSDKVersion;
diff --git a/pkgs/unified_analytics/pubspec.yaml b/pkgs/unified_analytics/pubspec.yaml
new file mode 100644
index 0000000..f0a99f1
--- /dev/null
+++ b/pkgs/unified_analytics/pubspec.yaml
@@ -0,0 +1,26 @@
+name: unified_analytics
+description: >-
+ A package for logging analytics for all Dart and Flutter related tooling
+ to Google Analytics.
+# LINT.IfChange
+# When updating this, keep the version consistent with the changelog and the
+# value in lib/src/constants.dart.
+version: 7.0.1
+# 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
+
+environment:
+ sdk: ^3.4.0
+
+dependencies:
+ clock: ^1.1.1
+ convert: ^3.1.1
+ file: '>=6.1.4 <8.0.0'
+ http: '>=0.13.5 <2.0.0'
+ meta: ^1.9.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ test: ^1.16.6
+ yaml: ^3.1.1
diff --git a/pkgs/unified_analytics/test/asserts_test.dart b/pkgs/unified_analytics/test/asserts_test.dart
new file mode 100644
index 0000000..9230fc6
--- /dev/null
+++ b/pkgs/unified_analytics/test/asserts_test.dart
@@ -0,0 +1,413 @@
+// Copyright (c) 2023, 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 'package:unified_analytics/src/asserts.dart';
+
+void main() {
+ test('Failure if client_id top level key is missing', () {
+ final body = <String, Object?>{};
+
+ final expectedErrorMessage = 'client_id missing from top level keys';
+ expect(
+ () => checkBody(body),
+ throwsA(predicate(
+ (AnalyticsException e) => e.message == expectedErrorMessage,
+ expectedErrorMessage)));
+ });
+
+ test('Failure if events top level key is missing', () {
+ final body = <String, Object?>{'client_id': 'xxxxxxx'};
+
+ final expectedErrorMessage = 'events missing from top level keys';
+ expect(
+ () => checkBody(body),
+ throwsA(predicate(
+ (AnalyticsException e) => e.message == expectedErrorMessage,
+ expectedErrorMessage)));
+ });
+
+ test('Failure if user_properties top level key is missing', () {
+ final body = <String, Object?>{'client_id': 'xxxxxxx', 'events': []};
+
+ final expectedErrorMessage = 'user_properties missing from top level keys';
+ expect(
+ () => checkBody(body),
+ throwsA(predicate(
+ (AnalyticsException e) => e.message == expectedErrorMessage,
+ expectedErrorMessage)));
+ });
+
+ test('Failure if more than 25 events found in events list', () {
+ final body = <String, Object?>{
+ 'client_id': 'xxxxxxx',
+ 'events': <Map<String, Object?>>[],
+ 'user_properties': <String, Object?>{}
+ };
+
+ // Add more than the 25 allowed events
+ for (var i = 0; i < 30; i++) {
+ (body['events'] as List).add({'name': i});
+ }
+
+ final expectedErrorMessage = '25 is the max number of events';
+ expect(
+ () => checkBody(body),
+ throwsA(predicate(
+ (AnalyticsException e) => e.message == expectedErrorMessage,
+ expectedErrorMessage)));
+ });
+
+ test('Failure when event name is greater than 40 chars', () {
+ final body = <String, Object?>{
+ 'client_id': 'xxxxxxx',
+ 'events': <Map<String, Object?>>[
+ {
+ 'name':
+ 'hot_reload_timehot_reload_timehot_reload_timehot_reload_time',
+ 'params': {'time_ms': 133, 'count': 1999000}
+ }
+ ],
+ 'user_properties': <String, Object?>{}
+ };
+
+ final expectedErrorMessage = 'Limit event names to 40 chars or less\n'
+ 'Event name: '
+ '"hot_reload_timehot_reload_timehot_reload_timehot_reload_time"'
+ ' is too long';
+ expect(
+ () => checkBody(body),
+ throwsA(predicate(
+ (AnalyticsException e) => e.message == expectedErrorMessage,
+ expectedErrorMessage)));
+ });
+
+ test('Failure when event name has invalid chars', () {
+ final body = <String, Object?>{
+ 'client_id': 'xxxxxxx',
+ 'events': <Map<String, Object?>>[
+ {
+ 'name': 'hot_reload_time!!',
+ 'params': {'time_ms': 133, 'count': 1999000}
+ }
+ ],
+ 'user_properties': <String, Object?>{}
+ };
+
+ final expectedErrorMessage =
+ 'Event name can only have alphanumeric chars and underscores\n'
+ 'Event name: "hot_reload_time!!" contains invalid characters';
+ expect(
+ () => checkBody(body),
+ throwsA(predicate(
+ (AnalyticsException e) => e.message == expectedErrorMessage,
+ expectedErrorMessage)));
+ });
+
+ test('Failure when event name does not start with alphabetic char', () {
+ final body = <String, Object?>{
+ 'client_id': 'xxxxxxx',
+ 'events': <Map<String, Object?>>[
+ {
+ 'name': '2hot_reload_time',
+ 'params': {'time_ms': 133, 'count': 1999000}
+ }
+ ],
+ 'user_properties': <String, Object?>{}
+ };
+
+ final expectedErrorMessage =
+ 'Event name first char must be alphabetic char\n'
+ 'Event name: "2hot_reload_time" must begin with a valid character';
+ expect(
+ () => checkBody(body),
+ throwsA(predicate(
+ (AnalyticsException e) => e.message == expectedErrorMessage,
+ expectedErrorMessage)));
+ });
+
+ test('Failure when an event has more than 25 event params', () {
+ final body = <String, Object?>{
+ 'client_id': 'xxxxxxx',
+ 'events': <Map<String, Object?>>[
+ {
+ 'name': 'hot_reload_time',
+ // 'params': {...} SUBBING THIS VALUE OUT 30 Maps
+ }
+ ],
+ 'user_properties': <String, Object?>{}
+ };
+
+ final params = <String, Object?>{};
+ for (var i = 0; i < 30; i++) {
+ params['$i'] = i;
+ }
+
+ // Add the params to the first event in the body
+ ((body['events'] as List).first as Map)['params'] = params;
+
+ final expectedErrorMessage = 'Limit params for each event to less than 25\n'
+ 'Event: "hot_reload_time" has too many parameters';
+ expect(
+ () => checkBody(body),
+ throwsA(predicate(
+ (AnalyticsException e) => e.message == expectedErrorMessage,
+ expectedErrorMessage)));
+ });
+
+ test('Failure when an value for event params is not a supported type (list)',
+ () {
+ final body = <String, Object?>{
+ 'client_id': 'xxxxxxx',
+ 'events': <Map<String, Object?>>[
+ {
+ 'name': 'hot_reload_time',
+ 'params': {
+ 'time_ms': 133,
+ // Lists and Maps are not supported
+ 'count': <int>[1999000],
+ }
+ }
+ ],
+ 'user_properties': <String, Object?>{}
+ };
+
+ final expectedErrorMessage =
+ 'Values for event params have to be String, int, double, or bool\n'
+ 'Value for "count" is not a valid type for event: "hot_reload_time"';
+ expect(
+ () => checkBody(body),
+ throwsA(predicate(
+ (AnalyticsException e) => e.message == expectedErrorMessage,
+ expectedErrorMessage)));
+ });
+
+ test('Failure when an value for event params is not a supported type (map)',
+ () {
+ final body = <String, Object?>{
+ 'client_id': 'xxxxxxx',
+ 'events': <Map<String, Object?>>[
+ {
+ 'name': 'hot_reload_time',
+ 'params': {
+ 'time_ms': 133,
+ // Lists and Maps are not supported
+ 'count': <int, int>{5: 20},
+ }
+ }
+ ],
+ 'user_properties': <String, Object?>{}
+ };
+
+ final expectedErrorMessage =
+ 'Values for event params have to be String, int, double, or bool\n'
+ 'Value for "count" is not a valid type for event: "hot_reload_time"';
+ expect(
+ () => checkBody(body),
+ throwsA(predicate(
+ (AnalyticsException e) => e.message == expectedErrorMessage,
+ expectedErrorMessage)));
+ });
+
+ test('Failure when event param name is more than 40 chars', () {
+ final body = <String, Object?>{
+ 'client_id': 'xxxxxxx',
+ 'events': <Map<String, Object?>>[
+ {
+ 'name': 'hot_reload_time',
+ 'params': {'time_mstime_mstime_mstime_mstime_mstime_ms': 133}
+ }
+ ],
+ 'user_properties': <String, Object?>{}
+ };
+
+ final expectedErrorMessage = 'Limit event param names to 40 chars or less\n'
+ 'The key: "time_mstime_mstime_mstime_mstime_mstime_ms" '
+ 'under the event: "hot_reload_time" is too long';
+ expect(
+ () => checkBody(body),
+ throwsA(predicate(
+ (AnalyticsException e) => e.message == expectedErrorMessage,
+ expectedErrorMessage)));
+ });
+
+ test('Failure for event param name that has invalid chars', () {
+ final body = <String, Object?>{
+ 'client_id': 'xxxxxxx',
+ 'events': <Map<String, Object?>>[
+ {
+ 'name': 'hot_reload_time',
+ 'params': {'time_ns!': 133}
+ }
+ ],
+ 'user_properties': <String, Object?>{}
+ };
+
+ final expectedErrorMessage =
+ 'Event param name can only have alphanumeric chars and underscores\n'
+ 'The key: "time_ns!" under the event: "hot_reload_time" contains '
+ 'invalid characters';
+
+ expect(
+ () => checkBody(body),
+ throwsA(predicate(
+ (AnalyticsException e) => e.message == expectedErrorMessage,
+ expectedErrorMessage)));
+ });
+
+ test(
+ 'Failure for event param name that does not start with an alphabetic char',
+ () {
+ final body = <String, Object?>{
+ 'client_id': 'xxxxxxx',
+ 'events': <Map<String, Object?>>[
+ {
+ 'name': 'hot_reload_time',
+ 'params': {'22time_ns': 133}
+ }
+ ],
+ 'user_properties': <String, Object?>{}
+ };
+
+ final expectedErrorMessage =
+ 'Event param name first char must be alphabetic char\n'
+ 'The key: "22time_ns" under the event: "hot_reload_time" must begin '
+ 'in a valid character';
+ expect(
+ () => checkBody(body),
+ throwsA(predicate(
+ (AnalyticsException e) => e.message == expectedErrorMessage,
+ expectedErrorMessage)));
+ },
+ );
+
+ test('Failure for event param values that are greater than 100 chars', () {
+ final body = <String, Object?>{
+ 'client_id': 'xxxxxxx',
+ 'events': <Map<String, Object?>>[
+ {
+ 'name': 'hot_reload_time',
+ 'params': {
+ 'time_ns': 'dsfjlksdjfajlfdsfjlks'
+ 'djfajlfdsfjlksdjfajlfdsfjlksdjfaj'
+ 'lfdsfjlksdjfajlfdsfjlksdjfajlfdsf'
+ 'jlksdjfajlfdsfjlksdjfajlf'
+ }
+ }
+ ],
+ 'user_properties': <String, Object?>{}
+ };
+
+ final expectedErrorMessage =
+ 'Limit characters in event param value to 100 chars or less\n'
+ 'Value for "time_ns" is too long, value="'
+ 'dsfjlksdjfajlfdsfjlks'
+ 'djfajlfdsfjlksdjfajlfdsfjlksdjfaj'
+ 'lfdsfjlksdjfajlfdsfjlksdjfajlfdsf'
+ 'jlksdjfajlfdsfjlksdjfajlf"';
+ expect(
+ () => checkBody(body),
+ throwsA(predicate(
+ (AnalyticsException e) => e.message == expectedErrorMessage,
+ expectedErrorMessage)));
+ });
+
+ test('Failure when body has more than 25 user properties', () {
+ final body = <String, Object?>{
+ 'client_id': 'xxxxxxx',
+ 'events': <Map<String, Object?>>[
+ {
+ 'name': 'hot_reload_time',
+ 'params': {'time_ns': 123}
+ }
+ ],
+ 'user_properties': <String, Object?>{}
+ };
+
+ for (var i = 0; i < 30; i++) {
+ (body['user_properties']! as Map<String, Object?>)['$i'] = i;
+ }
+
+ final expectedErrorMessage = 'Limit user properties to 25 or less';
+ expect(
+ () => checkBody(body),
+ throwsA(predicate(
+ (AnalyticsException e) => e.message == expectedErrorMessage,
+ expectedErrorMessage)));
+ });
+
+ test('Failure when user properties names are greater than 24 chars', () {
+ final body = <String, Object?>{
+ 'client_id': 'xxxxxxx',
+ 'events': <Map<String, Object?>>[
+ {
+ 'name': 'hot_reload_time',
+ 'params': {'time_ns': 123}
+ }
+ ],
+ 'user_properties': <String, Object?>{
+ 'testtesttesttesttesttesttest': <String, Object?>{}, // TOO LONG
+ }
+ };
+
+ final expectedErrorMessage =
+ 'Limit user property names to 24 chars or less\n'
+ 'The user property key: "testtesttesttesttesttesttest" is too long';
+ expect(
+ () => checkBody(body),
+ throwsA(predicate(
+ (AnalyticsException e) => e.message == expectedErrorMessage,
+ expectedErrorMessage)));
+ });
+
+ test('Failure when user properties values are greater than 36 chars', () {
+ final body = <String, Object?>{
+ 'client_id': 'xxxxxxx',
+ 'events': <Map<String, Object?>>[
+ {
+ 'name': 'hot_reload_time',
+ 'params': {'time_ns': 123}
+ }
+ ],
+ 'user_properties': <String, Object?>{
+ 'test': <String, Object?>{
+ 'value': 'testtesttesttesttesttesttesttesttesttest' // TOO LONG
+ },
+ }
+ };
+
+ final expectedErrorMessage =
+ 'Limit user property values to 36 chars or less\n'
+ 'For the user property key "test", the value '
+ '"testtesttesttesttesttesttesttesttesttest" is too long';
+ expect(
+ () => checkBody(body),
+ throwsA(predicate(
+ (AnalyticsException e) => e.message == expectedErrorMessage,
+ expectedErrorMessage)));
+ });
+
+ test('Successful body passes all asserts', () {
+ final body = <String, Object?>{
+ 'client_id': '46cc0ba6-f604-4fd9-aa2f-8a20beb24cd4',
+ 'events': [
+ {
+ 'name': 'testing',
+ 'params': {'time_ns': 345}
+ }
+ ],
+ 'user_properties': {
+ 'session_id': {'value': 1673466750423},
+ 'flutter_channel': {'value': 'ey-test-channel'},
+ 'host': {'value': 'macos'},
+ 'flutter_version': {'value': 'Flutter 3.6.0-7.0.pre.47'},
+ 'dart_version': {'value': 'Dart 2.19.0'},
+ 'tool': {'value': 'flutter-tools'},
+ 'local_time': {'value': '2023-01-11 14:53:31.471816 -0500'}
+ }
+ };
+
+ expect(() => checkBody(body), returnsNormally);
+ });
+}
diff --git a/pkgs/unified_analytics/test/error_handler_test.dart b/pkgs/unified_analytics/test/error_handler_test.dart
new file mode 100644
index 0000000..39b2df8
--- /dev/null
+++ b/pkgs/unified_analytics/test/error_handler_test.dart
@@ -0,0 +1,346 @@
+// Copyright (c) 2023, 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/memory.dart';
+import 'package:test/test.dart';
+
+import 'package:unified_analytics/src/constants.dart';
+import 'package:unified_analytics/src/enums.dart';
+import 'package:unified_analytics/unified_analytics.dart';
+
+void main() {
+ late MemoryFileSystem fs;
+ late Directory home;
+ late FakeAnalytics initializationAnalytics;
+ late FakeAnalytics analytics;
+ late File sessionFile;
+ late File logFile;
+
+ const homeDirName = 'home';
+ const initialTool = DashTool.flutterTool;
+ const toolsMessageVersion = 1;
+ const toolsMessage = 'toolsMessage';
+ const flutterChannel = 'flutterChannel';
+ const flutterVersion = 'flutterVersion';
+ const dartVersion = 'dartVersion';
+ const platform = DevicePlatform.macos;
+ const clientIde = 'VSCode';
+
+ final testEvent = Event.codeSizeAnalysis(platform: 'platform');
+
+ setUp(() {
+ // Setup the filesystem with the home directory
+ final fsStyle =
+ io.Platform.isWindows ? FileSystemStyle.windows : FileSystemStyle.posix;
+ fs = MemoryFileSystem.test(style: fsStyle);
+ home = fs.directory(homeDirName);
+
+ // This is the first analytics instance that will be used to demonstrate
+ // that events will not be sent with the first run of analytics
+ initializationAnalytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ );
+ expect(initializationAnalytics.shouldShowMessage, true);
+ initializationAnalytics.clientShowedMessage();
+ expect(initializationAnalytics.shouldShowMessage, false);
+
+ // The main analytics instance, other instances can be spawned within tests
+ // to test how to instances running together work
+ //
+ // This instance should have the same parameters as the one above for
+ // [initializationAnalytics]
+ analytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ clientIde: clientIde,
+ );
+ analytics.clientShowedMessage();
+
+ // The files that should have been generated that will be used for tests
+ sessionFile =
+ home.childDirectory(kDartToolDirectoryName).childFile(kSessionFileName);
+ logFile =
+ home.childDirectory(kDartToolDirectoryName).childFile(kLogFileName);
+ });
+
+ group('Session handler:', () {
+ test('no error when opted out already and opting in', () async {
+ // When we opt out from an analytics instance, we clear the contents of
+ // session file, as required by the privacy document. When creating a
+ // second instance of [Analytics], it should not detect that the file is
+ // empty and recreate it, it should remain opted out and no error event
+ // should have been sent
+ await analytics.setTelemetry(false);
+ expect(analytics.telemetryEnabled, false);
+ expect(sessionFile.readAsStringSync(), isEmpty);
+
+ final secondAnalytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ clientIde: clientIde,
+ );
+ expect(sessionFile.readAsStringSync(), isEmpty);
+ expect(secondAnalytics.telemetryEnabled, false);
+
+ expect(
+ analytics.sentEvents.where(
+ (element) => element.eventName == DashEvent.analyticsException),
+ isEmpty,
+ );
+ expect(
+ secondAnalytics.sentEvents.where(
+ (element) => element.eventName == DashEvent.analyticsException),
+ isEmpty,
+ );
+
+ await secondAnalytics.setTelemetry(true);
+ expect(sessionFile.readAsStringSync(), isNotEmpty,
+ reason: 'Toggling telemetry should bring back the session data');
+ });
+ test('only sends one event for FormatException', () {
+ // Begin with the session file empty, it should recreate the file
+ // and send an error event
+ sessionFile.writeAsStringSync('');
+ expect(sessionFile.readAsStringSync(), isEmpty);
+ analytics.send(testEvent);
+ expect(sessionFile.readAsStringSync(), isNotEmpty);
+
+ analytics.sendPendingErrorEvents();
+ expect(
+ analytics.sentEvents.where(
+ (element) => element.eventName == DashEvent.analyticsException),
+ hasLength(1));
+
+ // Making the file empty again and sending an event should not send
+ // an additional event
+ sessionFile.writeAsStringSync('');
+ expect(sessionFile.readAsStringSync(), isEmpty);
+ analytics.send(testEvent);
+ expect(sessionFile.readAsStringSync(), isNotEmpty);
+
+ expect(
+ analytics.sentEvents.where(
+ (element) => element.eventName == DashEvent.analyticsException),
+ hasLength(1),
+ reason: 'We should not have added a new error event');
+ });
+
+ test('only sends one event for FileSystemException', () {
+ // Deleting the session file should cause the file system exception and
+ // sending a new event should log the error the first time and recreate
+ // the file. If we delete the file again and attempt to send an event,
+ // the session file should get recreated without sending a second error.
+ sessionFile.deleteSync();
+ expect(sessionFile.existsSync(), isFalse);
+ analytics.send(testEvent);
+
+ analytics.sendPendingErrorEvents();
+ expect(
+ analytics.sentEvents.where(
+ (element) => element.eventName == DashEvent.analyticsException),
+ hasLength(1),
+ );
+ expect(sessionFile.existsSync(), isTrue);
+ expect(sessionFile.readAsStringSync(), isNotEmpty);
+
+ // Remove the file again and send an event
+ sessionFile.deleteSync();
+ expect(sessionFile.existsSync(), isFalse);
+ analytics.send(testEvent);
+
+ expect(
+ analytics.sentEvents.where(
+ (element) => element.eventName == DashEvent.analyticsException),
+ hasLength(1),
+ reason: 'Only the first error event should exist',
+ );
+ expect(sessionFile.existsSync(), isTrue);
+ expect(sessionFile.readAsStringSync(), isNotEmpty);
+ });
+
+ test('sends two unique errors', () {
+ // Begin with the session file empty, it should recreate the file
+ // and send an error event
+ sessionFile.writeAsStringSync('');
+ expect(sessionFile.readAsStringSync(), isEmpty);
+ analytics.send(testEvent);
+ expect(sessionFile.readAsStringSync(), isNotEmpty);
+
+ analytics.sendPendingErrorEvents();
+ expect(
+ analytics.sentEvents.where(
+ (element) => element.eventName == DashEvent.analyticsException),
+ hasLength(1));
+
+ // Deleting the file now before sending an additional event should
+ // cause a different test error
+ sessionFile.deleteSync();
+ expect(sessionFile.existsSync(), isFalse);
+
+ analytics.send(testEvent);
+ expect(sessionFile.readAsStringSync(), isNotEmpty);
+ analytics.sendPendingErrorEvents();
+ expect(
+ analytics.sentEvents.where(
+ (element) => element.eventName == DashEvent.analyticsException),
+ hasLength(2));
+ expect(analytics.sentEvents, hasLength(4));
+
+ sessionFile.deleteSync();
+ expect(sessionFile.existsSync(), isFalse);
+
+ analytics.send(testEvent);
+ expect(sessionFile.readAsStringSync(), isNotEmpty);
+ expect(
+ analytics.sentEvents.where(
+ (element) => element.eventName == DashEvent.analyticsException),
+ hasLength(2));
+ });
+ });
+
+ group('Log handler:', () {
+ test('only sends one event for FormatException', () {
+ expect(logFile.existsSync(), isTrue);
+
+ // Write invalid lines to the log file to have a FormatException
+ // thrown when trying to parse the log file
+ logFile.writeAsStringSync('''
+{{}
+{{}
+''');
+
+ // Send one event so that the logFileStats method returns a valid value
+ analytics.send(testEvent);
+ expect(analytics.sentEvents, hasLength(1));
+ expect(logFile.readAsLinesSync(), hasLength(3));
+ analytics.sendPendingErrorEvents();
+ expect(
+ analytics.sentEvents.where(
+ (element) => element.eventName == DashEvent.analyticsException),
+ isEmpty,
+ );
+
+ // This call below will cause a FormatException while parsing the log file
+ final logFileStats = analytics.logFileStats();
+ expect(logFileStats, isNotNull);
+ expect(logFileStats!.recordCount, 1,
+ reason: 'The error event is not counted');
+ analytics.sendPendingErrorEvents();
+ expect(
+ analytics.sentEvents.where(
+ (element) => element.eventName == DashEvent.analyticsException),
+ hasLength(1),
+ );
+ expect(logFile.readAsLinesSync(), hasLength(4));
+ });
+
+ test('only sends one event for TypeError', () {
+ expect(logFile.existsSync(), isTrue);
+ // Write valid json but have one of the types wrong for
+ // the keys so that we throw a TypeError while casting the values
+ //
+ // In the json below, we have made the session id value a string when
+ // it should be an integer
+ logFile.writeAsStringSync('''
+{"client_id":"fcd6c0d5-6582-4c36-b09e-3ecedee9145c","events":[{"name":"command_usage_values","params":{"workflow":"doctor","commandHasTerminal":true}}],"user_properties":{"session_id":{"value":"this should be a string"},"flutter_channel":{"value":"master"},"host":{"value":"macOS"},"flutter_version":{"value":"3.20.0-2.0.pre.9"},"dart_version":{"value":"3.4.0 (build 3.4.0-99.0.dev)"},"analytics_pkg_version":{"value":"5.8.1"},"tool":{"value":"flutter-tool"},"local_time":{"value":"2024-02-07 15:46:19.920784 -0500"},"host_os_version":{"value":"Version 14.3 (Build 23D56)"},"locale":{"value":"en"},"client_ide":{"value":null},"enabled_features":{"value":"enable-native-assets"}}}
+''');
+ expect(logFile.readAsLinesSync(), hasLength(1));
+
+ // Send the test event so that the LogFileStats object is not null
+ analytics.send(testEvent);
+
+ final logFileStats = analytics.logFileStats();
+ analytics.sendPendingErrorEvents();
+ expect(
+ analytics.sentEvents.where(
+ (element) => element.eventName == DashEvent.analyticsException),
+ hasLength(1),
+ );
+ expect(logFileStats, isNotNull);
+ expect(logFileStats!.recordCount, 1);
+ });
+
+ test('sends two unique errors', () {
+ expect(logFile.existsSync(), isTrue);
+
+ // Write invalid lines to the log file to have a FormatException
+ // thrown when trying to parse the log file
+ logFile.writeAsStringSync('''
+{{}
+{{}
+''');
+
+ // Send one event so that the logFileStats method returns a valid value
+ analytics.send(testEvent);
+ expect(analytics.sentEvents, hasLength(1));
+ expect(logFile.readAsLinesSync(), hasLength(3));
+ analytics.sendPendingErrorEvents();
+ expect(
+ analytics.sentEvents.where(
+ (element) => element.eventName == DashEvent.analyticsException),
+ isEmpty,
+ );
+
+ // This will cause the first error
+ analytics.logFileStats();
+ analytics.sendPendingErrorEvents();
+ expect(
+ analytics.sentEvents.where(
+ (element) => element.eventName == DashEvent.analyticsException),
+ hasLength(1),
+ );
+
+ // Overwrite the contents of the log file now to include something that
+ // will cause a TypeError by changing the expected value for session id
+ // from integer to a string
+ logFile.writeAsStringSync('''
+{"client_id":"fcd6c0d5-6582-4c36-b09e-3ecedee9145c","events":[{"name":"command_usage_values","params":{"workflow":"doctor","commandHasTerminal":true}}],"user_properties":{"session_id":{"value":"this should be a string"},"flutter_channel":{"value":"master"},"host":{"value":"macOS"},"flutter_version":{"value":"3.20.0-2.0.pre.9"},"dart_version":{"value":"3.4.0 (build 3.4.0-99.0.dev)"},"analytics_pkg_version":{"value":"5.8.1"},"tool":{"value":"flutter-tool"},"local_time":{"value":"2024-02-07 15:46:19.920784 -0500"},"host_os_version":{"value":"Version 14.3 (Build 23D56)"},"locale":{"value":"en"},"client_ide":{"value":null},"enabled_features":{"value":"enable-native-assets"}}}
+''');
+ expect(logFile.readAsLinesSync(), hasLength(1));
+
+ // This will cause the second error
+ analytics.logFileStats();
+ analytics.sendPendingErrorEvents();
+ expect(
+ analytics.sentEvents.where(
+ (element) => element.eventName == DashEvent.analyticsException),
+ hasLength(2),
+ );
+
+ // Attempting to cause the same error won't send another error event
+ analytics.logFileStats();
+ analytics.sendPendingErrorEvents();
+ expect(
+ analytics.sentEvents.where(
+ (element) => element.eventName == DashEvent.analyticsException),
+ hasLength(2),
+ );
+ });
+ });
+}
diff --git a/pkgs/unified_analytics/test/event_test.dart b/pkgs/unified_analytics/test/event_test.dart
new file mode 100644
index 0000000..2db0b2a
--- /dev/null
+++ b/pkgs/unified_analytics/test/event_test.dart
@@ -0,0 +1,714 @@
+// Copyright (c) 2023, 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:mirrors';
+
+import 'package:test/test.dart';
+import 'package:unified_analytics/src/enums.dart';
+import 'package:unified_analytics/src/event.dart';
+import 'package:unified_analytics/unified_analytics.dart';
+
+void main() {
+ test('Event.analyticsCollectionEnabled constructed', () {
+ Event generateEvent() => Event.analyticsCollectionEnabled(status: false);
+
+ final constructedEvent = generateEvent();
+
+ expect(generateEvent, returnsNormally);
+ expect(constructedEvent.eventName, DashEvent.analyticsCollectionEnabled);
+ expect(constructedEvent.eventData['status'], false);
+ expect(constructedEvent.eventData.length, 1);
+ });
+
+ test('Event.clientNotification constructed', () {
+ Event generateEvent() => Event.clientNotification(
+ duration: 'duration',
+ latency: 'latency',
+ method: 'method',
+ );
+
+ final constructedEvent = generateEvent();
+
+ expect(generateEvent, returnsNormally);
+ expect(constructedEvent.eventName, DashEvent.clientNotification);
+ expect(constructedEvent.eventData['duration'], 'duration');
+ expect(constructedEvent.eventData['latency'], 'latency');
+ expect(constructedEvent.eventData['method'], 'method');
+ expect(constructedEvent.eventData.length, 3);
+ });
+
+ test('Event.clientRequest constructed', () {
+ Event generateEvent() => Event.clientRequest(
+ duration: 'duration',
+ latency: 'latency',
+ method: 'method',
+ added: 'added',
+ excluded: 'excluded',
+ files: 'files',
+ included: 'included',
+ openWorkspacePaths: 'openWorkspacePaths',
+ removed: 'removed',
+ );
+
+ final constructedEvent = generateEvent();
+
+ expect(generateEvent, returnsNormally);
+ expect(constructedEvent.eventName, DashEvent.clientRequest);
+ expect(constructedEvent.eventData['duration'], 'duration');
+ expect(constructedEvent.eventData['latency'], 'latency');
+ expect(constructedEvent.eventData['method'], 'method');
+ expect(constructedEvent.eventData['added'], 'added');
+ expect(constructedEvent.eventData['excluded'], 'excluded');
+ expect(constructedEvent.eventData['files'], 'files');
+ expect(constructedEvent.eventData['included'], 'included');
+ expect(
+ constructedEvent.eventData['openWorkspacePaths'], 'openWorkspacePaths');
+ expect(constructedEvent.eventData['removed'], 'removed');
+ expect(constructedEvent.eventData.length, 9);
+ });
+
+ test('Event.commandExecuted constructed', () {
+ Event generateEvent() => Event.commandExecuted(
+ count: 5,
+ name: 'name',
+ );
+
+ final constructedEvent = generateEvent();
+
+ expect(generateEvent, returnsNormally);
+ expect(constructedEvent.eventName, DashEvent.commandExecuted);
+ expect(constructedEvent.eventData['count'], 5);
+ expect(constructedEvent.eventData['name'], 'name');
+ expect(constructedEvent.eventData.length, 2);
+ });
+
+ 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,
+ );
+
+ 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);
+ });
+
+ test('Event.dartCliCommandExecuted constructed', () {
+ Event generateEvent() => Event.dartCliCommandExecuted(
+ name: 'name',
+ enabledExperiments: 'enabledExperiments',
+ exitCode: 0,
+ );
+
+ final constructedEvent = generateEvent();
+
+ expect(generateEvent, returnsNormally);
+ expect(constructedEvent.eventName, DashEvent.dartCliCommandExecuted);
+ expect(constructedEvent.eventData['name'], 'name');
+ expect(
+ constructedEvent.eventData['enabledExperiments'], 'enabledExperiments');
+ expect(constructedEvent.eventData['exitCode'], 0);
+ expect(constructedEvent.eventData.length, 3);
+ });
+
+ test('Event.doctorValidatorResult constructed', () {
+ Event generateEvent() => Event.doctorValidatorResult(
+ validatorName: 'validatorName',
+ result: 'success',
+ partOfGroupedValidator: false,
+ doctorInvocationId: 123,
+ statusInfo: 'statusInfo',
+ );
+
+ final constructedEvent = generateEvent();
+
+ expect(generateEvent, returnsNormally);
+ expect(constructedEvent.eventName, DashEvent.doctorValidatorResult);
+ expect(constructedEvent.eventData['validatorName'], 'validatorName');
+ expect(constructedEvent.eventData['result'], 'success');
+ expect(constructedEvent.eventData['partOfGroupedValidator'], false);
+ expect(constructedEvent.eventData['doctorInvocationId'], 123);
+ expect(constructedEvent.eventData['statusInfo'], 'statusInfo');
+ expect(constructedEvent.eventData.length, 5);
+ });
+
+ test('Event.hotReloadTime constructed', () {
+ Event generateEvent() => Event.hotReloadTime(timeMs: 500);
+
+ final constructedEvent = generateEvent();
+
+ expect(generateEvent, returnsNormally);
+ expect(constructedEvent.eventName, DashEvent.hotReloadTime);
+ expect(constructedEvent.eventData['timeMs'], 500);
+ expect(constructedEvent.eventData.length, 1);
+ });
+
+ test('Event.lintUsageCount constructed', () {
+ Event generateEvent() => Event.lintUsageCount(
+ count: 5,
+ name: 'name',
+ );
+
+ final constructedEvent = generateEvent();
+
+ expect(generateEvent, returnsNormally);
+ expect(constructedEvent.eventName, DashEvent.lintUsageCount);
+ expect(constructedEvent.eventData['count'], 5);
+ expect(constructedEvent.eventData['name'], 'name');
+ expect(constructedEvent.eventData.length, 2);
+ });
+
+ test('Event.memoryInfo constructed', () {
+ Event generateEvent() => Event.memoryInfo(
+ rss: 4,
+ periodSec: 5,
+ mbPerSec: 5.55,
+ );
+
+ final constructedEvent = generateEvent();
+
+ expect(generateEvent, returnsNormally);
+ expect(constructedEvent.eventName, DashEvent.memoryInfo);
+ expect(constructedEvent.eventData['rss'], 4);
+ expect(constructedEvent.eventData['periodSec'], 5);
+ expect(constructedEvent.eventData['mbPerSec'], 5.55);
+ expect(constructedEvent.eventData.length, 3);
+ });
+
+ test('Event.pluginRequest constructed', () {
+ Event generateEvent() => Event.pluginRequest(
+ duration: 'duration',
+ method: 'method',
+ pluginId: 'pluginId',
+ );
+
+ final constructedEvent = generateEvent();
+
+ expect(generateEvent, returnsNormally);
+ expect(constructedEvent.eventName, DashEvent.pluginRequest);
+ expect(constructedEvent.eventData['duration'], 'duration');
+ expect(constructedEvent.eventData['method'], 'method');
+ expect(constructedEvent.eventData['pluginId'], 'pluginId');
+ expect(constructedEvent.eventData.length, 3);
+ });
+
+ test('Event.pluginUse constructed', () {
+ Event generateEvent() => Event.pluginUse(
+ count: 5,
+ enabled: 'enabled',
+ pluginId: 'pluginId',
+ );
+
+ final constructedEvent = generateEvent();
+
+ expect(generateEvent, returnsNormally);
+ expect(constructedEvent.eventName, DashEvent.pluginUse);
+ expect(constructedEvent.eventData['count'], 5);
+ expect(constructedEvent.eventData['enabled'], 'enabled');
+ expect(constructedEvent.eventData['pluginId'], 'pluginId');
+ expect(constructedEvent.eventData.length, 3);
+ });
+
+ test('Event.pubGet constructed', () {
+ Event generateEvent() => Event.pubGet(
+ packageName: 'packageName',
+ version: 'version',
+ dependencyType: 'dependencyType',
+ );
+
+ final constructedEvent = generateEvent();
+
+ expect(generateEvent, returnsNormally);
+ expect(constructedEvent.eventName, DashEvent.pubGet);
+ expect(constructedEvent.eventData['packageName'], 'packageName');
+ expect(constructedEvent.eventData['version'], 'version');
+ expect(constructedEvent.eventData['dependencyType'], 'dependencyType');
+ expect(constructedEvent.eventData.length, 3);
+ });
+
+ test('Event.serverSession constructed', () {
+ Event generateEvent() => Event.serverSession(
+ clientId: 'clientId',
+ clientVersion: 'clientVersion',
+ duration: 5,
+ flags: 'flags',
+ parameters: 'parameters',
+ );
+
+ final constructedEvent = generateEvent();
+
+ expect(generateEvent, returnsNormally);
+ expect(constructedEvent.eventName, DashEvent.serverSession);
+ expect(constructedEvent.eventData['clientId'], 'clientId');
+ expect(constructedEvent.eventData['clientVersion'], 'clientVersion');
+ expect(constructedEvent.eventData['duration'], 5);
+ expect(constructedEvent.eventData['flags'], 'flags');
+ expect(constructedEvent.eventData['parameters'], 'parameters');
+ expect(constructedEvent.eventData.length, 5);
+ });
+
+ test('Event.severityAdjustment constructed', () {
+ Event generateEvent() => Event.severityAdjustment(
+ diagnostic: 'diagnostic',
+ adjustments: 'adjustments',
+ );
+
+ final constructedEvent = generateEvent();
+
+ expect(generateEvent, returnsNormally);
+ expect(constructedEvent.eventName, DashEvent.severityAdjustment);
+ expect(constructedEvent.eventData['diagnostic'], 'diagnostic');
+ expect(constructedEvent.eventData['adjustments'], 'adjustments');
+ expect(constructedEvent.eventData.length, 2);
+ });
+
+ test('Event.surveyAction constructed', () {
+ Event generateEvent() => Event.surveyAction(
+ surveyId: 'surveyId',
+ status: 'status',
+ );
+
+ final constructedEvent = generateEvent();
+
+ expect(generateEvent, returnsNormally);
+ expect(constructedEvent.eventName, DashEvent.surveyAction);
+ expect(constructedEvent.eventData['surveyId'], 'surveyId');
+ expect(constructedEvent.eventData['status'], 'status');
+ expect(constructedEvent.eventData.length, 2);
+ });
+
+ test('Event.surveyShown constructed', () {
+ Event generateEvent() => Event.surveyShown(surveyId: 'surveyId');
+
+ final constructedEvent = generateEvent();
+
+ expect(generateEvent, returnsNormally);
+ expect(constructedEvent.eventName, DashEvent.surveyShown);
+ expect(constructedEvent.eventData['surveyId'], 'surveyId');
+ expect(constructedEvent.eventData.length, 1);
+ });
+
+ test('Event.flutterBuildInfo constructed', () {
+ Event generateEvent() => Event.flutterBuildInfo(
+ label: 'label',
+ buildType: 'buildType',
+ command: 'command',
+ settings: 'settings',
+ error: 'error',
+ );
+
+ final constructedEvent = generateEvent();
+
+ expect(generateEvent, returnsNormally);
+ expect(constructedEvent.eventName, DashEvent.flutterBuildInfo);
+ expect(constructedEvent.eventData['label'], 'label');
+ expect(constructedEvent.eventData['buildType'], 'buildType');
+ expect(constructedEvent.eventData['command'], 'command');
+ expect(constructedEvent.eventData['settings'], 'settings');
+ expect(constructedEvent.eventData['error'], 'error');
+ expect(constructedEvent.eventData.length, 5);
+ });
+
+ test('Event.hotRunnerInfo constructed', () {
+ Event generateEvent() => Event.hotRunnerInfo(
+ label: 'label',
+ targetPlatform: 'targetPlatform',
+ sdkName: 'sdkName',
+ emulator: false,
+ fullRestart: true,
+ reason: 'reason',
+ finalLibraryCount: 5,
+ syncedLibraryCount: 6,
+ syncedClassesCount: 7,
+ syncedProceduresCount: 8,
+ syncedBytes: 9,
+ invalidatedSourcesCount: 10,
+ transferTimeInMs: 11,
+ overallTimeInMs: 12,
+ compileTimeInMs: 13,
+ findInvalidatedTimeInMs: 14,
+ scannedSourcesCount: 15,
+ reassembleTimeInMs: 16,
+ reloadVMTimeInMs: 17,
+ );
+
+ final constructedEvent = generateEvent();
+
+ expect(generateEvent, returnsNormally);
+ expect(constructedEvent.eventName, DashEvent.hotRunnerInfo);
+ expect(constructedEvent.eventData['label'], 'label');
+ expect(constructedEvent.eventData['targetPlatform'], 'targetPlatform');
+ expect(constructedEvent.eventData['sdkName'], 'sdkName');
+ expect(constructedEvent.eventData['emulator'], false);
+ expect(constructedEvent.eventData['fullRestart'], true);
+ expect(constructedEvent.eventData['reason'], 'reason');
+ expect(constructedEvent.eventData['finalLibraryCount'], 5);
+ expect(constructedEvent.eventData['syncedLibraryCount'], 6);
+ expect(constructedEvent.eventData['syncedClassesCount'], 7);
+ expect(constructedEvent.eventData['syncedProceduresCount'], 8);
+ expect(constructedEvent.eventData['syncedBytes'], 9);
+ expect(constructedEvent.eventData['invalidatedSourcesCount'], 10);
+ expect(constructedEvent.eventData['transferTimeInMs'], 11);
+ expect(constructedEvent.eventData['overallTimeInMs'], 12);
+ expect(constructedEvent.eventData['compileTimeInMs'], 13);
+ expect(constructedEvent.eventData['findInvalidatedTimeInMs'], 14);
+ expect(constructedEvent.eventData['scannedSourcesCount'], 15);
+ expect(constructedEvent.eventData['reassembleTimeInMs'], 16);
+ expect(constructedEvent.eventData['reloadVMTimeInMs'], 17);
+ expect(constructedEvent.eventData.length, 19);
+ });
+
+ test('Event.flutterCommandResult constructed', () {
+ Event generateEvent() => Event.flutterCommandResult(
+ commandPath: 'commandPath',
+ result: 'result',
+ commandHasTerminal: true,
+ maxRss: 123,
+ );
+
+ final constructedEvent = generateEvent();
+
+ expect(generateEvent, returnsNormally);
+ expect(constructedEvent.eventName, DashEvent.flutterCommandResult);
+ expect(constructedEvent.eventData['commandPath'], 'commandPath');
+ expect(constructedEvent.eventData['result'], 'result');
+ expect(constructedEvent.eventData['commandHasTerminal'], true);
+ expect(constructedEvent.eventData['maxRss'], 123);
+ expect(constructedEvent.eventData.length, 4);
+ });
+
+ test('Event.codeSizeAnalysis constructed', () {
+ Event generateEvent() => Event.codeSizeAnalysis(platform: 'platform');
+
+ final constructedEvent = generateEvent();
+
+ expect(generateEvent, returnsNormally);
+ expect(constructedEvent.eventName, DashEvent.codeSizeAnalysis);
+ expect(constructedEvent.eventData['platform'], 'platform');
+ expect(constructedEvent.eventData.length, 1);
+ });
+
+ test('Event.appleUsageEvent constructed', () {
+ Event generateEvent() => Event.appleUsageEvent(
+ workflow: 'workflow',
+ parameter: 'parameter',
+ result: 'result',
+ );
+
+ final constructedEvent = generateEvent();
+
+ expect(generateEvent, returnsNormally);
+ expect(constructedEvent.eventName, DashEvent.appleUsageEvent);
+ expect(constructedEvent.eventData['workflow'], 'workflow');
+ expect(constructedEvent.eventData['parameter'], 'parameter');
+ expect(constructedEvent.eventData['result'], 'result');
+ expect(constructedEvent.eventData.length, 3);
+ });
+
+ test('Event.exception constructed', () {
+ Event generateEvent() => Event.exception(
+ exception: 'exception',
+ data: {'foo': 'bar', 'baz': 1, 'shouldBeRemoved': null},
+ );
+
+ final constructedEvent = generateEvent();
+
+ expect(generateEvent, returnsNormally);
+ expect(constructedEvent.eventName, DashEvent.exception);
+ expect(constructedEvent.eventData['exception'], 'exception');
+ expect(constructedEvent.eventData['foo'], 'bar');
+ expect(constructedEvent.eventData['baz'], 1);
+ expect(constructedEvent.eventData.length, 3);
+ });
+
+ test('Event.exception constructor works when no data is provided', () {
+ Event generateEvent() => Event.exception(
+ exception: 'exception',
+ );
+
+ expect(generateEvent, returnsNormally);
+ });
+
+ test('Event.timing constructed', () {
+ Event generateEvent() => Event.timing(
+ workflow: 'workflow',
+ variableName: 'variableName',
+ elapsedMilliseconds: 123,
+ label: 'label',
+ );
+
+ final constructedEvent = generateEvent();
+
+ expect(generateEvent, returnsNormally);
+ expect(constructedEvent.eventName, DashEvent.timing);
+ expect(constructedEvent.eventData['workflow'], 'workflow');
+ expect(constructedEvent.eventData['variableName'], 'variableName');
+ expect(constructedEvent.eventData['elapsedMilliseconds'], 123);
+ expect(constructedEvent.eventData['label'], 'label');
+ expect(constructedEvent.eventData.length, 4);
+ });
+
+ test('Event.commandUsageValues constructed', () {
+ Event generateEvent() => Event.commandUsageValues(
+ workflow: 'workflow',
+ commandHasTerminal: true,
+ buildBundleTargetPlatform: 'buildBundleTargetPlatform',
+ buildBundleIsModule: true,
+ buildAarProjectType: 'buildAarProjectType',
+ buildAarTargetPlatform: 'buildAarTargetPlatform',
+ buildApkTargetPlatform: 'buildApkTargetPlatform',
+ buildApkBuildMode: 'buildApkBuildMode',
+ buildApkSplitPerAbi: true,
+ buildAppBundleTargetPlatform: 'buildAppBundleTargetPlatform',
+ buildAppBundleBuildMode: 'buildAppBundleBuildMode',
+ createProjectType: 'createProjectType',
+ createAndroidLanguage: 'createAndroidLanguage',
+ createIosLanguage: 'createIosLanguage',
+ packagesNumberPlugins: 123,
+ packagesProjectModule: true,
+ packagesAndroidEmbeddingVersion: 'packagesAndroidEmbeddingVersion',
+ runIsEmulator: true,
+ runTargetName: 'runTargetName',
+ runTargetOsVersion: 'runTargetOsVersion',
+ runModeName: 'runModeName',
+ runProjectModule: true,
+ runProjectHostLanguage: 'runProjectHostLanguage',
+ runAndroidEmbeddingVersion: 'runAndroidEmbeddingVersion',
+ runEnableImpeller: true,
+ runIOSInterfaceType: 'runIOSInterfaceType',
+ runIsTest: true,
+ );
+
+ final constructedEvent = generateEvent();
+
+ expect(generateEvent, returnsNormally);
+ expect(constructedEvent.eventName, DashEvent.commandUsageValues);
+ expect(constructedEvent.eventData['workflow'], 'workflow');
+ expect(constructedEvent.eventData['buildBundleTargetPlatform'],
+ 'buildBundleTargetPlatform');
+ expect(constructedEvent.eventData['buildBundleIsModule'], true);
+ expect(constructedEvent.eventData['buildAarProjectType'],
+ 'buildAarProjectType');
+ expect(constructedEvent.eventData['buildAarTargetPlatform'],
+ 'buildAarTargetPlatform');
+ expect(constructedEvent.eventData['buildApkTargetPlatform'],
+ 'buildApkTargetPlatform');
+ expect(
+ constructedEvent.eventData['buildApkBuildMode'], 'buildApkBuildMode');
+ expect(constructedEvent.eventData['buildApkSplitPerAbi'], true);
+ expect(constructedEvent.eventData['buildAppBundleTargetPlatform'],
+ 'buildAppBundleTargetPlatform');
+ expect(constructedEvent.eventData['buildAppBundleBuildMode'],
+ 'buildAppBundleBuildMode');
+ expect(
+ constructedEvent.eventData['createProjectType'], 'createProjectType');
+ expect(constructedEvent.eventData['createAndroidLanguage'],
+ 'createAndroidLanguage');
+ expect(
+ constructedEvent.eventData['createIosLanguage'], 'createIosLanguage');
+ expect(constructedEvent.eventData['packagesNumberPlugins'], 123);
+ expect(constructedEvent.eventData['packagesProjectModule'], true);
+ expect(constructedEvent.eventData['packagesAndroidEmbeddingVersion'],
+ 'packagesAndroidEmbeddingVersion');
+ expect(constructedEvent.eventData['runIsEmulator'], true);
+ expect(constructedEvent.eventData['runTargetName'], 'runTargetName');
+ expect(
+ constructedEvent.eventData['runTargetOsVersion'], 'runTargetOsVersion');
+ expect(constructedEvent.eventData['runModeName'], 'runModeName');
+ expect(constructedEvent.eventData['runProjectModule'], true);
+ expect(constructedEvent.eventData['runProjectHostLanguage'],
+ 'runProjectHostLanguage');
+ expect(constructedEvent.eventData['runAndroidEmbeddingVersion'],
+ 'runAndroidEmbeddingVersion');
+ expect(constructedEvent.eventData['runEnableImpeller'], true);
+ expect(constructedEvent.eventData['runIOSInterfaceType'],
+ 'runIOSInterfaceType');
+ expect(constructedEvent.eventData.length, 27);
+ });
+
+ test('Event.analyticsException constructed', () {
+ Event generateEvent() => Event.analyticsException(
+ workflow: 'workflow',
+ error: 'error',
+ description: 'description',
+ );
+
+ final constructedEvent = generateEvent();
+
+ expect(generateEvent, returnsNormally);
+ expect(constructedEvent.eventName, DashEvent.analyticsException);
+ expect(constructedEvent.eventData['workflow'], 'workflow');
+ expect(constructedEvent.eventData['error'], 'error');
+ expect(constructedEvent.eventData['description'], 'description');
+ expect(constructedEvent.eventData.length, 3);
+ });
+
+ test('Event.devtoolsEvent constructed', () {
+ Event generateEvent() => Event.devtoolsEvent(
+ screen: 'screen',
+ eventCategory: 'eventCategory',
+ label: 'label',
+ value: 1,
+ userInitiatedInteraction: true,
+ g3Username: 'g3Username',
+ userApp: 'userApp',
+ userBuild: 'userBuild',
+ userPlatform: 'userPlatform',
+ devtoolsPlatform: 'devtoolsPlatform',
+ devtoolsChrome: 'devtoolsChrome',
+ devtoolsVersion: 'devtoolsVersion',
+ ideLaunched: 'ideLaunched',
+ isExternalBuild: 'isExternalBuild',
+ isEmbedded: 'isEmbedded',
+ ideLaunchedFeature: 'ideLaunchedFeature',
+ isWasm: 'true',
+ additionalMetrics: _TestMetrics(
+ stringField: 'test',
+ intField: 100,
+ boolField: false,
+ ),
+ );
+
+ final constructedEvent = generateEvent();
+
+ expect(generateEvent, returnsNormally);
+ expect(constructedEvent.eventData['screen'], 'screen');
+ expect(constructedEvent.eventData['eventCategory'], 'eventCategory');
+ expect(constructedEvent.eventData['label'], 'label');
+ expect(constructedEvent.eventData['value'], 1);
+ expect(constructedEvent.eventData['userInitiatedInteraction'], true);
+ expect(constructedEvent.eventData['g3Username'], 'g3Username');
+ expect(constructedEvent.eventData['userApp'], 'userApp');
+ expect(constructedEvent.eventData['userBuild'], 'userBuild');
+ expect(constructedEvent.eventData['userPlatform'], 'userPlatform');
+ expect(constructedEvent.eventData['devtoolsPlatform'], 'devtoolsPlatform');
+ expect(constructedEvent.eventData['devtoolsChrome'], 'devtoolsChrome');
+ expect(constructedEvent.eventData['devtoolsVersion'], 'devtoolsVersion');
+ expect(constructedEvent.eventData['ideLaunched'], 'ideLaunched');
+ expect(constructedEvent.eventData['isExternalBuild'], 'isExternalBuild');
+ expect(constructedEvent.eventData['isEmbedded'], 'isEmbedded');
+ expect(
+ constructedEvent.eventData['ideLaunchedFeature'],
+ 'ideLaunchedFeature',
+ );
+ expect(constructedEvent.eventData['isWasm'], 'true');
+ expect(constructedEvent.eventData['stringField'], 'test');
+ expect(constructedEvent.eventData['intField'], 100);
+ expect(constructedEvent.eventData['boolField'], false);
+ expect(constructedEvent.eventData.containsKey('nullableField'), false);
+ expect(constructedEvent.eventData.length, 20);
+ });
+
+ test('Confirm all constructors were checked', () {
+ var constructorCount = 0;
+ for (final declaration in reflectClass(Event).declarations.keys) {
+ // Count public constructors but omit private constructors
+ if (declaration.toString().contains('Event.') &&
+ !declaration.toString().contains('Event._')) {
+ constructorCount++;
+ }
+ }
+
+ // Change this integer below if your PR either adds or removes
+ // an Event constructor
+ final eventsAccountedForInTests = 27;
+ expect(eventsAccountedForInTests, constructorCount,
+ reason: 'If you added or removed an event constructor, '
+ 'ensure you have updated '
+ '`pkgs/unified_analytics/test/event_test.dart` '
+ 'to reflect the changes made');
+ });
+
+ test('Serializing event to json successful', () {
+ final event = Event.analyticsException(
+ workflow: 'workflow',
+ error: 'error',
+ description: 'description',
+ );
+
+ final expectedResult = '{"eventName":"analytics_exception",'
+ '"eventData":{"workflow":"workflow",'
+ '"error":"error",'
+ '"description":"description"}}';
+
+ expect(event.toJson(), expectedResult);
+ });
+
+ test('Deserializing string to event successful', () {
+ final eventJson = '{"eventName":"analytics_exception",'
+ '"eventData":{"workflow":"workflow",'
+ '"error":"error",'
+ '"description":"description"}}';
+
+ final eventConstructed = Event.fromJson(eventJson);
+ expect(eventConstructed, isNotNull);
+ eventConstructed!;
+
+ expect(eventConstructed.eventName, DashEvent.analyticsException);
+ expect(eventConstructed.eventData, {
+ 'workflow': 'workflow',
+ 'error': 'error',
+ 'description': 'description',
+ });
+ });
+
+ test('Deserializing string to event unsuccessful for invalid eventName', () {
+ final eventJson = '{"eventName":"NOT_VALID_NAME",'
+ '"eventData":{"workflow":"workflow",'
+ '"error":"error",'
+ '"description":"description"}}';
+
+ final eventConstructed = Event.fromJson(eventJson);
+ expect(eventConstructed, isNull);
+ });
+
+ test('Deserializing string to event unsuccessful for invalid eventData', () {
+ final eventJson = '{"eventName":"analytics_exception",'
+ '"eventData": "not_valid_event_data"}';
+
+ final eventConstructed = Event.fromJson(eventJson);
+ expect(eventConstructed, isNull);
+ });
+}
+
+final class _TestMetrics extends CustomMetrics {
+ _TestMetrics({
+ required this.stringField,
+ required this.intField,
+ required this.boolField,
+ });
+
+ final String stringField;
+ final int intField;
+ final bool boolField;
+
+ @override
+ Map<String, Object> toMap() => {
+ 'stringField': stringField,
+ 'intField': intField,
+ 'boolField': boolField,
+ };
+}
diff --git a/pkgs/unified_analytics/test/events_with_fake_test.dart b/pkgs/unified_analytics/test/events_with_fake_test.dart
new file mode 100644
index 0000000..9794275
--- /dev/null
+++ b/pkgs/unified_analytics/test/events_with_fake_test.dart
@@ -0,0 +1,146 @@
+// Copyright (c) 2023, 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:clock/clock.dart';
+import 'package:file/file.dart';
+import 'package:file/memory.dart';
+import 'package:test/test.dart';
+
+import 'package:unified_analytics/src/constants.dart';
+import 'package:unified_analytics/src/enums.dart';
+import 'package:unified_analytics/src/survey_handler.dart';
+import 'package:unified_analytics/unified_analytics.dart';
+
+void main() {
+ // The fake analytics instance can be used to ensure events
+ // are being sent when invoking methods on the `Analytics` instance
+
+ late FakeAnalytics fakeAnalytics;
+ late MemoryFileSystem fs;
+ late Directory homeDirectory;
+ late File dismissedSurveyFile;
+
+ /// Survey to load into the fake instance to fetch
+ ///
+ /// The 1.0 sample rate means that this will always show
+ /// up from the method to fetch available surveys
+ final testSurvey = Survey(
+ uniqueId: 'uniqueId',
+ startDate: DateTime(2022, 1, 1),
+ endDate: DateTime(2022, 12, 31),
+ description: 'description',
+ snoozeForMinutes: 10,
+ samplingRate: 1.0, // 100% sample rate
+ excludeDashToolList: [],
+ conditionList: <Condition>[],
+ buttonList: [
+ SurveyButton(
+ buttonText: 'buttonText',
+ action: 'accept',
+ promptRemainsVisible: false,
+ ),
+ SurveyButton(
+ buttonText: 'buttonText',
+ action: 'dismiss',
+ promptRemainsVisible: false,
+ ),
+ ],
+ );
+
+ setUp(() async {
+ fs = MemoryFileSystem.test(style: FileSystemStyle.posix);
+ homeDirectory = fs.directory('home');
+ dismissedSurveyFile = homeDirectory
+ .childDirectory(kDartToolDirectoryName)
+ .childFile(kDismissedSurveyFileName);
+
+ final initialAnalytics = Analytics.fake(
+ tool: DashTool.flutterTool,
+ homeDirectory: homeDirectory,
+ dartVersion: 'dartVersion',
+ toolsMessageVersion: 1,
+ fs: fs,
+ platform: DevicePlatform.macos,
+ );
+ initialAnalytics.clientShowedMessage();
+
+ // Recreate a second instance since events cannot be sent on
+ // the first run
+ withClock(Clock.fixed(DateTime(2022, 3, 3)), () {
+ final toolsMessageVersion = kToolsMessageVersion;
+ fakeAnalytics = Analytics.fake(
+ tool: DashTool.flutterTool,
+ homeDirectory: homeDirectory,
+ dartVersion: 'dartVersion',
+ platform: DevicePlatform.macos,
+ fs: fs,
+ toolsMessageVersion: toolsMessageVersion,
+ surveyHandler: FakeSurveyHandler.fromList(
+ dismissedSurveyFile: dismissedSurveyFile,
+ initializedSurveys: [testSurvey],
+ ),
+ );
+ });
+ });
+
+ test('event sent when survey shown', () async {
+ final surveyList = await fakeAnalytics.fetchAvailableSurveys();
+ expect(surveyList.length, 1);
+ expect(fakeAnalytics.sentEvents.length, 0);
+
+ final survey = surveyList.first;
+ expect(survey.uniqueId, 'uniqueId');
+
+ // Simulate the survey being shown
+ fakeAnalytics.surveyShown(survey);
+
+ expect(fakeAnalytics.sentEvents.length, 1);
+ expect(fakeAnalytics.sentEvents.last.eventName, DashEvent.surveyShown);
+ expect(fakeAnalytics.sentEvents.last.eventData, {'surveyId': 'uniqueId'});
+ });
+
+ test('event sent when survey accepted', () async {
+ final surveyList = await fakeAnalytics.fetchAvailableSurveys();
+ expect(surveyList.length, 1);
+ expect(fakeAnalytics.sentEvents.length, 0);
+
+ final survey = surveyList.first;
+ expect(survey.uniqueId, 'uniqueId');
+
+ // Simulate the survey being shown
+ //
+ // The first button is the accept button
+ fakeAnalytics.surveyInteracted(
+ survey: survey,
+ surveyButton: survey.buttonList.first,
+ );
+
+ expect(fakeAnalytics.sentEvents.length, 1);
+ expect(fakeAnalytics.sentEvents.last.eventName, DashEvent.surveyAction);
+ expect(fakeAnalytics.sentEvents.last.eventData,
+ {'surveyId': 'uniqueId', 'status': 'accept'});
+ });
+
+ test('event sent when survey rejected', () async {
+ final surveyList = await fakeAnalytics.fetchAvailableSurveys();
+ expect(surveyList.length, 1);
+ expect(fakeAnalytics.sentEvents.length, 0);
+
+ final survey = surveyList.first;
+ expect(survey.uniqueId, 'uniqueId');
+
+ // Simulate the survey being shown
+ //
+ // The last button is the reject button
+ fakeAnalytics.surveyInteracted(
+ survey: survey,
+ surveyButton: survey.buttonList.last,
+ );
+
+ expect(fakeAnalytics.sentEvents.length, 1);
+ expect(fakeAnalytics.sentEvents.last.eventName, DashEvent.surveyAction);
+ expect(fakeAnalytics.sentEvents.last.eventData,
+ {'surveyId': 'uniqueId', 'status': 'dismiss'});
+ });
+}
diff --git a/pkgs/unified_analytics/test/legacy_analytics_test.dart b/pkgs/unified_analytics/test/legacy_analytics_test.dart
new file mode 100644
index 0000000..36acee9
--- /dev/null
+++ b/pkgs/unified_analytics/test/legacy_analytics_test.dart
@@ -0,0 +1,325 @@
+// Copyright (c) 2023, 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/memory.dart';
+import 'package:test/test.dart';
+
+import 'package:unified_analytics/src/enums.dart';
+import 'package:unified_analytics/unified_analytics.dart';
+
+void main() {
+ late MemoryFileSystem fs;
+ late Directory home;
+ late Analytics analytics;
+
+ const homeDirName = 'home';
+ const initialTool = DashTool.flutterTool;
+ const toolsMessageVersion = 1;
+ const toolsMessage = 'toolsMessage';
+ const flutterChannel = 'flutterChannel';
+ const flutterVersion = 'flutterVersion';
+ const dartVersion = 'dartVersion';
+ const platform = DevicePlatform.macos;
+
+ setUp(() {
+ // Setup the filesystem with the home directory
+ final fsStyle =
+ io.Platform.isWindows ? FileSystemStyle.windows : FileSystemStyle.posix;
+ fs = MemoryFileSystem.test(style: fsStyle);
+ home = fs.directory(homeDirName);
+ });
+
+ test('Honor legacy dart analytics opt out', () {
+ // Create the file for the dart legacy opt out
+ final dartLegacyConfigFile =
+ home.childDirectory('.dart').childFile('dartdev.json');
+ dartLegacyConfigFile.createSync(recursive: true);
+ dartLegacyConfigFile.writeAsStringSync('''
+{
+ "firstRun": false,
+ "enabled": false,
+ "disclosureShown": true,
+ "clientId": "52710e60-7c70-4335-b3a4-9d922630f12a"
+}
+''');
+
+ // The main analytics instance, other instances can be spawned within tests
+ // to test how to instances running together work
+ analytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ );
+
+ expect(analytics.telemetryEnabled, false);
+ });
+
+ test('Telemetry enabled if legacy dart analytics is enabled', () {
+ // Create the file for the dart legacy opt out
+ final dartLegacyConfigFile =
+ home.childDirectory('.dart').childFile('dartdev.json');
+ dartLegacyConfigFile.createSync(recursive: true);
+ dartLegacyConfigFile.writeAsStringSync('''
+{
+ "firstRun": false,
+ "enabled": true,
+ "disclosureShown": true,
+ "clientId": "52710e60-7c70-4335-b3a4-9d922630f12a"
+}
+''');
+
+ // The main analytics instance, other instances can be spawned within tests
+ // to test how to instances running together work
+ analytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ );
+
+ expect(analytics.telemetryEnabled, true);
+ });
+
+ test('Honor legacy flutter analytics opt out', () {
+ // Create the file for the flutter legacy opt out
+ final flutterLegacyConfigFile =
+ home.childDirectory('.dart').childFile('dartdev.json');
+ flutterLegacyConfigFile.createSync(recursive: true);
+ flutterLegacyConfigFile.writeAsStringSync('''
+{
+ "firstRun": false,
+ "clientId": "4c3a3d1e-e545-47e7-b4f8-10129f6ab169",
+ "enabled": false
+}
+''');
+
+ // The main analytics instance, other instances can be spawned within tests
+ // to test how to instances running together work
+ analytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ );
+
+ expect(analytics.telemetryEnabled, false);
+ });
+
+ test('Telemetry enabled if legacy flutter analytics is enabled', () {
+ // Create the file for the flutter legacy opt out
+ final flutterLegacyConfigFile =
+ home.childDirectory('.dart').childFile('dartdev.json');
+ flutterLegacyConfigFile.createSync(recursive: true);
+ flutterLegacyConfigFile.writeAsStringSync('''
+{
+ "firstRun": false,
+ "clientId": "4c3a3d1e-e545-47e7-b4f8-10129f6ab169",
+ "enabled": true
+}
+''');
+
+ // The main analytics instance, other instances can be spawned within tests
+ // to test how to instances running together work
+ analytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ );
+
+ expect(analytics.telemetryEnabled, true);
+ });
+
+ test('Honor legacy devtools analytics opt out', () {
+ // Create the file for the devtools legacy opt out
+ final devtoolsLegacyConfigFile =
+ home.childDirectory('.flutter-devtools').childFile('.devtools');
+ devtoolsLegacyConfigFile.createSync(recursive: true);
+ devtoolsLegacyConfigFile.writeAsStringSync('''
+{
+ "analyticsEnabled": false,
+ "isFirstRun": false,
+ "lastReleaseNotesVersion": "2.31.0",
+ "2023-Q4": {
+ "surveyActionTaken": false,
+ "surveyShownCount": 0
+ }
+}
+''');
+
+ // The main analytics instance, other instances can be spawned within tests
+ // to test how to instances running together work
+ analytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ );
+
+ expect(analytics.telemetryEnabled, false);
+ });
+
+ test('Telemetry enabled if legacy devtools analytics is enabled', () {
+ // Create the file for the devtools legacy opt out
+ final devtoolsLegacyConfigFile =
+ home.childDirectory('.flutter-devtools').childFile('.devtools');
+ devtoolsLegacyConfigFile.createSync(recursive: true);
+ devtoolsLegacyConfigFile.writeAsStringSync('''
+{
+ "analyticsEnabled": true,
+ "isFirstRun": false,
+ "lastReleaseNotesVersion": "2.31.0",
+ "2023-Q4": {
+ "surveyActionTaken": false,
+ "surveyShownCount": 0
+ }
+}
+''');
+
+ // The main analytics instance, other instances can be spawned within tests
+ // to test how to instances running together work
+ analytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ );
+
+ expect(analytics.telemetryEnabled, true);
+ });
+
+ test('Telemetry disabled if dart config file corrupted', () {
+ // Create the file for the dart legacy opt out with text that
+ // is not valid JSON
+ final dartLegacyConfigFile =
+ home.childDirectory('.dart').childFile('dartdev.json');
+ dartLegacyConfigFile.createSync(recursive: true);
+ dartLegacyConfigFile.writeAsStringSync('''
+NOT VALID JSON
+{
+ "firstRun": false,
+ "clientId": "4c3a3d1e-e545-47e7-b4f8-10129f6ab169",
+ "enabled": true
+}
+''');
+
+ // The main analytics instance, other instances can be spawned within tests
+ // to test how to instances running together work
+ analytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ );
+
+ expect(analytics.telemetryEnabled, false);
+ });
+
+ test('Telemetry disabled if devtools config file corrupted', () {
+ // Create the file for the devtools legacy opt out with text that
+ // is not valid JSON
+ final devtoolsLegacyConfigFile =
+ home.childDirectory('.flutter-devtools').childFile('.devtools');
+ devtoolsLegacyConfigFile.createSync(recursive: true);
+ devtoolsLegacyConfigFile.writeAsStringSync('''
+NOT VALID JSON
+{
+ "analyticsEnabled": true,
+ "isFirstRun": false,
+ "lastReleaseNotesVersion": "2.31.0",
+ "2023-Q4": {
+ "surveyActionTaken": false,
+ "surveyShownCount": 0
+ }
+}
+''');
+
+ // The main analytics instance, other instances can be spawned within tests
+ // to test how to instances running together work
+ analytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ );
+
+ expect(analytics.telemetryEnabled, false);
+ });
+
+ test('Telemetry disabled if flutter config file corrupted', () {
+ // Create the file for the flutter legacy opt out with text that
+ // is not valid JSON
+ final fluttterLegacyConfigFile =
+ home.childDirectory('.dart').childFile('dartdev.json');
+ fluttterLegacyConfigFile.createSync(recursive: true);
+ fluttterLegacyConfigFile.writeAsStringSync('''
+NOT VALID JSON
+{
+ "firstRun": false,
+ "clientId": "4c3a3d1e-e545-47e7-b4f8-10129f6ab169",
+ "enabled": true
+}
+''');
+
+ // The main analytics instance, other instances can be spawned within tests
+ // to test how to instances running together work
+ analytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ );
+
+ expect(analytics.telemetryEnabled, false);
+ });
+}
diff --git a/pkgs/unified_analytics/test/log_handler_test.dart b/pkgs/unified_analytics/test/log_handler_test.dart
new file mode 100644
index 0000000..db14d96
--- /dev/null
+++ b/pkgs/unified_analytics/test/log_handler_test.dart
@@ -0,0 +1,381 @@
+// Copyright (c) 2023, 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 'package:file/file.dart';
+import 'package:file/memory.dart';
+import 'package:test/fake.dart';
+import 'package:test/test.dart';
+
+import 'package:unified_analytics/src/constants.dart';
+import 'package:unified_analytics/src/enums.dart';
+import 'package:unified_analytics/src/log_handler.dart';
+import 'package:unified_analytics/src/utils.dart';
+import 'package:unified_analytics/unified_analytics.dart';
+
+void main() {
+ late FakeAnalytics analytics;
+ late Directory homeDirectory;
+ late MemoryFileSystem fs;
+ late File logFile;
+
+ final testEvent = Event.hotReloadTime(timeMs: 10);
+
+ setUp(() {
+ fs = MemoryFileSystem.test(style: FileSystemStyle.posix);
+ homeDirectory = fs.directory('home');
+ logFile = homeDirectory
+ .childDirectory(kDartToolDirectoryName)
+ .childFile(kLogFileName);
+
+ // Create the initialization analytics instance to onboard the tool
+ final initializationAnalytics = Analytics.fake(
+ tool: DashTool.flutterTool,
+ homeDirectory: homeDirectory,
+ dartVersion: 'dartVersion',
+ fs: fs,
+ platform: DevicePlatform.macos,
+ );
+ initializationAnalytics.clientShowedMessage();
+
+ // This instance is free to send events since the instance above
+ // has confirmed that the client has shown the message
+ analytics = Analytics.fake(
+ tool: DashTool.flutterTool,
+ homeDirectory: homeDirectory,
+ dartVersion: 'dartVersion',
+ fs: fs,
+ platform: DevicePlatform.macos,
+ );
+ });
+
+ test('Ensure that log file is created', () {
+ expect(logFile.existsSync(), true);
+ });
+
+ test('LogFileStats is null before events are sent', () {
+ expect(analytics.logFileStats(), isNull);
+ });
+
+ test('LogFileStats returns valid response after sent events', () async {
+ final countOfEventsToSend = 10;
+
+ for (var i = 0; i < countOfEventsToSend; i++) {
+ analytics.send(testEvent);
+ }
+
+ expect(analytics.logFileStats(), isNotNull);
+ expect(logFile.readAsLinesSync().length, countOfEventsToSend);
+ expect(analytics.logFileStats()!.recordCount, countOfEventsToSend);
+ });
+
+ test('The only record in the log file is malformed', () async {
+ // Write invalid json for the only log record
+ logFile.writeAsStringSync('{{\n');
+
+ expect(logFile.readAsLinesSync().length, 1);
+ final logFileStats = analytics.logFileStats();
+ expect(logFileStats, isNull,
+ reason: 'Null should be returned since only '
+ 'one record is in there and it is malformed');
+
+ analytics.sendPendingErrorEvents();
+ expect(
+ analytics.sentEvents,
+ contains(
+ Event.analyticsException(
+ workflow: 'LogFileStats.logFileStats',
+ error: 'FormatException',
+ description: 'message: Unexpected character\nsource: {{',
+ ),
+ ));
+ });
+
+ test('The first record is malformed, but rest are valid', () async {
+ // Write invalid json for the only log record
+ logFile.writeAsStringSync('{{\n');
+
+ final countOfEventsToSend = 10;
+
+ for (var i = 0; i < countOfEventsToSend; i++) {
+ analytics.send(testEvent);
+ }
+ expect(logFile.readAsLinesSync().length, countOfEventsToSend + 1);
+ final logFileStats = analytics.logFileStats();
+
+ expect(logFileStats, isNotNull);
+ expect(logFileStats!.recordCount, countOfEventsToSend);
+ });
+
+ test('Several records are malformed', () async {
+ final countOfMalformedRecords = 4;
+ for (var i = 0; i < countOfMalformedRecords; i++) {
+ final currentContents = logFile.readAsStringSync();
+ logFile.writeAsStringSync('$currentContents{{\n');
+ }
+
+ final countOfEventsToSend = 10;
+
+ for (var i = 0; i < countOfEventsToSend; i++) {
+ analytics.send(testEvent);
+ }
+
+ expect(logFile.readAsLinesSync().length,
+ countOfEventsToSend + countOfMalformedRecords);
+ final logFileStats = analytics.logFileStats();
+
+ analytics.sendPendingErrorEvents();
+ expect(logFile.readAsLinesSync().length,
+ countOfEventsToSend + countOfMalformedRecords + 1,
+ reason:
+ 'There should have been on error event sent when getting stats');
+
+ expect(logFileStats, isNotNull);
+ expect(logFileStats!.recordCount, countOfEventsToSend);
+ });
+
+ test('Valid json but invalid keys', () {
+ // The second line here is missing the "events" top level
+ // key which should cause an error for that record only
+ //
+ // Important to note that this won't actually cause a FormatException
+ // like the other malformed records, instead the LogItem.fromRecord
+ // constructor will return null if all the keys are not available
+ final contents = '''
+{"client_id":"ffcea97b-db5e-4c66-98c2-3942de4fac40","events":[{"name":"hot_reload_time","params":{"timeMs":136}}],"user_properties":{"session_id":{"value":1699385899950},"flutter_channel":{"value":"ey-test-channel"},"host":{"value":"macOS"},"flutter_version":{"value":"Flutter 3.6.0-7.0.pre.47"},"dart_version":{"value":"Dart 2.19.0"},"analytics_pkg_version":{"value":"5.2.0"},"tool":{"value":"flutter-tool"},"local_time":{"value":"2023-11-07 15:37:26.685761 -0500"},"host_os_version":{"value":"Version 14.1 (Build 23B74)"},"locale":{"value":"en"},"client_ide":{"value":"VSCode"}}}
+{"client_id":"ffcea97b-db5e-4c66-98c2-3942de4fac40","WRONG_EVENT_KEY":[{"name":"hot_reload_time","params":{"timeMs":136}}],"user_properties":{"session_id":{"value":1699385899950},"flutter_channel":{"value":"ey-test-channel"},"host":{"value":"macOS"},"flutter_version":{"value":"Flutter 3.6.0-7.0.pre.47"},"dart_version":{"value":"Dart 2.19.0"},"analytics_pkg_version":{"value":"5.2.0"},"tool":{"value":"flutter-tool"},"local_time":{"value":"2023-11-07 15:37:26.685761 -0500"},"host_os_version":{"value":"Version 14.1 (Build 23B74)"},"locale":{"value":"en"},"client_ide":{"value":"VSCode"}}}
+{"client_id":"ffcea97b-db5e-4c66-98c2-3942de4fac40","events":[{"name":"hot_reload_time","params":{"timeMs":136}}],"user_properties":{"session_id":{"value":1699385899950},"flutter_channel":{"value":"ey-test-channel"},"host":{"value":"macOS"},"flutter_version":{"value":"Flutter 3.6.0-7.0.pre.47"},"dart_version":{"value":"Dart 2.19.0"},"analytics_pkg_version":{"value":"5.2.0"},"tool":{"value":"flutter-tool"},"local_time":{"value":"2023-11-07 15:37:26.685761 -0500"},"host_os_version":{"value":"Version 14.1 (Build 23B74)"},"locale":{"value":"en"},"client_ide":{"value":"VSCode"}}}
+''';
+ logFile.writeAsStringSync(contents);
+
+ final logFileStats = analytics.logFileStats();
+
+ expect(logFile.readAsLinesSync().length, 3);
+ expect(logFileStats, isNotNull);
+ expect(logFileStats!.recordCount, 2);
+ });
+
+ test('Malformed record gets phased out after several events', () async {
+ // Write invalid json for the only log record
+ logFile.writeAsStringSync('{{\n');
+
+ // Send the max number of events minus two so that we have
+ // one malformed record on top of the logs and the rest
+ // are valid log records
+ //
+ // We need to account for the event that is sent when
+ // calling [logFileStats()] fails and sends an instance
+ // of [Event.analyticsException]
+ final recordsToSendInitially = kLogFileLength - 2;
+ for (var i = 0; i < recordsToSendInitially; i++) {
+ analytics.send(testEvent);
+ }
+ final logFileStats = analytics.logFileStats();
+ analytics.sendPendingErrorEvents();
+ expect(analytics.sentEvents.last.eventName, DashEvent.analyticsException,
+ reason: 'Calling for the stats should have caused an error');
+ expect(logFile.readAsLinesSync().length, kLogFileLength);
+ expect(logFileStats, isNotNull);
+ expect(logFileStats!.recordCount, recordsToSendInitially,
+ reason: 'The first record should be malformed');
+ expect(logFile.readAsLinesSync()[0].trim(), '{{');
+
+ // Sending one more event should flush out the malformed record
+ analytics.send(testEvent);
+
+ final secondLogFileStats = analytics.logFileStats();
+ expect(analytics.sentEvents.last, testEvent);
+ expect(secondLogFileStats, isNotNull);
+ expect(secondLogFileStats!.recordCount, kLogFileLength);
+ expect(logFile.readAsLinesSync()[0].trim(), isNot('{{'));
+ });
+
+ test(
+ 'Catches and discards any FileSystemException raised from attempting '
+ 'to write to the log file', () async {
+ final logFilePath = 'log.txt';
+ final fs = MemoryFileSystem.test(opHandle: (context, operation) {
+ if (context == logFilePath && operation == FileSystemOp.write) {
+ throw FileSystemException(
+ 'writeFrom failed',
+ logFilePath,
+ const OSError('No space left on device', 28),
+ );
+ }
+ });
+ final logFile = fs.file(logFilePath);
+ logFile.createSync();
+ final logHandler = LogHandler(logFile: logFile);
+
+ logHandler.save(data: {});
+ });
+
+ test('deletes log file larger than kMaxLogFileSize', () async {
+ var deletedLargeLogFile = false;
+ var wroteDataToLogFile = false;
+ const data = <String, Object?>{};
+ final logFile = _FakeFile('log.txt')
+ .._deleteSyncImpl = (() => deletedLargeLogFile = true)
+ .._createSyncImpl = () {}
+ .._statSyncImpl = (() => _FakeFileStat(kMaxLogFileSize + 1))
+ .._writeAsStringSync = (contents, {mode = FileMode.append}) {
+ expect(contents.trim(), data.toString());
+ expect(mode, FileMode.writeOnlyAppend);
+ wroteDataToLogFile = true;
+ };
+ final logHandler = LogHandler(logFile: logFile);
+
+ logHandler.save(data: data);
+ expect(deletedLargeLogFile, isTrue);
+ expect(wroteDataToLogFile, isTrue);
+ });
+
+ test('does not delete log file if smaller than kMaxLogFileSize', () async {
+ var wroteDataToLogFile = false;
+ const data = <String, Object?>{};
+ final logFile = _FakeFile('log.txt')
+ .._deleteSyncImpl =
+ (() => fail('called logFile.deleteSync() when file was less than '
+ 'kMaxLogFileSize'))
+ .._createSyncImpl = () {}
+ .._readAsLinesSyncImpl = (() => ['three', 'previous', 'lines'])
+ .._statSyncImpl = (() => _FakeFileStat(kMaxLogFileSize - 1))
+ .._writeAsStringSync = (contents, {mode = FileMode.append}) {
+ expect(contents.trim(), data.toString());
+ expect(mode, FileMode.writeOnlyAppend);
+ wroteDataToLogFile = true;
+ };
+ final logHandler = LogHandler(logFile: logFile);
+
+ logHandler.save(data: data);
+ expect(wroteDataToLogFile, isTrue);
+ });
+
+ test('Catching cast errors for each log record silently', () async {
+ // Write a json array to the log file which will cause
+ // a cast error when parsing each line
+ logFile.writeAsStringSync('[{}, 1, 2, 3]\n');
+
+ final logFileStats = analytics.logFileStats();
+ expect(logFileStats, isNull);
+
+ // Ensure it will work as expected after writing correct logs
+ final countOfEventsToSend = 10;
+ for (var i = 0; i < countOfEventsToSend; i++) {
+ analytics.send(testEvent);
+ }
+ analytics.sendPendingErrorEvents();
+ final secondLogFileStats = analytics.logFileStats();
+
+ expect(secondLogFileStats, isNotNull);
+ expect(secondLogFileStats!.recordCount, countOfEventsToSend + 1,
+ reason: 'Plus one for the error event that is sent '
+ 'from the first logFileStats call');
+ });
+
+ test(
+ 'truncateStringToLength returns same string when '
+ 'max length greater than string length', () {
+ final testString = 'Version 14.1 (Build 23B74)';
+ final maxLength = 100;
+
+ expect(testString.length < maxLength, true);
+
+ String runTruncateString() => truncateStringToLength(testString, maxLength);
+
+ expect(runTruncateString, returnsNormally);
+
+ final newString = runTruncateString();
+ expect(newString, testString);
+ });
+
+ test(
+ 'truncateStringToLength returns truncated string when '
+ 'max length less than string length', () {
+ final testString = 'Version 14.1 (Build 23B74)';
+ final maxLength = 10;
+
+ expect(testString.length > maxLength, true);
+
+ String runTruncateString() => truncateStringToLength(testString, maxLength);
+
+ expect(runTruncateString, returnsNormally);
+
+ final newString = runTruncateString();
+ expect(newString.length, maxLength);
+ expect(newString, 'Version 14');
+ });
+
+ test('truncateStringToLength handle errors for invalid max length', () {
+ final testString = 'Version 14.1 (Build 23B74)';
+ var maxLength = 0;
+ String runTruncateString() => truncateStringToLength(testString, maxLength);
+
+ expect(runTruncateString, throwsArgumentError);
+
+ maxLength = -1;
+ expect(runTruncateString, throwsArgumentError);
+ });
+
+ test('truncateStringToLength same string when max length is the same', () {
+ final testString = 'Version 14.1 (Build 23B74)';
+ final maxLength = testString.length;
+
+ String runTruncateString() => truncateStringToLength(testString, maxLength);
+ expect(runTruncateString, returnsNormally);
+
+ final newString = runTruncateString();
+ expect(newString.length, maxLength);
+ expect(newString, testString);
+ });
+}
+
+class _FakeFileStat extends Fake implements FileStat {
+ _FakeFileStat(this.size);
+
+ @override
+ final int size;
+}
+
+class _FakeFile extends Fake implements File {
+ _FakeFile(this.path);
+
+ List<String> Function()? _readAsLinesSyncImpl;
+
+ @override
+ List<String> readAsLinesSync({Encoding encoding = utf8}) =>
+ _readAsLinesSyncImpl!();
+
+ @override
+ final String path;
+
+ FileStat Function()? _statSyncImpl;
+
+ @override
+ FileStat statSync() => _statSyncImpl!();
+
+ void Function()? _deleteSyncImpl;
+
+ @override
+ void deleteSync({bool recursive = false}) => _deleteSyncImpl!();
+
+ void Function()? _createSyncImpl;
+
+ @override
+ void createSync({bool recursive = false, bool exclusive = false}) {
+ return _createSyncImpl!();
+ }
+
+ void Function(String contents, {FileMode mode})? _writeAsStringSync;
+
+ @override
+ void writeAsStringSync(
+ String contents, {
+ FileMode mode = FileMode.write,
+ Encoding encoding = utf8,
+ bool flush = false,
+ }) =>
+ _writeAsStringSync!(contents, mode: mode);
+}
diff --git a/pkgs/unified_analytics/test/no_op_analytics_test.dart b/pkgs/unified_analytics/test/no_op_analytics_test.dart
new file mode 100644
index 0000000..66c1ef6
--- /dev/null
+++ b/pkgs/unified_analytics/test/no_op_analytics_test.dart
@@ -0,0 +1,103 @@
+// Copyright (c) 2023, 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:test/fake.dart';
+import 'package:test/test.dart';
+
+import 'package:unified_analytics/src/utils.dart';
+import 'package:unified_analytics/unified_analytics.dart';
+
+void main() {
+ final testEvent = Event.hotReloadTime(timeMs: 50);
+
+ test('NoOpAnalytics.telemetryEnabled is always false', () async {
+ final analytics = const NoOpAnalytics();
+
+ expect(analytics.telemetryEnabled, isFalse);
+ await analytics.setTelemetry(true);
+ expect(analytics.telemetryEnabled, isFalse);
+ });
+
+ test('NoOpAnalytics.shouldShowMessage is always false', () async {
+ final analytics = const NoOpAnalytics();
+
+ expect(analytics.shouldShowMessage, isFalse);
+ analytics.clientShowedMessage();
+ expect(analytics.shouldShowMessage, isFalse);
+ });
+
+ test('NoOpAnalytics.sendEvent() always returns null', () async {
+ final analytics = const NoOpAnalytics();
+
+ await analytics.setTelemetry(true);
+ analytics.clientShowedMessage();
+ expect(
+ analytics.send(testEvent),
+ isNull,
+ );
+ });
+
+ test('NoOpAnalytics.logFileStats() always returns null', () async {
+ final analytics = const NoOpAnalytics();
+
+ expect(analytics.logFileStats(), isNull);
+
+ await analytics.setTelemetry(true);
+ analytics.clientShowedMessage();
+ await analytics.send(testEvent);
+
+ expect(analytics.logFileStats(), isNull);
+ });
+
+ test('Home directory without write permissions', () {
+ final home = FakeDirectory(writeEnabled: false);
+
+ expect(checkDirectoryForWritePermissions(home), false);
+ });
+
+ test('Home directory with write permissions', () {
+ final home = FakeDirectory(writeEnabled: true);
+
+ expect(checkDirectoryForWritePermissions(home), true);
+ });
+
+ test('Fetching the client id', () {
+ final analytics = const NoOpAnalytics();
+ expect(analytics.clientId, 'xxxx-xxxx');
+ });
+}
+
+class FakeDirectory extends Fake implements Directory {
+ final String _fakeModeString;
+
+ /// This fake directory class allows you to pass the permissions for
+ /// the user level, the group and global permissions will default to
+ /// being denied as indicated by the last 6 characters in the mode string.
+ FakeDirectory({
+ required bool writeEnabled,
+ bool readEnabled = true,
+ bool executeEnabled = true,
+ }) : _fakeModeString = '${readEnabled ? "r" : "-"}'
+ '${writeEnabled ? "w" : "-"}'
+ '${executeEnabled ? "x" : "-"}'
+ '------' {
+ assert(_fakeModeString.length == 9);
+ }
+
+ @override
+ bool existsSync() => true;
+
+ @override
+ FileStat statSync() => FakeFileStat(_fakeModeString);
+}
+
+class FakeFileStat extends Fake implements FileStat {
+ final String _fakeModeString;
+
+ FakeFileStat(this._fakeModeString);
+
+ @override
+ String modeString() => _fakeModeString;
+}
diff --git a/pkgs/unified_analytics/test/suppression_test.dart b/pkgs/unified_analytics/test/suppression_test.dart
new file mode 100644
index 0000000..05fab15
--- /dev/null
+++ b/pkgs/unified_analytics/test/suppression_test.dart
@@ -0,0 +1,132 @@
+// Copyright (c) 2023, 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/memory.dart';
+import 'package:test/test.dart';
+import 'package:unified_analytics/src/enums.dart';
+import 'package:unified_analytics/unified_analytics.dart';
+
+void main() {
+ late MemoryFileSystem fs;
+ late Directory home;
+ late Analytics initializationAnalytics;
+ late Analytics analytics;
+
+ const homeDirName = 'home';
+ const initialTool = DashTool.flutterTool;
+ const toolsMessageVersion = 1;
+ const toolsMessage = 'toolsMessage';
+ const flutterChannel = 'flutterChannel';
+ const flutterVersion = 'flutterVersion';
+ const dartVersion = 'dartVersion';
+ const platform = DevicePlatform.macos;
+
+ final testEvent = Event.hotReloadTime(timeMs: 50);
+
+ setUp(() {
+ // Setup the filesystem with the home directory
+ final fsStyle =
+ io.Platform.isWindows ? FileSystemStyle.windows : FileSystemStyle.posix;
+ fs = MemoryFileSystem.test(style: fsStyle);
+ home = fs.directory(homeDirName);
+
+ // This is the first analytics instance that will be used to demonstrate
+ // that events will not be sent with the first run of analytics
+ initializationAnalytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ );
+ initializationAnalytics.clientShowedMessage();
+
+ // The main analytics instance, other instances can be spawned within tests
+ // to test how to instances running together work
+ //
+ // This instance should have the same parameters as the one above for
+ // [initializationAnalytics]
+ analytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ );
+ analytics.clientShowedMessage();
+ });
+
+ test('Suppression works as expected', () async {
+ analytics.suppressTelemetry();
+ analytics.send(testEvent);
+
+ final logFileStats = analytics.logFileStats();
+
+ expect(logFileStats, isNull,
+ reason: 'Returns null because no records have been recorded');
+ });
+
+ test('Second instance is not suppressed', () async {
+ analytics.suppressTelemetry();
+ analytics.send(testEvent);
+
+ final logFileStats = analytics.logFileStats();
+
+ expect(logFileStats, isNull,
+ reason: 'Returns null because no records have been recorded');
+
+ // The newly created instance will not be suppressed
+ final secondAnalytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ );
+
+ // Using a new event here to differentiate from the first one
+ final newEvent = Event.commandExecuted(count: 2, name: 'commandName');
+ secondAnalytics.send(newEvent);
+
+ // Both instances of `Analytics` should now have data retrieved
+ // from `LogFileStats()` even though only the second instance
+ // was the instance to send an event
+ final secondLogFileStats = analytics.logFileStats()!;
+ final thirdLogFileStats = secondAnalytics.logFileStats()!;
+
+ // Series of checks for each parameter in logFileStats
+ expect(secondLogFileStats.startDateTime, thirdLogFileStats.startDateTime);
+ expect(secondLogFileStats.minsFromStartDateTime,
+ thirdLogFileStats.minsFromStartDateTime);
+ expect(secondLogFileStats.endDateTime, thirdLogFileStats.endDateTime);
+ expect(secondLogFileStats.minsFromEndDateTime,
+ thirdLogFileStats.minsFromEndDateTime);
+ expect(secondLogFileStats.sessionCount, thirdLogFileStats.sessionCount);
+ expect(secondLogFileStats.flutterChannelCount,
+ thirdLogFileStats.flutterChannelCount);
+ expect(secondLogFileStats.toolCount, thirdLogFileStats.toolCount);
+ expect(secondLogFileStats.recordCount, thirdLogFileStats.recordCount);
+ expect(secondLogFileStats.eventCount, thirdLogFileStats.eventCount);
+
+ // Ensure the correct data is in the object
+ expect(secondLogFileStats.eventCount.containsKey(newEvent.eventName.label),
+ true);
+ expect(secondLogFileStats.eventCount[newEvent.eventName.label], 1);
+ });
+}
diff --git a/pkgs/unified_analytics/test/survey_handler_test.dart b/pkgs/unified_analytics/test/survey_handler_test.dart
new file mode 100644
index 0000000..3d8c8dd
--- /dev/null
+++ b/pkgs/unified_analytics/test/survey_handler_test.dart
@@ -0,0 +1,1229 @@
+// Copyright (c) 2023, 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 'package:clock/clock.dart';
+import 'package:file/file.dart';
+import 'package:file/memory.dart';
+import 'package:test/test.dart';
+
+import 'package:unified_analytics/src/constants.dart';
+import 'package:unified_analytics/src/enums.dart';
+import 'package:unified_analytics/src/survey_handler.dart';
+import 'package:unified_analytics/src/utils.dart';
+import 'package:unified_analytics/unified_analytics.dart';
+
+void main() {
+ final testEvent = Event.hotReloadTime(timeMs: 10);
+
+ group('Unit testing function sampleRate:', () {
+ // Set a string that can be used in place of a survey's unique ID
+ final iterations = 1000;
+ final uuid = Uuid(123);
+ final uniqueSurveyId = uuid.generateV4();
+
+ // Set how much the actual sampled rate can be (allowing 5% of variability)
+ final marginOfError = 0.05;
+
+ test('Unit testing the sampleRate method', () {
+ // These strings had a predetermined output from the utility function
+ final string1 = 'string1';
+ final string2 = 'string2';
+ expect(sampleRate(string1, string2), 0.40);
+ });
+
+ test('Simulating with various sample rates', () {
+ final sampleRateToTestList = [
+ 0.10,
+ 0.25,
+ 0.50,
+ 0.75,
+ 0.80,
+ 0.95,
+ ];
+ for (final sampleRateToTest in sampleRateToTestList) {
+ var count = 0;
+ for (var i = 0; i < iterations; i++) {
+ // Regenerate the client id to simulate a unique user
+ final generatedClientId = uuid.generateV4();
+ if (sampleRate(uniqueSurveyId, generatedClientId) <=
+ sampleRateToTest) {
+ count += 1;
+ }
+ }
+
+ final actualSampledRate = count / iterations;
+ final actualMarginOfError =
+ (sampleRateToTest - actualSampledRate).abs();
+
+ expect(actualMarginOfError < marginOfError, true,
+ reason: 'Failed on sample rate = $sampleRateToTest with'
+ ' actual rate $actualMarginOfError '
+ 'and a margin of error = $marginOfError');
+ }
+ });
+ });
+
+ group('Unit testing function checkSurveyDate:', () {
+ final date = DateTime(2023, 5, 1);
+ // Two surveys created, one that is within the survey date
+ // range, and one that is not
+ final validSurvey = Survey(
+ uniqueId: 'uniqueId',
+ startDate: DateTime(2023, 1, 1),
+ endDate: DateTime(2023, 12, 31),
+ description: 'description',
+ snoozeForMinutes: 10,
+ samplingRate: 1.0,
+ excludeDashToolList: [],
+ conditionList: <Condition>[],
+ buttonList: [],
+ );
+ final invalidSurvey = Survey(
+ uniqueId: 'uniqueId',
+ startDate: DateTime(2022, 1, 1),
+ endDate: DateTime(2022, 12, 31),
+ description: 'description',
+ snoozeForMinutes: 10,
+ samplingRate: 1.0,
+ excludeDashToolList: [],
+ conditionList: <Condition>[],
+ buttonList: [],
+ );
+
+ test('expired survey', () {
+ final clock = Clock.fixed(date);
+ withClock(clock, () {
+ expect(SurveyHandler.checkSurveyDate(invalidSurvey), false);
+ });
+ });
+
+ test('valid survey', () {
+ final clock = Clock.fixed(date);
+ withClock(clock, () {
+ expect(SurveyHandler.checkSurveyDate(validSurvey), true);
+ });
+ });
+ });
+
+ group('Unit testing function parseSurveysFromJson', () {
+ final validContents = '''
+[
+ {
+ "uniqueId": "xxxxx",
+ "startDate": "2023-06-01T09:00:00-07:00",
+ "endDate": "2023-06-30T09:00:00-07:00",
+ "description": "xxxxxxx",
+ "snoozeForMinutes": "10",
+ "samplingRate": "1.0",
+ "excludeDashTools": [],
+ "conditions": [
+ {
+ "field": "logFileStats.recordCount",
+ "operator": ">=",
+ "value": 1000
+ },
+ {
+ "field": "logFileStats.toolCount.flutter-tool",
+ "operator": "<",
+ "value": 3
+ }
+ ],
+ "buttons": [
+ {
+ "buttonText": "Take Survey",
+ "action": "accept",
+ "url": "https://google.qualtrics.com/jfe/form/SV_5gsB2EuG324y2",
+ "promptRemainsVisible": false
+ }
+ ]
+ }
+]
+''';
+
+ // The value for the condition is not a valid integer
+ final invalidConditionValueContents = '''
+[
+ {
+ "uniqueId": "xxxxx",
+ "startDate": "2023-06-01T09:00:00-07:00",
+ "endDate": "2023-06-30T09:00:00-07:00",
+ "description": "xxxxxxx",
+ "snoozeForMinutes": "10",
+ "samplingRate": "1.0",
+ "excludeDashTools": [],
+ "conditions": [
+ {
+ "field": "logFileStats.recordCount",
+ "operator": ">=",
+ "value": "1000xxxx"
+ }
+ ],
+ "buttons": [
+ {
+ "buttonText": "Take Survey",
+ "action": "accept",
+ "url": "https://google.qualtrics.com/jfe/form/SV_5gsB2EuG324y2",
+ "promptRemainsVisible": false
+ }
+ ]
+ }
+]
+''';
+
+ // Using a dash tool in the excludeDashTools array that is not a valid
+ // DashTool label
+ final invalidDashToolContents = '''
+[
+ {
+ "uniqueId": "xxxxx",
+ "startDate": "2023-06-01T09:00:00-07:00",
+ "endDate": "2023-06-30T09:00:00-07:00",
+ "description": "xxxxxxx",
+ "snoozeForMinutes": "10",
+ "samplingRate": "1.0",
+ "excludeDashTools": [
+ "not-a-valid-dash-tool"
+ ],
+ "conditions": [
+ {
+ "field": "logFileStats.recordCount",
+ "operator": ">=",
+ "value": 1000
+ },
+ {
+ "field": "logFileStats.toolCount.flutter-tool",
+ "operator": "<",
+ "value": 3
+ }
+ ],
+ "buttons": [
+ {
+ "buttonText": "Take Survey",
+ "action": "accept",
+ "url": "https://google.qualtrics.com/jfe/form/SV_5gsB2EuG324y2",
+ "promptRemainsVisible": false
+ }
+ ]
+ }
+]
+''';
+
+ test('valid json', () {
+ withClock(Clock.fixed(DateTime(2023, 6, 15)), () {
+ final parsedSurveys = SurveyHandler.parseSurveysFromJson(
+ jsonDecode(validContents) as List);
+
+ expect(parsedSurveys.length, 1);
+ expect(parsedSurveys.first.conditionList.length, 2);
+
+ final firstCondition = parsedSurveys.first.conditionList.first;
+ final secondCondition = parsedSurveys.first.conditionList[1];
+
+ expect(firstCondition.field, 'logFileStats.recordCount');
+ expect(firstCondition.operatorString, '>=');
+ expect(firstCondition.value, 1000);
+
+ expect(secondCondition.field, 'logFileStats.toolCount.flutter-tool');
+ expect(secondCondition.operatorString, '<');
+ expect(secondCondition.value, 3);
+
+ expect(parsedSurveys.first.buttonList.length, 1);
+ expect(
+ parsedSurveys.first.buttonList.first.promptRemainsVisible, false);
+ });
+ });
+
+ test('invalid condition json', () {
+ withClock(Clock.fixed(DateTime(2023, 6, 15)), () {
+ final parsedSurveys = SurveyHandler.parseSurveysFromJson(
+ jsonDecode(invalidConditionValueContents) as List);
+
+ expect(parsedSurveys.length, 0,
+ reason: 'The condition value is not a '
+ 'proper integer so it should error returning no surveys');
+ });
+ });
+
+ test('invalid dash tool json', () {
+ withClock(Clock.fixed(DateTime(2023, 6, 15)), () {
+ final parsedSurveys = SurveyHandler.parseSurveysFromJson(
+ jsonDecode(invalidDashToolContents) as List);
+
+ expect(parsedSurveys.length, 0,
+ reason: 'The dash tool in the exclude array is not valid '
+ 'so it should error returning no surveys');
+ });
+ });
+ });
+
+ group('Testing with FakeSurveyHandler', () {
+ late Analytics analytics;
+ late Directory homeDirectory;
+ late MemoryFileSystem fs;
+ late File clientIdFile;
+ late File dismissedSurveyFile;
+
+ setUp(() {
+ fs = MemoryFileSystem.test(style: FileSystemStyle.posix);
+ homeDirectory = fs.directory('home');
+
+ // Write the client ID file out so that we don't get
+ // a randomly assigned id for this test generated within
+ // the analytics constructor
+ clientIdFile = homeDirectory
+ .childDirectory(kDartToolDirectoryName)
+ .childFile(kClientIdFileName);
+ clientIdFile.createSync(recursive: true);
+ clientIdFile.writeAsStringSync('string1');
+
+ // Assign the json file that will hold the persisted surveys
+ dismissedSurveyFile = homeDirectory
+ .childDirectory(kDartToolDirectoryName)
+ .childFile(kDismissedSurveyFileName);
+
+ // Setup two tools to be onboarded with this package so
+ // that we can simulate two different tools interacting with
+ // surveys
+ //
+ // This is especially useful when testing the "excludeDashTools" array
+ // to prevent certain tools from getting a survey from this package
+ final initialAnalyticsFlutter = Analytics.fake(
+ tool: DashTool.flutterTool,
+ homeDirectory: homeDirectory,
+ dartVersion: 'dartVersion',
+ fs: fs,
+ platform: DevicePlatform.macos,
+ );
+ final initialAnalyticsDart = Analytics.fake(
+ tool: DashTool.dartTool,
+ homeDirectory: homeDirectory,
+ dartVersion: 'dartVersion',
+ fs: fs,
+ platform: DevicePlatform.macos,
+ );
+ initialAnalyticsFlutter.clientShowedMessage();
+ initialAnalyticsDart.clientShowedMessage();
+ });
+
+ test('returns valid survey', () async {
+ await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async {
+ analytics = Analytics.fake(
+ tool: DashTool.flutterTool,
+ homeDirectory: homeDirectory,
+ dartVersion: 'dartVersion',
+ fs: fs,
+ platform: DevicePlatform.macos,
+ surveyHandler: FakeSurveyHandler.fromList(
+ dismissedSurveyFile: dismissedSurveyFile,
+ initializedSurveys: <Survey>[
+ Survey(
+ uniqueId: 'uniqueId',
+ startDate: DateTime(2023, 1, 1),
+ endDate: DateTime(2023, 12, 31),
+ description: 'description',
+ snoozeForMinutes: 10,
+ samplingRate: 1.0,
+ excludeDashToolList: [],
+ conditionList: <Condition>[
+ Condition('logFileStats.recordCount', '>=', 50),
+ Condition('logFileStats.toolCount.flutter-tool', '>', 0),
+ ],
+ buttonList: [
+ SurveyButton(
+ buttonText: 'buttonText',
+ action: 'accept',
+ url: 'http://example.com',
+ promptRemainsVisible: false,
+ ),
+ ],
+ ),
+ ],
+ ),
+ );
+
+ // Simulate 60 events to send so that the first condition is satisified
+ for (var i = 0; i < 60; i++) {
+ analytics.send(testEvent);
+ }
+
+ final fetchedSurveys = await analytics.fetchAvailableSurveys();
+
+ expect(fetchedSurveys.length, 1);
+
+ final survey = fetchedSurveys.first;
+ expect(survey.conditionList.length, 2);
+ expect(survey.buttonList.length, 1);
+ });
+ });
+
+ test('does not return expired survey', () async {
+ await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async {
+ analytics = Analytics.fake(
+ tool: DashTool.flutterTool,
+ homeDirectory: homeDirectory,
+ dartVersion: 'dartVersion',
+ fs: fs,
+ platform: DevicePlatform.macos,
+ surveyHandler: FakeSurveyHandler.fromList(
+ dismissedSurveyFile: dismissedSurveyFile,
+ initializedSurveys: <Survey>[
+ Survey(
+ uniqueId: 'uniqueId',
+ startDate: DateTime(2022, 1, 1),
+ endDate: DateTime(2022, 12, 31),
+ description: 'description',
+ snoozeForMinutes: 10,
+ samplingRate: 1.0,
+ excludeDashToolList: [],
+ conditionList: <Condition>[
+ Condition('logFileStats.recordCount', '>=', 50),
+ Condition('logFileStats.toolCount.flutter-tool', '>', 0),
+ ],
+ buttonList: [],
+ ),
+ ],
+ ),
+ );
+
+ // Simulate 60 events to send so that the first condition is satisified
+ for (var i = 0; i < 60; i++) {
+ analytics.send(testEvent);
+ }
+
+ final fetchedSurveys = await analytics.fetchAvailableSurveys();
+
+ expect(fetchedSurveys.length, 0);
+ });
+ });
+
+ test('does not return survey if opted out of telemetry', () async {
+ await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async {
+ analytics = Analytics.fake(
+ tool: DashTool.flutterTool,
+ homeDirectory: homeDirectory,
+ dartVersion: 'dartVersion',
+ fs: fs,
+ platform: DevicePlatform.macos,
+ surveyHandler: FakeSurveyHandler.fromList(
+ dismissedSurveyFile: dismissedSurveyFile,
+ initializedSurveys: <Survey>[
+ Survey(
+ uniqueId: 'uniqueId',
+ startDate: DateTime(2023, 1, 1),
+ endDate: DateTime(2023, 12, 31),
+ description: 'description',
+ snoozeForMinutes: 10,
+ samplingRate: 1.0,
+ excludeDashToolList: [],
+ conditionList: <Condition>[
+ Condition('logFileStats.recordCount', '>=', 50),
+ Condition('logFileStats.toolCount.flutter-tool', '>', 0),
+ ],
+ buttonList: [],
+ ),
+ ],
+ ),
+ );
+
+ // Simulate 60 events to send so that the first condition is satisified
+ for (var i = 0; i < 60; i++) {
+ analytics.send(testEvent);
+ }
+
+ await analytics.setTelemetry(false);
+ expect(analytics.okToSend, false);
+
+ final fetchedSurveys = await analytics.fetchAvailableSurveys();
+
+ expect(fetchedSurveys.length, 0);
+ });
+ });
+
+ test('returns valid survey from json', () async {
+ await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async {
+ analytics = Analytics.fake(
+ tool: DashTool.flutterTool,
+ homeDirectory: homeDirectory,
+ dartVersion: 'dartVersion',
+ fs: fs,
+ platform: DevicePlatform.macos,
+ surveyHandler: FakeSurveyHandler.fromString(
+ dismissedSurveyFile: dismissedSurveyFile, content: '''
+[
+ {
+ "uniqueId": "uniqueId123",
+ "startDate": "2023-01-01T09:00:00-07:00",
+ "endDate": "2023-12-31T09:00:00-07:00",
+ "description": "description123",
+ "snoozeForMinutes": "10",
+ "samplingRate": "1.0",
+ "excludeDashTools": [],
+ "conditions": [
+ {
+ "field": "logFileStats.recordCount",
+ "operator": ">=",
+ "value": 50
+ }
+ ],
+ "buttons": [
+ {
+ "buttonText": "Take Survey",
+ "action": "accept",
+ "url": "https://google.qualtrics.com/jfe/form/SV_5gsB2EuG324y2",
+ "promptRemainsVisible": false
+ },
+ {
+ "buttonText": "Dismiss",
+ "action": "dismiss",
+ "url": null,
+ "promptRemainsVisible": false
+ },
+ {
+ "buttonText": "More Info",
+ "action": "snooze",
+ "url": "https://docs.flutter.dev/reference/crash-reporting",
+ "promptRemainsVisible": true
+ }
+ ]
+ }
+]
+'''),
+ );
+
+ // Simulate 60 events to send so that the first condition is satisified
+ for (var i = 0; i < 60; i++) {
+ analytics.send(testEvent);
+ }
+
+ final fetchedSurveys = await analytics.fetchAvailableSurveys();
+
+ expect(fetchedSurveys.length, 1);
+
+ final survey = fetchedSurveys.first;
+ expect(survey.uniqueId, 'uniqueId123');
+ expect(survey.startDate.year, 2023);
+ expect(survey.startDate.month, 1);
+ expect(survey.startDate.day, 1);
+ expect(survey.endDate.year, 2023);
+ expect(survey.endDate.month, 12);
+ expect(survey.endDate.day, 31);
+ expect(survey.description, 'description123');
+ expect(survey.snoozeForMinutes, 10);
+ expect(survey.samplingRate, 1.0);
+ expect(survey.conditionList.length, 1);
+
+ final condition = survey.conditionList.first;
+ expect(condition.field, 'logFileStats.recordCount');
+ expect(condition.operatorString, '>=');
+ expect(condition.value, 50);
+
+ final buttonList = survey.buttonList;
+ expect(buttonList.length, 3);
+ expect(buttonList.first.buttonText, 'Take Survey');
+ expect(buttonList.first.action, 'accept');
+ expect(buttonList.first.url,
+ 'https://google.qualtrics.com/jfe/form/SV_5gsB2EuG324y2');
+ expect(buttonList.first.promptRemainsVisible, false);
+
+ expect(buttonList.elementAt(1).buttonText, 'Dismiss');
+ expect(buttonList.elementAt(1).action, 'dismiss');
+ expect(buttonList.elementAt(1).url, isNull);
+ expect(buttonList.elementAt(1).promptRemainsVisible, false);
+
+ expect(buttonList.last.buttonText, 'More Info');
+ expect(buttonList.last.action, 'snooze');
+ expect(buttonList.last.url,
+ 'https://docs.flutter.dev/reference/crash-reporting');
+ expect(buttonList.last.promptRemainsVisible, true);
+ });
+ });
+
+ test('no survey returned from malformed json', () async {
+ // The date is not valid for the start date
+ await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async {
+ analytics = Analytics.fake(
+ tool: DashTool.flutterTool,
+ homeDirectory: homeDirectory,
+ dartVersion: 'dartVersion',
+ fs: fs,
+ platform: DevicePlatform.macos,
+ surveyHandler: FakeSurveyHandler.fromString(
+ dismissedSurveyFile: dismissedSurveyFile, content: '''
+[
+ {
+ "uniqueId": "uniqueId123",
+ "startDate": "NOT A REAL DATE",
+ "endDate": "2023-07-30T09:00:00-07:00",
+ "description": "Help improve Flutter's release builds with this 3-question survey!",
+ "snoozeForMinutes": "7200",
+ "samplingRate": "0.1",
+ "excludeDashTools": [],
+ "conditions": [
+ {
+ "field": "logFileStats.recordCount",
+ "operator": ">=",
+ "value": 50
+ }
+ ],
+ "buttons": [
+ {
+ "buttonText": "Take Survey",
+ "action": "accept",
+ "url": "https://google.qualtrics.com/jfe/form/SV_5gsB2EuG5Et5Yy2",
+ "promptRemainsVisible": false
+ },
+ {
+ "buttonText": "Dismiss",
+ "action": "dismiss",
+ "url": null,
+ "promptRemainsVisible": false
+ },
+ {
+ "buttonText": "More Info",
+ "action": "snooze",
+ "url": "https://docs.flutter.dev/reference/crash-reporting",
+ "promptRemainsVisible": false
+ }
+ ]
+ }
+]
+'''),
+ );
+
+ // Simulate 60 events to send so that the first condition is satisified
+ for (var i = 0; i < 60; i++) {
+ analytics.send(testEvent);
+ }
+
+ final fetchedSurveys = await analytics.fetchAvailableSurveys();
+
+ expect(fetchedSurveys.length, 0);
+ });
+ });
+
+ test('returns two valid survey from json', () async {
+ await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async {
+ analytics = Analytics.fake(
+ tool: DashTool.flutterTool,
+ homeDirectory: homeDirectory,
+ dartVersion: 'dartVersion',
+ fs: fs,
+ platform: DevicePlatform.macos,
+ surveyHandler: FakeSurveyHandler.fromString(
+ dismissedSurveyFile: dismissedSurveyFile, content: '''
+[
+ {
+ "uniqueId": "12345",
+ "startDate": "2023-01-01T09:00:00-07:00",
+ "endDate": "2023-12-31T09:00:00-07:00",
+ "description": "xxxxxxx",
+ "snoozeForMinutes": "10",
+ "samplingRate": "1.0",
+ "excludeDashTools": [],
+ "conditions": [
+ {
+ "field": "logFileStats.recordCount",
+ "operator": ">=",
+ "value": 50
+ }
+ ],
+ "buttons": []
+ },
+ {
+ "uniqueId": "67890",
+ "startDate": "2023-01-01T09:00:00-07:00",
+ "endDate": "2023-12-31T09:00:00-07:00",
+ "description": "xxxxxxx",
+ "snoozeForMinutes": "10",
+ "samplingRate": "1.0",
+ "excludeDashTools": [],
+ "conditions": [
+ {
+ "field": "logFileStats.recordCount",
+ "operator": ">=",
+ "value": 50
+ }
+ ],
+ "buttons": [
+ {
+ "buttonText": "More Info",
+ "action": "snooze",
+ "url": "https://docs.flutter.dev/reference/crash-reporting",
+ "promptRemainsVisible": true
+ }
+ ]
+ }
+]
+'''),
+ );
+
+ // Simulate 60 events to send so that the first condition is satisified
+ for (var i = 0; i < 60; i++) {
+ analytics.send(testEvent);
+ }
+
+ final fetchedSurveys = await analytics.fetchAvailableSurveys();
+
+ expect(fetchedSurveys.length, 2);
+
+ final firstSurvey = fetchedSurveys.first;
+ final secondSurvey = fetchedSurveys.last;
+
+ expect(firstSurvey.uniqueId, '12345');
+ expect(secondSurvey.uniqueId, '67890');
+
+ final secondSurveyButtons = secondSurvey.buttonList;
+ expect(secondSurveyButtons.length, 1);
+ expect(secondSurveyButtons.first.buttonText, 'More Info');
+ expect(secondSurveyButtons.first.action, 'snooze');
+ expect(secondSurveyButtons.first.url,
+ 'https://docs.flutter.dev/reference/crash-reporting');
+ expect(secondSurveyButtons.first.promptRemainsVisible, true);
+ });
+ });
+
+ test('valid survey not returned if opted out', () async {
+ await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async {
+ analytics = Analytics.fake(
+ tool: DashTool.flutterTool,
+ homeDirectory: homeDirectory,
+ dartVersion: 'dartVersion',
+ fs: fs,
+ platform: DevicePlatform.macos,
+ surveyHandler: FakeSurveyHandler.fromList(
+ dismissedSurveyFile: dismissedSurveyFile,
+ initializedSurveys: <Survey>[
+ Survey(
+ uniqueId: 'uniqueId',
+ startDate: DateTime(2023, 1, 1),
+ endDate: DateTime(2023, 12, 31),
+ description: 'description',
+ snoozeForMinutes: 10,
+ samplingRate: 1.0,
+ excludeDashToolList: [],
+ conditionList: <Condition>[
+ Condition('logFileStats.recordCount', '>=', 50),
+ Condition('logFileStats.toolCount.flutter-tool', '>', 0),
+ ],
+ buttonList: [],
+ ),
+ ],
+ ),
+ );
+
+ // Simulate 60 events to send so that the first condition is satisified
+ for (var i = 0; i < 60; i++) {
+ analytics.send(testEvent);
+ }
+
+ // Setting to false will prevent anything from getting returned
+ await analytics.setTelemetry(false);
+ var fetchedSurveys = await analytics.fetchAvailableSurveys();
+ expect(fetchedSurveys.length, 0);
+
+ // Setting telemetry back to true should enable the surveys to get
+ // returned again; we will also need to send the fake events again
+ // because on opt out, the log file will get cleared and one of
+ // the conditions for the fake survey loaded is that we need
+ // at least 50 records for one of the conditions
+ await analytics.setTelemetry(true);
+ for (var i = 0; i < 60; i++) {
+ analytics.send(testEvent);
+ }
+ fetchedSurveys = await analytics.fetchAvailableSurveys();
+ expect(fetchedSurveys.length, 1);
+ });
+ });
+
+ test('Sampling rate correctly returns a valid survey', () async {
+ // This test will use a predefined client ID string of `string1`
+ // which has been set in the setup along with a predefined
+ // string for the survey ID of `string2` to get a sample rate value
+ //
+ // The combination of `string1` and `string2` will return 0.40
+ // from the sampleRate utility function so we have set the threshold
+ // to be 0.6 which should return surveys
+ await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async {
+ final survey = Survey(
+ uniqueId: 'string2',
+ startDate: DateTime(2023, 1, 1),
+ endDate: DateTime(2023, 12, 31),
+ description: 'description',
+ snoozeForMinutes: 10,
+ samplingRate: 0.6,
+ excludeDashToolList: [],
+ conditionList: <Condition>[
+ Condition('logFileStats.recordCount', '>=', 50),
+ Condition('logFileStats.toolCount.flutter-tool', '>', 0),
+ ],
+ buttonList: [],
+ );
+ analytics = Analytics.fake(
+ tool: DashTool.flutterTool,
+ homeDirectory: homeDirectory,
+ dartVersion: 'dartVersion',
+ fs: fs,
+ platform: DevicePlatform.macos,
+ surveyHandler: FakeSurveyHandler.fromList(
+ dismissedSurveyFile: dismissedSurveyFile,
+ initializedSurveys: <Survey>[survey],
+ ),
+ );
+
+ // Simulate 60 events to send so that the first condition is satisified
+ for (var i = 0; i < 60; i++) {
+ analytics.send(testEvent);
+ }
+
+ final fetchedSurveys = await analytics.fetchAvailableSurveys();
+
+ expect(survey.samplingRate, 0.6);
+ expect(fetchedSurveys.length, 1);
+ });
+ });
+
+ test('Sampling rate filters out a survey', () async {
+ // We will reduce the survey's sampling rate to be 0.3 which is
+ // less than value returned from the predefined client ID and
+ // survey sample
+ await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async {
+ final survey = Survey(
+ uniqueId: 'string2',
+ startDate: DateTime(2023, 1, 1),
+ endDate: DateTime(2023, 12, 31),
+ description: 'description',
+ snoozeForMinutes: 10,
+ samplingRate: 0.15,
+ excludeDashToolList: [],
+ conditionList: <Condition>[
+ Condition('logFileStats.recordCount', '>=', 50),
+ Condition('logFileStats.toolCount.flutter-tool', '>', 0),
+ ],
+ buttonList: [],
+ );
+ analytics = Analytics.fake(
+ tool: DashTool.flutterTool,
+ homeDirectory: homeDirectory,
+ dartVersion: 'dartVersion',
+ fs: fs,
+ platform: DevicePlatform.macos,
+ surveyHandler: FakeSurveyHandler.fromList(
+ dismissedSurveyFile: dismissedSurveyFile,
+ initializedSurveys: <Survey>[survey],
+ ),
+ );
+
+ // Simulate 60 events to send so that the first condition is satisified
+ for (var i = 0; i < 60; i++) {
+ analytics.send(testEvent);
+ }
+
+ final fetchedSurveys = await analytics.fetchAvailableSurveys();
+
+ expect(survey.samplingRate, 0.15);
+ expect(fetchedSurveys.length, 0);
+ });
+ });
+
+ test('Snoozing survey is successful with snooze timeout from survey',
+ () async {
+ expect(dismissedSurveyFile.readAsStringSync(), '{}',
+ reason: 'Should be an empty object');
+
+ // Initialize the survey class that we will use for this test
+ final minutesToSnooze = 30;
+ final surveyToLoad = Survey(
+ uniqueId: 'uniqueId',
+ startDate: DateTime(2023, 1, 1),
+ endDate: DateTime(2023, 12, 31),
+ description: 'description',
+ snoozeForMinutes:
+ minutesToSnooze, // Initialized survey with `minutesToSnooze`
+ samplingRate: 1.0,
+ excludeDashToolList: [],
+ conditionList: <Condition>[],
+ buttonList: [],
+ );
+
+ await withClock(Clock.fixed(DateTime(2023, 3, 3, 12, 0)), () async {
+ analytics = Analytics.fake(
+ tool: DashTool.flutterTool,
+ homeDirectory: homeDirectory,
+ dartVersion: 'dartVersion',
+ fs: fs,
+ platform: DevicePlatform.macos,
+ surveyHandler: FakeSurveyHandler.fromList(
+ dismissedSurveyFile: dismissedSurveyFile,
+ initializedSurveys: <Survey>[surveyToLoad],
+ ),
+ );
+
+ final fetchedSurveys = await analytics.fetchAvailableSurveys();
+ expect(fetchedSurveys.length, 1);
+
+ final survey = fetchedSurveys.first;
+ expect(survey.snoozeForMinutes, minutesToSnooze);
+
+ // We will snooze the survey now and it should not show up
+ // if we fetch surveys again before the minutes to snooze time
+ // has finished
+ analytics.surveyShown(survey);
+ });
+
+ // This analytics instance will be simulated to be shortly after the first
+ // snooze, but before the snooze period has elapsed
+ await withClock(Clock.fixed(DateTime(2023, 3, 3, 12, 15)), () async {
+ analytics = Analytics.fake(
+ tool: DashTool.flutterTool,
+ homeDirectory: homeDirectory,
+ dartVersion: 'dartVersion',
+ fs: fs,
+ platform: DevicePlatform.macos,
+ surveyHandler: FakeSurveyHandler.fromList(
+ dismissedSurveyFile: dismissedSurveyFile,
+ initializedSurveys: <Survey>[surveyToLoad],
+ ),
+ );
+
+ final fetchedSurveys = await analytics.fetchAvailableSurveys();
+ expect(fetchedSurveys.length, 0,
+ reason: 'The snooze period has not elapsed yet');
+ });
+
+ // This analytics instance will be simulated to be after the snooze period
+ await withClock(Clock.fixed(DateTime(2023, 3, 3, 12, 35)), () async {
+ analytics = Analytics.fake(
+ tool: DashTool.flutterTool,
+ homeDirectory: homeDirectory,
+ dartVersion: 'dartVersion',
+ fs: fs,
+ platform: DevicePlatform.macos,
+ surveyHandler: FakeSurveyHandler.fromList(
+ dismissedSurveyFile: dismissedSurveyFile,
+ initializedSurveys: <Survey>[surveyToLoad],
+ ),
+ );
+
+ final fetchedSurveys = await analytics.fetchAvailableSurveys();
+ expect(fetchedSurveys.length, 1,
+ reason: 'The snooze period has elapsed');
+ });
+ });
+
+ test('Dimissing permanently is successful', () async {
+ final minutesToSnooze = 10;
+ final surveyToLoad = Survey(
+ uniqueId: 'uniqueId',
+ startDate: DateTime(2023, 1, 1),
+ endDate: DateTime(2023, 12, 31),
+ description: 'description',
+ snoozeForMinutes: minutesToSnooze,
+ samplingRate: 1.0,
+ excludeDashToolList: [],
+ conditionList: <Condition>[],
+ buttonList: [
+ SurveyButton(
+ buttonText: 'buttonText',
+ action: 'accept',
+ promptRemainsVisible: false,
+ ),
+ SurveyButton(
+ buttonText: 'buttonText',
+ action: 'dismiss',
+ promptRemainsVisible: false,
+ ),
+ ],
+ );
+
+ await withClock(Clock.fixed(DateTime(2023, 3, 3, 12, 0)), () async {
+ analytics = Analytics.fake(
+ tool: DashTool.flutterTool,
+ homeDirectory: homeDirectory,
+ dartVersion: 'dartVersion',
+ fs: fs,
+ platform: DevicePlatform.macos,
+ surveyHandler: FakeSurveyHandler.fromList(
+ dismissedSurveyFile: dismissedSurveyFile,
+ initializedSurveys: <Survey>[surveyToLoad],
+ ),
+ );
+
+ final fetchedSurveys = await analytics.fetchAvailableSurveys();
+ expect(fetchedSurveys.length, 1);
+
+ // Dismissing permanently will ensure that this survey is not
+ // shown again
+ final survey = fetchedSurveys.first;
+ analytics.surveyInteracted(
+ survey: survey,
+ surveyButton: survey.buttonList.first,
+ );
+ });
+
+ // Moving out a week
+ await withClock(Clock.fixed(DateTime(2023, 3, 10, 12, 0)), () async {
+ analytics = Analytics.fake(
+ tool: DashTool.flutterTool,
+ homeDirectory: homeDirectory,
+ dartVersion: 'dartVersion',
+ fs: fs,
+ platform: DevicePlatform.macos,
+ surveyHandler: FakeSurveyHandler.fromList(
+ dismissedSurveyFile: dismissedSurveyFile,
+ initializedSurveys: <Survey>[surveyToLoad],
+ ),
+ );
+
+ final fetchedSurveys = await analytics.fetchAvailableSurveys();
+ expect(fetchedSurveys.length, 0);
+ });
+ });
+
+ test('malformed persisted json file for surveys', () async {
+ // When the survey handler encounters an error when parsing the
+ // persisted json file, it will reset it using the static method
+ // under the [Initializer] class and reset it to be an empty json object
+ final minutesToSnooze = 10;
+ final surveyToLoad = Survey(
+ uniqueId: 'uniqueId',
+ startDate: DateTime(2023, 1, 1),
+ endDate: DateTime(2023, 12, 31),
+ description: 'description',
+ snoozeForMinutes: minutesToSnooze,
+ samplingRate: 1.0,
+ excludeDashToolList: [],
+ conditionList: <Condition>[],
+ buttonList: [
+ SurveyButton(
+ buttonText: 'buttonText',
+ action: 'accept',
+ promptRemainsVisible: false,
+ ),
+ SurveyButton(
+ buttonText: 'buttonText',
+ action: 'dismiss',
+ promptRemainsVisible: false,
+ ),
+ ],
+ );
+
+ await withClock(Clock.fixed(DateTime(2023, 3, 3, 12, 0)), () async {
+ analytics = Analytics.fake(
+ tool: DashTool.flutterTool,
+ homeDirectory: homeDirectory,
+ dartVersion: 'dartVersion',
+ fs: fs,
+ platform: DevicePlatform.macos,
+ surveyHandler: FakeSurveyHandler.fromList(
+ dismissedSurveyFile: dismissedSurveyFile,
+ initializedSurveys: <Survey>[surveyToLoad],
+ ),
+ );
+
+ final fetchedSurveys = await analytics.fetchAvailableSurveys();
+ expect(fetchedSurveys.length, 1);
+
+ // Dismissing permanently will ensure that this survey is not
+ // shown again
+ final survey = fetchedSurveys.first;
+ expect(survey.buttonList.length, 2);
+ analytics.surveyInteracted(
+ survey: survey,
+ surveyButton: survey.buttonList.first,
+ );
+ });
+
+ // Purposefully write invalid json into the persisted file
+ dismissedSurveyFile.writeAsStringSync('{');
+
+ // Moving out a week
+ await withClock(Clock.fixed(DateTime(2023, 3, 10, 12, 0)), () async {
+ analytics = Analytics.fake(
+ tool: DashTool.flutterTool,
+ homeDirectory: homeDirectory,
+ dartVersion: 'dartVersion',
+ fs: fs,
+ platform: DevicePlatform.macos,
+ surveyHandler: FakeSurveyHandler.fromList(
+ dismissedSurveyFile: dismissedSurveyFile,
+ initializedSurveys: <Survey>[surveyToLoad],
+ ),
+ );
+
+ final fetchedSurveys = await analytics.fetchAvailableSurveys();
+ expect(fetchedSurveys.length, 1);
+ });
+ });
+
+ test('persisted json file goes missing handled', () async {
+ // If the persisted json file with the dismissed surveys is missing
+ // there should be error handling to recreate the file again with
+ // an empty json object
+ final minutesToSnooze = 10;
+ final surveyToLoad = Survey(
+ uniqueId: 'uniqueId',
+ startDate: DateTime(2023, 1, 1),
+ endDate: DateTime(2023, 12, 31),
+ description: 'description',
+ snoozeForMinutes: minutesToSnooze,
+ samplingRate: 1.0,
+ excludeDashToolList: [],
+ conditionList: <Condition>[],
+ buttonList: [
+ SurveyButton(
+ buttonText: 'buttonText',
+ action: 'accept',
+ promptRemainsVisible: false,
+ ),
+ SurveyButton(
+ buttonText: 'buttonText',
+ action: 'dismiss',
+ promptRemainsVisible: false,
+ ),
+ ],
+ );
+
+ await withClock(Clock.fixed(DateTime(2023, 3, 3, 12, 0)), () async {
+ analytics = Analytics.fake(
+ tool: DashTool.flutterTool,
+ homeDirectory: homeDirectory,
+ dartVersion: 'dartVersion',
+ fs: fs,
+ platform: DevicePlatform.macos,
+ surveyHandler: FakeSurveyHandler.fromList(
+ dismissedSurveyFile: dismissedSurveyFile,
+ initializedSurveys: <Survey>[surveyToLoad],
+ ),
+ );
+
+ final fetchedSurveys = await analytics.fetchAvailableSurveys();
+ expect(fetchedSurveys.length, 1);
+
+ // Dismissing permanently will ensure that this survey is not
+ // shown again
+ final survey = fetchedSurveys.first;
+ expect(survey.buttonList.length, 2);
+ analytics.surveyInteracted(
+ survey: survey,
+ surveyButton: survey.buttonList.first,
+ );
+ });
+
+ // Moving out a week
+ await withClock(Clock.fixed(DateTime(2023, 3, 10, 12, 0)), () async {
+ analytics = Analytics.fake(
+ tool: DashTool.flutterTool,
+ homeDirectory: homeDirectory,
+ dartVersion: 'dartVersion',
+ fs: fs,
+ platform: DevicePlatform.macos,
+ surveyHandler: FakeSurveyHandler.fromList(
+ dismissedSurveyFile: dismissedSurveyFile,
+ initializedSurveys: <Survey>[surveyToLoad],
+ ),
+ );
+
+ // Purposefully delete the file
+ dismissedSurveyFile.deleteSync();
+
+ final fetchedSurveys = await analytics.fetchAvailableSurveys();
+ expect(fetchedSurveys.length, 1);
+ });
+ });
+
+ test('Filtering out with excludeDashTool array', () async {
+ await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async {
+ analytics = Analytics.fake(
+ tool: DashTool.flutterTool,
+ homeDirectory: homeDirectory,
+ dartVersion: 'dartVersion',
+ fs: fs,
+ platform: DevicePlatform.macos,
+ surveyHandler: FakeSurveyHandler.fromList(
+ dismissedSurveyFile: dismissedSurveyFile,
+ initializedSurveys: <Survey>[
+ Survey(
+ uniqueId: 'uniqueId',
+ startDate: DateTime(2023, 1, 1),
+ endDate: DateTime(2023, 12, 31),
+ description: 'description',
+ snoozeForMinutes: 10,
+ samplingRate: 1.0,
+ // This should be the same as the tool in the
+ // Analytics constructor above
+ excludeDashToolList: [
+ DashTool.flutterTool,
+ ],
+ conditionList: [],
+ buttonList: [
+ SurveyButton(
+ buttonText: 'buttonText',
+ action: 'accept',
+ url: 'http://example.com',
+ promptRemainsVisible: false,
+ ),
+ ],
+ ),
+ ],
+ ),
+ );
+
+ final fetchedSurveys = await analytics.fetchAvailableSurveys();
+
+ expect(fetchedSurveys.length, 0);
+ });
+ });
+
+ test(
+ 'Filter from excludeDashTool array does not '
+ 'apply for different tool', () async {
+ await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async {
+ analytics = Analytics.fake(
+ tool: DashTool.flutterTool,
+ homeDirectory: homeDirectory,
+ dartVersion: 'dartVersion',
+ fs: fs,
+ platform: DevicePlatform.macos,
+ surveyHandler: FakeSurveyHandler.fromList(
+ dismissedSurveyFile: dismissedSurveyFile,
+ initializedSurveys: <Survey>[
+ Survey(
+ uniqueId: 'uniqueId',
+ startDate: DateTime(2023, 1, 1),
+ endDate: DateTime(2023, 12, 31),
+ description: 'description',
+ snoozeForMinutes: 10,
+ samplingRate: 1.0,
+ // This should be different from the tool in the
+ // Analytics constructor above
+ excludeDashToolList: [
+ DashTool.devtools,
+ ],
+ conditionList: [],
+ buttonList: [
+ SurveyButton(
+ buttonText: 'buttonText',
+ action: 'accept',
+ url: 'http://example.com',
+ promptRemainsVisible: false,
+ ),
+ ],
+ ),
+ ],
+ ),
+ );
+
+ final fetchedSurveys = await analytics.fetchAvailableSurveys();
+
+ expect(fetchedSurveys.length, 1);
+
+ final survey = fetchedSurveys.first;
+ expect(survey.excludeDashToolList.length, 1);
+ expect(survey.excludeDashToolList.contains(DashTool.devtools), true);
+ });
+ });
+ });
+}
diff --git a/pkgs/unified_analytics/test/unified_analytics_test.dart b/pkgs/unified_analytics/test/unified_analytics_test.dart
new file mode 100644
index 0000000..2e78df4
--- /dev/null
+++ b/pkgs/unified_analytics/test/unified_analytics_test.dart
@@ -0,0 +1,1275 @@
+// Copyright (c) 2023, 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: lines_longer_than_80_chars, avoid_dynamic_calls
+
+import 'dart:convert';
+import 'dart:io' as io;
+import 'dart:math';
+
+import 'package:clock/clock.dart';
+import 'package:file/file.dart';
+import 'package:file/memory.dart';
+import 'package:test/test.dart';
+import 'package:unified_analytics/src/constants.dart';
+import 'package:unified_analytics/src/enums.dart';
+import 'package:unified_analytics/src/utils.dart';
+import 'package:unified_analytics/unified_analytics.dart';
+import 'package:yaml/yaml.dart';
+
+void main() {
+ late MemoryFileSystem fs;
+ late Directory home;
+ late Directory dartToolDirectory;
+ late Analytics initializationAnalytics;
+ late FakeAnalytics analytics;
+ late File clientIdFile;
+ late File sessionFile;
+ late File configFile;
+ late File logFile;
+ late File dismissedSurveyFile;
+
+ const homeDirName = 'home';
+ const initialTool = DashTool.flutterTool;
+ const secondTool = DashTool.dartTool;
+ const toolsMessageVersion = 1;
+ const toolsMessage = 'toolsMessage';
+ const flutterChannel = 'flutterChannel';
+ const flutterVersion = 'flutterVersion';
+ const dartVersion = 'dartVersion';
+ const platform = DevicePlatform.macos;
+ const clientIde = 'VSCode';
+
+ final testEvent = Event.hotReloadTime(timeMs: 50);
+
+ setUp(() {
+ // Setup the filesystem with the home directory
+ final fsStyle =
+ io.Platform.isWindows ? FileSystemStyle.windows : FileSystemStyle.posix;
+ fs = MemoryFileSystem.test(style: fsStyle);
+ home = fs.directory(homeDirName);
+ dartToolDirectory = home.childDirectory(kDartToolDirectoryName);
+
+ // This is the first analytics instance that will be used to demonstrate
+ // that events will not be sent with the first run of analytics
+ initializationAnalytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ );
+ expect(initializationAnalytics.shouldShowMessage, true);
+ initializationAnalytics.clientShowedMessage();
+ expect(initializationAnalytics.shouldShowMessage, false);
+
+ // The main analytics instance, other instances can be spawned within tests
+ // to test how to instances running together work
+ //
+ // This instance should have the same parameters as the one above for
+ // [initializationAnalytics]
+ analytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ clientIde: clientIde,
+ );
+ analytics.clientShowedMessage();
+
+ // The 5 files that should have been generated
+ clientIdFile = home
+ .childDirectory(kDartToolDirectoryName)
+ .childFile(kClientIdFileName);
+ sessionFile =
+ home.childDirectory(kDartToolDirectoryName).childFile(kSessionFileName);
+ configFile =
+ home.childDirectory(kDartToolDirectoryName).childFile(kConfigFileName);
+ logFile =
+ home.childDirectory(kDartToolDirectoryName).childFile(kLogFileName);
+ dismissedSurveyFile = home
+ .childDirectory(kDartToolDirectoryName)
+ .childFile(kDismissedSurveyFileName);
+ });
+
+ test('Initializer properly sets up on first run', () {
+ expect(dartToolDirectory.existsSync(), true,
+ reason: 'The directory should have been created');
+ expect(clientIdFile.existsSync(), true,
+ reason: 'The $kClientIdFileName file was not found');
+ expect(sessionFile.existsSync(), true,
+ reason: 'The $kSessionFileName file was not found');
+ expect(configFile.existsSync(), true,
+ reason: 'The $kConfigFileName was not found');
+ expect(logFile.existsSync(), true,
+ reason: 'The $kLogFileName file was not found');
+ expect(dismissedSurveyFile.existsSync(), true,
+ reason: 'The $dismissedSurveyFile file was not found');
+ expect(dartToolDirectory.listSync().length, equals(5),
+ reason:
+ 'There should only be 5 files in the $kDartToolDirectoryName directory');
+ expect(configFile.readAsLinesSync().length,
+ kConfigString.split('\n').length + 1,
+ reason: 'The number of lines should equal lines in constant value + 1 '
+ 'for the initialized tool');
+ });
+
+ test('Resetting session file when data is malformed', () {
+ // Purposefully write content to the session file that
+ // can't be decoded as an integer
+ sessionFile.writeAsStringSync('contents');
+
+ // Define the initial time to start
+ final start = DateTime(1995, 3, 3, 12, 0);
+
+ // Set the clock to the start value defined above
+ withClock(Clock.fixed(start), () {
+ final timestamp = clock.now().millisecondsSinceEpoch.toString();
+ expect(sessionFile.readAsStringSync(), 'contents');
+ analytics.userProperty.preparePayload();
+ expect(sessionFile.readAsStringSync(),
+ '{"session_id": $timestamp, "last_ping": $timestamp}');
+
+ analytics.sendPendingErrorEvents();
+
+ // Attempting to fetch the session id when malformed should also
+ // send an error event while parsing
+ final lastEvent = analytics.sentEvents.last;
+ expect(lastEvent, isNotNull);
+ expect(lastEvent.eventName, DashEvent.analyticsException);
+ expect(
+ lastEvent.eventData['workflow']!, 'UserProperty._refreshSessionData');
+ expect(lastEvent.eventData['error']!, 'FormatException');
+ });
+ });
+
+ test('Handles malformed session file on startup', () {
+ // Ensure that we are able to send an error message on startup if
+ // we encounter an error while parsing the contents of the session file
+ // for session data
+ sessionFile.writeAsStringSync('not a valid session id');
+
+ analytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ clientIde: clientIde,
+ );
+ analytics.clientShowedMessage();
+
+ // Invoking a send command should reset the session file to a good state
+ //
+ // Having it reformat the session file before any send event happens will just
+ // add additional work on startup
+ analytics.send(testEvent);
+
+ analytics.sendPendingErrorEvents();
+ final errorEvent = analytics.sentEvents
+ .where((element) => element.eventName == DashEvent.analyticsException)
+ .firstOrNull;
+ expect(errorEvent, isNotNull);
+ expect(
+ errorEvent!.eventData['workflow'], 'UserProperty._refreshSessionData');
+ expect(errorEvent.eventData['error'], 'FormatException');
+ expect(errorEvent.eventData['description'],
+ 'message: Unexpected character\nsource: not a valid session id');
+ });
+
+ test('Resetting session file when file is removed', () {
+ // Purposefully write delete the file
+ sessionFile.deleteSync();
+
+ // Define the initial time to start
+ final start = DateTime(1995, 3, 3, 12, 0);
+
+ // Set the clock to the start value defined above
+ withClock(Clock.fixed(start), () {
+ final timestamp = clock.now().millisecondsSinceEpoch.toString();
+ expect(sessionFile.existsSync(), false);
+ analytics.userProperty.preparePayload();
+ expect(sessionFile.readAsStringSync(),
+ '{"session_id": $timestamp, "last_ping": $timestamp}');
+
+ analytics.sendPendingErrorEvents();
+
+ // Attempting to fetch the session id when malformed should also
+ // send an error event while parsing
+ final lastEvent = analytics.sentEvents.last;
+ expect(lastEvent, isNotNull);
+ expect(lastEvent.eventName, DashEvent.analyticsException);
+ expect(
+ lastEvent.eventData['workflow']!, 'UserProperty._refreshSessionData');
+ expect(lastEvent.eventData['error']!, 'FileSystemException');
+ });
+ });
+
+ test('New tool is successfully added to config file', () {
+ // Create a new instance of the analytics class with the new tool
+ final secondAnalytics = Analytics.fake(
+ tool: secondTool,
+ homeDirectory: home,
+ flutterChannel: 'ey-test-channel',
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: 'Flutter 3.6.0-7.0.pre.47',
+ dartVersion: 'Dart 2.19.0',
+ fs: fs,
+ platform: platform,
+ );
+ secondAnalytics.clientShowedMessage();
+
+ expect(secondAnalytics.parsedTools.length, equals(2),
+ reason: 'There should be only 2 tools that have '
+ 'been parsed into the config file');
+ expect(secondAnalytics.parsedTools.containsKey(initialTool.label), true,
+ reason: 'The first tool: ${initialTool.label} should be in the map');
+ expect(secondAnalytics.parsedTools.containsKey(secondTool.label), true,
+ reason: 'The second tool: $secondAnalytics should be in the map');
+ expect(configFile.readAsStringSync().startsWith(kConfigString), true,
+ reason:
+ 'The config file should have the same message from the constants file');
+ });
+
+ test('First time analytics run will not send events, second time will', () {
+ // Send an event with the first analytics class; this should result
+ // in no logs in the log file which keeps track of all the events
+ // that have been sent
+ initializationAnalytics.send(testEvent);
+ initializationAnalytics.send(testEvent);
+
+ // Use the second instance of analytics defined in setUp() to send the actual
+ // events to simulate the second time the tool ran
+ analytics.send(testEvent);
+
+ expect(logFile.readAsLinesSync().length, 1,
+ reason: 'The second analytics instance should have logged an event');
+ });
+
+ test('Toggling telemetry boolean through Analytics class api', () async {
+ final originalClientId = clientIdFile.readAsStringSync();
+
+ expect(analytics.telemetryEnabled, true,
+ reason: 'Telemetry should be enabled by default '
+ 'when initialized for the first time');
+ // Use the API to disable analytics
+ expect(logFile.readAsLinesSync().length, 0);
+ await analytics.setTelemetry(false);
+ expect(analytics.telemetryEnabled, false,
+ reason: 'Analytics telemetry should be disabled');
+ expect(logFile.readAsLinesSync().length, 0,
+ reason: 'Log file should have been cleared after opting out');
+ expect(clientIdFile.readAsStringSync().length, 0,
+ reason: 'CLIENT ID file gets cleared on opt out');
+ expect(sessionFile.readAsStringSync().length, 0,
+ reason: 'Session file gets cleared on opt out');
+
+ // Toggle it back to being enabled
+ await analytics.setTelemetry(true);
+ expect(analytics.telemetryEnabled, true,
+ reason: 'Analytics telemetry should be enabled');
+ expect(logFile.readAsLinesSync().length, 1,
+ reason: 'There should only one event since it was cleared on opt out');
+ expect(clientIdFile.readAsStringSync().length, greaterThan(0),
+ reason: 'CLIENT ID file gets regenerated on opt in');
+ expect(sessionFile.readAsStringSync().length, greaterThan(0),
+ reason: 'Session file gets regenerated on opt in');
+
+ // Extract the last log item to check for the keys
+ final lastLogItem =
+ jsonDecode(logFile.readAsLinesSync().last) as Map<String, Object?>;
+ expect((lastLogItem['events'] as List).last['name'],
+ 'analytics_collection_enabled',
+ reason: 'Check on event name');
+ expect((lastLogItem['events'] as List).last['params']['status'], true,
+ reason: 'Status should be false');
+ expect((lastLogItem['client_id'] as String).isNotEmpty, true);
+ expect(originalClientId != lastLogItem['client_id'], true,
+ reason: 'When opting in again, the client id should be regenerated');
+ });
+
+ test('Confirm client id is not empty string after opting in', () async {
+ await analytics.setTelemetry(false);
+ expect(logFile.readAsLinesSync().length, 0,
+ reason: 'Log file should have been cleared after opting out');
+ expect(clientIdFile.readAsStringSync().length, 0,
+ reason: 'CLIENT ID file gets cleared on opt out');
+
+ // Start up a second instance to simulate starting another
+ // command being run
+ final secondAnalytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ );
+
+ // Setting telemetry back on will emit a new event
+ // where the client id string should not be empty
+ await secondAnalytics.setTelemetry(true);
+ expect(analytics.telemetryEnabled, true,
+ reason: 'Analytics telemetry should be enabled');
+ expect(logFile.readAsLinesSync().length, 1,
+ reason: 'There should only one event since it was cleared on opt out');
+ expect(clientIdFile.readAsStringSync().length, greaterThan(0),
+ reason: 'CLIENT ID file gets regenerated on opt in');
+
+ // Extract the last log item to check for the keys
+ final lastLogItem =
+ jsonDecode(logFile.readAsLinesSync().last) as Map<String, Object?>;
+ expect((lastLogItem['client_id'] as String).isNotEmpty, true,
+ reason: 'The client id should have been regenerated and '
+ 'emitted in the opt in event');
+ });
+
+ test(
+ 'Telemetry has been disabled by one '
+ 'tool and second tool correctly shows telemetry is disabled', () async {
+ expect(analytics.telemetryEnabled, true,
+ reason: 'Analytics telemetry should be enabled on initialization');
+ // Use the API to disable analytics
+ await analytics.setTelemetry(false);
+ expect(analytics.telemetryEnabled, false,
+ reason: 'Analytics telemetry should be disabled');
+
+ // Initialize a second analytics class, which simulates a second tool
+ // Create a new instance of the analytics class with the new tool
+ final secondAnalytics = Analytics.fake(
+ tool: secondTool,
+ homeDirectory: home,
+ flutterChannel: 'ey-test-channel',
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: 'Flutter 3.6.0-7.0.pre.47',
+ dartVersion: 'Dart 2.19.0',
+ fs: fs,
+ platform: platform,
+ );
+ secondAnalytics.clientShowedMessage();
+
+ expect(secondAnalytics.telemetryEnabled, false,
+ reason: 'Analytics telemetry should be disabled by the first class '
+ 'and the second class should show telemetry is disabled');
+ });
+
+ test(
+ 'Two concurrent instances are running '
+ 'and reflect an accurate up to date telemetry status', () async {
+ // Initialize a second analytics class, which simulates a second tool
+ final secondAnalytics = Analytics.fake(
+ tool: secondTool,
+ homeDirectory: home,
+ flutterChannel: 'ey-test-channel',
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: 'Flutter 3.6.0-7.0.pre.47',
+ dartVersion: 'Dart 2.19.0',
+ fs: fs,
+ platform: platform,
+ );
+ secondAnalytics.clientShowedMessage();
+
+ expect(analytics.telemetryEnabled, true,
+ reason: 'Telemetry should be enabled on initialization for '
+ 'first analytics instance');
+ expect(secondAnalytics.telemetryEnabled, true,
+ reason: 'Telemetry should be enabled on initialization for '
+ 'second analytics instance');
+
+ // Use the API to disable analytics on the first instance
+ await analytics.setTelemetry(false);
+ expect(analytics.telemetryEnabled, false,
+ reason: 'Analytics telemetry should be disabled on first instance');
+
+ expect(secondAnalytics.telemetryEnabled, false,
+ reason: 'Analytics telemetry should be disabled by the first class '
+ 'and the second class should show telemetry is disabled'
+ ' by checking the timestamp on the config file');
+ });
+
+ test('New line character is added if missing', () {
+ String currentConfigFileString;
+
+ expect(configFile.readAsStringSync().endsWith('\n'), true,
+ reason: 'When initialized, the tool should correctly '
+ 'add a trailing new line character');
+
+ // Remove the trailing new line character before initializing a second
+ // analytics class; the new class should correctly format the config file
+ currentConfigFileString = configFile.readAsStringSync();
+ currentConfigFileString = currentConfigFileString.substring(
+ 0, currentConfigFileString.length - 1);
+
+ // Write back out to the config file to be processed again
+ configFile.writeAsStringSync(currentConfigFileString);
+
+ expect(configFile.readAsStringSync().endsWith('\n'), false,
+ reason: 'The trailing new line should be missing');
+
+ // Initialize a second analytics class, which simulates a second tool
+ // which should correct the missing trailing new line character
+ final secondAnalytics = Analytics.fake(
+ tool: secondTool,
+ homeDirectory: home,
+ flutterChannel: 'ey-test-channel',
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: 'Flutter 3.6.0-7.0.pre.47',
+ dartVersion: 'Dart 2.19.0',
+ fs: fs,
+ platform: platform,
+ );
+ secondAnalytics.clientShowedMessage();
+
+ expect(secondAnalytics.telemetryEnabled, true);
+
+ expect(configFile.readAsStringSync().endsWith('\n'), true,
+ reason: 'The second analytics class will correct '
+ 'the missing new line character');
+ });
+
+ test('Incrementing the version for a tool is successful', () {
+ expect(analytics.parsedTools[initialTool.label]?.versionNumber,
+ toolsMessageVersion,
+ reason: 'On initialization, the first version number should '
+ 'be what is set in the setup method');
+
+ // Initialize a second analytics class for the same tool as
+ // the first analytics instance except with a newer version for
+ // the tools message and version
+ final secondAnalytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion + 1,
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ );
+ expect(secondAnalytics.shouldShowMessage, true);
+ expect(secondAnalytics.okToSend, false);
+ secondAnalytics.clientShowedMessage();
+ expect(secondAnalytics.shouldShowMessage, false);
+ expect(secondAnalytics.okToSend, false,
+ reason: 'New version for the message will be treated as a first run');
+
+ expect(secondAnalytics.parsedTools[initialTool.label]?.versionNumber,
+ toolsMessageVersion + 1,
+ reason:
+ 'The second analytics instance should have incremented the version');
+ });
+
+ test(
+ 'Config file resets when there is not exactly one match for the reporting flag',
+ () async {
+ // Write to the config file a string that is not formatted correctly
+ // (ie. there is more than one match for the reporting flag)
+ configFile.writeAsStringSync('''
+# INTRODUCTION
+#
+# This is the Flutter and Dart telemetry reporting
+# configuration file.
+#
+# Lines starting with a #" are documentation that
+# the tools maintain automatically.
+#
+# All other lines are configuration lines. They have
+# the form "name=value". If multiple lines contain
+# the same configuration name with different values,
+# the parser will default to a conservative value.
+
+# DISABLING TELEMETRY REPORTING
+#
+# To disable telemetry reporting, set "reporting" to
+# the value "0" and to enable, set to "1":
+reporting=1
+reporting=1
+
+# NOTIFICATIONS
+#
+# Each tool records when it last informed the user about
+# analytics reporting and the privacy policy.
+#
+# The following tools have so far read this file:
+#
+# dart-tools (Dart CLI developer tool)
+# devtools (DevTools debugging and performance tools)
+# flutter-tools (Flutter CLI developer tool)
+#
+# For each one, the file may contain a configuration line
+# where the name is the code in the list above, e.g. "dart-tool",
+# and the value is a date in the form YYYY-MM-DD, a comma, and
+# a number representing the version of the message that was
+# displayed.''');
+
+ // Disable telemetry which should result in a reset of the config file
+ await analytics.setTelemetry(false);
+
+ expect(configFile.readAsStringSync().startsWith(kConfigString), true,
+ reason: 'The tool should have reset the config file '
+ 'because it was not formatted correctly');
+ });
+
+ test('Config file resets when there is not exactly one match for the tool',
+ () {
+ // Write to the config file a string that is not formatted correctly
+ // (ie. there is more than one match for the reporting flag)
+ configFile.writeAsStringSync('''
+# INTRODUCTION
+#
+# This is the Flutter and Dart telemetry reporting
+# configuration file.
+#
+# Lines starting with a #" are documentation that
+# the tools maintain automatically.
+#
+# All other lines are configuration lines. They have
+# the form "name=value". If multiple lines contain
+# the same configuration name with different values,
+# the parser will default to a conservative value.
+
+# DISABLING TELEMETRY REPORTING
+#
+# To disable telemetry reporting, set "reporting" to
+# the value "0" and to enable, set to "1":
+reporting=1
+
+# NOTIFICATIONS
+#
+# Each tool records when it last informed the user about
+# analytics reporting and the privacy policy.
+#
+# The following tools have so far read this file:
+#
+# dart-tools (Dart CLI developer tool)
+# devtools (DevTools debugging and performance tools)
+# flutter-tools (Flutter CLI developer tool)
+#
+# For each one, the file may contain a configuration line
+# where the name is the code in the list above, e.g. "dart-tool",
+# and the value is a date in the form YYYY-MM-DD, a comma, and
+# a number representing the version of the message that was
+# displayed.
+${initialTool.label}=$dateStamp,$toolsMessageVersion
+${initialTool.label}=$dateStamp,$toolsMessageVersion
+''');
+
+ // Initialize a second analytics class for the same tool as
+ // the first analytics instance except with a newer version for
+ // the tools message and version
+ //
+ // This second instance should reset the config file when it goes
+ // to increment the version in the file
+ final secondAnalytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion + 1,
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ );
+ secondAnalytics.clientShowedMessage();
+
+ expect(
+ configFile.readAsStringSync(),
+ kConfigString,
+ reason: 'The config file should have been reset completely '
+ 'due to a malformed file that contained two lines for the same tool',
+ );
+
+ // Creating a third instance after the second instance
+ // has reset the config file should include the newly added
+ // tool again with its incremented version number
+ final thirdAnalytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion + 1,
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ );
+ thirdAnalytics.clientShowedMessage();
+
+ expect(
+ configFile.readAsStringSync().endsWith(
+ '# displayed.\n${initialTool.label}=$dateStamp,${toolsMessageVersion + 1}\n'),
+ true,
+ reason: 'The config file ends with the correctly formatted ending '
+ 'after removing the duplicate lines for a given tool',
+ );
+ expect(
+ thirdAnalytics.parsedTools[initialTool.label]?.versionNumber,
+ toolsMessageVersion + 1,
+ reason: 'The new version should have been incremented',
+ );
+ });
+
+ test('Check that UserProperty class has all the necessary keys', () {
+ const userPropertyKeys = <String>[
+ 'session_id',
+ 'flutter_channel',
+ 'host',
+ 'flutter_version',
+ 'dart_version',
+ 'analytics_pkg_version',
+ 'tool',
+ 'local_time',
+ 'host_os_version',
+ 'locale',
+ 'client_ide',
+ 'enabled_features',
+ ];
+ expect(analytics.userPropertyMap.keys.length, userPropertyKeys.length,
+ reason: 'There should only be ${userPropertyKeys.length} keys');
+ for (final key in userPropertyKeys) {
+ expect(analytics.userPropertyMap.keys.contains(key), true,
+ reason: 'The $key variable is required');
+ }
+ });
+
+ test('The minimum session duration should be at least 30 minutes', () {
+ expect(kSessionDurationMinutes < 30, false,
+ reason: 'Session is less than 30 minutes');
+ });
+
+ test(
+ 'The session id stays the same when duration'
+ ' is less than the constraint', () {
+ // For this test, we will need control clock time so we will delete
+ // the [dartToolDirectory] and all of its contents and reconstruct a
+ // new [Analytics] instance at a specific time
+ dartToolDirectory.deleteSync(recursive: true);
+ expect(dartToolDirectory.existsSync(), false,
+ reason: 'The directory should have been cleared');
+
+ // Define the initial time to start
+ final start = DateTime(1995, 3, 3, 12, 0);
+
+ // Set the clock to the start value defined above
+ withClock(Clock.fixed(start), () {
+ // This class will be constructed at a fixed time
+ final secondAnalytics = Analytics.fake(
+ tool: secondTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: 'Flutter 3.6.0-7.0.pre.47',
+ dartVersion: 'Dart 2.19.0',
+ fs: fs,
+ platform: platform,
+ );
+ secondAnalytics.clientShowedMessage();
+
+ expect(secondAnalytics.userPropertyMap['session_id']?['value'],
+ start.millisecondsSinceEpoch);
+ expect(sessionFile.lastModifiedSync().millisecondsSinceEpoch,
+ start.millisecondsSinceEpoch);
+ });
+
+ // Add time to the start time that is less than the duration
+ final end = start.add(const Duration(minutes: kSessionDurationMinutes - 1));
+
+ // Use a new clock to ensure that the session id didn't change
+ withClock(Clock.fixed(end), () {
+ // A new instance will need to be created since the second
+ // instance in the previous block is scoped - this new instance
+ // should not reset the files generated by the second instance
+ final thirdAnalytics = Analytics.fake(
+ tool: secondTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: 'Flutter 3.6.0-7.0.pre.47',
+ dartVersion: 'Dart 2.19.0',
+ fs: fs,
+ platform: platform,
+ );
+ thirdAnalytics.clientShowedMessage();
+
+ // Calling the send event method will result in the session file
+ // getting updated but because we use the `Analytics.fake()` constructor
+ // no events will be sent
+ thirdAnalytics.send(testEvent);
+
+ expect(thirdAnalytics.userPropertyMap['session_id']?['value'],
+ start.millisecondsSinceEpoch,
+ reason: 'The session id should not have changed since it was made '
+ 'within the duration');
+ expect(sessionFile.lastModifiedSync().millisecondsSinceEpoch,
+ end.millisecondsSinceEpoch,
+ reason: 'The last modified value should have been updated');
+ });
+ });
+
+ test('The session id is refreshed once event is sent after duration', () {
+ // For this test, we will need control clock time so we will delete
+ // the [dartToolDirectory] and all of its contents and reconstruct a
+ // new [Analytics] instance at a specific time
+ dartToolDirectory.deleteSync(recursive: true);
+ expect(dartToolDirectory.existsSync(), false,
+ reason: 'The directory should have been cleared');
+
+ // Define the initial time to start
+ final start = DateTime(1995, 3, 3, 12, 0);
+
+ // Set the clock to the start value defined above
+ withClock(Clock.fixed(start), () {
+ // This class will be constructed at a fixed time
+ final secondAnalytics = Analytics.fake(
+ tool: secondTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: 'Flutter 3.6.0-7.0.pre.47',
+ dartVersion: 'Dart 2.19.0',
+ fs: fs,
+ platform: platform,
+ );
+ secondAnalytics.clientShowedMessage();
+
+ expect(secondAnalytics.userPropertyMap['session_id']?['value'],
+ start.millisecondsSinceEpoch);
+ expect(sessionFile.lastModifiedSync().millisecondsSinceEpoch,
+ start.millisecondsSinceEpoch);
+
+ secondAnalytics.send(testEvent);
+ expect(sessionFile.readAsStringSync(),
+ '{"session_id": ${start.millisecondsSinceEpoch}, "last_ping": ${start.millisecondsSinceEpoch}}');
+ });
+
+ // Add time to the start time that is less than the duration
+ final end = start.add(const Duration(minutes: kSessionDurationMinutes + 1));
+
+ // Use a new clock to ensure that the session id didn't change
+ withClock(Clock.fixed(end), () {
+ // A new instance will need to be created since the second
+ // instance in the previous block is scoped - this new instance
+ // should not reset the files generated by the second instance
+ final thirdAnalytics = Analytics.fake(
+ tool: secondTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: 'Flutter 3.6.0-7.0.pre.47',
+ dartVersion: 'Dart 2.19.0',
+ fs: fs,
+ platform: platform,
+ );
+ thirdAnalytics.clientShowedMessage();
+
+ // Calling the send event method will result in the session file
+ // getting updated but because we use the `Analytics.fake()` constructor
+ // no events will be sent
+ thirdAnalytics.send(testEvent);
+
+ expect(thirdAnalytics.userPropertyMap['session_id']?['value'],
+ end.millisecondsSinceEpoch,
+ reason: 'The session id should have changed since it was made '
+ 'outside the duration');
+ expect(sessionFile.lastModifiedSync().millisecondsSinceEpoch,
+ end.millisecondsSinceEpoch,
+ reason: 'The last modified value should have been updated');
+ expect(sessionFile.readAsStringSync(),
+ '{"session_id": ${end.millisecondsSinceEpoch}, "last_ping": ${end.millisecondsSinceEpoch}}');
+ });
+ });
+
+ test('Validate the available enum types for DevicePlatform', () {
+ expect(DevicePlatform.values.length, 3,
+ reason: 'There should only be 3 supported device platforms');
+ expect(DevicePlatform.values.contains(DevicePlatform.windows), true);
+ expect(DevicePlatform.values.contains(DevicePlatform.macos), true);
+ expect(DevicePlatform.values.contains(DevicePlatform.linux), true);
+ });
+
+ test('Validate the request body', () {
+ // Sample map for event data
+ final eventData = <String, dynamic>{
+ 'time': 5,
+ 'command': 'run',
+ };
+
+ final Map<String, dynamic> body = generateRequestBody(
+ clientId: Uuid().generateV4(),
+ eventName: DashEvent.hotReloadTime,
+ eventData: eventData,
+ userProperty: analytics.userProperty,
+ );
+
+ // Checks for the top level keys
+ expect(body.containsKey('client_id'), true,
+ reason: '"client_id" is required at the top level');
+ expect(body.containsKey('events'), true,
+ reason: '"events" is required at the top level');
+ expect(body.containsKey('user_properties'), true,
+ reason: '"user_properties" is required at the top level');
+
+ // Regex for the client id
+ final clientIdPattern = RegExp(
+ r'^[0-9a-z]{8}\-[0-9a-z]{4}\-[0-9a-z]{4}\-[0-9a-z]{4}\-[0-9a-z]{12}$');
+
+ // Checks for the top level values
+ expect(body['client_id'].runtimeType, String,
+ reason: 'The client id must be a string');
+ expect(clientIdPattern.hasMatch(body['client_id'] as String), true,
+ reason: 'The client id is not properly formatted, ie '
+ '46cc0ba6-f604-4fd9-aa2f-8a20beb24cd4');
+ expect(
+ (body['events'][0] as Map<String, dynamic>).containsKey('name'), true,
+ reason: 'Each event in the events array needs a name');
+ expect(
+ (body['events'][0] as Map<String, dynamic>).containsKey('params'), true,
+ reason: 'Each event in the events array needs a params key');
+ });
+
+ test('Check that log file is correctly persisting events sent', () {
+ final int numberOfEvents = max((kLogFileLength * 0.1).floor(), 5);
+
+ for (var i = 0; i < numberOfEvents; i++) {
+ analytics.send(testEvent);
+ }
+
+ expect(logFile.readAsLinesSync().length, numberOfEvents,
+ reason: 'The number of events should be $numberOfEvents');
+
+ // Add the max number of events to confirm it does not exceed the max
+ for (var i = 0; i < kLogFileLength; i++) {
+ analytics.send(testEvent);
+ }
+
+ expect(logFile.readAsLinesSync().length, kLogFileLength,
+ reason: 'The number of events should be capped at $kLogFileLength');
+ });
+
+ test('Check the query on the log file works as expected', () {
+ // Define a new clock so that we can check the output of the
+ // log file stats method explicitly
+ final start = DateTime(1995, 3, 3, 12, 0);
+ final firstClock = Clock.fixed(start);
+
+ // Run with the simulated clock for the initial events
+ withClock(firstClock, () {
+ expect(analytics.logFileStats(), isNull,
+ reason: 'The result for the log file stats should be null when '
+ 'there are no logs');
+ analytics.send(testEvent);
+
+ final firstQuery = analytics.logFileStats()!;
+ expect(firstQuery.sessionCount, 1,
+ reason:
+ 'There should only be one session after the initial send event');
+ expect(firstQuery.flutterChannelCount, {'flutterChannel': 1},
+ reason: 'There should only be one flutter channel logged');
+ expect(firstQuery.toolCount, {'flutter-tool': 1},
+ reason: 'There should only be one tool logged');
+ });
+
+ // Define a new clock that is outside of the session duration
+ final secondClock =
+ start.add(const Duration(minutes: kSessionDurationMinutes + 1));
+
+ // Use the new clock to send an event that will change the session identifier
+ withClock(Clock.fixed(secondClock), () {
+ analytics.send(testEvent);
+
+ final secondQuery = analytics.logFileStats()!;
+
+ // Construct the expected response for the second query
+ expect(secondQuery.startDateTime, DateTime(1995, 3, 3, 12, 0));
+ expect(secondQuery.minsFromStartDateTime, 31);
+ expect(secondQuery.endDateTime, DateTime(1995, 3, 3, 12, 31));
+ expect(secondQuery.minsFromEndDateTime, 0);
+ expect(secondQuery.sessionCount, 2);
+ expect(secondQuery.flutterChannelCount, {'flutterChannel': 2});
+ expect(secondQuery.toolCount, {'flutter-tool': 2});
+ expect(secondQuery.recordCount, 2);
+ expect(secondQuery.eventCount, {'hot_reload_time': 2});
+ });
+ });
+
+ test('Check that the log file shows two different tools being used', () {
+ // Use a for loop two initialize the second analytics instance
+ // twice to account for no events being sent on the first instance
+ // run for a given tool
+ Analytics? secondAnalytics;
+ for (var i = 0; i < 2; i++) {
+ secondAnalytics = Analytics.fake(
+ tool: secondTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: 'Flutter 3.6.0-7.0.pre.47',
+ dartVersion: 'Dart 2.19.0',
+ fs: fs,
+ platform: platform,
+ );
+ secondAnalytics.clientShowedMessage();
+ }
+
+ // Send events with both instances of the classes
+ analytics.send(testEvent);
+ secondAnalytics!.send(testEvent);
+
+ // Query the log file stats to verify that there are two tools
+ final query = analytics.logFileStats()!;
+
+ expect(query.toolCount, {'flutter-tool': 1, 'dart-tool': 1},
+ reason: 'There should have been two tools in the persisted logs');
+ });
+
+ test('Check that log data missing some keys results in null for stats', () {
+ // The following string represents a log item that is malformed (missing the `tool` key)
+ const malformedLog = '{"client_id":"d40133a0-7ea6-4347-b668-ffae94bb8774",'
+ '"events":[{"name":"hot_reload_time","params":{"time_ns":345}}],'
+ '"user_properties":{'
+ '"session_id":{"value":1675193534342},'
+ '"flutter_channel":{"value":"ey-test-channel"},'
+ '"host":{"value":"macOS"},'
+ '"flutter_version":{"value":"Flutter 3.6.0-7.0.pre.47"},'
+ '"dart_version":{"value":"Dart 2.19.0"},'
+ // '"tool":{"value":"flutter-tools"},' NEEDS REMAIN REMOVED
+ '"local_time":{"value":"2023-01-31 14:32:14.592898 -0500"}}}';
+
+ logFile.writeAsStringSync(malformedLog);
+ final query = analytics.logFileStats();
+
+ expect(query, isNull,
+ reason:
+ 'The query should be null because `tool` is missing under `user_properties`');
+ });
+
+ test('Malformed local_time string should result in null for stats', () {
+ // The following string represents a log item that is malformed (missing the `tool` key)
+ const malformedLog = '{"client_id":"d40133a0-7ea6-4347-b668-ffae94bb8774",'
+ '"events":[{"name":"hot_reload_time","params":{"time_ns":345}}],'
+ '"user_properties":{'
+ '"session_id":{"value":1675193534342},'
+ '"flutter_channel":{"value":"ey-test-channel"},'
+ '"host":{"value":"macOS"},'
+ '"flutter_version":{"value":"Flutter 3.6.0-7.0.pre.47"},'
+ '"dart_version":{"value":"Dart 2.19.0"},'
+ '"tool":{"value":"flutter-tools"},'
+ '"local_time":{"value":"2023-xx-31 14:32:14.592898 -0500"}}}'; // PURPOSEFULLY MALFORMED
+
+ logFile.writeAsStringSync(malformedLog);
+ final query = analytics.logFileStats();
+
+ expect(query, isNull,
+ reason:
+ 'The query should be null because the `local_time` value is malformed');
+ });
+
+ test('Version is the same in the change log, pubspec, and constants.dart',
+ () {
+ // Parse the contents of the pubspec.yaml
+ final pubspecYamlString = io.File('pubspec.yaml').readAsStringSync();
+
+ // Parse into a yaml document to extract the version number
+ final doc = loadYaml(pubspecYamlString) as YamlMap;
+ final version = doc['version'] as String;
+
+ expect(version, kPackageVersion,
+ reason: 'The package version in the pubspec and '
+ 'constants.dart need to match\n'
+ 'Pubspec: $version && constants.dart: $kPackageVersion\n\n'
+ 'Make sure both are the same');
+
+ // Parse the contents of the change log file
+ final changeLogFirstLineString =
+ io.File('CHANGELOG.md').readAsLinesSync().first;
+ expect(changeLogFirstLineString.substring(3), kPackageVersion,
+ reason: 'The CHANGELOG.md file needs the first line to '
+ 'be the same version as the pubspec and constants.dart');
+ });
+
+ test('Null values for flutter parameters is reflected properly in log file',
+ () {
+ // Because we are using the `MemoryFileSystem.test` constructor,
+ // we don't have a real clock in the filesystem, and because we
+ // are checking the last modified timestamp for the session file
+ // to determine if we need to update the session id, manually setting
+ // that timestamp will ensure we are not updating session id when it
+ // first gets created
+ sessionFile.setLastModifiedSync(DateTime.now());
+
+ // Use a for loop two initialize the second analytics instance
+ // twice to account for no events being sent on the first instance
+ // run for a given tool
+ Analytics? secondAnalytics;
+ for (var i = 0; i < 2; i++) {
+ secondAnalytics = Analytics.fake(
+ tool: secondTool,
+ homeDirectory: home,
+ // flutterChannel: flutterChannel, THIS NEEDS TO REMAIN REMOVED
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: 'Flutter 3.6.0-7.0.pre.47',
+ dartVersion: 'Dart 2.19.0',
+ fs: fs,
+ platform: platform,
+ );
+ secondAnalytics.clientShowedMessage();
+ }
+
+ // Send an event and check that the query stats reflects what is expected
+ secondAnalytics!.send(testEvent);
+
+ // Query the log file stats to verify that there are two tools
+ final query = analytics.logFileStats()!;
+
+ expect(query.toolCount, {'dart-tool': 1},
+ reason: 'There should have only been on tool that sent events');
+ expect(query.flutterChannelCount.isEmpty, true,
+ reason:
+ 'The instance does not have flutter information so it should be 0');
+
+ // Sending a query with the first analytics instance which has flutter information
+ // available should reflect in the query that there is 1 flutter channel present
+ analytics.send(testEvent);
+ final query2 = analytics.logFileStats()!;
+
+ expect(query2.toolCount, {'dart-tool': 1, 'flutter-tool': 1},
+ reason: 'Two different analytics instances have '
+ 'been initialized and sent events');
+ expect(query2.sessionCount, query.sessionCount,
+ reason: 'The session should have remained the same');
+ expect(query2.flutterChannelCount, {'flutterChannel': 1},
+ reason: 'The first instance has flutter information initialized');
+ });
+
+ group('Testing against Google Analytics limitations:', () {
+ // Link to limitations documentation
+ // https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events?client_type=gtag#limitations
+ //
+ // Only the limitations specified below have been added, the other
+ // are not able to be validated because it will vary by each tool
+ //
+ // 1. Events can have a maximum of 25 user properties
+ // 2. User property names must be 24 characters or fewer
+ // 3. (Only for `tool` name) User property values must be 36 characters or fewer
+ // 4. Event names must be 40 characters or fewer, may only contain alpha-numeric
+ // characters and underscores, and must start with an alphabetic character
+ test('max 25 user properties per event', () {
+ final Map<String, Object> userPropPayload =
+ analytics.userProperty.preparePayload();
+ const maxUserPropKeys = 25;
+
+ expect(userPropPayload.keys.length < maxUserPropKeys, true,
+ reason: 'There are too many keys in the UserProperty payload');
+ });
+
+ test('max 24 characters for user prop keys', () {
+ final Map<String, Object> userPropPayload =
+ analytics.userProperty.preparePayload();
+ const maxUserPropLength = 24;
+
+ var userPropLengthValid = true;
+ final invalidUserProps = <String>[];
+ for (final key in userPropPayload.keys) {
+ if (key.length > maxUserPropLength) {
+ userPropLengthValid = false;
+ invalidUserProps.add(key);
+ }
+ }
+ expect(userPropLengthValid, true,
+ reason:
+ 'The max length for each user prop is $maxUserPropLength chars\n'
+ 'The below keys are too long:\n$invalidUserProps');
+ });
+
+ test('max 36 characters for user prop values (only `tool` key)', () {
+ // Checks item 3
+ // All tools must be under 36 characters (and enforce each tool
+ // begins with a letter)
+ final toolLabelPattern = RegExp(r'^[a-zA-Z][a-zA-Z\_-]{0,35}$');
+ var toolLengthValid = true;
+ final invalidTools = <DashTool>[];
+ for (final tool in DashTool.values) {
+ if (!toolLabelPattern.hasMatch(tool.label)) {
+ toolLengthValid = false;
+ invalidTools.add(tool);
+ }
+ }
+
+ expect(toolLengthValid, true,
+ reason:
+ 'All tool labels must be under 36 characters and begin with a letter\n'
+ 'The following are invalid\n$invalidTools');
+ });
+
+ test('max 40 characters for event names', () {
+ // Check that each event name is less than 40 chars and starts with
+ // an alphabetic character; the entire string has to be alphanumeric
+ // and underscores
+ final eventLabelPattern = RegExp(r'^[a-zA-Z]{1}[a-zA-Z0-9\_]{0,39}$');
+ var eventValid = true;
+ final invalidEvents = <DashEvent>[];
+ for (final event in DashEvent.values) {
+ if (!eventLabelPattern.hasMatch(event.label)) {
+ eventValid = false;
+ invalidEvents.add(event);
+ }
+ }
+
+ expect(eventValid, true,
+ reason: 'All event labels should have letters and underscores '
+ 'as a delimiter if needed; invalid events below\n$invalidEvents');
+ });
+ });
+
+ test('Confirm credentials for GA', () {
+ expect(kGoogleAnalyticsApiSecret, 'Ka1jc8tZSzWc_GXMWHfPHA');
+ expect(kGoogleAnalyticsMeasurementId, 'G-04BXPVBCWJ');
+ });
+
+ test('Consent message is formatted correctly for the flutter tool', () {
+ // Retrieve the consent message for flutter tools
+ final consentMessage = analytics.getConsentMessage;
+
+ expect(consentMessage, equalsIgnoringWhitespace(r'''
+The Flutter CLI developer tool uses Google Analytics to report usage and diagnostic
+data along with package dependencies, and crash reporting to send basic crash
+reports. This data is used to help improve the Dart platform, Flutter framework,
+and related tools.
+
+Telemetry is not sent on the very first run. To disable reporting of telemetry,
+run this terminal command:
+
+ flutter --disable-analytics
+
+If you opt out of telemetry, an opt-out event will be sent, and then no further
+information will be sent. This data is collected in accordance with the Google
+Privacy Policy (https://policies.google.com/privacy).
+'''));
+ });
+
+ test('Consent message is formatted correctly for any tool other than flutter',
+ () {
+ // Create a new instance of the analytics class with the new tool
+ final secondAnalytics = Analytics.fake(
+ tool: secondTool,
+ homeDirectory: home,
+ flutterChannel: 'ey-test-channel',
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: 'Flutter 3.6.0-7.0.pre.47',
+ dartVersion: 'Dart 2.19.0',
+ fs: fs,
+ platform: platform,
+ );
+
+ // Retrieve the consent message for flutter tools
+ final consentMessage = secondAnalytics.getConsentMessage;
+
+ expect(consentMessage, equalsIgnoringWhitespace(r'''
+The Dart CLI developer tool uses Google Analytics to report usage and diagnostic
+data along with package dependencies, and crash reporting to send basic crash
+reports. This data is used to help improve the Dart platform, Flutter framework,
+and related tools.
+
+Telemetry is not sent on the very first run. To disable reporting of telemetry,
+run this terminal command:
+
+ dart --disable-analytics
+
+If you opt out of telemetry, an opt-out event will be sent, and then no further
+information will be sent. This data is collected in accordance with the Google
+Privacy Policy (https://policies.google.com/privacy).
+'''));
+ });
+
+ test('Equality operator works for identical events', () {
+ final eventOne = Event.clientRequest(
+ duration: 'duration',
+ latency: 'latency',
+ method: 'method',
+ );
+ final eventTwo = Event.clientRequest(
+ duration: 'duration',
+ latency: 'latency',
+ method: 'method',
+ );
+
+ expect(eventOne == eventTwo, true);
+ });
+
+ test('Equality operator works for non-identical events', () {
+ final eventOne = Event.clientRequest(
+ duration: 'duration',
+ latency: 'latency',
+ method: 'method',
+ added: 'DIFFERENT FROM EVENT TWO',
+ );
+ final eventTwo = Event.clientRequest(
+ duration: 'duration',
+ latency: 'latency',
+ method: 'method',
+ );
+
+ expect(eventOne == eventTwo, false);
+ });
+
+ test('Find a match for an event in a list of events', () {
+ final eventList = [
+ Event.analyticsCollectionEnabled(status: true),
+ Event.memoryInfo(rss: 500),
+ Event.clientRequest(
+ duration: 'duration', latency: 'latency', method: 'method'),
+ ];
+
+ final eventToMatch = Event.memoryInfo(rss: 500);
+
+ expect(eventList.contains(eventToMatch), true);
+ expect(eventList.where((element) => element == eventToMatch).length, 1);
+ });
+
+ group('Unit tests for util dartSDKVersion', () {
+ test('parses correctly for non-stable version', () {
+ final originalVersion =
+ '3.4.0-148.0.dev (dev) (Thu Feb 15 12:05:45 2024 -0800) on "macos_arm64"';
+
+ expect(parseDartSDKVersion(originalVersion),
+ '3.4.0 (build 3.4.0-148.0.dev)');
+ });
+
+ test('parses correctly for stable version', () {
+ final originalVersion =
+ '3.3.0 (stable) (Tue Feb 13 10:25:19 2024 +0000) on "macos_arm64"';
+
+ expect(parseDartSDKVersion(originalVersion), '3.3.0');
+ });
+ });
+}
diff --git a/pkgs/unified_analytics/test/workflow_test.dart b/pkgs/unified_analytics/test/workflow_test.dart
new file mode 100644
index 0000000..3a623a0
--- /dev/null
+++ b/pkgs/unified_analytics/test/workflow_test.dart
@@ -0,0 +1,382 @@
+// Copyright (c) 2023, 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/memory.dart';
+import 'package:test/test.dart';
+import 'package:unified_analytics/src/constants.dart';
+import 'package:unified_analytics/src/enums.dart';
+import 'package:unified_analytics/src/utils.dart';
+import 'package:unified_analytics/unified_analytics.dart';
+
+void main() {
+ late MemoryFileSystem fs;
+ late Directory home;
+ late Directory dartToolDirectory;
+ late File clientIdFile;
+ late File sessionFile;
+ late File configFile;
+ late File logFile;
+ late File dismissedSurveyFile;
+
+ const homeDirName = 'home';
+ const initialTool = DashTool.flutterTool;
+ const secondTool = DashTool.dartTool;
+ const toolsMessageVersion = 1;
+ const toolsMessage = 'toolsMessage';
+ const flutterChannel = 'flutterChannel';
+ const flutterVersion = 'flutterVersion';
+ const dartVersion = 'dartVersion';
+ const platform = DevicePlatform.macos;
+
+ final testEvent = Event.hotReloadTime(timeMs: 50);
+
+ setUp(() {
+ // Setup the filesystem with the home directory
+ final fsStyle =
+ io.Platform.isWindows ? FileSystemStyle.windows : FileSystemStyle.posix;
+ fs = MemoryFileSystem.test(style: fsStyle);
+ home = fs.directory(homeDirName);
+ dartToolDirectory = home.childDirectory(kDartToolDirectoryName);
+
+ // The 3 files that should have been generated
+ clientIdFile = home
+ .childDirectory(kDartToolDirectoryName)
+ .childFile(kClientIdFileName);
+ sessionFile =
+ home.childDirectory(kDartToolDirectoryName).childFile(kSessionFileName);
+ configFile =
+ home.childDirectory(kDartToolDirectoryName).childFile(kConfigFileName);
+ logFile =
+ home.childDirectory(kDartToolDirectoryName).childFile(kLogFileName);
+ dismissedSurveyFile = home
+ .childDirectory(kDartToolDirectoryName)
+ .childFile(kDismissedSurveyFileName);
+ });
+
+ test('Confirm workflow for first run', () {
+ final firstAnalytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ );
+
+ expect(firstAnalytics.shouldShowMessage, true);
+ expect(firstAnalytics.okToSend, false);
+
+ firstAnalytics.clientShowedMessage();
+ expect(firstAnalytics.shouldShowMessage, false);
+ expect(firstAnalytics.okToSend, false,
+ reason: 'On the first run, we should not be ok '
+ 'to send any events, even if the user accepts');
+ });
+
+ test('Confirm workflow for updated tools message version + new tool', () {
+ // Helper function to check the state of the instance
+ void checkAnalyticsInstance(Analytics instance) {
+ expect(instance.shouldShowMessage, true);
+ expect(instance.okToSend, false);
+
+ instance.clientShowedMessage();
+ expect(instance.shouldShowMessage, false);
+ expect(instance.okToSend, false,
+ reason: 'On the first run, we should not be ok '
+ 'to send any events, even if the user accepts');
+ }
+
+ final firstAnalytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ );
+
+ checkAnalyticsInstance(firstAnalytics);
+
+ // Instance where we increment the version of the message
+ final secondAnalytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion + 1, // Incrementing version
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ );
+
+ // Running the same checks for the second instance, it should
+ // behave the same as if it was a first run
+ checkAnalyticsInstance(secondAnalytics);
+
+ // Instance for a different tool with the incremented version
+ final thirdAnalytics = Analytics.fake(
+ tool: secondTool, // Different tool
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion + 1, // Incrementing version
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ );
+
+ // The instance with a new tool getting onboarded should be
+ // treated the same as the 2 previous instances
+ checkAnalyticsInstance(thirdAnalytics);
+ });
+
+ test('Confirm workflow for checking tools into the config file', () {
+ final firstAnalytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ );
+
+ // Host of assertions to ensure all required artifacts
+ // are created
+ expect(dartToolDirectory.existsSync(), true,
+ reason: 'The directory should have been created');
+ expect(clientIdFile.existsSync(), true,
+ reason: 'The $kClientIdFileName file was not found');
+ expect(sessionFile.existsSync(), true,
+ reason: 'The $kSessionFileName file was not found');
+ expect(configFile.existsSync(), true,
+ reason: 'The $kConfigFileName was not found');
+ expect(logFile.existsSync(), true,
+ reason: 'The $kLogFileName file was not found');
+ expect(dismissedSurveyFile.existsSync(), true,
+ reason: 'The $dismissedSurveyFile file was not found');
+ expect(
+ dartToolDirectory.listSync().length,
+ equals(5),
+ reason: 'There should only be 5 files in the $kDartToolDirectoryName '
+ 'directory',
+ );
+ expect(configFile.readAsStringSync(), kConfigString);
+
+ expect(firstAnalytics.shouldShowMessage, true);
+
+ // Attempting to send a message with this instance should be
+ // blocked because it has not invoked `clientShowedMessage()`
+ // and it is the first run
+ //
+ // Even after invoking the method, it should be prevented from
+ // sending a message because it is the first time the tool was
+ // run in this instance
+ firstAnalytics.send(testEvent);
+ expect(logFile.readAsLinesSync().length, 0);
+ firstAnalytics.clientShowedMessage();
+
+ // Attempt to send two events, both should be blocked because it is
+ // part of the first instance
+ firstAnalytics.send(testEvent);
+ firstAnalytics.send(testEvent);
+ expect(logFile.readAsLinesSync().length, 0);
+
+ // Creating a second analytics instance from the same tool now should
+ // allow for events to be sent
+ final secondAnalytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ );
+
+ expect(secondAnalytics.shouldShowMessage, false);
+ secondAnalytics.send(testEvent);
+ expect(logFile.readAsLinesSync().length, 1,
+ reason: 'Events will be blocked until invoking method '
+ 'ensuring client has seen message');
+
+ secondAnalytics.send(testEvent);
+ secondAnalytics.send(testEvent);
+ expect(logFile.readAsLinesSync().length, 3);
+
+ // Next, we will want to confirm that the message should be showing when
+ // a new analytics instance has been created with a newer version for
+ // message that should be shown
+ //
+ // In this case, it should be treated as a new tool being added for the
+ // first time and all events should be blocked
+
+ // Delete the log file to reset the counter of events sent
+ logFile.deleteSync();
+ final thirdAnalytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion + 1, // Incrementing version
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ );
+
+ expect(logFile.existsSync(), true,
+ reason: 'The $kLogFileName file was not found');
+ expect(thirdAnalytics.shouldShowMessage, true,
+ reason: 'New version number should require showing message');
+ thirdAnalytics.send(testEvent);
+ expect(logFile.readAsLinesSync().length, 0);
+ thirdAnalytics.clientShowedMessage();
+
+ // Attempt to send two events, both should be blocked because it is
+ // part of the third instance which has a new version for the consent
+ // message which will be treated as a new tool being onboarded
+ thirdAnalytics.send(testEvent);
+ thirdAnalytics.send(testEvent);
+ expect(logFile.readAsLinesSync().length, 0);
+
+ // The fourth instance of the analytics class with the consent message
+ // version incremented should now be able to send messages
+ final fourthAnalytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion + 1, // Incrementing version
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ );
+
+ expect(fourthAnalytics.shouldShowMessage, false);
+ fourthAnalytics.send(testEvent);
+ expect(logFile.readAsLinesSync().length, 1,
+ reason: 'Events will be blocked until invoking method '
+ 'ensuring client has seen message');
+
+ fourthAnalytics.send(testEvent);
+ fourthAnalytics.send(testEvent);
+ expect(logFile.readAsLinesSync().length, 3);
+ });
+
+ test('Disable second instance if first one did not show message', () {
+ final firstAnalytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ );
+
+ expect(firstAnalytics.shouldShowMessage, true);
+
+ final secondAnalytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ );
+
+ expect(secondAnalytics.shouldShowMessage, true);
+
+ secondAnalytics.clientShowedMessage();
+
+ final thirdAnalytics = Analytics.fake(
+ tool: initialTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: toolsMessageVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: flutterVersion,
+ dartVersion: dartVersion,
+ fs: fs,
+ platform: platform,
+ );
+
+ expect(thirdAnalytics.shouldShowMessage, false);
+ });
+
+ test('Passing large version number gets logged in config', () {
+ final firstVersion = toolsMessageVersion + 3;
+ final secondAnalytics = Analytics.fake(
+ tool: secondTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: firstVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: 'Flutter 3.6.0-7.0.pre.47',
+ dartVersion: 'Dart 2.19.0',
+ fs: fs,
+ platform: platform,
+ );
+ secondAnalytics.clientShowedMessage();
+
+ expect(
+ configFile
+ .readAsStringSync()
+ .endsWith('${secondTool.label}=$dateStamp,$firstVersion\n'),
+ true);
+
+ // Create a new instane of the secondTool with an even
+ // bigger version
+ final secondVersion = firstVersion + 3;
+ final thirdAnalytics = Analytics.fake(
+ tool: secondTool,
+ homeDirectory: home,
+ flutterChannel: flutterChannel,
+ toolsMessageVersion: secondVersion,
+ toolsMessage: toolsMessage,
+ flutterVersion: 'Flutter 3.6.0-7.0.pre.47',
+ dartVersion: 'Dart 2.19.0',
+ fs: fs,
+ platform: platform,
+ );
+
+ expect(
+ configFile
+ .readAsStringSync()
+ .endsWith('${secondTool.label}=$dateStamp,$firstVersion\n'),
+ true);
+
+ // After invoking this method, it will get updated
+ // in the config with the next version
+ thirdAnalytics.clientShowedMessage();
+
+ expect(
+ configFile
+ .readAsStringSync()
+ .endsWith('${secondTool.label}=$dateStamp,$secondVersion\n'),
+ true);
+ });
+}
diff --git a/pkgs/watcher/.gitignore b/pkgs/watcher/.gitignore
new file mode 100644
index 0000000..ac98e87
--- /dev/null
+++ b/pkgs/watcher/.gitignore
@@ -0,0 +1,4 @@
+# Don’t commit the following directories created by pub.
+.dart_tool
+.packages
+pubspec.lock
diff --git a/pkgs/watcher/.test_config b/pkgs/watcher/.test_config
new file mode 100644
index 0000000..531426a
--- /dev/null
+++ b/pkgs/watcher/.test_config
@@ -0,0 +1,5 @@
+{
+ "test_package": {
+ "platforms": ["vm"]
+ }
+}
\ No newline at end of file
diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md
new file mode 100644
index 0000000..ef3a7e2
--- /dev/null
+++ b/pkgs/watcher/CHANGELOG.md
@@ -0,0 +1,130 @@
+## 1.1.1
+
+- Ensure `PollingFileWatcher.ready` completes for files that do not exist.
+- Require Dart SDK `^3.1.0`
+- Move to `dart-lang/tools` monorepo.
+
+## 1.1.0
+
+- Require Dart SDK >= 3.0.0
+- Remove usage of redundant ConstructableFileSystemEvent classes.
+
+## 1.0.3-dev
+
+- Require Dart SDK >= 2.19
+
+## 1.0.2
+
+- Require Dart SDK >= 2.14
+- Ensure `DirectoryWatcher.ready` completes even when errors occur that close the watcher.
+- Add markdown badges to the readme.
+
+## 1.0.1
+
+* Drop package:pedantic and use package:lints instead.
+
+## 1.0.0
+
+* Require Dart SDK >= 2.12
+* Add the ability to create custom Watcher types for specific file paths.
+
+## 0.9.7+15
+
+* Fix a bug on Mac where modifying a directory with a path exactly matching a
+ prefix of a modified file would suppress change events for that file.
+
+## 0.9.7+14
+
+* Prepare for breaking change in SDK where modified times for not found files
+ becomes meaningless instead of null.
+
+## 0.9.7+13
+
+* Catch & forward `FileSystemException` from unexpectedly closed file watchers
+ on windows; the watcher will also be automatically restarted when this occurs.
+
+## 0.9.7+12
+
+* Catch `FileSystemException` during `existsSync()` on Windows.
+* Internal cleanup.
+
+## 0.9.7+11
+
+* Fix an analysis hint.
+
+## 0.9.7+10
+
+* Set max SDK version to `<3.0.0`, and adjust other dependencies.
+
+## 0.9.7+9
+
+* Internal changes only.
+
+## 0.9.7+8
+
+* Fix Dart 2.0 type issues on Mac and Windows.
+
+## 0.9.7+7
+
+* Updates to support Dart 2.0 core library changes (wave 2.2).
+ See [issue 31847][sdk#31847] for details.
+
+ [sdk#31847]: https://github.com/dart-lang/sdk/issues/31847
+
+
+## 0.9.7+6
+
+* Internal changes only, namely removing dep on scheduled test.
+
+## 0.9.7+5
+
+* Fix an analysis warning.
+
+## 0.9.7+4
+
+* Declare support for `async` 2.0.0.
+
+## 0.9.7+3
+
+* Fix a crashing bug on Linux.
+
+## 0.9.7+2
+
+* Narrow the constraint on `async` to reflect the APIs this package is actually
+ using.
+
+## 0.9.7+1
+
+* Fix all strong-mode warnings.
+
+## 0.9.7
+
+* Fix a bug in `FileWatcher` where events could be added after watchers were
+ closed.
+
+## 0.9.6
+
+* Add a `Watcher` interface that encompasses watching both files and
+ directories.
+
+* Add `FileWatcher` and `PollingFileWatcher` classes for watching changes to
+ individual files.
+
+* Deprecate `DirectoryWatcher.directory`. Use `DirectoryWatcher.path` instead.
+
+## 0.9.5
+
+* Fix bugs where events could be added after watchers were closed.
+
+## 0.9.4
+
+* Treat add events for known files as modifications instead of discarding them
+ on Mac OS.
+
+## 0.9.3
+
+* Improved support for Windows via `WindowsDirectoryWatcher`.
+
+* Simplified `PollingDirectoryWatcher`.
+
+* Fixed bugs in `MacOSDirectoryWatcher`
diff --git a/pkgs/watcher/LICENSE b/pkgs/watcher/LICENSE
new file mode 100644
index 0000000..000cd7b
--- /dev/null
+++ b/pkgs/watcher/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2014, 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.
diff --git a/pkgs/watcher/README.md b/pkgs/watcher/README.md
new file mode 100644
index 0000000..83a0324
--- /dev/null
+++ b/pkgs/watcher/README.md
@@ -0,0 +1,10 @@
+[](https://github.com/dart-lang/tools/actions/workflows/watcher.yaml)
+[](https://pub.dev/packages/watcher)
+[](https://pub.dev/packages/watcher/publisher)
+
+A file system watcher.
+
+## What's this?
+
+`package:watcher` monitors changes to contents of directories and sends
+notifications when files have been added, removed, or modified.
diff --git a/pkgs/watcher/analysis_options.yaml b/pkgs/watcher/analysis_options.yaml
new file mode 100644
index 0000000..d978f81
--- /dev/null
+++ b/pkgs/watcher/analysis_options.yaml
@@ -0,0 +1 @@
+include: package:dart_flutter_team_lints/analysis_options.yaml
diff --git a/pkgs/watcher/benchmark/path_set.dart b/pkgs/watcher/benchmark/path_set.dart
new file mode 100644
index 0000000..e7929d8
--- /dev/null
+++ b/pkgs/watcher/benchmark/path_set.dart
@@ -0,0 +1,158 @@
+// Copyright (c) 2015, 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.
+
+/// Benchmarks for the PathSet class.
+library;
+
+import 'dart:io';
+import 'dart:math' as math;
+
+import 'package:benchmark_harness/benchmark_harness.dart';
+import 'package:path/path.dart' as p;
+import 'package:watcher/src/path_set.dart';
+
+final String root = Platform.isWindows ? r'C:\root' : '/root';
+
+/// Base class for benchmarks on [PathSet].
+abstract class PathSetBenchmark extends BenchmarkBase {
+ PathSetBenchmark(String method) : super('PathSet.$method');
+
+ final PathSet pathSet = PathSet(root);
+
+ /// Use a fixed [math.Random] with a constant seed to ensure the tests are
+ /// deterministic.
+ final math.Random random = math.Random(1234);
+
+ /// Walks over a virtual directory [depth] levels deep invoking [callback]
+ /// for each "file".
+ ///
+ /// Each virtual directory contains ten entries: either subdirectories or
+ /// files.
+ void walkTree(int depth, void Function(String) callback) {
+ void recurse(String path, int remainingDepth) {
+ for (var i = 0; i < 10; i++) {
+ var padded = i.toString().padLeft(2, '0');
+ if (remainingDepth == 0) {
+ callback(p.join(path, 'file_$padded.txt'));
+ } else {
+ var subdir = p.join(path, 'subdirectory_$padded');
+ recurse(subdir, remainingDepth - 1);
+ }
+ }
+ }
+
+ recurse(root, depth);
+ }
+}
+
+class AddBenchmark extends PathSetBenchmark {
+ AddBenchmark() : super('add()');
+
+ final List<String> paths = [];
+
+ @override
+ void setup() {
+ // Make a bunch of paths in about the same order we expect to get them from
+ // Directory.list().
+ walkTree(3, paths.add);
+ }
+
+ @override
+ void run() {
+ for (var path in paths) {
+ pathSet.add(path);
+ }
+ }
+}
+
+class ContainsBenchmark extends PathSetBenchmark {
+ ContainsBenchmark() : super('contains()');
+
+ final List<String> paths = [];
+
+ @override
+ void setup() {
+ // Add a bunch of paths to the set.
+ walkTree(3, (path) {
+ pathSet.add(path);
+ paths.add(path);
+ });
+
+ // Add some non-existent paths to test the false case.
+ for (var i = 0; i < 100; i++) {
+ paths.addAll([
+ '/nope',
+ '/root/nope',
+ '/root/subdirectory_04/nope',
+ '/root/subdirectory_04/subdirectory_04/nope',
+ '/root/subdirectory_04/subdirectory_04/subdirectory_04/nope',
+ '/root/subdirectory_04/subdirectory_04/subdirectory_04/nope/file_04.txt',
+ ]);
+ }
+ }
+
+ @override
+ void run() {
+ var contained = 0;
+ for (var path in paths) {
+ if (pathSet.contains(path)) contained++;
+ }
+
+ if (contained != 10000) throw StateError('Wrong result: $contained');
+ }
+}
+
+class PathsBenchmark extends PathSetBenchmark {
+ PathsBenchmark() : super('toSet()');
+
+ @override
+ void setup() {
+ walkTree(3, pathSet.add);
+ }
+
+ @override
+ void run() {
+ var count = 0;
+ for (var _ in pathSet.paths) {
+ count++;
+ }
+
+ if (count != 10000) throw StateError('Wrong result: $count');
+ }
+}
+
+class RemoveBenchmark extends PathSetBenchmark {
+ RemoveBenchmark() : super('remove()');
+
+ final List<String> paths = [];
+
+ @override
+ void setup() {
+ // Make a bunch of paths. Do this here so that we don't spend benchmarked
+ // time synthesizing paths.
+ walkTree(3, (path) {
+ pathSet.add(path);
+ paths.add(path);
+ });
+
+ // Shuffle the paths so that we delete them in a random order that
+ // hopefully mimics real-world file system usage. Do the shuffling here so
+ // that we don't spend benchmarked time shuffling.
+ paths.shuffle(random);
+ }
+
+ @override
+ void run() {
+ for (var path in paths) {
+ pathSet.remove(path);
+ }
+ }
+}
+
+void main() {
+ AddBenchmark().report();
+ ContainsBenchmark().report();
+ PathsBenchmark().report();
+ RemoveBenchmark().report();
+}
diff --git a/pkgs/watcher/example/watch.dart b/pkgs/watcher/example/watch.dart
new file mode 100644
index 0000000..37931d3
--- /dev/null
+++ b/pkgs/watcher/example/watch.dart
@@ -0,0 +1,19 @@
+// 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.
+
+/// Watches the given directory and prints each modification to it.
+library;
+
+import 'package:path/path.dart' as p;
+import 'package:watcher/watcher.dart';
+
+void main(List<String> arguments) {
+ if (arguments.length != 1) {
+ print('Usage: watch <directory path>');
+ return;
+ }
+
+ var watcher = DirectoryWatcher(p.absolute(arguments[0]));
+ watcher.events.listen(print);
+}
diff --git a/pkgs/watcher/lib/src/async_queue.dart b/pkgs/watcher/lib/src/async_queue.dart
new file mode 100644
index 0000000..f6c76a9
--- /dev/null
+++ b/pkgs/watcher/lib/src/async_queue.dart
@@ -0,0 +1,70 @@
+// 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:collection';
+
+typedef ItemProcessor<T> = Future<void> Function(T item);
+
+/// A queue of items that are sequentially, asynchronously processed.
+///
+/// Unlike [Stream.map] or [Stream.forEach], the callback used to process each
+/// item returns a [Future], and it will not advance to the next item until the
+/// current item is finished processing.
+///
+/// Items can be added at any point in time and processing will be started as
+/// needed. When all items are processed, it stops processing until more items
+/// are added.
+class AsyncQueue<T> {
+ final _items = Queue<T>();
+
+ /// Whether or not the queue is currently waiting on a processing future to
+ /// complete.
+ bool _isProcessing = false;
+
+ /// The callback to invoke on each queued item.
+ ///
+ /// The next item in the queue will not be processed until the [Future]
+ /// returned by this completes.
+ final ItemProcessor<T> _processor;
+
+ /// The handler for errors thrown during processing.
+ ///
+ /// Used to avoid top-leveling asynchronous errors.
+ final void Function(Object, StackTrace) _errorHandler;
+
+ AsyncQueue(this._processor,
+ {required void Function(Object, StackTrace) onError})
+ : _errorHandler = onError;
+
+ /// Enqueues [item] to be processed and starts asynchronously processing it
+ /// if a process isn't already running.
+ void add(T item) {
+ _items.add(item);
+
+ // Start up the asynchronous processing if not already running.
+ if (_isProcessing) return;
+ _isProcessing = true;
+
+ _processNextItem().catchError(_errorHandler);
+ }
+
+ /// Removes all remaining items to be processed.
+ void clear() {
+ _items.clear();
+ }
+
+ /// Pulls the next item off [_items] and processes it.
+ ///
+ /// When complete, recursively calls itself to continue processing unless
+ /// the process was cancelled.
+ Future<void> _processNextItem() async {
+ var item = _items.removeFirst();
+ await _processor(item);
+ if (_items.isNotEmpty) return _processNextItem();
+
+ // We have drained the queue, stop processing and wait until something
+ // has been enqueued.
+ _isProcessing = false;
+ }
+}
diff --git a/pkgs/watcher/lib/src/custom_watcher_factory.dart b/pkgs/watcher/lib/src/custom_watcher_factory.dart
new file mode 100644
index 0000000..fc4e3fb
--- /dev/null
+++ b/pkgs/watcher/lib/src/custom_watcher_factory.dart
@@ -0,0 +1,88 @@
+// Copyright (c) 2020, 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 '../watcher.dart';
+
+/// A factory to produce custom watchers for specific file paths.
+class _CustomWatcherFactory {
+ final String id;
+ final DirectoryWatcher? Function(String path, {Duration? pollingDelay})
+ createDirectoryWatcher;
+ final FileWatcher? Function(String path, {Duration? pollingDelay})
+ createFileWatcher;
+
+ _CustomWatcherFactory(
+ this.id, this.createDirectoryWatcher, this.createFileWatcher);
+}
+
+/// Registers a custom watcher.
+///
+/// Each custom watcher must have a unique [id] and the same watcher may not be
+/// registered more than once.
+/// [createDirectoryWatcher] and [createFileWatcher] should return watchers for
+/// the file paths they are able to handle. If the custom watcher is not able to
+/// handle the path it should return null.
+/// The paths handled by each custom watch may not overlap, at most one custom
+/// matcher may return a non-null watcher for a given path.
+///
+/// When a file or directory watcher is created the path is checked against each
+/// registered custom watcher, and if exactly one custom watcher is available it
+/// will be used instead of the default.
+void registerCustomWatcher(
+ String id,
+ DirectoryWatcher? Function(String path, {Duration? pollingDelay})?
+ createDirectoryWatcher,
+ FileWatcher? Function(String path, {Duration? pollingDelay})?
+ createFileWatcher,
+) {
+ if (_customWatcherFactories.containsKey(id)) {
+ throw ArgumentError('A custom watcher with id `$id` '
+ 'has already been registered');
+ }
+ _customWatcherFactories[id] = _CustomWatcherFactory(
+ id,
+ createDirectoryWatcher ?? (_, {pollingDelay}) => null,
+ createFileWatcher ?? (_, {pollingDelay}) => null);
+}
+
+/// Tries to create a custom [DirectoryWatcher] and returns it.
+///
+/// Returns `null` if no custom watcher was applicable and throws a [StateError]
+/// if more than one was.
+DirectoryWatcher? createCustomDirectoryWatcher(String path,
+ {Duration? pollingDelay}) {
+ DirectoryWatcher? customWatcher;
+ String? customFactoryId;
+ for (var watcherFactory in _customWatcherFactories.values) {
+ if (customWatcher != null) {
+ throw StateError('Two `CustomWatcherFactory`s applicable: '
+ '`$customFactoryId` and `${watcherFactory.id}` for `$path`');
+ }
+ customWatcher =
+ watcherFactory.createDirectoryWatcher(path, pollingDelay: pollingDelay);
+ customFactoryId = watcherFactory.id;
+ }
+ return customWatcher;
+}
+
+/// Tries to create a custom [FileWatcher] and returns it.
+///
+/// Returns `null` if no custom watcher was applicable and throws a [StateError]
+/// if more than one was.
+FileWatcher? createCustomFileWatcher(String path, {Duration? pollingDelay}) {
+ FileWatcher? customWatcher;
+ String? customFactoryId;
+ for (var watcherFactory in _customWatcherFactories.values) {
+ if (customWatcher != null) {
+ throw StateError('Two `CustomWatcherFactory`s applicable: '
+ '`$customFactoryId` and `${watcherFactory.id}` for `$path`');
+ }
+ customWatcher =
+ watcherFactory.createFileWatcher(path, pollingDelay: pollingDelay);
+ customFactoryId = watcherFactory.id;
+ }
+ return customWatcher;
+}
+
+final _customWatcherFactories = <String, _CustomWatcherFactory>{};
diff --git a/pkgs/watcher/lib/src/directory_watcher.dart b/pkgs/watcher/lib/src/directory_watcher.dart
new file mode 100644
index 0000000..158b86b
--- /dev/null
+++ b/pkgs/watcher/lib/src/directory_watcher.dart
@@ -0,0 +1,41 @@
+// 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';
+
+import '../watcher.dart';
+import 'custom_watcher_factory.dart';
+import 'directory_watcher/linux.dart';
+import 'directory_watcher/mac_os.dart';
+import 'directory_watcher/windows.dart';
+
+/// Watches the contents of a directory and emits [WatchEvent]s when something
+/// in the directory has changed.
+abstract class DirectoryWatcher implements Watcher {
+ /// The directory whose contents are being monitored.
+ @Deprecated('Expires in 1.0.0. Use DirectoryWatcher.path instead.')
+ String get directory;
+
+ /// Creates a new [DirectoryWatcher] monitoring [directory].
+ ///
+ /// If a native directory watcher is available for this platform, this will
+ /// use it. Otherwise, it will fall back to a [PollingDirectoryWatcher].
+ ///
+ /// If [pollingDelay] is passed, it specifies the amount of time the watcher
+ /// will pause between successive polls of the directory contents. Making this
+ /// shorter will give more immediate feedback at the expense of doing more IO
+ /// and higher CPU usage. Defaults to one second. Ignored for non-polling
+ /// watchers.
+ factory DirectoryWatcher(String directory, {Duration? pollingDelay}) {
+ if (FileSystemEntity.isWatchSupported) {
+ var customWatcher =
+ createCustomDirectoryWatcher(directory, pollingDelay: pollingDelay);
+ if (customWatcher != null) return customWatcher;
+ if (Platform.isLinux) return LinuxDirectoryWatcher(directory);
+ if (Platform.isMacOS) return MacOSDirectoryWatcher(directory);
+ if (Platform.isWindows) return WindowsDirectoryWatcher(directory);
+ }
+ return PollingDirectoryWatcher(directory, pollingDelay: pollingDelay);
+ }
+}
diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart
new file mode 100644
index 0000000..cb1d077
--- /dev/null
+++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart
@@ -0,0 +1,294 @@
+// 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';
+
+import 'package:async/async.dart';
+
+import '../directory_watcher.dart';
+import '../path_set.dart';
+import '../resubscribable.dart';
+import '../utils.dart';
+import '../watch_event.dart';
+
+/// Uses the inotify subsystem to watch for filesystem events.
+///
+/// Inotify doesn't suport recursively watching subdirectories, nor does
+/// [Directory.watch] polyfill that functionality. This class polyfills it
+/// instead.
+///
+/// This class also compensates for the non-inotify-specific issues of
+/// [Directory.watch] producing multiple events for a single logical action
+/// (issue 14372) and providing insufficient information about move events
+/// (issue 14424).
+class LinuxDirectoryWatcher extends ResubscribableWatcher
+ implements DirectoryWatcher {
+ @override
+ String get directory => path;
+
+ LinuxDirectoryWatcher(String directory)
+ : super(directory, () => _LinuxDirectoryWatcher(directory));
+}
+
+class _LinuxDirectoryWatcher
+ implements DirectoryWatcher, ManuallyClosedWatcher {
+ @override
+ String get directory => _files.root;
+ @override
+ String get path => _files.root;
+
+ @override
+ Stream<WatchEvent> get events => _eventsController.stream;
+ final _eventsController = StreamController<WatchEvent>.broadcast();
+
+ @override
+ bool get isReady => _readyCompleter.isCompleted;
+
+ @override
+ Future<void> get ready => _readyCompleter.future;
+ final _readyCompleter = Completer<void>();
+
+ /// A stream group for the [Directory.watch] events of [path] and all its
+ /// subdirectories.
+ final _nativeEvents = StreamGroup<FileSystemEvent>();
+
+ /// All known files recursively within [path].
+ final PathSet _files;
+
+ /// [Directory.watch] streams for [path]'s subdirectories, indexed by name.
+ ///
+ /// A stream is in this map if and only if it's also in [_nativeEvents].
+ final _subdirStreams = <String, Stream<FileSystemEvent>>{};
+
+ /// A set of all subscriptions that this watcher subscribes to.
+ ///
+ /// These are gathered together so that they may all be canceled when the
+ /// watcher is closed.
+ final _subscriptions = <StreamSubscription>{};
+
+ _LinuxDirectoryWatcher(String path) : _files = PathSet(path) {
+ _nativeEvents.add(Directory(path)
+ .watch()
+ .transform(StreamTransformer.fromHandlers(handleDone: (sink) {
+ // Handle the done event here rather than in the call to [_listen] because
+ // [innerStream] won't close until we close the [StreamGroup]. However, if
+ // we close the [StreamGroup] here, we run the risk of new-directory
+ // events being fired after the group is closed, since batching delays
+ // those events. See b/30768513.
+ _onDone();
+ })));
+
+ // Batch the inotify changes together so that we can dedup events.
+ var innerStream = _nativeEvents.stream.batchEvents();
+ _listen(innerStream, _onBatch,
+ onError: (Object error, StackTrace stackTrace) {
+ // Guarantee that ready always completes.
+ if (!isReady) {
+ _readyCompleter.complete();
+ }
+ _eventsController.addError(error, stackTrace);
+ });
+
+ _listen(
+ Directory(path).list(recursive: true),
+ (FileSystemEntity entity) {
+ if (entity is Directory) {
+ _watchSubdir(entity.path);
+ } else {
+ _files.add(entity.path);
+ }
+ },
+ onError: _emitError,
+ onDone: () {
+ if (!isReady) {
+ _readyCompleter.complete();
+ }
+ },
+ cancelOnError: true,
+ );
+ }
+
+ @override
+ void close() {
+ for (var subscription in _subscriptions) {
+ subscription.cancel();
+ }
+
+ _subscriptions.clear();
+ _subdirStreams.clear();
+ _files.clear();
+ _nativeEvents.close();
+ _eventsController.close();
+ }
+
+ /// Watch a subdirectory of [directory] for changes.
+ void _watchSubdir(String path) {
+ // TODO(nweiz): Right now it's possible for the watcher to emit an event for
+ // a file before the directory list is complete. This could lead to the user
+ // seeing a MODIFY or REMOVE event for a file before they see an ADD event,
+ // which is bad. We should handle that.
+ //
+ // One possibility is to provide a general means (e.g.
+ // `DirectoryWatcher.eventsAndExistingFiles`) to tell a watcher to emit
+ // events for all the files that already exist. This would be useful for
+ // 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();
+ _subdirStreams[path] = stream;
+ _nativeEvents.add(stream);
+ }
+
+ /// The callback that's run when a batch of changes comes in.
+ void _onBatch(List<FileSystemEvent> batch) {
+ var files = <String>{};
+ var dirs = <String>{};
+ var changed = <String>{};
+
+ // inotify event batches are ordered by occurrence, so we treat them as a
+ // log of what happened to a file. We only emit events based on the
+ // difference between the state before the batch and the state after it, not
+ // the intermediate state.
+ for (var event in batch) {
+ // If the watched directory is deleted or moved, we'll get a deletion
+ // event for it. Ignore it; we handle closing [this] when the underlying
+ // stream is closed.
+ if (event.path == path) continue;
+
+ 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);
+ }
+ }
+
+ _applyChanges(files, dirs, changed);
+ }
+
+ /// Applies the net changes computed for a batch.
+ ///
+ /// The [files] and [dirs] sets contain the files and directories that now
+ /// exist, respectively. The [changed] set contains all files and directories
+ /// that have changed (including being removed), and so is a superset of
+ /// [files] and [dirs].
+ void _applyChanges(Set<String> files, Set<String> dirs, Set<String> changed) {
+ for (var path in changed) {
+ var stream = _subdirStreams.remove(path);
+ if (stream != null) _nativeEvents.add(stream);
+
+ // Unless [path] was a file and still is, emit REMOVE events for it or its
+ // contents,
+ if (files.contains(path) && _files.contains(path)) continue;
+ for (var file in _files.remove(path)) {
+ _emitEvent(ChangeType.REMOVE, file);
+ }
+ }
+
+ for (var file in files) {
+ if (_files.contains(file)) {
+ _emitEvent(ChangeType.MODIFY, file);
+ } else {
+ _emitEvent(ChangeType.ADD, file);
+ _files.add(file);
+ }
+ }
+
+ for (var dir in dirs) {
+ _watchSubdir(dir);
+ _addSubdir(dir);
+ }
+ }
+
+ /// Emits [ChangeType.ADD] events for the recursive contents of [path].
+ void _addSubdir(String path) {
+ _listen(Directory(path).list(recursive: true), (FileSystemEntity entity) {
+ if (entity is Directory) {
+ _watchSubdir(entity.path);
+ } else {
+ _files.add(entity.path);
+ _emitEvent(ChangeType.ADD, entity.path);
+ }
+ }, onError: (Object error, StackTrace stackTrace) {
+ // Ignore an exception caused by the dir not existing. It's fine if it
+ // was added and then quickly removed.
+ if (error is FileSystemException) return;
+
+ _emitError(error, stackTrace);
+ }, cancelOnError: true);
+ }
+
+ /// Handles the underlying event stream closing, indicating that the directory
+ /// being watched was removed.
+ void _onDone() {
+ // Most of the time when a directory is removed, its contents will get
+ // individual REMOVE events before the watch stream is closed -- in that
+ // case, [_files] will be empty here. However, if the directory's removal is
+ // caused by a MOVE, we need to manually emit events.
+ if (isReady) {
+ for (var file in _files.paths) {
+ _emitEvent(ChangeType.REMOVE, file);
+ }
+ }
+
+ close();
+ }
+
+ /// Emits a [WatchEvent] with [type] and [path] if this watcher is in a state
+ /// to emit events.
+ void _emitEvent(ChangeType type, String path) {
+ if (!isReady) return;
+ if (_eventsController.isClosed) return;
+ _eventsController.add(WatchEvent(type, path));
+ }
+
+ /// Emit an error, then close the watcher.
+ void _emitError(Object error, StackTrace stackTrace) {
+ // Guarantee that ready always completes.
+ if (!isReady) {
+ _readyCompleter.complete();
+ }
+ _eventsController.addError(error, stackTrace);
+ close();
+ }
+
+ /// Like [Stream.listen], but automatically adds the subscription to
+ /// [_subscriptions] so that it can be canceled when [close] is called.
+ void _listen<T>(Stream<T> stream, void Function(T) onData,
+ {Function? onError,
+ void Function()? onDone,
+ bool cancelOnError = false}) {
+ late StreamSubscription<T> subscription;
+ subscription = stream.listen(onData, onError: onError, onDone: () {
+ _subscriptions.remove(subscription);
+ onDone?.call();
+ }, cancelOnError: cancelOnError);
+ _subscriptions.add(subscription);
+ }
+}
diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart
new file mode 100644
index 0000000..b461383
--- /dev/null
+++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart
@@ -0,0 +1,410 @@
+// 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';
+
+import 'package:path/path.dart' as p;
+
+import '../directory_watcher.dart';
+import '../path_set.dart';
+import '../resubscribable.dart';
+import '../utils.dart';
+import '../watch_event.dart';
+
+/// Uses the FSEvents subsystem to watch for filesystem events.
+///
+/// FSEvents has two main idiosyncrasies that this class works around. First, it
+/// will occasionally report events that occurred before the filesystem watch
+/// was initiated. Second, if multiple events happen to the same file in close
+/// succession, it won't report them in the order they occurred. See issue
+/// 14373.
+///
+/// This also works around issues 16003 and 14849 in the implementation of
+/// [Directory.watch].
+class MacOSDirectoryWatcher extends ResubscribableWatcher
+ implements DirectoryWatcher {
+ @override
+ String get directory => path;
+
+ MacOSDirectoryWatcher(String directory)
+ : super(directory, () => _MacOSDirectoryWatcher(directory));
+}
+
+class _MacOSDirectoryWatcher
+ implements DirectoryWatcher, ManuallyClosedWatcher {
+ @override
+ String get directory => path;
+ @override
+ final String path;
+
+ @override
+ Stream<WatchEvent> get events => _eventsController.stream;
+ final _eventsController = StreamController<WatchEvent>.broadcast();
+
+ @override
+ bool get isReady => _readyCompleter.isCompleted;
+
+ @override
+ Future<void> get ready => _readyCompleter.future;
+ final _readyCompleter = Completer<void>();
+
+ /// The set of files that are known to exist recursively within the watched
+ /// directory.
+ ///
+ /// The state of files on the filesystem is compared against this to determine
+ /// the real change that occurred when working around issue 14373. This is
+ /// also used to emit REMOVE events when subdirectories are moved out of the
+ /// watched directory.
+ final PathSet _files;
+
+ /// The subscription to the stream returned by [Directory.watch].
+ ///
+ /// This is separate from [_listSubscriptions] because this stream
+ /// occasionally needs to be resubscribed in order to work around issue 14849.
+ StreamSubscription<List<FileSystemEvent>>? _watchSubscription;
+
+ /// The subscription to the [Directory.list] call for the initial listing of
+ /// the directory to determine its initial state.
+ StreamSubscription<FileSystemEntity>? _initialListSubscription;
+
+ /// The subscriptions to [Directory.list] calls for listing the contents of a
+ /// subdirectory that was moved into the watched directory.
+ final _listSubscriptions = <StreamSubscription<FileSystemEntity>>{};
+
+ /// The timer for tracking how long we wait for an initial batch of bogus
+ /// events (see issue 14373).
+ late Timer _bogusEventTimer;
+
+ _MacOSDirectoryWatcher(this.path) : _files = PathSet(path) {
+ _startWatch();
+
+ // Before we're ready to emit events, wait for [_listDir] to complete and
+ // for enough time to elapse that if bogus events (issue 14373) would be
+ // emitted, they will be.
+ //
+ // If we do receive a batch of events, [_onBatch] will ensure that these
+ // futures don't fire and that the directory is re-listed.
+ Future.wait([_listDir(), _waitForBogusEvents()]).then((_) {
+ if (!isReady) {
+ _readyCompleter.complete();
+ }
+ });
+ }
+
+ @override
+ void close() {
+ _watchSubscription?.cancel();
+ _initialListSubscription?.cancel();
+ _watchSubscription = null;
+ _initialListSubscription = null;
+
+ for (var subscription in _listSubscriptions) {
+ subscription.cancel();
+ }
+ _listSubscriptions.clear();
+
+ _eventsController.close();
+ }
+
+ /// The callback that's run when [Directory.watch] emits a batch of events.
+ void _onBatch(List<FileSystemEvent> 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.
+ if (!isReady) {
+ // Cancel the timer because bogus events only occur in the first batch, so
+ // we can fire [ready] as soon as we're done listing the directory.
+ _bogusEventTimer.cancel();
+ _listDir().then((_) {
+ if (!isReady) {
+ _readyCompleter.complete();
+ }
+ });
+ return;
+ }
+
+ _sortEvents(batch).forEach((path, eventSet) {
+ var canonicalEvent = _canonicalEvent(eventSet);
+ var events = canonicalEvent == null
+ ? _eventsBasedOnFileSystem(path)
+ : [canonicalEvent];
+
+ for (var event in events) {
+ if (event is FileSystemCreateEvent) {
+ if (!event.isDirectory) {
+ // 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
+ // perspective, the file's contents just changed.
+ var type =
+ _files.contains(path) ? ChangeType.MODIFY : ChangeType.ADD;
+
+ _emitEvent(type, path);
+ _files.add(path);
+ continue;
+ }
+
+ if (_files.containsDir(path)) continue;
+
+ var stream = Directory(path).list(recursive: true);
+ 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);
+ }
+ }
+ }
+ });
+ }
+
+ /// 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<String, Set<FileSystemEvent>> _sortEvents(List<FileSystemEvent> batch) {
+ var eventsForPaths = <String, Set<FileSystemEvent>>{};
+
+ // 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 <String>{};
+ 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, () => <FileSystemEvent>{}).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<FileSystemEvent> batch) {
+ // An empty batch indicates that we've learned earlier that the batch is
+ // contradictory (e.g. because of a move).
+ 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;
+ }
+
+ // A CREATE event contradicts a REMOVE event and vice versa.
+ assert(type == FileSystemEvent.create || type == FileSystemEvent.delete);
+ if (type != event.type) 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;
+ }
+
+ 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');
+ }
+ }
+
+ /// Returns one or more events that describe the change between the last known
+ /// state of [path] and its current state on the filesystem.
+ ///
+ /// This returns a list whose order should be reflected in the events emitted
+ /// 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<FileSystemEvent> _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 = <FileSystemEvent>[];
+ if (fileExisted) {
+ if (fileExists) {
+ events.add(FileSystemModifyEvent(path, false, false));
+ } else {
+ events.add(FileSystemDeleteEvent(path, false));
+ }
+ } 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));
+ } else {
+ events.add(FileSystemDeleteEvent(path, true));
+ }
+ }
+
+ if (!fileExisted && fileExists) {
+ events.add(FileSystemCreateEvent(path, false));
+ } else if (!dirExisted && dirExists) {
+ events.add(FileSystemCreateEvent(path, true));
+ }
+
+ return events;
+ }
+
+ /// The callback that's run when the [Directory.watch] stream is closed.
+ void _onDone() {
+ _watchSubscription = null;
+
+ // If the directory still exists and we're still expecting bogus events,
+ // this is probably issue 14849 rather than a real close event. We should
+ // just restart the watcher.
+ if (!isReady && Directory(path).existsSync()) {
+ _startWatch();
+ return;
+ }
+
+ // FSEvents can fail to report the contents of the directory being removed
+ // when the directory itself is removed, so we need to manually mark the
+ // files as removed.
+ for (var file in _files.paths) {
+ _emitEvent(ChangeType.REMOVE, file);
+ }
+ _files.clear();
+ close();
+ }
+
+ /// 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();
+ _watchSubscription = innerStream.listen(_onBatch,
+ onError: _eventsController.addError, onDone: _onDone);
+ }
+
+ /// Starts or restarts listing the watched directory to get an initial picture
+ /// of its state.
+ Future<void> _listDir() {
+ assert(!isReady);
+ _initialListSubscription?.cancel();
+
+ _files.clear();
+ var completer = Completer<void>();
+ var stream = Directory(path).list(recursive: true);
+ _initialListSubscription = stream.listen((entity) {
+ if (entity is! Directory) _files.add(entity.path);
+ }, onError: _emitError, onDone: completer.complete, cancelOnError: true);
+ return completer.future;
+ }
+
+ /// Wait 200ms for a batch of bogus events (issue 14373) to come in.
+ ///
+ /// 200ms is short in terms of human interaction, but longer than any Mac OS
+ /// watcher tests take on the bots, so it should be safe to assume that any
+ /// bogus events will be signaled in that time frame.
+ Future<void> _waitForBogusEvents() {
+ var completer = Completer<void>();
+ _bogusEventTimer =
+ Timer(const Duration(milliseconds: 200), completer.complete);
+ return completer.future;
+ }
+
+ /// Emit an event with the given [type] and [path].
+ void _emitEvent(ChangeType type, String path) {
+ if (!isReady) return;
+ _eventsController.add(WatchEvent(type, path));
+ }
+
+ /// Emit an error, then close the watcher.
+ void _emitError(Object error, StackTrace stackTrace) {
+ // Guarantee that ready always completes.
+ if (!isReady) {
+ _readyCompleter.complete();
+ }
+ _eventsController.addError(error, stackTrace);
+ close();
+ }
+}
diff --git a/pkgs/watcher/lib/src/directory_watcher/polling.dart b/pkgs/watcher/lib/src/directory_watcher/polling.dart
new file mode 100644
index 0000000..207679b
--- /dev/null
+++ b/pkgs/watcher/lib/src/directory_watcher/polling.dart
@@ -0,0 +1,191 @@
+// 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';
+
+import '../async_queue.dart';
+import '../directory_watcher.dart';
+import '../resubscribable.dart';
+import '../stat.dart';
+import '../utils.dart';
+import '../watch_event.dart';
+
+/// Periodically polls a directory for changes.
+class PollingDirectoryWatcher extends ResubscribableWatcher
+ implements DirectoryWatcher {
+ @override
+ String get directory => path;
+
+ /// Creates a new polling watcher monitoring [directory].
+ ///
+ /// If [pollingDelay] is passed, it specifies the amount of time the watcher
+ /// will pause between successive polls of the directory contents. Making this
+ /// shorter will give more immediate feedback at the expense of doing more IO
+ /// and higher CPU usage. Defaults to one second.
+ PollingDirectoryWatcher(String directory, {Duration? pollingDelay})
+ : super(directory, () {
+ return _PollingDirectoryWatcher(
+ directory, pollingDelay ?? const Duration(seconds: 1));
+ });
+}
+
+class _PollingDirectoryWatcher
+ implements DirectoryWatcher, ManuallyClosedWatcher {
+ @override
+ String get directory => path;
+ @override
+ final String path;
+
+ @override
+ Stream<WatchEvent> get events => _events.stream;
+ final _events = StreamController<WatchEvent>.broadcast();
+
+ @override
+ bool get isReady => _readyCompleter.isCompleted;
+
+ @override
+ Future<void> get ready => _readyCompleter.future;
+ final _readyCompleter = Completer<void>();
+
+ /// The amount of time the watcher pauses between successive polls of the
+ /// directory contents.
+ final Duration _pollingDelay;
+
+ /// The previous modification times of the files in the directory.
+ ///
+ /// Used to tell which files have been modified.
+ final _lastModifieds = <String, DateTime?>{};
+
+ /// The subscription used while [directory] is being listed.
+ ///
+ /// Will be `null` if a list is not currently happening.
+ StreamSubscription<FileSystemEntity>? _listSubscription;
+
+ /// The queue of files waiting to be processed to see if they have been
+ /// modified.
+ ///
+ /// Processing a file is asynchronous, as is listing the directory, so the
+ /// queue exists to let each of those proceed at their own rate. The lister
+ /// will enqueue files as quickly as it can. Meanwhile, files are dequeued
+ /// and processed sequentially.
+ late final AsyncQueue<String?> _filesToProcess =
+ AsyncQueue<String?>(_processFile, onError: (error, stackTrace) {
+ if (!_events.isClosed) _events.addError(error, stackTrace);
+ });
+
+ /// The set of files that have been seen in the current directory listing.
+ ///
+ /// Used to tell which files have been removed: files that are in
+ /// [_lastModifieds] but not in here when a poll completes have been removed.
+ final _polledFiles = <String>{};
+
+ _PollingDirectoryWatcher(this.path, this._pollingDelay) {
+ _poll();
+ }
+
+ @override
+ void close() {
+ _events.close();
+
+ // If we're in the middle of listing the directory, stop.
+ _listSubscription?.cancel();
+
+ // Don't process any remaining files.
+ _filesToProcess.clear();
+ _polledFiles.clear();
+ _lastModifieds.clear();
+ }
+
+ /// Scans the contents of the directory once to see which files have been
+ /// added, removed, and modified.
+ void _poll() {
+ _filesToProcess.clear();
+ _polledFiles.clear();
+
+ void endListing() {
+ assert(!_events.isClosed);
+ _listSubscription = null;
+
+ // Null tells the queue consumer that we're done listing.
+ _filesToProcess.add(null);
+ }
+
+ var stream = Directory(path).list(recursive: true);
+ _listSubscription = stream.listen((entity) {
+ assert(!_events.isClosed);
+
+ if (entity is! File) return;
+ _filesToProcess.add(entity.path);
+ }, onError: (Object error, StackTrace stackTrace) {
+ // Guarantee that ready always completes.
+ if (!isReady) {
+ _readyCompleter.complete();
+ }
+ if (!isDirectoryNotFoundException(error)) {
+ // It's some unknown error. Pipe it over to the event stream so the
+ // user can see it.
+ _events.addError(error, stackTrace);
+ }
+
+ // When an error occurs, we end the listing normally, which has the
+ // desired effect of marking all files that were in the directory as
+ // being removed.
+ endListing();
+ }, onDone: endListing, cancelOnError: true);
+ }
+
+ /// Processes [file] to determine if it has been modified since the last
+ /// time it was scanned.
+ Future<void> _processFile(String? file) async {
+ // `null` is the sentinel which means the directory listing is complete.
+ if (file == null) {
+ await _completePoll();
+ return;
+ }
+
+ final modified = await modificationTime(file);
+
+ if (_events.isClosed) return;
+
+ var lastModified = _lastModifieds[file];
+
+ // If its modification time hasn't changed, assume the file is unchanged.
+ if (lastModified != null && lastModified == modified) {
+ // The file is still here.
+ _polledFiles.add(file);
+ return;
+ }
+
+ if (_events.isClosed) return;
+
+ _lastModifieds[file] = modified;
+ _polledFiles.add(file);
+
+ // Only notify if we're ready to emit events.
+ if (!isReady) return;
+
+ var type = lastModified == null ? ChangeType.ADD : ChangeType.MODIFY;
+ _events.add(WatchEvent(type, file));
+ }
+
+ /// After the directory listing is complete, this determines which files were
+ /// removed and then restarts the next poll.
+ Future<void> _completePoll() async {
+ // Any files that were not seen in the last poll but that we have a
+ // status for must have been removed.
+ var removedFiles = _lastModifieds.keys.toSet().difference(_polledFiles);
+ for (var removed in removedFiles) {
+ if (isReady) _events.add(WatchEvent(ChangeType.REMOVE, removed));
+ _lastModifieds.remove(removed);
+ }
+
+ if (!isReady) _readyCompleter.complete();
+
+ // Wait and then poll again.
+ await Future<void>.delayed(_pollingDelay);
+ if (_events.isClosed) return;
+ _poll();
+ }
+}
diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart
new file mode 100644
index 0000000..d1c98be
--- /dev/null
+++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart
@@ -0,0 +1,437 @@
+// Copyright (c) 2014, 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.
+// TODO(rnystrom): Merge with mac_os version.
+
+import 'dart:async';
+import 'dart:collection';
+import 'dart:io';
+
+import 'package:path/path.dart' as p;
+
+import '../directory_watcher.dart';
+import '../path_set.dart';
+import '../resubscribable.dart';
+import '../utils.dart';
+import '../watch_event.dart';
+
+class WindowsDirectoryWatcher extends ResubscribableWatcher
+ implements DirectoryWatcher {
+ @override
+ String get directory => path;
+
+ WindowsDirectoryWatcher(String directory)
+ : super(directory, () => _WindowsDirectoryWatcher(directory));
+}
+
+class _EventBatcher {
+ static const Duration _batchDelay = Duration(milliseconds: 100);
+ final List<FileSystemEvent> events = [];
+ Timer? timer;
+
+ void addEvent(FileSystemEvent event, void Function() callback) {
+ events.add(event);
+ timer?.cancel();
+ timer = Timer(_batchDelay, callback);
+ }
+
+ void cancelTimer() {
+ timer?.cancel();
+ }
+}
+
+class _WindowsDirectoryWatcher
+ implements DirectoryWatcher, ManuallyClosedWatcher {
+ @override
+ String get directory => path;
+ @override
+ final String path;
+
+ @override
+ Stream<WatchEvent> get events => _eventsController.stream;
+ final _eventsController = StreamController<WatchEvent>.broadcast();
+
+ @override
+ bool get isReady => _readyCompleter.isCompleted;
+
+ @override
+ Future<void> get ready => _readyCompleter.future;
+ final _readyCompleter = Completer<void>();
+
+ final Map<String, _EventBatcher> _eventBatchers =
+ HashMap<String, _EventBatcher>();
+
+ /// The set of files that are known to exist recursively within the watched
+ /// directory.
+ ///
+ /// The state of files on the filesystem is compared against this to determine
+ /// the real change that occurred. This is also used to emit REMOVE events
+ /// when subdirectories are moved out of the watched directory.
+ final PathSet _files;
+
+ /// The subscription to the stream returned by [Directory.watch].
+ StreamSubscription<FileSystemEvent>? _watchSubscription;
+
+ /// The subscription to the stream returned by [Directory.watch] of the
+ /// parent directory to [directory]. This is needed to detect changes to
+ /// [directory], as they are not included on Windows.
+ StreamSubscription<FileSystemEvent>? _parentWatchSubscription;
+
+ /// The subscription to the [Directory.list] call for the initial listing of
+ /// the directory to determine its initial state.
+ StreamSubscription<FileSystemEntity>? _initialListSubscription;
+
+ /// The subscriptions to the [Directory.list] calls for listing the contents
+ /// of subdirectories that were moved into the watched directory.
+ final Set<StreamSubscription<FileSystemEntity>> _listSubscriptions =
+ HashSet<StreamSubscription<FileSystemEntity>>();
+
+ _WindowsDirectoryWatcher(this.path) : _files = PathSet(path) {
+ // Before we're ready to emit events, wait for [_listDir] to complete.
+ _listDir().then((_) {
+ _startWatch();
+ _startParentWatcher();
+ if (!isReady) {
+ _readyCompleter.complete();
+ }
+ });
+ }
+
+ @override
+ void close() {
+ _watchSubscription?.cancel();
+ _parentWatchSubscription?.cancel();
+ _initialListSubscription?.cancel();
+ for (var sub in _listSubscriptions) {
+ sub.cancel();
+ }
+ _listSubscriptions.clear();
+ for (var batcher in _eventBatchers.values) {
+ batcher.cancelTimer();
+ }
+ _eventBatchers.clear();
+ _watchSubscription = null;
+ _parentWatchSubscription = null;
+ _initialListSubscription = null;
+ _eventsController.close();
+ }
+
+ /// On Windows, if [directory] is deleted, we will not receive any event.
+ ///
+ /// Instead, we add a watcher on the parent folder (if any), that can notify
+ /// us about [path]. This also includes events such as moves.
+ 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;
+ 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);
+ }
+ _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;
+ });
+ }
+
+ void _onEvent(FileSystemEvent event) {
+ assert(isReady);
+ final batcher = _eventBatchers.putIfAbsent(event.path, _EventBatcher.new);
+ batcher.addEvent(event, () {
+ _eventBatchers.remove(event.path);
+ _onBatch(batcher.events);
+ });
+ }
+
+ /// The callback that's run when [Directory.watch] emits a batch of events.
+ void _onBatch(List<FileSystemEvent> batch) {
+ _sortEvents(batch).forEach((path, eventSet) {
+ var canonicalEvent = _canonicalEvent(eventSet);
+ var events = canonicalEvent == null
+ ? _eventsBasedOnFileSystem(path)
+ : [canonicalEvent];
+
+ for (var event in events) {
+ if (event is FileSystemCreateEvent) {
+ if (!event.isDirectory) {
+ if (_files.contains(path)) continue;
+
+ _emitEvent(ChangeType.ADD, path);
+ _files.add(path);
+ continue;
+ }
+
+ if (_files.containsDir(path)) continue;
+
+ var stream = Directory(path).list(recursive: true);
+ 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((Object e, StackTrace stackTrace) {
+ _listSubscriptions.remove(subscription);
+ _emitError(e, stackTrace);
+ });
+ _listSubscriptions.add(subscription);
+ } else if (event is FileSystemModifyEvent) {
+ if (!event.isDirectory) {
+ _emitEvent(ChangeType.MODIFY, path);
+ }
+ } else {
+ assert(event is FileSystemDeleteEvent);
+ for (var removedPath in _files.remove(path)) {
+ _emitEvent(ChangeType.REMOVE, removedPath);
+ }
+ }
+ }
+ });
+ }
+
+ /// 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<String, Set<FileSystemEvent>> _sortEvents(List<FileSystemEvent> batch) {
+ var eventsForPaths = <String, Set<FileSystemEvent>>{};
+
+ // 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 <String>{};
+ 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, () => <FileSystemEvent>{}).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<FileSystemEvent> batch) {
+ // An empty batch indicates that we've learned earlier that the batch is
+ // contradictory (e.g. because of a move).
+ 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;
+ }
+
+ 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');
+ }
+ }
+
+ /// Returns zero or more events that describe the change between the last
+ /// known state of [path] and its current state on the filesystem.
+ ///
+ /// This returns a list whose order should be reflected in the events emitted
+ /// 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<FileSystemEvent> _eventsBasedOnFileSystem(String path) {
+ var fileExisted = _files.contains(path);
+ var dirExisted = _files.containsDir(path);
+
+ bool fileExists;
+ bool dirExists;
+ try {
+ fileExists = File(path).existsSync();
+ dirExists = Directory(path).existsSync();
+ } on FileSystemException {
+ return const <FileSystemEvent>[];
+ }
+
+ var events = <FileSystemEvent>[];
+ if (fileExisted) {
+ if (fileExists) {
+ events.add(FileSystemModifyEvent(path, false, false));
+ } else {
+ events.add(FileSystemDeleteEvent(path, false));
+ }
+ } 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));
+ } else {
+ events.add(FileSystemDeleteEvent(path, true));
+ }
+ }
+
+ if (!fileExisted && fileExists) {
+ events.add(FileSystemCreateEvent(path, false));
+ } else if (!dirExisted && dirExists) {
+ events.add(FileSystemCreateEvent(path, true));
+ }
+
+ return events;
+ }
+
+ /// The callback that's run when the [Directory.watch] stream is closed.
+ /// Note that this is unlikely to happen on Windows, unless the system itself
+ /// closes the handle.
+ void _onDone() {
+ _watchSubscription = null;
+
+ // Emit remove events for any remaining files.
+ for (var file in _files.paths) {
+ _emitEvent(ChangeType.REMOVE, file);
+ }
+ _files.clear();
+ close();
+ }
+
+ /// 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.
+ 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;
+ }
+ });
+ }
+
+ /// Starts or restarts listing the watched directory to get an initial picture
+ /// of its state.
+ Future<void> _listDir() {
+ assert(!isReady);
+ _initialListSubscription?.cancel();
+
+ _files.clear();
+ var completer = Completer<void>();
+ var stream = Directory(path).list(recursive: true);
+ void handleEntity(FileSystemEntity entity) {
+ if (entity is! Directory) _files.add(entity.path);
+ }
+
+ _initialListSubscription = stream.listen(handleEntity,
+ onError: _emitError, onDone: completer.complete, cancelOnError: true);
+ return completer.future;
+ }
+
+ /// Emit an event with the given [type] and [path].
+ void _emitEvent(ChangeType type, String path) {
+ if (!isReady) return;
+
+ _eventsController.add(WatchEvent(type, path));
+ }
+
+ /// Emit an error, then close the watcher.
+ void _emitError(Object error, StackTrace stackTrace) {
+ // Guarantee that ready always completes.
+ if (!isReady) {
+ _readyCompleter.complete();
+ }
+ _eventsController.addError(error, stackTrace);
+ close();
+ }
+}
diff --git a/pkgs/watcher/lib/src/file_watcher.dart b/pkgs/watcher/lib/src/file_watcher.dart
new file mode 100644
index 0000000..143aa31
--- /dev/null
+++ b/pkgs/watcher/lib/src/file_watcher.dart
@@ -0,0 +1,44 @@
+// Copyright (c) 2015, 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 '../watcher.dart';
+import 'custom_watcher_factory.dart';
+import 'file_watcher/native.dart';
+
+/// Watches a file and emits [WatchEvent]s when the file has changed.
+///
+/// Note that since each watcher only watches a single file, it will only emit
+/// [ChangeType.MODIFY] events, except when the file is deleted at which point
+/// it will emit a single [ChangeType.REMOVE] event and then close the stream.
+///
+/// If the file is deleted and quickly replaced (when a new file is moved in its
+/// place, for example) this will emit a [ChangeType.MODIFY] event.
+abstract class FileWatcher implements Watcher {
+ /// Creates a new [FileWatcher] monitoring [file].
+ ///
+ /// If a native file watcher is available for this platform, this will use it.
+ /// Otherwise, it will fall back to a [PollingFileWatcher]. Notably, native
+ /// file watching is *not* supported on Windows.
+ ///
+ /// If [pollingDelay] is passed, it specifies the amount of time the watcher
+ /// will pause between successive polls of the directory contents. Making this
+ /// shorter will give more immediate feedback at the expense of doing more IO
+ /// and higher CPU usage. Defaults to one second. Ignored for non-polling
+ /// watchers.
+ factory FileWatcher(String file, {Duration? pollingDelay}) {
+ var customWatcher =
+ createCustomFileWatcher(file, pollingDelay: pollingDelay);
+ if (customWatcher != null) return customWatcher;
+
+ // [File.watch] doesn't work on Windows, but
+ // [FileSystemEntity.isWatchSupported] is still true because directory
+ // watching does work.
+ if (FileSystemEntity.isWatchSupported && !Platform.isWindows) {
+ return NativeFileWatcher(file);
+ }
+ return PollingFileWatcher(file, pollingDelay: pollingDelay);
+ }
+}
diff --git a/pkgs/watcher/lib/src/file_watcher/native.dart b/pkgs/watcher/lib/src/file_watcher/native.dart
new file mode 100644
index 0000000..502aa10
--- /dev/null
+++ b/pkgs/watcher/lib/src/file_watcher/native.dart
@@ -0,0 +1,90 @@
+// Copyright (c) 2015, 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';
+
+import '../file_watcher.dart';
+import '../resubscribable.dart';
+import '../utils.dart';
+import '../watch_event.dart';
+
+/// Uses the native file system notifications to watch for filesystem events.
+///
+/// Single-file notifications are much simpler than those for multiple files, so
+/// this doesn't need to be split out into multiple OS-specific classes.
+class NativeFileWatcher extends ResubscribableWatcher implements FileWatcher {
+ NativeFileWatcher(String path) : super(path, () => _NativeFileWatcher(path));
+}
+
+class _NativeFileWatcher implements FileWatcher, ManuallyClosedWatcher {
+ @override
+ final String path;
+
+ @override
+ Stream<WatchEvent> get events => _eventsController.stream;
+ final _eventsController = StreamController<WatchEvent>.broadcast();
+
+ @override
+ bool get isReady => _readyCompleter.isCompleted;
+
+ @override
+ Future<void> get ready => _readyCompleter.future;
+ final _readyCompleter = Completer<void>();
+
+ StreamSubscription<List<FileSystemEvent>>? _subscription;
+
+ _NativeFileWatcher(this.path) {
+ _listen();
+
+ // We don't need to do any initial set-up, so we're ready immediately after
+ // being listened to.
+ _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 _onBatch(List<FileSystemEvent> batch) {
+ if (batch.any((event) => event.type == FileSystemEvent.delete)) {
+ // If the file is deleted, the underlying stream will close. We handle
+ // emitting our own REMOVE event in [_onDone].
+ return;
+ }
+
+ _eventsController.add(WatchEvent(ChangeType.MODIFY, path));
+ }
+
+ void _onDone() async {
+ var fileExists = await File(path).exists();
+
+ // Check for this after checking whether the file exists because it's
+ // possible that [close] was called between [File.exists] being called and
+ // it completing.
+ if (_eventsController.isClosed) return;
+
+ if (fileExists) {
+ // If the file exists now, it was probably removed and quickly replaced;
+ // this can happen for example when another file is moved on top of it.
+ // Re-subscribe and report a modify event.
+ _eventsController.add(WatchEvent(ChangeType.MODIFY, path));
+ _listen();
+ } else {
+ _eventsController.add(WatchEvent(ChangeType.REMOVE, path));
+ close();
+ }
+ }
+
+ @override
+ void close() {
+ _subscription?.cancel();
+ _subscription = null;
+ _eventsController.close();
+ }
+}
diff --git a/pkgs/watcher/lib/src/file_watcher/polling.dart b/pkgs/watcher/lib/src/file_watcher/polling.dart
new file mode 100644
index 0000000..15ff9ab
--- /dev/null
+++ b/pkgs/watcher/lib/src/file_watcher/polling.dart
@@ -0,0 +1,106 @@
+// Copyright (c) 2015, 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';
+
+import '../file_watcher.dart';
+import '../resubscribable.dart';
+import '../stat.dart';
+import '../watch_event.dart';
+
+/// Periodically polls a file for changes.
+class PollingFileWatcher extends ResubscribableWatcher implements FileWatcher {
+ PollingFileWatcher(String path, {Duration? pollingDelay})
+ : super(path, () {
+ return _PollingFileWatcher(
+ path, pollingDelay ?? const Duration(seconds: 1));
+ });
+}
+
+class _PollingFileWatcher implements FileWatcher, ManuallyClosedWatcher {
+ @override
+ final String path;
+
+ @override
+ Stream<WatchEvent> get events => _eventsController.stream;
+ final _eventsController = StreamController<WatchEvent>.broadcast();
+
+ @override
+ bool get isReady => _readyCompleter.isCompleted;
+
+ @override
+ Future<void> get ready => _readyCompleter.future;
+ final _readyCompleter = Completer<void>();
+
+ /// The timer that controls polling.
+ late final Timer _timer;
+
+ /// The previous modification time of the file.
+ ///
+ /// `null` indicates the file does not (or did not on the last poll) exist.
+ DateTime? _lastModified;
+
+ _PollingFileWatcher(this.path, Duration pollingDelay) {
+ _timer = Timer.periodic(pollingDelay, (_) => _poll());
+ _poll();
+ }
+
+ /// Checks the mtime of the file and whether it's been removed.
+ Future<void> _poll() async {
+ // We don't mark the file as removed if this is the first poll. Instead,
+ // below we forward the dart:io error that comes from trying to read the
+ // mtime below.
+ var pathExists = await File(path).exists();
+ if (_eventsController.isClosed) return;
+
+ if (_lastModified != null && !pathExists) {
+ _flagReady();
+ _eventsController.add(WatchEvent(ChangeType.REMOVE, path));
+ unawaited(close());
+ return;
+ }
+
+ DateTime? modified;
+ try {
+ modified = await modificationTime(path);
+ } on FileSystemException catch (error, stackTrace) {
+ if (!_eventsController.isClosed) {
+ _flagReady();
+ _eventsController.addError(error, stackTrace);
+ await close();
+ }
+ }
+ if (_eventsController.isClosed) {
+ _flagReady();
+ return;
+ }
+
+ if (!isReady) {
+ // If this is the first poll, don't emit an event, just set the last mtime
+ // and complete the completer.
+ _lastModified = modified;
+ _flagReady();
+ return;
+ }
+
+ if (_lastModified == modified) return;
+
+ _lastModified = modified;
+ _eventsController.add(WatchEvent(ChangeType.MODIFY, path));
+ }
+
+ /// Flags this watcher as ready if it has not already been done.
+ void _flagReady() {
+ if (!isReady) {
+ _readyCompleter.complete();
+ }
+ }
+
+ @override
+ Future<void> close() async {
+ _timer.cancel();
+ await _eventsController.close();
+ }
+}
diff --git a/pkgs/watcher/lib/src/path_set.dart b/pkgs/watcher/lib/src/path_set.dart
new file mode 100644
index 0000000..4f41cf9
--- /dev/null
+++ b/pkgs/watcher/lib/src/path_set.dart
@@ -0,0 +1,190 @@
+// 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:collection';
+
+import 'package:path/path.dart' as p;
+
+/// A set of paths, organized into a directory hierarchy.
+///
+/// When a path is [add]ed, it creates an implicit directory structure above
+/// that path. Directories can be inspected using [containsDir] and removed
+/// using [remove]. If they're removed, their contents are removed as well.
+///
+/// The paths in the set are normalized so that they all begin with [root].
+class PathSet {
+ /// The root path, which all paths in the set must be under.
+ final String root;
+
+ /// The path set's directory hierarchy.
+ ///
+ /// Each entry represents a directory or file. It may be a file or directory
+ /// that was explicitly added, or a parent directory that was implicitly
+ /// added in order to add a child.
+ final _Entry _entries = _Entry();
+
+ PathSet(this.root);
+
+ /// Adds [path] to the set.
+ void add(String path) {
+ path = _normalize(path);
+
+ var parts = p.split(path);
+ var entry = _entries;
+ for (var part in parts) {
+ entry = entry.contents.putIfAbsent(part, _Entry.new);
+ }
+
+ entry.isExplicit = true;
+ }
+
+ /// Removes [path] and any paths beneath it from the set and returns the
+ /// removed paths.
+ ///
+ /// Even if [path] itself isn't in the set, if it's a directory containing
+ /// paths that are in the set those paths will be removed and returned.
+ ///
+ /// If neither [path] nor any paths beneath it are in the set, returns an
+ /// empty set.
+ Set<String> remove(String path) {
+ path = _normalize(path);
+ var parts = Queue.of(p.split(path));
+
+ // Remove the children of [dir], as well as [dir] itself if necessary.
+ //
+ // [partialPath] is the path to [dir], and a prefix of [path]; the remaining
+ // components of [path] are in [parts].
+ Set<String> recurse(_Entry dir, String partialPath) {
+ if (parts.length > 1) {
+ // If there's more than one component left in [path], recurse down to
+ // the next level.
+ var part = parts.removeFirst();
+ var entry = dir.contents[part];
+ if (entry == null || entry.contents.isEmpty) return <String>{};
+
+ partialPath = p.join(partialPath, part);
+ var paths = recurse(entry, partialPath);
+ // After removing this entry's children, if it has no more children and
+ // it's not in the set in its own right, remove it as well.
+ if (entry.contents.isEmpty && !entry.isExplicit) {
+ dir.contents.remove(part);
+ }
+ return paths;
+ }
+
+ // If there's only one component left in [path], we should remove it.
+ var entry = dir.contents.remove(parts.first);
+ if (entry == null) return <String>{};
+
+ if (entry.contents.isEmpty) {
+ return {p.join(root, path)};
+ }
+
+ var set = _explicitPathsWithin(entry, path);
+ if (entry.isExplicit) {
+ set.add(p.join(root, path));
+ }
+
+ return set;
+ }
+
+ return recurse(_entries, root);
+ }
+
+ /// Recursively lists all of the explicit paths within [dir].
+ ///
+ /// [dirPath] should be the path to [dir].
+ Set<String> _explicitPathsWithin(_Entry dir, String dirPath) {
+ var paths = <String>{};
+ void recurse(_Entry dir, String path) {
+ dir.contents.forEach((name, entry) {
+ var entryPath = p.join(path, name);
+ if (entry.isExplicit) paths.add(p.join(root, entryPath));
+
+ recurse(entry, entryPath);
+ });
+ }
+
+ recurse(dir, dirPath);
+ return paths;
+ }
+
+ /// Returns whether this set contains [path].
+ ///
+ /// This only returns true for paths explicitly added to this set.
+ /// Implicitly-added directories can be inspected using [containsDir].
+ bool contains(String path) {
+ path = _normalize(path);
+ var entry = _entries;
+
+ for (var part in p.split(path)) {
+ var child = entry.contents[part];
+ if (child == null) return false;
+ entry = child;
+ }
+
+ return entry.isExplicit;
+ }
+
+ /// Returns whether this set contains paths beneath [path].
+ bool containsDir(String path) {
+ path = _normalize(path);
+ var entry = _entries;
+
+ for (var part in p.split(path)) {
+ var child = entry.contents[part];
+ if (child == null) return false;
+ entry = child;
+ }
+
+ return entry.contents.isNotEmpty;
+ }
+
+ /// All of the paths explicitly added to this set.
+ List<String> get paths {
+ var result = <String>[];
+
+ void recurse(_Entry dir, String path) {
+ for (var mapEntry in dir.contents.entries) {
+ var entry = mapEntry.value;
+ var entryPath = p.join(path, mapEntry.key);
+ if (entry.isExplicit) result.add(entryPath);
+ recurse(entry, entryPath);
+ }
+ }
+
+ recurse(_entries, root);
+ return result;
+ }
+
+ /// Removes all paths from this set.
+ void clear() {
+ _entries.contents.clear();
+ }
+
+ /// Returns a normalized version of [path].
+ ///
+ /// This removes any extra ".." or "."s and ensure that the returned path
+ /// begins with [root]. It's an error if [path] isn't within [root].
+ String _normalize(String path) {
+ assert(p.isWithin(root, path));
+
+ return p.relative(p.normalize(path), from: root);
+ }
+}
+
+/// A virtual file system entity tracked by the [PathSet].
+///
+/// It may have child entries in [contents], which implies it's a directory.
+class _Entry {
+ /// The child entries contained in this directory.
+ final Map<String, _Entry> contents = {};
+
+ /// If this entry was explicitly added as a leaf file system entity, this
+ /// will be true.
+ ///
+ /// Otherwise, it represents a parent directory that was implicitly added
+ /// when added some child of it.
+ bool isExplicit = false;
+}
diff --git a/pkgs/watcher/lib/src/resubscribable.dart b/pkgs/watcher/lib/src/resubscribable.dart
new file mode 100644
index 0000000..b99e9d7
--- /dev/null
+++ b/pkgs/watcher/lib/src/resubscribable.dart
@@ -0,0 +1,79 @@
+// 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 '../watcher.dart';
+
+/// A wrapper for [ManuallyClosedWatcher] that encapsulates support for closing
+/// the watcher when it has no subscribers and re-opening it when it's
+/// re-subscribed.
+///
+/// It's simpler to implement watchers without worrying about this behavior.
+/// This class wraps a watcher class which can be written with the simplifying
+/// assumption that it can continue emitting events until an explicit `close`
+/// method is called, at which point it will cease emitting events entirely. The
+/// [ManuallyClosedWatcher] interface is used for these watchers.
+///
+/// This would be more cleanly implemented as a function that takes a class and
+/// emits a new class, but Dart doesn't support that sort of thing. Instead it
+/// takes a factory function that produces instances of the inner class.
+abstract class ResubscribableWatcher implements Watcher {
+ /// The factory function that produces instances of the inner class.
+ final ManuallyClosedWatcher Function() _factory;
+
+ @override
+ final String path;
+
+ @override
+ Stream<WatchEvent> get events => _eventsController.stream;
+ late StreamController<WatchEvent> _eventsController;
+
+ @override
+ bool get isReady => _readyCompleter.isCompleted;
+
+ @override
+ Future<void> get ready => _readyCompleter.future;
+ var _readyCompleter = Completer<void>();
+
+ /// Creates a new [ResubscribableWatcher] wrapping the watchers
+ /// emitted by [_factory].
+ ResubscribableWatcher(this.path, this._factory) {
+ late ManuallyClosedWatcher watcher;
+ late StreamSubscription<WatchEvent> subscription;
+
+ _eventsController = StreamController<WatchEvent>.broadcast(
+ onListen: () async {
+ watcher = _factory();
+ subscription = watcher.events.listen(_eventsController.add,
+ onError: _eventsController.addError,
+ onDone: _eventsController.close);
+
+ // It's important that we complete the value of [_readyCompleter] at
+ // the time [onListen] is called, as opposed to the value when
+ // [watcher.ready] fires. A new completer may be created by that time.
+ await watcher.ready;
+ _readyCompleter.complete();
+ },
+ onCancel: () {
+ // Cancel the subscription before closing the watcher so that the
+ // watcher's `onDone` event doesn't close [events].
+ subscription.cancel();
+ watcher.close();
+ _readyCompleter = Completer();
+ },
+ sync: true);
+ }
+}
+
+/// An interface for watchers with an explicit, manual [close] method.
+///
+/// See [ResubscribableWatcher].
+abstract class ManuallyClosedWatcher implements Watcher {
+ /// Closes the watcher.
+ ///
+ /// Subclasses should close their [events] stream and release any internal
+ /// resources.
+ void close();
+}
diff --git a/pkgs/watcher/lib/src/stat.dart b/pkgs/watcher/lib/src/stat.dart
new file mode 100644
index 0000000..fe0f155
--- /dev/null
+++ b/pkgs/watcher/lib/src/stat.dart
@@ -0,0 +1,34 @@
+// 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';
+
+/// A function that takes a file path and returns the last modified time for
+/// the file at that path.
+typedef MockTimeCallback = DateTime? Function(String path);
+
+MockTimeCallback? _mockTimeCallback;
+
+/// Overrides the default behavior for accessing a file's modification time
+/// with [callback].
+///
+/// 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) {
+ _mockTimeCallback = callback;
+}
+
+/// Gets the modification time for the file at [path].
+/// Completes with `null` if the file does not exist.
+Future<DateTime?> modificationTime(String path) async {
+ var mockTimeCallback = _mockTimeCallback;
+ if (mockTimeCallback != null) {
+ return mockTimeCallback(path);
+ }
+
+ final stat = await FileStat.stat(path);
+ if (stat.type == FileSystemEntityType.notFound) return null;
+ return stat.modified;
+}
diff --git a/pkgs/watcher/lib/src/utils.dart b/pkgs/watcher/lib/src/utils.dart
new file mode 100644
index 0000000..c2e71b3
--- /dev/null
+++ b/pkgs/watcher/lib/src/utils.dart
@@ -0,0 +1,52 @@
+// 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:collection';
+import 'dart:io';
+
+/// Returns `true` if [error] is a [FileSystemException] for a missing
+/// directory.
+bool isDirectoryNotFoundException(Object error) {
+ if (error is! FileSystemException) return false;
+
+ // See dartbug.com/12461 and tests/standalone/io/directory_error_test.dart.
+ var notFoundCode = Platform.operatingSystem == 'windows' ? 3 : 2;
+ return error.osError?.errorCode == notFoundCode;
+}
+
+/// Returns the union of all elements in each set in [sets].
+Set<T> unionAll<T>(Iterable<Set<T>> sets) =>
+ sets.fold(<T>{}, (union, set) => union.union(set));
+
+extension BatchEvents<T> on Stream<T> {
+ /// Batches all events that are sent at the same time.
+ ///
+ /// When multiple events are synchronously added to a stream controller, the
+ /// [StreamController] implementation uses [scheduleMicrotask] to schedule the
+ /// asynchronous firing of each event. In order to recreate the synchronous
+ /// batches, this collates all the events that are received in "nearby"
+ /// microtasks.
+ Stream<List<T>> batchEvents() {
+ var batch = Queue<T>();
+ return StreamTransformer<T, List<T>>.fromHandlers(
+ handleData: (event, sink) {
+ batch.add(event);
+
+ // [Timer.run] schedules an event that runs after any microtasks that have
+ // been scheduled.
+ Timer.run(() {
+ if (batch.isEmpty) return;
+ sink.add(batch.toList());
+ batch.clear();
+ });
+ }, handleDone: (sink) {
+ if (batch.isNotEmpty) {
+ sink.add(batch.toList());
+ batch.clear();
+ }
+ sink.close();
+ }).bind(this);
+ }
+}
diff --git a/pkgs/watcher/lib/src/watch_event.dart b/pkgs/watcher/lib/src/watch_event.dart
new file mode 100644
index 0000000..b65afc2
--- /dev/null
+++ b/pkgs/watcher/lib/src/watch_event.dart
@@ -0,0 +1,38 @@
+// 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.
+
+/// An event describing a single change to the file system.
+class WatchEvent {
+ /// The manner in which the file at [path] has changed.
+ final ChangeType type;
+
+ /// The path of the file that changed.
+ final String path;
+
+ WatchEvent(this.type, this.path);
+
+ @override
+ String toString() => '$type $path';
+}
+
+/// Enum for what kind of change has happened to a file.
+class ChangeType {
+ /// A new file has been added.
+ // ignore: constant_identifier_names
+ static const ADD = ChangeType('add');
+
+ /// A file has been removed.
+ // ignore: constant_identifier_names
+ static const REMOVE = ChangeType('remove');
+
+ /// The contents of a file have changed.
+ // ignore: constant_identifier_names
+ static const MODIFY = ChangeType('modify');
+
+ final String _name;
+ const ChangeType(this._name);
+
+ @override
+ String toString() => _name;
+}
diff --git a/pkgs/watcher/lib/watcher.dart b/pkgs/watcher/lib/watcher.dart
new file mode 100644
index 0000000..12a5369
--- /dev/null
+++ b/pkgs/watcher/lib/watcher.dart
@@ -0,0 +1,70 @@
+// 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';
+
+import 'src/directory_watcher.dart';
+import 'src/file_watcher.dart';
+import 'src/watch_event.dart';
+
+export 'src/custom_watcher_factory.dart' show registerCustomWatcher;
+export 'src/directory_watcher.dart';
+export 'src/directory_watcher/polling.dart';
+export 'src/file_watcher.dart';
+export 'src/file_watcher/polling.dart';
+export 'src/watch_event.dart';
+
+abstract class Watcher {
+ /// The path to the file or directory whose contents are being monitored.
+ String get path;
+
+ /// The broadcast [Stream] of events that have occurred to the watched file or
+ /// files in the watched directory.
+ ///
+ /// Changes will only be monitored while this stream has subscribers. Any
+ /// changes that occur during periods when there are no subscribers will not
+ /// be reported the next time a subscriber is added.
+ Stream<WatchEvent> get events;
+
+ /// Whether the watcher is initialized and watching for changes.
+ ///
+ /// This is true if and only if [ready] is complete.
+ bool get isReady;
+
+ /// A [Future] that completes when the watcher is initialized and watching for
+ /// changes.
+ ///
+ /// If the watcher is not currently monitoring the file or directory (because
+ /// there are no subscribers to [events]), this returns a future that isn't
+ /// complete yet. It will complete when a subscriber starts listening and the
+ /// watcher finishes any initialization work it needs to do.
+ ///
+ /// If the watcher is already monitoring, this returns an already complete
+ /// future.
+ ///
+ /// This future always completes successfully as errors are provided through
+ /// the [events] stream.
+ Future get ready;
+
+ /// Creates a new [DirectoryWatcher] or [FileWatcher] monitoring [path],
+ /// depending on whether it's a file or directory.
+ ///
+ /// If a native watcher is available for this platform, this will use it.
+ /// Otherwise, it will fall back to a polling watcher. Notably, watching
+ /// individual files is not natively supported on Windows, although watching
+ /// directories is.
+ ///
+ /// If [pollingDelay] is passed, it specifies the amount of time the watcher
+ /// will pause between successive polls of the contents of [path]. Making this
+ /// shorter will give more immediate feedback at the expense of doing more IO
+ /// and higher CPU usage. Defaults to one second. Ignored for non-polling
+ /// watchers.
+ factory Watcher(String path, {Duration? pollingDelay}) {
+ if (File(path).existsSync()) {
+ return FileWatcher(path, pollingDelay: pollingDelay);
+ } else {
+ return DirectoryWatcher(path, pollingDelay: pollingDelay);
+ }
+ }
+}
diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml
new file mode 100644
index 0000000..7781bd4
--- /dev/null
+++ b/pkgs/watcher/pubspec.yaml
@@ -0,0 +1,19 @@
+name: watcher
+version: 1.1.1
+description: >-
+ A file system watcher. It monitors changes to contents of directories and
+ sends notifications when files have been added, removed, or modified.
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/watcher
+
+environment:
+ sdk: ^3.1.0
+
+dependencies:
+ async: ^2.5.0
+ path: ^1.8.0
+
+dev_dependencies:
+ benchmark_harness: ^2.0.0
+ dart_flutter_team_lints: ^3.0.0
+ test: ^1.16.6
+ test_descriptor: ^2.0.0
diff --git a/pkgs/watcher/test/custom_watcher_factory_test.dart b/pkgs/watcher/test/custom_watcher_factory_test.dart
new file mode 100644
index 0000000..e9d65bb
--- /dev/null
+++ b/pkgs/watcher/test/custom_watcher_factory_test.dart
@@ -0,0 +1,142 @@
+import 'dart:async';
+
+import 'package:test/test.dart';
+import 'package:watcher/watcher.dart';
+
+void main() {
+ late _MemFs memFs;
+ final memFsFactoryId = 'MemFs';
+ final noOpFactoryId = 'NoOp';
+
+ setUpAll(() {
+ memFs = _MemFs();
+ var memFsWatcherFactory = _MemFsWatcherFactory(memFs);
+ var noOpWatcherFactory = _NoOpWatcherFactory();
+ registerCustomWatcher(
+ noOpFactoryId,
+ noOpWatcherFactory.createDirectoryWatcher,
+ noOpWatcherFactory.createFileWatcher);
+ registerCustomWatcher(
+ memFsFactoryId,
+ memFsWatcherFactory.createDirectoryWatcher,
+ memFsWatcherFactory.createFileWatcher);
+ });
+
+ test('notifies for files', () async {
+ var watcher = FileWatcher('file.txt');
+
+ var completer = Completer<WatchEvent>();
+ watcher.events.listen((event) => completer.complete(event));
+ await watcher.ready;
+ memFs.add('file.txt');
+ var event = await completer.future;
+
+ expect(event.type, ChangeType.ADD);
+ expect(event.path, 'file.txt');
+ });
+
+ test('notifies for directories', () async {
+ var watcher = DirectoryWatcher('dir');
+
+ var completer = Completer<WatchEvent>();
+ watcher.events.listen((event) => completer.complete(event));
+ await watcher.ready;
+ memFs.add('dir');
+ var event = await completer.future;
+
+ expect(event.type, ChangeType.ADD);
+ expect(event.path, 'dir');
+ });
+
+ test('registering twice throws', () async {
+ expect(
+ () => registerCustomWatcher(
+ memFsFactoryId,
+ (_, {pollingDelay}) => throw UnimplementedError(),
+ (_, {pollingDelay}) => throw UnimplementedError()),
+ throwsA(isA<ArgumentError>()),
+ );
+ });
+
+ test('finding two applicable factories throws', () async {
+ // Note that _MemFsWatcherFactory always returns a watcher, so having two
+ // will always produce a conflict.
+ var watcherFactory = _MemFsWatcherFactory(memFs);
+ registerCustomWatcher('Different id', watcherFactory.createDirectoryWatcher,
+ watcherFactory.createFileWatcher);
+ expect(() => FileWatcher('file.txt'), throwsA(isA<StateError>()));
+ expect(() => DirectoryWatcher('dir'), throwsA(isA<StateError>()));
+ });
+}
+
+class _MemFs {
+ final _streams = <String, Set<StreamController<WatchEvent>>>{};
+
+ StreamController<WatchEvent> watchStream(String path) {
+ var controller = StreamController<WatchEvent>();
+ _streams
+ .putIfAbsent(path, () => <StreamController<WatchEvent>>{})
+ .add(controller);
+ return controller;
+ }
+
+ void add(String path) {
+ var controllers = _streams[path];
+ if (controllers != null) {
+ for (var controller in controllers) {
+ controller.add(WatchEvent(ChangeType.ADD, path));
+ }
+ }
+ }
+
+ void remove(String path) {
+ var controllers = _streams[path];
+ if (controllers != null) {
+ for (var controller in controllers) {
+ controller.add(WatchEvent(ChangeType.REMOVE, path));
+ }
+ }
+ }
+}
+
+class _MemFsWatcher implements FileWatcher, DirectoryWatcher, Watcher {
+ final String _path;
+ final StreamController<WatchEvent> _controller;
+
+ _MemFsWatcher(this._path, this._controller);
+
+ @override
+ String get path => _path;
+
+ @override
+ String get directory => throw UnsupportedError('directory is not supported');
+
+ @override
+ Stream<WatchEvent> get events => _controller.stream;
+
+ @override
+ bool get isReady => true;
+
+ @override
+ Future<void> get ready async {}
+}
+
+class _MemFsWatcherFactory {
+ final _MemFs _memFs;
+ _MemFsWatcherFactory(this._memFs);
+
+ DirectoryWatcher? createDirectoryWatcher(String path,
+ {Duration? pollingDelay}) =>
+ _MemFsWatcher(path, _memFs.watchStream(path));
+
+ FileWatcher? createFileWatcher(String path, {Duration? pollingDelay}) =>
+ _MemFsWatcher(path, _memFs.watchStream(path));
+}
+
+class _NoOpWatcherFactory {
+ DirectoryWatcher? createDirectoryWatcher(String path,
+ {Duration? pollingDelay}) =>
+ null;
+
+ FileWatcher? createFileWatcher(String path, {Duration? pollingDelay}) => null;
+}
diff --git a/pkgs/watcher/test/directory_watcher/linux_test.dart b/pkgs/watcher/test/directory_watcher/linux_test.dart
new file mode 100644
index 0000000..a10a72c
--- /dev/null
+++ b/pkgs/watcher/test/directory_watcher/linux_test.dart
@@ -0,0 +1,44 @@
+// 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.
+
+@TestOn('linux')
+library;
+
+import 'package:test/test.dart';
+import 'package:watcher/src/directory_watcher/linux.dart';
+import 'package:watcher/watcher.dart';
+
+import '../utils.dart';
+import 'shared.dart';
+
+void main() {
+ watcherFactory = LinuxDirectoryWatcher.new;
+
+ sharedTests();
+
+ test('DirectoryWatcher creates a LinuxDirectoryWatcher on Linux', () {
+ expect(DirectoryWatcher('.'), const TypeMatcher<LinuxDirectoryWatcher>());
+ });
+
+ test('emits events for many nested files moved out then immediately back in',
+ () async {
+ withPermutations(
+ (i, j, k) => writeFile('dir/sub/sub-$i/sub-$j/file-$k.txt'));
+ await startWatcher(path: 'dir');
+
+ renameDir('dir/sub', 'sub');
+ renameDir('sub', 'dir/sub');
+
+ await allowEither(() {
+ inAnyOrder(withPermutations(
+ (i, j, k) => isRemoveEvent('dir/sub/sub-$i/sub-$j/file-$k.txt')));
+
+ inAnyOrder(withPermutations(
+ (i, j, k) => isAddEvent('dir/sub/sub-$i/sub-$j/file-$k.txt')));
+ }, () {
+ inAnyOrder(withPermutations(
+ (i, j, k) => isModifyEvent('dir/sub/sub-$i/sub-$j/file-$k.txt')));
+ });
+ });
+}
diff --git a/pkgs/watcher/test/directory_watcher/mac_os_test.dart b/pkgs/watcher/test/directory_watcher/mac_os_test.dart
new file mode 100644
index 0000000..3376626
--- /dev/null
+++ b/pkgs/watcher/test/directory_watcher/mac_os_test.dart
@@ -0,0 +1,69 @@
+// 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.
+
+@TestOn('mac-os')
+library;
+
+import 'package:test/test.dart';
+import 'package:watcher/src/directory_watcher/mac_os.dart';
+import 'package:watcher/watcher.dart';
+
+import '../utils.dart';
+import 'shared.dart';
+
+void main() {
+ watcherFactory = MacOSDirectoryWatcher.new;
+
+ sharedTests();
+
+ test('DirectoryWatcher creates a MacOSDirectoryWatcher on Mac OS', () {
+ expect(DirectoryWatcher('.'), const TypeMatcher<MacOSDirectoryWatcher>());
+ });
+
+ test(
+ 'does not notify about the watched directory being deleted and '
+ 'recreated immediately before watching', () async {
+ createDir('dir');
+ writeFile('dir/old.txt');
+ deleteDir('dir');
+ createDir('dir');
+
+ await startWatcher(path: 'dir');
+ writeFile('dir/newer.txt');
+ await expectAddEvent('dir/newer.txt');
+ });
+
+ test('emits events for many nested files moved out then immediately back in',
+ () async {
+ withPermutations(
+ (i, j, k) => writeFile('dir/sub/sub-$i/sub-$j/file-$k.txt'));
+
+ await startWatcher(path: 'dir');
+
+ renameDir('dir/sub', 'sub');
+ renameDir('sub', 'dir/sub');
+
+ await allowEither(() {
+ inAnyOrder(withPermutations(
+ (i, j, k) => isRemoveEvent('dir/sub/sub-$i/sub-$j/file-$k.txt')));
+
+ inAnyOrder(withPermutations(
+ (i, j, k) => isAddEvent('dir/sub/sub-$i/sub-$j/file-$k.txt')));
+ }, () {
+ inAnyOrder(withPermutations(
+ (i, j, k) => isModifyEvent('dir/sub/sub-$i/sub-$j/file-$k.txt')));
+ });
+ });
+ test('does not suppress files with the same prefix as a directory', () async {
+ // Regression test for https://github.com/dart-lang/watcher/issues/83
+ writeFile('some_name.txt');
+
+ await startWatcher();
+
+ writeFile('some_name/some_name.txt');
+ deleteFile('some_name.txt');
+
+ await expectRemoveEvent('some_name.txt');
+ });
+}
diff --git a/pkgs/watcher/test/directory_watcher/polling_test.dart b/pkgs/watcher/test/directory_watcher/polling_test.dart
new file mode 100644
index 0000000..f4ec8f4
--- /dev/null
+++ b/pkgs/watcher/test/directory_watcher/polling_test.dart
@@ -0,0 +1,26 @@
+// 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:test/test.dart';
+import 'package:watcher/watcher.dart';
+
+import '../utils.dart';
+import 'shared.dart';
+
+void main() {
+ // Use a short delay to make the tests run quickly.
+ watcherFactory = (dir) => PollingDirectoryWatcher(dir,
+ pollingDelay: const Duration(milliseconds: 100));
+
+ sharedTests();
+
+ 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');
+ });
+}
diff --git a/pkgs/watcher/test/directory_watcher/shared.dart b/pkgs/watcher/test/directory_watcher/shared.dart
new file mode 100644
index 0000000..1ebc78d
--- /dev/null
+++ b/pkgs/watcher/test/directory_watcher/shared.dart
@@ -0,0 +1,344 @@
+// 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 'package:test/test.dart';
+import 'package:watcher/src/utils.dart';
+
+import '../utils.dart';
+
+void sharedTests() {
+ test('does not notify for files that already exist when started', () async {
+ // Make some pre-existing files.
+ writeFile('a.txt');
+ writeFile('b.txt');
+
+ await startWatcher();
+
+ // Change one after the watcher is running.
+ writeFile('b.txt', contents: 'modified');
+
+ // We should get a modify event for the changed file, but no add events
+ // for them before this.
+ await expectModifyEvent('b.txt');
+ });
+
+ test('notifies when a file is added', () async {
+ await startWatcher();
+ writeFile('file.txt');
+ await expectAddEvent('file.txt');
+ });
+
+ test('notifies when a file is modified', () async {
+ writeFile('file.txt');
+ await startWatcher();
+ writeFile('file.txt', contents: 'modified');
+ await expectModifyEvent('file.txt');
+ });
+
+ test('notifies when a file is removed', () async {
+ writeFile('file.txt');
+ await startWatcher();
+ deleteFile('file.txt');
+ await expectRemoveEvent('file.txt');
+ });
+
+ test('notifies when a file is modified multiple times', () async {
+ writeFile('file.txt');
+ await startWatcher();
+ writeFile('file.txt', contents: 'modified');
+ await expectModifyEvent('file.txt');
+ writeFile('file.txt', contents: 'modified again');
+ await expectModifyEvent('file.txt');
+ });
+
+ test('notifies even if the file contents are unchanged', () async {
+ writeFile('a.txt', contents: 'same');
+ writeFile('b.txt', contents: 'before');
+ await startWatcher();
+
+ writeFile('a.txt', contents: 'same');
+ writeFile('b.txt', contents: 'after');
+ await inAnyOrder([isModifyEvent('a.txt'), isModifyEvent('b.txt')]);
+ });
+
+ test('when the watched directory is deleted, removes all files', () async {
+ writeFile('dir/a.txt');
+ writeFile('dir/b.txt');
+
+ await startWatcher(path: 'dir');
+
+ deleteDir('dir');
+ await inAnyOrder([isRemoveEvent('dir/a.txt'), isRemoveEvent('dir/b.txt')]);
+ });
+
+ test('when the watched directory is moved, removes all files', () async {
+ writeFile('dir/a.txt');
+ writeFile('dir/b.txt');
+
+ await startWatcher(path: 'dir');
+
+ renameDir('dir', 'moved_dir');
+ createDir('dir');
+ await inAnyOrder([isRemoveEvent('dir/a.txt'), isRemoveEvent('dir/b.txt')]);
+ });
+
+ // Regression test for b/30768513.
+ test(
+ "doesn't crash when the directory is moved immediately after a subdir "
+ 'is added', () async {
+ writeFile('dir/a.txt');
+ writeFile('dir/b.txt');
+
+ await startWatcher(path: 'dir');
+
+ createDir('dir/subdir');
+ renameDir('dir', 'moved_dir');
+ createDir('dir');
+ await inAnyOrder([isRemoveEvent('dir/a.txt'), isRemoveEvent('dir/b.txt')]);
+ });
+
+ group('moves', () {
+ test('notifies when a file is moved within the watched directory',
+ () async {
+ writeFile('old.txt');
+ await startWatcher();
+ renameFile('old.txt', 'new.txt');
+
+ await inAnyOrder([isAddEvent('new.txt'), isRemoveEvent('old.txt')]);
+ });
+
+ test('notifies when a file is moved from outside the watched directory',
+ () async {
+ writeFile('old.txt');
+ createDir('dir');
+ await startWatcher(path: 'dir');
+
+ renameFile('old.txt', 'dir/new.txt');
+ await expectAddEvent('dir/new.txt');
+ });
+
+ test('notifies when a file is moved outside the watched directory',
+ () async {
+ writeFile('dir/old.txt');
+ await startWatcher(path: 'dir');
+
+ renameFile('dir/old.txt', 'new.txt');
+ await expectRemoveEvent('dir/old.txt');
+ });
+
+ test('notifies when a file is moved onto an existing one', () async {
+ writeFile('from.txt');
+ writeFile('to.txt');
+ await startWatcher();
+
+ 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')
+ });
+ });
+
+ // Most of the time, when multiple filesystem actions happen in sequence,
+ // they'll be batched together and the watcher will see them all at once.
+ // These tests verify that the watcher normalizes and combine these events
+ // properly. However, very occasionally the events will be reported in
+ // separate batches, and the watcher will report them as though they occurred
+ // far apart in time, so each of these tests has a "backup case" to allow for
+ // that as well.
+ group('clustered changes', () {
+ test("doesn't notify when a file is created and then immediately removed",
+ () async {
+ writeFile('test.txt');
+ await startWatcher();
+ writeFile('file.txt');
+ deleteFile('file.txt');
+
+ // Backup case.
+ startClosingEventStream();
+ await allowEvents(() {
+ expectAddEvent('file.txt');
+ expectRemoveEvent('file.txt');
+ });
+ });
+
+ test(
+ 'reports a modification when a file is deleted and then immediately '
+ 'recreated', () async {
+ writeFile('file.txt');
+ await startWatcher();
+
+ deleteFile('file.txt');
+ writeFile('file.txt', contents: 're-created');
+
+ await allowEither(() {
+ expectModifyEvent('file.txt');
+ }, () {
+ // Backup case.
+ expectRemoveEvent('file.txt');
+ expectAddEvent('file.txt');
+ });
+ });
+
+ test(
+ 'reports a modification when a file is moved and then immediately '
+ 'recreated', () async {
+ writeFile('old.txt');
+ await startWatcher();
+
+ renameFile('old.txt', 'new.txt');
+ writeFile('old.txt', contents: 're-created');
+
+ await allowEither(() {
+ inAnyOrder([isModifyEvent('old.txt'), isAddEvent('new.txt')]);
+ }, () {
+ // Backup case.
+ expectRemoveEvent('old.txt');
+ expectAddEvent('new.txt');
+ expectAddEvent('old.txt');
+ });
+ });
+
+ test(
+ 'reports a removal when a file is modified and then immediately '
+ 'removed', () async {
+ writeFile('file.txt');
+ await startWatcher();
+
+ writeFile('file.txt', contents: 'modified');
+ deleteFile('file.txt');
+
+ // Backup case.
+ await allowModifyEvent('file.txt');
+
+ await expectRemoveEvent('file.txt');
+ });
+
+ test('reports an add when a file is added and then immediately modified',
+ () async {
+ await startWatcher();
+
+ writeFile('file.txt');
+ writeFile('file.txt', contents: 'modified');
+
+ await expectAddEvent('file.txt');
+
+ // Backup case.
+ startClosingEventStream();
+ await allowModifyEvent('file.txt');
+ });
+ });
+
+ group('subdirectories', () {
+ test('watches files in subdirectories', () async {
+ await startWatcher();
+ writeFile('a/b/c/d/file.txt');
+ await expectAddEvent('a/b/c/d/file.txt');
+ });
+
+ test(
+ 'notifies when a subdirectory is moved within the watched directory '
+ 'and then its contents are modified', () async {
+ writeFile('old/file.txt');
+ await startWatcher();
+
+ renameDir('old', 'new');
+ await inAnyOrder(
+ [isRemoveEvent('old/file.txt'), isAddEvent('new/file.txt')]);
+
+ writeFile('new/file.txt', contents: 'modified');
+ await expectModifyEvent('new/file.txt');
+ });
+
+ test('notifies when a file is replaced by a subdirectory', () async {
+ writeFile('new');
+ writeFile('old/file.txt');
+ await startWatcher();
+
+ deleteFile('new');
+ renameDir('old', 'new');
+ await inAnyOrder([
+ isRemoveEvent('new'),
+ isRemoveEvent('old/file.txt'),
+ isAddEvent('new/file.txt')
+ ]);
+ });
+
+ test('notifies when a subdirectory is replaced by a file', () async {
+ writeFile('old');
+ writeFile('new/file.txt');
+ await startWatcher();
+
+ renameDir('new', 'newer');
+ renameFile('old', 'new');
+ await inAnyOrder([
+ isRemoveEvent('new/file.txt'),
+ isAddEvent('newer/file.txt'),
+ 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 {
+ withPermutations((i, j, k) => writeFile('sub/sub-$i/sub-$j/file-$k.txt'));
+
+ createDir('dir');
+ await startWatcher(path: 'dir');
+ renameDir('sub', 'dir/sub');
+
+ await inAnyOrder(withPermutations(
+ (i, j, k) => isAddEvent('dir/sub/sub-$i/sub-$j/file-$k.txt')));
+ });
+
+ test('emits events for many nested files removed at once', () async {
+ withPermutations(
+ (i, j, k) => writeFile('dir/sub/sub-$i/sub-$j/file-$k.txt'));
+
+ createDir('dir');
+ await startWatcher(path: 'dir');
+
+ // Rename the directory rather than deleting it because native watchers
+ // report a rename as a single DELETE event for the directory, whereas
+ // they report recursive deletion with DELETE events for every file in the
+ // directory.
+ renameDir('dir/sub', 'sub');
+
+ await inAnyOrder(withPermutations(
+ (i, j, k) => isRemoveEvent('dir/sub/sub-$i/sub-$j/file-$k.txt')));
+ });
+
+ test('emits events for many nested files moved at once', () async {
+ withPermutations(
+ (i, j, k) => writeFile('dir/old/sub-$i/sub-$j/file-$k.txt'));
+
+ createDir('dir');
+ await startWatcher(path: 'dir');
+ renameDir('dir/old', 'dir/new');
+
+ await inAnyOrder(unionAll(withPermutations((i, j, k) {
+ return {
+ isRemoveEvent('dir/old/sub-$i/sub-$j/file-$k.txt'),
+ isAddEvent('dir/new/sub-$i/sub-$j/file-$k.txt')
+ };
+ })));
+ });
+
+ test(
+ 'emits events for many files added at once in a subdirectory with the '
+ 'same name as a removed file', () async {
+ writeFile('dir/sub');
+ withPermutations((i, j, k) => writeFile('old/sub-$i/sub-$j/file-$k.txt'));
+ await startWatcher(path: 'dir');
+
+ deleteFile('dir/sub');
+ renameDir('old', 'dir/sub');
+
+ var events = withPermutations(
+ (i, j, k) => isAddEvent('dir/sub/sub-$i/sub-$j/file-$k.txt'));
+ events.add(isRemoveEvent('dir/sub'));
+ await inAnyOrder(events);
+ });
+ });
+}
diff --git a/pkgs/watcher/test/directory_watcher/windows_test.dart b/pkgs/watcher/test/directory_watcher/windows_test.dart
new file mode 100644
index 0000000..499e7fb
--- /dev/null
+++ b/pkgs/watcher/test/directory_watcher/windows_test.dart
@@ -0,0 +1,23 @@
+// Copyright (c) 2014, 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('windows')
+library;
+
+import 'package:test/test.dart';
+import 'package:watcher/src/directory_watcher/windows.dart';
+import 'package:watcher/watcher.dart';
+
+import '../utils.dart';
+import 'shared.dart';
+
+void main() {
+ watcherFactory = WindowsDirectoryWatcher.new;
+
+ group('Shared Tests:', sharedTests);
+
+ test('DirectoryWatcher creates a WindowsDirectoryWatcher on Windows', () {
+ expect(DirectoryWatcher('.'), const TypeMatcher<WindowsDirectoryWatcher>());
+ });
+}
diff --git a/pkgs/watcher/test/file_watcher/native_test.dart b/pkgs/watcher/test/file_watcher/native_test.dart
new file mode 100644
index 0000000..0d4ad63
--- /dev/null
+++ b/pkgs/watcher/test/file_watcher/native_test.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2015, 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('linux || mac-os')
+library;
+
+import 'package:test/test.dart';
+import 'package:watcher/src/file_watcher/native.dart';
+
+import '../utils.dart';
+import 'shared.dart';
+
+void main() {
+ watcherFactory = NativeFileWatcher.new;
+
+ setUp(() {
+ writeFile('file.txt');
+ });
+
+ sharedTests();
+}
diff --git a/pkgs/watcher/test/file_watcher/polling_test.dart b/pkgs/watcher/test/file_watcher/polling_test.dart
new file mode 100644
index 0000000..861fcb2
--- /dev/null
+++ b/pkgs/watcher/test/file_watcher/polling_test.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2015, 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 'package:watcher/watcher.dart';
+
+import '../utils.dart';
+import 'shared.dart';
+
+void main() {
+ watcherFactory = (file) =>
+ PollingFileWatcher(file, pollingDelay: const Duration(milliseconds: 100));
+
+ setUp(() {
+ writeFile('file.txt');
+ });
+
+ sharedTests();
+}
diff --git a/pkgs/watcher/test/file_watcher/shared.dart b/pkgs/watcher/test/file_watcher/shared.dart
new file mode 100644
index 0000000..081b92e
--- /dev/null
+++ b/pkgs/watcher/test/file_watcher/shared.dart
@@ -0,0 +1,73 @@
+// Copyright (c) 2015, 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 sharedTests() {
+ test("doesn't notify if the file isn't modified", () async {
+ await startWatcher(path: 'file.txt');
+ await pumpEventQueue();
+ deleteFile('file.txt');
+ await expectRemoveEvent('file.txt');
+ });
+
+ test('notifies when a file is modified', () async {
+ await startWatcher(path: 'file.txt');
+ writeFile('file.txt', contents: 'modified');
+ await expectModifyEvent('file.txt');
+ });
+
+ test('notifies when a file is removed', () async {
+ await startWatcher(path: 'file.txt');
+ deleteFile('file.txt');
+ await expectRemoveEvent('file.txt');
+ });
+
+ test('notifies when a file is modified multiple times', () async {
+ await startWatcher(path: 'file.txt');
+ writeFile('file.txt', contents: 'modified');
+ await expectModifyEvent('file.txt');
+ writeFile('file.txt', contents: 'modified again');
+ await expectModifyEvent('file.txt');
+ });
+
+ test('notifies even if the file contents are unchanged', () async {
+ await startWatcher(path: 'file.txt');
+ writeFile('file.txt');
+ await expectModifyEvent('file.txt');
+ });
+
+ test('emits a remove event when the watched file is moved away', () async {
+ await startWatcher(path: 'file.txt');
+ renameFile('file.txt', 'new.txt');
+ await expectRemoveEvent('file.txt');
+ });
+
+ test(
+ 'emits a modify event when another file is moved on top of the watched '
+ 'file', () async {
+ writeFile('old.txt');
+ await startWatcher(path: 'file.txt');
+ renameFile('old.txt', 'file.txt');
+ await expectModifyEvent('file.txt');
+ });
+
+ // Regression test for a race condition.
+ test('closes the watcher immediately after deleting the file', () async {
+ writeFile('old.txt');
+ var watcher = createWatcher(path: 'file.txt');
+ var sub = watcher.events.listen(null);
+
+ deleteFile('file.txt');
+ await Future<void>.delayed(const Duration(milliseconds: 10));
+ await sub.cancel();
+ });
+
+ test('ready completes even if file does not exist', () async {
+ // startWatcher awaits 'ready'
+ await startWatcher(path: 'foo/bar/baz');
+ });
+}
diff --git a/pkgs/watcher/test/no_subscription/linux_test.dart b/pkgs/watcher/test/no_subscription/linux_test.dart
new file mode 100644
index 0000000..aac0810
--- /dev/null
+++ b/pkgs/watcher/test/no_subscription/linux_test.dart
@@ -0,0 +1,18 @@
+// 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.
+
+@TestOn('linux')
+library;
+
+import 'package:test/test.dart';
+import 'package:watcher/src/directory_watcher/linux.dart';
+
+import '../utils.dart';
+import 'shared.dart';
+
+void main() {
+ watcherFactory = LinuxDirectoryWatcher.new;
+
+ sharedTests();
+}
diff --git a/pkgs/watcher/test/no_subscription/mac_os_test.dart b/pkgs/watcher/test/no_subscription/mac_os_test.dart
new file mode 100644
index 0000000..55a8308
--- /dev/null
+++ b/pkgs/watcher/test/no_subscription/mac_os_test.dart
@@ -0,0 +1,18 @@
+// 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.
+
+@TestOn('mac-os')
+library;
+
+import 'package:test/test.dart';
+import 'package:watcher/src/directory_watcher/mac_os.dart';
+
+import '../utils.dart';
+import 'shared.dart';
+
+void main() {
+ watcherFactory = MacOSDirectoryWatcher.new;
+
+ sharedTests();
+}
diff --git a/pkgs/watcher/test/no_subscription/polling_test.dart b/pkgs/watcher/test/no_subscription/polling_test.dart
new file mode 100644
index 0000000..bfd2958
--- /dev/null
+++ b/pkgs/watcher/test/no_subscription/polling_test.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 'package:watcher/watcher.dart';
+
+import '../utils.dart';
+import 'shared.dart';
+
+void main() {
+ watcherFactory = PollingDirectoryWatcher.new;
+
+ sharedTests();
+}
diff --git a/pkgs/watcher/test/no_subscription/shared.dart b/pkgs/watcher/test/no_subscription/shared.dart
new file mode 100644
index 0000000..e7a6144
--- /dev/null
+++ b/pkgs/watcher/test/no_subscription/shared.dart
@@ -0,0 +1,54 @@
+// 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:async';
+
+import 'package:async/async.dart';
+import 'package:test/test.dart';
+import 'package:watcher/watcher.dart';
+
+import '../utils.dart';
+
+void sharedTests() {
+ test('does not notify for changes when there are no subscribers', () async {
+ // Note that this test doesn't rely as heavily on the test functions in
+ // utils.dart because it needs to be very explicit about when the event
+ // stream is and is not subscribed.
+ var watcher = createWatcher();
+ var queue = StreamQueue(watcher.events);
+ unawaited(queue.hasNext);
+
+ var future =
+ expectLater(queue, emits(isWatchEvent(ChangeType.ADD, 'file.txt')));
+ expect(queue, neverEmits(anything));
+
+ await watcher.ready;
+
+ writeFile('file.txt');
+
+ await future;
+
+ // Unsubscribe.
+ await queue.cancel(immediate: true);
+
+ // Now write a file while we aren't listening.
+ writeFile('unwatched.txt');
+
+ queue = StreamQueue(watcher.events);
+ future =
+ expectLater(queue, emits(isWatchEvent(ChangeType.ADD, 'added.txt')));
+ expect(queue, neverEmits(isWatchEvent(ChangeType.ADD, 'unwatched.txt')));
+
+ // Wait until the watcher is ready to dispatch events again.
+ await watcher.ready;
+
+ // And add a third file.
+ writeFile('added.txt');
+
+ // Wait until we get an event for the third file.
+ await future;
+
+ await queue.cancel(immediate: true);
+ });
+}
diff --git a/pkgs/watcher/test/no_subscription/windows_test.dart b/pkgs/watcher/test/no_subscription/windows_test.dart
new file mode 100644
index 0000000..9f9e5a9
--- /dev/null
+++ b/pkgs/watcher/test/no_subscription/windows_test.dart
@@ -0,0 +1,18 @@
+// 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.
+
+@TestOn('windows')
+library;
+
+import 'package:test/test.dart';
+import 'package:watcher/src/directory_watcher/windows.dart';
+
+import '../utils.dart';
+import 'shared.dart';
+
+void main() {
+ watcherFactory = WindowsDirectoryWatcher.new;
+
+ sharedTests();
+}
diff --git a/pkgs/watcher/test/path_set_test.dart b/pkgs/watcher/test/path_set_test.dart
new file mode 100644
index 0000000..61ab2cd
--- /dev/null
+++ b/pkgs/watcher/test/path_set_test.dart
@@ -0,0 +1,228 @@
+// 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:path/path.dart' as p;
+import 'package:test/test.dart';
+import 'package:watcher/src/path_set.dart';
+
+Matcher containsPath(String path) => predicate(
+ (paths) => paths is PathSet && paths.contains(path),
+ 'set contains "$path"');
+
+Matcher containsDir(String path) => predicate(
+ (paths) => paths is PathSet && paths.containsDir(path),
+ 'set contains directory "$path"');
+
+void main() {
+ late PathSet paths;
+ setUp(() => paths = PathSet('root'));
+
+ group('adding a path', () {
+ test('stores the path in the set', () {
+ paths.add('root/path/to/file');
+ expect(paths, containsPath('root/path/to/file'));
+ });
+
+ test("that's a subdir of another path keeps both in the set", () {
+ paths.add('root/path');
+ paths.add('root/path/to/file');
+ expect(paths, containsPath('root/path'));
+ expect(paths, containsPath('root/path/to/file'));
+ });
+
+ test("that's not normalized normalizes the path before storing it", () {
+ paths.add('root/../root/path/to/../to/././file');
+ expect(paths, containsPath('root/path/to/file'));
+ });
+
+ test("that's absolute normalizes the path before storing it", () {
+ paths.add(p.absolute('root/path/to/file'));
+ expect(paths, containsPath('root/path/to/file'));
+ });
+ });
+
+ group('removing a path', () {
+ test("that's in the set removes and returns that path", () {
+ paths.add('root/path/to/file');
+ expect(paths.remove('root/path/to/file'),
+ unorderedEquals([p.normalize('root/path/to/file')]));
+ expect(paths, isNot(containsPath('root/path/to/file')));
+ });
+
+ test("that's not in the set returns an empty set", () {
+ paths.add('root/path/to/file');
+ expect(paths.remove('root/path/to/nothing'), isEmpty);
+ });
+
+ test("that's a directory removes and returns all files beneath it", () {
+ paths.add('root/outside');
+ paths.add('root/path/to/one');
+ paths.add('root/path/to/two');
+ paths.add('root/path/to/sub/three');
+
+ expect(
+ paths.remove('root/path'),
+ unorderedEquals([
+ 'root/path/to/one',
+ 'root/path/to/two',
+ 'root/path/to/sub/three'
+ ].map(p.normalize)));
+
+ expect(paths, containsPath('root/outside'));
+ expect(paths, isNot(containsPath('root/path/to/one')));
+ expect(paths, isNot(containsPath('root/path/to/two')));
+ expect(paths, isNot(containsPath('root/path/to/sub/three')));
+ });
+
+ test(
+ "that's a directory in the set removes and returns it and all files "
+ 'beneath it', () {
+ paths.add('root/path');
+ paths.add('root/path/to/one');
+ paths.add('root/path/to/two');
+ paths.add('root/path/to/sub/three');
+
+ expect(
+ paths.remove('root/path'),
+ unorderedEquals([
+ 'root/path',
+ 'root/path/to/one',
+ 'root/path/to/two',
+ 'root/path/to/sub/three'
+ ].map(p.normalize)));
+
+ expect(paths, isNot(containsPath('root/path')));
+ expect(paths, isNot(containsPath('root/path/to/one')));
+ expect(paths, isNot(containsPath('root/path/to/two')));
+ expect(paths, isNot(containsPath('root/path/to/sub/three')));
+ });
+
+ test("that's not normalized removes and returns the normalized path", () {
+ paths.add('root/path/to/file');
+ expect(paths.remove('root/../root/path/to/../to/./file'),
+ unorderedEquals([p.normalize('root/path/to/file')]));
+ });
+
+ test("that's absolute removes and returns the normalized path", () {
+ paths.add('root/path/to/file');
+ expect(paths.remove(p.absolute('root/path/to/file')),
+ unorderedEquals([p.normalize('root/path/to/file')]));
+ });
+ });
+
+ group('containsPath()', () {
+ test('returns false for a non-existent path', () {
+ paths.add('root/path/to/file');
+ expect(paths, isNot(containsPath('root/path/to/nothing')));
+ });
+
+ test("returns false for a directory that wasn't added explicitly", () {
+ paths.add('root/path/to/file');
+ expect(paths, isNot(containsPath('root/path')));
+ });
+
+ test('returns true for a directory that was added explicitly', () {
+ paths.add('root/path');
+ paths.add('root/path/to/file');
+ expect(paths, containsPath('root/path'));
+ });
+
+ test('with a non-normalized path normalizes the path before looking it up',
+ () {
+ paths.add('root/path/to/file');
+ expect(paths, containsPath('root/../root/path/to/../to/././file'));
+ });
+
+ test('with an absolute path normalizes the path before looking it up', () {
+ paths.add('root/path/to/file');
+ expect(paths, containsPath(p.absolute('root/path/to/file')));
+ });
+ });
+
+ group('containsDir()', () {
+ test('returns true for a directory that was added implicitly', () {
+ paths.add('root/path/to/file');
+ expect(paths, containsDir('root/path'));
+ expect(paths, containsDir('root/path/to'));
+ });
+
+ test('returns true for a directory that was added explicitly', () {
+ paths.add('root/path');
+ paths.add('root/path/to/file');
+ expect(paths, containsDir('root/path'));
+ });
+
+ test("returns false for a directory that wasn't added", () {
+ expect(paths, isNot(containsDir('root/nothing')));
+ });
+
+ test('returns false for a non-directory path that was added', () {
+ paths.add('root/path/to/file');
+ expect(paths, isNot(containsDir('root/path/to/file')));
+ });
+
+ test(
+ 'returns false for a directory that was added implicitly and then '
+ 'removed implicitly', () {
+ paths.add('root/path/to/file');
+ paths.remove('root/path/to/file');
+ expect(paths, isNot(containsDir('root/path')));
+ });
+
+ test(
+ 'returns false for a directory that was added explicitly whose '
+ 'children were then removed', () {
+ paths.add('root/path');
+ paths.add('root/path/to/file');
+ paths.remove('root/path/to/file');
+ expect(paths, isNot(containsDir('root/path')));
+ });
+
+ test('with a non-normalized path normalizes the path before looking it up',
+ () {
+ paths.add('root/path/to/file');
+ expect(paths, containsDir('root/../root/path/to/../to/.'));
+ });
+
+ test('with an absolute path normalizes the path before looking it up', () {
+ paths.add('root/path/to/file');
+ expect(paths, containsDir(p.absolute('root/path')));
+ });
+ });
+
+ group('paths', () {
+ test('returns paths added to the set', () {
+ paths.add('root/path');
+ paths.add('root/path/to/one');
+ paths.add('root/path/to/two');
+
+ expect(
+ paths.paths,
+ unorderedEquals([
+ 'root/path',
+ 'root/path/to/one',
+ 'root/path/to/two',
+ ].map(p.normalize)));
+ });
+
+ test("doesn't return paths removed from the set", () {
+ paths.add('root/path/to/one');
+ paths.add('root/path/to/two');
+ paths.remove('root/path/to/two');
+
+ expect(paths.paths, unorderedEquals([p.normalize('root/path/to/one')]));
+ });
+ });
+
+ group('clear', () {
+ test('removes all paths from the set', () {
+ paths.add('root/path');
+ paths.add('root/path/to/one');
+ paths.add('root/path/to/two');
+
+ paths.clear();
+ expect(paths.paths, isEmpty);
+ });
+ });
+}
diff --git a/pkgs/watcher/test/ready/linux_test.dart b/pkgs/watcher/test/ready/linux_test.dart
new file mode 100644
index 0000000..aac0810
--- /dev/null
+++ b/pkgs/watcher/test/ready/linux_test.dart
@@ -0,0 +1,18 @@
+// 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.
+
+@TestOn('linux')
+library;
+
+import 'package:test/test.dart';
+import 'package:watcher/src/directory_watcher/linux.dart';
+
+import '../utils.dart';
+import 'shared.dart';
+
+void main() {
+ watcherFactory = LinuxDirectoryWatcher.new;
+
+ sharedTests();
+}
diff --git a/pkgs/watcher/test/ready/mac_os_test.dart b/pkgs/watcher/test/ready/mac_os_test.dart
new file mode 100644
index 0000000..55a8308
--- /dev/null
+++ b/pkgs/watcher/test/ready/mac_os_test.dart
@@ -0,0 +1,18 @@
+// 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.
+
+@TestOn('mac-os')
+library;
+
+import 'package:test/test.dart';
+import 'package:watcher/src/directory_watcher/mac_os.dart';
+
+import '../utils.dart';
+import 'shared.dart';
+
+void main() {
+ watcherFactory = MacOSDirectoryWatcher.new;
+
+ sharedTests();
+}
diff --git a/pkgs/watcher/test/ready/polling_test.dart b/pkgs/watcher/test/ready/polling_test.dart
new file mode 100644
index 0000000..bfd2958
--- /dev/null
+++ b/pkgs/watcher/test/ready/polling_test.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 'package:watcher/watcher.dart';
+
+import '../utils.dart';
+import 'shared.dart';
+
+void main() {
+ watcherFactory = PollingDirectoryWatcher.new;
+
+ sharedTests();
+}
diff --git a/pkgs/watcher/test/ready/shared.dart b/pkgs/watcher/test/ready/shared.dart
new file mode 100644
index 0000000..ab2c3e1
--- /dev/null
+++ b/pkgs/watcher/test/ready/shared.dart
@@ -0,0 +1,84 @@
+// 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:async';
+
+import 'package:test/test.dart';
+
+import '../utils.dart';
+
+void sharedTests() {
+ test('ready does not complete until after subscription', () async {
+ var watcher = createWatcher();
+
+ var ready = false;
+ unawaited(watcher.ready.then((_) {
+ ready = true;
+ }));
+ await pumpEventQueue();
+
+ expect(ready, isFalse);
+
+ // Subscribe to the events.
+ var subscription = watcher.events.listen((event) {});
+
+ await watcher.ready;
+
+ // Should eventually be ready.
+ expect(watcher.isReady, isTrue);
+
+ await subscription.cancel();
+ });
+
+ test('ready completes immediately when already ready', () async {
+ var watcher = createWatcher();
+
+ // Subscribe to the events.
+ var subscription = watcher.events.listen((event) {});
+
+ // Allow watcher to become ready
+ await watcher.ready;
+
+ // Ensure ready completes immediately
+ expect(
+ watcher.ready.timeout(
+ const Duration(milliseconds: 0),
+ onTimeout: () => throw StateError('Does not complete immediately'),
+ ),
+ completes,
+ );
+
+ await subscription.cancel();
+ });
+
+ test('ready returns a future that does not complete after unsubscribing',
+ () async {
+ var watcher = createWatcher();
+
+ // Subscribe to the events.
+ var subscription = watcher.events.listen((event) {});
+
+ // Wait until ready.
+ await watcher.ready;
+
+ // Now unsubscribe.
+ await subscription.cancel();
+
+ // Should be back to not ready.
+ expect(watcher.ready, doesNotComplete);
+ });
+
+ test('ready completes even if directory does not exist', () async {
+ var watcher = createWatcher(path: 'does/not/exist');
+
+ // Subscribe to the events (else ready will never fire).
+ var subscription = watcher.events.listen((event) {}, onError: (error) {});
+
+ // Expect ready still completes.
+ await watcher.ready;
+
+ // Now unsubscribe.
+ await subscription.cancel();
+ });
+}
diff --git a/pkgs/watcher/test/ready/windows_test.dart b/pkgs/watcher/test/ready/windows_test.dart
new file mode 100644
index 0000000..9f9e5a9
--- /dev/null
+++ b/pkgs/watcher/test/ready/windows_test.dart
@@ -0,0 +1,18 @@
+// 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.
+
+@TestOn('windows')
+library;
+
+import 'package:test/test.dart';
+import 'package:watcher/src/directory_watcher/windows.dart';
+
+import '../utils.dart';
+import 'shared.dart';
+
+void main() {
+ watcherFactory = WindowsDirectoryWatcher.new;
+
+ sharedTests();
+}
diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart
new file mode 100644
index 0000000..7867b9f
--- /dev/null
+++ b/pkgs/watcher/test/utils.dart
@@ -0,0 +1,288 @@
+// 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:async';
+import 'dart:io';
+
+import 'package:async/async.dart';
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+import 'package:watcher/src/stat.dart';
+import 'package:watcher/watcher.dart';
+
+typedef WatcherFactory = Watcher Function(String directory);
+
+/// Sets the function used to create the watcher.
+set watcherFactory(WatcherFactory factory) {
+ _watcherFactory = factory;
+}
+
+/// The mock modification times (in milliseconds since epoch) for each file.
+///
+/// The actual file system has pretty coarse granularity for file modification
+/// times. This means using the real file system requires us to put delays in
+/// the tests to ensure we wait long enough between operations for the mod time
+/// to be different.
+///
+/// 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 = <String, int>{};
+
+late WatcherFactory _watcherFactory;
+
+/// Creates a new [Watcher] that watches a temporary file or directory.
+///
+/// If [path] is provided, watches a subdirectory in the sandbox with that name.
+Watcher createWatcher({String? path}) {
+ if (path == null) {
+ path = d.sandbox;
+ } else {
+ path = p.join(d.sandbox, path);
+ }
+
+ return _watcherFactory(path);
+}
+
+/// The stream of events from the watcher started with [startWatcher].
+late StreamQueue<WatchEvent> _watcherEvents;
+
+/// Whether the event stream has been closed.
+///
+/// If this is not done by a test (by calling [startClosingEventStream]) it will
+/// 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.
+///
+/// If [path] is provided, watches a path in the sandbox with that name.
+Future<void> startWatcher({String? path}) async {
+ mockGetModificationTime((path) {
+ 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}');
+
+ var mtime = _mockFileModificationTimes[normalized];
+ return mtime != null ? DateTime.fromMillisecondsSinceEpoch(mtime) : null;
+ });
+
+ // We want to wait until we're ready *after* we subscribe to the watcher's
+ // events.
+ var watcher = createWatcher(path: path);
+ _watcherEvents = StreamQueue(watcher.events);
+ // Forces a subscription to the underlying stream.
+ unawaited(_watcherEvents.hasNext);
+
+ _hasClosedStream = false;
+ addTearDown(startClosingEventStream);
+
+ await watcher.ready;
+}
+
+/// Schedule closing the watcher stream after the event queue has been pumped.
+///
+/// This is necessary when events are allowed to occur, but don't have to occur,
+/// at the end of a test. Otherwise, if they don't occur, the test will wait
+/// indefinitely because they might in the future and because the watcher is
+/// normally only closed after the test completes.
+void startClosingEventStream() async {
+ if (_hasClosedStream) return;
+ _hasClosedStream = true;
+ await pumpEventQueue();
+ await _watcherEvents.cancel(immediate: true);
+}
+
+/// A list of [StreamMatcher]s that have been collected using
+/// [_collectStreamMatcher].
+List<StreamMatcher>? _collectedStreamMatchers;
+
+/// Collects all stream matchers that are registered within [block] into a
+/// single stream matcher.
+///
+/// The returned matcher will match each of the collected matchers in order.
+StreamMatcher _collectStreamMatcher(void Function() block) {
+ var oldStreamMatchers = _collectedStreamMatchers;
+ var collectedStreamMatchers = _collectedStreamMatchers = <StreamMatcher>[];
+ try {
+ block();
+ return emitsInOrder(collectedStreamMatchers);
+ } finally {
+ _collectedStreamMatchers = oldStreamMatchers;
+ }
+}
+
+/// Either add [streamMatcher] as an expectation to [_watcherEvents], or collect
+/// it with [_collectStreamMatcher].
+///
+/// [streamMatcher] can be a [StreamMatcher], a [Matcher], or a value.
+Future _expectOrCollect(Matcher streamMatcher) {
+ var collectedStreamMatchers = _collectedStreamMatchers;
+ if (collectedStreamMatchers != null) {
+ collectedStreamMatchers.add(emits(streamMatcher));
+ return Future.sync(() {});
+ } else {
+ return expectLater(_watcherEvents, emits(streamMatcher));
+ }
+}
+
+/// Expects that [matchers] will match emitted events in any order.
+///
+/// [matchers] may be [Matcher]s or values, but not [StreamMatcher]s.
+Future inAnyOrder(Iterable matchers) {
+ matchers = matchers.toSet();
+ return _expectOrCollect(emitsInAnyOrder(matchers));
+}
+
+/// Expects that the expectations established in either [block1] or [block2]
+/// will match the emitted events.
+///
+/// If both blocks match, the one that consumed more events will be used.
+Future allowEither(void Function() block1, void Function() block2) =>
+ _expectOrCollect(emitsAnyOf(
+ [_collectStreamMatcher(block1), _collectStreamMatcher(block2)]));
+
+/// Allows the expectations established in [block] to match the emitted events.
+///
+/// If the expectations in [block] don't match, no error will be raised and no
+/// events will be consumed. If this is used at the end of a test,
+/// [startClosingEventStream] should be called before it.
+Future allowEvents(void Function() block) =>
+ _expectOrCollect(mayEmit(_collectStreamMatcher(block)));
+
+/// Returns a StreamMatcher that matches a [WatchEvent] with the given [type]
+/// and [path].
+Matcher isWatchEvent(ChangeType type, String path) {
+ return predicate((e) {
+ return e is WatchEvent &&
+ e.type == type &&
+ e.path == p.join(d.sandbox, p.normalize(path));
+ }, 'is $type $path');
+}
+
+/// Returns a [Matcher] that matches a [WatchEvent] for an add event for [path].
+Matcher isAddEvent(String path) => isWatchEvent(ChangeType.ADD, path);
+
+/// Returns a [Matcher] that matches a [WatchEvent] for a modification event for
+/// [path].
+Matcher isModifyEvent(String path) => isWatchEvent(ChangeType.MODIFY, path);
+
+/// Returns a [Matcher] that matches a [WatchEvent] for a removal event for
+/// [path].
+Matcher isRemoveEvent(String path) => isWatchEvent(ChangeType.REMOVE, path);
+
+/// Expects that the next event emitted will be for an add event for [path].
+Future expectAddEvent(String path) =>
+ _expectOrCollect(isWatchEvent(ChangeType.ADD, path));
+
+/// Expects that the next event emitted will be for a modification event for
+/// [path].
+Future expectModifyEvent(String path) =>
+ _expectOrCollect(isWatchEvent(ChangeType.MODIFY, path));
+
+/// Expects that the next event emitted will be for a removal event for [path].
+Future expectRemoveEvent(String path) =>
+ _expectOrCollect(isWatchEvent(ChangeType.REMOVE, path));
+
+/// Consumes a modification event for [path] if one is emitted at this point in
+/// the schedule, but doesn't throw an error if it isn't.
+///
+/// If this is used at the end of a test, [startClosingEventStream] should be
+/// called before it.
+Future allowModifyEvent(String path) =>
+ _expectOrCollect(mayEmit(isWatchEvent(ChangeType.MODIFY, path)));
+
+/// Track a fake timestamp to be used when writing files. This always increases
+/// so that files that are deleted and re-created do not have their timestamp
+/// set back to a previously used value.
+int _nextTimestamp = 1;
+
+/// Schedules writing a file in the sandbox at [path] with [contents].
+///
+/// If [contents] is omitted, creates an empty file. If [updateModified] is
+/// `false`, the mock file modification time is not changed.
+void writeFile(String path, {String? contents, bool? updateModified}) {
+ contents ??= '';
+ updateModified ??= true;
+
+ var fullPath = p.join(d.sandbox, path);
+
+ // Create any needed subdirectories.
+ var dir = Directory(p.dirname(fullPath));
+ if (!dir.existsSync()) {
+ dir.createSync(recursive: true);
+ }
+
+ File(fullPath).writeAsStringSync(contents);
+
+ if (updateModified) {
+ path = p.normalize(path);
+
+ _mockFileModificationTimes[path] = _nextTimestamp++;
+ }
+}
+
+/// Schedules deleting a file in the sandbox at [path].
+void deleteFile(String path) {
+ File(p.join(d.sandbox, path)).deleteSync();
+
+ _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));
+
+ // 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();
+}
+
+/// Schedules renaming 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);
+ }
+ }
+}
+
+/// Schedules deleting a directory in the sandbox at [path].
+void deleteDir(String path) {
+ Directory(p.join(d.sandbox, path)).deleteSync(recursive: true);
+}
+
+/// Runs [callback] with every permutation of non-negative numbers for each
+/// argument less than [limit].
+///
+/// Returns a set of all values returns by [callback].
+///
+/// [limit] defaults to 3.
+Set<S> withPermutations<S>(S Function(int, int, int) callback, {int? limit}) {
+ limit ??= 3;
+ var results = <S>{};
+ for (var i = 0; i < limit; i++) {
+ for (var j = 0; j < limit; j++) {
+ for (var k = 0; k < limit; k++) {
+ results.add(callback(i, j, k));
+ }
+ }
+ }
+ return results;
+}
diff --git a/pkgs/yaml/.gitignore b/pkgs/yaml/.gitignore
new file mode 100644
index 0000000..ab3cb76
--- /dev/null
+++ b/pkgs/yaml/.gitignore
@@ -0,0 +1,16 @@
+# Don’t commit the following directories created by pub.
+.buildlog
+.dart_tool/
+.pub/
+build/
+packages
+.packages
+
+# Or the files created by dart2js.
+*.dart.js
+*.js_
+*.js.deps
+*.js.map
+
+# Include when developing application packages.
+pubspec.lock
diff --git a/pkgs/yaml/CHANGELOG.md b/pkgs/yaml/CHANGELOG.md
new file mode 100644
index 0000000..3f9d3fd
--- /dev/null
+++ b/pkgs/yaml/CHANGELOG.md
@@ -0,0 +1,199 @@
+## 3.1.3
+
+* Require Dart 3.4
+* Fix UTF-16 surrogate pair handling in plain scaler.
+* Move to `dart-lang/tools` monorepo.
+
+## 3.1.2
+
+* Require Dart 2.19
+* Added `topics` in `pubspec.yaml`.
+
+## 3.1.1
+
+* Switch to using package:lints.
+* Populate the pubspec `repository` field.
+
+## 3.1.0
+
+* `loadYaml` and related functions now accept a `recover` flag instructing the parser
+ to attempt to recover from parse errors and may return invalid or synthetic nodes.
+ When recovering, an `ErrorListener` can also be supplied to listen for errors that
+ are recovered from.
+* Drop dependency on `package:charcode`.
+
+## 3.0.0
+
+* Stable null safety release.
+
+## 3.0.0-nullsafety.0
+
+* Updated to support 2.12.0 and null safety.
+* Allow `YamlNode`s to be wrapped with an optional `style` parameter.
+* **BREAKING** The `sourceUrl` named argument is statically typed as `Uri`
+ instead of allowing `String` or `Uri`.
+
+## 2.2.1
+
+* Update min Dart SDK to `2.4.0`.
+* Fixed span for null nodes in block lists.
+
+## 2.2.0
+
+* POSSIBLY BREAKING CHANGE: Make `YamlMap` preserve parsed key order.
+ This is breaking because some programs may rely on the
+ `HashMap` sort order.
+
+## 2.1.16
+
+* Fixed deprecated API usage in README.
+* Fixed lints that affect package score.
+
+## 2.1.15
+
+* Set max SDK version to `<3.0.0`, and adjust other dependencies.
+
+## 2.1.14
+
+* Remove use of deprecated features.
+* Updated SDK version to 2.0.0-dev.17.0
+
+## 2.1.13
+
+* Stop using comment-based generic syntax.
+
+## 2.1.12
+
+* Properly refuse mappings with duplicate keys.
+
+## 2.1.11
+
+* Fix an infinite loop when parsing some invalid documents.
+
+## 2.1.10
+
+* Support `string_scanner` 1.0.0.
+
+## 2.1.9
+
+* Fix all strong-mode warnings.
+
+## 2.1.8
+
+* Remove the dependency on `path`, since we don't actually import it.
+
+## 2.1.7
+
+* Fix more strong mode warnings.
+
+## 2.1.6
+
+* Fix two analysis issues with DDC's strong mode.
+
+## 2.1.5
+
+* Fix a bug with 2.1.4 where source span information was being discarded for
+ scalar values.
+
+## 2.1.4
+
+* Substantially improve performance.
+
+## 2.1.3
+
+* Add a hint that a colon might be missing when a mapping value is found in the
+ wrong context.
+
+## 2.1.2
+
+* Fix a crashing bug when parsing block scalars.
+
+## 2.1.1
+
+* Properly scope `SourceSpan`s for scalar values surrounded by whitespace.
+
+## 2.1.0
+
+* Rewrite the parser for a 10x speed improvement.
+
+* Support anchors and aliases (`&foo` and `*foo`).
+
+* Support explicit tags (e.g. `!!str`). Note that user-defined tags are still
+ not fully supported.
+
+* `%YAML` and `%TAG` directives are now parsed, although again user-defined tags
+ are not fully supported.
+
+* `YamlScalar`, `YamlList`, and `YamlMap` now expose the styles in which they
+ were written (for example plain vs folded, block vs flow).
+
+* A `yamlWarningCallback` field is exposed. This field can be used to customize
+ how YAML warnings are displayed.
+
+## 2.0.1+1
+
+* Fix an import in a test.
+
+* Widen the version constraint on the `collection` package.
+
+## 2.0.1
+
+* Fix a few lingering references to the old `Span` class in documentation and
+ tests.
+
+## 2.0.0
+
+* Switch from `source_maps`' `Span` class to `source_span`'s `SourceSpan` class.
+
+* For consistency with `source_span` and `string_scanner`, all `sourceName`
+ parameters have been renamed to `sourceUrl`. They now accept Urls as well as
+ Strings.
+
+## 1.1.1
+
+* Fix broken type arguments that caused breakage on dart2js.
+
+* Fix an analyzer warning in `yaml_node_wrapper.dart`.
+
+## 1.1.0
+
+* Add new publicly-accessible constructors for `YamlNode` subclasses. These
+ constructors make it possible to use the same API to access non-YAML data as
+ YAML data.
+
+* Make `YamlException` inherit from source_map's `SpanFormatException`. This
+ improves the error formatting and allows callers access to source range
+ information.
+
+## 1.0.0+1
+
+* Fix a variable name typo.
+
+## 1.0.0
+
+* **Backwards incompatibility**: The data structures returned by `loadYaml` and
+ `loadYamlStream` are now immutable.
+
+* **Backwards incompatibility**: The interface of the `YamlMap` class has
+ changed substantially in numerous ways. External users may no longer construct
+ their own instances.
+
+* Maps and lists returned by `loadYaml` and `loadYamlStream` now contain
+ information about their source locations.
+
+* A new `loadYamlNode` function returns the source location of top-level scalars
+ as well.
+
+## 0.10.0
+
+* Improve error messages when a file fails to parse.
+
+## 0.9.0+2
+
+* Ensure that maps are order-independent when used as map keys.
+
+## 0.9.0+1
+
+* The `YamlMap` class is deprecated. In a future version, maps returned by
+ `loadYaml` and `loadYamlStream` will be Dart `HashMap`s with a custom equality
+ operation.
diff --git a/pkgs/yaml/LICENSE b/pkgs/yaml/LICENSE
new file mode 100644
index 0000000..e7589cb
--- /dev/null
+++ b/pkgs/yaml/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2014, the Dart project authors.
+Copyright (c) 2006, Kirill Simonov.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/pkgs/yaml/README.md b/pkgs/yaml/README.md
new file mode 100644
index 0000000..ba56893
--- /dev/null
+++ b/pkgs/yaml/README.md
@@ -0,0 +1,33 @@
+[](https://github.com/dart-lang/tools/actions/workflows/yaml.yaml)
+[](https://pub.dev/packages/yaml)
+[](https://pub.dev/packages/yaml/publisher)
+
+
+A parser for [YAML](https://yaml.org/).
+
+## Usage
+
+Use `loadYaml` to load a single document, or `loadYamlStream` to load a
+stream of documents. For example:
+
+```dart
+import 'package:yaml/yaml.dart';
+
+main() {
+ var doc = loadYaml("YAML: YAML Ain't Markup Language");
+ print(doc['YAML']);
+}
+```
+
+This library currently doesn't support dumping to YAML. You should use
+`json.encode` from `dart:convert` instead:
+
+```dart
+import 'dart:convert';
+import 'package:yaml/yaml.dart';
+
+main() {
+ var doc = loadYaml("YAML: YAML Ain't Markup Language");
+ print(json.encode(doc));
+}
+```
diff --git a/pkgs/yaml/analysis_options.yaml b/pkgs/yaml/analysis_options.yaml
new file mode 100644
index 0000000..46e45f0
--- /dev/null
+++ b/pkgs/yaml/analysis_options.yaml
@@ -0,0 +1,18 @@
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ language:
+ strict-casts: true
+
+linter:
+ rules:
+ - avoid_private_typedef_functions
+ - avoid_redundant_argument_values
+ - avoid_unused_constructor_parameters
+ - cancel_subscriptions
+ - join_return_with_assignment
+ - missing_whitespace_between_adjacent_strings
+ - no_runtimeType_toString
+ - prefer_const_declarations
+ - prefer_expression_function_bodies
+ - use_string_buffers
diff --git a/pkgs/yaml/benchmark/benchmark.dart b/pkgs/yaml/benchmark/benchmark.dart
new file mode 100644
index 0000000..afc3c97
--- /dev/null
+++ b/pkgs/yaml/benchmark/benchmark.dart
@@ -0,0 +1,65 @@
+// Copyright (c) 2015, the Dart project authors.
+// Copyright (c) 2006, Kirill Simonov.
+//
+// Use of this source code is governed by an MIT-style
+// license that can be found in the LICENSE file or at
+// https://opensource.org/licenses/MIT.
+
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:path/path.dart' as p;
+import 'package:yaml/yaml.dart';
+
+const numTrials = 100;
+const runsPerTrial = 1000;
+
+final source = _loadFile('input.yaml');
+final expected = _loadFile('output.json');
+
+void main(List<String> args) {
+ var best = double.infinity;
+
+ // Run the benchmark several times. This ensures the VM is warmed up and lets
+ // us see how much variance there is.
+ for (var i = 0; i <= numTrials; i++) {
+ var start = DateTime.now();
+
+ // For a single benchmark, convert the source multiple times.
+ Object? result;
+ for (var j = 0; j < runsPerTrial; j++) {
+ result = loadYaml(source);
+ }
+
+ var elapsed =
+ DateTime.now().difference(start).inMilliseconds / runsPerTrial;
+
+ // Keep track of the best run so far.
+ if (elapsed >= best) continue;
+ best = elapsed;
+
+ // Sanity check to make sure the output is what we expect and to make sure
+ // the VM doesn't optimize "dead" code away.
+ if (jsonEncode(result) != expected) {
+ print('Incorrect output:\n${jsonEncode(result)}');
+ exit(1);
+ }
+
+ // Don't print the first run. It's always terrible since the VM hasn't
+ // warmed up yet.
+ if (i == 0) continue;
+ _printResult("Run ${'#$i'.padLeft(3, '')}", elapsed);
+ }
+
+ _printResult('Best ', best);
+}
+
+String _loadFile(String name) {
+ var path = p.join(p.dirname(p.fromUri(Platform.script)), name);
+ return File(path).readAsStringSync();
+}
+
+void _printResult(String label, double time) {
+ print('$label: ${time.toStringAsFixed(3).padLeft(4, '0')}ms '
+ "${'=' * ((time * 100).toInt())}");
+}
diff --git a/pkgs/yaml/benchmark/input.yaml b/pkgs/yaml/benchmark/input.yaml
new file mode 100644
index 0000000..89bf9dc
--- /dev/null
+++ b/pkgs/yaml/benchmark/input.yaml
@@ -0,0 +1,48 @@
+verb: RecommendCafes
+recipe:
+ - verb: List
+ outputs: ["Cafe[]"]
+ - verb: Fetch
+ inputs: ["Cafe[]"]
+ outputs: ["CafeWithMenu[]"]
+ - verb: Flatten
+ inputs: ["CafeWithMenu[]"]
+ outputs: ["DishOffering[]"]
+ - verb: Score
+ inputs: ["DishOffering[]"]
+ outputs: ["DishOffering[]/Scored"]
+ - verb: Display
+ inputs: ["DishOffering[]/Scored"]
+tags:
+ booleans: [ true, false ]
+ dates:
+ - canonical: 2001-12-15T02:59:43.1Z
+ - iso8601: 2001-12-14t21:59:43.10-05:00
+ - spaced: 2001-12-14 21:59:43.10 -5
+ - date: 2002-12-14
+ numbers:
+ - int: 12345
+ - negative: -345
+ - floating-point: 345.678
+ - hexidecimal: 0x123abc
+ - exponential: 12.3015e+02
+ - octal: 0o14
+ strings:
+ - unicode: "Sosa did fine.\u263A"
+ - control: "\b1998\t1999\t2000\n"
+ - hex esc: "\x0d\x0a is \r\n"
+ - single: '"Howdy!" he cried.'
+ - quoted: ' # Not a ''comment''.'
+ - tie-fighter: '|\-*-/|'
+ - plain:
+ This unquoted scalar
+ spans many lines.
+
+ - quoted: "So does this
+ quoted scalar.\n"
+ - accomplishment: >
+ Mark set a major league
+ home run record in 1998.
+ - stats: |
+ 65 Home Runs
+ 0.278 Batting Average
diff --git a/pkgs/yaml/benchmark/output.json b/pkgs/yaml/benchmark/output.json
new file mode 100644
index 0000000..9e6cb84
--- /dev/null
+++ b/pkgs/yaml/benchmark/output.json
@@ -0,0 +1 @@
+{"verb":"RecommendCafes","recipe":[{"verb":"List","outputs":["Cafe[]"]},{"verb":"Fetch","inputs":["Cafe[]"],"outputs":["CafeWithMenu[]"]},{"verb":"Flatten","inputs":["CafeWithMenu[]"],"outputs":["DishOffering[]"]},{"verb":"Score","inputs":["DishOffering[]"],"outputs":["DishOffering[]/Scored"]},{"verb":"Display","inputs":["DishOffering[]/Scored"]}],"tags":{"booleans":[true,false],"dates":[{"canonical":"2001-12-15T02:59:43.1Z"},{"iso8601":"2001-12-14t21:59:43.10-05:00"},{"spaced":"2001-12-14 21:59:43.10 -5"},{"date":"2002-12-14"}],"numbers":[{"int":12345},{"negative":-345},{"floating-point":345.678},{"hexidecimal":1194684},{"exponential":1230.15},{"octal":12}],"strings":[{"unicode":"Sosa did fine.☺"},{"control":"\b1998\t1999\t2000\n"},{"hex esc":"\r\n is \r\n"},{"single":"\"Howdy!\" he cried."},{"quoted":" # Not a 'comment'."},{"tie-fighter":"|\\-*-/|"},{"plain":"This unquoted scalar spans many lines."},{"quoted":"So does this quoted scalar.\n"},{"accomplishment":"Mark set a major league home run record in 1998.\n"},{"stats":"65 Home Runs\n0.278 Batting Average\n"}]}}
\ No newline at end of file
diff --git a/pkgs/yaml/example/example.dart b/pkgs/yaml/example/example.dart
new file mode 100644
index 0000000..bb283a3
--- /dev/null
+++ b/pkgs/yaml/example/example.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2020, the Dart project authors.
+// Copyright (c) 2006, Kirill Simonov.
+//
+// Use of this source code is governed by an MIT-style
+// license that can be found in the LICENSE file or at
+// https://opensource.org/licenses/MIT.
+
+import 'package:yaml/yaml.dart';
+
+void main() {
+ var doc = loadYaml("YAML: YAML Ain't Markup Language") as Map;
+ print(doc['YAML']);
+}
diff --git a/pkgs/yaml/lib/src/charcodes.dart b/pkgs/yaml/lib/src/charcodes.dart
new file mode 100644
index 0000000..602d597
--- /dev/null
+++ b/pkgs/yaml/lib/src/charcodes.dart
@@ -0,0 +1,48 @@
+// Copyright (c) 2021, the Dart project authors.
+// Copyright (c) 2006, Kirill Simonov.
+//
+// Use of this source code is governed by an MIT-style
+// license that can be found in the LICENSE file or at
+// https://opensource.org/licenses/MIT.
+
+/// Character `+`.
+const int $plus = 0x2b;
+
+/// Character `-`.
+const int $minus = 0x2d;
+
+/// Character `.`.
+const int $dot = 0x2e;
+
+/// Character `0`.
+const int $0 = 0x30;
+
+/// Character `9`.
+const int $9 = 0x39;
+
+/// Character `F`.
+const int $F = 0x46;
+
+/// Character `N`.
+const int $N = 0x4e;
+
+/// Character `T`.
+const int $T = 0x54;
+
+/// Character `f`.
+const int $f = 0x66;
+
+/// Character `n`.
+const int $n = 0x6e;
+
+/// Character `o`.
+const int $o = 0x6f;
+
+/// Character `t`.
+const int $t = 0x74;
+
+/// Character `x`.
+const int $x = 0x78;
+
+/// Character `~`.
+const int $tilde = 0x7e;
diff --git a/pkgs/yaml/lib/src/equality.dart b/pkgs/yaml/lib/src/equality.dart
new file mode 100644
index 0000000..c833dc6
--- /dev/null
+++ b/pkgs/yaml/lib/src/equality.dart
@@ -0,0 +1,128 @@
+// Copyright (c) 2014, the Dart project authors.
+// Copyright (c) 2006, Kirill Simonov.
+//
+// Use of this source code is governed by an MIT-style
+// license that can be found in the LICENSE file or at
+// https://opensource.org/licenses/MIT.
+
+import 'dart:collection';
+
+import 'package:collection/collection.dart';
+
+import 'yaml_node.dart';
+
+/// Returns a [Map] that compares its keys based on [deepEquals].
+Map<K, V> deepEqualsMap<K, V>() =>
+ LinkedHashMap(equals: deepEquals, hashCode: deepHashCode);
+
+/// Returns whether two objects are structurally equivalent.
+///
+/// This considers `NaN` values to be equivalent, handles self-referential
+/// structures, and considers [YamlScalar]s to be equal to their values.
+bool deepEquals(Object? obj1, Object? obj2) => _DeepEquals().equals(obj1, obj2);
+
+/// A class that provides access to the list of parent objects used for loop
+/// detection.
+class _DeepEquals {
+ final _parents1 = <Object?>[];
+ final _parents2 = <Object?>[];
+
+ /// Returns whether [obj1] and [obj2] are structurally equivalent.
+ bool equals(Object? obj1, Object? obj2) {
+ if (obj1 is YamlScalar) obj1 = obj1.value;
+ if (obj2 is YamlScalar) obj2 = obj2.value;
+
+ // _parents1 and _parents2 are guaranteed to be the same size.
+ for (var i = 0; i < _parents1.length; i++) {
+ var loop1 = identical(obj1, _parents1[i]);
+ var loop2 = identical(obj2, _parents2[i]);
+ // If both structures loop in the same place, they're equal at that point
+ // in the structure. If one loops and the other doesn't, they're not
+ // equal.
+ if (loop1 && loop2) return true;
+ if (loop1 || loop2) return false;
+ }
+
+ _parents1.add(obj1);
+ _parents2.add(obj2);
+ try {
+ if (obj1 is List && obj2 is List) {
+ return _listEquals(obj1, obj2);
+ } else if (obj1 is Map && obj2 is Map) {
+ return _mapEquals(obj1, obj2);
+ } else if (obj1 is num && obj2 is num) {
+ return _numEquals(obj1, obj2);
+ } else {
+ return obj1 == obj2;
+ }
+ } finally {
+ _parents1.removeLast();
+ _parents2.removeLast();
+ }
+ }
+
+ /// Returns whether [list1] and [list2] are structurally equal.
+ bool _listEquals(List list1, List list2) {
+ if (list1.length != list2.length) return false;
+
+ for (var i = 0; i < list1.length; i++) {
+ if (!equals(list1[i], list2[i])) return false;
+ }
+
+ return true;
+ }
+
+ /// Returns whether [map1] and [map2] are structurally equal.
+ bool _mapEquals(Map map1, Map map2) {
+ if (map1.length != map2.length) return false;
+
+ for (var key in map1.keys) {
+ if (!map2.containsKey(key)) return false;
+ if (!equals(map1[key], map2[key])) return false;
+ }
+
+ return true;
+ }
+
+ /// Returns whether two numbers are equivalent.
+ ///
+ /// This differs from `n1 == n2` in that it considers `NaN` to be equal to
+ /// itself.
+ bool _numEquals(num n1, num n2) {
+ if (n1.isNaN && n2.isNaN) return true;
+ return n1 == n2;
+ }
+}
+
+/// Returns a hash code for [obj] such that structurally equivalent objects
+/// will have the same hash code.
+///
+/// This supports deep equality for maps and lists, including those with
+/// self-referential structures, and returns the same hash code for
+/// [YamlScalar]s and their values.
+int deepHashCode(Object? obj) {
+ var parents = <Object?>[];
+
+ int deepHashCodeInner(Object? value) {
+ if (parents.any((parent) => identical(parent, value))) return -1;
+
+ parents.add(value);
+ try {
+ if (value is Map) {
+ var equality = const UnorderedIterableEquality<Object?>();
+ return equality.hash(value.keys.map(deepHashCodeInner)) ^
+ equality.hash(value.values.map(deepHashCodeInner));
+ } else if (value is Iterable) {
+ return const IterableEquality<Object?>().hash(value.map(deepHashCode));
+ } else if (value is YamlScalar) {
+ return (value.value as Object?).hashCode;
+ } else {
+ return value.hashCode;
+ }
+ } finally {
+ parents.removeLast();
+ }
+ }
+
+ return deepHashCodeInner(obj);
+}
diff --git a/pkgs/yaml/lib/src/error_listener.dart b/pkgs/yaml/lib/src/error_listener.dart
new file mode 100644
index 0000000..0498d68
--- /dev/null
+++ b/pkgs/yaml/lib/src/error_listener.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2021, the Dart project authors.
+// Copyright (c) 2006, Kirill Simonov.
+//
+// Use of this source code is governed by an MIT-style
+// license that can be found in the LICENSE file or at
+// https://opensource.org/licenses/MIT.
+
+import 'yaml_exception.dart';
+
+/// A listener that is notified of [YamlException]s during scanning/parsing.
+abstract class ErrorListener {
+ /// This method is invoked when an [error] has been found in the YAML.
+ void onError(YamlException error);
+}
+
+/// An [ErrorListener] that collects all errors into [errors].
+class ErrorCollector extends ErrorListener {
+ final List<YamlException> errors = [];
+
+ @override
+ void onError(YamlException error) => errors.add(error);
+}
diff --git a/pkgs/yaml/lib/src/event.dart b/pkgs/yaml/lib/src/event.dart
new file mode 100644
index 0000000..1476311
--- /dev/null
+++ b/pkgs/yaml/lib/src/event.dart
@@ -0,0 +1,171 @@
+// Copyright (c) 2014, the Dart project authors.
+// Copyright (c) 2006, Kirill Simonov.
+//
+// Use of this source code is governed by an MIT-style
+// license that can be found in the LICENSE file or at
+// https://opensource.org/licenses/MIT.
+
+import 'package:source_span/source_span.dart';
+
+import 'parser.dart';
+import 'style.dart';
+import 'yaml_document.dart';
+
+/// An event emitted by a [Parser].
+class Event {
+ final EventType type;
+ final FileSpan span;
+
+ Event(this.type, this.span);
+
+ @override
+ String toString() => type.toString();
+}
+
+/// An event indicating the beginning of a YAML document.
+class DocumentStartEvent implements Event {
+ @override
+ EventType get type => EventType.documentStart;
+ @override
+ final FileSpan span;
+
+ /// The document's `%YAML` directive, or `null` if there was none.
+ final VersionDirective? versionDirective;
+
+ /// The document's `%TAG` directives, if any.
+ final List<TagDirective> tagDirectives;
+
+ /// Whether the document started implicitly (that is, without an explicit
+ /// `===` sequence).
+ final bool isImplicit;
+
+ DocumentStartEvent(this.span,
+ {this.versionDirective,
+ List<TagDirective>? tagDirectives,
+ this.isImplicit = true})
+ : tagDirectives = tagDirectives ?? [];
+
+ @override
+ String toString() => 'DOCUMENT_START';
+}
+
+/// An event indicating the end of a YAML document.
+class DocumentEndEvent implements Event {
+ @override
+ EventType get type => EventType.documentEnd;
+ @override
+ final FileSpan span;
+
+ /// Whether the document ended implicitly (that is, without an explicit
+ /// `...` sequence).
+ final bool isImplicit;
+
+ DocumentEndEvent(this.span, {this.isImplicit = true});
+
+ @override
+ String toString() => 'DOCUMENT_END';
+}
+
+/// An event indicating that an alias was referenced.
+class AliasEvent implements Event {
+ @override
+ EventType get type => EventType.alias;
+ @override
+ final FileSpan span;
+
+ /// The alias name.
+ final String name;
+
+ AliasEvent(this.span, this.name);
+
+ @override
+ String toString() => 'ALIAS $name';
+}
+
+/// An event that can have associated anchor and tag properties.
+abstract class _ValueEvent implements Event {
+ /// The name of the value's anchor, or `null` if it wasn't anchored.
+ String? get anchor;
+
+ /// The text of the value's tag, or `null` if it wasn't tagged.
+ String? get tag;
+
+ @override
+ String toString() {
+ var buffer = StringBuffer('$type');
+ if (anchor != null) buffer.write(' &$anchor');
+ if (tag != null) buffer.write(' $tag');
+ return buffer.toString();
+ }
+}
+
+/// An event indicating a single scalar value.
+class ScalarEvent extends _ValueEvent {
+ @override
+ EventType get type => EventType.scalar;
+ @override
+ final FileSpan span;
+ @override
+ final String? anchor;
+ @override
+ final String? tag;
+
+ /// The contents of the scalar.
+ final String value;
+
+ /// The style of the scalar in the original source.
+ final ScalarStyle style;
+
+ ScalarEvent(this.span, this.value, this.style, {this.anchor, this.tag});
+
+ @override
+ String toString() => '${super.toString()} "$value"';
+}
+
+/// An event indicating the beginning of a sequence.
+class SequenceStartEvent extends _ValueEvent {
+ @override
+ EventType get type => EventType.sequenceStart;
+ @override
+ final FileSpan span;
+ @override
+ final String? anchor;
+ @override
+ final String? tag;
+
+ /// The style of the collection in the original source.
+ final CollectionStyle style;
+
+ SequenceStartEvent(this.span, this.style, {this.anchor, this.tag});
+}
+
+/// An event indicating the beginning of a mapping.
+class MappingStartEvent extends _ValueEvent {
+ @override
+ EventType get type => EventType.mappingStart;
+ @override
+ final FileSpan span;
+ @override
+ final String? anchor;
+ @override
+ final String? tag;
+
+ /// The style of the collection in the original source.
+ final CollectionStyle style;
+
+ MappingStartEvent(this.span, this.style, {this.anchor, this.tag});
+}
+
+/// The types of [Event] objects.
+enum EventType {
+ streamStart,
+ streamEnd,
+ documentStart,
+ documentEnd,
+ alias,
+ scalar,
+ sequenceStart,
+ sequenceEnd,
+ mappingStart,
+ mappingEnd
+}
diff --git a/pkgs/yaml/lib/src/loader.dart b/pkgs/yaml/lib/src/loader.dart
new file mode 100644
index 0000000..7cdf45a
--- /dev/null
+++ b/pkgs/yaml/lib/src/loader.dart
@@ -0,0 +1,343 @@
+// Copyright (c) 2014, the Dart project authors.
+// Copyright (c) 2006, Kirill Simonov.
+//
+// Use of this source code is governed by an MIT-style
+// license that can be found in the LICENSE file or at
+// https://opensource.org/licenses/MIT.
+
+import 'package:source_span/source_span.dart';
+
+import 'charcodes.dart';
+import 'equality.dart';
+import 'error_listener.dart';
+import 'event.dart';
+import 'parser.dart';
+import 'yaml_document.dart';
+import 'yaml_exception.dart';
+import 'yaml_node.dart';
+
+/// A loader that reads [Event]s emitted by a [Parser] and emits
+/// [YamlDocument]s.
+///
+/// This is based on the libyaml loader, available at
+/// https://github.com/yaml/libyaml/blob/master/src/loader.c. The license for
+/// that is available in ../../libyaml-license.txt.
+class Loader {
+ /// The underlying [Parser] that generates [Event]s.
+ final Parser _parser;
+
+ /// Aliases by the alias name.
+ final _aliases = <String, YamlNode>{};
+
+ /// The span of the entire stream emitted so far.
+ FileSpan get span => _span;
+ FileSpan _span;
+
+ /// Creates a loader that loads [source].
+ factory Loader(String source,
+ {Uri? sourceUrl, bool recover = false, ErrorListener? errorListener}) {
+ var parser = Parser(source,
+ sourceUrl: sourceUrl, recover: recover, errorListener: errorListener);
+ var event = parser.parse();
+ assert(event.type == EventType.streamStart);
+ return Loader._(parser, event.span);
+ }
+
+ Loader._(this._parser, this._span);
+
+ /// Loads the next document from the stream.
+ ///
+ /// If there are no more documents, returns `null`.
+ YamlDocument? load() {
+ if (_parser.isDone) return null;
+
+ var event = _parser.parse();
+ if (event.type == EventType.streamEnd) {
+ _span = _span.expand(event.span);
+ return null;
+ }
+
+ var document = _loadDocument(event as DocumentStartEvent);
+ _span = _span.expand(document.span as FileSpan);
+ _aliases.clear();
+ return document;
+ }
+
+ /// Composes a document object.
+ YamlDocument _loadDocument(DocumentStartEvent firstEvent) {
+ var contents = _loadNode(_parser.parse());
+
+ var lastEvent = _parser.parse() as DocumentEndEvent;
+ assert(lastEvent.type == EventType.documentEnd);
+
+ return YamlDocument.internal(
+ contents,
+ firstEvent.span.expand(lastEvent.span),
+ firstEvent.versionDirective,
+ firstEvent.tagDirectives,
+ startImplicit: firstEvent.isImplicit,
+ endImplicit: lastEvent.isImplicit);
+ }
+
+ /// Composes a node.
+ YamlNode _loadNode(Event firstEvent) => switch (firstEvent.type) {
+ EventType.alias => _loadAlias(firstEvent as AliasEvent),
+ EventType.scalar => _loadScalar(firstEvent as ScalarEvent),
+ EventType.sequenceStart =>
+ _loadSequence(firstEvent as SequenceStartEvent),
+ EventType.mappingStart => _loadMapping(firstEvent as MappingStartEvent),
+ _ => throw StateError('Unreachable')
+ };
+
+ /// Registers an anchor.
+ void _registerAnchor(String? anchor, YamlNode node) {
+ if (anchor == null) return;
+
+ // libyaml throws an error for duplicate anchors, but example 7.1 makes it
+ // clear that they should be overridden:
+ // http://yaml.org/spec/1.2/spec.html#id2786448.
+
+ _aliases[anchor] = node;
+ }
+
+ /// Composes a node corresponding to an alias.
+ YamlNode _loadAlias(AliasEvent event) {
+ var alias = _aliases[event.name];
+ if (alias != null) return alias;
+
+ throw YamlException('Undefined alias.', event.span);
+ }
+
+ /// Composes a scalar node.
+ YamlNode _loadScalar(ScalarEvent scalar) {
+ YamlNode node;
+ if (scalar.tag == '!') {
+ node = YamlScalar.internal(scalar.value, scalar);
+ } else if (scalar.tag != null) {
+ node = _parseByTag(scalar);
+ } else {
+ node = _parseScalar(scalar);
+ }
+
+ _registerAnchor(scalar.anchor, node);
+ return node;
+ }
+
+ /// Composes a sequence node.
+ YamlNode _loadSequence(SequenceStartEvent firstEvent) {
+ if (firstEvent.tag != '!' &&
+ firstEvent.tag != null &&
+ firstEvent.tag != 'tag:yaml.org,2002:seq') {
+ throw YamlException('Invalid tag for sequence.', firstEvent.span);
+ }
+
+ var children = <YamlNode>[];
+ var node = YamlList.internal(children, firstEvent.span, firstEvent.style);
+ _registerAnchor(firstEvent.anchor, node);
+
+ var event = _parser.parse();
+ while (event.type != EventType.sequenceEnd) {
+ children.add(_loadNode(event));
+ event = _parser.parse();
+ }
+
+ setSpan(node, firstEvent.span.expand(event.span));
+ return node;
+ }
+
+ /// Composes a mapping node.
+ YamlNode _loadMapping(MappingStartEvent firstEvent) {
+ if (firstEvent.tag != '!' &&
+ firstEvent.tag != null &&
+ firstEvent.tag != 'tag:yaml.org,2002:map') {
+ throw YamlException('Invalid tag for mapping.', firstEvent.span);
+ }
+
+ var children = deepEqualsMap<dynamic, YamlNode>();
+ var node = YamlMap.internal(children, firstEvent.span, firstEvent.style);
+ _registerAnchor(firstEvent.anchor, node);
+
+ var event = _parser.parse();
+ while (event.type != EventType.mappingEnd) {
+ var key = _loadNode(event);
+ var value = _loadNode(_parser.parse());
+ if (children.containsKey(key)) {
+ throw YamlException('Duplicate mapping key.', key.span);
+ }
+
+ children[key] = value;
+ event = _parser.parse();
+ }
+
+ setSpan(node, firstEvent.span.expand(event.span));
+ return node;
+ }
+
+ /// Parses a scalar according to its tag name.
+ YamlScalar _parseByTag(ScalarEvent scalar) {
+ switch (scalar.tag) {
+ case 'tag:yaml.org,2002:null':
+ var result = _parseNull(scalar);
+ if (result != null) return result;
+ throw YamlException('Invalid null scalar.', scalar.span);
+ case 'tag:yaml.org,2002:bool':
+ var result = _parseBool(scalar);
+ if (result != null) return result;
+ throw YamlException('Invalid bool scalar.', scalar.span);
+ case 'tag:yaml.org,2002:int':
+ var result = _parseNumber(scalar, allowFloat: false);
+ if (result != null) return result;
+ throw YamlException('Invalid int scalar.', scalar.span);
+ case 'tag:yaml.org,2002:float':
+ var result = _parseNumber(scalar, allowInt: false);
+ if (result != null) return result;
+ throw YamlException('Invalid float scalar.', scalar.span);
+ case 'tag:yaml.org,2002:str':
+ return YamlScalar.internal(scalar.value, scalar);
+ default:
+ throw YamlException('Undefined tag: ${scalar.tag}.', scalar.span);
+ }
+ }
+
+ /// Parses [scalar], which may be one of several types.
+ YamlScalar _parseScalar(ScalarEvent scalar) =>
+ _tryParseScalar(scalar) ?? YamlScalar.internal(scalar.value, scalar);
+
+ /// Tries to parse [scalar].
+ ///
+ /// If parsing fails, this returns `null`, indicating that the scalar should
+ /// be parsed as a string.
+ YamlScalar? _tryParseScalar(ScalarEvent scalar) {
+ // Quickly check for the empty string, which means null.
+ var length = scalar.value.length;
+ if (length == 0) return YamlScalar.internal(null, scalar);
+
+ // Dispatch on the first character.
+ var firstChar = scalar.value.codeUnitAt(0);
+ return switch (firstChar) {
+ $dot || $plus || $minus => _parseNumber(scalar),
+ $n || $N => length == 4 ? _parseNull(scalar) : null,
+ $t || $T => length == 4 ? _parseBool(scalar) : null,
+ $f || $F => length == 5 ? _parseBool(scalar) : null,
+ $tilde => length == 1 ? YamlScalar.internal(null, scalar) : null,
+ _ => (firstChar >= $0 && firstChar <= $9) ? _parseNumber(scalar) : null
+ };
+ }
+
+ /// Parse a null scalar.
+ ///
+ /// Returns a Dart `null` if parsing fails.
+ YamlScalar? _parseNull(ScalarEvent scalar) => switch (scalar.value) {
+ '' ||
+ 'null' ||
+ 'Null' ||
+ 'NULL' ||
+ '~' =>
+ YamlScalar.internal(null, scalar),
+ _ => null
+ };
+
+ /// Parse a boolean scalar.
+ ///
+ /// Returns `null` if parsing fails.
+ YamlScalar? _parseBool(ScalarEvent scalar) => switch (scalar.value) {
+ 'true' || 'True' || 'TRUE' => YamlScalar.internal(true, scalar),
+ 'false' || 'False' || 'FALSE' => YamlScalar.internal(false, scalar),
+ _ => null
+ };
+
+ /// Parses a numeric scalar.
+ ///
+ /// Returns `null` if parsing fails.
+ YamlScalar? _parseNumber(ScalarEvent scalar,
+ {bool allowInt = true, bool allowFloat = true}) {
+ var value = _parseNumberValue(scalar.value,
+ allowInt: allowInt, allowFloat: allowFloat);
+ return value == null ? null : YamlScalar.internal(value, scalar);
+ }
+
+ /// Parses the value of a number.
+ ///
+ /// Returns the number if it's parsed successfully, or `null` if it's not.
+ num? _parseNumberValue(String contents,
+ {bool allowInt = true, bool allowFloat = true}) {
+ assert(allowInt || allowFloat);
+
+ var firstChar = contents.codeUnitAt(0);
+ var length = contents.length;
+
+ // Quick check for single digit integers.
+ if (allowInt && length == 1) {
+ var value = firstChar - $0;
+ return value >= 0 && value <= 9 ? value : null;
+ }
+
+ var secondChar = contents.codeUnitAt(1);
+
+ // Hexadecimal or octal integers.
+ if (allowInt && firstChar == $0) {
+ // int.tryParse supports 0x natively.
+ if (secondChar == $x) return int.tryParse(contents);
+
+ if (secondChar == $o) {
+ var afterRadix = contents.substring(2);
+ return int.tryParse(afterRadix, radix: 8);
+ }
+ }
+
+ // Int or float starting with a digit or a +/- sign.
+ if ((firstChar >= $0 && firstChar <= $9) ||
+ ((firstChar == $plus || firstChar == $minus) &&
+ secondChar >= $0 &&
+ secondChar <= $9)) {
+ // Try to parse an int or, failing that, a double.
+ num? result;
+ if (allowInt) {
+ // Pass "radix: 10" explicitly to ensure that "-0x10", which is valid
+ // Dart but invalid YAML, doesn't get parsed.
+ result = int.tryParse(contents, radix: 10);
+ }
+
+ if (allowFloat) result ??= double.tryParse(contents);
+ return result;
+ }
+
+ if (!allowFloat) return null;
+
+ // Now the only possibility is to parse a float starting with a dot or a
+ // sign and a dot, or the signed/unsigned infinity values and not-a-numbers.
+ if ((firstChar == $dot && secondChar >= $0 && secondChar <= $9) ||
+ (firstChar == $minus || firstChar == $plus) && secondChar == $dot) {
+ // Starting with a . and a number or a sign followed by a dot.
+ if (length == 5) {
+ switch (contents) {
+ case '+.inf':
+ case '+.Inf':
+ case '+.INF':
+ return double.infinity;
+ case '-.inf':
+ case '-.Inf':
+ case '-.INF':
+ return -double.infinity;
+ }
+ }
+
+ return double.tryParse(contents);
+ }
+
+ if (length == 4 && firstChar == $dot) {
+ switch (contents) {
+ case '.inf':
+ case '.Inf':
+ case '.INF':
+ return double.infinity;
+ case '.nan':
+ case '.NaN':
+ case '.NAN':
+ return double.nan;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/pkgs/yaml/lib/src/null_span.dart b/pkgs/yaml/lib/src/null_span.dart
new file mode 100644
index 0000000..49e1a1c
--- /dev/null
+++ b/pkgs/yaml/lib/src/null_span.dart
@@ -0,0 +1,26 @@
+// Copyright (c) 2014, the Dart project authors.
+// Copyright (c) 2006, Kirill Simonov.
+//
+// Use of this source code is governed by an MIT-style
+// license that can be found in the LICENSE file or at
+// https://opensource.org/licenses/MIT.
+
+import 'package:source_span/source_span.dart';
+
+import 'yaml_node.dart';
+
+/// A [SourceSpan] with no location information.
+///
+/// This is used with [YamlMap.wrap] and [YamlList.wrap] to provide means of
+/// accessing a non-YAML map that behaves transparently like a map parsed from
+/// YAML.
+class NullSpan extends SourceSpanMixin {
+ @override
+ final SourceLocation start;
+ @override
+ SourceLocation get end => start;
+ @override
+ final text = '';
+
+ NullSpan(Object? sourceUrl) : start = SourceLocation(0, sourceUrl: sourceUrl);
+}
diff --git a/pkgs/yaml/lib/src/parser.dart b/pkgs/yaml/lib/src/parser.dart
new file mode 100644
index 0000000..e924e40
--- /dev/null
+++ b/pkgs/yaml/lib/src/parser.dart
@@ -0,0 +1,805 @@
+// Copyright (c) 2014, the Dart project authors.
+// Copyright (c) 2006, Kirill Simonov.
+//
+// Use of this source code is governed by an MIT-style
+// license that can be found in the LICENSE file or at
+// https://opensource.org/licenses/MIT.
+
+// ignore_for_file: constant_identifier_names
+
+import 'package:source_span/source_span.dart';
+import 'package:string_scanner/string_scanner.dart';
+
+import 'error_listener.dart';
+import 'event.dart';
+import 'scanner.dart';
+import 'style.dart';
+import 'token.dart';
+import 'utils.dart';
+import 'yaml_document.dart';
+import 'yaml_exception.dart';
+
+/// A parser that reads [Token]s emitted by a [Scanner] and emits [Event]s.
+///
+/// This is based on the libyaml parser, available at
+/// https://github.com/yaml/libyaml/blob/master/src/parser.c. The license for
+/// that is available in ../../libyaml-license.txt.
+class Parser {
+ /// The underlying [Scanner] that generates [Token]s.
+ final Scanner _scanner;
+
+ /// The stack of parse states for nested contexts.
+ final _states = <_State>[];
+
+ /// The current parse state.
+ var _state = _State.STREAM_START;
+
+ /// The custom tag directives, by tag handle.
+ final _tagDirectives = <String, TagDirective>{};
+
+ /// Whether the parser has finished parsing.
+ bool get isDone => _state == _State.END;
+
+ /// Creates a parser that parses [source].
+ ///
+ /// If [recover] is true, will attempt to recover from parse errors and may
+ /// return invalid or synthetic nodes. If [errorListener] is also supplied,
+ /// its onError method will be called for each error recovered from. It is not
+ /// valid to provide [errorListener] if [recover] is false.
+ Parser(String source,
+ {Uri? sourceUrl, bool recover = false, ErrorListener? errorListener})
+ : assert(recover || errorListener == null),
+ _scanner = Scanner(source,
+ sourceUrl: sourceUrl,
+ recover: recover,
+ errorListener: errorListener);
+
+ /// Consumes and returns the next event.
+ Event parse() {
+ try {
+ if (isDone) throw StateError('No more events.');
+ var event = _stateMachine();
+ return event;
+ } on StringScannerException catch (error) {
+ throw YamlException(error.message, error.span);
+ }
+ }
+
+ /// Dispatches parsing based on the current state.
+ Event _stateMachine() {
+ switch (_state) {
+ case _State.STREAM_START:
+ return _parseStreamStart();
+ case _State.DOCUMENT_START:
+ return _parseDocumentStart();
+ case _State.DOCUMENT_CONTENT:
+ return _parseDocumentContent();
+ case _State.DOCUMENT_END:
+ return _parseDocumentEnd();
+ case _State.BLOCK_NODE:
+ return _parseNode(block: true);
+ case _State.BLOCK_NODE_OR_INDENTLESS_SEQUENCE:
+ return _parseNode(block: true, indentlessSequence: true);
+ case _State.FLOW_NODE:
+ return _parseNode();
+ case _State.BLOCK_SEQUENCE_FIRST_ENTRY:
+ // Scan past the `BLOCK-SEQUENCE-FIRST-ENTRY` token to the
+ // `BLOCK-SEQUENCE-ENTRY` token.
+ _scanner.scan();
+ return _parseBlockSequenceEntry();
+ case _State.BLOCK_SEQUENCE_ENTRY:
+ return _parseBlockSequenceEntry();
+ case _State.INDENTLESS_SEQUENCE_ENTRY:
+ return _parseIndentlessSequenceEntry();
+ case _State.BLOCK_MAPPING_FIRST_KEY:
+ // Scan past the `BLOCK-MAPPING-FIRST-KEY` token to the
+ // `BLOCK-MAPPING-KEY` token.
+ _scanner.scan();
+ return _parseBlockMappingKey();
+ case _State.BLOCK_MAPPING_KEY:
+ return _parseBlockMappingKey();
+ case _State.BLOCK_MAPPING_VALUE:
+ return _parseBlockMappingValue();
+ case _State.FLOW_SEQUENCE_FIRST_ENTRY:
+ return _parseFlowSequenceEntry(first: true);
+ case _State.FLOW_SEQUENCE_ENTRY:
+ return _parseFlowSequenceEntry();
+ case _State.FLOW_SEQUENCE_ENTRY_MAPPING_KEY:
+ return _parseFlowSequenceEntryMappingKey();
+ case _State.FLOW_SEQUENCE_ENTRY_MAPPING_VALUE:
+ return _parseFlowSequenceEntryMappingValue();
+ case _State.FLOW_SEQUENCE_ENTRY_MAPPING_END:
+ return _parseFlowSequenceEntryMappingEnd();
+ case _State.FLOW_MAPPING_FIRST_KEY:
+ return _parseFlowMappingKey(first: true);
+ case _State.FLOW_MAPPING_KEY:
+ return _parseFlowMappingKey();
+ case _State.FLOW_MAPPING_VALUE:
+ return _parseFlowMappingValue();
+ case _State.FLOW_MAPPING_EMPTY_VALUE:
+ return _parseFlowMappingValue(empty: true);
+ default:
+ throw StateError('Unreachable');
+ }
+ }
+
+ /// Parses the production:
+ ///
+ /// stream ::=
+ /// STREAM-START implicit_document? explicit_document* STREAM-END
+ /// ************
+ Event _parseStreamStart() {
+ var token = _scanner.scan();
+ assert(token.type == TokenType.streamStart);
+
+ _state = _State.DOCUMENT_START;
+ return Event(EventType.streamStart, token.span);
+ }
+
+ /// Parses the productions:
+ ///
+ /// implicit_document ::= block_node DOCUMENT-END*
+ /// *
+ /// explicit_document ::=
+ /// DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END*
+ /// *************************
+ Event _parseDocumentStart() {
+ var token = _scanner.peek()!;
+
+ // libyaml requires any document beyond the first in the stream to have an
+ // explicit document start indicator, but the spec allows it to be omitted
+ // as long as there was an end indicator.
+
+ // Parse extra document end indicators.
+ while (token.type == TokenType.documentEnd) {
+ token = _scanner.advance()!;
+ }
+
+ if (token.type != TokenType.versionDirective &&
+ token.type != TokenType.tagDirective &&
+ token.type != TokenType.documentStart &&
+ token.type != TokenType.streamEnd) {
+ // Parse an implicit document.
+ _processDirectives();
+ _states.add(_State.DOCUMENT_END);
+ _state = _State.BLOCK_NODE;
+ return DocumentStartEvent(token.span.start.pointSpan());
+ }
+
+ if (token.type == TokenType.streamEnd) {
+ _state = _State.END;
+ _scanner.scan();
+ return Event(EventType.streamEnd, token.span);
+ }
+
+ // Parse an explicit document.
+ var start = token.span;
+ var (versionDirective, tagDirectives) = _processDirectives();
+ token = _scanner.peek()!;
+ if (token.type != TokenType.documentStart) {
+ throw YamlException('Expected document start.', token.span);
+ }
+
+ _states.add(_State.DOCUMENT_END);
+ _state = _State.DOCUMENT_CONTENT;
+ _scanner.scan();
+ return DocumentStartEvent(start.expand(token.span),
+ versionDirective: versionDirective,
+ tagDirectives: tagDirectives,
+ isImplicit: false);
+ }
+
+ /// Parses the productions:
+ ///
+ /// explicit_document ::=
+ /// DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END*
+ /// ***********
+ Event _parseDocumentContent() {
+ var token = _scanner.peek()!;
+
+ switch (token.type) {
+ case TokenType.versionDirective:
+ case TokenType.tagDirective:
+ case TokenType.documentStart:
+ case TokenType.documentEnd:
+ case TokenType.streamEnd:
+ _state = _states.removeLast();
+ return _processEmptyScalar(token.span.start);
+ default:
+ return _parseNode(block: true);
+ }
+ }
+
+ /// Parses the productions:
+ ///
+ /// implicit_document ::= block_node DOCUMENT-END*
+ /// *************
+ /// explicit_document ::=
+ /// DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END*
+ /// *************
+ Event _parseDocumentEnd() {
+ _tagDirectives.clear();
+ _state = _State.DOCUMENT_START;
+
+ var token = _scanner.peek()!;
+ if (token.type == TokenType.documentEnd) {
+ _scanner.scan();
+ return DocumentEndEvent(token.span, isImplicit: false);
+ } else {
+ return DocumentEndEvent(token.span.start.pointSpan());
+ }
+ }
+
+ /// Parses the productions:
+ ///
+ /// block_node_or_indentless_sequence ::=
+ /// ALIAS
+ /// *****
+ /// | properties (block_content | indentless_block_sequence)?
+ /// ********** *
+ /// | block_content | indentless_block_sequence
+ /// *
+ /// block_node ::= ALIAS
+ /// *****
+ /// | properties block_content?
+ /// ********** *
+ /// | block_content
+ /// *
+ /// flow_node ::= ALIAS
+ /// *****
+ /// | properties flow_content?
+ /// ********** *
+ /// | flow_content
+ /// *
+ /// properties ::= TAG ANCHOR? | ANCHOR TAG?
+ /// *************************
+ /// block_content ::= block_collection | flow_collection | SCALAR
+ /// ******
+ /// flow_content ::= flow_collection | SCALAR
+ /// ******
+ Event _parseNode({bool block = false, bool indentlessSequence = false}) {
+ var token = _scanner.peek()!;
+
+ if (token is AliasToken) {
+ _scanner.scan();
+ _state = _states.removeLast();
+ return AliasEvent(token.span, token.name);
+ }
+
+ String? anchor;
+ TagToken? tagToken;
+ var span = token.span.start.pointSpan();
+ Token parseAnchor(AnchorToken token) {
+ anchor = token.name;
+ span = span.expand(token.span);
+ return _scanner.advance()!;
+ }
+
+ Token parseTag(TagToken token) {
+ tagToken = token;
+ span = span.expand(token.span);
+ return _scanner.advance()!;
+ }
+
+ if (token is AnchorToken) {
+ token = parseAnchor(token);
+ if (token is TagToken) token = parseTag(token);
+ } else if (token is TagToken) {
+ token = parseTag(token);
+ if (token is AnchorToken) token = parseAnchor(token);
+ }
+
+ String? tag;
+ if (tagToken != null) {
+ if (tagToken!.handle == null) {
+ tag = tagToken!.suffix;
+ } else {
+ var tagDirective = _tagDirectives[tagToken!.handle];
+ if (tagDirective == null) {
+ throw YamlException('Undefined tag handle.', tagToken!.span);
+ }
+
+ tag = tagDirective.prefix + (tagToken?.suffix ?? '');
+ }
+ }
+
+ if (indentlessSequence && token.type == TokenType.blockEntry) {
+ _state = _State.INDENTLESS_SEQUENCE_ENTRY;
+ return SequenceStartEvent(span.expand(token.span), CollectionStyle.BLOCK,
+ anchor: anchor, tag: tag);
+ }
+
+ if (token is ScalarToken) {
+ // All non-plain scalars have the "!" tag by default.
+ if (tag == null && token.style != ScalarStyle.PLAIN) tag = '!';
+
+ _state = _states.removeLast();
+ _scanner.scan();
+ return ScalarEvent(span.expand(token.span), token.value, token.style,
+ anchor: anchor, tag: tag);
+ }
+
+ if (token.type == TokenType.flowSequenceStart) {
+ _state = _State.FLOW_SEQUENCE_FIRST_ENTRY;
+ return SequenceStartEvent(span.expand(token.span), CollectionStyle.FLOW,
+ anchor: anchor, tag: tag);
+ }
+
+ if (token.type == TokenType.flowMappingStart) {
+ _state = _State.FLOW_MAPPING_FIRST_KEY;
+ return MappingStartEvent(span.expand(token.span), CollectionStyle.FLOW,
+ anchor: anchor, tag: tag);
+ }
+
+ if (block && token.type == TokenType.blockSequenceStart) {
+ _state = _State.BLOCK_SEQUENCE_FIRST_ENTRY;
+ return SequenceStartEvent(span.expand(token.span), CollectionStyle.BLOCK,
+ anchor: anchor, tag: tag);
+ }
+
+ if (block && token.type == TokenType.blockMappingStart) {
+ _state = _State.BLOCK_MAPPING_FIRST_KEY;
+ return MappingStartEvent(span.expand(token.span), CollectionStyle.BLOCK,
+ anchor: anchor, tag: tag);
+ }
+
+ if (anchor != null || tag != null) {
+ _state = _states.removeLast();
+ return ScalarEvent(span, '', ScalarStyle.PLAIN, anchor: anchor, tag: tag);
+ }
+
+ throw YamlException('Expected node content.', span);
+ }
+
+ /// Parses the productions:
+ ///
+ /// block_sequence ::=
+ /// BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END
+ /// ******************** *********** * *********
+ Event _parseBlockSequenceEntry() {
+ var token = _scanner.peek()!;
+
+ if (token.type == TokenType.blockEntry) {
+ var start = token.span.start;
+ token = _scanner.advance()!;
+
+ if (token.type == TokenType.blockEntry ||
+ token.type == TokenType.blockEnd) {
+ _state = _State.BLOCK_SEQUENCE_ENTRY;
+ return _processEmptyScalar(start);
+ } else {
+ _states.add(_State.BLOCK_SEQUENCE_ENTRY);
+ return _parseNode(block: true);
+ }
+ }
+
+ if (token.type == TokenType.blockEnd) {
+ _scanner.scan();
+ _state = _states.removeLast();
+ return Event(EventType.sequenceEnd, token.span);
+ }
+
+ throw YamlException("While parsing a block collection, expected '-'.",
+ token.span.start.pointSpan());
+ }
+
+ /// Parses the productions:
+ ///
+ /// indentless_sequence ::= (BLOCK-ENTRY block_node?)+
+ /// *********** *
+ Event _parseIndentlessSequenceEntry() {
+ var token = _scanner.peek()!;
+
+ if (token.type != TokenType.blockEntry) {
+ _state = _states.removeLast();
+ return Event(EventType.sequenceEnd, token.span.start.pointSpan());
+ }
+
+ var start = token.span.start;
+ token = _scanner.advance()!;
+
+ if (token.type == TokenType.blockEntry ||
+ token.type == TokenType.key ||
+ token.type == TokenType.value ||
+ token.type == TokenType.blockEnd) {
+ _state = _State.INDENTLESS_SEQUENCE_ENTRY;
+ return _processEmptyScalar(start);
+ } else {
+ _states.add(_State.INDENTLESS_SEQUENCE_ENTRY);
+ return _parseNode(block: true);
+ }
+ }
+
+ /// Parses the productions:
+ ///
+ /// block_mapping ::= BLOCK-MAPPING_START
+ /// *******************
+ /// ((KEY block_node_or_indentless_sequence?)?
+ /// *** *
+ /// (VALUE block_node_or_indentless_sequence?)?)*
+ ///
+ /// BLOCK-END
+ /// *********
+ Event _parseBlockMappingKey() {
+ var token = _scanner.peek()!;
+ if (token.type == TokenType.key) {
+ var start = token.span.start;
+ token = _scanner.advance()!;
+
+ if (token.type == TokenType.key ||
+ token.type == TokenType.value ||
+ token.type == TokenType.blockEnd) {
+ _state = _State.BLOCK_MAPPING_VALUE;
+ return _processEmptyScalar(start);
+ } else {
+ _states.add(_State.BLOCK_MAPPING_VALUE);
+ return _parseNode(block: true, indentlessSequence: true);
+ }
+ }
+
+ // libyaml doesn't allow empty keys without an explicit key indicator, but
+ // the spec does. See example 8.18:
+ // http://yaml.org/spec/1.2/spec.html#id2798896.
+ if (token.type == TokenType.value) {
+ _state = _State.BLOCK_MAPPING_VALUE;
+ return _processEmptyScalar(token.span.start);
+ }
+
+ if (token.type == TokenType.blockEnd) {
+ _scanner.scan();
+ _state = _states.removeLast();
+ return Event(EventType.mappingEnd, token.span);
+ }
+
+ throw YamlException('Expected a key while parsing a block mapping.',
+ token.span.start.pointSpan());
+ }
+
+ /// Parses the productions:
+ ///
+ /// block_mapping ::= BLOCK-MAPPING_START
+ ///
+ /// ((KEY block_node_or_indentless_sequence?)?
+ ///
+ /// (VALUE block_node_or_indentless_sequence?)?)*
+ /// ***** *
+ /// BLOCK-END
+ ///
+ Event _parseBlockMappingValue() {
+ var token = _scanner.peek()!;
+
+ if (token.type != TokenType.value) {
+ _state = _State.BLOCK_MAPPING_KEY;
+ return _processEmptyScalar(token.span.start);
+ }
+
+ var start = token.span.start;
+ token = _scanner.advance()!;
+ if (token.type == TokenType.key ||
+ token.type == TokenType.value ||
+ token.type == TokenType.blockEnd) {
+ _state = _State.BLOCK_MAPPING_KEY;
+ return _processEmptyScalar(start);
+ } else {
+ _states.add(_State.BLOCK_MAPPING_KEY);
+ return _parseNode(block: true, indentlessSequence: true);
+ }
+ }
+
+ /// Parses the productions:
+ ///
+ /// flow_sequence ::= FLOW-SEQUENCE-START
+ /// *******************
+ /// (flow_sequence_entry FLOW-ENTRY)*
+ /// * **********
+ /// flow_sequence_entry?
+ /// *
+ /// FLOW-SEQUENCE-END
+ /// *****************
+ /// flow_sequence_entry ::=
+ /// flow_node | KEY flow_node? (VALUE flow_node?)?
+ /// *
+ Event _parseFlowSequenceEntry({bool first = false}) {
+ if (first) _scanner.scan();
+ var token = _scanner.peek()!;
+
+ if (token.type != TokenType.flowSequenceEnd) {
+ if (!first) {
+ if (token.type != TokenType.flowEntry) {
+ throw YamlException(
+ "While parsing a flow sequence, expected ',' or ']'.",
+ token.span.start.pointSpan());
+ }
+
+ token = _scanner.advance()!;
+ }
+
+ if (token.type == TokenType.key) {
+ _state = _State.FLOW_SEQUENCE_ENTRY_MAPPING_KEY;
+ _scanner.scan();
+ return MappingStartEvent(token.span, CollectionStyle.FLOW);
+ } else if (token.type != TokenType.flowSequenceEnd) {
+ _states.add(_State.FLOW_SEQUENCE_ENTRY);
+ return _parseNode();
+ }
+ }
+
+ _scanner.scan();
+ _state = _states.removeLast();
+ return Event(EventType.sequenceEnd, token.span);
+ }
+
+ /// Parses the productions:
+ ///
+ /// flow_sequence_entry ::=
+ /// flow_node | KEY flow_node? (VALUE flow_node?)?
+ /// *** *
+ Event _parseFlowSequenceEntryMappingKey() {
+ var token = _scanner.peek()!;
+
+ if (token.type == TokenType.value ||
+ token.type == TokenType.flowEntry ||
+ token.type == TokenType.flowSequenceEnd) {
+ // libyaml consumes the token here, but that seems like a bug, since it
+ // always causes [_parseFlowSequenceEntryMappingValue] to emit an empty
+ // scalar.
+
+ var start = token.span.start;
+ _state = _State.FLOW_SEQUENCE_ENTRY_MAPPING_VALUE;
+ return _processEmptyScalar(start);
+ } else {
+ _states.add(_State.FLOW_SEQUENCE_ENTRY_MAPPING_VALUE);
+ return _parseNode();
+ }
+ }
+
+ /// Parses the productions:
+ ///
+ /// flow_sequence_entry ::=
+ /// flow_node | KEY flow_node? (VALUE flow_node?)?
+ /// ***** *
+ Event _parseFlowSequenceEntryMappingValue() {
+ var token = _scanner.peek()!;
+
+ if (token.type == TokenType.value) {
+ token = _scanner.advance()!;
+ if (token.type != TokenType.flowEntry &&
+ token.type != TokenType.flowSequenceEnd) {
+ _states.add(_State.FLOW_SEQUENCE_ENTRY_MAPPING_END);
+ return _parseNode();
+ }
+ }
+
+ _state = _State.FLOW_SEQUENCE_ENTRY_MAPPING_END;
+ return _processEmptyScalar(token.span.start);
+ }
+
+ /// Parses the productions:
+ ///
+ /// flow_sequence_entry ::=
+ /// flow_node | KEY flow_node? (VALUE flow_node?)?
+ /// *
+ Event _parseFlowSequenceEntryMappingEnd() {
+ _state = _State.FLOW_SEQUENCE_ENTRY;
+ return Event(EventType.mappingEnd, _scanner.peek()!.span.start.pointSpan());
+ }
+
+ /// Parses the productions:
+ ///
+ /// flow_mapping ::= FLOW-MAPPING-START
+ /// ******************
+ /// (flow_mapping_entry FLOW-ENTRY)*
+ /// * **********
+ /// flow_mapping_entry?
+ /// ******************
+ /// FLOW-MAPPING-END
+ /// ****************
+ /// flow_mapping_entry ::=
+ /// flow_node | KEY flow_node? (VALUE flow_node?)?
+ /// * *** *
+ Event _parseFlowMappingKey({bool first = false}) {
+ if (first) _scanner.scan();
+ var token = _scanner.peek()!;
+
+ if (token.type != TokenType.flowMappingEnd) {
+ if (!first) {
+ if (token.type != TokenType.flowEntry) {
+ throw YamlException(
+ "While parsing a flow mapping, expected ',' or '}'.",
+ token.span.start.pointSpan());
+ }
+
+ token = _scanner.advance()!;
+ }
+
+ if (token.type == TokenType.key) {
+ token = _scanner.advance()!;
+ if (token.type != TokenType.value &&
+ token.type != TokenType.flowEntry &&
+ token.type != TokenType.flowMappingEnd) {
+ _states.add(_State.FLOW_MAPPING_VALUE);
+ return _parseNode();
+ } else {
+ _state = _State.FLOW_MAPPING_VALUE;
+ return _processEmptyScalar(token.span.start);
+ }
+ } else if (token.type != TokenType.flowMappingEnd) {
+ _states.add(_State.FLOW_MAPPING_EMPTY_VALUE);
+ return _parseNode();
+ }
+ }
+
+ _scanner.scan();
+ _state = _states.removeLast();
+ return Event(EventType.mappingEnd, token.span);
+ }
+
+ /// Parses the productions:
+ ///
+ /// flow_mapping_entry ::=
+ /// flow_node | KEY flow_node? (VALUE flow_node?)?
+ /// * ***** *
+ Event _parseFlowMappingValue({bool empty = false}) {
+ var token = _scanner.peek()!;
+
+ if (empty) {
+ _state = _State.FLOW_MAPPING_KEY;
+ return _processEmptyScalar(token.span.start);
+ }
+
+ if (token.type == TokenType.value) {
+ token = _scanner.advance()!;
+ if (token.type != TokenType.flowEntry &&
+ token.type != TokenType.flowMappingEnd) {
+ _states.add(_State.FLOW_MAPPING_KEY);
+ return _parseNode();
+ }
+ }
+
+ _state = _State.FLOW_MAPPING_KEY;
+ return _processEmptyScalar(token.span.start);
+ }
+
+ /// Generate an empty scalar event.
+ Event _processEmptyScalar(SourceLocation location) =>
+ ScalarEvent(location.pointSpan() as FileSpan, '', ScalarStyle.PLAIN);
+
+ /// Parses directives.
+ (VersionDirective?, List<TagDirective>) _processDirectives() {
+ var token = _scanner.peek()!;
+
+ VersionDirective? versionDirective;
+ var tagDirectives = <TagDirective>[];
+ while (token.type == TokenType.versionDirective ||
+ token.type == TokenType.tagDirective) {
+ if (token is VersionDirectiveToken) {
+ if (versionDirective != null) {
+ throw YamlException('Duplicate %YAML directive.', token.span);
+ }
+
+ if (token.major != 1 || token.minor == 0) {
+ throw YamlException(
+ 'Incompatible YAML document. This parser only supports YAML 1.1 '
+ 'and 1.2.',
+ token.span);
+ } else if (token.minor > 2) {
+ // TODO(nweiz): Print to stderr when issue 6943 is fixed and dart:io
+ // is available.
+ warn('Warning: this parser only supports YAML 1.1 and 1.2.',
+ token.span);
+ }
+
+ versionDirective = VersionDirective(token.major, token.minor);
+ } else if (token is TagDirectiveToken) {
+ var tagDirective = TagDirective(token.handle, token.prefix);
+ _appendTagDirective(tagDirective, token.span);
+ tagDirectives.add(tagDirective);
+ }
+
+ token = _scanner.advance()!;
+ }
+
+ _appendTagDirective(TagDirective('!', '!'), token.span.start.pointSpan(),
+ allowDuplicates: true);
+ _appendTagDirective(
+ TagDirective('!!', 'tag:yaml.org,2002:'), token.span.start.pointSpan(),
+ allowDuplicates: true);
+
+ return (versionDirective, tagDirectives);
+ }
+
+ /// Adds a tag directive to the directives stack.
+ void _appendTagDirective(TagDirective newDirective, FileSpan span,
+ {bool allowDuplicates = false}) {
+ if (_tagDirectives.containsKey(newDirective.handle)) {
+ if (allowDuplicates) return;
+ throw YamlException('Duplicate %TAG directive.', span);
+ }
+
+ _tagDirectives[newDirective.handle] = newDirective;
+ }
+}
+
+/// The possible states for the parser.
+class _State {
+ /// Expect [TokenType.streamStart].
+ static const STREAM_START = _State('STREAM_START');
+
+ /// Expect [TokenType.documentStart].
+ static const DOCUMENT_START = _State('DOCUMENT_START');
+
+ /// Expect the content of a document.
+ static const DOCUMENT_CONTENT = _State('DOCUMENT_CONTENT');
+
+ /// Expect [TokenType.documentEnd].
+ static const DOCUMENT_END = _State('DOCUMENT_END');
+
+ /// Expect a block node.
+ static const BLOCK_NODE = _State('BLOCK_NODE');
+
+ /// Expect a block node or indentless sequence.
+ static const BLOCK_NODE_OR_INDENTLESS_SEQUENCE =
+ _State('BLOCK_NODE_OR_INDENTLESS_SEQUENCE');
+
+ /// Expect a flow node.
+ static const FLOW_NODE = _State('FLOW_NODE');
+
+ /// Expect the first entry of a block sequence.
+ static const BLOCK_SEQUENCE_FIRST_ENTRY =
+ _State('BLOCK_SEQUENCE_FIRST_ENTRY');
+
+ /// Expect an entry of a block sequence.
+ static const BLOCK_SEQUENCE_ENTRY = _State('BLOCK_SEQUENCE_ENTRY');
+
+ /// Expect an entry of an indentless sequence.
+ static const INDENTLESS_SEQUENCE_ENTRY = _State('INDENTLESS_SEQUENCE_ENTRY');
+
+ /// Expect the first key of a block mapping.
+ static const BLOCK_MAPPING_FIRST_KEY = _State('BLOCK_MAPPING_FIRST_KEY');
+
+ /// Expect a block mapping key.
+ static const BLOCK_MAPPING_KEY = _State('BLOCK_MAPPING_KEY');
+
+ /// Expect a block mapping value.
+ static const BLOCK_MAPPING_VALUE = _State('BLOCK_MAPPING_VALUE');
+
+ /// Expect the first entry of a flow sequence.
+ static const FLOW_SEQUENCE_FIRST_ENTRY = _State('FLOW_SEQUENCE_FIRST_ENTRY');
+
+ /// Expect an entry of a flow sequence.
+ static const FLOW_SEQUENCE_ENTRY = _State('FLOW_SEQUENCE_ENTRY');
+
+ /// Expect a key of an ordered mapping.
+ static const FLOW_SEQUENCE_ENTRY_MAPPING_KEY =
+ _State('FLOW_SEQUENCE_ENTRY_MAPPING_KEY');
+
+ /// Expect a value of an ordered mapping.
+ static const FLOW_SEQUENCE_ENTRY_MAPPING_VALUE =
+ _State('FLOW_SEQUENCE_ENTRY_MAPPING_VALUE');
+
+ /// Expect the and of an ordered mapping entry.
+ static const FLOW_SEQUENCE_ENTRY_MAPPING_END =
+ _State('FLOW_SEQUENCE_ENTRY_MAPPING_END');
+
+ /// Expect the first key of a flow mapping.
+ static const FLOW_MAPPING_FIRST_KEY = _State('FLOW_MAPPING_FIRST_KEY');
+
+ /// Expect a key of a flow mapping.
+ static const FLOW_MAPPING_KEY = _State('FLOW_MAPPING_KEY');
+
+ /// Expect a value of a flow mapping.
+ static const FLOW_MAPPING_VALUE = _State('FLOW_MAPPING_VALUE');
+
+ /// Expect an empty value of a flow mapping.
+ static const FLOW_MAPPING_EMPTY_VALUE = _State('FLOW_MAPPING_EMPTY_VALUE');
+
+ /// Expect nothing.
+ static const END = _State('END');
+
+ final String name;
+
+ const _State(this.name);
+
+ @override
+ String toString() => name;
+}
diff --git a/pkgs/yaml/lib/src/scanner.dart b/pkgs/yaml/lib/src/scanner.dart
new file mode 100644
index 0000000..1cfd3af
--- /dev/null
+++ b/pkgs/yaml/lib/src/scanner.dart
@@ -0,0 +1,1695 @@
+// Copyright (c) 2014, the Dart project authors.
+// Copyright (c) 2006, Kirill Simonov.
+//
+// Use of this source code is governed by an MIT-style
+// license that can be found in the LICENSE file or at
+// https://opensource.org/licenses/MIT.
+
+// ignore_for_file: constant_identifier_names
+
+import 'package:collection/collection.dart';
+import 'package:source_span/source_span.dart';
+import 'package:string_scanner/string_scanner.dart';
+
+import 'error_listener.dart';
+import 'style.dart';
+import 'token.dart';
+import 'utils.dart';
+import 'yaml_exception.dart';
+
+/// A scanner that reads a string of Unicode characters and emits [Token]s.
+///
+/// This is based on the libyaml scanner, available at
+/// https://github.com/yaml/libyaml/blob/master/src/scanner.c. The license for
+/// that is available in ../../libyaml-license.txt.
+class Scanner {
+ static const TAB = 0x9;
+ static const LF = 0xA;
+ static const CR = 0xD;
+ static const SP = 0x20;
+ static const DOLLAR = 0x24;
+ static const LEFT_PAREN = 0x28;
+ static const RIGHT_PAREN = 0x29;
+ static const PLUS = 0x2B;
+ static const COMMA = 0x2C;
+ static const HYPHEN = 0x2D;
+ static const PERIOD = 0x2E;
+ static const QUESTION = 0x3F;
+ static const COLON = 0x3A;
+ static const SEMICOLON = 0x3B;
+ static const EQUALS = 0x3D;
+ static const LEFT_SQUARE = 0x5B;
+ static const RIGHT_SQUARE = 0x5D;
+ static const LEFT_CURLY = 0x7B;
+ static const RIGHT_CURLY = 0x7D;
+ static const HASH = 0x23;
+ static const AMPERSAND = 0x26;
+ static const ASTERISK = 0x2A;
+ static const EXCLAMATION = 0x21;
+ static const VERTICAL_BAR = 0x7C;
+ static const LEFT_ANGLE = 0x3C;
+ static const RIGHT_ANGLE = 0x3E;
+ static const SINGLE_QUOTE = 0x27;
+ static const DOUBLE_QUOTE = 0x22;
+ static const PERCENT = 0x25;
+ static const AT = 0x40;
+ static const GRAVE_ACCENT = 0x60;
+ static const TILDE = 0x7E;
+
+ static const NULL = 0x0;
+ static const BELL = 0x7;
+ static const BACKSPACE = 0x8;
+ static const VERTICAL_TAB = 0xB;
+ static const FORM_FEED = 0xC;
+ static const ESCAPE = 0x1B;
+ static const SLASH = 0x2F;
+ static const BACKSLASH = 0x5C;
+ static const UNDERSCORE = 0x5F;
+ static const NEL = 0x85;
+ static const NBSP = 0xA0;
+ static const LINE_SEPARATOR = 0x2028;
+ static const PARAGRAPH_SEPARATOR = 0x2029;
+ static const BOM = 0xFEFF;
+
+ static const NUMBER_0 = 0x30;
+ static const NUMBER_9 = 0x39;
+
+ static const LETTER_A = 0x61;
+ static const LETTER_B = 0x62;
+ static const LETTER_E = 0x65;
+ static const LETTER_F = 0x66;
+ static const LETTER_N = 0x6E;
+ static const LETTER_R = 0x72;
+ static const LETTER_T = 0x74;
+ static const LETTER_U = 0x75;
+ static const LETTER_V = 0x76;
+ static const LETTER_X = 0x78;
+ static const LETTER_Z = 0x7A;
+
+ static const LETTER_CAP_A = 0x41;
+ static const LETTER_CAP_F = 0x46;
+ static const LETTER_CAP_L = 0x4C;
+ static const LETTER_CAP_N = 0x4E;
+ static const LETTER_CAP_P = 0x50;
+ static const LETTER_CAP_U = 0x55;
+ static const LETTER_CAP_X = 0x58;
+ static const LETTER_CAP_Z = 0x5A;
+
+ /// Whether this scanner should attempt to recover when parsing invalid YAML.
+ final bool _recover;
+
+ /// A listener to report YAML errors to.
+ final ErrorListener? _errorListener;
+
+ /// The underlying [SpanScanner] used to read characters from the source text.
+ ///
+ /// This is also used to track line and column information and to generate
+ /// [SourceSpan]s.
+ final SpanScanner _scanner;
+
+ /// Whether this scanner has produced a [TokenType.streamStart] token
+ /// indicating the beginning of the YAML stream.
+ var _streamStartProduced = false;
+
+ /// Whether this scanner has produced a [TokenType.streamEnd] token
+ /// indicating the end of the YAML stream.
+ var _streamEndProduced = false;
+
+ /// The queue of tokens yet to be emitted.
+ ///
+ /// These are queued up in advance so that [TokenType.key] tokens can be
+ /// inserted once the scanner determines that a series of tokens represents a
+ /// mapping key.
+ final _tokens = QueueList<Token>();
+
+ /// The number of tokens that have been emitted.
+ ///
+ /// This doesn't count tokens in [_tokens].
+ var _tokensParsed = 0;
+
+ /// Whether the next token in [_tokens] is ready to be returned.
+ ///
+ /// It might not be ready if there may still be a [TokenType.key] inserted
+ /// before it.
+ var _tokenAvailable = false;
+
+ /// The stack of indent levels for the current nested block contexts.
+ ///
+ /// The YAML spec specifies that the initial indentation level is -1 spaces.
+ final _indents = <int>[-1];
+
+ /// Whether a simple key is allowed in this context.
+ ///
+ /// A simple key refers to any mapping key that doesn't have an explicit "?".
+ var _simpleKeyAllowed = true;
+
+ /// The stack of potential simple keys for each level of flow nesting.
+ ///
+ /// Entries in this list may be `null`, indicating that there is no valid
+ /// simple key for the associated level of nesting.
+ ///
+ /// When a ":" is parsed and there's a simple key available, a [TokenType.key]
+ /// token is inserted in [_tokens] before that key's token. This allows the
+ /// parser to tell that the key is intended to be a mapping key.
+ final _simpleKeys = <_SimpleKey?>[null];
+
+ /// The current indentation level.
+ int get _indent => _indents.last;
+
+ /// Whether the scanner's currently positioned in a block-level structure (as
+ /// opposed to flow-level).
+ bool get _inBlockContext => _simpleKeys.length == 1;
+
+ /// Whether the current character is a line break or the end of the source.
+ bool get _isBreakOrEnd => _scanner.isDone || _isBreak;
+
+ /// Whether the current character is a line break.
+ bool get _isBreak => _isBreakAt(0);
+
+ /// Whether the current character is whitespace or the end of the source.
+ bool get _isBlankOrEnd => _isBlankOrEndAt(0);
+
+ /// Whether the current character is whitespace.
+ bool get _isBlank => _isBlankAt(0);
+
+ /// Whether the current character is a valid tag name character.
+ ///
+ /// See http://yaml.org/spec/1.2/spec.html#ns-tag-name.
+ bool get _isTagChar {
+ var char = _scanner.peekChar();
+ if (char == null) return false;
+ switch (char) {
+ case HYPHEN:
+ case SEMICOLON:
+ case SLASH:
+ case COLON:
+ case AT:
+ case AMPERSAND:
+ case EQUALS:
+ case PLUS:
+ case DOLLAR:
+ case PERIOD:
+ case TILDE:
+ case QUESTION:
+ case ASTERISK:
+ case SINGLE_QUOTE:
+ case LEFT_PAREN:
+ case RIGHT_PAREN:
+ case PERCENT:
+ return true;
+ default:
+ return (char >= NUMBER_0 && char <= NUMBER_9) ||
+ (char >= LETTER_A && char <= LETTER_Z) ||
+ (char >= LETTER_CAP_A && char <= LETTER_CAP_Z);
+ }
+ }
+
+ /// Whether the current character is a valid anchor name character.
+ ///
+ /// See http://yaml.org/spec/1.2/spec.html#ns-anchor-name.
+ bool get _isAnchorChar {
+ if (!_isNonSpace) return false;
+
+ switch (_scanner.peekChar()) {
+ case COMMA:
+ case LEFT_SQUARE:
+ case RIGHT_SQUARE:
+ case LEFT_CURLY:
+ case RIGHT_CURLY:
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ /// Whether the character at the current position is a decimal digit.
+ bool get _isDigit {
+ var char = _scanner.peekChar();
+ return char != null && (char >= NUMBER_0 && char <= NUMBER_9);
+ }
+
+ /// Whether the character at the current position is a hexidecimal
+ /// digit.
+ bool get _isHex {
+ var char = _scanner.peekChar();
+ if (char == null) return false;
+ return (char >= NUMBER_0 && char <= NUMBER_9) ||
+ (char >= LETTER_A && char <= LETTER_F) ||
+ (char >= LETTER_CAP_A && char <= LETTER_CAP_F);
+ }
+
+ /// Whether the character at the current position is a plain character.
+ ///
+ /// See http://yaml.org/spec/1.2/spec.html#ns-plain-char(c).
+ bool get _isPlainChar => _isPlainCharAt(0);
+
+ /// Whether the character at the current position is a printable character
+ /// other than a line break or byte-order mark.
+ ///
+ /// See http://yaml.org/spec/1.2/spec.html#nb-char.
+ bool get _isNonBreak {
+ var char = _scanner.peekChar();
+ return switch (char) {
+ null => false,
+ LF || CR || BOM => false,
+ TAB || NEL => true,
+ _ => _isStandardCharacterAt(0),
+ };
+ }
+
+ /// Whether the character at the current position is a printable character
+ /// other than whitespace.
+ ///
+ /// See http://yaml.org/spec/1.2/spec.html#nb-char.
+ bool get _isNonSpace {
+ var char = _scanner.peekChar();
+ return switch (char) {
+ null => false,
+ LF || CR || BOM || SP => false,
+ NEL => true,
+ _ => _isStandardCharacterAt(0),
+ };
+ }
+
+ /// Returns Whether or not the current character begins a documentation
+ /// indicator.
+ ///
+ /// If so, this sets the scanner's last match to that indicator.
+ bool get _isDocumentIndicator =>
+ _scanner.column == 0 &&
+ _isBlankOrEndAt(3) &&
+ (_scanner.matches('---') || _scanner.matches('...'));
+
+ /// Creates a scanner that scans [source].
+ Scanner(String source,
+ {Uri? sourceUrl, bool recover = false, ErrorListener? errorListener})
+ : _recover = recover,
+ _errorListener = errorListener,
+ _scanner = SpanScanner.eager(source, sourceUrl: sourceUrl);
+
+ /// Consumes and returns the next token.
+ Token scan() {
+ if (_streamEndProduced) throw StateError('Out of tokens.');
+ if (!_tokenAvailable) _fetchMoreTokens();
+
+ var token = _tokens.removeFirst();
+ _tokenAvailable = false;
+ _tokensParsed++;
+ _streamEndProduced = token.type == TokenType.streamEnd;
+ return token;
+ }
+
+ /// Consumes the next token and returns the one after that.
+ Token? advance() {
+ scan();
+ return peek();
+ }
+
+ /// Returns the next token without consuming it.
+ Token? peek() {
+ if (_streamEndProduced) return null;
+ if (!_tokenAvailable) _fetchMoreTokens();
+ return _tokens.first;
+ }
+
+ /// Ensures that [_tokens] contains at least one token which can be returned.
+ void _fetchMoreTokens() {
+ while (true) {
+ if (_tokens.isNotEmpty) {
+ _staleSimpleKeys();
+
+ // If there are no more tokens to fetch, break.
+ if (_tokens.last.type == TokenType.streamEnd) break;
+
+ // If the current token could be a simple key, we need to scan more
+ // tokens until we determine whether it is or not. Otherwise we might
+ // not emit the `KEY` token before we emit the value of the key.
+ if (!_simpleKeys
+ .any((key) => key != null && key.tokenNumber == _tokensParsed)) {
+ break;
+ }
+ }
+
+ _fetchNextToken();
+ }
+ _tokenAvailable = true;
+ }
+
+ /// The dispatcher for token fetchers.
+ void _fetchNextToken() {
+ if (!_streamStartProduced) {
+ _fetchStreamStart();
+ return;
+ }
+
+ _scanToNextToken();
+ _staleSimpleKeys();
+ _unrollIndent(_scanner.column);
+
+ if (_scanner.isDone) {
+ _fetchStreamEnd();
+ return;
+ }
+
+ if (_scanner.column == 0) {
+ if (_scanner.peekChar() == PERCENT) {
+ _fetchDirective();
+ return;
+ }
+
+ if (_isBlankOrEndAt(3)) {
+ if (_scanner.matches('---')) {
+ _fetchDocumentIndicator(TokenType.documentStart);
+ return;
+ }
+
+ if (_scanner.matches('...')) {
+ _fetchDocumentIndicator(TokenType.documentEnd);
+ return;
+ }
+ }
+ }
+
+ switch (_scanner.peekChar()) {
+ case LEFT_SQUARE:
+ _fetchFlowCollectionStart(TokenType.flowSequenceStart);
+ return;
+ case LEFT_CURLY:
+ _fetchFlowCollectionStart(TokenType.flowMappingStart);
+ return;
+ case RIGHT_SQUARE:
+ _fetchFlowCollectionEnd(TokenType.flowSequenceEnd);
+ return;
+ case RIGHT_CURLY:
+ _fetchFlowCollectionEnd(TokenType.flowMappingEnd);
+ return;
+ case COMMA:
+ _fetchFlowEntry();
+ return;
+ case ASTERISK:
+ _fetchAnchor(anchor: false);
+ return;
+ case AMPERSAND:
+ _fetchAnchor();
+ return;
+ case EXCLAMATION:
+ _fetchTag();
+ return;
+ case SINGLE_QUOTE:
+ _fetchFlowScalar(singleQuote: true);
+ return;
+ case DOUBLE_QUOTE:
+ _fetchFlowScalar();
+ return;
+ case VERTICAL_BAR:
+ if (!_inBlockContext) _invalidScalarCharacter();
+ _fetchBlockScalar(literal: true);
+ return;
+ case RIGHT_ANGLE:
+ if (!_inBlockContext) _invalidScalarCharacter();
+ _fetchBlockScalar();
+ return;
+ case PERCENT:
+ case AT:
+ case GRAVE_ACCENT:
+ _invalidScalarCharacter();
+ return;
+
+ // These characters may sometimes begin plain scalars.
+ case HYPHEN:
+ if (_isPlainCharAt(1)) {
+ _fetchPlainScalar();
+ } else {
+ _fetchBlockEntry();
+ }
+ return;
+ case QUESTION:
+ if (_isPlainCharAt(1)) {
+ _fetchPlainScalar();
+ } else {
+ _fetchKey();
+ }
+ return;
+ case COLON:
+ if (!_inBlockContext && _tokens.isNotEmpty) {
+ // If a colon follows a "JSON-like" value (an explicit map or list, or
+ // a quoted string) it isn't required to have whitespace after it
+ // since it unambiguously describes a map.
+ var token = _tokens.last;
+ if (token.type == TokenType.flowSequenceEnd ||
+ token.type == TokenType.flowMappingEnd ||
+ (token.type == TokenType.scalar &&
+ (token as ScalarToken).style.isQuoted)) {
+ _fetchValue();
+ return;
+ }
+ }
+
+ if (_isPlainCharAt(1)) {
+ _fetchPlainScalar();
+ } else {
+ _fetchValue();
+ }
+ return;
+ default:
+ if (!_isNonBreak) _invalidScalarCharacter();
+
+ _fetchPlainScalar();
+ return;
+ }
+ }
+
+ /// Throws an error about a disallowed character.
+ void _invalidScalarCharacter() =>
+ _scanner.error('Unexpected character.', length: 1);
+
+ /// Checks the list of potential simple keys and remove the positions that
+ /// cannot contain simple keys anymore.
+ void _staleSimpleKeys() {
+ for (var i = 0; i < _simpleKeys.length; i++) {
+ var key = _simpleKeys[i];
+ if (key == null) continue;
+
+ // libyaml requires that all simple keys be a single line and no longer
+ // than 1024 characters. However, in section 7.4.2 of the spec
+ // (http://yaml.org/spec/1.2/spec.html#id2790832), these restrictions are
+ // only applied when the curly braces are omitted. It's difficult to
+ // retain enough context to know which keys need to have the restriction
+ // placed on them, so for now we go the other direction and allow
+ // everything but multiline simple keys in a block context.
+ if (!_inBlockContext) continue;
+
+ if (key.line == _scanner.line) continue;
+
+ if (key.required) {
+ _reportError(YamlException("Expected ':'.", _scanner.emptySpan));
+ _tokens.insert(key.tokenNumber - _tokensParsed,
+ Token(TokenType.key, key.location.pointSpan() as FileSpan));
+ }
+
+ _simpleKeys[i] = null;
+ }
+ }
+
+ /// Checks if a simple key may start at the current position and saves it if
+ /// so.
+ void _saveSimpleKey() {
+ // A simple key is required at the current position if the scanner is in the
+ // block context and the current column coincides with the indentation
+ // level.
+ var required = _inBlockContext && _indent == _scanner.column;
+
+ // A simple key is required only when it is the first token in the current
+ // line. Therefore it is always allowed. But we add a check anyway.
+ assert(_simpleKeyAllowed || !required);
+
+ if (!_simpleKeyAllowed) return;
+
+ // If the current position may start a simple key, save it.
+ _removeSimpleKey();
+ _simpleKeys[_simpleKeys.length - 1] = _SimpleKey(
+ _tokensParsed + _tokens.length,
+ _scanner.line,
+ _scanner.column,
+ _scanner.location,
+ required: required);
+ }
+
+ /// Removes a potential simple key at the current flow level.
+ void _removeSimpleKey() {
+ var key = _simpleKeys.last;
+ if (key != null && key.required) {
+ throw YamlException("Could not find expected ':' for simple key.",
+ key.location.pointSpan());
+ }
+
+ _simpleKeys[_simpleKeys.length - 1] = null;
+ }
+
+ /// Increases the flow level and resizes the simple key list.
+ void _increaseFlowLevel() {
+ _simpleKeys.add(null);
+ }
+
+ /// Decreases the flow level.
+ void _decreaseFlowLevel() {
+ if (_inBlockContext) return;
+ _simpleKeys.removeLast();
+ }
+
+ /// Pushes the current indentation level to the stack and sets the new level
+ /// if [column] is greater than [_indent].
+ ///
+ /// If it is, appends or inserts the specified token into [_tokens]. If
+ /// [tokenNumber] is provided, the corresponding token will be replaced;
+ /// otherwise, the token will be added at the end.
+ void _rollIndent(int column, TokenType type, SourceLocation location,
+ {int? tokenNumber}) {
+ if (!_inBlockContext) return;
+ if (_indent != -1 && _indent >= column) return;
+
+ // Push the current indentation level to the stack and set the new
+ // indentation level.
+ _indents.add(column);
+
+ // Create a token and insert it into the queue.
+ var token = Token(type, location.pointSpan() as FileSpan);
+ if (tokenNumber == null) {
+ _tokens.add(token);
+ } else {
+ _tokens.insert(tokenNumber - _tokensParsed, token);
+ }
+ }
+
+ /// Pops indentation levels from [_indents] until the current level becomes
+ /// less than or equal to [column].
+ ///
+ /// For each indentation level, appends a [TokenType.blockEnd] token.
+ void _unrollIndent(int column) {
+ if (!_inBlockContext) return;
+
+ while (_indent > column) {
+ _tokens.add(Token(TokenType.blockEnd, _scanner.emptySpan));
+ _indents.removeLast();
+ }
+ }
+
+ /// Pops indentation levels from [_indents] until the current level resets to
+ /// -1.
+ ///
+ /// For each indentation level, appends a [TokenType.blockEnd] token.
+ void _resetIndent() => _unrollIndent(-1);
+
+ /// Produces a [TokenType.streamStart] token.
+ void _fetchStreamStart() {
+ // Much of libyaml's initialization logic here is done in variable
+ // initializers instead.
+ _streamStartProduced = true;
+ _tokens.add(Token(TokenType.streamStart, _scanner.emptySpan));
+ }
+
+ /// Produces a [TokenType.streamEnd] token.
+ void _fetchStreamEnd() {
+ _resetIndent();
+ _removeSimpleKey();
+ _simpleKeyAllowed = false;
+ _tokens.add(Token(TokenType.streamEnd, _scanner.emptySpan));
+ }
+
+ /// Produces a [TokenType.versionDirective] or [TokenType.tagDirective]
+ /// token.
+ void _fetchDirective() {
+ _resetIndent();
+ _removeSimpleKey();
+ _simpleKeyAllowed = false;
+ var directive = _scanDirective();
+ if (directive != null) _tokens.add(directive);
+ }
+
+ /// Produces a [TokenType.documentStart] or [TokenType.documentEnd] token.
+ void _fetchDocumentIndicator(TokenType type) {
+ _resetIndent();
+ _removeSimpleKey();
+ _simpleKeyAllowed = false;
+
+ // Consume the indicator token.
+ var start = _scanner.state;
+ _scanner.readCodePoint();
+ _scanner.readCodePoint();
+ _scanner.readCodePoint();
+
+ _tokens.add(Token(type, _scanner.spanFrom(start)));
+ }
+
+ /// Produces a [TokenType.flowSequenceStart] or
+ /// [TokenType.flowMappingStart] token.
+ void _fetchFlowCollectionStart(TokenType type) {
+ _saveSimpleKey();
+ _increaseFlowLevel();
+ _simpleKeyAllowed = true;
+ _addCharToken(type);
+ }
+
+ /// Produces a [TokenType.flowSequenceEnd] or [TokenType.flowMappingEnd]
+ /// token.
+ void _fetchFlowCollectionEnd(TokenType type) {
+ _removeSimpleKey();
+ _decreaseFlowLevel();
+ _simpleKeyAllowed = false;
+ _addCharToken(type);
+ }
+
+ /// Produces a [TokenType.flowEntry] token.
+ void _fetchFlowEntry() {
+ _removeSimpleKey();
+ _simpleKeyAllowed = true;
+ _addCharToken(TokenType.flowEntry);
+ }
+
+ /// Produces a [TokenType.blockEntry] token.
+ void _fetchBlockEntry() {
+ if (_inBlockContext) {
+ if (!_simpleKeyAllowed) {
+ throw YamlException(
+ 'Block sequence entries are not allowed here.', _scanner.emptySpan);
+ }
+
+ _rollIndent(
+ _scanner.column, TokenType.blockSequenceStart, _scanner.location);
+ } else {
+ // It is an error for the '-' indicator to occur in the flow context, but
+ // we let the Parser detect and report it because it's able to point to
+ // the context.
+ }
+
+ _removeSimpleKey();
+ _simpleKeyAllowed = true;
+ _addCharToken(TokenType.blockEntry);
+ }
+
+ /// Produces the [TokenType.key] token.
+ void _fetchKey() {
+ if (_inBlockContext) {
+ if (!_simpleKeyAllowed) {
+ throw YamlException(
+ 'Mapping keys are not allowed here.', _scanner.emptySpan);
+ }
+
+ _rollIndent(
+ _scanner.column, TokenType.blockMappingStart, _scanner.location);
+ }
+
+ // Simple keys are allowed after `?` in a block context.
+ _simpleKeyAllowed = _inBlockContext;
+ _addCharToken(TokenType.key);
+ }
+
+ /// Produces the [TokenType.value] token.
+ void _fetchValue() {
+ var simpleKey = _simpleKeys.last;
+ if (simpleKey != null) {
+ // Add a [TokenType.KEY] directive before the first token of the simple
+ // key so the parser knows that it's part of a key/value pair.
+ _tokens.insert(simpleKey.tokenNumber - _tokensParsed,
+ Token(TokenType.key, simpleKey.location.pointSpan() as FileSpan));
+
+ // In the block context, we may need to add the
+ // [TokenType.BLOCK_MAPPING_START] token.
+ _rollIndent(
+ simpleKey.column, TokenType.blockMappingStart, simpleKey.location,
+ tokenNumber: simpleKey.tokenNumber);
+
+ // Remove the simple key.
+ _simpleKeys[_simpleKeys.length - 1] = null;
+
+ // A simple key cannot follow another simple key.
+ _simpleKeyAllowed = false;
+ } else if (_inBlockContext) {
+ if (!_simpleKeyAllowed) {
+ throw YamlException(
+ 'Mapping values are not allowed here. Did you miss a colon '
+ 'earlier?',
+ _scanner.emptySpan);
+ }
+
+ // If we're here, we've found the ':' indicator following a complex key.
+
+ _rollIndent(
+ _scanner.column, TokenType.blockMappingStart, _scanner.location);
+ _simpleKeyAllowed = true;
+ } else if (_simpleKeyAllowed) {
+ // If we're here, we've found the ':' indicator with an empty key. This
+ // behavior differs from libyaml, which disallows empty implicit keys.
+ _simpleKeyAllowed = false;
+ _addCharToken(TokenType.key);
+ }
+
+ _addCharToken(TokenType.value);
+ }
+
+ /// Adds a token with [type] to [_tokens].
+ ///
+ /// The span of the new token is the current character.
+ void _addCharToken(TokenType type) {
+ var start = _scanner.state;
+ _scanner.readCodePoint();
+ _tokens.add(Token(type, _scanner.spanFrom(start)));
+ }
+
+ /// Produces a [TokenType.alias] or [TokenType.anchor] token.
+ void _fetchAnchor({bool anchor = true}) {
+ _saveSimpleKey();
+ _simpleKeyAllowed = false;
+ _tokens.add(_scanAnchor(anchor: anchor));
+ }
+
+ /// Produces a [TokenType.tag] token.
+ void _fetchTag() {
+ _saveSimpleKey();
+ _simpleKeyAllowed = false;
+ _tokens.add(_scanTag());
+ }
+
+ /// Produces a [TokenType.scalar] token with style [ScalarStyle.LITERAL] or
+ /// [ScalarStyle.FOLDED].
+ void _fetchBlockScalar({bool literal = false}) {
+ _removeSimpleKey();
+ _simpleKeyAllowed = true;
+ _tokens.add(_scanBlockScalar(literal: literal));
+ }
+
+ /// Produces a [TokenType.scalar] token with style [ScalarStyle.SINGLE_QUOTED]
+ /// or [ScalarStyle.DOUBLE_QUOTED].
+ void _fetchFlowScalar({bool singleQuote = false}) {
+ _saveSimpleKey();
+ _simpleKeyAllowed = false;
+ _tokens.add(_scanFlowScalar(singleQuote: singleQuote));
+ }
+
+ /// Produces a [TokenType.scalar] token with style [ScalarStyle.PLAIN].
+ void _fetchPlainScalar() {
+ _saveSimpleKey();
+ _simpleKeyAllowed = false;
+ _tokens.add(_scanPlainScalar());
+ }
+
+ /// Eats whitespace and comments until the next token is found.
+ void _scanToNextToken() {
+ var afterLineBreak = false;
+ while (true) {
+ // Allow the BOM to start a line.
+ if (_scanner.column == 0) _scanner.scan('\uFEFF');
+
+ // Eat whitespace.
+ //
+ // libyaml disallows tabs after "-", "?", or ":", but the spec allows
+ // them. See section 6.2: http://yaml.org/spec/1.2/spec.html#id2778241.
+ while (_scanner.peekChar() == SP ||
+ ((!_inBlockContext || !afterLineBreak) &&
+ _scanner.peekChar() == TAB)) {
+ _scanner.readChar();
+ }
+
+ if (_scanner.peekChar() == TAB) {
+ _scanner.error('Tab characters are not allowed as indentation.',
+ length: 1);
+ }
+
+ // Eat a comment until a line break.
+ _skipComment();
+
+ // If we're at a line break, eat it.
+ if (_isBreak) {
+ _skipLine();
+
+ // In the block context, a new line may start a simple key.
+ if (_inBlockContext) _simpleKeyAllowed = true;
+ afterLineBreak = true;
+ } else {
+ // Otherwise we've found a token.
+ break;
+ }
+ }
+ }
+
+ /// Scans a [TokenType.versionDirective] or [TokenType.tagDirective] token.
+ ///
+ /// %YAML 1.2 # a comment \n
+ /// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ /// %TAG !yaml! tag:yaml.org,2002: \n
+ /// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ Token? _scanDirective() {
+ var start = _scanner.state;
+
+ // Eat '%'.
+ _scanner.readChar();
+
+ Token token;
+ var name = _scanDirectiveName();
+ if (name == 'YAML') {
+ token = _scanVersionDirectiveValue(start);
+ } else if (name == 'TAG') {
+ token = _scanTagDirectiveValue(start);
+ } else {
+ warn('Warning: unknown directive.', _scanner.spanFrom(start));
+
+ // libyaml doesn't support unknown directives, but the spec says to ignore
+ // them and warn: http://yaml.org/spec/1.2/spec.html#id2781147.
+ while (!_isBreakOrEnd) {
+ _scanner.readCodePoint();
+ }
+
+ return null;
+ }
+
+ // Eat the rest of the line, including any comments.
+ _skipBlanks();
+ _skipComment();
+
+ if (!_isBreakOrEnd) {
+ throw YamlException('Expected comment or line break after directive.',
+ _scanner.spanFrom(start));
+ }
+
+ _skipLine();
+ return token;
+ }
+
+ /// Scans a directive name.
+ ///
+ /// %YAML 1.2 # a comment \n
+ /// ^^^^
+ /// %TAG !yaml! tag:yaml.org,2002: \n
+ /// ^^^
+ String _scanDirectiveName() {
+ // libyaml only allows word characters in directive names, but the spec
+ // disagrees: http://yaml.org/spec/1.2/spec.html#ns-directive-name.
+ var start = _scanner.position;
+ while (_isNonSpace) {
+ _scanner.readCodePoint();
+ }
+
+ var name = _scanner.substring(start);
+ if (name.isEmpty) {
+ throw YamlException('Expected directive name.', _scanner.emptySpan);
+ } else if (!_isBlankOrEnd) {
+ throw YamlException(
+ 'Unexpected character in directive name.', _scanner.emptySpan);
+ }
+
+ return name;
+ }
+
+ /// Scans the value of a version directive.
+ ///
+ /// %YAML 1.2 # a comment \n
+ /// ^^^^^^
+ Token _scanVersionDirectiveValue(LineScannerState start) {
+ _skipBlanks();
+
+ var major = _scanVersionDirectiveNumber();
+ _scanner.expect('.');
+ var minor = _scanVersionDirectiveNumber();
+
+ return VersionDirectiveToken(_scanner.spanFrom(start), major, minor);
+ }
+
+ /// Scans the version number of a version directive.
+ ///
+ /// %YAML 1.2 # a comment \n
+ /// ^
+ /// %YAML 1.2 # a comment \n
+ /// ^
+ int _scanVersionDirectiveNumber() {
+ var start = _scanner.position;
+ while (_isDigit) {
+ _scanner.readChar();
+ }
+
+ var number = _scanner.substring(start);
+ if (number.isEmpty) {
+ throw YamlException('Expected version number.', _scanner.emptySpan);
+ }
+
+ return int.parse(number);
+ }
+
+ /// Scans the value of a tag directive.
+ ///
+ /// %TAG !yaml! tag:yaml.org,2002: \n
+ /// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ Token _scanTagDirectiveValue(LineScannerState start) {
+ _skipBlanks();
+
+ var handle = _scanTagHandle(directive: true);
+ if (!_isBlank) {
+ throw YamlException('Expected whitespace.', _scanner.emptySpan);
+ }
+
+ _skipBlanks();
+
+ var prefix = _scanTagUri();
+ if (!_isBlankOrEnd) {
+ throw YamlException('Expected whitespace.', _scanner.emptySpan);
+ }
+
+ return TagDirectiveToken(_scanner.spanFrom(start), handle, prefix);
+ }
+
+ /// Scans a [TokenType.anchor] token.
+ Token _scanAnchor({bool anchor = true}) {
+ var start = _scanner.state;
+
+ // Eat the indicator character.
+ _scanner.readCodePoint();
+
+ // libyaml only allows word characters in anchor names, but the spec
+ // disagrees: http://yaml.org/spec/1.2/spec.html#ns-anchor-char.
+ var startPosition = _scanner.position;
+ while (_isAnchorChar) {
+ _scanner.readCodePoint();
+ }
+ var name = _scanner.substring(startPosition);
+
+ var next = _scanner.peekChar();
+ if (name.isEmpty ||
+ (!_isBlankOrEnd &&
+ next != QUESTION &&
+ next != COLON &&
+ next != COMMA &&
+ next != RIGHT_SQUARE &&
+ next != RIGHT_CURLY &&
+ next != PERCENT &&
+ next != AT &&
+ next != GRAVE_ACCENT)) {
+ throw YamlException(
+ 'Expected alphanumeric character.', _scanner.emptySpan);
+ }
+
+ if (anchor) {
+ return AnchorToken(_scanner.spanFrom(start), name);
+ } else {
+ return AliasToken(_scanner.spanFrom(start), name);
+ }
+ }
+
+ /// Scans a [TokenType.tag] token.
+ Token _scanTag() {
+ String? handle;
+ String suffix;
+ var start = _scanner.state;
+
+ // Check if the tag is in the canonical form.
+ if (_scanner.peekChar(1) == LEFT_ANGLE) {
+ // Eat '!<'.
+ _scanner.readChar();
+ _scanner.readChar();
+
+ handle = '';
+ suffix = _scanTagUri();
+
+ _scanner.expect('>');
+ } else {
+ // The tag has either the '!suffix' or the '!handle!suffix' form.
+
+ // First, try to scan a handle.
+ handle = _scanTagHandle();
+
+ if (handle.length > 1 && handle.startsWith('!') && handle.endsWith('!')) {
+ suffix = _scanTagUri(flowSeparators: false);
+ } else {
+ suffix = _scanTagUri(head: handle, flowSeparators: false);
+
+ // There was no explicit handle.
+ if (suffix.isEmpty) {
+ // This is the special '!' tag.
+ handle = null;
+ suffix = '!';
+ } else {
+ handle = '!';
+ }
+ }
+ }
+
+ // libyaml insists on whitespace after a tag, but example 7.2 indicates
+ // that it's not required: http://yaml.org/spec/1.2/spec.html#id2786720.
+
+ return TagToken(_scanner.spanFrom(start), handle, suffix);
+ }
+
+ /// Scans a tag handle.
+ String _scanTagHandle({bool directive = false}) {
+ _scanner.expect('!');
+
+ var buffer = StringBuffer('!');
+
+ // libyaml only allows word characters in tags, but the spec disagrees:
+ // http://yaml.org/spec/1.2/spec.html#ns-tag-char.
+ var start = _scanner.position;
+ while (_isTagChar) {
+ _scanner.readChar();
+ }
+ buffer.write(_scanner.substring(start));
+
+ if (_scanner.peekChar() == EXCLAMATION) {
+ buffer.writeCharCode(_scanner.readCodePoint());
+ } else {
+ // It's either the '!' tag or not really a tag handle. If it's a %TAG
+ // directive, it's an error. If it's a tag token, it must be part of a
+ // URI.
+ if (directive && buffer.toString() != '!') _scanner.expect('!');
+ }
+
+ return buffer.toString();
+ }
+
+ /// Scans a tag URI.
+ ///
+ /// [head] is the initial portion of the tag that's already been scanned.
+ /// [flowSeparators] indicates whether the tag URI can contain flow
+ /// separators.
+ String _scanTagUri({String? head, bool flowSeparators = true}) {
+ var length = head == null ? 0 : head.length;
+ var buffer = StringBuffer();
+
+ // Copy the head if needed.
+ //
+ // Note that we don't copy the leading '!' character.
+ if (length > 1) buffer.write(head!.substring(1));
+
+ // The set of characters that may appear in URI is as follows:
+ //
+ // '0'-'9', 'A'-'Z', 'a'-'z', '_', '-', ';', '/', '?', ':', '@', '&',
+ // '=', '+', '$', ',', '.', '!', '~', '*', '\'', '(', ')', '[', ']',
+ // '%'.
+ //
+ // In a shorthand tag annotation, the flow separators ',', '[', and ']' are
+ // disallowed.
+ var start = _scanner.position;
+ var char = _scanner.peekChar();
+ while (_isTagChar ||
+ (flowSeparators &&
+ (char == COMMA || char == LEFT_SQUARE || char == RIGHT_SQUARE))) {
+ _scanner.readChar();
+ char = _scanner.peekChar();
+ }
+
+ // libyaml manually decodes the URL, but we don't have to do that.
+ return Uri.decodeFull(_scanner.substring(start));
+ }
+
+ /// Scans a block scalar.
+ Token _scanBlockScalar({bool literal = false}) {
+ var start = _scanner.state;
+
+ // Eat the indicator '|' or '>'.
+ _scanner.readCodePoint();
+
+ // Check for a chomping indicator.
+ var chomping = _Chomping.clip;
+ var increment = 0;
+ var char = _scanner.peekChar();
+ if (char == PLUS || char == HYPHEN) {
+ chomping = char == PLUS ? _Chomping.keep : _Chomping.strip;
+ _scanner.readCodePoint();
+
+ // Check for an indentation indicator.
+ if (_isDigit) {
+ // Check that the indentation is greater than 0.
+ if (_scanner.peekChar() == NUMBER_0) {
+ throw YamlException('0 may not be used as an indentation indicator.',
+ _scanner.spanFrom(start));
+ }
+
+ increment = _scanner.readCodePoint() - NUMBER_0;
+ }
+ } else if (_isDigit) {
+ // Do the same as above, but in the opposite order.
+ if (_scanner.peekChar() == NUMBER_0) {
+ throw YamlException('0 may not be used as an indentation indicator.',
+ _scanner.spanFrom(start));
+ }
+
+ increment = _scanner.readCodePoint() - NUMBER_0;
+
+ char = _scanner.peekChar();
+ if (char == PLUS || char == HYPHEN) {
+ chomping = char == PLUS ? _Chomping.keep : _Chomping.strip;
+ _scanner.readCodePoint();
+ }
+ }
+
+ // Eat whitespace and comments to the end of the line.
+ _skipBlanks();
+ _skipComment();
+
+ // Check if we're at the end of the line.
+ if (!_isBreakOrEnd) {
+ throw YamlException(
+ 'Expected comment or line break.', _scanner.emptySpan);
+ }
+
+ _skipLine();
+
+ // If the block scalar has an explicit indentation indicator, add that to
+ // the current indentation to get the indentation level for the scalar's
+ // contents.
+ var indent = 0;
+ if (increment != 0) {
+ indent = _indent >= 0 ? _indent + increment : increment;
+ }
+
+ // Scan the leading line breaks to determine the indentation level if
+ // needed.
+ var pair = _scanBlockScalarBreaks(indent);
+ indent = pair.indent;
+ var trailingBreaks = pair.trailingBreaks;
+
+ // Scan the block scalar contents.
+ var buffer = StringBuffer();
+ var leadingBreak = '';
+ var leadingBlank = false;
+ var trailingBlank = false;
+ var end = _scanner.state;
+ while (_scanner.column == indent && !_scanner.isDone) {
+ // Check for a document indicator. libyaml doesn't do this, but the spec
+ // mandates it. See example 9.5:
+ // http://yaml.org/spec/1.2/spec.html#id2801606.
+ if (_isDocumentIndicator) break;
+
+ // We are at the beginning of a non-empty line.
+
+ // Is there trailing whitespace?
+ trailingBlank = _isBlank;
+
+ // Check if we need to fold the leading line break.
+ if (!literal &&
+ leadingBreak.isNotEmpty &&
+ !leadingBlank &&
+ !trailingBlank) {
+ // Do we need to join the lines with a space?
+ if (trailingBreaks.isEmpty) buffer.writeCharCode(SP);
+ } else {
+ buffer.write(leadingBreak);
+ }
+ leadingBreak = '';
+
+ // Append the remaining line breaks.
+ buffer.write(trailingBreaks);
+
+ // Is there leading whitespace?
+ leadingBlank = _isBlank;
+
+ var startPosition = _scanner.position;
+ while (!_isBreakOrEnd) {
+ _scanner.readCodePoint();
+ }
+ buffer.write(_scanner.substring(startPosition));
+ end = _scanner.state;
+
+ // libyaml always reads a line here, but this breaks on block scalars at
+ // the end of the document that end without newlines. See example 8.1:
+ // http://yaml.org/spec/1.2/spec.html#id2793888.
+ if (!_scanner.isDone) leadingBreak = _readLine();
+
+ // Eat the following indentation and spaces.
+ var pair = _scanBlockScalarBreaks(indent);
+ indent = pair.indent;
+ trailingBreaks = pair.trailingBreaks;
+ }
+
+ // Chomp the tail.
+ if (chomping != _Chomping.strip) buffer.write(leadingBreak);
+ if (chomping == _Chomping.keep) buffer.write(trailingBreaks);
+
+ return ScalarToken(_scanner.spanFrom(start, end), buffer.toString(),
+ literal ? ScalarStyle.LITERAL : ScalarStyle.FOLDED);
+ }
+
+ /// Scans indentation spaces and line breaks for a block scalar.
+ ///
+ /// Determines the intendation level if needed. Returns the new indentation
+ /// level and the text of the line breaks.
+ ({int indent, String trailingBreaks}) _scanBlockScalarBreaks(int indent) {
+ var maxIndent = 0;
+ var breaks = StringBuffer();
+
+ while (true) {
+ while ((indent == 0 || _scanner.column < indent) &&
+ _scanner.peekChar() == SP) {
+ _scanner.readChar();
+ }
+
+ if (_scanner.column > maxIndent) maxIndent = _scanner.column;
+
+ // libyaml throws an error here if a tab character is detected, but the
+ // spec treats tabs like any other non-space character. See example 8.2:
+ // http://yaml.org/spec/1.2/spec.html#id2794311.
+
+ if (!_isBreak) break;
+ breaks.write(_readLine());
+ }
+
+ if (indent == 0) {
+ indent = maxIndent;
+ if (indent < _indent + 1) indent = _indent + 1;
+
+ // libyaml forces indent to be at least 1 here, but that doesn't seem to
+ // be supported by the spec.
+ }
+
+ return (indent: indent, trailingBreaks: breaks.toString());
+ }
+
+ // Scans a quoted scalar.
+ Token _scanFlowScalar({bool singleQuote = false}) {
+ var start = _scanner.state;
+ var buffer = StringBuffer();
+
+ // Eat the left quote.
+ _scanner.readChar();
+
+ while (true) {
+ // Check that there are no document indicators at the beginning of the
+ // line.
+ if (_isDocumentIndicator) {
+ _scanner.error('Unexpected document indicator.');
+ }
+
+ if (_scanner.isDone) {
+ throw YamlException('Unexpected end of file.', _scanner.emptySpan);
+ }
+
+ var leadingBlanks = false;
+ while (!_isBlankOrEnd) {
+ var char = _scanner.peekChar();
+ if (singleQuote &&
+ char == SINGLE_QUOTE &&
+ _scanner.peekChar(1) == SINGLE_QUOTE) {
+ // An escaped single quote.
+ _scanner.readChar();
+ _scanner.readChar();
+ buffer.writeCharCode(SINGLE_QUOTE);
+ } else if (char == (singleQuote ? SINGLE_QUOTE : DOUBLE_QUOTE)) {
+ // The closing quote.
+ break;
+ } else if (!singleQuote && char == BACKSLASH && _isBreakAt(1)) {
+ // An escaped newline.
+ _scanner.readChar();
+ _skipLine();
+ leadingBlanks = true;
+ break;
+ } else if (!singleQuote && char == BACKSLASH) {
+ var escapeStart = _scanner.state;
+
+ // An escape sequence.
+ int? codeLength;
+ switch (_scanner.peekChar(1)) {
+ case NUMBER_0:
+ buffer.writeCharCode(NULL);
+ break;
+ case LETTER_A:
+ buffer.writeCharCode(BELL);
+ break;
+ case LETTER_B:
+ buffer.writeCharCode(BACKSPACE);
+ break;
+ case LETTER_T:
+ case TAB:
+ buffer.writeCharCode(TAB);
+ break;
+ case LETTER_N:
+ buffer.writeCharCode(LF);
+ break;
+ case LETTER_V:
+ buffer.writeCharCode(VERTICAL_TAB);
+ break;
+ case LETTER_F:
+ buffer.writeCharCode(FORM_FEED);
+ break;
+ case LETTER_R:
+ buffer.writeCharCode(CR);
+ break;
+ case LETTER_E:
+ buffer.writeCharCode(ESCAPE);
+ break;
+ case SP:
+ case DOUBLE_QUOTE:
+ case SLASH:
+ case BACKSLASH:
+ // libyaml doesn't support an escaped forward slash, but it was
+ // added in YAML 1.2. See section 5.7:
+ // http://yaml.org/spec/1.2/spec.html#id2776092
+ buffer.writeCharCode(_scanner.peekChar(1)!);
+ break;
+ case LETTER_CAP_N:
+ buffer.writeCharCode(NEL);
+ break;
+ case UNDERSCORE:
+ buffer.writeCharCode(NBSP);
+ break;
+ case LETTER_CAP_L:
+ buffer.writeCharCode(LINE_SEPARATOR);
+ break;
+ case LETTER_CAP_P:
+ buffer.writeCharCode(PARAGRAPH_SEPARATOR);
+ break;
+ case LETTER_X:
+ codeLength = 2;
+ break;
+ case LETTER_U:
+ codeLength = 4;
+ break;
+ case LETTER_CAP_U:
+ codeLength = 8;
+ break;
+ default:
+ throw YamlException(
+ 'Unknown escape character.', _scanner.spanFrom(escapeStart));
+ }
+
+ _scanner.readChar();
+ _scanner.readChar();
+
+ if (codeLength != null) {
+ var value = 0;
+ for (var i = 0; i < codeLength; i++) {
+ if (!_isHex) {
+ _scanner.readChar();
+ throw YamlException(
+ 'Expected $codeLength-digit hexidecimal number.',
+ _scanner.spanFrom(escapeStart));
+ }
+
+ value = (value << 4) + _asHex(_scanner.readChar());
+ }
+
+ // Check the value and write the character.
+ if ((value >= 0xD800 && value <= 0xDFFF) || value > 0x10FFFF) {
+ throw YamlException('Invalid Unicode character escape code.',
+ _scanner.spanFrom(escapeStart));
+ }
+
+ buffer.writeCharCode(value);
+ }
+ } else {
+ buffer.writeCharCode(_scanner.readCodePoint());
+ }
+ }
+
+ // Check if we're at the end of a scalar.
+ if (_scanner.peekChar() == (singleQuote ? SINGLE_QUOTE : DOUBLE_QUOTE)) {
+ break;
+ }
+
+ var whitespace = StringBuffer();
+ var leadingBreak = '';
+ var trailingBreaks = StringBuffer();
+ while (_isBlank || _isBreak) {
+ if (_isBlank) {
+ // Consume a space or a tab.
+ if (!leadingBlanks) {
+ whitespace.writeCharCode(_scanner.readChar());
+ } else {
+ _scanner.readChar();
+ }
+ } else {
+ // Check if it's a first line break.
+ if (!leadingBlanks) {
+ whitespace.clear();
+ leadingBreak = _readLine();
+ leadingBlanks = true;
+ } else {
+ trailingBreaks.write(_readLine());
+ }
+ }
+ }
+
+ // Join the whitespace or fold line breaks.
+ if (leadingBlanks) {
+ if (leadingBreak.isNotEmpty && trailingBreaks.isEmpty) {
+ buffer.writeCharCode(SP);
+ } else {
+ buffer.write(trailingBreaks);
+ }
+ } else {
+ buffer.write(whitespace);
+ whitespace.clear();
+ }
+ }
+
+ // Eat the right quote.
+ _scanner.readChar();
+
+ return ScalarToken(_scanner.spanFrom(start), buffer.toString(),
+ singleQuote ? ScalarStyle.SINGLE_QUOTED : ScalarStyle.DOUBLE_QUOTED);
+ }
+
+ /// Scans a plain scalar.
+ Token _scanPlainScalar() {
+ var start = _scanner.state;
+ var end = _scanner.state;
+ var buffer = StringBuffer();
+ var leadingBreak = '';
+ var trailingBreaks = '';
+ var whitespace = StringBuffer();
+ var indent = _indent + 1;
+
+ while (true) {
+ // Check for a document indicator.
+ if (_isDocumentIndicator) break;
+
+ // Check for a comment.
+ if (_scanner.peekChar() == HASH) break;
+
+ if (_isPlainChar) {
+ // Join the whitespace or fold line breaks.
+ if (leadingBreak.isNotEmpty) {
+ if (trailingBreaks.isEmpty) {
+ buffer.writeCharCode(SP);
+ } else {
+ buffer.write(trailingBreaks);
+ }
+ leadingBreak = '';
+ trailingBreaks = '';
+ } else {
+ buffer.write(whitespace);
+ whitespace.clear();
+ }
+ }
+
+ // libyaml's notion of valid identifiers differs substantially from YAML
+ // 1.2's. We use [_isPlainChar] instead of libyaml's character here.
+ var startPosition = _scanner.position;
+ while (_isPlainChar) {
+ _scanner.readCodePoint();
+ }
+ buffer.write(_scanner.substring(startPosition));
+ end = _scanner.state;
+
+ // Is it the end?
+ if (!_isBlank && !_isBreak) break;
+
+ while (_isBlank || _isBreak) {
+ if (_isBlank) {
+ // Check for a tab character messing up the intendation.
+ if (leadingBreak.isNotEmpty &&
+ _scanner.column < indent &&
+ _scanner.peekChar() == TAB) {
+ _scanner.error('Expected a space but found a tab.', length: 1);
+ }
+
+ if (leadingBreak.isEmpty) {
+ whitespace.writeCharCode(_scanner.readChar());
+ } else {
+ _scanner.readChar();
+ }
+ } else {
+ // Check if it's a first line break.
+ if (leadingBreak.isEmpty) {
+ leadingBreak = _readLine();
+ whitespace.clear();
+ } else {
+ trailingBreaks = _readLine();
+ }
+ }
+ }
+
+ // Check the indentation level.
+ if (_inBlockContext && _scanner.column < indent) break;
+ }
+
+ // Allow a simple key after a plain scalar with leading blanks.
+ if (leadingBreak.isNotEmpty) _simpleKeyAllowed = true;
+
+ return ScalarToken(
+ _scanner.spanFrom(start, end), buffer.toString(), ScalarStyle.PLAIN);
+ }
+
+ /// Moves past the current line break, if there is one.
+ void _skipLine() {
+ var char = _scanner.peekChar();
+ if (char != CR && char != LF) return;
+ _scanner.readChar();
+ if (char == CR && _scanner.peekChar() == LF) _scanner.readChar();
+ }
+
+ // Moves past the current line break and returns a newline.
+ String _readLine() {
+ var char = _scanner.peekChar();
+
+ // libyaml supports NEL, PS, and LS characters as line separators, but this
+ // is explicitly forbidden in section 5.4 of the YAML spec.
+ if (char != CR && char != LF) {
+ throw YamlException('Expected newline.', _scanner.emptySpan);
+ }
+
+ _scanner.readChar();
+ // CR LF | CR | LF -> LF
+ if (char == CR && _scanner.peekChar() == LF) _scanner.readChar();
+ return '\n';
+ }
+
+ // Returns whether the character at [offset] is whitespace.
+ bool _isBlankAt(int offset) {
+ var char = _scanner.peekChar(offset);
+ return char == SP || char == TAB;
+ }
+
+ // Returns whether the character at [offset] is a line break.
+ bool _isBreakAt(int offset) {
+ // Libyaml considers NEL, LS, and PS to be line breaks as well, but that's
+ // contrary to the spec.
+ var char = _scanner.peekChar(offset);
+ return char == CR || char == LF;
+ }
+
+ // Returns whether the character at [offset] is whitespace or past the end of
+ // the source.
+ bool _isBlankOrEndAt(int offset) {
+ var char = _scanner.peekChar(offset);
+ return char == null ||
+ char == SP ||
+ char == TAB ||
+ char == CR ||
+ char == LF;
+ }
+
+ /// Returns whether the character at [offset] is a plain character.
+ ///
+ /// See http://yaml.org/spec/1.2/spec.html#ns-plain-char(c).
+ bool _isPlainCharAt(int offset) {
+ switch (_scanner.peekChar(offset)) {
+ case COLON:
+ return _isPlainSafeAt(offset + 1);
+ case HASH:
+ var previous = _scanner.peekChar(offset - 1);
+ return previous != SP && previous != TAB;
+ default:
+ return _isPlainSafeAt(offset);
+ }
+ }
+
+ /// Returns whether the character at [offset] is a plain-safe character.
+ ///
+ /// See http://yaml.org/spec/1.2/spec.html#ns-plain-safe(c).
+ bool _isPlainSafeAt(int offset) {
+ var char = _scanner.peekChar(offset);
+ return switch (char) {
+ null => false,
+ COMMA ||
+ LEFT_SQUARE ||
+ RIGHT_SQUARE ||
+ LEFT_CURLY ||
+ RIGHT_CURLY =>
+ // These characters are delimiters in a flow context and thus are only
+ // safe in a block context.
+ _inBlockContext,
+ SP || TAB || LF || CR || BOM => false,
+ NEL => true,
+ _ => _isStandardCharacterAt(offset)
+ };
+ }
+
+ bool _isStandardCharacterAt(int offset) {
+ var first = _scanner.peekChar(offset);
+ if (first == null) return false;
+
+ if (isHighSurrogate(first)) {
+ var next = _scanner.peekChar(offset + 1);
+ // A surrogate pair encodes code points from U+010000 to U+10FFFF, so it
+ // must be a standard character.
+ return next != null && isLowSurrogate(next);
+ }
+
+ return _isStandardCharacter(first);
+ }
+
+ bool _isStandardCharacter(int char) =>
+ (char >= 0x0020 && char <= 0x007E) ||
+ (char >= 0x00A0 && char <= 0xD7FF) ||
+ (char >= 0xE000 && char <= 0xFFFD);
+
+ /// Returns the hexidecimal value of [char].
+ int _asHex(int char) {
+ if (char <= NUMBER_9) return char - NUMBER_0;
+ if (char <= LETTER_CAP_F) return 10 + char - LETTER_CAP_A;
+ return 10 + char - LETTER_A;
+ }
+
+ /// Moves the scanner past any blank characters.
+ void _skipBlanks() {
+ while (_isBlank) {
+ _scanner.readChar();
+ }
+ }
+
+ /// Moves the scanner past a comment, if one starts at the current position.
+ void _skipComment() {
+ if (_scanner.peekChar() != HASH) return;
+ while (!_isBreakOrEnd) {
+ _scanner.readChar();
+ }
+ }
+
+ /// Reports a [YamlException] to [_errorListener] if [_recover] is true,
+ /// otherwise throws the exception.
+ void _reportError(YamlException exception) {
+ if (!_recover) {
+ throw exception;
+ }
+ _errorListener?.onError(exception);
+ }
+}
+
+/// A record of the location of a potential simple key.
+class _SimpleKey {
+ /// The index of the token that begins the simple key.
+ ///
+ /// This is the index relative to all tokens emitted, rather than relative to
+ /// [location].
+ final int tokenNumber;
+
+ /// The source location of the beginning of the simple key.
+ ///
+ /// This is used for error reporting and for determining when a simple key is
+ /// no longer on the current line.
+ final SourceLocation location;
+
+ /// The line on which the key appears.
+ ///
+ /// We could get this from [location], but that requires a binary search
+ /// whereas this is O(1).
+ final int line;
+
+ /// The column on which the key appears.
+ ///
+ /// We could get this from [location], but that requires a binary search
+ /// whereas this is O(1).
+ final int column;
+
+ /// Whether this key must exist for the document to be scanned.
+ final bool required;
+
+ _SimpleKey(
+ this.tokenNumber,
+ this.line,
+ this.column,
+ this.location, {
+ required this.required,
+ });
+}
+
+/// The ways to handle trailing whitespace for a block scalar.
+///
+/// See http://yaml.org/spec/1.2/spec.html#id2794534.
+enum _Chomping {
+ /// All trailing whitespace is discarded.
+ strip,
+
+ /// A single trailing newline is retained.
+ clip,
+
+ /// All trailing whitespace is preserved.
+ keep
+}
diff --git a/pkgs/yaml/lib/src/style.dart b/pkgs/yaml/lib/src/style.dart
new file mode 100644
index 0000000..96c3b94
--- /dev/null
+++ b/pkgs/yaml/lib/src/style.dart
@@ -0,0 +1,79 @@
+// Copyright (c) 2014, the Dart project authors.
+// Copyright (c) 2006, Kirill Simonov.
+//
+// Use of this source code is governed by an MIT-style
+// license that can be found in the LICENSE file or at
+// https://opensource.org/licenses/MIT.
+
+// ignore_for_file: constant_identifier_names
+
+import 'yaml_node.dart';
+
+/// An enum of source scalar styles.
+class ScalarStyle {
+ /// No source style was specified.
+ ///
+ /// This usually indicates a scalar constructed with [YamlScalar.wrap].
+ static const ANY = ScalarStyle._('ANY');
+
+ /// The plain scalar style, unquoted and without a prefix.
+ ///
+ /// See http://yaml.org/spec/1.2/spec.html#style/flow/plain.
+ static const PLAIN = ScalarStyle._('PLAIN');
+
+ /// The literal scalar style, with a `|` prefix.
+ ///
+ /// See http://yaml.org/spec/1.2/spec.html#id2795688.
+ static const LITERAL = ScalarStyle._('LITERAL');
+
+ /// The folded scalar style, with a `>` prefix.
+ ///
+ /// See http://yaml.org/spec/1.2/spec.html#id2796251.
+ static const FOLDED = ScalarStyle._('FOLDED');
+
+ /// The single-quoted scalar style.
+ ///
+ /// See http://yaml.org/spec/1.2/spec.html#style/flow/single-quoted.
+ static const SINGLE_QUOTED = ScalarStyle._('SINGLE_QUOTED');
+
+ /// The double-quoted scalar style.
+ ///
+ /// See http://yaml.org/spec/1.2/spec.html#style/flow/double-quoted.
+ static const DOUBLE_QUOTED = ScalarStyle._('DOUBLE_QUOTED');
+
+ final String name;
+
+ /// Whether this is a quoted style ([SINGLE_QUOTED] or [DOUBLE_QUOTED]).
+ bool get isQuoted => this == SINGLE_QUOTED || this == DOUBLE_QUOTED;
+
+ const ScalarStyle._(this.name);
+
+ @override
+ String toString() => name;
+}
+
+/// An enum of collection styles.
+class CollectionStyle {
+ /// No source style was specified.
+ ///
+ /// This usually indicates a collection constructed with [YamlList.wrap] or
+ /// [YamlMap.wrap].
+ static const ANY = CollectionStyle._('ANY');
+
+ /// The indentation-based block style.
+ ///
+ /// See http://yaml.org/spec/1.2/spec.html#id2797293.
+ static const BLOCK = CollectionStyle._('BLOCK');
+
+ /// The delimiter-based block style.
+ ///
+ /// See http://yaml.org/spec/1.2/spec.html#id2790088.
+ static const FLOW = CollectionStyle._('FLOW');
+
+ final String name;
+
+ const CollectionStyle._(this.name);
+
+ @override
+ String toString() => name;
+}
diff --git a/pkgs/yaml/lib/src/token.dart b/pkgs/yaml/lib/src/token.dart
new file mode 100644
index 0000000..7d5d6bc
--- /dev/null
+++ b/pkgs/yaml/lib/src/token.dart
@@ -0,0 +1,158 @@
+// Copyright (c) 2014, the Dart project authors.
+// Copyright (c) 2006, Kirill Simonov.
+//
+// Use of this source code is governed by an MIT-style
+// license that can be found in the LICENSE file or at
+// https://opensource.org/licenses/MIT.
+
+import 'package:source_span/source_span.dart';
+
+import 'scanner.dart';
+import 'style.dart';
+
+/// A token emitted by a [Scanner].
+class Token {
+ final TokenType type;
+ final FileSpan span;
+
+ Token(this.type, this.span);
+
+ @override
+ String toString() => type.toString();
+}
+
+/// A token representing a `%YAML` directive.
+class VersionDirectiveToken implements Token {
+ @override
+ TokenType get type => TokenType.versionDirective;
+ @override
+ final FileSpan span;
+
+ /// The declared major version of the document.
+ final int major;
+
+ /// The declared minor version of the document.
+ final int minor;
+
+ VersionDirectiveToken(this.span, this.major, this.minor);
+
+ @override
+ String toString() => 'VERSION_DIRECTIVE $major.$minor';
+}
+
+/// A token representing a `%TAG` directive.
+class TagDirectiveToken implements Token {
+ @override
+ TokenType get type => TokenType.tagDirective;
+ @override
+ final FileSpan span;
+
+ /// The tag handle used in the document.
+ final String handle;
+
+ /// The tag prefix that the handle maps to.
+ final String prefix;
+
+ TagDirectiveToken(this.span, this.handle, this.prefix);
+
+ @override
+ String toString() => 'TAG_DIRECTIVE $handle $prefix';
+}
+
+/// A token representing an anchor (`&foo`).
+class AnchorToken implements Token {
+ @override
+ TokenType get type => TokenType.anchor;
+ @override
+ final FileSpan span;
+
+ final String name;
+
+ AnchorToken(this.span, this.name);
+
+ @override
+ String toString() => 'ANCHOR $name';
+}
+
+/// A token representing an alias (`*foo`).
+class AliasToken implements Token {
+ @override
+ TokenType get type => TokenType.alias;
+ @override
+ final FileSpan span;
+
+ final String name;
+
+ AliasToken(this.span, this.name);
+
+ @override
+ String toString() => 'ALIAS $name';
+}
+
+/// A token representing a tag (`!foo`).
+class TagToken implements Token {
+ @override
+ TokenType get type => TokenType.tag;
+ @override
+ final FileSpan span;
+
+ /// The tag handle for named tags.
+ final String? handle;
+
+ /// The tag suffix.
+ final String suffix;
+
+ TagToken(this.span, this.handle, this.suffix);
+
+ @override
+ String toString() => 'TAG $handle $suffix';
+}
+
+/// A scalar value.
+class ScalarToken implements Token {
+ @override
+ TokenType get type => TokenType.scalar;
+ @override
+ final FileSpan span;
+
+ /// The unparsed contents of the value..
+ final String value;
+
+ /// The style of the scalar in the original source.
+ final ScalarStyle style;
+
+ ScalarToken(this.span, this.value, this.style);
+
+ @override
+ String toString() => 'SCALAR $style "$value"';
+}
+
+/// The types of [Token] objects.
+enum TokenType {
+ streamStart,
+ streamEnd,
+
+ versionDirective,
+ tagDirective,
+ documentStart,
+ documentEnd,
+
+ blockSequenceStart,
+ blockMappingStart,
+ blockEnd,
+
+ flowSequenceStart,
+ flowSequenceEnd,
+ flowMappingStart,
+ flowMappingEnd,
+
+ blockEntry,
+ flowEntry,
+ key,
+ value,
+
+ alias,
+ anchor,
+ tag,
+ scalar
+}
diff --git a/pkgs/yaml/lib/src/utils.dart b/pkgs/yaml/lib/src/utils.dart
new file mode 100644
index 0000000..0dc132f
--- /dev/null
+++ b/pkgs/yaml/lib/src/utils.dart
@@ -0,0 +1,40 @@
+// Copyright (c) 2013, the Dart project authors.
+// Copyright (c) 2006, Kirill Simonov.
+//
+// Use of this source code is governed by an MIT-style
+// license that can be found in the LICENSE file or at
+// https://opensource.org/licenses/MIT.
+
+import 'package:source_span/source_span.dart';
+
+/// Print a warning.
+///
+/// If [span] is passed, associates the warning with that span.
+void warn(String message, [SourceSpan? span]) =>
+ yamlWarningCallback(message, span);
+
+/// A callback for emitting a warning.
+///
+/// [message] is the text of the warning. If [span] is passed, it's the portion
+/// of the document that the warning is associated with and should be included
+/// in the printed warning.
+typedef YamlWarningCallback = void Function(String message, [SourceSpan? span]);
+
+/// A callback for emitting a warning.
+///
+/// In a very few cases, the YAML spec indicates that an implementation should
+/// emit a warning. To do so, it calls this callback. The default implementation
+/// prints a message using [print].
+// ignore: prefer_function_declarations_over_variables
+YamlWarningCallback yamlWarningCallback = (message, [SourceSpan? span]) {
+ // TODO(nweiz): Print to stderr with color when issue 6943 is fixed and
+ // dart:io is available.
+ if (span != null) message = span.message(message);
+ print(message);
+};
+
+/// Whether [codeUnit] is a UTF-16 high surrogate.
+bool isHighSurrogate(int codeUnit) => codeUnit >>> 10 == 0x36;
+
+/// Whether [codeUnit] is a UTF-16 low surrogate.
+bool isLowSurrogate(int codeUnit) => codeUnit >>> 10 == 0x37;
diff --git a/pkgs/yaml/lib/src/yaml_document.dart b/pkgs/yaml/lib/src/yaml_document.dart
new file mode 100644
index 0000000..da6aa1e
--- /dev/null
+++ b/pkgs/yaml/lib/src/yaml_document.dart
@@ -0,0 +1,71 @@
+// Copyright (c) 2014, the Dart project authors.
+// Copyright (c) 2006, Kirill Simonov.
+//
+// Use of this source code is governed by an MIT-style
+// license that can be found in the LICENSE file or at
+// https://opensource.org/licenses/MIT.
+
+import 'dart:collection';
+
+import 'package:source_span/source_span.dart';
+
+import 'yaml_node.dart';
+
+/// A YAML document, complete with metadata.
+class YamlDocument {
+ /// The contents of the document.
+ final YamlNode contents;
+
+ /// The span covering the entire document.
+ final SourceSpan span;
+
+ /// The version directive for the document, if any.
+ final VersionDirective? versionDirective;
+
+ /// The tag directives for the document.
+ final List<TagDirective> tagDirectives;
+
+ /// Whether the beginning of the document was implicit (versus explicit via
+ /// `===`).
+ final bool startImplicit;
+
+ /// Whether the end of the document was implicit (versus explicit via `...`).
+ final bool endImplicit;
+
+ /// Users of the library should not use this constructor.
+ YamlDocument.internal(this.contents, this.span, this.versionDirective,
+ List<TagDirective> tagDirectives,
+ {this.startImplicit = false, this.endImplicit = false})
+ : tagDirectives = UnmodifiableListView(tagDirectives);
+
+ @override
+ String toString() => contents.toString();
+}
+
+/// A directive indicating which version of YAML a document was written to.
+class VersionDirective {
+ /// The major version number.
+ final int major;
+
+ /// The minor version number.
+ final int minor;
+
+ VersionDirective(this.major, this.minor);
+
+ @override
+ String toString() => '%YAML $major.$minor';
+}
+
+/// A directive describing a custom tag handle.
+class TagDirective {
+ /// The handle for use in the document.
+ final String handle;
+
+ /// The prefix that the handle maps to.
+ final String prefix;
+
+ TagDirective(this.handle, this.prefix);
+
+ @override
+ String toString() => '%TAG $handle $prefix';
+}
diff --git a/pkgs/yaml/lib/src/yaml_exception.dart b/pkgs/yaml/lib/src/yaml_exception.dart
new file mode 100644
index 0000000..7aa5389
--- /dev/null
+++ b/pkgs/yaml/lib/src/yaml_exception.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2013, the Dart project authors.
+// Copyright (c) 2006, Kirill Simonov.
+//
+// Use of this source code is governed by an MIT-style
+// license that can be found in the LICENSE file or at
+// https://opensource.org/licenses/MIT.
+
+import 'package:source_span/source_span.dart';
+
+/// An error thrown by the YAML processor.
+class YamlException extends SourceSpanFormatException {
+ YamlException(super.message, super.span);
+}
diff --git a/pkgs/yaml/lib/src/yaml_node.dart b/pkgs/yaml/lib/src/yaml_node.dart
new file mode 100644
index 0000000..bd17b6c
--- /dev/null
+++ b/pkgs/yaml/lib/src/yaml_node.dart
@@ -0,0 +1,191 @@
+// Copyright (c) 2012, the Dart project authors.
+// Copyright (c) 2006, Kirill Simonov.
+//
+// Use of this source code is governed by an MIT-style
+// license that can be found in the LICENSE file or at
+// https://opensource.org/licenses/MIT.
+
+import 'dart:collection' as collection;
+
+import 'package:collection/collection.dart';
+import 'package:source_span/source_span.dart';
+
+import 'event.dart';
+import 'null_span.dart';
+import 'style.dart';
+import 'yaml_node_wrapper.dart';
+
+/// An interface for parsed nodes from a YAML source tree.
+///
+/// [YamlMap]s and [YamlList]s implement this interface in addition to the
+/// normal [Map] and [List] interfaces, so any maps and lists will be
+/// [YamlNode]s regardless of how they're accessed.
+///
+/// Scalars values like strings and numbers, on the other hand, don't have this
+/// interface by default. Instead, they can be accessed as [YamlScalar]s via
+/// [YamlMap.nodes] or [YamlList.nodes].
+abstract class YamlNode {
+ /// The source span for this node.
+ ///
+ /// [SourceSpan.message] can be used to produce a human-friendly message about
+ /// this node.
+ SourceSpan get span => _span;
+ SourceSpan _span;
+
+ YamlNode._(this._span);
+
+ /// The inner value of this node.
+ ///
+ /// For [YamlScalar]s, this will return the wrapped value. For [YamlMap] and
+ /// [YamlList], it will return `this`, since they already implement [Map] and
+ /// [List], respectively.
+ dynamic get value;
+}
+
+/// A read-only [Map] parsed from YAML.
+class YamlMap extends YamlNode with collection.MapMixin, UnmodifiableMapMixin {
+ /// A view of `this` where the keys and values are guaranteed to be
+ /// [YamlNode]s.
+ ///
+ /// The key type is `dynamic` to allow values to be accessed using
+ /// non-[YamlNode] keys, but [Map.keys] and [Map.forEach] will always expose
+ /// them as [YamlNode]s. For example, for `{"foo": [1, 2, 3]}` [nodes] will be
+ /// a map from a [YamlScalar] to a [YamlList], but since the key type is
+ /// `dynamic` `map.nodes["foo"]` will still work.
+ final Map<dynamic, YamlNode> nodes;
+
+ /// The style used for the map in the original document.
+ final CollectionStyle style;
+
+ @override
+ Map get value => this;
+
+ @override
+ Iterable get keys => nodes.keys.map((node) => (node as YamlNode).value);
+
+ /// Creates an empty YamlMap.
+ ///
+ /// This map's [span] won't have useful location information. However, it will
+ /// have a reasonable implementation of [SourceSpan.message]. If [sourceUrl]
+ /// is passed, it's used as the [SourceSpan.sourceUrl].
+ ///
+ /// [sourceUrl] may be either a [String], a [Uri], or `null`.
+ factory YamlMap({Object? sourceUrl}) => YamlMapWrapper(const {}, sourceUrl);
+
+ /// Wraps a Dart map so that it can be accessed (recursively) like a
+ /// [YamlMap].
+ ///
+ /// Any [SourceSpan]s returned by this map or its children will be dummies
+ /// without useful location information. However, they will have a reasonable
+ /// implementation of [SourceSpan.message]. If [sourceUrl] is
+ /// passed, it's used as the [SourceSpan.sourceUrl].
+ ///
+ /// [sourceUrl] may be either a [String], a [Uri], or `null`.
+ factory YamlMap.wrap(Map dartMap,
+ {Object? sourceUrl, CollectionStyle style = CollectionStyle.ANY}) =>
+ YamlMapWrapper(dartMap, sourceUrl, style: style);
+
+ /// Users of the library should not use this constructor.
+ YamlMap.internal(Map<dynamic, YamlNode> nodes, super.span, this.style)
+ : nodes = UnmodifiableMapView<dynamic, YamlNode>(nodes),
+ super._();
+
+ @override
+ dynamic operator [](Object? key) => nodes[key]?.value;
+}
+
+// TODO(nweiz): Use UnmodifiableListMixin when issue 18970 is fixed.
+/// A read-only [List] parsed from YAML.
+class YamlList extends YamlNode with collection.ListMixin {
+ final List<YamlNode> nodes;
+
+ /// The style used for the list in the original document.
+ final CollectionStyle style;
+
+ @override
+ List get value => this;
+
+ @override
+ int get length => nodes.length;
+
+ @override
+ set length(int index) {
+ throw UnsupportedError('Cannot modify an unmodifiable List');
+ }
+
+ /// Creates an empty YamlList.
+ ///
+ /// This list's [span] won't have useful location information. However, it
+ /// will have a reasonable implementation of [SourceSpan.message]. If
+ /// [sourceUrl] is passed, it's used as the [SourceSpan.sourceUrl].
+ ///
+ /// [sourceUrl] may be either a [String], a [Uri], or `null`.
+ factory YamlList({Object? sourceUrl}) => YamlListWrapper(const [], sourceUrl);
+
+ /// Wraps a Dart list so that it can be accessed (recursively) like a
+ /// [YamlList].
+ ///
+ /// Any [SourceSpan]s returned by this list or its children will be dummies
+ /// without useful location information. However, they will have a reasonable
+ /// implementation of [SourceSpan.message]. If [sourceUrl] is
+ /// passed, it's used as the [SourceSpan.sourceUrl].
+ ///
+ /// [sourceUrl] may be either a [String], a [Uri], or `null`.
+ factory YamlList.wrap(List dartList,
+ {Object? sourceUrl, CollectionStyle style = CollectionStyle.ANY}) =>
+ YamlListWrapper(dartList, sourceUrl, style: style);
+
+ /// Users of the library should not use this constructor.
+ YamlList.internal(List<YamlNode> nodes, super.span, this.style)
+ : nodes = UnmodifiableListView<YamlNode>(nodes),
+ super._();
+
+ @override
+ dynamic operator [](int index) => nodes[index].value;
+
+ @override
+ void operator []=(int index, Object? value) {
+ throw UnsupportedError('Cannot modify an unmodifiable List');
+ }
+}
+
+/// A wrapped scalar value parsed from YAML.
+class YamlScalar extends YamlNode {
+ @override
+ final dynamic value;
+
+ /// The style used for the scalar in the original document.
+ final ScalarStyle style;
+
+ /// Wraps a Dart value in a [YamlScalar].
+ ///
+ /// This scalar's [span] won't have useful location information. However, it
+ /// will have a reasonable implementation of [SourceSpan.message]. If
+ /// [sourceUrl] is passed, it's used as the [SourceSpan.sourceUrl].
+ ///
+ /// [sourceUrl] may be either a [String], a [Uri], or `null`.
+ YamlScalar.wrap(this.value, {Object? sourceUrl, this.style = ScalarStyle.ANY})
+ : super._(NullSpan(sourceUrl)) {
+ ArgumentError.checkNotNull(style, 'style');
+ }
+
+ /// Users of the library should not use this constructor.
+ YamlScalar.internal(this.value, ScalarEvent scalar)
+ : style = scalar.style,
+ super._(scalar.span);
+
+ /// Users of the library should not use this constructor.
+ YamlScalar.internalWithSpan(this.value, SourceSpan span)
+ : style = ScalarStyle.ANY,
+ super._(span);
+
+ @override
+ String toString() => value.toString();
+}
+
+/// Sets the source span of a [YamlNode].
+///
+/// This method is not exposed publicly.
+void setSpan(YamlNode node, SourceSpan span) {
+ node._span = span;
+}
diff --git a/pkgs/yaml/lib/src/yaml_node_wrapper.dart b/pkgs/yaml/lib/src/yaml_node_wrapper.dart
new file mode 100644
index 0000000..5250844
--- /dev/null
+++ b/pkgs/yaml/lib/src/yaml_node_wrapper.dart
@@ -0,0 +1,189 @@
+// Copyright (c) 2014, the Dart project authors.
+// Copyright (c) 2006, Kirill Simonov.
+//
+// Use of this source code is governed by an MIT-style
+// license that can be found in the LICENSE file or at
+// https://opensource.org/licenses/MIT.
+
+import 'dart:collection';
+
+import 'package:collection/collection.dart' as pkg_collection;
+import 'package:source_span/source_span.dart';
+
+import 'null_span.dart';
+import 'style.dart';
+import 'yaml_node.dart';
+
+/// A wrapper that makes a normal Dart map behave like a [YamlMap].
+class YamlMapWrapper extends MapBase
+ with pkg_collection.UnmodifiableMapMixin
+ implements YamlMap {
+ @override
+ final CollectionStyle style;
+
+ final Map _dartMap;
+
+ @override
+ final SourceSpan span;
+
+ @override
+ final Map<dynamic, YamlNode> nodes;
+
+ @override
+ Map get value => this;
+
+ @override
+ Iterable get keys => _dartMap.keys;
+
+ YamlMapWrapper(Map dartMap, Object? sourceUrl,
+ {CollectionStyle style = CollectionStyle.ANY})
+ : this._(dartMap, NullSpan(sourceUrl), style: style);
+
+ YamlMapWrapper._(Map dartMap, this.span, {this.style = CollectionStyle.ANY})
+ : _dartMap = dartMap,
+ nodes = _YamlMapNodes(dartMap, span) {
+ ArgumentError.checkNotNull(style, 'style');
+ }
+
+ @override
+ dynamic operator [](Object? key) {
+ var value = _dartMap[key];
+ if (value is Map) return YamlMapWrapper._(value, span);
+ if (value is List) return YamlListWrapper._(value, span);
+ return value;
+ }
+
+ @override
+ int get hashCode => _dartMap.hashCode;
+
+ @override
+ bool operator ==(Object other) =>
+ other is YamlMapWrapper && other._dartMap == _dartMap;
+}
+
+/// The implementation of [YamlMapWrapper.nodes] as a wrapper around the Dart
+/// map.
+class _YamlMapNodes extends MapBase<dynamic, YamlNode>
+ with pkg_collection.UnmodifiableMapMixin<dynamic, YamlNode> {
+ final Map _dartMap;
+
+ final SourceSpan _span;
+
+ @override
+ Iterable get keys =>
+ _dartMap.keys.map((key) => YamlScalar.internalWithSpan(key, _span));
+
+ _YamlMapNodes(this._dartMap, this._span);
+
+ @override
+ YamlNode? operator [](Object? key) {
+ // Use "as" here because key being assigned to invalidates type propagation.
+ if (key is YamlScalar) key = key.value;
+ if (!_dartMap.containsKey(key)) return null;
+ return _nodeForValue(_dartMap[key], _span);
+ }
+
+ @override
+ int get hashCode => _dartMap.hashCode;
+
+ @override
+ bool operator ==(Object other) =>
+ other is _YamlMapNodes && other._dartMap == _dartMap;
+}
+
+// TODO(nweiz): Use UnmodifiableListMixin when issue 18970 is fixed.
+/// A wrapper that makes a normal Dart list behave like a [YamlList].
+class YamlListWrapper extends ListBase implements YamlList {
+ @override
+ final CollectionStyle style;
+
+ final List _dartList;
+
+ @override
+ final SourceSpan span;
+
+ @override
+ final List<YamlNode> nodes;
+
+ @override
+ List get value => this;
+
+ @override
+ int get length => _dartList.length;
+
+ @override
+ set length(int index) {
+ throw UnsupportedError('Cannot modify an unmodifiable List.');
+ }
+
+ YamlListWrapper(List dartList, Object? sourceUrl,
+ {CollectionStyle style = CollectionStyle.ANY})
+ : this._(dartList, NullSpan(sourceUrl), style: style);
+
+ YamlListWrapper._(List dartList, this.span,
+ {this.style = CollectionStyle.ANY})
+ : _dartList = dartList,
+ nodes = _YamlListNodes(dartList, span) {
+ ArgumentError.checkNotNull(style, 'style');
+ }
+
+ @override
+ dynamic operator [](int index) {
+ var value = _dartList[index];
+ if (value is Map) return YamlMapWrapper._(value, span);
+ if (value is List) return YamlListWrapper._(value, span);
+ return value;
+ }
+
+ @override
+ void operator []=(int index, Object? value) {
+ throw UnsupportedError('Cannot modify an unmodifiable List.');
+ }
+
+ @override
+ int get hashCode => _dartList.hashCode;
+
+ @override
+ bool operator ==(Object other) =>
+ other is YamlListWrapper && other._dartList == _dartList;
+}
+
+// TODO(nweiz): Use UnmodifiableListMixin when issue 18970 is fixed.
+/// The implementation of [YamlListWrapper.nodes] as a wrapper around the Dart
+/// list.
+class _YamlListNodes extends ListBase<YamlNode> {
+ final List _dartList;
+
+ final SourceSpan _span;
+
+ @override
+ int get length => _dartList.length;
+
+ @override
+ set length(int index) {
+ throw UnsupportedError('Cannot modify an unmodifiable List.');
+ }
+
+ _YamlListNodes(this._dartList, this._span);
+
+ @override
+ YamlNode operator [](int index) => _nodeForValue(_dartList[index], _span);
+
+ @override
+ void operator []=(int index, Object? value) {
+ throw UnsupportedError('Cannot modify an unmodifiable List.');
+ }
+
+ @override
+ int get hashCode => _dartList.hashCode;
+
+ @override
+ bool operator ==(Object other) =>
+ other is _YamlListNodes && other._dartList == _dartList;
+}
+
+YamlNode _nodeForValue(Object? value, SourceSpan span) {
+ if (value is Map) return YamlMapWrapper._(value, span);
+ if (value is List) return YamlListWrapper._(value, span);
+ return YamlScalar.internalWithSpan(value, span);
+}
diff --git a/pkgs/yaml/lib/yaml.dart b/pkgs/yaml/lib/yaml.dart
new file mode 100644
index 0000000..26cc9b8
--- /dev/null
+++ b/pkgs/yaml/lib/yaml.dart
@@ -0,0 +1,126 @@
+// Copyright (c) 2012, the Dart project authors.
+// Copyright (c) 2006, Kirill Simonov.
+//
+// Use of this source code is governed by an MIT-style
+// license that can be found in the LICENSE file or at
+// https://opensource.org/licenses/MIT.
+
+import 'src/error_listener.dart';
+import 'src/loader.dart';
+import 'src/style.dart';
+import 'src/yaml_document.dart';
+import 'src/yaml_exception.dart';
+import 'src/yaml_node.dart';
+
+export 'src/style.dart';
+export 'src/utils.dart' show YamlWarningCallback, yamlWarningCallback;
+export 'src/yaml_document.dart';
+export 'src/yaml_exception.dart';
+export 'src/yaml_node.dart' hide setSpan;
+
+/// Loads a single document from a YAML string.
+///
+/// If the string contains more than one document, this throws a
+/// [YamlException]. In future releases, this will become an [ArgumentError].
+///
+/// The return value is mostly normal Dart objects. However, since YAML mappings
+/// support some key types that the default Dart map implementation doesn't
+/// (NaN, lists, and maps), all maps in the returned document are [YamlMap]s.
+/// These have a few small behavioral differences from the default Map
+/// implementation; for details, see the [YamlMap] class.
+///
+/// If [sourceUrl] is passed, it's used as the URL from which the YAML
+/// originated for error reporting.
+///
+/// If [recover] is true, will attempt to recover from parse errors and may
+/// return invalid or synthetic nodes. If [errorListener] is also supplied, its
+/// onError method will be called for each error recovered from. It is not valid
+/// to provide [errorListener] if [recover] is false.
+dynamic loadYaml(String yaml,
+ {Uri? sourceUrl, bool recover = false, ErrorListener? errorListener}) =>
+ loadYamlNode(yaml,
+ sourceUrl: sourceUrl,
+ recover: recover,
+ errorListener: errorListener)
+ .value;
+
+/// Loads a single document from a YAML string as a [YamlNode].
+///
+/// This is just like [loadYaml], except that where [loadYaml] would return a
+/// normal Dart value this returns a [YamlNode] instead. This allows the caller
+/// to be confident that the return value will always be a [YamlNode].
+YamlNode loadYamlNode(String yaml,
+ {Uri? sourceUrl, bool recover = false, ErrorListener? errorListener}) =>
+ loadYamlDocument(yaml,
+ sourceUrl: sourceUrl,
+ recover: recover,
+ errorListener: errorListener)
+ .contents;
+
+/// Loads a single document from a YAML string as a [YamlDocument].
+///
+/// This is just like [loadYaml], except that where [loadYaml] would return a
+/// normal Dart value this returns a [YamlDocument] instead. This allows the
+/// caller to access document metadata.
+YamlDocument loadYamlDocument(String yaml,
+ {Uri? sourceUrl, bool recover = false, ErrorListener? errorListener}) {
+ var loader = Loader(yaml,
+ sourceUrl: sourceUrl, recover: recover, errorListener: errorListener);
+ var document = loader.load();
+ if (document == null) {
+ return YamlDocument.internal(YamlScalar.internalWithSpan(null, loader.span),
+ loader.span, null, const []);
+ }
+
+ var nextDocument = loader.load();
+ if (nextDocument != null) {
+ throw YamlException('Only expected one document.', nextDocument.span);
+ }
+
+ return document;
+}
+
+/// Loads a stream of documents from a YAML string.
+///
+/// The return value is mostly normal Dart objects. However, since YAML mappings
+/// support some key types that the default Dart map implementation doesn't
+/// (NaN, lists, and maps), all maps in the returned document are [YamlMap]s.
+/// These have a few small behavioral differences from the default Map
+/// implementation; for details, see the [YamlMap] class.
+///
+/// If [sourceUrl] is passed, it's used as the URL from which the YAML
+/// originated for error reporting.
+YamlList loadYamlStream(String yaml, {Uri? sourceUrl}) {
+ var loader = Loader(yaml, sourceUrl: sourceUrl);
+
+ var documents = <YamlDocument>[];
+ var document = loader.load();
+ while (document != null) {
+ documents.add(document);
+ document = loader.load();
+ }
+
+ // TODO(jmesserly): the type on the `document` parameter is a workaround for:
+ // https://github.com/dart-lang/dev_compiler/issues/203
+ return YamlList.internal(
+ documents.map((YamlDocument document) => document.contents).toList(),
+ loader.span,
+ CollectionStyle.ANY);
+}
+
+/// Loads a stream of documents from a YAML string.
+///
+/// This is like [loadYamlStream], except that it returns [YamlDocument]s with
+/// metadata wrapping the document contents.
+List<YamlDocument> loadYamlDocuments(String yaml, {Uri? sourceUrl}) {
+ var loader = Loader(yaml, sourceUrl: sourceUrl);
+
+ var documents = <YamlDocument>[];
+ var document = loader.load();
+ while (document != null) {
+ documents.add(document);
+ document = loader.load();
+ }
+
+ return documents;
+}
diff --git a/pkgs/yaml/pubspec.yaml b/pkgs/yaml/pubspec.yaml
new file mode 100644
index 0000000..fb37436
--- /dev/null
+++ b/pkgs/yaml/pubspec.yaml
@@ -0,0 +1,21 @@
+name: yaml
+version: 3.1.3
+description: A parser for YAML, a human-friendly data serialization standard
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/yaml
+
+topics:
+ - yaml
+ - config-format
+
+environment:
+ sdk: ^3.4.0
+
+dependencies:
+ collection: ^1.15.0
+ source_span: ^1.8.0
+ string_scanner: ^1.2.0
+
+dev_dependencies:
+ dart_flutter_team_lints: ^3.0.0
+ path: ^1.8.0
+ test: ^1.16.6
diff --git a/pkgs/yaml/test/span_test.dart b/pkgs/yaml/test/span_test.dart
new file mode 100644
index 0000000..03b7f9c
--- /dev/null
+++ b/pkgs/yaml/test/span_test.dart
@@ -0,0 +1,173 @@
+// Copyright (c) 2019, the Dart project authors.
+// Copyright (c) 2006, Kirill Simonov.
+//
+// Use of this source code is governed by an MIT-style
+// license that can be found in the LICENSE file or at
+// https://opensource.org/licenses/MIT.
+
+import 'dart:convert';
+
+import 'package:source_span/source_span.dart';
+import 'package:test/test.dart';
+import 'package:yaml/yaml.dart';
+
+void _expectSpan(SourceSpan source, String expected) {
+ final result = source.message('message');
+ printOnFailure("r'''\n$result'''");
+
+ expect(result, expected);
+}
+
+void main() {
+ late YamlMap yaml;
+
+ setUpAll(() {
+ yaml = loadYaml(const JsonEncoder.withIndent(' ').convert({
+ 'num': 42,
+ 'nested': {
+ 'null': null,
+ 'num': 42,
+ },
+ 'null': null,
+ })) as YamlMap;
+ });
+
+ test('first root key', () {
+ _expectSpan(
+ yaml.nodes['num']!.span,
+ r'''
+line 2, column 9: message
+ ╷
+2 │ "num": 42,
+ │ ^^
+ ╵''',
+ );
+ });
+
+ test('first root key', () {
+ _expectSpan(
+ yaml.nodes['null']!.span,
+ r'''
+line 7, column 10: message
+ ╷
+7 │ "null": null
+ │ ^^^^
+ ╵''',
+ );
+ });
+
+ group('nested', () {
+ late YamlMap nestedMap;
+
+ setUpAll(() {
+ nestedMap = yaml.nodes['nested'] as YamlMap;
+ });
+
+ test('first root key', () {
+ _expectSpan(
+ nestedMap.nodes['null']!.span,
+ r'''
+line 4, column 11: message
+ ╷
+4 │ "null": null,
+ │ ^^^^
+ ╵''',
+ );
+ });
+
+ test('first root key', () {
+ _expectSpan(
+ nestedMap.nodes['num']!.span,
+ r'''
+line 5, column 10: message
+ ╷
+5 │ "num": 42
+ │ ┌──────────^
+6 │ │ },
+ │ └─^
+ ╵''',
+ );
+ });
+ });
+
+ group('block', () {
+ late YamlList list, nestedList;
+
+ setUpAll(() {
+ const yamlStr = '''
+- foo
+-
+ - one
+ -
+ - three
+ -
+ - five
+ -
+-
+ a : b
+ c : d
+- bar
+''';
+
+ list = loadYaml(yamlStr) as YamlList;
+ nestedList = list.nodes[1] as YamlList;
+ });
+
+ test('root nodes span', () {
+ _expectSpan(list.nodes[0].span, r'''
+line 1, column 3: message
+ ╷
+1 │ - foo
+ │ ^^^
+ ╵''');
+
+ _expectSpan(list.nodes[1].span, r'''
+line 3, column 3: message
+ ╷
+3 │ ┌ - one
+4 │ │ -
+5 │ │ - three
+6 │ │ -
+7 │ │ - five
+8 │ └ -
+ ╵''');
+
+ _expectSpan(list.nodes[2].span, r'''
+line 10, column 3: message
+ ╷
+10 │ ┌ a : b
+11 │ └ c : d
+ ╵''');
+
+ _expectSpan(list.nodes[3].span, r'''
+line 12, column 3: message
+ ╷
+12 │ - bar
+ │ ^^^
+ ╵''');
+ });
+
+ test('null nodes span', () {
+ _expectSpan(nestedList.nodes[1].span, r'''
+line 4, column 3: message
+ ╷
+4 │ -
+ │ ^
+ ╵''');
+
+ _expectSpan(nestedList.nodes[3].span, r'''
+line 6, column 3: message
+ ╷
+6 │ -
+ │ ^
+ ╵''');
+
+ _expectSpan(nestedList.nodes[5].span, r'''
+line 8, column 3: message
+ ╷
+8 │ -
+ │ ^
+ ╵''');
+ });
+ });
+}
diff --git a/pkgs/yaml/test/utils.dart b/pkgs/yaml/test/utils.dart
new file mode 100644
index 0000000..372440a
--- /dev/null
+++ b/pkgs/yaml/test/utils.dart
@@ -0,0 +1,95 @@
+// Copyright (c) 2014, the Dart project authors.
+// Copyright (c) 2006, Kirill Simonov.
+//
+// Use of this source code is governed by an MIT-style
+// license that can be found in the LICENSE file or at
+// https://opensource.org/licenses/MIT.
+
+import 'package:test/test.dart';
+import 'package:yaml/src/equality.dart' as equality;
+import 'package:yaml/yaml.dart';
+
+/// A matcher that validates that a closure or Future throws a [YamlException].
+final Matcher throwsYamlException = throwsA(isA<YamlException>());
+
+/// Returns a matcher that asserts that the value equals [expected].
+///
+/// This handles recursive loops and considers `NaN` to equal itself.
+Matcher deepEquals(Object? expected) => predicate(
+ (actual) => equality.deepEquals(actual, expected), 'equals $expected');
+
+/// Constructs a new yaml.YamlMap, optionally from a normal Map.
+Map deepEqualsMap([Map? from]) {
+ var map = equality.deepEqualsMap<Object?, Object?>();
+ if (from != null) map.addAll(from);
+ return map;
+}
+
+/// Asserts that an error has the given message and starts at the given line/col.
+void expectErrorAtLineCol(
+ YamlException error, String message, int line, int col) {
+ expect(error.message, equals(message));
+ expect(error.span!.start.line, equals(line));
+ expect(error.span!.start.column, equals(col));
+}
+
+/// Asserts that a string containing a single YAML document produces a given
+/// value when loaded.
+void expectYamlLoads(Object? expected, String source) {
+ var actual = loadYaml(cleanUpLiteral(source));
+ expect(actual, deepEquals(expected));
+}
+
+/// Asserts that a string containing a stream of YAML documents produces a given
+/// list of values when loaded.
+void expectYamlStreamLoads(List expected, String source) {
+ var actual = loadYamlStream(cleanUpLiteral(source));
+ expect(actual, deepEquals(expected));
+}
+
+/// Asserts that a string containing a single YAML document throws a
+/// [YamlException].
+void expectYamlFails(String source) {
+ expect(() => loadYaml(cleanUpLiteral(source)), throwsYamlException);
+}
+
+/// Removes eight spaces of leading indentation from a multiline string.
+///
+/// Note that this is very sensitive to how the literals are styled. They should
+/// be:
+/// '''
+/// Text starts on own line. Lines up with subsequent lines.
+/// Lines are indented exactly 8 characters from the left margin.
+/// Close is on the same line.'''
+///
+/// This does nothing if text is only a single line.
+String cleanUpLiteral(String text) {
+ var lines = text.split('\n');
+ if (lines.length <= 1) return text;
+
+ for (var j = 0; j < lines.length; j++) {
+ if (lines[j].length > 8) {
+ lines[j] = lines[j].substring(8, lines[j].length);
+ } else {
+ lines[j] = '';
+ }
+ }
+
+ return lines.join('\n');
+}
+
+/// Indents each line of [text] so that, when passed to [cleanUpLiteral], it
+/// will produce output identical to [text].
+///
+/// This is useful for literals that need to include newlines but can't be
+/// conveniently represented as multi-line strings.
+String indentLiteral(String text) {
+ var lines = text.split('\n');
+ if (lines.length <= 1) return text;
+
+ for (var i = 0; i < lines.length; i++) {
+ lines[i] = ' ${lines[i]}';
+ }
+
+ return lines.join('\n');
+}
diff --git a/pkgs/yaml/test/yaml_node_wrapper_test.dart b/pkgs/yaml/test/yaml_node_wrapper_test.dart
new file mode 100644
index 0000000..637b778
--- /dev/null
+++ b/pkgs/yaml/test/yaml_node_wrapper_test.dart
@@ -0,0 +1,235 @@
+// Copyright (c) 2012, the Dart project authors.
+// Copyright (c) 2006, Kirill Simonov.
+//
+// Use of this source code is governed by an MIT-style
+// license that can be found in the LICENSE file or at
+// https://opensource.org/licenses/MIT.
+
+// ignore_for_file: avoid_dynamic_calls
+
+import 'package:source_span/source_span.dart';
+import 'package:test/test.dart';
+import 'package:yaml/yaml.dart';
+
+void main() {
+ test('YamlMap() with no sourceUrl', () {
+ var map = YamlMap();
+ expect(map, isEmpty);
+ expect(map.nodes, isEmpty);
+ expect(map.span, isNullSpan(isNull));
+ });
+
+ test('YamlMap() with a sourceUrl', () {
+ var map = YamlMap(sourceUrl: 'source');
+ expect(map.span, isNullSpan(Uri.parse('source')));
+ });
+
+ test('YamlList() with no sourceUrl', () {
+ var list = YamlList();
+ expect(list, isEmpty);
+ expect(list.nodes, isEmpty);
+ expect(list.span, isNullSpan(isNull));
+ });
+
+ test('YamlList() with a sourceUrl', () {
+ var list = YamlList(sourceUrl: 'source');
+ expect(list.span, isNullSpan(Uri.parse('source')));
+ });
+
+ test('YamlMap.wrap() with no sourceUrl', () {
+ var map = YamlMap.wrap({
+ 'list': [1, 2, 3],
+ 'map': {
+ 'foo': 'bar',
+ 'nested': [4, 5, 6]
+ },
+ 'scalar': 'value'
+ });
+
+ expect(
+ map,
+ equals({
+ 'list': [1, 2, 3],
+ 'map': {
+ 'foo': 'bar',
+ 'nested': [4, 5, 6]
+ },
+ 'scalar': 'value'
+ }));
+
+ expect(map.span, isNullSpan(isNull));
+ expect(map['list'], isA<YamlList>());
+ expect(map['list'].nodes[0], isA<YamlScalar>());
+ expect(map['list'].span, isNullSpan(isNull));
+ expect(map['map'], isA<YamlMap>());
+ expect(map['map'].nodes['foo'], isA<YamlScalar>());
+ expect(map['map']['nested'], isA<YamlList>());
+ expect(map['map'].span, isNullSpan(isNull));
+ expect(map.nodes['scalar'], isA<YamlScalar>());
+ expect(map.nodes['scalar']!.value, 'value');
+ expect(map.nodes['scalar']!.span, isNullSpan(isNull));
+ expect(map['scalar'], 'value');
+ expect(map.keys, unorderedEquals(['list', 'map', 'scalar']));
+ expect(map.nodes.keys, everyElement(isA<YamlScalar>()));
+ expect(map.nodes[YamlScalar.wrap('list')], equals([1, 2, 3]));
+ expect(map.style, equals(CollectionStyle.ANY));
+ expect((map.nodes['list'] as YamlList).style, equals(CollectionStyle.ANY));
+ expect((map.nodes['map'] as YamlMap).style, equals(CollectionStyle.ANY));
+ expect((map['map'].nodes['nested'] as YamlList).style,
+ equals(CollectionStyle.ANY));
+ });
+
+ test('YamlMap.wrap() with a sourceUrl', () {
+ var map = YamlMap.wrap({
+ 'list': [1, 2, 3],
+ 'map': {
+ 'foo': 'bar',
+ 'nested': [4, 5, 6]
+ },
+ 'scalar': 'value'
+ }, sourceUrl: 'source');
+
+ var source = Uri.parse('source');
+ expect(map.span, isNullSpan(source));
+ expect(map['list'].span, isNullSpan(source));
+ expect(map['map'].span, isNullSpan(source));
+ expect(map.nodes['scalar']!.span, isNullSpan(source));
+ });
+
+ test('YamlMap.wrap() with a sourceUrl and style', () {
+ var map = YamlMap.wrap({
+ 'list': [1, 2, 3],
+ 'map': {
+ 'foo': 'bar',
+ 'nested': [4, 5, 6]
+ },
+ 'scalar': 'value'
+ }, sourceUrl: 'source', style: CollectionStyle.BLOCK);
+
+ expect(map.style, equals(CollectionStyle.BLOCK));
+ expect((map.nodes['list'] as YamlList).style, equals(CollectionStyle.ANY));
+ expect((map.nodes['map'] as YamlMap).style, equals(CollectionStyle.ANY));
+ expect((map['map'].nodes['nested'] as YamlList).style,
+ equals(CollectionStyle.ANY));
+ });
+
+ test('YamlList.wrap() with no sourceUrl', () {
+ var list = YamlList.wrap([
+ [1, 2, 3],
+ {
+ 'foo': 'bar',
+ 'nested': [4, 5, 6]
+ },
+ 'value'
+ ]);
+
+ expect(
+ list,
+ equals([
+ [1, 2, 3],
+ {
+ 'foo': 'bar',
+ 'nested': [4, 5, 6]
+ },
+ 'value'
+ ]));
+
+ expect(list.span, isNullSpan(isNull));
+ expect(list[0], isA<YamlList>());
+ expect(list[0].nodes[0], isA<YamlScalar>());
+ expect(list[0].span, isNullSpan(isNull));
+ expect(list[1], isA<YamlMap>());
+ expect(list[1].nodes['foo'], isA<YamlScalar>());
+ expect(list[1]['nested'], isA<YamlList>());
+ expect(list[1].span, isNullSpan(isNull));
+ expect(list.nodes[2], isA<YamlScalar>());
+ expect(list.nodes[2].value, 'value');
+ expect(list.nodes[2].span, isNullSpan(isNull));
+ expect(list[2], 'value');
+ expect(list.style, equals(CollectionStyle.ANY));
+ expect((list[0] as YamlList).style, equals(CollectionStyle.ANY));
+ expect((list[1] as YamlMap).style, equals(CollectionStyle.ANY));
+ expect((list[1]['nested'] as YamlList).style, equals(CollectionStyle.ANY));
+ });
+
+ test('YamlList.wrap() with a sourceUrl', () {
+ var list = YamlList.wrap([
+ [1, 2, 3],
+ {
+ 'foo': 'bar',
+ 'nested': [4, 5, 6]
+ },
+ 'value'
+ ], sourceUrl: 'source');
+
+ var source = Uri.parse('source');
+ expect(list.span, isNullSpan(source));
+ expect(list[0].span, isNullSpan(source));
+ expect(list[1].span, isNullSpan(source));
+ expect(list.nodes[2].span, isNullSpan(source));
+ });
+
+ test('YamlList.wrap() with a sourceUrl and style', () {
+ var list = YamlList.wrap([
+ [1, 2, 3],
+ {
+ 'foo': 'bar',
+ 'nested': [4, 5, 6]
+ },
+ 'value'
+ ], sourceUrl: 'source', style: CollectionStyle.FLOW);
+
+ expect(list.style, equals(CollectionStyle.FLOW));
+ expect((list[0] as YamlList).style, equals(CollectionStyle.ANY));
+ expect((list[1] as YamlMap).style, equals(CollectionStyle.ANY));
+ expect((list[1]['nested'] as YamlList).style, equals(CollectionStyle.ANY));
+ });
+
+ test('re-wrapped objects equal one another', () {
+ var list = YamlList.wrap([
+ [1, 2, 3],
+ {'foo': 'bar'}
+ ]);
+
+ expect(list[0] == list[0], isTrue);
+ expect(list[0].nodes == list[0].nodes, isTrue);
+ expect(list[0] == YamlList.wrap([1, 2, 3]), isFalse);
+ expect(list[1] == list[1], isTrue);
+ expect(list[1].nodes == list[1].nodes, isTrue);
+ expect(list[1] == YamlMap.wrap({'foo': 'bar'}), isFalse);
+ });
+
+ test('YamlScalar.wrap() with no sourceUrl', () {
+ var scalar = YamlScalar.wrap('foo');
+
+ expect(scalar.span, isNullSpan(isNull));
+ expect(scalar.value, 'foo');
+ expect(scalar.style, equals(ScalarStyle.ANY));
+ });
+
+ test('YamlScalar.wrap() with sourceUrl', () {
+ var scalar = YamlScalar.wrap('foo', sourceUrl: 'source');
+
+ var source = Uri.parse('source');
+ expect(scalar.span, isNullSpan(source));
+ });
+
+ test('YamlScalar.wrap() with sourceUrl and style', () {
+ var scalar = YamlScalar.wrap('foo',
+ sourceUrl: 'source', style: ScalarStyle.DOUBLE_QUOTED);
+
+ expect(scalar.style, equals(ScalarStyle.DOUBLE_QUOTED));
+ });
+}
+
+Matcher isNullSpan(Object sourceUrl) => predicate((SourceSpan span) {
+ expect(span, isA<SourceSpan>());
+ expect(span.length, equals(0));
+ expect(span.text, isEmpty);
+ expect(span.start, equals(span.end));
+ expect(span.start.offset, equals(0));
+ expect(span.start.line, equals(0));
+ expect(span.start.column, equals(0));
+ expect(span.sourceUrl, sourceUrl);
+ return true;
+ });
diff --git a/pkgs/yaml/test/yaml_test.dart b/pkgs/yaml/test/yaml_test.dart
new file mode 100644
index 0000000..3b5b77d
--- /dev/null
+++ b/pkgs/yaml/test/yaml_test.dart
@@ -0,0 +1,1921 @@
+// Copyright (c) 2012, the Dart project authors.
+// Copyright (c) 2006, Kirill Simonov.
+//
+// Use of this source code is governed by an MIT-style
+// license that can be found in the LICENSE file or at
+// https://opensource.org/licenses/MIT.
+
+// ignore_for_file: avoid_dynamic_calls
+
+import 'package:test/test.dart';
+import 'package:yaml/src/error_listener.dart';
+import 'package:yaml/yaml.dart';
+
+import 'utils.dart';
+
+void main() {
+ var infinity = double.parse('Infinity');
+ var nan = double.parse('NaN');
+
+ group('has a friendly error message for', () {
+ var tabError = predicate((e) =>
+ e.toString().contains('Tab characters are not allowed as indentation'));
+
+ test('using a tab as indentation', () {
+ expect(() => loadYaml('foo:\n\tbar'), throwsA(tabError));
+ });
+
+ test('using a tab not as indentation', () {
+ expect(() => loadYaml('''
+ "foo
+ \tbar"
+ error'''), throwsA(isNot(tabError)));
+ });
+ });
+
+ group('refuses', () {
+ // Regression test for #19.
+ test('invalid contents', () {
+ expectYamlFails('{');
+ });
+
+ test('duplicate mapping keys', () {
+ expectYamlFails('{a: 1, a: 2}');
+ });
+
+ group('documents that declare version', () {
+ test('1.0', () {
+ expectYamlFails('''
+ %YAML 1.0
+ --- text
+ ''');
+ });
+
+ test('1.3', () {
+ expectYamlFails('''
+ %YAML 1.3
+ --- text
+ ''');
+ });
+
+ test('2.0', () {
+ expectYamlFails('''
+ %YAML 2.0
+ --- text
+ ''');
+ });
+ });
+ });
+
+ group('recovers', () {
+ var collector = ErrorCollector();
+ setUp(() {
+ collector = ErrorCollector();
+ });
+
+ test('from incomplete leading keys', () {
+ final yaml = cleanUpLiteral(r'''
+ dependencies:
+ zero
+ one: any
+ ''');
+ var result = loadYaml(yaml, recover: true, errorListener: collector);
+ expect(
+ result,
+ deepEquals({
+ 'dependencies': {
+ 'zero': null,
+ 'one': 'any',
+ }
+ }));
+ expect(collector.errors.length, equals(1));
+ // These errors are reported at the start of the next token (after the
+ // whitespace/newlines).
+ expectErrorAtLineCol(collector.errors[0], "Expected ':'.", 2, 2);
+ // Skipped because this case is not currently handled. If it's the first
+ // package without the colon, because the value is indented from the line
+ // above, the whole `zero\n one` is treated as a scalar value.
+ }, skip: true);
+ test('from incomplete keys', () {
+ final yaml = cleanUpLiteral(r'''
+ dependencies:
+ one: any
+ two
+ three:
+ four
+ five:
+ 1.2.3
+ six: 5.4.3
+ ''');
+ var result = loadYaml(yaml, recover: true, errorListener: collector);
+ expect(
+ result,
+ deepEquals({
+ 'dependencies': {
+ 'one': 'any',
+ 'two': null,
+ 'three': null,
+ 'four': null,
+ 'five': '1.2.3',
+ 'six': '5.4.3',
+ }
+ }));
+
+ expect(collector.errors.length, equals(2));
+ // These errors are reported at the start of the next token (after the
+ // whitespace/newlines).
+ expectErrorAtLineCol(collector.errors[0], "Expected ':'.", 3, 2);
+ expectErrorAtLineCol(collector.errors[1], "Expected ':'.", 5, 2);
+ });
+ test('from incomplete trailing keys', () {
+ final yaml = cleanUpLiteral(r'''
+ dependencies:
+ six: 5.4.3
+ seven
+ ''');
+ var result = loadYaml(yaml, recover: true);
+ expect(
+ result,
+ deepEquals({
+ 'dependencies': {
+ 'six': '5.4.3',
+ 'seven': null,
+ }
+ }));
+ });
+ });
+
+ test('includes source span information', () {
+ var yaml = loadYamlNode(r'''
+- foo:
+ bar
+- 123
+''') as YamlList;
+
+ expect(yaml.span.start.line, equals(0));
+ expect(yaml.span.start.column, equals(0));
+ expect(yaml.span.end.line, equals(3));
+ expect(yaml.span.end.column, equals(0));
+
+ var map = yaml.nodes.first as YamlMap;
+ expect(map.span.start.line, equals(0));
+ expect(map.span.start.column, equals(2));
+ expect(map.span.end.line, equals(2));
+ expect(map.span.end.column, equals(0));
+
+ var key = map.nodes.keys.first;
+ expect(key.span.start.line, equals(0));
+ expect(key.span.start.column, equals(2));
+ expect(key.span.end.line, equals(0));
+ expect(key.span.end.column, equals(5));
+
+ var value = map.nodes.values.first;
+ expect(value.span.start.line, equals(1));
+ expect(value.span.start.column, equals(4));
+ expect(value.span.end.line, equals(1));
+ expect(value.span.end.column, equals(7));
+
+ var scalar = yaml.nodes.last;
+ expect(scalar.span.start.line, equals(2));
+ expect(scalar.span.start.column, equals(2));
+ expect(scalar.span.end.line, equals(2));
+ expect(scalar.span.end.column, equals(5));
+ });
+
+ // The following tests are all taken directly from the YAML spec
+ // (http://www.yaml.org/spec/1.2/spec.html). Most of them are code examples
+ // that are directly included in the spec, but additional tests are derived
+ // from the prose.
+
+ // A few examples from the spec are deliberately excluded, because they test
+ // features that this implementation doesn't intend to support (character
+ // encoding detection and user-defined tags). More tests are commented out,
+ // because they're intended to be supported but not yet implemented.
+
+ // Chapter 2 is just a preview of various Yaml documents. It's probably not
+ // necessary to test its examples, but it would be nice to test everything in
+ // the spec.
+ group('2.1: Collections', () {
+ test('[Example 2.1]', () {
+ expectYamlLoads(['Mark McGwire', 'Sammy Sosa', 'Ken Griffey'], '''
+ - Mark McGwire
+ - Sammy Sosa
+ - Ken Griffey''');
+ });
+
+ test('[Example 2.2]', () {
+ expectYamlLoads({'hr': 65, 'avg': 0.278, 'rbi': 147}, '''
+ hr: 65 # Home runs
+ avg: 0.278 # Batting average
+ rbi: 147 # Runs Batted In''');
+ });
+
+ test('[Example 2.3]', () {
+ expectYamlLoads({
+ 'american': ['Boston Red Sox', 'Detroit Tigers', 'New York Yankees'],
+ 'national': ['New York Mets', 'Chicago Cubs', 'Atlanta Braves'],
+ }, '''
+ american:
+ - Boston Red Sox
+ - Detroit Tigers
+ - New York Yankees
+ national:
+ - New York Mets
+ - Chicago Cubs
+ - Atlanta Braves''');
+ });
+
+ test('[Example 2.4]', () {
+ expectYamlLoads([
+ {'name': 'Mark McGwire', 'hr': 65, 'avg': 0.278},
+ {'name': 'Sammy Sosa', 'hr': 63, 'avg': 0.288},
+ ], '''
+ -
+ name: Mark McGwire
+ hr: 65
+ avg: 0.278
+ -
+ name: Sammy Sosa
+ hr: 63
+ avg: 0.288''');
+ });
+
+ test('[Example 2.5]', () {
+ expectYamlLoads([
+ ['name', 'hr', 'avg'],
+ ['Mark McGwire', 65, 0.278],
+ ['Sammy Sosa', 63, 0.288]
+ ], '''
+ - [name , hr, avg ]
+ - [Mark McGwire, 65, 0.278]
+ - [Sammy Sosa , 63, 0.288]''');
+ });
+
+ test('[Example 2.6]', () {
+ expectYamlLoads({
+ 'Mark McGwire': {'hr': 65, 'avg': 0.278},
+ 'Sammy Sosa': {'hr': 63, 'avg': 0.288}
+ }, '''
+ Mark McGwire: {hr: 65, avg: 0.278}
+ Sammy Sosa: {
+ hr: 63,
+ avg: 0.288
+ }''');
+ });
+ });
+
+ group('2.2: Structures', () {
+ test('[Example 2.7]', () {
+ expectYamlStreamLoads([
+ ['Mark McGwire', 'Sammy Sosa', 'Ken Griffey'],
+ ['Chicago Cubs', 'St Louis Cardinals']
+ ], '''
+ # Ranking of 1998 home runs
+ ---
+ - Mark McGwire
+ - Sammy Sosa
+ - Ken Griffey
+
+ # Team ranking
+ ---
+ - Chicago Cubs
+ - St Louis Cardinals''');
+ });
+
+ test('[Example 2.8]', () {
+ expectYamlStreamLoads([
+ {'time': '20:03:20', 'player': 'Sammy Sosa', 'action': 'strike (miss)'},
+ {'time': '20:03:47', 'player': 'Sammy Sosa', 'action': 'grand slam'},
+ ], '''
+ ---
+ time: 20:03:20
+ player: Sammy Sosa
+ action: strike (miss)
+ ...
+ ---
+ time: 20:03:47
+ player: Sammy Sosa
+ action: grand slam
+ ...''');
+ });
+
+ test('[Example 2.9]', () {
+ expectYamlLoads({
+ 'hr': ['Mark McGwire', 'Sammy Sosa'],
+ 'rbi': ['Sammy Sosa', 'Ken Griffey']
+ }, '''
+ ---
+ hr: # 1998 hr ranking
+ - Mark McGwire
+ - Sammy Sosa
+ rbi:
+ # 1998 rbi ranking
+ - Sammy Sosa
+ - Ken Griffey''');
+ });
+
+ test('[Example 2.10]', () {
+ expectYamlLoads({
+ 'hr': ['Mark McGwire', 'Sammy Sosa'],
+ 'rbi': ['Sammy Sosa', 'Ken Griffey']
+ }, '''
+ ---
+ hr:
+ - Mark McGwire
+ # Following node labeled SS
+ - &SS Sammy Sosa
+ rbi:
+ - *SS # Subsequent occurrence
+ - Ken Griffey''');
+ });
+
+ test('[Example 2.11]', () {
+ var doc = deepEqualsMap();
+ doc[['Detroit Tigers', 'Chicago cubs']] = ['2001-07-23'];
+ doc[['New York Yankees', 'Atlanta Braves']] = [
+ '2001-07-02',
+ '2001-08-12',
+ '2001-08-14'
+ ];
+ expectYamlLoads(doc, '''
+ ? - Detroit Tigers
+ - Chicago cubs
+ :
+ - 2001-07-23
+
+ ? [ New York Yankees,
+ Atlanta Braves ]
+ : [ 2001-07-02, 2001-08-12,
+ 2001-08-14 ]''');
+ });
+
+ test('[Example 2.12]', () {
+ expectYamlLoads([
+ {'item': 'Super Hoop', 'quantity': 1},
+ {'item': 'Basketball', 'quantity': 4},
+ {'item': 'Big Shoes', 'quantity': 1},
+ ], '''
+ ---
+ # Products purchased
+ - item : Super Hoop
+ quantity: 1
+ - item : Basketball
+ quantity: 4
+ - item : Big Shoes
+ quantity: 1''');
+ });
+ });
+
+ group('2.3: Scalars', () {
+ test('[Example 2.13]', () {
+ expectYamlLoads(cleanUpLiteral('''
+ \\//||\\/||
+ // || ||__'''), '''
+ # ASCII Art
+ --- |
+ \\//||\\/||
+ // || ||__''');
+ });
+
+ test('[Example 2.14]', () {
+ expectYamlLoads("Mark McGwire's year was crippled by a knee injury.", '''
+ --- >
+ Mark McGwire's
+ year was crippled
+ by a knee injury.''');
+ });
+
+ test('[Example 2.15]', () {
+ expectYamlLoads(cleanUpLiteral('''
+ Sammy Sosa completed another fine season with great stats.
+
+ 63 Home Runs
+ 0.288 Batting Average
+
+ What a year!'''), '''
+ >
+ Sammy Sosa completed another
+ fine season with great stats.
+
+ 63 Home Runs
+ 0.288 Batting Average
+
+ What a year!''');
+ });
+
+ test('[Example 2.16]', () {
+ expectYamlLoads({
+ 'name': 'Mark McGwire',
+ 'accomplishment': 'Mark set a major league home run record in 1998.\n',
+ 'stats': '65 Home Runs\n0.278 Batting Average'
+ }, '''
+ name: Mark McGwire
+ accomplishment: >
+ Mark set a major league
+ home run record in 1998.
+ stats: |
+ 65 Home Runs
+ 0.278 Batting Average''');
+ });
+
+ test('[Example 2.17]', () {
+ expectYamlLoads({
+ 'unicode': 'Sosa did fine.\u263A \u{1F680}',
+ 'control': '\b1998\t1999\t2000\n',
+ 'hex esc': '\r\n is \r\n',
+ 'single': '"Howdy!" he cried.',
+ 'quoted': " # Not a 'comment'.",
+ 'tie-fighter': '|\\-*-/|',
+ 'surrogate-pair': 'I \u{D83D}\u{DE03} ️Dart!',
+ 'key-\u{D83D}\u{DD11}': 'Look\u{D83D}\u{DE03}\u{D83C}\u{DF89}surprise!',
+ }, """
+ unicode: "Sosa did fine.\\u263A \\U0001F680"
+ control: "\\b1998\\t1999\\t2000\\n"
+ hex esc: "\\x0d\\x0a is \\r\\n"
+
+ single: '"Howdy!" he cried.'
+ quoted: ' # Not a ''comment''.'
+ tie-fighter: '|\\-*-/|'
+
+ surrogate-pair: I \u{D83D}\u{DE03} ️Dart!
+ key-\u{D83D}\u{DD11}: Look\u{D83D}\u{DE03}\u{D83C}\u{DF89}surprise!""");
+ });
+
+ test('[Example 2.18]', () {
+ expectYamlLoads({
+ 'plain': 'This unquoted scalar spans many lines.',
+ 'quoted': 'So does this quoted scalar.\n'
+ }, '''
+ plain:
+ This unquoted scalar
+ spans many lines.
+
+ quoted: "So does this
+ quoted scalar.\\n"''');
+ });
+ });
+
+ group('2.4: Tags', () {
+ test('[Example 2.19]', () {
+ expectYamlLoads({
+ 'canonical': 12345,
+ 'decimal': 12345,
+ 'octal': 12,
+ 'hexadecimal': 12
+ }, '''
+ canonical: 12345
+ decimal: +12345
+ octal: 0o14
+ hexadecimal: 0xC''');
+ });
+
+ test('[Example 2.20]', () {
+ expectYamlLoads({
+ 'canonical': 1230.15,
+ 'exponential': 1230.15,
+ 'fixed': 1230.15,
+ 'negative infinity': -infinity,
+ 'not a number': nan
+ }, '''
+ canonical: 1.23015e+3
+ exponential: 12.3015e+02
+ fixed: 1230.15
+ negative infinity: -.inf
+ not a number: .NaN''');
+ });
+
+ test('[Example 2.21]', () {
+ var doc = deepEqualsMap({
+ 'booleans': [true, false],
+ 'string': '012345'
+ });
+ doc[null] = null;
+ expectYamlLoads(doc, """
+ null:
+ booleans: [ true, false ]
+ string: '012345'""");
+ });
+
+ // Examples 2.22 through 2.26 test custom tag URIs, which this
+ // implementation currently doesn't plan to support.
+ });
+
+ group('2.5 Full Length Example', () {
+ // Example 2.27 tests custom tag URIs, which this implementation currently
+ // doesn't plan to support.
+
+ test('[Example 2.28]', () {
+ expectYamlStreamLoads([
+ {
+ 'Time': '2001-11-23 15:01:42 -5',
+ 'User': 'ed',
+ 'Warning': 'This is an error message for the log file'
+ },
+ {
+ 'Time': '2001-11-23 15:02:31 -5',
+ 'User': 'ed',
+ 'Warning': 'A slightly different error message.'
+ },
+ {
+ 'DateTime': '2001-11-23 15:03:17 -5',
+ 'User': 'ed',
+ 'Fatal': 'Unknown variable "bar"',
+ 'Stack': [
+ {
+ 'file': 'TopClass.py',
+ 'line': 23,
+ 'code': 'x = MoreObject("345\\n")\n'
+ },
+ {'file': 'MoreClass.py', 'line': 58, 'code': 'foo = bar'}
+ ]
+ }
+ ], '''
+ ---
+ Time: 2001-11-23 15:01:42 -5
+ User: ed
+ Warning:
+ This is an error message
+ for the log file
+ ---
+ Time: 2001-11-23 15:02:31 -5
+ User: ed
+ Warning:
+ A slightly different error
+ message.
+ ---
+ DateTime: 2001-11-23 15:03:17 -5
+ User: ed
+ Fatal:
+ Unknown variable "bar"
+ Stack:
+ - file: TopClass.py
+ line: 23
+ code: |
+ x = MoreObject("345\\n")
+ - file: MoreClass.py
+ line: 58
+ code: |-
+ foo = bar''');
+ });
+ });
+
+ // Chapter 3 just talks about the structure of loading and dumping Yaml.
+ // Chapter 4 explains conventions used in the spec.
+
+ // Chapter 5: Characters
+ group('5.1: Character Set', () {
+ void expectAllowsCharacter(int charCode) {
+ var char = String.fromCharCodes([charCode]);
+ expectYamlLoads('The character "$char" is allowed',
+ 'The character "$char" is allowed');
+ }
+
+ void expectAllowsQuotedCharacter(int charCode) {
+ var char = String.fromCharCodes([charCode]);
+ expectYamlLoads("The character '$char' is allowed",
+ '"The character \'$char\' is allowed"');
+ }
+
+ void expectDisallowsCharacter(int charCode) {
+ var char = String.fromCharCodes([charCode]);
+ expectYamlFails('The character "$char" is disallowed');
+ }
+
+ test("doesn't include C0 control characters", () {
+ expectDisallowsCharacter(0x0);
+ expectDisallowsCharacter(0x8);
+ expectDisallowsCharacter(0x1F);
+ });
+
+ test('includes TAB', () => expectAllowsCharacter(0x9));
+ test("doesn't include DEL", () => expectDisallowsCharacter(0x7F));
+
+ test("doesn't include C1 control characters", () {
+ expectDisallowsCharacter(0x80);
+ expectDisallowsCharacter(0x8A);
+ expectDisallowsCharacter(0x9F);
+ });
+
+ test('includes NEL', () => expectAllowsCharacter(0x85));
+
+ group('within quoted strings', () {
+ test('includes DEL', () => expectAllowsQuotedCharacter(0x7F));
+ test('includes C1 control characters', () {
+ expectAllowsQuotedCharacter(0x80);
+ expectAllowsQuotedCharacter(0x8A);
+ expectAllowsQuotedCharacter(0x9F);
+ });
+ });
+ });
+
+ // Skipping section 5.2 (Character Encodings), since at the moment the module
+ // assumes that the client code is providing it with a string of the proper
+ // encoding.
+
+ group('5.3: Indicator Characters', () {
+ test('[Example 5.3]', () {
+ expectYamlLoads({
+ 'sequence': ['one', 'two'],
+ 'mapping': {'sky': 'blue', 'sea': 'green'}
+ }, '''
+ sequence:
+ - one
+ - two
+ mapping:
+ ? sky
+ : blue
+ sea : green''');
+ });
+
+ test('[Example 5.4]', () {
+ expectYamlLoads({
+ 'sequence': ['one', 'two'],
+ 'mapping': {'sky': 'blue', 'sea': 'green'}
+ }, '''
+ sequence: [ one, two, ]
+ mapping: { sky: blue, sea: green }''');
+ });
+
+ test('[Example 5.5]', () => expectYamlLoads(null, '# Comment only.'));
+
+ // Skipping 5.6 because it uses an undefined tag.
+
+ test('[Example 5.7]', () {
+ expectYamlLoads({'literal': 'some\ntext\n', 'folded': 'some text\n'}, '''
+ literal: |
+ some
+ text
+ folded: >
+ some
+ text
+ ''');
+ });
+
+ test('[Example 5.8]', () {
+ expectYamlLoads({'single': 'text', 'double': 'text'}, '''
+ single: 'text'
+ double: "text"
+ ''');
+ });
+
+ test('[Example 5.9]', () {
+ expectYamlLoads('text', '''
+ %YAML 1.2
+ --- text''');
+ });
+
+ test('[Example 5.10]', () {
+ expectYamlFails('commercial-at: @text');
+ expectYamlFails('commercial-at: `text');
+ });
+ });
+
+ group('5.4: Line Break Characters', () {
+ group('include', () {
+ test('\\n', () => expectYamlLoads([1, 2], indentLiteral('- 1\n- 2')));
+ test('\\r', () => expectYamlLoads([1, 2], '- 1\r- 2'));
+ });
+
+ group('do not include', () {
+ test('form feed', () => expectYamlFails('- 1\x0C- 2'));
+ test('NEL', () => expectYamlLoads(['1\x85- 2'], '- 1\x85- 2'));
+ test('0x2028', () => expectYamlLoads(['1\u2028- 2'], '- 1\u2028- 2'));
+ test('0x2029', () => expectYamlLoads(['1\u2029- 2'], '- 1\u2029- 2'));
+ });
+
+ group('in a scalar context must be normalized', () {
+ test(
+ 'from \\r to \\n',
+ () => expectYamlLoads(
+ ['foo\nbar'], indentLiteral('- |\n foo\r bar')));
+ test(
+ 'from \\r\\n to \\n',
+ () => expectYamlLoads(
+ ['foo\nbar'], indentLiteral('- |\n foo\r\n bar')));
+ });
+
+ test('[Example 5.11]', () {
+ expectYamlLoads(cleanUpLiteral('''
+ Line break (no glyph)
+ Line break (glyphed)'''), '''
+ |
+ Line break (no glyph)
+ Line break (glyphed)''');
+ });
+ });
+
+ group('5.5: White Space Characters', () {
+ test('[Example 5.12]', () {
+ expectYamlLoads({
+ 'quoted': 'Quoted \t',
+ 'block': 'void main() {\n\tprintf("Hello, world!\\n");\n}\n'
+ }, '''
+ # Tabs and spaces
+ quoted: "Quoted \t"
+ block:\t|
+ void main() {
+ \tprintf("Hello, world!\\n");
+ }
+ ''');
+ });
+ });
+
+ group('5.7: Escaped Characters', () {
+ test('[Example 5.13]', () {
+ expectYamlLoads(
+ 'Fun with \x5C '
+ '\x22 \x07 \x08 \x1B \x0C '
+ '\x0A \x0D \x09 \x0B \x00 '
+ '\x20 \xA0 \x85 \u2028 \u2029 '
+ 'A A A',
+ '''
+ "Fun with \\\\
+ \\" \\a \\b \\e \\f \\
+ \\n \\r \\t \\v \\0 \\
+ \\ \\_ \\N \\L \\P \\
+ \\x41 \\u0041 \\U00000041"''');
+ });
+
+ test('[Example 5.14]', () {
+ expectYamlFails('Bad escape: "\\c"');
+ expectYamlFails('Bad escape: "\\xq-"');
+ });
+ });
+
+ // Chapter 6: Basic Structures
+ group('6.1: Indentation Spaces', () {
+ test('may not include TAB characters', () {
+ expectYamlFails('''
+ -
+ \t- foo
+ \t- bar''');
+ });
+
+ test('must be the same for all sibling nodes', () {
+ expectYamlFails('''
+ -
+ - foo
+ - bar''');
+ });
+
+ test('may be different for the children of sibling nodes', () {
+ expectYamlLoads([
+ ['foo'],
+ ['bar']
+ ], '''
+ -
+ - foo
+ -
+ - bar''');
+ });
+
+ test('[Example 6.1]', () {
+ expectYamlLoads({
+ 'Not indented': {
+ 'By one space': 'By four\n spaces\n',
+ 'Flow style': ['By two', 'Also by two', 'Still by two']
+ }
+ }, '''
+ # Leading comment line spaces are
+ # neither content nor indentation.
+
+ Not indented:
+ By one space: |
+ By four
+ spaces
+ Flow style: [ # Leading spaces
+ By two, # in flow style
+ Also by two, # are neither
+ \tStill by two # content nor
+ ] # indentation.''');
+ });
+
+ test('[Example 6.2]', () {
+ expectYamlLoads({
+ 'a': [
+ 'b',
+ ['c', 'd']
+ ]
+ }, '''
+ ? a
+ : -\tb
+ - -\tc
+ - d''');
+ });
+ });
+
+ group('6.2: Separation Spaces', () {
+ test('[Example 6.3]', () {
+ expectYamlLoads([
+ {'foo': 'bar'},
+ ['baz', 'baz']
+ ], '''
+ - foo:\t bar
+ - - baz
+ -\tbaz''');
+ });
+ });
+
+ group('6.3: Line Prefixes', () {
+ test('[Example 6.4]', () {
+ expectYamlLoads({
+ 'plain': 'text lines',
+ 'quoted': 'text lines',
+ 'block': 'text\n \tlines\n'
+ }, '''
+ plain: text
+ lines
+ quoted: "text
+ \tlines"
+ block: |
+ text
+ \tlines
+ ''');
+ });
+ });
+
+ group('6.4: Empty Lines', () {
+ test('[Example 6.5]', () {
+ expectYamlLoads({
+ 'Folding': 'Empty line\nas a line feed',
+ 'Chomping': 'Clipped empty lines\n',
+ }, '''
+ Folding:
+ "Empty line
+ \t
+ as a line feed"
+ Chomping: |
+ Clipped empty lines
+ ''');
+ });
+ });
+
+ group('6.5: Line Folding', () {
+ test('[Example 6.6]', () {
+ expectYamlLoads('trimmed\n\n\nas space', '''
+ >-
+ trimmed
+
+
+
+ as
+ space
+ ''');
+ });
+
+ test('[Example 6.7]', () {
+ expectYamlLoads('foo \n\n\t bar\n\nbaz\n', '''
+ >
+ foo
+
+ \t bar
+
+ baz
+ ''');
+ });
+
+ test('[Example 6.8]', () {
+ expectYamlLoads(' foo\nbar\nbaz ', '''
+ "
+ foo
+
+ \t bar
+
+ baz
+ "''');
+ });
+ });
+
+ group('6.6: Comments', () {
+ test('must be separated from other tokens by white space characters', () {
+ expectYamlLoads('foo#bar', 'foo#bar');
+ expectYamlLoads('foo:#bar', 'foo:#bar');
+ expectYamlLoads('-#bar', '-#bar');
+ });
+
+ test('[Example 6.9]', () {
+ expectYamlLoads({'key': 'value'}, '''
+ key: # Comment
+ value''');
+ });
+
+ group('outside of scalar content', () {
+ test('may appear on a line of their own', () {
+ expectYamlLoads([1, 2], '''
+ - 1
+ # Comment
+ - 2''');
+ });
+
+ test('are independent of indentation level', () {
+ expectYamlLoads([
+ [1, 2]
+ ], '''
+ -
+ - 1
+ # Comment
+ - 2''');
+ });
+
+ test('include lines containing only white space characters', () {
+ expectYamlLoads([1, 2], '''
+ - 1
+ \t
+ - 2''');
+ });
+ });
+
+ group('within scalar content', () {
+ test('may not appear on a line of their own', () {
+ expectYamlLoads(['foo\n# not comment\nbar\n'], '''
+ - |
+ foo
+ # not comment
+ bar
+ ''');
+ });
+
+ test("don't include lines containing only white space characters", () {
+ expectYamlLoads(['foo\n \t \nbar\n'], '''
+ - |
+ foo
+ \t
+ bar
+ ''');
+ });
+ });
+
+ test('[Example 6.10]', () {
+ expectYamlLoads(null, '''
+ # Comment
+
+ ''');
+ });
+
+ test('[Example 6.11]', () {
+ expectYamlLoads({'key': 'value'}, '''
+ key: # Comment
+ # lines
+ value
+ ''');
+ });
+
+ group('ending a block scalar header', () {
+ test('may not be followed by additional comment lines', () {
+ expectYamlLoads(['# not comment\nfoo\n'], '''
+ - | # comment
+ # not comment
+ foo
+ ''');
+ });
+ });
+ });
+
+ group('6.7: Separation Lines', () {
+ test('may not be used within implicit keys', () {
+ expectYamlFails('''
+ [1,
+ 2]: 3''');
+ });
+
+ test('[Example 6.12]', () {
+ var doc = deepEqualsMap();
+ doc[{'first': 'Sammy', 'last': 'Sosa'}] = {'hr': 65, 'avg': 0.278};
+ expectYamlLoads(doc, '''
+ { first: Sammy, last: Sosa }:
+ # Statistics:
+ hr: # Home runs
+ 65
+ avg: # Average
+ 0.278''');
+ });
+ });
+
+ group('6.8: Directives', () {
+ // TODO(nweiz): assert that this produces a warning
+ test('[Example 6.13]', () {
+ expectYamlLoads('foo', '''
+ %FOO bar baz # Should be ignored
+ # with a warning.
+ --- "foo"''');
+ });
+
+ // TODO(nweiz): assert that this produces a warning.
+ test('[Example 6.14]', () {
+ expectYamlLoads('foo', '''
+ %YAML 1.3 # Attempt parsing
+ # with a warning
+ ---
+ "foo"''');
+ });
+
+ test('[Example 6.15]', () {
+ expectYamlFails('''
+ %YAML 1.2
+ %YAML 1.1
+ foo''');
+ });
+
+ test('[Example 6.16]', () {
+ expectYamlLoads('foo', '''
+ %TAG !yaml! tag:yaml.org,2002:
+ ---
+ !yaml!str "foo"''');
+ });
+
+ test('[Example 6.17]', () {
+ expectYamlFails('''
+ %TAG ! !foo
+ %TAG ! !foo
+ bar''');
+ });
+
+ // Examples 6.18 through 6.22 test custom tag URIs, which this
+ // implementation currently doesn't plan to support.
+ });
+
+ group('6.9: Node Properties', () {
+ test('may be specified in any order', () {
+ expectYamlLoads(['foo', 'bar'], '''
+ - !!str &a1 foo
+ - &a2 !!str bar''');
+ });
+
+ test('[Example 6.23]', () {
+ expectYamlLoads({'foo': 'bar', 'baz': 'foo'}, '''
+ !!str &a1 "foo":
+ !!str bar
+ &a2 baz : *a1''');
+ });
+
+ // Example 6.24 tests custom tag URIs, which this implementation currently
+ // doesn't plan to support.
+
+ test('[Example 6.25]', () {
+ expectYamlFails('- !<!> foo');
+ expectYamlFails('- !<\$:?> foo');
+ });
+
+ // Examples 6.26 and 6.27 test custom tag URIs, which this implementation
+ // currently doesn't plan to support.
+
+ test('[Example 6.28]', () {
+ expectYamlLoads(['12', 12, '12'], '''
+ # Assuming conventional resolution:
+ - "12"
+ - 12
+ - ! 12''');
+ });
+
+ test('[Example 6.29]', () {
+ expectYamlLoads(
+ {'First occurrence': 'Value', 'Second occurrence': 'Value'}, '''
+ First occurrence: &anchor Value
+ Second occurrence: *anchor''');
+ });
+ });
+
+ // Chapter 7: Flow Styles
+ group('7.1: Alias Nodes', () {
+ test("must not use an anchor that doesn't previously occur", () {
+ expectYamlFails('''
+ - *anchor
+ - &anchor foo''');
+ });
+
+ test("don't have to exist for a given anchor node", () {
+ expectYamlLoads(['foo'], '- &anchor foo');
+ });
+
+ group('must not specify', () {
+ test('tag properties', () => expectYamlFails('''
+ - &anchor foo
+ - !str *anchor'''));
+
+ test('anchor properties', () => expectYamlFails('''
+ - &anchor foo
+ - &anchor2 *anchor'''));
+
+ test('content', () => expectYamlFails('''
+ - &anchor foo
+ - *anchor bar'''));
+ });
+
+ test('must preserve structural equality', () {
+ var doc = loadYaml(cleanUpLiteral('''
+ anchor: &anchor [a, b, c]
+ alias: *anchor'''));
+ var anchorList = doc['anchor'];
+ var aliasList = doc['alias'];
+ expect(anchorList, same(aliasList));
+
+ doc = loadYaml(cleanUpLiteral('''
+ ? &anchor [a, b, c]
+ : ? *anchor
+ : bar'''));
+ anchorList = doc.keys.first;
+ aliasList = doc[['a', 'b', 'c']].keys.first;
+ expect(anchorList, same(aliasList));
+ });
+
+ test('[Example 7.1]', () {
+ expectYamlLoads({
+ 'First occurrence': 'Foo',
+ 'Second occurrence': 'Foo',
+ 'Override anchor': 'Bar',
+ 'Reuse anchor': 'Bar',
+ }, '''
+ First occurrence: &anchor Foo
+ Second occurrence: *anchor
+ Override anchor: &anchor Bar
+ Reuse anchor: *anchor''');
+ });
+ });
+
+ group('7.2: Empty Nodes', () {
+ test('[Example 7.2]', () {
+ expectYamlLoads({'foo': '', '': 'bar'}, '''
+ {
+ foo : !!str,
+ !!str : bar,
+ }''');
+ });
+
+ test('[Example 7.3]', () {
+ var doc = deepEqualsMap({'foo': null});
+ doc[null] = 'bar';
+ expectYamlLoads(doc, '''
+ {
+ ? foo :,
+ : bar,
+ }''');
+ });
+ });
+
+ group('7.3: Flow Scalar Styles', () {
+ test('[Example 7.4]', () {
+ expectYamlLoads({
+ 'implicit block key': [
+ {'implicit flow key': 'value'}
+ ]
+ }, '''
+ "implicit block key" : [
+ "implicit flow key" : value,
+ ]''');
+ });
+
+ test('[Example 7.5]', () {
+ expectYamlLoads(
+ 'folded to a space,\nto a line feed, or \t \tnon-content', '''
+ "folded
+ to a space,\t
+
+ to a line feed, or \t\\
+ \\ \tnon-content"''');
+ });
+
+ test('[Example 7.6]', () {
+ expectYamlLoads(' 1st non-empty\n2nd non-empty 3rd non-empty ', '''
+ " 1st non-empty
+
+ 2nd non-empty
+ \t3rd non-empty "''');
+ });
+
+ test('[Example 7.7]', () {
+ expectYamlLoads("here's to \"quotes\"", "'here''s to \"quotes\"'");
+ });
+
+ test('[Example 7.8]', () {
+ expectYamlLoads({
+ 'implicit block key': [
+ {'implicit flow key': 'value'}
+ ]
+ }, """
+ 'implicit block key' : [
+ 'implicit flow key' : value,
+ ]""");
+ });
+
+ test('[Example 7.9]', () {
+ expectYamlLoads(' 1st non-empty\n2nd non-empty 3rd non-empty ', """
+ ' 1st non-empty
+
+ 2nd non-empty
+ \t3rd non-empty '""");
+ });
+
+ test('[Example 7.10]', () {
+ expectYamlLoads([
+ '::vector',
+ ': - ()',
+ 'Up, up, and away!',
+ -123,
+ 'http://example.com/foo#bar',
+ [
+ '::vector',
+ ': - ()',
+ 'Up, up, and away!',
+ -123,
+ 'http://example.com/foo#bar'
+ ]
+ ], '''
+ # Outside flow collection:
+ - ::vector
+ - ": - ()"
+ - Up, up, and away!
+ - -123
+ - http://example.com/foo#bar
+ # Inside flow collection:
+ - [ ::vector,
+ ": - ()",
+ "Up, up, and away!",
+ -123,
+ http://example.com/foo#bar ]''');
+ });
+
+ test('[Example 7.11]', () {
+ expectYamlLoads({
+ 'implicit block key': [
+ {'implicit flow key': 'value'}
+ ]
+ }, '''
+ implicit block key : [
+ implicit flow key : value,
+ ]''');
+ });
+
+ test('[Example 7.12]', () {
+ expectYamlLoads('1st non-empty\n2nd non-empty 3rd non-empty', '''
+ 1st non-empty
+
+ 2nd non-empty
+ \t3rd non-empty''');
+ });
+ });
+
+ group('7.4: Flow Collection Styles', () {
+ test('[Example 7.13]', () {
+ expectYamlLoads([
+ ['one', 'two'],
+ ['three', 'four']
+ ], '''
+ - [ one, two, ]
+ - [three ,four]''');
+ });
+
+ test('[Example 7.14]', () {
+ expectYamlLoads([
+ 'double quoted',
+ 'single quoted',
+ 'plain text',
+ ['nested'],
+ {'single': 'pair'}
+ ], """
+ [
+ "double
+ quoted", 'single
+ quoted',
+ plain
+ text, [ nested ],
+ single: pair,
+ ]""");
+ });
+
+ test('[Example 7.15]', () {
+ expectYamlLoads([
+ {'one': 'two', 'three': 'four'},
+ {'five': 'six', 'seven': 'eight'},
+ ], '''
+ - { one : two , three: four , }
+ - {five: six,seven : eight}''');
+ });
+
+ test('[Example 7.16]', () {
+ var doc = deepEqualsMap({'explicit': 'entry', 'implicit': 'entry'});
+ doc[null] = null;
+ expectYamlLoads(doc, '''
+ {
+ ? explicit: entry,
+ implicit: entry,
+ ?
+ }''');
+ });
+
+ test('[Example 7.17]', () {
+ var doc = deepEqualsMap({
+ 'unquoted': 'separate',
+ 'http://foo.com': null,
+ 'omitted value': null
+ });
+ doc[null] = 'omitted key';
+ expectYamlLoads(doc, '''
+ {
+ unquoted : "separate",
+ http://foo.com,
+ omitted value:,
+ : omitted key,
+ }''');
+ });
+
+ test('[Example 7.18]', () {
+ expectYamlLoads(
+ {'adjacent': 'value', 'readable': 'value', 'empty': null}, '''
+ {
+ "adjacent":value,
+ "readable": value,
+ "empty":
+ }''');
+ });
+
+ test('[Example 7.19]', () {
+ expectYamlLoads([
+ {'foo': 'bar'}
+ ], '''
+ [
+ foo: bar
+ ]''');
+ });
+
+ test('[Example 7.20]', () {
+ expectYamlLoads([
+ {'foo bar': 'baz'}
+ ], '''
+ [
+ ? foo
+ bar : baz
+ ]''');
+ });
+
+ test('[Example 7.21]', () {
+ var el1 = deepEqualsMap();
+ el1[null] = 'empty key entry';
+
+ var el2 = deepEqualsMap();
+ el2[{'JSON': 'like'}] = 'adjacent';
+
+ expectYamlLoads([
+ [
+ {'YAML': 'separate'}
+ ],
+ [el1],
+ [el2]
+ ], '''
+ - [ YAML : separate ]
+ - [ : empty key entry ]
+ - [ {JSON: like}:adjacent ]''');
+ });
+
+ // TODO(nweiz): enable this when we throw an error for long or multiline
+ // keys.
+ // test('[Example 7.22]', () {
+ // expectYamlFails(
+ // """
+ // [ foo
+ // bar: invalid ]""");
+ //
+ // var dotList = new List.filled(1024, ' ');
+ // var dots = dotList.join();
+ // expectYamlFails('[ "foo...$dots...bar": invalid ]');
+ // });
+ });
+
+ group('7.5: Flow Nodes', () {
+ test('[Example 7.23]', () {
+ expectYamlLoads([
+ ['a', 'b'],
+ {'a': 'b'},
+ 'a',
+ 'b',
+ 'c'
+ ], '''
+ - [ a, b ]
+ - { a: b }
+ - 'a'
+ - 'b'
+ - c''');
+ });
+
+ test('[Example 7.24]', () {
+ expectYamlLoads(['a', 'b', 'c', 'c', ''], '''
+ - !!str "a"
+ - 'b'
+ - &anchor "c"
+ - *anchor
+ - !!str''');
+ });
+ });
+
+ // Chapter 8: Block Styles
+ group('8.1: Block Scalar Styles', () {
+ test('[Example 8.1]', () {
+ expectYamlLoads(['literal\n', ' folded\n', 'keep\n\n', ' strip'], '''
+ - | # Empty header
+ literal
+ - >1 # Indentation indicator
+ folded
+ - |+ # Chomping indicator
+ keep
+
+ - >1- # Both indicators
+ strip''');
+ });
+
+ test('[Example 8.2]', () {
+ // Note: in the spec, the fourth element in this array is listed as
+ // "\t detected\n", not "\t\ndetected\n". However, I'm reasonably
+ // confident that "\t\ndetected\n" is correct when parsed according to the
+ // rest of the spec.
+ expectYamlLoads(
+ ['detected\n', '\n\n# detected\n', ' explicit\n', '\t\ndetected\n'],
+ '''
+ - |
+ detected
+ - >
+
+
+ # detected
+ - |1
+ explicit
+ - >
+ \t
+ detected
+ ''');
+ });
+
+ test('[Example 8.3]', () {
+ expectYamlFails('''
+ - |
+
+ text''');
+
+ expectYamlFails('''
+ - >
+ text
+ text''');
+
+ expectYamlFails('''
+ - |2
+ text''');
+ });
+
+ test('[Example 8.4]', () {
+ expectYamlLoads({'strip': 'text', 'clip': 'text\n', 'keep': 'text\n'}, '''
+ strip: |-
+ text
+ clip: |
+ text
+ keep: |+
+ text
+ ''');
+ });
+
+ test('[Example 8.5]', () {
+ // This example in the spec only includes a single newline in the "keep"
+ // value, but as far as I can tell that's not how it's supposed to be
+ // parsed according to the rest of the spec.
+ expectYamlLoads(
+ {'strip': '# text', 'clip': '# text\n', 'keep': '# text\n\n'}, '''
+ # Strip
+ # Comments:
+ strip: |-
+ # text
+
+ # Clip
+ # comments:
+
+ clip: |
+ # text
+
+ # Keep
+ # comments:
+
+ keep: |+
+ # text
+
+ # Trail
+ # comments.
+ ''');
+ });
+
+ test('[Example 8.6]', () {
+ expectYamlLoads({'strip': '', 'clip': '', 'keep': '\n'}, '''
+ strip: >-
+
+ clip: >
+
+ keep: |+
+
+ ''');
+ });
+
+ test('[Example 8.7]', () {
+ expectYamlLoads('literal\n\ttext\n', '''
+ |
+ literal
+ \ttext
+ ''');
+ });
+
+ test('[Example 8.8]', () {
+ expectYamlLoads('\n\nliteral\n \n\ntext\n', '''
+ |
+
+
+ literal
+
+
+ text
+
+ # Comment''');
+ });
+
+ test('[Example 8.9]', () {
+ expectYamlLoads('folded text\n', '''
+ >
+ folded
+ text
+ ''');
+ });
+
+ test('[Example 8.10]', () {
+ expectYamlLoads(cleanUpLiteral('''
+
+ folded line
+ next line
+ * bullet
+
+ * list
+ * lines
+
+ last line
+ '''), '''
+ >
+
+ folded
+ line
+
+ next
+ line
+ * bullet
+
+ * list
+ * lines
+
+ last
+ line
+
+ # Comment''');
+ });
+
+ // Examples 8.11 through 8.13 are duplicates of 8.10.
+ });
+
+ group('8.2: Block Collection Styles', () {
+ test('[Example 8.14]', () {
+ expectYamlLoads({
+ 'block sequence': [
+ 'one',
+ {'two': 'three'}
+ ]
+ }, '''
+ block sequence:
+ - one
+ - two : three''');
+ });
+
+ test('[Example 8.15]', () {
+ expectYamlLoads([
+ null,
+ 'block node\n',
+ ['one', 'two'],
+ {'one': 'two'}
+ ], '''
+ - # Empty
+ - |
+ block node
+ - - one # Compact
+ - two # sequence
+ - one: two # Compact mapping''');
+ });
+
+ test('[Example 8.16]', () {
+ expectYamlLoads({
+ 'block mapping': {'key': 'value'}
+ }, '''
+ block mapping:
+ key: value''');
+ });
+
+ test('[Example 8.17]', () {
+ expectYamlLoads({
+ 'explicit key': null,
+ 'block key\n': ['one', 'two']
+ }, '''
+ ? explicit key # Empty value
+ ? |
+ block key
+ : - one # Explicit compact
+ - two # block value''');
+ });
+
+ test('[Example 8.18]', () {
+ var doc = deepEqualsMap({
+ 'plain key': 'in-line value',
+ 'quoted key': ['entry']
+ });
+ doc[null] = null;
+ expectYamlLoads(doc, '''
+ plain key: in-line value
+ : # Both empty
+ "quoted key":
+ - entry''');
+ });
+
+ test('[Example 8.19]', () {
+ var el = deepEqualsMap();
+ el[{'earth': 'blue'}] = {'moon': 'white'};
+ expectYamlLoads([
+ {'sun': 'yellow'},
+ el
+ ], '''
+ - sun: yellow
+ - ? earth: blue
+ : moon: white''');
+ });
+
+ test('[Example 8.20]', () {
+ expectYamlLoads([
+ 'flow in block',
+ 'Block scalar\n',
+ {'foo': 'bar'}
+ ], '''
+ -
+ "flow in block"
+ - >
+ Block scalar
+ - !!map # Block collection
+ foo : bar''');
+ });
+
+ test('[Example 8.21]', () {
+ // The spec doesn't include a newline after "value" in the parsed map, but
+ // the block scalar is clipped so it should be retained.
+ expectYamlLoads({'literal': 'value\n', 'folded': 'value'}, '''
+ literal: |2
+ value
+ folded:
+ !!str
+ >1
+ value''');
+ });
+
+ test('[Example 8.22]', () {
+ expectYamlLoads({
+ 'sequence': [
+ 'entry',
+ ['nested']
+ ],
+ 'mapping': {'foo': 'bar'}
+ }, '''
+ sequence: !!seq
+ - entry
+ - !!seq
+ - nested
+ mapping: !!map
+ foo: bar''');
+ });
+ });
+
+ // Chapter 9: YAML Character Stream
+ group('9.1: Documents', () {
+ // Example 9.1 tests the use of a BOM, which this implementation currently
+ // doesn't plan to support.
+
+ test('[Example 9.2]', () {
+ expectYamlLoads('Document', '''
+ %YAML 1.2
+ ---
+ Document
+ ... # Suffix''');
+ });
+
+ test('[Example 9.3]', () {
+ // The spec example indicates that the comment after "%!PS-Adobe-2.0"
+ // should be stripped, which would imply that that line is not part of the
+ // literal defined by the "|". The rest of the spec is ambiguous on this
+ // point; the allowable indentation for non-indented literal content is
+ // not clearly explained. However, if both the "|" and the text were
+ // indented the same amount, the text would be part of the literal, which
+ // implies that the spec's parse of this document is incorrect.
+ expectYamlStreamLoads(
+ ['Bare document', '%!PS-Adobe-2.0 # Not the first line\n'], '''
+ Bare
+ document
+ ...
+ # No document
+ ...
+ |
+ %!PS-Adobe-2.0 # Not the first line
+ ''');
+ });
+
+ test('[Example 9.4]', () {
+ expectYamlStreamLoads([
+ {'matches %': 20},
+ null
+ ], '''
+ ---
+ { matches
+ % : 20 }
+ ...
+ ---
+ # Empty
+ ...''');
+ });
+
+ test('[Example 9.5]', () {
+ // The spec doesn't have a space between the second
+ // "YAML" and "1.2", but this seems to be a typo.
+ expectYamlStreamLoads(['%!PS-Adobe-2.0\n', null], '''
+ %YAML 1.2
+ --- |
+ %!PS-Adobe-2.0
+ ...
+ %YAML 1.2
+ ---
+ # Empty
+ ...''');
+ });
+
+ test('[Example 9.6]', () {
+ expectYamlStreamLoads([
+ 'Document',
+ null,
+ {'matches %': 20}
+ ], '''
+ Document
+ ---
+ # Empty
+ ...
+ %YAML 1.2
+ ---
+ matches %: 20''');
+ });
+ });
+
+ // Chapter 10: Recommended Schemas
+ group('10.1: Failsafe Schema', () {
+ test('[Example 10.1]', () {
+ expectYamlLoads({
+ 'Block style': {
+ 'Clark': 'Evans',
+ 'Ingy': 'döt Net',
+ 'Oren': 'Ben-Kiki'
+ },
+ 'Flow style': {'Clark': 'Evans', 'Ingy': 'döt Net', 'Oren': 'Ben-Kiki'}
+ }, '''
+ Block style: !!map
+ Clark : Evans
+ Ingy : döt Net
+ Oren : Ben-Kiki
+
+ Flow style: !!map { Clark: Evans, Ingy: döt Net, Oren: Ben-Kiki }''');
+ });
+
+ test('[Example 10.2]', () {
+ expectYamlLoads({
+ 'Block style': ['Clark Evans', 'Ingy döt Net', 'Oren Ben-Kiki'],
+ 'Flow style': ['Clark Evans', 'Ingy döt Net', 'Oren Ben-Kiki']
+ }, '''
+ Block style: !!seq
+ - Clark Evans
+ - Ingy döt Net
+ - Oren Ben-Kiki
+
+ Flow style: !!seq [ Clark Evans, Ingy döt Net, Oren Ben-Kiki ]''');
+ });
+
+ test('[Example 10.3]', () {
+ expectYamlLoads({
+ 'Block style': 'String: just a theory.',
+ 'Flow style': 'String: just a theory.'
+ }, '''
+ Block style: !!str |-
+ String: just a theory.
+
+ Flow style: !!str "String: just a theory."''');
+ });
+ });
+
+ group('10.2: JSON Schema', () {
+ test('[Example 10.4]', () {
+ var doc = deepEqualsMap({'key with null value': null});
+ doc[null] = 'value for null key';
+ expectYamlStreamLoads([doc], '''
+ !!null null: value for null key
+ key with null value: !!null null''');
+ });
+
+ test('[Example 10.5]', () {
+ expectYamlStreamLoads([
+ {'YAML is a superset of JSON': true, 'Pluto is a planet': false}
+ ], '''
+ YAML is a superset of JSON: !!bool true
+ Pluto is a planet: !!bool false''');
+ });
+
+ test('[Example 10.6]', () {
+ expectYamlStreamLoads([
+ {'negative': -12, 'zero': 0, 'positive': 34}
+ ], '''
+ negative: !!int -12
+ zero: !!int 0
+ positive: !!int 34''');
+ });
+
+ test('[Example 10.7]', () {
+ expectYamlStreamLoads([
+ {
+ 'negative': -1,
+ 'zero': 0,
+ 'positive': 23000,
+ 'infinity': infinity,
+ 'not a number': nan
+ }
+ ], '''
+ negative: !!float -1
+ zero: !!float 0
+ positive: !!float 2.3e4
+ infinity: !!float .inf
+ not a number: !!float .nan''');
+ }, skip: 'Fails for single digit float');
+
+ test('[Example 10.8]', () {
+ expectYamlStreamLoads([
+ {
+ 'A null': null,
+ 'Booleans': [true, false],
+ 'Integers': [0, -0, 3, -19],
+ 'Floats': [0, 0, 12000, -200000],
+ // Despite being invalid in the JSON schema, these values are valid in
+ // the core schema which this implementation supports.
+ 'Invalid': [true, null, 7, 0x3A, 12.3]
+ }
+ ], '''
+ A null: null
+ Booleans: [ true, false ]
+ Integers: [ 0, -0, 3, -19 ]
+ Floats: [ 0., -0.0, 12e03, -2E+05 ]
+ Invalid: [ True, Null, 0o7, 0x3A, +12.3 ]''');
+ });
+ });
+
+ group('10.3: Core Schema', () {
+ test('[Example 10.9]', () {
+ expectYamlLoads({
+ 'A null': null,
+ 'Also a null': null,
+ 'Not a null': '',
+ 'Booleans': [true, true, false, false],
+ 'Integers': [0, 7, 0x3A, -19],
+ 'Floats': [0, 0, 0.5, 12000, -200000],
+ 'Also floats': [infinity, -infinity, infinity, nan]
+ }, '''
+ A null: null
+ Also a null: # Empty
+ Not a null: ""
+ Booleans: [ true, True, false, FALSE ]
+ Integers: [ 0, 0o7, 0x3A, -19 ]
+ Floats: [ 0., -0.0, .5, +12e03, -2E+05 ]
+ Also floats: [ .inf, -.Inf, +.INF, .NAN ]''');
+ });
+ });
+
+ test('preserves key order', () {
+ const keys = ['a', 'b', 'c', 'd', 'e', 'f'];
+ var sanityCheckCount = 0;
+ for (var permutation in _generatePermutations(keys)) {
+ final yaml = permutation.map((key) => '$key: value').join('\n');
+ expect(loadYaml(yaml).keys.toList(), permutation);
+ sanityCheckCount++;
+ }
+ final expectedPermutationCount =
+ List.generate(keys.length, (i) => i + 1).reduce((n, i) => n * i);
+ expect(sanityCheckCount, expectedPermutationCount);
+ });
+}
+
+Iterable<List<String>> _generatePermutations(List<String> keys) sync* {
+ if (keys.length <= 1) {
+ yield keys;
+ return;
+ }
+ for (var i = 0; i < keys.length; i++) {
+ final first = keys[i];
+ final rest = <String>[...keys.sublist(0, i), ...keys.sublist(i + 1)];
+ for (var subPermutation in _generatePermutations(rest)) {
+ yield <String>[first, ...subPermutation];
+ }
+ }
+}
diff --git a/pkgs/yaml_edit/.gitignore b/pkgs/yaml_edit/.gitignore
new file mode 100644
index 0000000..7886c3d
--- /dev/null
+++ b/pkgs/yaml_edit/.gitignore
@@ -0,0 +1,3 @@
+/.dart_tool/
+/.packages
+/pubspec.lock
diff --git a/pkgs/yaml_edit/CHANGELOG.md b/pkgs/yaml_edit/CHANGELOG.md
new file mode 100644
index 0000000..9342e9f
--- /dev/null
+++ b/pkgs/yaml_edit/CHANGELOG.md
@@ -0,0 +1,95 @@
+## 2.2.2
+
+- Suppress warnings previously printed to `stdout` when parsing YAML internally.
+- Fix error thrown when inserting duplicate keys to different maps in the same
+ list.
+ ([#69](https://github.com/dart-lang/yaml_edit/issues/69))
+
+- Fix error thrown when inserting in nested list using `spliceList` method
+ ([#83](https://github.com/dart-lang/yaml_edit/issues/83))
+
+- Fix error thrown when string has spaces when applying `ScalarStyle.FOLDED`.
+ ([#41](https://github.com/dart-lang/yaml_edit/issues/41)). Resolves
+ ([[#86](https://github.com/dart-lang/yaml_edit/issues/86)]).
+
+- Require Dart 3.1
+
+- Move to `dart-lang/tools` monorepo.
+
+## 2.2.1
+
+- Require Dart 3.0
+- Fix removal of last key in blockmap when key has no value
+ ([#55](https://github.com/dart-lang/yaml_edit/issues/55)).
+
+## 2.2.0
+
+- Fix inconsistent line endings when inserting maps into a document using `\r\n`.
+ ([#65](https://github.com/dart-lang/yaml_edit/issues/65))
+
+- `AliasError` is changed to `AliasException` and exposed in the public API.
+
+ All node-mutating methods on `YamlEditor`, i.e. `update()`, `appendToList()`,
+ `prependToList()`, `insertIntoList()`, `spliceList()`, `remove()` will now
+ throw an exception instead of an error when encountering an alias on the path
+ to modify.
+
+ This allows catching and handling when this is happening.
+
+## 2.1.1
+
+- Require Dart 2.19
+
+## 2.1.0
+
+- **Breaking** `wrapAsYamlNode(value, collectionStyle, scalarStyle)` will apply
+ `collectionStyle` and `scalarStyle` recursively when wrapping a children of
+ `Map` and `List`.
+ While this may change the style of the YAML documents written by applications
+ that rely on the old behavior, such YAML documents should still be valid.
+ Hence, we hope it is reasonable to make this change in a minor release.
+- Fix for cases that can't be encoded correctly with
+ `scalarStyle: ScalarStyle.SINGLE_QUOTED`.
+- Fix YamlEditor `appendToList` and `insertIntoList` functions inserts new item into next yaml item
+ rather than at end of list.
+ ([#23](https://github.com/dart-lang/yaml_edit/issues/23))
+
+## 2.0.3
+
+- Updated the value of the pubspec `repository` field.
+
+## 2.0.2
+
+- Fix trailing whitespace after adding new key with block-value to map
+ ([#15](https://github.com/dart-lang/yaml_edit/issues/15)).
+- Updated `repository` and other meta-data in `pubspec.yaml`.
+
+## 2.0.1
+
+- License changed to BSD, as this package is now maintained by the Dart team.
+- Fixed minor lints.
+
+## 2.0.0
+
+- Migrated to null-safety.
+- API will no-longer return `null` in-place of a `YamlNode`, instead a
+ `YamlNode` with `YamlNode.value == null` should be used. These are easily
+ created with `wrapAsYamlNode(null)`.
+
+## 1.0.3
+
+- Fixed bug in adding an empty map as a map value.
+
+## 1.0.2
+
+- Throws an error if the final YAML after edit is not parsable.
+- Fixed bug in adding to empty map values, when it is followed by other content.
+
+## 1.0.1
+
+- Updated behavior surrounding list and map removal.
+- Fixed bug in dealing with empty values.
+
+## 1.0.0
+
+- Initial release.
diff --git a/pkgs/yaml_edit/LICENSE b/pkgs/yaml_edit/LICENSE
new file mode 100644
index 0000000..413ed83
--- /dev/null
+++ b/pkgs/yaml_edit/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2020, 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.
diff --git a/pkgs/yaml_edit/README.md b/pkgs/yaml_edit/README.md
new file mode 100644
index 0000000..f10560b
--- /dev/null
+++ b/pkgs/yaml_edit/README.md
@@ -0,0 +1,61 @@
+[](https://github.com/dart-lang/yaml_edit/actions/workflows/test-package.yml)
+[](https://pub.dev/packages/yaml_edit)
+[](https://pub.dev/packages/yaml_edit/publisher)
+[](https://coveralls.io/github/dart-lang/yaml_edit)
+
+A library for [YAML](https://yaml.org) manipulation while preserving comments.
+
+## Usage
+
+A simple usage example:
+
+```dart
+import 'package:yaml_edit/yaml_edit.dart';
+
+void main() {
+ final yamlEditor = YamlEditor('{YAML: YAML}');
+ yamlEditor.update(['YAML'], "YAML Ain't Markup Language");
+ print(yamlEditor);
+ // Expected output:
+ // {YAML: YAML Ain't Markup Language}
+}
+```
+
+### Example: Converting JSON to YAML (block formatted)
+
+```dart
+void main() {
+ final jsonString = r'''
+{
+ "key": "value",
+ "list": [
+ "first",
+ "second",
+ "last entry in the list"
+ ],
+ "map": {
+ "multiline": "this is a fairly long string with\nline breaks..."
+ }
+}
+''';
+ final jsonValue = json.decode(jsonString);
+
+ // Convert jsonValue to YAML
+ final yamlEditor = YamlEditor('');
+ yamlEditor.update([], jsonValue);
+ print(yamlEditor.toString());
+}
+```
+
+## Testing
+
+Testing is done in two strategies: Unit testing (`/test/editor_test.dart`) and
+Golden testing (`/test/golden_test.dart`). More information on Golden testing
+and the input/output format can be found at `/test/testdata/README.md`.
+
+These tests are automatically run with `pub run test`.
+
+## Limitations
+
+1. Users are not allowed to define tags in the modifications.
+2. Map keys will always be added in the flow style.
diff --git a/pkgs/yaml_edit/analysis_options.yaml b/pkgs/yaml_edit/analysis_options.yaml
new file mode 100644
index 0000000..937e7fe
--- /dev/null
+++ b/pkgs/yaml_edit/analysis_options.yaml
@@ -0,0 +1,8 @@
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+ errors:
+ inference_failure_on_collection_literal: ignore
+ inference_failure_on_function_invocation: ignore
+ inference_failure_on_function_return_type: ignore
+ inference_failure_on_instance_creation: ignore
diff --git a/pkgs/yaml_edit/example/example.dart b/pkgs/yaml_edit/example/example.dart
new file mode 100644
index 0000000..d49c39b
--- /dev/null
+++ b/pkgs/yaml_edit/example/example.dart
@@ -0,0 +1,12 @@
+import 'package:yaml_edit/yaml_edit.dart';
+
+void main() {
+ final doc = YamlEditor('''
+- 0 # comment 0
+- 1 # comment 1
+- 2 # comment 2
+''');
+ doc.remove([1]);
+
+ print(doc);
+}
diff --git a/pkgs/yaml_edit/example/json2yaml.dart b/pkgs/yaml_edit/example/json2yaml.dart
new file mode 100644
index 0000000..d6204d3
--- /dev/null
+++ b/pkgs/yaml_edit/example/json2yaml.dart
@@ -0,0 +1,28 @@
+// Copyright (c) 2023, 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' show json;
+
+import 'package:yaml_edit/yaml_edit.dart';
+
+void main() {
+ final jsonString = r'''
+{
+ "key": "value",
+ "list": [
+ "first",
+ "second",
+ "last entry in the list"
+ ],
+ "map": {
+ "multiline": "this is a fairly long string with\nline breaks..."
+ }
+}
+''';
+ final jsonValue = json.decode(jsonString);
+
+ final yamlEditor = YamlEditor('');
+ yamlEditor.update([], jsonValue);
+ print(yamlEditor.toString());
+}
diff --git a/pkgs/yaml_edit/lib/src/editor.dart b/pkgs/yaml_edit/lib/src/editor.dart
new file mode 100644
index 0000000..54775cc
--- /dev/null
+++ b/pkgs/yaml_edit/lib/src/editor.dart
@@ -0,0 +1,634 @@
+// Copyright (c) 2020, 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:meta/meta.dart';
+import 'package:source_span/source_span.dart';
+import 'package:yaml/yaml.dart';
+
+import 'equality.dart';
+import 'errors.dart';
+import 'list_mutations.dart';
+import 'map_mutations.dart';
+import 'source_edit.dart';
+import 'strings.dart';
+import 'utils.dart';
+import 'wrap.dart';
+
+/// An interface for modifying [YAML][1] documents while preserving comments
+/// and whitespaces.
+///
+/// YAML parsing is supported by `package:yaml`, and modifications are performed
+/// as string operations. An error will be thrown if internal assertions fail -
+/// such a situation should be extremely rare, and should only occur with
+/// degenerate formatting.
+///
+/// Most modification methods require the user to pass in an `Iterable<Object>`
+/// path that holds the keys/indices to navigate to the element.
+///
+/// **Example:**
+/// ```yaml
+/// a: 1
+/// b: 2
+/// c:
+/// - 3
+/// - 4
+/// - {e: 5, f: [6, 7]}
+/// ```
+///
+/// To get to `7`, our path will be `['c', 2, 'f', 1]`. The path for the base
+/// object is the empty array `[]`. All modification methods will throw a
+/// [ArgumentError] if the path provided is invalid. Note also that that the
+/// order of elements in the path is important, and it should be arranged in
+/// order of calling, with the first element being the first key or index to be
+/// called.
+///
+/// In most modification methods, users are required to pass in a value to be
+/// used for updating the YAML tree. This value is only allowed to either be a
+/// valid scalar that is recognizable by YAML (i.e. `bool`, `String`, `List`,
+/// `Map`, `num`, `null`) or a [YamlNode]. Should the user want to specify
+/// the style to be applied to the value passed in, the user may wrap the value
+/// using [wrapAsYamlNode] while passing in the appropriate `scalarStyle` or
+/// `collectionStyle`. While we try to respect the style that is passed in,
+/// there will be instances where the formatting will not result in valid YAML,
+/// and as such we will fallback to a default formatting while preserving the
+/// content.
+///
+/// To dump the YAML after all the modifications have been completed, simply
+/// call [toString()].
+///
+/// [1]: https://yaml.org/
+@sealed
+class YamlEditor {
+ final List<SourceEdit> _edits = [];
+
+ /// List of [SourceEdit]s that have been applied to [_yaml] since the creation
+ /// of this instance, in chronological order. Intended to be compatible with
+ /// `package:analysis_server`.
+ ///
+ /// The [SourceEdit] objects can be serialized to JSON using the `toJSON`
+ /// function, deserialized using [SourceEdit.fromJson], and applied to a
+ /// string using the `apply` function. Multiple [SourceEdit]s can be applied
+ /// to a string using [SourceEdit.applyAll].
+ ///
+ /// For more information, refer to the [SourceEdit] class.
+ List<SourceEdit> get edits => [..._edits];
+
+ /// Current YAML string.
+ String _yaml;
+
+ /// Root node of YAML AST.
+ YamlNode _contents;
+
+ /// Stores the list of nodes in [_contents] that are connected by aliases.
+ ///
+ /// When a node is anchored with an alias and subsequently referenced,
+ /// the full content of the anchored node is thought to be copied in the
+ /// following references.
+ ///
+ /// **Example:**
+ /// ```dart
+ /// a: &SS Sammy Sosa
+ /// b: *SS
+ /// ```
+ ///
+ /// is equivalent to
+ ///
+ /// ```dart
+ /// a: Sammy Sosa
+ /// b: Sammy Sosa
+ /// ```
+ ///
+ /// As such, aliased nodes have to be treated with special caution when
+ /// any modification is taking place.
+ ///
+ /// See 7.1 Alias Nodes: https://yaml.org/spec/1.2/spec.html#id2786196
+ Set<YamlNode> _aliases = {};
+
+ /// Returns the current YAML string.
+ @override
+ String toString() => _yaml;
+
+ factory YamlEditor(String yaml) => YamlEditor._(yaml);
+
+ YamlEditor._(this._yaml) : _contents = loadYamlNode(_yaml) {
+ _initialize();
+ }
+
+ /// Traverses the YAML tree formed to detect alias nodes.
+ void _initialize() {
+ _aliases = {};
+
+ /// Performs a DFS on [_contents] to detect alias nodes.
+ final visited = <YamlNode>{};
+ void collectAliases(YamlNode node) {
+ if (visited.add(node)) {
+ if (node is YamlMap) {
+ node.nodes.forEach((key, value) {
+ collectAliases(key as YamlNode);
+ collectAliases(value);
+ });
+ } else if (node is YamlList) {
+ node.nodes.forEach(collectAliases);
+ }
+ } else {
+ _aliases.add(node);
+ }
+ }
+
+ collectAliases(_contents);
+ }
+
+ /// Parses the document to return [YamlNode] currently present at [path].
+ ///
+ /// If no [YamlNode]s exist at [path], the result of invoking the [orElse]
+ /// function is returned.
+ ///
+ /// If [orElse] is omitted, it defaults to throwing a [ArgumentError].
+ ///
+ /// To get a default value when [path] does not point to a value in the
+ /// [YamlNode]-tree, simply pass `orElse: () => ...`.
+ ///
+ /// **Example:** (using orElse)
+ /// ```dart
+ /// final myYamlEditor('{"key": "value"}');
+ /// final node = myYamlEditor.valueAt(
+ /// ['invalid', 'path'],
+ /// orElse: () => wrapAsYamlNode(null),
+ /// );
+ /// print(node.value); // null
+ /// ```
+ ///
+ /// **Example:** (common usage)
+ /// ```dart
+ /// final doc = YamlEditor('''
+ /// a: 1
+ /// b:
+ /// d: 4
+ /// e: [5, 6, 7]
+ /// c: 3
+ /// ''');
+ /// print(doc.parseAt(['b', 'e', 2])); // 7
+ /// ```
+ /// The value returned by [parseAt] is invalidated when the documented is
+ /// mutated, as illustrated below:
+ ///
+ /// **Example:** (old [parseAt] value is invalidated)
+ /// ```dart
+ /// final doc = YamlEditor("YAML: YAML Ain't Markup Language");
+ /// final node = doc.parseAt(['YAML']);
+ ///
+ /// print(node.value); // Expected output: "YAML Ain't Markup Language"
+ ///
+ /// doc.update(['YAML'], 'YAML');
+ ///
+ /// final newNode = doc.parseAt(['YAML']);
+ ///
+ /// // Note that the value does not change
+ /// print(newNode.value); // "YAML"
+ /// print(node.value); // "YAML Ain't Markup Language"
+ /// ```
+ YamlNode parseAt(Iterable<Object?> path, {YamlNode Function()? orElse}) {
+ return _traverse(path, orElse: orElse);
+ }
+
+ /// Sets [value] in the [path].
+ ///
+ /// There is a subtle difference between [update] and [remove] followed by
+ /// an [insertIntoList], because [update] preserves comments at the same
+ /// level.
+ ///
+ /// Throws a [ArgumentError] if [path] is invalid.
+ ///
+ /// Throws an [AliasException] if a node on [path] is an alias or anchor.
+ ///
+ /// **Example:** (using [update])
+ /// ```dart
+ /// final doc = YamlEditor('''
+ /// - 0
+ /// - 1 # comment
+ /// - 2
+ /// ''');
+ /// doc.update([1], 'test');
+ /// ```
+ ///
+ /// **Expected Output:**
+ /// ```yaml
+ /// - 0
+ /// - test # comment
+ /// - 2
+ /// ```
+ ///
+ /// **Example:** (using [remove] and [insertIntoList])
+ /// ```dart
+ /// final doc2 = YamlEditor('''
+ /// - 0
+ /// - 1 # comment
+ /// - 2
+ /// ''');
+ /// doc2.remove([1]);
+ /// doc2.insertIntoList([], 1, 'test');
+ /// ```
+ ///
+ /// **Expected Output:**
+ /// ```yaml
+ /// - 0
+ /// - test
+ /// - 2
+ /// ```
+ void update(Iterable<Object?> path, Object? value) {
+ final valueNode = wrapAsYamlNode(value);
+
+ if (path.isEmpty) {
+ final start = _contents.span.start.offset;
+ final end = getContentSensitiveEnd(_contents);
+ final lineEnding = getLineEnding(_yaml);
+ final edit = SourceEdit(
+ start, end - start, yamlEncodeBlock(valueNode, 0, lineEnding));
+
+ return _performEdit(edit, path, valueNode);
+ }
+
+ final pathAsList = path.toList();
+ final collectionPath = pathAsList.take(path.length - 1);
+ final keyOrIndex = pathAsList.last;
+ final parentNode = _traverse(collectionPath, checkAlias: true);
+
+ if (parentNode is YamlList) {
+ if (keyOrIndex is! int) {
+ throw PathError(path, path, parentNode);
+ }
+ final expected = wrapAsYamlNode(
+ [...parentNode.nodes]..[keyOrIndex] = valueNode,
+ );
+
+ return _performEdit(updateInList(this, parentNode, keyOrIndex, valueNode),
+ collectionPath, expected);
+ }
+
+ if (parentNode is YamlMap) {
+ final expectedMap =
+ updatedYamlMap(parentNode, (nodes) => nodes[keyOrIndex] = valueNode);
+ return _performEdit(updateInMap(this, parentNode, keyOrIndex, valueNode),
+ collectionPath, expectedMap);
+ }
+
+ throw PathError.unexpected(
+ path, 'Scalar $parentNode does not have key $keyOrIndex');
+ }
+
+ /// Appends [value] to the list at [path].
+ ///
+ /// Throws a [ArgumentError] if the element at the given path is not a
+ /// [YamlList] or if the path is invalid.
+ ///
+ /// Throws an [AliasException] if a node on [path] is an alias or anchor.
+ ///
+ /// **Example:**
+ /// ```dart
+ /// final doc = YamlEditor('[0, 1]');
+ /// doc.appendToList([], 2); // [0, 1, 2]
+ /// ```
+ void appendToList(Iterable<Object?> path, Object? value) {
+ final yamlList = _traverseToList(path);
+
+ insertIntoList(path, yamlList.length, value);
+ }
+
+ /// Prepends [value] to the list at [path].
+ ///
+ /// Throws a [ArgumentError] if the element at the given path is not a
+ /// [YamlList] or if the path is invalid.
+ ///
+ /// Throws an [AliasException] if a node on [path] is an alias or anchor.
+ ///
+ /// **Example:**
+ /// ```dart
+ /// final doc = YamlEditor('[1, 2]');
+ /// doc.prependToList([], 0); // [0, 1, 2]
+ /// ```
+ void prependToList(Iterable<Object?> path, Object? value) {
+ insertIntoList(path, 0, value);
+ }
+
+ /// Inserts [value] into the list at [path].
+ ///
+ /// [index] must be non-negative and no greater than the list's length.
+ ///
+ /// Throws a [ArgumentError] if the element at the given path is not a
+ /// [YamlList] or if the path is invalid.
+ ///
+ /// Throws an [AliasException] if a node on [path] is an alias or anchor.
+ ///
+ /// **Example:**
+ /// ```dart
+ /// final doc = YamlEditor('[0, 2]');
+ /// doc.insertIntoList([], 1, 1); // [0, 1, 2]
+ /// ```
+ void insertIntoList(Iterable<Object?> path, int index, Object? value) {
+ final valueNode = wrapAsYamlNode(value);
+
+ final list = _traverseToList(path, checkAlias: true);
+ RangeError.checkValueInInterval(index, 0, list.length);
+
+ final edit = insertInList(this, list, index, valueNode);
+ final expected = wrapAsYamlNode(
+ [...list.nodes]..insert(index, valueNode),
+ );
+
+ _performEdit(edit, path, expected);
+ }
+
+ /// Changes the contents of the list at [path] by removing [deleteCount]
+ /// items at [index], and inserting [values] in-place. Returns the elements
+ /// that are deleted.
+ ///
+ /// [index] and [deleteCount] must be non-negative and [index] + [deleteCount]
+ /// must be no greater than the list's length.
+ ///
+ /// Throws a [ArgumentError] if the element at the given path is not a
+ /// [YamlList] or if the path is invalid.
+ ///
+ /// Throws an [AliasException] if a node on [path] is an alias or anchor.
+ ///
+ /// **Example:**
+ /// ```dart
+ /// final doc = YamlEditor('[Jan, March, April, June]');
+ /// doc.spliceList([], 1, 0, ['Feb']); // [Jan, Feb, March, April, June]
+ /// doc.spliceList([], 4, 1, ['May']); // [Jan, Feb, March, April, May]
+ /// ```
+ Iterable<YamlNode> spliceList(Iterable<Object?> path, int index,
+ int deleteCount, Iterable<Object?> values) {
+ final list = _traverseToList(path, checkAlias: true);
+
+ RangeError.checkValueInInterval(index, 0, list.length);
+ RangeError.checkValueInInterval(index + deleteCount, 0, list.length);
+
+ final nodesToRemove = list.nodes.getRange(index, index + deleteCount);
+
+ // Perform addition of elements before removal to avoid scenarios where
+ // a block list gets emptied out to {} to avoid changing collection styles
+ // where possible.
+
+ // Reverse [values] and insert them.
+ final reversedValues = values.toList().reversed;
+ for (final value in reversedValues) {
+ insertIntoList(path, index, value);
+ }
+
+ for (var i = 0; i < deleteCount; i++) {
+ remove([...path, index + values.length]);
+ }
+
+ return nodesToRemove;
+ }
+
+ /// Removes the node at [path]. Comments "belonging" to the node will be
+ /// removed while surrounding comments will be left untouched.
+ ///
+ /// Throws an [ArgumentError] if [path] is invalid.
+ ///
+ /// Throws an [AliasException] if a node on [path] is an alias or anchor.
+ ///
+ /// **Example:**
+ /// ```dart
+ /// final doc = YamlEditor('''
+ /// - 0 # comment 0
+ /// # comment A
+ /// - 1 # comment 1
+ /// # comment B
+ /// - 2 # comment 2
+ /// ''');
+ /// doc.remove([1]);
+ /// ```
+ ///
+ /// **Expected Result:**
+ /// ```dart
+ /// '''
+ /// - 0 # comment 0
+ /// # comment A
+ /// # comment B
+ /// - 2 # comment 2
+ /// '''
+ /// ```
+ YamlNode remove(Iterable<Object?> path) {
+ late SourceEdit edit;
+ late YamlNode expectedNode;
+ final nodeToRemove = _traverse(path, checkAlias: true);
+
+ if (path.isEmpty) {
+ edit = SourceEdit(0, _yaml.length, '');
+ expectedNode = wrapAsYamlNode(null);
+
+ /// Parsing an empty YAML document returns YamlScalar with value `null`.
+ _performEdit(edit, path, expectedNode);
+ return nodeToRemove;
+ }
+
+ final pathAsList = path.toList();
+ final collectionPath = pathAsList.take(path.length - 1);
+ final keyOrIndex = pathAsList.last;
+ final parentNode = _traverse(collectionPath);
+
+ if (parentNode is YamlList) {
+ edit = removeInList(this, parentNode, keyOrIndex as int);
+ expectedNode = wrapAsYamlNode(
+ [...parentNode.nodes]..removeAt(keyOrIndex),
+ );
+ } else if (parentNode is YamlMap) {
+ edit = removeInMap(this, parentNode, keyOrIndex);
+
+ expectedNode =
+ updatedYamlMap(parentNode, (nodes) => nodes.remove(keyOrIndex));
+ }
+
+ _performEdit(edit, collectionPath, expectedNode);
+
+ return nodeToRemove;
+ }
+
+ /// Traverses down [path] to return the [YamlNode] at [path] if successful.
+ ///
+ /// If no [YamlNode]s exist at [path], the result of invoking the [orElse]
+ /// function is returned.
+ ///
+ /// If [orElse] is omitted, it defaults to throwing a [PathError].
+ ///
+ /// If [checkAlias] is `true`, throw [AliasException] if an aliased node is
+ /// encountered.
+ YamlNode _traverse(Iterable<Object?> path,
+ {bool checkAlias = false, YamlNode Function()? orElse}) {
+ if (path.isEmpty) return _contents;
+
+ var currentNode = _contents;
+ final pathList = path.toList();
+
+ for (var i = 0; i < pathList.length; i++) {
+ final keyOrIndex = pathList[i];
+
+ if (checkAlias && _aliases.contains(currentNode)) {
+ throw AliasException(path, currentNode);
+ }
+
+ if (currentNode is YamlList) {
+ final list = currentNode;
+ if (!isValidIndex(keyOrIndex, list.length)) {
+ return _pathErrorOrElse(path, path.take(i + 1), list, orElse);
+ }
+
+ currentNode = list.nodes[keyOrIndex as int];
+ } else if (currentNode is YamlMap) {
+ final map = currentNode;
+
+ if (!containsKey(map, keyOrIndex)) {
+ return _pathErrorOrElse(path, path.take(i + 1), map, orElse);
+ }
+ final keyNode = getKeyNode(map, keyOrIndex);
+
+ if (checkAlias) {
+ if (_aliases.contains(keyNode)) throw AliasException(path, keyNode);
+ }
+
+ currentNode = map.nodes[keyNode]!;
+ } else {
+ return _pathErrorOrElse(path, path.take(i + 1), currentNode, orElse);
+ }
+ }
+
+ if (checkAlias) _assertNoChildAlias(path, currentNode);
+
+ return currentNode;
+ }
+
+ /// Throws a [PathError] if [orElse] is not provided, returns the result
+ /// of invoking the [orElse] function otherwise.
+ YamlNode _pathErrorOrElse(Iterable<Object?> path, Iterable<Object?> subPath,
+ YamlNode parent, YamlNode Function()? orElse) {
+ if (orElse == null) throw PathError(path, subPath, parent);
+ return orElse();
+ }
+
+ /// Asserts that [node] and none its children are aliases
+ void _assertNoChildAlias(Iterable<Object?> path, [YamlNode? node]) {
+ if (node == null) return _assertNoChildAlias(path, _traverse(path));
+ if (_aliases.contains(node)) throw AliasException(path, node);
+
+ if (node is YamlScalar) return;
+
+ if (node is YamlList) {
+ for (var i = 0; i < node.length; i++) {
+ final updatedPath = [...path, i];
+ _assertNoChildAlias(updatedPath, node.nodes[i]);
+ }
+ }
+
+ if (node is YamlMap) {
+ final keyList = node.keys.toList();
+ for (var i = 0; i < node.length; i++) {
+ final updatedPath = [...path, keyList[i]];
+ if (_aliases.contains(keyList[i])) {
+ throw AliasException(path, keyList[i] as YamlNode);
+ }
+ _assertNoChildAlias(updatedPath, node.nodes[keyList[i]]);
+ }
+ }
+ }
+
+ /// Traverses down the provided [path] to return the [YamlList] at [path].
+ ///
+ /// Convenience function to ensure that a [YamlList] is returned.
+ ///
+ /// Throws [ArgumentError] if the element at the given path is not a
+ /// [YamlList] or if the path is invalid. If [checkAlias] is `true`, and an
+ /// aliased node is encountered along [path], an [AliasException] will be
+ /// thrown.
+ YamlList _traverseToList(Iterable<Object?> path, {bool checkAlias = false}) {
+ final possibleList = _traverse(path, checkAlias: checkAlias);
+
+ if (possibleList is YamlList) {
+ return possibleList;
+ } else {
+ throw PathError.unexpected(
+ path, 'Path $path does not point to a YamlList!');
+ }
+ }
+
+ /// Utility method to replace the substring of [_yaml] according to [edit].
+ ///
+ /// When [_yaml] is modified with this method, the resulting string is parsed
+ /// and reloaded and traversed down [path] to ensure that the reloaded YAML
+ /// tree is equal to our expectations by deep equality of values. Throws an
+ /// [AssertionError] if the two trees do not match.
+ void _performEdit(
+ SourceEdit edit, Iterable<Object?> path, YamlNode expectedNode) {
+ final expectedTree = _deepModify(_contents, path, [], expectedNode);
+ final initialYaml = _yaml;
+ _yaml = edit.apply(_yaml);
+
+ try {
+ _initialize();
+ } on YamlException {
+ throw createAssertionError(
+ 'Failed to produce valid YAML after modification.',
+ initialYaml,
+ _yaml);
+ }
+
+ final actualTree = withYamlWarningCallback(() => loadYamlNode(_yaml));
+ if (!deepEquals(actualTree, expectedTree)) {
+ throw createAssertionError(
+ 'Modification did not result in expected result.',
+ initialYaml,
+ _yaml);
+ }
+
+ _contents = actualTree;
+ _edits.add(edit);
+ }
+
+ /// Utility method to produce an updated YAML tree equivalent to converting
+ /// the [YamlNode] at [path] to be [expectedNode]. [subPath] holds the portion
+ /// of [path] that has been traversed thus far.
+ ///
+ /// Throws a [PathError] if path is invalid.
+ ///
+ /// When called, it creates a new [YamlNode] of the same type as [tree], and
+ /// copies its children over, except for the child that is on the path. Doing
+ /// so allows us to "update" the immutable [YamlNode] without having to clone
+ /// the whole tree.
+ ///
+ /// [SourceSpan]s in this new tree are not guaranteed to be accurate.
+ YamlNode _deepModify(YamlNode tree, Iterable<Object?> path,
+ Iterable<Object?> subPath, YamlNode expectedNode) {
+ RangeError.checkValueInInterval(subPath.length, 0, path.length);
+
+ if (path.length == subPath.length) return expectedNode;
+
+ final keyOrIndex = path.elementAt(subPath.length);
+
+ if (tree is YamlList) {
+ if (!isValidIndex(keyOrIndex, tree.length)) {
+ throw PathError(path, subPath, tree);
+ }
+
+ return wrapAsYamlNode([...tree.nodes]..[keyOrIndex as int] = _deepModify(
+ tree.nodes[keyOrIndex],
+ path,
+ path.take(subPath.length + 1),
+ expectedNode));
+ }
+
+ if (tree is YamlMap) {
+ return updatedYamlMap(
+ tree,
+ (nodes) => nodes[keyOrIndex] = _deepModify(
+ nodes[keyOrIndex] as YamlNode,
+ path,
+ path.take(subPath.length + 1),
+ expectedNode));
+ }
+
+ /// Should not ever reach here.
+ throw PathError(path, subPath, tree);
+ }
+}
diff --git a/pkgs/yaml_edit/lib/src/equality.dart b/pkgs/yaml_edit/lib/src/equality.dart
new file mode 100644
index 0000000..0c6a952
--- /dev/null
+++ b/pkgs/yaml_edit/lib/src/equality.dart
@@ -0,0 +1,116 @@
+// Copyright (c) 2020, 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:collection';
+
+import 'package:collection/collection.dart';
+import 'package:yaml/yaml.dart';
+
+/// Creates a map that uses our custom [deepEquals] and [deepHashCode] functions
+/// to determine equality.
+Map<K, V> deepEqualsMap<K, V>() =>
+ LinkedHashMap(equals: deepEquals, hashCode: deepHashCode);
+
+/// Compares two [Object]s for deep equality. This implementation differs from
+/// `package:yaml`'s deep equality notation by allowing for comparison of
+/// non-scalar map keys.
+bool deepEquals(dynamic obj1, dynamic obj2) {
+ if (obj1 is YamlNode) obj1 = obj1.value;
+ if (obj2 is YamlNode) obj2 = obj2.value;
+
+ if (obj1 is Map && obj2 is Map) {
+ return mapDeepEquals(obj1, obj2);
+ }
+
+ if (obj1 is List && obj2 is List) {
+ return listDeepEquals(obj1, obj2);
+ }
+
+ return obj1 == obj2;
+}
+
+/// Compares two [List]s for deep equality.
+bool listDeepEquals(List list1, List list2) {
+ if (list1.length != list2.length) return false;
+
+ if (list1 is YamlList) list1 = list1.nodes;
+ if (list2 is YamlList) list2 = list2.nodes;
+
+ for (var i = 0; i < list1.length; i++) {
+ if (!deepEquals(list1[i], list2[i])) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/// Compares two [Map]s for deep equality. Differs from `package:yaml`'s deep
+/// equality notation by allowing for comparison of non-scalar map keys.
+bool mapDeepEquals(Map map1, Map map2) {
+ if (map1.length != map2.length) return false;
+
+ if (map1 is YamlList) map1 = (map1 as YamlMap).nodes;
+ if (map2 is YamlList) map2 = (map2 as YamlMap).nodes;
+
+ return map1.keys.every((key) {
+ if (!containsKey(map2, key)) return false;
+
+ /// Because two keys may be equal by deep equality but using one key on the
+ /// other map might not get a hit since they may not be both using our
+ /// [deepEqualsMap].
+ final key2 = getKey(map2, key);
+
+ if (!deepEquals(map1[key], map2[key2])) {
+ return false;
+ }
+
+ return true;
+ });
+}
+
+/// Returns a hashcode for [value] such that structures that are equal by
+/// [deepEquals] will have the same hash code.
+int deepHashCode(Object? value) {
+ if (value is Map) {
+ const equality = UnorderedIterableEquality();
+ return equality.hash(value.keys.map(deepHashCode)) ^
+ equality.hash(value.values.map(deepHashCode));
+ } else if (value is Iterable) {
+ return const IterableEquality().hash(value.map(deepHashCode));
+ } else if (value is YamlScalar) {
+ return (value.value as Object?).hashCode;
+ }
+
+ return value.hashCode;
+}
+
+/// Returns the [YamlNode] corresponding to the provided [key].
+YamlNode getKeyNode(YamlMap map, Object? key) {
+ return map.nodes.keys.firstWhere((node) => deepEquals(node, key)) as YamlNode;
+}
+
+/// Returns the [YamlNode] after the [YamlNode] corresponding to the provided
+/// [key].
+YamlNode? getNextKeyNode(YamlMap map, Object? key) {
+ final keyIterator = map.nodes.keys.iterator;
+ while (keyIterator.moveNext()) {
+ if (deepEquals(keyIterator.current, key) && keyIterator.moveNext()) {
+ return keyIterator.current as YamlNode?;
+ }
+ }
+
+ return null;
+}
+
+/// Returns the key in [map] that is equal to the provided [key] by the notion
+/// of deep equality.
+Object? getKey(Map map, Object? key) {
+ return map.keys.firstWhere((k) => deepEquals(k, key));
+}
+
+/// Checks if [map] has any keys equal to the provided [key] by deep equality.
+bool containsKey(Map map, Object? key) {
+ return map.keys.where((node) => deepEquals(node, key)).isNotEmpty;
+}
diff --git a/pkgs/yaml_edit/lib/src/errors.dart b/pkgs/yaml_edit/lib/src/errors.dart
new file mode 100644
index 0000000..0e60dd8
--- /dev/null
+++ b/pkgs/yaml_edit/lib/src/errors.dart
@@ -0,0 +1,99 @@
+// Copyright (c) 2020, 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:meta/meta.dart';
+import 'package:yaml/yaml.dart';
+
+/// Error thrown when a function is passed an invalid path.
+@sealed
+class PathError extends ArgumentError {
+ /// The full path that caused the error
+ final Iterable<Object?> path;
+
+ /// The subpath that caused the error
+ final Iterable<Object?> subPath;
+
+ /// The last element of [path] that could be traversed.
+ YamlNode? parent;
+
+ PathError(this.path, this.subPath, this.parent, [String? message])
+ : super.value(subPath, 'path', message);
+
+ PathError.unexpected(this.path, String message)
+ : subPath = path,
+ super(message);
+
+ @override
+ String toString() {
+ if (message == null) {
+ var errorMessage = 'Failed to traverse to subpath $subPath!';
+
+ if (subPath.isNotEmpty) {
+ errorMessage +=
+ ' Parent $parent does not contain key or index ${subPath.last}';
+ }
+
+ return 'Invalid path: $path. $errorMessage.';
+ }
+
+ return 'Invalid path: $path. $message';
+ }
+}
+
+/// Exception thrown when the path contains an alias along the way.
+///
+/// When a path contains an aliased node, the behavior becomes less well-defined
+/// because we cannot be certain if the user wishes for the change to propagate
+/// throughout all the other aliased nodes, or if the user wishes for only that
+/// particular node to be modified. As such, [AliasException] reflects the
+/// detection that our change will impact an alias, and we do not intend on
+/// supporting such changes for the foreseeable future.
+@sealed
+class AliasException extends FormatException {
+ /// The path that caused the error
+ final Iterable<Object?> path;
+
+ /// The anchor node of the alias
+ final YamlNode anchor;
+
+ AliasException(this.path, this.anchor)
+ : super('Encountered an alias node along $path! '
+ 'Alias nodes are nodes that refer to a previously serialized '
+ 'nodes, and are denoted by either the "*" or the "&" indicators in '
+ 'the original YAML. As the resulting behavior of mutations on '
+ 'these nodes is not well-defined, the operation will not be '
+ 'supported by this library.\n\n'
+ '${anchor.span.message('The alias was first defined here.')}');
+}
+
+/// Error thrown when an assertion about the YAML fails. Extends
+/// [AssertionError] to override the [toString] method for pretty printing.
+class _YamlAssertionError extends AssertionError {
+ _YamlAssertionError(super.message);
+
+ @override
+ String toString() {
+ if (message != null) {
+ return 'Assertion failed: $message';
+ }
+ return 'Assertion failed';
+ }
+}
+
+/// Throws an [AssertionError] with the given [message], and format
+/// [oldYaml] and [newYaml] for information.
+Error createAssertionError(String message, String oldYaml, String newYaml) {
+ return _YamlAssertionError('''
+(package:yaml_edit) $message
+
+# YAML before edit:
+> ${oldYaml.replaceAll('\n', '\n> ')}
+
+# YAML after edit:
+> ${newYaml.replaceAll('\n', '\n> ')}
+
+Please file an issue at:
+https://github.com/dart-lang/yaml_edit/issues/new?labels=bug
+''');
+}
diff --git a/pkgs/yaml_edit/lib/src/list_mutations.dart b/pkgs/yaml_edit/lib/src/list_mutations.dart
new file mode 100644
index 0000000..17da6dd
--- /dev/null
+++ b/pkgs/yaml_edit/lib/src/list_mutations.dart
@@ -0,0 +1,403 @@
+// Copyright (c) 2020, 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:yaml/yaml.dart';
+
+import 'editor.dart';
+import 'source_edit.dart';
+import 'strings.dart';
+import 'utils.dart';
+import 'wrap.dart';
+
+/// Returns a [SourceEdit] describing the change to be made on [yamlEdit] to
+/// achieve the effect of setting the element at [index] to [newValue] when
+/// re-parsed.
+SourceEdit updateInList(
+ YamlEditor yamlEdit, YamlList list, int index, YamlNode newValue) {
+ RangeError.checkValueInInterval(index, 0, list.length - 1);
+
+ final currValue = list.nodes[index];
+ var offset = currValue.span.start.offset;
+ final yaml = yamlEdit.toString();
+ String valueString;
+
+ /// We do not use [_formatNewBlock] since we want to only replace the contents
+ /// of this node while preserving comments/whitespace, while [_formatNewBlock]
+ /// produces a string representation of a new node.
+ if (list.style == CollectionStyle.BLOCK) {
+ final listIndentation = getListIndentation(yaml, list);
+ final indentation = listIndentation + getIndentation(yamlEdit);
+ final lineEnding = getLineEnding(yaml);
+ valueString =
+ yamlEncodeBlock(wrapAsYamlNode(newValue), indentation, lineEnding);
+
+ /// We prefer the compact nested notation for collections.
+ ///
+ /// By virtue of [yamlEncodeBlockString], collections automatically
+ /// have the necessary line endings.
+ if ((newValue is List && (newValue as List).isNotEmpty) ||
+ (newValue is Map && (newValue as Map).isNotEmpty)) {
+ valueString = valueString.substring(indentation);
+ } else if (currValue.collectionStyle == CollectionStyle.BLOCK) {
+ valueString += lineEnding;
+ }
+
+ var end = getContentSensitiveEnd(currValue);
+ if (end <= offset) {
+ offset++;
+ end = offset;
+ valueString = ' $valueString';
+ }
+
+ return SourceEdit(offset, end - offset, valueString);
+ } else {
+ valueString = yamlEncodeFlow(newValue);
+ return SourceEdit(offset, currValue.span.length, valueString);
+ }
+}
+
+/// Returns a [SourceEdit] describing the change to be made on [yamlEdit] to
+/// achieve the effect of appending [item] to the list.
+SourceEdit appendIntoList(YamlEditor yamlEdit, YamlList list, YamlNode item) {
+ if (list.style == CollectionStyle.FLOW) {
+ return _appendToFlowList(yamlEdit, list, item);
+ } else {
+ return _appendToBlockList(yamlEdit, list, item);
+ }
+}
+
+/// Returns a [SourceEdit] describing the change to be made on [yamlEdit] to
+/// achieve the effect of inserting [item] to the list at [index].
+SourceEdit insertInList(
+ YamlEditor yamlEdit, YamlList list, int index, YamlNode item) {
+ RangeError.checkValueInInterval(index, 0, list.length);
+
+ /// We call the append method if the user wants to append it to the end of the
+ /// list because appending requires different techniques.
+ if (index == list.length) {
+ return appendIntoList(yamlEdit, list, item);
+ } else {
+ if (list.style == CollectionStyle.FLOW) {
+ return _insertInFlowList(yamlEdit, list, index, item);
+ } else {
+ return _insertInBlockList(yamlEdit, list, index, item);
+ }
+ }
+}
+
+/// Returns a [SourceEdit] describing the change to be made on [yamlEdit] to
+/// achieve the effect of removing the element at [index] when re-parsed.
+SourceEdit removeInList(YamlEditor yamlEdit, YamlList list, int index) {
+ final nodeToRemove = list.nodes[index];
+
+ if (list.style == CollectionStyle.FLOW) {
+ return _removeFromFlowList(yamlEdit, list, nodeToRemove, index);
+ } else {
+ return _removeFromBlockList(yamlEdit, list, nodeToRemove, index);
+ }
+}
+
+/// Returns a [SourceEdit] describing the change to be made on [yamlEdit] to
+/// achieve the effect of addition [item] into [list], noting that this is a
+/// flow list.
+SourceEdit _appendToFlowList(
+ YamlEditor yamlEdit, YamlList list, YamlNode item) {
+ final valueString = _formatNewFlow(list, item, true);
+ return SourceEdit(list.span.end.offset - 1, 0, valueString);
+}
+
+/// Returns a [SourceEdit] describing the change to be made on [yamlEdit] to
+/// achieve the effect of addition [item] into [list], noting that this is a
+/// block list.
+SourceEdit _appendToBlockList(
+ YamlEditor yamlEdit, YamlList list, YamlNode item) {
+ var (indentSize, valueToIndent) = _formatNewBlock(yamlEdit, list, item);
+ var formattedValue = '${' ' * indentSize}$valueToIndent';
+
+ final yaml = yamlEdit.toString();
+ var offset = list.span.end.offset;
+
+ // Adjusts offset to after the trailing newline of the last entry, if it
+ // exists
+ if (list.isNotEmpty) {
+ final lastValueSpanEnd = list.nodes.last.span.end.offset;
+ final nextNewLineIndex = yaml.indexOf('\n', lastValueSpanEnd - 1);
+ if (nextNewLineIndex == -1) {
+ formattedValue = getLineEnding(yaml) + formattedValue;
+ } else {
+ offset = nextNewLineIndex + 1;
+ }
+ }
+
+ return SourceEdit(offset, 0, formattedValue);
+}
+
+/// Formats [item] into a new node for block lists.
+(int indentSize, String valueStringToIndent) _formatNewBlock(
+ YamlEditor yamlEdit, YamlList list, YamlNode item) {
+ final yaml = yamlEdit.toString();
+ final listIndentation = getListIndentation(yaml, list);
+ final newIndentation = listIndentation + getIndentation(yamlEdit);
+ final lineEnding = getLineEnding(yaml);
+
+ var valueString = yamlEncodeBlock(item, newIndentation, lineEnding);
+ if (isCollection(item) && !isFlowYamlCollectionNode(item) && !isEmpty(item)) {
+ valueString = valueString.substring(newIndentation);
+ }
+
+ return (listIndentation, '- $valueString$lineEnding');
+}
+
+/// Formats [item] into a new node for flow lists.
+String _formatNewFlow(YamlList list, YamlNode item, [bool isLast = false]) {
+ var valueString = yamlEncodeFlow(item);
+ if (list.isNotEmpty) {
+ if (isLast) {
+ valueString = ', $valueString';
+ } else {
+ valueString += ', ';
+ }
+ }
+
+ return valueString;
+}
+
+/// Returns a [SourceEdit] describing the change to be made on [yamlEdit] to
+/// achieve the effect of inserting [item] into [list] at [index], noting that
+/// this is a block list.
+///
+/// [index] should be non-negative and less than or equal to `list.length`.
+SourceEdit _insertInBlockList(
+ YamlEditor yamlEdit, YamlList list, int index, YamlNode item) {
+ RangeError.checkValueInInterval(index, 0, list.length);
+
+ if (index == list.length) return _appendToBlockList(yamlEdit, list, item);
+
+ var (indentSize, formattedValue) = _formatNewBlock(yamlEdit, list, item);
+
+ final currNode = list.nodes[index];
+ final currNodeStart = currNode.span.start.offset;
+ final yaml = yamlEdit.toString();
+
+ final currSequenceOffset = yaml.lastIndexOf('-', currNodeStart - 1);
+
+ final (isNested, offset) = _isNestedInBlockList(currSequenceOffset, yaml);
+
+ /// We have to get rid of the left indentation applied by default
+ if (isNested && index == 0) {
+ /// The [insertionIndex] will be equal to the start of
+ /// [currentSequenceOffset] of the element we are inserting before in most
+ /// cases.
+ ///
+ /// Example:
+ ///
+ /// - - value
+ /// ^ Inserting before this and we get rid of indent
+ ///
+ /// If not, we need to account for the space between them that is not an
+ /// indent.
+ ///
+ /// Example:
+ ///
+ /// - - value
+ /// ^ Inserting before this and we get rid of indent. But also account
+ /// for space in between
+ final leftPad = currSequenceOffset - offset;
+ final padding = ' ' * leftPad;
+
+ final indent = ' ' * (indentSize - leftPad);
+
+ // Give the indent to the first element
+ formattedValue = '$padding${formattedValue.trimLeft()}$indent';
+ } else {
+ final indent = ' ' * indentSize; // Calculate indent normally
+ formattedValue = '$indent$formattedValue';
+ }
+
+ return SourceEdit(offset, 0, formattedValue);
+}
+
+/// Determines if the list containing an element is nested within another list.
+/// The [currentSequenceOffset] indicates the index of the element's `-` and
+/// [yaml] represents the entire yaml document.
+///
+/// ```yaml
+/// # Returns true
+/// - - value
+///
+/// # Returns true
+/// - - value
+///
+/// # Returns false
+/// key:
+/// - value
+///
+/// # Returns false. Even though nested, a "\n" precedes the previous "-"
+/// -
+/// - value
+/// ```
+(bool isNested, int offset) _isNestedInBlockList(
+ int currentSequenceOffset, String yaml) {
+ final startIndex = currentSequenceOffset - 1;
+
+ /// Indicates the element we are inserting before is at index `0` of the list
+ /// at the root of the yaml
+ ///
+ /// Example:
+ ///
+ /// - foo
+ /// ^ Inserting before this
+ if (startIndex < 0) return (false, 0);
+
+ final newLineStart = yaml.lastIndexOf('\n', startIndex);
+ final seqStart = yaml.lastIndexOf('-', startIndex);
+
+ /// Indicates that a `\n` is closer to the last `-`. Meaning this list is not
+ /// nested.
+ ///
+ /// Example:
+ ///
+ /// key:
+ /// - value
+ /// ^ Inserting before this and we need to keep the indent.
+ ///
+ /// Also this list may be nested but the nested list starts its indent after
+ /// a new line.
+ ///
+ /// Example:
+ ///
+ /// -
+ /// - value
+ /// ^ Inserting before this and we need to keep the indent.
+ if (newLineStart >= seqStart) {
+ return (false, newLineStart + 1);
+ }
+
+ return (true, seqStart + 2); // Inclusive of space
+}
+
+/// Returns a [SourceEdit] describing the change to be made on [yamlEdit] to
+/// achieve the effect of inserting [item] into [list] at [index], noting that
+/// this is a flow list.
+///
+/// [index] should be non-negative and less than or equal to `list.length`.
+SourceEdit _insertInFlowList(
+ YamlEditor yamlEdit, YamlList list, int index, YamlNode item) {
+ RangeError.checkValueInInterval(index, 0, list.length);
+
+ if (index == list.length) return _appendToFlowList(yamlEdit, list, item);
+
+ final formattedValue = _formatNewFlow(list, item);
+
+ final yaml = yamlEdit.toString();
+ final currNode = list.nodes[index];
+ final currNodeStart = currNode.span.start.offset;
+ var start = yaml.lastIndexOf(RegExp(r',|\['), currNodeStart - 1) + 1;
+ if (yaml[start] == ' ') start++;
+
+ return SourceEdit(start, 0, formattedValue);
+}
+
+/// Returns a [SourceEdit] describing the change to be made on [yamlEdit] to
+/// achieve the effect of removing [nodeToRemove] from [list], noting that this
+/// is a block list.
+///
+/// [index] should be non-negative and less than or equal to `list.length`.
+SourceEdit _removeFromBlockList(
+ YamlEditor yamlEdit, YamlList list, YamlNode nodeToRemove, int index) {
+ RangeError.checkValueInInterval(index, 0, list.length - 1);
+
+ var end = getContentSensitiveEnd(nodeToRemove);
+
+ /// If we are removing the last element in a block list, convert it into a
+ /// flow empty list.
+ if (list.length == 1) {
+ final start = list.span.start.offset;
+
+ return SourceEdit(start, end - start, '[]');
+ }
+
+ final yaml = yamlEdit.toString();
+ final span = nodeToRemove.span;
+
+ /// Adjust the end to clear the new line after the end too.
+ ///
+ /// We do this because we suspect that our users will want the inline
+ /// comments to disappear too.
+ final nextNewLine = yaml.indexOf('\n', end);
+ if (nextNewLine != -1) {
+ end = nextNewLine + 1;
+ }
+
+ /// If the value is empty
+ if (span.length == 0) {
+ var start = span.start.offset;
+ return SourceEdit(start, end - start, '');
+ }
+
+ /// -1 accounts for the fact that the content can start with a dash
+ var start = yaml.lastIndexOf('-', span.start.offset - 1);
+
+ /// Check if there is a `-` before the node
+ if (start > 0) {
+ final lastHyphen = yaml.lastIndexOf('-', start - 1);
+ final lastNewLine = yaml.lastIndexOf('\n', start - 1);
+ if (lastHyphen > lastNewLine) {
+ start = lastHyphen + 2;
+
+ /// If there is a `-` before the node, we need to check if we have
+ /// to update the indentation of the next node.
+ if (index < list.length - 1) {
+ /// Since [end] is currently set to the next new line after the current
+ /// node, check if we see a possible comment first, or a hyphen first.
+ /// Note that no actual content can appear here.
+ ///
+ /// We check this way because the start of a span in a block list is
+ /// the start of its value, and checking from the back leaves us
+ /// easily confused if there are comments that have dashes in them.
+ final nextHash = yaml.indexOf('#', end);
+ final nextHyphen = yaml.indexOf('-', end);
+ final nextNewLine = yaml.indexOf('\n', end);
+
+ /// If [end] is on the same line as the hyphen of the next node
+ if ((nextHash == -1 || nextHyphen < nextHash) &&
+ nextHyphen < nextNewLine) {
+ end = nextHyphen;
+ }
+ }
+ } else if (lastNewLine > lastHyphen) {
+ start = lastNewLine + 1;
+ }
+ }
+
+ return SourceEdit(start, end - start, '');
+}
+
+/// Returns a [SourceEdit] describing the change to be made on [yamlEdit] to
+/// achieve the effect of removing [nodeToRemove] from [list], noting that this
+/// is a flow list.
+///
+/// [index] should be non-negative and less than or equal to `list.length`.
+SourceEdit _removeFromFlowList(
+ YamlEditor yamlEdit, YamlList list, YamlNode nodeToRemove, int index) {
+ RangeError.checkValueInInterval(index, 0, list.length - 1);
+
+ final span = nodeToRemove.span;
+ final yaml = yamlEdit.toString();
+ var start = span.start.offset;
+ var end = span.end.offset;
+
+ if (index == 0) {
+ start = yaml.lastIndexOf('[', start - 1) + 1;
+ if (index == list.length - 1) {
+ end = yaml.indexOf(']', end);
+ } else {
+ end = yaml.indexOf(',', end) + 1;
+ }
+ } else {
+ start = yaml.lastIndexOf(',', start - 1);
+ }
+
+ return SourceEdit(start, end - start, '');
+}
diff --git a/pkgs/yaml_edit/lib/src/map_mutations.dart b/pkgs/yaml_edit/lib/src/map_mutations.dart
new file mode 100644
index 0000000..46e8c79
--- /dev/null
+++ b/pkgs/yaml_edit/lib/src/map_mutations.dart
@@ -0,0 +1,257 @@
+// Copyright (c) 2020, 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:yaml/yaml.dart';
+
+import 'editor.dart';
+import 'equality.dart';
+import 'source_edit.dart';
+import 'strings.dart';
+import 'utils.dart';
+import 'wrap.dart';
+
+/// Performs the string operation on [yamlEdit] to achieve the effect of setting
+/// the element at [key] to [newValue] when re-parsed.
+SourceEdit updateInMap(
+ YamlEditor yamlEdit, YamlMap map, Object? key, YamlNode newValue) {
+ if (!containsKey(map, key)) {
+ final keyNode = wrapAsYamlNode(key);
+
+ if (map.style == CollectionStyle.FLOW) {
+ return _addToFlowMap(yamlEdit, map, keyNode, newValue);
+ } else {
+ return _addToBlockMap(yamlEdit, map, keyNode, newValue);
+ }
+ } else {
+ if (map.style == CollectionStyle.FLOW) {
+ return _replaceInFlowMap(yamlEdit, map, key, newValue);
+ } else {
+ return _replaceInBlockMap(yamlEdit, map, key, newValue);
+ }
+ }
+}
+
+/// Performs the string operation on [yamlEdit] to achieve the effect of
+/// removing the element at [key] when re-parsed.
+SourceEdit removeInMap(YamlEditor yamlEdit, YamlMap map, Object? key) {
+ assert(containsKey(map, key));
+ final keyNode = getKeyNode(map, key);
+ final valueNode = map.nodes[keyNode]!;
+
+ if (map.style == CollectionStyle.FLOW) {
+ return _removeFromFlowMap(yamlEdit, map, keyNode, valueNode);
+ } else {
+ return _removeFromBlockMap(yamlEdit, map, keyNode, valueNode);
+ }
+}
+
+/// Performs the string operation on [yamlEdit] to achieve the effect of adding
+/// the [key]:[newValue] pair when reparsed, bearing in mind that this is a
+/// block map.
+SourceEdit _addToBlockMap(
+ YamlEditor yamlEdit, YamlMap map, Object key, YamlNode newValue) {
+ final yaml = yamlEdit.toString();
+ final newIndentation =
+ getMapIndentation(yaml, map) + getIndentation(yamlEdit);
+ final keyString = yamlEncodeFlow(wrapAsYamlNode(key));
+ final lineEnding = getLineEnding(yaml);
+
+ var formattedValue = ' ' * getMapIndentation(yaml, map);
+ var offset = map.span.end.offset;
+
+ final insertionIndex = getMapInsertionIndex(map, keyString);
+
+ if (map.isNotEmpty) {
+ /// Adjusts offset to after the trailing newline of the last entry, if it
+ /// exists
+ if (insertionIndex == map.length) {
+ final lastValueSpanEnd = getContentSensitiveEnd(map.nodes.values.last);
+ final nextNewLineIndex = yaml.indexOf('\n', lastValueSpanEnd);
+
+ if (nextNewLineIndex != -1) {
+ offset = nextNewLineIndex + 1;
+ } else {
+ formattedValue = lineEnding + formattedValue;
+ }
+ } else {
+ final keyAtIndex = map.nodes.keys.toList()[insertionIndex] as YamlNode;
+ final keySpanStart = keyAtIndex.span.start.offset;
+ final prevNewLineIndex = yaml.lastIndexOf('\n', keySpanStart);
+
+ offset = prevNewLineIndex + 1;
+ }
+ }
+
+ var valueString = yamlEncodeBlock(newValue, newIndentation, lineEnding);
+ if (isCollection(newValue) &&
+ !isFlowYamlCollectionNode(newValue) &&
+ !isEmpty(newValue)) {
+ formattedValue += '$keyString:$lineEnding$valueString$lineEnding';
+ } else {
+ formattedValue += '$keyString: $valueString$lineEnding';
+ }
+
+ return SourceEdit(offset, 0, formattedValue);
+}
+
+/// Performs the string operation on [yamlEdit] to achieve the effect of adding
+/// the [keyNode]:[newValue] pair when reparsed, bearing in mind that this is a
+/// flow map.
+SourceEdit _addToFlowMap(
+ YamlEditor yamlEdit, YamlMap map, YamlNode keyNode, YamlNode newValue) {
+ final keyString = yamlEncodeFlow(keyNode);
+ final valueString = yamlEncodeFlow(newValue);
+
+ // The -1 accounts for the closing bracket.
+ if (map.isEmpty) {
+ return SourceEdit(map.span.end.offset - 1, 0, '$keyString: $valueString');
+ }
+
+ final insertionIndex = getMapInsertionIndex(map, keyString);
+
+ if (insertionIndex == map.length) {
+ return SourceEdit(map.span.end.offset - 1, 0, ', $keyString: $valueString');
+ }
+
+ final insertionOffset =
+ (map.nodes.keys.toList()[insertionIndex] as YamlNode).span.start.offset;
+
+ return SourceEdit(insertionOffset, 0, '$keyString: $valueString, ');
+}
+
+/// Performs the string operation on [yamlEdit] to achieve the effect of
+/// replacing the value at [key] with [newValue] when reparsed, bearing in mind
+/// that this is a block map.
+SourceEdit _replaceInBlockMap(
+ YamlEditor yamlEdit, YamlMap map, Object? key, YamlNode newValue) {
+ final yaml = yamlEdit.toString();
+ final lineEnding = getLineEnding(yaml);
+ final newIndentation =
+ getMapIndentation(yaml, map) + getIndentation(yamlEdit);
+
+ final keyNode = getKeyNode(map, key);
+ var valueAsString =
+ yamlEncodeBlock(wrapAsYamlNode(newValue), newIndentation, lineEnding);
+ if (isCollection(newValue) &&
+ !isFlowYamlCollectionNode(newValue) &&
+ !isEmpty(newValue)) {
+ valueAsString = lineEnding + valueAsString;
+ }
+
+ if (!valueAsString.startsWith(lineEnding)) {
+ // prepend whitespace to ensure there is space after colon.
+ valueAsString = ' $valueAsString';
+ }
+
+ /// +1 accounts for the colon
+ // TODO: What if here is a whitespace following the key, before the colon?
+ final start = keyNode.span.end.offset + 1;
+ var end = getContentSensitiveEnd(map.nodes[key]!);
+
+ /// `package:yaml` parses empty nodes in a way where the start/end of the
+ /// empty value node is the end of the key node, so we have to adjust for
+ /// this.
+ if (end < start) end = start;
+
+ return SourceEdit(start, end - start, valueAsString);
+}
+
+/// Performs the string operation on [yamlEdit] to achieve the effect of
+/// replacing the value at [key] with [newValue] when reparsed, bearing in mind
+/// that this is a flow map.
+SourceEdit _replaceInFlowMap(
+ YamlEditor yamlEdit, YamlMap map, Object? key, YamlNode newValue) {
+ final valueSpan = map.nodes[key]!.span;
+ final valueString = yamlEncodeFlow(newValue);
+
+ return SourceEdit(valueSpan.start.offset, valueSpan.length, valueString);
+}
+
+/// Performs the string operation on [yamlEdit] to achieve the effect of
+/// removing the [keyNode] from the map, bearing in mind that this is a block
+/// map.
+SourceEdit _removeFromBlockMap(
+ YamlEditor yamlEdit, YamlMap map, YamlNode keyNode, YamlNode valueNode) {
+ final keySpan = keyNode.span;
+ var end = getContentSensitiveEnd(valueNode);
+ final yaml = yamlEdit.toString();
+ final lineEnding = getLineEnding(yaml);
+
+ if (map.length == 1) {
+ final start = map.span.start.offset;
+ final nextNewLine = yaml.indexOf(lineEnding, end);
+ if (nextNewLine != -1) {
+ // Remove everything up to the next newline, this strips comments that
+ // follows on the same line as the value we're removing.
+ // It also ensures we consume colon when [valueNode.value] is `null`
+ // because there is no value (e.g. `key: \n`). Because [valueNode.span] in
+ // such cases point to the colon `:`.
+ end = nextNewLine;
+ } else {
+ // Remove everything until the end of the document, if there is no newline
+ end = yaml.length;
+ }
+ return SourceEdit(start, end - start, '{}');
+ }
+
+ var start = keySpan.start.offset;
+
+ /// Adjust the end to clear the new line after the end too.
+ ///
+ /// We do this because we suspect that our users will want the inline
+ /// comments to disappear too.
+ final nextNewLine = yaml.indexOf(lineEnding, end);
+ if (nextNewLine != -1) {
+ end = nextNewLine + lineEnding.length;
+ } else {
+ // Remove everything until the end of the document, if there is no newline
+ end = yaml.length;
+ }
+
+ final nextNode = getNextKeyNode(map, keyNode);
+
+ if (start > 0) {
+ final lastHyphen = yaml.lastIndexOf('-', start - 1);
+ final lastNewLine = yaml.lastIndexOf(lineEnding, start - 1);
+ if (lastHyphen > lastNewLine) {
+ start = lastHyphen + 2;
+
+ /// If there is a `-` before the node, and the end is on the same line
+ /// as the next node, we need to add the necessary offset to the end to
+ /// make sure the next node has the correct indentation.
+ if (nextNode != null &&
+ nextNode.span.start.offset - end <= nextNode.span.start.column) {
+ end += nextNode.span.start.column;
+ }
+ } else if (lastNewLine > lastHyphen) {
+ start = lastNewLine + lineEnding.length;
+ }
+ }
+
+ return SourceEdit(start, end - start, '');
+}
+
+/// Performs the string operation on [yamlEdit] to achieve the effect of
+/// removing the [keyNode] from the map, bearing in mind that this is a flow
+/// map.
+SourceEdit _removeFromFlowMap(
+ YamlEditor yamlEdit, YamlMap map, YamlNode keyNode, YamlNode valueNode) {
+ var start = keyNode.span.start.offset;
+ var end = valueNode.span.end.offset;
+ final yaml = yamlEdit.toString();
+
+ if (deepEquals(keyNode, map.keys.first)) {
+ start = yaml.lastIndexOf('{', start - 1) + 1;
+
+ if (deepEquals(keyNode, map.keys.last)) {
+ end = yaml.indexOf('}', end);
+ } else {
+ end = yaml.indexOf(',', end) + 1;
+ }
+ } else {
+ start = yaml.lastIndexOf(',', start - 1);
+ }
+
+ return SourceEdit(start, end - start, '');
+}
diff --git a/pkgs/yaml_edit/lib/src/source_edit.dart b/pkgs/yaml_edit/lib/src/source_edit.dart
new file mode 100644
index 0000000..d177a19
--- /dev/null
+++ b/pkgs/yaml_edit/lib/src/source_edit.dart
@@ -0,0 +1,133 @@
+// Copyright (c) 2020, 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:meta/meta.dart';
+
+/// A class representing a change on a [String], intended to be compatible with
+/// `package:analysis_server`'s [SourceEdit].
+///
+/// For example, changing a string from
+/// ```
+/// foo: foobar
+/// ```
+/// to
+/// ```
+/// foo: barbar
+/// ```
+/// will be represented by
+/// `SourceEdit(offset: 4, length: 3, replacement: 'bar')`
+@sealed
+class SourceEdit {
+ /// The offset from the start of the string where the modification begins.
+ final int offset;
+
+ /// The length of the substring to be replaced.
+ final int length;
+
+ /// The replacement string to be used.
+ final String replacement;
+
+ /// Creates a new [SourceEdit] instance. [offset], [length] and [replacement]
+ /// must be non-null, and [offset] and [length] must be non-negative.
+ factory SourceEdit(int offset, int length, String replacement) =>
+ SourceEdit._(offset, length, replacement);
+
+ SourceEdit._(this.offset, this.length, this.replacement) {
+ RangeError.checkNotNegative(offset);
+ RangeError.checkNotNegative(length);
+ }
+
+ @override
+ bool operator ==(Object other) {
+ if (other is SourceEdit) {
+ return offset == other.offset &&
+ length == other.length &&
+ replacement == other.replacement;
+ }
+
+ return false;
+ }
+
+ @override
+ int get hashCode => offset.hashCode ^ length.hashCode ^ replacement.hashCode;
+
+ /// Constructs a SourceEdit from JSON.
+ ///
+ /// **Example:**
+ /// ```dart
+ /// final edit = {
+ /// 'offset': 1,
+ /// 'length': 2,
+ /// 'replacement': 'replacement string'
+ /// };
+ ///
+ /// final sourceEdit = SourceEdit.fromJson(edit);
+ /// ```
+ factory SourceEdit.fromJson(Map<String, dynamic> json) {
+ final offset = json['offset'];
+ final length = json['length'];
+ final replacement = json['replacement'];
+
+ if (offset is int && length is int && replacement is String) {
+ return SourceEdit(offset, length, replacement);
+ }
+
+ throw const FormatException('Invalid JSON passed to SourceEdit');
+ }
+
+ /// Encodes this object as JSON-compatible structure.
+ ///
+ /// **Example:**
+ /// ```dart
+ /// import 'dart:convert' show jsonEncode;
+ ///
+ /// final edit = SourceEdit(offset, length, 'replacement string');
+ /// final jsonString = jsonEncode(edit.toJson());
+ /// print(jsonString);
+ /// ```
+ Map<String, dynamic> toJson() {
+ return {'offset': offset, 'length': length, 'replacement': replacement};
+ }
+
+ @override
+ String toString() => 'SourceEdit($offset, $length, "$replacement")';
+
+ /// Applies a series of [SourceEdit]s to an original string, and return the
+ /// final output.
+ ///
+ /// [edits] should be in order i.e. the first [SourceEdit] in [edits] should
+ /// be the first edit applied to [original].
+ ///
+ /// **Example:**
+ /// ```dart
+ /// const original = 'YAML: YAML';
+ /// final sourceEdits = [
+ /// SourceEdit(6, 4, "YAML Ain't Markup Language"),
+ /// SourceEdit(6, 4, "YAML Ain't Markup Language"),
+ /// SourceEdit(0, 4, "YAML Ain't Markup Language")
+ /// ];
+ /// final result = SourceEdit.applyAll(original, sourceEdits);
+ /// ```
+ /// **Expected result:**
+ /// ```dart
+ /// "YAML Ain't Markup Language: YAML Ain't Markup Language Ain't Markup
+ /// Language"
+ /// ```
+ static String applyAll(String original, Iterable<SourceEdit> edits) {
+ return edits.fold(original, (current, edit) => edit.apply(current));
+ }
+
+ /// Applies one [SourceEdit]s to an original string, and return the final
+ /// output.
+ ///
+ /// **Example:**
+ /// ```dart
+ /// final edit = SourceEdit(4, 3, 'bar');
+ /// final originalString = 'foo: foobar';
+ /// print(edit.apply(originalString)); // 'foo: barbar'
+ /// ```
+ String apply(String original) {
+ return original.replaceRange(offset, offset + length, replacement);
+ }
+}
diff --git a/pkgs/yaml_edit/lib/src/strings.dart b/pkgs/yaml_edit/lib/src/strings.dart
new file mode 100644
index 0000000..1b85641
--- /dev/null
+++ b/pkgs/yaml_edit/lib/src/strings.dart
@@ -0,0 +1,366 @@
+// Copyright (c) 2020, 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:collection/collection.dart';
+import 'package:yaml/yaml.dart';
+
+import 'utils.dart';
+
+/// Given [value], tries to format it into a plain string recognizable by YAML.
+///
+/// Not all values can be formatted into a plain string. If the string contains
+/// an escape sequence, it can only be detected when in a double-quoted
+/// sequence. Plain strings may also be misinterpreted by the YAML parser (e.g.
+/// ' null').
+///
+/// Returns `null` if [value] cannot be encoded as a plain string.
+String? _tryYamlEncodePlain(String value) {
+ /// If it contains a dangerous character we want to wrap the result with
+ /// double quotes because the double quoted style allows for arbitrary
+ /// strings with "\" escape sequences.
+ ///
+ /// See 7.3.1 Double-Quoted Style
+ /// https://yaml.org/spec/1.2/spec.html#id2787109
+ return isDangerousString(value) ? null : value;
+}
+
+/// Checks if [string] has unprintable characters according to
+/// [unprintableCharCodes].
+bool _hasUnprintableCharacters(String string) {
+ final codeUnits = string.codeUnits;
+
+ for (final key in unprintableCharCodes.keys) {
+ if (codeUnits.contains(key)) return true;
+ }
+
+ return false;
+}
+
+/// Generates a YAML-safe double-quoted string based on [string], escaping the
+/// list of characters as defined by the YAML 1.2 spec.
+///
+/// See 5.7 Escaped Characters https://yaml.org/spec/1.2/spec.html#id2776092
+String _yamlEncodeDoubleQuoted(String string) {
+ final buffer = StringBuffer();
+ for (final codeUnit in string.codeUnits) {
+ if (doubleQuoteEscapeChars[codeUnit] != null) {
+ buffer.write(doubleQuoteEscapeChars[codeUnit]);
+ } else {
+ buffer.writeCharCode(codeUnit);
+ }
+ }
+
+ return '"$buffer"';
+}
+
+/// Encodes [string] as YAML single quoted string.
+///
+/// Returns `null`, if the [string] can't be encoded as single-quoted string.
+/// This might happen if it contains line-breaks or [_hasUnprintableCharacters].
+///
+/// See: https://yaml.org/spec/1.2.2/#732-single-quoted-style
+String? _tryYamlEncodeSingleQuoted(String string) {
+ // If [string] contains a newline we'll use double quoted strings instead.
+ // Single quoted strings can represent newlines, but then we have to use an
+ // empty line (replace \n with \n\n). But since leading spaces following
+ // line breaks are ignored, we can't represent "\n ".
+ // Thus, if the string contains `\n` and we're asked to do single quoted,
+ // we'll fallback to a double quoted string.
+ if (_hasUnprintableCharacters(string) || string.contains('\n')) return null;
+
+ final result = string.replaceAll('\'', '\'\'');
+ return '\'$result\'';
+}
+
+/// Attempts to encode a [string] as a _YAML folded string_ and apply the
+/// appropriate _chomping indicator_.
+///
+/// Returns `null`, if the [string] cannot be encoded as a _YAML folded
+/// string_.
+///
+/// **Examples** of folded strings.
+/// ```yaml
+/// # With the "strip" chomping indicator
+/// key: >-
+/// my folded
+/// string
+///
+/// # With the "keep" chomping indicator
+/// key: >+
+/// my folded
+/// string
+/// ```
+///
+/// See: https://yaml.org/spec/1.2.2/#813-folded-style
+String? _tryYamlEncodeFolded(String string, int indentSize, String lineEnding) {
+ // A string that starts with space or newline followed by space can't be
+ // encoded in folded mode.
+ if (string.isEmpty || string.trim().length != string.length) return null;
+
+ if (_hasUnprintableCharacters(string)) return null;
+
+ // TODO: Are there other strings we can't encode in folded mode?
+
+ final indent = ' ' * indentSize;
+
+ /// Remove trailing `\n` & white-space to ease string folding
+ var trimmed = string.trimRight();
+ final stripped = string.substring(trimmed.length);
+
+ final trimmedSplit =
+ trimmed.replaceAll('\n', lineEnding + indent).split(lineEnding);
+
+ /// Try folding to match specification:
+ /// * https://yaml.org/spec/1.2.2/#65-line-folding
+ trimmed = trimmedSplit.reduceIndexed((index, previous, current) {
+ var updated = current;
+
+ /// If initially empty, this line holds only `\n` or white-space. This
+ /// tells us we don't need to apply an additional `\n`.
+ ///
+ /// See https://yaml.org/spec/1.2.2/#64-empty-lines
+ ///
+ /// If this line is not empty, we need to apply an additional `\n` if and
+ /// only if:
+ /// 1. The preceding line was non-empty too
+ /// 2. If the current line doesn't begin with white-space
+ ///
+ /// Such that we apply `\n` for `foo\nbar` but not `foo\n bar`.
+ if (current.trim().isNotEmpty &&
+ trimmedSplit[index - 1].trim().isNotEmpty &&
+ !current.replaceFirst(indent, '').startsWith(' ')) {
+ updated = lineEnding + updated;
+ }
+
+ /// Apply a `\n` by default.
+ return previous + lineEnding + updated;
+ });
+
+ return '>-\n'
+ '$indent$trimmed'
+ '${stripped.replaceAll('\n', lineEnding + indent)}';
+}
+
+/// Attempts to encode a [string] as a _YAML literal string_ and apply the
+/// appropriate _chomping indicator_.
+///
+/// Returns `null`, if the [string] cannot be encoded as a _YAML literal
+/// string_.
+///
+/// **Examples** of literal strings.
+/// ```yaml
+/// # With the "strip" chomping indicator
+/// key: |-
+/// my literal
+/// string
+///
+/// # Without chomping indicator
+/// key: |
+/// my literal
+/// string
+/// ```
+///
+/// See: https://yaml.org/spec/1.2.2/#812-literal-style
+String? _tryYamlEncodeLiteral(
+ String string, int indentSize, String lineEnding) {
+ if (string.isEmpty || string.trim().length != string.length) return null;
+
+ // A string that starts with space or newline followed by space can't be
+ // encoded in literal mode.
+ if (_hasUnprintableCharacters(string)) return null;
+
+ // TODO: Are there other strings we can't encode in literal mode?
+
+ final indent = ' ' * indentSize;
+
+ /// Simplest block style.
+ /// * https://yaml.org/spec/1.2.2/#812-literal-style
+ return '|-\n$indent${string.replaceAll('\n', lineEnding + indent)}';
+}
+
+/// Encodes a flow [YamlScalar] based on the provided [YamlScalar.style].
+///
+/// Falls back to [ScalarStyle.DOUBLE_QUOTED] if the [yamlScalar] cannot be
+/// encoded with the [YamlScalar.style] or with [ScalarStyle.PLAIN] when the
+/// [yamlScalar] is not a [String].
+String _yamlEncodeFlowScalar(YamlScalar yamlScalar) {
+ final YamlScalar(:value, :style) = yamlScalar;
+
+ if (value is! String) {
+ return value.toString();
+ }
+
+ switch (style) {
+ /// Only encode as double-quoted if it's a string.
+ case ScalarStyle.DOUBLE_QUOTED:
+ return _yamlEncodeDoubleQuoted(value);
+
+ case ScalarStyle.SINGLE_QUOTED:
+ return _tryYamlEncodeSingleQuoted(value) ??
+ _yamlEncodeDoubleQuoted(value);
+
+ /// Cast into [String] if [null] as this condition only returns [null]
+ /// for a [String] that can't be encoded.
+ default:
+ return _tryYamlEncodePlain(value) ?? _yamlEncodeDoubleQuoted(value);
+ }
+}
+
+/// Encodes a block [YamlScalar] based on the provided [YamlScalar.style].
+///
+/// Falls back to [ScalarStyle.DOUBLE_QUOTED] if the [yamlScalar] cannot be
+/// encoded with the [YamlScalar.style] provided.
+String _yamlEncodeBlockScalar(
+ YamlScalar yamlScalar,
+ int indentation,
+ String lineEnding,
+) {
+ final YamlScalar(:value, :style) = yamlScalar;
+ assertValidScalar(value);
+
+ if (value is! String) {
+ return value.toString();
+ }
+
+ switch (style) {
+ /// Prefer 'plain', fallback to "double quoted"
+ case ScalarStyle.PLAIN:
+ return _tryYamlEncodePlain(value) ?? _yamlEncodeDoubleQuoted(value);
+
+ // Prefer 'single quoted', fallback to "double quoted"
+ case ScalarStyle.SINGLE_QUOTED:
+ return _tryYamlEncodeSingleQuoted(value) ??
+ _yamlEncodeDoubleQuoted(value);
+
+ /// Prefer folded string, fallback to "double quoted"
+ case ScalarStyle.FOLDED:
+ return _tryYamlEncodeFolded(value, indentation, lineEnding) ??
+ _yamlEncodeDoubleQuoted(value);
+
+ /// Prefer literal string, fallback to "double quoted"
+ case ScalarStyle.LITERAL:
+ return _tryYamlEncodeLiteral(value, indentation, lineEnding) ??
+ _yamlEncodeDoubleQuoted(value);
+
+ /// Prefer plain, fallback to "double quoted"
+ default:
+ return _tryYamlEncodePlain(value) ?? _yamlEncodeDoubleQuoted(value);
+ }
+}
+
+/// Returns [value] with the necessary formatting applied in a flow context.
+///
+/// If [value] is a [YamlNode], we try to respect its [YamlScalar.style]
+/// parameter where possible. Certain cases make this impossible (e.g. a plain
+/// string scalar that starts with '>', a child having a block style
+/// parameters), in which case we will produce [value] with default styling
+/// options.
+String yamlEncodeFlow(YamlNode value) {
+ if (value is YamlList) {
+ final list = value.nodes;
+
+ final safeValues = list.map(yamlEncodeFlow);
+ return '[${safeValues.join(', ')}]';
+ } else if (value is YamlMap) {
+ final safeEntries = value.nodes.entries.map((entry) {
+ final safeKey = yamlEncodeFlow(entry.key as YamlNode);
+ final safeValue = yamlEncodeFlow(entry.value);
+ return '$safeKey: $safeValue';
+ });
+
+ return '{${safeEntries.join(', ')}}';
+ }
+
+ return _yamlEncodeFlowScalar(value as YamlScalar);
+}
+
+/// Returns [value] with the necessary formatting applied in a block context.
+String yamlEncodeBlock(
+ YamlNode value,
+ int indentation,
+ String lineEnding,
+) {
+ const additionalIndentation = 2;
+
+ if (!isBlockNode(value)) return yamlEncodeFlow(value);
+
+ final newIndentation = indentation + additionalIndentation;
+
+ if (value is YamlList) {
+ if (value.isEmpty) return '${' ' * indentation}[]';
+
+ Iterable<String> safeValues;
+
+ final children = value.nodes;
+
+ safeValues = children.map((child) {
+ var valueString = yamlEncodeBlock(child, newIndentation, lineEnding);
+ if (isCollection(child) && !isFlowYamlCollectionNode(child)) {
+ valueString = valueString.substring(newIndentation);
+ }
+
+ return '${' ' * indentation}- $valueString';
+ });
+
+ return safeValues.join(lineEnding);
+ } else if (value is YamlMap) {
+ if (value.isEmpty) return '${' ' * indentation}{}';
+
+ return value.nodes.entries.map((entry) {
+ final MapEntry(:key, :value) = entry;
+
+ final safeKey = yamlEncodeFlow(key as YamlNode);
+ final formattedKey = ' ' * indentation + safeKey;
+
+ final formattedValue = yamlEncodeBlock(
+ value,
+ newIndentation,
+ lineEnding,
+ );
+
+ /// Empty collections are always encoded in flow-style, so new-line must
+ /// be avoided
+ if (isCollection(value) && !isEmpty(value)) {
+ return '$formattedKey:$lineEnding$formattedValue';
+ }
+
+ return '$formattedKey: $formattedValue';
+ }).join(lineEnding);
+ }
+
+ return _yamlEncodeBlockScalar(
+ value as YamlScalar,
+ newIndentation,
+ lineEnding,
+ );
+}
+
+/// List of unprintable characters.
+///
+/// See 5.7 Escape Characters https://yaml.org/spec/1.2/spec.html#id2776092
+final Map<int, String> unprintableCharCodes = {
+ 0: '\\0', // Escaped ASCII null (#x0) character.
+ 7: '\\a', // Escaped ASCII bell (#x7) character.
+ 8: '\\b', // Escaped ASCII backspace (#x8) character.
+ 11: '\\v', // Escaped ASCII vertical tab (#xB) character.
+ 12: '\\f', // Escaped ASCII form feed (#xC) character.
+ 13: '\\r', // Escaped ASCII carriage return (#xD) character. Line Break.
+ 27: '\\e', // Escaped ASCII escape (#x1B) character.
+ 133: '\\N', // Escaped Unicode next line (#x85) character.
+ 160: '\\_', // Escaped Unicode non-breaking space (#xA0) character.
+ 8232: '\\L', // Escaped Unicode line separator (#x2028) character.
+ 8233: '\\P', // Escaped Unicode paragraph separator (#x2029) character.
+};
+
+/// List of escape characters.
+///
+/// See 5.7 Escape Characters https://yaml.org/spec/1.2/spec.html#id2776092
+final Map<int, String> doubleQuoteEscapeChars = {
+ ...unprintableCharCodes,
+ 9: '\\t', // Escaped ASCII horizontal tab (#x9) character. Printable
+ 10: '\\n', // Escaped ASCII line feed (#xA) character. Line Break.
+ 34: '\\"', // Escaped ASCII double quote (#x22).
+ 47: '\\/', // Escaped ASCII slash (#x2F), for JSON compatibility.
+ 92: '\\\\', // Escaped ASCII back slash (#x5C).
+};
diff --git a/pkgs/yaml_edit/lib/src/utils.dart b/pkgs/yaml_edit/lib/src/utils.dart
new file mode 100644
index 0000000..ef85526
--- /dev/null
+++ b/pkgs/yaml_edit/lib/src/utils.dart
@@ -0,0 +1,291 @@
+// Copyright (c) 2020, 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_span/source_span.dart';
+import 'package:yaml/yaml.dart';
+
+import 'editor.dart';
+import 'wrap.dart';
+
+/// Invoke [fn] while setting [yamlWarningCallback] to [warn], and restore
+/// [YamlWarningCallback] after [fn] returns.
+///
+/// Defaults to a [warn] function that ignores all warnings.
+T withYamlWarningCallback<T>(
+ T Function() fn, {
+ YamlWarningCallback warn = _ignoreWarning,
+}) {
+ final original = yamlWarningCallback;
+ try {
+ yamlWarningCallback = warn;
+ return fn();
+ } finally {
+ yamlWarningCallback = original;
+ }
+}
+
+void _ignoreWarning(String warning, [SourceSpan? span]) {/* ignore warning */}
+
+/// Determines if [string] is dangerous by checking if parsing the plain string
+/// can return a result different from [string].
+///
+/// This function is also capable of detecting if non-printable characters are
+/// in [string].
+bool isDangerousString(String string) {
+ try {
+ final node = withYamlWarningCallback(() => loadYamlNode(string));
+ if (node.value != string) {
+ return true;
+ }
+
+ // [string] should also not contain the `[`, `]`, `,`, `{` and `}` indicator
+ // characters.
+ return string.contains(RegExp(r'\{|\[|\]|\}|,'));
+ } catch (e) {
+ /// This catch statement catches [ArgumentError] in `loadYamlNode` when
+ /// a string can be interpreted as a URI tag, but catches for other
+ /// [YamlException]s
+ return true;
+ }
+}
+
+/// Asserts that [value] is a valid scalar according to YAML.
+///
+/// A valid scalar is a number, String, boolean, or null.
+void assertValidScalar(Object? value) {
+ if (value is num || value is String || value is bool || value == null) {
+ return;
+ }
+
+ throw ArgumentError.value(value, 'value', 'Not a valid scalar type!');
+}
+
+/// Checks if [node] is a [YamlNode] with block styling.
+///
+/// [ScalarStyle.ANY] and [CollectionStyle.ANY] are considered to be block
+/// styling by default for maximum flexibility.
+bool isBlockNode(YamlNode node) {
+ if (node is YamlScalar) {
+ if (node.style == ScalarStyle.LITERAL ||
+ node.style == ScalarStyle.FOLDED ||
+ node.style == ScalarStyle.ANY) {
+ return true;
+ }
+ }
+
+ if (node is YamlList &&
+ (node.style == CollectionStyle.BLOCK ||
+ node.style == CollectionStyle.ANY)) {
+ return true;
+ }
+ if (node is YamlMap &&
+ (node.style == CollectionStyle.BLOCK ||
+ node.style == CollectionStyle.ANY)) {
+ return true;
+ }
+
+ return false;
+}
+
+/// Returns the content sensitive ending offset of [yamlNode] (i.e. where the
+/// last meaningful content happens)
+int getContentSensitiveEnd(YamlNode yamlNode) {
+ if (yamlNode is YamlList) {
+ if (yamlNode.style == CollectionStyle.FLOW) {
+ return yamlNode.span.end.offset;
+ } else {
+ return getContentSensitiveEnd(yamlNode.nodes.last);
+ }
+ } else if (yamlNode is YamlMap) {
+ if (yamlNode.style == CollectionStyle.FLOW) {
+ return yamlNode.span.end.offset;
+ } else {
+ return getContentSensitiveEnd(yamlNode.nodes.values.last);
+ }
+ }
+
+ return yamlNode.span.end.offset;
+}
+
+/// Checks if the item is a Map or a List
+bool isCollection(Object item) => item is Map || item is List;
+
+/// Checks if [index] is [int], >=0, < [length]
+bool isValidIndex(Object? index, int length) {
+ return index is int && index >= 0 && index < length;
+}
+
+/// Checks if the item is empty, if it is a List or a Map.
+///
+/// Returns `false` if [item] is not a List or Map.
+bool isEmpty(Object item) {
+ if (item is Map) return item.isEmpty;
+ if (item is List) return item.isEmpty;
+
+ return false;
+}
+
+/// Creates a [SourceSpan] from [sourceUrl] with no meaningful location
+/// information.
+///
+/// Mainly used with [wrapAsYamlNode] to allow for a reasonable
+/// implementation of [SourceSpan.message].
+SourceSpan shellSpan(Object? sourceUrl) {
+ final shellSourceLocation = SourceLocation(0, sourceUrl: sourceUrl);
+ return SourceSpanBase(shellSourceLocation, shellSourceLocation, '');
+}
+
+/// Returns if [value] is a [YamlList] or [YamlMap] with [CollectionStyle.FLOW].
+bool isFlowYamlCollectionNode(Object value) =>
+ value is YamlNode && value.collectionStyle == CollectionStyle.FLOW;
+
+/// Determines the index where [newKey] will be inserted if the keys in [map]
+/// are in alphabetical order when converted to strings.
+///
+/// Returns the length of [map] if the keys in [map] are not in alphabetical
+/// order.
+int getMapInsertionIndex(YamlMap map, Object newKey) {
+ final keys = map.nodes.keys.map((k) => k.toString()).toList();
+
+ // We can't deduce ordering if list is empty, so then we just we just append
+ if (keys.length <= 1) {
+ return map.length;
+ }
+
+ for (var i = 1; i < keys.length; i++) {
+ if (keys[i].compareTo(keys[i - 1]) < 0) {
+ return map.length;
+ }
+ }
+
+ final insertionIndex =
+ keys.indexWhere((key) => key.compareTo(newKey as String) > 0);
+
+ if (insertionIndex != -1) return insertionIndex;
+
+ return map.length;
+}
+
+/// Returns the detected indentation step used in [editor], or defaults to a
+/// value of `2` if no indentation step can be detected.
+///
+/// Indentation step is determined by the difference in indentation of the
+/// first block-styled yaml collection in the second level as compared to the
+/// top-level elements. In the case where there are multiple possible
+/// candidates, we choose the candidate closest to the start of [editor].
+int getIndentation(YamlEditor editor) {
+ final node = editor.parseAt([]);
+ Iterable<YamlNode>? children;
+ var indentation = 2;
+
+ if (node is YamlMap && node.style == CollectionStyle.BLOCK) {
+ children = node.nodes.values;
+ } else if (node is YamlList && node.style == CollectionStyle.BLOCK) {
+ children = node.nodes;
+ }
+
+ if (children != null) {
+ for (final child in children) {
+ var indent = 0;
+ if (child is YamlList) {
+ indent = getListIndentation(editor.toString(), child);
+ } else if (child is YamlMap) {
+ indent = getMapIndentation(editor.toString(), child);
+ }
+
+ if (indent != 0) indentation = indent;
+ }
+ }
+ return indentation;
+}
+
+/// Gets the indentation level of [list]. This is 0 if it is a flow list,
+/// but returns the number of spaces before the hyphen of elements for
+/// block lists.
+///
+/// Throws [UnsupportedError] if an empty block map is passed in.
+int getListIndentation(String yaml, YamlList list) {
+ if (list.style == CollectionStyle.FLOW) return 0;
+
+ /// An empty block map doesn't really exist.
+ if (list.isEmpty) {
+ throw UnsupportedError('Unable to get indentation for empty block list');
+ }
+
+ final lastSpanOffset = list.nodes.last.span.start.offset;
+ final lastHyphen = yaml.lastIndexOf('-', lastSpanOffset - 1);
+
+ if (lastHyphen == 0) return lastHyphen;
+
+ // Look for '\n' that's before hyphen
+ final lastNewLine = yaml.lastIndexOf('\n', lastHyphen - 1);
+
+ return lastHyphen - lastNewLine - 1;
+}
+
+/// Gets the indentation level of [map]. This is 0 if it is a flow map,
+/// but returns the number of spaces before the keys for block maps.
+int getMapIndentation(String yaml, YamlMap map) {
+ if (map.style == CollectionStyle.FLOW) return 0;
+
+ /// An empty block map doesn't really exist.
+ if (map.isEmpty) {
+ throw UnsupportedError('Unable to get indentation for empty block map');
+ }
+
+ /// Use the number of spaces between the last key and the newline as
+ /// indentation.
+ final lastKey = map.nodes.keys.last as YamlNode;
+ final lastSpanOffset = lastKey.span.start.offset;
+ final lastNewLine = yaml.lastIndexOf('\n', lastSpanOffset);
+ final lastQuestionMark = yaml.lastIndexOf('?', lastSpanOffset);
+
+ if (lastQuestionMark == -1) {
+ if (lastNewLine == -1) return lastSpanOffset;
+ return lastSpanOffset - lastNewLine - 1;
+ }
+
+ /// If there is a question mark, it might be a complex key. Check if it
+ /// is on the same line as the key node to verify.
+ if (lastNewLine == -1) return lastQuestionMark;
+ if (lastQuestionMark > lastNewLine) {
+ return lastQuestionMark - lastNewLine - 1;
+ }
+
+ return lastSpanOffset - lastNewLine - 1;
+}
+
+/// Returns the detected line ending used in [yaml], more specifically, whether
+/// [yaml] appears to use Windows `\r\n` or Unix `\n` line endings.
+///
+/// The heuristic used is to count all `\n` in the text and if strictly more
+/// than half of them are preceded by `\r` we report that windows line endings
+/// are used.
+String getLineEnding(String yaml) {
+ var index = -1;
+ var unixNewlines = 0;
+ var windowsNewlines = 0;
+ while ((index = yaml.indexOf('\n', index + 1)) != -1) {
+ if (index != 0 && yaml[index - 1] == '\r') {
+ windowsNewlines++;
+ } else {
+ unixNewlines++;
+ }
+ }
+
+ return windowsNewlines > unixNewlines ? '\r\n' : '\n';
+}
+
+extension YamlNodeExtension on YamlNode {
+ /// Returns the [CollectionStyle] of `this` if `this` is [YamlMap] or
+ /// [YamlList].
+ ///
+ /// Otherwise, returns `null`.
+ CollectionStyle? get collectionStyle {
+ final me = this;
+ if (me is YamlMap) return me.style;
+ if (me is YamlList) return me.style;
+ return null;
+ }
+}
diff --git a/pkgs/yaml_edit/lib/src/wrap.dart b/pkgs/yaml_edit/lib/src/wrap.dart
new file mode 100644
index 0000000..73f7751
--- /dev/null
+++ b/pkgs/yaml_edit/lib/src/wrap.dart
@@ -0,0 +1,216 @@
+// Copyright (c) 2020, 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:collection' as collection;
+
+import 'package:collection/collection.dart';
+import 'package:source_span/source_span.dart';
+import 'package:yaml/yaml.dart';
+
+import 'equality.dart';
+import 'utils.dart';
+
+/// Returns a new [YamlMap] constructed by applying [update] onto the nodes of
+/// this [YamlMap].
+YamlMap updatedYamlMap(YamlMap map, Function(Map) update) {
+ final dummyMap = deepEqualsMap();
+ dummyMap.addAll(map.nodes);
+
+ update(dummyMap);
+
+ return wrapAsYamlNode(dummyMap) as YamlMap;
+}
+
+/// Wraps [value] into a [YamlNode].
+///
+/// [Map]s, [List]s and Scalars will be wrapped as [YamlMap]s, [YamlList]s,
+/// and [YamlScalar]s respectively. If [collectionStyle]/[scalarStyle] is
+/// defined, and [value] is a collection or scalar, the wrapped [YamlNode] will
+/// have the respective style, otherwise it defaults to the ANY style.
+///
+/// If [value] is a [Map] or [List], then [wrapAsYamlNode] will be called
+/// recursively on all children, and [collectionStyle]/[scalarStyle] will be
+/// applied to any children that are not instances of [YamlNode].
+///
+/// If a [YamlNode] is passed in, no further wrapping will be done, and the
+/// [collectionStyle]/[scalarStyle] will not be applied.
+YamlNode wrapAsYamlNode(
+ Object? value, {
+ CollectionStyle collectionStyle = CollectionStyle.ANY,
+ ScalarStyle scalarStyle = ScalarStyle.ANY,
+}) {
+ if (value is YamlScalar) {
+ assertValidScalar(value.value);
+ return value;
+ } else if (value is YamlList) {
+ for (final item in value.nodes) {
+ wrapAsYamlNode(item);
+ }
+
+ return value;
+ } else if (value is YamlMap) {
+ /// Both [entry.key] and [entry.values] are guaranteed to be [YamlNode]s,
+ /// so running this will just assert that they are valid scalars.
+ for (final entry in value.nodes.entries) {
+ wrapAsYamlNode(entry.key);
+ wrapAsYamlNode(entry.value);
+ }
+
+ return value;
+ } else if (value is Map) {
+ return YamlMapWrap(
+ value,
+ collectionStyle: collectionStyle,
+ scalarStyle: scalarStyle,
+ );
+ } else if (value is List) {
+ return YamlListWrap(
+ value,
+ collectionStyle: collectionStyle,
+ scalarStyle: scalarStyle,
+ );
+ } else {
+ assertValidScalar(value);
+
+ return YamlScalarWrap(value, style: scalarStyle);
+ }
+}
+
+/// Internal class that allows us to define a constructor on [YamlScalar]
+/// which takes in [style] as an argument.
+class YamlScalarWrap implements YamlScalar {
+ /// The [ScalarStyle] to be used for the scalar.
+ @override
+ final ScalarStyle style;
+
+ @override
+ final SourceSpan span;
+
+ @override
+ final dynamic value;
+
+ YamlScalarWrap(this.value, {this.style = ScalarStyle.ANY, Object? sourceUrl})
+ : span = shellSpan(sourceUrl);
+
+ @override
+ String toString() => value.toString();
+}
+
+/// Internal class that allows us to define a constructor on [YamlMap]
+/// which takes in [style] as an argument.
+class YamlMapWrap
+ with collection.MapMixin, UnmodifiableMapMixin
+ implements YamlMap {
+ /// The [CollectionStyle] to be used for the map.
+ @override
+ final CollectionStyle style;
+
+ @override
+ final Map<dynamic, YamlNode> nodes;
+
+ @override
+ final SourceSpan span;
+
+ factory YamlMapWrap(
+ Map dartMap, {
+ CollectionStyle collectionStyle = CollectionStyle.ANY,
+ ScalarStyle scalarStyle = ScalarStyle.ANY,
+ Object? sourceUrl,
+ }) {
+ final wrappedMap = deepEqualsMap<dynamic, YamlNode>();
+
+ for (final entry in dartMap.entries) {
+ final wrappedKey = wrapAsYamlNode(
+ entry.key,
+ collectionStyle: collectionStyle,
+ scalarStyle: scalarStyle,
+ );
+ final wrappedValue = wrapAsYamlNode(
+ entry.value,
+ collectionStyle: collectionStyle,
+ scalarStyle: scalarStyle,
+ );
+ wrappedMap[wrappedKey] = wrappedValue;
+ }
+
+ return YamlMapWrap._(
+ wrappedMap,
+ style: collectionStyle,
+ sourceUrl: sourceUrl,
+ );
+ }
+
+ YamlMapWrap._(
+ this.nodes, {
+ CollectionStyle style = CollectionStyle.ANY,
+ Object? sourceUrl,
+ }) : span = shellSpan(sourceUrl),
+ style = nodes.isEmpty ? CollectionStyle.FLOW : style;
+
+ @override
+ dynamic operator [](Object? key) => nodes[key]?.value;
+
+ @override
+ Iterable get keys => nodes.keys.map((node) => (node as YamlNode).value);
+
+ @override
+ Map get value => this;
+}
+
+/// Internal class that allows us to define a constructor on [YamlList]
+/// which takes in [style] as an argument.
+class YamlListWrap with collection.ListMixin implements YamlList {
+ /// The [CollectionStyle] to be used for the list.
+ @override
+ final CollectionStyle style;
+
+ @override
+ final List<YamlNode> nodes;
+
+ @override
+ final SourceSpan span;
+
+ @override
+ int get length => nodes.length;
+
+ @override
+ set length(int index) {
+ throw UnsupportedError('Cannot modify an unmodifiable List');
+ }
+
+ factory YamlListWrap(
+ List dartList, {
+ CollectionStyle collectionStyle = CollectionStyle.ANY,
+ ScalarStyle scalarStyle = ScalarStyle.ANY,
+ Object? sourceUrl,
+ }) {
+ return YamlListWrap._(
+ dartList
+ .map((v) => wrapAsYamlNode(
+ v,
+ collectionStyle: collectionStyle,
+ scalarStyle: scalarStyle,
+ ))
+ .toList(),
+ style: collectionStyle,
+ sourceUrl: sourceUrl,
+ );
+ }
+
+ YamlListWrap._(this.nodes,
+ {CollectionStyle style = CollectionStyle.ANY, Object? sourceUrl})
+ : span = shellSpan(sourceUrl),
+ style = nodes.isEmpty ? CollectionStyle.FLOW : style;
+
+ @override
+ dynamic operator [](int index) => nodes[index].value;
+
+ @override
+ void operator []=(int index, Object? value) {
+ throw UnsupportedError('Cannot modify an unmodifiable List');
+ }
+
+ @override
+ List get value => this;
+}
diff --git a/pkgs/yaml_edit/lib/yaml_edit.dart b/pkgs/yaml_edit/lib/yaml_edit.dart
new file mode 100644
index 0000000..49558b2
--- /dev/null
+++ b/pkgs/yaml_edit/lib/yaml_edit.dart
@@ -0,0 +1,28 @@
+// Copyright (c) 2020, 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.
+
+/// YAML parsing is supported by `package:yaml`, and each time a change is
+/// made, the resulting YAML AST is compared against our expected output
+/// with deep equality to ensure that the output conforms to our expectations.
+///
+/// **Example**
+/// ```dart
+/// import 'package:yaml_edit/yaml_edit.dart';
+///
+/// void main() {
+/// final yamlEditor = YamlEditor('{YAML: YAML}');
+/// yamlEditor.update(['YAML'], "YAML Ain't Markup Language");
+/// print(yamlEditor);
+/// // Expected Output:
+/// // {YAML: YAML Ain't Markup Language}
+/// }
+/// ```
+///
+/// [1]: https://yaml.org/
+library;
+
+export 'src/editor.dart';
+export 'src/errors.dart' show AliasException;
+export 'src/source_edit.dart';
+export 'src/wrap.dart' show wrapAsYamlNode;
diff --git a/pkgs/yaml_edit/pubspec.yaml b/pkgs/yaml_edit/pubspec.yaml
new file mode 100644
index 0000000..8127a12
--- /dev/null
+++ b/pkgs/yaml_edit/pubspec.yaml
@@ -0,0 +1,25 @@
+name: yaml_edit
+version: 2.2.2
+description: >-
+ A library for YAML manipulation with comment and whitespace preservation.
+repository: https://github.com/dart-lang/tools/tree/main/pkgs/yaml_edit
+
+issue_tracker: https://github.com/dart-lang/yaml_edit/issues
+
+topics:
+ - yaml
+
+environment:
+ sdk: ^3.1.0
+
+dependencies:
+ collection: ^1.15.0
+ meta: ^1.7.0
+ source_span: ^1.8.1
+ yaml: ^3.1.0
+
+dev_dependencies:
+ coverage: any # we only need format_coverage, don't care what version
+ dart_flutter_team_lints: ^3.0.0
+ path: ^1.8.0
+ test: ^1.17.12
diff --git a/pkgs/yaml_edit/test/alias_test.dart b/pkgs/yaml_edit/test/alias_test.dart
new file mode 100644
index 0000000..acc0df7
--- /dev/null
+++ b/pkgs/yaml_edit/test/alias_test.dart
@@ -0,0 +1,139 @@
+// Copyright (c) 2020, 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 'package:yaml_edit/yaml_edit.dart';
+
+import 'test_utils.dart';
+
+/// This test suite is a temporary measure until we are able to better handle
+/// aliases.
+void main() {
+ group('list ', () {
+ test('removing an alias anchor results in AliasError', () {
+ final doc = YamlEditor('''
+- &SS Sammy Sosa
+- *SS
+''');
+ expect(() => doc.remove([0]), throwsAliasException);
+ });
+
+ test('removing an alias reference results in AliasError', () {
+ final doc = YamlEditor('''
+- &SS Sammy Sosa
+- *SS
+''');
+
+ expect(() => doc.remove([1]), throwsAliasException);
+ });
+
+ test('it is okay to remove a non-alias node', () {
+ final doc = YamlEditor('''
+- &SS Sammy Sosa
+- *SS
+- Sammy Sosa
+''');
+
+ doc.remove([2]);
+ expect(doc.toString(), equals('''
+- &SS Sammy Sosa
+- *SS
+'''));
+ });
+ });
+
+ group('map', () {
+ test('removing an alias anchor value results in AliasError', () {
+ final doc = YamlEditor('''
+a: &SS Sammy Sosa
+b: *SS
+''');
+
+ expect(() => doc.remove(['a']), throwsAliasException);
+ });
+
+ test('removing an alias reference value results in AliasError', () {
+ final doc = YamlEditor('''
+a: &SS Sammy Sosa
+b: *SS
+''');
+
+ expect(() => doc.remove(['b']), throwsAliasException);
+ });
+
+ test('removing an alias anchor key results in AliasError', () {
+ final doc = YamlEditor('''
+&SS Sammy Sosa: a
+b: *SS
+''');
+
+ expect(() => doc.remove(['Sammy Sosa']), throwsAliasException);
+ });
+
+ test('removing an alias reference key results in AliasError', () {
+ final doc = YamlEditor('''
+a: &SS Sammy Sosa
+*SS : b
+''');
+
+ expect(() => doc.remove(['Sammy Sosa']), throwsAliasException);
+ });
+
+ test('it is okay to remove a non-alias node', () {
+ final doc = YamlEditor('''
+a: &SS Sammy Sosa
+b: *SS
+c: Sammy Sosa
+''');
+
+ doc.remove(['c']);
+ expect(doc.toString(), equals('''
+a: &SS Sammy Sosa
+b: *SS
+'''));
+ });
+ });
+
+ group('nested alias', () {
+ test('nested list alias anchors are detected too', () {
+ final doc = YamlEditor('''
+-
+ - &SS Sammy Sosa
+- *SS
+''');
+
+ expect(() => doc.remove([0]), throwsAliasException);
+ });
+
+ test('nested list alias references are detected too', () {
+ final doc = YamlEditor('''
+- &SS Sammy Sosa
+-
+ - *SS
+''');
+
+ expect(() => doc.remove([1]), throwsAliasException);
+ });
+
+ test('removing nested map alias anchor results in AliasError', () {
+ final doc = YamlEditor('''
+a:
+ c: &SS Sammy Sosa
+b: *SS
+''');
+
+ expect(() => doc.remove(['a']), throwsAliasException);
+ });
+
+ test('removing nested map alias reference results in AliasError', () {
+ final doc = YamlEditor('''
+a: &SS Sammy Sosa
+b:
+ c: *SS
+''');
+
+ expect(() => doc.remove(['b']), throwsAliasException);
+ });
+ });
+}
diff --git a/pkgs/yaml_edit/test/append_test.dart b/pkgs/yaml_edit/test/append_test.dart
new file mode 100644
index 0000000..cb705ed
--- /dev/null
+++ b/pkgs/yaml_edit/test/append_test.dart
@@ -0,0 +1,269 @@
+// Copyright (c) 2020, 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 'package:yaml_edit/yaml_edit.dart';
+
+import 'test_utils.dart';
+
+void main() {
+ group('throws PathError', () {
+ test('if it is a map', () {
+ final doc = YamlEditor('a:1');
+ expect(() => doc.appendToList([], 4), throwsPathError);
+ });
+
+ test('if it is a scalar', () {
+ final doc = YamlEditor('1');
+ expect(() => doc.appendToList([], 4), throwsPathError);
+ });
+ });
+
+ group('block list', () {
+ test('(1)', () {
+ final doc = YamlEditor('''
+- 0
+- 1
+- 2
+- 3
+''');
+ doc.appendToList([], 4);
+ expect(doc.toString(), equals('''
+- 0
+- 1
+- 2
+- 3
+- 4
+'''));
+ expectYamlBuilderValue(doc, [0, 1, 2, 3, 4]);
+ });
+
+ test('null path', () {
+ final doc = YamlEditor('''
+~:
+ - 0
+ - 1
+ - 2
+ - 3
+''');
+ doc.appendToList([null], 4);
+ expect(doc.toString(), equals('''
+~:
+ - 0
+ - 1
+ - 2
+ - 3
+ - 4
+'''));
+ expectYamlBuilderValue(doc, {
+ null: [0, 1, 2, 3, 4]
+ });
+ });
+
+ test('element to simple block list ', () {
+ final doc = YamlEditor('''
+- 0
+- 1
+- 2
+- 3
+''');
+ doc.appendToList([], [4, 5, 6]);
+ expect(doc.toString(), equals('''
+- 0
+- 1
+- 2
+- 3
+- - 4
+ - 5
+ - 6
+'''));
+ expectYamlBuilderValue(doc, [
+ 0,
+ 1,
+ 2,
+ 3,
+ [4, 5, 6]
+ ]);
+ });
+
+ test('nested', () {
+ final doc = YamlEditor('''
+- 0
+- - 1
+ - 2
+''');
+ doc.appendToList([1], 3);
+ expect(doc.toString(), equals('''
+- 0
+- - 1
+ - 2
+ - 3
+'''));
+ expectYamlBuilderValue(doc, [
+ 0,
+ [1, 2, 3]
+ ]);
+ });
+
+ test('block list element to nested block list ', () {
+ final doc = YamlEditor('''
+- 0
+- - 1
+ - 2
+''');
+ doc.appendToList([1], [3, 4, 5]);
+
+ expect(doc.toString(), equals('''
+- 0
+- - 1
+ - 2
+ - - 3
+ - 4
+ - 5
+'''));
+ expectYamlBuilderValue(doc, [
+ 0,
+ [
+ 1,
+ 2,
+ [3, 4, 5]
+ ]
+ ]);
+ });
+
+ test('nested', () {
+ final yamlEditor = YamlEditor('''
+a:
+ 1:
+ - null
+ 2: null
+''');
+ yamlEditor.appendToList(['a', 1], false);
+
+ expect(yamlEditor.toString(), equals('''
+a:
+ 1:
+ - null
+ - false
+ 2: null
+'''));
+ });
+
+ test('block append (1)', () {
+ final yamlEditor = YamlEditor('''
+# comment
+- z:
+ x: 1
+ y: 2
+- z:
+ x: 3
+ y: 4
+''');
+ yamlEditor.appendToList([], {
+ 'z': {'x': 5, 'y': 6}
+ });
+
+ expect(yamlEditor.toString(), equals('''
+# comment
+- z:
+ x: 1
+ y: 2
+- z:
+ x: 3
+ y: 4
+- z:
+ x: 5
+ y: 6
+'''));
+ });
+
+ test('block append (2)', () {
+ final yamlEditor = YamlEditor('''
+# comment
+a:
+ - z:
+ x: 1
+ y: 2
+ - z:
+ x: 3
+ y: 4
+b:
+ - w:
+ m: 2
+ n: 4
+''');
+ yamlEditor.appendToList([
+ 'a'
+ ], {
+ 'z': {'x': 5, 'y': 6}
+ });
+
+ expect(yamlEditor.toString(), equals('''
+# comment
+a:
+ - z:
+ x: 1
+ y: 2
+ - z:
+ x: 3
+ y: 4
+ - z:
+ x: 5
+ y: 6
+b:
+ - w:
+ m: 2
+ n: 4
+'''));
+ });
+
+ test('block append nested and with comments', () {
+ final yamlEditor = YamlEditor('''
+a:
+ b:
+ - c:
+ d: 1
+ - c:
+ d: 2
+# comment
+ e:
+ - g:
+ e: 1
+ f: 2
+# comment
+''');
+ expect(
+ () => yamlEditor.appendToList([
+ 'a',
+ 'e'
+ ], {
+ 'g': {'e': 3, 'f': 4}
+ }),
+ returnsNormally);
+ });
+ });
+
+ group('flow list', () {
+ test('(1)', () {
+ final doc = YamlEditor('[0, 1, 2]');
+ doc.appendToList([], 3);
+ expect(doc.toString(), equals('[0, 1, 2, 3]'));
+ expectYamlBuilderValue(doc, [0, 1, 2, 3]);
+ });
+
+ test('null value', () {
+ final doc = YamlEditor('[0, 1, 2]');
+ doc.appendToList([], null);
+ expect(doc.toString(), equals('[0, 1, 2, null]'));
+ expectYamlBuilderValue(doc, [0, 1, 2, null]);
+ });
+
+ test('empty ', () {
+ final doc = YamlEditor('[]');
+ doc.appendToList([], 0);
+ expect(doc.toString(), equals('[0]'));
+ expectYamlBuilderValue(doc, [0]);
+ });
+ });
+}
diff --git a/pkgs/yaml_edit/test/editor_test.dart b/pkgs/yaml_edit/test/editor_test.dart
new file mode 100644
index 0000000..b0a0081
--- /dev/null
+++ b/pkgs/yaml_edit/test/editor_test.dart
@@ -0,0 +1,56 @@
+// Copyright (c) 2020, 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 'package:yaml_edit/yaml_edit.dart';
+
+void main() {
+ group('YamlEditor records edits', () {
+ test('returns empty list at start', () {
+ final yamlEditor = YamlEditor('YAML: YAML');
+
+ expect(yamlEditor.edits, []);
+ });
+
+ test('after one change', () {
+ final yamlEditor = YamlEditor('YAML: YAML');
+ yamlEditor.update(['YAML'], "YAML Ain't Markup Language");
+
+ expect(
+ yamlEditor.edits, [SourceEdit(5, 5, " YAML Ain't Markup Language")]);
+ });
+
+ test('after multiple changes', () {
+ final yamlEditor = YamlEditor('YAML: YAML');
+ yamlEditor.update(['YAML'], "YAML Ain't Markup Language");
+ yamlEditor.update(['XML'], 'Extensible Markup Language');
+ yamlEditor.remove(['YAML']);
+
+ expect(yamlEditor.edits, [
+ SourceEdit(5, 5, " YAML Ain't Markup Language"),
+ SourceEdit(32, 0, '\nXML: Extensible Markup Language\n'),
+ SourceEdit(0, 33, '')
+ ]);
+ });
+
+ test('that do not automatically update with internal list', () {
+ final yamlEditor = YamlEditor('YAML: YAML');
+ yamlEditor.update(['YAML'], "YAML Ain't Markup Language");
+
+ final firstEdits = yamlEditor.edits;
+
+ expect(firstEdits, [SourceEdit(5, 5, " YAML Ain't Markup Language")]);
+
+ yamlEditor.update(['XML'], 'Extensible Markup Language');
+ yamlEditor.remove(['YAML']);
+
+ expect(firstEdits, [SourceEdit(5, 5, " YAML Ain't Markup Language")]);
+ expect(yamlEditor.edits, [
+ SourceEdit(5, 5, " YAML Ain't Markup Language"),
+ SourceEdit(32, 0, '\nXML: Extensible Markup Language\n'),
+ SourceEdit(0, 33, '')
+ ]);
+ });
+ });
+}
diff --git a/pkgs/yaml_edit/test/golden_test.dart b/pkgs/yaml_edit/test/golden_test.dart
new file mode 100644
index 0000000..1dd6ff3
--- /dev/null
+++ b/pkgs/yaml_edit/test/golden_test.dart
@@ -0,0 +1,40 @@
+// Copyright (c) 2020, 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:io';
+import 'dart:isolate';
+
+import 'package:test/test.dart';
+
+import 'test_case.dart';
+
+/// This script performs snapshot testing of the inputs in the testing directory
+/// against golden files if they exist, and creates the golden files otherwise.
+///
+/// Input directory should be in `test/test_cases`, while the golden files should
+/// be in `test/test_cases_golden`.
+///
+/// For more information on the expected input and output, refer to the README
+/// in the testdata folder
+Future<void> main() async {
+ final packageUri = await Isolate.resolvePackageUri(
+ Uri.parse('package:yaml_edit/yaml_edit.dart'));
+
+ final testdataUri = packageUri!.resolve('../test/testdata/');
+ final inputDirectory = Directory.fromUri(testdataUri.resolve('input/'));
+ final goldDirectoryUri = testdataUri.resolve('output/');
+
+ if (!inputDirectory.existsSync()) {
+ throw FileSystemException(
+ 'Testing Directory does not exist!', inputDirectory.path);
+ }
+
+ final testCases =
+ await TestCases.getTestCases(inputDirectory.uri, goldDirectoryUri);
+
+ testCases.test();
+}
diff --git a/pkgs/yaml_edit/test/insert_test.dart b/pkgs/yaml_edit/test/insert_test.dart
new file mode 100644
index 0000000..8c0e3b2
--- /dev/null
+++ b/pkgs/yaml_edit/test/insert_test.dart
@@ -0,0 +1,207 @@
+// Copyright (c) 2020, 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 'package:yaml_edit/yaml_edit.dart';
+
+import 'test_utils.dart';
+
+void main() {
+ group('throws PathError', () {
+ test('if it is a map', () {
+ final doc = YamlEditor('a:1');
+ expect(() => doc.insertIntoList([], 0, 4), throwsPathError);
+ });
+
+ test('if it is a scalar', () {
+ final doc = YamlEditor('1');
+ expect(() => doc.insertIntoList([], 0, 4), throwsPathError);
+ });
+ });
+
+ test('throws RangeError if index is out of range', () {
+ final doc = YamlEditor('[1, 2]');
+ expect(() => doc.insertIntoList([], -1, 0), throwsRangeError);
+ expect(() => doc.insertIntoList([], 3, 0), throwsRangeError);
+ });
+
+ group('block list', () {
+ test('(1)', () {
+ final doc = YamlEditor('''
+- 1
+- 2''');
+ doc.insertIntoList([], 0, 0);
+ expect(doc.toString(), equals('''
+- 0
+- 1
+- 2'''));
+ expectYamlBuilderValue(doc, [0, 1, 2]);
+ });
+
+ test('(2)', () {
+ final doc = YamlEditor('''
+- 1
+- 2''');
+ doc.insertIntoList([], 1, 3);
+ expect(doc.toString(), equals('''
+- 1
+- 3
+- 2'''));
+ expectYamlBuilderValue(doc, [1, 3, 2]);
+ });
+
+ test('(3)', () {
+ final doc = YamlEditor('''
+- 1
+- 2
+''');
+ doc.insertIntoList([], 2, 3);
+ expect(doc.toString(), equals('''
+- 1
+- 2
+- 3
+'''));
+ expectYamlBuilderValue(doc, [1, 2, 3]);
+ });
+
+ test('(4)', () {
+ final doc = YamlEditor('''
+- 1
+- 3
+''');
+ doc.insertIntoList([], 1, [4, 5, 6]);
+ expect(doc.toString(), equals('''
+- 1
+- - 4
+ - 5
+ - 6
+- 3
+'''));
+ expectYamlBuilderValue(doc, [
+ 1,
+ [4, 5, 6],
+ 3
+ ]);
+ });
+
+ test(' with comments', () {
+ final doc = YamlEditor('''
+- 0 # comment a
+- 2 # comment b
+''');
+ doc.insertIntoList([], 1, 1);
+ expect(doc.toString(), equals('''
+- 0 # comment a
+- 1
+- 2 # comment b
+'''));
+ expectYamlBuilderValue(doc, [0, 1, 2]);
+ });
+
+ for (var i = 0; i < 3; i++) {
+ test('block insert(1) at $i', () {
+ final yamlEditor = YamlEditor('''
+# comment
+- z:
+ x: 1
+ y: 2
+- z:
+ x: 3
+ y: 4
+''');
+ expect(
+ () => yamlEditor.insertIntoList(
+ [],
+ i,
+ {
+ 'z': {'x': 5, 'y': 6}
+ }),
+ returnsNormally);
+ });
+ }
+
+ for (var i = 0; i < 3; i++) {
+ test('block insert(2) at $i', () {
+ final yamlEditor = YamlEditor('''
+a:
+ - z:
+ x: 1
+ y: 2
+ - z:
+ x: 3
+ y: 4
+b:
+ - w:
+ m: 2
+ n: 4
+''');
+ expect(
+ () => yamlEditor.insertIntoList(
+ ['a'],
+ i,
+ {
+ 'z': {'x': 5, 'y': 6}
+ }),
+ returnsNormally);
+ });
+ }
+
+ for (var i = 0; i < 2; i++) {
+ test('block insert nested and with comments at $i', () {
+ final yamlEditor = YamlEditor('''
+a:
+ b:
+ - c:
+ d: 1
+ - c:
+ d: 2
+# comment
+ e:
+ - g:
+ e: 1
+ f: 2
+# comment
+''');
+ expect(
+ () => yamlEditor.insertIntoList(
+ ['a', 'b'],
+ i,
+ {
+ 'g': {'e': 3, 'f': 4}
+ }),
+ returnsNormally);
+ });
+ }
+ });
+
+ group('flow list', () {
+ test('(1)', () {
+ final doc = YamlEditor('[1, 2]');
+ doc.insertIntoList([], 0, 0);
+ expect(doc.toString(), equals('[0, 1, 2]'));
+ expectYamlBuilderValue(doc, [0, 1, 2]);
+ });
+
+ test('(2)', () {
+ final doc = YamlEditor('[1, 2]');
+ doc.insertIntoList([], 1, 3);
+ expect(doc.toString(), equals('[1, 3, 2]'));
+ expectYamlBuilderValue(doc, [1, 3, 2]);
+ });
+
+ test('(3)', () {
+ final doc = YamlEditor('[1, 2]');
+ doc.insertIntoList([], 2, 3);
+ expect(doc.toString(), equals('[1, 2, 3]'));
+ expectYamlBuilderValue(doc, [1, 2, 3]);
+ });
+
+ test('(4)', () {
+ final doc = YamlEditor('["[],", "[],"]');
+ doc.insertIntoList([], 1, 'test');
+ expect(doc.toString(), equals('["[],", test, "[],"]'));
+ expectYamlBuilderValue(doc, ['[],', 'test', '[],']);
+ });
+ });
+}
diff --git a/pkgs/yaml_edit/test/naughty_test.dart b/pkgs/yaml_edit/test/naughty_test.dart
new file mode 100644
index 0000000..533a535
--- /dev/null
+++ b/pkgs/yaml_edit/test/naughty_test.dart
@@ -0,0 +1,30 @@
+// Copyright (c) 2020, 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 'package:test/test.dart';
+import 'package:yaml_edit/yaml_edit.dart';
+
+import 'problem_strings.dart';
+
+void main() {
+ for (final string in problemStrings) {
+ test('expect string $string', () {
+ final doc = YamlEditor('');
+
+ /// Using [runZoned] to hide `package:yaml`'s warnings.
+ /// Test failures and errors will still be shown.
+ runZoned(() {
+ expect(() => doc.update([], string), returnsNormally);
+ final value = doc.parseAt([]).value;
+ expect(value, isA<String>());
+ expect(value, equals(string));
+ },
+ zoneSpecification: ZoneSpecification(
+ print: (Zone self, ZoneDelegate parent, Zone zone,
+ String message) {}));
+ });
+ }
+}
diff --git a/pkgs/yaml_edit/test/parse_test.dart b/pkgs/yaml_edit/test/parse_test.dart
new file mode 100644
index 0000000..382307c
--- /dev/null
+++ b/pkgs/yaml_edit/test/parse_test.dart
@@ -0,0 +1,156 @@
+// Copyright (c) 2020, 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 'package:yaml/yaml.dart';
+import 'package:yaml_edit/yaml_edit.dart';
+
+import 'test_utils.dart';
+
+void main() {
+ group('throws', () {
+ test('PathError if key does not exist', () {
+ final doc = YamlEditor('{a: 4}');
+ final path = ['b'];
+
+ expect(() => doc.parseAt(path), throwsPathError);
+ });
+
+ test('PathError if path tries to go deeper into a scalar', () {
+ final doc = YamlEditor('{a: 4}');
+ final path = ['a', 'b'];
+
+ expect(() => doc.parseAt(path), throwsPathError);
+ });
+
+ test('PathError if index is out of bounds', () {
+ final doc = YamlEditor('[0,1]');
+ final path = [2];
+
+ expect(() => doc.parseAt(path), throwsPathError);
+ });
+
+ test('PathError if index is not an integer', () {
+ final doc = YamlEditor('[0,1]');
+ final path = ['2'];
+
+ expect(() => doc.parseAt(path), throwsPathError);
+ });
+ });
+
+ group('orElse provides a default value', () {
+ test('simple example with null node return ', () {
+ final doc = YamlEditor('{a: {d: 4}, c: ~}');
+ final result = doc.parseAt(['b'], orElse: () => wrapAsYamlNode(null));
+
+ expect(result.value, equals(null));
+ });
+
+ test('simple example with map return', () {
+ final doc = YamlEditor('{a: {d: 4}, c: ~}');
+ final result =
+ doc.parseAt(['b'], orElse: () => wrapAsYamlNode({'a': 42}));
+
+ expect(result, isA<YamlMap>());
+ expect(result.value, equals({'a': 42}));
+ });
+
+ test('simple example with scalar return', () {
+ final doc = YamlEditor('{a: {d: 4}, c: ~}');
+ final result = doc.parseAt(['b'], orElse: () => wrapAsYamlNode(42));
+
+ expect(result, isA<YamlScalar>());
+ expect(result.value, equals(42));
+ });
+
+ test('simple example with list return', () {
+ final doc = YamlEditor('{a: {d: 4}, c: ~}');
+ final result = doc.parseAt(['b'], orElse: () => wrapAsYamlNode([42]));
+
+ expect(result, isA<YamlList>());
+ expect(result.value, equals([42]));
+ });
+ });
+
+ group('returns a YamlNode', () {
+ test('with the correct type', () {
+ final doc = YamlEditor("YAML: YAML Ain't Markup Language");
+ final expectedYamlScalar = doc.parseAt(['YAML']);
+
+ expect(expectedYamlScalar, isA<YamlScalar>());
+ });
+
+ test('with the correct value', () {
+ final doc = YamlEditor("YAML: YAML Ain't Markup Language");
+
+ expect(doc.parseAt(['YAML']).value, "YAML Ain't Markup Language");
+ });
+
+ test('with the correct value in nested collection', () {
+ final doc = YamlEditor('''
+a: 1
+b:
+ d: 4
+ e: [5, 6, 7]
+c: 3
+''');
+
+ expect(doc.parseAt(['b', 'e', 2]).value, 7);
+ });
+
+ test('with a null value in nested collection', () {
+ final doc = YamlEditor('''
+key1:
+ key2: null
+''');
+
+ expect(doc.parseAt(['key1', 'key2']).value, null);
+ });
+
+ test('with the correct type (2)', () {
+ final doc = YamlEditor("YAML: YAML Ain't Markup Language");
+ final expectedYamlMap = doc.parseAt([]);
+
+ expect(expectedYamlMap is YamlMap, equals(true));
+ });
+
+ test('that is immutable', () {
+ final doc = YamlEditor("YAML: YAML Ain't Markup Language");
+ final expectedYamlMap = doc.parseAt([]);
+
+ expect(() => (expectedYamlMap as YamlMap)['YAML'] = 'test',
+ throwsUnsupportedError);
+ });
+
+ test('that has immutable children', () {
+ final doc = YamlEditor("YAML: ['Y', 'A', 'M', 'L']");
+ final expectedYamlMap = doc.parseAt([]);
+
+ expect(() => ((expectedYamlMap as YamlMap)['YAML'] as List)[0] = 'X',
+ throwsUnsupportedError);
+ });
+ });
+
+ test('works with map keys', () {
+ final doc = YamlEditor('{a: {{[1, 2]: 3}: 4}}');
+ expect(
+ doc.parseAt([
+ 'a',
+ {
+ [1, 2]: 3
+ }
+ ]).value,
+ equals(4));
+ });
+
+ test('works with null in path', () {
+ final doc = YamlEditor('{a: { ~: 4}}');
+ expect(doc.parseAt(['a', null]).value, equals(4));
+ });
+
+ test('works with null value', () {
+ final doc = YamlEditor('{a: null}');
+ expect(doc.parseAt(['a']).value, equals(null));
+ });
+}
diff --git a/pkgs/yaml_edit/test/prepend_test.dart b/pkgs/yaml_edit/test/prepend_test.dart
new file mode 100644
index 0000000..3112653
--- /dev/null
+++ b/pkgs/yaml_edit/test/prepend_test.dart
@@ -0,0 +1,169 @@
+// Copyright (c) 2020, 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 'package:yaml/yaml.dart';
+import 'package:yaml_edit/yaml_edit.dart';
+
+import 'test_utils.dart';
+
+void main() {
+ group('throws PathError', () {
+ test('if it is a map', () {
+ final doc = YamlEditor('a:1');
+ expect(() => doc.prependToList([], 4), throwsPathError);
+ });
+
+ test('if it is a scalar', () {
+ final doc = YamlEditor('1');
+ expect(() => doc.prependToList([], 4), throwsPathError);
+ });
+ });
+
+ group('flow list', () {
+ test('(1)', () {
+ final doc = YamlEditor('[1, 2]');
+ doc.prependToList([], 0);
+ expect(doc.toString(), equals('[0, 1, 2]'));
+ expectYamlBuilderValue(doc, [0, 1, 2]);
+ });
+
+ test('null value', () {
+ final doc = YamlEditor('[1, 2]');
+ doc.prependToList([], null);
+ expect(doc.toString(), equals('[null, 1, 2]'));
+ expectYamlBuilderValue(doc, [null, 1, 2]);
+ });
+
+ test('with spaces (1)', () {
+ final doc = YamlEditor('[ 1 , 2 ]');
+ doc.prependToList([], 0);
+ expect(doc.toString(), equals('[ 0, 1 , 2 ]'));
+ expectYamlBuilderValue(doc, [0, 1, 2]);
+ });
+ });
+
+ group('block list', () {
+ test('(1)', () {
+ final doc = YamlEditor('''
+- 1
+- 2''');
+ doc.prependToList([], 0);
+ expect(doc.toString(), equals('''
+- 0
+- 1
+- 2'''));
+ expectYamlBuilderValue(doc, [0, 1, 2]);
+ });
+
+ /// Regression testing for no trailing spaces.
+ test('(2)', () {
+ final doc = YamlEditor('''- 1
+- 2''');
+ doc.prependToList([], 0);
+ expect(doc.toString(), equals('''- 0
+- 1
+- 2'''));
+ expectYamlBuilderValue(doc, [0, 1, 2]);
+ });
+
+ test('(3)', () {
+ final doc = YamlEditor('''
+- 1
+- 2
+''');
+ doc.prependToList([], [4, 5, 6]);
+ expect(doc.toString(), equals('''
+- - 4
+ - 5
+ - 6
+- 1
+- 2
+'''));
+ expectYamlBuilderValue(doc, [
+ [4, 5, 6],
+ 1,
+ 2
+ ]);
+ });
+
+ test('(4)', () {
+ final doc = YamlEditor('''
+a:
+ - b
+ - - c
+ - d
+''');
+ doc.prependToList(
+ ['a'], wrapAsYamlNode({1: 2}, collectionStyle: CollectionStyle.FLOW));
+
+ expect(doc.toString(), equals('''
+a:
+ - {1: 2}
+ - b
+ - - c
+ - d
+'''));
+ expectYamlBuilderValue(doc, {
+ 'a': [
+ {1: 2},
+ 'b',
+ ['c', 'd']
+ ]
+ });
+ });
+
+ test('with comments ', () {
+ final doc = YamlEditor('''
+# comments
+- 1 # comments
+- 2
+''');
+ doc.prependToList([], 0);
+ expect(doc.toString(), equals('''
+# comments
+- 0
+- 1 # comments
+- 2
+'''));
+ expectYamlBuilderValue(doc, [0, 1, 2]);
+ });
+
+ test('nested in map', () {
+ final doc = YamlEditor('''
+a:
+ - 1
+ - 2
+''');
+ doc.prependToList(['a'], 0);
+ expect(doc.toString(), equals('''
+a:
+ - 0
+ - 1
+ - 2
+'''));
+ expectYamlBuilderValue(doc, {
+ 'a': [0, 1, 2]
+ });
+ });
+
+ test('nested in map with comments ', () {
+ final doc = YamlEditor('''
+a: # comments
+ - 1 # comments
+ - 2
+''');
+ doc.prependToList(['a'], 0);
+ expect(doc.toString(), equals('''
+a: # comments
+ - 0
+ - 1 # comments
+ - 2
+'''));
+ expectYamlBuilderValue(doc, {
+ 'a': [0, 1, 2]
+ });
+ });
+ });
+}
diff --git a/pkgs/yaml_edit/test/preservation_test.dart b/pkgs/yaml_edit/test/preservation_test.dart
new file mode 100644
index 0000000..a763296
--- /dev/null
+++ b/pkgs/yaml_edit/test/preservation_test.dart
@@ -0,0 +1,61 @@
+// Copyright (c) 2020, 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 'test_utils.dart';
+
+void main() {
+ group('preserves original yaml: ', () {
+ test('number', expectLoadPreservesYAML('2'));
+ test('number with leading and trailing lines', expectLoadPreservesYAML('''
+
+ 2
+
+ '''));
+ test('octal numbers', expectLoadPreservesYAML('0o14'));
+ test('negative numbers', expectLoadPreservesYAML('-345'));
+ test('hexadecimal numbers', expectLoadPreservesYAML('0x123abc'));
+ test('floating point numbers', expectLoadPreservesYAML('345.678'));
+ test('exponential numbers', expectLoadPreservesYAML('12.3015e+02'));
+ test('string', expectLoadPreservesYAML('a string'));
+ test('string with control characters',
+ expectLoadPreservesYAML('a string \\n'));
+ test('string with control characters',
+ expectLoadPreservesYAML('a string \n\r'));
+ test('string with hex escapes',
+ expectLoadPreservesYAML('\\x0d\\x0a is \\r\\n'));
+ test('flow map', expectLoadPreservesYAML('{a: 2}'));
+ test('flow list', expectLoadPreservesYAML('[1, 2]'));
+ test('flow list with different types of elements',
+ expectLoadPreservesYAML('[1, a]'));
+ test('flow list with weird spaces',
+ expectLoadPreservesYAML('[ 1 , 2]'));
+ test('multiline string', expectLoadPreservesYAML('''
+ Mark set a major league
+ home run record in 1998.'''));
+ test('tilde', expectLoadPreservesYAML('~'));
+ test('false', expectLoadPreservesYAML('false'));
+
+ test('block map', expectLoadPreservesYAML('''a:
+ b: 1
+ '''));
+ test('block list', expectLoadPreservesYAML('''a:
+ - 1
+ '''));
+ test('complicated example', () {
+ expectLoadPreservesYAML('''verb: RecommendCafes
+map:
+ a:
+ b: 1
+recipe:
+ - verb: Score
+ outputs: ["DishOffering[]/Scored", "Suggestions"]
+ name: Hotpot
+ - verb: Rate
+ inputs: Dish
+ ''');
+ });
+ });
+}
diff --git a/pkgs/yaml_edit/test/problem_strings.dart b/pkgs/yaml_edit/test/problem_strings.dart
new file mode 100644
index 0000000..527a9e0
--- /dev/null
+++ b/pkgs/yaml_edit/test/problem_strings.dart
@@ -0,0 +1,91 @@
+// Copyright (c) 2020, 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: lines_longer_than_80_chars
+
+const problemStrings = [
+ '[]',
+ '{}',
+ '',
+ ',',
+ '~',
+ 'undefined',
+ 'undef',
+ 'null',
+ 'NULL',
+ '(null)',
+ 'nil',
+ 'NIL',
+ 'true',
+ 'false',
+ 'True',
+ 'False',
+ 'TRUE',
+ 'FALSE',
+ 'None',
+ '\\',
+ '\\\\',
+ '0',
+ '1',
+ '\$1.00',
+ '1/2',
+ '1E2',
+ '-\$1.00',
+ '-1/2',
+ '-1E+02',
+ '1/0',
+ '0/0',
+ '-0',
+ '+0.0',
+ '0..0',
+ '.',
+ '0.0.0',
+ '0,00',
+ ',',
+ '0.0/0',
+ '1.0/0.0',
+ '0.0/0.0',
+ '--1',
+ '-',
+ '-.',
+ '-,',
+ '999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999',
+ 'NaN',
+ 'Infinity',
+ '-Infinity',
+ 'INF',
+ '1#INF',
+ '0x0',
+ '0xffffffffffffffff',
+ "1'000.00",
+ '1,000,000.00',
+ '1.000,00',
+ "1'000,00",
+ '1.000.000,00',
+ ",./;'[]\\-=",
+ '<>?:"{}|_+',
+ '!@#\$%^&*()`~',
+ '\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f',
+ '\t\u000b\f
',
+ 'ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็ ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็ ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็',
+ "'",
+ '"',
+ "''",
+ '\'"',
+ "'\"'",
+ '社會科學院語學研究所',
+ 'Ⱥ',
+ 'ヽ༼ຈل͜ຈ༽ノ ヽ༼ຈل͜ຈ༽ノ',
+ '❤️ 💔 💌 💕 💞 💓 💗 💖 💘 💝 💟 💜 💛 💚 💙',
+ '𝕋𝕙𝕖 𝕢𝕦𝕚𝕔𝕜 𝕓𝕣𝕠𝕨𝕟 𝕗𝕠𝕩 𝕛𝕦𝕞𝕡𝕤 𝕠𝕧𝕖𝕣 𝕥𝕙𝕖 𝕝𝕒𝕫𝕪 𝕕𝕠𝕘',
+ ' ',
+ '%',
+ '%d',
+ '%s%s%s%s%s',
+ '{0}',
+ '%*.*s',
+ '%@',
+ '%n',
+ 'The quic\b\b\b\b\b\bk brown fo\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007x... [Beeeep]',
+];
diff --git a/pkgs/yaml_edit/test/random_test.dart b/pkgs/yaml_edit/test/random_test.dart
new file mode 100644
index 0000000..85cea4a
--- /dev/null
+++ b/pkgs/yaml_edit/test/random_test.dart
@@ -0,0 +1,311 @@
+// Copyright (c) 2020, 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' show Random;
+
+import 'package:test/test.dart';
+import 'package:yaml/yaml.dart';
+import 'package:yaml_edit/yaml_edit.dart';
+
+import 'problem_strings.dart';
+import 'test_utils.dart';
+
+/// Performs naive fuzzing on an initial YAML file based on an initial seed.
+///
+/// Starting with a template YAML, we randomly generate modifications and their
+/// inputs (boolean, null, strings, or numbers) to modify the YAML and assert
+/// that the change produced was expected.
+void main() {
+ final generator = _Generator(maxDepth: 5);
+
+ const roundsOfTesting = 40;
+ const modificationsPerRound = 1000;
+
+ for (var i = 0; i < roundsOfTesting; i++) {
+ test(
+ 'testing with randomly generated modifications: test $i',
+ () {
+ final editor = YamlEditor('''
+name: yaml_edit
+description: A library for YAML manipulation with comment and whitespace preservation.
+version: 0.0.1-dev
+
+environment:
+ sdk: ">=2.4.0 <3.0.0"
+
+dependencies:
+ meta: ^1.1.8
+ quiver_hashcode: ^2.0.0
+
+dev_dependencies:
+ pedantic: ^1.9.0
+ test: ^1.14.4
+''');
+
+ for (var j = 0; j < modificationsPerRound; j++) {
+ expect(
+ () => generator.performNextModification(editor, i),
+ returnsNormally,
+ );
+ }
+ },
+ );
+ }
+}
+
+/// Generates the random variables we need for fuzzing.
+class _Generator {
+ final Random r;
+
+ /// 2^32
+ static const int maxInt = 4294967296;
+
+ /// Maximum depth of random YAML collection generated.
+ final int maxDepth;
+
+ // ignore: unused_element
+ _Generator({int seed = 0, required this.maxDepth}) : r = Random(seed);
+
+ int nextInt([int max = maxInt]) => r.nextInt(max);
+
+ double nextDouble() => r.nextDouble();
+
+ bool nextBool() => r.nextBool();
+
+ /// Generates a new string by individually generating characters and
+ /// appending them to a buffer. Currently only generates strings from
+ /// ascii 32 - 127.
+ String nextString() {
+ if (nextBool()) {
+ return problemStrings[nextInt(problemStrings.length)];
+ }
+
+ final length = nextInt(100);
+ final buffer = StringBuffer();
+
+ for (var i = 0; i < length; i++) {
+ final charCode = nextInt(95) + 32;
+ buffer.writeCharCode(charCode);
+ }
+
+ return buffer.toString();
+ }
+
+ /// Generates a new scalar recognizable by YAML.
+ Object? nextScalar() {
+ final typeIndex = nextInt(5);
+
+ switch (typeIndex) {
+ case 0:
+ return nextBool();
+ case 1:
+ return nextDouble();
+ case 2:
+ return nextInt();
+ case 3:
+ return null;
+ default:
+ return nextString();
+ }
+ }
+
+ YamlScalar nextYamlScalar() {
+ return wrapAsYamlNode(nextScalar(), scalarStyle: nextScalarStyle())
+ as YamlScalar;
+ }
+
+ /// Generates the next [YamlList], with the current [depth].
+ YamlList nextYamlList(int depth) {
+ final length = nextInt(9);
+ final list = [];
+
+ for (var i = 0; i < length; i++) {
+ list.add(nextYamlNode(depth + 1));
+ }
+
+ return wrapAsYamlNode(list, collectionStyle: nextCollectionStyle())
+ as YamlList;
+ }
+
+ /// Generates the next [YamlList], with the current [depth].
+ YamlMap nextYamlMap(int depth) {
+ final length = nextInt(9);
+ final nodes = {};
+
+ for (var i = 0; i < length; i++) {
+ nodes[nextYamlNode(depth + 1)] = nextYamlScalar();
+ }
+
+ return wrapAsYamlNode(nodes, collectionStyle: nextCollectionStyle())
+ as YamlMap;
+ }
+
+ /// Returns a [YamlNode], with it being a [YamlScalar] 80% of the time, a
+ /// [YamlList] 10% of the time, and a [YamlMap] 10% of the time.
+ ///
+ /// If [depth] is greater than [maxDepth], we instantly return a [YamlScalar]
+ /// to prevent the parent from growing any further, to improve our speeds.
+ YamlNode nextYamlNode([int depth = 0]) {
+ if (depth >= maxDepth) {
+ return nextYamlScalar();
+ }
+
+ final roll = nextInt(10);
+
+ if (roll < 8) {
+ return nextYamlScalar();
+ } else if (roll == 8) {
+ return nextYamlList(depth);
+ } else {
+ return nextYamlMap(depth);
+ }
+ }
+
+ /// Performs a random modification
+ void performNextModification(YamlEditor editor, int count) {
+ final path = findPath(editor);
+ final node = editor.parseAt(path);
+ final initialString = editor.toString();
+ final args = [];
+ var method = YamlModificationMethod.remove;
+
+ try {
+ if (node is YamlScalar) {
+ editor.remove(path);
+ return;
+ }
+
+ if (node is YamlList) {
+ final methodIndex = nextInt(YamlModificationMethod.values.length);
+ method = YamlModificationMethod.values[methodIndex];
+
+ switch (method) {
+ case YamlModificationMethod.remove:
+ editor.remove(path);
+ break;
+ case YamlModificationMethod.update:
+ if (node.isEmpty) break;
+ final index = nextInt(node.length);
+ args.add(nextYamlNode());
+ path.add(index);
+ editor.update(path, args[0]);
+ break;
+ case YamlModificationMethod.appendTo:
+ args.add(nextYamlNode());
+ editor.appendToList(path, args[0]);
+ break;
+ case YamlModificationMethod.prependTo:
+ args.add(nextYamlNode());
+ editor.prependToList(path, args[0]);
+ break;
+ case YamlModificationMethod.insert:
+ args.add(nextInt(node.length + 1));
+ args.add(nextYamlNode());
+ editor.insertIntoList(path, args[0] as int, args[1]);
+ break;
+ case YamlModificationMethod.splice:
+ args.add(nextInt(node.length + 1));
+ args.add(nextInt(node.length + 1 - (args[0] as int)));
+ args.add(nextYamlList(0));
+ editor.spliceList(
+ path, args[0] as int, args[1] as int, args[2] as List);
+ break;
+ }
+ return;
+ }
+
+ if (node is YamlMap) {
+ final replace = nextBool();
+ method = YamlModificationMethod.update;
+
+ if (replace && node.isNotEmpty) {
+ final keyList = node.keys.toList();
+ path.add(keyList[nextInt(keyList.length)]);
+ } else {
+ path.add(nextScalar());
+ }
+ final value = nextYamlNode();
+ args.add(value);
+ editor.update(path, value);
+ return;
+ }
+ } catch (error, stacktrace) {
+ /// TODO: Fix once reproducible. Identify pattern.
+ if (count == 20) return;
+
+ print('''
+Failed to call $method on:
+$initialString
+with the following arguments:
+$args
+and path:
+$path
+
+Error Details:
+$error
+
+$stacktrace
+''');
+ rethrow;
+ }
+
+ throw AssertionError('Got invalid node');
+ }
+
+ /// Obtains a random path by traversing [editor].
+ ///
+ /// At every node, we return the path to the node if the node has no children.
+ /// Otherwise, we return at a 50% chance, or traverse to one random child.
+ List<Object?> findPath(YamlEditor editor) {
+ final path = <Object?>[];
+
+ // 50% chance of stopping at the collection
+ while (nextBool()) {
+ final node = editor.parseAt(path);
+
+ if (node is YamlList && node.isNotEmpty) {
+ path.add(nextInt(node.length));
+ } else if (node is YamlMap && node.isNotEmpty) {
+ final keyList = node.keys.toList();
+ path.add(keyList[nextInt(keyList.length)]);
+ } else {
+ break;
+ }
+ }
+
+ return path;
+ }
+
+ ScalarStyle nextScalarStyle() {
+ final seed = nextInt(6);
+
+ switch (seed) {
+ case 0:
+ return ScalarStyle.DOUBLE_QUOTED;
+ case 1:
+ return ScalarStyle.FOLDED;
+ case 2:
+ return ScalarStyle.LITERAL;
+ case 3:
+ return ScalarStyle.PLAIN;
+ case 4:
+ return ScalarStyle.SINGLE_QUOTED;
+ default:
+ return ScalarStyle.ANY;
+ }
+ }
+
+ CollectionStyle nextCollectionStyle() {
+ final seed = nextInt(3);
+
+ switch (seed) {
+ case 0:
+ return CollectionStyle.BLOCK;
+ case 1:
+ return CollectionStyle.FLOW;
+ default:
+ return CollectionStyle.ANY;
+ }
+ }
+}
diff --git a/pkgs/yaml_edit/test/remove_test.dart b/pkgs/yaml_edit/test/remove_test.dart
new file mode 100644
index 0000000..4742b56
--- /dev/null
+++ b/pkgs/yaml_edit/test/remove_test.dart
@@ -0,0 +1,603 @@
+// Copyright (c) 2020, 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 'package:yaml_edit/yaml_edit.dart';
+
+import 'test_utils.dart';
+
+void main() {
+ group('throws', () {
+ test('PathError if collectionPath points to a scalar', () {
+ final doc = YamlEditor('''
+a: 1
+b: 2
+c: 3
+''');
+
+ expect(() => doc.remove(['a', 0]), throwsPathError);
+ });
+
+ test('PathError if collectionPath is invalid', () {
+ final doc = YamlEditor('''
+a: 1
+b: 2
+c: 3
+''');
+
+ expect(() => doc.remove(['d']), throwsPathError);
+ });
+
+ test('PathError if collectionPath is invalid in nested path', () {
+ final doc = YamlEditor('''
+a:
+ b: 'foo'
+''');
+
+ expect(() => doc.remove(['d']), throwsPathError);
+ });
+
+ test('PathError if collectionPath is invalid - list', () {
+ final doc = YamlEditor('''
+[1, 2, 3]
+''');
+
+ expect(() => doc.remove([4]), throwsPathError);
+ });
+
+ test('PathError in list if using a non-integer as index', () {
+ final doc = YamlEditor("{ a: ['b', 'c'] }");
+ expect(() => doc.remove(['a', 'b']), throwsPathError);
+ });
+
+ test('PathError if path is invalid', () {
+ final doc = YamlEditor("{ a: ['b', 'c'] }");
+ expect(() => doc.remove(['a', 0, '1']), throwsPathError);
+ });
+ });
+
+ group('returns', () {
+ test('returns the removed node when successful', () {
+ final doc = YamlEditor('{ a: { b: foo } }');
+ final node = doc.remove(['a', 'b']);
+ expect(node.value, equals('foo'));
+ });
+
+ test('returns null-value node when doc is empty and path is empty', () {
+ final doc = YamlEditor('');
+ final node = doc.remove([]);
+ expect(node.value, equals(null));
+ });
+ });
+
+ test('empty path should clear string', () {
+ final doc = YamlEditor('''
+a: 1
+b: 2
+c: [3, 4]
+''');
+ doc.remove([]);
+ expect(doc.toString(), equals(''));
+ });
+
+ group('block map', () {
+ test('(1)', () {
+ final doc = YamlEditor('''
+a: 1
+b: 2
+c: 3
+''');
+ doc.remove(['b']);
+ expect(doc.toString(), equals('''
+a: 1
+c: 3
+'''));
+ });
+
+ test('empty value', () {
+ final doc = YamlEditor('''
+a: 1
+b:
+c: 3
+''');
+ doc.remove(['b']);
+ expect(doc.toString(), equals('''
+a: 1
+c: 3
+'''));
+ });
+
+ test('empty value (2)', () {
+ final doc = YamlEditor('''
+- a: 1
+ b:
+ c: 3
+''');
+ doc.remove([0, 'b']);
+ expect(doc.toString(), equals('''
+- a: 1
+ c: 3
+'''));
+ });
+
+ test('empty value (3)', () {
+ final doc = YamlEditor('''
+- a: 1
+ b:
+
+ c: 3
+''');
+ doc.remove([0, 'b']);
+ expect(doc.toString(), equals('''
+- a: 1
+
+ c: 3
+'''));
+ });
+
+ test('preserves comments', () {
+ final doc = YamlEditor('''
+a: 1 # preserved 1
+# preserved 2
+b: 2
+# preserved 3
+c: 3 # preserved 4
+''');
+ doc.remove(['b']);
+ expect(doc.toString(), equals('''
+a: 1 # preserved 1
+# preserved 2
+# preserved 3
+c: 3 # preserved 4
+'''));
+ });
+
+ test('final element in map', () {
+ final doc = YamlEditor('''
+a: 1
+b: 2
+''');
+ doc.remove(['b']);
+ expect(doc.toString(), equals('''
+a: 1
+'''));
+ });
+
+ test('final element in nested map', () {
+ final doc = YamlEditor('''
+a:
+ aa: 11
+ bb: 22
+b: 2
+''');
+ doc.remove(['a', 'bb']);
+ expect(doc.toString(), equals('''
+a:
+ aa: 11
+b: 2
+'''));
+ });
+
+ test('last element should return flow empty map', () {
+ final doc = YamlEditor('''
+a: 1
+''');
+ doc.remove(['a']);
+ expect(doc.toString(), equals('''
+{}
+'''));
+ });
+
+ test('last element should return flow empty map (2)', () {
+ final doc = YamlEditor('''
+- a: 1
+- b: 2
+''');
+ doc.remove([0, 'a']);
+ expect(doc.toString(), equals('''
+- {}
+- b: 2
+'''));
+ });
+
+ test('nested', () {
+ final doc = YamlEditor('''
+a: 1
+b:
+ d: 4
+ e: 5
+c: 3
+''');
+ doc.remove(['b', 'd']);
+ expect(doc.toString(), equals('''
+a: 1
+b:
+ e: 5
+c: 3
+'''));
+ });
+
+ test('issue #55 reopend', () {
+ final doc = YamlEditor('''name: sample
+version: 0.1.0
+environment:
+ sdk: ^3.0.0
+dependencies:
+ retry: ^3.1.2
+dev_dependencies:
+ retry:''');
+ doc.remove(['dev_dependencies']);
+ });
+
+ test('issue #55 reopend, variant 2', () {
+ final doc = YamlEditor('''name: sample
+version: 0.1.0
+environment:
+ sdk: ^3.0.0
+dependencies:
+ retry: ^3.1.2
+dev_dependencies:
+ retry:''');
+ doc.remove(['dev_dependencies', 'retry']);
+ });
+ });
+
+ group('flow map', () {
+ test('(1)', () {
+ final doc = YamlEditor('{a: 1, b: 2, c: 3}');
+ doc.remove(['b']);
+ expect(doc.toString(), equals('{a: 1, c: 3}'));
+ });
+
+ test('(2) ', () {
+ final doc = YamlEditor('{a: 1}');
+ doc.remove(['a']);
+ expect(doc.toString(), equals('{}'));
+ });
+
+ test('(3) ', () {
+ final doc = YamlEditor('{a: 1, b: 2}');
+ doc.remove(['a']);
+ expect(doc.toString(), equals('{ b: 2}'));
+ });
+
+ test('(4) ', () {
+ final doc =
+ YamlEditor('{"{}[],": {"{}[],": 1, b: "}{[]},", "}{[],": 3}}');
+ doc.remove(['{}[],', 'b']);
+ expect(doc.toString(), equals('{"{}[],": {"{}[],": 1, "}{[],": 3}}'));
+ });
+
+ test('empty value', () {
+ final doc = YamlEditor('{a: 1, b:, c: 3}');
+ doc.remove(['b']);
+ expect(doc.toString(), equals('{a: 1, c: 3}'));
+ });
+
+ test('nested flow map ', () {
+ final doc = YamlEditor('{a: 1, b: {d: 4, e: 5}, c: 3}');
+ doc.remove(['b', 'd']);
+ expect(doc.toString(), equals('{a: 1, b: { e: 5}, c: 3}'));
+ });
+
+ test('nested flow map (2)', () {
+ final doc = YamlEditor('{a: {{[1] : 2}: 3, b: 2}}');
+ doc.remove([
+ 'a',
+ {
+ [1]: 2
+ }
+ ]);
+ expect(doc.toString(), equals('{a: { b: 2}}'));
+ });
+ });
+
+ group('block list', () {
+ test('empty value', () {
+ final doc = YamlEditor('''
+- 0
+-
+- 2
+''');
+ doc.remove([1]);
+ expect(doc.toString(), equals('''
+- 0
+- 2
+'''));
+ });
+
+ test('last element should return flow empty list', () {
+ final doc = YamlEditor('''
+- 0
+''');
+ doc.remove([0]);
+ expect(doc.toString(), equals('''
+[]
+'''));
+ });
+
+ test('last element should return flow empty list (2)', () {
+ final doc = YamlEditor('''
+a:
+ - 1
+b: [3]
+''');
+ doc.remove(['a', 0]);
+ expect(doc.toString(), equals('''
+a:
+ []
+b: [3]
+'''));
+ });
+
+ test('last element should return flow empty list (3)', () {
+ final doc = YamlEditor('''
+a:
+ - 1
+b:
+ - 3
+''');
+ doc.remove(['a', 0]);
+ expect(doc.toString(), equals('''
+a:
+ []
+b:
+ - 3
+'''));
+ });
+
+ test('(1) ', () {
+ final doc = YamlEditor('''
+- 0
+- 1
+- 2
+- 3
+''');
+ doc.remove([1]);
+ expect(doc.toString(), equals('''
+- 0
+- 2
+- 3
+'''));
+ expectYamlBuilderValue(doc, [0, 2, 3]);
+ });
+
+ test('(2)', () {
+ final doc = YamlEditor('''
+- 0
+- [1,2,3]
+- 2
+- 3
+''');
+ doc.remove([1]);
+ expect(doc.toString(), equals('''
+- 0
+- 2
+- 3
+'''));
+ expectYamlBuilderValue(doc, [0, 2, 3]);
+ });
+
+ test('(3)', () {
+ final doc = YamlEditor('''
+- 0
+- {a: 1, b: 2}
+- 2
+- 3
+''');
+ doc.remove([1]);
+ expect(doc.toString(), equals('''
+- 0
+- 2
+- 3
+'''));
+ expectYamlBuilderValue(doc, [0, 2, 3]);
+ });
+
+ test('last element', () {
+ final doc = YamlEditor('''
+- 0
+- 1
+''');
+ doc.remove([1]);
+ expect(doc.toString(), equals('''
+- 0
+'''));
+ expectYamlBuilderValue(doc, [0]);
+ });
+
+ test('with comments', () {
+ final doc = YamlEditor('''
+- 0 # comment 0
+# comment 1
+- 1 # comment 2
+# comment 3
+- 2 # comment 4
+- 3
+''');
+ doc.remove([1]);
+ expect(doc.toString(), equals('''
+- 0 # comment 0
+# comment 1
+# comment 3
+- 2 # comment 4
+- 3
+'''));
+ expectYamlBuilderValue(doc, [0, 2, 3]);
+ });
+
+ test('nested list', () {
+ final doc = YamlEditor('''
+- - - 0
+ - 1
+''');
+ doc.remove([0, 0, 0]);
+ expect(doc.toString(), equals('''
+- - - 1
+'''));
+ expectYamlBuilderValue(doc, [
+ [
+ [1]
+ ]
+ ]);
+ });
+
+ test('nested list (2)', () {
+ final doc = YamlEditor('''
+- - 0
+ - 1
+- 2
+''');
+ doc.remove([0]);
+ expect(doc.toString(), equals('''
+- 2
+'''));
+ expectYamlBuilderValue(doc, [2]);
+ });
+
+ test('nested list (3)', () {
+ final doc = YamlEditor('''
+- - 0
+ - 1
+- 2
+''');
+ doc.remove([0, 1]);
+ expect(doc.toString(), equals('''
+- - 0
+- 2
+'''));
+ expectYamlBuilderValue(doc, [
+ [0],
+ 2
+ ]);
+ });
+
+ test('nested list (4)', () {
+ final doc = YamlEditor('''
+-
+ - - 0
+ - 1
+ - 2
+''');
+ doc.remove([0, 0, 1]);
+ expect(doc.toString(), equals('''
+-
+ - - 0
+ - 2
+'''));
+ expectYamlBuilderValue(doc, [
+ [
+ [0],
+ 2
+ ]
+ ]);
+ });
+
+ test('nested list (5)', () {
+ final doc = YamlEditor('''
+- - 0
+ -
+ 1
+''');
+ doc.remove([0, 0]);
+ expect(doc.toString(), equals('''
+- -
+ 1
+'''));
+ expectYamlBuilderValue(doc, [
+ [1]
+ ]);
+ });
+
+ test('nested list (6)', () {
+ final doc = YamlEditor('''
+- - 0 # -
+ # -
+ -
+ 1
+''');
+ doc.remove([0, 0]);
+ expect(doc.toString(), equals('''
+- # -
+ -
+ 1
+'''));
+ expectYamlBuilderValue(doc, [
+ [1]
+ ]);
+ });
+
+ test('nested map', () {
+ final doc = YamlEditor('''
+- - a: b
+ c: d
+''');
+ doc.remove([0, 0, 'a']);
+ expect(doc.toString(), equals('''
+- - c: d
+'''));
+ expectYamlBuilderValue(doc, [
+ [
+ {'c': 'd'}
+ ]
+ ]);
+ });
+
+ test('nested map (2)', () {
+ final doc = YamlEditor('''
+- a:
+ - 0
+ - 1
+ c: d
+''');
+ doc.remove([0, 'a', 1]);
+ expect(doc.toString(), equals('''
+- a:
+ - 0
+ c: d
+'''));
+ expectYamlBuilderValue(doc, [
+ {
+ 'a': [0],
+ 'c': 'd'
+ }
+ ]);
+ });
+ });
+
+ group('flow list', () {
+ test('(1)', () {
+ final doc = YamlEditor('[1, 2, 3]');
+ doc.remove([1]);
+ expect(doc.toString(), equals('[1, 3]'));
+ expectYamlBuilderValue(doc, [1, 3]);
+ });
+
+ test('(2)', () {
+ final doc = YamlEditor('[1, "b", "c"]');
+ doc.remove([0]);
+ expect(doc.toString(), equals('[ "b", "c"]'));
+ expectYamlBuilderValue(doc, ['b', 'c']);
+ });
+
+ test('(3)', () {
+ final doc = YamlEditor('[1, {a: 1}, "c"]');
+ doc.remove([1]);
+ expect(doc.toString(), equals('[1, "c"]'));
+ expectYamlBuilderValue(doc, [1, 'c']);
+ });
+
+ test('(4) ', () {
+ final doc = YamlEditor('["{}", b, "}{"]');
+ doc.remove([1]);
+ expect(doc.toString(), equals('["{}", "}{"]'));
+ });
+
+ test('(5) ', () {
+ final doc = YamlEditor('["{}[],", [test, "{}[],", "{}[],"], "{}[],"]');
+ doc.remove([1, 0]);
+ expect(doc.toString(), equals('["{}[],", [ "{}[],", "{}[],"], "{}[],"]'));
+ });
+ });
+}
diff --git a/pkgs/yaml_edit/test/source_edit_test.dart b/pkgs/yaml_edit/test/source_edit_test.dart
new file mode 100644
index 0000000..693455e
--- /dev/null
+++ b/pkgs/yaml_edit/test/source_edit_test.dart
@@ -0,0 +1,139 @@
+// Copyright (c) 2020, 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 'package:yaml_edit/yaml_edit.dart';
+
+void main() {
+ group('SourceEdit', () {
+ group('fromJson', () {
+ test('converts from jsonEncode', () {
+ final sourceEditMap = {
+ 'offset': 1,
+ 'length': 2,
+ 'replacement': 'replacement string'
+ };
+ final sourceEdit = SourceEdit.fromJson(sourceEditMap);
+
+ expect(sourceEdit.offset, 1);
+ expect(sourceEdit.length, 2);
+ expect(sourceEdit.replacement, 'replacement string');
+ });
+
+ test('throws formatException if offset is non-int', () {
+ final sourceEditJson = {
+ 'offset': '1',
+ 'length': 2,
+ 'replacement': 'replacement string'
+ };
+
+ expect(
+ () => SourceEdit.fromJson(sourceEditJson), throwsFormatException);
+ });
+
+ test('throws formatException if length is non-int', () {
+ final sourceEditJson = {
+ 'offset': 1,
+ 'length': '2',
+ 'replacement': 'replacement string'
+ };
+
+ expect(
+ () => SourceEdit.fromJson(sourceEditJson), throwsFormatException);
+ });
+
+ test('throws formatException if replacement is non-string', () {
+ final sourceEditJson = {'offset': 1, 'length': 2, 'replacement': 3};
+
+ expect(
+ () => SourceEdit.fromJson(sourceEditJson), throwsFormatException);
+ });
+
+ test('throws formatException if a field is not present', () {
+ final sourceEditJson = {'offset': 1, 'length': 2};
+
+ expect(
+ () => SourceEdit.fromJson(sourceEditJson), throwsFormatException);
+ });
+ });
+
+ test('toString returns a nice string representation', () {
+ final sourceEdit = SourceEdit(1, 2, 'replacement string');
+ expect(sourceEdit.toString(),
+ equals('SourceEdit(1, 2, "replacement string")'));
+ });
+
+ group('hashCode', () {
+ test('returns same value for equal SourceEdits', () {
+ final sourceEdit1 = SourceEdit(1, 2, 'replacement string');
+ final sourceEdit2 = SourceEdit(1, 2, 'replacement string');
+ expect(sourceEdit1.hashCode, equals(sourceEdit2.hashCode));
+ });
+
+ test('returns different value for equal SourceEdits', () {
+ final sourceEdit1 = SourceEdit(1, 2, 'replacement string');
+ final sourceEdit2 = SourceEdit(1, 3, 'replacement string');
+ expect(sourceEdit1.hashCode == sourceEdit2.hashCode, equals(false));
+ });
+ });
+
+ group('toJson', () {
+ test('behaves as expected', () {
+ final sourceEdit = SourceEdit(1, 2, 'replacement string');
+ final sourceEditJson = sourceEdit.toJson();
+
+ expect(
+ sourceEditJson,
+ equals({
+ 'offset': 1,
+ 'length': 2,
+ 'replacement': 'replacement string'
+ }));
+ });
+
+ test('is compatible with fromJson', () {
+ final sourceEdit = SourceEdit(1, 2, 'replacement string');
+ final sourceEditJson = sourceEdit.toJson();
+ final newSourceEdit = SourceEdit.fromJson(sourceEditJson);
+
+ expect(newSourceEdit.offset, 1);
+ expect(newSourceEdit.length, 2);
+ expect(newSourceEdit.replacement, 'replacement string');
+ });
+ });
+
+ group('applyAll', () {
+ test('returns original string when empty list is passed in', () {
+ const original = 'YAML: YAML';
+ final result = SourceEdit.applyAll(original, []);
+
+ expect(result, original);
+ });
+ test('works with list of one SourceEdit', () {
+ const original = 'YAML: YAML';
+ final sourceEdits = [SourceEdit(6, 4, 'YAML Ain\'t Markup Language')];
+
+ final result = SourceEdit.applyAll(original, sourceEdits);
+
+ expect(result, "YAML: YAML Ain't Markup Language");
+ });
+ test('works with list of multiple SourceEdits', () {
+ const original = 'YAML: YAML';
+ final sourceEdits = [
+ SourceEdit(6, 4, "YAML Ain't Markup Language"),
+ SourceEdit(6, 4, "YAML Ain't Markup Language"),
+ SourceEdit(0, 4, "YAML Ain't Markup Language")
+ ];
+
+ final result = SourceEdit.applyAll(original, sourceEdits);
+
+ expect(
+ result,
+ "YAML Ain't Markup Language: YAML Ain't Markup Language Ain't Markup "
+ 'Language',
+ );
+ });
+ });
+ });
+}
diff --git a/pkgs/yaml_edit/test/special_test.dart b/pkgs/yaml_edit/test/special_test.dart
new file mode 100644
index 0000000..9e6c011
--- /dev/null
+++ b/pkgs/yaml_edit/test/special_test.dart
@@ -0,0 +1,111 @@
+// Copyright (c) 2020, 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 'package:yaml_edit/yaml_edit.dart';
+
+import 'test_utils.dart';
+
+void main() {
+ test('test if "No" is recognized as false', () {
+ final doc = YamlEditor('''
+~: null
+false: false
+No: No
+true: true
+''');
+ doc.update([null], 'tilde');
+ doc.update([false], false);
+ doc.update(['No'], 'no');
+ doc.update([true], 'true');
+
+ expect(doc.toString(), equals('''
+~: tilde
+false: false
+No: no
+true: "true"
+'''));
+
+ expectYamlBuilderValue(
+ doc, {null: 'tilde', false: false, 'No': 'no', true: 'true'});
+ });
+
+ test('array keys are recognized', () {
+ final doc = YamlEditor('{[1,2,3]: a}');
+ doc.update([
+ [1, 2, 3]
+ ], 'sums to 6');
+
+ expect(doc.toString(), equals('{[1,2,3]: sums to 6}'));
+ expectYamlBuilderValue(doc, {
+ [1, 2, 3]: 'sums to 6'
+ });
+ });
+
+ test('map keys are recognized', () {
+ final doc = YamlEditor('{{a: 1}: a}');
+ doc.update([
+ {'a': 1}
+ ], 'sums to 6');
+
+ expect(doc.toString(), equals('{{a: 1}: sums to 6}'));
+ expectYamlBuilderValue(doc, {
+ {'a': 1}: 'sums to 6'
+ });
+ });
+
+ test('documents can have directives', () {
+ final doc = YamlEditor('''%YAML 1.2
+--- text''');
+ doc.update([], 'test');
+
+ expect(doc.toString(), equals('%YAML 1.2\n--- test'));
+ expectYamlBuilderValue(doc, 'test');
+ });
+
+ test('tags should be removed if value is changed', () {
+ final doc = YamlEditor('''
+ - !!str a
+ - b
+ - !!int 42
+ - d
+''');
+ doc.update([2], 'test');
+
+ expect(doc.toString(), equals('''
+ - !!str a
+ - b
+ - test
+ - d
+'''));
+ expectYamlBuilderValue(doc, ['a', 'b', 'test', 'd']);
+ });
+
+ test('tags should be removed if key is changed', () {
+ final doc = YamlEditor('''
+!!str a: b
+c: !!int 42
+e: !!str f
+g: h
+!!str 23: !!bool false
+''');
+ doc.remove(['23']);
+
+ expect(doc.toString(), equals('''
+!!str a: b
+c: !!int 42
+e: !!str f
+g: h
+'''));
+ expectYamlBuilderValue(doc, {'a': 'b', 'c': 42, 'e': 'f', 'g': 'h'});
+ });
+
+ test('detect invalid extra closing bracket', () {
+ final doc = YamlEditor('''[ a, b ]''');
+ doc.appendToList([], 'c ]');
+
+ expect(doc.toString(), equals('''[ a, b , "c ]"]'''));
+ expectYamlBuilderValue(doc, ['a', 'b', 'c ]']);
+ });
+}
diff --git a/pkgs/yaml_edit/test/splice_test.dart b/pkgs/yaml_edit/test/splice_test.dart
new file mode 100644
index 0000000..3efeea1
--- /dev/null
+++ b/pkgs/yaml_edit/test/splice_test.dart
@@ -0,0 +1,191 @@
+// Copyright (c) 2020, 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 'package:yaml_edit/yaml_edit.dart';
+
+import 'test_utils.dart';
+
+void main() {
+ test(
+ 'throws RangeError if invalid index + deleteCount combination is '
+ 'passed in', () {
+ final doc = YamlEditor('[0, 0]');
+ expect(() => doc.spliceList([], 1, 5, [1, 2]), throwsRangeError);
+ });
+
+ group('block list', () {
+ test('(1)', () {
+ final doc = YamlEditor('''
+- 0
+- 0
+''');
+ final nodes = doc.spliceList([], 1, 1, [1, 2]);
+ expect(doc.toString(), equals('''
+- 0
+- 1
+- 2
+'''));
+
+ expectDeepEquals(nodes.toList(), [0]);
+ });
+
+ test('(2)', () {
+ final doc = YamlEditor('''
+- 0
+- 0
+''');
+ final nodes = doc.spliceList([], 0, 2, [0, 1, 2]);
+ expect(doc.toString(), equals('''
+- 0
+- 1
+- 2
+'''));
+
+ expectDeepEquals(nodes.toList(), [0, 0]);
+ });
+
+ test('(3)', () {
+ final doc = YamlEditor('''
+- Jan
+- March
+- April
+- June
+''');
+ final nodes = doc.spliceList([], 1, 0, ['Feb']);
+ expect(doc.toString(), equals('''
+- Jan
+- Feb
+- March
+- April
+- June
+'''));
+
+ expectDeepEquals(nodes.toList(), []);
+
+ final nodes2 = doc.spliceList([], 4, 1, ['May']);
+ expect(doc.toString(), equals('''
+- Jan
+- Feb
+- March
+- April
+- May
+'''));
+
+ expectDeepEquals(nodes2.toList(), ['June']);
+ });
+
+ test('nested block list (inline)', () {
+ final doc = YamlEditor('''
+- - Jan
+ - Tuesday
+ - April
+''');
+
+ final nodes = doc.spliceList([0], 1, 1, ['Feb', 'March']);
+
+ expectDeepEquals(nodes.toList(), ['Tuesday']);
+
+ expect(doc.toString(), equals('''
+- - Jan
+ - Feb
+ - March
+ - April
+'''));
+ });
+
+ test('nested block list (inline with multiple new lines)', () {
+ final doc = YamlEditor('''
+-
+
+
+
+
+ - Jan
+ - Tuesday
+ - April
+''');
+
+ final nodes = doc.spliceList([0], 1, 1, ['Feb', 'March']);
+
+ expectDeepEquals(nodes.toList(), ['Tuesday']);
+
+ expect(doc.toString(), equals('''
+-
+
+
+
+
+ - Jan
+ - Feb
+ - March
+ - April
+'''));
+ });
+
+ test('update before nested list', () {
+ final doc = YamlEditor('''
+key:
+ - value
+ - another
+ - - nested
+ - continued
+''');
+
+ final nodes = doc.spliceList(['key'], 2, 0, ['spliced']);
+
+ expectDeepEquals(nodes.toList(), []);
+
+ expect(doc.toString(), equals('''
+key:
+ - value
+ - another
+ - spliced
+ - - nested
+ - continued
+'''));
+ });
+
+ test('replace nested block', () {
+ final doc = YamlEditor('''
+key:
+ - value
+ - another
+ - - nested
+ - continued
+''');
+
+ final nodes = doc.spliceList(['key'], 2, 1, ['spliced']);
+
+ expectDeepEquals(nodes.toList(), [
+ ['nested', 'continued'],
+ ]);
+
+ expect(doc.toString(), equals('''
+key:
+ - value
+ - another
+ - spliced
+'''));
+ });
+ });
+
+ group('flow list', () {
+ test('(1)', () {
+ final doc = YamlEditor('[0, 0]');
+ final nodes = doc.spliceList([], 1, 1, [1, 2]);
+ expect(doc.toString(), equals('[0, 1, 2]'));
+
+ expectDeepEquals(nodes.toList(), [0]);
+ });
+
+ test('(2)', () {
+ final doc = YamlEditor('[0, 0]');
+ final nodes = doc.spliceList([], 0, 2, [0, 1, 2]);
+ expect(doc.toString(), equals('[0, 1, 2]'));
+
+ expectDeepEquals(nodes.toList(), [0, 0]);
+ });
+ });
+}
diff --git a/pkgs/yaml_edit/test/string_test.dart b/pkgs/yaml_edit/test/string_test.dart
new file mode 100644
index 0000000..5ea7e73
--- /dev/null
+++ b/pkgs/yaml_edit/test/string_test.dart
@@ -0,0 +1,49 @@
+// Copyright (c) 2023, 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 'package:yaml/yaml.dart';
+import 'package:yaml_edit/yaml_edit.dart';
+
+final _testStrings = [
+ "this is a fairly' long string with\nline breaks",
+ 'whitespace\n after line breaks',
+ 'whitespace\n \nbetween line breaks',
+ '\n line break at the start',
+ 'whitespace and line breaks at end 1\n ',
+ 'whitespace and line breaks at end 2 \n \n',
+ 'whitespace and line breaks at end 3 \n\n',
+ 'whitespace and line breaks at end 4 \n\n ',
+ '\n\nline with multiple trailing line break \n\n\n\n\n',
+ 'word',
+ 'foo bar',
+ 'foo\nbar',
+ '"',
+ '\'',
+ 'word"word',
+ 'word\'word'
+];
+
+final _scalarStyles = [
+ ScalarStyle.ANY,
+ ScalarStyle.DOUBLE_QUOTED,
+ ScalarStyle.FOLDED,
+ ScalarStyle.LITERAL,
+ ScalarStyle.PLAIN,
+ ScalarStyle.SINGLE_QUOTED,
+];
+
+void main() {
+ for (final style in _scalarStyles) {
+ for (var i = 0; i < _testStrings.length; i++) {
+ final testString = _testStrings[i];
+ test('Root $style string (${i + 1})', () {
+ final yamlEditor = YamlEditor('');
+ yamlEditor.update([], wrapAsYamlNode(testString, scalarStyle: style));
+ final yaml = yamlEditor.toString();
+ expect(loadYaml(yaml), equals(testString));
+ });
+ }
+ }
+}
diff --git a/pkgs/yaml_edit/test/test_case.dart b/pkgs/yaml_edit/test/test_case.dart
new file mode 100644
index 0000000..107e4f7
--- /dev/null
+++ b/pkgs/yaml_edit/test/test_case.dart
@@ -0,0 +1,299 @@
+// Copyright (c) 2020, 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:path/path.dart' as p;
+import 'package:test/test.dart';
+import 'package:yaml/yaml.dart';
+import 'package:yaml_edit/src/utils.dart';
+import 'package:yaml_edit/yaml_edit.dart';
+
+import 'test_utils.dart';
+
+/// Interface for creating golden Test cases
+class TestCases {
+ final List<_TestCase> _testCases;
+
+ /// Creates a [TestCases] object based on test directory and golden directory
+ /// path.
+ static Future<TestCases> getTestCases(Uri testDirUri, Uri goldDirUri) async {
+ final testDir = Directory.fromUri(testDirUri);
+
+ if (!testDir.existsSync()) return TestCases([]);
+
+ /// Recursively grab all the files in the testing directory.
+ return TestCases(await testDir
+ .list(recursive: true, followLinks: false)
+ .where((entity) => entity.path.endsWith('.test'))
+ .map((entity) => entity.uri)
+ .map((inputUri) {
+ final inputWithoutExtension =
+ p.basenameWithoutExtension(inputUri.toFilePath());
+ final goldenUri = goldDirUri.resolve('./$inputWithoutExtension.golden');
+
+ return _TestCase(inputUri, goldenUri);
+ }).toList());
+ }
+
+ /// Tests all the [_TestCase]s if the golden files exist, create the golden
+ /// files otherwise.
+ void test() {
+ var tested = 0;
+ var created = 0;
+
+ for (final testCase in _testCases) {
+ testCase.testOrCreate();
+ if (testCase.state == _TestCaseStates.testedGoldenFile) {
+ tested++;
+ } else if (testCase.state == _TestCaseStates.createdGoldenFile) {
+ created++;
+ }
+ }
+
+ print('Successfully tested $tested inputs against golden files, created '
+ '$created golden files');
+ }
+
+ TestCases(this._testCases);
+
+ int get length => _testCases.length;
+}
+
+/// Enum representing the different states of [_TestCase]s.
+enum _TestCaseStates { initialized, createdGoldenFile, testedGoldenFile }
+
+/// Interface for a golden test case. Handles the logic for test conduct/golden
+/// test update accordingly.
+class _TestCase {
+ final Uri inputUri;
+ final Uri goldenUri;
+ final List<String> states = [];
+
+ late String info;
+ late YamlEditor yamlBuilder;
+ late List<_YamlModification> modifications;
+
+ String inputLineEndings = '\n';
+
+ _TestCaseStates state = _TestCaseStates.initialized;
+
+ _TestCase(this.inputUri, this.goldenUri) {
+ final inputFile = File.fromUri(inputUri);
+ if (!inputFile.existsSync()) {
+ throw Exception('Input File does not exist!');
+ }
+
+ _initialize(inputFile);
+ }
+
+ /// Initializes the [_TestCase] by reading the corresponding [inputFile] and
+ /// parsing the different portions, and then running the input yaml against
+ /// the specified modifications.
+ ///
+ /// Precondition: [inputFile] must exist, and inputs must be well-formatted.
+ void _initialize(File inputFile) {
+ final input = inputFile.readAsStringSync();
+
+ final inputLineEndings = getLineEnding(input);
+ final inputElements = input.split('---$inputLineEndings');
+
+ if (inputElements.length != 3) {
+ throw AssertionError('File ${inputFile.path} is not properly formatted.');
+ }
+
+ info = inputElements[0];
+ yamlBuilder = YamlEditor(inputElements[1]);
+ final rawModifications =
+ _getValueFromYamlNode(loadYaml(inputElements[2]) as YamlNode) as List;
+ modifications = _parseModifications(rawModifications);
+
+ /// Adds the initial state as well, so we can check that the simplest
+ /// parse -> immediately dump does not affect the string.
+ states.add(yamlBuilder.toString());
+
+ _performModifications();
+ }
+
+ void _performModifications() {
+ for (final mod in modifications) {
+ _performModification(mod);
+ states.add(yamlBuilder.toString());
+ }
+ }
+
+ void _performModification(_YamlModification mod) {
+ switch (mod.method) {
+ case YamlModificationMethod.update:
+ yamlBuilder.update(mod.path, mod.value);
+ return;
+ case YamlModificationMethod.remove:
+ yamlBuilder.remove(mod.path);
+ return;
+ case YamlModificationMethod.appendTo:
+ yamlBuilder.appendToList(mod.path, mod.value);
+ return;
+ case YamlModificationMethod.prependTo:
+ yamlBuilder.prependToList(mod.path, mod.value);
+ return;
+ case YamlModificationMethod.insert:
+ yamlBuilder.insertIntoList(mod.path, mod.index, mod.value);
+ return;
+ case YamlModificationMethod.splice:
+ yamlBuilder.spliceList(
+ mod.path, mod.index, mod.deleteCount, mod.value as List);
+ return;
+ }
+ }
+
+ void testOrCreate() {
+ final goldenFile = File.fromUri(goldenUri);
+ if (!goldenFile.existsSync()) {
+ createGoldenFile(goldenFile);
+ } else {
+ testGoldenFile(goldenFile);
+ }
+ }
+
+ void createGoldenFile(File goldenFile) {
+ /// Assumes user wants the golden file to have the same line endings as
+ /// the input file.
+ final goldenOutput = states.join('---$inputLineEndings');
+
+ goldenFile.writeAsStringSync(goldenOutput);
+ state = _TestCaseStates.createdGoldenFile;
+ }
+
+ /// Tests the golden file. Ensures that the number of states are the same, and
+ /// that the individual states are the same.
+ void testGoldenFile(File goldenFile) {
+ final inputFileName = p.basename(inputUri.toFilePath());
+ final golden = goldenFile.readAsStringSync();
+ final goldenStates = golden.split('---${getLineEnding(golden)}');
+
+ group('testing $inputFileName - input and golden files have', () {
+ test('same number of states', () {
+ expect(states.length, equals(goldenStates.length));
+ });
+
+ for (var i = 0; i < states.length; i++) {
+ test('same state $i', () {
+ expect(states[i], equals(goldenStates[i]));
+ });
+ }
+ });
+
+ state = _TestCaseStates.testedGoldenFile;
+ }
+}
+
+/// Converts [yamlList] into a Dart list.
+List _getValueFromYamlList(YamlList yamlList) {
+ return yamlList.value.map((n) {
+ if (n is YamlNode) return _getValueFromYamlNode(n);
+ return n;
+ }).toList();
+}
+
+/// Converts [yamlMap] into a Dart Map.
+Map _getValueFromYamlMap(YamlMap yamlMap) {
+ final keys = yamlMap.keys;
+ final result = {};
+ for (final key in keys) {
+ final value = yamlMap[key];
+
+ if (value is YamlNode) {
+ result[key] = _getValueFromYamlNode(value);
+ } else {
+ result[key] = value;
+ }
+ }
+
+ return result;
+}
+
+/// Converts a [YamlNode] into a Dart object.
+dynamic _getValueFromYamlNode(YamlNode node) {
+ if (node is YamlList) {
+ return _getValueFromYamlList(node);
+ }
+ if (node is YamlMap) {
+ return _getValueFromYamlMap(node);
+ }
+ return node.value;
+}
+
+/// Converts the list of modifications from the raw input to [_YamlModification]
+/// objects.
+List<_YamlModification> _parseModifications(List<dynamic> modifications) {
+ return modifications.map((mod) {
+ if (mod is! List) throw UnimplementedError();
+ Object? value;
+ var index = 0;
+ var deleteCount = 0;
+ final method = _getModificationMethod(mod[0] as String);
+
+ final path = mod[1] as List;
+
+ if (method == YamlModificationMethod.appendTo ||
+ method == YamlModificationMethod.update ||
+ method == YamlModificationMethod.prependTo) {
+ value = mod[2];
+ } else if (method == YamlModificationMethod.insert) {
+ index = mod[2] as int;
+ value = mod[3];
+ } else if (method == YamlModificationMethod.splice) {
+ index = mod[2] as int;
+ deleteCount = mod[3] as int;
+
+ if (mod[4] is! List) {
+ throw ArgumentError('Invalid array ${mod[4]} used in splice');
+ }
+
+ value = mod[4];
+ }
+
+ return _YamlModification(method, path, index, value, deleteCount);
+ }).toList();
+}
+
+/// Gets the YAML modification method corresponding to [method]
+YamlModificationMethod _getModificationMethod(String method) {
+ switch (method) {
+ case 'update':
+ return YamlModificationMethod.update;
+ case 'remove':
+ return YamlModificationMethod.remove;
+ case 'append':
+ case 'appendTo':
+ return YamlModificationMethod.appendTo;
+ case 'prepend':
+ case 'prependTo':
+ return YamlModificationMethod.prependTo;
+ case 'insert':
+ case 'insertIn':
+ return YamlModificationMethod.insert;
+ case 'splice':
+ return YamlModificationMethod.splice;
+ default:
+ throw Exception('$method not recognized!');
+ }
+}
+
+/// Class representing an abstract YAML modification to be performed
+class _YamlModification {
+ final YamlModificationMethod method;
+ final List<Object?> path;
+ final int index;
+ final dynamic value;
+ final int deleteCount;
+
+ _YamlModification(
+ this.method, this.path, this.index, this.value, this.deleteCount);
+
+ @override
+ String toString() =>
+ 'method: $method, path: $path, index: $index, value: $value, '
+ 'deleteCount: $deleteCount';
+}
diff --git a/pkgs/yaml_edit/test/test_utils.dart b/pkgs/yaml_edit/test/test_utils.dart
new file mode 100644
index 0000000..f60467d
--- /dev/null
+++ b/pkgs/yaml_edit/test/test_utils.dart
@@ -0,0 +1,45 @@
+// Copyright (c) 2020, 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 'package:yaml_edit/src/equality.dart';
+import 'package:yaml_edit/src/errors.dart';
+import 'package:yaml_edit/yaml_edit.dart';
+
+/// Asserts that a string containing a single YAML document is unchanged
+/// when dumped right after loading.
+void Function() expectLoadPreservesYAML(String source) {
+ final doc = YamlEditor(source);
+ return () => expect(doc.toString(), equals(source));
+}
+
+/// Asserts that [builder] has the same internal value as [expected].
+void expectYamlBuilderValue(YamlEditor builder, Object expected) {
+ final builderValue = builder.parseAt([]);
+ expectDeepEquals(builderValue, expected);
+}
+
+/// Asserts that [actual] has the same internal value as [expected].
+void expectDeepEquals(Object? actual, Object expected) {
+ expect(
+ actual, predicate((actual) => deepEquals(actual, expected), '$expected'));
+}
+
+Matcher notEquals(dynamic expected) => isNot(equals(expected));
+
+/// A matcher for functions that throw [PathError].
+Matcher throwsPathError = throwsA(isA<PathError>());
+
+/// A matcher for functions that throw [AliasException].
+Matcher throwsAliasException = throwsA(isA<AliasException>());
+
+/// Enum to hold the possible modification methods.
+enum YamlModificationMethod {
+ appendTo,
+ insert,
+ prependTo,
+ remove,
+ splice,
+ update,
+}
diff --git a/pkgs/yaml_edit/test/testdata/README.md b/pkgs/yaml_edit/test/testdata/README.md
new file mode 100644
index 0000000..5145264
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/README.md
@@ -0,0 +1,79 @@
+# Golden Testing
+
+This folder contains the files used for Golden testing performed by [golden_test.dart](../golden_test.dart).
+
+With golden testing, we are able to quickly ensure that our output conforms to our expectations given input parameters, which are extremely valuable especially on complex test cases not easily captured by unit tests.
+
+When the tests are run (see [Running Tests](#Running-Tests)), the series of specified modifications will be performed on the input, and the various output states will be compared against the `.golden` files if they exist. Otherwise, if the `.golden` files do not exist (such as in the case of a new test case), they will be created.
+
+## Table of Contents
+
+1. [Running Tests](#Running-Tests)
+1. [Input Format](#Input-Format)
+1. [Adding Test Cases](#Adding-Test-Cases)
+1. [Output Format](#Output-Format)
+
+## Running Tests
+
+By default, golden testing is performed with `pub run test`. If we only wanted to
+performed golden testing, simply do: `pub run test test/golden_test.dart`.
+
+## Input Format
+
+Input files have the following format:
+
+```
+INFORMATION (e.g. description) - parsed as text
+---
+INPUT - parsed as YAML
+---
+Modifications - parsed as YAML, must be a list.
+```
+
+The information section is meant for a brief description of your test, and other further elaboration on what your test is targeted at (e.g. modification of complex keys). The Input section should be the YAML that you wish to parse, and the modifications section should be a list of modification operations, formatted as a YAML list. The valid list of modifications are as follows:
+
+- [update, [ path ], newValue]
+- [remove, [ path ], keyOrIndex]
+- [append, [ collectionPath ], newValue]
+
+An example of what an input file might look like is:
+
+```
+BASIC LIST TEST - Ensures that the basic list operations work.
+---
+- 0
+- 1
+- 2
+- 3
+---
+- [remove, [1]]
+- [append, [], 4]
+```
+
+Note that the parser uses `\n---\n` as the delimiter to separate the different sections.
+
+## Adding Test Cases
+
+To add test cases, simple create `<your-test-name>.test` files in `/test/testdata/input` in the format explained in [Input Format](#Input-Format). When the test script is first run, the respective `.golden` files will be created in `/test/testdata/output`, you should check to ensure that the output is as expected since future collaborators will be counting on your output!
+
+## Output Format
+
+The output `.golden` files contain a series of YAML strings representing the state of the YAML after each specified modification, with the first string being the initial state as specified in the output. These states are separated by `\n---\n` as a delimiter. For example, the output file for the sample input file above is:
+
+```
+- 0
+- 1
+- 2
+- 3
+---
+- 0
+- 2
+- 3
+---
+- 0
+- 2
+- 3
+- 4
+```
+
+The first state is the input, the second state is the first state with the removal of the element at index 1, and the last state is the result of the second state with the insertion of the element 4 into the list.
diff --git a/pkgs/yaml_edit/test/testdata/input/allowed_characters_in_keys.test b/pkgs/yaml_edit/test/testdata/input/allowed_characters_in_keys.test
new file mode 100644
index 0000000..1057df0
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/input/allowed_characters_in_keys.test
@@ -0,0 +1,9 @@
+ALLOWED CHARACTERS IN KEYS
+---
+test: test
+---
+- [update, ["a!\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~"], safe]
+- [update, ['?foo'], safe question mark]
+- [update, [':foo'], safe colon]
+- [update, ['-foo'], safe dash]
+- [update, ['this is#not'], a comment]
\ No newline at end of file
diff --git a/pkgs/yaml_edit/test/testdata/input/basic.test b/pkgs/yaml_edit/test/testdata/input/basic.test
new file mode 100644
index 0000000..02c082d
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/input/basic.test
@@ -0,0 +1,5 @@
+BASIC TEST
+---
+YAML: YAML Ain't Markup Language
+---
+- [update, ['YAML'], hi]
\ No newline at end of file
diff --git a/pkgs/yaml_edit/test/testdata/input/basic_list.test b/pkgs/yaml_edit/test/testdata/input/basic_list.test
new file mode 100644
index 0000000..3ba942b
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/input/basic_list.test
@@ -0,0 +1,12 @@
+BASIC LIST TEST - Ensures that the basic list operations work.
+---
+- 0
+- 1
+- 2
+- 3
+---
+- [remove, [1]]
+- [append, [], 4]
+- [update, [2], 5]
+- [prepend, [], 6]
+- [splice, [], 2, 3, [7, 8]]
\ No newline at end of file
diff --git a/pkgs/yaml_edit/test/testdata/input/ignore_key_order.test b/pkgs/yaml_edit/test/testdata/input/ignore_key_order.test
new file mode 100644
index 0000000..e9d6201
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/input/ignore_key_order.test
@@ -0,0 +1,7 @@
+IGNORES KEY ORDER
+---
+Z: 1
+D: 2
+F: 3
+---
+- [update, ['A'], 4]
\ No newline at end of file
diff --git a/pkgs/yaml_edit/test/testdata/input/issue_55.test b/pkgs/yaml_edit/test/testdata/input/issue_55.test
new file mode 100644
index 0000000..ff1ac90
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/input/issue_55.test
@@ -0,0 +1,11 @@
+TEST FOR ISSUE #55 -- https://github.com/dart-lang/yaml_edit/issues/55
+---
+name: sample
+version: 0.1.0
+environment:
+ sdk: ^3.0.0
+dependencies:
+dev_dependencies:
+ retry:
+---
+ - [remove, ['dev_dependencies', 'retry']]
diff --git a/pkgs/yaml_edit/test/testdata/input/nested_block_map.test b/pkgs/yaml_edit/test/testdata/input/nested_block_map.test
new file mode 100644
index 0000000..bc7c35b
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/input/nested_block_map.test
@@ -0,0 +1,11 @@
+NESTED BLOCK MAP
+---
+a: 1
+b:
+ d: 4
+ e: 5
+c: 3
+---
+- [update, ['b', 'e'], 6]
+- [update, ['b', 'e'], [1,2,3]]
+- [update, ['b', 'f'], 6]
\ No newline at end of file
diff --git a/pkgs/yaml_edit/test/testdata/input/pubspec.test b/pkgs/yaml_edit/test/testdata/input/pubspec.test
new file mode 100644
index 0000000..5559e1d
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/input/pubspec.test
@@ -0,0 +1,29 @@
+TESTING WITH A SAMPLE PUBSPEC
+---
+name: yaml_edit
+description: A library for YAML manipulation with comment and whitespace preservation.
+version: 1.0.0
+
+homepage: https://github.com/google/dart-neats/tree/master/yaml_edit
+
+repository: https://github.com/google/dart-neats.git
+
+issue_tracker: https://github.com/google/dart-neats/labels/pkg:yaml_edit
+
+environment:
+ sdk: ">=2.4.0 <3.0.0"
+
+dependencies:
+ meta: ^1.1.8 # To annotate
+ # quiver_hashcode
+ quiver_hashcode: ^2.0.0 # For hashcodes
+ # yaml
+ yaml: ^2.2.1 # For YAML
+
+dev_dependencies:
+
+---
+- [update, ['dependencies', 'yaml'], ^3.2.0]
+- [update, ['dependencies', 'retry'], ^3.0.1]
+- [remove, ['dependencies', 'meta']]
+- [update, ['dev_dependencies'], {'test': '^1.14.4'}]
\ No newline at end of file
diff --git a/pkgs/yaml_edit/test/testdata/input/remove_block_list.test b/pkgs/yaml_edit/test/testdata/input/remove_block_list.test
new file mode 100644
index 0000000..cf61522
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/input/remove_block_list.test
@@ -0,0 +1,14 @@
+REMOVE FROM LIST IN BLOCK MODE
+---
+ - true
+ - test:
+ - foo: true
+ bar:
+ - baz:
+ - nested:
+ foo:
+---
+ - [remove, [1, 'test']]
+ - [remove, [2, 'bar']]
+ - [remove, [3, 'baz']]
+ - [remove, [4, 'nested', 'foo']]
diff --git a/pkgs/yaml_edit/test/testdata/input/remove_flow_map.test b/pkgs/yaml_edit/test/testdata/input/remove_flow_map.test
new file mode 100644
index 0000000..6bbf7ab
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/input/remove_flow_map.test
@@ -0,0 +1,44 @@
+REMOVE FROM MAP IN FLOW MODE
+---
+A: true
+B: {foo: }
+C: {
+ foo:,bar:true
+}
+D: {
+ foo:
+ ,bar:true
+}
+E: {
+ foo: # comment
+ ,bar:true
+}
+F: {
+ # comment
+ foo:
+ ,bar:true
+}
+G: {
+ # comment
+ foo:
+ # comment
+ ,bar:true
+}
+H: {
+ foo: # comment
+ ,
+ bar:true
+}
+I: {
+ bar: true, foo: }
+J: { foo : }
+---
+- [remove, [B, foo]]
+- [remove, [C, foo]]
+- [remove, [D, foo]]
+- [remove, [E, foo]]
+- [remove, [F, foo]]
+- [remove, [G, foo]]
+- [remove, [H, foo]]
+- [remove, [I, foo]]
+- [remove, [J, foo]]
diff --git a/pkgs/yaml_edit/test/testdata/input/remove_key.test b/pkgs/yaml_edit/test/testdata/input/remove_key.test
new file mode 100644
index 0000000..b28818c
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/input/remove_key.test
@@ -0,0 +1,6 @@
+REMOVE KEY FROM MAP
+---
+foo: true
+bar: false
+---
+- [remove, [bar]]
diff --git a/pkgs/yaml_edit/test/testdata/input/remove_key_with_trailing_comma.test b/pkgs/yaml_edit/test/testdata/input/remove_key_with_trailing_comma.test
new file mode 100644
index 0000000..ce67551
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/input/remove_key_with_trailing_comma.test
@@ -0,0 +1,6 @@
+REMOVE KEY FROM MAP WITH TRAILING COMMA
+---
+foo: true
+bar: false # remove this comment
+---
+- [remove, [bar]]
diff --git a/pkgs/yaml_edit/test/testdata/input/remove_nested_key.test b/pkgs/yaml_edit/test/testdata/input/remove_nested_key.test
new file mode 100644
index 0000000..201caca
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/input/remove_nested_key.test
@@ -0,0 +1,9 @@
+REMOVE NESTED KEY FROM MAP
+---
+A: true
+B:
+ foo: true
+ bar: true
+---
+- [remove, [B, foo]]
+- [remove, [B, bar]]
diff --git a/pkgs/yaml_edit/test/testdata/input/remove_nested_key_with_null.test b/pkgs/yaml_edit/test/testdata/input/remove_nested_key_with_null.test
new file mode 100644
index 0000000..2a66559
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/input/remove_nested_key_with_null.test
@@ -0,0 +1,7 @@
+REMOVE NESTED KEY FROM MAP WITH NULL
+---
+A: true
+B:
+ foo:
+---
+- [remove, [B, foo]]
diff --git a/pkgs/yaml_edit/test/testdata/input/remove_nested_key_with_trailing_comma.test b/pkgs/yaml_edit/test/testdata/input/remove_nested_key_with_trailing_comma.test
new file mode 100644
index 0000000..35ad665
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/input/remove_nested_key_with_trailing_comma.test
@@ -0,0 +1,7 @@
+REMOVE NESTED KEY FROM MAP WITH TRAILING COMMA
+---
+A: true
+B:
+ bar: false # remove this comment
+---
+- [remove, [B, bar]]
diff --git a/pkgs/yaml_edit/test/testdata/input/respect_key_order.test b/pkgs/yaml_edit/test/testdata/input/respect_key_order.test
new file mode 100644
index 0000000..dee95f4
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/input/respect_key_order.test
@@ -0,0 +1,6 @@
+RESPECTS THE KEY ORDER
+---
+A: first
+C: third
+---
+- [update, [B], second]
\ No newline at end of file
diff --git a/pkgs/yaml_edit/test/testdata/input/spaces.test b/pkgs/yaml_edit/test/testdata/input/spaces.test
new file mode 100644
index 0000000..d74fe73
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/input/spaces.test
@@ -0,0 +1,8 @@
+SPACE IN STRING - update a string that starts and ends with space
+---
+a: ' space around me '
+c: 'hello world'
+---
+- [update, ['b'], ' also spaced ']
+- [update, ['d'], ' ']
+- [update, ["\ne"], with newline]
\ No newline at end of file
diff --git a/pkgs/yaml_edit/test/testdata/input/special_keywords.test b/pkgs/yaml_edit/test/testdata/input/special_keywords.test
new file mode 100644
index 0000000..0b3b81f
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/input/special_keywords.test
@@ -0,0 +1,11 @@
+SPECIAL KEYWORDS
+---
+~: null
+false: false
+No: No
+true: true
+---
+- [update, [null], tilde]
+- [update, [false], false]
+- [update, [No], no]
+- [update, [true], True]
\ No newline at end of file
diff --git a/pkgs/yaml_edit/test/testdata/input/splice_list_in_nested_block_with_weird_spaces.test b/pkgs/yaml_edit/test/testdata/input/splice_list_in_nested_block_with_weird_spaces.test
new file mode 100644
index 0000000..ed9b990
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/input/splice_list_in_nested_block_with_weird_spaces.test
@@ -0,0 +1,13 @@
+SPLICE LIST IN A NESTED BLOCK LIST WITH WEIRD SPACES
+---
+key:
+ - - bar1
+ - bar2
+ - - foo
+ - - baz
+---
+ - [splice, [key, 0], 0, 0, ['pre-bar1']]
+ - [splice, [key, 0], 2, 0, ['post-bar2']]
+ - [splice, [key, 2], 1, 0, ['post-baz']]
+ - [splice, [key, 2], 0, 0, ['pre-baz']]
+ - [splice, [key, 1], 0, 0, ['pre-foo']]
\ No newline at end of file
diff --git a/pkgs/yaml_edit/test/testdata/input/splice_list_in_nested_block_without_initial_spaces.test b/pkgs/yaml_edit/test/testdata/input/splice_list_in_nested_block_without_initial_spaces.test
new file mode 100644
index 0000000..52c01b5
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/input/splice_list_in_nested_block_without_initial_spaces.test
@@ -0,0 +1,13 @@
+SPLICE LIST IN A NESTED BLOCK LIST WITHOUT INITIAL SPACES
+---
+key:
+- - bar1
+ - bar2
+- - foo
+- - baz
+---
+ - [splice, [key, 0], 0, 0, ['pre-bar1']]
+ - [splice, [key, 0], 2, 0, ['post-bar2']]
+ - [splice, [key, 2], 1, 0, ['post-baz']]
+ - [splice, [key, 2], 0, 0, ['pre-baz']]
+ - [splice, [key, 1], 0, 0, ['pre-foo']]
diff --git a/pkgs/yaml_edit/test/testdata/input/splicelist_in_nested_block_list.test b/pkgs/yaml_edit/test/testdata/input/splicelist_in_nested_block_list.test
new file mode 100644
index 0000000..da6c7c3
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/input/splicelist_in_nested_block_list.test
@@ -0,0 +1,13 @@
+SLICE LIST IN NESTED BLOCK LIST
+---
+key:
+ - foo:
+ - - bar:
+ - - - false
+ - - - false
+ - - - false
+---
+- [splice, [key], 0, 0, ['pre-foo']]
+- [splice, [key, 1, 'foo', 0], 0, 1, ['test']]
+- [splice, [key, 2], 0, 0, ['test']]
+- [splice, [key], 4, 1, ['tail-foo']]
diff --git a/pkgs/yaml_edit/test/testdata/output/allowed_characters_in_keys.golden b/pkgs/yaml_edit/test/testdata/output/allowed_characters_in_keys.golden
new file mode 100644
index 0000000..3fd7885
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/output/allowed_characters_in_keys.golden
@@ -0,0 +1,26 @@
+test: test
+---
+test: test
+"a!\"#$%&'()*+,-.\/09:;<=>?@AZ[\\]^_`az{|}~": safe
+---
+test: test
+"a!\"#$%&'()*+,-.\/09:;<=>?@AZ[\\]^_`az{|}~": safe
+?foo: safe question mark
+---
+test: test
+"a!\"#$%&'()*+,-.\/09:;<=>?@AZ[\\]^_`az{|}~": safe
+?foo: safe question mark
+:foo: safe colon
+---
+test: test
+"a!\"#$%&'()*+,-.\/09:;<=>?@AZ[\\]^_`az{|}~": safe
+?foo: safe question mark
+:foo: safe colon
+-foo: safe dash
+---
+test: test
+"a!\"#$%&'()*+,-.\/09:;<=>?@AZ[\\]^_`az{|}~": safe
+?foo: safe question mark
+:foo: safe colon
+-foo: safe dash
+this is#not: a comment
diff --git a/pkgs/yaml_edit/test/testdata/output/basic.golden b/pkgs/yaml_edit/test/testdata/output/basic.golden
new file mode 100644
index 0000000..e200f97
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/output/basic.golden
@@ -0,0 +1,3 @@
+YAML: YAML Ain't Markup Language
+---
+YAML: hi
diff --git a/pkgs/yaml_edit/test/testdata/output/basic_list.golden b/pkgs/yaml_edit/test/testdata/output/basic_list.golden
new file mode 100644
index 0000000..c3a2b65
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/output/basic_list.golden
@@ -0,0 +1,29 @@
+- 0
+- 1
+- 2
+- 3
+---
+- 0
+- 2
+- 3
+---
+- 0
+- 2
+- 3
+- 4
+---
+- 0
+- 2
+- 5
+- 4
+---
+- 6
+- 0
+- 2
+- 5
+- 4
+---
+- 6
+- 0
+- 7
+- 8
diff --git a/pkgs/yaml_edit/test/testdata/output/ignore_key_order.golden b/pkgs/yaml_edit/test/testdata/output/ignore_key_order.golden
new file mode 100644
index 0000000..93cc9df
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/output/ignore_key_order.golden
@@ -0,0 +1,8 @@
+Z: 1
+D: 2
+F: 3
+---
+Z: 1
+D: 2
+F: 3
+A: 4
diff --git a/pkgs/yaml_edit/test/testdata/output/issue_55.golden b/pkgs/yaml_edit/test/testdata/output/issue_55.golden
new file mode 100644
index 0000000..f752a14
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/output/issue_55.golden
@@ -0,0 +1,15 @@
+name: sample
+version: 0.1.0
+environment:
+ sdk: ^3.0.0
+dependencies:
+dev_dependencies:
+ retry:
+---
+name: sample
+version: 0.1.0
+environment:
+ sdk: ^3.0.0
+dependencies:
+dev_dependencies:
+ {}
diff --git a/pkgs/yaml_edit/test/testdata/output/nested_block_map.golden b/pkgs/yaml_edit/test/testdata/output/nested_block_map.golden
new file mode 100644
index 0000000..d28bf6c
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/output/nested_block_map.golden
@@ -0,0 +1,30 @@
+a: 1
+b:
+ d: 4
+ e: 5
+c: 3
+---
+a: 1
+b:
+ d: 4
+ e: 6
+c: 3
+---
+a: 1
+b:
+ d: 4
+ e:
+ - 1
+ - 2
+ - 3
+c: 3
+---
+a: 1
+b:
+ d: 4
+ e:
+ - 1
+ - 2
+ - 3
+ f: 6
+c: 3
diff --git a/pkgs/yaml_edit/test/testdata/output/pubspec.golden b/pkgs/yaml_edit/test/testdata/output/pubspec.golden
new file mode 100644
index 0000000..ffbe8ba
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/output/pubspec.golden
@@ -0,0 +1,116 @@
+name: yaml_edit
+description: A library for YAML manipulation with comment and whitespace preservation.
+version: 1.0.0
+
+homepage: https://github.com/google/dart-neats/tree/master/yaml_edit
+
+repository: https://github.com/google/dart-neats.git
+
+issue_tracker: https://github.com/google/dart-neats/labels/pkg:yaml_edit
+
+environment:
+ sdk: ">=2.4.0 <3.0.0"
+
+dependencies:
+ meta: ^1.1.8 # To annotate
+ # quiver_hashcode
+ quiver_hashcode: ^2.0.0 # For hashcodes
+ # yaml
+ yaml: ^2.2.1 # For YAML
+
+dev_dependencies:
+
+---
+name: yaml_edit
+description: A library for YAML manipulation with comment and whitespace preservation.
+version: 1.0.0
+
+homepage: https://github.com/google/dart-neats/tree/master/yaml_edit
+
+repository: https://github.com/google/dart-neats.git
+
+issue_tracker: https://github.com/google/dart-neats/labels/pkg:yaml_edit
+
+environment:
+ sdk: ">=2.4.0 <3.0.0"
+
+dependencies:
+ meta: ^1.1.8 # To annotate
+ # quiver_hashcode
+ quiver_hashcode: ^2.0.0 # For hashcodes
+ # yaml
+ yaml: ^3.2.0 # For YAML
+
+dev_dependencies:
+
+---
+name: yaml_edit
+description: A library for YAML manipulation with comment and whitespace preservation.
+version: 1.0.0
+
+homepage: https://github.com/google/dart-neats/tree/master/yaml_edit
+
+repository: https://github.com/google/dart-neats.git
+
+issue_tracker: https://github.com/google/dart-neats/labels/pkg:yaml_edit
+
+environment:
+ sdk: ">=2.4.0 <3.0.0"
+
+dependencies:
+ meta: ^1.1.8 # To annotate
+ # quiver_hashcode
+ quiver_hashcode: ^2.0.0 # For hashcodes
+ # yaml
+ retry: ^3.0.1
+ yaml: ^3.2.0 # For YAML
+
+dev_dependencies:
+
+---
+name: yaml_edit
+description: A library for YAML manipulation with comment and whitespace preservation.
+version: 1.0.0
+
+homepage: https://github.com/google/dart-neats/tree/master/yaml_edit
+
+repository: https://github.com/google/dart-neats.git
+
+issue_tracker: https://github.com/google/dart-neats/labels/pkg:yaml_edit
+
+environment:
+ sdk: ">=2.4.0 <3.0.0"
+
+dependencies:
+ # quiver_hashcode
+ quiver_hashcode: ^2.0.0 # For hashcodes
+ # yaml
+ retry: ^3.0.1
+ yaml: ^3.2.0 # For YAML
+
+dev_dependencies:
+
+---
+name: yaml_edit
+description: A library for YAML manipulation with comment and whitespace preservation.
+version: 1.0.0
+
+homepage: https://github.com/google/dart-neats/tree/master/yaml_edit
+
+repository: https://github.com/google/dart-neats.git
+
+issue_tracker: https://github.com/google/dart-neats/labels/pkg:yaml_edit
+
+environment:
+ sdk: ">=2.4.0 <3.0.0"
+
+dependencies:
+ # quiver_hashcode
+ quiver_hashcode: ^2.0.0 # For hashcodes
+ # yaml
+ retry: ^3.0.1
+ yaml: ^3.2.0 # For YAML
+
+dev_dependencies:
+ test: ^1.14.4
+
diff --git a/pkgs/yaml_edit/test/testdata/output/remove_block_list.golden b/pkgs/yaml_edit/test/testdata/output/remove_block_list.golden
new file mode 100644
index 0000000..7aa3880
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/output/remove_block_list.golden
@@ -0,0 +1,36 @@
+ - true
+ - test:
+ - foo: true
+ bar:
+ - baz:
+ - nested:
+ foo:
+---
+ - true
+ - {}
+ - foo: true
+ bar:
+ - baz:
+ - nested:
+ foo:
+---
+ - true
+ - {}
+ - foo: true
+ - baz:
+ - nested:
+ foo:
+---
+ - true
+ - {}
+ - foo: true
+ - {}
+ - nested:
+ foo:
+---
+ - true
+ - {}
+ - foo: true
+ - {}
+ - nested:
+ {}
diff --git a/pkgs/yaml_edit/test/testdata/output/remove_flow_map.golden b/pkgs/yaml_edit/test/testdata/output/remove_flow_map.golden
new file mode 100644
index 0000000..690dcab
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/output/remove_flow_map.golden
@@ -0,0 +1,258 @@
+A: true
+B: {foo: }
+C: {
+ foo:,bar:true
+}
+D: {
+ foo:
+ ,bar:true
+}
+E: {
+ foo: # comment
+ ,bar:true
+}
+F: {
+ # comment
+ foo:
+ ,bar:true
+}
+G: {
+ # comment
+ foo:
+ # comment
+ ,bar:true
+}
+H: {
+ foo: # comment
+ ,
+ bar:true
+}
+I: {
+ bar: true, foo: }
+J: { foo : }
+---
+A: true
+B: {}
+C: {
+ foo:,bar:true
+}
+D: {
+ foo:
+ ,bar:true
+}
+E: {
+ foo: # comment
+ ,bar:true
+}
+F: {
+ # comment
+ foo:
+ ,bar:true
+}
+G: {
+ # comment
+ foo:
+ # comment
+ ,bar:true
+}
+H: {
+ foo: # comment
+ ,
+ bar:true
+}
+I: {
+ bar: true, foo: }
+J: { foo : }
+---
+A: true
+B: {}
+C: {bar:true
+}
+D: {
+ foo:
+ ,bar:true
+}
+E: {
+ foo: # comment
+ ,bar:true
+}
+F: {
+ # comment
+ foo:
+ ,bar:true
+}
+G: {
+ # comment
+ foo:
+ # comment
+ ,bar:true
+}
+H: {
+ foo: # comment
+ ,
+ bar:true
+}
+I: {
+ bar: true, foo: }
+J: { foo : }
+---
+A: true
+B: {}
+C: {bar:true
+}
+D: {bar:true
+}
+E: {
+ foo: # comment
+ ,bar:true
+}
+F: {
+ # comment
+ foo:
+ ,bar:true
+}
+G: {
+ # comment
+ foo:
+ # comment
+ ,bar:true
+}
+H: {
+ foo: # comment
+ ,
+ bar:true
+}
+I: {
+ bar: true, foo: }
+J: { foo : }
+---
+A: true
+B: {}
+C: {bar:true
+}
+D: {bar:true
+}
+E: {bar:true
+}
+F: {
+ # comment
+ foo:
+ ,bar:true
+}
+G: {
+ # comment
+ foo:
+ # comment
+ ,bar:true
+}
+H: {
+ foo: # comment
+ ,
+ bar:true
+}
+I: {
+ bar: true, foo: }
+J: { foo : }
+---
+A: true
+B: {}
+C: {bar:true
+}
+D: {bar:true
+}
+E: {bar:true
+}
+F: {bar:true
+}
+G: {
+ # comment
+ foo:
+ # comment
+ ,bar:true
+}
+H: {
+ foo: # comment
+ ,
+ bar:true
+}
+I: {
+ bar: true, foo: }
+J: { foo : }
+---
+A: true
+B: {}
+C: {bar:true
+}
+D: {bar:true
+}
+E: {bar:true
+}
+F: {bar:true
+}
+G: {bar:true
+}
+H: {
+ foo: # comment
+ ,
+ bar:true
+}
+I: {
+ bar: true, foo: }
+J: { foo : }
+---
+A: true
+B: {}
+C: {bar:true
+}
+D: {bar:true
+}
+E: {bar:true
+}
+F: {bar:true
+}
+G: {bar:true
+}
+H: {
+ bar:true
+}
+I: {
+ bar: true, foo: }
+J: { foo : }
+---
+A: true
+B: {}
+C: {bar:true
+}
+D: {bar:true
+}
+E: {bar:true
+}
+F: {bar:true
+}
+G: {bar:true
+}
+H: {
+ bar:true
+}
+I: {
+ bar: true}
+J: { foo : }
+---
+A: true
+B: {}
+C: {bar:true
+}
+D: {bar:true
+}
+E: {bar:true
+}
+F: {bar:true
+}
+G: {bar:true
+}
+H: {
+ bar:true
+}
+I: {
+ bar: true}
+J: {}
diff --git a/pkgs/yaml_edit/test/testdata/output/remove_key.golden b/pkgs/yaml_edit/test/testdata/output/remove_key.golden
new file mode 100644
index 0000000..48b8640
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/output/remove_key.golden
@@ -0,0 +1,4 @@
+foo: true
+bar: false
+---
+foo: true
diff --git a/pkgs/yaml_edit/test/testdata/output/remove_key_with_trailing_comma.golden b/pkgs/yaml_edit/test/testdata/output/remove_key_with_trailing_comma.golden
new file mode 100644
index 0000000..d85be35
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/output/remove_key_with_trailing_comma.golden
@@ -0,0 +1,4 @@
+foo: true
+bar: false # remove this comment
+---
+foo: true
diff --git a/pkgs/yaml_edit/test/testdata/output/remove_nested_key.golden b/pkgs/yaml_edit/test/testdata/output/remove_nested_key.golden
new file mode 100644
index 0000000..011f1c1
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/output/remove_nested_key.golden
@@ -0,0 +1,12 @@
+A: true
+B:
+ foo: true
+ bar: true
+---
+A: true
+B:
+ bar: true
+---
+A: true
+B:
+ {}
diff --git a/pkgs/yaml_edit/test/testdata/output/remove_nested_key_with_null.golden b/pkgs/yaml_edit/test/testdata/output/remove_nested_key_with_null.golden
new file mode 100644
index 0000000..b0e771c
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/output/remove_nested_key_with_null.golden
@@ -0,0 +1,7 @@
+A: true
+B:
+ foo:
+---
+A: true
+B:
+ {}
diff --git a/pkgs/yaml_edit/test/testdata/output/remove_nested_key_with_trailing_comma.golden b/pkgs/yaml_edit/test/testdata/output/remove_nested_key_with_trailing_comma.golden
new file mode 100644
index 0000000..c93c46a
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/output/remove_nested_key_with_trailing_comma.golden
@@ -0,0 +1,7 @@
+A: true
+B:
+ bar: false # remove this comment
+---
+A: true
+B:
+ {}
diff --git a/pkgs/yaml_edit/test/testdata/output/respect_key_order.golden b/pkgs/yaml_edit/test/testdata/output/respect_key_order.golden
new file mode 100644
index 0000000..a20ecde
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/output/respect_key_order.golden
@@ -0,0 +1,6 @@
+A: first
+C: third
+---
+A: first
+B: second
+C: third
diff --git a/pkgs/yaml_edit/test/testdata/output/spaces.golden b/pkgs/yaml_edit/test/testdata/output/spaces.golden
new file mode 100644
index 0000000..7ee3372
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/output/spaces.golden
@@ -0,0 +1,17 @@
+a: ' space around me '
+c: 'hello world'
+---
+a: ' space around me '
+b: " also spaced "
+c: 'hello world'
+---
+a: ' space around me '
+b: " also spaced "
+c: 'hello world'
+d: " "
+---
+"\ne": with newline
+a: ' space around me '
+b: " also spaced "
+c: 'hello world'
+d: " "
diff --git a/pkgs/yaml_edit/test/testdata/output/special_keywords.golden b/pkgs/yaml_edit/test/testdata/output/special_keywords.golden
new file mode 100644
index 0000000..e0c179d
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/output/special_keywords.golden
@@ -0,0 +1,24 @@
+~: null
+false: false
+No: No
+true: true
+---
+~: tilde
+false: false
+No: No
+true: true
+---
+~: tilde
+false: false
+No: No
+true: true
+---
+~: tilde
+false: false
+No: no
+true: true
+---
+~: tilde
+false: false
+No: no
+true: true
diff --git a/pkgs/yaml_edit/test/testdata/output/splice_list_in_nested_block_with_weird_spaces.golden b/pkgs/yaml_edit/test/testdata/output/splice_list_in_nested_block_with_weird_spaces.golden
new file mode 100644
index 0000000..cfc4cc0
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/output/splice_list_in_nested_block_with_weird_spaces.golden
@@ -0,0 +1,50 @@
+key:
+ - - bar1
+ - bar2
+ - - foo
+ - - baz
+---
+key:
+ - - pre-bar1
+ - bar1
+ - bar2
+ - - foo
+ - - baz
+---
+key:
+ - - pre-bar1
+ - bar1
+ - post-bar2
+ - bar2
+ - - foo
+ - - baz
+---
+key:
+ - - pre-bar1
+ - bar1
+ - post-bar2
+ - bar2
+ - - foo
+ - - baz
+ - post-baz
+---
+key:
+ - - pre-bar1
+ - bar1
+ - post-bar2
+ - bar2
+ - - foo
+ - - pre-baz
+ - baz
+ - post-baz
+---
+key:
+ - - pre-bar1
+ - bar1
+ - post-bar2
+ - bar2
+ - - pre-foo
+ - foo
+ - - pre-baz
+ - baz
+ - post-baz
diff --git a/pkgs/yaml_edit/test/testdata/output/splice_list_in_nested_block_without_initial_spaces.golden b/pkgs/yaml_edit/test/testdata/output/splice_list_in_nested_block_without_initial_spaces.golden
new file mode 100644
index 0000000..3454bbd
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/output/splice_list_in_nested_block_without_initial_spaces.golden
@@ -0,0 +1,50 @@
+key:
+- - bar1
+ - bar2
+- - foo
+- - baz
+---
+key:
+- - pre-bar1
+ - bar1
+ - bar2
+- - foo
+- - baz
+---
+key:
+- - pre-bar1
+ - bar1
+ - post-bar2
+ - bar2
+- - foo
+- - baz
+---
+key:
+- - pre-bar1
+ - bar1
+ - post-bar2
+ - bar2
+- - foo
+- - baz
+ - post-baz
+---
+key:
+- - pre-bar1
+ - bar1
+ - post-bar2
+ - bar2
+- - foo
+- - pre-baz
+ - baz
+ - post-baz
+---
+key:
+- - pre-bar1
+ - bar1
+ - post-bar2
+ - bar2
+- - pre-foo
+ - foo
+- - pre-baz
+ - baz
+ - post-baz
diff --git a/pkgs/yaml_edit/test/testdata/output/splicelist_in_nested_block_list.golden b/pkgs/yaml_edit/test/testdata/output/splicelist_in_nested_block_list.golden
new file mode 100644
index 0000000..0c9d7c5
--- /dev/null
+++ b/pkgs/yaml_edit/test/testdata/output/splicelist_in_nested_block_list.golden
@@ -0,0 +1,40 @@
+key:
+ - foo:
+ - - bar:
+ - - - false
+ - - - false
+ - - - false
+---
+key:
+ - pre-foo
+ - foo:
+ - - bar:
+ - - - false
+ - - - false
+ - - - false
+---
+key:
+ - pre-foo
+ - foo:
+ - - test
+ - - - false
+ - - - false
+ - - - false
+---
+key:
+ - pre-foo
+ - foo:
+ - - test
+ - - test
+ - - false
+ - - - false
+ - - - false
+---
+key:
+ - pre-foo
+ - foo:
+ - - test
+ - - test
+ - - false
+ - - - false
+ - tail-foo
diff --git a/pkgs/yaml_edit/test/update_test.dart b/pkgs/yaml_edit/test/update_test.dart
new file mode 100644
index 0000000..01dfde0
--- /dev/null
+++ b/pkgs/yaml_edit/test/update_test.dart
@@ -0,0 +1,994 @@
+// Copyright (c) 2020, 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 'package:yaml/yaml.dart';
+import 'package:yaml_edit/yaml_edit.dart';
+
+import 'test_utils.dart';
+
+void main() {
+ group('throws', () {
+ test('RangeError in list if index is negative', () {
+ final doc = YamlEditor("- YAML Ain't Markup Language");
+ expect(() => doc.update([-1], 'test'), throwsRangeError);
+ });
+
+ test('RangeError in list if index is larger than list length', () {
+ final doc = YamlEditor("- YAML Ain't Markup Language");
+ expect(() => doc.update([2], 'test'), throwsRangeError);
+ });
+
+ test('PathError in list if attempting to set a key of a scalar', () {
+ final doc = YamlEditor("- YAML Ain't Markup Language");
+ expect(() => doc.update([0, 'a'], 'a'), throwsPathError);
+ });
+
+ test('PathError in list if using a non-integer as index', () {
+ final doc = YamlEditor("{ a: ['b', 'c'] }");
+ expect(() => doc.update(['a', 'b'], 'x'), throwsPathError);
+ });
+ });
+
+ group('works on top-level', () {
+ test('empty document', () {
+ final doc = YamlEditor('');
+ doc.update([], 'replacement');
+
+ expect(doc.toString(), equals('replacement'));
+ expectYamlBuilderValue(doc, 'replacement');
+ });
+
+ test('replaces string in document containing only a string', () {
+ final doc = YamlEditor('test');
+ doc.update([], 'replacement');
+
+ expect(doc.toString(), equals('replacement'));
+ expectYamlBuilderValue(doc, 'replacement');
+ });
+
+ test('replaces top-level string to map', () {
+ final doc = YamlEditor('test');
+ doc.update([], {'a': 1});
+
+ expect(doc.toString(), equals('a: 1'));
+ expectYamlBuilderValue(doc, {'a': 1});
+ });
+
+ test('replaces top-level list', () {
+ final doc = YamlEditor('- 1');
+ doc.update([], 'replacement');
+
+ expect(doc.toString(), equals('replacement'));
+ expectYamlBuilderValue(doc, 'replacement');
+ });
+
+ test('replaces top-level map', () {
+ final doc = YamlEditor('a: 1');
+ doc.update([], 'replacement');
+
+ expect(doc.toString(), equals('replacement'));
+ expectYamlBuilderValue(doc, 'replacement');
+ });
+
+ test('replaces top-level map with comment', () {
+ final doc = YamlEditor('a: 1 # comment');
+ doc.update([], 'replacement');
+
+ expect(doc.toString(), equals('replacement # comment'));
+ expectYamlBuilderValue(doc, 'replacement');
+ });
+ });
+
+ group('replaces in', () {
+ group('block map', () {
+ test('(1)', () {
+ final doc = YamlEditor("YAML: YAML Ain't Markup Language");
+ doc.update(['YAML'], 'test');
+
+ expect(doc.toString(), equals('YAML: test'));
+ expectYamlBuilderValue(doc, {'YAML': 'test'});
+ });
+
+ test('(2)', () {
+ final doc = YamlEditor('test: test');
+ doc.update(['test'], []);
+
+ expect(doc.toString(), equals('test: []'));
+ expectYamlBuilderValue(doc, {'test': []});
+ });
+
+ test('empty value', () {
+ final doc = YamlEditor('YAML:');
+ doc.update(['YAML'], 'test');
+
+ expect(doc.toString(), equals('YAML: test'));
+ expectYamlBuilderValue(doc, {'YAML': 'test'});
+ });
+
+ test('empty value (2)', () {
+ final doc = YamlEditor('YAML :');
+ doc.update(['YAML'], 'test');
+
+ expect(doc.toString(), equals('YAML : test'));
+ expectYamlBuilderValue(doc, {'YAML': 'test'});
+ });
+
+ test('with comment', () {
+ final doc = YamlEditor("YAML: YAML Ain't Markup Language # comment");
+ doc.update(['YAML'], 'test');
+
+ expect(doc.toString(), equals('YAML: test # comment'));
+ expectYamlBuilderValue(doc, {'YAML': 'test'});
+ });
+
+ test('nested', () {
+ final doc = YamlEditor('''
+a: 1
+b:
+ d: 4
+ e: 5
+c: 3
+''');
+ doc.update(['b', 'e'], 6);
+
+ expect(doc.toString(), equals('''
+a: 1
+b:
+ d: 4
+ e: 6
+c: 3
+'''));
+
+ expectYamlBuilderValue(doc, {
+ 'a': 1,
+ 'b': {'d': 4, 'e': 6},
+ 'c': 3
+ });
+ });
+
+ test('nested (2)', () {
+ final doc = YamlEditor('''
+a: 1
+b: {d: 4, e: 5}
+c: 3
+''');
+ doc.update(['b', 'e'], 6);
+
+ expect(doc.toString(), equals('''
+a: 1
+b: {d: 4, e: 6}
+c: 3
+'''));
+ expectYamlBuilderValue(doc, {
+ 'a': 1,
+ 'b': {'d': 4, 'e': 6},
+ 'c': 3
+ });
+ });
+
+ test('nested (3)', () {
+ final doc = YamlEditor('''
+a:
+ b: 4
+''');
+ doc.update(['a'], true);
+
+ expect(doc.toString(), equals('''
+a: true
+'''));
+
+ expectYamlBuilderValue(doc, {'a': true});
+ });
+
+ test('nested (4)', () {
+ final doc = YamlEditor('''
+a: 1
+''');
+ doc.update([
+ 'a'
+ ], [
+ {'a': true, 'b': false}
+ ]);
+
+ expectYamlBuilderValue(doc, {
+ 'a': [
+ {'a': true, 'b': false}
+ ]
+ });
+ });
+
+ test('nested (5)', () {
+ final doc = YamlEditor('''
+a:
+ - a: 1
+ b: 2
+ - null
+''');
+ doc.update(['a', 0], false);
+ expect(doc.toString(), equals('''
+a:
+ - false
+
+ - null
+'''));
+ expectYamlBuilderValue(doc, {
+ 'a': [false, null]
+ });
+ });
+
+ test('nested (6)', () {
+ final doc = YamlEditor('''
+a:
+ - - 1
+ - 2
+ - null
+''');
+ doc.update(['a', 0], false);
+ expect(doc.toString(), equals('''
+a:
+ - false
+
+ - null
+'''));
+ expectYamlBuilderValue(doc, {
+ 'a': [false, null]
+ });
+ });
+
+ test('nested (7)', () {
+ final doc = YamlEditor('''
+a:
+ - - 0
+b: false
+''');
+ doc.update(['a', 0], true);
+
+ expect(doc.toString(), equals('''
+a:
+ - true
+
+b: false
+'''));
+ });
+
+ test('nested (8)', () {
+ final doc = YamlEditor('''
+a:
+b: false
+''');
+ doc.update(['a'], {'retry': '3.0.1'});
+
+ expect(doc.toString(), equals('''
+a:
+ retry: 3.0.1
+b: false
+'''));
+ });
+
+ test('nested (9)', () {
+ final doc = YamlEditor('''
+# comment
+a: # comment
+# comment
+''');
+ doc.update(['a'], {'retry': '3.0.1'});
+
+ expect(doc.toString(), equals('''
+# comment
+a:
+ retry: 3.0.1 # comment
+# comment
+'''));
+ });
+
+ test('nested scalar -> flow list', () {
+ final doc = YamlEditor('''
+a: 1
+b:
+ d: 4
+ e: 5
+c: 3
+''');
+ doc.update(['b', 'e'], [1, 2, 3]);
+
+ expect(doc.toString(), equals('''
+a: 1
+b:
+ d: 4
+ e:
+ - 1
+ - 2
+ - 3
+c: 3
+'''));
+ expectYamlBuilderValue(doc, {
+ 'a': 1,
+ 'b': {
+ 'd': 4,
+ 'e': [1, 2, 3]
+ },
+ 'c': 3
+ });
+ });
+
+ test('nested block map -> scalar', () {
+ final doc = YamlEditor('''
+a: 1
+b:
+ d: 4
+ e: 5
+c: 3
+''');
+ doc.update(['b'], 2);
+
+ expect(doc.toString(), equals('''
+a: 1
+b: 2
+c: 3
+'''));
+ expectYamlBuilderValue(doc, {'a': 1, 'b': 2, 'c': 3});
+ });
+
+ test('nested block map -> scalar with comments', () {
+ final doc = YamlEditor('''
+a: 1
+b:
+ d: 4
+ e: 5
+
+
+# comment
+''');
+ doc.update(['b'], 2);
+
+ expect(doc.toString(), equals('''
+a: 1
+b: 2
+
+
+# comment
+'''));
+ expectYamlBuilderValue(doc, {
+ 'a': 1,
+ 'b': 2,
+ });
+ });
+
+ test('nested scalar -> block map', () {
+ final doc = YamlEditor('''
+a: 1
+b:
+ d: 4
+ e: 5
+c: 3
+''');
+ doc.update(['b', 'e'], {'x': 3, 'y': 4});
+
+ expect(doc.toString(), equals('''
+a: 1
+b:
+ d: 4
+ e:
+ x: 3
+ y: 4
+c: 3
+'''));
+ expectYamlBuilderValue(doc, {
+ 'a': 1,
+ 'b': {
+ 'd': 4,
+ 'e': {'x': 3, 'y': 4}
+ },
+ 'c': 3
+ });
+ });
+
+ test('nested block map with comments', () {
+ final doc = YamlEditor('''
+a: 1
+b:
+ d: 4
+ e: 5 # comment
+c: 3
+''');
+ doc.update(['b', 'e'], 6);
+
+ expect(doc.toString(), equals('''
+a: 1
+b:
+ d: 4
+ e: 6 # comment
+c: 3
+'''));
+ expectYamlBuilderValue(doc, {
+ 'a': 1,
+ 'b': {'d': 4, 'e': 6},
+ 'c': 3
+ });
+ });
+
+ test('nested block map with comments (2)', () {
+ final doc = YamlEditor('''
+a: 1
+b:
+ d: 4 # comment
+# comment
+ e: 5 # comment
+# comment
+c: 3
+''');
+ doc.update(['b', 'e'], 6);
+
+ expect(doc.toString(), equals('''
+a: 1
+b:
+ d: 4 # comment
+# comment
+ e: 6 # comment
+# comment
+c: 3
+'''));
+ expectYamlBuilderValue(doc, {
+ 'a': 1,
+ 'b': {'d': 4, 'e': 6},
+ 'c': 3
+ });
+ });
+ });
+
+ group('flow map', () {
+ test('(1)', () {
+ final doc = YamlEditor("{YAML: YAML Ain't Markup Language}");
+ doc.update(['YAML'], 'test');
+
+ expect(doc.toString(), equals('{YAML: test}'));
+ expectYamlBuilderValue(doc, {'YAML': 'test'});
+ });
+
+ test('(2)', () {
+ final doc = YamlEditor("{YAML: YAML Ain't Markup Language}");
+ doc.update(['YAML'], 'd9]zH`FoYC/>]');
+
+ expect(doc.toString(), equals('{YAML: "d9]zH`FoYC\\/>]"}'));
+ expectYamlBuilderValue(doc, {'YAML': 'd9]zH`FoYC/>]'});
+ });
+
+ test('empty value', () {
+ final doc = YamlEditor('{YAML: }');
+ doc.update(['YAML'], 'test');
+
+ expect(doc.toString(), equals('{YAML: test}'));
+ expectYamlBuilderValue(doc, {'YAML': 'test'});
+ });
+
+ test('empty value (2)', () {
+ final doc = YamlEditor('{YAML: , hi: bye}');
+ doc.update(['YAML'], 'test');
+
+ expect(doc.toString(), equals('{YAML: test, hi: bye}'));
+ expectYamlBuilderValue(doc, {'YAML': 'test', 'hi': 'bye'});
+ });
+
+ test('with spacing', () {
+ final doc = YamlEditor("{ YAML: YAML Ain't Markup Language , "
+ 'XML: Extensible Markup Language , '
+ 'HTML: Hypertext Markup Language }');
+ doc.update(['XML'], 'XML Markup Language');
+
+ expect(
+ doc.toString(),
+ equals("{ YAML: YAML Ain't Markup Language , "
+ 'XML: XML Markup Language, '
+ 'HTML: Hypertext Markup Language }'));
+ expectYamlBuilderValue(doc, {
+ 'YAML': "YAML Ain't Markup Language",
+ 'XML': 'XML Markup Language',
+ 'HTML': 'Hypertext Markup Language'
+ });
+ });
+ });
+
+ group('block list', () {
+ test('(1)', () {
+ final doc = YamlEditor("- YAML Ain't Markup Language");
+ doc.update([0], 'test');
+
+ expect(doc.toString(), equals('- test'));
+ expectYamlBuilderValue(doc, ['test']);
+ });
+
+ test('(2)', () {
+ final doc = YamlEditor('''
+- 1
+-
+- 3
+''');
+ doc.update([1], 2);
+
+ expect(doc.toString(), equals('''
+- 1
+- 2
+- 3
+'''));
+ expectYamlBuilderValue(doc, [1, 2, 3]);
+ });
+
+ test('nested (1)', () {
+ final doc = YamlEditor("- YAML Ain't Markup Language");
+ doc.update([0], [1, 2]);
+
+ expect(doc.toString(), equals('- - 1\n - 2'));
+ expectYamlBuilderValue(doc, [
+ [1, 2]
+ ]);
+ });
+
+ test('with comment', () {
+ final doc = YamlEditor("- YAML Ain't Markup Language # comment");
+ doc.update([0], 'test');
+
+ expect(doc.toString(), equals('- test # comment'));
+ expectYamlBuilderValue(doc, ['test']);
+ });
+
+ test('with comment (2)', () {
+ final doc = YamlEditor('''
+- 1
+- # comment
+- 3
+''');
+ doc.update([1], 2);
+
+ expect(doc.toString(), equals('''
+- 1
+- 2 # comment
+- 3
+'''));
+ expectYamlBuilderValue(doc, [1, 2, 3]);
+ });
+
+ test('with comment and spaces', () {
+ final doc = YamlEditor("- YAML Ain't Markup Language # comment");
+ doc.update([0], 'test');
+
+ expect(doc.toString(), equals('- test # comment'));
+ expectYamlBuilderValue(doc, ['test']);
+ });
+
+ test('nested (2)', () {
+ final doc = YamlEditor('''
+- 0
+- - 0
+ - 1
+ - 2
+- 2
+- 3
+''');
+ doc.update([1, 1], 4);
+ expect(doc.toString(), equals('''
+- 0
+- - 0
+ - 4
+ - 2
+- 2
+- 3
+'''));
+
+ expectYamlBuilderValue(doc, [
+ 0,
+ [0, 4, 2],
+ 2,
+ 3
+ ]);
+ });
+
+ test('nested (3)', () {
+ final doc = YamlEditor('''
+- 0
+- 1
+''');
+ doc.update([0], {'item': 'Super Hoop', 'quantity': 1});
+ doc.update([1], {'item': 'BasketBall', 'quantity': 4});
+ expect(doc.toString(), equals('''
+- item: Super Hoop
+ quantity: 1
+- item: BasketBall
+ quantity: 4
+'''));
+
+ expectYamlBuilderValue(doc, [
+ {'item': 'Super Hoop', 'quantity': 1},
+ {'item': 'BasketBall', 'quantity': 4}
+ ]);
+ });
+
+ test('nested list flow map -> scalar', () {
+ final doc = YamlEditor('''
+- 0
+- {a: 1, b: 2}
+- 2
+- 3
+''');
+ doc.update([1], 4);
+ expect(doc.toString(), equals('''
+- 0
+- 4
+- 2
+- 3
+'''));
+ expectYamlBuilderValue(doc, [0, 4, 2, 3]);
+ });
+
+ test('nested list-map-list-number update', () {
+ final doc = YamlEditor('''
+- 0
+- a:
+ - 1
+ - 2
+ - 3
+- 2
+- 3
+''');
+ doc.update([1, 'a', 0], 15);
+ expect(doc.toString(), equals('''
+- 0
+- a:
+ - 15
+ - 2
+ - 3
+- 2
+- 3
+'''));
+ expectYamlBuilderValue(doc, [
+ 0,
+ {
+ 'a': [15, 2, 3]
+ },
+ 2,
+ 3
+ ]);
+ });
+ });
+
+ group('flow list', () {
+ test('(1)', () {
+ final doc = YamlEditor("[YAML Ain't Markup Language]");
+ doc.update([0], 'test');
+
+ expect(doc.toString(), equals('[test]'));
+ expectYamlBuilderValue(doc, ['test']);
+ });
+
+ test('(2)', () {
+ final doc = YamlEditor("[YAML Ain't Markup Language]");
+ doc.update([0], [1, 2, 3]);
+
+ expect(doc.toString(), equals('[[1, 2, 3]]'));
+ expectYamlBuilderValue(doc, [
+ [1, 2, 3]
+ ]);
+ });
+
+ /// We cannot have empty values in a flow list.
+
+ test('with spacing (1)', () {
+ final doc = YamlEditor('[ 0 , 1 , 2 , 3 ]');
+ doc.update([1], 4);
+
+ expect(doc.toString(), equals('[ 0 , 4, 2 , 3 ]'));
+ expectYamlBuilderValue(doc, [0, 4, 2, 3]);
+ });
+ });
+ });
+
+ group('adds to', () {
+ group('flow map', () {
+ test('that is empty ', () {
+ final doc = YamlEditor('{}');
+ doc.update(['a'], 1);
+ expect(doc.toString(), equals('{a: 1}'));
+ expectYamlBuilderValue(doc, {'a': 1});
+ });
+
+ test('that is empty (2)', () {
+ final doc = YamlEditor('''
+- {}
+- []
+''');
+ doc.update([0, 'a'], [1]);
+ expect(doc.toString(), equals('''
+- {a: [1]}
+- []
+'''));
+ expectYamlBuilderValue(doc, [
+ {
+ 'a': [1]
+ },
+ []
+ ]);
+ });
+
+ test('(1)', () {
+ final doc = YamlEditor("{YAML: YAML Ain't Markup Language}");
+ doc.update(['XML'], 'Extensible Markup Language');
+
+ expect(
+ doc.toString(),
+ equals(
+ "{YAML: YAML Ain't Markup Language, "
+ 'XML: Extensible Markup Language}',
+ ),
+ );
+ expectYamlBuilderValue(doc, {
+ 'XML': 'Extensible Markup Language',
+ 'YAML': "YAML Ain't Markup Language",
+ });
+ });
+ });
+
+ group('block map', () {
+ test('(1)', () {
+ final doc = YamlEditor('''
+a: 1
+b: 2
+c: 3
+''');
+ doc.update(['d'], 4);
+ expect(doc.toString(), equals('''
+a: 1
+b: 2
+c: 3
+d: 4
+'''));
+ expectYamlBuilderValue(doc, {'a': 1, 'b': 2, 'c': 3, 'd': 4});
+ });
+
+ // Regression testing to ensure it works without leading whitespace
+ test('(2)', () {
+ final doc = YamlEditor('a: 1');
+ doc.update(['b'], 2);
+ expect(doc.toString(), equals('''a: 1
+b: 2
+'''));
+ expectYamlBuilderValue(doc, {'a': 1, 'b': 2});
+ });
+
+ test('(3)', () {
+ final doc = YamlEditor('''
+a:
+ aa: 1
+ zz: 1
+''');
+ doc.update([
+ 'a',
+ 'bb'
+ ], {
+ 'aaa': {'dddd': 'c'},
+ 'bbb': [0, 1, 2]
+ });
+
+ expect(doc.toString(), equals('''
+a:
+ aa: 1
+ bb:
+ aaa:
+ dddd: c
+ bbb:
+ - 0
+ - 1
+ - 2
+ zz: 1
+'''));
+ expectYamlBuilderValue(doc, {
+ 'a': {
+ 'aa': 1,
+ 'bb': {
+ 'aaa': {'dddd': 'c'},
+ 'bbb': [0, 1, 2]
+ },
+ 'zz': 1
+ }
+ });
+ });
+
+ test('(4)', () {
+ final doc = YamlEditor('''
+a:
+ aa: 1
+ zz: 1
+''');
+ doc.update([
+ 'a',
+ 'bb'
+ ], [
+ 0,
+ [1, 2],
+ {'aaa': 'b', 'bbb': 'c'}
+ ]);
+
+ expect(doc.toString(), equals('''
+a:
+ aa: 1
+ bb:
+ - 0
+ - - 1
+ - 2
+ - aaa: b
+ bbb: c
+ zz: 1
+'''));
+ expectYamlBuilderValue(doc, {
+ 'a': {
+ 'aa': 1,
+ 'bb': [
+ 0,
+ [1, 2],
+ {'aaa': 'b', 'bbb': 'c'}
+ ],
+ 'zz': 1
+ }
+ });
+ });
+
+ test('with complex keys', () {
+ final doc = YamlEditor('''
+? Sammy Sosa
+? Ken Griff''');
+ doc.update(['Mark McGwire'], null);
+ expect(doc.toString(), equals('''
+? Sammy Sosa
+? Ken Griff
+Mark McGwire: null
+'''));
+ expectYamlBuilderValue(
+ doc, {'Sammy Sosa': null, 'Ken Griff': null, 'Mark McGwire': null});
+ });
+
+ test('with trailing newline', () {
+ final doc = YamlEditor('''
+a: 1
+b: 2
+c: 3
+
+
+''');
+ doc.update(['d'], 4);
+ expect(doc.toString(), equals('''
+a: 1
+b: 2
+c: 3
+d: 4
+
+
+'''));
+ expectYamlBuilderValue(doc, {'a': 1, 'b': 2, 'c': 3, 'd': 4});
+ });
+
+ test('adds an empty map properly', () {
+ final doc = YamlEditor('a: b');
+ doc.update(['key'], {});
+ expectYamlBuilderValue(doc, {'a': 'b', 'key': {}});
+ });
+
+ test('adds an empty map properly (2)', () {
+ final doc = YamlEditor('a: b');
+ doc.update(['a'], {'key': {}});
+ expectYamlBuilderValue(doc, {
+ 'a': {'key': {}}
+ });
+ });
+
+ test('adds and preserves key order (ascending)', () {
+ final doc = YamlEditor('''
+a: 1
+b: 2
+c: 3
+
+
+''');
+
+ doc.update(['d'], 4);
+ expect(doc.toString(), equals('''
+a: 1
+b: 2
+c: 3
+d: 4
+
+
+'''));
+ });
+
+ test('adds at the end when no key order is present', () {
+ final doc = YamlEditor('''
+a: 1
+c: 2
+b: 3
+''');
+
+ doc.update(['d'], 4);
+ expect(doc.toString(), equals('''
+a: 1
+c: 2
+b: 3
+d: 4
+'''));
+ });
+ });
+
+ group('empty starting document', () {
+ test('empty map', () {
+ final doc = YamlEditor('');
+ doc.update([], {'key': {}});
+ expectYamlBuilderValue(doc, {'key': {}});
+ });
+
+ test('empty map (2)', () {
+ final doc = YamlEditor('');
+ doc.update([], {});
+ expectYamlBuilderValue(doc, {});
+ });
+
+ test('empty map (3)', () {
+ final doc = YamlEditor('');
+ doc.update(
+ [],
+ wrapAsYamlNode(
+ {'key': {}},
+ collectionStyle: CollectionStyle.BLOCK,
+ ),
+ );
+ expectYamlBuilderValue(doc, {'key': {}});
+ });
+
+ test('empty map (4)', () {
+ final doc = YamlEditor('');
+ doc.update(
+ [],
+ wrapAsYamlNode(
+ {},
+ collectionStyle: CollectionStyle.BLOCK,
+ ),
+ );
+ expectYamlBuilderValue(doc, {});
+ });
+
+ test('empty list', () {
+ final doc = YamlEditor('');
+ doc.update([], {'key': []});
+ expectYamlBuilderValue(doc, {'key': []});
+ });
+
+ test('empty list (2)', () {
+ final doc = YamlEditor('');
+ doc.update([], []);
+ expectYamlBuilderValue(doc, []);
+ });
+
+ test('empty list (3)', () {
+ final doc = YamlEditor('');
+ doc.update(
+ [],
+ wrapAsYamlNode(
+ {'key': []},
+ collectionStyle: CollectionStyle.BLOCK,
+ ),
+ );
+ expectYamlBuilderValue(doc, {'key': []});
+ });
+
+ test('empty map (4)', () {
+ final doc = YamlEditor('');
+ doc.update(
+ [],
+ wrapAsYamlNode(
+ [],
+ collectionStyle: CollectionStyle.BLOCK,
+ ),
+ );
+ expectYamlBuilderValue(doc, []);
+ });
+ });
+ });
+}
diff --git a/pkgs/yaml_edit/test/utils_test.dart b/pkgs/yaml_edit/test/utils_test.dart
new file mode 100644
index 0000000..57f0b2a
--- /dev/null
+++ b/pkgs/yaml_edit/test/utils_test.dart
@@ -0,0 +1,448 @@
+// Copyright (c) 2020, 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 'package:yaml/yaml.dart';
+import 'package:yaml_edit/src/utils.dart';
+import 'package:yaml_edit/yaml_edit.dart';
+
+import 'test_utils.dart';
+
+void main() {
+ group('indentation', () {
+ test('returns 2 for empty strings', () {
+ final doc = YamlEditor('');
+ expect(getIndentation(doc), equals(2));
+ });
+
+ test('returns 2 for strings consisting only scalars', () {
+ final doc = YamlEditor('foo');
+ expect(getIndentation(doc), equals(2));
+ });
+
+ test('returns 2 if only top-level elements are present', () {
+ final doc = YamlEditor('''
+- 1
+- 2
+- 3''');
+ expect(getIndentation(doc), equals(2));
+ });
+
+ test('detects the indentation used in nested list', () {
+ final doc = YamlEditor('''
+- 1
+- 2
+-
+ - 3
+ - 4''');
+ expect(getIndentation(doc), equals(3));
+ });
+
+ test('detects the indentation used in nested map', () {
+ final doc = YamlEditor('''
+a: 1
+b: 2
+c:
+ d: 4
+ e: 5''');
+ expect(getIndentation(doc), equals(3));
+ });
+
+ test('detects the indentation used in nested map in list', () {
+ final doc = YamlEditor('''
+- 1
+- 2
+-
+ d: 4
+ e: 5''');
+ expect(getIndentation(doc), equals(4));
+ });
+
+ test('detects the indentation used in nested map in list with complex keys',
+ () {
+ final doc = YamlEditor('''
+- 1
+- 2
+-
+ ? d
+ : 4''');
+ expect(getIndentation(doc), equals(4));
+ });
+
+ test('detects the indentation used in nested list in map', () {
+ final doc = YamlEditor('''
+a: 1
+b: 2
+c:
+ - 4
+ - 5''');
+ expect(getIndentation(doc), equals(2));
+ });
+ });
+
+ group('styling options', () {
+ group('update', () {
+ test('flow map with style', () {
+ final doc = YamlEditor("{YAML: YAML Ain't Markup Language}");
+ doc.update(['YAML'],
+ wrapAsYamlNode('hi', scalarStyle: ScalarStyle.DOUBLE_QUOTED));
+
+ expect(doc.toString(), equals('{YAML: "hi"}'));
+ expectYamlBuilderValue(doc, {'YAML': 'hi'});
+ });
+
+ test('prevents block scalars in flow map', () {
+ final doc = YamlEditor("{YAML: YAML Ain't Markup Language}");
+ doc.update(
+ ['YAML'], wrapAsYamlNode('test', scalarStyle: ScalarStyle.FOLDED));
+
+ expect(doc.toString(), equals('{YAML: test}'));
+ expectYamlBuilderValue(doc, {'YAML': 'test'});
+ });
+
+ test('wraps string in double-quotes if it contains dangerous characters',
+ () {
+ final doc = YamlEditor("{YAML: YAML Ain't Markup Language}");
+ doc.update(
+ ['YAML'], wrapAsYamlNode('> test', scalarStyle: ScalarStyle.PLAIN));
+
+ expect(doc.toString(), equals('{YAML: "> test"}'));
+ expectYamlBuilderValue(doc, {'YAML': '> test'});
+ });
+
+ test('list in map', () {
+ final doc = YamlEditor('''YAML: YAML Ain't Markup Language''');
+ doc.update(['YAML'],
+ wrapAsYamlNode([1, 2, 3], collectionStyle: CollectionStyle.FLOW));
+
+ expect(doc.toString(), equals('YAML: [1, 2, 3]'));
+ expectYamlBuilderValue(doc, {
+ 'YAML': [1, 2, 3]
+ });
+ });
+
+ test('nested map', () {
+ final doc = YamlEditor('''YAML: YAML Ain't Markup Language''');
+ doc.update(
+ ['YAML'],
+ wrapAsYamlNode({'YAML': "YAML Ain't Markup Language"},
+ collectionStyle: CollectionStyle.FLOW));
+
+ expect(
+ doc.toString(), equals("YAML: {YAML: YAML Ain't Markup Language}"));
+ expectYamlBuilderValue(doc, {
+ 'YAML': {'YAML': "YAML Ain't Markup Language"}
+ });
+ });
+
+ test('nested list', () {
+ final doc = YamlEditor('- 0');
+ doc.update(
+ [0],
+ wrapAsYamlNode([
+ 1,
+ 2,
+ wrapAsYamlNode([3, 4], collectionStyle: CollectionStyle.FLOW),
+ 5
+ ]));
+
+ expect(doc.toString(), equals('''
+- - 1
+ - 2
+ - [3, 4]
+ - 5'''));
+ expectYamlBuilderValue(doc, [
+ [
+ 1,
+ 2,
+ [3, 4],
+ 5
+ ]
+ ]);
+ });
+
+ test('different scalars in block list!', () {
+ final doc = YamlEditor('- 0');
+ doc.update(
+ [0],
+ wrapAsYamlNode([
+ wrapAsYamlNode('plain string', scalarStyle: ScalarStyle.PLAIN),
+ wrapAsYamlNode('folded string', scalarStyle: ScalarStyle.FOLDED),
+ wrapAsYamlNode('single-quoted string',
+ scalarStyle: ScalarStyle.SINGLE_QUOTED),
+ wrapAsYamlNode('literal string',
+ scalarStyle: ScalarStyle.LITERAL),
+ wrapAsYamlNode('double-quoted string',
+ scalarStyle: ScalarStyle.DOUBLE_QUOTED),
+ ]));
+
+ expect(doc.toString(), equals('''
+- - plain string
+ - >-
+ folded string
+ - 'single-quoted string'
+ - |-
+ literal string
+ - "double-quoted string"'''));
+ expectYamlBuilderValue(doc, [
+ [
+ 'plain string',
+ 'folded string',
+ 'single-quoted string',
+ 'literal string',
+ 'double-quoted string',
+ ]
+ ]);
+ });
+
+ test('different scalars in block map!', () {
+ final doc = YamlEditor('strings: strings');
+ doc.update(
+ ['strings'],
+ wrapAsYamlNode({
+ 'plain': wrapAsYamlNode('string', scalarStyle: ScalarStyle.PLAIN),
+ 'folded':
+ wrapAsYamlNode('string', scalarStyle: ScalarStyle.FOLDED),
+ 'single-quoted': wrapAsYamlNode('string',
+ scalarStyle: ScalarStyle.SINGLE_QUOTED),
+ 'literal':
+ wrapAsYamlNode('string', scalarStyle: ScalarStyle.LITERAL),
+ 'double-quoted': wrapAsYamlNode('string',
+ scalarStyle: ScalarStyle.DOUBLE_QUOTED),
+ }));
+
+ expect(doc.toString(), equals('''
+strings:
+ plain: string
+ folded: >-
+ string
+ single-quoted: 'string'
+ literal: |-
+ string
+ double-quoted: "string"'''));
+ expectYamlBuilderValue(doc, {
+ 'strings': {
+ 'plain': 'string',
+ 'folded': 'string',
+ 'single-quoted': 'string',
+ 'literal': 'string',
+ 'double-quoted': 'string',
+ }
+ });
+ });
+
+ test('different scalars in flow list!', () {
+ final doc = YamlEditor('[0]');
+ doc.update(
+ [0],
+ wrapAsYamlNode([
+ wrapAsYamlNode('plain string', scalarStyle: ScalarStyle.PLAIN),
+ wrapAsYamlNode('folded string', scalarStyle: ScalarStyle.FOLDED),
+ wrapAsYamlNode('single-quoted string',
+ scalarStyle: ScalarStyle.SINGLE_QUOTED),
+ wrapAsYamlNode('literal string',
+ scalarStyle: ScalarStyle.LITERAL),
+ wrapAsYamlNode('double-quoted string',
+ scalarStyle: ScalarStyle.DOUBLE_QUOTED),
+ ]));
+
+ expect(
+ doc.toString(),
+ equals(
+ '[[plain string, folded string, \'single-quoted string\', '
+ 'literal string, "double-quoted string"]]',
+ ),
+ );
+ expectYamlBuilderValue(doc, [
+ [
+ 'plain string',
+ 'folded string',
+ 'single-quoted string',
+ 'literal string',
+ 'double-quoted string',
+ ]
+ ]);
+ });
+
+ test('wraps non-printable strings in double-quotes in flow context', () {
+ final doc = YamlEditor('[0]');
+ doc.update([0], '\x00\x07\x08\x0b\x0c\x0d\x1b\x85\xa0\u2028\u2029"');
+ expect(
+ doc.toString(), equals('["\\0\\a\\b\\v\\f\\r\\e\\N\\_\\L\\P\\""]'));
+ expectYamlBuilderValue(
+ doc, ['\x00\x07\x08\x0b\x0c\x0d\x1b\x85\xa0\u2028\u2029"']);
+ });
+
+ test('wraps non-printable strings in double-quotes in block context', () {
+ final doc = YamlEditor('- 0');
+ doc.update([0], '\x00\x07\x08\x0b\x0c\x0d\x1b\x85\xa0\u2028\u2029"');
+ expect(
+ doc.toString(), equals('- "\\0\\a\\b\\v\\f\\r\\e\\N\\_\\L\\P\\""'));
+ expectYamlBuilderValue(
+ doc, ['\x00\x07\x08\x0b\x0c\x0d\x1b\x85\xa0\u2028\u2029"']);
+ });
+
+ test('generates folded strings properly', () {
+ final doc = YamlEditor('');
+ doc.update(
+ [], wrapAsYamlNode('test\ntest', scalarStyle: ScalarStyle.FOLDED));
+ expect(doc.toString(), equals('>-\n test\n\n test'));
+ });
+
+ test('rewrites folded strings properly', () {
+ final doc = YamlEditor('''
+- >
+ folded string
+''');
+ doc.update(
+ [0], wrapAsYamlNode('test\ntest', scalarStyle: ScalarStyle.FOLDED));
+ expect(doc.toString(), equals('''
+- >-
+ test
+
+ test
+'''));
+ });
+
+ test('rewrites folded strings properly (1)', () {
+ final doc = YamlEditor('''
+- >
+ folded string''');
+ doc.update(
+ [0], wrapAsYamlNode('test\ntest', scalarStyle: ScalarStyle.FOLDED));
+ expect(doc.toString(), equals('''
+- >-
+ test
+
+ test'''));
+ });
+
+ test('generates literal strings properly', () {
+ final doc = YamlEditor('');
+ doc.update(
+ [], wrapAsYamlNode('test\ntest', scalarStyle: ScalarStyle.LITERAL));
+ expect(doc.toString(), equals('|-\n test\n test'));
+ });
+
+ test('rewrites literal strings properly', () {
+ final doc = YamlEditor('''
+- |
+ literal string
+''');
+ doc.update([0],
+ wrapAsYamlNode('test\ntest', scalarStyle: ScalarStyle.LITERAL));
+ expect(doc.toString(), equals('''
+- |-
+ test
+ test
+'''));
+ });
+
+ test('prevents literal strings in flow maps, even if nested', () {
+ final doc = YamlEditor('''
+{1: 1}
+''');
+ doc.update([
+ 1
+ ], [
+ wrapAsYamlNode('d9]zH`FoYC/>]', scalarStyle: ScalarStyle.LITERAL)
+ ]);
+
+ expect(doc.toString(), equals('''
+{1: ["d9]zH`FoYC\\/>]"]}
+'''));
+ expect((doc.parseAt([1, 0]) as YamlScalar).style,
+ equals(ScalarStyle.DOUBLE_QUOTED));
+ });
+
+ test('prevents literal empty strings', () {
+ final doc = YamlEditor('''
+a:
+ c: 1
+''');
+ doc.update([
+ 'a'
+ ], {
+ 'f': wrapAsYamlNode('', scalarStyle: ScalarStyle.LITERAL),
+ 'g': 1
+ });
+
+ expect(doc.toString(), equals('''
+a:
+ f: ""
+ g: 1
+'''));
+ });
+
+ test('prevents literal strings with leading spaces', () {
+ final doc = YamlEditor('''
+a:
+ c: 1
+''');
+ doc.update([
+ 'a'
+ ], {
+ 'f': wrapAsYamlNode(' a', scalarStyle: ScalarStyle.LITERAL),
+ 'g': 1
+ });
+
+ expect(doc.toString(), equals('''
+a:
+ f: " a"
+ g: 1
+'''));
+ });
+
+ test(
+ 'flow collection structure does not get substringed when added to '
+ 'block structure', () {
+ final doc = YamlEditor('''
+a:
+ - false
+''');
+ doc.prependToList(['a'],
+ wrapAsYamlNode([1234], collectionStyle: CollectionStyle.FLOW));
+ expect(doc.toString(), equals('''
+a:
+ - [1234]
+ - false
+'''));
+ expectYamlBuilderValue(doc, {
+ 'a': [
+ [1234],
+ false
+ ]
+ });
+ });
+ });
+ });
+
+ group('assertValidScalar', () {
+ test('does nothing with a boolean', () {
+ expect(() => assertValidScalar(true), returnsNormally);
+ });
+
+ test('does nothing with a number', () {
+ expect(() => assertValidScalar(1.12), returnsNormally);
+ });
+ test('does nothing with infinity', () {
+ expect(() => assertValidScalar(double.infinity), returnsNormally);
+ });
+ test('does nothing with a String', () {
+ expect(() => assertValidScalar('test'), returnsNormally);
+ });
+
+ test('does nothing with null', () {
+ expect(() => assertValidScalar(null), returnsNormally);
+ });
+
+ test('throws on map', () {
+ expect(() => assertValidScalar({'a': 1}), throwsArgumentError);
+ });
+
+ test('throws on list', () {
+ expect(() => assertValidScalar([1]), throwsArgumentError);
+ });
+ });
+}
diff --git a/pkgs/yaml_edit/test/windows_test.dart b/pkgs/yaml_edit/test/windows_test.dart
new file mode 100644
index 0000000..50f79e7
--- /dev/null
+++ b/pkgs/yaml_edit/test/windows_test.dart
@@ -0,0 +1,228 @@
+// Copyright (c) 2020, 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 'package:yaml_edit/src/utils.dart';
+import 'package:yaml_edit/yaml_edit.dart';
+
+import 'test_utils.dart';
+
+void main() {
+ group('windows line ending detection', () {
+ test('empty string gives not windows', () {
+ final doc = YamlEditor('');
+ expect(getLineEnding(doc.toString()), equals('\n'));
+ });
+
+ test('accurately detects windows documents', () {
+ final doc = YamlEditor('\r\n');
+ expect(getLineEnding(doc.toString()), equals('\r\n'));
+ });
+
+ test('accurately detects windows documents (2)', () {
+ final doc = YamlEditor('''
+a:\r
+ b:\r
+ - 1\r
+ - 2\r
+c: 3\r
+''');
+ expect(getLineEnding(doc.toString()), equals('\r\n'));
+ });
+ });
+
+ group('modification with windows line endings', () {
+ test('append element to simple block list ', () {
+ final doc = YamlEditor('''
+- 0\r
+- 1\r
+- 2\r
+- 3\r
+''');
+ doc.appendToList([], [4, 5, 6]);
+ expect(doc.toString(), equals('''
+- 0\r
+- 1\r
+- 2\r
+- 3\r
+- - 4\r
+ - 5\r
+ - 6\r
+'''));
+ expectYamlBuilderValue(doc, [
+ 0,
+ 1,
+ 2,
+ 3,
+ [4, 5, 6]
+ ]);
+ });
+
+ test('update nested scalar -> flow list', () {
+ final doc = YamlEditor('''
+a: 1\r
+b:\r
+ d: 4\r
+ e: 5\r
+c: 3\r
+''');
+ doc.update(['b', 'e'], [1, 2, 3]);
+
+ expect(doc.toString(), equals('''
+a: 1\r
+b:\r
+ d: 4\r
+ e:\r
+ - 1\r
+ - 2\r
+ - 3\r
+c: 3\r
+'''));
+ expectYamlBuilderValue(doc, {
+ 'a': 1,
+ 'b': {
+ 'd': 4,
+ 'e': [1, 2, 3]
+ },
+ 'c': 3
+ });
+ });
+
+ test('update in nested list flow map -> scalar', () {
+ final doc = YamlEditor('''
+- 0\r
+- {a: 1, b: 2}\r
+- 2\r
+- 3\r
+''');
+ doc.update([1], 4);
+ expect(doc.toString(), equals('''
+- 0\r
+- 4\r
+- 2\r
+- 3\r
+'''));
+ expectYamlBuilderValue(doc, [0, 4, 2, 3]);
+ });
+
+ test('insert into a list with comments', () {
+ final doc = YamlEditor('''
+- 0 # comment a\r
+- 2 # comment b\r
+''');
+ doc.insertIntoList([], 1, 1);
+ expect(doc.toString(), equals('''
+- 0 # comment a\r
+- 1\r
+- 2 # comment b\r
+'''));
+ expectYamlBuilderValue(doc, [0, 1, 2]);
+ });
+
+ test('prepend into a list', () {
+ final doc = YamlEditor('''
+- 1\r
+- 2\r
+''');
+ doc.prependToList([], [4, 5, 6]);
+ expect(doc.toString(), equals('''
+- - 4\r
+ - 5\r
+ - 6\r
+- 1\r
+- 2\r
+'''));
+ expectYamlBuilderValue(doc, [
+ [4, 5, 6],
+ 1,
+ 2
+ ]);
+ });
+
+ test('remove from block list ', () {
+ final doc = YamlEditor('''
+- 0\r
+- 1\r
+- 2\r
+- 3\r
+''');
+ doc.remove([1]);
+ expect(doc.toString(), equals('''
+- 0\r
+- 2\r
+- 3\r
+'''));
+ expectYamlBuilderValue(doc, [0, 2, 3]);
+ });
+
+ test('remove from block list (2)', () {
+ final doc = YamlEditor('''
+- 0\r
+''');
+ doc.remove([0]);
+ expect(doc.toString(), equals('''
+[]\r
+'''));
+ expectYamlBuilderValue(doc, []);
+ });
+
+ test('inserted nested map', () {
+ final doc = YamlEditor('''
+a:\r
+ b:\r
+''');
+ doc.update(
+ ['a', 'b'],
+ {
+ 'c': {'d': 'e'}
+ },
+ );
+ expect(doc.toString(), equals('''
+a:\r
+ b:\r
+ c:\r
+ d: e\r
+'''));
+ });
+
+ test('remove from block map', () {
+ final doc = YamlEditor('''
+a: 1\r
+b: 2\r
+c: 3\r
+''');
+ doc.remove(['b']);
+ expect(doc.toString(), equals('''
+a: 1\r
+c: 3\r
+'''));
+ });
+
+ test('remove from block map (2)', () {
+ final doc = YamlEditor('''
+a: 1\r
+''');
+ doc.remove(['a']);
+ expect(doc.toString(), equals('''
+{}\r
+'''));
+ expectYamlBuilderValue(doc, {});
+ });
+
+ test('splice block list', () {
+ final doc = YamlEditor('''
+- 0\r
+- 0\r
+''');
+ final nodes = doc.spliceList([], 0, 2, [0, 1, 2]);
+ expect(doc.toString(), equals('''
+- 0\r
+- 1\r
+- 2\r
+'''));
+
+ expectDeepEquals(nodes.toList(), [0, 0]);
+ });
+ });
+}
diff --git a/pkgs/yaml_edit/test/wrap_test.dart b/pkgs/yaml_edit/test/wrap_test.dart
new file mode 100644
index 0000000..60237b9
--- /dev/null
+++ b/pkgs/yaml_edit/test/wrap_test.dart
@@ -0,0 +1,373 @@
+// Copyright (c) 2020, 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: avoid_dynamic_calls
+
+import 'dart:io';
+
+import 'package:test/test.dart';
+import 'package:yaml/yaml.dart';
+import 'package:yaml_edit/src/equality.dart';
+import 'package:yaml_edit/src/wrap.dart';
+
+import 'test_utils.dart';
+
+void main() {
+ group('wrapAsYamlNode', () {
+ group('checks for invalid scalars', () {
+ test('fails to wrap an invalid scalar', () {
+ expect(() => wrapAsYamlNode(File('test.dart')), throwsArgumentError);
+ });
+
+ test('fails to wrap an invalid map', () {
+ expect(() => wrapAsYamlNode({'a': File('test.dart')}),
+ throwsArgumentError);
+ });
+
+ test('fails to wrap an invalid list', () {
+ expect(
+ () => wrapAsYamlNode([
+ 'a',
+ [File('test.dart')]
+ ]),
+ throwsArgumentError);
+ });
+
+ test('checks YamlScalar for invalid scalar value', () {
+ expect(() => wrapAsYamlNode(YamlScalar.wrap(File('test.dart'))),
+ throwsArgumentError);
+ });
+
+ test('checks YamlMap for deep invalid scalar value', () {
+ expect(
+ () => wrapAsYamlNode(YamlMap.wrap({
+ 'a': {'b': File('test.dart')}
+ })),
+ throwsArgumentError);
+ });
+
+ test('checks YamlList for deep invalid scalar value', () {
+ expect(
+ () => wrapAsYamlNode(YamlList.wrap([
+ 'a',
+ [File('test.dart')]
+ ])),
+ throwsArgumentError);
+ });
+ });
+
+ test('wraps scalars', () {
+ final scalar = wrapAsYamlNode('foo');
+
+ expect((scalar as YamlScalar).style, equals(ScalarStyle.ANY));
+ expect(scalar.value, equals('foo'));
+ });
+
+ test('wraps scalars with style', () {
+ final scalar =
+ wrapAsYamlNode('foo', scalarStyle: ScalarStyle.DOUBLE_QUOTED);
+
+ expect((scalar as YamlScalar).style, equals(ScalarStyle.DOUBLE_QUOTED));
+ expect(scalar.value, equals('foo'));
+ });
+
+ test('wraps lists', () {
+ final list = wrapAsYamlNode([
+ [1, 2, 3],
+ {
+ 'foo': 'bar',
+ 'nested': [4, 5, 6]
+ },
+ 'value'
+ ]);
+
+ expect(
+ list,
+ equals([
+ [1, 2, 3],
+ {
+ 'foo': 'bar',
+ 'nested': [4, 5, 6]
+ },
+ 'value'
+ ]));
+ expect((list as YamlList).style, equals(CollectionStyle.ANY));
+ expect(list[0].style, equals(CollectionStyle.ANY));
+ expect(list[1].style, equals(CollectionStyle.ANY));
+ });
+
+ test('wraps lists with collectionStyle', () {
+ final list = wrapAsYamlNode([
+ [1, 2, 3],
+ {
+ 'foo': 'bar',
+ 'nested': [4, 5, 6]
+ },
+ 'value'
+ ], collectionStyle: CollectionStyle.BLOCK);
+
+ expect((list as YamlList).style, equals(CollectionStyle.BLOCK));
+ expect(list[0].style, equals(CollectionStyle.BLOCK));
+ expect(list[1].style, equals(CollectionStyle.BLOCK));
+ });
+
+ test('wraps nested lists while preserving style', () {
+ final list = wrapAsYamlNode([
+ wrapAsYamlNode([1, 2, 3], collectionStyle: CollectionStyle.FLOW),
+ wrapAsYamlNode({
+ 'foo': 'bar',
+ 'nested': [4, 5, 6]
+ }, collectionStyle: CollectionStyle.FLOW),
+ 'value'
+ ], collectionStyle: CollectionStyle.BLOCK);
+
+ expect((list as YamlList).style, equals(CollectionStyle.BLOCK));
+ expect(list[0].style, equals(CollectionStyle.FLOW));
+ expect(list[1].style, equals(CollectionStyle.FLOW));
+ });
+
+ test('wraps maps', () {
+ final map = wrapAsYamlNode({
+ 'list': [1, 2, 3],
+ 'map': {
+ 'foo': 'bar',
+ 'nested': [4, 5, 6]
+ },
+ 'scalar': 'value'
+ });
+
+ expect(
+ map,
+ equals({
+ 'list': [1, 2, 3],
+ 'map': {
+ 'foo': 'bar',
+ 'nested': [4, 5, 6]
+ },
+ 'scalar': 'value'
+ }));
+
+ expect((map as YamlMap).style, equals(CollectionStyle.ANY));
+ });
+
+ test('wraps maps with collectionStyle', () {
+ final map = wrapAsYamlNode({
+ 'list': [1, 2, 3],
+ 'map': {
+ 'foo': 'bar',
+ 'nested': [4, 5, 6]
+ },
+ 'scalar': 'value'
+ }, collectionStyle: CollectionStyle.BLOCK);
+
+ expect((map as YamlMap).style, equals(CollectionStyle.BLOCK));
+ });
+
+ test('wraps nested maps while preserving style', () {
+ final map = wrapAsYamlNode({
+ 'list':
+ wrapAsYamlNode([1, 2, 3], collectionStyle: CollectionStyle.FLOW),
+ 'map': wrapAsYamlNode({
+ 'foo': 'bar',
+ 'nested': [4, 5, 6]
+ }, collectionStyle: CollectionStyle.BLOCK),
+ 'scalar': 'value'
+ }, collectionStyle: CollectionStyle.BLOCK);
+
+ expect((map as YamlMap).style, equals(CollectionStyle.BLOCK));
+ expect(map['list'].style, equals(CollectionStyle.FLOW));
+ expect(map['map'].style, equals(CollectionStyle.BLOCK));
+ });
+
+ test('works with YamlMap.wrap', () {
+ final map = wrapAsYamlNode({
+ 'list':
+ wrapAsYamlNode([1, 2, 3], collectionStyle: CollectionStyle.FLOW),
+ 'map': YamlMap.wrap({
+ 'foo': 'bar',
+ 'nested': [4, 5, 6]
+ }),
+ }, collectionStyle: CollectionStyle.BLOCK);
+
+ expect((map as YamlMap).style, equals(CollectionStyle.BLOCK));
+ expect(map['list'].style, equals(CollectionStyle.FLOW));
+ expect(map['map'].style, equals(CollectionStyle.ANY));
+ });
+ });
+
+ test('applies collectionStyle recursively', () {
+ final list = wrapAsYamlNode([
+ [1, 2, 3],
+ {
+ 'foo': 'bar',
+ 'nested': [4, 5, 6]
+ },
+ ], collectionStyle: CollectionStyle.BLOCK);
+
+ expect((list as YamlList).style, equals(CollectionStyle.BLOCK));
+ expect(list[0].style, equals(CollectionStyle.BLOCK));
+ expect(list[1].style, equals(CollectionStyle.BLOCK));
+ expect(list[1]['nested'].style, equals(CollectionStyle.BLOCK));
+ });
+
+ test('applies scalarStyle recursively', () {
+ final list = wrapAsYamlNode([
+ ['a', 'b', 'c'],
+ {
+ 'foo': 'bar',
+ },
+ 'hello',
+ ], scalarStyle: ScalarStyle.SINGLE_QUOTED);
+
+ expect((list as YamlList).style, equals(CollectionStyle.ANY));
+ final item1 = list.nodes[0] as YamlList;
+ final item2 = list.nodes[1] as YamlMap;
+ final item3 = list.nodes[2] as YamlScalar;
+ expect(item1.style, equals(CollectionStyle.ANY));
+ expect(item2.style, equals(CollectionStyle.ANY));
+ expect(item3.style, equals(ScalarStyle.SINGLE_QUOTED));
+
+ final item1entry1 = item1.nodes[0] as YamlScalar;
+ expect(item1entry1.style, equals(ScalarStyle.SINGLE_QUOTED));
+
+ final item2foo = item2.nodes['foo'] as YamlScalar;
+ expect(item2foo.style, equals(ScalarStyle.SINGLE_QUOTED));
+ });
+
+ group('deepHashCode', () {
+ test('returns the same result for scalar and its value', () {
+ final hashCode1 = deepHashCode('foo');
+ final hashCode2 = deepHashCode(wrapAsYamlNode('foo'));
+
+ expect(hashCode1, equals(hashCode2));
+ });
+
+ test('returns different results for different values', () {
+ final hashCode1 = deepHashCode('foo');
+ final hashCode2 = deepHashCode(wrapAsYamlNode('bar'));
+
+ expect(hashCode1, notEquals(hashCode2));
+ });
+
+ test('returns the same result for YamlScalar with style and its value', () {
+ final hashCode1 = deepHashCode('foo');
+ final hashCode2 =
+ deepHashCode(wrapAsYamlNode('foo', scalarStyle: ScalarStyle.LITERAL));
+
+ expect(hashCode1, equals(hashCode2));
+ });
+
+ test(
+ 'returns the same result for two YamlScalars with same value but '
+ 'different styles', () {
+ final hashCode1 =
+ deepHashCode(wrapAsYamlNode('foo', scalarStyle: ScalarStyle.PLAIN));
+ final hashCode2 =
+ deepHashCode(wrapAsYamlNode('foo', scalarStyle: ScalarStyle.LITERAL));
+
+ expect(hashCode1, equals(hashCode2));
+ });
+
+ test('returns the same result for list and its value', () {
+ final hashCode1 = deepHashCode([1, 2, 3]);
+ final hashCode2 = deepHashCode(wrapAsYamlNode([1, 2, 3]));
+
+ expect(hashCode1, equals(hashCode2));
+ });
+
+ test('returns the same result for list and the YamlList.wrap() value', () {
+ final hashCode1 = deepHashCode([
+ 1,
+ [1, 2],
+ 3
+ ]);
+ final hashCode2 = deepHashCode(YamlList.wrap([
+ 1,
+ YamlList.wrap([1, 2]),
+ 3
+ ]));
+
+ expect(hashCode1, equals(hashCode2));
+ });
+
+ test('returns the different results for different lists', () {
+ final hashCode1 = deepHashCode([1, 2, 3]);
+ final hashCode2 = deepHashCode([1, 2, 4]);
+ final hashCode3 = deepHashCode([1, 2, 3, 4]);
+
+ expect(hashCode1, notEquals(hashCode2));
+ expect(hashCode2, notEquals(hashCode3));
+ expect(hashCode3, notEquals(hashCode1));
+ });
+
+ test('returns the same result for YamlList with style and its value', () {
+ final hashCode1 = deepHashCode([1, 2, 3]);
+ final hashCode2 = deepHashCode(
+ wrapAsYamlNode([1, 2, 3], collectionStyle: CollectionStyle.FLOW));
+
+ expect(hashCode1, equals(hashCode2));
+ });
+
+ test(
+ 'returns the same result for two YamlLists with same value but '
+ 'different styles', () {
+ final hashCode1 = deepHashCode(
+ wrapAsYamlNode([1, 2, 3], collectionStyle: CollectionStyle.BLOCK));
+ final hashCode2 = deepHashCode(wrapAsYamlNode([1, 2, 3]));
+
+ expect(hashCode1, equals(hashCode2));
+ });
+
+ test('returns the same result for a map and its value', () {
+ final hashCode1 = deepHashCode({'a': 1, 'b': 2});
+ final hashCode2 = deepHashCode(wrapAsYamlNode({'a': 1, 'b': 2}));
+
+ expect(hashCode1, equals(hashCode2));
+ });
+
+ test('returns the same result for list and the YamlList.wrap() value', () {
+ final hashCode1 = deepHashCode({
+ 'a': 1,
+ 'b': 2,
+ 'c': {'d': 4, 'e': 5}
+ });
+ final hashCode2 = deepHashCode(YamlMap.wrap({
+ 'a': 1,
+ 'b': 2,
+ 'c': YamlMap.wrap({'d': 4, 'e': 5})
+ }));
+
+ expect(hashCode1, equals(hashCode2));
+ });
+
+ test('returns the different results for different maps', () {
+ final hashCode1 = deepHashCode({'a': 1, 'b': 2});
+ final hashCode2 = deepHashCode({'a': 1, 'b': 3});
+ final hashCode3 = deepHashCode({'a': 1, 'b': 2, 'c': 3});
+
+ expect(hashCode1, notEquals(hashCode2));
+ expect(hashCode2, notEquals(hashCode3));
+ expect(hashCode3, notEquals(hashCode1));
+ });
+
+ test('returns the same result for YamlMap with style and its value', () {
+ final hashCode1 = deepHashCode({'a': 1, 'b': 2});
+ final hashCode2 = deepHashCode(wrapAsYamlNode({'a': 1, 'b': 2},
+ collectionStyle: CollectionStyle.FLOW));
+
+ expect(hashCode1, equals(hashCode2));
+ });
+
+ test(
+ 'returns the same result for two YamlMaps with same value but '
+ 'different styles', () {
+ final hashCode1 = deepHashCode(wrapAsYamlNode({'a': 1, 'b': 2},
+ collectionStyle: CollectionStyle.BLOCK));
+ final hashCode2 = deepHashCode(wrapAsYamlNode({'a': 1, 'b': 2},
+ collectionStyle: CollectionStyle.FLOW));
+
+ expect(hashCode1, equals(hashCode2));
+ });
+ });
+}
diff --git a/tool/pubspec.yaml b/tool/pubspec.yaml
new file mode 100644
index 0000000..1517197
--- /dev/null
+++ b/tool/pubspec.yaml
@@ -0,0 +1,9 @@
+name: readme_update
+publish_to: none
+environment:
+ sdk: ^3.6.0
+
+dependencies:
+ path: ^1.9.1
+ pubspec_parse: ^1.5.0
+ yaml: ^3.1.3
diff --git a/tool/readme_update.dart b/tool/readme_update.dart
new file mode 100644
index 0000000..fd086d9
--- /dev/null
+++ b/tool/readme_update.dart
@@ -0,0 +1,76 @@
+// 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:collection';
+import 'dart:io';
+
+import 'package:path/path.dart' as p;
+import 'package:pubspec_parse/pubspec_parse.dart';
+import 'package:yaml/yaml.dart';
+
+Future<void> main(List<String> args) async {
+ // assume we're being run from the root of the `tools` directory
+ final descriptions = SplayTreeMap<String, String>();
+
+ // * Enumerate all packages in `pkgs/`
+ for (var directory in Directory(p.join(Directory.current.path, 'pkgs'))
+ .listSync()
+ .whereType<Directory>()) {
+ final pubspecFile = File(p.join(directory.path, 'pubspec.yaml'));
+ final pubspec = Pubspec.parse(pubspecFile.readAsStringSync(),
+ sourceUrl: pubspecFile.uri);
+
+ assert(p.basename(directory.path) == pubspec.name);
+
+ // * Grab the `description` field from their pubspec files
+ descriptions[pubspec.name] = pubspec.description!;
+ }
+
+ // * Ensure all packages have a file in `.github/workflows`
+ for (var entry in descriptions.entries) {
+ final workflowFile = File(p.join('.github/workflows', '${entry.key}.yaml'));
+
+ final workflowYaml =
+ loadYaml(workflowFile.readAsStringSync(), sourceUrl: workflowFile.uri)
+ as YamlMap;
+
+ final workflowName = workflowYaml['name'] as String;
+ // * Ensure each has a name `package:[pkg name]`
+ assert(workflowName == 'package:${entry.key}');
+ }
+
+ // * Print out the readme table!
+
+ print('''
+| Package | Description | Issues | Version |
+| --- | --- | --- | --- |''');
+
+ for (var entry in descriptions.entries) {
+ final pkgName = entry.key;
+ final name = '[$pkgName](pkgs/$pkgName/)';
+
+ // [][bazel_worker_issues]
+ // [](https://pub.dev/packages/bazel_worker) |
+
+ final issues =
+ '[][${pkgName}_issues]';
+ final version =
+ '[](https://pub.dev/packages/$pkgName)';
+
+ print(['', name, entry.value, issues, version, ''].join(' | ').trim());
+ }
+
+ print('');
+
+ for (var entry in descriptions.entries) {
+ final pkgName = entry.key;
+
+ // [bazel_worker_issues]: https://github.com/dart-lang/core/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Abazel_worker
+
+ print('[${pkgName}_issues]: '
+ 'https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3A$pkgName');
+ }
+
+ print('');
+}